summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.mailmap43
-rw-r--r--AUTHORS144
-rw-r--r--CHANGELOG.md1491
-rw-r--r--LICENSE339
-rw-r--r--README.md56
-rw-r--r--VERSION1
-rw-r--r--application/VERSION1
-rw-r--r--application/clicommands/AutocompleteCommand.php120
-rw-r--r--application/clicommands/HelpCommand.php43
-rw-r--r--application/clicommands/ModuleCommand.php228
-rw-r--r--application/clicommands/VersionCommand.php55
-rw-r--r--application/clicommands/WebCommand.php101
-rw-r--r--application/controllers/AboutController.php27
-rw-r--r--application/controllers/AccountController.php83
-rw-r--r--application/controllers/AnnouncementsController.php123
-rw-r--r--application/controllers/ApplicationStateController.php95
-rw-r--r--application/controllers/AuthenticationController.php127
-rw-r--r--application/controllers/ConfigController.php507
-rw-r--r--application/controllers/DashboardController.php346
-rw-r--r--application/controllers/ErrorController.php158
-rw-r--r--application/controllers/GroupController.php418
-rw-r--r--application/controllers/HealthController.php65
-rw-r--r--application/controllers/IframeController.php20
-rw-r--r--application/controllers/IndexController.php31
-rw-r--r--application/controllers/LayoutController.php28
-rw-r--r--application/controllers/ListController.php59
-rw-r--r--application/controllers/ManageUserDevicesController.php84
-rw-r--r--application/controllers/MyDevicesController.php74
-rw-r--r--application/controllers/NavigationController.php447
-rw-r--r--application/controllers/RoleController.php392
-rw-r--r--application/controllers/SearchController.php28
-rw-r--r--application/controllers/StaticController.php114
-rw-r--r--application/controllers/UserController.php374
-rw-r--r--application/controllers/UsergroupbackendController.php133
-rw-r--r--application/fonts/fontello-ifont/LICENSE.txt57
-rw-r--r--application/fonts/fontello-ifont/README.txt75
-rw-r--r--application/fonts/fontello-ifont/config.json874
-rw-r--r--application/fonts/fontello-ifont/css/animation.css85
-rw-r--r--application/fonts/fontello-ifont/css/ifont-codes.css145
-rw-r--r--application/fonts/fontello-ifont/css/ifont-embedded.css198
-rw-r--r--application/fonts/fontello-ifont/css/ifont-ie7-codes.css145
-rw-r--r--application/fonts/fontello-ifont/css/ifont-ie7.css156
-rw-r--r--application/fonts/fontello-ifont/css/ifont.css201
-rw-r--r--application/fonts/fontello-ifont/demo.html519
-rw-r--r--application/fonts/fontello-ifont/font/ifont.eotbin0 -> 46504 bytes
-rw-r--r--application/fonts/fontello-ifont/font/ifont.svg298
-rw-r--r--application/fonts/fontello-ifont/font/ifont.ttfbin0 -> 46348 bytes
-rw-r--r--application/fonts/fontello-ifont/font/ifont.woffbin0 -> 27688 bytes
-rw-r--r--application/fonts/fontello-ifont/font/ifont.woff2bin0 -> 22984 bytes
-rw-r--r--application/fonts/icingaweb.md9
-rw-r--r--application/forms/Account/ChangePasswordForm.php123
-rw-r--r--application/forms/AcknowledgeApplicationStateMessageForm.php75
-rw-r--r--application/forms/ActionForm.php78
-rw-r--r--application/forms/Announcement/AcknowledgeAnnouncementForm.php92
-rw-r--r--application/forms/Announcement/AnnouncementForm.php135
-rw-r--r--application/forms/Authentication/LoginForm.php214
-rw-r--r--application/forms/AutoRefreshForm.php83
-rw-r--r--application/forms/Config/General/ApplicationConfigForm.php93
-rw-r--r--application/forms/Config/General/DefaultAuthenticationDomainConfigForm.php46
-rw-r--r--application/forms/Config/General/LoggingConfigForm.php142
-rw-r--r--application/forms/Config/General/ThemingConfigForm.php78
-rw-r--r--application/forms/Config/GeneralConfigForm.php40
-rw-r--r--application/forms/Config/Resource/DbResourceForm.php238
-rw-r--r--application/forms/Config/Resource/FileResourceForm.php67
-rw-r--r--application/forms/Config/Resource/LdapResourceForm.php129
-rw-r--r--application/forms/Config/Resource/SshResourceForm.php148
-rw-r--r--application/forms/Config/ResourceConfigForm.php441
-rw-r--r--application/forms/Config/User/CreateMembershipForm.php191
-rw-r--r--application/forms/Config/User/UserForm.php210
-rw-r--r--application/forms/Config/UserBackend/DbBackendForm.php82
-rw-r--r--application/forms/Config/UserBackend/ExternalBackendForm.php83
-rw-r--r--application/forms/Config/UserBackend/LdapBackendForm.php414
-rw-r--r--application/forms/Config/UserBackendConfigForm.php482
-rw-r--r--application/forms/Config/UserBackendReorderForm.php86
-rw-r--r--application/forms/Config/UserGroup/AddMemberForm.php182
-rw-r--r--application/forms/Config/UserGroup/DbUserGroupBackendForm.php79
-rw-r--r--application/forms/Config/UserGroup/LdapUserGroupBackendForm.php370
-rw-r--r--application/forms/Config/UserGroup/UserGroupBackendForm.php314
-rw-r--r--application/forms/Config/UserGroup/UserGroupForm.php158
-rw-r--r--application/forms/ConfigForm.php192
-rw-r--r--application/forms/ConfirmRemovalForm.php38
-rw-r--r--application/forms/Control/LimiterControlForm.php134
-rw-r--r--application/forms/Dashboard/DashletForm.php171
-rw-r--r--application/forms/LdapDiscoveryForm.php34
-rw-r--r--application/forms/Navigation/DashletForm.php35
-rw-r--r--application/forms/Navigation/MenuItemForm.php31
-rw-r--r--application/forms/Navigation/NavigationConfigForm.php852
-rw-r--r--application/forms/Navigation/NavigationItemForm.php114
-rw-r--r--application/forms/PreferenceForm.php485
-rw-r--r--application/forms/RepositoryForm.php453
-rw-r--r--application/forms/Security/RoleForm.php624
-rw-r--r--application/layouts/scripts/body.phtml98
-rw-r--r--application/layouts/scripts/external-logout.phtml34
-rw-r--r--application/layouts/scripts/guest-error.phtml10
-rw-r--r--application/layouts/scripts/inline.phtml2
-rw-r--r--application/layouts/scripts/layout.phtml107
-rw-r--r--application/layouts/scripts/parts/navigation.phtml35
-rw-r--r--application/layouts/scripts/pdf.phtml44
-rw-r--r--application/views/helpers/CreateTicketLinks.php23
-rw-r--r--application/views/helpers/FormDate.php46
-rw-r--r--application/views/helpers/FormDateTime.php63
-rw-r--r--application/views/helpers/FormNumber.php77
-rw-r--r--application/views/helpers/FormTime.php46
-rw-r--r--application/views/helpers/ProtectId.php13
-rw-r--r--application/views/helpers/Util.php68
-rw-r--r--application/views/scripts/about/index.phtml171
-rw-r--r--application/views/scripts/account/index.phtml11
-rw-r--r--application/views/scripts/announcements/index.phtml71
-rw-r--r--application/views/scripts/authentication/login.phtml74
-rw-r--r--application/views/scripts/authentication/logout.phtml79
-rw-r--r--application/views/scripts/config/devtools.phtml6
-rw-r--r--application/views/scripts/config/general.phtml6
-rw-r--r--application/views/scripts/config/module-configuration-error.phtml28
-rw-r--r--application/views/scripts/config/module.phtml136
-rw-r--r--application/views/scripts/config/modules.phtml42
-rw-r--r--application/views/scripts/config/resource.phtml73
-rw-r--r--application/views/scripts/config/resource/create.phtml6
-rw-r--r--application/views/scripts/config/resource/modify.phtml6
-rw-r--r--application/views/scripts/config/resource/remove.phtml6
-rw-r--r--application/views/scripts/config/userbackend/reorder.phtml75
-rw-r--r--application/views/scripts/dashboard/error.phtml13
-rw-r--r--application/views/scripts/dashboard/index.phtml26
-rw-r--r--application/views/scripts/dashboard/new-dashlet.phtml6
-rw-r--r--application/views/scripts/dashboard/remove-dashlet.phtml6
-rw-r--r--application/views/scripts/dashboard/remove-pane.phtml6
-rw-r--r--application/views/scripts/dashboard/rename-pane.phtml6
-rw-r--r--application/views/scripts/dashboard/settings.phtml91
-rw-r--r--application/views/scripts/dashboard/update-dashlet.phtml6
-rw-r--r--application/views/scripts/error/error.phtml106
-rw-r--r--application/views/scripts/filter/index.phtml11
-rw-r--r--application/views/scripts/form/reorder-authbackend.phtml83
-rw-r--r--application/views/scripts/group/form.phtml6
-rw-r--r--application/views/scripts/group/list.phtml96
-rw-r--r--application/views/scripts/group/show.phtml108
-rw-r--r--application/views/scripts/iframe/index.phtml8
-rw-r--r--application/views/scripts/index/welcome.phtml2
-rw-r--r--application/views/scripts/inline.phtml2
-rw-r--r--application/views/scripts/joystickPagination.phtml162
-rw-r--r--application/views/scripts/layout/announcements.phtml1
-rw-r--r--application/views/scripts/layout/menu.phtml20
-rw-r--r--application/views/scripts/list/applicationlog.phtml29
-rw-r--r--application/views/scripts/mixedPagination.phtml79
-rw-r--r--application/views/scripts/navigation/dashboard.phtml27
-rw-r--r--application/views/scripts/navigation/index.phtml78
-rw-r--r--application/views/scripts/navigation/shared.phtml68
-rw-r--r--application/views/scripts/pivottablePagination.phtml48
-rw-r--r--application/views/scripts/role/list.phtml65
-rw-r--r--application/views/scripts/search/hint.phtml8
-rw-r--r--application/views/scripts/search/index.phtml7
-rw-r--r--application/views/scripts/showConfiguration.phtml27
-rw-r--r--application/views/scripts/simple-form.phtml6
-rw-r--r--application/views/scripts/user/form.phtml6
-rw-r--r--application/views/scripts/user/list.phtml90
-rw-r--r--application/views/scripts/user/show.phtml138
-rwxr-xr-xbin/icingacli7
-rw-r--r--doc/01-About.md93
-rw-r--r--doc/02-Installation.md537
-rw-r--r--doc/02-Installation.md.d/01-Debian.md3
-rw-r--r--doc/02-Installation.md.d/02-Ubuntu.md3
-rw-r--r--doc/02-Installation.md.d/03-CentOS.md3
-rw-r--r--doc/02-Installation.md.d/04-RHEL.md3
-rw-r--r--doc/02-Installation.md.d/05-SLES.md3
-rw-r--r--doc/02-Installation.md.d/06-Amazon-Linux.md3
-rw-r--r--doc/02-Installation.md.d/07-From-Source.md3
-rw-r--r--doc/03-Configuration.md75
-rw-r--r--doc/04-Resources.md136
-rw-r--r--doc/05-Authentication.md293
-rw-r--r--doc/06-Security.md242
-rw-r--r--doc/07-Preferences.md21
-rw-r--r--doc/08-Modules.md69
-rw-r--r--doc/15-Auditing.md14
-rw-r--r--doc/20-Advanced-Topics.md380
-rw-r--r--doc/60-Hooks.md49
-rw-r--r--doc/70-Troubleshooting.md17
-rw-r--r--doc/80-Upgrading.md424
-rw-r--r--doc/90-SELinux.md76
-rw-r--r--doc/accessibility/ifont-mute.html21
-rw-r--r--doc/accessibility/ifont.html18
-rw-r--r--doc/accessibility/link-labels.html15
-rw-r--r--doc/accessibility/required-form-elements.html19
-rw-r--r--doc/accessibility/skip-content.html179
-rw-r--r--doc/accessibility/svg.html19
-rw-r--r--doc/accessibility/text-cue-for-required-form-control-labels.html36
-rw-r--r--doc/phpdoc.xml26
-rw-r--r--doc/res/GraphExample#1.pngbin0 -> 25851 bytes
-rw-r--r--doc/res/GraphExample#2.pngbin0 -> 23514 bytes
-rw-r--r--doc/res/GraphExample#3.pngbin0 -> 19380 bytes
-rw-r--r--doc/res/GraphExample#4.pngbin0 -> 35522 bytes
-rw-r--r--doc/res/GraphExample#5.pngbin0 -> 31663 bytes
-rw-r--r--doc/res/GraphExample#6.pngbin0 -> 59658 bytes
-rw-r--r--doc/res/GraphExample#7.1.pngbin0 -> 7438 bytes
-rw-r--r--doc/res/GraphExample#7.pngbin0 -> 34631 bytes
-rw-r--r--doc/res/GraphExample#8.pngbin0 -> 24815 bytes
-rw-r--r--doc/res/GraphExample#9.pngbin0 -> 37848 bytes
-rw-r--r--doc/res/gitlab-job-artifacts.pngbin0 -> 9523 bytes
-rw-r--r--doc/res/gitlab-rpm-package-pipeline-jobs.pngbin0 -> 52427 bytes
-rw-r--r--doc/res/monitoring-module-preview.pngbin0 -> 305002 bytes
-rw-r--r--etc/bash_completion.d/icingacli10
-rw-r--r--icingaweb2.ruleset.xml51
-rw-r--r--library/Icinga/Application/ApplicationBootstrap.php758
-rw-r--r--library/Icinga/Application/Benchmark.php299
-rw-r--r--library/Icinga/Application/ClassLoader.php334
-rw-r--r--library/Icinga/Application/Cli.php210
-rw-r--r--library/Icinga/Application/Config.php497
-rw-r--r--library/Icinga/Application/EmbeddedWeb.php114
-rw-r--r--library/Icinga/Application/Hook.php330
-rw-r--r--library/Icinga/Application/Hook/ApplicationStateHook.php90
-rw-r--r--library/Icinga/Application/Hook/AuditHook.php123
-rw-r--r--library/Icinga/Application/Hook/AuthenticationHook.php75
-rw-r--r--library/Icinga/Application/Hook/ConfigFormEventsHook.php137
-rw-r--r--library/Icinga/Application/Hook/GrapherHook.php111
-rw-r--r--library/Icinga/Application/Hook/HealthHook.php222
-rw-r--r--library/Icinga/Application/Hook/PdfexportHook.php25
-rw-r--r--library/Icinga/Application/Hook/ThemeLoaderHook.php22
-rw-r--r--library/Icinga/Application/Hook/Ticket/TicketPattern.php140
-rw-r--r--library/Icinga/Application/Hook/TicketHook.php210
-rw-r--r--library/Icinga/Application/Hook/WebBaseHook.php54
-rw-r--r--library/Icinga/Application/Icinga.php49
-rw-r--r--library/Icinga/Application/LegacyWeb.php31
-rw-r--r--library/Icinga/Application/Libraries.php91
-rw-r--r--library/Icinga/Application/Libraries/Library.php259
-rw-r--r--library/Icinga/Application/Logger.php349
-rw-r--r--library/Icinga/Application/Logger/LogWriter.php30
-rw-r--r--library/Icinga/Application/Logger/Writer/FileWriter.php80
-rw-r--r--library/Icinga/Application/Logger/Writer/PhpWriter.php39
-rw-r--r--library/Icinga/Application/Logger/Writer/StderrWriter.php61
-rw-r--r--library/Icinga/Application/Logger/Writer/StdoutWriter.php13
-rw-r--r--library/Icinga/Application/Logger/Writer/SyslogWriter.php90
-rw-r--r--library/Icinga/Application/Modules/DashboardContainer.php58
-rw-r--r--library/Icinga/Application/Modules/Manager.php698
-rw-r--r--library/Icinga/Application/Modules/MenuItemContainer.php55
-rw-r--r--library/Icinga/Application/Modules/Module.php1444
-rw-r--r--library/Icinga/Application/Modules/NavigationItemContainer.php117
-rw-r--r--library/Icinga/Application/Platform.php435
-rw-r--r--library/Icinga/Application/StaticWeb.php21
-rw-r--r--library/Icinga/Application/Version.php65
-rw-r--r--library/Icinga/Application/Web.php505
-rw-r--r--library/Icinga/Application/functions.php110
-rw-r--r--library/Icinga/Application/webrouter.php106
-rw-r--r--library/Icinga/Authentication/AdmissionLoader.php249
-rw-r--r--library/Icinga/Authentication/Auth.php453
-rw-r--r--library/Icinga/Authentication/AuthChain.php269
-rw-r--r--library/Icinga/Authentication/Authenticatable.php21
-rw-r--r--library/Icinga/Authentication/Role.php334
-rw-r--r--library/Icinga/Authentication/RolesConfig.php43
-rw-r--r--library/Icinga/Authentication/User/DbUserBackend.php256
-rw-r--r--library/Icinga/Authentication/User/DomainAwareInterface.php17
-rw-r--r--library/Icinga/Authentication/User/ExternalBackend.php124
-rw-r--r--library/Icinga/Authentication/User/LdapUserBackend.php477
-rw-r--r--library/Icinga/Authentication/User/UserBackend.php257
-rw-r--r--library/Icinga/Authentication/User/UserBackendInterface.php39
-rw-r--r--library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php325
-rw-r--r--library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php944
-rw-r--r--library/Icinga/Authentication/UserGroup/UserGroupBackend.php188
-rw-r--r--library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php56
-rw-r--r--library/Icinga/Chart/Axis.php485
-rw-r--r--library/Icinga/Chart/Chart.php162
-rw-r--r--library/Icinga/Chart/Donut.php465
-rw-r--r--library/Icinga/Chart/Format.php21
-rw-r--r--library/Icinga/Chart/Graph/BarGraph.php162
-rw-r--r--library/Icinga/Chart/Graph/LineGraph.php195
-rw-r--r--library/Icinga/Chart/Graph/StackedGraph.php88
-rw-r--r--library/Icinga/Chart/Graph/Tooltip.php143
-rw-r--r--library/Icinga/Chart/GridChart.php446
-rw-r--r--library/Icinga/Chart/Inline/Inline.php96
-rw-r--r--library/Icinga/Chart/Inline/PieChart.php41
-rw-r--r--library/Icinga/Chart/Legend.php102
-rw-r--r--library/Icinga/Chart/Palette.php65
-rw-r--r--library/Icinga/Chart/PieChart.php306
-rw-r--r--library/Icinga/Chart/Primitive/Animatable.php43
-rw-r--r--library/Icinga/Chart/Primitive/Animation.php87
-rw-r--r--library/Icinga/Chart/Primitive/Canvas.php140
-rw-r--r--library/Icinga/Chart/Primitive/Circle.php68
-rw-r--r--library/Icinga/Chart/Primitive/Drawable.php22
-rw-r--r--library/Icinga/Chart/Primitive/Line.php87
-rw-r--r--library/Icinga/Chart/Primitive/Path.php174
-rw-r--r--library/Icinga/Chart/Primitive/PieSlice.php293
-rw-r--r--library/Icinga/Chart/Primitive/RawElement.php43
-rw-r--r--library/Icinga/Chart/Primitive/Rect.php105
-rw-r--r--library/Icinga/Chart/Primitive/Styleable.php154
-rw-r--r--library/Icinga/Chart/Primitive/Text.php168
-rw-r--r--library/Icinga/Chart/Render/LayoutBox.php200
-rw-r--r--library/Icinga/Chart/Render/RenderContext.php225
-rw-r--r--library/Icinga/Chart/Render/Rotator.php80
-rw-r--r--library/Icinga/Chart/SVGRenderer.php331
-rw-r--r--library/Icinga/Chart/Unit/AxisUnit.php56
-rw-r--r--library/Icinga/Chart/Unit/CalendarUnit.php167
-rw-r--r--library/Icinga/Chart/Unit/LinearUnit.php227
-rw-r--r--library/Icinga/Chart/Unit/LogarithmicUnit.php263
-rw-r--r--library/Icinga/Chart/Unit/StaticAxis.php129
-rw-r--r--library/Icinga/Cli/AnsiScreen.php122
-rw-r--r--library/Icinga/Cli/Command.php208
-rw-r--r--library/Icinga/Cli/Documentation.php162
-rw-r--r--library/Icinga/Cli/Documentation/CommentParser.php85
-rw-r--r--library/Icinga/Cli/Loader.php499
-rw-r--r--library/Icinga/Cli/Params.php320
-rw-r--r--library/Icinga/Cli/Screen.php106
-rw-r--r--library/Icinga/Common/Database.php56
-rw-r--r--library/Icinga/Common/PdfExport.php105
-rw-r--r--library/Icinga/Crypt/AesCrypt.php337
-rw-r--r--library/Icinga/Data/ConfigObject.php289
-rw-r--r--library/Icinga/Data/ConnectionInterface.php8
-rw-r--r--library/Icinga/Data/DataArray/ArrayDatasource.php292
-rw-r--r--library/Icinga/Data/Db/DbConnection.php655
-rw-r--r--library/Icinga/Data/Db/DbQuery.php564
-rw-r--r--library/Icinga/Data/Extensible.php22
-rw-r--r--library/Icinga/Data/Fetchable.php47
-rw-r--r--library/Icinga/Data/Filter/Filter.php255
-rw-r--r--library/Icinga/Data/Filter/FilterAnd.php42
-rw-r--r--library/Icinga/Data/Filter/FilterChain.php286
-rw-r--r--library/Icinga/Data/Filter/FilterEqual.php16
-rw-r--r--library/Icinga/Data/Filter/FilterEqualOrGreaterThan.php16
-rw-r--r--library/Icinga/Data/Filter/FilterEqualOrLessThan.php26
-rw-r--r--library/Icinga/Data/Filter/FilterException.php15
-rw-r--r--library/Icinga/Data/Filter/FilterExpression.php224
-rw-r--r--library/Icinga/Data/Filter/FilterGreaterThan.php16
-rw-r--r--library/Icinga/Data/Filter/FilterLessThan.php26
-rw-r--r--library/Icinga/Data/Filter/FilterMatch.php8
-rw-r--r--library/Icinga/Data/Filter/FilterMatchCaseInsensitive.php13
-rw-r--r--library/Icinga/Data/Filter/FilterMatchNot.php12
-rw-r--r--library/Icinga/Data/Filter/FilterMatchNotCaseInsensitive.php13
-rw-r--r--library/Icinga/Data/Filter/FilterNot.php58
-rw-r--r--library/Icinga/Data/Filter/FilterNotEqual.php12
-rw-r--r--library/Icinga/Data/Filter/FilterOr.php39
-rw-r--r--library/Icinga/Data/Filter/FilterParseException.php10
-rw-r--r--library/Icinga/Data/Filter/FilterQueryString.php320
-rw-r--r--library/Icinga/Data/FilterColumns.php21
-rw-r--r--library/Icinga/Data/Filterable.php27
-rw-r--r--library/Icinga/Data/Identifiable.php17
-rw-r--r--library/Icinga/Data/Inspectable.php20
-rw-r--r--library/Icinga/Data/Inspection.php129
-rw-r--r--library/Icinga/Data/Limitable.php48
-rw-r--r--library/Icinga/Data/Paginatable.php10
-rw-r--r--library/Icinga/Data/PivotTable.php396
-rw-r--r--library/Icinga/Data/QueryInterface.php8
-rw-r--r--library/Icinga/Data/Queryable.php20
-rw-r--r--library/Icinga/Data/Reducible.php23
-rw-r--r--library/Icinga/Data/ResourceFactory.php138
-rw-r--r--library/Icinga/Data/Selectable.php17
-rw-r--r--library/Icinga/Data/SimpleQuery.php650
-rw-r--r--library/Icinga/Data/SortRules.php14
-rw-r--r--library/Icinga/Data/Sortable.php49
-rw-r--r--library/Icinga/Data/Tree/SimpleTree.php90
-rw-r--r--library/Icinga/Data/Tree/TreeNode.php109
-rw-r--r--library/Icinga/Data/Tree/TreeNodeIterator.php75
-rw-r--r--library/Icinga/Data/Updatable.php24
-rw-r--r--library/Icinga/Date/DateFormatter.php259
-rw-r--r--library/Icinga/Exception/AlreadyExistsException.php11
-rw-r--r--library/Icinga/Exception/AuthenticationException.php11
-rw-r--r--library/Icinga/Exception/ConfigurationError.php12
-rw-r--r--library/Icinga/Exception/Http/BaseHttpException.php73
-rw-r--r--library/Icinga/Exception/Http/HttpBadRequestException.php12
-rw-r--r--library/Icinga/Exception/Http/HttpException.php25
-rw-r--r--library/Icinga/Exception/Http/HttpExceptionInterface.php21
-rw-r--r--library/Icinga/Exception/Http/HttpMethodNotAllowedException.php36
-rw-r--r--library/Icinga/Exception/Http/HttpNotFoundException.php12
-rw-r--r--library/Icinga/Exception/IcingaException.php107
-rw-r--r--library/Icinga/Exception/InvalidPropertyException.php11
-rw-r--r--library/Icinga/Exception/Json/JsonDecodeException.php11
-rw-r--r--library/Icinga/Exception/Json/JsonEncodeException.php11
-rw-r--r--library/Icinga/Exception/Json/JsonException.php13
-rw-r--r--library/Icinga/Exception/MissingParameterException.php40
-rw-r--r--library/Icinga/Exception/NotFoundError.php8
-rw-r--r--library/Icinga/Exception/NotImplementedError.php12
-rw-r--r--library/Icinga/Exception/NotReadableError.php8
-rw-r--r--library/Icinga/Exception/NotWritableError.php8
-rw-r--r--library/Icinga/Exception/ProgrammingError.php12
-rw-r--r--library/Icinga/Exception/QueryException.php11
-rw-r--r--library/Icinga/Exception/StatementException.php8
-rw-r--r--library/Icinga/Exception/SystemPermissionException.php11
-rw-r--r--library/Icinga/File/Csv.php47
-rw-r--r--library/Icinga/File/Ini/Dom/Comment.php37
-rw-r--r--library/Icinga/File/Ini/Dom/Directive.php166
-rw-r--r--library/Icinga/File/Ini/Dom/Document.php132
-rw-r--r--library/Icinga/File/Ini/Dom/Section.php190
-rw-r--r--library/Icinga/File/Ini/IniParser.php310
-rw-r--r--library/Icinga/File/Ini/IniWriter.php205
-rw-r--r--library/Icinga/File/Pdf.php96
-rw-r--r--library/Icinga/File/Storage/LocalFileStorage.php158
-rw-r--r--library/Icinga/File/Storage/StorageInterface.php94
-rw-r--r--library/Icinga/File/Storage/TemporaryLocalFileStorage.php59
-rw-r--r--library/Icinga/Legacy/DashboardConfig.php137
-rw-r--r--library/Icinga/Less/Call.php77
-rw-r--r--library/Icinga/Less/ColorProp.php109
-rw-r--r--library/Icinga/Less/ColorPropOrVariable.php71
-rw-r--r--library/Icinga/Less/DeferredColorProp.php136
-rw-r--r--library/Icinga/Less/LightMode.php128
-rw-r--r--library/Icinga/Less/LightModeCall.php38
-rw-r--r--library/Icinga/Less/LightModeDefinition.php75
-rw-r--r--library/Icinga/Less/LightModeTrait.php30
-rw-r--r--library/Icinga/Less/LightModeVisitor.php26
-rw-r--r--library/Icinga/Less/Visitor.php243
-rw-r--r--library/Icinga/Protocol/Dns.php89
-rw-r--r--library/Icinga/Protocol/File/Exception/FileReaderException.php12
-rw-r--r--library/Icinga/Protocol/File/FileIterator.php81
-rw-r--r--library/Icinga/Protocol/File/FileQuery.php86
-rw-r--r--library/Icinga/Protocol/File/FileReader.php208
-rw-r--r--library/Icinga/Protocol/File/LogFileIterator.php149
-rw-r--r--library/Icinga/Protocol/Ldap/Discovery.php143
-rw-r--r--library/Icinga/Protocol/Ldap/LdapCapabilities.php439
-rw-r--r--library/Icinga/Protocol/Ldap/LdapConnection.php1584
-rw-r--r--library/Icinga/Protocol/Ldap/LdapException.php14
-rw-r--r--library/Icinga/Protocol/Ldap/LdapQuery.php361
-rw-r--r--library/Icinga/Protocol/Ldap/LdapUtils.php148
-rw-r--r--library/Icinga/Protocol/Ldap/Node.php69
-rw-r--r--library/Icinga/Protocol/Ldap/Root.php241
-rw-r--r--library/Icinga/Protocol/Nrpe/Connection.php111
-rw-r--r--library/Icinga/Protocol/Nrpe/Packet.php69
-rw-r--r--library/Icinga/Repository/DbRepository.php1077
-rw-r--r--library/Icinga/Repository/IniRepository.php420
-rw-r--r--library/Icinga/Repository/LdapRepository.php71
-rw-r--r--library/Icinga/Repository/Repository.php1261
-rw-r--r--library/Icinga/Repository/RepositoryQuery.php797
-rw-r--r--library/Icinga/Security/SecurityException.php13
-rw-r--r--library/Icinga/Test/BaseTestCase.php361
-rw-r--r--library/Icinga/Test/ClassLoader.php113
-rw-r--r--library/Icinga/Test/DbTest.php47
-rw-r--r--library/Icinga/User.php649
-rw-r--r--library/Icinga/User/Preferences.php169
-rw-r--r--library/Icinga/User/Preferences/PreferencesStore.php344
-rw-r--r--library/Icinga/Util/ASN1.php102
-rw-r--r--library/Icinga/Util/Color.php121
-rw-r--r--library/Icinga/Util/ConfigAwareFactory.php18
-rw-r--r--library/Icinga/Util/Dimension.php123
-rw-r--r--library/Icinga/Util/DirectoryIterator.php213
-rw-r--r--library/Icinga/Util/EnumeratingFilterIterator.php30
-rw-r--r--library/Icinga/Util/Environment.php42
-rw-r--r--library/Icinga/Util/File.php195
-rw-r--r--library/Icinga/Util/Format.php197
-rw-r--r--library/Icinga/Util/GlobFilter.php182
-rw-r--r--library/Icinga/Util/Json.php151
-rw-r--r--library/Icinga/Util/LessParser.php17
-rw-r--r--library/Icinga/Util/StringHelper.php184
-rw-r--r--library/Icinga/Util/TimezoneDetect.php107
-rw-r--r--library/Icinga/Web/Announcement.php158
-rw-r--r--library/Icinga/Web/Announcement/AnnouncementCookie.php138
-rw-r--r--library/Icinga/Web/Announcement/AnnouncementIniRepository.php152
-rw-r--r--library/Icinga/Web/ApplicationStateCookie.php74
-rw-r--r--library/Icinga/Web/Controller.php264
-rw-r--r--library/Icinga/Web/Controller/ActionController.php587
-rw-r--r--library/Icinga/Web/Controller/AuthBackendController.php149
-rw-r--r--library/Icinga/Web/Controller/BasePreferenceController.php39
-rw-r--r--library/Icinga/Web/Controller/ControllerTabCollector.php97
-rw-r--r--library/Icinga/Web/Controller/Dispatcher.php93
-rw-r--r--library/Icinga/Web/Controller/ModuleActionController.php80
-rw-r--r--library/Icinga/Web/Controller/StaticController.php87
-rw-r--r--library/Icinga/Web/Cookie.php299
-rw-r--r--library/Icinga/Web/CookieSet.php58
-rw-r--r--library/Icinga/Web/Dom/DomNodeIterator.php84
-rw-r--r--library/Icinga/Web/FileCache.php293
-rw-r--r--library/Icinga/Web/Form.php1666
-rw-r--r--library/Icinga/Web/Form/Decorator/Autosubmit.php133
-rw-r--r--library/Icinga/Web/Form/Decorator/ConditionalHidden.php35
-rw-r--r--library/Icinga/Web/Form/Decorator/ElementDoubler.php63
-rw-r--r--library/Icinga/Web/Form/Decorator/FormDescriptions.php76
-rw-r--r--library/Icinga/Web/Form/Decorator/FormHints.php142
-rw-r--r--library/Icinga/Web/Form/Decorator/FormNotifications.php125
-rw-r--r--library/Icinga/Web/Form/Decorator/Help.php113
-rw-r--r--library/Icinga/Web/Form/Decorator/Spinner.php48
-rw-r--r--library/Icinga/Web/Form/Element/Button.php80
-rw-r--r--library/Icinga/Web/Form/Element/Checkbox.php9
-rw-r--r--library/Icinga/Web/Form/Element/CsrfCounterMeasure.php99
-rw-r--r--library/Icinga/Web/Form/Element/Date.php19
-rw-r--r--library/Icinga/Web/Form/Element/DateTimePicker.php80
-rw-r--r--library/Icinga/Web/Form/Element/Note.php55
-rw-r--r--library/Icinga/Web/Form/Element/Number.php144
-rw-r--r--library/Icinga/Web/Form/Element/Textarea.php20
-rw-r--r--library/Icinga/Web/Form/Element/Time.php19
-rw-r--r--library/Icinga/Web/Form/ErrorLabeller.php71
-rw-r--r--library/Icinga/Web/Form/FormElement.php61
-rw-r--r--library/Icinga/Web/Form/InvalidCSRFTokenException.php11
-rw-r--r--library/Icinga/Web/Form/Validator/DateFormatValidator.php61
-rw-r--r--library/Icinga/Web/Form/Validator/DateTimeValidator.php77
-rw-r--r--library/Icinga/Web/Form/Validator/InArray.php28
-rw-r--r--library/Icinga/Web/Form/Validator/InternalUrlValidator.php41
-rw-r--r--library/Icinga/Web/Form/Validator/ReadablePathValidator.php53
-rw-r--r--library/Icinga/Web/Form/Validator/TimeFormatValidator.php58
-rw-r--r--library/Icinga/Web/Form/Validator/UrlValidator.php40
-rw-r--r--library/Icinga/Web/Form/Validator/WritablePathValidator.php72
-rw-r--r--library/Icinga/Web/Helper/CookieHelper.php81
-rw-r--r--library/Icinga/Web/Helper/HtmlPurifier.php99
-rw-r--r--library/Icinga/Web/Helper/Markdown.php38
-rw-r--r--library/Icinga/Web/Helper/Markdown/LinkTransformer.php73
-rw-r--r--library/Icinga/Web/Hook.php16
-rw-r--r--library/Icinga/Web/JavaScript.php269
-rw-r--r--library/Icinga/Web/LessCompiler.php257
-rw-r--r--library/Icinga/Web/Menu.php152
-rw-r--r--library/Icinga/Web/Navigation/ConfigMenu.php302
-rw-r--r--library/Icinga/Web/Navigation/DashboardPane.php84
-rw-r--r--library/Icinga/Web/Navigation/DropdownItem.php20
-rw-r--r--library/Icinga/Web/Navigation/Navigation.php571
-rw-r--r--library/Icinga/Web/Navigation/NavigationItem.php947
-rw-r--r--library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php139
-rw-r--r--library/Icinga/Web/Navigation/Renderer/HealthNavigationRenderer.php44
-rw-r--r--library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php233
-rw-r--r--library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php356
-rw-r--r--library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php142
-rw-r--r--library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php186
-rw-r--r--library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php72
-rw-r--r--library/Icinga/Web/Notification.php220
-rw-r--r--library/Icinga/Web/Paginator/Adapter/QueryAdapter.php84
-rw-r--r--library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php78
-rw-r--r--library/Icinga/Web/RememberMe.php363
-rw-r--r--library/Icinga/Web/RememberMeUserDevicesList.php144
-rw-r--r--library/Icinga/Web/RememberMeUserList.php106
-rw-r--r--library/Icinga/Web/Request.php142
-rw-r--r--library/Icinga/Web/Response.php429
-rw-r--r--library/Icinga/Web/Response/JsonResponse.php239
-rw-r--r--library/Icinga/Web/Session.php54
-rw-r--r--library/Icinga/Web/Session/Php72Session.php37
-rw-r--r--library/Icinga/Web/Session/PhpSession.php256
-rw-r--r--library/Icinga/Web/Session/Session.php126
-rw-r--r--library/Icinga/Web/Session/SessionNamespace.php201
-rw-r--r--library/Icinga/Web/StyleSheet.php341
-rw-r--r--library/Icinga/Web/Url.php806
-rw-r--r--library/Icinga/Web/UrlParams.php433
-rw-r--r--library/Icinga/Web/UserAgent.php86
-rw-r--r--library/Icinga/Web/View.php254
-rw-r--r--library/Icinga/Web/View/AppHealth.php89
-rw-r--r--library/Icinga/Web/View/Helper/IcingaCheckbox.php30
-rw-r--r--library/Icinga/Web/View/PrivilegeAudit.php622
-rw-r--r--library/Icinga/Web/View/helpers/format.php72
-rw-r--r--library/Icinga/Web/View/helpers/generic.php15
-rw-r--r--library/Icinga/Web/View/helpers/string.php36
-rw-r--r--library/Icinga/Web/View/helpers/url.php158
-rw-r--r--library/Icinga/Web/Widget.php48
-rw-r--r--library/Icinga/Web/Widget/AbstractWidget.php120
-rw-r--r--library/Icinga/Web/Widget/Announcements.php55
-rw-r--r--library/Icinga/Web/Widget/ApplicationStateMessages.php74
-rw-r--r--library/Icinga/Web/Widget/Chart/HistoryColorGrid.php373
-rw-r--r--library/Icinga/Web/Widget/Chart/InlinePie.php257
-rw-r--r--library/Icinga/Web/Widget/Dashboard.php475
-rw-r--r--library/Icinga/Web/Widget/Dashboard/Dashlet.php315
-rw-r--r--library/Icinga/Web/Widget/Dashboard/Pane.php335
-rw-r--r--library/Icinga/Web/Widget/Dashboard/UserWidget.php36
-rw-r--r--library/Icinga/Web/Widget/FilterEditor.php811
-rw-r--r--library/Icinga/Web/Widget/FilterWidget.php122
-rw-r--r--library/Icinga/Web/Widget/Limiter.php54
-rw-r--r--library/Icinga/Web/Widget/Paginator.php167
-rw-r--r--library/Icinga/Web/Widget/SearchDashboard.php111
-rw-r--r--library/Icinga/Web/Widget/SingleValueSearchControl.php200
-rw-r--r--library/Icinga/Web/Widget/SortBox.php260
-rw-r--r--library/Icinga/Web/Widget/Tab.php323
-rw-r--r--library/Icinga/Web/Widget/Tabextension/DashboardAction.php35
-rw-r--r--library/Icinga/Web/Widget/Tabextension/DashboardSettings.php39
-rw-r--r--library/Icinga/Web/Widget/Tabextension/MenuAction.php35
-rw-r--r--library/Icinga/Web/Widget/Tabextension/OutputFormat.php114
-rw-r--r--library/Icinga/Web/Widget/Tabextension/Tabextension.php25
-rw-r--r--library/Icinga/Web/Widget/Tabs.php445
-rw-r--r--library/Icinga/Web/Widget/Widget.php24
-rw-r--r--library/Icinga/Web/Window.php125
-rw-r--r--library/Icinga/Web/Wizard.php719
-rw-r--r--library/vendor/HTMLPurifier.autoload.php25
-rw-r--r--library/vendor/HTMLPurifier.php297
-rw-r--r--library/vendor/HTMLPurifier/Arborize.php71
-rw-r--r--library/vendor/HTMLPurifier/AttrCollections.php148
-rw-r--r--library/vendor/HTMLPurifier/AttrDef.php144
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS.php136
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/AlphaValue.php34
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Background.php113
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php157
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Border.php56
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Color.php161
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Composite.php48
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php44
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Filter.php77
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Font.php176
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/FontFamily.php219
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Ident.php32
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php56
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Length.php77
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/ListStyle.php112
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Multiple.php71
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Number.php90
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/Percentage.php54
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/TextDecoration.php46
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/CSS/URI.php77
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/Clone.php44
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/Enum.php73
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/Bool.php48
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/Class.php48
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/Color.php51
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/ContentEditable.php16
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/FrameTarget.php38
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/ID.php113
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/Length.php56
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/LinkTypes.php72
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/MultiLength.php60
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/Nmtokens.php70
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/HTML/Pixels.php76
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/Integer.php91
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/Lang.php86
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/Switch.php53
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/Text.php21
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/URI.php111
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/URI/Email.php20
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php29
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/URI/Host.php142
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/URI/IPv4.php45
-rw-r--r--library/vendor/HTMLPurifier/AttrDef/URI/IPv6.php89
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform.php60
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Background.php28
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/BdoDir.php27
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/BgColor.php28
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/BoolToCSS.php47
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Border.php26
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/EnumToCSS.php68
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/ImgRequired.php47
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/ImgSpace.php61
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Input.php56
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Lang.php31
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Length.php45
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Name.php33
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/NameSync.php46
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Nofollow.php52
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/SafeEmbed.php25
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/SafeObject.php28
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/SafeParam.php84
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/ScriptRequired.php23
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/TargetBlank.php45
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/TargetNoopener.php37
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/TargetNoreferrer.php37
-rw-r--r--library/vendor/HTMLPurifier/AttrTransform/Textarea.php27
-rw-r--r--library/vendor/HTMLPurifier/AttrTypes.php97
-rw-r--r--library/vendor/HTMLPurifier/AttrValidator.php178
-rw-r--r--library/vendor/HTMLPurifier/Bootstrap.php124
-rw-r--r--library/vendor/HTMLPurifier/CSSDefinition.php549
-rw-r--r--library/vendor/HTMLPurifier/ChildDef.php52
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/Chameleon.php67
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/Custom.php102
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/Empty.php38
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/List.php94
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/Optional.php45
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/Required.php118
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/StrictBlockquote.php110
-rw-r--r--library/vendor/HTMLPurifier/ChildDef/Table.php224
-rw-r--r--library/vendor/HTMLPurifier/Config.php920
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema.php176
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php48
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Builder/Xml.php144
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Exception.php11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Interchange.php47
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Interchange/Directive.php89
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Interchange/Id.php58
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/InterchangeBuilder.php226
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/Validator.php248
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/ValidatorAtom.php130
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema.ser1
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt19
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt5
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt31
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt15
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt46
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt18
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt18
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt160
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt17
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt15
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt7
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt19
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.Language.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt36
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt34
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt29
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt74
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt25
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt19
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt15
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt23
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt20
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt18
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt23
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt33
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt16
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt21
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt20
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt7
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt12
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt24
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt8
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt10
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt15
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt25
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt7
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt18
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Base.txt17
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt15
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt14
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt11
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt15
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Host.txt19
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt13
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt83
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt17
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt30
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt9
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt22
-rw-r--r--library/vendor/HTMLPurifier/ConfigSchema/schema/info.ini3
-rw-r--r--library/vendor/HTMLPurifier/ContentSets.php170
-rw-r--r--library/vendor/HTMLPurifier/Context.php95
-rw-r--r--library/vendor/HTMLPurifier/Definition.php55
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache.php129
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Decorator.php112
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php78
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Decorator/Memory.php85
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Decorator/Template.php.in82
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Null.php76
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Serializer.php311
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCache/Serializer/README3
-rw-r--r--library/vendor/HTMLPurifier/DefinitionCacheFactory.php106
-rw-r--r--library/vendor/HTMLPurifier/Doctype.php73
-rw-r--r--library/vendor/HTMLPurifier/DoctypeRegistry.php142
-rw-r--r--library/vendor/HTMLPurifier/ElementDef.php216
-rw-r--r--library/vendor/HTMLPurifier/Encoder.php617
-rw-r--r--library/vendor/HTMLPurifier/EntityLookup.php48
-rw-r--r--library/vendor/HTMLPurifier/EntityLookup/entities.ser1
-rw-r--r--library/vendor/HTMLPurifier/EntityParser.php285
-rw-r--r--library/vendor/HTMLPurifier/ErrorCollector.php244
-rw-r--r--library/vendor/HTMLPurifier/ErrorStruct.php74
-rw-r--r--library/vendor/HTMLPurifier/Exception.php12
-rw-r--r--library/vendor/HTMLPurifier/Filter.php56
-rw-r--r--library/vendor/HTMLPurifier/Filter/ExtractStyleBlocks.php341
-rw-r--r--library/vendor/HTMLPurifier/Filter/YouTube.php65
-rw-r--r--library/vendor/HTMLPurifier/Generator.php286
-rw-r--r--library/vendor/HTMLPurifier/HTMLDefinition.php493
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule.php285
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Bdo.php44
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/CommonAttributes.php32
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Edit.php55
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Forms.php194
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Hypertext.php40
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Iframe.php51
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Image.php49
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Legacy.php186
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/List.php51
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Name.php26
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Nofollow.php25
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php20
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Object.php62
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Presentation.php42
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Proprietary.php40
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Ruby.php36
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/SafeEmbed.php40
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/SafeObject.php62
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/SafeScripting.php40
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Scripting.php73
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/StyleAttribute.php33
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tables.php75
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Target.php28
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/TargetBlank.php24
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/TargetNoopener.php21
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/TargetNoreferrer.php21
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Text.php87
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy.php227
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy/Name.php33
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy/Proprietary.php34
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy/Strict.php43
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy/Transitional.php16
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTML.php26
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php182
-rw-r--r--library/vendor/HTMLPurifier/HTMLModule/XMLCommonAttributes.php20
-rw-r--r--library/vendor/HTMLPurifier/HTMLModuleManager.php467
-rw-r--r--library/vendor/HTMLPurifier/IDAccumulator.php57
-rw-r--r--library/vendor/HTMLPurifier/Injector.php283
-rw-r--r--library/vendor/HTMLPurifier/Injector/AutoParagraph.php356
-rw-r--r--library/vendor/HTMLPurifier/Injector/DisplayLinkURI.php40
-rw-r--r--library/vendor/HTMLPurifier/Injector/Linkify.php67
-rw-r--r--library/vendor/HTMLPurifier/Injector/PurifierLinkify.php71
-rw-r--r--library/vendor/HTMLPurifier/Injector/RemoveEmpty.php112
-rw-r--r--library/vendor/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php95
-rw-r--r--library/vendor/HTMLPurifier/Injector/SafeObject.php124
-rw-r--r--library/vendor/HTMLPurifier/LICENSE504
-rw-r--r--library/vendor/HTMLPurifier/Language.php204
-rw-r--r--library/vendor/HTMLPurifier/Language/messages/en.php55
-rw-r--r--library/vendor/HTMLPurifier/LanguageFactory.php209
-rw-r--r--library/vendor/HTMLPurifier/Length.php162
-rw-r--r--library/vendor/HTMLPurifier/Lexer.php387
-rw-r--r--library/vendor/HTMLPurifier/Lexer/DOMLex.php338
-rw-r--r--library/vendor/HTMLPurifier/Lexer/DirectLex.php539
-rw-r--r--library/vendor/HTMLPurifier/Lexer/PH5P.php4788
-rw-r--r--library/vendor/HTMLPurifier/Node.php49
-rw-r--r--library/vendor/HTMLPurifier/Node/Comment.php36
-rw-r--r--library/vendor/HTMLPurifier/Node/Element.php59
-rw-r--r--library/vendor/HTMLPurifier/Node/Text.php54
-rw-r--r--library/vendor/HTMLPurifier/PercentEncoder.php111
-rw-r--r--library/vendor/HTMLPurifier/Printer.php218
-rw-r--r--library/vendor/HTMLPurifier/Printer/CSSDefinition.php44
-rw-r--r--library/vendor/HTMLPurifier/Printer/ConfigForm.css10
-rw-r--r--library/vendor/HTMLPurifier/Printer/ConfigForm.js5
-rw-r--r--library/vendor/HTMLPurifier/Printer/ConfigForm.php451
-rw-r--r--library/vendor/HTMLPurifier/Printer/HTMLDefinition.php324
-rw-r--r--library/vendor/HTMLPurifier/PropertyList.php122
-rw-r--r--library/vendor/HTMLPurifier/PropertyListIterator.php43
-rw-r--r--library/vendor/HTMLPurifier/Queue.php56
-rw-r--r--library/vendor/HTMLPurifier/SOURCE10
-rw-r--r--library/vendor/HTMLPurifier/Strategy.php26
-rw-r--r--library/vendor/HTMLPurifier/Strategy/Composite.php30
-rw-r--r--library/vendor/HTMLPurifier/Strategy/Core.php17
-rw-r--r--library/vendor/HTMLPurifier/Strategy/FixNesting.php181
-rw-r--r--library/vendor/HTMLPurifier/Strategy/MakeWellFormed.php659
-rw-r--r--library/vendor/HTMLPurifier/Strategy/RemoveForeignElements.php207
-rw-r--r--library/vendor/HTMLPurifier/Strategy/ValidateAttributes.php45
-rw-r--r--library/vendor/HTMLPurifier/StringHash.php48
-rw-r--r--library/vendor/HTMLPurifier/StringHashParser.php136
-rw-r--r--library/vendor/HTMLPurifier/TagTransform.php37
-rw-r--r--library/vendor/HTMLPurifier/TagTransform/Font.php114
-rw-r--r--library/vendor/HTMLPurifier/TagTransform/Simple.php44
-rw-r--r--library/vendor/HTMLPurifier/Token.php100
-rw-r--r--library/vendor/HTMLPurifier/Token/Comment.php38
-rw-r--r--library/vendor/HTMLPurifier/Token/Empty.php15
-rw-r--r--library/vendor/HTMLPurifier/Token/End.php24
-rw-r--r--library/vendor/HTMLPurifier/Token/Start.php10
-rw-r--r--library/vendor/HTMLPurifier/Token/Tag.php68
-rw-r--r--library/vendor/HTMLPurifier/Token/Text.php53
-rw-r--r--library/vendor/HTMLPurifier/TokenFactory.php118
-rw-r--r--library/vendor/HTMLPurifier/URI.php316
-rw-r--r--library/vendor/HTMLPurifier/URIDefinition.php112
-rw-r--r--library/vendor/HTMLPurifier/URIFilter.php74
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/DisableExternal.php54
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/DisableExternalResources.php25
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/DisableResources.php22
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/HostBlacklist.php46
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/MakeAbsolute.php158
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/Munge.php115
-rw-r--r--library/vendor/HTMLPurifier/URIFilter/SafeIframe.php68
-rw-r--r--library/vendor/HTMLPurifier/URIParser.php71
-rw-r--r--library/vendor/HTMLPurifier/URIScheme.php102
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/data.php136
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/file.php44
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/ftp.php58
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/http.php36
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/https.php18
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/mailto.php40
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/news.php35
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/nntp.php32
-rw-r--r--library/vendor/HTMLPurifier/URIScheme/tel.php46
-rw-r--r--library/vendor/HTMLPurifier/URISchemeRegistry.php81
-rw-r--r--library/vendor/HTMLPurifier/UnitConverter.php307
-rw-r--r--library/vendor/HTMLPurifier/VERSION1
-rw-r--r--library/vendor/HTMLPurifier/VarParser.php198
-rw-r--r--library/vendor/HTMLPurifier/VarParser/Flexible.php130
-rw-r--r--library/vendor/HTMLPurifier/VarParser/Native.php38
-rw-r--r--library/vendor/HTMLPurifier/VarParserException.php11
-rw-r--r--library/vendor/HTMLPurifier/Zipper.php157
-rw-r--r--library/vendor/JShrink/LICENSE29
-rw-r--r--library/vendor/JShrink/Minifier.php613
-rw-r--r--library/vendor/JShrink/SOURCE7
-rw-r--r--library/vendor/Parsedown/LICENSE20
-rw-r--r--library/vendor/Parsedown/Parsedown.php1693
-rw-r--r--library/vendor/Parsedown/SOURCE7
-rw-r--r--library/vendor/Zend/Cache.php245
-rw-r--r--library/vendor/Zend/Cache/Backend.php285
-rw-r--r--library/vendor/Zend/Cache/Backend/Apc.php353
-rw-r--r--library/vendor/Zend/Cache/Backend/BlackHole.php248
-rw-r--r--library/vendor/Zend/Cache/Backend/ExtendedInterface.php125
-rw-r--r--library/vendor/Zend/Cache/Backend/File.php1035
-rw-r--r--library/vendor/Zend/Cache/Backend/Interface.php99
-rw-r--r--library/vendor/Zend/Cache/Backend/Libmemcached.php482
-rw-r--r--library/vendor/Zend/Cache/Backend/Memcached.php507
-rw-r--r--library/vendor/Zend/Cache/Backend/Sqlite.php676
-rw-r--r--library/vendor/Zend/Cache/Backend/Static.php577
-rw-r--r--library/vendor/Zend/Cache/Backend/Test.php414
-rw-r--r--library/vendor/Zend/Cache/Backend/TwoLevels.php546
-rw-r--r--library/vendor/Zend/Cache/Backend/WinCache.php347
-rw-r--r--library/vendor/Zend/Cache/Backend/Xcache.php219
-rw-r--r--library/vendor/Zend/Cache/Backend/ZendPlatform.php315
-rw-r--r--library/vendor/Zend/Cache/Backend/ZendServer.php205
-rw-r--r--library/vendor/Zend/Cache/Backend/ZendServer/Disk.php99
-rw-r--r--library/vendor/Zend/Cache/Backend/ZendServer/ShMem.php99
-rw-r--r--library/vendor/Zend/Cache/Core.php762
-rw-r--r--library/vendor/Zend/Cache/Exception.php31
-rw-r--r--library/vendor/Zend/Cache/Frontend/Capture.php87
-rw-r--r--library/vendor/Zend/Cache/Frontend/Class.php274
-rw-r--r--library/vendor/Zend/Cache/Frontend/File.php221
-rw-r--r--library/vendor/Zend/Cache/Frontend/Function.php178
-rw-r--r--library/vendor/Zend/Cache/Frontend/Output.php104
-rw-r--r--library/vendor/Zend/Cache/Frontend/Page.php403
-rw-r--r--library/vendor/Zend/Cache/Manager.php304
-rw-r--r--library/vendor/Zend/Config.php481
-rw-r--r--library/vendor/Zend/Config/Exception.php32
-rw-r--r--library/vendor/Zend/Config/Ini.php301
-rw-r--r--library/vendor/Zend/Config/Writer.php101
-rw-r--r--library/vendor/Zend/Config/Writer/Array.php54
-rw-r--r--library/vendor/Zend/Config/Writer/FileAbstract.php130
-rw-r--r--library/vendor/Zend/Config/Writer/Ini.php191
-rw-r--r--library/vendor/Zend/Controller/Action.php789
-rw-r--r--library/vendor/Zend/Controller/Action/Exception.php37
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/Abstract.php155
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/ActionStack.php133
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/AjaxContext.php79
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/AutoComplete/Abstract.php146
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/Cache.php286
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/ContextSwitch.php1377
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/Json.php130
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/Redirector.php531
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/Url.php116
-rw-r--r--library/vendor/Zend/Controller/Action/Helper/ViewRenderer.php996
-rw-r--r--library/vendor/Zend/Controller/Action/HelperBroker.php373
-rw-r--r--library/vendor/Zend/Controller/Action/HelperBroker/PriorityStack.php272
-rw-r--r--library/vendor/Zend/Controller/Action/Interface.php69
-rw-r--r--library/vendor/Zend/Controller/Dispatcher/Abstract.php435
-rw-r--r--library/vendor/Zend/Controller/Dispatcher/Exception.php36
-rw-r--r--library/vendor/Zend/Controller/Dispatcher/Interface.php204
-rw-r--r--library/vendor/Zend/Controller/Dispatcher/Standard.php504
-rw-r--r--library/vendor/Zend/Controller/Exception.php34
-rw-r--r--library/vendor/Zend/Controller/Front.php977
-rw-r--r--library/vendor/Zend/Controller/Plugin/Abstract.php151
-rw-r--r--library/vendor/Zend/Controller/Plugin/ActionStack.php277
-rw-r--r--library/vendor/Zend/Controller/Plugin/Broker.php361
-rw-r--r--library/vendor/Zend/Controller/Plugin/ErrorHandler.php299
-rw-r--r--library/vendor/Zend/Controller/Plugin/PutHandler.php58
-rw-r--r--library/vendor/Zend/Controller/Request/Abstract.php356
-rw-r--r--library/vendor/Zend/Controller/Request/Apache404.php80
-rw-r--r--library/vendor/Zend/Controller/Request/Exception.php36
-rw-r--r--library/vendor/Zend/Controller/Request/Http.php1087
-rw-r--r--library/vendor/Zend/Controller/Request/HttpTestCase.php275
-rw-r--r--library/vendor/Zend/Controller/Request/Simple.php54
-rw-r--r--library/vendor/Zend/Controller/Response/Abstract.php790
-rw-r--r--library/vendor/Zend/Controller/Response/Cli.php67
-rw-r--r--library/vendor/Zend/Controller/Response/Exception.php35
-rw-r--r--library/vendor/Zend/Controller/Response/Http.php37
-rw-r--r--library/vendor/Zend/Controller/Response/HttpTestCase.php129
-rw-r--r--library/vendor/Zend/Controller/Router/Abstract.php176
-rw-r--r--library/vendor/Zend/Controller/Router/Exception.php34
-rw-r--r--library/vendor/Zend/Controller/Router/Interface.php123
-rw-r--r--library/vendor/Zend/Controller/Router/Rewrite.php542
-rw-r--r--library/vendor/Zend/Controller/Router/Route.php603
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Abstract.php119
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Chain.php228
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Hostname.php377
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Interface.php38
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Module.php322
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Regex.php316
-rw-r--r--library/vendor/Zend/Controller/Router/Route/Static.php148
-rw-r--r--library/vendor/Zend/Crypt.php167
-rw-r--r--library/vendor/Zend/Crypt/DiffieHellman.php378
-rw-r--r--library/vendor/Zend/Crypt/DiffieHellman/Exception.php35
-rw-r--r--library/vendor/Zend/Crypt/Exception.php34
-rw-r--r--library/vendor/Zend/Crypt/Hmac.php178
-rw-r--r--library/vendor/Zend/Crypt/Hmac/Exception.php35
-rw-r--r--library/vendor/Zend/Crypt/Math.php186
-rw-r--r--library/vendor/Zend/Crypt/Math/BigInteger.php113
-rw-r--r--library/vendor/Zend/Crypt/Math/BigInteger/Bcmath.php226
-rw-r--r--library/vendor/Zend/Crypt/Math/BigInteger/Exception.php35
-rw-r--r--library/vendor/Zend/Crypt/Math/BigInteger/Gmp.php219
-rw-r--r--library/vendor/Zend/Crypt/Math/BigInteger/Interface.php51
-rw-r--r--library/vendor/Zend/Crypt/Math/Exception.php35
-rw-r--r--library/vendor/Zend/Crypt/Rsa.php334
-rw-r--r--library/vendor/Zend/Crypt/Rsa/Exception.php35
-rw-r--r--library/vendor/Zend/Crypt/Rsa/Key.php94
-rw-r--r--library/vendor/Zend/Crypt/Rsa/Key/Private.php72
-rw-r--r--library/vendor/Zend/Crypt/Rsa/Key/Public.php72
-rw-r--r--library/vendor/Zend/Date.php4872
-rw-r--r--library/vendor/Zend/Date/Cities.php321
-rw-r--r--library/vendor/Zend/Date/DateObject.php1094
-rw-r--r--library/vendor/Zend/Date/Exception.php48
-rw-r--r--library/vendor/Zend/Db.php282
-rw-r--r--library/vendor/Zend/Db/Adapter/Abstract.php1267
-rw-r--r--library/vendor/Zend/Db/Adapter/Db2.php827
-rw-r--r--library/vendor/Zend/Db/Adapter/Db2/Exception.php44
-rw-r--r--library/vendor/Zend/Db/Adapter/Exception.php56
-rw-r--r--library/vendor/Zend/Db/Adapter/Mysqli.php543
-rw-r--r--library/vendor/Zend/Db/Adapter/Mysqli/Exception.php39
-rw-r--r--library/vendor/Zend/Db/Adapter/Oracle.php631
-rw-r--r--library/vendor/Zend/Db/Adapter/Oracle/Exception.php59
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Abstract.php397
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Ibm.php354
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Ibm/Db2.php224
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Ibm/Ids.php297
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Mssql.php435
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Mysql.php269
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Oci.php375
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Pgsql.php333
-rw-r--r--library/vendor/Zend/Db/Adapter/Pdo/Sqlite.php305
-rw-r--r--library/vendor/Zend/Db/Adapter/Sqlsrv.php662
-rw-r--r--library/vendor/Zend/Db/Adapter/Sqlsrv/Exception.php62
-rw-r--r--library/vendor/Zend/Db/Exception.php34
-rw-r--r--library/vendor/Zend/Db/Expr.php77
-rw-r--r--library/vendor/Zend/Db/Profiler.php469
-rw-r--r--library/vendor/Zend/Db/Profiler/Exception.php39
-rw-r--r--library/vendor/Zend/Db/Profiler/Query.php213
-rw-r--r--library/vendor/Zend/Db/Select.php1368
-rw-r--r--library/vendor/Zend/Db/Select/Exception.php38
-rw-r--r--library/vendor/Zend/Db/Statement.php488
-rw-r--r--library/vendor/Zend/Db/Statement/Db2.php354
-rw-r--r--library/vendor/Zend/Db/Statement/Db2/Exception.php57
-rw-r--r--library/vendor/Zend/Db/Statement/Exception.php55
-rw-r--r--library/vendor/Zend/Db/Statement/Interface.php203
-rw-r--r--library/vendor/Zend/Db/Statement/Mysqli.php356
-rw-r--r--library/vendor/Zend/Db/Statement/Mysqli/Exception.php37
-rw-r--r--library/vendor/Zend/Db/Statement/Oracle.php561
-rw-r--r--library/vendor/Zend/Db/Statement/Oracle/Exception.php58
-rw-r--r--library/vendor/Zend/Db/Statement/Pdo.php426
-rw-r--r--library/vendor/Zend/Db/Statement/Pdo/Ibm.php92
-rw-r--r--library/vendor/Zend/Db/Statement/Pdo/Oci.php90
-rw-r--r--library/vendor/Zend/Db/Statement/Sqlsrv.php430
-rw-r--r--library/vendor/Zend/Db/Statement/Sqlsrv/Exception.php60
-rw-r--r--library/vendor/Zend/Db/Table.php77
-rw-r--r--library/vendor/Zend/Db/Table/Abstract.php1599
-rw-r--r--library/vendor/Zend/Db/Table/Definition.php131
-rw-r--r--library/vendor/Zend/Db/Table/Exception.php37
-rw-r--r--library/vendor/Zend/Db/Table/Row.php41
-rw-r--r--library/vendor/Zend/Db/Table/Row/Abstract.php1160
-rw-r--r--library/vendor/Zend/Db/Table/Row/Exception.php37
-rw-r--r--library/vendor/Zend/Db/Table/Rowset.php42
-rw-r--r--library/vendor/Zend/Db/Table/Rowset/Abstract.php443
-rw-r--r--library/vendor/Zend/Db/Table/Rowset/Exception.php36
-rw-r--r--library/vendor/Zend/Db/Table/Select.php221
-rw-r--r--library/vendor/Zend/Db/Table/Select/Exception.php38
-rw-r--r--library/vendor/Zend/Exception.php96
-rw-r--r--library/vendor/Zend/File/ClassFileLocator.php177
-rw-r--r--library/vendor/Zend/File/PhpClassFile.php56
-rw-r--r--library/vendor/Zend/File/Transfer.php122
-rw-r--r--library/vendor/Zend/File/Transfer/Adapter/Abstract.php1548
-rw-r--r--library/vendor/Zend/File/Transfer/Adapter/Http.php480
-rw-r--r--library/vendor/Zend/File/Transfer/Exception.php54
-rw-r--r--library/vendor/Zend/Filter.php236
-rw-r--r--library/vendor/Zend/Filter/Alnum.php144
-rw-r--r--library/vendor/Zend/Filter/Alpha.php144
-rw-r--r--library/vendor/Zend/Filter/BaseName.php49
-rw-r--r--library/vendor/Zend/Filter/Boolean.php369
-rw-r--r--library/vendor/Zend/Filter/Callback.php149
-rw-r--r--library/vendor/Zend/Filter/Compress.php192
-rw-r--r--library/vendor/Zend/Filter/Compress/Bz2.php181
-rw-r--r--library/vendor/Zend/Filter/Compress/CompressAbstract.php88
-rw-r--r--library/vendor/Zend/Filter/Compress/CompressInterface.php54
-rw-r--r--library/vendor/Zend/Filter/Compress/Gz.php220
-rw-r--r--library/vendor/Zend/Filter/Compress/Lzf.php87
-rw-r--r--library/vendor/Zend/Filter/Compress/Rar.php243
-rw-r--r--library/vendor/Zend/Filter/Compress/Tar.php234
-rw-r--r--library/vendor/Zend/Filter/Compress/Zip.php343
-rw-r--r--library/vendor/Zend/Filter/Decompress.php48
-rw-r--r--library/vendor/Zend/Filter/Decrypt.php48
-rw-r--r--library/vendor/Zend/Filter/Digits.php81
-rw-r--r--library/vendor/Zend/Filter/Dir.php49
-rw-r--r--library/vendor/Zend/Filter/Encrypt.php134
-rw-r--r--library/vendor/Zend/Filter/Encrypt/Interface.php47
-rw-r--r--library/vendor/Zend/Filter/Encrypt/Mcrypt.php352
-rw-r--r--library/vendor/Zend/Filter/Encrypt/Openssl.php480
-rw-r--r--library/vendor/Zend/Filter/Exception.php36
-rw-r--r--library/vendor/Zend/Filter/File/Decrypt.php101
-rw-r--r--library/vendor/Zend/Filter/File/Encrypt.php101
-rw-r--r--library/vendor/Zend/Filter/File/LowerCase.php79
-rw-r--r--library/vendor/Zend/Filter/File/Rename.php304
-rw-r--r--library/vendor/Zend/Filter/File/UpperCase.php79
-rw-r--r--library/vendor/Zend/Filter/HtmlEntities.php213
-rw-r--r--library/vendor/Zend/Filter/Inflector.php523
-rw-r--r--library/vendor/Zend/Filter/Input.php1196
-rw-r--r--library/vendor/Zend/Filter/Int.php49
-rw-r--r--library/vendor/Zend/Filter/Interface.php40
-rw-r--r--library/vendor/Zend/Filter/LocalizedToNormalized.php110
-rw-r--r--library/vendor/Zend/Filter/NormalizedToLocalized.php108
-rw-r--r--library/vendor/Zend/Filter/Null.php181
-rw-r--r--library/vendor/Zend/Filter/PregReplace.php172
-rw-r--r--library/vendor/Zend/Filter/RealPath.php133
-rw-r--r--library/vendor/Zend/Filter/StringToLower.php118
-rw-r--r--library/vendor/Zend/Filter/StringToUpper.php118
-rw-r--r--library/vendor/Zend/Filter/StringTrim.php123
-rw-r--r--library/vendor/Zend/Filter/StripNewlines.php47
-rw-r--r--library/vendor/Zend/Filter/StripTags.php351
-rw-r--r--library/vendor/Zend/Filter/Word/CamelCaseToDash.php43
-rw-r--r--library/vendor/Zend/Filter/Word/CamelCaseToSeparator.php48
-rw-r--r--library/vendor/Zend/Filter/Word/CamelCaseToUnderscore.php43
-rw-r--r--library/vendor/Zend/Filter/Word/DashToCamelCase.php43
-rw-r--r--library/vendor/Zend/Filter/Word/DashToSeparator.php41
-rw-r--r--library/vendor/Zend/Filter/Word/DashToUnderscore.php44
-rw-r--r--library/vendor/Zend/Filter/Word/Separator/Abstract.php74
-rw-r--r--library/vendor/Zend/Filter/Word/SeparatorToCamelCase.php63
-rw-r--r--library/vendor/Zend/Filter/Word/SeparatorToDash.php45
-rw-r--r--library/vendor/Zend/Filter/Word/SeparatorToSeparator.php127
-rw-r--r--library/vendor/Zend/Filter/Word/UnderscoreToCamelCase.php43
-rw-r--r--library/vendor/Zend/Filter/Word/UnderscoreToDash.php44
-rw-r--r--library/vendor/Zend/Filter/Word/UnderscoreToSeparator.php44
-rw-r--r--library/vendor/Zend/Form.php3472
-rw-r--r--library/vendor/Zend/Form/Decorator/Abstract.php251
-rw-r--r--library/vendor/Zend/Form/Decorator/Callback.php126
-rw-r--r--library/vendor/Zend/Form/Decorator/Captcha.php71
-rw-r--r--library/vendor/Zend/Form/Decorator/Description.php197
-rw-r--r--library/vendor/Zend/Form/Decorator/DtDdWrapper.php69
-rw-r--r--library/vendor/Zend/Form/Decorator/Errors.php76
-rw-r--r--library/vendor/Zend/Form/Decorator/Exception.php36
-rw-r--r--library/vendor/Zend/Form/Decorator/Fieldset.php156
-rw-r--r--library/vendor/Zend/Form/Decorator/File.php140
-rw-r--r--library/vendor/Zend/Form/Decorator/Form.php133
-rw-r--r--library/vendor/Zend/Form/Decorator/FormElements.php140
-rw-r--r--library/vendor/Zend/Form/Decorator/FormErrors.php514
-rw-r--r--library/vendor/Zend/Form/Decorator/HtmlTag.php253
-rw-r--r--library/vendor/Zend/Form/Decorator/Image.php152
-rw-r--r--library/vendor/Zend/Form/Decorator/Interface.php123
-rw-r--r--library/vendor/Zend/Form/Decorator/Label.php461
-rw-r--r--library/vendor/Zend/Form/Decorator/Marker/File/Interface.php33
-rw-r--r--library/vendor/Zend/Form/Decorator/PrepareElements.php89
-rw-r--r--library/vendor/Zend/Form/Decorator/Tooltip.php57
-rw-r--r--library/vendor/Zend/Form/Decorator/ViewHelper.php266
-rw-r--r--library/vendor/Zend/Form/Decorator/ViewScript.php190
-rw-r--r--library/vendor/Zend/Form/DisplayGroup.php1172
-rw-r--r--library/vendor/Zend/Form/Element.php2280
-rw-r--r--library/vendor/Zend/Form/Element/Button.php55
-rw-r--r--library/vendor/Zend/Form/Element/Checkbox.php202
-rw-r--r--library/vendor/Zend/Form/Element/Exception.php36
-rw-r--r--library/vendor/Zend/Form/Element/File.php910
-rw-r--r--library/vendor/Zend/Form/Element/Hidden.php41
-rw-r--r--library/vendor/Zend/Form/Element/Image.php131
-rw-r--r--library/vendor/Zend/Form/Element/Multi.php316
-rw-r--r--library/vendor/Zend/Form/Element/MultiCheckbox.php72
-rw-r--r--library/vendor/Zend/Form/Element/Multiselect.php53
-rw-r--r--library/vendor/Zend/Form/Element/Note.php63
-rw-r--r--library/vendor/Zend/Form/Element/Password.php87
-rw-r--r--library/vendor/Zend/Form/Element/Radio.php65
-rw-r--r--library/vendor/Zend/Form/Element/Reset.php41
-rw-r--r--library/vendor/Zend/Form/Element/Select.php47
-rw-r--r--library/vendor/Zend/Form/Element/Submit.php126
-rw-r--r--library/vendor/Zend/Form/Element/Text.php41
-rw-r--r--library/vendor/Zend/Form/Element/Textarea.php41
-rw-r--r--library/vendor/Zend/Form/Element/Xhtml.php36
-rw-r--r--library/vendor/Zend/Form/Exception.php34
-rw-r--r--library/vendor/Zend/Form/SubForm.php60
-rw-r--r--library/vendor/Zend/Json.php433
-rw-r--r--library/vendor/Zend/Json/Decoder.php570
-rw-r--r--library/vendor/Zend/Json/Encoder.php576
-rw-r--r--library/vendor/Zend/Json/Exception.php36
-rw-r--r--library/vendor/Zend/Json/Expr.php80
-rw-r--r--library/vendor/Zend/LICENSE.txt27
-rw-r--r--library/vendor/Zend/Layout.php788
-rw-r--r--library/vendor/Zend/Layout/Controller/Action/Helper/Layout.php181
-rw-r--r--library/vendor/Zend/Layout/Controller/Plugin/Layout.php155
-rw-r--r--library/vendor/Zend/Layout/Exception.php34
-rw-r--r--library/vendor/Zend/Loader.php338
-rw-r--r--library/vendor/Zend/Loader/Autoloader.php589
-rw-r--r--library/vendor/Zend/Loader/Autoloader/Interface.php43
-rw-r--r--library/vendor/Zend/Loader/Exception.php34
-rw-r--r--library/vendor/Zend/Loader/Exception/InvalidArgumentException.php33
-rw-r--r--library/vendor/Zend/Loader/PluginLoader.php497
-rw-r--r--library/vendor/Zend/Loader/PluginLoader/Exception.php38
-rw-r--r--library/vendor/Zend/Loader/PluginLoader/Interface.php75
-rw-r--r--library/vendor/Zend/Locale.php1996
-rw-r--r--library/vendor/Zend/Locale/Data.php1609
-rw-r--r--library/vendor/Zend/Locale/Data/Translation.php285
-rw-r--r--library/vendor/Zend/Locale/Exception.php36
-rw-r--r--library/vendor/Zend/Locale/Format.php1321
-rw-r--r--library/vendor/Zend/Locale/Math.php354
-rw-r--r--library/vendor/Zend/Locale/Math/Exception.php52
-rw-r--r--library/vendor/Zend/Locale/Math/PhpMath.php239
-rw-r--r--library/vendor/Zend/Log.php645
-rw-r--r--library/vendor/Zend/Log/Exception.php32
-rw-r--r--library/vendor/Zend/Log/FactoryInterface.php38
-rw-r--r--library/vendor/Zend/Log/Filter/Abstract.php57
-rw-r--r--library/vendor/Zend/Log/Filter/Interface.php40
-rw-r--r--library/vendor/Zend/Log/Filter/Message.php83
-rw-r--r--library/vendor/Zend/Log/Filter/Priority.php99
-rw-r--r--library/vendor/Zend/Log/Filter/Suppress.php76
-rw-r--r--library/vendor/Zend/Log/Formatter/Abstract.php38
-rw-r--r--library/vendor/Zend/Log/Formatter/Interface.php41
-rw-r--r--library/vendor/Zend/Log/Formatter/Simple.php106
-rw-r--r--library/vendor/Zend/Log/Formatter/Xml.php164
-rw-r--r--library/vendor/Zend/Log/Writer/Abstract.php138
-rw-r--r--library/vendor/Zend/Log/Writer/Db.php144
-rw-r--r--library/vendor/Zend/Log/Writer/Mail.php426
-rw-r--r--library/vendor/Zend/Log/Writer/Mock.php80
-rw-r--r--library/vendor/Zend/Log/Writer/Null.php55
-rw-r--r--library/vendor/Zend/Log/Writer/Stream.php132
-rw-r--r--library/vendor/Zend/Log/Writer/Syslog.php263
-rw-r--r--library/vendor/Zend/Log/Writer/ZendMonitor.php130
-rw-r--r--library/vendor/Zend/Mail.php1265
-rw-r--r--library/vendor/Zend/Mail/Exception.php36
-rw-r--r--library/vendor/Zend/Mail/Header/HeaderName.php91
-rw-r--r--library/vendor/Zend/Mail/Header/HeaderValue.php135
-rw-r--r--library/vendor/Zend/Mail/Message.php110
-rw-r--r--library/vendor/Zend/Mail/Message/File.php94
-rw-r--r--library/vendor/Zend/Mail/Message/Interface.php55
-rw-r--r--library/vendor/Zend/Mail/Part.php590
-rw-r--r--library/vendor/Zend/Mail/Part/File.php191
-rw-r--r--library/vendor/Zend/Mail/Part/Interface.php136
-rw-r--r--library/vendor/Zend/Mail/Protocol/Abstract.php436
-rw-r--r--library/vendor/Zend/Mail/Protocol/Exception.php38
-rw-r--r--library/vendor/Zend/Mail/Protocol/Imap.php830
-rw-r--r--library/vendor/Zend/Mail/Protocol/Pop3.php466
-rw-r--r--library/vendor/Zend/Mail/Protocol/Smtp.php433
-rw-r--r--library/vendor/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php107
-rw-r--r--library/vendor/Zend/Mail/Protocol/Smtp/Auth/Login.php97
-rw-r--r--library/vendor/Zend/Mail/Protocol/Smtp/Auth/Plain.php95
-rw-r--r--library/vendor/Zend/Mail/Storage.php40
-rw-r--r--library/vendor/Zend/Mail/Storage/Abstract.php364
-rw-r--r--library/vendor/Zend/Mail/Storage/Exception.php38
-rw-r--r--library/vendor/Zend/Mail/Storage/Folder.php235
-rw-r--r--library/vendor/Zend/Mail/Storage/Folder/Interface.php60
-rw-r--r--library/vendor/Zend/Mail/Storage/Folder/Maildir.php255
-rw-r--r--library/vendor/Zend/Mail/Storage/Folder/Mbox.php255
-rw-r--r--library/vendor/Zend/Mail/Storage/Imap.php620
-rw-r--r--library/vendor/Zend/Mail/Storage/Maildir.php462
-rw-r--r--library/vendor/Zend/Mail/Storage/Mbox.php437
-rw-r--r--library/vendor/Zend/Mail/Storage/Pop3.php321
-rw-r--r--library/vendor/Zend/Mail/Storage/Writable/Interface.php108
-rw-r--r--library/vendor/Zend/Mail/Storage/Writable/Maildir.php1014
-rw-r--r--library/vendor/Zend/Mail/Transport/Abstract.php345
-rw-r--r--library/vendor/Zend/Mail/Transport/Exception.php38
-rw-r--r--library/vendor/Zend/Mail/Transport/File.php131
-rw-r--r--library/vendor/Zend/Mail/Transport/Sendmail.php214
-rw-r--r--library/vendor/Zend/Mail/Transport/Smtp.php238
-rw-r--r--library/vendor/Zend/Mime.php670
-rw-r--r--library/vendor/Zend/Mime/Decode.php275
-rw-r--r--library/vendor/Zend/Mime/Exception.php35
-rw-r--r--library/vendor/Zend/Mime/Message.php302
-rw-r--r--library/vendor/Zend/Mime/Part.php329
-rw-r--r--library/vendor/Zend/Paginator.php1164
-rw-r--r--library/vendor/Zend/Paginator/Adapter/Array.php80
-rw-r--r--library/vendor/Zend/Paginator/Adapter/DbSelect.php285
-rw-r--r--library/vendor/Zend/Paginator/Adapter/DbTableSelect.php47
-rw-r--r--library/vendor/Zend/Paginator/Adapter/Interface.php40
-rw-r--r--library/vendor/Zend/Paginator/Adapter/Iterator.php99
-rw-r--r--library/vendor/Zend/Paginator/Adapter/Null.php79
-rw-r--r--library/vendor/Zend/Paginator/AdapterAggregate.php40
-rw-r--r--library/vendor/Zend/Paginator/Exception.php34
-rw-r--r--library/vendor/Zend/Paginator/ScrollingStyle/All.php49
-rw-r--r--library/vendor/Zend/Paginator/ScrollingStyle/Elastic.php62
-rw-r--r--library/vendor/Zend/Paginator/ScrollingStyle/Interface.php38
-rw-r--r--library/vendor/Zend/Paginator/ScrollingStyle/Jumping.php62
-rw-r--r--library/vendor/Zend/Paginator/ScrollingStyle/Sliding.php77
-rw-r--r--library/vendor/Zend/Paginator/SerializableLimitIterator.php159
-rw-r--r--library/vendor/Zend/ProgressBar.php207
-rw-r--r--library/vendor/Zend/ProgressBar/Adapter.php113
-rw-r--r--library/vendor/Zend/ProgressBar/Adapter/Exception.php37
-rw-r--r--library/vendor/Zend/ProgressBar/Adapter/JsPull.php115
-rw-r--r--library/vendor/Zend/ProgressBar/Adapter/JsPush.php146
-rw-r--r--library/vendor/Zend/ProgressBar/Exception.php37
-rw-r--r--library/vendor/Zend/README.md8
-rw-r--r--library/vendor/Zend/Registry.php192
-rw-r--r--library/vendor/Zend/Server/Abstract.php235
-rw-r--r--library/vendor/Zend/Server/Cache.php147
-rw-r--r--library/vendor/Zend/Server/Definition.php263
-rw-r--r--library/vendor/Zend/Server/Exception.php34
-rw-r--r--library/vendor/Zend/Server/Interface.php118
-rw-r--r--library/vendor/Zend/Server/Method/Callback.php204
-rw-r--r--library/vendor/Zend/Server/Method/Definition.php288
-rw-r--r--library/vendor/Zend/Server/Method/Parameter.php214
-rw-r--r--library/vendor/Zend/Server/Method/Prototype.php207
-rw-r--r--library/vendor/Zend/Server/Reflection.php105
-rw-r--r--library/vendor/Zend/Server/Reflection/Class.php195
-rw-r--r--library/vendor/Zend/Server/Reflection/Exception.php37
-rw-r--r--library/vendor/Zend/Server/Reflection/Function.php38
-rw-r--r--library/vendor/Zend/Server/Reflection/Function/Abstract.php506
-rw-r--r--library/vendor/Zend/Server/Reflection/Method.php109
-rw-r--r--library/vendor/Zend/Server/Reflection/Node.php201
-rw-r--r--library/vendor/Zend/Server/Reflection/Parameter.php158
-rw-r--r--library/vendor/Zend/Server/Reflection/Prototype.php99
-rw-r--r--library/vendor/Zend/Server/Reflection/ReturnValue.php108
-rw-r--r--library/vendor/Zend/Session.php895
-rw-r--r--library/vendor/Zend/Session/Abstract.php182
-rw-r--r--library/vendor/Zend/Session/Exception.php73
-rw-r--r--library/vendor/Zend/Session/Namespace.php511
-rw-r--r--library/vendor/Zend/Session/SaveHandler/DbTable.php579
-rw-r--r--library/vendor/Zend/Session/SaveHandler/Exception.php36
-rw-r--r--library/vendor/Zend/Session/SaveHandler/Interface.php81
-rw-r--r--library/vendor/Zend/Session/Validator/Abstract.php70
-rw-r--r--library/vendor/Zend/Session/Validator/Exception.php42
-rw-r--r--library/vendor/Zend/Session/Validator/HttpUserAgent.php65
-rw-r--r--library/vendor/Zend/Session/Validator/Interface.php52
-rw-r--r--library/vendor/Zend/Soap/AutoDiscover.php597
-rw-r--r--library/vendor/Zend/Soap/AutoDiscover/Exception.php33
-rw-r--r--library/vendor/Zend/Soap/Client.php1223
-rw-r--r--library/vendor/Zend/Soap/Client/Common.php76
-rw-r--r--library/vendor/Zend/Soap/Client/DotNet.php93
-rw-r--r--library/vendor/Zend/Soap/Client/Exception.php34
-rw-r--r--library/vendor/Zend/Soap/Client/Local.php97
-rw-r--r--library/vendor/Zend/Soap/Server.php999
-rw-r--r--library/vendor/Zend/Soap/Server/Exception.php36
-rw-r--r--library/vendor/Zend/Soap/Server/Proxy.php75
-rw-r--r--library/vendor/Zend/Soap/Wsdl.php661
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Exception.php36
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/Abstract.php65
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/AnyType.php58
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeComplex.php142
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeSequence.php154
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/Composite.php183
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/DefaultComplexType.php89
-rw-r--r--library/vendor/Zend/Soap/Wsdl/Strategy/Interface.php48
-rw-r--r--library/vendor/Zend/TimeSync.php296
-rw-r--r--library/vendor/Zend/TimeSync/Exception.php63
-rw-r--r--library/vendor/Zend/TimeSync/Ntp.php430
-rw-r--r--library/vendor/Zend/TimeSync/Protocol.php148
-rw-r--r--library/vendor/Zend/TimeSync/Sntp.php118
-rw-r--r--library/vendor/Zend/Translate.php220
-rw-r--r--library/vendor/Zend/Translate/Adapter.php988
-rw-r--r--library/vendor/Zend/Translate/Exception.php36
-rw-r--r--library/vendor/Zend/Translate/Plural.php223
-rw-r--r--library/vendor/Zend/Uri.php201
-rw-r--r--library/vendor/Zend/Uri/Exception.php36
-rw-r--r--library/vendor/Zend/Uri/Http.php745
-rw-r--r--library/vendor/Zend/VERSION1
-rw-r--r--library/vendor/Zend/Validate.php283
-rw-r--r--library/vendor/Zend/Validate/Abstract.php477
-rw-r--r--library/vendor/Zend/Validate/Alnum.php147
-rw-r--r--library/vendor/Zend/Validate/Alpha.php147
-rw-r--r--library/vendor/Zend/Validate/Barcode.php222
-rw-r--r--library/vendor/Zend/Validate/Barcode/AdapterAbstract.php314
-rw-r--r--library/vendor/Zend/Validate/Barcode/AdapterInterface.php68
-rw-r--r--library/vendor/Zend/Validate/Barcode/Code25.php61
-rw-r--r--library/vendor/Zend/Validate/Barcode/Code25interleaved.php61
-rw-r--r--library/vendor/Zend/Validate/Barcode/Code39.php97
-rw-r--r--library/vendor/Zend/Validate/Barcode/Code39ext.php55
-rw-r--r--library/vendor/Zend/Validate/Barcode/Code93.php117
-rw-r--r--library/vendor/Zend/Validate/Barcode/Code93ext.php55
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean12.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean13.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean14.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean18.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean2.php55
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean5.php55
-rw-r--r--library/vendor/Zend/Validate/Barcode/Ean8.php68
-rw-r--r--library/vendor/Zend/Validate/Barcode/Gtin12.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Gtin13.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Gtin14.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Identcode.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Intelligentmail.php55
-rw-r--r--library/vendor/Zend/Validate/Barcode/Issn.php118
-rw-r--r--library/vendor/Zend/Validate/Barcode/Itf14.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Leitcode.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Planet.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Postnet.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Royalmail.php120
-rw-r--r--library/vendor/Zend/Validate/Barcode/Sscc.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Upca.php51
-rw-r--r--library/vendor/Zend/Validate/Barcode/Upce.php68
-rw-r--r--library/vendor/Zend/Validate/Between.php222
-rw-r--r--library/vendor/Zend/Validate/Callback.php170
-rw-r--r--library/vendor/Zend/Validate/Ccnum.php110
-rw-r--r--library/vendor/Zend/Validate/CreditCard.php316
-rw-r--r--library/vendor/Zend/Validate/Date.php253
-rw-r--r--library/vendor/Zend/Validate/Db/Abstract.php350
-rw-r--r--library/vendor/Zend/Validate/Db/NoRecordExists.php50
-rw-r--r--library/vendor/Zend/Validate/Db/RecordExists.php50
-rw-r--r--library/vendor/Zend/Validate/Digits.php89
-rw-r--r--library/vendor/Zend/Validate/EmailAddress.php571
-rw-r--r--library/vendor/Zend/Validate/Exception.php33
-rw-r--r--library/vendor/Zend/Validate/File/Count.php279
-rw-r--r--library/vendor/Zend/Validate/File/Crc32.php177
-rw-r--r--library/vendor/Zend/Validate/File/ExcludeExtension.php92
-rw-r--r--library/vendor/Zend/Validate/File/ExcludeMimeType.php99
-rw-r--r--library/vendor/Zend/Validate/File/Exists.php201
-rw-r--r--library/vendor/Zend/Validate/File/Extension.php235
-rw-r--r--library/vendor/Zend/Validate/File/FilesSize.php161
-rw-r--r--library/vendor/Zend/Validate/File/Hash.php190
-rw-r--r--library/vendor/Zend/Validate/File/ImageSize.php357
-rw-r--r--library/vendor/Zend/Validate/File/IsCompressed.php148
-rw-r--r--library/vendor/Zend/Validate/File/IsImage.php171
-rw-r--r--library/vendor/Zend/Validate/File/Md5.php179
-rw-r--r--library/vendor/Zend/Validate/File/MimeType.php468
-rw-r--r--library/vendor/Zend/Validate/File/NotExists.php83
-rw-r--r--library/vendor/Zend/Validate/File/Sha1.php179
-rw-r--r--library/vendor/Zend/Validate/File/Size.php398
-rw-r--r--library/vendor/Zend/Validate/File/Upload.php249
-rw-r--r--library/vendor/Zend/Validate/File/WordCount.php99
-rw-r--r--library/vendor/Zend/Validate/Float.php131
-rw-r--r--library/vendor/Zend/Validate/GreaterThan.php122
-rw-r--r--library/vendor/Zend/Validate/Hex.php71
-rw-r--r--library/vendor/Zend/Validate/Hostname.php1987
-rw-r--r--library/vendor/Zend/Validate/Hostname/Biz.php2917
-rw-r--r--library/vendor/Zend/Validate/Hostname/Cn.php2199
-rw-r--r--library/vendor/Zend/Validate/Hostname/Com.php196
-rw-r--r--library/vendor/Zend/Validate/Hostname/Jp.php739
-rw-r--r--library/vendor/Zend/Validate/Iban.php246
-rw-r--r--library/vendor/Zend/Validate/Identical.php163
-rw-r--r--library/vendor/Zend/Validate/InArray.php202
-rw-r--r--library/vendor/Zend/Validate/Int.php145
-rw-r--r--library/vendor/Zend/Validate/Interface.php54
-rw-r--r--library/vendor/Zend/Validate/Ip.php190
-rw-r--r--library/vendor/Zend/Validate/Isbn.php274
-rw-r--r--library/vendor/Zend/Validate/LessThan.php120
-rw-r--r--library/vendor/Zend/Validate/NotEmpty.php277
-rw-r--r--library/vendor/Zend/Validate/PostCode.php202
-rw-r--r--library/vendor/Zend/Validate/Regex.php143
-rw-r--r--library/vendor/Zend/Validate/Sitemap/Changefreq.php94
-rw-r--r--library/vendor/Zend/Validate/Sitemap/Lastmod.php87
-rw-r--r--library/vendor/Zend/Validate/Sitemap/Loc.php85
-rw-r--r--library/vendor/Zend/Validate/Sitemap/Priority.php81
-rw-r--r--library/vendor/Zend/Validate/StringLength.php263
-rw-r--r--library/vendor/Zend/View.php158
-rw-r--r--library/vendor/Zend/View/Abstract.php1186
-rw-r--r--library/vendor/Zend/View/Exception.php50
-rw-r--r--library/vendor/Zend/View/Helper/Abstract.php63
-rw-r--r--library/vendor/Zend/View/Helper/Action.php161
-rw-r--r--library/vendor/Zend/View/Helper/BaseUrl.php114
-rw-r--r--library/vendor/Zend/View/Helper/Cycle.php225
-rw-r--r--library/vendor/Zend/View/Helper/DeclareVars.php94
-rw-r--r--library/vendor/Zend/View/Helper/Doctype.php239
-rw-r--r--library/vendor/Zend/View/Helper/Fieldset.php78
-rw-r--r--library/vendor/Zend/View/Helper/Form.php85
-rw-r--r--library/vendor/Zend/View/Helper/FormButton.php104
-rw-r--r--library/vendor/Zend/View/Helper/FormCheckbox.php163
-rw-r--r--library/vendor/Zend/View/Helper/FormElement.php202
-rw-r--r--library/vendor/Zend/View/Helper/FormErrors.php166
-rw-r--r--library/vendor/Zend/View/Helper/FormFile.php74
-rw-r--r--library/vendor/Zend/View/Helper/FormHidden.php65
-rw-r--r--library/vendor/Zend/View/Helper/FormImage.php94
-rw-r--r--library/vendor/Zend/View/Helper/FormLabel.php71
-rw-r--r--library/vendor/Zend/View/Helper/FormMultiCheckbox.php73
-rw-r--r--library/vendor/Zend/View/Helper/FormNote.php60
-rw-r--r--library/vendor/Zend/View/Helper/FormPassword.php88
-rw-r--r--library/vendor/Zend/View/Helper/FormRadio.php185
-rw-r--r--library/vendor/Zend/View/Helper/FormReset.php81
-rw-r--r--library/vendor/Zend/View/Helper/FormSelect.php199
-rw-r--r--library/vendor/Zend/View/Helper/FormSubmit.php80
-rw-r--r--library/vendor/Zend/View/Helper/FormText.php77
-rw-r--r--library/vendor/Zend/View/Helper/FormTextarea.php103
-rw-r--r--library/vendor/Zend/View/Helper/Gravatar.php361
-rw-r--r--library/vendor/Zend/View/Helper/HeadLink.php471
-rw-r--r--library/vendor/Zend/View/Helper/HeadMeta.php440
-rw-r--r--library/vendor/Zend/View/Helper/HeadScript.php512
-rw-r--r--library/vendor/Zend/View/Helper/HeadStyle.php426
-rw-r--r--library/vendor/Zend/View/Helper/HeadTitle.php218
-rw-r--r--library/vendor/Zend/View/Helper/HtmlElement.php165
-rw-r--r--library/vendor/Zend/View/Helper/HtmlFlash.php59
-rw-r--r--library/vendor/Zend/View/Helper/HtmlList.php88
-rw-r--r--library/vendor/Zend/View/Helper/HtmlObject.php79
-rw-r--r--library/vendor/Zend/View/Helper/HtmlPage.php74
-rw-r--r--library/vendor/Zend/View/Helper/HtmlQuicktime.php81
-rw-r--r--library/vendor/Zend/View/Helper/InlineScript.php60
-rw-r--r--library/vendor/Zend/View/Helper/Interface.php46
-rw-r--r--library/vendor/Zend/View/Helper/Json.php86
-rw-r--r--library/vendor/Zend/View/Helper/Layout.php79
-rw-r--r--library/vendor/Zend/View/Helper/PaginationControl.php142
-rw-r--r--library/vendor/Zend/View/Helper/Partial.php150
-rw-r--r--library/vendor/Zend/View/Helper/Partial/Exception.php38
-rw-r--r--library/vendor/Zend/View/Helper/PartialLoop.php98
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder.php85
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder/Container.php35
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder/Container/Abstract.php384
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder/Container/Exception.php38
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder/Container/Standalone.php322
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder/Registry.php183
-rw-r--r--library/vendor/Zend/View/Helper/Placeholder/Registry/Exception.php38
-rw-r--r--library/vendor/Zend/View/Helper/RenderToPlaceholder.php52
-rw-r--r--library/vendor/Zend/View/Helper/ServerUrl.php148
-rw-r--r--library/vendor/Zend/View/Helper/Translate.php174
-rw-r--r--library/vendor/Zend/View/Helper/Url.php50
-rw-r--r--library/vendor/Zend/View/Interface.php137
-rw-r--r--library/vendor/Zend/View/Stream.php183
-rw-r--r--library/vendor/Zend/Xml/Exception.php35
-rw-r--r--library/vendor/Zend/Xml/Security.php486
-rw-r--r--library/vendor/dompdf/AUTHORS.md24
-rw-r--r--library/vendor/dompdf/LICENSE.LGPL456
-rw-r--r--library/vendor/dompdf/README.md232
-rw-r--r--library/vendor/dompdf/VERSION1
-rw-r--r--library/vendor/dompdf/autoload.inc.php1
-rw-r--r--library/vendor/dompdf/vendor/autoload.php7
-rw-r--r--library/vendor/dompdf/vendor/composer/ClassLoader.php572
-rw-r--r--library/vendor/dompdf/vendor/composer/InstalledVersions.php350
-rw-r--r--library/vendor/dompdf/vendor/composer/LICENSE21
-rw-r--r--library/vendor/dompdf/vendor/composer/autoload_classmap.php11
-rw-r--r--library/vendor/dompdf/vendor/composer/autoload_namespaces.php9
-rw-r--r--library/vendor/dompdf/vendor/composer/autoload_psr4.php14
-rw-r--r--library/vendor/dompdf/vendor/composer/autoload_real.php57
-rw-r--r--library/vendor/dompdf/vendor/composer/autoload_static.php66
-rw-r--r--library/vendor/dompdf/vendor/composer/installed.json295
-rw-r--r--library/vendor/dompdf/vendor/composer/installed.php68
-rw-r--r--library/vendor/dompdf/vendor/composer/platform_check.php26
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/AUTHORS.md24
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/LICENSE.LGPL456
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/README.md232
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/VERSION1
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/composer.json47
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/Cpdf.php6501
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Bold.afm344
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-BoldOblique.afm344
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Oblique.afm344
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier.afm344
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ttfbin0 -> 705684 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ufm6067
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ttfbin0 -> 643292 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ufm5712
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ttfbin0 -> 635416 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ufm5268
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttfbin0 -> 757076 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ufm6661
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ttfbin0 -> 331992 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ufm3285
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ttfbin0 -> 253580 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ufm2707
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ttfbin0 -> 251932 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ufm2707
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ttfbin0 -> 340712 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ufm3284
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ttfbin0 -> 356088 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ufm4013
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ttfbin0 -> 347460 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ufm3892
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ttfbin0 -> 345996 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ufm3883
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ttfbin0 -> 380132 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ufm4012
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Bold.afm2829
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-BoldOblique.afm2829
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Oblique.afm3053
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica.afm3053
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Symbol.afm213
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Bold.afm2590
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-BoldItalic.afm2386
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Italic.afm2669
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Roman.afm2421
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/ZapfDingbats.afm225
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/installed-fonts.dist.json80
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/mustRead.html17
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.pngbin0 -> 618 bytes
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.svg8
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/html.css518
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/CPDF.php944
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/GD.php929
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/PDFLib.php1450
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Canvas.php477
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/CanvasFactory.php58
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Cellmap.php999
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/AttributeTranslator.php652
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Color.php339
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Style.php3743
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Stylesheet.php1689
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Dompdf.php1470
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception.php27
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception/ImageException.php30
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FontMetrics.php635
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame.php1217
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php263
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php100
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php324
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php88
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php923
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php256
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php120
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php121
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php117
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php112
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php33
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php753
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php343
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php143
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php28
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRowGroup.php74
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php191
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php705
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php949
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php213
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php188
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php47
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php37
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Page.php199
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php523
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php161
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php82
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php71
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php605
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Helpers.php1093
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Image/Cache.php254
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/JavascriptEmbedder.php51
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/LineBox.php412
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Options.php1159
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/PhpEvaluator.php62
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Absolute.php128
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/AbstractPositioner.php48
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Block.php40
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Fixed.php92
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Inline.php52
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/ListBullet.php42
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/NullPositioner.php26
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableCell.php29
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableRow.php34
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer.php291
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/AbstractRenderer.php1244
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Block.php88
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Image.php90
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Inline.php126
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/ListBullet.php235
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableCell.php188
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableRowGroup.php40
-rw-r--r--library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Text.php158
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/CREDITS11
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/LICENSE.txt66
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/README.md270
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/UPGRADING.md21
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/bin/entities.php26
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/composer.json42
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5.php246
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Elements.php619
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Entities.php2236
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Exception.php10
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/InstructionProcessor.php41
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php61
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php705
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php114
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php33
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/InputStream.php87
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/ParseError.php10
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/README.md53
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Scanner.php416
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/StringInputStream.php331
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php1197
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php127
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/UTF8Utils.php183
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php1533
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/OutputRules.php553
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/README.md33
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/RulesInterface.php99
-rw-r--r--library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/Traverser.php142
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/LICENSE456
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/README.md28
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/bower.json23
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/composer.json32
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/index.php1
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/adobe-standard-encoding.map231
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1250.map251
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1251.map255
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1252.map251
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1253.map239
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1254.map249
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1255.map233
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1257.map244
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1258.map247
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp874.map225
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-1.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-11.map248
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-15.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-16.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-2.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-4.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-5.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-7.map250
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-9.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-r.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-u.map256
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/AdobeFontMetrics.php217
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Autoloader.php43
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/BinaryStream.php449
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/File.php159
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/Header.php113
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EncodingMap.php37
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Exception/FontNotFoundException.php11
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Font.php89
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/Outline.php109
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComponent.php31
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComposite.php242
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineSimple.php335
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Header.php37
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/File.php18
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/TableDirectoryEntry.php18
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/DirectoryEntry.php134
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Table.php93
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/cmap.php298
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/glyf.php154
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/head.php46
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hhea.php44
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hmtx.php59
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/kern.php80
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/loca.php80
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/maxp.php42
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/name.php193
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/nameRecord.php53
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/os2.php47
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/post.php143
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Collection.php100
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/File.php471
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Header.php31
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/TableDirectoryEntry.php33
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/File.php81
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/Header.php32
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/TableDirectoryEntry.php34
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/LICENSE165
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/README.md13
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/composer.json31
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/CssLength.php135
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/DefaultStyle.php29
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Document.php406
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Gradient/Stop.php16
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Style.php541
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php6418
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php495
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceInterface.php90
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfacePDFLib.php430
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/AbstractTag.php236
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Anchor.php14
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Circle.php36
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/ClipPath.php33
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Ellipse.php42
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Group.php33
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Image.php68
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Line.php43
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/LinearGradient.php83
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php576
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polygon.php42
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polyline.php40
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/RadialGradient.php17
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Rect.php50
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Shape.php63
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Stop.php17
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/StyleTag.php27
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Text.php72
-rw-r--r--library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/UseTag.php102
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/CHANGELOG.md241
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/LICENSE21
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/README.md632
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/composer.json69
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php83
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php143
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php479
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/Document.php172
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/KeyFrame.php104
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Comment.php71
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Commentable.php25
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormat.php334
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormatter.php231
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parser.php60
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/OutputException.php18
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/ParserState.php516
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/SourceException.php32
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php12
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php51
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/AtRule.php34
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/CSSNamespace.php154
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Charset.php129
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Import.php137
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/KeyframeSelector.php23
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Selector.php138
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Renderable.php21
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Rule/Rule.php392
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php73
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php831
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/RuleSet.php326
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Settings.php89
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSFunction.php73
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSString.php105
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcFunction.php89
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php24
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Color.php166
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/LineName.php65
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/PrimitiveValue.php14
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/RuleValueList.php15
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Size.php209
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/URL.php82
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Value.php198
-rw-r--r--library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/ValueList.php100
-rw-r--r--library/vendor/lessphp/CHANGES.md72
-rw-r--r--library/vendor/lessphp/LICENSE178
-rw-r--r--library/vendor/lessphp/README.md332
-rw-r--r--library/vendor/lessphp/SECURITY.md5
-rwxr-xr-xlibrary/vendor/lessphp/bin/lessc191
-rw-r--r--library/vendor/lessphp/composer.json52
-rw-r--r--library/vendor/lessphp/lessc.inc.php274
-rw-r--r--library/vendor/lessphp/lib/Less/Autoloader.php77
-rw-r--r--library/vendor/lessphp/lib/Less/Cache.php290
-rw-r--r--library/vendor/lessphp/lib/Less/Colors.php169
-rw-r--r--library/vendor/lessphp/lib/Less/Configurable.php65
-rw-r--r--library/vendor/lessphp/lib/Less/Environment.php156
-rw-r--r--library/vendor/lessphp/lib/Less/Exception/Chunk.php200
-rw-r--r--library/vendor/lessphp/lib/Less/Exception/Compiler.php11
-rw-r--r--library/vendor/lessphp/lib/Less/Exception/Parser.php107
-rw-r--r--library/vendor/lessphp/lib/Less/Functions.php1186
-rw-r--r--library/vendor/lessphp/lib/Less/Less.php.combine17
-rw-r--r--library/vendor/lessphp/lib/Less/Mime.php41
-rw-r--r--library/vendor/lessphp/lib/Less/Output.php48
-rw-r--r--library/vendor/lessphp/lib/Less/Output/Mapped.php119
-rw-r--r--library/vendor/lessphp/lib/Less/Parser.php2676
-rw-r--r--library/vendor/lessphp/lib/Less/SourceMap/Base64VLQ.php187
-rw-r--r--library/vendor/lessphp/lib/Less/SourceMap/Generator.php354
-rw-r--r--library/vendor/lessphp/lib/Less/Tree.php84
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Alpha.php48
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Anonymous.php58
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Assignment.php39
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Attribute.php53
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Call.php117
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Color.php230
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Comment.php51
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Condition.php72
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/DefaultFunc.php34
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/DetachedRuleset.php39
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Dimension.php198
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Directive.php96
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Element.php70
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Expression.php95
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Extend.php80
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Import.php289
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Javascript.php30
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Keyword.php43
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Media.php173
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Mixin/Call.php193
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Mixin/Definition.php233
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/NameValue.php49
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Negative.php37
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Operation.php68
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Paren.php35
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Quoted.php79
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Rule.php112
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Ruleset.php620
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/RulesetCall.php26
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Selector.php172
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/UnicodeDescriptor.php28
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Unit.php142
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/UnitConversions.php35
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Url.php76
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Value.php47
-rw-r--r--library/vendor/lessphp/lib/Less/Tree/Variable.php51
-rw-r--r--library/vendor/lessphp/lib/Less/Version.php15
-rw-r--r--library/vendor/lessphp/lib/Less/Visitor.php46
-rw-r--r--library/vendor/lessphp/lib/Less/Visitor/extendFinder.php109
-rw-r--r--library/vendor/lessphp/lib/Less/Visitor/import.php137
-rw-r--r--library/vendor/lessphp/lib/Less/Visitor/joinSelector.php68
-rw-r--r--library/vendor/lessphp/lib/Less/Visitor/processExtends.php441
-rw-r--r--library/vendor/lessphp/lib/Less/Visitor/toCSS.php280
-rw-r--r--library/vendor/lessphp/lib/Less/VisitorReplacing.php70
-rw-r--r--modules/doc/application/controllers/IcingawebController.php62
-rw-r--r--modules/doc/application/controllers/IndexController.php27
-rw-r--r--modules/doc/application/controllers/ModuleController.php206
-rw-r--r--modules/doc/application/controllers/SearchController.php97
-rw-r--r--modules/doc/application/controllers/StyleController.php41
-rw-r--r--modules/doc/application/views/scripts/chapter.phtml6
-rw-r--r--modules/doc/application/views/scripts/index/index.phtml19
-rw-r--r--modules/doc/application/views/scripts/module/index.phtml19
-rw-r--r--modules/doc/application/views/scripts/pdf.phtml5
-rw-r--r--modules/doc/application/views/scripts/search/index.phtml8
-rw-r--r--modules/doc/application/views/scripts/style/font.phtml15
-rw-r--r--modules/doc/application/views/scripts/style/guide.phtml112
-rw-r--r--modules/doc/application/views/scripts/toc.phtml6
-rw-r--r--modules/doc/configuration.php24
-rw-r--r--modules/doc/doc/01-About.md6
-rw-r--r--modules/doc/doc/02-Installation.md15
-rw-r--r--modules/doc/doc/03-Module-Documentation.md87
-rw-r--r--modules/doc/doc/img/markdown.pngbin0 -> 2180 bytes
-rw-r--r--modules/doc/library/Doc/DocController.php116
-rw-r--r--modules/doc/library/Doc/DocParser.php235
-rw-r--r--modules/doc/library/Doc/DocSection.php159
-rw-r--r--modules/doc/library/Doc/DocSectionFilterIterator.php73
-rw-r--r--modules/doc/library/Doc/Exception/ChapterNotFoundException.php11
-rw-r--r--modules/doc/library/Doc/Exception/DocException.php13
-rw-r--r--modules/doc/library/Doc/Renderer/DocRenderer.php208
-rw-r--r--modules/doc/library/Doc/Renderer/DocSearchRenderer.php131
-rw-r--r--modules/doc/library/Doc/Renderer/DocSectionRenderer.php346
-rw-r--r--modules/doc/library/Doc/Renderer/DocTocRenderer.php117
-rw-r--r--modules/doc/library/Doc/Search/DocSearch.php95
-rw-r--r--modules/doc/library/Doc/Search/DocSearchIterator.php113
-rw-r--r--modules/doc/library/Doc/Search/DocSearchMatch.php215
-rw-r--r--modules/doc/module.info4
-rw-r--r--modules/doc/public/css/module.less109
-rw-r--r--modules/doc/public/js/module.js30
-rw-r--r--modules/doc/run.php64
-rw-r--r--modules/migrate/application/clicommands/ConfigCommand.php119
-rw-r--r--modules/migrate/application/clicommands/NavigationCommand.php195
-rw-r--r--modules/migrate/application/clicommands/PreferencesCommand.php131
-rw-r--r--modules/migrate/library/Migrate/Config/UserDomainMigration.php378
-rw-r--r--modules/migrate/module.info5
-rw-r--r--modules/monitoring/application/clicommands/ListCommand.php400
-rw-r--r--modules/monitoring/application/clicommands/NrpeCommand.php58
-rw-r--r--modules/monitoring/application/controllers/ActionsController.php135
-rw-r--r--modules/monitoring/application/controllers/CommentController.php91
-rw-r--r--modules/monitoring/application/controllers/CommentsController.php108
-rw-r--r--modules/monitoring/application/controllers/ConfigController.php298
-rw-r--r--modules/monitoring/application/controllers/DowntimeController.php108
-rw-r--r--modules/monitoring/application/controllers/DowntimesController.php108
-rw-r--r--modules/monitoring/application/controllers/EventController.php551
-rw-r--r--modules/monitoring/application/controllers/HealthController.php196
-rw-r--r--modules/monitoring/application/controllers/HostController.php185
-rw-r--r--modules/monitoring/application/controllers/HostsController.php260
-rw-r--r--modules/monitoring/application/controllers/ListController.php808
-rw-r--r--modules/monitoring/application/controllers/ServiceController.php147
-rw-r--r--modules/monitoring/application/controllers/ServicesController.php262
-rw-r--r--modules/monitoring/application/controllers/ShowController.php101
-rw-r--r--modules/monitoring/application/controllers/TacticalController.php128
-rw-r--r--modules/monitoring/application/controllers/TimelineController.php325
-rw-r--r--modules/monitoring/application/forms/Command/CommandForm.php92
-rw-r--r--modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php64
-rw-r--r--modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php279
-rw-r--r--modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php172
-rw-r--r--modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php148
-rw-r--r--modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php87
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php109
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php89
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php129
-rw-r--r--modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php89
-rw-r--r--modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php47
-rw-r--r--modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php139
-rw-r--r--modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php122
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php67
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php178
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php112
-rw-r--r--modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php263
-rw-r--r--modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php110
-rw-r--r--modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php187
-rw-r--r--modules/monitoring/application/forms/Config/BackendConfigForm.php367
-rw-r--r--modules/monitoring/application/forms/Config/SecurityConfigForm.php75
-rw-r--r--modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php75
-rw-r--r--modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php37
-rw-r--r--modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php185
-rw-r--r--modules/monitoring/application/forms/Config/TransportConfigForm.php392
-rw-r--r--modules/monitoring/application/forms/Config/TransportReorderForm.php87
-rw-r--r--modules/monitoring/application/forms/EventOverviewForm.php157
-rw-r--r--modules/monitoring/application/forms/Navigation/ActionForm.php79
-rw-r--r--modules/monitoring/application/forms/Navigation/HostActionForm.php8
-rw-r--r--modules/monitoring/application/forms/Navigation/ServiceActionForm.php8
-rw-r--r--modules/monitoring/application/forms/Setup/BackendPage.php51
-rw-r--r--modules/monitoring/application/forms/Setup/IdoResourcePage.php188
-rw-r--r--modules/monitoring/application/forms/Setup/SecurityPage.php27
-rw-r--r--modules/monitoring/application/forms/Setup/TransportPage.php55
-rw-r--r--modules/monitoring/application/forms/Setup/WelcomePage.php63
-rw-r--r--modules/monitoring/application/forms/StatehistoryForm.php141
-rw-r--r--modules/monitoring/application/views/helpers/CheckPerformance.php50
-rw-r--r--modules/monitoring/application/views/helpers/ContactFlags.php46
-rw-r--r--modules/monitoring/application/views/helpers/Customvar.php62
-rw-r--r--modules/monitoring/application/views/helpers/EscapeComment.php38
-rw-r--r--modules/monitoring/application/views/helpers/HostFlags.php33
-rw-r--r--modules/monitoring/application/views/helpers/IconImage.php64
-rw-r--r--modules/monitoring/application/views/helpers/Link.php72
-rw-r--r--modules/monitoring/application/views/helpers/MonitoringFlags.php40
-rw-r--r--modules/monitoring/application/views/helpers/Perfdata.php116
-rw-r--r--modules/monitoring/application/views/helpers/PluginOutput.php199
-rw-r--r--modules/monitoring/application/views/helpers/RuntimeVariables.php50
-rw-r--r--modules/monitoring/application/views/helpers/ServiceFlags.php33
-rw-r--r--modules/monitoring/application/views/scripts/comment/remove.phtml11
-rw-r--r--modules/monitoring/application/views/scripts/comment/show.phtml86
-rw-r--r--modules/monitoring/application/views/scripts/comments/delete-all.phtml12
-rw-r--r--modules/monitoring/application/views/scripts/comments/show.phtml19
-rw-r--r--modules/monitoring/application/views/scripts/config/form.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/config/index.phtml78
-rw-r--r--modules/monitoring/application/views/scripts/config/security.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/downtime/remove.phtml13
-rw-r--r--modules/monitoring/application/views/scripts/downtime/show.phtml173
-rw-r--r--modules/monitoring/application/views/scripts/downtimes/delete-all.phtml12
-rw-r--r--modules/monitoring/application/views/scripts/downtimes/show.phtml19
-rw-r--r--modules/monitoring/application/views/scripts/event/show.phtml34
-rw-r--r--modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml93
-rw-r--r--modules/monitoring/application/views/scripts/health/disable-notifications.phtml20
-rw-r--r--modules/monitoring/application/views/scripts/health/info.phtml87
-rw-r--r--modules/monitoring/application/views/scripts/health/not-running.phtml8
-rw-r--r--modules/monitoring/application/views/scripts/health/stats.phtml150
-rw-r--r--modules/monitoring/application/views/scripts/host/services.phtml23
-rw-r--r--modules/monitoring/application/views/scripts/host/show.phtml14
-rw-r--r--modules/monitoring/application/views/scripts/hosts/show.phtml206
-rw-r--r--modules/monitoring/application/views/scripts/list/comments.phtml61
-rw-r--r--modules/monitoring/application/views/scripts/list/components/hostssummary.phtml92
-rw-r--r--modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/list/components/servicesummary.phtml118
-rw-r--r--modules/monitoring/application/views/scripts/list/contactgroups.phtml53
-rw-r--r--modules/monitoring/application/views/scripts/list/contacts.phtml83
-rw-r--r--modules/monitoring/application/views/scripts/list/downtimes.phtml64
-rw-r--r--modules/monitoring/application/views/scripts/list/eventgrid.phtml123
-rw-r--r--modules/monitoring/application/views/scripts/list/eventhistory.phtml22
-rw-r--r--modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml173
-rw-r--r--modules/monitoring/application/views/scripts/list/hostgroups.phtml296
-rw-r--r--modules/monitoring/application/views/scripts/list/hosts.phtml106
-rw-r--r--modules/monitoring/application/views/scripts/list/notifications.phtml124
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml144
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegrid.phtml144
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml217
-rw-r--r--modules/monitoring/application/views/scripts/list/servicegroups.phtml184
-rw-r--r--modules/monitoring/application/views/scripts/list/services.phtml161
-rw-r--r--modules/monitoring/application/views/scripts/object/detail-history.phtml13
-rw-r--r--modules/monitoring/application/views/scripts/object/detail-tabhook.phtml21
-rw-r--r--modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml18
-rw-r--r--modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml24
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml82
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml10
-rw-r--r--modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml32
-rw-r--r--modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml101
-rw-r--r--modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml40
-rw-r--r--modules/monitoring/application/views/scripts/partials/event-history.phtml267
-rw-r--r--modules/monitoring/application/views/scripts/partials/host/objects-header.phtml41
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/detail-content.phtml53
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/host-header.phtml51
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml144
-rw-r--r--modules/monitoring/application/views/scripts/partials/object/service-header.phtml72
-rw-r--r--modules/monitoring/application/views/scripts/partials/service/objects-header.phtml45
-rw-r--r--modules/monitoring/application/views/scripts/partials/show-more.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/service/show.phtml8
-rw-r--r--modules/monitoring/application/views/scripts/services/show.phtml208
-rw-r--r--modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml94
-rw-r--r--modules/monitoring/application/views/scripts/show/components/actions.phtml43
-rw-r--r--modules/monitoring/application/views/scripts/show/components/checksource.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml85
-rw-r--r--modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml21
-rw-r--r--modules/monitoring/application/views/scripts/show/components/command.phtml52
-rw-r--r--modules/monitoring/application/views/scripts/show/components/comments.phtml86
-rw-r--r--modules/monitoring/application/views/scripts/show/components/contacts.phtml38
-rw-r--r--modules/monitoring/application/views/scripts/show/components/downtime.phtml109
-rw-r--r--modules/monitoring/application/views/scripts/show/components/extensions.phtml4
-rw-r--r--modules/monitoring/application/views/scripts/show/components/flags.phtml4
-rw-r--r--modules/monitoring/application/views/scripts/show/components/flapping.phtml14
-rw-r--r--modules/monitoring/application/views/scripts/show/components/grapher.phtml6
-rw-r--r--modules/monitoring/application/views/scripts/show/components/hostgroups.phtml19
-rw-r--r--modules/monitoring/application/views/scripts/show/components/notes.phtml48
-rw-r--r--modules/monitoring/application/views/scripts/show/components/notifications.phtml68
-rw-r--r--modules/monitoring/application/views/scripts/show/components/output.phtml5
-rw-r--r--modules/monitoring/application/views/scripts/show/components/perfdata.phtml4
-rw-r--r--modules/monitoring/application/views/scripts/show/components/reachable.phtml15
-rw-r--r--modules/monitoring/application/views/scripts/show/components/servicegroups.phtml20
-rw-r--r--modules/monitoring/application/views/scripts/show/components/status.phtml0
-rw-r--r--modules/monitoring/application/views/scripts/show/contact.phtml70
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml131
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml287
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml81
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml394
-rw-r--r--modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml74
-rw-r--r--modules/monitoring/application/views/scripts/tactical/index.phtml145
-rw-r--r--modules/monitoring/application/views/scripts/timeline/index.phtml145
-rw-r--r--modules/monitoring/configuration.php431
-rw-r--r--modules/monitoring/doc/01-About.md10
-rw-r--r--modules/monitoring/doc/02-Installation.md15
-rw-r--r--modules/monitoring/doc/03-Configuration.md69
-rw-r--r--modules/monitoring/doc/04-Backends.md30
-rw-r--r--modules/monitoring/doc/05-Command-Transports.md185
-rw-r--r--modules/monitoring/doc/06-Security.md66
-rw-r--r--modules/monitoring/doc/10-Restrict-Custom-Variables.md77
-rw-r--r--modules/monitoring/doc/11-Add-Columns-List-Views.md32
-rw-r--r--modules/monitoring/doc/20-Hooks.md161
-rw-r--r--modules/monitoring/doc/img/hooks-detailviewextension-01.pngbin0 -> 10714 bytes
-rw-r--r--modules/monitoring/doc/img/list_hosts_add_columns.pngbin0 -> 187915 bytes
-rw-r--r--modules/monitoring/doc/img/list_services_add_columns.pngbin0 -> 209925 bytes
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php10
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php74
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php61
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php158
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php39
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php139
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php214
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php116
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php163
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php42
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php38
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php51
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php57
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php16
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php15
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php134
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php49
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php36
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php131
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php202
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php44
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php197
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php247
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php208
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php40
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php204
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php31
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php200
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php295
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php142
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php283
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php222
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php338
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php91
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php1599
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php26
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php144
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php52
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php142
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php68
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php80
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php18
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php218
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php44
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php195
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php235
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php222
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php40
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php202
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php31
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php197
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php303
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php113
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php286
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php220
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php524
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php104
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php41
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php179
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php243
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php48
-rw-r--r--modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php48
-rw-r--r--modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php348
-rw-r--r--modules/monitoring/library/Monitoring/BackendStep.php206
-rw-r--r--modules/monitoring/library/Monitoring/Cli/CliUtils.php122
-rw-r--r--modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php126
-rw-r--r--modules/monitoring/library/Monitoring/Command/IcingaCommand.php21
-rw-r--r--modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php42
-rw-r--r--modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php122
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php144
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php80
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php40
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php38
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php110
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php110
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php61
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php176
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php48
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php21
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php48
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php75
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php92
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php190
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php82
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php113
-rw-r--r--modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php42
-rw-r--r--modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php324
-rw-r--r--modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php478
-rw-r--r--modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php11
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php291
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php170
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php22
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php168
-rw-r--r--modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php465
-rw-r--r--modules/monitoring/library/Monitoring/Controller.php159
-rw-r--r--modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php30
-rw-r--r--modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php25
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Command.php24
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Comment.php82
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Commentevent.php30
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Contact.php73
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Contactgroup.php57
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Customvar.php47
-rw-r--r--modules/monitoring/library/Monitoring/DataView/DataView.php608
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Downtime.php96
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Downtimeevent.php33
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventgrid.php60
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php7
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventgridservices.php7
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Eventhistory.php60
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Flappingevent.php27
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostcomment.php45
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostcontact.php17
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostdowntime.php50
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostgroup.php34
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php81
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hoststatus.php129
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php40
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Instance.php33
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Notification.php59
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Notificationevent.php29
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Programstatus.php44
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Runtimesummary.php38
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Runtimevariables.php34
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicecomment.php48
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicecontact.php8
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicedowntime.php50
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicegroup.php31
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php75
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicestatus.php180
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php45
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Statechangeevent.php32
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Statussummary.php111
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php28
-rw-r--r--modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php28
-rw-r--r--modules/monitoring/library/Monitoring/Exception/CommandTransportException.php13
-rw-r--r--modules/monitoring/library/Monitoring/Exception/CurlException.php13
-rw-r--r--modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php11
-rw-r--r--modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php98
-rw-r--r--modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php20
-rw-r--r--modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php126
-rw-r--r--modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php79
-rw-r--r--modules/monitoring/library/Monitoring/Hook/HostActionsHook.php52
-rw-r--r--modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php47
-rw-r--r--modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php60
-rw-r--r--modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php46
-rw-r--r--modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php52
-rw-r--r--modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php37
-rw-r--r--modules/monitoring/library/Monitoring/MonitoringWizard.php159
-rw-r--r--modules/monitoring/library/Monitoring/Object/Acknowledgement.php215
-rw-r--r--modules/monitoring/library/Monitoring/Object/Host.php204
-rw-r--r--modules/monitoring/library/Monitoring/Object/HostList.php133
-rw-r--r--modules/monitoring/library/Monitoring/Object/Macro.php82
-rw-r--r--modules/monitoring/library/Monitoring/Object/MonitoredObject.php930
-rw-r--r--modules/monitoring/library/Monitoring/Object/ObjectList.php293
-rw-r--r--modules/monitoring/library/Monitoring/Object/Service.php219
-rw-r--r--modules/monitoring/library/Monitoring/Object/ServiceList.php184
-rw-r--r--modules/monitoring/library/Monitoring/Plugin.php12
-rw-r--r--modules/monitoring/library/Monitoring/Plugin/Perfdata.php550
-rw-r--r--modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php144
-rw-r--r--modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php179
-rw-r--r--modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php32
-rw-r--r--modules/monitoring/library/Monitoring/ProvidedHook/Health.php102
-rw-r--r--modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php35
-rw-r--r--modules/monitoring/library/Monitoring/SecurityStep.php84
-rw-r--r--modules/monitoring/library/Monitoring/Timeline/TimeEntry.php233
-rw-r--r--modules/monitoring/library/Monitoring/Timeline/TimeLine.php491
-rw-r--r--modules/monitoring/library/Monitoring/Timeline/TimeRange.php258
-rw-r--r--modules/monitoring/library/Monitoring/TransportStep.php143
-rw-r--r--modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php337
-rw-r--r--modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php105
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php15
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/Action.php123
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php167
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php11
-rw-r--r--modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php297
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php270
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php120
-rw-r--r--modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php341
-rw-r--r--modules/monitoring/module.info5
-rw-r--r--modules/monitoring/public/css/module.less1919
-rw-r--r--modules/monitoring/public/css/service-grid.less75
-rw-r--r--modules/monitoring/public/css/tables.less282
-rw-r--r--modules/monitoring/public/js/module.js84
-rw-r--r--modules/monitoring/run.php8
-rw-r--r--modules/setup/application/clicommands/ConfigCommand.php185
-rw-r--r--modules/setup/application/clicommands/TokenCommand.php89
-rw-r--r--modules/setup/application/controllers/IndexController.php91
-rw-r--r--modules/setup/application/forms/AdminAccountPage.php423
-rw-r--r--modules/setup/application/forms/AuthBackendPage.php273
-rw-r--r--modules/setup/application/forms/AuthenticationPage.php69
-rw-r--r--modules/setup/application/forms/DatabaseCreationPage.php208
-rw-r--r--modules/setup/application/forms/DbResourcePage.php183
-rw-r--r--modules/setup/application/forms/GeneralConfigPage.php41
-rw-r--r--modules/setup/application/forms/LdapDiscoveryConfirmPage.php133
-rw-r--r--modules/setup/application/forms/LdapDiscoveryPage.php115
-rw-r--r--modules/setup/application/forms/LdapResourcePage.php152
-rw-r--r--modules/setup/application/forms/ModulePage.php108
-rw-r--r--modules/setup/application/forms/RequirementsPage.php68
-rw-r--r--modules/setup/application/forms/SummaryPage.php84
-rw-r--r--modules/setup/application/forms/UserGroupBackendPage.php147
-rw-r--r--modules/setup/application/forms/WelcomePage.php45
-rw-r--r--modules/setup/application/views/scripts/form/setup-modules.phtml33
-rw-r--r--modules/setup/application/views/scripts/form/setup-requirements.phtml48
-rw-r--r--modules/setup/application/views/scripts/form/setup-summary.phtml40
-rw-r--r--modules/setup/application/views/scripts/form/setup-welcome.phtml120
-rw-r--r--modules/setup/application/views/scripts/index/index.phtml153
-rw-r--r--modules/setup/application/views/scripts/index/parts/finish.phtml34
-rw-r--r--modules/setup/application/views/scripts/index/parts/wizard.phtml1
-rw-r--r--modules/setup/library/Setup/Exception/SetupException.php22
-rw-r--r--modules/setup/library/Setup/Requirement.php343
-rw-r--r--modules/setup/library/Setup/Requirement/ClassRequirement.php48
-rw-r--r--modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php42
-rw-r--r--modules/setup/library/Setup/Requirement/OSRequirement.php27
-rw-r--r--modules/setup/library/Setup/Requirement/PhpConfigRequirement.php22
-rw-r--r--modules/setup/library/Setup/Requirement/PhpModuleRequirement.php42
-rw-r--r--modules/setup/library/Setup/Requirement/PhpVersionRequirement.php28
-rw-r--r--modules/setup/library/Setup/Requirement/SetRequirement.php34
-rw-r--r--modules/setup/library/Setup/Requirement/WebLibraryRequirement.php24
-rw-r--r--modules/setup/library/Setup/Requirement/WebModuleRequirement.php31
-rw-r--r--modules/setup/library/Setup/RequirementSet.php335
-rw-r--r--modules/setup/library/Setup/RequirementsRenderer.php64
-rw-r--r--modules/setup/library/Setup/Setup.php99
-rw-r--r--modules/setup/library/Setup/SetupWizard.php24
-rw-r--r--modules/setup/library/Setup/Step.php31
-rw-r--r--modules/setup/library/Setup/Steps/AuthenticationStep.php238
-rw-r--r--modules/setup/library/Setup/Steps/DatabaseStep.php266
-rw-r--r--modules/setup/library/Setup/Steps/GeneralConfigStep.php131
-rw-r--r--modules/setup/library/Setup/Steps/ResourceStep.php199
-rw-r--r--modules/setup/library/Setup/Steps/UserGroupStep.php213
-rw-r--r--modules/setup/library/Setup/Utils/DbTool.php943
-rw-r--r--modules/setup/library/Setup/Utils/EnableModuleStep.php77
-rw-r--r--modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php73
-rw-r--r--modules/setup/library/Setup/WebWizard.php752
-rw-r--r--modules/setup/library/Setup/Webserver.php233
-rw-r--r--modules/setup/library/Setup/Webserver/Apache.php142
-rw-r--r--modules/setup/library/Setup/Webserver/Nginx.php36
-rw-r--r--modules/setup/module.info6
-rw-r--r--modules/translation/application/clicommands/CompileCommand.php40
-rw-r--r--modules/translation/application/clicommands/RefreshCommand.php40
-rw-r--r--modules/translation/application/clicommands/TestCommand.php140
-rw-r--r--modules/translation/doc/01-About.md6
-rw-r--r--modules/translation/doc/02-Installation.md15
-rw-r--r--modules/translation/doc/03-Translation.md204
-rw-r--r--modules/translation/doc/img/poedit_001.pngbin0 -> 24252 bytes
-rw-r--r--modules/translation/doc/img/poedit_002.pngbin0 -> 40936 bytes
-rw-r--r--modules/translation/doc/img/poedit_003.pngbin0 -> 21482 bytes
-rw-r--r--modules/translation/doc/img/poedit_004.pngbin0 -> 40052 bytes
-rw-r--r--modules/translation/doc/img/poedit_005.pngbin0 -> 23752 bytes
-rw-r--r--modules/translation/library/Translation/Cli/ArrayToTextTableHelper.php232
-rw-r--r--modules/translation/library/Translation/Cli/TranslationCommand.php73
-rw-r--r--modules/translation/library/Translation/Util/GettextTranslationHelper.php442
-rw-r--r--modules/translation/module.info7
-rw-r--r--public/css/icinga/about.less99
-rw-r--r--public/css/icinga/animation.less366
-rw-r--r--public/css/icinga/audit.less363
-rw-r--r--public/css/icinga/badges.less22
-rw-r--r--public/css/icinga/base.less344
-rw-r--r--public/css/icinga/compat.less35
-rw-r--r--public/css/icinga/configmenu.less303
-rw-r--r--public/css/icinga/controls.less281
-rw-r--r--public/css/icinga/dev.less10
-rw-r--r--public/css/icinga/forms.less596
-rw-r--r--public/css/icinga/grid.less47
-rw-r--r--public/css/icinga/health.less69
-rw-r--r--public/css/icinga/layout-structure.less167
-rw-r--r--public/css/icinga/layout.less379
-rw-r--r--public/css/icinga/login-orbs.less104
-rw-r--r--public/css/icinga/login.less183
-rw-r--r--public/css/icinga/main.less452
-rw-r--r--public/css/icinga/menu.less554
-rw-r--r--public/css/icinga/mixins.less201
-rw-r--r--public/css/icinga/modal.less113
-rw-r--r--public/css/icinga/nav.less50
-rw-r--r--public/css/icinga/php-diff.less17
-rw-r--r--public/css/icinga/print.less39
-rw-r--r--public/css/icinga/responsive.less167
-rw-r--r--public/css/icinga/setup.less479
-rw-r--r--public/css/icinga/spinner.less42
-rw-r--r--public/css/icinga/tabs.less105
-rw-r--r--public/css/icinga/widgets.less643
-rw-r--r--public/css/modes/light.less4
-rw-r--r--public/css/modes/none.less4
-rw-r--r--public/css/modes/system.less4
-rw-r--r--public/css/pdf/pdfprint.less103
-rw-r--r--public/css/themes/Winter.less32
-rw-r--r--public/css/themes/colorblind.less29
-rw-r--r--public/css/themes/high-contrast.less250
-rw-r--r--public/css/vendor/normalize.css427
-rw-r--r--public/error_norewrite.html1
-rw-r--r--public/error_unavailable.html7
-rw-r--r--public/font/ifont.eotbin0 -> 45480 bytes
-rw-r--r--public/font/ifont.svg286
-rw-r--r--public/font/ifont.ttfbin0 -> 45324 bytes
-rw-r--r--public/font/ifont.woffbin0 -> 27320 bytes
-rw-r--r--public/font/ifont.woff2bin0 -> 22700 bytes
-rw-r--r--public/img/favicon.pngbin0 -> 806 bytes
-rw-r--r--public/img/icinga-loader-light.gifbin0 -> 94929 bytes
-rw-r--r--public/img/icinga-loader.gifbin0 -> 32942 bytes
-rw-r--r--public/img/icinga-logo-big-dark.pngbin0 -> 3491 bytes
-rw-r--r--public/img/icinga-logo-big-dark.svg14
-rw-r--r--public/img/icinga-logo-big.pngbin0 -> 3586 bytes
-rw-r--r--public/img/icinga-logo-big.svg1
-rw-r--r--public/img/icinga-logo-compact-inverted.svg9
-rw-r--r--public/img/icinga-logo-compact.svg30
-rw-r--r--public/img/icinga-logo-dark.svg30
-rw-r--r--public/img/icinga-logo-inverted.svg1
-rw-r--r--public/img/icinga-logo.pngbin0 -> 1132 bytes
-rw-r--r--public/img/icinga-logo.svg32
-rw-r--r--public/img/icingaweb2-background-orbs.jpgbin0 -> 1467100 bytes
-rw-r--r--public/img/icingaweb2-background.jpgbin0 -> 305235 bytes
-rw-r--r--public/img/icons/acknowledgement.pngbin0 -> 501 bytes
-rw-r--r--public/img/icons/acknowledgement_petrol.pngbin0 -> 492 bytes
-rw-r--r--public/img/icons/active_checks_disabled.pngbin0 -> 511 bytes
-rw-r--r--public/img/icons/active_checks_disabled_petrol.pngbin0 -> 513 bytes
-rw-r--r--public/img/icons/active_passive_checks_disabled.pngbin0 -> 537 bytes
-rw-r--r--public/img/icons/active_passive_checks_disabled_petrol.pngbin0 -> 546 bytes
-rw-r--r--public/img/icons/comment.pngbin0 -> 491 bytes
-rw-r--r--public/img/icons/comment_petrol.pngbin0 -> 502 bytes
-rw-r--r--public/img/icons/configuration.pngbin0 -> 647 bytes
-rw-r--r--public/img/icons/configuration_petrol.pngbin0 -> 645 bytes
-rw-r--r--public/img/icons/create.pngbin0 -> 475 bytes
-rw-r--r--public/img/icons/create_petrol.pngbin0 -> 482 bytes
-rw-r--r--public/img/icons/csv.pngbin0 -> 509 bytes
-rw-r--r--public/img/icons/csv_petrol.pngbin0 -> 511 bytes
-rw-r--r--public/img/icons/dashboard.pngbin0 -> 415 bytes
-rw-r--r--public/img/icons/dashboard_petrol.pngbin0 -> 420 bytes
-rw-r--r--public/img/icons/disabled.pngbin0 -> 535 bytes
-rw-r--r--public/img/icons/disabled_petrol.pngbin0 -> 525 bytes
-rw-r--r--public/img/icons/down.pngbin0 -> 492 bytes
-rw-r--r--public/img/icons/down_petrol.pngbin0 -> 505 bytes
-rw-r--r--public/img/icons/downtime_end.pngbin0 -> 550 bytes
-rw-r--r--public/img/icons/downtime_end_petrol.pngbin0 -> 561 bytes
-rw-r--r--public/img/icons/downtime_start.pngbin0 -> 524 bytes
-rw-r--r--public/img/icons/downtime_start__petrol.pngbin0 -> 529 bytes
-rw-r--r--public/img/icons/edit.pngbin0 -> 486 bytes
-rw-r--r--public/img/icons/edit_petrol.pngbin0 -> 493 bytes
-rw-r--r--public/img/icons/error.pngbin0 -> 532 bytes
-rw-r--r--public/img/icons/error_petrol.pngbin0 -> 535 bytes
-rw-r--r--public/img/icons/error_white.pngbin0 -> 429 bytes
-rw-r--r--public/img/icons/expand.pngbin0 -> 2993 bytes
-rw-r--r--public/img/icons/expand_petrol.pngbin0 -> 2992 bytes
-rw-r--r--public/img/icons/flapping.pngbin0 -> 621 bytes
-rw-r--r--public/img/icons/flapping_petrol.pngbin0 -> 636 bytes
-rw-r--r--public/img/icons/history.pngbin0 -> 516 bytes
-rw-r--r--public/img/icons/history_petrol.pngbin0 -> 520 bytes
-rw-r--r--public/img/icons/host.pngbin0 -> 500 bytes
-rw-r--r--public/img/icons/host_petrol.pngbin0 -> 489 bytes
-rw-r--r--public/img/icons/hostgroup.pngbin0 -> 541 bytes
-rw-r--r--public/img/icons/hostgroup_petrol.pngbin0 -> 554 bytes
-rw-r--r--public/img/icons/in_downtime.pngbin0 -> 490 bytes
-rw-r--r--public/img/icons/in_downtime_petrol.pngbin0 -> 497 bytes
-rw-r--r--public/img/icons/json.pngbin0 -> 517 bytes
-rw-r--r--public/img/icons/json_petrol.pngbin0 -> 525 bytes
-rw-r--r--public/img/icons/logout.pngbin0 -> 462 bytes
-rw-r--r--public/img/icons/logout_petrol.pngbin0 -> 470 bytes
-rw-r--r--public/img/icons/next.pngbin0 -> 509 bytes
-rw-r--r--public/img/icons/next_petrol.pngbin0 -> 513 bytes
-rw-r--r--public/img/icons/notification.pngbin0 -> 544 bytes
-rw-r--r--public/img/icons/notification_disabled.pngbin0 -> 487 bytes
-rw-r--r--public/img/icons/notification_disabled_petrol.pngbin0 -> 494 bytes
-rw-r--r--public/img/icons/notification_petrol.pngbin0 -> 548 bytes
-rw-r--r--public/img/icons/pdf.pngbin0 -> 507 bytes
-rw-r--r--public/img/icons/pdf_petrol.pngbin0 -> 511 bytes
-rw-r--r--public/img/icons/prev.pngbin0 -> 514 bytes
-rw-r--r--public/img/icons/prev_petrol.pngbin0 -> 519 bytes
-rw-r--r--public/img/icons/refresh.pngbin0 -> 523 bytes
-rw-r--r--public/img/icons/refresh_petrol.pngbin0 -> 517 bytes
-rw-r--r--public/img/icons/remove.pngbin0 -> 661 bytes
-rw-r--r--public/img/icons/remove_petrol.pngbin0 -> 661 bytes
-rw-r--r--public/img/icons/reschedule.pngbin0 -> 400 bytes
-rw-r--r--public/img/icons/reschedule_petrol.pngbin0 -> 403 bytes
-rw-r--r--public/img/icons/save.pngbin0 -> 506 bytes
-rw-r--r--public/img/icons/save_petrol.pngbin0 -> 517 bytes
-rw-r--r--public/img/icons/search.pngbin0 -> 491 bytes
-rw-r--r--public/img/icons/search_icinga_blue.pngbin0 -> 432 bytes
-rw-r--r--public/img/icons/search_petrol.pngbin0 -> 493 bytes
-rw-r--r--public/img/icons/search_white.pngbin0 -> 1352 bytes
-rw-r--r--public/img/icons/service.pngbin0 -> 496 bytes
-rw-r--r--public/img/icons/service_petrol.pngbin0 -> 505 bytes
-rw-r--r--public/img/icons/servicegroup.pngbin0 -> 598 bytes
-rw-r--r--public/img/icons/servicegroup_petrol.pngbin0 -> 605 bytes
-rw-r--r--public/img/icons/softstate.pngbin0 -> 511 bytes
-rw-r--r--public/img/icons/submit.pngbin0 -> 418 bytes
-rw-r--r--public/img/icons/submit_petrol.pngbin0 -> 426 bytes
-rw-r--r--public/img/icons/success.pngbin0 -> 509 bytes
-rw-r--r--public/img/icons/success_petrol.pngbin0 -> 505 bytes
-rw-r--r--public/img/icons/tux.pngbin0 -> 495 bytes
-rw-r--r--public/img/icons/uebersicht.pngbin0 -> 20920 bytes
-rw-r--r--public/img/icons/unhandled.pngbin0 -> 553 bytes
-rw-r--r--public/img/icons/unhandled_petrol.pngbin0 -> 555 bytes
-rw-r--r--public/img/icons/up.pngbin0 -> 497 bytes
-rw-r--r--public/img/icons/up_petrol.pngbin0 -> 499 bytes
-rw-r--r--public/img/icons/user.pngbin0 -> 487 bytes
-rw-r--r--public/img/icons/user_petrol.pngbin0 -> 508 bytes
-rw-r--r--public/img/icons/win.pngbin0 -> 738 bytes
-rw-r--r--public/img/orb-analytics.pngbin0 -> 232972 bytes
-rw-r--r--public/img/orb-automation.pngbin0 -> 320334 bytes
-rw-r--r--public/img/orb-cloud.pngbin0 -> 294936 bytes
-rw-r--r--public/img/orb-icinga.pngbin0 -> 259264 bytes
-rw-r--r--public/img/orb-infrastructure.pngbin0 -> 293092 bytes
-rw-r--r--public/img/orb-metrics.pngbin0 -> 289015 bytes
-rw-r--r--public/img/orb-notifications.pngbin0 -> 308893 bytes
-rw-r--r--public/img/select-icon-2x.pngbin0 -> 305 bytes
-rw-r--r--public/img/select-icon.pngbin0 -> 176 bytes
-rw-r--r--public/img/select-icon.svg1
-rw-r--r--public/img/textarea-corner-2x.pngbin0 -> 238 bytes
-rw-r--r--public/img/textarea-corner.pngbin0 -> 181 bytes
-rw-r--r--public/img/theme-mode-thumbnail-dark.svg59
-rw-r--r--public/img/theme-mode-thumbnail-light.svg54
-rw-r--r--public/img/theme-mode-thumbnail-system.svg91
-rw-r--r--public/img/touch-icon.pngbin0 -> 4777 bytes
-rw-r--r--public/img/tree/tree-minus.gifbin0 -> 149 bytes
-rw-r--r--public/img/tree/tree-plus.gifbin0 -> 151 bytes
-rw-r--r--public/img/website-icon.svg4
-rw-r--r--public/img/winter/logo_icinga_big_winter.pngbin0 -> 9474 bytes
-rw-r--r--public/img/winter/snow1.pngbin0 -> 2765 bytes
-rw-r--r--public/img/winter/snow2.pngbin0 -> 4867 bytes
-rw-r--r--public/img/winter/snow3.pngbin0 -> 3117 bytes
-rw-r--r--public/index.php4
-rw-r--r--public/js/define.js118
-rw-r--r--public/js/helpers.js91
-rw-r--r--public/js/icinga.js279
-rw-r--r--public/js/icinga/behavior/actiontable.js498
-rw-r--r--public/js/icinga/behavior/application-state.js40
-rw-r--r--public/js/icinga/behavior/autofocus.js28
-rw-r--r--public/js/icinga/behavior/collapsible.js463
-rw-r--r--public/js/icinga/behavior/datetime-picker.js222
-rw-r--r--public/js/icinga/behavior/detach.js73
-rw-r--r--public/js/icinga/behavior/dropdown.js66
-rw-r--r--public/js/icinga/behavior/filtereditor.js77
-rw-r--r--public/js/icinga/behavior/flyover.js85
-rw-r--r--public/js/icinga/behavior/form.js96
-rw-r--r--public/js/icinga/behavior/input-enrichment.js148
-rw-r--r--public/js/icinga/behavior/modal.js231
-rw-r--r--public/js/icinga/behavior/navigation.js464
-rw-r--r--public/js/icinga/behavior/selectable.js49
-rw-r--r--public/js/icinga/eventlistener.js78
-rw-r--r--public/js/icinga/events.js415
-rw-r--r--public/js/icinga/history.js338
-rw-r--r--public/js/icinga/loader.js1323
-rw-r--r--public/js/icinga/logger.js129
-rw-r--r--public/js/icinga/module.js134
-rw-r--r--public/js/icinga/storage.js549
-rw-r--r--public/js/icinga/timer.js176
-rw-r--r--public/js/icinga/timezone.js105
-rw-r--r--public/js/icinga/ui.js634
-rw-r--r--public/js/icinga/utils.js582
-rw-r--r--schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql26
-rw-r--r--schema/mysql-upgrades/2.11.0.sql33
-rw-r--r--schema/mysql-upgrades/2.5.0.sql5
-rw-r--r--schema/mysql-upgrades/2.9.0.sql11
-rw-r--r--schema/mysql-upgrades/2.9.1.sql2
-rw-r--r--schema/mysql.schema.sql65
-rw-r--r--schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql60
-rw-r--r--schema/pgsql-upgrades/2.11.0.sql10
-rw-r--r--schema/pgsql-upgrades/2.5.0.sql5
-rw-r--r--schema/pgsql-upgrades/2.9.0.sql16
-rw-r--r--schema/pgsql-upgrades/2.9.1.sql2
-rw-r--r--schema/pgsql.schema.sql130
2560 files changed, 501152 insertions, 0 deletions
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..3f55aa6
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,43 @@
+Alexander A. Klimov <alexander.klimov@icinga.com> <Alexander.Klimov@netways.de>
+Alexander A. Klimov <alexander.klimov@icinga.com> <alexander.klimov@netways.de>
+Alexander A. Klimov <alexander.klimov@icinga.com> <Al2Klimov@users.noreply.github.com>
+Alexander A. Klimov <alexander.klimov@icinga.com> <grandmaster@al2klimov.de>
+Bernd Erk <bernd.erk@icinga.com> <bernd.erk@netways.de>
+Bernd Erk <bernd.erk@icinga.com><berk@nb-berk.int.netways.de>
+Bernd Erk <bernd.erk@icinga.com><berk@nb-berk.local>
+Bernd Erk <bernd.erk@icinga.com><bernd.erk@icinga.com>
+Bernd Erk <bernd.erk@icinga.com><bernd.erk@icinga.org>
+Blerim Sheqa <blerim.sheqa@icinga.com> <blerim.sheqa@netways.de>
+Carlos Cesario <carloscesario@gmail.com> <ccesario@tecnomega.com.br>
+Christopher Rüll <christopher.ruell@netways.de> <Christopher.Ruell@netways.de>
+Eric Lippmann <eric.lippmann@icinga.com> <eric.lippmann@netways.de>
+Eric Lippmann <eric.lippmann@icinga.com> <lippserd@googlemail.com>
+Florian Strohmaier <florian.strohmaier@icinga.com> <florian.strohmaier@netways.de>
+Florian Strohmaier <florian.strohmaier@icinga.com> <hello@florianstrohmaier.com>
+Florian Strohmaier <florian.strohmaier@icinga.com> <florian.strohmaier@me.com>
+Gunnar Beutner <gunnar.beutner@netways.de> <gunnar@beutner.name>
+Jannis Moßhammer <jannis.mosshammer@netways.de>
+Johannes Meyer <johannes.meyer@icinga.com> <johannes.meyer@netways.de>
+Jennifer Mourek <jennifer.mourek@icinga.com> <jennifer.mourek@netways.de>
+Marius Hein <marius.hein@netways.de> <mhein@itsocks.de>
+Markus Frosch <markus.frosch@icinga.com> <lazyfrosch@icinga.org>
+Markus Frosch <markus.frosch@icinga.com> <markus.frosch@netways.de>
+Markus Frosch <markus.frosch@icinga.com> <markus@lazyfrosch.de>
+Matthias Jentsch <matthias.jentsch@netways.de> <mjentsch@localhost.int.netways.de>
+Max Kozlov <m.v.kozlov@gmail.com> <M.V.Kozlov@gmail.com>
+Michael Friedrich <michael.friedrich@icinga.com> <Michael.Friedrich@netways.de>
+Michael Friedrich <michael.friedrich@icinga.com> <michael.friedrich@gmail.com>
+Michael Friedrich <michael.friedrich@icinga.com> <michael.friedrich@netways.de>
+Nicolai Buchwitz <nicolai.buchwitz@enda.eu> <nbuchwitz@users.noreply.github.com>
+Noah Hilverling <noah.hilverling@icinga.com> <noah.hilverling@netways.de>
+Noah Hilverling <noah.hilverling@icinga.com> <noah@hilverling.com>
+Philipp Dorschner <philipp.dorschner@netways.de> <pdorschner@netways.de>
+Sylph Lin <sylph.lin@gmail.com>
+Thomas Gelf <thomas.gelf@icinga.com> <root@squeeze-devel1.osmc.lab>
+Thomas Gelf <thomas.gelf@icinga.com> <tgelf@tgelf-web2dep.(none)>
+Thomas Gelf <thomas.gelf@icinga.com> <thomas.gelf@netways.de>
+Thomas Gelf <thomas.gelf@icinga.com> <thomas@gelf.net>
+Yonas Habteab <yonas.habteab@icinga.com> <yonas.habteab@netways.de>
+Ravi Kumar Kempapura Srinivasa <ravi.srinivasa@icinga.com> <33730024+raviks789@users.noreply.github.com>
+Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com> <54990055+sukhwinder33445@users.noreply.github.com>
+Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com> <sukhwinder33445@gmail.com>
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..b880d6a
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,144 @@
+Aaron Collins <acollins@chegg.com>
+Alexander A. Klimov <alexander.klimov@icinga.com>
+Alexander AleksandroviÄ Klimov <alexander.klimov@icinga.com>
+Alexander Fuhr <alexander.fuhr@netways.de>
+Alexander Wirt <formorer@debian.org>
+Andreas Olsson <andreas@arrakis.se>
+ayoubabid <ayoubabid@users.noreply.github.com>
+Bas Couwenberg <sebastic@xs4all.nl>
+baufrecht <baufrecht@users.noreply.github.com>
+Bence Nagy <bence@underyx.me>
+Benedikt Heine <bebe@bebehei.de>
+Bernd Arnold <wopfel@gmail.com>
+Bernd Erk <bernd.erk@icinga.com>
+Bernhard Friedreich <bernhard.friedreich@brz.gv.at>
+Blerim Sheqa <blerim.sheqa@icinga.com>
+Boden Garman <boden.garman@spintel.net.au>
+bradynathan <bradynathan@gmail.com>
+Carlos Cesario <carloscesario@gmail.com>
+Carsten <carsten.koebke@gmx.de>
+Carsten Koebke <carsten.koebke@koebbes.de>
+chisatohasimoto <hasimoto@designet.co.jp>
+Chivvv <60713338+Chivvv@users.noreply.github.com>
+Chris Reeves <chris.reeves@york.ac.uk>
+Christopher Rüll <christopher.ruell@netways.de>
+Christoph Niemann <kordolan@googlemail.com>
+Christoph Wiechert <wio@psitrax.de>
+Constantin Matheis <constantin.matheis@gmail.com>
+Cornelius Wachinger <cornelius@dercorn.com>
+cstegm <cstegm@users.noreply.github.com>
+Damiano Chini <damiano.chini@wuerth-phoenix.com>
+Daniel <d.lorych@gmail.com>
+Daniel Shirley <aditaa@ig2ad.com>
+Davide Bizzarri <davide.bizzarri@wuerth-phoenix.com>
+Davide Demuru <davide.demuru@buongiorno.com>
+Dirk Goetz <dirk.goetz@netways.de>
+Dokon <david.okon@hotmail.com>
+Elias Probst <mail@eliasprobst.eu>
+Emil Vikström <emil@pixelstore.se>
+Eric Jaw <naisanza@gmail.com>
+Eric Lippmann <eric.lippmann@icinga.com>
+Feu Mourek <feu.mourek@icinga.com>
+Florian Strohmaier <florian.strohmaier@icinga.com>
+Francesco Colista <fcolista@alpinelinux.org>
+Francesco Mazzi <fmazzi@comune.genova.it>
+Gianluca Piccolo <gianluca.piccolo@wuerth-phoenix.com>
+Goran Rakic <grakic@devbase.net>
+Gunnar Beutner <gunnar.beutner@netways.de>
+h0rmiga <github@hormiga.ru>
+hailthemelody@rm-laptop04 <hailthemelody@rm-laptop04>
+Hector Sanjuan <hector.sanjuan@nugg.ad>
+Heike Jurzik <huhn@lion-3.fritz.box>
+Ian Shearin <ishearin@womply.com>
+ignasr <ignas.linux@gmail.com>
+Janne Heß <janne@hess.ooo>
+Jannis Moßhammer <jannis.mosshammer@netways.de>
+Jennifer Mourek <jennifer.mourek@icinga.com>
+Jiri Pejchal <jiri.pejchal@gmail.com>
+Joe Doherty <git@pjuu.com>
+Johannes Meyer <johannes.meyer@icinga.com>
+Joonas Kylmälä <joonas.kylmala@kirjastot.fi>
+Jorge Vallecillo <jorgevallecilloc@gmail.com>
+Jo Rhett <jo@chegg.com>
+Ken Jungclaus <lum33n@web.de>
+Kevin Köllmann <mail@kevinkoellmann.de>
+Klaus Jrgensen <klaus@blackwoodseven.com>
+Lee Clemens <java@leeclemens.net>
+Loei Petrus Marogi <loeipetrus.marogi@netways.de>
+log1-c <24474580+log1-c@users.noreply.github.com>
+Louis Sautier <sautier.louis@gmail.com>
+mapa82 <maik.paetzold@akra.de>
+Marc DeTrano <marc@gridshield.net>
+Marcel Weinberg <marcel.weinberg@secucloud.com>
+Marcus Cobden <marcus@marcuscobden.co.uk>
+Marian Rainer-Harbach <marian@rainer-harbach.at>
+Mario Rimann <mario@rimann.org>
+Marius Hein <marius.hein@netways.de>
+Markus Frosch <markus.frosch@icinga.com>
+Markus Opolka <opolkams@iis.fraunhofer.de>
+Massimiliano Torromeo <massimiliano.torromeo@gmail.com>
+Matthias Jentsch <matthias.jentsch@netways.de>
+Matthias <pub@matthias-henning.de>
+Mattia Codato <mattia.codato@wuerth-phoenix.com>
+Max Kozlov <m.v.kozlov@gmail.com>
+Max Stephan <xam.stephan@web.de>
+mbaschnitzi <mbaschnitzi@users.noreply.github.com>
+mdetrano <marc@gridshield.net>
+Michael Friedrich <michael.friedrich@icinga.com>
+Michael T. DeGuzis <mdeguzis@users.noreply.github.com>
+Mike Pennisi <mike@mikepennisi.com>
+Mikesch-mp <Mikesch-mp@koebbes.de>
+Mikko Peltokangas <mikko@peltokangas.org>
+moreamazingnick <github@nicolas-schneider.at>
+mrdsam <69315803+mrdsam@users.noreply.github.com>
+mrzo2s45 <dominik.lueffe@komm.one>
+Munzir Taha <munzirtaha@gmail.com>
+Nicolai Buchwitz <nicolai.buchwitz@enda.eu>
+Niko Martini <niko.martini@netways.de>
+nmartini <niko.martini@netways.de>
+Noah Hilverling <noah.hilverling@icinga.com>
+Oliver Rahner <oliver@rahner.me>
+p4k8 <pkuznetsunit@gmail.com>
+Paolo Schiro <paolo.schiro@kpnqwest.it>
+papillon326 <udagawa@www2178ue.sakura.ne.jp>
+Patrick Dolinic <pdolinic@netways.de>
+Paul Richards <paul@minimoo.org>
+Pavlos Daoglou <pdaoglou@gmail.com>
+Peter Eckel <pe-git@hindenburgring.com>
+Philipp Dorschner <philipp.dorschner@netways.de>
+Pieter Lexis <pieter.lexis@powerdns.com>
+PunkoIvan <punkoivan@gmail.com>
+Ramy Talal <ramy@thinkquality.nl>
+Raphael Bicker <raphael@bicker.ch>
+Ravi Kumar Kempapura Srinivasa <ravi.srinivasa@icinga.com>
+rbelinsky <rbelinsky@dalet.com>
+realitygaps <github@gapsinreality.com>
+Rene Moser <rene.moser@swisstxt.ch>
+Rick Henry <rjh@rick-h.xyz>
+rkcpi <thieme.sandra@gmail.com>
+Roland Hopferwieser <rhopfer@ica.jku.at>
+Rudy Gevaert <rudy.gevaert@ugent.be>
+Rune Darrud <theflyingcorpse@gmail.com>
+Russell Kubik <russkubik@3d-p.com>
+Sander Ferdinand <sa.ferdinand@gmail.com>
+sant-swedge <simon.wedge@sant.ox.ac.uk>
+Simone Orsi <simahawk@users.noreply.github.com>
+ss23 <stephen@zxsecurity.co.nz>
+Sukhwinder Dhillon <sukhwinder.dhillon@icinga.com>
+Susanne Vestner-Ludwig <susanne.vestner-ludwig@inserteffect.com>
+Sylph Lin <sylph.lin@gmail.com>
+tfylling <torbfylling@gmail.com>
+Thomas Gelf <thomas.gelf@icinga.com>
+Tim Helfensdörfer <tim@visualappeal.de>
+Timm Ortloff <timm.ortloff@icinga.com>
+Tobias von der Krone <tobias.vonderkrone@profitbricks.com>
+Tomas Barton <barton.tomas@gmail.com>
+Tom Ford <exptom@users.noreply.github.com>
+Ulf Lange <mopp@gmx.net>
+Uwe Ebel <kobmaki@aol.com>
+ValeDaRold <36924916+ValeDaRold@users.noreply.github.com>
+Valentina Da Rold <Valentina.DaRold@wuerth-phoenix.com>
+Vladislav Ponomarev <vponomarev@team.mobile.de>
+xert <xert@users.noreply.github.com>
+Yonas Habteab <yonas.habteab@icinga.com>
+Yuri Konotopov <ykonotopov@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1c1a267
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,1491 @@
+# Icinga Web 2 Changelog
+
+Please make sure to always read our [Upgrading](doc/80-Upgrading.md) documentation before switching to a new version.
+
+## What's New
+
+### What's New in Version 2.11.4
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/78?closed=1).
+
+#### Notable Fixes
+
+* Add/Edit dashlet not possible [#4970](https://github.com/Icinga/icingaweb2/issues/4970)
+* Custom library path + custom library, without slash in its name, results in exception [#4971](https://github.com/Icinga/icingaweb2/issues/4971)
+* Reflected XSS vulnerability in User Backends config page [#4979](https://github.com/Icinga/icingaweb2/issues/4979)
+
+### What's New in Version 2.11.3
+
+**Notice**: This is a security release. It is recommended to upgrade immediately.
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/77?closed=1).
+
+#### Minor to Medium Vulnerabilities
+
+In late November we received multiple security vulnerability reports. They are listed below in order of severity
+where you can also find further notes:
+
+* Open Redirects for logged in users [#4945](https://github.com/Icinga/icingaweb2/issues/4945)
+ This one is quite old, though got worse and easier to exploit since v2.9. It is for this reason that
+ this fix has been backported all the way down to v2.9.8. It can be used to exploit incautious users,
+ no matter their browser and its security settings. They need to click a specifically crafted link
+ (in the easiest form) and log in to Icinga Web by filling in their access credentials. If they're
+ already logged in, (due to an existing session or SSO) the browser prevents the exploit from happening.
+ We encourage you to update to the latest release as soon as possible to mitigate any potential harm.
+
+* SSH Resource Configuration form XSS Bug [#4947](https://github.com/Icinga/icingaweb2/issues/4947)
+ Dashlets allow the user to run Javascript code [#4959](https://github.com/Icinga/icingaweb2/issues/4959)
+ These two are very similar. Both revolve around Javascript getting injected by logged in users
+ interacting with forms. The SSH resource configuration requires configuration access though and, since
+ custom dashlets are only shown to the user who created them, the dashlet configuration cannot affect
+ other users. Note that both interactions cannot be initiated externally by CSRF, the forms are protected
+ against this. Because of this we assess the severity of these two very low.
+
+* Role member suggestion endpoint is reachable for unauthorized users [#4961](https://github.com/Icinga/icingaweb2/issues/4961)
+ This is more a case of missing authorization checks than a full fledged security flaw. But nevertheless,
+ it allows any logged-in user, by use of a manually crafted request, to retrieve the names of all available
+ users and usergroups.
+
+#### The More Usual Dose of Fixes
+
+* Browser print dialog result broken [#4957](https://github.com/Icinga/icingaweb2/issues/4957)
+ If you tried to export a view using the browser's builtin print dialog, (e.g. Ctrl+P) you may have
+ noticed a degradation of fanciness since the update to v2.10. This looks nicer than ever now.
+
+* Shared navigation items are not accessible [#4953](https://github.com/Icinga/icingaweb2/issues/4953)
+ Since v2.11.0 the shared navigation overview hasn't been accessible using the configuration menu.
+ It is now accessible again.
+
+* While using dropdown filter menu it gets closed automatically due to autorefresh [#4942](https://github.com/Icinga/icingaweb2/issues/4942)
+ Are you annoyed by the filter editor repeatedly closing the column selection while you're looking for
+ something? We have you covered with a fix for this and the column selection should stay open as long
+ as you don't click anywhere else.
+
+### What's New in Version 2.11.2
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/76?closed=1).
+
+It brings performance improvements and general fixes. Most notable of which are that having e.g. notifications
+disabled globally is now visible in the menu again and that the event history is grouped by days again.
+
+### What's New in Version 2.11.1
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/75?closed=1).
+
+This update's main focus is to solve the issue that all history views didn't work correctly or showed invalid
+time and dates. ([#4853](https://github.com/Icinga/icingaweb2/issues/4853))
+
+### What's New in Version 2.11.0
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/70?closed=1).
+
+#### Enhancements, Some
+
+Many of you were waiting for it: PHP 8.1 Support. This means that Icinga Web should be available soon on e.g.
+Ubuntu 22.04. You'll also notice that we changed the sidebar, as the user menu went to the very bottom of it.
+With it moved the less frequently used menu entries (system and configuration) to a section that pops up by
+hovering over the :gear: icon. We did that in order to prepare an area where we can add further functionality
+in the future. Oh, and announcements are now visible in fullscreen mode. :upside_down_face:
+
+* Support for PHP 8.1 [#4609](https://github.com/Icinga/icingaweb2/issues/4609)
+* Redesign User Menu [#4651](https://github.com/Icinga/icingaweb2/issues/4651)
+* &showFullscreen suppresses announcements [#4596](https://github.com/Icinga/icingaweb2/issues/4596)
+
+#### Fixes, More
+
+There are also bug fixes of course. The first mentioned here is one we fixed *accidentally*, as by adding support for
+PHP 8.1 we avoided a common PHP quirk responsible for it. If you have a host or service with an asterisk in the name,
+it will show up correctly in the detail view now. There was also a remaining issue with the theme mode selection in the
+user preferences which is fixed now.
+
+* Navigation item filter `*` not working [#4772](https://github.com/Icinga/icingaweb2/issues/4772)
+* Objects with a `*` in the name are not found [#4682](https://github.com/Icinga/icingaweb2/issues/4682)
+* Theme mode switch disabled on theme with mode support [#4744](https://github.com/Icinga/icingaweb2/issues/4744)
+
+#### When developers become cleaning maniacs
+
+Usually I write a short note at the start of release notes to make you read the upgrading documentation. This time
+however, a more prominent hint is required. We've removed so much (legacy) stuff, anyone tasked with upgrading is
+obliged to read [the upgrading documentation](https://icinga.com/docs/icinga-web-2/latest/doc/80-Upgrading/#upgrading-to-icinga-web-211x).
+The changes mentioned below only provide a glimpse at it.
+
+* User preferences in INI files not supported anymore [#4765](https://github.com/Icinga/icingaweb2/pull/4765)
+* mysql: use of utf8 vs utfmb4 [#4680](https://github.com/Icinga/icingaweb2/issues/4680)
+* Remove Vagrant file and its assets [#4762](https://github.com/Icinga/icingaweb2/pull/4762)
+
+### What's New in Version 2.10.1
+
+It's a rather small update this time without any critical bugs. :tada: So let's get straight to the fixes:
+
+* Clicking anywhere on a list item in the dashboard now opens the primary link again, instead of nothing [#4710](https://github.com/Icinga/icingaweb2/issues/4710)
+* The `Check Now` and `Remove Acknowledgement` quick actions in an object's detail header are now working again [#4711](https://github.com/Icinga/icingaweb2/issues/4711)
+* Clicking on the big number in the tactical overview if there are `UNKNOWN` services, shows `UNKNOWN` services now [#4714](https://github.com/Icinga/icingaweb2/issues/4714)
+* The contrast of text in the sidebar, while in light mode, has been increased [#4720](https://github.com/Icinga/icingaweb2/issues/4720)
+* A theme without mode support, which is set globally, now also prevents users from configuring the mode [#4723](https://github.com/Icinga/icingaweb2/issues/4723)
+
+### What's New in Version 2.10.0
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/63?closed=1).
+
+Please make sure to also check the respective [upgrading section](https://icinga.com/docs/icinga-web-2/latest/doc/80-Upgrading/#upgrading-to-icinga-web-2-210x)
+in the documentation.
+
+#### The Appearance of Dark and Light
+
+We have already spoken a lot about the [theme mode support](https://icinga.com/blog/2021/06/16/introducing-dark-and-light-theme-modes/)
+that we were working on [for some time](https://icinga.com/blog/2022/02/10/icinga-web-not-just-black-and-white/) now.
+It was planned for v2.9.0, but in respect of many modules and themes out there we gave it the deserved attention.
+Below is a glimpse of what this looks like.
+
+[![Icinga Web 2 Theme Mode Preview](https://icinga.com/wp-content/uploads/2022/03/theme-mode-demo-small.jpg "Icinga Web 2 Theme Mode Preview")](https://icinga.com/wp-content/uploads/2022/03/theme-mode-demo.jpg)
+
+#### Custom Variables Shown Unaltered – Or not
+
+Icinga Web 2 had some bad habits when displaying custom variables in the UI. We've driven out the last one regarding
+names now. Uppercase characters are now shown as such. What Icinga Web 2 stopped doing though, can now be accomplished
+by modules. A new hook that enables modules to influence the rendering of custom variables has been introduced.
+
+* CustomVarNames should not be converted to lowercase [#4639](https://github.com/Icinga/icingaweb2/issues/4639)
+* Display the Director Caption of a Custom Variable [#3479](https://github.com/Icinga/icingaweb2/issues/3479)
+
+#### Surprising Beauty in Exported Places
+
+Anyone who already attempted to export a list of services to PDF has seen the degradation of details in recent years.
+Be it images, icons, colors or the general layout. We simply reached a technical limit with the builtin PDF export.
+That is why we made [Icinga PDF Export](https://github.com/Icinga/icingaweb2-module-pdfexport). Icinga Web 2 has now
+a much enhanced compatibility with it. Exporting a list of services while Icinga PDF Export is set up, will now lead
+to a much better looking result.
+
+* Enhance PDF export [#4685](https://github.com/Icinga/icingaweb2/pull/4685)
+* Image not found when creating PDF view of objects [#4674](https://github.com/Icinga/icingaweb2/issues/4674)
+
+### What's New in Version 2.9.6
+
+**Notice**: This is a security release. It is recommended to upgrade immediately.
+
+#### Security Fixes
+
+This release includes three security related fixes. The first is a path traversal issue that affects installations
+of v2.9.0 and above. Another one allows admins to run arbitrary PHP code just by accessing the UI. The last one may
+disclose unwanted details to restricted users. Please check the advisories on GitHub for more details.
+
+* Path traversal in static library file requests for unauthenticated users [GHSA-5p3f-rh28-8frw](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-5p3f-rh28-8frw)
+* SSH resources allow arbitrary code execution for authenticated users [GHSA-v9mv-h52f-7g63](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-v9mv-h52f-7g63)
+* Unwanted disclosure of hosts and related data, linked to decommissioned services [GHSA-qcmg-vr56-x9wf](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-qcmg-vr56-x9wf)
+
+### What's New in Version 2.9.5
+
+This is a hotfix release which fixes the following issues:
+
+* Some detail views of Icinga Director and other modules are broken with Web 2.9.4 [#4598](https://github.com/Icinga/icingaweb2/issues/4598)
+* Error on skipping LDAP Discovery [#4603](https://github.com/Icinga/icingaweb2/issues/4603)
+
+### What's New in Version 2.9.4
+
+You can also find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/68?closed=1).
+
+#### Broken Preference Configuration
+
+The preferences configuration broke with the release of v2.9 in some cases. Previously it was possible to access this
+and the general configuration without any configuration at all on disk. This is now possible again. The preferences of
+some users, which have a theme of a disabled module enabled, also showed an error. This doesn't happen anymore now.
+
+* Config/Preferences not accessible without config.ini [#4504](https://github.com/Icinga/icingaweb2/issues/4504)
+* "My Account" broken after Upgrade from 2.8.2 to 2.9.3 [#4512](https://github.com/Icinga/icingaweb2/issues/4512)
+
+#### Notable Fixes in the UI
+
+For a long time now, comments in lists had the bad habit to spread erratically if their content was large. They're
+limited to two lines now in lists and are still shown in full glory in their respective detail area. While talking
+of lines... Plugin output with subsequent empty lines erroneously showed only one of them. This is now fixed.
+
+* Proposal for new Feature make comments collapsible [#4515](https://github.com/Icinga/icingaweb2/issues/4515)
+* new line character is being removed in the plugin output [#4522](https://github.com/Icinga/icingaweb2/issues/4522)
+
+#### Less Notable But No Less Important Fixes
+
+We are actually very committed to provide a good experience for restricted users. So I'm happy to tell you that a nasty
+bug is fixed that resulted in the focus being lost randomly. Third party integrations are also important to us, hence
+I'm happy that this release fixes an issue where module specific JavaScript didn't load properly. Are you happy now?
+
+* `announcements` request clears focus [#4543](https://github.com/Icinga/icingaweb2/issues/4543)
+* js: Fix regression for loading dependent modules for sub-containers [#4533](https://github.com/Icinga/icingaweb2/issues/4533)
+
+### What's New in Version 2.9.3
+
+You can also find the issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/66?closed=1).
+
+#### Staying remembered on RHEL/CentOS 7 now possible
+
+RHEL/CentOS 7 still relies on OpenSSL v1.0.2 by default. A change in v2.9.1 resulted in an error in combination with
+this when ticking `Stay Logged In` during authentication. Staying logged in now works fine also on this platform.
+
+* Stay Logged In - Unknown cipher algorithm [#4493](https://github.com/Icinga/icingaweb2/issues/4493)
+
+#### Missing icons with SLES/OpenSUSE 15
+
+If you're running Icinga Web 2 Version 2.9.x on a SLES/OpenSUSE 15.x, you may have noticed some missing icons in the UI.
+This is due to a missing PHP extension `fileinfo`. By upgrading to this release using packages, this dependency will now
+be installed automatically.
+
+* Missing fileinfo php extension on SLES/OpenSUSE 15+ [#4503](https://github.com/Icinga/icingaweb2/issues/4503)
+
+#### Child downtimes for services are now removed automatically
+
+With Icinga v2.13, Icinga Web 2 will now make sure that service downtimes that were created automatically are also
+removed automatically. This will only work for downtimes you create with the `All Services` option after upgrading
+to this release. It will not work for downtimes created with earlier versions of Icinga Web 2.
+
+* If appropriate, set the API parameter all_services for schedule-downtime [#4501](https://github.com/Icinga/icingaweb2/pull/4501)
+
+### What's New in Version 2.9.2
+
+This is a hotfix release. v2.9.1 included a change that wasn't compatible with PostgreSQL again. This has been fixed
+in this release. (#4490)
+
+### What's New in Version 2.9.1
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/64?closed=1).
+
+Please make sure to also check the respective [upgrading section](https://icinga.com/docs/icinga-web-2/latest/doc/80-Upgrading/#upgrading-to-icinga-web-2-291)
+in the documentation.
+
+This release is accompanied by the minor releases v2.7.6 and v2.8.4 which include the fix for the flattened custom variables.
+
+#### Pancakes everywhere
+
+One of the security fixes included in v2.7.5, v2.8.3 and v2.9.0 went rampant and let you see similarities between custom
+variables and pancakes. These are gone now. Also, the login allowed some users to bake pancakes on their CPUs. However,
+we'd still recommend not to. What we do recommend, is to use graphical details to ease recognition. A pancake 🥞 in
+performance data labels for example.
+
+* Nested custom variables are flattened [#4439](https://github.com/Icinga/icingaweb2/issues/4439)
+* Disable login orb animation and all orbs for themes [#4468](https://github.com/Icinga/icingaweb2/pull/4468)
+* SVG chart library doesn't process input as UTF-8 [#4462](https://github.com/Icinga/icingaweb2/issues/4462)
+
+#### Staying remembered too difficult
+
+We all have sometimes difficulties remembering people we rarely meet. Especially obvious is this on those that slip
+through because they don't do the same things we do. With v2.9.0 this has happened for PostgreSQL, PHP v5.6-v7.0 and
+setup wizard users. Now they get their deserved attention, and Icinga Web 2 will remember them just like all others.
+
+* RememberMe not working with only PostgreSQL [#4441](https://github.com/Icinga/icingaweb2/issues/4441)
+* RememberMe compatibility with php version 5.6+ [#4472](https://github.com/Icinga/icingaweb2/pull/4472)
+* RememberMe fails after running the wizard for grants [#4434](https://github.com/Icinga/icingaweb2/issues/4434)
+
+#### Being picky pays off
+
+A custom datetime picker was introduced with v2.9.0. It had it's issues, but we didn't anticipate that much headwind.
+After careful reconsideration, we chose to only show the custom datetime picker for Firefox and IE users. Other browsers
+have their own capable enough native implementation which, in Chrome's case, may even be superior. If it is now used,
+it also closes automatically and doesn't swallow unrelated key presses.
+
+* Datetimepicker not usable by keyboard [#4442](https://github.com/Icinga/icingaweb2/issues/4442)
+* Close the datepicker automatically [#4461](https://github.com/Icinga/icingaweb2/issues/4461)
+* Paragraphs in Acknowledge/Downtime not possible [#4443](https://github.com/Icinga/icingaweb2/issues/4443)
+
+### What's New in Version 2.9.0
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/59?closed=1).
+
+Please make sure to also check the respective [upgrading section](https://icinga.com/docs/icinga-web-2/latest/doc/80-Upgrading/#upgrading-to-icinga-web-2-29x)
+in the documentation.
+
+This release is accompanied by the minor releases v2.7.5 and v2.8.3 which include the security fixes mentioned below.
+
+#### Icinga DB
+
+We continue our endeavour soon. Icinga Web 2 is still a crucial part of it and this update is again required
+for Icinga DB. If you like to participate again, don't forget to update Icinga Web 2 as well.
+
+#### Security Fixes
+
+This release includes two security related fixes. Both were published as part of a security advisory on Github.
+They allow the circumvention of custom variable protection rules and blacklists as well as a path traversal if
+the `doc` module is enabled. Please check the respective advisory for details.
+
+* Custom variable protection and blacklists can be circumvented [GHSA-2xv9-886q-p7xx](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-2xv9-886q-p7xx)
+* Possible path traversal by use of the `doc` module [GHSA-cmgc-h4cx-3v43](https://github.com/Icinga/icingaweb2/security/advisories/GHSA-cmgc-h4cx-3v43)
+
+#### RBAC, The Elephant In Icinga Web 2
+
+Role Based Access Control, for the non-initiated. I'll make it short: Permission refusals, Role inheritance,
+Privilege Audit. Icinga DB will also solve the long-standing issue [#2455](https://github.com/Icinga/icingaweb2/issues/2455)
+and also allows [#3349](https://github.com/Icinga/icingaweb2/issues/3349) and [#3550](https://github.com/Icinga/icingaweb2/issues/3550).
+I've also written a blog post about this very topic: https://icinga.com/blog/2021/04/07/web-access-control-redefined/
+
+* Authorization enhancements [#4306](https://github.com/Icinga/icingaweb2/pull/4306)
+* Audit View [#4336](https://github.com/Icinga/icingaweb2/pull/4336)
+* Highlight modules with permissions set inside a role [#4241](https://github.com/Icinga/icingaweb2/issues/4241)
+
+#### Support for PHP 8
+
+PHP 8 is released and with Icinga Web 2.9 it will now (hopefully) work flawlessly. We also took the chance
+to prepare to drop the support of some legacy PHP versions. We now require PHP 7.3 at a minimum and all
+versions below that will not be supported anymore with the release of v2.11.
+
+* Support PHP 8 [#4289](https://github.com/Icinga/icingaweb2/pull/4289)
+* Raise minimum required PHP version to 7.3 [#4397](https://github.com/Icinga/icingaweb2/pull/4397)
+
+#### Stay, Be Remembered
+
+Have you ever been disappointed that Icinga Web 2 always forgets you after closing your browser? This is in
+your hands now! Just tick the new checkbox on the login screen and Icinga Web 2 doesn't forget your presence
+anymore. Unless of course the administrator or you on a different device clears your session.
+
+* Implement a "remember me" feature [#2495](https://github.com/Icinga/icingaweb2/issues/2495)
+
+#### It Does Matter, When
+
+Browsers are bad when it's about date and time inputs. (I'm looking at you Mozilla!) Now we've given our hopes
+up and use a specifically invented solution to show you a date and time picker throughout every browser. With
+Icinga v2.13 onwards you will also be able to use this when defining an expiry date for comments! Though, you
+might not necessarily use it that often once you've configured new custom defaults for downtime endings.
+
+* Add datetime picker widget [#4354](https://github.com/Icinga/icingaweb2/pull/4354)
+* Expire Option for Comments [#3447](https://github.com/Icinga/icingaweb2/issues/3447)
+* Custom defaults for downtime end, comment and duration [#4364](https://github.com/Icinga/icingaweb2/issues/4364)
+
+### What's New in Version 2.8.2
+
+**Notice**: This is a security release. It is recommended to immediately upgrade to this release.
+
+You can find all issues related to this release on the respective [milestone](https://github.com/Icinga/icingaweb2/milestone/62?closed=1).
+
+#### Path Traversal Vulnerability
+
+The vulnerability in question allows an attacker to access arbitrary files which are readable by the process running
+Icinga Web 2. Technical details can be found at the corresponding [CVE-2020-24368](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-24368)
+and in the issue below.
+
+* Possible path traversal when serving static image files [#4226](https://github.com/Icinga/icingaweb2/issues/4226)
+
+#### Broken Negated Filters with PostgreSQL
+
+We've also included a small non-security related fix. Searching for e.g. `servicegroup!=support` leads to an error
+instead of the desired result when using a PostgreSQL database.
+
+* Single negated membership filter fails with PostgreSQL [#4196](https://github.com/Icinga/icingaweb2/issues/4196)
+
+### What's New in Version 2.8.1
+
+You can find all issues related to this release on the respective [milestone](https://github.com/Icinga/icingaweb2/milestone/61?closed=1).
+
+#### Case Sensitivity Problems
+
+A fix in v2.8.0 led to users being not able to login if they got their username's case wrong. A hostgroup name's case
+has also been incorrectly taken into account despite using a `CI` labelled column in the servicegrid and other lists.
+
+* Login usernames now case sensitive in 2.8 [#4184](https://github.com/Icinga/icingaweb2/issues/4184)
+* Case insensitive hostgroup filter in service grid not working [#4178](https://github.com/Icinga/icingaweb2/issues/4178)
+
+#### Issues With Numbers
+
+An attempt to avoid misrepresenting environments in the tactical overview had an opposite effect by showing negative
+numbers. Filtering for timestamps in the event history also showed no results because our filters couldn't cope with
+plain numbers anymore.
+
+* Tactical overview showing "-1 pending" hosts [#4174](https://github.com/Icinga/icingaweb2/issues/4174)
+* Timestamp filters not working correctly in history views [#4182](https://github.com/Icinga/icingaweb2/issues/4182)
+
+### What's New in Version 2.8.0
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/60?closed=1).
+
+#### Icinga DB
+
+It's happening. Yes. Our latest achievement is now available for those who are willing to participate in this enormous
+endeavour. Icinga Web 2 is also a crucial part of it and accompanies the first release of Icinga DB. If you like
+to participate, don't forget to update Icinga Web 2 as well.
+
+#### Support for PHP 7.4 and MySQL 8
+
+We also made sure that you won't be disappointed by Icinga Web 2 if you're running PHP 7.4 or trying to access a MySQL
+database with version 8+. These should pose no issues anymore now. But if you still somehow managed to get issues
+please let us now and we'll fix it asap.
+
+* Exceptions with MySQL 8 [#3740](https://github.com/Icinga/icingaweb2/issues/3740)
+* Support for PHP 7.4 [#4009](https://github.com/Icinga/icingaweb2/issues/4009)
+
+#### Find What You Search For
+
+It's been previously not possible to properly filter for range values. This was especially true for custom variables
+where, if you searched for e.g. `_host_interfaces>=20`, you wouldn't find the correct results. If you often copy some
+values in our search fields you may also been a victim of extraneous spaces which are now automatically trimmed.
+
+* Filter: more/less than doesn't seem to working [#3974](https://github.com/Icinga/icingaweb2/issues/3974)
+* Search object followed by a space finds no results [#4002](https://github.com/Icinga/icingaweb2/issues/4002)
+
+#### Don't Leave Your Little Sheep Unattended
+
+It's time again to further restrict your users. It's now possible to completely block any access to contacts and
+contactgroups for specific roles. These won't ever see again who's notified and who's not. Also, if you are using
+single accounts for a group of people you can now disable password changes for those.
+
+* Prohibit access to contacts and contactgroups [#3973](https://github.com/Icinga/icingaweb2/issues/3973)
+* Allow to forbid password changes on specific user accounts [#3286](https://github.com/Icinga/icingaweb2/issues/3286)
+
+#### In and Out, Access Control Done Right
+
+While we have no burgers (but cookies!) you are nevertheless welcome to visit Icinga Web 2. And now you can also
+successfully leave while being externally authenticated and unsuccessfully enter while being unable to not add
+extraneous spaces to your username.
+
+* External logout not working from the navigation dashboard [#3995](https://github.com/Icinga/icingaweb2/issues/3995)
+* Username with extraneous spaces are not invalid [#4030](https://github.com/Icinga/icingaweb2/pull/4030)
+
+### Changes in Packaging and Dependencies
+
+Valid for distributions:
+
+* RHEL / CentOS 7
+ * Upgrade to PHP 7.3 via RedHat SCL
+ * See [Upgrading to Icinga Web 2 2.8.x](doc/80-Upgrading.md#upgrading-to-icinga-web-2-28x)
+ for manual steps that are required
+
+#### Discontinued Package Updates
+
+Icinga Web 2 v2.8+ is not supported on these platforms:
+
+* RHEL / CentOS 6
+* Debian 8 Jessie
+* Ubuntu 16.04 LTS (Xenial Xerus)
+
+Please consider an upgrade of your central Icinga system to a newer distribution release.
+
+[icinga.com](https://icinga.com/subscription/support-details/) provides an overview about
+currently supported distributions.
+
+### What's New in Version 2.7.3
+
+This is a hotfix release and fixes the following issue:
+
+* Servicegroups for roles with filtered objects not available [#3983](https://github.com/Icinga/icingaweb2/issues/3983)
+
+### What's New in Version 2.7.2
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/57?closed=1).
+
+#### Less Smoky Database Servers
+
+The release of v2.7.1 introduced a change which revealed an inefficient part of our database queries. We made some
+general optimizations on our queries and changed the way we utilize them in some views. The result are faster
+response times by less work for the database server.
+
+* Consuming more CPU resources since upgraded to 2.7.1 [#3928](https://github.com/Icinga/icingaweb2/issues/3928)
+
+#### Anarchism Infested Dashboards
+
+Recent history already showed signs of anarchism. (Pun intended) A similar mindset now infested default dashboards
+which appeared in a different way than before v2.7.0. We taught their dashlets a lesson and order has been reestablished
+as previously.
+
+* Recently Recovered Services in dashboard "Current Incidents" seems out of order [#3931](https://github.com/Icinga/icingaweb2/issues/3931)
+
+#### Solitary Downtimes
+
+We improved the host and service distinction with v2.7.0. The downtimes list however got confused by this and didn't
+knew anymore how to combine multiple downtimes. If you now instruct the list to select multiple downtimes this works
+again as we removed the confusing parts.
+
+* Selection of multiple downtimes fails [#3920](https://github.com/Icinga/icingaweb2/issues/3920)
+
+### What's New in Version 2.7.1
+
+You can find all issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/56?closed=1).
+
+#### Sneaky Solution for Sneaky Links
+
+Usually we try to include only bugs in minor-releases. Sorry, bug-fixes, of course. But thanks to
+[@winem_](https://twitter.com/winem_/status/1156531270521896960) we have also a little enhancement this time:
+Links in comments, notes, etc. are now [highlighted](https://github.com/Icinga/icingaweb2/pull/3893) as such.
+
+* Highlight links in the notes of an object [#3888](https://github.com/Icinga/icingaweb2/issues/3888)
+
+#### Nobody's Perfect, Not Even Developers
+
+We knew it. We saw it coming. And forgot about it. Some views, especially histories, showed an anarchic behavior
+since v2.7.0. The change responsible for this has been undone and history's order is reestablished now.
+
+* Default sort rules no longer work in 2.7.0 [#3891](https://github.com/Icinga/icingaweb2/issues/3891)
+
+#### Restrictions Gone ~~Wild~~ Cagey
+
+A [fix](https://github.com/Icinga/icingaweb2/pull/3868) unfortunately caused restrictions using wildcards to show no
+results anymore. This is now solved and such restrictions are as permissive as ever.
+
+* Wildcard filters in chains broken [#3886](https://github.com/Icinga/icingaweb2/issues/3886)
+
+### What's New in Version 2.7.0
+
+You can find issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/52?closed=1).
+
+#### Icinga's Amazingness Spreads Further
+
+All the Japanese and Ukrainian monitoring enthusiasts can now appreciate our web-frontend in their native tongue. Being
+so late to the party is also of their advantage, though. Because they can adjust their dashboard without worrying it gets
+broke with the next update. (All other admins with non-english users, please have a look at our
+[upgrading documentation](doc/80-Upgrading.md#upgrading-to-icinga-web-2-27x-))
+
+* Add Japanese language support [#3776](https://github.com/Icinga/icingaweb2/pull/3776)
+* Add Ukrainian language support [#3828](https://github.com/Icinga/icingaweb2/pull/3828)
+* Don't translate pane and dashlet names in configs [#3837](https://github.com/Icinga/icingaweb2/pull/3837)
+
+#### Modules - Bonus Functionality Unleashed
+
+With this release module developers got additional ways to customize Icinga Web 2. Whether you ever wanted to hook into
+a configuration form's handling, to perform your very own Ajax requests or enhance our multi-select views with fancy
+graphs. All is possible now.
+
+* Allow to hook into a configuration form's handling [#3862](https://github.com/Icinga/icingaweb2/pull/3862)
+* Allow to fully customize click and submit handling [#3794](https://github.com/Icinga/icingaweb2/issues/3767)
+* Integrate DetailviewExtension into multi-select views [#3304](https://github.com/Icinga/icingaweb2/pull/3304)
+
+#### UI - Your Daily Routine and Incident Management, Enhanced
+
+Users with color deficiencies now have a built-in theme to ease navigating within Icinga Web 2. Also, our forms got
+a long overdue re-design and now look less boring. Though, the best of all features is that clicking while holding
+the Ctrl-key now actually opens a new browser tab! Lost comments? No more. Defining an expiry date again? No more!
+
+* Add colorblind theme [#3743](https://github.com/Icinga/icingaweb2/pull/3743)
+* Improve the look of forms [#3416](https://github.com/Icinga/icingaweb2/issues/3416)
+* Make ctrl-click open new tab [#3723](https://github.com/Icinga/icingaweb2/pull/3723)
+
+#### Stay Focused - More Room for More Important Stuff
+
+Some of you know that some checks tend to produce walls of text or measure (too) many interfaces. Now, plugin output
+and performance data will collapse if they exceed a certain height. If necessary they can of course be expanded and
+keep that way across browser restarts. The same is also true for the sidebar. (Though, this one stays *collapsed*)
+
+* Persistent Collapsible Containers [#3638](https://github.com/Icinga/icingaweb2/pull/3638)
+* Collapsible plugin output [#3870](https://github.com/Icinga/icingaweb2/pull/3870)
+* Collapsed sidebar should stay collapsed [#3682](https://github.com/Icinga/icingaweb2/issues/3628)
+
+#### Markdown - Tables, Lists and Emphasized Text The Easy Way
+
+Since we now have the possibility to collapse large content dynamically, we allow you to add entire wiki pages to hosts
+and services. Though, if you prefer to use a real wiki to maintain those (what we'd strongly suggest) it's now easier
+than ever before to link to it. Copy url, paste url, submit comment, Done.
+
+* Make notes, comments and announcements markdown aware [#3814](https://github.com/Icinga/icingaweb2/pull/3814)
+* Transform any URL in a Comment to a clickable Link [#3441](https://github.com/Icinga/icingaweb2/issues/3441)
+* Support relative links in plugin output [#2916](https://github.com/Icinga/icingaweb2/issues/2916)
+
+#### Things You Have Missed Previously
+
+The tactical overview, our fancy pie charts, is now the very first result when you search something in the sidebar.
+If you'll see two entirely green circles there, relax. Also overdue or unreachable checks are now appropriately marked
+in list views and the service grid now allows you to switch between everything or problems only.
+
+* Add tactical overview to global search [#3845](https://github.com/Icinga/icingaweb2/pull/3845)
+* Servicegrid: Add toggle to show problems only [#3871](https://github.com/Icinga/icingaweb2/pull/3871)
+* Make overdue/unreachable checks better visible [#3860](https://github.com/Icinga/icingaweb2/pull/3860)
+
+#### Authorization - Knowing and Controlling What's Going On
+
+Roles can now be even more tailored to users since the introduction of a new placeholder. This placeholder allows to
+use a user's name in restrictions. Things like `_service_responsible_person=$user:local_name$` are now possible. The
+audit log now receives failed login-attempts, that's been made possible since hooks can now run for anonymous users.
+
+* Allow roles to filter for the currently logged in user [#3493](https://github.com/Icinga/icingaweb2/issues/3493)
+* Add possibility to disable permission checks for hooks [#3849](https://github.com/Icinga/icingaweb2/pull/3849)
+* Send failed login-attempts to the audit log [#3856](https://github.com/Icinga/icingaweb2/pull/3856)
+
+See also the [audit module](https://github.com/Icinga/icingaweb2-module-audit/releases) which got an update and is
+required for [#3856](https://github.com/Icinga/icingaweb2/pull/3856) to work.
+
+### What's New in Version 2.6.3
+
+You can find issues related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/54?closed=1).
+
+#### PHP 7.3
+
+Now supported. :tada:
+
+#### LDAP - Community contributions, that's the spirit
+
+With the help of our users we've finally fixed the issue that defining multiple hostnames and enabling STARTTLS has
+never properly worked. Also, they've identified that defining multiple hostnames caused a customized port not being
+utilized and fixed it themselves.
+
+There has also a rare case been fixed that caused no group members being found in case object classes had a different
+casing than what we expected. (Good news for all the non-OpenLdap and non-MSActiveDirectory users)
+
+* LDAP connection fails with multiple servers using STARTTLS [#3639](https://github.com/Icinga/icingaweb2/issues/3639)
+* LDAPS authentication ignores custom port setting [#3713](https://github.com/Icinga/icingaweb2/issues/3713)
+* LDAP group members not found [#3650](https://github.com/Icinga/icingaweb2/issues/3650)
+
+#### We take care about your data even better now
+
+With this are newlines and HTML entities (such as `&nbsp;`) in plugin output and custom variables meant.
+Sorry if I've teased some data security folks now. :innocent:
+
+* Newlines in plugin output disappear [#3662](https://github.com/Icinga/icingaweb2/issues/3662)
+* Windows path separators are converted to newlines in custom variables [#3636](https://github.com/Icinga/icingaweb2/issues/3636)
+* HTML entities in plugin output are not resolved if no other HTML is there [#3707](https://github.com/Icinga/icingaweb2/issues/3707)
+
+#### You've wondered how you got into a famous blue police box?
+
+Don't worry, not only you and the european union are sometimes unsure what's the correct time.
+
+* Set client timezone on DB connection [#3525](https://github.com/Icinga/icingaweb2/issues/3525)
+* Ensure a valid default timezone is set in any case [#3747](https://github.com/Icinga/icingaweb2/pull/3747)
+* Fix that the event detail view is not showing times in correct timezone [#3660](https://github.com/Icinga/icingaweb2/pull/3660)
+
+#### UI - The portal to your monitoring environment, improved
+
+The collapsible sidebar introduced with v2.5 has been plagued by some issues since then. They're now fixed. Also,
+the UI should now flicker less and properly preserve the scroll position when interacting with action links. (This
+also allows the business process module to behave more stable when using drag and drop in large configurations.)
+
+* Collapsible Sidebar Issues [#3187](https://github.com/Icinga/icingaweb2/issues/3187)
+* Fix title when closing right column [#3654](https://github.com/Icinga/icingaweb2/issues/3654)
+* Preserve scroll position upon form submits [#3661](https://github.com/Icinga/icingaweb2/pull/3661)
+
+#### Corrected things we've broke recently
+
+That's due to preemptive changes to protect you from bad individuals. Unfortunately this meant that some unforeseen
+side-effects appeared after the release of v2.6.2. These are now fixed.
+
+* Multiline values in ini files broken [#3705](https://github.com/Icinga/icingaweb2/issues/3705)
+* PHP ini parser doesn't strip trailing whitespace [#3733](https://github.com/Icinga/icingaweb2/issues/3733)
+* Escaped characters in INI values are not unescaped [#3648](https://github.com/Icinga/icingaweb2/issues/3648)
+
+Though, if you've faced issue [#3705](https://github.com/Icinga/icingaweb2/issues/3705) you still need to take manual
+action (if not already done) as the provided fix does only prevent further occurrences of the resulting error. The
+required changes involve the transformation of all real newlines in Icinga Web 2's INI files to literal `\n` or `\r\n`
+sequences. (Files likely having such are the `roles.ini` and `announcements.ini`)
+
+### What's New in Version 2.6.2
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/53?closed=1).
+
+This bugfix release addresses the following topics:
+
+* Database connections to MySQL 8 no longer fail
+* LDAP connections now have a timeout configuration which defaults to 5 seconds
+* User groups are now correctly loaded for externally authenticated users
+* Filters are respected for all links in the host and service group overviews
+* Fixed permission problems where host and service actions provided by modules were missing
+* Fixed an SQL error in the contact list view when filtering for host groups
+* Fixed time zone (DST) detection
+* Fixed the contact details view if restrictions are active
+* Doc parser and documentation fixes
+
+### What's New in Version 2.6.1
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/51?closed=1).
+
+The command audit now logs a command's payload as JSON which fixes a
+[bug](https://github.com/Icinga/icingaweb2/issues/3535) that has been introduced in version 2.6.0.
+
+### What's New in Version 2.6.0
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/48?closed=1).
+
+#### Enabling you to do stuff you couldn't before
+
+* Support for PHP 7.2 added
+* Support for SQLite resources added
+* Login and Command (monitoring) auditing added with the help of a dedicated [module](https://github.com/Icinga/icingaweb2-module-audit)
+* Pluginoutput rendering is now hookable by modules which allows to render custom icons, emojis and .. cute kitties :octocat:
+
+#### Avoiding that you miss something
+
+* It's now possible to toggle between list- and grid-mode for the host- and servicegroup overviews
+* The servicegrid now supports to flip its axes which allows it to be put into a [landscape mode](https://github.com/Icinga/icingaweb2/pull/3449#issue-185415579)
+* Contacts only associated with services are visible now when restricted based on host filters
+* Negated and combined membership filters now work as expected ([#2934](https://github.com/Icinga/icingaweb2/issues/2934))
+* A more prominent error message in case the monitoring backend goes down
+* The filter editor doesn't get cleared anymore upon hitting Enter
+
+#### Making your life a bit easier
+
+* The tactical overview is now filterable and can be safely put into [the dashboard](https://github.com/Icinga/icingaweb2/pull/3446#issue-185379142)
+* It is now possible to register new announcements over the [REST Api](https://github.com/Icinga/icingaweb2/issues/2749#issuecomment-279667189)
+* Filtering for custom variables now works in UTF8 environments
+
+#### Ensuring you understand everything
+
+* The monitoring health is now beautiful to look at and properly behaves in [narrow environments](https://github.com/Icinga/icingaweb2/pull/3515#issue-200075373)
+* Updated German localization
+* Updated Italian localization
+
+#### Freeing you from unrealiable things
+
+* Removed support for PHP < 5.6
+* Removed support for persistent database connections
+
+### What's New in Version 2.5.3
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/50?closed=1).
+
+#### Fixes
+
+A fix for an issue introduced with v2.5.2 that prevented service-only contacts from appearing in the UI resulted in long
+database response times and has been reverted.
+
+### What's New in Version 2.5.2
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/49?closed=1).
+
+#### UI Changes
+
+The sidebar's search behaviour has been changed so that it does only react to user-input after the user stopped typing.
+Also, the cursor does not jump to the end of form-inputs anymore in case of an auto-refresh. We've also fixed an issue
+that caused [custom icons](https://github.com/Icinga/icingaweb2/issues/3181#issuecomment-378875462) to be inverted when
+placed in the sidebar. Last but not least, the header now expands its width beyond the 3840px mark and single dashlets
+do not show a horizontal scrollbar anymore.
+
+#### PHP7 MSSQL Compatibility
+
+Support for Microsoft's `sqlsrv` extension has been added. Also, it's now possible to setup MSSQL resources in the
+front-end using the `dblib` extension.
+
+#### Proper Error Responses
+
+An issue introduced with v2.5.1 has been resolved where some errors (especially HTTP 404 Not Found) were masked
+by another subsequent error.
+
+#### Broken LDAP Group Memberships
+
+An issue introduced with v2.5.1 has been resolved where users with a domain in their name were not associated with any
+LDAP groups.
+
+#### Monitoring Module
+
+Issuing a check using the "Check Now" action now properly causes a check being made by Icinga 2 even if outside the
+timeperiod. (Note: This issue was only present if using the Icinga 2 Api as command transport.)
+
+#### Login/Logout Expandability
+
+It's now possible for modules to provide hooks for the user authorization. This for example allows to transparently
+authenticate users in third-party applications such as [Grafana](https://github.com/Icinga/icingaweb2/pull/3401#issue-178030542).
+
+### What's New in Version 2.5.1
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/47?closed=1).
+
+Besides many other bug fixes, Icinga Web 2 v2.5.1 fixes an issue where it was no longer possible to filter by host
+custom variables in service related views. Also, this release introduces detail views for the event history and
+improved upgrading docs. Furthermore, this version censors sensitive information (e.g. LDAP passwords) in exception
+stack traces.
+
+### What's New in Version 2.5.0
+
+You can find issues and features related to this release on our [Roadmap](https://github.com/Icinga/icingaweb2/milestone/45?closed=1).
+
+#### Raised PHP Version Dependency
+
+Icinga Web 2 now requires at least PHP 5.6.
+
+#### UI Changes
+
+The style of the login screen and menu have been changed. Also, the menu of Icinga Web 2 is now collapsible.
+Browser tabs will not auto-refresh if they are inactive. Users are now allowed to change the default pagination limit
+via their preferences.
+
+#### Domain-aware Authentication for Active Directory and LDAP Backends
+
+If there are multiple AD/LDAP authentication backends with distinct domains, you are now able to make Icinga Web 2
+aware of the domains. This can be done by configuring each AD/LDAP backend's domain. You can also use the GUI for this
+purpose. Please read our [documentation](doc/05-Authentication.md#domain-aware-auth) for more information about this
+feature.
+
+#### Changes in Packaging and Dependencies
+
+Valid for distributions:
+
+* RHEL / CentOS 6 + 7
+ * Upgrading to PHP 7.0 / 7.1 via RedHat SCL (new dependency)
+ * See [Upgrading to FPM](doc/02-Installation.md#upgrading-to-fpm) for manual steps that are required
+* SUSE SLE 12
+ * Upgrading PHP to >= 5.6.0 via the alternative packages.
+ You might have to confirm the replacement of PHP < 5.6 - but that should work with any other PHP app as well
+ * Make sure to enable the new Apache module `a2enmod php7` and restart `apache2`
+
+#### Discontinued Package Updates
+
+For the following distributions Icinga Web 2 won't be updated past 2.4.x anymore:
+
+* Debian 7 wheezy
+* Ubuntu 14.04 LTS (trusty)
+* SUSE SLE 11 (all service packs)
+
+Please think about replacing your central Icinga system to a newer distribution release.
+
+Also see [packages.icinga.com](https://packages.icinga.com) for the currently supported distributions.
+
+### What's New in Version 2.4.2
+
+#### Bugfixes
+
+* Bug 2965: Transport config: Default port not changing upon auto-submit
+* Bug 2926: Wrong order when sorting by host_severity
+* Bug 2923: Number fields should be valid when empty
+* Bug 2919: Fix cached loading of module config
+* Bug 2911: Acknowledgements are not working without an expiry time
+* Bug 2878: process-check-result Button is visible even when user isn't allowed to use it
+* Bug 2850: Link to acknowledgements is wrong in the timeline
+* Bug 2841: Wrong menu height when switching back from mobile layout
+* Bug 2806: Wrong service state count in hostgroup overview
+* Bug 2805: Response from the Icinga 2 API w/ an empty result set leads to exception
+* Bug 2801: Wrong help text for the director in the icingacli
+* Bug 2784: Module and gravatar images are not served with their proper MIME type
+* Bug 2776: Defaults not respected when acknowledging problems
+* Bug 2767: Monitoring module: Config field protected vars not updated after zeroing config.ini
+* Bug 2728: Gracefully handle invalid Icinga 2 API response types
+* Bug 2718: Hide check attempt for hard states in history views
+* Bug 2716: Web 2 doesn't detect the browser time zone if the time zone offset is negative
+* Bug 2714: icingacli module disable fails on consecutive calls
+* Bug 2695: Macros cannot be used for a navigation item's url-port
+* Bug 2684: [dev.icinga.com #14027] Translation module should not write absolute path to .po files
+* Bug 2683: [dev.icinga.com #14025] Translation module should remove temp files
+* Bug 2661: [dev.icinga.com #13651] Don't offer the Icinga 2 API as transport if PHP cURL is missing
+* Bug 2660: [dev.icinga.com #13649] Make the Icinga 2 API the default command transport
+* Bug 2656: [dev.icinga.com #13627] Wrong count of handled critical service in the hover text
+* Bug 2645: [dev.icinga.com #13539] Improve error handling and validation of multiple LDAP URIs
+* Bug 2598: [dev.icinga.com #12977] Adding an empty user backend fails
+* Bug 2545: [dev.icinga.com #12640] MSSQL ressource not working
+* Bug 2523: [dev.icinga.com #12410] Click on Host in Service Grid can cause "Invalid Filter" error
+* Bug 2519: [dev.icinga.com #12330] Filter editor may show wrong values after searching
+* Bug 2509: [dev.icinga.com #12295] group_name_attribute should be "sAMAccountName" by default
+
+### What's New in Version 2.4.1
+
+Our public repositories and issue tracker have been migrated to GitHub.
+
+#### Bugfixes
+
+* Bug 2651: [dev.icinga.com #13607] Displayed times messed up in Icinga Web 2.4.0 w/ PostgreSQL
+* Bug 2654: [dev.icinga.com #13615] Setup wizard: Not possible to setup Icinga Web 2 with an external database
+* Bug 2663: [dev.icinga.com #13691] Hook::all() is broken on CLI
+* Bug 2669: [dev.icinga.com #13735] Setup wizard: Progress bar isn't shown correctly, if setup is at finish step
+* Bug 2681: [dev.icinga.com #13957] Support failover API command transport configuration
+* Bug 2686: Granular module permissions do not work for hooks
+* Bug 2687: Update URLs to icinga.com, remove wiki & update to GitHub
+
+### What's New in Version 2.4.0
+
+#### Feature
+
+* Feature 12598 (Authentication & Authorization): Support nested AD groups for Roles and not just login
+* Feature 11809 (Authentication & Authorization): Test and document multiple LDAP-URIs separated by space in LDAP ressources
+* Feature 10616 (Authentication & Authorization): Users w/o administrative permissions should be allowed to change their password
+* Feature 13381 (CLI): Allow to configure the default listen address for the CLI command web serve
+* Feature 11820 (Configuration): Check whether chosen locale is available
+* Feature 11214 (Configuration): Logger: Allow to configure the Syslog Facility
+* Feature 13117 (Framework): Add charset UTF-8 to default content type
+* Feature 12634 (Framework): Possibitlity to fold and unfold filter by click
+* Feature 11198 (Framework): Announce banner
+* Feature 11115 (Framework): Add SSL support to MySQL database resources
+* Feature 8270 (Installation): Add SELinux policy for Icinga Web 2
+* Feature 13187 (Monitoring): Command toolbar in the host and service detail views
+* Feature 12873 (Monitoring): Change default for sticky option of acknowledgements from true to false
+* Feature 12820 (Monitoring): Export detail views to JSON
+* Feature 12766 (Monitoring): Show flapping events in the host and service history views
+* Feature 12764 (Monitoring): Display downtime end even if it hasn't been started yet
+* Feature 12125 (Monitoring): Allow th in plugin output
+* Feature 11952 (Monitoring): Allow changing default of 'sticky' in acknowledgement and other command options
+* Feature 11398 (Monitoring): Send commands over Icinga 2's API
+* Feature 11835 (UI): Add clear button to search field
+* Feature 11792 (UI): Show hint if notifications are disabled globally
+* Feature 11664 (UI): Show git HEAD for modules if available
+* Feature 13461 (Vendor Libraries): Use Icinga's fork of Zend Framework 1 icingaweb2-vendor-zf1
+
+#### Bugfixes
+
+* Bug 12396 (Authentication & Authorization): Hooks don't respect module permissions
+* Bug 12164 (Authentication & Authorization): REDIRECT_REMOTE_USER not evaluated during external auth
+* Bug 12108 (Authentication & Authorization): assertPermission allows everything for unauthenticated requests
+* Bug 13357 (Configuration): Persistent database resources cannot be made non-persistent
+* Bug 12848 (Configuration): Empty "Protected Custom Variables" falls back to defaults
+* Bug 12655 (Configuration): Permission application/log is not configurable
+* Bug 12170 (Configuration): Adding a DB resource via webinterface requires one to enter a password
+* Bug 10401 (Configuration): LdapUserGroupBackendForm: user_* settings not purged
+* Bug 9804 (Configuration): Renaming the resource used for the config backend does not update the global configuration
+* Bug 11920 (Dashboard): Add to dashboard: wrong url makes whole dashboard unusable
+* Bug 13387 (Documentation): Can't display documentation of disabled modules
+* Bug 12923 (Framework): Navigation Item name must be of type string or NavigationItem
+* Bug 12852 (Framework): Hosts without any services are hidden from roles with monitoring/filter/objects set
+* Bug 12760 (Framework): Do not log exceptions other than those resulting in a HTTP 500 status-code
+* Bug 12583 (Framework): Unhandled exceptions while handling REST requests will silently drop the http response code
+* Bug 12580 (Framework): REST requests cannot be anonymous
+* Bug 12557 (Framework): Module description cannot be on a single line
+* Bug 12299 (Framework): FilterExpression renders a&!b as a=1&b!=1
+* Bug 12161 (Framework): Icinga Web 2 doesn't set Content-Type
+* Bug 12065 (Framework): IniRepository: update/delete not possible with iterator
+* Bug 11743 (Framework): INI writer must not persist section keys with a null value
+* Bug 11185 (Framework): SummaryNavigationItemRenderer should show worst state
+* Bug 10361 (Framework): Handle E_RECOVERABLE_ERROR
+* Bug 13459 (Installation): Setup: Can't view monitoring config summary with Icinga 2 API as command transport
+* Bug 13467 (JavaScript): renderLayout has side-effects
+* Bug 13115 (JavaScript): actiontable should not clear active row in case there is no newer one
+* Bug 12541 (JavaScript): Menu not reloaded in case no search is available
+* Bug 12328 (JavaScript): Separate vendor JavaScript libraries w/ semicolons and newlines on import
+* Bug 10704 (JavaScript): JS: Always use the jQuery find method w/ node context when selecting elements
+* Bug 10703 (JavaScript): JS: Don't use var self = this, but var _this = this
+* Bug 11431 (Modules): Modules can't require permission on menu items
+* Bug 10870 (Modules): Refuse erroneous module folder names when enabling the module
+* Bug 13243 (Monitoring): Inconsistent host and service flags
+* Bug 12889 (Monitoring): Timeline broken
+* Bug 12810 (Monitoring): Scheduling a downtime for all services of a host does not work w/ the Icinga 2 API as command transport
+* Bug 12313 (Monitoring): Multi-line strings within host.notes are being displayed as single line
+* Bug 12223 (Monitoring): State not highlighted in plugin output if it contains HTML
+* Bug 12019 (Monitoring): Contact view shows service filters with 'Downtime' even if not set
+* Bug 11915 (Monitoring): Performance data: negative values not handled
+* Bug 11859 (Monitoring): Can't separate between SOFT and HARD states in the history views
+* Bug 11766 (Monitoring): Performance data: Fit label column to show as much text as possible
+* Bug 11744 (Monitoring): Empty user groups are not displayed
+* Bug 10774 (Monitoring): Scheduling downtimes for child hosts doesn't work w/ Icinga 2.x (waiting for Icinga 2)
+* Bug 10537 (Monitoring): Filtering with not-equal on custom variable doesn't show hosts without this cv
+* Bug 7755 (Monitoring): Remove autosubmit in eventgrid
+* Bug 12133 (Navigation): Username and password not being passed in navigation item URLs
+* Bug 12776 (Print & Export): dompdf fails when border-style is set to auto
+* Bug 12723 (Print & Export): Allowed memory size exhausted when exporting the history view to CSV
+* Bug 12660 (QA): Choosing the Icinga theme floods the log with error messages
+* Bug 12774 (UI): Lot's of <span style="visibility:hidden; display:none;"></span> in Output
+* Bug 12134 (UI): Copy and paste: Plugin output contains unicode zero-width space characters
+* Bug 10691 (UI): Closing the detail area does not update the rows selected counter
+* Bug 13095 (Vagrant VM): TicketSalt constant missing
+* Bug 12717 (Vagrant VM): PluginContribDir constant removed during vagrant provisioning
+
+### What's New in Version 2.3.4/2.3.3
+
+#### Bugfixes
+
+* Bug 11267: Links in plugin output don't behave as expected
+* Bug 11348: Host aliases are not shown in detail area
+* Bug 11728: First non whitespace character after comma stripped from plugin output
+* Bug 11729: Sort by severity depends on state type
+* Bug 11737: Zero width space characters destroy state highlighting in plugin output
+* Bug 11796: Zero width space characters may destroy links in plugin output
+* Bug 11831: module.info parsing fails in case it contains newlines that are not part of the module's description
+* Bug 11850: "Add to menu" tab unnecessarily appears in command forms
+* Bug 11871: Colors used in the timeline are not accessible
+* Bug 11883: Delete action on comments and downtimes in list views not accessible because they lack context
+* Bug 11885: Database: Asterisk filters ignored when combined w/ other filters
+* Bug 11910: Web 2 lacks mobile meta tags
+* Fix remote code execution via remote command transport
+
+### What's New in Version 2.3.2
+
+#### Feature
+
+* Feature 11629: Simplified event-history date and time representation
+
+#### Bugfixes
+
+* Fix a privilege escalation issue in the monitoring module for authenticated users
+* Bug 10486: Menu rendering fails when no monitoring backend was configured
+* Bug 10847: Warn about illogical dates
+* Bug 10848: Can't change items per page if filter is in modify state
+* Bug 11392: Can't configure monitoring backend via the web interface when no monitoring backend was configured
+
+### What's New in Version 2.3.1
+
+#### Bugfixes
+
+* Bug 11598: Invalid SQL queries for PostgreSQL
+
+### What's New in Version 2.3.0
+
+#### Features
+
+* Feature 10887: lib: Provide User::getRoles()
+* Feature 10965: Roles: Restrict visibility of custom variables
+* Feature 11404: Add is_reachable filter column to host and service data views
+* Feature 11485: lib/LDAP: Support scopes base and one
+* Feature 11495: Support data URIs in href
+* Feature 11529: Don't offer command disable notifications /w expire time if backend is Icinga 2
+
+#### Bugfixes
+
+* Bug 9386: Improve order of documentation chapters
+* Bug 10820: Style problems with long plugin output lines
+* Bug 11078: Can't remove default dashboards
+* Bug 11099: Mobile menu icon is mispositioned
+* Bug 11128: Menu stops refreshing when there is text in the search field
+* Bug 11145: Pagination compontents should not float around
+* Bug 11171: Icinga Web 2 tries to load an ifont which results in 404
+* Bug 11245: icingacli monitoring list --problems throws an exception
+* Bug 11264: Cannot execute queries while other unbuffered queries are active
+* Bug 11277: external auth with PHP internal webserver still buggy
+* Bug 11279: Restrict access to Applicationlog
+* Bug 11299: Icon images no longer prepend img/icons
+* Bug 11391: External auth reads REMOTE_USER from process environment instead of request
+* Bug 11414: Doc module does not render images with relative path
+* Bug 11465: Stylesheet remains unchanged when module CSS/LESS files have been changed
+* Bug 11489: lib/LDAP: ordering does explicitly set fields
+* Bug 11490: lib/LDAP: LdapUtils::explodeDN replace deprecated use of eval in preg_replace
+* Bug 11516: Accessibility: Focus in Tactical Overview barely visible
+* Bug 11558: Missing ) in the documentation
+* Bug 11568: Docs: Global permissions table is broken
+
+### What's New in Version 2.2.0
+
+#### Features
+
+* Feature 8487: Number headings in the documentation module
+* Feature 8963: Feature commands in the multi select views
+* Feature 10654: Render links in acknowledgements, comments and downtimes
+* Feature 11062: Allow style classes in plugin output
+* Feature 11238: Puppet/Vagrant: Install mod_ssl and forward port 443
+
+#### Bugfixes
+
+* Bug 7350: Tabs are missing if JS is disabled
+* Bug 9800: Debian packaging: Ship translation module w/ the icingaweb2 package and install its config.ini
+* Bug 10173: Failed commands give no useful error any more
+* Bug 10251: Icinga Web 2 fails to run with PHP7
+* Bug 10277: Special characters are incorrectly escaped for tooltips in the service grid
+* Bug 10289: Doc module: Headers are cut off when clicking on TOC links
+* Bug 10309: Move auth backend configuration to app config
+* Bug 10310: Monitoring details: information/action ordering
+* Bug 10362: Debian packaging: Separate package for CLI missing
+* Bug 10366: Text plugin output treated as HTML in too many occasions
+* Bug 10369: Accessibility: Focus not visible and lost after refresh
+* Bug 10397: Users with no permissions can check multiple services
+* Bug 10442: Edit user control should be more prominent
+* Bug 10469: "Remove Acknowledgement" text missing in multi-select views
+* Bug 10506: HTTP basic auth request is sent when using Kerberos authentication with Apache2 and mod_php
+* Bug 10625: Return local date and time when lost connection to the web server
+* Bug 10640: Respect protected_variables in nested custom variables too
+* Bug 10778: Filters in the host group and service group overview not applied to state links
+* Bug 10786: Whitespace characters are ignored in the plugin output in list views
+* Bug 10805: Setup Wizard: Obsolete PHP sockets requirement
+* Bug 10856: Benchmark is not rendered on many pages
+* Bug 10871: Get rid of padding in controls
+* Bug 10878: Dashboards different depending on username casing
+* Bug 10881: Move iframe from modules to framework
+* Bug 10917: Event grid tiles: The filter column "from" is not allowed here
+* Bug 10918: Error on logout when using external authentication
+* Bug 10921: icingacli monitoring list --format=csv throws error
+* Bug 11000: Change license header to only reflect a file's year of creation/initial commit
+* Bug 11008: Wobbling spinners
+* Bug 11021: Global default theme is not applied while not authenticated
+* Bug 11032: Fix icon_image size and provide a CSS class for theming
+* Bug 11039: Misleading tooltip in Tactical Overview
+* Bug 11051: Preferences and navigation items stored in INI files rely on case sensitive usernames
+* Bug 11073: Active row is flickering on refresh
+* Bug 11091: Custom navigation items: URL is not escaped/encoded
+* Bug 11100: Comments are always persistent
+* Bug 11114: Validate that a proper root DN is set for LDAP resources
+* Bug 11117: Vendor: Update dompdf to version 0.6.2
+* Bug 11119: icingacli shows ugly exception when unable to access the config directory
+* Bug 11120: icingacli: command and action shortcuts have been broken
+* Bug 11126: Invalid cookie value in cookie icingaweb2-tzo
+* Bug 11142: LDAP User Groups backend group_filter
+* Bug 11143: Layout: Tabs should be left-aligned
+* Bug 11151: Having basic authentication on the webserver but not in Icinga Web 2 causes Web 2 to require basic auth
+* Bug 11168: Debian packaging: Don't patch HTMLPurifier loading and install HTMLPurifier*.php files from the library/vendor root
+* Bug 11187: Session cookie: Path too broad and unset secure flag on HTTPS
+* Bug 11197: Menu items without url should ignore the target configuration
+* Bug 11260: Scheduling downtimes through the API not working
+
+### What's New in Version 2.1.1
+
+#### Features
+
+* Feature 10488: Use _ENV variables with built-in PHP webserver
+* Feature 10705: Theming
+* Feature 10898: Winter theme
+
+#### Bugfixes
+
+* Bug 9685: Deprecate Module::registerHook() in favor of Hook::provideHook()
+* Bug 9957: Sort hosts and services by last state change
+* Bug 10123: CSS loading may fail w/ mkdir(): File exists in FileCache.php
+* Bug 10126: setup config directory --config should use mkdir -p instead of mkdir()
+* Bug 10166: library/vendor/HTMLPurifier tree is incorrectly unpacked
+* Bug 10170: Link to service downtimes from multiple selected services includes host downtimes aswell
+* Bug 10338: Debian: Failed to open stream HTMLPurifier/HTMLPurifier.php
+* Bug 10603: Line breaks are not respected in acknowledgements, comments and downtimes
+* Bug 10658: SUSE packages have the wrong dependencies
+* Bug 10659: LDAP group members are shown with their DN and membership registration does not work
+* Bug 10670: State not highlighted in plugin output
+* Bug 10671: Auto-focus the username field on the login page
+* Bug 10683: lib/CLI command web serve: rename variable basedir to something meaningful
+* Bug 10702: Host- and Service-Actions configured in Web 2 do not resolve any macros
+* Bug 10749: XHR application-state requests pollute the URL if not authenticated
+* Bug 10771: Login shows "Anmelden........" upon login with the german locale
+* Bug 10781: LoggingConfigForm.php complains about whitespace but checks with /^[^\W]+$/
+* Bug 10790: "Problems - Service Grid" does not work with host names that contain only digits
+* Bug 10884: Tabs MUST throw an exception when activating an inexistant tab
+* Bug 10886: "impacted" container is no longer fading out
+* Bug 10892: Wrong mask for FileCache's temp directory
+
+### What's New in Version 2.1.0
+
+#### Features
+
+* Feature 10613: Extend and simplify Hook api
+
+#### Bugfixes
+
+* Bug 8713: Invalid filter "host_name=*(test)*", unexpected ) at pos 17
+* Bug 8999: Navigation and search bar is not available using a small width
+* Bug 10229: Dashboard requests do not refresh the session
+* Bug 10268: Unhandled services in the hosts overview list don't stand out
+* Bug 10287: Redirect after login no longer working
+* Bug 10288: The order for the limit links is incorrect
+* Bug 10292: Hovered links in hover menu are unreadable
+* Bug 10293: Hover menu is missing it's arrow for menu entries providing badges
+* Bug 10295: Reset static line-height on body
+* Bug 10296: Scrolling to the bottom of the page does not load more events
+* Bug 10299: Badges are overridden by menu text
+* Bug 10301: Format helpers like timeSince are polluted with text-small
+* Bug 10303: Zooming in, or having another layout destroys the hover menu
+* Bug 10304: Cannot access a host's customvars for service actions
+* Bug 10305: Hover menu arrow color no longer fits background color
+* Bug 10316: Not all Servicegroups / Hostgroups are shown
+* Bug 10317: Event history style broken
+* Bug 10319: Recursive sharing navigation items doesn't work.
+* Bug 10321: Module iframe doesn't show website with parameters as a single column
+* Bug 10328: ZendFramework packages missing for SLES12
+* Bug 10359: Charset option not passed thru PDO adapter
+* Bug 10364: PostgreSQL queries apply LOWER() on selected columns
+* Bug 10367: Broken user- and group-management
+* Bug 10389: Host overview: vsprintf(): Too few arguments
+* Bug 10402: LdapUserGroupBackend: user_base_dn not used from UserBackend
+* Bug 10419: Swapped icon image order in service header
+* Bug 10490: Unhandled service counter in the hosts overview shows incorrect values
+* Bug 10533: Form notifications of type information are green
+* Bug 10567: Member user name used for basedn when querying usergroup members
+* Bug 10597: Empty PDO charset option is invalid
+* Bug 10614: Class loader: hardcode module and Zend prefixes
+* Bug 10623: Acknowledging multiple selected objects erroneous
+
+### What's New in Version 2.0.0
+
+#### Changes
+
+
+Upgrading to Icinga Web 2 2.0.0
+
+Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on php-ZendFramework which is available through the EPEL repository. Before, Zend was installed as Icinga Web 2 vendor library through the package icingaweb2-vendor-zend. After upgrading, please make sure to remove the package icingaweb2-vendor-zend.
+
+Icinga Web 2 version 2.0.0 requires permissions for accessing modules. Those permissions are automatically generated for each installed module in the format module/<moduleName>. Administrators have to grant the module permissions to users and/or user groups in the roles configuration for permitting access to specific modules. In addition, restrictions provided by modules are now configurable for each installed module too. Before, a module had to be enabled before having the possibility to configure restrictions.
+
+The instances.ini configuration file provided by the monitoring module has been renamed to commandtransports.ini. The content and location of the file remains unchanged.
+
+The location of a user's preferences has been changed from config-dir/preferences/username.ini to config-dir/preferences/username/config.ini. The content of the file remains unchanged.
+
+#### Features
+
+* Feature 5600: User specific menu entries
+* Feature 5647: GUI for permission and restriction assignment
+* Feature 5786: Namespace all web controllers
+* Feature 6144: Provide additional dashboard panes per default
+* Feature 6677: Allow to extend the content of a dashlet on the right
+* Feature 7180: Show active cluster hostname in the monitoring health view
+* Feature 7367: GUI for adding action and notes URLs
+* Feature 7570: Document installation
+* Feature 7773: Interpret links in custom variables
+* Feature 8336: IDO: Double check that we always add the is_active = 1 condition in our queries
+* Feature 8369: Show an indicator when automatic form submission is ongoing
+* Feature 8378: Indicate when check results are being late
+* Feature 8407: Document example commands for installing from source
+* Feature 8642: Show acknowledgement expire time (if any) in the host and service detail view
+* Feature 8645: Generic iFrame module
+* Feature 8758: Add support for file uploads
+* Feature 8848: Show activity indicator for dashlets
+* Feature 8884: Move the menu entry for notifications beneath history
+* Feature 8981: Combo backend for command transports (fallback mechanism)
+* Feature 8985: Visually separate enabled and disabled modules in the modules view
+* Feature 9029: Provide a complete list of available filter columns plus custom variables (where appropriate) in the filter editor
+* Feature 9030: Service grid: Add limit control
+* Feature 9247: Show Icinga Web 2's version in the frontend
+* Feature 9364: Apply sort rules for ldap queries on the server's side
+* Feature 9381: List installed modules, versions and state in the about page
+* Feature 9453: Vagrant: Upgrade to CentOS 7
+* Feature 9460: IDO resource configuration: Ensure that the user is running PostgreSQL 9.1+
+* Feature 9524: Improve setup wizard
+* Feature 9525: Configuration enhancements
+* Feature 9591: IP Address Search
+* Feature 9604: Add Inspection API for Connections
+* Feature 9605: LDAP Connection add Test Function
+* Feature 9630: Inspectable: Add inspectable API to LDAP connections
+* Feature 9641: Add Inspection API for DB Connections
+* Feature 9644: Permit access to modules
+* Feature 9645: Support for address6
+* Feature 9651: Automatically use the correct instance configuration based on a host's or service's instance
+* Feature 9660: Basic access authentication
+* Feature 9661: Query for limit+1 for "Show more results" candidates
+* Feature 9683: Allow to create MSSQL and Oracle DB resources
+* Feature 9702: Allow module developers to define additional static files
+* Feature 9761: Store active menu item as HTML5 history state information
+* Feature 9772: Allow to list groups from a LDAP backend
+* Feature 9826: Allow to select text in the host and service detail area header via double click
+* Feature 9830: Monitoring: Support the wildcard restriction for "administrative" roles
+* Feature 9888: Display a host's and service's check timeperiod as well as notification timeperiod in the detail view
+* Feature 9908: Use better icons for resources, backends and module state
+* Feature 9942: Add a warning to the navigition if the last IDO update is older than 5 minutes
+* Feature 9943: Offer instance_name as query column
+* Feature 9945: Show instance_name in a host's and service's detail view
+* Feature 10033: Provide "Counter"-View
+
+#### Bugfixes
+
+* Bug 6644: Default sort order is not applied
+* Bug 7383: This webpage has a redirect loop without cookies
+* Bug 7486: Instance Configuration: Instance must NOT be a GET parameter when creating an instance
+* Bug 7488: Instance Configuration: Instance parameter must be mandatory for updating and removing instances
+* Bug 7489: Instance Configuration: Custom validation errors must be shown in the form not as notification
+* Bug 7490: Instance Configuration: HTTP response code flaws
+* Bug 7818: Incorrect language & timezone detection w/ Safari
+* Bug 7930: Hide external commands which are not supported by Icinga 2
+* Bug 8312: Don't show last and next check information and schedule check controls for passive only checks
+* Bug 8620: Searching in the downtimes list view throws an exception
+* Bug 8623: Selected row lost after auto-refresh in every overview except for hosts and services
+* Bug 8703: Do not show computer accounts for Active Directory
+* Bug 8768: Range multiselection not working in IE11
+* Bug 8845: Missing downtime end information in host and service detail views
+* Bug 8954: Document and rename Ldap\Connection to Ldap\LdapConnection
+* Bug 8955: Document and rename Ldap\Query to Ldap\LdapQuery
+* Bug 8969: Tooltips hidden after auto refresh
+* Bug 8975: Error messages disappear after auto refresh #2
+* Bug 8983: Remove yellow boxes from forms and wherever else used
+* Bug 9024: Form autosubmits cause autorefreshs to not run anymore
+* Bug 9036: Plugin output HTML tags are always escaped
+* Bug 9042: Browser address bar gets not updated when closing the detail area while a request for the url that has just been closed is pending
+* Bug 9054: Multiselection not visible until a subsequent auto-refresh has been completed
+* Bug 9168: Can't use Icinga Web 2 w/ IDO version 1.7
+* Bug 9179: LDAP discovery relies on anonymous access and does not respect encryption
+* Bug 9266: Downtimes show "Starts in" for objects with non-problem state
+* Bug 9306: Installation Wizard complains about "required and must not be empty"-fields when the user changes the database type first
+* Bug 9314: RPM packages do not require Zend PDO packages which results in missing 'php-pdo' exception
+* Bug 9330: Uncaught TypeError: Cannot read property 'id' of undefined when deleting comments or downtimes via their respective overview
+* Bug 9333: Sorting the service grid by service description fails w/ PostgreSQL
+* Bug 9346: Potential active rows not deselected when navigating by browser history
+* Bug 9347: Service names with round bracket fail w/ innvalid filter exception when selecting multiple services
+* Bug 9348: LDAP filter input errors w/ "The filter must not be wrapped in parantheses"
+* Bug 9349: Duplicate headers from Controller::postDispatch()
+* Bug 9360: service matrix does not show all intersections
+* Bug 9374: Non-existent modules can be disabled
+* Bug 9375: Fatal error in icingacli (icingacli-2.0.0-3.beta3.el7.centos.noarch)
+* Bug 9376: INI writer must not persist section keys with a null value
+* Bug 9398: Rename menu "authentication" to "security"
+* Bug 9402: A command form's view script cannot be found if benchmark is enabled
+* Bug 9418: DB resources: Do not allow to configure table prefixes
+* Bug 9421: Sort controls misbehavior
+* Bug 9449: The use statement with non-compound name ... has no effect w/ PHP 5.6.9+
+* Bug 9454: Ghost host- and servicegroups
+* Bug 9472: Fetch object statistics only if they're actually displayed
+* Bug 9473: Inconsistent counters for service problems
+* Bug 9477: Command forms have no tabs
+* Bug 9483: Icinga\Web\Widget\Paginator should not require a full query interface
+* Bug 9484: Document that the web server has to be restarted after adding the web server user to the icingaweb2 system group
+* Bug 9494: Refresh button loads invalid links for views with complex filters
+* Bug 9497: Eventhistory: Quick search not working
+* Bug 9498: Service overview: Cannot quick search for hosts
+* Bug 9499: Hostgroup overview: Cannot quick search for hosts
+* Bug 9500: Servicegroup overview: Cannot quick search for services
+* Bug 9502: Comment overview: Cannot quick search
+* Bug 9503: Comment overview shows duplicate entries when filtering for services
+* Bug 9504: Contactgroup overview: Cannot quick search
+* Bug 9505: Contact overview: Cannot quick search
+* Bug 9506: Notification overview: Cannot quick search
+* Bug 9509: Setup: Authentication backend validation broken
+* Bug 9511: Setup: Cannot select an existing user as admin account when I've configured an authentication backend of type msldap
+* Bug 9516: Improve request processing for all monitoring config forms
+* Bug 9517: Behave nicely in case no monitoring instance resources are configured
+* Bug 9519: Monitoring backend configuration does not validate IDO resources
+* Bug 9529: RPM: Apache config ist not defined as configuration file
+* Bug 9530: Creating a dashlet with "()" in dashboard title affects all dashboards
+* Bug 9538: Use display_name for host and service names in the service grid
+* Bug 9553: User- and Group-Management broken on PHP > 5.3
+* Bug 9572: Cannot remove a user group from a MariaDB backend
+* Bug 9573: Selecting multiple services not working while being restricted
+* Bug 9574: Multiviews do not only display the chosen objects but everything, if a restriction is active
+* Bug 9582: icon_image does not allow to use an icon from our ifont
+* Bug 9597: Clicking on the row of a service notification will show the host
+* Bug 9607: Ignoring LDAP connection certificate errors does not have any effect
+* Bug 9608: LDAP connection must fail when the configured encryption is not possible
+* Bug 9611: generictts integration fails if regular expression is empty
+* Bug 9615: Hardcoded PHP and gettext tools path
+* Bug 9616: Security config form shows no tabs
+* Bug 9626: Tactical overview does not auto-refresh
+* Bug 9633: Icinga\Cli\Command is unable to detect exact action names
+* Bug 9646: If a CLI command fails, crucial exception information missing w/o --trace
+* Bug 9668: Browser history issues
+* Bug 9672: Invalid host passive check result state: unreachable
+* Bug 9674: Don't show comment(s) of acknowledgement(s) in the comment list of a host or service but next to whether the host or service problem is acknowledged
+* Bug 9687: @import rules not working in a module's module.less
+* Bug 9688: Icinga Web 2 ignores Cache-Control:no-cache
+* Bug 9692: Can't filter for custom variables
+* Bug 9694: Lib: Weird interface for creating problem menu entries
+* Bug 9695: IDO: Empty programstatus table not indicated as problem in the menu
+* Bug 9696: Logged exceptions for custom menu item renderers are missing crucial exception information
+* Bug 9719: Monitoring backend validation cannot be skipped
+* Bug 9739: DbUserBackend inspection unsuccessful for backends with just a single user
+* Bug 9751: Bad performance for quick searches
+* Bug 9765: instances.ini: transport is undocumented
+* Bug 9787: It's not possible to use Unix socket to connect to PostgreSQL
+* Bug 9790: Do not suggest to enable modules if it's not possible
+* Bug 9815: Multiview detail: controls have wrong link target
+* Bug 9817: Documentation: Required parameter 'chapter' missing
+* Bug 9819: JS Behaviors: Selection not updated when using multi detail controls
+* Bug 9828: Wrong count for queries having a group by clause
+* Bug 9837: Documentation: Don't suggest to install icingacli on Debian
+* Bug 9844: url anchors not working if a column hash (#!) is also part of the url
+* Bug 9869: A module's rendered event is not called upon initialization
+* Bug 9892: Module styles not visible for anonymous users
+* Bug 9901: Use the DN to fetch group memberships from LDAP
+* Bug 9932: Url to extend the timeline is pushed to history
+* Bug 9954: PostgreSQL queries use LOWER(...) for non-collated columns which have a collated counterpart
+* Bug 9955: PostgreSQL queries ordered by collated columns don't use LOWER
+* Bug 9956: Unnecessary GROUP BY clauses
+* Bug 9959: Authentication documentation suggests outdated backend identifier "ad"
+* Bug 9963: Service history is disordered and shows service and host history
+* Bug 9965: format=json does not respect the filter objects
+* Bug 9971: Seleting multiple objects at once doesn't work anymore
+* Bug 9995: "Show More" links broken in the Alert Summary
+* Bug 9998: Can't use custom variables as restriction filter
+* Bug 10009: Prettify page layout when accessing a non-existent route while not being authenticated
+* Bug 10016: config/* does not permit access to the application and authentication configuration
+* Bug 10025: Filter, submitting form via keyboard doesn't work on chrome
+* Bug 10031: Navigation by history is broken
+* Bug 10046: Menu is somehow confusing top/sub-level entries
+* Bug 10082: Adding an entry to a menu section influences it's position
+* Bug 10150: IniParser should unescape escaped sections automatically
+* Bug 10151: Do not validate section names in forms
+* Bug 10155: Multiselection disapperears when issuing commands
+* Bug 10160: Notifications/Alert Summary: Grouping errors w/ PostgreSQL
+* Bug 10163: Search for hostname does not work in snapshot release
+* Bug 10169: Multiselect URLs broken where base url != /icingaweb2
+* Bug 10172: Customvar filters are mostly broken, completely for Icinga 1.x
+* Bug 10218: Notes URL isn't showing properly
+* Bug 10236: notes_url and action_url target is always icinga.domain.de
+* Bug 10246: Use a separate configuration file for each type of navigation item
+* Bug 10263: Forms with target=_next remain unusable after first submission
+
+### What's New in Version 2.0.0-rc1
+
+#### Changes
+
+* Improve layout and look and feel in many ways
+* Apply host, service and custom variable restrictions to all monitoring objects
+* Add fullscreen mode (?showFullscreen)
+* User and group management
+* Comment and Downtime Detail View
+* Show icon_image in host/service views
+* Show Icinga program version in monitoring health
+
+#### Features
+
+* Feature 4139: Notify monitoring backend availability problems
+* Feature 4498: Allow to add columns to monitoring views via URL
+* Feature 6392: Resolve Icinga 2 runtime macros in action and notes URLs
+* Feature 6729: Fullscreen mode
+* Feature 7343: Fetch user groups from LDAP
+* Feature 7595: Remote connection resource configuration
+* Feature 7614: Right-align icons
+* Feature 7651: Add module information (module.info) to all core modules
+* Feature 8054: Host Groups should list number of hosts (as well as services)
+* Feature 8235: Show host and service notes in the host and service detail view
+* Feature 8247: Move notifications to the bottom of the page
+* Feature 8281: Improve layout of comments and downtimes in the host and service detail views
+* Feature 8310: Improve layout of performance data and check statistics in the host and service detail views
+* Feature 8565: Improve look and feel of the monitoring multi-select views
+* Feature 8613: IDO queries related to concrete objects should not depend on collations
+* Feature 8665: Show icon_image in the host and service detail views
+* Feature 8781: Automatically deselect rows when closing the detail area
+* Feature 8826: User and group management
+* Feature 8849: Show only three (or four) significant digits (e.g. in check execution time)
+* Feature 8877: Allow module developers to implement new/custom authentication methods
+* Feature 8886: Require mandatory parameters in controller actions and CLI commands
+* Feature 8902: Downtime detail view
+* Feature 8903: Comment detail view
+* Feature 9009: Apply host and service restrictions to related views as well
+* Feature 9203: Wizard: Validate that a resource is actually an IDO instance
+* Feature 9207: Show icinga program version in Monitoring Health
+* Feature 9223: Show the active ido endpoint in the monitoring health view
+* Feature 9284: Create a ServiceActionsHook
+* Feature 9300: Support icon_image_alt
+* Feature 9361: Refine UI for RC1
+* Feature 9377: Permission and restriction documentation
+* Feature 9379: Provide an about.md
+
+#### Bugfixes
+
+* Bug 6281: ShowController's hostAction() and serviceAction() do not respond with 400 for invalid/missing parameters and with 404 if the host or service wasn't found
+* Bug 6778: Duration and history time formatting isn't correct
+* Bug 6952: Unauthenticated users are provided helpful error messages
+* Bug 7151: Play nice with form-button-double-clickers
+* Bug 7165: Invalid host address leads to exception w/ PostgreSQL
+* Bug 7447: Commands sent over SSH are missing the -i option when using a ssh user aside from the webserver's user
+* Bug 7491: Switching from MySQL to PostgreSQL and vice versa doesn't change the port in the resource configuration
+* Bug 7642: Monitoring menu renderers should be moved to the monitoring module
+* Bug 7658: MenuItemRenderer is not so easy to extend
+* Bug 7876: Not all views can be added to the dashboard w/o breaking the layout
+* Bug 7931: Can't acknowledge multiple selected services which are in downtime
+* Bug 7997: Service-Detail-View tabs are changing their context when clicking the Host-Tab
+* Bug 7998: Navigating to the Services-Tab in the Service-Detail-View displays only the selected service
+* Bug 8006: Beautify command transport error exceptions
+* Bug 8205: List views should not show more than the five worst pies
+* Bug 8241: Take display_name into account when searching for host and service names
+* Bug 8334: Perfdata details partially hidden depending on the resolution
+* Bug 8339: Lib: SimpleQuery::paginate() must not fetch page and limit from request but use them from parameters
+* Bug 8343: Status summary does not respect restrictions
+* Bug 8363: Updating dashlets corrupts their URLs
+* Bug 8453: The filter column "_dev" is not allowed here
+* Bug 8472: Missing support for command line arguments in the format --arg=<value>
+* Bug 8474: Improve layout of dictionaries in the host and service detail views
+* Bug 8624: Delete multiple downtimes and comments at once
+* Bug 8696: Can't search for Icinga 2 custom variables
+* Bug 8705: Show all shell commands required to get ready in the setup wizard
+* Bug 8706: INI files should end with a newline character and should not contain superfluous newlines
+* Bug 8707: Wizard: setup seems to fail with just one DB user
+* Bug 8711: JS is logging "ugly" side exceptions
+* Bug 8731: Apply host restrictions to service views
+* Bug 8744: Performance data metrics with value 0 are not displayed
+* Bug 8747: Icinga 2 boolean variables not shown in the host and service detail views
+* Bug 8777: Server error: Service not found exception when service name begins or ends with whitespaces
+* Bug 8815: Only the first external command is sent over SSH when submitting commands for multiple selected hosts or services
+* Bug 8847: Missing indication that nothing was found in the docs when searching
+* Bug 8860: Host group view calculates states from service states; but states should be calculated from host states instead
+* Bug 8927: Tactical overview does not respect restrictions
+* Bug 8928: Host and service groups views do not respect restrictions
+* Bug 8929: Setup wizard does not validate whether the PostgreSQL user for creating the database owns the CREATE ROLE system privilege
+* Bug 8930: Error message about refused connection to the PostgreSQL database server displayed twice in the setup wizard
+* Bug 8934: Status text for ok/up becomes white when hovered
+* Bug 8941: Long plugin output makes the whole container horizontally scrollable instead of just the row containing the long plugin output
+* Bug 8950: Improve English for "The last one occured %s ago"
+* Bug 8953: LDAP encryption settings have no effect
+* Bug 8956: Can't login when creating the database connection for the preferences store fails
+* Bug 8957: Fall back on syslog if the logger's type directive is misconfigured
+* Bug 8958: Switching LDAP encryption to LDAPS doesn't change the port in the resource configuration
+* Bug 8960: Remove exclamation mark from the notification "Authentication order updated!"
+* Bug 8966: Show custom variables visually separated in the host and service detail views
+* Bug 8967: Remove right petrol border from plugin output in the host and service detail views
+* Bug 8972: Can't view Icinga Web 2's log file
+* Bug 8994: Uncaught exception on empty session.save_path()
+* Bug 9000: Only the first line of a stack trace is shown in the applications log view
+* Bug 9007: Misspelled host and service names in commands are not accepted by icinga
+* Bug 9008: Notification overview does not respect restrictions
+* Bug 9022: Browser title does not change in case of an error
+* Bug 9023: Toggling feature...
+* Bug 9025: A tooltip of the service grid's x-axe makes it difficult to click the title of the currently hovered column
+* Bug 9026: Add To Dashboard ... on the dashboard
+* Bug 9046: Detail View: Downtimes description misses space between duration and comment text
+* Bug 9056: Filter for host/servicegroup search doesn't work anymore
+* Bug 9057: contact_notify_host_timeperiod
+* Bug 9059: Can't initiate an ascending sort by host or service severity
+* Bug 9198: monitoring/command/feature/object does not grant the correct permissions
+* Bug 9202: The config\* permission does not permit to navigate to the configuration
+* Bug 9211: Empty filters are being rendered to SQL which leads to syntax errors
+* Bug 9214: Detect multitple icinga_instances entries and warn the user
+* Bug 9220: Centralize submission and apply handling of sort rules
+* Bug 9224: Allow anonymous LDAP binding
+* Bug 9281: Problem with Icingaweb 2 after PHP Upgrade 5.6.8 -> 5.6.9
+* Bug 9317: Web 2's ListController inherits from the monitoring module's base controller
+* Bug 9319: Downtimes overview does not respect restrictions
+* Bug 9350: Menu disappears in user group management view
+* Bug 9351: Timeline links are broken
+* Bug 9352: User list should be sorted
+* Bug 9353: Searching for users fails, at least with LDAP backend
+* Bug 9355: msldap seems not to be a first-class citizen
+* Bug 9378: Rpm calls usermod w/ invalid option on openSUSE
+* Bug 9384: Timeline+Role problem
+* Bug 9392: Command links seem to be broken
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ecbc059
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+ 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
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. 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 program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; 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.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bc1fc77
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# Icinga Web 2
+
+[![PHP Support](https://img.shields.io/badge/php-%3E%3D%207.2-777BB4?logo=PHP)](https://php.net/)
+![Build Status](https://github.com/icinga/icingaweb2/workflows/PHP%20Tests/badge.svg?branch=master)
+[![Github Tag](https://img.shields.io/github/tag/Icinga/icingaweb2.svg)](https://github.com/Icinga/icingaweb2)
+
+![Icinga Logo](https://icinga.com/wp-content/uploads/2014/06/icinga_logo.png)
+
+1. [About](#about)
+2. [License](#license)
+3. [Installation](#installation)
+4. [Documentation](#documentation)
+5. [Support](#support)
+6. [Contributing](#contributing)
+
+## About
+
+**Icinga Web 2** is the next generation open source monitoring web interface, framework
+and command-line interface developed by the [Icinga Project](https://icinga.com/), supporting Icinga 2,
+Icinga Core and any other monitoring backend compatible with the IDO database.
+
+![Icinga Web 2 Monitoring Module with Graphite](doc/res/monitoring-module-preview.png "Icinga Web 2 Monitoring Module with Graphite")
+
+## License
+
+Icinga Web 2 and the Icinga Web 2 documentation are licensed under the terms of the GNU
+General Public License Version 2, you will find a copy of this license in the
+COPYING file included in the source package.
+
+## Installation
+
+For installing Icinga Web 2 please check the [installation chapter](https://icinga.com/docs/icingaweb2/latest/doc/02-Installation/)
+in the documentation.
+
+## Documentation
+
+The documentation is located in the [doc/](doc/) directory and also available
+on [icinga.com/docs](https://icinga.com/docs/icingaweb2/latest/).
+
+## Support
+
+Check the [project website](https://icinga.com) for status updates. Join the
+[community channels](https://icinga.com/community/) for questions
+or ask an Icinga partner for [professional support](https://icinga.com/support/).
+
+## Contributing
+
+There are many ways to contribute to Icinga -- whether it be sending patches,
+testing, reporting bugs, or reviewing and updating the documentation. Every
+contribution is appreciated!
+
+Please continue reading in the [contributing chapter](CONTRIBUTING.md).
+
+### Security Issues
+
+For reporting security issues please visit [this page](https://icinga.com/contact/security/).
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..a634791
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+v2.11.4
diff --git a/application/VERSION b/application/VERSION
new file mode 100644
index 0000000..cebeac5
--- /dev/null
+++ b/application/VERSION
@@ -0,0 +1 @@
+11453bfa92a70a44efbf7f966f5e7f27e9300a28 2023-01-26 12:54:15 +0100
diff --git a/application/clicommands/AutocompleteCommand.php b/application/clicommands/AutocompleteCommand.php
new file mode 100644
index 0000000..34e4005
--- /dev/null
+++ b/application/clicommands/AutocompleteCommand.php
@@ -0,0 +1,120 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Clicommands;
+
+use Icinga\Cli\Command;
+use Icinga\Cli\Loader;
+
+/**
+ * Autocomplete for modules, commands and actions
+ *
+ * The autocomplete command shows help for a given command, module and also for a
+ * given module's command or a specific command's action.
+ *
+ * Usage: icingacli autocomplete [<module>] [<command> [<action>]]
+ */
+class AutocompleteCommand extends Command
+{
+ protected $defaultActionName = 'complete';
+
+ protected function suggest($suggestions)
+ {
+ if ($suggestions) {
+ $key = array_search('autocomplete', $suggestions);
+ if ($key !== false) {
+ unset($suggestions[$key]);
+ }
+ echo implode("\n", $suggestions)
+ //. serialize($GLOBALS['argv'])
+ . "\n";
+ }
+ }
+
+ /**
+ * Show help for modules, commands and actions [default]
+ *
+ * The help command shows help for a given command, module and also for a
+ * given module's command or a specific command's action.
+ *
+ * Usage: icingacli autocomplete [<module>] [<command> [<action>]]
+ */
+ public function completeAction()
+ {
+ $module = null;
+ $command = null;
+ $action = null;
+
+ $loader = new Loader($this->app);
+ $params = $this->params;
+ $bare_params = $GLOBALS['argv'];
+ $cword = (int) $params->shift('autoindex');
+
+ $search_word = $bare_params[$cword];
+ if ($search_word === '--') {
+ // TODO: Unfinished, completion missing
+ return $this->suggest(array('--verbose', '--help', '--debug'));
+ }
+
+ $search = $params->shift();
+ if (!$search) {
+ return $this->suggest(
+ array_merge($loader->listCommands(), $loader->listModules())
+ );
+ }
+ $found = $loader->resolveName($search);
+ if ($found) {
+ // Do not return suggestions if we are already on the next word:
+ if ($bare_params[$cword] === $search) {
+ return $this->suggest(array($found));
+ }
+ } else {
+ return $this->suggest($loader->getLastSuggestions());
+ }
+
+ $obj = null;
+ if ($loader->hasCommand($found)) {
+ $command = $found;
+ $obj = $loader->getCommandInstance($command);
+ } elseif ($loader->hasModule($found)) {
+ $module = $found;
+ $search = $params->shift();
+ if (! $search) {
+ return $this->suggest(
+ $loader->listModuleCommands($module)
+ );
+ }
+ $command = $loader->resolveModuleCommandName($found, $search);
+ if ($command) {
+ // Do not return suggestions if we are already on the next word:
+ if ($bare_params[$cword] === $search) {
+ return $this->suggest(array($command));
+ }
+ $obj = $loader->getModuleCommandInstance(
+ $module,
+ $command
+ );
+ } else {
+ return $this->suggest($loader->getLastSuggestions());
+ }
+ }
+
+ if ($obj !== null) {
+ $search = $params->shift();
+ if (! $search) {
+ return $this->suggest($obj->listActions());
+ }
+ $action = $loader->resolveObjectActionName(
+ $obj,
+ $search
+ );
+ if ($action) {
+ if ($bare_params[$cword] === $search) {
+ return $this->suggest(array($action));
+ }
+ } else {
+ return $this->suggest($loader->getLastSuggestions());
+ }
+ }
+ }
+}
diff --git a/application/clicommands/HelpCommand.php b/application/clicommands/HelpCommand.php
new file mode 100644
index 0000000..a863eb4
--- /dev/null
+++ b/application/clicommands/HelpCommand.php
@@ -0,0 +1,43 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Clicommands;
+
+use Icinga\Cli\Command;
+use Icinga\Cli\Loader;
+use Icinga\Cli\Documentation;
+
+/**
+ * Help for modules, commands and actions
+ *
+ * The help command shows help for a given command, module and also for a
+ * given module's command or a specific command's action.
+ *
+ * Usage: icingacli help [<module>] [<command> [<action>]]
+ */
+class HelpCommand extends Command
+{
+ protected $defaultActionName = 'show';
+
+ /**
+ * Show help for modules, commands and actions [default]
+ *
+ * The help command shows help for a given command, module and also for a
+ * given module's command or a specific command's action.
+ *
+ * Usage: icingacli help [<module>] [<command> [<action>]]
+ */
+ public function showAction()
+ {
+ $module = null;
+ $command = null;
+ $action = null;
+ $loader = new Loader($this->app);
+ $loader->parseParams();
+ echo $this->docs()->usage(
+ $loader->getModuleName(),
+ $loader->getCommandName(),
+ $loader->getActionName()
+ );
+ }
+}
diff --git a/application/clicommands/ModuleCommand.php b/application/clicommands/ModuleCommand.php
new file mode 100644
index 0000000..fc42167
--- /dev/null
+++ b/application/clicommands/ModuleCommand.php
@@ -0,0 +1,228 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Clicommands;
+
+use Icinga\Application\Logger;
+use Icinga\Application\Modules\Manager;
+use Icinga\Cli\Command;
+
+/**
+ * List and handle modules
+ *
+ * The module command allows you to handle your IcingaWeb modules
+ *
+ * Usage: icingacli module [<action>] [<modulename>]
+ */
+class ModuleCommand extends Command
+{
+ /**
+ * @var Manager
+ */
+ protected $modules;
+
+ public function init()
+ {
+ $this->modules = $this->app->getModuleManager();
+ }
+
+ /**
+ * List all enabled modules
+ *
+ * If you are interested in all installed modules pass 'installed' (or
+ * even --installed) as a command parameter. If you enable --verbose even
+ * more details will be shown
+ *
+ * Usage: icingacli module list [installed] [--verbose]
+ */
+ public function listAction()
+ {
+ if ($type = $this->params->shift()) {
+ if (! in_array($type, array('enabled', 'installed'))) {
+ return $this->showUsage();
+ }
+ } else {
+ $type = 'enabled';
+ $this->params->shift('enabled');
+ if ($this->params->shift('installed')) {
+ $type = 'installed';
+ }
+ }
+
+ if ($this->hasRemainingParams()) {
+ return $this->showUsage();
+ }
+
+ if ($type === 'enabled') {
+ $modules = $this->modules->listEnabledModules();
+ } else {
+ $modules = $this->modules->listInstalledModules();
+ }
+ if (empty($modules)) {
+ echo "There are no $type modules\n";
+ return;
+ }
+ if ($this->isVerbose) {
+ printf("%-14s %-9s %-9s DIRECTORY\n", 'MODULE', 'VERSION', 'STATE');
+ } else {
+ printf("%-14s %-9s %-9s %s\n", 'MODULE', 'VERSION', 'STATE', 'DESCRIPTION');
+ }
+ foreach ($modules as $module) {
+ $mod = $this->modules->loadModule($module)->getModule($module);
+ if ($this->isVerbose) {
+ $dir = ' ' . $this->modules->getModuleDir($module);
+ } else {
+ $dir = $mod->getTitle();
+ }
+ printf(
+ "%-14s %-9s %-9s %s\n",
+ $module,
+ $mod->getVersion(),
+ ($type === 'enabled' || $this->modules->hasEnabled($module))
+ ? $this->modules->hasInstalled($module) ? 'enabled' : 'dangling'
+ : 'disabled',
+ $dir
+ );
+ }
+ echo "\n";
+ }
+
+ /**
+ * Enable a given module
+ *
+ * Usage: icingacli module enable <module-name>
+ */
+ public function enableAction()
+ {
+ if (! $module = $this->params->shift()) {
+ $module = $this->params->shift('module');
+ }
+
+ if (! $module || $this->hasRemainingParams()) {
+ return $this->showUsage();
+ }
+
+ $this->modules->enableModule($module, true);
+ }
+
+ /**
+ * Disable a given module
+ *
+ * Usage: icingacli module disable <module-name>
+ */
+ public function disableAction()
+ {
+ if (! $module = $this->params->shift()) {
+ $module = $this->params->shift('module');
+ }
+ if (! $module || $this->hasRemainingParams()) {
+ return $this->showUsage();
+ }
+
+ if ($this->modules->hasEnabled($module)) {
+ $this->modules->disableModule($module);
+ } else {
+ Logger::info('Module "%s" is already disabled', $module);
+ }
+ }
+
+ /**
+ * Show all restrictions provided by your modules
+ *
+ * Asks each enabled module for all available restriction names and
+ * descriptions and shows a quick overview
+ *
+ * Usage: icingacli module restrictions
+ */
+ public function restrictionsAction()
+ {
+ printf("%-14s %-16s %s\n", 'MODULE', 'RESTRICTION', 'DESCRIPTION');
+ foreach ($this->modules->listEnabledModules() as $moduleName) {
+ $module = $this->modules->loadModule($moduleName)->getModule($moduleName);
+ foreach ($module->getProvidedRestrictions() as $restriction) {
+ printf(
+ "%-14s %-16s %s\n",
+ $moduleName,
+ $restriction->name,
+ $restriction->description
+ );
+ }
+ }
+ }
+
+ /**
+ * Show all permissions provided by your modules
+ *
+ * Asks each enabled module for it's available permission names and
+ * descriptions and shows a quick overview
+ *
+ * Usage: icingacli module permissions
+ */
+ public function permissionsAction()
+ {
+ printf("%-14s %-24s %s\n", 'MODULE', 'PERMISSION', 'DESCRIPTION');
+ foreach ($this->modules->listEnabledModules() as $moduleName) {
+ $module = $this->modules->loadModule($moduleName)->getModule($moduleName);
+ foreach ($module->getProvidedPermissions() as $restriction) {
+ printf(
+ "%-14s %-24s %s\n",
+ $moduleName,
+ $restriction->name,
+ $restriction->description
+ );
+ }
+ }
+ }
+
+ /**
+ * Search for a given module
+ *
+ * Does a lookup against your configured IcingaWeb app stores and tries to
+ * find modules matching your search string
+ *
+ * Usage: icingacli module search <search-string>
+ */
+ public function searchAction()
+ {
+ $this->fail("Not implemented yet");
+ }
+
+ /**
+ * Install a given module
+ *
+ * Downloads a given module or installes a module from a given archive
+ *
+ * Usage: icingacli module install <module-name>
+ * icingacli module install </path/to/archive.tar.gz>
+ */
+ public function installAction()
+ {
+ $this->fail("Not implemented yet");
+ }
+
+ /**
+ * Remove a given module
+ *
+ * Removes the given module from your disk. Module configuration will be
+ * preserved
+ *
+ * Usage: icingacli module remove <module-name>
+ */
+ public function removeAction()
+ {
+ $this->fail("Not implemented yet");
+ }
+
+ /**
+ * Purge a given module
+ *
+ * Removes the given module from your disk. Also wipes configuration files
+ * and other data stored and/or generated by this module
+ *
+ * Usage: icingacli module remove <module-name>
+ */
+ public function purgeAction()
+ {
+ $this->fail("Not implemented yet");
+ }
+}
diff --git a/application/clicommands/VersionCommand.php b/application/clicommands/VersionCommand.php
new file mode 100644
index 0000000..13c62dd
--- /dev/null
+++ b/application/clicommands/VersionCommand.php
@@ -0,0 +1,55 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Clicommands;
+
+use Icinga\Application\Version;
+use Icinga\Application\Icinga;
+use Icinga\Cli\Loader;
+use Icinga\Cli\Command;
+
+/**
+ * Shows version of Icinga Web 2, loaded modules and PHP
+ *
+ * The version command shows version numbers for Icinga Web 2, loaded modules and PHP.
+ *
+ * Usage: icingacli --version
+ */
+class VersionCommand extends Command
+{
+ protected $defaultActionName = 'show';
+
+ /**
+ * Shows version of Icinga Web 2, loaded modules and PHP
+ *
+ * The version command shows version numbers for Icinga Web 2, loaded modules and PHP.
+ *
+ * Usage: icingacli --version
+ */
+ public function showAction()
+ {
+ $getVersion = Version::get();
+ printf("%-12s %-9s \n", 'Icinga Web 2', $getVersion['appVersion']);
+
+ if (isset($getVersion['gitCommitID'])) {
+ printf("%-12s %-9s \n", 'Git Commit', $getVersion['gitCommitID']);
+ }
+
+ printf("%-12s %-9s \n", 'PHP Version', PHP_VERSION);
+
+ $modules = Icinga::app()->getModuleManager()->loadEnabledModules()->getLoadedModules();
+
+ $maxLength = 0;
+ foreach ($modules as $module) {
+ $length = strlen($module->getName());
+ if ($length > $maxLength) {
+ $maxLength = $length;
+ }
+ }
+
+ printf("%-${maxLength}s %-9s \n", 'MODULE', 'VERSION');
+ foreach ($modules as $module) {
+ printf("%-${maxLength}s %-9s \n", $module->getName(), $module->getVersion());
+ }
+ }
+}
diff --git a/application/clicommands/WebCommand.php b/application/clicommands/WebCommand.php
new file mode 100644
index 0000000..67d50a3
--- /dev/null
+++ b/application/clicommands/WebCommand.php
@@ -0,0 +1,101 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Clicommands;
+
+use Icinga\Application\Icinga;
+use Icinga\Cli\Command;
+use Icinga\Exception\IcingaException;
+
+class WebCommand extends Command
+{
+ /**
+ * Serve Icinga Web 2 with PHP's built-in web server
+ *
+ * USAGE
+ *
+ * icingacli web serve [options] [<document-root>]
+ *
+ * OPTIONS
+ *
+ * --daemonize Run in background
+ * --port=<port> The port to listen on
+ * --listen=<host:port> The address to listen on
+ * <document-root> The document root directory of Icinga Web 2 (e.g. ./public)
+ *
+ * EXAMPLES
+ *
+ * icingacli web serve --port=8080
+ * icingacli web serve --listen=127.0.0.1:8080 ./public
+ */
+ public function serveAction()
+ {
+ $fork = $this->params->get('daemonize');
+ $listen = $this->params->get('listen');
+ $port = $this->params->get('port');
+ $documentRoot = $this->params->shift();
+ if ($listen === null) {
+ $socket = $port === null ? $this->params->shift() : '0.0.0.0:' . $port;
+ } else {
+ $socket = $listen;
+ }
+
+ if ($socket === null) {
+ $socket = $this->Config()->get('standalone', 'listen', '0.0.0.0:80');
+ }
+ if ($documentRoot === null) {
+ $documentRoot = Icinga::app()->getBaseDir('public');
+ if (! file_exists($documentRoot) || ! is_dir($documentRoot)) {
+ throw new IcingaException('Document root directory is required');
+ }
+ }
+ $documentRoot = realpath($documentRoot);
+
+ if ($fork) {
+ $this->forkAndExit();
+ }
+ echo "Serving Icinga Web 2 from directory $documentRoot and listening on $socket\n";
+
+ // TODO: Store webserver log, switch uid, log index.php includes, pid file
+ pcntl_exec(
+ readlink('/proc/self/exe'),
+ ['-S', $socket, '-t', $documentRoot, Icinga::app()->getLibraryDir('/Icinga/Application/webrouter.php')]
+ );
+ }
+
+ public function stopAction()
+ {
+ // TODO: No, that's NOT what we want
+ $prog = readlink('/proc/self/exe');
+ `killall $prog`;
+ }
+
+ protected function forkAndExit()
+ {
+ $pid = pcntl_fork();
+ if ($pid == -1) {
+ throw new IcingaException('Could not fork');
+ } elseif ($pid) {
+ echo $this->screen->colorize('[OK]')
+ . " Icinga Web server forked successfully\n";
+ fclose(STDIN);
+ fclose(STDOUT);
+ fclose(STDERR);
+ exit;
+ // pcntl_wait($status);
+ } else {
+ // child
+
+ // Replace console with /dev/null by first freeing the (lowest possible) FDs 0, 1 and 2
+ // and then opening /dev/null once for every one of them (open(2) chooses the lowest free FD).
+
+ fclose(STDIN);
+ fclose(STDOUT);
+ fclose(STDERR);
+
+ fopen('/dev/null', 'rb');
+ fopen('/dev/null', 'wb');
+ fopen('/dev/null', 'wb');
+ }
+ }
+}
diff --git a/application/controllers/AboutController.php b/application/controllers/AboutController.php
new file mode 100644
index 0000000..59e3c20
--- /dev/null
+++ b/application/controllers/AboutController.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Version;
+use Icinga\Web\Controller;
+
+class AboutController extends Controller
+{
+ public function indexAction()
+ {
+ $this->view->version = Version::get();
+ $this->view->libraries = Icinga::app()->getLibraries();
+ $this->view->modules = Icinga::app()->getModuleManager()->getLoadedModules();
+ $this->view->title = $this->translate('About');
+ $this->view->tabs = $this->getTabs()->add(
+ 'about',
+ array(
+ 'label' => $this->translate('About'),
+ 'title' => $this->translate('About Icinga Web 2'),
+ 'url' => 'about'
+ )
+ )->activate('about');
+ }
+}
diff --git a/application/controllers/AccountController.php b/application/controllers/AccountController.php
new file mode 100644
index 0000000..f172cfe
--- /dev/null
+++ b/application/controllers/AccountController.php
@@ -0,0 +1,83 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Application\Config;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Forms\Account\ChangePasswordForm;
+use Icinga\Forms\PreferenceForm;
+use Icinga\User\Preferences\PreferencesStore;
+use Icinga\Web\Controller;
+
+/**
+ * My Account
+ */
+class AccountController extends Controller
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->getTabs()
+ ->add('account', array(
+ 'title' => $this->translate('Update your account'),
+ 'label' => $this->translate('My Account'),
+ 'url' => 'account'
+ ))
+ ->add('navigation', array(
+ 'title' => $this->translate('List and configure your own navigation items'),
+ 'label' => $this->translate('Navigation'),
+ 'url' => 'navigation'
+ ))
+ ->add(
+ 'devices',
+ array(
+ 'title' => $this->translate('List of devices you are logged in'),
+ 'label' => $this->translate('My Devices'),
+ 'url' => 'my-devices'
+ )
+ );
+ }
+
+ /**
+ * My account
+ */
+ public function indexAction()
+ {
+ $config = Config::app()->getSection('global');
+ $user = $this->Auth()->getUser();
+ if ($user->getAdditional('backend_type') === 'db') {
+ if ($user->can('user/password-change')) {
+ try {
+ $userBackend = UserBackend::create($user->getAdditional('backend_name'));
+ } catch (ConfigurationError $e) {
+ $userBackend = null;
+ }
+ if ($userBackend !== null) {
+ $changePasswordForm = new ChangePasswordForm();
+ $changePasswordForm
+ ->setBackend($userBackend)
+ ->handleRequest();
+ $this->view->changePasswordForm = $changePasswordForm;
+ }
+ }
+ }
+
+ $form = new PreferenceForm();
+ $form->setPreferences($user->getPreferences());
+ if (isset($config->config_resource)) {
+ $form->setStore(PreferencesStore::create(new ConfigObject(array(
+ 'resource' => $config->config_resource
+ )), $user));
+ }
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('My Account');
+ $this->getTabs()->activate('account');
+ }
+}
diff --git a/application/controllers/AnnouncementsController.php b/application/controllers/AnnouncementsController.php
new file mode 100644
index 0000000..ee7fd4c
--- /dev/null
+++ b/application/controllers/AnnouncementsController.php
@@ -0,0 +1,123 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\Announcement\AcknowledgeAnnouncementForm;
+use Icinga\Forms\Announcement\AnnouncementForm;
+use Icinga\Web\Announcement\AnnouncementIniRepository;
+use Icinga\Web\Controller;
+use Icinga\Web\Url;
+
+class AnnouncementsController extends Controller
+{
+ public function init()
+ {
+ $this->view->title = $this->translate('Announcements');
+
+ parent::init();
+ }
+
+ /**
+ * List all announcements
+ */
+ public function indexAction()
+ {
+ $this->getTabs()->add(
+ 'announcements',
+ array(
+ 'active' => true,
+ 'label' => $this->translate('Announcements'),
+ 'title' => $this->translate('List All Announcements'),
+ 'url' => Url::fromPath('announcements')
+ )
+ );
+
+ $announcements = (new AnnouncementIniRepository())
+ ->select([
+ 'id',
+ 'author',
+ 'message',
+ 'start',
+ 'end'
+ ]);
+
+ $sortAndFilterColumns = [
+ 'author' => $this->translate('Author'),
+ 'message' => $this->translate('Message'),
+ 'start' => $this->translate('Start'),
+ 'end' => $this->translate('End')
+ ];
+
+ $this->setupSortControl($sortAndFilterColumns, $announcements, ['start' => 'desc']);
+ $this->setupFilterControl($announcements, $sortAndFilterColumns, ['message']);
+
+ $this->view->announcements = $announcements->fetchAll();
+ }
+
+ /**
+ * Create an announcement
+ */
+ public function newAction()
+ {
+ $this->assertPermission('application/announcements');
+
+ $form = $this->prepareForm()->add();
+ $form->handleRequest();
+ $this->renderForm($form, $this->translate('New Announcement'));
+ }
+
+ /**
+ * Update an announcement
+ */
+ public function updateAction()
+ {
+ $this->assertPermission('application/announcements');
+
+ $form = $this->prepareForm()->edit($this->params->getRequired('id'));
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound($this->translate('Announcement not found'));
+ }
+ $this->renderForm($form, $this->translate('Update Announcement'));
+ }
+
+ /**
+ * Remove an announcement
+ */
+ public function removeAction()
+ {
+ $this->assertPermission('application/announcements');
+
+ $form = $this->prepareForm()->remove($this->params->getRequired('id'));
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound($this->translate('Announcement not found'));
+ }
+ $this->renderForm($form, $this->translate('Remove Announcement'));
+ }
+
+ public function acknowledgeAction()
+ {
+ $this->assertHttpMethod('POST');
+ $this->getResponse()->setHeader('X-Icinga-Container', 'ignore', true);
+ $form = new AcknowledgeAnnouncementForm();
+ $form->handleRequest();
+ }
+
+ /**
+ * Assert permission admin and return a prepared RepositoryForm
+ *
+ * @return AnnouncementForm
+ */
+ protected function prepareForm()
+ {
+ $form = new AnnouncementForm();
+ return $form
+ ->setRepository(new AnnouncementIniRepository())
+ ->setRedirectUrl(Url::fromPath('announcements'));
+ }
+}
diff --git a/application/controllers/ApplicationStateController.php b/application/controllers/ApplicationStateController.php
new file mode 100644
index 0000000..b828ca2
--- /dev/null
+++ b/application/controllers/ApplicationStateController.php
@@ -0,0 +1,95 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Forms\AcknowledgeApplicationStateMessageForm;
+use Icinga\Web\Announcement\AnnouncementCookie;
+use Icinga\Web\Announcement\AnnouncementIniRepository;
+use Icinga\Web\Controller;
+use Icinga\Web\RememberMe;
+use Icinga\Web\Session;
+use Icinga\Web\Widget;
+
+/**
+ * @TODO(el): https://dev.icinga.com/issues/10646
+ */
+class ApplicationStateController extends Controller
+{
+ protected $requiresAuthentication = false;
+
+ protected $autorefreshInterval = 60;
+
+ public function init()
+ {
+ $this->_helper->layout->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+ }
+
+ public function indexAction()
+ {
+ if ($this->Auth()->isAuthenticated()) {
+ if (isset($_COOKIE['icingaweb2-session'])) {
+ $last = (int) $_COOKIE['icingaweb2-session'];
+ } else {
+ $last = 0;
+ }
+ $now = time();
+ if ($last + 600 < $now) {
+ Session::getSession()->write();
+ $params = session_get_cookie_params();
+ setcookie(
+ 'icingaweb2-session',
+ $now,
+ 0,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+ $_COOKIE['icingaweb2-session'] = $now;
+ }
+ $announcementCookie = new AnnouncementCookie();
+ $announcementRepo = new AnnouncementIniRepository();
+ if ($announcementCookie->getEtag() !== $announcementRepo->getEtag()) {
+ $announcementCookie
+ ->setEtag($announcementRepo->getEtag())
+ ->setNextActive($announcementRepo->findNextActive());
+ $this->getResponse()->setCookie($announcementCookie);
+ $this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
+ } else {
+ $nextActive = $announcementCookie->getNextActive();
+ if ($nextActive && $nextActive <= $now) {
+ $announcementCookie->setNextActive($announcementRepo->findNextActive());
+ $this->getResponse()->setCookie($announcementCookie);
+ $this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
+ }
+ }
+ }
+
+ RememberMe::removeExpired();
+ }
+
+ public function summaryAction()
+ {
+ if ($this->Auth()->isAuthenticated()) {
+ $this->getResponse()->setBody((string) Widget::create('ApplicationStateMessages'));
+ }
+ }
+
+ public function acknowledgeMessageAction()
+ {
+ if (! $this->Auth()->isAuthenticated()) {
+ $this->getResponse()
+ ->setHttpResponseCode(401)
+ ->sendHeaders();
+ exit;
+ }
+
+ $this->assertHttpMethod('POST');
+
+ $this->getResponse()->setHeader('X-Icinga-Container', 'ignore', true);
+
+ (new AcknowledgeApplicationStateMessageForm())->handleRequest();
+ }
+}
diff --git a/application/controllers/AuthenticationController.php b/application/controllers/AuthenticationController.php
new file mode 100644
index 0000000..4254433
--- /dev/null
+++ b/application/controllers/AuthenticationController.php
@@ -0,0 +1,127 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Application\Hook\AuthenticationHook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Common\Database;
+use Icinga\Exception\AuthenticationException;
+use Icinga\Forms\Authentication\LoginForm;
+use Icinga\Web\Controller;
+use Icinga\Web\Helper\CookieHelper;
+use Icinga\Web\RememberMe;
+use Icinga\Web\Url;
+use RuntimeException;
+
+/**
+ * Application wide controller for authentication
+ */
+class AuthenticationController extends Controller
+{
+ use Database;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $requiresAuthentication = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $innerLayout = 'inline';
+
+ /**
+ * Log into the application
+ */
+ public function loginAction()
+ {
+ $icinga = Icinga::app();
+ if (($requiresSetup = $icinga->requiresSetup()) && $icinga->setupTokenExists()) {
+ $this->redirectNow(Url::fromPath('setup'));
+ }
+ $form = new LoginForm();
+
+ if (RememberMe::hasCookie() && $this->hasDb()) {
+ $authenticated = false;
+ try {
+ $rememberMeOld = RememberMe::fromCookie();
+ $authenticated = $rememberMeOld->authenticate();
+ if ($authenticated) {
+ $rememberMe = $rememberMeOld->renew();
+ $this->getResponse()->setCookie($rememberMe->getCookie());
+ $rememberMe->persist($rememberMeOld->getAesCrypt()->getIv());
+ }
+ } catch (RuntimeException $e) {
+ Logger::error("Can't authenticate user via remember me cookie: %s", $e->getMessage());
+ } catch (AuthenticationException $e) {
+ Logger::error($e);
+ }
+
+ if (! $authenticated) {
+ $this->getResponse()->setCookie(RememberMe::forget());
+ }
+ }
+
+ if ($this->Auth()->isAuthenticated()) {
+ // Call provided AuthenticationHook(s) when login action is called
+ // but icinga web user is already authenticated
+ AuthenticationHook::triggerLogin($this->Auth()->getUser());
+
+ $redirect = $this->params->get('redirect');
+ if ($redirect) {
+ $redirectUrl = Url::fromPath($redirect, [], $this->getRequest());
+ if ($redirectUrl->isExternal()) {
+ $this->httpBadRequest('nope');
+ }
+ } else {
+ $redirectUrl = $form->getRedirectUrl();
+ }
+
+ $this->redirectNow($redirectUrl);
+ }
+ if (! $requiresSetup) {
+ $cookies = new CookieHelper($this->getRequest());
+ if (! $cookies->isSupported()) {
+ $this
+ ->getResponse()
+ ->setBody("Cookies must be enabled to run this application.\n")
+ ->setHttpResponseCode(403)
+ ->sendResponse();
+ exit;
+ }
+ $form->handleRequest();
+ }
+ $this->view->form = $form;
+ $this->view->defaultTitle = $this->translate('Icinga Web 2 Login');
+ $this->view->requiresSetup = $requiresSetup;
+ }
+
+ /**
+ * Log out the current user
+ */
+ public function logoutAction()
+ {
+ $auth = $this->Auth();
+ if (! $auth->isAuthenticated()) {
+ $this->redirectToLogin();
+ }
+ // Get info whether the user is externally authenticated before removing authorization which destroys the
+ // session and the user object
+ $isExternalUser = $auth->getUser()->isExternalUser();
+ // Call provided AuthenticationHook(s) when logout action is called
+ AuthenticationHook::triggerLogout($auth->getUser());
+ $auth->removeAuthorization();
+ if ($isExternalUser) {
+ $this->view->layout()->setLayout('external-logout');
+ $this->getResponse()->setHttpResponseCode(401);
+ } else {
+ if (RememberMe::hasCookie() && $this->hasDb()) {
+ $this->getResponse()->setCookie(RememberMe::forget());
+ }
+
+ $this->redirectToLogin();
+ }
+ }
+}
diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php
new file mode 100644
index 0000000..30b92ec
--- /dev/null
+++ b/application/controllers/ConfigController.php
@@ -0,0 +1,507 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use Icinga\Application\Version;
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Module;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ActionForm;
+use Icinga\Forms\Config\GeneralConfigForm;
+use Icinga\Forms\Config\ResourceConfigForm;
+use Icinga\Forms\Config\UserBackendConfigForm;
+use Icinga\Forms\Config\UserBackendReorderForm;
+use Icinga\Forms\ConfirmRemovalForm;
+use Icinga\Security\SecurityException;
+use Icinga\Web\Controller;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+use Icinga\Web\Widget;
+
+/**
+ * Application and module configuration
+ */
+class ConfigController extends Controller
+{
+ /**
+ * Create and return the tabs to display when showing application configuration
+ */
+ public function createApplicationTabs()
+ {
+ $tabs = $this->getTabs();
+ if ($this->hasPermission('config/general')) {
+ $tabs->add('general', array(
+ 'title' => $this->translate('Adjust the general configuration of Icinga Web 2'),
+ 'label' => $this->translate('General'),
+ 'url' => 'config/general',
+ 'baseTarget' => '_main'
+ ));
+ }
+ if ($this->hasPermission('config/resources')) {
+ $tabs->add('resource', array(
+ 'title' => $this->translate('Configure which resources are being utilized by Icinga Web 2'),
+ 'label' => $this->translate('Resources'),
+ 'url' => 'config/resource',
+ 'baseTarget' => '_main'
+ ));
+ }
+ if ($this->hasPermission('config/access-control/users')
+ || $this->hasPermission('config/access-control/groups')
+ ) {
+ $tabs->add('authentication', array(
+ 'title' => $this->translate('Configure the user and group backends'),
+ 'label' => $this->translate('Access Control Backends'),
+ 'url' => 'config/userbackend',
+ 'baseTarget' => '_main'
+ ));
+ }
+
+ return $tabs;
+ }
+
+ public function devtoolsAction()
+ {
+ $this->view->tabs = null;
+ }
+
+ /**
+ * Redirect to the general configuration
+ */
+ public function indexAction()
+ {
+ if ($this->hasPermission('config/general')) {
+ $this->redirectNow('config/general');
+ } elseif ($this->hasPermission('config/resources')) {
+ $this->redirectNow('config/resource');
+ } elseif ($this->hasPermission('config/access-control/*')) {
+ $this->redirectNow('config/userbackend');
+ } else {
+ throw new SecurityException('No permission to configure Icinga Web 2');
+ }
+ }
+
+ /**
+ * General configuration
+ *
+ * @throws SecurityException If the user lacks the permission for configuring the general configuration
+ */
+ public function generalAction()
+ {
+ $this->assertPermission('config/general');
+ $form = new GeneralConfigForm();
+ $form->setIniConfig(Config::app());
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('General');
+ $this->createApplicationTabs()->activate('general');
+ }
+
+ /**
+ * Display the list of all modules
+ */
+ public function modulesAction()
+ {
+ $this->assertPermission('config/modules');
+ // Overwrite tabs created in init
+ // @TODO(el): This seems not natural to me. Module configuration should have its own controller.
+ $this->view->tabs = Widget::create('tabs')
+ ->add('modules', array(
+ 'label' => $this->translate('Modules'),
+ 'title' => $this->translate('List intalled modules'),
+ 'url' => 'config/modules'
+ ))
+ ->activate('modules');
+ $this->view->modules = Icinga::app()->getModuleManager()->select()
+ ->from('modules')
+ ->order('enabled', 'desc')
+ ->order('installed', 'asc')
+ ->order('name');
+ $this->setupLimitControl();
+ $this->setupPaginationControl($this->view->modules);
+ $this->view->title = $this->translate('Modules');
+ }
+
+ public function moduleAction()
+ {
+ $this->assertPermission('config/modules');
+ $app = Icinga::app();
+ $manager = $app->getModuleManager();
+ $name = $this->getParam('name');
+ if ($manager->hasInstalled($name) || $manager->hasEnabled($name)) {
+ $this->view->moduleData = $manager->select()->from('modules')->where('name', $name)->fetchRow();
+ if ($manager->hasLoaded($name)) {
+ $module = $manager->getModule($name);
+ } else {
+ $module = new Module($app, $name, $manager->getModuleDir($name));
+ }
+
+ $toggleForm = new ActionForm();
+ $toggleForm->setDefaults(['identifier' => $name]);
+ if (! $this->view->moduleData->enabled) {
+ $toggleForm->setAction(Url::fromPath('config/moduleenable'));
+ $toggleForm->setDescription(sprintf($this->translate('Enable the %s module'), $name));
+ } elseif ($this->view->moduleData->loaded) {
+ $toggleForm->setAction(Url::fromPath('config/moduledisable'));
+ $toggleForm->setDescription(sprintf($this->translate('Disable the %s module'), $name));
+ } else {
+ $toggleForm = null;
+ }
+
+ $this->view->module = $module;
+ $this->view->libraries = $app->getLibraries();
+ $this->view->moduleManager = $manager;
+ $this->view->toggleForm = $toggleForm;
+ $this->view->title = $module->getName();
+ $this->view->tabs = $module->getConfigTabs()->activate('info');
+ $this->view->moduleGitCommitId = Version::getGitHead($module->getBaseDir());
+ } else {
+ $this->view->module = false;
+ $this->view->tabs = null;
+ }
+ }
+
+ /**
+ * Enable a specific module provided by the 'name' param
+ */
+ public function moduleenableAction()
+ {
+ $this->assertPermission('config/modules');
+
+ $form = new ActionForm();
+ $form->setOnSuccess(function (ActionForm $form) {
+ $moduleName = $form->getValue('identifier');
+ $module = Icinga::app()->getModuleManager()
+ ->enableModule($moduleName)
+ ->getModule($moduleName);
+ Notification::success(sprintf($this->translate('Module "%s" enabled'), $moduleName));
+ $form->onSuccess();
+
+ if ($module->hasJs()) {
+ $this->getResponse()
+ ->setReloadWindow(true)
+ ->sendResponse();
+ } else {
+ if ($module->hasCss()) {
+ $this->reloadCss();
+ }
+
+ $this->rerenderLayout()->redirectNow('config/modules');
+ }
+ });
+
+ try {
+ $form->handleRequest();
+ } catch (Exception $e) {
+ $this->view->exceptionMessage = $e->getMessage();
+ $this->view->moduleName = $form->getValue('identifier');
+ $this->view->action = 'enable';
+ $this->render('module-configuration-error');
+ }
+ }
+
+ /**
+ * Disable a module specific module provided by the 'name' param
+ */
+ public function moduledisableAction()
+ {
+ $this->assertPermission('config/modules');
+
+ $form = new ActionForm();
+ $form->setOnSuccess(function (ActionForm $form) {
+ $mm = Icinga::app()->getModuleManager();
+ $moduleName = $form->getValue('identifier');
+ $module = $mm->getModule($moduleName);
+ $mm->disableModule($moduleName);
+ Notification::success(sprintf($this->translate('Module "%s" disabled'), $moduleName));
+ $form->onSuccess();
+
+ if ($module->hasJs()) {
+ $this->getResponse()
+ ->setReloadWindow(true)
+ ->sendResponse();
+ } else {
+ if ($module->hasCss()) {
+ $this->reloadCss();
+ }
+
+ $this->rerenderLayout()->redirectNow('config/modules');
+ }
+ });
+
+ try {
+ $form->handleRequest();
+ } catch (Exception $e) {
+ $this->view->exceptionMessage = $e->getMessage();
+ $this->view->moduleName = $form->getValue('identifier');
+ $this->view->action = 'disable';
+ $this->render('module-configuration-error');
+ }
+ }
+
+ /**
+ * Action for listing user and group backends
+ */
+ public function userbackendAction()
+ {
+ if ($this->hasPermission('config/access-control/users')) {
+ $form = new UserBackendReorderForm();
+ $form->setIniConfig(Config::app('authentication'));
+ $form->handleRequest();
+ $this->view->form = $form;
+ }
+
+ if ($this->hasPermission('config/access-control/groups')) {
+ $this->view->backendNames = Config::app('groups');
+ }
+
+ $this->createApplicationTabs()->activate('authentication');
+ $this->view->title = $this->translate('Authentication');
+ $this->render('userbackend/reorder');
+ }
+
+ /**
+ * Create a new user backend
+ */
+ public function createuserbackendAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $form = new UserBackendConfigForm();
+ $form
+ ->setRedirectUrl('config/userbackend')
+ ->addDescription($this->translate(
+ 'Create a new backend for authenticating your users. This backend'
+ . ' will be added at the end of your authentication order.'
+ ))
+ ->setIniConfig(Config::app('authentication'));
+
+ try {
+ $form->setResourceConfig(ResourceFactory::getResourceConfigs());
+ } catch (ConfigurationError $e) {
+ if ($this->hasPermission('config/resources')) {
+ Notification::error($e->getMessage());
+ $this->redirectNow('config/createresource');
+ }
+
+ throw $e; // No permission for resource configuration, show the error
+ }
+
+ $form->setOnSuccess(function (UserBackendConfigForm $form) {
+ try {
+ $form->add($form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(t('User backend successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->title = $this->translate('Authentication');
+ $this->renderForm($form, $this->translate('New User Backend'));
+ }
+
+ /**
+ * Edit a user backend
+ */
+ public function edituserbackendAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $backendName = $this->params->getRequired('backend');
+
+ $form = new UserBackendConfigForm();
+ $form->setRedirectUrl('config/userbackend');
+ $form->setIniConfig(Config::app('authentication'));
+ $form->setOnSuccess(function (UserBackendConfigForm $form) use ($backendName) {
+ try {
+ $form->edit($backendName, $form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(sprintf(t('User backend "%s" successfully updated'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($backendName);
+ $form->setResourceConfig(ResourceFactory::getResourceConfigs());
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('User backend "%s" not found'), $backendName));
+ }
+
+ $this->view->title = $this->translate('Authentication');
+ $this->renderForm($form, $this->translate('Update User Backend'));
+ }
+
+ /**
+ * Display a confirmation form to remove the backend identified by the 'backend' parameter
+ */
+ public function removeuserbackendAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $backendName = $this->params->getRequired('backend');
+
+ $backendForm = new UserBackendConfigForm();
+ $backendForm->setIniConfig(Config::app('authentication'));
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('config/userbackend');
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) {
+ try {
+ $backendForm->delete($backendName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($backendForm->save()) {
+ Notification::success(sprintf(t('User backend "%s" successfully removed'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->title = $this->translate('Authentication');
+ $this->renderForm($form, $this->translate('Remove User Backend'));
+ }
+
+ /**
+ * Display all available resources and a link to create a new one and to remove existing ones
+ */
+ public function resourceAction()
+ {
+ $this->assertPermission('config/resources');
+ $this->view->resources = Config::app('resources', true)->getConfigObject()
+ ->setKeyColumn('name')
+ ->select()
+ ->order('name');
+ $this->view->title = $this->translate('Resources');
+ $this->createApplicationTabs()->activate('resource');
+ }
+
+ /**
+ * Display a form to create a new resource
+ */
+ public function createresourceAction()
+ {
+ $this->assertPermission('config/resources');
+ $this->getTabs()->add('resources/new', array(
+ 'label' => $this->translate('New Resource'),
+ 'url' => Url::fromRequest()
+ ))->activate('resources/new');
+ $form = new ResourceConfigForm();
+ $form->addDescription($this->translate('Resources are entities that provide data to Icinga Web 2.'));
+ $form->setIniConfig(Config::app('resources'));
+ $form->setRedirectUrl('config/resource');
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('Resources');
+ $this->render('resource/create');
+ }
+
+ /**
+ * Display a form to edit a existing resource
+ */
+ public function editresourceAction()
+ {
+ $this->assertPermission('config/resources');
+ $this->getTabs()->add('resources/update', array(
+ 'label' => $this->translate('Update Resource'),
+ 'url' => Url::fromRequest()
+ ))->activate('resources/update');
+ $form = new ResourceConfigForm();
+ $form->setIniConfig(Config::app('resources'));
+ $form->setRedirectUrl('config/resource');
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('Resources');
+ $this->render('resource/modify');
+ }
+
+ /**
+ * Display a confirmation form to remove a resource
+ */
+ public function removeresourceAction()
+ {
+ $this->assertPermission('config/resources');
+ $this->getTabs()->add('resources/remove', array(
+ 'label' => $this->translate('Remove Resource'),
+ 'url' => Url::fromRequest()
+ ))->activate('resources/remove');
+ $form = new ConfirmRemovalForm(array(
+ 'onSuccess' => function ($form) {
+ $configForm = new ResourceConfigForm();
+ $configForm->setIniConfig(Config::app('resources'));
+ $resource = $form->getRequest()->getQuery('resource');
+
+ try {
+ $configForm->remove($resource);
+ } catch (InvalidArgumentException $e) {
+ Notification::error($e->getMessage());
+ return false;
+ }
+
+ if ($configForm->save()) {
+ Notification::success(sprintf(t('Resource "%s" has been successfully removed'), $resource));
+ } else {
+ return false;
+ }
+ }
+ ));
+ $form->setRedirectUrl('config/resource');
+ $form->handleRequest();
+
+ // Check if selected resource is currently used for authentication
+ $resource = $this->getRequest()->getQuery('resource');
+ $authConfig = Config::app('authentication');
+ foreach ($authConfig as $backendName => $config) {
+ if ($config->get('resource') === $resource) {
+ $form->warning(sprintf(
+ $this->translate(
+ 'The resource "%s" is currently utilized for authentication by user backend "%s".'
+ . ' Removing the resource can result in noone being able to log in any longer.'
+ ),
+ $resource,
+ $backendName
+ ));
+ }
+ }
+
+ // Check if selected resource is currently used as user preferences backend
+ if (Config::app()->get('global', 'config_resource') === $resource) {
+ $form->warning(sprintf(
+ $this->translate(
+ 'The resource "%s" is currently utilized to store user preferences. Removing the'
+ . ' resource causes all current user preferences not being available any longer.'
+ ),
+ $resource
+ ));
+ }
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('Resources');
+ $this->render('resource/remove');
+ }
+}
diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php
new file mode 100644
index 0000000..ff2580c
--- /dev/null
+++ b/application/controllers/DashboardController.php
@@ -0,0 +1,346 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use Zend_Controller_Action_Exception;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\Http\HttpNotFoundException;
+use Icinga\Forms\ConfirmRemovalForm;
+use Icinga\Forms\Dashboard\DashletForm;
+use Icinga\Web\Controller\ActionController;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Dashboard;
+use Icinga\Web\Widget\Tabextension\DashboardSettings;
+
+/**
+ * Handle creation, removal and displaying of dashboards, panes and dashlets
+ *
+ * @see Icinga\Web\Widget\Dashboard for more information about dashboards
+ */
+class DashboardController extends ActionController
+{
+ /**
+ * @var Dashboard;
+ */
+ private $dashboard;
+
+ public function init()
+ {
+ $this->dashboard = new Dashboard();
+ $this->dashboard->setUser($this->Auth()->getUser());
+ $this->dashboard->load();
+ }
+
+ public function newDashletAction()
+ {
+ $form = new DashletForm();
+ $this->getTabs()->add('new-dashlet', array(
+ 'active' => true,
+ 'label' => $this->translate('New Dashlet'),
+ 'url' => Url::fromRequest()
+ ));
+ $dashboard = $this->dashboard;
+ $form->setDashboard($dashboard);
+ if ($this->_request->getParam('url')) {
+ $params = $this->_request->getParams();
+ $params['url'] = rawurldecode($this->_request->getParam('url'));
+ $form->populate($params);
+ }
+ $action = $this;
+ $form->setOnSuccess(function (Form $form) use ($dashboard, $action) {
+ try {
+ $pane = $dashboard->getPane($form->getValue('pane'));
+ } catch (ProgrammingError $e) {
+ $pane = new Dashboard\Pane($form->getValue('pane'));
+ $pane->setUserWidget();
+ $dashboard->addPane($pane);
+ }
+ $dashlet = new Dashboard\Dashlet($form->getValue('dashlet'), $form->getValue('url'), $pane);
+ $dashlet->setUserWidget();
+ $pane->addDashlet($dashlet);
+ $dashboardConfig = $dashboard->getConfig();
+ try {
+ $dashboardConfig->saveIni();
+ } catch (Exception $e) {
+ $action->view->error = $e;
+ $action->view->config = $dashboardConfig;
+ $action->render('error');
+ return false;
+ }
+ Notification::success(t('Dashlet created'));
+ return true;
+ });
+ $form->setTitle($this->translate('Add Dashlet To Dashboard'));
+ $form->setRedirectUrl('dashboard');
+ $form->handleRequest();
+ $this->view->form = $form;
+ }
+
+ public function updateDashletAction()
+ {
+ $this->getTabs()->add('update-dashlet', array(
+ 'active' => true,
+ 'label' => $this->translate('Update Dashlet'),
+ 'url' => Url::fromRequest()
+ ));
+ $dashboard = $this->dashboard;
+ $form = new DashletForm();
+ $form->setDashboard($dashboard);
+ $form->setSubmitLabel($this->translate('Update Dashlet'));
+ if (! $this->_request->getParam('pane')) {
+ throw new Zend_Controller_Action_Exception(
+ 'Missing parameter "pane"',
+ 400
+ );
+ }
+ if (! $this->_request->getParam('dashlet')) {
+ throw new Zend_Controller_Action_Exception(
+ 'Missing parameter "dashlet"',
+ 400
+ );
+ }
+ $action = $this;
+ $form->setOnSuccess(function (Form $form) use ($dashboard, $action) {
+ try {
+ $pane = $dashboard->getPane($form->getValue('org_pane'));
+ $pane->setTitle($form->getValue('pane'));
+ } catch (ProgrammingError $e) {
+ $pane = new Dashboard\Pane($form->getValue('pane'));
+ $pane->setUserWidget();
+ $dashboard->addPane($pane);
+ }
+ try {
+ $dashlet = $pane->getDashlet($form->getValue('org_dashlet'));
+ $dashlet->setTitle($form->getValue('dashlet'));
+ $dashlet->setUrl($form->getValue('url'));
+ } catch (ProgrammingError $e) {
+ $dashlet = new Dashboard\Dashlet($form->getValue('dashlet'), $form->getValue('url'), $pane);
+ $pane->addDashlet($dashlet);
+ }
+ $dashlet->setUserWidget();
+ $dashboardConfig = $dashboard->getConfig();
+ try {
+ $dashboardConfig->saveIni();
+ } catch (Exception $e) {
+ $action->view->error = $e;
+ $action->view->config = $dashboardConfig;
+ $action->render('error');
+ return false;
+ }
+ Notification::success(t('Dashlet updated'));
+ return true;
+ });
+ $form->setTitle($this->translate('Edit Dashlet'));
+ $form->setRedirectUrl('dashboard/settings');
+ $form->handleRequest();
+ $pane = $dashboard->getPane($this->getParam('pane'));
+ $dashlet = $pane->getDashlet($this->getParam('dashlet'));
+ $form->load($dashlet);
+
+ $this->view->form = $form;
+ }
+
+ public function removeDashletAction()
+ {
+ $form = new ConfirmRemovalForm();
+ $this->getTabs()->add('remove-dashlet', array(
+ 'active' => true,
+ 'label' => $this->translate('Remove Dashlet'),
+ 'url' => Url::fromRequest()
+ ));
+ $dashboard = $this->dashboard;
+ if (! $this->_request->getParam('pane')) {
+ throw new Zend_Controller_Action_Exception(
+ 'Missing parameter "pane"',
+ 400
+ );
+ }
+ if (! $this->_request->getParam('dashlet')) {
+ throw new Zend_Controller_Action_Exception(
+ 'Missing parameter "dashlet"',
+ 400
+ );
+ }
+ $pane = $this->_request->getParam('pane');
+ $dashlet = $this->_request->getParam('dashlet');
+ $action = $this;
+ $form->setOnSuccess(function (Form $form) use ($dashboard, $dashlet, $pane, $action) {
+ $pane = $dashboard->getPane($pane);
+ $pane->removeDashlet($dashlet);
+ $dashboardConfig = $dashboard->getConfig();
+ try {
+ $dashboardConfig->saveIni();
+ Notification::success(t('Dashlet has been removed from') . ' ' . $pane->getTitle());
+ } catch (Exception $e) {
+ $action->view->error = $e;
+ $action->view->config = $dashboardConfig;
+ $action->render('error');
+ return false;
+ }
+ return true;
+ });
+ $form->setTitle($this->translate('Remove Dashlet From Dashboard'));
+ $form->setRedirectUrl('dashboard/settings');
+ $form->handleRequest();
+ $this->view->pane = $pane;
+ $this->view->dashlet = $dashlet;
+ $this->view->form = $form;
+ }
+
+ public function renamePaneAction()
+ {
+ $paneName = $this->params->getRequired('pane');
+ if (! $this->dashboard->hasPane($paneName)) {
+ throw new HttpNotFoundException('Pane not found');
+ }
+
+ $form = new Form();
+ $form->setRedirectUrl('dashboard/settings');
+ $form->setSubmitLabel($this->translate('Update Pane'));
+ $form->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Name')
+ )
+ );
+ $form->addElement(
+ 'text',
+ 'title',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Title')
+ )
+ );
+ $form->setDefaults(array(
+ 'name' => $paneName,
+ 'title' => $this->dashboard->getPane($paneName)->getTitle()
+ ));
+ $form->setOnSuccess(function ($form) use ($paneName) {
+ $newName = $form->getValue('name');
+ $newTitle = $form->getValue('title');
+
+ $pane = $this->dashboard->getPane($paneName);
+ $pane->setName($newName);
+ $pane->setTitle($newTitle);
+ $this->dashboard->getConfig()->saveIni();
+
+ Notification::success(
+ sprintf($this->translate('Pane "%s" successfully renamed to "%s"'), $paneName, $newName)
+ );
+ });
+
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->getTabs()->add(
+ 'update-pane',
+ array(
+ 'title' => $this->translate('Update Pane'),
+ 'url' => $this->getRequest()->getUrl()
+ )
+ )->activate('update-pane');
+ }
+
+ public function removePaneAction()
+ {
+ $form = new ConfirmRemovalForm();
+ $this->createTabs();
+ $dashboard = $this->dashboard;
+ if (! $this->_request->getParam('pane')) {
+ throw new Zend_Controller_Action_Exception(
+ 'Missing parameter "pane"',
+ 400
+ );
+ }
+ $pane = $this->_request->getParam('pane');
+ $action = $this;
+ $form->setOnSuccess(function (Form $form) use ($dashboard, $pane, $action) {
+ $pane = $dashboard->getPane($pane);
+ $dashboard->removePane($pane->getName());
+ $dashboardConfig = $dashboard->getConfig();
+ try {
+ $dashboardConfig->saveIni();
+ Notification::success(t('Dashboard has been removed') . ': ' . $pane->getTitle());
+ } catch (Exception $e) {
+ $action->view->error = $e;
+ $action->view->config = $dashboardConfig;
+ $action->render('error');
+ return false;
+ }
+ return true;
+ });
+ $form->setTitle($this->translate('Remove Dashboard'));
+ $form->setRedirectUrl('dashboard/settings');
+ $form->handleRequest();
+ $this->view->pane = $pane;
+ $this->view->form = $form;
+ }
+
+ /**
+ * Display the dashboard with the pane set in the 'pane' request parameter
+ *
+ * If no pane is submitted or the submitted one doesn't exist, the default pane is
+ * displayed (normally the first one)
+ */
+ public function indexAction()
+ {
+ $this->createTabs();
+ if (! $this->dashboard->hasPanes()) {
+ $this->view->title = 'Dashboard';
+ } else {
+ $panes = array_filter(
+ $this->dashboard->getPanes(),
+ function ($pane) {
+ return ! $pane->getDisabled();
+ }
+ );
+ if (empty($panes)) {
+ $this->view->title = 'Dashboard';
+ $this->getTabs()->add('dashboard', array(
+ 'active' => true,
+ 'title' => $this->translate('Dashboard'),
+ 'url' => Url::fromRequest()
+ ));
+ } else {
+ if ($this->_getParam('pane')) {
+ $pane = $this->_getParam('pane');
+ $this->dashboard->activate($pane);
+ }
+ if ($this->dashboard === null) {
+ $this->view->title = 'Dashboard';
+ } else {
+ $this->view->title = $this->dashboard->getActivePane()->getTitle() . ' :: Dashboard';
+ if ($this->hasParam('remove')) {
+ $this->dashboard->getActivePane()->removeDashlet($this->getParam('remove'));
+ $this->dashboard->getConfig()->saveIni();
+ $this->redirectNow(URL::fromRequest()->remove('remove'));
+ }
+ $this->view->dashboard = $this->dashboard;
+ }
+ }
+ }
+ }
+
+ /**
+ * Setting dialog
+ */
+ public function settingsAction()
+ {
+ $this->createTabs();
+ $this->view->dashboard = $this->dashboard;
+ }
+
+ /**
+ * Create tab aggregation
+ */
+ private function createTabs()
+ {
+ $this->view->tabs = $this->dashboard->getTabs()->extend(new DashboardSettings());
+ }
+}
diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php
new file mode 100644
index 0000000..580015b
--- /dev/null
+++ b/application/controllers/ErrorController.php
@@ -0,0 +1,158 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Exception\IcingaException;
+use Zend_Controller_Plugin_ErrorHandler;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Exception\Http\HttpExceptionInterface;
+use Icinga\Exception\MissingParameterException;
+use Icinga\Security\SecurityException;
+use Icinga\Web\Controller\ActionController;
+use Icinga\Web\Url;
+
+/**
+ * Application wide controller for displaying exceptions
+ */
+class ErrorController extends ActionController
+{
+ /**
+ * Regular expression to match exceptions resulting from missing functions/classes
+ */
+ const MISSING_DEP_ERROR =
+ "/Uncaught Error:.*(?:undefined function (\S+)|Class ['\"]([^']+)['\"] not found).* in ([^:]+)/";
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $requiresAuthentication = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->rerenderLayout = $this->params->has('renderLayout');
+ }
+
+ /**
+ * Display exception
+ */
+ public function errorAction()
+ {
+ $error = $this->_getParam('error_handler');
+ $exception = $error->exception;
+ /** @var \Exception $exception */
+
+ if (! ($isAuthenticated = $this->Auth()->isAuthenticated())) {
+ $this->innerLayout = 'guest-error';
+ }
+
+ $modules = Icinga::app()->getModuleManager();
+ $sourcePath = ltrim($this->_request->get('PATH_INFO'), '/');
+ $pathParts = preg_split('~/~', $sourcePath);
+ $moduleName = array_shift($pathParts);
+
+ $module = null;
+ switch ($error->type) {
+ case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
+ case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
+ case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
+ $this->getResponse()->setHttpResponseCode(404);
+ $this->view->messages = array($this->translate('Page not found.'));
+ if ($isAuthenticated) {
+ if ($modules->hasInstalled($moduleName) && ! $modules->hasEnabled($moduleName)) {
+ $this->view->messages[0] .= ' ' . sprintf(
+ $this->translate('Enabling the "%s" module might help!'),
+ $moduleName
+ );
+ }
+ }
+
+ break;
+ default:
+ switch (true) {
+ case $exception instanceof HttpExceptionInterface:
+ $this->getResponse()->setHttpResponseCode($exception->getStatusCode());
+ foreach ($exception->getHeaders() as $name => $value) {
+ $this->getResponse()->setHeader($name, $value, true);
+ }
+ break;
+ case $exception instanceof MissingParameterException:
+ $this->getResponse()->setHttpResponseCode(400);
+ $this->getResponse()->setHeader(
+ 'X-Status-Reason',
+ 'Missing parameter ' . $exception->getParameter()
+ );
+ break;
+ case $exception instanceof SecurityException:
+ $this->getResponse()->setHttpResponseCode(403);
+ break;
+ default:
+ $this->getResponse()->setHttpResponseCode(500);
+ $module = $modules->hasLoaded($moduleName) ? $modules->getModule($moduleName) : null;
+ Logger::error("%s\n%s", $exception, IcingaException::getConfidentialTraceAsString($exception));
+ break;
+ }
+
+ // Try to narrow down why the request has failed
+ if (preg_match(self::MISSING_DEP_ERROR, $exception->getMessage(), $match)) {
+ $sourcePath = $match[3];
+ foreach ($modules->listLoadedModules() as $name) {
+ $candidate = $modules->getModule($name);
+ $modulePath = $candidate->getBaseDir();
+ if (substr($sourcePath, 0, strlen($modulePath)) === $modulePath) {
+ $module = $candidate;
+ break;
+ }
+ }
+
+ if (preg_match('/^(?:Icinga\\\Module\\\(\w+)|(\w+)\\\(\w+))/', $match[1] ?: $match[2], $natch)) {
+ $this->view->requiredModule = isset($natch[1]) ? strtolower($natch[1]) : null;
+ $this->view->requiredVendor = isset($natch[2]) ? $natch[2] : null;
+ $this->view->requiredProject = isset($natch[3]) ? $natch[3] : null;
+ }
+ }
+
+ $this->view->messages = array();
+
+ if ($this->getInvokeArg('displayExceptions')) {
+ $this->view->stackTraces = array();
+
+ do {
+ $this->view->messages[] = $exception->getMessage();
+ $this->view->stackTraces[] = IcingaException::getConfidentialTraceAsString($exception);
+ $exception = $exception->getPrevious();
+ } while ($exception !== null);
+ } else {
+ do {
+ $this->view->messages[] = $exception->getMessage();
+ $exception = $exception->getPrevious();
+ } while ($exception !== null);
+ }
+
+ break;
+ }
+
+ if ($this->getRequest()->isApiRequest()) {
+ $this->getResponse()->json()
+ ->setErrorMessage($this->view->messages[0])
+ ->sendResponse();
+ }
+
+ $this->view->module = $module;
+ $this->view->request = $error->request;
+ if (! $isAuthenticated) {
+ $this->view->hideControls = true;
+ } else {
+ $this->view->hideControls = false;
+ $this->getTabs()->add('error', array(
+ 'active' => true,
+ 'label' => $this->translate('Error'),
+ 'url' => Url::fromRequest()
+ ));
+ }
+ }
+}
diff --git a/application/controllers/GroupController.php b/application/controllers/GroupController.php
new file mode 100644
index 0000000..d18397c
--- /dev/null
+++ b/application/controllers/GroupController.php
@@ -0,0 +1,418 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Authentication\User\DomainAwareInterface;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Reducible;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\Config\UserGroup\AddMemberForm;
+use Icinga\Forms\Config\UserGroup\UserGroupForm;
+use Icinga\User;
+use Icinga\Web\Controller\AuthBackendController;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+use Icinga\Web\Widget;
+
+class GroupController extends AuthBackendController
+{
+ public function init()
+ {
+ $this->view->title = $this->translate('User Groups');
+
+ parent::init();
+ }
+
+ /**
+ * List all user groups of a single backend
+ */
+ public function listAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $this->createListTabs()->activate('group/list');
+ $backendNames = array_map(
+ function ($b) {
+ return $b->getName();
+ },
+ $this->loadUserGroupBackends('Icinga\Data\Selectable')
+ );
+ if (empty($backendNames)) {
+ return;
+ }
+
+ $this->view->backendSelection = new Form();
+ $this->view->backendSelection->setAttrib('class', 'backend-selection icinga-controls');
+ $this->view->backendSelection->setUidDisabled();
+ $this->view->backendSelection->setMethod('GET');
+ $this->view->backendSelection->setTokenDisabled();
+ $this->view->backendSelection->addElement(
+ 'select',
+ 'backend',
+ array(
+ 'autosubmit' => true,
+ 'label' => $this->translate('User Group Backend'),
+ 'multiOptions' => array_combine($backendNames, $backendNames),
+ 'value' => $this->params->get('backend')
+ )
+ );
+
+ $backend = $this->getUserGroupBackend($this->params->get('backend'));
+ if ($backend === null) {
+ $this->view->backend = null;
+ return;
+ }
+
+ $query = $backend->select(array('group_name'));
+
+ $this->view->groups = $query;
+ $this->view->backend = $backend;
+
+ $this->setupPaginationControl($query);
+ $this->setupFilterControl($query);
+ $this->setupLimitControl();
+ $this->setupSortControl(
+ array(
+ 'group_name' => $this->translate('User Group'),
+ 'created_at' => $this->translate('Created at'),
+ 'last_modified' => $this->translate('Last modified')
+ ),
+ $query
+ );
+ }
+
+ /**
+ * Show a group
+ */
+ public function showAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $groupName = $this->params->getRequired('group');
+ $backend = $this->getUserGroupBackend($this->params->getRequired('backend'));
+
+ $group = $backend->select(array(
+ 'group_name',
+ 'created_at',
+ 'last_modified'
+ ))->where('group_name', $groupName)->fetchRow();
+ if ($group === false) {
+ $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName));
+ }
+
+ $members = $backend
+ ->select()
+ ->from('group_membership', array('user_name'))
+ ->where('group_name', $groupName);
+
+ $this->setupFilterControl($members, null, array('user'), array('group'));
+ $this->setupPaginationControl($members);
+ $this->setupLimitControl();
+ $this->setupSortControl(
+ array(
+ 'user_name' => $this->translate('Username'),
+ 'created_at' => $this->translate('Created at'),
+ 'last_modified' => $this->translate('Last modified')
+ ),
+ $members
+ );
+
+ $this->view->group = $group;
+ $this->view->backend = $backend;
+ $this->view->members = $members;
+ $this->createShowTabs($backend->getName(), $groupName)->activate('group/show');
+
+ if ($this->hasPermission('config/access-control/groups') && $backend instanceof Reducible) {
+ $removeForm = new Form();
+ $removeForm->setUidDisabled();
+ $removeForm->setAttrib('class', 'inline');
+ $removeForm->setAction(
+ Url::fromPath('group/removemember', array('backend' => $backend->getName(), 'group' => $groupName))
+ );
+ $removeForm->addElement('hidden', 'user_name', array(
+ 'isArray' => true,
+ 'decorators' => array('ViewHelper')
+ ));
+ $removeForm->addElement('hidden', 'redirect', array(
+ 'value' => Url::fromPath('group/show', array(
+ 'backend' => $backend->getName(),
+ 'group' => $groupName
+ )),
+ 'decorators' => array('ViewHelper')
+ ));
+ $removeForm->addElement('button', 'btn_submit', array(
+ 'escape' => false,
+ 'type' => 'submit',
+ 'class' => 'link-button spinner',
+ 'value' => 'btn_submit',
+ 'decorators' => array('ViewHelper'),
+ 'label' => $this->view->icon('trash'),
+ 'title' => $this->translate('Remove this member')
+ ));
+ $this->view->removeForm = $removeForm;
+ }
+ }
+
+ /**
+ * Add a group
+ */
+ public function addAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible');
+ $form = new UserGroupForm();
+ $form->setRedirectUrl(Url::fromPath('group/list', array('backend' => $backend->getName())));
+ $form->setRepository($backend);
+ $form->add()->handleRequest();
+
+ $this->renderForm($form, $this->translate('New User Group'));
+ }
+
+ /**
+ * Edit a group
+ */
+ public function editAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $groupName = $this->params->getRequired('group');
+ $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Updatable');
+
+ $form = new UserGroupForm();
+ $form->setRedirectUrl(
+ Url::fromPath('group/show', array('backend' => $backend->getName(), 'group' => $groupName))
+ );
+ $form->setRepository($backend);
+
+ try {
+ $form->edit($groupName)->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName));
+ }
+
+ $this->renderForm($form, $this->translate('Update User Group'));
+ }
+
+ /**
+ * Remove a group
+ */
+ public function removeAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $groupName = $this->params->getRequired('group');
+ $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible');
+
+ $form = new UserGroupForm();
+ $form->setRedirectUrl(Url::fromPath('group/list', array('backend' => $backend->getName())));
+ $form->setRepository($backend);
+
+ try {
+ $form->remove($groupName)->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName));
+ }
+
+ $this->renderForm($form, $this->translate('Remove User Group'));
+ }
+
+ /**
+ * Add a group member
+ */
+ public function addmemberAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $groupName = $this->params->getRequired('group');
+ $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible');
+
+ $form = new AddMemberForm();
+ $form->setDataSource($this->fetchUsers())
+ ->setBackend($backend)
+ ->setGroupName($groupName)
+ ->setRedirectUrl(
+ Url::fromPath('group/show', array('backend' => $backend->getName(), 'group' => $groupName))
+ )
+ ->setUidDisabled();
+
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName));
+ }
+
+ $this->renderForm($form, $this->translate('New User Group Member'));
+ }
+
+ /**
+ * Remove a group member
+ */
+ public function removememberAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $this->assertHttpMethod('POST');
+ $groupName = $this->params->getRequired('group');
+ $backend = $this->getUserGroupBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible');
+
+ $form = new Form(array(
+ 'onSuccess' => function ($form) use ($groupName, $backend) {
+ foreach ($form->getValue('user_name') as $userName) {
+ try {
+ $backend->delete(
+ 'group_membership',
+ Filter::matchAll(
+ Filter::where('group_name', $groupName),
+ Filter::where('user_name', $userName)
+ )
+ );
+ Notification::success(sprintf(
+ t('User "%s" has been removed from group "%s"'),
+ $userName,
+ $groupName
+ ));
+ } catch (NotFoundError $e) {
+ throw $e;
+ } catch (Exception $e) {
+ Notification::error($e->getMessage());
+ }
+ }
+
+ $redirect = $form->getValue('redirect');
+ if (! empty($redirect)) {
+ $form->setRedirectUrl(htmlspecialchars_decode($redirect));
+ }
+
+ return true;
+ }
+ ));
+ $form->setUidDisabled();
+ $form->setSubmitLabel('btn_submit'); // Required to ensure that isSubmitted() is called
+ $form->addElement('hidden', 'user_name', array('required' => true, 'isArray' => true));
+ $form->addElement('hidden', 'redirect');
+
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Group "%s" not found'), $groupName));
+ }
+ }
+
+ /**
+ * Fetch and return all users from all user backends
+ *
+ * @return ArrayDatasource
+ */
+ protected function fetchUsers()
+ {
+ $users = array();
+ foreach ($this->loadUserBackends('Icinga\Data\Selectable') as $backend) {
+ try {
+ if ($backend instanceof DomainAwareInterface) {
+ $domain = $backend->getDomain();
+ } else {
+ $domain = null;
+ }
+ foreach ($backend->select(array('user_name')) as $user) {
+ $userObj = new User($user->user_name);
+ if ($domain !== null) {
+ if ($userObj->hasDomain() && $userObj->getDomain() !== $domain) {
+ // Users listed in a user backend which is configured to be responsible for a domain should
+ // not have a domain in their username. Ultimately, if the username has a domain, it must
+ // not differ from the backend's domain. We could log here - but hey, who cares :)
+ continue;
+ } else {
+ $userObj->setDomain($domain);
+ }
+ }
+
+ $user->user_name = $userObj->getUsername();
+
+ $users[] = $user;
+ }
+ } catch (Exception $e) {
+ Logger::error($e);
+ Notification::warning(sprintf(
+ $this->translate('Failed to fetch any users from backend %s. Please check your log'),
+ $backend->getName()
+ ));
+ }
+ }
+
+ return new ArrayDatasource($users);
+ }
+
+ /**
+ * Create the tabs to display when showing a group
+ *
+ * @param string $backendName
+ * @param string $groupName
+ */
+ protected function createShowTabs($backendName, $groupName)
+ {
+ $tabs = $this->getTabs();
+ $tabs->add(
+ 'group/show',
+ array(
+ 'title' => sprintf($this->translate('Show group %s'), $groupName),
+ 'label' => $this->translate('Group'),
+ 'url' => Url::fromPath('group/show', array('backend' => $backendName, 'group' => $groupName))
+ )
+ );
+
+ return $tabs;
+ }
+
+ /**
+ * Create the tabs to display when listing groups
+ */
+ protected function createListTabs()
+ {
+ $tabs = $this->getTabs();
+
+ if ($this->hasPermission('config/access-control/roles')) {
+ $tabs->add(
+ 'role/list',
+ array(
+ 'baseTarget' => '_main',
+ 'label' => $this->translate('Roles'),
+ 'title' => $this->translate(
+ 'Configure roles to permit or restrict users and groups accessing Icinga Web 2'
+ ),
+ 'url' => 'role/list'
+ )
+ );
+
+ $tabs->add(
+ 'role/audit',
+ [
+ 'title' => $this->translate('Audit a user\'s or group\'s privileges'),
+ 'label' => $this->translate('Audit'),
+ 'url' => 'role/audit',
+ 'baseTarget' => '_main',
+ ]
+ );
+ }
+
+ if ($this->hasPermission('config/access-control/users')) {
+ $tabs->add(
+ 'user/list',
+ array(
+ 'title' => $this->translate('List users of authentication backends'),
+ 'label' => $this->translate('Users'),
+ 'url' => 'user/list'
+ )
+ );
+ }
+
+ $tabs->add(
+ 'group/list',
+ array(
+ 'title' => $this->translate('List groups of user group backends'),
+ 'label' => $this->translate('User Groups'),
+ 'url' => 'group/list'
+ )
+ );
+
+ return $tabs;
+ }
+}
diff --git a/application/controllers/HealthController.php b/application/controllers/HealthController.php
new file mode 100644
index 0000000..f176e69
--- /dev/null
+++ b/application/controllers/HealthController.php
@@ -0,0 +1,65 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Application\Hook\HealthHook;
+use Icinga\Web\View\AppHealth;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+use ipl\Html\Html;
+use ipl\Html\HtmlString;
+use ipl\Web\Compat\CompatController;
+
+class HealthController extends CompatController
+{
+ public function indexAction()
+ {
+ $query = HealthHook::collectHealthData()
+ ->select();
+
+ $this->setupSortControl(
+ [
+ 'module' => $this->translate('Module'),
+ 'name' => $this->translate('Name'),
+ 'state' => $this->translate('State')
+ ],
+ $query,
+ ['state' => 'desc']
+ );
+ $this->setupLimitControl();
+ $this->setupPaginationControl($query);
+ $this->setupFilterControl($query, [
+ 'module' => $this->translate('Module'),
+ 'name' => $this->translate('Name'),
+ 'state' => $this->translate('State'),
+ 'message' => $this->translate('Message')
+ ], ['name'], ['format']);
+
+ $this->getTabs()->extend(new OutputFormat(['csv']));
+ $this->handleFormatRequest($query);
+
+ $this->addControl(HtmlString::create((string) $this->view->paginator));
+ $this->addControl(Html::tag('div', ['class' => 'sort-controls-container'], [
+ HtmlString::create((string) $this->view->limiter),
+ HtmlString::create((string) $this->view->sortBox)
+ ]));
+ $this->addControl(HtmlString::create((string) $this->view->filterEditor));
+
+ $this->addTitleTab(t('Health'));
+ $this->setAutorefreshInterval(10);
+ $this->addContent(new AppHealth($query));
+ }
+
+ protected function handleFormatRequest($query)
+ {
+ $formatJson = $this->params->get('format') === 'json';
+ if (! $formatJson && ! $this->getRequest()->isApiRequest()) {
+ return;
+ }
+
+ $this->getResponse()
+ ->json()
+ ->setSuccessData($query->fetchAll())
+ ->sendResponse();
+ }
+}
diff --git a/application/controllers/IframeController.php b/application/controllers/IframeController.php
new file mode 100644
index 0000000..8aebba4
--- /dev/null
+++ b/application/controllers/IframeController.php
@@ -0,0 +1,20 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Web\Controller;
+
+/**
+ * Display external or internal links within an iframe
+ */
+class IframeController extends Controller
+{
+ /**
+ * Display iframe w/ the given URL
+ */
+ public function indexAction()
+ {
+ $this->view->url = $this->params->getRequired('url');
+ }
+}
diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php
new file mode 100644
index 0000000..dac1789
--- /dev/null
+++ b/application/controllers/IndexController.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Web\Controller\ActionController;
+use Icinga\Web\Url;
+
+/**
+ * Application wide index controller
+ */
+class IndexController extends ActionController
+{
+ /**
+ * Use a default redirection rule to welcome page
+ */
+ public function preDispatch()
+ {
+ if ($this->getRequest()->getActionName() !== 'welcome') {
+ // @TODO(el): Avoid landing page redirects: https://dev.icinga.com/issues/9656
+ $this->redirectNow(Url::fromRequest()->setPath('dashboard'));
+ }
+ }
+
+ /**
+ * Application's start page
+ */
+ public function welcomeAction()
+ {
+ }
+}
diff --git a/application/controllers/LayoutController.php b/application/controllers/LayoutController.php
new file mode 100644
index 0000000..237681c
--- /dev/null
+++ b/application/controllers/LayoutController.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Web\Controller\ActionController;
+use Icinga\Web\Menu;
+
+/**
+ * Create complex layout parts
+ */
+class LayoutController extends ActionController
+{
+ /**
+ * Render the menu
+ */
+ public function menuAction()
+ {
+ $this->setAutorefreshInterval(15);
+ $this->_helper->layout()->disableLayout();
+ $this->view->menuRenderer = (new Menu())->getRenderer();
+ }
+
+ public function announcementsAction()
+ {
+ $this->_helper->layout()->disableLayout();
+ }
+}
diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php
new file mode 100644
index 0000000..2fbc5a9
--- /dev/null
+++ b/application/controllers/ListController.php
@@ -0,0 +1,59 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\Protocol\File\FileReader;
+use Icinga\Web\Controller;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+
+/**
+ * Application wide controller for various listing actions
+ */
+class ListController extends Controller
+{
+ /**
+ * Add title tab
+ *
+ * @param string $action
+ */
+ protected function addTitleTab($action)
+ {
+ $this->getTabs()->add($action, array(
+ 'label' => ucfirst($action),
+ 'url' => Url::fromPath('list/' . str_replace(' ', '', $action))
+ ))->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction())->activate($action);
+ }
+
+ /**
+ * Display the application log
+ */
+ public function applicationlogAction()
+ {
+ $this->assertPermission('application/log');
+
+ if (! Logger::writesToFile()) {
+ $this->httpNotFound('Page not found');
+ }
+
+ $this->addTitleTab('application log');
+
+ $resource = new FileReader(new ConfigObject(array(
+ 'filename' => Config::app()->get('logging', 'file'),
+ 'fields' => '/(?<!.)(?<datetime>[0-9]{4}(?:-[0-9]{2}){2}' // date
+ . 'T[0-9]{2}(?::[0-9]{2}){2}(?:[\+\-][0-9]{2}:[0-9]{2})?)' // time
+ . ' - (?<loglevel>[A-Za-z]+) - (?<message>.*)(?!.)/msS' // loglevel, message
+ )));
+ $this->view->logData = $resource->select()->order('DESC');
+
+ $this->setupLimitControl();
+ $this->setupPaginationControl($this->view->logData);
+ $this->view->title = $this->translate('Application Log');
+ }
+}
diff --git a/application/controllers/ManageUserDevicesController.php b/application/controllers/ManageUserDevicesController.php
new file mode 100644
index 0000000..db054d1
--- /dev/null
+++ b/application/controllers/ManageUserDevicesController.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Common\Database;
+use Icinga\Web\Notification;
+use Icinga\Web\RememberMe;
+use Icinga\Web\RememberMeUserList;
+use Icinga\Web\RememberMeUserDevicesList;
+use ipl\Web\Compat\CompatController;
+use ipl\Web\Url;
+
+/**
+ * ManageUserDevicesController
+ *
+ * you need 'application/sessions' permission to use this controller
+ */
+class ManageUserDevicesController extends CompatController
+{
+ use Database;
+
+ public function init()
+ {
+ $this->assertPermission('application/sessions');
+ }
+
+ public function indexAction()
+ {
+ $this->getTabs()
+ ->add(
+ 'manage-user-devices',
+ array(
+ 'title' => $this->translate('List of users who stay logged in'),
+ 'label' => $this->translate('Users'),
+ 'url' => 'manage-user-devices',
+ 'data-base-target' => '_self'
+ )
+ )->activate('manage-user-devices');
+
+ $usersList = (new RememberMeUserList())
+ ->setUsers(RememberMe::getAllUser())
+ ->setUrl('manage-user-devices/devices');
+
+ $this->addContent($usersList);
+
+ if (! $this->hasDb()) {
+ Notification::warning(
+ $this->translate("Users can't stay logged in without a database configuration backend")
+ );
+ }
+ }
+
+ public function devicesAction()
+ {
+ $this->getTabs()
+ ->add(
+ 'manage-devices',
+ array(
+ 'title' => $this->translate('List of devices'),
+ 'label' => $this->translate('Devices'),
+ 'url' => 'manage-user-devices/devices'
+ )
+ )->activate('manage-devices');
+
+ $name = $this->params->getRequired('name');
+ $data = (new RememberMeUserDevicesList())
+ ->setDevicesList(RememberMe::getAllByUsername($name))
+ ->setUsername($name)
+ ->setUrl('manage-user-devices/delete');
+
+ $this->addContent($data);
+ }
+
+ public function deleteAction()
+ {
+ (new RememberMe())->remove($this->params->getRequired('fingerprint'));
+
+ $this->redirectNow(
+ Url::fromPath('manage-user-devices/devices')
+ ->addParams(['name' => $this->params->getRequired('name')])
+ );
+ }
+}
diff --git a/application/controllers/MyDevicesController.php b/application/controllers/MyDevicesController.php
new file mode 100644
index 0000000..e0fb98a
--- /dev/null
+++ b/application/controllers/MyDevicesController.php
@@ -0,0 +1,74 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Common\Database;
+use Icinga\Web\Notification;
+use Icinga\Web\RememberMe;
+use Icinga\Web\RememberMeUserDevicesList;
+use ipl\Web\Compat\CompatController;
+
+/**
+ * MyDevicesController
+ *
+ * this controller shows you all the devices you are logged in
+ */
+class MyDevicesController extends CompatController
+{
+ use Database;
+
+ public function init()
+ {
+ $this->getTabs()
+ ->add(
+ 'account',
+ array(
+ 'title' => $this->translate('Update your account'),
+ 'label' => $this->translate('My Account'),
+ 'url' => 'account'
+ )
+ )
+ ->add(
+ 'navigation',
+ array(
+ 'title' => $this->translate('List and configure your own navigation items'),
+ 'label' => $this->translate('Navigation'),
+ 'url' => 'navigation'
+ )
+ )
+ ->add(
+ 'devices',
+ array(
+ 'title' => $this->translate('List of devices you are logged in'),
+ 'label' => $this->translate('My Devices'),
+ 'url' => 'my-devices'
+ )
+ )->activate('devices');
+ }
+
+ public function indexAction()
+ {
+ $name = $this->auth->getUser()->getUsername();
+
+ $data = (new RememberMeUserDevicesList())
+ ->setDevicesList(RememberMe::getAllByUsername($name))
+ ->setUsername($name)
+ ->setUrl('my-devices/delete');
+
+ $this->addContent($data);
+
+ if (! $this->hasDb()) {
+ Notification::warning(
+ $this->translate("Users can't stay logged in without a database configuration backend")
+ );
+ }
+ }
+
+ public function deleteAction()
+ {
+ (new RememberMe())->remove($this->params->getRequired('fingerprint'));
+
+ $this->redirectNow('my-devices');
+ }
+}
diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php
new file mode 100644
index 0000000..b0babc3
--- /dev/null
+++ b/application/controllers/NavigationController.php
@@ -0,0 +1,447 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Exception\NotFoundError;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\Filter\FilterMatchCaseInsensitive;
+use Icinga\Forms\ConfirmRemovalForm;
+use Icinga\Forms\Navigation\NavigationConfigForm;
+use Icinga\Web\Controller;
+use Icinga\Web\Form;
+use Icinga\Web\Menu;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+
+/**
+ * Navigation configuration
+ */
+class NavigationController extends Controller
+{
+ /**
+ * The global navigation item type configuration
+ *
+ * @var array
+ */
+ protected $itemTypeConfig;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ parent::init();
+ $this->itemTypeConfig = Navigation::getItemTypeConfiguration();
+ }
+
+ /**
+ * Return the label for the given navigation item type
+ *
+ * @param string $type
+ *
+ * @return string $type if no label can be found
+ */
+ protected function getItemLabel($type)
+ {
+ return isset($this->itemTypeConfig[$type]['label']) ? $this->itemTypeConfig[$type]['label'] : $type;
+ }
+
+ /**
+ * Return a list of available navigation item types
+ *
+ * @return array
+ */
+ protected function listItemTypes()
+ {
+ $types = array();
+ foreach ($this->itemTypeConfig as $type => $options) {
+ $types[$type] = isset($options['label']) ? $options['label'] : $type;
+ }
+
+ return $types;
+ }
+
+ /**
+ * Return all shared navigation item configurations
+ *
+ * @param string $owner A username if only items shared by a specific user are desired
+ *
+ * @return array
+ */
+ protected function fetchSharedNavigationItemConfigs($owner = null)
+ {
+ $configs = array();
+ foreach ($this->itemTypeConfig as $type => $_) {
+ $config = Config::navigation($type);
+ $config->getConfigObject()->setKeyColumn('name');
+ $query = $config->select();
+ if ($owner !== null) {
+ $query->applyFilter(new FilterMatchCaseInsensitive('owner', '=', $owner));
+ }
+
+ foreach ($query as $itemConfig) {
+ $configs[] = $itemConfig;
+ }
+ }
+
+ return $configs;
+ }
+
+ /**
+ * Return all user navigation item configurations
+ *
+ * @param string $username
+ *
+ * @return array
+ */
+ protected function fetchUserNavigationItemConfigs($username)
+ {
+ $configs = array();
+ foreach ($this->itemTypeConfig as $type => $_) {
+ $config = Config::navigation($type, $username);
+ $config->getConfigObject()->setKeyColumn('name');
+ foreach ($config->select() as $itemConfig) {
+ $configs[] = $itemConfig;
+ }
+ }
+
+ return $configs;
+ }
+
+ /**
+ * Show the current user a list of his/her navigation items
+ */
+ public function indexAction()
+ {
+ $user = $this->Auth()->getUser();
+ $ds = new ArrayDatasource(array_merge(
+ $this->fetchSharedNavigationItemConfigs($user->getUsername()),
+ $this->fetchUserNavigationItemConfigs($user->getUsername())
+ ));
+ $query = $ds->select();
+
+ $this->view->types = $this->listItemTypes();
+ $this->view->items = $query;
+
+ $this->view->title = $this->translate('Navigation');
+ $this->getTabs()
+ ->add(
+ 'account',
+ array(
+ 'title' => $this->translate('Update your account'),
+ 'label' => $this->translate('My Account'),
+ 'url' => 'account'
+ )
+ )
+ ->add(
+ 'navigation',
+ array(
+ 'active' => true,
+ 'title' => $this->translate('List and configure your own navigation items'),
+ 'label' => $this->translate('Navigation'),
+ 'url' => 'navigation'
+ )
+ )
+ ->add(
+ 'devices',
+ array(
+ 'title' => $this->translate('List of devices you are logged in'),
+ 'label' => $this->translate('My Devices'),
+ 'url' => 'my-devices'
+ )
+ );
+ $this->setupSortControl(
+ array(
+ 'type' => $this->translate('Type'),
+ 'owner' => $this->translate('Shared'),
+ 'name' => $this->translate('Navigation')
+ ),
+ $query
+ );
+ }
+
+ /**
+ * List all shared navigation items
+ */
+ public function sharedAction()
+ {
+ $this->assertPermission('config/navigation');
+ $ds = new ArrayDatasource($this->fetchSharedNavigationItemConfigs());
+ $query = $ds->select();
+
+ $removeForm = new Form();
+ $removeForm->setUidDisabled();
+ $removeForm->setAttrib('class', 'inline');
+ $removeForm->addElement('hidden', 'name', array(
+ 'decorators' => array('ViewHelper')
+ ));
+ $removeForm->addElement('hidden', 'redirect', array(
+ 'value' => Url::fromPath('navigation/shared'),
+ 'decorators' => array('ViewHelper')
+ ));
+ $removeForm->addElement('button', 'btn_submit', array(
+ 'escape' => false,
+ 'type' => 'submit',
+ 'class' => 'link-button spinner',
+ 'value' => 'btn_submit',
+ 'decorators' => array('ViewHelper'),
+ 'label' => $this->view->icon('trash'),
+ 'title' => $this->translate('Unshare this navigation item')
+ ));
+
+ $this->view->removeForm = $removeForm;
+ $this->view->types = $this->listItemTypes();
+ $this->view->items = $query;
+
+ $this->view->title = $this->translate('Shared Navigation');
+ $this->getTabs()->add(
+ 'navigation/shared',
+ array(
+ 'title' => $this->translate('List and configure shared navigation items'),
+ 'label' => $this->translate('Shared Navigation'),
+ 'url' => 'navigation/shared'
+ )
+ )->activate('navigation/shared');
+ $this->setupSortControl(
+ array(
+ 'type' => $this->translate('Type'),
+ 'owner' => $this->translate('Owner'),
+ 'name' => $this->translate('Shared Navigation')
+ ),
+ $query
+ );
+ }
+
+ /**
+ * Add a navigation item
+ */
+ public function addAction()
+ {
+ $form = new NavigationConfigForm();
+ $form->setRedirectUrl('navigation');
+ $form->setUser($this->Auth()->getUser());
+ $form->setItemTypes($this->listItemTypes());
+ $form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.'));
+
+ // TODO: Fetch all "safe" parameters from the url and populate them
+ $form->setDefaultUrl(rawurldecode($this->params->get('url', '')));
+
+ $form->setOnSuccess(function (NavigationConfigForm $form) {
+ $data = $form::transformEmptyValuesToNull($form->getValues());
+
+ try {
+ $form->add($data);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ if ($data['type'] === 'menu-item') {
+ $form->getResponse()->setRerenderLayout();
+ }
+
+ Notification::success(t('Navigation item successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->title = $this->translate('Navigation');
+ $this->renderForm($form, $this->translate('New Navigation Item'));
+ }
+
+ /**
+ * Edit a navigation item
+ */
+ public function editAction()
+ {
+ $itemName = $this->params->getRequired('name');
+ $itemType = $this->params->getRequired('type');
+ $referrer = $this->params->get('referrer', 'index');
+
+ $user = $this->Auth()->getUser();
+ if ($user->can('config/navigation')) {
+ $itemOwner = $this->params->get('owner', $user->getUsername());
+ } else {
+ $itemOwner = $user->getUsername();
+ }
+
+ $form = new NavigationConfigForm();
+ $form->setUser($user);
+ $form->setShareConfig(Config::navigation($itemType));
+ $form->setUserConfig(Config::navigation($itemType, $itemOwner));
+ $form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
+ $form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) {
+ $data = $form::transformEmptyValuesToNull($form->getValues());
+
+ try {
+ $form->edit($itemName, $data);
+ } catch (NotFoundError $e) {
+ throw $e;
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ if (isset($data['type']) && $data['type'] === 'menu-item') {
+ $form->getResponse()->setRerenderLayout();
+ }
+
+ Notification::success(sprintf(t('Navigation item "%s" successfully updated'), $itemName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($itemName);
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $itemName));
+ }
+
+ $this->view->title = $this->translate('Navigation');
+ $this->renderForm($form, $this->translate('Update Navigation Item'));
+ }
+
+ /**
+ * Remove a navigation item
+ */
+ public function removeAction()
+ {
+ $itemName = $this->params->getRequired('name');
+ $itemType = $this->params->getRequired('type');
+ $user = $this->Auth()->getUser();
+
+ $navigationConfigForm = new NavigationConfigForm();
+ $navigationConfigForm->setUser($user);
+ $navigationConfigForm->setShareConfig(Config::navigation($itemType));
+ $navigationConfigForm->setUserConfig(Config::navigation($itemType, $user->getUsername()));
+
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('navigation');
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) {
+ try {
+ $itemConfig = $navigationConfigForm->delete($itemName);
+ } catch (NotFoundError $e) {
+ Notification::success(sprintf(t('Navigation Item "%s" not found. No action required'), $itemName));
+ return true;
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($navigationConfigForm->save()) {
+ if ($itemConfig->type === 'menu-item') {
+ $form->getResponse()->setRerenderLayout();
+ }
+
+ Notification::success(sprintf(t('Navigation Item "%s" successfully removed'), $itemName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->title = $this->translate('Navigation');
+ $this->renderForm($form, $this->translate('Remove Navigation Item'));
+ }
+
+ /**
+ * Unshare a navigation item
+ */
+ public function unshareAction()
+ {
+ $this->assertPermission('config/navigation');
+ $this->assertHttpMethod('POST');
+
+ // TODO: I'd like these being form fields
+ $itemType = $this->params->getRequired('type');
+ $itemOwner = $this->params->getRequired('owner');
+
+ $navigationConfigForm = new NavigationConfigForm();
+ $navigationConfigForm->setUser($this->Auth()->getUser());
+ $navigationConfigForm->setShareConfig(Config::navigation($itemType));
+ $navigationConfigForm->setUserConfig(Config::navigation($itemType, $itemOwner));
+
+ $form = new Form(array(
+ 'onSuccess' => function ($form) use ($navigationConfigForm) {
+ $itemName = $form->getValue('name');
+
+ try {
+ $newConfig = $navigationConfigForm->unshare($itemName);
+ if ($navigationConfigForm->save()) {
+ if ($newConfig->getSection($itemName)->type === 'menu-item') {
+ $form->getResponse()->setRerenderLayout();
+ }
+
+ Notification::success(sprintf(
+ t('Navigation item "%s" has been unshared'),
+ $form->getValue('name')
+ ));
+ } else {
+ // TODO: It failed obviously to write one of the configs, so we're leaving the user in
+ // a inconsistent state. Luckily, it's nothing lost but possibly duplicated...
+ Notification::error(sprintf(
+ t('Failed to unshare navigation item "%s"'),
+ $form->getValue('name')
+ ));
+ }
+ } catch (NotFoundError $e) {
+ throw $e;
+ } catch (Exception $e) {
+ Notification::error($e->getMessage());
+ }
+
+ $redirect = $form->getValue('redirect');
+ if (! empty($redirect)) {
+ $form->setRedirectUrl(htmlspecialchars_decode($redirect));
+ }
+
+ return true;
+ }
+ ));
+ $form->setUidDisabled();
+ $form->setSubmitLabel('btn_submit'); // Required to ensure that isSubmitted() is called
+ $form->addElement('hidden', 'name', array('required' => true));
+ $form->addElement('hidden', 'redirect');
+
+ try {
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $form->getValue('name')));
+ }
+ }
+
+ public function dashboardAction()
+ {
+ $name = $this->params->getRequired('name');
+
+ $this->getTabs()->add('dashboard', array(
+ 'active' => true,
+ 'label' => ucwords($name),
+ 'url' => Url::fromRequest()
+ ));
+
+ $menu = new Menu();
+
+ $navigation = $menu->findItem($name);
+
+ if ($navigation === null) {
+ $this->httpNotFound($this->translate('Navigation not found'));
+ }
+
+ $this->view->navigation = $navigation;
+ $this->view->title = $navigation->getLabel();
+ }
+}
diff --git a/application/controllers/RoleController.php b/application/controllers/RoleController.php
new file mode 100644
index 0000000..4223d33
--- /dev/null
+++ b/application/controllers/RoleController.php
@@ -0,0 +1,392 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use GuzzleHttp\Psr7\ServerRequest;
+use Icinga\Authentication\AdmissionLoader;
+use Icinga\Authentication\Auth;
+use Icinga\Authentication\RolesConfig;
+use Icinga\Authentication\User\DomainAwareInterface;
+use Icinga\Data\Selectable;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\Security\RoleForm;
+use Icinga\Repository\Repository;
+use Icinga\Security\SecurityException;
+use Icinga\User;
+use Icinga\Web\Controller\AuthBackendController;
+use Icinga\Web\View\PrivilegeAudit;
+use Icinga\Web\Widget\SingleValueSearchControl;
+use ipl\Html\Html;
+use ipl\Html\HtmlString;
+use ipl\Web\Url;
+use ipl\Web\Widget\Link;
+
+/**
+ * Manage user permissions and restrictions based on roles
+ *
+ * @TODO(el): Rename to RolesController: https://dev.icinga.com/issues/10015
+ */
+class RoleController extends AuthBackendController
+{
+ public function init()
+ {
+ $this->assertPermission('config/access-control/roles');
+ $this->view->title = $this->translate('Roles');
+
+ parent::init();
+ }
+
+ public function indexAction()
+ {
+ if ($this->hasPermission('config/access-control/roles')) {
+ $this->redirectNow('role/list');
+ } elseif ($this->hasPermission('config/access-control/users')) {
+ $this->redirectNow('user/list');
+ } elseif ($this->hasPermission('config/access-control/groups')) {
+ $this->redirectNow('group/list');
+ } else {
+ throw new SecurityException('No permission to configure Icinga Web 2');
+ }
+ }
+
+ /**
+ * List roles
+ *
+ * @TODO(el): Rename to indexAction()
+ */
+ public function listAction()
+ {
+ $this->createListTabs()->activate('role/list');
+ $this->view->roles = (new RolesConfig())
+ ->select();
+
+ $sortAndFilterColumns = [
+ 'name' => $this->translate('Name'),
+ 'users' => $this->translate('Users'),
+ 'groups' => $this->translate('Groups'),
+ 'permissions' => $this->translate('Permissions')
+ ];
+
+ $this->setupFilterControl($this->view->roles, $sortAndFilterColumns, ['name']);
+ $this->setupLimitControl();
+ $this->setupPaginationControl($this->view->roles);
+ $this->setupSortControl($sortAndFilterColumns, $this->view->roles, ['name']);
+ }
+
+ /**
+ * Create a new role
+ *
+ * @TODO(el): Rename to newAction()
+ */
+ public function addAction()
+ {
+ $role = new RoleForm();
+ $role->setRedirectUrl('__CLOSE__');
+ $role->setRepository(new RolesConfig());
+ $role->setSubmitLabel($this->translate('Create Role'));
+ $role->add()->handleRequest();
+
+ $this->renderForm($role, $this->translate('New Role'));
+ }
+
+ /**
+ * Update a role
+ *
+ * @TODO(el): Rename to updateAction()
+ */
+ public function editAction()
+ {
+ $name = $this->params->getRequired('role');
+ $role = new RoleForm();
+ $role->setRedirectUrl('__CLOSE__');
+ $role->setRepository(new RolesConfig());
+ $role->setSubmitLabel($this->translate('Update Role'));
+ $role->edit($name);
+
+ try {
+ $role->handleRequest();
+ } catch (NotFoundError $e) {
+ $this->httpNotFound($this->translate('Role not found'));
+ }
+
+ $this->renderForm($role, $this->translate('Update Role'));
+ }
+
+ /**
+ * Remove a role
+ */
+ public function removeAction()
+ {
+ $name = $this->params->getRequired('role');
+ $role = new RoleForm();
+ $role->setRedirectUrl('__CLOSE__');
+ $role->setRepository(new RolesConfig());
+ $role->setSubmitLabel($this->translate('Remove Role'));
+ $role->remove($name);
+
+ try {
+ $role->handleRequest();
+ } catch (NotFoundError $e) {
+ $this->httpNotFound($this->translate('Role not found'));
+ }
+
+ $this->renderForm($role, $this->translate('Remove Role'));
+ }
+
+ public function auditAction()
+ {
+ $this->createListTabs()->activate('role/audit');
+ $this->view->title = t('Audit');
+
+ $roleName = $this->params->get('role');
+ $type = $this->params->has('group') ? 'group' : 'user';
+ $name = $this->params->get($type);
+
+ $backend = null;
+ if ($type === 'user') {
+ if ($name) {
+ $backend = $this->params->getRequired('backend');
+ } else {
+ $backends = $this->loadUserBackends();
+ if (! empty($backends)) {
+ $backend = array_shift($backends)->getName();
+ }
+ }
+ }
+
+ $form = new SingleValueSearchControl();
+ $form->setMetaDataNames('type', 'backend');
+ $form->populate(['q' => $name, 'q-type' => $type, 'q-backend' => $backend]);
+ $form->setInputLabel(t('Enter user or group name'));
+ $form->setSubmitLabel(t('Inspect'));
+ $form->setSuggestionUrl(Url::fromPath(
+ 'role/suggest-role-member',
+ ['_disableLayout' => true, 'showCompact' => true]
+ ));
+
+ $form->on(SingleValueSearchControl::ON_SUCCESS, function ($form) {
+ $type = $form->getValue('q-type') ?: 'user';
+ $params = [$type => $form->getValue('q')];
+
+ if ($type === 'user') {
+ $params['backend'] = $form->getValue('q-backend');
+ }
+
+ $this->redirectNow(Url::fromPath('role/audit', $params));
+ })->handleRequest(ServerRequest::fromGlobals());
+
+ $this->addControl($form);
+
+ if (! $name) {
+ $this->addContent(Html::wantHtml(t('No user or group selected.')));
+ return;
+ }
+
+ if ($type === 'user') {
+ $header = Html::tag('h2', sprintf(t('Privilege Audit for User "%s"'), $name));
+
+ $user = new User($name);
+ $user->setAdditional('backend_name', $backend);
+ Auth::getInstance()->setupUser($user);
+ } else {
+ $header = Html::tag('h2', sprintf(t('Privilege Audit for Group "%s"'), $name));
+
+ $user = new User((string) time());
+ $user->setGroups([$name]);
+ (new AdmissionLoader())->applyRoles($user);
+ }
+
+ $chosenRole = null;
+ $assignedRoles = array_filter($user->getRoles(), function ($role) use ($user, &$chosenRole, $roleName) {
+ if (! in_array($role->getName(), $user->getAdditional('assigned_roles'), true)) {
+ return false;
+ }
+
+ if ($role->getName() === $roleName) {
+ $chosenRole = $role;
+ }
+
+ return true;
+ });
+
+ $this->addControl(Html::tag(
+ 'ul',
+ ['class' => 'privilege-audit-role-control'],
+ [
+ Html::tag('li', $roleName ? null : ['class' => 'active'], new Link(
+ t('All roles'),
+ Url::fromRequest()->without('role'),
+ ['class' => 'button-link', 'title' => t('Show privileges of all roles')]
+ )),
+ array_map(function ($role) use ($roleName) {
+ return Html::tag(
+ 'li',
+ $role->getName() === $roleName ? ['class' => 'active'] : null,
+ new Link(
+ $role->getName(),
+ Url::fromRequest()->setParam('role', $role->getName()),
+ [
+ 'class' => 'button-link',
+ 'title' => sprintf(t('Only show privileges of role %s'), $role->getName())
+ ]
+ )
+ );
+ }, $assignedRoles)
+ ]
+ ));
+
+ $this->addControl($header);
+ $this->addContent(
+ (new PrivilegeAudit($chosenRole !== null ? [$chosenRole] : $assignedRoles))
+ ->addAttributes(['id' => 'role-audit'])
+ );
+ }
+
+ public function suggestRoleMemberAction()
+ {
+ $this->assertHttpMethod('POST');
+ $requestData = $this->getRequest()->getPost();
+ $limit = $this->params->get('limit', 50);
+
+ $searchTerm = $requestData['term']['label'];
+ $userBackends = $this->loadUserBackends(Selectable::class);
+
+ $suggestions = [];
+ while ($limit > 0 && ! empty($userBackends)) {
+ /** @var Repository $backend */
+ $backend = array_shift($userBackends);
+ $query = $backend->select()
+ ->from('user', ['user_name'])
+ ->where('user_name', $searchTerm)
+ ->limit($limit);
+
+ try {
+ $names = $query->fetchColumn();
+ } catch (Exception $e) {
+ continue;
+ }
+
+ $domain = '';
+ if ($backend instanceof DomainAwareInterface) {
+ $domain = '@' . $backend->getDomain();
+ }
+
+ $users = [];
+ foreach ($names as $name) {
+ $users[] = [$name . $domain, [
+ 'type' => 'user',
+ 'backend' => $backend->getName()
+ ]];
+ }
+
+ if (! empty($users)) {
+ $suggestions[] = [
+ [
+ t('Users'),
+ HtmlString::create('&nbsp;'),
+ Html::tag('span', ['class' => 'badge'], $backend->getName())
+ ],
+ $users
+ ];
+ }
+
+ $limit -= count($names);
+ }
+
+ $groupBackends = $this->loadUserGroupBackends(Selectable::class);
+
+ while ($limit > 0 && ! empty($groupBackends)) {
+ /** @var Repository $backend */
+ $backend = array_shift($groupBackends);
+ $query = $backend->select()
+ ->from('group', ['group_name'])
+ ->where('group_name', $searchTerm)
+ ->limit($limit);
+
+ try {
+ $names = $query->fetchColumn();
+ } catch (Exception $e) {
+ continue;
+ }
+
+ $groups = [];
+ foreach ($names as $name) {
+ $groups[] = [$name, ['type' => 'group']];
+ }
+
+ if (! empty($groups)) {
+ $suggestions[] = [
+ [
+ t('Groups'),
+ HtmlString::create('&nbsp;'),
+ Html::tag('span', ['class' => 'badge'], $backend->getName())
+ ],
+ $groups
+ ];
+ }
+
+ $limit -= count($names);
+ }
+
+ if (empty($suggestions)) {
+ $suggestions[] = [t('Your search does not match any user or group'), []];
+ }
+
+ $this->document->add(SingleValueSearchControl::createSuggestions($suggestions));
+ }
+
+ /**
+ * Create the tabs to display when listing roles
+ */
+ protected function createListTabs()
+ {
+ $tabs = $this->getTabs();
+ $tabs->add(
+ 'role/list',
+ array(
+ 'baseTarget' => '_main',
+ 'label' => $this->translate('Roles'),
+ 'title' => $this->translate(
+ 'Configure roles to permit or restrict users and groups accessing Icinga Web 2'
+ ),
+ 'url' => 'role/list'
+ )
+ );
+
+ $tabs->add(
+ 'role/audit',
+ [
+ 'title' => $this->translate('Audit a user\'s or group\'s privileges'),
+ 'label' => $this->translate('Audit'),
+ 'url' => 'role/audit',
+ 'baseTarget' => '_main'
+ ]
+ );
+
+ if ($this->hasPermission('config/access-control/users')) {
+ $tabs->add(
+ 'user/list',
+ array(
+ 'title' => $this->translate('List users of authentication backends'),
+ 'label' => $this->translate('Users'),
+ 'url' => 'user/list'
+ )
+ );
+ }
+
+ if ($this->hasPermission('config/access-control/groups')) {
+ $tabs->add(
+ 'group/list',
+ array(
+ 'title' => $this->translate('List groups of user group backends'),
+ 'label' => $this->translate('User Groups'),
+ 'url' => 'group/list'
+ )
+ );
+ }
+
+ return $tabs;
+ }
+}
diff --git a/application/controllers/SearchController.php b/application/controllers/SearchController.php
new file mode 100644
index 0000000..92aeabe
--- /dev/null
+++ b/application/controllers/SearchController.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Web\Controller\ActionController;
+use Icinga\Web\Widget;
+use Icinga\Web\Widget\SearchDashboard;
+
+/**
+ * Search controller
+ */
+class SearchController extends ActionController
+{
+ public function indexAction()
+ {
+ $searchDashboard = new SearchDashboard();
+ $searchDashboard->setUser($this->Auth()->getUser());
+ $this->view->dashboard = $searchDashboard->search($this->params->get('q'));
+
+ // NOTE: This renders the dashboard twice. Remove this once we can catch exceptions thrown in view scripts.
+ $this->view->dashboard->render();
+ }
+
+ public function hintAction()
+ {
+ }
+}
diff --git a/application/controllers/StaticController.php b/application/controllers/StaticController.php
new file mode 100644
index 0000000..6e43476
--- /dev/null
+++ b/application/controllers/StaticController.php
@@ -0,0 +1,114 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Controller;
+use Icinga\Web\FileCache;
+
+/**
+ * Deliver static content to clients
+ */
+class StaticController extends Controller
+{
+ /**
+ * Static routes don't require authentication
+ *
+ * @var bool
+ */
+ protected $requiresAuthentication = false;
+
+ /**
+ * Disable layout rendering as this controller doesn't provide any html layouts
+ */
+ public function init()
+ {
+ $this->_helper->viewRenderer->setNoRender(true);
+ $this->_helper->layout()->disableLayout();
+ }
+
+ public function gravatarAction()
+ {
+ $response = $this->getResponse();
+ $response->setHeader('Cache-Control', 'public, max-age=1814400, stale-while-revalidate=604800', true);
+ $response->setHeader('Content-Type', 'image/png', true);
+
+ $noCache = $this->getRequest()->getHeader('Cache-Control') === 'no-cache'
+ || $this->getRequest()->getHeader('Pragma') === 'no-cache';
+
+ $cache = FileCache::instance();
+ $filename = md5(strtolower(trim($this->getParam('email'))));
+ $cacheFile = 'gravatar-' . $filename;
+
+ if (! $noCache && $cache->has($cacheFile, time() - 1814400)) {
+ if ($cache->etagMatchesCachedFile($cacheFile)) {
+ $response->setHttpResponseCode(304);
+ return;
+ }
+
+ $response->setHeader('Content-Type', 'image/jpg', true);
+ $response->setHeader('ETag', sprintf('"%s"', $cache->etagForCachedFile($cacheFile)));
+ $cache->send($cacheFile);
+ return;
+ }
+
+ $img = @file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=120&d=mm');
+ if ($img === false) {
+ $this->httpNotFound('Unable to connect to gravatar.com');
+ }
+
+ $cache->store($cacheFile, $img);
+ $response->setHeader('ETag', sprintf('"%s"', $cache->etagForCachedFile($cacheFile)));
+
+ echo $img;
+ }
+
+ /**
+ * Return an image from a module's public folder
+ */
+ public function imgAction()
+ {
+ $imgRoot = Icinga::app()
+ ->getModuleManager()
+ ->getModule($this->getParam('module_name'))
+ ->getBaseDir() . '/public/img/';
+
+ $file = $this->getParam('file');
+ $filePath = realpath($imgRoot . $file);
+
+ if ($filePath === false || substr($filePath, 0, strlen($imgRoot)) !== $imgRoot) {
+ $this->httpNotFound('%s does not exist', $file);
+ }
+
+ if (preg_match('/\.([a-z]+)$/i', $file, $m)) {
+ $extension = $m[1];
+ if ($extension === 'svg') {
+ $extension = 'svg+xml';
+ }
+ } else {
+ $extension = 'fixme';
+ }
+
+ $s = stat($filePath);
+ $eTag = sprintf('%x-%x-%x', $s['ino'], $s['size'], (float) str_pad($s['mtime'], 16, '0'));
+
+ $this->getResponse()->setHeader(
+ 'Cache-Control',
+ 'public, max-age=1814400, stale-while-revalidate=604800',
+ true
+ );
+
+ if ($this->getRequest()->getServer('HTTP_IF_NONE_MATCH') === $eTag) {
+ $this->getResponse()
+ ->setHttpResponseCode(304);
+ } else {
+ $this->getResponse()
+ ->setHeader('ETag', $eTag)
+ ->setHeader('Content-Type', 'image/' . $extension, true)
+ ->setHeader('Last-Modified', gmdate('D, d M Y H:i:s', $s['mtime']) . ' GMT');
+
+ readfile($filePath);
+ }
+ }
+}
diff --git a/application/controllers/UserController.php b/application/controllers/UserController.php
new file mode 100644
index 0000000..dac80d3
--- /dev/null
+++ b/application/controllers/UserController.php
@@ -0,0 +1,374 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Authentication\AdmissionLoader;
+use Icinga\Authentication\User\DomainAwareInterface;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\Config\User\CreateMembershipForm;
+use Icinga\Forms\Config\User\UserForm;
+use Icinga\User;
+use Icinga\Web\Controller\AuthBackendController;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+use Icinga\Web\Url;
+use Icinga\Web\Widget;
+
+class UserController extends AuthBackendController
+{
+ public function init()
+ {
+ $this->view->title = $this->translate('Users');
+
+ parent::init();
+ }
+
+ /**
+ * List all users of a single backend
+ */
+ public function listAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $this->createListTabs()->activate('user/list');
+ $backendNames = array_map(
+ function ($b) {
+ return $b->getName();
+ },
+ $this->loadUserBackends('Icinga\Data\Selectable')
+ );
+ if (empty($backendNames)) {
+ return;
+ }
+
+ $this->view->backendSelection = new Form();
+ $this->view->backendSelection->setAttrib('class', 'backend-selection icinga-controls');
+ $this->view->backendSelection->setUidDisabled();
+ $this->view->backendSelection->setMethod('GET');
+ $this->view->backendSelection->setTokenDisabled();
+ $this->view->backendSelection->addElement(
+ 'select',
+ 'backend',
+ array(
+ 'autosubmit' => true,
+ 'label' => $this->translate('User Backend'),
+ 'multiOptions' => array_combine($backendNames, $backendNames),
+ 'value' => $this->params->get('backend')
+ )
+ );
+
+ $backend = $this->getUserBackend($this->params->get('backend'));
+ if ($backend === null) {
+ $this->view->backend = null;
+ return;
+ }
+
+ $query = $backend->select(array('user_name'));
+
+ $this->view->users = $query;
+ $this->view->backend = $backend;
+
+ $this->setupPaginationControl($query);
+ $this->setupFilterControl($query);
+ $this->setupLimitControl();
+ $this->setupSortControl(
+ array(
+ 'user_name' => $this->translate('Username'),
+ 'is_active' => $this->translate('Active'),
+ 'created_at' => $this->translate('Created at'),
+ 'last_modified' => $this->translate('Last modified')
+ ),
+ $query
+ );
+ }
+
+ /**
+ * Show a user
+ */
+ public function showAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $userName = $this->params->getRequired('user');
+ $backend = $this->getUserBackend($this->params->getRequired('backend'));
+
+ $user = $backend->select(array(
+ 'user_name',
+ 'is_active',
+ 'created_at',
+ 'last_modified'
+ ))->where('user_name', $userName)->fetchRow();
+ if ($user === false) {
+ $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
+ }
+
+ $userObj = new User($userName);
+ if ($backend instanceof DomainAwareInterface) {
+ $userObj->setDomain($backend->getDomain());
+ }
+
+ $memberships = $this->loadMemberships($userObj)->select();
+
+ $this->setupFilterControl(
+ $memberships,
+ array('group_name' => t('User Group')),
+ array('group'),
+ array('user')
+ );
+ $this->setupPaginationControl($memberships);
+ $this->setupLimitControl();
+ $this->setupSortControl(
+ array(
+ 'group_name' => $this->translate('Group')
+ ),
+ $memberships
+ );
+
+ if ($this->hasPermission('config/access-control/groups')) {
+ $extensibleBackends = $this->loadUserGroupBackends('Icinga\Data\Extensible');
+ $this->view->showCreateMembershipLink = ! empty($extensibleBackends);
+ } else {
+ $this->view->showCreateMembershipLink = false;
+ }
+
+ $this->view->user = $user;
+ $this->view->backend = $backend;
+ $this->view->memberships = $memberships;
+ $this->createShowTabs($backend->getName(), $userName)->activate('user/show');
+
+ if ($this->hasPermission('config/access-control/groups')) {
+ $removeForm = new Form();
+ $removeForm->setUidDisabled();
+ $removeForm->setAttrib('class', 'inline');
+ $removeForm->addElement('hidden', 'user_name', array(
+ 'isArray' => true,
+ 'value' => $userName,
+ 'decorators' => array('ViewHelper')
+ ));
+ $removeForm->addElement('hidden', 'redirect', array(
+ 'value' => Url::fromPath('user/show', array(
+ 'backend' => $backend->getName(),
+ 'user' => $userName
+ )),
+ 'decorators' => array('ViewHelper')
+ ));
+ $removeForm->addElement('button', 'btn_submit', array(
+ 'escape' => false,
+ 'type' => 'submit',
+ 'class' => 'link-button spinner',
+ 'value' => 'btn_submit',
+ 'decorators' => array('ViewHelper'),
+ 'label' => $this->view->icon('trash'),
+ 'title' => $this->translate('Cancel this membership')
+ ));
+ $this->view->removeForm = $removeForm;
+ }
+
+ $admissionLoader = new AdmissionLoader();
+ $admissionLoader->applyRoles($userObj);
+ $this->view->userObj = $userObj;
+ $this->view->allowedToEditRoles = $this->hasPermission('config/access-control/groups');
+ }
+
+ /**
+ * Add a user
+ */
+ public function addAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Extensible');
+ $form = new UserForm();
+ $form->setRedirectUrl(Url::fromPath('user/list', array('backend' => $backend->getName())));
+ $form->setRepository($backend);
+ $form->add()->handleRequest();
+
+ $this->renderForm($form, $this->translate('New User'));
+ }
+
+ /**
+ * Edit a user
+ */
+ public function editAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $userName = $this->params->getRequired('user');
+ $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Updatable');
+
+ $form = new UserForm();
+ $form->setRedirectUrl(Url::fromPath('user/show', array('backend' => $backend->getName(), 'user' => $userName)));
+ $form->setRepository($backend);
+
+ try {
+ $form->edit($userName)->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
+ }
+
+ $this->renderForm($form, $this->translate('Update User'));
+ }
+
+ /**
+ * Remove a user
+ */
+ public function removeAction()
+ {
+ $this->assertPermission('config/access-control/users');
+ $userName = $this->params->getRequired('user');
+ $backend = $this->getUserBackend($this->params->getRequired('backend'), 'Icinga\Data\Reducible');
+
+ $form = new UserForm();
+ $form->setRedirectUrl(Url::fromPath('user/list', array('backend' => $backend->getName())));
+ $form->setRepository($backend);
+
+ try {
+ $form->remove($userName)->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
+ }
+
+ $this->renderForm($form, $this->translate('Remove User'));
+ }
+
+ /**
+ * Create a membership for a user
+ */
+ public function createmembershipAction()
+ {
+ $this->assertPermission('config/access-control/groups');
+ $userName = $this->params->getRequired('user');
+ $backend = $this->getUserBackend($this->params->getRequired('backend'));
+
+ if ($backend->select()->where('user_name', $userName)->count() === 0) {
+ $this->httpNotFound(sprintf($this->translate('User "%s" not found'), $userName));
+ }
+
+ $backends = $this->loadUserGroupBackends('Icinga\Data\Extensible');
+ if (empty($backends)) {
+ throw new ConfigurationError($this->translate(
+ 'You\'ll need to configure at least one user group backend first that allows to create new memberships'
+ ));
+ }
+
+ $form = new CreateMembershipForm();
+ $form->setBackends($backends)
+ ->setUsername($userName)
+ ->setRedirectUrl(Url::fromPath('user/show', array('backend' => $backend->getName(), 'user' => $userName)))
+ ->handleRequest();
+
+ $this->renderForm($form, $this->translate('Create New Membership'));
+ }
+
+ /**
+ * Fetch and return the given user's groups from all user group backends
+ *
+ * @param User $user
+ *
+ * @return ArrayDatasource
+ */
+ protected function loadMemberships(User $user)
+ {
+ $groups = $alreadySeen = array();
+ foreach ($this->loadUserGroupBackends() as $backend) {
+ try {
+ foreach ($backend->getMemberships($user) as $groupName) {
+ if (array_key_exists($groupName, $alreadySeen)) {
+ continue; // Ignore duplicate memberships
+ }
+
+ $alreadySeen[$groupName] = null;
+ $groups[] = (object) array(
+ 'group_name' => $groupName,
+ 'group' => $groupName,
+ 'backend' => $backend
+ );
+ }
+ } catch (Exception $e) {
+ Logger::error($e);
+ Notification::warning(sprintf(
+ $this->translate('Failed to fetch memberships from backend %s. Please check your log'),
+ $backend->getName()
+ ));
+ }
+ }
+
+ return new ArrayDatasource($groups);
+ }
+
+ /**
+ * Create the tabs to display when showing a user
+ *
+ * @param string $backendName
+ * @param string $userName
+ */
+ protected function createShowTabs($backendName, $userName)
+ {
+ $tabs = $this->getTabs();
+ $tabs->add(
+ 'user/show',
+ array(
+ 'title' => sprintf($this->translate('Show user %s'), $userName),
+ 'label' => $this->translate('User'),
+ 'url' => Url::fromPath('user/show', array('backend' => $backendName, 'user' => $userName))
+ )
+ );
+
+ return $tabs;
+ }
+
+ /**
+ * Create the tabs to display when listing users
+ */
+ protected function createListTabs()
+ {
+ $tabs = $this->getTabs();
+
+ if ($this->hasPermission('config/access-control/roles')) {
+ $tabs->add(
+ 'role/list',
+ array(
+ 'baseTarget' => '_main',
+ 'label' => $this->translate('Roles'),
+ 'title' => $this->translate(
+ 'Configure roles to permit or restrict users and groups accessing Icinga Web 2'
+ ),
+ 'url' => 'role/list'
+ )
+ );
+
+ $tabs->add(
+ 'role/audit',
+ [
+ 'title' => $this->translate('Audit a user\'s or group\'s privileges'),
+ 'label' => $this->translate('Audit'),
+ 'url' => 'role/audit',
+ 'baseTarget' => '_main'
+ ]
+ );
+ }
+
+ $tabs->add(
+ 'user/list',
+ array(
+ 'title' => $this->translate('List users of authentication backends'),
+ 'label' => $this->translate('Users'),
+ 'url' => 'user/list'
+ )
+ );
+
+ if ($this->hasPermission('config/access-control/groups')) {
+ $tabs->add(
+ 'group/list',
+ array(
+ 'title' => $this->translate('List groups of user group backends'),
+ 'label' => $this->translate('User Groups'),
+ 'url' => 'group/list'
+ )
+ );
+ }
+
+ return $tabs;
+ }
+}
diff --git a/application/controllers/UsergroupbackendController.php b/application/controllers/UsergroupbackendController.php
new file mode 100644
index 0000000..a96ab75
--- /dev/null
+++ b/application/controllers/UsergroupbackendController.php
@@ -0,0 +1,133 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Controllers;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\Config\UserGroup\UserGroupBackendForm;
+use Icinga\Forms\ConfirmRemovalForm;
+use Icinga\Web\Controller;
+use Icinga\Web\Notification;
+
+/**
+ * Controller to configure user group backends
+ */
+class UsergroupbackendController extends Controller
+{
+ /**
+ * Initialize this controller
+ */
+ public function init()
+ {
+ $this->assertPermission('config/access-control/users');
+ }
+
+ /**
+ * Redirect to this controller's list action
+ */
+ public function indexAction()
+ {
+ $this->redirectNow('config/userbackend');
+ }
+
+ /**
+ * Create a new user group backend
+ */
+ public function createAction()
+ {
+ $form = new UserGroupBackendForm();
+ $form->setRedirectUrl('config/userbackend');
+ $form->addDescription($this->translate('Create a new backend to associate users and groups with.'));
+ $form->setIniConfig(Config::app('groups'));
+ $form->setOnSuccess(function (UserGroupBackendForm $form) {
+ try {
+ $form->add($form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(t('User group backend successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->title = $this->translate('Authentication');
+ $this->renderForm($form, $this->translate('New User Group Backend'));
+ }
+
+ /**
+ * Edit an user group backend
+ */
+ public function editAction()
+ {
+ $backendName = $this->params->getRequired('backend');
+
+ $form = new UserGroupBackendForm();
+ $form->setRedirectUrl('config/userbackend');
+ $form->setIniConfig(Config::app('groups'));
+ $form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) {
+ try {
+ $form->edit($backendName, $form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(sprintf(t('User group backend "%s" successfully updated'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($backendName);
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('User group backend "%s" not found'), $backendName));
+ }
+
+ $this->view->title = $this->translate('Authentication');
+ $this->renderForm($form, $this->translate('Update User Group Backend'));
+ }
+
+ /**
+ * Remove a user group backend
+ */
+ public function removeAction()
+ {
+ $backendName = $this->params->getRequired('backend');
+
+ $backendForm = new UserGroupBackendForm();
+ $backendForm->setIniConfig(Config::app('groups'));
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('config/userbackend');
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) {
+ try {
+ $backendForm->delete($backendName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($backendForm->save()) {
+ Notification::success(sprintf(t('User group backend "%s" successfully removed'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->title = $this->translate('Authentication');
+ $this->renderForm($form, $this->translate('Remove User Group Backend'));
+ }
+}
diff --git a/application/fonts/fontello-ifont/LICENSE.txt b/application/fonts/fontello-ifont/LICENSE.txt
new file mode 100644
index 0000000..b4cfe3f
--- /dev/null
+++ b/application/fonts/fontello-ifont/LICENSE.txt
@@ -0,0 +1,57 @@
+Font license info
+
+
+## Font Awesome
+
+ Copyright (C) 2016 by Dave Gandy
+
+ Author: Dave Gandy
+ License: SIL ()
+ Homepage: http://fortawesome.github.com/Font-Awesome/
+
+
+## Iconic
+
+ Copyright (C) 2012 by P.J. Onori
+
+ Author: P.J. Onori
+ License: SIL (http://scripts.sil.org/OFL)
+ Homepage: http://somerandomdude.com/work/iconic/
+
+
+## Entypo
+
+ Copyright (C) 2012 by Daniel Bruce
+
+ Author: Daniel Bruce
+ License: SIL (http://scripts.sil.org/OFL)
+ Homepage: http://www.entypo.com
+
+
+## Fontelico
+
+ Copyright (C) 2012 by Fontello project
+
+ Author: Crowdsourced, for Fontello project
+ License: SIL (http://scripts.sil.org/OFL)
+ Homepage: http://fontello.com
+
+
+## Typicons
+
+ (c) Stephen Hutchings 2012
+
+ Author: Stephen Hutchings
+ License: SIL (http://scripts.sil.org/OFL)
+ Homepage: http://typicons.com/
+
+
+## MFG Labs
+
+ Copyright (C) 2012 by Daniel Bruce
+
+ Author: MFG Labs
+ License: SIL (http://scripts.sil.org/OFL)
+ Homepage: http://www.mfglabs.com/
+
+
diff --git a/application/fonts/fontello-ifont/README.txt b/application/fonts/fontello-ifont/README.txt
new file mode 100644
index 0000000..beaab33
--- /dev/null
+++ b/application/fonts/fontello-ifont/README.txt
@@ -0,0 +1,75 @@
+This webfont is generated by http://fontello.com open source project.
+
+
+================================================================================
+Please, note, that you should obey original font licenses, used to make this
+webfont pack. Details available in LICENSE.txt file.
+
+- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
+ site in "About" section.
+
+- If your project is open-source, usually, it will be ok to make LICENSE.txt
+ file publicly available in your repository.
+
+- Fonts, used in Fontello, don't require a clickable link on your site.
+ But any kind of additional authors crediting is welcome.
+================================================================================
+
+
+Comments on archive content
+---------------------------
+
+- /font/* - fonts in different formats
+
+- /css/* - different kinds of css, for all situations. Should be ok with
+ twitter bootstrap. Also, you can skip <i> style and assign icon classes
+ directly to text elements, if you don't mind about IE7.
+
+- demo.html - demo file, to show your webfont content
+
+- LICENSE.txt - license info about source fonts, used to build your one.
+
+- config.json - keeps your settings. You can import it back into fontello
+ anytime, to continue your work
+
+
+Why so many CSS files ?
+-----------------------
+
+Because we like to fit all your needs :)
+
+- basic file, <your_font_name>.css - is usually enough, it contains @font-face
+ and character code definitions
+
+- *-ie7.css - if you need IE7 support, but still don't wish to put char codes
+ directly into html
+
+- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
+ rules, but still wish to benefit from css generation. That can be very
+ convenient for automated asset build systems. When you need to update font -
+ no need to manually edit files, just override old version with archive
+ content. See fontello source code for examples.
+
+- *-embedded.css - basic css file, but with embedded WOFF font, to avoid
+ CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
+ We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
+ server headers. But if you ok with dirty hack - this file is for you. Note,
+ that data url moved to separate @font-face to avoid problems with <IE9, when
+ string is too long.
+
+- animate.css - use it to get ideas about spinner rotation animation.
+
+
+Attention for server setup
+--------------------------
+
+You MUST setup server to reply with proper `mime-types` for font files -
+otherwise some browsers will fail to show fonts.
+
+Usually, `apache` already has necessary settings, but `nginx` and other
+webservers should be tuned. Here is list of mime types for our file extensions:
+
+- `application/vnd.ms-fontobject` - eot
+- `application/x-font-woff` - woff
+- `application/x-font-ttf` - ttf
+- `image/svg+xml` - svg
diff --git a/application/fonts/fontello-ifont/config.json b/application/fonts/fontello-ifont/config.json
new file mode 100644
index 0000000..a982335
--- /dev/null
+++ b/application/fonts/fontello-ifont/config.json
@@ -0,0 +1,874 @@
+{
+ "name": "ifont",
+ "css_prefix_text": "icon-",
+ "css_use_suffix": false,
+ "hinting": true,
+ "units_per_em": 1000,
+ "ascent": 850,
+ "glyphs": [
+ {
+ "uid": "9bc2902722abb366a213a052ade360bc",
+ "css": "spin6",
+ "code": 59508,
+ "src": "fontelico"
+ },
+ {
+ "uid": "9dd9e835aebe1060ba7190ad2b2ed951",
+ "css": "search",
+ "code": 59484,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "8b80d36d4ef43889db10bc1f0dc9a862",
+ "css": "user",
+ "code": 59393,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "31972e4e9d080eaa796290349ae6c1fd",
+ "css": "users",
+ "code": 59394,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "b1887b423d2fd15c345e090320c91ca0",
+ "css": "dashboard",
+ "code": 59392,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ce3cf091d6ebd004dd0b52d24074e6e3",
+ "css": "help",
+ "code": 59483,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3d4ea8a78dc34efe891f3a0f3d961274",
+ "css": "info",
+ "code": 59482,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "d7271d490b71df4311e32cdacae8b331",
+ "css": "home",
+ "code": 59481,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "0d6ab6194c0eddda2b8c9cedf2ab248e",
+ "css": "attach",
+ "code": 59498,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "c1f1975c885aa9f3dad7810c53b82074",
+ "css": "lock",
+ "code": 59480,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "657ab647f6248a6b57a5b893beaf35a9",
+ "css": "lock-open",
+ "code": 59479,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "05376be04a27d5a46e855a233d6e8508",
+ "css": "lock-open-alt",
+ "code": 59478,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "c5fd349cbd3d23e4ade333789c29c729",
+ "css": "eye",
+ "code": 59475,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7fd683b2c518ceb9e5fa6757f2276faa",
+ "css": "eye-off",
+ "code": 59491,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3db5347bd219f3bce6025780f5d9ef45",
+ "css": "tag",
+ "code": 59476,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "a3f89e106175a5c5c4e9738870b12e55",
+ "css": "tags",
+ "code": 59477,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "acf41aa4018e58d49525665469e35665",
+ "css": "thumbs-up",
+ "code": 59495,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7533e68038fc6d520ede7a7ffa0a2f64",
+ "css": "thumbs-down",
+ "code": 59496,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
+ "css": "download",
+ "code": 59400,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "eeec3208c90b7b48e804919d0d2d4a41",
+ "css": "upload",
+ "code": 59401,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "c6be5a58ee4e63a5ec399c2b0d15cf2c",
+ "css": "reply",
+ "code": 59473,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "1b5597a3bacaeca6600e88ae36d02e0a",
+ "css": "reply-all",
+ "code": 59474,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3d39c828009c04ddb6764c0b04cd2439",
+ "css": "forward",
+ "code": 59472,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "41087bc74d4b20b55059c60a33bf4008",
+ "css": "edit",
+ "code": 59471,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7277ded7695b2a307a5f9d50097bb64c",
+ "css": "print",
+ "code": 59470,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ecb97add13804c190456025e43ec003b",
+ "css": "keyboard",
+ "code": 59499,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "85528017f1e6053b2253785c31047f44",
+ "css": "comment",
+ "code": 59464,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
+ "css": "chat",
+ "code": 59465,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9c1376672bb4f1ed616fdd78a23667e9",
+ "css": "comment-empty",
+ "code": 59463,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "31951fbb9820ed0690f675b3d495c8da",
+ "css": "chat-empty",
+ "code": 59466,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "cd21cbfb28ad4d903cede582157f65dc",
+ "css": "bell",
+ "code": 59467,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "671f29fa10dda08074a4c6a341bb4f39",
+ "css": "bell-alt",
+ "code": 59468,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "563683020e0bf9f22f3f055a69b5c57a",
+ "css": "bell-off",
+ "code": 59488,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "8a074400a056c59d389f2d0517281bd5",
+ "css": "bell-off-empty",
+ "code": 59489,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "00391fac5d419345ffcccd95b6f76263",
+ "css": "attention-alt",
+ "code": 59469,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
+ "css": "trash",
+ "code": 59462,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "5408be43f7c42bccee419c6be53fdef5",
+ "css": "doc-text",
+ "code": 59461,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9daa1fdf0838118518a7e22715e83abc",
+ "css": "file-pdf",
+ "code": 59458,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "310ffd629da85142bc8669f010556f2d",
+ "css": "file-word",
+ "code": 59459,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "f761c3bbe16ba2d332914ecb28e7a042",
+ "css": "file-excel",
+ "code": 59460,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9f7e588c66cfd6891f6f507cf6f6596b",
+ "css": "phone",
+ "code": 59457,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "559647a6f430b3aeadbecd67194451dd",
+ "css": "menu",
+ "code": 59500,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "e99461abfef3923546da8d745372c995",
+ "css": "service",
+ "code": 59456,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "98687378abd1faf8f6af97c254eb6cd6",
+ "css": "services",
+ "code": 59455,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "5bb103cd29de77e0e06a52638527b575",
+ "css": "wrench",
+ "code": 59453,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "21b42d3c3e6be44c3cc3d73042faa216",
+ "css": "sliders",
+ "code": 59454,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "531bc468eecbb8867d822f1c11f1e039",
+ "css": "calendar",
+ "code": 59452,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ead4c82d04d7758db0f076584893a8c1",
+ "css": "calendar-empty",
+ "code": 59451,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3a00327e61b997b58518bd43ed83c3df",
+ "css": "endtime",
+ "code": 59449,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "0d20938846444af8deb1920dc85a29fb",
+ "css": "starttime",
+ "code": 59450,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "19c50c52858a81de58f9db488aba77bc",
+ "css": "mic",
+ "code": 59448,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "43c629249e2cca7e73cd4ef410c9551f",
+ "css": "mute",
+ "code": 59447,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "e44601720c64e6bb6a2d5cba6b0c588c",
+ "css": "volume-off",
+ "code": 59446,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "fee6e00f36e8ca8ef3e4a62caa213bf6",
+ "css": "volume-down",
+ "code": 59445,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "76857a03fbaa6857fe063b6c25aa98ed",
+ "css": "volume-up",
+ "code": 59444,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "598a5f2bcf3521d1615de8e1881ccd17",
+ "css": "clock",
+ "code": 59443,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "5278ef7773e948d56c4d442c8c8c98cf",
+ "css": "lightbulb",
+ "code": 59442,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "98d9c83c1ee7c2c25af784b518c522c5",
+ "css": "block",
+ "code": 59440,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "e594fc6e5870b4ab7e49f52571d52577",
+ "css": "resize-full",
+ "code": 59434,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "b013f6403e5ab0326614e68d1850fd6b",
+ "css": "resize-full-alt",
+ "code": 59433,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3c24ee33c9487bbf18796ca6dffa1905",
+ "css": "resize-small",
+ "code": 59435,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
+ "css": "resize-vertical",
+ "code": 59438,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3c73d058e4589b65a8d959c0fc8f153d",
+ "css": "resize-horizontal",
+ "code": 59437,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "6605ee6441bf499ffa3c63d3c7409471",
+ "css": "move",
+ "code": 59436,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "0b2b66e526028a6972d51a6f10281b4b",
+ "css": "zoom-in",
+ "code": 59439,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "d25d10efa900f529ad1d275657cfd30e",
+ "css": "zoom-out",
+ "code": 59441,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "2d6150442079cbda7df64522dc24f482",
+ "css": "down-dir",
+ "code": 59421,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "80cd1022bd9ea151d554bec1fa05f2de",
+ "css": "up-dir",
+ "code": 59422,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9dc654095085167524602c9acc0c5570",
+ "css": "left-dir",
+ "code": 59423,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "fb1c799ffe5bf8fb7f8bcb647c8fe9e6",
+ "css": "right-dir",
+ "code": 59424,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ccddff8e8670dcd130e3cb55fdfc2fd0",
+ "css": "down-open",
+ "code": 59425,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "d870630ff8f81e6de3958ecaeac532f2",
+ "css": "left-open",
+ "code": 59428,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "399ef63b1e23ab1b761dfbb5591fa4da",
+ "css": "right-open",
+ "code": 59426,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "fe6697b391355dec12f3d86d6d490397",
+ "css": "up-open",
+ "code": 59427,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "745f12abe1472d14f8f658de7e5aba66",
+ "css": "angle-double-left",
+ "code": 59514,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "fdfbd1fcbd4cb229716a810801a5f207",
+ "css": "angle-double-right",
+ "code": 59515,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "1c4068ed75209e21af36017df8871802",
+ "css": "down-big",
+ "code": 59432,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "555ef8c86832e686fef85f7af2eb7cde",
+ "css": "left-big",
+ "code": 59431,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ad6b3fbb5324abe71a9c0b6609cbb9f1",
+ "css": "right-big",
+ "code": 59430,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "95376bf082bfec6ce06ea1cda7bd7ead",
+ "css": "up-big",
+ "code": 59429,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "d407a4707f719b042ed2ad28d2619d7e",
+ "css": "barchart",
+ "code": 59420,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "cd4bfdae4dc89b175ff49330ce29611a",
+ "css": "wifi",
+ "code": 59501,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "500fc1f109021e4b1de4deda2f7ed399",
+ "css": "host",
+ "code": 59494,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "197375a3cea8cb90b02d06e4ddf1433d",
+ "css": "globe",
+ "code": 59417,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "2c413e78faf1d6631fd7b094d14c2253",
+ "css": "cloud",
+ "code": 59418,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3212f42c65d41ed91cb435d0490e29ed",
+ "css": "flash",
+ "code": 59419,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "567e3e257f2cc8fba2c12bf691c9f2d8",
+ "css": "moon",
+ "code": 59502,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "8772331a9fec983cdb5d72902a6f9e0e",
+ "css": "scissors",
+ "code": 59416,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "b429436ec5a518c78479d44ef18dbd60",
+ "css": "paste",
+ "code": 59415,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
+ "css": "off",
+ "code": 59413,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9755f76110ae4d12ac5f9466c9152031",
+ "css": "book",
+ "code": 59414,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "130380e481a7defc690dfb24123a1f0c",
+ "css": "circle",
+ "code": 59516,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "266d5d9adf15a61800477a5acf9a4462",
+ "css": "chart-bar",
+ "code": 59505,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7d1ca956f4181a023de4b9efbed92524",
+ "css": "chart-area",
+ "code": 59504,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "554ee96588a6c9ee632624cd051fe6fc",
+ "css": "chart-pie",
+ "code": 59503,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ea2d9a8c51ca42b38ef0d2a07f16d9a7",
+ "css": "chart-line",
+ "code": 59487,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3e674995cacc2b09692c096ea7eb6165",
+ "css": "megaphone",
+ "code": 59409,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7432077e6a2d6aa19984ca821bb6bbda",
+ "css": "bug",
+ "code": 59410,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "9396b2d8849e0213a0f11c5fd7fcc522",
+ "css": "tasks",
+ "code": 59411,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "4109c474ff99cad28fd5a2c38af2ec6f",
+ "css": "filter",
+ "code": 59412,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "0f444c61b0d2c9966016d7ddb12f5837",
+ "css": "beaker",
+ "code": 59506,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "ff70f7b3228702e0d590e60ed3b90bea",
+ "css": "magic",
+ "code": 59507,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "3ed68ae14e9cde775121954242a412b2",
+ "css": "sort-name-up",
+ "code": 59407,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "6586267200a42008a9fc0a1bf7ac06c7",
+ "css": "sort-name-down",
+ "code": 59408,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "0bda4bc779d4c32623dec2e43bd67ee8",
+ "css": "gauge",
+ "code": 59405,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "6fe95ffc3c807e62647d4f814a96e0d7",
+ "css": "sitemap",
+ "code": 59406,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "cda0cdcfd38f5f1d9255e722dad42012",
+ "css": "spinner",
+ "code": 59497,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "af95ef0ddda80a78828c62d386506433",
+ "css": "cubes",
+ "code": 59403,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "347c38a8b96a509270fdcabc951e7571",
+ "css": "database",
+ "code": 59404,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "a14be0c7e0689076e2bdde97f8e309f9",
+ "css": "plug",
+ "code": 59490,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "4743b088aa95d6f3b6b990e770d3b647",
+ "css": "facebook-squared",
+ "code": 59519,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "e7cb72a17f3b21e3576f35c3f0a7639b",
+ "css": "git",
+ "code": 59402,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "f0cf7db1b03cb65adc450aa3bdaf8c4d",
+ "css": "gplus-squared",
+ "code": 59520,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "627abcdb627cb1789e009c08e2678ef9",
+ "css": "twitter",
+ "code": 59518,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7e4164950ffa4990961958b2d6318658",
+ "css": "info-circled",
+ "code": 59517,
+ "src": "entypo"
+ },
+ {
+ "uid": "465bb89b6f204234e5787c326b4ae54c",
+ "css": "rewind",
+ "code": 59486,
+ "src": "entypo"
+ },
+ {
+ "uid": "bb46b15cb78cc4cc05d3d715d522ac4d",
+ "css": "cw",
+ "code": 59493,
+ "src": "entypo"
+ },
+ {
+ "uid": "3bd18d47a12b8709e9f4fe9ead4f7518",
+ "css": "reschedule",
+ "code": 59524,
+ "src": "entypo"
+ },
+ {
+ "uid": "p57wgnf4glngbchbucdi029iptu8oxb8",
+ "css": "pin",
+ "code": 59513,
+ "src": "typicons"
+ },
+ {
+ "uid": "c16a63e911bc47b46dc2a7129d2f0c46",
+ "css": "down-small",
+ "code": 59509,
+ "src": "typicons"
+ },
+ {
+ "uid": "58b78b6ca784d5c3db5beefcd9e18061",
+ "css": "left-small",
+ "code": 59510,
+ "src": "typicons"
+ },
+ {
+ "uid": "877a233d7fdca8a1d82615b96ed0d7a2",
+ "css": "right-small",
+ "code": 59511,
+ "src": "typicons"
+ },
+ {
+ "uid": "62bc6fe2a82e4864e2b94d4c0985ee0c",
+ "css": "up-small",
+ "code": 59512,
+ "src": "typicons"
+ },
+ {
+ "uid": "11e664deed5b2587456a4f9c01d720b6",
+ "css": "cancel",
+ "code": 59396,
+ "src": "iconic"
+ },
+ {
+ "uid": "dbd39eb5a1d67beb54cfcb535e840e0f",
+ "css": "plus",
+ "code": 59397,
+ "src": "iconic"
+ },
+ {
+ "uid": "9559f17a471856ef50ed266e726cfa25",
+ "css": "minus",
+ "code": 59398,
+ "src": "iconic"
+ },
+ {
+ "uid": "13ea1e82d38c7ed614d9ee85e9c42053",
+ "css": "folder-empty",
+ "code": 59399,
+ "src": "iconic"
+ },
+ {
+ "uid": "8f28d948aa6379b1a69d2a090e7531d4",
+ "css": "warning-empty",
+ "code": 59525,
+ "src": "typicons"
+ },
+ {
+ "uid": "d4816c0845aa43767213d45574b3b145",
+ "css": "history",
+ "code": 61914,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "b035c28eba2b35c6ffe92aee8b0df507",
+ "css": "attention-circled",
+ "code": 59521,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "73ffeb70554099177620847206c12457",
+ "css": "binoculars",
+ "code": 61925,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "a73c5deb486c8d66249811642e5d719a",
+ "css": "arrows-cw",
+ "code": 59492,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "dd6c6b221a1088ff8a9b9cd32d0b3dd5",
+ "css": "check",
+ "code": 59523,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "b90d80c250a9bbdd6cd3fe00e6351710",
+ "css": "ok",
+ "code": 59395,
+ "src": "iconic"
+ },
+ {
+ "uid": "37c5ab63f10d7ad0b84d0978dcd0c7a8",
+ "css": "flapping",
+ "code": 59485,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "0f6a2573a7b6df911ed199bb63717e27",
+ "css": "github-circled",
+ "code": 61595,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "5eb43711f62fb4dcbef10d0224c28065",
+ "css": "th-thumb-empty",
+ "code": 61451,
+ "src": "mfglabs"
+ },
+ {
+ "uid": "1b58555745e7378f7634ee7c63eada46",
+ "css": "th-list",
+ "code": 61449,
+ "src": "mfglabs"
+ },
+ {
+ "uid": "63b3012c8cbe3654ba5bea598235aa3a",
+ "css": "angle-double-up",
+ "code": 61698,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "dfec4ffa849d8594c2e4b86f6320b8a6",
+ "css": "angle-double-down",
+ "code": 61699,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "f3f90c8c89795da30f7444634476ea4f",
+ "css": "angle-left",
+ "code": 61700,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "7bf14281af5633a597f85b061ef1cfb9",
+ "css": "angle-right",
+ "code": 61701,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "5de9370846a26947e03f63142a3f1c07",
+ "css": "angle-up",
+ "code": 61702,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "e4dde1992f787163e2e2b534b8c8067d",
+ "css": "angle-down",
+ "code": 61703,
+ "src": "fontawesome"
+ }
+ ]
+} \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/css/animation.css b/application/fonts/fontello-ifont/css/animation.css
new file mode 100644
index 0000000..ac5a956
--- /dev/null
+++ b/application/fonts/fontello-ifont/css/animation.css
@@ -0,0 +1,85 @@
+/*
+ Animation example, for spinners
+*/
+.animate-spin {
+ -moz-animation: spin 2s infinite linear;
+ -o-animation: spin 2s infinite linear;
+ -webkit-animation: spin 2s infinite linear;
+ animation: spin 2s infinite linear;
+ display: inline-block;
+}
+@-moz-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-webkit-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-o-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-ms-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
diff --git a/application/fonts/fontello-ifont/css/ifont-codes.css b/application/fonts/fontello-ifont/css/ifont-codes.css
new file mode 100644
index 0000000..74856f3
--- /dev/null
+++ b/application/fonts/fontello-ifont/css/ifont-codes.css
@@ -0,0 +1,145 @@
+
+.icon-dashboard:before { content: '\e800'; } /* 'î €' */
+.icon-user:before { content: '\e801'; } /* 'î ' */
+.icon-users:before { content: '\e802'; } /* 'î ‚' */
+.icon-ok:before { content: '\e803'; } /* 'î ƒ' */
+.icon-cancel:before { content: '\e804'; } /* 'î „' */
+.icon-plus:before { content: '\e805'; } /* 'î …' */
+.icon-minus:before { content: '\e806'; } /* 'î †' */
+.icon-folder-empty:before { content: '\e807'; } /* 'î ‡' */
+.icon-download:before { content: '\e808'; } /* 'î ˆ' */
+.icon-upload:before { content: '\e809'; } /* 'î ‰' */
+.icon-git:before { content: '\e80a'; } /* 'î Š' */
+.icon-cubes:before { content: '\e80b'; } /* 'î ‹' */
+.icon-database:before { content: '\e80c'; } /* '' */
+.icon-gauge:before { content: '\e80d'; } /* 'î ' */
+.icon-sitemap:before { content: '\e80e'; } /* 'î Ž' */
+.icon-sort-name-up:before { content: '\e80f'; } /* 'î ' */
+.icon-sort-name-down:before { content: '\e810'; } /* 'î ' */
+.icon-megaphone:before { content: '\e811'; } /* 'î ‘' */
+.icon-bug:before { content: '\e812'; } /* 'î ’' */
+.icon-tasks:before { content: '\e813'; } /* 'î “' */
+.icon-filter:before { content: '\e814'; } /* 'î ”' */
+.icon-off:before { content: '\e815'; } /* 'î •' */
+.icon-book:before { content: '\e816'; } /* 'î –' */
+.icon-paste:before { content: '\e817'; } /* 'î —' */
+.icon-scissors:before { content: '\e818'; } /* 'î ˜' */
+.icon-globe:before { content: '\e819'; } /* 'î ™' */
+.icon-cloud:before { content: '\e81a'; } /* 'î š' */
+.icon-flash:before { content: '\e81b'; } /* 'î ›' */
+.icon-barchart:before { content: '\e81c'; } /* '' */
+.icon-down-dir:before { content: '\e81d'; } /* 'î ' */
+.icon-up-dir:before { content: '\e81e'; } /* 'î ž' */
+.icon-left-dir:before { content: '\e81f'; } /* 'î Ÿ' */
+.icon-right-dir:before { content: '\e820'; } /* 'î  ' */
+.icon-down-open:before { content: '\e821'; } /* 'î ¡' */
+.icon-right-open:before { content: '\e822'; } /* 'î ¢' */
+.icon-up-open:before { content: '\e823'; } /* 'î £' */
+.icon-left-open:before { content: '\e824'; } /* 'î ¤' */
+.icon-up-big:before { content: '\e825'; } /* 'î ¥' */
+.icon-right-big:before { content: '\e826'; } /* 'î ¦' */
+.icon-left-big:before { content: '\e827'; } /* 'î §' */
+.icon-down-big:before { content: '\e828'; } /* 'î ¨' */
+.icon-resize-full-alt:before { content: '\e829'; } /* 'î ©' */
+.icon-resize-full:before { content: '\e82a'; } /* 'î ª' */
+.icon-resize-small:before { content: '\e82b'; } /* 'î «' */
+.icon-move:before { content: '\e82c'; } /* 'î ¬' */
+.icon-resize-horizontal:before { content: '\e82d'; } /* 'î ­' */
+.icon-resize-vertical:before { content: '\e82e'; } /* 'î ®' */
+.icon-zoom-in:before { content: '\e82f'; } /* 'î ¯' */
+.icon-block:before { content: '\e830'; } /* 'î °' */
+.icon-zoom-out:before { content: '\e831'; } /* 'î ±' */
+.icon-lightbulb:before { content: '\e832'; } /* 'î ²' */
+.icon-clock:before { content: '\e833'; } /* 'î ³' */
+.icon-volume-up:before { content: '\e834'; } /* 'î ´' */
+.icon-volume-down:before { content: '\e835'; } /* 'î µ' */
+.icon-volume-off:before { content: '\e836'; } /* 'î ¶' */
+.icon-mute:before { content: '\e837'; } /* 'î ·' */
+.icon-mic:before { content: '\e838'; } /* 'î ¸' */
+.icon-endtime:before { content: '\e839'; } /* 'î ¹' */
+.icon-starttime:before { content: '\e83a'; } /* 'î º' */
+.icon-calendar-empty:before { content: '\e83b'; } /* 'î »' */
+.icon-calendar:before { content: '\e83c'; } /* 'î ¼' */
+.icon-wrench:before { content: '\e83d'; } /* 'î ½' */
+.icon-sliders:before { content: '\e83e'; } /* 'î ¾' */
+.icon-services:before { content: '\e83f'; } /* 'î ¿' */
+.icon-service:before { content: '\e840'; } /* 'î¡€' */
+.icon-phone:before { content: '\e841'; } /* 'î¡' */
+.icon-file-pdf:before { content: '\e842'; } /* 'î¡‚' */
+.icon-file-word:before { content: '\e843'; } /* '' */
+.icon-file-excel:before { content: '\e844'; } /* 'î¡„' */
+.icon-doc-text:before { content: '\e845'; } /* 'î¡…' */
+.icon-trash:before { content: '\e846'; } /* '' */
+.icon-comment-empty:before { content: '\e847'; } /* '' */
+.icon-comment:before { content: '\e848'; } /* '' */
+.icon-chat:before { content: '\e849'; } /* '' */
+.icon-chat-empty:before { content: '\e84a'; } /* 'î¡Š' */
+.icon-bell:before { content: '\e84b'; } /* 'î¡‹' */
+.icon-bell-alt:before { content: '\e84c'; } /* '' */
+.icon-attention-alt:before { content: '\e84d'; } /* 'î¡' */
+.icon-print:before { content: '\e84e'; } /* 'î¡Ž' */
+.icon-edit:before { content: '\e84f'; } /* 'î¡' */
+.icon-forward:before { content: '\e850'; } /* 'î¡' */
+.icon-reply:before { content: '\e851'; } /* 'î¡‘' */
+.icon-reply-all:before { content: '\e852'; } /* 'î¡’' */
+.icon-eye:before { content: '\e853'; } /* 'î¡“' */
+.icon-tag:before { content: '\e854'; } /* 'î¡”' */
+.icon-tags:before { content: '\e855'; } /* 'î¡•' */
+.icon-lock-open-alt:before { content: '\e856'; } /* 'î¡–' */
+.icon-lock-open:before { content: '\e857'; } /* 'î¡—' */
+.icon-lock:before { content: '\e858'; } /* '' */
+.icon-home:before { content: '\e859'; } /* 'î¡™' */
+.icon-info:before { content: '\e85a'; } /* 'î¡š' */
+.icon-help:before { content: '\e85b'; } /* 'î¡›' */
+.icon-search:before { content: '\e85c'; } /* '' */
+.icon-flapping:before { content: '\e85d'; } /* 'î¡' */
+.icon-rewind:before { content: '\e85e'; } /* 'î¡ž' */
+.icon-chart-line:before { content: '\e85f'; } /* 'î¡Ÿ' */
+.icon-bell-off:before { content: '\e860'; } /* 'î¡ ' */
+.icon-bell-off-empty:before { content: '\e861'; } /* 'î¡¡' */
+.icon-plug:before { content: '\e862'; } /* 'î¡¢' */
+.icon-eye-off:before { content: '\e863'; } /* 'î¡£' */
+.icon-arrows-cw:before { content: '\e864'; } /* '' */
+.icon-cw:before { content: '\e865'; } /* 'î¡¥' */
+.icon-host:before { content: '\e866'; } /* '' */
+.icon-thumbs-up:before { content: '\e867'; } /* '' */
+.icon-thumbs-down:before { content: '\e868'; } /* '' */
+.icon-spinner:before { content: '\e869'; } /* 'î¡©' */
+.icon-attach:before { content: '\e86a'; } /* '' */
+.icon-keyboard:before { content: '\e86b'; } /* 'î¡«' */
+.icon-menu:before { content: '\e86c'; } /* '' */
+.icon-wifi:before { content: '\e86d'; } /* 'î¡­' */
+.icon-moon:before { content: '\e86e'; } /* 'î¡®' */
+.icon-chart-pie:before { content: '\e86f'; } /* '' */
+.icon-chart-area:before { content: '\e870'; } /* 'î¡°' */
+.icon-chart-bar:before { content: '\e871'; } /* '' */
+.icon-beaker:before { content: '\e872'; } /* '' */
+.icon-magic:before { content: '\e873'; } /* '' */
+.icon-spin6:before { content: '\e874'; } /* 'î¡´' */
+.icon-down-small:before { content: '\e875'; } /* '' */
+.icon-left-small:before { content: '\e876'; } /* '' */
+.icon-right-small:before { content: '\e877'; } /* 'î¡·' */
+.icon-up-small:before { content: '\e878'; } /* '' */
+.icon-pin:before { content: '\e879'; } /* '' */
+.icon-angle-double-left:before { content: '\e87a'; } /* '' */
+.icon-angle-double-right:before { content: '\e87b'; } /* 'î¡»' */
+.icon-circle:before { content: '\e87c'; } /* '' */
+.icon-info-circled:before { content: '\e87d'; } /* '' */
+.icon-twitter:before { content: '\e87e'; } /* '' */
+.icon-facebook-squared:before { content: '\e87f'; } /* 'î¡¿' */
+.icon-gplus-squared:before { content: '\e880'; } /* '' */
+.icon-attention-circled:before { content: '\e881'; } /* 'î¢' */
+.icon-check:before { content: '\e883'; } /* '' */
+.icon-reschedule:before { content: '\e884'; } /* '' */
+.icon-warning-empty:before { content: '\e885'; } /* '' */
+.icon-th-list:before { content: '\f009'; } /* '' */
+.icon-th-thumb-empty:before { content: '\f00b'; } /* '' */
+.icon-github-circled:before { content: '\f09b'; } /* 'ï‚›' */
+.icon-angle-double-up:before { content: '\f102'; } /* 'ï„‚' */
+.icon-angle-double-down:before { content: '\f103'; } /* '' */
+.icon-angle-left:before { content: '\f104'; } /* 'ï„„' */
+.icon-angle-right:before { content: '\f105'; } /* 'ï„…' */
+.icon-angle-up:before { content: '\f106'; } /* '' */
+.icon-angle-down:before { content: '\f107'; } /* '' */
+.icon-history:before { content: '\f1da'; } /* '' */
+.icon-binoculars:before { content: '\f1e5'; } /* '' */ \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/css/ifont-embedded.css b/application/fonts/fontello-ifont/css/ifont-embedded.css
new file mode 100644
index 0000000..beb6f9a
--- /dev/null
+++ b/application/fonts/fontello-ifont/css/ifont-embedded.css
@@ -0,0 +1,198 @@
+@font-face {
+ font-family: 'ifont';
+ src: url('../font/ifont.eot?21447335');
+ src: url('../font/ifont.eot?21447335#iefix') format('embedded-opentype'),
+ url('../font/ifont.svg?21447335#ifont') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+@font-face {
+ font-family: 'ifont';
+ src: url('data:application/octet-stream;base64,d09GRgABAAAAAGwoAA8AAAAAtQwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+IVLUY21hcAAAAdgAAAMtAAAJesmSl21jdnQgAAAFCAAAABMAAAAgBtf/AmZwZ20AAAUcAAAFkAAAC3CKkZBZZ2FzcAAACqwAAAAIAAAACAAAABBnbHlmAAAKtAAAWTwAAJDm6DgeXmhlYWQAAGPwAAAAMwAAADYZdM73aGhlYQAAZCQAAAAgAAAAJAf3BOVobXR4AABkRAAAAOIAAAJE6Mr/lmxvY2EAAGUoAAABJAAAASS9sOQFbWF4cAAAZkwAAAAgAAAAIAIIDb5uYW1lAABmbAAAAXcAAAKpxRR69HBvc3QAAGfkAAADyAAABlAML0mAcHJlcAAAa6wAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZI5nnMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDA4vGD4+ZQ76n8UQxRzMMB0ozAiSAwDvugx8AHic5dbHdltlGIXhV7ZjYxxaCBA6mN5777333nvvPXRCEnrxPFNGXA3jzLPWHkpXEN7js5nAJSCtx5L+gc9ZWt+3t4ANwLzO1ALM/cnEd0z+8HSyfj7PvuvnC5Otft7EgZ7MZVt2TpenK9Nds6XZ7tmevXshZHvP5v45+9djwqbJ5smWPlcnq+tnc/7HBe9kkSX2YdnrrbCR/difA7zaQV7zYDZzCIdyGFs4nCM4kqM4mmM4luM4nlVO4ERO4mRO4VRO43TO8L7P4mzO4VzO43wu4EIu4mIu4VIu43Ku4Equ4mqu4Vqu43pu4EZu4mZu4VZu43bu4E7u4m7u4V7u434e4EEe4mEe4VEe43Ge4Eme4mme4Vme43le4EVe4mVe4VVe43Xe4E3e4m3e4V3e430+4EM+4mM+YSuf8hmf8wVf8hVf8w3b+Jbt7GAn3/E9P/AjP/Ezv/Arv/E7a35Bi//5Hv9/j43Dn8W/+mltmLbRMKUpJ4nUMNWpYbJTw8SnnDhSzh4pp5CU80hq2ISUM0pquLuUc0vKCSblLJNyqkk536ScdFLOPCmnn5R7QMqNIOVukHJLSLkvpNwcUu4QKbeJlHtFyg0j5a6RcutIuX+k3ERS7iQpt5OUe0rKjSXl7pJyi0m5z6TcbFLuOKkhvVLuPSkTgJRZQMpUIGU+kDIpSJkZpEwPUuYIKROFlNlCypQhZd6QMnlImUGkTCNS5hIpE4qUWUXK1CJlfpEyyUiZaaRMN1LmHCkTj5TZR8oUJGUekjIZSZmRpExLUuYmKROUlFlKylQlZb6SMmlJmbmkTF9S5jApE5mU2UzKlCZlXpMyuUmZ4aRMc1LmOikTnpRZT8rUJ2X+k7IJSNkJpGwHUvYEKRuDlN1ByhYhZZ+QsllI2TGkbBtS9g4pG4iUXUTKViJlP5GyqUjZWaRsL1L2GCkbjZTdRsqWI2XfkbL5SNmBpGxDUvYiKRuSlF1JytYkZX+Sskn9DTJieN0xYnjdObJnmS6PbFymKyO7l+mukS3MbG5kHzObH9nMzBZGdjSzDSPbmtniyN5mtjSywZntHtnlzPaMWPsbqOC/vgAAAHicY2BAAxIQyBz8PxOEARJwA90AeJytVml300YUHXlJnIQsJQstamHExGmwRiZswYAJQbJjIF2crZWgixQ76b7xid/gX/Nk2nPoN35a7xsvJJC053Cak6N3583VzNtlElqS2AvrkZSbL8XU1iaN7DwJ6YZNy1F8KDt7IWWKyd8FURCtltq3HYdERCJQta6wRBD7HlmaZHzoUUbLtqRXTcotPekuW+NBvVXffho6yrE7oaRmM3RoPbIlVRhVokimPVLSpmWo+itJK7y/wsxXzVDCiE4iabwZxtBI3htntMpoNbbjKIpsstwoUiSa4UEUeZTVEufkigkMygfNkPLKpxHlw/yIrNijnFawS7bT/L4vead3OT+xX29RtuRAH8iO7ODsdCVfhFtbYdy0k+0oVBF213dCbNnsVP9mj/KaRgO3KzK90IxgqXyFECs/ocz+IVktnE/5kkejWrKRE0HrZU7sSz6B1uOIKXHNGFnQ3dEJEdT9kjMM9pg+Hvzx3imWCxMCeBzLekclnAgTKWFzNEnaMHJgJWWLKqn1rpg45XVaxFvCfu3a0ZfOaONQd2I8Ww8dWzlRyfFoUqeZTJ3aSc2jKQ2ilHQmeMyvAyg/oklebWM1iZVH0zhmxoREIgIt3EtTQSw7saQpBM2jGb25G6a5di1apMkD9dyj9/TmVri501PaDvSzRn9Wp2I62AvT6WnkL/Fp2uUiRen66Rl+TOJB1gIykS02w5SDB2/9DtLL15YchdcG2O7t8yuofdZE8KQB+xvQHk/VKQlMhZhViFZAYq1rWZbJ1awWqcjUd0OaVr6s0wSKchwXx76Mcf1fMzOWmBK+34nTsyMuPXPtSwjTHHybdT2a16nFcgFxZnlOp1mW7+s0x/IDneZZntfpCEtbp6MsP9RpgeVHOh1jeUELmnTfwZCLMOQCDpAwhKUDQ1hegiEsFQxhuQhDWBZhCMslGMLyYxjCchmGsLysZdXUU0nj2plYBmxCYGKOHrnMReVqKrlUQrtoVGpDnhJulVQUz6p/ZaBePPKGObAWSJfIml8xzpWPRuX41hUtbxo7V8Cx6m8fjvY58VLWi4U/Bf/V1lQlvWLNw5Or8BuGnmwnqjapeHRNl89VPbr+X1RUWAv0G0iFWCjKsmxwZyKEjzqdhmqglUPMbMw8tOt1y5qfw/03MUIWUP34NxQaC9yDTllJWe3grNXX27LcO4NyOBMsSTE38/pW+CIjs9J+kVnKno98HnAFjEpl2GoDrRW82ScxD5neJM8EcVtRNkja2M4EiQ0c84B5850EJmHqqg3kTuGGDfgFYW7BeSdconqjLIfuRezzKKT8W6fiRPaoaIzAs9kbYa/vQspvcQwkNPmlfgxUFaGpGDUV0DRSbqgGX8bZum1Cxg70Iyp2w7Ks4sPHFveVkm0ZhHykiNWjo5/WXqJOqtx+ZhSX752+BcEgNTF/e990cZDKu1rJMkdtA1O3GpVT15pD41WH6uZR9b3j7BM5a5puuiceel/TqtvBxVwssPZtDtJSJhfU9WGFDaLLxaVQ6mU0Se+4BxgWGNDvUIqN/6v62HyeK1WF0XEk307Ut9HnYAz8D9h/R/UD0Pdj6HINLs/3mhOfbvThbJmuohfrp+g3MGutuVm6BtzQdAPiIUetjrjKDXynBnF6pLkc6SHgY90V4gHAJoDF4BPdtYzmUwCj+Yw5PsDnzGHQZA6DLeYw2GbOGsAOcxjsMofBHnMYfMGcdYAvmcMgZA6DiDkMnjAnAHjKHAZfMYfB18xh8A1z7gN8yxwGMXMYJMxhsK/p1jDMLV7QXaC2QVWgA1NPWNzD4lBTZcj+jheG/b1BzP7BIKb+qOn2kPoTLwz1Z4OY+otBTP1V050h9TdeGOrvBjH1D4OY+ky/GMtlBr+MfJcKB5RdbD7n74n3D9vFQLkAAQAB//8AD3icxL0LeBvHdTA6Z/a9izcWC5AEQeJBgAQpiAJBQCIpCqIokaIomaIomZRlmn5IlkU9HEexXUdyXMvXv52kUuLm4TiOYyV2kqZO61fz+pukt3XS1k1TN72VkzatGydNFefGTVsnt9FvwfecWYCi/O7//9+9Eri7szM7O3PmzHnNObPMYOyVX0tnJYv5WRtbydaxS9gV7Ah7DzvFLqlOBr1cC3i4Kmnqgt/gkk/nHCS+YCkcGIMZOgObNWXOgE2efN+dJ259943vPLy47+r5y3bt2Lalv/6vL6S0dndEbFVLp7K5/lI52ld0wpjO1dMVTMOr8indDW56ENz065VfC29envIr9fdRPqVFfqLdeTyagNce9y1L1O5/oxyRcNrfoFQ9Ay5c3nfQwWTkoNMOCVAX6RRdpBK1v1yWwzOLVJ4OtW//VwuBvnSfMY5j+xh/WvKwCEuwjmqKKaAckQBkOMJkLh/BEvwIY2xfOBoMRouq0tzdYavpZCrbXxqWok6xUkxIkq2mClBOAH9606paZtUmM5YfXrH56Yn8SDaunzr25C3y7Y/csXFodnaod2bXUCeMj2eHZ3bBH83eeuujt/FjjKmvvPLKAXmlNM2CrMgG2SjbRThWPXiJj0scxphl6IalHwqAzg2dL/qBS4hrixooiFUKHPKYHFSGIGOqjL9FH0hezJDYQhAMwzPOZNkrb56/fPfcrpnpqS0Tm8c2jKwbHljTFLGbKulwMkDYBwJXKqW+YgIqxYoascHta640rOBNPgzroIw9lqOYkcqWyoghtlqAYe4ohEjZXLm/lHP6isMQLeaWimzZPbBlRRU2yvmRZLZD4iemN9Rio1MgewLt2YGkmimMT21q6groqdXZ9oAPzn9r5sYZ/MHdAjxP3gIbhgtb1uxeIXV0JEc75Y1j9fx5abSn55tOM3gjgW21y0a2bRtJrB5ZXco6sXgzdwLNJneypdUjcX5qkB6Yqf3r3K38li/erN7xN90FGJXWbwtEvLEY1LMRluyVX0mn+LeYwtQnZAYruhWoRCEKC7XHH3gUPvBJE7Y++HvwwQeZKPus1Mb/mVlYVgMs26HltFwlV4lWoprUdv8LP7v/hRfu/9kL979w9BM/+9knXnhBHBGbxLOflE5Jrfhsb3UFkyVENWBwvapwiTFpms4S24UkRGIT+IDFrCD905Smbogkg+lgsj8Z7AtKp2qPPVd7DC55Dr7zXO1RmHoOLqk9RvVjLafgO/h4otqCLwQ2Te/dRcQIqEaJSUFJiXRX+pORkHT8X597Tty80K4gzonR6nrGdUQyrsxjtiwxeR4LqZqkzhuAfdam8aTBLMPEZCjk8YQioYgd9gQ9wWAo3Bcwqb3JYH3KBJNKBNvdD8FkEJ6Bb4wUzl9dGIGv1x7nn6jh+fzV/Avnr145MrJS8l7/3PVHzl8teoMvFjT4NM4PHduVQxqMLRsCTTcYxxk6ho3QJV2TDjFVkiVVPqQATmKGsFzAZ7nM9zBd9+ib163NdDipUMeaWMgkclsqgA+cYagsXRDBTLoEsi+J83sdJIuO5PhBTa2EbAWpYp20apG+In/aTtg81hz7Lbs9xJ14bFO78/JfCIoG0pbkruQkSE77F83QOTNhngsaZvSU4zvlc+BU7Bq/eJDb/sbF+58QxOsJp32yHX/QGQ2cs6xzgWjknN8Gx3euDofHEA4FAYcuVmWbqhv6QVPrcGCGahzRcR6rR5gmaUdE52eWA0PmswSPybVD6b50qngBElkfT+Dkbpwj9ZkvGE0CkBmIXkvIHpZ4EB6QHhTbwHkTQLzoKadPpcreFxEQRuyU7T+FvTkVDQcFTEKtPoeH2kNys6dxcfcTRJ/xAG2dnW0JmHbq/e/BRxCKhNoqwuGA9MeIDypy5UE2xhbZUXauGpubDgdlyRxfxf3S5h6u+aWxACiwacujvqnZ6qXMEwzonr1M1bmu8kNMMv2m5D/ENL/h14xDjBlgMDiEs5+bCl/wWlz3gSnp5pX4GoP5jQUWCARnWTCIJBW5xKxLV1uqs1Rz0BM49L+76rlq+p03HDm8eN21V1915RXzl+2evXRyYsPI2qHBgTWVbCaVTiZDSgzHL52y+4pSKZtLRfAiHFE1hwbKh8OVUKJ2OlXg/aVKf5YOBcjhsBajxXC63B8s5frUSNDuiKhYSFoLfTiwuZTW39cvcvoiqSwNel+RaD3yAAcm98wuzCRT7Rs3jnzEjhnTGx2nOVss5B3+F9nR4cy+bLqzBAe2lQvlXx7j/JgE051r0sV4QAZLkzyRsvwe6Qq9XV/Zm6r9XU+1B3pG8srqd8IPUz2wfQvA9Zoai266xqvY0aDjM22nt+3zvvahrj1Jyeld65e884Wt+6G5trJpFcz0h8PF2u+suvqwE0sM9GSeQuHrQD5hRzce4N/ezNPtRegd6YUiYxrhjPwNxBkJr0maC7MozqD+anFtL071SDgUDPh9Xss0dE1VkO2yGCPey7lnHOcP87LNlXK2oy0hKXa3UuknUq9B/ZxrpBHoLrGonxDWOIfWgTjQZElAK+DMgL1nzmSef/6H0no8//CHz8fw9Ktf/UraaYfP+TK+Gkh48p4L2xCz/z0WPudNeM/ZsX+3Y2sXH92zbuHKK2ufql9s2PPhPeuuO3CgduNP7ZRxXNffA4DH40bK/mkko+9/zsnoi6q6aKSiz+3XM6xORw5IlyEsfKyV9bC1JGus8UgoxOaT2PGWEFdkacwC1gVyMwobm5imItlnh3QSapExLph4VLisLFigqp5xAziXZpkkeaXNhRVtiYAfWLl/xdrC2mxHoqetJ+r4WwOtusZ84PMg/IieRGwfpAg+CQhfnKy4Sd7ITIC2lFsaBvjCHT8ZueHPfvz0YWnkJ//HG10ffepG7iaOPgUP9668IjuSxd8VK3trM5jKUSqHqTNuFs8Pd2KKn8C7Y24mnQTePCad5X8iaG4aodXPhtkUu6Z65QrgahnlMC3DiFOP6ZjWVK7hrJeQRy4agLxSYosy8lyUzhZJUuCWuqAAmCYgXcbzLDPBnNw6uRHn9JrVpb7eld35XDZsh0MenNdIj1NqgpfDePYh6pSjlXKBUMuHRJ/oNCJkIwuWyixd2BpR8RwJqxzltzP5gR7eOZw9hH9yodz7szVSoDkurU6WdatndsJrNMOu/FAn71ldqN1bL/Or+vn3LzHtl//dNi+ZuuMP/+wP75j6q0ReFAWnfvGLv/U1q57A30o9ye7JWKE7We1oFPnN+sUl9fM/fvBYMPjue/Z9+a5t2+76cl0eekzoWDvZtuoW5rE8R7xgmdYRv6EpkgKcAHxIRyWKHUayybk5gyeT71KBm3xiZvqSrZMTCMLBgdVIt5LBDvrrC9RVqoZKE8UsZNtOdJka9Ab38JDsT1/QlKKC60WSLuN3pLN1paW/9qP+ZarRq5N5nO0X3YDW1h7AO/ctcbnFxUT0DRJYdFFIBJROwGI+sZioz91P4tw1WQoxcRukqv4ool+ph6t6FsCQxrY8GkZ2txEnLSLkXhlhpHJzMQymYZhXI7y9Huadt0MBn+zxg6V7rHlEblXT1fkgSBIyIMOAOUJPL2xu2fKojZVtep3KVNNY/K/WVh1zK+KH/hdrmpurdq5bl06v27Zu29ZJVGnGxzZtHN0wsr6aHk4Prx0MBptsO51JBCKk2vSh9NafRpKsgV0X5nBcg+7gRoI4pCSaholQp0nGSSNWSDjPIkLkC2O6L5fW+ugc7gvzj13VqaKejzSv9nfq44pPfULXvYumvqib+IPv1r76HU1RddW8fQOs/Y6sq7pive9dp/PNdzXnH8zddtlt/PAtzQa3TFM9v0nVHleUJ4yIxHTT1M8zc+L2IuQsBdG+elux9j3ZREmbj8Avh4ampoaG4B21U0s0vIEHRbYVClXPpuGo5JPK4A3yOhJMML8v5PPvtSGEIvnVzPJylD0I+MzLUWwKMq8V9F6BFSKF1+aZzyfNBgSwwx5Tl1UVdhlkwriACVuW1egLBRf/l6qsTrq1hQ7976iOcKKvL53u29q3dXKziw39pXQxXVzVu7Kwogc1qAs40XEBJ5Q3xImLx11q4EhKzS1hCSFJX/9boASXlw2+5aIHoKriIohH1T/e5yKEob3nNfjwwcHBqanBQbihdpKfONEHOVNR6FWEHd/34+UGBA5/5ZVXHhE0tMBKbGt1ohdkpQhM7kPxf6Xfkrgkj0WAjzJFVkgzQJVTArJVHUIR57DQEWeIFs+SJjrZg2wo1R4OB1QlitzaQfJHJDCbQz0pityF4yGbK8jlCtkMhLpU7EA6KQWxoCOdbs3nW3viP/1Be1a2LdnT3OwE9yw0yc2mT9b10Qxyqw7w/AdAax7Gf/DMd+HnyBngDKb+OGSWhu1wJp5ygu1xX8w7nhopVBOljsWO0rM98fPf5/bnoh+NCtvNn6CuWmDrUf7fW71qbYFLKGIzLRP3qsgppDETdK/Hq3sELqEGBIeIdhxGSc5jeD3zKHZLmiHNW4A8XpvBk8ZmVayATW7auGGkOrxmdbk/YmdzwWDUCfuIjJCVw9aiCA3EFs0npVETR8kar1F4zpbWEUKRLQgRIkfGjzZIVspFhwgJmd+SZA+RUAk83nHUtBPmEV3JpgaaxlpX5xOmvs8KeB39He3HSSX0Hr/ScuLWlfDsghWPyfqVeLf2y9rH93/oAAygCrkw8i4r7liHNTkW8sFLNY8vZuv6UU84Yb1n3W5Ux+ChK82EbV55Jb3oyoccKE3s31/XnVx7pisHt7Bu1KCuqO7pAcUqgSnHQTJslFfkMcxVZAtVf5mZhmwuEMA4Agxhr2tcX8DnEVowj5Wqwr6gzmL16uRApbgy35nNdgibRcBDkw31/hz+daB2AcuU7GiybuWspyv1dHpJCS86fBD21e47w4fOPwV7n30WEo7v5QWhE0oPitMbpqrjZ8aeHTt/9qS4c9LvgA1CAXfVUAcCmBAqucPglV+jDBJDOc9msWrEJyHKoPSPc0EYAFfnuOJ0g6NBkKQqIhb9QSQY2EDJbwRrP3c0zW+eMms/D4Vj/Oko/4KDN2tXRU3JOmX5wA/hYA8T8/Q8vmcO6XYnztNUtQ01c5yEqBqSaM32oAztkTb3FVf0NDfZshJBpPNxxLEKAqfsRJ26TseLw3gLZyAMC3UiSOa2SlAg2NzoDQ8+8qnDE9Lu7bGhQEiPlYcKkweO7p/Kw1A5amaGott31z6OwiDQpJu/9IGjo6NHH7h03+PDWDY6FOy6ecPAgckCPjMwemM+NNCrh9Y+CeO1+2hSwz48kq1IwOt3EI+2syvZO9hMdftqVLWnkOr4vR6Jq9IYsm6ukTKMIjKXFslUKYNAJ02VtQUyN0kz2F82S6anycMH910zf/munVvGUZIb6Nep744moahLyEC2BuxwP5LfYUC5t3GuuBel7DCsExbKYR6NCLypP6pGHUyVK+FyzlFUBzGONLEsYhypaEtpKqlJv+147jOrHefPd0+hLohsVwY55I2hpiNrpseRQEbZ02s2SzKq+aahqKql2HYgosHvdBW8H7M7S7UWv0fxbeBSIKF8VobI+a+iWAY7dB/3cNWofUHz8QFJ12AHkg8LL+iOxMfaumpapheGejpSfq+m+JCvNUeGIqpjGV572FZi+H6vbyQi24ZHs4KmbYaIq4BS7aypI1kI+nMhH896w3pQM8GjZsTxifq1B0x9SWY4K+xoMZz5FbalOh7CaqQi9rQFFI6DpiHKo6ZHtiNF5vNMwaQCOFqSPktmtPH6RFe96ub+dH84mgknAwbOcSUZdO1CyA4EeFFodnBCl8N9OSXYmPN0CLoTHRkF6cPwBXjppyjfnjlparWbRFvhbs089dnP0vrD+Z8ietf+WojP/AoUiz36jwPOJbAvET17OnOhf7UPfvbF2t2i2Fx7hL8orr6k6T/2l5n2yjnE1ReEDjeB2LaPvZPdxu5k767efNON1WHZ0G8+dHD/tSMrNMU48Rt5Cdidt92SCymmfHsEebJCMquOcihisiVZnOyMTDdUnSiiwgxlgdW1YqKUFlFKLGTNMMsindiSJvddc8Xls7vyzZ2due7msKCIyFN9UODlqOIIU0GuA8mKo/kEK8llK4TcfcUo4mYum6NbqPxmy+JeAqKO1gYdrjlBUxCZs+mUhrjs9BWlYRApH1AqHO1HrpTT6BXpNoCcEtHaOIJdqeD7NB5rmN2egJaAEhjXwlrtPcOypHO5vHJiarK3T5PLhYnthayij44iuypsnyiUZclpWrl1aqKwWuK6Ngy/iY+N4+Pnr2uOryhV8hE89a3ualoRb3a6VvfhKZKvfLQc0nv8IBsAN5fhvrHajjlFRkbth+fHav94Nagw78RFa1L/aHXqGVn+XkaKN3dPta7sWbkxj6eQrnR2KVpwZWJbd3LI6VnZuq2npUXq+J4iZ4ycZ2tsIp60Y+OJZO1d8YmonaQD3JTUUABp9nF88e0hWJ/5RbVfkRCj/Y+nf7yVc2Eqr9Nki3nga/D/8Ju2PGpMza4fYl9j/519mX2SfYTdhUOO9IqdIkECr/6O/Q3OkTkkfiOohPWxdtaEgriGQsYDcC98BN4P74Nb4F2wF64Gif2Q/RPzYA047WErdOLzqNfAS/D38NfwF/BH8HVYDX14D+g+G0NJ28T3b6i//S5EKwXf/TVG6wHK/wdt0FCk+gjRa/zb1PL/HyDm5sRIVPuJhUjCskIrrYtM1SVVX2Q6SDogY4HDBhAjmcETk2aRbklMmnTBWB2QQVK4Iu1lXFO4toh1KG4diluHcqEORXHrUHZh35WJlv/JN8/NrW8S/Px7cAa+Al+CS2EX+1P2TfYH7En2GPs99hvsRoQRymIIDcA/E19HVrEECDsXMjIy8RSHoZ/oQJS4Hf7UbL+tlbJqf0FG+aNEYqedBzulprQyTnqkA30FjoQCb7eBoyJtcFkcyat4gWQil9Xor5jFWZumSnMO8VWUGvqcUq4oCqhRKowvyGG1WGsuS+kEFJHq4KtUR0NS5OTSeI10qhTNqZpgodFKFB/WHI1IFZIjLcHtiqPhY/hgLqs6fVRPGzaoorZJqEGoVF8/lnIq5VyB9/chPVMTvA/bXUzIbRItv5DIU0kRnYsg2Sv3Yy14oN5ny9FiGbuL3bLVSLqMdK6M9zUkfRLJRZTOUbtQnSthP5wy1oQNdioJjtApVxwkpMMoxuX6SWIX9tlcEUuksDXD0OfQseKUUbCIVMppaiMBuNiPAJHKlSyKgOUsvhB/fsCeRRBetIzkR6kwS3AvqxEU1QpQEfRaRbjaqgNfOPqtG2741tk/O6ze8ocQ5jrqYLIUjISRkaFIIeGQybKpqDLopJxJMv5TQeW6gcQSS4LuASUuS6j/6fgyjgqMjDIKypAa8nHFK0m2LyzrKj7MFYND2EASq6impMuI/JJqYG2KISuShNwdfJrllwMS1irroNMJK0bZRg4pkseDr+eephZJVZSwIlmy18IXqbIuG/L2oqxwVZEgZmIbFJnaia9EgcnUtJCsGchCZO7DNEfKy7lfl7BqSQHZNAFrUDwoEeqSoTmqquh6QLaxHqxc8kkymIoeNDn+Q4EEUxwFYo7Q0ElO1ix8D9dtSccHqN8KqbCo4soxyZCwAZKX+wgcMuao2AaEE8puuqJ5ZExw7L1oiEfmIRLmFJ+BEp2OoFJV5P4e87p3ToEHvPh8hMgGAVrx4JzHf0AtN3GEOEleHuqcbPmBGyZI1tGnXnjqqDjU/gF0rA0LS4qFxbAKlE80AVfgqkdREa4y0ODiCa+5TmAF7DmOtYYyoanJiqp4CDWwax4DgaJgF6Qg+ZfQfcnAYZVU8MkmVqlgt0xZ0zQwFF3TEUgSwRLRwZQkH2UrssZRrPJziYiZj2wBKv7HRqy4RKZRl1W/iW1QZISHbXFQmzlEEeMkxZakAMJY1hVdBivmVTzYa9mj+2QfmJatIflEkONYhCRTllEi5pIpAMwDeojwF9thojRCQ4nwDih+osXcwk5jUo75DJ9iAGIleTpgn5A4cz/iCKbxpytRlLQRkD5umgrekC1DIdTAMcA+yzghEAQqYPfwQRp3PNS8kZ3UZxVlC5oHCGpuSireQuj6VE5lCJ+oHiWuBw2f4eFyALXZV1555ddyr0RacVs13hQyJbEEhFVccAVJdxUlJUp+ICTQklqWJUW16ETpELG1BMgrj+0+f/raD8FUFT534+zJVK48OBMdn/+ruWNwz/6JWxIB/cbPXTWVnhnMp4NHkU28UnvlAPwnvred5au5Jmxv3CS8HkPuw8lOIx1paKJetjlWLqNmGBPaqJpGUlcJ14XG8DAI45WtSSQpwoumWvuuFtANkx9+niumZkrXc5/+JcvHF/5BwUFwLO/5W3wgBXT4k9Wg6V74S930IURrtTInoaNul7/g+7SzOr0ZNGMNKFoXTpAmBO76VcmgQSscKCgbmnGEaYp2hCmSQi0nFU94QpGBaVZHaiSTJ9TWLdn6vzQtQFde5Xn0mnTQTfcHXz8dflX6lDC8icOZ5ZfaQc0Uh2dft0ChcWFWn6ArOvy32tfpEkbw+J3zZ+max/AIzstPU0Iq45G5Phd8CH7OfCxatWm4lpkP+m1hPmjYGRuGDfOcFbfOWagJvORz4M99Fi35W3HHIZx4BesbhD/H+oJV31JNkSLV1CFMJgVoLMAP2v6ax29zj1hWN3+XbBwJixbMLVZvG1zLjyH2vLZt0bBoW9B2m0ZVOrDXbjfE4/EYn615fI5Vbypbqm89P45tM79EddggLBXUPRqDYcBGwXpT9AUfM2HeV++n20h3zedfpC/yLmazpqrjFW0S7imiUXY0RLPMIM2o3qio4XbWln63dhV2s3aVZV2OZ+iETivu2WPBqdrVlgWfsBLmHsuqfQ9vW3usuPuuP+JHpVF814o/MMSrtjyaRNnQoszrMe0hY7SxBJO5J+MhgopRH6+K0WgEvp8fwFfHrcvxXZ2179Ub8YAJ19WuNM3LMQe6qEVUgArW4YV93eT2VQF3YRkJCi0s74vaoq+okKVySy+sv0v63T3Yndr38D3uGx+g+h+wFvcgBnbVnjVNyse3m+6r6n29t95XeFt9jUZFX7kjBjBXfz2Bmp+sPQtdbq8I3viihHm5yT+PLXpWXJrwSdF9AYaltbwT/F9YgrVXWwMaKgnYWZSKj3B8I0NCBvuikX6XgtUnRND1KekPahf1XjoR6XIG7Yc728YS+YfsITsficCiPYSHCJTiDiYfqt3UmoFsHO5+KBLJRwYpK1L7sCPacZfUz3+O7chUkwx50hEXAIhkfIYhX58lwj7pdGTrbdGgMQOS9QXDZG5p1KV+G6sdiOQd59P0zkwr3P1pB5sw4GCTYCBh1z5kYwOHIp/OJ7C58LDb3tqH7Xpb5rAtraItgqiTv9erTWDZqJMWbREm0Ohrx8Mv/JHmMq21m+r9pbfCQWwFAQPuRmDM14HVXc8giHQ5A85DnQ3fs7+STiBNF+MT87t2TmST5PjYIDL90WUwaUzANlocITegSLAimoPjI2BS+7B4Q+Qh7Hlr/mF70OmKwO0EERyvgyIp2paHnrhoeJdTx5Pd2I6NrFhdOdzfl0L2XUq2t4SCAQ7k0TSGMhGOmlj+dWdKdqDSMrCG3DBBi1bKTisIsk/GwZxGQnZjCbcNKjmXJ9Y5SNTxQ5TEf8EetIgw4ub4t7/1ralgjx04FzXM4HZKGGaUHI56glOYsnya5ji+c1HToPT2oGlgroOMV/NZPIMlQuHYOZ8TjdDD24ORKBaOhUPiWcl4bZYhET3mS32PsV62qlrobGtCkS3kQUQAZPqu6+mrOf++ATvaXFHIpQ7xgJS6ui8wUVt3QIDcJEgtrOMxLUIIR+CHla9MmbZfuFLFramvKj61qsCTSOSnvqLStfLV7SLfQb6ufmW7mRA+V0SssYC6ToW/X0pU8eF6iXpfviJNY8uj2Jfe6op8pgVHza9ytyvk2ijmHmHYUlfsyJqBmOgKYnr2VS0Wrsf1LtKA0vKSs9TFL2AHtn9FCchVVf3KFPIUBDI8fOHSTpjbqU/Y5gFMc2P7VxWlijewBHIjuE5TviqufCJ36iuYq7jzwrWNjrKp6lYLDJSWDO2QF6UiU9MXVSBqwcTCJEmmix7QFURKHeaZaXrGXd+X9dW1Q4NrSn2FnnA0HOyIJMO2u3AkOONaSNM60AUpYBD605HlfLdVuF81ppvof4S8EcRsO2udtBL2s+PIjE8hzT2FHR5/1qYLStczkFA3MrA48d23U255zQ1+dZYHUR7trHYoqJrUqff1DHUGeZqh6rCLoZA+EcZ+hm0xIxt9TL6qR8mL23/+qQtN4UMXt/KiljVaUx8beC+OjWiPTD7PCoNDSLA4UodFpig4AJx7+eZIkoDutkfNpYPkzLwM3GShrjtiOXC34xsbI6wW0ET0p1dyw20A/yTi0uu0FX9MIn9t6bTkFf5AG9nl1d1tMdRNwiFAPudFDYNtAA2KYGgoEqMWhcoare+ghqEdYiZTZZO8gMjHDMgtU2OGrhkLTNe9+ua1Q/19djhip7N2Om0R8ggv/bqrfuWCV8qSawrpHqUCqLYzDCBgT658fFhO8CitVqKcqR13zeTHhcH8uKmT5RwT4/c8cw/+IJEfsr9xzbun7tlf5UMHTz508uAQbPxGBD7kPkQ2efeh4yTyHjebrr2Hf+Tpe9X306JQ5Bsbhw984FMnDw/II/s+svXd13wjwgSMHpXmpAAzWAjhhHoNrUELZiORIRMWGHmt0pozOaUyPhltymYE1ynlgLzIcWyz6ZQPNLuDnMEk6hD/QW/tssndw9dPFc9/Fx7esmfH+6eA/0CseF2/iY8cfeDR+2+swsLuidqeYnHqhuvg4eLUyenLLpt98AbMvvH+J++7eVidOPgZ1+7bGEc/UuJVbLC62tBxRJCESiyBii2tYSHhUvkC4SAqM7RopciSsuC6Z0bTOFLZtKa0uKO0bNXyLUek9nUxFjDyNkdBgP+tAU8+3I/yYcnDAizHLmO7qjsUhC5s3byxXCyQjjxGixXA5ENMVkBWKIpAxd8iU9XDTEdirUsLrxaSZi9dXx1eOzjQHM3YIUOwIFpvLOH4ZIeBJhn2l6d8XLPbuBPtK4v1i4qt4R1SR+t/atrHszl6kJRm8Vfgwj9NSnD4uMfj40OtmofrRrzcM5sdnpycHM5CNhgc196jj6mOmh1b05Rql5q93iY902QVir1Gcwa0Jp+vmafamwaKU/v3799W5kFC06a4GTBD+dbO0UIsVhjtXNMTCu/cvn2n2qz0rLl0XUt+pMXfZvv9kdaA19scb4rz9mgcqw60Rvx+u80fr/Y0r7u0sjCc4Z0D1yytE1yBfNtmSVZg1epaMnEZ4PK6xkKuymj5U6CMTCgjK4o8gwijzJLvxWTEiTjpC0hzQc1tTGjyRpco3KLSMAYjfOElU3/cnXxaRjfPTr/3M++f4jN3ff7OXe9u2BNf+uqtPHWONFws8RQW/vaO987wqZOfOokl37vjXXUD1dGn6muBfy2d5j9jYezJFnaQTVTHciZSsQrS1hnQFGlsP/CNjKvIt1VQjzA3LEYwdEbCtILCvKKhlq9p+6anxjZ1royGnKxOtDcouPpSKBIZmJFLCC+JCg52Qa4ID2REm6W7FcKZkrBRC3vKsswcsRLMpSUwsQ5GlVIBIQx89R6xvH/PV4m39eYUAwm3xwkEZD3ms2Xbbyi53lMLWzUNhZtIu9VVKHRZ7RHbr2vb5j9w8ka8j483NUVGt/GJTZEmOSShbKBpN57kJ88/SAzhq47vDJb5qmnX/s/8hF+yA56Ax5Nob9dDegArlfwT+Z33tEsOMqyAkdi9f3fCCCAHc6TkB3bd9r0iZthej8/Xd+/n7+3zeyVV8tpYn9R3xpWf8HCCPybGAamkGIO6RP4akLN9AsZCcnrbMH7bUPovdLhuA/iCaHdrtdmEJTViScjDhpJe+3oNfU2TXvvqejzKY5Il7WAW24A4erJqb2wLexE/VRtFr1IxTabfMXeNqJsclDWVHWK0fk1rM0i9rmZkwlUk4QKE7OdyxFWPhupvz2tLa0KSeL3ic9XwxPi64Up51cqermwmGgkFdMXuDms0cYeB1BAQ7lGpnIaMqyxkckfcIebsA3JnIdNcfza3TMMlAS9HOlW06Hx0dYxWP298ev9EW6K/PHTth1BQd+Ch3zjpd3goWPs4LdQGTm1aexIl34Kq8sdQCi+of1d7+MDmNXtgw5qB4THhNzSWgJVPj03sB6scv2d//d6xL3onHDLFOBNa9qSqFrAS/pg4b6x9BlonDsBI3YfsMX4C4Z1jfSjVWHHkD2HypeJ1IMeRmEkKSTOkuV4tYhwZXE4CmIJQbV2WrUhs8eL8uaoRaWlN93UI8awOnQSQs0+Blmn6C1BpQA4ZpwvFXNAmTzOEUgL4iYduOSV6VPu4OJ36jc8IMH3o2x/heD61/yP7gDyk/nx5j71fPFYHw7Uf4h+4Tlz/ee1HE/v5wXHqOt/H6nrMr1FHfwolgSppAGTfh7EkNn8TkzSKeTqEtBBlAXLVIY65yIicEx/VmMo1dQEJoqzMYF8pekORJ3s68rmwk7Z1JV7XpEkDaEwDUmqisGTJrJASOwxRQp9gKYuqXSlL61n1mw7/sFX7axSf69EwKIvCSuup+Q2Po2hA6y6g6493kBP4k6rJeePOhnmSrWvPoHB72p1Zp2mW4Y0/qN28Yd7ULUP1ajqUM3AXPi1zXa7fmV+CySL/E8SHKhuprrPJmI4yEafFn0M4xDhXFikuTJIZCbgIHxSVMLHcqOGkmzryHTnidx22I3zX0xcstZGouEXLiVrQRtGhiJLAMmC5sal1YMFe3ugX9lw3lzpewh48qSKr4iqBKe6cI8euc0hkEEoFHlJ8hgzzG+CuZX0uZWs344O0FtDz+jC6ECtxWsSNhFGOHWO/WfVEFK7xVi/5NY25i8xdloG3JEnbq4Ar3l4hFqpmTXiVz0xLNe+W5Zp06K0Kz1VDm0bXDw8O9HWUs9lsxvWmiyaDSXKyQbxRkfY4FJ7V8ap0ZSnqbploQeplqW4BQZwak0Ivv/gsCpYkV+ABHjH1NF3j4dme+MsvksPXeKkj01E6U86kEUHGWvOX8KHF01To4kPP+X4qzf+iNV/uKJU6yu6RXMVQur4AwwuxNy2sDeHZxypsUKwsvJPdW/3w+g7eGtq8IiOFW/lYso23ho3WmRYIN0W9kqGHjStijkfSI0ENyYyuzNsBVZL9loSiOY5vM4RCidl2SCQ84z6TQinjsywe98Y333D9oQPX7rt64fLLdu3YOrlxdN2wCKFaXSn3l1at7ELsS7a3JVrjLc1NsSgKquFQsP4vkCKAo1ZOf7llZ3jVWSKvJ8Ji8nrCWQzLylfqedF63tsanNOnv/n4499sHOHjTzxx5vHH4ZHTp8888cRTDQ8vOn5c3Dpz+nQIR05oCm86fqdPn848/vjjmdPnnzp9jg6Zx6H3tKjttPCkymLe6dOLy269yeASj75RehBlYh2l4gKbqwayIEuduZBEKz0UF7Pl0SzOkGZGVoTlYdWuMVi4gcfeIHPf3Jfi6RWxEAk8jeDEjiUTeQI6UMOAHAUaV4qOsJdGokK/d87UQxJhX+1EZMAZjETgmDMDH/O23H7J/nvu2d++qckwPn2Q57ck/eZSGOKvaidse6096MCxysw/OB1b5uGeZz7AEc1C2vzxId60wkaqYAi5hHiFtITJtDq2AanDJ6v3d4OlDoIXNa3mYETSAqBamjofBZNZHtNaaPLbkuID2avI8zHwMC/3eBfCwhV7xjFCEuhEEdh8uB5TxaXJ0dFqdVVvsj0ej8VsW0GaNDo2OrZpY3VDdcPI+jXl3nWr1nVl21cmV8bb4ojHsZYYYrIdtRGXvaYcVsIi4LeFAn4j6f6OYsNyKsjrOiC/drxPZ6noCi6NbQbgVWnyiQ9juaeegrsbzrO+s7XE8ePSXG3vcfKxDbj+tmJNq+5p64P1x4/XEtWxsfH6I3T7zNgYJMbHzz81NsZPNB4jN93a843nyFt3zC3m+i7fIK+UNiOuhRDqt7Avsn9j91c/9uL3uew7cDlX9Gf+8HoUPb7+yIPvmp7cmG41gD3xQBWlxIEVyBw+fBu3JG3sX/6e+zbeDtYokltDMXTkZMi/uaodYj4mGz55gRk6M5CT06qUghoc8bUZ5LNMk4AcVy1LMDhrllmSNfnPP/rTb33+c+9776GD11y1e65U7M6HbRtJiJ/EHIrgsp1iWUHsRYUGryQR1IUkWnODBTVCXERjshKQm4hYx8wJSZE0HdKksxRI6A5VuYIKEt0kb5kyKk2CYqAoGUW5mkiXqFH4VZKRW2jewl8Za0PO61aZFXbxili6xirJgFSheSMqoPYElz+bzYln3+aj8FI9PP8LPat7gLyM6+fv6fKlqifcJsue0YBSjTiqJnv3q6Y3HB2RveqUrGR0r7ZT0XVll2a55UxVrYZjKHxRQcCS6xWvsl2OBXWvulNVYd8O1YzzUVAirZbH0nokGJVaTW3HDs1slUoBkPN6MBiPynwDjxt4u146r4vS8psWhnl3W4Aftfbwzjakf3wOD4P5/F9ei23xhaMtHapHDo7IRUsdavHq2CBPUZa3+BVF7/HEHC/o2n5lqaQSwJKmPtQsSlqrqKSsJiLelrCH67W/2mboft86H+edLR0AVgk6Oce0Xze2GUbASzkJo4RI2BmFHGV5Awbvwzz3qRxleUo59yEDH9KXHrIAcksP6cvXH3R2gG2sjuydm1wvM3nQRIG91NkSkCVUq0SgiAoiUsQlxxI0bPl8357LdmzfPN6dT7WHQxqtH4uoQ0TvjouxOve6aO2isIvXboDsW+C1W5mL2Hxw+uZpvuvoLogjhE0r3Kkq/imvpm1tajY0OXBM9wRaopeoAXWTIyt6p+nX96G8Zyr7dV+0wy2rb401G7oUPIbszR+PXqL4tXFblg23sNkY+5spP5CItBRVnxqZAmXIq0/GA6Z2reEZUtRqArUwT9Efb/GDRxNlm5rbV6DOaE8tK2oNKsqGeL1ocwBpPnN9TaQQjsE2lq62F0HY1zl/lbfJSDXZnqWNGsjfxFXuV3LXCEKwo00JXMtJHwKJgBNNcAqd1WxK9GNaALFSHOZR1LUIno4U8qjWmlKfPxFsHix3j93X3RI2dV1G4tbaFvf1+nXZtAOmrfGA3p5NkGOSL394EkKKpWpmItHu0QIxvpCT+AP+Xl+8PY5AjtgtPfeOd5fj0UCo3ecvldZYqodLuTY74eexgG61JxKmoluSA1sP5z2oN0B7JgV6gJfDJvIzxESSESkuIsyaUS6cY/PsILuJ3cqOVt+Beod88ACKzEev3LVZVqUjWW6qt5a4Ze7BSaaOMV0jB6lDZImTVC4tIKswLZUcsZmmWxqZa8mSCZ5xDygKKSbkSNPScuzdt/zG1NbVlWJvV64l3ZIOZ+3+spesKwmINHzW6+7qURTLyNsv7Lq5J4MgXH6ijman62RSUFruOm8jPSQ0Rp3WXX/E0aOofMRtH4TVaFmMG20kk5PwTO6W5PuUTTt9xYrUF3AS0fMhdy+Jl5yyf+K7ivb76rfOkjP8Dngk1Jpsnw4aOoCUaZnIvHthZ1HXfNh1pVzQFRzGrO3VdTXo8WomBT1pbbV/b13Z2vmsodFwohanbZrcAarRHfe3BWHBuz7fonN+a6AUO3+reKt0DE9OIPvdoPr72suP8qH2yE9f/gxs8QXlFm8AeMQJeZP3yMg2fdHVzQq2ZOW6yU2FXXFH9zRL4IzmLqv9m3eLU4R/ixZlmzvkSefRa7+V8Ct6x9re4TYVq2noVxfGfQd7omrReE8OU/AICo8hFB7XBHweyZRUydzrtQyKrla1RR00RdGuJjdkk8Z8aYz9NMZz9TGuDi49qx66+GFJQyX+TZ+eq2YymZaWzI7MjuntY5vqGkNfZy7bsYQuwbeJLuEIxRAiTvT3kW0Tr1EziPSRj24a9QPUEKS0JFI0t/v63wIJBnN7ZsdJbSQ/xPG5y3KfRbwhVzfOt6/bLu6isry6+DYG9aXB2inwWz4DxVJbrv0bvGNw8JeWz4PiuBxSoLf2jFeXPR6f9cvB1xmvv3DHa3qQa1ZjvEaQHkqW5Nkb8HklS8PfYjhoIokRTtQGqIqiioHzXAT60EUDN1qvxDr0hrVIqjuCb1zNXDXfGMHtU5ds2zq5vkrKnzuSF42j/TbHsRIhjQ6xs0/Q2DZa1EVVr+KOJjkAp0vuCGI6qr3FMH7uE/kBSVZp4EDmQ92fzi4ezN7ftYbr5MwqqYP5T2UPHHrrQTwxOLhbNlFOlXHUpN2Dg/c+MDg4J5u0Vq1qc5Su75tyYez6UGPZwW6oHqHx68ngfKuu5roxNc5NSyYHpQYt1QxVW/CCwXTL0Bd8YDFTscw3oKqjo6VSS8vojtHpyYnShtLI0EBvoTO7BGb/250u7jpaa30Tqn7lwjVJwn2vTr8FnE9RVBEZK2o36agyN66W3zX1tzFXflRfXjH1uQuGk1MXLhv70zwmeLvrNzmIcL6Wdoy4dCNX9a5kk+suycdomxGVqcAOyZyL+OZFVEEM3Wcs+L1c95ioeOjqPNMsS5thmkYKh2ZN7r3myvnL5nZMXzK5eWz9Ojtju46UtN8BvMoRMvoW6XAwGbQTgJAeBuhDwSytaopYlaqbKHLBdGN9ithaH4kT5FGSeN3en6zDJqOZ4zWPrnN4hut67e5zLbLymCrDz0y9XOqo9XaUoJ/KPZIzepzHo3kj9wUclS/VviFGZL0Ykde/rl3Dg+f/1WObps33rkfepuzAN57/18LoSIGHRSMuj8QhYV9uCjn3gNi7I8rWskq1VMIGsaxFXt1jtPpJUVu0TkceHQuuBwewwYFMqrUlFGBRiKq0V4lYgsZuIwdHQTUqlqYLQJEa6RQxbVc3zpazIkhjmK8jobY0DO20anfDt47C1OZev7d558ZYezaFaX7zH8Ntd/7krlz+8G+3ZCQdhXSZSx7Za2t2QPPPXgN3/gQCP7mTH9t2x+TwDV3x/r5CZigiKdvuuPeObbXnr3xoQb4yixQZxR1dlvyKz9Hj8XC+eM8MZi08JPwlrhP9zrJ4talFrM2gLju6tDSTSaW5G1va6IP6el3gZJ+Xzi611fRJr27q1B1/eM1dP7kT5hutAo/5mmZtvmNhNbaLxuNR0a4OtoWtqw5tjnMG60BiXUilKGBbgk1FilF32ykx0je40LXJR0DikyPV4aF0Ki1f3HiyJ2dL5f51vKwhZa648C+6K4hC6m2ThDKSrQfT0SoNonOulMP+hQ48udhb3LQz0sJVD0UVSBKgtO6TN26DA0+eefLAmalRxaO3GLKCihE3tbi9a1Ox96PXtwd3PzSycRK84zPw8NY7Jo1VUYV2a0CyiOKGBAklFgp0Ht2Awzh5xw823JjzOWbSkhSVIGSCrERXGRNScWXpzonufO5md+1ewCeCNGOB1u73eHQJWL+BGDotHBLStJ/VRpBHNeDCJ0kgsKQAEmpFvmj9AXVxtnPH9ksScWcwilIQi0BEvwihG+gMJYRYMcFJXXsTgMJbgdJW4e6dH9kFQ/3lcNjflOjOY2rXR3Ye+OJ+fvDxg28MZF5+KwBP8WMD16xZsSvRXrDMkKoPXHP9vtXjk3fcf/s2eBO4nz/65jD/bzfWbZgHpKekHcyDfHGEvafqIcDCWGcLwr2+e1i3BkQwlL2MNqO5mpxYFHJikeVGXCznF2z8PReV5otvWnyu6mlLhTqdYDocEpG0JbGGmyJrcbEjmQ26vhWRgEKEmTwvyAuof1iuRx8Mgxt3ELUTEpw3kr0QGug0au/nZz7cXJo+OF1q5p/Jt55DhfZcaz5e6M2E+B3XKe097cqB28FJ9fYu6L1Jw+gagN/5HHTFh1anUquH4rVnP9eaRzV4MN8aK87M37V15p6AaaF+mYpYZuCemW13LkyX6jTWhV2J5nSzV+xFKfajA1VSwV3loI2QEBYqwYJ2jMLuNzY5w76n02RFaHlVz/8rvYU/fZs9fJt9+jX01vl3T7WrKRiQuHBjXubJ2IhpIOPIZH/W9V1qbOHa2HI1LIm1LZTts/UtVS/YP0OBuoHTD473CZ9z33Fh8jxOZlM+XNshzKfwBdqdAPUkWpuVTot9G2i/0rVsrrqLItSjoJLziSQbsmQc0kEVu02Rm5ksmfK8hVKbwilGGRFa5bSup2litwZtlmlMmxwa6Otd0R3u6wqHk8HG+lI//RfdSMJFmyOmlzZnqO/7Ee0LuoKaiEWmhYsztB0FvDTntNc+zk80dgT0qMfbnfNnnYRwJ6t2Z9ojcFO07N/pj0F6xbB26syZdmeudpO7H6IcCJ7VTJijvY/mPOrZnipAr9Me8+0MlJ1Tw4yJ2JRfSz9GeNAegGvYMBsHrRqpDg/iUIHG5BJteji2HmSawLRE18s0SZY0+RC55QDbi4CUEWwLKGgzVQH1CkaRUkQ/idconOJHLVraq5dHzfmtH4j+T72oumr5IzJtFPhWz8zNzVURL0Y3rB1aVejKJlqcMG2+YRtkK6rkcCQiFJioUnxnY0eNflfWjgqXyFw2nayvuSaL5bUiXtKRoqiLgpZzfW1t+EV1Z7UfIobxlBHCv8z8hlovrb3CM+mEIWktuun1CGmunIFnOkpKRo9VTtfuPs2P9J3uC/QEdga+tn7n+rYy3NOoovb1A24FI/Pgk8NqHEWFukRYzmzSsAYdTj1Yu/tBKJROl/z+nYGepf3BVrIe8jVZ7hXNmbBG5lv7ljmsi5VjH4qxUdvRkIDkXCezSjnZWEiWzlq1H1lx53hl167h1Smbog6Dqqyr0lhiN5w8TgvHrRb83G/WnrdwkqqhRG919+q2rIysx2MqPlv65OVHtvzwlFgpft66sIdZgXWzrmqW1sCIXohVLaDtXS+KIYuG10ZpTatDeMgRiaPtPfpLBUU4Riz5/NNSYHsUmzsGpqxoQYoKs1Orh3ftqhy32w3sA7UzHuPH4eTuxA8v/6QcCsimB7m8lG1bvbvamwip2EYLxfWEiQfbf+qHW+r237NSP7PZAOuudmZJ10uKCRMAaeOrojVWV3ryrfEMyVwddcd5t3mogGmqHVVsH6f9BSgCrUymyYsKRbEULQvsFTukuC0O9vyDZUkLoea4bimWhHJHbEfmxssiuioAjs0NRCM/uW7+VznaP6Xe+GA4mvtHK279R/vMpk0BB3m5/7k9nT3a+5eXMczob/sPLrg8/dcoSx1nQZZCTXdTdYNFcjaKwZJwsUdJVvipcMGYiN0vkrOnLJw9FUUQd/LYAwUFqYwdiTnCYy+bIz/GAiA6kWjZTi4XWkqN0MqO8FsrK9h7uT1D1uxyD8g4eqd33pa655l7Urft3PJPID9f+1LA2nRNwAmM9loB+L61rfaftb+r/ec2y9oGOmRB32bBwB3rBzYIf5UNA+vvuOGuu2Azlr1moxUIWL2jgW+Hw795332/iQr1bffxB261XZv+16StwqbvRplQNMeYG2aCmcKbk5gVZ5Orm0XAUZiWpBK83IjycJeYSMXDGXKuvlkg32nXXnQG7do7Iol8609bxyNwyuaziTzPVjNqb+37iUjtxQjejIy3nm3NAybfEanLVF+TM/X2lCgyIEuiELjhDSLW5HoK7cCW4anRtPamLmwdOQi9aeuidJPYKpRLlJV+G41+FhvqtNrNAVAj1Mjx1uMiI/JWvWkVNwNBfG6QshKNPtKc/7Xwl0qzcrUPhW4UwdkhTrEPCnO3B0CKLTbG9CioWra3tcTscMBnqCwNaU1shClU8oYlRGw9iHy0rqTTSmE0wrcKg4TYCaXdCZKzk+3P+BxaZIOHE9Ha18XWwTDitMMTAmdIwCAp54l6G1Fu8KDOhWMgi3UGTn5vFJuJcqmETURMJ9ZCxhwFJsMd/elIZslJy5VmcDAEd7+otUkhjUmnHR82Z/rmaehf3lK4idbOg/WF3MEZPrX24MWtPUjezHU/I04xSRrKW/lqTqHdl0S0hdibjGIOZurGJhkm0+GOMm1MhjiCDaBlVeXVIKz7UDwBrQJIz1wMPbIRPXxQNOj0Rc3Z96EP7TuYqPs93cO/yQJsFStUuwvZjljE7/PqIHkIfBSZ1OBB5OO2L9+ZTiWDtlJ3d9TSCEFCz6ABTkXoUxTZR/atsuv5t+TzEXVsKeT4zpwixyNYj/+PpRSNa0rt1tqtmldJy1yBD4Z6w3eJwNRbVOis/ZLWzs84ZGrUa/8JmS7a4nmk9nUs2qX6FNjm97/jIFlMfrRPDjT8uGCfiPkqkIzeBoraHELNWiepUHi5kymVvNvF1wtIbEHxFk8X5Nt0Nun0CSeuZft3Vfr6lxzaCDdob8xlErCQePfagdqPBA6k0+JEPU37nWWeANeMiQw6nnFPjq/2DYe/a1FcLroicB1P1qB+TLs+L/4Bq0c3+lHoyjKK65f3ig7g1FOQ9yquE/ZyOb2lmnMLEmq9acm5qpGzuxNZMRFeI9bbPklsxLV013VeTrlrdLQ0RwHYCQ53+/Q7DZ/PuNPwPBaIZZsj0QQmdM9EPhkvpTIxu1MzNe0ynctzn1+xe7zwYSwI4hnwwMZEKdUeMr29XhN1BaO5MB0OtBdTEPAVDXmTGtA/lBrY5fptunEMOksSP2/DXvgQJ4jRsYaRi3xlF9woGYpbED418KZRCicacQk9A68bl3D7W0aBuHzgT1DWqLIQq7B3sE9XY4sAFnlWdFFE/ipUyXflcEAUHMhmHMgCISMT7oUy/hbJ7HO9CaABGUoXDJBxFiLvXmCW5bFQ1S4zC6wjb/mUJMkzeJKlWZ3imXB8o4cPHbjustnpqZHq2iHaercvUo7RtrugarmClLuAxBfit1Iqjq2SK1cScqURmBW8kEvCnEbSkI8ve3yYi13KlKUn4Giq29YTzQXHjad5H/+O8oTiN3sTieactxDrbM54k92eQKK5J3bK9IkNITH7VEtPe7Mn1BSIZUJd0fJI1n26JZ+xA8HmuCeTKZSrne4DfFNpb1cg2+zhgm6c/6aOVXjURZz0YJqOk7VL1zQl8rbNMVd5XIH/u14gkEq1pNZ2FEcjK5tjDoinQ+2ZpvTa4eZqb0/KI7kP1PnfI9Ij/J9YlG2oVsM6Cli0VykfU6A+yVSKm0FN5jURJcAidijo91mGKpOltc4MAyQ4VsimFEDep/Vpub5h4PbnX3zx8ycPb5q56tYnnzz3JD/55S8f47/g//SL2iO/mJ0YehLYk7937Nvf/swLdd8G+SakdQryki3V8ZjFZSUaCuD4B4WVkXjckfoumI3oRFUIItMqCSI0oeirC60tEdvnpX2A6MMLTrcSoU8u0JxfCcIFTcsJUk77chGhk2/6H3ePSWdtc7T2SdWn3n52EcYUPIO0wdC9/4HTfFza/fKL/Mv/7jENKqPe/tODsEmlEqO6Y9btUCgbkb97lK1j49WNg9kMl2XFQt1TmKyFgIElFcG7aScNN/qNWi7WYryw2esp9aGU4fdEvdFQRjhnoMBUt6XkosWSRqIF2VhQxdNctiT2rlxuYtGEiYW2HJT2pu/IJ87N3DgNJw2jvTc0wGdl9X/8TAnIK2V1v0XmFhtuPyC351MgbzzQ+gLmwE1/er2wtAxcGQLX9CLdqgbkl29SuNKPF7uD4ddaYMzx0tOYv+R3K2DhQVjk2CXsA9WWyWyHT1alfoR/ErAIjpo8VolxlXT8ABIP1BVVWQWhR3MZVW+V0QdRyLZP5g5N2J1Iqle85K++4uLi/NCblp+rejaOhkOdmXQ4pDeMVf11Y5WBJF8TAVBLRrpcJYl/Tjj6NqDMRwVkO43a38JPo/sm+nYVm4F/tg73HfCZPam16SdXvhXcl8xcEILPrRoBsuM9e84diPHrPlr5U/6x/rc5Co24SxET28pWVPM4faC5KeT1GIqsC1eVhixJS4Du4l+0qVnEfJArZpTMxCgKVSjkktx4yO5AZ+lvIpHndsxd9vL+p/c+vbH5srmZ5yJDndx57sRzzgDfGRlwnpuZu6x5I+ZeW9s9t+O5SH6AMiN1uw/yE/4YM1gzG2aj7FJ2aXWm1IKosEPF6Tu9gQOfGunK6UCb8LhuTKy+ASxrfCaHDJKu8IuIJoRLYvsym5zcEi40ZexWjbg+eW+Qnue8Rsmr2BG1ofRSFBs0nJeixPfJ5UYT0ThhUlVoz8608HfKic2goNzduxpQHZyHA8tUwacHXxiMK6Y2ajRN3Vm0rF0vf7RYbFNMyWdlLDAis5s/IZ+znNzMD4513fRnG9dfnu6/qt267pL0gbWkJH4Arl2uIr5Lhutqu68rGjnV1PKZm7cG86ET95plQ1VtFZTa+W23tUCsaT4czqxYODBh3nHdNdV1mavK4YZ/2GVIQzNsjKJbN3Rw1ViJQmMUaCsm5J9jzNBV3VAPUfQgVxU3LloV23xKpFocoh3cFF1dqO8BsxQOsGljtqOj3JHttzOmCI2O+ERQhKZekBlVP7hfAai7gTU+TIV4lCUX67o7WaWfIC48xBxpS+bHH9t+79D4hdDurZ3XVCZuz6kx2YMsy2cH3LvT12/Bm1HFc0TzQOYnH9t+Hz0UI/P8R746vGbcjSN34tbWTB4mhs3VXg98uX5nq5tW5XpJd448hTzwP10eGKB9joK0wxBtZiLR/kQuDwTiga+KBARmh/xe09BUWVrGA6PE9QpAfqN9xUqU9NxAGe793IvyLV/+8gdkZIBP3rp/26bDJz/3i1/w/zz7me9859gjT9aeHJqc/QXM/KJuK/61vFLIyahPsSLt2tMU5MKE7QqC8/RhHyR6QjRy92M1hVfGDJ5Uap+qTPZ0J9uB9a7oLvasymXa88l8azwSRuUV5RsL21pxTYoi3JWWi1H1ERt+4k2xASj01VWFSr9SLOGI2X1nyXGcXyGsfedPeVR+hdjk+rRm8lfmSh0vn8iUAWmhMWcYd5U64G4qXrsJiz+/bGtQ8Jx/qSzUtnI5FGrEie0W+5quY0doL9qJkdWSpGB36wFdDNkrN1EORsKl6Jw+Y6TJEjmGIblX9yAFM2aZgaL35sMHr927Z/fO6Usmh9euKnR2ZNKRbgt5aGVJVCZf/lI5h6clcY88Ht1oczlK4Rf9pRzqA/Q9rCztIrcUAFtX7HPZC2vwhOQkINJSKxZAfD7lBi7xEz3xp8qQqvh8dtSb4I7P45j4M0MhzfHFPXF+4+YNizxgm62mEYqNtSgRSC5sGy/v37jeIzbg/pi9pj3u9TgxJ75yorPlqoFdS98dgqnWPLTf0F4pgX9kIubNSs22r8nS/RrUPs5VVZflrsv9ASPfGYrnfCkDipFiV8juzno8q7u37W6ORvOtsDeR927OJ3yj47FIZtv6vtUzjbW0OWH3qbIb6Bt579gf9MtiyxuSypkYDDBMoO1TOTPEYOiKpFMAlSorKi2SCe7r1TZTzEa+I9ORSDatbaFo+PCyYUiLZcdySXx4jEBKkUR+7o4ELUjQRn/I1KJFMVqCbGOmE3FXjlHEp0XMC8FIdQeHVlgKWV4ailhI90CraQd4edXY5hsDXp83EjL1UMhwLPwfML0Jb9T2+coZKMFT8Z6xmcGrWnKThbgTdlSf2ZJcY3+MhuS0Z/34gYF12xaSEFFa+GxjPGrfCZfMlC8bD+Y7zYB/ZaFLEVu0cRwO07TNRMgbzHpjEyP+cjl5fbKnZc0uZE0jWzORULm34I13b/bmE7C3NR+NxWB2q9S92uPJ5iOhLlb3rTwpPVX/flbz0ldMxthsdWcL6okGuJ/Rkj3gkYFIt+bRcXJ4mUf2ehZUALFnkQh/xyHcw0yTXFEsj7l508aR9fTpkt6VYTscs+nLJbTjRUfj829K4+t4tFxfv1ApGrJ+4X5FzgcX31HcwpiVJY/rgR6w6x8iqR2tf6KE3485ZLY7/9PSRAl//Lfq1r/aJ4Z3D+MPOieuHYeJfRPjrt/2B1vzogQcSIga1FTtz+q3uLeDqijVvtnIgmtXUB3DD5ZEFRONfSielAKSzspsbXUAKQkyPvWQQFqZ9reWNFKxNSQ6Gm0RcLFkkY1mO8KRSIrCAekrgsJAPgy0y5Mw+KdUMmESkpZygs3ZYgcQsdW4CIgGRwrsrm5ZXdu27tijVZhWUgGl9oWeo9OQh8fShUwB0rX/7gsHHN8/qxmf+s/p6opVaRhdvXX9bAUerz56C6yr/Z4SSCmwo3vmXd21SzK96QIW8Dkp/49VvP/PqVWFKtZRrsds/Vru5ceELpIWXkxjbAe7gi2ym9gJdop9kn2aPcHeX717HARjSbAWJdEy3xyN+C1FcWyficq2Ii80Bb2GLMUCHp12M+RXhjUOIZWzOOJTG8RbW+PTeIq3zrLWeOvkgw8+/vuf/+yDn37w0586/cn777v3w/ecev/dd5647fgtNx19x+HF/XuvumLP3K7pqa0TYxQYt7qv/q/Y7m4jh1QApQiazMuuc8uukcimk3RNXyx4gzLRt3E/enGdb1Wmn9LkbQXLYr+Ic54x+g38jRvHDfyNuyk+ZNQShgHPG7WPG2UDf/WMMzql9LF6yj096xapPe+eTzVOWOMwXoy9fIV0trPt5SuIAEmnE/lviafudI/uo99/za17X3PtHiEibtV/T4lCRm2Hu+hb84g94l9K5BuxYb+WdvOn6n5wlWqpE2RFGB1Re1CYwI+G0fHVBrhsP/7vW7K+LftCgLRsr41oPZ1enp6zAy//q/sZgKCISHrD1DLLowOBcbHkLo7gGyPj5Bg9UNdH8UB7P5isnQ2wafa+6l1jYOkrQVYjwDWJvMnGgl5uoWBsqYcCBtdQqdT4IT/JIDRHDtFOv7rHJCmEFhvF1wkl+aKu+2jZbXJwMJm0LGCD04PTk1s2bqiuSw4kB0p9K7pzHVa71d7cFA75fapCX48KoSTmfqWT9Em1kiLR0S+WD1CwcCguB2/mhHoqpd3bFCjQuM8bmyxUypl2Wdzmg9YUbOoZh+n3Q358fJPjmDNKz+2339GtzJxU1cnbd65c2LS6nRsz6sQz3/vuZhXvald/r3bmGk01ZkDdD+3QA6lrlZlpKxTj8YA1/dF4PO7zzZgouffyUpeqmTP3KgOrIZbKxPCuMjHNt00qePejyuws37NLoaL7b7hhP5UUsek1lPHKCP8Q6yWbvL58hbVh5ORcgJHWyIFPdnZmepJimZUMm6TKkeNTgpNmQRIAfVWBRC+x7botvnpajiaA/1Gibd8j+2Dg+pMwMH/n+NQHHu7/v45d+uD147x6+L6ZJjvcW0TITI+sjQZ1+d3KNb+3d3FP8us3jd+5Z1gaPfSOu+hrCjvvv2GTBIVg99HqjvfOoOgQ1B13zxnqB/FiiwWYI9aNEDcCxIHJY0aWZKB1SS6TaYeTiE6fRkI9VfbIm4N2MJL+fyu7+tg4juu+b/bzvnZv727vjtRpdbwvkscTRfG+RPFDJ1IUKYqyFdGRKEWhaVmOvkKRtimJta04QopAgB3bbYrENVqniY0gLuI0gQO0aeDETWwFSJomAVLlDwlB4KJF2rqF0SBp6uqOfW92j1+i24bHu92Z272Z2Z2dee/Ne78ftchDYdQcG6QYbiM9s83s5VD6Pf0vHrt0Y0Fa+tbvnYW/GB482SDcD3ig8ePBYXDwaaemX+yHJXj1kRvCenuZTyjVdvq8HlHk+JDNcB/JMY5Ja41jmuoYxuRVwxhWIQyOEezEM3dm34O9rsGrH0eGYdjb+Lbg6u9OeRQLHOA8ox+pPSjIXnneAK/onfeDqInzOmigzfs4JJ1HZaivC/MBUCRJOYIbRToqoHQ0EQrpuoayqiA4zKO6qZtBQwtoAT+2Q1IlFZ8RCn0MksOgmTb526kv7mUp9dCz8NAz7PtYb/aDZyk103gbnw+7/iYbqF/lTcCBbQpevVm/yj7RXIf4mvioSDOjLRRrPfaWmEZoumME+ALcQod3bNXiSX6+DCZD4XQx47AIRx29CMoOgE4b4SB0gWVAm1hOW+J923L99av9uW0Qz3DkB7A5hEHmhc/Cp14ay/fuh/Gd8M2fO+Lpz3PvAvzqV9zv+A03zjoiJFB7Lwgl1Eq39PZ0F/IduUwqabfGo1gZkxZOSgkGowe/uu1/x4aMc3TQaruaXQFIdMErsjEDdsAe2AbOFmLrt+z5lwdehoq33uu1vfNeL/s7vq3rlYppVqvmT+fmUm1zc22sExMmZja+TN/gm+kvDbx0xqAz8QSbzsTth4J0VrD6B/ys1FzjOiaqmAnd7jc8rurd5SfFfxVHhT7BqPkLIIxSQ8IEUJrlbq78VeUhZxWbFDiC5o7Sy/VjpPAqtVxx8ohxRRGPjd5/4nsnZvZ9OJtubf2gFPZvGe7WdNUz2toSjY1MnDt1Y6TcB8nOqcM/fOjSlUsnZ3ekGdtVDXpG021Ktn3fh598/OrHH5Riqql2D20JeCZmZz40MzIRCY0eGnz+8JHJB2qDqRR0hsP7DywcOX7si6OrOKtMY5/HZ6SlFg35JNH14eWLjpYVX7G1uXRDnACGwzPCfxnGr39t2AVjyc4nl4w8nDAKlFOwjSvwFF/EvnTFcMv4JDuFPQaf/oDjNcDRZrDPLlB/BuE+CVaYoUAIGj6vIgkRiMiOwYJmY5cUiUwxWB021SzAXi3008l8Eq7QvlOxy4LQxLml8i1a+ZUcWZK7/uIjs7DR/zediZjc0YdaSyYjjtZJAO6E1QnjRj6IP95lG0uNS1GbpSLw1JLBpprX4opdsOGyUVh7bR2+H5UJo6suL5YV4ZfWbZXJPT5MXl4VJPypvLGUzNtL1Bj2ed4+LNBKskSMCsSSmvrC6+K4KKPOlai1qDLH2lTWOmJ3hFw4XyxFDlO/LPbyeD4C8KeeyQbtruArjbfe+VLcr6f+9OETg55K7MeW3vLk9l3ZmB185R3ofyXWluzb/rF40PpJrOLZc/ziCq5QiX0Ar2sXld4Z5qU7IJ+8/8S2Oiif2U1gRrHjb5oLMwoFMjbukG1VDkoJRbl1S7kpJ5RmJu4kZPn2bfmISjsg4+GYi/tB6fYtRd00t1nf1Gp923wb65uIJ+5Cal0FaI1tmguTcuO/sSynnrw4LPumjHXl9bx1S+Z1x8bAbpWO5clbt51zGndkbdNc7LdNLDD/enxmB015DXKXeHwNPhfMrEHhEpfvLD8nviAWUB4MC53Cqdr9BBefTsVjkqwyop2RFZWvaSqydEEDARV1lJZnXRJuYmEl1wgvrPhGWBGvB4RcJmlvaYl0Wp1B3RP2hmVJwNmUTHZQQfkJpU4hllIshbTNcklEcYhcufGSkXGVghrx1sONb0FQafym8fuNf1fA/x/5bpZK5rq/bn+0FOqJ6958a499eVewFPX7M61injV+6xzphScg9Jme1kSykNxd/0qpFM/k/mx2d3drKvX8WeeZ+E/xOvsu6gOHahM4LyWZLCsteAOiIcJ3J9KlMQEbvnGNblO/20w0mjZTHdw1i6A3dLGdu6STRd7ZDGLX6IYSGd5d3LBKVVEr4vVsOX906UsPfPZqOP6xU/0nQ2EjHt9zJFfIFlr2vbEgn5+4tzxYsfpL7GKlPXbguU+errEPsEOwvyIqgVPDzGIt98zmp07LVuTgWejzJ2sZRWj6AR7ntvOJ2liIOtaYCi42HAV1XeQEI5JAFgJalEbJTjyqkK14AgSbQ5BYYdMgAUZThAykvc4oyyOm0maxmnYYhXMcUCRtFc2mayf7K3KtPDsffLI1ty+99+BJWFho/IzyRN/s3vovh2cADl2b2oaC4qPy9NT91xqvze5lccxu2lCPc+z7dqFKMlkIaMlUI3c5DScETZjz0vKJoKAi48PB2cfmUICTxADpcCij0arKDK2zyBRNI/v93KnIPy34Zf9kpdjd1Z7LZtIp0wyl0+mMye1EUGrHFtBdy3EbR5UjPkZjvdy1hLxorKJVDLv+Pa4DaxTF4u8/xoLYA6v33FMtpXYP3Hf8CfbxYycfeWOQ//3xzEj9n6nhLDYyA8cNayJgJ6Jw+OLhtr7qE19/jC2ODA0ODg3CWX5V3Kswu4Lxx7mAUILqJqb5DpvJUtTSiaqBRm9ahpXIWEnsE7S+AXyGoidQpPmxkE+3tcZDQVUR/OB3jPqrxC5hF6iHcGI42lO4xM3BqmKFHeiedRwhf+NViVLUr15QUddTZO1xTVa8Xs8ZzbuWdKI+fp4M6efp0O/BCVWUZVFtfFHRtBUcJ8Jo2S4ME8NuPw69Wb5IIWDXBJUWPokygmNCMkm+0IR2WoF0Wo9THYlZ7eW+cpEjW23mXJzezLuYYqXDsIoNuw2agO6hjX7FOOptcCzG8U7yXWv0BTL6u7o+qGf0T8M5TAwF4ODX1nsUy7DqUfwpTU4rxJPW2K3r7/LjA3RiAH+B65RvihX2W86Dna2ltiZiplciQDgY8wCMRtdO1Q4i/B4ws9VcCOfoGC3rgaxGSYZs57Kj+teLjXemF6XLp5+WTo9PS/AFiFBy9jl2CpNHH3l6sXE7ePQxOLx0OviR4Pj0e/AeJhtfXToVOUvJk4vczLD8D8ufkWwxh32wBTWMTmGydiBFJBlj7gI1DiSMXFso+gdHEhxkUPnFLkgjI7kbgjipB0DoaCdMIysUaNFbNJl6o4fH/+BMyUnxcHZvp1mS30Cc5sOl3kgqzMVJvjzN2M5k42+T+WH28N5CqvHDth3n9+CtPHO1/uzjF2pQHmq/fv0PlbEDSvdwgb1erS9XDx+uMqieu1BfPvfNb1xgcKG+OHodntCN114z9GQ3623GR/5GfIXd4O3LCUWhJhwSBmp9PpC9wI1CHuJX4mYh1fFFU7kvmsKtI5MH9o/sGSgVnVcvhSyUnUfHebTM903gE0btLvHeab5vIm8n7fz7fvxL3mY7aL/+E/rcfH+IyK7zeWDrt/UvbJ4vaMvLy0+Jfy7u5LayNB+Dx4SDwr30vHqBGcQ7o87ooPh4NM0sSgMBoGlkxs+lgCN+LgVgt0Ux4F6XE3n/yFB/pVgyccwtl8ucEdtqsu+5dsgWcOyQqjvGUlS6SbhWG44Lu/ZKa8NxdhRsKwl2tPF21F6TsJLHb+If/GBdHlCKjuMH/Az/hikNSQszbJtV8VjSHN6mvEbsJkz+/Z1//L+OaK5xvMnH7SnhhHC/cFo4hyr4o8KScL52JmNvsfDiPBRAFeI8XsgxfGwlUtYp5npOcAC9BYpVIt8AfDqIJUwGggeQ5YuaowNpa3WgRx95eGH/vl3VnT2Frq0JYQqmPO44T7ZKhVay24ckWp1qJzKoXErtxmEiplgRW+QDR4q0zB1ASEBOFLnNKsUqhSwo7eRdPgRihci4orFK7q6PakmJVKq448wVL18z9K3J3l2pVlawWoIDllW6WNbsmtESKcRTu3raLMXfmkvpvqTf59eYJkr+uKKoqY6cPwBB/drLi9/5IyYrDLwRyad6Izi/eBNSwBPIgimZuXA4CSEWounm0tO/6BJ17+VKi6jbhfGekZ7iHjmqBwxDCbUqe4o9IzvGuxNBFsnJSjwWIsJMryKKiq0HrFaNib0J5tXFrl88jVJw/XkK9pMUQ/QpfkvSVT0iBbx+jyL7FUmFAPiIrUdY0WWm2YNcN+iqdWwFJqvAo3hlJjtY+RL5HTQJNeKxCAclz7pY6+t5Jcqb5rJprtrcvuWoNTimNlWUuzMvqxuUB1IofqS6mo+bSYrCuvpfatY/i4KCwQMRcOgGaZ5cBWAV4zUSi1M/4CDm6wkYnLic8qa5WH9UTIISqWFUZ9RWeJ03y4QFdaO6hpX+EWWicsYzXYVJadohYIbrZrFaRNzAH9PS5La5m7AC1UVH7VqnMrKUKqPSp2/QBN1yJpvl+NaXk3A5dP7/at77aHSu7j8NP3XsKupdfCwRh+Hpd+glr6vyStnNey+v2nCm4Z+csoy7eG4cQ8PvckfhL51m8Uu3UuqKXZLGwJ3CfuFE7VgeRHkrSvC1LJO0/iJTaNQjKjaNJD2UqHCcA4VEvDlyg9ZkvnqpAIGZbogZSefaO8uxdCzj4U47OqMlA15d4tEr9g6JqB+i8hhdw8HBkcFZdjOQa05SfHT2KwOSPKmYUuflkd1nD+dZ9+RH5093HDLDLgnH+MALR469uDjc+JN16NcOafGRA4ouHZJBKe8mfuPzk90d2UPB7Q4Fx3jf0L7FFyfec+Es3yKSJnnFny0oRIQsaqB9wpna6WInU7U2kNlWK+DHCVUckyjIWvGLPpQFCRcYpwjUx2k2wGlBuOABVW2CdZJlLCAcME1BqJZ7d27v6mjPpLah9m1GzEg4hCUZ1QA3PkcUdSVMIYxTKbdGNzPojfJx1kq7F0he2SOeAteF/ikpAJ97hvVHdZ7E/38LSI2htzgEJ1xxtuxzjfvwm8a3HeC9rfBLf2MJrjf8rhc+7MX3l/2vXnW8h+nzfwBpRuuSeJxjYGRgYADi8itp8vH8Nl8ZuJlfAEUYbt08uhpG/3/8P5NlP3MwkMvBwAQSBQCTxw8bAHicY2BkYGAO+p/FwMDK+v/x/ycs+xmAIihgIgCZggbkeJxtUTsOwjAMTetWYs1JKiQ2Nm6RA3AFDsDAGRizVuIkXIOlIwMLCGr8HKcNqMOT7frzXl5pcI4E9dW5pmOmKPVaEBcgc20rMaRcMfVsF988Pynwh6JEneMXatyvHjwq3xaQeu8ckDVMMRpP+MsL7uqCnvAGcDPjZuKVG0fre+SYWXhPwJvtpreZEr545x9UM7iX+nYXGrIWROyoxrwn+prDjHJfe12haUh6sl6dD/CSx2l//hf3eiVcJ0B8kZx22XPz6MzvX1+Tb7SRezXfqJe8T/zQr37av8p11vUF6jqIwwAAAAAAAADuATIB9gIMAioCWgJ2AsIDRgPKBOQFagYABrIHSAhMCVQJzApoCvQLKAuMDGYM4g3yEfYSMhJ+ExQTPBNiE4oTrBPiFCIUWBSYFNwVIhVoFawWKhaQFvQXehfCGAoYnBjuGVgaBhpsGzAbjBu+HHYc9B1+HgAeoh+QIAogxiJqIywjtCSwJYgmZicaJ94oWiimKTYp8iqQKvorRCvMLMItFC1qLdwuVC6cLw4vYC+yL/owYjDGMVIxoDKOMtgzNjO8NH40yDV6NhA2WjbSN5g4YjkGOXw6njsEO8Q8KDxwPKg9Cj1WPdg+Pj5wPrA+7D8eP1w/tEAMQC5AqEEYQXRB+EJiQu5DOkOqRDRE1EXCRiJGgka2RupHIEdWR+ZIcwABAAAAkQH4AA8AAAAAAAIARABUAHMAAACwC3AAAAAAeJx1ks1OwkAUhc8IaIToQhM3bu5GIzEptYkbVhoiLFyYsGDjqkJpS0qHTAcTXsB38AF8LZ/F02FUXNhmpt8592fuJAVwgk8obJ9bri0rtKi2vIcD9D036N97bpKHnlvo4NHzPtXEcxvXePbcwSne2EE1D6kWePes0FYNz3s4VkeeG/TPPTfJF55bOFM9z/v0Hzy3MVFPnju4VB8DvdqYPM2sXA26EoVRKC8b0bTyMi4kXttMm0ruZK5LmxSFDqZ6mddinKTrIjaO3TZJTJXrUm6C0OlRUiYmtsms7li9ppG1c5kbvZSh7yUroxfJ1AaZtat+r7d7BgbQWGEDgxwpMlgIruh2+Y0QuiV4YYYwc5uVo0SMgk6MNSsyF6mo77jmVCXdhBkFOcCU+5JV35ExYykrC9abHf+XJsyoO+ZOC27YJdyJjxgvXU7sTpr9zFjhlb0jupbZ9TTGnS78Qf7OJbx3HVvQmdIP3O0t3T56fP+5xxdzvHj7AHicbVSFluM2FM2d2DHMJLOzZWauy90yMzOjLMu2GtnyCiaTKTPD9osr2U63c05zTqT73tHTg3vlydZk+KWT//+dwhamCBBihggxEqTYxg7mWGAXx7CH4zgDZ+IsnI1zcC7Ow/m4ABfiIlyMS3ApLsPluAJX4ipcjWtwLa7D9bgBNyLDTbgZt+BW3IbbcQfuxAnchbtxD+7FfbgfD+BBPISH8QgexWN4HE/gSTyFp/EMnsVzeB4v4EW8hJfxCl7Fa3gdb+BNvIW38Q7exXt4Hx/gQ3yEj0GQg6IAQ4kKNTg+wRICDVpIdDgJBQ0Di32scIA1DvEpPsPn+AJf4it8jW/wLb7D9/gBP+In/Ixf8Ct+w+/4A3/iL5zC35OkILrOJVFFYDVToV/0llzOKGkpE0EnrA4b3lq9U0pRMJWxpjPruJCrVkhSzGznt2nFTUhtznRcEENyollYEVuxSHPDGtLtaKlM1pKGZbZbnDb8PUnDKtLVsmXT3FahIXqpZyUXhqmpLMsgl3IZdkQbFmvKtQvWYSVkzkIqpC3CUrge4pwoWhNl+tKygitXmt9iwUrjQaJ4VQ+oPyI71qaDz8PIHfd70p/3yF+Q82qMc2i4yoP+Agd2FdP8kGWlFSIjwmz/x94ZsW6IEEEj99ne6Kml4oeyNURs4veZMpwSER1K2WS8DXMh6TLuLWlNInwJuRW5b5kuk30pbD/K7RH5gtIR+5k11rBpw2nE2sLwhiXauNl4tHBpnJNsmNyYs5ViLa0jLbijWcdOCPucMh2NIOwZih0vLOuKMunBSqoi7RE7cHJxc6GZYQcmNMpxMqeyaVhrhkzRaAWOJpP6ZfAHORMi9ouf4JwY4w5x2Xor7BR3EazgJiqlWjmdhop1Yp30qzsipmzNpoZUgfvruZ9OT56PTv61Ao+CWjYs4G0pg5qJbqaZl0zs5NN1vK1miq14W6S9ijLBXbN9UW6ciw0YK3avoopcXu9KiFJypTO62qIrl0KbxNS2ybVnZ0SenUi7JC1TM9cgcWmXbD28OzcTG6x4yZ1GZJsM6TvOxkKIYmR0OonPckaW7p02pOI09FeeSHsx9ipLe4H2cHtQbY9jJ+QeTN35PdJWwgvG5m7zAcePePq4GeWKCrbjh5UNuIjMijtu1LGSUObfZKZPWlddMa/8V2Jj7Z0mcAwMac3oMnVSd6Cwgs0dj62b+KgLU7tpa7Nwez+vwb1wn5Ta5ptLdo8UabujbfTyHzy+o+0B9q3EA7ZduolwXNQun1TrNOetpFYQpSeTfwDl8f2IeJxj8N7BcCIoYiMjY1/kBsadHAwcDMkFGxlYnTYxMDJogRibuZgYOSAsPgYwi81pF9MBoDQnkM3utIvBAcJmZnDZqMLYERixwaEjYiNzistGNRBvF0cDAyOLQ0dySARISSQQbOZhYuTR2sH4v3UDS+9GJgYXAAx2I/QAAA==') format('woff'),
+ url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+IVLUAAABUAAAAFZjbWFwyZKXbQAAAagAAAl6Y3Z0IAbX/wIAAKj0AAAAIGZwZ22KkZBZAACpFAAAC3BnYXNwAAAAEAAAqOwAAAAIZ2x5Zug4Hl4AAAskAACQ5mhlYWQZdM73AACcDAAAADZoaGVhB/cE5QAAnEQAAAAkaG10eOjK/5YAAJxoAAACRGxvY2G9sOQFAACerAAAASRtYXhwAggNvgAAn9AAAAAgbmFtZcUUevQAAJ/wAAACqXBvc3QML0mAAACinAAABlBwcmVw5UErvAAAtIQAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDXwGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA8eUDUv9qAFoDUwCXAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAKqAAEAAAAAAaQAAwABAAAALAADAAoAAAKqAAQBeAAAABIAEAADAALogeiF8AnwC/Cb8Qfx2vHl//8AAOgA6IPwCfAL8JvxAvHa8eX//wAAAAAAAAAAAAAAAAAAAAAAAQASARQBGAEYARgBGAEiASIAAAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAAxADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0ATgBPAFAAUQBSAFMAVABVAFYAVwBYAFkAWgBbAFwAXQBeAF8AYABhAGIAYwBkAGUAZgBnAGgAaQBqAGsAbABtAG4AbwBwAHEAcgBzAHQAdQB2AHcAeAB5AHoAewB8AH0AfgB/AIAAgQCCAIMAhACFAIYAhwCIAIkAigCLAIwAjQCOAI8AkAAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAG0AAAAAAAAACQAADoAAAA6AAAAAABAADoAQAA6AEAAAACAADoAgAA6AIAAAADAADoAwAA6AMAAAAEAADoBAAA6AQAAAAFAADoBQAA6AUAAAAGAADoBgAA6AYAAAAHAADoBwAA6AcAAAAIAADoCAAA6AgAAAAJAADoCQAA6AkAAAAKAADoCgAA6AoAAAALAADoCwAA6AsAAAAMAADoDAAA6AwAAAANAADoDQAA6A0AAAAOAADoDgAA6A4AAAAPAADoDwAA6A8AAAAQAADoEAAA6BAAAAARAADoEQAA6BEAAAASAADoEgAA6BIAAAATAADoEwAA6BMAAAAUAADoFAAA6BQAAAAVAADoFQAA6BUAAAAWAADoFgAA6BYAAAAXAADoFwAA6BcAAAAYAADoGAAA6BgAAAAZAADoGQAA6BkAAAAaAADoGgAA6BoAAAAbAADoGwAA6BsAAAAcAADoHAAA6BwAAAAdAADoHQAA6B0AAAAeAADoHgAA6B4AAAAfAADoHwAA6B8AAAAgAADoIAAA6CAAAAAhAADoIQAA6CEAAAAiAADoIgAA6CIAAAAjAADoIwAA6CMAAAAkAADoJAAA6CQAAAAlAADoJQAA6CUAAAAmAADoJgAA6CYAAAAnAADoJwAA6CcAAAAoAADoKAAA6CgAAAApAADoKQAA6CkAAAAqAADoKgAA6CoAAAArAADoKwAA6CsAAAAsAADoLAAA6CwAAAAtAADoLQAA6C0AAAAuAADoLgAA6C4AAAAvAADoLwAA6C8AAAAwAADoMAAA6DAAAAAxAADoMQAA6DEAAAAyAADoMgAA6DIAAAAzAADoMwAA6DMAAAA0AADoNAAA6DQAAAA1AADoNQAA6DUAAAA2AADoNgAA6DYAAAA3AADoNwAA6DcAAAA4AADoOAAA6DgAAAA5AADoOQAA6DkAAAA6AADoOgAA6DoAAAA7AADoOwAA6DsAAAA8AADoPAAA6DwAAAA9AADoPQAA6D0AAAA+AADoPgAA6D4AAAA/AADoPwAA6D8AAABAAADoQAAA6EAAAABBAADoQQAA6EEAAABCAADoQgAA6EIAAABDAADoQwAA6EMAAABEAADoRAAA6EQAAABFAADoRQAA6EUAAABGAADoRgAA6EYAAABHAADoRwAA6EcAAABIAADoSAAA6EgAAABJAADoSQAA6EkAAABKAADoSgAA6EoAAABLAADoSwAA6EsAAABMAADoTAAA6EwAAABNAADoTQAA6E0AAABOAADoTgAA6E4AAABPAADoTwAA6E8AAABQAADoUAAA6FAAAABRAADoUQAA6FEAAABSAADoUgAA6FIAAABTAADoUwAA6FMAAABUAADoVAAA6FQAAABVAADoVQAA6FUAAABWAADoVgAA6FYAAABXAADoVwAA6FcAAABYAADoWAAA6FgAAABZAADoWQAA6FkAAABaAADoWgAA6FoAAABbAADoWwAA6FsAAABcAADoXAAA6FwAAABdAADoXQAA6F0AAABeAADoXgAA6F4AAABfAADoXwAA6F8AAABgAADoYAAA6GAAAABhAADoYQAA6GEAAABiAADoYgAA6GIAAABjAADoYwAA6GMAAABkAADoZAAA6GQAAABlAADoZQAA6GUAAABmAADoZgAA6GYAAABnAADoZwAA6GcAAABoAADoaAAA6GgAAABpAADoaQAA6GkAAABqAADoagAA6GoAAABrAADoawAA6GsAAABsAADobAAA6GwAAABtAADobQAA6G0AAABuAADobgAA6G4AAABvAADobwAA6G8AAABwAADocAAA6HAAAABxAADocQAA6HEAAAByAADocgAA6HIAAABzAADocwAA6HMAAAB0AADodAAA6HQAAAB1AADodQAA6HUAAAB2AADodgAA6HYAAAB3AADodwAA6HcAAAB4AADoeAAA6HgAAAB5AADoeQAA6HkAAAB6AADoegAA6HoAAAB7AADoewAA6HsAAAB8AADofAAA6HwAAAB9AADofQAA6H0AAAB+AADofgAA6H4AAAB/AADofwAA6H8AAACAAADogAAA6IAAAACBAADogQAA6IEAAACCAADogwAA6IMAAACDAADohAAA6IQAAACEAADohQAA6IUAAACFAADwCQAA8AkAAACGAADwCwAA8AsAAACHAADwmwAA8JsAAACIAADxAgAA8QIAAACJAADxAwAA8QMAAACKAADxBAAA8QQAAACLAADxBQAA8QUAAACMAADxBgAA8QYAAACNAADxBwAA8QcAAACOAADx2gAA8doAAACPAADx5QAA8eUAAACQAAAACQAA//kD6AMLAA8AHwAvAD8ATwBfAG8AfwCPAE9ATBENAgcQDAIGAwcGYA8JAgMOCAICAQMCYAsFAgEAAAFUCwUCAQEAWAoEAgABAEyOi4aDfnt2c25rZmNeW1ZTTks1NTU1NTU1NTMSBR0rJRUUBgcjIiYnNTQ2FzMyFhMVFAYnIyImJzU0NjczMhYBFRQGByMiJic1NDYXMzIWARUUBisBIiYnNTQ2OwEyFgEVFAYnIyImJzU0NjczMhYBFRQGByMiJj0BNDYXMzIWARUUBisBIiYnNTQ2OwEyFgEVFAYnIyImPQE0NjczMhYTFRQGKwEiJj0BNDY7ATIWAR4gFrIXHgEgFrIXHgEgFrIXHgEgFrIXHgFmIBayFx4BIBayFx7+nCAWshceASAWshceAWYgFrIXHgEgFrIXHgFmIBayFiAgFrIXHv6cIBayFx4BIBayFx4BZiAWshYgIBayFx4BIBayFiAgFrIXHppsFh4BIBVsFiABHgEGaxYgAR4XaxceASD+zWwWHgEgFWwWIAEeAiRrFiAgFmsWICD+zGsWIAEeF2sXHgEg/s1sFh4BIBVsFiABHgIkaxYgIBZrFiAg/sxrFiABHhdrFx4BIAEIaxYgIBZrFiAgAAACAAD/sQLKAwwAFQAeACVAIgAFAQVvAwEBBAFvAAQCBG8AAgACbwAAAGYTFxERFzIGBRorJRQGIyEiJjU0PgMXFjI3Mh4DAxQGIi4BNh4BAspGMf4kMUYKGCo+LUnKSipCJhwIj3y0egSCrIRFPFhYPDBUVjwoAUhIJj5UVgHAWH5+sIACfAAABv///2oELwNSABEAMgA7AEQAVgBfAG9AbE8OAgMCAUcACwkICQsIbRABCAIJCAJrDwECAwkCA2sHAQUAAQAFAW0MCgIBBgABBmsABgQABgRrDgEDDQEABQMAYBEBCQkMSAAEBA0ESV5dWllWVFJQS0pJR0NCPz46ORkVFBk3IxMhEBIFHSsBBgcjIiY3NDMyHgE3MjcGFRQBFAYjISImJzQ+BTMyHgI+AT8BNjcyHgQXARQGIiY0NjIWARQGLgE+AhYFFAYnIyYnNjU0JxYzMj4BFzInFAYiJjQ2MhYBS1o6Sy1AAUUEKkIhJiUDAoNSQ/4YRFABBAwQICY6IQYkLkhQRhkpEAgiOCYgEA4B/cZUdlRUdlQBiX6wgAJ8tHoBQz4uSzlaLQMlJSFEKARFR1R2VFR2VAFeA0QsLMUWGgENFRBO/ltCTk5CHjhCODQmFhgcGgIWEBoKAhYmNDhCHAKPO1RUdlRU/u9ZfgJ6tngGhNMrLgFEA0FOEBUNGBgBjztUVHZUVAABAAD/9gOPAsYABQAGswQAAS0rBQE3FwEXAWD+sp6wAZCfCgFNoK4BkaAAAAEAAP/XAx8C5QALAAazBwEBLSslBycHJzcnNxc3FwcDH5zq65zq6pzr6pzqdJ3r653q6p3r653qAAAAAAEAAP+fA48DHQALADBALQAEAwRvAAEAAXAGBQIDAAADUgYFAgMDAFYCAQADAEoAAAALAAsREREREQcFGSsBFSERIxEhNSERMxEDj/6x3/6xAU/fAc7f/rABUN8BT/6xAAEAAAAAA48BzgADAB5AGwAAAQEAUgAAAAFWAgEBAAFKAAAAAwADEQMFFSs3NSEVEgN979/fAAAAAwAA/58DjwMdAAsAEQAVAERAQQACCAEFAAIFXgAAAAQDAAReAAMABgcDBl4JAQcBAQdSCQEHBwFYAAEHAUwSEgwMEhUSFRQTDBEMERESEzMQCgUZKwEhERQGIyEiJjURIQUVITUhNQERIREB0AG/Qi79Yy5CAb7+sgKd/kIBvv1jAq39Yy9CQi8DDXDfcG/9YwFP/rEAAAAEAAD/+QOhA1IACAARACcAPwBEQEE8AQcICQACAgACRwkBBwgDCAcDbQAGAwQDBgRtBQEDAQEAAgMAYAAEAAIEAlwACAgMCEk/PSQlFiISJTkYEgoFHSslNC4BDgEWPgE3NC4BDgEWPgE3FRQGByEiJic1NDYzIRcWMj8BITIWAxYPAQYiLwEmNzY7ATU0NjczMhYHFTMyAsoUHhQCGBoYjRQgEgIWHBhGIBb8yxceASAWAQNLIVYhTAEDFiC2ChL6Ch4K+hEJChePFg6PDhYBjxhkDxQCGBoYAhQPDxQCGBoYAhSMsxYeASAVsxYgTCAgTCABKBcQ+gsL+hAXFfoPFAEWDvoAAAAEAAD/sQOhAy4ACAARACkAQABGQEM1AQcGCQACAgACRwAJBglvCAEGBwZvAAcDB28ABAACBFQFAQMBAQACAwBgAAQEAlgAAgQCTD08IzMjIjIlORgSCgUdKyU0Jg4CHgE2NzQmDgIeATY3FRQGIyEiJic1NDYXMx4BOwEyNjczMhYDBisBFRQGByMiJic1IyImPwE2Mh8BFgLKFB4UAhgaGI0UIBICFhwYRiAW/MsXHgEgFu4MNiOPIjYN7hYgtgkYjxQPjw8UAY8XExH6Ch4K+hIdDhYCEiASBBoMDhYCEiASBBqJsxYgIBazFiABHygoHx4BUhb6DxQBFg76LBH6Cgr6EQAAAAAGAAD/agPCA1IABgAPADsARwBrAHQA+kAYWVITEQQDCkgxAg8DSSwCBw8DRxABBQFGS7AOUFhAVwAMERAIDGUABggCCAYCbQADCg8KAw9tAAcPCQ8HCW0AAAkBCQABbQAFAAIKBQJgDQsCCA4BCgMICmEADwAJAA8JYAAQEBFYABERDEgAAQEEWAAEBA0ESRtAWAAMERARDBBtAAYIAggGAm0AAwoPCgMPbQAHDwkPBwltAAAJAQkAAW0ABQACCgUCYA0LAggOAQoDCAphAA8ACQAPCWAAEBARWAAREQxIAAEBBFgABAQNBElZQCNzcm9ua2lnY2JhX15bWlhXTEpDQj08Ozo5NyYkIiMhIRIFGCslNCMiFDMyAzQmJyIVFDMyExUGBxYVFAYHDgEVFB4FFxQjIi4CNTQ3NSY1NDc1LgEnNDYXMhcyEyM2NRE0JzMGFREUJRUGIyIuAz0BMzUjIiciBzUzNTQnMwYVMxUiJisBFRQzMgEUBi4CPgEWAUxcWGBUISIgRUVClhQYCVJFFhYaJjIuKhYCyyZEPiRmJiMoNAFqTjYuNvV8AgJ8AwFSKDkjMhwQBAELBwMMFTYEfwNfCCAILzAi/tosQCwBLEIqBThzAeEiLAFRSwEBcAcGGBdGZA0FFBcRFg4KFBYwH6oOIDwpXCEDFjA9DwMNXi5NaAEa/i8ZMQFUNRMTMv6pMWNuFhgeOiwkxAIBA2oqHhQXRWoCzEkCIyAyATBCMAEyAAAHAAD/agS/A1IAAwAHAAsADwATABcAQAA1QDI9MCEXFhUTEhEQDw4NCwoJCAcGBQMCAQAYAAIBRwACAgxIAQEAAA0ASTc2JiUfHgMFFCsFNzUHJzcnBwE3NQcnNycHJzc1Byc3JwcBFRQGDwEGIi8BBg8BBiIvAS4BJzU0Nj8BNTQ2PwE2Mh8BHgEdARceAQFl1tYk4uLhA0HW1iTh4eIY1tYk9vb2A1UUE/oOJA7+AQP6DiQN+hMUARgU8hgT+g0eDfoUGPIUGD1rsFw/YGFh/qJrsFw/YGFhQ1yVXD9pamr+dukUIgl9CAh/AQF9CAh9CSIU6RUkCGjfFiQIawYGawkiF99oCCQAAAAABAAA/2oDWwNSAA4AHQAsAD0Ab0BsOQwDAwcGKiECAQAbEgIFBANHCwEAKQEEGgECA0YABwYABgcAbQgBAAABBAABYAoBBAAFAgQFYAsBBgYMSAkBAgIDWAADAw0DSS4tHx4QDwEANjUtPS49JiUeLB8sFxYPHRAdCAcADgEODAUUKwEyNjcVFA4BIi4BJzUeARMyNjcVFA4BIi4BJzUeATcyNjcVFA4CLgEnNR4BEzIeAQcVFA4BIi4BJzU0PgEBrYTmQnLI5MpuA0LmhYTmQnLI5MpuA0LmhYTmQnLI5MpuA0LmhXTEdgJyyOTKbgN0xAGlMC9fJkImJkImXy8w/lQwL18nQiYmQidfLzDWMC9fJkImAio+KF8vMAKDJkInRydCJiZCJ0cnQiYABwAA/7ED6ALDAAgAEQAjACwANQA+AFAAZEBhLQECBjYJAgMHJAACAQADRwgBAgYHBgIHbQAHAwYHA2sJAQMABgMAawQBAAEGAAFrAAsABgILBmAFAQEKCgFUBQEBAQpYAAoBCkxNTEVCPTw5ODQzMC8rKicmExQTEgwFGCs3NCYiBh4CNhM0JiIOAR4BNhc3Ni4BBg8BDgEHBh4BNjc2JiU0JiIOAR4BNgE0JiIOAR4BNhc0JiIOAR4BNhcUBwYjISInJjU0PgIyHgLWKjosAig+Jm0oPiYELjYw6zkDEBocAzghNggLLFhKDQkaAVYqPCgCLDgu/pgoPiYELjYw9ig+JgQuNjCvTwoU/PIUCk9QhLzIvIRQzx4qKjwoAiwBFh4qKjwoAizw1Q4aBgwQ1QMsIStMGC4rIUAlHioqPCgCLAGBHioqPCgCLE8eKio8KAIs3pF8ERF7kma4iE5OiLgAAAABAAD/sQPoAwsAVQBOQEsADAsMbw0BCwoLbw8JBwUDBQECAAIBAG0IBAIAAG4OAQoCAgpUDgEKCgJWBgECCgJKVFJPTUxKRUI9Ozo4NTM1IRElNSERJTMQBR0rJRUUBisBIiY9ATQ2FzM1IRUzMhYXFRQGKwEiJic1NDYXMzUhFTMyFhcVFAYrASImJzU0NhczNTQ2FyE1IyImJzU0NjsBMhYXFRQGByMVITIWBxUzMhYD6CAWshYgIBY1/uM1Fx4BIBayFx4BIBY1/uM1Fx4BIBayFx4BIBY1Kh4BHTUXHgEgFrIXHgEgFjUBHR0sATUXHpqzFiAgFrMWIAFrax4XsxYgIBazFiABa2seF7MWICAWsxYgAWsdLAFrIBWzFiAgFrMWHgFrKh5rHgAEAAD/agOfA1IACgAiAD4ATgEiQA8XAQADNCwCBggmAQEJA0dLsBNQWEBFAAcGAgYHZQQBAgoGAgprEwEKCQkKYwAAAA0MAA1eFBIQDgQMDwELCAwLXgAIAAYHCAZeEQEDAwxIAAkJAVkFAQEBDQFJG0uwFFBYQEYABwYCBgdlBAECCgYCCmsTAQoJBgoJawAAAA0MAA1eFBIQDgQMDwELCAwLXgAIAAYHCAZeEQEDAwxIAAkJAVkFAQEBDQFJG0BHAAcGAgYHAm0EAQIKBgIKaxMBCgkGCglrAAAADQwADV4UEhAOBAwPAQsIDAteAAgABgcIBl4RAQMDDEgACQkBWQUBAQENAUlZWUAoPz8jIz9OP05NTEtKSUhHRkVEQ0JBQCM+Iz49OxERGRQUIyQeEBUFHSsBMy8BJjUjDwEGBwEUDwEGIi8BJjY7ARE0NjsBMhYVETMyFgUVITUTNj8BNSMGKwEVIzUhFQMGDwEVNzY7ATUTFSM1MycjBzMVIzUzEzMTApliKAYCAgECAgP+2gayBQ4GswgIDWsKCGsICmsICgHS/rrOBwUGCAYKgkMBPc4ECAYIBQuLdaEqGogaKqAngFuAAm56GgkCCwoKBv1GBgeyBQWzCRUDAAgKCgj9AApKgjIBJwsFBQECQIAy/tgECgcBAQJCAfU8PFBQPDwBcf6PAAAABAAA/2oDnwNSAAoAIgAyAE0BLkAMRj4XAw4DNgENEQJHS7ATUFhASgAPDhIOD2UUARIRERJjAAsNAg0LAm0EAQIADQIAawARAA0LEQ1fAAAABwYAB14ADg4DWBABAwMMSBMMCggEBgYBVgkFAgEBDQFJG0uwFFBYQEsADw4SDg9lFAESEQ4SEWsACw0CDQsCbQQBAgANAgBrABEADQsRDV8AAAAHBgAHXgAODgNYEAEDAwxIEwwKCAQGBgFWCQUCAQENAUkbQEwADw4SDg8SbRQBEhEOEhFrAAsNAg0LAm0EAQIADQIAawARAA0LEQ1fAAAABwYAB14ADg4DWBABAwMMSBMMCggEBgYBVgkFAgEBDQFJWVlAKDMzIyMzTTNNTElFRENCQUA1NCMyIzIxMC8uLSwREREUFCMkHhAVBR0rJTMvASY1Iw8BBgcFFA8BBiIvASY2OwERNDY7ATIWFREzMhYFFSM1MycjBzMVIzUzEzMTAxUhNRM2PwE1IgYnBisBFSM1IRUDDwEVNzM1ApliKAYCAgECAgP+2gayBQ4GswgIDWsKCGsICmsICgIEoSoaiBoqoCeAW4AL/rrOBwUGAQQDBgqCQwE9zgwGCJszehoJAgsKCQd/BgeyBQWzCRUDAAgKCgj9AAqROztQUDs7AXL+jgKDgzMBJwoFBQICAQJAgDL+2Q8FAgJDAAAAAv///6wD6AMLAC4ANABNQEowAQQFMgEABDMBAwEvDwsDAgMERxUBAkQABQQFbwAEAARvAAMBAgEDAm0AAgJuAAABAQBUAAAAAVgAAQABTCwrKiciIBMTEAYFFysBMhYUBgcVFAYHJicOARYXDgEeAhcOASYnLgQ2NyMiJjc1NDYzITIlMhYXAxEGBxUWA6EdKiodLBzp3CAmBBQLBAwaGhYRXGAZBBoKDgQICEQkNgE0JQEM8wEBHSoBSNzQ0gHtKjwoAdYdKgHCEgo0PhQTJBwiFhEgHA4YDUgiQi5AHjQlayU01ywc/dkCFKgXlxcAAgAA/8MDjwMuAEEARwBlQGI9LgIDCQABAAckHA0GBAIAA0cKAQgNDA0IDG0EAQIAAQACAW0FAQEBbgANAAwJDQxeAAkAAwcJA14LAQcAAAdUCwEHBwBYBgEABwBMRkVDQkA+OTg2NRUUJicRERcWEw4FHSsBFAYnIxQHFxYUBiIvAQcOAyMRIxEiLgIvAQcGIyImND8BJjUjIi4BNjczNScmNDYyHwEhNzYyFgYPARUzMhYBITQ2MhYDjxYOfSV0ChQeCm8IBSYiOhlHHTgqHgoIZgsQDRYIcSB9DxQCGA19YQsWHAthAddgCxwYBAhhfQ8U/vX+m2iUagE6DhYBYEJ1CxwWC24HBBgSDgH0/gwOGBQICHQMEx4Lfz9aFB4UAaRhCh4UCmFhChQeCmGkFgE0SmhoAAAABgAA//kD6AMLAAMABwALABsAKwA7AF9AXCwBBQs0AQoEHAEDCRQBBgAERwALAAUECwVeAAQACgkECmAACQADAgkDXgACAAgHAghgAAcAAQAHAV4AAAYGAFIAAAAGWAAGAAZMOjcyLyooJiYlEREREREQDAUdKyUhNSEnITUhJTM1IwEVFAYHISImJzU0NhchMhYTFRQGJyEiJic1NDY3ITIWExUUBiMhIiYnNTQ2MyEyFgI7AWb+mtYCPP3EAWXX1wEeFg78YA8UARYOA6APFAEWDvxgDxQBFg4DoA8UARYO/GAPFAEWDgOgDxRASNZH10f96I4PFAEWDo4PFgEUAQ6PDhYBFA+PDxQBFgEQjw4WFg6PDhYWAAH/+f+xAxgCwwAUABhAFQ4DAgABAUcAAQABbwAAAGY4JwIFFisBFgcBERQHBiMiLwEmNREBJjYzITIDDwkR/u0WBwcPCo8K/u0SExgCyhcCrRYR/u3+YhcKAwuPCw4BDwETESwAAAAAAv/9/7EDWQNSACgANAAiQB8AAgMBAwIBbQABAAABAFwAAwMMA0kzMi0sGhkUBAUVKwEUDgIiLgI3NDY3NhYXFgYHDgEVFB4CMj4CNzQmJy4BPgEXHgEBERQGIiY3ETQ2MhYDWURyoKyibkoDWlEYPBASCBg2PC5ManRoUCoBPDYXCiQ8F1Fa/psqOiwBKjwoAV5XnnRERHSeV2ayPhIIGBc8ESl4QzpqTC4uTGo6RHYqEjowCBI9tAFI/podKiodAWYdKioAAAAD//n/sQOpAwsAUQBhAHEAVEBROAEFAVABBAUPDQwDAgYDRwAGBwIHBgJtAAIDBwIDawABAAUEAQVeAAQABwYEB2AAAwAAA1QAAwMAWAAAAwBMbmxmZF5dVlVLSEVCPTo1CAUVKwEWBwMOAQchIiYnJj8BNjc0JjU2PwE+ATc2JjY/AT4BNzYmNzY/AT4BNzQmPgE/Aj4BPwE+AhcVNjMhMhYHAw4BByEiBhcWMyEyNjcTNicWBQYWFyEyNj8BNiYnISIGDwEGFhchMjY/ATYmByEiBgcDkxYMmgpAJf39K1APDg0BAQIEAQQSDRgFAgQEBwoMFgMBBAICCg0KGgMEAggGCgkFBgYLBRQUEBUHAakpLg2ZFCg0/hsPDAUOQwIDEB4FpwQBFf26AgYIAVMIDgIMAgYJ/q0HDgI6AwgHAVMHDgMLAwgH/q0HDgMCRx8p/gckMAE8LCUiDw0HBQ4EBgYaFTwVBhYLCQ0UPhQFGAQHCg0OQhUEFAkMBwsRChQKEggKAgQBBUAo/gZCJgERDycSDgImDRMIEQcKAQwGJAcKAQwGswcKAQwGJAcMAQoIAAAABAAA/2oD6ANSAAgAGAAbADcAS0BIEgoCBAMyAQIEGwEFAgNHAAcBAAEHAG0ABAACBQQCXgAFAAEHBQFgAAMDCFgACAgMSAAAAAZYAAYGDQZJNSM1ExckEyEQCQUdKwUhESMiJic1Izc1NCYnISIGFxUUFjchMjYTMycFERQGByEiJic1ISImJxE0NjchMhYHFRYfAR4BAa0B9OkWHgHWjgoH/ncHDAEKCAGJBwqPp6cBHiAW/ekXHgH+0RceASAWAl8WIAEMCOQQFk8BZh4X6KEkBwoBDAYkBwwBCv6Rp+7+iRceASAWWSAVAu4XHgEgFrcHCOQPNgAH//r/sQPqAsMACABKAFgAZgBzAIAAhgB7QHh3dkA+BAkIeG1saGdCLQcFCYN5KgMBAIaAeicSBQoEghUCCwoFRwAHBggGBwhtAAILAwsCA20ABgAICQYIYAAJAAUACQVgAAAAAQQAAWAABAAKCwQKYAALAgMLVAALCwNYAAMLA0xmZF9dWFYqGigoJysaExAMBR0rATIWDgEuAjYXBRYGDwEGIiclBwYjFgcOAQcGIyInJjc+ATc2MzIXNj8BJyYnBiMiJy4BJyY2NzYzMhceARcWBx8BJTYyHwEeAQcFNiYnJiMiBwYWFxYzMgM+AScmIyIHDgEXFjMyExc1ND8BJwcGDwEGIx8BAScFFQcfAhYfAQU3JQcGBwIYDhYCEiASBBqzARsQBRBIBxMH/n8+BAMIAgQ2L0pQTDAzBwQ2LkpRLiYFCERECAUmLlFKLjYEAxYZL01QSi44AwIIBz4BgQcTB0gQBRD9aRocLTQ3KhUaHC0zOCkZLRwaFik4My0cGhUqN5c2EggsDwEECQEBeDYBmkf+U1kFBAYEAg8B4kf+3mMBBgFeFhwWAhIgEiLeCygIJAQE2CQDHBorUB0vLC9FKlAdLxIIBSgpBQcRLx5OKyE8FiwvHU4sGxsDJdgFBCQJJwxNGEocIRQYSB4h/nUcShcUIRxKFxQBdyEHFAsEGg4CBAkBghIBQSTwQDUFAwcFAQ+yI+RNAgIAAAAAA//9/7EDWQMLAAwBvQH3AndLsAlQWEE8AL0AuwC4AJ8AlgCIAAYAAwAAAI8AAQACAAMA2gDTAG0AWQBRAEIAPgAzACAAGQAKAAcAAgGeAZgBlgGMAYsBegF1AWUBYwEDAOEA4AAMAAYABwFTAU0BKAADAAgABgH0AdsB0QHLAcABvgE4ATMACAABAAgABgBHG0uwClBYQUMAuwC4AJ8AiAAEAAUAAAC9AAEAAwAFAI8AAQACAAMA2gDTAG0AWQBRAEIAPgAzACAAGQAKAAcAAgGeAZgBlgGMAYsBegF1AWUBYwEDAOEA4AAMAAYABwFTAU0BKAADAAgABgH0AdsB0QHLAcABvgE4ATMACAABAAgABwBHAJYAAQAFAAEARhtBPAC9ALsAuACfAJYAiAAGAAMAAACPAAEAAgADANoA0wBtAFkAUQBCAD4AMwAgABkACgAHAAIBngGYAZYBjAGLAXoBdQFlAWMBAwDhAOAADAAGAAcBUwFNASgAAwAIAAYB9AHbAdEBywHAAb4BOAEzAAgAAQAIAAYAR1lZS7AJUFhANQACAwcDAgdtAAcGAwcGawAGCAMGCGsACAEDCAFrAAEBbgkBAAMDAFQJAQAAA1gFBAIDAANMG0uwClBYQDoEAQMFAgUDZQACBwUCB2sABwYFBwZrAAYIBQYIawAIAQUIAWsAAQFuCQEABQUAVAkBAAAFVgAFAAVKG0A1AAIDBwMCB20ABwYDBwZrAAYIAwYIawAIAQMIAWsAAQFuCQEAAwMAVAkBAAADWAUEAgMAA0xZWUEZAAEAAAHYAdYBuQG3AVcBVgDHAMUAtQC0ALEArgB5AHYABwAGAAAADAABAAwACgAFABQrATIeARQOASIuAj4BAQ4BBzI+ATU+ATc2FyY2PwE2PwEGJjUUBzQmBjUuBC8BJjQvAQcGFCoBFCIGIgc2JyYjNiYnMy4CJy4BBwYUHwEWBh4BBwYPAQYWFxYUBiIPAQYmJyYnJgcmJyYHMiYHPgEjNj8BNicWPwE2NzYyFjMWNCcyJyYnJgcGFyIPAQYvASYnIgc2JiM2JyYiDwEGHgEyFxYHIgYiBhYHLgEnFicjIgYiJyY3NBcnBgcyNj8BNhc3FyYHBgcWBycuASciBwYHHgIUNxYHMhcWFxYHJyYGFjMiDwEGHwEGFjcGHwMeAhcGFgciBjUeAhQWNzYnLgI1MzIfAQYeAjMeAQcyHgQfAxYyPwE2FhcWNyIfAR4BFR4BFzY1BhYzNjUGLwEmNCY2FzI2LgInBiYnFAYVIzY0PwE2LwEmByIHDgMmJy4BND8BNic2PwE2OwEyNDYmIxY2FxY3JyY3FjceAh8BFjY3FhceAT4BJjUnNS4BNjc0Nj8BNicyNycmIjc2Jz4BMxY2Jz4BNxY2Jj4BFTc2IxY3Nic2JiczMjU2JyYDNjcmIi8BNiYvASYvASYPASIPARUmJyIuAQ4BDwEmNiYGDwEGNgYVDgEVLgE3HgEXFgcGBwYXFAYWAa10xnJyxujIbgZ6vAETAggDAQIEAxEVEwoBDAIIBgMBBwYEBAoFBgQBCAECAQMDBAQEBAYBBgIICQUEBgIEAwEIDAEFHAQDAgIBCAEOAQIHCQMEBAEEAgMBBwoCBAUNAwMUDhMECAYBAgECBQkCARMJBgQCBQYKAwgEBwUCAwYJBAYBBQkEBQMDAgUEAQ4HCw8EEAMDAQgECAEIAwEIBAMCAgMEAgQSBQMMDAEDAwIMGRsDBgUFEwUDCwQNCwEEAgYECAQJBFEyBAUCBgUDARgKAQIHBQQDBAQEAQIBAQECCgcHEgQHCQQDCAQCDgEBAgIOAgQCAg8IAwQDAgMFAQQKCgEECAQFDAcCAwgDCQcWBgYFCAgQBBQKAQIEAgYDDgMEAQoFCBEKAgICAgEFAgQBCgIDDAMCCAECCAMBAwIHCwQBAgIIFAMICgECAQQCAwUCAQMCAQMBBBgDCQMBAQEDDQIOBAIDAQQDBQIGCAQCAgEIBAQHCAUHDAQEAgICBgEFBAMCAwUMBAISAQQCAgUOCQICCggFCQIGBgcFCQwKaXNQAQwBDQEEAxUBAwUCAwICAQUMCAMGBgYGAQEECAQKAQcGAgoCBAEMAQECAgQLDwECCQoBAwt0xOrEdHTE6sR0/t0BCAIGBgEECAMFCwEMAQMCAgwBCgcCAwQCBAECBgwFBgMDAgQBAQMDBAIEAQMDAgIIBAIGBAEDBAEEBAYHAwgHCgcEBQYFDAMBAgQCAQMMCQ4DBAUHCAUDEQIDDggFDAMBAwkJBgQDBgEOBAoEAQIFAgIGCgQHBwcBCQUIBwgDAgcDAgQCBgIEBQoDAw4CBQICBQQHAgEKCA8CAwMHAwIOAwIDBAYEBgQEAQEtTwQBCAQDBAYPCgIGBAUEBQ4JFAsCAQYaAgEXBQQGAwUUAwMQBQIBBAgFCAQBCxgNBQwCAgQEDAgOBA4BCgsUBwgBBQMNAgECARIDCgQECQUGAgMKAwIDBQwCEAgSAwMEBAYCBAoHDgEFAgQBBAICEAUPBQIFAwILAggEBAICBBgOCQ4FCQEEBgECAwIBBAMGBwYFAg8KAQQBAgMBAgMIBRcEAggIAwUOAgoKBQECAwQLCQUCAgICBgIKBgoEBAQDAQQKBAYBBwIBBwYFBAIDAQUEAv4NFVUCAgUEBgIPAQECAQIBAQMCCgMGAgIFBgcDDgYCAQUEAggBAggCAgICBRwIEQkOCQwCBBAHAAH////5BDADCwAbAB9AHBkSCgMAAgFHAAECAW8AAgACbwAAAGYjKTIDBRcrJRQGByEiJjc0NjcmNTQ2MzIWFzYzMhYVFAceAQQvfFr9oWeUAVBAAah2WI4iJzY7VBdIXs9ZfAGSaEp6HhAIdqhiUCNUOyojEXQAAAH//v9qAfgDCwAgACpAJxkBAwIcCgIBAwJHAAIDAm8AAwEDbwABAAFvAAAADQBJGDY2FAQFGCsBFgcBBiMnLgE3EwcGIyInJjcTPgE7ATIWFRQHAzc2MzIB7goG/tIHEAgJCgJu4gIFCgcKA3ACDgi3Cw4CYN0FAgsCFgsN/XoOAQMQCAHDOAEHCA0BzQgKDgoEBv7+NgIABQAA/7ED6AMLAA8AHwAvAD8ATwBVQFJJAQcJOQEFBykBAwUZAQEDQTEhEQkBBgABBUcACQcJbwAHBQdvAAUDBW8AAwEAA1QAAQAAAVQAAQEAWAgGBAIEAAEATE1LJiYmJiYmJiYjCgUdKzcVFAYrASImPQE0NjsBMhY3FRQGKwEiJj0BNDY7ATIWNxEUBisBIiY1ETQ2OwEyFjcRFAYrASImNRE0NjsBMhYTERQGKwEiJjURNDY7ATIWjwoIawgKCghrCArWCghrCAoKCGsICtYKB2wHCgoHbAcK1woIawgKCghrCArWCghrCAoKCGsICi5rCAoKCGsICgpAswgKCgizCAoKh/6+CAoKCAFCCAoKzv3oCAoKCAIYCAoKARb8yggKCggDNggKCgAAAQAAAAACPAHtAA4AF0AUAAEAAQFHAAEAAW8AAABmNRQCBRYrARQPAQYiLwEmNDYzITIWAjsK+gscC/oLFg4B9A4WAckOC/oLC/oLHBYWAAAB//8AAAI7AckADgARQA4AAQABbwAAAGYVMgIFFislFAYnISIuAT8BNjIfARYCOxQP/gwPFAIM+goeCvoKqw4WARQeC/oKCvoLAAAAAQAAAAABZwJ8AA0AF0AUAAEAAQFHAAEAAW8AAABmFxMCBRYrAREUBiIvASY0PwE2MhYBZRQgCfoKCvoLHBgCWP4MDhYL+gscC/oLFgAAAAABAAAAAAFBAn0ADgAKtwAAAGYUAQUVKwEUDwEGIiY1ETQ+AR8BFgFBCvoLHBYWHAv6CgFeDgv6CxYOAfQPFAIM+goAAAEAAP/nA7YCKQAUABlAFg0BAAEBRwIBAQABbwAAAGYUFxIDBRcrCQEGIicBJjQ/ATYyFwkBNjIfARYUA6v+YgoeCv5iCwtdCh4KASgBKAscDFwLAY/+YwsLAZ0LHgpcCwv+2AEoCwtcCxwAAAEAAP/AAnQDRAAUAC21CQEAAQFHS7AhUFhACwAAAQBwAAEBDAFJG0AJAAEAAW8AAABmWbQcEgIFFisJAQYiLwEmNDcJASY0PwE2MhcBFhQCav5iCxwLXQsLASj+2AsLXQoeCgGeCgFp/mEKCl0LHAsBKQEoCxwLXQsL/mILHAAAAQAAAAADtgJGABQAGUAWBQEAAgFHAAIAAm8BAQAAZhcUEgMFFyslBwYiJwkBBiIvASY0NwE2MhcBFhQDq1wLHgr+2P7YCxwLXQsLAZ4LHAsBngtrXAoKASn+1woKXAseCgGeCgr+YgscAAAAAQAA/8ACmANEABQALbUBAQABAUdLsCFQWEALAAABAHAAAQEMAUkbQAkAAQABbwAAAGZZtBcXAgUWKwkCFhQPAQYiJwEmNDcBNjIfARYUAo7+1wEpCgpdCxwL/mILCwGeCh4KXQoCqv7Y/tcKHgpdCgoBnwoeCgGeCwtdCh4AAAABAAD/sQODAucAHgAgQB0QBwIAAwFHAAMAA28CAQABAG8AAQFmFxU1FAQFGCsBFA8BBiIvAREUBgcjIiY1EQcGIi8BJjQ3ATYyFwEWA4MVKRY7FKUoH0ceKqQUPBQqFRUBaxQ8FQFrFQE0HBYqFRWk/ncdJAEmHAGJpBUVKhU7FQFrFRX+lRYAAQAA/4gDNQLtAB4AJEAhAAMCA28AAAEAcAACAQECVAACAgFYAAECAUwWJSYUBAUYKwEUBwEGIi8BJjQ/ASEiJj0BNDYXIScmND8BNjIXARYDNRT+lRY6FSoWFqP+dx0kJB0BiaMWFioVOhYBaxQBOh4U/pQUFCoVPBWjKh5HHioBpRQ8FCoVFf6VFAABAAD/iANZAu0AHQAkQCEAAgMCbwABAAFwAAMAAANUAAMDAFgAAAMATCYXFiMEBRgrARUUBiMhFxYUDwEGIicBJjQ3ATYyHwEWFA8BITIWA1kkHf53pBUVKhU7Ff6UFBQBbBU6FioVFaQBiR0kAV5HHiqkFDwUKxQUAWwVOhYBaxUVKRY6FqQoAAAAAAEAAP/PA4MDCwAeACBAHRgPAgABAUcAAgECbwMBAQABbwAAAGYVNRcUBAUYKwEUBwEGIicBJjQ/ATYyHwERNDY3MzIWFRE3NjIfARYDgxX+lRY6Ff6VFRUpFjoVpCoeRx0qpRQ7FikVAYIeFP6UFRUBbBQ7FikVFaQBiR0qASwc/nekFRUpFgABAAD/sQNaAwsARQAyQC8+NTMiBAIDNCEgGxIREAIBCQACAkcEAQMCA28FAQIAAm8BAQAAZiY6Nxs6OQYFGisBBxc3NhYdARQGKwEiJyY/AScHFxYHBisBIiYnNTQ2HwE3JwcGIyInJj0BNDY7ATIXFg8BFzcnJjc2OwEyFgcVFAcGIyInAszGxlARLBQQ+hcJChFRxsZQEQkKF/oPFAEsEVDGxlALDgcHFhYO+hcKCRFQxsZREQoJF/oPFgEWBwcOCwIkxsZQEhMY+g4WFxURUcbGUREVFxYO+hgTElDGxlALAwkY+g4WFxURUcbGUREVFxYO+hgJAwsAAAACAAD/sQNaAwsAGAAwADFALigfGQMCBBIMAwMAAQJHAAQCBG8AAgMCbwADAQNvAAEAAW8AAABmOhQXGjcFBRkrARQPARcWFAYHIyImJzU0PgEfATc2Mh8BFgEVFA4BLwEHBiIvASY0PwEnJjQ2NzMyFgGlBblQChQP+g8UARYcC1C6BQ4GQAUBtBQgCVC5Bg4GQAUFulEKFA/6DxYBBQcGuVEKHhQBFg76DxQCDFC5BgY/BgHb+g8UAgxQuQYGQAUOBrlRCh4UARYAAAACAAD/uQNSAwMAFwAwADBALSokGwMCAw8GAgABAkcABAMEbwADAgNvAAIBAm8AAQABbwAAAGYUFTk6GAUFGSsBFRQGJi8BBwYiLwEmND8BJyY0NjsBMhYBFA8BFxYUBisBIiY3NTQ2Fh8BNzYyHwEWAa0WHAtRuQUQBEAGBrlQCxYO+g4WAaUGuVALFg76DhYBFB4KUbkGDgY/BgE6+g4WAglRugUFQAYOBrlQCxwWFgFpBwW6UAscFhYO+g4WAglQuQUFQAUAAAEAAP9qA+gDUgBEAFBATQsBCQoHCgkHbQ0BBwgKBwhrBgEAAQIBAAJtBAECAwECA2sMAQgFAQEACAFeAAoKDEgAAwMNA0lBQD08Ozk0My4sExcTESUVIRMUDgUdKwEUDwEGIiY9ASMVMzIWFA8BBiIvASY0NjsBNSMVFAYiLwEmND8BNjIWHQEzNSMiJjQ/ATYyHwEWFAYrARUzNTQ2Mh8BFgPoC44LHhTXSA4WC48KHgqPCxYOSNcUHgqPCwuPCh4U10gOFguPCxwLjwsWDkjXFB4LjgsBXg4LjwsWDkjXFB4KjwsLjwoeFNdIDhYLjwscC48LFg5I1xQeC44LC44LHhTXSA4WC48KAAABAAAAAAPoAhEAIAAoQCUFAQMEA28CAQABAHAABAEBBFIABAQBVgABBAFKExMXExMUBgUaKwEUDwEGIiY9ASEVFAYiLwEmND8BNjIWHQEhNTQ2Mh8BFgPoC44LHhT9xBQeCo8LC48KHhQCPBQeC44LAV4OC48LFg5ISA4WC48LHAuPCxYOSEgOFguPCgAAAAABAAD/agGKA1IAIAAoQCUEAQAFAQUAAW0DAQECBQECawAFBQxIAAICDQJJFSElFSETBgUaKwEUBicjETMyHgEPAQYiLwEmNDY7AREjIiY2PwE2Mh8BFgGJFg5HRw8UAgyPCh4KjwoUD0hIDhYCCY8LHAuPCwKfDhYB/cQUHgqPCwuPCh4UAjwUHguOCwuOCwAD////agOhAw0AIwAsAEUAXUBaHxgCAwQTEgEDAAMNBgIBAEMBBwEyAQkHBUcABAYDBgQDbQABAAcAAQdtAAoABgQKBmAFAQMCAQABAwBgAAcACQgHCWAACAgNCEk9PDUzFBMVFCMmFCMjCwUdKwEVFAYnIxUUBicjIiY3NSMiJic1NDY7ATU0NjsBMhYXFTMyFhc0LgEGFBY+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAjsKB30MBiQHDAF9BwoBDAZ9CggkBwoBfQcKSJLQkpLQkgEeKjwUv2R7UJJoQAI8bI6kjmw8AUW/FQGUJAcMAX0HDAEKCH0KCCQHCn0ICgoIfQoZZ5IClsqYBoz+mh0qFb9FPmqQoo5uOgRCZpZNe2S/FQAAA////7ADWQMQAAkAEgAjACpAJwsDAgMAAQFHAAMAAQADAWAAAAICAFQAAAACWAACAAJMFxkmJAQFGCsBNCcBFjMyPgIFASYjIg4BBxQlFA4CLgM+BB4CAtww/ltMWj5wUDL90gGlS1xTjFABAtxEcqCsonBGAkJ0nrCcdkABYFpK/lwyMlByaQGlMlCOUltbWKByRgJCdpy0mng+BkpspgAAAAAD////agOhAw0ADwAYADEAO0A4CQgBAwABLwEDAB4BBQMDRwAGAAIBBgJgAAEAAAMBAGAAAwAFBAMFYAAEBA0ESRcjFBMVJiMHBRsrARUUBichIiYnNTQ2MyEyFhc0LgEGFBY+AQEUBiIvAQYjIi4CPgQeAhcUBxcWAjsKB/6+BwoBDAYBQgcKSJLQkpLQkgEeKjwUv2R7UJJoQAI8bI6kjmw8AUW/FQGUJAcMAQoIJAcKChlnkgKWypgGjP6aHSoVv0U+apCijm46BEJmlk17ZL8VAAMAAP+wAj4DDAAQACcAWwBWQFMFAAIAAU1JRTYyLgYFBAJHAAABBAEABG0ABAUBBAVrBwEFBgEFBmsABgZuAAgAAwIIA2AAAgEBAlQAAgIBWAABAgFMWFdBQD49OzoaFyQUEgkFGSsBFAYiJjc0JiMiJj4BMzIeARc0LgIiDgIHFB8CFhczNjc+ATc2NxQHDgIHFhUUBxYVFAcWFRQGIw4CJiciJjc0NyY1NDcmNTQ3LgInJjU0PgMeAgGbDAwOAjwdBwwCCAkcNixYJj5MTEw+JgEmERFIB38IRwYWBiZHORkiIAMaDQ0ZCCQZCy4yMAkaJAEHGQ4OGgIiIBk6MlBoaGhONgIRCAoKCBkcChAKEiodKEQuGBguRCg5LBITVVFRVQYaBSw5Vz8bKkIbDx8UDw8VHRANDRocGRwCIBccGg0NEB0VDw8UHw8cQCwaP1c3YD4kAig6ZAAAAAP//f+xA18DCwAUACEALgBAQD0OAQECCQECAAECRwACAwEDAgFtAAYAAwIGA2AAAQAABAEAYAAEBQUEVAAEBAVYAAUEBUwVFhUWIyYjBwUbKwEVFAYrASImPQE0NjsBNTQ2OwEyFhc0LgEOAx4CPgE3FA4BIi4CPgEyHgEB9AoIsggKCgh9CgckCAroUoqmjFACVIiqhlZ7csboyG4Gerz0un4CIvoHCgoHJAgKxAgKCsxTilQCUI6ijlACVIpTdcR0dMTqxHR0xAAAAAQAAP/RA6EC6wATAC4ASwBsAEpARycKAgMENwEFAFQBBwUDR2gBAkUAAgYCbwAGAQZvAAEEAW8ABAMEbwADAANvAAAFAG8ABQcFbwAHB2ZSUEdGKC8XEhYmCAUaKwERFAYmLwEjIiYnNTQ2NzM3NjIWExQGBwYjIiY3ND4DLgQ3NDYXMhceARcUBgcGIyImNzQ3Njc+ATQmJyYnJjU0NjMyFx4BFxQGBwYjIiYnND8BNjc+AS4BJyYnLgEnJjU0NjcyFx4BAa0WHAu6kg8UARYOkroKHhTXMCcFCQ4WAQwWEBAECBgOFAQUDwkFJzCPYE0HBw8WARUgCykuLikLIBUUDwgHTl6QjnYHBw8UARYZGRVETgJKRhUZBBIDFhYOBwd2jgKO/aAOFgIJuhYO1g8UAboKFP7BKkoPAxQQDBAMDB4gIAgSCBAPFgEDD0oqVZIgAxYOFgsQCR5aaFoeCRALFg4WAyGQVoDYMgMWDhQNDA4OM5iqmDMPDQMGAw0UDxQBAzPWAAAAAgAAAAACgwKxABMALgAqQCcnCgIDBAFHAAIBAm8AAQQBbwAEAwRvAAMAA28AAABmLxcSFiYFBRkrAREUBiYvASMiJic1NDY3Mzc2MhYTFAYHBiMiJjc0PgMuBDc0NhcyFx4BAa0WHAu6kg8UARYOkroKHhTXMCcFCQ4WAQwWEBAECBgOFAQUDwkFJzACjv2gDhYCCboWDtYPFAG6ChT+wSpKDwMUEAwQDAweICAIEggQDxYBAw9KAAEAAAAAAa0CsQATAB1AGgoBAAEBRwACAQJvAAEAAW8AAABmEhYmAwUXKwERFAYmLwEjIiYnNTQ2NzM3NjIWAa0WHAu6kg8UARYOkroKHhQCjv2gDhYCCboWDtYPFAG6ChQAAAADAAD/sQMLA1MACwBDAEsAjkAURR8TDQEFAAYUAQEANDIjAwIBA0dLsAlQWEArAAYHAAcGAG0AAAEHAAFrAAECAgFjAAUCAwIFA20EAQIAAwIDXQAHBwwHSRtALAAGBwAHBgBtAAABBwABawABAgcBAmsABQIDAgUDbQQBAgADAgNdAAcHDAdJWUATSkg/Pjc2MS8sKSYkFxUSEAgFFCsTByY9ATQ+ARYdARQBBxUUBgciJwcWMzI2JzU0PgEWBxUUBgcVMzIWDgEjISImPgE7ATUmJwcGIi8BJjQ3ATYyHwEWFCcBETQ2FzIWlzgYFhwWAnbKaEofHjU2PGeUARYcFgGkeY4PFgISEf6bDhYCEhCPRj2OBRAELgYGArEFDgYuBtr+pWpJOVwBQzk6PkcPFAIYDUceAS/KR0poAQs2HJJoRw8UAhgNR3y2DUoWHBYWHBZKByaOBgYuBRAEArEGBi4FEEX+pgEdSmoBQgAAAAL///+xAoMDUwAnADMAXUALHAEEBRMEAgADAkdLsAlQWEAcAAQFAwUEA20AAwAAA2MCAQAAAQABXQAFBQwFSRtAHQAEBQMFBANtAAMABQMAawIBAAABAAFdAAUFDAVJWUAJFRsdIzMlBgUaKwEVFAYHFTMyHgEGIyEiLgE2OwE1LgE3NTQ+ARYHFRQWPgEnNTQ+ARYnERQOASYnETQ2HgECg6R6jw8UAhgN/psPFAIYDY95pgEWHBYBlMyWAhYcFo9olmYBaJRqAclHfLYNShYcFhYcFkoNtnxHDxQCGA1HZ5QCkGlHDxQCGMn+40poAmxIAR1KagJmAAAAAAIAAP/5A1kCxAAYAEAAUEBNDAEBAgFHIQEAAUYAAwcGBwMGbQACBgEGAgFtAAEFBgEFawAABQQFAARtAAcABgIHBmAABQAEBVQABQUEWAAEBQRMLCUqJxMWIxQIBRwrARQHAQYiJj0BIyImJzU0NjczNTQ2FhcBFjcRFAYrASImNycmPwE+ARczMjYnETQmByMiNCY2LwEmPwE+ARczMhYClQv+0QseFPoPFAEWDvoUHgsBLwvEXkOyBwwBAQEBAgEICLIlNgE0JrQGCgICAQEBAgEICLJDXgFeDgv+0AoUD6EWDtYPFAGhDhYCCf7QCrX+eENeCggLCQYNBwgBNiQBiCU2AQQCCAQLCQYNBwgBXgAAAAIAAP/5A2sCwwAnAEAAQkA/FAECAQFHAAYCBQIGBW0ABQMCBQNrAAQDAAMEAG0AAQACBgECYAADBAADVAADAwBYAAADAEwWIxklKiUnBwUbKyUUFg8BDgEHIyImNRE0NjsBMhYVFxYPAQ4BJyMiBgcRFBYXMzIeAgEUBwEGIiY9ASMiJj0BNDY3MzU0NhYXARYBZQIBAgEICLJDXl5DsggKAQEBAgEICLIlNAE2JLQGAgYCAgYL/tELHBb6DhYWDvoWHAsBLwsuAhIFDgkEAV5DAYhDXgoICwkGDQcIATQm/nglNAEEAggBLA4L/tAKFA+hFg7WDxQBoQ4WAgn+0AoAAAAABAAA/2oDoQNSAAMAEwAjAEcAgUAMFQUCBwIdDQIDBwJHS7AKUFhAKQsJAgcCAwMHZQUBAwABAAMBXwQBAgIIWAoBCAgMSAAAAAZYAAYGDQZJG0AqCwkCBwIDAgcDbQUBAwABAAMBXwQBAgIIWAoBCAgMSAAAAAZYAAYGDQZJWUASRkRBPjs6MyU2JiYmJBEQDAUdKxchESE3NTQmKwEiBh0BFBY7ATI2JTU0JisBIgYdARQWOwEyNjcRFAYjISImNRE0NjsBNTQ2OwEyFh0BMzU0NjsBMhYHFTMyFkcDEvzu1woIJAgKCggkCAoBrAoIIwgKCggjCArXLBz87h0qKh1INCUkJTTWNiQjJTYBRx0qTwI8a6EICgoIoQgKCgihCAoKCKEICgos/TUdKiodAssdKjYlNDQlNjYlNDQlNioAAAAADwAA/2oDoQNSAAMABwALAA8AEwAXABsAHwAjADMANwA7AD8ATwBzAJhAlUElAh0SSS0kAxMdAkchHwIdEwkdVBsBExkXDQMJCBMJXxgWDAMIFREHAwUECAVeFBAGAwQPCwMDAQAEAV4aARISHlggAR4eDEgOCgIDAAAcWAAcHA0cSXJwbWpnZmNgXVtWU01MRUQ/Pj08Ozo5ODc2NTQxLyknIyIhIB8eHRwbGhkYFxYVFBMSEREREREREREQIgUdKxczNSMXMzUjJzM1IxczNSMnMzUjATM1IyczNSMBMzUjJzM1IwM1NCYnIyIGBxUUFjczMjYBMzUjJzM1IxczNSM3NTQmJyMiBhcVFBY3MzI2NxEUBiMhIiY1ETQ2OwE1NDY7ATIWHQEzNTQ2OwEyFgcVMzIWR6GhxbKyxaGhxbKyxaGhAZuzs9aysgGsoaHWs7PEDAYkBwoBDAYkBwoBm6Gh1rOz1qGhEgoIIwcMAQoIIwgK1ywc/O4dKiodSDQlJCU01jYkIyU2AUcdKk+hoaEksrKyJKH9xKH6of3EoSSyATChBwoBDAahBwwBCv4msiShoaFroQcKAQwGoQcMAQos/TUdKiodAssdKjYlNDQlNjYlNDQlNioAAAADAAD/dgOgAwsACAAUAC4AWUAQJgEEAygnEgMCBAABAQADR0uwJlBYQBoAAwQDbwAEAgRvAAIAAm8AAAEAbwABAQ0BSRtAGAADBANvAAQCBG8AAgACbwAAAQBvAAEBZlm3HCMtGBIFBRkrNzQmDgIeATYlAQYiLwEmNDcBHgElFAcOASciJjQ2NzIWFxYUDwEVFzY/ATYyFtYUHhQCGBoYAWb+gxU6FjsVFQF8FlQBmQ0bgk9okpJoIEYZCQmjbAIqSyEPCh0OFgISIBIEGvb+gxQUPRQ7FgF8N1TdFiVLXgGS0JACFBAGEgdefTwCGS0UCgAACQAA/7EDWQLEAAMAEwAXABsAHwAvAD8AQwBHAJ9AnCsBCwY7AQ0EAkcaERUDBxABBgsHBl4XAQoACwwKC2AZDxQDBQ4BBA0FBF4YAQwADQIMDWATAQIBAwJUFgkSAwEIAQADAQBeEwECAgNYAAMCA0xEREBAMTAhIBwcGBgUFAUEAABER0RHRkVAQ0BDQkE5NjA/MT8pJiAvIS8cHxwfHh0YGxgbGhkUFxQXFhUNCgQTBRMAAwADERsFFSs3FSM1JTIWHQEUBisBIiY9ATQ2PwEVITUTFSM1ARUhNQMyFgcVFAYHIyImJzU0NhcBMhYHFRQGByMiJic1NDYXBRUjNRMVITXExAGJDhYWDo8OFhYO6P4efX0DWf5lfQ8WARQQjg8UARYOAfQOFgEUD48PFAEWDgFBfX3+HkBHR0gWDo8OFhYOjw8UAdZHRwEeSEj9xEdHAoMUEI4PFAEWDo4PFgH+4hQPjw8UARYOjw4WAUdHRwEeSEgAAAYAAP9yBC8DSQAIABIAGwB6ALYA8QCcQJnu2QIEDmpdAgUI0LxwAwAFvqygdVJMRSMdCQEAs55AAwIBOi0CBgKVgAILAwdH59sCDkWCAQtECgEICQUJCAVtAAYCBwIGB20ADgAECQ4EYAAJCAAJVAAFDQEAAQUAYAACBgECVAwBAQAHAwEHYAADCwsDVAADAwtYAAsDC0zl48fGqqiLim1sZGJaWTQyKyoTFBQUExIPBRorATQmIgYUFjI2BTQmDgEXFBYyNgM0JiIGHgEyNgcVFAYPAQYHFhcWFAcOASIvAQYHBgcGKwEiJjUnJicHBiInJjU0Nz4BNyYvAS4BPQE0Nj8BNjcmJyY0Nz4BMzIfATY3Njc2OwEyFh8BFhc3NjIXFhUUDwEGBxYfAR4BARUUBwYHFhUUBwYjIi8BBiInDgEHIicmNTQ3JicmPQE0NzY3JjU0PwE2MzIWFzcXNj8BMhcWFRQHFhcWERUUBwYHFhUUBwYjIiYnBiInDgEiJyY1NDcmJyY9ATQ3NjcmNTQ/ATYzMhYXNxc2PwEyFxYVFAcWFxYB9FR2VFR2VAGtLDgsASo6LAEsOCwBKjos2AgEVwYMEx8EBAxEEAVAFRYGBwQNaAYKDRMXQgQNBlAEBSQIDQdVBQgIBVYHCxMfBAQMRAoGBkATGAYHAw1oBgoBDRMXQQUNBVEEGBEIDQZVBgYBZlMGChwCRAEFFR0LDAsHLAMBRAMdCgdTUwcKHQM0EAEEKggRERwXBAJDAhwJB1NTBgocAkQBBSoICwwLBywERAMdCgdTUwcKHQM0EAEEKggRERwXBAJDAhwJB1MBXjtUVHZUVOMdLAIoHx0qKgJZHSoqOyoqzWcGCgEOExcbJQYMBBFCBDILBjwbDQgGVQYMMgQESw8FBQgsDBgWDQEIB2gFCgEOExcbJQYMBRBCBDIKCDwaDQgGVQYLMQQESw8EBh4VDRsTDAII/s9OCQgPDj8OAgIoGyUBAQs0ASgCAg4/Dg8ICU4JCRANPw4CAh4JNAwBASgXAScCAg4/DRAJAjNOCQkPDj8OAgInNAwBAQw0JwICDj8ODwkJTgkIEA0/DgICHgk0CwEBJxcBJwICDj8NEAgAAAIAAP+xA1oDCwAIAGoARUBCZVlMQQQABDsKAgEANCgbEAQDAQNHAAUEBW8GAQQABG8AAAEAbwABAwFvAAMCA28AAgJmXFtTUUlIKyoiIBMSBwUWKwE0JiIOARYyNiUVFAYPAQYHFhcWFAcOASciLwEGBwYHBisBIiY1JyYnBwYiJyYnJjQ3PgE3Ji8BLgEnNTQ2PwE2NyYnJjQ3PgEzMh8BNjc2NzY7ATIWHwEWFzc2MhcWFxYUBw4BBxYfAR4BAjtSeFICVnRWARwIB2gKCxMoBgUPUA0HB00ZGgkHBBB8CAwQGxdPBhAGRhYEBQgoCg8IZgcIAQoFaAgOFyUGBQ9QDQcITRgaCQgDEXwHDAEPHBdPBQ8HSBQEBAkoCg8IZgcKAV47VFR2VFR4fAcMARAeFRsyBg4GFVABBTwNCEwcEAoHZwkMPAUGQB4FDgYMMg8cGw8BDAd8BwwBEBkaIC0HDAcUUAU8DQhMHBAKB2cJCzsFBUMcBQ4GDDIPHBoQAQwAAAAB////+QMSAwsATgAjQCAyAQIBAAEAAgJHAAECAW8AAgACbwAAAGZCQCEgJgMFFSslFAYHBgcGIyImLwImJy4BJyYvAS4BLwEmNzQ3Njc+ATMyFxYfAR4BFx4CFRQOAgcUHwEeATUeARcyFh8BFjcyPgIXMh4BHwEWFxYDEgwGCzk0Mw8eERo7NitHmisbEwoICAQHAwEdHxwOMA8IBAoUEAoUBwIQCCAmHgEDBAEOKm5MARIFCwYHCh4eIAwHEBgCYCcDAp4PMA4cIBwEBQgVFBssmEgrNhwXEBIgDg80NDkLBgwCAycfFB4PAhgQCAsgHh4KBQgLAxYBTW4qDAIFAwEgJCIBCBACNhMKBAAAAAgAAP9qA1kDUgATABoAIwBZAF4AbAB3AH4AdEBxFAECBGxqAgMCdGFWSQQGA28mAgoGfjQCCwpcAQgHBkcACAcFBwgFbQkBAgADBgIDYAAGAAoLBgpgAAsABwgLB2AABAQBWAABAQxIDAEFBQBYAAAADQBJGxt8e3p5UE04NzIwKScbIxsjEyYUNTYNBRkrAR4BFREUBgchIiYnETQ2NyEyFhcHFTMmLwEmExEjIiYnNSERARYXNjMyFxYHFCMHBiMiJicGBwYjIi8CJjc+ATc2FxYVNjc2Ny4BNzY7ATIXFgcGBxUGBxYBNjcOARMGFzY3NDc2NyImNTQnAzY3Ii8BJicGBwYFJiMWMzI3AzMQFh4X/RIXHgEgFgH0FjYPStIFB68GxugXHgH+UwGsEh0hIFIRCQgBAQMkG0oke2BVMggHDgMGAgU2LggFAR0fJhQNCAgGEQwNBwoFAQEBBx/+8h0vHSjXCQcBAwQBAgEBB0ZMUwEGCSscDx8RAWANQSobCAICfhA0GP1+Fx4BIBYDfBceARYQJtIRBq8H/LACPCAV6fymAUsOEQQbDRABAhUWEg0hkgQHAgYOFzgaBQgBAS8/TEYuVhwWCAwaAwEWRCdb/vENSxYyAfEXMgQUAhYDAgIBDAj+jR4PBQglPTA+HwYNEAEAAAQAAP9qA1kDUgATABoAIwBTALNACxQBAgRMPgIHBgJHS7ASUFhAORAODAMKAwYDCmUNCwkDBgcDBgdrCAEHBQUHYwACAAMKAgNgAAQEAVgAAQEMSA8BBQUAWQAAAA0ASRtAOxAODAMKAwYDCgZtDQsJAwYHAwYHawgBBwUDBwVrAAIAAwoCA2AABAQBWAABAQxIDwEFBQBZAAAADQBJWUAkJCQbGyRTJFNSUUdGOjk4NzY1NDMoJyYlGyMbIxMmFDU2EQUZKwEeARURFAYHISImJxE0NjchMhYXBxUzJi8BJhMRIyImJzUhERMVMxMzEzY3NjUzFx4BFxMzEzM1IxUzBwYPASM1NCY0JicDIwMHBg8BIycmLwEzNQMzEBYeF/0SFx4BIBYB9BY2D0rSBQevBsboFx4B/lM7J1xYSAQBAgIBAQICSFlbJ6cyNwMBAQMCAgJRP1ECAQECAgIBAjgyAn4QNBj9fhceASAWA3wXHgEWECbSEQavB/ywAjwgFen8pgH0O/6PAQ8LDgkFDgEUBP7xAXE7O/ULDgwEAgQEEgUBMP7QDQgEDAwOC/U7AAQAAP9qA1kDUgATABoAIwBTAMtACxQBAgRSOwIHCwJHS7ASUFhAQg8BDAMLAwxlEA4NAwsHAwsHaxMRCggEBwYDBwZrCQEGBQUGYwACAAMMAgNgAAQEAVgAAQEMSBIBBQUAWQAAAA0ASRtARA8BDAMLAwwLbRAODQMLBwMLB2sTEQoIBAcGAwcGawkBBgUDBgVrAAIAAwwCA2AABAQBWAABAQxIEgEFBQBZAAAADQBJWUAqJCQbGyRTJFNRUE9OTUxBQD8+PTw6OTg3NjUoJyYlGyMbIxMmFDU2FAUZKwEeARURFAYHISImJxE0NjchMhYXBxUzJi8BJhMRIyImJzUhETcVMzUjNz4CBzMUHwEeAR8BIxUzNSMnNzM1IxUzBw4BDwEjNCcmLwEzNSMVMxcHAzMQFh4X/RIXHgEgFgH0FjYPStIFB68GxugXHgH+U6idKjoDBAYBAQMCAQQCPCujJmtsJpwpOQIIAQEBAwMGOyqiJmptAn4QNBj9fhceASAWA3wXHgEWECbSEQavB/ywAjwgFen8poM7O1oECgYBAgQEAgQDWjs7mJ47O1kECgMBAgMGB1k7O5ieAAYAAP9qA1kDUgATABoAIwAzAEMAUwByQG8UAQIELCQCBwZAOAIICVBIAgoLBEcAAgADBgIDYAAGAAcJBgdgDQEJAAgLCQhgDgELAAoFCwpgAAQEAVgAAQEMSAwBBQUAWAAAAA0ASURENDQbG0RTRFJMSjRDNEI8OjAuKCYbIxsjEyYUNTYPBRkrAR4BFREUBgchIiYnETQ2NyEyFhcHFTMmLwEmExEjIiYnNSEREzQ2MyEyFh0BFAYjISImNQUyFh0BFAYjISImPQE0NjMFMhYdARQGIyEiJj0BNDYzAzMQFh4X/RIXHgEgFgH0FjYPStIFB68GxugXHgH+U48KCAGJCAoKCP53CAoBmwgKCgj+dwgKCggBiQgKCgj+dwgKCggCfhA0GP1+Fx4BIBYDfBceARYQJtIRBq8H/LACPCAV6fymAeMHCgoHJAgKCghZCggkCAoKCCQICo8KCCQICgoIJAgKAAAAAAYAAP+xAxIDCwAPAB8ALwA7AEMAZwBkQGFXRQIGCCkhGREJAQYAAQJHBQMCAQYABgEAbQQCAgAHBgAHawAOAAkIDglgDw0CCAwKAgYBCAZeAAcLCwdUAAcHC1gACwcLTGVkYV5bWVNST0xJR0E/FCQUJiYmJiYjEAUdKwERFAYrASImNRE0NjsBMhYXERQGKwEiJjURNDY7ATIWFxEUBisBIiY1ETQ2OwEyFhMRIREUHgEzITI+AQEzJyYnIwYHBRUUBisBERQGIyEiJicRIyImPQE0NjsBNz4BNzMyFh8BMzIWAR4KCCQICgoIJAgKjwoIJAgKCggkCAqOCgckCAoKCCQHCkj+DAgIAgHQAggI/on6GwQFsQYEAesKCDY0Jf4wJTQBNQgKCgisJwksFrIXKgknrQgKAbf+vwgKCggBQQgKCgj+vwgKCggBQQgKCgj+vwgKCggBQQgKCv5kAhH97wwUCgoUAmVBBQEBBVMkCAr97y5EQi4CEwoIJAgKXRUcAR4UXQoAAgAA/2oD6ALDABcAPQA3QDQ0CAIBACYLAgMCAkcABAUBAAEEAGAAAQACAwECYAADAw0DSQEAOzokIh0bEhAAFwEXBgUUKwEiDgEHFBYfAQcGBzY/ARcWMzI+Ai4BARQOASMiJwYHBgcjIiYnNSY2Jj8BNj8BPgI/AS4BJzQ+ASAeAQH0csZ0AVBJMA8NGlVFGCAmInLGdAJ4wgGAhuaIJypukxskAwgOAgIEAgMMBA0UBxQQBw9YZAGG5gEQ5oYCfE6ETD5yKRw1My4kPBUDBU6EmIRO/uJhpGAEYSYIBAwJAQIIBAMPBQ4WCBwcEyoyklRhpGBgpAABAAD/aQPoAsMAJgAcQBkbAQABAUcNAQBEAAEAAW8AAABmJCIjAgUVKwEUDgEjIicGBwYHBiYnNSY2Jj8BNj8BPgI/AS4BJzQ+AjMyHgED6IbmiCcqbpMbJAoOAwIEAgMMBA0UBxQQBw9YZAFQhLxkiOaGAV5hpGAEYSYIBAEMCgECCAQDDwUOFggcHBMqMpJUSYRgOGCkAAIAAP+wA+gCwwAlAEsAP0A8SRwCAAE/AQMAKQECAwNHCgEDAUYyAQJEAAEAAW8AAAMAbwADAgIDVAADAwJYAAIDAkxCQD48IyIjBAUVKwEUDgEjIicGBwYHIyImNSY0NjU/AjYHNz4CNy4BJzQ+ATIeARcUBgceAR8BFh8DFAcOAScmJyYnBiMiJxYzMjY3PgEnNCceAQMSarRrMDJGVRUbAgYMAQIBBAMDARwFDg4ERU4BarTWtGrWUEQFDAgbCQQFBAMBAgoHHBRWRjIwl3AgEVqkQkVMAQ1IVAGlTYRMCTEXBQQKBwEEBAEDBgMDAR4FGBIQKHRDToRMTITcQ3YnDhYKIQsDBQYKAQIICgEEBRcxCUoDMi80hkorKid4AAMAAP+wA+gCwwAVADsAYABWQFNcDAgDAQA1CQIDAVIBBQMDRyMBBQFGRQEERAcBAgYBAAECAGAAAQADBQEDYAAFBAQFVAAFBQRYAAQFBEwXFgEAVVNRTx4cFjsXOxAOABUBFQgFFCsBIg4BBxQWHwEHNj8BFxYzMj4BNC4BJzIeAg4BJyInBgcGByMiJjUmNDY1PwI2Bzc+AjcuASc0PgEBHgEfARYfAxQHDgEnJicmJwYjIicWMzI2Nz4BJzQnHgEUBgGJVZZWATw1NhMTDxkeKypVllZWllVqtmgCbLJsMDJGVRUbAgYMAQIBBAMDARwFDg4ERU4BarQCNgUMCBsJBAUEAwECCgccFFZGMjCXcCARWqRCRUwBDUhUUAJ8OmQ5LVYeIC4LChIGCDpkcGY4SEyEnIJOAQkxFwUECgcBBAQBAwYDAwEeBRgSECh0Q06ETP10DhYKIQsDBQYKAQIICgEEBRcxCUoDMi80hkorKid4h3YAAAADAAD/agPEA1MADAAaAEIAf0AMAAECAAFHKBsCAwFGS7AOUFhAKwcBBQEAAQVlAAACAQBjAAMAAQUDAWAABAQIWAAICAxIAAICBlgABgYNBkkbQCwHAQUBAAEFZQAAAgEAAmsAAwABBQMBYAAEBAhYAAgIDEgAAgIGWAAGBg0GSVlADB8iEigWESMTEgkFHSsFNCMiJjc0IhUUFjcyJSEmETQuAiIOAhUQBRQGKwEUBiImNSMiJjU+BDc0NjcmNTQ+ARYVFAceARcUHgMB/QkhMAESOigJ/owC1pUaNFJsUjQaAqYqHfpUdlT6HSocLjAkEgKEaQUgLCAFaoIBFiIwMGAIMCEJCSk6AamoASkcPDgiIjg8HP7XqB0qO1RUOyodGDJUXohNVJIQCgsXHgIiFQsKEJJUToZgUjQAAgAA/2oDxANTAAwANAA/QDwaDQIBBgABAgACRwABBgMGAQNtBQEDAAYDAGsAAAIGAAJrAAYGDEgAAgIEWAAEBA0ESR8iEiMjExIHBRsrBTQjIiY3NCIVFBY3MiUUBisBFAYiJjUjIiY1PgQ3NDY3JjU0PgEWFRQHHgEXFB4DAf0JITABEjooCQHHKh36VHZU+h0qHC4wJBIChGkFICwgBWqCARYiMDBgCDAhCQkpOgGpHSo7VFQ7Kh0YMlReiE1UkhAKCxceAiIVCwoQklROhmBSNAACAAD/+QEwAwsADwAfACxAKRkREAMCAwFHAAMCA28AAgECbwABAAABVAABAQBYAAABAEw1JiYkBAUYKyUVFAYHIyImPQE0NhczMhYTAw4BJyMiJicDJjY7ATIWAR4WDo8OFhYOjw8UEhABFg6PDhYBDwEWDbMOFpp9DxQBFg59DhYBFAI+/lMOFgEUDwGtDhYWAAAABP///7EDoQMLAAMADAAVAD0AWUBWDQEBAhcBBgECRwADBAkEAwltCAEGAQABBgBtAAoABAMKBF4LAQkABQIJBWAAAgABBgIBXgAABwcAUgAAAAdYAAcAB0w8OjMwLSsTMykTEyERERAMBR0rFyE1ITUhNSMiJj0BIQE0LgEOARY+ATcVFAYHIxUUBiMhIiYnNSMiJjc1NDYXMxE0NjMhMhYfAR4BBxUzMhbWAfT+DAH0WRYg/psCgxQgEgIWHBhGDAZ9IBb96BYeAX0HDAFAKyQgFQF3FzYPVQ8YASMtPgeP1tYgFln+dw8UAhgaGAQQEegHCgFZFiAgFlkMBugsQAEBMBYgGA5VEDYWjz4AAAAFAAD/+QPkAwsABgAPADkAPgBIAQdAFUA+OxADAgEHAAQ0AQEAAkdBAQQBRkuwClBYQDAABwMEAwcEbQAABAEBAGUAAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkwbS7ALUFhAKQAABAEBAGUHAQMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMG0uwF1BYQDAABwMEAwcEbQAABAEBAGUAAwAEAAMEYAgBAQAGBQEGXwAFAgIFVAAFBQJYAAIFAkwbQDEABwMEAwcEbQAABAEEAAFtAAMABAADBGAIAQEABgUBBl8ABQICBVQABQUCWAACBQJMWVlZQBYAAERDPTwxLikmHhsWEwAGAAYUCQUVKyU3JwcVMxUBJg8BBhY/ATYTFRQGIyEiJjURNDY3ITIXHgEPAQYnJiMhIgYHERQWFyEyNj0BND8BNhYDFwEjNQEHJzc2Mh8BFhQB8EBVQDUBFQkJxAkSCcQJJF5D/jBDXl5DAdAjHgkDBxsICg0M/jAlNAE2JAHQJTQFJAgYN6H+iaECbzOhMxAsEFUQvUFVQR82AZIJCcQJEgnECf6+akNeXkMB0EJeAQ4EEwYcCAQDNCX+MCU0ATYkRgcFJAgIAY+g/omgAS40oTQPD1UQLAABAAD/sQPoAy8ALAAdQBoAAwEDbwABAAFvAAACAG8AAgJmKh0zFAQFGCsBFAcBBiImPQEjIg4FFRQXFBYHFAYiJy4CJyY1NDc2ITM1NDYWFwEWA+gL/uMLHBZ9N1ZWPjgiFAMEAQoRBgQIBgNHHloBjn0WHAsBHQsB7Q8K/uILFg6PBhIeMEBaOB8mBBIGCAwKBQ4UA59db0vhjw4WAgn+4gsAAAEAAP+xA+gDLgArAClAJiYBBAMBRwADBANvAAQBBG8AAQIBbwACAAJvAAAAZiMXEz0XBQUZKyUUBw4CBwYiJjU0Njc2NTQuBSsBFRQGIicBJjQ3ATYyFgcVMyAXFgPoRwEKBAUHEQoCAQMUIjg+VlY3fRQgCf7jCwsBHQscGAJ9AY5aHuFdnwQSEAQKDAgFFAMmHzhaQDAeEgaPDhYLAR4KHgoBHgoUD4/hSwACAAD/sQPoAzUAFAA6ACtAKCYAAgADIQEBAAJHEAEDRQADAANvAgEAAQBvAAEBZjg3LCodHCQEBRUrJRUUBwYjIicBJjQ3ATYWHQEHBhQXBRQOAg8BBiMiJyY3NicuAScVFAcGIyInASY0NwE2FxYdARYXFgFlFgcHDwr+4wsLAR0RLN0LCwNgEhocCAsFCwMCDgEYUyR2WxUIBg8K/uILCwEeEBcV5mle9icXCgMLAR4KHgoBHhETFyfeCxwL8yBURkYQFgoBBA/fXCgsB4wXCgMLAR4KHgoBHhEJCheTD2xgAAADAAD/+QPoAn0AEQAiADMARkBDCwICBAINAQADAkcABAIDAgQDbQADAAIDAGsAAAECAAFrAAYAAgQGAmAAAQUFAVQAAQEFWAAFAQVMFxYkFBUYFgcFGysBJicWFRQGLgE1NDcGBx4BIDYBNCYHIgYVFBYyNjU0NjMyNgUUBwYEICQnJjQ3NiwBBBcWA6FVgCKS0JIigFVL4AEE4v63EAtGZBAWEEQwCxAB2QtO/vj+2v74TgsLTgEIASYBCE4LATqEQTpDZ5QCkGlDOkGEcoiIAUkLEAFkRQsQEAswRBDMExOBmpqBEyYUgJoCnn4UAAACAAD/vQNNAwsACAAdACRAIQABAQABRwABAAFwAAIAAAJUAAICAFgAAAIATDgaEgMFFysTNCYOAR4CNgEUBwEGIicBLgE9ATQ2NzMyFhcBFvoqOiwCKD4mAlUU/u4WOxT+cRUeKh3pHUgVAY8UAlgeKgImQCQGMP7ZHhX+7hUVAY8VSB3oHSoBHhX+cRUAAAADAAD/vQQkAwsACAAdADQAMEAtJgACAQABRwAEAgRvAwEBAAFwBQECAAACVAUBAgIAWAAAAgBMIBkpOBoSBgUaKxM0Jg4BHgI2ARQHAQYiJwEuAT0BNDY3MzIWFwEWFxQHAQYjIiYnATY0JwEuASMzMhYXARb6KjosAig+JgJVFP7uFjsU/nEVHiod6R1IFQGPFNcV/u4WHRQaEAEGFRX+cRVIHX0dSBUBjxUCWB4qAiZAJAYw/tkeFf7uFRUBjxVIHegdKgEeFf5xFR0eFf7uFRARAQYVOxUBjxUeHhX+cRUAAAABAAD/+QKDA1MAIwA2QDMABAUABQQAbQIGAgABBQABawABAW4ABQUDWAADAwwFSQEAIB8bGBQTEA4JBgAjASMHBRQrATIWFxEUBgchIiYnETQ2FzM1NDYeAQcUBisBIiY1NCYiBhcVAk0XHgEgFv3pFx4BIBYRlMyWAhQPJA4WVHZUAQGlHhf+vhYeASAVAUIWIAGzZ5QCkGkOFhYOO1RUO7MAAAEAAP/5A6EDDAAlADBALQQBAgEAAQIAbQAAAwEAA2sAAwNuAAUBAQVUAAUFAVgAAQUBTBMlNSMVJAYFGisBFRQGByMiJj0BNCYOAQcVMzIWFxEUBgchIiYnETQ2FyE1ND4BFgOhFg4kDhZSeFIBNRceASAW/ekXHgEgFgF3ktCQAhGPDxQBFg6PO1QCUD1sHhf+vhYeASAVAUIWIAFsZ5IClgAAAgAA//kCgwMLAAcAHwAqQCcFAwIAAQIBAAJtAAICbgAEAQEEVAAEBAFYAAEEAUwjEyU2ExAGBRorEyE1NCYOARcFERQGByEiJicRNDYXMzU0NjIWBxUzMhazAR1UdlQBAdAgFv3pFx4BIBYRlMyWAhIXHgGlbDtUAlA9of6+Fh4BIBUBQhYgAWxmlJRmbB4AAgAA//kDkgLFABAAMQAuQCsuJiUYFQ8ODQgBAwwBAAECRwQBAwEDbwABAAFvAgEAAGYqKCMiIREUBQUXKwERFAYHIzUjFSMiJicRCQEWNwcGByMiJwkBBiYvASY2NwE2Mh8BNTQ2OwEyFh0BFxYUAxIWDtaP1g8UAQFBAUEBfCIFBwIHBf5+/n4HDQUjBAIFAZESMBOICghrCAp6BgEo/vUPFAHW1hYOAQ8BCP74ASQpBQEDAUL+vgQCBSkGDgUBTg8PcWwICgoI42YEEAAAAAIAAP/5AWYDCwAeAC4AP0A8HwEFBhoSAgIDCAACAAEDRwAGAAUDBgVgAAMAAgEDAmAEAQEAAAFUBAEBAQBYAAABAEw1JiMmIRYzBwUbKyUVFAYHISImJzU0NjczNSMiJic1NDY3MzIWFxEzMhYDFRQGByMiJj0BNDY7ATIWAWUUEP7jDxQBFg4jIw8UARYO1g8UASMPFkgWDo8OFhYOjw8UZEcPFAEWDkcPFAHWFg5HDxQBFg7+vxYCdWsPFAEWDmsOFhYAAAAAAgAA//kCOQLDAA8AOwBrtQABAAEBR0uwD1BYQCYABAMCAwRlAAIBAwIBawAFAAMEBQNgAAEAAAFUAAEBAFgAAAEATBtAJwAEAwIDBAJtAAIBAwIBawAFAAMEBQNgAAEAAAFUAAEBAFgAAAEATFlACScUKx4mJAYFGislFRQGByMiJj0BNDYXMzIWExQOAwcOARUUBgcjIiY9ATQ2Nz4BNCYnIgcGBwYjIi8BLgE3NjMyHgIBiQ4IhgkODgmGCQyxEBgmGhUXHg4JhggMSiohHDQiJBgUKAcKBwdbCAIEWaotWkgulYYJDAEOCIYJDgEMAUUeNCIgEgoNMA0KEAEWCRouUhMQIDIiARAOMgkERgYQCJQiOlYAAAL///9qA6EDDQAIACEAK0AoHwEBAA4BAwECRwAEAAABBABgAAEAAwIBA2AAAgINAkkXIxQTEgUFGSsBNC4BBhQWPgEBFAYiLwEGIyIuAj4EHgIXFAcXFgKDktCSktCSAR4sOhS/ZHtQkmhAAjxsjqSObDwBRb8VAYJnkgKWypgGjP6aHSoVv0U+apCijm46BEJmlk17ZL8VAAAAAAMAAP/DA+gDQAASADcAcQCjQBhrAQELDQEAASkCAgUGMQEEBVYnAgMEBUdLsBpQWEAuAAYABQAGBW0ABQQABQRrAAIDAnAKAQEHAQAGAQBgCQEECAEDAgQDYAALCwwLSRtANgALAQtvAAYABQAGBW0ABQQABQRrAAIDAnAKAQEHAQAGAQBgCQEEAwMEVAkBBAQDWAgBAwQDTFlAF25tamlbWFJQQkA9PDQzMC8zFTYYDAUYKwEGBycuAycjIiY9ATQ2OwEyARQPAQYiJj0BIyIGLwEuBSc2Nx4ENzM1NDYyHwEWERQPAQYiJj0BIyIOAgcGBw4CDwEOAicjIiY9ATQ2OwEyPgI3Nj8BPgU3MzU0NjIfARYBdCIrFAgeGi4WfQgKCgh9iwLOBbMFDwowHh4aJw0uGCgaJA0hKwwQHhosGI8KDgeyBQWzBQ8KjxssIBoMEhkQGCQSKRc2QiZ9CAoKCH0bKiQUEBEaHAwkJC42QCiPCg4HsgUCRjRlKRAmGgwCCghrCAr9xQgFswUMBmsCAgMBCgoWFiYUNGQZHioUFAJrCAoFsgUB7AgFswUMBmsQIiIbIj0lMkQVLxoYFgEKCGsIChIgJBkjPT4aQDAsIgwDawgKBbIFAAAAAQAA/6wDrALgABcAQ0BAEwgCAgQHAQECAkcFAQQDAgMEAm0GAQAAAwQAA2AAAgEBAlQAAgIBWAABAgFMAQAVFBIRDw4LCQYEABcBFwcFFCsBMhYQBiMiJzcWMzI2ECYiBgczByczPgECFKru7qqObkZUYn60tPq0Ao64uHwC8ALg8P6s8FhKPLQBALSufMzMpuoAAAIAAP+xBHcDCwAFAB8AS0BIGAsCBAUXEhADAwQRAQIDA0cAAQUBbwAFBAVvAAQDBG8AAwIDbwYBAgAAAlIGAQICAFYAAAIASgAAHRsVFA4NAAUABRERBwUWKwUVIREzEQEVFAYvAQEGIi8BBycBNjIfAQEnJjY7ATIWBHf7iUcD6BQKRP6fBg4GguhrAUcFDgaCAQNDCQgN8wcKB0gDWvzuArjyDAoJRP6fBgaC6WwBRgYGggEDRAgWCgAAAwAA/2oEbwNTAAsAFwA/AEhARTsmJAIEBAULAQMAAkcABAUABQQAbQAAAwUAA2sAAwIFAwJrAAUFDEgGAQICAVgAAQENAUkNDDQzFBMQDwwXDRcSJAcFFisBFhcUBisBFAYiJicXMjQHIiY1NCIVFBYBFhQHAQYmLwEmND8BJjU+BDc0NjcmNTQ+ARYHFAceARc3NhYXA2UjhCoe+lR2UgGOCQkgMBI6AlgEBvvrBRAELwQGaAscLjAkFAGCagQgKiIBBEVqHeoFEAQBd8dwHSo7VFQ6YRIBMCEJCSk6A34GEAT8dwUCBTUGEARaERMYMlReiE1UkhAKCxceAiIVCwoKSDTKBQIFAAAAAAQAAP9qBG8DUwAMABcAJwBPAJBAG0wmJQ4EBgM1AQEGIQEABAABAgAERzcYAgYBRkuwEFBYQCwAAQYEBgEEbQAABAIEAGUABgAEAAYEYAADAwdYAAcHDEgAAgIFWAAFBQ0FSRtALQABBgQGAQRtAAAEAgQAAm0ABgAEAAYEYAADAwdYAAcHDEgAAgIFWAAFBQ0FSVlADEVEExIoJCMTEggFGysFNCMiJjU0IhUUFjcyCQEuAQciDgIHFAUUBisBFAYiJic3ISYnNxYTFxYUBwEGJi8BJjQ/ASY1PgQ3NDY3JjU0PgEWBxQHHgEXNzYWAkQJIDASOigJ/tUB6RdmSjNWMhoBAqcqHvpUdlIBUwGmXCI9I7QvBAb76wUQBC8EBmgLHC4wJBQBgmoEICoiAQRFah3qBRBgCDAhCQkpOgEBEgGoMUIBIjg8HNf6HSo7VFQ6SGmXN8cCmTUGEAT8dwUCBTUGEARaERMYMlReiE1UkhAKCxceAiIVCwoKSDTKBQIAAAABAAD/agPoA1IAHQAtQCoRAQIBGhkSDQwJBQQIAAICRwACAQABAgBtAAEBDEgAAAANAEkXGRoDBRcrARYUDwEXBw4BJwcjNTcmNj8BFzc2Mh4BDwEXNzYyA9MVFd9TWVv8aMplykUaW1lU3xU8KAIW34PfFjoCVRU6Ft9UWVsaRcplymf+WllT3xUqOhbfg98VAAAABQAA/8MD6AKxAAkAGgA+AEQAVwBXQFQ0GwIABFMGAgIAUkMCAQJQQiknCAEGBgEERwAFBAVvAAIAAQACAW0AAQYAAQZrAAYDAAYDawADA24ABAAABFQABAQAWAAABABMTEsTLhkkFB0HBRorJTcuATc0NwYHFgE0JgciBhUUFjI2NTQ2MzI2NxQVBgIPAQYjIicmNTQ3LgEnJjQ3PgEzMhc3NjMyFh8BFgcWExQGBxMWFxQHBgcOASM3PgE3Jic3HgEXFgE2KzA4ASKAVV4BahALRmQQFhBEMAsQyjvqOxwFCgdECRlQhjILC1b8lzIyHwUKAw4LJAsBCRVYSZ0E+gsWJ1TcfCl3yEVBXSM1YiALaU8jaj1DOkGEkAFnCxABZEULEBALMEQQdQQBaf5aaTIJJwYKByokeE0RKhKDmAo2CQYGFAYBBf79ToAbARgZXhMTJC1gakoKhGlkQD8kYjYTAAACAAD/sQNbAwsAJABHAF1AWkMlAgYJLwEFBhcBAwIIAQEDBEcACQgGCAkGbQcBBQYCBgUCbQQBAgMGAgNrAAEDAAMBAG0ACAAGBQgGYAADAQADVAADAwBYAAADAExGRSYlJTYlJjUUJAoFHSsBFBUOASMiJicHBiImPQE0NjsBMhYGDwEeATcyNjc2NzY7ATIWExUUBisBIiY2PwEmIyIGBwYHBisBIiY3NT4BMzIWFzc2MhYDSyTkmVGYPEgLHBYWDvoOFgIJTShkN0qCJwYYBAxrCAoOFBD6DhYCCU1ScEuCJwYXBQxvBwwBJOaZUZo8SAscGAEFAwGWuj45SAsWDvoOFhYcC00kKgFKPgo4DQwBuPoOFhYcC01NSj4KOA0MBgSWuj45SAsWAAABAAD/xAOsAvgAFwBDQEAQBQIEAREBBQQCRwIBAQMEAwEEbQYBAAADAQADYAAEBQUEVAAEBAVYAAUEBUwBABQSDw0KCQcGBAMAFwEXBwUUKwEyFhczByczLgEiBhQWMzI3FwYjIiYQNgGYqO4Eeri4kAS0+rS0fmhORm6OqPDwAvjops7OfKy0/rQ8TFjwAVTwAAAABP////kELwLDAA8AHwAqADIAVUBSGRECAgMBRwABAAMCAQNeAAIIAQAEAgBgCQEEAAcGBAdgCgEGBQUGVAoBBgYFWAAFBgVMLCshIAEAMC0rMiwxJyQgKiEqHRwVEwkGAA8BDgsFFCs3IiY1ETQ2MyEyFhcRFAYjAREUFjchMjY1ETQmJyEiBgEzFRQGByEiJjc1BTI0KwEiFDPoJTQ0JQJfJTQBNiT9jwwGAl8ICgoI/aEHCgL/WTQl/IMkNgECRAkJWQkJiDQlAYklNDQl/nclNAHi/ncHDAEKCAGJBwoBDP30NhYeASAVNjYSEgAAAwAA/7EDWgNSAAgAPwBvAFRAUUpCOAMDBQFHAAUCAwIFA20ACgAAAgoAYAAIAAIFCAJeAAMABwQDB2AABAAGBAZcAAEBCVgACQkMAUlubGdlXFpVUk9MPj0xLiglJCMVKwsFFis3NC4BBhQWPgEBNCYnIzQ2JzQmJw4CBwYHDgIPAQYPAQYnIxEzMh4EFxY7ATI1NCc+ATQnNjU0Jic+ATcUBxYVFAcWFRQHFAYrASImJyYrASImNRE0NjsBNjc2Nz4CNzYzMh4BFRQHMzIWjxYcFhYcFgKDLBzENgEiNw4OFBcNHgIWDgwWCgwWCgoSEgcWDhwMHAJ2SUNrAhAUCh0KCRIYRxsFFQEhYE5INmhFQQyhHSoqHZkUOSAcDQwWGBYcL0ooG2I6VmQPFAIYGhgCFAFQHSoBIHIgNzQBD0JKGA0mAxoUDhkLCA8HAf6bAgYGCAQEKV0PEAkqKBIcJw4iCQEyFTIpEhQrJgwMOCtOWhoXFyodAWUeKg1JKh4OREgYFSROQTM4VAAAAwAA/2oDWQMLAAgAQAByAE9ATHFoEQ8EAAIBRwAAAgMCAANtAAoAAQkKAWAACQACAAkCXgADAAgFAwhgAAUABgQFBmAABAQHWAAHBw0HSWZjYF0qJSQlHiEZPRsLBR0rEzQuAQYUFj4BATQmIz4BJzQnNjQmJzY1NCYrASIPAQ4BDwIGJyMRMzIWHwEeAh8BFhceAhcyNic0JiczMjY3FAYnIxYVFA4BIyInLgMnJicmJyMiJjURNDY7ATI3PgE3MzIWHQEWFRQHFhUUBxaPFhwWFhwWAoMYEggMAR0KFBACNjFHSXYQDQ4NFRIKCBISCRYLFgsWEAoNHg0XFA4ONiQBNAHEHCxHVDtiGydMLhwWExYGDgobITkUmR0qKh2hDEFIajo/TmAhARUFGwJYDxQCGBoYAhT+zhM0CiIOJhwRKigKEA8vLikFBAYEBgQCAf6bCgoUCh4SDREmDRhKQg82NiFwISwbOVYBNzRCTSQVEjYwLg0cK0kNKh4BZR0qFxgYAVhNAys4DAwmKhUSKQAAAAAIAAD/jgPEA1IACAARABoAIwAsADUAPgBHAFhAVRsBAwEJAQIAAkcJAQQMAQwEAW0ACAAHDAgHYAANAAwEDQxgBgEBBQEAAgEAYAADAAIDAlwACgoLWAALCwwKSUZFQkE9PDk4MC8TFBMYFBMUExIOBR0rJRQGIiY0NjIWBRQGIi4BNh4BARQOAS4BNh4BARQGIiY+AR4BARQGIiY0NjIWARQOASY+AR4BARQGIiY0NjIWBRQOAS4BNjIWASYqOyoqOiwBFCg+JgQuNjD+dCo8KAIsOC4CnCo7KgImQCT96TRKNDRKNAKNKjosAig+Jv6dPlo+Plo+AShKZ0gBSmZKSB0qKjsqKpEdKio6LAIoAWoeKAIsOC4GIv7IHSoqOiwCKAINJTQ0SjQ0/sUeKAIsOC4GIgFnLT4+Wj4+oDRIAUpmSkoAAAAAAQAA/7QDEAMIADYAPUA6AAIFBgUCBm0ABgQFBgRrAAEAAwcBA2AABwAFAgcFYAAEAAAEVAAEBABYAAAEAEwmFyYlExUVIggFHCslFAYjIicBJjQ+ARcBFhQGIicBJiIGFhcBFjMyNjc0JwEmIyIGFB8BFhQGIi8BJjU0NjMyFwEWAxBaQEs4/k4/fLBAAVIFIhAF/q0sdFIBKgGxIy4kLgEj/rsOExAWDuUGJA4G5SNALTEjAUQ4TUFYNwGyQLB6AT/+rgUQIgUBUytUdSv+TyQwIy4jAUQOFiIP5AYQIgXlIjEuQCP+uzYAAAAPAAD/+QQwAnwACwAXACMALwA7AEcAUwBfAGsAdwCDAI8AnwCjALMAjECJSAECAwFHAB4AGwUeG14aFxUPCwUFFhQOCgQEAwUEYBkRDQkEAxgQDAgEAgEDAmETBwIBEgYCABwBAGAfARwdHRxSHwEcHB1YAB0cHUygoLKvqqego6CjoqGfnJqYlZKPjImGg4B9end0cW5raGViX1xZVlJQTUpHREE+OzgzMzMzMzMzMzIgBR0rNxUUKwEiPQE0OwEyNxUUKwEiPQE0OwEyJxUUKwEiPQE0OwEyARUUIyEiPQE0MyEyJRUUKwEiPQE0OwEyJxUUKwEiPQE0OwEyFxUUKwEiPQE0OwEyJxUUKwEiPQE0OwEyFxUUKwEiPQE0OwEyFxUUKwEiPQE0OwEyARUUKwEiPQE0OwEyFxUUKwEiPQE0OwEyFxUUKwEiPQE0OwE1NDsBMhMRIREBERQGIyEiJjURNDYzITIW1gk1CQk1CUgJfQkJfQlICTUJCTUJAjwJ/h4JCQHiCf6bCTYJCTYJSAk1CQk1CdYINgkJNghHCTUJCTUJ1gk1CQk1CdcJNgkJNgn+4gk2CQk2CY8JNgkJNgmPCX0JCT4JNglH/F8D6Cgf/F8dKiodA6EeKsY1CQk1CYY1CQk1CYY2CQk2Cf7ZNQkJNQmGNQkJNQmGNgkJNgmYNQkJNQmGNgkJNgmYNQkJNQmYNQkJNQkBFTYJCTYJCTYJCTYJCcQJCTUJhgn+UwH0/gwB9P4MHSoqHQH0HioqAAAAAwAA//kDWgLEAA8AHwAvADdANCgBBAUIAAIAAQJHAAUABAMFBGAAAwACAQMCYAABAAABVAABAQBYAAABAEwmNSY1JjMGBRorJRUUBgchIiYnNTQ2NyEyFgMVFAYnISImJzU0NhchMhYDFRQGIyEiJic1NDYXITIWA1kUEPzvDxQBFg4DEQ8WARQQ/O8PFAEWDgMRDxYBFBD87w8UARYOAxEPFmRHDxQBFg5HDxQBFgEQSA4WARQPSA4WARQBDkcOFhYORw8WARQAAAAABAAAAAAEXwMLAAoAIAA6AFIAi0CIRwELCC8BBAYVAQIHAwEAAQRHEQ0CCwgGCAsGbRAJAgcEAgQHAm0PBQIDAgECAwFtAAwACggMCmAACAAGBAgGYAAEAAIDBAJgAAEAAAFUAAEBAFgOAQABAEw7OyEhCwsBADtSO1JMS0VDQD8hOiE6NDMtKyclCyALIBoZExIPDgYFAAoBChIFFCshIiYnND4BFgcUBjciLgEiBg8BIiY1NDc+AhYXFhUUBjciJy4BByIOAyMiJjU0Nz4BHgEXFhUUBjciJy4CBgcGIyImJzQ3NiQgBBcWFRQGAjsLUAFGLEgBUowBKkhIRhYWClQFLIKChCsFVI4GBkyCVS9gRjggAglUBkrQ2NJJBlSOBgdj2P7WZAcGCVQBBmgBIAEsASJnBVRSCxIYAhwQC1KXHBwcDg5UCgcGKzACNCkGBwpUmAU6OAEYIiQYVAoHBUpSAk5MBQcKVJcFWFgCXFYFVAoHBmhycmgGBwpUAAAAAv/+/7EDNgMLABIAMAAuQCsIAQQDAUcAAwQDbwAEAAABBABgAAECAgFUAAEBAlgAAgECTCgoJCwhBQUZKyUGIyIuATc0Nw4BBxQeAjcyNjcOASMiLgI3ND4CNzYWBw4BBxQeATcyNzYXHgECwB4fZqxmATpwjgE6XoZIUJClNdR8V6BwSAJAbppUGRQTMDIBUoxSQj0XEQgEewVkrmVrXCG+d0iGXD4DRG1xiER0nldVnHJGAwEuESt0QFOKVAEdChEIFgAAAAAD//7/sQPEA1IACwAQABYANkAzAAECAxABAgACAkcAAQQDBAEDbQADAgQDAmsAAgAEAgBrAAAAbgAEBAwESREUERUjBQUZKwkBDgEHIi4CPgEzEyEUBgcTIREyHgEBrQEwO55XdcZwBHi+eWgBr0I9XP5TdcR0AWH+0D1CAXTE6sR0/lNYnjsBeAGtcsYAAAACAAD/sQR3AwsABQALADRAMQsKCQMDAQFHAAEDAW8AAwIDbwQBAgAAAlIEAQICAFYAAAIASgAACAcABQAFEREFBRYrBRUhETMRARMhERMBBHf7iUcDWo78YPoBQQdIA1r87gI7/gwBQgFB/r8AAAAABQAA/7EEdwMLAAMABwANABEAFQBmQGMABQoFbw8BCgMKbwwBAwgDbw4BCAEIbwsBAQABbwkHAgMABgBvDQEGBAQGUg0BBgYEVgAEBgRKEhIODggIBAQAABIVEhUUEw4RDhEQDwgNCA0MCwoJBAcEBwYFAAMAAxEQBRUrAREjEQERIxEBFSERMxEBESMRJREjEQFljwFljgLK+4lHAsuPAWWPAV7+4gEeAR79xAI8/X1IA1r87gH0/lMBrdb9fQKDAAAAAAIAAP+xA3MDCwAXAB4AM0AwHhsXCAQEAQFHAAQBAAEEAG0AAABuAAIBAQJUAAICAVgFAwIBAgFMEhMjMyQyBgUaKyUWBgchIiY3ATUjIiY+ATMhMh4BBisBFQ8BIQM1IxUDVB8mO/19OyYfARgkDhYCEhABHg8UAhgNJJqXAY2jRyoyRgFIMQG73hYcFhYcFt4m8AEB8/MABgAA/8ADoQNSAAMAFAAcACQALAA0AENAGzIwLiwqKCYkIiAeGhgWAwIBEQABAUc0HAIBRUuwH1BYQAsAAAEAcAABAQwBSRtACQABAAFvAAAAZlm0FxgCBRYrATcnByUUBwEGIi8BJjQ3ATYyHwEWJRcPAS8BPwEfAQ8BLwE/AQEXDwEvAT8BARcPAS8BPwECmKQ8pAE2Cv0yCh4KbwoKAs4KHgpvCv0ONjYRETc3EdRtbSIhbW0hAik3NxERNjYR/qw2NhERNjYRAg6jPKNnDwr9MgoKbwoeCgLOCgpvClsQETc3ERA3kSIhbW0hIm3+iBEQNzcQETcBLhARNzcREDcAAAAB//D/fwPrA0UAOQAPQAwsAQBFAAAAZhMBBRUrJQYHBiYnJicmJyY3Nj8BNjc2HgIHBgcGBwYXFhcWFxY2Nz4BJzQnJicuAQc1NhcWFxYXFhcWBgcGA1dFX1rHWl5EXSUjGhpVBBMMG0IuCA4HCUUaGRYXQ0ppYsZDNTkBIClTUM1ldXd1XGAvIwICODcQCUUjIQYlJ0Rdf3t9gGMEFwcRBy4+Gw0JSmBeW15DShQSRU09mFBSTGFAPSIiASkTE0ZJcFJZV6ZFFgAAAAABAAAAAAIIAqEAFQAZQBYSCwQDAEQAAQABbwIBAABmFRUYAwUXKwEWFA8BJyY0NjIfARE0NjIWFRE3NjIB+Q8P9fUPHiwPeB4qIHgPKgFaDywP9fUPLB4PdwGLFR4eFf51dw8AAQAAAAAChgJiABQANEAxDQEBAAFHAAMAA28AAgECcAQBAAEBAFQEAQAAAVgAAQABTAEAEA8LCgYEABQBFAUFFCsBMhYUBichFxYUBiIvATc2MhYUDwECUxUeHhX+dXcPHiwP9fUPLB4PdwGTICogAXcPLB4P9fUPHiwPdgAAAAAB//8AAAKGAmIAFQAqQCcEAQIDAUcAAAMAbwABAgFwAAMCAgNUAAMDAlgAAgMCTCMkFBEEBRgrATYyHwEHBiImND8BISIuATY3IScmNAFIDyoQ9fUPKx4PeP51Fh4CIhQBi3gPAlMPD/X1Dx4sD3ceLB4Bdg8sAAABAAAAAAIIAqEAFAAYQBUOBwIARQIBAAEAbwABAWYVFRQDBRcrARcWFAYiLwERFAYuATURBwYiJjQ3AQT1Dx4qD3ggKh54DyweDwKh9Q8sHg94/nUVIAIcFwGLeA8eLA8AAAAAAQAA/70DSAMFABoAHEAZBwUCAAEBRwYBAEQAAQABbwAAAGYoEgIFFislFAYiLwEFEycmNzYzMjc2Nz4BHwEWBgcGBwYCPR4rEKn+xeyoGAwOIp1xWj0JNhfQFQ4Zfy04JRceEKnsATupFyEgOS1+GBAV0Rc2CT9ZbgAAAAIAAAAAAjQCUQAVACsAHEAZKRMCAAEBRwMBAQABbwIBAABmFx0XFAQFGCslFA8BBiInASY0NwE2Mh8BFhQPARcWFxQPAQYiJwEmNDcBNjIfARYUDwEXFgFeBhwFDgb+/AYGAQQFEAQcBgbb2wbWBRwGDgb+/AYGAQQGDgYcBQXc3AVSBwYcBQUBBQUOBgEEBgYcBRAE3NsGBwcGHAUFAQUFDgYBBAYGHAUQBNzbBgAAAgAAAAACIgJRABUAKwAcQBkhCwIAAQFHAwEBAAFvAgEAAGYcGBwUBAUYKwEUBwEGIi8BJjQ/AScmND8BNjIXARYXFAcBBiIvASY0PwEnJjQ/ATYyFwEWAUwF/vsFDgYcBgbb2wYGHAUQBAEFBdYF/vwGDgYcBQXb2wUFHAYOBgEEBQE6BwX++wUFHAYOBtvcBQ4GHAYG/vwFCAcF/vsFBRwGDgbb3AUOBhwGBv78BQAB//3/sQNfAwsADAARQA4AAQABbwAAAGYVEwIFFisBFA4BIi4CPgEyHgEDWXLG6MhuBnq89Lp+AV51xHR0xOrEdHTEAAP//P+QA5oDLAAIABMAKQBiQF8MAQMCIyIYFwQFBwJHAAcGBQYHBW0ABQQGBQRrCAEACQECAwACYAADAAYHAwZgCgEEAQEEVAoBBAQBWAABBAFMFRQKCQEAJiQgHhsZFCkVKRAOCRMKEwUEAAgBCAsFFCsBNgASAAQAAgAXIgYVBhYzMjY1NAMyNjcnBiMiPwE2IyIGBxc2MzIPAQYBxr4BEAb+9v6E/u4GAQzyKi4CIiAmLrQebDQSMBgOCioaMB52OBA0FgwMJBoDKgL++P6E/u4GAQoBfAESljAaHCAsIDr9rjQ0GCQmoGA6LhoiIphoAAABAAD/9wOIAsMALwBNQEouLCogAgUFBhkBBAUWEgIDBAsBAQIERwAGBQZvAAUEBW8ABAMEbwADAgNvAAIBAm8AAQAAAVQAAQEAWAAAAQBMJBYWIxEiKAcFGysBBgcVFA4DJyInFjMyNy4BJxYzMjcuAT0BFhcuATQ3HgEXJjU0NjcyFzY3Bgc2A4glNSpWeKhhl30TGH5iO1wSEw8YGD9SJiwlLBlEwHAFakpPNT02FTs0Am42JxdJkIZkQAJRAk0BRjYDBg1iQgIVAhlOYCpTZAUVFEtoATkMIEAkBgAAAAEAAP+xA1kDCwAkAEpARxIBBAUBRwcBAgMBAwIBbQgBAQFuCQEAAAUEAAVgAAQDAwRUAAQEA1YGAQMEA0oBAB4cGxoZGBUTEQ8MCwoJCAYAJAEjCgUUKwEyFhURFAYHIxEzNyM1NDY/ATUmIyIGFxUjFTMRISImNRE0NjcCuENeXkNobxB/GiZEI0FLXAFwcP7XQ15eQwMLYEH96EJeAQFNgVMfHgEBcwVYU1+B/rNgQQIYQl4BAAADAAD/sQNZAwsAGwAnADcAZkBjEgEDBBEBCAMCRwAIAwADCABtCgEGAAEABgFtAAsBAgELAm0ADQAEAw0EYAADCQcCAAYDAF4AAQACBQECYAAFDAwFVAAFBQxYAAwFDEw2My4rJyYlJCMiERESIyMjJBESDgUdKwE0JyMVMw4DJyImNDYzMhc3JiMiDgEWFzI2NzM1IzUjFSMVMxUzExEUBgchIiY1ETQ2NyEyFgIABMp6AhAaMB43Tk43NCI6PFRZfAKAV1xywD09PT09PZleQ/3pQ15eQwIXQ14BWQ8VSg0eHBYBUG5QITk3fLR6AnRDPj09Pj0BaP3oQl4BYEECGEJeAWAAAAAD//3/sQNZAwsADAAcAC4AREBBKB4CBQQWFQ4DAwICRwYBAAAEBQAEYAAFAAIDBQJgAAMBAQNUAAMDAVgAAQMBTAEALCojIRoYEhAHBgAMAQwHBRQrATIeARQOASIuAj4BEzU0JisBIgYHFRQWFzMyNicTNCcmKwEiBwYVExQWOwEyNgGtdMZycsboyG4GerzBCgdrCAoBDAdrBwoBCgYFCHsIBQYKCglnCAoDC3TE6sR0dMTqxHT9SGoICgoIaggKAQzHAVoHAwUFAwf+pgYICAAAAAIAAP/5A6ADCwAtAEIATkBLOwEEBiUBBQQCRwAHAQIBBwJtAAYCBAIGBG0ABAUCBAVrAAUDAgUDawABAAIGAQJgAAMAAANUAAMDAFgAAAMATBQXFSc1OTUzCAUcKwEVFAYjISImNRE0NjchMhceAQ8BBiMnJiMhIgYHERQWFyEyNj0BND8BNjMyFxYTAQYiLwEmND8BNjIfAQE2Mh8BFhQDEl5D/jBDXl5DAdAjHgkDBxsGBwUNDP4wJTQBNiQB0CU0BSQGBwMEC4H+OQ0kDvAODj0OJA6TAWkNJA4+DQFLsUNeXkMB0EJeAQ4EEwYcBQEDNCX+MCU0ATYkjQgFIwYCBAEF/joODvANJA4+DQ2TAWkNDT0OJAAC//7/xAM2AvgADgAdACVAIh0cFxEKBAEHAAEBRwkBAUUWAQBEAAEAAW8AAABmHBICBRYrPwERJTcmEjc2NxcGBw4BAQUHFgIHBgcnNjc+AScHunT+7Fh0BHZkjARkSFgEAaIBFFh0BHZgkAJiSFgEVnKMdP7cEFZ6AVB4ZBBmEEhY+gH6EFZ6/rB4YhRoEEhY+lx0AAAAAAT/4/+WBB4DJgAMABkAHgApAExASSIBBAYBRwAGAAQABgRtCAECBwEABgIAYAAEAAUBBAVgAAEDAwFUAAEBA1gAAwEDTA4NAQAoJx4dHBsVEg0ZDhkIBQAMAQwJBRQrASIHAQYWMyEyNicBJicyFwEWBiMhIiY3ATYTNDIUIhMUDwEnJjU0PgEWAgIxIP7MICpCAnFBLCL+zSEvaj8BND9nff2Pe2tAATU+J4iIkgZHSQYuQiwCvTf9/zdQUDcCATdpa/3/abu5awIBa/10RYgBfA4Ps7MPDiAuAjIAAAAABgAA//YDqQLGAAwAGQAmADMAQABNADxAOQsBBQoBBAMFBGAJAQMIAQIBAwJgBwEBAAABVAcBAQEAWAYBAAEATExJRkM/PDQzNDM0MzQzMgwFHSs1FBY7ATI2NCYrASIGERQWOwEyNjQmKwEiBhEUFjsBMjY0JisBIgYTFBYzITI2NCYjISIGERQWMyEyNjQmIyEiBhEUFjMhMjY0JiMhIgYqHiAeKioeIB4qKh4gHioqHiAeKioeIB4qKh4gHirqKh4CLx4qKh790R4qKh4CLx4qKh790R4qKh4CLx4qKh790R4qPh4qKjwqKgECHioqPCoqAQIeKio8Kir9oh4qKjwqKgECHioqPCoqAQIeKio8KioACP///4sDqgMxAA8AHwAjACcANwBHAEsATwBOQEsKAQIPAQcGAgdeDgEGCwEDAAYDYAgBAA0BBQQABV4MAQQBAQRSDAEEBAFYCQEBBAFMT05NTEtKSUhGQz47NjM0EREREjU1NTMQBR0rFRE0NjchMhYHERQGIyEiJhkBNDYzITIWBxEUBgchIiYTMzUjETM1IwERNDY3ITIWBxEUBiMhIiYTETQ2MyEyFhURFAYHISImEzM1IxEzNSMeFgEeFSABHhb+4hYeHhYBHhUgAR4W/uIVIFnW1tbWAcseFgEeFSABHhb+4hUgAR4WAR4WHh4W/uIVIFnX19fXQgEeFh4BIBX+4hUeHgI3AR4VHh4V/uIWHgEg/hfWAUzV/OUBHhYeASAV/uIVHh4CNwEeFR4eFf7iFh4BIP4X1gFM1QAAAAAIAAD/xANZAwsAUwBaAF8AZABpAG4AcwB4AGpAZyQeGxUEBAFlDQIDAmoBBwZHAQUHBEcABAECAQQCbQACAwECA2sAAwYBAwZrAAYHAQYHawAHBQEHBWsABQVuCAEAAQEAVAgBAAABWAABAAFMAQBzcnFwRkQ4NzEwLCsdHABTAVMJBRQrATIeARUUBgcGJj0BNCc+BCc0JzYnJgYPASYiBy4CBwYXBhUUHgMXBgcOASImJy4BLwEiBh4BHwEeAR8BHgI2MzcVFBcUBicuATU0PgEDNicmBwYWFzYmBhYXNiYGFhc2JgYWFzYmBhY3NAYUNjcmBhY2Aa10xnKkgQ8OHSAyOCIaAiwVGRA8FRU0bjUIHkAPGRQsGCI4MCEVBgwaJiIOCyAMCwwIAggDBAwYBgYHIigmDA0BEA6BpHTClAIFBgIBChQECwcKFAYKCgocBA0JDSUBEQQRJhMTIAESAhIDC3TEdYzgKwMOCnY2GQMOHixIMEMwMz8FFg4NDw8GEhoGPzMwQy9ILhwQAhQmBQYYFxIWAwEECgYDAwYeDg0VGggCAzIcAgoOAyvgjHXEdP2YBAMBAgQGDwMLBgwVBA4HDhQEDQoMCQYFDAYEBwENAQsHAw4GAAAAAAIAAAAAAlgCYwAVACsAK0AoHQECBQcBAwICRwAFAgVvAAIDAm8EAQMAA28BAQAAZhcUGBcUFAYFGislFA8BBiIvAQcGIi8BJjQ3ATYyFwEWNRQPAQYiLwEHBiIvASY0NwE2MhcBFgJYBhwFDgbc2wUQBBwGBgEEBQ4GAQQGBhwFDgbc2wUQBBwGBgEEBQ4GAQQGdgcGHAUF29sFBRwGDgYBBAUF/vwGzwcGHAUF3NwFBRwGDgYBBAYG/vwGAAAAAAIAAAAAAlgCdQAVACsAK0AoJQEDAQ8BAAMCRwUBBAEEbwIBAQMBbwADAANvAAAAZhQXGBQXFAYFGisBFAcBBiInASY0PwE2Mh8BNzYyHwEWNRQHAQYiJwEmND8BNjIfATc2Mh8BFgJYBv78BRAE/vwGBhwFDgbb3AUQBBwGBv78BRAE/vwGBhwFDgbb3AUQBBwGAXAHBv78BgYBBAYOBhwFBdzcBQUcBs8HBv78BQUBBAYOBhwGBtvbBgYcBgAAAAEAAAAAAV4CUQAVABdAFAMBAAEBRwABAAFvAAAAZhcZAgUWKwEUDwEXFhQPAQYiJwEmNDcBNjIfARYBXgbb2wYGHAUOBv78BgYBBAUQBBwGAiIHBdzbBg4GHAUFAQUFDgYBBAYGHAUAAQAAAAABTAJRABUAF0AUCwEAAQFHAAEAAW8AAABmHBQCBRYrARQHAQYiLwEmND8BJyY0PwE2MhcBFgFMBf77BQ4GHAYG29sGBhwFEAQBBQUBOgcF/vsFBRwGDgbb3AUOBhwGBv78BQABAAAAAAJYAdQAFQAZQBYHAQACAUcAAgACbwEBAABmFxQUAwUXKyUUDwEGIi8BBwYiLwEmNDcBNjIXARYCWAYcBQ4G3NsFEAQcBgYBBAUOBgEEBr0HBRwGBtvbBgYcBQ4GAQQGBv78BQAAAAABAAAAAAJYAeYAFQAZQBYPAQABAUcCAQEAAW8AAABmFBcUAwUXKwEUBwEGIicBJjQ/ATYyHwE3NjIfARYCWAb+/AUQBP78BgYcBQ4G29wFEAQcBgG3BwX++wUFAQUFDgYcBgbb2wYGHAUAAAACAAD/sQNZAwsAMQBGAFpAVyoBAwUdAQgDQCUCBAg7MwIGBwRHAAgDBAMIBG0ABAcDBAdrAAEGAgYBAm0ABQADCAUDYAAHAAYBBwZgAAIAAAJUAAICAFgAAAIATCMmJyk1FyMXJAkFHSsBFA4CIyImJyY0PwE2FhceATMyPgMuAiIGBxcWBisBIiYnNTQ2HwE+ATMyHgIlFRQGKwEiJj0BNDY7ATU0NjsBMhYDWURyoFZgrjwEBUwGEQQpdkM6aFAqAi5MbG9kKE0RExf6DxQBLBFIPJpSV550Qv6cCgiyCAoKCH0KByQICgFeV550RFJJBg4ETQUBBjU6LkxqdGpMLiglTRAtFg76GBMSSDk+RHSeSvoICgoIIwgKxQgKCgAFAAD/agPoA1IAEAAUACUALwA5AGdAZDMpAgcIIQEFAh0VDQwEAAUDRwQBBQFGBgwDCwQBBwIHAQJtAAIFBwIFawAFAAcFAGsJAQcHCFgKAQgIDEgEAQAADQBJEREAADc1MjEtKygnJCIfHhsZERQRFBMSABAADzcNBRUrAREUBgcRFAYHISImJxETNjMhESMRAREUBgchIiYnESImJxEzMhclFSM1NDY7ATIWBRUjNTQ2OwEyFgGJFg4UEP7jDxQBiwQNAZ+OAjsWDv7jDxQBDxQB7Q0E/j7FCgihCAoBd8UKCKEICgKf/lQPFAH+vw8UARYOAR0B6Az+eAGI/gz+4w8UARYOAUEWDgGsDK19fQgKCgh9fQgKCgAAAAEAAAABAAB31GYfXw889QALA+gAAAAA2tnFqwAAAADa2cWr/+P/aQS/A1MAAAAIAAIAAAAAAAAAAQAAA1L/agAABQX/4//kBL8AAQAAAAAAAAAAAAAAAAAAAJED6AAAA+gAAALKAAAEL///A6AAAAMxAAADoAAAA6AAAAOgAAADoAAAA6AAAAPoAAAFBQAAA1kAAAPoAAAD6AAAA6AAAAOgAAAD6P//A6AAAAPoAAADEf/5A1n//QOg//kD6AAAA+j/+gNZ//0EL///AfT//gPoAAACOwAAAjv//wFlAAABZQAAA+gAAALKAAAD6AAAAsoAAAOgAAADWQAAA1kAAAOgAAADWQAAA1kAAANZAAAD6AAAA+gAAAGsAAADoP//A1n//wOg//8COwAAA1n//QOgAAACggAAAawAAAMRAAACgv//A1kAAAOgAAADoAAAA6AAAAOgAAADWQAABC8AAANZAAADEf//A1kAAANZAAADWQAAA1kAAAMRAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAABZQAAA6D//wPoAAAD6AAAA+gAAAPoAAAD6AAAA1kAAAQvAAACggAAA6AAAAKCAAADoAAAAWUAAAI7AAADoP//A+gAAAOsAAAEdgAABHYAAAR2AAAD6AAAA+gAAANZAAADrAAABC///wNZAAADWQAAA+gAAAMRAAAELwAAA1kAAAR2AAADWf/+A+j//gR2AAAEdgAAA6AAAAOgAAAD6P/wAggAAAKGAAAChv//AggAAANCAAACOwAAAjsAAANZ//0DmP/8A6AAAANZAAADWQAAA1n//QOgAAADNP/+BAL/4wOpAAADqf//A1kAAAKCAAACggAAAWUAAAFlAAACggAAAoIAAANZAAAD6AAAAAAAAADuATIB9gIMAioCWgJ2AsIDRgPKBOQFagYABrIHSAhMCVQJzApoCvQLKAuMDGYM4g3yEfYSMhJ+ExQTPBNiE4oTrBPiFCIUWBSYFNwVIhVoFawWKhaQFvQXehfCGAoYnBjuGVgaBhpsGzAbjBu+HHYc9B1+HgAeoh+QIAogxiJqIywjtCSwJYgmZicaJ94oWiimKTYp8iqQKvorRCvMLMItFC1qLdwuVC6cLw4vYC+yL/owYjDGMVIxoDKOMtgzNjO8NH40yDV6NhA2WjbSN5g4YjkGOXw6njsEO8Q8KDxwPKg9Cj1WPdg+Pj5wPrA+7D8eP1w/tEAMQC5AqEEYQXRB+EJiQu5DOkOqRDRE1EXCRiJGgka2RupHIEdWR+ZIcwABAAAAkQH4AA8AAAAAAAIARABUAHMAAACwC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEABQA1AAEAAAAAAAIABwA6AAEAAAAAAAMABQBBAAEAAAAAAAQABQBGAAEAAAAAAAUACwBLAAEAAAAAAAYABQBWAAEAAAAAAAoAKwBbAAEAAAAAAAsAEwCGAAMAAQQJAAAAagCZAAMAAQQJAAEACgEDAAMAAQQJAAIADgENAAMAAQQJAAMACgEbAAMAAQQJAAQACgElAAMAAQQJAAUAFgEvAAMAAQQJAAYACgFFAAMAAQQJAAoAVgFPAAMAAQQJAAsAJgGlQ29weXJpZ2h0IChDKSAyMDIwIGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21pZm9udFJlZ3VsYXJpZm9udGlmb250VmVyc2lvbiAxLjBpZm9udEdlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMgAwACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBpAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGYAbwBuAHQAaQBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkQECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEvATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlAWYBZwFoAWkBagFrAWwBbQFuAW8BcAFxAXIBcwF0AXUBdgF3AXgBeQF6AXsBfAF9AX4BfwGAAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPAZABkQGSAAlkYXNoYm9hcmQEdXNlcgV1c2VycwJvawZjYW5jZWwEcGx1cwVtaW51cwxmb2xkZXItZW1wdHkIZG93bmxvYWQGdXBsb2FkA2dpdAVjdWJlcwhkYXRhYmFzZQVnYXVnZQdzaXRlbWFwDHNvcnQtbmFtZS11cA5zb3J0LW5hbWUtZG93bgltZWdhcGhvbmUDYnVnBXRhc2tzBmZpbHRlcgNvZmYEYm9vawVwYXN0ZQhzY2lzc29ycwVnbG9iZQVjbG91ZAVmbGFzaAhiYXJjaGFydAhkb3duLWRpcgZ1cC1kaXIIbGVmdC1kaXIJcmlnaHQtZGlyCWRvd24tb3BlbgpyaWdodC1vcGVuB3VwLW9wZW4JbGVmdC1vcGVuBnVwLWJpZwlyaWdodC1iaWcIbGVmdC1iaWcIZG93bi1iaWcPcmVzaXplLWZ1bGwtYWx0C3Jlc2l6ZS1mdWxsDHJlc2l6ZS1zbWFsbARtb3ZlEXJlc2l6ZS1ob3Jpem9udGFsD3Jlc2l6ZS12ZXJ0aWNhbAd6b29tLWluBWJsb2NrCHpvb20tb3V0CWxpZ2h0YnVsYgVjbG9jawl2b2x1bWUtdXALdm9sdW1lLWRvd24Kdm9sdW1lLW9mZgRtdXRlA21pYwdlbmR0aW1lCXN0YXJ0dGltZQ5jYWxlbmRhci1lbXB0eQhjYWxlbmRhcgZ3cmVuY2gHc2xpZGVycwhzZXJ2aWNlcwdzZXJ2aWNlBXBob25lCGZpbGUtcGRmCWZpbGUtd29yZApmaWxlLWV4Y2VsCGRvYy10ZXh0BXRyYXNoDWNvbW1lbnQtZW1wdHkHY29tbWVudARjaGF0CmNoYXQtZW1wdHkEYmVsbAhiZWxsLWFsdA1hdHRlbnRpb24tYWx0BXByaW50BGVkaXQHZm9yd2FyZAVyZXBseQlyZXBseS1hbGwDZXllA3RhZwR0YWdzDWxvY2stb3Blbi1hbHQJbG9jay1vcGVuBGxvY2sEaG9tZQRpbmZvBGhlbHAGc2VhcmNoCGZsYXBwaW5nBnJld2luZApjaGFydC1saW5lCGJlbGwtb2ZmDmJlbGwtb2ZmLWVtcHR5BHBsdWcHZXllLW9mZglhcnJvd3MtY3cCY3cEaG9zdAl0aHVtYnMtdXALdGh1bWJzLWRvd24Hc3Bpbm5lcgZhdHRhY2gIa2V5Ym9hcmQEbWVudQR3aWZpBG1vb24JY2hhcnQtcGllCmNoYXJ0LWFyZWEJY2hhcnQtYmFyBmJlYWtlcgVtYWdpYwVzcGluNgpkb3duLXNtYWxsCmxlZnQtc21hbGwLcmlnaHQtc21hbGwIdXAtc21hbGwDcGluEWFuZ2xlLWRvdWJsZS1sZWZ0EmFuZ2xlLWRvdWJsZS1yaWdodAZjaXJjbGUMaW5mby1jaXJjbGVkB3R3aXR0ZXIQZmFjZWJvb2stc3F1YXJlZA1ncGx1cy1zcXVhcmVkEWF0dGVudGlvbi1jaXJjbGVkBWNoZWNrCnJlc2NoZWR1bGUNd2FybmluZy1lbXB0eQd0aC1saXN0DnRoLXRodW1iLWVtcHR5DmdpdGh1Yi1jaXJjbGVkD2FuZ2xlLWRvdWJsZS11cBFhbmdsZS1kb3VibGUtZG93bgphbmdsZS1sZWZ0C2FuZ2xlLXJpZ2h0CGFuZ2xlLXVwCmFuZ2xlLWRvd24HaGlzdG9yeQpiaW5vY3VsYXJzAAAAAQAB//8ADwAAAAAAAAAAAAAAAAAAAAAAGAAYABgAGANT/2kDU/9psAAsILAAVVhFWSAgS7gADlFLsAZTWliwNBuwKFlgZiCKVViwAiVhuQgACABjYyNiGyEhsABZsABDI0SyAAEAQ2BCLbABLLAgYGYtsAIsIGQgsMBQsAQmWrIoAQpDRWNFUltYISMhG4pYILBQUFghsEBZGyCwOFBYIbA4WVkgsQEKQ0VjRWFksChQWCGxAQpDRWNFILAwUFghsDBZGyCwwFBYIGYgiophILAKUFhgGyCwIFBYIbAKYBsgsDZQWCGwNmAbYFlZWRuwAStZWSOwAFBYZVlZLbADLCBFILAEJWFkILAFQ1BYsAUjQrAGI0IbISFZsAFgLbAELCMhIyEgZLEFYkIgsAYjQrEBCkNFY7EBCkOwAWBFY7ADKiEgsAZDIIogirABK7EwBSWwBCZRWGBQG2FSWVgjWSEgsEBTWLABKxshsEBZI7AAUFhlWS2wBSywB0MrsgACAENgQi2wBiywByNCIyCwACNCYbACYmawAWOwAWCwBSotsAcsICBFILALQ2O4BABiILAAUFiwQGBZZrABY2BEsAFgLbAILLIHCwBDRUIqIbIAAQBDYEItsAkssABDI0SyAAEAQ2BCLbAKLCAgRSCwASsjsABDsAQlYCBFiiNhIGQgsCBQWCGwABuwMFBYsCAbsEBZWSOwAFBYZVmwAyUjYUREsAFgLbALLCAgRSCwASsjsABDsAQlYCBFiiNhIGSwJFBYsAAbsEBZI7AAUFhlWbADJSNhRESwAWAtsAwsILAAI0KyCwoDRVghGyMhWSohLbANLLECAkWwZGFELbAOLLABYCAgsAxDSrAAUFggsAwjQlmwDUNKsABSWCCwDSNCWS2wDywgsBBiZrABYyC4BABjiiNhsA5DYCCKYCCwDiNCIy2wECxLVFixBGREWSSwDWUjeC2wESxLUVhLU1ixBGREWRshWSSwE2UjeC2wEiyxAA9DVVixDw9DsAFhQrAPK1mwAEOwAiVCsQwCJUKxDQIlQrABFiMgsAMlUFixAQBDYLAEJUKKiiCKI2GwDiohI7ABYSCKI2GwDiohG7EBAENgsAIlQrACJWGwDiohWbAMQ0ewDUNHYLACYiCwAFBYsEBgWWawAWMgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLEAABMjRLABQ7AAPrIBAQFDYEItsBMsALEAAkVUWLAPI0IgRbALI0KwCiOwAWBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsBQssQATKy2wFSyxARMrLbAWLLECEystsBcssQMTKy2wGCyxBBMrLbAZLLEFEystsBossQYTKy2wGyyxBxMrLbAcLLEIEystsB0ssQkTKy2wHiwAsA0rsQACRVRYsA8jQiBFsAsjQrAKI7ABYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wHyyxAB4rLbAgLLEBHistsCEssQIeKy2wIiyxAx4rLbAjLLEEHistsCQssQUeKy2wJSyxBh4rLbAmLLEHHistsCcssQgeKy2wKCyxCR4rLbApLCA8sAFgLbAqLCBgsBBgIEMjsAFgQ7ACJWGwAWCwKSohLbArLLAqK7AqKi2wLCwgIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgjIIpVWCBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4GyFZLbAtLACxAAJFVFiwARawLCqwARUwGyJZLbAuLACwDSuxAAJFVFiwARawLCqwARUwGyJZLbAvLCA1sAFgLbAwLACwAUVjuAQAYiCwAFBYsEBgWWawAWOwASuwC0NjuAQAYiCwAFBYsEBgWWawAWOwASuwABa0AAAAAABEPiM4sS8BFSotsDEsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYTgtsDIsLhc8LbAzLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2GwAUNjOC2wNCyxAgAWJSAuIEewACNCsAIlSYqKRyNHI2EgWGIbIVmwASNCsjMBARUUKi2wNSywABawBCWwBCVHI0cjYbAJQytlii4jICA8ijgtsDYssAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgsAhDIIojRyNHI2EjRmCwBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2EjICCwBCYjRmE4GyOwCENGsAIlsAhDRyNHI2FgILAEQ7ACYiCwAFBYsEBgWWawAWNgIyCwASsjsARDYLABK7AFJWGwBSWwAmIgsABQWLBAYFlmsAFjsAQmYSCwBCVgZCOwAyVgZFBYIRsjIVkjICCwBCYjRmE4WS2wNyywABYgICCwBSYgLkcjRyNhIzw4LbA4LLAAFiCwCCNCICAgRiNHsAErI2E4LbA5LLAAFrADJbACJUcjRyNhsABUWC4gPCMhG7ACJbACJUcjRyNhILAFJbAEJUcjRyNhsAYlsAUlSbACJWG5CAAIAGNjIyBYYhshWWO4BABiILAAUFiwQGBZZrABY2AjLiMgIDyKOCMhWS2wOiywABYgsAhDIC5HI0cjYSBgsCBgZrACYiCwAFBYsEBgWWawAWMjICA8ijgtsDssIyAuRrACJUZSWCA8WS6xKwEUKy2wPCwjIC5GsAIlRlBYIDxZLrErARQrLbA9LCMgLkawAiVGUlggPFkjIC5GsAIlRlBYIDxZLrErARQrLbA+LLA1KyMgLkawAiVGUlggPFkusSsBFCstsD8ssDYriiAgPLAEI0KKOCMgLkawAiVGUlggPFkusSsBFCuwBEMusCsrLbBALLAAFrAEJbAEJiAuRyNHI2GwCUMrIyA8IC4jOLErARQrLbBBLLEIBCVCsAAWsAQlsAQlIC5HI0cjYSCwBCNCsAlDKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgR7AEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYbACJUZhOCMgPCM4GyEgIEYjR7ABKyNhOCFZsSsBFCstsEIssDUrLrErARQrLbBDLLA2KyEjICA8sAQjQiM4sSsBFCuwBEMusCsrLbBELLAAFSBHsAAjQrIAAQEVFBMusDEqLbBFLLAAFSBHsAAjQrIAAQEVFBMusDEqLbBGLLEAARQTsDIqLbBHLLA0Ki2wSCywABZFIyAuIEaKI2E4sSsBFCstsEkssAgjQrBIKy2wSiyyAABBKy2wSyyyAAFBKy2wTCyyAQBBKy2wTSyyAQFBKy2wTiyyAABCKy2wTyyyAAFCKy2wUCyyAQBCKy2wUSyyAQFCKy2wUiyyAAA+Ky2wUyyyAAE+Ky2wVCyyAQA+Ky2wVSyyAQE+Ky2wViyyAABAKy2wVyyyAAFAKy2wWCyyAQBAKy2wWSyyAQFAKy2wWiyyAABDKy2wWyyyAAFDKy2wXCyyAQBDKy2wXSyyAQFDKy2wXiyyAAA/Ky2wXyyyAAE/Ky2wYCyyAQA/Ky2wYSyyAQE/Ky2wYiywNysusSsBFCstsGMssDcrsDsrLbBkLLA3K7A8Ky2wZSywABawNyuwPSstsGYssDgrLrErARQrLbBnLLA4K7A7Ky2waCywOCuwPCstsGkssDgrsD0rLbBqLLA5Ky6xKwEUKy2wayywOSuwOystsGwssDkrsDwrLbBtLLA5K7A9Ky2wbiywOisusSsBFCstsG8ssDorsDsrLbBwLLA6K7A8Ky2wcSywOiuwPSstsHIsswkEAgNFWCEbIyFZQiuwCGWwAyRQeLABFTAtAEu4AMhSWLEBAY5ZsAG5CAAIAGNwsQAFQrIAAQAqsQAFQrMKAgEIKrEABUKzDgABCCqxAAZCugLAAAEACSqxAAdCugBAAAEACSqxAwBEsSQBiFFYsECIWLEDZESxJgGIUVi6CIAAAQRAiGNUWLEDAERZWVlZswwCAQwquAH/hbAEjbECAEQAAA==') format('truetype');
+}
+/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
+/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
+/*
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ @font-face {
+ font-family: 'ifont';
+ src: url('../font/ifont.svg?21447335#ifont') format('svg');
+ }
+}
+*/
+
+ [class^="icon-"]:before, [class*=" icon-"]:before {
+ font-family: "ifont";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+
+ display: inline-block;
+ text-decoration: inherit;
+ width: 1em;
+ margin-right: .2em;
+ text-align: center;
+ /* opacity: .8; */
+
+ /* For safety - reset parent styles, that can break glyph codes*/
+ font-variant: normal;
+ text-transform: none;
+
+ /* fix buttons height, for twitter bootstrap */
+ line-height: 1em;
+
+ /* Animation center compensation - margins should be symmetric */
+ /* remove if not needed */
+ margin-left: .2em;
+
+ /* you can be more comfortable with increased icons size */
+ /* font-size: 120%; */
+
+ /* Uncomment for 3D effect */
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
+}
+.icon-dashboard:before { content: '\e800'; } /* 'î €' */
+.icon-user:before { content: '\e801'; } /* 'î ' */
+.icon-users:before { content: '\e802'; } /* 'î ‚' */
+.icon-ok:before { content: '\e803'; } /* 'î ƒ' */
+.icon-cancel:before { content: '\e804'; } /* 'î „' */
+.icon-plus:before { content: '\e805'; } /* 'î …' */
+.icon-minus:before { content: '\e806'; } /* 'î †' */
+.icon-folder-empty:before { content: '\e807'; } /* 'î ‡' */
+.icon-download:before { content: '\e808'; } /* 'î ˆ' */
+.icon-upload:before { content: '\e809'; } /* 'î ‰' */
+.icon-git:before { content: '\e80a'; } /* 'î Š' */
+.icon-cubes:before { content: '\e80b'; } /* 'î ‹' */
+.icon-database:before { content: '\e80c'; } /* '' */
+.icon-gauge:before { content: '\e80d'; } /* 'î ' */
+.icon-sitemap:before { content: '\e80e'; } /* 'î Ž' */
+.icon-sort-name-up:before { content: '\e80f'; } /* 'î ' */
+.icon-sort-name-down:before { content: '\e810'; } /* 'î ' */
+.icon-megaphone:before { content: '\e811'; } /* 'î ‘' */
+.icon-bug:before { content: '\e812'; } /* 'î ’' */
+.icon-tasks:before { content: '\e813'; } /* 'î “' */
+.icon-filter:before { content: '\e814'; } /* 'î ”' */
+.icon-off:before { content: '\e815'; } /* 'î •' */
+.icon-book:before { content: '\e816'; } /* 'î –' */
+.icon-paste:before { content: '\e817'; } /* 'î —' */
+.icon-scissors:before { content: '\e818'; } /* 'î ˜' */
+.icon-globe:before { content: '\e819'; } /* 'î ™' */
+.icon-cloud:before { content: '\e81a'; } /* 'î š' */
+.icon-flash:before { content: '\e81b'; } /* 'î ›' */
+.icon-barchart:before { content: '\e81c'; } /* '' */
+.icon-down-dir:before { content: '\e81d'; } /* 'î ' */
+.icon-up-dir:before { content: '\e81e'; } /* 'î ž' */
+.icon-left-dir:before { content: '\e81f'; } /* 'î Ÿ' */
+.icon-right-dir:before { content: '\e820'; } /* 'î  ' */
+.icon-down-open:before { content: '\e821'; } /* 'î ¡' */
+.icon-right-open:before { content: '\e822'; } /* 'î ¢' */
+.icon-up-open:before { content: '\e823'; } /* 'î £' */
+.icon-left-open:before { content: '\e824'; } /* 'î ¤' */
+.icon-up-big:before { content: '\e825'; } /* 'î ¥' */
+.icon-right-big:before { content: '\e826'; } /* 'î ¦' */
+.icon-left-big:before { content: '\e827'; } /* 'î §' */
+.icon-down-big:before { content: '\e828'; } /* 'î ¨' */
+.icon-resize-full-alt:before { content: '\e829'; } /* 'î ©' */
+.icon-resize-full:before { content: '\e82a'; } /* 'î ª' */
+.icon-resize-small:before { content: '\e82b'; } /* 'î «' */
+.icon-move:before { content: '\e82c'; } /* 'î ¬' */
+.icon-resize-horizontal:before { content: '\e82d'; } /* 'î ­' */
+.icon-resize-vertical:before { content: '\e82e'; } /* 'î ®' */
+.icon-zoom-in:before { content: '\e82f'; } /* 'î ¯' */
+.icon-block:before { content: '\e830'; } /* 'î °' */
+.icon-zoom-out:before { content: '\e831'; } /* 'î ±' */
+.icon-lightbulb:before { content: '\e832'; } /* 'î ²' */
+.icon-clock:before { content: '\e833'; } /* 'î ³' */
+.icon-volume-up:before { content: '\e834'; } /* 'î ´' */
+.icon-volume-down:before { content: '\e835'; } /* 'î µ' */
+.icon-volume-off:before { content: '\e836'; } /* 'î ¶' */
+.icon-mute:before { content: '\e837'; } /* 'î ·' */
+.icon-mic:before { content: '\e838'; } /* 'î ¸' */
+.icon-endtime:before { content: '\e839'; } /* 'î ¹' */
+.icon-starttime:before { content: '\e83a'; } /* 'î º' */
+.icon-calendar-empty:before { content: '\e83b'; } /* 'î »' */
+.icon-calendar:before { content: '\e83c'; } /* 'î ¼' */
+.icon-wrench:before { content: '\e83d'; } /* 'î ½' */
+.icon-sliders:before { content: '\e83e'; } /* 'î ¾' */
+.icon-services:before { content: '\e83f'; } /* 'î ¿' */
+.icon-service:before { content: '\e840'; } /* 'î¡€' */
+.icon-phone:before { content: '\e841'; } /* 'î¡' */
+.icon-file-pdf:before { content: '\e842'; } /* 'î¡‚' */
+.icon-file-word:before { content: '\e843'; } /* '' */
+.icon-file-excel:before { content: '\e844'; } /* 'î¡„' */
+.icon-doc-text:before { content: '\e845'; } /* 'î¡…' */
+.icon-trash:before { content: '\e846'; } /* '' */
+.icon-comment-empty:before { content: '\e847'; } /* '' */
+.icon-comment:before { content: '\e848'; } /* '' */
+.icon-chat:before { content: '\e849'; } /* '' */
+.icon-chat-empty:before { content: '\e84a'; } /* 'î¡Š' */
+.icon-bell:before { content: '\e84b'; } /* 'î¡‹' */
+.icon-bell-alt:before { content: '\e84c'; } /* '' */
+.icon-attention-alt:before { content: '\e84d'; } /* 'î¡' */
+.icon-print:before { content: '\e84e'; } /* 'î¡Ž' */
+.icon-edit:before { content: '\e84f'; } /* 'î¡' */
+.icon-forward:before { content: '\e850'; } /* 'î¡' */
+.icon-reply:before { content: '\e851'; } /* 'î¡‘' */
+.icon-reply-all:before { content: '\e852'; } /* 'î¡’' */
+.icon-eye:before { content: '\e853'; } /* 'î¡“' */
+.icon-tag:before { content: '\e854'; } /* 'î¡”' */
+.icon-tags:before { content: '\e855'; } /* 'î¡•' */
+.icon-lock-open-alt:before { content: '\e856'; } /* 'î¡–' */
+.icon-lock-open:before { content: '\e857'; } /* 'î¡—' */
+.icon-lock:before { content: '\e858'; } /* '' */
+.icon-home:before { content: '\e859'; } /* 'î¡™' */
+.icon-info:before { content: '\e85a'; } /* 'î¡š' */
+.icon-help:before { content: '\e85b'; } /* 'î¡›' */
+.icon-search:before { content: '\e85c'; } /* '' */
+.icon-flapping:before { content: '\e85d'; } /* 'î¡' */
+.icon-rewind:before { content: '\e85e'; } /* 'î¡ž' */
+.icon-chart-line:before { content: '\e85f'; } /* 'î¡Ÿ' */
+.icon-bell-off:before { content: '\e860'; } /* 'î¡ ' */
+.icon-bell-off-empty:before { content: '\e861'; } /* 'î¡¡' */
+.icon-plug:before { content: '\e862'; } /* 'î¡¢' */
+.icon-eye-off:before { content: '\e863'; } /* 'î¡£' */
+.icon-arrows-cw:before { content: '\e864'; } /* '' */
+.icon-cw:before { content: '\e865'; } /* 'î¡¥' */
+.icon-host:before { content: '\e866'; } /* '' */
+.icon-thumbs-up:before { content: '\e867'; } /* '' */
+.icon-thumbs-down:before { content: '\e868'; } /* '' */
+.icon-spinner:before { content: '\e869'; } /* 'î¡©' */
+.icon-attach:before { content: '\e86a'; } /* '' */
+.icon-keyboard:before { content: '\e86b'; } /* 'î¡«' */
+.icon-menu:before { content: '\e86c'; } /* '' */
+.icon-wifi:before { content: '\e86d'; } /* 'î¡­' */
+.icon-moon:before { content: '\e86e'; } /* 'î¡®' */
+.icon-chart-pie:before { content: '\e86f'; } /* '' */
+.icon-chart-area:before { content: '\e870'; } /* 'î¡°' */
+.icon-chart-bar:before { content: '\e871'; } /* '' */
+.icon-beaker:before { content: '\e872'; } /* '' */
+.icon-magic:before { content: '\e873'; } /* '' */
+.icon-spin6:before { content: '\e874'; } /* 'î¡´' */
+.icon-down-small:before { content: '\e875'; } /* '' */
+.icon-left-small:before { content: '\e876'; } /* '' */
+.icon-right-small:before { content: '\e877'; } /* 'î¡·' */
+.icon-up-small:before { content: '\e878'; } /* '' */
+.icon-pin:before { content: '\e879'; } /* '' */
+.icon-angle-double-left:before { content: '\e87a'; } /* '' */
+.icon-angle-double-right:before { content: '\e87b'; } /* 'î¡»' */
+.icon-circle:before { content: '\e87c'; } /* '' */
+.icon-info-circled:before { content: '\e87d'; } /* '' */
+.icon-twitter:before { content: '\e87e'; } /* '' */
+.icon-facebook-squared:before { content: '\e87f'; } /* 'î¡¿' */
+.icon-gplus-squared:before { content: '\e880'; } /* '' */
+.icon-attention-circled:before { content: '\e881'; } /* 'î¢' */
+.icon-check:before { content: '\e883'; } /* '' */
+.icon-reschedule:before { content: '\e884'; } /* '' */
+.icon-warning-empty:before { content: '\e885'; } /* '' */
+.icon-th-list:before { content: '\f009'; } /* '' */
+.icon-th-thumb-empty:before { content: '\f00b'; } /* '' */
+.icon-github-circled:before { content: '\f09b'; } /* 'ï‚›' */
+.icon-angle-double-up:before { content: '\f102'; } /* 'ï„‚' */
+.icon-angle-double-down:before { content: '\f103'; } /* '' */
+.icon-angle-left:before { content: '\f104'; } /* 'ï„„' */
+.icon-angle-right:before { content: '\f105'; } /* 'ï„…' */
+.icon-angle-up:before { content: '\f106'; } /* '' */
+.icon-angle-down:before { content: '\f107'; } /* '' */
+.icon-history:before { content: '\f1da'; } /* '' */
+.icon-binoculars:before { content: '\f1e5'; } /* '' */ \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/css/ifont-ie7-codes.css b/application/fonts/fontello-ifont/css/ifont-ie7-codes.css
new file mode 100644
index 0000000..df7974b
--- /dev/null
+++ b/application/fonts/fontello-ifont/css/ifont-ie7-codes.css
@@ -0,0 +1,145 @@
+
+.icon-dashboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
+.icon-user { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
+.icon-users { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
+.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
+.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
+.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
+.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
+.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
+.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
+.icon-git { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
+.icon-cubes { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
+.icon-database { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
+.icon-gauge { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
+.icon-sitemap { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
+.icon-sort-name-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
+.icon-sort-name-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
+.icon-megaphone { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
+.icon-bug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
+.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
+.icon-filter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); }
+.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); }
+.icon-book { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }
+.icon-paste { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }
+.icon-scissors { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }
+.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
+.icon-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
+.icon-flash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
+.icon-barchart { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }
+.icon-down-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81d;&nbsp;'); }
+.icon-up-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81e;&nbsp;'); }
+.icon-left-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81f;&nbsp;'); }
+.icon-right-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe820;&nbsp;'); }
+.icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe821;&nbsp;'); }
+.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe822;&nbsp;'); }
+.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe823;&nbsp;'); }
+.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe824;&nbsp;'); }
+.icon-up-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe825;&nbsp;'); }
+.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe826;&nbsp;'); }
+.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe827;&nbsp;'); }
+.icon-down-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe828;&nbsp;'); }
+.icon-resize-full-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe829;&nbsp;'); }
+.icon-resize-full { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82a;&nbsp;'); }
+.icon-resize-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82b;&nbsp;'); }
+.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82c;&nbsp;'); }
+.icon-resize-horizontal { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82d;&nbsp;'); }
+.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82e;&nbsp;'); }
+.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82f;&nbsp;'); }
+.icon-block { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe830;&nbsp;'); }
+.icon-zoom-out { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe831;&nbsp;'); }
+.icon-lightbulb { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
+.icon-clock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe833;&nbsp;'); }
+.icon-volume-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
+.icon-volume-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe835;&nbsp;'); }
+.icon-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe836;&nbsp;'); }
+.icon-mute { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe837;&nbsp;'); }
+.icon-mic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe838;&nbsp;'); }
+.icon-endtime { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe839;&nbsp;'); }
+.icon-starttime { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83a;&nbsp;'); }
+.icon-calendar-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83b;&nbsp;'); }
+.icon-calendar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83c;&nbsp;'); }
+.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83d;&nbsp;'); }
+.icon-sliders { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83e;&nbsp;'); }
+.icon-services { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83f;&nbsp;'); }
+.icon-service { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe840;&nbsp;'); }
+.icon-phone { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe841;&nbsp;'); }
+.icon-file-pdf { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe842;&nbsp;'); }
+.icon-file-word { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe843;&nbsp;'); }
+.icon-file-excel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe844;&nbsp;'); }
+.icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe845;&nbsp;'); }
+.icon-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe846;&nbsp;'); }
+.icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe847;&nbsp;'); }
+.icon-comment { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe848;&nbsp;'); }
+.icon-chat { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe849;&nbsp;'); }
+.icon-chat-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84a;&nbsp;'); }
+.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84b;&nbsp;'); }
+.icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84c;&nbsp;'); }
+.icon-attention-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84d;&nbsp;'); }
+.icon-print { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84e;&nbsp;'); }
+.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84f;&nbsp;'); }
+.icon-forward { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe850;&nbsp;'); }
+.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe851;&nbsp;'); }
+.icon-reply-all { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe852;&nbsp;'); }
+.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe853;&nbsp;'); }
+.icon-tag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe854;&nbsp;'); }
+.icon-tags { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe855;&nbsp;'); }
+.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe856;&nbsp;'); }
+.icon-lock-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe857;&nbsp;'); }
+.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe858;&nbsp;'); }
+.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe859;&nbsp;'); }
+.icon-info { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85a;&nbsp;'); }
+.icon-help { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85b;&nbsp;'); }
+.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85c;&nbsp;'); }
+.icon-flapping { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85d;&nbsp;'); }
+.icon-rewind { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85e;&nbsp;'); }
+.icon-chart-line { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85f;&nbsp;'); }
+.icon-bell-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe860;&nbsp;'); }
+.icon-bell-off-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe861;&nbsp;'); }
+.icon-plug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe862;&nbsp;'); }
+.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe863;&nbsp;'); }
+.icon-arrows-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe864;&nbsp;'); }
+.icon-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe865;&nbsp;'); }
+.icon-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&nbsp;'); }
+.icon-thumbs-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe867;&nbsp;'); }
+.icon-thumbs-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe868;&nbsp;'); }
+.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe869;&nbsp;'); }
+.icon-attach { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86a;&nbsp;'); }
+.icon-keyboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86b;&nbsp;'); }
+.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86c;&nbsp;'); }
+.icon-wifi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86d;&nbsp;'); }
+.icon-moon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86e;&nbsp;'); }
+.icon-chart-pie { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86f;&nbsp;'); }
+.icon-chart-area { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe870;&nbsp;'); }
+.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe871;&nbsp;'); }
+.icon-beaker { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe872;&nbsp;'); }
+.icon-magic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe873;&nbsp;'); }
+.icon-spin6 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe874;&nbsp;'); }
+.icon-down-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe875;&nbsp;'); }
+.icon-left-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe876;&nbsp;'); }
+.icon-right-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe877;&nbsp;'); }
+.icon-up-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe878;&nbsp;'); }
+.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe879;&nbsp;'); }
+.icon-angle-double-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87a;&nbsp;'); }
+.icon-angle-double-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87b;&nbsp;'); }
+.icon-circle { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87c;&nbsp;'); }
+.icon-info-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87d;&nbsp;'); }
+.icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87e;&nbsp;'); }
+.icon-facebook-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87f;&nbsp;'); }
+.icon-gplus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe880;&nbsp;'); }
+.icon-attention-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe881;&nbsp;'); }
+.icon-check { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe883;&nbsp;'); }
+.icon-reschedule { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe884;&nbsp;'); }
+.icon-warning-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe885;&nbsp;'); }
+.icon-th-list { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf009;&nbsp;'); }
+.icon-th-thumb-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf00b;&nbsp;'); }
+.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09b;&nbsp;'); }
+.icon-angle-double-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf102;&nbsp;'); }
+.icon-angle-double-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf103;&nbsp;'); }
+.icon-angle-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf104;&nbsp;'); }
+.icon-angle-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf105;&nbsp;'); }
+.icon-angle-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf106;&nbsp;'); }
+.icon-angle-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf107;&nbsp;'); }
+.icon-history { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1da;&nbsp;'); }
+.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); } \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/css/ifont-ie7.css b/application/fonts/fontello-ifont/css/ifont-ie7.css
new file mode 100644
index 0000000..084c292
--- /dev/null
+++ b/application/fonts/fontello-ifont/css/ifont-ie7.css
@@ -0,0 +1,156 @@
+[class^="icon-"], [class*=" icon-"] {
+ font-family: 'ifont';
+ font-style: normal;
+ font-weight: normal;
+
+ /* fix buttons height */
+ line-height: 1em;
+
+ /* you can be more comfortable with increased icons size */
+ /* font-size: 120%; */
+}
+
+.icon-dashboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
+.icon-user { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
+.icon-users { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
+.icon-ok { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
+.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
+.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
+.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
+.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
+.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
+.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
+.icon-git { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
+.icon-cubes { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
+.icon-database { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
+.icon-gauge { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
+.icon-sitemap { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
+.icon-sort-name-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
+.icon-sort-name-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
+.icon-megaphone { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
+.icon-bug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
+.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }
+.icon-filter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe814;&nbsp;'); }
+.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe815;&nbsp;'); }
+.icon-book { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe816;&nbsp;'); }
+.icon-paste { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe817;&nbsp;'); }
+.icon-scissors { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe818;&nbsp;'); }
+.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe819;&nbsp;'); }
+.icon-cloud { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81a;&nbsp;'); }
+.icon-flash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81b;&nbsp;'); }
+.icon-barchart { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81c;&nbsp;'); }
+.icon-down-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81d;&nbsp;'); }
+.icon-up-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81e;&nbsp;'); }
+.icon-left-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe81f;&nbsp;'); }
+.icon-right-dir { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe820;&nbsp;'); }
+.icon-down-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe821;&nbsp;'); }
+.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe822;&nbsp;'); }
+.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe823;&nbsp;'); }
+.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe824;&nbsp;'); }
+.icon-up-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe825;&nbsp;'); }
+.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe826;&nbsp;'); }
+.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe827;&nbsp;'); }
+.icon-down-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe828;&nbsp;'); }
+.icon-resize-full-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe829;&nbsp;'); }
+.icon-resize-full { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82a;&nbsp;'); }
+.icon-resize-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82b;&nbsp;'); }
+.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82c;&nbsp;'); }
+.icon-resize-horizontal { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82d;&nbsp;'); }
+.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82e;&nbsp;'); }
+.icon-zoom-in { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe82f;&nbsp;'); }
+.icon-block { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe830;&nbsp;'); }
+.icon-zoom-out { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe831;&nbsp;'); }
+.icon-lightbulb { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
+.icon-clock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe833;&nbsp;'); }
+.icon-volume-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
+.icon-volume-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe835;&nbsp;'); }
+.icon-volume-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe836;&nbsp;'); }
+.icon-mute { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe837;&nbsp;'); }
+.icon-mic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe838;&nbsp;'); }
+.icon-endtime { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe839;&nbsp;'); }
+.icon-starttime { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83a;&nbsp;'); }
+.icon-calendar-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83b;&nbsp;'); }
+.icon-calendar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83c;&nbsp;'); }
+.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83d;&nbsp;'); }
+.icon-sliders { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83e;&nbsp;'); }
+.icon-services { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83f;&nbsp;'); }
+.icon-service { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe840;&nbsp;'); }
+.icon-phone { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe841;&nbsp;'); }
+.icon-file-pdf { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe842;&nbsp;'); }
+.icon-file-word { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe843;&nbsp;'); }
+.icon-file-excel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe844;&nbsp;'); }
+.icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe845;&nbsp;'); }
+.icon-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe846;&nbsp;'); }
+.icon-comment-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe847;&nbsp;'); }
+.icon-comment { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe848;&nbsp;'); }
+.icon-chat { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe849;&nbsp;'); }
+.icon-chat-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84a;&nbsp;'); }
+.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84b;&nbsp;'); }
+.icon-bell-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84c;&nbsp;'); }
+.icon-attention-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84d;&nbsp;'); }
+.icon-print { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84e;&nbsp;'); }
+.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe84f;&nbsp;'); }
+.icon-forward { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe850;&nbsp;'); }
+.icon-reply { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe851;&nbsp;'); }
+.icon-reply-all { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe852;&nbsp;'); }
+.icon-eye { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe853;&nbsp;'); }
+.icon-tag { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe854;&nbsp;'); }
+.icon-tags { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe855;&nbsp;'); }
+.icon-lock-open-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe856;&nbsp;'); }
+.icon-lock-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe857;&nbsp;'); }
+.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe858;&nbsp;'); }
+.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe859;&nbsp;'); }
+.icon-info { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85a;&nbsp;'); }
+.icon-help { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85b;&nbsp;'); }
+.icon-search { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85c;&nbsp;'); }
+.icon-flapping { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85d;&nbsp;'); }
+.icon-rewind { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85e;&nbsp;'); }
+.icon-chart-line { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe85f;&nbsp;'); }
+.icon-bell-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe860;&nbsp;'); }
+.icon-bell-off-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe861;&nbsp;'); }
+.icon-plug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe862;&nbsp;'); }
+.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe863;&nbsp;'); }
+.icon-arrows-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe864;&nbsp;'); }
+.icon-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe865;&nbsp;'); }
+.icon-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&nbsp;'); }
+.icon-thumbs-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe867;&nbsp;'); }
+.icon-thumbs-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe868;&nbsp;'); }
+.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe869;&nbsp;'); }
+.icon-attach { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86a;&nbsp;'); }
+.icon-keyboard { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86b;&nbsp;'); }
+.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86c;&nbsp;'); }
+.icon-wifi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86d;&nbsp;'); }
+.icon-moon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86e;&nbsp;'); }
+.icon-chart-pie { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe86f;&nbsp;'); }
+.icon-chart-area { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe870;&nbsp;'); }
+.icon-chart-bar { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe871;&nbsp;'); }
+.icon-beaker { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe872;&nbsp;'); }
+.icon-magic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe873;&nbsp;'); }
+.icon-spin6 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe874;&nbsp;'); }
+.icon-down-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe875;&nbsp;'); }
+.icon-left-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe876;&nbsp;'); }
+.icon-right-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe877;&nbsp;'); }
+.icon-up-small { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe878;&nbsp;'); }
+.icon-pin { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe879;&nbsp;'); }
+.icon-angle-double-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87a;&nbsp;'); }
+.icon-angle-double-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87b;&nbsp;'); }
+.icon-circle { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87c;&nbsp;'); }
+.icon-info-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87d;&nbsp;'); }
+.icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87e;&nbsp;'); }
+.icon-facebook-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87f;&nbsp;'); }
+.icon-gplus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe880;&nbsp;'); }
+.icon-attention-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe881;&nbsp;'); }
+.icon-check { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe883;&nbsp;'); }
+.icon-reschedule { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe884;&nbsp;'); }
+.icon-warning-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe885;&nbsp;'); }
+.icon-th-list { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf009;&nbsp;'); }
+.icon-th-thumb-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf00b;&nbsp;'); }
+.icon-github-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09b;&nbsp;'); }
+.icon-angle-double-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf102;&nbsp;'); }
+.icon-angle-double-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf103;&nbsp;'); }
+.icon-angle-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf104;&nbsp;'); }
+.icon-angle-right { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf105;&nbsp;'); }
+.icon-angle-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf106;&nbsp;'); }
+.icon-angle-down { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf107;&nbsp;'); }
+.icon-history { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1da;&nbsp;'); }
+.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); } \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/css/ifont.css b/application/fonts/fontello-ifont/css/ifont.css
new file mode 100644
index 0000000..afabb48
--- /dev/null
+++ b/application/fonts/fontello-ifont/css/ifont.css
@@ -0,0 +1,201 @@
+@font-face {
+ font-family: 'ifont';
+ src: url('../font/ifont.eot?95568481');
+ src: url('../font/ifont.eot?95568481#iefix') format('embedded-opentype'),
+ url('../font/ifont.woff2?95568481') format('woff2'),
+ url('../font/ifont.woff?95568481') format('woff'),
+ url('../font/ifont.ttf?95568481') format('truetype'),
+ url('../font/ifont.svg?95568481#ifont') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
+/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
+/*
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+ @font-face {
+ font-family: 'ifont';
+ src: url('../font/ifont.svg?95568481#ifont') format('svg');
+ }
+}
+*/
+
+ [class^="icon-"]:before, [class*=" icon-"]:before {
+ font-family: "ifont";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+
+ display: inline-block;
+ text-decoration: inherit;
+ width: 1em;
+ margin-right: .2em;
+ text-align: center;
+ /* opacity: .8; */
+
+ /* For safety - reset parent styles, that can break glyph codes*/
+ font-variant: normal;
+ text-transform: none;
+
+ /* fix buttons height, for twitter bootstrap */
+ line-height: 1em;
+
+ /* Animation center compensation - margins should be symmetric */
+ /* remove if not needed */
+ margin-left: .2em;
+
+ /* you can be more comfortable with increased icons size */
+ /* font-size: 120%; */
+
+ /* Font smoothing. That was taken from TWBS */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ /* Uncomment for 3D effect */
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
+}
+
+.icon-dashboard:before { content: '\e800'; } /* 'î €' */
+.icon-user:before { content: '\e801'; } /* 'î ' */
+.icon-users:before { content: '\e802'; } /* 'î ‚' */
+.icon-ok:before { content: '\e803'; } /* 'î ƒ' */
+.icon-cancel:before { content: '\e804'; } /* 'î „' */
+.icon-plus:before { content: '\e805'; } /* 'î …' */
+.icon-minus:before { content: '\e806'; } /* 'î †' */
+.icon-folder-empty:before { content: '\e807'; } /* 'î ‡' */
+.icon-download:before { content: '\e808'; } /* 'î ˆ' */
+.icon-upload:before { content: '\e809'; } /* 'î ‰' */
+.icon-git:before { content: '\e80a'; } /* 'î Š' */
+.icon-cubes:before { content: '\e80b'; } /* 'î ‹' */
+.icon-database:before { content: '\e80c'; } /* '' */
+.icon-gauge:before { content: '\e80d'; } /* 'î ' */
+.icon-sitemap:before { content: '\e80e'; } /* 'î Ž' */
+.icon-sort-name-up:before { content: '\e80f'; } /* 'î ' */
+.icon-sort-name-down:before { content: '\e810'; } /* 'î ' */
+.icon-megaphone:before { content: '\e811'; } /* 'î ‘' */
+.icon-bug:before { content: '\e812'; } /* 'î ’' */
+.icon-tasks:before { content: '\e813'; } /* 'î “' */
+.icon-filter:before { content: '\e814'; } /* 'î ”' */
+.icon-off:before { content: '\e815'; } /* 'î •' */
+.icon-book:before { content: '\e816'; } /* 'î –' */
+.icon-paste:before { content: '\e817'; } /* 'î —' */
+.icon-scissors:before { content: '\e818'; } /* 'î ˜' */
+.icon-globe:before { content: '\e819'; } /* 'î ™' */
+.icon-cloud:before { content: '\e81a'; } /* 'î š' */
+.icon-flash:before { content: '\e81b'; } /* 'î ›' */
+.icon-barchart:before { content: '\e81c'; } /* '' */
+.icon-down-dir:before { content: '\e81d'; } /* 'î ' */
+.icon-up-dir:before { content: '\e81e'; } /* 'î ž' */
+.icon-left-dir:before { content: '\e81f'; } /* 'î Ÿ' */
+.icon-right-dir:before { content: '\e820'; } /* 'î  ' */
+.icon-down-open:before { content: '\e821'; } /* 'î ¡' */
+.icon-right-open:before { content: '\e822'; } /* 'î ¢' */
+.icon-up-open:before { content: '\e823'; } /* 'î £' */
+.icon-left-open:before { content: '\e824'; } /* 'î ¤' */
+.icon-up-big:before { content: '\e825'; } /* 'î ¥' */
+.icon-right-big:before { content: '\e826'; } /* 'î ¦' */
+.icon-left-big:before { content: '\e827'; } /* 'î §' */
+.icon-down-big:before { content: '\e828'; } /* 'î ¨' */
+.icon-resize-full-alt:before { content: '\e829'; } /* 'î ©' */
+.icon-resize-full:before { content: '\e82a'; } /* 'î ª' */
+.icon-resize-small:before { content: '\e82b'; } /* 'î «' */
+.icon-move:before { content: '\e82c'; } /* 'î ¬' */
+.icon-resize-horizontal:before { content: '\e82d'; } /* 'î ­' */
+.icon-resize-vertical:before { content: '\e82e'; } /* 'î ®' */
+.icon-zoom-in:before { content: '\e82f'; } /* 'î ¯' */
+.icon-block:before { content: '\e830'; } /* 'î °' */
+.icon-zoom-out:before { content: '\e831'; } /* 'î ±' */
+.icon-lightbulb:before { content: '\e832'; } /* 'î ²' */
+.icon-clock:before { content: '\e833'; } /* 'î ³' */
+.icon-volume-up:before { content: '\e834'; } /* 'î ´' */
+.icon-volume-down:before { content: '\e835'; } /* 'î µ' */
+.icon-volume-off:before { content: '\e836'; } /* 'î ¶' */
+.icon-mute:before { content: '\e837'; } /* 'î ·' */
+.icon-mic:before { content: '\e838'; } /* 'î ¸' */
+.icon-endtime:before { content: '\e839'; } /* 'î ¹' */
+.icon-starttime:before { content: '\e83a'; } /* 'î º' */
+.icon-calendar-empty:before { content: '\e83b'; } /* 'î »' */
+.icon-calendar:before { content: '\e83c'; } /* 'î ¼' */
+.icon-wrench:before { content: '\e83d'; } /* 'î ½' */
+.icon-sliders:before { content: '\e83e'; } /* 'î ¾' */
+.icon-services:before { content: '\e83f'; } /* 'î ¿' */
+.icon-service:before { content: '\e840'; } /* 'î¡€' */
+.icon-phone:before { content: '\e841'; } /* 'î¡' */
+.icon-file-pdf:before { content: '\e842'; } /* 'î¡‚' */
+.icon-file-word:before { content: '\e843'; } /* '' */
+.icon-file-excel:before { content: '\e844'; } /* 'î¡„' */
+.icon-doc-text:before { content: '\e845'; } /* 'î¡…' */
+.icon-trash:before { content: '\e846'; } /* '' */
+.icon-comment-empty:before { content: '\e847'; } /* '' */
+.icon-comment:before { content: '\e848'; } /* '' */
+.icon-chat:before { content: '\e849'; } /* '' */
+.icon-chat-empty:before { content: '\e84a'; } /* 'î¡Š' */
+.icon-bell:before { content: '\e84b'; } /* 'î¡‹' */
+.icon-bell-alt:before { content: '\e84c'; } /* '' */
+.icon-attention-alt:before { content: '\e84d'; } /* 'î¡' */
+.icon-print:before { content: '\e84e'; } /* 'î¡Ž' */
+.icon-edit:before { content: '\e84f'; } /* 'î¡' */
+.icon-forward:before { content: '\e850'; } /* 'î¡' */
+.icon-reply:before { content: '\e851'; } /* 'î¡‘' */
+.icon-reply-all:before { content: '\e852'; } /* 'î¡’' */
+.icon-eye:before { content: '\e853'; } /* 'î¡“' */
+.icon-tag:before { content: '\e854'; } /* 'î¡”' */
+.icon-tags:before { content: '\e855'; } /* 'î¡•' */
+.icon-lock-open-alt:before { content: '\e856'; } /* 'î¡–' */
+.icon-lock-open:before { content: '\e857'; } /* 'î¡—' */
+.icon-lock:before { content: '\e858'; } /* '' */
+.icon-home:before { content: '\e859'; } /* 'î¡™' */
+.icon-info:before { content: '\e85a'; } /* 'î¡š' */
+.icon-help:before { content: '\e85b'; } /* 'î¡›' */
+.icon-search:before { content: '\e85c'; } /* '' */
+.icon-flapping:before { content: '\e85d'; } /* 'î¡' */
+.icon-rewind:before { content: '\e85e'; } /* 'î¡ž' */
+.icon-chart-line:before { content: '\e85f'; } /* 'î¡Ÿ' */
+.icon-bell-off:before { content: '\e860'; } /* 'î¡ ' */
+.icon-bell-off-empty:before { content: '\e861'; } /* 'î¡¡' */
+.icon-plug:before { content: '\e862'; } /* 'î¡¢' */
+.icon-eye-off:before { content: '\e863'; } /* 'î¡£' */
+.icon-arrows-cw:before { content: '\e864'; } /* '' */
+.icon-cw:before { content: '\e865'; } /* 'î¡¥' */
+.icon-host:before { content: '\e866'; } /* '' */
+.icon-thumbs-up:before { content: '\e867'; } /* '' */
+.icon-thumbs-down:before { content: '\e868'; } /* '' */
+.icon-spinner:before { content: '\e869'; } /* 'î¡©' */
+.icon-attach:before { content: '\e86a'; } /* '' */
+.icon-keyboard:before { content: '\e86b'; } /* 'î¡«' */
+.icon-menu:before { content: '\e86c'; } /* '' */
+.icon-wifi:before { content: '\e86d'; } /* 'î¡­' */
+.icon-moon:before { content: '\e86e'; } /* 'î¡®' */
+.icon-chart-pie:before { content: '\e86f'; } /* '' */
+.icon-chart-area:before { content: '\e870'; } /* 'î¡°' */
+.icon-chart-bar:before { content: '\e871'; } /* '' */
+.icon-beaker:before { content: '\e872'; } /* '' */
+.icon-magic:before { content: '\e873'; } /* '' */
+.icon-spin6:before { content: '\e874'; } /* 'î¡´' */
+.icon-down-small:before { content: '\e875'; } /* '' */
+.icon-left-small:before { content: '\e876'; } /* '' */
+.icon-right-small:before { content: '\e877'; } /* 'î¡·' */
+.icon-up-small:before { content: '\e878'; } /* '' */
+.icon-pin:before { content: '\e879'; } /* '' */
+.icon-angle-double-left:before { content: '\e87a'; } /* '' */
+.icon-angle-double-right:before { content: '\e87b'; } /* 'î¡»' */
+.icon-circle:before { content: '\e87c'; } /* '' */
+.icon-info-circled:before { content: '\e87d'; } /* '' */
+.icon-twitter:before { content: '\e87e'; } /* '' */
+.icon-facebook-squared:before { content: '\e87f'; } /* 'î¡¿' */
+.icon-gplus-squared:before { content: '\e880'; } /* '' */
+.icon-attention-circled:before { content: '\e881'; } /* 'î¢' */
+.icon-check:before { content: '\e883'; } /* '' */
+.icon-reschedule:before { content: '\e884'; } /* '' */
+.icon-warning-empty:before { content: '\e885'; } /* '' */
+.icon-th-list:before { content: '\f009'; } /* '' */
+.icon-th-thumb-empty:before { content: '\f00b'; } /* '' */
+.icon-github-circled:before { content: '\f09b'; } /* 'ï‚›' */
+.icon-angle-double-up:before { content: '\f102'; } /* 'ï„‚' */
+.icon-angle-double-down:before { content: '\f103'; } /* '' */
+.icon-angle-left:before { content: '\f104'; } /* 'ï„„' */
+.icon-angle-right:before { content: '\f105'; } /* 'ï„…' */
+.icon-angle-up:before { content: '\f106'; } /* '' */
+.icon-angle-down:before { content: '\f107'; } /* '' */
+.icon-history:before { content: '\f1da'; } /* '' */
+.icon-binoculars:before { content: '\f1e5'; } /* '' */ \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/demo.html b/application/fonts/fontello-ifont/demo.html
new file mode 100644
index 0000000..c3a67d4
--- /dev/null
+++ b/application/fonts/fontello-ifont/demo.html
@@ -0,0 +1,519 @@
+<!DOCTYPE html>
+<html>
+ <head><!--[if lt IE 9]><script language="javascript" type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
+ <meta charset="UTF-8"><style>/*
+ * Bootstrap v2.2.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+.clearfix {
+ *zoom: 1;
+}
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ content: "";
+ line-height: 0;
+}
+.clearfix:after {
+ clear: both;
+}
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+a:hover,
+a:active {
+ outline: 0;
+}
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+button,
+input {
+ *overflow: visible;
+ line-height: normal;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+body {
+ margin: 0;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
+ color: #333;
+ background-color: #fff;
+}
+a {
+ color: #08c;
+ text-decoration: none;
+}
+a:hover {
+ color: #005580;
+ text-decoration: underline;
+}
+.row {
+ margin-left: -20px;
+ *zoom: 1;
+}
+.row:before,
+.row:after {
+ display: table;
+ content: "";
+ line-height: 0;
+}
+.row:after {
+ clear: both;
+}
+[class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+}
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ width: 940px;
+}
+.span12 {
+ width: 940px;
+}
+.span11 {
+ width: 860px;
+}
+.span10 {
+ width: 780px;
+}
+.span9 {
+ width: 700px;
+}
+.span8 {
+ width: 620px;
+}
+.span7 {
+ width: 540px;
+}
+.span6 {
+ width: 460px;
+}
+.span5 {
+ width: 380px;
+}
+.span4 {
+ width: 300px;
+}
+.span3 {
+ width: 220px;
+}
+.span2 {
+ width: 140px;
+}
+.span1 {
+ width: 60px;
+}
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
+.container {
+ margin-right: auto;
+ margin-left: auto;
+ *zoom: 1;
+}
+.container:before,
+.container:after {
+ display: table;
+ content: "";
+ line-height: 0;
+}
+.container:after {
+ clear: both;
+}
+p {
+ margin: 0 0 10px;
+}
+.lead {
+ margin-bottom: 20px;
+ font-size: 21px;
+ font-weight: 200;
+ line-height: 30px;
+}
+small {
+ font-size: 85%;
+}
+h1 {
+ margin: 10px 0;
+ font-family: inherit;
+ font-weight: bold;
+ line-height: 20px;
+ color: inherit;
+ text-rendering: optimizelegibility;
+}
+h1 small {
+ font-weight: normal;
+ line-height: 1;
+ color: #999;
+}
+h1 {
+ line-height: 40px;
+}
+h1 {
+ font-size: 38.5px;
+}
+h1 small {
+ font-size: 24.5px;
+}
+body {
+ margin-top: 90px;
+}
+.header {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ margin-left: -480px;
+ background-color: #fff;
+ border-bottom: 1px solid #ddd;
+ padding-top: 10px;
+ z-index: 10;
+}
+.footer {
+ color: #ddd;
+ font-size: 12px;
+ text-align: center;
+ margin-top: 20px;
+}
+.footer a {
+ color: #ccc;
+ text-decoration: underline;
+}
+.the-icons {
+ font-size: 14px;
+ line-height: 24px;
+}
+.switch {
+ position: absolute;
+ right: 0;
+ bottom: 10px;
+ color: #666;
+}
+.switch input {
+ margin-right: 0.3em;
+}
+.codesOn .i-name {
+ display: none;
+}
+.codesOn .i-code {
+ display: inline;
+}
+.i-code {
+ display: none;
+}
+@font-face {
+ font-family: 'ifont';
+ src: url('./font/ifont.eot?64148803');
+ src: url('./font/ifont.eot?64148803#iefix') format('embedded-opentype'),
+ url('./font/ifont.woff?64148803') format('woff'),
+ url('./font/ifont.ttf?64148803') format('truetype'),
+ url('./font/ifont.svg?64148803#ifont') format('svg');
+ font-weight: normal;
+ font-style: normal;
+ }
+
+
+ .demo-icon
+ {
+ font-family: "ifont";
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+
+ display: inline-block;
+ text-decoration: inherit;
+ width: 1em;
+ margin-right: .2em;
+ text-align: center;
+ /* opacity: .8; */
+
+ /* For safety - reset parent styles, that can break glyph codes*/
+ font-variant: normal;
+ text-transform: none;
+
+ /* fix buttons height, for twitter bootstrap */
+ line-height: 1em;
+
+ /* Animation center compensation - margins should be symmetric */
+ /* remove if not needed */
+ margin-left: .2em;
+
+ /* You can be more comfortable with increased icons size */
+ /* font-size: 120%; */
+
+ /* Font smoothing. That was taken from TWBS */
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ /* Uncomment for 3D effect */
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
+ }
+ </style>
+ <link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><link rel="stylesheet" href="css/" + font.fontname + "-ie7.css"><![endif]-->
+ <script>
+ function toggleCodes(on) {
+ var obj = document.getElementById('icons');
+
+ if (on) {
+ obj.className += ' codesOn';
+ } else {
+ obj.className = obj.className.replace(' codesOn', '');
+ }
+ }
+
+ </script>
+ </head>
+ <body>
+ <div class="container header">
+ <h1>ifont <small>font demo</small></h1>
+ <label class="switch">
+ <input type="checkbox" onclick="toggleCodes(this.checked)">show codes
+ </label>
+ </div>
+ <div class="container" id="icons">
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe800"><i class="demo-icon icon-dashboard">&#xe800;</i> <span class="i-name">icon-dashboard</span><span class="i-code">0xe800</span></div>
+ <div class="the-icons span3" title="Code: 0xe801"><i class="demo-icon icon-user">&#xe801;</i> <span class="i-name">icon-user</span><span class="i-code">0xe801</span></div>
+ <div class="the-icons span3" title="Code: 0xe802"><i class="demo-icon icon-users">&#xe802;</i> <span class="i-name">icon-users</span><span class="i-code">0xe802</span></div>
+ <div class="the-icons span3" title="Code: 0xe803"><i class="demo-icon icon-ok">&#xe803;</i> <span class="i-name">icon-ok</span><span class="i-code">0xe803</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe804"><i class="demo-icon icon-cancel">&#xe804;</i> <span class="i-name">icon-cancel</span><span class="i-code">0xe804</span></div>
+ <div class="the-icons span3" title="Code: 0xe805"><i class="demo-icon icon-plus">&#xe805;</i> <span class="i-name">icon-plus</span><span class="i-code">0xe805</span></div>
+ <div class="the-icons span3" title="Code: 0xe806"><i class="demo-icon icon-minus">&#xe806;</i> <span class="i-name">icon-minus</span><span class="i-code">0xe806</span></div>
+ <div class="the-icons span3" title="Code: 0xe807"><i class="demo-icon icon-folder-empty">&#xe807;</i> <span class="i-name">icon-folder-empty</span><span class="i-code">0xe807</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe808"><i class="demo-icon icon-download">&#xe808;</i> <span class="i-name">icon-download</span><span class="i-code">0xe808</span></div>
+ <div class="the-icons span3" title="Code: 0xe809"><i class="demo-icon icon-upload">&#xe809;</i> <span class="i-name">icon-upload</span><span class="i-code">0xe809</span></div>
+ <div class="the-icons span3" title="Code: 0xe80a"><i class="demo-icon icon-git">&#xe80a;</i> <span class="i-name">icon-git</span><span class="i-code">0xe80a</span></div>
+ <div class="the-icons span3" title="Code: 0xe80b"><i class="demo-icon icon-cubes">&#xe80b;</i> <span class="i-name">icon-cubes</span><span class="i-code">0xe80b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe80c"><i class="demo-icon icon-database">&#xe80c;</i> <span class="i-name">icon-database</span><span class="i-code">0xe80c</span></div>
+ <div class="the-icons span3" title="Code: 0xe80d"><i class="demo-icon icon-gauge">&#xe80d;</i> <span class="i-name">icon-gauge</span><span class="i-code">0xe80d</span></div>
+ <div class="the-icons span3" title="Code: 0xe80e"><i class="demo-icon icon-sitemap">&#xe80e;</i> <span class="i-name">icon-sitemap</span><span class="i-code">0xe80e</span></div>
+ <div class="the-icons span3" title="Code: 0xe80f"><i class="demo-icon icon-sort-name-up">&#xe80f;</i> <span class="i-name">icon-sort-name-up</span><span class="i-code">0xe80f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-sort-name-down">&#xe810;</i> <span class="i-name">icon-sort-name-down</span><span class="i-code">0xe810</span></div>
+ <div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-megaphone">&#xe811;</i> <span class="i-name">icon-megaphone</span><span class="i-code">0xe811</span></div>
+ <div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-bug">&#xe812;</i> <span class="i-name">icon-bug</span><span class="i-code">0xe812</span></div>
+ <div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-tasks">&#xe813;</i> <span class="i-name">icon-tasks</span><span class="i-code">0xe813</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe814"><i class="demo-icon icon-filter">&#xe814;</i> <span class="i-name">icon-filter</span><span class="i-code">0xe814</span></div>
+ <div class="the-icons span3" title="Code: 0xe815"><i class="demo-icon icon-off">&#xe815;</i> <span class="i-name">icon-off</span><span class="i-code">0xe815</span></div>
+ <div class="the-icons span3" title="Code: 0xe816"><i class="demo-icon icon-book">&#xe816;</i> <span class="i-name">icon-book</span><span class="i-code">0xe816</span></div>
+ <div class="the-icons span3" title="Code: 0xe817"><i class="demo-icon icon-paste">&#xe817;</i> <span class="i-name">icon-paste</span><span class="i-code">0xe817</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe818"><i class="demo-icon icon-scissors">&#xe818;</i> <span class="i-name">icon-scissors</span><span class="i-code">0xe818</span></div>
+ <div class="the-icons span3" title="Code: 0xe819"><i class="demo-icon icon-globe">&#xe819;</i> <span class="i-name">icon-globe</span><span class="i-code">0xe819</span></div>
+ <div class="the-icons span3" title="Code: 0xe81a"><i class="demo-icon icon-cloud">&#xe81a;</i> <span class="i-name">icon-cloud</span><span class="i-code">0xe81a</span></div>
+ <div class="the-icons span3" title="Code: 0xe81b"><i class="demo-icon icon-flash">&#xe81b;</i> <span class="i-name">icon-flash</span><span class="i-code">0xe81b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe81c"><i class="demo-icon icon-barchart">&#xe81c;</i> <span class="i-name">icon-barchart</span><span class="i-code">0xe81c</span></div>
+ <div class="the-icons span3" title="Code: 0xe81d"><i class="demo-icon icon-down-dir">&#xe81d;</i> <span class="i-name">icon-down-dir</span><span class="i-code">0xe81d</span></div>
+ <div class="the-icons span3" title="Code: 0xe81e"><i class="demo-icon icon-up-dir">&#xe81e;</i> <span class="i-name">icon-up-dir</span><span class="i-code">0xe81e</span></div>
+ <div class="the-icons span3" title="Code: 0xe81f"><i class="demo-icon icon-left-dir">&#xe81f;</i> <span class="i-name">icon-left-dir</span><span class="i-code">0xe81f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe820"><i class="demo-icon icon-right-dir">&#xe820;</i> <span class="i-name">icon-right-dir</span><span class="i-code">0xe820</span></div>
+ <div class="the-icons span3" title="Code: 0xe821"><i class="demo-icon icon-down-open">&#xe821;</i> <span class="i-name">icon-down-open</span><span class="i-code">0xe821</span></div>
+ <div class="the-icons span3" title="Code: 0xe822"><i class="demo-icon icon-right-open">&#xe822;</i> <span class="i-name">icon-right-open</span><span class="i-code">0xe822</span></div>
+ <div class="the-icons span3" title="Code: 0xe823"><i class="demo-icon icon-up-open">&#xe823;</i> <span class="i-name">icon-up-open</span><span class="i-code">0xe823</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe824"><i class="demo-icon icon-left-open">&#xe824;</i> <span class="i-name">icon-left-open</span><span class="i-code">0xe824</span></div>
+ <div class="the-icons span3" title="Code: 0xe825"><i class="demo-icon icon-up-big">&#xe825;</i> <span class="i-name">icon-up-big</span><span class="i-code">0xe825</span></div>
+ <div class="the-icons span3" title="Code: 0xe826"><i class="demo-icon icon-right-big">&#xe826;</i> <span class="i-name">icon-right-big</span><span class="i-code">0xe826</span></div>
+ <div class="the-icons span3" title="Code: 0xe827"><i class="demo-icon icon-left-big">&#xe827;</i> <span class="i-name">icon-left-big</span><span class="i-code">0xe827</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe828"><i class="demo-icon icon-down-big">&#xe828;</i> <span class="i-name">icon-down-big</span><span class="i-code">0xe828</span></div>
+ <div class="the-icons span3" title="Code: 0xe829"><i class="demo-icon icon-resize-full-alt">&#xe829;</i> <span class="i-name">icon-resize-full-alt</span><span class="i-code">0xe829</span></div>
+ <div class="the-icons span3" title="Code: 0xe82a"><i class="demo-icon icon-resize-full">&#xe82a;</i> <span class="i-name">icon-resize-full</span><span class="i-code">0xe82a</span></div>
+ <div class="the-icons span3" title="Code: 0xe82b"><i class="demo-icon icon-resize-small">&#xe82b;</i> <span class="i-name">icon-resize-small</span><span class="i-code">0xe82b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe82c"><i class="demo-icon icon-move">&#xe82c;</i> <span class="i-name">icon-move</span><span class="i-code">0xe82c</span></div>
+ <div class="the-icons span3" title="Code: 0xe82d"><i class="demo-icon icon-resize-horizontal">&#xe82d;</i> <span class="i-name">icon-resize-horizontal</span><span class="i-code">0xe82d</span></div>
+ <div class="the-icons span3" title="Code: 0xe82e"><i class="demo-icon icon-resize-vertical">&#xe82e;</i> <span class="i-name">icon-resize-vertical</span><span class="i-code">0xe82e</span></div>
+ <div class="the-icons span3" title="Code: 0xe82f"><i class="demo-icon icon-zoom-in">&#xe82f;</i> <span class="i-name">icon-zoom-in</span><span class="i-code">0xe82f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe830"><i class="demo-icon icon-block">&#xe830;</i> <span class="i-name">icon-block</span><span class="i-code">0xe830</span></div>
+ <div class="the-icons span3" title="Code: 0xe831"><i class="demo-icon icon-zoom-out">&#xe831;</i> <span class="i-name">icon-zoom-out</span><span class="i-code">0xe831</span></div>
+ <div class="the-icons span3" title="Code: 0xe832"><i class="demo-icon icon-lightbulb">&#xe832;</i> <span class="i-name">icon-lightbulb</span><span class="i-code">0xe832</span></div>
+ <div class="the-icons span3" title="Code: 0xe833"><i class="demo-icon icon-clock">&#xe833;</i> <span class="i-name">icon-clock</span><span class="i-code">0xe833</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe834"><i class="demo-icon icon-volume-up">&#xe834;</i> <span class="i-name">icon-volume-up</span><span class="i-code">0xe834</span></div>
+ <div class="the-icons span3" title="Code: 0xe835"><i class="demo-icon icon-volume-down">&#xe835;</i> <span class="i-name">icon-volume-down</span><span class="i-code">0xe835</span></div>
+ <div class="the-icons span3" title="Code: 0xe836"><i class="demo-icon icon-volume-off">&#xe836;</i> <span class="i-name">icon-volume-off</span><span class="i-code">0xe836</span></div>
+ <div class="the-icons span3" title="Code: 0xe837"><i class="demo-icon icon-mute">&#xe837;</i> <span class="i-name">icon-mute</span><span class="i-code">0xe837</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe838"><i class="demo-icon icon-mic">&#xe838;</i> <span class="i-name">icon-mic</span><span class="i-code">0xe838</span></div>
+ <div class="the-icons span3" title="Code: 0xe839"><i class="demo-icon icon-endtime">&#xe839;</i> <span class="i-name">icon-endtime</span><span class="i-code">0xe839</span></div>
+ <div class="the-icons span3" title="Code: 0xe83a"><i class="demo-icon icon-starttime">&#xe83a;</i> <span class="i-name">icon-starttime</span><span class="i-code">0xe83a</span></div>
+ <div class="the-icons span3" title="Code: 0xe83b"><i class="demo-icon icon-calendar-empty">&#xe83b;</i> <span class="i-name">icon-calendar-empty</span><span class="i-code">0xe83b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe83c"><i class="demo-icon icon-calendar">&#xe83c;</i> <span class="i-name">icon-calendar</span><span class="i-code">0xe83c</span></div>
+ <div class="the-icons span3" title="Code: 0xe83d"><i class="demo-icon icon-wrench">&#xe83d;</i> <span class="i-name">icon-wrench</span><span class="i-code">0xe83d</span></div>
+ <div class="the-icons span3" title="Code: 0xe83e"><i class="demo-icon icon-sliders">&#xe83e;</i> <span class="i-name">icon-sliders</span><span class="i-code">0xe83e</span></div>
+ <div class="the-icons span3" title="Code: 0xe83f"><i class="demo-icon icon-services">&#xe83f;</i> <span class="i-name">icon-services</span><span class="i-code">0xe83f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe840"><i class="demo-icon icon-service">&#xe840;</i> <span class="i-name">icon-service</span><span class="i-code">0xe840</span></div>
+ <div class="the-icons span3" title="Code: 0xe841"><i class="demo-icon icon-phone">&#xe841;</i> <span class="i-name">icon-phone</span><span class="i-code">0xe841</span></div>
+ <div class="the-icons span3" title="Code: 0xe842"><i class="demo-icon icon-file-pdf">&#xe842;</i> <span class="i-name">icon-file-pdf</span><span class="i-code">0xe842</span></div>
+ <div class="the-icons span3" title="Code: 0xe843"><i class="demo-icon icon-file-word">&#xe843;</i> <span class="i-name">icon-file-word</span><span class="i-code">0xe843</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe844"><i class="demo-icon icon-file-excel">&#xe844;</i> <span class="i-name">icon-file-excel</span><span class="i-code">0xe844</span></div>
+ <div class="the-icons span3" title="Code: 0xe845"><i class="demo-icon icon-doc-text">&#xe845;</i> <span class="i-name">icon-doc-text</span><span class="i-code">0xe845</span></div>
+ <div class="the-icons span3" title="Code: 0xe846"><i class="demo-icon icon-trash">&#xe846;</i> <span class="i-name">icon-trash</span><span class="i-code">0xe846</span></div>
+ <div class="the-icons span3" title="Code: 0xe847"><i class="demo-icon icon-comment-empty">&#xe847;</i> <span class="i-name">icon-comment-empty</span><span class="i-code">0xe847</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe848"><i class="demo-icon icon-comment">&#xe848;</i> <span class="i-name">icon-comment</span><span class="i-code">0xe848</span></div>
+ <div class="the-icons span3" title="Code: 0xe849"><i class="demo-icon icon-chat">&#xe849;</i> <span class="i-name">icon-chat</span><span class="i-code">0xe849</span></div>
+ <div class="the-icons span3" title="Code: 0xe84a"><i class="demo-icon icon-chat-empty">&#xe84a;</i> <span class="i-name">icon-chat-empty</span><span class="i-code">0xe84a</span></div>
+ <div class="the-icons span3" title="Code: 0xe84b"><i class="demo-icon icon-bell">&#xe84b;</i> <span class="i-name">icon-bell</span><span class="i-code">0xe84b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe84c"><i class="demo-icon icon-bell-alt">&#xe84c;</i> <span class="i-name">icon-bell-alt</span><span class="i-code">0xe84c</span></div>
+ <div class="the-icons span3" title="Code: 0xe84d"><i class="demo-icon icon-attention-alt">&#xe84d;</i> <span class="i-name">icon-attention-alt</span><span class="i-code">0xe84d</span></div>
+ <div class="the-icons span3" title="Code: 0xe84e"><i class="demo-icon icon-print">&#xe84e;</i> <span class="i-name">icon-print</span><span class="i-code">0xe84e</span></div>
+ <div class="the-icons span3" title="Code: 0xe84f"><i class="demo-icon icon-edit">&#xe84f;</i> <span class="i-name">icon-edit</span><span class="i-code">0xe84f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe850"><i class="demo-icon icon-forward">&#xe850;</i> <span class="i-name">icon-forward</span><span class="i-code">0xe850</span></div>
+ <div class="the-icons span3" title="Code: 0xe851"><i class="demo-icon icon-reply">&#xe851;</i> <span class="i-name">icon-reply</span><span class="i-code">0xe851</span></div>
+ <div class="the-icons span3" title="Code: 0xe852"><i class="demo-icon icon-reply-all">&#xe852;</i> <span class="i-name">icon-reply-all</span><span class="i-code">0xe852</span></div>
+ <div class="the-icons span3" title="Code: 0xe853"><i class="demo-icon icon-eye">&#xe853;</i> <span class="i-name">icon-eye</span><span class="i-code">0xe853</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe854"><i class="demo-icon icon-tag">&#xe854;</i> <span class="i-name">icon-tag</span><span class="i-code">0xe854</span></div>
+ <div class="the-icons span3" title="Code: 0xe855"><i class="demo-icon icon-tags">&#xe855;</i> <span class="i-name">icon-tags</span><span class="i-code">0xe855</span></div>
+ <div class="the-icons span3" title="Code: 0xe856"><i class="demo-icon icon-lock-open-alt">&#xe856;</i> <span class="i-name">icon-lock-open-alt</span><span class="i-code">0xe856</span></div>
+ <div class="the-icons span3" title="Code: 0xe857"><i class="demo-icon icon-lock-open">&#xe857;</i> <span class="i-name">icon-lock-open</span><span class="i-code">0xe857</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe858"><i class="demo-icon icon-lock">&#xe858;</i> <span class="i-name">icon-lock</span><span class="i-code">0xe858</span></div>
+ <div class="the-icons span3" title="Code: 0xe859"><i class="demo-icon icon-home">&#xe859;</i> <span class="i-name">icon-home</span><span class="i-code">0xe859</span></div>
+ <div class="the-icons span3" title="Code: 0xe85a"><i class="demo-icon icon-info">&#xe85a;</i> <span class="i-name">icon-info</span><span class="i-code">0xe85a</span></div>
+ <div class="the-icons span3" title="Code: 0xe85b"><i class="demo-icon icon-help">&#xe85b;</i> <span class="i-name">icon-help</span><span class="i-code">0xe85b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe85c"><i class="demo-icon icon-search">&#xe85c;</i> <span class="i-name">icon-search</span><span class="i-code">0xe85c</span></div>
+ <div class="the-icons span3" title="Code: 0xe85d"><i class="demo-icon icon-flapping">&#xe85d;</i> <span class="i-name">icon-flapping</span><span class="i-code">0xe85d</span></div>
+ <div class="the-icons span3" title="Code: 0xe85e"><i class="demo-icon icon-rewind">&#xe85e;</i> <span class="i-name">icon-rewind</span><span class="i-code">0xe85e</span></div>
+ <div class="the-icons span3" title="Code: 0xe85f"><i class="demo-icon icon-chart-line">&#xe85f;</i> <span class="i-name">icon-chart-line</span><span class="i-code">0xe85f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe860"><i class="demo-icon icon-bell-off">&#xe860;</i> <span class="i-name">icon-bell-off</span><span class="i-code">0xe860</span></div>
+ <div class="the-icons span3" title="Code: 0xe861"><i class="demo-icon icon-bell-off-empty">&#xe861;</i> <span class="i-name">icon-bell-off-empty</span><span class="i-code">0xe861</span></div>
+ <div class="the-icons span3" title="Code: 0xe862"><i class="demo-icon icon-plug">&#xe862;</i> <span class="i-name">icon-plug</span><span class="i-code">0xe862</span></div>
+ <div class="the-icons span3" title="Code: 0xe863"><i class="demo-icon icon-eye-off">&#xe863;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe863</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe864"><i class="demo-icon icon-arrows-cw">&#xe864;</i> <span class="i-name">icon-arrows-cw</span><span class="i-code">0xe864</span></div>
+ <div class="the-icons span3" title="Code: 0xe865"><i class="demo-icon icon-cw">&#xe865;</i> <span class="i-name">icon-cw</span><span class="i-code">0xe865</span></div>
+ <div class="the-icons span3" title="Code: 0xe866"><i class="demo-icon icon-host">&#xe866;</i> <span class="i-name">icon-host</span><span class="i-code">0xe866</span></div>
+ <div class="the-icons span3" title="Code: 0xe867"><i class="demo-icon icon-thumbs-up">&#xe867;</i> <span class="i-name">icon-thumbs-up</span><span class="i-code">0xe867</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe868"><i class="demo-icon icon-thumbs-down">&#xe868;</i> <span class="i-name">icon-thumbs-down</span><span class="i-code">0xe868</span></div>
+ <div class="the-icons span3" title="Code: 0xe869"><i class="demo-icon icon-spinner">&#xe869;</i> <span class="i-name">icon-spinner</span><span class="i-code">0xe869</span></div>
+ <div class="the-icons span3" title="Code: 0xe86a"><i class="demo-icon icon-attach">&#xe86a;</i> <span class="i-name">icon-attach</span><span class="i-code">0xe86a</span></div>
+ <div class="the-icons span3" title="Code: 0xe86b"><i class="demo-icon icon-keyboard">&#xe86b;</i> <span class="i-name">icon-keyboard</span><span class="i-code">0xe86b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe86c"><i class="demo-icon icon-menu">&#xe86c;</i> <span class="i-name">icon-menu</span><span class="i-code">0xe86c</span></div>
+ <div class="the-icons span3" title="Code: 0xe86d"><i class="demo-icon icon-wifi">&#xe86d;</i> <span class="i-name">icon-wifi</span><span class="i-code">0xe86d</span></div>
+ <div class="the-icons span3" title="Code: 0xe86e"><i class="demo-icon icon-moon">&#xe86e;</i> <span class="i-name">icon-moon</span><span class="i-code">0xe86e</span></div>
+ <div class="the-icons span3" title="Code: 0xe86f"><i class="demo-icon icon-chart-pie">&#xe86f;</i> <span class="i-name">icon-chart-pie</span><span class="i-code">0xe86f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe870"><i class="demo-icon icon-chart-area">&#xe870;</i> <span class="i-name">icon-chart-area</span><span class="i-code">0xe870</span></div>
+ <div class="the-icons span3" title="Code: 0xe871"><i class="demo-icon icon-chart-bar">&#xe871;</i> <span class="i-name">icon-chart-bar</span><span class="i-code">0xe871</span></div>
+ <div class="the-icons span3" title="Code: 0xe872"><i class="demo-icon icon-beaker">&#xe872;</i> <span class="i-name">icon-beaker</span><span class="i-code">0xe872</span></div>
+ <div class="the-icons span3" title="Code: 0xe873"><i class="demo-icon icon-magic">&#xe873;</i> <span class="i-name">icon-magic</span><span class="i-code">0xe873</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe874"><i class="demo-icon icon-spin6 animate-spin">&#xe874;</i> <span class="i-name">icon-spin6</span><span class="i-code">0xe874</span></div>
+ <div class="the-icons span3" title="Code: 0xe875"><i class="demo-icon icon-down-small">&#xe875;</i> <span class="i-name">icon-down-small</span><span class="i-code">0xe875</span></div>
+ <div class="the-icons span3" title="Code: 0xe876"><i class="demo-icon icon-left-small">&#xe876;</i> <span class="i-name">icon-left-small</span><span class="i-code">0xe876</span></div>
+ <div class="the-icons span3" title="Code: 0xe877"><i class="demo-icon icon-right-small">&#xe877;</i> <span class="i-name">icon-right-small</span><span class="i-code">0xe877</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe878"><i class="demo-icon icon-up-small">&#xe878;</i> <span class="i-name">icon-up-small</span><span class="i-code">0xe878</span></div>
+ <div class="the-icons span3" title="Code: 0xe879"><i class="demo-icon icon-pin">&#xe879;</i> <span class="i-name">icon-pin</span><span class="i-code">0xe879</span></div>
+ <div class="the-icons span3" title="Code: 0xe87a"><i class="demo-icon icon-angle-double-left">&#xe87a;</i> <span class="i-name">icon-angle-double-left</span><span class="i-code">0xe87a</span></div>
+ <div class="the-icons span3" title="Code: 0xe87b"><i class="demo-icon icon-angle-double-right">&#xe87b;</i> <span class="i-name">icon-angle-double-right</span><span class="i-code">0xe87b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe87c"><i class="demo-icon icon-circle">&#xe87c;</i> <span class="i-name">icon-circle</span><span class="i-code">0xe87c</span></div>
+ <div class="the-icons span3" title="Code: 0xe87d"><i class="demo-icon icon-info-circled">&#xe87d;</i> <span class="i-name">icon-info-circled</span><span class="i-code">0xe87d</span></div>
+ <div class="the-icons span3" title="Code: 0xe87e"><i class="demo-icon icon-twitter">&#xe87e;</i> <span class="i-name">icon-twitter</span><span class="i-code">0xe87e</span></div>
+ <div class="the-icons span3" title="Code: 0xe87f"><i class="demo-icon icon-facebook-squared">&#xe87f;</i> <span class="i-name">icon-facebook-squared</span><span class="i-code">0xe87f</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe880"><i class="demo-icon icon-gplus-squared">&#xe880;</i> <span class="i-name">icon-gplus-squared</span><span class="i-code">0xe880</span></div>
+ <div class="the-icons span3" title="Code: 0xe881"><i class="demo-icon icon-attention-circled">&#xe881;</i> <span class="i-name">icon-attention-circled</span><span class="i-code">0xe881</span></div>
+ <div class="the-icons span3" title="Code: 0xe883"><i class="demo-icon icon-check">&#xe883;</i> <span class="i-name">icon-check</span><span class="i-code">0xe883</span></div>
+ <div class="the-icons span3" title="Code: 0xe884"><i class="demo-icon icon-reschedule">&#xe884;</i> <span class="i-name">icon-reschedule</span><span class="i-code">0xe884</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xe885"><i class="demo-icon icon-warning-empty">&#xe885;</i> <span class="i-name">icon-warning-empty</span><span class="i-code">0xe885</span></div>
+ <div class="the-icons span3" title="Code: 0xf009"><i class="demo-icon icon-th-list">&#xf009;</i> <span class="i-name">icon-th-list</span><span class="i-code">0xf009</span></div>
+ <div class="the-icons span3" title="Code: 0xf00b"><i class="demo-icon icon-th-thumb-empty">&#xf00b;</i> <span class="i-name">icon-th-thumb-empty</span><span class="i-code">0xf00b</span></div>
+ <div class="the-icons span3" title="Code: 0xf09b"><i class="demo-icon icon-github-circled">&#xf09b;</i> <span class="i-name">icon-github-circled</span><span class="i-code">0xf09b</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xf102"><i class="demo-icon icon-angle-double-up">&#xf102;</i> <span class="i-name">icon-angle-double-up</span><span class="i-code">0xf102</span></div>
+ <div class="the-icons span3" title="Code: 0xf103"><i class="demo-icon icon-angle-double-down">&#xf103;</i> <span class="i-name">icon-angle-double-down</span><span class="i-code">0xf103</span></div>
+ <div class="the-icons span3" title="Code: 0xf104"><i class="demo-icon icon-angle-left">&#xf104;</i> <span class="i-name">icon-angle-left</span><span class="i-code">0xf104</span></div>
+ <div class="the-icons span3" title="Code: 0xf105"><i class="demo-icon icon-angle-right">&#xf105;</i> <span class="i-name">icon-angle-right</span><span class="i-code">0xf105</span></div>
+ </div>
+ <div class="row">
+ <div class="the-icons span3" title="Code: 0xf106"><i class="demo-icon icon-angle-up">&#xf106;</i> <span class="i-name">icon-angle-up</span><span class="i-code">0xf106</span></div>
+ <div class="the-icons span3" title="Code: 0xf107"><i class="demo-icon icon-angle-down">&#xf107;</i> <span class="i-name">icon-angle-down</span><span class="i-code">0xf107</span></div>
+ <div class="the-icons span3" title="Code: 0xf1da"><i class="demo-icon icon-history">&#xf1da;</i> <span class="i-name">icon-history</span><span class="i-code">0xf1da</span></div>
+ <div class="the-icons span3" title="Code: 0xf1e5"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
+ </div>
+ </div>
+ <div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>
+ </body>
+</html> \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/font/ifont.eot b/application/fonts/fontello-ifont/font/ifont.eot
new file mode 100644
index 0000000..091db2f
--- /dev/null
+++ b/application/fonts/fontello-ifont/font/ifont.eot
Binary files differ
diff --git a/application/fonts/fontello-ifont/font/ifont.svg b/application/fonts/fontello-ifont/font/ifont.svg
new file mode 100644
index 0000000..9257938
--- /dev/null
+++ b/application/fonts/fontello-ifont/font/ifont.svg
@@ -0,0 +1,298 @@
+<?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>Copyright (C) 2020 by original authors @ fontello.com</metadata>
+<defs>
+<font id="ifont" horiz-adv-x="1000" >
+<font-face font-family="ifont" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
+<missing-glyph horiz-adv-x="1000" />
+<glyph glyph-name="dashboard" unicode="&#xe800;" d="M286 154v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m0 285v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m357-285v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m-357 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m357-286v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m357-285v-108q0-22-16-37t-38-16h-178q-22 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m-357 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m357-286v-107q0-22-16-38t-38-15h-178q-22 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m0 286v-107q0-22-16-38t-38-16h-178q-22 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="user" unicode="&#xe801;" d="M714 69q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
+
+<glyph glyph-name="users" unicode="&#xe802;" d="M331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="ok" unicode="&#xe803;" d="M352-10l-334 333 158 160 176-174 400 401 159-160z" horiz-adv-x="928" />
+
+<glyph glyph-name="cancel" unicode="&#xe804;" d="M799 116l-156-157-234 235-235-235-156 157 234 234-234 234 156 157 235-235 234 235 156-157-234-234z" horiz-adv-x="817" />
+
+<glyph glyph-name="plus" unicode="&#xe805;" d="M911 462l0-223-335 0 0-336-223 0 0 336-335 0 0 223 335 0 0 335 223 0 0-335 335 0z" horiz-adv-x="928" />
+
+<glyph glyph-name="minus" unicode="&#xe806;" d="M18 239l0 223 893 0 0-223-893 0z" horiz-adv-x="928" />
+
+<glyph glyph-name="folder-empty" unicode="&#xe807;" d="M464 685l447 0 0-669q0-47-33-80t-79-33l-669 0q-46 0-79 33t-33 80l0 781 446 0 0-112z m-334 0l0-223 669 0 0 112-446 0 0 111-223 0z m669-669l0 335-669 0 0-335 669 0z" horiz-adv-x="928" />
+
+<glyph glyph-name="download" unicode="&#xe808;" d="M714 100q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-39l-250-250q-10-11-25-11t-25 11l-250 250q-17 16-8 39 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="upload" unicode="&#xe809;" d="M714 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="git" unicode="&#xe80a;" d="M332 5q0 56-92 56-88 0-88-58 0-57 96-57 84 0 84 59z m-33 422q0 34-17 56t-49 23q-69 0-69-81 0-75 69-75 66 0 66 77z m150 180v-112q-20-7-44-13 9-24 9-47 0-70-41-120t-110-63q-22-5-33-15t-11-33q0-17 13-28t32-18 44-12 48-15 44-21 32-35 13-55q0-170-203-170-38 0-72 7t-65 23-49 46-18 71q0 92 102 125v3q-38 22-38 70 0 61 35 76v3q-40 13-66 60t-27 93q0 77 53 129t131 51q54 0 100-26 54 0 121 26z m178-491h-124q2 25 2 74v340q0 53-2 72h124q-3-19-3-69v-343q0-49 3-74z m335 124v-110q-40-22-97-22-35 0-60 12t-39 27-22 44-10 51-2 58v196h1v2q-4 0-11 0t-10 1q-12 0-33-3v106h54v42q0 30-4 50h127q-3-23-3-92h95v-106q-8 0-24 1t-24 1h-47v-204q0-73 48-73 34 0 61 19z m-321 528q0-32-22-57t-54-24q-32 0-54 24t-23 57q0 33 22 57t55 25q33 0 54-25t22-57z" horiz-adv-x="1000" />
+
+<glyph glyph-name="cubes" unicode="&#xe80b;" d="M357-61l214 107v176l-214-92v-191z m-36 254l226 96-226 97-225-97z m608-254l214 107v176l-214-92v-191z m-36 254l225 96-225 97-226-97z m-250 163l214 92v149l-214-92v-149z m-36 212l246 105-246 106-246-106z m607-289v-233q0-20-10-37t-29-26l-250-125q-14-8-32-8t-32 8l-250 125q-2 1-4 2-1-1-4-2l-250-125q-14-8-32-8t-31 8l-250 125q-19 9-29 26t-11 37v233q0 21 12 39t32 26l242 104v223q0 22 12 40t31 26l250 107q13 6 28 6t28-6l250-107q20-9 32-26t12-40v-223l242-104q20-8 32-26t11-39z" horiz-adv-x="1285.7" />
+
+<glyph glyph-name="database" unicode="&#xe80c;" d="M429 421q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-19-215 19-156 52-58 71v95q66-47 181-71t248-24z m0-428q132 0 247 24t181 71v-95q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v95q66-47 181-71t248-24z m0 214q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-20-215 20-156 52-58 71v95q66-47 181-71t248-24z m0 643q116 0 214-19t157-52 57-72v-71q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v71q0 39 58 72t156 52 215 19z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="gauge" unicode="&#xe80d;" d="M214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
+
+<glyph glyph-name="sitemap" unicode="&#xe80e;" d="M1000 154v-179q0-22-16-38t-38-16h-178q-22 0-38 16t-16 38v179q0 22 16 38t38 15h53v107h-285v-107h53q23 0 38-15t16-38v-179q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v179q0 22 16 38t38 15h53v107h-285v-107h53q23 0 38-15t16-38v-179q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v179q0 22 16 38t38 15h53v107q0 29 21 51t51 21h285v107h-53q-23 0-38 16t-16 37v179q0 22 16 38t38 16h178q23 0 38-16t16-38v-179q0-22-16-37t-38-16h-53v-107h285q29 0 51-21t21-51v-107h53q23 0 38-15t16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="sort-name-up" unicode="&#xe80f;" d="M665 622h98l-40 122-6 26q-2 9-2 11h-2l-1-11q0 0-2-10t-5-16z m-254-576q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m466-66v-130h-326v50l206 295q7 11 12 16l6 5v1q-1 0-3 0t-5 0q-6-2-16-2h-130v-64h-67v128h317v-50l-206-296q-4-4-12-14l-6-7v-1l8 1q5 2 16 2h139v66h67z m50 501v-60h-161v60h42l-26 80h-136l-26-80h42v-60h-160v60h39l128 369h91l128-369h39z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="sort-name-down" unicode="&#xe810;" d="M665 51h98l-40 122-6 26q-2 9-2 11h-2l-1-11q0-1-2-10t-5-16z m-254-5q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m516-137v-59h-161v59h42l-26 80h-136l-26-80h42v-59h-160v59h39l128 370h91l128-370h39z m-50 643v-131h-326v51l206 295q7 10 12 15l6 5v2q-1 0-3-1t-5 0q-6-2-16-2h-130v-64h-67v128h317v-50l-206-295q-4-5-12-15l-6-5v-2l8 2q5 0 16 0h139v67h67z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="megaphone" unicode="&#xe811;" d="M929 493q29 0 50-21t21-51-21-50-50-21v-214q0-29-22-50t-50-22q-233 194-453 212-32-10-51-36t-17-57 22-51q-11-19-13-37t4-32 19-31 26-28 35-28q-17-32-63-46t-94-7-73 31q-4 13-17 49t-18 53-12 50-9 56 2 55 12 62h-68q-36 0-63 26t-26 63v107q0 37 26 63t63 26h268q243 0 500 215 29 0 50-22t22-50v-214z m-72-337v532q-220-168-428-191v-151q210-23 428-190z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bug" unicode="&#xe812;" d="M911 314q0-14-11-25t-25-10h-125q0-96-37-162l116-117q10-11 10-25t-10-25q-10-11-25-11t-25 11l-111 110q-3-3-8-7t-24-16-36-21-46-16-54-7v500h-71v-500q-29 0-57 7t-49 19-36 22-25 18l-8 8-102-116q-11-12-27-12-13 0-24 9-11 10-11 25t8 26l113 127q-32 63-32 153h-125q-15 0-25 10t-11 25 11 25 25 11h125v164l-97 97q-11 10-11 25t11 25 25 10 25-10l97-97h471l96 97q11 10 25 10t26-10 10-25-10-25l-97-97v-164h125q15 0 25-11t11-25z m-268 322h-357q0 74 52 126t126 52 127-52 52-126z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="tasks" unicode="&#xe813;" d="M571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="filter" unicode="&#xe814;" d="M783 685q9-22-8-39l-275-275v-414q0-23-22-33-7-3-14-3-15 0-25 11l-143 143q-10 11-10 25v271l-275 275q-18 17-8 39 9 22 33 22h714q23 0 33-22z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="off" unicode="&#xe815;" d="M857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 23-111t61-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-23 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="book" unicode="&#xe816;" d="M915 583q22-31 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 64-32t10-72l-153-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t20 23l167 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-1 12t-12 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-7 0-14-5t-10-13z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="paste" unicode="&#xe817;" d="M429-79h500v358h-233q-22 0-37 15t-16 38v232h-214v-643z m142 804v36q0 7-5 12t-12 6h-393q-7 0-13-6t-5-12v-36q0-7 5-13t13-5h393q7 0 12 5t5 13z m143-375h167l-167 167v-167z m286-71v-375q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v89h-303q-23 0-38 16t-16 37v750q0 23 16 38t38 16h607q22 0 38-16t15-38v-183q12-7 20-15l228-228q16-15 27-42t11-49z" horiz-adv-x="1000" />
+
+<glyph glyph-name="scissors" unicode="&#xe818;" d="M536 350q14 0 25-11t10-25-10-25-25-10-25 10-11 25 11 25 25 11z m167-36l283-222q16-11 14-31-3-20-19-28l-72-36q-7-4-16-4-10 0-17 4l-385 216-62-36q-4-3-7-3 8-28 6-54-4-43-31-83t-74-69q-74-47-154-47-76 0-124 44-51 47-44 116 4 42 31 82t73 69q74 47 155 47 46 0 84-18 5 8 13 13l68 40-68 41q-8 5-13 12-38-17-84-17-81 0-155 47-46 30-73 69t-31 82q-3 33 8 63t36 52q47 44 124 44 80 0 154-47 46-29 74-68t31-83q2-27-6-54 3-1 7-3l62-37 385 216q7 5 17 5 9 0 16-4l72-36q16-9 19-28 2-20-14-32z m-380 145q26 24 12 61t-59 65q-52 33-107 33-42 0-63-20-26-24-12-60t59-66q51-33 107-33 41 0 63 20z m-47-415q45 28 59 65t-12 60q-22 20-63 20-56 0-107-33-45-28-59-65t12-60q21-20 63-20 55 0 107 33z m99 342l54-33v7q0 20 18 31l8 4-44 26-15-14q-1-2-5-6t-7-7q-1-1-2-2t-2-1z m125-125l54-18 410 321-71 36-429-240v-64l-89-53 5-5q1-1 4-3 2-2 6-7t6-6l15-15z m393-232l71 35-290 228-99-77q-1-2-7-4z" horiz-adv-x="1000" />
+
+<glyph glyph-name="globe" unicode="&#xe819;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="cloud" unicode="&#xe81a;" d="M1071 207q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 74 40 135t104 91q-1 16-1 24 0 118 84 202t202 84q88 0 159-49t105-129q39 35 93 35 59 0 101-42t42-101q0-42-23-77 72-17 119-75t46-134z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="flash" unicode="&#xe81b;" d="M494 534q10-11 4-24l-302-646q-7-14-23-14-2 0-8 1-9 3-14 11t-3 16l110 451-226-56q-2-1-7-1-10 0-17 7-10 8-7 21l112 461q2 8 9 13t15 5h183q11 0 18-7t7-17q0-4-2-10l-96-258 221 54q5 2 7 2 11 0 19-9z" horiz-adv-x="500" />
+
+<glyph glyph-name="barchart" unicode="&#xe81c;" d="M143 46v-107q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v107q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 72v-179q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v179q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 143v-322q0-8-5-13t-12-5h-108q-7 0-12 5t-5 13v322q0 8 5 13t12 5h108q7 0 12-5t5-13z m215 214v-536q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v536q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 286v-822q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v822q0 8 5 13t13 5h107q8 0 13-5t5-13z" horiz-adv-x="1000" />
+
+<glyph glyph-name="down-dir" unicode="&#xe81d;" d="M571 457q0-14-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 11-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="up-dir" unicode="&#xe81e;" d="M571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="left-dir" unicode="&#xe81f;" d="M357 600v-500q0-14-10-25t-26-11-25 11l-250 250q-10 11-10 25t10 25l250 250q11 11 25 11t26-11 10-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="right-dir" unicode="&#xe820;" d="M321 350q0-14-10-25l-250-250q-11-11-25-11t-25 11-11 25v500q0 15 11 25t25 11 25-11l250-250q10-10 10-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="down-open" unicode="&#xe821;" d="M939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+
+<glyph glyph-name="right-open" unicode="&#xe822;" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
+
+<glyph glyph-name="up-open" unicode="&#xe823;" d="M939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+
+<glyph glyph-name="left-open" unicode="&#xe824;" d="M654 682l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
+
+<glyph glyph-name="up-big" unicode="&#xe825;" d="M899 308q0-28-21-50l-41-42q-22-21-51-21-30 0-50 21l-165 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50 0 30 21 51l363 363q20 21 50 21 30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="right-big" unicode="&#xe826;" d="M821 314q0-30-20-50l-363-364q-22-20-51-20-29 0-50 20l-42 42q-22 21-22 51t22 51l163 163h-393q-29 0-47 21t-18 51v71q0 30 18 51t47 20h393l-163 165q-22 20-22 50t22 50l42 42q21 21 50 21 29 0 51-21l363-363q20-20 20-51z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="left-big" unicode="&#xe827;" d="M857 350v-71q0-30-18-51t-47-21h-393l164-164q21-20 21-50t-21-50l-42-43q-21-20-51-20-29 0-50 20l-364 364q-20 21-20 50 0 29 20 51l364 363q21 21 50 21 29 0 51-21l42-41q21-22 21-51t-21-51l-164-164h393q29 0 47-20t18-51z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="down-big" unicode="&#xe828;" d="M899 386q0-30-21-50l-363-364q-22-21-51-21-29 0-50 21l-363 364q-21 20-21 50 0 29 21 51l41 41q22 21 51 21 29 0 50-21l164-164v393q0 29 21 50t51 22h71q29 0 50-22t21-50v-393l165 164q20 21 50 21 29 0 51-21l41-41q21-22 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="resize-full-alt" unicode="&#xe829;" d="M716 548l-198-198 198-198 80 80q17 18 39 8 22-9 22-33v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 21 7 38l81 81-198 198-198-198 80-81q17-17 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l80-80 198 198-198 198-80-80q-11-11-25-11-7 0-14 3-22 9-22 33v250q0 14 11 25t25 11h250q23 0 33-23 9-21-8-38l-80-81 198-198 198 198-81 81q-17 17-7 38 9 23 32 23h250q15 0 26-11t10-25v-250q0-24-22-33-7-3-14-3-14 0-25 11z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="resize-full" unicode="&#xe82a;" d="M421 261q0-7-5-13l-185-185 80-81q10-10 10-25t-10-25-25-11h-250q-15 0-25 11t-11 25v250q0 15 11 25t25 11 25-11l80-80 186 185q5 6 12 6t13-6l64-63q5-6 5-13z m436 482v-250q0-15-10-25t-26-11-25 11l-80 80-185-185q-6-6-13-6t-13 6l-64 64q-5 5-5 12t5 13l186 185-81 81q-10 10-10 25t10 25 25 11h250q15 0 26-11t10-25z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="resize-small" unicode="&#xe82b;" d="M429 314v-250q0-14-11-25t-25-10-25 10l-81 81-185-186q-5-5-13-5t-12 5l-64 64q-6 6-6 13t6 13l185 185-80 80q-11 11-11 25t11 25 25 11h250q14 0 25-11t11-25z m421 375q0-7-6-12l-185-186 80-80q11-11 11-25t-11-25-25-11h-250q-14 0-25 11t-10 25v250q0 14 10 25t25 10 25-10l81-80 185 185q6 5 13 5t13-5l63-64q6-5 6-13z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="move" unicode="&#xe82c;" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="resize-horizontal" unicode="&#xe82d;" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-572v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h572v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="resize-vertical" unicode="&#xe82e;" d="M393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-10 10-10 25t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
+
+<glyph glyph-name="zoom-in" unicode="&#xe82f;" d="M571 404v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="block" unicode="&#xe830;" d="M732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-111-41-153q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="zoom-out" unicode="&#xe831;" d="M571 404v-36q0-7-5-13t-12-5h-322q-7 0-12 5t-6 13v36q0 7 6 12t12 5h322q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="lightbulb" unicode="&#xe832;" d="M411 529q0-8-6-13t-12-5-13 5-5 13q0 25-30 39t-59 14q-7 0-13 5t-5 13 5 13 13 5q28 0 55-9t49-30 21-50z m89 0q0 40-19 74t-50 57-69 35-76 12-76-12-69-35-50-57-20-74q0-57 38-101 6-6 17-18t17-19q72-85 79-166h127q8 81 79 166 6 6 17 19t17 18q38 44 38 101z m71 0q0-87-57-150-25-27-42-48t-33-54-19-60q26-15 26-46 0-20-13-35 13-15 13-36 0-29-25-45 8-13 8-26 0-26-18-40t-43-14q-11-25-34-39t-48-15-49 15-33 39q-26 0-44 14t-17 40q0 13 7 26-25 16-25 45 0 21 14 36-14 15-14 35 0 31 26 46-2 28-19 60t-33 54-41 48q-58 63-58 150 0 55 25 103t65 79 92 49 104 19 104-19 91-49 66-79 24-103z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="clock" unicode="&#xe833;" d="M500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="volume-up" unicode="&#xe834;" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z m214-304q0-42-24-79t-63-52q-5-3-14-3-14 0-25 10t-10 26q0 12 6 20t17 14 19 12 16 21 6 31-6 32-16 20-19 13-17 13-6 20q0 15 10 26t25 10q9 0 14-3 39-15 63-52t24-79z m143 0q0-85-48-158t-125-105q-7-3-14-3-15 0-26 11t-10 25q0 22 21 33 32 16 43 25 41 30 64 75t23 97-23 97-64 75q-11 9-43 25-21 11-21 33 0 14 10 25t25 11q8 0 15-3 78-33 125-105t48-158z m143 0q0-128-71-236t-189-158q-7-3-14-3-15 0-25 11t-11 25q0 20 22 33 4 2 12 6t13 6q25 14 46 28 68 51 107 127t38 161-38 161-107 127q-21 15-46 28-4 3-13 6t-12 6q-22 13-22 33 0 15 11 25t25 11q7 0 14-3 118-51 189-158t71-236z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="volume-down" unicode="&#xe835;" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z m214-304q0-42-24-79t-63-52q-5-3-14-3-14 0-25 10t-10 26q0 12 6 20t17 14 19 12 16 21 6 31-6 32-16 20-19 13-17 13-6 20q0 15 10 26t25 10q9 0 14-3 39-15 63-52t24-79z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="volume-off" unicode="&#xe836;" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z" horiz-adv-x="428.6" />
+
+<glyph glyph-name="mute" unicode="&#xe837;" d="M151 323l-56-57q-24 58-24 120v71q0 15 11 25t25 11 25-11 11-25v-71q0-30 8-63z m622 336l-202-202v-71q0-74-52-126t-126-53q-31 0-61 11l-53-54q54-28 114-28 103 0 177 73t73 177v71q0 15 11 25t25 11 25-11 10-25v-71q0-124-82-215t-203-104v-74h142q15 0 26-11t10-25-10-25-26-11h-357q-14 0-25 11t-10 25 10 25 25 11h143v74q-70 7-131 45l-142-142q-5-6-13-6t-12 6l-46 46q-6 5-6 13t6 12l689 689q5 6 12 6t13-6l46-46q6-5 6-13t-6-12z m-212 73l-347-346v285q0 74 53 127t126 52q57 0 103-33t65-85z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="mic" unicode="&#xe838;" d="M643 457v-71q0-124-82-215t-204-104v-74h143q15 0 25-11t11-25-11-25-25-11h-357q-15 0-25 11t-11 25 11 25 25 11h143v74q-121 13-204 104t-82 215v71q0 15 11 25t25 11 25-11 10-25v-71q0-103 74-177t176-73 177 73 73 177v71q0 15 11 25t25 11 25-11 11-25z m-143 214v-285q0-74-52-126t-127-53-126 53-52 126v285q0 74 52 127t126 52 127-52 52-127z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="endtime" unicode="&#xe839;" d="M661 350q0-14-11-25l-303-304q-11-10-26-10t-25 10-10 25v161h-250q-15 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 10 25t25 10 26-10l303-304q11-10 11-25z m196 196v-392q0-67-47-114t-114-47h-178q-7 0-13 5t-5 13q0 2-1 11t0 15 2 13 5 11 12 3h178q37 0 64 27t26 63v392q0 37-26 64t-64 26h-174t-6 0-6 2-5 3-4 5-1 8q0 2-1 11t0 15 2 13 5 11 12 3h178q67 0 114-47t47-114z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="starttime" unicode="&#xe83a;" d="M357 46q0-2 1-11t0-14-2-14-5-11-12-3h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="calendar-empty" unicode="&#xe83b;" d="M71-79h786v572h-786v-572z m215 679v161q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h36q8 0 13 5t5 13z m428 0v161q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="calendar" unicode="&#xe83c;" d="M71-79h161v161h-161v-161z m197 0h178v161h-178v-161z m-197 197h161v178h-161v-178z m197 0h178v178h-178v-178z m-197 214h161v161h-161v-161z m411-411h179v161h-179v-161z m-214 411h178v161h-178v-161z m428-411h161v161h-161v-161z m-214 197h179v178h-179v-178z m-196 482v161q0 7-6 12t-12 6h-36q-7 0-12-6t-6-12v-161q0-7 6-13t12-5h36q7 0 12 5t6 13z m410-482h161v178h-161v-178z m-214 214h179v161h-179v-161z m214 0h161v161h-161v-161z m18 268v161q0 7-5 12t-13 6h-35q-7 0-13-6t-5-12v-161q0-7 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="wrench" unicode="&#xe83d;" d="M214 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="sliders" unicode="&#xe83e;" d="M196 64v-71h-196v71h196z m197 72q14 0 25-11t11-25v-143q0-14-11-25t-25-11h-143q-14 0-25 11t-11 25v143q0 15 11 25t25 11h143z m89 214v-71h-482v71h482z m-357 286v-72h-125v72h125z m732-572v-71h-411v71h411z m-536 643q15 0 26-10t10-26v-142q0-15-10-25t-26-11h-142q-15 0-25 11t-11 25v142q0 15 11 26t25 10h142z m358-286q14 0 25-10t10-25v-143q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v143q0 14 11 25t25 10h143z m178-71v-71h-125v71h125z m0 286v-72h-482v72h482z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="services" unicode="&#xe83f;" d="M500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="service" unicode="&#xe840;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="phone" unicode="&#xe841;" d="M786 158q0-15-6-39t-12-38q-11-28-68-60-52-28-103-28-15 0-30 2t-32 7-26 8-31 11-28 10q-54 20-97 47-71 44-148 120t-120 148q-27 43-46 97-2 5-10 28t-12 31-8 26-7 32-2 29q0 52 29 104 31 57 59 68 14 6 38 12t39 6q8 0 12-2 10-3 30-42 6-11 16-31t20-35 17-30q2-2 10-14t12-20 4-16q0-11-16-27t-35-31-34-30-16-25q0-5 3-13t4-11 8-14 7-10q42-77 97-132t131-97q1 0 10-6t14-8 11-5 13-2q10 0 25 16t30 34 31 35 28 16q7 0 15-4t20-12 14-10q14-8 30-17t36-20 30-17q39-19 42-29 2-4 2-12z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="file-pdf" unicode="&#xe842;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-287 331q18-14 47-31 33 4 65 4 82 0 99-27 9-13 1-29 0-1-1-1l-1-2v0q-3-21-39-21-27 0-64 11t-73 29q-123-13-219-46-85-146-135-146-8 0-15 4l-14 7q0 0-3 2-6 6-4 20 5 23 32 51t73 54q8 5 13-3 1-1 1-2 29 47 60 110 38 76 58 146-13 46-17 89t4 71q6 22 23 22h12q13 0 20-8 10-12 5-38-1-3-2-4 0-2 0-5v-17q-1-68-8-107 31-91 82-133z m-321-229q29 13 76 88-29-22-49-47t-27-41z m222 513q-9-23-2-73 1 4 4 24 0 2 4 24 1 3 3 5-1 0-1 1-1 1-1 2 0 12-7 20 0-1 0-1v-2z m-70-368q76 30 159 45-1 0-7 5t-9 8q-43 37-71 98-15-48-46-110-17-31-26-46z m361 9q-13 13-78 13 42-16 69-16 8 0 10 1 0 0-1 2z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="file-word" unicode="&#xe843;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-656 500v-59h39l92-369h88l72 271q4 11 5 25 2 9 2 14h2l1-14q1-1 2-11t3-14l72-271h89l91 369h39v59h-167v-59h50l-55-245q-3-11-4-25l-1-12h-3q0 2 0 4t-1 4 0 4q-1 2-2 11t-3 14l-81 304h-63l-81-304q-1-5-2-13t-2-12l-2-12h-2l-2 12q-1 14-3 25l-56 245h50v59h-167z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="file-excel" unicode="&#xe844;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-547 131v-59h157v59h-42l58 90q3 4 5 9t5 8 2 2h1q0-2 3-6 1-2 2-4t3-4 4-5l60-90h-43v-59h163v59h-38l-107 152 108 158h38v59h-156v-59h41l-57-89q-2-4-6-9t-5-8l-1-1h-1q0 2-3 5-3 6-9 13l-59 89h42v59h-162v-59h38l106-152-109-158h-38z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="doc-text" unicode="&#xe845;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="trash" unicode="&#xe846;" d="M286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="comment-empty" unicode="&#xe847;" d="M500 636q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
+
+<glyph glyph-name="comment" unicode="&#xe848;" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" />
+
+<glyph glyph-name="chat" unicode="&#xe849;" d="M786 421q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" />
+
+<glyph glyph-name="chat-empty" unicode="&#xe84a;" d="M393 636q-85 0-160-29t-118-79-44-107q0-45 30-88t83-73l54-32-19-46q19 11 34 21l25 18 30-6q43-8 85-8 85 0 160 29t118 79 43 106-43 107-118 79-160 29z m0 71q106 0 197-38t143-104 53-144-53-143-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38z m459-652q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128q0-67-40-126t-108-98z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bell" unicode="&#xe84b;" d="M509-96q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-372 160h726q-149 168-149 465 0 28-13 58t-39 58-67 45-95 17-95-17-67-45-39-58-13-58q0-297-149-465z m827 0q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bell-alt" unicode="&#xe84c;" d="M509-96q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
+
+<glyph glyph-name="attention-alt" unicode="&#xe84d;" d="M286 154v-125q0-15-11-25t-25-11h-143q-14 0-25 11t-11 25v125q0 14 11 25t25 10h143q15 0 25-10t11-25z m17 589l-16-429q-1-14-12-25t-25-10h-143q-14 0-25 10t-12 25l-15 429q-1 14 10 25t24 11h179q14 0 25-11t10-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="print" unicode="&#xe84e;" d="M214-7h500v143h-500v-143z m0 357h500v214h-89q-22 0-38 16t-16 38v89h-357v-357z m643-36q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m72 0v-232q0-7-6-12t-12-6h-125v-89q0-22-16-38t-38-16h-536q-22 0-37 16t-16 38v89h-125q-7 0-13 6t-5 12v232q0 44 32 76t75 31h36v304q0 22 16 38t37 16h375q23 0 50-12t42-26l85-85q15-16 27-43t11-49v-143h35q45 0 76-31t32-76z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="edit" unicode="&#xe84f;" d="M496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="forward" unicode="&#xe850;" d="M1000 493q0-15-11-25l-285-286q-11-11-25-11t-25 11-11 25v143h-125q-55 0-98-3t-86-12-74-24-59-39-45-56-27-77-10-101q0-31 3-69 0-4 2-13t1-15q0-8-5-14t-13-6q-9 0-15 10-4 5-8 12t-7 17-6 13q-71 159-71 252 0 111 30 186 90 225 488 225h125v143q0 14 11 25t25 10 25-10l285-286q11-11 11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="reply" unicode="&#xe851;" d="M1000 225q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
+
+<glyph glyph-name="reply-all" unicode="&#xe852;" d="M357 246v-39q0-23-22-33-7-3-14-3-15 0-25 11l-285 286q-11 10-11 25t11 25l285 286q17 17 39 8 22-10 22-33v-39l-221-222q-11-11-11-25t11-25z m643-21q0-32-9-74t-22-77-27-70-22-51l-11-22q-5-10-16-10-3 0-5 1-14 4-13 19 24 223-59 315-36 40-95 62t-150 29v-140q0-23-21-33-8-3-14-3-15 0-25 11l-286 286q-11 10-11 25t11 25l286 286q16 17 39 8 21-10 21-33v-147q230-15 335-123 94-96 94-284z" horiz-adv-x="1000" />
+
+<glyph glyph-name="eye" unicode="&#xe853;" d="M929 314q-85 132-213 197 34-58 34-125 0-103-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 243 68 186 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
+
+<glyph glyph-name="tag" unicode="&#xe854;" d="M250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="tags" unicode="&#xe855;" d="M250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z m215 0q0-30-21-51l-274-274q-22-21-51-21-20 0-33 8t-29 25l262 262q21 21 21 51 0 29-21 50l-399 399q-21 21-57 36t-65 15h125q29 0 65-15t57-36l399-399q21-21 21-50z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="lock-open-alt" unicode="&#xe856;" d="M589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="lock-open" unicode="&#xe857;" d="M929 529v-143q0-15-11-25t-25-11h-36q-14 0-25 11t-11 25v143q0 59-41 101t-101 41-101-41-42-101v-108h53q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h375v108q0 103 73 176t177 74 176-74 74-176z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="lock" unicode="&#xe858;" d="M179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="home" unicode="&#xe859;" d="M786 296v-267q0-15-11-25t-25-11h-214v214h-143v-214h-214q-15 0-25 11t-11 25v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-3-7 1-12 6l-35 41q-4 6-3 13t6 12l401 334q18 15 42 15t43-15l136-113v108q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q6-4 6-12t-4-13z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="info" unicode="&#xe85a;" d="M357 100v-71q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v71q0 15 11 25t25 11h35v214h-35q-15 0-25 11t-11 25v71q0 15 11 25t25 11h214q15 0 25-11t11-25v-321h35q15 0 26-11t10-25z m-71 643v-107q0-15-11-25t-25-11h-143q-14 0-25 11t-11 25v107q0 14 11 25t25 11h143q15 0 25-11t11-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="help" unicode="&#xe85b;" d="M393 149v-134q0-9-7-15t-15-7h-134q-9 0-16 7t-7 15v134q0 9 7 16t16 6h134q9 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 11t-6 20v26q0 46 37 87t79 60q33 16 47 32t14 42q0 24-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="search" unicode="&#xe85c;" d="M643 386q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="flapping" unicode="&#xe85d;" d="M372 582q-34-52-77-153-12 25-20 41t-23 35-28 32-36 19-45 8h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125q139 0 229-125z m628-446q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107q-18 0-48 0t-45-1-41 1-39 3-36 6-35 10-32 16-33 22-31 30-31 39q33 52 76 152 12-25 20-40t23-36 28-31 35-20 46-8h143v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z m0 500q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-143q-27 0-49-8t-38-25-29-34-25-44q-18-34-43-95-16-37-28-62t-30-59-36-55-41-47-50-38-60-23-71-10h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125q27 0 48 9t39 25 28 34 26 43q17 35 43 96 16 36 28 62t30 58 36 56 41 46 50 39 59 23 72 9h143v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
+
+<glyph glyph-name="rewind" unicode="&#xe85e;" d="M532 736q170 0 289-120t119-290-119-290-289-120q-142 0-252 88l70 74q84-60 182-60 126 0 216 90t90 218-90 218-216 90q-124 0-214-87t-92-211l142 0-184-204-184 204 124 0q2 166 122 283t286 117z" horiz-adv-x="940" />
+
+<glyph glyph-name="chart-line" unicode="&#xe85f;" d="M1143-7v-72h-1143v858h71v-786h1072z m-72 696v-242q0-12-10-17t-20 4l-68 68-353-353q-6-6-13-6t-13 6l-130 130-232-233-107 108 327 326q5 6 12 6t13-6l130-130 259 259-67 68q-9 8-5 19t17 11h243q7 0 12-5t5-13z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="bell-off" unicode="&#xe860;" d="M869 375q35-199 167-311 0-29-21-50t-51-21h-250q0-59-42-101t-101-42-100 42-42 100z m-298-480q9 0 9 9t-9 8q-32 0-56 24t-24 57q0 9-9 9t-9-9q0-41 29-70t69-28z m560 893q4-6 4-14t-6-12l-1045-905q-5-5-13-4t-12 6l-47 53q-4 6-4 14t6 12l104 90q-11 17-11 36 28 24 51 49t47 67 42 89 28 115 11 145q0 84 65 157t171 89q-4 10-4 21 0 23 16 38t37 16 38-16 16-38q0-11-4-21 69-10 122-46t82-88l234 202q5 5 13 4t12-6z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="bell-off-empty" unicode="&#xe861;" d="M580-96q0 8-9 8-32 0-56 24t-24 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-299 265l489 424q-23 49-74 82t-125 32q-51 0-94-17t-68-45-38-58-14-58q0-215-76-360z m755-105q0-29-21-50t-51-21h-250q0-59-42-101t-101-42-100 42-42 100l83 72h422q-92 105-126 256l61 55q35-199 167-311z m48 777l47-53q4-6 4-14t-6-12l-1045-905q-5-5-13-4t-12 6l-47 53q-4 6-4 14t6 12l104 90q-11 17-11 36 28 24 51 49t47 67 42 89 28 115 11 145q0 84 65 157t171 89q-4 10-4 21 0 23 16 38t37 16 38-16 16-38q0-11-4-21 69-10 122-46t82-88l234 202q5 5 13 4t12-6z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="plug" unicode="&#xe862;" d="M979 597q21-21 21-50t-21-51l-223-223 83-84-89-89q-91-91-217-104t-230 56l-202-202h-101v101l202 202q-69 103-56 230t104 217l89 89 84-83 223 223q21 21 51 21t50-21 21-50-21-51l-223-223 131-131 223 223q22 21 51 21t50-21z" horiz-adv-x="1000" />
+
+<glyph glyph-name="eye-off" unicode="&#xe863;" d="M310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
+
+<glyph glyph-name="arrows-cw" unicode="&#xe864;" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="cw" unicode="&#xe865;" d="M408 760q168 0 287-116t123-282l122 0-184-206-184 206 144 0q-4 124-94 210t-214 86q-126 0-216-90t-90-218q0-126 90-216t216-90q104 0 182 60l70-76q-110-88-252-88-168 0-288 120t-120 290 120 290 288 120z" horiz-adv-x="940" />
+
+<glyph glyph-name="host" unicode="&#xe866;" d="M232 136q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="thumbs-up" unicode="&#xe867;" d="M143 100q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m643 321q0 29-22 50t-50 22h-196q0 32 27 89t26 89q0 55-17 81t-72 27q-14-15-21-48t-17-70-33-61q-13-13-43-51-2-3-13-16t-18-23-19-24-22-25-22-19-22-15-20-6h-18v-357h18q7 0 18-1t18-4 21-6 20-7 20-6 16-6q118-41 191-41h67q107 0 107 93 0 15-2 31 16 9 26 30t10 41-10 38q29 28 29 67 0 14-5 31t-14 26q18 1 30 26t12 45z m71 1q0-50-27-91 5-18 5-38 0-43-21-81 1-12 1-24 0-56-33-99 0-78-48-123t-126-45h-72q-54 0-106 13t-121 36q-65 23-77 23h-161q-29 0-50 21t-21 50v357q0 30 21 51t50 21h153q20 13 77 86 32 42 60 72 13 14 19 48t17 70 35 60q22 21 50 21 47 0 84-18t57-57 20-104q0-51-27-107h98q58 0 101-42t42-100z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="thumbs-down" unicode="&#xe868;" d="M143 600q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m643-321q0 19-12 45t-30 26q8 10 14 27t5 31q0 38-29 66 10 17 10 38 0 21-10 41t-26 30q2 16 2 31 0 47-27 70t-76 23h-71q-73 0-191-41-3-1-16-5t-20-7-20-7-21-6-18-4-18-1h-18v-357h18q9 0 20-5t22-15 22-20 22-25 19-24 18-22 13-17q30-38 43-51 23-24 33-61t17-70 21-48q54 0 72 27t17 81q0 33-26 89t-27 89h196q28 0 50 22t22 50z m71-1q0-57-42-100t-101-42h-98q27-55 27-107 0-66-20-104-19-39-57-57t-84-18q-28 0-50 21-19 18-30 45t-14 51-10 47-17 36q-27 28-60 71-57 73-77 86h-153q-29 0-50 21t-21 51v357q0 29 21 50t50 21h161q12 0 77 23 72 24 125 36t111 13h63q78 0 126-44t48-121v-3q33-43 33-99 0-12-1-24 21-38 21-80 0-21-5-39 27-41 27-91z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="spinner" unicode="&#xe869;" d="M294 72q0-29-21-50t-51-21q-29 0-50 21t-21 50q0 30 21 51t50 21 51-21 21-51z m277-115q0-29-20-50t-51-21-50 21-21 50 21 51 50 21 51-21 20-51z m-392 393q0-30-21-50t-51-21-50 21-21 50 21 51 50 20 51-20 21-51z m670-278q0-29-21-50t-50-21q-30 0-51 21t-20 50 20 51 51 21 50-21 21-51z m-538 556q0-37-26-63t-63-26-63 26-26 63 26 63 63 26 63-26 26-63z m653-278q0-30-21-50t-50-21-51 21-21 50 21 51 51 20 50-20 21-51z m-357 393q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m296-115q0-52-37-88t-88-37q-52 0-88 37t-37 88q0 51 37 88t88 37q51 0 88-37t37-88z" horiz-adv-x="1000" />
+
+<glyph glyph-name="attach" unicode="&#xe86a;" d="M784 77q0-65-45-109t-109-44q-75 0-131 55l-434 434q-63 64-63 151 0 89 62 150t150 62q88 0 152-63l338-338q5-5 5-12 0-9-17-26t-26-17q-7 0-12 5l-339 339q-44 43-101 43-59 0-100-42t-40-101q0-58 42-101l433-433q35-36 81-36 36 0 59 24t24 59q0 46-35 81l-325 324q-14 14-33 14-16 0-27-11t-11-27q0-18 14-33l229-228q6-6 6-13 0-9-18-26t-26-17q-6 0-12 5l-229 229q-35 34-35 83 0 46 32 78t77 32q49 0 84-35l324-325q56-54 56-131z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="keyboard" unicode="&#xe86b;" d="M214 198v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m72 143v-53q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h125q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m572-286v-53q0-9-9-9h-482q-9 0-9 9v53q0 9 9 9h482q9 0 9-9z m-357 143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-8-9h-54q-9 0-9 9v53q0 9 9 9h54q8 0 8-9z m-71 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m215-143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-286 286v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-196q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h62v134q0 9 9 9h54q9 0 9-9z m71-420v500h-929v-500h929z m71 500v-500q0-29-20-50t-51-21h-929q-29 0-50 21t-21 50v500q0 30 21 51t50 21h929q30 0 51-21t20-51z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="menu" unicode="&#xe86c;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="wifi" unicode="&#xe86d;" d="M571 0q-11 0-51 41t-41 52q0 18 35 30t57 13 58-13 35-30q0-11-41-52t-52-41z m151 151q-1 0-22 14t-57 28-72 14-71-14-57-28-22-14q-10 0-52 42t-42 52q0 7 5 13 44 43 109 67t130 25 131-25 109-67q5-6 5-13 0-10-42-52t-52-42z m152 152q-6 0-12 5-76 58-141 86t-150 27q-47 0-95-12t-83-29-63-35-44-30-18-12q-9 0-51 42t-42 52q0 7 6 12 74 74 178 115t212 40 213-40 178-115q6-5 6-12 0-10-42-52t-52-42z m152 151q-6 0-13 5-99 88-207 132t-235 45-234-45-207-132q-7-5-13-5-9 0-51 42t-43 52q0 7 6 13 104 104 248 161t294 57 295-57 248-161q5-6 5-13 0-10-42-52t-51-42z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="moon" unicode="&#xe86e;" d="M704 123q-30-5-61-5-102 0-188 50t-137 137-50 188q0 107 58 199-112-33-183-128t-72-214q0-72 29-139t76-113 114-77 139-28q80 0 152 34t123 96z m114 47q-53-113-159-181t-230-68q-87 0-167 34t-136 92-92 137-34 166q0 85 32 163t87 135 132 92 161 38q25 1 34-22 11-23-8-40-48-43-73-101t-26-122q0-83 41-152t111-111 152-41q66 0 127 29 23 10 40-7 8-8 10-19t-2-22z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="chart-pie" unicode="&#xe86f;" d="M429 353l304-304q-59-61-138-94t-166-34q-117 0-216 58t-155 156-58 215 58 215 155 156 216 58v-426z m104-3h431q0-88-33-167t-94-138z m396 71h-429v429q117 0 215-57t156-156 58-216z" horiz-adv-x="1000" />
+
+<glyph glyph-name="chart-area" unicode="&#xe870;" d="M1143-7v-72h-1143v858h71v-786h1072z m-214 571l142-500h-928v322l250 321 321-321z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="chart-bar" unicode="&#xe871;" d="M357 350v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="beaker" unicode="&#xe872;" d="M852 42q31-50 12-85t-78-36h-643q-59 0-78 36t12 85l280 443v222h-36q-14 0-25 11t-10 25 10 25 25 11h286q15 0 25-11t11-25-11-25-25-11h-36v-222z m-435 405l-151-240h397l-152 240-11 17v243h-71v-243z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="magic" unicode="&#xe873;" d="M664 526l164 163-60 60-164-163z m250 163q0-15-10-25l-718-718q-10-10-25-10t-25 10l-111 111q-10 10-10 25t10 25l718 718q10 10 25 10t25-10l111-111q10-10 10-25z m-754 106l54-16-54-17-17-55-17 55-55 17 55 16 17 55z m195-90l109-34-109-33-34-109-33 109-109 33 109 34 33 109z m519-267l55-17-55-16-17-55-17 55-54 16 54 17 17 55z m-357 357l54-16-54-17-17-55-17 55-54 17 54 16 17 55z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="spin6" unicode="&#xe874;" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
+
+<glyph glyph-name="down-small" unicode="&#xe875;" d="M505 346q15-15 15-37t-15-37l-245-245-245 245q-15 15-15 37t15 37 37 15 37-15l120-119 0 395q0 21 15 36t36 15 37-15 16-36l0-395 120 119q15 15 36 15t36-15z" horiz-adv-x="520" />
+
+<glyph glyph-name="left-small" unicode="&#xe876;" d="M595 403q21 0 36-16t15-37-15-37-36-15l-395 0 119-119q15-15 15-37t-15-37-36-15q-23 0-38 15l-245 245 245 245q15 15 37 15t37-15 15-37-15-37l-119-118 395 0z" horiz-adv-x="646" />
+
+<glyph glyph-name="right-small" unicode="&#xe877;" d="M328 595q15 15 36 15t37-15l245-245-245-245q-15-15-36-15-22 0-37 15t-15 37 15 37l120 119-395 0q-22 0-37 15t-16 37 16 37 37 16l395 0-120 118q-15 15-15 37t15 37z" horiz-adv-x="646" />
+
+<glyph glyph-name="up-small" unicode="&#xe878;" d="M260 673l245-245q15-15 15-37t-15-37-36-15-36 15l-120 120 0-395q0-21-16-37t-37-15-36 15-15 37l0 395-120-120q-15-15-37-15t-37 15-15 37 15 37z" horiz-adv-x="520" />
+
+<glyph glyph-name="pin" unicode="&#xe879;" d="M573 37q0-23-15-38t-37-15q-21 0-37 16l-169 169-315-236 236 315-168 169q-24 23-12 56 14 32 48 32 157 0 270 57 90 45 151 171 9 24 36 32t50-13l208-209q21-23 14-50t-32-36q-127-63-172-152-56-110-56-268z" horiz-adv-x="834" />
+
+<glyph glyph-name="angle-double-left" unicode="&#xe87a;" d="M350 82q0-7-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13z m214 0q0-7-5-13l-28-28q-6-5-13-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q6 6 13 6t13-6l28-28q5-5 5-13t-5-12l-220-220 220-219q5-6 5-13z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="angle-double-right" unicode="&#xe87b;" d="M332 314q0-7-5-12l-261-261q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l261-260q5-5 5-13z m214 0q0-7-5-12l-260-261q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="circle" unicode="&#xe87c;" d="M857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="info-circled" unicode="&#xe87d;" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m52-152q-42 0-65-24t-23-50q-2-28 15-44t49-16q38 0 61 22t23 54q0 58-60 58z m-120-594q30 0 84 26t106 78l-18 24q-48-36-72-36-14 0-4 38l42 160q26 96-22 96-30 0-89-29t-115-75l16-26q52 34 74 34 12 0 0-34l-36-152q-26-104 34-104z" horiz-adv-x="920" />
+
+<glyph glyph-name="twitter" unicode="&#xe87e;" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="facebook-squared" unicode="&#xe87f;" d="M696 779q67 0 114-48t47-113v-536q0-66-47-113t-114-48h-104v333h111l16 129h-127v83q0 31 13 46t51 16l68 1v115q-35 5-100 5-75 0-121-44t-45-127v-95h-112v-129h112v-333h-297q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="gplus-squared" unicode="&#xe880;" d="M512 345q0 15-4 36h-202v-74h122q-2-13-10-28t-21-29-37-25-54-10q-55 0-94 40t-39 95 39 95 94 40q52 0 86-33l58 57q-60 55-144 55-89 0-151-62t-63-152 63-151 151-63q92 0 149 58t57 151z m192-26h61v62h-61v61h-61v-61h-61v-62h61v-61h61v61z m153 299v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="attention-circled" unicode="&#xe881;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m71-696v106q0 8-5 13t-12 5h-107q-8 0-13-5t-6-13v-106q0-8 6-13t13-6h107q7 0 12 6t5 13z m-1 192l10 346q0 7-6 10-5 5-13 5h-123q-8 0-13-5-6-3-6-10l10-346q0-6 5-10t14-4h103q8 0 13 4t6 10z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="check" unicode="&#xe883;" d="M786 331v-177q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-6-5-13-5-1 0-5 1-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v141q0 8 5 13l36 35q6 6 13 6 3 0 7-2 11-4 11-16z m129 273l-455-454q-13-14-31-14t-32 14l-240 240q-14 13-14 31t14 32l61 62q14 13 32 13t32-13l147-147 361 361q13 13 31 13t32-13l62-61q13-14 13-32t-13-32z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="reschedule" unicode="&#xe884;" d="M186 140l116 116 0-292-276 16 88 86q-116 122-114 290t120 288q100 100 240 116l4-102q-100-16-172-88-88-88-90-213t84-217z m332 598l276-16-88-86q116-122 114-290t-120-288q-96-98-240-118l-2 104q98 16 170 88 88 88 90 213t-84 217l-114-116z" horiz-adv-x="820" />
+
+<glyph glyph-name="warning-empty" unicode="&#xe885;" d="M514 701q-49 0-81-55l-308-513q-32-55-11-95t87-40l625 0q65 0 87 40t-12 95l-307 513q-33 55-80 55z m0 105q106 0 169-107l308-513q63-105 12-199-52-93-177-93l-625 0q-123 0-177 93-53 92 11 199l309 513q62 107 170 107z m-69-652q0 69 69 69 67 0 67-69 0-67-67-67-69 0-69 67z m146 313q0-14-6-29l-71-179q-44 108-73 179-6 15-6 29 0 32 23 55t56 24 55-24 22-55z" horiz-adv-x="1026" />
+
+<glyph glyph-name="th-list" unicode="&#xf009;" d="M0 62q0-30 21-51t51-21h32q30 0 51 21t21 51-21 51-51 21h-32q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h32q30 0 51 21t21 51-21 51-51 21h-32q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h32q30 0 51 21t21 51-21 51-51 21h-32q-30 0-51-21t-21-51z m234-576q0-30 21-51t51-21h559q30 0 51 21t21 51-21 51-51 21h-559q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h559q30 0 51 21t21 51-21 51-51 21h-559q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h559q30 0 51 21t21 51-21 51-51 21h-559q-30 0-51-21t-21-51z" horiz-adv-x="937.5" />
+
+<glyph glyph-name="th-thumb-empty" unicode="&#xf00b;" d="M0-66v286q0 22 15 37t37 16h286q21 0 37-16t15-37v-286q0-21-15-36t-37-15h-286q-22 0-37 15t-15 36z m0 546v286q0 21 15 36t37 15h286q21 0 37-15t15-36v-286q0-22-15-37t-37-16h-286q-21 0-37 16t-15 37z m88-510h214v214h-214v-214z m0 546h214v213h-214v-213z m459-582v286q0 22 15 37t37 16h286q21 0 37-16t15-37v-286q0-21-15-36t-37-15h-286q-21 0-37 15t-15 36z m0 546v286q0 21 15 36t37 15h286q22 0 37-15t15-36v-286q0-22-15-37t-37-16h-286q-21 0-37 16t-15 37z m88-510h215v214h-215v-214z m0 546h215v213h-215v-213z" horiz-adv-x="937.5" />
+
+<glyph glyph-name="github-circled" unicode="&#xf09b;" d="M429 779q116 0 215-58t156-156 57-215q0-140-82-252t-211-155q-15-3-22 4t-7 17q0 1 0 43t0 75q0 54-29 79 32 3 57 10t53 22 45 37 30 58 11 84q0 67-44 115 21 51-4 114-16 5-46-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22-47 7q-25-63-5-114-44-48-44-115 0-47 12-83t29-59 45-37 52-22 57-10q-21-20-27-58-12-5-25-8t-32-3-36 12-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7 5-8 7-6l4-3q12-6 24-21t18-29l6-13q7-21 24-34t37-17 39-3 31 1l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252q0 117 58 215t155 156 216 58z m-267-616q2 4-3 7-6 1-8-1-1-4 4-7 5-3 7 1z m18-19q4 3-1 9-6 5-9 2-4-3 1-9 5-6 9-2z m16-25q6 4 0 11-4 7-9 3-5-3 0-10t9-4z m24-23q4 4-2 10-7 7-11 2-5-5 2-11 6-6 11-1z m32-14q1 6-8 9-8 2-10-4t7-9q8-3 11 4z m35-3q0 7-10 6-9 0-9-6 0-7 10-6 9 0 9 6z m32 5q-1 7-10 5-9-1-8-8t10-4 8 7z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="angle-double-up" unicode="&#xf102;" d="M600 118q0-7-6-13l-28-28q-5-5-12-5t-13 5l-220 219-219-219q-5-5-13-5t-12 5l-28 28q-6 6-6 13t6 13l260 260q5 5 12 5t13-5l260-260q6-6 6-13z m0 214q0-7-6-13l-28-28q-5-5-12-5t-13 5l-220 220-219-220q-5-5-13-5t-12 5l-28 28q-6 6-6 13t6 13l260 260q5 6 12 6t13-6l260-260q6-6 6-13z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="angle-double-down" unicode="&#xf103;" d="M600 368q0-7-6-13l-260-260q-5-6-13-6t-12 6l-260 260q-6 6-6 13t6 13l28 28q5 5 12 5t13-5l219-220 220 220q5 5 13 5t12-5l28-28q6-6 6-13z m0 214q0-7-6-13l-260-260q-5-5-13-5t-12 5l-260 260q-6 6-6 13t6 13l28 28q5 6 12 6t13-6l219-219 220 219q5 6 13 6t12-6l28-28q6-6 6-13z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="angle-left" unicode="&#xf104;" d="M350 546q0-7-6-12l-219-220 219-219q6-6 6-13t-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="angle-right" unicode="&#xf105;" d="M332 314q0-7-5-12l-261-261q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l261-260q5-5 5-13z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="angle-up" unicode="&#xf106;" d="M600 189q0-7-6-12l-28-28q-5-6-12-6t-13 6l-220 219-219-219q-5-6-13-6t-12 6l-28 28q-6 5-6 12t6 13l260 260q5 6 12 6t13-6l260-260q6-5 6-13z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="angle-down" unicode="&#xf107;" d="M600 439q0-7-6-12l-260-261q-5-5-13-5t-12 5l-260 261q-6 5-6 12t6 13l28 28q5 6 12 6t13-6l219-219 220 219q5 6 13 6t12-6l28-28q6-5 6-13z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="history" unicode="&#xf1da;" d="M857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z m-357 161v-250q0-8-5-13t-13-5h-178q-8 0-13 5t-5 13v35q0 8 5 13t13 5h125v197q0 8 5 13t12 5h36q8 0 13-5t5-13z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 671v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
+</font>
+</defs>
+</svg> \ No newline at end of file
diff --git a/application/fonts/fontello-ifont/font/ifont.ttf b/application/fonts/fontello-ifont/font/ifont.ttf
new file mode 100644
index 0000000..2853b70
--- /dev/null
+++ b/application/fonts/fontello-ifont/font/ifont.ttf
Binary files differ
diff --git a/application/fonts/fontello-ifont/font/ifont.woff b/application/fonts/fontello-ifont/font/ifont.woff
new file mode 100644
index 0000000..d6d485d
--- /dev/null
+++ b/application/fonts/fontello-ifont/font/ifont.woff
Binary files differ
diff --git a/application/fonts/fontello-ifont/font/ifont.woff2 b/application/fonts/fontello-ifont/font/ifont.woff2
new file mode 100644
index 0000000..948e103
--- /dev/null
+++ b/application/fonts/fontello-ifont/font/ifont.woff2
Binary files differ
diff --git a/application/fonts/icingaweb.md b/application/fonts/icingaweb.md
new file mode 100644
index 0000000..5699f07
--- /dev/null
+++ b/application/fonts/icingaweb.md
@@ -0,0 +1,9 @@
+# fontello-ifont font files moved
+
+New target is: public/font
+
+The font directory has been moved to the public structure because of
+Internet Explorer version 8 compatibility. The common way for browsers is to
+include the binary embeded font type in the javascript. IE8 falls back and
+include one of the provided font sources. Therefore it is important to have
+the font files available public and exported by the HTTP server.
diff --git a/application/forms/Account/ChangePasswordForm.php b/application/forms/Account/ChangePasswordForm.php
new file mode 100644
index 0000000..5bca11c
--- /dev/null
+++ b/application/forms/Account/ChangePasswordForm.php
@@ -0,0 +1,123 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Account;
+
+use Icinga\Authentication\User\DbUserBackend;
+use Icinga\Data\Filter\Filter;
+use Icinga\User;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form for changing user passwords
+ */
+class ChangePasswordForm extends Form
+{
+ /**
+ * The user backend
+ *
+ * @var DbUserBackend
+ */
+ protected $backend;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setSubmitLabel($this->translate('Update Account'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'password',
+ 'old_password',
+ array(
+ 'label' => $this->translate('Old Password'),
+ 'required' => true
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'new_password',
+ array(
+ 'label' => $this->translate('New Password'),
+ 'required' => true
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'new_password_confirmation',
+ array(
+ 'label' => $this->translate('Confirm New Password'),
+ 'required' => true,
+ 'validators' => array(
+ array('identical', false, array('new_password'))
+ )
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $backend = $this->getBackend();
+ $backend->update(
+ $backend->getBaseTable(),
+ array('password' => $this->getElement('new_password')->getValue()),
+ Filter::where('user_name', $this->Auth()->getUser()->getUsername())
+ );
+ Notification::success($this->translate('Account updated'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($formData)
+ {
+ $valid = parent::isValid($formData);
+ if (! $valid) {
+ return false;
+ }
+
+ $oldPasswordEl = $this->getElement('old_password');
+
+ if (! $this->backend->authenticate($this->Auth()->getUser(), $oldPasswordEl->getValue())) {
+ $oldPasswordEl->addError($this->translate('Old password is invalid'));
+ $this->markAsError();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the user backend
+ *
+ * @return DbUserBackend
+ */
+ public function getBackend()
+ {
+ return $this->backend;
+ }
+
+ /**
+ * Set the user backend
+ *
+ * @param DbUserBackend $backend
+ *
+ * @return $this
+ */
+ public function setBackend(DbUserBackend $backend)
+ {
+ $this->backend = $backend;
+ return $this;
+ }
+}
diff --git a/application/forms/AcknowledgeApplicationStateMessageForm.php b/application/forms/AcknowledgeApplicationStateMessageForm.php
new file mode 100644
index 0000000..61f5824
--- /dev/null
+++ b/application/forms/AcknowledgeApplicationStateMessageForm.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Icinga\Application\Hook\ApplicationStateHook;
+use Icinga\Web\ApplicationStateCookie;
+use Icinga\Web\Form;
+use Icinga\Web\Url;
+
+class AcknowledgeApplicationStateMessageForm extends Form
+{
+ public function init()
+ {
+ $this->setAction(Url::fromPath('application-state/acknowledge-message'));
+ $this->setAttrib('class', 'application-state-acknowledge-message-control');
+ $this->setRedirectUrl('application-state/summary');
+ }
+
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ [
+ 'class' => 'link-button spinner',
+ 'decorators' => [
+ 'ViewHelper',
+ ['HtmlTag', ['tag' => 'div', 'class' => 'control-group form-controls']]
+ ],
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('cancel'),
+ 'title' => $this->translate('Acknowledge message'),
+ 'type' => 'submit'
+ ]
+ );
+ return $this;
+ }
+
+ public function createElements(array $formData = [])
+ {
+ $this->addElements(
+ [
+ [
+ 'hidden',
+ 'id',
+ [
+ 'required' => true,
+ 'validators' => ['NotEmpty'],
+ 'decorators' => ['ViewHelper']
+ ]
+ ]
+ ]
+ );
+
+ return $this;
+ }
+
+ public function onSuccess()
+ {
+ $cookie = new ApplicationStateCookie();
+
+ $ack = $cookie->getAcknowledgedMessages();
+ $ack[] = $this->getValue('id');
+
+ $active = ApplicationStateHook::getAllMessages();
+
+ $cookie->setAcknowledgedMessages(array_keys(array_intersect_key($active, array_flip($ack))));
+
+ $this->getResponse()->setCookie($cookie);
+
+ return true;
+ }
+}
diff --git a/application/forms/ActionForm.php b/application/forms/ActionForm.php
new file mode 100644
index 0000000..5b5b6ed
--- /dev/null
+++ b/application/forms/ActionForm.php
@@ -0,0 +1,78 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Icinga\Application\Hook\ConfigFormEventsHook;
+use Icinga\Web\Form;
+
+class ActionForm extends Form
+{
+ /**
+ * The icon shown on the button
+ *
+ * @var string
+ */
+ protected $icon = 'arrows-cw';
+
+ /**
+ * Set the icon to show on the button
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setIcon($name)
+ {
+ $this->icon = (string) $name;
+ return $this;
+ }
+
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ $this->setUidDisabled(true);
+ $this->setDecorators(['FormElements', 'Form']);
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'hidden',
+ 'identifier',
+ [
+ 'required' => true,
+ 'decorators' => ['ViewHelper']
+ ]
+ );
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ [
+ 'escape' => false,
+ 'type' => 'submit',
+ 'class' => 'link-button spinner',
+ 'value' => 'btn_submit',
+ 'decorators' => ['ViewHelper'],
+ 'label' => $this->getView()->icon($this->icon),
+ 'title' => $this->getDescription()
+ ]
+ );
+ }
+
+ public function isValid($formData)
+ {
+ $valid = parent::isValid($formData);
+
+ if ($valid) {
+ $valid = ConfigFormEventsHook::runIsValid($this);
+ }
+
+ return $valid;
+ }
+
+ public function onSuccess()
+ {
+ ConfigFormEventsHook::runOnSuccess($this);
+ }
+}
diff --git a/application/forms/Announcement/AcknowledgeAnnouncementForm.php b/application/forms/Announcement/AcknowledgeAnnouncementForm.php
new file mode 100644
index 0000000..85fecdc
--- /dev/null
+++ b/application/forms/Announcement/AcknowledgeAnnouncementForm.php
@@ -0,0 +1,92 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Announcement;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Announcement\AnnouncementCookie;
+use Icinga\Web\Announcement\AnnouncementIniRepository;
+use Icinga\Web\Form;
+use Icinga\Web\Url;
+
+class AcknowledgeAnnouncementForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAction(Url::fromPath('announcements/acknowledge'));
+ $this->setAttrib('class', 'acknowledge-announcement-control');
+ $this->setRedirectUrl('layout/announcements');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('cancel'),
+ 'title' => $this->translate('Acknowledge this announcement'),
+ 'type' => 'submit'
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(
+ array(
+ array(
+ 'hidden',
+ 'hash',
+ array(
+ 'required' => true,
+ 'validators' => array('NotEmpty'),
+ 'decorators' => array('ViewHelper')
+ )
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $cookie = new AnnouncementCookie();
+ $repo = new AnnouncementIniRepository();
+ $query = $repo->findActive();
+ $filter = array();
+ foreach ($cookie->getAcknowledged() as $hash) {
+ $filter[] = Filter::expression('hash', '=', $hash);
+ }
+ $query->addFilter(Filter::matchAny($filter));
+ $acknowledged = array();
+ foreach ($query as $row) {
+ $acknowledged[] = $row->hash;
+ }
+ $acknowledged[] = $this->getElement('hash')->getValue();
+ $cookie->setAcknowledged($acknowledged);
+ $this->getResponse()->setCookie($cookie);
+ return true;
+ }
+}
diff --git a/application/forms/Announcement/AnnouncementForm.php b/application/forms/Announcement/AnnouncementForm.php
new file mode 100644
index 0000000..4da47e2
--- /dev/null
+++ b/application/forms/Announcement/AnnouncementForm.php
@@ -0,0 +1,135 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Announcement;
+
+use DateTime;
+use Icinga\Authentication\Auth;
+use Icinga\Data\Filter\Filter;
+use Icinga\Forms\RepositoryForm;
+
+/**
+ * Create, update and delete announcements
+ */
+class AnnouncementForm extends RepositoryForm
+{
+ protected function fetchEntry()
+ {
+ $entry = parent::fetchEntry();
+ if ($entry !== false) {
+ if ($entry->start !== null) {
+ $entry->start = (new DateTime())->setTimestamp($entry->start);
+ }
+ if ($entry->end !== null) {
+ $entry->end = (new DateTime())->setTimestamp($entry->end);
+ }
+ }
+
+ return $entry;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function createInsertElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'author',
+ array(
+ 'disabled' => ! $this->getRequest()->isApiRequest(),
+ 'required' => true,
+ 'value' => Auth::getInstance()->getUser()->getUsername()
+ )
+ );
+ $this->addElement(
+ 'textarea',
+ 'message',
+ array(
+ 'description' => $this->translate('The message to display to users'),
+ 'label' => $this->translate('Message'),
+ 'required' => true
+ )
+ );
+ $this->addElement(
+ 'dateTimePicker',
+ 'start',
+ array(
+ 'description' => $this->translate('The time to display the announcement from'),
+ 'label' => $this->translate('Start'),
+ 'placeholder' => new DateTime('tomorrow'),
+ 'required' => true
+ )
+ );
+ $this->addElement(
+ 'dateTimePicker',
+ 'end',
+ array(
+ 'description' => $this->translate('The time to display the announcement until'),
+ 'label' => $this->translate('End'),
+ 'placeholder' => new DateTime('tomorrow +1day'),
+ 'required' => true
+ )
+ );
+
+ $this->setTitle($this->translate('Create a new announcement'));
+ $this->setSubmitLabel($this->translate('Create'));
+ }
+ /**
+ * {@inheritDoc}
+ */
+ protected function createUpdateElements(array $formData)
+ {
+ $this->createInsertElements($formData);
+ $this->setTitle(sprintf($this->translate('Edit announcement %s'), $this->getIdentifier()));
+ $this->setSubmitLabel($this->translate('Save'));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function createDeleteElements(array $formData)
+ {
+ $this->setTitle(sprintf($this->translate('Remove announcement %s?'), $this->getIdentifier()));
+ $this->setSubmitLabel($this->translate('Yes'));
+ $this->setAttrib('class', 'icinga-controls');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function createFilter()
+ {
+ return Filter::where('id', $this->getIdentifier());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getInsertMessage($success)
+ {
+ return $success
+ ? $this->translate('Announcement created')
+ : $this->translate('Failed to create announcement');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getUpdateMessage($success)
+ {
+ return $success
+ ? $this->translate('Announcement updated')
+ : $this->translate('Failed to update announcement');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function getDeleteMessage($success)
+ {
+ return $success
+ ? $this->translate('Announcement removed')
+ : $this->translate('Failed to remove announcement');
+ }
+}
diff --git a/application/forms/Authentication/LoginForm.php b/application/forms/Authentication/LoginForm.php
new file mode 100644
index 0000000..87b32ab
--- /dev/null
+++ b/application/forms/Authentication/LoginForm.php
@@ -0,0 +1,214 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Authentication;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Application\Hook\AuthenticationHook;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Authentication\User\ExternalBackend;
+use Icinga\Common\Database;
+use Icinga\Exception\Http\HttpBadRequestException;
+use Icinga\User;
+use Icinga\Web\Form;
+use Icinga\Web\RememberMe;
+use Icinga\Web\Url;
+
+/**
+ * Form for user authentication
+ */
+class LoginForm extends Form
+{
+ use Database;
+
+ const DEFAULT_CLASSES = 'icinga-controls';
+
+ /**
+ * Redirect URL
+ */
+ const REDIRECT_URL = 'dashboard';
+
+ public static $defaultElementDecorators = [
+ ['ViewHelper', ['separator' => '']],
+ ['Help', []],
+ ['Errors', ['separator' => '']],
+ ['HtmlTag', ['tag' => 'div', 'class' => 'control-group']]
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setRequiredCue(null);
+ $this->setName('form_login');
+ $this->setSubmitLabel($this->translate('Login'));
+ $this->setProgressLabel($this->translate('Logging in'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'username',
+ array(
+ 'autocapitalize' => 'off',
+ 'autocomplete' => 'username',
+ 'class' => false === isset($formData['username']) ? 'autofocus' : '',
+ 'placeholder' => $this->translate('Username'),
+ 'required' => true
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'password',
+ array(
+ 'required' => true,
+ 'autocomplete' => 'current-password',
+ 'placeholder' => $this->translate('Password'),
+ 'class' => isset($formData['username']) ? 'autofocus' : ''
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'rememberme',
+ [
+ 'label' => $this->translate('Stay logged in'),
+ 'decorators' => [
+ ['ViewHelper', ['separator' => '']],
+ ['Label', [
+ 'tag' => 'span',
+ 'separator' => '',
+ 'class' => 'control-label',
+ 'placement' => 'APPEND'
+ ]],
+ ['Help', []],
+ ['Errors', ['separator' => '']],
+ ['HtmlTag', ['tag' => 'div', 'class' => 'control-group remember-me-box']]
+ ]
+ ]
+ );
+ if (! RememberMe::isSupported()) {
+ $this->getElement('rememberme')
+ ->setAttrib('disabled', true)
+ ->setDescription($this->translate(
+ 'Staying logged in requires a database configuration backend'
+ . ' and an appropriate OpenSSL encryption method'
+ ));
+ }
+
+ $this->addElement(
+ 'hidden',
+ 'redirect',
+ array(
+ 'value' => Url::fromRequest()->getParam('redirect')
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRedirectUrl()
+ {
+ $redirect = null;
+ if ($this->created) {
+ $redirect = $this->getElement('redirect')->getValue();
+ }
+
+ if (empty($redirect) || strpos($redirect, 'authentication/logout') !== false) {
+ $redirect = static::REDIRECT_URL;
+ }
+
+ $redirectUrl = Url::fromPath($redirect);
+ if ($redirectUrl->isExternal()) {
+ throw new HttpBadRequestException('nope');
+ }
+
+ return $redirectUrl;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $auth = Auth::getInstance();
+ $authChain = $auth->getAuthChain();
+ $authChain->setSkipExternalBackends(true);
+ $user = new User($this->getElement('username')->getValue());
+ if (! $user->hasDomain()) {
+ $user->setDomain(Config::app()->get('authentication', 'default_domain'));
+ }
+ $password = $this->getElement('password')->getValue();
+ $authenticated = $authChain->authenticate($user, $password);
+ if ($authenticated) {
+ $auth->setAuthenticated($user);
+ if ($this->getElement('rememberme')->isChecked()) {
+ try {
+ $rememberMe = RememberMe::fromCredentials($user->getUsername(), $password);
+ $this->getResponse()->setCookie($rememberMe->getCookie());
+ $rememberMe->persist();
+ } catch (Exception $e) {
+ Logger::error('Failed to let user "%s" stay logged in: %s', $user->getUsername(), $e);
+ }
+ }
+
+ // Call provided AuthenticationHook(s) after successful login
+ AuthenticationHook::triggerLogin($user);
+ $this->getResponse()->setRerenderLayout(true);
+ return true;
+ }
+ switch ($authChain->getError()) {
+ case $authChain::EEMPTY:
+ $this->addError($this->translate(
+ 'No authentication methods available.'
+ . ' Did you create authentication.ini when setting up Icinga Web 2?'
+ ));
+ break;
+ case $authChain::EFAIL:
+ $this->addError($this->translate(
+ 'All configured authentication methods failed.'
+ . ' Please check the system log or Icinga Web 2 log for more information.'
+ ));
+ break;
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case $authChain::ENOTALL:
+ $this->addError($this->translate(
+ 'Please note that not all authentication methods were available.'
+ . ' Check the system log or Icinga Web 2 log for more information.'
+ ));
+ // Move to default
+ default:
+ $this->getElement('password')->addError($this->translate('Incorrect username or password'));
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onRequest()
+ {
+ $auth = Auth::getInstance();
+ $onlyExternal = true;
+ // TODO(el): This may be set on the auth chain once iterated. See Auth::authExternal().
+ foreach ($auth->getAuthChain() as $backend) {
+ if (! $backend instanceof ExternalBackend) {
+ $onlyExternal = false;
+ }
+ }
+ if ($onlyExternal) {
+ $this->addError($this->translate(
+ 'You\'re currently not authenticated using any of the web server\'s authentication mechanisms.'
+ . ' Make sure you\'ll configure such, otherwise you\'ll not be able to login.'
+ ));
+ }
+ }
+}
diff --git a/application/forms/AutoRefreshForm.php b/application/forms/AutoRefreshForm.php
new file mode 100644
index 0000000..122f635
--- /dev/null
+++ b/application/forms/AutoRefreshForm.php
@@ -0,0 +1,83 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Icinga\Application\Logger;
+use Icinga\User\Preferences;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+use Icinga\Web\Session;
+use Icinga\Web\Url;
+
+/**
+ * Form class to adjust user auto refresh preferences
+ */
+class AutoRefreshForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_auto_refresh');
+ // Post against the current location
+ $this->setAction('');
+ }
+
+ /**
+ * Adjust preferences and persist them
+ *
+ * @see Form::onSuccess()
+ */
+ public function onSuccess()
+ {
+ /** @var Preferences $preferences */
+ $preferences = $this->getRequest()->getUser()->getPreferences();
+ $icingaweb = $preferences->get('icingaweb');
+
+ if ((bool) $preferences->getValue('icingaweb', 'auto_refresh', true) === false) {
+ $icingaweb['auto_refresh'] = '1';
+ $notification = $this->translate('Auto refresh successfully enabled');
+ } else {
+ $icingaweb['auto_refresh'] = '0';
+ $notification = $this->translate('Auto refresh successfully disabled');
+ }
+ $preferences->icingaweb = $icingaweb;
+
+ Session::getSession()->user->setPreferences($preferences);
+ Notification::success($notification);
+
+ $this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
+ $this->setRedirectUrl(Url::fromRequest()->without('renderLayout'));
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $preferences = $this->getRequest()->getUser()->getPreferences();
+
+ if ((bool) $preferences->getValue('icingaweb', 'auto_refresh', true) === false) {
+ $value = $this->translate('Enable auto refresh');
+ } else {
+ $value = $this->translate('Disable auto refresh');
+ }
+
+ $this->addElements(array(
+ array(
+ 'button',
+ 'btn_submit',
+ array(
+ 'ignore' => true,
+ 'type' => 'submit',
+ 'value' => $value,
+ 'decorators' => array('ViewHelper'),
+ 'escape' => false,
+ 'class' => 'link-like'
+ )
+ )
+ ));
+ }
+}
diff --git a/application/forms/Config/General/ApplicationConfigForm.php b/application/forms/Config/General/ApplicationConfigForm.php
new file mode 100644
index 0000000..0e5c700
--- /dev/null
+++ b/application/forms/Config/General/ApplicationConfigForm.php
@@ -0,0 +1,93 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\General;
+
+use Icinga\Application\Icinga;
+use Icinga\Data\ResourceFactory;
+use Icinga\Web\Form;
+
+/**
+ * Configuration form for general application options
+ *
+ * This form is not used directly but as subform to the {@link GeneralConfigForm}.
+ */
+class ApplicationConfigForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_general_application');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'checkbox',
+ 'global_show_stacktraces',
+ array(
+ 'value' => true,
+ 'label' => $this->translate('Show Stacktraces'),
+ 'description' => $this->translate(
+ 'Set whether to show an exception\'s stacktrace by default. This can also'
+ . ' be set in a user\'s preferences with the appropriate permission.'
+ )
+ )
+ );
+
+ $this->addElement(
+ 'checkbox',
+ 'global_show_application_state_messages',
+ array(
+ 'value' => true,
+ 'label' => $this->translate('Show Application State Messages'),
+ 'description' => $this->translate(
+ "Set whether to show application state messages."
+ . " This can also be set in a user's preferences."
+ )
+ )
+ );
+
+ $this->addElement(
+ 'text',
+ 'global_module_path',
+ array(
+ 'label' => $this->translate('Module Path'),
+ 'required' => true,
+ 'value' => implode(':', Icinga::app()->getModuleManager()->getModuleDirs()),
+ 'description' => $this->translate(
+ 'Contains the directories that will be searched for available modules, separated by '
+ . 'colons. Modules that don\'t exist in these directories can still be symlinked in '
+ . 'the module folder, but won\'t show up in the list of disabled modules.'
+ )
+ )
+ );
+
+ $backends = array_keys(ResourceFactory::getResourceConfigs()->toArray());
+ $backends = array_combine($backends, $backends);
+
+ $this->addElement(
+ 'select',
+ 'global_config_resource',
+ array(
+ 'required' => true,
+ 'multiOptions' => array_merge(
+ ['' => sprintf(' - %s - ', $this->translate('Please choose'))],
+ $backends
+ ),
+ 'disable' => [''],
+ 'value' => '',
+ 'label' => $this->translate('Configuration Database')
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/General/DefaultAuthenticationDomainConfigForm.php b/application/forms/Config/General/DefaultAuthenticationDomainConfigForm.php
new file mode 100644
index 0000000..0ff6c32
--- /dev/null
+++ b/application/forms/Config/General/DefaultAuthenticationDomainConfigForm.php
@@ -0,0 +1,46 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\General;
+
+use Icinga\Web\Form;
+
+/**
+ * Configuration form for the default domain for authentication
+ *
+ * This form is not used directly but as subform to the {@link GeneralConfigForm}.
+ */
+class DefaultAuthenticationDomainConfigForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_general_authentication');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'authentication_default_domain',
+ array(
+ 'label' => $this->translate('Default Login Domain'),
+ 'description' => $this->translate(
+ 'If a user logs in without specifying any domain (e.g. "jdoe" instead of "jdoe@example.com"),'
+ . ' this default domain will be assumed for the user. Note that if none your LDAP authentication'
+ . ' backends are configured to be responsible for this domain or if none of your authentication'
+ . ' backends holds usernames with the domain part, users will not be able to login.'
+ )
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/General/LoggingConfigForm.php b/application/forms/Config/General/LoggingConfigForm.php
new file mode 100644
index 0000000..bbc7723
--- /dev/null
+++ b/application/forms/Config/General/LoggingConfigForm.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\General;
+
+use Icinga\Application\Logger;
+use Icinga\Application\Logger\Writer\SyslogWriter;
+use Icinga\Application\Platform;
+use Icinga\Web\Form;
+
+/**
+ * Configuration form for logging options
+ *
+ * This form is not used directly but as subform for the {@link GeneralConfigForm}.
+ */
+class LoggingConfigForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_general_logging');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function createElements(array $formData)
+ {
+ $defaultType = getenv('ICINGAWEB_OFFICIAL_DOCKER_IMAGE') ? 'php' : 'syslog';
+
+ $this->addElement(
+ 'select',
+ 'logging_log',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Logging Type'),
+ 'description' => $this->translate('The type of logging to utilize.'),
+ 'value' => $defaultType,
+ 'multiOptions' => array(
+ 'syslog' => 'Syslog',
+ 'php' => $this->translate('Webserver Log', 'app.config.logging.type'),
+ 'file' => $this->translate('File', 'app.config.logging.type'),
+ 'none' => $this->translate('None', 'app.config.logging.type')
+ )
+ )
+ );
+
+ if (! isset($formData['logging_log']) || $formData['logging_log'] !== 'none') {
+ $this->addElement(
+ 'select',
+ 'logging_level',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Logging Level'),
+ 'description' => $this->translate('The maximum logging level to emit.'),
+ 'multiOptions' => array(
+ Logger::$levels[Logger::ERROR] => $this->translate('Error', 'app.config.logging.level'),
+ Logger::$levels[Logger::WARNING] => $this->translate('Warning', 'app.config.logging.level'),
+ Logger::$levels[Logger::INFO] => $this->translate('Information', 'app.config.logging.level'),
+ Logger::$levels[Logger::DEBUG] => $this->translate('Debug', 'app.config.logging.level')
+ )
+ )
+ );
+ }
+
+ if (! isset($formData['logging_log']) || in_array($formData['logging_log'], array('syslog', 'php'))) {
+ $this->addElement(
+ 'text',
+ 'logging_application',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Application Prefix'),
+ 'description' => $this->translate(
+ 'The name of the application by which to prefix log messages.'
+ ),
+ 'requirement' => $this->translate('The application prefix must not contain whitespace.'),
+ 'value' => 'icingaweb2',
+ 'validators' => array(
+ array(
+ 'Regex',
+ false,
+ array(
+ 'pattern' => '/^\S+$/',
+ 'messages' => array(
+ 'regexNotMatch' => $this->translate(
+ 'The application prefix must not contain whitespace.'
+ )
+ )
+ )
+ )
+ )
+ )
+ );
+
+ if ((isset($formData['logging_log']) ? $formData['logging_log'] : $defaultType) === 'syslog') {
+ if (Platform::isWindows()) {
+ /* @see https://secure.php.net/manual/en/function.openlog.php */
+ $this->addElement(
+ 'hidden',
+ 'logging_facility',
+ array(
+ 'value' => 'user',
+ 'disabled' => true
+ )
+ );
+ } else {
+ $facilities = array_keys(SyslogWriter::$facilities);
+ $this->addElement(
+ 'select',
+ 'logging_facility',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Facility'),
+ 'description' => $this->translate('The syslog facility to utilize.'),
+ 'value' => 'user',
+ 'multiOptions' => array_combine($facilities, $facilities)
+ )
+ );
+ }
+ }
+ } elseif (isset($formData['logging_log']) && $formData['logging_log'] === 'file') {
+ $this->addElement(
+ 'text',
+ 'logging_file',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('File path'),
+ 'description' => $this->translate('The full path to the log file to write messages to.'),
+ 'value' => '/var/log/icingaweb2/icingaweb2.log',
+ 'validators' => array('WritablePathValidator')
+ )
+ );
+ }
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/General/ThemingConfigForm.php b/application/forms/Config/General/ThemingConfigForm.php
new file mode 100644
index 0000000..54ef2b1
--- /dev/null
+++ b/application/forms/Config/General/ThemingConfigForm.php
@@ -0,0 +1,78 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\General;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Web\Form;
+use Icinga\Web\StyleSheet;
+
+/**
+ * Configuration form for theming options
+ *
+ * This form is not used directly but as subform for the {@link GeneralConfigForm}.
+ */
+class ThemingConfigForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_general_theming');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return $this
+ */
+ public function createElements(array $formData)
+ {
+ $themes = Icinga::app()->getThemes();
+ $themes[StyleSheet::DEFAULT_THEME] .= ' (' . $this->translate('default') . ')';
+
+ $this->addElement(
+ 'select',
+ 'themes_default',
+ array(
+ 'description' => $this->translate('The default theme', 'Form element description'),
+ 'disabled' => count($themes) < 2 ? 'disabled' : null,
+ 'label' => $this->translate('Default Theme', 'Form element label'),
+ 'multiOptions' => $themes,
+ 'value' => StyleSheet::DEFAULT_THEME
+ )
+ );
+
+ $this->addElement(
+ 'checkbox',
+ 'themes_disabled',
+ array(
+ 'description' => $this->translate(
+ 'Check this box for disallowing users to change the theme. If a default theme is set, it will be'
+ . ' used nonetheless',
+ 'Form element description'
+ ),
+ 'label' => $this->translate('Users Can\'t Change Theme', 'Form element label')
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+ if ($values['themes_default'] === '' || $values['themes_default'] === StyleSheet::DEFAULT_THEME) {
+ $values['themes_default'] = null;
+ }
+ if (! $values['themes_disabled']) {
+ $values['themes_disabled'] = null;
+ }
+ return $values;
+ }
+}
diff --git a/application/forms/Config/GeneralConfigForm.php b/application/forms/Config/GeneralConfigForm.php
new file mode 100644
index 0000000..5f15512
--- /dev/null
+++ b/application/forms/Config/GeneralConfigForm.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config;
+
+use Icinga\Forms\Config\General\ApplicationConfigForm;
+use Icinga\Forms\Config\General\DefaultAuthenticationDomainConfigForm;
+use Icinga\Forms\Config\General\LoggingConfigForm;
+use Icinga\Forms\Config\General\ThemingConfigForm;
+use Icinga\Forms\ConfigForm;
+
+/**
+ * Configuration form for application-wide options
+ */
+class GeneralConfigForm extends ConfigForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_general');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $appConfigForm = new ApplicationConfigForm();
+ $loggingConfigForm = new LoggingConfigForm();
+ $themingConfigForm = new ThemingConfigForm();
+ $domainConfigForm = new DefaultAuthenticationDomainConfigForm();
+ $this->addSubForm($appConfigForm->create($formData));
+ $this->addSubForm($loggingConfigForm->create($formData));
+ $this->addSubForm($themingConfigForm->create($formData));
+ $this->addSubForm($domainConfigForm->create($formData));
+ }
+}
diff --git a/application/forms/Config/Resource/DbResourceForm.php b/application/forms/Config/Resource/DbResourceForm.php
new file mode 100644
index 0000000..b9979ee
--- /dev/null
+++ b/application/forms/Config/Resource/DbResourceForm.php
@@ -0,0 +1,238 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\Resource;
+
+use Icinga\Application\Platform;
+use Icinga\Web\Form;
+
+/**
+ * Form class for adding/modifying database resources
+ */
+class DbResourceForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_resource_db');
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData The data sent by the user
+ */
+ public function createElements(array $formData)
+ {
+ $dbChoices = array();
+ if (Platform::hasMysqlSupport()) {
+ $dbChoices['mysql'] = 'MySQL';
+ }
+ if (Platform::hasPostgresqlSupport()) {
+ $dbChoices['pgsql'] = 'PostgreSQL';
+ }
+ if (Platform::hasMssqlSupport()) {
+ $dbChoices['mssql'] = 'MSSQL';
+ }
+ if (Platform::hasIbmSupport()) {
+ $dbChoices['ibm'] = 'IBM (DB2)';
+ }
+ if (Platform::hasOracleSupport()) {
+ $dbChoices['oracle'] = 'Oracle';
+ }
+ if (Platform::hasOciSupport()) {
+ $dbChoices['oci'] = 'Oracle (OCI8)';
+ }
+ if (Platform::hasSqliteSupport()) {
+ $dbChoices['sqlite'] = 'SQLite';
+ }
+
+ $offerPostgres = false;
+ $offerMysql = false;
+ $dbChoice = isset($formData['db']) ? $formData['db'] : key($dbChoices);
+ if ($dbChoice === 'pgsql') {
+ $offerPostgres = true;
+ } elseif ($dbChoice === 'mysql') {
+ $offerMysql = true;
+ }
+
+ if ($dbChoice === 'oracle') {
+ $hostIsRequired = false;
+ } else {
+ $hostIsRequired = true;
+ }
+
+ $socketInfo = '';
+ if ($offerPostgres) {
+ $socketInfo = $this->translate(
+ 'For using unix domain sockets, specify the path to the unix domain socket directory'
+ );
+ } elseif ($offerMysql) {
+ $socketInfo = $this->translate(
+ 'For using unix domain sockets, specify localhost'
+ );
+ }
+
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Resource Name'),
+ 'description' => $this->translate('The unique name of this resource')
+ )
+ );
+ $this->addElement(
+ 'select',
+ 'db',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Database Type'),
+ 'description' => $this->translate('The type of SQL database'),
+ 'multiOptions' => $dbChoices
+ )
+ );
+ if ($dbChoice === 'sqlite') {
+ $this->addElement(
+ 'text',
+ 'dbname',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Database Name'),
+ 'description' => $this->translate('The name of the database to use')
+ )
+ );
+ } else {
+ $this->addElement(
+ 'text',
+ 'host',
+ array (
+ 'required' => $hostIsRequired,
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate('The hostname of the database')
+ . ($socketInfo ? '. ' . $socketInfo : ''),
+ 'value' => $hostIsRequired ? 'localhost' : ''
+ )
+ );
+ $this->addElement(
+ 'number',
+ 'port',
+ array(
+ 'description' => $this->translate('The port to use'),
+ 'label' => $this->translate('Port'),
+ 'preserveDefault' => true,
+ 'required' => $offerPostgres,
+ 'value' => $offerPostgres ? 5432 : null
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'dbname',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Database Name'),
+ 'description' => $this->translate('The name of the database to use')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'username',
+ array (
+ 'required' => true,
+ 'label' => $this->translate('Username'),
+ 'description' => $this->translate('The user name to use for authentication')
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'password',
+ array(
+ 'required' => true,
+ 'renderPassword' => true,
+ 'label' => $this->translate('Password'),
+ 'description' => $this->translate('The password to use for authentication')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'charset',
+ array (
+ 'description' => $this->translate('The character set for the database'),
+ 'label' => $this->translate('Character Set')
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'use_ssl',
+ array(
+ 'autosubmit' => true,
+ 'label' => $this->translate('Use SSL'),
+ 'description' => $this->translate(
+ 'Whether to encrypt the connection or to authenticate using certificates'
+ )
+ )
+ );
+ if (isset($formData['use_ssl']) && $formData['use_ssl']) {
+ if (defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) {
+ $this->addElement(
+ 'checkbox',
+ 'ssl_do_not_verify_server_cert',
+ array(
+ 'label' => $this->translate('SSL Do Not Verify Server Certificate'),
+ 'description' => $this->translate(
+ 'Whether to disable verification of the server certificate'
+ )
+ )
+ );
+ }
+ $this->addElement(
+ 'text',
+ 'ssl_key',
+ array(
+ 'label' => $this->translate('SSL Key'),
+ 'description' => $this->translate('The client key file path')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'ssl_cert',
+ array(
+ 'label' => $this->translate('SSL Certificate'),
+ 'description' => $this->translate('The certificate file path')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'ssl_ca',
+ array(
+ 'label' => $this->translate('SSL CA'),
+ 'description' => $this->translate('The CA certificate file path')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'ssl_capath',
+ array(
+ 'label' => $this->translate('SSL CA Path'),
+ 'description' => $this->translate(
+ 'The trusted CA certificates in PEM format directory path'
+ )
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'ssl_cipher',
+ array(
+ 'label' => $this->translate('SSL Cipher'),
+ 'description' => $this->translate('The list of permissible ciphers')
+ )
+ );
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/Resource/FileResourceForm.php b/application/forms/Config/Resource/FileResourceForm.php
new file mode 100644
index 0000000..b98f1b4
--- /dev/null
+++ b/application/forms/Config/Resource/FileResourceForm.php
@@ -0,0 +1,67 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\Resource;
+
+use Zend_Validate_Callback;
+use Icinga\Web\Form;
+
+/**
+ * Form class for adding/modifying file resources
+ */
+class FileResourceForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_resource_file');
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Resource Name'),
+ 'description' => $this->translate('The unique name of this resource')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'filename',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Filepath'),
+ 'description' => $this->translate('The filename to fetch information from'),
+ 'validators' => array('ReadablePathValidator')
+ )
+ );
+ $callbackValidator = new Zend_Validate_Callback(function ($value) {
+ return @preg_match($value, '') !== false;
+ });
+ $callbackValidator->setMessage(
+ $this->translate('"%value%" is not a valid regular expression.'),
+ Zend_Validate_Callback::INVALID_VALUE
+ );
+ $this->addElement(
+ 'text',
+ 'fields',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Pattern'),
+ 'description' => $this->translate('The pattern by which to identify columns.'),
+ 'requirement' => $this->translate('The column pattern must be a valid regular expression.'),
+ 'validators' => array($callbackValidator)
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/Resource/LdapResourceForm.php b/application/forms/Config/Resource/LdapResourceForm.php
new file mode 100644
index 0000000..7ffccdc
--- /dev/null
+++ b/application/forms/Config/Resource/LdapResourceForm.php
@@ -0,0 +1,129 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\Resource;
+
+use Icinga\Web\Form;
+use Icinga\Web\Url;
+use Icinga\Protocol\Ldap\LdapConnection;
+
+/**
+ * Form class for adding/modifying ldap resources
+ */
+class LdapResourceForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_resource_ldap');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $defaultPort = ! array_key_exists('encryption', $formData) || $formData['encryption'] !== LdapConnection::LDAPS
+ ? 389
+ : 636;
+
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Resource Name'),
+ 'description' => $this->translate('The unique name of this resource')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'hostname',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate(
+ 'The hostname or address of the LDAP server to use for authentication.'
+ . ' You can also provide multiple hosts separated by a space'
+ ),
+ 'value' => 'localhost'
+ )
+ );
+ $this->addElement(
+ 'number',
+ 'port',
+ array(
+ 'required' => true,
+ 'preserveDefault' => true,
+ 'label' => $this->translate('Port'),
+ 'description' => $this->translate('The port of the LDAP server to use for authentication'),
+ 'value' => $defaultPort
+ )
+ );
+ $this->addElement(
+ 'select',
+ 'encryption',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Encryption'),
+ 'description' => $this->translate(
+ 'Whether to encrypt communication. Choose STARTTLS or LDAPS for encrypted communication or'
+ . ' none for unencrypted communication'
+ ),
+ 'multiOptions' => array(
+ 'none' => $this->translate('None', 'resource.ldap.encryption'),
+ LdapConnection::STARTTLS => 'STARTTLS',
+ LdapConnection::LDAPS => 'LDAPS'
+ )
+ )
+ );
+
+ $this->addElement(
+ 'text',
+ 'root_dn',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Root DN'),
+ 'description' => $this->translate(
+ 'Only the root and its child nodes will be accessible on this resource.'
+ )
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'bind_dn',
+ array(
+ 'label' => $this->translate('Bind DN'),
+ 'description' => $this->translate(
+ 'The user dn to use for querying the ldap server. Leave the dn and password empty for attempting'
+ . ' an anonymous bind'
+ )
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'bind_pw',
+ array(
+ 'renderPassword' => true,
+ 'label' => $this->translate('Bind Password'),
+ 'description' => $this->translate('The password to use for querying the ldap server')
+ )
+ );
+
+ $this->addElement(
+ 'number',
+ 'timeout',
+ array(
+ 'preserveDefault' => true,
+ 'label' => $this->translate('Timeout'),
+ 'description' => $this->translate('Connection timeout for every LDAP connection'),
+ 'value' => 5 // see LdapConnection::__construct()
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/Resource/SshResourceForm.php b/application/forms/Config/Resource/SshResourceForm.php
new file mode 100644
index 0000000..a15dc8c
--- /dev/null
+++ b/application/forms/Config/Resource/SshResourceForm.php
@@ -0,0 +1,148 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\Resource;
+
+use Icinga\Application\Icinga;
+use Icinga\Data\ConfigObject;
+use Icinga\Forms\Config\ResourceConfigForm;
+use Icinga\Web\Form;
+use Icinga\Util\File;
+use Zend_Validate_Callback;
+
+/**
+ * Form class for adding/modifying ssh identity resources
+ */
+class SshResourceForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_resource_ssh');
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Resource Name'),
+ 'description' => $this->translate('The unique name of this resource')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'user',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('User'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ )
+ )
+ );
+
+ if ($this->getRequest()->getActionName() != 'editresource') {
+ $callbackValidator = new Zend_Validate_Callback(function ($value) {
+ if (substr(ltrim($value), 0, 7) === 'file://'
+ || openssl_pkey_get_private($value) === false
+ ) {
+ return false;
+ }
+
+ return true;
+ });
+ $callbackValidator->setMessage(
+ $this->translate('The given SSH key is invalid'),
+ Zend_Validate_Callback::INVALID_VALUE
+ );
+
+ $this->addElement(
+ 'textarea',
+ 'private_key',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Private Key'),
+ 'description' => $this->translate('The private key which will be used for the SSH connections'),
+ 'class' => 'resource ssh-identity',
+ 'validators' => array($callbackValidator)
+ )
+ );
+ } else {
+ $resourceName = $formData['name'];
+ $this->addElement(
+ 'note',
+ 'private_key_note',
+ array(
+ 'escape' => false,
+ 'label' => $this->translate('Private Key'),
+ 'value' => sprintf(
+ '<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
+ $this->getView()->url('config/removeresource', array('resource' => $resourceName)),
+ $this->getView()->escape(sprintf($this->translate(
+ 'Remove the %s resource'
+ ), $resourceName)),
+ $this->translate('To modify the private key you must recreate this resource.')
+ )
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove the assigned key to the resource
+ *
+ * @param ConfigObject $config
+ *
+ * @return bool
+ */
+ public static function beforeRemove(ConfigObject $config)
+ {
+ $file = $config->private_key;
+
+ if (file_exists($file)) {
+ unlink($file);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Creates the assigned key to the resource
+ *
+ * @param ResourceConfigForm $form
+ *
+ * @return bool
+ */
+ public static function beforeAdd(ResourceConfigForm $form)
+ {
+ $configDir = Icinga::app()->getConfigDir();
+ $user = $form->getElement('user')->getValue();
+
+ $filePath = join(DIRECTORY_SEPARATOR, [$configDir, 'ssh', sha1($user)]);
+ if (! file_exists($filePath)) {
+ $file = File::create($filePath, 0600);
+ } else {
+ $form->error(
+ sprintf($form->translate('The private key for the user "%s" already exists.'), $user)
+ );
+ return false;
+ }
+
+ $file->fwrite($form->getElement('private_key')->getValue());
+
+ $form->getElement('private_key')->setValue($filePath);
+
+ return true;
+ }
+}
diff --git a/application/forms/Config/ResourceConfigForm.php b/application/forms/Config/ResourceConfigForm.php
new file mode 100644
index 0000000..fe12aca
--- /dev/null
+++ b/application/forms/Config/ResourceConfigForm.php
@@ -0,0 +1,441 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config;
+
+use Icinga\Application\Config;
+use InvalidArgumentException;
+use Icinga\Application\Platform;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Data\ResourceFactory;
+use Icinga\Forms\ConfigForm;
+use Icinga\Forms\Config\Resource\DbResourceForm;
+use Icinga\Forms\Config\Resource\FileResourceForm;
+use Icinga\Forms\Config\Resource\LdapResourceForm;
+use Icinga\Forms\Config\Resource\SshResourceForm;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+class ResourceConfigForm extends ConfigForm
+{
+ /**
+ * Bogus password when inspecting password elements
+ *
+ * @var string
+ */
+ protected static $dummyPassword = '_web_form_5847ed1b5b8ca';
+
+ /**
+ * If the global config must be updated because a resource has been changed, this is the updated global config
+ *
+ * @var Config|null
+ */
+ protected $updatedAppConfig = null;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_resource');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ $this->setValidatePartial(true);
+ }
+
+ /**
+ * Return a form object for the given resource type
+ *
+ * @param string $type The resource type for which to return a form
+ *
+ * @return Form
+ */
+ public function getResourceForm($type)
+ {
+ if ($type === 'db') {
+ return new DbResourceForm();
+ } elseif ($type === 'ldap') {
+ return new LdapResourceForm();
+ } elseif ($type === 'file') {
+ return new FileResourceForm();
+ } elseif ($type === 'ssh') {
+ return new SshResourceForm();
+ } else {
+ throw new InvalidArgumentException(sprintf($this->translate('Invalid resource type "%s" provided'), $type));
+ }
+ }
+
+ /**
+ * Add a particular resource
+ *
+ * The backend to add is identified by the array-key `name'.
+ *
+ * @param array $values The values to extend the configuration with
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case the resource does already exist
+ */
+ public function add(array $values)
+ {
+ $name = isset($values['name']) ? $values['name'] : '';
+ if (! $name) {
+ throw new InvalidArgumentException($this->translate('Resource name missing'));
+ } elseif ($this->config->hasSection($name)) {
+ throw new InvalidArgumentException($this->translate('Resource already exists'));
+ }
+
+ unset($values['name']);
+ $this->config->setSection($name, $values);
+ return $this;
+ }
+
+ /**
+ * Edit a particular resource
+ *
+ * @param string $name The name of the resource to edit
+ * @param array $values The values to edit the configuration with
+ *
+ * @return array The edited configuration
+ *
+ * @throws InvalidArgumentException In case the resource does not exist
+ */
+ public function edit($name, array $values)
+ {
+ if (! $name) {
+ throw new InvalidArgumentException($this->translate('Old resource name missing'));
+ } elseif (! ($newName = isset($values['name']) ? $values['name'] : '')) {
+ throw new InvalidArgumentException($this->translate('New resource name missing'));
+ } elseif (! $this->config->hasSection($name)) {
+ throw new InvalidArgumentException($this->translate('Unknown resource provided'));
+ }
+
+ $resourceConfig = $this->config->getSection($name);
+ $this->config->removeSection($name);
+ unset($values['name']);
+ $this->config->setSection($newName, $resourceConfig->merge($values));
+
+ if ($newName !== $name) {
+ $appConfig = Config::app();
+ $section = $appConfig->getSection('global');
+ if ($section->config_resource === $name) {
+ $section->config_resource = $newName;
+ $this->updatedAppConfig = $appConfig->setSection('global', $section);
+ }
+ }
+
+ return $resourceConfig;
+ }
+
+ /**
+ * Remove a particular resource
+ *
+ * @param string $name The name of the resource to remove
+ *
+ * @return array The removed resource configuration
+ *
+ * @throws InvalidArgumentException In case the resource does not exist
+ */
+ public function remove($name)
+ {
+ if (! $name) {
+ throw new InvalidArgumentException($this->translate('Resource name missing'));
+ } elseif (! $this->config->hasSection($name)) {
+ throw new InvalidArgumentException($this->translate('Unknown resource provided'));
+ }
+
+ $resourceConfig = $this->config->getSection($name);
+ $resourceForm = $this->getResourceForm($resourceConfig->type);
+ if (method_exists($resourceForm, 'beforeRemove')) {
+ $resourceForm::beforeRemove($resourceConfig);
+ }
+
+ $this->config->removeSection($name);
+ return $resourceConfig;
+ }
+
+ /**
+ * Add or edit a resource and save the configuration
+ *
+ * Performs a connectivity validation using the submitted values. A checkbox is
+ * added to the form to skip the check if it fails and redirection is aborted.
+ *
+ * @see Form::onSuccess()
+ */
+ public function onSuccess()
+ {
+ $resourceForm = $this->getResourceForm($this->getElement('type')->getValue());
+
+ if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) {
+ $inspection = static::inspectResource($this);
+ if ($inspection !== null && $inspection->hasError()) {
+ $this->error($inspection->getError());
+ $this->addElement($this->getForceCreationCheckbox());
+ return false;
+ }
+ }
+
+ $resource = $this->request->getQuery('resource');
+ try {
+ if ($resource === null) { // create new resource
+ if (method_exists($resourceForm, 'beforeAdd')) {
+ if (! $resourceForm::beforeAdd($this)) {
+ return false;
+ }
+ }
+ $this->add(static::transformEmptyValuesToNull($this->getValues()));
+ $message = $this->translate('Resource "%s" has been successfully created');
+ } else { // edit existing resource
+ $this->edit($resource, static::transformEmptyValuesToNull($this->getValues()));
+ $message = $this->translate('Resource "%s" has been successfully changed');
+ }
+ } catch (InvalidArgumentException $e) {
+ Notification::error($e->getMessage());
+ return false;
+ }
+
+ if ($this->save()) {
+ Notification::success(sprintf($message, $this->getElement('name')->getValue()));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Populate the form in case a resource is being edited
+ *
+ * @see Form::onRequest()
+ *
+ * @throws ConfigurationError In case the backend name is missing in the request or is invalid
+ */
+ public function onRequest()
+ {
+ $resource = $this->request->getQuery('resource');
+ if ($resource !== null) {
+ if ($resource === '') {
+ throw new ConfigurationError($this->translate('Resource name missing'));
+ } elseif (! $this->config->hasSection($resource)) {
+ throw new ConfigurationError($this->translate('Unknown resource provided'));
+ }
+ $configValues = $this->config->getSection($resource)->toArray();
+ $configValues['name'] = $resource;
+ $this->populate($configValues);
+ foreach ($this->getElements() as $element) {
+ if ($element->getType() === 'Zend_Form_Element_Password' && strlen($element->getValue())) {
+ $element->setValue(static::$dummyPassword);
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a checkbox to be displayed at the beginning of the form
+ * which allows the user to skip the connection validation
+ *
+ * @return Zend_Form_Element
+ */
+ protected function getForceCreationCheckbox()
+ {
+ return $this->createElement(
+ 'checkbox',
+ 'force_creation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'label' => $this->translate('Force Changes'),
+ 'description' => $this->translate('Check this box to enforce changes without connectivity validation')
+ )
+ );
+ }
+
+ /**
+ * @see Form::createElemeents()
+ */
+ public function createElements(array $formData)
+ {
+ $resourceType = isset($formData['type']) ? $formData['type'] : 'db';
+
+ $resourceTypes = array(
+ 'file' => $this->translate('File'),
+ 'ssh' => $this->translate('SSH Identity'),
+ );
+ if ($resourceType === 'ldap' || Platform::hasLdapSupport()) {
+ $resourceTypes['ldap'] = 'LDAP';
+ }
+ if ($resourceType === 'db' || Platform::hasDatabaseSupport()) {
+ $resourceTypes['db'] = $this->translate('SQL Database');
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Resource Type'),
+ 'description' => $this->translate('The type of resource'),
+ 'multiOptions' => $resourceTypes,
+ 'value' => $resourceType
+ )
+ );
+
+ if (isset($formData['force_creation']) && $formData['force_creation']) {
+ // In case another error occured and the checkbox was displayed before
+ $this->addElement($this->getForceCreationCheckbox());
+ }
+
+ $this->addElements($this->getResourceForm($resourceType)->createElements($formData)->getElements());
+ }
+
+ /**
+ * Create a resource by using the given form's values and return its inspection results
+ *
+ * @param Form $form
+ *
+ * @return Inspection
+ */
+ public static function inspectResource(Form $form)
+ {
+ if ($form->getValue('type') !== 'ssh') {
+ $resource = ResourceFactory::createResource(new ConfigObject($form->getValues()));
+ if ($resource instanceof Inspectable) {
+ return $resource->inspect();
+ }
+ }
+ }
+
+ /**
+ * Run the configured resource's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'resource_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if ($this->getElement('resource_validation')->isChecked() && parent::isValid($formData)) {
+ $inspection = static::inspectResource($this);
+ if ($inspection !== null) {
+ $join = function ($e) use (&$join) {
+ return is_array($e) ? join("\n", array_map($join, $e)) : $e;
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+
+ if ($inspection->hasError()) {
+ $this->warning(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a submit button to this form and one to manually validate the configuration
+ *
+ * Calls parent::addSubmitButton() to add the submit button.
+ *
+ * @return $this
+ */
+ public function addSubmitButton()
+ {
+ parent::addSubmitButton()
+ ->getElement('btn_submit')
+ ->setDecorators(array('ViewHelper'));
+
+ $this->addElement(
+ 'submit',
+ 'resource_validation',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Validate Configuration'),
+ 'data-progress-label' => $this->translate('Validation In Progress'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->setAttrib('data-progress-element', 'resource-progress');
+ $this->addElement(
+ 'note',
+ 'resource-progress',
+ array(
+ 'decorators' => array(
+ 'ViewHelper',
+ array('Spinner', array('id' => 'resource-progress'))
+ )
+ )
+ );
+
+ $this->addDisplayGroup(
+ array('btn_submit', 'resource_validation', 'resource-progress'),
+ 'submit_validation',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+ $resource = $this->request->getQuery('resource');
+ if ($resource !== null && $this->config->hasSection($resource)) {
+ $resourceConfig = $this->config->getSection($resource)->toArray();
+ foreach ($this->getElements() as $element) {
+ if ($element->getType() === 'Zend_Form_Element_Password') {
+ $name = $element->getName();
+ if (isset($values[$name]) && $values[$name] === static::$dummyPassword) {
+ if (isset($resourceConfig[$name])) {
+ $values[$name] = $resourceConfig[$name];
+ } else {
+ unset($values[$name]);
+ }
+ }
+ }
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function writeConfig(Config $config)
+ {
+ parent::writeConfig($config);
+ if ($this->updatedAppConfig !== null) {
+ $this->updatedAppConfig->saveIni();
+ }
+ }
+}
diff --git a/application/forms/Config/User/CreateMembershipForm.php b/application/forms/Config/User/CreateMembershipForm.php
new file mode 100644
index 0000000..2828e95
--- /dev/null
+++ b/application/forms/Config/User/CreateMembershipForm.php
@@ -0,0 +1,191 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\User;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form for creating one or more group memberships
+ */
+class CreateMembershipForm extends Form
+{
+ /**
+ * The user group backends to fetch groups from
+ *
+ * Each backend must implement the Icinga\Data\Extensible and Icinga\Data\Selectable interface.
+ *
+ * @var array
+ */
+ protected $backends;
+
+ /**
+ * The username to create memberships for
+ *
+ * @var string
+ */
+ protected $userName;
+
+ /**
+ * Set the user group backends to fetch groups from
+ *
+ * @param array $backends
+ *
+ * @return $this
+ */
+ public function setBackends($backends)
+ {
+ $this->backends = $backends;
+ return $this;
+ }
+
+ /**
+ * Set the username to create memberships for
+ *
+ * @param string $userName
+ *
+ * @return $this
+ */
+ public function setUsername($userName)
+ {
+ $this->userName = $userName;
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData The data sent by the user
+ */
+ public function createElements(array $formData)
+ {
+ $query = $this->createDataSource()->select()->from('group', array('group_name', 'backend_name'));
+
+ $options = array();
+ foreach ($query as $row) {
+ $options[$row->backend_name . ';' . $row->group_name] = $row->group_name . ' (' . $row->backend_name . ')';
+ }
+
+ $this->addElement(
+ 'multiselect',
+ 'groups',
+ array(
+ 'required' => true,
+ 'multiOptions' => $options,
+ 'label' => $this->translate('Groups'),
+ 'description' => sprintf(
+ $this->translate('Select one or more groups where to add %s as member'),
+ $this->userName
+ ),
+ 'class' => 'grant-permissions'
+ )
+ );
+
+ $this->setTitle(sprintf($this->translate('Create memberships for %s'), $this->userName));
+ $this->setSubmitLabel($this->translate('Create'));
+ }
+
+ /**
+ * Instantly redirect back in case the user is already a member of all groups
+ */
+ public function onRequest()
+ {
+ if ($this->createDataSource()->select()->from('group')->count() === 0) {
+ Notification::info(sprintf($this->translate('User %s is already a member of all groups'), $this->userName));
+ $this->getResponse()->redirectAndExit($this->getRedirectUrl());
+ }
+ }
+
+ /**
+ * Create the memberships for the user
+ *
+ * @return bool
+ */
+ public function onSuccess()
+ {
+ $backendMap = array();
+ foreach ($this->backends as $backend) {
+ $backendMap[$backend->getName()] = $backend;
+ }
+
+ $single = null;
+ foreach ($this->getValue('groups') as $backendAndGroup) {
+ list($backendName, $groupName) = explode(';', $backendAndGroup, 2);
+ try {
+ $backendMap[$backendName]->insert(
+ 'group_membership',
+ array(
+ 'group_name' => $groupName,
+ 'user_name' => $this->userName
+ )
+ );
+ } catch (Exception $e) {
+ Notification::error(sprintf(
+ $this->translate('Failed to add "%s" as group member for "%s"'),
+ $this->userName,
+ $groupName
+ ));
+ $this->error($e->getMessage());
+ return false;
+ }
+
+ $single = $single === null;
+ }
+
+ if ($single) {
+ Notification::success(
+ sprintf($this->translate('Membership for group %s created successfully'), $groupName)
+ );
+ } else {
+ Notification::success($this->translate('Memberships created successfully'));
+ }
+
+ return true;
+ }
+
+ /**
+ * Create and return a data source to fetch all groups from all backends where the user is not already a member of
+ *
+ * @return ArrayDatasource
+ */
+ protected function createDataSource()
+ {
+ $groups = $failures = array();
+ foreach ($this->backends as $backend) {
+ try {
+ $memberships = $backend
+ ->select()
+ ->from('group_membership', array('group_name'))
+ ->where('user_name', $this->userName)
+ ->fetchColumn();
+ foreach ($backend->select(array('group_name')) as $row) {
+ if (! in_array($row->group_name, $memberships)) { // TODO(jom): Apply this as native query filter
+ $row->backend_name = $backend->getName();
+ $groups[] = $row;
+ }
+ }
+ } catch (Exception $e) {
+ $failures[] = array($backend->getName(), $e);
+ }
+ }
+
+ if (empty($groups) && !empty($failures)) {
+ // In case there are only failures, throw the very first exception again
+ throw $failures[0][1];
+ } elseif (! empty($failures)) {
+ foreach ($failures as $failure) {
+ Logger::error($failure[1]);
+ Notification::warning(sprintf(
+ $this->translate('Failed to fetch any groups from backend %s. Please check your log'),
+ $failure[0]
+ ));
+ }
+ }
+
+ return new ArrayDatasource($groups);
+ }
+}
diff --git a/application/forms/Config/User/UserForm.php b/application/forms/Config/User/UserForm.php
new file mode 100644
index 0000000..fb2ef4d
--- /dev/null
+++ b/application/forms/Config/User/UserForm.php
@@ -0,0 +1,210 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\User;
+
+use Icinga\Application\Hook\ConfigFormEventsHook;
+use Icinga\Data\Filter\Filter;
+use Icinga\Forms\RepositoryForm;
+use Icinga\Web\Notification;
+
+class UserForm extends RepositoryForm
+{
+ /**
+ * Create and add elements to this form to insert or update a user
+ *
+ * @param array $formData The data sent by the user
+ */
+ protected function createInsertElements(array $formData)
+ {
+ $this->addElement(
+ 'checkbox',
+ 'is_active',
+ array(
+ 'value' => true,
+ 'label' => $this->translate('Active'),
+ 'description' => $this->translate('Prevents the user from logging in if unchecked')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'user_name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Username')
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'password',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Password')
+ )
+ );
+
+ $this->setTitle($this->translate('Add a new user'));
+ $this->setSubmitLabel($this->translate('Add'));
+ }
+
+ /**
+ * Create and add elements to this form to update a user
+ *
+ * @param array $formData The data sent by the user
+ */
+ protected function createUpdateElements(array $formData)
+ {
+ $this->createInsertElements($formData);
+
+ $this->addElement(
+ 'password',
+ 'password',
+ array(
+ 'description' => $this->translate('Leave empty for not updating the user\'s password'),
+ 'label' => $this->translate('Password'),
+ )
+ );
+
+ $this->setTitle(sprintf($this->translate('Edit user %s'), $this->getIdentifier()));
+ $this->setSubmitLabel($this->translate('Save'));
+ }
+
+ /**
+ * Update a user
+ *
+ * @return bool
+ */
+ protected function onUpdateSuccess()
+ {
+ if (parent::onUpdateSuccess()) {
+ if (($newName = $this->getValue('user_name')) !== $this->getIdentifier()) {
+ $this->getRedirectUrl()->setParam('user', $newName);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve all form element values
+ *
+ * Strips off the password if null or the empty string.
+ *
+ * @param bool $suppressArrayNotation
+ *
+ * @return array
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+ // before checking if password values is empty
+ // we have to check that the password field is set
+ // otherwise an error is thrown
+ if (isset($values['password']) && ! $values['password']) {
+ unset($values['password']);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Create and add elements to this form to delete a user
+ *
+ * @param array $formData The data sent by the user
+ */
+ protected function createDeleteElements(array $formData)
+ {
+ $this->setTitle(sprintf($this->translate('Remove user %s?'), $this->getIdentifier()));
+ $this->setSubmitLabel($this->translate('Yes'));
+ $this->setAttrib('class', 'icinga-controls');
+ }
+
+ /**
+ * Create and return a filter to use when updating or deleting a user
+ *
+ * @return Filter
+ */
+ protected function createFilter()
+ {
+ return Filter::where('user_name', $this->getIdentifier());
+ }
+
+ /**
+ * Return a notification message to use when inserting a user
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ protected function getInsertMessage($success)
+ {
+ if ($success) {
+ return $this->translate('User added successfully');
+ } else {
+ return $this->translate('Failed to add user');
+ }
+ }
+
+ /**
+ * Return a notification message to use when updating a user
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ protected function getUpdateMessage($success)
+ {
+ if ($success) {
+ return sprintf($this->translate('User "%s" has been edited'), $this->getIdentifier());
+ } else {
+ return sprintf($this->translate('Failed to edit user "%s"'), $this->getIdentifier());
+ }
+ }
+
+ /**
+ * Return a notification message to use when deleting a user
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ protected function getDeleteMessage($success)
+ {
+ if ($success) {
+ return sprintf($this->translate('User "%s" has been removed'), $this->getIdentifier());
+ } else {
+ return sprintf($this->translate('Failed to remove user "%s"'), $this->getIdentifier());
+ }
+ }
+
+ public function isValid($formData)
+ {
+ $valid = parent::isValid($formData);
+
+ if ($valid && ConfigFormEventsHook::runIsValid($this) === false) {
+ foreach (ConfigFormEventsHook::getLastErrors() as $msg) {
+ $this->error($msg);
+ }
+
+ $valid = false;
+ }
+
+ return $valid;
+ }
+
+ public function onSuccess()
+ {
+ if (parent::onSuccess() === false) {
+ return false;
+ }
+
+ if (ConfigFormEventsHook::runOnSuccess($this) === false) {
+ Notification::error($this->translate(
+ 'Configuration successfully stored. Though, one or more module hooks failed to run.'
+ . ' See logs for details'
+ ));
+ }
+ }
+}
diff --git a/application/forms/Config/UserBackend/DbBackendForm.php b/application/forms/Config/UserBackend/DbBackendForm.php
new file mode 100644
index 0000000..693ea14
--- /dev/null
+++ b/application/forms/Config/UserBackend/DbBackendForm.php
@@ -0,0 +1,82 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserBackend;
+
+use Icinga\Web\Form;
+
+/**
+ * Form class for adding/modifying database user backends
+ */
+class DbBackendForm extends Form
+{
+ /**
+ * The database resource names the user can choose from
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_authbackend_db');
+ }
+
+ /**
+ * Set the resource names the user can choose from
+ *
+ * @param array $resources The resources to choose from
+ *
+ * @return $this
+ */
+ public function setResources(array $resources)
+ {
+ $this->resources = $resources;
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this authentication provider that is used to differentiate it from others'
+ )
+ )
+ );
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Database Connection'),
+ 'description' => $this->translate(
+ 'The database connection to use for authenticating with this provider'
+ ),
+ 'multiOptions' => !empty($this->resources)
+ ? array_combine($this->resources, $this->resources)
+ : array()
+ )
+ );
+ $this->addElement(
+ 'hidden',
+ 'backend',
+ array(
+ 'disabled' => true,
+ 'value' => 'db'
+ )
+ );
+ }
+}
diff --git a/application/forms/Config/UserBackend/ExternalBackendForm.php b/application/forms/Config/UserBackend/ExternalBackendForm.php
new file mode 100644
index 0000000..f4a4639
--- /dev/null
+++ b/application/forms/Config/UserBackend/ExternalBackendForm.php
@@ -0,0 +1,83 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserBackend;
+
+use Zend_Validate_Callback;
+use Icinga\Web\Form;
+
+/**
+ * Form class for adding/modifying user backends of type "external"
+ */
+class ExternalBackendForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_authbackend_external');
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this authentication provider that is used to differentiate it from others'
+ )
+ )
+ );
+ $callbackValidator = new Zend_Validate_Callback(function ($value) {
+ return @preg_match($value, '') !== false;
+ });
+ $callbackValidator->setMessage(
+ $this->translate('"%value%" is not a valid regular expression.'),
+ Zend_Validate_Callback::INVALID_VALUE
+ );
+ $this->addElement(
+ 'text',
+ 'strip_username_regexp',
+ array(
+ 'label' => $this->translate('Filter Pattern'),
+ 'description' => $this->translate(
+ 'The filter to use to strip specific parts off from usernames.'
+ . ' Leave empty if you do not want to strip off anything.'
+ ),
+ 'requirement' => $this->translate('The filter pattern must be a valid regular expression.'),
+ 'validators' => array($callbackValidator)
+ )
+ );
+ $this->addElement(
+ 'hidden',
+ 'backend',
+ array(
+ 'disabled' => true,
+ 'value' => 'external'
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * Validate the configuration by creating a backend and requesting the user count
+ *
+ * Returns always true as backends of type "external" are just "passive" backends.
+ *
+ * @param Form $form The form to fetch the configuration values from
+ *
+ * @return bool Whether validation succeeded or not
+ */
+ public static function isValidUserBackend(Form $form)
+ {
+ return true;
+ }
+}
diff --git a/application/forms/Config/UserBackend/LdapBackendForm.php b/application/forms/Config/UserBackend/LdapBackendForm.php
new file mode 100644
index 0000000..e7804cc
--- /dev/null
+++ b/application/forms/Config/UserBackend/LdapBackendForm.php
@@ -0,0 +1,414 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserBackend;
+
+use Exception;
+use Icinga\Data\ResourceFactory;
+use Icinga\Protocol\Ldap\LdapCapabilities;
+use Icinga\Protocol\Ldap\LdapConnection;
+use Icinga\Protocol\Ldap\LdapException;
+use Icinga\Web\Form;
+
+/**
+ * Form class for adding/modifying LDAP user backends
+ */
+class LdapBackendForm extends Form
+{
+ /**
+ * The ldap resource names the user can choose from
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * Default values for the form elements
+ *
+ * @var string[]
+ */
+ protected $suggestions = array();
+
+ /**
+ * Cache for {@link getLdapCapabilities()}
+ *
+ * @var LdapCapabilities
+ */
+ protected $ldapCapabilities;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_authbackend_ldap');
+ }
+
+ /**
+ * Set the resource names the user can choose from
+ *
+ * @param array $resources The resources to choose from
+ *
+ * @return $this
+ */
+ public function setResources(array $resources)
+ {
+ $this->resources = $resources;
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $isAd = isset($formData['type']) ? $formData['type'] === 'msldap' : false;
+
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this authentication provider that is used to differentiate it from others.'
+ ),
+ 'value' => $this->getSuggestion('name')
+ )
+ );
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('LDAP Connection'),
+ 'description' => $this->translate(
+ 'The LDAP connection to use for authenticating with this provider.'
+ ),
+ 'multiOptions' => !empty($this->resources)
+ ? array_combine($this->resources, $this->resources)
+ : array(),
+ 'value' => $this->getSuggestion('resource')
+ )
+ );
+
+ if (! $isAd && !empty($this->resources)) {
+ $this->addElement(
+ 'button',
+ 'discovery_btn',
+ array(
+ 'class' => 'control-button',
+ 'type' => 'submit',
+ 'value' => 'discovery_btn',
+ 'label' => $this->translate('Discover', 'A button to discover LDAP capabilities'),
+ 'title' => $this->translate(
+ 'Push to fill in the chosen connection\'s default settings.'
+ ),
+ 'decorators' => array(
+ array('ViewHelper', array('separator' => '')),
+ array('Spinner'),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ }
+
+ if ($isAd) {
+ // ActiveDirectory defaults
+ $userClass = 'user';
+ $filter = '!(objectClass=computer)';
+ $userNameAttribute = 'sAMAccountName';
+ } else {
+ // OpenLDAP defaults
+ $userClass = 'inetOrgPerson';
+ $filter = null;
+ $userNameAttribute = 'uid';
+ }
+
+ $this->addElement(
+ 'text',
+ 'user_class',
+ array(
+ 'preserveDefault' => true,
+ 'required' => ! $isAd,
+ 'ignore' => $isAd,
+ 'disabled' => $isAd ?: null,
+ 'label' => $this->translate('LDAP User Object Class'),
+ 'description' => $this->translate('The object class used for storing users on the LDAP server.'),
+ 'value' => $this->getSuggestion('user_class', $userClass)
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'filter',
+ array(
+ 'preserveDefault' => true,
+ 'allowEmpty' => true,
+ 'value' => $this->getSuggestion('filter', $filter),
+ 'label' => $this->translate('LDAP Filter'),
+ 'description' => $this->translate(
+ 'An additional filter to use when looking up users using the specified connection. '
+ . 'Leave empty to not to use any additional filter rules.'
+ ),
+ 'requirement' => $this->translate(
+ 'The filter needs to be expressed as standard LDAP expression.'
+ . ' (e.g. &(foo=bar)(bar=foo) or foo=bar)'
+ ),
+ 'validators' => array(
+ array(
+ 'Callback',
+ false,
+ array(
+ 'callback' => function ($v) {
+ // This is not meant to be a full syntax check. It will just
+ // ensure that we can safely strip unnecessary parentheses.
+ $v = trim($v);
+ return ! $v || $v[0] !== '(' || (
+ strpos($v, ')(') !== false ? substr($v, -2) === '))' : substr($v, -1) === ')'
+ );
+ },
+ 'messages' => array(
+ 'callbackValue' => $this->translate('The filter is invalid. Please check your syntax.')
+ )
+ )
+ )
+ )
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'user_name_attribute',
+ array(
+ 'preserveDefault' => true,
+ 'required' => ! $isAd,
+ 'ignore' => $isAd,
+ 'disabled' => $isAd ?: null,
+ 'label' => $this->translate('LDAP User Name Attribute'),
+ 'description' => $this->translate(
+ 'The attribute name used for storing the user name on the LDAP server.'
+ ),
+ 'value' => $this->getSuggestion('user_name_attribute', $userNameAttribute)
+ )
+ );
+ $this->addElement(
+ 'hidden',
+ 'backend',
+ array(
+ 'disabled' => true,
+ 'value' => $this->getSuggestion('backend', $isAd ? 'msldap' : 'ldap')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'base_dn',
+ array(
+ 'preserveDefault' => true,
+ 'required' => false,
+ 'label' => $this->translate('LDAP Base DN'),
+ 'description' => $this->translate(
+ 'The path where users can be found on the LDAP server. Leave ' .
+ 'empty to select all users available using the specified connection.'
+ ),
+ 'value' => $this->getSuggestion('base_dn')
+ )
+ );
+
+ $this->addElement(
+ 'text',
+ 'domain',
+ array(
+ 'label' => $this->translate('Domain'),
+ 'description' => $this->translate(
+ 'The domain the LDAP server is responsible for upon authentication.'
+ . ' Note that if you specify a domain here,'
+ . ' the LDAP backend only authenticates users who specify a domain upon login.'
+ . ' If the domain of the user matches the domain configured here, this backend is responsible for'
+ . ' authenticating the user based on the username without the domain part.'
+ . ' If your LDAP backend holds usernames with a domain part or if it is not necessary in your setup'
+ . ' to authenticate users based on their domains, leave this field empty.'
+ ),
+ 'preserveDefault' => true,
+ 'value' => $this->getSuggestion('domain')
+ )
+ );
+
+ $this->addElement(
+ 'button',
+ 'btn_discover_domain',
+ array(
+ 'class' => 'control-button',
+ 'type' => 'submit',
+ 'value' => 'discovery_btn',
+ 'label' => $this->translate('Discover the domain'),
+ 'title' => $this->translate(
+ 'Push to disover and fill in the domain of the LDAP server.'
+ ),
+ 'decorators' => array(
+ array('ViewHelper', array('separator' => '')),
+ array('Spinner'),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ }
+
+ public function isValidPartial(array $formData)
+ {
+ $isAd = isset($formData['type']) && $formData['type'] === 'msldap';
+ $baseDn = null;
+ $hasAdOid = false;
+ $discoverySuccessful = false;
+
+ if (! $isAd && ! empty($this->resources) && isset($formData['discovery_btn'])
+ && $formData['discovery_btn'] === 'discovery_btn') {
+ $discoverySuccessful = true;
+ try {
+ $capabilities = $this->getLdapCapabilities($formData);
+ $baseDn = $capabilities->getDefaultNamingContext();
+ $hasAdOid = $capabilities->isActiveDirectory();
+ } catch (Exception $e) {
+ $this->warning(sprintf(
+ $this->translate('Failed to discover the chosen LDAP connection: %s'),
+ $e->getMessage()
+ ));
+ $discoverySuccessful = false;
+ }
+ }
+
+ if ($discoverySuccessful) {
+ if ($isAd || $hasAdOid) {
+ // ActiveDirectory defaults
+ $userClass = 'user';
+ $filter = '!(objectClass=computer)';
+ $userNameAttribute = 'sAMAccountName';
+ } else {
+ // OpenLDAP defaults
+ $userClass = 'inetOrgPerson';
+ $filter = null;
+ $userNameAttribute = 'uid';
+ }
+
+ $formData['user_class'] = $userClass;
+
+ if (! isset($formData['filter']) || $formData['filter'] === '') {
+ $formData['filter'] = $filter;
+ }
+
+ $formData['user_name_attribute'] = $userNameAttribute;
+
+ if ($baseDn !== null && (! isset($formData['base_dn']) || $formData['base_dn'] === '')) {
+ $formData['base_dn'] = $baseDn;
+ }
+ }
+
+ if (isset($formData['btn_discover_domain']) && $formData['btn_discover_domain'] === 'discovery_btn') {
+ try {
+ $formData['domain'] = $this->discoverDomain($formData);
+ } catch (LdapException $e) {
+ $this->error($e->getMessage());
+ }
+ }
+
+ return parent::isValidPartial($formData);
+ }
+
+ /**
+ * Get the LDAP capabilities of either the resource specified by the user or the default one
+ *
+ * @param string[] $formData
+ *
+ * @return LdapCapabilities
+ */
+ protected function getLdapCapabilities(array $formData)
+ {
+ if ($this->ldapCapabilities === null) {
+ $this->ldapCapabilities = ResourceFactory::create(
+ isset($formData['resource']) ? $formData['resource'] : reset($this->resources)
+ )->bind()->getCapabilities();
+ }
+
+ return $this->ldapCapabilities;
+ }
+
+ /**
+ * Discover the domain the LDAP server is responsible for
+ *
+ * @param string[] $formData
+ *
+ * @return string
+ */
+ protected function discoverDomain(array $formData)
+ {
+ $cap = $this->getLdapCapabilities($formData);
+
+ if ($cap->isActiveDirectory()) {
+ $netBiosName = $cap->getNetBiosName();
+ if ($netBiosName !== null) {
+ return $netBiosName;
+ }
+ }
+
+ return $this->defaultNamingContextToFQDN($cap);
+ }
+
+ /**
+ * Get the default naming context as FQDN
+ *
+ * @param LdapCapabilities $cap
+ *
+ * @return string|null
+ */
+ protected function defaultNamingContextToFQDN(LdapCapabilities $cap)
+ {
+ $defaultNamingContext = $cap->getDefaultNamingContext();
+ if ($defaultNamingContext !== null) {
+ $validationMatches = array();
+ if (preg_match('/\bdc=[^,]+(?:,dc=[^,]+)*$/', strtolower($defaultNamingContext), $validationMatches)) {
+ $splitMatches = array();
+ preg_match_all('/dc=([^,]+)/', $validationMatches[0], $splitMatches);
+ return implode('.', $splitMatches[1]);
+ }
+ }
+ }
+
+ /**
+ * Get the default values for the form elements
+ *
+ * @return string[]
+ */
+ public function getSuggestions()
+ {
+ return $this->suggestions;
+ }
+
+ /**
+ * Get the default value for the given form element or the given default
+ *
+ * @param string $element
+ * @param string $default
+ *
+ * @return string
+ */
+ public function getSuggestion($element, $default = null)
+ {
+ return isset($this->suggestions[$element]) ? $this->suggestions[$element] : $default;
+ }
+
+ /**
+ * Set the default values for the form elements
+ *
+ * @param string[] $suggestions
+ *
+ * @return $this
+ */
+ public function setSuggestions(array $suggestions)
+ {
+ $this->suggestions = $suggestions;
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/UserBackendConfigForm.php b/application/forms/Config/UserBackendConfigForm.php
new file mode 100644
index 0000000..fdca657
--- /dev/null
+++ b/application/forms/Config/UserBackendConfigForm.php
@@ -0,0 +1,482 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config;
+
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Forms\ConfigForm;
+use Icinga\Forms\Config\UserBackend\ExternalBackendForm;
+use Icinga\Forms\Config\UserBackend\DbBackendForm;
+use Icinga\Forms\Config\UserBackend\LdapBackendForm;
+use Icinga\Web\Form;
+
+/**
+ * Form for managing user backends
+ */
+class UserBackendConfigForm extends ConfigForm
+{
+ /**
+ * The available user backend resources split by type
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * The backend to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $backendToLoad;
+
+ /**
+ * The loaded custom backends list
+ *
+ * @var array
+ */
+ protected $customBackends;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_authbackend');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ $this->setValidatePartial(true);
+ $this->customBackends = UserBackend::getCustomBackendConfigForms();
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param Config $resourceConfig The resource configuration
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError In case there are no valid resources for authentication available
+ */
+ public function setResourceConfig(Config $resourceConfig)
+ {
+ $resources = array();
+ foreach ($resourceConfig as $name => $resource) {
+ if (in_array($resource->type, array('db', 'ldap'))) {
+ $resources[$resource->type][] = $name;
+ }
+ }
+
+ if (empty($resources)) {
+ $externalBackends = $this->config->toArray();
+ array_walk(
+ $externalBackends,
+ function (&$authBackendCfg) {
+ if (! isset($authBackendCfg['backend']) || $authBackendCfg['backend'] !== 'external') {
+ $authBackendCfg = null;
+ }
+ }
+ );
+ if (count(array_filter($externalBackends)) > 0 && (
+ $this->backendToLoad === null || !isset($externalBackends[$this->backendToLoad])
+ )) {
+ throw new ConfigurationError($this->translate(
+ 'Could not find any valid user backend resources.'
+ . ' Please configure a resource for authentication first.'
+ ));
+ }
+ }
+
+ $this->resources = $resources;
+ return $this;
+ }
+
+ /**
+ * Return a form object for the given backend type
+ *
+ * @param string $type The backend type for which to return a form
+ *
+ * @return Form
+ *
+ * @throws InvalidArgumentException In case the given backend type is invalid
+ */
+ public function getBackendForm($type)
+ {
+ switch ($type) {
+ case 'db':
+ $form = new DbBackendForm();
+ $form->setResources(isset($this->resources['db']) ? $this->resources['db'] : array());
+ break;
+ case 'ldap':
+ case 'msldap':
+ $form = new LdapBackendForm();
+ $form->setResources(isset($this->resources['ldap']) ? $this->resources['ldap'] : array());
+ break;
+ case 'external':
+ $form = new ExternalBackendForm();
+ break;
+ default:
+ if (isset($this->customBackends[$type])) {
+ return new $this->customBackends[$type]();
+ }
+
+ throw new InvalidArgumentException(
+ sprintf($this->translate('Invalid backend type "%s" provided'), $type)
+ );
+ }
+
+ return $form;
+ }
+
+ /**
+ * Populate the form with the given backend's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function load($name)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No user backend called "%s" found', $name);
+ }
+
+ $this->backendToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new user backend
+ *
+ * The backend to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a backend name
+ * @throws IcingaException In case a backend with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ }
+
+ $backendName = $data['name'];
+ if ($this->config->hasSection($backendName)) {
+ throw new IcingaException(
+ $this->translate('A user backend with the name "%s" does already exist'),
+ $backendName
+ );
+ }
+
+ unset($data['name']);
+ $this->config->setSection($backendName, $data);
+ return $this;
+ }
+
+ /**
+ * Edit a user backend
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function edit($name, array $data)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No user backend called "%s" found', $name);
+ }
+
+ $backendConfig = $this->config->getSection($name);
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $this->config->removeSection($name);
+ $name = $data['name'];
+ }
+
+ unset($data['name']);
+ }
+
+ $backendConfig->merge($data);
+ $this->config->setSection($name, $backendConfig);
+ return $this;
+ }
+
+ /**
+ * Remove a user backend
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function delete($name)
+ {
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Move the given user backend up or down in order
+ *
+ * @param string $name The name of the backend to be moved
+ * @param int $position The new (absolute) position of the backend
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function move($name, $position)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No user backend called "%s" found', $name);
+ }
+
+ $backendOrder = $this->config->keys();
+ array_splice($backendOrder, array_search($name, $backendOrder), 1);
+ array_splice($backendOrder, $position, 0, $name);
+
+ $newConfig = array();
+ foreach ($backendOrder as $backendName) {
+ $newConfig[$backendName] = $this->config->getSection($backendName);
+ }
+
+ $config = Config::fromArray($newConfig);
+ $this->config = $config->setConfigFile($this->config->getConfigFile());
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $backendTypes = array();
+ $backendType = isset($formData['type']) ? $formData['type'] : null;
+
+ if (isset($this->resources['db'])) {
+ $backendTypes['db'] = $this->translate('Database');
+ }
+ if (isset($this->resources['ldap'])) {
+ $backendTypes['ldap'] = 'LDAP';
+ $backendTypes['msldap'] = 'ActiveDirectory';
+ }
+
+ $externalBackends = array_filter(
+ $this->config->toArray(),
+ function ($authBackendCfg) {
+ return isset($authBackendCfg['backend']) && $authBackendCfg['backend'] === 'external';
+ }
+ );
+ if ($backendType === 'external' || empty($externalBackends)) {
+ $backendTypes['external'] = $this->translate('External');
+ }
+
+ $customBackendTypes = array_keys($this->customBackends);
+ $backendTypes += array_combine($customBackendTypes, $customBackendTypes);
+
+ if ($backendType === null) {
+ $backendType = key($backendTypes);
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'ignore' => true,
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate(
+ 'The type of the resource to use for this authenticaton provider'
+ ),
+ 'multiOptions' => $backendTypes
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ // In case another error occured and the checkbox was displayed before
+ $this->addSkipValidationCheckbox();
+ }
+
+ $this->addSubForm($this->getBackendForm($backendType)->create($formData), 'backend_form');
+ }
+
+ /**
+ * Populate the configuration of the backend to load
+ */
+ public function onRequest()
+ {
+ if ($this->backendToLoad) {
+ $data = $this->config->getSection($this->backendToLoad)->toArray();
+ $data['name'] = $this->backendToLoad;
+ $data['type'] = $data['backend'];
+ $this->populate($data);
+ }
+ }
+
+ /**
+ * Return whether the given values are valid
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (($el = $this->getElement('skip_validation')) === null || false === $el->isChecked()) {
+ $inspection = static::inspectUserBackend($this);
+ if ($inspection && $inspection->hasError()) {
+ $this->error($inspection->getError());
+ if ($el === null) {
+ $this->addSkipValidationCheckbox();
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a user backend by using the given form's values and return its inspection results
+ *
+ * Returns null for non-inspectable backends.
+ *
+ * @param Form $form
+ *
+ * @return Inspection|null
+ */
+ public static function inspectUserBackend(Form $form)
+ {
+ $backend = UserBackend::create(null, new ConfigObject($form->getValues()));
+ if ($backend instanceof Inspectable) {
+ return $backend->inspect();
+ }
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the connection validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate(
+ 'Check this box to enforce changes without validating that authentication is possible.'
+ )
+ )
+ );
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (! parent::isValidPartial($formData)) {
+ return false;
+ }
+
+ if ($this->getElement('backend_validation')->isChecked() && parent::isValid($formData)) {
+ $inspection = static::inspectUserBackend($this);
+ if ($inspection !== null) {
+ $join = function ($e) use (&$join) {
+ return is_array($e) ? join("\n", array_map($join, $e)) : $e;
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+
+ if ($inspection->hasError()) {
+ $this->warning(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a submit button to this form and one to manually validate the configuration
+ *
+ * Calls parent::addSubmitButton() to add the submit button.
+ *
+ * @return $this
+ */
+ public function addSubmitButton()
+ {
+ parent::addSubmitButton()
+ ->getElement('btn_submit')
+ ->setDecorators(array('ViewHelper'));
+
+ $this->addElement(
+ 'submit',
+ 'backend_validation',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Validate Configuration'),
+ 'data-progress-label' => $this->translate('Validation In Progress'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ $this->addDisplayGroup(
+ array('btn_submit', 'backend_validation'),
+ 'submit_validation',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/UserBackendReorderForm.php b/application/forms/Config/UserBackendReorderForm.php
new file mode 100644
index 0000000..019c032
--- /dev/null
+++ b/application/forms/Config/UserBackendReorderForm.php
@@ -0,0 +1,86 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config;
+
+use Icinga\Application\Config;
+use Icinga\Forms\ConfigForm;
+use Icinga\Exception\NotFoundError;
+use Icinga\Web\Notification;
+
+class UserBackendReorderForm extends ConfigForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_reorder_authbackend');
+ $this->setViewScript('form/reorder-authbackend.phtml');
+ }
+
+ /**
+ * Return the ordered backend names
+ *
+ * @return array
+ */
+ public function getBackendOrder()
+ {
+ return $this->config->keys();
+ }
+
+ /**
+ * Return the ordered backend configuration
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ // This adds just a dummy element to be able to utilize Form::getValue as part of onSuccess()
+ $this->addElement('hidden', 'backend_newpos');
+ }
+
+ /**
+ * Update the user backend order and save the configuration
+ */
+ public function onSuccess()
+ {
+ $newPosData = $this->getValue('backend_newpos');
+ if ($newPosData) {
+ $configForm = $this->getConfigForm();
+ list($backendName, $position) = explode('|', $newPosData, 2);
+
+ try {
+ if ($configForm->move($backendName, $position)->save()) {
+ Notification::success($this->translate('Authentication order updated'));
+ } else {
+ return false;
+ }
+ } catch (NotFoundError $_) {
+ Notification::error(sprintf($this->translate('User backend "%s" not found'), $backendName));
+ }
+ }
+ }
+
+ /**
+ * Return the config form for user backends
+ *
+ * @return ConfigForm
+ */
+ protected function getConfigForm()
+ {
+ $form = new UserBackendConfigForm();
+ $form->setIniConfig($this->config);
+ return $form;
+ }
+}
diff --git a/application/forms/Config/UserGroup/AddMemberForm.php b/application/forms/Config/UserGroup/AddMemberForm.php
new file mode 100644
index 0000000..debb9b7
--- /dev/null
+++ b/application/forms/Config/UserGroup/AddMemberForm.php
@@ -0,0 +1,182 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserGroup;
+
+use Exception;
+use Icinga\Data\Extensible;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Selectable;
+use Icinga\Exception\NotFoundError;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form for adding one or more group members
+ */
+class AddMemberForm extends Form
+{
+ /**
+ * The data source to fetch users from
+ *
+ * @var Selectable
+ */
+ protected $ds;
+
+ /**
+ * The user group backend to use
+ *
+ * @var Extensible
+ */
+ protected $backend;
+
+ /**
+ * The group to add members for
+ *
+ * @var string
+ */
+ protected $groupName;
+
+ /**
+ * Set the data source to fetch users from
+ *
+ * @param Selectable $ds
+ *
+ * @return $this
+ */
+ public function setDataSource(Selectable $ds)
+ {
+ $this->ds = $ds;
+ return $this;
+ }
+
+ /**
+ * Set the user group backend to use
+ *
+ * @param Extensible $backend
+ *
+ * @return $this
+ */
+ public function setBackend(Extensible $backend)
+ {
+ $this->backend = $backend;
+ return $this;
+ }
+
+ /**
+ * Set the group to add members for
+ *
+ * @param string $groupName
+ *
+ * @return $this
+ */
+ public function setGroupName($groupName)
+ {
+ $this->groupName = $groupName;
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData The data sent by the user
+ */
+ public function createElements(array $formData)
+ {
+ // TODO(jom): Fetching already existing members to prevent the user from mistakenly creating duplicate
+ // memberships (no matter whether the data source permits it or not, a member does never need to be
+ // added more than once) should be kept at backend level (GroupController::fetchUsers) but this does
+ // not work currently as our ldap protocol stuff is unable to handle our filter implementation..
+ $members = $this->backend
+ ->select()
+ ->from('group_membership', array('user_name'))
+ ->where('group_name', $this->groupName)
+ ->fetchColumn();
+ $filter = empty($members) ? Filter::matchAll() : Filter::not(Filter::where('user_name', $members));
+
+ $users = $this->ds->select()->from('user', array('user_name'))->applyFilter($filter)->fetchColumn();
+ if (! empty($users)) {
+ $this->addElement(
+ 'multiselect',
+ 'user_name',
+ array(
+ 'multiOptions' => array_combine($users, $users),
+ 'label' => $this->translate('Backend Users'),
+ 'description' => $this->translate(
+ 'Select one or more users (fetched from your user backends) to add as group member'
+ ),
+ 'class' => 'grant-permissions'
+ )
+ );
+ }
+
+ $this->addElement(
+ 'textarea',
+ 'users',
+ array(
+ 'required' => empty($users),
+ 'label' => $this->translate('Users'),
+ 'description' => $this->translate(
+ 'Provide one or more usernames separated by comma to add as group member'
+ )
+ )
+ );
+
+ $this->setTitle(sprintf($this->translate('Add members for group %s'), $this->groupName));
+ $this->setSubmitLabel($this->translate('Add'));
+ }
+
+ /**
+ * Insert the members for the group
+ *
+ * @return bool
+ */
+ public function onSuccess()
+ {
+ $userNames = $this->getValue('user_name') ?: array();
+ if (($users = $this->getValue('users'))) {
+ $userNames = array_merge($userNames, array_map('trim', explode(',', $users)));
+ }
+
+ if (empty($userNames)) {
+ $this->info($this->translate(
+ 'Please provide at least one username, either by choosing one '
+ . 'in the list or by manually typing one in the text box below'
+ ));
+ return false;
+ }
+
+ $single = null;
+ foreach ($userNames as $userName) {
+ try {
+ $this->backend->insert(
+ 'group_membership',
+ array(
+ 'group_name' => $this->groupName,
+ 'user_name' => $userName
+ )
+ );
+ } catch (NotFoundError $e) {
+ throw $e; // Trigger 404, the group name is initially accessed as GET parameter
+ } catch (Exception $e) {
+ Notification::error(sprintf(
+ $this->translate('Failed to add "%s" as group member for "%s"'),
+ $userName,
+ $this->groupName
+ ));
+ $this->error($e->getMessage());
+ return false;
+ }
+
+ $single = $single === null;
+ }
+
+ if ($single) {
+ Notification::success(sprintf($this->translate('Group member "%s" added successfully'), $userName));
+ } else {
+ Notification::success($this->translate('Group members added successfully'));
+ }
+
+ return true;
+ }
+}
diff --git a/application/forms/Config/UserGroup/DbUserGroupBackendForm.php b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php
new file mode 100644
index 0000000..daea8de
--- /dev/null
+++ b/application/forms/Config/UserGroup/DbUserGroupBackendForm.php
@@ -0,0 +1,79 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserGroup;
+
+use Icinga\Data\ResourceFactory;
+use Icinga\Web\Form;
+
+/**
+ * Form for managing database user group backends
+ */
+class DbUserGroupBackendForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_dbusergroupbackend');
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this user group backend that is used to differentiate it from others'
+ )
+ )
+ );
+
+ $resourceNames = $this->getDatabaseResourceNames();
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Database Connection'),
+ 'description' => $this->translate('The database connection to use for this backend'),
+ 'multiOptions' => empty($resourceNames) ? array() : array_combine($resourceNames, $resourceNames)
+ )
+ );
+
+ $this->addElement(
+ 'hidden',
+ 'backend',
+ array(
+ 'disabled' => true, // Prevents the element from being submitted, see #7717
+ 'value' => 'db'
+ )
+ );
+ }
+
+ /**
+ * Return the names of all configured database resources
+ *
+ * @return array
+ */
+ protected function getDatabaseResourceNames()
+ {
+ $names = array();
+ foreach (ResourceFactory::getResourceConfigs() as $name => $config) {
+ if (strtolower($config->type) === 'db') {
+ $names[] = $name;
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php
new file mode 100644
index 0000000..10c069a
--- /dev/null
+++ b/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php
@@ -0,0 +1,370 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserGroup;
+
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Authentication\UserGroup\LdapUserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Protocol\Ldap\LdapConnection;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form for managing LDAP user group backends
+ */
+class LdapUserGroupBackendForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_ldapusergroupbackend');
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this user group backend that is used to differentiate it from others'
+ )
+ )
+ );
+
+ $resourceNames = $this->getLdapResourceNames();
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('LDAP Connection'),
+ 'description' => $this->translate('The LDAP connection to use for this backend.'),
+ 'multiOptions' => array_combine($resourceNames, $resourceNames)
+ )
+ );
+ $resource = ResourceFactory::create(
+ isset($formData['resource']) && in_array($formData['resource'], $resourceNames)
+ ? $formData['resource']
+ : $resourceNames[0]
+ );
+
+ $userBackendNames = $this->getLdapUserBackendNames($resource);
+ if (! empty($userBackendNames)) {
+ $userBackends = array_combine($userBackendNames, $userBackendNames);
+ $userBackends['none'] = $this->translate('None', 'usergroupbackend.ldap.user_backend');
+ } else {
+ $userBackends = array('none' => $this->translate('None', 'usergroupbackend.ldap.user_backend'));
+ }
+ $this->addElement(
+ 'select',
+ 'user_backend',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('User Backend'),
+ 'description' => $this->translate('The user backend to link with this user group backend.'),
+ 'multiOptions' => $userBackends
+ )
+ );
+
+ $groupBackend = new LdapUserGroupBackend($resource);
+ if ($formData['type'] === 'ldap') {
+ $defaults = $groupBackend->getOpenLdapDefaults();
+ $groupConfigDisabled = $userConfigDisabled = null; // MUST BE null, do NOT change this to false!
+ } else { // $formData['type'] === 'msldap'
+ $defaults = $groupBackend->getActiveDirectoryDefaults();
+ $groupConfigDisabled = $userConfigDisabled = true;
+ }
+
+ if ($formData['type'] === 'msldap') {
+ $this->addElement(
+ 'checkbox',
+ 'nested_group_search',
+ array(
+ 'description' => $this->translate(
+ 'Check this box for nested group search in Active Directory based on the user'
+ ),
+ 'label' => $this->translate('Nested Group Search')
+ )
+ );
+ } else {
+ // This is required to purge already present options
+ $this->addElement('hidden', 'nested_group_search', array('disabled' => true));
+ }
+
+ $this->createGroupConfigElements($defaults, $groupConfigDisabled);
+ if (count($userBackends) === 1 || (isset($formData['user_backend']) && $formData['user_backend'] === 'none')) {
+ $this->createUserConfigElements($defaults, $userConfigDisabled);
+ } else {
+ $this->createHiddenUserConfigElements();
+ }
+
+ $this->addElement(
+ 'hidden',
+ 'backend',
+ array(
+ 'disabled' => true, // Prevents the element from being submitted, see #7717
+ 'value' => $formData['type']
+ )
+ );
+ }
+
+ /**
+ * Create and add all elements to this form required for the group configuration
+ *
+ * @param ConfigObject $defaults
+ * @param null|bool $disabled
+ */
+ protected function createGroupConfigElements(ConfigObject $defaults, $disabled)
+ {
+ $this->addElement(
+ 'text',
+ 'group_class',
+ array(
+ 'preserveDefault' => true,
+ 'ignore' => $disabled,
+ 'disabled' => $disabled,
+ 'label' => $this->translate('LDAP Group Object Class'),
+ 'description' => $this->translate('The object class used for storing groups on the LDAP server.'),
+ 'value' => $defaults->group_class
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'group_filter',
+ array(
+ 'preserveDefault' => true,
+ 'allowEmpty' => true,
+ 'label' => $this->translate('LDAP Group Filter'),
+ 'description' => $this->translate(
+ 'An additional filter to use when looking up groups using the specified connection. '
+ . 'Leave empty to not to use any additional filter rules.'
+ ),
+ 'requirement' => $this->translate(
+ 'The filter needs to be expressed as standard LDAP expression, without'
+ . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)'
+ ),
+ 'validators' => array(
+ array(
+ 'Callback',
+ false,
+ array(
+ 'callback' => function ($v) {
+ return strpos($v, '(') !== 0;
+ },
+ 'messages' => array(
+ 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.')
+ )
+ )
+ )
+ ),
+ 'value' => $defaults->group_filter
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'group_name_attribute',
+ array(
+ 'preserveDefault' => true,
+ 'ignore' => $disabled,
+ 'disabled' => $disabled,
+ 'label' => $this->translate('LDAP Group Name Attribute'),
+ 'description' => $this->translate(
+ 'The attribute name used for storing a group\'s name on the LDAP server.'
+ ),
+ 'value' => $defaults->group_name_attribute
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'group_member_attribute',
+ array(
+ 'preserveDefault' => true,
+ 'ignore' => $disabled,
+ 'disabled' => $disabled,
+ 'label' => $this->translate('LDAP Group Member Attribute'),
+ 'description' => $this->translate('The attribute name used for storing a group\'s members.'),
+ 'value' => $defaults->group_member_attribute
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'base_dn',
+ array(
+ 'preserveDefault' => true,
+ 'label' => $this->translate('LDAP Group Base DN'),
+ 'description' => $this->translate(
+ 'The path where groups can be found on the LDAP server. Leave ' .
+ 'empty to select all users available using the specified connection.'
+ ),
+ 'value' => $defaults->base_dn
+ )
+ );
+ }
+
+ /**
+ * Create and add all elements to this form required for the user configuration
+ *
+ * @param ConfigObject $defaults
+ * @param null|bool $disabled
+ */
+ protected function createUserConfigElements(ConfigObject $defaults, $disabled)
+ {
+ $this->addElement(
+ 'text',
+ 'user_class',
+ array(
+ 'preserveDefault' => true,
+ 'ignore' => $disabled,
+ 'disabled' => $disabled,
+ 'label' => $this->translate('LDAP User Object Class'),
+ 'description' => $this->translate('The object class used for storing users on the LDAP server.'),
+ 'value' => $defaults->user_class
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'user_filter',
+ array(
+ 'preserveDefault' => true,
+ 'allowEmpty' => true,
+ 'label' => $this->translate('LDAP User Filter'),
+ 'description' => $this->translate(
+ 'An additional filter to use when looking up users using the specified connection. '
+ . 'Leave empty to not to use any additional filter rules.'
+ ),
+ 'requirement' => $this->translate(
+ 'The filter needs to be expressed as standard LDAP expression, without'
+ . ' outer parentheses. (e.g. &(foo=bar)(bar=foo) or foo=bar)'
+ ),
+ 'validators' => array(
+ array(
+ 'Callback',
+ false,
+ array(
+ 'callback' => function ($v) {
+ return strpos($v, '(') !== 0;
+ },
+ 'messages' => array(
+ 'callbackValue' => $this->translate('The filter must not be wrapped in parantheses.')
+ )
+ )
+ )
+ ),
+ 'value' => $defaults->user_filter
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'user_name_attribute',
+ array(
+ 'preserveDefault' => true,
+ 'ignore' => $disabled,
+ 'disabled' => $disabled,
+ 'label' => $this->translate('LDAP User Name Attribute'),
+ 'description' => $this->translate(
+ 'The attribute name used for storing a user\'s name on the LDAP server.'
+ ),
+ 'value' => $defaults->user_name_attribute
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'user_base_dn',
+ array(
+ 'preserveDefault' => true,
+ 'label' => $this->translate('LDAP User Base DN'),
+ 'description' => $this->translate(
+ 'The path where users can be found on the LDAP server. Leave ' .
+ 'empty to select all users available using the specified connection.'
+ ),
+ 'value' => $defaults->user_base_dn
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'domain',
+ array(
+ 'label' => $this->translate('Domain'),
+ 'description' => $this->translate(
+ 'The domain the LDAP server is responsible for.'
+ )
+ )
+ );
+ }
+
+ /**
+ * Create and add all elements for the user configuration as hidden inputs
+ *
+ * This is required to purge already present options when unlinking a group backend with a user backend.
+ */
+ protected function createHiddenUserConfigElements()
+ {
+ $this->addElement('hidden', 'user_class', array('disabled' => true));
+ $this->addElement('hidden', 'user_filter', array('disabled' => true));
+ $this->addElement('hidden', 'user_name_attribute', array('disabled' => true));
+ $this->addElement('hidden', 'user_base_dn', array('disabled' => true));
+ $this->addElement('hidden', 'domain', array('disabled' => true));
+ }
+
+ /**
+ * Return the names of all configured LDAP resources
+ *
+ * @return array
+ */
+ protected function getLdapResourceNames()
+ {
+ $names = array();
+ foreach (ResourceFactory::getResourceConfigs() as $name => $config) {
+ if (in_array(strtolower($config->type), array('ldap', 'msldap'))) {
+ $names[] = $name;
+ }
+ }
+
+ if (empty($names)) {
+ Notification::error(
+ $this->translate('No LDAP resources available. Please configure an LDAP resource first.')
+ );
+ $this->getResponse()->redirectAndExit('config/createresource');
+ }
+
+ return $names;
+ }
+
+ /**
+ * Return the names of all configured LDAP user backends
+ *
+ * @param LdapConnection $resource
+ *
+ * @return array
+ */
+ protected function getLdapUserBackendNames(LdapConnection $resource)
+ {
+ $names = array();
+ foreach (UserBackend::getBackendConfigs() as $name => $config) {
+ if (in_array(strtolower($config->backend), array('ldap', 'msldap'))) {
+ $backendResource = ResourceFactory::create($config->resource);
+ if ($backendResource->getHostname() === $resource->getHostname()
+ && $backendResource->getPort() === $resource->getPort()
+ ) {
+ $names[] = $name;
+ }
+ }
+ }
+
+ return $names;
+ }
+}
diff --git a/application/forms/Config/UserGroup/UserGroupBackendForm.php b/application/forms/Config/UserGroup/UserGroupBackendForm.php
new file mode 100644
index 0000000..9ee4032
--- /dev/null
+++ b/application/forms/Config/UserGroup/UserGroupBackendForm.php
@@ -0,0 +1,314 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserGroup;
+
+use Icinga\Authentication\UserGroup\UserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Web\Form;
+use InvalidArgumentException;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfigForm;
+
+/**
+ * Form for managing user group backends
+ */
+class UserGroupBackendForm extends ConfigForm
+{
+ protected $validatePartial = true;
+
+ /**
+ * The backend to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $backendToLoad;
+
+ /**
+ * Known custom backends
+ *
+ * @var array
+ */
+ protected $customBackends;
+
+ /**
+ * Create a user group backend by using the given form's values and return its inspection results
+ *
+ * Returns null for non-inspectable backends.
+ *
+ * @param Form $form
+ *
+ * @return Inspection|null
+ */
+ public static function inspectUserBackend(Form $form)
+ {
+ $backend = UserGroupBackend::create(null, new ConfigObject($form->getValues()));
+ if ($backend instanceof Inspectable) {
+ return $backend->inspect();
+ }
+ }
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_usergroupbackend');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ $this->customBackends = UserGroupBackend::getCustomBackendConfigForms();
+ }
+
+ /**
+ * Return a form object for the given backend type
+ *
+ * @param string $type The backend type for which to return a form
+ *
+ * @return Form
+ *
+ * @throws InvalidArgumentException In case the given backend type is invalid
+ */
+ public function getBackendForm($type)
+ {
+ switch ($type) {
+ case 'db':
+ return new DbUserGroupBackendForm();
+ case 'ldap':
+ case 'msldap':
+ return new LdapUserGroupBackendForm();
+ default:
+ if (isset($this->customBackends[$type])) {
+ return new $this->customBackends[$type]();
+ }
+
+ throw new InvalidArgumentException(
+ sprintf($this->translate('Invalid backend type "%s" provided'), $type)
+ );
+ }
+ }
+
+ /**
+ * Populate the form with the given backend's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function load($name)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No user group backend called "%s" found', $name);
+ }
+
+ $this->backendToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new user group backend
+ *
+ * The backend to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a backend name
+ * @throws IcingaException In case a backend with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ }
+
+ $backendName = $data['name'];
+ if ($this->config->hasSection($backendName)) {
+ throw new IcingaException('A user group backend with the name "%s" does already exist', $backendName);
+ }
+
+ unset($data['name']);
+ $this->config->setSection($backendName, $data);
+ return $this;
+ }
+
+ /**
+ * Edit a user group backend
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function edit($name, array $data)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No user group backend called "%s" found', $name);
+ }
+
+ $backendConfig = $this->config->getSection($name);
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $this->config->removeSection($name);
+ $name = $data['name'];
+ }
+
+ unset($data['name']);
+ }
+
+ $this->config->setSection($name, $backendConfig->merge($data));
+ return $this;
+ }
+
+ /**
+ * Remove a user group backend
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function delete($name)
+ {
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $backendTypes = array(
+ 'db' => $this->translate('Database'),
+ 'ldap' => 'LDAP',
+ 'msldap' => 'ActiveDirectory'
+ );
+
+ $customBackendTypes = array_keys($this->customBackends);
+ $backendTypes += array_combine($customBackendTypes, $customBackendTypes);
+
+ $backendType = isset($formData['type']) ? $formData['type'] : null;
+ if ($backendType === null) {
+ $backendType = key($backendTypes);
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'ignore' => true,
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate('The type of this user group backend'),
+ 'multiOptions' => $backendTypes
+ )
+ );
+
+ $this->addSubForm($this->getBackendForm($backendType)->create($formData), 'backend_form');
+ }
+
+ /**
+ * Populate the configuration of the backend to load
+ */
+ public function onRequest()
+ {
+ if ($this->backendToLoad) {
+ $data = $this->config->getSection($this->backendToLoad)->toArray();
+ $data['type'] = $data['backend'];
+ $data['name'] = $this->backendToLoad;
+ $this->populate($data);
+ }
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['backend_validation']) && parent::isValid($formData)) {
+ $inspection = static::inspectUserBackend($this);
+ if ($inspection !== null) {
+ $join = function ($e) use (&$join) {
+ return is_array($e) ? join("\n", array_map($join, $e)) : $e;
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+
+ if ($inspection->hasError()) {
+ $this->warning(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a submit button to this form and one to manually validate the configuration
+ *
+ * Calls parent::addSubmitButton() to add the submit button.
+ *
+ * @return $this
+ */
+ public function addSubmitButton()
+ {
+ parent::addSubmitButton()
+ ->getElement('btn_submit')
+ ->setDecorators(array('ViewHelper'));
+
+ $this->addElement(
+ 'submit',
+ 'backend_validation',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Validate Configuration'),
+ 'data-progress-label' => $this->translate('Validation In Progress'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ $this->addDisplayGroup(
+ array('btn_submit', 'backend_validation'),
+ 'submit_validation',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Config/UserGroup/UserGroupForm.php b/application/forms/Config/UserGroup/UserGroupForm.php
new file mode 100644
index 0000000..b944e97
--- /dev/null
+++ b/application/forms/Config/UserGroup/UserGroupForm.php
@@ -0,0 +1,158 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Config\UserGroup;
+
+use Icinga\Application\Hook\ConfigFormEventsHook;
+use Icinga\Data\Filter\Filter;
+use Icinga\Forms\RepositoryForm;
+use Icinga\Web\Notification;
+
+class UserGroupForm extends RepositoryForm
+{
+ /**
+ * Create and add elements to this form to insert or update a group
+ *
+ * @param array $formData The data sent by the user
+ */
+ protected function createInsertElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'group_name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Group Name')
+ )
+ );
+
+ if ($this->shouldInsert()) {
+ $this->setTitle($this->translate('Add a new group'));
+ $this->setSubmitLabel($this->translate('Add'));
+ } else { // $this->shouldUpdate()
+ $this->setTitle(sprintf($this->translate('Edit group %s'), $this->getIdentifier()));
+ $this->setSubmitLabel($this->translate('Save'));
+ }
+ }
+
+ /**
+ * Update a group
+ *
+ * @return bool
+ */
+ protected function onUpdateSuccess()
+ {
+ if (parent::onUpdateSuccess()) {
+ if (($newName = $this->getValue('group_name')) !== $this->getIdentifier()) {
+ $this->getRedirectUrl()->setParam('group', $newName);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Create and add elements to this form to delete a group
+ *
+ * @param array $formData The data sent by the user
+ */
+ protected function createDeleteElements(array $formData)
+ {
+ $this->setTitle(sprintf($this->translate('Remove group %s?'), $this->getIdentifier()));
+ $this->addDescription($this->translate(
+ 'Note that all users that are currently a member of this group will'
+ . ' have their membership cleared automatically.'
+ ));
+ $this->setSubmitLabel($this->translate('Yes'));
+ $this->setAttrib('class', 'icinga-form icinga-controls');
+ }
+
+ /**
+ * Create and return a filter to use when updating or deleting a group
+ *
+ * @return Filter
+ */
+ protected function createFilter()
+ {
+ return Filter::where('group_name', $this->getIdentifier());
+ }
+
+ /**
+ * Return a notification message to use when inserting a group
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ protected function getInsertMessage($success)
+ {
+ if ($success) {
+ return $this->translate('Group added successfully');
+ } else {
+ return $this->translate('Failed to add group');
+ }
+ }
+
+ /**
+ * Return a notification message to use when updating a group
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ protected function getUpdateMessage($success)
+ {
+ if ($success) {
+ return sprintf($this->translate('Group "%s" has been edited'), $this->getIdentifier());
+ } else {
+ return sprintf($this->translate('Failed to edit group "%s"'), $this->getIdentifier());
+ }
+ }
+
+ /**
+ * Return a notification message to use when deleting a group
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ protected function getDeleteMessage($success)
+ {
+ if ($success) {
+ return sprintf($this->translate('Group "%s" has been removed'), $this->getIdentifier());
+ } else {
+ return sprintf($this->translate('Failed to remove group "%s"'), $this->getIdentifier());
+ }
+ }
+
+ public function isValid($formData)
+ {
+ $valid = parent::isValid($formData);
+
+ if ($valid && ConfigFormEventsHook::runIsValid($this) === false) {
+ foreach (ConfigFormEventsHook::getLastErrors() as $msg) {
+ $this->error($msg);
+ }
+
+ $valid = false;
+ }
+
+ return $valid;
+ }
+
+ public function onSuccess()
+ {
+ if (parent::onSuccess() === false) {
+ return false;
+ }
+
+ if (ConfigFormEventsHook::runOnSuccess($this) === false) {
+ Notification::error($this->translate(
+ 'Configuration successfully stored. Though, one or more module hooks failed to run.'
+ . ' See logs for details'
+ ));
+ }
+ }
+}
diff --git a/application/forms/ConfigForm.php b/application/forms/ConfigForm.php
new file mode 100644
index 0000000..8b0c5f9
--- /dev/null
+++ b/application/forms/ConfigForm.php
@@ -0,0 +1,192 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Exception;
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Config;
+use Icinga\Application\Hook\ConfigFormEventsHook;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form base-class providing standard functionality for configuration forms
+ */
+class ConfigForm extends Form
+{
+ /**
+ * The configuration to work with
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * {@inheritdoc}
+ *
+ * Values from subforms are directly added to the returned values array instead of being grouped by the subforms'
+ * names.
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+ foreach (array_keys($this->_subForms) as $name) {
+ // Zend returns values from subforms grouped by their names, but we want them flat
+ $values = array_merge($values, $values[$name]);
+ unset($values[$name]);
+ }
+ return $values;
+ }
+
+ /**
+ * Set the configuration to use when populating the form or when saving the user's input
+ *
+ * @param Config $config The configuration to use
+ *
+ * @return $this
+ */
+ public function setIniConfig(Config $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ public function isValid($formData)
+ {
+ $valid = parent::isValid($formData);
+
+ if ($valid && ConfigFormEventsHook::runIsValid($this) === false) {
+ foreach (ConfigFormEventsHook::getLastErrors() as $msg) {
+ $this->error($msg);
+ }
+
+ $valid = false;
+ }
+
+ return $valid;
+ }
+
+ public function onSuccess()
+ {
+ $sections = array();
+ foreach (static::transformEmptyValuesToNull($this->getValues()) as $sectionAndPropertyName => $value) {
+ list($section, $property) = explode('_', $sectionAndPropertyName, 2);
+ $sections[$section][$property] = $value;
+ }
+
+ foreach ($sections as $section => $config) {
+ if ($this->isEmptyConfig($config)) {
+ $this->config->removeSection($section);
+ } else {
+ $this->config->setSection($section, $config);
+ }
+ }
+
+ if ($this->save()) {
+ Notification::success($this->translate('New configuration has successfully been stored'));
+ } else {
+ return false;
+ }
+
+ if (ConfigFormEventsHook::runOnSuccess($this) === false) {
+ Notification::error($this->translate(
+ 'Configuration successfully stored. Though, one or more module hooks failed to run.'
+ . ' See logs for details'
+ ));
+ }
+ }
+
+ public function onRequest()
+ {
+ $values = array();
+ foreach ($this->config as $section => $properties) {
+ foreach ($properties as $name => $value) {
+ $values[$section . '_' . $name] = $value;
+ }
+ }
+
+ $this->populate($values);
+ }
+
+ /**
+ * Persist the current configuration to disk
+ *
+ * If an error occurs the user is shown a view describing the issue and displaying the raw INI configuration.
+ *
+ * @return bool Whether the configuration could be persisted
+ */
+ public function save()
+ {
+ try {
+ $this->writeConfig($this->config);
+ } catch (ConfigurationError $e) {
+ $this->addError($e->getMessage());
+
+ return false;
+ } catch (Exception $e) {
+ $this->addDecorator('ViewScript', array(
+ 'viewModule' => 'default',
+ 'viewScript' => 'showConfiguration.phtml',
+ 'errorMessage' => $e->getMessage(),
+ 'configString' => $this->config,
+ 'filePath' => $this->config->getConfigFile(),
+ 'placement' => Zend_Form_Decorator_Abstract::PREPEND
+ ));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Write the configuration to disk
+ *
+ * @param Config $config
+ */
+ protected function writeConfig(Config $config)
+ {
+ $config->saveIni();
+ }
+
+ /**
+ * Get whether the given config is empty or has only empty values
+ *
+ * @param array|Config $config
+ *
+ * @return bool
+ */
+ protected function isEmptyConfig($config)
+ {
+ if ($config instanceof Config) {
+ $config = $config->toArray();
+ }
+
+ foreach ($config as $value) {
+ if ($value !== null) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Transform all empty values of the given array to null
+ *
+ * @param array $values
+ *
+ * @return array
+ */
+ public static function transformEmptyValuesToNull(array $values)
+ {
+ array_walk($values, function (&$v) {
+ if ($v === '' || $v === false || $v === array()) {
+ $v = null;
+ }
+ });
+
+ return $values;
+ }
+}
diff --git a/application/forms/ConfirmRemovalForm.php b/application/forms/ConfirmRemovalForm.php
new file mode 100644
index 0000000..39fc661
--- /dev/null
+++ b/application/forms/ConfirmRemovalForm.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Icinga\Web\Form;
+
+/**
+ * Form for confirming removal of an object
+ */
+class ConfirmRemovalForm extends Form
+{
+ const DEFAULT_CLASSES = 'icinga-controls';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_confirm_removal');
+ $this->getSubmitLabel() ?: $this->setSubmitLabel($this->translate('Confirm Removal'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ parent::addSubmitButton();
+
+ if (($submit = $this->getElement('btn_submit')) !== null) {
+ $class = $submit->getAttrib('class');
+ $submit->setAttrib('class', empty($class) ? 'autofocus' : $class . ' autofocus');
+ }
+
+ return $this;
+ }
+}
diff --git a/application/forms/Control/LimiterControlForm.php b/application/forms/Control/LimiterControlForm.php
new file mode 100644
index 0000000..88adf4b
--- /dev/null
+++ b/application/forms/Control/LimiterControlForm.php
@@ -0,0 +1,134 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Control;
+
+use Icinga\Web\Form;
+
+/**
+ * Limiter control form
+ */
+class LimiterControlForm extends Form
+{
+ /**
+ * CSS class for the limiter control
+ *
+ * @var string
+ */
+ const CSS_CLASS_LIMITER = 'limiter-control icinga-controls inline';
+
+ /**
+ * Default limit
+ *
+ * @var int
+ */
+ const DEFAULT_LIMIT = 50;
+
+ /**
+ * Selectable default limits
+ *
+ * @var int[]
+ */
+ public static $limits = array(
+ 10 => '10',
+ 25 => '25',
+ 50 => '50',
+ 100 => '100',
+ 500 => '500'
+ );
+
+ public static $defaultElementDecorators = [
+ ['Label', ['tag' => 'span', 'separator' => '']],
+ ['ViewHelper', ['separator' => '']],
+ ];
+
+ /**
+ * Default limit for this instance
+ *
+ * @var int|null
+ */
+ protected $defaultLimit;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', static::CSS_CLASS_LIMITER);
+ }
+
+ /**
+ * Get the default limit
+ *
+ * @return int
+ */
+ public function getDefaultLimit()
+ {
+ return $this->defaultLimit !== null ? $this->defaultLimit : static::DEFAULT_LIMIT;
+ }
+
+ /**
+ * Set the default limit
+ *
+ * @param int $defaultLimit
+ *
+ * @return $this
+ */
+ public function setDefaultLimit($defaultLimit)
+ {
+ $defaultLimit = (int) $defaultLimit;
+
+ if (! isset(static::$limits[$defaultLimit])) {
+ static::$limits[$defaultLimit] = $defaultLimit;
+ }
+
+ $this->defaultLimit = $defaultLimit;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRedirectUrl()
+ {
+ return $this->getRequest()->getUrl()
+ ->setParam('limit', $this->getElement('limit')->getValue())
+ ->without('page');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $options = static::$limits;
+ $pageSize = (int) $this->getRequest()->getUrl()->getParam('limit', $this->getDefaultLimit());
+
+ if (! isset($options[$pageSize])) {
+ $options[$pageSize] = $pageSize;
+ }
+
+ $this->addElement(
+ 'select',
+ 'limit',
+ array(
+ 'autosubmit' => true,
+ 'escape' => false,
+ 'label' => '#',
+ 'multiOptions' => $options,
+ 'value' => $pageSize
+ )
+ );
+ }
+
+ /**
+ * Limiter control is always successful
+ *
+ * @return bool
+ */
+ public function onSuccess()
+ {
+ return true;
+ }
+}
diff --git a/application/forms/Dashboard/DashletForm.php b/application/forms/Dashboard/DashletForm.php
new file mode 100644
index 0000000..1af65a9
--- /dev/null
+++ b/application/forms/Dashboard/DashletForm.php
@@ -0,0 +1,171 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Dashboard;
+
+use Icinga\Web\Form;
+use Icinga\Web\Form\Validator\InternalUrlValidator;
+use Icinga\Web\Form\Validator\UrlValidator;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Dashboard;
+use Icinga\Web\Widget\Dashboard\Dashlet;
+
+/**
+ * Form to add an url a dashboard pane
+ */
+class DashletForm extends Form
+{
+ /**
+ * @var Dashboard
+ */
+ private $dashboard;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_dashboard_addurl');
+ if (! $this->getSubmitLabel()) {
+ $this->setSubmitLabel($this->translate('Add To Dashboard'));
+ }
+ $this->setAction(Url::fromRequest());
+ }
+
+ /**
+ * Build AddUrl form elements
+ *
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $groupElements = array();
+ $panes = array();
+
+ if ($this->dashboard) {
+ $panes = $this->dashboard->getPaneKeyTitleArray();
+ }
+
+ $sectionNameValidator = ['Callback', true, [
+ 'callback' => function ($value) {
+ if (strpos($value, '[') === false && strpos($value, ']') === false) {
+ return true;
+ }
+ },
+ 'messages' => [
+ 'callbackValue' => $this->translate('Brackets ([, ]) cannot be used here')
+ ]
+ ]];
+
+ $this->addElement(
+ 'hidden',
+ 'org_pane',
+ array(
+ 'required' => false
+ )
+ );
+
+ $this->addElement(
+ 'hidden',
+ 'org_dashlet',
+ array(
+ 'required' => false
+ )
+ );
+
+ $this->addElement(
+ 'textarea',
+ 'url',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Url'),
+ 'description' => $this->translate(
+ 'Enter url to be loaded in the dashlet. You can paste the full URL, including filters.'
+ ),
+ 'validators' => array(new UrlValidator(), new InternalUrlValidator())
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'dashlet',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Dashlet Title'),
+ 'description' => $this->translate('Enter a title for the dashlet.'),
+ 'validators' => [$sectionNameValidator]
+ )
+ );
+ $this->addElement(
+ 'note',
+ 'note',
+ array(
+ 'decorators' => array(
+ array('HtmlTag', array('tag' => 'hr'))
+ )
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'create_new_pane',
+ array(
+ 'autosubmit' => true,
+ 'required' => false,
+ 'label' => $this->translate('New dashboard'),
+ 'description' => $this->translate('Check this box if you want to add the dashlet to a new dashboard')
+ )
+ );
+ if (empty($panes) || ((isset($formData['create_new_pane']) && $formData['create_new_pane'] != false))) {
+ $this->addElement(
+ 'text',
+ 'pane',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('New Dashboard Title'),
+ 'description' => $this->translate('Enter a title for the new dashboard'),
+ 'validators' => [$sectionNameValidator]
+ )
+ );
+ } else {
+ $this->addElement(
+ 'select',
+ 'pane',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Dashboard'),
+ 'multiOptions' => $panes,
+ 'description' => $this->translate('Select a dashboard you want to add the dashlet to')
+ )
+ );
+ }
+ }
+
+ /**
+ * @param \Icinga\Web\Widget\Dashboard $dashboard
+ */
+ public function setDashboard(Dashboard $dashboard)
+ {
+ $this->dashboard = $dashboard;
+ }
+
+ /**
+ * @return \Icinga\Web\Widget\Dashboard
+ */
+ public function getDashboard()
+ {
+ return $this->dashboard;
+ }
+
+ /**
+ * @param Dashlet $dashlet
+ */
+ public function load(Dashlet $dashlet)
+ {
+ $this->populate(array(
+ 'pane' => $dashlet->getPane()->getTitle(),
+ 'org_pane' => $dashlet->getPane()->getName(),
+ 'dashlet' => $dashlet->getTitle(),
+ 'org_dashlet' => $dashlet->getName(),
+ 'url' => $dashlet->getUrl()->getRelativeUrl()
+ ));
+ }
+}
diff --git a/application/forms/LdapDiscoveryForm.php b/application/forms/LdapDiscoveryForm.php
new file mode 100644
index 0000000..5c7fc87
--- /dev/null
+++ b/application/forms/LdapDiscoveryForm.php
@@ -0,0 +1,34 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Icinga\Web\Form;
+
+class LdapDiscoveryForm extends Form
+{
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('form_ldap_discovery');
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'domain',
+ array(
+ 'label' => $this->translate('Search Domain'),
+ 'description' => $this->translate('Search this domain for records of available servers.'),
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/application/forms/Navigation/DashletForm.php b/application/forms/Navigation/DashletForm.php
new file mode 100644
index 0000000..6575fd7
--- /dev/null
+++ b/application/forms/Navigation/DashletForm.php
@@ -0,0 +1,35 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Navigation;
+
+class DashletForm extends NavigationItemForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'pane',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Pane'),
+ 'description' => $this->translate('The name of the dashboard pane in which to display this dashlet')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'url',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Url'),
+ 'description' => $this->translate(
+ 'The url to load in the dashlet. For external urls, make sure to prepend'
+ . ' an appropriate protocol identifier (e.g. http://example.tld)'
+ )
+ )
+ );
+ }
+}
diff --git a/application/forms/Navigation/MenuItemForm.php b/application/forms/Navigation/MenuItemForm.php
new file mode 100644
index 0000000..c9fa729
--- /dev/null
+++ b/application/forms/Navigation/MenuItemForm.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Navigation;
+
+class MenuItemForm extends NavigationItemForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $requiresParentSelection = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ parent::createElements($formData);
+
+ // Remove _self and _next as for menu entries only _main is valid
+ $this->getElement('target')->removeMultiOption('_self');
+ $this->getElement('target')->removeMultiOption('_next');
+
+ $parentElement = $this->getParent()->getElement('parent');
+ if ($parentElement !== null) {
+ $parentElement->setDescription($this->translate(
+ 'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry'
+ ));
+ }
+ }
+}
diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php
new file mode 100644
index 0000000..e9fecc8
--- /dev/null
+++ b/application/forms/Navigation/NavigationConfigForm.php
@@ -0,0 +1,852 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Navigation;
+
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Application\Icinga;
+use Icinga\Authentication\Auth;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Forms\ConfigForm;
+use Icinga\User;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Form;
+
+/**
+ * Form for managing navigation items
+ */
+class NavigationConfigForm extends ConfigForm
+{
+ /**
+ * The class namespace where to locate navigation type forms
+ *
+ * @var string
+ */
+ const FORM_NS = 'Forms\\Navigation';
+
+ /**
+ * The secondary configuration to write
+ *
+ * This is always the reduced configuration and is only written to
+ * disk once the main configuration has been successfully written.
+ *
+ * @var Config
+ */
+ protected $secondaryConfig;
+
+ /**
+ * The navigation item to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $itemToLoad;
+
+ /**
+ * The user for whom to manage navigation items
+ *
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * The user's navigation configuration
+ *
+ * @var Config
+ */
+ protected $userConfig;
+
+ /**
+ * The shared navigation configuration
+ *
+ * @var Config
+ */
+ protected $shareConfig;
+
+ /**
+ * The available navigation item types
+ *
+ * @var array
+ */
+ protected $itemTypes;
+
+ private $defaultUrl;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_navigation');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * Set the user for whom to manage navigation items
+ *
+ * @param User $user
+ *
+ * @return $this
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ return $this;
+ }
+
+ /**
+ * Return the user for whom to manage navigation items
+ *
+ * @return User
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Set the user's navigation configuration
+ *
+ * @param Config $config
+ *
+ * @return $this
+ */
+ public function setUserConfig(Config $config)
+ {
+ $config->getConfigObject()->setKeyColumn('name');
+ $this->userConfig = $config;
+ return $this;
+ }
+
+ /**
+ * Return the user's navigation configuration
+ *
+ * @param string $type
+ *
+ * @return Config
+ */
+ public function getUserConfig($type = null)
+ {
+ if ($this->userConfig === null || $type !== null) {
+ if ($type === null) {
+ throw new ProgrammingError('You need to pass a type if no user configuration is set');
+ }
+
+ $this->setUserConfig(Config::navigation($type, $this->getUser()->getUsername()));
+ }
+
+ return $this->userConfig;
+ }
+
+ /**
+ * Set the shared navigation configuration
+ *
+ * @param Config $config
+ *
+ * @return $this
+ */
+ public function setShareConfig(Config $config)
+ {
+ $config->getConfigObject()->setKeyColumn('name');
+ $this->shareConfig = $config;
+ return $this;
+ }
+
+ /**
+ * Return the shared navigation configuration
+ *
+ * @param string $type
+ *
+ * @return Config
+ */
+ public function getShareConfig($type = null)
+ {
+ if ($this->shareConfig === null) {
+ if ($type === null) {
+ throw new ProgrammingError('You need to pass a type if no share configuration is set');
+ }
+
+ $this->setShareConfig(Config::navigation($type));
+ }
+
+ return $this->shareConfig;
+ }
+
+ /**
+ * Set the available navigation item types
+ *
+ * @param array $itemTypes
+ *
+ * @return $this
+ */
+ public function setItemTypes(array $itemTypes)
+ {
+ $this->itemTypes = $itemTypes;
+ return $this;
+ }
+
+ /**
+ * Return the available navigation item types
+ *
+ * @return array
+ */
+ public function getItemTypes()
+ {
+ return $this->itemTypes ?: array();
+ }
+
+ /**
+ * Return a list of available parent items for the given type of navigation item
+ *
+ * @param string $type
+ * @param string $owner
+ *
+ * @return array
+ */
+ public function listAvailableParents($type, $owner = null)
+ {
+ $children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array();
+
+ $names = array();
+ foreach ($this->getShareConfig($type) as $sectionName => $sectionConfig) {
+ if ((string) $sectionName !== $this->itemToLoad
+ && $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername())
+ && ! in_array($sectionName, $children, true)
+ ) {
+ $names[] = $sectionName;
+ }
+ }
+
+ foreach ($this->getUserConfig($type) as $sectionName => $sectionConfig) {
+ if ((string) $sectionName !== $this->itemToLoad
+ && ! in_array($sectionName, $children, true)
+ ) {
+ $names[] = $sectionName;
+ }
+ }
+
+ return $names;
+ }
+
+ /**
+ * Recursively return all children of the given navigation item
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ protected function getFlattenedChildren($name)
+ {
+ $config = $this->getConfigForItem($name);
+ if ($config === null) {
+ return array();
+ }
+
+ $children = array();
+ foreach ($config->toArray() as $sectionName => $sectionConfig) {
+ if (isset($sectionConfig['parent']) && $sectionConfig['parent'] === $name) {
+ $children[] = $sectionName;
+ $children = array_merge($children, $this->getFlattenedChildren($sectionName));
+ }
+ }
+
+ return $children;
+ }
+
+ /**
+ * Populate the form with the given navigation item's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no navigation item with the given name is found
+ */
+ public function load($name)
+ {
+ if ($this->getConfigForItem($name) === null) {
+ throw new NotFoundError('No navigation item called "%s" found', $name);
+ }
+
+ $this->itemToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new navigation item
+ *
+ * The navigation item to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a navigation item name or type
+ * @throws IcingaException In case a navigation item with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ } elseif (! isset($data['type'])) {
+ throw new InvalidArgumentException('Key \'type\' missing');
+ }
+
+ $shared = false;
+ $config = $this->getUserConfig($data['type']);
+ if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) {
+ if ($this->getUser()->can('user/share/navigation')) {
+ $data['owner'] = $this->getUser()->getUsername();
+ $config = $this->getShareConfig($data['type']);
+ $shared = true;
+ } else {
+ unset($data['users']);
+ unset($data['groups']);
+ }
+ } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'], $data['type'])) {
+ $data['owner'] = $this->getUser()->getUsername();
+ $config = $this->getShareConfig($data['type']);
+ $shared = true;
+ }
+
+ $itemName = $data['name'];
+ $exists = $config->hasSection($itemName);
+ if (! $exists) {
+ if ($shared) {
+ $exists = $this->getUserConfig($data['type'])->hasSection($itemName);
+ } else {
+ $exists = (bool) $this->getShareConfig($data['type'])
+ ->select()
+ ->where('name', $itemName)
+ ->where('owner', $this->getUser()->getUsername())
+ ->count();
+ }
+ }
+
+ if ($exists) {
+ throw new IcingaException(
+ $this->translate('A navigation item with the name "%s" does already exist'),
+ $itemName
+ );
+ }
+
+ unset($data['name']);
+ $config->setSection($itemName, $data);
+ $this->setIniConfig($config);
+ return $this;
+ }
+
+ /**
+ * Edit a navigation item
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no navigation item with the given name is found
+ * @throws IcingaException In case a navigation item with the same name already exists
+ */
+ public function edit($name, array $data)
+ {
+ $config = $this->getConfigForItem($name);
+ if ($config === null) {
+ throw new NotFoundError('No navigation item called "%s" found', $name);
+ } else {
+ $itemConfig = $config->getSection($name);
+ }
+
+ $shared = false;
+ if ($this->hasBeenShared($name)) {
+ if (isset($data['parent']) && $data['parent']
+ ? ! $this->hasBeenShared($data['parent'])
+ : ((! isset($data['users']) || ! $data['users']) && (! isset($data['groups']) || ! $data['groups']))
+ ) {
+ // It is shared but shouldn't anymore
+ $config = $this->unshare($name, isset($data['parent']) ? $data['parent'] : null);
+ }
+ } elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) {
+ if ($this->getUser()->can('user/share/navigation')) {
+ // It is not shared yet but should be
+ $this->secondaryConfig = $config;
+ $config = $this->getShareConfig();
+ $data['owner'] = $this->getUser()->getUsername();
+ $shared = true;
+ } else {
+ unset($data['users']);
+ unset($data['groups']);
+ }
+ } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) {
+ // Its parent is shared so should it itself
+ $this->secondaryConfig = $config;
+ $config = $this->getShareConfig();
+ $data['owner'] = $this->getUser()->getUsername();
+ $shared = true;
+ }
+
+ $oldName = null;
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $oldName = $name;
+ $name = $data['name'];
+
+ $exists = $config->hasSection($name);
+ if (! $exists) {
+ $ownerName = $itemConfig->owner ?: $this->getUser()->getUsername();
+ if ($shared || $this->hasBeenShared($oldName)) {
+ if ($ownerName === $this->getUser()->getUsername()) {
+ $exists = $this->getUserConfig()->hasSection($name);
+ } else {
+ $exists = Config::navigation($itemConfig->type, $ownerName)->hasSection($name);
+ }
+ } else {
+ $exists = (bool) $this->getShareConfig()
+ ->select()
+ ->where('name', $name)
+ ->where('owner', $ownerName)
+ ->count();
+ }
+ }
+
+ if ($exists) {
+ throw new IcingaException(
+ $this->translate('A navigation item with the name "%s" does already exist'),
+ $name
+ );
+ }
+ }
+
+ unset($data['name']);
+ }
+
+ $itemConfig->merge($data);
+
+ if ($shared) {
+ // Share all descendant children
+ foreach ($this->getFlattenedChildren($oldName ?: $name) as $child) {
+ $childConfig = $this->secondaryConfig->getSection($child);
+ $this->secondaryConfig->removeSection($child);
+ $childConfig->owner = $this->getUser()->getUsername();
+ $config->setSection($child, $childConfig);
+ }
+ }
+
+ if ($oldName) {
+ // Update the parent name on all direct children
+ foreach ($config as $sectionConfig) {
+ if ($sectionConfig->parent === $oldName) {
+ $sectionConfig->parent = $name;
+ }
+ }
+
+ $config->removeSection($oldName);
+ }
+
+ if ($this->secondaryConfig !== null) {
+ $this->secondaryConfig->removeSection($oldName ?: $name);
+ }
+
+ $config->setSection($name, $itemConfig);
+ $this->setIniConfig($config);
+ return $this;
+ }
+
+ /**
+ * Remove a navigation item
+ *
+ * @param string $name
+ *
+ * @return ConfigObject The navigation item's config
+ *
+ * @throws NotFoundError In case no navigation item with the given name is found
+ * @throws IcingaException In case the navigation item has still children
+ */
+ public function delete($name)
+ {
+ $config = $this->getConfigForItem($name);
+ if ($config === null) {
+ throw new NotFoundError('No navigation item called "%s" found', $name);
+ }
+
+ $children = $this->getFlattenedChildren($name);
+ if (! empty($children)) {
+ throw new IcingaException(
+ $this->translate(
+ 'Unable to delete navigation item "%s". There'
+ . ' are other items dependent from it: %s'
+ ),
+ $name,
+ join(', ', $children)
+ );
+ }
+
+ $section = $config->getSection($name);
+ $config->removeSection($name);
+ $this->setIniConfig($config);
+ return $section;
+ }
+
+ /**
+ * Unshare the given navigation item
+ *
+ * @param string $name
+ * @param string $parent
+ *
+ * @return Config The new config of the given navigation item
+ *
+ * @throws NotFoundError In case no navigation item with the given name is found
+ * @throws IcingaException In case the navigation item has a parent assigned to it
+ */
+ public function unshare($name, $parent = null)
+ {
+ $config = $this->getShareConfig();
+ if (! $config->hasSection($name)) {
+ throw new NotFoundError('No navigation item called "%s" found', $name);
+ }
+
+ $itemConfig = $config->getSection($name);
+ if ($parent === null) {
+ $parent = $itemConfig->parent;
+ }
+
+ if ($parent && $this->hasBeenShared($parent)) {
+ throw new IcingaException(
+ $this->translate(
+ 'Unable to unshare navigation item "%s". It is dependent from item "%s".'
+ . ' Dependent items can only be unshared by unsharing their parent'
+ ),
+ $name,
+ $parent
+ );
+ }
+
+ $children = $this->getFlattenedChildren($name);
+ $config->removeSection($name);
+ $this->secondaryConfig = $config;
+
+ if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) {
+ $config = $this->getUserConfig();
+ } else {
+ $config = Config::navigation($itemConfig->type, $itemConfig->owner);
+ }
+
+ foreach ($children as $child) {
+ $childConfig = $this->secondaryConfig->getSection($child);
+ unset($childConfig->owner);
+ $this->secondaryConfig->removeSection($child);
+ $config->setSection($child, $childConfig);
+ }
+
+ unset($itemConfig->owner);
+ unset($itemConfig->users);
+ unset($itemConfig->groups);
+
+ $config->setSection($name, $itemConfig);
+ $this->setIniConfig($config);
+ return $config;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $shared = false;
+ $itemTypes = $this->getItemTypes();
+ $itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes);
+ if ($itemType === null) {
+ throw new ProgrammingError(
+ 'This should actually not happen. Create a bug report at https://github.com/icinga/icingaweb2'
+ . ' or remove this assertion if you know what you\'re doing'
+ );
+ }
+
+ $itemForm = $this->getItemForm($itemType);
+
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Name'),
+ 'description' => $this->translate(
+ 'The name of this navigation item that is used to differentiate it from others'
+ )
+ )
+ );
+
+ if ((! $itemForm->requiresParentSelection() || ! isset($formData['parent']) || ! $formData['parent'])
+ && $this->getUser()->can('user/share/navigation')
+ ) {
+ $checked = isset($formData['shared']) ? null : (isset($formData['users']) || isset($formData['groups']));
+
+ $this->addElement(
+ 'checkbox',
+ 'shared',
+ array(
+ 'autosubmit' => true,
+ 'ignore' => true,
+ 'value' => $checked,
+ 'label' => $this->translate('Shared'),
+ 'description' => $this->translate('Tick this box to share this item with others')
+ )
+ );
+
+ if ($checked || (isset($formData['shared']) && $formData['shared'])) {
+ $shared = true;
+ $this->addElement(
+ 'textarea',
+ 'users',
+ array(
+ 'label' => $this->translate('Users'),
+ 'description' => $this->translate(
+ 'Comma separated list of usernames to share this item with'
+ )
+ )
+ );
+ $this->addElement(
+ 'textarea',
+ 'groups',
+ array(
+ 'label' => $this->translate('Groups'),
+ 'description' => $this->translate(
+ 'Comma separated list of group names to share this item with'
+ )
+ )
+ );
+ }
+ }
+
+ if (empty($itemTypes) || count($itemTypes) === 1) {
+ $this->addElement(
+ 'hidden',
+ 'type',
+ array(
+ 'value' => $itemType
+ )
+ );
+ } else {
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Type'),
+ 'description' => $this->translate('The type of this navigation item'),
+ 'multiOptions' => $itemTypes
+ )
+ );
+ }
+
+ if (! $shared && $itemForm->requiresParentSelection()) {
+ if ($this->itemToLoad && $this->hasBeenShared($this->itemToLoad)) {
+ $itemConfig = $this->getShareConfig()->getSection($this->itemToLoad);
+ $availableParents = $this->listAvailableParents($itemType, $itemConfig->owner);
+ } else {
+ $availableParents = $this->listAvailableParents($itemType);
+ }
+
+ $this->addElement(
+ 'select',
+ 'parent',
+ array(
+ 'allowEmpty' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Parent'),
+ 'description' => $this->translate(
+ 'The parent item to assign this navigation item to. '
+ . 'Select "None" to make this a main navigation item'
+ ),
+ 'multiOptions' => ['' => $this->translate('None', 'No parent for a navigation item')]
+ + (empty($availableParents) ? [] : array_combine($availableParents, $availableParents))
+ )
+ );
+ } else {
+ $this->addElement('hidden', 'parent', ['disabled' => true]);
+ }
+
+ $this->addSubForm($itemForm, 'item_form');
+ $itemForm->create($formData); // May require a parent which gets set by addSubForm()
+ }
+
+ /**
+ * DO NOT USE! This will be removed soon, very soon...
+ */
+ public function setDefaultUrl($url)
+ {
+ $this->defaultUrl = $url;
+ }
+
+ /**
+ * Populate the configuration of the navigation item to load
+ */
+ public function onRequest()
+ {
+ if ($this->itemToLoad) {
+ $data = $this->getConfigForItem($this->itemToLoad)->getSection($this->itemToLoad)->toArray();
+ $data['name'] = $this->itemToLoad;
+ $this->populate($data);
+ } elseif ($this->defaultUrl !== null) {
+ $this->populate(array('url' => $this->defaultUrl));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ $valid = true;
+ if (isset($formData['users']) && $formData['users']) {
+ $parsedUserRestrictions = array();
+ foreach (Auth::getInstance()->getRestrictions('application/share/users') as $userRestriction) {
+ $parsedUserRestrictions[] = array_map('trim', explode(',', $userRestriction));
+ }
+
+ if (! empty($parsedUserRestrictions)) {
+ $desiredUsers = array_map('trim', explode(',', $formData['users']));
+ array_unshift($parsedUserRestrictions, $desiredUsers);
+ $forbiddenUsers = call_user_func_array('array_diff', $parsedUserRestrictions);
+ if (! empty($forbiddenUsers)) {
+ $valid = false;
+ $this->getElement('users')->addError(
+ sprintf(
+ $this->translate(
+ 'You are not permitted to share this navigation item with the following users: %s'
+ ),
+ implode(', ', $forbiddenUsers)
+ )
+ );
+ }
+ }
+ }
+
+ if (isset($formData['groups']) && $formData['groups']) {
+ $parsedGroupRestrictions = array();
+ foreach (Auth::getInstance()->getRestrictions('application/share/groups') as $groupRestriction) {
+ $parsedGroupRestrictions[] = array_map('trim', explode(',', $groupRestriction));
+ }
+
+ if (! empty($parsedGroupRestrictions)) {
+ $desiredGroups = array_map('trim', explode(',', $formData['groups']));
+ array_unshift($parsedGroupRestrictions, $desiredGroups);
+ $forbiddenGroups = call_user_func_array('array_diff', $parsedGroupRestrictions);
+ if (! empty($forbiddenGroups)) {
+ $valid = false;
+ $this->getElement('groups')->addError(
+ sprintf(
+ $this->translate(
+ 'You are not permitted to share this navigation item with the following groups: %s'
+ ),
+ implode(', ', $forbiddenGroups)
+ )
+ );
+ }
+ }
+ }
+
+ return $valid;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function writeConfig(Config $config)
+ {
+ parent::writeConfig($config);
+
+ if ($this->secondaryConfig !== null) {
+ $this->config = $this->secondaryConfig; // Causes the config being displayed to the user in case of an error
+ parent::writeConfig($this->secondaryConfig);
+ }
+ }
+
+ /**
+ * Return the navigation configuration the given item is a part of
+ *
+ * @param string $name
+ *
+ * @return Config|null In case the item is not part of any configuration
+ */
+ protected function getConfigForItem($name)
+ {
+ if ($this->getUserConfig()->hasSection($name)) {
+ return $this->getUserConfig();
+ } elseif ($this->getShareConfig()->hasSection($name)) {
+ if ($this->getShareConfig()->get($name, 'owner') === $this->getUser()->getUsername()
+ || $this->getUser()->can('user/share/navigation')
+ ) {
+ return $this->getShareConfig();
+ }
+ }
+ }
+
+ /**
+ * Return whether the given navigation item has been shared
+ *
+ * @param string $name
+ * @param string $type
+ *
+ * @return bool
+ */
+ protected function hasBeenShared($name, $type = null)
+ {
+ return $this->getShareConfig($type) === $this->getConfigForItem($name);
+ }
+
+ /**
+ * Return the form for the given type of navigation item
+ *
+ * @param string $type
+ *
+ * @return Form
+ */
+ protected function getItemForm($type)
+ {
+ $className = StringHelper::cname($type, '-') . 'Form';
+
+ $form = null;
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ $classPath = 'Icinga\\Module\\'
+ . ucfirst($module->getName())
+ . '\\'
+ . static::FORM_NS
+ . '\\'
+ . $className;
+ if (class_exists($classPath)) {
+ $form = new $classPath();
+ break;
+ }
+ }
+
+ if ($form === null) {
+ $classPath = 'Icinga\\' . static::FORM_NS . '\\' . $className;
+ if (class_exists($classPath)) {
+ $form = new $classPath();
+ }
+ }
+
+ if ($form === null) {
+ Logger::debug(
+ 'Failed to find custom navigation item form %s for item %s. Using form NavigationItemForm now',
+ $className,
+ $type
+ );
+
+ $form = new NavigationItemForm();
+ } elseif (! $form instanceof NavigationItemForm) {
+ throw new ProgrammingError('Class %s must inherit from NavigationItemForm', $classPath);
+ }
+
+ return $form;
+ }
+}
diff --git a/application/forms/Navigation/NavigationItemForm.php b/application/forms/Navigation/NavigationItemForm.php
new file mode 100644
index 0000000..6cf15e7
--- /dev/null
+++ b/application/forms/Navigation/NavigationItemForm.php
@@ -0,0 +1,114 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Navigation;
+
+use Icinga\Web\Form;
+use Icinga\Web\Url;
+
+class NavigationItemForm extends Form
+{
+ /**
+ * Whether to create a select input to choose a parent for a navigation item of a particular type
+ *
+ * @var bool
+ */
+ protected $requiresParentSelection = false;
+
+ /**
+ * Return whether to create a select input to choose a parent for a navigation item of a particular type
+ *
+ * @return bool
+ */
+ public function requiresParentSelection()
+ {
+ return $this->requiresParentSelection;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'select',
+ 'target',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Target'),
+ 'description' => $this->translate('The target where to open this navigation item\'s url'),
+ 'multiOptions' => array(
+ '_blank' => $this->translate('New Window'),
+ '_next' => $this->translate('New Column'),
+ '_main' => $this->translate('Single Column'),
+ '_self' => $this->translate('Current Column')
+ )
+ )
+ );
+
+ $this->addElement(
+ 'textarea',
+ 'url',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Url'),
+ 'description' => $this->translate(
+ 'The url of this navigation item. Leave blank if only the name should be displayed.'
+ . ' For urls with username and password and for all external urls,'
+ . ' make sure to prepend an appropriate protocol identifier (e.g. http://example.tld)'
+ ),
+ 'validators' => array(
+ array(
+ 'Callback',
+ false,
+ array(
+ 'callback' => function ($url) {
+ // Matches if the given url contains obviously
+ // a username but not any protocol identifier
+ return !preg_match('#^((?=[^/@]).)+@.*$#', $url);
+ },
+ 'messages' => array(
+ 'callbackValue' => $this->translate(
+ 'Missing protocol identifier'
+ )
+ )
+ )
+ )
+ )
+ )
+ );
+
+ $this->addElement(
+ 'text',
+ 'icon',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Icon'),
+ 'description' => $this->translate(
+ 'The icon of this navigation item. Leave blank if you do not want a icon being displayed'
+ )
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+ // The regex here specifically matches the port-macro as it's the only one preventing Url::fromPath() from
+ // successfully parsing the given url. Any other macro such as for the scheme or host simply gets identified
+ // as path which is just fine in this case.
+ if (isset($values['url']) && $values['url'] && !preg_match('~://.+:\d*?(\$.+\$)~', $values['url'])) {
+ $url = Url::fromPath($values['url']);
+ if ($url->getBasePath() === $this->getRequest()->getBasePath()) {
+ $values['url'] = $url->getRelativeUrl();
+ } else {
+ $values['url'] = $url->getAbsoluteUrl();
+ }
+ }
+
+ return $values;
+ }
+}
diff --git a/application/forms/PreferenceForm.php b/application/forms/PreferenceForm.php
new file mode 100644
index 0000000..23a7594
--- /dev/null
+++ b/application/forms/PreferenceForm.php
@@ -0,0 +1,485 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use DateTimeZone;
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\User\Preferences;
+use Icinga\User\Preferences\PreferencesStore;
+use Icinga\Util\TimezoneDetect;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+use Icinga\Web\Session;
+use Icinga\Web\StyleSheet;
+use ipl\Html\HtmlElement;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\Locale;
+use ipl\I18n\StaticTranslator;
+
+/**
+ * Form class to adjust user preferences
+ */
+class PreferenceForm extends Form
+{
+ /**
+ * The preferences to work with
+ *
+ * @var Preferences
+ */
+ protected $preferences;
+
+ /**
+ * The preference store to use
+ *
+ * @var PreferencesStore
+ */
+ protected $store;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_preferences');
+ $this->setSubmitLabel($this->translate('Save to the Preferences'));
+ }
+
+ /**
+ * Set preferences to work with
+ *
+ * @param Preferences $preferences The preferences to work with
+ *
+ * @return $this
+ */
+ public function setPreferences(Preferences $preferences)
+ {
+ $this->preferences = $preferences;
+ return $this;
+ }
+
+ /**
+ * Set the preference store to use
+ *
+ * @param PreferencesStore $store The preference store to use
+ *
+ * @return $this
+ */
+ public function setStore(PreferencesStore $store)
+ {
+ $this->store = $store;
+ return $this;
+ }
+
+ /**
+ * Persist preferences
+ *
+ * @return $this
+ */
+ public function save()
+ {
+ $this->store->save($this->preferences);
+ return $this;
+ }
+
+ /**
+ * Adjust preferences and persist them
+ *
+ * @see Form::onSuccess()
+ */
+ public function onSuccess()
+ {
+ $currentPreferences = $this->Auth()->getUser()->getPreferences();
+ $oldTheme = $currentPreferences->getValue('icingaweb', 'theme');
+ $oldMode = $currentPreferences->getValue('icingaweb', 'theme_mode');
+ $oldLocale = $currentPreferences->getValue('icingaweb', 'language');
+ $defaultTheme = Config::app()->get('themes', 'default', StyleSheet::DEFAULT_THEME);
+
+ $this->preferences = new Preferences($this->store ? $this->store->load() : array());
+ $webPreferences = $this->preferences->get('icingaweb', array());
+ foreach ($this->getValues() as $key => $value) {
+ if ($value === ''
+ || $value === null
+ || $value === 'autodetect'
+ || ($key === 'theme' && $value === $defaultTheme)
+ ) {
+ if (isset($webPreferences[$key])) {
+ unset($webPreferences[$key]);
+ }
+ } else {
+ $webPreferences[$key] = $value;
+ }
+ }
+ $this->preferences->icingaweb = $webPreferences;
+ Session::getSession()->user->setPreferences($this->preferences);
+
+ if ((($theme = $this->getElement('theme')) !== null
+ && ($theme = $theme->getValue()) !== $oldTheme
+ && ($theme !== $defaultTheme || $oldTheme !== null))
+ || (($mode = $this->getElement('theme_mode')) !== null
+ && ($mode->getValue()) !== $oldMode)
+ ) {
+ $this->getResponse()->setReloadCss(true);
+ }
+
+ if (($locale = $this->getElement('language')) !== null
+ && $locale->getValue() !== 'autodetect'
+ && $locale->getValue() !== $oldLocale
+ ) {
+ $this->getResponse()->setHeader('X-Icinga-Redirect-Http', 'yes');
+ }
+
+ try {
+ if ($this->store && $this->getElement('btn_submit')->isChecked()) {
+ $this->save();
+ Notification::success($this->translate('Preferences successfully saved'));
+ } else {
+ Notification::success($this->translate('Preferences successfully saved for the current session'));
+ }
+ } catch (Exception $e) {
+ Logger::error($e);
+ Notification::error($e->getMessage());
+ }
+ }
+
+ /**
+ * Populate preferences
+ *
+ * @see Form::onRequest()
+ */
+ public function onRequest()
+ {
+ $auth = Auth::getInstance();
+ $values = $auth->getUser()->getPreferences()->get('icingaweb');
+
+ if (! isset($values['language'])) {
+ $values['language'] = 'autodetect';
+ }
+
+ if (! isset($values['timezone'])) {
+ $values['timezone'] = 'autodetect';
+ }
+
+ if (! isset($values['auto_refresh'])) {
+ $values['auto_refresh'] = '1';
+ }
+
+ $this->populate($values);
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ if (setlocale(LC_ALL, 0) === 'C') {
+ $this->warning(
+ $this->translate(
+ 'Your language setting is not applied because your platform is missing the corresponding locale.'
+ . ' Make sure to install the correct language pack and restart your web server afterwards.'
+ ),
+ false
+ );
+ }
+
+ $themeFile = StyleSheet::getThemeFile(Config::app()->get('themes', 'default'));
+ if (! (bool) Config::app()->get('themes', 'disabled', false)) {
+ $themes = Icinga::app()->getThemes();
+ if (count($themes) > 1) {
+ $defaultTheme = Config::app()->get('themes', 'default', StyleSheet::DEFAULT_THEME);
+ if (isset($themes[$defaultTheme])) {
+ $themes[$defaultTheme] .= ' (' . $this->translate('default') . ')';
+ }
+ $this->addElement(
+ 'select',
+ 'theme',
+ array(
+ 'label' => $this->translate('Theme', 'Form element label'),
+ 'multiOptions' => $themes,
+ 'autosubmit' => true,
+ 'value' => $this->preferences->getValue(
+ 'icingaweb',
+ 'theme',
+ $defaultTheme
+ )
+ )
+ );
+ }
+ }
+
+ if (isset($formData['theme'])) {
+ $themeFile = StyleSheet::getThemeFile($formData['theme']);
+ }
+
+ if ($themeFile !== null) {
+ $file = @file_get_contents($themeFile);
+ if ($file && strpos($file, StyleSheet::LIGHT_MODE_IDENTIFIER) === false) {
+ $disabled = ['', 'light', 'system'];
+ }
+ }
+
+ $this->addElement(
+ 'radio',
+ 'theme_mode',
+ [
+ 'class' => 'theme-mode-input',
+ 'label' => $this->translate('Theme Mode'),
+ 'multiOptions' => [
+ '' => HtmlElement::create(
+ 'img',
+ ['src' => $this->getView()->href('img/theme-mode-thumbnail-dark.svg')]
+ ) . HtmlElement::create('span', [], $this->translate('Dark')),
+ 'light' => HtmlElement::create(
+ 'img',
+ ['src' => $this->getView()->href('img/theme-mode-thumbnail-light.svg')]
+ ) . HtmlElement::create('span', [], $this->translate('Light')),
+ 'system' => HtmlElement::create(
+ 'img',
+ ['src' => $this->getView()->href('img/theme-mode-thumbnail-system.svg')]
+ ) . HtmlElement::create('span', [], $this->translate('System'))
+ ],
+ 'value' => isset($value) ? $value : '',
+ 'disable' => isset($disabled) ? $disabled : [],
+ 'escape' => false,
+ 'decorators' => array_merge(
+ array_slice(self::$defaultElementDecorators, 0, -1),
+ [['HtmlTag', ['tag' => 'div', 'class' => 'control-group theme-mode']]]
+ )
+ ]
+ );
+
+ /** @var GettextTranslator $translator */
+ $translator = StaticTranslator::$instance;
+
+ $languages = array();
+ $availableLocales = $translator->listLocales();
+
+ $locale = $this->getLocale($availableLocales);
+ if ($locale !== null) {
+ $languages['autodetect'] = sprintf($this->translate('Browser (%s)', 'preferences.form'), $locale);
+ }
+
+ $availableLocales[] = $translator->getDefaultLocale();
+ foreach ($availableLocales as $language) {
+ $languages[$language] = $language;
+ }
+
+ $tzList = array();
+ $tzList['autodetect'] = sprintf(
+ $this->translate('Browser (%s)', 'preferences.form'),
+ $this->getDefaultTimezone()
+ );
+ foreach (DateTimeZone::listIdentifiers() as $tz) {
+ $tzList[$tz] = $tz;
+ }
+
+ $this->addElement(
+ 'select',
+ 'language',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Your Current Language'),
+ 'description' => $this->translate('Use the following language to display texts and messages'),
+ 'multiOptions' => $languages,
+ 'value' => substr(setlocale(LC_ALL, 0), 0, 5)
+ )
+ );
+
+ $this->addElement(
+ 'select',
+ 'timezone',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Your Current Timezone'),
+ 'description' => $this->translate('Use the following timezone for dates and times'),
+ 'multiOptions' => $tzList,
+ 'value' => $this->getDefaultTimezone()
+ )
+ );
+
+ $this->addElement(
+ 'select',
+ 'show_application_state_messages',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Show application state messages'),
+ 'description' => $this->translate('Whether to show application state messages.'),
+ 'multiOptions' => [
+ 'system' => (bool) Config::app()->get('global', 'show_application_state_messages', true)
+ ? $this->translate('System (Yes)')
+ : $this->translate('System (No)'),
+ 1 => $this->translate('Yes'),
+ 0 => $this->translate('No')],
+ 'value' => 'system'
+ )
+ );
+
+ if (Auth::getInstance()->hasPermission('user/application/stacktraces')) {
+ $this->addElement(
+ 'checkbox',
+ 'show_stacktraces',
+ array(
+ 'value' => $this->getDefaultShowStacktraces(),
+ 'label' => $this->translate('Show Stacktraces'),
+ 'description' => $this->translate('Set whether to show an exception\'s stacktrace.')
+ )
+ );
+ }
+
+ $this->addElement(
+ 'checkbox',
+ 'show_benchmark',
+ array(
+ 'label' => $this->translate('Use benchmark')
+ )
+ );
+
+ $this->addElement(
+ 'checkbox',
+ 'auto_refresh',
+ array(
+ 'required' => false,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Enable auto refresh'),
+ 'description' => $this->translate(
+ 'This option allows you to enable or to disable the global page content auto refresh'
+ ),
+ 'value' => 1
+ )
+ );
+
+ if (isset($formData['auto_refresh']) && $formData['auto_refresh']) {
+ $this->addElement(
+ 'select',
+ 'auto_refresh_speed',
+ [
+ 'required' => false,
+ 'label' => $this->translate('Auto refresh speed'),
+ 'description' => $this->translate(
+ 'This option allows you to speed up or to slow down the global page content auto refresh'
+ ),
+ 'multiOptions' => [
+ '0.5' => $this->translate('Fast', 'refresh_speed'),
+ '' => $this->translate('Default', 'refresh_speed'),
+ '2' => $this->translate('Moderate', 'refresh_speed'),
+ '4' => $this->translate('Slow', 'refresh_speed')
+ ],
+ 'value' => ''
+ ]
+ );
+ }
+
+ $this->addElement(
+ 'number',
+ 'default_page_size',
+ array(
+ 'label' => $this->translate('Default page size'),
+ 'description' => $this->translate('Default number of items per page for list views'),
+ 'placeholder' => 25,
+ 'min' => 25,
+ 'step' => 1
+ )
+ );
+
+ if ($this->store) {
+ $this->addElement(
+ 'submit',
+ 'btn_submit',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Save to the Preferences'),
+ 'decorators' => array('ViewHelper'),
+ 'class' => 'btn-primary'
+ )
+ );
+ }
+
+ $this->addElement(
+ 'submit',
+ 'btn_submit_session',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Save for the current Session'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->setAttrib('data-progress-element', 'preferences-progress');
+ $this->addElement(
+ 'note',
+ 'preferences-progress',
+ array(
+ 'decorators' => array(
+ 'ViewHelper',
+ array('Spinner', array('id' => 'preferences-progress'))
+ )
+ )
+ );
+
+ $this->addDisplayGroup(
+ array('btn_submit', 'btn_submit_session', 'preferences-progress'),
+ 'submit_buttons',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+ }
+
+ public function addSubmitButton()
+ {
+ return $this;
+ }
+
+ public function isSubmitted()
+ {
+ if (parent::isSubmitted()) {
+ return true;
+ }
+
+ return $this->getElement('btn_submit_session')->isChecked();
+ }
+
+ /**
+ * Return the current default timezone
+ *
+ * @return string
+ */
+ protected function getDefaultTimezone()
+ {
+ $detect = new TimezoneDetect();
+ if ($detect->success()) {
+ return $detect->getTimezoneName();
+ } else {
+ return @date_default_timezone_get();
+ }
+ }
+
+ /**
+ * Return the preferred locale based on the given HTTP header and the available translations
+ *
+ * @return string|null
+ */
+ protected function getLocale($availableLocales)
+ {
+ return isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])
+ ? (new Locale())->getPreferred($_SERVER['HTTP_ACCEPT_LANGUAGE'], $availableLocales)
+ : null;
+ }
+
+ /**
+ * Return the default global setting for show_stacktraces
+ *
+ * @return bool
+ */
+ protected function getDefaultShowStacktraces()
+ {
+ return Config::app()->get('global', 'show_stacktraces', true);
+ }
+}
diff --git a/application/forms/RepositoryForm.php b/application/forms/RepositoryForm.php
new file mode 100644
index 0000000..22718ee
--- /dev/null
+++ b/application/forms/RepositoryForm.php
@@ -0,0 +1,453 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\NotFoundError;
+use Icinga\Repository\Repository;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form base-class providing standard functionality for extensible, updatable and reducible repositories
+ */
+abstract class RepositoryForm extends Form
+{
+ /**
+ * Insert mode
+ */
+ const MODE_INSERT = 0;
+
+ /**
+ * Update mode
+ */
+ const MODE_UPDATE = 1;
+
+ /**
+ * Delete mode
+ */
+ const MODE_DELETE = 2;
+
+ /**
+ * The repository being worked with
+ *
+ * @var Repository
+ */
+ protected $repository;
+
+ /**
+ * The target being worked with
+ *
+ * @var mixed
+ */
+ protected $baseTable;
+
+ /**
+ * How to interact with the repository
+ *
+ * @var int
+ */
+ protected $mode;
+
+ /**
+ * The name of the entry being handled when in mode update or delete
+ *
+ * @var string
+ */
+ protected $identifier;
+
+ /**
+ * The data of the entry to pre-populate the form with when in mode insert or update
+ *
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * Set the repository to work with
+ *
+ * @param Repository $repository
+ *
+ * @return $this
+ */
+ public function setRepository(Repository $repository)
+ {
+ $this->repository = $repository;
+ return $this;
+ }
+
+ /**
+ * Return the target being worked with
+ *
+ * @return mixed
+ */
+ protected function getBaseTable()
+ {
+ if ($this->baseTable === null) {
+ return $this->repository->getBaseTable();
+ }
+
+ return $this->baseTable;
+ }
+
+ /**
+ * Return the name of the entry to handle
+ *
+ * @return string
+ */
+ protected function getIdentifier()
+ {
+ return $this->identifier;
+ }
+
+ /**
+ * Return the current data of the entry being handled
+ *
+ * @return array
+ */
+ protected function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Return whether an entry should be inserted
+ *
+ * @return bool
+ */
+ public function shouldInsert()
+ {
+ return $this->mode === self::MODE_INSERT;
+ }
+
+ /**
+ * Return whether an entry should be udpated
+ *
+ * @return bool
+ */
+ public function shouldUpdate()
+ {
+ return $this->mode === self::MODE_UPDATE;
+ }
+
+ /**
+ * Return whether an entry should be deleted
+ *
+ * @return bool
+ */
+ public function shouldDelete()
+ {
+ return $this->mode === self::MODE_DELETE;
+ }
+
+ /**
+ * Add a new entry
+ *
+ * @param array $data The defaults to use, if any
+ *
+ * @return $this
+ */
+ public function add(array $data = null)
+ {
+ $this->mode = static::MODE_INSERT;
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Edit an entry
+ *
+ * @param string $name The entry's name
+ * @param array $data The entry's current data
+ *
+ * @return $this
+ */
+ public function edit($name, array $data = null)
+ {
+ $this->mode = static::MODE_UPDATE;
+ $this->identifier = $name;
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Remove an entry
+ *
+ * @param string $name The entry's name
+ *
+ * @return $this
+ */
+ public function remove($name)
+ {
+ $this->mode = static::MODE_DELETE;
+ $this->identifier = $name;
+ return $this;
+ }
+
+ /**
+ * Fetch and return the entry to pre-populate the form with when in mode update
+ *
+ * @return false|object
+ */
+ protected function fetchEntry()
+ {
+ return $this->repository
+ ->select()
+ ->from($this->getBaseTable())
+ ->applyFilter($this->createFilter())
+ ->fetchRow();
+ }
+
+ /**
+ * Return whether the entry supposed to be removed exists
+ *
+ * @return bool
+ */
+ protected function entryExists()
+ {
+ $count = $this->repository
+ ->select()
+ ->from($this->getBaseTable())
+ ->addFilter($this->createFilter())
+ ->count();
+ return $count > 0;
+ }
+
+ /**
+ * Insert the new entry
+ */
+ protected function insertEntry()
+ {
+ $this->repository->insert($this->getBaseTable(), $this->getValues());
+ }
+
+ /**
+ * Update the entry
+ */
+ protected function updateEntry()
+ {
+ $this->repository->update($this->getBaseTable(), $this->getValues(), $this->createFilter());
+ }
+
+ /**
+ * Delete the entry
+ */
+ protected function deleteEntry()
+ {
+ $this->repository->delete($this->getBaseTable(), $this->createFilter());
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData The data sent by the user
+ */
+ public function createElements(array $formData)
+ {
+ if ($this->shouldInsert()) {
+ $this->createInsertElements($formData);
+ } elseif ($this->shouldUpdate()) {
+ $this->createUpdateElements($formData);
+ } elseif ($this->shouldDelete()) {
+ $this->createDeleteElements($formData);
+ }
+ }
+
+ /**
+ * Prepare the form for the requested mode
+ */
+ public function onRequest()
+ {
+ if ($this->shouldInsert()) {
+ $this->onInsertRequest();
+ } elseif ($this->shouldUpdate()) {
+ $this->onUpdateRequest();
+ } elseif ($this->shouldDelete()) {
+ $this->onDeleteRequest();
+ }
+ }
+
+ /**
+ * Prepare the form for mode insert
+ *
+ * Populates the form with the data passed to add().
+ */
+ protected function onInsertRequest()
+ {
+ $data = $this->getData();
+ if (! empty($data)) {
+ $this->setDefaults($data);
+ }
+ }
+
+ /**
+ * Prepare the form for mode update
+ *
+ * Populates the form with either the data passed to edit() or tries to fetch it from the repository.
+ *
+ * @throws NotFoundError In case the entry to update cannot be found
+ */
+ protected function onUpdateRequest()
+ {
+ $data = $this->getData();
+ if ($data === null) {
+ $row = $this->fetchEntry();
+ if ($row === false) {
+ throw new NotFoundError('Entry "%s" not found', $this->getIdentifier());
+ }
+
+ $data = get_object_vars($row);
+ }
+
+ $this->setDefaults($data);
+ }
+
+ /**
+ * Prepare the form for mode delete
+ *
+ * Verifies that the repository contains the entry to delete.
+ *
+ * @throws NotFoundError In case the entry to delete cannot be found
+ */
+ protected function onDeleteRequest()
+ {
+ if (! $this->entryExists()) {
+ throw new NotFoundError('Entry "%s" not found', $this->getIdentifier());
+ }
+ }
+
+ /**
+ * Apply the requested mode on the repository
+ *
+ * @return bool
+ */
+ public function onSuccess()
+ {
+ if ($this->shouldInsert()) {
+ return $this->onInsertSuccess();
+ } elseif ($this->shouldUpdate()) {
+ return $this->onUpdateSuccess();
+ } elseif ($this->shouldDelete()) {
+ return $this->onDeleteSuccess();
+ }
+ }
+
+ /**
+ * Apply mode insert on the repository
+ *
+ * @return bool
+ */
+ protected function onInsertSuccess()
+ {
+ try {
+ $this->insertEntry();
+ } catch (Exception $e) {
+ Notification::error($this->getInsertMessage(false));
+ $this->error($e->getMessage());
+ return false;
+ }
+
+ Notification::success($this->getInsertMessage(true));
+ return true;
+ }
+
+ /**
+ * Apply mode update on the repository
+ *
+ * @return bool
+ */
+ protected function onUpdateSuccess()
+ {
+ try {
+ $this->updateEntry();
+ } catch (Exception $e) {
+ Notification::error($this->getUpdateMessage(false));
+ $this->error($e->getMessage());
+ return false;
+ }
+
+ Notification::success($this->getUpdateMessage(true));
+ return true;
+ }
+
+ /**
+ * Apply mode delete on the repository
+ *
+ * @return bool
+ */
+ protected function onDeleteSuccess()
+ {
+ try {
+ $this->deleteEntry();
+ } catch (Exception $e) {
+ Notification::error($this->getDeleteMessage(false));
+ $this->error($e->getMessage());
+ return false;
+ }
+
+ Notification::success($this->getDeleteMessage(true));
+ return true;
+ }
+
+ /**
+ * Create and add elements to this form to insert an entry
+ *
+ * @param array $formData The data sent by the user
+ */
+ abstract protected function createInsertElements(array $formData);
+
+ /**
+ * Create and add elements to this form to update an entry
+ *
+ * Calls createInsertElements() by default. Overwrite this to add different elements when in mode update.
+ *
+ * @param array $formData The data sent by the user
+ */
+ protected function createUpdateElements(array $formData)
+ {
+ $this->createInsertElements($formData);
+ }
+
+ /**
+ * Create and add elements to this form to delete an entry
+ *
+ * @param array $formData The data sent by the user
+ */
+ abstract protected function createDeleteElements(array $formData);
+
+ /**
+ * Create and return a filter to use when selecting, updating or deleting an entry
+ *
+ * @return Filter
+ */
+ abstract protected function createFilter();
+
+ /**
+ * Return a notification message to use when inserting an entry
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ abstract protected function getInsertMessage($success);
+
+ /**
+ * Return a notification message to use when updating an entry
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ abstract protected function getUpdateMessage($success);
+
+ /**
+ * Return a notification message to use when deleting an entry
+ *
+ * @param bool $success true or false, whether the operation was successful
+ *
+ * @return string
+ */
+ abstract protected function getDeleteMessage($success);
+}
diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php
new file mode 100644
index 0000000..cc8b78c
--- /dev/null
+++ b/application/forms/Security/RoleForm.php
@@ -0,0 +1,624 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Forms\Security;
+
+use Icinga\Application\Hook\ConfigFormEventsHook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Manager;
+use Icinga\Authentication\AdmissionLoader;
+use Icinga\Data\Filter\Filter;
+use Icinga\Forms\ConfigForm;
+use Icinga\Forms\RepositoryForm;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Notification;
+use ipl\Web\Widget\Icon;
+
+/**
+ * Form for managing roles
+ */
+class RoleForm extends RepositoryForm
+{
+ /**
+ * The name to use instead of `*`
+ */
+ const WILDCARD_NAME = 'allAndEverything';
+
+ /**
+ * The prefix used to deny a permission
+ */
+ const DENY_PREFIX = 'no-';
+
+ /**
+ * Provided permissions by currently installed modules
+ *
+ * @var array
+ */
+ protected $providedPermissions;
+
+ /**
+ * Provided restrictions by currently installed modules
+ *
+ * @var array
+ */
+ protected $providedRestrictions;
+
+ public function init()
+ {
+ $this->setAttrib('class', self::DEFAULT_CLASSES . ' role-form');
+
+ list($this->providedPermissions, $this->providedRestrictions) = static::collectProvidedPrivileges();
+ }
+
+ protected function createFilter()
+ {
+ return Filter::where('name', $this->getIdentifier());
+ }
+
+ public function createInsertElements(array $formData = array())
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ [
+ 'required' => true,
+ 'label' => $this->translate('Role Name'),
+ 'description' => $this->translate('The name of the role')
+ ]
+ );
+ $this->addElement(
+ 'select',
+ 'parent',
+ [
+ 'label' => $this->translate('Inherit From'),
+ 'description' => $this->translate('Choose a role from which to inherit privileges'),
+ 'value' => '',
+ 'multiOptions' => array_merge(
+ ['' => $this->translate('None', 'parent role')],
+ $this->collectRoles()
+ )
+ ]
+ );
+ $this->addElement(
+ 'textarea',
+ 'users',
+ [
+ 'label' => $this->translate('Users'),
+ 'description' => $this->translate('Comma-separated list of users that are assigned to the role')
+ ]
+ );
+ $this->addElement(
+ 'textarea',
+ 'groups',
+ [
+ 'label' => $this->translate('Groups'),
+ 'description' => $this->translate('Comma-separated list of groups that are assigned to the role')
+ ]
+ );
+ $this->addElement(
+ 'checkbox',
+ self::WILDCARD_NAME,
+ [
+ 'autosubmit' => true,
+ 'label' => $this->translate('Administrative Access'),
+ 'description' => $this->translate('Everything is allowed')
+ ]
+ );
+ $this->addElement(
+ 'checkbox',
+ 'unrestricted',
+ [
+ 'autosubmit' => true,
+ 'uncheckedValue' => null,
+ 'label' => $this->translate('Unrestricted Access'),
+ 'description' => $this->translate('Access to any data is completely unrestricted')
+ ]
+ );
+
+ $hasAdminPerm = isset($formData[self::WILDCARD_NAME]) && $formData[self::WILDCARD_NAME];
+ $isUnrestricted = isset($formData['unrestricted']) && $formData['unrestricted'];
+ foreach ($this->providedPermissions as $moduleName => $permissionList) {
+ $this->sortPermissions($permissionList);
+
+ $anythingGranted = false;
+ $anythingRefused = false;
+ $anythingRestricted = false;
+
+ $elements = [$moduleName . '_header'];
+ // The actual element is added last
+
+ $elements[] = 'permission_header';
+ $this->addElement('note', 'permission_header', [
+ 'decorators' => [['Callback', ['callback' => function () {
+ return '<h4>' . $this->translate('Permissions') . '</h4>'
+ . $this->getView()->icon('ok', $this->translate(
+ 'Grant access by toggling a switch below'
+ ))
+ . $this->getView()->icon('cancel', $this->translate(
+ 'Deny access by toggling a switch below'
+ ));
+ }]], ['HtmlTag', ['tag' => 'div']]]
+ ]);
+
+ $hasFullPerm = false;
+ foreach ($permissionList as $name => $spec) {
+ $elementName = $this->filterName($name);
+
+ if (isset($formData[$elementName]) && $formData[$elementName]) {
+ $anythingGranted = true;
+ }
+
+ if ($hasFullPerm || $hasAdminPerm) {
+ $elementName .= '_fake';
+ }
+
+ $denyCheckbox = null;
+ if (! isset($spec['isFullPerm'])
+ && substr($name, 0, strlen(self::DENY_PREFIX)) !== self::DENY_PREFIX
+ ) {
+ $denyCheckbox = $this->createElement('checkbox', self::DENY_PREFIX . $name, [
+ 'decorators' => ['ViewHelper']
+ ]);
+ $this->addElement($denyCheckbox);
+ $this->removeFromIteration($denyCheckbox->getName());
+
+ if (isset($formData[$denyCheckbox->getName()]) && $formData[$denyCheckbox->getName()]) {
+ $anythingRefused = true;
+ }
+ }
+
+ $elements[] = $elementName;
+ $this->addElement(
+ 'checkbox',
+ $elementName,
+ [
+ 'ignore' => $hasFullPerm || $hasAdminPerm,
+ 'autosubmit' => isset($spec['isFullPerm']),
+ 'disabled' => $hasFullPerm || $hasAdminPerm ?: null,
+ 'value' => $hasFullPerm || $hasAdminPerm,
+ 'label' => isset($spec['label'])
+ ? $spec['label']
+ : join('', iterator_to_array(call_user_func(function ($segments) {
+ foreach ($segments as $segment) {
+ if ($segment[0] === '/') {
+ // Adds a zero-width char after each slash to help browsers break onto newlines
+ yield '/&#8203;';
+ yield '<span class="no-wrap">' . substr($segment, 1) . '</span>';
+ } else {
+ yield '<em>' . $segment . '</em>';
+ }
+ }
+ }, preg_split(
+ '~(/[^/]+)~',
+ $name,
+ -1,
+ PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
+ )))),
+ 'description' => isset($spec['description']) ? $spec['description'] : $name,
+ 'decorators' => array_merge(
+ array_slice(self::$defaultElementDecorators, 0, 3),
+ [['Callback', ['callback' => function () use ($denyCheckbox) {
+ return $denyCheckbox ? $denyCheckbox->render() : '';
+ }]]],
+ array_slice(self::$defaultElementDecorators, 3)
+ )
+ ]
+ )
+ ->getElement($elementName)
+ ->getDecorator('Label')
+ ->setOption('escape', false);
+
+ if ($hasFullPerm || $hasAdminPerm) {
+ // Add a hidden element to preserve the configured permission value
+ $this->addElement('hidden', $this->filterName($name));
+ }
+
+ if (isset($spec['isFullPerm'])) {
+ $filteredName = $this->filterName($name);
+ $hasFullPerm = isset($formData[$filteredName]) && $formData[$filteredName];
+ }
+ }
+
+ if (isset($this->providedRestrictions[$moduleName])) {
+ $elements[] = 'restriction_header';
+ $this->addElement('note', 'restriction_header', [
+ 'value' => '<h4>' . $this->translate('Restrictions') . '</h4>',
+ 'decorators' => ['ViewHelper']
+ ]);
+
+ foreach ($this->providedRestrictions[$moduleName] as $name => $spec) {
+ $elementName = $this->filterName($name);
+
+ if (isset($formData[$elementName]) && $formData[$elementName]) {
+ $anythingRestricted = true;
+ }
+
+ $elements[] = $elementName;
+ $this->addElement(
+ 'text',
+ $elementName,
+ [
+ 'label' => isset($spec['label'])
+ ? $spec['label']
+ : join('', iterator_to_array(call_user_func(function ($segments) {
+ foreach ($segments as $segment) {
+ if ($segment[0] === '/') {
+ // Add zero-width char after each slash to help browsers break onto newlines
+ yield '/&#8203;';
+ yield '<span class="no-wrap">' . substr($segment, 1) . '</span>';
+ } else {
+ yield '<em>' . $segment . '</em>';
+ }
+ }
+ }, preg_split(
+ '~(/[^/]+)~',
+ $name,
+ -1,
+ PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
+ )))),
+ 'description' => $spec['description'],
+ 'style' => $isUnrestricted ? 'text-decoration:line-through;' : '',
+ 'readonly' => $isUnrestricted ?: null
+ ]
+ )
+ ->getElement($elementName)
+ ->getDecorator('Label')
+ ->setOption('escape', false);
+ }
+ }
+
+ $this->addElement(
+ 'note',
+ $moduleName . '_header',
+ [
+ 'decorators' => ['ViewHelper'],
+ 'value' => '<summary class="collapsible-control">'
+ . '<span>' . ($moduleName !== 'application'
+ ? sprintf('%s <em>%s</em>', $moduleName, $this->translate('Module'))
+ : 'Icinga Web 2'
+ ) . '</span>'
+ . '<span class="privilege-preview">'
+ . ($hasAdminPerm || $anythingGranted ? new Icon('check-circle', ['class' => 'granted']) : '')
+ . ($anythingRefused ? new Icon('times-circle', ['class' => 'refused']) : '')
+ . (! $isUnrestricted && $anythingRestricted
+ ? new Icon('filter', ['class' => 'restricted'])
+ : ''
+ )
+ . '</span>'
+ . new Icon('angles-down', ['class' => 'collapse-icon'])
+ . new Icon('angles-left', ['class' => 'expand-icon'])
+ . '</summary>'
+ ]
+ );
+
+ $this->addDisplayGroup($elements, $moduleName . '_elements', [
+ 'decorators' => [
+ 'FormElements',
+ ['HtmlTag', [
+ 'tag' => 'details',
+ 'class' => 'collapsible'
+ ]],
+ ['Fieldset']
+ ]
+ ]);
+ }
+ }
+
+ protected function createDeleteElements(array $formData)
+ {
+ }
+
+ public function fetchEntry()
+ {
+ $role = parent::fetchEntry();
+ if ($role === false) {
+ return false;
+ }
+
+ $values = [
+ 'parent' => $role->parent,
+ 'name' => $role->name,
+ 'users' => $role->users,
+ 'groups' => $role->groups,
+ 'unrestricted' => $role->unrestricted,
+ self::WILDCARD_NAME => $role->permissions && preg_match('~(?>^|,)\*(?>$|,)~', $role->permissions)
+ ];
+
+ if (! empty($role->permissions) || ! empty($role->refusals)) {
+ $permissions = StringHelper::trimSplit($role->permissions);
+ $refusals = StringHelper::trimSplit($role->refusals);
+
+ list($permissions, $newRefusals) = AdmissionLoader::migrateLegacyPermissions($permissions);
+ if (! empty($newRefusals)) {
+ array_push($refusals, ...$newRefusals);
+ }
+
+ foreach ($this->providedPermissions as $moduleName => $permissionList) {
+ $hasFullPerm = false;
+ foreach ($permissionList as $name => $spec) {
+ if (in_array($name, $permissions, true)) {
+ $values[$this->filterName($name)] = 1;
+
+ if (isset($spec['isFullPerm'])) {
+ $hasFullPerm = true;
+ }
+ }
+
+ if (in_array($name, $refusals, true)) {
+ $values[$this->filterName(self::DENY_PREFIX . $name)] = 1;
+ }
+ }
+
+ if ($hasFullPerm) {
+ unset($values[$this->filterName(Manager::MODULE_PERMISSION_NS . $moduleName)]);
+ }
+ }
+ }
+
+ foreach ($this->providedRestrictions as $moduleName => $restrictionList) {
+ foreach ($restrictionList as $name => $spec) {
+ if (isset($role->$name)) {
+ $values[$this->filterName($name)] = $role->$name;
+ }
+ }
+ }
+
+ return (object) $values;
+ }
+
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+
+ foreach ($this->providedRestrictions as $moduleName => $restrictionList) {
+ foreach ($restrictionList as $name => $spec) {
+ $elementName = $this->filterName($name);
+ if (isset($values[$elementName])) {
+ $values[$name] = $values[$elementName];
+ unset($values[$elementName]);
+ }
+ }
+ }
+
+ $permissions = [];
+ if (isset($values[self::WILDCARD_NAME]) && $values[self::WILDCARD_NAME]) {
+ $permissions[] = '*';
+ }
+
+ $refusals = [];
+ foreach ($this->providedPermissions as $moduleName => $permissionList) {
+ $hasFullPerm = false;
+ foreach ($permissionList as $name => $spec) {
+ $elementName = $this->filterName($name);
+ if (isset($values[$elementName]) && $values[$elementName]) {
+ $permissions[] = $name;
+
+ if (isset($spec['isFullPerm'])) {
+ $hasFullPerm = true;
+ }
+ }
+
+ $denyName = $this->filterName(self::DENY_PREFIX . $name);
+ if (isset($values[$denyName]) && $values[$denyName]) {
+ $refusals[] = $name;
+ }
+
+ unset($values[$elementName], $values[$denyName]);
+ }
+
+ $modulePermission = Manager::MODULE_PERMISSION_NS . $moduleName;
+ if ($hasFullPerm && ! in_array($modulePermission, $permissions, true)) {
+ $permissions[] = $modulePermission;
+ }
+ }
+
+ unset($values[self::WILDCARD_NAME]);
+ $values['refusals'] = join(',', $refusals);
+ $values['permissions'] = join(',', $permissions);
+ return ConfigForm::transformEmptyValuesToNull($values);
+ }
+
+ protected function getInsertMessage($success)
+ {
+ return $success ? $this->translate('Role created') : $this->translate('Role creation failed');
+ }
+
+ protected function getUpdateMessage($success)
+ {
+ return $success ? $this->translate('Role updated') : $this->translate('Role update failed');
+ }
+
+ protected function getDeleteMessage($success)
+ {
+ return $success ? $this->translate('Role removed') : $this->translate('Role removal failed');
+ }
+
+ protected function sortPermissions(&$permissions)
+ {
+ return uksort($permissions, function ($a, $b) use ($permissions) {
+ if (isset($permissions[$a]['isUsagePerm'])) {
+ return isset($permissions[$b]['isFullPerm']) ? 1 : -1;
+ } elseif (isset($permissions[$b]['isUsagePerm'])) {
+ return isset($permissions[$a]['isFullPerm']) ? -1 : 1;
+ }
+
+ $aParts = explode('/', $a);
+ $bParts = explode('/', $b);
+
+ do {
+ $a = array_shift($aParts);
+ $b = array_shift($bParts);
+ } while ($a === $b);
+
+ return strnatcmp($a ?? '', $b ?? '');
+ });
+ }
+
+ protected function collectRoles()
+ {
+ // Function to get all connected children. Used to avoid reference loops
+ $getChildren = function ($name, $children = []) use (&$getChildren) {
+ foreach ($this->repository->select()->where('parent', $name) as $child) {
+ if (isset($children[$child->name])) {
+ // Don't follow already established loops here,
+ // the user should be able to solve such in the UI
+ continue;
+ }
+
+ $children[$child->name] = true;
+ $children = $getChildren($child->name, $children);
+ }
+
+ return $children;
+ };
+
+ $children = $this->getIdentifier() !== null ? $getChildren($this->getIdentifier()) : [];
+
+ $names = [];
+ foreach ($this->repository->select() as $role) {
+ if ($role->name !== $this->getIdentifier() && ! isset($children[$role->name])) {
+ $names[] = $role->name;
+ }
+ }
+
+ return array_combine($names, $names);
+ }
+
+ public function isValid($formData)
+ {
+ $valid = parent::isValid($formData);
+
+ if ($valid && ConfigFormEventsHook::runIsValid($this) === false) {
+ foreach (ConfigFormEventsHook::getLastErrors() as $msg) {
+ $this->error($msg);
+ }
+
+ $valid = false;
+ }
+
+ return $valid;
+ }
+
+ public function onSuccess()
+ {
+ if (parent::onSuccess() === false) {
+ return false;
+ }
+
+ if ($this->getIdentifier() && ($newName = $this->getValue('name')) !== $this->getIdentifier()) {
+ $this->repository->update(
+ $this->getBaseTable(),
+ ['parent' => $newName],
+ Filter::where('parent', $this->getIdentifier())
+ );
+ }
+
+ if (ConfigFormEventsHook::runOnSuccess($this) === false) {
+ Notification::error($this->translate(
+ 'Configuration successfully stored. Though, one or more module hooks failed to run.'
+ . ' See logs for details'
+ ));
+ }
+ }
+
+ /**
+ * Collect permissions and restrictions provided by Icinga Web 2 and modules
+ *
+ * @return array[$permissions, $restrictions]
+ */
+ public static function collectProvidedPrivileges()
+ {
+ $providedPermissions['application'] = [
+ 'application/announcements' => [
+ 'description' => t('Allow to manage announcements')
+ ],
+ 'application/log' => [
+ 'description' => t('Allow to view the application log')
+ ],
+ 'config/*' => [
+ 'description' => t('Allow full config access')
+ ],
+ 'config/general' => [
+ 'description' => t('Allow to adjust the general configuration')
+ ],
+ 'config/modules' => [
+ 'description' => t('Allow to enable/disable and configure modules')
+ ],
+ 'config/resources' => [
+ 'description' => t('Allow to manage resources')
+ ],
+ 'config/navigation' => [
+ 'description' => t('Allow to view and adjust shared navigation items')
+ ],
+ 'config/access-control/*' => [
+ 'description' => t('Allow to fully manage access-control')
+ ],
+ 'config/access-control/users' => [
+ 'description' => t('Allow to manage user accounts')
+ ],
+ 'config/access-control/groups' => [
+ 'description' => t('Allow to manage user groups')
+ ],
+ 'config/access-control/roles' => [
+ 'description' => t('Allow to manage roles')
+ ],
+ 'user/*' => [
+ 'description' => t('Allow all account related functionalities')
+ ],
+ 'user/password-change' => [
+ 'description' => t('Allow password changes in the account preferences')
+ ],
+ 'user/application/stacktraces' => [
+ 'description' => t('Allow to adjust in the preferences whether to show stacktraces')
+ ],
+ 'user/share/navigation' => [
+ 'description' => t('Allow to share navigation items')
+ ],
+ 'application/sessions' => [
+ 'description' => t('Allow to manage user sessions')
+ ]
+ ];
+
+ $providedRestrictions['application'] = [
+ 'application/share/users' => [
+ 'description' => t('Restrict which users this role can share items and information with')
+ ],
+ 'application/share/groups' => [
+ 'description' => t('Restrict which groups this role can share items and information with')
+ ]
+ ];
+
+ $mm = Icinga::app()->getModuleManager();
+ foreach ($mm->listInstalledModules() as $moduleName) {
+ $modulePermission = Manager::MODULE_PERMISSION_NS . $moduleName;
+ $providedPermissions[$moduleName][$modulePermission] = [
+ 'isUsagePerm' => true,
+ 'label' => t('General Module Access'),
+ 'description' => sprintf(t('Allow access to module %s'), $moduleName)
+ ];
+
+ $module = $mm->getModule($moduleName, false);
+ $permissions = $module->getProvidedPermissions();
+
+ $providedPermissions[$moduleName][$moduleName . '/*'] = [
+ 'isFullPerm' => true,
+ 'label' => t('Full Module Access')
+ ];
+
+ foreach ($permissions as $permission) {
+ /** @var object $permission */
+ $providedPermissions[$moduleName][$permission->name] = [
+ 'description' => $permission->description
+ ];
+ }
+
+ foreach ($module->getProvidedRestrictions() as $restriction) {
+ $providedRestrictions[$moduleName][$restriction->name] = [
+ 'description' => $restriction->description
+ ];
+ }
+ }
+
+ return [$providedPermissions, $providedRestrictions];
+ }
+}
diff --git a/application/layouts/scripts/body.phtml b/application/layouts/scripts/body.phtml
new file mode 100644
index 0000000..87b570b
--- /dev/null
+++ b/application/layouts/scripts/body.phtml
@@ -0,0 +1,98 @@
+<?php
+
+use Icinga\Web\Url;
+use Icinga\Web\Notification;
+use Icinga\Authentication\Auth;
+use ipl\Html\HtmlString;
+use ipl\Web\Widget\Icon;
+
+$moduleName = $this->layout()->moduleName;
+if ($moduleName !== 'default') {
+ $moduleClass = ' icinga-module module-' . $moduleName;
+} else {
+ $moduleClass = '';
+}
+
+$refresh = '';
+if ($this->layout()->autorefreshInterval) {
+ $refresh = ' data-icinga-refresh="' . $this->layout()->autorefreshInterval . '"';
+}
+
+if ($this->layout()->inlineLayout) {
+ $inlineLayoutScript = $this->layout()->inlineLayout . '.phtml';
+} else {
+ $inlineLayoutScript = 'inline.phtml';
+}
+
+?>
+<div id="header">
+ <div id="announcements" class="container">
+ <?= $this->widget('announcements') ?>
+ </div>
+</div>
+<div id="content-wrapper">
+<?php if (! $this->layout()->isIframe): ?>
+ <div id="sidebar">
+ <div id="header-logo-container">
+ <?= $this->qlink(
+ '',
+ Auth::getInstance()->isAuthenticated() ? 'dashboard' : '',
+ null,
+ array(
+ 'aria-hidden' => 'true',
+ 'data-base-target' => '_main',
+ 'id' => 'header-logo'
+ )
+ ); ?>
+ <div id="mobile-menu-toggle">
+ <button type="button"><?= $this->icon('menu') ?><?= $this->icon('cancel') ?></button>
+ </div>
+ </div>
+ <?= $this->render('parts/navigation.phtml'); ?>
+ </div>
+<?php endif ?>
+ <div id="main" role="main">
+ <div id="col1"
+ class="container<?= $moduleClass ?>"
+ <?php if ($moduleName): ?>
+ data-icinga-module="<?= $moduleName ?>"
+ <?php endif ?>
+ data-icinga-url="<?= $this->escape(Url::fromRequest()->without('renderLayout')->getAbsoluteUrl()); ?>"
+ <?= $refresh; ?>
+ >
+ <?= $this->render($inlineLayoutScript) ?>
+ </div>
+ <div id="col2" class="container"></div>
+ <div id="col3" class="container"></div>
+ </div>
+</div>
+<div id="footer">
+ <ul role="alert" id="notifications"><?php
+
+ $notifications = Notification::getInstance();
+ if ($notifications->hasMessages()) {
+ foreach ($notifications->popMessages() as $m) {
+ switch ($m->type) {
+ case 'success':
+ $icon = new HtmlString(new Icon('check-circle'));
+ break;
+ case 'error':
+ $icon = new HtmlString(new Icon('times'));
+ break;
+ case 'warning':
+ $icon = new HtmlString(new Icon('exclamation-triangle'));
+ break;
+ case 'info':
+ $icon = new HtmlString(new Icon('info-circle'));
+ break;
+ default:
+ $icon = '';
+ break;
+ }
+
+ echo '<li class="' . $m->type . '">' . $icon . $this->escape($m->message) . '</li>';
+ }
+ }
+ ?></ul>
+ <div id="application-state-summary" class="container" data-icinga-url="<?= $this->url('application-state/summary') ?>" data-last-update="-1" data-icinga-refresh="60"></div>
+</div>
diff --git a/application/layouts/scripts/external-logout.phtml b/application/layouts/scripts/external-logout.phtml
new file mode 100644
index 0000000..19b7e32
--- /dev/null
+++ b/application/layouts/scripts/external-logout.phtml
@@ -0,0 +1,34 @@
+<?php
+
+use ipl\I18n\Locale;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\StaticTranslator;
+
+/** @var GettextTranslator $translator */
+$translator = StaticTranslator::$instance;
+
+$lang = (new Locale())->parseLocale($translator->getLocale())->language;
+$showFullscreen = $this->layout()->showFullscreen;
+$innerLayoutScript = $this->layout()->innerLayout . '.phtml';
+
+?><!DOCTYPE html>
+<html class="no-js" lang="<?= $lang ?>">
+<head>
+ <meta charset="utf-8">
+ <meta name="google" value="notranslate">
+ <meta http-equiv="cleartype" content="on">
+ <title><?= $this->title ? $this->escape($this->title) : $this->defaultTitle ?></title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="application-name" content="Icinga Web 2">
+ <meta name="apple-mobile-web-app-title" content="Icinga">
+ <link rel="mask-icon" href="<?= $this->baseUrl('img/website-icon.svg') ?>" color="#0096BF">
+ <link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
+ <link rel="apple-touch-icon" href="<?= $this->baseUrl('img/touch-icon.png') ?>">
+</head>
+<body id="body">
+<div id="layout" class="default-layout<?php if ($showFullscreen): ?> fullscreen-layout<?php endif ?>">
+ <?= $this->render($innerLayoutScript); ?>
+</div>
+</body>
+</html>
diff --git a/application/layouts/scripts/guest-error.phtml b/application/layouts/scripts/guest-error.phtml
new file mode 100644
index 0000000..49cdd68
--- /dev/null
+++ b/application/layouts/scripts/guest-error.phtml
@@ -0,0 +1,10 @@
+<div id="guest-error">
+ <div class="centered-ghost">
+ <div class="centered-content">
+ <div id="icinga-logo" aria-hidden="true"></div>
+ <div id="guest-error-message">
+ <?= $this->render('inline.phtml') ?>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/application/layouts/scripts/inline.phtml b/application/layouts/scripts/inline.phtml
new file mode 100644
index 0000000..47d5672
--- /dev/null
+++ b/application/layouts/scripts/inline.phtml
@@ -0,0 +1,2 @@
+<?= $this->layout()->content ?>
+<?= $this->layout()->benchmark ?>
diff --git a/application/layouts/scripts/layout.phtml b/application/layouts/scripts/layout.phtml
new file mode 100644
index 0000000..880c2a9
--- /dev/null
+++ b/application/layouts/scripts/layout.phtml
@@ -0,0 +1,107 @@
+<?php
+
+use ipl\I18n\Locale;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\StaticTranslator;
+use ipl\Web\Widget\Icon;
+
+if (array_key_exists('_dev', $_GET)) {
+ $jsfile = 'js/icinga.dev.js';
+ $cssfile = 'css/icinga.css';
+} else {
+ $jsfile = 'js/icinga.min.js';
+ $cssfile = 'css/icinga.min.css';
+}
+
+/** @var GettextTranslator $translator */
+$translator = StaticTranslator::$instance;
+
+$lang = (new Locale())->parseLocale($translator->getLocale())->language;
+$timezone = date_default_timezone_get();
+$isIframe = $this->layout()->isIframe;
+$showFullscreen = $this->layout()->showFullscreen;
+$iframeClass = $isIframe ? ' iframe' : '';
+$innerLayoutScript = $this->layout()->innerLayout . '.phtml';
+
+?><!DOCTYPE html>
+<html class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>">
+<head>
+ <meta charset="utf-8">
+ <meta name="google" value="notranslate">
+ <meta http-equiv="cleartype" content="on">
+ <title><?= $this->title ? $this->escape($this->title) . ' :: ' : '' ?><?= $this->defaultTitle ?></title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="application-name" content="Icinga Web 2">
+ <meta name="apple-mobile-web-app-title" content="Icinga">
+ <link rel="mask-icon" href="<?= $this->baseUrl('img/website-icon.svg') ?>" color="#0096BF">
+<?php if ($isIframe): ?>
+ <base target="_parent">
+<?php else: ?>
+ <base href="<?= $this->baseUrl(); ?>/">
+ <script type="text/javascript">
+ (function() {
+ var html = document.getElementsByTagName('html')[0];
+ html.className = html.className.replace(/no-js/, 'js');
+ }());
+ </script>
+<?php endif ?>
+ <link rel="stylesheet" href="<?= $this->href($cssfile) ?>" media="all" type="text/css" />
+ <link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
+ <link rel="apple-touch-icon" href="<?= $this->baseUrl('img/touch-icon.png') ?>">
+</head>
+<body id="body" class="loading">
+<pre id="responsive-debug"></pre>
+<div id="layout" class="default-layout<?php if ($showFullscreen): ?> fullscreen-layout<?php endif ?>">
+<?= $this->render($innerLayoutScript); ?>
+</div>
+<script type="text/javascript">
+ (function() {
+ if (document.defaultView && document.defaultView.getComputedStyle) {
+ var matched;
+ var html = document.getElementsByTagName('html')[0];
+ var element = document.getElementById('layout');
+ var name = document.defaultView
+ .getComputedStyle(html)['font-family']
+ .replace(/['",]/g, '');
+
+ if (null !== (matched = name.match(/^([a-z]+)-layout$/))) {
+ element.className = element.className.replace('default-layout', name);
+ if ('object' === typeof window.console) {
+ window.console.log('Icinga Web 2: setting initial layout to ' + name);
+ }
+ }
+ }
+ }());
+</script>
+<div id="collapsible-control-ghost" class="collapsible-control">
+ <button type="button">
+ <!-- TODO: Accessibility attributes are missing since usage of the Icon class -->
+ <?= new Icon('angle-double-down', ['class' => 'expand-icon', 'title' => $this->translate('Expand')]) ?>
+ <?= new Icon('angle-double-up', ['class' => 'collapse-icon', 'title' => $this->translate('Collapse')]) ?>
+ </button>
+</div>
+<div id="modal-ghost">
+ <div>
+ <section class="modal-window">
+ <div class="modal-header">
+ <h1></h1>
+ <button type="button"><?= $this->icon('cancel') ?></button>
+ </div>
+ <div class="modal-area">
+ <div id="modal-content" data-base-target="modal-content" tabindex data-no-icinga-ajax></div>
+ </div>
+ </section>
+ </div>
+</div>
+<script type="text/javascript" src="<?= $this->href($jsfile) ?>"></script>
+<script type="text/javascript">
+window.name = '<?= $this->protectId('Icinga') ?>';
+var icinga = new Icinga({
+ baseUrl: '<?= $this->baseUrl(); ?>',
+ locale: '<?= $lang; ?>',
+ timezone: '<?= $timezone ?>'
+});
+</script>
+</body>
+</html>
diff --git a/application/layouts/scripts/parts/navigation.phtml b/application/layouts/scripts/parts/navigation.phtml
new file mode 100644
index 0000000..dd973f5
--- /dev/null
+++ b/application/layouts/scripts/parts/navigation.phtml
@@ -0,0 +1,35 @@
+<?php
+
+use Icinga\Web\Menu;
+
+// Don't render a menu for unauthenticated users unless menu is auth aware
+if (! $this->auth()->isAuthenticated()) {
+ return;
+}
+
+?>
+<div class="skip-links">
+ <h1 class="sr-only"><?= t('Accessibility Skip Links') ?></h1>
+ <ul>
+ <li>
+ <a href="#main"><?= t('Skip to Content') ?></a>
+ </li>
+ <li>
+ <?= $this->layout()->autoRefreshForm ?>
+ </li>
+ </ul>
+</div>
+<div id="menu" data-last-update="-1" data-base-target="_main" class="container"
+ data-icinga-url="<?= $this->href('layout/menu') ?>" data-icinga-refresh="15">
+ <?= $this->partial(
+ 'layout/menu.phtml',
+ 'default',
+ array(
+ 'menuRenderer' => (new Menu())->getRenderer()->setUseStandardItemRenderer()
+ )
+ ) ?>
+</div>
+<button id="toggle-sidebar" title="<?= $this->translate('Toggle Menu') ?>">
+ <i id="close-sidebar" class="icon-angle-double-left"></i>
+ <i id="open-sidebar" class="icon-angle-double-right"></i>
+</button>
diff --git a/application/layouts/scripts/pdf.phtml b/application/layouts/scripts/pdf.phtml
new file mode 100644
index 0000000..87d07f8
--- /dev/null
+++ b/application/layouts/scripts/pdf.phtml
@@ -0,0 +1,44 @@
+<?php
+
+use Icinga\Application\Icinga;
+use Icinga\Web\StyleSheet;
+
+
+$moduleName = $this->layout()->moduleName;
+if ($moduleName !== 'default') {
+ $moduleClass = ' icinga-module module-' . $moduleName;
+} else {
+ $moduleClass = '';
+}
+
+$logoPath = Icinga::app()->getBootstrapDirectory() . '/img/icinga-logo-big-dark.png';
+$logo = base64_encode(file_get_contents($logoPath));
+
+
+?><!DOCTYPE html>
+<html>
+<head>
+<style>
+<?= StyleSheet::forPdf() ?>
+</style>
+<base href="<?= $this->serverUrl() ?>">
+</head>
+<body>
+<div id="header">
+ <table>
+ <tbody>
+ <tr>
+ <th class="title"><?= $this->escape($this->title) ?></th>
+ <td style="text-align: right;"><img width="75" src="data:image/png;base64,<?= $logo ?>"></td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+<div id="footer">
+ <div class="page-number"></div>
+</div>
+<div class="<?= $moduleClass ?>">
+ <?= $this->render('inline.phtml') ?>
+</div>
+</body>
+</html>
diff --git a/application/views/helpers/CreateTicketLinks.php b/application/views/helpers/CreateTicketLinks.php
new file mode 100644
index 0000000..4f8a272
--- /dev/null
+++ b/application/views/helpers/CreateTicketLinks.php
@@ -0,0 +1,23 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+/**
+ * Helper for creating ticket links from ticket hooks
+ */
+class Zend_View_Helper_CreateTicketLinks extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create ticket links form ticket hooks
+ *
+ * @param string $text
+ *
+ * @return string
+ * @see \Icinga\Application\Hook\TicketHook::createLinks()
+ */
+ public function createTicketLinks($text)
+ {
+ $tickets = $this->view->tickets;
+ /** @var \Icinga\Application\Hook\TicketHook $tickets */
+ return isset($tickets) ? $tickets->createLinks($text) : $text;
+ }
+}
diff --git a/application/views/helpers/FormDate.php b/application/views/helpers/FormDate.php
new file mode 100644
index 0000000..39e6d94
--- /dev/null
+++ b/application/views/helpers/FormDate.php
@@ -0,0 +1,46 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+/**
+ * Render date input controls
+ */
+class Zend_View_Helper_FormDate extends Zend_View_Helper_FormElement
+{
+ /**
+ * Render the date input control
+ *
+ * @param string $name
+ * @param int $value
+ * @param array $attribs
+ *
+ * @return string The rendered date input control
+ */
+ public function formDate($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+
+ extract($info); // name, id, value, attribs, options, listsep, disable
+ /** @var string $id */
+ /** @var bool $disable */
+
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ /** @var \Icinga\Web\View $view */
+ $view = $this->view;
+
+ $html5 = sprintf(
+ '<input type="date" name="%s" id="%s" value="%s"%s%s%s',
+ $view->escape($name),
+ $view->escape($id),
+ $view->escape($value),
+ $disabled,
+ $this->_htmlAttribs($attribs),
+ $this->getClosingBracket()
+ );
+
+ return $html5;
+ }
+}
diff --git a/application/views/helpers/FormDateTime.php b/application/views/helpers/FormDateTime.php
new file mode 100644
index 0000000..de5eb4b
--- /dev/null
+++ b/application/views/helpers/FormDateTime.php
@@ -0,0 +1,63 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Render date-and-time input controls
+ */
+class Zend_View_Helper_FormDateTime extends Zend_View_Helper_FormElement
+{
+ /**
+ * Format date and time
+ *
+ * @param DateTime $dateTime
+ * @param bool $local
+ *
+ * @return string
+ */
+ public function formatDate(DateTime $dateTime, $local)
+ {
+ $format = (bool) $local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339;
+ return $dateTime->format($format);
+ }
+
+ /**
+ * Render the date-and-time input control
+ *
+ * @param string $name The element name
+ * @param DateTime $value The default timestamp
+ * @param array $attribs Attributes for the element tag
+ *
+ * @return string The element XHTML
+ */
+ public function formDateTime($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, id, value, attribs, options, listsep, disable
+ /** @var string $id */
+ /** @var bool $disable */
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+ if ($value instanceof DateTime) {
+ // If value was valid, it's a DateTime object
+ $value = $this->formatDate($value, $attribs['local']);
+ }
+ if (isset($attribs['placeholder']) && $attribs['placeholder'] instanceof DateTime) {
+ $attribs['placeholder'] = $this->formatDate($attribs['placeholder'], $attribs['local']);
+ }
+ $type = $attribs['local'] === true ? 'datetime-local' : 'datetime';
+ unset($attribs['local']); // Unset local to not render it again in $this->_htmlAttribs($attribs)
+ $html5 = sprintf(
+ '<input type="%s" data-use-datetime-picker name="%s" id="%s" step="1" value="%s"%s%s%s',
+ $type,
+ $this->view->escape($name),
+ $this->view->escape($id),
+ $this->view->escape($value),
+ $disabled,
+ $this->_htmlAttribs($attribs),
+ $this->getClosingBracket()
+ );
+ return $html5;
+ }
+}
diff --git a/application/views/helpers/FormNumber.php b/application/views/helpers/FormNumber.php
new file mode 100644
index 0000000..f447180
--- /dev/null
+++ b/application/views/helpers/FormNumber.php
@@ -0,0 +1,77 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Render number input controls
+ */
+class Zend_View_Helper_FormNumber extends Zend_View_Helper_FormElement
+{
+ /**
+ * Format a number
+ *
+ * @param $number
+ *
+ * @return string
+ */
+ public function formatNumber($number)
+ {
+ if (empty($number)) {
+ return $number;
+ }
+ return $this->view->escape(
+ sprintf(
+ ctype_digit((string) $number) ? '%d' : '%F',
+ $number
+ )
+ );
+ }
+
+ /**
+ * Render the number input control
+ *
+ * @param string $name
+ * @param int $value
+ * @param array $attribs
+ *
+ * @return string The rendered number input control
+ */
+ public function formNumber($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, id, value, attribs, options, listsep, disable
+ /** @var string $id */
+ /** @var bool $disable */
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+ $min = '';
+ if (isset($attribs['min'])) {
+ $min = sprintf(' min="%s"', $this->formatNumber($attribs['min']));
+ }
+ unset($attribs['min']); // Unset min to not render it again in $this->_htmlAttribs($attribs)
+ $max = '';
+ if (isset($attribs['max'])) {
+ $max = sprintf(' max="%s"', $this->formatNumber($attribs['max']));
+ }
+ unset($attribs['max']); // Unset max to not render it again in $this->_htmlAttribs($attribs)
+ $step = '';
+ if (isset($attribs['step'])) {
+ $step = sprintf(' step="%s"', $attribs['step'] === 'any' ? 'any' : $this->formatNumber($attribs['step']));
+ }
+ unset($attribs['step']); // Unset step to not render it again in $this->_htmlAttribs($attribs)
+ $html5 = sprintf(
+ '<input type="number" name="%s" id="%s" value="%s"%s%s%s%s%s%s',
+ $this->view->escape($name),
+ $this->view->escape($id),
+ $this->view->escape($this->formatNumber($value)),
+ $min,
+ $max,
+ $step,
+ $disabled,
+ $this->_htmlAttribs($attribs),
+ $this->getClosingBracket()
+ );
+ return $html5;
+ }
+}
diff --git a/application/views/helpers/FormTime.php b/application/views/helpers/FormTime.php
new file mode 100644
index 0000000..39d1b83
--- /dev/null
+++ b/application/views/helpers/FormTime.php
@@ -0,0 +1,46 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+/**
+ * Render time input controls
+ */
+class Zend_View_Helper_FormTime extends Zend_View_Helper_FormElement
+{
+ /**
+ * Render the time input control
+ *
+ * @param string $name
+ * @param int $value
+ * @param array $attribs
+ *
+ * @return string The rendered time input control
+ */
+ public function formTime($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+
+ extract($info); // name, id, value, attribs, options, listsep, disable
+ /** @var string $id */
+ /** @var bool $disable */
+
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ /** @var \Icinga\Web\View $view */
+ $view = $this->view;
+
+ $html5 = sprintf(
+ '<input type="time" name="%s" id="%s" value="%s"%s%s%s',
+ $view->escape($name),
+ $view->escape($id),
+ $view->escape($value),
+ $disabled,
+ $this->_htmlAttribs($attribs),
+ $this->getClosingBracket()
+ );
+
+ return $html5;
+ }
+}
diff --git a/application/views/helpers/ProtectId.php b/application/views/helpers/ProtectId.php
new file mode 100644
index 0000000..f6dc226
--- /dev/null
+++ b/application/views/helpers/ProtectId.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+/**
+ * Class Zend_View_Helper_Util
+ */
+class Zend_View_Helper_ProtectId extends Zend_View_Helper_Abstract
+{
+ public function protectId($id)
+ {
+ return Zend_Controller_Front::getInstance()->getRequest()->protectId($id);
+ }
+}
diff --git a/application/views/helpers/Util.php b/application/views/helpers/Util.php
new file mode 100644
index 0000000..7a3e410
--- /dev/null
+++ b/application/views/helpers/Util.php
@@ -0,0 +1,68 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Class Zend_View_Helper_Util
+ */
+class Zend_View_Helper_Util extends Zend_View_Helper_Abstract
+{
+ public function util()
+ {
+ return $this;
+ }
+
+ public static function showTimeSince($timestamp)
+ {
+ if (! $timestamp) {
+ return 'unknown';
+ }
+ $duration = time() - $timestamp;
+ if ($duration > 3600 * 24 * 3) {
+ if (date('Y') === date('Y', $timestamp)) {
+ return date('d.m.', $timestamp);
+ }
+ return date('m.Y', $timestamp);
+ }
+ return self::showHourMin($duration);
+ }
+
+ public static function showHourMin($sec)
+ {
+ $min = floor($sec / 60);
+ if ($min < 60) {
+ return $min . 'm ' . ($sec % 60) . 's';
+ }
+ $hour = floor($min / 60);
+ if ($hour < 24) {
+ return date('H:i', time() - $sec);
+ }
+ return floor($hour / 24) . 'd ' . ($hour % 24) . 'h';
+ }
+
+ public static function showSeconds($sec)
+ {
+ // Todo: localization
+ if ($sec < 1) {
+ return round($sec * 1000) . 'ms';
+ }
+ if ($sec < 60) {
+ return $sec . 's';
+ }
+ return floor($sec / 60) . 'm ' . ($sec % 60) . 's';
+ }
+
+ public static function showTime($timestamp)
+ {
+ // Todo: localization
+ if ($timestamp < 86400) {
+ return 'undef';
+ }
+ if (date('Ymd') === date('Ymd', $timestamp)) {
+ return date('H:i:s', $timestamp);
+ }
+ if (date('Y') === date('Y', $timestamp)) {
+ return date('H:i d.m.', $timestamp);
+ }
+ return date('H:i d.m.Y', $timestamp);
+ }
+}
diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml
new file mode 100644
index 0000000..e80cd89
--- /dev/null
+++ b/application/views/scripts/about/index.phtml
@@ -0,0 +1,171 @@
+<?php
+
+use ipl\Html\HtmlElement;
+use ipl\Web\Widget\Icon;
+
+?>
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div id="about" class="content">
+
+ <?= $this->img('img/icinga-logo-big.svg', null, array('class' => 'icinga-logo', 'width' => 194)) ?>
+
+ <section>
+ <table class="name-value-table">
+ <?php if (isset($version['appVersion'])): ?>
+ <tr>
+ <th><?= $this->translate('Icinga Web 2 Version') ?></th>
+ <td><?= $this->escape($version['appVersion']) ?></td>
+ </tr>
+ <?php endif ?>
+ <?php if (isset($version['gitCommitID'])): ?>
+ <tr>
+ <th><?= $this->translate('Git commit') ?></th>
+ <td><?= $this->escape($version['gitCommitID']) ?></td>
+ </tr>
+ <?php endif ?>
+ <tr>
+ <th><?= $this->translate('PHP Version') ?></th>
+ <td><?= $this->escape(PHP_VERSION) ?></td>
+ </tr>
+ <?php if (isset($version['gitCommitDate'])): ?>
+ <tr>
+ <th><?= $this->translate('Git commit date') ?></th>
+ <td><?= $this->escape($version['gitCommitDate']) ?></td>
+ </tr>
+ <?php endif ?>
+ </table>
+
+ <div class="external-links">
+ <div class="col">
+ <?=
+ HtmlElement::create('a', [
+ 'href' => 'https://icinga.com/support/',
+ 'target' => '_blank',
+ 'title' => $this->translate('Get Icinga Support')
+ ], [
+ new Icon('life-ring'),
+ $this->translate('Get Icinga Support'),
+ ]
+ );
+ ?>
+ </div>
+ <div class="col">
+ <?=
+ HtmlElement::create('a', [
+ 'href' => 'https://icinga.com/community/',
+ 'target' => '_blank',
+ 'title' => $this->translate('Icinga Community')
+ ], [
+ new Icon('globe-europe'),
+ $this->translate('Icinga Community'),
+ ]
+ );
+ ?>
+ </div>
+ <div class="col">
+ <?=
+ HtmlElement::create('a', [
+ 'href' => 'https://github.com/icinga/icingaweb2/issues',
+ 'target' => '_blank',
+ 'title' => $this->translate('Icinga Community')
+ ], [
+ new Icon('bullhorn'),
+ $this->translate('Report a bug'),
+ ]
+ );
+ ?>
+ </div>
+ <div class="col">
+ <?=
+ HtmlElement::create('a', [
+ 'href' => 'https://icinga.com/docs/icinga-web-2/'
+ . (isset($version['docVersion']) ? $version['docVersion'] : 'latest'),
+ 'target' => '_blank',
+ 'title' => $this->translate('Icinga Documentation')
+ ], [
+ new Icon('book'),
+ $this->translate('Icinga Documentation'),
+ ]
+ );
+ ?>
+ </div>
+ </div>
+
+ <h2><?= $this->translate('Loaded Libraries') ?></h2>
+ <table class="name-value-table" data-base-target="_next">
+ <?php foreach ($libraries as $library): ?>
+ <tr>
+ <th>
+ <?= $this->escape($library->getName()) ?>
+ </th>
+ <td>
+ <?= $this->escape($library->getVersion()) ?: '-' ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </table>
+
+ <h2><?= $this->translate('Loaded Modules') ?></h2>
+ <table class="name-value-table" data-base-target="_next">
+ <?php foreach ($modules as $module): ?>
+ <tr>
+ <th>
+ <?= $this->escape($module->getName()) ?>
+ </th>
+ <td>
+ <td>
+ <?= $this->escape($module->getVersion()) ?>
+ </td>
+ <td>
+ <?php if ($this->hasPermission('config/modules')): ?>
+ <?= $this->qlink(
+ $this->translate('Configure'),
+ 'config/module/',
+ array('name' => $module->getName()),
+ array('title' => sprintf($this->translate('Show the overview of the %s module'), $module->getName()))
+ ) ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </table>
+ </section>
+
+ <footer>
+ <div class="about-copyright">
+ <?= $this->translate('Copyright') ?>
+ <span>&copy; 2013-<?= date('Y') ?></span>
+ <?= $this->qlink(
+ 'Icinga GmbH',
+ 'https://icinga.com',
+ null,
+ array(
+ 'target' => '_blank'
+ )
+ ) ?>
+ </div>
+ <div class="about-social">
+ <?= $this->qlink(
+ null,
+ 'https://www.twitter.com/icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'twitter',
+ 'title' => $this->translate('Icinga on Twitter')
+ )
+ ) ?> <?= $this->qlink(
+ null,
+ 'https://www.facebook.com/icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'facebook-squared',
+ 'title' => $this->translate('Icinga on Facebook')
+ )
+ ) ?>
+ </div>
+ </footer>
+</div>
diff --git a/application/views/scripts/account/index.phtml b/application/views/scripts/account/index.phtml
new file mode 100644
index 0000000..efc2bcb
--- /dev/null
+++ b/application/views/scripts/account/index.phtml
@@ -0,0 +1,11 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+<?php if (isset($changePasswordForm)): ?>
+ <h1><?= $this->translate('Account') ?></h1>
+ <?= $changePasswordForm ?>
+<?php endif ?>
+ <h1><?= $this->translate('Preferences') ?></h1>
+ <?= $form ?>
+</div>
diff --git a/application/views/scripts/announcements/index.phtml b/application/views/scripts/announcements/index.phtml
new file mode 100644
index 0000000..ff87c66
--- /dev/null
+++ b/application/views/scripts/announcements/index.phtml
@@ -0,0 +1,71 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if ($this->hasPermission('application/announcements')) {
+ echo $this->qlink(
+ $this->translate('Create a New Announcement') ,
+ 'announcements/new',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new announcement')
+ )
+ );
+} ?>
+<?php if (empty($this->announcements)): ?>
+ <p><?= $this->translate('No announcements found.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next" class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Author') ?></th>
+ <th><?= $this->translate('Message') ?></th>
+ <th><?= $this->translate('Start') ?></th>
+ <th><?= $this->translate('End') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->announcements as $announcement): /** @var object $announcement */ ?>
+ <tr>
+ <td><?= $this->escape($announcement->author) ?></td>
+ <?php if ($this->hasPermission('application/announcements')): ?>
+ <td>
+ <a href="<?= $this->href('announcements/update', array('id' => $announcement->id)) ?>">
+ <?= $this->ellipsis($this->escape($announcement->message), 100) ?>
+ </a>
+ </td>
+ <?php else: ?>
+ <td><?= $this->ellipsis($this->escape($announcement->message), 100) ?></td>
+ <?php endif ?>
+ <td><?= $this->formatDateTime($announcement->start) ?></td>
+ <td><?= $this->formatDateTime($announcement->end) ?></td>
+ <?php if ($this->hasPermission('application/announcements')): ?>
+ <td class="icon-col"><?= $this->qlink(
+ null,
+ 'announcements/remove',
+ array('id' => $announcement->id),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => $this->translate('Remove this announcement')
+ )
+ ) ?></td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/authentication/login.phtml b/application/views/scripts/authentication/login.phtml
new file mode 100644
index 0000000..167a468
--- /dev/null
+++ b/application/views/scripts/authentication/login.phtml
@@ -0,0 +1,74 @@
+<div id="login">
+ <div class="login-form" data-base-target="layout">
+ <div role="status" class="sr-only">
+ <?= $this->translate(
+ 'Welcome to Icinga Web 2. For users of the screen reader Jaws full and expectant compliant'
+ . ' accessibility is possible only with use of the Firefox browser. VoiceOver on Mac OS X is tested on'
+ . ' Chrome, Safari and Firefox.'
+ ) ?>
+ </div>
+ <div class="logo-wrapper"><div id="icinga-logo" aria-hidden="true"></div></div>
+ <?php if ($requiresSetup): ?>
+ <p class="config-note"><?= sprintf(
+ $this->translate(
+ 'It appears that you did not configure Icinga Web 2 yet so it\'s not possible to log in without any defined '
+ . 'authentication method. Please define a authentication method by following the instructions in the'
+ . ' %1$sdocumentation%3$s or by using our %2$sweb-based setup-wizard%3$s.'
+ ),
+ '<a href="https://icinga.com/docs/icinga-web-2/latest/doc/05-Authentication/#authentication" title="'
+ . $this->translate('Icinga Web 2 Documentation') . '">',
+ '<a href="' . $this->href('setup') . '" title="' . $this->translate('Icinga Web 2 Setup-Wizard') . '">',
+ '</a>'
+ ) ?></p>
+ <?php endif ?>
+ <?= $this->form ?>
+ <div id="login-footer">
+ <p>Icinga Web 2 &copy; 2013-<?= date('Y') ?></p>
+ <?= $this->qlink($this->translate('icinga.com'), 'https://icinga.com') ?>
+ </div>
+ </div>
+ <ul id="social">
+ <li>
+ <?= $this->qlink(
+ null,
+ 'https://twitter.com/icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'twitter',
+ 'title' => $this->translate('Icinga on Twitter')
+ )
+ ) ?>
+ </li>
+ <li>
+ <?= $this->qlink(
+ null,
+ 'https://www.facebook.com/icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'facebook-squared',
+ 'title' => $this->translate('Icinga on Facebook')
+ )
+ ) ?>
+ </li>
+ <li><?= $this->qlink(
+ null,
+ 'https://github.com/Icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'github-circled',
+ 'title' => $this->translate('Icinga on GitHub')
+ )
+ ) ?>
+ </li>
+ </ul>
+</div>
+<div id="orb-analytics" class="orb" ><?= $this->img('img/orb-analytics.png'); ?></div>
+<div id="orb-automation" class="orb"><?= $this->img('img/orb-automation.png'); ?></div>
+<div id="orb-cloud" class="orb"><?= $this->img('img/orb-cloud.png'); ?></div>
+<div id="orb-icinga" class="orb"><?= $this->img('img/orb-icinga.png'); ?></div>
+<div id="orb-infrastructure" class="orb"><?= $this->img('img/orb-infrastructure.png'); ?></div>
+<div id="orb-metrics" class="orb" ><?= $this->img('img/orb-metrics.png'); ?></div>
+<div id="orb-notifactions" class="orb"><?= $this->img('img/orb-notifications.png'); ?></div>
diff --git a/application/views/scripts/authentication/logout.phtml b/application/views/scripts/authentication/logout.phtml
new file mode 100644
index 0000000..d4bd78e
--- /dev/null
+++ b/application/views/scripts/authentication/logout.phtml
@@ -0,0 +1,79 @@
+<!--
+ This view provides a workaround to logout from an external authentication provider, in case external
+ authentication was configured (the default is to handle authentications internally in Icingaweb2).
+
+ The <a href="http://tools.ietf.org/html/rfc2617">Http Basic and Digest Authentication</a> is not
+ designed to handle logout. When the user has provided valid credentials, the client is adviced to include these
+ in every further request until the browser was closed. To allow logout and to allow the user to change the
+ logged-in user this JavaScript provides a workaround to force a new authentication prompt in most browsers.
+-->
+<div class="content">
+ <div id="icinga-logo" aria-hidden="true"></div>
+ <div class="alert alert-warning" id="logout-status">
+ <b><?= $this->translate('Logging out...'); ?></b>
+ <br>
+ <?= $this->translate(
+ 'If this message does not disappear, it might be necessary to quit the'
+ . ' current session manually by clearing the cache, or by closing the current'
+ . ' browser session.'
+ ); ?>
+ </div>
+
+ <div class="container">
+ <a href="<?= $this->href('dashboard'); ?>"><?= $this->translate('Login'); ?></a>
+ </div>
+</div>
+<script type="text/javascript">
+ /*
+ * When JavaScript is available, trigger an XmlHTTPRequest with the non-existing user 'logout' and abort it
+ * before it is able to finish. This will cause the browser to show a new authentication prompt in the next
+ * request.
+ */
+ document.addEventListener('DOMContentLoaded', function () {
+ var msg = document.getElementById('logout-status');
+ try {
+ if (navigator.userAgent.toLowerCase().indexOf('msie') !== -1) {
+ document.execCommand('ClearAuthenticationCache');
+ } else {
+ var xhttp = new XMLHttpRequest();
+ xhttp.open('GET', 'arbitrary url', true, 'logout', 'logout');
+ xhttp.send('');
+ xhttp.abort();
+ }
+ } catch (e) {
+ }
+ msg.innerHTML = '<?= $this->translate('Logout successful!'); ?>';
+ msg.className = 'alert alert-success';
+ });
+</script>
+<style type="text/css">
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ background-color: #0095bf;
+ color: white;
+ }
+ .content {
+ text-align: center;
+ }
+
+ #icinga-logo {
+ background-image: url('../img/icinga-logo-big.svg');
+ background-position: center bottom;
+ background-repeat: no-repeat;
+ background-size: contain;
+ height: 177px;
+ margin-top: 10em;
+ width: 100%;
+ }
+
+ #logout-status {
+ margin: 2em 0 1em;
+ font-size: 2em;
+ font-weight: bold;
+ }
+
+ .container a {
+ color: white;
+ font-size: 1.5em;
+ }
+</style>
diff --git a/application/views/scripts/config/devtools.phtml b/application/views/scripts/config/devtools.phtml
new file mode 100644
index 0000000..245a71a
--- /dev/null
+++ b/application/views/scripts/config/devtools.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+<?= $this->tabs ?>
+</div>
+<table class="avp">
+<tr><th><?= $this->translate('UI Debug') ?></th><td><a href="javascript:void(0);" onclick="icinga.ui.toggleDebug();"><?= $this->translate('toggle') ?></td></tr>
+</table>
diff --git a/application/views/scripts/config/general.phtml b/application/views/scripts/config/general.phtml
new file mode 100644
index 0000000..13a8ed9
--- /dev/null
+++ b/application/views/scripts/config/general.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?= $form ?>
+</div>
diff --git a/application/views/scripts/config/module-configuration-error.phtml b/application/views/scripts/config/module-configuration-error.phtml
new file mode 100644
index 0000000..85fb128
--- /dev/null
+++ b/application/views/scripts/config/module-configuration-error.phtml
@@ -0,0 +1,28 @@
+<?php
+ $action = (isset($this->action)) ? $this->action : 'do something with';
+ $moduleName = $this->moduleName;
+ $exceptionMessage = $this->exceptionMessage;
+?>
+<?= $this->tabs->render($this); ?>
+<br/>
+<div>
+ <h1>Could not <?= $action; ?> module "<?= $moduleName; ?>"</h1>
+ <p>
+ While operation the following error occurred:
+ <br />
+ <?= $exceptionMessage; ?>
+ </p>
+</div>
+
+<p>
+ This could have one or more of the following reasons:
+<ul>
+ <li>No file permissions to write into module directory</li>
+ <li>Errors on filesystems: Mount points, operational errors </li>
+ <li>General application error</li>
+</ul>
+</p>
+
+<p>
+ Details can be seen in your application log (if you don't have access to this file, call your administrator in this case).
+</p> \ No newline at end of file
diff --git a/application/views/scripts/config/module.phtml b/application/views/scripts/config/module.phtml
new file mode 100644
index 0000000..6d41ab2
--- /dev/null
+++ b/application/views/scripts/config/module.phtml
@@ -0,0 +1,136 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <?php if (! $module): ?>
+ <?= $this->translate('There is no such module installed.') ?>
+ <?php return; endif ?>
+ <?php
+ $requiredMods = $module->getRequiredModules();
+ $requiredLibs = $module->getRequiredLibraries();
+ $restrictions = $module->getProvidedRestrictions();
+ $permissions = $module->getProvidedPermissions();
+ $unmetDependencies = $moduleManager->hasUnmetDependencies($module->getName());
+ $isIcingadbSupported = isset($requiredMods['icingadb']);
+ $state = $moduleData->enabled ? ($moduleData->loaded ? 'enabled' : 'failed') : 'disabled';
+ ?>
+ <table class="name-value-table">
+ <tr>
+ <th><?= $this->escape($this->translate('Name')) ?></th>
+ <td><?= $this->escape($module->getName()) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <td>
+ <?= $state ?>
+ <?php if (isset($this->toggleForm)): ?>
+ <?php if ($moduleData->enabled || ! $unmetDependencies): ?>
+ <?= $this->toggleForm ?>
+ <?php else: ?>
+ <?= $this->icon('attention-alt', $this->translate('Module can\'t be enabled due to unmet dependencies')) ?>
+ <?php endif ?>
+ <?php endif ?>
+ </td>
+ <tr>
+ <th><?= $this->escape($this->translate('Version')) ?></th>
+ <td><?= $this->escape($module->getVersion()) ?></td>
+ </tr>
+ <?php if (isset($moduleGitCommitId) && $moduleGitCommitId !== false): ?>
+ <tr>
+ <th><?= $this->escape($this->translate('Git commit')) ?></th>
+ <td><?= $this->escape($moduleGitCommitId) ?></td>
+ </tr>
+ <?php endif ?>
+ <tr>
+ <th><?= $this->escape($this->translate('Description')) ?></th>
+ <td>
+ <strong><?= $this->escape($module->getTitle()) ?></strong><br>
+ <?= nl2br($this->escape($module->getDescription())) ?>
+ </td>
+ </tr>
+ <tr>
+ <th><?= $this->escape($this->translate('Dependencies')) ?></th>
+ <td class="module-dependencies">
+ <?php if (empty($requiredLibs) && empty($requiredMods)): ?>
+ <?= $this->translate('This module has no dependencies') ?>
+ <?php else: ?>
+ <?php if ($unmetDependencies): ?>
+ <strong class="unmet-dependencies">
+ <?= $this->translate('Unmet dependencies found! Module can\'t be enabled unless all dependencies are met.') ?>
+ </strong>
+ <?php endif ?>
+ <?php if (! empty($requiredLibs)): ?>
+ <table class="name-value-table">
+ <caption><?= $this->translate('Libraries') ?></caption>
+ <?php foreach ($requiredLibs as $libraryName => $versionString): ?>
+ <tr>
+ <th><?= $this->escape($libraryName) ?></th>
+ <td>
+ <?php if ($libraries->has($libraryName, $versionString === true ? null : $versionString)): ?>
+ <?= $versionString === true ? '*' : $this->escape($versionString) ?>
+ <?php else: ?>
+ <span class="missing"><?= $versionString === true ? '*' : $this->escape($versionString) ?></span>
+ <?php if (($library = $libraries->get($libraryName)) !== null): ?>
+ (<?= $library->getVersion() ?>)
+ <?php endif ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </table>
+ <?php endif ?>
+ <?php if (! empty($requiredMods)): ?>
+ <table class="name-value-table">
+ <caption><?= $this->translate('Modules') ?></caption>
+ <?php foreach ($requiredMods as $moduleName => $versionString): ?>
+ <?php if ($moduleName === 'monitoring' && $isIcingadbSupported && $moduleManager->has('icingadb', $requiredMods['icingadb'])) : ?>
+ <?php continue; ?>
+ <?php endif ?>
+ <tr>
+ <th><?= $this->escape($moduleName) ?></th>
+ <td>
+ <?php if ($moduleManager->has($moduleName, $versionString === true ? null : $versionString)): ?>
+ <?= $versionString === true ? '*' : $this->escape($versionString) ?>
+ <?php else: ?>
+ <span <?= ($moduleName === 'icingadb' && isset($requiredMods['monitoring']) && $moduleManager->has('monitoring', $requiredMods['monitoring'])) ? 'class="optional"' : 'class="missing"' ?>>
+ <?= $versionString === true ? '*' : $this->escape($versionString) ?>
+ </span>
+ <?php if (! $moduleManager->hasInstalled($moduleName)): ?>
+ (<?= $this->translate('not installed') ?>)
+ <?php else: ?>
+ (<?= $moduleManager->getModule($moduleName, false)->getVersion() ?><?= $moduleManager->hasEnabled($moduleName) ? '' : ', ' . $this->translate('disabled') ?>)
+ <?php endif ?>
+ <?php endif ?>
+ </td>
+ <?php if ($moduleName === 'monitoring' && $isIcingadbSupported) : ?>
+ <td class="or-separator"><?= $this->translate('or') ?></td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </table>
+ <?php endif ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php if (! empty($permissions)): ?>
+ <tr>
+ <th><?= $this->escape($this->translate('Permissions')) ?></th>
+ <td>
+ <?php foreach ($permissions as $permission): ?>
+ <strong><?= $this->escape($permission->name) ?></strong>: <?= $this->escape($permission->description) ?><br />
+ <?php endforeach ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ <?php if (! empty($restrictions)): ?>
+ <tr>
+ <th><?= $this->escape($this->translate('Restrictions')) ?></th>
+ <td>
+ <?php foreach ($restrictions as $restriction): ?>
+ <strong><?= $this->escape($restriction->name) ?></strong>: <?= $this->escape($restriction->description) ?><br />
+ <?php endforeach ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </table>
+</div>
diff --git a/application/views/scripts/config/modules.phtml b/application/views/scripts/config/modules.phtml
new file mode 100644
index 0000000..b13b378
--- /dev/null
+++ b/application/views/scripts/config/modules.phtml
@@ -0,0 +1,42 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+</div>
+<?php endif ?>
+<div class="content">
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Module') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($modules as $module): ?>
+ <tr>
+ <td>
+ <?php if (! $module->installed) {
+ $this->icon('flash', sprintf($this->translate('Module %s is dangling'), $module->name));
+ } elseif ($module->enabled && $module->loaded) {
+ echo $this->icon('thumbs-up', sprintf($this->translate('Module %s is enabled'), $module->name));
+ } elseif (! $module->enabled) {
+ echo $this->icon('block', sprintf($this->translate('Module %s is disabled'), $module->name));
+ } else { // ! $module->loaded
+ echo $this->icon('block', sprintf($this->translate('Module %s has failed to load'), $module->name));
+ }
+
+ echo $this->qlink(
+ $module->name,
+ 'config/module',
+ array('name' => $module->name),
+ array(
+ 'class' => 'rowaction',
+ 'title' => sprintf($this->translate('Show the overview of the %s module'), $module->name)
+ )
+ ); ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/config/resource.phtml b/application/views/scripts/config/resource.phtml
new file mode 100644
index 0000000..317c115
--- /dev/null
+++ b/application/views/scripts/config/resource.phtml
@@ -0,0 +1,73 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?= $this->qlink(
+ $this->translate('Create a New Resource') ,
+ 'config/createresource',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new resource')
+ )
+ ) ?>
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Resource') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<?php foreach ($this->resources as $name => $value): ?>
+ <tr>
+ <td>
+ <?php
+ switch ($value->type) {
+ case 'db':
+ $icon = 'database';
+ break;
+ case 'ldap':
+ $icon = 'sitemap';
+ break;
+ case 'ssh':
+ $icon = 'user';
+ break;
+ case 'file':
+ case 'ini':
+ $icon = 'doc-text';
+ break;
+ default:
+ $icon = 'edit';
+ break;
+ }
+ ?>
+ <?= $this->qlink(
+ $name,
+ 'config/editresource',
+ array('resource' => $name),
+ array(
+ 'icon' => $icon,
+ 'title' => sprintf($this->translate('Edit resource %s'), $name)
+ )
+ ) ?>
+ </td>
+ <td class="icon-col text-right">
+ <?= $this->qlink(
+ '',
+ 'config/removeresource',
+ array('resource' => $name),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove resource %s'), $name)
+ )
+ ) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/config/resource/create.phtml b/application/views/scripts/config/resource/create.phtml
new file mode 100644
index 0000000..13a8ed9
--- /dev/null
+++ b/application/views/scripts/config/resource/create.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?= $form ?>
+</div>
diff --git a/application/views/scripts/config/resource/modify.phtml b/application/views/scripts/config/resource/modify.phtml
new file mode 100644
index 0000000..13a8ed9
--- /dev/null
+++ b/application/views/scripts/config/resource/modify.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?= $form ?>
+</div>
diff --git a/application/views/scripts/config/resource/remove.phtml b/application/views/scripts/config/resource/remove.phtml
new file mode 100644
index 0000000..13a8ed9
--- /dev/null
+++ b/application/views/scripts/config/resource/remove.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?= $form ?>
+</div>
diff --git a/application/views/scripts/config/userbackend/reorder.phtml b/application/views/scripts/config/userbackend/reorder.phtml
new file mode 100644
index 0000000..c77fd2e
--- /dev/null
+++ b/application/views/scripts/config/userbackend/reorder.phtml
@@ -0,0 +1,75 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?php if ($this->auth()->hasPermission('config/access-control/users')): ?>
+ <h1><?= $this->translate('User Backends') ?></h1>
+ <?= $this->qlink(
+ $this->translate('Create a New User Backend') ,
+ 'config/createuserbackend',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new user backend')
+ )
+ ) ?>
+ <?= $form ?>
+ <?php endif ?>
+
+ <?php if ($this->auth()->hasPermission('config/access-control/groups')): ?>
+ <h1><?= $this->translate('User Group Backends') ?></h1>
+ <?= $this->qlink(
+ $this->translate('Create a New User Group Backend') ,
+ 'usergroupbackend/create',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new user group backend')
+ )
+ ) ?>
+<?php if (! count($backendNames)) { return; } ?>
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Backend') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<?php foreach ($backendNames as $backendName => $config):
+ $type = $config->get('backend');
+?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $backendName,
+ 'usergroupbackend/edit',
+ array('backend' => $backendName),
+ array(
+ 'icon' => $type === 'external' ? 'magic' : ($type === 'ldap' || $type === 'msldap' ? 'sitemap' : 'database'),
+ 'title' => sprintf($this->translate('Edit user group backend %s'), $backendName)
+ )
+ ); ?>
+ </td>
+ <td class="icon-col text-right">
+ <?= $this->qlink(
+ null,
+ 'usergroupbackend/remove',
+ array('backend' => $backendName),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove user group backend %s'), $backendName)
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+ <?php endif ?>
+</div>
diff --git a/application/views/scripts/dashboard/error.phtml b/application/views/scripts/dashboard/error.phtml
new file mode 100644
index 0000000..9396b49
--- /dev/null
+++ b/application/views/scripts/dashboard/error.phtml
@@ -0,0 +1,13 @@
+<div class="content">
+ <h1><?= $this->translate('Could not save dashboard'); ?></h1>
+ <p>
+ <?= $this->translate('Please copy the following dashboard snippet to '); ?>
+ <strong><?= $this->config->getConfigFile(); ?>;</strong>.
+ <br>
+ <?= $this->translate('Make sure that the webserver can write to this file.'); ?>
+ </p>
+ <pre><?= $this->config; ?></pre>
+ <hr>
+ <h2><?= $this->translate('Error details'); ?></h2>
+ <p><?= $this->error->getMessage(); ?></p>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/dashboard/index.phtml b/application/views/scripts/dashboard/index.phtml
new file mode 100644
index 0000000..1d56114
--- /dev/null
+++ b/application/views/scripts/dashboard/index.phtml
@@ -0,0 +1,26 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+<?= $this->tabs ?>
+<?php endif ?>
+</div>
+<?php if ($this->dashboard): ?>
+ <div class="dashboard content">
+ <?= $this->dashboard ?>
+ </div>
+<?php else: ?>
+ <div class="content">
+ <h1><?= $this->escape($this->translate('Welcome to Icinga Web!')) ?></h1>
+ <p>
+ <?php if (! $this->hasPermission('config/modules')) {
+ echo $this->escape($this->translate(
+ 'Currently there is no dashlet available. Please contact the administrator.'
+ ));
+ } else {
+ printf(
+ $this->escape($this->translate('Currently there is no dashlet available. This might change once you enabled some of the available %s.')),
+ $this->qlink($this->translate('modules'), 'config/modules')
+ );
+ } ?>
+ </p>
+ </div>
+<?php endif ?>
diff --git a/application/views/scripts/dashboard/new-dashlet.phtml b/application/views/scripts/dashboard/new-dashlet.phtml
new file mode 100644
index 0000000..b265a25
--- /dev/null
+++ b/application/views/scripts/dashboard/new-dashlet.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <?= $this->form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/dashboard/remove-dashlet.phtml b/application/views/scripts/dashboard/remove-dashlet.phtml
new file mode 100644
index 0000000..b265a25
--- /dev/null
+++ b/application/views/scripts/dashboard/remove-dashlet.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <?= $this->form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/dashboard/remove-pane.phtml b/application/views/scripts/dashboard/remove-pane.phtml
new file mode 100644
index 0000000..b265a25
--- /dev/null
+++ b/application/views/scripts/dashboard/remove-pane.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <?= $this->form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/dashboard/rename-pane.phtml b/application/views/scripts/dashboard/rename-pane.phtml
new file mode 100644
index 0000000..b265a25
--- /dev/null
+++ b/application/views/scripts/dashboard/rename-pane.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <?= $this->form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/dashboard/settings.phtml b/application/views/scripts/dashboard/settings.phtml
new file mode 100644
index 0000000..a6cfe83
--- /dev/null
+++ b/application/views/scripts/dashboard/settings.phtml
@@ -0,0 +1,91 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <h1><?= t('Dashboard Settings'); ?></h1>
+
+ <table class="avp action" data-base-target="_next">
+ <thead>
+ <tr>
+ <th style="width: 18em;">
+ <strong><?= t('Dashlet Name') ?></strong>
+ </th>
+ <th>
+ <strong><?= t('Url') ?></strong>
+ </th>
+ <th style="width: 1.4em;">&nbsp;</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->dashboard->getPanes() as $pane): ?>
+ <?php if ($pane->getDisabled()) continue; ?>
+ <tr>
+ <th colspan="2" style="text-align: left; padding: 0.5em;">
+ <?php if ($pane->isUserWidget()): ?>
+ <?= $this->qlink(
+ $pane->getName(),
+ 'dashboard/rename-pane',
+ array('pane' => $pane->getName()),
+ array('title' => sprintf($this->translate('Edit pane %s'), $pane->getName()))
+ ) ?>
+ <?php else: ?>
+ <?= $this->escape($pane->getName()) ?>
+ <?php endif ?>
+ </th>
+ <th>
+ <?= $this->qlink(
+ '',
+ 'dashboard/remove-pane',
+ array('pane' => $pane->getName()),
+ array(
+ 'icon' => 'trash',
+ 'title' => sprintf($this->translate('Remove pane %s'), $pane->getName())
+ )
+ ); ?>
+ </th>
+ </tr>
+ <?php $dashlets = $pane->getDashlets(); ?>
+ <?php if(empty($dashlets)): ?>
+ <tr>
+ <td colspan="3">
+ <?= $this->translate('No dashlets added to dashboard') ?>.
+ </td>
+ </tr>
+ <?php else: ?>
+ <?php foreach ($dashlets as $dashlet): ?>
+ <?php if ($dashlet->getDisabled()) continue; ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $dashlet->getTitle(),
+ 'dashboard/update-dashlet',
+ array('pane' => $pane->getName(), 'dashlet' => $dashlet->getName()),
+ array('title' => sprintf($this->translate('Edit dashlet %s'), $dashlet->getTitle()))
+ ); ?>
+ </td>
+ <td style="table-layout: fixed; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
+ <?= $this->qlink(
+ $dashlet->getUrl()->getRelativeUrl(),
+ $dashlet->getUrl()->getRelativeUrl(),
+ null,
+ array('title' => sprintf($this->translate('Show dashlet %s'), $dashlet->getTitle()))
+ ); ?>
+ </td>
+ <td>
+ <?= $this->qlink(
+ '',
+ 'dashboard/remove-dashlet',
+ array('pane' => $pane->getName(), 'dashlet' => $dashlet->getName()),
+ array(
+ 'icon' => 'trash',
+ 'title' => sprintf($this->translate('Remove dashlet %s from pane %s'), $dashlet->getTitle(), $pane->getTitle())
+ )
+ ); ?>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ <?php endif; ?>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/dashboard/update-dashlet.phtml b/application/views/scripts/dashboard/update-dashlet.phtml
new file mode 100644
index 0000000..b265a25
--- /dev/null
+++ b/application/views/scripts/dashboard/update-dashlet.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<div class="content">
+ <?= $this->form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml
new file mode 100644
index 0000000..3c7462f
--- /dev/null
+++ b/application/views/scripts/error/error.phtml
@@ -0,0 +1,106 @@
+<?php if (! $this->compact && ! $hideControls): ?>
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php
+if (isset($stackTraces)) {
+ foreach ($messages as $i => $message) {
+ echo '<p tabindex="-1" class="autofocus error-message">' . nl2br($this->escape($message)) . '</p>'
+ . '<hr>'
+ . '<pre>' . $this->escape($stackTraces[$i]) . '</pre>';
+ }
+} else {
+ foreach ($messages as $message) {
+ echo '<p tabindex="-1" class="autofocus error-message">' . nl2br($this->escape($message)) . '</p>';
+ }
+}
+
+$libraries = \Icinga\Application\Icinga::app()->getLibraries();
+$coreReason = [];
+$modReason = [];
+
+if (isset($requiredVendor, $requiredProject) && $requiredVendor && $requiredProject) {
+ // TODO: I don't like this, can we define requirements somewhere else?
+ $coreDeps = ['icinga-php-library' => '>= 0.9', 'icinga-php-thirdparty' => '>= 0.11'];
+
+ foreach ($coreDeps as $libraryName => $requiredVersion) {
+ if (! $libraries->has($libraryName)) {
+ $coreReason[] = sprintf($this->translate(
+ 'Library "%s" is required and missing. Please install a version of it matching the required one: %s'
+ ), $libraryName, $requiredVersion);
+ } elseif (! $libraries->has($libraryName, $requiredVersion) && $libraries->get($libraryName)->isRequired($requiredVendor, $requiredProject)) {
+ $coreReason[] = sprintf($this->translate(
+ 'Library "%s" is required and installed, but its version (%s) does not satisfy the required one: %s'
+ ), $libraryName, $libraries->get($libraryName)->getVersion() ?: '-', $requiredVersion);
+ }
+ }
+
+ if (! empty($coreReason)) {
+ array_unshift($coreReason, $this->translate('You have unmet dependencies. Please check Icinga Web 2\'s installation instructions.'));
+ }
+}
+
+if (isset($module)) {
+ $manager = \Icinga\Application\Icinga::app()->getModuleManager();
+ if ($manager->hasUnmetDependencies($module->getName())) {
+ if (isset($requiredModule) && $requiredModule && isset($module->getRequiredModules()[$requiredModule])) {
+ if (! $manager->hasInstalled($requiredModule)) {
+ $modReason[] = sprintf($this->translate(
+ 'Module "%s" is required and missing. Please install a version of it matching the required one: %s'
+ ), $requiredModule, $module->getRequiredModules()[$requiredModule]);
+ } elseif (! $manager->hasEnabled($requiredModule)) {
+ $modReason[] = sprintf($this->translate(
+ 'Module "%s" is required and installed, but not enabled. Please enable module "%1$s".'
+ ), $requiredModule);
+ } elseif (! $manager->has($requiredModule, $module->getRequiredModules()[$requiredModule])) {
+ $modReason[] = sprintf($this->translate(
+ 'Module "%s" is required and installed, but its version (%s) does not satisfy the required one: %s'
+ ), $requiredModule, $manager->getModule($requiredModule, false)->getVersion(), $module->getRequiredModules()[$requiredModule]);
+ }
+ } elseif (isset($requiredVendor, $requiredProject) && $requiredVendor && $requiredProject) {
+ foreach ($module->getRequiredLibraries() as $libraryName => $requiredVersion) {
+ if (! $libraries->has($libraryName)) {
+ $modReason[] = sprintf($this->translate(
+ 'Library "%s" is required and missing. Please install a version of it matching the required one: %s'
+ ), $libraryName, $requiredVersion);
+ } elseif (! $libraries->has($libraryName, $requiredVersion) && $libraries->get($libraryName)->isRequired($requiredVendor, $requiredProject)) {
+ $modReason[] = sprintf($this->translate(
+ 'Library "%s" is required and installed, but its version (%s) does not satisfy the required one: %s'
+ ), $libraryName, $libraries->get($libraryName)->getVersion() ?: '-', $requiredVersion);
+ }
+ }
+ }
+
+ if (! empty($modReason)) {
+ array_unshift($modReason, sprintf($this->translate(
+ 'This error might have occurred because module "%s" has unmet dependencies.'
+ . ' Please check it\'s installation instructions and install missing dependencies.'
+ ), $module->getName()));
+ }
+ }
+}
+
+// The following doesn't use ipl\Html because that's what the error possibly is about
+?>
+<?php if (! empty($coreReason)): ?>
+<div class="error-reason">
+<?php endif ?>
+<?php foreach ($coreReason as $msg): ?>
+ <p><?= $msg ?></p>
+<?php endforeach ?>
+<?php if (! empty($coreReason)): ?>
+</div>
+<?php endif ?>
+
+<?php if (! empty($modReason)): ?>
+<div class="error-reason">
+<?php endif ?>
+<?php foreach ($modReason as $msg): ?>
+ <p><?= $msg ?></p>
+<?php endforeach ?>
+<?php if (! empty($modReason)): ?>
+</div>
+<?php endif ?>
+</div>
diff --git a/application/views/scripts/filter/index.phtml b/application/views/scripts/filter/index.phtml
new file mode 100644
index 0000000..5e6a63d
--- /dev/null
+++ b/application/views/scripts/filter/index.phtml
@@ -0,0 +1,11 @@
+<?php
+
+echo $this->form;
+
+if ($this->tree) {
+ echo $this->tree->render($this);
+ echo '<br/><pre><code>';
+ echo $this->sqlString;
+ echo '</pre></code>';
+ print_r($this->params);
+} \ No newline at end of file
diff --git a/application/views/scripts/form/reorder-authbackend.phtml b/application/views/scripts/form/reorder-authbackend.phtml
new file mode 100644
index 0000000..34b10b3
--- /dev/null
+++ b/application/views/scripts/form/reorder-authbackend.phtml
@@ -0,0 +1,83 @@
+<form id="<?=
+$this->escape($form->getId())
+?>" name="<?=
+$this->escape($form->getName())
+?>" enctype="<?=
+$this->escape($form->getEncType())
+?>" method="<?=
+$this->escape($form->getMethod())
+?>" action="<?=
+$this->escape($form->getAction())
+?>">
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <th><?= $this->translate('Backend') ?></th>
+ <th></th>
+ <th></th>
+ </thead>
+ <tbody>
+<?php
+ $backendNames = $form->getBackendOrder();
+ $backendConfigs = $form->getConfig();
+ for ($i = 0; $i < count($backendNames); $i++):
+ $type = $backendConfigs->getSection($backendNames[$i])->get('backend');
+?>
+ <tr>
+ <td class="action">
+ <?= $this->qlink(
+ $backendNames[$i],
+ 'config/edituserbackend',
+ array('backend' => $backendNames[$i]),
+ array(
+ 'icon' => $type === 'external' ?
+ 'magic' : ($type === 'ldap' || $type === 'msldap' ? 'sitemap' : 'database'),
+ 'class' => 'rowaction',
+ 'title' => sprintf($this->translate('Edit user backend %s'), $backendNames[$i])
+ )
+ ) ?>
+ </td>
+ <td class="icon-col text-right">
+ <?= $this->qlink(
+ '',
+ 'config/removeuserbackend',
+ array('backend' => $backendNames[$i]),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove user backend %s'), $backendNames[$i])
+ )
+ ) ?>
+ </td>
+ <td class="icon-col text-right" data-base-target="_self">
+<?php if ($i > 0): ?>
+ <button type="submit" name="backend_newpos" class="link-button icon-only animated move-up" value="<?= $this->escape(
+ $backendNames[$i] . '|' . ($i - 1)
+ ) ?>" title="<?= $this->translate(
+ 'Move up in authentication order'
+ ) ?>" aria-label="<?= $this->escape(sprintf(
+ $this->translate('Move user backend %s upwards'),
+ $backendNames[$i]
+ )) ?>">
+ <?= $this->icon('up-small') ?>
+ </button>
+<?php endif ?>
+<?php if ($i + 1 < count($backendNames)): ?>
+ <button type="submit" name="backend_newpos" class="link-button icon-only animated move-down" value="<?= $this->escape(
+ $backendNames[$i] . '|' . ($i + 1)
+ ) ?>" title="<?= $this->translate(
+ 'Move down in authentication order'
+ ) ?>" aria-label="<?= $this->escape(sprintf(
+ $this->translate('Move user backend %s downwards'),
+ $backendNames[$i]
+ )) ?>">
+ <?= $this->icon('down-small') ?>
+ </button>
+<?php endif ?>
+ </td>
+ </tr>
+<?php endfor ?>
+ </tbody>
+ </table>
+ <?= $form->getElement($form->getTokenElementName()) ?>
+ <?= $form->getElement($form->getUidElementName()) ?>
+</form>
diff --git a/application/views/scripts/group/form.phtml b/application/views/scripts/group/form.phtml
new file mode 100644
index 0000000..cbf0659
--- /dev/null
+++ b/application/views/scripts/group/form.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs->showOnlyCloseButton(); ?>
+</div>
+<div class="content">
+ <?= $form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/group/list.phtml b/application/views/scripts/group/list.phtml
new file mode 100644
index 0000000..d362db4
--- /dev/null
+++ b/application/views/scripts/group/list.phtml
@@ -0,0 +1,96 @@
+<?php
+
+use Icinga\Data\Extensible;
+use Icinga\Data\Reducible;
+
+if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->backendSelection ?>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php
+
+if (! isset($backend)) {
+ echo $this->translate('No backend found which is able to list user groups') . '</div>';
+ return;
+} else {
+ $extensible = $this->hasPermission('config/access-control/groups') && $backend instanceof Extensible;
+ $reducible = $this->hasPermission('config/access-control/groups') && $backend instanceof Reducible;
+}
+?>
+
+<?php if ($extensible): ?>
+ <?= $this->qlink(
+ $this->translate('Add a New User Group'),
+ 'group/add',
+ array('backend' => $backend->getName()),
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus'
+ )
+ ) ?>
+<?php endif ?>
+
+<?php if (! $groups->hasResult()): ?>
+ <p><?= $this->translate('No user groups found matching the filter'); ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next" class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('User Group'); ?></th>
+ <?php if ($reducible): ?>
+ <th><?= $this->translate('Remove'); ?></th>
+ <?php endif ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($groups as $group): ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $group->group_name,
+ 'group/show',
+ array(
+ 'backend' => $backend->getName(),
+ 'group' => $group->group_name
+ ),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for user group %s'),
+ $group->group_name
+ )
+ )
+ ); ?>
+ </td>
+ <?php if ($reducible): ?>
+ <td class="icon-col">
+ <?= $this->qlink(
+ null,
+ 'group/remove',
+ array(
+ 'backend' => $backend->getName(),
+ 'group' => $group->group_name
+ ),
+ array(
+ 'class' => 'action-link',
+ 'title' => sprintf($this->translate('Remove user group %s'), $group->group_name),
+ 'icon' => 'cancel'
+ )
+ ); ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml
new file mode 100644
index 0000000..75f0b75
--- /dev/null
+++ b/application/views/scripts/group/show.phtml
@@ -0,0 +1,108 @@
+<?php
+
+use Icinga\Data\Extensible;
+use Icinga\Data\Updatable;
+
+$extensible = $this->hasPermission('config/access-control/groups') && $backend instanceof Extensible;
+
+$editLink = null;
+if ($this->hasPermission('config/access-control/groups') && $backend instanceof Updatable) {
+ $editLink = $this->qlink(
+ null,
+ 'group/edit',
+ array(
+ 'backend' => $backend->getName(),
+ 'group' => $group->group_name
+ ),
+ array(
+ 'title' => sprintf($this->translate('Edit group %s'), $group->group_name),
+ 'class' => 'group-edit',
+ 'icon' => 'edit'
+ )
+ );
+}
+
+?>
+<div class="controls separated">
+<?php if (! $this->compact): ?>
+ <?= $tabs; ?>
+<?php endif ?>
+ <h2 class="clearfix"><?= $this->escape($group->group_name) ?><span class="pull-right"><?= $editLink ?></span></h2>
+ <table class="name-value-table">
+ <tr>
+ <th><?= $this->translate('Created at'); ?></th>
+ <td><?= $group->created_at === null ? '-' : $this->formatDateTime($group->created_at); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last modified'); ?></th>
+ <td><?= $group->last_modified === null ? '-' : $this->formatDateTime($group->last_modified); ?></td>
+ </tr>
+ </table>
+<?php if (! $this->compact): ?>
+ <h2><?= $this->translate('Members'); ?></h2>
+ <div class="sort-controls-container">
+ <?= $this->limiter; ?>
+ <?= $this->paginator; ?>
+ <?= $this->sortBox; ?>
+ </div>
+ <?= $this->filterEditor; ?>
+<?php endif ?>
+</div>
+<div class="content">
+<?php if ($extensible): ?>
+ <?= $this->qlink(
+ $this->translate('Add New Member'),
+ 'group/addmember',
+ array(
+ 'backend' => $backend->getName(),
+ 'group' => $group->group_name
+ ),
+ array(
+ 'icon' => 'plus',
+ 'class' => 'button-link'
+ )
+ ) ?>
+<?php endif ?>
+
+<?php if (! $members->hasResult()): ?>
+ <p><?= $this->translate('No group member found matching the filter'); ?></p>
+</div>
+<?php return; endif ?>
+
+ <table data-base-target="_next" class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Username'); ?></th>
+ <?php if (isset($removeForm)): ?>
+ <th><?= $this->translate('Remove'); ?></th>
+ <?php endif ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($members as $member): ?>
+ <tr>
+ <td>
+ <?php if (
+ $this->hasPermission('config/access-control/users')
+ && ($userBackend = $backend->getUserBackendName($member->user_name)) !== null
+ ): ?>
+ <?= $this->qlink($member->user_name, 'user/show', array(
+ 'backend' => $userBackend,
+ 'user' => $member->user_name
+ ), array(
+ 'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name)
+ )); ?>
+ <?php else: ?>
+ <?= $this->escape($member->user_name); ?>
+ <?php endif ?>
+ </td>
+ <?php if (isset($removeForm)): ?>
+ <td class="icon-col" data-base-target="_self">
+ <?php $removeForm->getElement('user_name')->setValue($member->user_name); echo $removeForm; ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/iframe/index.phtml b/application/views/scripts/iframe/index.phtml
new file mode 100644
index 0000000..96e9de7
--- /dev/null
+++ b/application/views/scripts/iframe/index.phtml
@@ -0,0 +1,8 @@
+<?php if (! $compact): ?>
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<?php endif ?>
+<div class="iframe-container">
+ <iframe src="<?= $this->escape($url) ?>" frameborder="no"></iframe>
+</div>
diff --git a/application/views/scripts/index/welcome.phtml b/application/views/scripts/index/welcome.phtml
new file mode 100644
index 0000000..496dec9
--- /dev/null
+++ b/application/views/scripts/index/welcome.phtml
@@ -0,0 +1,2 @@
+<h1>Welcome to Icinga!</h1>
+You should install/configure some <a href="<?= $this->href('config/modules');?>">modules</a> now!
diff --git a/application/views/scripts/inline.phtml b/application/views/scripts/inline.phtml
new file mode 100644
index 0000000..2534d44
--- /dev/null
+++ b/application/views/scripts/inline.phtml
@@ -0,0 +1,2 @@
+<?= $this->layout()->content ?>
+
diff --git a/application/views/scripts/joystickPagination.phtml b/application/views/scripts/joystickPagination.phtml
new file mode 100644
index 0000000..a8c24c9
--- /dev/null
+++ b/application/views/scripts/joystickPagination.phtml
@@ -0,0 +1,162 @@
+<?php
+
+use Icinga\Web\Url;
+
+$showText = $this->translate('%s: Show %s %u to %u out of %u', 'pagination.joystick');
+$xAxisPages = $xAxisPaginator->getPages('all');
+$yAxisPages = $yAxisPaginator->getPages('all');
+
+$flipUrl = Url::fromRequest();
+if ($flipUrl->getParam('flipped')) {
+ $flipUrl->remove('flipped');
+} else {
+ $flipUrl->setParam('flipped');
+}
+if ($flipUrl->hasParam('page')) {
+ $flipUrl->setParam('page', implode(',', array_reverse(explode(',', $flipUrl->getParam('page')))));
+}
+if ($flipUrl->hasParam('limit')) {
+ $flipUrl->setParam('limit', implode(',', array_reverse(explode(',', $flipUrl->getParam('limit')))));
+}
+
+$totalYAxisPages = $yAxisPaginator->count();
+$currentYAxisPage = $yAxisPaginator->getCurrentPageNumber();
+$prevYAxisPage = $currentYAxisPage > 1 ? $currentYAxisPage - 1 : null;
+$nextYAxisPage = $currentYAxisPage < $totalYAxisPages ? $currentYAxisPage + 1 : null;
+
+$totalXAxisPages = $xAxisPaginator->count();
+$currentXAxisPage = $xAxisPaginator->getCurrentPageNumber();
+$prevXAxisPage = $currentXAxisPage > 1 ? $currentXAxisPage - 1 : null;
+$nextXAxisPage = $currentXAxisPage < $totalXAxisPages ? $currentXAxisPage + 1 : null;
+
+?>
+
+<table class="joystick-pagination">
+ <tbody>
+ <tr>
+ <td>&nbsp;</td>
+ <td>
+ <?php if ($prevYAxisPage): ?>
+ <?= $this->qlink(
+ '',
+ Url::fromRequest(),
+ array(
+ 'page' => $currentXAxisPage . ',' . $prevYAxisPage
+ ),
+ array(
+ 'icon' => 'up-open',
+ 'data-base-target' => '_self',
+ 'title' => sprintf(
+ $showText,
+ $this->translate('Y-Axis', 'pagination.joystick'),
+ $this->translate('hosts', 'pagination.joystick'),
+ ($prevYAxisPage - 1) * $yAxisPages->itemCountPerPage + 1,
+ $prevYAxisPage * $yAxisPages->itemCountPerPage,
+ $yAxisPages->totalItemCount
+ )
+ )
+ ); ?>
+ <?php else: ?>
+ <?= $this->icon('up-open'); ?>
+ <?php endif ?>
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ <tr>
+ <td>
+ <?php if ($prevXAxisPage): ?>
+ <?= $this->qlink(
+ '',
+ Url::fromRequest(),
+ array(
+ 'page' => $prevXAxisPage . ',' . $currentYAxisPage
+ ),
+ array(
+ 'icon' => 'left-open',
+ 'data-base-target' => '_self',
+ 'title' => sprintf(
+ $showText,
+ $this->translate('X-Axis', 'pagination.joystick'),
+ $this->translate('services', 'pagination.joystick'),
+ ($prevXAxisPage - 1) * $xAxisPages->itemCountPerPage + 1,
+ $prevXAxisPage * $xAxisPages->itemCountPerPage,
+ $xAxisPages->totalItemCount
+ )
+ )
+ ); ?>
+ <?php else: ?>
+ <?= $this->icon('left-open'); ?>
+ <?php endif ?>
+ </td>
+ <?php if ($this->flippable): ?>
+ <td><?= $this->qlink(
+ '',
+ $flipUrl,
+ null,
+ array(
+ 'icon' => 'arrows-cw',
+ 'data-base-target' => '_self',
+ 'title' => $this->translate('Flip grid axes')
+ )
+ ) ?></td>
+ <?php else: ?>
+ <td>&nbsp;</td>
+ <?php endif ?>
+ <td>
+ <?php if ($nextXAxisPage): ?>
+ <?= $this->qlink(
+ '',
+ Url::fromRequest(),
+ array(
+ 'page' => $nextXAxisPage . ',' . $currentYAxisPage
+ ),
+ array(
+ 'icon' => 'right-open',
+ 'data-base-target' => '_self',
+ 'title' => sprintf(
+ $showText,
+ $this->translate('X-Axis', 'pagination.joystick'),
+ $this->translate('services', 'pagination.joystick'),
+ $currentXAxisPage * $xAxisPages->itemCountPerPage + 1,
+ $nextXAxisPage === $xAxisPages->last ? $xAxisPages->totalItemCount : $nextXAxisPage * $xAxisPages->itemCountPerPage,
+ $xAxisPages->totalItemCount
+ )
+ ),
+ false
+ ); ?>
+ <?php else: ?>
+ <?= $this->icon('right-open'); ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>
+ <?php if ($nextYAxisPage): ?>
+ <?= $this->qlink(
+ '',
+ Url::fromRequest(),
+ array(
+ 'page' => $currentXAxisPage . ',' . $nextYAxisPage
+ ),
+ array(
+ 'icon' => 'down-open',
+ 'data-base-target' => '_self',
+ 'title' => sprintf(
+ $showText,
+ $this->translate('Y-Axis', 'pagination.joystick'),
+ $this->translate('hosts', 'pagination.joystick'),
+ $currentYAxisPage * $yAxisPages->itemCountPerPage + 1,
+ $nextYAxisPage === $yAxisPages->last ? $yAxisPages->totalItemCount : $nextYAxisPage * $yAxisPages->itemCountPerPage,
+ $yAxisPages->totalItemCount
+ )
+ )
+ ); ?>
+ <?php else: ?>
+ <?= $this->icon('down-open'); ?>
+ <?php endif ?>
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
+</table>
diff --git a/application/views/scripts/layout/announcements.phtml b/application/views/scripts/layout/announcements.phtml
new file mode 100644
index 0000000..3be6b83
--- /dev/null
+++ b/application/views/scripts/layout/announcements.phtml
@@ -0,0 +1 @@
+<?= $this->widget('announcements') ?>
diff --git a/application/views/scripts/layout/menu.phtml b/application/views/scripts/layout/menu.phtml
new file mode 100644
index 0000000..dfb544d
--- /dev/null
+++ b/application/views/scripts/layout/menu.phtml
@@ -0,0 +1,20 @@
+<?php
+
+use Icinga\Web\Navigation\ConfigMenu;
+use Icinga\Web\Widget\SearchDashboard;
+
+$searchDashboard = new SearchDashboard();
+$searchDashboard->setUser($this->Auth()->getUser());
+
+if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
+ <form action="<?= $this->href('search') ?>" method="get" role="search" class="search-control">
+ <input type="text" name="q" id="search" class="search search-input" required
+ placeholder="<?= $this->translate('Search') ?> &hellip;"
+ autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
+ <button class="search-reset icon-cancel" type="reset"></button>
+ </form>
+<?php endif; ?>
+<?= $menuRenderer->setCssClass('primary-nav')->setElementTag('nav')->setHeading(t('Navigation')); ?>
+<nav class="config-menu">
+ <?= new ConfigMenu() ?>
+</nav>
diff --git a/application/views/scripts/list/applicationlog.phtml b/application/views/scripts/list/applicationlog.phtml
new file mode 100644
index 0000000..aa1a90a
--- /dev/null
+++ b/application/views/scripts/list/applicationlog.phtml
@@ -0,0 +1,29 @@
+<?php if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ </div>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if ($this->logData !== null): ?>
+ <table class="action">
+ <tbody>
+ <?php foreach ($this->logData as $value): ?>
+ <?php $datetime = new Datetime($value->datetime) ?>
+ <tr class="state">
+ <td style="width: 6em; text-align: center">
+ <?= $this->escape($datetime->format('d.m. H:i')) ?><br />
+ <?= $this->escape($value->loglevel) ?>
+ </td>
+ <td>
+ <?= nl2br($this->escape($value->message), false) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php endif ?>
+</div>
diff --git a/application/views/scripts/mixedPagination.phtml b/application/views/scripts/mixedPagination.phtml
new file mode 100644
index 0000000..e92a9c9
--- /dev/null
+++ b/application/views/scripts/mixedPagination.phtml
@@ -0,0 +1,79 @@
+<?php if ($this->pageCount <= 1) return; ?>
+<div class="pagination-control" role="navigation">
+ <h2 id="<?= $this->protectId('pagination') ?>" class="sr-only" tabindex="-1"><?= $this->translate('Pagination') ?></h2>
+ <ul class="nav tab-nav">
+ <?php if (isset($this->previous)): ?>
+ <?php $label = sprintf(
+ $this->translate('Show rows %u to %u of %u'),
+ ($this->current - 2) * $this->itemCountPerPage + 1,
+ ($this->current - 1) * $this->itemCountPerPage,
+ $this->totalItemCount
+ ) ?>
+ <li class="nav-item">
+ <a href="<?= $this->escape($this->url()->overwriteParams(array('page' => $this->previous))->getAbsoluteUrl()) ?>"
+ title="<?= $label ?>"
+ aria-label="<?= $label ?>"
+ class="previous-page">
+ <?= $this->icon('angle-double-left') ?>
+ </a>
+ </li>
+ <?php else: ?>
+ <li class="nav-item disabled" aria-hidden="true">
+ <span class="previous-page">
+ <span class="sr-only"><?= $this->translate('Previous page') ?></span>
+ <?= $this->icon('angle-double-left') ?>
+ </span>
+ </li>
+ <?php endif ?>
+ <?php foreach ($this->pagesInRange as $page): ?>
+ <?php if ($page === '...'): ?>
+ <li class="nav-item disabled">
+ <span>...</span>
+ </li>
+ <?php else: ?>
+ <?php
+ $end = $page * $this->itemCountPerPage;
+ if ($end > $this->totalItemCount) {
+ $end = $this->totalItemCount;
+ }
+ $label = sprintf(
+ $this->translate('Show rows %u to %u of %u'),
+ ($page - 1) * $this->itemCountPerPage + 1,
+ $end,
+ $this->totalItemCount
+ );
+ ?>
+ <li<?= $page === $this->current ? ' class="active nav-item"' : ' class="nav-item"' ?>>
+ <a href="<?= $this->escape($this->url()->overwriteParams(array('page' => $page))->getAbsoluteUrl()) ?>"
+ title="<?= $label ?>"
+ aria-label="<?= $label ?>">
+ <?= $page ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php endforeach ?>
+ <?php if (isset($this->next)): ?>
+ <?php $label = sprintf(
+ $this->translate('Show rows %u to %u of %u'),
+ $this->current * $this->itemCountPerPage + 1,
+ ($this->current + 1) * $this->itemCountPerPage,
+ $this->totalItemCount
+ ) ?>
+ <li class="nav-item">
+ <a href="<?= $this->escape($this->url()->overwriteParams(array('page' => $this->next))->getAbsoluteUrl()) ?>"
+ title="<?= $label ?>"
+ aria-label="<?= $label ?>"
+ class="next-page">
+ <?= $this->icon('angle-double-right') ?>
+ </a>
+ </li>
+ <?php else: ?>
+ <li class="disabled nav-item" aria-hidden="true">
+ <span class="next-page">
+ <span class="sr-only"><?= $this->translate('Next page') ?></span>
+ <?= $this->icon('angle-double-right') ?>
+ </span>
+ </li>
+ <?php endif ?>
+ </ul>
+</div>
diff --git a/application/views/scripts/navigation/dashboard.phtml b/application/views/scripts/navigation/dashboard.phtml
new file mode 100644
index 0000000..f069882
--- /dev/null
+++ b/application/views/scripts/navigation/dashboard.phtml
@@ -0,0 +1,27 @@
+<?php
+
+use ipl\Web\Widget\Icon;
+
+?>
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?php foreach ($navigation as $item): /** @var \Icinga\Web\Navigation\NavigationItem $item */?>
+ <a class="dashboard-link" href="<?= $this->url($item->getUrl(), $item->getUrlParameters()) ?>"<?= $this->propertiesToString($item->getAttributes()) ?>>
+ <div class="link-icon">
+ <?php
+ if (substr($item->getUrl()->getPath(), 0, 9) === 'icingadb/') {
+ echo new Icon($item->getIcon(), [ 'aria-hidden' => 1]);
+ } else {
+ echo $this->icon($item->getIcon() ?: 'forward', null, array('aria-hidden' => true));
+ }
+ ?>
+ </div>
+ <div class="link-meta">
+ <div class="link-label"><?= $this->escape($item->getLabel()) ?></div>
+ <div class="link-description"><?= $this->escape($item->getDescription() ?: sprintf('Open %s', strtolower($item->getLabel()))) ?></div>
+ </div>
+ </a>
+ <?php endforeach ?>
+</div>
diff --git a/application/views/scripts/navigation/index.phtml b/application/views/scripts/navigation/index.phtml
new file mode 100644
index 0000000..bf08562
--- /dev/null
+++ b/application/views/scripts/navigation/index.phtml
@@ -0,0 +1,78 @@
+<?php if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+ <div class="grid">
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+ <?= $this->qlink(
+ $this->translate('Create a New Navigation Item') ,
+ 'navigation/add',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new navigation item')
+ )
+ ) ?>
+<?php if (count($items) === 0): ?>
+ <p><?= $this->translate('You did not create any navigation item yet.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Navigation') ?></th>
+ <th><?= $this->translate('Type') ?></th>
+ <th><?= $this->translate('Shared') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<?php foreach ($items as $item): ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $item->name,
+ 'navigation/edit',
+ array(
+ 'name' => $item->name,
+ 'type' => $item->type
+ ),
+ array(
+ 'title' => sprintf($this->translate('Edit navigation item %s'), $item->name)
+ )
+ ) ?>
+ </td>
+ <td>
+ <?= $item->type && isset($types[$item->type])
+ ? $this->escape($types[$item->type])
+ : $this->escape($this->translate('Unknown')) ?>
+ </td>
+ <td class="icon-col">
+ <?= $item->owner ? $this->translate('Yes') : $this->translate('No') ?>
+ </td>
+ <td class="icon-col text-right">
+ <?= $this->qlink(
+ '',
+ 'navigation/remove',
+ array(
+ 'name' => $item->name,
+ 'type' => $item->type
+ ),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove navigation item %s'), $item->name)
+ )
+ ) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml
new file mode 100644
index 0000000..b6739fb
--- /dev/null
+++ b/application/views/scripts/navigation/shared.phtml
@@ -0,0 +1,68 @@
+<?php
+
+use Icinga\Web\Url;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+ <div class="grid">
+ <?= $this->sortBox ?>
+ </div>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php if (count($items) === 0): ?>
+ <p><?= $this->translate('There are currently no navigation items being shared'); ?></p>
+<?php else: ?>
+ <table class="table-row-selectable common-table">
+ <thead>
+ <th><?= $this->translate('Shared Navigation'); ?></th>
+ <th style="width: 10em"><?= $this->translate('Type'); ?></th>
+ <th style="width: 10em"><?= $this->translate('Owner'); ?></th>
+ <th style="width: 5em"><?= $this->translate('Unshare'); ?></th>
+ </thead>
+ <tbody>
+ <?php foreach ($items as $item): ?>
+ <tr>
+ <td><?= $this->qlink(
+ $item->name,
+ 'navigation/edit',
+ array(
+ 'name' => $item->name,
+ 'type' => $item->type,
+ 'owner' => $item->owner,
+ 'referrer' => 'shared'
+ ),
+ array(
+ 'title' => sprintf($this->translate('Edit shared navigation item %s'), $item->name)
+ )
+ ); ?></td>
+ <td><?= $item->type && isset($types[$item->type])
+ ? $this->escape($types[$item->type])
+ : $this->escape($this->translate('Unknown')); ?></td>
+ <td><?= $this->escape($item->owner); ?></td>
+ <?php if ($item->parent): ?>
+ <td><?= $this->icon(
+ 'block',
+ sprintf(
+ $this->translate(
+ 'This is a child of the navigation item %1$s. You can'
+ . ' only unshare this item by unsharing %1$s'
+ ),
+ $item->parent
+ )
+ ); ?></td>
+ <?php else: ?>
+ <td data-base-target="_self"><?= $removeForm
+ ->setDefault('name', $item->name)
+ ->setAction(Url::fromPath(
+ 'navigation/unshare',
+ array('type' => $item->type, 'owner' => $item->owner)
+ )); ?></td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php endif ?>
+</div>
diff --git a/application/views/scripts/pivottablePagination.phtml b/application/views/scripts/pivottablePagination.phtml
new file mode 100644
index 0000000..ce18014
--- /dev/null
+++ b/application/views/scripts/pivottablePagination.phtml
@@ -0,0 +1,48 @@
+<?php
+
+use Icinga\Web\Url;
+
+if ($xAxisPaginator->count() <= 1 && $yAxisPaginator->count() <= 1) {
+ return; // Display this pagination only if there are multiple pages
+}
+
+$fromTo = t('%s: %d to %d of %d (on the %s-axis)');
+$xAxisPages = $xAxisPaginator->getPages('all');
+$yAxisPages = $yAxisPaginator->getPages('all');
+
+?>
+
+<div class="pivot-pagination">
+ <span><?= t('Navigation'); ?></span>
+ <table>
+ <tbody>
+<?php foreach ($yAxisPages->pagesInRange as $yAxisPage): ?>
+ <tr>
+<?php foreach ($xAxisPages->pagesInRange as $xAxisPage): ?>
+ <td<?= $xAxisPage === $xAxisPages->current && $yAxisPage === $yAxisPages->current ? ' class="active"' : ''; ?>>
+<?php if ($xAxisPage !== $xAxisPages->current || $yAxisPage !== $yAxisPages->current): ?>
+ <a href="<?= Url::fromRequest()->overwriteParams(
+ array('page' => $xAxisPage . ',' . $yAxisPage)
+ )->getAbsoluteUrl(); ?>" title="<?= sprintf(
+ $fromTo,
+ t('Hosts'),
+ ($yAxisPage - 1) * $yAxisPages->itemCountPerPage + 1,
+ $yAxisPage === $yAxisPages->last ? $yAxisPages->totalItemCount : $yAxisPage * $yAxisPages->itemCountPerPage,
+ $yAxisPages->totalItemCount,
+ 'y'
+ ) . '; ' . sprintf(
+ $fromTo,
+ t('Services'),
+ ($xAxisPage - 1) * $xAxisPages->itemCountPerPage + 1,
+ $xAxisPage === $xAxisPages->last ? $xAxisPages->totalItemCount : $xAxisPage * $xAxisPages->itemCountPerPage,
+ $xAxisPages->totalItemCount,
+ 'x'
+ ); ?>"></a>
+<?php endif ?>
+ </td>
+<?php endforeach ?>
+ </tr>
+<?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/role/list.phtml b/application/views/scripts/role/list.phtml
new file mode 100644
index 0000000..352e3e2
--- /dev/null
+++ b/application/views/scripts/role/list.phtml
@@ -0,0 +1,65 @@
+<div class="controls separated">
+ <?= $tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<div class="content">
+ <?= $this->qlink(
+ $this->translate('Create a New Role') ,
+ 'role/add',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new role')
+ )
+ ) ?>
+<?php /** @var \Icinga\Application\Config $roles */ if (! $roles->hasResult()): ?>
+ <p><?= $this->translate('No roles found.') ?></p>
+<?php return; endif ?>
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Name') ?></th>
+ <th><?= $this->translate('Users') ?></th>
+ <th><?= $this->translate('Groups') ?></th>
+ <th><?= $this->translate('Inherits From') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+<?php foreach ($roles as $name => $role): /** @var object $role */ ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $name,
+ 'role/edit',
+ array('role' => $name),
+ array('title' => sprintf($this->translate('Edit role %s'), $name))
+ ) ?>
+ </td>
+ <td><?= $this->escape($role->users) ?></td>
+ <td><?= $this->escape($role->groups) ?></td>
+ <td><?= $this->escape($role->parent) ?></td>
+ <td class="icon-col text-right">
+ <?= $this->qlink(
+ '',
+ 'role/remove',
+ array('role' => $name),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove role %s'), $name)
+ )
+ ) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/search/hint.phtml b/application/views/scripts/search/hint.phtml
new file mode 100644
index 0000000..d54c0b2
--- /dev/null
+++ b/application/views/scripts/search/hint.phtml
@@ -0,0 +1,8 @@
+<div class="content">
+<h1><?= $this->translate("I'm ready to search, waiting for your input") ?></h1>
+<p><strong><?= $this->translate('Hint') ?>: </strong><?= $this->translate(
+ 'Please use the asterisk (*) as a placeholder for wildcard searches.'
+ . " For convenience I'll always add a wildcard in front and after your"
+ . ' search string.'
+) ?></p>
+</div>
diff --git a/application/views/scripts/search/index.phtml b/application/views/scripts/search/index.phtml
new file mode 100644
index 0000000..321597e
--- /dev/null
+++ b/application/views/scripts/search/index.phtml
@@ -0,0 +1,7 @@
+<div class="controls">
+<?= $this->dashboard->getTabs() ?>
+</div>
+
+<div class="content dashboard">
+<?= $this->dashboard ?>
+</div>
diff --git a/application/views/scripts/showConfiguration.phtml b/application/views/scripts/showConfiguration.phtml
new file mode 100644
index 0000000..682b349
--- /dev/null
+++ b/application/views/scripts/showConfiguration.phtml
@@ -0,0 +1,27 @@
+<div>
+ <h4><?= $this->translate('Saving Configuration Failed'); ?></h4>
+ <p>
+ <?= sprintf(
+ $this->translate('The file %s couldn\'t be stored. (Error: "%s")'),
+ $this->escape($filePath),
+ $this->escape($errorMessage)
+ ); ?>
+ <br>
+ <?= $this->translate('This could have one or more of the following reasons:'); ?>
+ </p>
+ <ul>
+ <li><?= $this->translate('You don\'t have file-system permissions to write to the file'); ?></li>
+ <li><?= $this->translate('Something went wrong while writing the file'); ?></li>
+ <li><?= $this->translate('There\'s an application error preventing you from persisting the configuration'); ?></li>
+ </ul>
+</div>
+<p>
+ <?= $this->translate('Details can be found in the application log. (If you don\'t have access to this log, call your administrator in this case)'); ?>
+ <br>
+ <?= $this->translate('In case you can access the file by yourself, you can open it and insert the config manually:'); ?>
+</p>
+<p>
+ <pre>
+ <code><?= $this->escape($configString); ?></code>
+ </pre>
+</p>
diff --git a/application/views/scripts/simple-form.phtml b/application/views/scripts/simple-form.phtml
new file mode 100644
index 0000000..9bcba74
--- /dev/null
+++ b/application/views/scripts/simple-form.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+<div class="content">
+ <?= $form->create()->setTitle(null) // @TODO(el): create() has to be called because the UserForm is setting the title there ?>
+</div>
diff --git a/application/views/scripts/user/form.phtml b/application/views/scripts/user/form.phtml
new file mode 100644
index 0000000..cbf0659
--- /dev/null
+++ b/application/views/scripts/user/form.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs->showOnlyCloseButton(); ?>
+</div>
+<div class="content">
+ <?= $form; ?>
+</div> \ No newline at end of file
diff --git a/application/views/scripts/user/list.phtml b/application/views/scripts/user/list.phtml
new file mode 100644
index 0000000..bdb5f1a
--- /dev/null
+++ b/application/views/scripts/user/list.phtml
@@ -0,0 +1,90 @@
+<?php
+
+use Icinga\Data\Extensible;
+use Icinga\Data\Reducible;
+
+if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->backendSelection ?>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php
+
+if (! isset($backend)) {
+ echo $this->translate('No backend found which is able to list users') . '</div>';
+ return;
+} else {
+ $extensible = $this->hasPermission('config/access-control/users') && $backend instanceof Extensible;
+ $reducible = $this->hasPermission('config/access-control/users') && $backend instanceof Reducible;
+}
+?>
+
+<?php if ($extensible): ?>
+ <?= $this->qlink(
+ $this->translate('Add a New User') ,
+ 'user/add',
+ array('backend' => $backend->getName()),
+ array(
+ 'class' => 'button-link',
+ 'data-base-target' => '_next',
+ 'icon' => 'plus'
+ )
+ ) ?>
+<?php endif ?>
+
+<?php if (! $users->hasResult()): ?>
+ <p><?= $this->translate('No users found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+
+ <table data-base-target="_next" class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Username') ?></th>
+ <?php if ($reducible): ?>
+ <th><?= $this->translate('Remove') ?></th>
+ <?php endif ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($users as $user): ?>
+ <tr>
+ <td><?= $this->qlink(
+ $user->user_name,
+ 'user/show',
+ array(
+ 'backend' => $backend->getName(),
+ 'user' => $user->user_name
+ ),
+ array(
+ 'title' => sprintf($this->translate('Show detailed information about %s'), $user->user_name)
+ )
+ ) ?></td>
+ <?php if ($reducible): ?>
+ <td class="icon-col"><?= $this->qlink(
+ null,
+ 'user/remove',
+ array(
+ 'backend' => $backend->getName(),
+ 'user' => $user->user_name
+ ),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove user %s'), $user->user_name)
+ )
+ ) ?></td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/application/views/scripts/user/show.phtml b/application/views/scripts/user/show.phtml
new file mode 100644
index 0000000..b19c15a
--- /dev/null
+++ b/application/views/scripts/user/show.phtml
@@ -0,0 +1,138 @@
+<?php
+
+use Icinga\Data\Updatable;
+use Icinga\Data\Reducible;
+use Icinga\Data\Selectable;
+
+?>
+<div class="controls separated">
+<?php if (! $this->compact): ?>
+ <?= $tabs; ?>
+<?php endif ?>
+ <h2><?= $this->escape($user->user_name) ?></h2>
+ <?php
+ if ($this->hasPermission('config/access-control/users') && $backend instanceof Updatable) {
+ echo $this->qlink(
+ $this->translate('Edit User'),
+ 'user/edit',
+ array(
+ 'backend' => $backend->getName(),
+ 'user' => $user->user_name
+ ),
+ array(
+ 'class' => 'button-link',
+ 'icon' => 'edit',
+ 'title' => sprintf($this->translate('Edit user %s'), $user->user_name)
+ )
+ );
+ }
+ ?>
+ <table class="name-value-table">
+ <tr>
+ <th><?= $this->translate('State'); ?></th>
+ <td><?= $user->is_active === null ? '-' : ($user->is_active ? $this->translate('Active') : $this->translate('Inactive')); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Created at'); ?></th>
+ <td><?= $user->created_at === null ? '-' : $this->formatDateTime($user->created_at); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last modified'); ?></th>
+ <td><?= $user->last_modified === null ? '-' : $this->formatDateTime($user->last_modified); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Role Memberships'); ?></th>
+ <td>
+ <?php $roles = $userObj->getRoles(); ?>
+ <?php if (! empty($roles)): ?>
+ <ul class="role-memberships">
+ <?php foreach($roles as $role): ?>
+ <li>
+ <?php if ($this->allowedToEditRoles): ?>
+ <?= $this->qlink(
+ $role->getName(),
+ 'role/edit',
+ ['role' => $role->getName()],
+ ['title' => sprintf($this->translate('Edit role %s'), $role->getName())]
+ );
+ $role === end($roles) ? print '' : print ', '; ?>
+ <?php else: ?>
+ <?= $role->getName() ?>
+ <?php endif ?>
+ </li>
+ <?php endforeach ?>
+ </ul>
+ <?php else: ?>
+ <p><?= $this->translate('No memberships found'); ?></p>
+ <?php endif ?>
+ </td>
+ </tr>
+ </table>
+<?php if (! $this->compact): ?>
+ <h2><?= $this->translate('Group Memberships'); ?></h2>
+ <div class="sort-controls-container">
+ <?= $this->limiter; ?>
+ <?= $this->paginator; ?>
+ <?= $this->sortBox; ?>
+ </div>
+ <?= $this->filterEditor; ?>
+<?php endif ?>
+</div>
+<div class="content">
+<?php if ($showCreateMembershipLink): ?>
+ <?= $this->qlink(
+ $this->translate('Create New Membership'),
+ 'user/createmembership',
+ array(
+ 'backend' => $backend->getName(),
+ 'user' => $user->user_name
+ ),
+ array(
+ 'icon' => 'plus',
+ 'class' => 'button-link'
+ )
+ ) ?>
+<?php endif ?>
+
+<?php if (! $memberships->hasResult()): ?>
+ <p><?= $this->translate('No memberships found matching the filter'); ?></p>
+</div>
+<?php return; endif ?>
+
+ <table data-base-target="_next" class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Group'); ?></th>
+ <th><?= $this->translate('Cancel', 'group.membership'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($memberships as $membership): ?>
+ <tr>
+ <td>
+ <?php if ($this->hasPermission('config/access-control/groups') && $membership->backend instanceof Selectable): ?>
+ <?= $this->qlink($membership->group_name, 'group/show', array(
+ 'backend' => $membership->backend->getName(),
+ 'group' => $membership->group_name
+ ), array(
+ 'title' => sprintf($this->translate('Show detailed information for group %s'), $membership->group_name)
+ )); ?>
+ <?php else: ?>
+ <?= $this->escape($membership->group_name); ?>
+ <?php endif ?>
+ </td>
+ <td class="icon-col" data-base-target="_self">
+ <?php if (isset($removeForm) && $membership->backend instanceof Reducible): ?>
+ <?= $removeForm->setAction($this->url('group/removemember', array(
+ 'backend' => $membership->backend->getName(),
+ 'group' => $membership->group_name
+ ))); ?>
+ <?php else: ?>
+ -
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/bin/icingacli b/bin/icingacli
new file mode 100755
index 0000000..056d521
--- /dev/null
+++ b/bin/icingacli
@@ -0,0 +1,7 @@
+#!/usr/bin/env php
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+require_once dirname(__DIR__) . '/library/Icinga/Application/Cli.php';
+
+Icinga\Application\Cli::start()->dispatch();
diff --git a/doc/01-About.md b/doc/01-About.md
new file mode 100644
index 0000000..19b6149
--- /dev/null
+++ b/doc/01-About.md
@@ -0,0 +1,93 @@
+# About Icinga Web 2 <a id="about"></a>
+
+Icinga Web 2 is a powerful PHP framework for web applications that comes in a clean and reduced design.
+It's fast, responsive, accessible and easily extensible with modules.
+
+## Installation <a id="about-installation"></a>
+
+Icinga Web 2 can be installed easily from packages from the official package repositories.
+Setting it up is also easy with the web based setup wizard.
+
+See [here](02-Installation.md#installation) for more information about the installation.
+
+## Configuration <a id="about-configuration"></a>
+
+Icinga Web 2 can be configured via the user interface and .ini files.
+
+See [here](03-Configuration.md#configuration) for more information about the configuration.
+
+## Authentication <a id="about-authentication"></a>
+
+With Icinga Web 2 you can authenticate against relational databases, LDAP and more.
+These authentication methods can be easily configured (via the corresponding .ini file).
+
+See [here](05-Authentication.md#authentication) for more information about
+the different authentication methods available and how to configure them.
+
+## Authorization <a id="about-authorization"></a>
+
+In Icinga Web 2 there are permissions and restrictions to allow and deny (respectively)
+roles to view or to do certain things.
+These roles can be assigned to users and groups.
+
+See [here](06-Security.md#security) for more information about authorization
+and how to configure roles.
+
+## User preferences <a id="about-preferences"></a>
+
+Besides the global configuration each user has individual configuration options
+like the interface's language or the current timezone.
+They can be stored either in a database or in .ini files.
+
+See [here](07-Preferences.md#preferences) for more information about a user's preferences
+and how to configure their storage type.
+
+## Modules
+
+Modules extend Icinga Web 2 with additional functionality. They allow the integration of
+capabilities into existing views and even other modules. Be it a graph provider such as
+[Graphite](https://github.com/Icinga/icingaweb2-module-graphite) or a UI for the Icinga 2
+configuration like the [Director](https://github.com/Icinga/icingaweb2-module-director).
+
+See [here](08-Modules.md#modules) for information on how to install and configure modules.
+
+### The monitoring module <a id="about-monitoring"></a>
+
+> **Note for Icinga DB Users**
+>
+> This module is only for the IDO backend. Use [Icinga DB Web](https://github.com/Icinga/icingadb-web) instead.
+
+This is the core module for most Icinga Web 2 users.
+
+It provides an intuitive user interface for monitoring with Icinga 2.
+There are lots of list and detail views (e.g. for hosts and services)
+you can sort and filter depending on what you want to see.
+
+You can also control the monitoring process itself by sending external commands to Icinga.
+Most such actions (like rescheduling a check) can be done with just a single click in the UI.
+
+More details about this module can be found in [this chapter](../modules/monitoring/doc/01-About.md#monitoring-module-about).
+
+### Documentation <a id="about-documentation"></a>
+
+With the documentation module you can read the documentation of the framework (and any module) directly in the user interface.
+
+The module can also export the documentation to PDF.
+
+More details about this module can be found in [this chapter](../modules/doc/doc/01-About.md#doc-module-about).
+
+### Translation <a id="about-translation"></a>
+
+Icinga Web 2 and all modules by Icinga utilize gettext to provide translations into other languages from the default
+English (en_US). However, the actual language specific files (locales) are not separately included in every project.
+
+Icinga uses a central repository to manage locales: https://github.com/Icinga/L10n
+
+If you want to provide or update a translation for your own language, please head over there where you will find
+[instructions](https://github.com/Icinga/L10n/blob/master/CONTRIBUTING.md) on how to contribute.
+
+## Accessibility <a id="about-accessibility"></a>
+
+In the Icinga Web 2 interface even the blind can see -
+easy navigation with a screen reader and specific themes for different kinds of vision deficiencies
+make it possible for everyone to monitor their systems without impairments.
diff --git a/doc/02-Installation.md b/doc/02-Installation.md
new file mode 100644
index 0000000..fcd91cb
--- /dev/null
+++ b/doc/02-Installation.md
@@ -0,0 +1,537 @@
+<!-- {% if index %} -->
+# Installation <a id="installation"></a>
+
+The preferred way of installing Icinga Web 2 is to use the official package repositories depending on which operating
+system and distribution you are running.
+
+Please follow the steps listed for your operating system. Packages for distributions other than the ones
+listed here may also be available. Please refer to [icinga.com/get-started/download](https://icinga.com/get-started/download/#community)
+for a full list of available community repositories.
+
+## Browser Support
+
+Icinga Web 2 and modules made by Icinga don't require a particular browser or set of browsers. The
+vendor of the browser in question doesn't matter much. However, the features a browser supports do.
+
+This generally applies to CSS and Javascript features. Since there a plethora of features in each
+category which Icinga Web 2 and modules may require, we will only mention the most prominent feature
+or sub-category here:
+
+* For CSS this is [the flexible box layout module](https://caniuse.com/flexbox)
+* For Javascript it is [the ECMAScript 2015 specification](https://caniuse.com/es6)
+
+If your desired browser and its version is showing up in green when visiting the respective link,
+it's probably okay to use it for Icinga Web 2.
+
+## Upgrade <a id="upgrade"></a>
+
+In case you are upgrading from an older version of Icinga Web 2
+please make sure to read the [upgrading](80-Upgrading.md#upgrading) section
+thoroughly.
+<!-- {% elif not from_source %} -->
+
+## Installation Requirements <a id="installation-requirements"></a>
+
+* [Icinga 2](https://icinga.com/docs/icinga-2) and [Icinga DB](https://icinga.com/docs/icinga-db) to
+ monitor your infrastructure
+* A web server, e.g. Apache or Nginx
+* PHP version ≥ 7.2
+
+### Optional Requirements
+
+* The [pdfexport](https://github.com/Icinga/icingaweb2-module-pdfexport) module (≥0.10) is required for the
+ export to PDF
+* LDAP PHP library when using Active Directory or LDAP for authentication
+
+## Add Icinga Package Repository <a id="add-icinga-package-repository"></a>
+
+You need to add the Icinga repository to your package management configuration for installing Icinga Web 2.
+If you've already configured your OS to use the Icinga repository for installing Icinga 2, you may skip this step.
+
+<!-- {% if debian %} -->
+### Debian Repository <a id="ubuntu-repository"></a>
+
+```bash
+apt-get update
+apt-get -y install apt-transport-https wget gnupg
+
+wget -O - https://packages.icinga.com/icinga.key | apt-key add -
+
+DIST=$(awk -F"[)(]+" '/VERSION=/ {print $2}' /etc/os-release); \
+ echo "deb https://packages.icinga.com/debian icinga-${DIST} main" > \
+ /etc/apt/sources.list.d/${DIST}-icinga.list
+ echo "deb-src https://packages.icinga.com/debian icinga-${DIST} main" >> \
+ /etc/apt/sources.list.d/${DIST}-icinga.list
+
+apt-get update
+```
+<!-- {% endif %} -->
+
+<!-- {% if ubuntu %} -->
+### Ubuntu Repository <a id="ubuntu-repository"></a>
+
+```bash
+apt-get update
+apt-get -y install apt-transport-https wget gnupg
+
+wget -O - https://packages.icinga.com/icinga.key | apt-key add -
+
+. /etc/os-release; if [ ! -z ${UBUNTU_CODENAME+x} ]; then DIST="${UBUNTU_CODENAME}"; else DIST="$(lsb_release -c| awk '{print $2}')"; fi; \
+ echo "deb https://packages.icinga.com/ubuntu icinga-${DIST} main" > \
+ /etc/apt/sources.list.d/${DIST}-icinga.list
+ echo "deb-src https://packages.icinga.com/ubuntu icinga-${DIST} main" >> \
+ /etc/apt/sources.list.d/${DIST}-icinga.list
+
+apt-get update
+```
+<!-- {% endif %} -->
+
+<!-- {% if centos %} -->
+### CentOS Repository <a id="centos-repository"></a>
+
+```bash
+rpm --import https://packages.icinga.com/icinga.key
+wget https://packages.icinga.com/centos/ICINGA-release.repo -O /etc/yum.repos.d/ICINGA-release.repo
+```
+
+The packages for CentOS depend on other packages which are distributed
+as part of the [EPEL repository](https://fedoraproject.org/wiki/EPEL).
+
+CentOS 7:
+
+```bash
+yum install epel-release
+```
+
+Since Icinga Web v2.5 we also require a **newer PHP version** than what is available
+in RedHat itself. You need to enable the SCL repository, so that the dependencies
+can pull in the newer PHP.
+
+```bash
+yum install centos-release-scl
+```
+<!-- {% endif %} -->
+
+<!-- {% if rhel %} -->
+### RHEL Repository <a id="rhel-repository"></a>
+
+!!! info
+
+ A paid repository subscription is required for RHEL repositories. Get more information on
+ [icinga.com/subscription](https://icinga.com/subscription)
+
+ Don't forget to fill in the username and password section with your credentials in the local .repo file.
+
+```bash
+rpm --import https://packages.icinga.com/icinga.key
+wget https://packages.icinga.com/subscription/rhel/ICINGA-release.repo -O /etc/yum.repos.d/ICINGA-release.repo
+```
+
+If you are using RHEL you need to additionally enable the `optional` and `codeready-builder`
+repository before installing the [EPEL rpm package](https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F).
+
+#### RHEL 8
+
+```bash
+ARCH=$( /bin/arch )
+
+subscription-manager repos --enable rhel-8-server-optional-rpms
+subscription-manager repos --enable "codeready-builder-for-rhel-8-${ARCH}-rpms"
+
+dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
+```
+
+#### RHEL 7
+Since Icinga Web v2.5 we also require a **newer PHP version** than what is available
+in RedHat itself. You need to enable the SCL repository, so that the dependencies
+can pull in the newer PHP.
+
+```bash
+subscription-manager repos --enable rhel-7-server-optional-rpms
+subscription-manager repos --enable rhel-server-rhscl-7-rpms
+
+yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
+```
+<!-- {% endif %} -->
+
+<!-- {% if sles %} -->
+### SLES Repository <a id="rhel-repository"></a>
+
+!!! info
+
+ A paid repository subscription is required for RHEL repositories. Get more information on
+ [icinga.com/subscription](https://icinga.com/subscription)
+
+ Don't forget to fill in the username and password section with your credentials in the local .repo file.
+
+```bash
+rpm --import https://packages.icinga.com/icinga.key
+
+zypper ar https://packages.icinga.com/subscription/sles/ICINGA-release.repo
+zypper ref
+```
+
+You need to additionally enable a couple of SLES repositories to fulfill dependencies:
+
+```bash
+source /etc/os-release
+
+SUSEConnect -p sle-module-desktop-applications/$VERSION_ID/x86_64
+SUSEConnect -p sle-module-development-tools/$VERSION_ID/x86_64
+SUSEConnect -p sle-module-web-scripting/$VERSION_ID/x86_64
+SUSEConnect -p PackageHub/$VERSION_ID/x86_64
+```
+<!-- {% endif %} -->
+
+<!-- {% if amazon_linux %} -->
+### Amazon Linux 2 Repository <a id="amazon-linux-2-repository"></a>
+
+!!! info
+
+ A paid repository subscription is required for Amazon Linux repositories. Get more information on
+ [icinga.com/subscription](https://icinga.com/subscription)
+
+ Don't forget to fill in the username and password section with your credentials in the local .repo file.
+
+```bash
+rpm --import https://packages.icinga.com/icinga.key
+wget https://packages.icinga.com/subscription/amazon/ICINGA-release.repo -O /etc/yum.repos.d/ICINGA-release.repo
+```
+
+You need to install and enable the `amazon-linux-extras` repository to meet the requirements of
+Icinga Web 2 on Amazon Linux 2:
+
+```bash
+yum install -y amazon-linux-extras
+
+amazon-linux-extras enable php8.0
+```
+<!-- {% endif %} -->
+
+## Install Icinga Web 2 <a id="install-icingaweb2"></a>
+
+You can install Icinga Web 2 by using your distribution's package manager to install the `icingaweb2` package.
+The additional package `icingacli` is necessary to follow further steps in this guide.
+
+<!-- {% if debian %} -->
+<!-- {% if not icingaDocs %} -->
+#### Debian
+<!-- {% endif %} -->
+```bash
+apt-get install icingaweb2 icingacli
+```
+<!-- {% endif %} -->
+
+<!-- {% if ubuntu %} -->
+<!-- {% if not icingaDocs %} -->
+#### Ubuntu
+<!-- {% endif %} -->
+```bash
+apt-get install icingaweb2 libapache2-mod-php icingacli
+```
+
+The additional package `libapache2-mod-php` is necessary on Ubuntu to automatically
+install a web server and PHP and make Icinga Web 2 work out-of-the-box.
+<!-- {% endif %} -->
+
+<!-- {% if centos or rhel or amazon_linux %} -->
+!!! tip
+
+ If you have [SELinux](90-SELinux.md) enabled, the package `icingaweb2-selinux` is also required.
+<!-- {% endif %} -->
+
+<!-- {% if centos %} -->
+<!-- {% if not icingaDocs %} -->
+#### CentOS
+<!-- {% endif %} -->
+```
+dnf install icingaweb2 icingacli
+```
+<!-- {% endif %} -->
+
+<!-- {% if rhel %} -->
+<!-- {% if not icingaDocs %} -->
+#### RHEL
+<!-- {% endif %} -->
+#### RHEL 8
+```bash
+dnf install icingaweb2 icingacli
+```
+
+#### RHEL 7
+```bash
+yum install icingaweb2 icingacli
+```
+<!-- {% endif %} -->
+
+<!-- {% if sles %} -->
+<!-- {% if not icingaDocs %} -->
+#### SLES
+<!-- {% endif %} -->
+```bash
+zypper install icingaweb2 icingacli
+```
+<!-- {% endif %} -->
+
+<!-- {% if amazon_linux %} -->
+<!-- {% if not icingaDocs %} -->
+#### Amazon Linux 2
+<!-- {% endif %} -->
+```bash
+yum install icingaweb2 icingacli
+```
+<!-- {% endif %} -->
+
+## Install the Web Server <a id="install-the-web-server"></a>
+
+Ensure that you have a web server with PHP installed before proceeding,
+such as Apache or Nginx with PHP version ≥ 7.2. Depending on your operating system,
+you may need to install and configure the web server separately.
+An Apache configuration file to serve Icinga Web is already installed.
+If you want to use Nginx, you must manually create a configuration file using the following command.
+Save the output as a new file in the web server configuration directory:
+
+```bash
+icingacli setup config webserver nginx --document-root /usr/share/icingaweb2/public
+```
+
+## Prepare Web Setup <a id="prepare-web-setup-from-package"></a>
+
+You can set up Icinga Web 2 quickly and easily with the Icinga Web 2 setup wizard which is available the first time
+you visit Icinga Web 2 in your browser. When using the web setup you are required to authenticate using a token.
+In order to generate a token use the `icingacli`:
+
+```bash
+icingacli setup token create
+```
+
+In case you do not remember the token you can show it using the `icingacli`:
+
+```bash
+icingacli setup token show
+```
+
+<!-- {% if debian or ubuntu %} -->
+You need to manually create a database and a database user prior to starting the web wizard.
+This is due to local security restrictions whereas the web wizard cannot create a database/user through
+a local unix domain socket.
+
+```bash
+MariaDB [mysql]> CREATE DATABASE icingaweb2;
+
+MariaDB [mysql]> GRANT ALL ON icingaweb2.* TO icingaweb2@localhost IDENTIFIED BY 'CHANGEME';
+```
+
+You may also create a separate administrative account with all privileges instead.
+
+!!! note
+
+ This is only required if you are using a local database as authentication type.
+<!-- {% endif %} -->
+
+### Start Web Setup <a id="start-web-setup-from-package"></a>
+
+Finally visit Icinga Web 2 in your browser to access the setup wizard and complete the installation:
+`/icingaweb2/setup`.
+
+<!-- {% if debian or ubuntu %} -->
+!!! hint
+
+ Use the same database, user and password details created above when asked.
+<!-- {% endif %} -->
+
+The setup wizard automatically detects the required packages. In case one of them is missing,
+e.g. a PHP module, please install the package, restart your webserver and reload the setup page.
+
+<!-- {% if sles %} -->
+!!! note
+
+ If you're using php-fpm on SLES 15 SP2 onwards, `/etc/icingaweb2` may not be writable.
+ That's because the default systemd unit file for php-fpm has `ProtectSystem=full`
+ enabled. You want to lookup/add the systemd setting `ReadWritePaths=` in this case and
+ add `/etc/icingaweb2` to it. Alternatively you can also define a different configuration
+ directory using the environment variable `ICINGAWEB_CONFIGDIR`.
+<!-- {% endif %} -->
+
+<!-- {% if centos or rhel or amazon_linux %} -->
+!!! note
+
+ If you have SELinux enabled, please ensure to either have the selinux package for Icinga Web 2 installed, or disable it.
+<!-- {% endif %} -->
+
+<!-- {% else %} --><!-- {# end from_source elif #} -->
+<!-- {% if not icingaDocs %} -->
+## Installing Icinga Web 2 from Source <a id="installing-from-source"></a>
+<!-- {% endif %} -->
+
+Although the preferred way of installing Icinga Web 2 is to use packages, it is also possible to install Icinga Web 2
+directly from source.
+
+### Getting the Source <a id="getting-the-source"></a>
+
+First of all, you need to download the sources.
+
+Git clone:
+
+```bash
+cd /usr/share/
+git clone https://github.com/Icinga/icingaweb2.git icingaweb2
+```
+
+Tarball download (latest [release](https://github.com/Icinga/icingaweb2/releases/latest)):
+
+```bash
+cd /usr/share
+wget https://github.com/Icinga/icingaweb2/archive/v2.9.5.zip
+unzip v2.9.5.zip
+mv icingaweb2-2.9.5 icingaweb2
+```
+
+### Installing Requirements from Source <a id="installing-from-source-requirements"></a>
+
+You will need to install certain dependencies depending on your setup:
+
+* [Icinga 2](https://github.com/Icinga/icinga2) and [Icinga DB](https://github.com/Icinga/icingadb) to
+ monitor your infrastructure
+* A web server, e.g. Apache or Nginx
+* PHP version ≥ 7.2
+* [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (≥ 0.9)
+* [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (≥ 0.11)
+* The following PHP modules must be installed: cURL, json, gettext, fileinfo, intl, dom, OpenSSL and xml
+* The [pdfexport](https://github.com/Icinga/icingaweb2-module-pdfexport) module (≥0.10) is required for the
+ export to PDF
+* LDAP PHP library when using Active Directory or LDAP for authentication
+* MySQL or PostgreSQL PHP libraries
+
+The following example installs Apache2 as web server, MySQL as RDBMS and uses the PHP adapter for MySQL.
+Adopt the package requirements to your needs (e.g. adding ldap for authentication) and distribution.
+
+Example for RHEL/CentOS/Fedora:
+
+```bash
+yum install httpd mysql-server
+yum install php php-gd php-intl
+```
+
+The setup wizard will check the pre-requisites later on.
+
+
+### Installing Icinga Web 2 <a id="installing-from-source-example"></a>
+
+Choose a target directory and move Icinga Web 2 there.
+
+```bash
+mv icingaweb2 /usr/share/icingaweb2
+```
+
+### Configuring the Web Server <a id="configuring-web-server"></a>
+
+Use `icingacli` to generate web server configuration for either Apache or nginx.
+
+**Apache**:
+```bash
+./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public
+```
+
+**nginx**:
+```bash
+./bin/icingacli setup config webserver nginx --document-root /usr/share/icingaweb2/public
+```
+
+Save the output as new file in your webserver's configuration directory.
+
+Example for Apache on RHEL or CentOS:
+```bash
+./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public > /etc/httpd/conf.d/icingaweb2.conf
+```
+
+Example for Apache on SUSE:
+```bash
+./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public > /etc/apache2/conf.d/icingaweb2.conf
+```
+
+Example for Apache on Debian Jessie:
+```bash
+./bin/icingacli setup config webserver apache --document-root /usr/share/icingaweb2/public > /etc/apache2/conf-available/icingaweb2.conf
+a2enconf icingaweb2
+```
+
+Example for Apache on Alpine Linux:
+```bash
+icingacli setup config webserver apache --document-root /usr/share/webapps/icingaweb2/public > /etc/apache2/conf.d/icingaweb2.conf
+```
+### Preparing Icinga Web 2 Setup <a id="preparing-web-setup-from-source"></a>
+
+You can set up Icinga Web 2 quickly and easily with the Icinga Web 2 setup wizard which is available the first time
+you visit Icinga Web 2 in your browser. Please follow the steps listed below for preparing the web setup.
+
+Because both web and CLI must have access to configuration and logs, permissions will be managed using a special
+system group. The web server user and CLI user have to be added to this system group.
+
+Add the system group `icingaweb2` in the first place.
+
+**Fedora, RHEL, CentOS, SLES and OpenSUSE**:
+```bash
+groupadd -r icingaweb2
+```
+
+**Debian and Ubuntu**:
+```bash
+addgroup --system icingaweb2
+```
+
+Add your web server's user to the system group `icingaweb2`
+and restart the web server:
+
+**Fedora, RHEL and CentOS**:
+```bash
+usermod -a -G icingaweb2 apache
+service httpd restart
+```
+
+**SLES and OpenSUSE**:
+```bash
+usermod -A icingaweb2 wwwrun
+service apache2 restart
+```
+
+**Debian and Ubuntu**:
+```bash
+usermod -a -G icingaweb2 www-data
+service apache2 restart
+```
+
+**Alpine Linux**:
+```bash
+gpasswd -a apache icingaweb2
+rc-service apache2 restart
+```
+
+
+Use `icingacli` to create the configuration directory which defaults to **/etc/icingaweb2**:
+```bash
+./bin/icingacli setup config directory
+```
+
+
+When using the web setup you are required to authenticate using a token. In order to generate a token use the
+`icingacli`:
+```bash
+./bin/icingacli setup token create
+```
+
+In case you do not remember the token you can show it using the `icingacli`:
+```bash
+./bin/icingacli setup token show
+```
+
+### Icinga Web 2 Setup Wizard <a id="web-setup-from-source-wizard"></a>
+
+Finally visit Icinga Web 2 in your browser to access the setup wizard and complete the installation:
+`/icingaweb2/setup`.
+
+Paste the previously generated token and follow the steps on-screen. Then you are done here.
+
+If you prefer to set up the configuration manually, follow the
+[Icinga Web 2 Manual Configuration instructions](20-Advanced-Topics.md#web-setup-manual-from-source-config)
+<!-- {% endif %} --><!-- {# end index if #} -->
diff --git a/doc/02-Installation.md.d/01-Debian.md b/doc/02-Installation.md.d/01-Debian.md
new file mode 100644
index 0000000..9ff5665
--- /dev/null
+++ b/doc/02-Installation.md.d/01-Debian.md
@@ -0,0 +1,3 @@
+# Install Icinga Web 2 on Debian
+<!-- {% set debian = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/02-Installation.md.d/02-Ubuntu.md b/doc/02-Installation.md.d/02-Ubuntu.md
new file mode 100644
index 0000000..74b7075
--- /dev/null
+++ b/doc/02-Installation.md.d/02-Ubuntu.md
@@ -0,0 +1,3 @@
+# Install Icinga Web 2 on Ubuntu
+<!-- {% set ubuntu = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/02-Installation.md.d/03-CentOS.md b/doc/02-Installation.md.d/03-CentOS.md
new file mode 100644
index 0000000..0be7b1c
--- /dev/null
+++ b/doc/02-Installation.md.d/03-CentOS.md
@@ -0,0 +1,3 @@
+# Install Icinga Web 2 on CentOS
+<!-- {% set centos = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/02-Installation.md.d/04-RHEL.md b/doc/02-Installation.md.d/04-RHEL.md
new file mode 100644
index 0000000..cab5194
--- /dev/null
+++ b/doc/02-Installation.md.d/04-RHEL.md
@@ -0,0 +1,3 @@
+# Install Icinga Web 2 on RHEL
+<!-- {% set rhel = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/02-Installation.md.d/05-SLES.md b/doc/02-Installation.md.d/05-SLES.md
new file mode 100644
index 0000000..f17ab34
--- /dev/null
+++ b/doc/02-Installation.md.d/05-SLES.md
@@ -0,0 +1,3 @@
+# Install Icinga 2 on SLES
+<!-- {% set sles = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/02-Installation.md.d/06-Amazon-Linux.md b/doc/02-Installation.md.d/06-Amazon-Linux.md
new file mode 100644
index 0000000..d9cb7f2
--- /dev/null
+++ b/doc/02-Installation.md.d/06-Amazon-Linux.md
@@ -0,0 +1,3 @@
+# Install Icinga Web 2 on Amazon Linux
+<!-- {% set amazon_linux = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/02-Installation.md.d/07-From-Source.md b/doc/02-Installation.md.d/07-From-Source.md
new file mode 100644
index 0000000..540335f
--- /dev/null
+++ b/doc/02-Installation.md.d/07-From-Source.md
@@ -0,0 +1,3 @@
+# Install Icinga Web 2 from Source
+<!-- {% set from_source = True %} -->
+<!-- {% include "02-Installation.md" %} -->
diff --git a/doc/03-Configuration.md b/doc/03-Configuration.md
new file mode 100644
index 0000000..a854165
--- /dev/null
+++ b/doc/03-Configuration.md
@@ -0,0 +1,75 @@
+# Configuration <a id="configuration"></a>
+
+## Overview <a id="configuration-overview"></a>
+
+Apart from its web configuration capabilities, the local configuration is
+stored in `/etc/icingaweb2` by default (depending on your configuration setup).
+
+File/Directory | Description
+------------------------------------------------------- | ---------------------------------
+[config.ini](03-Configuration.md#configuration-general) | General configuration (global, logging, themes, etc.)
+[resources.ini](04-Resources.md#resources) | Global resources (Icinga Web 2 database for preferences and authentication, Icinga 2 IDO database)
+[roles.ini](06-Security.md#security-roles) | User specific roles (e.g. `administrators`) and permissions
+[authentication.ini](05-Authentication.md) | Authentication backends (e.g. database)
+enabledModules | Symlinks to enabled modules
+modules | Directory for module specific configuration
+
+
+## General Configuration <a id="configuration-general"></a>
+
+Navigate into **Configuration > Application > General **.
+
+This configuration is stored in the `config.ini` file in `/etc/icingaweb2`.
+
+### Global Configuration <a id="configuration-general-global"></a>
+
+
+Option | Description
+-------------------------|-----------------------------------------------
+show\_stacktraces | **Optional.** Whether to show debug stacktraces. Defaults to `0`.
+module\_path | **Optional.** Specifies the directories where modules can be installed. Multiple directories must be separated with colons.
+config\_resource | **Required.** Specify a defined [resource](04-Resources.md#resources-configuration-database) name.
+
+
+Example for storing the user preferences in the database resource `icingaweb_db`:
+
+```
+[global]
+show_stacktraces = "0"
+config_resource = "icingaweb_db"
+module_path = "/usr/share/icingaweb2/modules"
+```
+
+### Logging Configuration <a id="configuration-general-logging"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+log | **Optional.** Specifies the logging type. Can be set to `syslog`, `file`, `php` (web server's error log) or `none`.
+level | **Optional.** Specifies the logging level. Can be set to `ERROR`, `WARNING`, `INFORMATION` or `DEBUG`.
+file | **Optional.** Specifies the log file path if `log` is set to `file`.
+application | **Optional.** Specifies the application name if `log` is set to `syslog`.
+facility | **Optional.** Specifies the syslog facility if `log` is set to `syslog`. Can be set to `user`, `local0` to `local7`. Defaults to `user`.
+
+Example for more verbose debug logging into a file:
+
+```
+[logging]
+log = "file"
+level = "DEBUG"
+file = "/usr/share/icingaweb2/log/icingaweb2.log"
+```
+
+### Theme Configuration <a id="configuration-general-theme"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+default | **Optional.** Choose the default theme. Can be set to `Icinga`, `high-contrast`, `Winter`, 'colorblind' or your own installed theme. Defaults to `Icinga`. Note that this setting is case-sensitive because it refers to the filename of the theme.
+disabled | **Optional.** Set this to `1` if users should not be allowed to change their theme. Defaults to `0`.
+
+Example:
+
+```
+[themes]
+disabled = "1"
+default = "high-contrast"
+```
diff --git a/doc/04-Resources.md b/doc/04-Resources.md
new file mode 100644
index 0000000..ca362fa
--- /dev/null
+++ b/doc/04-Resources.md
@@ -0,0 +1,136 @@
+# Resources <a id="resources"></a>
+
+The configuration file `resources.ini` contains information about data sources that can be referenced in other
+configuration files. This allows you to manage all data sources at one central place, avoiding the need to edit several
+different files when the information about a data source changes.
+
+## Configuration <a id="resources-configuration"></a>
+
+Each section in `resources.ini` represents a data source with the section name being the identifier used to
+reference this specific data source. Depending on the data source type, the sections define different directives.
+The available data source types are `db`, `ldap` and `ssh` which will described in detail in the following
+paragraphs.
+
+Type | Description
+-------------------------|-----------------------------------------------
+db | A [database](04-Resources.md#resources-configuration-database) resource (e.g. Icinga 2 DB IDO or Icinga Web 2 user preferences)
+ldap | An [LDAP](04-Resources.md#resources-configuration-ldap) resource for authentication.
+ssh | Manage [SSH](04-Resources.md#resources-configuration-ssh) keys for remote access (e.g. command transport).
+
+
+### Database <a id="resources-configuration-database"></a>
+
+A Database resource defines a connection to a SQL database which
+can contain users and groups to handle authentication and authorization, monitoring data or user preferences.
+
+Option | Description
+------------------------------------|------------
+type | **Required.** Specifies the resource type. Must be set to `db`.
+db | **Required.** Database type. In most cases `mysql` or `pgsql`.
+host | **Required.** Connect to the database server on the given host. For using unix domain sockets, specify `localhost` for MySQL and the path to the unix domain socket directory for PostgreSQL.
+port | **Required.** Port number to use. MySQL defaults to `3306`, PostgreSQL defaults to `5432`. Mandatory for connections to a PostgreSQL database.
+username | **Required.** The database username.
+password | **Required.** The database password.
+dbname | **Required.** The database name.
+charset | **Optional.** The character set for the database connection.
+use\_ssl | **Optional.** Use SSL. Enables the following SSL options.
+ssl\_do\_not\_verify\_server\_cert | **Optional.** Disable validation of the server certificate. Only available for the `mysql` database and on PHP versions > 5.6.
+ssl\_cert | **Optional.** The file path to the SSL certificate. Only available for the `mysql` database.
+ssl\_key | **Optional.** The file path to the SSL key. Only available for the `mysql` database.
+ssl\_ca | **Optional.** The file path to the SSL certificate authority. Only available for the `mysql` database.
+ssl\_capath | **Optional.** The file path to the directory that contains the trusted SSL CA certificates, which are stored in PEM format.Only available for the `mysql` database.
+ssl\_cipher | **Optional.** A list of one or more permissible ciphers to use for SSL encryption, in a format understood by OpenSSL. For example: `DHE-RSA-AES256-SHA:AES128-SHA`. Only available for the `mysql` database.
+
+
+#### Example <a id="resources-configuration-database-example"></a>
+
+The name in brackets defines the resource name.
+
+```
+[icingaweb-mysql-tcp]
+type = db
+db = mysql
+host = 127.0.0.1
+port = 3306
+username = icingaweb
+password = icingaweb
+dbname = icingaweb
+
+[icingaweb-mysql-socket]
+type = db
+db = mysql
+host = localhost
+username = icingaweb
+password = icingaweb
+dbname = icingaweb
+
+[icingaweb-pgsql-socket]
+type = db
+db = pgsql
+host = /var/run/postgresql
+port = 5432
+username = icingaweb
+password = icingaweb
+dbname = icingaweb
+```
+
+### LDAP <a id="resources-configuration-ldap"></a>
+
+A LDAP resource represents a tree in a LDAP directory.
+LDAP is usually used for authentication and authorization.
+
+Option | Description
+-------------------------|-----------------------------------------------
+type | **Required.** Specifies the resource type. Must be set to `ldap`.
+hostname | **Required.** Connect to the LDAP server on the given host. You can also provide multiple hosts separated by a space.
+port | **Required.** Port number to use for the connection.
+root\_dn | **Required.** Root object of the tree, e.g. `ou=people,dc=icinga,dc=org`.
+bind\_dn | **Required.** The user to use when connecting to the server.
+bind\_pw | **Required.** The password to use when connecting to the server.
+encryption | **Optional.** Type of encryption to use: `none` (default), `starttls`, `ldaps`.
+timeout | **Optional.** Connection timeout for every LDAP connection. Defaults to `5`.
+disable_server_side_sort | **Optional.** Disable server side sorting. Defaults to automatic detection whether the server supports this.
+
+#### Server Side Sorting <a id="ldap-server-side-sort"></a>
+
+Icinga Web automatically detects whether the LDAP server supports server side sorting.
+If that is not the case, results get sorted on the client side.
+There are LDAP servers though which report that they support this feature in general but have it disabled for certain
+fields. This may lead to failures. With `disable_server_side_sort` it is possible to disable server side sorting and it
+has precedence over the automatic detection.
+
+#### Example <a id="resources-configuration-ldap-example"></a>
+
+The name in brackets defines the resource name.
+
+```
+[ad]
+type = ldap
+hostname = localhost
+port = 389
+root_dn = "ou=people,dc=icinga,dc=org"
+bind_dn = "cn=admin,ou=people,dc=icinga,dc=org"
+bind_pw = admin
+```
+
+### SSH <a id="resources-configuration-ssh"></a>
+
+A SSH resource contains the information about the user and the private key location, which can be used for the key-based
+ssh authentication.
+
+Option | Description
+-------------------------|-----------------------------------------------
+type | **Required.** Specifies the resource type. Must be set to `ssh`.
+user | **Required.** The username to use when connecting to the server.
+private\_key | **Required.** The path to the private key of the user.
+
+#### Example <a id="resources-configuration-ssh-example"></a>
+
+The name in brackets defines the resource name.
+
+```
+[ssh]
+type = "ssh"
+user = "ssh-user"
+private_key = "/etc/icingaweb2/ssh/ssh-user"
+```
diff --git a/doc/05-Authentication.md b/doc/05-Authentication.md
new file mode 100644
index 0000000..5923a8c
--- /dev/null
+++ b/doc/05-Authentication.md
@@ -0,0 +1,293 @@
+# Authentication <a id="authentication"></a>
+
+You can authenticate against Active Directory, LDAP, a MySQL or a PostgreSQL database or delegate
+authentication to the web server.
+
+Authentication methods can be chained to set up fallback authentication methods
+or if users are spread over multiple places.
+
+## Configuration <a id="authentication-configuration"></a>
+
+Navigate into **Configuration > Application > Authentication **.
+
+Authentication methods are configured in the `/etc/icingaweb2/authentication.ini` file.
+
+Each section in the authentication configuration represents a single authentication method.
+
+The order of entries in the authentication configuration determines the order of the authentication methods.
+If the current authentication method errors or if the current authentication method does not know the account being
+authenticated, the next authentication method will be used.
+
+## External Authentication <a id="authentication-configuration-external-authentication"></a>
+
+Authentication to the web server can be delegated with the `autologin` section
+which specifies an external backend.
+
+Option | Description
+-------------------------|-----------------------------------------------
+backend | **Required.** Specifies the backend type. Must be set to `external`.
+strip\_username\_regexp | **Optional.** Regular expression to strip off specific user name parts.
+
+Example:
+
+```
+# vim /etc/icingaweb2/authentication.ini
+
+[autologin]
+backend = external
+```
+
+If your web server is not configured for authentication though, the `autologin` section has no effect.
+
+### Example Configuration for Apache and Basic Authentication <a id="authentication-configuration-external-authentication-example"></a>
+
+The following example will show you how to enable external authentication in Apache
+using basic authentication.
+
+#### Create Basic Auth User <a id="authentication-configuration-external-authentication-example-user"></a>
+
+You can use the tool `htpasswd` to generate basic authentication credentials. This example writes the
+user credentials into the `.http-users` file.
+
+The following command creates a new file which adds the user `icingaadmin`.
+`htpasswd` will prompt you for a password.
+If you want to add more users to the file you have to omit the `-c` switch to not overwrite the file.
+
+```
+sudo htpasswd -c /etc/icingaweb2/.http-users icingaadmin
+```
+
+#### Apache Configuration <a id="authentication-configuration-external-authentication-example-apache"></a>
+
+Add the following configuration to the `&lt;Directory&gt;` directive in the `icingaweb2.conf` web server
+configuration file.
+
+```
+AuthType Basic
+AuthName "Icinga Web 2"
+AuthUserFile /etc/icingaweb2/.http-users
+Require valid-user
+```
+
+Restart your web server to apply the changes.
+
+Example on CentOS 7:
+
+```
+systemctl restart httpd
+```
+
+## Active Directory or LDAP Authentication <a id="authentication-configuration-ad-or-ldap-authentication"></a>
+
+If you want to authenticate against Active Directory or LDAP, you have to define an
+[LDAP resource](04-Resources.md#resources-configuration-ldap).
+This is referenced as data source for the Active Directory or LDAP configuration method.
+
+### LDAP <a id="authentication-configuration-ldap-authentication"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+backend | **Required.** Specifies the backend type. Must be set to `ldap`.
+resource | **Required.** The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
+user\_class | **Optional.** LDAP user class. Defaults to `inetOrgPerson`.
+user\_name\_attribute | **Optional.** LDAP attribute which contains the username. Defaults to `uid`.
+filter | **Optional.** LDAP search filter. Requires `user_class` and `user_name_attribute`.
+
+> **Note for SELinux**
+>
+> If you run into problems connecting with LDAP and have SELinux enabled, take a look [here](90-SELinux.md#selinux-optional-booleans).
+
+Example:
+
+```
+# vim /etc/icingaweb2/authentication.ini
+
+[auth_ldap]
+backend = ldap
+resource = my_ldap
+user_class = inetOrgPerson
+user_name_attribute = uid
+filter = "memberOf=cn=icinga_users,cn=groups,cn=accounts,dc=icinga,dc=org"
+```
+
+If `user_name_attribute` specifies multiple values all of them must be unique.
+Please keep in mind that a user will be logged in with the exact user id used to authenticate
+with Icinga Web 2 (e.g. an alias) ignoring the actual primary user id.
+
+### Active Directory <a id="authentication-configuration-ad-authentication"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+backend | **Required.** Specifies the backend type. Must be set to `msldap`.
+resource | **Required.** The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
+user\_class | **Optional.** LDAP user class. Defaults to `user`.
+user\_name\_attribute | **Optional.** LDAP attribute which contains the username. Defaults to `sAMAccountName`.
+filter | **Optional.** LDAP search filter. Requires `user_class` and `user_name_attribute`.
+
+Example:
+
+```
+# vim /etc/icingaweb2/authentication.ini
+
+[auth_ad]
+backend = msldap
+resource = my_ad
+```
+
+## Database Authentication <a id="authentication-configuration-db-authentication"></a>
+
+If you want to authenticate against a MySQL or a PostgreSQL database, you have to define a
+[database resource](04-Resources.md#resources-configuration-database) which will be referenced as data source for the database
+authentication method.
+
+Option | Description
+-------------------------|-----------------------------------------------
+backend | **Required.** Specifies the backend type. Must be set to `db`.
+resource | **Required.** The name of the database resource defined in [resources.ini](04-Resources.md#resources). |
+
+Example:
+
+```
+# vim /etc/icingaweb2/authentication.ini
+
+[auth_db]
+backend = db
+resource = icingaweb-mysql
+```
+
+Please read [this chapter](20-Advanced-Topics.md#advanced-topics-authentication-tips-manual-user-database-auth)
+in order to manually create users directly inside the database.
+
+
+## Groups <a id="authentication-configuration-groups"></a>
+
+Navigate into **Configuration > Application > Authentication **.
+
+Group configuration is stored in the `/etc/icingaweb2/groups.ini` file.
+
+### LDAP Groups <a id="authentication-configuration-groups-ldap"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+backend | **Required.** Specifies the backend type. Can be set to `ldap`, `msldap`.
+resource | **Required.** The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
+domain | **Optional.** The domain the LDAP server is responsible for. See [Domain-aware Authentication](05-Authentication.md#domain-aware-auth).
+user\_class | **Optional.** LDAP user class. Defaults to `inetOrgPerson` with `msldap` and `user` with `ldap`.
+user\_name\_attribute | **Optional.** LDAP attribute which contains the username. Defaults to `sAMAccountName` with `msldap` and `uid` with `ldap`.
+user\_base\_dn | **Optional.** The path where users can be found on the LDAP server.
+base_dn | **Optional.** LDAP base dn for groups. Leave empty to select all groups available using the specified resource.
+group\_class | **Optional.** LDAP group class. Defaults to `group`.
+group\_member\_attribute | **Optional.** LDAP attribute where a group's members are stored. Defaults to `member`.
+group\_name\_attribute | **Optional.** LDAP attribute which contains the groupname. Defaults to `sAMAccountName` with `msldap` and `gid` with `ldap`.
+group\_filter | **Optional.** LDAP group search filter. Requires `group_class` and `group_name_attribute`.
+nested\_group\_search | **Optional.** Enable nested group search in Active Directory based on the user. Defaults to `0`. Only available with `backend` type `msldap`.
+
+Example for Active Directory groups:
+
+```
+# vim /etc/icingaweb2/groups.ini
+
+[active directory]
+backend = "msldap"
+resource = "auth_ad"
+group_class = "group"
+user_class = "user"
+user_name_attribute = "userPrincipalName"
+```
+
+Example for Active Directory using the group backend resource `ad_company`.
+It also references the defined user backend resource `ad_users_company`.
+
+```
+# vim /etc/icingaweb2/groups.ini
+
+[ad_groups_company]
+backend = "msldap"
+resource = "ad_company"
+user_backend = "ad_users_company"
+nested_group_search = "1"
+base_dn = "ou=Icinga,ou=Groups,dc=company,dc=com"
+```
+
+### Database Groups <a id="authentication-configuration-groups-database"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+backend | **Required.** Specifies the backend type. Must be set to `db`.
+resource | **Required.** The name of the database resource defined in [resources.ini](04-Resources.md#resources).
+
+Example:
+
+```
+# vim /etc/icingaweb2/groups.ini
+
+[icingaweb2]
+backend = "db"
+resource = "icingaweb_db"
+```
+
+
+## Domain-aware Authentication <a id="domain-aware-auth"></a>
+
+If there are multiple LDAP/AD authentication backends with distinct domains, you should make Icinga Web 2 aware of the
+domains. This is possible since version 2.5 and can be done by configuring each LDAP/AD backend's domain. You can also
+use the GUI for this purpose. This enables you to automatically discover a suitable value based on your LDAP server's
+configuration. (AD: NetBIOS name, other LDAP: domain in DNS-notation)
+
+**Example:**
+
+```
+# vim /etc/icingaweb2/authentication.ini
+
+[auth_icinga]
+backend = ldap
+resource = icinga_ldap
+user_class = inetOrgPerson
+user_name_attribute = uid
+filter = "memberOf=cn=icinga_users,cn=groups,cn=accounts,dc=icinga,dc=com"
+domain = "icinga.com"
+
+[auth_example]
+backend = msldap
+resource = example_ad
+domain = EXAMPLE
+```
+
+If you configure the domains like above, the icinga.com user "jdoe" will have to log in as "jdoe@icinga.com" and the
+EXAMPLE employee "rroe" will have to log in as "rroe@EXAMPLE". They could also log in as "EXAMPLE\\rroe", but this gets
+converted to "rroe@EXAMPLE" as soon as the user logs in.
+
+> **Caution!**
+>
+> Enabling domain-awareness or changing domains in existing setups requires migration of the usernames in the Icinga Web 2
+> configuration. Consult `icingacli --help migrate config users` for details.
+
+### Default Domain <a id="default-auth-domain"></a>
+
+For the sake of simplicity a default domain can be configured (in `config.ini`).
+
+**Example:**
+
+```
+# vim /etc/icingaweb2/config.ini
+
+[authentication]
+default_domain = "icinga.com"
+```
+
+If you configure the default domain like above, the user "jdoe@icinga.com" will be able to just type "jdoe" as username
+while logging in.
+
+### How it works <a id="domain-aware-auth-process"></a>
+
+### Active Directory <a id="domain-aware-auth-ad"></a>
+
+When the user "jdoe@ICINGA" logs in, Icinga Web 2 walks through all configured authentication backends until it finds
+one which is responsible for that user -- e.g. an Active Directory backend with the domain "ICINGA". Then Icinga Web 2
+asks that backend to authenticate the user with the sAMAccountName "jdoe".
+
+### SQL Database <a id="domain-aware-auth-sqldb"></a>
+
+When the user "jdoe@icinga.com" logs in, Icinga Web 2 walks through all configured authentication backends until it
+finds one which is responsible for that user -- e.g. a MySQL backend (SQL database backends aren't domain-aware). Then
+Icinga Web 2 asks that backend to authenticate the user with the username "jdoe@icinga.com".
diff --git a/doc/06-Security.md b/doc/06-Security.md
new file mode 100644
index 0000000..b8d7cf2
--- /dev/null
+++ b/doc/06-Security.md
@@ -0,0 +1,242 @@
+# Security
+
+Access control is a vital part of configuring Icinga Web 2 securely. It is important that not every user that has
+access to Icinga Web 2 can perform any action or see any host and service. Allow only a small group of administrators
+to change the Icinga Web 2 configuration to prevent mis-configuration and security breaches. Define different rules
+to users and groups of users which should only see a part of the monitoring environment they're in charge of.
+
+This chapter will describe how to configure such rules in Icinga Web 2 and how permissions, refusals, restrictions
+and role inheritance work.
+
+## Basics
+
+Icinga Web 2 access control is done by defining **roles** that associate privileges with **users** and **groups**.
+Privileges of a role consist of **permissions**, **refusals** and **restrictions**. A role can **inherit** privileges
+from another role.
+
+### Role Memberships
+
+A role is tied to users or groups of users. Upon login, a user's roles are identified by the username or names of
+groups the user is a member of.
+
+> **Note**
+>
+> Since Icinga Web 2, users in the Icinga configuration and the web authentication are separated, to allow use of
+> external authentication providers. This means that users and groups defined in the Icinga configuration are not
+> available to Icinga Web 2. It uses its own authentication backend to fetch users and groups from,
+> [which must be configured separately](05-Authentication.md#authentication).
+
+### Privileges
+
+Permissions are used to grant access. Whether this means that a user can see a certain area or perform a distinct
+action is fully up to the permission in question. Without granting a permission, the user will lack access and won't
+see the area or perform the action.
+
+Refusals are used to deny access. So they're the exact opposite of permissions. Most permissions can be refused.
+Refusing a permission will block the user's access no matter if another role grants the permission. Refusals
+override permissions.
+
+Restrictions are expressions that limit access. What this exactly means is up to how the restriction is being utilized.
+Without any restriction, a user is supposed to see *everything*. A user that occupies multiple roles, which all define
+a restriction of the same type, will see *more*.
+
+## Roles
+
+A user can occupy multiple roles. Permissions and restrictions stack up in this case, thus will grant *more* access.
+Refusals still override permissions however. A refusal of one role negates the granted permission of any other role.
+
+### Configuration
+
+Roles can be changed either through the UI, by navigating to the page **Configuration > Authentication > Roles**,
+or by editing the configuration file `/etc/icingaweb2/roles.ini`.
+
+#### Example
+
+The following shows a role definition from the configuration file mentioned above:
+
+```
+[winadmin]
+users = "jdoe, janedoe"
+groups = "admin"
+permissions = "config/*, module/monitoring, monitoring/commands/schedule-check"
+refusals = "config/authentication"
+monitoring/filter/objects = "host_name=*win*"
+```
+
+This describes a role with the name `winadmin`. The users `jdoe` and `janedoe` are members of it. Just like the
+members of group `admin` are. Full configuration access is granted, except of the authentication configuration,
+which is forbidden. It also grants access to the *monitoring* module which includes the ability to re-schedule
+checks, but only on objects related to hosts whose name contain `win`.
+
+#### Syntax
+
+Each role is defined as a section, with the name of the role as section name. The following
+options can be defined for each role in a default Icinga Web 2 installation:
+
+Name | Description
+--------------------------|-----------------------------------------------
+parent | The name of the role from which to inherit privileges.
+users | Comma-separated list of **usernames** that should occupy this role.
+groups | Comma-separated list of **group names** whose users should occupy this role.
+permissions | Comma-separated list of **permissions** granted by this role.
+refusals | Comma-separated list of **permissions** refused by this role.
+unrestricted | If set to `1`, owners of this role are not restricted in any way (Default: `0`)
+monitoring/filter/objects | **Filter expression** that restricts the access to monitoring objects.
+
+### Administrative Roles
+
+Roles that have the wildcard `*` as permission, have full access and don't need any further permissions. However,
+they are still affected by refusals.
+
+Unrestricted roles are supposed to allow users to access data without being limited to a subset of it. Once a user
+occupies an unrestricted role, restrictions of the same and any other role are ignored.
+
+### Inheritance
+
+A role can inherit privileges from another role. Privileges are then combined the same way as if a user occupies
+all roles in the inheritance path. Or to rephrase that, each role shares its members with all of its parents.
+
+## Permissions
+
+Each permission in Icinga Web 2 is denoted by a **namespaced key**, which is used to group permissions. All permissions
+that affect the configuration of Icinga Web 2, are in a namespace called **config**, while all configuration options
+that affect modules are covered by the permission `config/modules`.
+
+**Wildcards** can be used to grant all permissions in a certain namespace. The permission `config/*` grants access to
+all configuration options. Just specifying a wildcard `*` will grant all permissions.
+
+Access to modules is restricted to users who have the related module permission granted. Icinga Web 2 provides
+a module permission in the format `module/<moduleName>` for each installed module.
+
+### General Permissions
+
+Name | Permits
+-----------------------------|-----------------------------------------------
+\* | allow everything, including module-specific permissions
+application/announcements | allow to manage announcements
+application/log | allow to view the application log
+config/\* | allow full config access
+config/access-control/\* | allow to fully manage access control
+config/access-control/groups | allow to manage groups
+config/access-control/roles | allow to manage roles
+config/access-control/users | allow to manage user accounts
+config/general | allow to adjust the general configuration
+config/modules | allow to enable/disable and configure modules
+config/navigation | allow to view and adjust shared navigation items
+config/resources | allow to manage resources
+user/\* | allow all account related functionalities
+user/application/stacktraces | allow to adjust in the preferences whether to show stacktraces
+user/password-change | allow password changes in the account preferences
+user/share/navigation | allow to share navigation items
+module/`<moduleName>` | allow access to module `<moduleName>` (e.g. `module/monitoring`)
+
+### Monitoring Module Permissions
+
+The built-in monitoring module defines an additional set of permissions, that
+is described in detail in the monitoring module documentation.
+
+## Restrictions
+
+Restrictions can be used to define what a user can see by specifying an expression that applies to a defined set of
+data. By default, when no restrictions are defined, a user will be able to see the entire data that is available.
+
+The syntax of the expression used to define a particular restriction varies. This can be a comma-separated list of
+terms, or [a full-blown filter](06-Security.md#filter-expressions). For more details on particular restrictions,
+check the table below or the module's documentation providing the restriction.
+
+### General Restrictions
+
+Name | Applies to
+--------------------------|------------------------------------------------------------------------------------------
+application/share/users | which users a user can share navigation items with (comma-separated list of usernames)
+application/share/groups | which groups a user can share navigation items with (comma-separated list of group names)
+
+### Username placeholder
+
+It is possible to reference the local username (without the domain part) of the user in restrictions. To accomplish
+this, put the macro `$user.local_name$` in the restriction where you want it to appear.
+
+This can come in handy if you have e.g. an attribute on hosts or services defining which user is responsible for it:
+`_host_deputy=$user.local_name$|_service_deputy=$user.local_name$`
+
+### Filter Expressions
+
+Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in
+the monitoring module documentation.
+
+Any filter expression that is allowed in the filtered view, is also an allowed filter expression.
+This means, that it is possible to define negations, wildcards, and even nested
+filter expressions containing AND and OR-Clauses.
+
+The filter expression will be **implicitly** added as an **AND-Clause** to each query on
+the filtered data. The following shows the filter expression `host_name=*win*` being applied on `monitoring/filter/objects`.
+
+
+Regular filter query:
+
+ AND-- service_problem = 1
+ |
+ +--- service_handled = 0
+
+
+With our restriction applied, any user affected by this restrictions will see the
+results of this query instead:
+
+
+ AND-- host_name = *win*
+ |
+ +--AND-- service_problem = 1
+ |
+ +--- service_handled = 0
+
+#### Stacking Filters
+
+When multiple roles assign restrictions to the same user, either directly or indirectly
+through a group, all filters will be combined using an **OR-Clause**, resulting in the final
+expression:
+
+
+ AND-- OR-- $FILTER1
+ | |
+ | +-- $FILTER2
+ | |
+ | +-- $FILTER3
+ |
+ +--AND-- service_problem = 1
+ |
+ +--- service_handled = 0
+
+
+As a result, a user is be able to see hosts that are matched by **ANY** of
+the filter expressions. The following examples will show the usefulness of this behavior:
+
+#### Example 1: Negation
+
+```
+[winadmin]
+groups = "windows-admins"
+monitoring/filter/objects = "host_name=*win*"
+```
+
+Will display only hosts and services whose host name contains **win**.
+
+```
+[webadmin]
+groups = "web-admins"
+monitoring/filter/objects = "host_name!=*win*"
+```
+
+Will only match hosts and services whose host name does **not** contain **win**
+
+Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services.
+
+#### Example 2: Hostgroups
+
+```
+[unix-server]
+groups = "unix-admins"
+monitoring/filter/objects = "(hostgroup_name=bsd-servers|hostgroup_name=linux-servers)"
+```
+
+This role allows all members of the group unix-admins to see hosts and services
+that are part of the host-group linux-servers or the host-group bsd-servers.
diff --git a/doc/07-Preferences.md b/doc/07-Preferences.md
new file mode 100644
index 0000000..73abead
--- /dev/null
+++ b/doc/07-Preferences.md
@@ -0,0 +1,21 @@
+# Preferences <a id="preferences"></a>
+
+Preferences are settings a user can set for their account only,
+for example the language and time zone.
+
+Preferences can be stored either in a MySQL or in a PostgreSQL database. The database must be configured.
+
+## Configuration <a id="preferences-configuration"></a>
+
+The preference configuration backend is defined in the global [config.ini](03-Configuration.md#configuration-general-global) file.
+
+You have to define a [database resource](04-Resources.md#resources-configuration-database)
+which will be referenced as resource for the preferences storage.
+
+You need to add the following section to the global [config.ini](03-Configuration.md#configuration-general-global) file
+in order to store preferences in a database.
+
+```
+[global]
+config_resource = "icingaweb_db"
+```
diff --git a/doc/08-Modules.md b/doc/08-Modules.md
new file mode 100644
index 0000000..6c81c5b
--- /dev/null
+++ b/doc/08-Modules.md
@@ -0,0 +1,69 @@
+# Modules
+
+## Installation
+
+A module should be installed in one of the [configured module paths](03-Configuration.md#general-configuration).
+The default path in most installations is `/usr/share/icingaweb2/modules`.
+
+Each directory in there contains the files for a particular module. The directory's name has to be the one
+that is provided by the module's documentation. If there is none provided, it is usually the name of the
+module in all lowercase. Some modules may use a name prefixed with `icingaweb2-module-`. If this is the case,
+the directory's name should be that, but without the prefix.
+(e.g. `icingaweb2-module-map` turns into `/usr/share/icingaweb2/modules/map`)
+
+> **Note:**
+>
+> Remember to ensure that your web-server can access those files. Though, read permission only.
+
+Once a module's files are in place, it needs to be enabled first before it can be used. This can either be done in
+the UI at `Configuration -> Modules` or by using the icingacli: `icingacli module enable map`
+
+In order for other non-admin users to access the module's functionality, it is required to permit access first.
+This is done by granting the permission `module/<module-name>`. (e.g. `module/map`)
+
+### Module Specific Instructions
+
+A module may require further installation steps. Whether these need to be performed before enabling the module,
+should be provided by the module's documentation. In any case, don't forget to apply these as well, otherwise
+the module will most likely not function correctly.
+
+### Examples
+
+There are sample installation instructions provided for your convenience:
+
+**Sample Tarball installation**
+
+```sh
+MODULE_NAME="map"
+MODULE_VERSION="v1.1.0"
+MODULE_AUTHOR="nbuchwitz"
+MODULES_PATH="/usr/share/icingaweb2/modules"
+MODULE_PATH="${MODULES_PATH}/${MODULE_NAME}"
+RELEASES="https://github.com/${MODULE_AUTHOR}/icingaweb2-module-${MODULE_NAME}/archive"
+mkdir "$MODULE_PATH" \
+&& wget -q $RELEASES/${MODULE_VERSION}.tar.gz -O - \
+ | tar xfz - -C "$MODULE_PATH" --strip-components 1
+icingacli module enable "${MODULE_NAME}"
+```
+
+**Sample GIT installation**
+
+```sh
+MODULE_NAME="map"
+MODULE_VERSION="v1.1.0"
+MODULE_AUTHOR="nbuchwitz"
+REPO="https://github.com/${MODULE_AUTHOR}/icingaweb2-module-${MODULE_NAME}"
+MODULES_PATH="/usr/share/icingaweb2/modules"
+git clone ${REPO} "${MODULES_PATH}/${MODULE_NAME}" --branch "${MODULE_VERSION}"
+icingacli module enable "${MODULE_NAME}"
+```
+
+## Configuration
+
+A module may also require configuration. Most modules provide additional tabs at their configuration page.
+This is accessible in the UI at `Configuration -> Modules`. If not, and something isn't working, check the
+module's documentation again for hints.
+
+If you need access to a module's configuration files directly, they should be in a subdirectory `modules`
+of Icinga Web 2's configuration directory. That is usually `/etc/icingaweb2/modules`. Each directory in
+there should be named the same as its installation path. (e.g. `/etc/icingaweb2/modules/map`)
diff --git a/doc/15-Auditing.md b/doc/15-Auditing.md
new file mode 100644
index 0000000..c44cbfe
--- /dev/null
+++ b/doc/15-Auditing.md
@@ -0,0 +1,14 @@
+# Auditing <a id="auditing"></a>
+
+Auditing in Icinga Web 2 is possible with a separate [module](https://github.com/Icinga/icingaweb2-module-audit).
+
+This module provides different logging facilities to store/record activities by Icinga Web 2 users.
+
+Icinga Web 2 currently emits the following activities:
+
+## Authentication
+
+Activity | Additional Data
+---------|----------------
+login | username
+logout | username
diff --git a/doc/20-Advanced-Topics.md b/doc/20-Advanced-Topics.md
new file mode 100644
index 0000000..4f47e02
--- /dev/null
+++ b/doc/20-Advanced-Topics.md
@@ -0,0 +1,380 @@
+# Advanced Topics <a id="advanced-topics"></a>
+
+This chapter provides details for advanced Icinga Web 2 topics.
+
+* [Global URL parameters](20-Advanced-Topics.md#global-url-parameters)
+* [VirtualHost configuration](20-Advanced-Topics.md#virtualhost-configuration)
+* [Advanced Authentication Tips](20-Advanced-Topics.md#advanced-topics-authentication-tips)
+* [Source installation](20-Advanced-Topics.md#installing-from-source)
+* [Automated setup](20-Advanced-Topics.md#web-setup-automation)
+* [Kiosk Mode Configuration](20-Advanced-Topics.md#kiosk-mode)
+
+## Global URL Parameters <a id="global-url-parameters"></a>
+
+Parameters starting with `_` are for development purposes only.
+
+Parameter | Value | Description
+------------------|---------------|--------------------------------
+showFullscreen | - | Hides the left menu and optimizes the layout for full screen resolution.
+showCompact | - | Provides a compact view. Hides the title and upper menu. This is helpful to embed a dashboard item into an external iframe.
+format | json/csv/sql | Selected views can be exported as JSON or CSV. This also is available in the upper menu. You can also export the SQL queries for manual analysis.
+\_dev | 0/1 | Whether the server should return compressed or full JS/CSS files. This helps debugging browser console errors.
+
+
+
+Examples for `showFullscreen`:
+
+http://localhost/icingaweb2/dashboard?showFullscreen
+http://localhost/icingaweb2/monitoring/list/services?service_problem=1&sort=service_severity&showFullscreen
+
+Examples for `showCompact`:
+
+http://localhost/icingaweb2/dashboard?showCompact&showFullscreen
+http://localhost/icingaweb2/monitoring/list/services?service_problem=1&sort=service_severity&showCompact
+
+Examples for `format`:
+
+http://localhost/icingaweb2/monitoring/list/services?format=json
+http://localhost/icingaweb2/monitoring/list/services?service_problem=1&sort=service_severity&dir=desc&format=csv
+
+
+## VirtualHost Configuration <a id="virtualhost-configuration"></a>
+
+This describes how to run Icinga Web 2 on your FQDN's `/` entry point without
+any redirect to `/icingaweb2`.
+
+### VirtualHost Configuration for Apache <a id="virtualhost-configuration-apache"></a>
+
+Use the setup CLI commands to generate the default Apache configuration which serves
+Icinga Web 2 underneath `/icingaweb2`.
+
+The next steps are to create the VirtualHost configuration:
+
+* Copy the `<Directory "/usr/share/icingaweb2/public">` into the main VHost configuration. Don't forget to correct the indent.
+* Set the `DocumentRoot` variable to look into `/usr/share/icingaweb2/public`
+* Modify the `RewriteBase` variable to use `/` instead of `/icingaweb2`
+
+Example on RHEL/CentOS:
+
+```
+vim /etc/httpd/conf.d/web.icinga.com.conf
+
+<VirtualHost *:80>
+ ServerName web.icinga.com
+
+ ## Vhost docroot
+ # modified for Icinga Web 2
+ DocumentRoot "/usr/share/icingaweb2/public"
+
+ ## Rewrite rules
+ RewriteEngine On
+
+ <Directory "/usr/share/icingaweb2/public">
+ Options SymLinksIfOwnerMatch
+ AllowOverride None
+
+ <IfModule mod_authz_core.c>
+ # Apache 2.4
+ <RequireAll>
+ Require all granted
+ </RequireAll>
+ </IfModule>
+
+ <IfModule !mod_authz_core.c>
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+ </IfModule>
+
+ SetEnv ICINGAWEB_CONFIGDIR "/etc/icingaweb2"
+
+ EnableSendfile Off
+
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ # modified base
+ RewriteBase /
+ RewriteCond %{REQUEST_FILENAME} -s [OR]
+ RewriteCond %{REQUEST_FILENAME} -l [OR]
+ RewriteCond %{REQUEST_FILENAME} -d
+ RewriteRule ^.*$ - [NC,L]
+ RewriteRule ^.*$ index.php [NC,L]
+ </IfModule>
+
+ <IfModule !mod_rewrite.c>
+ DirectoryIndex error_norewrite.html
+ ErrorDocument 404 /error_norewrite.html
+ </IfModule>
+ </Directory>
+</VirtualHost>
+```
+
+Reload Apache and open the FQDN in your web browser.
+
+```
+systemctl reload httpd
+```
+
+## Advanced Authentication Tips <a id="advanced-topics-authentication-tips"></a>
+
+### Manual User Creation for Database Authentication Backend <a id="advanced-topics-authentication-tips-manual-user-database-auth"></a>
+
+Icinga Web 2 v2.5+ uses the [native password hash algorithm](https://php.net/manual/en/faq.passwords.php)
+provided by PHP 5.6+.
+
+In order to generate a password, run the following command with the PHP CLI >= 5.6:
+
+```
+php -r 'echo password_hash("yourtopsecretpassword", PASSWORD_DEFAULT);'
+```
+
+Please note that the hashed output changes each time. This is expected.
+
+Insert the user into the database using the generated password hash.
+
+```
+INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('icingaadmin', 1, '$2y$10$bEKU6.1bRYjE7wxktqfeO.IGV9pYAkDBeXEbjMFSNs26lKTI0JQ1q');
+```
+
+#### Puppet <a id="advanced-topics-authentication-tips-manual-user-database-auth-puppet"></a>
+
+Please do note that the `$` character needs to be escaped with a leading backslash in your
+Puppet manifests.
+
+Example from [puppet-icingaweb2](https://github.com/Icinga/puppet-icingaweb2):
+
+```
+ exec { 'create default user':
+ command => "mysql -h '${db_host}' -P '${db_port}' -u '${db_username}' -p'${db_password}' '${db_name}' -Ns -e 'INSERT INTO icingaweb_user (name, active, password_hash) VALUES (\"icingaadmin\", 1, \"\$2y\$10\$QnXfBjl1RE6TqJcY85ZKJuP9AvAV3ont9QihMTFQ/D/vHmAWaz.lG\")'",
+ refreshonly => true,
+ }
+```
+
+
+## Icinga Web 2 Manual Setup <a id="web-setup-manual-from-source"></a>
+
+If you have chosen not to run the setup wizard, you will need further knowledge
+about
+
+* manual creation of the Icinga Web 2 database `icingaweb2` including a default user (optional as authentication and session backend)
+* additional configuration for the application
+* additional configuration for the monitoring module (e.g. the IDO database and external command pipe from Icinga 2)
+
+This comes in handy if you are planning to deploy Icinga Web 2 automatically using
+Puppet, Ansible, Chef, etc.
+
+> **Warning**
+>
+> Read the documentation on the respective linked configuration sections before
+> deploying the configuration manually.
+>
+> If you are unsure about certain settings, use the setup wizard as described in the
+> [installation instructions](02-Installation.md) once and then collect the generated
+> configuration as well as sql dumps.
+
+### Icinga Web 2 Manual Database Setup <a id="web-setup-manual-from-source-database"></a>
+
+Create the database and add a new user as shown below for MySQL/MariaDB:
+
+```
+sudo mysql -p
+
+CREATE DATABASE icingaweb2;
+GRANT SELECT, INSERT, UPDATE, DELETE, DROP, CREATE VIEW, INDEX, EXECUTE ON icingaweb2.* TO 'icingaweb2'@'localhost' IDENTIFIED BY 'icingaweb2';
+quit
+
+mysql -p icingaweb2 < /usr/share/icingaweb2/schema/mysql.schema.sql
+```
+
+
+Then generate a new password hash as described in the [authentication docs](05-Authentication.md#authentication-configuration-db-setup)
+and use it to insert a new user called `icingaadmin` into the database.
+
+```
+mysql -p icingaweb2
+
+INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('icingaadmin', 1, '$1$EzxLOFDr$giVx3bGhVm4lDUAw6srGX1');
+quit
+```
+
+### Icinga Web 2 Manual Configuration <a id="web-setup-manual-from-source-config"></a>
+
+
+[resources.ini](04-Resources.md#resources) providing the details for the Icinga Web 2 and
+Icinga 2 IDO database configuration. Example for MySQL:
+
+```
+vim /etc/icingaweb2/resources.ini
+
+[icingaweb2]
+type = "db"
+db = "mysql"
+host = "localhost"
+port = "3306"
+dbname = "icingaweb2"
+username = "icingaweb2"
+password = "icingaweb2"
+
+
+[icinga2]
+type = "db"
+db = "mysql"
+host = "localhost"
+port = "3306"
+dbname = "icinga"
+username = "icinga"
+password = "icinga"
+```
+
+[config.ini](03-Configuration.md#configuration) defining general application settings.
+
+```
+vim /etc/icingaweb2/config.ini
+
+[logging]
+log = "syslog"
+level = "ERROR"
+application = "icingaweb2"
+
+
+[preferences]
+type = "db"
+resource = "icingaweb2"
+```
+
+[authentication.ini](05-Authentication.md#authentication) for e.g. using the previously created database.
+
+```
+vim /etc/icingaweb2/authentication.ini
+
+[icingaweb2]
+backend = "db"
+resource = "icingaweb2"
+```
+
+
+[roles.ini](06-Security.md#security) granting the previously added `icingaadmin` user all permissions.
+
+```
+vim /etc/icingaweb2/roles.ini
+
+[admins]
+users = "icingaadmin"
+permissions = "*"
+```
+
+### Icinga Web 2 Manual Configuration Monitoring Module <a id="web-setup-manual-from-source-config-monitoring-module"></a>
+
+
+**config.ini** defining additional security settings.
+
+```
+vim /etc/icingaweb2/modules/monitoring/config.ini
+
+[security]
+protected_customvars = "*pw*,*pass*,community"
+```
+
+**backends.ini** referencing the Icinga 2 DB IDO resource.
+
+```
+vim /etc/icingaweb2/modules/monitoring/backends.ini
+
+[icinga2]
+type = "ido"
+resource = "icinga2"
+```
+
+**commandtransports.ini** defining the Icinga 2 API command transport.
+
+```
+vim /etc/icingaweb2/modules/monitoring/commandtransports.ini
+
+[icinga2]
+transport = "api"
+host = "localhost"
+port = "5665"
+username = "api"
+password = "api"
+```
+
+### Icinga Web 2 Manual Setup Login <a id="web-setup-manual-from-source-login"></a>
+
+Finally visit Icinga Web 2 in your browser to login as `icingaadmin` user: `/icingaweb2`.
+
+## Automating the Installation of Icinga Web 2 <a id="web-setup-automation"></a>
+
+Prior to creating your own script, please look into the official resources
+which may help you already:
+
+* [Puppet module](https://icinga.com/products/integrations/puppet/)
+* [Chef cookbook](https://icinga.com/products/integrations/chef/)
+
+If you are automating the installation of Icinga Web 2, you may want to skip the wizard and do things yourself.
+These are the steps you'd need to take assuming you are using MySQL/MariaDB. If you are using PostgreSQL please adapt
+accordingly. Note you need to have successfully completed the Icinga 2 installation, installed the Icinga Web 2 packages
+and all the other steps described above first.
+
+1. Install PHP dependencies: `php`, `php-intl`, `php-imagick`, `php-gd`, `php-mysql`, `php-curl`, `php-mbstring` used
+by Icinga Web 2.
+2. Create a database for Icinga Web 2, i.e. `icingaweb2`.
+3. Import the database schema: `mysql -D icingaweb2 < /usr/share/icingaweb2/schema/mysql.schema.sql`.
+4. Insert administrator user in the `icingaweb2` database:
+`INSERT INTO icingaweb_user (name, active, password_hash) VALUES ('admin', 1, '<hash>')`, where `<hash>` is the output
+of `php -r 'echo password_hash("yourtopsecretpassword", PASSWORD_DEFAULT);'`.
+5. Make sure the `ido-mysql` and `api` features are enabled in Icinga 2: `icinga2 feature enable ido-mysql` and
+`icinga2 feature enable api`.
+6. Generate Apache/nginx config. This command will print an apache config for you on stdout:
+`icingacli setup config webserver apache`. Similarly for nginx. You need to place that configuration in the right place,
+for example `/etc/apache2/sites-enabled/icingaweb2.conf`.
+7. Add `www-data` user to `icingaweb2` group if not done already (`usermod -a -G icingaweb2 www-data`).
+8. Create the Icinga Web 2 configuration in `/etc/icingaweb2`. The directory can be easily created with:
+`icingacli setup config directory`. This command ensures that the directory has the appropriate ownership and
+permissions. If you want to create the directory manually, make sure to chown the group to `icingaweb2` and set the
+access mode to `2770`.
+
+The structure of the configurations looks like the following:
+
+```
+/etc/icingaweb2/
+/etc/icingaweb2/authentication.ini
+/etc/icingaweb2/modules
+/etc/icingaweb2/modules/monitoring
+/etc/icingaweb2/modules/monitoring/config.ini
+/etc/icingaweb2/modules/monitoring/instances.ini
+/etc/icingaweb2/modules/monitoring/backends.ini
+/etc/icingaweb2/roles.ini
+/etc/icingaweb2/config.ini
+/etc/icingaweb2/enabledModules
+/etc/icingaweb2/enabledModules/monitoring
+/etc/icingaweb2/enabledModules/doc
+/etc/icingaweb2/resources.ini
+```
+
+Have a look [here](20-Advanced-Topics.md#web-setup-manual-from-source-config) for the contents of the files.
+
+## Kiosk Mode Configuration <a id="kiosk-mode"></a>
+
+Be aware that when you create a kiosk user every person who has access to the kiosk is able to perform tasks on it.
+Therefore you would need to create a user in the `roles.ini` in `/etc/icingaweb2/roles.ini`.
+
+ [kioskusers]
+ users = "kiosk"
+
+If you need special permissions you should add those permissions to the user via the admin account in icingaweb2 to the role of the kiosk user.
+
+For the Dashboard system where you want to display the kiosk you can add also the following part into the `icingaweb2.conf`.
+So it starts directly into the kiosk mode.
+If you want to show a specific Dashboard you can enforce this onto the kiosk user via the [enforceddashboard](https://github.com/Thomas-Gelf/icingaweb2-module-enforceddashboard) module.
+
+```
+<ifmodule mod_authz_core.c>
+ # Apache 2.4
+ SetEnvIf Remote_Addr "X.X.X.X" REMOTE_USER=kiosk
+ <requireall>
+ Require all granted
+ </requireall>
+</ifmodule>
+```
+
+Replace Remote_Addr with the IP where the kiosk user is accessing the Web to restrict further usage from other IPs.
diff --git a/doc/60-Hooks.md b/doc/60-Hooks.md
new file mode 100644
index 0000000..2dc645d
--- /dev/null
+++ b/doc/60-Hooks.md
@@ -0,0 +1,49 @@
+# Hooks
+
+## ConfigFormEventsHook
+
+The `ConfigFormEventsHook` allows developers to hook into the handling of configuration forms. It provides three methods:
+
+* `appliesTo()`
+* `isValid()`
+* `onSuccess()`
+
+`appliesTo()` determines whether the hook should run for a given configuration form.
+Developers should use `instanceof` checks in order to decide whether the hook should run or not.
+If `appliesTo()` returns `false`, `isValid()` and `onSuccess()` won't get called for this hook.
+
+`isValid()` is called after the configuration form has been validated successfully.
+An exception thrown here indicates form errors and prevents the config from being stored.
+The exception's error message is shown in the frontend automatically.
+If there are multiple hooks indicating errors, every error will be displayed.
+
+`onSuccess()` is called after the configuration has been stored successfully.
+Form handling can't be interrupted here. Any exception will be caught, logged and notified.
+
+Hook example:
+
+```php
+namespace Icinga\Module\Acme\ProvidedHook;
+
+use Icinga\Application\Hook\ConfigFormEventsHook;
+use Icinga\Forms\ConfigForm;
+use Icinga\Forms\Security\RoleForm;
+
+class ConfigFormEvents extends ConfigFormEventsHook
+{
+ public function appliesTo(ConfigForm $form)
+ {
+ return $form instanceof RoleForm;
+ }
+
+ public function onSuccess(ConfigForm $form)
+ {
+ $this->updateMyModuleConfig();
+ }
+
+ protected function updateMyModuleConfig()
+ {
+ // ...
+ }
+}
+```
diff --git a/doc/70-Troubleshooting.md b/doc/70-Troubleshooting.md
new file mode 100644
index 0000000..d71e08a
--- /dev/null
+++ b/doc/70-Troubleshooting.md
@@ -0,0 +1,17 @@
+# Troubleshooting <a id="troubleshooting"></a>
+
+## PageSpeed Module Incompatibility <a id="pagespeed-incompatibility"></a>
+
+It seems that Web 2 is not compatible with the PageSpeed module. Please disable the PageSpeed module using one of the
+following methods.
+
+**Apache**:
+```
+ModPagespeedDisallow "*/icingaweb2/*"
+```
+
+**Nginx**:
+```
+pagespeed Disallow "*/icingaweb2/*";
+```
+
diff --git a/doc/80-Upgrading.md b/doc/80-Upgrading.md
new file mode 100644
index 0000000..ee3602d
--- /dev/null
+++ b/doc/80-Upgrading.md
@@ -0,0 +1,424 @@
+# Upgrading Icinga Web 2 <a id="upgrading"></a>
+
+Specific version upgrades are described below. Please note that upgrades are incremental. An upgrade from
+v2.6 to v2.8 requires to follow the instructions for v2.7 too.
+
+## Upgrading to Icinga Web 2.11.x
+
+**General**
+
+* Support for Internet Explorer 11 has been removed.
+* The Vagrant file and all its assets have been removed.
+
+**Database Schema**
+
+* Please apply the `v2.11.0.sql` upgrade script depending on your database vendor.
+ As of version `2.11.4`, upgrade scripts can be found at `/usr/share/icingaweb2/schema/*-upgrades/`.
+ Older versions install these files to `/usr/share/doc/icingaweb2/schema/*-upgrades/` for RPM-based systems
+ and `/usr/share/icingaweb2/etc/schema/*-upgrades/` for Debian or Ubuntu.
+
+**Breaking changes**
+
+* The `user:local_name` macro in restrictions has been removed. Use `user.local_name` now.
+* User preferences stored in INI files are not loaded anymore. Migrate yours with
+ `icingacli migrate preferences` before the upgrade, if you haven't already.
+
+**Framework changes affecting third-party code**
+
+* When loading library CSS assets, CSS files and LESS files are handled differently now. Only the latter
+ is parsed as LESS.
+* jQuery is not bundled anymore as it's now part of the library icinga-php-thirdparty v0.11.0. It's shipped there
+ in version 3.6.0. (Previously bundled was jQuery 3.4.1)
+* All the following classes have been removed:
+ * `Icinga\User\Preferences\Store\IniStore`: Preferences in INI files are not supported anymore.
+ * `Icinga\User\Preferences\Store\DbStore`: Its methods have been added to the `PreferencesStore` class.
+ * `Icinga\Util\String`: Use `Icinga\Util\StringHelper` instead.
+ * `Icinga\Util\Translator`: Use `\ipl\I18n\StaticTranslator::$instance` or `\ipl\I18n\Translation` instead.
+ * `Icinga\Module\Migrate\Clicommands\DashboardCommand`: Deleted without substitution.
+ * `Icinga\Web\Hook\TicketHook`: Use `Icinga\Application\Hook\TicketHook` instead.
+ * `Icinga\Web\Hook\GrapherHook`: Use `Icinga\Application\Hook\GrapherHook` instead.
+ * `Icinga\Module\Monitoring\Environment`: Not in use.
+ * `Icinga\Module\Monitoring\Backend`: Use `Icinga\Module\Monitoring\Backend\MonitoringBackend` instead.
+* All the following methods have been removed:
+ * `loader.js.addUrlFlag()`: Use `Icinga.Utils.addUrlFlag()` instead.
+ * `Url::setBaseUrl()`: Please create a new url from scratch instead.
+ * `Url::getBaseUrl()`: Use either `Url::getBasePath()` or `Url::getAbsoluteUrl()` now.
+ * `ApplicationBootstrap::setupZendAutoloader()`: Since it does nothing, all usages removed.
+ * `ApplicationBootstrap::listLocales()`: Use `\ipl\I18n\GettextTranslator::listLocales()` instead.
+ * `Module::registerHook()`: Use `provideHook()` instead.
+ * `Web::getMenu()`: Instantiate the menu class `new Menu()` directly instead.
+ * `AesCrypt::encryptToBase64()`: Use `AesCrypt::encrypt()` instead as it also returns a base64 encoded string.
+ * `AesCrypt::decryptFromBase64()`: Use `AesCrypt::decrypt()` instead as it also returns a base64 decoded string.
+ * `InlinePie::disableNoScript()`: Empty method.
+ * `SimpleQuery::paginate()`: Use `Icinga\Web\Controller::setupPaginationControl()` and/or `Icinga\Web\Widget\Paginator` instead.
+ * `LdapConnection::connect()`: The connection is established lazily since .. a long time.
+ * `MonitoredObject::matches()`: Use `$filter->matches($object)` instead.
+ * `MonitoredObject::fromParams()`: Deleted without substitution.
+ * `DataView::fromRequest()`: Use `$backend->select()->from($viewName)` instead.
+ * `DataView::sort()`: Use `DataView::order()` instead.
+ * `MonitoringBackend::createBackend()`: Use `MonitoringBackend::instance()` instead.
+ * `DbConnection::getConnection()`: Use `Connection::getDbAdapter()` instead.
+ * `DbQuery::renderFilter()`: Use `DbConnection::renderFilter()` instead.
+ * `DbQuery::whereToSql()`: Use `DbConnection::renderFilter()` instead.
+
+## Upgrading to Icinga Web 2 2.10.x
+
+**General**
+
+* The theme "solarized-dark" has been removed due to the introduction of the new default dark mode.
+
+**Deprecations**
+
+* Builtin support for PDF exports using the `dompdf` library will be dropped with version 2.12.
+ It is highly recommended to use [Icinga PDF Export](https://github.com/Icinga/icingaweb2-module-pdfexport)
+ instead.
+
+**Discontinued package updates**
+
+* We will stop offering major updates for Debian 9 (Stretch) starting with version 2.11.
+ However, versions 2.9 and 2.10 will continue to receive minor updates on this platform.
+
+[icinga.com](https://icinga.com/subscription/support-details/) provides an overview about
+currently supported distributions.
+
+**Framework changes affecting third-party code**
+
+* Asset support for modules (#3961) introduced with v2.8 has now been removed.
+* `expandable-toggle`-support has been removed. Use `class="collapsible" data-visible-height=0`
+ to achieve the same effect. (Available since v2.7.0)
+* The `.var()` LESS mixin and the LESS function `extract-variable-default` have been removed (introduced with v2.9)
+
+## Upgrading to Icinga Web 2 2.9.1
+
+**Database Schema**
+
+* Please apply the `v2.9.1.sql` upgrade script depending on your database vendor.
+ In package installations this file can be found in `/usr/share/doc/icingaweb2/schema/*-upgrades/`
+ (Debian/Ubuntu: `/usr/share/icingaweb2/etc/schema/*-upgrades/`).
+
+## Upgrading to Icinga Web 2 2.9.x
+
+**Installation**
+
+* Icinga Web 2 now requires the [Icinga PHP Library (ipl)](https://github.com/Icinga/icinga-php-library) (>= 0.6)
+ and [Icinga PHP Thirdparty](https://github.com/Icinga/icinga-php-thirdparty) (>= 0.10). Please make sure to
+ install both when upgrading. We provide packages for them and if you've installed Icinga Web 2 already by
+ package they should be installed automatically during the upgrade.
+* [Icinga Business Process Modelling](https://github.com/Icinga/icingaweb2-module-businessprocess/releases/tag/v2.3.1)
+ has been updated to v2.3.1. If you're using this module, this version is required when upgrading.
+
+**General**
+
+* For database connections to the IDO running on MySQL, a default charset (`latin1`) is now applied.
+ If you had previously problems with special characters and umlauts and you've set this charset
+ already manually, no change is required. However, if your IDO resource configuration has another
+ charset configured than this, it is highly recommended to clear this setting. Otherwise the default
+ won't apply and characters may still be shown incorrectly in the UI.
+
+**Database Schema**
+
+* Icinga Web 2 now permits its users to stay logged in. This requires a new database table.
+ * Please apply the `v2.9.0.sql` upgrade script depending on your database vendor.
+ In package installations this file can be found in `/usr/share/doc/icingaweb2/schema/*-upgrades/`
+ (Debian/Ubuntu: `/usr/share/icingaweb2/etc/schema/*-upgrades/`).
+
+**Breaking changes**
+
+* Password changes are not allowed by default anymore
+ * The fake refusal `no-user/password-change` has now been changed to a grant `user/password-change`.
+ Any user that had `no-user/password-change` previously still cannot change passwords. Though any
+ user that didn't have this *permission*, needs to be granted `user/password-change` now in order
+ to change passwords.
+
+**Deprecations**
+
+* Support for EOL PHP versions (5.6, 7.0, 7.1 and 7.2) will be removed with version 2.11
+* Support for Internet Explorer will be completely removed with version 2.11
+ * New features after v2.9 will already not (necessarily) be available in Internet Explorer
+* `user.local_name` replaces the `user:local_name` macro in restrictions, and the latter will be removed with
+ version 2.11
+* The configuration backend type `INI` is not configurable anymore. **A database is now mandatory.**
+ * Existing configurations using this configuration backend type will stop working with the
+ release of v2.11.
+ * To migrate your local user preferences to database, enable the `migrate` module and use the command
+ `icingacli migrate preferences`. If you already setup the configuration database, it will work right
+ away. If not, pass it the resource you'd like to use as configuration database with `--resource=`.
+ * Note that this only applies to user preferences. Other configurations are still stored
+ in `.ini` files. (#3770)
+* The Vagrant file and all its assets will be removed with version 2.11
+
+**Framework changes affecting third-party code**
+
+* The `jquery-migrate` compatibility layer for Javascript code working with jQuery 2.x has been removed.
+ It has been introduced with v2.7 when we upgraded jQuery to v3.4.1 in order to allow module developers
+ a seamless upgrade chance. If a module still has UI glitches after an upgrade to v2.9, please contact
+ the module developer.
+* The method `getHtmlForEvent` of the `EventDetailsExtensionHook` previously received the host or service
+ object of an event. Now the actual event object is passed to it instead.
+* Asset support for modules (#3961) introduced with v2.8 has now been deprecated in favor of library
+ support (#4272) and will be removed with v2.10. We don't expect broad usage of this feature since
+ it's been introduced with the latest major version, so it's already being removed with the next one.
+
+## Upgrading to Icinga Web 2 2.8.x
+
+**Changes in packaging and dependencies**
+
+Valid for distributions:
+
+* RHEL / CentOS 7
+ * Upgrade to PHP 7.3 via RedHat SCL
+
+After upgrading to version 2.8.0 you'll get the new `rh-php73` dependency installed. This is a drop-in replacement
+for the previous `rh-php71` dependency and only requires the setup of a new fpm service and possibly some copying
+of customized configurations.
+
+**php.ini or php-fpm settings** you have tuned in the past need to be copied over to the new path:
+
+From `/etc/opt/rh/rh-php71/` to `/etc/opt/rh/rh-php73/`.
+
+Don't forget to also install any additional **php-modules** for PHP 7.3 you've had previously installed
+for e.g. Icinga Web 2 modules.
+
+There's also a new **service** included which replaces the previous one for php-fpm:
+
+Stop the old service: `systemctl stop rh-php71-php-fpm.service`
+Start the new service: `systemctl start rh-php73-php-fpm.service`
+
+You can now safely remove the previous dependency if you like:
+
+`yum remove rh-php71*`
+
+**Discontinued package updates**
+
+Icinga Web 2 v2.8+ is not supported on these platforms:
+
+* RHEL / CentOS 6
+* Debian 8 Jessie
+* Ubuntu 16.04 LTS (Xenial Xerus)
+
+Please consider an upgrade of your central Icinga system to a newer distribution release.
+
+[icinga.com](https://icinga.com/subscription/support-details/) provides an overview about
+currently supported distributions.
+
+**Framework changes affecting third-party code**
+
+* Url parameter `view=compact` is now deprecated. `showCompact` should be used instead.
+ Details are in pull request [#4164](https://github.com/Icinga/icingaweb2/pull/4164).
+* Form elements of type checkbox now need to be checked prior submission if they're
+ required. Previously setting `required => true` didn't cause the browser to complain
+ if such a checkbox wasn't checked. Browsers now do complain if so.
+* The general layout now uses flexbox instead of fixed positioning. This applies to the
+ `#header`, `#sidebar`, `#main`, `#footer`, `#col1`, `#col2` and a column's controls.
+ `#sidebar` and `#main` are now additionally wrapped in a new container `#content-wrapper`.
+
+## Upgrading to Icinga Web 2 2.7.x <a id="upgrading-to-2.7.x"></a>
+
+**Breaking changes**
+
+* We've upgraded jQuery to version 3.4.1. If you're a module developer, please add `?_dev` to your address bar to check
+ for log messages emitted by jquery-migrate. (https://github.com/jquery/jquery-migrate) Your javascript code will still
+ work, though jquery-migrate will notify you if you're utilizing deprecated/removed functions. jquery-migrate will be
+ removed with Icinga Web v2.8 and code not adjusted accordingly will stop working.
+* If you're using a language other than english and you've adjusted or disabled module dashboards, you'll need to
+ update all of your `dashboard.ini` files. A CLI command exists to assist you with this task. Enable the `migrate`
+ module and run the following on the host where these files exist: `icingacli migrate dashboard sections --verbose`
+
+## Upgrading to Icinga Web 2 2.6.x <a id="upgrading-to-2.6.x"></a>
+
+* Icinga Web 2 version 2.6.x does not introduce any backward incompatible change.
+
+## Upgrading to Icinga Web 2 2.5.x <a id="upgrading-to-2.5.x"></a>
+
+> **Attention**
+>
+> Icinga Web 2 v2.5 requires **at least PHP 5.6**.
+
+**Breaking changes**
+
+* Hash marks (`#`) in INI files are no longer recognized as comments by
+ [parse_ini_file](https://secure.php.net/manual/en/function.parse-ini-file.php) since PHP 7.0.
+* Existing sessions of logged-in users do no longer work as expected due to a change in the `User` data structure.
+ Everyone who was logged in before the upgrade has to log out once.
+
+**Changes in packaging and dependencies**
+
+Valid for distributions:
+
+* RHEL / CentOS 6 + 7
+ * Upgrading to PHP 7.0 / 7.1 via RedHat SCL (new dependency)
+ * See [Upgrading to FPM](02-Installation.md#upgrading-to-fpm) for manual steps that
+ are required
+* SUSE SLE 12
+ * Upgrading PHP to >= 5.6.0 via the alternative packages.
+ You might have to confirm the replacement of PHP < 5.6 - but that
+ should work with any other PHP app as well.
+ * Make sure to enable the new Apache module `a2enmod php7` and restart `apache2`
+
+**Discontinued package updates**
+
+Icinga Web 2 v2.5+ is not supported on these platforms:
+
+* Debian 7 wheezy
+* Ubuntu 14.04 LTS (trusty)
+* SUSE SLE 11 (all service packs)
+
+Please consider an upgrade of your central Icinga system to a newer distribution release.
+
+[packages.icinga.com](https://packages.icinga.com) provides an overview about currently supported distributions.
+
+**Database schema**
+
+Icinga Web 2 v2.5.0 requires a schema update for the database. The database schema has been adjusted to support
+usernames up to 254 characters. This is necessary to support the new domain-aware authentication feature.
+
+Continue here for [MySQL](80-Upgrading.md#upgrading-mysql-db) and [PostgreSQL](80-Upgrading.md#upgrading-pgsql-db).
+
+## Upgrading to Icinga Web 2 2.4.x <a id="upgrading-to-2.4.x"></a>
+
+* Icinga Web 2 version 2.4.x does not introduce any backward incompatible change.
+
+## Upgrading to Icinga Web 2 2.3.x <a id="upgrading-to-2.3.x"></a>
+
+* Icinga Web 2 version 2.3.x does not introduce any backward incompatible change.
+
+## Upgrading to Icinga Web 2 2.2.0 <a id="upgrading-to-2.2.0"></a>
+
+* The menu entry `Authorization` beneath `Config` has been renamed to `Authentication`. The role, user backend and user
+ group backend configuration which was previously found beneath `Authentication` has been moved to `Application`.
+
+## Upgrading to Icinga Web 2 2.1.x <a id="upgrading-to-2.1.x"></a>
+
+* Since Icinga Web 2 version 2.1.3 LDAP user group backends respect the configuration option `group_filter`.
+ Users who changed the configuration manually and used the option `filter` instead
+ have to change it back to `group_filter`.
+
+## Upgrading to Icinga Web 2 2.0.0 <a id="upgrading-to-2.0.0"></a>
+
+* Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on `php-ZendFramework` which is available through
+ the [EPEL repository](https://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library
+ through the package `icingaweb2-vendor-zend`. After upgrading, please make sure to remove the package
+ `icingaweb2-vendor-zend`.
+
+* Icinga Web 2 version 2.0.0 requires permissions for accessing modules. Those permissions are automatically generated
+ for each installed module in the format `module/<moduleName>`. Administrators have to grant the module permissions to
+ users and/or user groups in the roles configuration for permitting access to specific modules.
+ In addition, restrictions provided by modules are now configurable for each installed module too. Before,
+ a module had to be enabled before having the possibility to configure restrictions.
+
+* The **instances.ini** configuration file provided by the monitoring module
+ has been renamed to **commandtransports.ini**. The content and location of
+ the file remains unchanged.
+
+* The location of a user's preferences has been changed from
+ **&lt;config-dir&gt;/preferences/&lt;username&gt;.ini** to
+ **&lt;config-dir&gt;/preferences/&lt;username&gt;/config.ini**.
+ The content of the file remains unchanged.
+
+## Upgrading to Icinga Web 2 Release Candidate 1 <a id="upgrading-to-rc1"></a>
+
+The first release candidate of Icinga Web 2 introduces the following non-backward compatible changes:
+
+* The database schema has been adjusted and the tables `icingaweb_group` and
+ `icingaweb_group_membership` were altered to ensure referential integrity.
+ Please use the upgrade script located in **etc/schema/** to update your
+ database schema
+
+* Users who are using PostgreSQL < v9.1 are required to upgrade their
+ environment to v9.1+ as this is the new minimum required version
+ for utilizing PostgreSQL as database backend
+
+* The restrictions `monitoring/hosts/filter` and `monitoring/services/filter`
+ provided by the monitoring module were merged together. The new
+ restriction is called `monitoring/filter/objects` and supports only a
+ predefined subset of filter columns. Please see the module's security
+ related documentation for more details.
+
+## Upgrading to Icinga Web 2 Beta 3 <a id="upgrading-to-beta3"></a>
+
+Because Icinga Web 2 Beta 3 does not introduce any backward incompatible change you don't have to change your
+configuration files after upgrading to Icinga Web 2 Beta 3.
+
+## Upgrading to Icinga Web 2 Beta 2 <a id="upgrading-to-beta2"></a>
+
+Icinga Web 2 Beta 2 introduces access control based on roles for secured actions. If you've already set up Icinga Web 2,
+you are required to create the file **roles.ini** beneath Icinga Web 2's configuration directory with the following
+content:
+```
+[administrators]
+users = "your_user_name, another_user_name"
+permissions = "*"
+```
+
+After please log out from Icinga Web 2 and log in again for having all permissions granted.
+
+If you delegated authentication to your web server using the `autologin` backend, you have to switch to the `external`
+authentication backend to be able to log in again. The new name better reflects
+what's going on. A similar change
+affects environments that opted for not storing preferences, your new backend is `none`.
+
+## Upgrading the MySQL Database <a id="upgrading-mysql-db"></a>
+
+If you installed Icinga Web 2 from package, please check the upgrade scripts located in
+**/usr/share/doc/icingaweb2/schema/mysql-upgrades** (Debian/Ubuntu: **/usr/share/icingaweb2/etc/schema/mysql-upgrades**)
+to update your database schema.
+In case you installed Icinga Web 2 from source, please find the upgrade scripts in **etc/schema/mysql-upgrades**.
+
+> **Note**
+>
+> If there isn't an upgrade file for your current version available, there's nothing to do.
+
+Apply all database schema upgrade files incrementally.
+
+```
+# mysql -u root -p icingaweb2 < /usr/share/doc/icingaweb2/schema/mysql-upgrades/<version>.sql
+```
+
+**Example:** You are upgrading Icinga Web 2 from version `2.4.0` to `2.5.0`. Look into
+the `upgrade` directory:
+
+```
+$ ls /usr/share/doc/icingaweb2/schema/mysql-upgrades/
+2.0.0beta3-2.0.0rc1.sql 2.5.0.sql
+```
+
+The upgrade file `2.5.0.sql` must be applied for the v2.5.0 release. If there are multiple
+upgrade files involved, apply them incrementally.
+
+```
+# mysql -u root -p icinga < /usr/share/doc/icingaweb2/schema/mysql-upgrades/2.5.0.sql
+```
+
+## Upgrading the PostgreSQL Database <a id="upgrading-pgsql-db"></a>
+
+If you installed Icinga Web 2 from package, please check the upgrade scripts located in
+**/usr/share/doc/icingaweb2/schema/pgsql-upgrades** (Debian/Ubuntu: **/usr/share/icingaweb2/etc/schema/pgsql-upgrades**)
+to update your database schema.
+In case you installed Icinga Web 2 from source, please find the upgrade scripts in **etc/schema/pgsql-upgrades**.
+
+> **Note**
+>
+> If there isn't an upgrade file for your current version available, there's nothing to do.
+
+Apply all database schema upgrade files incrementally.
+
+```
+# export PGPASSWORD=icingaweb2
+# psql -U icingaweb2 -d icingaweb2 < /usr/share/doc/icingaweb2/schema/pgsql-upgrades/<version>.sql
+```
+
+**Example:** You are upgrading Icinga Web 2 from version `2.4.0` to `2.5.0`. Look into
+the `upgrade` directory:
+
+```
+$ ls /usr/share/doc/icingaweb2/schema/pgsql-upgrades/
+2.0.0beta3-2.0.0rc1.sql 2.5.0.sql
+```
+
+The upgrade file `2.5.0.sql` must be applied for the v2.5.0 release. If there are multiple
+upgrade files involved, apply them incrementally.
+
+```
+# export PGPASSWORD=icingaweb2
+# psql -U icingaweb2 -d icingaweb2 < /usr/share/doc/icingaweb2/schema/pgsql-upgrades/2.5.0.sql
+```
diff --git a/doc/90-SELinux.md b/doc/90-SELinux.md
new file mode 100644
index 0000000..d19ca82
--- /dev/null
+++ b/doc/90-SELinux.md
@@ -0,0 +1,76 @@
+# SELinux <a id="selinux"></a>
+
+## Introduction <a id="selinux-introduction"></a>
+
+SELinux is a mandatory access control (MAC) system on Linux which adds a fine granular permission system for access
+to all resources on the system such as files, devices, networks and inter-process communication.
+
+The most important questions are answered briefly in the [FAQ of the SELinux Project](https://selinuxproject.org/page/FAQ).
+For more details on SELinux and how to actually use and administrate it on your systems have a look at
+[Red Hat Enterprise Linux 7 - SELinux User's and Administrator's Guide](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/index.html).
+For a simplified (and funny) introduction download the [SELinux Coloring Book](https://github.com/mairin/selinux-coloring-book).
+
+
+## Policy <a id="selinux-policy"></a>
+
+Icinga Web 2 is providing its own SELinux policy for RPM-based systems running the targeted policy
+which confines Icinga Web 2 with support for all its modules.
+
+The policy for Icinga Web 2 will also require the policy for Icinga 2 which provides access to its interfaces.
+It covers only the scenario running Icinga Web 2 in Apache HTTP Server with mod_php.
+
+Use your distribution's package manager to install the `icingaweb2-selinux` package.
+
+## General <a id="selinux-policy-general"></a>
+
+When the SELinux policy package for Icinga Web 2 is installed, it creates its own type of apache content and labels its
+configuration `icingaweb2_config_t` to allow confining access to it.
+
+## Types <a id="selinux-policy-types"></a>
+
+The configuration is labeled `icingaweb2_config_t` and other services can request access to it by using the interfaces
+`icingaweb2_read_config` and `icingaweb2_manage_config`.
+Files requiring read access are labeled `icingaweb2_content_t`. Files requiring write access are labeled
+`icingaweb2_rw_content_t`.
+
+## Booleans <a id="selinux-policy-booleans"></a>
+
+SELinux is based on the least level of access required for a service to run. Using booleans you can grant more access in
+a defined way. The Icinga Web 2 policy package provides the following booleans.
+
+**httpd_can_manage_icingaweb2_config**
+
+Having this boolean enabled allows httpd to write to the configuration labeled `icingaweb2_config_t`. This is enabled by
+default. If not needed, you can disable it for more security. But this will disable all web based configuration of
+Icinga Web 2.
+
+### Optional Booleans <a id="selinux-optional-booleans"></a>
+
+The Icinga Web 2 policy package does not enable booleans not required by default. In order to allow these things,
+you'll need to enable them manually. (i.e. with the tool `setsebool`)
+
+**Ldap**
+If you want to allow httpd to connect to the ldap port, you must turn on the `httpd_can_connect_ldap` boolean.
+Disabled by default.
+
+## Bugreports <a id="selinux-bugreports"></a>
+
+If you experience any problems while running SELinux in enforcing mode try to reproduce it in permissive mode. If the
+problem persists, it is not related to SELinux because in permissive mode SELinux will not deny anything.
+
+When filing a bug report please add the following information additionally to the
+[common ones](https://icinga.com/icinga/faq/):
+* Output of `semodule -l | grep -e icinga2 -e icingaweb2 -e nagios -e apache`
+* Output of `semanage boolean -l | grep icinga`
+* Output of `ps -eZ | grep httpd`
+* Output of `audit2allow -li /var/log/audit/audit.log`
+
+If access to a file is blocked and you can tell which one, please provided the output of `ls -lZ /path/to/file` and the
+directory above.
+
+If asked for full audit.log, add `-w /etc/shadow -p w` to `/etc/audit/rules.d/audit.rules` and restart the audit daemon.
+Reproduce the problem and add `/var/log/audit/audit.log` to the bug report. The added audit rule includes
+the path of files where access was denied.
+
+If asked to provide full audit log with dontaudit rules disabled, execute `semodule -DB` before reproducing the problem.
+After that enable the rules again to prevent auditd spamming your logfile by executing `semodule -B`.
diff --git a/doc/accessibility/ifont-mute.html b/doc/accessibility/ifont-mute.html
new file mode 100644
index 0000000..f8252e3
--- /dev/null
+++ b/doc/accessibility/ifont-mute.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: Muted Icon Fonts</title>
+ <meta name="description" content="Accessible icon fonts">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <style type="text/css">
+ .icon-star:before {
+ content: "★";
+ }
+ </style>
+</head>
+<body>
+<a href="#">
+ <i aria-hidden="true" class="icon-star"></i>
+ Visit top rated article
+</a>
+</body>
+</html>
diff --git a/doc/accessibility/ifont.html b/doc/accessibility/ifont.html
new file mode 100644
index 0000000..32f1221
--- /dev/null
+++ b/doc/accessibility/ifont.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: Icon Fonts</title>
+ <meta name="description" content="Accessible icon fonts">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <style type="text/css">
+ .icon-star:before {
+ content: "★";
+ }
+ </style>
+</head>
+<body>
+ <i role="img" class="icon-star" aria-label="Top rated article" title="Top rated article"></i>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/accessibility/link-labels.html b/doc/accessibility/link-labels.html
new file mode 100644
index 0000000..439adb8
--- /dev/null
+++ b/doc/accessibility/link-labels.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: Link Labels</title>
+ <meta name="description" content="Accessible links">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+<body>
+ <a href="/monitoring/host/show?host=localhost"
+ title="Show detailed information about the host localhost"
+ aria-label="Show detailed information about the host localhost">localhost</a>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/accessibility/required-form-elements.html b/doc/accessibility/required-form-elements.html
new file mode 100644
index 0000000..86fc937
--- /dev/null
+++ b/doc/accessibility/required-form-elements.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: Required form elements</title>
+ <meta name="description" content="Required form elements">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+<body>
+ <form>
+ <label>
+ Enter some text:&nbsp;
+ <input type="text" name="some_text" value="" aria-required="true" required>
+ </label>
+ <input type="submit" name="btn_submit" value="Submit">
+ </form>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/accessibility/skip-content.html b/doc/accessibility/skip-content.html
new file mode 100644
index 0000000..3c1b2b4
--- /dev/null
+++ b/doc/accessibility/skip-content.html
@@ -0,0 +1,179 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: Skip Links</title>
+ <meta name="description" content="Accessible skip links">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <style type="text/css">
+ .sr-only {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ }
+ .clearfix {
+ *zoom: 1;
+ }
+ .clearfix:before,
+ .clearfix:after {
+ display: table;
+ content: "";
+ line-height: 0;
+ }
+ .clearfix:after {
+ clear: both;
+ }
+ body {
+ margin: 0;
+ padding: 0;
+ }
+ .head {
+ background-color: #049baf;
+ padding: 5px;
+ }
+ .nav {
+ width: 200px;
+ float: left;
+ margin: 0 20px 20px 0;
+ padding: 0 20px 20px 0;
+ }
+ .container {
+ margin: 10px 0 0 0;
+ }
+ .content {
+ overflow: auto;
+ }
+ .skip-links {
+ position: absolute;
+ opacity: 1;
+ }
+ .skip-links-inline {
+ margin-top: -3.5em;
+ }
+ .skip-links ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ }
+ .skip-links ul li {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ }
+ .skip-links ul li a {
+ position: absolute;
+ display: block;
+ left: -999em;
+ width: 200px;
+ padding: 0.6em;
+ background-color: white;
+ }
+ .skip-links ul li a:focus {
+ left: 0;
+ }
+ </style>
+</head>
+<body>
+ <div class="head">
+ <div class="skip-links">
+ <nav>
+ <h1 class="sr-only">Skip Links</h1>
+ <ul>
+ <li><a tabindex="0" href="#content">Skip to Content</a></li>
+ <li><a tabindex="0" href="#searchField">Skip to Search</a></li>
+ <li><a tabindex="0" href="#navigation">Skip to Navigation</a></li>
+ </ul>
+ </nav>
+ </div>
+ <div id="logo">
+ <a href="skip-content.html">
+ <img width="92" height="32" title="" alt="" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAAAgCAYAAACfDx/iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAABcSAAAXEgFnn9JSAAAAB3RJTUUH3gkEDx0B5aU5bgAACCxJREFUaN7tmn2slmUdx7+/5+UcPFhAeQR0SSnOEodFWSPZdGxGZsWhiZFZq+myl5WZDWchIW4VODa3cDPNNWfFS9aquWmWyaJss1qDcE1txstKFPCEHA7ynPM8n/7gdx1/XN73cx7o0B9xru3e/dzX+/W9vtfv7Xqk40zAdOAhYAjYClzk+RpPY5iAKlAHnuS1ado4Qu1T5TjaVCVNlnRRyGv5e+E4pGMPOJKGJA1k35L0z3FIx16kmL9XZOJk07gMHz0l8GRmEVSZ2WvyA6BmZgD3SPqXpG1m9iBQM7PhcVjbM1aAAV3AJ4CzIpPbtFsInBO+a+MMHz3VnK4AWyW9RVIX0COpATRz5rvcr0s6VdJOoCqpaWbDRSclKFrzehSdnJNGabromCjpXElNz59tZs2c6WYmM2tJ6pU0aGZDZjayKUUget40STNi3kl7EtLCgZuBg8A6YFkmJq4A7gDuAj4GLE2b4SKpnXL9NHAAeAW4H6j5qRiX5cGx6QZuB3qBfwRLpAU0gQawIGunCD7Q5e9mZs3Mcn1x0gNdcSAqDtwZDlDD3feWP8P+BniXb1AVqGT9ngXM9XrD/gDMcU/15FSaQf62gIabfAI+GxSrZZ5mSg+bWW8A+UJJl3udbZK2SLpJ0hqv8pjnlZqhUcb/PypWK2B8zS2RJyS9s03bpgN7naTTJPVIetzMNsW4i5k1gV5Jp5nZ33IbHzAzo+z0FWyERUsnbky7TYonqmSzO5rHaP3HuRQZErWSTahJOrPjXTNbFQaueB+tZOlI2ivpYKrOkdXFDXmzpI9IOsdDBX+X9EMz2wPUzWwokcHNz14vGwE/OWNer0/SeyRNkPSSpMfM7Amg4ic53/Q0jzdKWixpppNuu6SNZrYb6DKzRhvQU9+zzWxrJyQYYbgrzb/SPg0lOZ4rzUwvzAB2ugV0ZWS/v1cFnZCn5cEaSlbPg172k9iP/54FPO96J6YG8Dvg1NBPJIiALwY9k6fVYT2lJyeEq3/teqo6qq4KE1rbAeDDcdIFHmwFWBwsnPXZItd72aGC/g/6+zuhz4luXvYnWgMTfJw5AdzhAsABdjjo1YwYa9rMY9DfG9t54MBs4OXQx9udvB1bL2c4SIcKGJjY/cGySQTAPxra/SiUf8HzXvH3duAmYGkwRdO4lzjLJzkAgwHwHn8PBIsI4F7gBuBh/z7s780Zsfqy8n3A14FbgGc9L5m2i3xNloVFzM3omJ7MT2C72Er6fX4Ju5vA16Lz0wbwqyLgnt8D/CnkbwziLLH/8VD+K8+b4gQYAdzzbwygtYDzMlCvCeVN4OIw3qNhnN8WzOOhUP7nPG7kprS5c5efqJmFsZQCVzxNdrukr7rCmyepW9IOSY9IqqcFHaMZV5V0uqRZ/r1f0uecqYdCvas9EilJT4fYe1FaIqkhqUvSMjN7GqhLGgZOMbMfAEskXeH1F0j6vaRJrlzlMf1rgQmSDgf5fI2kfa5En8nmYa5IP+mxpWS5Vf33bZI+fpTFknmKlchYYE2bk3C9K9daEcvbMLwKvDWyJsjh/ISdCVwavNbXlzB8R5CdF2R9pfDDl8OY93q7qeHEPucnyArmMR14v5/MWJ5OUH8QSRtCn7t9DSP4VHJmm1ky2T4l6VtAdxAdUUv/TNI6SVeV2a8dplZijAfHYtkeSZsljRZjj+MfTv6F92XpPYo53PJnZB4Bk+fN7BEzG5REKAdY5CfFwsnc5thOlTTfyww44mkC50taJmk6sE/SfZIOmdnetJsOaiTWfEmLJH3IZdszbY58budbG8CiudXwumMRB+i4j2Cvf1fSG7xtXdIvzexul+84Jtf5HOuSVrk9v0HSBd7dCjN7INnxNVcwT2Ue5EJJHygKpQYG/kLSXyQ95TJz7Nzfo1nOiQK2zfgt4FZJn3HWVxyXPuDHkvolVYEZki4LOuAe34y7JN3i7c4G5prZHwDVJK0tUGqStNLvKaPHGNk3aGZzkrg5BrHCaCGG4AFW0mL/S7F1POnZ5EFmc5wkqd893qud2UkkbfL1NdzASBu11sMkVpN0acmA57rmHQgXEzGmQbBSjuUWxzpheHK39epfMP6XEVQzs/XADpf3v3E9MiEwviVpZSBQTdKbMkmB13sHcKGZbalIeqFk3AEfhDJQXMFSdtvThuFRCU52Nli24Cbwc9/Yy04Utg6MObl69OoFuST90cw2B+AkqeHs/lJ2YodcYadn2NvUvf/r03FZHRo0kw0q6admdiBn9xgt8t+Sdvr3TEkLHOBuVy74rdKHvf7qgtDwWKRBFx3mFkWfy+9uSV0O7LUOWpcTK/kGSx2riqRvSrrYpcUl/sxPIHu6EpiS7MnvZZ7khrG42Ciyw0P5feFSIwGcypZn87nZ8yeV2OHbgx0+MwaNgscY7fDvh7ars7FWhrY3ZDdWd4ZwQCuUWZEL717o1lBvcZzQNOB9wOn+Xc+jYccJeIylrA/lU4C9WRyjKOC0E3id95UAH8gA3xWCXeeVAP6V0Pf9WUBsVwfz2OOOUhV4IJTf4XOrlIQ4bg2k2pLszYqZ7TazR83sRf8eOkbZXJZeDL/3BhndL+ndOvL3uC4/ns0g1uru+MwzswNm1jKz/V4+MRtjl8tfuU6qHr1mqsEtl6TngjV0UNJcLy+bxz5J7zWzF1yR10Nf69ySa+UXG97/7UE/nXJC70oDy/qAG0vuUc1ZsD+ImJeBFQWBIgGTgW8DZ2dx8m8A8/KAWjaPJcDnC/IrIQj2kouApgekbovzCKJtOXB5UVy8YJ1vA+4Epv4HBqUeGKwVd68AAAAASUVORK5CYII=" />
+ </a>
+ </div>
+ </div>
+ <div class="container clearfix">
+ <div class="nav">
+ <h1 class="sr-only">Navigation</h1>
+
+ <div id="search">
+ <h2 class="sr-only">Search</h2>
+ <form>
+ <fieldset>
+ <label for="searchField">Search</label>
+ <input type="text" name="searchField" id="searchField" />
+ </fieldset>
+ </form>
+ </div>
+ <nav>
+ <h2 id="navigation" tabindex="0" class="sr-only">Site Links</h2>
+ <ul>
+ <li><a href="#">Link1</a></li>
+ <li><a href="#">Link2</a></li>
+ <li><a href="#">Link3</a></li>
+ </ul>
+ </nav>
+ </div>
+ <div class="content">
+ <h1 tabindex="0" id="content">Content</h1>
+ <div class="skip-links skip-links-inline">
+ <nav>
+ <ul>
+ <li><a tabindex="0" href="#content2">Skip content</a></li>
+ </ul>
+ </nav>
+ </div>
+ <p>
+ Organised prehistoric cultures began developing on Bulgarian lands
+ during the Neolithic period. Its ancient history saw the presence
+ of the Thracians and later the Greeks and Romans. The emergence of
+ a unified Bulgarian state dates back to the establishment of the
+ <a href="#">First Bulgarian Empire</a>
+ in 681 CE, which dominated most of the
+ Balkans and functioned as a cultural hub for Slavs during the
+ Middle Ages.
+ </p>
+ <p>
+ With the downfall of the Second Bulgarian Empire in 1396, its
+ <a href="#">territories came under Ottoman</a>
+ rule for nearly five centuries.
+ The Russo-Turkish War (1877–78) led to the formation of the Third
+ Bulgarian State. The following years saw several conflicts with its
+ neighbours, which prompted Bulgaria to align with Germany in both
+ world wars.
+ </p>
+ <p>
+ In 1946 it became a single-party socialist state as part of the
+ Soviet-led Eastern Bloc. In December 1989 the ruling Communist
+ Party allowed multi-party elections, which subsequently led to
+ Bulgaria's transition into a democracy and a market-based economy.
+ </p>
+
+ <h1 tabindex="0" id="content2">Content2</h1>
+ <p>
+ The development of Final Fantasy VIII began in 1997, during the
+ English localization process of Final Fantasy VII. It was produced
+ <a href="#">by Shinji Hashimoto</a>,
+ and directed by Yoshinori Kitase. The music
+ was scored by regular series composer Nobuo Uematsu, and in a
+ series first a vocal piece was written as the game's theme, "Eyes
+ on Me", performed by Faye Wong.
+ </p>
+ <p>
+ The game was positively received by
+ critics,
+ <a href="#">who praised the originality </a>
+ and scope of the game. It was
+ voted the 22nd-best game of all time in 2006 by readers of the
+ Japanese magazine Famitsu. The game was a commercial success;
+ thirteen weeks after its release,
+ </p>
+ </div>
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/accessibility/svg.html b/doc/accessibility/svg.html
new file mode 100644
index 0000000..8ee548f
--- /dev/null
+++ b/doc/accessibility/svg.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: SVGs</title>
+ <meta name="description" content="Accessible icon fonts">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+<body>
+<svg version="1.2" role="img" aria-labelledby="title desc" tabindex="0">
+ <title id="title">A Circle</title>
+ <desc id="desc">A red circle with a black border.</desc>
+ <circle cy="50" cx="50" r="50" stroke="black" stroke-width="1" fill="red" />
+</svg>
+</body>
+</html>
+
+
diff --git a/doc/accessibility/text-cue-for-required-form-control-labels.html b/doc/accessibility/text-cue-for-required-form-control-labels.html
new file mode 100644
index 0000000..1dd38eb
--- /dev/null
+++ b/doc/accessibility/text-cue-for-required-form-control-labels.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html class="no-js" lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>Accessibility: Text cue for required form control labels</title>
+ <meta name="description" content="Text cue for required form control labels">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <style type="text/css">
+ label.required span.required-indicator:after {
+ content: " *";
+ }
+ .sr-only {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+ }
+ </style>
+</head>
+<body>
+ <form>
+ <label class="required">
+ Enter some text
+ <span class="required-indicator" aria-hidden="true"></span>
+ <span class="sr-only"> (required)</span>
+ <input type="text" name="some_text" value="" aria-required="true" required>
+ </label>
+ <input type="submit" name="btn_submit" value="Submit">
+ </form>
+</body>
+</html> \ No newline at end of file
diff --git a/doc/phpdoc.xml b/doc/phpdoc.xml
new file mode 100644
index 0000000..0d9b207
--- /dev/null
+++ b/doc/phpdoc.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<phpdoc>
+ <title>Icinga Web 2</title>
+ <parser>
+ <target>./api</target>
+ <extensions>
+ <extension>php</extension>
+ </extensions>
+ </parser>
+ <visibility>public,private,protected</visibility>
+ <transformer>
+ <target>./api</target>
+ </transformer>
+ <logging>
+ <level>quiet</level>
+ </logging>
+ <transformations>
+ <template name="responsive" />
+ </transformations>
+ <files>
+ <directory>../library/Icinga</directory>
+ <directory>../library/application</directory>
+ <directory>../modules/*/library</directory>
+ <directory>../modules/*/application</directory>
+ </files>
+</phpdoc>
diff --git a/doc/res/GraphExample#1.png b/doc/res/GraphExample#1.png
new file mode 100644
index 0000000..fc3fe57
--- /dev/null
+++ b/doc/res/GraphExample#1.png
Binary files differ
diff --git a/doc/res/GraphExample#2.png b/doc/res/GraphExample#2.png
new file mode 100644
index 0000000..f81c4fb
--- /dev/null
+++ b/doc/res/GraphExample#2.png
Binary files differ
diff --git a/doc/res/GraphExample#3.png b/doc/res/GraphExample#3.png
new file mode 100644
index 0000000..0aed97f
--- /dev/null
+++ b/doc/res/GraphExample#3.png
Binary files differ
diff --git a/doc/res/GraphExample#4.png b/doc/res/GraphExample#4.png
new file mode 100644
index 0000000..1555ae3
--- /dev/null
+++ b/doc/res/GraphExample#4.png
Binary files differ
diff --git a/doc/res/GraphExample#5.png b/doc/res/GraphExample#5.png
new file mode 100644
index 0000000..ec659ae
--- /dev/null
+++ b/doc/res/GraphExample#5.png
Binary files differ
diff --git a/doc/res/GraphExample#6.png b/doc/res/GraphExample#6.png
new file mode 100644
index 0000000..1bd44f6
--- /dev/null
+++ b/doc/res/GraphExample#6.png
Binary files differ
diff --git a/doc/res/GraphExample#7.1.png b/doc/res/GraphExample#7.1.png
new file mode 100644
index 0000000..8f67832
--- /dev/null
+++ b/doc/res/GraphExample#7.1.png
Binary files differ
diff --git a/doc/res/GraphExample#7.png b/doc/res/GraphExample#7.png
new file mode 100644
index 0000000..41b762e
--- /dev/null
+++ b/doc/res/GraphExample#7.png
Binary files differ
diff --git a/doc/res/GraphExample#8.png b/doc/res/GraphExample#8.png
new file mode 100644
index 0000000..4c2928e
--- /dev/null
+++ b/doc/res/GraphExample#8.png
Binary files differ
diff --git a/doc/res/GraphExample#9.png b/doc/res/GraphExample#9.png
new file mode 100644
index 0000000..18b1898
--- /dev/null
+++ b/doc/res/GraphExample#9.png
Binary files differ
diff --git a/doc/res/gitlab-job-artifacts.png b/doc/res/gitlab-job-artifacts.png
new file mode 100644
index 0000000..ba7af9a
--- /dev/null
+++ b/doc/res/gitlab-job-artifacts.png
Binary files differ
diff --git a/doc/res/gitlab-rpm-package-pipeline-jobs.png b/doc/res/gitlab-rpm-package-pipeline-jobs.png
new file mode 100644
index 0000000..ed8d50a
--- /dev/null
+++ b/doc/res/gitlab-rpm-package-pipeline-jobs.png
Binary files differ
diff --git a/doc/res/monitoring-module-preview.png b/doc/res/monitoring-module-preview.png
new file mode 100644
index 0000000..d07596b
--- /dev/null
+++ b/doc/res/monitoring-module-preview.png
Binary files differ
diff --git a/etc/bash_completion.d/icingacli b/etc/bash_completion.d/icingacli
new file mode 100644
index 0000000..f9be7bc
--- /dev/null
+++ b/etc/bash_completion.d/icingacli
@@ -0,0 +1,10 @@
+_icingacli_completion()
+{
+ local cur opts
+ opts="${COMP_WORDS[*]}"
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($($opts --autocomplete --autoindex $COMP_CWORD < /dev/null))
+ return 0
+}
+
+complete -F _icingacli_completion icingacli
diff --git a/icingaweb2.ruleset.xml b/icingaweb2.ruleset.xml
new file mode 100644
index 0000000..537a6be
--- /dev/null
+++ b/icingaweb2.ruleset.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<!-- PHP Codesniffer ruleset configuration -->
+<ruleset name="icingaweb2">
+ <description>The default PSR-2 standard with specifically excluded non-critical sniffs</description>
+ <!-- Include the whole PSR-2 standard -->
+ <rule ref="PSR2"/>
+ <!-- Exclude patterns for PSR-2 Sniffs -->
+ <rule ref="PSR2.Methods.MethodDeclaration.Underscore">
+ <severity>0</severity>
+ </rule>
+ <rule ref="PSR2.Classes.PropertyDeclaration.Underscore">
+ <severity>0</severity>
+ </rule>
+ <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
+ <severity>0</severity>
+ </rule>
+ <rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
+ <exclude-pattern>library/Icinga/Application/Cli.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/Application/StaticWeb.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/Application/EmbeddedWeb.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/Application/functions.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/Application/LegacyWeb.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/Application/Web.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/File/Pdf.php</exclude-pattern>
+ <exclude-pattern>library/Icinga/Util/LessParser.php</exclude-pattern>
+ <exclude-pattern>modules/doc/library/Doc/Renderer/DocSectionRenderer.php</exclude-pattern>
+ <exclude-pattern>modules/monitoring/library/Monitoring/Plugin.php</exclude-pattern>
+ </rule>
+ <rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
+ <exclude-pattern>*/test/php/*</exclude-pattern>
+ </rule>
+ <rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
+ <exclude-pattern>*/test/php/*</exclude-pattern>
+ </rule>
+ <rule ref="PSR2.Namespaces.UseDeclaration.UseAfterNamespace">
+ <exclude-pattern>*/test/php/*</exclude-pattern>
+ <exclude-pattern>*/library/Icinga/Test/BaseTestCase.php</exclude-pattern>
+ </rule>
+ <rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
+ <exclude-pattern>*/application/views/helpers/*</exclude-pattern>
+ <exclude-pattern>*/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php</exclude-pattern>
+ </rule>
+ <rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
+ <exclude-pattern>*/application/views/helpers/*</exclude-pattern>
+ <exclude-pattern>*/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php</exclude-pattern>
+ </rule>
+ <rule ref="Generic.Files.LineLength.TooLong">
+ <exclude-pattern>*/modules/monitoring/library/Monitoring/Backend/Ido/Query/*</exclude-pattern>
+ <exclude-pattern>*/modules/monitoring/library/Monitoring/Backend/Livestatus/Query/*</exclude-pattern>
+ </rule>
+</ruleset>
diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php
new file mode 100644
index 0000000..b550495
--- /dev/null
+++ b/library/Icinga/Application/ApplicationBootstrap.php
@@ -0,0 +1,758 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use DirectoryIterator;
+use ErrorException;
+use Exception;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\StaticTranslator;
+use LogicException;
+use Icinga\Application\Modules\Manager as ModuleManager;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\IcingaException;
+
+/**
+ * This class bootstraps a thin Icinga application layer
+ *
+ * Usage example for CLI:
+ * <code>
+ * use Icinga\Application\Cli;
+
+ * Cli::start();
+ * </code>
+ *
+ * Usage example for Icinga Web application:
+ * <code>
+ * use Icinga\Application\Web;
+ * Web::start()->dispatch();
+ * </code>
+ *
+ * Usage example for Icinga-Web 1.x compatibility mode:
+ * <code>
+ * use Icinga\Application\LegacyWeb;
+ * LegacyWeb::start()->setIcingaWebBasedir(ICINGAWEB_BASEDIR)->dispatch();
+ * </code>
+ */
+abstract class ApplicationBootstrap
+{
+ /**
+ * Base directory
+ *
+ * Parent folder for at least application, bin, modules, library/vendor and public
+ *
+ * @var string
+ */
+ protected $baseDir;
+
+ /**
+ * Application directory
+ *
+ * @var string
+ */
+ protected $appDir;
+
+ /**
+ * Vendor library directory
+ *
+ * @var string
+ */
+ protected $vendorDir;
+
+ /**
+ * Icinga library directory
+ *
+ * @var string
+ */
+ protected $libDir;
+
+ /**
+ * Configuration directory
+ *
+ * @var string
+ */
+ protected $configDir;
+
+ /**
+ * Locale directory
+ *
+ * @var string
+ */
+ protected $localeDir;
+
+ /**
+ * Common storage directory
+ *
+ * @var string
+ */
+ protected $storageDir;
+
+ /**
+ * External library paths
+ *
+ * @var string[]
+ */
+ protected $libraryPaths;
+
+ /**
+ * Loaded external libraries
+ *
+ * @var Libraries
+ */
+ protected $libraries;
+
+ /**
+ * Icinga class loader
+ *
+ * @var ClassLoader
+ */
+ private $loader;
+
+ /**
+ * Config object
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * Module manager
+ *
+ * @var ModuleManager
+ */
+ private $moduleManager;
+
+ /**
+ * Flag indicates we're on cli environment
+ *
+ * @var bool
+ */
+ protected $isCli = false;
+
+ /**
+ * Flag indicates we're on web environment
+ *
+ * @var bool
+ */
+ protected $isWeb = false;
+
+ /**
+ * Whether Icinga Web 2 requires setup
+ *
+ * @var bool
+ */
+ protected $requiresSetup = false;
+
+ /**
+ * Constructor
+ *
+ * @param string $baseDir Icinga Web 2 base directory
+ * @param string $configDir Path to Icinga Web 2's configuration files
+ * @param string $storageDir Path to Icinga Web 2's stored files
+ */
+ protected function __construct($baseDir = null, $configDir = null, $storageDir = null)
+ {
+ if ($baseDir === null) {
+ $baseDir = dirname($this->getBootstrapDirectory());
+ }
+ $this->baseDir = $baseDir;
+ $this->appDir = $baseDir . '/application';
+ $this->vendorDir = $baseDir . '/library/vendor';
+ if (substr(__DIR__, 0, 8) === 'phar:///') {
+ $this->libDir = dirname(dirname(__DIR__));
+ } else {
+ $this->libDir = realpath(__DIR__ . '/../..');
+ }
+
+ $this->setupAutoloader();
+
+ if ($configDir === null) {
+ $configDir = getenv('ICINGAWEB_CONFIGDIR');
+ if ($configDir === false) {
+ $configDir = Platform::isWindows()
+ ? $baseDir . '/config'
+ : '/etc/icingaweb2';
+ }
+ }
+ $canonical = realpath($configDir);
+ $this->configDir = $canonical ? $canonical : $configDir;
+
+ if ($storageDir === null) {
+ $storageDir = getenv('ICINGAWEB_STORAGEDIR');
+ if ($storageDir === false) {
+ $storageDir = Platform::isWindows()
+ ? $baseDir . '/storage'
+ : '/var/lib/icingaweb2';
+ }
+ }
+ $canonical = realpath($storageDir);
+ $this->storageDir = $canonical ? $canonical : $storageDir;
+
+ if ($this->libraryPaths === null) {
+ $libraryPaths = getenv('ICINGAWEB_LIBDIR');
+ if ($libraryPaths !== false) {
+ $this->libraryPaths = array_filter(array_map(
+ 'realpath',
+ explode(':', $libraryPaths)
+ ), 'is_dir');
+ } else {
+ $this->libraryPaths = is_dir('/usr/share/icinga-php')
+ ? ['/usr/share/icinga-php']
+ : [];
+ }
+ }
+
+ set_include_path(
+ implode(
+ PATH_SEPARATOR,
+ array($this->vendorDir, get_include_path())
+ )
+ );
+
+ Benchmark::measure('Bootstrap, autoloader registered');
+
+ Icinga::setApp($this);
+
+ require_once dirname(__FILE__) . '/functions.php';
+ }
+
+ /**
+ * Bootstrap interface method for concrete bootstrap objects
+ *
+ * @return mixed
+ */
+ abstract protected function bootstrap();
+
+ /**
+ * Get loaded external libraries
+ *
+ * @return Libraries
+ */
+ public function getLibraries()
+ {
+ return $this->libraries;
+ }
+
+ /**
+ * Getter for module manager
+ *
+ * @return ModuleManager
+ */
+ public function getModuleManager()
+ {
+ return $this->moduleManager;
+ }
+
+ /**
+ * Getter for class loader
+ *
+ * @return ClassLoader
+ */
+ public function getLoader()
+ {
+ return $this->loader;
+ }
+
+ /**
+ * Getter for configuration object
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Flag indicates we're on cli environment
+ *
+ * @return bool
+ */
+ public function isCli()
+ {
+ return $this->isCli;
+ }
+
+ /**
+ * Flag indicates we're on web environment
+ *
+ * @return bool
+ */
+ public function isWeb()
+ {
+ return $this->isWeb;
+ }
+
+ /**
+ * Helper to glue directories together
+ *
+ * @param string $dir
+ * @param string $subdir
+ *
+ * @return string
+ */
+ private function getDirWithSubDir($dir, $subdir = null)
+ {
+ if ($subdir !== null) {
+ $dir .= '/' . ltrim($subdir, '/');
+ }
+
+ return $dir;
+ }
+
+ /**
+ * Get the base directory
+ *
+ * @param string $subDir Optional sub directory to get
+ *
+ * @return string
+ */
+ public function getBaseDir($subDir = null)
+ {
+ return $this->getDirWithSubDir($this->baseDir, $subDir);
+ }
+
+ /**
+ * Get the application directory
+ *
+ * @param string $subDir Optional sub directory to get
+ *
+ * @return string
+ */
+ public function getApplicationDir($subDir = null)
+ {
+ return $this->getDirWithSubDir($this->appDir, $subDir);
+ }
+
+ /**
+ * Get the vendor library directory
+ *
+ * @param string $subDir Optional sub directory to get
+ *
+ * @return string
+ */
+ public function getVendorDir($subDir = null)
+ {
+ return $this->getDirWithSubDir($this->vendorDir, $subDir);
+ }
+
+ /**
+ * Get the configuration directory
+ *
+ * @param string $subDir Optional sub directory to get
+ *
+ * @return string
+ */
+ public function getConfigDir($subDir = null)
+ {
+ return $this->getDirWithSubDir($this->configDir, $subDir);
+ }
+
+ /**
+ * Get the common storage directory
+ *
+ * @param string $subDir Optional sub directory to get
+ *
+ * @return string
+ */
+ public function getStorageDir($subDir = null)
+ {
+ return $this->getDirWithSubDir($this->storageDir, $subDir);
+ }
+
+ /**
+ * Get the Icinga library directory
+ *
+ * @param string $subDir Optional sub directory to get
+ *
+ * @return string
+ */
+ public function getLibraryDir($subDir = null)
+ {
+ return $this->getDirWithSubDir($this->libDir, $subDir);
+ }
+
+ /**
+ * Get the path to the bootstrapping directory
+ *
+ * This is usually /public for Web and EmbeddedWeb and /bin for the CLI
+ *
+ * @return string
+ *
+ * @throws LogicException If the base directory can not be detected
+ */
+ public function getBootstrapDirectory()
+ {
+ $script = $_SERVER['SCRIPT_FILENAME'];
+ $canonical = realpath($script);
+ if ($canonical !== false) {
+ $dir = dirname($canonical);
+ } elseif (substr($script, -14) === '/webrouter.php') {
+ // If Icinga Web 2 is served using PHP's built-in webserver with our webrouter.php script, the $_SERVER
+ // variable SCRIPT_FILENAME is set to DOCUMENT_ROOT/webrouter.php which is not a valid path to
+ // realpath but DOCUMENT_ROOT here still is the bootstrapping directory
+ $dir = dirname($script);
+ } else {
+ throw new LogicException('Can\'t detected base directory');
+ }
+ return $dir;
+ }
+
+ /**
+ * Start the bootstrap
+ *
+ * @param string $baseDir Icinga Web 2 base directory
+ * @param string $configDir Path to Icinga Web 2's configuration files
+ *
+ * @return static
+ */
+ public static function start($baseDir = null, $configDir = null)
+ {
+ $application = new static($baseDir, $configDir);
+ $application->bootstrap();
+ return $application;
+ }
+
+ /**
+ * Setup Icinga class loader
+ *
+ * @return $this
+ */
+ public function setupAutoloader()
+ {
+ require_once $this->libDir . '/Icinga/Application/ClassLoader.php';
+
+ $this->loader = new ClassLoader();
+ $this->loader->registerNamespace('Icinga', $this->libDir . '/Icinga');
+ $this->loader->registerNamespace('Icinga', $this->libDir . '/Icinga', $this->appDir);
+ $this->loader->register();
+
+ return $this;
+ }
+
+ /**
+ * Setup module manager
+ *
+ * @return $this
+ */
+ protected function setupModuleManager()
+ {
+ $paths = $this->getAvailableModulePaths();
+ $this->moduleManager = new ModuleManager(
+ $this,
+ $this->configDir . '/enabledModules',
+ $paths
+ );
+ return $this;
+ }
+
+ protected function getAvailableModulePaths()
+ {
+ $paths = array();
+ $configured = $this->config->get('global', 'module_path', $this->baseDir . '/modules');
+ $nextIsPhar = false;
+ foreach (explode(':', $configured) as $path) {
+ if ($path === 'phar') {
+ $nextIsPhar = true;
+ continue;
+ }
+
+ if ($nextIsPhar) {
+ $nextIsPhar = false;
+ $paths[] = 'phar:' . $path;
+ } else {
+ $paths[] = $path;
+ }
+ }
+
+ return $paths;
+ }
+
+ /**
+ * Load all enabled modules
+ *
+ * @return $this
+ */
+ protected function loadEnabledModules()
+ {
+ try {
+ $this->moduleManager->loadEnabledModules();
+ } catch (NotReadableError $e) {
+ Logger::error(new IcingaException('Cannot load enabled modules. An exception was thrown:', $e));
+ }
+ return $this;
+ }
+
+ /**
+ * Load the setup module if Icinga Web 2 requires setup or the setup token exists
+ *
+ * @return $this
+ */
+ protected function loadSetupModuleIfNecessary()
+ {
+ if (! @file_exists($this->config->resolvePath('authentication.ini'))) {
+ $this->requiresSetup = true;
+ if ($this->moduleManager->hasInstalled('setup')) {
+ $this->moduleManager->loadModule('setup');
+ }
+ } elseif ($this->setupTokenExists()) {
+ // Load setup module but do not require setup
+ if ($this->moduleManager->hasInstalled('setup')) {
+ $this->moduleManager->loadModule('setup');
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Get whether Icinga Web 2 requires setup
+ *
+ * @return bool
+ */
+ public function requiresSetup()
+ {
+ return $this->requiresSetup;
+ }
+
+ /**
+ * Get whether the setup token exists
+ *
+ * @return bool
+ */
+ public function setupTokenExists()
+ {
+ return @file_exists($this->config->resolvePath('setup.token'));
+ }
+
+ /**
+ * Load external libraries
+ *
+ * @return $this
+ */
+ protected function loadLibraries()
+ {
+ $this->libraries = new Libraries();
+ foreach ($this->libraryPaths as $libraryPath) {
+ foreach (new DirectoryIterator($libraryPath) as $path) {
+ if (! $path->isDot() && is_dir($path->getRealPath())) {
+ $this->libraries->registerPath($path->getPathname())
+ ->registerAutoloader();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Setup default logging
+ *
+ * @return $this
+ */
+ protected function setupLogging()
+ {
+ Logger::create(
+ new ConfigObject(
+ array(
+ 'log' => 'syslog'
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Load Configuration
+ *
+ * @return $this
+ */
+ protected function loadConfig()
+ {
+ Config::$configDir = $this->configDir;
+
+ try {
+ $this->config = Config::app();
+ } catch (NotReadableError $e) {
+ Logger::error(new IcingaException('Cannot load application configuration. An exception was thrown:', $e));
+ $this->config = new Config();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Error handling configuration
+ *
+ * @return $this
+ */
+ protected function setupErrorHandling()
+ {
+ error_reporting(E_ALL | E_STRICT);
+ ini_set('display_startup_errors', 1);
+ ini_set('display_errors', 1);
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+ if (! (error_reporting() & $errno)) {
+ // Error was suppressed with the @-operator
+ return false; // Continue with the normal error handler
+ }
+ switch ($errno) {
+ case E_NOTICE:
+ case E_WARNING:
+ case E_STRICT:
+ case E_RECOVERABLE_ERROR:
+ throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
+ }
+ return false; // Continue with the normal error handler
+ });
+ return $this;
+ }
+
+ /**
+ * Set up logger
+ *
+ * @return $this
+ */
+ protected function setupLogger()
+ {
+ if ($this->config->hasSection('logging')) {
+ $loggingConfig = $this->config->getSection('logging');
+
+ try {
+ Logger::create($loggingConfig);
+ } catch (ConfigurationError $e) {
+ Logger::getInstance()->registerConfigError($e->getMessage());
+
+ try {
+ Logger::getInstance()->setLevel($loggingConfig->get('level', Logger::ERROR));
+ } catch (ConfigurationError $e) {
+ Logger::getInstance()->registerConfigError($e->getMessage());
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set up the user backend factory
+ *
+ * @return $this
+ */
+ protected function setupUserBackendFactory()
+ {
+ try {
+ UserBackend::setConfig(Config::app('authentication'));
+ } catch (NotReadableError $e) {
+ Logger::error(
+ new IcingaException('Cannot load user backend configuration. An exception was thrown:', $e)
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Detect the timezone
+ *
+ * @return null|string
+ */
+ protected function detectTimezone()
+ {
+ return null;
+ }
+
+ /**
+ * Set up the timezone
+ *
+ * @return $this
+ */
+ final protected function setupTimezone()
+ {
+ $timezone = $this->detectTimeZone();
+ if ($timezone === null || @date_default_timezone_set($timezone) === false) {
+ date_default_timezone_set(@date_default_timezone_get());
+ }
+ return $this;
+ }
+
+ /**
+ * Detect the locale
+ *
+ * @return null|string
+ */
+ protected function detectLocale()
+ {
+ return null;
+ }
+
+ /**
+ * Prepare internationalization using gettext
+ *
+ * @return $this
+ */
+ protected function prepareInternationalization()
+ {
+ StaticTranslator::$instance = (new GettextTranslator())
+ ->setDefaultDomain('icinga');
+
+ return $this;
+ }
+
+ /**
+ * Set up internationalization using gettext
+ *
+ * @return $this
+ */
+ final protected function setupInternationalization()
+ {
+ /** @var GettextTranslator $translator */
+ $translator = StaticTranslator::$instance;
+
+ if ($this->hasLocales()) {
+ $translator->addTranslationDirectory($this->getLocaleDir(), 'icinga');
+ }
+
+ $locale = $this->detectLocale();
+ if ($locale === null) {
+ $locale = $translator->getDefaultLocale();
+ }
+
+ try {
+ $translator->setLocale($locale);
+ } catch (Exception $error) {
+ Logger::error($error);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string Our locale directory
+ */
+ public function getLocaleDir()
+ {
+ if ($this->localeDir === null) {
+ $L10nLocales = getenv('ICINGAWEB_LOCALEDIR') ?: '/usr/share/icinga-L10n/locale';
+ if (file_exists($L10nLocales) && is_dir($L10nLocales)) {
+ $this->localeDir = $L10nLocales;
+ } else {
+ $this->localeDir = false;
+ }
+ }
+
+ return $this->localeDir;
+ }
+
+ /**
+ * return bool Whether Icinga Web has translations
+ */
+ public function hasLocales()
+ {
+ $localedir = $this->getLocaleDir();
+ return $localedir !== false && file_exists($localedir) && is_dir($localedir);
+ }
+}
diff --git a/library/Icinga/Application/Benchmark.php b/library/Icinga/Application/Benchmark.php
new file mode 100644
index 0000000..6514ad5
--- /dev/null
+++ b/library/Icinga/Application/Benchmark.php
@@ -0,0 +1,299 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga\Application\Benchmark class
+ */
+namespace Icinga\Application;
+
+use Icinga\Util\Format;
+
+/**
+ * This class provides a simple and lightweight benchmark class
+ *
+ * <code>
+ * Benchmark::measure('Program started');
+ * // ...do something...
+ * Benchmark::measure('Task finieshed');
+ * Benchmark::dump();
+ * </code>
+ */
+class Benchmark
+{
+ const TIME = 0x01;
+ const MEMORY = 0x02;
+
+ protected static $instance;
+ protected $start;
+ protected $measures = array();
+
+ /**
+ * Add a measurement to your benchmark
+ *
+ * The same identifier can also be used multiple times
+ *
+ * @param string A comment identifying the current measurement
+ * @return void
+ */
+ public static function measure($message)
+ {
+ self::getInstance()->measures[] = (object) array(
+ 'timestamp' => microtime(true),
+ 'memory_real' => memory_get_usage(true),
+ 'memory' => memory_get_usage(),
+ 'message' => $message
+ );
+ }
+
+ /**
+ * Throws all measurements away
+ *
+ * This empties your measurement table and allows you to restart your
+ * benchmark from scratch
+ *
+ * @return void
+ */
+ public static function reset()
+ {
+ self::$instance = null;
+ }
+
+ /**
+ * Rerieve benchmark start time
+ *
+ * This will give you the timestamp of your first measurement
+ *
+ * @return float
+ */
+ public static function getStartTime()
+ {
+ return self::getInstance()->start;
+ }
+
+ /**
+ * Dump benchmark data
+ *
+ * Will dump a text table if running on CLI and a simple HTML table
+ * otherwise. Use Benchmark::TIME and Benchmark::MEMORY to choose whether
+ * you prefer to show either time or memory or both in your output
+ *
+ * @param int Whether to get time and/or memory summary
+ * @return string
+ */
+ public static function dump($what = null)
+ {
+ if (Icinga::app()->isCli()) {
+ echo self::renderToText($what);
+ } else {
+ echo self::renderToHtml($what);
+ }
+ }
+
+ /**
+ * Render benchmark data to a simple text table
+ *
+ * Use Benchmark::TIME and Icinga::MEMORY to choose whether you prefer to
+ * show either time or memory or both in your output
+ *
+ * @param int Whether to get time and/or memory summary
+ * @return string
+ */
+ public static function renderToText($what = null)
+ {
+ $data = self::prepareDataForRendering($what);
+ $sep = '+';
+ $title = '|';
+ foreach ($data->columns as & $col) {
+ $col->format = ' %'
+ . ($col->align === 'right' ? '' : '-')
+ . $col->maxlen . 's |';
+
+ $sep .= str_repeat('-', $col->maxlen) . '--+';
+ $title .= sprintf($col->format, $col->title);
+ }
+
+ $out = $sep . "\n" . $title . "\n" . $sep . "\n";
+ foreach ($data->rows as & $row) {
+ $r = '|';
+ foreach ($data->columns as $key => & $col) {
+ $r .= sprintf($col->format, $row[$key]);
+ }
+ $out .= $r . "\n";
+ }
+
+ $out .= $sep . "\n";
+ return $out;
+ }
+
+ /**
+ * Render benchmark data to a simple HTML table
+ *
+ * Use Benchmark::TIME and Benchmark::MEMORY to choose whether you prefer
+ * to show either time or memory or both in your output
+ *
+ * @param int Whether to get time and/or memory summary
+ * @return string
+ */
+ public static function renderToHtml($what = null)
+ {
+ $data = self::prepareDataForRendering($what);
+
+ // TODO: Move formatting to CSS file
+ $html = '<table class="benchmark">' . "\n" . '<tr>';
+ foreach ($data->columns as & $col) {
+ if ($col->title === 'Time') {
+ continue;
+ }
+ $html .= sprintf(
+ '<td align="%s">%s</td>',
+ $col->align,
+ htmlspecialchars($col->title)
+ );
+ }
+ $html .= "</tr>\n";
+
+ foreach ($data->rows as & $row) {
+ $html .= '<tr>';
+ foreach ($data->columns as $key => & $col) {
+ if ($col->title === 'Time') {
+ continue;
+ }
+ $html .= sprintf(
+ '<td align="%s">%s</td>',
+ $col->align,
+ $row[$key]
+ );
+ }
+ $html .= "</tr>\n";
+ }
+ $html .= "</table>\n";
+ return $html;
+ }
+
+ /**
+ * Prepares benchmark data for output
+ *
+ * Use Benchmark::TIME and Benchmark::MEMORY to choose whether you prefer
+ * to have either time or memory or both in your output
+ *
+ * @param int Whether to get time and/or memory summary
+ * @return array
+ */
+ protected static function prepareDataForRendering($what = null)
+ {
+ if ($what === null) {
+ $what = self::TIME | self::MEMORY;
+ }
+
+ $columns = array(
+ (object) array(
+ 'title' => 'Time',
+ 'align' => 'left',
+ 'maxlen' => 4
+ ),
+ (object) array(
+ 'title' => 'Description',
+ 'align' => 'left',
+ 'maxlen' => 11
+ )
+ );
+ if ($what & self::TIME) {
+ $columns[] = (object) array(
+ 'title' => 'Off (ms)',
+ 'align' => 'right',
+ 'maxlen' => 11
+ );
+ $columns[] = (object) array(
+ 'title' => 'Dur (ms)',
+ 'align' => 'right',
+ 'maxlen' => 13
+ );
+ }
+ if ($what & self::MEMORY) {
+ $columns[] = (object) array(
+ 'title' => 'Mem (diff)',
+ 'align' => 'right',
+ 'maxlen' => 10
+ );
+ $columns[] = (object) array(
+ 'title' => 'Mem (total)',
+ 'align' => 'right',
+ 'maxlen' => 11
+ );
+ }
+
+ $bench = self::getInstance();
+ $last = $bench->start;
+ $rows = array();
+ $lastmem = 0;
+ foreach ($bench->measures as $m) {
+ $micro = sprintf(
+ '%03d',
+ round(($m->timestamp - floor($m->timestamp)) * 1000)
+ );
+ $vals = array(
+ date('H:i:s', (int) $m->timestamp) . '.' . $micro,
+ $m->message
+ );
+
+ if ($what & self::TIME) {
+ $m->relative = $m->timestamp - $bench->start;
+ $m->offset = $m->timestamp - $last;
+ $last = $m->timestamp;
+ $vals[] = sprintf('%0.3f', $m->relative * 1000);
+ $vals[] = sprintf('%0.3f', $m->offset * 1000);
+ }
+
+ if ($what & self::MEMORY) {
+ $mem = $m->memory - $lastmem;
+ $lastmem = $m->memory;
+ $vals[] = Format::bytes($mem);
+ $vals[] = Format::bytes($m->memory);
+ }
+
+ $row = & $rows[];
+ foreach ($vals as $col => $val) {
+ $row[$col] = $val;
+ $columns[$col]->maxlen = max(
+ strlen($val),
+ $columns[$col]->maxlen
+ );
+ }
+ }
+
+ return (object) array(
+ 'columns' => $columns,
+ 'rows' => $rows
+ );
+ }
+
+ /**
+ * Singleton
+ *
+ * Benchmark is run only once, but you are not allowed to directly access
+ * the getInstance() method
+ *
+ * @return self
+ */
+ protected static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new Benchmark();
+ self::$instance->start = microtime(true);
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Constructor
+ *
+ * Singleton usage is enforced, the only way to instantiate Benchmark is by
+ * starting your measurements
+ *
+ * @return void
+ */
+ protected function __construct()
+ {
+ }
+}
diff --git a/library/Icinga/Application/ClassLoader.php b/library/Icinga/Application/ClassLoader.php
new file mode 100644
index 0000000..aed26f7
--- /dev/null
+++ b/library/Icinga/Application/ClassLoader.php
@@ -0,0 +1,334 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Zend_Loader_Autoloader;
+
+/**
+ * PSR-4 class loader
+ */
+class ClassLoader
+{
+ /**
+ * Namespace separator
+ */
+ const NAMESPACE_SEPARATOR = '\\';
+
+ /**
+ * Icinga Web 2 module namespace prefix
+ */
+ const MODULE_PREFIX = 'Icinga\\Module\\';
+
+ /**
+ * Icinga Web 2 module namespace prefix length
+ *
+ * Helps to make substr/strpos operations even faster
+ */
+ const MODULE_PREFIX_LENGTH = 14;
+
+ /**
+ * A hardcoded class/subdir map for application ns prefixes
+ *
+ * When a module registers with an application directory, those
+ * namespace prefixes (after the module prefix) will be looked up
+ * in the corresponding application subdirectories
+ *
+ * @var array
+ */
+ protected $applicationPrefixes = array(
+ 'Clicommands' => 'clicommands',
+ 'Controllers' => 'controllers',
+ 'Forms' => 'forms'
+ );
+
+ /**
+ * Whether we already instantiated the ZF autoloader
+ *
+ * @var boolean
+ */
+ protected $gotZend = false;
+
+ /**
+ * Namespaces
+ *
+ * @var array
+ */
+ private $namespaces = array();
+
+ /**
+ * Application directories
+ *
+ * @var array
+ */
+ private $applicationDirectories = array();
+
+ /**
+ * Register a base directory for a namespace prefix
+ *
+ * Application directory is optional and provides additional lookup
+ * logic for hardcoded namespaces like "Forms"
+ *
+ * @param string $namespace
+ * @param string $directory
+ * @param string $appDirectory
+ *
+ * @return $this
+ */
+ public function registerNamespace($namespace, $directory, $appDirectory = null)
+ {
+ $this->namespaces[$namespace] = $directory;
+
+ if ($appDirectory !== null) {
+ $this->applicationDirectories[$namespace] = $appDirectory;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Test whether a namespace exists
+ *
+ * @param string $namespace
+ *
+ * @return bool
+ */
+ public function hasNamespace($namespace)
+ {
+ return array_key_exists($namespace, $this->namespaces);
+ }
+
+ /**
+ * Get the source file of the given class or interface
+ *
+ * @param string $class Name of the class or interface
+ *
+ * @return string|null
+ */
+ public function getSourceFile($class)
+ {
+ if ($file = $this->getModuleSourceFile($class)) {
+ return $file;
+ }
+
+ foreach ($this->namespaces as $namespace => $dir) {
+ if ($class === strstr($class, "$namespace\\")) {
+ return $this->buildClassFilename($class, $namespace);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the source file of the given module class or interface
+ *
+ * @param string $class Module class or interface name
+ *
+ * @return string|null
+ */
+ protected function getModuleSourceFile($class)
+ {
+ if (! $this->classBelongsToModule($class)) {
+ return null;
+ }
+
+ $modules = Icinga::app()->getModuleManager();
+ $namespace = $this->extractModuleNamespace($class);
+
+ if ($this->hasNamespace($namespace)) {
+ return $this->buildClassFilename($class, $namespace);
+ } elseif (! $modules->loadedAllEnabledModules()) {
+ $moduleName = $this->extractModuleName($class);
+
+ if ($modules->hasEnabled($moduleName)) {
+ $modules->loadModule($moduleName);
+
+ return $this->buildClassFilename($class, $namespace);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Extract the Icinga module namespace from a given namespaced class name
+ *
+ * Does no validation, prefix must have been checked before
+ *
+ * @return string
+ */
+ protected function extractModuleNamespace($class)
+ {
+ return substr(
+ $class,
+ 0,
+ strpos($class, self::NAMESPACE_SEPARATOR, self::MODULE_PREFIX_LENGTH + 1)
+ );
+ }
+
+ /**
+ * Extract the Icinga module name from a given namespaced class name
+ *
+ * Does no validation, prefix must have been checked before
+ *
+ * @return string
+ */
+ public static function extractModuleName($class)
+ {
+ return lcfirst(
+ substr(
+ $class,
+ self::MODULE_PREFIX_LENGTH,
+ strpos(
+ $class,
+ self::NAMESPACE_SEPARATOR,
+ self::MODULE_PREFIX_LENGTH + 1
+ ) - self::MODULE_PREFIX_LENGTH
+ )
+ );
+ }
+
+ /**
+ * Whether the given class name belongs to a module namespace
+ *
+ * @return boolean
+ */
+ public static function classBelongsToModule($class)
+ {
+ return substr($class, 0, self::MODULE_PREFIX_LENGTH) === self::MODULE_PREFIX;
+ }
+
+ /**
+ * Prepare a filename string for the given class
+ *
+ * Expects the given namespace to be registered with a path name
+ *
+ * @return string
+ */
+ protected function buildClassFilename($class, $namespace)
+ {
+ $relNs = substr($class, strlen($namespace) + 1);
+
+ if ($this->namespaceHasApplictionDirectory($namespace)) {
+ $prefixSeparator = strpos($relNs, self::NAMESPACE_SEPARATOR);
+ $prefix = substr($relNs, 0, $prefixSeparator);
+
+ if ($this->isApplicationPrefix($prefix)) {
+ return $this->applicationDirectories[$namespace]
+ . DIRECTORY_SEPARATOR
+ . $this->applicationPrefixes[$prefix]
+ . $this->classToRelativePhpFilename(substr($relNs, $prefixSeparator));
+ }
+ }
+
+ return $this->namespaces[$namespace] . DIRECTORY_SEPARATOR . $this->classToRelativePhpFilename($relNs);
+ }
+
+ /**
+ * Return the relative file name for the given (namespaces) class
+ *
+ * @param string $class
+ *
+ * @return string
+ */
+ protected function classToRelativePhpFilename($class)
+ {
+ return str_replace(
+ self::NAMESPACE_SEPARATOR,
+ DIRECTORY_SEPARATOR,
+ $class
+ ) . '.php';
+ }
+
+ /**
+ * Whether given prefix (Forms, Controllers...) makes part of "application"
+ *
+ * @param string $prefix
+ *
+ * @return boolean
+ */
+ protected function isApplicationPrefix($prefix)
+ {
+ return array_key_exists($prefix, $this->applicationPrefixes);
+ }
+
+ /**
+ * Whether the given namespace registered an application directory
+ *
+ * @return boolean
+ */
+ protected function namespaceHasApplictionDirectory($namespace)
+ {
+ return array_key_exists($namespace, $this->applicationDirectories);
+ }
+
+ /**
+ * Require ZF autoloader
+ *
+ * @return Zend_Loader_Autoloader
+ */
+ protected function requireZendAutoloader()
+ {
+ require_once 'Zend/Loader/Autoloader.php';
+ $this->gotZend = true;
+ return Zend_Loader_Autoloader::getInstance();
+ }
+
+ /**
+ * Load the given class or interface
+ *
+ * @param string $class Name of the class or interface
+ *
+ * @return bool Whether the class or interface has been loaded
+ */
+ public function loadClass($class)
+ {
+ // We are aware of the Zend_ prefix and lazyload it's autoloader.
+ // Return as fast as possible if we already did so.
+ if (substr($class, 0, 5) === 'Zend_') {
+ if (! $this->gotZend) {
+ $zendLoader = $this->requireZendAutoloader();
+ if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
+ // PHP7 seems to remember the autoload function stack before auto-loading. Thus
+ // autoload functions registered during autoload never get called
+ return $zendLoader::autoload($class);
+ }
+ }
+ return false;
+ }
+
+ if ($file = $this->getSourceFile($class)) {
+ if (file_exists($file)) {
+ require $file;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Register {@link loadClass()} as an autoloader
+ */
+ public function register()
+ {
+ spl_autoload_register(array($this, 'loadClass'));
+ }
+
+ /**
+ * Unregister {@link loadClass()} as an autoloader
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Unregister this as an autoloader
+ */
+ public function __destruct()
+ {
+ $this->unregister();
+ }
+}
diff --git a/library/Icinga/Application/Cli.php b/library/Icinga/Application/Cli.php
new file mode 100644
index 0000000..719bf92
--- /dev/null
+++ b/library/Icinga/Application/Cli.php
@@ -0,0 +1,210 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Icinga\Application\Platform;
+use Icinga\Application\ApplicationBootstrap;
+use Icinga\Authentication\Auth;
+use Icinga\Cli\Params;
+use Icinga\Cli\Loader;
+use Icinga\Cli\Screen;
+use Icinga\Application\Logger;
+use Icinga\Application\Benchmark;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\ProgrammingError;
+use Icinga\User;
+
+require_once __DIR__ . '/ApplicationBootstrap.php';
+
+class Cli extends ApplicationBootstrap
+{
+ protected $isCli = true;
+
+ protected $params;
+
+ protected $showBenchmark = false;
+
+ protected $watchTimeout;
+
+ protected $cliLoader;
+
+ protected $verbose;
+
+ protected $debug;
+
+ protected function bootstrap()
+ {
+ $this->assertRunningOnCli();
+ $this->setupLogging()
+ ->setupErrorHandling()
+ ->loadLibraries()
+ ->loadConfig()
+ ->setupTimezone()
+ ->prepareInternationalization()
+ ->setupInternationalization()
+ ->parseBasicParams()
+ ->setupLogger()
+ ->setupModuleManager()
+ ->setupUserBackendFactory()
+ ->loadSetupModuleIfNecessary()
+ ->setupFakeAuthentication();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setupLogging()
+ {
+ Logger::create(
+ new ConfigObject(
+ array(
+ 'log' => 'stderr'
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setupLogger()
+ {
+ $config = new ConfigObject();
+ $config->log = $this->params->shift('log', 'stderr');
+ if ($config->log === 'file') {
+ $config->file = $this->params->shiftRequired('log-path');
+ } elseif ($config->log === 'syslog') {
+ $config->application = 'icingacli';
+ }
+
+ if ($this->params->get('verbose', false)) {
+ $config->level = Logger::INFO;
+ } elseif ($this->params->get('debug', false)) {
+ $config->level = Logger::DEBUG;
+ } else {
+ $config->level = Logger::WARNING;
+ }
+
+ Logger::create($config);
+ return $this;
+ }
+
+ protected function setupFakeAuthentication()
+ {
+ Auth::getInstance()->setUser(new User('cli'));
+
+ return $this;
+ }
+
+ public function cliLoader()
+ {
+ if ($this->cliLoader === null) {
+ $this->cliLoader = new Loader($this);
+ }
+ return $this->cliLoader;
+ }
+
+ protected function parseBasicParams()
+ {
+ $this->params = Params::parse();
+ if ($this->params->shift('help')) {
+ $this->params->unshift('help');
+ }
+ if ($this->params->shift('version')) {
+ $this->params->unshift('version');
+ }
+ if ($this->params->shift('autocomplete')) {
+ $this->params->unshift('autocomplete');
+ }
+
+ $watch = $this->params->shift('watch');
+ if ($watch === true) {
+ $this->watchTimeout = 5;
+ } elseif (is_numeric($watch)) {
+ $this->watchTimeout = (int) $watch;
+ }
+
+ $this->debug = (int) $this->params->get('debug');
+ $this->verbose = (int) $this->params->get('verbose');
+
+ $this->showBenchmark = (bool) $this->params->shift('benchmark');
+ return $this;
+ }
+
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ public function dispatchModule($name, $basedir = null)
+ {
+ $this->getModuleManager()->loadModule($name, $basedir);
+ $this->cliLoader()->setModuleName($name);
+ $this->dispatch();
+ }
+
+ public function dispatch()
+ {
+ Benchmark::measure('Dispatching CLI command');
+
+ if ($this->watchTimeout === null) {
+ $this->dispatchOnce();
+ } else {
+ $this->dispatchEndless();
+ }
+ }
+
+ protected function dispatchOnce()
+ {
+ $loader = $this->cliLoader();
+ $loader->parseParams();
+ $result = $loader->dispatch();
+ Benchmark::measure('All done');
+ if ($this->showBenchmark) {
+ Benchmark::dump();
+ }
+ if ($result === false) {
+ exit(3);
+ }
+ }
+
+ protected function dispatchEndless()
+ {
+ $loader = $this->cliLoader();
+ $loader->parseParams();
+ $screen = Screen::instance();
+
+ while (true) {
+ Benchmark::measure('Watch mode - loop begins');
+ ob_start();
+ $params = clone($this->params);
+ $loader->dispatch($params);
+ Benchmark::measure('Dispatch done');
+ if ($this->showBenchmark) {
+ Benchmark::dump();
+ }
+ Benchmark::reset();
+ $out = ob_get_contents();
+ ob_end_clean();
+ echo $screen->clear() . $out;
+ sleep($this->watchTimeout);
+ }
+ }
+
+ /**
+ * Fail if Icinga has not been called on CLI
+ *
+ * @throws ProgrammingError
+ * @return void
+ */
+ private function assertRunningOnCli()
+ {
+ if (Platform::isCli()) {
+ return;
+ }
+ throw new ProgrammingError('Icinga is not running on CLI');
+ }
+}
diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php
new file mode 100644
index 0000000..006c40f
--- /dev/null
+++ b/library/Icinga/Application/Config.php
@@ -0,0 +1,497 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Iterator;
+use Countable;
+use LogicException;
+use UnexpectedValueException;
+use Icinga\Util\File;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Selectable;
+use Icinga\Data\SimpleQuery;
+use Icinga\File\Ini\IniWriter;
+use Icinga\File\Ini\IniParser;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotReadableError;
+use Icinga\Web\Navigation\Navigation;
+
+/**
+ * Container for INI like configuration and global registry of application and module related configuration.
+ */
+class Config implements Countable, Iterator, Selectable
+{
+ /**
+ * Configuration directory where ALL (application and module) configuration is located
+ *
+ * @var string
+ */
+ public static $configDir;
+
+ /**
+ * Application config instances per file
+ *
+ * @var array
+ */
+ protected static $app = array();
+
+ /**
+ * Module config instances per file
+ *
+ * @var array
+ */
+ protected static $modules = array();
+
+ /**
+ * Navigation config instances per type
+ *
+ * @var array
+ */
+ protected static $navigation = array();
+
+ /**
+ * The internal ConfigObject
+ *
+ * @var ConfigObject
+ */
+ protected $config;
+
+ /**
+ * The INI file this config has been loaded from or should be written to
+ *
+ * @var string
+ */
+ protected $configFile;
+
+ /**
+ * Create a new config
+ *
+ * @param ConfigObject $config The config object to handle
+ */
+ public function __construct(ConfigObject $config = null)
+ {
+ $this->config = $config !== null ? $config : new ConfigObject();
+ }
+
+ /**
+ * Return this config's file path
+ *
+ * @return string
+ */
+ public function getConfigFile()
+ {
+ return $this->configFile;
+ }
+
+ /**
+ * Set this config's file path
+ *
+ * @param string $filepath The path to the ini file
+ *
+ * @return $this
+ */
+ public function setConfigFile($filepath)
+ {
+ $this->configFile = $filepath;
+ return $this;
+ }
+
+ /**
+ * Return the internal ConfigObject
+ *
+ * @return ConfigObject
+ */
+ public function getConfigObject()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Provide a query for the internal config object
+ *
+ * @return SimpleQuery
+ */
+ public function select()
+ {
+ return $this->config->select();
+ }
+
+ /**
+ * Return the count of available sections
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return $this->select()->count();
+ }
+
+ /**
+ * Reset the current position of the internal config object
+ *
+ * @return void
+ */
+ public function rewind(): void
+ {
+ $this->config->rewind();
+ }
+
+ /**
+ * Return the section of the current iteration
+ *
+ * @return ConfigObject
+ */
+ public function current(): ConfigObject
+ {
+ return $this->config->current();
+ }
+
+ /**
+ * Return whether the position of the current iteration is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return $this->config->valid();
+ }
+
+ /**
+ * Return the section's name of the current iteration
+ *
+ * @return string
+ */
+ public function key(): string
+ {
+ return $this->config->key();
+ }
+
+ /**
+ * Advance the position of the current iteration and return the new section
+ *
+ * @return void
+ */
+ public function next(): void
+ {
+ $this->config->next();
+ }
+
+ /**
+ * Return whether this config has any sections
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return $this->config->isEmpty();
+ }
+
+ /**
+ * Return this config's section names
+ *
+ * @return array
+ */
+ public function keys()
+ {
+ return $this->config->keys();
+ }
+
+ /**
+ * Return this config's data as associative array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->config->toArray();
+ }
+
+ /**
+ * Return the value from a section's property
+ *
+ * @param string $section The section where the given property can be found
+ * @param string $key The section's property to fetch the value from
+ * @param mixed $default The value to return in case the section or the property is missing
+ *
+ * @return mixed
+ *
+ * @throws UnexpectedValueException In case the given section does not hold any configuration
+ */
+ public function get($section, $key, $default = null)
+ {
+ $value = $this->config->$section;
+ if ($value instanceof ConfigObject) {
+ $value = $value->$key;
+ } elseif ($value !== null) {
+ throw new UnexpectedValueException(
+ sprintf('Value "%s" is not of type "%s" or a sub-type of it', $value, get_class($this->config))
+ );
+ }
+
+ if ($value === null && $default !== null) {
+ $value = $default;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Return the given section
+ *
+ * @param string $name The section's name
+ *
+ * @return ConfigObject
+ */
+ public function getSection($name)
+ {
+ $section = $this->config->get($name);
+ return $section !== null ? $section : new ConfigObject();
+ }
+
+ /**
+ * Set or replace a section
+ *
+ * @param string $name
+ * @param array|ConfigObject $config
+ *
+ * @return $this
+ */
+ public function setSection($name, $config = null)
+ {
+ if ($config === null) {
+ $config = new ConfigObject();
+ } elseif (! $config instanceof ConfigObject) {
+ $config = new ConfigObject($config);
+ }
+
+ $this->config->$name = $config;
+ return $this;
+ }
+
+ /**
+ * Remove a section
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function removeSection($name)
+ {
+ unset($this->config->$name);
+ return $this;
+ }
+
+ /**
+ * Return whether the given section exists
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasSection($name)
+ {
+ return isset($this->config->$name);
+ }
+
+ /**
+ * Initialize a new config using the given array
+ *
+ * The returned config has no file associated to it.
+ *
+ * @param array $array The array to initialize the config with
+ *
+ * @return Config
+ */
+ public static function fromArray(array $array)
+ {
+ return new static(new ConfigObject($array));
+ }
+
+ /**
+ * Load configuration from the given INI file
+ *
+ * @param string $file The file to parse
+ *
+ * @throws NotReadableError When the file cannot be read
+ */
+ public static function fromIni($file)
+ {
+ $emptyConfig = new static();
+
+ $filepath = realpath($file);
+ if ($filepath === false) {
+ $emptyConfig->setConfigFile($file);
+ } elseif (is_readable($filepath)) {
+ return IniParser::parseIniFile($filepath);
+ } elseif (@file_exists($filepath)) {
+ throw new NotReadableError(t('Cannot read config file "%s". Permission denied'), $filepath);
+ }
+
+ return $emptyConfig;
+ }
+
+ /**
+ * Save configuration to the given INI file
+ *
+ * @param string|null $filePath The path to the INI file or null in case this config's path should be used
+ * @param int $fileMode The file mode to store the file with
+ *
+ * @throws LogicException In case this config has no path and none is passed in either
+ * @throws NotWritableError In case the INI file cannot be written
+ *
+ * @todo create basepath and throw NotWritableError in case its not possible
+ */
+ public function saveIni($filePath = null, $fileMode = 0660)
+ {
+ if ($filePath === null && $this->configFile) {
+ $filePath = $this->configFile;
+ } elseif ($filePath === null) {
+ throw new LogicException('You need to pass $filePath or set a path using Config::setConfigFile()');
+ }
+
+ if (! file_exists($filePath)) {
+ File::create($filePath, $fileMode);
+ }
+
+ $this->getIniWriter($filePath, $fileMode)->write();
+ }
+
+ /**
+ * Return a IniWriter for this config
+ *
+ * @param string|null $filePath
+ * @param int $fileMode
+ *
+ * @return IniWriter
+ */
+ protected function getIniWriter($filePath = null, $fileMode = null)
+ {
+ return new IniWriter($this, $filePath, $fileMode);
+ }
+
+ /**
+ * Prepend configuration base dir to the given relative path
+ *
+ * @param string $path A relative path
+ *
+ * @return string
+ */
+ public static function resolvePath($path)
+ {
+ return self::$configDir . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
+ }
+
+ /**
+ * Retrieve a application config
+ *
+ * @param string $configname The configuration name (without ini suffix) to read and return
+ * @param bool $fromDisk When set true, the configuration will be read from disk, even
+ * if it already has been read
+ *
+ * @return Config The requested configuration
+ */
+ public static function app($configname = 'config', $fromDisk = false)
+ {
+ if (! isset(self::$app[$configname]) || $fromDisk) {
+ self::$app[$configname] = static::fromIni(static::resolvePath($configname . '.ini'));
+ }
+
+ return self::$app[$configname];
+ }
+
+ /**
+ * Retrieve a module config
+ *
+ * @param string $modulename The name of the module where to look for the requested configuration
+ * @param string $configname The configuration name (without ini suffix) to read and return
+ * @param string $fromDisk When set true, the configuration will be read from disk, even
+ * if it already has been read
+ *
+ * @return Config The requested configuration
+ */
+ public static function module($modulename, $configname = 'config', $fromDisk = false)
+ {
+ if (! isset(self::$modules[$modulename])) {
+ self::$modules[$modulename] = array();
+ }
+
+ if (! isset(self::$modules[$modulename][$configname]) || $fromDisk) {
+ self::$modules[$modulename][$configname] = static::fromIni(
+ static::resolvePath('modules/' . $modulename . '/' . $configname . '.ini')
+ );
+ }
+ return self::$modules[$modulename][$configname];
+ }
+
+ /**
+ * Retrieve a navigation config
+ *
+ * @param string $type The type identifier of the navigation item for which to return its config
+ * @param string $username A user's name or null if the shared config is desired
+ * @param bool $fromDisk If true, the configuration will be read from disk
+ *
+ * @return Config The requested configuration
+ */
+ public static function navigation($type, $username = null, $fromDisk = false)
+ {
+ if (! isset(self::$navigation[$type])) {
+ self::$navigation[$type] = array();
+ }
+
+ $branch = $username ?: 'shared';
+ $typeConfigs = self::$navigation[$type];
+ if (! isset($typeConfigs[$branch]) || $fromDisk) {
+ $typeConfigs[$branch] = static::fromIni(static::getNavigationConfigPath($type, $username));
+ }
+
+ return $typeConfigs[$branch];
+ }
+
+ /**
+ * Return the path to the configuration file for the given navigation item type and user
+ *
+ * @param string $type
+ * @param string $username
+ *
+ * @return string
+ *
+ * @throws IcingaException In case the given type is unknown
+ */
+ protected static function getNavigationConfigPath($type, $username = null)
+ {
+ $itemTypeConfig = Navigation::getItemTypeConfiguration();
+ if (! isset($itemTypeConfig[$type])) {
+ throw new IcingaException('Invalid navigation item type %s provided', $type);
+ }
+
+ if (isset($itemTypeConfig[$type]['config'])) {
+ $filename = $itemTypeConfig[$type]['config'] . '.ini';
+ } else {
+ $filename = $type . 's.ini';
+ }
+
+ if ($username) {
+ $path = static::resolvePath(implode(DIRECTORY_SEPARATOR, array('preferences', $username, $filename)));
+ if (realpath($path) === false) {
+ $path = static::resolvePath(implode(
+ DIRECTORY_SEPARATOR,
+ array('preferences', strtolower($username), $filename)
+ ));
+ }
+ } else {
+ $path = static::resolvePath('navigation' . DIRECTORY_SEPARATOR . $filename);
+ }
+ return $path;
+ }
+
+ /**
+ * Return this config rendered as a INI structured string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getIniWriter()->render();
+ }
+}
diff --git a/library/Icinga/Application/EmbeddedWeb.php b/library/Icinga/Application/EmbeddedWeb.php
new file mode 100644
index 0000000..8d03e11
--- /dev/null
+++ b/library/Icinga/Application/EmbeddedWeb.php
@@ -0,0 +1,114 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+require_once dirname(__FILE__) . '/ApplicationBootstrap.php';
+
+use Icinga\Web\Request;
+use Icinga\Web\Response;
+use ipl\I18n\NoopTranslator;
+use ipl\I18n\StaticTranslator;
+
+/**
+ * Use this if you want to make use of Icinga functionality in other web projects
+ *
+ * Usage example:
+ * <code>
+ * use Icinga\Application\EmbeddedWeb;
+ * EmbeddedWeb::start();
+ * </code>
+ */
+class EmbeddedWeb extends ApplicationBootstrap
+{
+ /**
+ * Request
+ *
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * Response
+ *
+ * @var Response
+ */
+ protected $response;
+
+ /**
+ * Get the request
+ *
+ * @return Request
+ */
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ /**
+ * Get the response
+ *
+ * @return Response
+ */
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ /**
+ * Embedded bootstrap parts
+ *
+ * @see ApplicationBootstrap::bootstrap
+ *
+ * @return $this
+ */
+ protected function bootstrap()
+ {
+ return $this
+ ->setupErrorHandling()
+ ->loadLibraries()
+ ->loadConfig()
+ ->setupLogging()
+ ->setupLogger()
+ ->setupRequest()
+ ->setupResponse()
+ ->setupTimezone()
+ ->prepareFakeInternationalization()
+ ->setupModuleManager()
+ ->loadEnabledModules();
+ }
+
+ /**
+ * Set the request
+ *
+ * @return $this
+ */
+ protected function setupRequest()
+ {
+ $this->request = new Request();
+ return $this;
+ }
+
+ /**
+ * Set the response
+ *
+ * @return $this
+ */
+ protected function setupResponse()
+ {
+ $this->response = new Response();
+ return $this;
+ }
+
+ /**
+ * Prepare fake internationalization
+ *
+ * @return $this
+ */
+ protected function prepareFakeInternationalization()
+ {
+ StaticTranslator::$instance = new NoopTranslator();
+
+ return $this;
+ }
+}
diff --git a/library/Icinga/Application/Hook.php b/library/Icinga/Application/Hook.php
new file mode 100644
index 0000000..b3d12fe
--- /dev/null
+++ b/library/Icinga/Application/Hook.php
@@ -0,0 +1,330 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Exception;
+use Icinga\Authentication\Auth;
+use Icinga\Application\Modules\Manager;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Icinga Hook registry
+ *
+ * Modules making use of predefined hooks have to use this registry
+ *
+ * Usage:
+ * <code>
+ * Hook::register('grapher', 'My\\Grapher\\Class');
+ * </code>
+ */
+class Hook
+{
+ /**
+ * Our hook name registry
+ *
+ * @var array
+ */
+ protected static $hooks = array();
+
+ /**
+ * Hooks that have already been instantiated
+ *
+ * @var array
+ */
+ protected static $instances = array();
+
+ /**
+ * Namespace prefix
+ *
+ * @var string
+ */
+ public static $BASE_NS = 'Icinga\\Application\\Hook\\';
+
+ /**
+ * Append this string to base class
+ *
+ * All base classes renamed to *Hook
+ *
+ * @var string
+ */
+ public static $classSuffix = 'Hook';
+
+ /**
+ * Reset object state
+ */
+ public static function clean()
+ {
+ self::$hooks = array();
+ self::$instances = array();
+ self::$BASE_NS = 'Icinga\\Application\\Hook\\';
+ }
+
+ /**
+ * Whether someone registered itself for the given hook name
+ *
+ * @param string $name One of the predefined hook names
+ *
+ * @return bool
+ */
+ public static function has($name)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (! array_key_exists($name, self::$hooks)) {
+ return false;
+ }
+
+ foreach (self::$hooks[$name] as $hook) {
+ list($class, $alwaysRun) = $hook;
+ if ($alwaysRun || self::hasPermission($class)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected static function normalizeHookName($name)
+ {
+ if (strpos($name, '\\') === false) {
+ $parts = explode('/', $name);
+ foreach ($parts as & $part) {
+ $part = ucfirst($part);
+ }
+
+ return implode('\\', $parts);
+ }
+
+ return $name;
+ }
+
+ /**
+ * Create or return an instance of a given hook
+ *
+ * TODO: Should return some kind of a hook interface
+ *
+ * @param string $name One of the predefined hook names
+ * @param string $key The identifier of a specific subtype
+ *
+ * @return mixed
+ */
+ public static function createInstance($name, $key)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (!self::has($name, $key)) {
+ return null;
+ }
+
+ if (isset(self::$instances[$name][$key])) {
+ return self::$instances[$name][$key];
+ }
+
+ $class = self::$hooks[$name][$key][0];
+
+ if (! class_exists($class)) {
+ throw new ProgrammingError(
+ 'Erraneous hook implementation, class "%s" does not exist',
+ $class
+ );
+ }
+ try {
+ $instance = new $class();
+ } catch (Exception $e) {
+ Logger::debug(
+ 'Hook "%s" (%s) (%s) failed, will be unloaded: %s',
+ $name,
+ $key,
+ $class,
+ $e->getMessage()
+ );
+ // TODO: Persist unloading for "some time" or "current session"
+ unset(self::$hooks[$name][$key]);
+ return null;
+ }
+
+ self::assertValidHook($instance, $name);
+ self::$instances[$name][$key] = $instance;
+ return $instance;
+ }
+
+ protected static function splitHookName($name)
+ {
+ $sep = '\\';
+ if (false === $module = strpos($name, $sep)) {
+ return array(null, $name);
+ }
+ return array(
+ substr($name, 0, $module),
+ substr($name, $module + 1)
+ );
+ }
+
+ /**
+ * Extract the Icinga module name from a given namespaced class name
+ *
+ * Does no validation, prefix must have been checked before
+ *
+ * Shameless copy of ClassLoader::extractModuleName()
+ *
+ * @param string $class The hook's class path
+ *
+ * @return string
+ */
+ protected static function extractModuleName($class)
+ {
+ return lcfirst(
+ substr(
+ $class,
+ ClassLoader::MODULE_PREFIX_LENGTH,
+ strpos(
+ $class,
+ ClassLoader::NAMESPACE_SEPARATOR,
+ ClassLoader::MODULE_PREFIX_LENGTH + 1
+ ) - ClassLoader::MODULE_PREFIX_LENGTH
+ )
+ );
+ }
+
+ /**
+ * Return whether the user has the permission to access the module which provides the given hook
+ *
+ * @param string $class The hook's class path
+ *
+ * @return bool
+ */
+ protected static function hasPermission($class)
+ {
+ if (Icinga::app()->isCli()) {
+ return true;
+ }
+
+ return Auth::getInstance()->hasPermission(
+ Manager::MODULE_PERMISSION_NS . self::extractModuleName($class)
+ );
+ }
+
+ /**
+ * Test for a valid class name
+ *
+ * @param mixed $instance
+ * @param string $name
+ *
+ * @throws ProgrammingError
+ */
+ private static function assertValidHook($instance, $name)
+ {
+ $name = self::normalizeHookName($name);
+
+ $suffix = self::$classSuffix; // 'Hook'
+ $base = self::$BASE_NS; // 'Icinga\\Web\\Hook\\'
+
+ list($module, $name) = self::splitHookName($name);
+
+ if ($module === null) {
+ $base_class = $base . ucfirst($name) . 'Hook';
+
+ // I'm unsure whether this makes sense. Unused and Wrong.
+ if (strpos($base_class, $suffix) === false) {
+ $base_class .= $suffix;
+ }
+ } else {
+ $base_class = 'Icinga\\Module\\'
+ . ucfirst($module)
+ . '\\Hook\\'
+ . ucfirst($name)
+ . $suffix;
+ }
+
+ if (!$instance instanceof $base_class) {
+ // This is a compatibility check. Should be removed one far day:
+ if ($module !== null) {
+ $compat_class = 'Icinga\\Module\\'
+ . ucfirst($module)
+ . '\\Web\\Hook\\'
+ . ucfirst($name)
+ . $suffix;
+
+ if ($instance instanceof $compat_class) {
+ return;
+ }
+ }
+
+ throw new ProgrammingError(
+ '%s is not an instance of %s',
+ get_class($instance),
+ $base_class
+ );
+ }
+ }
+
+ /**
+ * Return all instances of a specific name
+ *
+ * @param string $name One of the predefined hook names
+ *
+ * @return array
+ */
+ public static function all($name)
+ {
+ $name = self::normalizeHookName($name);
+ if (! self::has($name)) {
+ return array();
+ }
+
+ foreach (self::$hooks[$name] as $key => $hook) {
+ list($class, $alwaysRun) = $hook;
+ if ($alwaysRun || self::hasPermission($class)) {
+ if (self::createInstance($name, $key) === null) {
+ return array();
+ }
+ }
+ }
+
+ return isset(self::$instances[$name]) ? self::$instances[$name] : array();
+ }
+
+ /**
+ * Get the first hook
+ *
+ * @param string $name One of the predefined hook names
+ *
+ * @return null|mixed
+ */
+ public static function first($name)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (self::has($name)) {
+ foreach (self::$hooks[$name] as $key => $hook) {
+ list($class, $alwaysRun) = $hook;
+ if ($alwaysRun || self::hasPermission($class)) {
+ return self::createInstance($name, $key);
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a class
+ *
+ * @param string $name One of the predefined hook names
+ * @param string $key The identifier of a specific subtype
+ * @param string $class Your class name, must inherit one of the
+ * classes in the Icinga/Application/Hook folder
+ * @param bool $alwaysRun To run the hook always (e.g. without permission check)
+ */
+ public static function register($name, $key, $class, $alwaysRun = false)
+ {
+ $name = self::normalizeHookName($name);
+
+ if (!isset(self::$hooks[$name])) {
+ self::$hooks[$name] = array();
+ }
+
+ $class = ltrim($class, ClassLoader::NAMESPACE_SEPARATOR);
+
+ self::$hooks[$name][$key] = [$class, $alwaysRun];
+ }
+}
diff --git a/library/Icinga/Application/Hook/ApplicationStateHook.php b/library/Icinga/Application/Hook/ApplicationStateHook.php
new file mode 100644
index 0000000..be973fe
--- /dev/null
+++ b/library/Icinga/Application/Hook/ApplicationStateHook.php
@@ -0,0 +1,90 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use Icinga\Application\Hook;
+use Icinga\Application\Logger;
+
+/**
+ * Application state hook base class
+ */
+abstract class ApplicationStateHook
+{
+ const ERROR = 'error';
+
+ private $messages = [];
+
+ final public function hasMessages()
+ {
+ return ! empty($this->messages);
+ }
+
+ final public function getMessages()
+ {
+ return $this->messages;
+ }
+
+ /**
+ * Add an error message
+ *
+ * The timestamp of the message is used for deduplication and thus must refer to the time when the error first
+ * occurred. Don't use {@link time()} here!
+ *
+ * @param string $id ID of the message. The ID must be prefixed with the module name
+ * @param int $timestamp Timestamp when the error first occurred
+ * @param string $message Error message
+ *
+ * @return $this
+ */
+ final public function addError($id, $timestamp, $message)
+ {
+ $id = trim($id);
+ $timestamp = (int) $timestamp;
+
+ if (! strlen($id)) {
+ throw new \InvalidArgumentException('ID expected.');
+ }
+
+ if (! $timestamp) {
+ throw new \InvalidArgumentException('Timestamp expected.');
+ }
+
+ $this->messages[sha1($id . $timestamp)] = [self::ERROR, $timestamp, $message];
+
+ return $this;
+ }
+
+ /**
+ * Override this method in order to provide application state messages
+ */
+ abstract public function collectMessages();
+
+ final public static function getAllMessages()
+ {
+ $messages = [];
+
+ if (! Hook::has('ApplicationState')) {
+ return $messages;
+ }
+
+ foreach (Hook::all('ApplicationState') as $hook) {
+ /** @var self $hook */
+ try {
+ $hook->collectMessages();
+ } catch (\Exception $e) {
+ Logger::error(
+ "Failed to collect messages from hook '%s'. An error occurred: %s",
+ get_class($hook),
+ $e
+ );
+ }
+
+ if ($hook->hasMessages()) {
+ $messages += $hook->getMessages();
+ }
+ }
+
+ return $messages;
+ }
+}
diff --git a/library/Icinga/Application/Hook/AuditHook.php b/library/Icinga/Application/Hook/AuditHook.php
new file mode 100644
index 0000000..e6209da
--- /dev/null
+++ b/library/Icinga/Application/Hook/AuditHook.php
@@ -0,0 +1,123 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use Exception;
+use InvalidArgumentException;
+use Icinga\Authentication\Auth;
+use Icinga\Application\Hook;
+use Icinga\Application\Logger;
+
+abstract class AuditHook
+{
+ /**
+ * Log an activity to the audit log
+ *
+ * Propagates the given message details to all known hook implementations.
+ *
+ * @param string $type An arbitrary name identifying the type of activity
+ * @param string $message A detailed description possibly referencing parameters in $data
+ * @param array $data Additional information (How this is stored or used is up to each implementation)
+ * @param string $identity An arbitrary name identifying the responsible subject, defaults to the current user
+ * @param int $time A timestamp defining when the activity occurred, defaults to now
+ */
+ public static function logActivity($type, $message, array $data = null, $identity = null, $time = null)
+ {
+ if (! Hook::has('audit')) {
+ return;
+ }
+
+ if ($identity === null) {
+ $identity = Auth::getInstance()->getUser()->getUsername();
+ }
+
+ if ($time === null) {
+ $time = time();
+ }
+
+ foreach (Hook::all('audit') as $hook) {
+ /** @var self $hook */
+ try {
+ $formattedMessage = $message;
+ if ($data !== null) {
+ // Calling formatMessage on each hook is intended and allows
+ // intercepting message formatting while keeping it implicit
+ $formattedMessage = $hook->formatMessage($message, $data);
+ }
+
+ $hook->logMessage($time, $identity, $type, $formattedMessage, $data);
+ } catch (Exception $e) {
+ Logger::error(
+ 'Failed to propagate audit message to hook "%s". An error occurred: %s',
+ get_class($hook),
+ $e
+ );
+ }
+ }
+ }
+
+ /**
+ * Log a message to the audit log
+ *
+ * @param int $time A timestamp defining when the activity occurred
+ * @param string $identity An arbitrary name identifying the responsible subject
+ * @param string $type An arbitrary name identifying the type of activity
+ * @param string $message A detailed description of the activity
+ * @param array $data Additional activity information
+ */
+ abstract public function logMessage($time, $identity, $type, $message, array $data = null);
+
+ /**
+ * Substitute the given message with its accompanying data
+ *
+ * @param string $message
+ * @param array $messageData
+ *
+ * @return string
+ */
+ public function formatMessage($message, array $messageData)
+ {
+ return preg_replace_callback('/{{(.+?)}}/', function ($match) use ($messageData) {
+ return $this->extractMessageValue(explode('.', $match[1]), $messageData);
+ }, $message);
+ }
+
+ /**
+ * Extract the given value path from the given message data
+ *
+ * @param array $path
+ * @param array $messageData
+ *
+ * @return mixed
+ *
+ * @throws InvalidArgumentException In case of an invalid or missing format parameter
+ */
+ protected function extractMessageValue(array $path, array $messageData)
+ {
+ $key = array_shift($path);
+ if (array_key_exists($key, $messageData)) {
+ $value = $messageData[$key];
+ } else {
+ throw new InvalidArgumentException("Missing format parameter '$key'");
+ }
+
+ if (empty($path)) {
+ if (! is_scalar($value)) {
+ throw new InvalidArgumentException(
+ 'Invalid format parameter. Expected scalar for path "' . join('.', $path) . '".'
+ . ' Got "' . gettype($value) . '" instead'
+ );
+ }
+
+ return $value;
+ } elseif (! is_array($value)) {
+ throw new InvalidArgumentException(
+ 'Invalid format parameter. Expected array for path "'. join('.', $path) . '".'
+ . ' Got "' . gettype($value) . '" instead'
+ );
+ }
+
+ return $this->extractMessageValue($path, $value);
+ }
+}
diff --git a/library/Icinga/Application/Hook/AuthenticationHook.php b/library/Icinga/Application/Hook/AuthenticationHook.php
new file mode 100644
index 0000000..41cc661
--- /dev/null
+++ b/library/Icinga/Application/Hook/AuthenticationHook.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Icinga\Application\Hook;
+
+use Icinga\User;
+use Icinga\Web\Hook;
+use Icinga\Application\Logger;
+
+/**
+ * Icinga Web Authentication Hook base class
+ *
+ * This hook can be used to authenticate the user in a third party application.
+ * Extend this class if you want to perform arbitrary actions during the login and logout.
+ */
+abstract class AuthenticationHook
+{
+ /**
+ * Name of the hook
+ */
+ const NAME = 'authentication';
+
+ /**
+ * Triggered after login in Icinga Web and when calling login action even if already authenticated in Icinga Web
+ *
+ * @param User $user
+ */
+ public function onLogin(User $user)
+ {
+ }
+
+ /**
+ * Triggered before logout from Icinga Web
+ *
+ * @param User $user
+ */
+ public function onLogout(User $user)
+ {
+ }
+
+ /**
+ * Call the onLogin() method of all registered AuthHook(s)
+ *
+ * @param User $user
+ */
+ public static function triggerLogin(User $user)
+ {
+ /** @var AuthenticationHook $hook */
+ foreach (Hook::all(self::NAME) as $hook) {
+ try {
+ $hook->onLogin($user);
+ } catch (\Exception $e) {
+ // Avoid error propagation if login failed in third party application
+ Logger::error($e);
+ }
+ }
+ }
+
+ /**
+ * Call the onLogout() method of all registered AuthHook(s)
+ *
+ * @param User $user
+ */
+ public static function triggerLogout(User $user)
+ {
+ /** @var AuthenticationHook $hook */
+ foreach (Hook::all(self::NAME) as $hook) {
+ try {
+ $hook->onLogout($user);
+ } catch (\Exception $e) {
+ // Avoid error propagation if login failed in third party application
+ Logger::error($e);
+ }
+ }
+ }
+}
diff --git a/library/Icinga/Application/Hook/ConfigFormEventsHook.php b/library/Icinga/Application/Hook/ConfigFormEventsHook.php
new file mode 100644
index 0000000..aa0cadc
--- /dev/null
+++ b/library/Icinga/Application/Hook/ConfigFormEventsHook.php
@@ -0,0 +1,137 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use Icinga\Application\Hook;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Web\Form;
+
+/**
+ * Base class for config form event hooks
+ */
+abstract class ConfigFormEventsHook
+{
+ /** @var array Array of errors found while processing the form event hooks */
+ private static $lastErrors = [];
+
+ /**
+ * Get whether the hook applies to the given config form
+ *
+ * @param Form $form
+ *
+ * @return bool
+ */
+ public function appliesTo(Form $form)
+ {
+ return false;
+ }
+
+ /**
+ * isValid event hook
+ *
+ * Implement this method in order to run code after the form has been validated successfully.
+ * Throw an exception here if either the form is not valid or you want interrupt the form handling.
+ * The exception's message will be automatically added as form error message so that it will be
+ * displayed in the frontend.
+ *
+ * @param Form $form
+ *
+ * @throws \Exception If either the form is not valid or to interrupt the form handling
+ */
+ public function isValid(Form $form)
+ {
+ }
+
+ /**
+ * onSuccess event hook
+ *
+ * Implement this method in order to run code after the configuration form has been stored successfully.
+ * You can't interrupt the form handling here. Any exception will be caught, logged and notified.
+ *
+ * @param Form $form
+ */
+ public function onSuccess(Form $form)
+ {
+ }
+
+ /**
+ * Get an array of errors found while processing the form event hooks
+ *
+ * @return array
+ */
+ final public static function getLastErrors()
+ {
+ return static::$lastErrors;
+ }
+
+ /**
+ * Run all isValid hooks
+ *
+ * @param Form $form
+ *
+ * @return bool Returns false if any hook threw an exception
+ */
+ final public static function runIsValid(Form $form)
+ {
+ return self::runEventMethod('isValid', $form);
+ }
+
+ /**
+ * Run all onSuccess hooks
+ *
+ * @param Form $form
+ *
+ * @return bool Returns false if any hook threw an exception
+ */
+ final public static function runOnSuccess(Form $form)
+ {
+ return self::runEventMethod('onSuccess', $form);
+ }
+
+ private static function runEventMethod($eventMethod, Form $form)
+ {
+ static::$lastErrors = [];
+
+ if (! Hook::has('ConfigFormEvents')) {
+ return true;
+ }
+
+ $success = true;
+
+ foreach (Hook::all('ConfigFormEvents') as $hook) {
+ /** @var self $hook */
+ if (! $hook->runAppliesTo($form)) {
+ continue;
+ }
+
+ try {
+ $hook->$eventMethod($form);
+ } catch (\Exception $e) {
+ static::$lastErrors[] = $e->getMessage();
+
+ Logger::error("%s\n%s", $e, IcingaException::getConfidentialTraceAsString($e));
+
+ $success = false;
+ }
+ }
+
+ return $success;
+ }
+
+ private function runAppliesTo(Form $form)
+ {
+ try {
+ $appliesTo = $this->appliesTo($form);
+ } catch (\Exception $e) {
+ // Don't save exception to last errors because we do not want to disturb the user for messed up
+ // appliesTo checks
+ Logger::error("%s\n%s", $e, IcingaException::getConfidentialTraceAsString($e));
+
+ $appliesTo = false;
+ }
+
+ return $appliesTo === true;
+ }
+}
diff --git a/library/Icinga/Application/Hook/GrapherHook.php b/library/Icinga/Application/Hook/GrapherHook.php
new file mode 100644
index 0000000..dfb2135
--- /dev/null
+++ b/library/Icinga/Application/Hook/GrapherHook.php
@@ -0,0 +1,111 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Icinga Web Grapher Hook base class
+ *
+ * Extend this class if you want to integrate your graphing solution nicely into
+ * Icinga Web.
+ */
+abstract class GrapherHook extends WebBaseHook
+{
+ /**
+ * Whether this grapher provides previews
+ *
+ * @var bool
+ */
+ protected $hasPreviews = false;
+
+ /**
+ * Whether this grapher provides tiny previews
+ *
+ * @var bool
+ */
+ protected $hasTinyPreviews = false;
+
+ /**
+ * Constructor must live without arguments right now
+ *
+ * Therefore the constructor is final, we might change our opinion about
+ * this one far day
+ */
+ final public function __construct()
+ {
+ $this->init();
+ }
+
+ /**
+ * Overwrite this function if you want to do some initialization stuff
+ *
+ * @return void
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Whether this grapher provides previews
+ *
+ * @return bool
+ */
+ public function hasPreviews()
+ {
+ return $this->hasPreviews;
+ }
+
+ /**
+ * Whether this grapher provides tiny previews
+ *
+ * @return bool
+ */
+ public function hasTinyPreviews()
+ {
+ return $this->hasTinyPreviews;
+ }
+
+ /**
+ * Whether a graph for the monitoring object exist
+ *
+ * @param MonitoredObject $object
+ *
+ * @return bool
+ */
+ abstract public function has(MonitoredObject $object);
+
+ /**
+ * Get a preview for the given object
+ *
+ * This function must return an empty string if no graph exists.
+ *
+ * @param MonitoredObject $object
+ *
+ * @return string
+ * @throws ProgrammingError
+ *
+ */
+ public function getPreviewHtml(MonitoredObject $object)
+ {
+ throw new ProgrammingError('This hook provide previews but it is not implemented');
+ }
+
+
+ /**
+ * Get a tiny preview for the given object
+ *
+ * This function must return an empty string if no graph exists.
+ *
+ * @param MonitoredObject $object
+ *
+ * @return string
+ * @throws ProgrammingError
+ */
+ public function getTinyPreviewHtml(MonitoredObject $object)
+ {
+ throw new ProgrammingError('This hook provide tiny previews but it is not implemented');
+ }
+}
diff --git a/library/Icinga/Application/Hook/HealthHook.php b/library/Icinga/Application/Hook/HealthHook.php
new file mode 100644
index 0000000..f6420b5
--- /dev/null
+++ b/library/Icinga/Application/Hook/HealthHook.php
@@ -0,0 +1,222 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use Exception;
+use Icinga\Application\Hook;
+use Icinga\Application\Logger;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Exception\IcingaException;
+use ipl\Web\Url;
+use LogicException;
+
+abstract class HealthHook
+{
+ /** @var int */
+ const STATE_OK = 0;
+
+ /** @var int */
+ const STATE_WARNING = 1;
+
+ /** @var int */
+ const STATE_CRITICAL = 2;
+
+ /** @var int */
+ const STATE_UNKNOWN = 3;
+
+ /** @var int The overall state */
+ protected $state;
+
+ /** @var string Message describing the overall state */
+ protected $message;
+
+ /** @var array Available metrics */
+ protected $metrics;
+
+ /** @var Url Url to a graphical representation of the available metrics */
+ protected $url;
+
+ /**
+ * Get overall state
+ *
+ * @return int
+ */
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ /**
+ * Set overall state
+ *
+ * @param int $state
+ *
+ * @return $this
+ */
+ public function setState($state)
+ {
+ $this->state = $state;
+
+ return $this;
+ }
+
+ /**
+ * Get the message describing the overall state
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Set the message describing the overall state
+ *
+ * @param string $message
+ *
+ * @return $this
+ */
+ public function setMessage($message)
+ {
+ $this->message = $message;
+
+ return $this;
+ }
+
+ /**
+ * Get available metrics
+ *
+ * @return array
+ */
+ public function getMetrics()
+ {
+ return $this->metrics;
+ }
+
+ /**
+ * Set available metrics
+ *
+ * @param array $metrics
+ *
+ * @return $this
+ */
+ public function setMetrics(array $metrics)
+ {
+ $this->metrics = $metrics;
+
+ return $this;
+ }
+
+ /**
+ * Get the url to a graphical representation of the available metrics
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set the url to a graphical representation of the available metrics
+ *
+ * @param Url $url
+ *
+ * @return $this
+ */
+ public function setUrl(Url $url)
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * Collect available health data from hooks
+ *
+ * @return ArrayDatasource
+ */
+ final public static function collectHealthData()
+ {
+ $checks = [];
+ foreach (Hook::all('health') as $hook) {
+ /** @var self $hook */
+
+ try {
+ $hook->checkHealth();
+ $url = $hook->getUrl();
+ $state = $hook->getState();
+ $message = $hook->getMessage();
+ $metrics = $hook->getMetrics();
+ } catch (Exception $e) {
+ Logger::error('Failed to check health: %s', $e);
+
+ $state = self::STATE_UNKNOWN;
+ $message = IcingaException::describe($e);
+ $metrics = null;
+ $url = null;
+ }
+
+ $checks[] = (object) [
+ 'module' => $hook->getModuleName(),
+ 'name' => $hook->getName(),
+ 'url' => $url ? $url->getAbsoluteUrl() : null,
+ 'state' => $state,
+ 'message' => $message,
+ 'metrics' => (object) $metrics
+ ];
+ }
+
+ return (new ArrayDatasource($checks))
+ ->setKeyColumn('name');
+ }
+
+ /**
+ * Get the name of the hook
+ *
+ * Only used in API responses to differentiate it from other hooks of the same module.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ $classPath = get_class($this);
+ $parts = explode('\\', $classPath);
+ $className = array_pop($parts);
+
+ if (substr($className, -4) === 'Hook') {
+ $className = substr($className, 1, -4);
+ }
+
+ return strtolower($className[0]) . substr($className, 1);
+ }
+
+ /**
+ * Get the name of the module providing this hook
+ *
+ * @return string
+ *
+ * @throws LogicException
+ */
+ public function getModuleName()
+ {
+ $classPath = get_class($this);
+ if (substr($classPath, 0, 14) !== 'Icinga\\Module\\') {
+ throw new LogicException('Not a module hook');
+ }
+
+ $withoutPrefix = substr($classPath, 14);
+ return strtolower(substr($withoutPrefix, 0, strpos($withoutPrefix, '\\')));
+ }
+
+ /**
+ * Check health
+ *
+ * Implement this method and set the overall state, message, url and metrics.
+ *
+ * @return void
+ */
+ abstract public function checkHealth();
+}
diff --git a/library/Icinga/Application/Hook/PdfexportHook.php b/library/Icinga/Application/Hook/PdfexportHook.php
new file mode 100644
index 0000000..36e9f51
--- /dev/null
+++ b/library/Icinga/Application/Hook/PdfexportHook.php
@@ -0,0 +1,25 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+/**
+ * Base class for the PDF Export Hook
+ */
+abstract class PdfexportHook
+{
+ /**
+ * Get whether PDF export is supported
+ *
+ * @return bool
+ */
+ abstract public function isSupported();
+
+ /**
+ * Render the specified HTML to PDF and stream it to the client
+ *
+ * @param string $html The HTML to render to PDF
+ * @param string $filename The filename for the generated PDF
+ */
+ abstract public function streamPdfFromHtml($html, $filename);
+}
diff --git a/library/Icinga/Application/Hook/ThemeLoaderHook.php b/library/Icinga/Application/Hook/ThemeLoaderHook.php
new file mode 100644
index 0000000..5320dd5
--- /dev/null
+++ b/library/Icinga/Application/Hook/ThemeLoaderHook.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+/**
+ * Provide an implementation of this hook to dynamically provide themes.
+ * Note that only the first registered hook is utilized. Also note that
+ * for ordinary themes this hook is not required. Place such in your
+ * module's theme path: <module-path>/public/css/themes
+ */
+abstract class ThemeLoaderHook
+{
+ /**
+ * Get the path for the given theme
+ *
+ * @param ?string $theme
+ *
+ * @return ?string The path or NULL if the theme is unknown
+ */
+ abstract public function getThemeFile(?string $theme): ?string;
+}
diff --git a/library/Icinga/Application/Hook/Ticket/TicketPattern.php b/library/Icinga/Application/Hook/Ticket/TicketPattern.php
new file mode 100644
index 0000000..e37fcc1
--- /dev/null
+++ b/library/Icinga/Application/Hook/Ticket/TicketPattern.php
@@ -0,0 +1,140 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook\Ticket;
+
+use ArrayAccess;
+
+/**
+ * A ticket pattern
+ *
+ * This class should be used by modules which provide implementations for the Web 2 ticket hook.
+ * Have a look at the GenericTTS module for a possible use case.
+ */
+class TicketPattern implements ArrayAccess
+{
+ /**
+ * The result of a performed ticket match
+ *
+ * @var array
+ */
+ protected $match = array();
+
+ /**
+ * The name of the TTS integration
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The ticket pattern
+ *
+ * @var string
+ */
+ protected $pattern;
+
+ public function offsetExists($offset): bool
+ {
+ return isset($this->match[$offset]);
+ }
+
+ public function offsetGet($offset): ?string
+ {
+ return array_key_exists($offset, $this->match) ? $this->match[$offset] : null;
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ if ($offset === null) {
+ $this->match[] = $value;
+ } else {
+ $this->match[$offset] = $value;
+ }
+ }
+
+ public function offsetUnset($offset): void
+ {
+ unset($this->match[$offset]);
+ }
+
+
+ /**
+ * Get the result of a performed ticket match
+ *
+ * @return array
+ */
+ public function getMatch()
+ {
+ return $this->match;
+ }
+
+ /**
+ * Set the result of a performed ticket match
+ *
+ * @param array $match
+ *
+ * @return $this
+ */
+ public function setMatch(array $match)
+ {
+ $this->match = $match;
+ return $this;
+ }
+
+ /**
+ * Get the name of the TTS integration
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the name of the TTS integration
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Get the ticket pattern
+ *
+ * @return string
+ */
+ public function getPattern()
+ {
+ return $this->pattern;
+ }
+
+ /**
+ * Set the ticket pattern
+ *
+ * @param string $pattern
+ *
+ * @return $this
+ */
+ public function setPattern($pattern)
+ {
+ $this->pattern = $pattern;
+ return $this;
+ }
+
+ /**
+ * Whether the integration is properly configured, i.e. the pattern and the URL are not empty
+ *
+ * @return bool
+ */
+ public function isValid()
+ {
+ return ! empty($this->pattern);
+ }
+}
diff --git a/library/Icinga/Application/Hook/TicketHook.php b/library/Icinga/Application/Hook/TicketHook.php
new file mode 100644
index 0000000..ceb3738
--- /dev/null
+++ b/library/Icinga/Application/Hook/TicketHook.php
@@ -0,0 +1,210 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use ArrayIterator;
+use ErrorException;
+use Exception;
+use Icinga\Application\Hook\Ticket\TicketPattern;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+
+/**
+ * Base class for ticket hooks
+ *
+ * Extend this class if you want to integrate your ticketing solution into Icinga Web 2.
+ */
+abstract class TicketHook
+{
+ /**
+ * Last error, if any
+ *
+ * @var string|null
+ */
+ protected $lastError;
+
+ /**
+ * Create a new ticket hook
+ *
+ * @see init() For hook initialization.
+ */
+ final public function __construct()
+ {
+ $this->init();
+ }
+
+ /**
+ * Overwrite this function for hook initialization, e.g. loading the hook's config
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Create a link for each matched element in the subject text
+ *
+ * @param array|TicketPattern $match Matched element according to {@link getPattern()}
+ *
+ * @return string Replacement string
+ */
+ abstract public function createLink($match);
+
+ /**
+ * Get the pattern(s) to search for
+ *
+ * Return an array of TicketPattern instances here to support multiple TTS integrations.
+ *
+ * @return string|TicketPattern[]
+ */
+ abstract public function getPattern();
+
+ /**
+ * Apply ticket patterns to the given text
+ *
+ * @param string $text
+ * @param TicketPattern[] $ticketPatterns
+ *
+ * @return string
+ */
+ private function applyTicketPatterns($text, array $ticketPatterns)
+ {
+ $out = '';
+ $start = 0;
+
+ $iterator = new ArrayIterator($ticketPatterns);
+ $iterator->rewind();
+
+ while ($iterator->valid()) {
+ $ticketPattern = $iterator->current();
+
+ try {
+ preg_match($ticketPattern->getPattern(), $text, $match, PREG_OFFSET_CAPTURE, $start);
+ } catch (ErrorException $e) {
+ $this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e));
+ $iterator->next();
+ continue;
+ }
+
+ if (empty($match)) {
+ $iterator->next();
+ continue;
+ }
+
+ // Remove preg_offset from match for the ticket pattern
+ $carry = array();
+ array_walk($match, function ($value, $key) use (&$carry) {
+ $carry[$key] = $value[0];
+ }, $carry);
+ $ticketPattern->setMatch($carry);
+
+ $offsetLeft = $match[0][1];
+ $matchLength = strlen($match[0][0]);
+
+ $out .= substr($text, $start, $offsetLeft - $start);
+
+ try {
+ $out .= $this->createLink($ticketPattern);
+ } catch (Exception $e) {
+ $this->fail('Can\'t create ticket links: %s', IcingaException::describe($e));
+ return $text;
+ }
+
+ $start = $offsetLeft + $matchLength;
+ }
+
+ $out .= substr($text, $start);
+
+ return $out;
+ }
+
+ /**
+ * Helper function to create a TicketPattern instance
+ *
+ * @param string $name Name of the TTS integration
+ * @param string $pattern Ticket pattern
+ *
+ * @return TicketPattern
+ */
+ protected function createTicketPattern($name, $pattern)
+ {
+ $ticketPattern = new TicketPattern();
+ $ticketPattern
+ ->setName($name)
+ ->setPattern($pattern);
+ return $ticketPattern;
+ }
+
+ /**
+ * Set the hook as failed w/ the given message
+ *
+ * @param string $message Error message or error format string
+ * @param mixed ...$arg Format string argument
+ */
+ private function fail($message)
+ {
+ $args = array_slice(func_get_args(), 1);
+ $lastError = vsprintf($message, $args);
+ Logger::debug($lastError);
+ $this->lastError = $lastError;
+ }
+
+ /**
+ * Get the last error, if any
+ *
+ * @return string|null
+ */
+ public function getLastError()
+ {
+ return $this->lastError;
+ }
+
+ /**
+ * Create links w/ {@link createLink()} in the given text that matches to the subject from {@link getPattern()}
+ *
+ * In case of errors a debug message is recorded to the log and any subsequent call to {@link createLinks()} will
+ * be a no-op.
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ final public function createLinks($text)
+ {
+ if ($this->lastError !== null) {
+ return $text;
+ }
+
+ try {
+ $pattern = $this->getPattern();
+ } catch (Exception $e) {
+ $this->fail('Can\'t create ticket links: Retrieving the pattern failed: %s', IcingaException::describe($e));
+ return $text;
+ }
+
+ if (empty($pattern)) {
+ $this->fail('Can\'t create ticket links: Pattern is empty');
+ return $text;
+ }
+
+ if (is_array($pattern)) {
+ $text = $this->applyTicketPatterns($text, $pattern);
+ } else {
+ try {
+ $text = preg_replace_callback(
+ $pattern,
+ array($this, 'createLink'),
+ $text
+ );
+ } catch (ErrorException $e) {
+ $this->fail('Can\'t create ticket links: Pattern is invalid: %s', IcingaException::describe($e));
+ return $text;
+ } catch (Exception $e) {
+ $this->fail('Can\'t create ticket links: %s', IcingaException::describe($e));
+ return $text;
+ }
+ }
+
+ return $text;
+ }
+}
diff --git a/library/Icinga/Application/Hook/WebBaseHook.php b/library/Icinga/Application/Hook/WebBaseHook.php
new file mode 100644
index 0000000..09e8f4f
--- /dev/null
+++ b/library/Icinga/Application/Hook/WebBaseHook.php
@@ -0,0 +1,54 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Hook;
+
+use Zend_Controller_Action_HelperBroker;
+use Zend_View;
+
+/**
+ * Base class for web hooks
+ *
+ * The class provides access to the view
+ */
+class WebBaseHook
+{
+ /**
+ * View instance
+ *
+ * @var Zend_View
+ */
+ private $view;
+
+ /**
+ * Set the view instance
+ *
+ * @param Zend_View $view
+ *
+ * @return $this
+ */
+ public function setView(Zend_View $view)
+ {
+ $this->view = $view;
+
+ return $this;
+ }
+
+ /**
+ * Get the view instance
+ *
+ * @return Zend_View
+ */
+ public function getView()
+ {
+ if ($this->view === null) {
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ if ($viewRenderer->view === null) {
+ $viewRenderer->initView();
+ }
+ $this->view = $viewRenderer->view;
+ }
+
+ return $this->view;
+ }
+}
diff --git a/library/Icinga/Application/Icinga.php b/library/Icinga/Application/Icinga.php
new file mode 100644
index 0000000..ba54015
--- /dev/null
+++ b/library/Icinga/Application/Icinga.php
@@ -0,0 +1,49 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Icinga application container
+ */
+class Icinga
+{
+ /**
+ * @var ApplicationBootstrap
+ */
+ private static $app;
+
+ /**
+ * Getter for an application environment
+ *
+ * @return ApplicationBootstrap|Web
+ * @throws ProgrammingError
+ */
+ public static function app()
+ {
+ if (self::$app == null) {
+ throw new ProgrammingError('Icinga has never been started');
+ }
+
+ return self::$app;
+ }
+
+ /**
+ * Setter for an application environment
+ *
+ * @param ApplicationBootstrap $app
+ * @param bool $overwrite
+ *
+ * @throws ProgrammingError
+ */
+ public static function setApp(ApplicationBootstrap $app, $overwrite = false)
+ {
+ if (self::$app !== null && !$overwrite) {
+ throw new ProgrammingError('Cannot start Icinga twice');
+ }
+
+ self::$app = $app;
+ }
+}
diff --git a/library/Icinga/Application/LegacyWeb.php b/library/Icinga/Application/LegacyWeb.php
new file mode 100644
index 0000000..5673820
--- /dev/null
+++ b/library/Icinga/Application/LegacyWeb.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+require_once dirname(__FILE__) . '/Web.php';
+use Icinga\Exception\ProgrammingError;
+
+class LegacyWeb extends Web
+{
+ // IcingaWeb 1.x base dir
+ protected $legacyBasedir;
+
+ protected function bootstrap()
+ {
+ parent::bootstrap();
+ throw new ProgrammingError('Not yet');
+ // $this->setupIcingaLegacyWrapper();
+ }
+
+ /**
+ * Get the Icinga-Web 1.x base path
+ *
+ * @throws Exception
+ * @return self
+ */
+ public function getLecacyBasedir()
+ {
+ return $this->legacyBasedir;
+ }
+}
diff --git a/library/Icinga/Application/Libraries.php b/library/Icinga/Application/Libraries.php
new file mode 100644
index 0000000..8e4a79d
--- /dev/null
+++ b/library/Icinga/Application/Libraries.php
@@ -0,0 +1,91 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Application;
+
+use ArrayIterator;
+use IteratorAggregate;
+use Icinga\Application\Libraries\Library;
+use Traversable;
+
+class Libraries implements IteratorAggregate
+{
+ /** @var Library[] */
+ protected $libraries = [];
+
+ /**
+ * Iterate over registered libraries
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->libraries);
+ }
+
+ /**
+ * Register a library from the given path
+ *
+ * @param string $path
+ *
+ * @return Library The registered library
+ */
+ public function registerPath($path)
+ {
+ $library = new Library($path);
+ $this->libraries[] = $library;
+
+ return $library;
+ }
+
+ /**
+ * Check if a library with the given name has been registered
+ *
+ * Passing a version constraint also verifies that the library's version matches.
+ *
+ * @param string $name
+ * @param string $version
+ *
+ * @return bool
+ */
+ public function has($name, $version = null)
+ {
+ $library = $this->get($name);
+ if ($library === null) {
+ return false;
+ } elseif ($version === null || $version === true) {
+ return true;
+ }
+
+ $operator = '=';
+ if (preg_match('/^([<>=]{1,2})\s*v?((?:[\d.]+)(?:\D+)?)$/', $version, $match)) {
+ $operator = $match[1];
+ $version = $match[2];
+ }
+
+ return version_compare($library->getVersion(), $version, $operator);
+ }
+
+ /**
+ * Get a library by name
+ *
+ * @param string $name
+ *
+ * @return Library|null
+ */
+ public function get($name)
+ {
+ $candidate = null;
+ foreach ($this->libraries as $library) {
+ $libraryName = $library->getName();
+ if ($libraryName === $name) {
+ return $library;
+ } elseif (strpos($libraryName, '/') !== false && explode('/', $libraryName)[1] === $name) {
+ // Also return libs which only partially match
+ $candidate = $library;
+ }
+ }
+
+ return $candidate;
+ }
+}
diff --git a/library/Icinga/Application/Libraries/Library.php b/library/Icinga/Application/Libraries/Library.php
new file mode 100644
index 0000000..63e50b2
--- /dev/null
+++ b/library/Icinga/Application/Libraries/Library.php
@@ -0,0 +1,259 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Application\Libraries;
+
+use CallbackFilterIterator;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\Json\JsonDecodeException;
+use Icinga\Util\Json;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+class Library
+{
+ /** @var string */
+ protected $path;
+
+ /** @var string */
+ protected $jsAssetPath;
+
+ /** @var string */
+ protected $cssAssetPath;
+
+ /** @var string */
+ protected $staticAssetPath;
+
+ /** @var string */
+ protected $version;
+
+ /** @var array */
+ protected $metaData;
+
+ /** @var array */
+ protected $assets;
+
+ /**
+ * Create a new Library
+ *
+ * @param string $path
+ */
+ public function __construct($path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Get this library's path
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Get path of this library's JS assets
+ *
+ * @return string
+ */
+ public function getJsAssetPath()
+ {
+ $this->assets();
+ return $this->jsAssetPath;
+ }
+
+ /**
+ * Get path of this library's CSS assets
+ *
+ * @return string
+ */
+ public function getCssAssetPath()
+ {
+ $this->assets();
+ return $this->cssAssetPath;
+ }
+
+ /**
+ * Get path of this library's static assets
+ *
+ * @return string
+ */
+ public function getStaticAssetPath()
+ {
+ $this->assets();
+ return $this->staticAssetPath;
+ }
+
+ /**
+ * Get this library's name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->metaData()['name'];
+ }
+
+ /**
+ * Get this library's version
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ if ($this->version === null) {
+ if (isset($this->metaData()['version'])) {
+ $this->version = trim(ltrim($this->metaData()['version'], 'v'));
+ } else {
+ $versionFile = $this->path . DIRECTORY_SEPARATOR . 'VERSION';
+ if (file_exists($versionFile)) {
+ $this->version = trim(ltrim(file_get_contents($versionFile), 'v'));
+ } else {
+ $this->version = '';
+ }
+ }
+ }
+
+ return $this->version;
+ }
+
+ /**
+ * Check whether the given package is required
+ *
+ * @param string $vendor The vendor of the project
+ * @param string $project The project's name
+ *
+ * @return bool
+ */
+ public function isRequired($vendor, $project)
+ {
+ // Ensure the parts are lowercase and separated by dashes, not capital letters
+ $project = strtolower(join('-', preg_split('/\w(?=[A-Z])/', $project)));
+
+ return isset($this->metaData()['require'][strtolower($vendor) . '/' . $project]);
+ }
+
+ /**
+ * Get this library's JS assets
+ *
+ * @return string[] Asset paths
+ */
+ public function getJsAssets()
+ {
+ return $this->assets()['js'];
+ }
+
+ /**
+ * Get this library's CSS assets
+ *
+ * @return string[] Asset paths
+ */
+ public function getCssAssets()
+ {
+ return $this->assets()['css'];
+ }
+
+ /**
+ * Get this library's static assets
+ *
+ * @return string[] Asset paths
+ */
+ public function getStaticAssets()
+ {
+ return $this->assets()['static'];
+ }
+
+ /**
+ * Register this library's autoloader
+ *
+ * @return void
+ */
+ public function registerAutoloader()
+ {
+ $autoloaderPath = join(DIRECTORY_SEPARATOR, [$this->path, 'vendor', 'autoload.php']);
+ if (file_exists($autoloaderPath)) {
+ require_once $autoloaderPath;
+ }
+ }
+
+ /**
+ * Parse and return this library's metadata
+ *
+ * @return array
+ *
+ * @throws ConfigurationError
+ * @throws JsonDecodeException
+ */
+ protected function metaData()
+ {
+ if ($this->metaData === null) {
+ $metaData = @file_get_contents($this->path . DIRECTORY_SEPARATOR . 'composer.json');
+ if ($metaData === false) {
+ throw new ConfigurationError('Library at "%s" is not a composerized project', $this->path);
+ }
+
+ $this->metaData = Json::decode($metaData, true);
+ }
+
+ return $this->metaData;
+ }
+
+ /**
+ * Register and return this library's assets
+ *
+ * @return array
+ */
+ protected function assets()
+ {
+ if ($this->assets !== null) {
+ return $this->assets;
+ }
+
+ $listAssets = function ($type) {
+ $dir = join(DIRECTORY_SEPARATOR, [$this->path, 'asset', $type]);
+ if (! is_dir($dir)) {
+ return [];
+ }
+
+ $this->{$type . 'AssetPath'} = $dir;
+
+ $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(
+ $dir,
+ RecursiveDirectoryIterator::CURRENT_AS_FILEINFO | RecursiveDirectoryIterator::SKIP_DOTS
+ ));
+ if ($type === 'static') {
+ return $iterator;
+ }
+
+ return new CallbackFilterIterator(
+ $iterator,
+ function ($path) use ($type) {
+ if ($type === 'js' && $path->getExtension() === 'js') {
+ return substr($path->getPathname(), -5 - strlen($type)) !== ".min.$type";
+ } elseif ($type === 'css'
+ && ($path->getExtension() === 'css' || $path->getExtension() === 'less')
+ ) {
+ return substr($path->getPathname(), -5 - strlen($type)) !== ".min.$type";
+ }
+
+ return false;
+ }
+ );
+ };
+
+ $this->assets = [];
+
+ $jsAssets = $listAssets('js');
+ $this->assets['js'] = is_array($jsAssets) ? $jsAssets : iterator_to_array($jsAssets);
+
+ $cssAssets = $listAssets('css');
+ $this->assets['css'] = is_array($cssAssets) ? $cssAssets : iterator_to_array($cssAssets);
+
+ $staticAssets = $listAssets('static');
+ $this->assets['static'] = is_array($staticAssets) ? $staticAssets : iterator_to_array($staticAssets);
+
+ return $this->assets;
+ }
+}
diff --git a/library/Icinga/Application/Logger.php b/library/Icinga/Application/Logger.php
new file mode 100644
index 0000000..347af44
--- /dev/null
+++ b/library/Icinga/Application/Logger.php
@@ -0,0 +1,349 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Exception;
+use Icinga\Data\ConfigObject;
+use Icinga\Application\Logger\Writer\FileWriter;
+use Icinga\Application\Logger\Writer\SyslogWriter;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Util\Json;
+
+/**
+ * Logger
+ */
+class Logger
+{
+ /**
+ * Debug message
+ */
+ const DEBUG = 1;
+
+ /**
+ * Informational message
+ */
+ const INFO = 2;
+
+ /**
+ * Warning message
+ */
+ const WARNING = 4;
+
+ /**
+ * Error message
+ */
+ const ERROR = 8;
+
+ /**
+ * Log levels
+ *
+ * @var array
+ */
+ public static $levels = array(
+ Logger::DEBUG => 'DEBUG',
+ Logger::INFO => 'INFO',
+ Logger::WARNING => 'WARNING',
+ Logger::ERROR => 'ERROR'
+ );
+
+ /**
+ * This logger's instance
+ *
+ * @var static
+ */
+ protected static $instance;
+
+ /**
+ * Log writer
+ *
+ * @var \Icinga\Application\Logger\LogWriter
+ */
+ protected $writer;
+
+ /**
+ * Maximum level to emit
+ *
+ * @var int
+ */
+ protected $level;
+
+ /**
+ * Error messages to be displayed prior to any other log message
+ *
+ * @var array
+ */
+ protected $configErrors = array();
+
+ /**
+ * Create a new logger object
+ *
+ * @param ConfigObject $config
+ *
+ * @throws ConfigurationError If the logging configuration directive 'log' is missing or if the logging level is
+ * not defined
+ */
+ public function __construct(ConfigObject $config)
+ {
+ if ($config->log === null) {
+ throw new ConfigurationError('Required logging configuration directive \'log\' missing');
+ }
+
+ $this->setLevel($config->get('level', static::ERROR));
+
+ if (strtolower($config->get('log', 'syslog')) !== 'none') {
+ $this->writer = $this->createWriter($config);
+ }
+ }
+
+ /**
+ * Set the logging level to use
+ *
+ * @param mixed $level
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError In case the given level is invalid
+ */
+ public function setLevel($level)
+ {
+ if (is_numeric($level)) {
+ $level = (int) $level;
+ if (! isset(static::$levels[$level])) {
+ throw new ConfigurationError(
+ 'Can\'t set logging level %d. Logging level is invalid. Use one of %s or one of the'
+ . ' Logger\'s constants.',
+ $level,
+ implode(', ', array_keys(static::$levels))
+ );
+ }
+
+ $this->level = $level;
+ } else {
+ $level = strtoupper($level);
+ $levels = array_flip(static::$levels);
+ if (! isset($levels[$level])) {
+ throw new ConfigurationError(
+ 'Can\'t set logging level "%s". Logging level is invalid. Use one of %s.',
+ $level,
+ implode(', ', array_keys($levels))
+ );
+ }
+
+ $this->level = $levels[$level];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the logging level being used
+ *
+ * @return int
+ */
+ public function getLevel()
+ {
+ return $this->level;
+ }
+
+ /**
+ * Register the given message as config error
+ *
+ * Config errors are logged every time a log message is being logged.
+ *
+ * @param mixed $arg,... A string, exception or format-string + substitutions
+ *
+ * @return $this
+ */
+ public function registerConfigError()
+ {
+ if (func_num_args() > 0) {
+ $this->configErrors[] = static::formatMessage(func_get_args());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a new logger object
+ *
+ * @param ConfigObject $config
+ *
+ * @return static
+ */
+ public static function create(ConfigObject $config)
+ {
+ static::$instance = new static($config);
+ return static::$instance;
+ }
+
+ /**
+ * Create a log writer
+ *
+ * @param ConfigObject $config The configuration to initialize the writer with
+ *
+ * @return \Icinga\Application\Logger\LogWriter The requested log writer
+ * @throws ConfigurationError If the requested writer cannot be found
+ */
+ protected function createWriter(ConfigObject $config)
+ {
+ $class = 'Icinga\\Application\\Logger\\Writer\\' . ucfirst(strtolower($config->log)) . 'Writer';
+ if (! class_exists($class)) {
+ throw new ConfigurationError(
+ 'Cannot find log writer of type "%s"',
+ $config->log
+ );
+ }
+ return new $class($config);
+ }
+
+ /**
+ * Log a message
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ */
+ public function log($level, $message)
+ {
+ if ($this->writer !== null && $this->level <= $level) {
+ foreach ($this->configErrors as $error_message) {
+ $this->writer->log(static::ERROR, $error_message);
+ }
+
+ $this->writer->log($level, $message);
+ }
+ }
+
+ /**
+ * Return a string representation of the passed arguments
+ *
+ * This method provides three different processing techniques:
+ * - If the only passed argument is a string it is returned unchanged
+ * - If the only passed argument is an exception it is formatted as follows:
+ * <name> in <file>:<line> with message: <message>[ <- <name> ...]
+ * - If multiple arguments are passed the first is interpreted as format-string
+ * that gets substituted with the remaining ones which can be of any type
+ *
+ * @param array $arguments The arguments to format
+ *
+ * @return string The formatted result
+ */
+ protected static function formatMessage(array $arguments)
+ {
+ if (count($arguments) === 1) {
+ $message = $arguments[0];
+
+ if ($message instanceof Exception) {
+ $messages = array();
+ $error = $message;
+ do {
+ $messages[] = IcingaException::describe($error);
+ } while ($error = $error->getPrevious());
+ $message = implode(' <- ', $messages);
+ }
+
+ return $message;
+ }
+
+ return vsprintf(
+ array_shift($arguments),
+ array_map(
+ function ($a) {
+ return is_string($a) ? $a : ($a instanceof Exception
+ ? IcingaException::describe($a)
+ : Json::encode($a));
+ },
+ $arguments
+ )
+ );
+ }
+
+ /**
+ * Log a message with severity ERROR
+ *
+ * @param mixed $arg,... A string, exception or format-string + substitutions
+ */
+ public static function error()
+ {
+ if (static::$instance !== null && func_num_args() > 0) {
+ static::$instance->log(static::ERROR, static::formatMessage(func_get_args()));
+ }
+ }
+
+ /**
+ * Log a message with severity WARNING
+ *
+ * @param mixed $arg,... A string, exception or format-string + substitutions
+ */
+ public static function warning()
+ {
+ if (static::$instance !== null && func_num_args() > 0) {
+ static::$instance->log(static::WARNING, static::formatMessage(func_get_args()));
+ }
+ }
+
+ /**
+ * Log a message with severity INFO
+ *
+ * @param mixed $arg,... A string, exception or format-string + substitutions
+ */
+ public static function info()
+ {
+ if (static::$instance !== null && func_num_args() > 0) {
+ static::$instance->log(static::INFO, static::formatMessage(func_get_args()));
+ }
+ }
+
+ /**
+ * Log a message with severity DEBUG
+ *
+ * @param mixed $arg,... A string, exception or format-string + substitutions
+ */
+ public static function debug()
+ {
+ if (static::$instance !== null && func_num_args() > 0) {
+ static::$instance->log(static::DEBUG, static::formatMessage(func_get_args()));
+ }
+ }
+
+ /**
+ * Get the log writer to use
+ *
+ * @return \Icinga\Application\Logger\LogWriter
+ */
+ public function getWriter()
+ {
+ return $this->writer;
+ }
+
+ /**
+ * Is the logger writing to Syslog?
+ *
+ * @return bool
+ */
+ public static function writesToSyslog()
+ {
+ return static::$instance && static::$instance->getWriter() instanceof SyslogWriter;
+ }
+
+ /**
+ * Is the logger writing to a file?
+ *
+ * @return bool
+ */
+ public static function writesToFile()
+ {
+ return static::$instance && static::$instance->getWriter() instanceof FileWriter;
+ }
+
+ /**
+ * Get this' instance
+ *
+ * @return static
+ */
+ public static function getInstance()
+ {
+ return static::$instance;
+ }
+}
diff --git a/library/Icinga/Application/Logger/LogWriter.php b/library/Icinga/Application/Logger/LogWriter.php
new file mode 100644
index 0000000..019bdad
--- /dev/null
+++ b/library/Icinga/Application/Logger/LogWriter.php
@@ -0,0 +1,30 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Logger;
+
+use Icinga\Data\ConfigObject;
+
+/**
+ * Abstract class for writers that write messages to a log
+ */
+abstract class LogWriter
+{
+ /**
+ * @var ConfigObject
+ */
+ protected $config;
+
+ /**
+ * Create a new log writer initialized with the given configuration
+ */
+ public function __construct(ConfigObject $config)
+ {
+ $this->config = $config;
+ }
+
+ /**
+ * Log a message with the given severity
+ */
+ abstract public function log($severity, $message);
+}
diff --git a/library/Icinga/Application/Logger/Writer/FileWriter.php b/library/Icinga/Application/Logger/Writer/FileWriter.php
new file mode 100644
index 0000000..6b4ed54
--- /dev/null
+++ b/library/Icinga/Application/Logger/Writer/FileWriter.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Logger\Writer;
+
+use Exception;
+use Icinga\Data\ConfigObject;
+use Icinga\Application\Logger;
+use Icinga\Application\Logger\LogWriter;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Util\File;
+
+/**
+ * Log to a file
+ */
+class FileWriter extends LogWriter
+{
+ /**
+ * Path to the file
+ *
+ * @var string
+ */
+ protected $file;
+
+ /**
+ * Create a new file log writer
+ *
+ * @param ConfigObject $config
+ *
+ * @throws ConfigurationError If the configuration directive 'file' is missing or if the path to 'file' does
+ * not exist or if writing to 'file' is not possible
+ */
+ public function __construct(ConfigObject $config)
+ {
+ if ($config->file === null) {
+ throw new ConfigurationError('Required logging configuration directive \'file\' missing');
+ }
+ $this->file = $config->file;
+
+ if (substr($this->file, 0, 6) !== 'php://' && ! file_exists(dirname($this->file))) {
+ throw new ConfigurationError(
+ 'Log path "%s" does not exist',
+ dirname($this->file)
+ );
+ }
+
+ try {
+ $this->write(''); // Avoid to handle such errors on every write access
+ } catch (Exception $e) {
+ throw new ConfigurationError(
+ 'Cannot write to log file "%s" (%s)',
+ $this->file,
+ $e->getMessage()
+ );
+ }
+ }
+
+ /**
+ * Log a message
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ */
+ public function log($level, $message)
+ {
+ $this->write(date('c') . ' - ' . Logger::$levels[$level] . ' - ' . $message . PHP_EOL);
+ }
+
+ /**
+ * Write a message to the log
+ *
+ * @param string $message
+ */
+ protected function write($message)
+ {
+ $file = new File($this->file, 'a');
+ $file->fwrite($message);
+ $file->fflush();
+ }
+}
diff --git a/library/Icinga/Application/Logger/Writer/PhpWriter.php b/library/Icinga/Application/Logger/Writer/PhpWriter.php
new file mode 100644
index 0000000..dedb2bd
--- /dev/null
+++ b/library/Icinga/Application/Logger/Writer/PhpWriter.php
@@ -0,0 +1,39 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Logger\Writer;
+
+use Icinga\Application\Logger;
+use Icinga\Application\Logger\LogWriter;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\NotWritableError;
+
+/**
+ * Log to the webserver log, a file or syslog
+ *
+ * @see https://secure.php.net/manual/en/errorfunc.configuration.php#ini.error-log
+ */
+class PhpWriter extends LogWriter
+{
+ /**
+ * Prefix to prepend to each message
+ *
+ * @var string
+ */
+ protected $ident;
+
+ public function __construct(ConfigObject $config)
+ {
+ parent::__construct($config);
+ $this->ident = $config->get('application', 'icingaweb2');
+ }
+
+ public function log($severity, $message)
+ {
+ if (ini_get('error_log') === 'syslog') {
+ $message = str_replace("\n", ' ', $message);
+ }
+
+ error_log($this->ident . ': ' . Logger::$levels[$severity] . ' - ' . $message);
+ }
+}
diff --git a/library/Icinga/Application/Logger/Writer/StderrWriter.php b/library/Icinga/Application/Logger/Writer/StderrWriter.php
new file mode 100644
index 0000000..2f35ff2
--- /dev/null
+++ b/library/Icinga/Application/Logger/Writer/StderrWriter.php
@@ -0,0 +1,61 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Logger\Writer;
+
+use Icinga\Cli\Screen;
+use Icinga\Application\Logger;
+use Icinga\Application\Logger\LogWriter;
+
+/**
+ * Class to write log messages to STDERR
+ */
+class StderrWriter extends LogWriter
+{
+ /**
+ * The current Screen in use
+ *
+ * @var Screen
+ */
+ protected $screen;
+
+ /**
+ * Return the current Screen
+ *
+ * @return Screen
+ */
+ protected function screen()
+ {
+ if ($this->screen === null) {
+ $this->screen = Screen::instance(STDERR);
+ }
+
+ return $this->screen;
+ }
+
+ /**
+ * Log a message with the given severity
+ *
+ * @param int $severity The severity to use
+ * @param string $message The message to log
+ */
+ public function log($severity, $message)
+ {
+ switch ($severity) {
+ case Logger::ERROR:
+ $color = 'red';
+ break;
+ case Logger::WARNING:
+ $color = 'yellow';
+ break;
+ case Logger::INFO:
+ $color = 'green';
+ break;
+ case Logger::DEBUG:
+ $color = 'blue';
+ break;
+ }
+
+ file_put_contents('php://stderr', $this->screen()->colorize($message, $color) . "\n");
+ }
+}
diff --git a/library/Icinga/Application/Logger/Writer/StdoutWriter.php b/library/Icinga/Application/Logger/Writer/StdoutWriter.php
new file mode 100644
index 0000000..a6f43e5
--- /dev/null
+++ b/library/Icinga/Application/Logger/Writer/StdoutWriter.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Logger\Writer;
+
+/**
+ * Deprecated, compat only.
+ *
+ * Use Icinga\Application\Logger\Writer\StderrWriter instead.
+ */
+class StdoutWriter extends StderrWriter
+{
+}
diff --git a/library/Icinga/Application/Logger/Writer/SyslogWriter.php b/library/Icinga/Application/Logger/Writer/SyslogWriter.php
new file mode 100644
index 0000000..93efc2a
--- /dev/null
+++ b/library/Icinga/Application/Logger/Writer/SyslogWriter.php
@@ -0,0 +1,90 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Logger\Writer;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Application\Logger;
+use Icinga\Application\Logger\LogWriter;
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * Log to the syslog service
+ */
+class SyslogWriter extends LogWriter
+{
+ /**
+ * Syslog facility
+ *
+ * @var int
+ */
+ protected $facility;
+
+ /**
+ * Prefix to prepend to each message
+ *
+ * @var string
+ */
+ protected $ident;
+
+ /**
+ * Known syslog facilities
+ *
+ * @var array
+ */
+ public static $facilities = array(
+ 'user' => LOG_USER,
+ 'local0' => LOG_LOCAL0,
+ 'local1' => LOG_LOCAL1,
+ 'local2' => LOG_LOCAL2,
+ 'local3' => LOG_LOCAL3,
+ 'local4' => LOG_LOCAL4,
+ 'local5' => LOG_LOCAL5,
+ 'local6' => LOG_LOCAL6,
+ 'local7' => LOG_LOCAL7
+ );
+
+ /**
+ * Log level to syslog severity map
+ *
+ * @var array
+ */
+ public static $severityMap = array(
+ Logger::ERROR => LOG_ERR,
+ Logger::WARNING => LOG_WARNING,
+ Logger::INFO => LOG_INFO,
+ Logger::DEBUG => LOG_DEBUG
+ );
+
+ /**
+ * Create a new syslog log writer
+ *
+ * @param ConfigObject $config
+ */
+ public function __construct(ConfigObject $config)
+ {
+ $this->ident = $config->get('application', 'icingaweb2');
+
+ $configuredFacility = $config->get('facility', 'user');
+ if (! isset(static::$facilities[$configuredFacility])) {
+ throw new ConfigurationError(
+ 'Invalid logging facility: "%s" (expected one of: %s)',
+ $configuredFacility,
+ implode(', ', array_keys(static::$facilities))
+ );
+ }
+ $this->facility = static::$facilities[$configuredFacility];
+ }
+
+ /**
+ * Log a message
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ */
+ public function log($level, $message)
+ {
+ openlog($this->ident, LOG_PID, $this->facility);
+ syslog(static::$severityMap[$level], str_replace("\n", ' ', $message));
+ }
+}
diff --git a/library/Icinga/Application/Modules/DashboardContainer.php b/library/Icinga/Application/Modules/DashboardContainer.php
new file mode 100644
index 0000000..f3c8bc6
--- /dev/null
+++ b/library/Icinga/Application/Modules/DashboardContainer.php
@@ -0,0 +1,58 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Modules;
+
+/**
+ * Container for module dashboards
+ */
+class DashboardContainer extends NavigationItemContainer
+{
+ /**
+ * This dashboard's dashlets
+ *
+ * @var array
+ */
+ protected $dashlets;
+
+ /**
+ * Set this dashboard's dashlets
+ *
+ * @param array $dashlets
+ *
+ * @return $this
+ */
+ public function setDashlets(array $dashlets)
+ {
+ $this->dashlets = $dashlets;
+ return $this;
+ }
+
+ /**
+ * Return this dashboard's dashlets
+ *
+ * @return array
+ */
+ public function getDashlets()
+ {
+ return $this->dashlets ?: array();
+ }
+
+ /**
+ * Add a new dashlet
+ *
+ * @param string $name
+ * @param string $url
+ * @param int $priority
+ *
+ * @return $this
+ */
+ public function add($name, $url, $priority = null)
+ {
+ $this->dashlets[$name] = [
+ 'url' => $url,
+ 'priority' => $priority
+ ];
+ return $this;
+ }
+}
diff --git a/library/Icinga/Application/Modules/Manager.php b/library/Icinga/Application/Modules/Manager.php
new file mode 100644
index 0000000..55d074d
--- /dev/null
+++ b/library/Icinga/Application/Modules/Manager.php
@@ -0,0 +1,698 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Modules;
+
+use Icinga\Application\ApplicationBootstrap;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\SimpleQuery;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\SystemPermissionException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\NotReadableError;
+
+/**
+ * Module manager that handles detecting, enabling and disabling of modules
+ *
+ * Modules can have 3 states:
+ * * installed, module exists but is disabled
+ * * enabled, module enabled and should be loaded
+ * * loaded, module enabled and loaded via the autoloader
+ *
+ */
+class Manager
+{
+ /**
+ * Namespace for module permissions
+ *
+ * @var string
+ */
+ const MODULE_PERMISSION_NS = 'module/';
+
+ /**
+ * Array of all installed module's base directories
+ *
+ * @var array
+ */
+ private $installedBaseDirs = array();
+
+ /**
+ * Array of all enabled modules base dirs
+ *
+ * @var array
+ */
+ private $enabledDirs = array();
+
+ /**
+ * Array of all module names that have been loaded
+ *
+ * @var array
+ */
+ private $loadedModules = array();
+
+ /**
+ * Reference to Icinga::app
+ *
+ * @var Icinga
+ */
+ private $app;
+
+ /**
+ * The directory that is used to detect enabled modules
+ *
+ * @var string
+ */
+ private $enableDir;
+
+ /**
+ * All paths to look for installed modules that can be enabled
+ *
+ * @var array
+ */
+ private $modulePaths = array();
+
+ /**
+ * Whether we loaded all enabled modules
+ *
+ * @var bool
+ */
+ private $loadedAllEnabledModules = false;
+
+ /**
+ * Create a new instance of the module manager
+ *
+ * @param ApplicationBootstrap $app
+ * @param string $enabledDir Enabled modules location. The application maintains symlinks within
+ * the given path
+ * @param array $availableDirs Installed modules location
+ **/
+ public function __construct($app, $enabledDir, array $availableDirs)
+ {
+ $this->app = $app;
+ $this->modulePaths = $availableDirs;
+ $this->enableDir = $enabledDir;
+ }
+
+ /**
+ * Query interface for the module manager
+ *
+ * @return SimpleQuery
+ */
+ public function select()
+ {
+ $source = new ArrayDatasource($this->getModuleInfo());
+ return $source->select();
+ }
+
+ /**
+ * Check for enabled modules
+ *
+ * Update the internal $enabledDirs property with the enabled modules.
+ *
+ * @throws ConfigurationError If module dir does not exist, is not a directory or not readable
+ */
+ private function detectEnabledModules()
+ {
+ if (! file_exists($parent = dirname($this->enableDir))) {
+ return;
+ }
+ if (! is_readable($parent)) {
+ throw new NotReadableError(
+ 'Cannot read enabled modules. Config directory "%s" is not readable',
+ $parent
+ );
+ }
+
+ if (! file_exists($this->enableDir)) {
+ return;
+ }
+ if (! is_dir($this->enableDir)) {
+ throw new NotReadableError(
+ 'Cannot read enabled modules. Module directory "%s" is not a directory',
+ $this->enableDir
+ );
+ }
+ if (! is_readable($this->enableDir)) {
+ throw new NotReadableError(
+ 'Cannot read enabled modules. Module directory "%s" is not readable',
+ $this->enableDir
+ );
+ }
+ if (($dh = opendir($this->enableDir)) !== false) {
+ $isPhar = substr($this->enableDir, 0, 8) === 'phar:///';
+ $this->enabledDirs = array();
+ while (($file = readdir($dh)) !== false) {
+ if ($file[0] === '.' || $file === 'README') {
+ continue;
+ }
+
+ $link = $this->enableDir . DIRECTORY_SEPARATOR . $file;
+ if (! $isPhar && ! is_link($link)) {
+ Logger::warning(
+ 'Found invalid module in enabledModule directory "%s": "%s" is not a symlink',
+ $this->enableDir,
+ $link
+ );
+ continue;
+ }
+
+ $dir = $isPhar ? $link : realpath($link);
+ if ($dir !== false && is_dir($dir)) {
+ $this->enabledDirs[$file] = $dir;
+ } else {
+ $this->enabledDirs[$file] = null;
+
+ Logger::warning(
+ 'Found invalid module in enabledModule directory "%s": "%s" points to non existing path "%s"',
+ $this->enableDir,
+ $link,
+ $dir
+ );
+ }
+
+ ksort($this->enabledDirs);
+ }
+ closedir($dh);
+ }
+ }
+
+ /**
+ * Try to set all enabled modules in loaded sate
+ *
+ * @return $this
+ * @see Manager::loadModule()
+ */
+ public function loadEnabledModules()
+ {
+ if (! $this->loadedAllEnabledModules) {
+ foreach ($this->listEnabledModules() as $name) {
+ $this->loadModule($name);
+ }
+
+ $this->loadedAllEnabledModules = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Whether we loaded all enabled modules
+ *
+ * @return bool
+ */
+ public function loadedAllEnabledModules()
+ {
+ return $this->loadedAllEnabledModules;
+ }
+
+
+ /**
+ * Try to load the module and register it in the application
+ *
+ * @param string $name The name of the module to load
+ * @param mixed $basedir Optional module base directory
+ *
+ * @return $this
+ */
+ public function loadModule($name, $basedir = null)
+ {
+ if ($this->hasLoaded($name)) {
+ return $this;
+ }
+
+ $module = null;
+ if ($basedir === null) {
+ $module = new Module($this->app, $name, $this->getModuleDir($name));
+ } else {
+ $module = new Module($this->app, $name, $basedir);
+ }
+
+ if ($name !== 'ipl' && $name !== 'reactbundle') {
+ $module->register();
+ }
+
+ $this->loadedModules[$name] = $module;
+ return $this;
+ }
+
+ /**
+ * Set the given module to the enabled state
+ *
+ * @param string $name The module to enable
+ * @param bool $force Whether to ignore unmet dependencies
+ *
+ * @return $this
+ * @throws ConfigurationError When trying to enable a module that is not installed
+ * @throws SystemPermissionException When insufficient permissions for the application exist
+ */
+ public function enableModule($name, $force = false)
+ {
+ if (! $this->hasInstalled($name)) {
+ throw new ConfigurationError(
+ 'Cannot enable module "%s". Module is not installed.',
+ $name
+ );
+ }
+
+ if (strtolower(substr($name, 0, 18)) === 'icingaweb2-module-') {
+ throw new ConfigurationError(
+ 'Cannot enable module "%s": Directory name does not match the module\'s name.'
+ . ' Please rename the module to "%s" before enabling.',
+ $name,
+ substr($name, 18)
+ );
+ }
+
+ if ($this->hasUnmetDependencies($name)) {
+ if ($force) {
+ Logger::warning(t('Enabling module "%s" although it has unmet dependencies'), $name);
+ } else {
+ throw new ConfigurationError(
+ t('Module "%s" can\'t be enabled. Module has unmet dependencies'),
+ $name
+ );
+ }
+ }
+
+ clearstatcache(true);
+ $target = $this->installedBaseDirs[$name];
+ $link = $this->enableDir . DIRECTORY_SEPARATOR . $name;
+
+ if (! is_dir($this->enableDir)) {
+ if (!@mkdir($this->enableDir, 0777, true)) {
+ $error = error_get_last();
+ throw new SystemPermissionException(
+ 'Failed to create enabledModules directory "%s" (%s)',
+ $this->enableDir,
+ $error['message']
+ );
+ }
+
+ chmod($this->enableDir, 02770);
+ } elseif (! is_writable($this->enableDir)) {
+ throw new SystemPermissionException(
+ 'Cannot enable module "%s". Check the permissions for the enabledModules directory: %s',
+ $name,
+ $this->enableDir
+ );
+ }
+
+ $this->loadedAllEnabledModules = false;
+
+ if (file_exists($link) && is_link($link)) {
+ return $this;
+ }
+
+ if (! @symlink($target, $link)) {
+ $error = error_get_last();
+ if (strstr($error["message"], "File exists") === false) {
+ throw new SystemPermissionException(
+ 'Cannot enable module "%s" at %s due to file system errors. '
+ . 'Please check path and mounting points because this is not a permission error. '
+ . 'Primary error was: %s',
+ $name,
+ $this->enableDir,
+ $error['message']
+ );
+ }
+ }
+
+ $this->enabledDirs[$name] = $link;
+ $this->loadModule($name);
+ return $this;
+ }
+
+ /**
+ * Disable the given module and remove its enabled state
+ *
+ * @param string $name The name of the module to disable
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError When the module is not installed or it's not a symlink
+ * @throws SystemPermissionException When insufficient permissions for the application exist
+ */
+ public function disableModule($name)
+ {
+ if (! $this->hasEnabled($name)) {
+ throw new ConfigurationError(
+ 'Cannot disable module "%s". Module is not installed.',
+ $name
+ );
+ }
+
+ if (! is_writable($this->enableDir)) {
+ throw new SystemPermissionException(
+ 'Cannot disable module "%s". Check the permissions for the enabledModules directory: %s',
+ $name,
+ $this->enableDir
+ );
+ }
+
+ $link = $this->enableDir . DIRECTORY_SEPARATOR . $name;
+ if (! is_link($link)) {
+ throw new ConfigurationError(
+ 'Cannot disable module %s at %s. '
+ . 'It looks like you have installed this module manually and moved it to your module folder. '
+ . 'In order to dynamically enable and disable modules, you have to create a symlink to '
+ . 'the enabledModules folder.',
+ $name,
+ $this->enableDir
+ );
+ }
+
+ if (is_link($link)) {
+ if (! @unlink($link)) {
+ $error = error_get_last();
+ throw new SystemPermissionException(
+ 'Cannot enable module "%s" at %s due to file system errors. '
+ . 'Please check path and mounting points because this is not a permission error. '
+ . 'Primary error was: %s',
+ $name,
+ $this->enableDir,
+ $error['message']
+ );
+ }
+ }
+
+ unset($this->enabledDirs[$name]);
+ return $this;
+ }
+
+ /**
+ * Return the directory of the given module as a string, optionally with a given sub directoy
+ *
+ * @param string $name The module name to return the module directory of
+ * @param string $subdir The sub directory to append to the path
+ *
+ * @return string
+ *
+ * @throws ProgrammingError When the module is not installed or existing
+ */
+ public function getModuleDir($name, $subdir = '')
+ {
+ if ($this->hasLoaded($name)) {
+ return $this->getModule($name)->getBaseDir() . $subdir;
+ }
+
+ if ($this->hasEnabled($name)) {
+ return $this->enabledDirs[$name]. $subdir;
+ }
+
+ if ($this->hasInstalled($name)) {
+ return $this->installedBaseDirs[$name] . $subdir;
+ }
+
+ throw new ProgrammingError(
+ 'Trying to access uninstalled module dir: %s',
+ $name
+ );
+ }
+
+ /**
+ * Return true when the module with the given name is installed, otherwise false
+ *
+ * @param string $name The module to check for being installed
+ *
+ * @return bool
+ */
+ public function hasInstalled($name)
+ {
+ if (!count($this->installedBaseDirs)) {
+ $this->detectInstalledModules();
+ }
+ return array_key_exists($name, $this->installedBaseDirs);
+ }
+
+ /**
+ * Return true when the given module is in enabled state, otherwise false
+ *
+ * @param string $name The module to check for being enabled
+ *
+ * @return bool
+ */
+ public function hasEnabled($name)
+ {
+ return array_key_exists($name, $this->enabledDirs);
+ }
+
+ /**
+ * Return true when the module is in loaded state, otherwise false
+ *
+ * @param string $name The module to check for being loaded
+ *
+ * @return bool
+ */
+ public function hasLoaded($name)
+ {
+ return array_key_exists($name, $this->loadedModules);
+ }
+
+ /**
+ * Check if a module with the given name is enabled
+ *
+ * Passing a version constraint also verifies that the module's version matches.
+ *
+ * @param string $name
+ * @param string $version
+ *
+ * @return bool
+ */
+ public function has($name, $version = null)
+ {
+ if (! $this->hasEnabled($name)) {
+ return false;
+ } elseif ($version === null || $version === true) {
+ return true;
+ }
+
+ $operator = '=';
+ if (preg_match('/^([<>=]{1,2})\s*v?((?:[\d.]+)(?:.+)?)$/', $version, $match)) {
+ $operator = $match[1];
+ $version = $match[2];
+ }
+
+ $modVersion = ltrim($this->getModule($name)->getVersion(), 'v');
+ return version_compare($modVersion, $version, $operator);
+ }
+
+ /**
+ * Get the currently loaded modules
+ *
+ * @return Module[]
+ */
+ public function getLoadedModules()
+ {
+ return $this->loadedModules;
+ }
+
+ /**
+ * Get a module
+ *
+ * @param string $name Name of the module
+ * @param bool $assertLoaded Whether or not to throw an exception if the module hasn't been loaded
+ *
+ * @return Module
+ * @throws ProgrammingError If the module hasn't been loaded
+ */
+ public function getModule($name, $assertLoaded = true)
+ {
+ if ($this->hasLoaded($name)) {
+ return $this->loadedModules[$name];
+ } elseif (! (bool) $assertLoaded) {
+ return new Module($this->app, $name, $this->getModuleDir($name));
+ }
+ throw new ProgrammingError(
+ 'Can\'t access module %s because it hasn\'t been loaded',
+ $name
+ );
+ }
+
+ /**
+ * Return an array containing information objects for each available module
+ *
+ * Each entry has the following fields
+ * * name, name of the module as a string
+ * * path, path where the module is located as a string
+ * * installed, whether the module is installed or not as a boolean
+ * * enabled, whether the module is enabled or not as a boolean
+ * * loaded, whether the module is loaded or not as a boolean
+ *
+ * @return array
+ */
+ public function getModuleInfo()
+ {
+ $info = array();
+
+ $installed = $this->listInstalledModules();
+ foreach ($installed as $name) {
+ $info[$name] = (object) array(
+ 'name' => $name,
+ 'path' => $this->installedBaseDirs[$name],
+ 'installed' => true,
+ 'enabled' => $this->hasEnabled($name),
+ 'loaded' => $this->hasLoaded($name)
+ );
+ }
+
+ $enabled = $this->listEnabledModules();
+ foreach ($enabled as $name) {
+ $info[$name] = (object) array(
+ 'name' => $name,
+ 'path' => $this->enabledDirs[$name],
+ 'installed' => $this->enabledDirs[$name] !== null,
+ 'enabled' => true,
+ 'loaded' => $this->hasLoaded($name)
+ );
+ }
+
+ return $info;
+ }
+
+ /**
+ * Check if the given module has unmet dependencies
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasUnmetDependencies($name)
+ {
+ $module = $this->getModule($name, false);
+
+ $requiredMods = $module->getRequiredModules();
+
+ if (isset($requiredMods['monitoring'], $requiredMods['icingadb'])) {
+ if (! $this->has('monitoring', $requiredMods['monitoring'])
+ && ! $this->has('icingadb', $requiredMods['icingadb'])
+ ) {
+ return true;
+ }
+
+ unset($requiredMods['monitoring'], $requiredMods['icingadb']);
+ }
+
+ foreach ($requiredMods as $moduleName => $moduleVersion) {
+ if (! $this->has($moduleName, $moduleVersion)) {
+ return true;
+ }
+ }
+
+ $libraries = Icinga::app()->getLibraries();
+
+ $requiredLibs = $module->getRequiredLibraries();
+ foreach ($requiredLibs as $libraryName => $libraryVersion) {
+ if (! $libraries->has($libraryName, $libraryVersion)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return an array containing all enabled module names as strings
+ *
+ * @return array
+ */
+ public function listEnabledModules()
+ {
+ if (count($this->enabledDirs) === 0) {
+ $this->detectEnabledModules();
+ }
+
+ return array_keys($this->enabledDirs);
+ }
+
+ /**
+ * Return an array containing all loaded module names as strings
+ *
+ * @return array
+ */
+ public function listLoadedModules()
+ {
+ return array_keys($this->loadedModules);
+ }
+
+ /**
+ * Return an array of module names from installed modules
+ *
+ * Calls detectInstalledModules() if no module discovery has been performed yet
+ *
+ * @return array
+ *
+ * @see detectInstalledModules()
+ */
+ public function listInstalledModules()
+ {
+ if (!count($this->installedBaseDirs)) {
+ $this->detectInstalledModules();
+ }
+
+ if (count($this->installedBaseDirs)) {
+ return array_keys($this->installedBaseDirs);
+ }
+
+ return array();
+ }
+
+ /**
+ * Detect installed modules from every path provided in modulePaths
+ *
+ * @param array $availableDirs Installed modules location
+ *
+ * @return $this
+ */
+ public function detectInstalledModules(array $availableDirs = null)
+ {
+ $modulePaths = $availableDirs !== null ? $availableDirs : $this->modulePaths;
+ foreach ($modulePaths as $basedir) {
+ $canonical = realpath($basedir);
+ if ($canonical === false) {
+ Logger::warning('Module path "%s" does not exist', $basedir);
+ continue;
+ }
+ if (!is_dir($canonical)) {
+ Logger::error('Module path "%s" is not a directory', $canonical);
+ continue;
+ }
+ if (!is_readable($canonical)) {
+ Logger::error('Module path "%s" is not readable', $canonical);
+ continue;
+ }
+ if (($dh = opendir($canonical)) !== false) {
+ while (($file = readdir($dh)) !== false) {
+ if ($file[0] === '.') {
+ continue;
+ }
+ if (is_dir($canonical . '/' . $file)) {
+ if (! array_key_exists($file, $this->installedBaseDirs)) {
+ $this->installedBaseDirs[$file] = $canonical . '/' . $file;
+ } else {
+ Logger::debug(
+ 'Module "%s" already exists in installation path "%s" and is ignored.',
+ $canonical . '/' . $file,
+ $this->installedBaseDirs[$file]
+ );
+ }
+ }
+ }
+ closedir($dh);
+ }
+ }
+ ksort($this->installedBaseDirs);
+ return $this;
+ }
+
+ /**
+ * Get the directories where to look for installed modules
+ *
+ * @return array
+ */
+ public function getModuleDirs()
+ {
+ return $this->modulePaths;
+ }
+}
diff --git a/library/Icinga/Application/Modules/MenuItemContainer.php b/library/Icinga/Application/Modules/MenuItemContainer.php
new file mode 100644
index 0000000..88599e6
--- /dev/null
+++ b/library/Icinga/Application/Modules/MenuItemContainer.php
@@ -0,0 +1,55 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Modules;
+
+/**
+ * Container for module menu items
+ */
+class MenuItemContainer extends NavigationItemContainer
+{
+ /**
+ * This menu item's children
+ *
+ * @var MenuItemContainer[]
+ */
+ protected $children;
+
+ /**
+ * Set this menu item's children
+ *
+ * @param MenuItemContainer[] $children
+ *
+ * @return $this
+ */
+ public function setChildren(array $children)
+ {
+ $this->children = $children;
+ return $this;
+ }
+
+ /**
+ * Return this menu item's children
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ return $this->children ?: array();
+ }
+
+ /**
+ * Add a new sub menu
+ *
+ * @param string $name
+ * @param array $properties
+ *
+ * @return MenuItemContainer The newly added sub menu
+ */
+ public function add($name, array $properties = array())
+ {
+ $child = new MenuItemContainer($name, $properties);
+ $this->children[] = $child;
+ return $child;
+ }
+}
diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php
new file mode 100644
index 0000000..00a3383
--- /dev/null
+++ b/library/Icinga/Application/Modules/Module.php
@@ -0,0 +1,1444 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Modules;
+
+use Exception;
+use Icinga\Application\ApplicationBootstrap;
+use Icinga\Application\Config;
+use Icinga\Application\Hook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Setup\SetupWizard;
+use Icinga\Util\File;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Widget;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\StaticTranslator;
+use ipl\I18n\Translation;
+use Zend_Controller_Router_Route;
+use Zend_Controller_Router_Route_Abstract;
+use Zend_Controller_Router_Route_Regex;
+
+/**
+ * Module handling
+ *
+ * Register modules and initialize it
+ */
+class Module
+{
+ use Translation {
+ translate as protected;
+ translatePlural as protected;
+ }
+
+ /**
+ * Module name
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * Base directory of module
+ *
+ * @var string
+ */
+ private $basedir;
+
+ /**
+ * Directory for styles
+ *
+ * @var string
+ */
+ private $cssdir;
+
+ /**
+ * Directory for Javascript
+ *
+ * @var string
+ */
+ private $jsdir;
+
+ /**
+ * Base application directory
+ *
+ * @var string
+ */
+ private $appdir;
+
+ /**
+ * Library directory
+ *
+ * @var string
+ */
+ private $libdir;
+
+ /**
+ * Directory containing translations
+ *
+ * @var string
+ */
+ private $localedir;
+
+ /**
+ * Directory where controllers reside
+ *
+ * @var string
+ */
+ private $controllerdir;
+
+ /**
+ * Directory containing form implementations
+ *
+ * @var string
+ */
+ private $formdir;
+
+ /**
+ * Module bootstrapping script
+ *
+ * @var string
+ */
+ private $runScript;
+
+ /**
+ * Module configuration script
+ *
+ * @var string
+ */
+ private $configScript;
+
+ /**
+ * Module metadata filename
+ *
+ * @var string
+ */
+ private $metadataFile;
+
+ /**
+ * Module metadata (version...)
+ *
+ * @var object
+ */
+ private $metadata;
+
+ /**
+ * Whether we already tried to include the module configuration script
+ *
+ * @var bool
+ */
+ private $triedToLaunchConfigScript = false;
+
+ /**
+ * Whether the module's namespaces have been registered on our autoloader
+ *
+ * @var bool
+ */
+ protected $registeredAutoloader = false;
+
+ /**
+ * Whether this module has been registered
+ *
+ * @var bool
+ */
+ private $registered = false;
+
+ /**
+ * Provided permissions
+ *
+ * @var array
+ */
+ private $permissionList = array();
+
+ /**
+ * Provided restrictions
+ *
+ * @var array
+ */
+ private $restrictionList = array();
+
+ /**
+ * Provided config tabs
+ *
+ * @var array
+ */
+ private $configTabs = array();
+
+ /**
+ * Provided setup wizard
+ *
+ * @var string
+ */
+ private $setupWizard;
+
+ /**
+ * Icinga application
+ *
+ * @var \Icinga\Application\Web
+ */
+ private $app;
+
+ /**
+ * The CSS/LESS files this module provides
+ *
+ * @var array
+ */
+ protected $cssFiles = array();
+
+ /**
+ * The Javascript files this module provides
+ *
+ * @var array
+ */
+ protected $jsFiles = array();
+
+ /**
+ * Routes to add to the route chain
+ *
+ * @var array Array of name-route pairs
+ *
+ * @see addRoute()
+ */
+ protected $routes = array();
+
+ /**
+ * A set of menu elements
+ *
+ * @var MenuItemContainer[]
+ */
+ protected $menuItems = array();
+
+ /**
+ * A set of Pane elements
+ *
+ * @var array
+ */
+ protected $paneItems = array();
+
+ /**
+ * A set of objects representing a searchUrl configuration
+ *
+ * @var array
+ */
+ protected $searchUrls = array();
+
+ /**
+ * This module's user backends providing several authentication mechanisms
+ *
+ * @var array
+ */
+ protected $userBackends = array();
+
+ /**
+ * This module's user group backends
+ *
+ * @var array
+ */
+ protected $userGroupBackends = array();
+
+ /**
+ * This module's configurable navigation items
+ *
+ * @var array
+ */
+ protected $navigationItems = array();
+
+ /**
+ * Create a new module object
+ *
+ * @param ApplicationBootstrap $app
+ * @param string $name
+ * @param string $basedir
+ */
+ public function __construct(ApplicationBootstrap $app, $name, $basedir)
+ {
+ $this->app = $app;
+ $this->name = $name;
+ $this->basedir = $basedir;
+ $this->cssdir = $basedir . '/public/css';
+ $this->jsdir = $basedir . '/public/js';
+ $this->libdir = $basedir . '/library';
+ $this->configdir = $app->getConfigDir('modules/' . $name);
+ $this->appdir = $basedir . '/application';
+ $this->localedir = $basedir . '/application/locale';
+ $this->formdir = $basedir . '/application/forms';
+ $this->controllerdir = $basedir . '/application/controllers';
+ $this->runScript = $basedir . '/run.php';
+ $this->configScript = $basedir . '/configuration.php';
+ $this->metadataFile = $basedir . '/module.info';
+
+ $this->translationDomain = $name;
+ }
+
+ /**
+ * Provide a search URL
+ *
+ * @param string $title
+ * @param string $url
+ * @param int $priority
+ *
+ * @return $this
+ */
+ public function provideSearchUrl($title, $url, $priority = 0)
+ {
+ $this->searchUrls[] = (object) array(
+ 'title' => (string) $title,
+ 'url' => (string) $url,
+ 'priority' => (int) $priority
+ );
+
+ return $this;
+ }
+
+ /**
+ * Get this module's search urls
+ *
+ * @return array
+ */
+ public function getSearchUrls()
+ {
+ $this->launchConfigScript();
+ return $this->searchUrls;
+ }
+
+ /**
+ * Return this module's dashboard
+ *
+ * @return Navigation
+ */
+ public function getDashboard()
+ {
+ $this->launchConfigScript();
+ return $this->createDashboard($this->paneItems);
+ }
+
+ /**
+ * Create and return a new navigation for the given dashboard panes
+ *
+ * @param DashboardContainer[] $panes
+ *
+ * @return Navigation
+ */
+ public function createDashboard(array $panes)
+ {
+ $navigation = new Navigation();
+ foreach ($panes as $pane) {
+ /** @var DashboardContainer $pane */
+ $dashlets = [];
+ foreach ($pane->getDashlets() as $dashletName => $dashletConfig) {
+ $dashlets[$dashletName] = [
+ 'label' => $this->translate($dashletName),
+ 'url' => $dashletConfig['url'],
+ 'priority' => $dashletConfig['priority']
+ ];
+ }
+
+ $navigation->addItem(
+ $pane->getName(),
+ array_merge(
+ $pane->getProperties(),
+ array(
+ 'label' => $this->translate($pane->getName()),
+ 'type' => 'dashboard-pane',
+ 'children' => $dashlets
+ )
+ )
+ );
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Add or get a dashboard pane
+ *
+ * @param string $name
+ * @param array $properties
+ *
+ * @return DashboardContainer
+ */
+ protected function dashboard($name, array $properties = array())
+ {
+ if (array_key_exists($name, $this->paneItems)) {
+ $this->paneItems[$name]->setProperties($properties);
+ } else {
+ $this->paneItems[$name] = new DashboardContainer($name, $properties);
+ }
+
+ return $this->paneItems[$name];
+ }
+
+ /**
+ * Return this module's menu
+ *
+ * @return Navigation
+ */
+ public function getMenu()
+ {
+ $this->launchConfigScript();
+ return Navigation::fromArray($this->createMenu($this->menuItems));
+ }
+
+ /**
+ * Create and return an array structure for the given menu items
+ *
+ * @param MenuItemContainer[] $items
+ *
+ * @return array
+ */
+ private function createMenu(array $items)
+ {
+ $navigation = array();
+ foreach ($items as $item) {
+ /** @var MenuItemContainer $item */
+ $properties = $item->getProperties();
+ $properties['children'] = $this->createMenu($item->getChildren());
+ if (! isset($properties['label'])) {
+ $properties['label'] = $this->translate($item->getName());
+ }
+
+ $navigation[$item->getName()] = $properties;
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Add or get a menu section
+ *
+ * @param string $name
+ * @param array $properties
+ *
+ * @return MenuItemContainer
+ */
+ protected function menuSection($name, array $properties = array())
+ {
+ if (array_key_exists($name, $this->menuItems)) {
+ $this->menuItems[$name]->setProperties($properties);
+ } else {
+ $this->menuItems[$name] = new MenuItemContainer($name, $properties);
+ }
+
+ return $this->menuItems[$name];
+ }
+
+ /**
+ * Register module
+ *
+ * @return bool
+ */
+ public function register()
+ {
+ if ($this->registered) {
+ return true;
+ }
+
+ $this->registerAutoloader();
+ try {
+ $this->launchRunScript();
+ } catch (Exception $e) {
+ Logger::warning(
+ 'Launching the run script %s for module %s failed with the following exception: %s',
+ $this->runScript,
+ $this->name,
+ $e->getMessage()
+ );
+ return false;
+ }
+ $this->registerWebIntegration();
+ $this->registered = true;
+
+ return true;
+ }
+
+ /**
+ * Get whether this module has been registered
+ *
+ * @return bool
+ */
+ public function isRegistered()
+ {
+ return $this->registered;
+ }
+
+ /**
+ * Test for an enabled module by name
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public static function exists($name)
+ {
+ return Icinga::app()->getModuleManager()->hasEnabled($name);
+ }
+
+ /**
+ * Get a module by name
+ *
+ * @param string $name
+ * @param bool $autoload
+ *
+ * @return self
+ *
+ * @throws ProgrammingError When the module is not yet loaded
+ */
+ public static function get($name, $autoload = false)
+ {
+ $manager = Icinga::app()->getModuleManager();
+ if (!$manager->hasLoaded($name)) {
+ if ($autoload === true && $manager->hasEnabled($name)) {
+ $manager->loadModule($name);
+ }
+ }
+ // Throws ProgrammingError when the module is not yet loaded
+ return $manager->getModule($name);
+ }
+
+ /**
+ * Provide an additional CSS/LESS file
+ *
+ * @param string $path The path to the file, relative to self::$cssdir
+ *
+ * @return $this
+ */
+ protected function provideCssFile($path)
+ {
+ $this->cssFiles[] = $this->cssdir . DIRECTORY_SEPARATOR . $path;
+ return $this;
+ }
+
+ /**
+ * Test if module provides css
+ *
+ * @return bool
+ */
+ public function hasCss()
+ {
+ if (file_exists($this->getCssFilename())) {
+ return true;
+ }
+
+ $this->launchConfigScript();
+ return !empty($this->cssFiles);
+ }
+
+ /**
+ * Returns the complete less file name
+ *
+ * @return string
+ */
+ public function getCssFilename()
+ {
+ return $this->cssdir . '/module.less';
+ }
+
+ /**
+ * Return the CSS/LESS files this module provides
+ *
+ * @return array
+ */
+ public function getCssFiles()
+ {
+ $this->launchConfigScript();
+ $files = $this->cssFiles;
+ if (file_exists($this->getCssFilename())) {
+ $files[] = $this->getCssFilename();
+ }
+ return $files;
+ }
+
+ /**
+ * Provide an additional Javascript file
+ *
+ * @param string $path The path to the file, relative to self::$jsdir
+ *
+ * @return $this
+ */
+ protected function provideJsFile($path)
+ {
+ $this->jsFiles[] = $this->jsdir . DIRECTORY_SEPARATOR . $path;
+ return $this;
+ }
+
+ /**
+ * Test if module provides js
+ *
+ * @return bool
+ */
+ public function hasJs()
+ {
+ if (file_exists($this->getJsFilename())) {
+ return true;
+ }
+
+ $this->launchConfigScript();
+ return !empty($this->jsFiles);
+ }
+
+ /**
+ * Returns the complete js file name
+ *
+ * @return string
+ */
+ public function getJsFilename()
+ {
+ return $this->jsdir . '/module.js';
+ }
+
+ /**
+ * Return the Javascript files this module provides
+ *
+ * @return array
+ */
+ public function getJsFiles()
+ {
+ $this->launchConfigScript();
+ $files = $this->jsFiles;
+ $files[] = $this->getJsFilename();
+ return $files;
+ }
+
+ /**
+ * Get the module name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get the module namespace
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return 'Icinga\\Module\\' . ucfirst($this->getName());
+ }
+
+ /**
+ * Get the module version
+ *
+ * @return string
+ */
+ public function getVersion()
+ {
+ return $this->metadata()->version;
+ }
+
+ /**
+ * Get the module description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->metadata()->description;
+ }
+
+ /**
+ * Get the module title (short description)
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->metadata()->title;
+ }
+
+ /**
+ * Get the module dependencies
+ *
+ * @return array
+ * @deprecated Use method getRequiredModules() instead
+ */
+ public function getDependencies()
+ {
+ return $this->metadata()->depends;
+ }
+
+ /**
+ * Get required libraries
+ *
+ * @return array
+ */
+ public function getRequiredLibraries()
+ {
+ $requiredLibraries = $this->metadata()->libraries;
+
+ // Register module requirements for ipl and reactbundle as library requirements
+ $requiredModules = $this->metadata()->modules ?: $this->metadata()->depends;
+ if (isset($requiredModules['ipl']) && ! isset($requiredLibraries['icinga-php-library'])) {
+ $requiredLibraries['icinga-php-library'] = $requiredModules['ipl'];
+ }
+
+ if (isset($requiredModules['reactbundle']) && ! isset($requiredLibraries['icinga-php-thirdparty'])) {
+ $requiredLibraries['icinga-php-thirdparty'] = $requiredModules['reactbundle'];
+ }
+
+ return $requiredLibraries;
+ }
+
+ /**
+ * Get required modules
+ *
+ * @return array
+ */
+ public function getRequiredModules()
+ {
+ $requiredModules = $this->metadata()->modules ?: $this->metadata()->depends;
+
+ $hasIcingadb = isset($requiredModules['icingadb']);
+ if (isset($requiredModules['monitoring']) && ($this->isSupportingIcingadb() || $hasIcingadb)) {
+ $requiredMods = [];
+ $icingadbVersion = true;
+ if ($hasIcingadb) {
+ $icingadbVersion = isset($requiredModules['icingadb']) ? $requiredModules['icingadb'] : true;
+ unset($requiredModules['icingadb']);
+ }
+
+ foreach ($requiredModules as $name => $version) {
+ $requiredMods[$name] = $version;
+ if ($name === 'monitoring') {
+ $requiredMods['icingadb'] = $icingadbVersion;
+ }
+ }
+
+ $requiredModules = $requiredMods;
+ }
+
+ // Both modules are deprecated and their successors are now dependencies of web itself
+ unset($requiredModules['ipl'], $requiredModules['reactbundle']);
+
+ return $requiredModules;
+ }
+
+ /**
+ * Check whether module supports icingadb
+ *
+ * @return bool
+ */
+ protected function isSupportingIcingadb()
+ {
+ $icingadbSupportingModules = [
+ 'cube' => '1.2.0',
+ 'jira' => '1.2.0',
+ 'graphite' => '1.2.0',
+ 'director' => '1.9.0',
+ 'toplevelview' => '0.4.0',
+ 'businessprocess' => '2.4.0'
+ ];
+
+ return array_key_exists($this->getName(), $icingadbSupportingModules)
+ && version_compare($this->getVersion(), $icingadbSupportingModules[$this->getName()], '>=');
+ }
+
+ /**
+ * Fetch module metadata
+ *
+ * @return object
+ */
+ protected function metadata()
+ {
+ if ($this->metadata === null) {
+ $metadata = (object) [
+ 'name' => $this->getName(),
+ 'version' => '0.0.0',
+ 'title' => null,
+ 'description' => '',
+ 'depends' => [],
+ 'libraries' => [],
+ 'modules' => []
+ ];
+
+ if (file_exists($this->metadataFile)) {
+ $key = null;
+ $simpleRequires = false;
+ $file = new File($this->metadataFile, 'r');
+ foreach ($file as $lineno => $line) {
+ $line = rtrim($line);
+
+ if ($key === 'description') {
+ if (empty($line)) {
+ $metadata->description .= "\n";
+ continue;
+ } elseif ($line[0] === ' ') {
+ $metadata->description .= $line;
+ continue;
+ }
+ } elseif (empty($line)) {
+ continue;
+ }
+
+ if (strpos($line, ':') === false) {
+ Logger::debug(
+ "Can't process line %d in %s: Line does not specify a key:value pair"
+ . " nor is it part of the description (indented with a single space)",
+ $lineno,
+ $this->metadataFile
+ );
+
+ break;
+ }
+
+ $parts = preg_split('/:\s+/', $line, 2);
+ if (count($parts) === 1) {
+ $parts[] = '';
+ }
+
+ list($key, $val) = $parts;
+
+ $key = strtolower($key);
+ switch ($key) {
+ case 'requires':
+ if ($val) {
+ $simpleRequires = true;
+ $key = 'libraries';
+ } else {
+ break;
+ }
+
+ // Shares the syntax with `Depends`
+ case ' libraries':
+ case ' modules':
+ if ($simpleRequires && $key[0] === ' ') {
+ Logger::debug(
+ 'Can\'t process line %d in %s: Requirements already registered by a previous line',
+ $lineno,
+ $this->metadataFile
+ );
+ break;
+ }
+
+ $key = ltrim($key);
+ // Shares the syntax with `Depends`
+ case 'depends':
+ if (strpos($val, ' ') === false) {
+ $metadata->{$key}[$val] = true;
+ continue 2;
+ }
+
+ $parts = preg_split('/,\s+/', $val);
+ foreach ($parts as $part) {
+ if (preg_match('/^([\w\-\/]+)\s+\((.+)\)$/', $part, $m)) {
+ $metadata->{$key}[$m[1]] = $m[2];
+ } else {
+ $metadata->{$key}[$part] = true;
+ }
+ }
+
+ break;
+ case 'description':
+ if ($metadata->title === null) {
+ $metadata->title = $val;
+ } else {
+ $metadata->description = $val;
+ }
+ break;
+
+ default:
+ $metadata->{$key} = $val;
+ }
+ }
+ }
+
+ if ($metadata->title === null) {
+ $metadata->title = $this->getName();
+ }
+
+ if ($metadata->description === '') {
+ $metadata->description = t(
+ 'This module has no description'
+ );
+ }
+
+ $this->metadata = $metadata;
+ }
+ return $this->metadata;
+ }
+
+ /**
+ * Get the module's CSS directory
+ *
+ * @return string
+ */
+ public function getCssDir()
+ {
+ return $this->cssdir;
+ }
+
+ /**
+ * Get the module's JS directory
+ *
+ * @return string
+ */
+ public function getJsDir()
+ {
+ return $this->jsdir;
+ }
+
+ /**
+ * Get the module's controller directory
+ *
+ * @return string
+ */
+ public function getControllerDir()
+ {
+ return $this->controllerdir;
+ }
+
+ /**
+ * Get the module's base directory
+ *
+ * @return string
+ */
+ public function getBaseDir()
+ {
+ return $this->basedir;
+ }
+
+ /**
+ * Get the module's application directory
+ *
+ * @return string
+ */
+ public function getApplicationDir()
+ {
+ return $this->appdir;
+ }
+
+ /**
+ * Get the module's library directory
+ *
+ * @return string
+ */
+ public function getLibDir()
+ {
+ return $this->libdir;
+ }
+
+ /**
+ * Get the module's configuration directory
+ *
+ * @return string
+ */
+ public function getConfigDir()
+ {
+ return $this->configdir;
+ }
+
+ /**
+ * Get the module's form directory
+ *
+ * @return string
+ */
+ public function getFormDir()
+ {
+ return $this->formdir;
+ }
+
+ /**
+ * Get the module config
+ *
+ * @param string $file
+ *
+ * @return Config
+ */
+ public function getConfig($file = 'config')
+ {
+ return $this->app->getConfig()->module($this->name, $file);
+ }
+
+ /**
+ * Get provided permissions
+ *
+ * @return array
+ */
+ public function getProvidedPermissions()
+ {
+ $this->launchConfigScript();
+ return $this->permissionList;
+ }
+
+ /**
+ * Get provided restrictions
+ *
+ * @return array
+ */
+ public function getProvidedRestrictions()
+ {
+ $this->launchConfigScript();
+ return $this->restrictionList;
+ }
+
+ /**
+ * Whether the module provides the given restriction
+ *
+ * @param string $name Restriction name
+ *
+ * @return bool
+ */
+ public function providesRestriction($name)
+ {
+ $this->launchConfigScript();
+ return array_key_exists($name, $this->restrictionList);
+ }
+
+ /**
+ * Whether the module provides the given permission
+ *
+ * @param string $name Permission name
+ *
+ * @return bool
+ */
+ public function providesPermission($name)
+ {
+ $this->launchConfigScript();
+ return array_key_exists($name, $this->permissionList);
+ }
+
+ /**
+ * Get the module configuration tabs
+ *
+ * @return \Icinga\Web\Widget\Tabs
+ */
+ public function getConfigTabs()
+ {
+ $this->launchConfigScript();
+ $tabs = Widget::create('tabs');
+ /** @var \Icinga\Web\Widget\Tabs $tabs */
+ $tabs->add('info', array(
+ 'url' => 'config/module',
+ 'urlParams' => array('name' => $this->getName()),
+ 'label' => 'Module: ' . $this->getName()
+ ));
+
+ if ($this->app->getModuleManager()->hasEnabled($this->name)) {
+ foreach ($this->configTabs as $name => $config) {
+ $tabs->add($name, $config);
+ }
+ }
+
+ return $tabs;
+ }
+
+ /**
+ * Whether the module provides a setup wizard
+ *
+ * @return bool
+ */
+ public function providesSetupWizard()
+ {
+ $this->launchConfigScript();
+ if ($this->setupWizard && class_exists($this->setupWizard)) {
+ $wizard = new $this->setupWizard;
+ return $wizard instanceof SetupWizard;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the module's setup wizard
+ *
+ * @return SetupWizard
+ */
+ public function getSetupWizard()
+ {
+ return new $this->setupWizard;
+ }
+
+ /**
+ * Get the module's user backends
+ *
+ * @return array
+ */
+ public function getUserBackends()
+ {
+ $this->launchConfigScript();
+ return $this->userBackends;
+ }
+
+ /**
+ * Get the module's user group backends
+ *
+ * @return array
+ */
+ public function getUserGroupBackends()
+ {
+ $this->launchConfigScript();
+ return $this->userGroupBackends;
+ }
+
+ /**
+ * Return this module's configurable navigation items
+ *
+ * @return array
+ */
+ public function getNavigationItems()
+ {
+ $this->launchConfigScript();
+ return $this->navigationItems;
+ }
+
+ /**
+ * Provide a named permission
+ *
+ * @param string $name Unique permission name
+ * @param string $description Permission description
+ *
+ * @throws IcingaException If the permission is already provided
+ */
+ protected function providePermission($name, $description)
+ {
+ if ($this->providesPermission($name)) {
+ throw new IcingaException(
+ 'Cannot provide permission "%s" twice',
+ $name
+ );
+ }
+ $this->permissionList[$name] = (object) array(
+ 'name' => $name,
+ 'description' => $description
+ );
+ }
+
+ /**
+ * Provide a named restriction
+ *
+ * @param string $name Unique restriction name
+ * @param string $description Restriction description
+ *
+ * @throws IcingaException If the restriction is already provided
+ */
+ protected function provideRestriction($name, $description)
+ {
+ if ($this->providesRestriction($name)) {
+ throw new IcingaException(
+ 'Cannot provide restriction "%s" twice',
+ $name
+ );
+ }
+ $this->restrictionList[$name] = (object) array(
+ 'name' => $name,
+ 'description' => $description
+ );
+ }
+
+ /**
+ * Provide a module config tab
+ *
+ * @param string $name Unique tab name
+ * @param array $config Tab config
+ *
+ * @return $this
+ * @throws ProgrammingError If $config lacks the key 'url'
+ */
+ protected function provideConfigTab($name, $config = array())
+ {
+ if (! array_key_exists('url', $config)) {
+ throw new ProgrammingError('A module config tab MUST provide a "url"');
+ }
+ $config['url'] = $this->getName() . '/' . ltrim($config['url'], '/');
+ $this->configTabs[$name] = $config;
+ return $this;
+ }
+
+ /**
+ * Provide a setup wizard
+ *
+ * @param string $className The name of the class
+ *
+ * @return $this
+ */
+ protected function provideSetupWizard($className)
+ {
+ $this->setupWizard = $className;
+ return $this;
+ }
+
+ /**
+ * Provide a user backend capable of authenticating users
+ *
+ * @param string $identifier The identifier of the new backend type
+ * @param string $className The name of the class
+ *
+ * @return $this
+ */
+ protected function provideUserBackend($identifier, $className)
+ {
+ $this->userBackends[strtolower($identifier)] = $className;
+ return $this;
+ }
+
+ /**
+ * Provide a user group backend
+ *
+ * @param string $identifier The identifier of the new backend type
+ * @param string $className The name of the class
+ *
+ * @return $this
+ */
+ protected function provideUserGroupBackend($identifier, $className)
+ {
+ $this->userGroupBackends[strtolower($identifier)] = $className;
+ return $this;
+ }
+
+ /**
+ * Provide a new type of configurable navigation item with a optional label and config filename
+ *
+ * @param string $type
+ * @param string $label
+ * @param string $config
+ *
+ * @return $this
+ */
+ protected function provideNavigationItem($type, $label = null, $config = null)
+ {
+ $this->navigationItems[$type] = array(
+ 'label' => $label,
+ 'config' => $config
+ );
+
+ return $this;
+ }
+
+ /**
+ * Register module namespaces on our class loader
+ *
+ * @return $this
+ */
+ protected function registerAutoloader()
+ {
+ if ($this->registeredAutoloader) {
+ return $this;
+ }
+
+ $moduleName = ucfirst($this->getName());
+
+ $this->app->getLoader()->registerNamespace(
+ 'Icinga\\Module\\' . $moduleName,
+ $this->getLibDir() . '/'. $moduleName,
+ $this->getApplicationDir()
+ );
+
+ $this->registeredAutoloader = true;
+
+ return $this;
+ }
+
+ /**
+ * Bind text domain for i18n
+ *
+ * @return $this
+ */
+ protected function registerLocales()
+ {
+ if ($this->hasLocales() && StaticTranslator::$instance instanceof GettextTranslator) {
+ StaticTranslator::$instance->addTranslationDirectory($this->localedir, $this->name);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get whether the module has translations
+ */
+ public function hasLocales()
+ {
+ return file_exists($this->localedir) && is_dir($this->localedir);
+ }
+
+ /**
+ * List all available locales
+ *
+ * @return array Locale list
+ */
+ public function listLocales()
+ {
+ $locales = array();
+ if (! $this->hasLocales()) {
+ return $locales;
+ }
+
+ $dh = opendir($this->localedir);
+ while (false !== ($file = readdir($dh))) {
+ $filename = $this->localedir . DIRECTORY_SEPARATOR . $file;
+ if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) {
+ $locales[] = $file;
+ }
+ }
+ closedir($dh);
+ sort($locales);
+ return $locales;
+ }
+
+ /**
+ * Register web integration
+ *
+ * Add controller directory to mvc
+ *
+ * @return $this
+ */
+ protected function registerWebIntegration()
+ {
+ if (! $this->app->isWeb()) {
+ return $this;
+ }
+
+ return $this
+ ->registerLocales()
+ ->registerRoutes();
+ }
+
+ /**
+ * Add routes for static content and any route added via {@link addRoute()} to the route chain
+ *
+ * @return $this
+ */
+ protected function registerRoutes()
+ {
+ $router = $this->app->getFrontController()->getRouter();
+
+ // TODO: We should not be required to do this. Please check dispatch()
+ $this->app->getfrontController()->addControllerDirectory(
+ $this->getControllerDir(),
+ $this->getName()
+ );
+
+ /** @var \Zend_Controller_Router_Rewrite $router */
+ foreach ($this->routes as $name => $route) {
+ $router->addRoute($name, $route);
+ }
+ $router->addRoute(
+ $this->name . '_jsprovider',
+ new Zend_Controller_Router_Route(
+ 'js/' . $this->name . '/:file',
+ array(
+ 'action' => 'javascript',
+ 'controller' => 'static',
+ 'module' => 'default',
+ 'module_name' => $this->name
+ )
+ )
+ );
+ $router->addRoute(
+ $this->name . '_img',
+ new Zend_Controller_Router_Route_Regex(
+ 'img/' . $this->name . '/(.+)',
+ array(
+ 'action' => 'img',
+ 'controller' => 'static',
+ 'module' => 'default',
+ 'module_name' => $this->name
+ ),
+ array(
+ 1 => 'file'
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Run module bootstrap script
+ *
+ * @return $this
+ */
+ protected function launchRunScript()
+ {
+ return $this->includeScript($this->runScript);
+ }
+
+ /**
+ * Include a php script if it is readable
+ *
+ * @param string $file File to include
+ *
+ * @return $this
+ */
+ protected function includeScript($file)
+ {
+ if (file_exists($file) && is_readable($file)) {
+ include $file;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Run module config script
+ *
+ * @return $this
+ */
+ protected function launchConfigScript()
+ {
+ if ($this->triedToLaunchConfigScript) {
+ return $this;
+ }
+ $this->triedToLaunchConfigScript = true;
+ $this->registerAutoloader();
+ return $this->includeScript($this->configScript);
+ }
+
+ protected function slashesToNamespace($class)
+ {
+ $list = explode('/', $class);
+ foreach ($list as &$part) {
+ $part = ucfirst($part);
+ }
+
+ return implode('\\', $list);
+ }
+
+ /**
+ * Provide a hook implementation
+ *
+ * @param string $name Name of the hook for which to provide an implementation
+ * @param string $implementation Fully qualified name of the class providing the hook implementation.
+ * Defaults to the module's ProvidedHook namespace plus the hook's name for the
+ * class name
+ * @param bool $alwaysRun To run the hook always (e.g. without permission check)
+ *
+ * @return $this
+ */
+ protected function provideHook($name, $implementation = null, $alwaysRun = false)
+ {
+ if ($implementation === null) {
+ $implementation = $name;
+ }
+
+ if (strpos($implementation, '\\') === false) {
+ $class = $this->getNamespace()
+ . '\\ProvidedHook\\'
+ . $this->slashesToNamespace($implementation);
+ } else {
+ $class = $implementation;
+ }
+
+ Hook::register($name, $class, $class, $alwaysRun);
+ return $this;
+ }
+
+ /**
+ * Add a route which will be added to the route chain
+ *
+ * @param string $name Name of the route
+ * @param Zend_Controller_Router_Route_Abstract $route Instance of the route
+ *
+ * @return $this
+ * @see registerRoutes()
+ */
+ protected function addRoute($name, Zend_Controller_Router_Route_Abstract $route)
+ {
+ $this->routes[$name] = $route;
+ return $this;
+ }
+}
diff --git a/library/Icinga/Application/Modules/NavigationItemContainer.php b/library/Icinga/Application/Modules/NavigationItemContainer.php
new file mode 100644
index 0000000..c906ccb
--- /dev/null
+++ b/library/Icinga/Application/Modules/NavigationItemContainer.php
@@ -0,0 +1,117 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application\Modules;
+
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Container for module navigation items
+ */
+abstract class NavigationItemContainer
+{
+ /**
+ * This navigation item's name
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * This navigation item's properties
+ *
+ * @var array
+ */
+ protected $properties;
+
+ /**
+ * Create a new NavigationItemContainer
+ *
+ * @param string $name
+ * @param array $properties
+ */
+ public function __construct($name, array $properties = array())
+ {
+ $this->name = $name;
+ $this->properties = $properties;
+ }
+
+ /**
+ * Set this menu item's name
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Return this menu item's name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set this menu item's properties
+ *
+ * @param array $properties
+ *
+ * @return $this
+ */
+ public function setProperties(array $properties)
+ {
+ $this->properties = $properties;
+ return $this;
+ }
+
+ /**
+ * Return this menu item's properties
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties ?: array();
+ }
+
+ /**
+ * Allow dynamic setters and getters for properties
+ *
+ * @param string $name
+ * @param array $arguments
+ *
+ * @return mixed
+ *
+ * @throws ProgrammingError In case the called method is not supported
+ */
+ public function __call($name, $arguments)
+ {
+ if (method_exists($this, $name)) {
+ return call_user_func(array($this, $name), $this, $arguments);
+ }
+
+ $type = substr($name, 0, 3);
+ if ($type !== 'set' && $type !== 'get') {
+ throw new ProgrammingError(
+ 'Dynamic method %s is not supported. Only getters (get*) and setters (set*) are.',
+ $name
+ );
+ }
+
+ $propertyName = strtolower(join('_', preg_split('~(?=[A-Z])~', lcfirst(substr($name, 3)))));
+ if ($type === 'set') {
+ $this->properties[$propertyName] = $arguments[0];
+ return $this;
+ } else { // $type === 'get'
+ return array_key_exists($propertyName, $this->properties) ? $this->properties[$propertyName] : null;
+ }
+ }
+}
diff --git a/library/Icinga/Application/Platform.php b/library/Icinga/Application/Platform.php
new file mode 100644
index 0000000..b1b8d90
--- /dev/null
+++ b/library/Icinga/Application/Platform.php
@@ -0,0 +1,435 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+/**
+ * Platform tests for icingaweb
+ */
+class Platform
+{
+ /**
+ * Domain name
+ *
+ * @var string
+ */
+ protected static $domain;
+
+ /**
+ * Host name
+ *
+ * @var string
+ */
+ protected static $hostname;
+
+ /**
+ * Fully qualified domain name
+ *
+ * @var string
+ */
+ protected static $fqdn;
+
+ /**
+ * Return the operating system's name
+ *
+ * @return string
+ */
+ public static function getOperatingSystemName()
+ {
+ return php_uname('s');
+ }
+
+ /**
+ * Test of windows
+ *
+ * @return bool
+ */
+ public static function isWindows()
+ {
+ return strtoupper(substr(self::getOperatingSystemName(), 0, 3)) === 'WIN';
+ }
+
+ /**
+ * Test of linux
+ *
+ * @return bool
+ */
+ public static function isLinux()
+ {
+ return strtoupper(substr(self::getOperatingSystemName(), 0, 5)) === 'LINUX';
+ }
+
+ /**
+ * Return the Linux distribution's name
+ * or 'linux' if the name could not be found out
+ * or false if the OS isn't Linux or an error occurred
+ *
+ * @param int $reliable
+ * 3: Only parse /etc/os-release (or /usr/lib/os-release).
+ * For the paranoid ones.
+ * 2: If that (3) doesn't help, check /etc/*-release, too.
+ * If something is unclear, return 'linux'.
+ * 1: Almost equal to mode 2. The possible return values also include:
+ * 'redhat' -- unclear whether RHEL/Fedora/...
+ * 'suse' -- unclear whether SLES/openSUSE/...
+ * 0: If even that (1) doesn't help, check /proc/version, too.
+ * This may not work (as expected) on LXC containers!
+ * (No reliability at all!)
+ *
+ * @return string|bool
+ */
+ public static function getLinuxDistro($reliable = 2)
+ {
+ if (! self::isLinux()) {
+ return false;
+ }
+
+ foreach (array('/etc/os-release', '/usr/lib/os-release') as $osReleaseFile) {
+ if (false === ($osRelease = @file(
+ $osReleaseFile,
+ FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
+ ))) {
+ continue;
+ }
+
+ foreach ($osRelease as $osInfo) {
+ if (false === ($res = @preg_match('/(?<!.)[ \t]*#/ms', $osInfo))) {
+ return false;
+ }
+ if ($res === 1) {
+ continue;
+ }
+
+ $matches = array();
+ if (false === ($res = @preg_match(
+ '/(?<!.)[ \t]*ID[ \t]*=[ \t]*(\'|"|)(.*?)(?:\1)[ \t]*(?!.)/msi',
+ $osInfo,
+ $matches
+ ))) {
+ return false;
+ }
+ if (! ($res === 0 || $matches[2] === '' || $matches[2] === 'linux')) {
+ return $matches[2];
+ }
+ }
+ }
+
+ if ($reliable > 2) {
+ return 'linux';
+ }
+
+ foreach (array(
+ 'fedora' => '/etc/fedora-release',
+ 'centos' => '/etc/centos-release'
+ ) as $distro => $releaseFile) {
+ if (! (false === (
+ $release = @file_get_contents($releaseFile)
+ ) || false === strpos(strtolower($release), $distro))) {
+ return $distro;
+ }
+ }
+
+ if (false !== ($release = @file_get_contents('/etc/redhat-release'))) {
+ $release = strtolower($release);
+ if (false !== strpos($release, 'red hat enterprise linux')) {
+ return 'rhel';
+ }
+ foreach (array('fedora', 'centos') as $distro) {
+ if (false !== strpos($release, $distro)) {
+ return $distro;
+ }
+ }
+ return $reliable < 2 ? 'redhat' : 'linux';
+ }
+
+ if (false !== ($release = @file_get_contents('/etc/SuSE-release'))) {
+ $release = strtolower($release);
+ foreach (array(
+ 'opensuse' => 'opensuse',
+ 'sles' => 'suse linux enterprise server',
+ 'sled' => 'suse linux enterprise desktop'
+ ) as $distro => $name) {
+ if (false !== strpos($release, $name)) {
+ return $distro;
+ }
+ }
+ return $reliable < 2 ? 'suse' : 'linux';
+ }
+
+ if ($reliable < 1) {
+ if (false === ($procVersion = @file_get_contents('/proc/version'))) {
+ return false;
+ }
+ $procVersion = strtolower($procVersion);
+ foreach (array(
+ 'redhat' => 'red hat',
+ 'suse' => 'suse linux',
+ 'ubuntu' => 'ubuntu',
+ 'debian' => 'debian'
+ ) as $distro => $name) {
+ if (false !== strpos($procVersion, $name)) {
+ return $distro;
+ }
+ }
+ }
+
+ return 'linux';
+ }
+
+ /**
+ * Test of CLI environment
+ *
+ * @return bool
+ */
+ public static function isCli()
+ {
+ if (PHP_SAPI == 'cli') {
+ return true;
+ } elseif ((PHP_SAPI == 'cgi' || PHP_SAPI == 'cgi-fcgi')
+ && empty($_SERVER['SERVER_NAME'])) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the hostname
+ *
+ * @return string
+ */
+ public static function getHostname()
+ {
+ if (self::$hostname === null) {
+ self::discoverHostname();
+ }
+ return self::$hostname;
+ }
+
+ /**
+ * Get the domain name
+ *
+ * @return string
+ */
+ public static function getDomain()
+ {
+ if (self::$domain === null) {
+ self::discoverHostname();
+ }
+ return self::$domain;
+ }
+
+ /**
+ * Get the fully qualified domain name
+ *
+ * @return string
+ */
+ public static function getFqdn()
+ {
+ if (self::$fqdn === null) {
+ self::discoverHostname();
+ }
+ return self::$fqdn;
+ }
+
+ /**
+ * Initialize domain and host strings
+ */
+ protected static function discoverHostname()
+ {
+ self::$hostname = gethostname();
+ self::$fqdn = gethostbyaddr(gethostbyname(self::$hostname));
+
+ if (substr(self::$fqdn, 0, strlen(self::$hostname)) === self::$hostname) {
+ self::$domain = substr(self::$fqdn, strlen(self::$hostname) + 1);
+ } else {
+ $parts = preg_split('~\.~', self::$hostname, 2);
+ self::$domain = array_shift($parts);
+ }
+ }
+
+ /**
+ * Return the version of PHP
+ *
+ * @return string
+ */
+ public static function getPhpVersion()
+ {
+ return phpversion();
+ }
+
+ /**
+ * Return the username PHP is running as
+ *
+ * @return string
+ */
+ public static function getPhpUser()
+ {
+ if (static::isWindows()) {
+ return get_current_user(); // http://php.net/manual/en/function.get-current-user.php#75059
+ }
+
+ if (function_exists('posix_geteuid')) {
+ $userInfo = posix_getpwuid(posix_geteuid());
+ return $userInfo['name'];
+ }
+ }
+
+ /**
+ * Test for php extension
+ *
+ * @param string $extensionName E.g. mysql, ldap
+ *
+ * @return bool
+ */
+ public static function extensionLoaded($extensionName)
+ {
+ return extension_loaded($extensionName);
+ }
+
+ /**
+ * Return the value for the given PHP configuration option
+ *
+ * @param string $option The option name for which to return the value
+ *
+ * @return string|false
+ */
+ public static function getPhpConfig($option)
+ {
+ return ini_get($option);
+ }
+
+ /**
+ * Return whether the given class exists
+ *
+ * @param string $name The name of the class to check
+ *
+ * @return bool
+ */
+ public static function classExists($name)
+ {
+ if (@class_exists($name)) {
+ return true;
+ }
+
+ if (strpos($name, '_') !== false) {
+ // Assume it's a Zend-Framework class
+ return (@include str_replace('_', '/', $name) . '.php') !== false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return whether it's possible to connect to a LDAP server
+ *
+ * Checks whether the ldap extension is loaded
+ *
+ * @return bool
+ */
+ public static function hasLdapSupport()
+ {
+ return static::extensionLoaded('ldap');
+ }
+
+ /**
+ * Return whether it's possible to connect to any of the supported database servers
+ *
+ * @return bool
+ */
+ public static function hasDatabaseSupport()
+ {
+ return static::hasMssqlSupport() || static::hasMysqlSupport() || static::hasOciSupport()
+ || static::hasOracleSupport() || static::hasPostgresqlSupport();
+ }
+
+ /**
+ * Return whether it's possible to connect to a MSSQL database
+ *
+ * Checks whether the mssql/dblib pdo or sqlsrv extension has
+ * been loaded and Zend framework adapter for MSSQL is available
+ *
+ * @return bool
+ */
+ public static function hasMssqlSupport()
+ {
+ if ((static::extensionLoaded('mssql') || static::extensionLoaded('pdo_dblib'))
+ && static::classExists('Zend_Db_Adapter_Pdo_Mssql')
+ ) {
+ return true;
+ }
+
+ return static::extensionLoaded('sqlsrv') && static::classExists('Zend_Db_Adapter_Sqlsrv');
+ }
+
+ /**
+ * Return whether it's possible to connect to a MySQL database
+ *
+ * Checks whether the mysql pdo extension has been loaded and the Zend framework adapter for MySQL is available
+ *
+ * @return bool
+ */
+ public static function hasMysqlSupport()
+ {
+ return static::extensionLoaded('pdo_mysql') && static::classExists('Zend_Db_Adapter_Pdo_Mysql');
+ }
+
+ /**
+ * Return whether it's possible to connect to a IBM DB2 database
+ *
+ * Checks whether the ibm pdo extension has been loaded and the Zend framework adapter for IBM is available
+ *
+ * @return bool
+ */
+ public static function hasIbmSupport()
+ {
+ return static::extensionLoaded('pdo_ibm') && static::classExists('Zend_Db_Adapter_Pdo_Ibm');
+ }
+
+ /**
+ * Return whether it's possible to connect to a Oracle database using OCI8
+ *
+ * Checks whether the OCI8 extension has been loaded and the Zend framework adapter for Oracle is available
+ *
+ * @return bool
+ */
+ public static function hasOciSupport()
+ {
+ return static::extensionLoaded('oci8') && static::classExists('Zend_Db_Adapter_Oracle');
+ }
+
+ /**
+ * Return whether it's possible to connect to a Oracle database using PDO_OCI
+ *
+ * Checks whether the OCI PDO extension has been loaded and the Zend framework adapter for Oci is available
+ *
+ * @return bool
+ */
+ public static function hasOracleSupport()
+ {
+ return static::extensionLoaded('pdo_oci') && static::classExists('Zend_Db_Adapter_Pdo_Mysql');
+ }
+
+ /**
+ * Return whether it's possible to connect to a PostgreSQL database
+ *
+ * Checks whether the pgsql pdo extension has been loaded and the Zend framework adapter for PostgreSQL is available
+ *
+ * @return bool
+ */
+ public static function hasPostgresqlSupport()
+ {
+ return static::extensionLoaded('pdo_pgsql') && static::classExists('Zend_Db_Adapter_Pdo_Pgsql');
+ }
+
+ /**
+ * Return whether it's possible to connect to a SQLite database
+ *
+ * Checks whether the sqlite pdo extension has been loaded and the Zend framework adapter for SQLite is available
+ *
+ * @return bool
+ */
+ public static function hasSqliteSupport()
+ {
+ return static::extensionLoaded('pdo_sqlite') && static::classExists('Zend_Db_Adapter_Pdo_Sqlite');
+ }
+}
diff --git a/library/Icinga/Application/StaticWeb.php b/library/Icinga/Application/StaticWeb.php
new file mode 100644
index 0000000..5c64dcb
--- /dev/null
+++ b/library/Icinga/Application/StaticWeb.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Application;
+
+require_once dirname(__FILE__) . '/EmbeddedWeb.php';
+
+class StaticWeb extends EmbeddedWeb
+{
+ protected function bootstrap()
+ {
+ return $this
+ ->setupErrorHandling()
+ ->loadLibraries()
+ ->loadConfig()
+ ->setupLogging()
+ ->setupLogger()
+ ->setupRequest()
+ ->setupResponse();
+ }
+}
diff --git a/library/Icinga/Application/Version.php b/library/Icinga/Application/Version.php
new file mode 100644
index 0000000..3045e8c
--- /dev/null
+++ b/library/Icinga/Application/Version.php
@@ -0,0 +1,65 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+/**
+ * Retrieve the version of Icinga Web 2
+ */
+class Version
+{
+ const VERSION = '2.11.4';
+
+ /**
+ * Get the version of this instance of Icinga Web 2
+ *
+ * @return array
+ */
+ public static function get()
+ {
+ $version = array('appVersion' => self::VERSION);
+ preg_match('/2.(\d+)\./', self::VERSION, $matches);
+ $version['docVersion'] = isset($matches[1]) ? '2.' . $matches[1] : null;
+
+ if (false !== ($appVersion = @file_get_contents(Icinga::app()->getApplicationDir('VERSION')))) {
+ $matches = array();
+ if (@preg_match('/^(?P<gitCommitID>\w+) (?P<gitCommitDate>\S+)/', $appVersion, $matches)) {
+ return array_merge($version, $matches);
+ }
+ }
+
+ $gitCommitId = static::getGitHead(Icinga::app()->getBaseDir());
+ if ($gitCommitId !== false) {
+ $version['gitCommitID'] = $gitCommitId;
+ }
+
+ return $version;
+ }
+
+ /**
+ * Get the current commit of the Git repository in the given path
+ *
+ * @param string $repo Path to the Git repository
+ * @param bool $bare Whether the Git repository is bare
+ *
+ * @return string|bool False if not available
+ */
+ public static function getGitHead($repo, $bare = false)
+ {
+ if (! $bare) {
+ $repo .= '/.git';
+ }
+
+ $head = @file_get_contents($repo . '/HEAD');
+
+ if ($head !== false) {
+ if (preg_match('/^ref: (.+)/', $head, $matches)) {
+ return @file_get_contents($repo . '/' . $matches[1]);
+ }
+
+ return $head;
+ }
+
+ return false;
+ }
+}
diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php
new file mode 100644
index 0000000..4ce9b45
--- /dev/null
+++ b/library/Icinga/Application/Web.php
@@ -0,0 +1,505 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+require_once __DIR__ . '/EmbeddedWeb.php';
+
+use ErrorException;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\Locale;
+use ipl\I18n\StaticTranslator;
+use Zend_Controller_Action_HelperBroker;
+use Zend_Controller_Front;
+use Zend_Controller_Router_Route;
+use Zend_Layout;
+use Zend_Paginator;
+use Zend_View_Helper_PaginationControl;
+use Icinga\Authentication\Auth;
+use Icinga\User;
+use Icinga\Util\DirectoryIterator;
+use Icinga\Util\TimezoneDetect;
+use Icinga\Web\Controller\Dispatcher;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Notification;
+use Icinga\Web\Session;
+use Icinga\Web\Session\Session as BaseSession;
+use Icinga\Web\StyleSheet;
+use Icinga\Web\View;
+
+/**
+ * Use this if you want to make use of Icinga functionality in other web projects
+ *
+ * Usage example:
+ * <code>
+ * use Icinga\Application\Web;
+ * Web::start();
+ * </code>
+ */
+class Web extends EmbeddedWeb
+{
+ /**
+ * View object
+ *
+ * @var View
+ */
+ private $viewRenderer;
+
+ /**
+ * Zend front controller instance
+ *
+ * @var Zend_Controller_Front
+ */
+ private $frontController;
+
+ /**
+ * Session object
+ *
+ * @var BaseSession
+ */
+ private $session;
+
+ /**
+ * User object
+ *
+ * @var User
+ */
+ private $user;
+
+ /**
+ * Identify web bootstrap
+ *
+ * @var bool
+ */
+ protected $isWeb = true;
+
+ /**
+ * Initialize all together
+ *
+ * @return $this
+ */
+ protected function bootstrap()
+ {
+ return $this
+ ->setupLogging()
+ ->setupErrorHandling()
+ ->loadLibraries()
+ ->loadConfig()
+ ->setupLogger()
+ ->setupRequest()
+ ->setupSession()
+ ->setupNotifications()
+ ->setupResponse()
+ ->setupZendMvc()
+ ->prepareInternationalization()
+ ->setupModuleManager()
+ ->loadSetupModuleIfNecessary()
+ ->loadEnabledModules()
+ ->setupRoute()
+ ->setupPagination()
+ ->setupUserBackendFactory()
+ ->setupUser()
+ ->setupTimezone()
+ ->setupInternationalization()
+ ->setupFatalErrorHandling();
+ }
+
+ /**
+ * Get themes provided by Web 2 and all enabled modules
+ *
+ * @return string[] Array of theme names as keys and values
+ */
+ public function getThemes()
+ {
+ $themes = array(StyleSheet::DEFAULT_THEME);
+ $applicationThemePath = $this->getBaseDir('public/css/themes');
+ if (DirectoryIterator::isReadable($applicationThemePath)) {
+ foreach (new DirectoryIterator($applicationThemePath, 'less') as $name => $theme) {
+ $themes[] = substr($name, 0, -5);
+ }
+ }
+ $mm = $this->getModuleManager();
+ foreach ($mm->listEnabledModules() as $moduleName) {
+ $moduleThemePath = $mm->getModule($moduleName)->getCssDir() . '/themes';
+ if (! DirectoryIterator::isReadable($moduleThemePath)) {
+ continue;
+ }
+ foreach (new DirectoryIterator($moduleThemePath, 'less') as $name => $theme) {
+ $themes[] = $moduleName . '/' . substr($name, 0, -5);
+ }
+ }
+ return array_combine($themes, $themes);
+ }
+
+ /**
+ * Prepare routing
+ *
+ * @return $this
+ */
+ private function setupRoute()
+ {
+ $this->frontController->getRouter()->addRoute(
+ 'module_javascript',
+ new Zend_Controller_Router_Route(
+ 'js/components/:module_name/:file',
+ array(
+ 'controller' => 'static',
+ 'action' => 'javascript'
+ )
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * Getter for frontController
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController()
+ {
+ return $this->frontController;
+ }
+
+ /**
+ * Getter for view
+ *
+ * @return View
+ */
+ public function getViewRenderer()
+ {
+ return $this->viewRenderer;
+ }
+
+ private function hasAccessToSharedNavigationItem(&$config, Config $navConfig)
+ {
+ // TODO: Provide a more sophisticated solution
+
+ if (isset($config['owner']) && strtolower($config['owner']) === strtolower($this->user->getUsername())) {
+ unset($config['owner']);
+ unset($config['users']);
+ unset($config['groups']);
+ return true;
+ }
+
+ if (isset($config['parent']) && $navConfig->hasSection($config['parent'])) {
+ unset($config['owner']);
+ if (isset($this->accessibleMenuItems[$config['parent']])) {
+ return $this->accessibleMenuItems[$config['parent']];
+ }
+
+ $parentConfig = $navConfig->getSection($config['parent']);
+ $this->accessibleMenuItems[$config['parent']] = $this->hasAccessToSharedNavigationItem(
+ $parentConfig,
+ $navConfig
+ );
+ return $this->accessibleMenuItems[$config['parent']];
+ }
+
+ if (isset($config['users'])) {
+ $users = array_map('trim', explode(',', strtolower($config['users'])));
+ if (in_array('*', $users, true) || in_array(strtolower($this->user->getUsername()), $users, true)) {
+ unset($config['owner']);
+ unset($config['users']);
+ unset($config['groups']);
+ return true;
+ }
+ }
+
+ if (isset($config['groups'])) {
+ $groups = array_map('trim', explode(',', strtolower($config['groups'])));
+ if (in_array('*', $groups, true)) {
+ unset($config['owner']);
+ unset($config['users']);
+ unset($config['groups']);
+ return true;
+ }
+
+ $userGroups = array_map('strtolower', $this->user->getGroups());
+ $matches = array_intersect($userGroups, $groups);
+ if (! empty($matches)) {
+ unset($config['owner']);
+ unset($config['users']);
+ unset($config['groups']);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Load and return the shared navigation of the given type
+ *
+ * @param string $type
+ *
+ * @return Navigation
+ */
+ public function getSharedNavigation($type)
+ {
+ $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type);
+
+ if ($type === 'dashboard-pane') {
+ $panes = array();
+ foreach ($config as $dashletName => $dashletConfig) {
+ if ($this->hasAccessToSharedNavigationItem($dashletConfig)) {
+ // TODO: Throw ConfigurationError if pane or url is missing
+ $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
+ }
+ }
+
+ $navigation = new Navigation();
+ foreach ($panes as $paneName => $dashlets) {
+ $navigation->addItem(
+ $paneName,
+ array(
+ 'type' => 'dashboard-pane',
+ 'dashlets' => $dashlets
+ )
+ );
+ }
+ } else {
+ $items = array();
+ foreach ($config as $name => $typeConfig) {
+ if (isset($this->accessibleMenuItems[$name])) {
+ if ($this->accessibleMenuItems[$name]) {
+ $items[$name] = $typeConfig;
+ }
+ } else {
+ if ($this->hasAccessToSharedNavigationItem($typeConfig, $config)) {
+ $this->accessibleMenuItems[$name] = true;
+ $items[$name] = $typeConfig;
+ } else {
+ $this->accessibleMenuItems[$name] = false;
+ }
+ }
+ }
+
+ $navigation = Navigation::fromConfig($items);
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Dispatch public interface
+ */
+ public function dispatch()
+ {
+ $this->frontController->dispatch($this->getRequest(), $this->getResponse());
+ }
+
+ /**
+ * Prepare Zend MVC Base
+ *
+ * @return $this
+ */
+ private function setupZendMvc()
+ {
+ Zend_Layout::startMvc(
+ array(
+ 'layout' => 'layout',
+ 'layoutPath' => $this->getApplicationDir('/layouts/scripts')
+ )
+ );
+
+ $this->setupFrontController();
+ $this->setupViewRenderer();
+ return $this;
+ }
+
+ /**
+ * Create user object
+ *
+ * @return $this
+ */
+ private function setupUser()
+ {
+ $auth = Auth::getInstance();
+ if (! $this->request->isXmlHttpRequest() && $this->request->isApiRequest() && ! $auth->isAuthenticated()) {
+ $auth->authHttp();
+ }
+ if ($auth->isAuthenticated()) {
+ $user = $auth->getUser();
+ $this->getRequest()->setUser($user);
+ $this->user = $user;
+
+ if ($user->can('user/application/stacktraces')) {
+ $displayExceptions = $this->user->getPreferences()->getValue(
+ 'icingaweb',
+ 'show_stacktraces'
+ );
+
+ if ($displayExceptions !== null) {
+ $this->frontController->setParams(
+ array(
+ 'displayExceptions' => $displayExceptions
+ )
+ );
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Initialize a session provider
+ *
+ * @return $this
+ */
+ private function setupSession()
+ {
+ $this->session = Session::create();
+ return $this;
+ }
+
+ /**
+ * Initialize notifications to remove them immediately from session
+ *
+ * @return $this
+ */
+ private function setupNotifications()
+ {
+ Notification::getInstance();
+ return $this;
+ }
+
+ /**
+ * Instantiate front controller
+ *
+ * @return $this
+ */
+ private function setupFrontController()
+ {
+ $this->frontController = Zend_Controller_Front::getInstance();
+ $this->frontController->setDispatcher(new Dispatcher());
+ $this->frontController->setRequest($this->getRequest());
+ $this->frontController->setControllerDirectory($this->getApplicationDir('/controllers'));
+
+ $displayExceptions = $this->config->get('global', 'show_stacktraces', true);
+
+ $this->frontController->setParams(
+ array(
+ 'displayExceptions' => $displayExceptions
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Register helper paths and views for renderer
+ *
+ * @return $this
+ */
+ private function setupViewRenderer()
+ {
+ $view = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ /** @var \Zend_Controller_Action_Helper_ViewRenderer $view */
+ $view->setView(new View());
+ $view->view->addHelperPath($this->getApplicationDir('/views/helpers'));
+ $view->view->setEncoding('UTF-8');
+ $view->view->headTitle()->prepend($this->config->get('global', 'project', 'Icinga'));
+ $view->view->headTitle()->setSeparator(' :: ');
+ $this->viewRenderer = $view;
+ return $this;
+ }
+
+ /**
+ * Configure pagination settings
+ *
+ * @return $this
+ */
+ private function setupPagination()
+ {
+ // TODO: document what we need for whatever reason?!
+ Zend_Paginator::addScrollingStylePrefixPath(
+ 'Icinga_Web_Paginator_ScrollingStyle_',
+ $this->getLibraryDir('Icinga/Web/Paginator/ScrollingStyle')
+ );
+
+ Zend_Paginator::addScrollingStylePrefixPath(
+ 'Icinga_Web_Paginator_ScrollingStyle',
+ 'Icinga/Web/Paginator/ScrollingStyle'
+ );
+
+ Zend_Paginator::setDefaultScrollingStyle('SlidingWithBorder');
+ Zend_View_Helper_PaginationControl::setDefaultViewPartial(
+ array('mixedPagination.phtml', 'default')
+ );
+ return $this;
+ }
+
+ /**
+ * Fatal error handling configuration
+ *
+ * @return $this
+ */
+ protected function setupFatalErrorHandling()
+ {
+ register_shutdown_function(function () {
+ $error = error_get_last();
+
+ if ($error !== null && $error['type'] === E_ERROR) {
+ $frontController = Icinga::app()->getFrontController();
+ $response = $frontController->getResponse();
+
+ $response->setException(new ErrorException(
+ $error['message'],
+ 0,
+ $error['type'],
+ $error['file'],
+ $error['line']
+ ));
+
+ // Clean PHP's fatal error stack trace and replace it with ours
+ ob_end_clean();
+ $frontController->dispatch($frontController->getRequest(), $response);
+ }
+ });
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see ApplicationBootstrap::detectTimezone() For the method documentation.
+ */
+ protected function detectTimezone()
+ {
+ $auth = Auth::getInstance();
+ if (! $auth->isAuthenticated()
+ || ($timezone = $auth->getUser()->getPreferences()->getValue('icingaweb', 'timezone')) === null
+ ) {
+ $detect = new TimezoneDetect();
+ $timezone = $detect->getTimezoneName();
+ }
+ return $timezone;
+ }
+
+ /**
+ * Setup internationalization using gettext
+ *
+ * Uses the preferred user language or the browser suggested language or our default.
+ *
+ * @return string Detected locale code
+ */
+ protected function detectLocale()
+ {
+ $auth = Auth::getInstance();
+ if ($auth->isAuthenticated()
+ && ($locale = $auth->getUser()->getPreferences()->getValue('icingaweb', 'language')) !== null
+ ) {
+ return $locale;
+ }
+
+ /** @var GettextTranslator $translator */
+ $translator = StaticTranslator::$instance;
+
+ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+ return (new Locale())->getPreferred($_SERVER['HTTP_ACCEPT_LANGUAGE'], $translator->listLocales());
+ }
+
+ return $translator->getDefaultLocale();
+ }
+}
diff --git a/library/Icinga/Application/functions.php b/library/Icinga/Application/functions.php
new file mode 100644
index 0000000..12736fb
--- /dev/null
+++ b/library/Icinga/Application/functions.php
@@ -0,0 +1,110 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+use ipl\Stdlib\Contract\Translator;
+use ipl\I18n\StaticTranslator;
+
+/**
+ * No-op translate
+ *
+ * Supposed to be used for marking a string as available for translation without actually translating it immediately.
+ * The returned string is the one given in the input. This does only work with the standard gettext macros t() and mt().
+ *
+ * @param string $messageId
+ *
+ * @return string
+ */
+function N_(string $messageId): string
+{
+ return $messageId;
+}
+
+// Workaround for test issues, this is required unless our tests are able to
+// accomplish "real" bootstrapping
+if (function_exists('t')) {
+ return;
+}
+
+if (extension_loaded('gettext')) {
+
+ /**
+ * @see Translator::translate() For the function documentation.
+ */
+ function t(string $messageId, ?string $context = null): string
+ {
+ return StaticTranslator::$instance->translate($messageId, $context);
+ }
+
+ /**
+ * @see Translator::translateInDomain() For the function documentation.
+ */
+ function mt(string $domain, string $messageId, ?string $context = null): string
+ {
+ return StaticTranslator::$instance->translateInDomain($domain, $messageId, $context);
+ }
+
+ /**
+ * @see Translator::translatePlural() For the function documentation.
+ */
+ function tp(string $messageId, string $messageId2, ?int $number, ?string $context = null): string
+ {
+ return StaticTranslator::$instance->translatePlural($messageId, $messageId2, $number ?? 0, $context);
+ }
+
+ /**
+ * @see Translator::translatePluralInDomain() For the function documentation.
+ */
+ function mtp(string $domain, string $messageId, string $messageId2, ?int $number, ?string $context = null): string
+ {
+ return StaticTranslator::$instance->translatePluralInDomain(
+ $domain,
+ $messageId,
+ $messageId2,
+ $number ?? 0,
+ $context
+ );
+ }
+
+} else {
+
+ /**
+ * @see Translator::translate() For the function documentation.
+ */
+ function t(string $messageId, ?string $context = null): string
+ {
+ return $messageId;
+ }
+
+ /**
+ * @see Translator::translate() For the function documentation.
+ */
+ function mt(string $domain, string $messageId, ?string $context = null): string
+ {
+ return $messageId;
+ }
+
+ /**
+ * @see Translator::translatePlural() For the function documentation.
+ */
+ function tp(string $messageId, string $messageId2, ?int $number, ?string $context = null): string
+ {
+ if ((int) $number !== 1) {
+ return $messageId2;
+ }
+
+ return $messageId;
+ }
+
+ /**
+ * @see Translator::translatePlural() For the function documentation.
+ */
+ function mtp(string $domain, string $messageId, string $messageId2, ?int $number, ?string $context = null): string
+ {
+ if ((int) $number !== 1) {
+ return $messageId2;
+ }
+
+ return $messageId;
+ }
+
+}
diff --git a/library/Icinga/Application/webrouter.php b/library/Icinga/Application/webrouter.php
new file mode 100644
index 0000000..d9ab30b
--- /dev/null
+++ b/library/Icinga/Application/webrouter.php
@@ -0,0 +1,106 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Application;
+
+use Icinga\Chart\Inline\PieChart;
+use Icinga\Web\Controller\StaticController;
+use Icinga\Web\JavaScript;
+use Icinga\Web\StyleSheet;
+
+error_reporting(E_ALL | E_STRICT);
+
+if (isset($_SERVER['REQUEST_URI'])) {
+ $ruri = $_SERVER['REQUEST_URI'];
+} else {
+ return false;
+}
+
+// Workaround, PHPs internal Webserver seems to mess up SCRIPT_FILENAME
+// as it prefixes it's absolute path with DOCUMENT_ROOT
+if (preg_match('/^PHP .* Development Server/', $_SERVER['SERVER_SOFTWARE'])) {
+ $script = basename($_SERVER['SCRIPT_FILENAME']);
+ $_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'] = '/' . $script;
+ $_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT']
+ . DIRECTORY_SEPARATOR
+ . $script;
+}
+
+$baseDir = $_SERVER['DOCUMENT_ROOT'];
+$baseDir = dirname($_SERVER['SCRIPT_FILENAME']);
+
+// Fix aliases
+$remove = str_replace('\\', '/', dirname($_SERVER['PHP_SELF']));
+if (substr($ruri, 0, strlen($remove)) !== $remove) {
+ return false;
+}
+$ruri = ltrim(substr($ruri, strlen($remove)), '/');
+
+if (strpos($ruri, '?') === false) {
+ $params = '';
+ $path = $ruri;
+} else {
+ list($path, $params) = preg_split('/\?/', $ruri, 2);
+}
+
+$special = array(
+ 'css/icinga.css',
+ 'css/icinga.min.css',
+ 'js/icinga.dev.js',
+ 'js/icinga.min.js'
+);
+
+if (in_array($path, $special)) {
+ include_once __DIR__ . '/EmbeddedWeb.php';
+ EmbeddedWeb::start();
+
+ switch ($path) {
+ case 'css/icinga.css':
+ Stylesheet::send();
+ exit;
+ case 'css/icinga.min.css':
+ Stylesheet::send(true);
+ exit;
+
+ case 'js/icinga.dev.js':
+ JavaScript::send();
+ exit;
+
+ case 'js/icinga.min.js':
+ JavaScript::sendMinified();
+ break;
+
+ default:
+ return false;
+ }
+} elseif ($path === 'svg/chart.php') {
+ if (!array_key_exists('data', $_GET)) {
+ return false;
+ }
+ include __DIR__ . '/EmbeddedWeb.php';
+ EmbeddedWeb::start();
+ header('Content-Type: image/svg+xml');
+ $pie = new PieChart();
+ $pie->initFromRequest();
+ $pie->toSvg();
+} elseif ($path === 'png/chart.php') {
+ if (!array_key_exists('data', $_GET)) {
+ return false;
+ }
+ include __DIR__ . '/EmbeddedWeb.php';
+ EmbeddedWeb::start();
+ header('Content-Type: image/png');
+ $pie = new PieChart();
+ $pie->initFromRequest();
+ $pie->toPng();
+} elseif (substr($path, 0, 4) === 'lib/') {
+ include_once __DIR__ . '/StaticWeb.php';
+ $app = StaticWeb::start();
+ (new StaticController())->handle($app->getRequest());
+ $app->getResponse()->sendResponse();
+} elseif (file_exists($baseDir . '/' . $path) && is_file($baseDir . '/' . $path)) {
+ return false;
+} else {
+ include __DIR__ . '/Web.php';
+ Web::start()->dispatch();
+}
diff --git a/library/Icinga/Authentication/AdmissionLoader.php b/library/Icinga/Authentication/AdmissionLoader.php
new file mode 100644
index 0000000..0c3fd3f
--- /dev/null
+++ b/library/Icinga/Authentication/AdmissionLoader.php
@@ -0,0 +1,249 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+use Generator;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Data\ConfigObject;
+use Icinga\User;
+use Icinga\Util\StringHelper;
+
+/**
+ * Retrieve restrictions and permissions for users
+ */
+class AdmissionLoader
+{
+ const LEGACY_PERMISSIONS = [
+ 'admin' => 'application/announcements',
+ 'application/stacktraces' => 'user/application/stacktraces',
+ 'application/share/navigation' => 'user/share/navigation',
+ // Migrating config/application/* would include config/modules, so that's skipped
+ //'config/application/*' => 'config/*',
+ 'config/application/general' => 'config/general',
+ 'config/application/resources' => 'config/resources',
+ 'config/application/navigation' => 'config/navigation',
+ 'config/application/userbackend' => 'config/access-control/users',
+ 'config/application/usergroupbackend' => 'config/access-control/groups',
+ 'config/authentication/*' => 'config/access-control/*',
+ 'config/authentication/users/*' => 'config/access-control/users',
+ 'config/authentication/users/show' => 'config/access-control/users',
+ 'config/authentication/users/add' => 'config/access-control/users',
+ 'config/authentication/users/edit' => 'config/access-control/users',
+ 'config/authentication/users/remove' => 'config/access-control/users',
+ 'config/authentication/groups/*' => 'config/access-control/groups',
+ 'config/authentication/groups/show' => 'config/access-control/groups',
+ 'config/authentication/groups/edit' => 'config/access-control/groups',
+ 'config/authentication/groups/add' => 'config/access-control/groups',
+ 'config/authentication/groups/remove' => 'config/access-control/groups',
+ 'config/authentication/roles/*' => 'config/access-control/roles',
+ 'config/authentication/roles/show' => 'config/access-control/roles',
+ 'config/authentication/roles/add' => 'config/access-control/roles',
+ 'config/authentication/roles/edit' => 'config/access-control/roles',
+ 'config/authentication/roles/remove' => 'config/access-control/roles'
+ ];
+
+ /** @var Role[] */
+ protected $roles;
+
+ /** @var ConfigObject */
+ protected $roleConfig;
+
+ public function __construct()
+ {
+ try {
+ $this->roleConfig = Config::app('roles');
+ } catch (NotReadableError $e) {
+ Logger::error('Can\'t access roles configuration. An exception was thrown:', $e);
+ }
+ }
+
+ /**
+ * Whether the user or groups are a member of the role
+ *
+ * @param string $username
+ * @param array $userGroups
+ * @param ConfigObject $section
+ *
+ * @return bool
+ */
+ protected function match($username, $userGroups, ConfigObject $section)
+ {
+ $username = strtolower($username);
+ if (! empty($section->users)) {
+ $users = array_map('strtolower', StringHelper::trimSplit($section->users));
+ if (in_array('*', $users)) {
+ return true;
+ }
+
+ if (in_array($username, $users)) {
+ return true;
+ }
+ }
+
+ if (! empty($section->groups)) {
+ $groups = array_map('strtolower', StringHelper::trimSplit($section->groups));
+ foreach ($userGroups as $userGroup) {
+ if (in_array(strtolower($userGroup), $groups)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Process role configuration and yield resulting roles
+ *
+ * This will also resolve any parent-child relationships.
+ *
+ * @param string $name
+ * @param ConfigObject $section
+ *
+ * @return Generator
+ * @throws ConfigurationError
+ */
+ protected function loadRole($name, ConfigObject $section)
+ {
+ if (! isset($this->roles[$name])) {
+ $permissions = $section->permissions ? StringHelper::trimSplit($section->permissions) : [];
+ $refusals = $section->refusals ? StringHelper::trimSplit($section->refusals) : [];
+
+ list($permissions, $newRefusals) = self::migrateLegacyPermissions($permissions);
+ if (! empty($newRefusals)) {
+ array_push($refusals, ...$newRefusals);
+ }
+
+ $restrictions = $section->toArray();
+ unset($restrictions['users'], $restrictions['groups']);
+ unset($restrictions['parent'], $restrictions['unrestricted']);
+ unset($restrictions['refusals'], $restrictions['permissions']);
+
+ $role = new Role();
+ $this->roles[$name] = $role
+ ->setName($name)
+ ->setRefusals($refusals)
+ ->setPermissions($permissions)
+ ->setRestrictions($restrictions)
+ ->setIsUnrestricted($section->get('unrestricted', false));
+
+ if (isset($section->parent)) {
+ $parentName = $section->parent;
+ if (! $this->roleConfig->hasSection($parentName)) {
+ Logger::error(
+ 'Failed to parse authentication configuration: Missing parent role "%s" (required by "%s")',
+ $parentName,
+ $name
+ );
+ throw new ConfigurationError(
+ t('Unable to parse authentication configuration. Check the log for more details.')
+ );
+ }
+
+ foreach ($this->loadRole($parentName, $this->roleConfig->getSection($parentName)) as $parent) {
+ if ($parent->getName() === $parentName) {
+ $role->setParent($parent);
+ $parent->addChild($role);
+
+ // Only yield main role once fully assembled
+ yield $role;
+ }
+
+ yield $parent;
+ }
+ } else {
+ yield $role;
+ }
+ } else {
+ yield $this->roles[$name];
+ }
+ }
+
+ /**
+ * Apply permissions, restrictions and roles to the given user
+ *
+ * @param User $user
+ */
+ public function applyRoles(User $user)
+ {
+ if ($this->roleConfig === null) {
+ return;
+ }
+
+ $username = $user->getUsername();
+ $userGroups = $user->getGroups();
+
+ $roles = [];
+ $permissions = [];
+ $restrictions = [];
+ $assignedRoles = [];
+ $isUnrestricted = false;
+ foreach ($this->roleConfig as $roleName => $roleConfig) {
+ $assigned = $this->match($username, $userGroups, $roleConfig);
+ if ($assigned) {
+ $assignedRoles[] = $roleName;
+ }
+
+ if (! isset($roles[$roleName]) && $assigned) {
+ foreach ($this->loadRole($roleName, $roleConfig) as $role) {
+ /** @var Role $role */
+ if (isset($roles[$role->getName()])) {
+ continue;
+ }
+
+ $roles[$role->getName()] = $role;
+
+ $permissions = array_merge(
+ $permissions,
+ array_diff($role->getPermissions(), $permissions)
+ );
+
+ $roleRestrictions = $role->getRestrictions();
+ foreach ($roleRestrictions as $name => & $restriction) {
+ $restriction = str_replace(
+ '$user.local_name$',
+ $user->getLocalUsername(),
+ $restriction
+ );
+ $restrictions[$name][] = $restriction;
+ }
+
+ $role->setRestrictions($roleRestrictions);
+
+ if (! $isUnrestricted) {
+ $isUnrestricted = $role->isUnrestricted();
+ }
+ }
+ }
+ }
+
+ $user->setAdditional('assigned_roles', $assignedRoles);
+
+ $user->setIsUnrestricted($isUnrestricted);
+ $user->setRestrictions($isUnrestricted ? [] : $restrictions);
+ $user->setPermissions($permissions);
+ $user->setRoles(array_values($roles));
+ }
+
+ public static function migrateLegacyPermissions(array $permissions)
+ {
+ $migratedGrants = [];
+ $refusals = [];
+
+ foreach ($permissions as $permission) {
+ if (array_key_exists($permission, self::LEGACY_PERMISSIONS)) {
+ $migratedGrants[] = self::LEGACY_PERMISSIONS[$permission];
+ } elseif ($permission === 'no-user/password-change') {
+ $refusals[] = 'user/password-change';
+ } else {
+ $migratedGrants[] = $permission;
+ }
+ }
+
+ return [$migratedGrants, $refusals];
+ }
+}
diff --git a/library/Icinga/Authentication/Auth.php b/library/Icinga/Authentication/Auth.php
new file mode 100644
index 0000000..f358eac
--- /dev/null
+++ b/library/Icinga/Authentication/Auth.php
@@ -0,0 +1,453 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Application\Hook\AuditHook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Authentication\User\ExternalBackend;
+use Icinga\Authentication\UserGroup\UserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotReadableError;
+use Icinga\User;
+use Icinga\User\Preferences;
+use Icinga\User\Preferences\PreferencesStore;
+use Icinga\Web\Session;
+use Icinga\Web\StyleSheet;
+
+class Auth
+{
+ /**
+ * Singleton instance
+ *
+ * @var self
+ */
+ private static $instance;
+
+ /**
+ * Request
+ *
+ * @var \Icinga\Web\Request
+ */
+ protected $request;
+
+ /**
+ * Response
+ *
+ * @var \Icinga\Web\Response
+ */
+ protected $response;
+
+ /**
+ * Authenticated user
+ *
+ * @var User|null
+ */
+ private $user;
+
+
+ /**
+ * @see getInstance()
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return self
+ */
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Get the auth chain
+ *
+ * @return AuthChain
+ */
+ public function getAuthChain()
+ {
+ return new AuthChain();
+ }
+
+ /**
+ * Get whether the user is authenticated
+ *
+ * @return bool
+ */
+ public function isAuthenticated()
+ {
+ if ($this->user !== null) {
+ return true;
+ }
+ $this->authenticateFromSession();
+ if ($this->user === null && ! $this->authExternal()) {
+ return false;
+ }
+ return true;
+ }
+
+ public function setAuthenticated(User $user, $persist = true)
+ {
+ $this->setupUser($user);
+
+ // Reload CSS if the theme changed
+ $themingConfig = Icinga::app()->getConfig()->getSection('themes');
+ $userTheme = $user->getPreferences()->getValue('icingaweb', 'theme');
+ if (! (bool) $themingConfig->get('disabled', false) && $userTheme !== null) {
+ $defaultTheme = $themingConfig->get('default', StyleSheet::DEFAULT_THEME);
+ if ($userTheme !== $defaultTheme) {
+ $this->getResponse()->setReloadCss(true);
+ }
+ }
+
+ // Also reload CSS if the theme mode changed
+ $themeMode = $user->getPreferences()->getValue('icingaweb', 'theme_mode');
+ if ($themeMode && $themeMode !== StyleSheet::DEFAULT_MODE) {
+ $this->getResponse()->setReloadCss(true);
+ }
+
+ // Reload entire layout if the locale changed
+ if (($locale = $user->getPreferences()->getValue('icingaweb', 'language')) !== null) {
+ if (setlocale(LC_ALL, 0) !== $locale && $this->getRequest()->isXmlHttpRequest()) {
+ $this->getResponse()->setHeader('X-Icinga-Redirect-Http', 'yes');
+ }
+ }
+
+ $this->user = $user;
+ if ($persist) {
+ $this->persistCurrentUser();
+ }
+
+ AuditHook::logActivity('login', 'User logged in');
+ }
+
+ /**
+ * Getter for groups belonged to authenticated user
+ *
+ * @return array
+ * @see User::getGroups
+ */
+ public function getGroups()
+ {
+ return $this->user->getGroups();
+ }
+
+ /**
+ * Get the request
+ *
+ * @return \Icinga\Web\Request
+ */
+ public function getRequest()
+ {
+ if ($this->request === null) {
+ $this->request = Icinga::app()->getRequest();
+ }
+ return $this->request;
+ }
+
+ /**
+ * Get the response
+ *
+ * @return \Icinga\Web\Response
+ */
+ public function getResponse()
+ {
+ if ($this->response === null) {
+ $this->response = Icinga::app()->getResponse();
+ }
+ return $this->response;
+ }
+
+ /**
+ * Get applied restrictions matching a given restriction name
+ *
+ * Returns a list of applied restrictions, empty if no user is
+ * authenticated
+ *
+ * @param string $restriction Restriction name
+ * @return array
+ */
+ public function getRestrictions($restriction)
+ {
+ if (! $this->isAuthenticated()) {
+ return array();
+ }
+ return $this->user->getRestrictions($restriction);
+ }
+
+ /**
+ * Returns the current user or null if no user is authenticated
+ *
+ * @return User|null
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Set the authenticated user
+ *
+ * Note that this method just sets the authenticated user and thus bypasses our default authentication process in
+ * {@link setAuthenticated()}.
+ *
+ * @param User $user
+ *
+ * @return $this
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+
+ return $this;
+ }
+
+ /**
+ * Try to authenticate the user with the current session
+ *
+ * Authentication for externally-authenticated users will be revoked if the username changed or external
+ * authentication is no longer in effect
+ */
+ public function authenticateFromSession()
+ {
+ $this->user = Session::getSession()->get('user');
+ if ($this->user !== null && $this->user->isExternalUser()) {
+ list($originUsername, $field) = $this->user->getExternalUserInformation();
+ $username = ExternalBackend::getRemoteUser($field);
+ if ($username === null || $username !== $originUsername) {
+ $this->removeAuthorization();
+ }
+ }
+ }
+
+ /**
+ * Attempt to authenticate a user from external user backends
+ *
+ * @return bool
+ */
+ protected function authExternal()
+ {
+ $user = new User('');
+ foreach ($this->getAuthChain() as $userBackend) {
+ if ($userBackend instanceof ExternalBackend) {
+ if ($userBackend->authenticate($user)) {
+ if (! $user->hasDomain()) {
+ $user->setDomain(Config::app()->get('authentication', 'default_domain'));
+ }
+ $this->setAuthenticated($user);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to authenticate a user using HTTP authentication on API requests only
+ *
+ * Supports only the Basic HTTP authentication scheme. XHR will be ignored.
+ *
+ * @return bool
+ */
+ public function authHttp()
+ {
+ $request = $this->getRequest();
+ $header = $request->getHeader('Authorization');
+ if (empty($header)) {
+ return false;
+ }
+ list($scheme) = explode(' ', $header, 2);
+ if ($scheme !== 'Basic') {
+ return false;
+ }
+ $authorization = substr($header, strlen('Basic '));
+ $credentials = base64_decode($authorization);
+ $credentials = array_filter(explode(':', $credentials, 2));
+ if (count($credentials) !== 2) {
+ // Deny empty username and/or password
+ return false;
+ }
+ $user = new User($credentials[0]);
+ if (! $user->hasDomain()) {
+ $user->setDomain(Config::app()->get('authentication', 'default_domain'));
+ }
+ $password = $credentials[1];
+ if ($this->getAuthChain()->setSkipExternalBackends(true)->authenticate($user, $password)) {
+ $this->setAuthenticated($user, false);
+ $user->setIsHttpUser(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Challenge client immediately for HTTP authentication
+ *
+ * Sends the response w/ the 401 Unauthorized status code and WWW-Authenticate header.
+ */
+ public function challengeHttp()
+ {
+ $response = $this->getResponse();
+ $response->setHttpResponseCode(401);
+ $response->setHeader('WWW-Authenticate', 'Basic realm="Icinga Web 2"');
+ $response->sendHeaders();
+ exit();
+ }
+
+ /**
+ * Whether an authenticated user has a given permission
+ *
+ * @param string $permission Permission name
+ *
+ * @return bool True if the user owns the given permission, false if not or if not authenticated
+ */
+ public function hasPermission($permission)
+ {
+ if (! $this->isAuthenticated()) {
+ return false;
+ }
+ return $this->user->can($permission);
+ }
+
+ /**
+ * Writes the current user to the session
+ */
+ public function persistCurrentUser()
+ {
+ // @TODO(el): https://dev.icinga.com/issues/10646
+ $params = session_get_cookie_params();
+ setcookie(
+ 'icingaweb2-session',
+ time(),
+ 0,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+ Session::getSession()->set('user', $this->user)->refreshId();
+ }
+
+ /**
+ * Purges the current authorization information and session
+ */
+ public function removeAuthorization()
+ {
+ AuditHook::logActivity('logout', 'User logged out');
+ $this->user = null;
+ Session::getSession()->purge();
+ }
+
+ /**
+ * Setup the given user
+ *
+ * This loads preferences, groups and roles.
+ *
+ * @param User $user
+ *
+ * @return void
+ */
+ public function setupUser(User $user)
+ {
+ // Load the user's preferences
+
+ try {
+ $config = Config::app();
+ } catch (NotReadableError $e) {
+ Logger::error(
+ new IcingaException(
+ 'Cannot load preferences for user "%s". An exception was thrown: %s',
+ $user->getUsername(),
+ $e
+ )
+ );
+ $config = new Config();
+ }
+
+ $preferencesConfig = new ConfigObject([
+ 'resource' => $config->get('global', 'config_resource')
+ ]);
+
+ try {
+ $preferencesStore = PreferencesStore::create($preferencesConfig, $user);
+ $preferences = new Preferences($preferencesStore->load());
+ } catch (Exception $e) {
+ Logger::error(
+ new IcingaException(
+ 'Cannot load preferences for user "%s". An exception was thrown: %s',
+ $user->getUsername(),
+ $e
+ )
+ );
+ $preferences = new Preferences();
+ }
+
+ $user->setPreferences($preferences);
+
+ // Load the user's groups
+ $groups = $user->getGroups();
+ $userBackendName = $user->getAdditional('backend_name');
+ foreach (Config::app('groups') as $name => $config) {
+ $groupsUserBackend = $config->user_backend;
+ if ($groupsUserBackend
+ && $groupsUserBackend !== 'none'
+ && $userBackendName !== null
+ && $groupsUserBackend !== $userBackendName
+ ) {
+ // Do not ask for Group membership if a specific User Backend
+ // has been assigned to that Group Backend, and the user has
+ // been authenticated by another User Backend
+ continue;
+ }
+
+ try {
+ $groupBackend = UserGroupBackend::create($name, $config);
+ $groupsFromBackend = $groupBackend->getMemberships($user);
+ } catch (Exception $e) {
+ Logger::error(
+ 'Can\'t get group memberships for user \'%s\' from backend \'%s\'. An exception was thrown: %s',
+ $user->getUsername(),
+ $name,
+ $e
+ );
+ continue;
+ }
+
+ if (empty($groupsFromBackend)) {
+ Logger::debug(
+ 'No groups found in backend "%s" which the user "%s" is a member of.',
+ $name,
+ $user->getUsername()
+ );
+ continue;
+ }
+
+ $groupsFromBackend = array_values($groupsFromBackend);
+ Logger::debug(
+ 'Groups found in backend "%s" for user "%s": %s',
+ $name,
+ $user->getUsername(),
+ join(', ', $groupsFromBackend)
+ );
+ $groups = array_merge($groups, array_combine($groupsFromBackend, $groupsFromBackend));
+ }
+
+ $user->setGroups($groups);
+
+ // Load the user's roles
+ $admissionLoader = new AdmissionLoader();
+ $admissionLoader->applyRoles($user);
+ }
+}
diff --git a/library/Icinga/Authentication/AuthChain.php b/library/Icinga/Authentication/AuthChain.php
new file mode 100644
index 0000000..39468e3
--- /dev/null
+++ b/library/Icinga/Authentication/AuthChain.php
@@ -0,0 +1,269 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+use Icinga\Application\Hook\AuditHook;
+use Iterator;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Authentication\User\ExternalBackend;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Authentication\User\UserBackendInterface;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\AuthenticationException;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotReadableError;
+use Icinga\User;
+
+/**
+ * Iterate user backends created from config
+ */
+class AuthChain implements Authenticatable, Iterator
+{
+ /**
+ * Authentication config file
+ *
+ * @var string
+ */
+ const AUTHENTICATION_CONFIG = 'authentication';
+
+ /**
+ * Error code if the authentication configuration was not readable
+ *
+ * @var int
+ */
+ const EPERM = 1;
+
+ /**
+ * Error code if the authentication configuration is empty
+ */
+ const EEMPTY = 2;
+
+ /**
+ * Error code if all authentication methods failed
+ *
+ * @var int
+ */
+ const EFAIL = 3;
+
+ /**
+ * Error code if not all authentication methods were available
+ *
+ * @var int
+ */
+ const ENOTALL = 4;
+
+ /**
+ * User backends configuration
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * The consecutive user backend while looping
+ *
+ * @var UserBackendInterface
+ */
+ protected $currentBackend;
+
+ /**
+ * Last error code
+ *
+ * @var int|null
+ */
+ protected $error;
+
+ /**
+ * Whether external user backends should be skipped on iteration
+ *
+ * @var bool
+ */
+ protected $skipExternalBackends = false;
+
+ /**
+ * Create a new authentication chain from config
+ *
+ * @param Config $config User backends configuration
+ */
+ public function __construct(Config $config = null)
+ {
+ if ($config === null) {
+ try {
+ $this->config = Config::app(static::AUTHENTICATION_CONFIG);
+ } catch (NotReadableError $e) {
+ $this->config = new Config();
+ $this->error = static::EPERM;
+ }
+ } else {
+ $this->config = $config;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function authenticate(User $user, $password)
+ {
+ $this->error = null;
+ $backendsTried = 0;
+ $backendsWithError = 0;
+ foreach ($this as $backend) {
+ ++$backendsTried;
+ try {
+ $authenticated = $backend->authenticate($user, $password);
+ } catch (AuthenticationException $e) {
+ Logger::error($e);
+ ++$backendsWithError;
+ continue;
+ }
+ if ($authenticated) {
+ $user->setAdditional('backend_name', $backend->getName());
+ $user->setAdditional('backend_type', $this->config->current()->get('backend'));
+ return true;
+ }
+ }
+
+ if ($backendsTried === 0) {
+ $this->error = static::EEMPTY;
+ } elseif ($backendsTried === $backendsWithError) {
+ $this->error = static::EFAIL;
+ } elseif ($backendsWithError) {
+ $this->error = static::ENOTALL;
+ } else {
+ AuditHook::logActivity('login-failed', 'User failed to authenticate', null, $user->getUsername());
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the last error code
+ *
+ * @return int|null
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Whether authentication had errors
+ *
+ * @return bool
+ */
+ public function hasError()
+ {
+ return $this->error !== null;
+ }
+
+ /**
+ * Get whether to skip external user backends on iteration
+ *
+ * @return bool
+ */
+ public function getSkipExternalBackends()
+ {
+ return $this->skipExternalBackends;
+ }
+
+ /**
+ * Set whether to skip external user backends on iteration
+ *
+ * @param bool $skipExternalBackends
+ *
+ * @return $this
+ */
+ public function setSkipExternalBackends($skipExternalBackends = true)
+ {
+ $this->skipExternalBackends = (bool) $skipExternalBackends;
+ return $this;
+ }
+
+ /**
+ * Rewind the chain
+ *
+ * @return void
+ */
+ public function rewind(): void
+ {
+ $this->currentBackend = null;
+ $this->config->rewind();
+ }
+
+ /**
+ * Get the current user backend
+ *
+ * @return UserBackendInterface
+ */
+ public function current(): UserBackendInterface
+ {
+ return $this->currentBackend;
+ }
+
+ /**
+ * Get the key of the current user backend config
+ *
+ * @return string
+ */
+ public function key(): string
+ {
+ return $this->config->key();
+ }
+
+ /**
+ * Move forward to the next user backend config
+ *
+ * @return void
+ */
+ public function next(): void
+ {
+ $this->config->next();
+ }
+
+ /**
+ * Check whether the current user backend is valid, i.e. it's enabled, not an external user backend and whether its
+ * config is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ if (! $this->config->valid()) {
+ // Stop when there are no more backends to check
+ return false;
+ }
+
+ $backendConfig = $this->config->current();
+ if ((bool) $backendConfig->get('disabled', false)) {
+ $this->next();
+ return $this->valid();
+ }
+
+ $name = $this->key();
+ try {
+ $backend = UserBackend::create($name, $backendConfig);
+ } catch (ConfigurationError $e) {
+ Logger::error(
+ new ConfigurationError(
+ 'Can\'t create authentication backend "%s". An exception was thrown:',
+ $name,
+ $e
+ )
+ );
+ $this->next();
+ return $this->valid();
+ }
+
+ if ($this->getSkipExternalBackends()
+ && $backend instanceof ExternalBackend
+ ) {
+ $this->next();
+ return $this->valid();
+ }
+
+ $this->currentBackend = $backend;
+ return true;
+ }
+}
diff --git a/library/Icinga/Authentication/Authenticatable.php b/library/Icinga/Authentication/Authenticatable.php
new file mode 100644
index 0000000..c10d6d3
--- /dev/null
+++ b/library/Icinga/Authentication/Authenticatable.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+use Icinga\User;
+
+interface Authenticatable
+{
+ /**
+ * Authenticate a user
+ *
+ * @param User $user
+ * @param string $password
+ *
+ * @return bool
+ *
+ * @throws \Icinga\Exception\AuthenticationException If authentication errors
+ */
+ public function authenticate(User $user, $password);
+}
diff --git a/library/Icinga/Authentication/Role.php b/library/Icinga/Authentication/Role.php
new file mode 100644
index 0000000..c409ba4
--- /dev/null
+++ b/library/Icinga/Authentication/Role.php
@@ -0,0 +1,334 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+class Role
+{
+ /**
+ * Name of the role
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The role from which to inherit privileges
+ *
+ * @var Role
+ */
+ protected $parent;
+
+ /**
+ * The roles to which privileges are inherited
+ *
+ * @var Role[]
+ */
+ protected $children;
+
+ /**
+ * Whether restrictions should not apply to owners of the role
+ *
+ * @var bool
+ */
+ protected $unrestricted = false;
+
+ /**
+ * Permissions of the role
+ *
+ * @var string[]
+ */
+ protected $permissions = [];
+
+ /**
+ * Refusals of the role
+ *
+ * @var string[]
+ */
+ protected $refusals = [];
+
+ /**
+ * Restrictions of the role
+ *
+ * @var string[]
+ */
+ protected $restrictions = [];
+
+ /**
+ * Get the name of the role
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the name of the role
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get the role from which privileges are inherited
+ *
+ * @return Role
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set the role from which to inherit privileges
+ *
+ * @param Role $parent
+ *
+ * @return $this
+ */
+ public function setParent(Role $parent)
+ {
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Get the roles to which privileges are inherited
+ *
+ * @return Role[]
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Set the roles to which inherit privileges
+ *
+ * @param Role[] $children
+ *
+ * @return $this
+ */
+ public function setChildren(array $children)
+ {
+ $this->children = $children;
+
+ return $this;
+ }
+
+ /**
+ * Add a role to which inherit privileges
+ *
+ * @param Role $role
+ *
+ * @return $this
+ */
+ public function addChild(Role $role)
+ {
+ $this->children[] = $role;
+
+ return $this;
+ }
+
+ /**
+ * Get whether restrictions should not apply to owners of the role
+ *
+ * @return bool
+ */
+ public function isUnrestricted()
+ {
+ return $this->unrestricted;
+ }
+
+ /**
+ * Set whether restrictions should not apply to owners of the role
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setIsUnrestricted($state)
+ {
+ $this->unrestricted = (bool) $state;
+
+ return $this;
+ }
+
+ /**
+ * Get the permissions of the role
+ *
+ * @return string[]
+ */
+ public function getPermissions()
+ {
+ return $this->permissions;
+ }
+
+ /**
+ * Set the permissions of the role
+ *
+ * @param string[] $permissions
+ *
+ * @return $this
+ */
+ public function setPermissions(array $permissions)
+ {
+ $this->permissions = $permissions;
+
+ return $this;
+ }
+
+ /**
+ * Get the refusals of the role
+ *
+ * @return string[]
+ */
+ public function getRefusals()
+ {
+ return $this->refusals;
+ }
+
+ /**
+ * Set the refusals of the role
+ *
+ * @param array $refusals
+ *
+ * @return $this
+ */
+ public function setRefusals(array $refusals)
+ {
+ $this->refusals = $refusals;
+
+ return $this;
+ }
+
+ /**
+ * Get the restrictions of the role
+ *
+ * @param string $name Optional name of the restriction
+ *
+ * @return string[]|null
+ */
+ public function getRestrictions($name = null)
+ {
+ $restrictions = $this->restrictions;
+
+ if ($name === null) {
+ return $restrictions;
+ }
+
+ if (isset($restrictions[$name])) {
+ return $restrictions[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the restrictions of the role
+ *
+ * @param string[] $restrictions
+ *
+ * @return $this
+ */
+ public function setRestrictions(array $restrictions)
+ {
+ $this->restrictions = $restrictions;
+
+ return $this;
+ }
+
+ /**
+ * Whether this role grants the given permission
+ *
+ * @param string $permission
+ * @param bool $ignoreParent Only evaluate the role's own permissions
+ * @param bool $cascadeUpwards `false` if `foo/bar/*` and `foo/bar/raboof` should not match `foo/*`
+ *
+ * @return bool
+ */
+ public function grants($permission, $ignoreParent = false, $cascadeUpwards = true)
+ {
+ foreach ($this->permissions as $grantedPermission) {
+ if ($this->match($grantedPermission, $permission, $cascadeUpwards)) {
+ return true;
+ }
+ }
+
+ if (! $ignoreParent && $this->getParent() !== null) {
+ return $this->getParent()->grants($permission, false, $cascadeUpwards);
+ }
+
+ return false;
+ }
+
+ /**
+ * Whether this role denies the given permission
+ *
+ * @param string $permission
+ * @param bool $ignoreParent Only evaluate the role's own refusals
+ *
+ * @return bool
+ */
+ public function denies($permission, $ignoreParent = false)
+ {
+ foreach ($this->refusals as $refusedPermission) {
+ if ($this->match($refusedPermission, $permission, false)) {
+ return true;
+ }
+ }
+
+ if (! $ignoreParent && $this->getParent() !== null) {
+ return $this->getParent()->denies($permission);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get whether the role expression matches the required permission
+ *
+ * @param string $roleExpression
+ * @param string $requiredPermission
+ * @param bool $cascadeUpwards `false` if `foo/bar/*` and `foo/bar/raboof` should not match `foo/*`
+ *
+ * @return bool
+ */
+ protected function match($roleExpression, $requiredPermission, $cascadeUpwards = true)
+ {
+ if ($roleExpression === '*' || $roleExpression === $requiredPermission) {
+ return true;
+ }
+
+ $requiredWildcard = strpos($requiredPermission, '*');
+ if ($requiredWildcard !== false) {
+ if (($grantedWildcard = strpos($roleExpression, '*')) !== false) {
+ $wildcard = $cascadeUpwards ? min($requiredWildcard, $grantedWildcard) : $grantedWildcard;
+ } else {
+ $wildcard = $cascadeUpwards ? $requiredWildcard : false;
+ }
+ } else {
+ $wildcard = strpos($roleExpression, '*');
+ }
+
+ if ($wildcard !== false && $wildcard > 0) {
+ if (substr($requiredPermission, 0, $wildcard) === substr($roleExpression, 0, $wildcard)) {
+ return true;
+ }
+ } elseif ($requiredPermission === $roleExpression) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/Icinga/Authentication/RolesConfig.php b/library/Icinga/Authentication/RolesConfig.php
new file mode 100644
index 0000000..ac5695f
--- /dev/null
+++ b/library/Icinga/Authentication/RolesConfig.php
@@ -0,0 +1,43 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Authentication;
+
+use Icinga\Application\Icinga;
+use Icinga\Repository\IniRepository;
+
+class RolesConfig extends IniRepository
+{
+ protected $configs = [
+ 'roles' => [
+ 'name' => 'roles',
+ 'keyColumn' => 'name'
+ ]
+ ];
+
+ protected function initializeQueryColumns()
+ {
+ $columns = [
+ 'roles' => [
+ 'parent',
+ 'name',
+ 'users',
+ 'groups',
+ 'refusals',
+ 'permissions',
+ 'unrestricted',
+ 'application/share/users',
+ 'application/share/groups'
+ ]
+ ];
+
+ $moduleManager = Icinga::app()->getModuleManager();
+ foreach ($moduleManager->listInstalledModules() as $moduleName) {
+ foreach ($moduleManager->getModule($moduleName, false)->getProvidedRestrictions() as $restriction) {
+ $columns['roles'][] = $restriction->name;
+ }
+ }
+
+ return $columns;
+ }
+}
diff --git a/library/Icinga/Authentication/User/DbUserBackend.php b/library/Icinga/Authentication/User/DbUserBackend.php
new file mode 100644
index 0000000..0e8cc6a
--- /dev/null
+++ b/library/Icinga/Authentication/User/DbUserBackend.php
@@ -0,0 +1,256 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\User;
+
+use Exception;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\AuthenticationException;
+use Icinga\Repository\DbRepository;
+use Icinga\User;
+use PDO;
+
+class DbUserBackend extends DbRepository implements UserBackendInterface, Inspectable
+{
+ /**
+ * The query columns being provided
+ *
+ * @var array
+ */
+ protected $queryColumns = array(
+ 'user' => array(
+ 'user' => 'name COLLATE utf8mb4_general_ci',
+ 'user_name' => 'name',
+ 'is_active' => 'active',
+ 'created_at' => 'UNIX_TIMESTAMP(ctime)',
+ 'last_modified' => 'UNIX_TIMESTAMP(mtime)'
+ )
+ );
+
+ /**
+ * The statement columns being provided
+ *
+ * @var array
+ */
+ protected $statementColumns = array(
+ 'user' => array(
+ 'password' => 'password_hash',
+ 'created_at' => 'ctime',
+ 'last_modified' => 'mtime'
+ )
+ );
+
+ /**
+ * The columns which are not permitted to be queried
+ *
+ * @var array
+ */
+ protected $blacklistedQueryColumns = array('user');
+
+ /**
+ * The search columns being provided
+ *
+ * @var array
+ */
+ protected $searchColumns = array('user');
+
+ /**
+ * The default sort rules to be applied on a query
+ *
+ * @var array
+ */
+ protected $sortRules = array(
+ 'user_name' => array(
+ 'columns' => array(
+ 'is_active desc',
+ 'user_name'
+ )
+ )
+ );
+
+ /**
+ * The value conversion rules to apply on a query or statement
+ *
+ * @var array
+ */
+ protected $conversionRules = array(
+ 'user' => array(
+ 'password'
+ )
+ );
+
+ /**
+ * Initialize this database user backend
+ */
+ protected function init()
+ {
+ if (! $this->ds->getTablePrefix()) {
+ $this->ds->setTablePrefix('icingaweb_');
+ }
+ }
+
+ /**
+ * Initialize this repository's filter columns
+ *
+ * @return array
+ */
+ protected function initializeFilterColumns()
+ {
+ $userLabel = t('Username') . ' ' . t('(Case insensitive)');
+ return array(
+ $userLabel => 'user',
+ t('Username') => 'user_name',
+ t('Active') => 'is_active',
+ t('Created at') => 'created_at',
+ t('Last modified') => 'last_modified'
+ );
+ }
+
+ /**
+ * Insert a table row with the given data
+ *
+ * @param string $table
+ * @param array $bind
+ *
+ * @return void
+ */
+ public function insert($table, array $bind, array $types = array())
+ {
+ $this->requireTable($table);
+ $bind['created_at'] = date('Y-m-d H:i:s');
+ $this->ds->insert(
+ $this->prependTablePrefix($table),
+ $this->requireStatementColumns($table, $bind),
+ array(
+ 'active' => PDO::PARAM_INT,
+ 'password_hash' => PDO::PARAM_LOB
+ )
+ );
+ }
+
+ /**
+ * Update table rows with the given data, optionally limited by using a filter
+ *
+ * @param string $table
+ * @param array $bind
+ * @param Filter $filter
+ */
+ public function update($table, array $bind, Filter $filter = null, array $types = array())
+ {
+ $this->requireTable($table);
+ $bind['last_modified'] = date('Y-m-d H:i:s');
+ if ($filter) {
+ $filter = $this->requireFilter($table, $filter);
+ }
+
+ $this->ds->update(
+ $this->prependTablePrefix($table),
+ $this->requireStatementColumns($table, $bind),
+ $filter,
+ array(
+ 'active' => PDO::PARAM_INT,
+ 'password_hash' => PDO::PARAM_LOB
+ )
+ );
+ }
+
+ /**
+ * Hash and return the given password
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected function persistPassword($value)
+ {
+ return password_hash($value, PASSWORD_DEFAULT);
+ }
+
+ /**
+ * Fetch the hashed password for the given user
+ *
+ * @param string $username The name of the user
+ *
+ * @return string
+ */
+ protected function getPasswordHash($username)
+ {
+ if ($this->ds->getDbType() === 'pgsql') {
+ // Since PostgreSQL version 9.0 the default value for bytea_output is 'hex' instead of 'escape'
+ $columns = array('password_hash' => 'ENCODE(password_hash, \'escape\')');
+ } else {
+ $columns = array('password_hash');
+ }
+
+ $nameColumn = 'name';
+ if ($this->ds->getDbType() === 'mysql') {
+ $username = strtolower($username);
+ $nameColumn = 'BINARY LOWER(name)';
+ }
+
+ $query = $this->ds->select()
+ ->from($this->prependTablePrefix('user'), $columns)
+ ->where($nameColumn, $username)
+ ->where('active', true);
+
+ $statement = $this->ds->getDbAdapter()->prepare($query->getSelectQuery());
+ $statement->execute();
+ $statement->bindColumn(1, $lob, PDO::PARAM_LOB);
+ $statement->fetch(PDO::FETCH_BOUND);
+ if (is_resource($lob)) {
+ $lob = stream_get_contents($lob);
+ }
+
+ if ($lob === null) {
+ return '';
+ }
+
+ return $this->ds->getDbType() === 'pgsql' ? pg_unescape_bytea($lob) : $lob;
+ }
+
+ /**
+ * Authenticate the given user
+ *
+ * @param User $user
+ * @param string $password
+ *
+ * @return bool True on success, false on failure
+ *
+ * @throws AuthenticationException In case authentication is not possible due to an error
+ */
+ public function authenticate(User $user, $password)
+ {
+ try {
+ return password_verify(
+ $password,
+ $this->getPasswordHash($user->getUsername())
+ );
+ } catch (Exception $e) {
+ throw new AuthenticationException(
+ 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
+ $user->getUsername(),
+ $this->getName(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Inspect this object to gain extended information about its health
+ *
+ * @return Inspection The inspection result
+ */
+ public function inspect()
+ {
+ $insp = new Inspection('Db User Backend');
+ $insp->write($this->ds->inspect());
+ try {
+ $insp->write(sprintf('%s active users', $this->select()->where('is_active', true)->count()));
+ } catch (Exception $e) {
+ $insp->error(sprintf('Query failed: %s', $e->getMessage()));
+ }
+ return $insp;
+ }
+}
diff --git a/library/Icinga/Authentication/User/DomainAwareInterface.php b/library/Icinga/Authentication/User/DomainAwareInterface.php
new file mode 100644
index 0000000..3ff9c31
--- /dev/null
+++ b/library/Icinga/Authentication/User/DomainAwareInterface.php
@@ -0,0 +1,17 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\User;
+
+/**
+ * Interface for user backends that are responsible for a specific domain
+ */
+interface DomainAwareInterface
+{
+ /**
+ * Get the domain the backend is responsible for
+ *
+ * @return string
+ */
+ public function getDomain();
+}
diff --git a/library/Icinga/Authentication/User/ExternalBackend.php b/library/Icinga/Authentication/User/ExternalBackend.php
new file mode 100644
index 0000000..6e79928
--- /dev/null
+++ b/library/Icinga/Authentication/User/ExternalBackend.php
@@ -0,0 +1,124 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\User;
+
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\User;
+
+/**
+ * Test login with external authentication mechanism, e.g. Apache
+ */
+class ExternalBackend implements UserBackendInterface
+{
+ /**
+ * Possible variables where to read the user from
+ *
+ * @var string[]
+ */
+ public static $remoteUserEnvvars = array('REMOTE_USER', 'REDIRECT_REMOTE_USER');
+
+ /**
+ * The name of this backend
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * Regexp expression to strip values from a username
+ *
+ * @var string
+ */
+ protected $stripUsernameRegexp;
+
+ /**
+ * Create new authentication backend of type "external"
+ *
+ * @param ConfigObject $config
+ */
+ public function __construct(ConfigObject $config)
+ {
+ $this->stripUsernameRegexp = $config->get('strip_username_regexp');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Get the remote user from environment or $_SERVER, if any
+ *
+ * @param string $variable The name of the variable where to read the user from
+ *
+ * @return string|null
+ */
+ public static function getRemoteUser($variable = 'REMOTE_USER')
+ {
+ $username = getenv($variable);
+ if (! empty($username)) {
+ return $username;
+ }
+
+ if (array_key_exists($variable, $_SERVER) && ! empty($_SERVER[$variable])) {
+ return $_SERVER[$variable];
+ }
+ }
+
+ /**
+ * Get the remote user information from environment or $_SERVER, if any
+ *
+ * @return array Contains always two entries, the username and origin which may both set to null.
+ */
+ public static function getRemoteUserInformation()
+ {
+ foreach (static::$remoteUserEnvvars as $envVar) {
+ $username = static::getRemoteUser($envVar);
+ if ($username !== null) {
+ return array($username, $envVar);
+ }
+ }
+
+ return array(null, null);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function authenticate(User $user, $password = null)
+ {
+ list($username, $field) = static::getRemoteUserInformation();
+ if ($username !== null) {
+ $user->setExternalUserInformation($username, $field);
+
+ if ($this->stripUsernameRegexp) {
+ $stripped = @preg_replace($this->stripUsernameRegexp, '', $username);
+ if ($stripped === false) {
+ Logger::error('Failed to strip external username. The configured regular expression is invalid.');
+ return false;
+ }
+
+ $username = $stripped;
+ }
+
+ $user->setUsername($username);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/Icinga/Authentication/User/LdapUserBackend.php b/library/Icinga/Authentication/User/LdapUserBackend.php
new file mode 100644
index 0000000..8c8a230
--- /dev/null
+++ b/library/Icinga/Authentication/User/LdapUserBackend.php
@@ -0,0 +1,477 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\User;
+
+use DateTime;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Exception\AuthenticationException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Repository\LdapRepository;
+use Icinga\Repository\RepositoryQuery;
+use Icinga\Protocol\Ldap\LdapException;
+use Icinga\User;
+
+class LdapUserBackend extends LdapRepository implements UserBackendInterface, DomainAwareInterface, Inspectable
+{
+ /**
+ * The base DN to use for a query
+ *
+ * @var string
+ */
+ protected $baseDn;
+
+ /**
+ * The objectClass where look for users
+ *
+ * @var string
+ */
+ protected $userClass;
+
+ /**
+ * The attribute name where to find a user's name
+ *
+ * @var string
+ */
+ protected $userNameAttribute;
+
+ /**
+ * The custom LDAP filter to apply on search queries
+ *
+ * @var string
+ */
+ protected $filter;
+
+ /**
+ * The domain the backend is responsible for
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * The columns which are not permitted to be queried
+ *
+ * @var array
+ */
+ protected $blacklistedQueryColumns = array('user');
+
+ /**
+ * The search columns being provided
+ *
+ * @var array
+ */
+ protected $searchColumns = array('user');
+
+ /**
+ * The default sort rules to be applied on a query
+ *
+ * @var array
+ */
+ protected $sortRules = array(
+ 'user_name' => array(
+ 'columns' => array(
+ 'is_active desc',
+ 'user_name'
+ )
+ )
+ );
+
+ /**
+ * Set the base DN to use for a query
+ *
+ * @param string $baseDn
+ *
+ * @return $this
+ */
+ public function setBaseDn($baseDn)
+ {
+ if ($baseDn && ($baseDn = trim($baseDn))) {
+ $this->baseDn = $baseDn;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the base DN to use for a query
+ *
+ * @return string
+ */
+ public function getBaseDn()
+ {
+ return $this->baseDn;
+ }
+
+ /**
+ * Set the objectClass where to look for users
+ *
+ * @param string $userClass
+ *
+ * @return $this
+ */
+ public function setUserClass($userClass)
+ {
+ $this->userClass = $this->getNormedAttribute($userClass);
+ return $this;
+ }
+
+ /**
+ * Return the objectClass where to look for users
+ *
+ * @return string
+ */
+ public function getUserClass()
+ {
+ return $this->userClass;
+ }
+
+ /**
+ * Set the attribute name where to find a user's name
+ *
+ * @param string $userNameAttribute
+ *
+ * @return $this
+ */
+ public function setUserNameAttribute($userNameAttribute)
+ {
+ $this->userNameAttribute = $this->getNormedAttribute($userNameAttribute);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a user's name
+ *
+ * @return string
+ */
+ public function getUserNameAttribute()
+ {
+ return $this->userNameAttribute;
+ }
+
+ /**
+ * Set the custom LDAP filter to apply on search queries
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setFilter($filter)
+ {
+ if ($filter && ($filter = trim($filter))) {
+ if ($filter[0] === '(') {
+ $filter = substr($filter, 1, -1);
+ }
+
+ $this->filter = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the custom LDAP filter to apply on search queries
+ *
+ * @return string
+ */
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function getDomain()
+ {
+ return $this->domain;
+ }
+
+ /**
+ * Set the domain the backend is responsible for
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setDomain($domain)
+ {
+ if ($domain && ($domain = trim($domain))) {
+ $this->domain = $domain;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Initialize this repository's virtual tables
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case $this->userClass has not been set yet
+ */
+ protected function initializeVirtualTables()
+ {
+ if ($this->userClass === null) {
+ throw new ProgrammingError('It is required to set the object class where to find users first');
+ }
+
+ return array(
+ 'user' => $this->userClass
+ );
+ }
+
+ /**
+ * Initialize this repository's query columns
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case $this->userNameAttribute has not been set yet
+ */
+ protected function initializeQueryColumns()
+ {
+ if ($this->userNameAttribute === null) {
+ throw new ProgrammingError('It is required to set a attribute name where to find a user\'s name first');
+ }
+
+ if ($this->ds->getCapabilities()->isActiveDirectory()) {
+ $isActiveAttribute = 'userAccountControl';
+ $createdAtAttribute = 'whenCreated';
+ $lastModifiedAttribute = 'whenChanged';
+ } else {
+ // TODO(jom): Elaborate whether it is possible to add dynamic support for the ppolicy
+ $isActiveAttribute = 'shadowExpire';
+
+ $createdAtAttribute = 'createTimestamp';
+ $lastModifiedAttribute = 'modifyTimestamp';
+ }
+
+ return array(
+ 'user' => array(
+ 'user' => $this->userNameAttribute,
+ 'user_name' => $this->userNameAttribute,
+ 'is_active' => $isActiveAttribute,
+ 'created_at' => $createdAtAttribute,
+ 'last_modified' => $lastModifiedAttribute
+ )
+ );
+ }
+
+ /**
+ * Initialize this repository's filter columns
+ *
+ * @return array
+ */
+ protected function initializeFilterColumns()
+ {
+ return array(
+ t('Username') => 'user_name',
+ t('Active') => 'is_active',
+ t('Created At') => 'created_at',
+ t('Last modified') => 'last_modified'
+ );
+ }
+
+ /**
+ * Initialize this repository's conversion rules
+ *
+ * @return array
+ */
+ protected function initializeConversionRules()
+ {
+ if ($this->ds->getCapabilities()->isActiveDirectory()) {
+ $stateConverter = 'user_account_control';
+ } else {
+ $stateConverter = 'shadow_expire';
+ }
+
+ return array(
+ 'user' => array(
+ 'is_active' => $stateConverter,
+ 'created_at' => 'generalized_time',
+ 'last_modified' => 'generalized_time'
+ )
+ );
+ }
+
+ /**
+ * Return whether the given userAccountControl value defines that a user is permitted to login
+ *
+ * @param string|null $value
+ *
+ * @return bool
+ */
+ protected function retrieveUserAccountControl($value)
+ {
+ if ($value === null) {
+ return $value;
+ }
+
+ $ADS_UF_ACCOUNTDISABLE = 2;
+ return ((int) $value & $ADS_UF_ACCOUNTDISABLE) === 0;
+ }
+
+ /**
+ * Return whether the given shadowExpire value defines that a user is permitted to login
+ *
+ * @param string|null $value
+ *
+ * @return bool
+ */
+ protected function retrieveShadowExpire($value)
+ {
+ if ($value === null) {
+ return $value;
+ }
+
+ $now = new DateTime();
+ $bigBang = clone $now;
+ $bigBang->setTimestamp(0);
+ return ((int) $value) >= $bigBang->diff($now)->days;
+ }
+
+ /**
+ * Validate that the requested table exists
+ *
+ * @param string $table The table to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case the given table does not exist
+ */
+ public function requireTable($table, RepositoryQuery $query = null)
+ {
+ if ($query !== null) {
+ $query->getQuery()->setBase($this->baseDn);
+ if ($this->filter) {
+ $query->getQuery()->setNativeFilter($this->filter);
+ }
+ }
+
+ return parent::requireTable($table, $query);
+ }
+
+ /**
+ * Validate that the given column is a valid query target and return it or the actual name if it's an alias
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid query column
+ */
+ public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
+ {
+ $column = parent::requireQueryColumn($table, $name, $query);
+ if ($name === 'user_name' && $query !== null) {
+ $query->getQuery()->setUnfoldAttribute('user_name');
+ }
+
+ return $column;
+ }
+
+ /**
+ * Authenticate the given user
+ *
+ * @param User $user
+ * @param string $password
+ *
+ * @return bool True on success, false on failure
+ *
+ * @throws AuthenticationException In case authentication is not possible due to an error
+ */
+ public function authenticate(User $user, $password)
+ {
+ if ($this->domain !== null) {
+ if (! $user->hasDomain() || strtolower($user->getDomain()) !== strtolower($this->domain)) {
+ return false;
+ }
+
+ $username = $user->getLocalUsername();
+ } else {
+ $username = $user->getUsername();
+ }
+
+ try {
+ $userDn = $this
+ ->select()
+ ->where('user_name', str_replace('*', '', $username))
+ ->getQuery()
+ ->setUsePagedResults(false)
+ ->fetchDn();
+ if ($userDn === null) {
+ return false;
+ }
+
+ $validCredentials = $this->ds->testCredentials($userDn, $password);
+ if ($validCredentials) {
+ $user->setAdditional('ldap_dn', $userDn);
+ }
+
+ return $validCredentials;
+ } catch (LdapException $e) {
+ throw new AuthenticationException(
+ 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
+ $username,
+ $this->getName(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Inspect if this LDAP User Backend is working as expected by probing the backend
+ * and testing if thea uthentication is possible
+ *
+ * Try to bind to the backend and fetch a single user to check if:
+ * <ul>
+ * <li>Connection credentials are correct and the bind is possible</li>
+ * <li>At least one user exists</li>
+ * <li>The specified userClass has the property specified by userNameAttribute</li>
+ * </ul>
+ *
+ * @return Inspection Inspection result
+ */
+ public function inspect()
+ {
+ $result = new Inspection('Ldap User Backend');
+
+ // inspect the used connection to get more diagnostic info in case the connection is not working
+ $result->write($this->ds->inspect());
+ try {
+ try {
+ $res = $this->select()->fetchRow();
+ } catch (LdapException $e) {
+ throw new AuthenticationException('Connection not possible', $e);
+ }
+ $result->write('Searching for: ' . sprintf(
+ 'objectClass "%s" in DN "%s" (Filter: %s)',
+ $this->userClass,
+ $this->baseDn ?: $this->ds->getDn(),
+ $this->filter ?: 'None'
+ ));
+ if ($res === false) {
+ throw new AuthenticationException('Error, no users found in backend');
+ }
+ $result->write(sprintf('%d users found in backend', $this->select()->count()));
+ if (! isset($res->user_name)) {
+ throw new AuthenticationException(
+ 'UserNameAttribute "%s" not existing in objectClass "%s"',
+ $this->userNameAttribute,
+ $this->userClass
+ );
+ }
+ } catch (AuthenticationException $e) {
+ if (($previous = $e->getPrevious()) !== null) {
+ $result->error($previous->getMessage());
+ } else {
+ $result->error($e->getMessage());
+ }
+ } catch (Exception $e) {
+ $result->error(sprintf('Unable to validate authentication: %s', $e->getMessage()));
+ }
+ return $result;
+ }
+}
diff --git a/library/Icinga/Authentication/User/UserBackend.php b/library/Icinga/Authentication/User/UserBackend.php
new file mode 100644
index 0000000..f2059ed
--- /dev/null
+++ b/library/Icinga/Authentication/User/UserBackend.php
@@ -0,0 +1,257 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\User;
+
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Application\Icinga;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Util\ConfigAwareFactory;
+
+/**
+ * Factory for user backends
+ */
+class UserBackend implements ConfigAwareFactory
+{
+ /**
+ * The default user backend types provided by Icinga Web 2
+ *
+ * @var array
+ */
+ protected static $defaultBackends = array(
+ 'external',
+ 'db',
+ 'ldap',
+ 'msldap'
+ );
+
+ /**
+ * The registered custom user backends with their identifier as key and class name as value
+ *
+ * @var array
+ */
+ protected static $customBackends;
+
+ /**
+ * User backend configuration
+ *
+ * @var Config
+ */
+ private static $backends;
+
+ /**
+ * Set user backend configuration
+ *
+ * @param Config $config
+ */
+ public static function setConfig($config)
+ {
+ self::$backends = $config;
+ }
+
+ /**
+ * Return the configuration of all existing user backends
+ *
+ * @return Config
+ */
+ public static function getBackendConfigs()
+ {
+ self::assertBackendsExist();
+ return self::$backends;
+ }
+
+ /**
+ * Check if any user backends exist. If not, throw an error.
+ *
+ * @throws ConfigurationError
+ */
+ private static function assertBackendsExist()
+ {
+ if (self::$backends === null) {
+ throw new ConfigurationError(
+ 'User backends not set up. Please contact your Icinga Web administrator'
+ );
+ }
+ }
+
+ /**
+ * Register all custom user backends from all loaded modules
+ */
+ protected static function registerCustomUserBackends()
+ {
+ if (static::$customBackends !== null) {
+ return;
+ }
+
+ static::$customBackends = array();
+ $providedBy = array();
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ foreach ($module->getUserBackends() as $identifier => $className) {
+ if (array_key_exists($identifier, $providedBy)) {
+ Logger::warning(
+ 'Cannot register user backend of type "%s" provided by module "%s".'
+ . ' The type is already provided by module "%s"',
+ $identifier,
+ $module->getName(),
+ $providedBy[$identifier]
+ );
+ } elseif (in_array($identifier, static::$defaultBackends)) {
+ Logger::warning(
+ 'Cannot register user backend of type "%s" provided by module "%s".'
+ . ' The type is a default type provided by Icinga Web 2',
+ $identifier,
+ $module->getName()
+ );
+ } else {
+ $providedBy[$identifier] = $module->getName();
+ static::$customBackends[$identifier] = $className;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get config forms of all custom user backends
+ */
+ public static function getCustomBackendConfigForms()
+ {
+ $customBackendConfigForms = [];
+ static::registerCustomUserBackends();
+ foreach (self::$customBackends as $customBackendType => $customBackendClass) {
+ if (method_exists($customBackendClass, 'getConfigurationFormClass')) {
+ $customBackendConfigForms[$customBackendType] = $customBackendClass::getConfigurationFormClass();
+ }
+ }
+
+ return $customBackendConfigForms;
+ }
+
+ /**
+ * Return the class for the given custom user backend
+ *
+ * @param string $identifier The identifier of the custom user backend
+ *
+ * @return string|null The name of the class or null in case there was no
+ * backend found with the given identifier
+ *
+ * @throws ConfigurationError In case the class associated to the given identifier does not exist
+ */
+ protected static function getCustomUserBackend($identifier)
+ {
+ static::registerCustomUserBackends();
+ if (array_key_exists($identifier, static::$customBackends)) {
+ $className = static::$customBackends[$identifier];
+ if (! class_exists($className)) {
+ throw new ConfigurationError(
+ 'Cannot utilize user backend of type "%s". Class "%s" does not exist',
+ $identifier,
+ $className
+ );
+ }
+
+ return $className;
+ }
+ }
+
+ /**
+ * Create and return a user backend with the given name and given configuration applied to it
+ *
+ * @param string $name
+ * @param ConfigObject $backendConfig
+ *
+ * @return UserBackendInterface
+ *
+ * @throws ConfigurationError
+ */
+ public static function create($name, ConfigObject $backendConfig = null)
+ {
+ if ($backendConfig === null) {
+ self::assertBackendsExist();
+ if (self::$backends->hasSection($name)) {
+ $backendConfig = self::$backends->getSection($name);
+ } else {
+ throw new ConfigurationError('User backend "%s" does not exist', $name);
+ }
+ }
+
+ if ($backendConfig->name !== null) {
+ $name = $backendConfig->name;
+ }
+
+ if (! ($backendType = strtolower($backendConfig->backend))) {
+ throw new ConfigurationError(
+ 'Authentication configuration for user backend "%s" is missing the \'backend\' directive',
+ $name
+ );
+ }
+ if ($backendType === 'external') {
+ $backend = new ExternalBackend($backendConfig);
+ $backend->setName($name);
+ return $backend;
+ }
+ if (in_array($backendType, static::$defaultBackends)) {
+ // The default backend check is the first one because of performance reasons:
+ // Do not attempt to load a custom user backend unless it's actually required
+ } elseif (($customClass = static::getCustomUserBackend($backendType)) !== null) {
+ $backend = new $customClass($backendConfig);
+ if (! is_a($backend, 'Icinga\Authentication\User\UserBackendInterface')) {
+ throw new ConfigurationError(
+ 'Cannot utilize user backend of type "%s". Class "%s" does not implement UserBackendInterface',
+ $backendType,
+ $customClass
+ );
+ }
+
+ $backend->setName($name);
+ return $backend;
+ } else {
+ throw new ConfigurationError(
+ 'Authentication configuration for user backend "%s" defines an invalid backend type.'
+ . ' Backend type "%s" is not supported',
+ $name,
+ $backendType
+ );
+ }
+
+ if ($backendConfig->resource === null) {
+ throw new ConfigurationError(
+ 'Authentication configuration for user backend "%s" is missing the \'resource\' directive',
+ $name
+ );
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($backendConfig->resource);
+ if ($backendType === 'db' && $resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ $resource = ResourceFactory::createResource($resourceConfig);
+ switch ($backendType) {
+ case 'db':
+ $backend = new DbUserBackend($resource);
+ break;
+ case 'msldap':
+ $backend = new LdapUserBackend($resource);
+ $backend->setBaseDn($backendConfig->base_dn);
+ $backend->setUserClass($backendConfig->get('user_class', 'user'));
+ $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'sAMAccountName'));
+ $backend->setFilter($backendConfig->filter);
+ $backend->setDomain($backendConfig->domain);
+ break;
+ case 'ldap':
+ $backend = new LdapUserBackend($resource);
+ $backend->setBaseDn($backendConfig->base_dn);
+ $backend->setUserClass($backendConfig->get('user_class', 'inetOrgPerson'));
+ $backend->setUserNameAttribute($backendConfig->get('user_name_attribute', 'uid'));
+ $backend->setFilter($backendConfig->filter);
+ $backend->setDomain($backendConfig->domain);
+ break;
+ }
+
+ $backend->setName($name);
+ return $backend;
+ }
+}
diff --git a/library/Icinga/Authentication/User/UserBackendInterface.php b/library/Icinga/Authentication/User/UserBackendInterface.php
new file mode 100644
index 0000000..4660eb0
--- /dev/null
+++ b/library/Icinga/Authentication/User/UserBackendInterface.php
@@ -0,0 +1,39 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\User;
+
+use Icinga\Authentication\Authenticatable;
+use Icinga\User;
+
+/**
+ * Interface for user backends
+ */
+interface UserBackendInterface extends Authenticatable
+{
+ /**
+ * Set this backend's name
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name);
+
+ /**
+ * Return this backend's name
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Return this backend's configuration form class path
+ *
+ * This is not part of the interface to not break existing implementations.
+ * If you need a custom backend form, implement this method.
+ *
+ * @return string
+ */
+ //public static function getConfigurationFormClass();
+}
diff --git a/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php
new file mode 100644
index 0000000..66db97f
--- /dev/null
+++ b/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php
@@ -0,0 +1,325 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\UserGroup;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Exception\NotFoundError;
+use Icinga\Repository\DbRepository;
+use Icinga\Repository\RepositoryQuery;
+use Icinga\User;
+
+class DbUserGroupBackend extends DbRepository implements Inspectable, UserGroupBackendInterface
+{
+ /**
+ * The query columns being provided
+ *
+ * @var array
+ */
+ protected $queryColumns = array(
+ 'group' => array(
+ 'group_id' => 'g.id',
+ 'group' => 'g.name COLLATE utf8mb4_general_ci',
+ 'group_name' => 'g.name',
+ 'parent' => 'g.parent',
+ 'created_at' => 'UNIX_TIMESTAMP(g.ctime)',
+ 'last_modified' => 'UNIX_TIMESTAMP(g.mtime)'
+ ),
+ 'group_membership' => array(
+ 'group_id' => 'gm.group_id',
+ 'user' => 'gm.username COLLATE utf8mb4_general_ci',
+ 'user_name' => 'gm.username',
+ 'created_at' => 'UNIX_TIMESTAMP(gm.ctime)',
+ 'last_modified' => 'UNIX_TIMESTAMP(gm.mtime)'
+ )
+ );
+
+ /**
+ * The table aliases being applied
+ *
+ * @var array
+ */
+ protected $tableAliases = array(
+ 'group' => 'g',
+ 'group_membership' => 'gm'
+ );
+
+ /**
+ * The statement columns being provided
+ *
+ * @var array
+ */
+ protected $statementColumns = array(
+ 'group' => array(
+ 'group_id' => 'id',
+ 'group_name' => 'name',
+ 'parent' => 'parent',
+ 'created_at' => 'ctime',
+ 'last_modified' => 'mtime'
+ ),
+ 'group_membership' => array(
+ 'group_id' => 'group_id',
+ 'group_name' => 'group_id',
+ 'user_name' => 'username',
+ 'created_at' => 'ctime',
+ 'last_modified' => 'mtime'
+ )
+ );
+
+ /**
+ * The columns which are not permitted to be queried
+ *
+ * @var array
+ */
+ protected $blacklistedQueryColumns = array('group', 'user');
+
+ /**
+ * The search columns being provided
+ *
+ * @var array
+ */
+ protected $searchColumns = array('group', 'user');
+
+ /**
+ * The value conversion rules to apply on a query or statement
+ *
+ * @var array
+ */
+ protected $conversionRules = array(
+ 'group' => array(
+ 'parent' => 'group_id'
+ ),
+ 'group_membership' => array(
+ 'group_name' => 'group_id'
+ )
+ );
+
+ /**
+ * Initialize this database user group backend
+ */
+ protected function init()
+ {
+ if (! $this->ds->getTablePrefix()) {
+ $this->ds->setTablePrefix('icingaweb_');
+ }
+ }
+
+ /**
+ * Initialize this repository's filter columns
+ *
+ * @return array
+ */
+ protected function initializeFilterColumns()
+ {
+ $userLabel = t('Username') . ' ' . t('(Case insensitive)');
+ $groupLabel = t('User Group') . ' ' . t('(Case insensitive)');
+ return array(
+ $userLabel => 'user',
+ t('Username') => 'user_name',
+ $groupLabel => 'group',
+ t('User Group') => 'group_name',
+ t('Parent') => 'parent',
+ t('Created At') => 'created_at',
+ t('Last modified') => 'last_modified'
+ );
+ }
+
+ /**
+ * Insert a table row with the given data
+ *
+ * @param string $table
+ * @param array $bind
+ */
+ public function insert($table, array $bind, array $types = array())
+ {
+ $bind['created_at'] = date('Y-m-d H:i:s');
+ parent::insert($table, $bind);
+ }
+
+ /**
+ * Update table rows with the given data, optionally limited by using a filter
+ *
+ * @param string $table
+ * @param array $bind
+ * @param Filter $filter
+ */
+ public function update($table, array $bind, Filter $filter = null, array $types = array())
+ {
+ $bind['last_modified'] = date('Y-m-d H:i:s');
+ parent::update($table, $bind, $filter);
+ }
+
+ /**
+ * Delete table rows, optionally limited by using a filter
+ *
+ * @param string $table
+ * @param Filter $filter
+ */
+ public function delete($table, Filter $filter = null)
+ {
+ if ($table === 'group') {
+ parent::delete('group_membership', $filter);
+ $idQuery = $this->select(array('group_id'));
+ if ($filter !== null) {
+ $idQuery->applyFilter($filter);
+ }
+
+ $this->update('group', array('parent' => null), Filter::where('parent', $idQuery->fetchColumn()));
+ }
+
+ parent::delete($table, $filter);
+ }
+
+ /**
+ * Return the groups the given user is a member of
+ *
+ * @param User $user
+ *
+ * @return array
+ */
+ public function getMemberships(User $user)
+ {
+ $groupQuery = $this->ds
+ ->select()
+ ->from(
+ array('g' => $this->prependTablePrefix('group')),
+ array(
+ 'group_name' => 'g.name',
+ 'parent_name' => 'gg.name'
+ )
+ )->joinLeft(
+ array('gg' => $this->prependTablePrefix('group')),
+ 'g.parent = gg.id',
+ array()
+ );
+
+ $groups = array();
+ foreach ($groupQuery as $group) {
+ $groups[$group->group_name] = $group->parent_name;
+ }
+
+ $membershipQuery = $this
+ ->select()
+ ->from('group_membership', array('group_name'))
+ ->where('user_name', $user->getUsername());
+
+ $memberships = array();
+ foreach ($membershipQuery as $membership) {
+ $memberships[] = $membership->group_name;
+ $parent = $groups[$membership->group_name];
+ while ($parent !== null) {
+ $memberships[] = $parent;
+ // Usually a parent is an existing group, but since we do not have a constraint on our table..
+ $parent = isset($groups[$parent]) ? $groups[$parent] : null;
+ }
+ }
+
+ return $memberships;
+ }
+
+ /**
+ * Return the name of the backend that is providing the given user
+ *
+ * @param string $username Currently unused
+ *
+ * @return null|string The name of the backend or null in case this information is not available
+ */
+ public function getUserBackendName($username)
+ {
+ return null; // TODO(10373): Store this to the database when inserting and fetch it here
+ }
+
+ /**
+ * Join group into group_membership
+ *
+ * @param RepositoryQuery $query
+ */
+ protected function joinGroup(RepositoryQuery $query)
+ {
+ $query->getQuery()->join(
+ $this->requireTable('group'),
+ 'gm.group_id = g.id',
+ array()
+ );
+ }
+
+ /**
+ * Join group_membership into group
+ *
+ * @param RepositoryQuery $query
+ */
+ protected function joinGroupMembership(RepositoryQuery $query)
+ {
+ $query->getQuery()->joinLeft(
+ $this->requireTable('group_membership'),
+ 'g.id = gm.group_id',
+ array()
+ )->group('g.id');
+ }
+
+ /**
+ * Fetch and return the corresponding id for the given group's name
+ *
+ * @param string|array $groupName
+ *
+ * @return int
+ *
+ * @throws NotFoundError
+ */
+ protected function persistGroupId($groupName)
+ {
+ if (! $groupName || empty($groupName) || is_numeric($groupName)) {
+ return $groupName;
+ }
+
+ if (is_array($groupName)) {
+ if (is_numeric($groupName[0])) {
+ return $groupName; // In case the array contains mixed types...
+ }
+
+ $groupIds = $this->ds
+ ->select()
+ ->from($this->prependTablePrefix('group'), array('id'))
+ ->where('name', $groupName)
+ ->fetchColumn();
+ if (empty($groupIds)) {
+ throw new NotFoundError('No groups found matching one of: %s', implode(', ', $groupName));
+ }
+
+ return $groupIds;
+ }
+
+ $groupId = $this->ds
+ ->select()
+ ->from($this->prependTablePrefix('group'), array('id'))
+ ->where('name', $groupName)
+ ->fetchOne();
+ if ($groupId === false) {
+ throw new NotFoundError('Group "%s" does not exist', $groupName);
+ }
+
+ return $groupId;
+ }
+
+ /**
+ * Inspect this object to gain extended information about its health
+ *
+ * @return Inspection The inspection result
+ */
+ public function inspect()
+ {
+ $insp = new Inspection('Db User Group Backend');
+ $insp->write($this->ds->inspect());
+
+ try {
+ $insp->write(sprintf('%s group(s)', $this->select()->count()));
+ } catch (Exception $e) {
+ $insp->error(sprintf('Query failed: %s', $e->getMessage()));
+ }
+
+ return $insp;
+ }
+}
diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
new file mode 100644
index 0000000..54ccaa9
--- /dev/null
+++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
@@ -0,0 +1,944 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\UserGroup;
+
+use Exception;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Authentication\User\LdapUserBackend;
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Exception\AuthenticationException;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Protocol\Ldap\LdapException;
+use Icinga\Protocol\Ldap\LdapUtils;
+use Icinga\Repository\LdapRepository;
+use Icinga\Repository\RepositoryQuery;
+use Icinga\User;
+
+class LdapUserGroupBackend extends LdapRepository implements Inspectable, UserGroupBackendInterface
+{
+ /**
+ * The user backend being associated with this user group backend
+ *
+ * @var LdapUserBackend
+ */
+ protected $userBackend;
+
+ /**
+ * The base DN to use for a user query
+ *
+ * @var string
+ */
+ protected $userBaseDn;
+
+ /**
+ * The base DN to use for a group query
+ *
+ * @var string
+ */
+ protected $groupBaseDn;
+
+ /**
+ * The objectClass where look for users
+ *
+ * @var string
+ */
+ protected $userClass;
+
+ /**
+ * The objectClass where look for groups
+ *
+ * @var string
+ */
+ protected $groupClass;
+
+ /**
+ * The attribute name where to find a user's name
+ *
+ * @var string
+ */
+ protected $userNameAttribute;
+
+ /**
+ * The attribute name where to find a group's name
+ *
+ * @var string
+ */
+ protected $groupNameAttribute;
+
+ /**
+ * The attribute name where to find a group's member
+ *
+ * @var string
+ */
+ protected $groupMemberAttribute;
+
+ /**
+ * Whether the attribute name where to find a group's member holds ambiguous values
+ *
+ * @var bool
+ */
+ protected $ambiguousMemberAttribute;
+
+ /**
+ * The custom LDAP filter to apply on a user query
+ *
+ * @var string
+ */
+ protected $userFilter;
+
+ /**
+ * The custom LDAP filter to apply on a group query
+ *
+ * @var string
+ */
+ protected $groupFilter;
+
+ /**
+ * ActiveDirectory nested group on the user?
+ *
+ * @var bool
+ */
+ protected $nestedGroupSearch;
+
+ /**
+ * The domain the backend is responsible for
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * The columns which are not permitted to be queried
+ *
+ * @var array
+ */
+ protected $blacklistedQueryColumns = array('group', 'user');
+
+ /**
+ * The search columns being provided
+ *
+ * @var array
+ */
+ protected $searchColumns = array('group', 'user');
+
+ /**
+ * The default sort rules to be applied on a query
+ *
+ * @var array
+ */
+ protected $sortRules = array(
+ 'group_name' => array(
+ 'order' => 'asc'
+ )
+ );
+
+ /**
+ * Set the user backend to be associated with this user group backend
+ *
+ * @param LdapUserBackend $backend
+ *
+ * @return $this
+ */
+ public function setUserBackend(LdapUserBackend $backend)
+ {
+ $this->userBackend = $backend;
+ return $this;
+ }
+
+ /**
+ * Return the user backend being associated with this user group backend
+ *
+ * @return LdapUserBackend
+ */
+ public function getUserBackend()
+ {
+ return $this->userBackend;
+ }
+
+ /**
+ * Set the base DN to use for a user query
+ *
+ * @param string $baseDn
+ *
+ * @return $this
+ */
+ public function setUserBaseDn($baseDn)
+ {
+ if ($baseDn && ($baseDn = trim($baseDn))) {
+ $this->userBaseDn = $baseDn;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the base DN to use for a user query
+ *
+ * @return string
+ */
+ public function getUserBaseDn()
+ {
+ return $this->userBaseDn;
+ }
+
+ /**
+ * Set the base DN to use for a group query
+ *
+ * @param string $baseDn
+ *
+ * @return $this
+ */
+ public function setGroupBaseDn($baseDn)
+ {
+ if ($baseDn && ($baseDn = trim($baseDn))) {
+ $this->groupBaseDn = $baseDn;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the base DN to use for a group query
+ *
+ * @return string
+ */
+ public function getGroupBaseDn()
+ {
+ return $this->groupBaseDn;
+ }
+
+ /**
+ * Set the objectClass where to look for users
+ *
+ * @param string $userClass
+ *
+ * @return $this
+ */
+ public function setUserClass($userClass)
+ {
+ $this->userClass = $this->getNormedAttribute($userClass);
+ return $this;
+ }
+
+ /**
+ * Return the objectClass where to look for users
+ *
+ * @return string
+ */
+ public function getUserClass()
+ {
+ return $this->userClass;
+ }
+
+ /**
+ * Set the objectClass where to look for groups
+ *
+ * @param string $groupClass
+ *
+ * @return $this
+ */
+ public function setGroupClass($groupClass)
+ {
+ $this->groupClass = $this->getNormedAttribute($groupClass);
+ return $this;
+ }
+
+ /**
+ * Return the objectClass where to look for groups
+ *
+ * @return string
+ */
+ public function getGroupClass()
+ {
+ return $this->groupClass;
+ }
+
+ /**
+ * Set the attribute name where to find a user's name
+ *
+ * @param string $userNameAttribute
+ *
+ * @return $this
+ */
+ public function setUserNameAttribute($userNameAttribute)
+ {
+ $this->userNameAttribute = $this->getNormedAttribute($userNameAttribute);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a user's name
+ *
+ * @return string
+ */
+ public function getUserNameAttribute()
+ {
+ return $this->userNameAttribute;
+ }
+
+ /**
+ * Set the attribute name where to find a group's name
+ *
+ * @param string $groupNameAttribute
+ *
+ * @return $this
+ */
+ public function setGroupNameAttribute($groupNameAttribute)
+ {
+ $this->groupNameAttribute = $this->getNormedAttribute($groupNameAttribute);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a group's name
+ *
+ * @return string
+ */
+ public function getGroupNameAttribute()
+ {
+ return $this->groupNameAttribute;
+ }
+
+ /**
+ * Set the attribute name where to find a group's member
+ *
+ * @param string $groupMemberAttribute
+ *
+ * @return $this
+ */
+ public function setGroupMemberAttribute($groupMemberAttribute)
+ {
+ $this->groupMemberAttribute = $this->getNormedAttribute($groupMemberAttribute);
+ return $this;
+ }
+
+ /**
+ * Return the attribute name where to find a group's member
+ *
+ * @return string
+ */
+ public function getGroupMemberAttribute()
+ {
+ return $this->groupMemberAttribute;
+ }
+
+ /**
+ * Set the custom LDAP filter to apply on a user query
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setUserFilter($filter)
+ {
+ if ($filter && ($filter = trim($filter))) {
+ if ($filter[0] === '(') {
+ $filter = substr($filter, 1, -1);
+ }
+
+ $this->userFilter = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the custom LDAP filter to apply on a user query
+ *
+ * @return string
+ */
+ public function getUserFilter()
+ {
+ return $this->userFilter;
+ }
+
+ /**
+ * Set the custom LDAP filter to apply on a group query
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setGroupFilter($filter)
+ {
+ if ($filter && ($filter = trim($filter))) {
+ $this->groupFilter = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the custom LDAP filter to apply on a group query
+ *
+ * @return string
+ */
+ public function getGroupFilter()
+ {
+ return $this->groupFilter;
+ }
+
+ /**
+ * Set nestedGroupSearch for the group query
+ *
+ * @param bool $enable
+ *
+ * @return $this
+ */
+ public function setNestedGroupSearch($enable = true)
+ {
+ $this->nestedGroupSearch = $enable;
+ return $this;
+ }
+
+ /**
+ * Get nestedGroupSearch for the group query
+ *
+ * @return bool
+ */
+ public function getNestedGroupSearch()
+ {
+ return $this->nestedGroupSearch;
+ }
+
+ /**
+ * Get the domain the backend is responsible for
+ *
+ * If the LDAP group backend is linked with a LDAP user backend,
+ * the domain of the user backend will be returned.
+ *
+ * @return string
+ */
+ public function getDomain()
+ {
+ return $this->userBackend !== null ? $this->userBackend->getDomain() : $this->domain;
+ }
+
+ /**
+ * Set the domain the backend is responsible for
+ *
+ * If the LDAP group backend is linked with a LDAP user backend,
+ * the domain of the user backend will be used nonetheless.
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setDomain($domain)
+ {
+ if ($domain && ($domain = trim($domain))) {
+ $this->domain = $domain;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether the attribute name where to find a group's member holds ambiguous values
+ *
+ * This tries to detect if the member attribute of groups contain:
+ *
+ * full DN -> distinguished name of another object
+ * other -> ambiguous field referencing the member by userNameAttribute
+ *
+ * @return bool
+ *
+ * @throws ProgrammingError In case either $this->groupClass or $this->groupMemberAttribute
+ * has not been set yet
+ */
+ protected function isMemberAttributeAmbiguous()
+ {
+ if ($this->ambiguousMemberAttribute === null) {
+ if ($this->groupClass === null) {
+ throw new ProgrammingError(
+ 'It is required to set the objectClass where to look for groups first'
+ );
+ } elseif ($this->groupMemberAttribute === null) {
+ throw new ProgrammingError(
+ 'It is required to set a attribute name where to find a group\'s members first'
+ );
+ }
+
+ $sampleValues = $this->ds
+ ->select()
+ ->from($this->groupClass, array($this->groupMemberAttribute))
+ ->where($this->groupMemberAttribute, '*')
+ ->limit(Logger::getInstance()->getLevel() === Logger::DEBUG ? 3 : 1)
+ ->setUnfoldAttribute($this->groupMemberAttribute)
+ ->setBase($this->groupBaseDn)
+ ->fetchAll();
+
+ Logger::debug('Ambiguity query returned %d results', count($sampleValues));
+
+ $i = 0;
+ $sampleValue = null;
+ foreach ($sampleValues as $key => $value) {
+ if ($sampleValue === null) {
+ $sampleValue = $value;
+ }
+
+ Logger::debug('Result %d: %s (%s)', ++$i, $value, $key);
+ }
+
+ if (is_object($sampleValue) && isset($sampleValue->{$this->groupMemberAttribute})) {
+ $this->ambiguousMemberAttribute = ! LdapUtils::isDn($sampleValue->{$this->groupMemberAttribute});
+
+ Logger::debug(
+ 'Ambiguity check came to the conclusion that the member attribute %s ambiguous. Tested sample: %s',
+ $this->ambiguousMemberAttribute ? 'is' : 'is not',
+ $sampleValue->{$this->groupMemberAttribute}
+ );
+ } else {
+ Logger::warning(
+ 'Ambiguity query returned zero or invalid results. Sample value is `%s`',
+ print_r($sampleValue, true)
+ );
+ }
+ }
+
+ return $this->ambiguousMemberAttribute;
+ }
+
+ /**
+ * Initialize this repository's virtual tables
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case $this->groupClass has not been set yet
+ */
+ protected function initializeVirtualTables()
+ {
+ if ($this->groupClass === null) {
+ throw new ProgrammingError('It is required to set the object class where to find groups first');
+ }
+
+ return array(
+ 'group' => $this->groupClass,
+ 'group_membership' => $this->groupClass
+ );
+ }
+
+ /**
+ * Initialize this repository's query columns
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case either $this->groupNameAttribute or
+ * $this->groupMemberAttribute has not been set yet
+ */
+ protected function initializeQueryColumns()
+ {
+ if ($this->groupNameAttribute === null) {
+ throw new ProgrammingError('It is required to set a attribute name where to find a group\'s name first');
+ }
+ if ($this->groupMemberAttribute === null) {
+ throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
+ }
+
+ if ($this->ds->getCapabilities()->isActiveDirectory()) {
+ $createdAtAttribute = 'whenCreated';
+ $lastModifiedAttribute = 'whenChanged';
+ } else {
+ $createdAtAttribute = 'createTimestamp';
+ $lastModifiedAttribute = 'modifyTimestamp';
+ }
+
+ $columns = array(
+ 'group' => $this->groupNameAttribute,
+ 'group_name' => $this->groupNameAttribute,
+ 'user' => $this->groupMemberAttribute,
+ 'user_name' => $this->groupMemberAttribute,
+ 'created_at' => $createdAtAttribute,
+ 'last_modified' => $lastModifiedAttribute
+ );
+ return array('group' => $columns, 'group_membership' => $columns);
+ }
+
+ /**
+ * Initialize this repository's filter columns
+ *
+ * @return array
+ */
+ protected function initializeFilterColumns()
+ {
+ return array(
+ t('Username') => 'user_name',
+ t('User Group') => 'group_name',
+ t('Created At') => 'created_at',
+ t('Last modified') => 'last_modified'
+ );
+ }
+
+ /**
+ * Initialize this repository's conversion rules
+ *
+ * @return array
+ */
+ protected function initializeConversionRules()
+ {
+ $rules = array(
+ 'group' => array(
+ 'created_at' => 'generalized_time',
+ 'last_modified' => 'generalized_time'
+ ),
+ 'group_membership' => array(
+ 'created_at' => 'generalized_time',
+ 'last_modified' => 'generalized_time'
+ )
+ );
+ if (! $this->isMemberAttributeAmbiguous()) {
+ $rules['group_membership']['user_name'] = 'user_name';
+ $rules['group_membership']['user'] = 'user_name';
+ $rules['group']['user_name'] = 'user_name';
+ $rules['group']['user'] = 'user_name';
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Return the distinguished name for the given uid or gid
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function persistUserName($name)
+ {
+ try {
+ $userDn = $this->ds
+ ->select()
+ ->from($this->userClass, array())
+ ->where($this->userNameAttribute, $name)
+ ->setBase($this->userBaseDn)
+ ->setUsePagedResults(false)
+ ->fetchDn();
+ if ($userDn) {
+ return $userDn;
+ }
+
+ $groupDn = $this->ds
+ ->select()
+ ->from($this->groupClass, array())
+ ->where($this->groupNameAttribute, $name)
+ ->setBase($this->groupBaseDn)
+ ->setUsePagedResults(false)
+ ->fetchDn();
+ if ($groupDn) {
+ return $groupDn;
+ }
+ } catch (LdapException $_) {
+ // pass
+ }
+
+ Logger::debug('Unable to persist uid or gid "%s" in repository "%s". No DN found.', $name, $this->getName());
+ return $name;
+ }
+
+ /**
+ * Return the uid for the given distinguished name
+ *
+ * @param string $username
+ *
+ * @param string
+ */
+ protected function retrieveUserName($dn)
+ {
+ return $this->ds
+ ->select()
+ ->from('*', array($this->userNameAttribute))
+ ->setUnfoldAttribute($this->userNameAttribute)
+ ->setBase($dn)
+ ->fetchOne();
+ }
+
+ /**
+ * Validate that the requested table exists
+ *
+ * @param string $table The table to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case the given table does not exist
+ */
+ public function requireTable($table, RepositoryQuery $query = null)
+ {
+ if ($query !== null) {
+ $query->getQuery()->setBase($this->groupBaseDn);
+ if ($table === 'group' && $this->groupFilter) {
+ $query->getQuery()->setNativeFilter($this->groupFilter);
+ }
+ }
+
+ return parent::requireTable($table, $query);
+ }
+
+ /**
+ * Validate that the given column is a valid query target and return it or the actual name if it's an alias
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid query column
+ */
+ public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
+ {
+ $column = parent::requireQueryColumn($table, $name, $query);
+ if (($name === 'user_name' || $name === 'group_name') && $query !== null) {
+ $query->getQuery()->setUnfoldAttribute($name);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Return the groups the given user is a member of
+ *
+ * @param User $user
+ *
+ * @return array
+ */
+ public function getMemberships(User $user)
+ {
+ $domain = $this->getDomain();
+
+ if ($domain !== null) {
+ if (! $user->hasDomain() || strtolower($user->getDomain()) !== strtolower($domain)) {
+ return array();
+ }
+
+ $username = $user->getLocalUsername();
+ } else {
+ $username = $user->getUsername();
+ }
+
+ if ($this->isMemberAttributeAmbiguous()) {
+ $queryValue = $username;
+ } elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
+ $userQuery = $this->ds
+ ->select()
+ ->from($this->userClass)
+ ->where($this->userNameAttribute, $username)
+ ->setBase($this->userBaseDn)
+ ->setUsePagedResults(false);
+ if ($this->userFilter) {
+ $userQuery->setNativeFilter($this->userFilter);
+ }
+
+ if (($queryValue = $userQuery->fetchDn()) === null) {
+ return array();
+ }
+ }
+
+ if ($this->nestedGroupSearch) {
+ $groupMemberAttribute = $this->groupMemberAttribute . ':1.2.840.113556.1.4.1941:';
+ } else {
+ $groupMemberAttribute = $this->groupMemberAttribute;
+ }
+
+ $groupQuery = $this->ds
+ ->select()
+ ->from($this->groupClass, array($this->groupNameAttribute))
+ ->setUnfoldAttribute($this->groupNameAttribute)
+ ->where($groupMemberAttribute, $queryValue)
+ ->setBase($this->groupBaseDn);
+ if ($this->groupFilter) {
+ $groupQuery->setNativeFilter($this->groupFilter);
+ }
+
+ $groups = array();
+ foreach ($groupQuery as $row) {
+ $groups[] = $row->{$this->groupNameAttribute};
+ if ($domain !== null) {
+ $groups[] = $row->{$this->groupNameAttribute} . "@$domain";
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Return the name of the backend that is providing the given user
+ *
+ * @param string $username Unused
+ *
+ * @return null|string The name of the backend or null in case this information is not available
+ */
+ public function getUserBackendName($username)
+ {
+ $userBackend = $this->getUserBackend();
+ if ($userBackend !== null) {
+ return $userBackend->getName();
+ }
+ }
+
+ /**
+ * Apply the given configuration on this backend
+ *
+ * @param ConfigObject $config
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError In case a linked user backend does not exist or is invalid
+ */
+ public function setConfig(ConfigObject $config)
+ {
+ if ($config->backend === 'ldap') {
+ $defaults = $this->getOpenLdapDefaults();
+ } elseif ($config->backend === 'msldap') {
+ $defaults = $this->getActiveDirectoryDefaults();
+ } else {
+ $defaults = new ConfigObject();
+ }
+
+ if ($config->user_backend && $config->user_backend !== 'none') {
+ $userBackend = UserBackend::create($config->user_backend);
+ if (! $userBackend instanceof LdapUserBackend) {
+ throw new ConfigurationError('User backend "%s" is not of type LDAP', $config->user_backend);
+ }
+
+ if ($this->ds->getHostname() !== $userBackend->getDataSource()->getHostname()
+ || $this->ds->getPort() !== $userBackend->getDataSource()->getPort()
+ ) {
+ // TODO(jom): Elaborate whether it makes sense to link directories on different hosts
+ throw new ConfigurationError(
+ 'It is required that a linked user backend refers to the '
+ . 'same directory as it\'s user group backend counterpart'
+ );
+ }
+
+ $this->setUserBackend($userBackend);
+ $defaults->merge(array(
+ 'user_base_dn' => $userBackend->getBaseDn(),
+ 'user_class' => $userBackend->getUserClass(),
+ 'user_name_attribute' => $userBackend->getUserNameAttribute(),
+ 'user_filter' => $userBackend->getFilter(),
+ 'domain' => $userBackend->getDomain()
+ ));
+ }
+
+ return $this
+ ->setGroupBaseDn($config->base_dn)
+ ->setUserBaseDn($config->get('user_base_dn', $defaults->get('user_base_dn', $this->getGroupBaseDn())))
+ ->setGroupClass($config->get('group_class', $defaults->group_class))
+ ->setUserClass($config->get('user_class', $defaults->user_class))
+ ->setGroupNameAttribute($config->get('group_name_attribute', $defaults->group_name_attribute))
+ ->setUserNameAttribute($config->get('user_name_attribute', $defaults->user_name_attribute))
+ ->setGroupMemberAttribute($config->get('group_member_attribute', $defaults->group_member_attribute))
+ ->setGroupFilter($config->group_filter)
+ ->setUserFilter($config->user_filter)
+ ->setNestedGroupSearch((bool) $config->get('nested_group_search', $defaults->nested_group_search))
+ ->setDomain($defaults->get('domain', $config->domain));
+ }
+
+ /**
+ * Return the configuration defaults for an OpenLDAP environment
+ *
+ * @return ConfigObject
+ */
+ public function getOpenLdapDefaults()
+ {
+ return new ConfigObject(array(
+ 'group_class' => 'group',
+ 'user_class' => 'inetOrgPerson',
+ 'group_name_attribute' => 'gid',
+ 'user_name_attribute' => 'uid',
+ 'group_member_attribute' => 'member',
+ 'nested_group_search' => '0'
+ ));
+ }
+
+ /**
+ * Return the configuration defaults for an ActiveDirectory environment
+ *
+ * @return ConfigObject
+ */
+ public function getActiveDirectoryDefaults()
+ {
+ return new ConfigObject(array(
+ 'group_class' => 'group',
+ 'user_class' => 'user',
+ 'group_name_attribute' => 'sAMAccountName',
+ 'user_name_attribute' => 'sAMAccountName',
+ 'group_member_attribute' => 'member',
+ 'nested_group_search' => '0'
+ ));
+ }
+
+ /**
+ * Inspect if this LDAP User Group Backend is working as expected by probing the backend
+ *
+ * Try to bind to the backend and fetch a single group to check if:
+ * <ul>
+ * <li>Connection credentials are correct and the bind is possible</li>
+ * <li>At least one group exists</li>
+ * <li>The specified groupClass has the property specified by groupNameAttribute</li>
+ * </ul>
+ *
+ * @return Inspection Inspection result
+ */
+ public function inspect()
+ {
+ $result = new Inspection('Ldap User Group Backend');
+
+ // inspect the used connection to get more diagnostic info in case the connection is not working
+ $result->write($this->ds->inspect());
+
+ try {
+ try {
+ $groupQuery = $this->ds
+ ->select()
+ ->from($this->groupClass, array($this->groupNameAttribute))
+ ->setBase($this->groupBaseDn);
+
+ if ($this->groupFilter) {
+ $groupQuery->setNativeFilter($this->groupFilter);
+ }
+
+ $res = $groupQuery->fetchRow();
+ } catch (LdapException $e) {
+ throw new AuthenticationException('Connection not possible', $e);
+ }
+
+ $result->write('Searching for: ' . sprintf(
+ 'objectClass "%s" in DN "%s" (Filter: %s)',
+ $this->groupClass,
+ $this->groupBaseDn ?: $this->ds->getDn(),
+ $this->groupFilter ?: 'None'
+ ));
+
+ if ($res === false) {
+ throw new AuthenticationException('Error, no groups found in backend');
+ }
+
+ $result->write(sprintf('%d groups found in backend', $groupQuery->count()));
+
+ if (! isset($res->{$this->groupNameAttribute})) {
+ throw new AuthenticationException(
+ 'GroupNameAttribute "%s" not existing in objectClass "%s"',
+ $this->groupNameAttribute,
+ $this->groupClass
+ );
+ }
+ } catch (AuthenticationException $e) {
+ if (($previous = $e->getPrevious()) !== null) {
+ $result->error($previous->getMessage());
+ } else {
+ $result->error($e->getMessage());
+ }
+ } catch (Exception $e) {
+ $result->error(sprintf('Unable to validate backend: %s', $e->getMessage()));
+ }
+
+ return $result;
+ }
+}
diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackend.php b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php
new file mode 100644
index 0000000..76fa2d0
--- /dev/null
+++ b/library/Icinga/Authentication/UserGroup/UserGroupBackend.php
@@ -0,0 +1,188 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\UserGroup;
+
+use Icinga\Application\Logger;
+use Icinga\Application\Icinga;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * Factory for user group backends
+ */
+class UserGroupBackend
+{
+ /**
+ * The default user group backend types provided by Icinga Web 2
+ *
+ * @var array
+ */
+ protected static $defaultBackends = array(
+ 'db',
+ 'ldap',
+ 'msldap'
+ );
+
+ /**
+ * The registered custom user group backends with their identifier as key and class name as value
+ *
+ * @var array
+ */
+ protected static $customBackends;
+
+ /**
+ * Register all custom user group backends from all loaded modules
+ */
+ public static function registerCustomUserGroupBackends()
+ {
+ if (static::$customBackends !== null) {
+ return;
+ }
+
+ static::$customBackends = array();
+ $providedBy = array();
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ foreach ($module->getUserGroupBackends() as $identifier => $className) {
+ if (array_key_exists($identifier, $providedBy)) {
+ Logger::warning(
+ 'Cannot register user group backend of type "%s" provided by module "%s".'
+ . ' The type is already provided by module "%s"',
+ $identifier,
+ $module->getName(),
+ $providedBy[$identifier]
+ );
+ } elseif (in_array($identifier, static::$defaultBackends)) {
+ Logger::warning(
+ 'Cannot register user group backend of type "%s" provided by module "%s".'
+ . ' The type is a default type provided by Icinga Web 2',
+ $identifier,
+ $module->getName()
+ );
+ } else {
+ $providedBy[$identifier] = $module->getName();
+ static::$customBackends[$identifier] = $className;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get config forms of all custom user group backends
+ */
+ public static function getCustomBackendConfigForms()
+ {
+ $customBackendConfigForms = [];
+ static::registerCustomUserGroupBackends();
+ foreach (self::$customBackends as $customBackendType => $customBackendClass) {
+ if (method_exists($customBackendClass, 'getConfigurationFormClass')) {
+ $customBackendConfigForms[$customBackendType] = $customBackendClass::getConfigurationFormClass();
+ }
+ }
+
+ return $customBackendConfigForms;
+ }
+
+ /**
+ * Return the class for the given custom user group backend
+ *
+ * @param string $identifier The identifier of the custom user group backend
+ *
+ * @return string|null The name of the class or null in case there was no
+ * backend found with the given identifier
+ *
+ * @throws ConfigurationError In case the class associated to the given identifier does not exist
+ */
+ protected static function getCustomUserGroupBackend($identifier)
+ {
+ static::registerCustomUserGroupBackends();
+ if (array_key_exists($identifier, static::$customBackends)) {
+ $className = static::$customBackends[$identifier];
+ if (! class_exists($className)) {
+ throw new ConfigurationError(
+ 'Cannot utilize user group backend of type "%s". Class "%s" does not exist',
+ $identifier,
+ $className
+ );
+ }
+
+ return $className;
+ }
+ }
+
+ /**
+ * Create and return a user group backend with the given name and given configuration applied to it
+ *
+ * @param string $name
+ * @param ConfigObject $backendConfig
+ *
+ * @return UserGroupBackendInterface
+ *
+ * @throws ConfigurationError
+ */
+ public static function create($name, ConfigObject $backendConfig)
+ {
+ if ($backendConfig->name !== null) {
+ $name = $backendConfig->name;
+ }
+
+ if (! ($backendType = strtolower($backendConfig->backend))) {
+ throw new ConfigurationError(
+ 'Configuration for user group backend "%s" is missing the \'backend\' directive',
+ $name
+ );
+ }
+ if (in_array($backendType, static::$defaultBackends)) {
+ // The default backend check is the first one because of performance reasons:
+ // Do not attempt to load a custom user group backend unless it's actually required
+ } elseif (($customClass = static::getCustomUserGroupBackend($backendType)) !== null) {
+ $backend = new $customClass($backendConfig);
+ if (! is_a($backend, 'Icinga\Authentication\UserGroup\UserGroupBackendInterface')) {
+ throw new ConfigurationError(
+ 'Cannot utilize user group backend of type "%s".'
+ . ' Class "%s" does not implement UserGroupBackendInterface',
+ $backendType,
+ $customClass
+ );
+ }
+
+ $backend->setName($name);
+ return $backend;
+ } else {
+ throw new ConfigurationError(
+ 'Configuration for user group backend "%s" defines an invalid backend type.'
+ . ' Backend type "%s" is not supported',
+ $name,
+ $backendType
+ );
+ }
+
+ if ($backendConfig->resource === null) {
+ throw new ConfigurationError(
+ 'Configuration for user group backend "%s" is missing the \'resource\' directive',
+ $name
+ );
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($backendConfig->resource);
+ if ($backendType === 'db' && $resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ $resource = ResourceFactory::createResource($resourceConfig);
+ switch ($backendType) {
+ case 'db':
+ $backend = new DbUserGroupBackend($resource);
+ break;
+ case 'ldap':
+ case 'msldap':
+ $backend = new LdapUserGroupBackend($resource);
+ $backend->setConfig($backendConfig);
+ break;
+ }
+
+ $backend->setName($name);
+ return $backend;
+ }
+}
diff --git a/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php b/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php
new file mode 100644
index 0000000..cc9438f
--- /dev/null
+++ b/library/Icinga/Authentication/UserGroup/UserGroupBackendInterface.php
@@ -0,0 +1,56 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Authentication\UserGroup;
+
+use Icinga\User;
+
+/**
+ * Interface for user group backends
+ */
+interface UserGroupBackendInterface
+{
+ /**
+ * Set this backend's name
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name);
+
+ /**
+ * Return this backend's name
+ *
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Return the groups the given user is a member of
+ *
+ * @param User $user
+ *
+ * @return array
+ */
+ public function getMemberships(User $user);
+
+ /**
+ * Return the name of the backend that is providing the given user
+ *
+ * @param string $username
+ *
+ * @return null|string The name of the backend or null in case this information is not available
+ */
+ public function getUserBackendName($username);
+
+ /**
+ * Return this backend's configuration form class path
+ *
+ * This is not part of the interface to not break existing implementations.
+ * If you need a custom backend form, implement this method.
+ *
+ * @return string
+ */
+ //public static function getConfigurationFormClass();
+}
diff --git a/library/Icinga/Chart/Axis.php b/library/Icinga/Chart/Axis.php
new file mode 100644
index 0000000..1639939
--- /dev/null
+++ b/library/Icinga/Chart/Axis.php
@@ -0,0 +1,485 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Primitive\Line;
+use Icinga\Chart\Primitive\Text;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Render\Rotator;
+use Icinga\Chart\Unit\AxisUnit;
+use Icinga\Chart\Unit\CalendarUnit;
+use Icinga\Chart\Unit\LinearUnit;
+
+/**
+ * Axis class for the GridChart class.
+ *
+ * Implements drawing functions for the axis and its labels but delegates tick and label calculations
+ * to the AxisUnit implementations
+ *
+ * @see GridChart
+ * @see AxisUnit
+ */
+class Axis implements Drawable
+{
+ /**
+ * Draw the label text horizontally
+ */
+ const LABEL_ROTATE_HORIZONTAL = 'normal';
+
+ /**
+ * Draw the label text diagonally
+ */
+ const LABEL_ROTATE_DIAGONAL = 'diagonal';
+
+ /**
+ * Whether to draw the horizontal lines for the background grid
+ *
+ * @var bool
+ */
+ private $drawXGrid = true;
+
+ /**
+ * Whether to draw the vertical lines for the background grid
+ *
+ * @var bool
+ */
+ private $drawYGrid = true;
+
+ /**
+ * The label for the x axis
+ *
+ * @var string
+ */
+ private $xLabel = "";
+
+ /**
+ * The label for the y axis
+ *
+ * @var string
+ */
+ private $yLabel = "";
+
+ /**
+ * The AxisUnit implementation to use for calculating the ticks for the x axis
+ *
+ * @var AxisUnit
+ */
+ private $xUnit = null;
+
+ /**
+ * The AxisUnit implementation to use for calculating the ticks for the y axis
+ *
+ * @var AxisUnit
+ */
+ private $yUnit = null;
+
+ /**
+ * The minimum amount of units each step must take up
+ *
+ * @var int
+ */
+ public $minUnitsPerStep = 80;
+
+ /**
+ * The minimum amount of units each tick must take up
+ *
+ * @var int
+ */
+ public $minUnitsPerTick = 15;
+
+ /**
+ * If the displayed labels should be aligned horizontally or diagonally
+ */
+ protected $labelRotationStyle = self::LABEL_ROTATE_HORIZONTAL;
+
+ /**
+ * Inform the axis about an added dataset
+ *
+ * This is especially needed when one or more AxisUnit implementations dynamically define
+ * their min or max values, as this is the point where they detect the min and max value
+ * from the datasets
+ *
+ * @param array $dataset An dataset to respect on axis generation
+ */
+ public function addDataset(array $dataset)
+ {
+ $this->xUnit->addValues($dataset, 0);
+ $this->yUnit->addValues($dataset, 1);
+ }
+
+ /**
+ * Set the AxisUnit implementation to use for generating the x axis
+ *
+ * @param AxisUnit $unit The AxisUnit implementation to use for the x axis
+ *
+ * @return $this This Axis Object
+ * @see Axis::CalendarUnit
+ * @see Axis::LinearUnit
+ */
+ public function setUnitForXAxis(AxisUnit $unit)
+ {
+ $this->xUnit = $unit;
+ return $this;
+ }
+
+ /**
+ * Set the AxisUnit implementation to use for generating the y axis
+ *
+ * @param AxisUnit $unit The AxisUnit implementation to use for the y axis
+ *
+ * @return $this This Axis Object
+ * @see Axis::CalendarUnit
+ * @see Axis::LinearUnit
+ */
+ public function setUnitForYAxis(AxisUnit $unit)
+ {
+ $this->yUnit = $unit;
+ return $this;
+ }
+
+ /**
+ * Return the padding this axis requires
+ *
+ * @return array An array containing the padding for all sides
+ */
+ public function getRequiredPadding()
+ {
+ return array(10, 5, 15, 10);
+ }
+
+ /**
+ * Render the horizontal axis
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @param DOMElement $group The DOMElement this axis will be added to
+ */
+ private function renderHorizontalAxis(RenderContext $ctx, DOMElement $group)
+ {
+ $steps = $this->ticksPerX($this->xUnit->getTicks(), $ctx->getNrOfUnitsX(), $this->minUnitsPerStep);
+ $ticks = $this->ticksPerX($this->xUnit->getTicks(), $ctx->getNrOfUnitsX(), $this->minUnitsPerTick);
+
+ // Steps should always be ticks
+ if ($ticks !== $steps) {
+ $steps = $ticks * 5;
+ }
+
+ // Check whether there is enough room for regular labels
+ $labelRotationStyle = $this->labelRotationStyle;
+ if ($this->labelsOversized($this->xUnit, 6)) {
+ $labelRotationStyle = self::LABEL_ROTATE_DIAGONAL;
+ }
+
+ /*
+ $line = new Line(0, 100, 100, 100);
+ $line->setStrokeWidth(2);
+ $group->appendChild($line->toSvg($ctx));
+ */
+
+ // contains the approximate end position of the last label
+ $lastLabelEnd = -1;
+ $shift = 0;
+
+ $i = 0;
+ foreach ($this->xUnit as $label => $pos) {
+ if ($i % $ticks === 0) {
+ /*
+ $tick = new Line($pos, 100, $pos, 101);
+ $group->appendChild($tick->toSvg($ctx));
+ */
+ }
+
+ if ($i % $steps === 0) {
+ if ($labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) {
+ // If the last label would overlap this label we shift the y axis a bit
+ if ($lastLabelEnd > $pos) {
+ $shift = ($shift + 5) % 10;
+ } else {
+ $shift = 0;
+ }
+ }
+
+ $labelField = new Text($pos + 0.5, ($this->xLabel ? 107 : 105) + $shift, $label);
+ if ($labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) {
+ $labelField->setAlignment(Text::ALIGN_MIDDLE)
+ ->setFontSize('2.5em');
+ } else {
+ $labelField->setFontSize('2.5em');
+ }
+
+ if ($labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
+ $labelField = new Rotator($labelField, 45);
+ }
+ $labelField = $labelField->toSvg($ctx);
+
+ $group->appendChild($labelField);
+
+ if ($this->drawYGrid) {
+ $bgLine = new Line($pos, 0, $pos, 100);
+ $bgLine->setStrokeWidth(0.5)
+ ->setStrokeColor('#BFBFBF');
+ $group->appendChild($bgLine->toSvg($ctx));
+ }
+ $lastLabelEnd = $pos + strlen($label) * 1.2;
+ }
+ $i++;
+ }
+ }
+
+ /**
+ * Render the vertical axis
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @param DOMElement $group The DOMElement this axis will be added to
+ */
+ private function renderVerticalAxis(RenderContext $ctx, DOMElement $group)
+ {
+ $steps = $this->ticksPerX($this->yUnit->getTicks(), $ctx->getNrOfUnitsY(), $this->minUnitsPerStep);
+ $ticks = $this->ticksPerX($this->yUnit->getTicks(), $ctx->getNrOfUnitsY(), $this->minUnitsPerTick);
+
+ // Steps should always be ticks
+ if ($ticks !== $steps) {
+ $steps = $ticks * 5;
+ }
+ /*
+ $line = new Line(0, 0, 0, 100);
+ $line->setStrokeWidth(2);
+ $group->appendChild($line->toSvg($ctx));
+ */
+
+ $i = 0;
+ foreach ($this->yUnit as $label => $pos) {
+ $pos = 100 - $pos;
+
+ if ($i % $ticks === 0) {
+ // draw a tick
+ //$tick = new Line(0, $pos, -1, $pos);
+ //$group->appendChild($tick->toSvg($ctx));
+ }
+
+ if ($i % $steps === 0) {
+ // draw a step
+ $labelField = new Text(-0.5, $pos + 0.5, $label);
+ $labelField->setFontSize('2.5em')
+ ->setAlignment(Text::ALIGN_END);
+
+ $group->appendChild($labelField->toSvg($ctx));
+ if ($this->drawXGrid) {
+ $bgLine = new Line(0, $pos, 100, $pos);
+ $bgLine->setStrokeWidth(0.5)
+ ->setStrokeColor('#BFBFBF');
+ $group->appendChild($bgLine->toSvg($ctx));
+ }
+ }
+ $i++;
+ }
+
+ if ($this->yLabel || $this->xLabel) {
+ if ($this->yLabel && $this->xLabel) {
+ $txt = $this->yLabel . ' / ' . $this->xLabel;
+ } elseif ($this->xLabel) {
+ $txt = $this->xLabel;
+ } else {
+ $txt = $this->yLabel;
+ }
+
+ $axisLabel = new Text(50, -3, $txt);
+ $axisLabel->setFontSize('2em')
+ ->setFontWeight('bold')
+ ->setAlignment(Text::ALIGN_MIDDLE);
+
+ $group->appendChild($axisLabel->toSvg($ctx));
+ }
+ }
+
+ /**
+ * Factory method, create an Axis instance using Linear ticks as the unit
+ *
+ * @return Axis The axis that has been created
+ * @see LinearUnit
+ */
+ public static function createLinearAxis()
+ {
+ $axis = new Axis();
+ $axis->setUnitForXAxis(self::linearUnit());
+ $axis->setUnitForYAxis(self::linearUnit());
+ return $axis;
+ }
+
+ /**
+ * Set the label for the x axis
+ *
+ * An empty string means 'no label'.
+ *
+ * @param string $label The label to use for the x axis
+ *
+ * @return $this Fluid interface
+ */
+ public function setXLabel($label)
+ {
+ $this->xLabel = $label;
+ return $this;
+ }
+
+ /**
+ * Set the label for the y axis
+ *
+ * An empty string means 'no label'.
+ *
+ * @param string $label The label to use for the y axis
+ *
+ * @return $this Fluid interface
+ */
+ public function setYLabel($label)
+ {
+ $this->yLabel = $label;
+ return $this;
+ }
+
+ /**
+ * Set the labels minimum value for the x axis
+ *
+ * Setting the value to null let's the axis unit decide which value to use for the minimum
+ *
+ * @param int $xMin The minimum value to use for the x axis
+ *
+ * @return $this Fluid interface
+ */
+ public function setXMin($xMin)
+ {
+ $this->xUnit->setMin($xMin);
+ return $this;
+ }
+
+ /**
+ * Set the labels minimum value for the y axis
+ *
+ * Setting the value to null let's the axis unit decide which value to use for the minimum
+ *
+ * @param int $yMin The minimum value to use for the x axis
+ *
+ * @return $this Fluid interface
+ */
+ public function setYMin($yMin)
+ {
+ $this->yUnit->setMin($yMin);
+ return $this;
+ }
+
+ /**
+ * Set the labels maximum value for the x axis
+ *
+ * Setting the value to null let's the axis unit decide which value to use for the maximum
+ *
+ * @param int $xMax The minimum value to use for the x axis
+ *
+ * @return $this Fluid interface
+ */
+ public function setXMax($xMax)
+ {
+ $this->xUnit->setMax($xMax);
+ return $this;
+ }
+
+ /**
+ * Set the labels maximum value for the y axis
+ *
+ * Setting the value to null let's the axis unit decide which value to use for the maximum
+ *
+ * @param int $yMax The minimum value to use for the y axis
+ *
+ * @return $this Fluid interface
+ */
+ public function setYMax($yMax)
+ {
+ $this->yUnit->setMax($yMax);
+ return $this;
+ }
+
+ /**
+ * Transform all coordinates of the given dataset to coordinates that fit the graph's coordinate system
+ *
+ * @param array $dataSet The absolute coordinates as provided in the draw call
+ *
+ * @return array A graph relative representation of the given coordinates
+ */
+ public function transform(array &$dataSet)
+ {
+ $result = array();
+ foreach ($dataSet as &$points) {
+ $result[] = array(
+ $this->xUnit->transform($points[0]),
+ 100 - $this->yUnit->transform($points[1])
+ );
+ }
+ return $result;
+ }
+
+ /**
+ * Create an AxisUnit that can be used in the axis to represent timestamps
+ *
+ * @return CalendarUnit
+ */
+ public static function calendarUnit()
+ {
+ return new CalendarUnit();
+ }
+
+ /**
+ * Create an AxisUnit that can be used in the axis to represent a dataset as equally distributed
+ * ticks
+ *
+ * @param int $ticks
+ * @return LinearUnit
+ */
+ public static function linearUnit($ticks = 10)
+ {
+ return new LinearUnit($ticks);
+ }
+
+ /**
+ * Return the SVG representation of this object
+ *
+ * @param RenderContext $ctx The context to use for calculations
+ *
+ * @return DOMElement
+ * @see Drawable::toSvg
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $group = $ctx->getDocument()->createElement('g');
+ $this->renderHorizontalAxis($ctx, $group);
+ $this->renderVerticalAxis($ctx, $group);
+ return $group;
+ }
+
+ protected function ticksPerX($ticks, $units, $min)
+ {
+ $per = 1;
+ while ($per * $units / $ticks < $min) {
+ $per++;
+ }
+ return $per;
+ }
+
+ /**
+ * Returns whether at least one label of the given Axis
+ * is bigger than the given maxLength
+ *
+ * @param AxisUnit $axis The axis that contains the labels that will be checked
+ *
+ * @return boolean Whether at least one label is bigger than maxLength
+ */
+ private function labelsOversized(AxisUnit $axis, $maxLength = 5)
+ {
+ $oversized = false;
+ foreach ($axis as $label => $pos) {
+ if (strlen($label) > $maxLength) {
+ $oversized = true;
+ }
+ }
+ return $oversized;
+ }
+}
diff --git a/library/Icinga/Chart/Chart.php b/library/Icinga/Chart/Chart.php
new file mode 100644
index 0000000..eaf69d1
--- /dev/null
+++ b/library/Icinga/Chart/Chart.php
@@ -0,0 +1,162 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use Imagick;
+use Icinga\Chart\Legend;
+use Icinga\Chart\Palette;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\SVGRenderer;
+use Icinga\Exception\IcingaException;
+
+/**
+ * Base class for charts, extended by all other Chart classes.
+ */
+abstract class Chart implements Drawable
+{
+ protected $align = false;
+
+ /**
+ * SVG renderer that handles
+ *
+ * @var SVGRenderer
+ */
+ protected $renderer;
+
+ /**
+ * Legend to use for this chart
+ *
+ * @var Legend
+ */
+ protected $legend;
+
+ /**
+ * The style-palette for this chart
+ *
+ * @var Palette
+ */
+ protected $palette;
+
+ /**
+ * The title of this chart, used for providing accessibility features
+ *
+ * @var string
+ */
+ public $title;
+
+ /**
+ * The description for this chart, mandatory for providing accessibility features
+ *
+ * @var string
+ */
+ public $description;
+
+ /**
+ * Create a new chart object and create internal objects
+ *
+ * If you want to extend this class use the init() method as an extension point,
+ * as this will be called at the end of the construct call
+ */
+ public function __construct()
+ {
+ $this->legend = new Legend();
+ $this->palette = new Palette();
+ $this->init();
+ }
+
+ /**
+ * Extension point for subclasses, called on __construct
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Extension point for implementing rendering logic
+ *
+ * This method is called after data validation, but before toSvg is called
+ */
+ protected function build()
+ {
+ }
+
+ /**
+ * Check if the current dataset has the proper structure for this chart.
+ *
+ * Needs to be overwritten by extending classes. The default implementation returns false.
+ *
+ * @return bool True when the dataset is valid, otherwise false
+ */
+ abstract public function isValidDataFormat();
+
+
+ /**
+ * Disable the legend for this chart
+ */
+ public function disableLegend()
+ {
+ $this->legend = null;
+ }
+
+ /**
+ * Render this graph and return the created SVG
+ *
+ * @return string The SVG created by the SvgRenderer
+ *
+ * @throws IcingaException Thrown wen the dataset is not valid for this graph
+ * @see SVGRenderer::render
+ */
+ public function render()
+ {
+ if (!$this->isValidDataFormat()) {
+ throw new IcingaException('Dataset for graph doesn\'t have the proper structure');
+ }
+ $this->build();
+ if ($this->align) {
+ $this->renderer->preserveAspectRatio();
+ $this->renderer->setXAspectRatioAlignment(SVGRenderer::X_ASPECT_RATIO_MIN);
+ $this->renderer->setYAspectRatioAlignment(SVGRenderer::Y_ASPECT_RATIO_MIN);
+ }
+
+ $this->renderer->setAriaDescription($this->description);
+ $this->renderer->setAriaTitle($this->title);
+ $this->renderer->getCanvas()->setAriaRole('presentation');
+
+ $this->renderer->getCanvas()->addElement($this);
+ return $this->renderer->render();
+ }
+
+ /**
+ * Return this graph rendered as PNG
+ *
+ * @param int $width The width of the PNG in pixel
+ * @param int $height The height of the PNG in pixel
+ *
+ * @return string A PNG binary string
+ *
+ * @throws IcingaException In case ImageMagick is not available
+ */
+ public function toPng($width, $height)
+ {
+ if (! class_exists('Imagick')) {
+ throw new IcingaException('Cannot render PNGs without ImageMagick');
+ }
+
+ $image = new Imagick();
+ $image->readImageBlob($this->render());
+ $image->setImageFormat('png24');
+ $image->resizeImage($width, $height, imagick::FILTER_LANCZOS, 1);
+ return $image;
+ }
+
+ /**
+ * Align the chart to the top left corner instead of centering it
+ *
+ * @param bool $align
+ */
+ public function alignTopLeft($align = true)
+ {
+ $this->align = $align;
+ }
+}
diff --git a/library/Icinga/Chart/Donut.php b/library/Icinga/Chart/Donut.php
new file mode 100644
index 0000000..9d2a2a8
--- /dev/null
+++ b/library/Icinga/Chart/Donut.php
@@ -0,0 +1,465 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use Icinga\Web\Url;
+
+/** Donut chart implementation */
+class Donut
+{
+ /**
+ * Big label in the middle of the donut, color is critical (red)
+ *
+ * @var string
+ */
+ protected $labelBig;
+
+ /**
+ * Url behind the big label
+ *
+ * @var Url
+ */
+ protected $labelBigUrl;
+
+ /**
+ * The state the big label shall indicate
+ *
+ * @var string|null
+ */
+ protected $labelBigState = 'critical';
+
+ /**
+ * Small label in the lower part of the donuts hole
+ *
+ * @var string
+ */
+ protected $labelSmall;
+
+ /**
+ * Thickness of the donut ring
+ *
+ * @var int
+ */
+ protected $thickness = 6;
+
+ /**
+ * Radius based of 100 to simplify the calculations
+ *
+ * 100 / (2 * M_PI)
+ *
+ * @var float
+ */
+ protected $radius = 15.9154943092;
+
+ /**
+ * Color of the hole in the donut
+ *
+ * Transparent by default so it can be placed anywhere with ease
+ *
+ * @var string
+ */
+ protected $centerColor = 'transparent';
+
+ /**
+ * The different colored parts that represent the data
+ *
+ * @var array
+ */
+ protected $slices = array();
+
+ /**
+ * The total amount of data units
+ *
+ * @var int
+ */
+ protected $count = 0;
+
+ /**
+ * Adds a colored part that represent the data
+ *
+ * @param integer $data Units of data
+ * @param array $attributes HTML attributes for this slice. (For example ['class' => 'slice-state-ok'])
+ *
+ * @return $this
+ */
+ public function addSlice($data, $attributes = array())
+ {
+ $this->slices[] = array($data, $attributes);
+
+ $this->count += $data;
+
+ return $this;
+ }
+
+ /**
+ * Set the thickness for this Donut
+ *
+ * @param integer $thickness
+ *
+ * @return $this
+ */
+ public function setThickness($thickness)
+ {
+ $this->thickness = $thickness;
+
+ return $this;
+ }
+ /**
+ * Get the thickness for this Donut
+ *
+ * @return integer
+ */
+ public function getThickness()
+ {
+ return $this->thickness;
+ }
+
+ /**
+ * Set the center color for this Donut
+ *
+ * @param string $centerColor
+ *
+ * @return $this
+ */
+ public function setCenterColor($centerColor)
+ {
+ $this->centerColor = $centerColor;
+
+ return $this;
+ }
+
+ /**
+ * Get the center color for this Donut
+ *
+ * @return string
+ */
+ public function getCenterColor()
+ {
+ return $this->centerColor;
+ }
+
+ /**
+ * Set the text of the big label
+ *
+ * @param string $labelBig
+ *
+ * @return $this
+ */
+ public function setLabelBig($labelBig)
+ {
+ $this->labelBig = $labelBig;
+
+ return $this;
+ }
+
+ /**
+ * Get the text of the big label
+ *
+ * @return string
+ */
+ public function getLabelBig()
+ {
+ return $this->labelBig;
+ }
+
+ /**
+ * Set the url behind the big label
+ *
+ * @param Url $labelBigUrl
+ *
+ * @return $this
+ */
+ public function setLabelBigUrl($labelBigUrl)
+ {
+ $this->labelBigUrl = $labelBigUrl;
+
+ return $this;
+ }
+
+ /**
+ * Get the url behind the big label
+ *
+ * @return Url
+ */
+ public function getLabelBigUrl()
+ {
+ return $this->labelBigUrl;
+ }
+
+ /**
+ * Get whether the big label shall be eye-catching
+ *
+ * @return bool
+ */
+ public function getLabelBigEyeCatching()
+ {
+ return $this->labelBigState !== null;
+ }
+
+ /**
+ * Set whether the big label shall be eye-catching
+ *
+ * @param bool $labelBigEyeCatching
+ *
+ * @return $this
+ */
+ public function setLabelBigEyeCatching($labelBigEyeCatching = true)
+ {
+ $this->labelBigState = $labelBigEyeCatching ? 'critical' : null;
+
+ return $this;
+ }
+
+ /**
+ * Get the state the big label shall indicate
+ *
+ * @return string|null
+ */
+ public function getLabelBigState()
+ {
+ return $this->labelBigState;
+ }
+
+ /**
+ * Set the state the big label shall indicate
+ *
+ * @param string|null $labelBigState
+ *
+ * @return $this
+ */
+ public function setLabelBigState($labelBigState)
+ {
+ $this->labelBigState = $labelBigState;
+
+ return $this;
+ }
+
+ /**
+ * Set the text of the small label
+ *
+ * @param string $labelSmall
+ *
+ * @return $this
+ */
+ public function setLabelSmall($labelSmall)
+ {
+ $this->labelSmall = $labelSmall;
+
+ return $this;
+ }
+
+ /**
+ * Get the text of the small label
+ *
+ * @return string
+ */
+ public function getLabelSmall()
+ {
+ return $this->labelSmall;
+ }
+
+ /**
+ * Put together all slices of this Donut
+ *
+ * @return array $svg
+ */
+ protected function assemble()
+ {
+ // svg tag containing the ring
+ $svg = array(
+ 'tag' => 'svg',
+ 'attributes' => array(
+ 'xmlns' => 'http://www.w3.org/2000/svg',
+ 'viewbox' => '0 0 40 40',
+ 'class' => 'donut-graph'
+ ),
+ 'content' => array()
+ );
+
+ // Donut hole
+ $svg['content'][] = array(
+ 'tag' => 'circle',
+ 'attributes' => array(
+ 'cx' => 20,
+ 'cy' => 20,
+ 'r' => sprintf('%F', $this->radius),
+ 'fill' => $this->getCenterColor()
+ )
+ );
+
+ // When there is no data show gray circle
+ $svg['content'][] = array(
+ 'tag' => 'circle',
+ 'attributes' => array(
+ 'aria-hidden' => true,
+ 'cx' => 20,
+ 'cy' => 20,
+ 'r' => sprintf('%F', $this->radius),
+ 'fill' => $this->getCenterColor(),
+ 'stroke-width' => $this->getThickness(),
+ 'class' => 'slice-state-not-checked'
+ )
+ );
+
+ $slices = $this->slices;
+
+ if ($this->count !== 0) {
+ array_walk($slices, function (&$slice) {
+ $slice[0] = round(100 / $this->count * $slice[0], 2);
+ });
+ }
+
+ // on 0 the donut would start at "3 o'clock" and the offset shifts counterclockwise
+ $offset = 25;
+
+ foreach ($slices as $slice) {
+ $svg['content'][] = array(
+ 'tag' => 'circle',
+ 'attributes' => $slice[1] + array(
+ 'cx' => 20,
+ 'cy' => 20,
+ 'r' => sprintf('%F', $this->radius),
+ 'fill' => 'transparent',
+ 'stroke-width' => $this->getThickness(),
+ 'stroke-dasharray' => sprintf('%F', $slice[0])
+ . ' '
+ . sprintf('%F', (99.9 - $slice[0])), // 99.9 prevents gaps (slight overlap)
+ 'stroke-dashoffset' => sprintf('%F', $offset)
+ )
+ );
+ // negative values shift in the clockwise direction
+ $offset -= $slice[0];
+ }
+
+ $result = array(
+ 'tag' => 'div',
+ 'content' => array($svg)
+ );
+
+ $labelBig = (string) $this->getLabelBig();
+ $labelSmall = (string) $this->getLabelSmall();
+
+ if ($labelBig !== '' || $labelSmall !== '') {
+ $labels = array(
+ 'tag' => 'div',
+ 'attributes' => array(
+ 'class' => 'donut-label'
+ ),
+ 'content' => array()
+ );
+
+ if ($labelBig !== '') {
+ $labels['content'][] =
+ array(
+ 'tag' => 'a',
+ 'attributes' => array(
+ 'aria-label' => $labelBig . ' ' . $labelSmall,
+ 'href' => $this->getLabelBigUrl() ? $this->getLabelBigUrl()->getAbsoluteUrl() : null,
+ 'class' => $this->labelBigState === null
+ ? 'donut-label-big'
+ : 'donut-label-big state-' . $this->labelBigState
+ ),
+ 'content' => $this->shortenLabel($labelBig)
+ );
+ }
+
+ if ($labelSmall !== '') {
+ $labels['content'][] = array(
+ 'tag' => 'p',
+ 'attributes' => array(
+ 'class' => 'donut-label-small',
+ 'x' => '50%',
+ 'y' => '50%'
+ ),
+ 'content' => $labelSmall
+ );
+ }
+
+ $result['content'][] = $labels;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Shorten the label to 3 digits if it is numeric
+ *
+ * 10 => 10 ... 1111 => ~1k ... 1888 => ~2k
+ *
+ * @param int|string $label
+ *
+ * @return string
+ */
+ protected function shortenLabel($label)
+ {
+ if (is_numeric($label) && strlen($label) > 3) {
+ return round($label, -3)/1000 . 'k';
+ }
+
+ return $label;
+ }
+
+ protected function encode($content)
+ {
+ return htmlspecialchars($content, ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8', true);
+ }
+
+ protected function renderAttributes(array $attributes)
+ {
+ $html = array();
+
+ foreach ($attributes as $name => $value) {
+ if ($value === null) {
+ continue;
+ }
+
+ if (is_bool($value) && $value) {
+ $html[] = $name;
+ continue;
+ }
+
+ if (is_array($value)) {
+ $value = implode(' ', $value);
+ }
+
+ $html[] = "$name=\"" . $this->encode($value) . '"';
+ }
+
+ return implode(' ', $html);
+ }
+
+ protected function renderContent(array $element)
+ {
+ $tag = $element['tag'];
+ $attributes = isset($element['attributes']) ? $element['attributes'] : array();
+ $content = isset($element['content']) ? $element['content'] : null;
+
+ $html = array(
+ // rtrim because attributes may be empty
+ rtrim("<$tag " . $this->renderAttributes($attributes))
+ . ">"
+ );
+
+ if ($content !== null) {
+ if (is_array($content)) {
+ foreach ($content as $child) {
+ $html[] = is_array($child) ? $this->renderContent($child) : $this->encode($child);
+ }
+ } else {
+ $html[] = $this->encode($content);
+ }
+ }
+
+ $html[] = "</$tag>";
+
+ return implode("\n", $html);
+ }
+
+ public function render()
+ {
+ $svg = $this->assemble();
+
+ return $this->renderContent($svg);
+ }
+}
diff --git a/library/Icinga/Chart/Format.php b/library/Icinga/Chart/Format.php
new file mode 100644
index 0000000..9e6c4db
--- /dev/null
+++ b/library/Icinga/Chart/Format.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+class Format
+{
+ /**
+ * Format a number into a number-string as defined by the SVG-Standard
+ *
+ * @see http://www.w3.org/TR/SVG/types.html#DataTypeNumber
+ *
+ * @param $number
+ *
+ * @return string
+ */
+ public static function formatSVGNumber($number)
+ {
+ return number_format($number, 1, '.', '');
+ }
+}
diff --git a/library/Icinga/Chart/Graph/BarGraph.php b/library/Icinga/Chart/Graph/BarGraph.php
new file mode 100644
index 0000000..adef428
--- /dev/null
+++ b/library/Icinga/Chart/Graph/BarGraph.php
@@ -0,0 +1,162 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Graph;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Animation;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Primitive\Rect;
+use Icinga\Chart\Primitive\Styleable;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Bar graph implementation
+ */
+class BarGraph extends Styleable implements Drawable
+{
+ /**
+ * The dataset order
+ *
+ * @var int
+ */
+ private $order = 0;
+
+ /**
+ * The width of the bars.
+ *
+ * @var int
+ */
+ private $barWidth = 3;
+
+ /**
+ * The dataset to use for this bar graph
+ *
+ * @var array
+ */
+ private $dataSet;
+
+ /**
+ * The tooltips
+ *
+ * @var
+ */
+ private $tooltips;
+
+ /**
+ * All graphs
+ *
+ * @var
+ */
+ private $graphs;
+
+ /**
+ * Create a new BarGraph with the given dataset
+ *
+ * @param array $dataSet An array of data points
+ * @param int $order The graph number displayed by this BarGraph
+ * @param array $tooltips The tooltips to display for each value
+ */
+ public function __construct(
+ array $dataSet,
+ array &$graphs,
+ $order,
+ array $tooltips = null
+ ) {
+ $this->order = $order;
+ $this->dataSet = $dataSet;
+
+ $this->tooltips = $tooltips;
+ foreach ($this->tooltips as $value) {
+ $ts[] = $value;
+ }
+ $this->tooltips = $ts;
+
+ $this->graphs = $graphs;
+ }
+
+ /**
+ * Apply configuration styles from the $cfg
+ *
+ * @param array $cfg The configuration as given in the drawBars call
+ */
+ public function setStyleFromConfig(array $cfg)
+ {
+ foreach ($cfg as $elem => $value) {
+ if ($elem === 'color') {
+ $this->setFill($value);
+ } elseif ($elem === 'width') {
+ $this->setStrokeWidth($value);
+ }
+ }
+ }
+
+ /**
+ * Draw a single rectangle
+ *
+ * @param array $point The
+ * @param string $fill The fill color to use
+ * @param $strokeWidth
+ * @param null $index
+ *
+ * @return Rect
+ */
+ private function drawSingleBar($point, $fill, $strokeWidth, $index = null)
+ {
+ $rect = new Rect($point[0] - ($this->barWidth / 2), $point[1], $this->barWidth, 100 - $point[1]);
+ $rect->setFill($fill);
+ $rect->setStrokeWidth($strokeWidth);
+ $rect->setStrokeColor('black');
+ if (isset($index)) {
+ $rect->setAttribute('data-icinga-graph-index', $index);
+ }
+ $rect->setAttribute('data-icinga-graph-type', 'bar');
+ $rect->setAdditionalStyle('clip-path: url(#clip);');
+ return $rect;
+ }
+
+ /**
+ * Render this BarChart
+ *
+ * @param RenderContext $ctx The rendering context to use for drawing
+ *
+ * @return DOMElement $dom Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $group = $doc->createElement('g');
+ $idx = 0;
+
+ if (count($this->dataSet) > 15) {
+ $this->barWidth = 2;
+ }
+ if (count($this->dataSet) > 25) {
+ $this->barWidth = 1;
+ }
+
+ foreach ($this->dataSet as $x => $point) {
+ // add white background bar, to prevent other bars from altering transparency effects
+ $bar = $this->drawSingleBar($point, 'white', $this->strokeWidth, $idx++)->toSvg($ctx);
+ $group->appendChild($bar);
+
+ // draw actual bar
+ $bar = $this->drawSingleBar($point, $this->fill, $this->strokeWidth)->toSvg($ctx);
+ if (isset($this->tooltips[$x])) {
+ $data = array(
+ 'label' => isset($this->graphs[$this->order]['label']) ?
+ strtolower($this->graphs[$this->order]['label']) : '',
+ 'color' => isset($this->graphs[$this->order]['color']) ?
+ strtolower($this->graphs[$this->order]['color']) : '#fff'
+ );
+ $format = isset($this->graphs[$this->order]['tooltip'])
+ ? $this->graphs[$this->order]['tooltip'] : null;
+ $title = $ctx->getDocument()->createElement('title');
+ $title->textContent = $this->tooltips[$x]->renderNoHtml($this->order, $data, $format);
+ $bar->appendChild($title);
+ }
+ $group->appendChild($bar);
+ }
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Graph/LineGraph.php b/library/Icinga/Chart/Graph/LineGraph.php
new file mode 100644
index 0000000..6954c59
--- /dev/null
+++ b/library/Icinga/Chart/Graph/LineGraph.php
@@ -0,0 +1,195 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Graph;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Primitive\Path;
+use Icinga\Chart\Primitive\Circle;
+use Icinga\Chart\Primitive\Styleable;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * LineGraph implementation for drawing a set of datapoints as
+ * a connected path
+ */
+class LineGraph extends Styleable implements Drawable
+{
+ /**
+ * The dataset to use
+ *
+ * @var array
+ */
+ private $dataset;
+
+ /**
+ * True to show dots for each datapoint
+ *
+ * @var bool
+ */
+ private $showDataPoints = false;
+
+ /**
+ * When true, the path will be discrete, i.e. showing hard steps instead of a direct line
+ *
+ * @var bool
+ */
+ private $isDiscrete = false;
+
+ /**
+ * The tooltips
+ *
+ * @var
+ */
+ private $tooltips;
+
+ /**
+ * The default stroke width
+ * @var int
+ */
+ public $strokeWidth = 5;
+
+ /**
+ * The size of the displayed dots
+ *
+ * @var int
+ */
+ public $dotWith = 0;
+
+ /**
+ * Create a new LineGraph displaying the given dataset
+ *
+ * @param array $dataset An array of [x, y] arrays to display
+ */
+ public function __construct(
+ array $dataset,
+ array &$graphs,
+ $order,
+ array $tooltips = null
+ ) {
+ usort($dataset, array($this, 'sortByX'));
+ $this->dataset = $dataset;
+ $this->graphs = $graphs;
+
+ $this->tooltips = $tooltips;
+ foreach ($this->tooltips as $value) {
+ $ts[] = $value;
+ }
+ $this->tooltips = $ts;
+ $this->order = $order;
+ }
+
+ /**
+ * Set datapoints to be emphased via dots
+ *
+ * @param bool $bool True to enable datapoints, otherwise false
+ */
+ public function setShowDataPoints($bool)
+ {
+ $this->showDataPoints = $bool;
+ }
+
+ /**
+ * Sort the daset by the xaxis
+ *
+ * @param array $v1
+ * @param array $v2
+ * @return int
+ */
+ private function sortByX(array $v1, array $v2)
+ {
+ if ($v1[0] === $v2[0]) {
+ return 0;
+ }
+ return ($v1[0] < $v2[0]) ? -1 : 1;
+ }
+
+ /**
+ * Configure this style
+ *
+ * @param array $cfg The configuration as given in the drawLine call
+ */
+ public function setStyleFromConfig(array $cfg)
+ {
+ $fill = false;
+ foreach ($cfg as $elem => $value) {
+ if ($elem === 'color') {
+ $this->setStrokeColor($value);
+ } elseif ($elem === 'width') {
+ $this->setStrokeWidth($value);
+ } elseif ($elem === 'showPoints') {
+ $this->setShowDataPoints($value);
+ } elseif ($elem === 'fill') {
+ $fill = $value;
+ } elseif ($elem === 'discrete') {
+ $this->isDiscrete = true;
+ }
+ }
+ if ($fill) {
+ $this->setFill($this->strokeColor);
+ $this->setStrokeColor('black');
+ }
+ }
+
+ /**
+ * Render this BarChart
+ *
+ * @param RenderContext $ctx The rendering context to use for drawing
+ *
+ * @return DOMElement $dom Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $path = new Path($this->dataset);
+ if ($this->isDiscrete) {
+ $path->setDiscrete(true);
+ }
+ $path->setStrokeColor($this->strokeColor);
+ $path->setStrokeWidth($this->strokeWidth);
+
+ $path->setAttribute('data-icinga-graph-type', 'line');
+ if ($this->fill !== 'none') {
+ $firstX = $this->dataset[0][0];
+ $lastX = $this->dataset[count($this->dataset)-1][0];
+ $path->prepend(array($firstX, 100))
+ ->append(array($lastX, 100));
+ $path->setFill($this->fill);
+ }
+
+ $path->setAdditionalStyle('clip-path: url(#clip);');
+ $path->setId($this->id);
+ $group = $path->toSvg($ctx);
+
+ foreach ($this->dataset as $x => $point) {
+ if ($this->showDataPoints === true) {
+ $dot = new Circle($point[0], $point[1], $this->dotWith);
+ $dot->setFill($this->strokeColor);
+ $group->appendChild($dot->toSvg($ctx));
+ }
+
+ // Draw invisible circle for tooltip hovering
+ if (isset($this->tooltips[$x])) {
+ $invisible = new Circle($point[0], $point[1], 20);
+ $invisible->setFill($this->strokeColor);
+ $invisible->setAdditionalStyle('opacity: 0.0;');
+ $data = array(
+ 'label' => isset($this->graphs[$this->order]['label']) ?
+ strtolower($this->graphs[$this->order]['label']) : '',
+ 'color' => isset($this->graphs[$this->order]['color']) ?
+ strtolower($this->graphs[$this->order]['color']) : '#fff'
+ );
+ $format = isset($this->graphs[$this->order]['tooltip'])
+ ? $this->graphs[$this->order]['tooltip'] : null;
+ $title = $ctx->getDocument()->createElement('title');
+ $title->textContent = $this->tooltips[$x]->renderNoHtml($this->order, $data, $format);
+ $invisibleRendered = $invisible->toSvg($ctx);
+ $invisibleRendered->appendChild($title);
+ $group->appendChild($invisibleRendered);
+ }
+ }
+
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Graph/StackedGraph.php b/library/Icinga/Chart/Graph/StackedGraph.php
new file mode 100644
index 0000000..49801a9
--- /dev/null
+++ b/library/Icinga/Chart/Graph/StackedGraph.php
@@ -0,0 +1,88 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Graph;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Graph implementation that stacks several graphs and displays them in a cumulative way
+ */
+class StackedGraph implements Drawable
+{
+ /**
+ * All graphs displayed in this stackedgraph
+ *
+ * @var array
+ */
+ private $stack = array();
+
+ /**
+ * An associative array containing x points as the key and an array of y values as the value
+ *
+ * @var array
+ */
+ private $points = array();
+
+ /**
+ * Add a graph to this stack and aggregate the values on the fly
+ *
+ * This modifies the dataset as a side effect
+ *
+ * @param array $subGraph
+ */
+ public function addGraph(array &$subGraph)
+ {
+ foreach ($subGraph['data'] as &$point) {
+ $x = $point[0];
+ if (!isset($this->points[$x])) {
+ $this->points[$x] = 0;
+ }
+ // store old y-value for displaying the actual (non-aggregated)
+ // value in the tooltip
+ $point[2] = $point[1];
+
+ $this->points[$x] += $point[1];
+ $point[1] = $this->points[$x];
+ }
+ }
+
+ /**
+ * Add a graph to the stack
+ *
+ * @param $graph
+ */
+ public function addToStack($graph)
+ {
+ $this->stack[] = $graph;
+ }
+
+ /**
+ * Empty the stack
+ *
+ * @return bool
+ */
+ public function stackEmpty()
+ {
+ return empty($this->stack);
+ }
+
+ /**
+ * Render this stack in the correct order
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG representation of this graph
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $group = $ctx->getDocument()->createElement('g');
+ $renderOrder = array_reverse($this->stack);
+ foreach ($renderOrder as $stackElem) {
+ $group->appendChild($stackElem->toSvg($ctx));
+ }
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Graph/Tooltip.php b/library/Icinga/Chart/Graph/Tooltip.php
new file mode 100644
index 0000000..7236685
--- /dev/null
+++ b/library/Icinga/Chart/Graph/Tooltip.php
@@ -0,0 +1,143 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Graph;
+
+/**
+ * A tooltip that stores and aggregates information about displayed data
+ * points of a graph and replaces them in a format string to render the description
+ * for specific data points of the graph.
+ *
+ * When render() is called, placeholders for the keys for each data entry will be replaced by
+ * the current value of this data set and the formatted string will be returned.
+ * The content of the replaced keys can change for each data set and depends on how the data
+ * is passed to this class. There are several types of properties:
+ *
+ * <ul>
+ * <li>Global properties</li>: Key-value pairs that stay the same every time render is called, and are
+ * passed to an instance in the constructor.
+ * <li>Aggregated properties</li>: Global properties that are created automatically from
+ * all attached data points.
+ * <li>Local properties</li>: Key-value pairs that only apply to a single data point and
+ * are passed to the render-function.
+ * </ul>
+ */
+class Tooltip
+{
+ /**
+ * The default format string used
+ * when no other format is specified
+ *
+ * @var string
+ */
+ private $defaultFormat;
+
+ /**
+ * All aggregated points
+ *
+ * @var array
+ */
+ private $points = array();
+
+ /**
+ * Contains all static replacements
+ *
+ * @var array
+ */
+ private $data = array(
+ 'sum' => 0
+ );
+
+ /**
+ * Used to format the displayed tooltip.
+ *
+ * @var string
+ */
+ protected $tooltipFormat;
+
+ /**
+ * Create a new tooltip with the specified default format string
+ *
+ * Allows you to set the global data for this tooltip, that is displayed every
+ * time render is called.
+ *
+ * @param array $data Map of global properties
+ * @param string $format The default format string
+ */
+ public function __construct(
+ $data = array(),
+ $format = '<b>{title}</b>: {value} {label}'
+ ) {
+ $this->data = array_merge($this->data, $data);
+ $this->defaultFormat = $format;
+ }
+
+ /**
+ * Add a single data point to update the aggregated properties for this tooltip
+ *
+ * @param $point array Contains the (x,y) values of the data set
+ */
+ public function addDataPoint($point)
+ {
+ // set x-value
+ if (!isset($this->data['title'])) {
+ $this->data['title'] = $point[0];
+ }
+
+ // aggregate y-values
+ $y = (int)$point[1];
+ if (isset($point[2])) {
+ // load original value in case value already aggregated
+ $y = (int)$point[2];
+ }
+
+ if (!isset($this->data['min']) || $this->data['min'] > $y) {
+ $this->data['min'] = $y;
+ }
+ if (!isset($this->data['max']) || $this->data['max'] < $y) {
+ $this->data['max'] = $y;
+ }
+ $this->data['sum'] += $y;
+ $this->points[] = $y;
+ }
+
+ /**
+ * Format the tooltip for a certain data point
+ *
+ * @param array $order Which data set to render
+ * @param array $data The local data for this tooltip
+ * @param string $format Use a custom format string for this data set
+ *
+ * @return mixed|string The tooltip value
+ */
+ public function render($order, $data = array(), $format = null)
+ {
+ if (isset($format)) {
+ $str = $format;
+ } else {
+ $str = $this->defaultFormat;
+ }
+ $data['value'] = $this->points[$order];
+ foreach (array_merge($this->data, $data) as $key => $value) {
+ $str = str_replace('{' . $key . '}', $value, $str);
+ }
+ return $str;
+ }
+
+ /**
+ * Format the tooltip for a certain data point but remove all
+ * occurring html tags
+ *
+ * This is useful for rendering clean tooltips on client without JavaScript
+ *
+ * @param array $order Which data set to render
+ * @param array $data The local data for this tooltip
+ * @param string $format Use a custom format string for this data set
+ *
+ * @return mixed|string The tooltip value, without any HTML tags
+ */
+ public function renderNoHtml($order, $data, $format)
+ {
+ return strip_tags($this->render($order, $data, $format));
+ }
+}
diff --git a/library/Icinga/Chart/GridChart.php b/library/Icinga/Chart/GridChart.php
new file mode 100644
index 0000000..a8cfca6
--- /dev/null
+++ b/library/Icinga/Chart/GridChart.php
@@ -0,0 +1,446 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use DOMElement;
+use Icinga\Chart\Chart;
+use Icinga\Chart\Axis;
+use Icinga\Chart\Graph\BarGraph;
+use Icinga\Chart\Graph\LineGraph;
+use Icinga\Chart\Graph\StackedGraph;
+use Icinga\Chart\Graph\Tooltip;
+use Icinga\Chart\Primitive\Canvas;
+use Icinga\Chart\Primitive\Rect;
+use Icinga\Chart\Primitive\Path;
+use Icinga\Chart\Render\LayoutBox;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Unit\AxisUnit;
+
+/**
+ * Base class for grid based charts.
+ *
+ * Allows drawing of Line and Barcharts. See the graphing documentation for further details.
+ *
+ * Example:
+ * <pre>
+ * <code>
+ * $this->chart = new GridChart();
+ * $this->chart->setAxisLabel("X axis label", "Y axis label");
+ * $this->chart->setXAxis(Axis::CalendarUnit());
+ * $this->chart->drawLines(
+ * array(
+ * 'data' => array(
+ * array(time()-7200, 10),array(time()-3620, 30), array(time()-1800, 15), array(time(), 92))
+ * )
+ * );
+ * </code>
+ * </pre>
+ */
+class GridChart extends Chart
+{
+ /**
+ * Internal identifier for Line Chart elements
+ */
+ const TYPE_LINE = "LINE";
+
+ /**
+ * Internal identifier fo Bar Chart elements
+ */
+ const TYPE_BAR = "BAR";
+
+ /**
+ * Internal array containing all elements to be drawn in the order they are drawn
+ *
+ * @var array
+ */
+ private $graphs = array();
+
+ /**
+ * An associative array containing all axis of this Chart in the "name" => Axis() form.
+ *
+ * Currently only the 'default' axis is really supported
+ *
+ * @var array
+ */
+ private $axis = array();
+
+ /**
+ * An associative array containing all StackedGraph objects used for cumulative graphs
+ *
+ * The array key is the 'stack' value given in the graph definitions
+ *
+ * @var array
+ */
+ private $stacks = array();
+
+ /**
+ * An associative array containing all Tooltips used to render the titles
+ *
+ * Each tooltip represents the summary for all y-values of a certain x-value
+ * in the grid chart
+ *
+ * @var Tooltip
+ */
+ private $tooltips = array();
+
+ public function __construct()
+ {
+ $this->title = t('Grid Chart');
+ $this->description = t('Contains data in a bar or line chart.');
+ parent::__construct();
+ }
+
+ /**
+ * Check if the current dataset has the proper structure for this chart.
+ *
+ * Needs to be overwritten by extending classes. The default implementation returns false.
+ *
+ * @return bool True when the dataset is valid, otherwise false
+ */
+ public function isValidDataFormat()
+ {
+ foreach ($this->graphs as $values) {
+ foreach ($values as $value) {
+ if (!isset($value['data']) || !is_array($value['data'])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Calls Axis::addDataset for every graph added to this GridChart
+ *
+ * @see Axis::addDataset
+ */
+ private function configureAxisFromDatasets()
+ {
+ foreach ($this->graphs as $axis => &$graphs) {
+ $axisObj = $this->axis[$axis];
+ foreach ($graphs as &$graph) {
+ $axisObj->addDataset($graph);
+ }
+ }
+ }
+
+ /**
+ * Add an arbitrary number of lines to be drawn
+ *
+ * Refer to the graphs.md for a detailed list of allowed attributes
+ *
+ * @param array $axis,... The line definitions to draw
+ *
+ * @return $this Fluid interface
+ */
+ public function drawLines(array $axis)
+ {
+ $this->draw(self::TYPE_LINE, func_get_args());
+ return $this;
+ }
+
+ /**
+ * Add arbitrary number of bars to be drawn
+ *
+ * Refer to the graphs.md for a detailed list of allowed attributes
+ *
+ * @param array $axis
+ * @return $this
+ */
+ public function drawBars(array $axis)
+ {
+ $this->draw(self::TYPE_BAR, func_get_args());
+ return $this;
+ }
+
+ /**
+ * Generic method for adding elements to the drawing stack
+ *
+ * @param string $type The type of the element to draw (see TYPE_ constants in this class)
+ * @param array $data The data given to the draw call
+ */
+ private function draw($type, $data)
+ {
+ $axisName = 'default';
+ if (is_string($data[0])) {
+ $axisName = $data[0];
+ array_shift($data);
+ }
+ foreach ($data as &$graph) {
+ $graph['graphType'] = $type;
+ if (isset($graph['stack'])) {
+ if (!isset($this->stacks[$graph['stack']])) {
+ $this->stacks[$graph['stack']] = new StackedGraph();
+ }
+ $this->stacks[$graph['stack']]->addGraph($graph);
+ $graph['stack'] = $this->stacks[$graph['stack']];
+ }
+
+ if (!isset($graph['color'])) {
+ $colorType = isset($graph['palette']) ? $graph['palette'] : Palette::NEUTRAL;
+ $graph['color'] = $this->palette->getNext($colorType);
+ }
+ $this->graphs[$axisName][] = $graph;
+ if ($this->legend) {
+ $this->legend->addDataset($graph);
+ }
+ }
+ $this->initTooltips($data);
+ }
+
+
+ private function initTooltips($data)
+ {
+ foreach ($data as &$graph) {
+ foreach ($graph['data'] as $x => $point) {
+ if (!array_key_exists($x, $this->tooltips)) {
+ $this->tooltips[$x] = new Tooltip(
+ array(
+ 'color' => $graph['color'],
+
+ )
+ );
+ }
+ $this->tooltips[$x]->addDataPoint($point);
+ }
+ }
+ }
+
+ /**
+ * Set the label for the x and y axis
+ *
+ * @param string $xAxisLabel The label to use for the x axis
+ * @param string $yAxisLabel The label to use for the y axis
+ * @param string $axisName The name of the axis, for now 'default'
+ *
+ * @return $this Fluid interface
+ */
+ public function setAxisLabel($xAxisLabel, $yAxisLabel, $axisName = 'default')
+ {
+ $this->axis[$axisName]->setXLabel($xAxisLabel)->setYLabel($yAxisLabel);
+ return $this;
+ }
+
+ /**
+ * Set the AxisUnit to use for calculating the values of the x axis
+ *
+ * @param AxisUnit $unit The unit for the x axis
+ * @param string $axisName The name of the axis to set the label for, currently only 'default'
+ *
+ * @return $this Fluid interface
+ */
+ public function setXAxis(AxisUnit $unit, $axisName = 'default')
+ {
+ $this->axis[$axisName]->setUnitForXAxis($unit);
+ return $this;
+ }
+
+ /**
+ * Set the AxisUnit to use for calculating the values of the y axis
+ *
+ * @param AxisUnit $unit The unit for the y axis
+ * @param string $axisName The name of the axis to set the label for, currently only 'default'
+ *
+ * @return $this Fluid interface
+ */
+ public function setYAxis(AxisUnit $unit, $axisName = 'default')
+ {
+ $this->axis[$axisName]->setUnitForYAxis($unit);
+ return $this;
+ }
+
+ /**
+ * Pre-render setup of the axis
+ *
+ * @see Chart::build
+ */
+ protected function build()
+ {
+ $this->configureAxisFromDatasets();
+ }
+
+ /**
+ * Initialize the renderer and overwrite it with an 2:1 ration renderer
+ */
+ protected function init()
+ {
+ $this->renderer = new SVGRenderer(100, 100);
+ $this->setAxis(Axis::createLinearAxis());
+ }
+
+ /**
+ * Overwrite the axis to use
+ *
+ * @param Axis $axis The new axis to use
+ * @param string $name The name of the axis, currently only 'default'
+ *
+ * @return $this Fluid interface
+ */
+ public function setAxis(Axis $axis, $name = 'default')
+ {
+ $this->axis = array($name => $axis);
+ return $this;
+ }
+
+ /**
+ * Add an axis to this graph (not really supported right now)
+ *
+ * @param Axis $axis The axis object to add
+ * @param string $name The name of the axis
+ *
+ * @return $this Fluid interface
+ */
+ public function addAxis(Axis $axis, $name)
+ {
+ $this->axis[$name] = $axis;
+ return $this;
+ }
+
+ /**
+ * Set minimum values for the x and y axis.
+ *
+ * Setting null to an axis means this will use a value determined by the dataset
+ *
+ * @param int $xMin The minimum value for the x axis or null to use a dynamic value
+ * @param int $yMin The minimum value for the y axis or null to use a dynamic value
+ * @param string $axisName The name of the axis to set the minimum, currently only 'default'
+ *
+ * @return $this Fluid interface
+ */
+ public function setAxisMin($xMin = null, $yMin = null, $axisName = 'default')
+ {
+ $this->axis[$axisName]->setXMin($xMin)->setYMin($yMin);
+ return $this;
+ }
+
+ /**
+ * Set maximum values for the x and y axis.
+ *
+ * Setting null to an axis means this will use a value determined by the dataset
+ *
+ * @param int $xMax The maximum value for the x axis or null to use a dynamic value
+ * @param int $yMax The maximum value for the y axis or null to use a dynamic value
+ * @param string $axisName The name of the axis to set the maximum, currently only 'default'
+ *
+ * @return $this Fluid interface
+ */
+ public function setAxisMax($xMax = null, $yMax = null, $axisName = 'default')
+ {
+ $this->axis[$axisName]->setXMax($xMax)->setYMax($yMax);
+ return $this;
+ }
+
+ /**
+ * Render this GridChart to SVG
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $outerBox = new Canvas('outerGraph', new LayoutBox(0, 0, 100, 100));
+ $innerBox = new Canvas('graph', new LayoutBox(0, 0, 95, 90));
+
+ $maxPadding = array(0,0,0,0);
+ foreach ($this->axis as $axis) {
+ $padding = $axis->getRequiredPadding();
+ for ($i=0; $i < count($padding); $i++) {
+ $maxPadding[$i] = max($maxPadding[$i], $padding[$i]);
+ }
+ $innerBox->addElement($axis);
+ }
+ $this->renderGraphContent($innerBox);
+
+ $innerBox->getLayout()->setPadding($maxPadding[0], $maxPadding[1], $maxPadding[2], $maxPadding[3]);
+ $this->createContentClipBox($innerBox);
+
+ $outerBox->addElement($innerBox);
+ if ($this->legend) {
+ $outerBox->addElement($this->legend);
+ }
+ return $outerBox->toSvg($ctx);
+ }
+
+ /**
+ * Create a clip box that defines which area of the graph is drawable and adds it to the graph.
+ *
+ * The clipbox has the id '#clip' and can be used in the clip-mask element
+ *
+ * @param Canvas $innerBox The inner canvas of the graph to add the clip box to
+ */
+ private function createContentClipBox(Canvas $innerBox)
+ {
+ $clipBox = new Canvas('clip', new LayoutBox(0, 0, 100, 100));
+ $clipBox->toClipPath();
+ $innerBox->addElement($clipBox);
+ $rect = new Rect(0.1, 0, 100, 99.9);
+ $clipBox->addElement($rect);
+ }
+
+ /**
+ * Render the content of the graph, i.e. the draw stack
+ *
+ * @param Canvas $innerBox The inner canvas of the graph to add the content to
+ */
+ private function renderGraphContent(Canvas $innerBox)
+ {
+ foreach ($this->graphs as $axisName => $graphs) {
+ $axis = $this->axis[$axisName];
+ $graphObj = null;
+ foreach ($graphs as $dataset => $graph) {
+ // determine the type and create a graph object for it
+ switch ($graph['graphType']) {
+ case self::TYPE_BAR:
+ $graphObj = new BarGraph(
+ $axis->transform($graph['data']),
+ $graphs,
+ $dataset,
+ $this->tooltips
+ );
+ break;
+ case self::TYPE_LINE:
+ $graphObj = new LineGraph(
+ $axis->transform($graph['data']),
+ $graphs,
+ $dataset,
+ $this->tooltips
+ );
+ break;
+ default:
+ continue 2;
+ }
+ $el = $this->setupGraph($graphObj, $graph);
+ if ($el) {
+ $innerBox->addElement($el);
+ }
+ }
+ }
+ }
+
+ /**
+ * Setup the provided Graph type
+ *
+ * @param mixed $graphObject The graph class, needs the setStyleFromConfig method
+ * @param array $graphConfig The configration array of the graph
+ *
+ * @return mixed Either the graph to be added or null if the graph is not directly added
+ * to the document (e.g. stacked graphs are added by
+ * the StackedGraph Composite object)
+ */
+ private function setupGraph($graphObject, array $graphConfig)
+ {
+ $graphObject->setStyleFromConfig($graphConfig);
+ // When in a stack return the StackedGraph object instead of the graphObject
+ if (isset($graphConfig['stack'])) {
+ $graphConfig['stack']->addToStack($graphObject);
+ if (!$graphConfig['stack']->stackEmpty()) {
+ return $graphConfig['stack'];
+ }
+ // return no object when the graph should not be rendered
+ return null;
+ }
+ return $graphObject;
+ }
+}
diff --git a/library/Icinga/Chart/Inline/Inline.php b/library/Icinga/Chart/Inline/Inline.php
new file mode 100644
index 0000000..3acbd73
--- /dev/null
+++ b/library/Icinga/Chart/Inline/Inline.php
@@ -0,0 +1,96 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Inline;
+
+/**
+ * Class to render and inline chart directly from the request params.
+ *
+ * When rendering huge amounts of inline charts it is too expensive
+ * to bootstrap the complete application for ever single chart and
+ * we need to be able render Charts in a compact environment without
+ * the other Icinga classes.
+ *
+ * Class Inline
+ * @package Icinga\Chart\Inline
+ */
+class Inline
+{
+
+ /**
+ * The data displayed in this chart
+ *
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * The colors used to display this chart
+ *
+ * @var array
+ */
+ protected $colors = array(
+ '#00FF00', // OK
+ '#FFFF00', // Warning
+ '#FF0000', // Critical
+ '#E066FF' // Unreachable
+ );
+
+ /**
+ * The labels displayed on this chart
+ *
+ * @var array
+ */
+ protected $labels = array();
+
+ /**
+ * The height in percent
+ *
+ * @var int
+ */
+ protected $height = 100;
+
+ /**
+ * The width in percent
+ *
+ * @var int
+ */
+ protected $width = 100;
+
+ protected function sanitizeStringArray(array $arr)
+ {
+ $sanitized = array();
+ foreach ($arr as $key => $value) {
+ $sanitized[$key] = htmlspecialchars($value);
+ }
+ return $sanitized;
+ }
+
+ /**
+ * Populate the properties from the current request.
+ */
+ public function initFromRequest()
+ {
+ $this->data = explode(',', $_GET['data']);
+ foreach ($this->data as $key => $value) {
+ $this->data[$key] = (int)$value;
+ }
+ for ($i = 0; $i < count($this->data); $i++) {
+ $this->labels[] = '';
+ }
+
+ if (array_key_exists('colors', $_GET)) {
+ $this->colors = $this->sanitizeStringArray(explode(',', $_GET['colors']));
+ }
+ while (count($this->colors) < count($this->data)) {
+ $this->colors[] = '#FEFEFE';
+ }
+
+ if (array_key_exists('width', $_GET)) {
+ $this->width = (int)$_GET['width'];
+ }
+ if (array_key_exists('height', $_GET)) {
+ $this->height = (int)$_GET['height'];
+ }
+ }
+}
diff --git a/library/Icinga/Chart/Inline/PieChart.php b/library/Icinga/Chart/Inline/PieChart.php
new file mode 100644
index 0000000..de68213
--- /dev/null
+++ b/library/Icinga/Chart/Inline/PieChart.php
@@ -0,0 +1,41 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Inline;
+
+use Icinga\Chart\PieChart as PieChartRenderer;
+
+/**
+ * Draw an inline pie-chart directly from the available request parameters.
+ */
+class PieChart extends Inline
+{
+ protected function getChart()
+ {
+ $pie = new PieChartRenderer();
+ $pie->alignTopLeft();
+ $pie->disableLegend();
+ $pie->drawPie(array(
+ 'data' => $this->data, 'colors' => $this->colors, 'labels' => $this->labels
+ ));
+ return $pie;
+ }
+
+ public function toSvg($output = true)
+ {
+ if ($output) {
+ echo $this->getChart()->render();
+ } else {
+ return $this->getChart()->render();
+ }
+ }
+
+ public function toPng($output = true)
+ {
+ if ($output) {
+ echo $this->getChart()->toPng($this->width, $this->height);
+ } else {
+ return $this->getChart()->toPng($this->width, $this->height);
+ }
+ }
+}
diff --git a/library/Icinga/Chart/Legend.php b/library/Icinga/Chart/Legend.php
new file mode 100644
index 0000000..ab1c9e0
--- /dev/null
+++ b/library/Icinga/Chart/Legend.php
@@ -0,0 +1,102 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use DOMElement;
+use Icinga\Chart\Palette;
+use Icinga\Chart\Primitive\Canvas;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Primitive\Rect;
+use Icinga\Chart\Primitive\Text;
+use Icinga\Chart\Render\LayoutBox;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Drawable for creating a Graph Legend on the bottom of a graph.
+ *
+ * Usually used by the GridChart class internally.
+ */
+class Legend implements Drawable
+{
+
+ /**
+ * Internal counter for unnamed label identifiers
+ *
+ * @var int
+ */
+ private $internalCtr = 0;
+
+ /**
+ *
+ * Content of this legend
+ *
+ * @var array
+ */
+ private $dataset = array();
+
+
+ /**
+ * Set the content to be displayed by this legend
+ *
+ * @param array $dataset An array of datasets in the form they are provided to the graphing implementation
+ */
+ public function addDataset(array $dataset)
+ {
+ if (!isset($dataset['label'])) {
+ $dataset['label'] = 'Dataset ' . (++$this->internalCtr);
+ }
+ if (!isset($dataset['color'])) {
+ return;
+ }
+ $this->dataset[$dataset['color']] = $dataset['label'];
+ }
+
+ /**
+ * Render the legend to an SVG object
+ *
+ * @param RenderContext $ctx The context to use for rendering this legend
+ *
+ * @return DOMElement The SVG representation of this legend
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $outer = new Canvas('legend', new LayoutBox(0, 0, 100, 100));
+ $outer->getLayout()->setPadding(2, 2, 2, 2);
+ $nrOfColumns = 4;
+
+ $topstep = 10 / $nrOfColumns + 2;
+
+ $top = 0;
+ $left = 0;
+ $lastLabelEndPos = -1;
+ foreach ($this->dataset as $color => $text) {
+ $leftstep = 100 / $nrOfColumns + strlen($text);
+
+ // Make sure labels don't overlap each other
+ while ($lastLabelEndPos >= $left) {
+ $left += $leftstep;
+ }
+ // When a label is longer than the available space, use the next line
+ if ($left + strlen($text) > 100) {
+ $top += $topstep;
+ $left = 0;
+ }
+
+ $colorBox = new Rect($left, $top, 2, 2);
+ $colorBox->setFill($color)->setStrokeWidth(2);
+ $colorBox->keepRatio();
+ $outer->addElement($colorBox);
+
+ $textBox = new Text($left+5, $top+2, $text);
+ $textBox->setFontSize('2em');
+ $outer->addElement($textBox);
+
+ // readjust layout
+ $lastLabelEndPos = $left + strlen($text);
+ $left += $leftstep;
+ }
+ $svg = $outer->toSvg($ctx);
+ return $svg;
+ }
+}
diff --git a/library/Icinga/Chart/Palette.php b/library/Icinga/Chart/Palette.php
new file mode 100644
index 0000000..90ad74b
--- /dev/null
+++ b/library/Icinga/Chart/Palette.php
@@ -0,0 +1,65 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+/**
+ * Provide a set of colors that will be used by the chart as default values
+ */
+class Palette
+{
+ /**
+ * Neutral colors without special meaning
+ */
+ const NEUTRAL = 'neutral';
+
+ /**
+ * A set of problem (i.e. red) colors
+ */
+ const PROBLEM = 'problem';
+
+ /**
+ * A set of ok (i.e. green) colors
+ */
+ const OK = 'ok';
+
+ /**
+ * A set of warning (i.e. yellow) colors
+ */
+ const WARNING = 'warning';
+
+ /**
+ * The colorsets for specific categories
+ *
+ * @var array
+ */
+ public $colorSets = array(
+ self::OK => array('#00FF00'),
+ self::PROBLEM => array('#FF0000'),
+ self::WARNING => array('#FFFF00'),
+ self::NEUTRAL => array('#f3f3f3')
+ );
+
+ /**
+ * Return the next available color as an hex string for the given type
+ *
+ * @param string $type The type to receive a color from
+ *
+ * @return string The color in hex format
+ */
+ public function getNext($type = self::NEUTRAL)
+ {
+ if (!isset($this->colorSets[$type])) {
+ $type = self::NEUTRAL;
+ }
+
+ $color = current($this->colorSets[$type]);
+ if ($color === false) {
+ reset($this->colorSets[$type]);
+
+ $color = current($this->colorSets[$type]);
+ }
+ next($this->colorSets[$type]);
+ return $color;
+ }
+}
diff --git a/library/Icinga/Chart/PieChart.php b/library/Icinga/Chart/PieChart.php
new file mode 100644
index 0000000..1bcf380
--- /dev/null
+++ b/library/Icinga/Chart/PieChart.php
@@ -0,0 +1,306 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use DOMElement;
+use Icinga\Chart\Chart;
+use Icinga\Chart\Primitive\Canvas;
+use Icinga\Chart\Primitive\PieSlice;
+use Icinga\Chart\Primitive\RawElement;
+use Icinga\Chart\Primitive\Rect;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Render\LayoutBox;
+
+/**
+ * Graphing component for rendering Pie Charts.
+ *
+ * See the graphs.md documentation for further information about how to use this component
+ */
+class PieChart extends Chart
+{
+ /**
+ * Stack multiple pies
+ */
+ const STACKED = "stacked";
+
+ /**
+ * Draw multiple pies beneath each other
+ */
+ const ROW = "row";
+
+ /**
+ * The drawing stack containing all pie definitions in the order they will be drawn
+ *
+ * @var array
+ */
+ private $pies = array();
+
+ /**
+ * The composition type currently used
+ *
+ * @var string
+ */
+ private $type = PieChart::STACKED;
+
+ /**
+ * Disable drawing of captions when set true
+ *
+ * @var bool
+ */
+ private $noCaption = false;
+
+ public function __construct()
+ {
+ $this->title = t('Pie Chart');
+ $this->description = t('Contains data in a pie chart.');
+ parent::__construct();
+ }
+
+ /**
+ * Test if the given pies have the correct format
+ *
+ * @return bool True when the given pies are correct, otherwise false
+ */
+ public function isValidDataFormat()
+ {
+ foreach ($this->pies as $pie) {
+ if (!isset($pie['data']) || !is_array($pie['data'])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Create renderer and normalize the dataset to represent percentage information
+ */
+ protected function build()
+ {
+ $this->renderer = new SVGRenderer(($this->type === self::STACKED) ? 1 : count($this->pies), 1);
+ foreach ($this->pies as &$pie) {
+ $this->normalizeDataSet($pie);
+ }
+ }
+
+ /**
+ * Normalize the given dataset to represent percentage information instead of absolute valuess
+ *
+ * @param array $pie The pie definition given in the drawPie call
+ */
+ private function normalizeDataSet(&$pie)
+ {
+ $total = array_sum($pie['data']);
+ if ($total === 100) {
+ return;
+ }
+ if ($total == 0) {
+ return;
+ }
+ foreach ($pie['data'] as &$slice) {
+ $slice = $slice/$total * 100;
+ }
+ }
+
+ /**
+ * Draw an arbitrary number of pies in this chart
+ *
+ * @param array $dataSet,... The pie definition, see graphs.md for further details concerning the format
+ *
+ * @return $this Fluent interface
+ */
+ public function drawPie(array $dataSet)
+ {
+ $dataSets = func_get_args();
+ $this->pies += $dataSets;
+ foreach ($dataSets as $dataSet) {
+ $this->legend->addDataset($dataSet);
+ }
+ return $this;
+ }
+
+ /**
+ * Return the SVG representation of this graph
+ *
+ * @param RenderContext $ctx The context to use for drawings
+ *
+ * @return DOMElement The SVG representation of this graph
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $labelBox = $ctx->getDocument()->createElement('g');
+ if (!$this->noCaption) {
+ // Scale SVG to make room for captions
+ $outerBox = new Canvas('outerGraph', new LayoutBox(33, -5, 40, 40));
+ $innerBox = new Canvas('graph', new LayoutBox(0, 0, 100, 100));
+ $innerBox->getLayout()->setPadding(10, 10, 10, 10);
+ } else {
+ $outerBox = new Canvas('outerGraph', new LayoutBox(1.5, -10, 124, 124));
+ $innerBox = new Canvas('graph', new LayoutBox(0, 0, 100, 100));
+ $innerBox->getLayout()->setPadding(0, 0, 0, 0);
+ }
+ $this->createContentClipBox($innerBox);
+ $this->renderPies($innerBox, $labelBox);
+ $innerBox->addElement(new RawElement($labelBox));
+ $outerBox->addElement($innerBox);
+
+ return $outerBox->toSvg($ctx);
+ }
+
+ /**
+ * Render the pies in the draw stack using the selected algorithm for composition
+ *
+ * @param Canvas $innerBox The canvas to use for inserting the pies
+ * @param DOMElement $labelBox The DOM element to add the labels to (so they can't be overlapped by pie elements)
+ */
+ private function renderPies(Canvas $innerBox, DOMElement $labelBox)
+ {
+ if ($this->type === self::STACKED) {
+ $this->renderStackedPie($innerBox, $labelBox);
+ } else {
+ $this->renderPieRow($innerBox, $labelBox);
+ }
+ }
+
+ /**
+ * Return the color to be used for the given pie slice
+ *
+ * @param array $pie The pie configuration as provided in the drawPie call
+ * @param int $dataIdx The index of the pie slice in the pie configuration
+ *
+ * @return string The hex color string to use for the pie slice
+ */
+ private function getColorForPieSlice(array $pie, $dataIdx)
+ {
+ if (isset($pie['colors']) && is_array($pie['colors']) && isset($pie['colors'][$dataIdx])) {
+ return $pie['colors'][$dataIdx];
+ }
+ $type = Palette::NEUTRAL;
+ if (isset($pie['palette']) && is_array($pie['palette']) && isset($pie['palette'][$dataIdx])) {
+ $type = $pie['palette'][$dataIdx];
+ }
+ return $this->palette->getNext($type);
+ }
+
+ /**
+ * Render a row of pies
+ *
+ * @param Canvas $innerBox The canvas to insert the pies to
+ * @param DOMElement $labelBox The DOMElement to use for adding label elements
+ */
+ private function renderPieRow(Canvas $innerBox, DOMElement $labelBox)
+ {
+ $radius = 50 / count($this->pies);
+ $x = $radius;
+ foreach ($this->pies as $pie) {
+ $labelPos = 0;
+ $lastRadius = 0;
+
+ foreach ($pie['data'] as $idx => $dataset) {
+ $slice = new PieSlice($radius, $dataset, $lastRadius);
+ $slice->setX($x)
+ ->setStrokeColor('#000')
+ ->setStrokeWidth(1)
+ ->setY(50)
+ ->setFill($this->getColorForPieSlice($pie, $idx));
+ $innerBox->addElement($slice);
+ // add caption if not disabled
+ if (!$this->noCaption && isset($pie['labels'])) {
+ $slice->setCaption($pie['labels'][$labelPos++])
+ ->setLabelGroup($labelBox);
+ }
+ $lastRadius += $dataset;
+ }
+ // shift right for next pie
+ $x += $radius*2;
+ }
+ }
+
+ /**
+ * Render pies in a stacked way so one pie is nested in the previous pie
+ *
+ * @param Canvas $innerBox The canvas to insert the pie to
+ * @param DOMElement $labelBox The DOMElement to use for adding label elements
+ */
+ private function renderStackedPie(Canvas $innerBox, DOMElement $labelBox)
+ {
+ $radius = 40;
+ $minRadius = 20;
+ if (count($this->pies) == 0) {
+ return;
+ }
+ $shrinkStep = ($radius - $minRadius) / count($this->pies);
+ $x = $radius;
+
+ for ($i = 0; $i < count($this->pies); $i++) {
+ $pie = $this->pies[$i];
+ // the offset for the caption path, outer caption indicator shouldn't point
+ // to the middle of the slice as there will be another pie
+ $offset = isset($this->pies[$i+1]) ? $radius - $shrinkStep : 0;
+ $labelPos = 0;
+ $lastRadius = 0;
+ foreach ($pie['data'] as $idx => $dataset) {
+ $color = $this->getColorForPieSlice($pie, $idx);
+ if ($dataset == 0) {
+ $labelPos++;
+ continue;
+ }
+ $slice = new PieSlice($radius, $dataset, $lastRadius);
+ $slice->setY(50)
+ ->setX($x)
+ ->setStrokeColor('#000')
+ ->setStrokeWidth(1)
+ ->setFill($color)
+ ->setLabelGroup($labelBox);
+
+ if (!$this->noCaption && isset($pie['labels'])) {
+ $slice->setCaption($pie['labels'][$labelPos++])
+ ->setCaptionOffset($offset)
+ ->setOuterCaptionBound(50);
+ }
+ $innerBox->addElement($slice);
+ $lastRadius += $dataset;
+ }
+ // shrinken the next pie
+ $radius -= $shrinkStep;
+ }
+ }
+
+ /**
+ * Set the composition type of this PieChart
+ *
+ * @param string $type Either self::STACKED or self::ROW
+ *
+ * @return $this Fluent interface
+ */
+ public function setType($type)
+ {
+ $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Hide the caption from this PieChart
+ *
+ * @return $this Fluent interface
+ */
+ public function disableLegend()
+ {
+ $this->noCaption = true;
+ return $this;
+ }
+
+ /**
+ * Create the content for this PieChart
+ *
+ * @param Canvas $innerBox The innerbox to add the clip mask to
+ */
+ private function createContentClipBox(Canvas $innerBox)
+ {
+ $clipBox = new Canvas('clip', new LayoutBox(0, 0, 100, 100));
+ $clipBox->toClipPath();
+ $innerBox->addElement($clipBox);
+ $rect = new Rect(0.1, 0, 100, 99.9);
+ $clipBox->addElement($rect);
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Animatable.php b/library/Icinga/Chart/Primitive/Animatable.php
new file mode 100644
index 0000000..69ba0e1
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Animatable.php
@@ -0,0 +1,43 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Base interface for animatable objects
+ */
+abstract class Animatable extends Styleable
+{
+ /**
+ * The animation object set
+ *
+ * @var Animation
+ */
+ public $animation = null;
+
+ /**
+ * Set the animation for this object
+ *
+ * @param Animation $anim The animation to use
+ */
+ public function setAnimation(Animation $anim)
+ {
+ $this->animation = $anim;
+ }
+
+ /**
+ * Append the animation to the given element
+ *
+ * @param DOMElement $dom The element to append the animation to
+ * @param RenderContext $ctx The context to use for rendering the animation object
+ */
+ protected function appendAnimation(DOMElement $dom, RenderContext $ctx)
+ {
+ if ($this->animation) {
+ $dom->appendChild($this->animation->toSvg($ctx));
+ }
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Animation.php b/library/Icinga/Chart/Primitive/Animation.php
new file mode 100644
index 0000000..4e55d0e
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Animation.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Drawable for the SVG animate tag
+ */
+class Animation implements Drawable
+{
+ /**
+ * The attribute to animate
+ *
+ * @var string
+ */
+ private $attribute;
+
+ /**
+ * The 'from' value
+ *
+ * @var mixed
+ */
+ private $from;
+
+ /**
+ * The to value
+ *
+ * @var mixed
+ */
+ private $to;
+
+ /**
+ * The begin value (in seconds)
+ *
+ * @var float
+ */
+ private $begin = 0;
+
+ /**
+ * The duration value (in seconds)
+ *
+ * @var float
+ */
+ private $duration = 0.5;
+
+ /**
+ * Create an animation object
+ *
+ * @param string $attribute The attribute to animate
+ * @param string $from The from value for the animation
+ * @param string $to The to value for the animation
+ * @param float $duration The duration of the duration
+ * @param float $begin The begin of the duration
+ */
+ public function __construct($attribute, $from, $to, $duration = 0.5, $begin = 0.0)
+ {
+ $this->attribute = $attribute;
+ $this->from = $from;
+ $this->to = $to;
+ $this->duration = $duration;
+ $this->begin = $begin;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+
+ $animate = $ctx->getDocument()->createElement('animate');
+ $animate->setAttribute('attributeName', $this->attribute);
+ $animate->setAttribute('attributeType', 'XML');
+ $animate->setAttribute('from', $this->from);
+ $animate->setAttribute('to', $this->to);
+ $animate->setAttribute('begin', $this->begin . 's');
+ $animate->setAttribute('dur', $this->duration . 's');
+ $animate->setAttributE('fill', "freeze");
+
+ return $animate;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Canvas.php b/library/Icinga/Chart/Primitive/Canvas.php
new file mode 100644
index 0000000..32f06bf
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Canvas.php
@@ -0,0 +1,140 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\LayoutBox;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Canvas SVG component that encapsulates grouping and padding and allows rendering
+ * multiple elements in a group
+ *
+ */
+class Canvas implements Drawable
+{
+ /**
+ * The name of the canvas, will be used as the id
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * An array of child elements of this Canvas
+ *
+ * @var array
+ */
+ private $children = array();
+
+ /**
+ * When true, this canvas is encapsulated in a clipPath tag and not drawn
+ *
+ * @var bool
+ */
+ private $isClipPath = false;
+
+ /**
+ * The LayoutBox of this Canvas
+ *
+ * @var LayoutBox
+ */
+ private $rect;
+
+ /**
+ * The aria role used to describe this canvas' purpose in the accessibility tree
+ *
+ * @var string
+ */
+ private $ariaRole;
+
+ /**
+ * Create this canvas
+ *
+ * @param String $name The name of this canvas
+ * @param LayoutBox $rect The layout and size of this canvas
+ */
+ public function __construct($name, LayoutBox $rect)
+ {
+ $this->rect = $rect;
+ $this->name = $name;
+ }
+
+ /**
+ * Convert this canvas to a clipPath element
+ */
+ public function toClipPath()
+ {
+ $this->isClipPath = true;
+ }
+
+ /**
+ * Return the layout of this canvas
+ *
+ * @return LayoutBox
+ */
+ public function getLayout()
+ {
+ return $this->rect;
+ }
+
+ /**
+ * Add an element to this canvas
+ *
+ * @param Drawable $child
+ */
+ public function addElement(Drawable $child)
+ {
+ $this->children[] = $child;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ if ($this->isClipPath) {
+ $outer = $doc->createElement('defs');
+ $innerContainer = $element = $doc->createElement('clipPath');
+ $outer->appendChild($element);
+ } else {
+ $outer = $element = $doc->createElement('g');
+ $innerContainer = $doc->createElement('g');
+ $innerContainer->setAttribute('x', 0);
+ $innerContainer->setAttribute('y', 0);
+ $innerContainer->setAttribute('id', $this->name . '_inner');
+ $innerContainer->setAttribute('transform', $this->rect->getInnerTransform($ctx));
+ $element->appendChild($innerContainer);
+ }
+
+ $element->setAttribute('id', $this->name);
+ foreach ($this->children as $child) {
+ $innerContainer->appendChild($child->toSvg($ctx));
+ }
+
+ if (isset($this->ariaRole)) {
+ $outer->setAttribute('role', $this->ariaRole);
+ }
+ return $outer;
+ }
+
+ /**
+ * Set the aria role used to determine the meaning of this canvas in the accessibility tree
+ *
+ * The role 'presentation' will indicate that the purpose of this canvas is entirely decorative, while the role
+ * 'img' will indicate that the canvas contains an image, with a possible title or a description. For other
+ * possible roles, see http://www.w3.org/TR/wai-aria/roles
+ *
+ * @param $role string The aria role to set
+ */
+ public function setAriaRole($role)
+ {
+ $this->ariaRole = $role;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Circle.php b/library/Icinga/Chart/Primitive/Circle.php
new file mode 100644
index 0000000..2cb2b72
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Circle.php
@@ -0,0 +1,68 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable for svg circles
+ */
+class Circle extends Styleable implements Drawable
+{
+ /**
+ * The circles x position
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The circles y position
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The circles radius
+ *
+ * @var int
+ */
+ private $radius;
+
+ /**
+ * Construct the circle
+ *
+ * @param int $x The x position of the circle
+ * @param int $y The y position of the circle
+ * @param int $radius The radius of the circle
+ */
+ public function __construct($x, $y, $radius)
+ {
+ $this->x = $x;
+ $this->y = $y;
+ $this->radius = $radius;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $coords = $ctx->toAbsolute($this->x, $this->y);
+ $circle = $ctx->getDocument()->createElement('circle');
+ $circle->setAttribute('cx', Format::formatSVGNumber($coords[0]));
+ $circle->setAttribute('cy', Format::formatSVGNumber($coords[1]));
+ $circle->setAttribute('r', $this->radius);
+ $circle->setAttribute('style', $this->getStyle());
+ $this->applyAttributes($circle);
+ return $circle;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Drawable.php b/library/Icinga/Chart/Primitive/Drawable.php
new file mode 100644
index 0000000..5b4355c
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Drawable.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Drawable element for creating svg out of components
+ */
+interface Drawable
+{
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx);
+}
diff --git a/library/Icinga/Chart/Primitive/Line.php b/library/Icinga/Chart/Primitive/Line.php
new file mode 100644
index 0000000..5916bb5
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Line.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable for the svg line element
+ */
+class Line extends Styleable implements Drawable
+{
+
+ /**
+ * The default stroke width
+ *
+ * @var int
+ */
+ public $strokeWidth = 1;
+
+ /**
+ * The line's start x coordinate
+ *
+ * @var int
+ */
+ private $xStart = 0;
+
+ /**
+ * The line's end x coordinate
+ *
+ * @var int
+ */
+ private $xEnd = 0;
+
+ /**
+ * The line's start y coordinate
+ *
+ * @var int
+ */
+ private $yStart = 0;
+
+ /**
+ * The line's end y coordinate
+ *
+ * @var int
+ */
+ private $yEnd = 0;
+
+ /**
+ * Create a line object starting at the first coordinate and ending at the second one
+ *
+ * @param int $x1 The line's start x coordinate
+ * @param int $y1 The line's start y coordinate
+ * @param int $x2 The line's end x coordinate
+ * @param int $y2 The line's end y coordinate
+ */
+ public function __construct($x1, $y1, $x2, $y2)
+ {
+ $this->xStart = $x1;
+ $this->xEnd = $x2;
+ $this->yStart = $y1;
+ $this->yEnd = $y2;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ list($x1, $y1) = $ctx->toAbsolute($this->xStart, $this->yStart);
+ list($x2, $y2) = $ctx->toAbsolute($this->xEnd, $this->yEnd);
+ $line = $doc->createElement('line');
+ $line->setAttribute('x1', Format::formatSVGNumber($x1));
+ $line->setAttribute('x2', Format::formatSVGNumber($x2));
+ $line->setAttribute('y1', Format::formatSVGNumber($y1));
+ $line->setAttribute('y2', Format::formatSVGNumber($y2));
+ $line->setAttribute('style', $this->getStyle());
+ $this->applyAttributes($line);
+ return $line;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Path.php b/library/Icinga/Chart/Primitive/Path.php
new file mode 100644
index 0000000..2fa793a
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Path.php
@@ -0,0 +1,174 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable for creating a svg path element
+ */
+class Path extends Styleable implements Drawable
+{
+ /**
+ * Syntax template for moving
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
+ */
+ const TPL_MOVE = 'M %s %s ';
+
+ /**
+ * Syntax template for bezier curve
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
+ */
+ const TPL_BEZIER = 'S %s %s ';
+
+ /**
+ * Syntax template for straight lines
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
+ */
+ const TPL_STRAIGHT = 'L %s %s ';
+
+ /**
+ * The default stroke width
+ *
+ * @var int
+ */
+ public $strokeWidth = 1;
+
+ /**
+ * True to treat coordinates as absolute values
+ *
+ * @var bool
+ */
+ protected $isAbsolute = false;
+
+ /**
+ * The points to draw, in the order they are drawn
+ *
+ * @var array
+ */
+ protected $points = array();
+
+ /**
+ * True to draw the path discrete, i.e. make hard steps between points
+ *
+ * @var bool
+ */
+ protected $discrete = false;
+
+ /**
+ * Create the path using the given points
+ *
+ * @param array $points Either a single [x, y] point or an array of x, y points
+ */
+ public function __construct(array $points)
+ {
+ $this->append($points);
+ }
+
+ /**
+ * Append a single point or an array of points to this path
+ *
+ * @param array $points Either a single [x, y] point or an array of x, y points
+ *
+ * @return $this Fluid interface
+ */
+ public function append(array $points)
+ {
+ if (count($points) === 0) {
+ return $this;
+ }
+ if (!is_array($points[0])) {
+ $points = array($points);
+ }
+ $this->points = array_merge($this->points, $points);
+ return $this;
+ }
+
+ /**
+ * Prepend a single point or an array of points to this path
+ *
+ * @param array $points Either a single [x, y] point or an array of x, y points
+ *
+ * @return $this Fluid interface
+ */
+ public function prepend(array $points)
+ {
+ if (count($points) === 0) {
+ return $this;
+ }
+ if (!is_array($points[0])) {
+ $points = array($points);
+ }
+ $this->points = array_merge($points, $this->points);
+ return $this;
+ }
+
+ /**
+ * Set this path to be discrete
+ *
+ * @param boolean $bool True to draw discrete or false to draw straight lines between points
+ *
+ * @return $this Fluid interface
+ */
+ public function setDiscrete($bool)
+ {
+ $this->discrete = $bool;
+ return $this;
+ }
+
+ /**
+ * Mark this path as containing absolute coordinates
+ *
+ * @return $this Fluid interface
+ */
+ public function toAbsolute()
+ {
+ $this->isAbsolute = true;
+ return $this;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $group = $doc->createElement('g');
+
+ $pathDescription = '';
+ $tpl = self::TPL_MOVE;
+ $lastPoint = null;
+ foreach ($this->points as $point) {
+ if (!$this->isAbsolute) {
+ $point = $ctx->toAbsolute($point[0], $point[1]);
+ }
+ $point[0] = Format::formatSVGNumber($point[0]);
+ $point[1] = Format::formatSVGNumber($point[1]);
+ if ($lastPoint && $this->discrete) {
+ $pathDescription .= sprintf($tpl, $point[0], $lastPoint[1]);
+ }
+ $pathDescription .= vsprintf($tpl, $point);
+ $lastPoint = $point;
+ $tpl = self::TPL_STRAIGHT;
+ }
+
+ $path = $doc->createElement('path');
+ if ($this->id) {
+ $path->setAttribute('id', $this->id);
+ }
+ $path->setAttribute('d', $pathDescription);
+ $path->setAttribute('style', $this->getStyle());
+ $this->applyAttributes($path);
+ $group->appendChild($path);
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/PieSlice.php b/library/Icinga/Chart/Primitive/PieSlice.php
new file mode 100644
index 0000000..f5a0ce9
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/PieSlice.php
@@ -0,0 +1,293 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Component for drawing a pie slice
+ */
+class PieSlice extends Animatable implements Drawable
+{
+ /**
+ * The radius of this pieslice relative to the canvas
+ *
+ * @var int
+ */
+ private $radius = 50;
+
+ /**
+ * The start radian of the pie slice
+ *
+ * @var float
+ */
+ private $startRadian = 0;
+
+ /**
+ * The end radian of the pie slice
+ *
+ * @var float
+ */
+ private $endRadian = 0;
+
+ /**
+ * The x position of the pie slice's center
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position of the pie slice's center
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The caption of the pie slice, empty string means no caption
+ *
+ * @var string
+ */
+ private $caption = "";
+
+ /**
+ * The offset of the caption, shifting the indicator from the center of the pie slice
+ *
+ * This is required for nested pie slices.
+ *
+ * @var int
+ */
+ private $captionOffset = 0;
+
+ /**
+ * The minimum radius the label must respect
+ *
+ * @var int
+ */
+ private $outerCaptionBound = 0;
+
+ /**
+ * An optional group element to add labels to when rendering
+ *
+ * @var DOMElement
+ */
+ private $labelGroup;
+
+ /**
+ * Create a pie slice
+ *
+ * @param int $radius The radius of the slice
+ * @param int $percent The percentage the slice represents
+ * @param int $percentStart The percentage where this slice starts
+ */
+ public function __construct($radius, $percent, $percentStart = 0)
+ {
+ $this->x = $this->y = $this->radius = $radius;
+
+ $this->startRadian = M_PI * $percentStart/50;
+ $this->endRadian = M_PI * ($percent + $percentStart)/50;
+ }
+
+ /**
+ * Create the path for the pie slice
+ *
+ * @param int $x The x position of the pie slice
+ * @param int $y The y position of the pie slice
+ * @param int $r The absolute radius of the pie slice
+ *
+ * @return string A SVG path string
+ */
+ private function getPieSlicePath($x, $y, $r)
+ {
+ // The coordinate system is mirrored on the Y axis, so we have to flip cos and sin
+ $xStart = $x + ($r * sin($this->startRadian));
+ $yStart = $y - ($r * cos($this->startRadian));
+
+ if ($this->endRadian - $this->startRadian == 2*M_PI) {
+ // To draw a full circle, adjust arc endpoint by a small (unvisible) value
+ $this->endRadian -= 0.001;
+ $pathString = 'M ' . Format::formatSVGNumber($xStart) . ' ' . Format::formatSVGNumber($yStart);
+ } else {
+ // Start at the center of the pieslice
+ $pathString = 'M ' . $x . ' ' . $y;
+ // Draw a straight line to the upper part of the arc
+ $pathString .= ' L ' . Format::formatSVGNumber($xStart) . ' ' . Format::formatSVGNumber($yStart);
+ }
+
+ // Instead of directly connecting the upper part of the arc (leaving a triangle), draw a bow with the radius
+ $pathString .= ' A ' . Format::formatSVGNumber($r) . ' ' . Format::formatSVGNumber($r);
+ // These are the flags for the bow, see the SVG path documentation for details
+ // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
+ $pathString .= ' 0 ' . (($this->endRadian - $this->startRadian > M_PI) ? '1' : '0 ') . ' 1';
+
+ // xEnd and yEnd are the lower point of the arc
+ $xEnd = $x + ($r * sin($this->endRadian));
+ $yEnd = $y - ($r * cos($this->endRadian));
+ $pathString .= ' ' . Format::formatSVGNumber($xEnd) . ' ' . Format::formatSVGNumber($yEnd);
+
+ return $pathString;
+ }
+
+ /**
+ * Draw the label handler and the text for this pie slice
+ *
+ * @param RenderContext $ctx The rendering context to use for coordinate translation
+ * @param int $r The radius of the pie in absolute coordinates
+ *
+ * @return DOMElement The group DOMElement containing the handle and label
+ */
+ private function drawDescriptionLabel(RenderContext $ctx, $r)
+ {
+ $group = $ctx->getDocument()->createElement('g');
+ $rOuter = ($ctx->xToAbsolute($this->outerCaptionBound) + $ctx->yToAbsolute($this->outerCaptionBound)) / 2;
+ $addOffset = $rOuter - $r ;
+ if ($addOffset < 0) {
+ $addOffset = 0;
+ }
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+ $midRadius = $this->startRadian + ($this->endRadian - $this->startRadian) / 2;
+ list($offsetX, $offsetY) = $ctx->toAbsolute($this->captionOffset, $this->captionOffset);
+
+ $midX = $x + intval(($offsetX + $r)/2 * sin($midRadius));
+ $midY = $y - intval(($offsetY + $r)/2 * cos($midRadius));
+
+ // Draw the handle
+ $path = new Path(array($midX, $midY));
+
+ $midX += ($addOffset + $r/3) * ($midRadius > M_PI ? -1 : 1);
+ $path->append(array($midX, $midY))->toAbsolute();
+
+ $midX += intval($r/2 * sin(M_PI/9)) * ($midRadius > M_PI ? -1 : 1);
+ $midY -= intval($r/2 * cos(M_PI/3)) * ($midRadius < M_PI*1.4 && $midRadius > M_PI/3 ? -1 : 1);
+
+ if ($ctx->ytoRelative($midY) > 100) {
+ $midY = $ctx->yToAbsolute(100);
+ } elseif ($ctx->ytoRelative($midY) < 0) {
+ $midY = $ctx->yToAbsolute($ctx->ytoRelative(100+$midY));
+ }
+
+ $path->append(array($midX , $midY));
+ $rel = $ctx->toRelative($midX, $midY);
+
+ // Draw the text box
+ $text = new Text($rel[0]+1.5, $rel[1], $this->caption);
+ $text->setFontSize('5em');
+ $text->setAlignment(($midRadius > M_PI ? Text::ALIGN_END : Text::ALIGN_START));
+
+ $group->appendChild($path->toSvg($ctx));
+ $group->appendChild($text->toSvg($ctx));
+
+ return $group;
+ }
+
+ /**
+ * Set the x position of the pie slice
+ *
+ * @param int $x The new x position
+ *
+ * @return $this Fluid interface
+ */
+ public function setX($x)
+ {
+ $this->x = $x;
+ return $this;
+ }
+
+ /**
+ * Set the y position of the pie slice
+ *
+ * @param int $y The new y position
+ *
+ * @return $this Fluid interface
+ */
+ public function setY($y)
+ {
+ $this->y = $y;
+ return $this;
+ }
+
+ /**
+ * Set a root element to be used for drawing labels
+ *
+ * @param DOMElement $group The label group
+ *
+ * @return $this Fluid interface
+ */
+ public function setLabelGroup(DOMElement $group)
+ {
+ $this->labelGroup = $group;
+ return $this;
+ }
+
+ /**
+ * Set the caption for this label
+ *
+ * @param string $caption The caption for this element
+ *
+ * @return $this Fluid interface
+ */
+ public function setCaption($caption)
+ {
+ $this->caption = $caption;
+ return $this;
+ }
+
+ /**
+ * Set the internal offset of the caption handle
+ *
+ * @param int $offset The offset for the caption handle
+ *
+ * @return $this Fluid interface
+ */
+ public function setCaptionOffset($offset)
+ {
+ $this->captionOffset = $offset;
+ return $this;
+ }
+
+ /**
+ * Set the minimum radius to be used for drawing labels
+ *
+ * @param int $bound The offset for the caption text
+ *
+ * @return $this Fluid interface
+ */
+ public function setOuterCaptionBound($bound)
+ {
+ $this->outerCaptionBound = $bound;
+ return $this;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $group = $doc->createElement('g');
+ $r = ($ctx->xToAbsolute($this->radius) + $ctx->yToAbsolute($this->radius)) / 2;
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+
+ $slicePath = $doc->createElement('path');
+
+ $slicePath->setAttribute('d', $this->getPieSlicePath($x, $y, $r));
+ $slicePath->setAttribute('style', $this->getStyle());
+ $slicePath->setAttribute('data-icinga-graph-type', 'pieslice');
+
+ $this->applyAttributes($slicePath);
+ $group->appendChild($slicePath);
+ if ($this->caption != "") {
+ $lblGroup = ($this->labelGroup ? $this->labelGroup : $group);
+ $lblGroup->appendChild($this->drawDescriptionLabel($ctx, $r));
+ }
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/RawElement.php b/library/Icinga/Chart/Primitive/RawElement.php
new file mode 100644
index 0000000..721b6e0
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/RawElement.php
@@ -0,0 +1,43 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Wrapper for raw elements to be added as Drawable's
+ */
+class RawElement implements Drawable
+{
+
+ /**
+ * The DOMElement wrapped by this Drawable
+ *
+ * @var DOMElement
+ */
+ private $domEl;
+
+ /**
+ * Create this RawElement
+ *
+ * @param DOMElement $el The element to wrap here
+ */
+ public function __construct(DOMElement $el)
+ {
+ $this->domEl = $el;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ return $this->domEl;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Rect.php b/library/Icinga/Chart/Primitive/Rect.php
new file mode 100644
index 0000000..0145e07
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Rect.php
@@ -0,0 +1,105 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DomElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable representing the SVG rect element
+ */
+class Rect extends Animatable implements Drawable
+{
+ /**
+ * The x position
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The width of this rect
+ *
+ * @var int
+ */
+ private $width;
+
+ /**
+ * The height of this rect
+ *
+ * @var int
+ */
+ private $height;
+
+ /**
+ * Whether to keep the ratio
+ *
+ * @var bool
+ */
+ private $keepRatio = false;
+
+ /**
+ * Create this rect
+ *
+ * @param int $x The x position of the rect
+ * @param int $y The y position of the rectangle
+ * @param int $width The width of the rectangle
+ * @param int $height The height of the rectangle
+ */
+ public function __construct($x, $y, $width, $height)
+ {
+ $this->x = $x;
+ $this->y = $y;
+ $this->width = $width;
+ $this->height = $height;
+ }
+
+ /**
+ * Call to let the rectangle keep the ratio
+ */
+ public function keepRatio()
+ {
+ $this->keepRatio = true;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $rect = $doc->createElement('rect');
+
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+ if ($this->keepRatio) {
+ $ctx->keepRatio();
+ }
+ list($width, $height) = $ctx->toAbsolute($this->width, $this->height);
+ if ($this->keepRatio) {
+ $ctx->ignoreRatio();
+ }
+ $rect->setAttribute('x', Format::formatSVGNumber($x));
+ $rect->setAttribute('y', Format::formatSVGNumber($y));
+ $rect->setAttribute('width', Format::formatSVGNumber($width));
+ $rect->setAttribute('height', Format::formatSVGNumber($height));
+ $rect->setAttribute('style', $this->getStyle());
+
+ $this->applyAttributes($rect);
+ $this->appendAnimation($rect, $ctx);
+
+ return $rect;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Styleable.php b/library/Icinga/Chart/Primitive/Styleable.php
new file mode 100644
index 0000000..678b940
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Styleable.php
@@ -0,0 +1,154 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+
+/**
+ * Base class for stylable drawables
+ */
+class Styleable
+{
+
+ /**
+ * The stroke width to use
+ *
+ * @var int
+ */
+ public $strokeWidth = 0;
+
+ /**
+ * The stroke color to use
+ *
+ * @var string
+ */
+ public $strokeColor = '#000';
+
+ /**
+ * The fill color to use
+ *
+ * @var string
+ */
+ public $fill = 'none';
+
+ /**
+ * Additional styles to be appended to the style attribute
+ *
+ * @var string
+ */
+ public $additionalStyle = '';
+
+ /**
+ * The id of this element
+ *
+ * @var string
+ */
+ public $id = null;
+
+ /**
+ * Additional attributes to be set
+ *
+ * @var array
+ */
+ public $attributes = array();
+
+ /**
+ * Set the stroke width for this drawable
+ *
+ * @param string $width The stroke with with unit
+ *
+ * @return $this Fluid interface
+ */
+ public function setStrokeWidth($width)
+ {
+ $this->strokeWidth = $width;
+ return $this;
+ }
+
+ /**
+ * Set the color for the stroke or none for no stroke
+ *
+ * @param string $color The color to set for the stroke
+ *
+ * @return $this Fluid interface
+ */
+ public function setStrokeColor($color)
+ {
+ $this->strokeColor = $color ? $color : 'none';
+ return $this;
+ }
+
+ /**
+ * Set additional styles for this drawable
+ *
+ * @param string $styles The styles to set additionally
+ *
+ * @return $this Fluid interface
+ */
+ public function setAdditionalStyle($styles)
+ {
+ $this->additionalStyle = $styles;
+ return $this;
+ }
+
+ /**
+ * Set the fill for this styleable
+ *
+ * @param string $color The color to use for filling or null to use no fill
+ *
+ * @return $this Fluid interface
+ */
+ public function setFill($color = null)
+ {
+ $this->fill = $color ? $color : 'none';
+ return $this;
+ }
+
+ /**
+ * Set the id for this element
+ *
+ * @param string $id The id to set for this element
+ *
+ * @return $this Fluid interface
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+
+ return $this;
+ }
+
+ /**
+ * Return the content of the style attribute as a string
+ *
+ * @return string A string containing styles
+ */
+ public function getStyle()
+ {
+ $base = sprintf("fill: %s; stroke: %s;stroke-width: %s;", $this->fill, $this->strokeColor, $this->strokeWidth);
+ $base .= ';' . $this->additionalStyle . ';';
+ return $base;
+ }
+
+ /**
+ * Add an additional attribute to this element
+ */
+ public function setAttribute($key, $value)
+ {
+ $this->attributes[$key] = $value;
+ }
+
+ /**
+ * Apply attribute to a DOMElement
+ *
+ * @param DOMElement $el Element to apply attributes
+ */
+ protected function applyAttributes(DOMElement $el)
+ {
+ foreach ($this->attributes as $name => $value) {
+ $el->setAttribute($name, $value);
+ }
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Text.php b/library/Icinga/Chart/Primitive/Text.php
new file mode 100644
index 0000000..e647f40
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Text.php
@@ -0,0 +1,168 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use DOMText;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Wrapper for the SVG text element
+ */
+class Text extends Styleable implements Drawable
+{
+ /**
+ * Align the text to end at the x and y position
+ */
+ const ALIGN_END = 'end';
+
+ /**
+ * Align the text to start at the x and y position
+ */
+ const ALIGN_START = 'start';
+
+ /**
+ * Align the text to be centered at the x and y position
+ */
+ const ALIGN_MIDDLE = 'middle';
+
+ /**
+ * The x position of the Text
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position of the Text
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The text content
+ *
+ * @var string
+ */
+ private $text;
+
+ /**
+ * The size of the font
+ *
+ * @var string
+ */
+ private $fontSize = '1.5em';
+
+ /**
+ * The weight of the font
+ *
+ * @var string
+ */
+ private $fontWeight = 'normal';
+
+ /**
+ * The default fill color
+ *
+ * @var string
+ */
+ public $fill = '#000';
+
+ /**
+ * The alignment of the text
+ *
+ * @var string
+ */
+ private $alignment = self::ALIGN_START;
+
+ /**
+ * Set the font-stretch property of the text
+ */
+ private $fontStretch = 'semi-condensed';
+
+ /**
+ * Construct a new text drawable
+ *
+ * @param int $x The x position of the text
+ * @param int $y The y position of the text
+ * @param string $text The text this component should contain
+ * @param string $fontSize The font size of the text
+ */
+ public function __construct($x, $y, $text, $fontSize = '1.5em')
+ {
+ $this->x = $x;
+ $this->y = $y;
+ $this->text = $text;
+ $this->fontSize = $fontSize;
+ }
+
+ /**
+ * Set the font size of the svg text element
+ *
+ * @param string $size The font size including a unit
+ *
+ * @return $this Fluid interface
+ */
+ public function setFontSize($size)
+ {
+ $this->fontSize = $size;
+ return $this;
+ }
+
+ /**
+ * Set the text alignment with one of the ALIGN_* constants
+ *
+ * @param String $align Value how to align
+ *
+ * @return $this Fluid interface
+ */
+ public function setAlignment($align)
+ {
+ $this->alignment = $align;
+ return $this;
+ }
+
+ /**
+ * Set the weight of the current font
+ *
+ * @param string $weight The weight of the string
+ *
+ * @return $this Fluid interface
+ */
+ public function setFontWeight($weight)
+ {
+ $this->fontWeight = $weight;
+ return $this;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+ $text = $ctx->getDocument()->createElement('text');
+ $text->setAttribute('x', Format::formatSVGNumber($x - 15));
+ $text->setAttribute(
+ 'style',
+ $this->getStyle()
+ . ';font-size:' . $this->fontSize
+ . '; font-family: Ubuntu, Calibri, Trebuchet MS, Helvetica, Verdana, sans-serif'
+ . ';font-weight: ' . $this->fontWeight
+ . ';font-stretch: ' . $this->fontStretch
+ . '; font-style: normal;'
+ . 'text-anchor: ' . $this->alignment
+ );
+
+ $text->setAttribute('y', Format::formatSVGNumber($y));
+ $text->appendChild(new DOMText($this->text));
+ return $text;
+ }
+}
diff --git a/library/Icinga/Chart/Render/LayoutBox.php b/library/Icinga/Chart/Render/LayoutBox.php
new file mode 100644
index 0000000..fa49461
--- /dev/null
+++ b/library/Icinga/Chart/Render/LayoutBox.php
@@ -0,0 +1,200 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Render;
+
+use Icinga\Chart\Format;
+
+/**
+ * Layout class encapsulating size, padding and margin information
+ */
+class LayoutBox
+{
+ /**
+ * Padding index for top padding
+ */
+ const PADDING_TOP = 0;
+
+ /**
+ * Padding index for right padding
+ */
+ const PADDING_RIGHT = 1;
+
+ /**
+ * Padding index for bottom padding
+ */
+ const PADDING_BOTTOM = 2;
+
+ /**
+ * Padding index for left padding
+ */
+ const PADDING_LEFT = 3;
+
+ /**
+ * The height of this layout element
+ *
+ * @var int
+ */
+ private $height;
+
+ /**
+ * The width of this layout element
+ *
+ * @var int
+ */
+ private $width;
+
+ /**
+ * The x position of this layout
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position of this layout
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The padding of this layout
+ *
+ * @var array
+ */
+ private $padding = array(0, 0, 0, 0);
+
+ /**
+ * Create this layout box
+ *
+ * Note that x, y, width and height are relative: x with 0 means leftmost, x with 100 means rightmost
+ *
+ * @param int $x The relative x coordinate
+ * @param int $y The relative y coordinate
+ * @param int $width The optional, relative width
+ * @param int $height The optional, relative height
+ */
+ public function __construct($x, $y, $width = null, $height = null)
+ {
+ $this->height = $height ? $height : 100;
+ $this->width = $width ? $width : 100;
+ $this->x = $x;
+ $this->y = $y;
+ }
+
+ /**
+ * Set a padding to all four sides uniformly
+ *
+ * @param int $padding The padding to set for all four sides
+ */
+ public function setUniformPadding($padding)
+ {
+ $this->padding = array($padding, $padding, $padding, $padding);
+ }
+
+ /**
+ * Set the padding for this LayoutBox
+ *
+ * @param int $top The top side padding
+ * @param int $right The right side padding
+ * @param int $bottom The bottom side padding
+ * @param int $left The left side padding
+ */
+ public function setPadding($top, $right, $bottom, $left)
+ {
+ $this->padding = array($top, $right, $bottom, $left);
+ }
+
+ /**
+ * Return a string containing the SVG transform attribute values for the padding
+ *
+ * @param RenderContext $ctx The context to determine the translation coordinates
+ *
+ * @return string The transformation string
+ */
+ public function getInnerTransform(RenderContext $ctx)
+ {
+ list($translateX, $translateY) = $ctx->toAbsolute(
+ $this->padding[self::PADDING_LEFT] + $this->getX(),
+ $this->padding[self::PADDING_TOP] + $this->getY()
+ );
+ list($scaleX, $scaleY) = $ctx->paddingToScaleFactor($this->padding);
+
+ $scaleX *= $this->getWidth()/100;
+ $scaleY *= $this->getHeight()/100;
+ return sprintf(
+ 'translate(%s, %s) scale(%s, %s)',
+ Format::formatSVGNumber($translateX),
+ Format::formatSVGNumber($translateY),
+ Format::formatSVGNumber($scaleX),
+ Format::formatSVGNumber($scaleY)
+ );
+ }
+
+ /**
+ * String representation for this Layout, for debug purposes
+ *
+ * @return string A string containing the bounds of this LayoutBox
+ */
+ public function __toString()
+ {
+ return sprintf(
+ 'Rectangle: x: %s y: %s, height: %s, width: %s',
+ $this->x,
+ $this->y,
+ $this->height,
+ $this->width
+ );
+ }
+
+ /**
+ * Return a four element array with the padding
+ *
+ * @return array The padding of this LayoutBox
+ */
+ public function getPadding()
+ {
+ return $this->padding;
+ }
+
+ /**
+ * Return the height of this LayoutBox
+ *
+ * @return int The height of this box
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Return the width of this LayoutBox
+ *
+ * @return int The width of this box
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Return the x position of this LayoutBox
+ *
+ * @return int The x position of this box
+ */
+ public function getX()
+ {
+ return $this->x;
+ }
+
+ /**
+ * Return the y position of this LayoutBox
+ *
+ * @return int The y position of this box
+ */
+ public function getY()
+ {
+ return $this->y;
+ }
+}
diff --git a/library/Icinga/Chart/Render/RenderContext.php b/library/Icinga/Chart/Render/RenderContext.php
new file mode 100644
index 0000000..457fbf3
--- /dev/null
+++ b/library/Icinga/Chart/Render/RenderContext.php
@@ -0,0 +1,225 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Render;
+
+use DOMDocument;
+
+/**
+ * Context for rendering, handles ratio based coordinate calculations.
+ *
+ * The most important functions when rendering are the toAbsolute and roRelative
+ * values, taking world coordinates and translating them into local coordinates.
+ */
+class RenderContext
+{
+
+ /**
+ * The base size of the viewport, i.e. how many units are available on a 1:1 ratio
+ *
+ * @var array
+ */
+ private $viewBoxSize = array(1000, 1000);
+
+
+ /**
+ * The DOMDocument for modifying the elements
+ *
+ * @var DOMDocument
+ */
+ private $document;
+
+ /**
+ * If true no ratio correction will be made
+ *
+ * @var bool
+ */
+ private $respectRatio = false;
+
+ /**
+ * The ratio on the x side. A x ration of 2 means that the width of the SVG is divided in 2000
+ * units (see $viewBox)
+ *
+ * @var int
+ */
+ private $xratio = 1;
+
+ /**
+ * The ratio on the y side. A y ration of 2 means that the height of the SVG is divided in 2000
+ * units (see $viewBox)
+ *
+ * @var int
+ */
+ private $yratio = 1;
+
+ /**
+ * Creates a new context for the given DOM Document
+ *
+ * @param DOMDocument $document The DOM document represented by this context
+ * @param int $width The width (may be approximate) of the document
+ * (only required for ratio calculation)
+ * @param int $height The height (may be approximate) of the document
+ * (only required for ratio calculation)
+ */
+ public function __construct(DOMDocument $document, $width, $height)
+ {
+ $this->document = $document;
+ if ($width > $height) {
+ $this->xratio = $width / $height;
+ } elseif ($height > $width) {
+ $this->yratio = $height / $width;
+ }
+ }
+
+ /**
+ * Return the document represented by this Rendering context
+ *
+ * @return DOMDocument The DOMDocument for creating files
+ */
+ public function getDocument()
+ {
+ return $this->document;
+ }
+
+ /**
+ * Let successive toAbsolute operations ignore ratio correction
+ *
+ * This can be called to avoid distortion on certain elements like rectangles.
+ */
+ public function keepRatio()
+ {
+ $this->respectRatio = true;
+ }
+
+ /**
+ * Let successive toAbsolute operations perform ratio correction
+ *
+ * This will cause distortion on certain elements like rectangles.
+ */
+ public function ignoreRatio()
+ {
+ $this->respectRatio = false;
+ }
+
+ /**
+ * Return how many unit s are available in the Y axis
+ *
+ * @return int The number of units available on the y axis
+ */
+ public function getNrOfUnitsY()
+ {
+ return intval($this->viewBoxSize[1] * $this->yratio);
+ }
+
+ /**
+ * Return how many unit s are available in the X axis
+ *
+ * @return int The number of units available on the x axis
+ */
+ public function getNrOfUnitsX()
+ {
+ return intval($this->viewBoxSize[0] * $this->xratio);
+ }
+
+ /**
+ * Transforms the x,y coordinate from relative coordinates to absolute world coordinates
+ *
+ * (50, 50) would be a point in the middle of the document and map to 500, 1000 on a
+ * 1000 x 1000 viewbox with a 1:2 ratio.
+ *
+ * @param int $x The relative x coordinate
+ * @param int $y The relative y coordinate
+ *
+ * @return array An x,y tuple containing absolute coordinates
+ * @see RenderContext::toRelative
+ */
+ public function toAbsolute($x, $y)
+ {
+ return array($this->xToAbsolute($x), $this->yToAbsolute($y));
+ }
+
+ /**
+ * Transforms the x,y coordinate from absolute coordinates to relative world coordinates
+ *
+ * This is the inverse function of toAbsolute
+ *
+ * @param int $x The absolute x coordinate
+ * @param int $y The absolute y coordinate
+ *
+ * @return array An x,y tupel containing absolute coordinates
+ * @see RenderContext::toAbsolute
+ */
+ public function toRelative($x, $y)
+ {
+ return array($this->xToRelative($x), $this->yToRelative($y));
+ }
+
+ /**
+ * Calculates the scale transformation required to apply the padding on an Canvas
+ *
+ * @param array $padding A 4 element array containing top, right, bottom and left padding
+ *
+ * @return array An array containing the x and y scale
+ */
+ public function paddingToScaleFactor(array $padding)
+ {
+ list($horizontalPadding, $verticalPadding) = $this->toAbsolute(
+ $padding[LayoutBox::PADDING_RIGHT] + $padding[LayoutBox::PADDING_LEFT],
+ $padding[LayoutBox::PADDING_TOP] + $padding[LayoutBox::PADDING_BOTTOM]
+ );
+
+ return array(
+ ($this->getNrOfUnitsX() - $horizontalPadding) / $this->getNrOfUnitsX(),
+ ($this->getNrOfUnitsY() - $verticalPadding) / $this->getNrOfUnitsY()
+ );
+ }
+
+ /**
+ * Transform a relative x coordinate to an absolute one
+ *
+ * @param int $x A relative x coordinate
+ *
+ * @return int An absolute x coordinate
+ **/
+ public function xToAbsolute($x)
+ {
+ return $this->getNrOfUnitsX() / 100 * $x / ($this->respectRatio ? $this->xratio : 1);
+ }
+
+ /**
+ * Transform a relative y coordinate to an absolute one
+ *
+ * @param int $y A relative y coordinate
+ *
+ * @return int An absolute y coordinate
+ */
+ public function yToAbsolute($y)
+ {
+ return $this->getNrOfUnitsY() / 100 * $y / ($this->respectRatio ? $this->yratio : 1);
+ }
+
+ /**
+ * Transform a absolute x coordinate to an relative one
+ *
+ * @param int $x An absolute x coordinate
+ *
+ * @return int A relative x coordinate
+ */
+ public function xToRelative($x)
+ {
+ return $x / $this->getNrOfUnitsX() * 100 * ($this->respectRatio ? $this->xratio : 1);
+ }
+
+ /**
+ * Transform a absolute y coordinate to an relative one
+ *
+ * @param int $y An absolute x coordinate
+ *
+ * @return int A relative x coordinate
+ */
+ public function yToRelative($y)
+ {
+ return $y / $this->getNrOfUnitsY() * 100 * ($this->respectRatio ? $this->yratio : 1);
+ }
+}
diff --git a/library/Icinga/Chart/Render/Rotator.php b/library/Icinga/Chart/Render/Rotator.php
new file mode 100644
index 0000000..3e7071c
--- /dev/null
+++ b/library/Icinga/Chart/Render/Rotator.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Render;
+
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Primitive\Drawable;
+use DOMElement;
+
+/**
+ * Class Rotator
+ * @package Icinga\Chart\Render
+ */
+class Rotator implements Drawable
+{
+ /**
+ * The drawable element to rotate
+ *
+ * @var Drawable
+ */
+ private $element;
+
+ /**
+ * @var int
+ */
+ private $degrees;
+
+ /**
+ * Wrap an element into a new instance of Rotator
+ *
+ * @param Drawable $element The element to rotate
+ * @param int $degrees The amount of degrees
+ */
+ public function __construct(Drawable $element, $degrees)
+ {
+ $this->element = $element;
+ $this->degrees = $degrees;
+ }
+
+ /**
+ * Rotate the given element.
+ *
+ * @param RenderContext $ctx The rendering context
+ * @param DOMElement $el The element to rotate
+ * @param $degrees The amount of degrees
+ *
+ * @return DOMElement The rotated DOMElement
+ */
+ private function rotate(RenderContext $ctx, DOMElement $el, $degrees)
+ {
+ // Create a box containing the rotated element relative to the original element position
+ $container = $ctx->getDocument()->createElement('g');
+ $x = $el->getAttribute('x');
+ $y = $el->getAttribute('y');
+ $container->setAttribute('transform', 'translate(' . $x . ',' . $y . ')');
+ $el->removeAttribute('x');
+ $el->removeAttribute('y');
+
+ // Put the element into a rotated group
+ //$rotate = $ctx->getDocument()->createElement('g');
+ $el->setAttribute('transform', 'rotate(' . $degrees . ')');
+ //$rotate->appendChild($el);
+
+ $container->appendChild($el);
+ return $container;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $el = $this->element->toSvg($ctx);
+ return $this->rotate($ctx, $el, $this->degrees);
+ }
+}
diff --git a/library/Icinga/Chart/SVGRenderer.php b/library/Icinga/Chart/SVGRenderer.php
new file mode 100644
index 0000000..d3891f2
--- /dev/null
+++ b/library/Icinga/Chart/SVGRenderer.php
@@ -0,0 +1,331 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use DOMNode;
+use DOMElement;
+use DOMDocument;
+use DOMImplementation;
+use Icinga\Chart\Render\LayoutBox;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Primitive\Canvas;
+
+/**
+ * SVG Renderer component.
+ *
+ * Creates the basic DOM tree of the SVG to use
+ */
+class SVGRenderer
+{
+ const X_ASPECT_RATIO_MIN = 'xMin';
+
+ const X_ASPECT_RATIO_MID = 'xMid';
+
+ const X_ASPECT_RATIO_MAX = 'xMax';
+
+ const Y_ASPECT_RATIO_MIN = 'YMin';
+
+ const Y_ASPECT_RATIO_MID = 'YMid';
+
+ const Y_ASPECT_RATIO_MAX = 'YMax';
+
+ const ASPECT_RATIO_PAD = 'meet';
+
+ const ASPECT_RATIO_CUTOFF = 'slice';
+
+ /**
+ * The XML-document
+ *
+ * @var DOMDocument
+ */
+ private $document;
+
+ /**
+ * The SVG-element
+ *
+ * @var DOMNode
+ */
+ private $svg;
+
+ /**
+ * The description of this SVG, useful for screen readers
+ *
+ * @var string
+ */
+ private $ariaDescription;
+
+ /**
+ * The title of this SVG, useful for screen readers
+ *
+ * @var string
+ */
+ private $ariaTitle;
+
+ /**
+ * The aria role used by this svg element
+ *
+ * @var string
+ */
+ private $ariaRole = 'img';
+
+ /**
+ * The root layer for all elements
+ *
+ * @var Canvas
+ */
+ private $rootCanvas;
+
+ /**
+ * The width of this renderer
+ *
+ * @var int
+ */
+ private $width = 100;
+
+ /**
+ * The height of this renderer
+ *
+ * @var int
+ */
+ private $height = 100;
+
+ /**
+ * Whether the aspect ratio is preversed
+ *
+ * @var bool
+ */
+ private $preserveAspectRatio = false;
+
+ /**
+ * Horizontal alignment of SVG element
+ *
+ * @var string
+ */
+ private $xAspectRatio = self::X_ASPECT_RATIO_MID;
+
+ /**
+ * Vertical alignment of SVG element
+ *
+ * @var string
+ */
+ private $yAspectRatio = self::Y_ASPECT_RATIO_MID;
+
+ /**
+ * Define whether aspect differences should be handled using padding (default) or cutoff
+ *
+ * @var string
+ */
+ private $xFillMode = "meet";
+
+
+ /**
+ * Create the root document and the SVG root node
+ */
+ private function createRootDocument()
+ {
+ $implementation = new DOMImplementation();
+ $docType = $implementation->createDocumentType(
+ 'svg',
+ '-//W3C//DTD SVG 1.1//EN',
+ 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
+ );
+
+ $this->document = $implementation->createDocument(null, '', $docType);
+ $this->svg = $this->createOuterBox();
+ $this->document->appendChild($this->svg);
+ }
+
+ /**
+ * Create the outer SVG box containing the root svg element and namespace and return it
+ *
+ * @return DOMElement The SVG root node
+ */
+ private function createOuterBox()
+ {
+ $ctx = $this->createRenderContext();
+ $svg = $this->document->createElement('svg');
+ $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
+ $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $svg->setAttribute('role', $this->ariaRole);
+ $svg->setAttribute('width', '100%');
+ $svg->setAttribute('height', '100%');
+ $svg->setAttribute(
+ 'viewBox',
+ sprintf(
+ '0 0 %s %s',
+ $ctx->getNrOfUnitsX(),
+ $ctx->getNrOfUnitsY()
+ )
+ );
+ if ($this->preserveAspectRatio) {
+ $svg->setAttribute(
+ 'preserveAspectRatio',
+ sprintf(
+ '%s%s %s',
+ $this->xAspectRatio,
+ $this->yAspectRatio,
+ $this->xFillMode
+ )
+ );
+ }
+ return $svg;
+ }
+
+ /**
+ * Add aria title and description
+ *
+ * Adds an aria title and desc element to the given SVG node, which are used to describe this SVG by accessibility
+ * tools such as screen readers.
+ *
+ * @param DOMNode $svg The SVG DOMNode to which the aria attributes should be attached
+ * @param $title The title text
+ * @param $description The description text
+ */
+ private function addAriaDescription(DOMNode $svg, $titleText, $descriptionText)
+ {
+ $doc = $svg->ownerDocument;
+
+ $titleId = $descId = '';
+ if (isset($this->ariaTitle)) {
+ $titleId = 'aria-title-' . $this->stripNonAlphanumeric($titleText);
+ $title = $doc->createElement('title');
+ $title->setAttribute('id', $titleId);
+
+ $title->appendChild($doc->createTextNode($titleText));
+ $svg->appendChild($title);
+ }
+
+ if (isset($this->ariaDescription)) {
+ $descId = 'aria-desc-' . $this->stripNonAlphanumeric($descriptionText);
+ $desc = $doc->createElement('desc');
+ $desc->setAttribute('id', $descId);
+
+ $desc->appendChild($doc->createTextNode($descriptionText));
+ $svg->appendChild($desc);
+ }
+
+ $svg->setAttribute('aria-labelledby', join(' ', array($titleId, $descId)));
+ }
+
+ /**
+ * Initialises the XML-document, SVG-element and this figure's root canvas
+ *
+ * @param int $width The width ratio
+ * @param int $height The height ratio
+ */
+ public function __construct($width, $height)
+ {
+ $this->width = $width;
+ $this->height = $height;
+ $this->rootCanvas = new Canvas('root', new LayoutBox(0, 0));
+ }
+
+ /**
+ * Render the SVG-document
+ *
+ * @return string The resulting XML structure
+ */
+ public function render()
+ {
+ $this->createRootDocument();
+ $ctx = $this->createRenderContext();
+ $this->addAriaDescription($this->svg, $this->ariaTitle, $this->ariaDescription);
+ $this->svg->appendChild($this->rootCanvas->toSvg($ctx));
+ $this->document->formatOutput = true;
+ $this->document->encoding = 'UTF-8';
+ return $this->document->saveXML();
+ }
+
+ /**
+ * Create a render context that will be used for rendering elements
+ *
+ * @return RenderContext The created RenderContext instance
+ */
+ public function createRenderContext()
+ {
+ return new RenderContext($this->document, $this->width, $this->height);
+ }
+
+ /**
+ * Return the root canvas of this rendered
+ *
+ * @return Canvas The canvas that will be the uppermost element in this figure
+ */
+ public function getCanvas()
+ {
+ return $this->rootCanvas;
+ }
+
+ /**
+ * Preserve the aspect ratio of the rendered object
+ *
+ * Do not deform the content of the SVG when the aspect ratio of the viewBox
+ * differs from the aspect ratio of the SVG element, but add padding or cutoff
+ * instead
+ *
+ * @param bool $preserve Whether the aspect ratio should be preserved
+ */
+ public function preserveAspectRatio($preserve = true)
+ {
+ $this->preserveAspectRatio = $preserve;
+ }
+
+ /**
+ * Change the horizontal alignment of the SVG element
+ *
+ * Change the horizontal alignment of the svg, when preserveAspectRatio is used and
+ * padding is present. Defaults to
+ */
+ public function setXAspectRatioAlignment($alignment)
+ {
+ $this->xAspectRatio = $alignment;
+ }
+
+ /**
+ * Change the vertical alignment of the SVG element
+ *
+ * Change the vertical alignment of the svg, when preserveAspectRatio is used and
+ * padding is present.
+ */
+ public function setYAspectRatioAlignment($alignment)
+ {
+ $this->yAspectRatio = $alignment;
+ }
+
+ /**
+ * Set the aria description, that is used as a title for this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaTitle($text)
+ {
+ $this->ariaTitle = $text;
+ }
+
+ /**
+ * Set the aria description, that is used to describe this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaDescription($text)
+ {
+ $this->ariaDescription = $text;
+ }
+
+ /**
+ * Set the aria role, that is used to describe the purpose of this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaRole($text)
+ {
+ $this->ariaRole = $text;
+ }
+
+
+ private function stripNonAlphanumeric($str)
+ {
+ return preg_replace('/[^A-Za-z]+/', '', $str);
+ }
+}
diff --git a/library/Icinga/Chart/Unit/AxisUnit.php b/library/Icinga/Chart/Unit/AxisUnit.php
new file mode 100644
index 0000000..251787f
--- /dev/null
+++ b/library/Icinga/Chart/Unit/AxisUnit.php
@@ -0,0 +1,56 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Unit;
+
+use Iterator;
+
+/**
+ * Base class for Axis Units
+ *
+ * An AxisUnit takes a set of values and places them on a given range
+ *
+ * Concrete subclasses must implement the iterator interface, with
+ * getCurrent returning the axis relative position and getValue the label
+ * that will be displayed
+ */
+interface AxisUnit extends Iterator
+{
+ /**
+ * Add a dataset to this AxisUnit, required for dynamic min and max vlaues
+ *
+ * @param array $dataset The dataset that will be shown in the Axis
+ * @param int $id The idx in the dataset (0 for x, 1 for y)
+ */
+ public function addValues(array $dataset, $id = 0);
+
+ /**
+ * Transform the given absolute value in an axis relative value
+ *
+ * @param int $value The absolute, dataset dependent value
+ *
+ * @return int An axis relative value
+ */
+ public function transform($value);
+
+ /**
+ * Set the axis minimum value to a fixed value
+ *
+ * @param int $min The new minimum value
+ */
+ public function setMin($min);
+
+ /**
+ * Set the axis maximum value to a fixed value
+ *
+ * @param int $max The new maximum value
+ */
+ public function setMax($max);
+
+ /**
+ * Get the amount of ticks of this axis
+ *
+ * @return int
+ */
+ public function getTicks();
+}
diff --git a/library/Icinga/Chart/Unit/CalendarUnit.php b/library/Icinga/Chart/Unit/CalendarUnit.php
new file mode 100644
index 0000000..7ce6a99
--- /dev/null
+++ b/library/Icinga/Chart/Unit/CalendarUnit.php
@@ -0,0 +1,167 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Unit;
+
+use DateTime;
+
+/**
+ * Calendar Axis Unit that transforms timestamps into user-readable values
+ *
+ */
+class CalendarUnit extends LinearUnit
+{
+ /**
+ * Constant for a minute
+ */
+ const MINUTE = 60;
+
+ /**
+ * Constant for an hour
+ */
+ const HOUR = 3600;
+
+ /**
+ * Constant for a day
+ */
+ const DAY = 864000;
+
+ /**
+ * Constant for ~a month
+ * 30 Days, this is sufficient for our needs
+ */
+ const MONTH = 2592000; // x
+
+ /**
+ * An array containing all labels that will be displayed
+ *
+ * @var array
+ */
+ private $labels = array();
+
+ /**
+ * The date format to use
+ *
+ * @var string
+ */
+ private $dateFormat = 'd-m';
+
+ /**
+ * The time format to use
+ *
+ * @var string
+ */
+ private $timeFormat = 'g:i:s';
+
+ /**
+ * Create the labels for the given dataset
+ */
+ private function createLabels()
+ {
+ $this->labels = array();
+ $duration = $this->getMax() - $this->getMin();
+
+ if ($duration <= self::HOUR) {
+ $unit = self::MINUTE;
+ } elseif ($duration <= self::DAY) {
+ $unit = self::HOUR;
+ } elseif ($duration <= self::MONTH) {
+ $unit = self::DAY;
+ } else {
+ $unit = self::MONTH;
+ }
+ $this->calculateLabels($unit);
+ }
+
+ /**
+ * Calculate the labels for this dataset
+ *
+ * @param integer $unit The unit to use as the basis for calculation
+ */
+ private function calculateLabels($unit)
+ {
+ $fac = new DateTime();
+
+ $duration = $this->getMax() - $this->getMin();
+
+ // Calculate number of ticks, but not more than 30
+ $tickCount = ($duration/$unit * 10);
+ if ($tickCount > 30) {
+ $tickCount = 30;
+ }
+
+ $step = $duration / $tickCount;
+ $format = $this->timeFormat;
+ if ($unit === self::DAY) {
+ $format = $this->dateFormat;
+ } elseif ($unit === self::MONTH) {
+ $format = $this->dateFormat;
+ }
+
+ for ($i = 0; $i <= $duration; $i += $step) {
+ $this->labels[] = $fac->setTimestamp($this->getMin() + $i)->format($format);
+ }
+ }
+
+ /**
+ * Add a dataset to this CalendarUnit and update labels
+ *
+ * @param array $dataset The dataset to update
+ * @param int $idx The index to use for determining the data
+ *
+ * @return $this Fluid interface
+ */
+ public function addValues(array $dataset, $idx = 0)
+ {
+ parent::addValues($dataset, $idx);
+ $this->createLabels();
+ return $this;
+ }
+
+ /**
+ * Return the current axis relative position
+ *
+ * @return int The position of the next tick (between 0 and 100)
+ */
+ public function current()
+ {
+ return 100 * (key($this->labels) / count($this->labels));
+ }
+
+ /**
+ * Move to next tick
+ */
+ public function next()
+ {
+ next($this->labels);
+ }
+
+ /**
+ * Return the current tick caption
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return current($this->labels);
+ }
+
+ /**
+ * Return true when the iterator is in a valid range
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return current($this->labels) !== false;
+ }
+
+ /**
+ * Rewind the internal array
+ */
+ public function rewind()
+ {
+ reset($this->labels);
+ }
+}
diff --git a/library/Icinga/Chart/Unit/LinearUnit.php b/library/Icinga/Chart/Unit/LinearUnit.php
new file mode 100644
index 0000000..ea4792b
--- /dev/null
+++ b/library/Icinga/Chart/Unit/LinearUnit.php
@@ -0,0 +1,227 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Unit;
+
+/**
+ * Linear tick distribution over the axis
+ */
+class LinearUnit implements AxisUnit
+{
+ /**
+ * The minimum value to display
+ *
+ * @var int
+ */
+ protected $min;
+
+ /**
+ * The maximum value to display
+ *
+ * @var int
+ */
+ protected $max;
+
+ /**
+ * True when the minimum value is static and isn't affected by the dataset
+ *
+ * @var bool
+ */
+ protected $staticMin = false;
+
+ /**
+ * True when the maximum value is static and isn't affected by the dataset
+ *
+ * @var bool
+ */
+ protected $staticMax = false;
+
+ /**
+ * The number of ticks to use
+ *
+ * @var int
+ */
+ protected $nrOfTicks = 10;
+
+ /**
+ * The currently displayed tick
+ *
+ * @var int
+ */
+ protected $currentTick = 0;
+
+ /**
+ * The currently displayed value
+ * @var int
+ */
+ protected $currentValue = 0;
+
+ /**
+ * Create and initialize this AxisUnit
+ *
+ * @param int $nrOfTicks The number of ticks to use
+ */
+ public function __construct($nrOfTicks = 10)
+ {
+ $this->min = PHP_INT_MAX;
+ $this->max = ~PHP_INT_MAX;
+ $this->nrOfTicks = $nrOfTicks;
+ }
+
+ /**
+ * Add a dataset and calculate the minimum and maximum value for this AxisUnit
+ *
+ * @param array $dataset The dataset to add
+ * @param int $idx The idx (0 for x, 1 for y)
+ *
+ * @return $this Fluent interface
+ */
+ public function addValues(array $dataset, $idx = 0)
+ {
+ $datapoints = array();
+
+ foreach ($dataset['data'] as $points) {
+ $datapoints[] = $points[$idx];
+ }
+ if (empty($datapoints)) {
+ return $this;
+ }
+ sort($datapoints);
+ if (!$this->staticMax) {
+ $this->max = max($this->max, $datapoints[count($datapoints) - 1]);
+ }
+ if (!$this->staticMin) {
+ $this->min = min($this->min, $datapoints[0]);
+ }
+ $this->currentTick = 0;
+ $this->currentValue = $this->min;
+ if ($this->max === $this->min) {
+ $this->max = $this->min + 10;
+ }
+ $this->nrOfTicks = $this->max - $this->min;
+ return $this;
+ }
+
+ /**
+ * Transform the absolute value to an axis relative value
+ *
+ * @param int $value The absolute coordinate from the dataset
+ * @return float|int The axis relative coordinate (between 0 and 100)
+ */
+ public function transform($value)
+ {
+ if ($value < $this->min) {
+ return 0;
+ } elseif ($value > $this->max) {
+ return 100;
+ } else {
+ return 100 * ($value - $this->min) / $this->nrOfTicks;
+ }
+ }
+
+ /**
+ * Return the position of the current tick
+ *
+ * @return int
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return $this->currentTick;
+ }
+
+ /**
+ * Calculate the next tick and tick value
+ */
+ public function next(): void
+ {
+ $this->currentTick += (100 / $this->nrOfTicks);
+ $this->currentValue += (($this->max - $this->min) / $this->nrOfTicks);
+ }
+
+ /**
+ * Return the label for the current tick
+ *
+ * @return string The label for the current tick
+ */
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return (string) intval($this->currentValue);
+ }
+
+ /**
+ * True when we're at a valid tick (iterator interface)
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return $this->currentTick >= 0 && $this->currentTick <= 100;
+ }
+
+ /**
+ * Reset the current tick and label value
+ */
+ public function rewind(): void
+ {
+ $this->currentTick = 0;
+ $this->currentValue = $this->min;
+ }
+
+ /**
+ * Set the axis maximum value to a fixed value
+ *
+ * @param int $max The new maximum value
+ */
+ public function setMax($max)
+ {
+ if ($max !== null) {
+ $this->max = $max;
+ $this->staticMax = true;
+ }
+ }
+
+ /**
+ * Set the axis minimum value to a fixed value
+ *
+ * @param int $min The new minimum value
+ */
+ public function setMin($min)
+ {
+ if ($min !== null) {
+ $this->min = $min;
+ $this->staticMin = true;
+ }
+ }
+
+ /**
+ * Return the current minimum value of the axis
+ *
+ * @return int The minimum set for this axis
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * Return the current maximum value of the axis
+ *
+ * @return int The maximum set for this axis
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Get the amount of ticks necessary to display this AxisUnit
+ *
+ * @return int
+ */
+ public function getTicks()
+ {
+ return $this->nrOfTicks;
+ }
+}
diff --git a/library/Icinga/Chart/Unit/LogarithmicUnit.php b/library/Icinga/Chart/Unit/LogarithmicUnit.php
new file mode 100644
index 0000000..90cad57
--- /dev/null
+++ b/library/Icinga/Chart/Unit/LogarithmicUnit.php
@@ -0,0 +1,263 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Unit;
+
+/**
+ * Logarithmic tick distribution over the axis
+ *
+ * This class does not use the actual logarithm, but a slightly altered version called the
+ * Log-Modulo transformation. This is necessary, since a regular logarithmic scale is not able to display negative
+ * values and zero-points. See <a href="http://blogs.sas.com/content/iml/2014/07/14/log-transformation-of-pos-neg>
+ * this article </a> for a more detailed description.
+ */
+class LogarithmicUnit implements AxisUnit
+{
+ /**
+ * @var int
+ */
+ protected $base;
+
+ /**
+ * @var
+ */
+ protected $currentTick;
+
+ /**
+ * @var
+ */
+ protected $minExp;
+
+ /**
+ * @var
+ */
+ protected $maxExp;
+
+ /**
+ * True when the minimum value is static and isn't affected by the data set
+ *
+ * @var bool
+ */
+ protected $staticMin = false;
+
+ /**
+ * True when the maximum value is static and isn't affected by the data set
+ *
+ * @var bool
+ */
+ protected $staticMax = false;
+
+ /**
+ * Create and initialize this AxisUnit
+ *
+ * @param int $nrOfTicks The number of ticks to use
+ */
+ public function __construct($base = 10)
+ {
+ $this->base = $base;
+ $this->minExp = PHP_INT_MAX;
+ $this->maxExp = ~PHP_INT_MAX;
+ }
+
+ /**
+ * Add a dataset and calculate the minimum and maximum value for this AxisUnit
+ *
+ * @param array $dataset The dataset to add
+ * @param int $idx The idx (0 for x, 1 for y)
+ *
+ * @return $this Fluent interface
+ */
+ public function addValues(array $dataset, $idx = 0)
+ {
+ $datapoints = array();
+
+ foreach ($dataset['data'] as $points) {
+ $datapoints[] = $points[$idx];
+ }
+ if (empty($datapoints)) {
+ return $this;
+ }
+ sort($datapoints);
+ if (!$this->staticMax) {
+ $this->maxExp = max($this->maxExp, $this->logCeil($datapoints[count($datapoints) - 1]));
+ }
+ if (!$this->staticMin) {
+ $this->minExp = min($this->minExp, $this->logFloor($datapoints[0]));
+ }
+ $this->currentTick = 0;
+
+ return $this;
+ }
+
+ /**
+ * Transform the absolute value to an axis relative value
+ *
+ * @param int $value The absolute coordinate from the data set
+ * @return float|int The axis relative coordinate (between 0 and 100)
+ */
+ public function transform($value)
+ {
+ if ($value < $this->pow($this->minExp)) {
+ return 0;
+ } elseif ($value > $this->pow($this->maxExp)) {
+ return 100;
+ } else {
+ return 100 * ($this->log($value) - $this->minExp) / $this->getTicks();
+ }
+ }
+
+ /**
+ * Return the position of the current tick
+ *
+ * @return int
+ */
+ public function current()
+ {
+ return $this->currentTick * (100 / $this->getTicks());
+ }
+
+ /**
+ * Calculate the next tick and tick value
+ */
+ public function next()
+ {
+ ++ $this->currentTick;
+ }
+
+ /**
+ * Return the label for the current tick
+ *
+ * @return string The label for the current tick
+ */
+ public function key()
+ {
+ $currentBase = $this->currentTick + $this->minExp;
+ if (abs($currentBase) > 4) {
+ return $this->base . 'E' . $currentBase;
+ }
+ return (string) intval($this->pow($currentBase));
+ }
+
+ /**
+ * True when we're at a valid tick (iterator interface)
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentTick >= 0 && $this->currentTick < $this->getTicks();
+ }
+
+ /**
+ * Reset the current tick and label value
+ */
+ public function rewind()
+ {
+ $this->currentTick = 0;
+ }
+
+ /**
+ * Perform a log-modulo transformation
+ *
+ * @param $value The value to transform
+ *
+ * @return double The transformed value
+ */
+ protected function log($value)
+ {
+ $sign = $value > 0 ? 1 : -1;
+ return $sign * log1p($sign * $value) / log($this->base);
+ }
+
+ /**
+ * Calculate the biggest exponent necessary to display the given data point
+ *
+ * @param $value
+ *
+ * @return float
+ */
+ protected function logCeil($value)
+ {
+ return ceil($this->log($value)) + 1;
+ }
+
+ /**
+ * Calculate the smallest exponent necessary to display the given data point
+ *
+ * @param $value
+ *
+ * @return float
+ */
+ protected function logFloor($value)
+ {
+ return floor($this->log($value));
+ }
+
+ /**
+ * Inverse function to the log-modulo transformation
+ *
+ * @param $value
+ *
+ * @return double
+ */
+ protected function pow($value)
+ {
+ if ($value == 0) {
+ return 0;
+ }
+ $sign = $value > 0 ? 1 : -1;
+ return $sign * (pow($this->base, $sign * $value));
+ }
+
+ /**
+ * Set the axis minimum value to a fixed value
+ *
+ * @param int $min The new minimum value
+ */
+ public function setMin($min)
+ {
+ $this->minExp = $this->logFloor($min);
+ $this->staticMin = true;
+ }
+
+ /**
+ * Set the axis maximum value to a fixed value
+ *
+ * @param int $max The new maximum value
+ */
+ public function setMax($max)
+ {
+ $this->maxExp = $this->logCeil($max);
+ $this->staticMax = true;
+ }
+
+ /**
+ * Return the current minimum value of the axis
+ *
+ * @return int The minimum set for this axis
+ */
+ public function getMin()
+ {
+ return $this->pow($this->minExp);
+ }
+
+ /**
+ * Return the current maximum value of the axis
+ *
+ * @return int The maximum set for this axis
+ */
+ public function getMax()
+ {
+ return $this->pow($this->maxExp);
+ }
+
+ /**
+ * Get the amount of ticks necessary to display this AxisUnit
+ *
+ * @return int
+ */
+ public function getTicks()
+ {
+ return $this->maxExp - $this->minExp;
+ }
+}
diff --git a/library/Icinga/Chart/Unit/StaticAxis.php b/library/Icinga/Chart/Unit/StaticAxis.php
new file mode 100644
index 0000000..d563091
--- /dev/null
+++ b/library/Icinga/Chart/Unit/StaticAxis.php
@@ -0,0 +1,129 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Unit;
+
+class StaticAxis implements AxisUnit
+{
+ private $items = array();
+
+ /**
+ * Add a dataset to this AxisUnit, required for dynamic min and max values
+ *
+ * @param array $dataset The dataset that will be shown in the Axis
+ * @param int $idx The idx in the dataset (0 for x, 1 for y)
+ *
+ * @return $this Fluent interface
+ */
+ public function addValues(array $dataset, $idx = 0)
+ {
+ $datapoints = array();
+ foreach ($dataset['data'] as $points) {
+ $this->items[] = $points[$idx];
+ }
+ $this->items = array_unique($this->items);
+
+ return $this;
+ }
+
+ /**
+ * Transform the given absolute value in an axis relative value
+ *
+ * @param int $value The absolute, dataset dependent value
+ *
+ * @return int An axis relative value
+ */
+ public function transform($value)
+ {
+ $flipped = array_flip($this->items);
+ if (!isset($flipped[$value])) {
+ return 0;
+ }
+ $pos = $flipped[$value];
+ return 1 + (99 / count($this->items) * $pos);
+ }
+ /**
+ * Set the axis minimum value to a fixed value
+ *
+ * @param int $min The new minimum value
+ */
+ public function setMin($min)
+ {
+ }
+
+ /**
+ * Set the axis maximum value to a fixed value
+ *
+ * @param int $max The new maximum value
+ */
+ public function setMax($max)
+ {
+ }
+
+ /**
+ * (PHP 5 &gt;= 5.0.0)<br/>
+ * Return the current element
+ * @link http://php.net/manual/en/iterator.current.php
+ * @return mixed Can return any type.
+ */
+ public function current()
+ {
+ return 1 + (99 / count($this->items) * key($this->items));
+ }
+
+ /**
+ * (PHP 5 &gt;= 5.0.0)<br/>
+ * Move forward to next element
+ * @link http://php.net/manual/en/iterator.next.php
+ * @return void Any returned value is ignored.
+ */
+ public function next()
+ {
+ return next($this->items);
+ }
+
+ /**
+ * (PHP 5 &gt;= 5.0.0)<br/>
+ * Return the key of the current element
+ * @link http://php.net/manual/en/iterator.key.php
+ * @return mixed scalar on success, or null on failure.
+ */
+ public function key()
+ {
+ return current($this->items);
+ }
+
+ /**
+ * (PHP 5 &gt;= 5.0.0)<br/>
+ * Checks if current position is valid
+ * @link http://php.net/manual/en/iterator.valid.php
+ * @return boolean The return value will be casted to boolean and then evaluated.
+ * Returns true on success or false on failure.
+ */
+ public function valid()
+ {
+ return current($this->items) !== false;
+ }
+
+ /**
+ * (PHP 5 &gt;= 5.0.0)<br/>
+ * Rewind the Iterator to the first element
+ * @link http://php.net/manual/en/iterator.rewind.php
+ * @return void Any returned value is ignored.
+ */
+ public function rewind()
+ {
+ return reset($this->items);
+ }
+
+ /**
+ * Get the amount of ticks of this axis
+ *
+ * @return int
+ */
+ public function getTicks()
+ {
+ return count($this->items);
+ }
+}
diff --git a/library/Icinga/Cli/AnsiScreen.php b/library/Icinga/Cli/AnsiScreen.php
new file mode 100644
index 0000000..2780f08
--- /dev/null
+++ b/library/Icinga/Cli/AnsiScreen.php
@@ -0,0 +1,122 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Cli\Screen;
+use Icinga\Exception\IcingaException;
+
+// @see http://en.wikipedia.org/wiki/ANSI_escape_code
+
+class AnsiScreen extends Screen
+{
+ protected $fgColors = array(
+ 'black' => '30',
+ 'darkgray' => '1;30',
+ 'red' => '31',
+ 'lightred' => '1;31',
+ 'green' => '32',
+ 'lightgreen' => '1;32',
+ 'brown' => '33',
+ 'yellow' => '1;33',
+ 'blue' => '34',
+ 'lightblue' => '1;34',
+ 'purple' => '35',
+ 'lightpurple' => '1;35',
+ 'cyan' => '36',
+ 'lightcyan' => '1;36',
+ 'lightgray' => '37',
+ 'white' => '1;37',
+ );
+
+ protected $bgColors = array(
+ 'black' => '40',
+ 'red' => '41',
+ 'green' => '42',
+ 'brown' => '43',
+ 'blue' => '44',
+ 'purple' => '45',
+ 'cyan' => '46',
+ 'lightgray' => '47',
+ );
+
+ public function strlen($string)
+ {
+ return strlen($this->stripAnsiCodes($string));
+ }
+
+ public function stripAnsiCodes($string)
+ {
+ return preg_replace('/\e\[?.*?[\@-~]/', '', $string);
+ }
+
+ public function clear()
+ {
+ return "\033[2J" // Clear the whole screen
+ . "\033[1;1H" // Move the cursor to row 1, column 1
+ . "\033[1S"; // Scroll whole page up by 1 line (why?)
+ }
+
+ public function underline($text)
+ {
+ return "\033[4m"
+ . $text
+ . "\033[0m"; // Reset color codes
+ }
+
+ public function colorize($text, $fgColor = null, $bgColor = null)
+ {
+ return $this->startColor($fgColor, $bgColor)
+ . $text
+ . "\033[0m"; // Reset color codes
+ }
+
+ protected function fgColor($color)
+ {
+ if (! array_key_exists($color, $this->fgColors)) {
+ throw new IcingaException(
+ 'There is no such foreground color: %s',
+ $color
+ );
+ }
+ return $this->fgColors[$color];
+ }
+
+ protected function bgColor($color)
+ {
+ if (! array_key_exists($color, $this->bgColors)) {
+ throw new IcingaException(
+ 'There is no such background color: %s',
+ $color
+ );
+ }
+ return $this->bgColors[$color];
+ }
+
+ protected function startColor($fgColor = null, $bgColor = null)
+ {
+ $escape = "ESC[";
+ $parts = array();
+ if ($fgColor !== null
+ && $bgColor !== null
+ && ! array_key_exists($bgColor, $this->bgColors)
+ && array_key_exists($bgColor, $this->fgColors)
+ && array_key_exists($fgColor, $this->bgColors)
+ ) {
+ $parts[] = '7'; // reverse video, negative image
+ $parts[] = $this->bgColor($fgColor);
+ $parts[] = $this->fgColor($bgColor);
+ } else {
+ if ($fgColor !== null) {
+ $parts[] = $this->fgColor($fgColor);
+ }
+ if ($bgColor !== null) {
+ $parts[] = $this->bgColor($bgColor);
+ }
+ }
+ if (empty($parts)) {
+ return '';
+ }
+ return "\033[" . implode(';', $parts) . 'm';
+ }
+}
diff --git a/library/Icinga/Cli/Command.php b/library/Icinga/Cli/Command.php
new file mode 100644
index 0000000..d1a1986
--- /dev/null
+++ b/library/Icinga/Cli/Command.php
@@ -0,0 +1,208 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Application\ApplicationBootstrap as App;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotReadableError;
+use ipl\I18n\Translation;
+
+abstract class Command
+{
+ use Translation;
+
+ protected $app;
+ protected $docs;
+
+ /**
+ * @var Params
+ */
+ protected $params;
+ protected $screen;
+
+ /**
+ * Whether the --verbose switch is given and thus the set log level INFO is
+ *
+ * @var bool
+ */
+ protected $isVerbose;
+
+ /**
+ * Whether the --debug switch is given and thus the set log level DEBUG is
+ *
+ * @var bool
+ */
+ protected $isDebugging;
+
+ protected $moduleName;
+ protected $commandName;
+ protected $actionName;
+
+ protected $config;
+
+ protected $configs;
+
+ protected $defaultActionName = 'default';
+
+ /** @var bool Whether to automatically load enabled modules */
+ protected $loadEnabledModules = true;
+
+ public function __construct(App $app, $moduleName, $commandName, $actionName, $initialize = true)
+ {
+ $this->app = $app;
+ $this->moduleName = $moduleName;
+ $this->commandName = $commandName;
+ $this->actionName = $actionName;
+ $this->params = $app->getParams();
+ $this->screen = Screen::instance();
+ $this->trace = $this->params->shift('trace', false);
+ $this->isVerbose = $this->params->shift('verbose', false);
+ $this->isDebugging = $this->params->shift('debug', false);
+ $this->configs = [];
+
+ $this->translationDomain = $moduleName ?: 'icinga';
+
+ if ($this->loadEnabledModules) {
+ try {
+ $app->getModuleManager()->loadEnabledModules();
+ } catch (NotReadableError $e) {
+ Logger::error(new IcingaException('Cannot load enabled modules. An exception was thrown:', $e));
+ }
+ }
+
+ if ($initialize) {
+ $this->init();
+ }
+ }
+
+ public function Config($file = null)
+ {
+ if ($this->isModule()) {
+ return $this->getModuleConfig($file);
+ } else {
+ return $this->getMainConfig($file);
+ }
+ }
+
+ private function getModuleConfig($file = null)
+ {
+ if ($file === null) {
+ if ($this->config === null) {
+ $this->config = Config::module($this->moduleName);
+ }
+ return $this->config;
+ } else {
+ if (! array_key_exists($file, $this->configs)) {
+ $this->configs[$file] = Config::module($this->moduleName, $file);
+ }
+ return $this->configs[$file];
+ }
+ }
+
+ private function getMainConfig($file = null)
+ {
+ if ($file === null) {
+ if ($this->config === null) {
+ $this->config = Config::app();
+ }
+ return $this->config;
+ } else {
+ if (! array_key_exists($file, $this->configs)) {
+ $this->configs[$file] = Config::app($file);
+ }
+ return $this->configs[$file];
+ }
+ return $this->config;
+ }
+
+ public function isModule()
+ {
+ return substr(get_class($this), 0, 14) === 'Icinga\\Module\\';
+ }
+
+ public function setParams(Params $params)
+ {
+ $this->params = $params;
+ }
+
+ public function hasRemainingParams()
+ {
+ return $this->params->count() > 0;
+ }
+
+ public function showTrace()
+ {
+ return $this->trace;
+ }
+
+ public function fail($msg)
+ {
+ throw new IcingaException('%s', $msg);
+ }
+
+ public function getDefaultActionName()
+ {
+ return $this->defaultActionName;
+ }
+
+ /**
+ * Get {@link moduleName}
+ *
+ * @return string
+ */
+ public function getModuleName()
+ {
+ return $this->moduleName;
+ }
+
+ public function hasDefaultActionName()
+ {
+ return $this->hasActionName($this->defaultActionName);
+ }
+
+ public function hasActionName($name)
+ {
+ $actions = $this->listActions();
+ return in_array($name, $actions);
+ }
+
+ public function listActions()
+ {
+ $actions = array();
+ foreach (get_class_methods($this) as $method) {
+ if (preg_match('~^([A-Za-z0-9]+)Action$~', $method, $m)) {
+ $actions[] = $m[1];
+ }
+ }
+ sort($actions);
+ return $actions;
+ }
+
+ public function docs()
+ {
+ if ($this->docs === null) {
+ $this->docs = new Documentation($this->app);
+ }
+ return $this->docs;
+ }
+
+ public function showUsage($action = null)
+ {
+ if ($action === null) {
+ $action = $this->actionName;
+ }
+ echo $this->docs()->usage(
+ $this->moduleName,
+ $this->commandName,
+ $action
+ );
+ return false;
+ }
+
+ public function init()
+ {
+ }
+}
diff --git a/library/Icinga/Cli/Documentation.php b/library/Icinga/Cli/Documentation.php
new file mode 100644
index 0000000..111745e
--- /dev/null
+++ b/library/Icinga/Cli/Documentation.php
@@ -0,0 +1,162 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Application\ApplicationBootstrap as App;
+use Icinga\Cli\Documentation\CommentParser;
+use ReflectionClass;
+use ReflectionMethod;
+
+class Documentation
+{
+ protected $icinga;
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->loader = $app->cliLoader();
+ }
+
+ public function usage($module = null, $command = null, $action = null)
+ {
+ if ($module) {
+ $module = $this->loader->resolveModuleName($module);
+ return $this->moduleUsage($module, $command, $action);
+ }
+ if ($command) {
+ $command = $this->loader->resolveCommandName($command);
+ return $this->commandUsage($command, $action);
+ }
+ return $this->globalUsage();
+ }
+
+ public function globalUsage()
+ {
+ $d = "USAGE: icingacli [module] <command> [action] [options]\n\n"
+ . "Available commands:\n\n";
+ foreach ($this->loader->listCommands() as $command) {
+ if ($command !== 'autocomplete') {
+ $obj = $this->loader->getCommandInstance($command);
+ $d .= sprintf(
+ " %-14s %s\n",
+ $command,
+ $this->getClassTitle($obj)
+ );
+ }
+ }
+ $d .= "\nAvailable modules:\n\n";
+ foreach ($this->loader->listModules() as $module) {
+ $d .= ' ' . $module . "\n";
+ }
+ $d .= "\nGlobal options:\n\n"
+ . " --log [t] Log to <t>, either stderr, file or syslog (default: stderr)\n"
+ . " --log-path <f> Which file to log into in case of --log file\n"
+ . " --verbose Be verbose\n"
+ . " --debug Show debug output\n"
+ . " --help Show help\n"
+ . " --benchmark Show benchmark summary\n"
+ . " --watch [s] Refresh output every <s> seconds (default: 5)\n"
+ . " --version Shows version of Icinga Web 2, loaded modules and PHP\n"
+ ;
+ $d .= "\nShow help on a specific command : icingacli help <command>"
+ . "\nShow help on a specific module : icingacli help <module>"
+ . "\n";
+ return $d;
+ }
+
+ public function moduleUsage($module, $command = null, $action = null)
+ {
+ $commands = $this->loader->listModuleCommands($module);
+
+ if (empty($commands)) {
+ return "The '$module' module does not provide any CLI commands\n";
+ }
+ $d = '';
+ if ($command) {
+ $obj = $this->loader->getModuleCommandInstance($module, $command);
+ }
+ if ($command === null) {
+ $d = "USAGE: icingacli $module <command> [<action>] [options]\n\n"
+ . "Available commands:\n\n";
+ foreach ($commands as $command) {
+ $d .= ' ' . $command . "\n";
+ }
+ $d .= "\nShow help on a specific command: icingacli help $module <command>\n";
+ } elseif ($action === null) {
+ $d .= $this->showCommandActions($obj, $command);
+ } else {
+ $action = $this->loader->resolveObjectActionName($obj, $action);
+ $d .= $this->getMethodDocumentation($obj, $action);
+ }
+ return $d;
+ }
+
+ /**
+ * @param Command $command
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function showCommandActions($command, $name)
+ {
+ $actions = $command->listActions();
+ $d = $this->getClassDocumentation($command)
+ . "Available actions:\n\n";
+ foreach ($actions as $action) {
+ $d .= sprintf(
+ " %-14s %s\n",
+ $action,
+ $this->getMethodTitle($command, $action)
+ );
+ }
+ $d .= "\nShow help on a specific action: icingacli help ";
+ if ($command->isModule()) {
+ $d .= $command->getModuleName() . ' ';
+ }
+ $d .= "$name <action>\n";
+ return $d;
+ }
+
+ public function commandUsage($command, $action = null)
+ {
+ $obj = $this->loader->getCommandInstance($command);
+ $action = $this->loader->resolveObjectActionName($obj, $action);
+
+ $d = "\n";
+ if ($action) {
+ $d .= $this->getMethodDocumentation($obj, $action);
+ } else {
+ $d .= $this->showCommandActions($obj, $command);
+ }
+ return $d;
+ }
+
+ protected function getClassTitle($class)
+ {
+ $ref = new ReflectionClass($class);
+ $comment = new CommentParser($ref->getDocComment());
+ return $comment->getTitle();
+ }
+
+ protected function getClassDocumentation($class)
+ {
+ $ref = new ReflectionClass($class);
+ $comment = new CommentParser($ref->getDocComment());
+ return $comment->dump();
+ }
+
+ protected function getMethodTitle($class, $method)
+ {
+ $ref = new ReflectionMethod($class, $method . 'Action');
+ $comment = new CommentParser($ref->getDocComment());
+ return $comment->getTitle();
+ }
+
+ protected function getMethodDocumentation($class, $method)
+ {
+ $ref = new ReflectionMethod($class, $method . 'Action');
+ $comment = new CommentParser($ref->getDocComment());
+ return $comment->dump();
+ }
+}
diff --git a/library/Icinga/Cli/Documentation/CommentParser.php b/library/Icinga/Cli/Documentation/CommentParser.php
new file mode 100644
index 0000000..4104848
--- /dev/null
+++ b/library/Icinga/Cli/Documentation/CommentParser.php
@@ -0,0 +1,85 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli\Documentation;
+
+use Icinga\Cli\Screen;
+
+class CommentParser
+{
+ protected $raw;
+ protected $plain;
+ protected $title;
+ protected $paragraphs = array();
+
+ public function __construct($raw)
+ {
+ $this->raw = $raw;
+ if ($raw) {
+ $this->parse();
+ }
+ }
+
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ protected function parse()
+ {
+ $plain = $this->raw;
+
+ // Strip comment start /**
+ $plain = preg_replace('~^/\s*\*\*\n~s', '', $plain);
+
+ // Strip comment end */
+ $plain = preg_replace('~\n\s*\*/\s*~s', "\n", $plain);
+ $p = null;
+ foreach (preg_split('~\n~', $plain) as $line) {
+ // Strip * at line start
+ $line = preg_replace('~^\s*\*\s?~', '', $line);
+ $line = rtrim($line);
+ if ($this->title === null) {
+ $this->title = $line;
+ continue;
+ }
+ if ($p === null && empty($this->paragraphs)) {
+ $p = & $this->paragraphs[];
+ }
+
+ if ($line === '') {
+ if ($p !== null) {
+ $p = & $this->paragraphs[];
+ }
+ continue;
+ }
+ if ($p === null) {
+ $p = $line;
+ } else {
+ if (substr($line, 0, 2) === ' ') {
+ $p .= "\n" . $line;
+ } else {
+ $p .= ' ' . $line;
+ }
+ }
+ }
+ if ($p === null) {
+ array_pop($this->paragraphs);
+ }
+ }
+
+ public function dump()
+ {
+ if ($this->title) {
+ $res = $this->title . "\n" . str_repeat('=', strlen($this->title)) . "\n\n";
+ } else {
+ $res = '';
+ }
+
+ foreach ($this->paragraphs as $p) {
+ $res .= wordwrap($p, Screen::instance()->getColumns()) . "\n\n";
+ }
+
+ return $res;
+ }
+}
diff --git a/library/Icinga/Cli/Loader.php b/library/Icinga/Cli/Loader.php
new file mode 100644
index 0000000..732c3bc
--- /dev/null
+++ b/library/Icinga/Cli/Loader.php
@@ -0,0 +1,499 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Application\ApplicationBootstrap as App;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Cli\Params;
+use Icinga\Cli\Screen;
+use Icinga\Cli\Command;
+use Icinga\Cli\Documentation;
+use Exception;
+
+/**
+ *
+ */
+class Loader
+{
+ protected $app;
+
+ protected $docs;
+
+ protected $commands;
+
+ protected $modules;
+
+ protected $moduleCommands = array();
+
+ protected $coreAppDir;
+
+ protected $screen;
+
+ protected $moduleName;
+
+ protected $commandName;
+
+ protected $actionName; // Should this better be moved to the Command?
+
+ /**
+ * [$command] = $class;
+ */
+ protected $commandClassMap = array();
+
+ /**
+ * [$command] = $file;
+ */
+ protected $commandFileMap = array();
+
+ /**
+ * [$module][$command] = $class;
+ */
+ protected $moduleClassMap = array();
+
+ /**
+ * [$module][$command] = $file;
+ */
+ protected $moduleFileMap = array();
+
+ protected $commandInstances = array();
+
+ protected $moduleInstances = array();
+
+ protected $lastSuggestions = array();
+
+ public function __construct(App $app)
+ {
+ $this->app = $app;
+ $this->coreAppDir = $app->getApplicationDir('clicommands');
+ }
+
+ /**
+ * Screen shortcut
+ *
+ * @return Screen
+ */
+ protected function screen()
+ {
+ if ($this->screen === null) {
+ $this->screen = Screen::instance(STDERR);
+ }
+
+ return $this->screen;
+ }
+
+ /**
+ * Documentation shortcut
+ *
+ * @return Documentation
+ */
+ protected function docs()
+ {
+ if ($this->docs === null) {
+ $this->docs = new Documentation($this->app);
+ }
+ return $this->docs;
+ }
+
+ /**
+ * Show given message and exit
+ *
+ * @param string $msg message to show
+ */
+ public function fail($msg)
+ {
+ fprintf(STDERR, "%s: %s\n", $this->screen()->colorize('ERROR', 'red'), $msg);
+ exit(1);
+ }
+
+ public function getModuleName()
+ {
+ return $this->moduleName;
+ }
+
+ public function setModuleName($name)
+ {
+ $this->moduleName = $name;
+ return $this;
+ }
+
+ public function getCommandName()
+ {
+ return $this->commandName;
+ }
+
+ public function getActionName()
+ {
+ return $this->actionName;
+ }
+
+ public function getCommandInstance($command)
+ {
+ if (! array_key_exists($command, $this->commandInstances)) {
+ $this->assertCommandExists($command);
+ require_once $this->commandFileMap[$command];
+ $className = $this->commandClassMap[$command];
+ $this->commandInstances[$command] = new $className(
+ $this->app,
+ null,
+ $command,
+ null,
+ false
+ );
+ }
+ return $this->commandInstances[$command];
+ }
+
+ public function getModuleCommandInstance($module, $command)
+ {
+ if (! array_key_exists($command, $this->moduleInstances[$module])) {
+ $this->assertModuleCommandExists($module, $command);
+ require_once $this->moduleFileMap[$module][$command];
+ $className = $this->moduleClassMap[$module][$command];
+ $this->moduleInstances[$module][$command] = new $className(
+ $this->app,
+ $module,
+ $command,
+ null,
+ false
+ );
+ }
+ return $this->moduleInstances[$module][$command];
+ }
+
+ public function getLastSuggestions()
+ {
+ return $this->lastSuggestions;
+ }
+
+ public function showLastSuggestions()
+ {
+ if (! empty($this->lastSuggestions)) {
+ foreach ($this->lastSuggestions as & $s) {
+ $s = $this->screen()->colorize($s, 'lightblue');
+ }
+ fprintf(
+ STDERR,
+ "Did you mean %s?\n",
+ implode(" or ", $this->lastSuggestions)
+ );
+ }
+ }
+
+ public function parseParams(Params $params = null)
+ {
+ if ($params === null) {
+ $params = $this->app->getParams();
+ }
+
+ if ($this->moduleName === null) {
+ $first = $params->shift();
+ if (! $first) {
+ return;
+ }
+ $found = $this->resolveName($first);
+ } else {
+ $found = $this->moduleName;
+ }
+ if (! $found) {
+ $msg = "There is no such module or command: '$first'";
+ fprintf(STDERR, "%s: %s\n", $this->screen()->colorize('ERROR', 'red'), $msg);
+ $this->showLastSuggestions();
+ fwrite(STDERR, "\n");
+ }
+
+ $obj = null;
+ if ($this->hasCommand($found)) {
+ $this->commandName = $found;
+ $obj = $this->getCommandInstance($this->commandName);
+ } elseif ($this->hasModule($found)) {
+ $this->moduleName = $found;
+ $command = $this->resolveModuleCommandName($found, $params->shift());
+ if ($command) {
+ $this->commandName = $command;
+ $obj = $this->getModuleCommandInstance(
+ $this->moduleName,
+ $this->commandName
+ );
+ }
+ }
+ if ($obj !== null) {
+ $action = $this->resolveObjectActionName(
+ $obj,
+ $params->getStandalone()
+ );
+ if ($obj->hasActionName($action)) {
+ $this->actionName = $action;
+ $params->shift();
+ } elseif ($obj->hasDefaultActionName()) {
+ $this->actionName = $obj->getDefaultActionName();
+ }
+ }
+ return $this;
+ }
+
+ public function handleParams(Params $params = null)
+ {
+ $this->parseParams($params);
+ $this->dispatch();
+ }
+
+ public function dispatch(Params $overrideParams = null)
+ {
+ if ($this->commandName === null) {
+ fwrite(STDERR, $this->docs()->usage($this->moduleName));
+ return false;
+ } elseif ($this->actionName === null) {
+ fwrite(STDERR, $this->docs()->usage($this->moduleName, $this->commandName));
+ return false;
+ }
+
+ try {
+ if ($this->moduleName) {
+ $this->app->getModuleManager()->loadModule($this->moduleName);
+ $obj = $this->getModuleCommandInstance(
+ $this->moduleName,
+ $this->commandName
+ );
+ } else {
+ $obj = $this->getCommandInstance($this->commandName);
+ }
+ if ($overrideParams !== null) {
+ $obj->setParams($overrideParams);
+ }
+ $obj->init();
+ return $obj->{$this->actionName . 'Action'}();
+ } catch (Exception $e) {
+ if ($obj && $obj instanceof Command && $obj->showTrace()) {
+ fwrite(STDERR, $this->formatTrace($e->getTrace()));
+ }
+
+ $this->fail(IcingaException::describe($e));
+ }
+ }
+
+ protected function searchMatch($needle, $haystack)
+ {
+ if ($needle === null) {
+ $needle = '';
+ }
+
+ $this->lastSuggestions = preg_grep(sprintf('/^%s.*$/', preg_quote($needle, '/')), $haystack);
+ $match = array_search($needle, $haystack, true);
+ if (false !== $match) {
+ return $haystack[$match];
+ }
+ if (count($this->lastSuggestions) === 1) {
+ $lastSuggestions = array_values($this->lastSuggestions);
+ return $lastSuggestions[0];
+ }
+ return false;
+ }
+
+ public function resolveName($name)
+ {
+ return $this->searchMatch(
+ $name,
+ array_merge($this->listCommands(), $this->listModules())
+ );
+ }
+
+ public function resolveCommandName($name)
+ {
+ return $this->searchMatch($name, $this->listCommands());
+ }
+
+ public function resolveModuleName($name)
+ {
+ return $this->searchMatch($name, $this->listModules());
+ }
+
+ public function resolveModuleCommandName($module, $name)
+ {
+ return $this->searchMatch($name, $this->listModuleCommands($module));
+ }
+
+ public function resolveObjectActionName($obj, $name)
+ {
+ return $this->searchMatch($name, $obj->listActions());
+ }
+
+ protected function assertModuleExists($module)
+ {
+ if (! $this->hasModule($module)) {
+ throw new ProgrammingError(
+ 'There is no such module: %s',
+ $module
+ );
+ }
+ }
+
+ protected function assertCommandExists($command)
+ {
+ if (! $this->hasCommand($command)) {
+ throw new ProgrammingError(
+ 'There is no such command: %s',
+ $command
+ );
+ }
+ }
+
+ protected function assertModuleCommandExists($module, $command)
+ {
+ $this->assertModuleExists($module);
+ if (! $this->hasModuleCommand($module, $command)) {
+ throw new ProgrammingError(
+ 'The module \'%s\' has no such command: %s',
+ $module,
+ $command
+ );
+ }
+ }
+
+ protected function formatTrace($trace)
+ {
+ $output = array();
+ foreach ($trace as $i => $step) {
+ $object = '';
+ if (isset($step['object']) && is_object($step['object'])) {
+ $object = sprintf('[%s]', get_class($step['object'])) . $step['type'];
+ } elseif (! empty($step['object'])) {
+ $object = (string) $step['object'] . $step['type'];
+ }
+ if (isset($step['args']) && is_array($step['args'])) {
+ foreach ($step['args'] as & $arg) {
+ if (is_object($arg)) {
+ $arg = sprintf('[%s]', get_class($arg));
+ }
+ if (is_string($arg)) {
+ $arg = preg_replace('~\n~', '\n', $arg);
+ if (strlen($arg) > 50) {
+ $arg = substr($arg, 0, 47) . '...';
+ }
+ $arg = "'" . $arg . "'";
+ }
+ if ($arg === null) {
+ $arg = 'NULL';
+ }
+ if (is_bool($arg)) {
+ $arg = $arg ? 'TRUE' : 'FALSE';
+ }
+ }
+ } else {
+ $step['args'] = array();
+ }
+ $args = $step['args'];
+ foreach ($args as & $v) {
+ if (is_array($v)) {
+ $v = var_export($v, 1);
+ } else {
+ $v = (string) $v;
+ }
+ }
+ $output[$i] = sprintf(
+ '#%d %s:%d %s%s(%s)',
+ $i,
+ isset($step['file']) ? preg_replace(
+ '~.+/library/~',
+ 'library/',
+ $step['file']
+ ) : '[unknown file]',
+ isset($step['line']) ? $step['line'] : '0',
+ $object,
+ $step['function'],
+ implode(', ', $args)
+ );
+ }
+ return implode(PHP_EOL, $output) . PHP_EOL;
+ }
+
+ public function hasCommand($name)
+ {
+ return in_array($name, $this->listCommands());
+ }
+
+ public function hasModule($name)
+ {
+ return in_array($name, $this->listModules());
+ }
+
+ public function hasModuleCommand($module, $name)
+ {
+ return in_array($name, $this->listModuleCommands($module));
+ }
+
+ public function listModules()
+ {
+ if ($this->modules === null) {
+ $this->modules = array();
+ try {
+ $this->modules = array_unique(array_merge(
+ $this->app->getModuleManager()->listEnabledModules(),
+ $this->app->getModuleManager()->listLoadedModules()
+ ));
+ } catch (NotReadableError $e) {
+ $this->fail($e->getMessage());
+ }
+ }
+ return $this->modules;
+ }
+
+ protected function retrieveCommandsFromDir($dirname)
+ {
+ $commands = array();
+ if (! @file_exists($dirname) || ! is_readable($dirname)) {
+ return $commands;
+ }
+
+ $base = opendir($dirname);
+ if ($base === false) {
+ return $commands;
+ }
+ while (false !== ($dir = readdir($base))) {
+ if ($dir[0] === '.') {
+ continue;
+ }
+ if (preg_match('~^([A-Za-z0-9]+)Command\.php$~', $dir, $m)) {
+ $cmd = strtolower($m[1]);
+ $commands[] = $cmd;
+ }
+ }
+ closedir($base);
+ sort($commands);
+ return $commands;
+ }
+
+ public function listCommands()
+ {
+ if ($this->commands === null) {
+ $this->commands = array();
+ $ns = 'Icinga\\Clicommands\\';
+ $this->commands = $this->retrieveCommandsFromDir($this->coreAppDir);
+ foreach ($this->commands as $cmd) {
+ $this->commandClassMap[$cmd] = $ns . ucfirst($cmd) . 'Command';
+ $this->commandFileMap[$cmd] = $this->coreAppDir . '/' . ucfirst($cmd) . 'Command.php';
+ }
+ }
+ return $this->commands;
+ }
+
+ public function listModuleCommands($module)
+ {
+ if (! array_key_exists($module, $this->moduleCommands)) {
+ $ns = 'Icinga\\Module\\' . ucfirst($module) . '\\Clicommands\\';
+ $this->assertModuleExists($module);
+ $manager = $this->app->getModuleManager();
+ $manager->loadModule($module);
+ $dir = $manager->getModuleDir($module) . '/application/clicommands';
+ $this->moduleCommands[$module] = $this->retrieveCommandsFromDir($dir);
+ $this->moduleInstances[$module] = array();
+ foreach ($this->moduleCommands[$module] as $cmd) {
+ $this->moduleClassMap[$module][$cmd] = $ns . ucfirst($cmd) . 'Command';
+ $this->moduleFileMap[$module][$cmd] = $dir . '/' . ucfirst($cmd) . 'Command.php';
+ }
+ }
+ return $this->moduleCommands[$module];
+ }
+}
diff --git a/library/Icinga/Cli/Params.php b/library/Icinga/Cli/Params.php
new file mode 100644
index 0000000..463d4ae
--- /dev/null
+++ b/library/Icinga/Cli/Params.php
@@ -0,0 +1,320 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Exception\MissingParameterException;
+
+/**
+ * Params
+ *
+ * A class to ease commandline-option and -argument handling.
+ */
+class Params
+{
+ /**
+ * The name and path of the executable
+ *
+ * @var string
+ */
+ protected $program;
+
+ /**
+ * The arguments
+ *
+ * @var array
+ */
+ protected $standalone = array();
+
+ /**
+ * The options
+ *
+ * @var array
+ */
+ protected $params = array();
+
+ /**
+ * Parse the given commandline and create a new Params object
+ *
+ * @param array $argv The commandline
+ */
+ public function __construct($argv)
+ {
+ $noOptionFlag = false;
+ $this->program = array_shift($argv);
+ for ($i = 0; $i < count($argv); $i++) {
+ if ($argv[$i] === '--') {
+ $noOptionFlag = true;
+ } elseif (!$noOptionFlag && substr($argv[$i], 0, 2) === '--') {
+ $key = substr($argv[$i], 2);
+ $matches = array();
+ if (1 === preg_match(
+ '/(?<!.)([^=]+)=(.*)(?!.)/ms',
+ $key,
+ $matches
+ )) {
+ $this->params[$matches[1]] = $matches[2];
+ } elseif (! isset($argv[$i + 1]) || substr($argv[$i + 1], 0, 2) === '--') {
+ $this->params[$key] = true;
+ } elseif (array_key_exists($key, $this->params)) {
+ if (!is_array($this->params[$key])) {
+ $this->params[$key] = array($this->params[$key]);
+ }
+ $this->params[$key][] = $argv[++$i];
+ } else {
+ $this->params[$key] = $argv[++$i];
+ }
+ } else {
+ $this->standalone[] = $argv[$i];
+ }
+ }
+ }
+
+ /**
+ * Return the value for an argument by position
+ *
+ * @param int $pos The position of the argument
+ * @param mixed $default The default value to return
+ *
+ * @return mixed
+ */
+ public function getStandalone($pos = 0, $default = null)
+ {
+ if (isset($this->standalone[$pos])) {
+ return $this->standalone[$pos];
+ }
+ return $default;
+ }
+
+ /**
+ * Count and return the number of arguments and options
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->standalone) + count($this->params);
+ }
+
+ /**
+ * Return the options
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Return the arguments
+ *
+ * @return array
+ */
+ public function getAllStandalone()
+ {
+ return $this->standalone;
+ }
+
+ /**
+ * Support isset() and empty() checks on options
+ *
+ * @param $name
+ *
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->params[$name]);
+ }
+
+ /**
+ * @see Params::get()
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Return whether the given option exists
+ *
+ * @param string $key The option name to check
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ return array_key_exists($key, $this->params);
+ }
+
+ /**
+ * Return the value of the given option
+ *
+ * @param string $key The option name
+ * @param mixed $default The default value to return
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if ($this->has($key)) {
+ return $this->params[$key];
+ }
+ return $default;
+ }
+
+ /**
+ * Require a parameter
+ *
+ * @param string $name Name of the parameter
+ * @param bool $strict Whether the parameter's value must not be the empty string
+ *
+ * @return mixed
+ *
+ * @throws MissingParameterException If the parameter was not given
+ */
+ public function getRequired($name, $strict = true)
+ {
+ if ($this->has($name)) {
+ $value = $this->get($name);
+ if (! $strict || strlen($value) > 0) {
+ return $value;
+ }
+ }
+ $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name);
+ $e->setParameter($name);
+ throw $e;
+ }
+
+ /**
+ * Set a value for the given option
+ *
+ * @param string $key The option name
+ * @param mixed $value The value to set
+ *
+ * @return $this
+ */
+ public function set($key, $value)
+ {
+ $this->params[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Remove a single option or multiple options
+ *
+ * @param string|array $keys The option or options to remove
+ *
+ * @return $this
+ */
+ public function remove($keys = array())
+ {
+ if (! is_array($keys)) {
+ $keys = array($keys);
+ }
+ foreach ($keys as $key) {
+ if (array_key_exists($key, $this->params)) {
+ unset($this->params[$key]);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Return a copy of this object with the given options being removed
+ *
+ * @param string|array $keys The option or options to remove
+ *
+ * @return Params
+ */
+ public function without($keys = array())
+ {
+ $params = clone($this);
+ return $params->remove($keys);
+ }
+
+ /**
+ * Remove and return the value of the given option
+ *
+ * Called multiple times for an option with multiple values returns
+ * them one by one in case the default is not an array.
+ *
+ * @param string $key The option name
+ * @param mixed $default The default value to return
+ *
+ * @return mixed
+ */
+ public function shift($key = null, $default = null)
+ {
+ if ($key === null) {
+ if (count($this->standalone) > 0) {
+ return array_shift($this->standalone);
+ }
+ return $default;
+ }
+ $result = $this->get($key, $default);
+ if (is_array($result) && !is_array($default)) {
+ $result = array_shift($result) || $default;
+ if ($result === $default) {
+ $this->remove($key);
+ }
+ } else {
+ $this->remove($key);
+ }
+ return $result;
+ }
+
+ /**
+ * Require and remove a parameter
+ *
+ * @param string $name Name of the parameter
+ * @param bool $strict Whether the parameter's value must not be the empty string
+ *
+ * @return mixed
+ *
+ * @throws MissingParameterException If the parameter was not given
+ */
+ public function shiftRequired($name, $strict = true)
+ {
+ if ($this->has($name)) {
+ $value = $this->get($name);
+ if (! $strict || strlen($value) > 0) {
+ $this->shift($name);
+ return $value;
+ }
+ }
+ $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name);
+ $e->setParameter($name);
+ throw $e;
+ }
+
+ /**
+ * Put the given value onto the argument stack
+ *
+ * @param mixed $key The argument
+ *
+ * @return $this
+ */
+ public function unshift($key)
+ {
+ array_unshift($this->standalone, $key);
+ return $this;
+ }
+
+ /**
+ * Parse the given commandline
+ *
+ * @param array $argv The commandline to parse
+ *
+ * @return Params
+ */
+ public static function parse($argv = null)
+ {
+ if ($argv === null) {
+ $argv = $GLOBALS['argv'];
+ }
+ $params = new self($argv);
+ return $params;
+ }
+}
diff --git a/library/Icinga/Cli/Screen.php b/library/Icinga/Cli/Screen.php
new file mode 100644
index 0000000..4ffad72
--- /dev/null
+++ b/library/Icinga/Cli/Screen.php
@@ -0,0 +1,106 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Cli;
+
+use Icinga\Cli\AnsiScreen;
+
+class Screen
+{
+ protected static $instances = [];
+
+ protected $isUtf8;
+
+ public function getColumns()
+ {
+ $cols = (int) getenv('COLUMNS');
+ if (! $cols) {
+ // stty -a ?
+ $cols = (int) exec('tput cols');
+ }
+ if (! $cols) {
+ $cols = 80;
+ }
+ return $cols;
+ }
+
+ public function getRows()
+ {
+ $rows = (int) getenv('ROWS');
+ if (! $rows) {
+ // stty -a ?
+ $rows = (int) exec('tput lines');
+ }
+ if (! $rows) {
+ $rows = 25;
+ }
+ return $rows;
+ }
+
+ public function strlen($string)
+ {
+ return strlen($string);
+ }
+
+ public function newlines($count = 1)
+ {
+ return str_repeat("\n", $count);
+ }
+
+ public function center($txt)
+ {
+ $len = $this->strlen($txt);
+ $width = floor(($this->getColumns() + $len) / 2) - $len;
+ return str_repeat(' ', $width) . $txt;
+ }
+
+ public function hasUtf8()
+ {
+ if ($this->isUtf8 === null) {
+ // null should equal 0 here, however seems to equal '' on some systems:
+ $current = setlocale(LC_ALL, 0);
+
+ $parts = preg_split('/;/', $current);
+ $lc_parts = array();
+ foreach ($parts as $part) {
+ if (strpos($part, '=') === false) {
+ continue;
+ }
+ list($key, $val) = preg_split('/=/', $part, 2);
+ $lc_parts[$key] = $val;
+ }
+
+ $this->isUtf8 = array_key_exists('LC_CTYPE', $lc_parts)
+ && preg_match('~\.UTF-8$~i', $lc_parts['LC_CTYPE']);
+ }
+ return $this->isUtf8;
+ }
+
+ public function clear()
+ {
+ return "\n";
+ }
+
+ public function underline($text)
+ {
+ return $text;
+ }
+
+ public function colorize($text, $fgColor = null, $bgColor = null)
+ {
+ return $text;
+ }
+
+ public static function instance($output = STDOUT)
+ {
+ if (! isset(self::$instances[(int) $output])) {
+ if (function_exists('posix_isatty') && posix_isatty($output)) {
+ self::$instances[(int) $output] = new AnsiScreen();
+ } else {
+ self::$instances[(int) $output] = new Screen();
+ }
+ }
+
+ return self::$instances[(int) $output];
+ }
+}
diff --git a/library/Icinga/Common/Database.php b/library/Icinga/Common/Database.php
new file mode 100644
index 0000000..4c97765
--- /dev/null
+++ b/library/Icinga/Common/Database.php
@@ -0,0 +1,56 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Common;
+
+use Icinga\Application\Config as IcingaConfig;
+use Icinga\Data\ResourceFactory;
+use ipl\Sql\Config as SqlConfig;
+use ipl\Sql\Connection;
+use LogicException;
+use PDO;
+
+/**
+ * Trait for accessing the Icinga Web database
+ */
+trait Database
+{
+ /**
+ * Get a connection to the Icinga Web database
+ *
+ * @return Connection
+ *
+ * @throws \Icinga\Exception\ConfigurationError
+ */
+ protected function getDb()
+ {
+ if (! $this->hasDb()) {
+ throw new LogicException('Please check if a db instance exists at all');
+ }
+
+ $config = new SqlConfig(ResourceFactory::getResourceConfig(
+ IcingaConfig::app()->get('global', 'config_resource')
+ ));
+ if ($config->db === 'mysql') {
+ $config->charset = 'utf8mb4';
+ }
+
+ $config->options = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ];
+ if ($config->db === 'mysql') {
+ $config->options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION SQL_MODE='STRICT_TRANS_TABLES"
+ . ",NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
+ }
+
+ return new Connection($config);
+ }
+
+ /**
+ * Check if db exists
+ *
+ * @return bool true if a database was found otherwise false
+ */
+ protected function hasDb()
+ {
+ return (bool) IcingaConfig::app()->get('global', 'config_resource');
+ }
+}
diff --git a/library/Icinga/Common/PdfExport.php b/library/Icinga/Common/PdfExport.php
new file mode 100644
index 0000000..c3f10d4
--- /dev/null
+++ b/library/Icinga/Common/PdfExport.php
@@ -0,0 +1,105 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Common;
+
+use Icinga\Application\Icinga;
+use Icinga\Date\DateFormatter;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Pdfexport\PrintableHtmlDocument;
+use Icinga\Util\Environment;
+use Icinga\Web\Controller;
+use ipl\Html\Html;
+use ipl\Html\HtmlString;
+use ipl\Html\ValidHtml;
+use ipl\Web\Compat\CompatController;
+use ipl\Web\Url;
+
+trait PdfExport
+{
+ /** @var string The image to show in a pdf exports page header */
+ private $pdfHeaderImage = 'img/icinga-logo-big-dark.png';
+
+ /**
+ * Export the requested action to PDF and send it
+ *
+ * @return never
+ * @throws ConfigurationError If the pdfexport module is not available
+ */
+ protected function sendAsPdf()
+ {
+ if (! Icinga::app()->getModuleManager()->has('pdfexport')) {
+ throw new ConfigurationError('The pdfexport module is required for exports to PDF');
+ }
+
+ putenv('ICINGAWEB_EXPORT_FORMAT=pdf');
+ Environment::raiseMemoryLimit('512M');
+ Environment::raiseExecutionTime(300);
+
+ $time = DateFormatter::formatDateTime(time());
+ $iconPath = is_readable($this->pdfHeaderImage)
+ ? $this->pdfHeaderImage
+ : Icinga::app()->getBootstrapDirectory() . '/' . $this->pdfHeaderImage;
+ $encodedIcon = is_readable($iconPath) ? base64_encode(file_get_contents($iconPath)) : null;
+ $html = $this instanceof CompatController && ! empty($this->content)
+ ? $this->content
+ : $this->renderControllerAction();
+
+ $doc = (new PrintableHtmlDocument())
+ ->setTitle($this->view->title)
+ ->setHeader(Html::wantHtml([
+ Html::tag('span', ['class' => 'title']),
+ $encodedIcon
+ ? Html::tag('img', ['height' => 13, 'src' => 'data:image/png;base64,' . $encodedIcon])
+ : null,
+ Html::tag('time', null, $time)
+ ]))
+ ->setFooter(Html::wantHtml([
+ Html::tag('span', null, [
+ t('Page') . ' ',
+ Html::tag('span', ['class' => 'pageNumber']),
+ ' / ',
+ Html::tag('span', ['class' => 'totalPages'])
+ ]),
+ Html::tag('p', null, rawurldecode(Url::fromRequest()->setParams($this->params)))
+ ]))
+ ->addHtml($html);
+
+ if (($moduleName = $this->getRequest()->getModuleName()) !== 'default') {
+ $doc->getAttributes()->add('class', 'icinga-module module-' . $moduleName);
+ }
+
+ \Icinga\Module\Pdfexport\ProvidedHook\Pdfexport::first()->streamPdfFromHtml($doc, sprintf(
+ '%s-%s',
+ $this->view->title ?: $this->getRequest()->getActionName(),
+ $time
+ ));
+ }
+
+ /**
+ * Render the requested action
+ *
+ * @return ValidHtml
+ */
+ protected function renderControllerAction()
+ {
+ /** @var Controller $this */
+ $this->view->compact = true;
+
+ $viewRenderer = $this->getHelper('viewRenderer');
+ $viewRenderer->postDispatch();
+
+ $layoutHelper = $this->getHelper('layout');
+ $oldLayout = $layoutHelper->getLayout();
+ $layout = $layoutHelper->setLayout('inline');
+
+ $layout->content = $this->getResponse();
+ $html = $layout->render();
+
+ // Restore previous layout and reset content, to properly show errors
+ $this->getResponse()->clearBody($viewRenderer->getResponseSegment());
+ $layoutHelper->setLayout($oldLayout);
+
+ return HtmlString::create($html);
+ }
+}
diff --git a/library/Icinga/Crypt/AesCrypt.php b/library/Icinga/Crypt/AesCrypt.php
new file mode 100644
index 0000000..8e9d453
--- /dev/null
+++ b/library/Icinga/Crypt/AesCrypt.php
@@ -0,0 +1,337 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Crypt;
+
+use UnexpectedValueException;
+use RuntimeException;
+
+/**
+ * Data encryption and decryption using symmetric algorithm
+ *
+ * # Example Usage
+ *
+ * ```php
+ *
+ * // Encryption
+ * $encryptedData = (new AesCrypt())->encrypt($data); // Accepts a string
+ *
+ *
+ * // Encrypt and encode to Base64
+ * $encryptedData = (new AesCrypt())->encryptToBase64($data); // Accepts a string
+ *
+ *
+ * // Decryption
+ * $aesCrypt = (new AesCrypt())
+ * ->setTag($tag) // if exists
+ * ->setIV($iv)
+ * ->setKey($key);
+ *
+ * $decryptedData = $aesCrypt->decrypt($data);
+ *
+ * // Decode from Base64 and decrypt
+ * $aesCrypt = (new AesCrypt())
+ * ->setTag($tag)
+ * ->setIV($iv)
+ * ->setKey($key);
+ *
+ * $decryptedData = $aesCrypt->decryptFromBase64($data);
+ * ```
+ *
+ */
+class AesCrypt
+{
+ /** @var array The list of cipher methods */
+ const METHODS = [
+ 'aes-256-gcm',
+ 'aes-256-cbc',
+ 'aes-256-ctr'
+ ];
+
+ /** @var string The encryption key */
+ private $key;
+
+ /** @var int The length of the key */
+ private $keyLength;
+
+ /** @var string The initialization vector which is not NULL */
+ private $iv;
+
+ /** @var string The authentication tag which is passed by reference when using AEAD cipher mode */
+ private $tag;
+
+ /** @var string The cipher method */
+ private $method;
+
+ public function __construct($keyLength = 128)
+ {
+ $this->keyLength = $keyLength;
+ }
+
+ /**
+ * Set the method
+ *
+ * @return $this
+ */
+ public function setMethod($method)
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ /**
+ * Get the method
+ *
+ * @return string
+ */
+ public function getMethod()
+ {
+ if ($this->method === null) {
+ $this->method = $this->getSupportedMethod();
+ }
+
+ return $this->method;
+ }
+
+ /**
+ * Get supported method
+ *
+ * @return string
+ *
+ * @throws RuntimeException If none of the methods listed in the METHODS array is available
+ */
+ protected function getSupportedMethod()
+ {
+ $availableMethods = openssl_get_cipher_methods();
+ $methods = self::METHODS;
+
+ if (! $this->isAuthenticatedEncryptionSupported()) {
+ unset($methods[0]);
+ }
+
+ foreach ($methods as $method) {
+ if (in_array($method, $availableMethods)) {
+ return $method;
+ }
+ }
+
+ throw new RuntimeException('No supported method found');
+ }
+
+ /**
+ * Set the key
+ *
+ * @return $this
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+
+ return $this;
+ }
+
+ /**
+ * Get the key
+ *
+ * @return string
+ *
+ */
+ public function getKey()
+ {
+ if (empty($this->key)) {
+ $this->key = random_bytes($this->keyLength);
+ }
+
+ return $this->key;
+ }
+
+ /**
+ * Set the IV
+ *
+ * @return $this
+ */
+ public function setIV($iv)
+ {
+ $this->iv = $iv;
+
+ return $this;
+ }
+
+ /**
+ * Get the IV
+ *
+ * @return string
+ *
+ */
+ public function getIV()
+ {
+ if (empty($this->iv)) {
+ $len = openssl_cipher_iv_length($this->getMethod());
+ $this->iv = random_bytes($len);
+ }
+
+ return $this->iv;
+ }
+
+ /**
+ * Set the Tag
+ *
+ * @return $this
+ *
+ * @throws RuntimeException If a tag is available but authenticated encryption (AE) is not supported.
+ *
+ * @throws UnexpectedValueException If tag length is less then 16
+ */
+ public function setTag($tag)
+ {
+ if (! $this->isAuthenticatedEncryptionSupported()) {
+ throw new RuntimeException(sprintf(
+ "The given decryption method is not supported in php version '%s'",
+ PHP_VERSION
+ ));
+ }
+
+ if (strlen($tag) !== 16) {
+ throw new UnexpectedValueException(sprintf(
+ 'expects tag length to be 16, got instead %s',
+ strlen($tag)
+ ));
+ }
+
+ $this->tag = $tag;
+
+ return $this;
+ }
+
+ /**
+ * Get the Tag
+ *
+ * @return string
+ *
+ * @throws RuntimeException If the Tag is not set
+ */
+ public function getTag()
+ {
+ if (empty($this->tag)) {
+ throw new RuntimeException('No tag set');
+ }
+
+ return $this->tag;
+ }
+
+ /**
+ * Decrypt the given string
+ *
+ * @param string $data
+ *
+ * @return string
+ *
+ * @throws RuntimeException If decryption fails
+ */
+ public function decrypt($data)
+ {
+ if (! $this->isAuthenticatedEncryptionRequired()) {
+ return $this->nonAEDecrypt($data);
+ }
+
+ $decrypt = openssl_decrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV(), $this->getTag());
+
+ if ($decrypt === false) {
+ throw new RuntimeException('Decryption failed');
+ }
+
+ return $decrypt;
+ }
+
+ /**
+ * Encrypt the given string
+ *
+ * @param string $data
+ *
+ * @return string encrypted string
+ *
+ * @throws RuntimeException If decryption fails
+ */
+ public function encrypt($data)
+ {
+ if (! $this->isAuthenticatedEncryptionRequired()) {
+ return $this->nonAEEncrypt($data);
+ }
+
+ $encrypt = openssl_encrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV(), $this->tag);
+
+ if ($encrypt === false) {
+ throw new RuntimeException('Encryption failed');
+ }
+
+ return $encrypt;
+ }
+
+ /**
+ * Decrypt the given string with non Authenticated encryption (AE) cipher method
+ *
+ * @param string $data
+ *
+ * @return string decrypted string
+ *
+ * @throws RuntimeException If decryption fails
+ */
+ private function nonAEDecrypt($data)
+ {
+ $c = base64_decode($data);
+ $hmac = substr($c, 0, 32);
+ $data = substr($c, 32);
+
+ $decrypt = openssl_decrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV());
+ $calcHmac = hash_hmac('sha256', $this->getIV() . $data, $this->getKey(), true);
+
+ if ($decrypt === false || ! hash_equals($hmac, $calcHmac)) {
+ throw new RuntimeException('Decryption failed');
+ }
+
+ return $decrypt;
+ }
+
+ /**
+ * Encrypt the given string with non Authenticated encryption (AE) cipher method
+ *
+ * @param string $data
+ *
+ * @return string encrypted string
+ *
+ * @throws RuntimeException If encryption fails
+ */
+ private function nonAEEncrypt($data)
+ {
+ $encrypt = openssl_encrypt($data, $this->getMethod(), $this->getKey(), 0, $this->getIV());
+
+ if ($encrypt === false) {
+ throw new RuntimeException('Encryption failed');
+ }
+
+ $hmac = hash_hmac('sha256', $this->getIV() . $encrypt, $this->getKey(), true);
+
+ return base64_encode($hmac . $encrypt);
+ }
+
+ /**
+ * Whether the Authenticated encryption (a tag) is required
+ *
+ * @return bool True if required false otherwise
+ */
+ public function isAuthenticatedEncryptionRequired()
+ {
+ return $this->getMethod() === 'aes-256-gcm';
+ }
+
+ /**
+ * Whether the php version supports Authenticated encryption (AE) or not
+ *
+ * @return bool True if supported false otherwise
+ */
+ public function isAuthenticatedEncryptionSupported()
+ {
+ return PHP_VERSION_ID >= 70100;
+ }
+}
diff --git a/library/Icinga/Data/ConfigObject.php b/library/Icinga/Data/ConfigObject.php
new file mode 100644
index 0000000..c9a3134
--- /dev/null
+++ b/library/Icinga/Data/ConfigObject.php
@@ -0,0 +1,289 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Iterator;
+use ArrayAccess;
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Container for configuration values
+ */
+class ConfigObject extends ArrayDatasource implements Iterator, ArrayAccess
+{
+ /**
+ * Create a new config
+ *
+ * @param array $data The data to initialize the new config with
+ */
+ public function __construct(array $data = array())
+ {
+ // Convert all embedded arrays to ConfigObjects as well
+ foreach ($data as & $value) {
+ if (is_array($value)) {
+ $value = new static($value);
+ }
+ }
+
+ parent::__construct($data);
+ }
+
+ /**
+ * Deep clone this config
+ */
+ public function __clone()
+ {
+ $array = array();
+ foreach ($this->data as $key => $value) {
+ if ($value instanceof self) {
+ $array[$key] = clone $value;
+ } else {
+ $array[$key] = $value;
+ }
+ }
+
+ $this->data = $array;
+ }
+
+ /**
+ * Reset the current position of $this->data
+ *
+ * @return void
+ */
+ public function rewind(): void
+ {
+ reset($this->data);
+ }
+
+ /**
+ * Return the section's or property's value of the current iteration
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return current($this->data);
+ }
+
+ /**
+ * Return whether the position of the current iteration is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return key($this->data) !== null;
+ }
+
+ /**
+ * Return the section's or property's name of the current iteration
+ *
+ * @return string
+ */
+ public function key(): string
+ {
+ return key($this->data);
+ }
+
+ /**
+ * Advance the position of the current iteration and return the new section's or property's value
+ *
+ * @return void
+ */
+ public function next(): void
+ {
+ next($this->data);
+ }
+
+ /**
+ * Return whether the given section or property is set
+ *
+ * @param string $key The name of the section or property
+ *
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return isset($this->data[$key]);
+ }
+
+ /**
+ * Return the value for the given property or the config for the given section
+ *
+ * @param string $key The name of the property or section
+ *
+ * @return mixed|NULL The value or NULL in case $key does not exist
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Add a new property or section
+ *
+ * @param string $key The name of the new property or section
+ * @param mixed $value The value to set for the new property or section
+ */
+ public function __set($key, $value)
+ {
+ if (is_array($value)) {
+ $this->data[$key] = new static($value);
+ } else {
+ $this->data[$key] = $value;
+ }
+ }
+
+ /**
+ * Remove the given property or section
+ *
+ * @param string $key The property or section to remove
+ */
+ public function __unset($key)
+ {
+ unset($this->data[$key]);
+ }
+
+ /**
+ * Return whether the given section or property is set
+ *
+ * @param string $key The name of the section or property
+ *
+ * @return bool
+ */
+ public function offsetExists($key): bool
+ {
+ return isset($this->$key);
+ }
+
+ /**
+ * Return the value for the given property or the config for the given section
+ *
+ * @param string $key The name of the property or section
+ *
+ * @return ?mixed The value or NULL in case $key does not exist
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Add a new property or section
+ *
+ * @param string $key The name of the new property or section
+ * @param mixed $value The value to set for the new property or section
+ *
+ * @throws ProgrammingError If the key is null
+ */
+ public function offsetSet($key, $value): void
+ {
+ if ($key === null) {
+ throw new ProgrammingError('Appending values without an explicit key is not supported');
+ }
+
+ $this->$key = $value;
+ }
+
+ /**
+ * Remove the given property or section
+ *
+ * @param string $key The property or section to remove
+ */
+ public function offsetUnset($key): void
+ {
+ unset($this->$key);
+ }
+
+ /**
+ * Return whether this config has any data
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->data);
+ }
+
+ /**
+ * Return the value for the given property or the config for the given section
+ *
+ * @param string $key The name of the property or section
+ * @param mixed $default The value to return in case the property or section is missing
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if (array_key_exists($key, $this->data)) {
+ return $this->data[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return all section and property names
+ *
+ * @return array
+ */
+ public function keys()
+ {
+ return array_keys($this->data);
+ }
+
+ /**
+ * Return this config's data as associative array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $array = array();
+ foreach ($this->data as $key => $value) {
+ if ($value instanceof self) {
+ $array[$key] = $value->toArray();
+ } else {
+ $array[$key] = $value;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Merge the given data with this config
+ *
+ * @param array|ConfigObject $data An array or a config
+ *
+ * @return $this
+ */
+ public function merge($data)
+ {
+ if ($data instanceof self) {
+ $data = $data->toArray();
+ }
+
+ foreach ($data as $key => $value) {
+ if (array_key_exists($key, $this->data)) {
+ if (is_array($value)) {
+ if ($this->data[$key] instanceof self) {
+ $this->data[$key]->merge($value);
+ } else {
+ $this->data[$key] = new static($value);
+ }
+ } else {
+ $this->data[$key] = $value;
+ }
+ } else {
+ $this->data[$key] = is_array($value) ? new static($value) : $value;
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Icinga/Data/ConnectionInterface.php b/library/Icinga/Data/ConnectionInterface.php
new file mode 100644
index 0000000..bd7d026
--- /dev/null
+++ b/library/Icinga/Data/ConnectionInterface.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+interface ConnectionInterface extends Selectable, Queryable
+{
+}
diff --git a/library/Icinga/Data/DataArray/ArrayDatasource.php b/library/Icinga/Data/DataArray/ArrayDatasource.php
new file mode 100644
index 0000000..e300616
--- /dev/null
+++ b/library/Icinga/Data/DataArray/ArrayDatasource.php
@@ -0,0 +1,292 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\DataArray;
+
+use ArrayIterator;
+use Icinga\Data\Selectable;
+use Icinga\Data\SimpleQuery;
+
+class ArrayDatasource implements Selectable
+{
+ /**
+ * The array being used as data source
+ *
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * The current result
+ *
+ * @var array
+ */
+ protected $result;
+
+ /**
+ * The result of a counted query
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * The name of the column to map array keys on
+ *
+ * In case the array being used as data source provides keys of type string,this name
+ * will be used to set such as column on each row, if the column is not set already.
+ *
+ * @var string
+ */
+ protected $keyColumn;
+
+ /**
+ * Create a new data source for the given array
+ *
+ * @param array $data The array you're going to use as a data source
+ */
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Set the name of the column to map array keys on
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setKeyColumn($name)
+ {
+ $this->keyColumn = $name;
+ return $this;
+ }
+
+ /**
+ * Return the name of the column to map array keys on
+ *
+ * @return string
+ */
+ public function getKeyColumn()
+ {
+ return $this->keyColumn;
+ }
+
+ /**
+ * Provide a query for this data source
+ *
+ * @return SimpleQuery
+ */
+ public function select()
+ {
+ return new SimpleQuery(clone $this);
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result set using an iterator
+ *
+ * @param SimpleQuery $query
+ *
+ * @return ArrayIterator
+ */
+ public function query(SimpleQuery $query)
+ {
+ return new ArrayIterator($this->fetchAll($query));
+ }
+
+ /**
+ * Fetch and return a column of all rows of the result set as an array
+ *
+ * @param SimpleQuery $query
+ *
+ * @return array
+ */
+ public function fetchColumn(SimpleQuery $query)
+ {
+ $result = array();
+ foreach ($this->getResult($query) as $row) {
+ $arr = (array) $row;
+ $result[] = array_shift($arr);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result as a flattened key/value based array
+ *
+ * @param SimpleQuery $query
+ *
+ * @return array
+ */
+ public function fetchPairs(SimpleQuery $query)
+ {
+ $result = array();
+ $keys = null;
+ foreach ($this->getResult($query) as $row) {
+ if ($keys === null) {
+ $keys = array_keys((array) $row);
+ if (count($keys) < 2) {
+ $keys[1] = $keys[0];
+ }
+ }
+
+ $result[$row->{$keys[0]}] = $row->{$keys[1]};
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch and return the first row of the given query's result
+ *
+ * @param SimpleQuery $query
+ *
+ * @return object|false The row or false in case the result is empty
+ */
+ public function fetchRow(SimpleQuery $query)
+ {
+ $result = $this->getResult($query);
+ if (empty($result)) {
+ return false;
+ }
+
+ return array_shift($result);
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result as an array
+ *
+ * @param SimpleQuery $query
+ *
+ * @return array
+ */
+ public function fetchAll(SimpleQuery $query)
+ {
+ return $this->getResult($query);
+ }
+
+ /**
+ * Count all rows of the given query's result
+ *
+ * @param SimpleQuery $query
+ *
+ * @return int
+ */
+ public function count(SimpleQuery $query)
+ {
+ if ($this->count === null) {
+ $this->count = count($this->createResult($query));
+ }
+
+ return $this->count;
+ }
+
+ /**
+ * Create and return the result for the given query
+ *
+ * @param SimpleQuery $query
+ *
+ * @return array
+ */
+ protected function createResult(SimpleQuery $query)
+ {
+ $columns = $query->getColumns();
+ $filter = $query->getFilter();
+ $offset = $query->hasOffset() ? $query->getOffset() : 0;
+ $limit = $query->hasLimit() ? $query->getLimit() : 0;
+ $data = $this->data;
+
+ if ($query->hasOrder()) {
+ uasort($data, [$query, 'compare']);
+ }
+
+ $foundStringKey = false;
+ $result = [];
+ $skipped = 0;
+ foreach ($data as $key => $row) {
+ if ($this->keyColumn !== null && !isset($row->{$this->keyColumn})) {
+ $row = clone $row; // Make sure that this won't affect the actual data
+ $row->{$this->keyColumn} = $key;
+ }
+
+ if (! $filter->matches($row)) {
+ continue;
+ } elseif ($skipped < $offset) {
+ $skipped++;
+ continue;
+ }
+
+ // Get only desired columns if asked so
+ if (! empty($columns)) {
+ $filteredRow = (object) array();
+ foreach ($columns as $alias => $name) {
+ if (! is_string($alias)) {
+ $alias = $name;
+ }
+
+ if (isset($row->$name)) {
+ $filteredRow->$alias = $row->$name;
+ } else {
+ $filteredRow->$alias = null;
+ }
+ }
+ } else {
+ $filteredRow = $row;
+ }
+
+ $foundStringKey |= is_string($key);
+ $result[$key] = $filteredRow;
+
+ if (count($result) === $limit) {
+ break;
+ }
+ }
+
+ if (! $foundStringKey) {
+ $result = array_values($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return whether a query result exists
+ *
+ * @return bool
+ */
+ protected function hasResult()
+ {
+ return $this->result !== null;
+ }
+
+ /**
+ * Set the current result
+ *
+ * @param array $result
+ *
+ * @return $this
+ */
+ protected function setResult(array $result)
+ {
+ $this->result = $result;
+ return $this;
+ }
+
+ /**
+ * Return the result for the given query
+ *
+ * @param SimpleQuery $query
+ *
+ * @return array
+ */
+ protected function getResult(SimpleQuery $query)
+ {
+ if (! $this->hasResult()) {
+ $this->setResult($this->createResult($query));
+ }
+
+ return $this->result;
+ }
+}
diff --git a/library/Icinga/Data/Db/DbConnection.php b/library/Icinga/Data/Db/DbConnection.php
new file mode 100644
index 0000000..fc6814d
--- /dev/null
+++ b/library/Icinga/Data/Db/DbConnection.php
@@ -0,0 +1,655 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Db;
+
+use DateTime;
+use DateTimeZone;
+use Exception;
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Data\Filter\FilterNotEqual;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use PDO;
+use Iterator;
+use Zend_Db;
+use Zend_Db_Expr;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Extensible;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterAnd;
+use Icinga\Data\Filter\FilterNot;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\Reducible;
+use Icinga\Data\ResourceFactory;
+use Icinga\Data\Selectable;
+use Icinga\Data\Updatable;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Encapsulate database connections and query creation
+ */
+class DbConnection implements Selectable, Extensible, Updatable, Reducible, Inspectable
+{
+ /**
+ * Connection config
+ *
+ * @var ConfigObject
+ */
+ private $config;
+
+ /**
+ * Database type
+ *
+ * @var string
+ */
+ private $dbType;
+
+ /**
+ * @var \Zend_Db_Adapter_Abstract
+ */
+ private $dbAdapter;
+
+ /**
+ * Table prefix
+ *
+ * @var string
+ */
+ private $tablePrefix = '';
+
+ private static $genericAdapterOptions = array(
+ Zend_Db::AUTO_QUOTE_IDENTIFIERS => false,
+ Zend_Db::CASE_FOLDING => Zend_Db::CASE_LOWER
+ );
+
+ private static $driverOptions = array(
+ PDO::ATTR_TIMEOUT => 10,
+ PDO::ATTR_CASE => PDO::CASE_LOWER,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
+ );
+
+ /**
+ * Create a new connection object
+ *
+ * @param ConfigObject $config
+ */
+ public function __construct(ConfigObject $config = null)
+ {
+ $this->config = $config;
+ $this->connect();
+ }
+
+ /**
+ * Provide a query on this connection
+ *
+ * @return DbQuery
+ */
+ public function select()
+ {
+ return new DbQuery($this);
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result set using an iterator
+ *
+ * @param DbQuery $query
+ *
+ * @return Iterator
+ */
+ public function query(DbQuery $query)
+ {
+ return $query->getSelectQuery()->query();
+ }
+
+ /**
+ * Get the connection configuration
+ *
+ * @return ConfigObject
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Getter for database type
+ *
+ * @return string
+ */
+ public function getDbType()
+ {
+ return $this->dbType;
+ }
+
+ /**
+ * Getter for the Zend_Db_Adapter
+ *
+ * @return \Zend_Db_Adapter_Abstract
+ */
+ public function getDbAdapter()
+ {
+ return $this->dbAdapter;
+ }
+
+ /**
+ * Create a new connection
+ */
+ private function connect()
+ {
+ $genericAdapterOptions = self::$genericAdapterOptions;
+ $driverOptions = self::$driverOptions;
+ $adapterParamaters = array(
+ 'host' => $this->config->host,
+ 'username' => $this->config->username,
+ 'password' => $this->config->password,
+ 'dbname' => $this->config->dbname,
+ 'charset' => $this->config->charset ?: null,
+ 'options' => & $genericAdapterOptions,
+ 'driver_options' => & $driverOptions
+ );
+ $this->dbType = strtolower($this->config->get('db', 'mysql'));
+ switch ($this->dbType) {
+ case 'mssql':
+ $adapter = 'Pdo_Mssql';
+ $pdoType = $this->config->get('pdoType');
+ if (empty($pdoType)) {
+ if (extension_loaded('sqlsrv')) {
+ $adapter = 'Sqlsrv';
+ } else {
+ $pdoType = 'dblib';
+ }
+ }
+ if ($pdoType === 'dblib') {
+ // Driver does not support setting attributes
+ unset($adapterParamaters['options']);
+ unset($adapterParamaters['driver_options']);
+ }
+ if (! empty($pdoType)) {
+ $adapterParamaters['pdoType'] = $pdoType;
+ }
+ $defaultPort = 1433;
+ break;
+ case 'mysql':
+ $adapter = 'Pdo_Mysql';
+ if ($this->config->use_ssl) {
+ # The presence of these keys as empty strings or null cause non-ssl connections to fail
+ if ($this->config->ssl_key) {
+ $adapterParamaters['driver_options'][PDO::MYSQL_ATTR_SSL_KEY] = $this->config->ssl_key;
+ }
+ if ($this->config->ssl_cert) {
+ $adapterParamaters['driver_options'][PDO::MYSQL_ATTR_SSL_CERT] = $this->config->ssl_cert;
+ }
+ if ($this->config->ssl_ca) {
+ $adapterParamaters['driver_options'][PDO::MYSQL_ATTR_SSL_CA] = $this->config->ssl_ca;
+ }
+ if ($this->config->ssl_capath) {
+ $adapterParamaters['driver_options'][PDO::MYSQL_ATTR_SSL_CAPATH] = $this->config->ssl_capath;
+ }
+ if ($this->config->ssl_cipher) {
+ $adapterParamaters['driver_options'][PDO::MYSQL_ATTR_SSL_CIPHER] = $this->config->ssl_cipher;
+ }
+ if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && $this->config->ssl_do_not_verify_server_cert
+ ) {
+ $adapterParamaters['driver_options'][PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
+ }
+ }
+ /*
+ * Set MySQL server SQL modes to behave as closely as possible to Oracle and PostgreSQL. Note that the
+ * ONLY_FULL_GROUP_BY mode is left on purpose because MySQL requires you to specify all non-aggregate
+ * columns in the group by list even if the query is grouped by the master table's primary key which is
+ * valid ANSI SQL though. Further in that case the query plan would suffer if you add more columns to
+ * the group by list.
+ */
+ $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] =
+ 'SET SESSION SQL_MODE=\'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,'
+ . 'ANSI_QUOTES,PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION\'';
+ if (isset($adapterParamaters['charset'])) {
+ $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .= ', NAMES ' . $adapterParamaters['charset'];
+ if (trim($adapterParamaters['charset']) === 'latin1') {
+ // Required for MySQL 8+ because we need PIPES_AS_CONCAT and
+ // have several columns with explicit COLLATE instructions
+ $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .= ' COLLATE latin1_general_ci';
+ }
+
+ unset($adapterParamaters['charset']);
+ }
+
+ $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .= ", time_zone='" . $this->defaultTimezoneOffset() . "'";
+ $driverOptions[PDO::MYSQL_ATTR_INIT_COMMAND] .=';';
+ $defaultPort = 3306;
+ break;
+ case 'oci':
+ $adapter = 'Oracle';
+ unset($adapterParamaters['options']);
+ unset($adapterParamaters['driver_options']);
+ $adapterParamaters['driver_options'] = array(
+ 'lob_as_string' => true
+ );
+ $defaultPort = 1521;
+ break;
+ case 'oracle':
+ $adapter = 'Pdo_Oci';
+ $defaultPort = 1521;
+
+ // remove host parameter when not configured
+ if (empty($this->config->host)) {
+ unset($adapterParamaters['host']);
+ }
+ break;
+ case 'pgsql':
+ $adapter = 'Pdo_Pgsql';
+ $defaultPort = 5432;
+ break;
+ case 'ibm':
+ $adapter = 'Pdo_Ibm';
+ $defaultPort = 50000;
+ break;
+ case 'sqlite':
+ $adapter = 'Pdo_Sqlite';
+ $defaultPort = 0; // Dummy port because a value is required
+ break;
+ default:
+ throw new ConfigurationError(
+ 'Backend "%s" is not supported',
+ $this->dbType
+ );
+ }
+ $adapterParamaters['port'] = $this->config->get('port', $defaultPort);
+ $this->dbAdapter = Zend_Db::factory($adapter, $adapterParamaters);
+ $this->dbAdapter->setFetchMode(Zend_Db::FETCH_OBJ);
+ // TODO(el/tg): The profiler is disabled per default, why do we disable the profiler explicitly?
+ $this->dbAdapter->getProfiler()->setEnabled(false);
+ }
+
+ public static function fromResourceName($name)
+ {
+ return new static(ResourceFactory::getResourceConfig($name));
+ }
+
+ /**
+ * Getter for the table prefix
+ *
+ * @return string
+ */
+ public function getTablePrefix()
+ {
+ return $this->tablePrefix;
+ }
+
+ /**
+ * Setter for the table prefix
+ *
+ * @param string $prefix
+ *
+ * @return $this
+ */
+ public function setTablePrefix($prefix)
+ {
+ $this->tablePrefix = $prefix;
+ return $this;
+ }
+
+ /**
+ * Get offset from the current default timezone to GMT
+ *
+ * @return string
+ */
+ protected function defaultTimezoneOffset()
+ {
+ $tz = new DateTimeZone(date_default_timezone_get());
+ $offset = $tz->getOffset(new DateTime());
+ $prefix = $offset >= 0 ? '+' : '-';
+ $offset = abs($offset);
+ $hours = (int) floor($offset / 3600);
+ $minutes = (int) floor(($offset % 3600) / 60);
+ return sprintf('%s%d:%02d', $prefix, $hours, $minutes);
+ }
+
+ /**
+ * Count all rows of the result set
+ *
+ * @param DbQuery $query
+ *
+ * @return int
+ */
+ public function count(DbQuery $query)
+ {
+ return (int) $this->dbAdapter->fetchOne($query->getCountQuery());
+ }
+
+ /**
+ * Retrieve an array containing all rows of the result set
+ *
+ * @param DbQuery $query
+ *
+ * @return array
+ */
+ public function fetchAll(DbQuery $query)
+ {
+ return $this->dbAdapter->fetchAll($query->getSelectQuery());
+ }
+
+ /**
+ * Fetch the first row of the result set
+ *
+ * @param DbQuery $query
+ *
+ * @return mixed
+ */
+ public function fetchRow(DbQuery $query)
+ {
+ return $this->dbAdapter->fetchRow($query->getSelectQuery());
+ }
+
+ /**
+ * Fetch the first column of all rows of the result set as an array
+ *
+ * @param DbQuery $query
+ *
+ * @return array
+ */
+ public function fetchColumn(DbQuery $query)
+ {
+ return $this->dbAdapter->fetchCol($query->getSelectQuery());
+ }
+
+ /**
+ * Fetch the first column of the first row of the result set
+ *
+ * @param DbQuery $query
+ *
+ * @return string
+ */
+ public function fetchOne(DbQuery $query)
+ {
+ return $this->dbAdapter->fetchOne($query->getSelectQuery());
+ }
+
+ /**
+ * Fetch all rows of the result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
+ *
+ * @param DbQuery $query
+ *
+ * @return array
+ */
+ public function fetchPairs(DbQuery $query)
+ {
+ return $this->dbAdapter->fetchPairs($query->getSelectQuery());
+ }
+
+ /**
+ * Insert a table row with the given data
+ *
+ * Note that the base implementation does not perform any quoting on the $table argument.
+ * Pass an array with a column name (the same as in $bind) and a PDO::PARAM_* constant as value
+ * as third parameter $types to define a different type than string for a particular column.
+ *
+ * @param string $table
+ * @param array $bind
+ * @param array $types
+ *
+ * @return int The number of affected rows
+ */
+ public function insert($table, array $bind, array $types = array())
+ {
+ $columns = $values = array();
+ foreach ($bind as $column => $value) {
+ $columns[] = $column;
+ if ($value instanceof Zend_Db_Expr) {
+ $values[] = (string) $value;
+ unset($bind[$column]);
+ } else {
+ $values[] = ':' . $column;
+ }
+ }
+
+ $sql = 'INSERT INTO ' . $table
+ . ' (' . join(', ', $columns) . ') '
+ . 'VALUES (' . join(', ', $values) . ')';
+ $statement = $this->dbAdapter->prepare($sql);
+
+ foreach ($bind as $column => $value) {
+ $type = isset($types[$column]) ? $types[$column] : PDO::PARAM_STR;
+ $statement->bindValue(':' . $column, $value, $type);
+ }
+
+ $statement->execute();
+ return $statement->rowCount();
+ }
+
+ /**
+ * Update table rows with the given data, optionally limited by using a filter
+ *
+ * Note that the base implementation does not perform any quoting on the $table argument.
+ * Pass an array with a column name (the same as in $bind) and a PDO::PARAM_* constant as value
+ * as fourth parameter $types to define a different type than string for a particular column.
+ *
+ * @param string $table
+ * @param array $bind
+ * @param Filter $filter
+ * @param array $types
+ *
+ * @return int The number of affected rows
+ */
+ public function update($table, array $bind, Filter $filter = null, array $types = array())
+ {
+ $set = array();
+ foreach ($bind as $column => $value) {
+ if ($value instanceof Zend_Db_Expr) {
+ $set[] = $column . ' = ' . $value;
+ unset($bind[$column]);
+ } else {
+ $set[] = $column . ' = :' . $column;
+ }
+ }
+
+ $sql = 'UPDATE ' . $table
+ . ' SET ' . join(', ', $set)
+ . ($filter ? ' WHERE ' . $this->renderFilter($filter) : '');
+ $statement = $this->dbAdapter->prepare($sql);
+
+ foreach ($bind as $column => $value) {
+ $type = isset($types[$column]) ? $types[$column] : PDO::PARAM_STR;
+ $statement->bindValue(':' . $column, $value, $type);
+ }
+
+ $statement->execute();
+ return $statement->rowCount();
+ }
+
+ /**
+ * Delete table rows, optionally limited by using a filter
+ *
+ * @param string $table
+ * @param Filter $filter
+ *
+ * @return int The number of affected rows
+ */
+ public function delete($table, Filter $filter = null)
+ {
+ return $this->dbAdapter->delete($table, $filter ? $this->renderFilter($filter) : '');
+ }
+
+ /**
+ * Render and return the given filter as SQL-WHERE clause
+ *
+ * @param Filter $filter
+ *
+ * @return string
+ */
+ public function renderFilter(Filter $filter, $level = 0)
+ {
+ // TODO: This is supposed to supersede DbQuery::renderFilter()
+ $where = '';
+ if ($filter->isChain()) {
+ if ($filter instanceof FilterAnd) {
+ $operator = ' AND ';
+ } elseif ($filter instanceof FilterOr) {
+ $operator = ' OR ';
+ } elseif ($filter instanceof FilterNot) {
+ $operator = ' AND ';
+ $where .= ' NOT ';
+ } else {
+ throw new ProgrammingError('Cannot render filter: %s', get_class($filter));
+ }
+
+ if (! $filter->isEmpty()) {
+ $parts = array();
+ foreach ($filter->filters() as $filterPart) {
+ $part = $this->renderFilter($filterPart, $level + 1);
+ if ($part) {
+ $parts[] = $part;
+ }
+ }
+
+ if (! empty($parts)) {
+ if ($level > 0) {
+ $where .= ' (' . implode($operator, $parts) . ') ';
+ } else {
+ $where .= implode($operator, $parts);
+ }
+ }
+ } else {
+ return ''; // Explicitly return the empty string due to the FilterNot case
+ }
+ } else {
+ $where .= $this->renderFilterExpression($filter);
+ }
+
+ return $where;
+ }
+
+ /**
+ * Render and return the given filter expression
+ *
+ * @param Filter $filter
+ *
+ * @return string
+ */
+ protected function renderFilterExpression(Filter $filter)
+ {
+ $column = $filter->getColumn();
+ $sign = $filter->getSign();
+ $value = $filter->getExpression();
+
+ if (is_array($value)) {
+ $comp = [];
+ $pattern = [];
+ foreach ($value as $val) {
+ if (strpos($val, '*') === false) {
+ $comp[] = $val;
+ } else {
+ $pattern[] = $this->renderFilterExpression(Filter::expression($column, $sign, $val));
+ }
+ }
+
+ $sql = $pattern;
+ if ($sign === '=') {
+ if (! empty($comp)) {
+ $sql[] = $column . ' IN (' . $this->dbAdapter->quote($comp) . ')';
+ }
+
+ $operator = 'OR';
+ } elseif ($sign === '!=') {
+ if (! empty($comp)) {
+ $sql[] = sprintf(
+ '(%1$s NOT IN (%2$s) OR %1$s IS NULL)',
+ $column,
+ $this->dbAdapter->quote($comp)
+ );
+ }
+
+ $operator = 'AND';
+ } else {
+ throw new ProgrammingError(
+ 'Unable to render array expressions with operators other than equal or not equal'
+ );
+ }
+
+ return count($sql) === 1 ? $sql[0] : '(' . implode(" $operator ", $sql) . ')';
+ } elseif ($sign === '='
+ && ! $filter instanceof FilterEqual
+ && $value !== null
+ && strpos($value, '*') !== false
+ ) {
+ if ($value === '*') {
+ return $column . ' IS NOT NULL';
+ }
+
+ return $column . ' LIKE ' . $this->dbAdapter->quote(preg_replace('~\*~', '%', $value));
+ } elseif ($sign === '!='
+ && ! $filter instanceof FilterNotEqual
+ && $value !== null
+ && strpos($value, '*') !== false
+ ) {
+ if ($value === '*') {
+ return $column . ' IS NULL';
+ }
+
+ return sprintf(
+ '(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
+ $column,
+ $this->dbAdapter->quote(preg_replace('~\*~', '%', $value))
+ );
+ } elseif ($sign === '!=') {
+ return sprintf('(%1$s != %2$s OR %1$s IS NULL)', $column, $this->dbAdapter->quote($value));
+ } else {
+ return sprintf('%s %s %s', $column, $sign, $this->dbAdapter->quote($value));
+ }
+ }
+
+ public function inspect()
+ {
+ $insp = new Inspection('Db Connection');
+ try {
+ $this->getDbAdapter()->getConnection();
+ $config = $this->dbAdapter->getConfig();
+ $insp->write(sprintf(
+ 'Connection to %s as %s on %s:%s successful',
+ $config['dbname'],
+ $config['username'],
+ array_key_exists('host', $config) ? $config['host'] : '(none)',
+ $config['port']
+ ));
+ switch ($this->dbType) {
+ case 'mysql':
+ $rows = $this->dbAdapter->query(
+ 'SHOW VARIABLES WHERE variable_name ' .
+ 'IN (\'version\', \'protocol_version\', \'version_compile_os\', \'have_ssl\');'
+ )->fetchAll();
+ $sqlinsp = new Inspection('MySQL');
+ $hasSsl = false;
+ foreach ($rows as $row) {
+ $sqlinsp->write($row->variable_name . ': ' . $row->value);
+ if ($row->variable_name === 'have_ssl' && $row->value === 'YES') {
+ $hasSsl = true;
+ }
+ }
+ if ($hasSsl) {
+ $ssl_rows = $this->dbAdapter->query(
+ 'SHOW STATUS WHERE variable_name ' .
+ 'IN (\'Ssl_Cipher\');'
+ )->fetchAll();
+ foreach ($ssl_rows as $ssl_row) {
+ $sqlinsp->write($ssl_row->variable_name . ': ' . $ssl_row->value);
+ }
+ }
+ $insp->write($sqlinsp);
+ break;
+ case 'pgsql':
+ $row = $this->dbAdapter->query('SELECT version();')->fetchAll();
+ $sqlinsp = new Inspection('PostgreSQL');
+ $sqlinsp->write($row[0]->version);
+ $insp->write($sqlinsp);
+ break;
+ }
+ } catch (Exception $e) {
+ return $insp->error(sprintf('Connection failed %s', $e->getMessage()));
+ }
+ return $insp;
+ }
+}
diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php
new file mode 100644
index 0000000..30816a7
--- /dev/null
+++ b/library/Icinga/Data/Db/DbQuery.php
@@ -0,0 +1,564 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Db;
+
+use DateInterval;
+use DateTime;
+use DateTimeZone;
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Application\Logger;
+use Icinga\Data\SimpleQuery;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\QueryException;
+
+/**
+ * Database query class
+ */
+class DbQuery extends SimpleQuery
+{
+ /**
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $db;
+
+ /**
+ * Whether or not the query is a sub query
+ *
+ * Sub queries are automatically wrapped in parentheses
+ *
+ * @var bool
+ */
+ protected $isSubQuery = false;
+
+ /**
+ * Select query
+ *
+ * @var Zend_Db_Select
+ */
+ protected $select;
+
+ /**
+ * Whether to use a subquery for counting
+ *
+ * When the query is distinct or has a HAVING or GROUP BY clause this must be set to true
+ *
+ * @var bool
+ */
+ protected $useSubqueryCount = false;
+
+ /**
+ * Count query result
+ *
+ * Count queries are only executed once
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * GROUP BY clauses
+ *
+ * @var string|array
+ */
+ protected $group;
+
+ protected function init()
+ {
+ $this->db = $this->ds->getDbAdapter();
+ $this->select = $this->db->select();
+ parent::init();
+ }
+
+ /**
+ * Get whether or not the query is a sub query
+ */
+ public function getIsSubQuery()
+ {
+ return $this->isSubQuery;
+ }
+
+ /**
+ * Set whether or not the query is a sub query
+ *
+ * @param bool $isSubQuery
+ *
+ * @return $this
+ */
+ public function setIsSubQuery($isSubQuery = true)
+ {
+ $this->isSubQuery = (bool) $isSubQuery;
+ return $this;
+ }
+
+ public function setUseSubqueryCount($useSubqueryCount = true)
+ {
+ $this->useSubqueryCount = $useSubqueryCount;
+ return $this;
+ }
+
+ public function from($target, array $fields = null)
+ {
+ parent::from($target, $fields);
+ $this->select->from($this->target, array());
+ return $this;
+ }
+
+ public function where($condition, $value = null)
+ {
+ // $this->count = $this->select = null;
+ return parent::where($condition, $value);
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ $this->expressionsToTimestamp($filter);
+ return parent::addFilter($filter);
+ }
+
+ private function expressionsToTimestamp(Filter $filter)
+ {
+ if ($filter->isChain()) {
+ foreach ($filter->filters() as $child) {
+ $this->expressionsToTimestamp($child);
+ }
+ } elseif ($this->isTimestamp($filter->getColumn())) {
+ $filter->setExpression($this->valueToTimestamp($filter->getExpression()));
+ }
+ }
+
+ protected function dbSelect()
+ {
+ return clone $this->select;
+ }
+
+ /**
+ * Return the underlying select
+ *
+ * @return Zend_Db_Select
+ */
+ public function select()
+ {
+ return $this->select;
+ }
+
+ /**
+ * Get the select query
+ *
+ * Applies order and limit if any
+ *
+ * @return Zend_Db_Select
+ */
+ public function getSelectQuery()
+ {
+ $select = $this->dbSelect();
+ // Add order fields to select for postgres distinct queries (#6351)
+ if ($this->hasOrder()
+ && $this->getDatasource()->getDbType() === 'pgsql'
+ && $select->getPart(Zend_Db_Select::DISTINCT) === true) {
+ foreach ($this->getOrder() as $fieldAndDirection) {
+ if (array_search($fieldAndDirection[0], $this->columns, true) === false) {
+ $this->columns[] = $fieldAndDirection[0];
+ }
+ }
+ }
+
+ $group = $this->getGroup();
+ if ($group) {
+ $select->group($group);
+ }
+
+ if (! empty($this->columns)) {
+ $select->columns($this->columns);
+ }
+
+ $this->applyFilterSql($select);
+
+ if ($this->hasLimit() || $this->hasOffset()) {
+ $select->limit($this->getLimit(), $this->getOffset());
+ }
+ if ($this->hasOrder()) {
+ foreach ($this->getOrder() as $fieldAndDirection) {
+ $select->order(
+ $fieldAndDirection[0] . ' ' . $fieldAndDirection[1]
+ );
+ }
+ }
+
+ return $select;
+ }
+
+ protected function applyFilterSql($select)
+ {
+ $where = $this->getDatasource()->renderFilter($this->filter);
+ if ($where !== '') {
+ $select->where($where);
+ }
+ }
+
+ protected function escapeForSql($value)
+ {
+ // bindParam? bindValue?
+ if (is_array($value)) {
+ $ret = array();
+ foreach ($value as $val) {
+ $ret[] = $this->escapeForSql($val);
+ }
+ return implode(', ', $ret);
+ } else {
+ //if (preg_match('/^\d+$/', $value)) {
+ // return $value;
+ //} else {
+ return $this->db->quote($value);
+ //}
+ }
+ }
+
+ protected function escapeWildcards($value)
+ {
+ return preg_replace('/\*/', '%', $value);
+ }
+
+ protected function valueToTimestamp($value)
+ {
+ if (is_string($value)) {
+ if (ctype_digit($value)) {
+ $value = (int) $value;
+ } else {
+ $value = strtotime($value);
+ }
+ } elseif (! is_int($value)) {
+ $value = (int) $value;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Render the given timestamp based on the local timezone
+ *
+ * Since {@see DbConnection::defaultTimezoneOffset()} tells the database the timezone with just an offset,
+ * this will prepare the rendered value in a way that it plays fine with daylight savings.
+ *
+ * @param int $value
+ * @return string
+ */
+ protected function timestampForSql($value)
+ {
+ if ($this->getDatasource()->getDbType() === 'pgsql') {
+ // We don't tell PostgreSQL the user's timezone
+ $dateTime = (new DateTime())
+ ->setTimezone(new DateTimeZone('UTC'))
+ ->setTimestamp($value);
+ } else {
+ $dateTime = new DateTime();
+ // Get "current" offset the database will use
+ $offsetToUTC = $dateTime->getOffset();
+ // Set timezone to UTC and initialize it with the timestamp
+ $dateTime->setTimezone(new DateTimeZone('UTC'))->setTimestamp($value);
+ // Normalize every datetime based on the only offset the database knows about
+ if ($offsetToUTC >= 0) {
+ $dateTime->add(new DateInterval("PT{$offsetToUTC}S"));
+ } else {
+ $offsetToUTC = abs($offsetToUTC);
+ $dateTime->sub(new DateInterval("PT{$offsetToUTC}S"));
+ }
+ }
+
+ return $dateTime->format('Y-m-d H:i:s');
+ }
+
+ /**
+ * Check for timestamp fields
+ *
+ * TODO: This is not here to do automagic timestamp stuff. One may
+ * override this function for custom voodoo, IdoQuery right now
+ * does. IMO we need to split whereToSql functionality, however
+ * I'd prefer to wait with this unless we understood how other
+ * backends will work. We probably should also rename this
+ * function to isTimestampColumn().
+ *
+ * @param string $field Field Field name to checked
+ * @return bool Whether this field expects timestamps
+ */
+ public function isTimestamp($field)
+ {
+ return false;
+ }
+
+ /**
+ * Get the count query
+ *
+ * @return Zend_Db_Select
+ */
+ public function getCountQuery()
+ {
+ // TODO: there may be situations where we should clone the "select"
+ $count = $this->dbSelect();
+ $this->applyFilterSql($count);
+ $group = $this->getGroup();
+ if ($this->useSubqueryCount || $group) {
+ if (! empty($this->columns)) {
+ $count->columns($this->columns);
+ }
+ if ($group) {
+ $count->group($group);
+ }
+ $columns = array('cnt' => 'COUNT(*)');
+ return $this->db->select()->from($count, $columns);
+ }
+
+ $count->columns(array('cnt' => 'COUNT(*)'));
+ return $count;
+ }
+
+ /**
+ * Count all rows of the result set
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ if ($this->count === null) {
+ $this->count = parent::count();
+ }
+
+ return $this->count;
+ }
+
+ /**
+ * Return the select and count query as a textual representation
+ *
+ * @return string A string containing the select and count query, using unix style newlines as linebreaks
+ */
+ public function dump()
+ {
+ return "QUERY\n=====\n"
+ . $this->getSelectQuery()
+ . "\n\nCOUNT\n=====\n"
+ . $this->getCountQuery()
+ . "\n\n";
+ }
+
+ public function __clone()
+ {
+ parent::__clone();
+ $this->select = clone $this->select;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ $select = (string) $this->getSelectQuery();
+ return $this->getIsSubQuery() ? ('(' . $select . ')') : $select;
+ } catch (Exception $e) {
+ Logger::debug('Failed to render DbQuery. An error occured: %s', $e);
+ return '';
+ }
+ }
+
+ /**
+ * Add a GROUP BY clause
+ *
+ * @param string|array $group
+ *
+ * @return $this
+ */
+ public function group($group)
+ {
+ $this->group = $group;
+ return $this;
+ }
+
+ /**
+ * Return the GROUP BY clause
+ *
+ * @return string|array
+ */
+ public function getGroup()
+ {
+ return $this->group;
+ }
+
+ /**
+ * Return whether the given table has been joined
+ *
+ * @param string $table
+ *
+ * @return bool
+ */
+ public function hasJoinedTable($table)
+ {
+ $fromPart = $this->select->getPart(Zend_Db_Select::FROM);
+ if (isset($fromPart[$table])) {
+ return true;
+ }
+
+ foreach ($fromPart as $options) {
+ if ($options['tableName'] === $table && $options['joinType'] !== Zend_Db_Select::FROM) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the alias used for joining the given table
+ *
+ * @param string $table
+ *
+ * @return string|null null in case no alias is being used
+ *
+ * @throws ProgrammingError In case the given table has not been joined
+ */
+ public function getJoinedTableAlias($table)
+ {
+ $fromPart = $this->select->getPart(Zend_Db_Select::FROM);
+ if (isset($fromPart[$table])) {
+ if ($fromPart[$table]['joinType'] === Zend_Db_Select::FROM) {
+ throw new ProgrammingError('Table "%s" has not been joined', $table);
+ }
+
+ return; // No alias in use
+ }
+
+ foreach ($fromPart as $alias => $options) {
+ if ($options['tableName'] === $table && $options['joinType'] !== Zend_Db_Select::FROM) {
+ return $alias;
+ }
+ }
+
+ throw new ProgrammingError('Table "%s" has not been joined', $table);
+ }
+
+ /**
+ * Add an INNER JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function join($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinInner($name, $cond, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add an INNER JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function joinInner($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinInner($name, $cond, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add a LEFT OUTER JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function joinLeft($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinLeft($name, $cond, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add a RIGHT OUTER JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function joinRight($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinRight($name, $cond, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add a FULL OUTER JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function joinFull($name, $cond, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinFull($name, $cond, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add a CROSS JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function joinCross($name, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinCross($name, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add a NATURAL JOIN table and colums to the query
+ *
+ * @param array|string|Zend_Db_Expr $name The table name
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any
+ *
+ * @return $this
+ */
+ public function joinNatural($name, $cols = Zend_Db_Select::SQL_WILDCARD, $schema = null)
+ {
+ $this->select->joinNatural($name, $cols, $schema);
+ return $this;
+ }
+
+ /**
+ * Add a UNION clause to the query
+ *
+ * @param array $select Select clauses for the union
+ * @param string $type Type of UNION to use
+ *
+ * @return $this
+ */
+ public function union($select = array(), $type = Zend_Db_Select::SQL_UNION)
+ {
+ $this->select->union($select, $type);
+ return $this;
+ }
+}
diff --git a/library/Icinga/Data/Extensible.php b/library/Icinga/Data/Extensible.php
new file mode 100644
index 0000000..ad690d8
--- /dev/null
+++ b/library/Icinga/Data/Extensible.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Exception\StatementException;
+
+/**
+ * Interface for data insertion
+ */
+interface Extensible
+{
+ /**
+ * Insert the given data for the given target
+ *
+ * @param string $target
+ * @param array $data
+ *
+ * @throws StatementException
+ */
+ public function insert($target, array $data);
+}
diff --git a/library/Icinga/Data/Fetchable.php b/library/Icinga/Data/Fetchable.php
new file mode 100644
index 0000000..342740a
--- /dev/null
+++ b/library/Icinga/Data/Fetchable.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * Interface for retrieving data
+ */
+interface Fetchable
+{
+ /**
+ * Retrieve an array containing all rows of the result set
+ *
+ * @return array
+ */
+ public function fetchAll();
+
+ /**
+ * Fetch the first row of the result set
+ *
+ * @return mixed
+ */
+ public function fetchRow();
+
+ /**
+ * Fetch the first column of all rows of the result set as an array
+ *
+ * @return array
+ */
+ public function fetchColumn();
+
+ /**
+ * Fetch the first column of the first row of the result set
+ *
+ * @return string
+ */
+ public function fetchOne();
+
+ /**
+ * Fetch all rows of the result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
+ *
+ * @return array
+ */
+ public function fetchPairs();
+}
diff --git a/library/Icinga/Data/Filter/Filter.php b/library/Icinga/Data/Filter/Filter.php
new file mode 100644
index 0000000..f5d8bdf
--- /dev/null
+++ b/library/Icinga/Data/Filter/Filter.php
@@ -0,0 +1,255 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+use Icinga\Web\UrlParams;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Filter
+ *
+ * Base class for filters (why?) and factory for the different FilterOperators
+ */
+abstract class Filter
+{
+ protected $id = '1';
+
+ public function setId($id)
+ {
+ $this->id = (string) $id;
+ return $this;
+ }
+
+ abstract public function isExpression();
+
+ abstract public function isChain();
+
+ abstract public function isEmpty();
+
+ abstract public function toQueryString();
+
+ abstract public function andFilter(Filter $filter);
+
+ abstract public function orFilter(Filter $filter);
+
+ /**
+ * Whether the give row matches this Filter
+ *
+ * @param mixed $row Preferrably an stdClass instance
+ * @return bool
+ */
+ abstract public function matches($row);
+
+ public function getUrlParams()
+ {
+ return UrlParams::fromQueryString($this->toQueryString());
+ }
+
+ public function getById($id)
+ {
+ if ((string) $id === $this->getId()) {
+ return $this;
+ }
+ throw new ProgrammingError(
+ 'Trying to get invalid filter index "%s" from "%s" ("%s")',
+ $id,
+ $this,
+ $this->id
+ );
+ }
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function isRootNode()
+ {
+ return false === strpos($this->id, '-');
+ }
+
+ abstract public function listFilteredColumns();
+
+ public function applyChanges($changes)
+ {
+ $filter = $this;
+ $pairs = array();
+ foreach ($changes as $k => $v) {
+ if (preg_match('/^(column|value|sign|operator)_([\d-]+)$/', $k, $m)) {
+ $pairs[$m[2]][$m[1]] = $v;
+ }
+ }
+ $operators = array();
+ foreach ($pairs as $id => $fs) {
+ if (array_key_exists('operator', $fs)) {
+ $operators[$id] = $fs['operator'];
+ } else {
+ $f = $filter->getById($id);
+ $f->setColumn($fs['column']);
+ if ($f->getSign() !== $fs['sign']) {
+ if ($f->isRootNode()) {
+ $filter = $f->setSign($fs['sign']);
+ } else {
+ $filter->replaceById($id, $f->setSign($fs['sign']));
+ }
+ }
+ $f->setExpression($fs['value']);
+ }
+ }
+
+ krsort($operators, SORT_NATURAL);
+ foreach ($operators as $id => $operator) {
+ $f = $filter->getById($id);
+ if ($f->getOperatorName() !== $operator) {
+ if ($f->isRootNode()) {
+ $filter = $f->setOperatorName($operator);
+ } else {
+ $filter->replaceById($id, $f->setOperatorName($operator));
+ }
+ }
+ }
+
+ return $filter;
+ }
+
+ public function getParentId()
+ {
+ if ($this->isRootNode()) {
+ throw new ProgrammingError('Filter root nodes have no parent');
+ }
+ return substr($this->id, 0, strrpos($this->id, '-'));
+ }
+
+ public function getParent()
+ {
+ return $this->getById($this->getParentId());
+ }
+
+ public function hasId($id)
+ {
+ if ($id === $this->getId()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Where Filter factory
+ *
+ * @param string $col Column to be filtered
+ * @param string $filter Filter expression
+ *
+ * @throws FilterException
+ * @return FilterExpression
+ */
+ public static function where($col, $filter)
+ {
+ return new FilterExpression($col, '=', $filter);
+ }
+
+ public static function expression($col, $op, $expression)
+ {
+ switch ($op) {
+ case '=':
+ return new FilterMatch($col, $op, $expression);
+ case '<':
+ return new FilterLessThan($col, $op, $expression);
+ case '>':
+ return new FilterGreaterThan($col, $op, $expression);
+ case '>=':
+ return new FilterEqualOrGreaterThan($col, $op, $expression);
+ case '<=':
+ return new FilterEqualOrLessThan($col, $op, $expression);
+ case '!=':
+ return new FilterMatchNot($col, $op, $expression);
+ default:
+ throw new ProgrammingError(
+ 'There is no such filter sign: %s',
+ $op
+ );
+ }
+ }
+
+ /**
+ * Or FilterOperator factory
+ *
+ * @param Filter $filter,... Unlimited optional list of Filters
+ *
+ * @return FilterOr
+ */
+ public static function matchAny()
+ {
+ $args = func_get_args();
+ if (count($args) === 1 && is_array($args[0])) {
+ $args = $args[0];
+ }
+ return new FilterOr($args);
+ }
+
+ /**
+ * Or FilterOperator factory
+ *
+ * @param Filter $filter,... Unlimited optional list of Filters
+ *
+ * @return FilterAnd
+ */
+ public static function matchAll()
+ {
+ $args = func_get_args();
+ if (count($args) === 1 && is_array($args[0])) {
+ $args = $args[0];
+ }
+ return new FilterAnd($args);
+ }
+
+ /**
+ * FilterNot factory, negates the given filter
+ *
+ * @param Filter $filter Filter to be negated
+ *
+ * @return FilterNot
+ */
+ public static function not()
+ {
+ $args = func_get_args();
+ if (count($args) === 1) {
+ if (is_array($args[0])) {
+ $args = $args[0];
+ }
+ }
+ if (count($args) > 1) {
+ return new FilterNot(array(new FilterAnd($args)));
+ } else {
+ return new FilterNot($args);
+ }
+ }
+
+ public static function chain($operator, $filters = array())
+ {
+ switch ($operator) {
+ case 'AND':
+ return self::matchAll($filters);
+ case 'OR':
+ return self::matchAny($filters);
+ case 'NOT':
+ return self::not($filters);
+ }
+ throw new ProgrammingError(
+ '"%s" is not a valid filter chain operator',
+ $operator
+ );
+ }
+
+ /**
+ * Create filter from queryString
+ *
+ * This is still pretty basic, need improvement
+ *
+ * @return static
+ */
+ public static function fromQueryString($query)
+ {
+ return FilterQueryString::parse($query);
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterAnd.php b/library/Icinga/Data/Filter/FilterAnd.php
new file mode 100644
index 0000000..96b68cc
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterAnd.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+/**
+ * Filter list AND
+ *
+ * Binary AND, all contained filters must succeed
+ */
+class FilterAnd extends FilterChain
+{
+ protected $operatorName = 'AND';
+
+ protected $operatorSymbol = '&';
+
+ /**
+ * Whether the given row object matches this filter
+ *
+ * @object $row
+ * @return boolean
+ */
+ public function matches($row)
+ {
+ foreach ($this->filters as $filter) {
+ if (! $filter->matches($row)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function andFilter(Filter $filter)
+ {
+ return $this->addFilter($filter);
+ }
+
+ public function orFilter(Filter $filter)
+ {
+ return Filter::matchAny($this, $filter);
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterChain.php b/library/Icinga/Data/Filter/FilterChain.php
new file mode 100644
index 0000000..0f1e071
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterChain.php
@@ -0,0 +1,286 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\QueryException;
+
+/**
+ * FilterChain
+ *
+ * A FilterChain contains a list ...
+ */
+abstract class FilterChain extends Filter
+{
+ protected $filters = array();
+
+ protected $operatorName;
+
+ protected $operatorSymbol;
+
+ protected $allowedColumns;
+
+ /**
+ * Set the filters
+ *
+ * @param array $filters
+ *
+ * @return $this
+ */
+ public function setFilters(array $filters)
+ {
+ $this->filters = $filters;
+
+ $this->refreshChildIds();
+
+ return $this;
+ }
+
+ public function hasId($id)
+ {
+ foreach ($this->filters() as $filter) {
+ if ($filter->hasId($id)) {
+ return true;
+ }
+ }
+ return parent::hasId($id);
+ }
+
+ public function getById($id)
+ {
+ foreach ($this->filters() as $filter) {
+ if ($filter->hasId($id)) {
+ return $filter->getById($id);
+ }
+ }
+ return parent::getById($id);
+ }
+
+ public function removeId($id)
+ {
+ if ($id === $this->getId()) {
+ $this->filters = array();
+ return $this;
+ }
+ $remove = null;
+ foreach ($this->filters as $key => $filter) {
+ if ($filter->getId() === $id) {
+ $remove = $key;
+ } elseif ($filter instanceof FilterChain) {
+ $filter->removeId($id);
+ }
+ }
+ if ($remove !== null) {
+ unset($this->filters[$remove]);
+ $this->filters = array_values($this->filters);
+ }
+ $this->refreshChildIds();
+ return $this;
+ }
+
+ public function replaceById($id, $filter)
+ {
+ $found = false;
+ foreach ($this->filters as $k => $child) {
+ if ($child->getId() == $id) {
+ $this->filters[$k] = $filter;
+ $found = true;
+ break;
+ }
+ if ($child->hasId($id)) {
+ $child->replaceById($id, $filter);
+ $found = true;
+ break;
+ }
+ }
+ if (! $found) {
+ throw new ProgrammingError('You tried to replace an unexistant child filter');
+ }
+ $this->refreshChildIds();
+ return $this;
+ }
+
+ protected function refreshChildIds()
+ {
+ $i = 0;
+ $id = $this->getId();
+ foreach ($this->filters as $filter) {
+ $i++;
+ $filter->setId($id . '-' . $i);
+ }
+ return $this;
+ }
+
+ public function setId($id)
+ {
+ return parent::setId($id)->refreshChildIds();
+ }
+
+ public function getOperatorName()
+ {
+ return $this->operatorName;
+ }
+
+ public function setOperatorName($name)
+ {
+ if ($name !== $this->operatorName) {
+ return Filter::chain($name, $this->filters);
+ }
+ return $this;
+ }
+
+ public function getOperatorSymbol()
+ {
+ return $this->operatorSymbol;
+ }
+
+ public function setAllowedFilterColumns(array $columns)
+ {
+ $this->allowedColumns = $columns;
+ return $this;
+ }
+
+ /**
+ * List and return all column names referenced in this filter
+ *
+ * @param array $columns The columns listed so far
+ *
+ * @return array
+ */
+ public function listFilteredColumns(array $columns = array())
+ {
+ foreach ($this->filters as $filter) {
+ if ($filter instanceof FilterExpression) {
+ $column= $filter->getColumn();
+ if (! in_array($column, $columns, true)) {
+ $columns[] = $column;
+ }
+ } else {
+ $columns = $filter->listFilteredColumns($columns);
+ }
+ }
+
+ return $columns;
+ }
+
+ public function toQueryString()
+ {
+ $parts = array();
+ if (empty($this->filters)) {
+ return '';
+ }
+ foreach ($this->filters() as $filter) {
+ if (! $filter->isEmpty()) {
+ $parts[] = $filter->toQueryString();
+ }
+ }
+
+ // TODO: getLevel??
+ if (strpos($this->getId(), '-')) {
+ return '(' . implode($this->getOperatorSymbol(), $parts) . ')';
+ } else {
+ return implode($this->getOperatorSymbol(), $parts);
+ }
+ }
+
+ /**
+ * Get simple string representation
+ *
+ * Useful for debugging only
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if (empty($this->filters)) {
+ return '';
+ }
+ $parts = array();
+ foreach ($this->filters as $filter) {
+ if ($filter instanceof FilterChain) {
+ $parts[] = '(' . $filter . ')';
+ } else {
+ $parts[] = (string) $filter;
+ }
+ }
+ $op = ' ' . $this->getOperatorSymbol() . ' ';
+ return implode($op, $parts);
+ }
+
+ public function __construct($filters = array())
+ {
+ foreach ($filters as $filter) {
+ $this->addFilter($filter);
+ }
+ }
+
+ public function isExpression()
+ {
+ return false;
+ }
+
+ public function isChain()
+ {
+ return true;
+ }
+
+ public function isEmpty()
+ {
+ return empty($this->filters);
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ if (! empty($this->allowedColumns)) {
+ $this->validateFilterColumns($filter);
+ }
+
+ $this->filters[] = $filter;
+ $filter->setId($this->getId() . '-' . $this->count());
+ return $this;
+ }
+
+ protected function validateFilterColumns(Filter $filter)
+ {
+ if ($filter->isExpression()) {
+ $valid = false;
+ foreach ($this->allowedColumns as $column) {
+ if (is_callable($column)) {
+ if (call_user_func($column, $filter->getColumn())) {
+ $valid = true;
+ break;
+ }
+ } elseif ($filter->getColumn() === $column) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if (! $valid) {
+ throw new QueryException('Invalid filter column provided: %s', $filter->getColumn());
+ }
+ } else {
+ foreach ($filter->filters() as $subFilter) {
+ $this->validateFilterColumns($subFilter);
+ }
+ }
+ }
+
+ public function &filters()
+ {
+ return $this->filters;
+ }
+
+ public function count()
+ {
+ return count($this->filters);
+ }
+
+ public function __clone()
+ {
+ foreach ($this->filters as & $filter) {
+ $filter = clone $filter;
+ }
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterEqual.php b/library/Icinga/Data/Filter/FilterEqual.php
new file mode 100644
index 0000000..da53d3f
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterEqual.php
@@ -0,0 +1,16 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterEqual extends FilterExpression
+{
+ public function matches($row)
+ {
+ if (! isset($row->{$this->column})) {
+ return false;
+ }
+
+ return (string) $row->{$this->column} === (string) $this->expression;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterEqualOrGreaterThan.php b/library/Icinga/Data/Filter/FilterEqualOrGreaterThan.php
new file mode 100644
index 0000000..d7bd5b8
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterEqualOrGreaterThan.php
@@ -0,0 +1,16 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterEqualOrGreaterThan extends FilterExpression
+{
+ public function matches($row)
+ {
+ if (! isset($row->{$this->column})) {
+ return false;
+ }
+
+ return (string) $row->{$this->column} >= (string) $this->expression;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterEqualOrLessThan.php b/library/Icinga/Data/Filter/FilterEqualOrLessThan.php
new file mode 100644
index 0000000..8016fc4
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterEqualOrLessThan.php
@@ -0,0 +1,26 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterEqualOrLessThan extends FilterExpression
+{
+ public function __toString()
+ {
+ return $this->column . ' <= ' . $this->expression;
+ }
+
+ public function toQueryString()
+ {
+ return $this->column . '<=' . $this->expression;
+ }
+
+ public function matches($row)
+ {
+ if (! isset($row->{$this->column})) {
+ return false;
+ }
+
+ return (string) $row->{$this->column} <= (string) $this->expression;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterException.php b/library/Icinga/Data/Filter/FilterException.php
new file mode 100644
index 0000000..842d7ab
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterException.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Filter Exception Class
+ *
+ * Filter Exceptions should be thrown on filter parse errors or similar
+ */
+class FilterException extends IcingaException
+{
+}
diff --git a/library/Icinga/Data/Filter/FilterExpression.php b/library/Icinga/Data/Filter/FilterExpression.php
new file mode 100644
index 0000000..73fb625
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterExpression.php
@@ -0,0 +1,224 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+use Exception;
+
+class FilterExpression extends Filter
+{
+ protected $column;
+ protected $sign;
+ protected $expression;
+
+ /**
+ * Does this filter compare case sensitive?
+ *
+ * @var bool
+ */
+ protected $caseSensitive;
+
+ public function __construct($column, $sign, $expression)
+ {
+ $column = trim($column);
+ $this->column = $column;
+ $this->sign = $sign;
+ $this->expression = $expression;
+ $this->caseSensitive = true;
+ }
+
+ public function isExpression()
+ {
+ return true;
+ }
+
+ public function isChain()
+ {
+ return false;
+ }
+
+ public function isEmpty()
+ {
+ return false;
+ }
+
+ public function getColumn()
+ {
+ return $this->column;
+ }
+
+ public function getSign()
+ {
+ return $this->sign;
+ }
+
+ public function setColumn($column)
+ {
+ $this->column = $column;
+ return $this;
+ }
+
+ public function getExpression()
+ {
+ return $this->expression;
+ }
+
+ /**
+ * Return whether this filter compares case sensitive
+ *
+ * @return bool
+ */
+ public function getCaseSensitive()
+ {
+ return $this->caseSensitive;
+ }
+
+ public function setExpression($expression)
+ {
+ $this->expression = $expression;
+ return $this;
+ }
+
+ public function setSign($sign)
+ {
+ if ($sign !== $this->sign) {
+ return Filter::expression($this->column, $sign, $this->expression);
+ }
+ return $this;
+ }
+
+ /**
+ * Set this filter's case sensitivity
+ *
+ * @param bool $caseSensitive
+ *
+ * @return $this
+ */
+ public function setCaseSensitive($caseSensitive = true)
+ {
+ $this->caseSensitive = $caseSensitive;
+ return $this;
+ }
+
+ public function listFilteredColumns()
+ {
+ return array($this->getColumn());
+ }
+
+ public function __toString()
+ {
+ if ($this->isBooleanTrue()) {
+ return $this->column;
+ }
+
+ $expression = is_array($this->expression) ?
+ '( ' . implode(' | ', $this->expression) . ' )' :
+ $this->expression;
+
+ return sprintf(
+ '%s %s %s',
+ $this->column,
+ $this->sign,
+ $expression
+ );
+ }
+
+ public function toQueryString()
+ {
+ if ($this->isBooleanTrue()) {
+ return $this->column;
+ }
+
+ $expression = is_array($this->expression) ?
+ '(' . implode('|', array_map('rawurlencode', $this->expression)) . ')' :
+ rawurlencode($this->expression);
+
+ return $this->column . $this->sign . $expression;
+ }
+
+ protected function isBooleanTrue()
+ {
+ return $this->sign === '=' && $this->expression === true;
+ }
+
+ /**
+ * If $var is a scalar, do the same as strtolower() would do.
+ * If $var is an array, map $this->strtolowerRecursive() to its elements.
+ * Otherwise, return $var unchanged.
+ *
+ * @param mixed $var
+ *
+ * @return mixed
+ */
+ protected function strtolowerRecursive($var)
+ {
+ if ($var === null) {
+ return '';
+ }
+ if (is_scalar($var)) {
+ return strtolower($var);
+ }
+ if (is_array($var)) {
+ return array_map(array($this, 'strtolowerRecursive'), $var);
+ }
+ return $var;
+ }
+
+ public function matches($row)
+ {
+ try {
+ $rowValue = $row->{$this->column};
+ } catch (Exception $e) {
+ // TODO: REALLY? Exception?
+ return false;
+ }
+
+ if ($this->caseSensitive) {
+ $expression = $this->expression;
+ } else {
+ $rowValue = $this->strtolowerRecursive($rowValue);
+ $expression = $this->strtolowerRecursive($this->expression);
+ }
+
+ if (is_array($expression)) {
+ return in_array($rowValue, $expression);
+ }
+
+ $expression = (string) $expression;
+ if (strpos($expression, '*') === false) {
+ if (is_array($rowValue)) {
+ return in_array($expression, $rowValue);
+ }
+
+ return (string) $rowValue === $expression;
+ }
+
+ $parts = array();
+ foreach (preg_split('~\*~', $expression) as $part) {
+ $parts[] = preg_quote($part, '/');
+ }
+ $pattern = '/^' . implode('.*', $parts) . '$/';
+
+ if (is_array($rowValue)) {
+ foreach ($rowValue as $candidate) {
+ if (preg_match($pattern, $candidate)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return $rowValue !== null && preg_match($pattern, $rowValue);
+ }
+
+ public function andFilter(Filter $filter)
+ {
+ return Filter::matchAll($this, $filter);
+ }
+
+ public function orFilter(Filter $filter)
+ {
+ return Filter::matchAny($this, $filter);
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterGreaterThan.php b/library/Icinga/Data/Filter/FilterGreaterThan.php
new file mode 100644
index 0000000..92a0e62
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterGreaterThan.php
@@ -0,0 +1,16 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterGreaterThan extends FilterExpression
+{
+ public function matches($row)
+ {
+ if (! isset($row->{$this->column})) {
+ // TODO: REALLY? Exception?
+ return false;
+ }
+ return (string) $row->{$this->column} > (string) $this->expression;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterLessThan.php b/library/Icinga/Data/Filter/FilterLessThan.php
new file mode 100644
index 0000000..c13a1ce
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterLessThan.php
@@ -0,0 +1,26 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterLessThan extends FilterExpression
+{
+ public function __toString()
+ {
+ return $this->column . ' < ' . $this->expression;
+ }
+
+ public function toQueryString()
+ {
+ return $this->column . '<' . $this->expression;
+ }
+
+ public function matches($row)
+ {
+ if (! isset($row->{$this->column})) {
+ return false;
+ }
+
+ return (string) $row->{$this->column} < (string) $this->expression;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterMatch.php b/library/Icinga/Data/Filter/FilterMatch.php
new file mode 100644
index 0000000..a3befad
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterMatch.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterMatch extends FilterExpression
+{
+}
diff --git a/library/Icinga/Data/Filter/FilterMatchCaseInsensitive.php b/library/Icinga/Data/Filter/FilterMatchCaseInsensitive.php
new file mode 100644
index 0000000..9eca173
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterMatchCaseInsensitive.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterMatchCaseInsensitive extends FilterMatch
+{
+ public function __construct($column, $sign, $expression)
+ {
+ parent::__construct($column, $sign, $expression);
+ $this->caseSensitive = false;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterMatchNot.php b/library/Icinga/Data/Filter/FilterMatchNot.php
new file mode 100644
index 0000000..1e5050e
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterMatchNot.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterMatchNot extends FilterExpression
+{
+ public function matches($row)
+ {
+ return !parent::matches($row);
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterMatchNotCaseInsensitive.php b/library/Icinga/Data/Filter/FilterMatchNotCaseInsensitive.php
new file mode 100644
index 0000000..3838fa2
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterMatchNotCaseInsensitive.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterMatchNotCaseInsensitive extends FilterMatchNot
+{
+ public function __construct($column, $sign, $expression)
+ {
+ parent::__construct($column, $sign, $expression);
+ $this->caseSensitive = false;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterNot.php b/library/Icinga/Data/Filter/FilterNot.php
new file mode 100644
index 0000000..b61f497
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterNot.php
@@ -0,0 +1,58 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterNot extends FilterChain
+{
+ protected $operatorName = 'NOT';
+
+ protected $operatorSymbol = '!'; // BULLSHIT
+
+// TODO: Max count 1 or autocreate sub-and?
+
+ public function matches($row)
+ {
+ foreach ($this->filters() as $filter) {
+ if ($filter->matches($row)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public function andFilter(Filter $filter)
+ {
+ return Filter::matchAll($this, $filter);
+ }
+
+ public function orFilter(Filter $filter)
+ {
+ return Filter::matchAny($filter);
+ }
+
+ public function toQueryString()
+ {
+ $parts = array();
+ if (empty($this->filters)) {
+ return '';
+ }
+
+ foreach ($this->filters() as $filter) {
+ $parts[] = $filter->toQueryString();
+ }
+ if (count($parts) === 1) {
+ return '!' . $parts[0];
+ } else {
+ return '!(' . implode('&', $parts) . ')';
+ }
+ }
+
+ public function __toString()
+ {
+ if (count($this->filters) === 1) {
+ return '! ' . $this->filters[0];
+ }
+ return '! (' . implode('&', $this->filters) . ')';
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterNotEqual.php b/library/Icinga/Data/Filter/FilterNotEqual.php
new file mode 100644
index 0000000..8915a3d
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterNotEqual.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterNotEqual extends FilterExpression
+{
+ public function matches($row)
+ {
+ return (string) $row->{$this->column} !== (string) $this->expression;
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterOr.php b/library/Icinga/Data/Filter/FilterOr.php
new file mode 100644
index 0000000..aca91f3
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterOr.php
@@ -0,0 +1,39 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterOr extends FilterChain
+{
+ protected $operatorName = 'OR';
+
+ protected $operatorSymbol = '|';
+
+ public function matches($row)
+ {
+ foreach ($this->filters as $filter) {
+ if ($filter->matches($row)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function setOperatorName($name)
+ {
+ if ($this->count() > 1 && $name === 'NOT') {
+ return Filter::not(clone $this);
+ }
+ return parent::setOperatorName($name);
+ }
+
+ public function andFilter(Filter $filter)
+ {
+ return Filter::matchAll($this, $filter);
+ }
+
+ public function orFilter(Filter $filter)
+ {
+ return $this->addFilter($filter);
+ }
+}
diff --git a/library/Icinga/Data/Filter/FilterParseException.php b/library/Icinga/Data/Filter/FilterParseException.php
new file mode 100644
index 0000000..f2b732b
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterParseException.php
@@ -0,0 +1,10 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+use Icinga\Exception\IcingaException;
+
+class FilterParseException extends IcingaException
+{
+}
diff --git a/library/Icinga/Data/Filter/FilterQueryString.php b/library/Icinga/Data/Filter/FilterQueryString.php
new file mode 100644
index 0000000..8535df5
--- /dev/null
+++ b/library/Icinga/Data/Filter/FilterQueryString.php
@@ -0,0 +1,320 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Filter;
+
+class FilterQueryString
+{
+ protected $string;
+
+ protected $pos;
+
+ protected $debug = array();
+
+ protected $reportDebug = false;
+
+ protected $length;
+
+ protected function __construct()
+ {
+ }
+
+ protected function debug($msg, $level = 0, $op = null)
+ {
+ if ($op === null) {
+ $op = 'NULL';
+ }
+ $this->debug[] = sprintf(
+ '%s[%d=%s] (%s): %s',
+ str_repeat('* ', $level),
+ $this->pos,
+ $this->string[$this->pos - 1],
+ $op,
+ $msg
+ );
+ }
+
+ public static function parse($string)
+ {
+ $parser = new static();
+ return $parser->parseQueryString($string);
+ }
+
+ protected function readNextKey()
+ {
+ $str = $this->readUnlessSpecialChar();
+
+ if ($str === false) {
+ return $str;
+ }
+ return rawurldecode($str);
+ }
+
+ protected function readNextValue()
+ {
+ if ($this->nextChar() === '(') {
+ $this->readChar();
+ $var = preg_split('~\|~', $this->readUnless(')'));
+ if ($this->readChar() !== ')') {
+ $this->parseError(null, 'Expected ")"');
+ }
+ } else {
+ $var = rawurldecode($this->readUnless(array(')', '&', '|', '>', '<')));
+ }
+ return $var;
+ }
+
+ protected function readNextExpression()
+ {
+ if ('' === ($key = $this->readNextKey())) {
+ return false;
+ }
+
+ foreach (array('<', '>') as $sign) {
+ if (false !== ($pos = strpos($key, $sign))) {
+ if ($this->nextChar() === '=') {
+ break;
+ }
+ $var = substr($key, $pos + 1);
+ $key = substr($key, 0, $pos);
+
+ if (ctype_digit($var)) {
+ $var = (float) $var;
+ }
+
+ return Filter::expression($key, $sign, $var);
+ }
+ }
+ if (in_array($this->nextChar(), array('=', '>', '<', '!'))) {
+ $sign = $this->readChar();
+ } else {
+ $sign = false;
+ }
+ if ($sign === false) {
+ return Filter::expression($key, '=', true);
+ }
+
+ $toFloat = false;
+ if ($sign === '=') {
+ $last = substr($key, -1);
+ if ($last === '>' || $last === '<') {
+ $sign = $last . $sign;
+ $key = substr($key, 0, -1);
+ $toFloat = true;
+ }
+ // TODO: Same as above for unescaped <> - do we really need this?
+ } elseif ($sign === '>' || $sign === '<' || $sign === '!') {
+ $toFloat = $sign === '>' || $sign === '<';
+ if ($this->nextChar() === '=') {
+ $sign .= $this->readChar();
+ }
+ }
+
+ $var = $this->readNextValue();
+ if ($toFloat && ctype_digit($var)) {
+ $var = (float) $var;
+ }
+
+ return Filter::expression($key, $sign, $var);
+ }
+
+ protected function parseError($char = null, $extraMsg = null)
+ {
+ if ($extraMsg === null) {
+ $extra = '';
+ } else {
+ $extra = ': ' . $extraMsg;
+ }
+ if ($char === null) {
+ $char = $this->string[$this->pos];
+ }
+ if ($this->reportDebug) {
+ $extra .= "\n" . implode("\n", $this->debug);
+ }
+
+ throw new FilterParseException(
+ 'Invalid filter "%s", unexpected %s at pos %d%s',
+ $this->string,
+ $char,
+ $this->pos,
+ $extra
+ );
+ }
+
+ protected function readFilters($nestingLevel = 0, $op = null)
+ {
+ $filters = array();
+ while ($this->pos < $this->length) {
+ if ($op === '!' && count($filters) === 1) {
+ break;
+ }
+ $filter = $this->readNextExpression();
+ $next = $this->readChar();
+
+
+ if ($filter === false) {
+ $this->debug('Got no next expression, next is ' . $next, $nestingLevel, $op);
+ if ($next === '!') {
+ $not = $this->readFilters($nestingLevel + 1, '!');
+ $filters[] = $not;
+ if (in_array($this->nextChar(), array('|', '&', ')'))) {
+ $next = $this->readChar();
+ $this->debug('Got NOT, next is now: ' . $next, $nestingLevel, $op);
+ } else {
+ $this->debug('Breaking after NOT: ' . $not, $nestingLevel, $op);
+ break;
+ }
+ }
+
+ if ($op === null && count($filters) > 0 && ($next === '&' || $next === '|')) {
+ $op = $next;
+ continue;
+ }
+
+ if ($next === false) {
+ // Nothing more to read
+ break;
+ }
+
+ if ($next === ')') {
+ if ($nestingLevel > 0) {
+ $this->debug('Closing without filter: ' . $next, $nestingLevel, $op);
+ break;
+ }
+ $this->parseError($next);
+ }
+ if ($next === '(') {
+ $filters[] = $this->readFilters($nestingLevel + 1, null);
+ continue;
+ }
+ if ($next === $op) {
+ continue;
+ }
+ $this->parseError($next, "$op level $nestingLevel");
+ } else {
+ $this->debug('Got new expression: ' . $filter, $nestingLevel, $op);
+
+ $filters[] = $filter;
+
+ if ($next === false) {
+ $this->debug('Next is false, nothing to read but got filter', $nestingLevel, $op);
+ // Got filter, nothing more to read
+ break;
+ }
+
+ if ($op === '!') {
+ $this->pos--;
+ break;
+ }
+ if ($next === $op) {
+ $this->debug('Next matches operator', $nestingLevel, $op);
+ continue; // Break??
+ }
+
+ if ($next === ')') {
+ if ($nestingLevel > 0) {
+ $this->debug('Closing with filter: ' . $next, $nestingLevel, $op);
+ break;
+ }
+ $this->parseError($next);
+ }
+ if ($op === null && in_array($next, array('&', '|'))) {
+ $this->debug('Setting op to ' . $next, $nestingLevel, $op);
+ $op = $next;
+ continue;
+ }
+ $this->parseError($next);
+ }
+ }
+
+ if ($nestingLevel === 0 && $this->pos < $this->length) {
+ $this->parseError($op, 'Did not read full filter');
+ }
+
+ if ($nestingLevel === 0 && count($filters) === 1 && $op !== '!') {
+ // There is only one filter expression, no chain
+ $this->debug('Returning first filter only: ' . $filters[0], $nestingLevel, $op);
+ return $filters[0];
+ }
+
+ if ($op === null && count($filters) === 1) {
+ $this->debug('No op, single filter, setting AND', $nestingLevel, $op);
+ $op = '&';
+ }
+ $this->debug(sprintf('Got %d filters, returning', count($filters)), $nestingLevel, $op);
+
+ switch ($op) {
+ case '&':
+ return Filter::matchAll($filters);
+ case '|':
+ return Filter::matchAny($filters);
+ case '!':
+ return Filter::not($filters);
+ case null:
+ return Filter::matchAll();
+ default:
+ $this->parseError($op);
+ }
+ }
+
+ protected function parseQueryString($string)
+ {
+ $this->pos = 0;
+
+ $this->string = $string;
+
+ $this->length = $string ? strlen($string) : 0;
+
+ if ($this->length === 0) {
+ return Filter::matchAll();
+ }
+ return $this->readFilters();
+ }
+
+ protected function readUnless($char)
+ {
+ $buffer = '';
+ while (false !== ($c = $this->readChar())) {
+ if (is_array($char)) {
+ if (in_array($c, $char)) {
+ $this->pos--;
+ break;
+ }
+ } else {
+ if ($c === $char) {
+ $this->pos--;
+ break;
+ }
+ }
+ $buffer .= $c;
+ }
+
+ return $buffer;
+ }
+
+ protected function readUnlessSpecialChar()
+ {
+ return $this->readUnless(array('=', '(', ')', '&', '|', '>', '<', '!'));
+ }
+
+ protected function readExpressionOperator()
+ {
+ return $this->readUnless(array('=', '>', '<', '!'));
+ }
+
+ protected function readChar()
+ {
+ if ($this->length > $this->pos) {
+ return $this->string[$this->pos++];
+ }
+ return false;
+ }
+
+ protected function nextChar()
+ {
+ if ($this->length > $this->pos) {
+ return $this->string[$this->pos];
+ }
+ return false;
+ }
+}
diff --git a/library/Icinga/Data/FilterColumns.php b/library/Icinga/Data/FilterColumns.php
new file mode 100644
index 0000000..7eaacea
--- /dev/null
+++ b/library/Icinga/Data/FilterColumns.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+interface FilterColumns
+{
+ /**
+ * Return a filterable's filter columns with their optional label as key
+ *
+ * @return array
+ */
+ public function getFilterColumns();
+
+ /**
+ * Return a filterable's search columns
+ *
+ * @return array
+ */
+ public function getSearchColumns();
+}
diff --git a/library/Icinga/Data/Filterable.php b/library/Icinga/Data/Filterable.php
new file mode 100644
index 0000000..ceca22f
--- /dev/null
+++ b/library/Icinga/Data/Filterable.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Interface for filtering a result set
+ *
+ * @deprecated(EL): addFilter and applyFilter do the same in all usages.
+ * addFilter could be replaced w/ getFilter()->add(). We must no require classes implementing this interface to
+ * implement redundant methods over and over again. This interface must be moved to the namespace Icinga\Data\Filter.
+ * It lacks documentation.
+ */
+interface Filterable
+{
+ public function applyFilter(Filter $filter);
+
+ public function setFilter(Filter $filter);
+
+ public function getFilter();
+
+ public function addFilter(Filter $filter);
+
+ public function where($condition, $value = null);
+}
diff --git a/library/Icinga/Data/Identifiable.php b/library/Icinga/Data/Identifiable.php
new file mode 100644
index 0000000..7435026
--- /dev/null
+++ b/library/Icinga/Data/Identifiable.php
@@ -0,0 +1,17 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * Interface for objects that are identifiable by an ID of any type
+ */
+interface Identifiable
+{
+ /**
+ * Get the ID associated with this Identifiable object
+ *
+ * @return mixed
+ */
+ public function getId();
+}
diff --git a/library/Icinga/Data/Inspectable.php b/library/Icinga/Data/Inspectable.php
new file mode 100644
index 0000000..d40ce57
--- /dev/null
+++ b/library/Icinga/Data/Inspectable.php
@@ -0,0 +1,20 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * An object for which the user can retrieve status information
+ *
+ * This interface is useful for providing summaries or diagnostic information about objects
+ * to users.
+ */
+interface Inspectable
+{
+ /**
+ * Inspect this object to gain extended information about its health
+ *
+ * @return Inspection The inspection result
+ */
+ public function inspect();
+}
diff --git a/library/Icinga/Data/Inspection.php b/library/Icinga/Data/Inspection.php
new file mode 100644
index 0000000..4ee7626
--- /dev/null
+++ b/library/Icinga/Data/Inspection.php
@@ -0,0 +1,129 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Application\Logger;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Contains information about an object in the form of human-readable log entries and indicates if the object has errors
+ */
+class Inspection
+{
+ /**
+ * @var array
+ */
+ protected $log = array();
+
+ /**
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * @var string|Inspection
+ */
+ protected $error;
+
+ /**
+ * @param $description Describes the object that is being inspected
+ */
+ public function __construct($description)
+ {
+ $this->description = $description;
+ }
+
+ /**
+ * Get the name of this Inspection
+ *
+ * @return mixed
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Append the given log entry or nested inspection
+ *
+ * @throws ProgrammingError When called after erroring
+ *
+ * @param $entry string|Inspection A log entry or nested inspection
+ */
+ public function write($entry)
+ {
+ if (isset($this->error)) {
+ throw new ProgrammingError('Inspection object used after error');
+ }
+ if ($entry instanceof Inspection) {
+ $this->log[$entry->description] = $entry->toArray();
+ } else {
+ Logger::debug($entry);
+ $this->log[] = $entry;
+ }
+ }
+
+ /**
+ * Append the given log entry and fail this inspection with the given error
+ *
+ * @param $entry string|Inspection A log entry or nested inspection
+ *
+ * @throws ProgrammingError When called multiple times
+ *
+ * @return this fluent interface
+ */
+ public function error($entry)
+ {
+ if (isset($this->error)) {
+ throw new ProgrammingError('Inspection object used after error');
+ }
+ Logger::error($entry);
+ $this->log[] = $entry;
+ $this->error = $entry;
+ return $this;
+ }
+
+ /**
+ * If the inspection resulted in an error
+ *
+ * @return bool
+ */
+ public function hasError()
+ {
+ return isset($this->error);
+ }
+
+ /**
+ * The error that caused the inspection to fail
+ *
+ * @return Inspection|string
+ */
+ public function getError()
+ {
+ return $this->error;
+ }
+
+ /**
+ * Convert the inspection to an array
+ *
+ * @return array An array of strings that describe the state in a human-readable form, each array element
+ * represents one log entry about this object.
+ */
+ public function toArray()
+ {
+ return $this->log;
+ }
+
+ /**
+ * Return a text representation of the inspection log entries
+ */
+ public function __toString()
+ {
+ return sprintf(
+ 'Inspection: description: "%s" error: "%s"',
+ $this->description,
+ $this->error
+ );
+ }
+}
diff --git a/library/Icinga/Data/Limitable.php b/library/Icinga/Data/Limitable.php
new file mode 100644
index 0000000..8591a79
--- /dev/null
+++ b/library/Icinga/Data/Limitable.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * Interface for retrieving just a portion of a result set
+ */
+interface Limitable
+{
+ /**
+ * Set a limit count and offset
+ *
+ * @param int $count Number of rows to return
+ * @param int $offset Start returning after this many rows
+ *
+ * @return self
+ */
+ public function limit($count = null, $offset = null);
+
+ /**
+ * Whether a limit is set
+ *
+ * @return bool
+ */
+ public function hasLimit();
+
+ /**
+ * Get the limit if any
+ *
+ * @return int|null
+ */
+ public function getLimit();
+
+ /**
+ * Whether an offset is set
+ *
+ * @return bool
+ */
+ public function hasOffset();
+
+ /**
+ * Get the offset if any
+ *
+ * @return int|null
+ */
+ public function getOffset();
+}
diff --git a/library/Icinga/Data/Paginatable.php b/library/Icinga/Data/Paginatable.php
new file mode 100644
index 0000000..468cca2
--- /dev/null
+++ b/library/Icinga/Data/Paginatable.php
@@ -0,0 +1,10 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Countable;
+
+interface Paginatable extends Limitable, Countable
+{
+}
diff --git a/library/Icinga/Data/PivotTable.php b/library/Icinga/Data/PivotTable.php
new file mode 100644
index 0000000..6c7f806
--- /dev/null
+++ b/library/Icinga/Data/PivotTable.php
@@ -0,0 +1,396 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Application\Icinga;
+use Icinga\Web\Paginator\Adapter\QueryAdapter;
+use Zend_Paginator;
+
+class PivotTable implements Sortable
+{
+ /**
+ * The query to fetch as pivot table
+ *
+ * @var SimpleQuery
+ */
+ protected $baseQuery;
+
+ /**
+ * X-axis pivot column
+ *
+ * @var string
+ */
+ protected $xAxisColumn;
+
+ /**
+ * Y-axis pivot column
+ *
+ * @var string
+ */
+ protected $yAxisColumn;
+
+ /**
+ * Column for sorting the result set
+ *
+ * @var array
+ */
+ protected $order = array();
+
+ /**
+ * The filter being applied on the query for the x-axis
+ *
+ * @var Filter
+ */
+ protected $xAxisFilter;
+
+ /**
+ * The filter being applied on the query for the y-axis
+ *
+ * @var Filter
+ */
+ protected $yAxisFilter;
+
+ /**
+ * The query to fetch the leading x-axis rows and their headers
+ *
+ * @var SimpleQuery
+ */
+ protected $xAxisQuery;
+
+ /**
+ * The query to fetch the leading y-axis rows and their headers
+ *
+ * @var SimpleQuery
+ */
+ protected $yAxisQuery;
+
+ /**
+ * X-axis header column
+ *
+ * @var string|null
+ */
+ protected $xAxisHeader;
+
+ /**
+ * Y-axis header column
+ *
+ * @var string|null
+ */
+ protected $yAxisHeader;
+
+ /**
+ * Create a new pivot table
+ *
+ * @param SimpleQuery $query The query to fetch as pivot table
+ * @param string $xAxisColumn X-axis pivot column
+ * @param string $yAxisColumn Y-axis pivot column
+ */
+ public function __construct(SimpleQuery $query, $xAxisColumn, $yAxisColumn)
+ {
+ $this->baseQuery = $query;
+ $this->xAxisColumn = $xAxisColumn;
+ $this->yAxisColumn = $yAxisColumn;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasOrder()
+ {
+ return ! empty($this->order);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($field, $direction = null)
+ {
+ $this->order[$field] = $direction;
+ return $this;
+ }
+
+ /**
+ * Set the filter to apply on the query for the x-axis
+ *
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function setXAxisFilter(Filter $filter = null)
+ {
+ $this->xAxisFilter = $filter;
+ return $this;
+ }
+
+ /**
+ * Set the filter to apply on the query for the y-axis
+ *
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function setYAxisFilter(Filter $filter = null)
+ {
+ $this->yAxisFilter = $filter;
+ return $this;
+ }
+
+ /**
+ * Get the x-axis header
+ *
+ * Defaults to {@link $xAxisColumn} in case no x-axis header has been set using {@link setXAxisHeader()}
+ *
+ * @return string
+ */
+ public function getXAxisHeader()
+ {
+ return $this->xAxisHeader !== null ? $this->xAxisHeader : $this->xAxisColumn;
+ }
+
+ /**
+ * Set the x-axis header
+ *
+ * @param string $xAxisHeader
+ *
+ * @return $this
+ */
+ public function setXAxisHeader($xAxisHeader)
+ {
+ $this->xAxisHeader = (string) $xAxisHeader;
+ return $this;
+ }
+
+ /**
+ * Get the y-axis header
+ *
+ * Defaults to {@link $yAxisColumn} in case no x-axis header has been set using {@link setYAxisHeader()}
+ *
+ * @return string
+ */
+ public function getYAxisHeader()
+ {
+ return $this->yAxisHeader !== null ? $this->yAxisHeader : $this->yAxisColumn;
+ }
+
+ /**
+ * Set the y-axis header
+ *
+ * @param string $yAxisHeader
+ *
+ * @return $this
+ */
+ public function setYAxisHeader($yAxisHeader)
+ {
+ $this->yAxisHeader = (string) $yAxisHeader;
+ return $this;
+ }
+
+ /**
+ * Return the value for the given request parameter
+ *
+ * @param string $axis The axis for which to return the parameter ('x' or 'y')
+ * @param string $param The parameter name to return
+ * @param int $default The default value to return
+ *
+ * @return int
+ */
+ protected function getPaginationParameter($axis, $param, $default = null)
+ {
+ $request = Icinga::app()->getRequest();
+
+ $value = $request->getParam($param, '');
+ if (strpos($value, ',') > 0) {
+ $parts = explode(',', $value, 2);
+ return intval($parts[$axis === 'x' ? 0 : 1]);
+ }
+
+ return $default !== null ? $default : 0;
+ }
+
+ /**
+ * Query horizontal (x) axis
+ *
+ * @return SimpleQuery
+ */
+ protected function queryXAxis()
+ {
+ if ($this->xAxisQuery === null) {
+ $this->xAxisQuery = clone $this->baseQuery;
+ $this->xAxisQuery->clearGroupingRules();
+ $xAxisHeader = $this->getXAxisHeader();
+ $columns = array($this->xAxisColumn, $xAxisHeader);
+ $this->xAxisQuery->group(array_unique($columns)); // xAxisColumn and header may be the same column
+ $this->xAxisQuery->columns($columns);
+
+ if ($this->xAxisFilter !== null) {
+ $this->xAxisQuery->addFilter($this->xAxisFilter);
+ }
+
+ $this->xAxisQuery->order(
+ $xAxisHeader,
+ isset($this->order[$xAxisHeader]) ? $this->order[$xAxisHeader] : self::SORT_ASC
+ );
+ }
+
+ return $this->xAxisQuery;
+ }
+
+ /**
+ * Query vertical (y) axis
+ *
+ * @return SimpleQuery
+ */
+ protected function queryYAxis()
+ {
+ if ($this->yAxisQuery === null) {
+ $this->yAxisQuery = clone $this->baseQuery;
+ $this->yAxisQuery->clearGroupingRules();
+ $yAxisHeader = $this->getYAxisHeader();
+ $columns = array($this->yAxisColumn, $yAxisHeader);
+ $this->yAxisQuery->group(array_unique($columns)); // yAxisColumn and header may be the same column
+ $this->yAxisQuery->columns($columns);
+
+ if ($this->yAxisFilter !== null) {
+ $this->yAxisQuery->addFilter($this->yAxisFilter);
+ }
+
+ $this->yAxisQuery->order(
+ $yAxisHeader,
+ isset($this->order[$yAxisHeader]) ? $this->order[$yAxisHeader] : self::SORT_ASC
+ );
+ }
+ return $this->yAxisQuery;
+ }
+
+ /**
+ * Return a pagination adapter for the x-axis query
+ *
+ * $limit and $page are taken from the current request if not given.
+ *
+ * @param int $limit The maximum amount of entries to fetch
+ * @param int $page The page to set as current one
+ *
+ * @return Zend_Paginator
+ */
+ public function paginateXAxis($limit = null, $page = null)
+ {
+ if ($limit === null || $page === null) {
+ if ($limit === null) {
+ $limit = $this->getPaginationParameter('x', 'limit', 20);
+ }
+
+ if ($page === null) {
+ $page = $this->getPaginationParameter('x', 'page', 1);
+ }
+ }
+
+ $query = $this->queryXAxis();
+ $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0);
+
+ $paginator = new Zend_Paginator(new QueryAdapter($query));
+ $paginator->setItemCountPerPage($limit);
+ $paginator->setCurrentPageNumber($page);
+ return $paginator;
+ }
+
+ /**
+ * Return a pagination adapter for the y-axis query
+ *
+ * $limit and $page are taken from the current request if not given.
+ *
+ * @param int $limit The maximum amount of entries to fetch
+ * @param int $page The page to set as current one
+ *
+ * @return Zend_Paginator
+ */
+ public function paginateYAxis($limit = null, $page = null)
+ {
+ if ($limit === null || $page === null) {
+ if ($limit === null) {
+ $limit = $this->getPaginationParameter('y', 'limit', 20);
+ }
+
+ if ($page === null) {
+ $page = $this->getPaginationParameter('y', 'page', 1);
+ }
+ }
+
+ $query = $this->queryYAxis();
+ $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0);
+
+ $paginator = new Zend_Paginator(new QueryAdapter($query));
+ $paginator->setItemCountPerPage($limit);
+ $paginator->setCurrentPageNumber($page);
+ return $paginator;
+ }
+
+ /**
+ * Return the pivot table as an array of pivot data and pivot header
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ if (($this->xAxisFilter === null && $this->yAxisFilter === null)
+ || ($this->xAxisFilter !== null && $this->yAxisFilter !== null)
+ ) {
+ $xAxis = $this->queryXAxis()->fetchPairs();
+ $yAxis = $this->queryYAxis()->fetchPairs();
+ $xAxisKeys = array_keys($xAxis);
+ $yAxisKeys = array_keys($yAxis);
+ } else {
+ if ($this->xAxisFilter !== null) {
+ $xAxis = $this->queryXAxis()->fetchPairs();
+ $xAxisKeys = array_keys($xAxis);
+ $yAxis = $this->queryYAxis()->where($this->xAxisColumn, $xAxisKeys)->fetchPairs();
+ $yAxisKeys = array_keys($yAxis);
+ } else { // $this->yAxisFilter !== null
+ $yAxis = $this->queryYAxis()->fetchPairs();
+ $yAxisKeys = array_keys($yAxis);
+ $xAxis = $this->queryXAxis()->where($this->yAxisColumn, $yAxisKeys)->fetchPairs();
+ $xAxisKeys = array_keys($xAxis);
+ }
+ }
+ $pivotData = array();
+ $pivotHeader = array(
+ 'cols' => $xAxis,
+ 'rows' => $yAxis
+ );
+ if (! empty($xAxis) && ! empty($yAxis)) {
+ $this->baseQuery
+ ->where($this->xAxisColumn, array_map(
+ function ($key) {
+ return (string) $key;
+ },
+ $xAxisKeys
+ ))
+ ->where($this->yAxisColumn, array_map(
+ function ($key) {
+ return (string) $key;
+ },
+ $yAxisKeys
+ ));
+
+ foreach ($yAxisKeys as $yAxisKey) {
+ foreach ($xAxisKeys as $xAxisKey) {
+ $pivotData[$yAxisKey][$xAxisKey] = null;
+ }
+ }
+
+ foreach ($this->baseQuery as $row) {
+ $pivotData[$row->{$this->yAxisColumn}][$row->{$this->xAxisColumn}] = $row;
+ }
+ }
+ return array($pivotData, $pivotHeader);
+ }
+}
diff --git a/library/Icinga/Data/QueryInterface.php b/library/Icinga/Data/QueryInterface.php
new file mode 100644
index 0000000..e723857
--- /dev/null
+++ b/library/Icinga/Data/QueryInterface.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+interface QueryInterface extends Fetchable, Filterable, Paginatable, Sortable
+{
+}
diff --git a/library/Icinga/Data/Queryable.php b/library/Icinga/Data/Queryable.php
new file mode 100644
index 0000000..75cdc98
--- /dev/null
+++ b/library/Icinga/Data/Queryable.php
@@ -0,0 +1,20 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * Interface for specifying data sources
+ */
+interface Queryable
+{
+ /**
+ * Set the target and fields to query
+ *
+ * @param string $target
+ * @param array $fields
+ *
+ * @return Fetchable
+ */
+ public function from($target, array $fields = null);
+}
diff --git a/library/Icinga/Data/Reducible.php b/library/Icinga/Data/Reducible.php
new file mode 100644
index 0000000..6ece17e
--- /dev/null
+++ b/library/Icinga/Data/Reducible.php
@@ -0,0 +1,23 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\StatementException;
+
+/**
+ * Interface for data deletion
+ */
+interface Reducible
+{
+ /**
+ * Delete entries in the given target, optionally limiting the affected entries by using a filter
+ *
+ * @param string $target
+ * @param Filter $filter
+ *
+ * @throws StatementException
+ */
+ public function delete($target, Filter $filter = null);
+}
diff --git a/library/Icinga/Data/ResourceFactory.php b/library/Icinga/Data/ResourceFactory.php
new file mode 100644
index 0000000..5b477c7
--- /dev/null
+++ b/library/Icinga/Data/ResourceFactory.php
@@ -0,0 +1,138 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Application\Config;
+use Icinga\Util\ConfigAwareFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Data\Db\DbConnection;
+use Icinga\Protocol\Ldap\LdapConnection;
+use Icinga\Protocol\File\FileReader;
+
+/**
+ * Create resources from names or resource configuration
+ */
+class ResourceFactory implements ConfigAwareFactory
+{
+ /**
+ * Resource configuration
+ *
+ * @var Config
+ */
+ private static $resources;
+
+ /**
+ * Set resource configurations
+ *
+ * @param Config $config
+ */
+ public static function setConfig($config)
+ {
+ self::$resources = $config;
+ }
+
+ /**
+ * Get the configuration for a specific resource
+ *
+ * @param $resourceName String The resource's name
+ *
+ * @return ConfigObject The configuration of the resource
+ *
+ * @throws ConfigurationError
+ */
+ public static function getResourceConfig($resourceName)
+ {
+ self::assertResourcesExist();
+ $resourceConfig = self::$resources->getSection($resourceName);
+ if ($resourceConfig->isEmpty()) {
+ throw new ConfigurationError(
+ 'Cannot load resource config "%s". Resource does not exist',
+ $resourceName
+ );
+ }
+ return $resourceConfig;
+ }
+
+ /**
+ * Get the configuration of all existing resources, or all resources of the given type
+ *
+ * @param string $type Filter for resource type
+ *
+ * @return Config The resources configuration
+ */
+ public static function getResourceConfigs($type = null)
+ {
+ self::assertResourcesExist();
+ if ($type === null) {
+ return self::$resources;
+ }
+ $resources = array();
+ foreach (self::$resources as $name => $resource) {
+ if ($resource->get('type') === $type) {
+ $resources[$name] = $resource;
+ }
+ }
+ return Config::fromArray($resources);
+ }
+
+ /**
+ * Check if the existing resources are set. If not, load them from resources.ini
+ *
+ * @throws ConfigurationError
+ */
+ private static function assertResourcesExist()
+ {
+ if (self::$resources === null) {
+ self::$resources = Config::app('resources');
+ }
+ }
+
+ /**
+ * Create and return a resource based on the given configuration
+ *
+ * @param ConfigObject $config The configuration of the resource to create
+ *
+ * @return Selectable The resource
+ * @throws ConfigurationError In case of an unsupported type or invalid configuration
+ */
+ public static function createResource(ConfigObject $config)
+ {
+ switch (strtolower($config->type)) {
+ case 'db':
+ $resource = new DbConnection($config);
+ break;
+ case 'ldap':
+ if (empty($config->root_dn)) {
+ throw new ConfigurationError('LDAP root DN missing');
+ }
+
+ $resource = new LdapConnection($config);
+ break;
+ case 'file':
+ $resource = new FileReader($config);
+ break;
+ case 'ini':
+ $resource = Config::fromIni($config->ini);
+ break;
+ default:
+ throw new ConfigurationError(
+ 'Unsupported resource type "%s"',
+ $config->type
+ );
+ }
+
+ return $resource;
+ }
+
+ /**
+ * Create a resource from name
+ *
+ * @param string $resourceName
+ * @return DbConnection|LdapConnection
+ */
+ public static function create($resourceName)
+ {
+ return self::createResource(self::getResourceConfig($resourceName));
+ }
+}
diff --git a/library/Icinga/Data/Selectable.php b/library/Icinga/Data/Selectable.php
new file mode 100644
index 0000000..ace4e79
--- /dev/null
+++ b/library/Icinga/Data/Selectable.php
@@ -0,0 +1,17 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * Interface for classes providing a data source to fetch data from
+ */
+interface Selectable
+{
+ /**
+ * Provide a data source to fetch data from
+ *
+ * @return Queryable
+ */
+ public function select();
+}
diff --git a/library/Icinga/Data/SimpleQuery.php b/library/Icinga/Data/SimpleQuery.php
new file mode 100644
index 0000000..1ef0c27
--- /dev/null
+++ b/library/Icinga/Data/SimpleQuery.php
@@ -0,0 +1,650 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Iterator;
+use IteratorAggregate;
+use Icinga\Application\Benchmark;
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+
+class SimpleQuery implements QueryInterface, Queryable, Iterator
+{
+ /**
+ * Query data source
+ *
+ * @var mixed
+ */
+ protected $ds;
+
+ /**
+ * This query's iterator
+ *
+ * @var Iterator
+ */
+ protected $iterator;
+
+ /**
+ * The current position of this query's iterator
+ *
+ * @var int
+ */
+ protected $iteratorPosition;
+
+ /**
+ * The amount of rows previously calculated
+ *
+ * @var int
+ */
+ protected $cachedCount;
+
+ /**
+ * The target you are going to query
+ *
+ * @var mixed
+ */
+ protected $target;
+
+ /**
+ * The columns you asked for
+ *
+ * All columns if null, no column if empty??? Alias handling goes here!
+ *
+ * @var array
+ */
+ protected $desiredColumns = array();
+
+ /**
+ * The columns you are interested in
+ *
+ * All columns if null, no column if empty??? Alias handling goes here!
+ *
+ * @var array
+ */
+ protected $columns = array();
+
+ /**
+ * The columns and their aliases flipped in order to handle aliased sort columns
+ *
+ * Supposed to be used and populated by $this->compare *only*.
+ *
+ * @var array
+ */
+ protected $flippedColumns;
+
+ /**
+ * The columns you're using to sort the query result
+ *
+ * @var array
+ */
+ protected $order = array();
+
+ /**
+ * Number of rows to return
+ *
+ * @var int
+ */
+ protected $limitCount;
+
+ /**
+ * Result starts with this row
+ *
+ * @var int
+ */
+ protected $limitOffset;
+
+ /**
+ * Whether to peek ahead for more results
+ *
+ * @var bool
+ */
+ protected $peekAhead;
+
+ /**
+ * Whether the query did not yield all available results
+ *
+ * @var bool
+ */
+ protected $hasMore;
+
+ protected $filter;
+
+ /**
+ * Constructor
+ *
+ * @param mixed $ds
+ */
+ public function __construct($ds, $columns = null)
+ {
+ $this->ds = $ds;
+ $this->filter = Filter::matchAll();
+ if ($columns !== null) {
+ $this->desiredColumns = $columns;
+ }
+ $this->init();
+ if ($this->desiredColumns !== null) {
+ $this->columns($this->desiredColumns);
+ }
+ }
+
+ /**
+ * Initialize query
+ *
+ * Overwrite this instead of __construct (it's called at the end of the construct) to
+ * implement custom initialization logic on construction time
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Get the data source
+ *
+ * @return mixed
+ */
+ public function getDatasource()
+ {
+ return $this->ds;
+ }
+
+ /**
+ * Return the current position of this query's iterator
+ *
+ * @return int
+ */
+ public function getIteratorPosition()
+ {
+ return $this->iteratorPosition;
+ }
+
+ /**
+ * Start or rewind the iteration
+ */
+ public function rewind(): void
+ {
+ if ($this->iterator === null) {
+ $iterator = $this->ds->query($this);
+ if ($iterator instanceof IteratorAggregate) {
+ $this->iterator = $iterator->getIterator();
+ } else {
+ $this->iterator = $iterator;
+ }
+ }
+
+ $this->iterator->rewind();
+ $this->iteratorPosition = null;
+ Benchmark::measure('Query result iteration started');
+ }
+
+ /**
+ * Fetch and return the current row of this query's result
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return $this->iterator->current();
+ }
+
+ /**
+ * Return whether the current row of this query's result is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ $valid = $this->iterator->valid();
+ if ($valid && $this->peekAhead && $this->hasLimit() && $this->iteratorPosition + 1 === $this->getLimit()) {
+ $this->hasMore = true;
+ $valid = false; // We arrived at the last result, which is the requested extra row, so stop the iteration
+ } elseif (! $valid) {
+ $this->hasMore = false;
+ }
+
+ if (! $valid) {
+ Benchmark::measure('Query result iteration finished');
+ return false;
+ } elseif ($this->iteratorPosition === null) {
+ $this->iteratorPosition = 0;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return the key for the current row of this query's result
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->iterator->key();
+ }
+
+ /**
+ * Advance to the next row of this query's result
+ */
+ public function next(): void
+ {
+ $this->iterator->next();
+ $this->iteratorPosition += 1;
+ }
+
+ /**
+ * Choose a table and the columns you are interested in
+ *
+ * Query will return all available columns if none are given here.
+ *
+ * @param mixed $target
+ * @param array $fields
+ *
+ * @return $this
+ */
+ public function from($target, array $fields = null)
+ {
+ $this->target = $target;
+ if ($fields !== null) {
+ $this->columns($fields);
+ }
+ return $this;
+ }
+
+ /**
+ * Add a where condition to the query by and
+ *
+ * The syntax of the condition and valid values are defined by the concrete backend-specific query implementation.
+ *
+ * @param string $condition
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function where($condition, $value = null)
+ {
+ // TODO: more intelligence please
+ $this->filter->addFilter(Filter::expression($condition, '=', $value));
+ return $this;
+ }
+
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ return $this->addFilter($filter);
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ $this->filter->addFilter($filter);
+ return $this;
+ }
+
+ public function setFilter(Filter $filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ public function setOrderColumns(array $orderColumns)
+ {
+ throw new IcingaException('This function does nothing and will be removed');
+ }
+
+ /**
+ * Split order field into its field and sort direction
+ *
+ * @param string $field
+ *
+ * @return array
+ */
+ public function splitOrder($field)
+ {
+ $fieldAndDirection = explode(' ', $field, 2);
+ if (count($fieldAndDirection) === 1) {
+ $direction = null;
+ } else {
+ $field = $fieldAndDirection[0];
+ $direction = (strtoupper(trim($fieldAndDirection[1])) === 'DESC') ?
+ Sortable::SORT_DESC : Sortable::SORT_ASC;
+ }
+ return array($field, $direction);
+ }
+
+ /**
+ * Sort result set by the given field (and direction)
+ *
+ * Preferred usage:
+ * <code>
+ * $query->order('field, 'ASC')
+ * </code>
+ *
+ * @param string $field
+ * @param string $direction
+ *
+ * @return $this
+ */
+ public function order($field, $direction = null)
+ {
+ if ($direction === null) {
+ list($field, $direction) = $this->splitOrder($field);
+ if ($direction === null) {
+ $direction = Sortable::SORT_ASC;
+ }
+ } else {
+ switch (($direction = strtoupper($direction))) {
+ case Sortable::SORT_ASC:
+ case Sortable::SORT_DESC:
+ break;
+ default:
+ $direction = Sortable::SORT_ASC;
+ break;
+ }
+ }
+ $this->order[] = array($field, $direction);
+ return $this;
+ }
+
+ /**
+ * Compare $a with $b based on this query's sort rules and column aliases
+ *
+ * @param object $a
+ * @param object $b
+ * @param int $orderIndex
+ *
+ * @return int
+ */
+ public function compare($a, $b, $orderIndex = 0)
+ {
+ if (! array_key_exists($orderIndex, $this->order)) {
+ return 0; // Last column to sort reached, rows are considered being equal
+ }
+
+ if ($this->flippedColumns === null) {
+ $this->flippedColumns = array_flip($this->columns);
+ }
+
+ $column = $this->order[$orderIndex][0];
+ if (array_key_exists($column, $this->flippedColumns) && is_string($this->flippedColumns[$column])) {
+ $column = $this->flippedColumns[$column];
+ }
+
+ $result = strcmp(strtolower($a->$column ?? ''), strtolower($b->$column ?? ''));
+ if ($result === 0) {
+ return $this->compare($a, $b, ++$orderIndex);
+ }
+
+ $direction = $this->order[$orderIndex][1];
+ if ($direction === self::SORT_ASC) {
+ return $result;
+ } else {
+ return $result * -1;
+ }
+ }
+
+ /**
+ * Clear the order if any
+ *
+ * @return $this
+ */
+ public function clearOrder()
+ {
+ $this->order = array();
+ return $this;
+ }
+
+ /**
+ * Whether an order is set
+ *
+ * @return bool
+ */
+ public function hasOrder()
+ {
+ return ! empty($this->order);
+ }
+
+ /**
+ * Get the order
+ *
+ * @return array
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * Set whether this query should peek ahead for more results
+ *
+ * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will
+ * be removed from the result set. Note that this only applies when fetching multiple results of limited queries.
+ *
+ * @return $this
+ */
+ public function peekAhead($state = true)
+ {
+ $this->peekAhead = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this query did not yield all available results
+ *
+ * @return bool
+ *
+ * @throws ProgrammingError In case the query did not run yet
+ */
+ public function hasMore()
+ {
+ if ($this->hasMore === null) {
+ throw new ProgrammingError('Query did not run. Cannot determine whether there are more results.');
+ }
+
+ return $this->hasMore;
+ }
+
+ /**
+ * Return whether this query will or has yielded any result
+ *
+ * @return bool
+ */
+ public function hasResult()
+ {
+ return $this->cachedCount > 0 || $this->iteratorPosition !== null || $this->fetchRow() !== false;
+ }
+
+ /**
+ * Set a limit count and offset to the query
+ *
+ * @param int $count Number of rows to return
+ * @param int $offset Start returning after this many rows
+ *
+ * @return $this
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->limitCount = $count !== null ? (int) $count : null;
+ $this->limitOffset = (int) $offset;
+ return $this;
+ }
+
+ /**
+ * Whether a limit is set
+ *
+ * @return bool
+ */
+ public function hasLimit()
+ {
+ return $this->limitCount !== null && $this->limitCount > 0;
+ }
+
+ /**
+ * Get the limit if any
+ *
+ * @return int|null
+ */
+ public function getLimit()
+ {
+ return $this->peekAhead && $this->hasLimit() ? $this->limitCount + 1 : $this->limitCount;
+ }
+
+ /**
+ * Whether an offset is set
+ *
+ * @return bool
+ */
+ public function hasOffset()
+ {
+ return $this->limitOffset > 0;
+ }
+
+ /**
+ * Get the offset if any
+ *
+ * @return int|null
+ */
+ public function getOffset()
+ {
+ return $this->limitOffset;
+ }
+
+ /**
+ * Retrieve an array containing all rows of the result set
+ *
+ * @return array
+ */
+ public function fetchAll()
+ {
+ Benchmark::measure('Fetching all results started');
+ $results = $this->ds->fetchAll($this);
+ Benchmark::measure('Fetching all results finished');
+
+ if ($this->peekAhead && $this->hasLimit() && count($results) === $this->getLimit()) {
+ $this->hasMore = true;
+ array_pop($results);
+ } else {
+ $this->hasMore = false;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Fetch the first row of the result set
+ *
+ * @return mixed
+ */
+ public function fetchRow()
+ {
+ Benchmark::measure('Fetching one row started');
+ $row = $this->ds->fetchRow($this);
+ Benchmark::measure('Fetching one row finished');
+ return $row;
+ }
+
+ /**
+ * Fetch the first column of all rows of the result set as an array
+ *
+ * @return array
+ */
+ public function fetchColumn()
+ {
+ Benchmark::measure('Fetching one column started');
+ $values = $this->ds->fetchColumn($this);
+ Benchmark::measure('Fetching one column finished');
+
+ if ($this->peekAhead && $this->hasLimit() && count($values) === $this->getLimit()) {
+ $this->hasMore = true;
+ array_pop($values);
+ } else {
+ $this->hasMore = false;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Fetch the first column of the first row of the result set
+ *
+ * @return string
+ */
+ public function fetchOne()
+ {
+ Benchmark::measure('Fetching one value started');
+ $value = $this->ds->fetchOne($this);
+ Benchmark::measure('Fetching one value finished');
+ return $value;
+ }
+
+ /**
+ * Fetch all rows of the result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
+ *
+ * @return array
+ */
+ public function fetchPairs()
+ {
+ Benchmark::measure('Fetching pairs started');
+ $pairs = $this->ds->fetchPairs($this);
+ Benchmark::measure('Fetching pairs finished');
+
+ if ($this->peekAhead && $this->hasLimit() && count($pairs) === $this->getLimit()) {
+ $this->hasMore = true;
+ array_pop($pairs);
+ } else {
+ $this->hasMore = false;
+ }
+
+ return $pairs;
+ }
+
+ /**
+ * Count all rows of the result set, ignoring limit and offset
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ $query = clone $this;
+ $query->limit(0, 0);
+ Benchmark::measure('Counting all results started');
+ $count = $this->ds->count($query);
+ $this->cachedCount = $count;
+ Benchmark::measure('Counting all results finished');
+ return $count;
+ }
+
+ /**
+ * Set columns
+ *
+ * @param array $columns
+ *
+ * @return $this
+ */
+ public function columns(array $columns)
+ {
+ $this->columns = $columns;
+ $this->flippedColumns = null; // Reset, due to updated columns
+ return $this;
+ }
+
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Deep clone self::$filter
+ */
+ public function __clone()
+ {
+ $this->filter = clone $this->filter;
+ }
+}
diff --git a/library/Icinga/Data/SortRules.php b/library/Icinga/Data/SortRules.php
new file mode 100644
index 0000000..c93bdda
--- /dev/null
+++ b/library/Icinga/Data/SortRules.php
@@ -0,0 +1,14 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+interface SortRules
+{
+ /**
+ * Return some sort rules
+ *
+ * @return array
+ */
+ public function getSortRules();
+}
diff --git a/library/Icinga/Data/Sortable.php b/library/Icinga/Data/Sortable.php
new file mode 100644
index 0000000..11d38c3
--- /dev/null
+++ b/library/Icinga/Data/Sortable.php
@@ -0,0 +1,49 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+/**
+ * Interface for sorting a result set
+ */
+interface Sortable
+{
+ /**
+ * Sort ascending
+ */
+ const SORT_ASC = 'ASC';
+
+ /**
+ * Sort descending
+ */
+ const SORT_DESC = 'DESC';
+
+ /**
+ * Sort result set by the given field (and direction)
+ *
+ * Preferred usage:
+ * <code>
+ * $query->order('field, 'ASC')
+ * </code>
+ *
+ * @param string $field
+ * @param string $direction
+ *
+ * @return self
+ */
+ public function order($field, $direction = null);
+
+ /**
+ * Whether an order is set
+ *
+ * @return bool
+ */
+ public function hasOrder();
+
+ /**
+ * Get the order if any
+ *
+ * @return array|null
+ */
+ public function getOrder();
+}
diff --git a/library/Icinga/Data/Tree/SimpleTree.php b/library/Icinga/Data/Tree/SimpleTree.php
new file mode 100644
index 0000000..e89f589
--- /dev/null
+++ b/library/Icinga/Data/Tree/SimpleTree.php
@@ -0,0 +1,90 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Tree;
+
+use IteratorAggregate;
+use LogicException;
+use Traversable;
+
+/**
+ * A simple tree
+ */
+class SimpleTree implements IteratorAggregate
+{
+ /**
+ * Root node
+ *
+ * @var TreeNode
+ */
+ protected $sentinel;
+
+ /**
+ * Nodes
+ *
+ * @var array
+ */
+ protected $nodes = array();
+
+ /**
+ * Create a new simple tree
+ */
+ public function __construct()
+ {
+ $this->sentinel = new TreeNode();
+ }
+
+ /**
+ * Add a child node
+ *
+ * @param TreeNode $child
+ * @param TreeNode $parent
+ *
+ * @return $this
+ */
+ public function addChild(TreeNode $child, TreeNode $parent = null)
+ {
+ if ($parent === null) {
+ $parent = $this->sentinel;
+ } elseif (! isset($this->nodes[$parent->getId()])) {
+ throw new LogicException(sprintf(
+ 'Can\'t append child node %s to parent node %s: Parent node does not exist',
+ $child->getId(),
+ $parent->getId()
+ ));
+ }
+ if (isset($this->nodes[$child->getId()])) {
+ throw new LogicException(sprintf(
+ 'Can\'t append child node %s to parent node %s: Child node does already exist',
+ $child->getId(),
+ $parent->getId()
+ ));
+ }
+ $this->nodes[$child->getId()] = $child;
+ $parent->appendChild($child);
+ return $this;
+ }
+
+ /**
+ * Get a node by its ID
+ *
+ * @param mixed $id
+ *
+ * @return TreeNode|null
+ */
+ public function getNode($id)
+ {
+ if (! isset($this->nodes[$id])) {
+ return null;
+ }
+ return $this->nodes[$id];
+ }
+
+ /**
+ * @return TreeNodeIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new TreeNodeIterator($this->sentinel);
+ }
+}
diff --git a/library/Icinga/Data/Tree/TreeNode.php b/library/Icinga/Data/Tree/TreeNode.php
new file mode 100644
index 0000000..66bce79
--- /dev/null
+++ b/library/Icinga/Data/Tree/TreeNode.php
@@ -0,0 +1,109 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Tree;
+
+use Icinga\Data\Identifiable;
+
+class TreeNode implements Identifiable
+{
+ /**
+ * The node's ID
+ *
+ * @var mixed
+ */
+ protected $id;
+
+ /**
+ * The node's value
+ *
+ * @var mixed
+ */
+ protected $value;
+
+ /**
+ * The node's children
+ *
+ * @var array
+ */
+ protected $children = array();
+
+ /**
+ * Set the node's ID
+ *
+ * @param mixed $id ID of the node
+ *
+ * @return $this
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see Identifiable::getId() For the method documentation.
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set the node's value
+ *
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+ return $this;
+ }
+
+ /**
+ * Get the node's value
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Append a child node as the last child of this node
+ *
+ * @param TreeNode $child The child to append
+ *
+ * @return $this
+ */
+ public function appendChild(TreeNode $child)
+ {
+ $this->children[] = $child;
+ return $this;
+ }
+
+
+ /**
+ * Get whether the node has children
+ *
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return ! empty($this->children);
+ }
+
+ /**
+ * Get the node's children
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+}
diff --git a/library/Icinga/Data/Tree/TreeNodeIterator.php b/library/Icinga/Data/Tree/TreeNodeIterator.php
new file mode 100644
index 0000000..cffc9f4
--- /dev/null
+++ b/library/Icinga/Data/Tree/TreeNodeIterator.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data\Tree;
+
+use ArrayIterator;
+use RecursiveIterator;
+
+/**
+ * Iterator over a tree node's children
+ */
+class TreeNodeIterator implements RecursiveIterator
+{
+ /**
+ * The node's children
+ *
+ * @var array
+ */
+ protected $children;
+
+ /**
+ * Create a new iterator over a tree node's children
+ *
+ * @param TreeNode $node
+ */
+ public function __construct(TreeNode $node)
+ {
+ $this->children = new ArrayIterator($node->getChildren());
+ }
+
+ public function current(): TreeNode
+ {
+ return $this->children->current();
+ }
+
+ public function key(): int
+ {
+ return $this->children->key();
+ }
+
+ public function next(): void
+ {
+ $this->children->next();
+ }
+
+ public function rewind(): void
+ {
+ $this->children->rewind();
+ }
+
+ public function valid(): bool
+ {
+ return $this->children->valid();
+ }
+
+ public function hasChildren(): bool
+ {
+ return $this->current()->hasChildren();
+ }
+
+ public function getChildren(): TreeNodeIterator
+ {
+ return new static($this->current());
+ }
+
+ /**
+ * Get whether the iterator is empty
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return ! $this->children->count();
+ }
+}
diff --git a/library/Icinga/Data/Updatable.php b/library/Icinga/Data/Updatable.php
new file mode 100644
index 0000000..ff70b99
--- /dev/null
+++ b/library/Icinga/Data/Updatable.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Data;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\StatementException;
+
+/**
+ * Interface for data updating
+ */
+interface Updatable
+{
+ /**
+ * Update the target with the given data and optionally limit the affected entries by using a filter
+ *
+ * @param string $target
+ * @param array $data
+ * @param Filter $filter
+ *
+ * @throws StatementException
+ */
+ public function update($target, array $data, Filter $filter = null);
+}
diff --git a/library/Icinga/Date/DateFormatter.php b/library/Icinga/Date/DateFormatter.php
new file mode 100644
index 0000000..341bb4d
--- /dev/null
+++ b/library/Icinga/Date/DateFormatter.php
@@ -0,0 +1,259 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Date;
+
+/**
+ * Date formatting
+ */
+class DateFormatter
+{
+ /**
+ * Format relative
+ *
+ * @var int
+ */
+ const RELATIVE = 0;
+
+ /**
+ * Format time
+ *
+ * @var int
+ */
+ const TIME = 1;
+
+ /**
+ * Format date
+ *
+ * @var int
+ */
+ const DATE = 2;
+
+ /**
+ * Format date and time
+ *
+ * @var int
+ */
+ const DATETIME = 4;
+
+ /**
+ * Get the diff between the given time and the current time
+ *
+ * @param int|float $time
+ * @param bool $requireTime
+ *
+ * @return array
+ */
+ protected static function diff($time, $requireTime = false)
+ {
+ $invert = false;
+ $now = time();
+ $time = (int) $time;
+ $diff = $time - $now;
+ if ($diff < 0) {
+ $diff = abs($diff);
+ $invert = true;
+ }
+ if ($diff > 3600 * 24 * 3) {
+ $type = static::DATE;
+ if (date('Y') === date('Y', $time)) {
+ $formatted = date($requireTime ? 'M j H:i' : 'M j', $time);
+ } else {
+ $formatted = date($requireTime ? 'Y-m-d H:i' : 'Y-m', $time);
+ }
+ } else {
+ $minutes = floor($diff / 60);
+ if ($minutes < 60) {
+ $type = static::RELATIVE;
+ $formatted = sprintf('%dm %ds', $minutes, $diff % 60);
+ } else {
+ $hours = floor($minutes / 60);
+ if ($hours < 24) {
+ if (date('d') === date('d', $time)) {
+ $type = static::TIME;
+ $formatted = date('H:i', $time);
+ } else {
+ $type = static::DATE;
+ $formatted = date('M j H:i', $time);
+ }
+ } else {
+ $type = static::RELATIVE;
+ $formatted = sprintf('%dd %dh', floor($hours / 24), $hours % 24);
+ }
+ }
+ }
+ return array($type, $formatted, $invert);
+ }
+
+ /**
+ * Format date
+ *
+ * @param int|float $date
+ *
+ * @return string
+ */
+ public static function formatDate($date)
+ {
+ return date('Y-m-d', (int) $date);
+ }
+
+ /**
+ * Format date and time
+ *
+ * @param int|float $dateTime
+ *
+ * @return string
+ */
+ public static function formatDateTime($dateTime)
+ {
+ return date('Y-m-d H:i:s', (int) $dateTime);
+ }
+
+ /**
+ * Format a duration
+ *
+ * @param int|float $seconds Duration in seconds
+ *
+ * @return string
+ */
+ public static function formatDuration($seconds)
+ {
+ $minutes = floor((float) $seconds / 60);
+ if ($minutes < 60) {
+ $formatted = sprintf('%dm %ds', $minutes, $seconds % 60);
+ } else {
+ $hours = floor($minutes / 60);
+ if ($hours < 24) {
+ $formatted = sprintf('%dh %dm', $hours, $minutes % 60);
+ } else {
+ $formatted = sprintf('%dd %dh', floor($hours / 24), $hours % 24);
+ }
+ }
+ return $formatted;
+ }
+
+ /**
+ * Format time
+ *
+ * @param int|float $time
+ *
+ * @return string
+ */
+ public static function formatTime($time)
+ {
+ return date('H:i:s', (int) $time);
+ }
+
+ /**
+ * Format time as time ago
+ *
+ * @param int|float $time
+ * @param bool $timeOnly
+ * @param bool $requireTime
+ *
+ * @return string
+ */
+ public static function timeAgo($time, $timeOnly = false, $requireTime = false)
+ {
+ list($type, $ago, $invert) = static::diff($time, $requireTime);
+ if ($timeOnly) {
+ return $ago;
+ }
+ switch ($type) {
+ case static::DATE:
+ // Move to next case
+ case static::DATETIME:
+ $formatted = sprintf(
+ t('on %s', 'An event happened on the given date or date and time'),
+ $ago
+ );
+ break;
+ case static::RELATIVE:
+ $formatted = sprintf(
+ t('%s ago', 'An event that happened the given time interval ago'),
+ $ago
+ );
+ break;
+ case static::TIME:
+ $formatted = sprintf(t('at %s', 'An event happened at the given time'), $ago);
+ break;
+ }
+ return $formatted;
+ }
+
+ /**
+ * Format time as time since
+ *
+ * @param int|float $time
+ * @param bool $timeOnly
+ * @param bool $requireTime
+ *
+ * @return string
+ */
+ public static function timeSince($time, $timeOnly = false, $requireTime = false)
+ {
+ list($type, $since, $invert) = static::diff($time, $requireTime);
+ if ($timeOnly) {
+ return $since;
+ }
+ switch ($type) {
+ case static::RELATIVE:
+ $formatted = sprintf(
+ t('for %s', 'A status is lasting for the given time interval'),
+ $since
+ );
+ break;
+ case static::DATE:
+ // Move to next case
+ case static::DATETIME:
+ // Move to next case
+ case static::TIME:
+ $formatted = sprintf(
+ t('since %s', 'A status is lasting since the given time, date or date and time'),
+ $since
+ );
+ break;
+ }
+ return $formatted;
+ }
+
+ /**
+ * Format time as time until
+ *
+ * @param int|float $time
+ * @param bool $timeOnly
+ * @param bool $requireTime
+ *
+ * @return string
+ */
+ public static function timeUntil($time, $timeOnly = false, $requireTime = false)
+ {
+ list($type, $until, $invert) = static::diff($time, $requireTime);
+ if ($invert && $type === static::RELATIVE) {
+ $until = '-' . $until;
+ }
+ if ($timeOnly) {
+ return $until;
+ }
+ switch ($type) {
+ case static::DATE:
+ // Move to next case
+ case static::DATETIME:
+ $formatted = sprintf(
+ t('on %s', 'An event will happen on the given date or date and time'),
+ $until
+ );
+ break;
+ case static::RELATIVE:
+ $formatted = sprintf(
+ t('in %s', 'An event will happen after the given time interval has elapsed'),
+ $until
+ );
+ break;
+ case static::TIME:
+ $formatted = sprintf(t('at %s', 'An event will happen at the given time'), $until);
+ break;
+ }
+ return $formatted;
+ }
+}
diff --git a/library/Icinga/Exception/AlreadyExistsException.php b/library/Icinga/Exception/AlreadyExistsException.php
new file mode 100644
index 0000000..d70c58f
--- /dev/null
+++ b/library/Icinga/Exception/AlreadyExistsException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Exception thrown if something to add already exists
+ */
+class AlreadyExistsException extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/AuthenticationException.php b/library/Icinga/Exception/AuthenticationException.php
new file mode 100644
index 0000000..50910b8
--- /dev/null
+++ b/library/Icinga/Exception/AuthenticationException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Exception thrown if an error occurs during authentication
+ */
+class AuthenticationException extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/ConfigurationError.php b/library/Icinga/Exception/ConfigurationError.php
new file mode 100644
index 0000000..e66ec46
--- /dev/null
+++ b/library/Icinga/Exception/ConfigurationError.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Class ConfigurationError
+ * @package Icinga\Exception
+ */
+class ConfigurationError extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/Http/BaseHttpException.php b/library/Icinga/Exception/Http/BaseHttpException.php
new file mode 100644
index 0000000..cad41c6
--- /dev/null
+++ b/library/Icinga/Exception/Http/BaseHttpException.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Http;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Base class for HTTP exceptions
+ */
+class BaseHttpException extends IcingaException implements HttpExceptionInterface
+{
+ /**
+ * This exception's HTTP status code
+ *
+ * @var int
+ */
+ protected $statusCode;
+
+ /**
+ * This exception's HTTP response headers
+ *
+ * @var array
+ */
+ protected $headers;
+
+ /**
+ * Return this exception's HTTP status code
+ *
+ * @return int
+ */
+ public function getStatusCode()
+ {
+ return $this->statusCode;
+ }
+
+ /**
+ * Set this exception's HTTP response headers
+ *
+ * @param array $headers
+ *
+ * @return $this
+ */
+ public function setHeaders(array $headers)
+ {
+ $this->headers = $headers;
+ return $this;
+ }
+
+ /**
+ * Set/Add a HTTP response header
+ *
+ * @param string $name
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setHeader($name, $value)
+ {
+ $this->headers[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Return this exception's HTTP response headers
+ *
+ * @return array An array where each key is a header name and the value its value
+ */
+ public function getHeaders()
+ {
+ return $this->headers ?: array();
+ }
+}
diff --git a/library/Icinga/Exception/Http/HttpBadRequestException.php b/library/Icinga/Exception/Http/HttpBadRequestException.php
new file mode 100644
index 0000000..004eabd
--- /dev/null
+++ b/library/Icinga/Exception/Http/HttpBadRequestException.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Http;
+
+/**
+ * Exception thrown for sending a HTTP 400 response w/ a custom message
+ */
+class HttpBadRequestException extends BaseHttpException
+{
+ protected $statusCode = 400;
+}
diff --git a/library/Icinga/Exception/Http/HttpException.php b/library/Icinga/Exception/Http/HttpException.php
new file mode 100644
index 0000000..cd6b543
--- /dev/null
+++ b/library/Icinga/Exception/Http/HttpException.php
@@ -0,0 +1,25 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Http;
+
+class HttpException extends BaseHttpException
+{
+ /**
+ * Create a new HttpException
+ *
+ * @param int $statusCode HTTP status code
+ * @param string $message Exception message or exception format string
+ * @param mixed ...$arg Format string argument
+ *
+ * If there is at least one exception, the last one will be used for exception chaining.
+ */
+ public function __construct($statusCode, $message)
+ {
+ $this->statusCode = (int) $statusCode;
+
+ $args = func_get_args();
+ array_shift($args);
+ call_user_func_array('parent::__construct', $args);
+ }
+}
diff --git a/library/Icinga/Exception/Http/HttpExceptionInterface.php b/library/Icinga/Exception/Http/HttpExceptionInterface.php
new file mode 100644
index 0000000..c5e0cc7
--- /dev/null
+++ b/library/Icinga/Exception/Http/HttpExceptionInterface.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Http;
+
+interface HttpExceptionInterface
+{
+ /**
+ * Return this exception's HTTP status code
+ *
+ * @return int
+ */
+ public function getStatusCode();
+
+ /**
+ * Return this exception's HTTP response headers
+ *
+ * @return array An array where each key is a header name and the value its value
+ */
+ public function getHeaders();
+}
diff --git a/library/Icinga/Exception/Http/HttpMethodNotAllowedException.php b/library/Icinga/Exception/Http/HttpMethodNotAllowedException.php
new file mode 100644
index 0000000..4e40b6a
--- /dev/null
+++ b/library/Icinga/Exception/Http/HttpMethodNotAllowedException.php
@@ -0,0 +1,36 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Http;
+
+/**
+ * Exception thrown if the HTTP method is not allowed
+ */
+class HttpMethodNotAllowedException extends BaseHttpException
+{
+ protected $statusCode = 405;
+
+ /**
+ * Get the allowed HTTP methods
+ *
+ * @return string
+ */
+ public function getAllowedMethods()
+ {
+ $headers = $this->getHeaders();
+ return isset($headers['Allow']) ? $headers['Allow'] : null;
+ }
+
+ /**
+ * Set the allowed HTTP methods
+ *
+ * @param string $allowedMethods
+ *
+ * @return $this
+ */
+ public function setAllowedMethods($allowedMethods)
+ {
+ $this->setHeader('Allow', (string) $allowedMethods);
+ return $this;
+ }
+}
diff --git a/library/Icinga/Exception/Http/HttpNotFoundException.php b/library/Icinga/Exception/Http/HttpNotFoundException.php
new file mode 100644
index 0000000..eb91d63
--- /dev/null
+++ b/library/Icinga/Exception/Http/HttpNotFoundException.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Http;
+
+/**
+ * Exception thrown for sending a HTTP 404 response w/ a custom message
+ */
+class HttpNotFoundException extends BaseHttpException
+{
+ protected $statusCode = 404;
+}
diff --git a/library/Icinga/Exception/IcingaException.php b/library/Icinga/Exception/IcingaException.php
new file mode 100644
index 0000000..a87ea4a
--- /dev/null
+++ b/library/Icinga/Exception/IcingaException.php
@@ -0,0 +1,107 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+use Exception;
+use ReflectionClass;
+
+class IcingaException extends Exception
+{
+ /**
+ * Create a new exception
+ *
+ * @param string $message Exception message or exception format string
+ * @param mixed ...$arg Format string argument
+ *
+ * If there is at least one exception, the last one will be used for exception chaining.
+ */
+ public function __construct($message)
+ {
+ $args = array_slice(func_get_args(), 1);
+ $exc = null;
+ foreach ($args as &$arg) {
+ if ($arg instanceof Exception) {
+ $exc = $arg;
+ }
+ }
+ parent::__construct(vsprintf($message, $args), 0, $exc);
+ }
+
+ /**
+ * Create the exception from an array of arguments
+ *
+ * @param array $args
+ *
+ * @return static
+ */
+ public static function create(array $args)
+ {
+ $e = new ReflectionClass(get_called_class());
+ return $e->newInstanceArgs($args);
+ }
+
+ /**
+ * Return the given exception formatted as one-liner
+ *
+ * The format used is: %class% in %path%:%line% with message: %message%
+ *
+ * @param Exception $exception
+ *
+ * @return string
+ */
+ public static function describe(Exception $exception)
+ {
+ return sprintf(
+ '%s in %s:%d with message: %s',
+ get_class($exception),
+ $exception->getFile(),
+ $exception->getLine(),
+ $exception->getMessage()
+ );
+ }
+
+ /**
+ * Return the same as {@link Exception::getTraceAsString()} for the given exception,
+ * but show only the types of scalar arguments
+ *
+ * @param Exception $exception
+ *
+ * @return string
+ */
+ public static function getConfidentialTraceAsString(Exception $exception)
+ {
+ $trace = array();
+
+ foreach ($exception->getTrace() as $index => $frame) {
+ $trace[] = isset($frame['file'])
+ ? "#{$index} {$frame['file']}({$frame['line']}): "
+ : "#{$index} [internal function]: ";
+
+ if (isset($frame['class'])) {
+ $trace[] = $frame['class'];
+ }
+
+ if (isset($frame['type'])) {
+ $trace[] = $frame['type'];
+ }
+
+ $trace[] = "{$frame['function']}(";
+
+ if (isset($frame['args'])) {
+ $args = array();
+ foreach ($frame['args'] as $arg) {
+ $type = gettype($arg);
+ $args[] = $type === 'object' ? 'Object(' . get_class($arg) . ')' : ucfirst($type);
+ }
+
+ $trace[] = implode(', ', $args);
+ }
+ $trace[] = ")\n";
+ }
+
+ $trace[] = '#' . ($index + 1) . ' {main}';
+
+ return implode($trace);
+ }
+}
diff --git a/library/Icinga/Exception/InvalidPropertyException.php b/library/Icinga/Exception/InvalidPropertyException.php
new file mode 100644
index 0000000..e7bcf32
--- /dev/null
+++ b/library/Icinga/Exception/InvalidPropertyException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Exception thrown if a property does not exist
+ */
+class InvalidPropertyException extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/Json/JsonDecodeException.php b/library/Icinga/Exception/Json/JsonDecodeException.php
new file mode 100644
index 0000000..978eb30
--- /dev/null
+++ b/library/Icinga/Exception/Json/JsonDecodeException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Json;
+
+/**
+ * Exception thrown by {@link \Icinga\Util\Json::decode()} on failure
+ */
+class JsonDecodeException extends JsonException
+{
+}
diff --git a/library/Icinga/Exception/Json/JsonEncodeException.php b/library/Icinga/Exception/Json/JsonEncodeException.php
new file mode 100644
index 0000000..0bcc6c0
--- /dev/null
+++ b/library/Icinga/Exception/Json/JsonEncodeException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Json;
+
+/**
+ * Exception thrown by {@link \Icinga\Util\Json::encode()} on failure
+ */
+class JsonEncodeException extends JsonException
+{
+}
diff --git a/library/Icinga/Exception/Json/JsonException.php b/library/Icinga/Exception/Json/JsonException.php
new file mode 100644
index 0000000..2ca3605
--- /dev/null
+++ b/library/Icinga/Exception/Json/JsonException.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception\Json;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown by {@link \Icinga\Util\Json} on failure
+ */
+abstract class JsonException extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/MissingParameterException.php b/library/Icinga/Exception/MissingParameterException.php
new file mode 100644
index 0000000..a8bd78d
--- /dev/null
+++ b/library/Icinga/Exception/MissingParameterException.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Exception thrown if a mandatory parameter was not given
+ */
+class MissingParameterException extends IcingaException
+{
+ /**
+ * Name of the missing parameter
+ *
+ * @var string
+ */
+ protected $parameter;
+
+ /**
+ * Get the name of the missing parameter
+ *
+ * @return string
+ */
+ public function getParameter()
+ {
+ return $this->parameter;
+ }
+
+ /**
+ * Set the name of the missing parameter
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setParameter($name)
+ {
+ $this->parameter = (string) $name;
+ return $this;
+ }
+}
diff --git a/library/Icinga/Exception/NotFoundError.php b/library/Icinga/Exception/NotFoundError.php
new file mode 100644
index 0000000..74e6941
--- /dev/null
+++ b/library/Icinga/Exception/NotFoundError.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+class NotFoundError extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/NotImplementedError.php b/library/Icinga/Exception/NotImplementedError.php
new file mode 100644
index 0000000..395b4b2
--- /dev/null
+++ b/library/Icinga/Exception/NotImplementedError.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Class NotImplementedError
+ * @package Icinga\Exception
+ */
+class NotImplementedError extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/NotReadableError.php b/library/Icinga/Exception/NotReadableError.php
new file mode 100644
index 0000000..6bf2b3c
--- /dev/null
+++ b/library/Icinga/Exception/NotReadableError.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+class NotReadableError extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/NotWritableError.php b/library/Icinga/Exception/NotWritableError.php
new file mode 100644
index 0000000..efe1fbb
--- /dev/null
+++ b/library/Icinga/Exception/NotWritableError.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+class NotWritableError extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/ProgrammingError.php b/library/Icinga/Exception/ProgrammingError.php
new file mode 100644
index 0000000..02d4b47
--- /dev/null
+++ b/library/Icinga/Exception/ProgrammingError.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Class ProgrammingError
+ * @package Icinga\Exception
+ */
+class ProgrammingError extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/QueryException.php b/library/Icinga/Exception/QueryException.php
new file mode 100644
index 0000000..9344b86
--- /dev/null
+++ b/library/Icinga/Exception/QueryException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Exception thrown if a query encountered an error
+ */
+class QueryException extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/StatementException.php b/library/Icinga/Exception/StatementException.php
new file mode 100644
index 0000000..7501c86
--- /dev/null
+++ b/library/Icinga/Exception/StatementException.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+class StatementException extends IcingaException
+{
+}
diff --git a/library/Icinga/Exception/SystemPermissionException.php b/library/Icinga/Exception/SystemPermissionException.php
new file mode 100644
index 0000000..5651169
--- /dev/null
+++ b/library/Icinga/Exception/SystemPermissionException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Exception;
+
+/**
+ * Handle problems according to file system permissions
+ */
+class SystemPermissionException extends IcingaException
+{
+}
diff --git a/library/Icinga/File/Csv.php b/library/Icinga/File/Csv.php
new file mode 100644
index 0000000..9379241
--- /dev/null
+++ b/library/Icinga/File/Csv.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File;
+
+use Traversable;
+
+class Csv
+{
+ protected $query;
+
+ protected function __construct()
+ {
+ }
+
+ public static function fromQuery(Traversable $query)
+ {
+ $csv = new static();
+ $csv->query = $query;
+ return $csv;
+ }
+
+ public function dump()
+ {
+ header('Content-type: text/csv');
+ echo (string) $this;
+ }
+
+ public function __toString()
+ {
+ $first = true;
+ $csv = '';
+ foreach ($this->query as $row) {
+ if ($first) {
+ $csv .= implode(',', array_keys((array) $row)) . "\r\n";
+ $first = false;
+ }
+ $out = array();
+ foreach ($row as & $val) {
+ $out[] = '"' . $val . '"';
+ }
+ $csv .= implode(',', $out) . "\r\n";
+ }
+
+ return $csv;
+ }
+}
diff --git a/library/Icinga/File/Ini/Dom/Comment.php b/library/Icinga/File/Ini/Dom/Comment.php
new file mode 100644
index 0000000..c202d0f
--- /dev/null
+++ b/library/Icinga/File/Ini/Dom/Comment.php
@@ -0,0 +1,37 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Ini\Dom;
+
+/**
+ * A single comment-line in an INI file
+ */
+class Comment
+{
+ /**
+ * The comment text
+ *
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * Set the text content of this comment
+ *
+ * @param $content
+ */
+ public function setContent($content)
+ {
+ $this->content = $content;
+ }
+
+ /**
+ * Render this comment into INI markup
+ *
+ * @return string
+ */
+ public function render()
+ {
+ return ';' . $this->content;
+ }
+}
diff --git a/library/Icinga/File/Ini/Dom/Directive.php b/library/Icinga/File/Ini/Dom/Directive.php
new file mode 100644
index 0000000..4279a5f
--- /dev/null
+++ b/library/Icinga/File/Ini/Dom/Directive.php
@@ -0,0 +1,166 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Ini\Dom;
+
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * A key value pair in a Section
+ */
+class Directive
+{
+ /**
+ * The value of this configuration directive
+ *
+ * @var string
+ */
+ protected $key;
+
+ /**
+ * The immutable name of this configuration directive
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * Comments added one line before this directive
+ *
+ * @var Comment[] The comment lines
+ */
+ protected $commentsPre = null;
+
+ /**
+ * Comment added at the end of the same line
+ *
+ * @var Comment
+ */
+ protected $commentPost = null;
+
+ /**
+ * @param string $key The name of this configuration directive
+ *
+ * @throws ConfigurationError
+ */
+ public function __construct($key)
+ {
+ $this->key = trim($key);
+ if (strlen($this->key) < 1) {
+ throw new ConfigurationError(sprintf('Ini error: empty directive key.'));
+ }
+ }
+
+ /**
+ * Return the name of this directive
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * Return the value of this configuration directive
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the value of this configuration directive
+ *
+ * @param string $value
+ */
+ public function setValue($value)
+ {
+ $this->value = trim($value);
+ }
+
+ /**
+ * Set the comments to be rendered on the line before this directive
+ *
+ * @param Comment[] $comments
+ */
+ public function setCommentsPre(array $comments)
+ {
+ $this->commentsPre = $comments;
+ }
+
+ /**
+ * Return the comments to be rendered on the line before this directive
+ *
+ * @return Comment[]
+ */
+ public function getCommentsPre()
+ {
+ return $this->commentsPre;
+ }
+
+ /**
+ * Set the comment rendered on the same line of this directive
+ *
+ * @param Comment $comment
+ */
+ public function setCommentPost(Comment $comment)
+ {
+ $this->commentPost = $comment;
+ }
+
+ /**
+ * Render this configuration directive into INI markup
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $str = '';
+ if (! empty($this->commentsPre)) {
+ $comments = array();
+ foreach ($this->commentsPre as $comment) {
+ $comments[] = $comment->render();
+ }
+ $str = implode(PHP_EOL, $comments) . PHP_EOL;
+ }
+ $str .= sprintf('%s = "%s"', $this->sanitizeKey($this->key), $this->sanitizeValue($this->value));
+ if (isset($this->commentPost)) {
+ $str .= ' ' . $this->commentPost->render();
+ }
+ return $str;
+ }
+
+ /**
+ * Assure that the given identifier contains no newlines and pending or trailing whitespaces
+ *
+ * @param $str The string to sanitize
+ *
+ * @return string
+ */
+ protected function sanitizeKey($str)
+ {
+ return trim(str_replace(PHP_EOL, ' ', $str));
+ }
+
+ /**
+ * Escape the significant characters in directive values, normalize line breaks and assure that
+ * the character contains no linebreaks
+ *
+ * @param $str The string to sanitize
+ *
+ * @return mixed|string
+ */
+ protected function sanitizeValue($str)
+ {
+ $str = trim($str);
+ $str = str_replace('\\', '\\\\', $str);
+ $str = str_replace('"', '\"', $str);
+ $str = str_replace("\r", '\r', $str);
+ $str = str_replace("\n", '\n', $str);
+
+ return $str;
+ }
+}
diff --git a/library/Icinga/File/Ini/Dom/Document.php b/library/Icinga/File/Ini/Dom/Document.php
new file mode 100644
index 0000000..f38f33e
--- /dev/null
+++ b/library/Icinga/File/Ini/Dom/Document.php
@@ -0,0 +1,132 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Ini\Dom;
+
+class Document
+{
+ /**
+ * The sections of this INI file
+ *
+ * @var Section[]
+ */
+ protected $sections = array();
+
+ /**
+ * The comemnts at file end that belong to no particular section
+ *
+ * @var Comment[]
+ */
+ protected $commentsDangling;
+
+ /**
+ * Append a section to the end of this INI file
+ *
+ * @param Section $section
+ */
+ public function addSection(Section $section)
+ {
+ $this->sections[$section->getName()] = $section;
+ }
+
+ /**
+ * Return whether this INI file has the section with the given key
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function hasSection($name)
+ {
+ return isset($this->sections[trim($name)]);
+ }
+
+ /**
+ * Return the section with the given name
+ *
+ * @param string $name
+ *
+ * @return Section
+ */
+ public function getSection($name)
+ {
+ return $this->sections[trim($name)];
+ }
+
+ /**
+ * Set the section with the given name
+ *
+ * @param string $name
+ * @param Section $section
+ *
+ * @return Section
+ */
+ public function setSection($name, Section $section)
+ {
+ return $this->sections[trim($name)] = $section;
+ }
+
+ /**
+ * Remove the section with the given name
+ *
+ * @param string $name
+ */
+ public function removeSection($name)
+ {
+ unset($this->sections[trim($name)]);
+ }
+
+ /**
+ * Set the dangling comments at file end that belong to no particular directive
+ *
+ * @param Comment[] $comments
+ */
+ public function setCommentsDangling(array $comments)
+ {
+ $this->commentsDangling = $comments;
+ }
+
+ /**
+ * Get the dangling comments at file end that belong to no particular directive
+ *
+ * @return array
+ */
+ public function getCommentsDangling()
+ {
+ return $this->commentsDangling;
+ }
+
+ /**
+ * Render this document into the corresponding INI markup
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $sections = array();
+ foreach ($this->sections as $section) {
+ $sections []= $section->render();
+ }
+ $str = implode(PHP_EOL, $sections);
+ if (! empty($this->commentsDangling)) {
+ foreach ($this->commentsDangling as $comment) {
+ $str .= PHP_EOL . $comment->render();
+ }
+ }
+ return $str;
+ }
+
+ /**
+ * Convert $this to an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $a = array();
+ foreach ($this->sections as $section) {
+ $a[$section->getName()] = $section->toArray();
+ }
+ return $a;
+ }
+}
diff --git a/library/Icinga/File/Ini/Dom/Section.php b/library/Icinga/File/Ini/Dom/Section.php
new file mode 100644
index 0000000..f877c87
--- /dev/null
+++ b/library/Icinga/File/Ini/Dom/Section.php
@@ -0,0 +1,190 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Ini\Dom;
+
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * A section in an INI file
+ */
+class Section
+{
+ /**
+ * The immutable name of this section
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * All configuration directives of this section
+ *
+ * @var Directive[]
+ */
+ protected $directives = array();
+
+ /**
+ * Comments added one line before this section
+ *
+ * @var Comment[]
+ */
+ protected $commentsPre;
+
+ /**
+ * Comment added at the end of the same line
+ *
+ * @var string
+ */
+ protected $commentPost;
+
+ /**
+ * @param string $name The immutable name of this section
+ *
+ * @throws ConfigurationError When the section name is empty or contains brackets
+ */
+ public function __construct($name)
+ {
+ $this->name = trim($name);
+ if (strlen($this->name) < 1) {
+ throw new ConfigurationError('Ini file error: empty section identifier');
+ } elseif (strpos($name, '[') !== false || strpos($name, ']') !== false) {
+ throw new ConfigurationError(
+ 'Ini file error: Section name "%s" must not contain any brackets ([, ])',
+ $name
+ );
+ }
+ }
+
+ /**
+ * Append a directive to the end of this section
+ *
+ * @param Directive $directive The directive to append
+ */
+ public function addDirective(Directive $directive)
+ {
+ $this->directives[$directive->getKey()] = $directive;
+ }
+
+ /**
+ * Remove the directive with the given name
+ *
+ * @param string $key They name of the directive to remove
+ */
+ public function removeDirective($key)
+ {
+ unset($this->directives[$key]);
+ }
+
+ /**
+ * Return whether this section has a directive with the given key
+ *
+ * @param string $key The name of the directive
+ *
+ * @return bool
+ */
+ public function hasDirective($key)
+ {
+ return isset($this->directives[$key]);
+ }
+
+ /**
+ * Get the directive with the given key
+ *
+ * @param $key string
+ *
+ * @return Directive
+ */
+ public function getDirective($key)
+ {
+ return $this->directives[$key];
+ }
+
+ /**
+ * Return the name of this section
+ *
+ * @return string The name
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the comments to be rendered on the line before this section
+ *
+ * @param Comment[] $comments
+ */
+ public function setCommentsPre(array $comments)
+ {
+ $this->commentsPre = $comments;
+ }
+
+ /**
+ * Set the comment rendered on the same line of this section
+ *
+ * @param Comment $comment
+ */
+ public function setCommentPost(Comment $comment)
+ {
+ $this->commentPost = $comment;
+ }
+
+ /**
+ * Render this section into INI markup
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $dirs = '';
+ $i = 0;
+ foreach ($this->directives as $directive) {
+ $comments = $directive->getCommentsPre();
+ $dirs .= (($i++ > 0 && ! empty($comments)) ? PHP_EOL : '')
+ . $directive->render() . PHP_EOL;
+ }
+ $cms = '';
+ if (! empty($this->commentsPre)) {
+ foreach ($this->commentsPre as $comment) {
+ $comments[] = $comment->render();
+ }
+ $cms = implode(PHP_EOL, $comments) . PHP_EOL;
+ }
+ $post = '';
+ if (isset($this->commentPost)) {
+ $post = ' ' . $this->commentPost->render();
+ }
+ return $cms . sprintf('[%s]', $this->sanitize($this->name)) . $post . PHP_EOL . $dirs;
+ }
+
+ /**
+ * Escape the significant characters in sections and normalize line breaks
+ *
+ * @param $str The string to sanitize
+ *
+ * @return mixed
+ */
+ protected function sanitize($str)
+ {
+ $str = trim($str);
+ $str = str_replace('\\', '\\\\', $str);
+ $str = str_replace('"', '\\"', $str);
+ $str = str_replace(';', '\\;', $str);
+ return str_replace(PHP_EOL, ' ', $str);
+ }
+
+ /**
+ * Convert $this to an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $a = array();
+ foreach ($this->directives as $directive) {
+ $a[$directive->getKey()] = $directive->getValue();
+ }
+ return $a;
+ }
+}
diff --git a/library/Icinga/File/Ini/IniParser.php b/library/Icinga/File/Ini/IniParser.php
new file mode 100644
index 0000000..279aa45
--- /dev/null
+++ b/library/Icinga/File/Ini/IniParser.php
@@ -0,0 +1,310 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Ini;
+
+use ErrorException;
+use Icinga\File\Ini\Dom\Section;
+use Icinga\File\Ini\Dom\Comment;
+use Icinga\File\Ini\Dom\Document;
+use Icinga\File\Ini\Dom\Directive;
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Application\Config;
+
+class IniParser
+{
+ const LINE_START = 0;
+ const SECTION = 1;
+ const ESCAPE = 2;
+ const DIRECTIVE_KEY = 4;
+ const DIRECTIVE_VALUE_START = 5;
+ const DIRECTIVE_VALUE = 6;
+ const DIRECTIVE_VALUE_QUOTED = 7;
+ const COMMENT = 8;
+ const COMMENT_END = 9;
+ const LINE_END = 10;
+
+ /**
+ * Cancel the parsing with an error
+ *
+ * @param $message The error description
+ * @param $line The line in which the error occured
+ *
+ * @throws ConfigurationError
+ */
+ private static function throwParseError($message, $line)
+ {
+ throw new ConfigurationError(sprintf('Ini parser error: %s. (l. %d)', $message, $line));
+ }
+
+ /**
+ * Read the ini file contained in a string and return a mutable DOM that can be used
+ * to change the content of an INI file.
+ *
+ * @param $str A string containing the whole ini file
+ *
+ * @return Document The mutable DOM object.
+ * @throws ConfigurationError In case the file is not parseable
+ */
+ public static function parseIni($str)
+ {
+ $doc = new Document();
+ $sec = null;
+ $dir = null;
+ $coms = array();
+ $state = self::LINE_START;
+ $escaping = null;
+ $token = '';
+ $line = 0;
+
+ for ($i = 0; $i < strlen($str); $i++) {
+ $s = $str[$i];
+ switch ($state) {
+ case self::LINE_START:
+ if (ctype_space($s)) {
+ continue 2;
+ }
+ switch ($s) {
+ case '[':
+ $state = self::SECTION;
+ break;
+ case ';':
+ $state = self::COMMENT;
+ break;
+ default:
+ $state = self::DIRECTIVE_KEY;
+ $token = $s;
+ break;
+ }
+ break;
+
+ case self::ESCAPE:
+ $token .= $s;
+ $state = $escaping;
+ $escaping = null;
+ break;
+
+ case self::SECTION:
+ if ($s === "\n") {
+ self::throwParseError('Unterminated SECTION', $line);
+ } elseif ($s === '\\') {
+ $state = self::ESCAPE;
+ $escaping = self::SECTION;
+ } elseif ($s !== ']') {
+ $token .= $s;
+ } else {
+ $sec = new Section($token);
+ $sec->setCommentsPre($coms);
+ $doc->addSection($sec);
+ $dir = null;
+ $coms = array();
+
+ $state = self::LINE_END;
+ $token = '';
+ }
+ break;
+
+ case self::DIRECTIVE_KEY:
+ if ($s !== '=') {
+ $token .= $s;
+ } else {
+ $dir = new Directive($token);
+ $dir->setCommentsPre($coms);
+ if (isset($sec)) {
+ $sec->addDirective($dir);
+ } else {
+ Logger::warning(sprintf(
+ 'Ini parser warning: section-less directive "%s" ignored. (l. %d)',
+ $token,
+ $line
+ ));
+ }
+
+ $coms = array();
+ $state = self::DIRECTIVE_VALUE_START;
+ $token = '';
+ }
+ break;
+
+ case self::DIRECTIVE_VALUE_START:
+ if (ctype_space($s)) {
+ continue 2;
+ } elseif ($s === '"') {
+ $state = self::DIRECTIVE_VALUE_QUOTED;
+ } else {
+ $state = self::DIRECTIVE_VALUE;
+ $token = $s;
+ }
+ break;
+
+ case self::DIRECTIVE_VALUE:
+ /*
+ Escaping non-quoted values is not supported by php_parse_ini, it might
+ be reasonable to include in case we are switching completely our own
+ parser implementation
+ */
+ if ($s === "\n" || $s === ";") {
+ $dir->setValue($token);
+ $token = '';
+
+ if ($s === "\n") {
+ $state = self::LINE_START;
+ $line ++;
+ } elseif ($s === ';') {
+ $state = self::COMMENT;
+ }
+ } else {
+ $token .= $s;
+ }
+ break;
+
+ case self::DIRECTIVE_VALUE_QUOTED:
+ if ($s === '\\') {
+ $state = self::ESCAPE;
+ $escaping = self::DIRECTIVE_VALUE_QUOTED;
+ } elseif ($s !== '"') {
+ $token .= $s;
+ } else {
+ $dir->setValue($token);
+ $token = '';
+ $state = self::LINE_END;
+ }
+ break;
+
+ case self::COMMENT:
+ case self::COMMENT_END:
+ if ($s !== "\n") {
+ $token .= $s;
+ } else {
+ $com = new Comment();
+ $com->setContent($token);
+ $token = '';
+
+ // Comments at the line end belong to the current line's directive or section. Comments
+ // on empty lines belong to the next directive that shows up.
+ if ($state === self::COMMENT_END) {
+ if (isset($dir)) {
+ $dir->setCommentPost($com);
+ } else {
+ $sec->setCommentPost($com);
+ }
+ } else {
+ $coms[] = $com;
+ }
+ $state = self::LINE_START;
+ $line ++;
+ }
+ break;
+
+ case self::LINE_END:
+ if ($s === "\n") {
+ $state = self::LINE_START;
+ $line ++;
+ } elseif ($s === ';') {
+ $state = self::COMMENT_END;
+ }
+ break;
+ }
+ }
+
+ // process the last token
+ switch ($state) {
+ case self::COMMENT:
+ case self::COMMENT_END:
+ $com = new Comment();
+ $com->setContent($token);
+ if ($state === self::COMMENT_END) {
+ if (isset($dir)) {
+ $dir->setCommentPost($com);
+ } else {
+ $sec->setCommentPost($com);
+ }
+ } else {
+ $coms[] = $com;
+ }
+ break;
+
+ case self::DIRECTIVE_VALUE:
+ $dir->setValue($token);
+ $sec->addDirective($dir);
+ break;
+
+ case self::ESCAPE:
+ case self::DIRECTIVE_VALUE_QUOTED:
+ case self::DIRECTIVE_KEY:
+ case self::SECTION:
+ self::throwParseError('File ended in unterminated state ' . $state, $line);
+ }
+ if (! empty($coms)) {
+ $doc->setCommentsDangling($coms);
+ }
+ return $doc;
+ }
+
+ /**
+ * Read the ini file and parse it with ::parseIni()
+ *
+ * @param string $file The ini file to read
+ *
+ * @return Config
+ * @throws NotReadableError When the file cannot be read
+ */
+ public static function parseIniFile($file)
+ {
+ if (($path = realpath($file)) === false) {
+ throw new NotReadableError('Couldn\'t compute the absolute path of `%s\'', $file);
+ }
+
+ if (($content = file_get_contents($path)) === false) {
+ throw new NotReadableError('Couldn\'t read the file `%s\'', $path);
+ }
+
+ try {
+ $configArray = parse_ini_string($content, true, INI_SCANNER_RAW);
+ } catch (ErrorException $e) {
+ throw new ConfigurationError('Couldn\'t parse the INI file `%s\'', $path, $e);
+ }
+
+ $unescaped = array();
+ foreach ($configArray as $section => $options) {
+ $unescaped[self::unescapeSectionName($section)] = array_map([__CLASS__, 'unescapeOptionValue'], $options);
+ }
+
+ return Config::fromArray($unescaped)->setConfigFile($file);
+ }
+
+ /**
+ * Unescape significant characters in the given section name
+ *
+ * @param string $str
+ *
+ * @return string
+ */
+ protected static function unescapeSectionName($str)
+ {
+ $str = str_replace('\"', '"', $str);
+ $str = str_replace('\;', ';', $str);
+
+ return str_replace('\\\\', '\\', $str);
+ }
+
+ /**
+ * Unescape significant characters in the given option value
+ *
+ * @param string $str
+ *
+ * @return string
+ */
+ protected static function unescapeOptionValue($str)
+ {
+ $str = str_replace('\n', "\n", $str);
+ $str = str_replace('\r', "\r", $str);
+ $str = str_replace('\"', '"', $str);
+ $str = str_replace('\\\\', '\\', $str);
+
+ // This replacement is a work-around for PHP bug #76965. Fixed with versions 7.1.24, 7.2.12 and 7.3.0.
+ return preg_replace('~^([\'"])(.*?)\1\s+$~', '$2', $str);
+ }
+}
diff --git a/library/Icinga/File/Ini/IniWriter.php b/library/Icinga/File/Ini/IniWriter.php
new file mode 100644
index 0000000..f00040e
--- /dev/null
+++ b/library/Icinga/File/Ini/IniWriter.php
@@ -0,0 +1,205 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Ini;
+
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\ProgrammingError;
+use Icinga\File\Ini\Dom\Directive;
+use Icinga\File\Ini\Dom\Document;
+use Icinga\File\Ini\Dom\Section;
+use Zend_Config_Exception;
+use Icinga\Application\Config;
+
+/**
+ * A INI file adapter that respects the file structure and the comments of already existing ini files
+ */
+class IniWriter
+{
+ /**
+ * Stores the options
+ *
+ * @var array
+ */
+ protected $options;
+
+ /**
+ * The configuration object to write
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * The mode to set on new files
+ *
+ * @var int
+ */
+ protected $fileMode;
+
+ /**
+ * The path to write to
+ *
+ * @var string
+ */
+ protected $filename;
+
+ /**
+ * Create a new INI writer
+ *
+ * @param Config $config The configuration to write
+ * @param string $filename The file name to write to
+ * @param int $filemode Octal file persmissions
+ *
+ * @link http://framework.zend.com/apidoc/1.12/files/Config.Writer.html#\Zend_Config_Writer
+ */
+ public function __construct(Config $config, $filename, $filemode = 0660, $options = array())
+ {
+ $this->config = $config;
+ $this->filename = $filename;
+ $this->fileMode = $filemode;
+ $this->options = $options;
+ }
+
+ /**
+ * Render the Zend_Config into a config filestring
+ *
+ * @return string
+ */
+ public function render()
+ {
+ if (file_exists($this->filename)) {
+ $oldconfig = Config::fromIni($this->filename);
+ $content = trim(file_get_contents($this->filename));
+ } else {
+ $oldconfig = Config::fromArray(array());
+ $content = '';
+ }
+ $doc = IniParser::parseIni($content);
+ $this->diffPropertyUpdates($this->config, $doc);
+ $this->diffPropertyDeletions($oldconfig, $this->config, $doc);
+ $doc = $this->updateSectionOrder($this->config, $doc);
+ return $doc->render();
+ }
+
+ /**
+ * Write configuration to file and set file mode in case it does not exist yet
+ *
+ * @param string $filename
+ * @param bool $exclusiveLock
+ *
+ * @throws Zend_Config_Exception
+ */
+ public function write($filename = null, $exclusiveLock = false)
+ {
+ $filePath = isset($filename) ? $filename : $this->filename;
+ $setMode = false === file_exists($filePath);
+
+ if (file_put_contents($filePath, $this->render(), $exclusiveLock ? LOCK_EX : 0) === false) {
+ throw new Zend_Config_Exception('Could not write to file "' . $filePath . '"');
+ }
+
+ if ($setMode) {
+ // file was newly created
+ $mode = $this->fileMode;
+ if (is_int($this->fileMode) && false === @chmod($filePath, $this->fileMode)) {
+ throw new Zend_Config_Exception(sprintf('Failed to set file mode "%o" on file "%s"', $mode, $filePath));
+ }
+ }
+ }
+
+ /**
+ * Update the order of the sections in the ini file to match the order of the new config
+ *
+ * @return Document A new document with the changed section order applied
+ */
+ protected function updateSectionOrder(Config $newconfig, Document $oldDoc)
+ {
+ $doc = new Document();
+ $dangling = $oldDoc->getCommentsDangling();
+ if (isset($dangling)) {
+ $doc->setCommentsDangling($dangling);
+ }
+ foreach ($newconfig->toArray() as $section => $directives) {
+ $doc->addSection($oldDoc->getSection($section));
+ }
+ return $doc;
+ }
+
+ /**
+ * Search for created and updated properties and use the editor to create or update these entries
+ *
+ * @param Config $newconfig The config representing the state after the change
+ * @param Document $doc
+ *
+ * @throws ProgrammingError
+ */
+ protected function diffPropertyUpdates(Config $newconfig, Document $doc)
+ {
+ foreach ($newconfig->toArray() as $section => $directives) {
+ if (! is_array($directives)) {
+ Logger::warning('Section-less property ' . (string)$directives . ' was ignored.');
+ continue;
+ }
+ if (!$doc->hasSection($section)) {
+ $domSection = new Section($section);
+ $doc->addSection($domSection);
+ } else {
+ $domSection = $doc->getSection($section);
+ }
+ foreach ($directives as $key => $value) {
+ if ($value === null) {
+ continue;
+ }
+
+ if ($value instanceof ConfigObject) {
+ throw new ProgrammingError('Cannot diff recursive configs');
+ }
+ if ($domSection->hasDirective($key)) {
+ $domSection->getDirective($key)->setValue($value);
+ } else {
+ $dir = new Directive($key);
+ $dir->setValue($value);
+ $domSection->addDirective($dir);
+ }
+ }
+ }
+ }
+
+ /**
+ * Search for deleted properties and use the editor to delete these entries
+ *
+ * @param Config $oldconfig The config representing the state before the change
+ * @param Config $newconfig The config representing the state after the change
+ * @param Document $doc
+ *
+ * @throws ProgrammingError
+ */
+ protected function diffPropertyDeletions(Config $oldconfig, Config $newconfig, Document $doc)
+ {
+ // Iterate over all properties in the old configuration file and remove those that don't
+ // exist in the new config
+ foreach ($oldconfig->toArray() as $section => $directives) {
+ if (! is_array($directives)) {
+ Logger::warning('Section-less property ' . (string)$directives . ' was ignored.');
+ continue;
+ }
+
+ if ($newconfig->hasSection($section)) {
+ $newSection = $newconfig->getSection($section);
+ $oldDomSection = $doc->getSection($section);
+ foreach ($directives as $key => $value) {
+ if ($value instanceof ConfigObject) {
+ throw new ProgrammingError('Cannot diff recursive configs');
+ }
+ if (null === $newSection->get($key) && $oldDomSection->hasDirective($key)) {
+ $oldDomSection->removeDirective($key);
+ }
+ }
+ } else {
+ $doc->removeSection($section);
+ }
+ }
+ }
+}
diff --git a/library/Icinga/File/Pdf.php b/library/Icinga/File/Pdf.php
new file mode 100644
index 0000000..e159e2f
--- /dev/null
+++ b/library/Icinga/File/Pdf.php
@@ -0,0 +1,96 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File;
+
+use Dompdf\Dompdf;
+use Dompdf\Options;
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Util\Environment;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+
+call_user_func(function () {
+ /**
+ * @package dompdf
+ * @link http://dompdf.github.com/
+ * @author Benj Carson <benjcarson@digitaljunkies.ca>
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @author Alexander A. Klimov <alexander.klimov@icinga.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+ $baseDir = Icinga::app()->getBaseDir('library/vendor/dompdf');
+
+ require_once "$baseDir/vendor/autoload.php";
+});
+
+class Pdf
+{
+ protected function assertNoHeadersSent()
+ {
+ if (headers_sent()) {
+ throw new ProgrammingError(
+ 'Could not send pdf-response, content already written to output.'
+ );
+ }
+ }
+
+ public function renderControllerAction($controller)
+ {
+ $this->assertNoHeadersSent();
+
+ Environment::raiseMemoryLimit('512M');
+ Environment::raiseExecutionTime(300);
+
+ $viewRenderer = $controller->getHelper('viewRenderer');
+ $viewRenderer->postDispatch();
+
+ $layoutHelper = $controller->getHelper('layout');
+ $oldLayout = $layoutHelper->getLayout();
+ $layout = $layoutHelper->setLayout('pdf');
+
+ $layout->content = $controller->getResponse();
+ $html = $layout->render();
+
+ // Restore previous layout and reset content, to properly show errors
+ $controller->getResponse()->clearBody($viewRenderer->getResponseSegment());
+ $layoutHelper->setLayout($oldLayout);
+
+ $imgDir = Url::fromPath('img');
+ $html = preg_replace(
+ '~src="' . $imgDir . '/~',
+ 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/',
+ $html
+ );
+
+ $request = $controller->getRequest();
+
+ if (Hook::has('Pdfexport')) {
+ $pdfexport = Hook::first('Pdfexport');
+ $pdfexport->streamPdfFromHtml($html, sprintf(
+ '%s-%s-%d',
+ $request->getControllerName(),
+ $request->getActionName(),
+ time()
+ ));
+
+ return;
+ }
+
+ $options = new Options();
+ $options->set('defaultPaperSize', 'A4');
+ $dompdf = new Dompdf($options);
+ $dompdf->loadHtml($html);
+ $dompdf->render();
+ $dompdf->stream(
+ sprintf(
+ '%s-%s-%d',
+ $request->getControllerName(),
+ $request->getActionName(),
+ time()
+ )
+ );
+ }
+}
diff --git a/library/Icinga/File/Storage/LocalFileStorage.php b/library/Icinga/File/Storage/LocalFileStorage.php
new file mode 100644
index 0000000..e38167e
--- /dev/null
+++ b/library/Icinga/File/Storage/LocalFileStorage.php
@@ -0,0 +1,158 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Storage;
+
+use ErrorException;
+use Icinga\Exception\AlreadyExistsException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\NotWritableError;
+use InvalidArgumentException;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use Traversable;
+use UnexpectedValueException;
+
+/**
+ * Stores files in the local file system
+ */
+class LocalFileStorage implements StorageInterface
+{
+ /**
+ * The root directory of this storage
+ *
+ * @var string
+ */
+ protected $baseDir;
+
+ /**
+ * Constructor
+ *
+ * @param string $baseDir The root directory of this storage
+ */
+ public function __construct($baseDir)
+ {
+ $this->baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR);
+ }
+
+ public function getIterator(): Traversable
+ {
+ try {
+ return new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator(
+ $this->baseDir,
+ RecursiveDirectoryIterator::CURRENT_AS_FILEINFO
+ | RecursiveDirectoryIterator::KEY_AS_PATHNAME
+ | RecursiveDirectoryIterator::SKIP_DOTS
+ )
+ );
+ } catch (UnexpectedValueException $e) {
+ throw new NotReadableError('Couldn\'t read the directory "%s": %s', $this->baseDir, $e);
+ }
+ }
+
+ public function has($path)
+ {
+ return is_file($this->resolvePath($path));
+ }
+
+ public function create($path, $content)
+ {
+ $resolvedPath = $this->resolvePath($path);
+
+ $this->ensureDir(dirname($resolvedPath));
+
+ try {
+ $stream = fopen($resolvedPath, 'x');
+ } catch (ErrorException $e) {
+ throw new AlreadyExistsException('Couldn\'t create the file "%s": %s', $path, $e);
+ }
+
+ try {
+ fclose($stream);
+ chmod($resolvedPath, 0664);
+ file_put_contents($resolvedPath, $content);
+ } catch (ErrorException $e) {
+ throw new NotWritableError('Couldn\'t create the file "%s": %s', $path, $e);
+ }
+ }
+
+ public function read($path)
+ {
+ $resolvedPath = $this->resolvePath($path, true);
+
+ try {
+ return file_get_contents($resolvedPath);
+ } catch (ErrorException $e) {
+ throw new NotReadableError('Couldn\'t read the file "%s": %s', $path, $e);
+ }
+ }
+
+ public function update($path, $content)
+ {
+ $resolvedPath = $this->resolvePath($path, true);
+
+ try {
+ file_put_contents($resolvedPath, $content);
+ } catch (ErrorException $e) {
+ throw new NotWritableError('Couldn\'t update the file "%s": %s', $path, $e);
+ }
+ }
+
+ public function delete($path)
+ {
+ $resolvedPath = $this->resolvePath($path, true);
+
+ try {
+ unlink($resolvedPath);
+ } catch (ErrorException $e) {
+ throw new NotWritableError('Couldn\'t delete the file "%s": %s', $path, $e);
+ }
+ }
+
+ public function resolvePath($path, $assertExistence = false)
+ {
+ if ($assertExistence && ! $this->has($path)) {
+ throw new NotFoundError('No such file: "%s"', $path);
+ }
+
+ $steps = preg_split('~/~', $path, -1, PREG_SPLIT_NO_EMPTY);
+ for ($i = 0; $i < count($steps);) {
+ if ($steps[$i] === '.') {
+ array_splice($steps, $i, 1);
+ } elseif ($steps[$i] === '..' && $i > 0 && $steps[$i - 1] !== '..') {
+ array_splice($steps, $i - 1, 2);
+ --$i;
+ } else {
+ ++$i;
+ }
+ }
+
+ if ($steps[0] === '..') {
+ throw new InvalidArgumentException('Paths above the base directory are not allowed');
+ }
+
+ return $this->baseDir . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $steps);
+ }
+
+ /**
+ * Ensure that the given directory exists
+ *
+ * @param string $dir
+ *
+ * @throws NotWritableError
+ */
+ protected function ensureDir($dir)
+ {
+ if (! is_dir($dir)) {
+ $this->ensureDir(dirname($dir));
+
+ try {
+ mkdir($dir, 02770);
+ } catch (ErrorException $e) {
+ throw new NotWritableError('Couldn\'t create the directory "%s": %s', $dir, $e);
+ }
+ }
+ }
+}
diff --git a/library/Icinga/File/Storage/StorageInterface.php b/library/Icinga/File/Storage/StorageInterface.php
new file mode 100644
index 0000000..f416b00
--- /dev/null
+++ b/library/Icinga/File/Storage/StorageInterface.php
@@ -0,0 +1,94 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Storage;
+
+use Icinga\Exception\AlreadyExistsException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\NotWritableError;
+use IteratorAggregate;
+use Traversable;
+
+interface StorageInterface extends IteratorAggregate
+{
+ /**
+ * Iterate over all existing files' paths
+ *
+ * @return Traversable
+ *
+ * @throws NotReadableError If the file list can't be read
+ */
+ public function getIterator(): Traversable;
+
+ /**
+ * Return whether the given file exists
+ *
+ * @param string $path
+ *
+ * @return bool
+ */
+ public function has($path);
+
+ /**
+ * Create the given file with the given content
+ *
+ * @param string $path
+ * @param mixed $content
+ *
+ * @return $this
+ *
+ * @throws AlreadyExistsException If the file already exists
+ * @throws NotWritableError If the file can't be written to
+ */
+ public function create($path, $content);
+
+ /**
+ * Load the content of the given file
+ *
+ * @param string $path
+ *
+ * @return mixed
+ *
+ * @throws NotFoundError If the file can't be found
+ * @throws NotReadableError If the file can't be read
+ */
+ public function read($path);
+
+ /**
+ * Overwrite the given file with the given content
+ *
+ * @param string $path
+ * @param mixed $content
+ *
+ * @return $this
+ *
+ * @throws NotFoundError If the file can't be found
+ * @throws NotWritableError If the file can't be written to
+ */
+ public function update($path, $content);
+
+ /**
+ * Delete the given file
+ *
+ * @param string $path
+ *
+ * @return $this
+ *
+ * @throws NotFoundError If the file can't be found
+ * @throws NotWritableError If the file can't be deleted
+ */
+ public function delete($path);
+
+ /**
+ * Get the absolute path to the given file
+ *
+ * @param string $path
+ * @param bool $assertExistence Whether to require that the given file exists
+ *
+ * @return string
+ *
+ * @throws NotFoundError If the file has to exist, but can't be found
+ */
+ public function resolvePath($path, $assertExistence = false);
+}
diff --git a/library/Icinga/File/Storage/TemporaryLocalFileStorage.php b/library/Icinga/File/Storage/TemporaryLocalFileStorage.php
new file mode 100644
index 0000000..faf91f5
--- /dev/null
+++ b/library/Icinga/File/Storage/TemporaryLocalFileStorage.php
@@ -0,0 +1,59 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\File\Storage;
+
+use ErrorException;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+
+/**
+ * Stores files in a temporary directory
+ */
+class TemporaryLocalFileStorage extends LocalFileStorage
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid();
+ mkdir($path, 0700);
+
+ parent::__construct($path);
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ // Some classes may have cleaned up the tmp file, so we need to check this
+ // beforehand to prevent an unexpected crash.
+ if (! @realpath($this->baseDir)) {
+ return;
+ }
+
+ $directoryIterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator(
+ $this->baseDir,
+ RecursiveDirectoryIterator::CURRENT_AS_FILEINFO
+ | RecursiveDirectoryIterator::KEY_AS_PATHNAME
+ | RecursiveDirectoryIterator::SKIP_DOTS
+ ),
+ RecursiveIteratorIterator::CHILD_FIRST
+ );
+
+ foreach ($directoryIterator as $path => $entry) {
+ /** @var \SplFileInfo $entry */
+
+ if ($entry->isDir() && ! $entry->isLink()) {
+ rmdir($path);
+ } else {
+ unlink($path);
+ }
+ }
+
+ rmdir($this->baseDir);
+ }
+}
diff --git a/library/Icinga/Legacy/DashboardConfig.php b/library/Icinga/Legacy/DashboardConfig.php
new file mode 100644
index 0000000..3fb5c2f
--- /dev/null
+++ b/library/Icinga/Legacy/DashboardConfig.php
@@ -0,0 +1,137 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Legacy;
+
+use Icinga\Application\Config;
+use Icinga\User;
+use Icinga\Web\Navigation\DashboardPane;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Navigation\NavigationItem;
+
+/**
+ * Legacy dashboard config class for case insensitive interpretation of dashboard config files
+ *
+ * Before 2.2, the username part in dashboard config files was not lowered.
+ *
+ * @deprecated(el): Remove. TBD.
+ */
+class DashboardConfig extends Config
+{
+ /**
+ * User
+ *
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * Get the user
+ *
+ * @return User
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Set the user
+ *
+ * @param User $user
+ *
+ * @return $this
+ */
+ public function setUser($user)
+ {
+ $this->user = $user;
+ return $this;
+ }
+
+
+ /**
+ * List all dashboard configuration files that match the given user
+ *
+ * @param User $user
+ *
+ * @return string[]
+ */
+ public static function listConfigFilesForUser(User $user)
+ {
+ $files = array();
+ $dashboards = static::resolvePath('dashboards');
+ if ($handle = @opendir($dashboards)) {
+ while (false !== ($entry = readdir($handle))) {
+ if ($entry[0] === '.' || ! is_dir($dashboards . '/' . $entry)) {
+ continue;
+ }
+ if (strtolower($entry) === strtolower($user->getUsername())) {
+ $files[] = $dashboards . '/' . $entry . '/dashboard.ini';
+ }
+ }
+ closedir($handle);
+ }
+ return $files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function saveIni($filePath = null, $fileMode = 0660)
+ {
+ // Preprocessing start, ensures that the non-translated names are used to save module dashboard changes
+ // TODO: This MUST NOT survive the new dashboard implementation (yes, it's still a thing..)
+ $dashboardNavigation = new Navigation();
+ $dashboardNavigation->load('dashboard-pane');
+ $getDashboardPane = function ($label) use ($dashboardNavigation) {
+ foreach ($dashboardNavigation as $dashboardPane) {
+ /** @var DashboardPane $dashboardPane */
+ if ($dashboardPane->getLabel() === $label) {
+ return $dashboardPane;
+ }
+
+ foreach ($dashboardPane->getChildren() as $dashlet) {
+ /** @var NavigationItem $dashlet */
+ if ($dashlet->getLabel() === $label) {
+ return $dashlet;
+ }
+ }
+ }
+ };
+
+ foreach (clone $this->config as $name => $options) {
+ if (strpos($name, '.') !== false) {
+ list($dashboardLabel, $dashletLabel) = explode('.', $name, 2);
+ } else {
+ $dashboardLabel = $name;
+ $dashletLabel = null;
+ }
+
+ $dashboardPane = $getDashboardPane($dashboardLabel);
+ if ($dashboardPane !== null) {
+ $dashboardLabel = $dashboardPane->getName();
+ }
+
+ if ($dashletLabel !== null) {
+ $dashletItem = $getDashboardPane($dashletLabel);
+ if ($dashletItem !== null) {
+ $dashletLabel = $dashletItem->getName();
+ }
+ }
+
+ unset($this->config[$name]);
+ $this->config[$dashboardLabel . ($dashletLabel ? '.' . $dashletLabel : '')] = $options;
+ }
+ // Preprocessing end
+
+ parent::saveIni($filePath, $fileMode);
+ if ($filePath === null) {
+ $filePath = $this->configFile;
+ }
+ foreach (static::listConfigFilesForUser($this->user) as $file) {
+ if ($file !== $filePath) {
+ @unlink($file);
+ }
+ }
+ }
+}
diff --git a/library/Icinga/Less/Call.php b/library/Icinga/Less/Call.php
new file mode 100644
index 0000000..0a78cb5
--- /dev/null
+++ b/library/Icinga/Less/Call.php
@@ -0,0 +1,77 @@
+<?php
+
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Tree_Call;
+use Less_Tree_Color;
+use Less_Tree_Value;
+use Less_Tree_Variable;
+
+class Call extends Less_Tree_Call
+{
+ public static function fromCall(Less_Tree_Call $call)
+ {
+ return new static($call->name, $call->args, $call->index, $call->currentFileInfo);
+ }
+
+ public function compile($env = null)
+ {
+ if (! $env) {
+ // Not sure how to trigger this, but if there is no $env, there is nothing we can do
+ return parent::compile($env);
+ }
+
+ foreach ($this->args as $arg) {
+ if (! is_array($arg->value)) {
+ continue;
+ }
+
+ $name = null;
+ if ($arg->value[0] instanceof Less_Tree_Variable) {
+ // This is the case when defining a variable with a callable LESS rules such as fade, fadeout..
+ // Example: `@foo: #fff; @foo-bar: fade(@foo, 10);`
+ $name = $arg->value[0]->name;
+ } elseif ($arg->value[0] instanceof ColorPropOrVariable) {
+ // This is the case when defining a CSS rule using the LESS functions and passing
+ // a variable as an argument to them. Example: `... { color: fade(@foo, 10%); }`
+ $name = $arg->value[0]->getVariable()->name;
+ }
+
+ if ($name) {
+ foreach ($env->frames as $frame) {
+ if (($v = $frame->variable($name))) {
+ // Variables from the frame stack are always of type LESS Tree Rule
+ $vr = $v->value;
+ if ($vr instanceof Less_Tree_Value) {
+ // Get the actual color prop, otherwise this may cause an invalid argument error
+ $vr = $vr->compile($env);
+ }
+
+ if ($vr instanceof DeferredColorProp) {
+ if (! $vr->hasReference()) {
+ // Should never happen, though just for safety's sake
+ $vr->compile($env);
+ }
+
+ // Get the uppermost variable of the variable references
+ while (! $vr instanceof ColorProp) {
+ $vr = $vr->getRef();
+ }
+ } elseif ($vr instanceof Less_Tree_Color) {
+ $vr = ColorProp::fromColor($vr);
+ $vr->setName($name);
+ }
+
+ $arg->value[0] = $vr;
+
+ break;
+ }
+ }
+ }
+ }
+
+ return parent::compile($env);
+ }
+}
diff --git a/library/Icinga/Less/ColorProp.php b/library/Icinga/Less/ColorProp.php
new file mode 100644
index 0000000..3f83c5e
--- /dev/null
+++ b/library/Icinga/Less/ColorProp.php
@@ -0,0 +1,109 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Tree_Call;
+use Less_Tree_Color;
+use Less_Tree_Keyword;
+
+/**
+ * ColorProp renders Less colors as CSS var() function calls
+ *
+ * It extends {@link Less_Tree_Color} so that Less functions that take a Less_Tree_Color as an argument do not fail.
+ */
+class ColorProp extends Less_Tree_Color
+{
+ /** @var Less_Tree_Color Color with which we created the ColorProp */
+ protected $color;
+
+ /** @var int */
+ protected $index;
+
+ /** @var string Color variable name */
+ protected $name;
+
+ public function __construct()
+ {
+ }
+
+ /**
+ * @param Less_Tree_Color $color
+ *
+ * @return static
+ */
+ public static function fromColor(Less_Tree_Color $color)
+ {
+ $self = new static();
+ $self->color = $color;
+
+ foreach ($color as $k => $v) {
+ if ($k === 'name') {
+ $self->setName($v); // Removes the @ char from the name
+ } else {
+ $self->$k = $v;
+ }
+ }
+
+ return $self;
+ }
+
+ /**
+ * @return int
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+
+ /**
+ * @param int $index
+ *
+ * @return $this
+ */
+ public function setIndex($index)
+ {
+ $this->index = $index;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ if ($name[0] === '@') {
+ $name = substr($name, 1);
+ }
+
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function genCSS($output)
+ {
+ $css = (new Less_Tree_Call(
+ 'var',
+ [
+ new Less_Tree_Keyword('--' . $this->getName()),
+ // Use the Less_Tree_Color with which we created the ColorProp so that we don't get into genCSS() loops.
+ $this->color
+ ],
+ $this->getIndex()
+ ))->toCSS();
+
+ $output->add($css);
+ }
+}
diff --git a/library/Icinga/Less/ColorPropOrVariable.php b/library/Icinga/Less/ColorPropOrVariable.php
new file mode 100644
index 0000000..7918674
--- /dev/null
+++ b/library/Icinga/Less/ColorPropOrVariable.php
@@ -0,0 +1,71 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Tree;
+use Less_Tree_Color;
+use Less_Tree_Variable;
+
+/**
+ * Compile a Less variable to {@link ColorProp} if it is a color
+ */
+class ColorPropOrVariable extends Less_Tree
+{
+ public $type = 'Variable';
+
+ /** @var Less_Tree_Variable */
+ protected $variable;
+
+ /**
+ * @return Less_Tree_Variable
+ */
+ public function getVariable()
+ {
+ return $this->variable;
+ }
+
+ /**
+ * @param Less_Tree_Variable $variable
+ *
+ * @return $this
+ */
+ public function setVariable(Less_Tree_Variable $variable)
+ {
+ $this->variable = $variable;
+
+ return $this;
+ }
+
+ public function compile($env)
+ {
+ $v = $this->getVariable();
+
+ if ($v->name[1] === '@') {
+ // Evaluate variable variable as in Less_Tree_Variable:28.
+ $vv = new Less_Tree_Variable(substr($v->name, 1), $v->index + 1, $v->currentFileInfo);
+ // Overwrite the name so that the variable variable is not evaluated again.
+ $result = $vv->compile($env);
+ if ($result instanceof DeferredColorProp) {
+ $v->name = $result->name;
+ } else {
+ $v->name = '@' . $result->value;
+ }
+ }
+
+ $compiled = $v->compile($env);
+
+ if ($compiled instanceof ColorProp) {
+ // We may already have a ColorProp, which is the case with mixin calls.
+ return $compiled;
+ }
+
+ if ($compiled instanceof Less_Tree_Color) {
+ return ColorProp::fromColor($compiled)
+ ->setIndex($v->index)
+ ->setName($v->name);
+ }
+
+ return $compiled;
+ }
+}
diff --git a/library/Icinga/Less/DeferredColorProp.php b/library/Icinga/Less/DeferredColorProp.php
new file mode 100644
index 0000000..c9c39ad
--- /dev/null
+++ b/library/Icinga/Less/DeferredColorProp.php
@@ -0,0 +1,136 @@
+<?php
+
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Exception_Compiler;
+use Less_Tree_Call;
+use Less_Tree_Color;
+use Less_Tree_Keyword;
+use Less_Tree_Value;
+use Less_Tree_Variable;
+
+class DeferredColorProp extends Less_Tree_Variable
+{
+ /** @var DeferredColorProp|ColorProp */
+ protected $reference;
+
+ protected $resolved = false;
+
+ public function __construct($name, $variable, $index = null, $currentFileInfo = null)
+ {
+ parent::__construct($name, $index, $currentFileInfo);
+
+ if ($variable instanceof Less_Tree_Variable) {
+ $this->reference = self::fromVariable($variable);
+ }
+ }
+
+ public function isResolved()
+ {
+ return $this->resolved;
+ }
+
+ public function getName()
+ {
+ $name = $this->name;
+ if ($this->name[0] === '@') {
+ $name = substr($this->name, 1);
+ }
+
+ return $name;
+ }
+
+ public function hasReference()
+ {
+ return $this->reference !== null;
+ }
+
+ public function getRef()
+ {
+ return $this->reference;
+ }
+
+ public function setReference($ref)
+ {
+ $this->reference = $ref;
+
+ return $this;
+ }
+
+ public static function fromVariable(Less_Tree_Variable $variable)
+ {
+ $static = new static($variable->name, $variable->index, $variable->currentFileInfo);
+ $static->evaluating = $variable->evaluating;
+ $static->type = $variable->type;
+
+ return $static;
+ }
+
+ public function compile($env)
+ {
+ if (! $this->hasReference()) {
+ // This is never supposed to happen, however, we might have a deferred color prop
+ // without a reference. In this case we can simply use the parent method.
+ return parent::compile($env);
+ }
+
+ if ($this->isResolved()) {
+ // The dependencies are already resolved, no need to traverse the frame stack over again!
+ return $this;
+ }
+
+ if ($this->evaluating) { // Just like the parent method
+ throw new Less_Exception_Compiler(
+ "Recursive variable definition for " . $this->name,
+ null,
+ $this->index,
+ $this->currentFileInfo
+ );
+ }
+
+ $this->evaluating = true;
+
+ foreach ($env->frames as $frame) {
+ if (($v = $frame->variable($this->getRef()->name))) {
+ $rv = $v->value;
+ if ($rv instanceof Less_Tree_Value) {
+ $rv = $rv->compile($env);
+ }
+
+ // As we are at it anyway, let's cast the tree color to our color prop as well!
+ if ($rv instanceof Less_Tree_Color) {
+ $rv = ColorProp::fromColor($rv);
+ $rv->setName($this->getRef()->getName());
+ }
+
+ $this->evaluating = false;
+ $this->resolved = true;
+ $this->setReference($rv);
+
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ public function genCSS($output)
+ {
+ if (! $this->hasReference()) {
+ return; // Nothing to generate
+ }
+
+ $css = (new Less_Tree_Call(
+ 'var',
+ [
+ new Less_Tree_Keyword('--' . $this->getName()),
+ $this->getRef() // Each of the references will be generated recursively
+ ],
+ $this->index
+ ))->toCSS();
+
+ $output->add($css);
+ }
+}
diff --git a/library/Icinga/Less/LightMode.php b/library/Icinga/Less/LightMode.php
new file mode 100644
index 0000000..b4b72a0
--- /dev/null
+++ b/library/Icinga/Less/LightMode.php
@@ -0,0 +1,128 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use ArrayIterator;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Less_Environment;
+use Traversable;
+
+/**
+ * Registry for light modes and the environments in which they are defined
+ */
+class LightMode implements IteratorAggregate
+{
+ /** @var array Mode environments as mode-environment pairs */
+ protected $envs = [];
+
+ /** @var array Assoc list of modes */
+ protected $modes = [];
+
+ /** @var array Mode selectors as mode-selector pairs */
+ protected $selectors = [];
+
+ /**
+ * @param string $mode
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If the mode already exists
+ */
+ public function add($mode)
+ {
+ if (array_key_exists($mode, $this->modes)) {
+ throw new InvalidArgumentException("$mode already exists");
+ }
+
+ $this->modes[$mode] = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $mode
+ *
+ * @return Less_Environment
+ *
+ * @throws InvalidArgumentException If there is no environment for the given mode
+ */
+ public function getEnv($mode)
+ {
+ if (! isset($this->envs[$mode])) {
+ throw new InvalidArgumentException("$mode does not exist");
+ }
+
+ return $this->envs[$mode];
+ }
+
+ /**
+ * @param string $mode
+ * @param Less_Environment $env
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If an environment for given the mode already exists
+ */
+ public function setEnv($mode, Less_Environment $env)
+ {
+ if (array_key_exists($mode, $this->envs)) {
+ throw new InvalidArgumentException("$mode already exists");
+ }
+
+ $this->envs[$mode] = $env;
+
+ return $this;
+ }
+
+ /**
+ * @param string $mode
+ *
+ * @return bool
+ */
+ public function hasSelector($mode)
+ {
+ return isset($this->selectors[$mode]);
+ }
+
+ /**
+ * @param string $mode
+ *
+ * @return string
+ *
+ * @throws InvalidArgumentException If there is no selector for the given mode
+ */
+ public function getSelector($mode)
+ {
+ if (! isset($this->selectors[$mode])) {
+ throw new InvalidArgumentException("$mode does not exist");
+ }
+
+ return $this->selectors[$mode];
+ }
+
+ /**
+ * @param string $mode
+ * @param string $selector
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If a selector for given the mode already exists
+ */
+ public function setSelector($mode, $selector)
+ {
+ if (array_key_exists($mode, $this->selectors)) {
+ throw new InvalidArgumentException("$mode already exists");
+ }
+
+ $this->selectors[$mode] = $selector;
+
+ return $this;
+ }
+
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator(array_keys($this->modes));
+ }
+}
diff --git a/library/Icinga/Less/LightModeCall.php b/library/Icinga/Less/LightModeCall.php
new file mode 100644
index 0000000..d899e3c
--- /dev/null
+++ b/library/Icinga/Less/LightModeCall.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Environment;
+use Less_Tree_Ruleset;
+use Less_Tree_RulesetCall;
+
+/**
+ * Use the environment where the light mode was defined to evaluate the call
+ */
+class LightModeCall extends Less_Tree_RulesetCall
+{
+ use LightModeTrait;
+
+ /**
+ * @param Less_Tree_RulesetCall $c
+ *
+ * @return static
+ */
+ public static function fromRulesetCall(Less_Tree_RulesetCall $c)
+ {
+ return new static($c->variable);
+ }
+
+ /**
+ * @param Less_Environment $env
+ *
+ * @return Less_Tree_Ruleset
+ */
+ public function compile($env)
+ {
+ return parent::compile(
+ $env->copyEvalEnv(array_merge($env->frames, $this->getLightMode()->getEnv($this->variable)->frames))
+ );
+ }
+}
diff --git a/library/Icinga/Less/LightModeDefinition.php b/library/Icinga/Less/LightModeDefinition.php
new file mode 100644
index 0000000..929e95c
--- /dev/null
+++ b/library/Icinga/Less/LightModeDefinition.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Environment;
+use Less_Exception_Compiler;
+use Less_Tree_DetachedRuleset;
+use Less_Tree_Ruleset;
+
+/**
+ * Register the environment in which the light mode is defined
+ */
+class LightModeDefinition extends Less_Tree_DetachedRuleset
+{
+ use LightModeTrait;
+
+ /** @var string */
+ protected $name;
+
+ /**
+ * @param Less_Tree_DetachedRuleset $drs
+ *
+ * @return static
+ */
+ public static function fromDetachedRuleset(Less_Tree_DetachedRuleset $drs)
+ {
+ return new static($drs->ruleset, $drs->frames);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * @param Less_Environment $env
+ *
+ * @return Less_Tree_DetachedRuleset
+ */
+ public function compile($env)
+ {
+ $drs = parent::compile($env);
+
+ /** @var $frame Less_Tree_Ruleset */
+ foreach ($env->frames as $frame) {
+ if ($frame->variable($this->getName())) {
+ if (! empty($frame->first_oelements) && ! isset($frame->first_oelements['.icinga-module'])) {
+ throw new Less_Exception_Compiler('Light mode definition not allowed in selectors');
+ }
+
+ break;
+ }
+ }
+
+ $this->getLightMode()->setEnv($this->getName(), $env->copyEvalEnv($env->frames));
+
+ return $drs;
+ }
+}
diff --git a/library/Icinga/Less/LightModeTrait.php b/library/Icinga/Less/LightModeTrait.php
new file mode 100644
index 0000000..d328265
--- /dev/null
+++ b/library/Icinga/Less/LightModeTrait.php
@@ -0,0 +1,30 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+trait LightModeTrait
+{
+ /** @var LightMode */
+ private $lightMode;
+
+ /**
+ * @return LightMode
+ */
+ public function getLightMode()
+ {
+ return $this->lightMode;
+ }
+
+ /**
+ * @param LightMode $lightMode
+ *
+ * @return $this
+ */
+ public function setLightMode(LightMode $lightMode)
+ {
+ $this->lightMode = $lightMode;
+
+ return $this;
+ }
+}
diff --git a/library/Icinga/Less/LightModeVisitor.php b/library/Icinga/Less/LightModeVisitor.php
new file mode 100644
index 0000000..35758b4
--- /dev/null
+++ b/library/Icinga/Less/LightModeVisitor.php
@@ -0,0 +1,26 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_VisitorReplacing;
+
+/**
+ * Ensure that light mode calls have access to the environment in which the mode was defined
+ */
+class LightModeVisitor extends Less_VisitorReplacing
+{
+ use LightModeTrait;
+
+ public $isPreVisitor = true;
+
+ public function visitRulesetCall($c)
+ {
+ return LightModeCall::fromRulesetCall($c)->setLightMode($this->getLightMode());
+ }
+
+ public function run($node)
+ {
+ return $this->visitObj($node);
+ }
+}
diff --git a/library/Icinga/Less/Visitor.php b/library/Icinga/Less/Visitor.php
new file mode 100644
index 0000000..48417c8
--- /dev/null
+++ b/library/Icinga/Less/Visitor.php
@@ -0,0 +1,243 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Less;
+
+use Less_Parser;
+use Less_Tree_Expression;
+use Less_Tree_Rule;
+use Less_Tree_Value;
+use Less_Tree_Variable;
+use Less_VisitorReplacing;
+use LogicException;
+use ReflectionProperty;
+
+/**
+ * Replace compiled Less colors with CSS var() function calls and inject light mode calls
+ *
+ * Color replacing basically works by replacing every visited Less variable with {@link ColorPropOrVariable},
+ * which is later compiled to {@link ColorProp} if it is a color.
+ *
+ * Light mode calls are generated from light mode definitions.
+ */
+class Visitor extends Less_VisitorReplacing
+{
+ const LIGHT_MODE_CSS = <<<'CSS'
+@media (min-height: @prefer-light-color-scheme), print,
+(prefers-color-scheme: light) and (min-height: @enable-color-preference) {
+ %s
+}
+CSS;
+
+ const LIGHT_MODE_NAME = 'light-mode';
+
+ public $isPreEvalVisitor = true;
+
+ /**
+ * Whether calling var() CSS function
+ *
+ * If that's the case, don't try to replace compiled Less colors with CSS var() function calls.
+ *
+ * @var bool|string
+ */
+ protected $callingVar = false;
+
+ /**
+ * Whether defining a variable
+ *
+ * If that's the case, don't try to replace compiled Less colors with CSS var() function calls.
+ *
+ * @var false|string
+ */
+ protected $definingVariable = false;
+
+ /** @var Less_Tree_Rule If defining a variable, determines the origin rule of the variable */
+ protected $variableOrigin;
+
+ /** @var LightMode Light mode registry */
+ protected $lightMode;
+
+ /** @var false|string Whether parsing module Less */
+ protected $moduleScope = false;
+
+ /** @var null|string CSS module selector if any */
+ protected $moduleSelector;
+
+ public function visitCall($c)
+ {
+ if ($c->name !== 'var') {
+ // We need to use our own tree call class , so that we can precompile the arguments before making
+ // the actual LESS function calls. Otherwise, it will produce lots of invalid argument exceptions!
+ $c = Call::fromCall($c);
+ }
+
+ return $c;
+ }
+
+ public function visitDetachedRuleset($drs)
+ {
+ if ($this->variableOrigin->name === '@' . static::LIGHT_MODE_NAME) {
+ $this->variableOrigin->name .= '-' . substr(sha1(uniqid(mt_rand(), true)), 0, 7);
+
+ $this->lightMode->add($this->variableOrigin->name);
+
+ if ($this->moduleSelector !== false) {
+ $this->lightMode->setSelector($this->variableOrigin->name, $this->moduleSelector);
+ }
+
+ $drs = LightModeDefinition::fromDetachedRuleset($drs)
+ ->setLightMode($this->lightMode)
+ ->setName($this->variableOrigin->name);
+ }
+
+ // Since a detached ruleset is a variable definition in the first place,
+ // just reset that we define a variable.
+ $this->definingVariable = false;
+
+ return $drs;
+ }
+
+ public function visitMixinCall($c)
+ {
+ // Less_Tree_Mixin_Call::accept() does not visit arguments, but we have to replace them if necessary.
+ foreach ($c->arguments as $a) {
+ $a['value'] = $this->visitObj($a['value']);
+ }
+
+ return $c;
+ }
+
+ public function visitMixinDefinition($m)
+ {
+ // Less_Tree_Mixin_Definition::accept() does not visit params, but we have to replace them if necessary.
+ foreach ($m->params as $p) {
+ if (! isset($p['value'])) {
+ continue;
+ }
+
+ $p['value'] = $this->visitObj($p['value']);
+ }
+
+ return $m;
+ }
+
+ public function visitRule($r)
+ {
+ if ($r->name[0] === '@' && $r->variable) {
+ if ($this->definingVariable !== false) {
+ throw new LogicException('Already defining a variable');
+ }
+
+ $this->definingVariable = spl_object_hash($r);
+ $this->variableOrigin = $r;
+
+ if ($r->value instanceof Less_Tree_Value) {
+ if ($r->value->value[0] instanceof Less_Tree_Expression) {
+ if ($r->value->value[0]->value[0] instanceof Less_Tree_Variable) {
+ // Transform the variable definition rule into our own class
+ $r->value->value[0]->value[0] = new DeferredColorProp($r->name, $r->value->value[0]->value[0]);
+ }
+ }
+ }
+ }
+
+ return $r;
+ }
+
+ public function visitRuleOut($r)
+ {
+ if ($this->definingVariable !== false && $this->definingVariable === spl_object_hash($r)) {
+ $this->definingVariable = false;
+ $this->variableOrigin = null;
+ }
+ }
+
+ public function visitRuleset($rs)
+ {
+ // Method is required, otherwise visitRulesetOut will not be called.
+ return $rs;
+ }
+
+ public function visitRulesetOut($rs)
+ {
+ if ($this->moduleScope !== false
+ && isset($rs->selectors)
+ && spl_object_hash($rs->selectors[0]) === $this->moduleScope
+ ) {
+ $this->moduleSelector = null;
+ $this->moduleScope = false;
+ }
+ }
+
+ public function visitSelector($s)
+ {
+ if ($s->_oelements_len === 2 && $s->_oelements[0] === '.icinga-module') {
+ $this->moduleSelector = implode('', $s->_oelements);
+ $this->moduleScope = spl_object_hash($s);
+ }
+
+ return $s;
+ }
+
+ public function visitVariable($v)
+ {
+ if ($this->definingVariable !== false) {
+ return $v;
+ }
+
+ return (new ColorPropOrVariable())
+ ->setVariable($v);
+ }
+
+ public function visitColor($c)
+ {
+ if ($this->definingVariable !== false) {
+ // Make sure that all less tree colors do have a proper name
+ $c->name = $this->variableOrigin->name;
+ }
+
+ return $c;
+ }
+
+ public function run($node)
+ {
+ $this->lightMode = new LightMode();
+
+ $evald = $this->visitObj($node);
+
+ // The visitor has registered all light modes in visitDetachedRuleset, but has not called them yet.
+ // Now the light mode calls are prepared with the appropriate CSS selectors.
+ $calls = [];
+ foreach ($this->lightMode as $mode) {
+ if ($this->lightMode->hasSelector($mode)) {
+ $calls[] = "{$this->lightMode->getSelector($mode)} {\n$mode();\n}";
+ } else {
+ $calls[] = "$mode();";
+ }
+ }
+
+ if (! empty($calls)) {
+ // Place and parse light mode calls into a new anonymous file,
+ // leaving the original Less in which the light modes were defined untouched.
+ $parser = (new Less_Parser())
+ ->parse(sprintf(static::LIGHT_MODE_CSS, implode("\n", $calls)));
+
+ // Because Less variables are block scoped,
+ // we can't just access the light mode definitions in the calls above.
+ // The LightModeVisitor ensures that all calls have access to the environment in which the mode was defined.
+ // Finally, the rules are merged so that the light mode calls are also rendered to CSS.
+ $rules = new ReflectionProperty(get_class($parser), 'rules');
+ $rules->setAccessible(true);
+ $evald->rules = array_merge(
+ $evald->rules,
+ (new LightModeVisitor())
+ ->setLightMode($this->lightMode)
+ ->visitArray($rules->getValue($parser))
+ );
+ // The LightModeVisitor is used explicitly here instead of using it as a plugin
+ // since we only need to process the newly created rules for the light mode calls.
+ }
+
+ return $evald;
+ }
+}
diff --git a/library/Icinga/Protocol/Dns.php b/library/Icinga/Protocol/Dns.php
new file mode 100644
index 0000000..3d422d7
--- /dev/null
+++ b/library/Icinga/Protocol/Dns.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol;
+
+/**
+ * Discover dns records using regular or reverse lookup
+ */
+class Dns
+{
+ /**
+ * Discover all service records on a given domain
+ *
+ * @param string $domain The domain to search
+ * @param string $service The type of the service, like for example 'ldaps' or 'ldap'
+ * @param string $protocol The transport protocol used by the service, defaults to 'tcp'
+ *
+ * @return array An array of all found service records
+ */
+ public static function getSrvRecords($domain, $service, $protocol = 'tcp')
+ {
+ $records = dns_get_record('_' . $service . '._' . $protocol . '.' . $domain, DNS_SRV);
+ return $records === false ? array() : $records;
+ }
+
+ /**
+ * Get all ldap records for the given domain
+ *
+ * @param string $query The domain to query
+ * @param int $type The type of DNS-entry to fetch, see
+ * http://www.php.net/manual/de/function.dns-get-record.php for available types
+ *
+ * @return array|null An array of record entries
+ */
+ public static function records($query, $type = DNS_ANY)
+ {
+ return dns_get_record($query, $type);
+ }
+
+ /**
+ * Reverse lookup all host names available on the given ip address
+ *
+ * @param string $ipAddress
+ * @param int $type
+ *
+ * @return array|null
+ */
+ public static function ptr($ipAddress, $type = DNS_ANY)
+ {
+ $host = gethostbyaddr($ipAddress);
+ if ($host === false || $host === $ipAddress) {
+ // malformed input or no host found
+ return null;
+ }
+ return self::records($host, $type);
+ }
+
+ /**
+ * Get the IPv4 address of the given hostname.
+ *
+ * @param $hostname The hostname to resolve
+ *
+ * @return string|null The IPv4 address of the given hostname or null, when no entry exists.
+ */
+ public static function ipv4($hostname)
+ {
+ $records = dns_get_record($hostname, DNS_A);
+ if ($records !== false && count($records) > 0) {
+ return $records[0]['ip'];
+ }
+ return null;
+ }
+
+ /**
+ * Get the IPv6 address of the given hostname.
+ *
+ * @param $hostname The hostname to resolve
+ *
+ * @return string|null The IPv6 address of the given hostname or null, when no entry exists.
+ */
+ public static function ipv6($hostname)
+ {
+ $records = dns_get_record($hostname, DNS_AAAA);
+ if ($records !== false && count($records) > 0) {
+ return $records[0]['ip'];
+ }
+ return null;
+ }
+}
diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php
new file mode 100644
index 0000000..237352c
--- /dev/null
+++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+namespace Icinga\Protocol\File;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown if a file reader specific error occurs
+ */
+class FileReaderException extends IcingaException
+{
+}
diff --git a/library/Icinga/Protocol/File/FileIterator.php b/library/Icinga/Protocol/File/FileIterator.php
new file mode 100644
index 0000000..64b6600
--- /dev/null
+++ b/library/Icinga/Protocol/File/FileIterator.php
@@ -0,0 +1,81 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Icinga\Util\EnumeratingFilterIterator;
+use Icinga\Util\File;
+
+/**
+ * Class FileIterator
+ *
+ * Iterate over a file, yielding only fields of non-empty lines which match a PCRE expression
+ */
+class FileIterator extends EnumeratingFilterIterator
+{
+ /**
+ * A PCRE string with the fields to extract from the file's lines as named subpatterns
+ *
+ * @var string
+ */
+ protected $fields;
+
+ /**
+ * An associative array of the current line's fields ($field => $value)
+ *
+ * @var array
+ */
+ protected $currentData;
+
+ public function __construct($filename, $fields)
+ {
+ $this->fields = $fields;
+ $f = new File($filename);
+ $f->setFlags(
+ File::DROP_NEW_LINE |
+ File::READ_AHEAD |
+ File::SKIP_EMPTY
+ );
+ parent::__construct($f);
+ }
+
+ /**
+ * Return the current data
+ *
+ * @return array
+ */
+ public function current(): array
+ {
+ return $this->currentData;
+ }
+
+ /**
+ * Accept lines matching the given PCRE pattern
+ *
+ * @return bool
+ *
+ * @throws FileReaderException If PHP failed parsing the PCRE pattern
+ */
+ public function accept(): bool
+ {
+ $data = array();
+ $matched = preg_match(
+ $this->fields,
+ $this->getInnerIterator()->current(),
+ $data
+ );
+
+ if ($matched === false) {
+ throw new FileReaderException('Failed parsing regular expression!');
+ } elseif ($matched === 1) {
+ foreach ($data as $key => $value) {
+ if (is_int($key)) {
+ unset($data[$key]);
+ }
+ }
+ $this->currentData = $data;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/library/Icinga/Protocol/File/FileQuery.php b/library/Icinga/Protocol/File/FileQuery.php
new file mode 100644
index 0000000..504de2e
--- /dev/null
+++ b/library/Icinga/Protocol/File/FileQuery.php
@@ -0,0 +1,86 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Icinga\Data\SimpleQuery;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Class FileQuery
+ *
+ * Query for Datasource Icinga\Protocol\File\FileReader
+ *
+ * @package Icinga\Protocol\File
+ */
+class FileQuery extends SimpleQuery
+{
+ /**
+ * Sort direction
+ *
+ * @var int
+ */
+ private $sortDir;
+
+ /**
+ * Filters to apply on result
+ *
+ * @var array
+ */
+ private $filters = array();
+
+ /**
+ * Nothing to do here
+ */
+ public function applyFilter(Filter $filter)
+ {
+ }
+
+ /**
+ * Sort query result chronological
+ *
+ * @param string $dir Sort direction, 'ASC' or 'DESC' (default)
+ *
+ * @return FileQuery
+ */
+ public function order($field, $direction = null)
+ {
+ $this->sortDir = (
+ $direction === null || strtoupper(trim($direction)) === 'DESC'
+ ) ? self::SORT_DESC : self::SORT_ASC;
+ return $this;
+ }
+
+ /**
+ * Return true if sorting descending, false otherwise
+ *
+ * @return bool
+ */
+ public function sortDesc()
+ {
+ return $this->sortDir === self::SORT_DESC;
+ }
+
+ /**
+ * Add an mandatory filter expression to be applied on this query
+ *
+ * @param string $expression the filter expression to be applied
+ *
+ * @return FileQuery
+ */
+ public function andWhere($expression)
+ {
+ $this->filters[] = $expression;
+ return $this;
+ }
+
+ /**
+ * Get filters currently applied on this query
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+}
diff --git a/library/Icinga/Protocol/File/FileReader.php b/library/Icinga/Protocol/File/FileReader.php
new file mode 100644
index 0000000..d160387
--- /dev/null
+++ b/library/Icinga/Protocol/File/FileReader.php
@@ -0,0 +1,208 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Countable;
+use ArrayIterator;
+use Icinga\Data\Selectable;
+use Icinga\Data\ConfigObject;
+
+/**
+ * Read file line by line
+ */
+class FileReader implements Selectable, Countable
+{
+ /**
+ * A PCRE string with the fields to extract from the file's lines as named subpatterns
+ *
+ * @var string
+ */
+ protected $fields;
+
+ /**
+ * Name of the target file
+ *
+ * @var string
+ */
+ protected $filename;
+
+ /**
+ * Cache for static::count()
+ *
+ * @var int
+ */
+ protected $count = null;
+
+ /**
+ * Create a new reader
+ *
+ * @param ConfigObject $config
+ *
+ * @throws FileReaderException If a required $config directive (filename or fields) is missing
+ */
+ public function __construct(ConfigObject $config)
+ {
+ foreach (array('filename', 'fields') as $key) {
+ if (isset($config->{$key})) {
+ $this->{$key} = $config->{$key};
+ } else {
+ throw new FileReaderException('The directive `%s\' is required', $key);
+ }
+ }
+ }
+
+ /**
+ * Instantiate a FileIterator object with the target file
+ *
+ * @return FileIterator
+ */
+ public function iterate()
+ {
+ return new LogFileIterator($this->filename, $this->fields);
+ }
+
+ /**
+ * Instantiate a FileQuery object
+ *
+ * @return FileQuery
+ */
+ public function select()
+ {
+ return new FileQuery($this);
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result set using an iterator
+ *
+ * @param FileQuery $query
+ *
+ * @return ArrayIterator
+ */
+ public function query(FileQuery $query)
+ {
+ return new ArrayIterator($this->fetchAll($query));
+ }
+
+ /**
+ * Return the number of available valid lines.
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ if ($this->count === null) {
+ $this->count = iterator_count($this->iterate());
+ }
+ return $this->count;
+ }
+
+ /**
+ * Fetch result as an array of objects
+ *
+ * @param FileQuery $query
+ *
+ * @return array
+ */
+ public function fetchAll(FileQuery $query)
+ {
+ $all = array();
+ foreach ($this->fetchPairs($query) as $index => $value) {
+ $all[$index] = (object) $value;
+ }
+ return $all;
+ }
+
+ /**
+ * Fetch result as a key/value pair array
+ *
+ * @param FileQuery $query
+ *
+ * @return array
+ */
+ public function fetchPairs(FileQuery $query)
+ {
+ $skip = $query->getOffset();
+ $read = $query->getLimit();
+ if ($skip === null) {
+ $skip = 0;
+ }
+ $lines = array();
+ if ($query->sortDesc()) {
+ $count = $this->count($query);
+ if ($count <= $skip) {
+ return $lines;
+ } elseif ($count < ($skip + $read)) {
+ $read = $count - $skip;
+ $skip = 0;
+ } else {
+ $skip = $count - ($skip + $read);
+ }
+ }
+ foreach ($this->iterate() as $index => $line) {
+ if ($index >= $skip) {
+ if ($index >= $skip + $read) {
+ break;
+ }
+ $lines[] = $line;
+ }
+ }
+ if ($query->sortDesc()) {
+ $lines = array_reverse($lines);
+ }
+ return $lines;
+ }
+
+ /**
+ * Fetch first result row
+ *
+ * @param FileQuery $query
+ *
+ * @return object
+ */
+ public function fetchRow(FileQuery $query)
+ {
+ $all = $this->fetchAll($query);
+ if (isset($all[0])) {
+ return $all[0];
+ }
+ return null;
+ }
+
+ /**
+ * Fetch first result column
+ *
+ * @param FileQuery $query
+ *
+ * @return array
+ */
+ public function fetchColumn(FileQuery $query)
+ {
+ $column = array();
+ foreach ($this->fetchPairs($query) as $pair) {
+ foreach ($pair as $value) {
+ $column[] = $value;
+ break;
+ }
+ }
+ return $column;
+ }
+
+ /**
+ * Fetch first column value from first result row
+ *
+ * @param FileQuery $query
+ *
+ * @return mixed
+ */
+ public function fetchOne(FileQuery $query)
+ {
+ $pairs = $this->fetchPairs($query);
+ if (isset($pairs[0])) {
+ foreach ($pairs[0] as $value) {
+ return $value;
+ }
+ }
+ return null;
+ }
+}
diff --git a/library/Icinga/Protocol/File/LogFileIterator.php b/library/Icinga/Protocol/File/LogFileIterator.php
new file mode 100644
index 0000000..67a4d99
--- /dev/null
+++ b/library/Icinga/Protocol/File/LogFileIterator.php
@@ -0,0 +1,149 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\File;
+
+use Icinga\Exception\IcingaException;
+use SplFileObject;
+use Iterator;
+
+/**
+ * Iterate over a log file, yielding the regex fields of the log messages
+ */
+class LogFileIterator implements Iterator
+{
+ /**
+ * Log file
+ *
+ * @var SplFileObject
+ */
+ protected $file;
+
+ /**
+ * A PCRE string with the fields to extract
+ * from the log messages as named subpatterns
+ *
+ * @var string
+ */
+ protected $fields;
+
+ /**
+ * Value for static::current()
+ *
+ * @var array
+ */
+ protected $current;
+
+ /**
+ * Index for static::key()
+ *
+ * @var int
+ */
+ protected $index;
+
+ /**
+ * Value for static::valid()
+ *
+ * @var boolean
+ */
+ protected $valid;
+
+ /**
+ * @var string
+ */
+ protected $next = null;
+
+ /**
+ * @param string $filename The log file's name
+ * @param string $fields A PCRE string with the fields to extract
+ * from the log messages as named subpatterns
+ */
+ public function __construct($filename, $fields)
+ {
+ $this->file = new SplFileObject($filename);
+ $this->file->setFlags(
+ SplFileObject::DROP_NEW_LINE |
+ SplFileObject::READ_AHEAD
+ );
+ $this->fields = $fields;
+ }
+
+ public function rewind(): void
+ {
+ $this->file->rewind();
+ $this->index = 0;
+ $this->nextMessage();
+ }
+
+ public function next(): void
+ {
+ $this->file->next();
+ ++$this->index;
+ $this->nextMessage();
+ }
+
+ public function current(): array
+ {
+ return $this->current;
+ }
+
+ public function key(): int
+ {
+ return $this->index;
+ }
+
+ public function valid(): bool
+ {
+ return $this->valid;
+ }
+
+ protected function nextMessage()
+ {
+ $message = $this->next === null ? array() : array($this->next);
+ $this->valid = null;
+ while ($this->file->valid()) {
+ if (false === ($res = preg_match(
+ $this->fields,
+ $current = $this->file->current()
+ ))) {
+ throw new IcingaException('Failed at preg_match()');
+ }
+ if (empty($message)) {
+ if ($res === 1) {
+ $message[] = $current;
+ }
+ } elseif ($res === 1) {
+ $this->next = $current;
+ $this->valid = true;
+ break;
+ } else {
+ $message[] = $current;
+ }
+
+ $this->file->next();
+ }
+ if ($this->valid === null) {
+ $this->next = null;
+ $this->valid = ! empty($message);
+ }
+
+ if ($this->valid) {
+ while (! empty($message)) {
+ $matches = array();
+ if (false === ($res = preg_match(
+ $this->fields,
+ implode(PHP_EOL, $message),
+ $matches
+ ))) {
+ throw new IcingaException('Failed at preg_match()');
+ }
+ if ($res === 1) {
+ $this->current = $matches;
+ return;
+ }
+ array_pop($message);
+ }
+ $this->valid = false;
+ }
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/Discovery.php b/library/Icinga/Protocol/Ldap/Discovery.php
new file mode 100644
index 0000000..d2080aa
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/Discovery.php
@@ -0,0 +1,143 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Protocol\Dns;
+
+class Discovery
+{
+ /**
+ * @var LdapConnection
+ */
+ private $connection;
+
+ /**
+ * @param LdapConnection $conn The ldap connection to use for the discovery
+ */
+ public function __construct(LdapConnection $conn)
+ {
+ $this->connection = $conn;
+ }
+
+ /**
+ * Suggests a resource configuration of hostname, port and root_dn
+ * based on the discovery
+ *
+ * @return array The suggested configuration as an array
+ */
+ public function suggestResourceSettings()
+ {
+ return array(
+ 'hostname' => $this->connection->getHostname(),
+ 'port' => $this->connection->getPort(),
+ 'root_dn' => $this->connection->getCapabilities()->getDefaultNamingContext()
+ );
+ }
+
+ /**
+ * Suggests a backend configuration of base_dn, user_class and user_name_attribute
+ * based on the discovery
+ *
+ * @return array The suggested configuration as an array
+ */
+ public function suggestBackendSettings()
+ {
+ if ($this->isAd()) {
+ return array(
+ 'backend' => 'msldap',
+ 'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(),
+ 'user_class' => 'user',
+ 'user_name_attribute' => 'sAMAccountName'
+ );
+ } else {
+ return array(
+ 'backend' => 'ldap',
+ 'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(),
+ 'user_class' => 'inetOrgPerson',
+ 'user_name_attribute' => 'uid'
+ );
+ }
+ }
+
+ /**
+ * Whether the suggested ldap server is an ActiveDirectory
+ *
+ * @return boolean
+ */
+ public function isAd()
+ {
+ return $this->connection->getCapabilities()->isActiveDirectory();
+ }
+
+ /**
+ * Whether the discovery was successful
+ *
+ * @return bool False when the suggestions are guessed
+ */
+ public function isSuccess()
+ {
+ return $this->connection->discoverySuccessful();
+ }
+
+ /**
+ * Why the discovery failed
+ *
+ * @return \Exception|null
+ */
+ public function getError()
+ {
+ return $this->connection->getDiscoveryError();
+ }
+
+ /**
+ * Discover LDAP servers on the given domain
+ *
+ * @param string $domain The object containing the form elements
+ *
+ * @return Discovery True when the discovery was successful, false when the configuration was guessed
+ */
+ public static function discoverDomain($domain)
+ {
+ if (! isset($domain)) {
+ return false;
+ }
+
+ // Attempt 1: Connect to the domain directly
+ $disc = Discovery::discover($domain, 389);
+ if ($disc->isSuccess()) {
+ return $disc;
+ }
+
+ // Attempt 2: Discover all available ldap dns records and connect to the first one
+ $records = array_merge(Dns::getSrvRecords($domain, 'ldap'), Dns::getSrvRecords($domain, 'ldaps'));
+ if (isset($records[0])) {
+ $record = $records[0];
+ return Discovery::discover(
+ isset($record['target']) ? $record['target'] : $domain,
+ isset($record['port']) ? $record['port'] : $domain
+ );
+ }
+
+ // Return the first failed discovery, which will suggest properties based on guesses
+ return $disc;
+ }
+
+ /**
+ * Convenience method to instantiate a new Discovery
+ *
+ * @param $host The host on which to execute the discovery
+ * @param $port The port on which to execute the discovery
+ *
+ * @return Discover The resulting Discovery
+ */
+ public static function discover($host, $port)
+ {
+ $conn = new LdapConnection(new ConfigObject(array(
+ 'hostname' => $host,
+ 'port' => $port
+ )));
+ return new Discovery($conn);
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/LdapCapabilities.php b/library/Icinga/Protocol/Ldap/LdapCapabilities.php
new file mode 100644
index 0000000..0e562b1
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/LdapCapabilities.php
@@ -0,0 +1,439 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+use Icinga\Application\Logger;
+
+/**
+ * The properties and capabilities of an LDAP server
+ *
+ * Provides information about the available encryption mechanisms (StartTLS), the supported
+ * LDAP protocol (v2/v3), vendor-specific extensions or protocols controls and extensions.
+ */
+class LdapCapabilities
+{
+ const LDAP_SERVER_START_TLS_OID = '1.3.6.1.4.1.1466.20037';
+
+ const LDAP_PAGED_RESULT_OID_STRING = '1.2.840.113556.1.4.319';
+
+ const LDAP_SERVER_SHOW_DELETED_OID = '1.2.840.113556.1.4.417';
+
+ const LDAP_SERVER_SORT_OID = '1.2.840.113556.1.4.473';
+
+ const LDAP_SERVER_CROSSDOM_MOVE_TARGET_OID = '1.2.840.113556.1.4.521';
+
+ const LDAP_SERVER_NOTIFICATION_OID = '1.2.840.113556.1.4.528';
+
+ const LDAP_SERVER_EXTENDED_DN_OID = '1.2.840.113556.1.4.529';
+
+ const LDAP_SERVER_LAZY_COMMIT_OID = '1.2.840.113556.1.4.619';
+
+ const LDAP_SERVER_SD_FLAGS_OID = '1.2.840.113556.1.4.801';
+
+ const LDAP_SERVER_TREE_DELETE_OID = '1.2.840.113556.1.4.805';
+
+ const LDAP_SERVER_DIRSYNC_OID = '1.2.840.113556.1.4.841';
+
+ const LDAP_SERVER_VERIFY_NAME_OID = '1.2.840.113556.1.4.1338';
+
+ const LDAP_SERVER_DOMAIN_SCOPE_OID = '1.2.840.113556.1.4.1339';
+
+ const LDAP_SERVER_SEARCH_OPTIONS_OID = '1.2.840.113556.1.4.1340';
+
+ const LDAP_SERVER_PERMISSIVE_MODIFY_OID = '1.2.840.113556.1.4.1413';
+
+ const LDAP_SERVER_ASQ_OID = '1.2.840.113556.1.4.1504';
+
+ const LDAP_SERVER_FAST_BIND_OID = '1.2.840.113556.1.4.1781';
+
+ const LDAP_CONTROL_VLVREQUEST = '2.16.840.1.113730.3.4.9';
+
+
+ // MS Capabilities, Source: http://msdn.microsoft.com/en-us/library/cc223359.aspx
+
+ // Running Active Directory as AD DS
+ const LDAP_CAP_ACTIVE_DIRECTORY_OID = '1.2.840.113556.1.4.800';
+
+ // Capable of signing and sealing on an NTLM authenticated connection
+ // and of performing subsequent binds on a signed or sealed connection
+ const LDAP_CAP_ACTIVE_DIRECTORY_LDAP_INTEG_OID = '1.2.840.113556.1.4.1791';
+
+ // If AD DS: running at least W2K3, if AD LDS running at least W2K8
+ const LDAP_CAP_ACTIVE_DIRECTORY_V51_OID = '1.2.840.113556.1.4.1670';
+
+ // If AD LDS: accepts DIGEST-MD5 binds for AD LDSsecurity principals
+ const LDAP_CAP_ACTIVE_DIRECTORY_ADAM_DIGEST = '1.2.840.113556.1.4.1880';
+
+ // Running Active Directory as AD LDS
+ const LDAP_CAP_ACTIVE_DIRECTORY_ADAM_OID = '1.2.840.113556.1.4.1851';
+
+ // If AD DS: it's a Read Only DC (RODC)
+ const LDAP_CAP_ACTIVE_DIRECTORY_PARTIAL_SECRETS_OID = '1.2.840.113556.1.4.1920';
+
+ // Running at least W2K8
+ const LDAP_CAP_ACTIVE_DIRECTORY_V60_OID = '1.2.840.113556.1.4.1935';
+
+ // Running at least W2K8r2
+ const LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID = '1.2.840.113556.1.4.2080';
+
+ // Running at least W2K12
+ const LDAP_CAP_ACTIVE_DIRECTORY_W8_OID = '1.2.840.113556.1.4.2237';
+
+ /**
+ * Attributes of the LDAP Server returned by the discovery query
+ *
+ * @var StdClass
+ */
+ private $attributes;
+
+ /**
+ * Map of supported available OIDS
+ *
+ * @var array
+ */
+ private $oids;
+
+ /**
+ * Construct a new capability
+ *
+ * @param $attributes StdClass The attributes returned, may be null for guessing default capabilities
+ */
+ public function __construct($attributes = null)
+ {
+ $this->setAttributes($attributes);
+ }
+
+ /**
+ * Set the attributes and (re)build the OIDs
+ *
+ * @param $attributes StdClass The attributes returned, may be null for guessing default capabilities
+ */
+ protected function setAttributes($attributes)
+ {
+ $this->attributes = $attributes;
+ $this->oids = array();
+
+ $keys = array('supportedControl', 'supportedExtension', 'supportedFeatures', 'supportedCapabilities');
+ foreach ($keys as $key) {
+ if (isset($attributes->$key)) {
+ if (is_array($attributes->$key)) {
+ foreach ($attributes->$key as $oid) {
+ $this->oids[$oid] = true;
+ }
+ } else {
+ $this->oids[$attributes->$key] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return if the capability object contains support for StartTLS
+ *
+ * @return bool Whether StartTLS is supported
+ */
+ public function hasStartTls()
+ {
+ return isset($this->oids[self::LDAP_SERVER_START_TLS_OID]);
+ }
+
+ /**
+ * Return if the capability object contains support for paged results
+ *
+ * @return bool Whether StartTLS is supported
+ */
+ public function hasPagedResult()
+ {
+ return isset($this->oids[self::LDAP_PAGED_RESULT_OID_STRING]);
+ }
+
+ /**
+ * Whether the ldap server is an ActiveDirectory server
+ *
+ * @return boolean
+ */
+ public function isActiveDirectory()
+ {
+ return isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_OID]);
+ }
+
+ /**
+ * Whether the ldap server is an OpenLDAP server
+ *
+ * @return bool
+ */
+ public function isOpenLdap()
+ {
+ return isset($this->attributes->structuralObjectClass) &&
+ $this->attributes->structuralObjectClass === 'OpenLDAProotDSE';
+ }
+
+ /**
+ * Return if the capability objects contains support for LdapV3, defaults to true if discovery failed
+ *
+ * @return bool
+ */
+ public function hasLdapV3()
+ {
+ if (! isset($this->attributes) || ! isset($this->attributes->supportedLDAPVersion)) {
+ // Default to true, if unknown
+ return true;
+ }
+
+ return (is_string($this->attributes->supportedLDAPVersion)
+ && (int) $this->attributes->supportedLDAPVersion === 3)
+ || (is_array($this->attributes->supportedLDAPVersion)
+ && in_array(3, $this->attributes->supportedLDAPVersion));
+ }
+
+ /**
+ * Whether the capability with the given OID is supported
+ *
+ * @param $oid string The OID of the capability
+ *
+ * @return bool
+ */
+ public function hasOid($oid)
+ {
+ return isset($this->oids[$oid]);
+ }
+
+ /**
+ * Get the default naming context
+ *
+ * @return string|null the default naming context, or null when no contexts are available
+ */
+ public function getDefaultNamingContext()
+ {
+ // defaultNamingContext entry has higher priority
+ if (isset($this->attributes->defaultNamingContext)) {
+ return $this->attributes->defaultNamingContext;
+ }
+
+ // if its missing use namingContext
+ $namingContexts = $this->namingContexts();
+ return empty($namingContexts) ? null : $namingContexts[0];
+ }
+
+ /**
+ * Get the configuration naming context
+ *
+ * @return string|null
+ */
+ public function getConfigurationNamingContext()
+ {
+ if (isset($this->attributes->configurationNamingContext)) {
+ return $this->attributes->configurationNamingContext;
+ }
+ }
+
+ /**
+ * Get the NetBIOS name
+ *
+ * @return string|null
+ */
+ public function getNetBiosName()
+ {
+ if (isset($this->attributes->nETBIOSName)) {
+ return $this->attributes->nETBIOSName;
+ }
+ }
+
+ /**
+ * Fetch the namingContexts
+ *
+ * @return array the available naming contexts
+ */
+ public function namingContexts()
+ {
+ if (!isset($this->attributes->namingContexts)) {
+ return array();
+ }
+ if (!is_array($this->attributes->namingContexts)) {
+ return array($this->attributes->namingContexts);
+ }
+ return$this->attributes->namingContexts;
+ }
+
+ public function getVendor()
+ {
+ /*
+ rfc #3045 specifies that the name of the server MAY be included in the attribute 'verndorName',
+ AD and OpenLDAP don't do this, but for all all other vendors we follow the standard and
+ just hope for the best.
+ */
+
+ if ($this->isActiveDirectory()) {
+ return 'Microsoft Active Directory';
+ }
+
+ if ($this->isOpenLdap()) {
+ return 'OpenLDAP';
+ }
+
+ if (! isset($this->attributes->vendorName)) {
+ return null;
+ }
+ return $this->attributes->vendorName;
+ }
+
+ public function getVersion()
+ {
+ /*
+ rfc #3045 specifies that the version of the server MAY be included in the attribute 'vendorVersion',
+ but AD and OpenLDAP don't do this. For OpenLDAP there is no way to query the server versions, but for all
+ all other vendors we follow the standard and just hope for the best.
+ */
+
+ if ($this->isActiveDirectory()) {
+ return $this->getAdObjectVersionName();
+ }
+
+ if (! isset($this->attributes->vendorVersion)) {
+ return null;
+ }
+ return $this->attributes->vendorVersion;
+ }
+
+ /**
+ * Discover the capabilities of the given LDAP server
+ *
+ * @param LdapConnection $connection The ldap connection to use
+ *
+ * @return LdapCapabilities
+ *
+ * @throws LdapException In case the capability query has failed
+ */
+ public static function discoverCapabilities(LdapConnection $connection)
+ {
+ $ds = $connection->getConnection();
+
+ $fields = array(
+ 'configurationNamingContext',
+ 'defaultNamingContext',
+ 'namingContexts',
+ 'vendorName',
+ 'vendorVersion',
+ 'supportedSaslMechanisms',
+ 'dnsHostName',
+ 'schemaNamingContext',
+ 'supportedLDAPVersion', // => array(3, 2)
+ 'supportedCapabilities',
+ 'supportedControl',
+ 'supportedExtension',
+ 'objectVersion',
+ '+'
+ );
+
+ $result = @ldap_read($ds, '', (string) $connection->select()->from('*', $fields), $fields);
+ if (! $result) {
+ throw new LdapException(
+ 'Capability query failed (%s; Default port: %d): %s. Check if hostname and port'
+ . ' of the ldap resource are correct and if anonymous access is permitted.',
+ $connection->getHostname(),
+ $connection->getPort(),
+ ldap_error($ds)
+ );
+ }
+
+ $entry = ldap_first_entry($ds, $result);
+ if ($entry === false) {
+ throw new LdapException(
+ 'Capabilities not available (%s; Default port: %d): %s. Discovery of root DSE probably not permitted.',
+ $connection->getHostname(),
+ $connection->getPort(),
+ ldap_error($ds)
+ );
+ }
+
+ $cap = new LdapCapabilities($connection->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields));
+ $cap->discoverAdConfigOptions($connection);
+
+ if (isset($cap->attributes) && Logger::getInstance()->getLevel() === Logger::DEBUG) {
+ Logger::debug('Capability query discovered the following attributes:');
+ foreach ($cap->attributes as $name => $value) {
+ if ($value !== null) {
+ Logger::debug(' %s = %s', $name, $value);
+ }
+ }
+ Logger::debug('Capability query attribute listing ended.');
+ }
+
+ return $cap;
+ }
+
+ /**
+ * Discover the AD-specific configuration options of the given LDAP server
+ *
+ * @param LdapConnection $connection The ldap connection to use
+ *
+ * @throws LdapException In case the configuration options query has failed
+ */
+ protected function discoverAdConfigOptions(LdapConnection $connection)
+ {
+ if ($this->isActiveDirectory()) {
+ $configurationNamingContext = $this->getConfigurationNamingContext();
+ $defaultNamingContext = $this->getDefaultNamingContext();
+ if (!($configurationNamingContext === null || $defaultNamingContext === null)) {
+ $ds = $connection->bind()->getConnection();
+ $adFields = array('nETBIOSName');
+ $partitions = 'CN=Partitions,' . $configurationNamingContext;
+
+ $result = @ldap_list(
+ $ds,
+ $partitions,
+ (string) $connection->select()->from('*', $adFields)->where('nCName', $defaultNamingContext),
+ $adFields
+ );
+ if ($result) {
+ $entry = ldap_first_entry($ds, $result);
+ if ($entry === false) {
+ throw new LdapException(
+ 'Configuration options not available (%s:%d). Discovery of "%s" probably not permitted.',
+ $connection->getHostname(),
+ $connection->getPort(),
+ $partitions
+ );
+ }
+
+ $this->setAttributes((object) array_merge(
+ (array) $this->attributes,
+ (array) $connection->cleanupAttributes(ldap_get_attributes($ds, $entry), $adFields)
+ ));
+ } else {
+ if (ldap_errno($ds) !== 1) {
+ // One stands for "operations error" which occurs if not bound non-anonymously.
+
+ throw new LdapException(
+ 'Configuration options query failed (%s:%d): %s. Check if hostname and port of the'
+ . ' ldap resource are correct and if anonymous access is permitted.',
+ $connection->getHostname(),
+ $connection->getPort(),
+ ldap_error($ds)
+ );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine the active directory version using the available capabillities
+ *
+ * @return null|string The server version description or null when unknown
+ */
+ protected function getAdObjectVersionName()
+ {
+ if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_W8_OID])) {
+ return 'Windows Server 2012 (or newer)';
+ }
+ if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V61_R2_OID])) {
+ return 'Windows Server 2008 R2 (or newer)';
+ }
+ if (isset($this->oids[self::LDAP_CAP_ACTIVE_DIRECTORY_V60_OID])) {
+ return 'Windows Server 2008 (or newer)';
+ }
+ return null;
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php
new file mode 100644
index 0000000..982db42
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/LdapConnection.php
@@ -0,0 +1,1584 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+use ArrayIterator;
+use Exception;
+use Icinga\Data\Filter\FilterNot;
+use LogicException;
+use stdClass;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Inspectable;
+use Icinga\Data\Inspection;
+use Icinga\Data\Selectable;
+use Icinga\Data\Sortable;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Url;
+
+/**
+ * Encapsulate LDAP connections and query creation
+ */
+class LdapConnection implements Selectable, Inspectable
+{
+ /**
+ * Indicates that the target object cannot be found
+ *
+ * @var int
+ */
+ const LDAP_NO_SUCH_OBJECT = 32;
+
+ /**
+ * Indicates that in a search operation, the size limit specified by the client or the server has been exceeded
+ *
+ * @var int
+ */
+ const LDAP_SIZELIMIT_EXCEEDED = 4;
+
+ /**
+ * Indicates that an LDAP server limit set by an administrative authority has been exceeded
+ *
+ * @var int
+ */
+ const LDAP_ADMINLIMIT_EXCEEDED = 11;
+
+ /**
+ * Indicates that during a bind operation one of the following occurred: The client passed either an incorrect DN
+ * or password, or the password is incorrect because it has expired, intruder detection has locked the account, or
+ * another similar reason.
+ *
+ * @var int
+ */
+ const LDAP_INVALID_CREDENTIALS = 49;
+
+ /**
+ * The default page size to use for paged queries
+ *
+ * @var int
+ */
+ const PAGE_SIZE = 1000;
+
+ /**
+ * Encrypt connection using STARTTLS (upgrading a plain text connection)
+ *
+ * @var string
+ */
+ const STARTTLS = 'starttls';
+
+ /**
+ * Encrypt connection using LDAP over SSL (using a separate port)
+ *
+ * @var string
+ */
+ const LDAPS = 'ldaps';
+
+ /** @var ConfigObject Connection configuration */
+ protected $config;
+
+ /**
+ * Encryption for the connection if any
+ *
+ * @var string
+ */
+ protected $encryption;
+
+ /**
+ * The LDAP link identifier being used
+ *
+ * @var resource
+ */
+ protected $ds;
+
+ /**
+ * The ip address, hostname or ldap URI being used to connect with the LDAP server
+ *
+ * @var string
+ */
+ protected $hostname;
+
+ /**
+ * The port being used to connect with the LDAP server
+ *
+ * @var int
+ */
+ protected $port;
+
+ /**
+ * The distinguished name being used to bind to the LDAP server
+ *
+ * @var string
+ */
+ protected $bindDn;
+
+ /**
+ * The password being used to bind to the LDAP server
+ *
+ * @var string
+ */
+ protected $bindPw;
+
+ /**
+ * The distinguished name being used as the base path for queries which do not provide one theirselves
+ *
+ * @var string
+ */
+ protected $rootDn;
+
+ /**
+ * Whether the bind on this connection has already been performed
+ *
+ * @var bool
+ */
+ protected $bound;
+
+ /**
+ * The current connection's root node
+ *
+ * @var Root
+ */
+ protected $root;
+
+ /**
+ * LDAP_OPT_NETWORK_TIMEOUT for the LDAP connection
+ *
+ * @var int
+ */
+ protected $timeout;
+
+ /**
+ * The properties and capabilities of the LDAP server
+ *
+ * @var LdapCapabilities
+ */
+ protected $capabilities;
+
+ /**
+ * Whether discovery was successful
+ *
+ * @var bool
+ */
+ protected $discoverySuccess;
+
+ /**
+ * The cause of the discovery's failure
+ *
+ * @var Exception|null
+ */
+ private $discoveryError;
+
+ /**
+ * Whether the current connection is encrypted
+ *
+ * @var bool
+ */
+ protected $encrypted = null;
+
+ /**
+ * Create a new connection object
+ *
+ * @param ConfigObject $config
+ */
+ public function __construct(ConfigObject $config)
+ {
+ $this->config = $config;
+ $this->hostname = $config->hostname;
+ $this->bindDn = $config->bind_dn;
+ $this->bindPw = $config->bind_pw;
+ $this->rootDn = $config->root_dn;
+ $this->port = (int) $config->get('port', 389);
+ $this->timeout = (int) $config->get('timeout', 5);
+
+ $this->encryption = $config->encryption;
+ if ($this->encryption !== null) {
+ $this->encryption = strtolower($this->encryption);
+ }
+ }
+
+ /**
+ * Return the ip address, hostname or ldap URI being used to connect with the LDAP server
+ *
+ * @return string
+ */
+ public function getHostname()
+ {
+ return $this->hostname;
+ }
+
+ /**
+ * Return the port being used to connect with the LDAP server
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Return the distinguished name being used as the base path for queries which do not provide one theirselves
+ *
+ * @return string
+ */
+ public function getDn()
+ {
+ return $this->rootDn;
+ }
+
+ /**
+ * Return the root node for this connection
+ *
+ * @return Root
+ */
+ public function root()
+ {
+ if ($this->root === null) {
+ $this->root = Root::forConnection($this);
+ }
+
+ return $this->root;
+ }
+
+ /**
+ * Return the LDAP link identifier being used
+ *
+ * Establishes a connection if necessary.
+ *
+ * @return resource
+ */
+ public function getConnection()
+ {
+ if ($this->ds === null) {
+ $this->ds = $this->prepareNewConnection();
+ }
+
+ return $this->ds;
+ }
+
+ /**
+ * Return the capabilities of the current connection
+ *
+ * @return LdapCapabilities
+ */
+ public function getCapabilities()
+ {
+ if ($this->capabilities === null) {
+ try {
+ $this->capabilities = LdapCapabilities::discoverCapabilities($this);
+ $this->discoverySuccess = true;
+ $this->discoveryError = null;
+ } catch (LdapException $e) {
+ Logger::debug($e);
+ Logger::warning('LADP discovery failed, assuming default LDAP capabilities.');
+ $this->capabilities = new LdapCapabilities(); // create empty default capabilities
+ $this->discoverySuccess = false;
+ $this->discoveryError = $e;
+ }
+ }
+
+ return $this->capabilities;
+ }
+
+ /**
+ * Return whether discovery was successful
+ *
+ * @return bool true if the capabilities were successfully determined, false if the capabilities were guessed
+ */
+ public function discoverySuccessful()
+ {
+ if ($this->discoverySuccess === null) {
+ $this->getCapabilities(); // Initializes self::$discoverySuccess
+ }
+
+ return $this->discoverySuccess;
+ }
+
+ /**
+ * Get discovery error if any
+ *
+ * @return Exception|null
+ */
+ public function getDiscoveryError()
+ {
+ return $this->discoveryError;
+ }
+
+ /**
+ * Return whether the current connection is encrypted
+ *
+ * @return bool
+ */
+ public function isEncrypted()
+ {
+ if ($this->encrypted === null) {
+ return false;
+ }
+
+ return $this->encrypted;
+ }
+
+ /**
+ * Perform a LDAP bind on the current connection
+ *
+ * @throws LdapException In case the LDAP bind was unsuccessful or insecure
+ */
+ public function bind()
+ {
+ if ($this->bound) {
+ return $this;
+ }
+
+ $ds = $this->getConnection();
+
+ $success = @ldap_bind($ds, $this->bindDn, $this->bindPw);
+ if (! $success) {
+ throw new LdapException(
+ 'LDAP bind (%s / %s) to %s failed: %s',
+ $this->bindDn,
+ '***' /* $this->bindPw */,
+ $this->normalizeHostname($this->hostname),
+ ldap_error($ds)
+ );
+ }
+
+ $this->bound = true;
+ return $this;
+ }
+
+ /**
+ * Provide a query on this connection
+ *
+ * @return LdapQuery
+ */
+ public function select()
+ {
+ return new LdapQuery($this);
+ }
+
+ /**
+ * Fetch and return all rows of the given query's result set using an iterator
+ *
+ * @param LdapQuery $query The query returning the result set
+ *
+ * @return ArrayIterator
+ */
+ public function query(LdapQuery $query)
+ {
+ return new ArrayIterator($this->fetchAll($query));
+ }
+
+ /**
+ * Count all rows of the given query's result set
+ *
+ * @param LdapQuery $query The query returning the result set
+ *
+ * @return int
+ */
+ public function count(LdapQuery $query)
+ {
+ $this->bind();
+
+ if (($unfoldAttribute = $query->getUnfoldAttribute()) !== null) {
+ $desiredColumns = $query->getColumns();
+ if (isset($desiredColumns[$unfoldAttribute])) {
+ $fields = array($unfoldAttribute => $desiredColumns[$unfoldAttribute]);
+ } elseif (in_array($unfoldAttribute, $desiredColumns, true)) {
+ $fields = array($unfoldAttribute);
+ } else {
+ throw new ProgrammingError(
+ 'The attribute used to unfold a query\'s result must be selected'
+ );
+ }
+
+ $res = $this->runQuery($query, $fields);
+ return count($res);
+ }
+
+ $ds = $this->getConnection();
+ $results = $this->ldapSearch($query, array('dn'));
+
+ if ($results === false) {
+ if (ldap_errno($ds) !== self::LDAP_NO_SUCH_OBJECT) {
+ throw new LdapException(
+ 'LDAP count query "%s" (base %s) failed: %s',
+ (string) $query,
+ $query->getBase() ?: $this->getDn(),
+ ldap_error($ds)
+ );
+ }
+ }
+
+ return ldap_count_entries($ds, $results);
+ }
+
+ /**
+ * Retrieve an array containing all rows of the result set
+ *
+ * @param LdapQuery $query The query returning the result set
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ *
+ * @return array
+ */
+ public function fetchAll(LdapQuery $query, array $fields = null)
+ {
+ $this->bind();
+
+ if ($query->getUsePagedResults() && $this->getCapabilities()->hasPagedResult()) {
+ return $this->runPagedQuery($query, $fields);
+ } else {
+ return $this->runQuery($query, $fields);
+ }
+ }
+
+ /**
+ * Fetch the first row of the result set
+ *
+ * @param LdapQuery $query The query returning the result set
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ *
+ * @return mixed
+ */
+ public function fetchRow(LdapQuery $query, array $fields = null)
+ {
+ $clonedQuery = clone $query;
+ $clonedQuery->limit(1);
+ $clonedQuery->setUsePagedResults(false);
+ $results = $this->fetchAll($clonedQuery, $fields);
+ return array_shift($results) ?: false;
+ }
+
+ /**
+ * Fetch the first column of all rows of the result set as an array
+ *
+ * @param LdapQuery $query The query returning the result set
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case no attribute is being requested
+ */
+ public function fetchColumn(LdapQuery $query, array $fields = null)
+ {
+ if ($fields === null) {
+ $fields = $query->getColumns();
+ }
+
+ if (empty($fields)) {
+ throw new ProgrammingError('You must request at least one attribute when fetching a single column');
+ }
+
+ $alias = key($fields);
+ $results = $this->fetchAll($query, array($alias => current($fields)));
+ $column = is_int($alias) ? current($fields) : $alias;
+ $values = array();
+ foreach ($results as $row) {
+ if (isset($row->$column)) {
+ $values[] = $row->$column;
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Fetch the first column of the first row of the result set
+ *
+ * @param LdapQuery $query The query returning the result set
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ *
+ * @return string
+ */
+ public function fetchOne(LdapQuery $query, array $fields = null)
+ {
+ $row = $this->fetchRow($query, $fields);
+ if ($row === false) {
+ return false;
+ }
+
+ $values = get_object_vars($row);
+ if (empty($values)) {
+ return false;
+ }
+
+ if ($fields === null) {
+ // Fetch the desired columns from the query if not explicitly overriden in the method's parameter
+ $fields = $query->getColumns();
+ }
+
+ if (empty($fields)) {
+ // The desired columns may be empty independently whether provided by the query or the method's parameter
+ return array_shift($values);
+ }
+
+ $alias = key($fields);
+ return $values[is_string($alias) ? $alias : $fields[$alias]];
+ }
+
+ /**
+ * Fetch all rows of the result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
+ *
+ * @param LdapQuery $query The query returning the result set
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case there are less than two attributes being requested
+ */
+ public function fetchPairs(LdapQuery $query, array $fields = null)
+ {
+ if ($fields === null) {
+ $fields = $query->getColumns();
+ }
+
+ if (count($fields) < 2) {
+ throw new ProgrammingError('You are required to request at least two attributes');
+ }
+
+ $columns = $desiredColumnNames = array();
+ foreach ($fields as $alias => $column) {
+ if (is_int($alias)) {
+ $columns[] = $column;
+ $desiredColumnNames[] = $column;
+ } else {
+ $columns[$alias] = $column;
+ $desiredColumnNames[] = $alias;
+ }
+
+ if (count($desiredColumnNames) === 2) {
+ break;
+ }
+ }
+
+ $results = $this->fetchAll($query, $columns);
+ $pairs = array();
+ foreach ($results as $row) {
+ $colOne = $desiredColumnNames[0];
+ $colTwo = $desiredColumnNames[1];
+ $pairs[$row->$colOne] = $row->$colTwo;
+ }
+
+ return $pairs;
+ }
+
+ /**
+ * Fetch an LDAP entry by its DN
+ *
+ * @param string $dn
+ * @param array|null $fields
+ *
+ * @return StdClass|bool
+ */
+ public function fetchByDn($dn, array $fields = null)
+ {
+ return $this->select()
+ ->from('*', $fields)
+ ->setBase($dn)
+ ->setScope('base')
+ ->fetchRow();
+ }
+
+ /**
+ * Test the given LDAP credentials by establishing a connection and attempting a LDAP bind
+ *
+ * @param string $bindDn
+ * @param string $bindPw
+ *
+ * @return bool Whether the given credentials are valid
+ *
+ * @throws LdapException In case an error occured while establishing the connection or attempting the bind
+ */
+ public function testCredentials($bindDn, $bindPw)
+ {
+ $ds = $this->getConnection();
+ $success = @ldap_bind($ds, $bindDn, $bindPw);
+ if (! $success) {
+ if (ldap_errno($ds) === self::LDAP_INVALID_CREDENTIALS) {
+ Logger::debug(
+ 'Testing LDAP credentials (%s / %s) failed: %s',
+ $bindDn,
+ '***',
+ ldap_error($ds)
+ );
+ return false;
+ }
+
+ throw new LdapException(ldap_error($ds));
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether an entry identified by the given distinguished name exists
+ *
+ * @param string $dn
+ *
+ * @return bool
+ */
+ public function hasDn($dn)
+ {
+ $ds = $this->getConnection();
+ $this->bind();
+
+ $result = ldap_read($ds, $dn, '(objectClass=*)', array('objectClass'));
+ return ldap_count_entries($ds, $result) > 0;
+ }
+
+ /**
+ * Delete a root entry and all of its children identified by the given distinguished name
+ *
+ * @param string $dn
+ *
+ * @return bool
+ *
+ * @throws LdapException In case an error occured while deleting an entry
+ */
+ public function deleteRecursively($dn)
+ {
+ $ds = $this->getConnection();
+ $this->bind();
+
+ $result = @ldap_list($ds, $dn, '(objectClass=*)', array('objectClass'));
+ if ($result === false) {
+ if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
+ return false;
+ }
+
+ throw new LdapException('LDAP list for "%s" failed: %s', $dn, ldap_error($ds));
+ }
+
+ $children = ldap_get_entries($ds, $result);
+ for ($i = 0; $i < $children['count']; $i++) {
+ $result = $this->deleteRecursively($children[$i]['dn']);
+ if (! $result) {
+ // TODO: return result code, if delete fails
+ throw new LdapException('Recursively deleting "%s" failed', $dn);
+ }
+ }
+
+ return $this->deleteDn($dn);
+ }
+
+ /**
+ * Delete a single entry identified by the given distinguished name
+ *
+ * @param string $dn
+ *
+ * @return bool
+ *
+ * @throws LdapException In case an error occured while deleting the entry
+ */
+ public function deleteDn($dn)
+ {
+ $ds = $this->getConnection();
+ $this->bind();
+
+ $result = @ldap_delete($ds, $dn);
+ if ($result === false) {
+ if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
+ return false; // TODO: Isn't it a success if something i'd like to remove is not existing at all???
+ }
+
+ throw new LdapException('LDAP delete for "%s" failed: %s', $dn, ldap_error($ds));
+ }
+
+ return true;
+ }
+
+ /**
+ * Fetch the distinguished name of the result of the given query
+ *
+ * @param LdapQuery $query The query returning the result set
+ *
+ * @return string The distinguished name, or false when the given query yields no results
+ *
+ * @throws LdapException In case the query yields multiple results
+ */
+ public function fetchDn(LdapQuery $query)
+ {
+ $rows = $this->fetchAll($query, array());
+ if (count($rows) > 1) {
+ throw new LdapException('Cannot fetch single DN for %s', $query);
+ }
+
+ return key($rows);
+ }
+
+ /**
+ * Run the given LDAP query and return the resulting entries
+ *
+ * @param LdapQuery $query The query to fetch results with
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ *
+ * @return array
+ *
+ * @throws LdapException In case an error occured while fetching the results
+ */
+ protected function runQuery(LdapQuery $query, array $fields = null)
+ {
+ $limit = $query->getLimit();
+ $offset = $query->hasOffset() ? $query->getOffset() : 0;
+
+ if ($fields === null) {
+ $fields = $query->getColumns();
+ }
+
+ $ds = $this->getConnection();
+
+ $serverSorting = ! $this->config->disable_server_side_sort
+ && $this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID);
+
+ if ($query->hasOrder()) {
+ if ($serverSorting) {
+ ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array(
+ array(
+ 'oid' => LdapCapabilities::LDAP_SERVER_SORT_OID,
+ 'value' => $this->encodeSortRules($query->getOrder())
+ )
+ ));
+ } elseif (! empty($fields)) {
+ foreach ($query->getOrder() as $rule) {
+ if (! in_array($rule[0], $fields, true)) {
+ $fields[] = $rule[0];
+ }
+ }
+ }
+ }
+
+ $unfoldAttribute = $query->getUnfoldAttribute();
+ if ($unfoldAttribute) {
+ foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) {
+ $fieldKey = array_search($filterColumn, $fields, true);
+ if ($fieldKey === false || is_string($fieldKey)) {
+ $fields[] = $filterColumn;
+ }
+ }
+ }
+
+ $results = $this->ldapSearch(
+ $query,
+ array_values($fields),
+ 0,
+ ($serverSorting || ! $query->hasOrder()) && $limit ? $offset + $limit : 0
+ );
+ if ($results === false) {
+ if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
+ return array();
+ }
+
+ throw new LdapException(
+ 'LDAP query "%s" (base %s) failed. Error: %s',
+ $query,
+ $query->getBase() ?: $this->rootDn,
+ ldap_error($ds)
+ );
+ } elseif (ldap_count_entries($ds, $results) === 0) {
+ return array();
+ }
+
+ $count = 0;
+ $entries = array();
+ $entry = ldap_first_entry($ds, $results);
+ do {
+ if ($unfoldAttribute) {
+ $rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute);
+ if (is_array($rows)) {
+ // TODO: Register the DN the same way as a section name in the ArrayDatasource!
+ foreach ($rows as $row) {
+ if ($query->getFilter()->matches($row)) {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[] = $row;
+ }
+
+ if ($serverSorting && $limit > 0 && $limit === count($entries)) {
+ break;
+ }
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $rows;
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
+ ldap_get_attributes($ds, $entry),
+ $fields
+ );
+ }
+ }
+ } while ((! $serverSorting || $limit === 0 || $limit !== count($entries))
+ && ($entry = ldap_next_entry($ds, $entry))
+ );
+
+ if (! $serverSorting) {
+ if ($query->hasOrder()) {
+ uasort($entries, array($query, 'compare'));
+ }
+
+ if ($limit && $count > $limit) {
+ $entries = array_splice($entries, $query->hasOffset() ? $query->getOffset() : 0, $limit);
+ }
+ }
+
+ ldap_free_result($results);
+ return $entries;
+ }
+
+ /**
+ * Run the given LDAP query and return the resulting entries
+ *
+ * This utilizes paged search requests as defined in RFC 2696.
+ *
+ * @param LdapQuery $query The query to fetch results with
+ * @param array $fields Request these attributes instead of the ones registered in the given query
+ * @param int $pageSize The maximum page size, defaults to self::PAGE_SIZE
+ *
+ * @return array
+ *
+ * @throws LdapException In case an error occured while fetching the results
+ */
+ protected function runPagedQuery(LdapQuery $query, array $fields = null, $pageSize = null)
+ {
+ if ($pageSize === null) {
+ $pageSize = static::PAGE_SIZE;
+ }
+
+ $limit = $query->getLimit();
+ $offset = $query->hasOffset() ? $query->getOffset() : 0;
+
+ if ($fields === null) {
+ $fields = $query->getColumns();
+ }
+
+ $ds = $this->getConnection();
+
+ $serverSorting = false;//$this->getCapabilities()->hasOid(LdapCapabilities::LDAP_SERVER_SORT_OID);
+ if (! $serverSorting && $query->hasOrder() && ! empty($fields)) {
+ foreach ($query->getOrder() as $rule) {
+ if (! in_array($rule[0], $fields, true)) {
+ $fields[] = $rule[0];
+ }
+ }
+ }
+
+ $unfoldAttribute = $query->getUnfoldAttribute();
+ if ($unfoldAttribute) {
+ foreach ($query->getFilter()->listFilteredColumns() as $filterColumn) {
+ $fieldKey = array_search($filterColumn, $fields, true);
+ if ($fieldKey === false || is_string($fieldKey)) {
+ $fields[] = $filterColumn;
+ }
+ }
+ }
+
+ $controls = [];
+ $legacyControlHandling = version_compare(PHP_VERSION, '7.3.0') < 0;
+ if ($serverSorting && $query->hasOrder()) {
+ $control = [
+ 'oid' => LDAP_CONTROL_SORTREQUEST,
+ 'value' => $this->encodeSortRules($query->getOrder())
+ ];
+ if ($legacyControlHandling) {
+ ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, [$control]);
+ } else {
+ $controls[LDAP_CONTROL_SORTREQUEST] = $control;
+ }
+ }
+
+ $count = 0;
+ $cookie = '';
+ $entries = array();
+ do {
+ if ($legacyControlHandling) {
+ // Do not request the pagination control as a critical extension, as we want the
+ // server to return results even if the paged search request cannot be satisfied
+ ldap_control_paged_result($ds, $pageSize, false, $cookie);
+ } else {
+ $controls[LDAP_CONTROL_PAGEDRESULTS] = [
+ 'oid' => LDAP_CONTROL_PAGEDRESULTS,
+ 'iscritical' => false, // See above
+ 'value' => [
+ 'size' => $pageSize,
+ 'cookie' => $cookie
+ ]
+ ];
+ }
+
+ $results = $this->ldapSearch(
+ $query,
+ array_values($fields),
+ 0,
+ ($serverSorting || ! $query->hasOrder()) && $limit ? $offset + $limit : 0,
+ 0,
+ LDAP_DEREF_NEVER,
+ empty($controls) ? null : $controls
+ );
+ if ($results === false) {
+ if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
+ break;
+ }
+
+ throw new LdapException(
+ 'LDAP query "%s" (base %s) failed. Error: %s',
+ (string) $query,
+ $query->getBase() ?: $this->getDn(),
+ ldap_error($ds)
+ );
+ } elseif (ldap_count_entries($ds, $results) === 0) {
+ if (in_array(
+ ldap_errno($ds),
+ array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED),
+ true
+ )) {
+ Logger::warning(
+ 'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
+ $count,
+ ldap_error($ds)
+ );
+ }
+
+ break;
+ }
+
+ $entry = ldap_first_entry($ds, $results);
+ do {
+ if ($unfoldAttribute) {
+ $rows = $this->cleanupAttributes(ldap_get_attributes($ds, $entry), $fields, $unfoldAttribute);
+ if (is_array($rows)) {
+ // TODO: Register the DN the same way as a section name in the ArrayDatasource!
+ foreach ($rows as $row) {
+ if ($query->getFilter()->matches($row)) {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[] = $row;
+ }
+
+ if ($serverSorting && $limit > 0 && $limit === count($entries)) {
+ break;
+ }
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $rows;
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
+ ldap_get_attributes($ds, $entry),
+ $fields
+ );
+ }
+ }
+ } while ((! $serverSorting || $limit === 0 || $limit !== count($entries))
+ && ($entry = ldap_next_entry($ds, $entry))
+ );
+
+ if ($legacyControlHandling) {
+ if (false === @ldap_control_paged_result_response($ds, $results, $cookie)) {
+ // If the page size is greater than or equal to the sizeLimit value, the server should ignore the
+ // control as the request can be satisfied in a single page: https://www.ietf.org/rfc/rfc2696.txt
+ // This applies no matter whether paged search requests are permitted or not. You're done once you
+ // got everything you were out for.
+ if ($serverSorting && count($entries) !== $limit) {
+ // The server does not support pagination, but still returned a response by ignoring the
+ // pagedResultsControl. We output a warning to indicate that the pagination control was ignored.
+ Logger::warning(
+ 'Unable to request paged LDAP results. Does the server allow paged search requests?'
+ );
+ }
+ }
+ } else {
+ ldap_parse_result($ds, $results, $errno, $dn, $errmsg, $refs, $controlsReturned);
+ $cookie = $controlsReturned[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
+ }
+
+ ldap_free_result($results);
+ } while ($cookie && (! $serverSorting || $limit === 0 || count($entries) < $limit));
+
+ if ($legacyControlHandling && $cookie) {
+ // A sequence of paged search requests is abandoned by the client sending a search request containing a
+ // pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
+ // the server: https://www.ietf.org/rfc/rfc2696.txt
+ ldap_control_paged_result($ds, 0, false, $cookie);
+ // Returns no entries, due to the page size
+ ldap_search($ds, $query->getBase() ?: $this->getDn(), (string) $query);
+ }
+
+ if (! $serverSorting) {
+ if ($query->hasOrder()) {
+ uasort($entries, array($query, 'compare'));
+ }
+
+ if ($limit && $count > $limit) {
+ $entries = array_splice($entries, $query->hasOffset() ? $query->getOffset() : 0, $limit);
+ }
+ }
+
+ return $entries;
+ }
+
+ /**
+ * Clean up the given attributes and return them as simple object
+ *
+ * Applies column aliases, aggregates/unfolds multi-value attributes
+ * as array and sets null for each missing attribute.
+ *
+ * @param array $attributes
+ * @param array $requestedFields
+ * @param string $unfoldAttribute
+ *
+ * @return object|array An array in case the object has been unfolded
+ */
+ public function cleanupAttributes($attributes, array $requestedFields, $unfoldAttribute = null)
+ {
+ // In case the result contains attributes with a differing case than the requested fields, it is
+ // necessary to create another array to map attributes case insensitively to their requested counterparts.
+ // This does also apply the virtual alias handling. (Since an LDAP server does not handle such)
+ $loweredFieldMap = array();
+ foreach ($requestedFields as $alias => $name) {
+ $loweredName = strtolower($name);
+ if (isset($loweredFieldMap[$loweredName])) {
+ if (! is_array($loweredFieldMap[$loweredName])) {
+ $loweredFieldMap[$loweredName] = array($loweredFieldMap[$loweredName]);
+ }
+
+ $loweredFieldMap[$loweredName][] = is_string($alias) ? $alias : $name;
+ } else {
+ $loweredFieldMap[$loweredName] = is_string($alias) ? $alias : $name;
+ }
+ }
+
+ $cleanedAttributes = array();
+ for ($i = 0; $i < $attributes['count']; $i++) {
+ $attribute_name = $attributes[$i];
+ if ($attributes[$attribute_name]['count'] === 1) {
+ $attribute_value = $attributes[$attribute_name][0];
+ } else {
+ $attribute_value = array();
+ for ($j = 0; $j < $attributes[$attribute_name]['count']; $j++) {
+ $attribute_value[] = $attributes[$attribute_name][$j];
+ }
+ }
+
+ $requestedAttributeName = isset($loweredFieldMap[strtolower($attribute_name)])
+ ? $loweredFieldMap[strtolower($attribute_name)]
+ : $attribute_name;
+ if (is_array($requestedAttributeName)) {
+ foreach ($requestedAttributeName as $requestedName) {
+ $cleanedAttributes[$requestedName] = $attribute_value;
+ }
+ } else {
+ $cleanedAttributes[$requestedAttributeName] = $attribute_value;
+ }
+ }
+
+ // The result may not contain all requested fields, so populate the cleaned
+ // result with the missing fields and their value being set to null
+ foreach ($requestedFields as $alias => $name) {
+ if (! is_string($alias)) {
+ $alias = $name;
+ }
+
+ if (! array_key_exists($alias, $cleanedAttributes)) {
+ $cleanedAttributes[$alias] = null;
+ Logger::debug('LDAP query result does not provide the requested field "%s"', $name);
+ }
+ }
+
+ if ($unfoldAttribute !== null
+ && isset($cleanedAttributes[$unfoldAttribute])
+ && is_array($cleanedAttributes[$unfoldAttribute])
+ ) {
+ $siblings = array();
+ foreach ($loweredFieldMap as $loweredName => $requestedNames) {
+ if (is_array($requestedNames) && in_array($unfoldAttribute, $requestedNames, true)) {
+ $siblings = array_diff($requestedNames, array($unfoldAttribute));
+ break;
+ }
+ }
+
+ $values = $cleanedAttributes[$unfoldAttribute];
+ unset($cleanedAttributes[$unfoldAttribute]);
+ $baseRow = (object) $cleanedAttributes;
+ $rows = array();
+ foreach ($values as $value) {
+ $row = clone $baseRow;
+ $row->{$unfoldAttribute} = $value;
+ foreach ($siblings as $sibling) {
+ $row->{$sibling} = $value;
+ }
+
+ $rows[] = $row;
+ }
+
+ return $rows;
+ }
+
+ return (object) $cleanedAttributes;
+ }
+
+ /**
+ * Encode the given array of sort rules as ASN.1 octet stream according to RFC 2891
+ *
+ * @param array $sortRules
+ *
+ * @return string Binary representation of the octet stream
+ */
+ protected function encodeSortRules(array $sortRules)
+ {
+ $sequenceOf = '';
+
+ foreach ($sortRules as $rule) {
+ if ($rule[1] === Sortable::SORT_DESC) {
+ $reversed = '8101ff';
+ } else {
+ $reversed = '';
+ }
+
+ $attributeType = unpack('H*', $rule[0]);
+ $attributeType = $attributeType[1];
+ $attributeOctets = strlen($attributeType) / 2;
+ if ($attributeOctets >= 127) {
+ // Use the indefinite form of the length octets (the long form would be another option)
+ $attributeType = '0440' . $attributeType . '0000';
+ } else {
+ $attributeType = '04' . str_pad(dechex($attributeOctets), 2, '0', STR_PAD_LEFT) . $attributeType;
+ }
+
+ $sequence = $attributeType . $reversed;
+ $sequenceOctects = strlen($sequence) / 2;
+ if ($sequenceOctects >= 127) {
+ $sequence = '3040' . $sequence . '0000';
+ } else {
+ $sequence = '30' . str_pad(dechex($sequenceOctects), 2, '0', STR_PAD_LEFT) . $sequence;
+ }
+
+ $sequenceOf .= $sequence;
+ }
+
+ $sequenceOfOctets = strlen($sequenceOf) / 2;
+ if ($sequenceOfOctets >= 127) {
+ $sequenceOf = '3040' . $sequenceOf . '0000';
+ } else {
+ $sequenceOf = '30' . str_pad(dechex($sequenceOfOctets), 2, '0', STR_PAD_LEFT) . $sequenceOf;
+ }
+
+ return hex2bin($sequenceOf);
+ }
+
+ /**
+ * Prepare and establish a connection with the LDAP server
+ *
+ * @param Inspection $info Optional inspection to fill with diagnostic info
+ *
+ * @return resource A LDAP link identifier
+ *
+ * @throws LdapException In case the connection is not possible
+ */
+ protected function prepareNewConnection(Inspection $info = null)
+ {
+ if (! isset($info)) {
+ $info = new Inspection('');
+ }
+
+ $hostname = $this->normalizeHostname($this->hostname);
+
+ $ds = ldap_connect($hostname, $this->port);
+
+ // Set a proper timeout for each connection
+ ldap_set_option($ds, LDAP_OPT_NETWORK_TIMEOUT, $this->timeout);
+
+ // Usage of ldap_rename, setting LDAP_OPT_REFERRALS to 0 or using STARTTLS requires LDAPv3.
+ // If this does not work we're probably not in a PHP 5.3+ environment as it is VERY
+ // unlikely that the server complains about it by itself prior to a bind request
+ ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
+
+ // Not setting this results in "Operations error" on AD when using the whole domain as search base
+ ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
+
+ if ($this->encryption === static::LDAPS) {
+ $info->write('Connect using LDAPS');
+ } elseif ($this->encryption === static::STARTTLS) {
+ $this->encrypted = true;
+ $info->write('Connect using STARTTLS');
+ if (! ldap_start_tls($ds)) {
+ throw new LdapException('LDAP STARTTLS failed: %s', ldap_error($ds));
+ }
+ } elseif ($this->encryption !== static::LDAPS) {
+ $this->encrypted = false;
+ $info->write('Connect without encryption');
+ }
+
+ return $ds;
+ }
+
+ /**
+ * Perform a LDAP search and return the result
+ *
+ * @param LdapQuery $query
+ * @param array $attributes An array of the required attributes
+ * @param int $attrsonly Should be set to 1 if only attribute types are wanted
+ * @param int $sizelimit Enables you to limit the count of entries fetched
+ * @param int $timelimit Sets the number of seconds how long is spend on the search
+ * @param int $deref
+ * @param array $controls LDAP Controls to send with the request (Only supported with PHP v7.3+)
+ *
+ * @return resource|bool A search result identifier or false on error
+ *
+ * @throws LogicException If the LDAP query search scope is unsupported
+ */
+ public function ldapSearch(
+ LdapQuery $query,
+ array $attributes = null,
+ $attrsonly = 0,
+ $sizelimit = 0,
+ $timelimit = 0,
+ $deref = LDAP_DEREF_NEVER,
+ $controls = null
+ ) {
+ $queryString = (string) $query;
+ $baseDn = $query->getBase() ?: $this->getDn();
+ $scope = $query->getScope();
+
+ if (Logger::getInstance()->getLevel() === Logger::DEBUG) {
+ // We're checking the level by ourselves to avoid rendering the ldapsearch commandline for nothing
+ $starttlsParam = $this->encryption === static::STARTTLS ? ' -ZZ' : '';
+
+ $bindParams = '';
+ if ($this->bound) {
+ $bindParams = ' -D "' . $this->bindDn . '"' . ($this->bindPw ? ' -W' : '');
+ }
+
+ if ($deref === LDAP_DEREF_NEVER) {
+ $derefName = 'never';
+ } elseif ($deref === LDAP_DEREF_ALWAYS) {
+ $derefName = 'always';
+ } elseif ($deref === LDAP_DEREF_SEARCHING) {
+ $derefName = 'search';
+ } else { // $deref === LDAP_DEREF_FINDING
+ $derefName = 'find';
+ }
+
+ Logger::debug("Issuing LDAP search. Use '%s' to reproduce.", sprintf(
+ 'ldapsearch -P 3%s -H "%s"%s -b "%s" -s "%s" -z %u -l %u -a "%s"%s%s%s',
+ $starttlsParam,
+ $this->normalizeHostname($this->hostname),
+ $bindParams,
+ $baseDn,
+ $scope,
+ $sizelimit,
+ $timelimit,
+ $derefName,
+ $attrsonly ? ' -A' : '',
+ $queryString ? ' "' . $queryString . '"' : '',
+ $attributes ? ' "' . join('" "', $attributes) . '"' : ''
+ ));
+ }
+
+ switch ($scope) {
+ case LdapQuery::SCOPE_SUB:
+ $function = 'ldap_search';
+ break;
+ case LdapQuery::SCOPE_ONE:
+ $function = 'ldap_list';
+ break;
+ case LdapQuery::SCOPE_BASE:
+ $function = 'ldap_read';
+ break;
+ default:
+ throw new LogicException('LDAP scope %s not supported by ldapSearch', $scope);
+ }
+
+ // Explicit calls with and without controls,
+ // because the parameter is only supported since PHP 7.3.
+ // Since it is a public method,
+ // providing controls will naturally fail if the parameter is not supported by PHP.
+ if ($controls !== null) {
+ return @$function(
+ $this->getConnection(),
+ $baseDn,
+ $queryString,
+ $attributes,
+ $attrsonly,
+ $sizelimit,
+ $timelimit,
+ $deref,
+ $controls
+ );
+ } else {
+ return @$function(
+ $this->getConnection(),
+ $baseDn,
+ $queryString,
+ $attributes,
+ $attrsonly,
+ $sizelimit,
+ $timelimit,
+ $deref
+ );
+ }
+ }
+
+ /**
+ * Create an LDAP entry
+ *
+ * @param string $dn The distinguished name to use
+ * @param array $attributes The entry's attributes
+ *
+ * @return bool Whether the operation was successful
+ */
+ public function addEntry($dn, array $attributes)
+ {
+ return ldap_add($this->getConnection(), $dn, $attributes);
+ }
+
+ /**
+ * Modify an LDAP entry
+ *
+ * @param string $dn The distinguished name to use
+ * @param array $attributes The attributes to update the entry with
+ *
+ * @return bool Whether the operation was successful
+ */
+ public function modifyEntry($dn, array $attributes)
+ {
+ return ldap_modify($this->getConnection(), $dn, $attributes);
+ }
+
+ /**
+ * Change the distinguished name of an LDAP entry
+ *
+ * @param string $dn The entry's current distinguished name
+ * @param string $newRdn The new relative distinguished name
+ * @param string $newParentDn The new parent or superior entry's distinguished name
+ *
+ * @return resource The resulting search result identifier
+ *
+ * @throws LdapException In case an error occured
+ */
+ public function moveEntry($dn, $newRdn, $newParentDn)
+ {
+ $ds = $this->getConnection();
+ $result = ldap_rename($ds, $dn, $newRdn, $newParentDn, false);
+ if ($result === false) {
+ throw new LdapException('Could not move entry "%s" to "%s": %s', $dn, $newRdn, ldap_error($ds));
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return the LDAP specific configuration directory with the given relative path being appended
+ *
+ * @param string $sub
+ *
+ * @return string
+ */
+ protected function getConfigDir($sub = null)
+ {
+ $dir = Config::$configDir . '/ldap';
+ if ($sub !== null) {
+ $dir .= '/' . $sub;
+ }
+
+ return $dir;
+ }
+
+ /**
+ * Render and return a valid LDAP filter representation of the given filter
+ *
+ * @param Filter $filter
+ * @param int $level
+ *
+ * @return string
+ */
+ public function renderFilter(Filter $filter, $level = 0)
+ {
+ if ($filter->isExpression()) {
+ /** @var $filter FilterExpression */
+ return $this->renderFilterExpression($filter);
+ }
+
+ /** @var $filter FilterChain */
+ $parts = array();
+ foreach ($filter->filters() as $filterPart) {
+ $part = $this->renderFilter($filterPart, $level + 1);
+ if ($part) {
+ $parts[] = $part;
+ }
+ }
+
+ if (empty($parts)) {
+ return '';
+ }
+
+ $format = '%1$s(%2$s)';
+ if (count($parts) === 1 && ! $filter instanceof FilterNot) {
+ $format = '%2$s';
+ }
+ if ($level === 0) {
+ $format = '(' . $format . ')';
+ }
+
+ return sprintf($format, $filter->getOperatorSymbol(), implode(')(', $parts));
+ }
+
+ /**
+ * Render and return a valid LDAP filter expression of the given filter
+ *
+ * @param FilterExpression $filter
+ *
+ * @return string
+ */
+ protected function renderFilterExpression(FilterExpression $filter)
+ {
+ $column = $filter->getColumn();
+ $sign = $filter->getSign();
+ $expression = $filter->getExpression();
+ $format = '%1$s%2$s%3$s';
+
+ if ($expression === null || $expression === true) {
+ $expression = '*';
+ } elseif (is_array($expression)) {
+ $seqFormat = '|(%s)';
+ if ($sign === '!=') {
+ $seqFormat = '!(' . $seqFormat . ')';
+ $sign = '=';
+ }
+
+ $seqParts = array();
+ foreach ($expression as $expressionValue) {
+ $seqParts[] = sprintf(
+ $format,
+ LdapUtils::quoteForSearch($column),
+ $sign,
+ LdapUtils::quoteForSearch($expressionValue, true)
+ );
+ }
+
+ return sprintf($seqFormat, implode(')(', $seqParts));
+ }
+
+ if ($sign === '!=') {
+ $format = '!(%1$s=%3$s)';
+ }
+
+ return sprintf(
+ $format,
+ LdapUtils::quoteForSearch($column),
+ $sign,
+ LdapUtils::quoteForSearch($expression, true)
+ );
+ }
+
+ /**
+ * Inspect if this LDAP Connection is working as expected
+ *
+ * Check if connection, bind and encryption is working as expected and get additional
+ * information about the used
+ *
+ * @return Inspection Inspection result
+ */
+ public function inspect()
+ {
+ $insp = new Inspection('Ldap Connection');
+
+ // Try to connect to the server with the given connection parameters
+ try {
+ $ds = $this->prepareNewConnection($insp);
+ } catch (Exception $e) {
+ if ($this->encryption === 'starttls') {
+ // The Exception does not return any proper error messages in case of certificate errors. Connecting
+ // by STARTTLS will usually fail at this point when the certificate is unknown,
+ // so at least try to give some hints.
+ $insp->write('NOTE: There might be an issue with the chosen encryption. Ensure that the LDAP-Server ' .
+ 'supports STARTTLS and that the LDAP-Client is configured to accept its certificate.');
+ }
+ return $insp->error($e->getMessage());
+ }
+
+ // Try a bind-command with the given user credentials, this must not fail
+ $success = @ldap_bind($ds, $this->bindDn, $this->bindPw);
+ $msg = sprintf(
+ 'LDAP bind (%s / %s) to %s',
+ $this->bindDn,
+ '***' /* $this->bindPw */,
+ $this->normalizeHostname($this->hostname)
+ );
+ if (! $success) {
+ // ldap_error does not return any proper error messages in case of certificate errors. Connecting
+ // by LDAPS will usually fail at this point when the certificate is unknown, so at least try to give
+ // some hints.
+ if ($this->encryption === 'ldaps') {
+ $insp->write('NOTE: There might be an issue with the chosen encryption. Ensure that the LDAP-Server ' .
+ ' supports LDAPS and that the LDAP-Client is configured to accept its certificate.');
+ }
+ return $insp->error(sprintf('%s failed: %s', $msg, ldap_error($ds)));
+ }
+ $insp->write(sprintf($msg . ' successful'));
+
+ // Try to execute a schema discovery this may fail if schema discovery is not supported
+ try {
+ $cap = LdapCapabilities::discoverCapabilities($this);
+ $discovery = new Inspection('Discovery Results');
+ $vendor = $cap->getVendor();
+ if (isset($vendor)) {
+ $discovery->write($vendor);
+ }
+ $version = $cap->getVersion();
+ if (isset($version)) {
+ $discovery->write($version);
+ }
+ $discovery->write('Supports STARTTLS: ' . ($cap->hasStartTls() ? 'True' : 'False'));
+ $discovery->write('Default naming context: ' . $cap->getDefaultNamingContext());
+ $insp->write($discovery);
+ } catch (Exception $e) {
+ $insp->write('Schema discovery not possible: ' . $e->getMessage());
+ }
+ return $insp;
+ }
+
+ protected function normalizeHostname($hostname)
+ {
+ $scheme = $this->encryption === static::LDAPS ? 'ldaps://' : 'ldap://';
+ $normalizeHostname = function ($hostname) use ($scheme) {
+ if (strpos($hostname, $scheme) === false) {
+ $hostname = $scheme . $hostname;
+ }
+
+ if (! preg_match('/:\d+$/', $hostname)) {
+ $hostname .= ':' . $this->port;
+ }
+
+ return $hostname;
+ };
+
+ $ldapUrls = explode(' ', $hostname);
+ if (count($ldapUrls) > 1) {
+ foreach ($ldapUrls as & $uri) {
+ $uri = $normalizeHostname($uri);
+ }
+
+ $hostname = implode(' ', $ldapUrls);
+ } else {
+ $hostname = $normalizeHostname($hostname);
+ }
+
+ return $hostname;
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/LdapException.php b/library/Icinga/Protocol/Ldap/LdapException.php
new file mode 100644
index 0000000..740ee29
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/LdapException.php
@@ -0,0 +1,14 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Class LdapException
+ * @package Icinga\Protocol\Ldap
+ */
+class LdapException extends IcingaException
+{
+}
diff --git a/library/Icinga/Protocol/Ldap/LdapQuery.php b/library/Icinga/Protocol/Ldap/LdapQuery.php
new file mode 100644
index 0000000..42a236f
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/LdapQuery.php
@@ -0,0 +1,361 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+use Icinga\Data\Filter\Filter;
+use LogicException;
+use Icinga\Data\SimpleQuery;
+
+/**
+ * LDAP query class
+ */
+class LdapQuery extends SimpleQuery
+{
+ /**
+ * The base dn being used for this query
+ *
+ * @var string
+ */
+ protected $base;
+
+ /**
+ * Whether this query is permitted to utilize paged results
+ *
+ * @var bool
+ */
+ protected $usePagedResults;
+
+ /**
+ * The name of the attribute used to unfold the result
+ *
+ * @var string
+ */
+ protected $unfoldAttribute;
+
+ /**
+ * This query's native LDAP filter
+ *
+ * @var string
+ */
+ protected $nativeFilter;
+
+ /**
+ * Only fetch the entry at the base of the search
+ */
+ const SCOPE_BASE = 'base';
+
+ /**
+ * Fetch entries one below the base DN
+ */
+ const SCOPE_ONE = 'one';
+
+ /**
+ * Fetch all entries below the base DN
+ */
+ const SCOPE_SUB = 'sub';
+
+ /**
+ * All available scopes
+ *
+ * @var array
+ */
+ public static $scopes = array(
+ LdapQuery::SCOPE_BASE,
+ LdapQuery::SCOPE_ONE,
+ LdapQuery::SCOPE_SUB
+ );
+
+ /**
+ * LDAP search scope (default: SCOPE_SUB)
+ *
+ * @var string
+ */
+ protected $scope = LdapQuery::SCOPE_SUB;
+
+ /**
+ * Initialize this query
+ */
+ protected function init()
+ {
+ $this->usePagedResults = false;
+ }
+
+ /**
+ * Set the base dn to be used for this query
+ *
+ * @param string $base
+ *
+ * @return $this
+ */
+ public function setBase($base)
+ {
+ $this->base = $base;
+ return $this;
+ }
+
+ /**
+ * Return the base dn being used for this query
+ *
+ * @return string
+ */
+ public function getBase()
+ {
+ return $this->base;
+ }
+
+ /**
+ * Set whether this query is permitted to utilize paged results
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setUsePagedResults($state = true)
+ {
+ $this->usePagedResults = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this query is permitted to utilize paged results
+ *
+ * @return bool
+ */
+ public function getUsePagedResults()
+ {
+ return $this->usePagedResults;
+ }
+
+ /**
+ * Set the attribute to be used to unfold the result
+ *
+ * @param string $attributeName
+ *
+ * @return $this
+ */
+ public function setUnfoldAttribute($attributeName)
+ {
+ $this->unfoldAttribute = $attributeName;
+ return $this;
+ }
+
+ /**
+ * Return the attribute to use to unfold the result
+ *
+ * @return string
+ */
+ public function getUnfoldAttribute()
+ {
+ return $this->unfoldAttribute;
+ }
+
+ /**
+ * Set this query's native LDAP filter
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setNativeFilter($filter)
+ {
+ $this->nativeFilter = $filter;
+ return $this;
+ }
+
+ /**
+ * Return this query's native LDAP filter
+ *
+ * @return string
+ */
+ public function getNativeFilter()
+ {
+ return $this->nativeFilter;
+ }
+
+ /**
+ * Choose an objectClass and the columns you are interested in
+ *
+ * {@inheritdoc} This creates an objectClass filter.
+ */
+ public function from($target, array $fields = null)
+ {
+ $this->where('objectClass', $target);
+ return parent::from($target, $fields);
+ }
+
+ public function where($condition, $value = null)
+ {
+ $this->addFilter(Filter::expression($condition, '=', $value));
+ return $this;
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ $this->makeCaseInsensitive($filter);
+ return parent::addFilter($filter);
+ }
+
+ public function setFilter(Filter $filter)
+ {
+ $this->makeCaseInsensitive($filter);
+ return parent::setFilter($filter);
+ }
+
+ protected function makeCaseInsensitive(Filter $filter)
+ {
+ if ($filter->isExpression()) {
+ /** @var \Icinga\Data\Filter\FilterExpression $filter */
+ $filter->setCaseSensitive(false);
+ } else {
+ /** @var \Icinga\Data\Filter\FilterChain $filter */
+ foreach ($filter->filters() as $subFilter) {
+ $this->makeCaseInsensitive($subFilter);
+ }
+ }
+ }
+
+ public function compare($a, $b, $orderIndex = 0)
+ {
+ if (array_key_exists($orderIndex, $this->order)) {
+ $column = $this->order[$orderIndex][0];
+ $direction = $this->order[$orderIndex][1];
+
+ $flippedColumns = $this->flippedColumns ?: array_flip($this->columns);
+ if (array_key_exists($column, $flippedColumns) && is_string($flippedColumns[$column])) {
+ $column = $flippedColumns[$column];
+ }
+
+ if (is_array($a->$column)) {
+ // rfc2891 states: If a sort key is a multi-valued attribute, and an entry happens to
+ // have multiple values for that attribute and no other controls are
+ // present that affect the sorting order, then the server SHOULD use the
+ // least value (according to the ORDERING rule for that attribute).
+ $a = clone $a;
+ $a->$column = array_reduce($a->$column, function ($carry, $item) use ($direction) {
+ $result = $carry === null ? 0 : strcmp($item, $carry);
+ return ($direction === self::SORT_ASC ? $result : $result * -1) < 1 ? $item : $carry;
+ });
+ }
+
+ if (is_array($b->$column)) {
+ $b = clone $b;
+ $b->$column = array_reduce($b->$column, function ($carry, $item) use ($direction) {
+ $result = $carry === null ? 0 : strcmp($item, $carry);
+ return ($direction === self::SORT_ASC ? $result : $result * -1) < 1 ? $item : $carry;
+ });
+ }
+ }
+
+ return parent::compare($a, $b, $orderIndex);
+ }
+
+ /**
+ * Fetch result as tree
+ *
+ * @return Root
+ *
+ * @todo This is untested waste, not being used anywhere and ignores the query's order and base dn.
+ * Evaluate whether it's reasonable to properly implement and test it.
+ */
+ public function fetchTree()
+ {
+ $result = $this->fetchAll();
+ $sorted = array();
+ $quotedDn = preg_quote($this->ds->getDn(), '/');
+ foreach ($result as $key => & $item) {
+ $new_key = LdapUtils::implodeDN(
+ array_reverse(
+ LdapUtils::explodeDN(
+ preg_replace('/,' . $quotedDn . '$/', '', $key)
+ )
+ )
+ );
+ $sorted[$new_key] = $key;
+ }
+ unset($groups);
+ ksort($sorted);
+
+ $tree = Root::forConnection($this->ds);
+ $root_dn = $tree->getDN();
+ foreach ($sorted as $sort_key => & $key) {
+ if ($key === $root_dn) {
+ continue;
+ }
+ $tree->createChildByDN($key, $result[$key]);
+ }
+ return $tree;
+ }
+
+ /**
+ * Fetch the distinguished name of the first result
+ *
+ * @return string|false The distinguished name or false in case it's not possible to fetch a result
+ *
+ * @throws LdapException In case the query returns multiple results
+ * (i.e. it's not possible to fetch a unique DN)
+ */
+ public function fetchDn()
+ {
+ return $this->ds->fetchDn($this);
+ }
+
+ /**
+ * Render and return this query's filter
+ *
+ * @return string
+ */
+ public function renderFilter()
+ {
+ $filter = $this->ds->renderFilter($this->filter);
+ if ($this->nativeFilter) {
+ $filter = '(&(' . $this->nativeFilter . ')' . $filter . ')';
+ }
+
+ return $filter;
+ }
+
+ /**
+ * Return the LDAP filter to be applied on this query
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->renderFilter();
+ }
+
+ /**
+ * Get LDAP search scope
+ *
+ * @return string
+ */
+ public function getScope()
+ {
+ return $this->scope;
+ }
+
+ /**
+ * Set LDAP search scope
+ *
+ * Valid: sub one base (Default: sub)
+ *
+ * @param string $scope
+ *
+ * @return LdapQuery
+ *
+ * @throws LogicException If scope value is invalid
+ */
+ public function setScope($scope)
+ {
+ if (! in_array($scope, static::$scopes)) {
+ throw new LogicException(
+ 'Can\'t set scope %d, it is is invalid. Use one of %s or LdapQuery\'s constants.',
+ $scope,
+ implode(', ', static::$scopes)
+ );
+ }
+ $this->scope = $scope;
+ return $this;
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/LdapUtils.php b/library/Icinga/Protocol/Ldap/LdapUtils.php
new file mode 100644
index 0000000..9c9ae10
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/LdapUtils.php
@@ -0,0 +1,148 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+/**
+ * This class provides useful LDAP-related functions
+ *
+ * @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.com>
+ * @author Icinga-Web Team <info@icinga.com>
+ * @package Icinga\Protocol\Ldap
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ */
+class LdapUtils
+{
+ /**
+ * Extends PHPs ldap_explode_dn() function
+ *
+ * UTF-8 chars like German umlauts would otherwise be escaped and shown
+ * as backslash-prefixed hexcode-sequenzes.
+ *
+ * @param string $dn DN
+ * @param boolean $with_type Returns 'type=value' when true and 'value' when false
+ *
+ * @return array
+ */
+ public static function explodeDN($dn, $with_type = true)
+ {
+ $res = ldap_explode_dn($dn, $with_type ? 0 : 1);
+
+ foreach ($res as $k => $v) {
+ $res[$k] = preg_replace_callback(
+ '/\\\([0-9a-f]{2})/i',
+ function ($m) {
+ return chr(hexdec($m[1]));
+ },
+ $v
+ );
+ }
+ unset($res['count']);
+ return $res;
+ }
+
+ /**
+ * Implode unquoted RDNs to a DN
+ *
+ * TODO: throw away, this is not how it shall be done
+ *
+ * @param array $parts DN-component
+ *
+ * @return string
+ */
+ public static function implodeDN($parts)
+ {
+ $str = '';
+ foreach ($parts as $part) {
+ if ($str !== '') {
+ $str .= ',';
+ }
+ list($key, $val) = preg_split('~=~', $part, 2);
+ $str .= $key . '=' . self::quoteForDN($val);
+ }
+ return $str;
+ }
+
+ /**
+ * Test if supplied value looks like a DN
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ */
+ public static function isDn($value)
+ {
+ if (is_string($value)) {
+ return ldap_dn2ufn($value) !== false;
+ }
+ return false;
+ }
+
+ /**
+ * Quote a string that should be used in a DN
+ *
+ * Special characters will be escaped
+ *
+ * @param string $str DN-component
+ *
+ * @return string
+ */
+ public static function quoteForDN($str)
+ {
+ return self::quoteChars(
+ $str,
+ array(
+ ',',
+ '=',
+ '+',
+ '<',
+ '>',
+ ';',
+ '\\',
+ '"',
+ '#'
+ )
+ );
+ }
+
+ /**
+ * Quote a string that should be used in an LDAP search
+ *
+ * Special characters will be escaped
+ *
+ * @param string String to be escaped
+ * @param bool $allow_wildcard
+ * @return string
+ */
+ public static function quoteForSearch($str, $allow_wildcard = false)
+ {
+ if ($allow_wildcard) {
+ return self::quoteChars($str, array('(', ')', '\\', chr(0)));
+ }
+ return self::quoteChars($str, array('*', '(', ')', '\\', chr(0)));
+ }
+
+ /**
+ * Escape given characters in the given string
+ *
+ * Special characters will be escaped
+ *
+ * @param $str
+ * @param $chars
+ * @internal param String $string to be escaped
+ * @return string
+ */
+ protected static function quoteChars($str, $chars)
+ {
+ $quotedChars = array();
+ foreach ($chars as $k => $v) {
+ // Temporarily prefixing with illegal '('
+ $quotedChars[$k] = '(' . str_pad(dechex(ord($v)), 2, '0');
+ }
+ $str = str_replace($chars, $quotedChars, $str);
+ // Replacing temporary '(' with '\\'. This is a workaround, as
+ // str_replace behaves pretty strange with leading a backslash:
+ $str = preg_replace('~\(~', '\\', $str);
+ return $str;
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/Node.php b/library/Icinga/Protocol/Ldap/Node.php
new file mode 100644
index 0000000..176f962
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/Node.php
@@ -0,0 +1,69 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+/**
+ * This class represents an LDAP node object
+ *
+ * @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.com>
+ * @author Icinga-Web Team <info@icinga.com>
+ * @package Icinga\Protocol\Ldap
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ */
+class Node extends Root
+{
+ /**
+ * @var LdapConnection
+ */
+ protected $connection;
+
+ /**
+ * @var
+ */
+ protected $rdn;
+
+ /**
+ * @var Root
+ */
+ protected $parent;
+
+ /**
+ * @param Root $parent
+ */
+ protected function __construct(Root $parent)
+ {
+ $this->connection = $parent->getConnection();
+ $this->parent = $parent;
+ }
+
+ /**
+ * @param $parent
+ * @param $rdn
+ * @param array $props
+ * @return Node
+ */
+ public static function createWithRDN($parent, $rdn, $props = array())
+ {
+ $node = new Node($parent);
+ $node->rdn = $rdn;
+ $node->props = $props;
+ return $node;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getRDN()
+ {
+ return $this->rdn;
+ }
+
+ /**
+ * @return mixed|string
+ */
+ public function getDN()
+ {
+ return $this->getRDN() . ',' . $this->parent->getDN();
+ }
+}
diff --git a/library/Icinga/Protocol/Ldap/Root.php b/library/Icinga/Protocol/Ldap/Root.php
new file mode 100644
index 0000000..5d7a63d
--- /dev/null
+++ b/library/Icinga/Protocol/Ldap/Root.php
@@ -0,0 +1,241 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Ldap;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * This class is a special node object, representing your connections root node
+ *
+ * @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.com>
+ * @author Icinga-Web Team <info@icinga.com>
+ * @package Icinga\Protocol\Ldap
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @package Icinga\Protocol\Ldap
+ */
+class Root
+{
+ /**
+ * @var string
+ */
+ protected $rdn;
+
+ /**
+ * @var LdapConnection
+ */
+ protected $connection;
+
+ /**
+ * @var array
+ */
+ protected $children = array();
+
+ /**
+ * @var array
+ */
+ protected $props = array();
+
+ /**
+ * @param LdapConnection $connection
+ */
+ protected function __construct(LdapConnection $connection)
+ {
+ $this->connection = $connection;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasParent()
+ {
+ return false;
+ }
+
+ /**
+ * @param LdapConnection $connection
+ * @return Root
+ */
+ public static function forConnection(LdapConnection $connection)
+ {
+ $root = new Root($connection);
+ return $root;
+ }
+
+ /**
+ * @param $dn
+ * @param array $props
+ * @return Node
+ */
+ public function createChildByDN($dn, $props = array())
+ {
+ $dn = $this->stripMyDN($dn);
+ $parts = array_reverse(LdapUtils::explodeDN($dn));
+ $parent = $this;
+ while ($rdn = array_shift($parts)) {
+ if ($parent->hasChildRDN($rdn)) {
+ $child = $parent->getChildByRDN($rdn);
+ } else {
+ $child = Node::createWithRDN($parent, $rdn, (array)$props);
+ $parent->addChild($child);
+ }
+ $parent = $child;
+ }
+ return $child;
+ }
+
+ /**
+ * @param $rdn
+ * @return bool
+ */
+ public function hasChildRDN($rdn)
+ {
+ return array_key_exists(strtolower($rdn), $this->children);
+ }
+
+ /**
+ * @param $rdn
+ * @return mixed
+ * @throws IcingaException
+ */
+ public function getChildByRDN($rdn)
+ {
+ if (!$this->hasChildRDN($rdn)) {
+ throw new IcingaException(
+ 'The child RDN "%s" is not available',
+ $rdn
+ );
+ }
+ return $this->children[strtolower($rdn)];
+ }
+
+ /**
+ * @return array
+ */
+ public function children()
+ {
+ return $this->children;
+ }
+
+ public function countChildren()
+ {
+ return count($this->children);
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return !empty($this->children);
+ }
+
+ /**
+ * @param Node $child
+ * @return $this
+ */
+ public function addChild(Node $child)
+ {
+ $this->children[strtolower($child->getRDN())] = $child;
+ return $this;
+ }
+
+ /**
+ * @param $dn
+ * @return string
+ */
+ protected function stripMyDN($dn)
+ {
+ $this->assertSubDN($dn);
+ return substr($dn, 0, strlen($dn) - strlen($this->getDN()) - 1);
+ }
+
+ /**
+ * @param $dn
+ * @return $this
+ * @throws IcingaException
+ */
+ protected function assertSubDN($dn)
+ {
+ $mydn = $this->getDN();
+ $end = substr($dn, -1 * strlen($mydn));
+ if (strtolower($end) !== strtolower($mydn)) {
+ throw new IcingaException(
+ '"%s" is not a child of "%s"',
+ $dn,
+ $mydn
+ );
+ }
+ if (strlen($dn) === strlen($mydn)) {
+ throw new IcingaException(
+ '"%s" is not a child of "%s", they are equal',
+ $dn,
+ $mydn
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * @param LdapConnection $connection
+ * @return $this
+ */
+ public function setConnection(LdapConnection $connection)
+ {
+ $this->connection = $connection;
+ return $this;
+ }
+
+ /**
+ * @return LdapConnection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasBeenChanged()
+ {
+ return false;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getRDN()
+ {
+ return $this->getDN();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getDN()
+ {
+ return $this->connection->getDn();
+ }
+
+ /**
+ * @param $key
+ * @return null
+ */
+ public function __get($key)
+ {
+ if (!array_key_exists($key, $this->props)) {
+ return null;
+ }
+ return $this->props[$key];
+ }
+
+ /**
+ * @param $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return array_key_exists($key, $this->props);
+ }
+}
diff --git a/library/Icinga/Protocol/Nrpe/Connection.php b/library/Icinga/Protocol/Nrpe/Connection.php
new file mode 100644
index 0000000..491a965
--- /dev/null
+++ b/library/Icinga/Protocol/Nrpe/Connection.php
@@ -0,0 +1,111 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Nrpe;
+
+use Icinga\Exception\IcingaException;
+
+class Connection
+{
+ protected $host;
+ protected $port;
+ protected $connection;
+ protected $use_ssl = false;
+ protected $lastReturnCode = null;
+
+ public function __construct($host, $port = 5666)
+ {
+ $this->host = $host;
+ $this->port = $port;
+ }
+
+ public function useSsl($use_ssl = true)
+ {
+ $this->use_ssl = $use_ssl;
+ return $this;
+ }
+
+ public function sendCommand($command, $args = null)
+ {
+ if (! empty($args)) {
+ $command .= '!' . implode('!', $args);
+ }
+
+ $packet = Packet::createQuery($command);
+ return $this->send($packet);
+ }
+
+ public function getLastReturnCode()
+ {
+ return $this->lastReturnCode;
+ }
+
+ public function send(Packet $packet)
+ {
+ $conn = $this->connection();
+ $bytes = $packet->getBinary();
+ fputs($conn, $bytes, strlen($bytes));
+ // TODO: Check result checksum!
+ $result = fread($conn, 8192);
+ if ($result === false) {
+ throw new IcingaException('CHECK_NRPE: Error receiving data from daemon.');
+ } elseif (strlen($result) === 0) {
+ throw new IcingaException(
+ 'CHECK_NRPE: Received 0 bytes from daemon. Check the remote server logs for error messages'
+ );
+ }
+ // TODO: CHECK_NRPE: Receive underflow - only %d bytes received (%d expected)
+ $code = unpack('n', substr($result, 8, 2));
+ $this->lastReturnCode = $code[1];
+ $this->disconnect();
+ return rtrim(substr($result, 10, -2));
+ }
+
+ protected function connect()
+ {
+ $ctx = stream_context_create();
+ if ($this->use_ssl) {
+ // TODO: fail if not ok:
+ $res = stream_context_set_option($ctx, 'ssl', 'ciphers', 'ADH');
+ $uri = sprintf('ssl://%s:%d', $this->host, $this->port);
+ } else {
+ $uri = sprintf('tcp://%s:%d', $this->host, $this->port);
+ }
+ $this->connection = @stream_socket_client(
+ $uri,
+ $errno,
+ $errstr,
+ 10,
+ STREAM_CLIENT_CONNECT,
+ $ctx
+ );
+ if (! $this->connection) {
+ throw new IcingaException(
+ 'NRPE Connection failed: %s',
+ $errstr
+ );
+ }
+ }
+
+ protected function connection()
+ {
+ if ($this->connection === null) {
+ $this->connect();
+ }
+ return $this->connection;
+ }
+
+ protected function disconnect()
+ {
+ if (is_resource($this->connection)) {
+ fclose($this->connection);
+ $this->connection = null;
+ }
+ return $this;
+ }
+
+ public function __destruct()
+ {
+ $this->disconnect();
+ }
+}
diff --git a/library/Icinga/Protocol/Nrpe/Packet.php b/library/Icinga/Protocol/Nrpe/Packet.php
new file mode 100644
index 0000000..54c8526
--- /dev/null
+++ b/library/Icinga/Protocol/Nrpe/Packet.php
@@ -0,0 +1,69 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Protocol\Nrpe;
+
+class Packet
+{
+ const QUERY = 0x01;
+ const RESPONSE = 0x02;
+
+ protected $version = 0x02;
+ protected $type;
+ protected $body;
+ protected static $randomBytes;
+
+ public function __construct($type, $body)
+ {
+ $this->type = $type;
+ $this->body = $body;
+ $this->regenerateRandomBytes();
+ }
+
+ // TODO: renew "from time to time" to allow long-running daemons
+ protected function regenerateRandomBytes()
+ {
+ self::$randomBytes = '';
+ for ($i = 0; $i < 4096; $i++) {
+ self::$randomBytes .= pack('N', mt_rand());
+ }
+ }
+
+ public static function createQuery($body)
+ {
+ $packet = new Packet(self::QUERY, $body);
+ return $packet;
+ }
+
+ protected function getFillString($length)
+ {
+ $max = strlen(self::$randomBytes) - $length;
+ return substr(self::$randomBytes, rand(0, $max), $length);
+ }
+
+ // TODO: WTF is SR? And 2324?
+ public function getBinary()
+ {
+ $version = pack('n', $this->version);
+ $type = pack('n', $this->type);
+ $dummycrc = "\x00\x00\x00\x00";
+ $result = "\x00\x00";
+ $result = pack('n', 2324);
+ $body = $this->body
+ . "\x00"
+ . $this->getFillString(1023 - strlen($this->body))
+ . 'SR';
+
+ $crc = pack(
+ 'N',
+ crc32($version . $type . $dummycrc . $result . $body)
+ );
+ $bytes = $version . $type . $crc . $result . $body;
+ return $bytes;
+ }
+
+ public function __toString()
+ {
+ return $this->body;
+ }
+}
diff --git a/library/Icinga/Repository/DbRepository.php b/library/Icinga/Repository/DbRepository.php
new file mode 100644
index 0000000..039d221
--- /dev/null
+++ b/library/Icinga/Repository/DbRepository.php
@@ -0,0 +1,1077 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Repository;
+
+use Zend_Db_Expr;
+use Icinga\Data\Db\DbConnection;
+use Icinga\Data\Extensible;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Reducible;
+use Icinga\Data\Updatable;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\StatementException;
+use Icinga\Util\StringHelper;
+
+/**
+ * Abstract base class for concrete database repository implementations
+ *
+ * Additionally provided features:
+ * <ul>
+ * <li>Support for table aliases</li>
+ * <li>Automatic table prefix handling</li>
+ * <li>Insert, update and delete capabilities</li>
+ * <li>Differentiation between statement and query columns</li>
+ * <li>Capability to join additional tables depending on the columns being selected or used in a filter</li>
+ * </ul>
+ *
+ * @method DbConnection getDataSource($table = null)
+ */
+abstract class DbRepository extends Repository implements Extensible, Updatable, Reducible
+{
+ /**
+ * The datasource being used
+ *
+ * @var DbConnection
+ */
+ protected $ds;
+
+ /**
+ * The table aliases being applied
+ *
+ * This must be initialized by repositories which are going to make use of table aliases. Every table for which
+ * aliased columns are provided must be defined in this array using its name as key and the alias being used as
+ * value. Failure to do so will result in invalid queries.
+ *
+ * @var array
+ */
+ protected $tableAliases;
+
+ /**
+ * The join probability rules
+ *
+ * This may be initialized by repositories which make use of the table join capability. It allows to define
+ * probability rules to enhance control how ambiguous column aliases are associated with the correct table.
+ * To define a rule use the name of a base table as key and another array of table names as probable join
+ * targets ordered by priority. (Ascending: Lower means higher priority)
+ * <code>
+ * array(
+ * 'table_name' => array('target1', 'target2', 'target3')
+ * )
+ * </code>
+ *
+ * @todo Support for tree-ish rules
+ *
+ * @var array
+ */
+ protected $joinProbabilities;
+
+ /**
+ * The statement columns being provided
+ *
+ * This may be initialized by repositories which are going to make use of table aliases. It allows to provide
+ * alias-less column names to be used for a statement. The array needs to be in the following format:
+ * <code>
+ * array(
+ * 'table_name' => array(
+ * 'column1',
+ * 'alias1' => 'column2',
+ * 'alias2' => 'column3'
+ * )
+ * )
+ * </code>
+ *
+ * @var array
+ */
+ protected $statementColumns;
+
+ /**
+ * An array to map table names to statement columns/aliases
+ *
+ * @var array
+ */
+ protected $statementAliasTableMap;
+
+ /**
+ * A flattened array to map statement columns to aliases
+ *
+ * @var array
+ */
+ protected $statementAliasColumnMap;
+
+ /**
+ * An array to map table names to statement columns
+ *
+ * @var array
+ */
+ protected $statementColumnTableMap;
+
+ /**
+ * A flattened array to map aliases to statement columns
+ *
+ * @var array
+ */
+ protected $statementColumnAliasMap;
+
+ /**
+ * List of column names or aliases mapped to their table where the COLLATE SQL-instruction has been removed
+ *
+ * This list is being populated in case of a PostgreSQL backend only,
+ * to ensure case-insensitive string comparison in WHERE clauses.
+ *
+ * @var array
+ */
+ protected $caseInsensitiveColumns;
+
+ /**
+ * Create a new DB repository object
+ *
+ * In case $this->queryColumns has already been initialized, this initializes
+ * $this->caseInsensitiveColumns in case of a PostgreSQL connection.
+ *
+ * @param DbConnection $ds The datasource to use
+ */
+ public function __construct(DbConnection $ds)
+ {
+ parent::__construct($ds);
+
+ if ($ds->getDbType() === 'pgsql' && $this->queryColumns !== null) {
+ $this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
+ }
+ }
+
+ /**
+ * Return the query columns being provided
+ *
+ * Initializes $this->caseInsensitiveColumns in case of a PostgreSQL connection.
+ *
+ * @return array
+ */
+ public function getQueryColumns()
+ {
+ if ($this->queryColumns === null) {
+ $this->queryColumns = parent::getQueryColumns();
+ if ($this->ds->getDbType() === 'pgsql') {
+ $this->queryColumns = $this->removeCollateInstruction($this->queryColumns);
+ }
+ }
+
+ return $this->queryColumns;
+ }
+
+ /**
+ * Return the table aliases to be applied
+ *
+ * Calls $this->initializeTableAliases() in case $this->tableAliases is null.
+ *
+ * @return array
+ */
+ public function getTableAliases()
+ {
+ if ($this->tableAliases === null) {
+ $this->tableAliases = $this->initializeTableAliases();
+ }
+
+ return $this->tableAliases;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the table aliases lazily
+ *
+ * @return array
+ */
+ protected function initializeTableAliases()
+ {
+ return array();
+ }
+
+ /**
+ * Return the join probability rules
+ *
+ * Calls $this->initializeJoinProbabilities() in case $this->joinProbabilities is null.
+ *
+ * @return array
+ */
+ public function getJoinProbabilities()
+ {
+ if ($this->joinProbabilities === null) {
+ $this->joinProbabilities = $this->initializeJoinProbabilities();
+ }
+
+ return $this->joinProbabilities;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the join probabilities lazily
+ *
+ * @return array
+ */
+ protected function initializeJoinProbabilities()
+ {
+ return array();
+ }
+
+ /**
+ * Remove each COLLATE SQL-instruction from all given query columns
+ *
+ * @param array $queryColumns
+ *
+ * @return array $queryColumns, the updated version
+ */
+ protected function removeCollateInstruction($queryColumns)
+ {
+ foreach ($queryColumns as $table => & $columns) {
+ foreach ($columns as $alias => & $column) {
+ // Using a regex here because COLLATE may occur anywhere in the string
+ $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
+ if ($count > 0) {
+ $this->caseInsensitiveColumns[$table][is_string($alias) ? $alias : $column] = true;
+ }
+ }
+ }
+
+ return $queryColumns;
+ }
+
+ /**
+ * Initialize table, column and alias maps
+ *
+ * @throws ProgrammingError In case $this->queryColumns does not provide any column information
+ */
+ protected function initializeAliasMaps()
+ {
+ parent::initializeAliasMaps();
+
+ foreach ($this->aliasTableMap as $alias => $table) {
+ if ($table !== null) {
+ if (strpos($alias, '.') !== false) {
+ $prefixedAlias = str_replace('.', '_', $alias);
+ } else {
+ $prefixedAlias = $table . '_' . $alias;
+ }
+
+ if (array_key_exists($prefixedAlias, $this->aliasTableMap)) {
+ if ($this->aliasTableMap[$prefixedAlias] !== null) {
+ $existingTable = $this->aliasTableMap[$prefixedAlias];
+ $existingColumn = $this->aliasColumnMap[$prefixedAlias];
+ $this->aliasTableMap[$existingTable . '.' . $prefixedAlias] = $existingTable;
+ $this->aliasColumnMap[$existingTable . '.' . $prefixedAlias] = $existingColumn;
+ $this->aliasTableMap[$prefixedAlias] = null;
+ $this->aliasColumnMap[$prefixedAlias] = null;
+ }
+
+ $this->aliasTableMap[$table . '.' . $prefixedAlias] = $table;
+ $this->aliasColumnMap[$table . '.' . $prefixedAlias] = $this->aliasColumnMap[$alias];
+ } else {
+ $this->aliasTableMap[$prefixedAlias] = $table;
+ $this->aliasColumnMap[$prefixedAlias] = $this->aliasColumnMap[$alias];
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the given table with the datasource's prefix being prepended
+ *
+ * @param array|string $table
+ *
+ * @return array|string
+ *
+ * @throws IcingaException In case $table is not of a supported type
+ */
+ protected function prependTablePrefix($table)
+ {
+ $prefix = $this->ds->getTablePrefix();
+ if (! $prefix) {
+ return $table;
+ }
+
+ if (is_array($table)) {
+ foreach ($table as & $tableName) {
+ if (strpos($tableName, $prefix) === false) {
+ $tableName = $prefix . $tableName;
+ }
+ }
+ } elseif (is_string($table)) {
+ $table = (strpos($table, $prefix) === false ? $prefix : '') . $table;
+ } else {
+ throw new IcingaException('Table prefix handling for type "%s" is not supported', type($table));
+ }
+
+ return $table;
+ }
+
+ /**
+ * Remove the datasource's prefix from the given table name and return the remaining part
+ *
+ * @param array|string $table
+ *
+ * @return array|string
+ *
+ * @throws IcingaException In case $table is not of a supported type
+ */
+ protected function removeTablePrefix($table)
+ {
+ $prefix = $this->ds->getTablePrefix();
+ if (! $prefix) {
+ return $table;
+ }
+
+ if (is_array($table)) {
+ foreach ($table as & $tableName) {
+ if (strpos($tableName, $prefix) === 0) {
+ $tableName = str_replace($prefix, '', $tableName);
+ }
+ }
+ } elseif (is_string($table)) {
+ if (strpos($table, $prefix) === 0) {
+ $table = str_replace($prefix, '', $table);
+ }
+ } else {
+ throw new IcingaException('Table prefix handling for type "%s" is not supported', type($table));
+ }
+
+ return $table;
+ }
+
+ /**
+ * Return the given table with its alias being applied
+ *
+ * @param array|string $table
+ * @param string $virtualTable
+ *
+ * @return array|string
+ */
+ protected function applyTableAlias($table, $virtualTable = null)
+ {
+ if (! is_array($table)) {
+ $tableAliases = $this->getTableAliases();
+ if ($virtualTable !== null && isset($tableAliases[$virtualTable])) {
+ return array($tableAliases[$virtualTable] => $table);
+ }
+
+ if (isset($tableAliases[($nonPrefixedTable = $this->removeTablePrefix($table))])) {
+ return array($tableAliases[$nonPrefixedTable] => $table);
+ }
+ }
+
+ return $table;
+ }
+
+ /**
+ * Return the given table with its alias being cleared
+ *
+ * @param array|string $table
+ *
+ * @return string
+ *
+ * @throws IcingaException In case $table is not of a supported type
+ */
+ protected function clearTableAlias($table)
+ {
+ if (is_string($table)) {
+ return $table;
+ }
+
+ if (is_array($table)) {
+ return reset($table);
+ }
+
+ throw new IcingaException('Table alias handling for type "%s" is not supported', type($table));
+ }
+
+ /**
+ * Insert a table row with the given data
+ *
+ * Note that the base implementation does not perform any quoting on the $table argument.
+ * Pass an array with a column name (the same as in $bind) and a PDO::PARAM_* constant as value
+ * as third parameter $types to define a different type than string for a particular column.
+ *
+ * @param string $table
+ * @param array $bind
+ * @param array $types
+ *
+ * @return int The number of affected rows
+ */
+ public function insert($table, array $bind, array $types = array())
+ {
+ $realTable = $this->clearTableAlias($this->requireTable($table));
+
+ foreach ($types as $alias => $type) {
+ unset($types[$alias]);
+ $types[$this->requireStatementColumn($table, $alias)] = $type;
+ }
+
+ return $this->ds->insert($realTable, $this->requireStatementColumns($table, $bind), $types);
+ }
+
+ /**
+ * Update table rows with the given data, optionally limited by using a filter
+ *
+ * Note that the base implementation does not perform any quoting on the $table argument.
+ * Pass an array with a column name (the same as in $bind) and a PDO::PARAM_* constant as value
+ * as fourth parameter $types to define a different type than string for a particular column.
+ *
+ * @param string $table
+ * @param array $bind
+ * @param Filter $filter
+ * @param array $types
+ *
+ * @return int The number of affected rows
+ */
+ public function update($table, array $bind, Filter $filter = null, array $types = array())
+ {
+ $realTable = $this->clearTableAlias($this->requireTable($table));
+
+ if ($filter) {
+ $filter = $this->requireFilter($table, $filter);
+ }
+
+ foreach ($types as $alias => $type) {
+ unset($types[$alias]);
+ $types[$this->requireStatementColumn($table, $alias)] = $type;
+ }
+
+ return $this->ds->update($realTable, $this->requireStatementColumns($table, $bind), $filter, $types);
+ }
+
+ /**
+ * Delete table rows, optionally limited by using a filter
+ *
+ * @param string $table
+ * @param Filter $filter
+ *
+ * @return int The number of affected rows
+ */
+ public function delete($table, Filter $filter = null)
+ {
+ $realTable = $this->clearTableAlias($this->requireTable($table));
+
+ if ($filter) {
+ $filter = $this->requireFilter($table, $filter);
+ }
+
+ return $this->ds->delete($realTable, $filter);
+ }
+
+ /**
+ * Return the statement columns being provided
+ *
+ * Calls $this->initializeStatementColumns() in case $this->statementColumns is null.
+ *
+ * @return array
+ */
+ public function getStatementColumns()
+ {
+ if ($this->statementColumns === null) {
+ $this->statementColumns = $this->initializeStatementColumns();
+ }
+
+ return $this->statementColumns;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the statement columns lazily
+ *
+ * @return array
+ */
+ protected function initializeStatementColumns()
+ {
+ return array();
+ }
+
+ /**
+ * Return an array to map table names to statement columns/aliases
+ *
+ * @return array
+ */
+ protected function getStatementAliasTableMap()
+ {
+ if ($this->statementAliasTableMap === null) {
+ $this->initializeStatementMaps();
+ }
+
+ return $this->statementAliasTableMap;
+ }
+
+ /**
+ * Return a flattened array to map statement columns to aliases
+ *
+ * @return array
+ */
+ protected function getStatementAliasColumnMap()
+ {
+ if ($this->statementAliasColumnMap === null) {
+ $this->initializeStatementMaps();
+ }
+
+ return $this->statementAliasColumnMap;
+ }
+
+ /**
+ * Return an array to map table names to statement columns
+ *
+ * @return array
+ */
+ protected function getStatementColumnTableMap()
+ {
+ if ($this->statementColumnTableMap === null) {
+ $this->initializeStatementMaps();
+ }
+
+ return $this->statementColumnTableMap;
+ }
+
+ /**
+ * Return a flattened array to map aliases to statement columns
+ *
+ * @return array
+ */
+ protected function getStatementColumnAliasMap()
+ {
+ if ($this->statementColumnAliasMap === null) {
+ $this->initializeStatementMaps();
+ }
+
+ return $this->statementColumnAliasMap;
+ }
+
+ /**
+ * Initialize $this->statementAliasTableMap and $this->statementAliasColumnMap
+ */
+ protected function initializeStatementMaps()
+ {
+ $this->statementAliasTableMap = array();
+ $this->statementAliasColumnMap = array();
+ $this->statementColumnTableMap = array();
+ $this->statementColumnAliasMap = array();
+ foreach ($this->getStatementColumns() as $table => $columns) {
+ foreach ($columns as $alias => $column) {
+ $key = is_string($alias) ? $alias : $column;
+ if (array_key_exists($key, $this->statementAliasTableMap)) {
+ if ($this->statementAliasTableMap[$key] !== null) {
+ $existingTable = $this->statementAliasTableMap[$key];
+ $existingColumn = $this->statementAliasColumnMap[$key];
+ $this->statementAliasTableMap[$existingTable . '.' . $key] = $existingTable;
+ $this->statementAliasColumnMap[$existingTable . '.' . $key] = $existingColumn;
+ $this->statementAliasTableMap[$key] = null;
+ $this->statementAliasColumnMap[$key] = null;
+ }
+
+ $this->statementAliasTableMap[$table . '.' . $key] = $table;
+ $this->statementAliasColumnMap[$table . '.' . $key] = $column;
+ } else {
+ $this->statementAliasTableMap[$key] = $table;
+ $this->statementAliasColumnMap[$key] = $column;
+ }
+
+ if (array_key_exists($column, $this->statementColumnTableMap)) {
+ if ($this->statementColumnTableMap[$column] !== null) {
+ $existingTable = $this->statementColumnTableMap[$column];
+ $existingAlias = $this->statementColumnAliasMap[$column];
+ $this->statementColumnTableMap[$existingTable . '.' . $column] = $existingTable;
+ $this->statementColumnAliasMap[$existingTable . '.' . $column] = $existingAlias;
+ $this->statementColumnTableMap[$column] = null;
+ $this->statementColumnAliasMap[$column] = null;
+ }
+
+ $this->statementColumnTableMap[$table . '.' . $column] = $table;
+ $this->statementColumnAliasMap[$table . '.' . $column] = $key;
+ } else {
+ $this->statementColumnTableMap[$column] = $table;
+ $this->statementColumnAliasMap[$column] = $key;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return whether this repository is capable of converting values for the given table and optional column
+ *
+ * This does not check whether any conversion for the given table is available if $column is not given, as it
+ * may be possible that columns from another table where joined in which would otherwise not being converted.
+ *
+ * @param string $table
+ * @param string $column
+ *
+ * @return bool
+ */
+ public function providesValueConversion($table, $column = null)
+ {
+ if ($column !== null) {
+ if ($column instanceof Zend_Db_Expr) {
+ return false;
+ }
+
+ if ($this->validateQueryColumnAssociation($table, $column)) {
+ return parent::providesValueConversion($table, $column);
+ }
+
+ if (($tableName = $this->findTableName($column, $table))) {
+ return parent::providesValueConversion($tableName, $column);
+ }
+
+ return false;
+ }
+
+ $conversionRules = $this->getConversionRules();
+ return !empty($conversionRules);
+ }
+
+ /**
+ * Return the name of the conversion method for the given alias or column name and context
+ *
+ * If a query column or a filter column, which is part of a query filter, needs to be converted,
+ * you'll need to pass $query, otherwise the column is considered a statement column.
+ *
+ * @param string $table The datasource's table
+ * @param string $name The alias or column name for which to return a conversion method
+ * @param string $context The context of the conversion: persist or retrieve
+ * @param RepositoryQuery $query If given the column is considered a query column,
+ * statement column otherwise
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case a conversion rule is found but not any conversion method
+ */
+ protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
+ {
+ if ($name instanceof Zend_Db_Expr) {
+ return;
+ }
+
+ if (! ($query !== null && $this->validateQueryColumnAssociation($table, $name))
+ && !($query === null && $this->validateStatementColumnAssociation($table, $name))
+ ) {
+ $table = $this->findTableName($name, $table);
+ if (! $table) {
+ if ($query !== null) {
+ // It may be an aliased Zend_Db_Expr
+ $desiredColumns = $query->getColumns();
+ if (isset($desiredColumns[$name]) && $desiredColumns[$name] instanceof Zend_Db_Expr) {
+ return;
+ }
+ }
+
+ throw new ProgrammingError('Column name validation seems to have failed. Did you require the column?');
+ }
+ }
+
+ return parent::getConverter($table, $name, $context, $query);
+ }
+
+ /**
+ * Validate that the requested table exists
+ *
+ * This will prepend the datasource's table prefix and will apply the table's alias, if any.
+ *
+ * @param string $table The table to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ * (unused by the base implementation)
+ *
+ * @return array|string
+ *
+ * @throws ProgrammingError In case the given table does not exist
+ */
+ public function requireTable($table, RepositoryQuery $query = null)
+ {
+ $virtualTable = null;
+ $statementColumns = $this->getStatementColumns();
+ if (! isset($statementColumns[$table])) {
+ $newTable = parent::requireTable($table);
+ if ($newTable !== $table) {
+ $virtualTable = $table;
+ }
+
+ $table = $newTable;
+ } else {
+ $virtualTables = $this->getVirtualTables();
+ if (isset($virtualTables[$table])) {
+ $virtualTable = $table;
+ $table = $virtualTables[$table];
+ }
+ }
+
+ return $this->prependTablePrefix($this->applyTableAlias($table, $virtualTable));
+ }
+
+ /**
+ * Return the alias for the given table or null if none has been defined
+ *
+ * @param string $table
+ *
+ * @return string|null
+ */
+ public function resolveTableAlias($table)
+ {
+ $tableAliases = $this->getTableAliases();
+ if (isset($tableAliases[$table])) {
+ return $tableAliases[$table];
+ }
+ }
+
+ /**
+ * Return the alias for the given query column name or null in case the query column name does not exist
+ *
+ * @param string $table
+ * @param string $column
+ *
+ * @return string|null
+ */
+ public function reassembleQueryColumnAlias($table, $column)
+ {
+ $alias = parent::reassembleQueryColumnAlias($table, $column);
+ if ($alias === null
+ && !$this->validateQueryColumnAssociation($table, $column)
+ && ($tableName = $this->findTableName($column, $table))
+ ) {
+ return parent::reassembleQueryColumnAlias($tableName, $column);
+ }
+
+ return $alias;
+ }
+
+ /**
+ * Validate that the given column is a valid query target and return it or the actual name if it's an alias
+ *
+ * Attempts to join the given column from a different table if its association to the given table cannot be
+ * verified.
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context,
+ * if not given no join will be attempted
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid query column
+ * @throws ProgrammingError In case the given column is not found in $table and cannot be joined in
+ */
+ public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
+ {
+ if ($name instanceof Zend_Db_Expr) {
+ return $name;
+ }
+
+ if ($query === null || $this->validateQueryColumnAssociation($table, $name)) {
+ return parent::requireQueryColumn($table, $name, $query);
+ }
+
+ $column = $this->joinColumn($name, $table, $query);
+ if ($column === null) {
+ if ($query !== null) {
+ // It may be an aliased Zend_Db_Expr
+ $desiredColumns = $query->getColumns();
+ if (isset($desiredColumns[$name]) && $desiredColumns[$name] instanceof Zend_Db_Expr) {
+ $column = $desiredColumns[$name];
+ }
+ }
+
+ if ($column === null) {
+ throw new ProgrammingError(
+ 'Unable to find a valid table for column "%s" to join into "%s"',
+ $name,
+ $table
+ );
+ }
+ }
+
+ return $column;
+ }
+
+ /**
+ * Validate that the given column is a valid filter target and return it or the actual name if it's an alias
+ *
+ * Attempts to join the given column from a different table if its association to the given table cannot be
+ * verified. In case of a PostgreSQL connection and if a COLLATE SQL-instruction is part of the resolved column,
+ * this applies LOWER() on the column and, if given, strtolower() on the filter's expression.
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context,
+ * if not given the column is considered being used for a statement filter
+ * @param FilterExpression $filter An optional filter to pass as context
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid filter column
+ * @throws ProgrammingError In case the given column is not found in $table and cannot be joined in
+ */
+ public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null)
+ {
+ if ($name instanceof Zend_Db_Expr) {
+ return $name;
+ }
+
+ $joined = false;
+ if ($query === null) {
+ $column = $this->requireStatementColumn($table, $name);
+ } elseif ($this->validateQueryColumnAssociation($table, $name)) {
+ $column = parent::requireFilterColumn($table, $name, $query, $filter);
+ } else {
+ $column = $this->joinColumn($name, $table, $query);
+ if ($column === null) {
+ if ($query !== null) {
+ // It may be an aliased Zend_Db_Expr
+ $desiredColumns = $query->getColumns();
+ if (isset($desiredColumns[$name]) && $desiredColumns[$name] instanceof Zend_Db_Expr) {
+ $column = $desiredColumns[$name];
+ }
+ }
+
+ if ($column === null) {
+ throw new ProgrammingError(
+ 'Unable to find a valid table for column "%s" to join into "%s"',
+ $name,
+ $table
+ );
+ }
+ } else {
+ $joined = true;
+ }
+ }
+
+ if (! empty($this->caseInsensitiveColumns)) {
+ if ($joined) {
+ $table = $this->findTableName($name, $table);
+ }
+
+ if ($column === $name) {
+ if ($query === null) {
+ $name = $this->reassembleStatementColumnAlias($table, $name);
+ } else {
+ $name = $this->reassembleQueryColumnAlias($table, $name);
+ }
+ }
+
+ if (isset($this->caseInsensitiveColumns[$table][$name])) {
+ $column = 'LOWER(' . $column . ')';
+ if ($filter !== null) {
+ $expression = $filter->getExpression();
+ if (is_array($expression)) {
+ $filter->setExpression(array_map('strtolower', $expression));
+ } else {
+ $filter->setExpression(strtolower($expression));
+ }
+ }
+ }
+ }
+
+ return $column;
+ }
+
+ /**
+ * Return the statement column name for the given alias or null in case the alias does not exist
+ *
+ * @param string $table
+ * @param string $alias
+ *
+ * @return string|null
+ */
+ public function resolveStatementColumnAlias($table, $alias)
+ {
+ $statementAliasColumnMap = $this->getStatementAliasColumnMap();
+ if (isset($statementAliasColumnMap[$alias])) {
+ return $statementAliasColumnMap[$alias];
+ }
+
+ $prefixedAlias = $table . '.' . $alias;
+ if (isset($statementAliasColumnMap[$prefixedAlias])) {
+ return $statementAliasColumnMap[$prefixedAlias];
+ }
+ }
+
+ /**
+ * Return the alias for the given statement column name or null in case the statement column does not exist
+ *
+ * @param string $table
+ * @param string $column
+ *
+ * @return string|null
+ */
+ public function reassembleStatementColumnAlias($table, $column)
+ {
+ $statementColumnAliasMap = $this->getStatementColumnAliasMap();
+ if (isset($statementColumnAliasMap[$column])) {
+ return $statementColumnAliasMap[$column];
+ }
+
+ $prefixedColumn = $table . '.' . $column;
+ if (isset($statementColumnAliasMap[$prefixedColumn])) {
+ return $statementColumnAliasMap[$prefixedColumn];
+ }
+ }
+
+ /**
+ * Return whether the given alias or statement column name is available in the given table
+ *
+ * @param string $table
+ * @param string $alias
+ *
+ * @return bool
+ */
+ public function validateStatementColumnAssociation($table, $alias)
+ {
+ $statementAliasTableMap = $this->getStatementAliasTableMap();
+ if (isset($statementAliasTableMap[$alias])) {
+ return $statementAliasTableMap[$alias] === $table;
+ }
+
+ $prefixedAlias = $table . '.' . $alias;
+ if (isset($statementAliasTableMap[$prefixedAlias])) {
+ return true;
+ }
+
+ $statementColumnTableMap = $this->getStatementColumnTableMap();
+ if (isset($statementColumnTableMap[$alias])) {
+ return $statementColumnTableMap[$alias] === $table;
+ }
+
+ return isset($statementColumnTableMap[$prefixedAlias]);
+ }
+
+ /**
+ * Return whether the given column name or alias of the given table is a valid statement column
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The column name or alias to check
+ *
+ * @return bool
+ */
+ public function hasStatementColumn($table, $name)
+ {
+ if (($this->resolveStatementColumnAlias($table, $name) === null
+ && $this->reassembleStatementColumnAlias($table, $name) === null)
+ || !$this->validateStatementColumnAssociation($table, $name)
+ ) {
+ return parent::hasStatementColumn($table, $name);
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate that the given column is a valid statement column and return it or the actual name if it's an alias
+ *
+ * @param string $table The table for which to require the column
+ * @param string $name The name or alias of the column to validate
+ *
+ * @return string The given column's name
+ *
+ * @throws StatementException In case the given column is not a statement column
+ */
+ public function requireStatementColumn($table, $name)
+ {
+ if (($column = $this->resolveStatementColumnAlias($table, $name)) !== null) {
+ $alias = $name;
+ } elseif (($alias = $this->reassembleStatementColumnAlias($table, $name)) !== null) {
+ $column = $name;
+ } else {
+ return parent::requireStatementColumn($table, $name);
+ }
+
+ if (! $this->validateStatementColumnAssociation($table, $alias)) {
+ throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Join alias or column $name into $table using $query
+ *
+ * Attempts to find a valid table for the given alias or column name and a method labelled join<TableName>
+ * to process the actual join logic. If neither of those is found, null is returned.
+ * The method is called with the same parameters but in reversed order.
+ *
+ * @param string $name The alias or column name to join into $target
+ * @param string $target The table to join $name into
+ * @param RepositoryQUery $query The query to apply the JOIN-clause on
+ *
+ * @return string|null The resolved alias or $name, null if no join logic is found
+ */
+ public function joinColumn($name, $target, RepositoryQuery $query)
+ {
+ if (! ($tableName = $this->findTableName($name, $target))) {
+ return;
+ }
+
+ if (($column = $this->resolveQueryColumnAlias($tableName, $name)) === null) {
+ $column = $name;
+ }
+
+ if (($joinIdentifier = $this->resolveTableAlias($tableName)) === null) {
+ $joinIdentifier = $this->prependTablePrefix($tableName);
+ }
+ if ($query->getQuery()->hasJoinedTable($joinIdentifier)) {
+ return $column;
+ }
+
+ $joinMethod = 'join' . StringHelper::cname($tableName);
+ if (! method_exists($this, $joinMethod)) {
+ throw new ProgrammingError(
+ 'Unable to join table "%s" into "%s". Method "%s" not found',
+ $tableName,
+ $target,
+ $joinMethod
+ );
+ }
+
+ $this->$joinMethod($query, $target, $name);
+ return $column;
+ }
+
+ /**
+ * Return the table name for the given alias or column name
+ *
+ * @param string $column The alias or column name
+ * @param string $origin The base table of a SELECT query
+ *
+ * @return string|null null in case no table is found
+ */
+ protected function findTableName($column, $origin)
+ {
+ // First, try to produce an exact match since it's faster and cheaper
+ $aliasTableMap = $this->getAliasTableMap();
+ if (isset($aliasTableMap[$column])) {
+ $table = $aliasTableMap[$column];
+ } else {
+ $columnTableMap = $this->getColumnTableMap();
+ if (isset($columnTableMap[$column])) {
+ $table = $columnTableMap[$column];
+ }
+ }
+
+ // But only return it if it's a probable join...
+ $joinProbabilities = $this->getJoinProbabilities();
+ if (isset($joinProbabilities[$origin])) {
+ $probableJoins = $joinProbabilities[$origin];
+ }
+
+ // ...if probability can be determined
+ if (isset($table) && (empty($probableJoins) || in_array($table, $probableJoins, true))) {
+ return $table;
+ }
+
+ // Without a proper exact match, there is only one fast and cheap way to find a suitable table..
+ if (! empty($probableJoins)) {
+ foreach ($probableJoins as $table) {
+ if (isset($aliasTableMap[$table . '.' . $column])) {
+ return $table;
+ }
+ }
+ }
+
+ // Last chance to find a table. Though, this usually ends up with a QueryException..
+ foreach ($aliasTableMap as $prefixedAlias => $table) {
+ if (strpos($prefixedAlias, '.') !== false) {
+ list($_, $alias) = explode('.', $prefixedAlias, 2);
+ if ($alias === $column) {
+ return $table;
+ }
+ }
+ }
+ }
+}
diff --git a/library/Icinga/Repository/IniRepository.php b/library/Icinga/Repository/IniRepository.php
new file mode 100644
index 0000000..7385b3e
--- /dev/null
+++ b/library/Icinga/Repository/IniRepository.php
@@ -0,0 +1,420 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Repository;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Extensible;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Updatable;
+use Icinga\Data\Reducible;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\StatementException;
+
+/**
+ * Abstract base class for concrete INI repository implementations
+ *
+ * Additionally provided features:
+ * <ul>
+ * <li>Insert, update and delete capabilities</li>
+ * <li>Triggers for inserts, updates and deletions</li>
+ * <li>Lazy initialization of table specific configs</li>
+ * </ul>
+ */
+abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible
+{
+ /**
+ * The configuration files used as table specific datasources
+ *
+ * This must be initialized by concrete repository implementations, in the following format
+ * <code>
+ * array(
+ * 'table_name' => array(
+ * 'name' => 'name_of_the_ini_file_without_extension',
+ * 'keyColumn' => 'the_name_of_the_column_to_use_as_key_column',
+ * ['module' => 'the_name_of_the_module_if_any']
+ * )
+ * )
+ * </code>
+ *
+ * @var array
+ */
+ protected $configs;
+
+ /**
+ * The tables for which triggers are available when inserting, updating or deleting rows
+ *
+ * This may be initialized by concrete repository implementations and describes for which table names triggers
+ * are available. The repository attempts to find a method depending on the type of event and table for which
+ * to run the trigger. The name of such a method is expected to be declared using lowerCamelCase.
+ * (e.g. group_membership will be translated to onUpdateGroupMembership and groupmembership will be translated
+ * to onUpdateGroupmembership) The available events are onInsert, onUpdate and onDelete.
+ *
+ * @var array
+ */
+ protected $triggers;
+
+ /**
+ * Create a new INI repository object
+ *
+ * @param Config|null $ds The data source to use
+ *
+ * @throws ProgrammingError In case the given data source does not provide a valid key column
+ */
+ public function __construct(Config $ds = null)
+ {
+ parent::__construct($ds); // First! Due to init().
+
+ if ($ds !== null && !$ds->getConfigObject()->getKeyColumn()) {
+ throw new ProgrammingError('INI repositories require their data source to provide a valid key column');
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return Config
+ */
+ public function getDataSource($table = null)
+ {
+ if ($this->ds !== null) {
+ return parent::getDataSource($table);
+ }
+
+ $table = $table ?: $this->getBaseTable();
+ $configs = $this->getConfigs();
+ if (! isset($configs[$table])) {
+ throw new ProgrammingError('Config for table "%s" missing', $table);
+ } elseif (! $configs[$table] instanceof Config) {
+ $configs[$table] = $this->createConfig($configs[$table], $table);
+ }
+
+ if (! $configs[$table]->getConfigObject()->getKeyColumn()) {
+ throw new ProgrammingError(
+ 'INI repositories require their data source to provide a valid key column'
+ );
+ }
+
+ return $configs[$table];
+ }
+
+ /**
+ * Return the configuration files used as table specific datasources
+ *
+ * Calls $this->initializeConfigs() in case $this->configs is null.
+ *
+ * @return array
+ */
+ public function getConfigs()
+ {
+ if ($this->configs === null) {
+ $this->configs = $this->initializeConfigs();
+ }
+
+ return $this->configs;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the configs lazily
+ *
+ * @return array
+ */
+ protected function initializeConfigs()
+ {
+ return array();
+ }
+
+ /**
+ * Return the tables for which triggers are available when inserting, updating or deleting rows
+ *
+ * Calls $this->initializeTriggers() in case $this->triggers is null.
+ *
+ * @return array
+ */
+ public function getTriggers()
+ {
+ if ($this->triggers === null) {
+ $this->triggers = $this->initializeTriggers();
+ }
+
+ return $this->triggers;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the triggers lazily
+ *
+ * @return array
+ */
+ protected function initializeTriggers()
+ {
+ return array();
+ }
+
+ /**
+ * Run a trigger for the given table and row which is about to be inserted
+ *
+ * @param string $table
+ * @param ConfigObject $new
+ *
+ * @return ConfigObject
+ */
+ public function onInsert($table, ConfigObject $new)
+ {
+ $trigger = $this->getTrigger($table, 'onInsert');
+ if ($trigger !== null) {
+ $row = $this->$trigger($new);
+ if ($row !== null) {
+ $new = $row;
+ }
+ }
+
+ return $new;
+ }
+
+ /**
+ * Run a trigger for the given table and row which is about to be updated
+ *
+ * @param string $table
+ * @param ConfigObject $old
+ * @param ConfigObject $new
+ *
+ * @return ConfigObject
+ */
+ public function onUpdate($table, ConfigObject $old, ConfigObject $new)
+ {
+ $trigger = $this->getTrigger($table, 'onUpdate');
+ if ($trigger !== null) {
+ $row = $this->$trigger($old, $new);
+ if ($row !== null) {
+ $new = $row;
+ }
+ }
+
+ return $new;
+ }
+
+ /**
+ * Run a trigger for the given table and row which has been deleted
+ *
+ * @param string $table
+ * @param ConfigObject $old
+ *
+ * @return ConfigObject
+ */
+ public function onDelete($table, ConfigObject $old)
+ {
+ $trigger = $this->getTrigger($table, 'onDelete');
+ if ($trigger !== null) {
+ $this->$trigger($old);
+ }
+ }
+
+ /**
+ * Return the name of the trigger method for the given table and event-type
+ *
+ * @param string $table The table name for which to return a trigger method
+ * @param string $event The name of the event type
+ *
+ * @return string
+ */
+ protected function getTrigger($table, $event)
+ {
+ if (! in_array($table, $this->getTriggers())) {
+ return;
+ }
+
+ $identifier = join('', array_map('ucfirst', explode('_', $table)));
+ if (method_exists($this, $event . $identifier)) {
+ return $event . $identifier;
+ }
+ }
+
+ /**
+ * Insert the given data for the given target
+ *
+ * $data must provide a proper value for the data source's key column.
+ *
+ * @param string $target
+ * @param array $data
+ *
+ * @throws StatementException In case the operation has failed
+ */
+ public function insert($target, array $data)
+ {
+ $ds = $this->getDataSource($target);
+ $newData = $this->requireStatementColumns($target, $data);
+
+ $config = $this->onInsert($target, new ConfigObject($newData));
+ $section = $this->extractSectionName($config, $ds->getConfigObject()->getKeyColumn());
+
+ if ($ds->hasSection($section)) {
+ throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section);
+ }
+
+ $ds->setSection($section, $config);
+
+ try {
+ $ds->saveIni();
+ } catch (Exception $e) {
+ throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage());
+ }
+ }
+
+ /**
+ * Update the target with the given data and optionally limit the affected entries by using a filter
+ *
+ * @param string $target
+ * @param array $data
+ * @param Filter $filter
+ *
+ * @throws StatementException In case the operation has failed
+ */
+ public function update($target, array $data, Filter $filter = null)
+ {
+ $ds = $this->getDataSource($target);
+ $newData = $this->requireStatementColumns($target, $data);
+
+ $keyColumn = $ds->getConfigObject()->getKeyColumn();
+ if ($filter === null && isset($newData[$keyColumn])) {
+ throw new StatementException(
+ t('Cannot update. Column "%s" holds a section\'s name which must be unique'),
+ $keyColumn
+ );
+ }
+
+ $query = $ds->select();
+ if ($filter !== null) {
+ $query->addFilter($this->requireFilter($target, $filter));
+ }
+
+ /** @var ConfigObject $config */
+ $newSection = null;
+ foreach ($query as $section => $config) {
+ if ($newSection !== null) {
+ throw new StatementException(
+ t('Cannot update. Column "%s" holds a section\'s name which must be unique'),
+ $keyColumn
+ );
+ }
+
+ $newConfig = clone $config;
+ foreach ($newData as $column => $value) {
+ if ($column === $keyColumn) {
+ if ($value !== $config->get($keyColumn)) {
+ $newSection = $value;
+ }
+ } else {
+ $newConfig->$column = $value;
+ }
+ }
+
+ // This is necessary as the query result set contains the key column.
+ unset($newConfig->$keyColumn);
+
+ if ($newSection) {
+ if ($ds->hasSection($newSection)) {
+ throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection);
+ }
+
+ $ds->removeSection($section)->setSection(
+ $newSection,
+ $this->onUpdate($target, $config, $newConfig)
+ );
+ } else {
+ $ds->setSection(
+ $section,
+ $this->onUpdate($target, $config, $newConfig)
+ );
+ }
+ }
+
+ try {
+ $ds->saveIni();
+ } catch (Exception $e) {
+ throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage());
+ }
+ }
+
+ /**
+ * Delete entries in the given target, optionally limiting the affected entries by using a filter
+ *
+ * @param string $target
+ * @param Filter $filter
+ *
+ * @throws StatementException In case the operation has failed
+ */
+ public function delete($target, Filter $filter = null)
+ {
+ $ds = $this->getDataSource($target);
+
+ $query = $ds->select();
+ if ($filter !== null) {
+ $query->addFilter($this->requireFilter($target, $filter));
+ }
+
+ /** @var ConfigObject $config */
+ foreach ($query as $section => $config) {
+ $ds->removeSection($section);
+ $this->onDelete($target, $config);
+ }
+
+ try {
+ $ds->saveIni();
+ } catch (Exception $e) {
+ throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage());
+ }
+ }
+
+ /**
+ * Create and return a Config for the given meta and table
+ *
+ * @param array $meta
+ * @param string $table
+ *
+ * @return Config
+ *
+ * @throws ProgrammingError In case the given meta is invalid
+ */
+ protected function createConfig(array $meta, $table)
+ {
+ if (! isset($meta['name'])) {
+ throw new ProgrammingError('Config file name missing for table "%s"', $table);
+ } elseif (! isset($meta['keyColumn'])) {
+ throw new ProgrammingError('Config key column name missing for table "%s"', $table);
+ }
+
+ if (isset($meta['module'])) {
+ $config = Config::module($meta['module'], $meta['name']);
+ } else {
+ $config = Config::app($meta['name']);
+ }
+
+ $config->getConfigObject()->setKeyColumn($meta['keyColumn']);
+ return $config;
+ }
+
+ /**
+ * Extract and return the section name off of the given $config
+ *
+ * @param array|ConfigObject $config
+ * @param string $keyColumn
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case no valid section name is available
+ */
+ protected function extractSectionName(&$config, $keyColumn)
+ {
+ if (! is_array($config) && !$config instanceof ConfigObject) {
+ throw new ProgrammingError('$config is neither an array nor a ConfigObject');
+ } elseif (! isset($config[$keyColumn])) {
+ throw new ProgrammingError('$config does not provide a value for key column "%s"', $keyColumn);
+ }
+
+ $section = $config[$keyColumn];
+ unset($config[$keyColumn]);
+ return $section;
+ }
+}
diff --git a/library/Icinga/Repository/LdapRepository.php b/library/Icinga/Repository/LdapRepository.php
new file mode 100644
index 0000000..e7a380b
--- /dev/null
+++ b/library/Icinga/Repository/LdapRepository.php
@@ -0,0 +1,71 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Repository;
+
+use Icinga\Protocol\Ldap\LdapConnection;
+
+/**
+ * Abstract base class for concrete LDAP repository implementations
+ *
+ * Additionally provided features:
+ * <ul>
+ * <li>Attribute name normalization</li>
+ * </ul>
+ */
+abstract class LdapRepository extends Repository
+{
+ /**
+ * The datasource being used
+ *
+ * @var LdapConnection
+ */
+ protected $ds;
+
+ /**
+ * Normed attribute names based on known LDAP environments
+ *
+ * @var array
+ */
+ protected $normedAttributes = array(
+ 'uid' => 'uid',
+ 'gid' => 'gid',
+ 'user' => 'user',
+ 'group' => 'group',
+ 'member' => 'member',
+ 'memberuid' => 'memberUid',
+ 'posixgroup' => 'posixGroup',
+ 'uniquemember' => 'uniqueMember',
+ 'groupofnames' => 'groupOfNames',
+ 'inetorgperson' => 'inetOrgPerson',
+ 'samaccountname' => 'sAMAccountName',
+ 'groupofuniquenames' => 'groupOfUniqueNames'
+ );
+
+ /**
+ * Create a new LDAP repository object
+ *
+ * @param LdapConnection $ds The data source to use
+ */
+ public function __construct(LdapConnection $ds)
+ {
+ parent::__construct($ds);
+ }
+
+ /**
+ * Return the given attribute name normed to known LDAP enviroments, if possible
+ *
+ * @param string $name
+ *
+ * @return string
+ */
+ protected function getNormedAttribute($name)
+ {
+ $loweredName = strtolower($name ?? '');
+ if (array_key_exists($loweredName, $this->normedAttributes)) {
+ return $this->normedAttributes[$loweredName];
+ }
+
+ return $name;
+ }
+}
diff --git a/library/Icinga/Repository/Repository.php b/library/Icinga/Repository/Repository.php
new file mode 100644
index 0000000..bda93aa
--- /dev/null
+++ b/library/Icinga/Repository/Repository.php
@@ -0,0 +1,1261 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Repository;
+
+use DateTime;
+use Icinga\Application\Logger;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Selectable;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\QueryException;
+use Icinga\Exception\StatementException;
+use Icinga\Util\ASN1;
+use Icinga\Util\StringHelper;
+use InvalidArgumentException;
+
+/**
+ * Abstract base class for concrete repository implementations
+ *
+ * To utilize this class and its features, the following is required:
+ * <ul>
+ * <li>Concrete implementations need to initialize Repository::$queryColumns</li>
+ * <li>The datasource passed to a repository must implement the Selectable interface</li>
+ * <li>The datasource must yield an instance of Queryable when its select() method is called</li>
+ * </ul>
+ */
+abstract class Repository implements Selectable
+{
+ /**
+ * The format to use when converting values of type date_time
+ */
+ const DATETIME_FORMAT = 'd/m/Y g:i A';
+
+ /**
+ * The name of this repository
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The datasource being used
+ *
+ * @var Selectable
+ */
+ protected $ds;
+
+ /**
+ * The base table name this repository is responsible for
+ *
+ * This will be automatically set to the first key of $queryColumns if not explicitly set.
+ *
+ * @var string
+ */
+ protected $baseTable;
+
+ /**
+ * The virtual tables being provided
+ *
+ * This may be initialized by concrete repository implementations with an array
+ * where a key is the name of a virtual table and its value the real table name.
+ *
+ * @var array
+ */
+ protected $virtualTables;
+
+ /**
+ * The query columns being provided
+ *
+ * This must be initialized by concrete repository implementations, in the following format
+ * <code>
+ * array(
+ * 'baseTable' => array(
+ * 'column1',
+ * 'alias1' => 'column2',
+ * 'alias2' => 'column3'
+ * )
+ * )
+ * </code>
+ *
+ * @var array
+ */
+ protected $queryColumns;
+
+ /**
+ * The columns (or aliases) which are not permitted to be queried
+ *
+ * Blacklisted query columns can still occur in a filter expression or sort rule.
+ *
+ * @var array An array of strings
+ */
+ protected $blacklistedQueryColumns;
+
+ /**
+ * Whether the blacklisted query columns are in the legacy format
+ *
+ * @var bool
+ */
+ protected $legacyBlacklistedQueryColumns;
+
+ /**
+ * The filter columns being provided
+ *
+ * This may be intialized by concrete repository implementations, in the following format
+ * <code>
+ * array(
+ * 'alias_or_column_name',
+ * 'label_to_show_in_the_filter_editor' => 'alias_or_column_name'
+ * )
+ * </code>
+ *
+ * @var array
+ */
+ protected $filterColumns;
+
+ /**
+ * Whether the provided filter columns are in the legacy format
+ *
+ * @var bool
+ */
+ protected $legacyFilterColumns;
+
+ /**
+ * The search columns (or aliases) being provided
+ *
+ * @var array An array of strings
+ */
+ protected $searchColumns;
+
+ /**
+ * Whether the provided search columns are in the legacy format
+ *
+ * @var bool
+ */
+ protected $legacySearchColumns;
+
+ /**
+ * The sort rules to be applied on a query
+ *
+ * This may be initialized by concrete repository implementations, in the following format
+ * <code>
+ * array(
+ * 'alias_or_column_name' => array(
+ * 'order' => 'asc'
+ * ),
+ * 'alias_or_column_name' => array(
+ * 'columns' => array(
+ * 'once_more_the_alias_or_column_name_as_in_the_parent_key',
+ * 'an_additional_alias_or_column_name_with_a_specific_direction asc'
+ * ),
+ * 'order' => 'desc'
+ * ),
+ * 'alias_or_column_name' => array(
+ * 'columns' => array('a_different_alias_or_column_name_designated_to_act_as_the_only_sort_column')
+ * // Ascendant sort by default
+ * )
+ * )
+ * </code>
+ * Note that it's mandatory to supply the alias name in case there is one.
+ *
+ * @var array
+ */
+ protected $sortRules;
+
+ /**
+ * Whether the provided sort rules are in the legacy format
+ *
+ * @var bool
+ */
+ protected $legacySortRules;
+
+ /**
+ * The value conversion rules to apply on a query or statement
+ *
+ * This may be initialized by concrete repository implementations and describes for which aliases or column
+ * names what type of conversion is available. For entries, where the key is the alias/column and the value
+ * is the type identifier, the repository attempts to find a conversion method for the alias/column first and,
+ * if none is found, then for the type. If an entry only provides a value, which is the alias/column, the
+ * repository only attempts to find a conversion method for the alias/column. The name of a conversion method
+ * is expected to be declared using lowerCamelCase. (e.g. user_name will be translated to persistUserName and
+ * groupname will be translated to retrieveGroupname)
+ *
+ * @var array
+ */
+ protected $conversionRules;
+
+ /**
+ * An array to map table names to aliases
+ *
+ * @var array
+ */
+ protected $aliasTableMap;
+
+ /**
+ * A flattened array to map query columns to aliases
+ *
+ * @var array
+ */
+ protected $aliasColumnMap;
+
+ /**
+ * An array to map table names to query columns
+ *
+ * @var array
+ */
+ protected $columnTableMap;
+
+ /**
+ * A flattened array to map aliases to query columns
+ *
+ * @var array
+ */
+ protected $columnAliasMap;
+
+ /**
+ * Create a new repository object
+ *
+ * @param Selectable|null $ds The datasource to use.
+ * Only pass null if you have overridden {@link getDataSource()}!
+ */
+ public function __construct(Selectable $ds = null)
+ {
+ $this->ds = $ds;
+ $this->aliasTableMap = array();
+ $this->aliasColumnMap = array();
+ $this->columnTableMap = array();
+ $this->columnAliasMap = array();
+
+ $this->init();
+ }
+
+ /**
+ * Initialize this repository
+ *
+ * Supposed to be overwritten by concrete repository implementations.
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Set this repository's name
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Return this repository's name
+ *
+ * In case no name has been explicitly set yet, the class name is returned.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name ?: __CLASS__;
+ }
+
+ /**
+ * Return the datasource being used for the given table
+ *
+ * @param string $table
+ *
+ * @return Selectable
+ *
+ * @throws ProgrammingError In case no datasource is available
+ */
+ public function getDataSource($table = null)
+ {
+ if ($this->ds === null) {
+ throw new ProgrammingError(
+ 'No data source available. It is required to either pass it'
+ . ' at initialization time or by overriding this method.'
+ );
+ }
+
+ return $this->ds;
+ }
+
+ /**
+ * Return the base table name this repository is responsible for
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case no base table name has been set and
+ * $this->queryColumns does not provide one either
+ */
+ public function getBaseTable()
+ {
+ if ($this->baseTable === null) {
+ $queryColumns = $this->getQueryColumns();
+ reset($queryColumns);
+ $this->baseTable = key($queryColumns);
+ if (is_int($this->baseTable) || !is_array($queryColumns[$this->baseTable])) {
+ throw new ProgrammingError('"%s" is not a valid base table', $this->baseTable);
+ }
+ }
+
+ return $this->baseTable;
+ }
+
+ /**
+ * Return the virtual tables being provided
+ *
+ * Calls $this->initializeVirtualTables() in case $this->virtualTables is null.
+ *
+ * @return array
+ */
+ public function getVirtualTables()
+ {
+ if ($this->virtualTables === null) {
+ $this->virtualTables = $this->initializeVirtualTables();
+ }
+
+ return $this->virtualTables;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the virtual tables lazily
+ *
+ * @return array
+ */
+ protected function initializeVirtualTables()
+ {
+ return array();
+ }
+
+ /**
+ * Return the query columns being provided
+ *
+ * Calls $this->initializeQueryColumns() in case $this->queryColumns is null.
+ *
+ * @return array
+ */
+ public function getQueryColumns()
+ {
+ if ($this->queryColumns === null) {
+ $this->queryColumns = $this->initializeQueryColumns();
+ }
+
+ return $this->queryColumns;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the query columns lazily
+ *
+ * @return array
+ */
+ protected function initializeQueryColumns()
+ {
+ return array();
+ }
+
+ /**
+ * Return the columns (or aliases) which are not permitted to be queried
+ *
+ * Calls $this->initializeBlacklistedQueryColumns() in case $this->blacklistedQueryColumns is null.
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ public function getBlacklistedQueryColumns($table = null)
+ {
+ if ($this->blacklistedQueryColumns === null) {
+ $this->legacyBlacklistedQueryColumns = false;
+
+ $blacklistedQueryColumns = $this->initializeBlacklistedQueryColumns($table);
+ if (is_int(key($blacklistedQueryColumns))) {
+ $this->blacklistedQueryColumns[$table] = $blacklistedQueryColumns;
+ } else {
+ $this->blacklistedQueryColumns = $blacklistedQueryColumns;
+ }
+ } elseif ($this->legacyBlacklistedQueryColumns === null) {
+ $this->legacyBlacklistedQueryColumns = is_int(key($this->blacklistedQueryColumns));
+ }
+
+ if ($this->legacyBlacklistedQueryColumns) {
+ return $this->blacklistedQueryColumns;
+ } elseif (! isset($this->blacklistedQueryColumns[$table])) {
+ $this->blacklistedQueryColumns[$table] = $this->initializeBlacklistedQueryColumns($table);
+ }
+
+ return $this->blacklistedQueryColumns[$table];
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the
+ * blacklisted query columns lazily or dependent on a query's current base table
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ protected function initializeBlacklistedQueryColumns()
+ {
+ // $table is not part of the signature due to PHP strict standards
+ return array();
+ }
+
+ /**
+ * Return the filter columns being provided
+ *
+ * Calls $this->initializeFilterColumns() in case $this->filterColumns is null.
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ public function getFilterColumns($table = null)
+ {
+ if ($this->filterColumns === null) {
+ $this->legacyFilterColumns = false;
+
+ $filterColumns = $this->initializeFilterColumns($table);
+ $foundTables = array_intersect_key($this->getQueryColumns(), $filterColumns);
+ if (empty($foundTables)) {
+ $this->filterColumns[$table] = $filterColumns;
+ } else {
+ $this->filterColumns = $filterColumns;
+ }
+ } elseif ($this->legacyFilterColumns === null) {
+ $foundTables = array_intersect_key($this->getQueryColumns(), $this->filterColumns);
+ $this->legacyFilterColumns = empty($foundTables);
+ }
+
+ if ($this->legacyFilterColumns) {
+ return $this->filterColumns;
+ } elseif (! isset($this->filterColumns[$table])) {
+ $this->filterColumns[$table] = $this->initializeFilterColumns($table);
+ }
+
+ return $this->filterColumns[$table];
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize
+ * the filter columns lazily or dependent on a query's current base table
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ protected function initializeFilterColumns()
+ {
+ // $table is not part of the signature due to PHP strict standards
+ return array();
+ }
+
+ /**
+ * Return the search columns being provided
+ *
+ * Calls $this->initializeSearchColumns() in case $this->searchColumns is null.
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ public function getSearchColumns($table = null)
+ {
+ if ($this->searchColumns === null) {
+ $this->legacySearchColumns = false;
+
+ $searchColumns = $this->initializeSearchColumns($table);
+ if (is_int(key($searchColumns))) {
+ $this->searchColumns[$table] = $searchColumns;
+ } else {
+ $this->searchColumns = $searchColumns;
+ }
+ } elseif ($this->legacySearchColumns === null) {
+ $this->legacySearchColumns = is_int(key($this->searchColumns));
+ }
+
+ if ($this->legacySearchColumns) {
+ return $this->searchColumns;
+ } elseif (! isset($this->searchColumns[$table])) {
+ $this->searchColumns[$table] = $this->initializeSearchColumns($table);
+ }
+
+ return $this->searchColumns[$table];
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize
+ * the search columns lazily or dependent on a query's current base table
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ protected function initializeSearchColumns()
+ {
+ // $table is not part of the signature due to PHP strict standards
+ return array();
+ }
+
+ /**
+ * Return the sort rules to be applied on a query
+ *
+ * Calls $this->initializeSortRules() in case $this->sortRules is null.
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ public function getSortRules($table = null)
+ {
+ if ($this->sortRules === null) {
+ $this->legacySortRules = false;
+
+ $sortRules = $this->initializeSortRules($table);
+ $foundTables = array_intersect_key($this->getQueryColumns(), $sortRules);
+ if (empty($foundTables)) {
+ $this->sortRules[$table] = $sortRules;
+ } else {
+ $this->sortRules = $sortRules;
+ }
+ } elseif ($this->legacySortRules === null) {
+ $foundTables = array_intersect_key($this->getQueryColumns(), $this->sortRules);
+ $this->legacySortRules = empty($foundTables);
+ }
+
+ if ($this->legacySortRules) {
+ return $this->sortRules;
+ } elseif (! isset($this->sortRules[$table])) {
+ $this->sortRules[$table] = $this->initializeSortRules($table);
+ }
+
+ return $this->sortRules[$table];
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize
+ * the sort rules lazily or dependent on a query's current base table
+ *
+ * @param string $table
+ *
+ * @return array
+ */
+ protected function initializeSortRules()
+ {
+ // $table is not part of the signature due to PHP strict standards
+ return array();
+ }
+
+ /**
+ * Return the value conversion rules to apply on a query
+ *
+ * Calls $this->initializeConversionRules() in case $this->conversionRules is null.
+ *
+ * @return array
+ */
+ public function getConversionRules()
+ {
+ if ($this->conversionRules === null) {
+ $this->conversionRules = $this->initializeConversionRules();
+ }
+
+ return $this->conversionRules;
+ }
+
+ /**
+ * Overwrite this in your repository implementation in case you need to initialize the conversion rules lazily
+ *
+ * @return array
+ */
+ protected function initializeConversionRules()
+ {
+ return array();
+ }
+
+ /**
+ * Return an array to map table names to aliases
+ *
+ * @return array
+ */
+ protected function getAliasTableMap()
+ {
+ if (empty($this->aliasTableMap)) {
+ $this->initializeAliasMaps();
+ }
+
+ return $this->aliasTableMap;
+ }
+
+ /**
+ * Return a flattened array to map query columns to aliases
+ *
+ * @return array
+ */
+ protected function getAliasColumnMap()
+ {
+ if (empty($this->aliasColumnMap)) {
+ $this->initializeAliasMaps();
+ }
+
+ return $this->aliasColumnMap;
+ }
+
+ /**
+ * Return an array to map table names to query columns
+ *
+ * @return array
+ */
+ protected function getColumnTableMap()
+ {
+ if (empty($this->columnTableMap)) {
+ $this->initializeAliasMaps();
+ }
+
+ return $this->columnTableMap;
+ }
+
+ /**
+ * Return a flattened array to map aliases to query columns
+ *
+ * @return array
+ */
+ protected function getColumnAliasMap()
+ {
+ if (empty($this->columnAliasMap)) {
+ $this->initializeAliasMaps();
+ }
+
+ return $this->columnAliasMap;
+ }
+
+ /**
+ * Initialize $this->aliasTableMap and $this->aliasColumnMap
+ *
+ * @throws ProgrammingError In case $this->queryColumns does not provide any column information
+ */
+ protected function initializeAliasMaps()
+ {
+ $queryColumns = $this->getQueryColumns();
+ if (empty($queryColumns)) {
+ throw new ProgrammingError('Repositories are required to initialize $this->queryColumns first');
+ }
+
+ foreach ($queryColumns as $table => $columns) {
+ foreach ($columns as $alias => $column) {
+ if (! is_string($alias)) {
+ $key = $column;
+ } else {
+ $key = $alias;
+ $column = preg_replace('~\n\s*~', ' ', $column);
+ }
+
+ if (array_key_exists($key, $this->aliasTableMap)) {
+ if ($this->aliasTableMap[$key] !== null) {
+ $existingTable = $this->aliasTableMap[$key];
+ $existingColumn = $this->aliasColumnMap[$key];
+ $this->aliasTableMap[$existingTable . '.' . $key] = $existingTable;
+ $this->aliasColumnMap[$existingTable . '.' . $key] = $existingColumn;
+ $this->aliasTableMap[$key] = null;
+ $this->aliasColumnMap[$key] = null;
+ }
+
+ $this->aliasTableMap[$table . '.' . $key] = $table;
+ $this->aliasColumnMap[$table . '.' . $key] = $column;
+ } else {
+ $this->aliasTableMap[$key] = $table;
+ $this->aliasColumnMap[$key] = $column;
+ }
+
+ if (array_key_exists($column, $this->columnTableMap)) {
+ if ($this->columnTableMap[$column] !== null) {
+ $existingTable = $this->columnTableMap[$column];
+ $existingAlias = $this->columnAliasMap[$column];
+ $this->columnTableMap[$existingTable . '.' . $column] = $existingTable;
+ $this->columnAliasMap[$existingTable . '.' . $column] = $existingAlias;
+ $this->columnTableMap[$column] = null;
+ $this->columnAliasMap[$column] = null;
+ }
+
+ $this->columnTableMap[$table . '.' . $column] = $table;
+ $this->columnAliasMap[$table . '.' . $column] = $key;
+ } else {
+ $this->columnTableMap[$column] = $table;
+ $this->columnAliasMap[$column] = $key;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a new query for the given columns
+ *
+ * @param array $columns The desired columns, if null all columns will be queried
+ *
+ * @return RepositoryQuery
+ */
+ public function select(array $columns = null)
+ {
+ $query = new RepositoryQuery($this);
+ $query->from($this->getBaseTable(), $columns);
+ return $query;
+ }
+
+ /**
+ * Return whether this repository is capable of converting values for the given table and optional column
+ *
+ * @param string $table
+ * @param string $column
+ *
+ * @return bool
+ */
+ public function providesValueConversion($table, $column = null)
+ {
+ $conversionRules = $this->getConversionRules();
+ if (empty($conversionRules)) {
+ return false;
+ }
+
+ if (! isset($conversionRules[$table])) {
+ return false;
+ } elseif ($column === null) {
+ return true;
+ }
+
+ $alias = $this->reassembleQueryColumnAlias($table, $column) ?: $column;
+ return array_key_exists($alias, $conversionRules[$table]) || in_array($alias, $conversionRules[$table]);
+ }
+
+ /**
+ * Convert a value supposed to be transmitted to the data source
+ *
+ * @param string $table The table where to persist the value
+ * @param string $name The alias or column name
+ * @param mixed $value The value to convert
+ * @param RepositoryQuery $query An optional query to pass as context
+ * (Directly passed through to $this->getConverter)
+ *
+ * @return mixed If conversion was possible, the converted value,
+ * otherwise the unchanged value
+ */
+ public function persistColumn($table, $name, $value, RepositoryQuery $query = null)
+ {
+ $converter = $this->getConverter($table, $name, 'persist', $query);
+ if ($converter !== null) {
+ $value = $this->$converter($value, $name, $table, $query);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Convert a value which was fetched from the data source
+ *
+ * @param string $table The table the value has been fetched from
+ * @param string $name The alias or column name
+ * @param mixed $value The value to convert
+ * @param RepositoryQuery $query An optional query to pass as context
+ * (Directly passed through to $this->getConverter)
+ *
+ * @return mixed If conversion was possible, the converted value,
+ * otherwise the unchanged value
+ */
+ public function retrieveColumn($table, $name, $value, RepositoryQuery $query = null)
+ {
+ $converter = $this->getConverter($table, $name, 'retrieve', $query);
+ if ($converter !== null) {
+ $value = $this->$converter($value, $name, $table, $query);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Return the name of the conversion method for the given alias or column name and context
+ *
+ * @param string $table The datasource's table
+ * @param string $name The alias or column name for which to return a conversion method
+ * @param string $context The context of the conversion: persist or retrieve
+ * @param RepositoryQuery $query An optional query to pass as context
+ * (unused by the base implementation)
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case a conversion rule is found but not any conversion method
+ */
+ protected function getConverter($table, $name, $context, RepositoryQuery $query = null)
+ {
+ $conversionRules = $this->getConversionRules();
+ if (! isset($conversionRules[$table])) {
+ return;
+ }
+
+ $tableRules = $conversionRules[$table];
+ if (($alias = $this->reassembleQueryColumnAlias($table, $name)) === null) {
+ $alias = $name;
+ }
+
+ // Check for a conversion method for the alias/column first
+ if (array_key_exists($alias, $tableRules) || in_array($alias, $tableRules)) {
+ $methodName = $context . join('', array_map('ucfirst', explode('_', $alias)));
+ if (method_exists($this, $methodName)) {
+ return $methodName;
+ }
+ }
+
+ // The conversion method for the type is just a fallback, but it is required to exist if defined
+ if (isset($tableRules[$alias])) {
+ $identifier = join('', array_map('ucfirst', explode('_', $tableRules[$alias])));
+ if (! method_exists($this, $context . $identifier)) {
+ // Do not throw an error in case at least one conversion method exists
+ if (! method_exists($this, ($context === 'persist' ? 'retrieve' : 'persist') . $identifier)) {
+ throw new ProgrammingError(
+ 'Cannot find any conversion method for type "%s"'
+ . '. Add a proper conversion method or remove the type definition',
+ $tableRules[$alias]
+ );
+ }
+
+ Logger::debug(
+ 'Conversion method "%s" for type definition "%s" does not exist in repository "%s".',
+ $context . $identifier,
+ $tableRules[$alias],
+ $this->getName()
+ );
+ } else {
+ return $context . $identifier;
+ }
+ }
+ }
+
+ /**
+ * Convert a timestamp or DateTime object to a string formatted using static::DATETIME_FORMAT
+ *
+ * @param mixed $value
+ *
+ * @return string
+ */
+ protected function persistDateTime($value)
+ {
+ if (is_numeric($value)) {
+ $value = date(static::DATETIME_FORMAT, $value);
+ } elseif ($value instanceof DateTime) {
+ $value = date(static::DATETIME_FORMAT, $value->getTimestamp()); // Using date here, to ignore any timezone
+ } elseif ($value !== null) {
+ throw new ProgrammingError(
+ 'Cannot persist value "%s" as type date_time. It\'s not a timestamp or DateTime object',
+ $value
+ );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Convert a string formatted using static::DATETIME_FORMAT to a unix timestamp
+ *
+ * @param string $value
+ *
+ * @return int
+ */
+ protected function retrieveDateTime($value)
+ {
+ if (is_numeric($value)) {
+ $value = (int) $value;
+ } elseif (is_string($value)) {
+ $dateTime = DateTime::createFromFormat(static::DATETIME_FORMAT, $value);
+ if ($dateTime === false) {
+ Logger::debug(
+ 'Unable to parse string "%s" as type date_time with format "%s" in repository "%s"',
+ $value,
+ static::DATETIME_FORMAT,
+ $this->getName()
+ );
+ $value = null;
+ } else {
+ $value = $dateTime->getTimestamp();
+ }
+ } elseif ($value !== null) {
+ throw new ProgrammingError(
+ 'Cannot retrieve value "%s" as type date_time. It\'s not a integer or (numeric) string',
+ $value
+ );
+ }
+
+ return $value;
+ }
+
+ /**
+ * Convert the given array to an comma separated string
+ *
+ * @param array|string $value
+ *
+ * @return string
+ */
+ protected function persistCommaSeparatedString($value)
+ {
+ if (is_array($value)) {
+ $value = join(',', array_map('trim', $value));
+ } elseif ($value !== null && !is_string($value)) {
+ throw new ProgrammingError('Cannot persist value "%s" as comma separated string', $value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Convert the given comma separated string to an array
+ *
+ * @param string $value
+ *
+ * @return array
+ */
+ protected function retrieveCommaSeparatedString($value)
+ {
+ if ($value && is_string($value)) {
+ $value = StringHelper::trimSplit($value);
+ } elseif ($value !== null) {
+ throw new ProgrammingError('Cannot retrieve value "%s" as array. It\'s not a string', $value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Parse the given value based on the ASN.1 standard (GeneralizedTime) and return its timestamp representation
+ *
+ * @param string|null $value
+ *
+ * @return int
+ *
+ * @see https://tools.ietf.org/html/rfc4517#section-3.3.13
+ */
+ protected function retrieveGeneralizedTime($value)
+ {
+ if ($value === null) {
+ return $value;
+ }
+
+ try {
+ return ASN1::parseGeneralizedTime($value)->getTimeStamp();
+ } catch (InvalidArgumentException $e) {
+ Logger::debug(sprintf('Repository "%s": %s', $this->getName(), $e->getMessage()));
+ }
+ }
+
+ /**
+ * Validate that the requested table exists and resolve it's real name if necessary
+ *
+ * @param string $table The table to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ * (unused by the base implementation)
+ *
+ * @return string The table's name, may differ from the given one
+ *
+ * @throws ProgrammingError In case the given table does not exist
+ */
+ public function requireTable($table, RepositoryQuery $query = null)
+ {
+ $queryColumns = $this->getQueryColumns();
+ if (! isset($queryColumns[$table])) {
+ throw new ProgrammingError('Table "%s" not found', $table);
+ }
+
+ $virtualTables = $this->getVirtualTables();
+ if (isset($virtualTables[$table])) {
+ $table = $virtualTables[$table];
+ }
+
+ return $table;
+ }
+
+ /**
+ * Recurse the given filter, require each column for the given table and convert all values
+ *
+ * @param string $table The table being filtered
+ * @param Filter $filter The filter to recurse
+ * @param RepositoryQuery $query An optional query to pass as context
+ * (Directly passed through to $this->requireFilterColumn)
+ * @param bool $clone Whether to clone $filter first
+ *
+ * @return Filter The udpated filter
+ */
+ public function requireFilter($table, Filter $filter, RepositoryQuery $query = null, $clone = true)
+ {
+ if ($clone) {
+ $filter = clone $filter;
+ }
+
+ if ($filter->isExpression()) {
+ $column = $filter->getColumn();
+ $filter->setColumn($this->requireFilterColumn($table, $column, $query, $filter));
+ $filter->setExpression($this->persistColumn($table, $column, $filter->getExpression(), $query));
+ } elseif ($filter->isChain()) {
+ foreach ($filter->filters() as $chainOrExpression) {
+ $this->requireFilter($table, $chainOrExpression, $query, false);
+ }
+ }
+
+ return $filter;
+ }
+
+ /**
+ * Return this repository's query columns of the given table mapped to their respective aliases
+ *
+ * @param string $table
+ *
+ * @return array
+ *
+ * @throws ProgrammingError In case $table does not exist
+ */
+ public function requireAllQueryColumns($table)
+ {
+ $queryColumns = $this->getQueryColumns();
+ if (! array_key_exists($table, $queryColumns)) {
+ throw new ProgrammingError('Table name "%s" not found', $table);
+ }
+
+ $blacklist = $this->getBlacklistedQueryColumns($table);
+ $columns = array();
+ foreach ($queryColumns[$table] as $alias => $column) {
+ $name = is_string($alias) ? $alias : $column;
+ if (! in_array($name, $blacklist)) {
+ $columns[$alias] = $this->resolveQueryColumnAlias($table, $name);
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Return the query column name for the given alias or null in case the alias does not exist
+ *
+ * @param string $table
+ * @param string $alias
+ *
+ * @return string|null
+ */
+ public function resolveQueryColumnAlias($table, $alias)
+ {
+ $aliasColumnMap = $this->getAliasColumnMap();
+ if (isset($aliasColumnMap[$alias])) {
+ return $aliasColumnMap[$alias];
+ }
+
+ $prefixedAlias = $table . '.' . $alias;
+ if (isset($aliasColumnMap[$prefixedAlias])) {
+ return $aliasColumnMap[$prefixedAlias];
+ }
+ }
+
+ /**
+ * Return the alias for the given query column name or null in case the query column name does not exist
+ *
+ * @param string $table
+ * @param string $column
+ *
+ * @return string|null
+ */
+ public function reassembleQueryColumnAlias($table, $column)
+ {
+ $columnAliasMap = $this->getColumnAliasMap();
+ if (isset($columnAliasMap[$column])) {
+ return $columnAliasMap[$column];
+ }
+
+ $prefixedColumn = $table . '.' . $column;
+ if (isset($columnAliasMap[$prefixedColumn])) {
+ return $columnAliasMap[$prefixedColumn];
+ }
+ }
+
+ /**
+ * Return whether the given alias or query column name is available in the given table
+ *
+ * @param string $table
+ * @param string $alias
+ *
+ * @return bool
+ */
+ public function validateQueryColumnAssociation($table, $alias)
+ {
+ $aliasTableMap = $this->getAliasTableMap();
+ if (isset($aliasTableMap[$alias])) {
+ return $aliasTableMap[$alias] === $table;
+ }
+
+ $prefixedAlias = $table . '.' . $alias;
+ if (isset($aliasTableMap[$prefixedAlias])) {
+ return true;
+ }
+
+ $columnTableMap = $this->getColumnTableMap();
+ if (isset($columnTableMap[$alias])) {
+ return $columnTableMap[$alias] === $table;
+ }
+
+ return isset($columnTableMap[$prefixedAlias]);
+ }
+
+ /**
+ * Return whether the given column name or alias is a valid query column
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The column name or alias to check
+ *
+ * @return bool
+ */
+ public function hasQueryColumn($table, $name)
+ {
+ if ($this->resolveQueryColumnAlias($table, $name) !== null) {
+ $alias = $name;
+ } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) === null) {
+ return false;
+ }
+
+ return !in_array($alias, $this->getBlacklistedQueryColumns($table))
+ && $this->validateQueryColumnAssociation($table, $name);
+ }
+
+ /**
+ * Validate that the given column is a valid query target and return it or the actual name if it's an alias
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context (unused by the base implementation)
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid query column
+ */
+ public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
+ {
+ if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
+ $alias = $name;
+ } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) !== null) {
+ $column = $name;
+ } else {
+ throw new QueryException(t('Query column "%s" not found'), $name);
+ }
+
+ if (in_array($alias, $this->getBlacklistedQueryColumns($table))) {
+ throw new QueryException(t('Column "%s" cannot be queried'), $name);
+ }
+
+ if (! $this->validateQueryColumnAssociation($table, $alias)) {
+ throw new QueryException(t('Query column "%s" not found in table "%s"'), $name, $table);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Return whether the given column name or alias is a valid filter column
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The column name or alias to check
+ *
+ * @return bool
+ */
+ public function hasFilterColumn($table, $name)
+ {
+ return ($this->resolveQueryColumnAlias($table, $name) !== null
+ || $this->reassembleQueryColumnAlias($table, $name) !== null)
+ && $this->validateQueryColumnAssociation($table, $name);
+ }
+
+ /**
+ * Validate that the given column is a valid filter target and return it or the actual name if it's an alias
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context (unused by the base implementation)
+ * @param FilterExpression $filter An optional filter to pass as context (unused by the base implementation)
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid filter column
+ */
+ public function requireFilterColumn($table, $name, RepositoryQuery $query = null, FilterExpression $filter = null)
+ {
+ if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
+ $alias = $name;
+ } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) !== null) {
+ $column = $name;
+ } else {
+ throw new QueryException(t('Filter column "%s" not found'), $name);
+ }
+
+ if (! $this->validateQueryColumnAssociation($table, $alias)) {
+ throw new QueryException(t('Filter column "%s" not found in table "%s"'), $name, $table);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Return whether the given column name or alias of the given table is a valid statement column
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The column name or alias to check
+ *
+ * @return bool
+ */
+ public function hasStatementColumn($table, $name)
+ {
+ return $this->hasQueryColumn($table, $name);
+ }
+
+ /**
+ * Validate that the given column is a valid statement column and return it or the actual name if it's an alias
+ *
+ * @param string $table The table for which to require the column
+ * @param string $name The name or alias of the column to validate
+ *
+ * @return string The given column's name
+ *
+ * @throws StatementException In case the given column is not a statement column
+ */
+ public function requireStatementColumn($table, $name)
+ {
+ if (($column = $this->resolveQueryColumnAlias($table, $name)) !== null) {
+ $alias = $name;
+ } elseif (($alias = $this->reassembleQueryColumnAlias($table, $name)) !== null) {
+ $column = $name;
+ } else {
+ throw new StatementException('Statement column "%s" not found', $name);
+ }
+
+ if (in_array($alias, $this->getBlacklistedQueryColumns($table))) {
+ throw new StatementException('Column "%s" cannot be referenced in a statement', $name);
+ }
+
+ if (! $this->validateQueryColumnAssociation($table, $alias)) {
+ throw new StatementException('Statement column "%s" not found in table "%s"', $name, $table);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Resolve the given aliases or column names of the given table supposed to be persisted and convert their values
+ *
+ * @param string $table
+ * @param array $data
+ *
+ * @return array
+ */
+ public function requireStatementColumns($table, array $data)
+ {
+ $resolved = array();
+ foreach ($data as $alias => $value) {
+ $resolved[$this->requireStatementColumn($table, $alias)] = $this->persistColumn($table, $alias, $value);
+ }
+
+ return $resolved;
+ }
+}
diff --git a/library/Icinga/Repository/RepositoryQuery.php b/library/Icinga/Repository/RepositoryQuery.php
new file mode 100644
index 0000000..84f7c6e
--- /dev/null
+++ b/library/Icinga/Repository/RepositoryQuery.php
@@ -0,0 +1,797 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Repository;
+
+use Iterator;
+use IteratorAggregate;
+use Traversable;
+use Icinga\Application\Benchmark;
+use Icinga\Application\Logger;
+use Icinga\Data\QueryInterface;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\FilterColumns;
+use Icinga\Data\SortRules;
+use Icinga\Exception\QueryException;
+
+/**
+ * Query class supposed to mediate between a repository and its datasource's query
+ */
+class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Iterator
+{
+ /**
+ * The repository being used
+ *
+ * @var Repository
+ */
+ protected $repository;
+
+ /**
+ * The real query being used
+ *
+ * @var QueryInterface
+ */
+ protected $query;
+
+ /**
+ * The current target to be queried
+ *
+ * @var mixed
+ */
+ protected $target;
+
+ /**
+ * The real query's iterator
+ *
+ * @var Iterator
+ */
+ protected $iterator;
+
+ /**
+ * This query's custom aliases
+ *
+ * @var array
+ */
+ protected $customAliases;
+
+ /**
+ * Create a new repository query
+ *
+ * @param Repository $repository The repository to use
+ */
+ public function __construct(Repository $repository)
+ {
+ $this->repository = $repository;
+ }
+
+ /**
+ * Clone all state relevant properties of this query
+ */
+ public function __clone()
+ {
+ if ($this->query !== null) {
+ $this->query = clone $this->query;
+ }
+ if ($this->iterator !== null) {
+ $this->iterator = clone $this->iterator;
+ }
+ }
+
+ /**
+ * Return a string representation of this query
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->query;
+ }
+
+ /**
+ * Return the real query being used
+ *
+ * @return QueryInterface
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * Set where to fetch which columns
+ *
+ * This notifies the repository about each desired query column.
+ *
+ * @param mixed $target The target from which to fetch the columns
+ * @param array $columns If null or an empty array, all columns will be fetched
+ *
+ * @return $this
+ */
+ public function from($target, array $columns = null)
+ {
+ $this->query = $this->repository->getDataSource($target)->select();
+ $this->query->from($this->repository->requireTable($target, $this));
+ $this->query->columns($this->prepareQueryColumns($target, $columns));
+ $this->target = $target;
+ return $this;
+ }
+
+ /**
+ * Return the columns to fetch
+ *
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->query->getColumns();
+ }
+
+ /**
+ * Set which columns to fetch
+ *
+ * This notifies the repository about each desired query column.
+ *
+ * @param array $columns If null or an empty array, all columns will be fetched
+ *
+ * @return $this
+ */
+ public function columns(array $columns)
+ {
+ $this->query->columns($this->prepareQueryColumns($this->target, $columns));
+ return $this;
+ }
+
+ /**
+ * Resolve the given columns supposed to be fetched
+ *
+ * This notifies the repository about each desired query column.
+ *
+ * @param mixed $target The target where to look for each column
+ * @param array $desiredColumns Pass null or an empty array to require all query columns
+ *
+ * @return array The desired columns indexed by their respective alias
+ */
+ protected function prepareQueryColumns($target, array $desiredColumns = null)
+ {
+ $this->customAliases = array();
+ if (empty($desiredColumns)) {
+ $columns = $this->repository->requireAllQueryColumns($target);
+ } else {
+ $columns = array();
+ foreach ($desiredColumns as $customAlias => $columnAlias) {
+ $resolvedColumn = $this->repository->requireQueryColumn($target, $columnAlias, $this);
+ if ($resolvedColumn !== $columnAlias) {
+ if (is_string($customAlias)) {
+ $columns[$customAlias] = $resolvedColumn;
+ $this->customAliases[$customAlias] = $columnAlias;
+ } else {
+ $columns[$columnAlias] = $resolvedColumn;
+ }
+ } elseif (is_string($customAlias)) {
+ $columns[$customAlias] = $columnAlias;
+ $this->customAliases[$customAlias] = $columnAlias;
+ } else {
+ $columns[] = $columnAlias;
+ }
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Return the native column alias for the given custom alias
+ *
+ * If no custom alias is found with the given name, it is returned unchanged.
+ *
+ * @param string $customAlias
+ *
+ * @return string
+ */
+ protected function getNativeAlias($customAlias)
+ {
+ if (isset($this->customAliases[$customAlias])) {
+ return $this->customAliases[$customAlias];
+ }
+
+ return $customAlias;
+ }
+
+ /**
+ * Return this query's available filter columns with their optional label as key
+ *
+ * @return array
+ */
+ public function getFilterColumns()
+ {
+ return $this->repository->getFilterColumns($this->target);
+ }
+
+ /**
+ * Return this query's available search columns
+ *
+ * @return array
+ */
+ public function getSearchColumns()
+ {
+ return $this->repository->getSearchColumns($this->target);
+ }
+
+ /**
+ * Filter this query using the given column and value
+ *
+ * This notifies the repository about the required filter column.
+ *
+ * @param string $column
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function where($column, $value = null)
+ {
+ $this->addFilter(Filter::where($column, $value));
+ return $this;
+ }
+
+ /**
+ * Add an additional filter expression to this query
+ *
+ * This notifies the repository about each required filter column.
+ *
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function applyFilter(Filter $filter)
+ {
+ return $this->addFilter($filter);
+ }
+
+ /**
+ * Set a filter for this query
+ *
+ * This notifies the repository about each required filter column.
+ *
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function setFilter(Filter $filter)
+ {
+ $this->query->setFilter($this->repository->requireFilter($this->target, $filter, $this));
+ return $this;
+ }
+
+ /**
+ * Add an additional filter expression to this query
+ *
+ * This notifies the repository about each required filter column.
+ *
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function addFilter(Filter $filter)
+ {
+ $this->query->addFilter($this->repository->requireFilter($this->target, $filter, $this));
+ return $this;
+ }
+
+ /**
+ * Return the current filter
+ *
+ * @return Filter
+ */
+ public function getFilter()
+ {
+ return $this->query->getFilter();
+ }
+
+ /**
+ * Return the sort rules being applied on this query
+ *
+ * @return array
+ */
+ public function getSortRules()
+ {
+ return $this->repository->getSortRules($this->target);
+ }
+
+ /**
+ * Add a sort rule for this query
+ *
+ * If called without a specific column, the repository's defaul sort rules will be applied.
+ * This notifies the repository about each column being required as filter column.
+ *
+ * @param string $field The name of the column by which to sort the query's result
+ * @param string $direction The direction to use when sorting (asc or desc, default is asc)
+ * @param bool $ignoreDefault Whether to ignore any default sort rules if $field is given
+ *
+ * @return $this
+ */
+ public function order($field = null, $direction = null, $ignoreDefault = false)
+ {
+ $sortRules = $this->getSortRules();
+ if ($field === null) {
+ // Use first available sort rule as default
+ if (empty($sortRules)) {
+ // Return early in case of no sort defaults and no given $field
+ return $this;
+ }
+
+ $sortColumns = reset($sortRules);
+ if (! array_key_exists('columns', $sortColumns)) {
+ $sortColumns['columns'] = array(key($sortRules));
+ }
+ if ($direction !== null || !array_key_exists('order', $sortColumns)) {
+ $sortColumns['order'] = $direction ?: static::SORT_ASC;
+ }
+ } else {
+ $alias = $this->repository->reassembleQueryColumnAlias($this->target, $field) ?: $field;
+ if (! $ignoreDefault && array_key_exists($alias, $sortRules)) {
+ $sortColumns = $sortRules[$alias];
+ if (! array_key_exists('columns', $sortColumns)) {
+ $sortColumns['columns'] = array($alias);
+ }
+ if ($direction !== null || !array_key_exists('order', $sortColumns)) {
+ $sortColumns['order'] = $direction ?: static::SORT_ASC;
+ }
+ } else {
+ $sortColumns = array(
+ 'columns' => array($alias),
+ 'order' => $direction
+ );
+ }
+ }
+
+ $baseDirection = isset($sortColumns['order']) && strtoupper($sortColumns['order']) === static::SORT_DESC
+ ? static::SORT_DESC
+ : static::SORT_ASC;
+
+ foreach ($sortColumns['columns'] as $column) {
+ list($column, $specificDirection) = $this->splitOrder($column);
+
+ if ($this->hasLimit() && $this->repository->providesValueConversion($this->target, $column)) {
+ Logger::debug(
+ 'Cannot order by column "%s" in repository "%s". The query is'
+ . ' limited and applies value conversion rules on the column',
+ $column,
+ $this->repository->getName()
+ );
+ continue;
+ }
+
+ try {
+ $this->query->order(
+ $this->repository->requireFilterColumn($this->target, $column, $this),
+ $specificDirection ?: $baseDirection
+ // I would have liked the following solution, but hey, a coder should be allowed to produce crap...
+ // $specificDirection && (! $direction || $column !== $field) ? $specificDirection : $baseDirection
+ );
+ } catch (QueryException $_) {
+ Logger::info('Cannot order by column "%s" in repository "%s"', $column, $this->repository->getName());
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Extract and return the name and direction of the given sort column definition
+ *
+ * @param string $field
+ *
+ * @return array An array of two items: $columnName, $direction
+ */
+ protected function splitOrder($field)
+ {
+ $columnAndDirection = explode(' ', $field, 2);
+ if (count($columnAndDirection) === 1) {
+ $column = $field;
+ $direction = null;
+ } else {
+ $column = $columnAndDirection[0];
+ $direction = strtoupper($columnAndDirection[1]) === static::SORT_DESC
+ ? static::SORT_DESC
+ : static::SORT_ASC;
+ }
+
+ return array($column, $direction);
+ }
+
+ /**
+ * Return whether any sort rules were applied to this query
+ *
+ * @return bool
+ */
+ public function hasOrder()
+ {
+ return $this->query->hasOrder();
+ }
+
+ /**
+ * Return the sort rules applied to this query
+ *
+ * @return array
+ */
+ public function getOrder()
+ {
+ return $this->query->getOrder();
+ }
+
+ /**
+ * Set whether this query should peek ahead for more results
+ *
+ * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will
+ * be removed from the result set. Note that this only applies when fetching multiple results of limited queries.
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function peekAhead($state = true)
+ {
+ $this->query->peekAhead($state);
+ return $this;
+ }
+
+ /**
+ * Return whether this query did not yield all available results
+ *
+ * @return bool
+ */
+ public function hasMore()
+ {
+ return $this->query->hasMore();
+ }
+
+ /**
+ * Return whether this query will or has yielded any result
+ *
+ * @return bool
+ */
+ public function hasResult()
+ {
+ return $this->query->hasResult();
+ }
+
+ /**
+ * Limit this query's results
+ *
+ * @param int $count When to stop returning results
+ * @param int $offset When to start returning results
+ *
+ * @return $this
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->query->limit($count, $offset);
+ return $this;
+ }
+
+ /**
+ * Return whether this query does not return all available entries from its result
+ *
+ * @return bool
+ */
+ public function hasLimit()
+ {
+ return $this->query->hasLimit();
+ }
+
+ /**
+ * Return the limit when to stop returning results
+ *
+ * @return int
+ */
+ public function getLimit()
+ {
+ return $this->query->getLimit();
+ }
+
+ /**
+ * Return whether this query does not start returning results at the very first entry
+ *
+ * @return bool
+ */
+ public function hasOffset()
+ {
+ return $this->query->hasOffset();
+ }
+
+ /**
+ * Return the offset when to start returning results
+ *
+ * @return int
+ */
+ public function getOffset()
+ {
+ return $this->query->getOffset();
+ }
+
+ /**
+ * Fetch and return the first column of this query's first row
+ *
+ * @return mixed|false False in case of no result
+ */
+ public function fetchOne()
+ {
+ if (! $this->hasOrder()) {
+ $this->order();
+ }
+
+ $result = $this->query->fetchOne();
+ if ($result !== false && $this->repository->providesValueConversion($this->target)) {
+ $columns = $this->getColumns();
+ $column = isset($columns[0]) ? $columns[0] : $this->getNativeAlias(key($columns));
+ return $this->repository->retrieveColumn($this->target, $column, $result, $this);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch and return the first row of this query's result
+ *
+ * @return object|false False in case of no result
+ */
+ public function fetchRow()
+ {
+ if (! $this->hasOrder()) {
+ $this->order();
+ }
+
+ $result = $this->query->fetchRow();
+ if ($result !== false && $this->repository->providesValueConversion($this->target)) {
+ foreach ($this->getColumns() as $alias => $column) {
+ if (! is_string($alias)) {
+ $alias = $column;
+ }
+
+ $result->$alias = $this->repository->retrieveColumn(
+ $this->target,
+ $this->getNativeAlias($alias),
+ $result->$alias,
+ $this
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch and return the first column of all rows of the result set as an array
+ *
+ * @return array
+ */
+ public function fetchColumn()
+ {
+ if (! $this->hasOrder()) {
+ $this->order();
+ }
+
+ $results = $this->query->fetchColumn();
+ if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
+ $columns = $this->getColumns();
+ $aliases = array_keys($columns);
+ $column = is_int($aliases[0]) ? $columns[0] : $this->getNativeAlias($aliases[0]);
+ if ($this->repository->providesValueConversion($this->target, $column)) {
+ foreach ($results as & $value) {
+ $value = $this->repository->retrieveColumn($this->target, $column, $value, $this);
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Fetch and return all rows of this query's result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
+ *
+ * @return array
+ */
+ public function fetchPairs()
+ {
+ if (! $this->hasOrder()) {
+ $this->order();
+ }
+
+ $results = $this->query->fetchPairs();
+ if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
+ $columns = $this->getColumns();
+ $aliases = array_keys($columns);
+ $colOne = $aliases[0] !== 0 ? $this->getNativeAlias($aliases[0]) : $columns[0];
+ $colTwo = count($aliases) < 2 ? $colOne : (
+ $aliases[1] !== 1 ? $this->getNativeAlias($aliases[1]) : $columns[1]
+ );
+
+ if ($this->repository->providesValueConversion($this->target, $colOne)
+ || $this->repository->providesValueConversion($this->target, $colTwo)
+ ) {
+ $newResults = array();
+ foreach ($results as $colOneValue => $colTwoValue) {
+ $colOneValue = $this->repository->retrieveColumn($this->target, $colOne, $colOneValue, $this);
+ $newResults[$colOneValue] = $this->repository->retrieveColumn(
+ $this->target,
+ $colTwo,
+ $colTwoValue,
+ $this
+ );
+ }
+
+ $results = $newResults;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Fetch and return all results of this query
+ *
+ * @return array
+ */
+ public function fetchAll()
+ {
+ if (! $this->hasOrder()) {
+ $this->order();
+ }
+
+ $results = $this->query->fetchAll();
+ if (! empty($results) && $this->repository->providesValueConversion($this->target)) {
+ $updateOrder = false;
+ $columns = $this->getColumns();
+ $flippedColumns = array_flip($columns);
+ foreach ($results as $row) {
+ foreach ($columns as $alias => $column) {
+ if (! is_string($alias)) {
+ $alias = $column;
+ }
+
+ $row->$alias = $this->repository->retrieveColumn(
+ $this->target,
+ $this->getNativeAlias($alias),
+ $row->$alias,
+ $this
+ );
+ }
+
+ foreach (($this->getOrder() ?: array()) as $rule) {
+ $nativeAlias = $this->getNativeAlias($rule[0]);
+ if (! array_key_exists($rule[0], $flippedColumns) && property_exists($row, $rule[0])) {
+ if ($this->repository->providesValueConversion($this->target, $nativeAlias)) {
+ $updateOrder = true;
+ $row->{$rule[0]} = $this->repository->retrieveColumn(
+ $this->target,
+ $nativeAlias,
+ $row->{$rule[0]},
+ $this
+ );
+ }
+ } elseif (array_key_exists($rule[0], $flippedColumns)) {
+ if ($this->repository->providesValueConversion($this->target, $nativeAlias)) {
+ $updateOrder = true;
+ }
+ }
+ }
+ }
+
+ if ($updateOrder) {
+ uasort($results, array($this->query, 'compare'));
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Count all results of this query
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return $this->query->count();
+ }
+
+ /**
+ * Return the current position of this query's iterator
+ *
+ * @return int
+ */
+ public function getIteratorPosition()
+ {
+ return $this->query->getIteratorPosition();
+ }
+
+ /**
+ * Start or rewind the iteration
+ */
+ public function rewind(): void
+ {
+ if ($this->iterator === null) {
+ if (! $this->hasOrder()) {
+ $this->order();
+ }
+
+ if ($this->query instanceof Traversable) {
+ $iterator = $this->query;
+ } else {
+ $iterator = $this->repository->getDataSource($this->target)->query($this->query);
+ }
+
+ if ($iterator instanceof IteratorAggregate) {
+ $this->iterator = $iterator->getIterator();
+ } else {
+ $this->iterator = $iterator;
+ }
+ }
+
+ $this->iterator->rewind();
+ Benchmark::measure('Query result iteration started');
+ }
+
+ /**
+ * Fetch and return the current row of this query's result
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ $row = $this->iterator->current();
+ if ($this->repository->providesValueConversion($this->target)) {
+ foreach ($this->getColumns() as $alias => $column) {
+ if (! is_string($alias)) {
+ $alias = $column;
+ }
+
+ $row->$alias = $this->repository->retrieveColumn(
+ $this->target,
+ $this->getNativeAlias($alias),
+ $row->$alias,
+ $this
+ );
+ }
+ }
+
+ return $row;
+ }
+
+ /**
+ * Return whether the current row of this query's result is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ if (! $this->iterator->valid()) {
+ Benchmark::measure('Query result iteration finished');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return the key for the current row of this query's result
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->iterator->key();
+ }
+
+ /**
+ * Advance to the next row of this query's result
+ */
+ public function next(): void
+ {
+ $this->iterator->next();
+ }
+}
diff --git a/library/Icinga/Security/SecurityException.php b/library/Icinga/Security/SecurityException.php
new file mode 100644
index 0000000..861dcf1
--- /dev/null
+++ b/library/Icinga/Security/SecurityException.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Security;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown when a caller does not have the permissions required to access a resource
+ */
+class SecurityException extends IcingaException
+{
+}
diff --git a/library/Icinga/Test/BaseTestCase.php b/library/Icinga/Test/BaseTestCase.php
new file mode 100644
index 0000000..6702df0
--- /dev/null
+++ b/library/Icinga/Test/BaseTestCase.php
@@ -0,0 +1,361 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace {
+
+ if (!function_exists('t')) {
+ function t()
+ {
+ return func_get_arg(0);
+ }
+ }
+
+ if (!function_exists('mt')) {
+ function mt()
+ {
+ return func_get_arg(0);
+ }
+ }
+}
+
+namespace Icinga\Test {
+
+ use Exception;
+ use ipl\I18n\NoopTranslator;
+ use ipl\I18n\StaticTranslator;
+ use RuntimeException;
+ use Mockery;
+ use Icinga\Application\Icinga;
+ use Icinga\Data\ConfigObject;
+ use Icinga\Data\ResourceFactory;
+ use Icinga\Data\Db\DbConnection;
+
+ /**
+ * Class BaseTestCase
+ */
+ abstract class BaseTestCase extends Mockery\Adapter\Phpunit\MockeryTestCase implements DbTest
+ {
+ /**
+ * Path to application/
+ *
+ * @var string
+ */
+ public static $appDir;
+
+ /**
+ * Path to library/Icinga
+ *
+ * @var string
+ */
+ public static $libDir;
+
+ /**
+ * Path to etc/
+ *
+ * @var
+ */
+ public static $etcDir;
+
+ /**
+ * Path to test/php/
+ *
+ * @var string
+ */
+ public static $testDir;
+
+ /**
+ * Path to share/icinga2-web
+ *
+ * @var string
+ */
+ public static $shareDir;
+
+ /**
+ * Path to modules/
+ *
+ * @var string
+ */
+ public static $moduleDir;
+
+ /**
+ * Resource configuration for different database types
+ *
+ * @var array
+ */
+ protected static $dbConfiguration = array(
+ 'mysql' => array(
+ 'type' => 'db',
+ 'db' => 'mysql',
+ 'host' => '127.0.0.1',
+ 'port' => 3306,
+ 'dbname' => 'icinga_unittest',
+ 'username' => 'icinga_unittest',
+ 'password' => 'icinga_unittest'
+ ),
+ 'pgsql' => array(
+ 'type' => 'db',
+ 'db' => 'pgsql',
+ 'host' => '127.0.0.1',
+ 'port' => 5432,
+ 'dbname' => 'icinga_unittest',
+ 'username' => 'icinga_unittest',
+ 'password' => 'icinga_unittest'
+ ),
+ );
+
+ /**
+ * Setup the default timezone
+ */
+ public static function setupTimezone()
+ {
+ date_default_timezone_set('UTC');
+ }
+
+ /**
+ * Setup test path environment
+ *
+ * @throws RuntimeException
+ */
+ public static function setupDirectories()
+ {
+ $baseDir = getenv('ICINGAWEB_BASEDIR') ?: realpath(__DIR__ . '/../../../');
+ if ($baseDir === false) {
+ throw new RuntimeException('Application base dir not found');
+ }
+
+ $libDir = getenv('ICINGAWEB_ICINGA_LIB') ?: realpath($baseDir . '/library/Icinga');
+ if ($libDir === false) {
+ throw new RuntimeException('Icinga library dir not found');
+ }
+
+ self::$appDir = $baseDir . '/application';
+ self::$libDir = $libDir;
+ self::$etcDir = $baseDir . '/etc';
+ self::$testDir = $baseDir . '/test/php';
+ self::$shareDir = $baseDir . '/share/icinga2-web';
+ self::$moduleDir = getenv('ICINGAWEB_MODULES_DIR') ?: $baseDir . '/modules';
+ }
+
+ /**
+ * Setup MVC bootstrapping and ensure that the Icinga-Mock gets reinitialized
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ StaticTranslator::$instance = new NoopTranslator();
+ $this->setupIcingaMock();
+ }
+
+ /**
+ * Setup mock object for the application's bootstrap
+ *
+ * @return Mockery\Mock
+ */
+ protected function setupIcingaMock()
+ {
+ $requestMock = Mockery::mock('Icinga\Web\Request')->shouldDeferMissing();
+ $requestMock->shouldReceive('getPathInfo')->andReturn('')->byDefault()
+ ->shouldReceive('getBaseUrl')->andReturn('/')->byDefault()
+ ->shouldReceive('getQuery')->andReturn(array())->byDefault()
+ ->shouldReceive('getParam')->with(Mockery::type('string'), Mockery::type('string'))
+ ->andReturnUsing(function ($name, $default) {
+ return $default;
+ })->byDefault();
+
+ $responseMock = Mockery::mock('Icinga\Web\Response')->shouldDeferMissing();
+ // Can't express this as demeter chains. See: https://github.com/padraic/mockery/issues/59
+ $bootstrapMock = Mockery::mock('Icinga\Application\ApplicationBootstrap')->shouldDeferMissing();
+ $libDir = dirname(self::$libDir);
+ $bootstrapMock->shouldReceive('getFrontController')->andReturn($bootstrapMock)
+ ->shouldReceive('getApplicationDir')->andReturn(self::$appDir)
+ ->shouldReceive('getLibraryDir')->andReturnUsing(function ($subdir = null) use ($libDir) {
+ if ($subdir !== null) {
+ $libDir .= '/' . ltrim($subdir, '/');
+ }
+ return $libDir;
+ })
+ ->shouldReceive('getRequest')->andReturn($requestMock)
+ ->shouldReceive('getResponse')->andReturn($responseMock);
+
+ Icinga::setApp($bootstrapMock, true);
+ return $bootstrapMock;
+ }
+
+ /**
+ * Return the currently active request mock object
+ *
+ * @return Icinga\Web\Request
+ */
+ public function getRequestMock()
+ {
+ return Icinga::app()->getRequest();
+ }
+
+ /**
+ * Return the currently active response mock object
+ *
+ * @return Icinga\Web\Response
+ */
+ public function getResponseMock()
+ {
+ return Icinga::app()->getFrontController()->getResponse();
+ }
+
+ /**
+ * Create Config for database configuration
+ *
+ * @param string $name
+ *
+ * @return ConfigObject
+ * @throws RuntimeException
+ */
+ protected function createDbConfigFor($name)
+ {
+ if (array_key_exists($name, self::$dbConfiguration)) {
+ $config = new ConfigObject(self::$dbConfiguration[$name]);
+
+ $host = getenv(sprintf('ICINGAWEB_TEST_%s_HOST', strtoupper($name)));
+ if ($host) {
+ $config['host'] = $host;
+ }
+
+ $port = getenv(sprintf('ICINGAWEB_TEST_%s_PORT', strtoupper($name)));
+ if ($port) {
+ $config['port'] = $port;
+ }
+
+ return $config;
+ }
+
+ throw new RuntimeException('Configuration for database type not available: ' . $name);
+ }
+
+ /**
+ * Creates an array of Icinga\Data\Db\DbConnection
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ protected function createDbConnectionFor($name)
+ {
+ try {
+ $conn = ResourceFactory::createResource($this->createDbConfigFor($name));
+ } catch (Exception $e) {
+ $conn = $e->getMessage();
+ }
+
+ return array(
+ array($conn)
+ );
+ }
+
+ /**
+ * PHPUnit provider for mysql
+ *
+ * @return DbConnection
+ */
+ public function mysqlDb()
+ {
+ return $this->createDbConnectionFor('mysql');
+ }
+
+ /**
+ * PHPUnit provider for pgsql
+ *
+ * @return DbConnection
+ */
+ public function pgsqlDb()
+ {
+ return $this->createDbConnectionFor('pgsql');
+ }
+
+ /**
+ * PHPUnit provider for oracle
+ *
+ * @return DbConnection
+ */
+ public function oracleDb()
+ {
+ return $this->createDbConnectionFor('oracle');
+ }
+
+ /**
+ * Executes sql file by using the database connection
+ *
+ * @param DbConnection $resource
+ * @param string $filename
+ *
+ * @throws RuntimeException
+ */
+ public function loadSql(DbConnection $resource, $filename)
+ {
+ if (!is_file($filename)) {
+ throw new RuntimeException(
+ 'Sql file not found: ' . $filename . ' (test=' . $this->getName() . ')'
+ );
+ }
+
+ $sqlData = file_get_contents($filename);
+
+ if (!$sqlData) {
+ throw new RuntimeException(
+ 'Sql file is empty: ' . $filename . ' (test=' . $this->getName() . ')'
+ );
+ }
+
+ $resource->getDbAdapter()->exec($sqlData);
+ }
+
+ /**
+ * Setup provider for testcase
+ *
+ * @param string|DbConnection|null $resource
+ */
+ public function setupDbProvider($resource)
+ {
+ if (!$resource instanceof DbConnection) {
+ if (is_string($resource)) {
+ $this->markTestSkipped('Could not initialize provider: ' . $resource);
+ } else {
+ $this->markTestSkipped('Could not initialize provider');
+ }
+ return;
+ }
+
+ $adapter = $resource->getDbAdapter();
+
+ try {
+ $adapter->getConnection();
+ } catch (Exception $e) {
+ $this->markTestSkipped('Could not connect to provider: '. $e->getMessage());
+ }
+
+ $tables = $adapter->listTables();
+ foreach ($tables as $table) {
+ $adapter->exec('DROP TABLE ' . $table . ';');
+ }
+ }
+
+ /**
+ * Add assertMatchesRegularExpression() method for phpunit >= 8.0 < 9.0 for compatibility with PHP 7.2.
+ *
+ * @TODO Remove once PHP 7.2 support is not needed for testing anymore.
+ */
+ public static function assertMatchesRegularExpression(
+ string $pattern,
+ string $string,
+ string $message = ''
+ ): void {
+ if (method_exists(parent::class, 'assertMatchesRegularExpression')) {
+ parent::assertMatchesRegularExpression($pattern, $string, $message);
+ } else {
+ static::assertRegExp($pattern, $string, $message);
+ }
+ }
+ }
+
+ BaseTestCase::setupTimezone();
+ BaseTestCase::setupDirectories();
+}
diff --git a/library/Icinga/Test/ClassLoader.php b/library/Icinga/Test/ClassLoader.php
new file mode 100644
index 0000000..af90a7e
--- /dev/null
+++ b/library/Icinga/Test/ClassLoader.php
@@ -0,0 +1,113 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Test;
+
+/**
+ * PSR-4 class loader
+ */
+class ClassLoader
+{
+ /**
+ * Namespace separator
+ */
+ const NAMESPACE_SEPARATOR = '\\';
+
+ /**
+ * Namespaces
+ *
+ * @var array
+ */
+ private $namespaces = array();
+
+ /**
+ * Register a base directory for a namespace prefix
+ *
+ * @param string $namespace
+ * @param string $directory
+ *
+ * @return $this
+ */
+ public function registerNamespace($namespace, $directory)
+ {
+ $this->namespaces[$namespace] = $directory;
+
+ return $this;
+ }
+
+ /**
+ * Test whether a namespace exists
+ *
+ * @param string $namespace
+ *
+ * @return bool
+ */
+ public function hasNamespace($namespace)
+ {
+ return array_key_exists($namespace, $this->namespaces);
+ }
+
+ /**
+ * Get the source file of the given class or interface
+ *
+ * @param string $class Name of the class or interface
+ *
+ * @return string|null
+ */
+ public function getSourceFile($class)
+ {
+ foreach ($this->namespaces as $namespace => $dir) {
+ if ($class === strstr($class, $namespace)) {
+ $classPath = str_replace(
+ self::NAMESPACE_SEPARATOR,
+ DIRECTORY_SEPARATOR,
+ substr($class, strlen($namespace))
+ ) . '.php';
+ if (file_exists($file = $dir . $classPath)) {
+ return $file;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Load the given class or interface
+ *
+ * @param string $class Name of the class or interface
+ *
+ * @return bool Whether the class or interface has been loaded
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->getSourceFile($class)) {
+ require $file;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Register {@link loadClass()} as an autoloader
+ */
+ public function register()
+ {
+ spl_autoload_register(array($this, 'loadClass'));
+ }
+
+ /**
+ * Unregister {@link loadClass()} as an autoloader
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Unregister this as an autoloader
+ */
+ public function __destruct()
+ {
+ $this->unregister();
+ }
+}
diff --git a/library/Icinga/Test/DbTest.php b/library/Icinga/Test/DbTest.php
new file mode 100644
index 0000000..d1b1ff0
--- /dev/null
+++ b/library/Icinga/Test/DbTest.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Test;
+
+use Icinga\Data\Db\DbConnection;
+
+interface DbTest
+{
+ /**
+ * PHPUnit provider for mysql
+ *
+ * @return DbConnection
+ */
+ public function mysqlDb();
+
+ /**
+ * PHPUnit provider for pgsql
+ *
+ * @return DbConnection
+ */
+ public function pgsqlDb();
+
+ /**
+ * PHPUnit provider for oracle
+ *
+ * @return DbConnection
+ */
+ public function oracleDb();
+
+ /**
+ * Executes sql file on PDO object
+ *
+ * @param DbConnection $resource
+ * @param string $filename
+ *
+ * @return boolean Operational success flag
+ */
+ public function loadSql(DbConnection $resource, $filename);
+
+ /**
+ * Setup provider for testcase
+ *
+ * @param string|DbConnection|null $resource
+ */
+ public function setupDbProvider($resource);
+}
diff --git a/library/Icinga/User.php b/library/Icinga/User.php
new file mode 100644
index 0000000..8610dd0
--- /dev/null
+++ b/library/Icinga/User.php
@@ -0,0 +1,649 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga;
+
+use DateTimeZone;
+use Icinga\Authentication\AdmissionLoader;
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Authentication\Role;
+use Icinga\Exception\ProgrammingError;
+use Icinga\User\Preferences;
+use Icinga\Web\Navigation\Navigation;
+
+/**
+ * This class represents an authorized user
+ *
+ * You can retrieve authorization information (@TODO: Not implemented yet) or user information
+ */
+class User
+{
+ /**
+ * Firstname
+ *
+ * @var string
+ */
+ protected $firstname;
+
+ /**
+ * Lastname
+ *
+ * @var string
+ */
+ protected $lastname;
+
+ /**
+ * Users email address
+ *
+ * @var string
+ */
+ protected $email;
+
+ /**
+ * {@link username} without {@link domain}
+ *
+ * @var string
+ */
+ protected $localUsername;
+
+ /**
+ * Domain
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * More information about this user
+ *
+ * @var array
+ */
+ protected $additionalInformation = array();
+
+ /**
+ * Information if the user is externally authenticated
+ *
+ * Keys:
+ *
+ * 0: origin username
+ * 1: origin field name
+ *
+ * @var array
+ */
+ protected $externalUserInformation = array();
+
+ /**
+ * Whether restrictions should not apply to this user
+ *
+ * @var bool
+ */
+ protected $unrestricted = false;
+
+ /**
+ * Set of permissions
+ *
+ * @var array
+ */
+ protected $permissions = array();
+
+ /**
+ * Set of restrictions
+ *
+ * @var array
+ */
+ protected $restrictions = array();
+
+ /**
+ * Groups for this user
+ *
+ * @var array
+ */
+ protected $groups = array();
+
+ /**
+ * Roles of this user
+ *
+ * @var Role[]
+ */
+ protected $roles = array();
+
+ /**
+ * Preferences object
+ *
+ * @var Preferences
+ */
+ protected $preferences;
+
+ /**
+ * Whether the user is authenticated using a HTTP authentication mechanism
+ *
+ * @var bool
+ */
+ protected $isHttpUser = false;
+
+ /**
+ * Creates a user object given the provided information
+ *
+ * @param string $username
+ * @param string $firstname
+ * @param string $lastname
+ * @param string $email
+ */
+ public function __construct($username, $firstname = null, $lastname = null, $email = null)
+ {
+ $this->setUsername($username);
+
+ if ($firstname !== null) {
+ $this->setFirstname($firstname);
+ }
+
+ if ($lastname !== null) {
+ $this->setLastname($lastname);
+ }
+
+ if ($email !== null) {
+ $this->setEmail($email);
+ }
+ }
+
+ /**
+ * Setter for preferences
+ *
+ * @param Preferences $preferences
+ *
+ * @return $this
+ */
+ public function setPreferences(Preferences $preferences)
+ {
+ $this->preferences = $preferences;
+ return $this;
+ }
+
+ /**
+ * Getter for preferences
+ *
+ * @return Preferences
+ */
+ public function getPreferences()
+ {
+ if ($this->preferences === null) {
+ $this->preferences = new Preferences();
+ }
+
+ return $this->preferences;
+ }
+
+ /**
+ * Return all groups this user belongs to
+ *
+ * @return array
+ */
+ public function getGroups()
+ {
+ return $this->groups;
+ }
+
+ /**
+ * Set the groups this user belongs to
+ *
+ * @param array $groups
+ *
+ * @return $this
+ */
+ public function setGroups(array $groups)
+ {
+ $this->groups = $groups;
+ return $this;
+ }
+
+ /**
+ * Return true if the user is a member of this group
+ *
+ * @param string $group
+ *
+ * @return boolean
+ */
+ public function isMemberOf($group)
+ {
+ return in_array($group, $this->groups);
+ }
+
+ /**
+ * Get whether restrictions should not apply to this user
+ *
+ * @return bool
+ */
+ public function isUnrestricted()
+ {
+ return $this->unrestricted;
+ }
+
+ /**
+ * Set whether restrictions should not apply to this user
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setIsUnrestricted($state)
+ {
+ $this->unrestricted = (bool) $state;
+
+ return $this;
+ }
+
+ /**
+ * Get the user's permissions
+ *
+ * @return array
+ */
+ public function getPermissions()
+ {
+ return $this->permissions;
+ }
+
+ /**
+ * Set the user's permissions
+ *
+ * @param array $permissions
+ *
+ * @return $this
+ */
+ public function setPermissions(array $permissions)
+ {
+ if (! empty($permissions)) {
+ natcasesort($permissions);
+ $this->permissions = array_combine($permissions, $permissions);
+ }
+ return $this;
+ }
+
+ /**
+ * Return restriction information for this user
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ public function getRestrictions($name)
+ {
+ if (array_key_exists($name, $this->restrictions)) {
+ return $this->restrictions[$name];
+ }
+
+ return array();
+ }
+
+ /**
+ * Set the user's restrictions
+ *
+ * @param string[] $restrictions
+ *
+ * @return $this
+ */
+ public function setRestrictions(array $restrictions)
+ {
+ $this->restrictions = $restrictions;
+ return $this;
+ }
+
+ /**
+ * Get the roles of the user
+ *
+ * @return Role[]
+ */
+ public function getRoles()
+ {
+ return $this->roles;
+ }
+
+ /**
+ * Set the roles of the user
+ *
+ * @param Role[] $roles
+ *
+ * @return $this
+ */
+ public function setRoles(array $roles)
+ {
+ $this->roles = $roles;
+ return $this;
+ }
+
+ /**
+ * Getter for username
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->domain === null ? $this->localUsername : $this->localUsername . '@' . $this->domain;
+ }
+
+ /**
+ * Setter for username
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setUsername($name)
+ {
+ $parts = explode('\\', $name, 2);
+ if (count($parts) === 2) {
+ list($this->domain, $this->localUsername) = $parts;
+ } else {
+ $parts = explode('@', $name, 2);
+ if (count($parts) === 2) {
+ list($this->localUsername, $this->domain) = $parts;
+ } else {
+ $this->localUsername = $name;
+ $this->domain = null;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Getter for firstname
+ *
+ * @return string
+ */
+ public function getFirstname()
+ {
+ return $this->firstname;
+ }
+
+ /**
+ * Setter for firstname
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setFirstname($name)
+ {
+ $this->firstname = $name;
+ return $this;
+ }
+
+ /**
+ * Getter for lastname
+ *
+ * @return string
+ */
+ public function getLastname()
+ {
+ return $this->lastname;
+ }
+
+ /**
+ * Setter for lastname
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setLastname($name)
+ {
+ $this->lastname = $name;
+ return $this;
+ }
+
+ /**
+ * Getter for email
+ *
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ /**
+ * Setter for mail
+ *
+ * @param string $mail
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException When an invalid mail is provided
+ */
+ public function setEmail($mail)
+ {
+ if ($mail !== null && !filter_var($mail, FILTER_VALIDATE_EMAIL)) {
+ throw new InvalidArgumentException(
+ sprintf('Invalid mail given for user %s: %s', $this->getUsername(), $mail)
+ );
+ }
+
+ $this->email = $mail;
+ return $this;
+ }
+
+ /**
+ * Set the domain
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setDomain($domain)
+ {
+ if ($domain && ($domain = trim($domain))) {
+ $this->domain = $domain;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get whether the user has a domain
+ *
+ * @return bool
+ */
+ public function hasDomain()
+ {
+ return $this->domain !== null;
+ }
+
+ /**
+ * Get the domain
+ *
+ * @return string
+ *
+ * @throws ProgrammingError If the user does not have a domain
+ */
+ public function getDomain()
+ {
+ if ($this->domain === null) {
+ throw new ProgrammingError(
+ 'User does not have a domain.'
+ . ' Use User::hasDomain() to check whether the user has a domain beforehand.'
+ );
+ }
+ return $this->domain;
+ }
+
+ /**
+ * Get the local username, ie. the username without its domain
+ *
+ * @return string
+ */
+ public function getLocalUsername()
+ {
+ return $this->localUsername;
+ }
+
+ /**
+ * Set additional information about user
+ *
+ * @param string $key
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setAdditional($key, $value)
+ {
+ $this->additionalInformation[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Getter for additional information
+ *
+ * @param string $key
+ * @return mixed|null
+ */
+ public function getAdditional($key)
+ {
+ if (isset($this->additionalInformation[$key])) {
+ return $this->additionalInformation[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the user's timezone
+ *
+ * If the user did not set a timezone, the default timezone set via config.ini is returned
+ *
+ * @return DateTimeZone
+ */
+ public function getTimeZone()
+ {
+ $tz = $this->preferences->get('timezone');
+ if ($tz === null) {
+ $tz = date_default_timezone_get();
+ }
+
+ return new DateTimeZone($tz);
+ }
+
+ /**
+ * Set additional external user information
+ *
+ * @param string $username
+ * @param string $field
+ *
+ * @return $this
+ */
+ public function setExternalUserInformation($username, $field)
+ {
+ $this->externalUserInformation = array($username, $field);
+ return $this;
+ }
+
+ /**
+ * Get additional external user information
+ *
+ * @return array
+ */
+ public function getExternalUserInformation()
+ {
+ return $this->externalUserInformation;
+ }
+
+ /**
+ * Return true if user has external user information set
+ *
+ * @return bool
+ */
+ public function isExternalUser()
+ {
+ return ! empty($this->externalUserInformation);
+ }
+
+ /**
+ * Get whether the user is authenticated using a HTTP authentication mechanism
+ *
+ * @return bool
+ */
+ public function getIsHttpUser()
+ {
+ return $this->isHttpUser;
+ }
+
+ /**
+ * Set whether the user is authenticated using a HTTP authentication mechanism
+ *
+ * @param bool $isHttpUser
+ *
+ * @return $this
+ */
+ public function setIsHttpUser($isHttpUser = true)
+ {
+ $this->isHttpUser = (bool) $isHttpUser;
+ return $this;
+ }
+
+ /**
+ * Whether the user has a given permission
+ *
+ * @param string $requiredPermission
+ *
+ * @return bool
+ */
+ public function can($requiredPermission)
+ {
+ list($permissions, $refusals) = AdmissionLoader::migrateLegacyPermissions([$requiredPermission]);
+ if (! empty($permissions)) {
+ $requiredPermission = array_pop($permissions);
+ } elseif (! empty($refusals)) {
+ throw new InvalidArgumentException(
+ 'Refusals are not supported anymore. Check for a grant instead!'
+ );
+ }
+
+ $granted = false;
+ foreach ($this->getRoles() as $role) {
+ if ($role->denies($requiredPermission)) {
+ return false;
+ }
+
+ if (! $granted && $role->grants($requiredPermission)) {
+ $granted = true;
+ }
+ }
+
+ return $granted;
+ }
+
+ /**
+ * Load and return this user's configured navigation of the given type
+ *
+ * @param string $type
+ *
+ * @return Navigation
+ */
+ public function getNavigation($type)
+ {
+ $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type, $this->getUsername());
+
+ if ($type === 'dashboard-pane') {
+ $panes = array();
+ foreach ($config as $dashletName => $dashletConfig) {
+ // TODO: Throw ConfigurationError if pane or url is missing
+ $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
+ }
+
+ $navigation = new Navigation();
+ foreach ($panes as $paneName => $dashlets) {
+ $navigation->addItem(
+ $paneName,
+ array(
+ 'type' => 'dashboard-pane',
+ 'dashlets' => $dashlets
+ )
+ );
+ }
+ } else {
+ $navigation = Navigation::fromConfig($config);
+ }
+
+ return $navigation;
+ }
+}
diff --git a/library/Icinga/User/Preferences.php b/library/Icinga/User/Preferences.php
new file mode 100644
index 0000000..b09462b
--- /dev/null
+++ b/library/Icinga/User/Preferences.php
@@ -0,0 +1,169 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\User;
+
+use Countable;
+
+/**
+ * User preferences container
+ *
+ * Usage example:
+ * <code>
+ * <?php
+ *
+ * use Icinga\User\Preferences;
+ *
+ * $preferences = new Preferences(); // Start with empty preferences
+ *
+ * $preferences = new Preferences(array('aPreference' => 'value')); // Start with initial preferences
+ *
+ * $preferences->aNewPreference = 'value'; // Set a preference
+ *
+ * unset($preferences->aPreference); // Unset a preference
+ *
+ * // Retrieve a preference and return a default value if the preference does not exist
+ * $anotherPreference = $preferences->get('anotherPreference', 'defaultValue');
+ */
+class Preferences implements Countable
+{
+ /**
+ * Preferences key-value array
+ *
+ * @var array
+ */
+ protected $preferences = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $preferences Preferences key-value array
+ */
+ public function __construct(array $preferences = array())
+ {
+ $this->preferences = $preferences;
+ }
+
+ /**
+ * Count all preferences
+ *
+ * @return int The number of preferences
+ */
+ public function count(): int
+ {
+ return count($this->preferences);
+ }
+
+ /**
+ * Determine whether a preference exists
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->preferences);
+ }
+
+ /**
+ * Write data to a preference
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value)
+ {
+ $this->preferences[$name] = $value;
+ }
+
+ /**
+ * Retrieve a preference section
+ *
+ * @param string $name
+ *
+ * @return array|null
+ */
+ public function get($name)
+ {
+ if (array_key_exists($name, $this->preferences)) {
+ return $this->preferences[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve a value from a specific section
+ *
+ * @param string $section
+ * @param string $name
+ * @param null $default
+ *
+ * @return array|null
+ */
+ public function getValue($section, $name, $default = null)
+ {
+ if (array_key_exists($section, $this->preferences)
+ && array_key_exists($name, $this->preferences[$section])
+ ) {
+ return $this->preferences[$section][$name];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Magic method so that $obj->value will work.
+ *
+ * @param string $name
+ *
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Remove a given preference
+ *
+ * @param string $name Preference name
+ */
+ public function remove($name)
+ {
+ unset($this->preferences[$name]);
+ }
+
+ /**
+ * Determine if a preference is set and is not NULL
+ *
+ * @param string $name Preference name
+ *
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return isset($this->preferences[$name]);
+ }
+
+ /**
+ * Unset a given preference
+ *
+ * @param string $name Preference name
+ */
+ public function __unset($name)
+ {
+ $this->remove($name);
+ }
+
+ /**
+ * Get preferences as array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->preferences;
+ }
+}
diff --git a/library/Icinga/User/Preferences/PreferencesStore.php b/library/Icinga/User/Preferences/PreferencesStore.php
new file mode 100644
index 0000000..8ecc677
--- /dev/null
+++ b/library/Icinga/User/Preferences/PreferencesStore.php
@@ -0,0 +1,344 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\User\Preferences;
+
+use Exception;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\NotWritableError;
+use Icinga\User;
+use Icinga\User\Preferences;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Zend_Db_Expr;
+
+/**
+ * Preferences store factory
+ *
+ * Load and save user preferences by using a database
+ *
+ * Usage example:
+ * <code>
+ * <?php
+ *
+ * use Icinga\Data\ConfigObject;
+ * use Icinga\User\Preferences;
+ * use Icinga\User\Preferences\PreferencesStore;
+ *
+ * // Create a db store
+ * $store = PreferencesStore::create(
+ * new ConfigObject(
+ * 'resource' => 'resource name'
+ * ),
+ * $user // Instance of \Icinga\User
+ * );
+ *
+ * $preferences = new Preferences($store->load());
+ * $preferences->aPreference = 'value';
+ * $store->save($preferences);
+ * </code>
+ */
+class PreferencesStore
+{
+ /**
+ * Column name for username
+ */
+ const COLUMN_USERNAME = 'username';
+
+ /**
+ * Column name for section
+ */
+ const COLUMN_SECTION = 'section';
+
+ /**
+ * Column name for preference
+ */
+ const COLUMN_PREFERENCE = 'name';
+
+ /**
+ * Column name for value
+ */
+ const COLUMN_VALUE = 'value';
+
+ /**
+ * Column name for created time
+ */
+ const COLUMN_CREATED_TIME = 'ctime';
+
+ /**
+ * Column name for modified time
+ */
+ const COLUMN_MODIFIED_TIME = 'mtime';
+
+ /**
+ * Table name
+ *
+ * @var string
+ */
+ protected $table = 'icingaweb_user_preference';
+
+ /**
+ * Stored preferences
+ *
+ * @var array
+ */
+ protected $preferences = [];
+
+ /**
+ * Store config
+ *
+ * @var ConfigObject
+ */
+ protected $config;
+
+ /**
+ * Given user
+ *
+ * @var User
+ */
+ protected $user;
+
+ /**
+ * Create a new store
+ *
+ * @param ConfigObject $config The config for this adapter
+ * @param User $user The user to which these preferences belong
+ */
+ public function __construct(ConfigObject $config, User $user)
+ {
+ $this->config = $config;
+ $this->user = $user;
+ $this->init();
+ }
+
+ /**
+ * Getter for the store config
+ *
+ * @return ConfigObject
+ */
+ public function getStoreConfig(): ConfigObject
+ {
+ return $this->config;
+ }
+
+ /**
+ * Getter for the user
+ *
+ * @return User
+ */
+ public function getUser(): User
+ {
+ return $this->user;
+ }
+
+ /**
+ * Initialize the store
+ */
+ protected function init(): void
+ {
+ }
+
+ /**
+ * Load preferences from the database
+ *
+ * @return array
+ *
+ * @throws NotReadableError In case the database operation failed
+ */
+ public function load(): array
+ {
+ try {
+ $select = $this->getStoreConfig()->connection->getDbAdapter()->select();
+ $result = $select
+ ->from($this->table, [self::COLUMN_SECTION, self::COLUMN_PREFERENCE, self::COLUMN_VALUE])
+ ->where(self::COLUMN_USERNAME . ' = ?', $this->getUser()->getUsername())
+ ->query()
+ ->fetchAll();
+ } catch (Exception $e) {
+ throw new NotReadableError(
+ 'Cannot fetch preferences for user %s from database',
+ $this->getUser()->getUsername(),
+ $e
+ );
+ }
+
+ if ($result !== false) {
+ $values = [];
+ foreach ($result as $row) {
+ $values[$row->{self::COLUMN_SECTION}][$row->{self::COLUMN_PREFERENCE}] = $row->{self::COLUMN_VALUE};
+ }
+
+ $this->preferences = $values;
+ }
+
+ return $this->preferences;
+ }
+
+ /**
+ * Save the given preferences in the database
+ *
+ * @param Preferences $preferences The preferences to save
+ */
+ public function save(Preferences $preferences): void
+ {
+ $preferences = $preferences->toArray();
+
+ $sections = array_keys($preferences);
+
+ foreach ($sections as $section) {
+ if (! array_key_exists($section, $this->preferences)) {
+ $this->preferences[$section] = [];
+ }
+
+ if (! array_key_exists($section, $preferences)) {
+ $preferences[$section] = [];
+ }
+
+ $toBeInserted = array_diff_key($preferences[$section], $this->preferences[$section]);
+ if (!empty($toBeInserted)) {
+ $this->insert($toBeInserted, $section);
+ }
+
+ $toBeUpdated = array_intersect_key(
+ array_diff_assoc($preferences[$section], $this->preferences[$section]),
+ array_diff_assoc($this->preferences[$section], $preferences[$section])
+ );
+
+ if (!empty($toBeUpdated)) {
+ $this->update($toBeUpdated, $section);
+ }
+
+ $toBeDeleted = array_keys(array_diff_key($this->preferences[$section], $preferences[$section]));
+ if (!empty($toBeDeleted)) {
+ $this->delete($toBeDeleted, $section);
+ }
+ }
+ }
+
+ /**
+ * Insert the given preferences into the database
+ *
+ * @param array $preferences The preferences to insert
+ * @param string $section The preferences in section to update
+ *
+ * @throws NotWritableError In case the database operation failed
+ */
+ protected function insert(array $preferences, string $section): void
+ {
+ /** @var \Zend_Db_Adapter_Abstract $db */
+ $db = $this->getStoreConfig()->connection->getDbAdapter();
+
+ try {
+ foreach ($preferences as $key => $value) {
+ $db->insert(
+ $this->table,
+ [
+ self::COLUMN_USERNAME => $this->getUser()->getUsername(),
+ $db->quoteIdentifier(self::COLUMN_SECTION) => $section,
+ $db->quoteIdentifier(self::COLUMN_PREFERENCE) => $key,
+ self::COLUMN_VALUE => $value,
+ self::COLUMN_CREATED_TIME => new Zend_Db_Expr('NOW()'),
+ self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
+ ]
+ );
+ }
+ } catch (Exception $e) {
+ throw new NotWritableError(
+ 'Cannot insert preferences for user %s into database',
+ $this->getUser()->getUsername(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Update the given preferences in the database
+ *
+ * @param array $preferences The preferences to update
+ * @param string $section The preferences in section to update
+ *
+ * @throws NotWritableError In case the database operation failed
+ */
+ protected function update(array $preferences, string $section): void
+ {
+ /** @var \Zend_Db_Adapter_Abstract $db */
+ $db = $this->getStoreConfig()->connection->getDbAdapter();
+
+ try {
+ foreach ($preferences as $key => $value) {
+ $db->update(
+ $this->table,
+ [
+ self::COLUMN_VALUE => $value,
+ self::COLUMN_MODIFIED_TIME => new Zend_Db_Expr('NOW()')
+ ],
+ [
+ self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
+ $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
+ $db->quoteIdentifier(self::COLUMN_PREFERENCE) . '=?' => $key
+ ]
+ );
+ }
+ } catch (Exception $e) {
+ throw new NotWritableError(
+ 'Cannot update preferences for user %s in database',
+ $this->getUser()->getUsername(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Delete the given preference names from the database
+ *
+ * @param array $preferenceKeys The preference names to delete
+ * @param string $section The preferences in section to update
+ *
+ * @throws NotWritableError In case the database operation failed
+ */
+ protected function delete(array $preferenceKeys, string $section): void
+ {
+ /** @var \Zend_Db_Adapter_Abstract $db */
+ $db = $this->getStoreConfig()->connection->getDbAdapter();
+
+ try {
+ $db->delete(
+ $this->table,
+ [
+ self::COLUMN_USERNAME . '=?' => $this->getUser()->getUsername(),
+ $db->quoteIdentifier(self::COLUMN_SECTION) . '=?' => $section,
+ $db->quoteIdentifier(self::COLUMN_PREFERENCE) . ' IN (?)' => $preferenceKeys
+ ]
+ );
+ } catch (Exception $e) {
+ throw new NotWritableError(
+ 'Cannot delete preferences for user %s from database',
+ $this->getUser()->getUsername(),
+ $e
+ );
+ }
+ }
+
+ /**
+ * Create preferences storage adapter from config
+ *
+ * @param ConfigObject $config The config for the adapter
+ * @param User $user The user to which these preferences belong
+ *
+ * @return self
+ *
+ * @throws ConfigurationError When the configuration defines an invalid storage type
+ */
+ public static function create(ConfigObject $config, User $user): self
+ {
+ $resourceConfig = ResourceFactory::getResourceConfig($config->resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ $config->connection = ResourceFactory::createResource($resourceConfig);
+
+ return new self($config, $user);
+ }
+}
diff --git a/library/Icinga/Util/ASN1.php b/library/Icinga/Util/ASN1.php
new file mode 100644
index 0000000..9e00258
--- /dev/null
+++ b/library/Icinga/Util/ASN1.php
@@ -0,0 +1,102 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use DateInterval;
+use DateTime;
+use InvalidArgumentException;
+
+/**
+ * Parsers for ASN.1 types
+ */
+class ASN1
+{
+ /**
+ * Parse the given value based on the "3.3.13. Generalized Time" syntax as specified by IETF RFC 4517
+ *
+ * @param string $value
+ *
+ * @return DateTime
+ *
+ * @throws InvalidArgumentException
+ *
+ * @see https://tools.ietf.org/html/rfc4517#section-3.3.13
+ */
+ public static function parseGeneralizedTime($value)
+ {
+ $generalizedTimePattern = <<<EOD
+/\A
+ (?P<YmdH>
+ [0-9]{4} # century year
+ (?:0[1-9]|1[0-2]) # month
+ (?:0[1-9]|[12][0-9]|3[0-1]) # day
+ (?:[01][0-9]|2[0-3]) # hour
+ )
+ (?:
+ (?P<i>[0-5][0-9]) # minute
+ (?P<s>[0-5][0-9]|60)? # second or leap-second
+ )?
+ (?:[.,](?P<frac>[0-9]+))? # fraction
+ (?P<tz> # g-time-zone
+ Z
+ |
+ [-+]
+ (?:[01][0-9]|2[0-3]) # hour
+ (?:[0-5][0-9])? # minute
+ )
+\z/x
+EOD;
+
+ $matches = array();
+
+ if (preg_match($generalizedTimePattern, $value, $matches)) {
+ $dateTimeRaw = $matches['YmdH'];
+ $dateTimeFormat = 'YmdH';
+
+ if ($matches['i'] !== '') {
+ $dateTimeRaw .= $matches['i'];
+ $dateTimeFormat .= 'i';
+
+ if ($matches['s'] !== '') {
+ $dateTimeRaw .= $matches['s'];
+ $dateTimeFormat .= 's';
+ $fractionOfSeconds = 1;
+ } else {
+ $fractionOfSeconds = 60;
+ }
+ } else {
+ $fractionOfSeconds = 3600;
+ }
+
+ $dateTimeFormat .= 'O';
+
+ if ($matches['tz'] === 'Z') {
+ $dateTimeRaw .= '+0000';
+ } else {
+ $dateTimeRaw .= $matches['tz'];
+
+ if (strlen($matches['tz']) === 3) {
+ $dateTimeRaw .= '00';
+ }
+ }
+
+ $dateTime = DateTime::createFromFormat($dateTimeFormat, $dateTimeRaw);
+
+ if ($dateTime !== false) {
+ if (isset($matches['frac'])) {
+ $dateTime->add(new DateInterval(
+ 'PT' . round((float) ('0.' . $matches['frac']) * $fractionOfSeconds) . 'S'
+ ));
+ }
+
+ return $dateTime;
+ }
+ }
+
+ throw new InvalidArgumentException(sprintf(
+ 'Failed to parse %s based on the ASN.1 standard (GeneralizedTime)',
+ var_export($value, true)
+ ));
+ }
+}
diff --git a/library/Icinga/Util/Color.php b/library/Icinga/Util/Color.php
new file mode 100644
index 0000000..cf88f41
--- /dev/null
+++ b/library/Icinga/Util/Color.php
@@ -0,0 +1,121 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+/**
+ * Provide functions to change and convert colors.
+ */
+class Color
+{
+ /**
+ * Convert a given color string to an rgb-array containing
+ * each color as a decimal value.
+ *
+ * @param $color The color-string #RRGGBB
+ *
+ * @return array The converted rgb-array.
+ */
+ public static function rgbAsArray($color)
+ {
+ if (substr($color, 0, 1) !== '#') {
+ $color = '#' . $color;
+ }
+ if (strlen($color) !== 7) {
+ return;
+ }
+ $r = (float)intval(substr($color, 1, 2), 16);
+ $g = (float)intval(substr($color, 3, 2), 16);
+ $b = (float)intval(substr($color, 5, 2), 16);
+ return array($r, $g, $b);
+ }
+
+ /**
+ * Convert a rgb array to a color-string
+ *
+ * @param array $rgb The rgb-array
+ *
+ * @return string The color string #RRGGBB
+ */
+ public static function arrayToRgb(array $rgb)
+ {
+ $r = (string)dechex($rgb[0]);
+ $g = (string)dechex($rgb[1]);
+ $b = (string)dechex($rgb[2]);
+ return '#'
+ . (strlen($r) > 1 ? $r : '0' . $r)
+ . (strlen($g) > 1 ? $g : '0' . $g)
+ . (strlen($b) > 1 ? $b : '0' . $b);
+ }
+
+ /**
+ * Change the saturation for a given color.
+ *
+ * @param $color string The color to change
+ * @param $change float The change.
+ * 0.0 creates a black-and-white image.
+ * 0.5 reduces the color saturation by half.
+ * 1.0 causes no change.
+ * 2.0 doubles the color saturation.
+ * @return string
+ */
+ public static function changeSaturation($color, $change)
+ {
+ return self::arrayToRgb(self::changeRgbSaturation(self::rgbAsArray($color), $change));
+ }
+
+ /**
+ * Change the brightness for a given color
+ *
+ * @param $color string The color to change
+ * @param $change float The change in percent
+ *
+ * @return string
+ */
+ public static function changeBrightness($color, $change)
+ {
+ return self::arrayToRgb(self::changeRgbBrightness(self::rgbAsArray($color), $change));
+ }
+
+ /**
+ * @param $rgb array The rgb-array to change
+ * @param $change float The factor
+ *
+ * @return array The updated rgb-array
+ */
+ private static function changeRgbSaturation(array $rgb, $change)
+ {
+ $pr = 0.499; // 0.299
+ $pg = 0.387; // 0.587
+ $pb = 0.114; // 0.114
+ $r = $rgb[0];
+ $g = $rgb[1];
+ $b = $rgb[2];
+ $p = sqrt(
+ $r * $r * $pr +
+ $g * $g * $pg +
+ $b * $b * $pb
+ );
+ $rgb[0] = (int)($p + ($r - $p) * $change);
+ $rgb[1] = (int)($p + ($g - $p) * $change);
+ $rgb[2] = (int)($p + ($b - $p) * $change);
+ return $rgb;
+ }
+
+ /**
+ * @param $rgb array The rgb-array to change
+ * @param $change float The factor
+ *
+ * @return array The updated rgb-array
+ */
+ private static function changeRgbBrightness(array $rgb, $change)
+ {
+ $red = $rgb[0] + ($rgb[0] * $change);
+ $green = $rgb[1] + ($rgb[1] * $change);
+ $blue = $rgb[2] + ($rgb[2] * $change);
+ $rgb[0] = $red < 255 ? (int) $red : 255;
+ $rgb[1] = $green < 255 ? (int) $green : 255;
+ $rgb[2] = $blue < 255 ? (int) $blue : 255;
+ return $rgb;
+ }
+}
diff --git a/library/Icinga/Util/ConfigAwareFactory.php b/library/Icinga/Util/ConfigAwareFactory.php
new file mode 100644
index 0000000..133887a
--- /dev/null
+++ b/library/Icinga/Util/ConfigAwareFactory.php
@@ -0,0 +1,18 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+/**
+ * Interface defining a factory which is configured at runtime
+ */
+interface ConfigAwareFactory
+{
+ /**
+ * Set the factory's config
+ *
+ * @param mixed $config
+ * @throws \Icinga\Exception\ConfigurationError if the given config is not valid
+ */
+ public static function setConfig($config);
+}
diff --git a/library/Icinga/Util/Dimension.php b/library/Icinga/Util/Dimension.php
new file mode 100644
index 0000000..6860fd8
--- /dev/null
+++ b/library/Icinga/Util/Dimension.php
@@ -0,0 +1,123 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+class Dimension
+{
+ /**
+ * Defines this dimension as nr of pixels
+ */
+ const UNIT_PX = "px";
+
+ /**
+ * Defines this dimension as width of 'M' in current font
+ */
+ const UNIT_EM = "em";
+
+ /**
+ * Defines this dimension as a percentage value
+ */
+ const UNIT_PERCENT = "%";
+
+ /**
+ * Defines this dimension in points
+ */
+ const UNIT_PT = "pt";
+
+ /**
+ * The current set value for this dimension
+ *
+ * @var int
+ */
+ private $value = 0;
+
+ /**
+ * The unit to interpret the value with
+ *
+ * @var string
+ */
+ private $unit = self::UNIT_PX;
+
+ /**
+ * Create a new Dimension object with the given size and unit
+ *
+ * @param int $value The new value
+ * @param string $unit The unit to use (default: px)
+ */
+ public function __construct($value, $unit = self::UNIT_PX)
+ {
+ $this->setValue($value, $unit);
+ }
+
+ /**
+ * Change the value and unit of this dimension
+ *
+ * @param int $value The new value
+ * @param string $unit The unit to use (default: px)
+ */
+ public function setValue($value, $unit = self::UNIT_PX)
+ {
+ $this->value = intval($value);
+ $this->unit = $unit;
+ }
+
+ /**
+ * Return true when the value is > 0
+ *
+ * @return bool
+ */
+ public function isDefined()
+ {
+ return $this->value > 0;
+ }
+
+ /**
+ * Return the underlying value without unit information
+ *
+ * @return int
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Return the unit used for the value
+ *
+ * @return string
+ */
+ public function getUnit()
+ {
+ return $this->unit;
+ }
+
+ /**
+ * Return this value with it's according unit as a string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if (!$this->isDefined()) {
+ return "";
+ }
+ return $this->value.$this->unit;
+ }
+
+ /**
+ * Create a new Dimension object from a string containing the numeric value and the dimension (e.g. 200px, 20%)
+ *
+ * @param $string The string to parse
+ *
+ * @return Dimension
+ */
+ public static function fromString($string)
+ {
+ $matches = array();
+ if (!preg_match_all('/^ *([0-9]+)(px|pt|em|\%) */i', $string, $matches)) {
+ return new Dimension(0);
+ }
+ return new Dimension(intval($matches[1][0]), $matches[2][0]);
+ }
+}
diff --git a/library/Icinga/Util/DirectoryIterator.php b/library/Icinga/Util/DirectoryIterator.php
new file mode 100644
index 0000000..a2bed1c
--- /dev/null
+++ b/library/Icinga/Util/DirectoryIterator.php
@@ -0,0 +1,213 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use ArrayIterator;
+use InvalidArgumentException;
+use RecursiveIterator;
+
+/**
+ * Iterator for traversing a directory
+ */
+class DirectoryIterator implements RecursiveIterator
+{
+ /**
+ * Iterate files first
+ *
+ * @var int
+ */
+ const FILES_FIRST = 1;
+
+ /**
+ * Current directory item
+ *
+ * @var string|false
+ */
+ private $current;
+
+ /**
+ * The file extension to filter for
+ *
+ * @var string
+ */
+ protected $extension;
+
+ /**
+ * Scanned files
+ *
+ * @var ArrayIterator
+ */
+ private $files;
+
+ /**
+ * Iterator flags
+ *
+ * @var int
+ */
+ protected $flags;
+
+ /**
+ * Current key
+ *
+ * @var string|false
+ */
+ private $key;
+
+ /**
+ * The path of the directory to traverse
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * Directory queue if FILES_FIRST flag is set
+ *
+ * @var array
+ */
+ private $queue;
+
+ /**
+ * Whether to skip empty files
+ *
+ * Defaults to true.
+ *
+ * @var bool
+ */
+ protected $skipEmpty = true;
+
+ /**
+ * Whether to skip hidden files
+ *
+ * Defaults to true.
+ *
+ * @var bool
+ */
+ protected $skipHidden = true;
+
+ /**
+ * Create a new directory iterator from path
+ *
+ * The given path will not be validated whether it is readable. Use {@link isReadable()} before creating a new
+ * directory iterator instance.
+ *
+ * @param string $path The path of the directory to traverse
+ * @param string $extension The file extension to filter for. A leading dot is optional
+ * @param int $flags Iterator flags
+ */
+ public function __construct($path, $extension = null, $flags = null)
+ {
+ if (empty($path)) {
+ throw new InvalidArgumentException('The path can\'t be empty');
+ }
+ $this->path = $path;
+ if (! empty($extension)) {
+ $this->extension = '.' . ltrim($extension, '.');
+ }
+ if ($flags !== null) {
+ $this->flags = $flags;
+ }
+ }
+
+ /**
+ * Check whether the given path is a directory and is readable
+ *
+ * @param string $path The path of the directory
+ *
+ * @return bool
+ */
+ public static function isReadable($path)
+ {
+ return is_dir($path) && is_readable($path);
+ }
+
+ public function hasChildren(): bool
+ {
+ return static::isReadable($this->current);
+ }
+
+ public function getChildren(): DirectoryIterator
+ {
+ return new static($this->current, $this->extension, $this->flags);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return $this->current;
+ }
+
+ public function next(): void
+ {
+ do {
+ $this->files->next();
+ $skip = false;
+ if (! $this->files->valid()) {
+ $file = false;
+ $path = false;
+ break;
+ } else {
+ $file = $this->files->current();
+ do {
+ if ($this->skipHidden && $file[0] === '.') {
+ $skip = true;
+ break;
+ }
+
+ $path = $this->path . '/' . $file;
+
+ if (is_dir($path)) {
+ if ($this->flags & static::FILES_FIRST === static::FILES_FIRST) {
+ $this->queue[] = array($path, $file);
+ $skip = true;
+ }
+ break;
+ }
+
+ if ($this->skipEmpty && ! filesize($path)) {
+ $skip = true;
+ break;
+ }
+
+ if ($this->extension && ! StringHelper::endsWith($file, $this->extension)) {
+ $skip = true;
+ break;
+ }
+ } while (0);
+ }
+ } while ($skip);
+
+ /** @noinspection PhpUndefinedVariableInspection */
+
+ if ($path === false && ! empty($this->queue)) {
+ list($path, $file) = array_shift($this->queue);
+ }
+
+ $this->current = $path;
+ $this->key = $file;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->key;
+ }
+
+ public function valid(): bool
+ {
+ return $this->current !== false;
+ }
+
+ public function rewind(): void
+ {
+ if ($this->files === null) {
+ $files = scandir($this->path);
+ natcasesort($files);
+ $this->files = new ArrayIterator($files);
+ }
+ $this->files->rewind();
+ $this->queue = array();
+ $this->next();
+ }
+}
diff --git a/library/Icinga/Util/EnumeratingFilterIterator.php b/library/Icinga/Util/EnumeratingFilterIterator.php
new file mode 100644
index 0000000..0659961
--- /dev/null
+++ b/library/Icinga/Util/EnumeratingFilterIterator.php
@@ -0,0 +1,30 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use FilterIterator;
+
+/**
+ * Class EnumeratingFilterIterator
+ *
+ * FilterIterator with continuous numeric key (index)
+ */
+abstract class EnumeratingFilterIterator extends FilterIterator
+{
+ /**
+ * @var int
+ */
+ private $index;
+
+ public function rewind(): void
+ {
+ parent::rewind();
+ $this->index = 0;
+ }
+
+ public function key(): int
+ {
+ return $this->index++;
+ }
+}
diff --git a/library/Icinga/Util/Environment.php b/library/Icinga/Util/Environment.php
new file mode 100644
index 0000000..8d47b84
--- /dev/null
+++ b/library/Icinga/Util/Environment.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+/**
+ * Helper for configuring the PHP environment
+ */
+class Environment
+{
+ /**
+ * Raise the PHP memory_limit
+ *
+ * Unless it is not already set to a higher limit
+ *
+ * @param string|int $minimum
+ */
+ public static function raiseMemoryLimit($minimum = '512M')
+ {
+ if (is_string($minimum)) {
+ $minimum = Format::unpackShorthandBytes($minimum);
+ }
+
+ if (Format::unpackShorthandBytes(ini_get('memory_limit')) < $minimum) {
+ ini_set('memory_limit', $minimum);
+ }
+ }
+
+ /**
+ * Raise the PHP max_execution_time
+ *
+ * Unless it is not already configured to a higher value.
+ *
+ * @param int $minimum
+ */
+ public static function raiseExecutionTime($minimum = 300)
+ {
+ if ((int) ini_get('max_execution_time') < $minimum) {
+ ini_set('max_execution_time', $minimum);
+ }
+ }
+}
diff --git a/library/Icinga/Util/File.php b/library/Icinga/Util/File.php
new file mode 100644
index 0000000..dad332a
--- /dev/null
+++ b/library/Icinga/Util/File.php
@@ -0,0 +1,195 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use SplFileObject;
+use ErrorException;
+use RuntimeException;
+use Icinga\Exception\NotWritableError;
+
+/**
+ * File
+ *
+ * A class to ease opening files and reading/writing to them.
+ */
+class File extends SplFileObject
+{
+ /**
+ * The mode used to open the file
+ *
+ * @var string
+ */
+ protected $openMode;
+
+ /**
+ * The access mode to use when creating directories
+ *
+ * @var int
+ */
+ public static $dirMode = 1528; // 2770
+
+ /**
+ * @see SplFileObject::__construct()
+ */
+ public function __construct($filename, $openMode = 'r', $useIncludePath = false, $context = null)
+ {
+ $this->openMode = $openMode;
+ if ($context === null) {
+ parent::__construct($filename, $openMode, $useIncludePath);
+ } else {
+ parent::__construct($filename, $openMode, $useIncludePath, $context);
+ }
+ }
+
+ /**
+ * Create a file using the given access mode and return a instance of File open for writing
+ *
+ * @param string $path The path to the file
+ * @param int $accessMode The access mode to set
+ * @param bool $recursive Whether missing nested directories of the given path should be created
+ *
+ * @return File
+ *
+ * @throws RuntimeException In case the file cannot be created or the access mode cannot be set
+ * @throws NotWritableError In case the path's (existing) parent is not writable
+ */
+ public static function create($path, $accessMode, $recursive = true)
+ {
+ $dirPath = dirname($path);
+ if ($recursive && !is_dir($dirPath)) {
+ static::createDirectories($dirPath);
+ } elseif (! is_writable($dirPath)) {
+ throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath));
+ }
+
+ $file = new static($path, 'x+');
+
+ if (! @chmod($path, $accessMode)) {
+ $error = error_get_last();
+ throw new RuntimeException(sprintf(
+ 'Cannot set access mode "%s" on file "%s" (%s)',
+ decoct($accessMode),
+ $path,
+ $error['message']
+ ));
+ }
+
+ return $file;
+ }
+
+ /**
+ * Create missing directories
+ *
+ * @param string $path
+ *
+ * @throws RuntimeException In case a directory cannot be created or the access mode cannot be set
+ */
+ protected static function createDirectories($path)
+ {
+ $part = strpos($path, DIRECTORY_SEPARATOR) === 0 ? DIRECTORY_SEPARATOR : '';
+ foreach (explode(DIRECTORY_SEPARATOR, ltrim($path, DIRECTORY_SEPARATOR)) as $dir) {
+ $part .= $dir . DIRECTORY_SEPARATOR;
+
+ if (! is_dir($part)) {
+ if (! @mkdir($part, static::$dirMode)) {
+ $error = error_get_last();
+ throw new RuntimeException(sprintf(
+ 'Failed to create missing directory "%s" (%s)',
+ $part,
+ $error['message']
+ ));
+ }
+
+ if (! @chmod($part, static::$dirMode)) {
+ $error = error_get_last();
+ throw new RuntimeException(sprintf(
+ 'Failed to set access mode "%s" for directory "%s" (%s)',
+ decoct(static::$dirMode),
+ $part,
+ $error['message']
+ ));
+ }
+ }
+ }
+ }
+
+ #[\ReturnTypeWillChange]
+ public function fwrite($str, $length = null)
+ {
+ $this->assertOpenForWriting();
+ $this->setupErrorHandler();
+ $retVal = $length === null ? parent::fwrite($str) : parent::fwrite($str, $length);
+ restore_error_handler();
+ return $retVal;
+ }
+
+ public function ftruncate($size): bool
+ {
+ $this->assertOpenForWriting();
+ $this->setupErrorHandler();
+ $retVal = parent::ftruncate($size);
+ restore_error_handler();
+ return $retVal;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function ftell()
+ {
+ $this->setupErrorHandler();
+ $retVal = parent::ftell();
+ restore_error_handler();
+ return $retVal;
+ }
+
+ public function flock($operation, &$wouldblock = null): bool
+ {
+ $this->setupErrorHandler();
+ $retVal = parent::flock($operation, $wouldblock);
+ restore_error_handler();
+ return $retVal;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function fgetc()
+ {
+ $this->setupErrorHandler();
+ $retVal = parent::fgetc();
+ restore_error_handler();
+ return $retVal;
+ }
+
+ public function fflush(): bool
+ {
+ $this->setupErrorHandler();
+ $retVal = parent::fflush();
+ restore_error_handler();
+ return $retVal;
+ }
+
+ /**
+ * Setup an error handler that throws a RuntimeException for every emitted E_WARNING
+ */
+ protected function setupErrorHandler()
+ {
+ set_error_handler(
+ function ($errno, $errstr, $errfile, $errline) {
+ restore_error_handler();
+ throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
+ },
+ E_WARNING
+ );
+ }
+
+ /**
+ * Assert that the file was opened for writing and throw an exception otherwise
+ *
+ * @throws NotWritableError In case the file was not opened for writing
+ */
+ protected function assertOpenForWriting()
+ {
+ if (!preg_match('@w|a|\+@', $this->openMode)) {
+ throw new NotWritableError('File not open for writing');
+ }
+ }
+}
diff --git a/library/Icinga/Util/Format.php b/library/Icinga/Util/Format.php
new file mode 100644
index 0000000..1158208
--- /dev/null
+++ b/library/Icinga/Util/Format.php
@@ -0,0 +1,197 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use DateTime;
+
+class Format
+{
+ const STANDARD_IEC = 0;
+ const STANDARD_SI = 1;
+ protected static $instance;
+
+ protected static $bitPrefix = array(
+ array('bit', 'Kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'),
+ array('bit', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'),
+ );
+ protected static $bitBase = array(1024, 1000);
+
+ protected static $bytePrefix = array(
+ array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'),
+ array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'),
+ );
+ protected static $byteBase = array(1024, 1000);
+
+ protected static $secondPrefix = array('s', 'ms', 'µs', 'ns', 'ps', 'fs', 'as');
+ protected static $secondBase = 1000;
+
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new Format;
+ }
+ return self::$instance;
+ }
+
+ public static function bits($value, $standard = self::STANDARD_SI)
+ {
+ return self::formatForUnits(
+ $value,
+ self::$bitPrefix[$standard],
+ self::$bitBase[$standard]
+ );
+ }
+
+ public static function bytes($value, $standard = self::STANDARD_IEC)
+ {
+ return self::formatForUnits(
+ $value,
+ self::$bytePrefix[$standard],
+ self::$byteBase[$standard]
+ );
+ }
+
+ public static function seconds($value)
+ {
+ if ($value === null) {
+ return '';
+ }
+
+ $absValue = abs($value);
+
+ if ($absValue < 60) {
+ return self::formatForUnits($value, self::$secondPrefix, self::$secondBase);
+ } elseif ($absValue < 3600) {
+ return sprintf('%0.2f m', $value / 60);
+ } elseif ($absValue < 86400) {
+ return sprintf('%0.2f h', $value / 3600);
+ }
+
+ // TODO: Do we need weeks, months and years?
+ return sprintf('%0.2f d', $value / 86400);
+ }
+
+ protected static function formatForUnits($value, &$units, $base)
+ {
+ if ($value === null) {
+ return '';
+ }
+
+ $sign = '';
+ if ($value < 0) {
+ $value = abs($value);
+ $sign = '-';
+ }
+
+ if ($value == 0) {
+ $pow = $result = 0;
+ } else {
+ $pow = floor(log($value, $base));
+ $result = $value / pow($base, $pow);
+ }
+
+ // 1034.23 looks better than 1.03, but 2.03 is fine:
+ if ($pow > 0 && $result < 2) {
+ $result = $value / pow($base, --$pow);
+ }
+
+ return sprintf(
+ '%s%0.2f %s',
+ $sign,
+ $result,
+ $units[abs($pow)]
+ );
+ }
+
+ /**
+ * Return the amount of seconds based on the given month
+ *
+ * @param DateTime|int $dateTimeOrTimestamp The date and time to use
+ *
+ * @return int
+ */
+ public static function secondsByMonth($dateTimeOrTimestamp)
+ {
+ if ($dateTimeOrTimestamp === null) {
+ return 0;
+ }
+
+ if (!($dt = $dateTimeOrTimestamp) instanceof DateTime) {
+ $dt = new DateTime();
+ $dt->setTimestamp($dateTimeOrTimestamp);
+ }
+
+ return (int) $dt->format('t') * 24 * 3600;
+ }
+
+ /**
+ * Return the amount of seconds based on the given year
+ *
+ * @param DateTime|int $dateTimeOrTimestamp The date and time to use
+ *
+ * @return int
+ */
+ public static function secondsByYear($dateTimeOrTimestamp)
+ {
+ if ($dateTimeOrTimestamp === null) {
+ return 0;
+ }
+
+ return (self::isLeapYear($dateTimeOrTimestamp) ? 366 : 365) * 24 * 3600;
+ }
+
+ /**
+ * Return whether the given year is a leap year
+ *
+ * @param DateTime|int $dateTimeOrTimestamp The date and time to use
+ *
+ * @return bool
+ */
+ public static function isLeapYear($dateTimeOrTimestamp)
+ {
+ if ($dateTimeOrTimestamp === null) {
+ return false;
+ }
+
+ if (!($dt = $dateTimeOrTimestamp) instanceof DateTime) {
+ $dt = new DateTime();
+ $dt->setTimestamp($dateTimeOrTimestamp);
+ }
+
+ return $dt->format('L') == 1;
+ }
+
+ /**
+ * Unpack shorthand bytes PHP directives to bytes
+ *
+ * @param string $subject
+ *
+ * @return int
+ */
+ public static function unpackShorthandBytes($subject)
+ {
+ $base = (int) $subject;
+
+ if ($base <= -1) {
+ return INF;
+ }
+
+ switch (strtoupper($subject[strlen($subject) - 1])) {
+ case 'K':
+ $multiplier = 1024;
+ break;
+ case 'M':
+ $multiplier = 1024 ** 2;
+ break;
+ case 'G':
+ $multiplier = 1024 ** 3;
+ break;
+ default:
+ $multiplier = 1;
+ break;
+ }
+
+ return $base * $multiplier;
+ }
+}
diff --git a/library/Icinga/Util/GlobFilter.php b/library/Icinga/Util/GlobFilter.php
new file mode 100644
index 0000000..15cbe0b
--- /dev/null
+++ b/library/Icinga/Util/GlobFilter.php
@@ -0,0 +1,182 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use stdClass;
+
+/**
+ * GLOB-like filter for simple data structures
+ *
+ * e.g. this filters:
+ *
+ * foo.bar.baz
+ * foo.b*r.baz
+ * **.baz
+ *
+ * match this one:
+ *
+ * array(
+ * 'foo' => array(
+ * 'bar' => array(
+ * 'baz' => 'deadbeef' // <---
+ * )
+ * )
+ * )
+ */
+class GlobFilter
+{
+ /**
+ * The prepared filters
+ *
+ * @var array
+ */
+ protected $filters;
+
+ /**
+ * Create a new filter from a comma-separated list of GLOB-like filters or an array of such lists.
+ *
+ * @param string|\Traversable $filters
+ */
+ public function __construct($filters)
+ {
+ $patterns = array(array(''));
+ $lastIndex1 = $lastIndex2 = 0;
+
+ foreach ((is_string($filters) ? array($filters) : $filters) as $rawPatterns) {
+ $escape = false;
+
+ foreach (str_split($rawPatterns) as $c) {
+ if ($escape) {
+ $escape = false;
+ $patterns[$lastIndex1][$lastIndex2] .= preg_quote($c, '/');
+ } else {
+ switch ($c) {
+ case '\\':
+ $escape = true;
+ break;
+ case ',':
+ $patterns[] = array('');
+ ++$lastIndex1;
+ $lastIndex2 = 0;
+ break;
+ case '.':
+ $patterns[$lastIndex1][] = '';
+ ++$lastIndex2;
+ break;
+ case '*':
+ $patterns[$lastIndex1][$lastIndex2] .= '.*';
+ break;
+ default:
+ $patterns[$lastIndex1][$lastIndex2] .= preg_quote($c, '/');
+ }
+ }
+ }
+
+ if ($escape) {
+ $patterns[$lastIndex1][$lastIndex2] .= '\\\\';
+ }
+ }
+
+ $this->filters = array();
+
+ foreach ($patterns as $pattern) {
+ foreach ($pattern as $i => $subPattern) {
+ if ($subPattern === '') {
+ unset($pattern[$i]);
+ } elseif ($subPattern === '.*.*') {
+ $pattern[$i] = '**';
+ } elseif ($subPattern === '.*') {
+ $pattern[$i] = '/^' . $subPattern . '$/';
+ } else {
+ $pattern[$i] = '/^' . trim($subPattern) . '$/i';
+ }
+ }
+
+ if (! empty($pattern)) {
+ $found = false;
+ foreach ($pattern as $i => $v) {
+ if ($found) {
+ if ($v === '**') {
+ unset($pattern[$i]);
+ } else {
+ $found = false;
+ }
+ } elseif ($v === '**') {
+ $found = true;
+ }
+ }
+
+ if (end($pattern) === '**') {
+ $pattern[] = '/^.*$/';
+ }
+
+ $this->filters[] = array_values($pattern);
+ }
+ }
+ }
+
+ /**
+ * Remove all keys/attributes matching any of $this->filters from $dataStructure
+ *
+ * @param stdClass|array $dataStructure
+ *
+ * @return stdClass|array The modified copy of $dataStructure
+ */
+ public function removeMatching($dataStructure)
+ {
+ foreach ($this->filters as $filter) {
+ $dataStructure = static::removeMatchingRecursive($dataStructure, $filter);
+ }
+ return $dataStructure;
+ }
+
+ /**
+ * Helper method for removeMatching()
+ *
+ * @param stdClass|array $dataStructure
+ * @param array $filter
+ *
+ * @return stdClass|array
+ */
+ protected static function removeMatchingRecursive($dataStructure, $filter)
+ {
+ $multiLevelPattern = $filter[0] === '**';
+ if ($multiLevelPattern) {
+ $dataStructure = static::removeMatchingRecursive($dataStructure, array_slice($filter, 1));
+ }
+
+ $isObject = $dataStructure instanceof stdClass;
+ if ($isObject || is_array($dataStructure)) {
+ if ($isObject) {
+ $dataStructure = (array) $dataStructure;
+ }
+
+ if ($multiLevelPattern) {
+ foreach ($dataStructure as $k => & $v) {
+ $v = static::removeMatchingRecursive($v, $filter);
+ unset($v);
+ }
+ } else {
+ $currentLevel = $filter[0];
+ $nextLevels = count($filter) === 1 ? null : array_slice($filter, 1);
+ foreach ($dataStructure as $k => & $v) {
+ if (preg_match($currentLevel, (string) $k)) {
+ if ($nextLevels === null) {
+ unset($dataStructure[$k]);
+ } else {
+ $v = static::removeMatchingRecursive($v, $nextLevels);
+ }
+ }
+ unset($v);
+ }
+ }
+
+ if ($isObject) {
+ $dataStructure = (object) $dataStructure;
+ }
+ }
+
+ return $dataStructure;
+ }
+}
diff --git a/library/Icinga/Util/Json.php b/library/Icinga/Util/Json.php
new file mode 100644
index 0000000..0b89dcc
--- /dev/null
+++ b/library/Icinga/Util/Json.php
@@ -0,0 +1,151 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+use Icinga\Exception\Json\JsonDecodeException;
+use Icinga\Exception\Json\JsonEncodeException;
+
+/**
+ * Wrap {@link json_encode()} and {@link json_decode()} with error handling
+ */
+class Json
+{
+ /**
+ * {@link json_encode()} wrapper
+ *
+ * @param mixed $value
+ * @param int $options
+ * @param int $depth
+ *
+ * @return string
+ * @throws JsonEncodeException
+ */
+ public static function encode($value, $options = 0, $depth = 512)
+ {
+ return static::encodeAndSanitize($value, $options, $depth, false);
+ }
+
+ /**
+ * {@link json_encode()} wrapper, automatically sanitizes bad UTF-8
+ *
+ * @param mixed $value
+ * @param int $options
+ * @param int $depth
+ *
+ * @return string
+ * @throws JsonEncodeException
+ */
+ public static function sanitize($value, $options = 0, $depth = 512)
+ {
+ return static::encodeAndSanitize($value, $options, $depth, true);
+ }
+
+ /**
+ * {@link json_encode()} wrapper, sanitizes bad UTF-8
+ *
+ * @param mixed $value
+ * @param int $options
+ * @param int $depth
+ * @param bool $autoSanitize Automatically sanitize invalid UTF-8 (if any)
+ *
+ * @return string
+ * @throws JsonEncodeException
+ */
+ protected static function encodeAndSanitize($value, $options, $depth, $autoSanitize)
+ {
+ $encoded = json_encode($value, $options, $depth);
+
+ switch (json_last_error()) {
+ case JSON_ERROR_NONE:
+ return $encoded;
+
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case JSON_ERROR_UTF8:
+ if ($autoSanitize) {
+ return static::encode(static::sanitizeUtf8Recursive($value), $options, $depth);
+ }
+ // Fallthrough
+
+ default:
+ throw new JsonEncodeException('%s: %s', json_last_error_msg(), var_export($value, true));
+ }
+ }
+
+ /**
+ * {@link json_decode()} wrapper
+ *
+ * @param string $json
+ * @param bool $assoc
+ * @param int $depth
+ * @param int $options
+ *
+ * @return mixed
+ * @throws JsonDecodeException
+ */
+ public static function decode($json, $assoc = false, $depth = 512, $options = 0)
+ {
+ $decoded = $json ? json_decode($json, $assoc, $depth, $options) : null;
+
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ throw new JsonDecodeException('%s: %s', json_last_error_msg(), var_export($json, true));
+ }
+ return $decoded;
+ }
+
+ /**
+ * Replace bad byte sequences in UTF-8 strings inside the given JSON-encodable structure with question marks
+ *
+ * @param mixed $value
+ *
+ * @return mixed
+ */
+ protected static function sanitizeUtf8Recursive($value)
+ {
+ switch (gettype($value)) {
+ case 'string':
+ return static::sanitizeUtf8String($value);
+
+ case 'array':
+ $sanitized = array();
+
+ foreach ($value as $key => $val) {
+ if (is_string($key)) {
+ $key = static::sanitizeUtf8String($key);
+ }
+
+ $sanitized[$key] = static::sanitizeUtf8Recursive($val);
+ }
+
+ return $sanitized;
+
+ case 'object':
+ $sanitized = array();
+
+ foreach ($value as $key => $val) {
+ if (is_string($key)) {
+ $key = static::sanitizeUtf8String($key);
+ }
+
+ $sanitized[$key] = static::sanitizeUtf8Recursive($val);
+ }
+
+ return (object) $sanitized;
+
+ default:
+ return $value;
+ }
+ }
+
+ /**
+ * Replace bad byte sequences in the given UTF-8 string with question marks
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ protected static function sanitizeUtf8String($string)
+ {
+ return mb_convert_encoding($string, 'UTF-8', 'UTF-8');
+ }
+}
diff --git a/library/Icinga/Util/LessParser.php b/library/Icinga/Util/LessParser.php
new file mode 100644
index 0000000..46aa061
--- /dev/null
+++ b/library/Icinga/Util/LessParser.php
@@ -0,0 +1,17 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Util;
+
+use Icinga\Less\Visitor;
+use lessc;
+
+require_once 'lessphp/lessc.inc.php';
+
+class LessParser extends lessc
+{
+ public function __construct()
+ {
+ $this->setOption('plugins', [new Visitor()]);
+ }
+}
diff --git a/library/Icinga/Util/StringHelper.php b/library/Icinga/Util/StringHelper.php
new file mode 100644
index 0000000..67a836b
--- /dev/null
+++ b/library/Icinga/Util/StringHelper.php
@@ -0,0 +1,184 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+/**
+ * Common string functions
+ */
+class StringHelper
+{
+ /**
+ * Split string into an array and trim spaces
+ *
+ * @param string $value
+ * @param string $delimiter
+ * @param int $limit
+ *
+ * @return array
+ */
+ public static function trimSplit($value, $delimiter = ',', $limit = null)
+ {
+ if ($value === null) {
+ return [];
+ }
+
+ if ($limit !== null) {
+ $exploded = explode($delimiter, $value, $limit);
+ } else {
+ $exploded = explode($delimiter, $value);
+ }
+
+ return array_map('trim', $exploded);
+ }
+
+ /**
+ * Uppercase the first character of each word in a string
+ *
+ * Converts 'first_name' to 'FirstName' for example.
+ *
+ * @param string $name
+ * @param string $separator Word separator
+ *
+ * @return string
+ */
+ public static function cname($name, $separator = '_')
+ {
+ if ($name === null) {
+ return '';
+ }
+
+ return str_replace(' ', '', ucwords(str_replace($separator, ' ', strtolower($name))));
+ }
+
+ /**
+ * Add ellipsis when a string is longer than max length
+ *
+ * @param string $string
+ * @param int $maxLength
+ * @param string $ellipsis
+ *
+ * @return string
+ */
+ public static function ellipsis($string, $maxLength, $ellipsis = '...')
+ {
+ if ($string === null) {
+ return '';
+ }
+
+ if (strlen($string) > $maxLength) {
+ return substr($string, 0, $maxLength - strlen($ellipsis)) . $ellipsis;
+ }
+
+ return $string;
+ }
+
+ /**
+ * Add ellipsis in the center of a string when a string is longer than max length
+ *
+ * @param string $string
+ * @param int $maxLength
+ * @param string $ellipsis
+ *
+ * @return string
+ */
+ public static function ellipsisCenter($string, $maxLength, $ellipsis = '...')
+ {
+ if ($string === null) {
+ return '';
+ }
+
+ $start = ceil($maxLength / 2.0);
+ $end = floor($maxLength / 2.0);
+ if (strlen($string) > $maxLength) {
+ return substr($string, 0, $start - strlen($ellipsis)) . $ellipsis . substr($string, - $end);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Find and return all similar strings in $possibilites matching $string with the given minimum $similarity
+ *
+ * @param string $string
+ * @param array $possibilities
+ * @param float $similarity
+ *
+ * @return array
+ */
+ public static function findSimilar($string, array $possibilities, $similarity = 0.33)
+ {
+ if (empty($string)) {
+ return array();
+ }
+
+ $matches = array();
+ foreach ($possibilities as $possibility) {
+ $distance = levenshtein($string, $possibility);
+ if ($distance / strlen($string) <= $similarity) {
+ $matches[] = $possibility;
+ }
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Test whether the given string ends with the given suffix
+ *
+ * @param string $string The string to test
+ * @param string $suffix The suffix the string must end with
+ *
+ * @return bool
+ */
+ public static function endsWith($string, $suffix)
+ {
+ if ($string === null) {
+ return false;
+ }
+
+ $stringSuffix = substr($string, -strlen($suffix));
+ return $stringSuffix !== false ? $stringSuffix === $suffix : false;
+ }
+
+ /**
+ * Generates an array of strings that constitutes the cartesian product of all passed sets, with all
+ * string combinations concatenated using the passed join-operator.
+ *
+ * <pre>
+ * cartesianProduct(
+ * array(array('foo', 'bar'), array('mumble', 'grumble', null)),
+ * '_'
+ * );
+ * => array('foo_mumble', 'foo_grumble', 'bar_mumble', 'bar_grumble', 'foo', 'bar')
+ * </pre>
+ *
+ * @param array $sets An array of arrays containing all sets for which the cartesian
+ * product should be calculated.
+ * @param string $glue The glue used to join the strings, defaults to ''.
+ *
+ * @returns array The cartesian product in one array of strings.
+ */
+ public static function cartesianProduct(array $sets, $glue = '')
+ {
+ $product = null;
+ foreach ($sets as $set) {
+ if (! isset($product)) {
+ $product = $set;
+ } else {
+ $newProduct = array();
+ foreach ($product as $strA) {
+ foreach ($set as $strB) {
+ if ($strB === null) {
+ $newProduct []= $strA;
+ } else {
+ $newProduct []= $strA . $glue . $strB;
+ }
+ }
+ }
+ $product = $newProduct;
+ }
+ }
+ return $product;
+ }
+}
diff --git a/library/Icinga/Util/TimezoneDetect.php b/library/Icinga/Util/TimezoneDetect.php
new file mode 100644
index 0000000..4967c7f
--- /dev/null
+++ b/library/Icinga/Util/TimezoneDetect.php
@@ -0,0 +1,107 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Util;
+
+/**
+ * Retrieve timezone information from cookie
+ */
+class TimezoneDetect
+{
+ /**
+ * If detection was successful
+ *
+ * @var bool
+ */
+ private static $success;
+
+ /**
+ * Timezone offset in minutes
+ *
+ * @var int
+ */
+ private static $offset = 0;
+
+ /**
+ * @var string
+ */
+ private static $timezoneName;
+
+ /**
+ * Cookie name
+ *
+ * @var string
+ */
+ public static $cookieName = 'icingaweb2-tzo';
+
+ /**
+ * Timezone name
+ *
+ * @var string
+ */
+ private static $timezone;
+
+ /**
+ * Create new object and try to identify the timezone
+ */
+ public function __construct()
+ {
+ if (self::$success !== null) {
+ return;
+ }
+
+ if (array_key_exists(self::$cookieName, $_COOKIE)) {
+ $matches = array();
+ if (preg_match('/\A(-?\d+)[\-,](\d+)\z/', $_COOKIE[self::$cookieName], $matches)) {
+ $offset = $matches[1];
+ $timezoneName = timezone_name_from_abbr('', (int) $offset, (int) $matches[2]);
+
+ self::$success = (bool) $timezoneName;
+ if (self::$success) {
+ self::$offset = $offset;
+ self::$timezoneName = $timezoneName;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get offset
+ *
+ * @return int
+ */
+ public function getOffset()
+ {
+ return self::$offset;
+ }
+
+ /**
+ * Get timezone name
+ *
+ * @return string
+ */
+ public function getTimezoneName()
+ {
+ return self::$timezoneName;
+ }
+
+ /**
+ * True on success
+ *
+ * @return bool
+ */
+ public function success()
+ {
+ return self::$success;
+ }
+
+ /**
+ * Reset object
+ */
+ public function reset()
+ {
+ self::$success = null;
+ self::$timezoneName = null;
+ self::$offset = 0;
+ }
+}
diff --git a/library/Icinga/Web/Announcement.php b/library/Icinga/Web/Announcement.php
new file mode 100644
index 0000000..9835ce0
--- /dev/null
+++ b/library/Icinga/Web/Announcement.php
@@ -0,0 +1,158 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+/**
+ * An announcement to be displayed prominently in the web UI
+ */
+class Announcement
+{
+ /**
+ * @var string
+ */
+ protected $author;
+
+ /**
+ * @var string
+ */
+ protected $message;
+
+ /**
+ * @var int
+ */
+ protected $start;
+
+ /**
+ * @var int
+ */
+ protected $end;
+
+ /**
+ * Hash of the message
+ *
+ * @var string|null
+ */
+ protected $hash = null;
+
+ /**
+ * Announcement constructor
+ *
+ * @param array $properties
+ */
+ public function __construct(array $properties = array())
+ {
+ foreach ($properties as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ }
+
+ /**
+ * Get the author of the acknowledged
+ *
+ * @return string
+ */
+ public function getAuthor()
+ {
+ return $this->author;
+ }
+
+ /**
+ * Set the author of the acknowledged
+ *
+ * @param string $author
+ *
+ * @return $this
+ */
+ public function setAuthor($author)
+ {
+ $this->author = $author;
+ return $this;
+ }
+
+ /**
+ * Get the message of the acknowledged
+ *
+ * @return string
+ */
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ /**
+ * Set the message of the acknowledged
+ *
+ * @param string $message
+ *
+ * @return $this
+ */
+ public function setMessage($message)
+ {
+ $this->message = $message;
+ $this->hash = null;
+ return $this;
+ }
+
+ /**
+ * Get the start date and time of the acknowledged
+ *
+ * @return int
+ */
+ public function getStart()
+ {
+ return $this->start;
+ }
+
+ /**
+ * Set the start date and time of the acknowledged
+ *
+ * @param int $start
+ *
+ * @return $this
+ */
+ public function setStart($start)
+ {
+ $this->start = $start;
+ return $this;
+ }
+
+ /**
+ * Get the end date and time of the acknowledged
+ *
+ * @return int
+ */
+ public function getEnd()
+ {
+ return $this->end;
+ }
+
+ /**
+ * Set the end date and time of the acknowledged
+ *
+ * @param int $end
+ *
+ * @return $this
+ */
+ public function setEnd($end)
+ {
+ $this->end = $end;
+ return $this;
+ }
+
+ /**
+ * Get the hash of the acknowledgement
+ *
+ * @return string
+ */
+ public function getHash()
+ {
+ if ($this->hash === null) {
+ $this->hash = md5($this->message);
+ }
+ return $this->hash;
+ }
+}
diff --git a/library/Icinga/Web/Announcement/AnnouncementCookie.php b/library/Icinga/Web/Announcement/AnnouncementCookie.php
new file mode 100644
index 0000000..f4398ba
--- /dev/null
+++ b/library/Icinga/Web/Announcement/AnnouncementCookie.php
@@ -0,0 +1,138 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Announcement;
+
+use Icinga\Util\Json;
+use Icinga\Web\Cookie;
+
+/**
+ * Handle acknowledged announcements via cookie
+ */
+class AnnouncementCookie extends Cookie
+{
+ /**
+ * Array of hashes representing acknowledged announcements
+ *
+ * @var string[]
+ */
+ protected $acknowledged = array();
+
+ /**
+ * ETag of the last known announcements.ini
+ *
+ * @var string
+ */
+ protected $etag;
+
+ /**
+ * Timestamp of the next active acknowledgement, if any
+ *
+ * @var int|null
+ */
+ protected $nextActive;
+
+ /**
+ * AnnouncementCookie constructor
+ */
+ public function __construct()
+ {
+ parent::__construct('icingaweb2-announcements');
+ $this->setExpire(2147483648);
+ if (isset($_COOKIE['icingaweb2-announcements'])) {
+ $cookie = json_decode($_COOKIE['icingaweb2-announcements'], true);
+ if ($cookie !== null) {
+ if (isset($cookie['acknowledged'])) {
+ $this->setAcknowledged($cookie['acknowledged']);
+ }
+ if (isset($cookie['etag'])) {
+ $this->setEtag($cookie['etag']);
+ }
+ if (isset($cookie['next'])) {
+ $this->setNextActive($cookie['next']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the hashes of the acknowledged announcements
+ *
+ * @return string[]
+ */
+ public function getAcknowledged()
+ {
+ return $this->acknowledged;
+ }
+
+ /**
+ * Set the hashes of the acknowledged announcements
+ *
+ * @param string[] $acknowledged
+ *
+ * @return $this
+ */
+ public function setAcknowledged(array $acknowledged)
+ {
+ $this->acknowledged = $acknowledged;
+ return $this;
+ }
+
+ /**
+ * Get the ETag
+ *
+ * @return string
+ */
+ public function getEtag()
+ {
+ return $this->etag;
+ }
+
+ /**
+ * Set the ETag
+ *
+ * @param string $etag
+ *
+ * @return $this
+ */
+ public function setEtag($etag)
+ {
+ $this->etag = $etag;
+ return $this;
+ }
+
+ /**
+ * Get the timestamp of the next active announcement
+ *
+ * @return int
+ */
+ public function getNextActive()
+ {
+ return $this->nextActive;
+ }
+
+ /**
+ * Set the timestamp of the next active announcement
+ *
+ * @param int $nextActive
+ *
+ * @return $this
+ */
+ public function setNextActive($nextActive)
+ {
+ $this->nextActive = $nextActive;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue()
+ {
+ return Json::encode(array(
+ 'acknowledged' => $this->getAcknowledged(),
+ 'etag' => $this->getEtag(),
+ 'next' => $this->getNextActive()
+ ));
+ }
+}
diff --git a/library/Icinga/Web/Announcement/AnnouncementIniRepository.php b/library/Icinga/Web/Announcement/AnnouncementIniRepository.php
new file mode 100644
index 0000000..d972a1d
--- /dev/null
+++ b/library/Icinga/Web/Announcement/AnnouncementIniRepository.php
@@ -0,0 +1,152 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Announcement;
+
+use DateTime;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterAnd;
+use Icinga\Data\SimpleQuery;
+use Icinga\Repository\IniRepository;
+use Icinga\Web\Announcement;
+
+/**
+ * A collection of announcements stored in an INI file
+ */
+class AnnouncementIniRepository extends IniRepository
+{
+ protected $queryColumns = array('announcement' => array('id', 'author', 'message', 'hash', 'start', 'end'));
+
+ protected $triggers = array('announcement');
+
+ protected $configs = array('announcement' => array(
+ 'name' => 'announcements',
+ 'keyColumn' => 'id'
+ ));
+
+ protected $conversionRules = array('announcement' => array(
+ 'start' => 'timestamp',
+ 'end' => 'timestamp'
+ ));
+
+ /**
+ * Get a DateTime's timestamp
+ *
+ * @param DateTime $datetime
+ *
+ * @return int|null
+ */
+ protected function persistTimestamp(DateTime $datetime)
+ {
+ return $datetime === null ? null : $datetime->getTimestamp();
+ }
+
+ /**
+ * Before-insert trigger (per row)
+ *
+ * @param ConfigObject $new The original data to insert
+ *
+ * @return ConfigObject The eventually modified data to insert
+ */
+ protected function onInsertAnnouncement(ConfigObject $new)
+ {
+ if (! isset($new->id)) {
+ $new->id = uniqid();
+ }
+
+ if (! isset($new->hash)) {
+ $announcement = new Announcement($new->toArray());
+ $new->hash = $announcement->getHash();
+ }
+
+ return $new;
+ }
+
+ /**
+ * Before-update trigger (per row)
+ *
+ * @param ConfigObject $old The original data as currently stored
+ * @param ConfigObject $new The original data to update
+ *
+ * @return ConfigObject The eventually modified data to update
+ */
+ protected function onUpdateAnnouncement(ConfigObject $old, ConfigObject $new)
+ {
+ if ($new->message !== $old->message) {
+ $announcement = new Announcement($new->toArray());
+ $new->hash = $announcement->getHash();
+ }
+
+ return $new;
+ }
+
+ /**
+ * Get the ETag of the announcements.ini file
+ *
+ * @return string
+ */
+ public function getEtag()
+ {
+ $file = $this->getDataSource('announcement')->getConfigFile();
+
+ if (@is_readable($file)) {
+ $mtime = filemtime($file);
+ $size = filesize($file);
+
+ return hash('crc32', $mtime . $size);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the query for all active announcements
+ *
+ * @return SimpleQuery
+ */
+ public function findActive()
+ {
+ $now = new DateTime();
+
+ $query = $this
+ ->select(array('hash', 'message', 'start'))
+ ->setFilter(new FilterAnd(array(
+ Filter::expression('start', '<=', $now),
+ Filter::expression('end', '>=', $now)
+ )))
+ ->order('start');
+
+ return $query;
+ }
+
+ /**
+ * Get the timestamp of the next active announcement
+ *
+ * @return int|null
+ */
+ public function findNextActive()
+ {
+ $now = new DateTime();
+
+ $query = $this
+ ->select(array('start', 'end'))
+ ->setFilter(Filter::matchAny(array(
+ Filter::expression('start', '>', $now), Filter::expression('end', '>', $now)
+ )));
+
+ $refresh = null;
+
+ foreach ($query as $row) {
+ $min = min($row->start, $row->end);
+
+ if ($refresh === null) {
+ $refresh = $min;
+ } else {
+ $refresh = min($refresh, $min);
+ }
+ }
+
+ return $refresh;
+ }
+}
diff --git a/library/Icinga/Web/ApplicationStateCookie.php b/library/Icinga/Web/ApplicationStateCookie.php
new file mode 100644
index 0000000..e40c17b
--- /dev/null
+++ b/library/Icinga/Web/ApplicationStateCookie.php
@@ -0,0 +1,74 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\Json\JsonDecodeException;
+use Icinga\Util\Json;
+
+/**
+ * Handle acknowledged application state messages via cookie
+ */
+class ApplicationStateCookie extends Cookie
+{
+ /** @var array */
+ protected $acknowledgedMessages = [];
+
+ public function __construct()
+ {
+ parent::__construct('icingaweb2-application-state');
+
+ $this->setExpire(2147483648);
+
+ if (isset($_COOKIE['icingaweb2-application-state'])) {
+ try {
+ $cookie = Json::decode($_COOKIE['icingaweb2-application-state'], true);
+ } catch (JsonDecodeException $e) {
+ Logger::error(
+ "Can't decode the application state cookie of user '%s'. An error occurred: %s",
+ Auth::getInstance()->getUser()->getUsername(),
+ $e
+ );
+
+ return;
+ }
+
+ if (isset($cookie['acknowledged-messages'])) {
+ $this->setAcknowledgedMessages($cookie['acknowledged-messages']);
+ }
+ }
+ }
+
+ /**
+ * Get the acknowledged messages
+ *
+ * @return array
+ */
+ public function getAcknowledgedMessages()
+ {
+ return $this->acknowledgedMessages;
+ }
+
+ /**
+ * Set the acknowledged messages
+ *
+ * @param array $acknowledged
+ *
+ * @return $this
+ */
+ public function setAcknowledgedMessages(array $acknowledged)
+ {
+ $this->acknowledgedMessages = $acknowledged;
+
+ return $this;
+ }
+
+ public function getValue()
+ {
+ return Json::encode([
+ 'acknowledged-messages' => $this->getAcknowledgedMessages()
+ ]);
+ }
+}
diff --git a/library/Icinga/Web/Controller.php b/library/Icinga/Web/Controller.php
new file mode 100644
index 0000000..a2730d5
--- /dev/null
+++ b/library/Icinga/Web/Controller.php
@@ -0,0 +1,264 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Data\Filterable;
+use Icinga\Data\Sortable;
+use Icinga\Data\QueryInterface;
+use Icinga\Exception\Http\HttpBadRequestException;
+use Icinga\Exception\Http\HttpNotFoundException;
+use Icinga\Web\Controller\ModuleActionController;
+use Icinga\Web\Widget\Limiter;
+use Icinga\Web\Widget\Paginator;
+use Icinga\Web\Widget\SortBox;
+
+/**
+ * This is the controller all modules should inherit from
+ * We will flip code with the ModuleActionController as soon as a couple
+ * of pending feature branches are merged back to the master.
+ *
+ * @property View $view
+ */
+class Controller extends ModuleActionController
+{
+ /**
+ * Cache for page size configured via user preferences
+ *
+ * @var false|int
+ */
+ protected $userPageSize;
+
+ /**
+ * @see ActionController::init
+ */
+ public function init()
+ {
+ parent::init();
+ $this->handleSortControlSubmit();
+ }
+
+ /**
+ * Check whether the sort control has been submitted and redirect using GET parameters
+ */
+ protected function handleSortControlSubmit()
+ {
+ $request = $this->getRequest();
+ if (! $request->isPost()) {
+ return;
+ }
+
+ if (($sort = $request->getPost('sort')) || ($direction = $request->getPost('dir'))) {
+ $url = Url::fromRequest();
+ if ($sort) {
+ $url->setParam('sort', $sort);
+ $url->remove('dir');
+ } else {
+ $url->setParam('dir', $direction);
+ }
+
+ $this->redirectNow($url);
+ }
+ }
+
+ /**
+ * Immediately respond w/ HTTP 400
+ *
+ * @param string $message Exception message or exception format string
+ * @param mixed ...$arg Format string argument
+ *
+ * @throws HttpBadRequestException
+ */
+ public function httpBadRequest($message)
+ {
+ throw HttpBadRequestException::create(func_get_args());
+ }
+
+ /**
+ * Immediately respond w/ HTTP 404
+ *
+ * @param string $message Exception message or exception format string
+ * @param mixed ...$arg Format string argument
+ *
+ * @throws HttpNotFoundException
+ */
+ public function httpNotFound($message)
+ {
+ throw HttpNotFoundException::create(func_get_args());
+ }
+
+ /**
+ * Render the given form using a simple view script
+ *
+ * @param Form $form
+ * @param string $tab
+ */
+ public function renderForm(Form $form, $tab)
+ {
+ $this->getTabs()->add(uniqid(), array(
+ 'active' => true,
+ 'label' => $tab,
+ 'url' => Url::fromRequest()
+ ));
+ $this->view->form = $form;
+ $this->render('simple-form', null, true);
+ }
+
+ /**
+ * Create a SortBox widget and apply its sort rules on the given query
+ *
+ * The widget is set on the `sortBox' view property only if the current view has not been requested as compact
+ *
+ * @param array $columns An array containing the sort columns, with the
+ * submit value as the key and the label as the value
+ * @param Sortable $query Query to apply the user chosen sort rules on
+ * @param array $defaults An array containing default sort directions for specific columns
+ *
+ * @return $this
+ */
+ protected function setupSortControl(array $columns, Sortable $query = null, array $defaults = null)
+ {
+ $request = $this->getRequest();
+ $sortBox = SortBox::create('sortbox-' . $request->getActionName(), $columns, $defaults);
+ $sortBox->setRequest($request);
+
+ if ($query) {
+ $sortBox->setQuery($query);
+ $sortBox->handleRequest($request);
+ }
+
+ if (! $this->view->compact) {
+ $this->view->sortBox = $sortBox;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a Limiter widget at the `limiter' view property
+ *
+ * In case the current view has been requested as compact this method does nothing.
+ *
+ * @param int $itemsPerPage Default number of items per page
+ *
+ * @return $this
+ */
+ protected function setupLimitControl($itemsPerPage = 25)
+ {
+ if (! $this->view->compact) {
+ $this->view->limiter = new Limiter();
+ $this->view->limiter->setDefaultLimit($this->getPageSize($itemsPerPage));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the page size configured via user preferences or return the default value
+ *
+ * @param int $default
+ *
+ * @return int
+ */
+ protected function getPageSize($default)
+ {
+ if ($this->userPageSize === null) {
+ $user = $this->Auth()->getUser();
+ if ($user !== null) {
+ $pageSize = $user->getPreferences()->getValue('icingaweb', 'default_page_size');
+ $this->userPageSize = $pageSize ? (int) $pageSize : false;
+ } else {
+ $this->userPageSize = false;
+ }
+ }
+
+ return $this->userPageSize !== false ? $this->userPageSize : $default;
+ }
+
+ /**
+ * Apply the given page limit and number on the given query and setup a paginator for it
+ *
+ * The $itemsPerPage and $pageNumber parameters are only applied if not available in the current request.
+ * The paginator is set on the `paginator' view property only if the current view has not been requested as compact.
+ *
+ * @param QueryInterface $query The query to create a paginator for
+ * @param int $itemsPerPage Default number of items per page
+ * @param int $pageNumber Default page number
+ *
+ * @return $this
+ */
+ protected function setupPaginationControl(QueryInterface $query, $itemsPerPage = 25, $pageNumber = 0)
+ {
+ $request = $this->getRequest();
+ $limit = $request->getParam('limit', $this->getPageSize($itemsPerPage));
+ $page = $request->getParam('page', $pageNumber);
+ $query->limit($limit, $page > 0 ? ($page - 1) * $limit : 0);
+
+ if (! $this->view->compact) {
+ $paginator = new Paginator();
+ $paginator->setQuery($query);
+ $this->view->paginator = $paginator;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create a FilterEditor widget and apply the user's chosen filter options on the given filterable
+ *
+ * The widget is set on the `filterEditor' view property only if the current view has not been requested as compact.
+ * The optional $filterColumns parameter should be an array of key-value pairs where the key is the name of the
+ * column and the value the label to show to the user. The optional $searchColumns parameter should be an array
+ * of column names to be used to handle quick searches.
+ *
+ * If the given filterable is an instance of Icinga\Data\FilterColumns, $filterable->getFilterColumns() and
+ * $filterable->getSearchColumns() is called to provide the respective columns if $filterColumns or $searchColumns
+ * is not given.
+ *
+ * @param Filterable $filterable The filterable to create a filter editor for
+ * @param array $filterColumns The filter columns to offer to the user
+ * @param array $searchColumns The search columns to utilize for quick searches
+ * @param array $preserveParams The url parameters to preserve
+ *
+ * @return $this
+ *
+ * @todo Preserving and ignoring parameters should be configurable (another two method params? property magic?)
+ */
+ protected function setupFilterControl(
+ Filterable $filterable,
+ array $filterColumns = null,
+ array $searchColumns = null,
+ array $preserveParams = null
+ ) {
+ $defaultPreservedParams = array(
+ 'limit', // setupPaginationControl()
+ 'sort', // setupSortControl()
+ 'dir', // setupSortControl()
+ 'backend', // Framework
+ 'showCompact', // Framework
+ '_dev' // Framework
+ );
+
+ $editor = Widget::create('filterEditor');
+ /** @var \Icinga\Web\Widget\FilterEditor $editor */
+ call_user_func_array(
+ array($editor, 'preserveParams'),
+ array_merge($defaultPreservedParams, $preserveParams ?: array())
+ );
+
+ $editor
+ ->setQuery($filterable)
+ ->ignoreParams('page') // setupPaginationControl()
+ ->setColumns($filterColumns)
+ ->setSearchColumns($searchColumns)
+ ->handleRequest($this->getRequest());
+
+ if ($this->view->compact) {
+ $editor->setVisible(false);
+ }
+
+ $this->view->filterEditor = $editor;
+
+ return $this;
+ }
+}
diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php
new file mode 100644
index 0000000..76ff650
--- /dev/null
+++ b/library/Icinga/Web/Controller/ActionController.php
@@ -0,0 +1,587 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+use Icinga\Application\Modules\Module;
+use Icinga\Common\PdfExport;
+use Icinga\File\Pdf;
+use ipl\I18n\Translation;
+use Zend_Controller_Action;
+use Zend_Controller_Action_HelperBroker;
+use Zend_Controller_Request_Abstract;
+use Zend_Controller_Response_Abstract;
+use Icinga\Application\Benchmark;
+use Icinga\Application\Config;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\Http\HttpMethodNotAllowedException;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Forms\AutoRefreshForm;
+use Icinga\Security\SecurityException;
+use Icinga\Web\Session;
+use Icinga\Web\Url;
+use Icinga\Web\UrlParams;
+use Icinga\Web\Widget\Tabs;
+use Icinga\Web\Window;
+
+/**
+ * Base class for all core action controllers
+ *
+ * All Icinga Web core controllers should extend this class
+ *
+ * @method \Icinga\Web\Request getRequest() {
+ * {@inheritdoc}
+ * @return \Icinga\Web\Request
+ * }
+ *
+ * @method \Icinga\Web\Response getResponse() {
+ * {@inheritdoc}
+ * @return \Icinga\Web\Response
+ * }
+ */
+class ActionController extends Zend_Controller_Action
+{
+ use Translation;
+ use PdfExport {
+ sendAsPdf as private newSendAsPdf;
+ }
+
+ /**
+ * The login route to use when requiring authentication
+ */
+ const LOGIN_ROUTE = 'authentication/login';
+
+ /**
+ * The default page title to use
+ */
+ const DEFAULT_TITLE = 'Icinga Web';
+
+ /**
+ * Whether the controller requires the user to be authenticated
+ *
+ * @var bool
+ */
+ protected $requiresAuthentication = true;
+
+ /**
+ * The current module's name
+ *
+ * @var string
+ */
+ protected $moduleName;
+
+ /**
+ * A page's automatic refresh interval
+ *
+ * The initial value will not be subject to a user's preferences.
+ *
+ * @var int
+ */
+ protected $autorefreshInterval;
+
+ protected $reloadCss = false;
+
+ protected $window;
+
+ protected $rerenderLayout = false;
+
+ /**
+ * The inline layout (inside columns) to use
+ *
+ * @var string
+ */
+ protected $inlineLayout = 'inline';
+
+ /**
+ * The inner layout (inside the body) to use
+ *
+ * @var string
+ */
+ protected $innerLayout = 'body';
+
+ /**
+ * Authentication manager
+ *
+ * @var Auth|null
+ */
+ protected $auth;
+
+ /**
+ * URL parameters
+ *
+ * @var UrlParams
+ */
+ protected $params;
+
+ /**
+ * The constructor starts benchmarking, loads the configuration and sets
+ * other useful controller properties
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param Zend_Controller_Response_Abstract $response
+ * @param array $invokeArgs Any additional invocation arguments
+ */
+ public function __construct(
+ Zend_Controller_Request_Abstract $request,
+ Zend_Controller_Response_Abstract $response,
+ array $invokeArgs = array()
+ ) {
+ /** @var \Icinga\Web\Request $request */
+ /** @var \Icinga\Web\Response $response */
+ $this->params = UrlParams::fromQueryString();
+
+ $this->setRequest($request)
+ ->setResponse($response)
+ ->_setInvokeArgs($invokeArgs);
+ $this->_helper = new Zend_Controller_Action_HelperBroker($this);
+
+ $moduleName = $this->getModuleName();
+ $this->view->defaultTitle = static::DEFAULT_TITLE;
+ $this->translationDomain = $moduleName !== 'default' ? $moduleName : 'icinga';
+ $this->view->translationDomain = $this->translationDomain;
+ $this->_helper->layout()->isIframe = $request->getUrl()->shift('isIframe');
+ $this->_helper->layout()->showFullscreen = $request->getUrl()->shift('showFullscreen');
+ $this->_helper->layout()->moduleName = $moduleName;
+
+ $this->view->compact = false;
+ if ($request->getUrl()->getParam('view') === 'compact') {
+ $request->getUrl()->remove('view');
+ $this->view->compact = true;
+ }
+ if ($request->getUrl()->shift('showCompact')) {
+ $this->view->compact = true;
+ }
+ $this->rerenderLayout = $request->getUrl()->shift('renderLayout');
+ if ($request->getUrl()->shift('_disableLayout')) {
+ $this->_helper->layout()->disableLayout();
+ }
+
+ // $auth->authenticate($request, $response, $this->requiresLogin());
+ if ($this->requiresLogin()) {
+ if (! $request->isXmlHttpRequest() && $request->isApiRequest()) {
+ Auth::getInstance()->challengeHttp();
+ }
+ $this->redirectToLogin(Url::fromRequest());
+ }
+
+ $this->view->tabs = new Tabs();
+ $this->prepareInit();
+ $this->init();
+ }
+
+ /**
+ * Prepare controller initialization
+ *
+ * As it should not be required for controllers to call the parent's init() method, base controllers should use
+ * prepareInit() in order to prepare the controller initialization.
+ *
+ * @see \Zend_Controller_Action::init() For the controller initialization method.
+ */
+ protected function prepareInit()
+ {
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return Auth
+ */
+ public function Auth()
+ {
+ if ($this->auth === null) {
+ $this->auth = Auth::getInstance();
+ }
+ return $this->auth;
+ }
+
+ /**
+ * Whether the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @return bool
+ */
+ public function hasPermission($permission)
+ {
+ return $this->Auth()->hasPermission($permission);
+ }
+
+ /**
+ * Assert that the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @throws SecurityException If the current user lacks the given permission
+ */
+ public function assertPermission($permission)
+ {
+ if (! $this->Auth()->hasPermission($permission)) {
+ throw new SecurityException('No permission for %s', $permission);
+ }
+ }
+
+ /**
+ * Return the current module's name
+ *
+ * @return string
+ */
+ public function getModuleName()
+ {
+ if ($this->moduleName === null) {
+ $this->moduleName = $this->getRequest()->getModuleName();
+ }
+
+ return $this->moduleName;
+ }
+
+ public function Config($file = null)
+ {
+ if ($file === null) {
+ return Config::app();
+ } else {
+ return Config::app($file);
+ }
+ }
+
+ public function Window()
+ {
+ if ($this->window === null) {
+ $this->window = Window::getInstance();
+ }
+
+ return $this->window;
+ }
+
+ protected function reloadCss()
+ {
+ $this->reloadCss = true;
+ return $this;
+ }
+
+ /**
+ * Respond with HTTP 405 if the current request's method is not one of the given methods
+ *
+ * @param string $httpMethod Unlimited number of allowed HTTP methods
+ *
+ * @throws HttpMethodNotAllowedException If the request method is not one of the given methods
+ */
+ public function assertHttpMethod($httpMethod)
+ {
+ $httpMethods = array_flip(array_map('strtoupper', func_get_args()));
+ if (! isset($httpMethods[$this->getRequest()->getMethod()])) {
+ $e = new HttpMethodNotAllowedException($this->translate('Method Not Allowed'));
+ $e->setAllowedMethods(implode(', ', array_keys($httpMethods)));
+ throw $e;
+ }
+ }
+
+ /**
+ * Return restriction information for an eventually authenticated user
+ *
+ * @param string $name Restriction name
+ *
+ * @return array
+ */
+ public function getRestrictions($name)
+ {
+ return $this->Auth()->getRestrictions($name);
+ }
+
+ /**
+ * Check whether the controller requires a login. That is when the controller requires authentication and the
+ * user is currently not authenticated
+ *
+ * @return bool
+ */
+ protected function requiresLogin()
+ {
+ if (! $this->requiresAuthentication) {
+ return false;
+ }
+
+ return ! $this->Auth()->isAuthenticated();
+ }
+
+ /**
+ * Return the tabs
+ *
+ * @return Tabs
+ */
+ public function getTabs()
+ {
+ return $this->view->tabs;
+ }
+
+ protected function ignoreXhrBody()
+ {
+ if ($this->isXhr()) {
+ $this->getResponse()->setHeader('X-Icinga-Container', 'ignore');
+ }
+ }
+
+ /**
+ * Set the interval (in seconds) at which the page should automatically refresh
+ *
+ * This may be adjusted based on the user's preferences. The result could be a
+ * lower or higher rate of the page's automatic refresh. If this is not desired,
+ * the only way to bypass this is to initialize the {@see ActionController::$autorefreshInterval}
+ * property or to set the `autorefreshInterval` property of the layout directly.
+ *
+ * @param int $interval
+ *
+ * @return $this
+ */
+ public function setAutorefreshInterval($interval)
+ {
+ if (! is_int($interval) || $interval < 1) {
+ throw new ProgrammingError(
+ 'Setting autorefresh interval smaller than 1 second is not allowed'
+ );
+ }
+
+ $user = $this->getRequest()->getUser();
+ if ($user !== null) {
+ $speed = (float) $user->getPreferences()->getValue('icingaweb', 'auto_refresh_speed', 1.0);
+ $interval = max(round($interval * $speed), min($interval, 5));
+ }
+
+ $this->autorefreshInterval = $interval;
+
+ return $this;
+ }
+
+ public function disableAutoRefresh()
+ {
+ $this->autorefreshInterval = null;
+
+ return $this;
+ }
+
+ /**
+ * Redirect to login
+ *
+ * XHR will always redirect to __SELF__ if an URL to redirect to after successful login is set. __SELF__ instructs
+ * JavaScript to redirect to the current window's URL if it's an auto-refresh request or to redirect to the URL
+ * which required login if it's not an auto-refreshing one.
+ *
+ * XHR will respond with HTTP status code 403 Forbidden.
+ *
+ * @param Url|string $redirect URL to redirect to after successful login
+ */
+ protected function redirectToLogin($redirect = null)
+ {
+ $login = Url::fromPath(static::LOGIN_ROUTE);
+ if ($this->isXhr()) {
+ if ($redirect !== null) {
+ $login->setParam('redirect', '__SELF__');
+ }
+
+ $this->_response->setHttpResponseCode(403);
+ } elseif ($redirect !== null) {
+ if (! $redirect instanceof Url) {
+ $redirect = Url::fromPath($redirect);
+ }
+
+ if (($relativeUrl = $redirect->getRelativeUrl())) {
+ $login->setParam('redirect', $relativeUrl);
+ }
+ }
+
+ $this->rerenderLayout()->redirectNow($login);
+ }
+
+ protected function rerenderLayout()
+ {
+ $this->rerenderLayout = true;
+ return $this;
+ }
+
+ public function isXhr()
+ {
+ return $this->getRequest()->isXmlHttpRequest();
+ }
+
+ protected function redirectXhr($url)
+ {
+ $response = $this->getResponse();
+
+ if ($this->reloadCss) {
+ $response->setReloadCss(true);
+ }
+
+ if ($this->rerenderLayout) {
+ $response->setRerenderLayout(true);
+ }
+
+ $response->redirectAndExit($url);
+ }
+
+ protected function redirectHttp($url)
+ {
+ if ($this->isXhr()) {
+ $this->getResponse()->setHeader('X-Icinga-Redirect-Http', 'yes');
+ }
+
+ $this->getResponse()->redirectAndExit($url);
+ }
+
+ /**
+ * Redirect to a specific url, updating the browsers URL field
+ *
+ * @param Url|string $url The target to redirect to
+ **/
+ public function redirectNow($url)
+ {
+ if ($this->isXhr()) {
+ $this->redirectXhr($url);
+ } else {
+ $this->redirectHttp($url);
+ }
+ }
+
+ /**
+ * @see Zend_Controller_Action::preDispatch()
+ */
+ public function preDispatch()
+ {
+ $form = new AutoRefreshForm();
+ if (! $this->getRequest()->isApiRequest()) {
+ $form->handleRequest();
+ }
+ $this->_helper->layout()->autoRefreshForm = $form;
+ }
+
+ /**
+ * Detect whether the current request requires changes in the layout and apply them before rendering
+ *
+ * @see Zend_Controller_Action::postDispatch()
+ */
+ public function postDispatch()
+ {
+ Benchmark::measure('Action::postDispatch()');
+
+ $req = $this->getRequest();
+ $layout = $this->_helper->layout();
+ $layout->innerLayout = $this->innerLayout;
+ $layout->inlineLayout = $this->inlineLayout;
+
+ if ($user = $req->getUser()) {
+ if ((bool) $user->getPreferences()->getValue('icingaweb', 'show_benchmark', false)) {
+ if ($this->_helper->layout()->isEnabled()) {
+ $layout->benchmark = $this->renderBenchmark();
+ }
+ }
+
+ if (! (bool) $user->getPreferences()->getValue('icingaweb', 'auto_refresh', true)) {
+ $this->disableAutoRefresh();
+ }
+ }
+
+ if ($this->autorefreshInterval !== null) {
+ $layout->autorefreshInterval = $this->autorefreshInterval;
+ }
+
+ if ($req->getParam('error_handler') === null && $req->getParam('format') === 'pdf') {
+ $this->sendAsPdf();
+ $this->shutdownSession();
+ exit;
+ }
+
+ if ($this->isXhr()) {
+ $this->postDispatchXhr();
+ }
+
+ $this->shutdownSession();
+ }
+
+ protected function postDispatchXhr()
+ {
+ $resp = $this->getResponse();
+
+ if ($this->reloadCss) {
+ $resp->setReloadCss(true);
+ }
+
+ if ($this->view->title) {
+ if (preg_match('~[\r\n]~', $this->view->title)) {
+ // TODO: Innocent exception and error log for hack attempts
+ throw new IcingaException('No way, guy');
+ }
+ $resp->setHeader(
+ 'X-Icinga-Title',
+ rawurlencode($this->view->title . ' :: ' . $this->view->defaultTitle),
+ true
+ );
+ } else {
+ $resp->setHeader('X-Icinga-Title', rawurlencode($this->view->defaultTitle), true);
+ }
+
+ $layout = $this->_helper->layout();
+ if ($this->rerenderLayout) {
+ $layout->setLayout($this->innerLayout);
+ $resp->setRerenderLayout(true);
+ } else {
+ // The layout may be disabled and there's no indication that the layout is explicitly desired,
+ // that's why we're passing false as second parameter to setLayout
+ $layout->setLayout($this->inlineLayout, false);
+ }
+
+ if ($this->autorefreshInterval !== null) {
+ $resp->setAutoRefreshInterval($this->autorefreshInterval);
+ }
+ }
+
+ protected function sendAsPdf()
+ {
+ if (Module::exists('pdfexport')) {
+ $this->newSendAsPdf();
+ } else {
+ $pdf = new Pdf();
+ $pdf->renderControllerAction($this);
+ }
+ }
+
+ protected function shutdownSession()
+ {
+ $session = Session::getSession();
+ if ($session->hasChanged()) {
+ $session->write();
+ }
+ }
+
+ /**
+ * Render the benchmark
+ *
+ * @return string Benchmark HTML
+ */
+ protected function renderBenchmark()
+ {
+ $this->_helper->viewRenderer->postDispatch();
+ Benchmark::measure('Response ready');
+ return Benchmark::renderToHtml();
+ }
+
+ /**
+ * Try to call compatible methods from older zend versions
+ *
+ * Methods like getParam and redirect are _getParam/_redirect in older Zend versions (which reside for example
+ * in Debian Wheezy). Using those methods without the "_" causes the application to fail on those platforms, but
+ * using the version with "_" forces us to use deprecated code. So we try to catch this issue by looking for methods
+ * with the same name, but with a "_" prefix prepended.
+ *
+ * @param string $name The method name to check
+ * @param mixed $params The method parameters
+ * @return mixed Anything the method returns
+ */
+ public function __call($name, $params)
+ {
+ $deprecatedMethod = '_' . $name;
+
+ if (method_exists($this, $deprecatedMethod)) {
+ return call_user_func_array(array($this, $deprecatedMethod), $params);
+ }
+
+ return parent::__call($name, $params);
+ }
+}
diff --git a/library/Icinga/Web/Controller/AuthBackendController.php b/library/Icinga/Web/Controller/AuthBackendController.php
new file mode 100644
index 0000000..97dc4b3
--- /dev/null
+++ b/library/Icinga/Web/Controller/AuthBackendController.php
@@ -0,0 +1,149 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+use ipl\Web\Compat\CompatController;
+use Zend_Controller_Action_Exception;
+use Icinga\Application\Config;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Authentication\User\UserBackendInterface;
+use Icinga\Authentication\UserGroup\UserGroupBackend;
+use Icinga\Authentication\UserGroup\UserGroupBackendInterface;
+
+/**
+ * Base class for authentication backend controllers
+ */
+class AuthBackendController extends CompatController
+{
+ public function init()
+ {
+ parent::init();
+
+ $this->tabs->disableLegacyExtensions();
+ }
+
+ /**
+ * Redirect to this controller's list action
+ */
+ public function indexAction()
+ {
+ $this->redirectNow($this->getRequest()->getControllerName() . '/list');
+ }
+
+ /**
+ * Return all user backends implementing the given interface
+ *
+ * @param string $interface The class path of the interface, or null if no interface check should be made
+ *
+ * @return array
+ */
+ protected function loadUserBackends($interface = null)
+ {
+ $backends = array();
+ foreach (Config::app('authentication') as $backendName => $backendConfig) {
+ $candidate = UserBackend::create($backendName, $backendConfig);
+ if (! $interface || $candidate instanceof $interface) {
+ $backends[] = $candidate;
+ }
+ }
+
+ return $backends;
+ }
+
+ /**
+ * Return the given user backend or the first match in order
+ *
+ * @param string $name The name of the backend, or null in case the first match should be returned
+ * @param string $interface The interface the backend should implement, no interface check if null
+ *
+ * @return UserBackendInterface
+ *
+ * @throws Zend_Controller_Action_Exception In case the given backend name is invalid
+ */
+ protected function getUserBackend($name = null, $interface = 'Icinga\Data\Selectable')
+ {
+ if ($name !== null) {
+ $config = Config::app('authentication');
+ if (! $config->hasSection($name)) {
+ $this->httpNotFound(sprintf($this->translate('Authentication backend "%s" not found'), $name));
+ } else {
+ $backend = UserBackend::create($name, $config->getSection($name));
+ if ($interface && !$backend instanceof $interface) {
+ $interfaceParts = explode('\\', strtolower($interface));
+ throw new Zend_Controller_Action_Exception(
+ sprintf(
+ $this->translate('Authentication backend "%s" is not %s'),
+ $name,
+ array_pop($interfaceParts)
+ ),
+ 400
+ );
+ }
+ }
+ } else {
+ $backends = $this->loadUserBackends($interface);
+ $backend = array_shift($backends);
+ }
+
+ return $backend;
+ }
+
+ /**
+ * Return all user group backends implementing the given interface
+ *
+ * @param string $interface The class path of the interface, or null if no interface check should be made
+ *
+ * @return array
+ */
+ protected function loadUserGroupBackends($interface = null)
+ {
+ $backends = array();
+ foreach (Config::app('groups') as $backendName => $backendConfig) {
+ $candidate = UserGroupBackend::create($backendName, $backendConfig);
+ if (! $interface || $candidate instanceof $interface) {
+ $backends[] = $candidate;
+ }
+ }
+
+ return $backends;
+ }
+
+ /**
+ * Return the given user group backend or the first match in order
+ *
+ * @param string $name The name of the backend, or null in case the first match should be returned
+ * @param string $interface The interface the backend should implement, no interface check if null
+ *
+ * @return UserGroupBackendInterface
+ *
+ * @throws Zend_Controller_Action_Exception In case the given backend name is invalid
+ */
+ protected function getUserGroupBackend($name = null, $interface = 'Icinga\Data\Selectable')
+ {
+ if ($name !== null) {
+ $config = Config::app('groups');
+ if (! $config->hasSection($name)) {
+ $this->httpNotFound(sprintf($this->translate('User group backend "%s" not found'), $name));
+ } else {
+ $backend = UserGroupBackend::create($name, $config->getSection($name));
+ if ($interface && !$backend instanceof $interface) {
+ $interfaceParts = explode('\\', strtolower($interface));
+ throw new Zend_Controller_Action_Exception(
+ sprintf(
+ $this->translate('User group backend "%s" is not %s'),
+ $name,
+ array_pop($interfaceParts)
+ ),
+ 400
+ );
+ }
+ }
+ } else {
+ $backends = $this->loadUserGroupBackends($interface);
+ $backend = array_shift($backends);
+ }
+
+ return $backend;
+ }
+}
diff --git a/library/Icinga/Web/Controller/BasePreferenceController.php b/library/Icinga/Web/Controller/BasePreferenceController.php
new file mode 100644
index 0000000..8f2da8f
--- /dev/null
+++ b/library/Icinga/Web/Controller/BasePreferenceController.php
@@ -0,0 +1,39 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+/**
+ * Base class for Preference Controllers
+ *
+ * Module preferences use this class to make sure they are automatically
+ * added to the general preferences dialog. If you create a subclass of
+ * BasePreferenceController and overwrite @see init(), make sure you call
+ * parent::init(), otherwise you won't have the $tabs property in your view.
+ *
+ */
+class BasePreferenceController extends ActionController
+{
+ /**
+ * Return an array of tabs provided by this preference controller.
+ *
+ * Those tabs will automatically be added to the application's preference dialog
+ *
+ * @return array
+ */
+ public static function createProvidedTabs()
+ {
+ return array();
+ }
+
+ /**
+ * Initialize the controller and collect all tabs for it from the application and its modules
+ *
+ * @see ActionController::init()
+ */
+ public function init()
+ {
+ parent::init();
+ $this->view->tabs = ControllerTabCollector::collectControllerTabs('PreferenceController');
+ }
+}
diff --git a/library/Icinga/Web/Controller/ControllerTabCollector.php b/library/Icinga/Web/Controller/ControllerTabCollector.php
new file mode 100644
index 0000000..b452a20
--- /dev/null
+++ b/library/Icinga/Web/Controller/ControllerTabCollector.php
@@ -0,0 +1,97 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+use Icinga\Application\Modules\Module;
+use Icinga\Application\Icinga;
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Static helper class that collects tabs provided by the 'createProvidedTabs' method of controllers
+ */
+class ControllerTabCollector
+{
+ /**
+ * Scan all controllers with given name in the application and (loaded) module folders and collects their provided
+ * tabs
+ *
+ * @param string $controllerName The name of the controllers to use for tab collection
+ *
+ * @return Tabs A {@link Tabs} instance containing the application tabs first followed by the
+ * tabs provided from the modules
+ */
+ public static function collectControllerTabs($controllerName)
+ {
+ $controller = '\Icinga\\' . Dispatcher::CONTROLLER_NAMESPACE . '\\' . $controllerName;
+ $applicationTabs = $controller::createProvidedTabs();
+ $moduleTabs = self::collectModuleTabs($controllerName);
+
+ $tabs = new Tabs();
+ foreach ($applicationTabs as $name => $tab) {
+ $tabs->add($name, $tab);
+ }
+
+ foreach ($moduleTabs as $name => $tab) {
+ // Don't overwrite application tabs if the module wants to
+ if ($tabs->has($name)) {
+ continue;
+ }
+ $tabs->add($name, $tab);
+ }
+ return $tabs;
+ }
+
+ /**
+ * Collect module tabs for all modules containing the given controller
+ *
+ * @param string $controller The controller name to use for tab collection
+ *
+ * @return array An array of Tabs objects or arrays containing Tab descriptions
+ */
+ private static function collectModuleTabs($controller)
+ {
+ $moduleManager = Icinga::app()->getModuleManager();
+ $modules = $moduleManager->listEnabledModules();
+ $tabs = array();
+ foreach ($modules as $module) {
+ $tabs += self::createModuleConfigurationTabs($controller, $moduleManager->getModule($module));
+ }
+
+ return $tabs;
+ }
+
+ /**
+ * Collects the tabs from the createProvidedTabs() method in the configuration controller
+ *
+ * If the module doesn't have the given controller or createProvidedTabs method in the controller an empty array
+ * will be returned
+ *
+ * @param string $controllerName The name of the controller that provides tabs via createProvidedTabs
+ * @param Module $module The module instance that provides the controller
+ *
+ * @return array
+ */
+ private static function createModuleConfigurationTabs($controllerName, Module $module)
+ {
+ // TODO(el): Only works for controllers w/o namepsace: https://dev.icinga.com/issues/4149
+ $controllerDir = $module->getControllerDir();
+ $name = $module->getName();
+
+ $controllerDir = $controllerDir . '/' . $controllerName . '.php';
+ $controllerName = ucfirst($name) . '_' . $controllerName;
+
+ if (is_readable($controllerDir)) {
+ require_once(realpath($controllerDir));
+ if (! method_exists($controllerName, 'createProvidedTabs')) {
+ return array();
+ }
+ $tab = $controllerName::createProvidedTabs();
+ if (! is_array($tab)) {
+ $tab = array($name => $tab);
+ }
+ return $tab;
+ }
+ return array();
+ }
+}
diff --git a/library/Icinga/Web/Controller/Dispatcher.php b/library/Icinga/Web/Controller/Dispatcher.php
new file mode 100644
index 0000000..e2dfb80
--- /dev/null
+++ b/library/Icinga/Web/Controller/Dispatcher.php
@@ -0,0 +1,93 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+use Exception;
+use Icinga\Util\StringHelper;
+use Zend_Controller_Action;
+use Zend_Controller_Action_Interface;
+use Zend_Controller_Dispatcher_Exception;
+use Zend_Controller_Dispatcher_Standard;
+use Zend_Controller_Request_Abstract;
+use Zend_Controller_Response_Abstract;
+
+/**
+ * Dispatcher supporting Zend-style and namespaced controllers
+ *
+ * Does not support a namespaced default controller in combination w/ the Zend parameter useDefaultControllerAlways.
+ */
+class Dispatcher extends Zend_Controller_Dispatcher_Standard
+{
+ /**
+ * Controller namespace
+ *
+ * @var string
+ */
+ const CONTROLLER_NAMESPACE = 'Controllers';
+
+ /**
+ * Dispatch request to a controller and action
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param Zend_Controller_Response_Abstract $response
+ *
+ * @throws Zend_Controller_Dispatcher_Exception If the controller is not an instance of
+ * Zend_Controller_Action_Interface
+ * @throws Exception If dispatching the request fails
+ */
+ public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
+ {
+ $this->setResponse($response);
+ $controllerName = $request->getControllerName();
+ if (! $controllerName) {
+ parent::dispatch($request, $response);
+ return;
+ }
+ $controllerName = StringHelper::cname($controllerName, '-') . 'Controller';
+ $moduleName = $request->getModuleName();
+ if ($moduleName === null || $moduleName === $this->_defaultModule) {
+ $controllerClass = 'Icinga\\' . self::CONTROLLER_NAMESPACE . '\\' . $controllerName;
+ } else {
+ $controllerClass = 'Icinga\\Module\\' . ucfirst($moduleName) . '\\' . self::CONTROLLER_NAMESPACE . '\\'
+ . $controllerName;
+ }
+ if (! class_exists($controllerClass)) {
+ parent::dispatch($request, $response);
+ return;
+ }
+ $controller = new $controllerClass($request, $response, $this->getParams());
+ if (! $controller instanceof Zend_Controller_Action
+ && ! $controller instanceof Zend_Controller_Action_Interface
+ ) {
+ throw new Zend_Controller_Dispatcher_Exception(
+ 'Controller "' . $controllerClass . '" is not an instance of Zend_Controller_Action_Interface'
+ );
+ }
+ $action = $this->getActionMethod($request);
+ $request->setDispatched(true);
+ // Buffer output by default
+ $disableOb = $this->getParam('disableOutputBuffering');
+ $obLevel = ob_get_level();
+ if (empty($disableOb)) {
+ ob_start();
+ }
+ try {
+ $controller->dispatch($action);
+ } catch (Exception $e) {
+ // Clean output buffer on error
+ $curObLevel = ob_get_level();
+ if ($curObLevel > $obLevel) {
+ do {
+ ob_get_clean();
+ $curObLevel = ob_get_level();
+ } while ($curObLevel > $obLevel);
+ }
+ throw $e;
+ }
+ if (empty($disableOb)) {
+ $content = ob_get_clean();
+ $response->appendBody($content);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Controller/ModuleActionController.php b/library/Icinga/Web/Controller/ModuleActionController.php
new file mode 100644
index 0000000..ad66264
--- /dev/null
+++ b/library/Icinga/Web/Controller/ModuleActionController.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Manager;
+use Icinga\Application\Modules\Module;
+
+/**
+ * Base class for module action controllers
+ */
+class ModuleActionController extends ActionController
+{
+ protected $config;
+
+ protected $configs = array();
+
+ protected $module;
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Controller\ActionController For the method documentation.
+ */
+ protected function prepareInit()
+ {
+ $this->moduleInit();
+ if (($this->Auth()->isAuthenticated() || $this->requiresLogin())
+ && $this->getFrontController()->getDefaultModule() !== $this->getModuleName()) {
+ $this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->getModuleName());
+ }
+ }
+
+ /**
+ * Prepare module action controller initialization
+ */
+ protected function moduleInit()
+ {
+ }
+
+ public function Config($file = null)
+ {
+ if ($file === null) {
+ if ($this->config === null) {
+ $this->config = Config::module($this->getModuleName());
+ }
+ return $this->config;
+ } else {
+ if (! array_key_exists($file, $this->configs)) {
+ $this->configs[$file] = Config::module($this->getModuleName(), $file);
+ }
+ return $this->configs[$file];
+ }
+ }
+
+ /**
+ * Return this controller's module
+ *
+ * @return Module
+ */
+ public function Module()
+ {
+ if ($this->module === null) {
+ $this->module = Icinga::app()->getModuleManager()->getModule($this->getModuleName());
+ }
+
+ return $this->module;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Controller\ActionController::postDispatchXhr() For the method documentation.
+ */
+ public function postDispatchXhr()
+ {
+ parent::postDispatchXhr();
+ $this->getResponse()->setHeader('X-Icinga-Module', $this->getModuleName(), true);
+ }
+}
diff --git a/library/Icinga/Web/Controller/StaticController.php b/library/Icinga/Web/Controller/StaticController.php
new file mode 100644
index 0000000..f5ce163
--- /dev/null
+++ b/library/Icinga/Web/Controller/StaticController.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Controller;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Request;
+
+class StaticController
+{
+ /**
+ * Handle incoming request
+ *
+ * @param Request $request
+ *
+ * @returns void
+ */
+ public function handle(Request $request)
+ {
+ $app = Icinga::app();
+
+ // +4 because strlen('/lib') === 4
+ $assetPath = ltrim(substr($request->getRequestUri(), strlen($request->getBaseUrl()) + 4), '/');
+
+ $library = null;
+ foreach ($app->getLibraries() as $candidate) {
+ if (substr($assetPath, 0, strlen($candidate->getName())) === $candidate->getName()) {
+ $library = $candidate;
+ $assetPath = ltrim(substr($assetPath, strlen($candidate->getName())), '/');
+ break;
+ }
+ }
+
+ if ($library === null) {
+ $app->getResponse()
+ ->setHttpResponseCode(404);
+
+ return;
+ }
+
+ $assetRoot = $library->getStaticAssetPath();
+ if (empty($assetRoot)) {
+ $app->getResponse()
+ ->setHttpResponseCode(404);
+
+ return;
+ }
+
+ $filePath = $assetRoot . DIRECTORY_SEPARATOR . $assetPath;
+ $dirPath = realpath(dirname($filePath)); // dirname, because the file may be a link
+
+ if ($dirPath === false
+ || substr($dirPath, 0, strlen($assetRoot)) !== $assetRoot
+ || ! is_file($filePath)
+ ) {
+ $app->getResponse()
+ ->setHttpResponseCode(404);
+
+ return;
+ }
+
+ $fileStat = stat($filePath);
+ $eTag = sprintf(
+ '%x-%x-%x',
+ $fileStat['ino'],
+ $fileStat['size'],
+ (float) str_pad($fileStat['mtime'], 16, '0')
+ );
+
+ $app->getResponse()->setHeader(
+ 'Cache-Control',
+ 'public, max-age=1814400, stale-while-revalidate=604800',
+ true
+ );
+
+ if ($request->getServer('HTTP_IF_NONE_MATCH') === $eTag) {
+ $app->getResponse()
+ ->setHttpResponseCode(304);
+ } else {
+ $app->getResponse()
+ ->setHeader('ETag', $eTag)
+ ->setHeader('Content-Type', mime_content_type($filePath), true)
+ ->setHeader('Last-Modified', gmdate('D, d M Y H:i:s', $fileStat['mtime']) . ' GMT')
+ ->setBody(file_get_contents($filePath));
+ }
+ }
+}
diff --git a/library/Icinga/Web/Cookie.php b/library/Icinga/Web/Cookie.php
new file mode 100644
index 0000000..283f07a
--- /dev/null
+++ b/library/Icinga/Web/Cookie.php
@@ -0,0 +1,299 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use InvalidArgumentException;
+
+/**
+ * A HTTP cookie
+ */
+class Cookie
+{
+ /**
+ * Domain of the cookie
+ *
+ * @var string
+ */
+ protected $domain;
+
+ /**
+ * The timestamp at which the cookie expires
+ *
+ * @var int
+ */
+ protected $expire;
+
+ /**
+ * Whether to protect the cookie against client side script code attempts to read the cookie
+ *
+ * Defaults to true.
+ *
+ * @var bool
+ */
+ protected $httpOnly = true;
+
+ /**
+ * Name of the cookie
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The path on the web server where the cookie is available
+ *
+ * Defaults to the base URL.
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * Whether to send the cookie only over a secure connection
+ *
+ * Defaults to auto-detection so that if the current request was sent over a secure connection the secure flag will
+ * be set to true.
+ *
+ * @var bool
+ */
+ protected $secure;
+
+ /**
+ * Value of the cookie
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * Create a new cookie
+ *
+ * @param string $name
+ * @param string $value
+ */
+ public function __construct($name, $value = null)
+ {
+ if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
+ throw new InvalidArgumentException(sprintf(
+ 'Cookie name can\'t contain these characters: =,; \t\r\n\013\014 (%s)',
+ $name
+ ));
+ }
+ if (empty($name)) {
+ throw new InvalidArgumentException('The cookie name can\'t be empty');
+ }
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ /**
+ * Get the domain of the cookie
+ *
+ * @return string
+ */
+ public function getDomain()
+ {
+ if ($this->domain === null) {
+ $this->domain = Config::app()->get('cookie', 'domain');
+ }
+ return $this->domain;
+ }
+
+ /**
+ * Set the domain of the cookie
+ *
+ * @param string $domain
+ *
+ * @return $this
+ */
+ public function setDomain($domain)
+ {
+ $this->domain = $domain;
+ return $this;
+ }
+
+ /**
+ * Get the timestamp at which the cookie expires
+ *
+ * @return int
+ */
+ public function getExpire()
+ {
+ return $this->expire;
+ }
+
+ /**
+ * Set the timestamp at which the cookie expires
+ *
+ * @param int $expire
+ *
+ * @return $this
+ */
+ public function setExpire($expire)
+ {
+ $this->expire = $expire;
+ return $this;
+ }
+
+ /**
+ * Get whether to protect the cookie against client side script code attempts to read the cookie
+ *
+ * @return bool
+ */
+ public function isHttpOnly()
+ {
+ return $this->httpOnly;
+ }
+
+ /**
+ * Set whether to protect the cookie against client side script code attempts to read the cookie
+ *
+ * @param bool $httpOnly
+ *
+ * @return $this
+ */
+ public function setHttpOnly($httpOnly)
+ {
+ $this->httpOnly = $httpOnly;
+ return $this;
+ }
+
+ /**
+ * Get the name of the cookie
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get the path on the web server where the cookie is available
+ *
+ * If the path has not been set either via {@link setPath()} or via config, the base URL will be returned.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ if ($this->path === null) {
+ $path = Config::app()->get('cookie', 'path');
+ if ($path === null) {
+ // The following call could be used as default for ConfigObject::get(), but we prevent unnecessary
+ // function calls here, if the path is set in the config
+ $path = Icinga::app()->getRequest()->getBaseUrl() . '/'; // Zend has rtrim($baseUrl, '/')
+ }
+ $this->path = $path;
+ }
+ return $this->path;
+ }
+
+ /**
+ * Set the path on the web server where the cookie is available
+ *
+ * @param string $path
+ *
+ * @return $this
+ */
+ public function setPath($path)
+ {
+ $this->path = $path;
+ return $this;
+ }
+
+ /**
+ * Get whether to send the cookie only over a secure connection
+ *
+ * If the secure flag has not been set either via {@link setSecure()} or via config and if the current request was
+ * sent over a secure connection, true will be returned.
+ *
+ * @return bool
+ */
+ public function isSecure()
+ {
+ if ($this->secure === null) {
+ $secure = Config::app()->get('cookie', 'secure');
+ if ($secure === null) {
+ // The following call could be used as default for ConfigObject::get(), but we prevent unnecessary
+ // function calls here, if the secure flag is set in the config
+ $secure = Icinga::app()->getRequest()->isSecure();
+ }
+ $this->secure = $secure;
+ }
+ return $this->secure;
+ }
+
+ /**
+ * Set whether to send the cookie only over a secure connection
+ *
+ * @param bool $secure
+ *
+ * @return $this
+ */
+ public function setSecure($secure)
+ {
+ $this->secure = $secure;
+ return $this;
+ }
+
+ /**
+ * Get the value of the cookie
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the value of the cookie
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+ return $this;
+ }
+
+ /**
+ * Create invalidation cookie
+ *
+ * This method clones the current cookie and sets its value to null and expire time to 1.
+ * That way, the cookie removes itself when it has been sent to and processed by the client.
+ *
+ * We're cloning the current cookie in order to meet the [RFC6265 spec](https://tools.ietf.org/search/rfc6265)
+ * regarding the `Path` and `Domain` attribute:
+ *
+ * > Finally, to remove a cookie, the server returns a Set-Cookie header with an expiration date in the past.
+ * > The server will be successful in removing the cookie only if the Path and the Domain attribute in the
+ * > Set-Cookie header match the values used when the cookie was created.
+ *
+ * Note that the cookie has to be sent to the client.
+ *
+ * # Example Usage
+ *
+ * ```php
+ * $response->setCookie(
+ * $cookie->forgetMe()
+ * );
+ * ```
+ *
+ * @return static
+ */
+ public function forgetMe()
+ {
+ $forgetMe = clone $this;
+
+ return $forgetMe
+ ->setValue(null)
+ ->setExpire(1);
+ }
+}
diff --git a/library/Icinga/Web/CookieSet.php b/library/Icinga/Web/CookieSet.php
new file mode 100644
index 0000000..019be29
--- /dev/null
+++ b/library/Icinga/Web/CookieSet.php
@@ -0,0 +1,58 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use ArrayIterator;
+use IteratorAggregate;
+use Traversable;
+
+/**
+ * Maintain a set of cookies
+ */
+class CookieSet implements IteratorAggregate
+{
+ /**
+ * Cookies in this set indexed by the cookie names
+ *
+ * @var Cookie[]
+ */
+ protected $cookies = array();
+
+ /**
+ * Get an iterator for traversing the cookies in this set
+ *
+ * @return ArrayIterator An iterator for traversing the cookies in this set
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->cookies);
+ }
+
+ /**
+ * Add a cookie to the set
+ *
+ * If a cookie with the same name already exists, the cookie will be overridden.
+ *
+ * @param Cookie $cookie The cookie to add
+ *
+ * @return $this
+ */
+ public function add(Cookie $cookie)
+ {
+ $this->cookies[$cookie->getName()] = $cookie;
+ return $this;
+ }
+
+ /**
+ * Get the cookie with the given name from the set
+ *
+ * @param string $name The name of the cookie
+ *
+ * @return Cookie|null The cookie with the given name or null if the cookie does not exist
+ */
+ public function get($name)
+ {
+ return isset($this->cookies[$name]) ? $this->cookies[$name] : null;
+ }
+}
diff --git a/library/Icinga/Web/Dom/DomNodeIterator.php b/library/Icinga/Web/Dom/DomNodeIterator.php
new file mode 100644
index 0000000..1ea20b8
--- /dev/null
+++ b/library/Icinga/Web/Dom/DomNodeIterator.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Dom;
+
+use DOMNode;
+use IteratorIterator;
+use RecursiveIterator;
+
+/**
+ * Recursive iterator over a DOMNode
+ *
+ * Usage example:
+ * <code>
+ * <?php
+ *
+ * namespace Icinga\Example;
+ *
+ * use DOMDocument;
+ * use RecursiveIteratorIterator;
+ * use Icinga\Web\Dom\DomIterator;
+ *
+ * $doc = new DOMDocument();
+ * $doc->loadHTML(...);
+ * $dom = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST);
+ * foreach ($dom as $node) {
+ * ....
+ * }
+ * </code>
+ */
+class DomNodeIterator implements RecursiveIterator
+{
+ /**
+ * The node's children
+ *
+ * @var IteratorIterator
+ */
+ protected $children;
+
+ /**
+ * Create a new iterator over a DOMNode's children
+ *
+ * @param DOMNode $node
+ */
+ public function __construct(DOMNode $node)
+ {
+ $this->children = new IteratorIterator($node->childNodes);
+ }
+
+ public function current(): ?DOMNode
+ {
+ return $this->children->current();
+ }
+
+ public function key(): int
+ {
+ return $this->children->key();
+ }
+
+ public function next(): void
+ {
+ $this->children->next();
+ }
+
+ public function rewind(): void
+ {
+ $this->children->rewind();
+ }
+
+ public function valid(): bool
+ {
+ return $this->children->valid();
+ }
+
+ public function hasChildren(): bool
+ {
+ return $this->current()->hasChildNodes();
+ }
+
+ public function getChildren(): DomNodeIterator
+ {
+ return new static($this->current());
+ }
+}
diff --git a/library/Icinga/Web/FileCache.php b/library/Icinga/Web/FileCache.php
new file mode 100644
index 0000000..03f0c19
--- /dev/null
+++ b/library/Icinga/Web/FileCache.php
@@ -0,0 +1,293 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+class FileCache
+{
+ /**
+ * FileCache singleton instances
+ *
+ * @var array
+ */
+ protected static $instances = array();
+
+ /**
+ * Cache instance base directory
+ *
+ * @var string
+ */
+ protected $basedir;
+
+ /**
+ * Instance name
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * Whether the cache is enabled
+ *
+ * @var bool
+ */
+ protected $enabled = false;
+
+ /**
+ * The protected constructor creates a new instance with the given name
+ *
+ * @param string $name Cache instance name
+ */
+ protected function __construct($name)
+ {
+ $this->name = $name;
+ $tmpDir = sys_get_temp_dir();
+ $runtimePath = $tmpDir . '/FileCache_' . $name;
+ if (is_dir($runtimePath)) {
+ // Don't combine the following if with the above because else the elseif path will be evaluated if the
+ // runtime path exists and is not writeable
+ if (is_writeable($runtimePath)) {
+ $this->basedir = $runtimePath;
+ $this->enabled = true;
+ }
+ } elseif (is_dir($tmpDir) && is_writeable($tmpDir) && @mkdir($runtimePath, octdec('1750'), true)) {
+ // Suppress mkdir errors because it may error w/ no such file directory if the systemd private tmp directory
+ // for the web server has been removed
+ $this->basedir = $runtimePath;
+ $this->enabled = true;
+ }
+ }
+
+ /**
+ * Store the given content to the desired file name
+ *
+ * @param string $file new (relative) filename
+ * @param string $content the content to be stored
+ *
+ * @return bool whether the file has been stored
+ */
+ public function store($file, $content)
+ {
+ if (! $this->enabled) {
+ return false;
+ }
+
+ return file_put_contents($this->filename($file), $content);
+ }
+
+ /**
+ * Find out whether a given file exists
+ *
+ * @param string $file the (relative) filename
+ * @param int $newerThan optional timestamp to compare against
+ *
+ * @return bool whether such file exists
+ */
+ public function has($file, $newerThan = null)
+ {
+ if (! $this->enabled) {
+ return false;
+ }
+
+ $filename = $this->filename($file);
+
+ if (! file_exists($filename) || ! is_readable($filename)) {
+ return false;
+ }
+
+ if ($newerThan === null) {
+ return true;
+ }
+
+ $info = stat($filename);
+
+ if ($info === false) {
+ return false;
+ }
+
+ return (int) $newerThan < $info['mtime'];
+ }
+
+ /**
+ * Get a specific file or false if no such file available
+ *
+ * @param string $file the disired file name
+ *
+ * @return string|bool Filename content or false
+ */
+ public function get($file)
+ {
+ if ($this->has($file)) {
+ return file_get_contents($this->filename($file));
+ }
+
+ return false;
+ }
+
+ /**
+ * Send a specific file to the browser (output)
+ *
+ * @param string $file the disired file name
+ *
+ * @return bool Whether the file has been sent
+ */
+ public function send($file)
+ {
+ if ($this->has($file)) {
+ readfile($this->filename($file));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get absolute filename for a given file
+ *
+ * @param string $file the disired file name
+ *
+ * @return string absolute filename
+ */
+ protected function filename($file)
+ {
+ return $this->basedir . '/' . $file;
+ }
+
+ /**
+ * Prepare a sub directory with the given name and return its path
+ *
+ * @param string $name
+ *
+ * @return string|false Returns FALSE in case the cache is not enabled or an error occurred
+ */
+ public function directory($name)
+ {
+ if (! $this->enabled) {
+ return false;
+ }
+
+ $path = $this->filename($name);
+ if (! is_dir($path) && ! @mkdir($path, octdec('1750'), true)) {
+ return false;
+ }
+
+ return $path;
+ }
+
+ /**
+ * Whether the given ETag matches a cached file
+ *
+ * If no ETag is given we'll try to fetch the one from the current
+ * HTTP request.
+ *
+ * @param string $file The cached file you want to check
+ * @param string $match The ETag to match against
+ *
+ * @return string|bool ETag on match, otherwise false
+ */
+ public function etagMatchesCachedFile($file, $match = null)
+ {
+ return self::etagMatchesFiles($this->filename($file), $match);
+ }
+
+ /**
+ * Create an ETag for the given file
+ *
+ * @param string $file The desired cache file
+ *
+ * @return string your ETag
+ */
+ public function etagForCachedFile($file)
+ {
+ return self::etagForFiles($this->filename($file));
+ }
+
+ /**
+ * Whether the given ETag matchesspecific file(s) on disk
+ *
+ * @param string|array $files file(s) to check
+ * @param string $match ETag to match against
+ *
+ * @return string|bool ETag on match, otherwise false
+ */
+ public static function etagMatchesFiles($files, $match = null)
+ {
+ if ($match === null) {
+ $match = isset($_SERVER['HTTP_IF_NONE_MATCH'])
+ ? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"')
+ : false;
+ }
+ if (! $match) {
+ return false;
+ }
+
+ if (preg_match('/([0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8})-\w+/i', $match, $matches)) {
+ // Removes compression suffixes as our custom algorithm can't handle compressed cache files anyway
+ $match = $matches[1];
+ }
+
+ $etag = self::etagForFiles($files);
+ return $match === $etag ? $etag : false;
+ }
+
+ /**
+ * Create ETag for the given files
+ *
+ * Custom algorithm creating an ETag based on filenames, mtimes
+ * and file sizes. Supports single files or a list of files. This
+ * way we are able to create ETags for virtual files depending on
+ * multiple source files (e.g. compressed JS, CSS).
+ *
+ * @param string|array $files Single file or a list of such
+ *
+ * @return string The generated ETag
+ */
+ public static function etagForFiles($files)
+ {
+ if (is_string($files)) {
+ $files = array($files);
+ }
+
+ $sizes = array();
+ $mtimes = array();
+
+ foreach ($files as $file) {
+ $file = realpath($file);
+ if ($file !== false && $info = stat($file)) {
+ $mtimes[] = $info['mtime'];
+ $sizes[] = $info['size'];
+ } else {
+ $mtimes[] = time();
+ $sizes[] = 0;
+ }
+ }
+
+ return sprintf(
+ '%s-%s-%s',
+ hash('crc32', implode('|', $files)),
+ hash('crc32', implode('|', $sizes)),
+ hash('crc32', implode('|', $mtimes))
+ );
+ }
+
+ /**
+ * Factory creating your cache instance
+ *
+ * @param string $name Instance name
+ *
+ * @return FileCache
+ */
+ public static function instance($name = 'icingaweb')
+ {
+ if ($name !== 'icingaweb') {
+ $name = 'icingaweb/modules/' . $name;
+ }
+
+ if (!array_key_exists($name, self::$instances)) {
+ self::$instances[$name] = new static($name);
+ }
+
+ return self::$instances[$name];
+ }
+}
diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php
new file mode 100644
index 0000000..b421849
--- /dev/null
+++ b/library/Icinga/Web/Form.php
@@ -0,0 +1,1666 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Web\Form\Element\DateTimePicker;
+use ipl\I18n\Translation;
+use Zend_Config;
+use Zend_Form;
+use Zend_Form_Element;
+use Zend_View_Interface;
+use Icinga\Application\Icinga;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Security\SecurityException;
+use Icinga\Web\Form\ErrorLabeller;
+use Icinga\Web\Form\Decorator\Autosubmit;
+use Icinga\Web\Form\Element\CsrfCounterMeasure;
+
+/**
+ * Base class for forms providing CSRF protection, confirmation logic and auto submission
+ *
+ * @method \Zend_Form_Element[] getElements() {
+ * {@inheritdoc}
+ * @return \Zend_Form_Element[]
+ * }
+ */
+class Form extends Zend_Form
+{
+ use Translation {
+ translate as i18nTranslate;
+ translatePlural as i18nTranslatePlural;
+ }
+
+ /**
+ * The suffix to append to a field's hidden default field name
+ */
+ const DEFAULT_SUFFIX = '_default';
+
+ /**
+ * A form's default CSS classes
+ */
+ const DEFAULT_CLASSES = 'icinga-form icinga-controls';
+
+ /**
+ * Identifier for notifications of type error
+ */
+ const NOTIFICATION_ERROR = 0;
+
+ /**
+ * Identifier for notifications of type warning
+ */
+ const NOTIFICATION_WARNING = 1;
+
+ /**
+ * Identifier for notifications of type info
+ */
+ const NOTIFICATION_INFO = 2;
+
+ /**
+ * Whether this form has been created
+ *
+ * @var bool
+ */
+ protected $created = false;
+
+ /**
+ * This form's parent
+ *
+ * Gets automatically set upon calling addSubForm().
+ *
+ * @var Form
+ */
+ protected $_parent;
+
+ /**
+ * Whether the form is an API target
+ *
+ * When the form is an API target, the form evaluates as submitted if the request method equals the form method.
+ * That means, that the submit button and form identification are not taken into account. In addition, the CSRF
+ * counter measure will not be added to the form's elements.
+ *
+ * @var bool
+ */
+ protected $isApiTarget = false;
+
+ /**
+ * The request associated with this form
+ *
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * The callback to call instead of Form::onSuccess()
+ *
+ * @var callable
+ */
+ protected $onSuccess;
+
+ /**
+ * Label to use for the standard submit button
+ *
+ * @var string
+ */
+ protected $submitLabel;
+
+ /**
+ * Label to use for showing the user an activity indicator when submitting the form
+ *
+ * @var string
+ */
+ protected $progressLabel;
+
+ /**
+ * The url to redirect to upon success
+ *
+ * @var Url
+ */
+ protected $redirectUrl;
+
+ /**
+ * The view script to use when rendering this form
+ *
+ * @var string
+ */
+ protected $viewScript;
+
+ /**
+ * Whether this form should NOT add random generated "challenge" tokens that are associated with the user's current
+ * session in order to prevent Cross-Site Request Forgery (CSRF). It is the form's responsibility to verify the
+ * existence and correctness of this token
+ *
+ * @var bool
+ */
+ protected $tokenDisabled = false;
+
+ /**
+ * Name of the CSRF token element
+ *
+ * @var string
+ */
+ protected $tokenElementName = 'CSRFToken';
+
+ /**
+ * Whether this form should add a UID element being used to distinct different forms posting to the same action
+ *
+ * @var bool
+ */
+ protected $uidDisabled = false;
+
+ /**
+ * Name of the form identification element
+ *
+ * @var string
+ */
+ protected $uidElementName = 'formUID';
+
+ /**
+ * Whether the form should validate the sent data when being automatically submitted
+ *
+ * @var bool
+ */
+ protected $validatePartial = false;
+
+ /**
+ * Whether element ids will be protected against collisions by appending a request-specific unique identifier
+ *
+ * @var bool
+ */
+ protected $protectIds = true;
+
+ /**
+ * The cue that is appended to each element's label if it's required
+ *
+ * @var string
+ */
+ protected $requiredCue = '*';
+
+ /**
+ * The descriptions of this form
+ *
+ * @var array
+ */
+ protected $descriptions;
+
+ /**
+ * The notifications of this form
+ *
+ * @var array
+ */
+ protected $notifications;
+
+ /**
+ * The hints of this form
+ *
+ * @var array
+ */
+ protected $hints;
+
+ /**
+ * Whether the Autosubmit decorator should be applied to this form
+ *
+ * If this is true, the Autosubmit decorator is being applied to this form instead of to each of its elements.
+ *
+ * @var bool
+ */
+ protected $useFormAutosubmit = false;
+
+ /**
+ * Authentication manager
+ *
+ * @var Auth|null
+ */
+ private $auth;
+
+ /**
+ * Default element decorators
+ *
+ * @var array
+ */
+ public static $defaultElementDecorators = array(
+ array('Label', array('tag'=>'span', 'separator' => '', 'class' => 'control-label')),
+ array(array('labelWrap' => 'HtmlTag'), array('tag' => 'div', 'class' => 'control-label-group')),
+ array('ViewHelper', array('separator' => '')),
+ array('Help', array()),
+ array('Errors', array('separator' => '')),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group'))
+ );
+
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::construct() For the method documentation.
+ */
+ public function __construct($options = null)
+ {
+ // Zend's plugin loader reverses the order of added prefix paths thus trying our paths first before trying
+ // Zend paths
+ $this->addPrefixPaths(array(
+ array(
+ 'prefix' => 'Icinga\\Web\\Form\\Element\\',
+ 'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Element'),
+ 'type' => static::ELEMENT
+ ),
+ array(
+ 'prefix' => 'Icinga\\Web\\Form\\Decorator\\',
+ 'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Decorator'),
+ 'type' => static::DECORATOR
+ )
+ ));
+
+ if (! isset($options['attribs']['class'])) {
+ $options['attribs']['class'] = static::DEFAULT_CLASSES;
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Set this form's parent
+ *
+ * @param Form $form
+ *
+ * @return $this
+ */
+ public function setParent(Form $form)
+ {
+ $this->_parent = $form;
+ return $this;
+ }
+
+ /**
+ * Return this form's parent
+ *
+ * @return Form
+ */
+ public function getParent()
+ {
+ return $this->_parent;
+ }
+
+ /**
+ * Set a callback that is called instead of this form's onSuccess method
+ *
+ * It is called using the following signature: (Form $this).
+ *
+ * @param callable $onSuccess Callback
+ *
+ * @return $this
+ *
+ * @throws ProgrammingError If the callback is not callable
+ */
+ public function setOnSuccess($onSuccess)
+ {
+ if (! is_callable($onSuccess)) {
+ throw new ProgrammingError('The option `onSuccess\' is not callable');
+ }
+ $this->onSuccess = $onSuccess;
+ return $this;
+ }
+
+ /**
+ * Set the label to use for the standard submit button
+ *
+ * @param string $label The label to use for the submit button
+ *
+ * @return $this
+ */
+ public function setSubmitLabel($label)
+ {
+ $this->submitLabel = $label;
+ return $this;
+ }
+
+ /**
+ * Return the label being used for the standard submit button
+ *
+ * @return string
+ */
+ public function getSubmitLabel()
+ {
+ return $this->submitLabel;
+ }
+
+ /**
+ * Set the label to use for showing the user an activity indicator when submitting the form
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setProgressLabel($label)
+ {
+ $this->progressLabel = $label;
+ return $this;
+ }
+
+ /**
+ * Return the label to use for showing the user an activity indicator when submitting the form
+ *
+ * @return string
+ */
+ public function getProgressLabel()
+ {
+ return $this->progressLabel;
+ }
+
+ /**
+ * Set the url to redirect to upon success
+ *
+ * @param string|Url $url The url to redirect to
+ *
+ * @return $this
+ *
+ * @throws ProgrammingError In case $url is neither a string nor a instance of Icinga\Web\Url
+ */
+ public function setRedirectUrl($url)
+ {
+ if (is_string($url)) {
+ $url = Url::fromPath($url, array(), $this->getRequest());
+ } elseif (! $url instanceof Url) {
+ throw new ProgrammingError('$url must be a string or instance of Icinga\Web\Url');
+ }
+
+ $this->redirectUrl = $url;
+ return $this;
+ }
+
+ /**
+ * Return the url to redirect to upon success
+ *
+ * @return Url
+ */
+ public function getRedirectUrl()
+ {
+ if ($this->redirectUrl === null) {
+ $this->redirectUrl = $this->getRequest()->getUrl();
+ if ($this->getMethod() === 'get') {
+ // Be sure to remove all form dependent params because we do not want to submit it again
+ $this->redirectUrl = $this->redirectUrl->without(array_keys($this->getElements()));
+ }
+ }
+
+ return $this->redirectUrl;
+ }
+
+ /**
+ * Set the view script to use when rendering this form
+ *
+ * @param string $viewScript The view script to use
+ *
+ * @return $this
+ */
+ public function setViewScript($viewScript)
+ {
+ $this->viewScript = $viewScript;
+ return $this;
+ }
+
+ /**
+ * Return the view script being used when rendering this form
+ *
+ * @return string
+ */
+ public function getViewScript()
+ {
+ return $this->viewScript;
+ }
+
+ /**
+ * Disable CSRF counter measure and remove its field if already added
+ *
+ * @param bool $disabled Set true in order to disable CSRF protection for this form, otherwise false
+ *
+ * @return $this
+ */
+ public function setTokenDisabled($disabled = true)
+ {
+ $this->tokenDisabled = (bool) $disabled;
+
+ if ($disabled && $this->getElement($this->tokenElementName) !== null) {
+ $this->removeElement($this->tokenElementName);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether CSRF counter measures are disabled for this form
+ *
+ * @return bool
+ */
+ public function getTokenDisabled()
+ {
+ return $this->tokenDisabled;
+ }
+
+ /**
+ * Set the name to use for the CSRF element
+ *
+ * @param string $name The name to set
+ *
+ * @return $this
+ */
+ public function setTokenElementName($name)
+ {
+ $this->tokenElementName = $name;
+ return $this;
+ }
+
+ /**
+ * Return the name of the CSRF element
+ *
+ * @return string
+ */
+ public function getTokenElementName()
+ {
+ return $this->tokenElementName;
+ }
+
+ /**
+ * Disable form identification and remove its field if already added
+ *
+ * @param bool $disabled Set true in order to disable identification for this form, otherwise false
+ *
+ * @return $this
+ */
+ public function setUidDisabled($disabled = true)
+ {
+ $this->uidDisabled = (bool) $disabled;
+
+ if ($disabled && $this->getElement($this->uidElementName) !== null) {
+ $this->removeElement($this->uidElementName);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether identification is disabled for this form
+ *
+ * @return bool
+ */
+ public function getUidDisabled()
+ {
+ return $this->uidDisabled;
+ }
+
+ /**
+ * Set the name to use for the form identification element
+ *
+ * @param string $name The name to set
+ *
+ * @return $this
+ */
+ public function setUidElementName($name)
+ {
+ $this->uidElementName = $name;
+ return $this;
+ }
+
+ /**
+ * Return the name of the form identification element
+ *
+ * @return string
+ */
+ public function getUidElementName()
+ {
+ return $this->uidElementName;
+ }
+
+ /**
+ * Set whether this form should validate the sent data when being automatically submitted
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setValidatePartial($state)
+ {
+ $this->validatePartial = $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this form should validate the sent data when being automatically submitted
+ *
+ * @return bool
+ */
+ public function getValidatePartial()
+ {
+ return $this->validatePartial;
+ }
+
+ /**
+ * Set whether each element's id should be altered to avoid duplicates
+ *
+ * @param bool $value
+ *
+ * @return Form
+ */
+ public function setProtectIds($value = true)
+ {
+ $this->protectIds = (bool) $value;
+ return $this;
+ }
+
+ /**
+ * Return whether each element's id is being altered to avoid duplicates
+ *
+ * @return bool
+ */
+ public function getProtectIds()
+ {
+ return $this->protectIds;
+ }
+
+ /**
+ * Set the cue to append to each element's label if it's required
+ *
+ * @param string $cue
+ *
+ * @return Form
+ */
+ public function setRequiredCue($cue)
+ {
+ $this->requiredCue = $cue;
+ return $this;
+ }
+
+ /**
+ * Return the cue being appended to each element's label if it's required
+ *
+ * @return string
+ */
+ public function getRequiredCue()
+ {
+ return $this->requiredCue;
+ }
+
+ /**
+ * Set the descriptions for this form
+ *
+ * @param array $descriptions
+ *
+ * @return Form
+ */
+ public function setDescriptions(array $descriptions)
+ {
+ $this->descriptions = $descriptions;
+ return $this;
+ }
+
+ /**
+ * Add a description for this form
+ *
+ * If $description is an array the second value should be
+ * an array as well containing additional HTML properties.
+ *
+ * @param string|array $description
+ *
+ * @return Form
+ */
+ public function addDescription($description)
+ {
+ $this->descriptions[] = $description;
+ return $this;
+ }
+
+ /**
+ * Return the descriptions of this form
+ *
+ * @return array
+ */
+ public function getDescriptions()
+ {
+ if ($this->descriptions === null) {
+ return array();
+ }
+
+ return $this->descriptions;
+ }
+
+ /**
+ * Set the notifications for this form
+ *
+ * @param array $notifications
+ *
+ * @return $this
+ */
+ public function setNotifications(array $notifications)
+ {
+ $this->notifications = $notifications;
+ return $this;
+ }
+
+ /**
+ * Add a notification for this form
+ *
+ * If $notification is an array the second value should be
+ * an array as well containing additional HTML properties.
+ *
+ * @param string|array $notification
+ * @param int $type
+ *
+ * @return $this
+ */
+ public function addNotification($notification, $type)
+ {
+ $this->notifications[$type][] = $notification;
+ return $this;
+ }
+
+ /**
+ * Return the notifications of this form
+ *
+ * @return array
+ */
+ public function getNotifications()
+ {
+ if ($this->notifications === null) {
+ return array();
+ }
+
+ return $this->notifications;
+ }
+
+ /**
+ * Set the hints for this form
+ *
+ * @param array $hints
+ *
+ * @return $this
+ */
+ public function setHints(array $hints)
+ {
+ $this->hints = $hints;
+ return $this;
+ }
+
+ /**
+ * Add a hint for this form
+ *
+ * If $hint is an array the second value should be an
+ * array as well containing additional HTML properties.
+ *
+ * @param string|array $hint
+ *
+ * @return $this
+ */
+ public function addHint($hint)
+ {
+ $this->hints[] = $hint;
+ return $this;
+ }
+
+ /**
+ * Return the hints of this form
+ *
+ * @return array
+ */
+ public function getHints()
+ {
+ if ($this->hints === null) {
+ return array();
+ }
+
+ return $this->hints;
+ }
+
+ /**
+ * Set whether the Autosubmit decorator should be applied to this form
+ *
+ * If true, the Autosubmit decorator is being applied to this form instead of to each of its elements.
+ *
+ * @param bool $state
+ *
+ * @return Form
+ */
+ public function setUseFormAutosubmit($state = true)
+ {
+ $this->useFormAutosubmit = (bool) $state;
+ if ($this->useFormAutosubmit) {
+ $this->setAttrib('data-progress-element', 'header-' . $this->getId());
+ } else {
+ $this->removeAttrib('data-progress-element');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether the Autosubmit decorator is being applied to this form
+ *
+ * @return bool
+ */
+ public function getUseFormAutosubmit()
+ {
+ return $this->useFormAutosubmit;
+ }
+
+ /**
+ * Get whether the form is an API target
+ *
+ * @todo This should probably only return true if the request is also an api request
+ * @return bool
+ */
+ public function getIsApiTarget()
+ {
+ return $this->isApiTarget;
+ }
+
+ /**
+ * Set whether the form is an API target
+ *
+ * @param bool $isApiTarget
+ *
+ * @return $this
+ */
+ public function setIsApiTarget($isApiTarget = true)
+ {
+ $this->isApiTarget = (bool) $isApiTarget;
+ return $this;
+ }
+
+ /**
+ * Create this form
+ *
+ * @param array $formData The data sent by the user
+ *
+ * @return $this
+ */
+ public function create(array $formData = array())
+ {
+ if (! $this->created) {
+ $this->createElements($formData);
+ $this->addFormIdentification()
+ ->addCsrfCounterMeasure()
+ ->addSubmitButton();
+
+ // Use Form::getAttrib() instead of Form::getAction() here because we want to explicitly check against
+ // null. Form::getAction() would return the empty string '' if the action is not set.
+ // For not setting the action attribute use Form::setAction(''). This is required for for the
+ // accessibility's enable/disable auto-refresh mechanic
+ if ($this->getAttrib('action') === null) {
+ $action = $this->getRequest()->getUrl();
+ if ($this->getMethod() === 'get') {
+ $action = $action->without(array_keys($this->getElements()));
+ }
+
+ // TODO(el): Re-evalute this necessity.
+ // JavaScript could use the container'sURL if there's no action set.
+ // We MUST set an action as JS gets confused otherwise, if
+ // this form is being displayed in an additional column
+ $this->setAction($action);
+ }
+
+ $this->created = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * Intended to be implemented by concrete form classes.
+ *
+ * @param array $formData The data sent by the user
+ */
+ public function createElements(array $formData)
+ {
+ }
+
+ /**
+ * Perform actions after this form was submitted using a valid request
+ *
+ * Intended to be implemented by concrete form classes. The base implementation returns always FALSE.
+ *
+ * @return null|bool Return FALSE in case no redirect should take place
+ */
+ public function onSuccess()
+ {
+ return false;
+ }
+
+ /**
+ * Perform actions when no form dependent data was sent
+ *
+ * Intended to be implemented by concrete form classes.
+ */
+ public function onRequest()
+ {
+ }
+
+ /**
+ * Add a submit button to this form
+ *
+ * Uses the label previously set with Form::setSubmitLabel(). Overwrite this
+ * method in order to add multiple submit buttons or one with a custom name.
+ *
+ * @return $this
+ */
+ public function addSubmitButton()
+ {
+ $submitLabel = $this->getSubmitLabel();
+ if ($submitLabel) {
+ $this->addElement(
+ 'submit',
+ 'btn_submit',
+ array(
+ 'class' => 'btn-primary',
+ 'ignore' => true,
+ 'label' => $submitLabel,
+ 'data-progress-label' => $this->getProgressLabel(),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('Spinner', array('separator' => '')),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a subform
+ *
+ * @param Zend_Form $form The subform to add
+ * @param string $name The name of the subform or null to use the name of $form
+ * @param int $order The location where to insert the form
+ *
+ * @return Zend_Form
+ */
+ public function addSubForm(Zend_Form $form, $name = null, $order = null)
+ {
+ if ($form instanceof self) {
+ $form->setDecorators(array('FormElements')); // TODO: Makes it difficult to customise subform decorators..
+ $form->setSubmitLabel('');
+ $form->setTokenDisabled();
+ $form->setUidDisabled();
+ $form->setParent($this);
+ }
+
+ if ($name === null) {
+ $name = $form->getName();
+ }
+
+ return parent::addSubForm($form, $name, $order);
+ }
+
+ /**
+ * Create a new element
+ *
+ * Icinga Web 2 loads its own default element decorators. For loading Zend's default element decorators set the
+ * `disableLoadDefaultDecorators' option to any other value than `true'. For loading custom element decorators use
+ * the 'decorators' option.
+ *
+ * @param string $type The type of the element
+ * @param string $name The name of the element
+ * @param mixed $options The options for the element
+ *
+ * @return Zend_Form_Element
+ *
+ * @see Form::$defaultElementDecorators For Icinga Web 2's default element decorators.
+ */
+ public function createElement($type, $name, $options = null)
+ {
+ if ($options !== null) {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+ if (! isset($options['decorators'])
+ && ! array_key_exists('disabledLoadDefaultDecorators', $options)
+ ) {
+ $options['decorators'] = static::$defaultElementDecorators;
+ if (! isset($options['data-progress-label']) && ($type === 'submit'
+ || ($type === 'button' && isset($options['type']) && $options['type'] === 'submit'))
+ ) {
+ array_splice($options['decorators'], 1, 0, array(array('Spinner', array('separator' => ''))));
+ } elseif ($type === 'hidden') {
+ $options['decorators'] = array('ViewHelper');
+ }
+ }
+ } else {
+ $options = array('decorators' => static::$defaultElementDecorators);
+ if ($type === 'submit') {
+ array_splice($options['decorators'], 1, 0, array(array('Spinner', array('separator' => ''))));
+ } elseif ($type === 'hidden') {
+ $options['decorators'] = array('ViewHelper');
+ }
+ }
+
+ $el = parent::createElement($type, $name, $options);
+ $el->setTranslator(new ErrorLabeller(array('element' => $el)));
+
+ $el->addPrefixPaths(array(
+ array(
+ 'prefix' => 'Icinga\\Web\\Form\\Validator\\',
+ 'path' => Icinga::app()->getLibraryDir('Icinga/Web/Form/Validator'),
+ 'type' => $el::VALIDATE
+ )
+ ));
+
+ if ($this->protectIds) {
+ $el->setAttrib('id', $this->getRequest()->protectId($this->getId(false) . '_' . $el->getId()));
+ }
+
+ if ($el->getAttrib('autosubmit')) {
+ if ($this->getUseFormAutosubmit()) {
+ $warningId = 'autosubmit_warning_' . $el->getId();
+ $warningText = $this->getView()->escape($this->translate(
+ 'This page will be automatically updated upon change of the value'
+ ));
+ $autosubmitDecorator = $this->_getDecorator('Callback', array(
+ 'placement' => 'PREPEND',
+ 'callback' => function ($content) use ($warningId, $warningText) {
+ return '<span class="sr-only" id="' . $warningId . '">' . $warningText . '</span>';
+ }
+ ));
+ } else {
+ $autosubmitDecorator = new Autosubmit();
+ $autosubmitDecorator->setAccessible();
+ $warningId = $autosubmitDecorator->getWarningId($el);
+ }
+
+ $decorators = $el->getDecorators();
+ $pos = array_search('Zend_Form_Decorator_ViewHelper', array_keys($decorators), true) + 1;
+ $el->setDecorators(
+ array_slice($decorators, 0, $pos, true)
+ + array('autosubmit' => $autosubmitDecorator)
+ + array_slice($decorators, $pos, count($decorators) - $pos, true)
+ );
+
+ if (($describedBy = $el->getAttrib('aria-describedby')) !== null) {
+ $el->setAttrib('aria-describedby', $describedBy . ' ' . $warningId);
+ } else {
+ $el->setAttrib('aria-describedby', $warningId);
+ }
+
+ $class = $el->getAttrib('class');
+ if (is_array($class)) {
+ $class[] = 'autosubmit';
+ } elseif ($class === null) {
+ $class = 'autosubmit';
+ } else {
+ $class .= ' autosubmit';
+ }
+ $el->setAttrib('class', $class);
+
+ unset($el->autosubmit);
+ }
+
+ if ($el->getAttrib('preserveDefault')) {
+ $el->addDecorator(
+ array('preserveDefault' => 'HtmlTag'),
+ array(
+ 'tag' => 'input',
+ 'type' => 'hidden',
+ 'name' => $name . static::DEFAULT_SUFFIX,
+ 'value' => $el instanceof DateTimePicker
+ ? $el->getValue()->format($el->getFormat())
+ : $el->getValue()
+ )
+ );
+
+ unset($el->preserveDefault);
+ }
+
+ return $this->ensureElementAccessibility($el);
+ }
+
+ /**
+ * Add accessibility related attributes
+ *
+ * @param Zend_Form_Element $element
+ *
+ * @return Zend_Form_Element
+ */
+ public function ensureElementAccessibility(Zend_Form_Element $element)
+ {
+ if ($element->isRequired()) {
+ $element->setAttrib('aria-required', 'true'); // ARIA
+ $element->setAttrib('required', ''); // HTML5
+ if (($cue = $this->getRequiredCue()) !== null && ($label = $element->getDecorator('label')) !== false) {
+ $element->setLabel($this->getView()->escape($element->getLabel()));
+ $label->setOption('escape', false);
+ $label->setRequiredSuffix(sprintf(' <span aria-hidden="true">%s</span>', $cue));
+ }
+ }
+
+ if ($element->getDescription() !== null && ($help = $element->getDecorator('help')) !== false) {
+ if (($describedBy = $element->getAttrib('aria-describedby')) !== null) {
+ // Assume that it's because of the element being of type autosubmit or
+ // that one who did set the property manually removes the help decorator
+ // in case it has already an aria-describedby property set
+ $element->setAttrib(
+ 'aria-describedby',
+ $help->setAccessible()->getDescriptionId($element) . ' ' . $describedBy
+ );
+ } else {
+ $element->setAttrib('aria-describedby', $help->setAccessible()->getDescriptionId($element));
+ }
+ }
+
+ return $element;
+ }
+
+ /**
+ * Add a field with a unique and form specific ID
+ *
+ * @return $this
+ */
+ public function addFormIdentification()
+ {
+ if (! $this->uidDisabled && $this->getElement($this->uidElementName) === null) {
+ $this->addElement(
+ 'hidden',
+ $this->uidElementName,
+ array(
+ 'ignore' => true,
+ 'value' => $this->getName(),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add CSRF counter measure field to this form
+ *
+ * @return $this
+ */
+ public function addCsrfCounterMeasure()
+ {
+ if (! $this->tokenDisabled) {
+ $request = $this->getRequest();
+ if (! $request->isXmlHttpRequest()
+ && ($this->getIsApiTarget() || $request->isApiRequest())
+ ) {
+ return $this;
+ }
+ if ($this->getElement($this->tokenElementName) === null) {
+ $this->addElement('CsrfCounterMeasure', $this->tokenElementName);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Creates the form if not created yet.
+ *
+ * @param array $values
+ *
+ * @return $this
+ */
+ public function setDefaults(array $values)
+ {
+ $this->create($values);
+ return parent::setDefaults($values);
+ }
+
+ /**
+ * Populate the elements with the given values
+ *
+ * @param array $defaults The values to populate the elements with
+ *
+ * @return $this
+ */
+ public function populate(array $defaults)
+ {
+ $this->create($defaults);
+ $this->preserveDefaults($this, $defaults);
+ return parent::populate($defaults);
+ }
+
+ /**
+ * Recurse the given form and unset all unchanged default values
+ *
+ * @param Zend_Form $form
+ * @param array $defaults
+ */
+ protected function preserveDefaults(Zend_Form $form, array &$defaults)
+ {
+ foreach ($form->getElements() as $name => $element) {
+ if ((array_key_exists($name, $defaults)
+ && array_key_exists($name . static::DEFAULT_SUFFIX, $defaults)
+ && $defaults[$name] === $defaults[$name . static::DEFAULT_SUFFIX])
+ || $element->getAttrib('disabled')
+ ) {
+ unset($defaults[$name]);
+ }
+ }
+
+ foreach ($form->getSubForms() as $_ => $subForm) {
+ $this->preserveDefaults($subForm, $defaults);
+ }
+ }
+
+ /**
+ * Process the given request using this form
+ *
+ * Redirects to the url set with setRedirectUrl() upon success. See onSuccess()
+ * and onRequest() wherewith you can customize the processing logic.
+ *
+ * @param Request $request The request to be processed
+ *
+ * @return Request The request supposed to be processed
+ */
+ public function handleRequest(Request $request = null)
+ {
+ if ($request === null) {
+ $request = $this->getRequest();
+ } else {
+ $this->request = $request;
+ }
+
+ $formData = $this->getRequestData();
+ if ($this->getIsApiTarget()
+ // TODO: Very very bad, wasSent() must not be bypassed if it's only an api request but not an qpi target
+ || $this->getRequest()->isApiRequest()
+ || $this->getUidDisabled()
+ || $this->wasSent($formData)
+ ) {
+ $this->populate($formData); // Necessary to get isSubmitted() to work
+ if (! $this->getSubmitLabel() || $this->isSubmitted()) {
+ if ($this->isValid($formData)
+ && (($this->onSuccess !== null && false !== call_user_func($this->onSuccess, $this))
+ || ($this->onSuccess === null && false !== $this->onSuccess()))
+ ) {
+ // TODO: Still bad. An api target must not behave as one if it's not an api request
+ if ($this->getIsApiTarget() || $this->getRequest()->isApiRequest()) {
+ // API targets and API requests will never redirect but immediately respond w/ JSON-encoded
+ // notifications
+ $notifications = Notification::getInstance()->popMessages();
+ $message = null;
+ foreach ($notifications as $notification) {
+ if ($notification->type === Notification::SUCCESS) {
+ $message = $notification->message;
+ break;
+ }
+ }
+ $this->getResponse()->json()
+ ->setSuccessData($message !== null ? array('message' => $message) : null)
+ ->sendResponse();
+ } else {
+ $this->getResponse()->redirectAndExit($this->getRedirectUrl());
+ }
+ // TODO: Still bad. An api target must not behave as one if it's not an api request
+ } elseif ($this->getIsApiTarget() || $this->getRequest()->isApiRequest()) {
+ $this->getResponse()->json()->setFailData($this->getMessages())->sendResponse();
+ }
+ } elseif ($this->getValidatePartial()) {
+ // The form can't be processed but we may want to show validation errors though
+ $this->isValidPartial($formData);
+ }
+ } else {
+ $this->onRequest();
+ }
+
+ return $request;
+ }
+
+ /**
+ * Return whether the submit button of this form was pressed
+ *
+ * When overwriting Form::addSubmitButton() be sure to overwrite this method as well.
+ *
+ * @return bool True in case it was pressed, False otherwise or no submit label was set
+ */
+ public function isSubmitted()
+ {
+ $requestMethod = $this->getRequest()->getMethod();
+ if (strtolower($requestMethod ?: '') !== $this->getMethod()) {
+ return false;
+ }
+ if ($this->getIsApiTarget() || $this->getRequest()->isApiRequest()) {
+ return true;
+ }
+ if ($this->getSubmitLabel()) {
+ return $this->getElement('btn_submit')->isChecked();
+ }
+
+ return false;
+ }
+
+ /**
+ * Return whether the data sent by the user refers to this form
+ *
+ * Ensures that the correct form gets processed in case there are multiple forms
+ * with equal submit button names being posted against the same route.
+ *
+ * @param array $formData The data sent by the user
+ *
+ * @return bool Whether the given data refers to this form
+ */
+ public function wasSent(array $formData)
+ {
+ return isset($formData[$this->uidElementName]) && $formData[$this->uidElementName] === $this->getName();
+ }
+
+ /**
+ * Return whether the given values (possibly incomplete) are valid
+ *
+ * Unlike Zend_Form::isValid() this will not set NULL as value for
+ * an element that is not present in the given data.
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ $this->create($formData);
+
+ foreach ($this->getElements() as $name => $element) {
+ if (array_key_exists($name, $formData)) {
+ if ($element->getAttrib('disabled')) {
+ // Ensure that disabled elements are not overwritten
+ // (http://www.zendframework.com/issues/browse/ZF-6909)
+ $formData[$name] = $element->getValue();
+ } elseif (array_key_exists($name . static::DEFAULT_SUFFIX, $formData)
+ && $formData[$name] === $formData[$name . static::DEFAULT_SUFFIX]
+ ) {
+ unset($formData[$name]);
+ }
+ }
+ }
+
+ return parent::isValidPartial($formData);
+ }
+
+ /**
+ * Return whether the given values are valid
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($formData)
+ {
+ $this->create($formData);
+
+ // Ensure that disabled elements are not overwritten (http://www.zendframework.com/issues/browse/ZF-6909)
+ foreach ($this->getElements() as $name => $element) {
+ if ($element->getAttrib('disabled')) {
+ $formData[$name] = $element->getValue();
+ }
+ }
+
+ return parent::isValid($formData);
+ }
+
+ /**
+ * Remove all elements of this form
+ *
+ * @return self
+ */
+ public function clearElements()
+ {
+ $this->created = false;
+ return parent::clearElements();
+ }
+
+ /**
+ * Load the default decorators
+ *
+ * Overwrites Zend_Form::loadDefaultDecorators to avoid having
+ * the HtmlTag-Decorator added and to provide view script usage
+ *
+ * @return $this
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ if ($this->viewScript) {
+ $this->addDecorator('ViewScript', array(
+ 'viewScript' => $this->viewScript,
+ 'form' => $this
+ ));
+ } else {
+ $this->addDecorator('Description', array('tag' => 'h1'));
+ if ($this->getUseFormAutosubmit()) {
+ $this->getDecorator('Description')->setEscape(false);
+ $this->addDecorator(
+ 'HtmlTag',
+ array(
+ 'tag' => 'div',
+ 'class' => 'header',
+ 'id' => 'header-' . $this->getId()
+ )
+ );
+ }
+
+ $this->addDecorator('FormDescriptions')
+ ->addDecorator('FormNotifications')
+ ->addDecorator('FormErrors', array('onlyCustomFormErrors' => true))
+ ->addDecorator('FormElements')
+ ->addDecorator('FormHints')
+ //->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form'))
+ ->addDecorator('Form');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get element id
+ *
+ * Returns the protected id, in case id protection is enabled.
+ *
+ * @param bool $protect
+ *
+ * @return string
+ */
+ public function getId($protect = true)
+ {
+ $id = parent::getId();
+ return $protect && $this->protectIds ? $this->getRequest()->protectId($id) : $id;
+ }
+
+ /**
+ * Return the name of this form
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ $name = parent::getName();
+ if (! $name) {
+ $name = get_class($this);
+ $this->setName($name);
+ $name = parent::getName();
+ }
+ return $name;
+ }
+
+ /**
+ * Retrieve form description
+ *
+ * This will return the escaped description with the autosubmit warning icon if form autosubmit is enabled.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ $description = parent::getDescription();
+ if ($description && $this->getUseFormAutosubmit()) {
+ $autosubmit = $this->_getDecorator('Autosubmit', array('accessible' => true));
+ $autosubmit->setElement($this);
+ $description = $autosubmit->render($this->getView()->escape($description));
+ }
+
+ return $description;
+ }
+
+ /**
+ * Set the action to submit this form against
+ *
+ * Note that if you'll pass a instance of URL, Url::getAbsoluteUrl('&') is called to set the action.
+ *
+ * @param Url|string $action
+ *
+ * @return $this
+ */
+ public function setAction($action)
+ {
+ if ($action instanceof Url) {
+ $action = $action->getAbsoluteUrl('&');
+ }
+
+ return parent::setAction($action);
+ }
+
+ /**
+ * Set form description
+ *
+ * Alias for Zend_Form::setDescription().
+ *
+ * @param string $value
+ *
+ * @return Form
+ */
+ public function setTitle($value)
+ {
+ return $this->setDescription($value);
+ }
+
+ /**
+ * Return the request associated with this form
+ *
+ * Returns the global request if none has been set for this form yet.
+ *
+ * @return Request
+ */
+ public function getRequest()
+ {
+ if ($this->request === null) {
+ $this->request = Icinga::app()->getRequest();
+ }
+
+ return $this->request;
+ }
+
+ /**
+ * Set the request
+ *
+ * @param Request $request
+ *
+ * @return $this
+ */
+ public function setRequest(Request $request)
+ {
+ $this->request = $request;
+ return $this;
+ }
+
+ /**
+ * Return the current Response
+ *
+ * @return Response
+ */
+ public function getResponse()
+ {
+ return Icinga::app()->getFrontController()->getResponse();
+ }
+
+ /**
+ * Return the request data based on this form's request method
+ *
+ * @return array
+ */
+ protected function getRequestData()
+ {
+ $requestMethod = $this->getRequest()->getMethod();
+ if (strtolower($requestMethod ?: '') === $this->getMethod()) {
+ return $this->request->{'get' . ($this->request->isPost() ? 'Post' : 'Query')}();
+ }
+
+ return array();
+ }
+
+ /**
+ * Get the translation domain for this form
+ *
+ * The returned translation domain is either determined based on this form's qualified name or it is the default
+ * 'icinga' domain
+ *
+ * @return string
+ */
+ protected function getTranslationDomain()
+ {
+ $parts = explode('\\', get_called_class());
+ if (count($parts) > 1 && $parts[1] === 'Module') {
+ // Assume format Icinga\Module\ModuleName\Forms\...
+ return strtolower($parts[2]);
+ }
+
+ return 'icinga';
+ }
+
+ /**
+ * Translate a string
+ *
+ * @param string $text The string to translate
+ * @param string|null $context Optional parameter for context based translation
+ *
+ * @return string The translated string
+ */
+ protected function translate($text, $context = null)
+ {
+ $this->translationDomain = $this->getTranslationDomain();
+
+ return $this->i18nTranslate($text, $context);
+ }
+
+ /**
+ * Translate a plural string
+ *
+ * @param string $textSingular The string in singular form to translate
+ * @param string $textPlural The string in plural form to translate
+ * @param integer $number The amount to determine from whether to return singular or plural
+ * @param string|null $context Optional parameter for context based translation
+ *
+ * @return string The translated string
+ */
+ protected function translatePlural($textSingular, $textPlural, $number, $context = null)
+ {
+ $this->translationDomain = $this->getTranslationDomain();
+
+ return $this->i18nTranslatePlural($textSingular, $textPlural, $number, $context);
+ }
+
+ /**
+ * Render this form
+ *
+ * @param Zend_View_Interface $view The view context to use
+ *
+ * @return string
+ */
+ public function render(Zend_View_Interface $view = null)
+ {
+ $this->create();
+ return parent::render($view);
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return Auth
+ */
+ public function Auth()
+ {
+ if ($this->auth === null) {
+ $this->auth = Auth::getInstance();
+ }
+ return $this->auth;
+ }
+
+ /**
+ * Whether the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @return bool
+ */
+ public function hasPermission($permission)
+ {
+ return $this->Auth()->hasPermission($permission);
+ }
+
+ /**
+ * Assert that the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @throws SecurityException If the current user lacks the given permission
+ */
+ public function assertPermission($permission)
+ {
+ if (! $this->Auth()->hasPermission($permission)) {
+ throw new SecurityException('No permission for %s', $permission);
+ }
+ }
+
+ /**
+ * Add a error notification
+ *
+ * @param string|array $message The notification message
+ * @param bool $markAsError Whether to prevent the form from being successfully validated or not
+ *
+ * @return $this
+ */
+ public function error($message, $markAsError = true)
+ {
+ if ($this->getIsApiTarget()) {
+ $this->addErrorMessage($message);
+ } else {
+ $this->addNotification($message, self::NOTIFICATION_ERROR);
+ }
+
+ if ($markAsError) {
+ $this->markAsError();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a warning notification
+ *
+ * @param string|array $message The notification message
+ * @param bool $markAsError Whether to prevent the form from being successfully validated or not
+ *
+ * @return $this
+ */
+ public function warning($message, $markAsError = true)
+ {
+ if ($this->getIsApiTarget()) {
+ $this->addErrorMessage($message);
+ } else {
+ $this->addNotification($message, self::NOTIFICATION_WARNING);
+ }
+
+ if ($markAsError) {
+ $this->markAsError();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a info notification
+ *
+ * @param string|array $message The notification message
+ * @param bool $markAsError Whether to prevent the form from being successfully validated or not
+ *
+ * @return $this
+ */
+ public function info($message, $markAsError = true)
+ {
+ if ($this->getIsApiTarget()) {
+ $this->addErrorMessage($message);
+ } else {
+ $this->addNotification($message, self::NOTIFICATION_INFO);
+ }
+
+ if ($markAsError) {
+ $this->markAsError();
+ }
+
+ return $this;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/Autosubmit.php b/library/Icinga/Web/Form/Decorator/Autosubmit.php
new file mode 100644
index 0000000..4405d0b
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/Autosubmit.php
@@ -0,0 +1,133 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+use Icinga\Web\Form;
+
+/**
+ * Decorator to add an icon and a submit button encapsulated in noscript-tags
+ *
+ * The icon is shown in JS environments to indicate that a specific form field does automatically request an update
+ * of its form upon it has changed. The button allows users in non-JS environments to trigger the update manually.
+ */
+class Autosubmit extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Whether a hidden <span> should be created with the same warning as in the icon label
+ *
+ * @var bool
+ */
+ protected $accessible;
+
+ /**
+ * The id used to identify the auto-submit warning associated with the decorated form element
+ *
+ * @var string
+ */
+ protected $warningId;
+
+ /**
+ * Set whether a hidden <span> should be created with the same warning as in the icon label
+ *
+ * @param bool $state
+ *
+ * @return Autosubmit
+ */
+ public function setAccessible($state = true)
+ {
+ $this->accessible = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether a hidden <span> is being created with the same warning as in the icon label
+ *
+ * @return bool
+ */
+ public function getAccessible()
+ {
+ if ($this->accessible === null) {
+ $this->accessible = $this->getOption('accessible') ?: false;
+ }
+
+ return $this->accessible;
+ }
+
+ /**
+ * Return the id used to identify the auto-submit warning associated with the decorated element
+ *
+ * @param mixed $element The element for which to generate a id
+ *
+ * @return string
+ */
+ public function getWarningId($element = null)
+ {
+ if ($this->warningId === null) {
+ $element = $element ?: $this->getElement();
+ $this->warningId = 'autosubmit_warning_' . $element->getId();
+ }
+
+ return $this->warningId;
+ }
+
+ /**
+ * Return the current view
+ *
+ * @return View
+ */
+ protected function getView()
+ {
+ return Icinga::app()->getViewRenderer()->view;
+ }
+
+ /**
+ * Add a auto-submit icon and submit button encapsulated in noscript-tags to the element
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ if ($content) {
+ $isForm = $this->getElement() instanceof Form;
+ $warning = $isForm
+ ? t('This page will be automatically updated upon change of any of this form\'s fields')
+ : t('This page will be automatically updated upon change of the value');
+ $content .= $this->getView()->icon('cw', $warning, array(
+ 'aria-hidden' => $isForm ? 'false' : 'true',
+ 'class' => 'spinner autosubmit-info'
+ ));
+ if (! $isForm && $this->getAccessible()) {
+ $content = '<span id="'
+ . $this->getWarningId()
+ . '" class="sr-only">'
+ . $warning
+ . '</span>'
+ . $content;
+ }
+
+ $content .= sprintf(
+ '<noscript><button'
+ . ' name="noscript_apply"'
+ . ' class="noscript-apply"'
+ . ' type="submit"'
+ . ' value="1"'
+ . ($this->getAccessible() ? ' aria-label="%1$s"' : '')
+ . ' title="%1$s"'
+ . '>%2$s</button></noscript>',
+ $isForm
+ ? t('Push this button to update the form to reflect the changes that were made below')
+ : t('Push this button to update the form to reflect the change'
+ . ' that was made in the field on the left'),
+ $this->getView()->icon('cw') . t('Apply')
+ );
+ }
+
+ return $content;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/ConditionalHidden.php b/library/Icinga/Web/Form/Decorator/ConditionalHidden.php
new file mode 100644
index 0000000..0f84535
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/ConditionalHidden.php
@@ -0,0 +1,35 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * Decorator to hide elements using a &gt;noscript&lt; tag instead of
+ * type='hidden' or css styles.
+ *
+ * This allows to hide depending elements for browsers with javascript
+ * (who can then automatically refresh their pages) but show them in
+ * case JavaScript is disabled
+ */
+class ConditionalHidden extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Generate a field that will be wrapped in <noscript> tag if the
+ * "condition" attribute is set and false or 0
+ *
+ * @param string $content The tag's content
+ *
+ * @return string The generated tag
+ */
+ public function render($content = '')
+ {
+ $attributes = $this->getElement()->getAttribs();
+ $condition = isset($attributes['condition']) ? $attributes['condition'] : 1;
+ if ($condition != 1) {
+ $content = '<noscript>' . $content . '</noscript>';
+ }
+ return $content;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/ElementDoubler.php b/library/Icinga/Web/Form/Decorator/ElementDoubler.php
new file mode 100644
index 0000000..2da5646
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/ElementDoubler.php
@@ -0,0 +1,63 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Element;
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * A decorator that will double a single element of a display group
+ *
+ * The options `condition', `double' and `attributes' can be passed to the constructor and are used to affect whether
+ * the doubling should take effect, which element should be doubled and which HTML attributes should be applied to the
+ * doubled element, respectively.
+ *
+ * `condition' must be an element's name that when it's part of the display group causes the condition to be met.
+ * `double' must be an element's name and must be part of the display group.
+ * `attributes' is just an array of key-value pairs.
+ *
+ * You can also pass `placement' to control whether the doubled element is prepended or appended.
+ */
+class ElementDoubler extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Return the display group's elements with an additional copy of an element being added if the condition is met
+ *
+ * @param string $content The HTML rendered so far
+ *
+ * @return string
+ */
+ public function render($content)
+ {
+ $group = $this->getElement();
+ if ($group->getElement($this->getOption('condition')) !== null) {
+ if ($this->getPlacement() === static::APPEND) {
+ return $content . $this->applyAttributes($group->getElement($this->getOption('double')))->render();
+ } else { // $this->getPlacement() === static::PREPEND
+ return $this->applyAttributes($group->getElement($this->getOption('double')))->render() . $content;
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Apply all element attributes
+ *
+ * @param Zend_Form_Element $element The element to apply the attributes to
+ *
+ * @return Zend_Form_Element
+ */
+ protected function applyAttributes(Zend_Form_Element $element)
+ {
+ $attributes = $this->getOption('attributes');
+ if ($attributes !== null) {
+ foreach ($attributes as $name => $value) {
+ $element->setAttrib($name, $value);
+ }
+ }
+
+ return $element;
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/FormDescriptions.php b/library/Icinga/Web/Form/Decorator/FormDescriptions.php
new file mode 100644
index 0000000..88ea5d9
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/FormDescriptions.php
@@ -0,0 +1,76 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Form;
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * Decorator to add a list of descriptions at the top or bottom of a form
+ */
+class FormDescriptions extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Render form descriptions
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $form = $this->getElement();
+ if (! $form instanceof Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if ($view === null) {
+ return $content;
+ }
+
+ $descriptions = $this->recurseForm($form);
+ if (empty($descriptions)) {
+ return $content;
+ }
+
+ $html = '<div class="form-description">'
+ . Icinga::app()->getViewRenderer()->view->icon('info-circled', '', ['class' => 'form-description-icon'])
+ . '<ul class="form-description-list">';
+
+ foreach ($descriptions as $description) {
+ if (is_array($description)) {
+ list($description, $properties) = $description;
+ $html .= '<li' . $view->propertiesToString($properties) . '>' . $view->escape($description) . '</li>';
+ } else {
+ $html .= '<li>' . $view->escape($description) . '</li>';
+ }
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $html . '</ul></div>';
+ case self::PREPEND:
+ return $html . '</ul></div>' . $content;
+ }
+ }
+
+ /**
+ * Recurse the given form and return the descriptions for it and all of its subforms
+ *
+ * @param Form $form The form to recurse
+ *
+ * @return array
+ */
+ protected function recurseForm(Form $form)
+ {
+ $descriptions = array($form->getDescriptions());
+ foreach ($form->getSubForms() as $subForm) {
+ $descriptions[] = $this->recurseForm($subForm);
+ }
+
+ return call_user_func_array('array_merge', $descriptions);
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/FormHints.php b/library/Icinga/Web/Form/Decorator/FormHints.php
new file mode 100644
index 0000000..797be26
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/FormHints.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+use Icinga\Web\Form;
+
+/**
+ * Decorator to add a list of hints at the top or bottom of a form
+ *
+ * The hint for required form elements is automatically being handled.
+ */
+class FormHints extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * A list of element class names to be ignored when detecting which message to use to describe required elements
+ *
+ * @var array
+ */
+ protected $blacklist;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct($options = null)
+ {
+ parent::__construct($options);
+ $this->blacklist = array(
+ 'Zend_Form_Element_Hidden',
+ 'Zend_Form_Element_Submit',
+ 'Zend_Form_Element_Button',
+ 'Icinga\Web\Form\Element\Note',
+ 'Icinga\Web\Form\Element\Button',
+ 'Icinga\Web\Form\Element\CsrfCounterMeasure'
+ );
+ }
+
+ /**
+ * Render form hints
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $form = $this->getElement();
+ if (! $form instanceof Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if ($view === null) {
+ return $content;
+ }
+
+ $hints = $this->recurseForm($form, $entirelyRequired);
+ if ($entirelyRequired !== null) {
+ $hints[] = sprintf(
+ $form->getView()->translate('%s Required field'),
+ $form->getRequiredCue()
+ );
+ }
+
+ if (empty($hints)) {
+ return $content;
+ }
+
+ $html = '<ul class="form-info">';
+ foreach ($hints as $hint) {
+ if (is_array($hint)) {
+ list($hint, $properties) = $hint;
+ $html .= '<li' . $view->propertiesToString($properties) . '>' . $view->escape($hint) . '</li>';
+ } else {
+ $html .= '<li>' . $view->escape($hint) . '</li>';
+ }
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $html . '</ul>';
+ case self::PREPEND:
+ return $html . '</ul>' . $content;
+ }
+ }
+
+ /**
+ * Recurse the given form and return the hints for it and all of its subforms
+ *
+ * @param Form $form The form to recurse
+ * @param mixed $entirelyRequired Set by reference, true means all elements in the hierarchy are
+ * required, false only a partial subset and null none at all
+ * @param bool $elementsPassed Whether there were any elements passed during the recursion until now
+ *
+ * @return array
+ */
+ protected function recurseForm(Form $form, &$entirelyRequired = null, $elementsPassed = false)
+ {
+ $requiredLabels = array();
+ if ($form->getRequiredCue() !== null) {
+ $partiallyRequired = $partiallyOptional = false;
+ foreach ($form->getElements() as $element) {
+ if (! in_array($element->getType(), $this->blacklist)) {
+ if (! $element->isRequired()) {
+ $partiallyOptional = true;
+ if ($entirelyRequired) {
+ $entirelyRequired = false;
+ }
+ } else {
+ $partiallyRequired = true;
+ if (($label = $element->getDecorator('label')) !== false) {
+ $requiredLabels[] = $label;
+ }
+ }
+ }
+ }
+
+ if (! $elementsPassed) {
+ $elementsPassed = $partiallyRequired || $partiallyOptional;
+ if ($entirelyRequired === null && $partiallyRequired) {
+ $entirelyRequired = ! $partiallyOptional;
+ }
+ } elseif ($entirelyRequired === null && $partiallyRequired) {
+ $entirelyRequired = false;
+ }
+ }
+
+ $hints = array($form->getHints());
+ foreach ($form->getSubForms() as $subForm) {
+ $hints[] = $this->recurseForm($subForm, $entirelyRequired, $elementsPassed);
+ }
+
+ if ($entirelyRequired) {
+ foreach ($requiredLabels as $label) {
+ $label->setRequiredSuffix('');
+ }
+ }
+
+ return call_user_func_array('array_merge', $hints);
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/FormNotifications.php b/library/Icinga/Web/Form/Decorator/FormNotifications.php
new file mode 100644
index 0000000..46734df
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/FormNotifications.php
@@ -0,0 +1,125 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Form;
+use Zend_Form_Decorator_Abstract;
+
+/**
+ * Decorator to add a list of notifications at the top or bottom of a form
+ */
+class FormNotifications extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Render form notifications
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $form = $this->getElement();
+ if (! $form instanceof Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if ($view === null) {
+ return $content;
+ }
+
+ $notifications = $this->recurseForm($form);
+ if (empty($notifications)) {
+ return $content;
+ }
+
+ $html = '<ul class="form-notification-list">';
+ foreach (array(Form::NOTIFICATION_ERROR, Form::NOTIFICATION_WARNING, Form::NOTIFICATION_INFO) as $type) {
+ if (isset($notifications[$type])) {
+ $html .= '<li><ul class="notification-' . $this->getNotificationTypeName($type) . '">';
+ foreach ($notifications[$type] as $message) {
+ if (is_array($message)) {
+ list($message, $properties) = $message;
+ $html .= '<li' . $view->propertiesToString($properties) . '>'
+ . $view->escape($message)
+ . '</li>';
+ } else {
+ $html .= '<li>' . $view->escape($message) . '</li>';
+ }
+ }
+
+ $html .= '</ul></li>';
+ }
+ }
+
+ if (isset($notifications[Form::NOTIFICATION_ERROR])) {
+ $icon = 'cancel';
+ $class = 'error';
+ } elseif (isset($notifications[Form::NOTIFICATION_WARNING])) {
+ $icon = 'warning-empty';
+ $class = 'warning';
+ } else {
+ $icon = 'info';
+ $class = 'info';
+ }
+
+ $html = "<div class=\"form-notifications $class\">"
+ . Icinga::app()->getViewRenderer()->view->icon($icon, '', ['class' => 'form-notification-icon'])
+ . $html;
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $html . '</ul></div>';
+ case self::PREPEND:
+ return $html . '</ul></div>' . $content;
+ }
+ }
+
+ /**
+ * Recurse the given form and return the notifications for it and all of its subforms
+ *
+ * @param Form $form The form to recurse
+ *
+ * @return array
+ */
+ protected function recurseForm(Form $form)
+ {
+ $notifications = $form->getNotifications();
+ foreach ($form->getSubForms() as $subForm) {
+ foreach ($this->recurseForm($subForm) as $type => $messages) {
+ foreach ($messages as $message) {
+ $notifications[$type][] = $message;
+ }
+ }
+ }
+
+ return $notifications;
+ }
+
+ /**
+ * Return the name for the given notification type
+ *
+ * @param int $type
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case the given type is invalid
+ */
+ protected function getNotificationTypeName($type)
+ {
+ switch ($type) {
+ case Form::NOTIFICATION_ERROR:
+ return 'error';
+ case Form::NOTIFICATION_WARNING:
+ return 'warning';
+ case Form::NOTIFICATION_INFO:
+ return 'info';
+ default:
+ throw new ProgrammingError('Invalid notification type "%s" provided', $type);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/Help.php b/library/Icinga/Web/Form/Decorator/Help.php
new file mode 100644
index 0000000..c521abe
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/Help.php
@@ -0,0 +1,113 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Element;
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+
+/**
+ * Decorator to add helptext to a form element
+ */
+class Help extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Whether a hidden <span> should be created to describe the decorated form element
+ *
+ * @var bool
+ */
+ protected $accessible = false;
+
+ /**
+ * The id used to identify the description associated with the decorated form element
+ *
+ * @var string
+ */
+ protected $descriptionId;
+
+ /**
+ * Set whether a hidden <span> should be created to describe the decorated form element
+ *
+ * @param bool $state
+ *
+ * @return Help
+ */
+ public function setAccessible($state = true)
+ {
+ $this->accessible = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return the id used to identify the description associated with the decorated element
+ *
+ * @param Zend_Form_Element $element The element for which to generate a id
+ *
+ * @return string
+ */
+ public function getDescriptionId(Zend_Form_Element $element = null)
+ {
+ if ($this->descriptionId === null) {
+ $element = $element ?: $this->getElement();
+ $this->descriptionId = 'desc_' . $element->getId();
+ }
+
+ return $this->descriptionId;
+ }
+
+ /**
+ * Return the current view
+ *
+ * @return View
+ */
+ protected function getView()
+ {
+ return Icinga::app()->getViewRenderer()->view;
+ }
+
+ /**
+ * Add a help icon to the left of an element
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $element = $this->getElement();
+ $description = $element->getDescription();
+ $requirement = $element->getAttrib('requirement');
+ unset($element->requirement);
+
+ $helpContent = '';
+ if ($description || $requirement) {
+ if ($this->accessible) {
+ $helpContent = '<span id="'
+ . $this->getDescriptionId()
+ . '" class="sr-only">'
+ . $description
+ . ($description && $requirement ? ' ' : '')
+ . $requirement
+ . '</span>';
+ }
+
+ $helpContent = $this->getView()->icon(
+ 'info-circled',
+ $description . ($description && $requirement ? ' ' : '') . $requirement,
+ array(
+ 'class' => 'control-info',
+ 'aria-hidden' => $this->accessible ? 'true' : 'false'
+ )
+ ) . $helpContent;
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $helpContent;
+ case self::PREPEND:
+ return $helpContent . $content;
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Decorator/Spinner.php b/library/Icinga/Web/Form/Decorator/Spinner.php
new file mode 100644
index 0000000..9cfa568
--- /dev/null
+++ b/library/Icinga/Web/Form/Decorator/Spinner.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Decorator;
+
+use Zend_Form_Decorator_Abstract;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+
+/**
+ * Decorator to add a spinner next to an element
+ */
+class Spinner extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Return the current view
+ *
+ * @return View
+ */
+ protected function getView()
+ {
+ return Icinga::app()->getViewRenderer()->view;
+ }
+
+ /**
+ * Add a spinner icon to a form element
+ *
+ * @param string $content The html rendered so far
+ *
+ * @return string The updated html
+ */
+ public function render($content = '')
+ {
+ $spinner = '<div '
+ . ($this->getOption('id') !== null ? ' id="' . $this->getOption('id') . '"' : '')
+ . 'class="spinner ' . ($this->getOption('class') ?: '') . '"'
+ . '>'
+ . $this->getView()->icon('spin6')
+ . '</div>';
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $spinner;
+ case self::PREPEND:
+ return $spinner . $content;
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Button.php b/library/Icinga/Web/Form/Element/Button.php
new file mode 100644
index 0000000..9cbe915
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Button.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Request;
+use Icinga\Application\Icinga;
+use Icinga\Web\Form\FormElement;
+
+/**
+ * A button
+ */
+class Button extends FormElement
+{
+ /**
+ * Use formButton view helper by default
+ *
+ * @var string
+ */
+ public $helper = 'formButton';
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $spec Element name or configuration
+ * @param string|array|Zend_Config $options Element value or configuration
+ */
+ public function __construct($spec, $options = null)
+ {
+ if (is_string($spec) && ((null !== $options) && is_string($options))) {
+ $options = array('label' => $options);
+ }
+
+ if (!isset($options['ignore'])) {
+ $options['ignore'] = true;
+ }
+
+ parent::__construct($spec, $options);
+
+ if ($label = $this->getLabel()) {
+ // Necessary to get the label shown on the generated HTML
+ $this->content = $label;
+ }
+ }
+
+ /**
+ * Validate element value (pseudo)
+ *
+ * There is no need to reset the value
+ *
+ * @param mixed $value Is always ignored
+ * @param mixed $context Is always ignored
+ *
+ * @return bool Returns always TRUE
+ */
+ public function isValid($value, $context = null)
+ {
+ return true;
+ }
+
+ /**
+ * Has this button been selected?
+ *
+ * @return bool
+ */
+ public function isChecked()
+ {
+ return $this->getRequest()->getParam($this->getName()) === $this->getValue();
+ }
+
+ /**
+ * Return the current request
+ *
+ * @return Request
+ */
+ protected function getRequest()
+ {
+ return Icinga::app()->getRequest();
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Checkbox.php b/library/Icinga/Web/Form/Element/Checkbox.php
new file mode 100644
index 0000000..d4499a0
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Checkbox.php
@@ -0,0 +1,9 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+class Checkbox extends \Zend_Form_Element_Checkbox
+{
+ public $helper = 'icingaCheckbox';
+}
diff --git a/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php b/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php
new file mode 100644
index 0000000..c59e1f9
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/CsrfCounterMeasure.php
@@ -0,0 +1,99 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Session;
+use Icinga\Web\Form\FormElement;
+use Icinga\Web\Form\InvalidCSRFTokenException;
+
+/**
+ * CSRF counter measure element
+ *
+ * You must not set a value to successfully use this element, just give it a name and you're good to go.
+ */
+class CsrfCounterMeasure extends FormElement
+{
+ /**
+ * Default form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formHidden';
+
+ /**
+ * Counter measure element is required
+ *
+ * @var bool
+ */
+ protected $_ignore = true;
+
+ /**
+ * Ignore element when retrieving values at form level
+ *
+ * @var bool
+ */
+ protected $_required = true;
+
+ /**
+ * Initialize this form element
+ */
+ public function init()
+ {
+ $this->setDecorators(['ViewHelper']);
+ $this->setValue($this->generateCsrfToken());
+ }
+
+ /**
+ * Check whether $value is a valid CSRF token
+ *
+ * @param string $value The value to check
+ * @param mixed $context Context to use
+ *
+ * @return bool True, in case the CSRF token is valid
+ *
+ * @throws InvalidCSRFTokenException In case the CSRF token is not valid
+ */
+ public function isValid($value, $context = null)
+ {
+ if (parent::isValid($value, $context) && $this->isValidCsrfToken($value)) {
+ return true;
+ }
+
+ throw new InvalidCSRFTokenException();
+ }
+
+ /**
+ * Check whether the given value is a valid CSRF token for the current session
+ *
+ * @param string $token The CSRF token
+ *
+ * @return bool
+ */
+ protected function isValidCsrfToken($token)
+ {
+ if (strpos($token, '|') === false) {
+ return false;
+ }
+
+ list($seed, $hash) = explode('|', $token);
+
+ if (false === is_numeric($seed)) {
+ return false;
+ }
+
+ return $hash === hash('sha256', Session::getSession()->getId() . $seed);
+ }
+
+ /**
+ * Generate a new (seed, token) pair
+ *
+ * @return string
+ */
+ protected function generateCsrfToken()
+ {
+ $seed = mt_rand();
+ $hash = hash('sha256', Session::getSession()->getId() . $seed);
+ return sprintf('%s|%s', $seed, $hash);
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Date.php b/library/Icinga/Web/Form/Element/Date.php
new file mode 100644
index 0000000..8e0985c
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Date.php
@@ -0,0 +1,19 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Form\FormElement;
+
+/**
+ * A date input control
+ */
+class Date extends FormElement
+{
+ /**
+ * Form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formDate';
+}
diff --git a/library/Icinga/Web/Form/Element/DateTimePicker.php b/library/Icinga/Web/Form/Element/DateTimePicker.php
new file mode 100644
index 0000000..284a744
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/DateTimePicker.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use DateTime;
+use Icinga\Web\Form\FormElement;
+use Icinga\Web\Form\Validator\DateTimeValidator;
+
+/**
+ * A date-and-time input control
+ */
+class DateTimePicker extends FormElement
+{
+ /**
+ * Form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formDateTime';
+
+ /**
+ * @var bool
+ */
+ protected $local = true;
+
+ /**
+ * (non-PHPDoc)
+ * @see Zend_Form_Element::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->addValidator(
+ new DateTimeValidator($this->local),
+ true // true for breaking the validator chain on failure
+ );
+ }
+
+ /**
+ * Get the expected date and time format of any user input
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->local ? 'Y-m-d\TH:i:s' : DateTime::RFC3339;
+ }
+
+ /**
+ * Is the date and time valid?
+ *
+ * @param string|DateTime $value
+ * @param mixed $context
+ *
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ if (is_scalar($value) && $value !== '' && ! preg_match('/\D/', $value)) {
+ $dateTime = new DateTime();
+ $value = $dateTime->setTimestamp($value)->format($this->getFormat());
+ }
+
+ if (! parent::isValid($value, $context)) {
+ return false;
+ }
+
+ if (! $value instanceof DateTime) {
+ $format = $this->getFormat();
+ $dateTime = DateTime::createFromFormat($format, $value);
+ if ($dateTime === false) {
+ $dateTime = DateTime::createFromFormat(substr($format, 0, strrpos($format, ':')), $value);
+ }
+
+ $this->setValue($dateTime);
+ }
+
+ return true;
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Note.php b/library/Icinga/Web/Form/Element/Note.php
new file mode 100644
index 0000000..9569dee
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Note.php
@@ -0,0 +1,55 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Form\FormElement;
+
+/**
+ * A note
+ */
+class Note extends FormElement
+{
+ /**
+ * Form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formNote';
+
+ /**
+ * Ignore element when retrieving values at form level
+ *
+ * @var bool
+ */
+ protected $_ignore = true;
+
+ /**
+ * (non-PHPDoc)
+ * @see Zend_Form_Element::init() For the method documentation.
+ */
+ public function init()
+ {
+ if (count($this->getDecorators()) === 0) {
+ $this->setDecorators(array(
+ 'ViewHelper',
+ array(
+ 'HtmlTag',
+ array('tag' => 'p')
+ )
+ ));
+ }
+ }
+
+ /**
+ * Validate element value (pseudo)
+ *
+ * @param mixed $value Ignored
+ *
+ * @return bool Always true
+ */
+ public function isValid($value, $context = null)
+ {
+ return true;
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Number.php b/library/Icinga/Web/Form/Element/Number.php
new file mode 100644
index 0000000..afbd07d
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Number.php
@@ -0,0 +1,144 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Form\FormElement;
+
+/**
+ * A number input control
+ */
+class Number extends FormElement
+{
+ /**
+ * Form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formNumber';
+
+ /**
+ * The expected lower bound for the element’s value
+ *
+ * @var float|null
+ */
+ protected $min;
+
+ /**
+ * The expected upper bound for the element’s
+ *
+ * @var float|null
+ */
+ protected $max;
+
+ /**
+ * The value granularity of the element’s value
+ *
+ * Normally, number input controls are limited to an accuracy of integer values.
+ *
+ * @var float|string|null
+ */
+ protected $step;
+
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form_Element::init() For the method documentation.
+ */
+ public function init()
+ {
+ if ($this->min !== null || $this->max !== null) {
+ $this->addValidator('Between', true, array(
+ 'min' => $this->min === null ? -INF : $this->min,
+ 'max' => $this->max === null ? INF : $this->max,
+ 'inclusive' => true
+ ));
+ }
+ }
+
+ /**
+ * Set the expected lower bound for the element’s value
+ *
+ * @param float $min
+ *
+ * @return $this
+ */
+ public function setMin($min)
+ {
+ $this->min = (float) $min;
+ return $this;
+ }
+
+ /**
+ * Get the expected lower bound for the element’s value
+ *
+ * @return float|null
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * Set the expected upper bound for the element’s value
+ *
+ * @param float $max
+ *
+ * @return $this
+ */
+ public function setMax($max)
+ {
+ $this->max = (float) $max;
+ return $this;
+ }
+
+ /**
+ * Get the expected upper bound for the element’s value
+ *
+ * @return float|null
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Set the value granularity of the element’s value
+ *
+ * @param float|string $step
+ *
+ * @return $this
+ */
+ public function setStep($step)
+ {
+ if ($step !== 'any') {
+ $step = (float) $step;
+ }
+ $this->step = $step;
+ return $this;
+ }
+
+ /**
+ * Get the value granularity of the element’s value
+ *
+ * @return float|string|null
+ */
+ public function getStep()
+ {
+ return $this->step;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form_Element::isValid() For the method documentation.
+ */
+ public function isValid($value, $context = null)
+ {
+ $this->setValue($value);
+ $value = $this->getValue();
+ if ($value !== null && $value !== '' && ! is_numeric($value)) {
+ $this->addError(sprintf(t('\'%s\' is not a valid number'), $value));
+ return false;
+ }
+ return parent::isValid($value, $context);
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Textarea.php b/library/Icinga/Web/Form/Element/Textarea.php
new file mode 100644
index 0000000..119cd56
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Textarea.php
@@ -0,0 +1,20 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Form\FormElement;
+
+class Textarea extends FormElement
+{
+ public $helper = 'formTextarea';
+
+ public function __construct($spec, $options = null)
+ {
+ parent::__construct($spec, $options);
+
+ if ($this->getAttrib('rows') === null) {
+ $this->setAttrib('rows', 3);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Element/Time.php b/library/Icinga/Web/Form/Element/Time.php
new file mode 100644
index 0000000..4b76a33
--- /dev/null
+++ b/library/Icinga/Web/Form/Element/Time.php
@@ -0,0 +1,19 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Element;
+
+use Icinga\Web\Form\FormElement;
+
+/**
+ * A time input control
+ */
+class Time extends FormElement
+{
+ /**
+ * Form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formTime';
+}
diff --git a/library/Icinga/Web/Form/ErrorLabeller.php b/library/Icinga/Web/Form/ErrorLabeller.php
new file mode 100644
index 0000000..3f822d5
--- /dev/null
+++ b/library/Icinga/Web/Form/ErrorLabeller.php
@@ -0,0 +1,71 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form;
+
+use BadMethodCallException;
+use Zend_Translate_Adapter;
+use Zend_Validate_NotEmpty;
+use Zend_Validate_File_MimeType;
+use Icinga\Web\Form\Validator\DateTimeValidator;
+use Icinga\Web\Form\Validator\ReadablePathValidator;
+use Icinga\Web\Form\Validator\WritablePathValidator;
+
+class ErrorLabeller extends Zend_Translate_Adapter
+{
+ protected $messages;
+
+ public function __construct($options = array())
+ {
+ if (! isset($options['element'])) {
+ throw new BadMethodCallException('Option "element" is missing');
+ }
+
+ $this->messages = $this->createMessages($options['element']);
+ }
+
+ public function isTranslated($messageId, $original = false, $locale = null)
+ {
+ return array_key_exists($messageId, $this->messages);
+ }
+
+ public function translate($messageId, $locale = null)
+ {
+ if (array_key_exists($messageId, $this->messages)) {
+ return $this->messages[$messageId];
+ }
+
+ return $messageId;
+ }
+
+ protected function createMessages($element)
+ {
+ $label = $element->getLabel() ?: $element->getName();
+
+ return array(
+ Zend_Validate_NotEmpty::IS_EMPTY => sprintf(t('%s is required and must not be empty'), $label),
+ Zend_Validate_File_MimeType::FALSE_TYPE => sprintf(
+ t('%s (%%value%%) has a false MIME type of "%%type%%"'),
+ $label
+ ),
+ Zend_Validate_File_MimeType::NOT_DETECTED => sprintf(t('%s (%%value%%) has no MIME type'), $label),
+ WritablePathValidator::NOT_WRITABLE => sprintf(t('%s is not writable', 'config.path'), $label),
+ WritablePathValidator::DOES_NOT_EXIST => sprintf(t('%s does not exist', 'config.path'), $label),
+ ReadablePathValidator::NOT_READABLE => sprintf(t('%s is not readable', 'config.path'), $label),
+ DateTimeValidator::INVALID_DATETIME_FORMAT => sprintf(
+ t('%s not in the expected format: %%value%%'),
+ $label
+ )
+ );
+ }
+
+ protected function _loadTranslationData($data, $locale, array $options = array())
+ {
+ // nonsense, required as being abstract otherwise...
+ }
+
+ public function toString()
+ {
+ return 'ErrorLabeller'; // nonsense, required as being abstract otherwise...
+ }
+}
diff --git a/library/Icinga/Web/Form/FormElement.php b/library/Icinga/Web/Form/FormElement.php
new file mode 100644
index 0000000..0ac2ee4
--- /dev/null
+++ b/library/Icinga/Web/Form/FormElement.php
@@ -0,0 +1,61 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form;
+
+use Zend_Form_Element;
+use Icinga\Web\Form;
+
+/**
+ * Base class for Icinga Web 2 form elements
+ */
+class FormElement extends Zend_Form_Element
+{
+ /**
+ * Whether loading default decorators is disabled
+ *
+ * Icinga Web 2 loads its own default element decorators. For loading Zend's default element decorators set this
+ * property to false.
+ *
+ * @var null|bool
+ */
+ protected $_disableLoadDefaultDecorators;
+
+ /**
+ * Whether loading default decorators is disabled
+ *
+ * @return bool
+ */
+ public function loadDefaultDecoratorsIsDisabled()
+ {
+ return $this->_disableLoadDefaultDecorators === true;
+ }
+
+ /**
+ * Load default decorators
+ *
+ * Icinga Web 2 loads its own default element decorators. For loading Zend's default element decorators set
+ * FormElement::$_disableLoadDefaultDecorators to false.
+ *
+ * @return this
+ * @see Form::$defaultElementDecorators For Icinga Web 2's default element decorators.
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ if (! isset($this->_disableLoadDefaultDecorators)) {
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ // Load Icinga Web 2's default element decorators
+ $this->addDecorators(Form::$defaultElementDecorators);
+ }
+ } else {
+ // Load Zend's default decorators
+ parent::loadDefaultDecorators();
+ }
+ return $this;
+ }
+}
diff --git a/library/Icinga/Web/Form/InvalidCSRFTokenException.php b/library/Icinga/Web/Form/InvalidCSRFTokenException.php
new file mode 100644
index 0000000..d0eb68a
--- /dev/null
+++ b/library/Icinga/Web/Form/InvalidCSRFTokenException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form;
+
+/**
+ * Exceptions for invalid form tokens
+ */
+class InvalidCSRFTokenException extends \Exception
+{
+}
diff --git a/library/Icinga/Web/Form/Validator/DateFormatValidator.php b/library/Icinga/Web/Form/Validator/DateFormatValidator.php
new file mode 100644
index 0000000..eacb29c
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/DateFormatValidator.php
@@ -0,0 +1,61 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Zend_Validate_Abstract;
+
+/**
+ * Validator that checks if a textfield contains a correct date format
+ */
+class DateFormatValidator extends Zend_Validate_Abstract
+{
+
+ /**
+ * Valid date characters according to @see http://www.php.net/manual/en/function.date.php
+ *
+ * @var array
+ *
+ * @see http://www.php.net/manual/en/function.date.php
+ */
+ private $validChars =
+ array('d', 'D', 'j', 'l', 'N', 'S', 'w', 'z', 'W', 'F', 'm', 'M', 'n', 't', 'L', 'o', 'Y', 'y');
+
+ /**
+ * List of sensible time separators
+ *
+ * @var array
+ */
+ private $validSeparators = array(' ', ':', '-', '/', ';', ',', '.');
+
+ /**
+ * Error templates
+ *
+ * @var array
+ *
+ * @see Zend_Validate_Abstract::$_messageTemplates
+ */
+ protected $_messageTemplates = array(
+ 'INVALID_CHARACTERS' => 'Invalid date format'
+ );
+
+ /**
+ * Validate the input value
+ *
+ * @param string $value The format string to validate
+ * @param null $context The form context (ignored)
+ *
+ * @return bool True when the input is valid, otherwise false
+ *
+ * @see Zend_Validate_Abstract::isValid()
+ */
+ public function isValid($value, $context = null)
+ {
+ $rest = trim($value, join(' ', array_merge($this->validChars, $this->validSeparators)));
+ if (strlen($rest) > 0) {
+ $this->_error('INVALID_CHARACTERS');
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/DateTimeValidator.php b/library/Icinga/Web/Form/Validator/DateTimeValidator.php
new file mode 100644
index 0000000..5ef327d
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/DateTimeValidator.php
@@ -0,0 +1,77 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use DateTime;
+use Zend_Validate_Abstract;
+
+/**
+ * Validator for date-and-time input controls
+ *
+ * @see \Icinga\Web\Form\Element\DateTimePicker For the date-and-time input control.
+ */
+class DateTimeValidator extends Zend_Validate_Abstract
+{
+ const INVALID_DATETIME_TYPE = 'invalidDateTimeType';
+ const INVALID_DATETIME_FORMAT = 'invalidDateTimeFormat';
+
+ /**
+ * The messages to write on differen error states
+ *
+ * @var array
+ *
+ * @see Zend_Validate_Abstract::$_messageTemplates‚
+ */
+ protected $_messageTemplates = array(
+ self::INVALID_DATETIME_TYPE => 'Invalid type given. Instance of DateTime or date/time string expected',
+ self::INVALID_DATETIME_FORMAT => 'Date/time string not in the expected format: %value%'
+ );
+
+ protected $local;
+
+ /**
+ * Create a new date-and-time input control validator
+ *
+ * @param bool $local
+ */
+ public function __construct($local)
+ {
+ $this->local = (bool) $local;
+ }
+
+ /**
+ * Is the date and time valid?
+ *
+ * @param string|DateTime $value
+ * @param mixed $context
+ *
+ * @return bool
+ *
+ * @see \Zend_Validate_Interface::isValid()
+ */
+ public function isValid($value, $context = null)
+ {
+ if (! $value instanceof DateTime && ! is_string($value)) {
+ $this->_error(self::INVALID_DATETIME_TYPE);
+ return false;
+ }
+
+ if (! $value instanceof DateTime) {
+ $format = $baseFormat = $this->local === true ? 'Y-m-d\TH:i:s' : DateTime::RFC3339;
+ $dateTime = DateTime::createFromFormat($format, $value);
+
+ if ($dateTime === false) {
+ $format = substr($format, 0, strrpos($format, ':'));
+ $dateTime = DateTime::createFromFormat($format, $value);
+ }
+
+ if ($dateTime === false || $dateTime->format($format) !== $value) {
+ $this->_error(self::INVALID_DATETIME_FORMAT, $baseFormat);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/InArray.php b/library/Icinga/Web/Form/Validator/InArray.php
new file mode 100644
index 0000000..5d3925e
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/InArray.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Zend_Validate_InArray;
+use Icinga\Util\StringHelper;
+
+class InArray extends Zend_Validate_InArray
+{
+ protected function _error($messageKey, $value = null)
+ {
+ if ($messageKey === static::NOT_IN_ARRAY) {
+ $matches = StringHelper::findSimilar($this->_value, $this->_haystack);
+ if (empty($matches)) {
+ $this->_messages[$messageKey] = sprintf(t('"%s" is not in the list of allowed values.'), $this->_value);
+ } else {
+ $this->_messages[$messageKey] = sprintf(
+ t('"%s" is not in the list of allowed values. Did you mean one of the following?: %s'),
+ $this->_value,
+ implode(', ', $matches)
+ );
+ }
+ } else {
+ parent::_error($messageKey, $value);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/InternalUrlValidator.php b/library/Icinga/Web/Form/Validator/InternalUrlValidator.php
new file mode 100644
index 0000000..f936bb5
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/InternalUrlValidator.php
@@ -0,0 +1,41 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Icinga\Application\Icinga;
+use Zend_Validate_Abstract;
+use Icinga\Web\Url;
+
+/**
+ * Validator that checks whether a textfield doesn't contain an external URL
+ */
+class InternalUrlValidator extends Zend_Validate_Abstract
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($value)
+ {
+ $url = Url::fromPath($value);
+ if ($url->getRelativeUrl() === '' || $url->isExternal()) {
+ $this->_error('IS_EXTERNAL');
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function _error($messageKey, $value = null)
+ {
+ if ($messageKey === 'IS_EXTERNAL') {
+ $this->_messages[$messageKey] = t('The url must not be external.');
+ } else {
+ parent::_error($messageKey, $value);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/ReadablePathValidator.php b/library/Icinga/Web/Form/Validator/ReadablePathValidator.php
new file mode 100644
index 0000000..826421c
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/ReadablePathValidator.php
@@ -0,0 +1,53 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Zend_Validate_Abstract;
+
+/**
+ * Validator that interprets the value as a filepath and checks if it's readable
+ *
+ * This validator should be preferred due to Zend_Validate_File_Exists is
+ * getting confused if there is another element in the form called `name'.
+ */
+class ReadablePathValidator extends Zend_Validate_Abstract
+{
+ const NOT_READABLE = 'notReadable';
+ const DOES_NOT_EXIST = 'doesNotExist';
+
+ /**
+ * The messages to write on different error states
+ *
+ * @var array
+ *
+ * @see Zend_Validate_Abstract::$_messageTemplates‚
+ */
+ protected $_messageTemplates = array(
+ self::NOT_READABLE => 'Path is not readable',
+ self::DOES_NOT_EXIST => 'Path does not exist'
+ );
+
+ /**
+ * Check whether the given value is a readable filepath
+ *
+ * @param string $value The value submitted in the form
+ * @param mixed $context The context of the form
+ *
+ * @return bool Whether the value was successfully validated
+ */
+ public function isValid($value, $context = null)
+ {
+ if (false === file_exists($value)) {
+ $this->_error(self::DOES_NOT_EXIST);
+ return false;
+ }
+
+ if (false === is_readable($value)) {
+ $this->_error(self::NOT_READABLE);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/TimeFormatValidator.php b/library/Icinga/Web/Form/Validator/TimeFormatValidator.php
new file mode 100644
index 0000000..9c1c99a
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/TimeFormatValidator.php
@@ -0,0 +1,58 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Zend_Validate_Abstract;
+
+/**
+ * Validator that checks if a textfield contains a correct time format
+ */
+class TimeFormatValidator extends Zend_Validate_Abstract
+{
+
+ /**
+ * Valid time characters according to @see http://www.php.net/manual/en/function.date.php
+ *
+ * @var array
+ * @see http://www.php.net/manual/en/function.date.php
+ */
+ private $validChars = array('a', 'A', 'B', 'g', 'G', 'h', 'H', 'i', 's', 'u');
+
+ /**
+ * List of sensible time separators
+ *
+ * @var array
+ */
+ private $validSeparators = array(' ', ':', '-', '/', ';', ',', '.');
+
+ /**
+ * Error templates
+ *
+ * @var array
+ * @see Zend_Validate_Abstract::$_messageTemplates
+ */
+ protected $_messageTemplates = array(
+ 'INVALID_CHARACTERS' => 'Invalid time format'
+ );
+
+ /**
+ * Validate the input value
+ *
+ * @param string $value The format string to validate
+ * @param null $context The form context (ignored)
+ *
+ * @return bool True when the input is valid, otherwise false
+ *
+ * @see Zend_Validate_Abstract::isValid()
+ */
+ public function isValid($value, $context = null)
+ {
+ $rest = trim($value, join(' ', array_merge($this->validChars, $this->validSeparators)));
+ if (strlen($rest) > 0) {
+ $this->_error('INVALID_CHARACTERS');
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/UrlValidator.php b/library/Icinga/Web/Form/Validator/UrlValidator.php
new file mode 100644
index 0000000..b1b578f
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/UrlValidator.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Zend_Validate_Abstract;
+
+/**
+ * Validator that checks whether a textfield doesn't contain raw double quotes
+ */
+class UrlValidator extends Zend_Validate_Abstract
+{
+ /**
+ * Constructor
+ */
+ public function __construct()
+ {
+ $this->_messageTemplates = array('HAS_QUOTES' => t(
+ 'The url must not contain raw double quotes. If you really need double quotes, use %22 instead.'
+ ));
+ }
+
+ /**
+ * Validate the input value
+ *
+ * @param string $value The string to validate
+ *
+ * @return bool true if and only if the input is valid, otherwise false
+ *
+ * @see Zend_Validate_Abstract::isValid()
+ */
+ public function isValid($value)
+ {
+ $hasQuotes = false === strpos($value, '"');
+ if (! $hasQuotes) {
+ $this->_error('HAS_QUOTES');
+ }
+ return $hasQuotes;
+ }
+}
diff --git a/library/Icinga/Web/Form/Validator/WritablePathValidator.php b/library/Icinga/Web/Form/Validator/WritablePathValidator.php
new file mode 100644
index 0000000..76efb58
--- /dev/null
+++ b/library/Icinga/Web/Form/Validator/WritablePathValidator.php
@@ -0,0 +1,72 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Form\Validator;
+
+use Zend_Validate_Abstract;
+
+/**
+ * Validator that interprets the value as a path and checks if it's writable
+ */
+class WritablePathValidator extends Zend_Validate_Abstract
+{
+ const NOT_WRITABLE = 'notWritable';
+ const DOES_NOT_EXIST = 'doesNotExist';
+
+ /**
+ * The messages to write on differen error states
+ *
+ * @var array
+ *
+ * @see Zend_Validate_Abstract::$_messageTemplates‚
+ */
+ protected $_messageTemplates = array(
+ self::NOT_WRITABLE => 'Path is not writable',
+ self::DOES_NOT_EXIST => 'Path does not exist'
+ );
+
+ /**
+ * When true, the file or directory must exist
+ *
+ * @var bool
+ */
+ private $requireExistence = false;
+
+ /**
+ * Set this validator to require the target file to exist
+ */
+ public function setRequireExistence()
+ {
+ $this->requireExistence = true;
+ }
+
+ /**
+ * Check whether the given value is writable path
+ *
+ * @param string $value The value submitted in the form
+ * @param mixed $context The context of the form
+ *
+ * @return bool True when validation worked, otherwise false
+ *
+ * @see Zend_Validate_Abstract::isValid()
+ */
+ public function isValid($value, $context = null)
+ {
+ $value = (string) $value;
+
+ $this->_setValue($value);
+ if ($this->requireExistence && !file_exists($value)) {
+ $this->_error(self::DOES_NOT_EXIST);
+ return false;
+ }
+
+ if ((file_exists($value) && is_writable($value)) ||
+ (is_dir(dirname($value)) && is_writable(dirname($value)))
+ ) {
+ return true;
+ }
+
+ $this->_error(self::NOT_WRITABLE);
+ return false;
+ }
+}
diff --git a/library/Icinga/Web/Helper/CookieHelper.php b/library/Icinga/Web/Helper/CookieHelper.php
new file mode 100644
index 0000000..cc7c448
--- /dev/null
+++ b/library/Icinga/Web/Helper/CookieHelper.php
@@ -0,0 +1,81 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Helper;
+
+use Icinga\Web\Request;
+
+/**
+ * Helper Class Cookie
+ */
+class CookieHelper
+{
+ /**
+ * The name of the control cookie
+ */
+ const CHECK_COOKIE = '_chc';
+
+ /**
+ * The request
+ *
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * Create a new cookie
+ *
+ * @param Request $request
+ */
+ public function __construct(Request $request)
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * Check whether cookies are supported or not
+ *
+ * @return bool
+ */
+ public function isSupported()
+ {
+ if (! empty($_COOKIE)) {
+ $this->cleanupCheck();
+ return true;
+ }
+
+ $url = $this->request->getUrl();
+
+ if ($url->hasParam('_checkCookie') && empty($_COOKIE)) {
+ return false;
+ }
+
+ if (! $url->hasParam('_checkCookie')) {
+ $this->provideCheck();
+ }
+
+ return false;
+ }
+
+ /**
+ * Prepare check to detect cookie support
+ */
+ public function provideCheck()
+ {
+ setcookie(self::CHECK_COOKIE, '1');
+
+ $requestUri = $this->request->getUrl()->addParams(array('_checkCookie' => 1));
+ $this->request->getResponse()->redirectAndExit($requestUri);
+ }
+
+ /**
+ * Cleanup the cookie support check
+ */
+ public function cleanupCheck()
+ {
+ if ($this->request->getUrl()->hasParam('_checkCookie') && isset($_COOKIE[self::CHECK_COOKIE])) {
+ $requestUri =$this->request->getUrl()->without('_checkCookie');
+ $this->request->getResponse()->redirectAndExit($requestUri);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Helper/HtmlPurifier.php b/library/Icinga/Web/Helper/HtmlPurifier.php
new file mode 100644
index 0000000..f0c292f
--- /dev/null
+++ b/library/Icinga/Web/Helper/HtmlPurifier.php
@@ -0,0 +1,99 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Helper;
+
+use Closure;
+use Icinga\Web\FileCache;
+use InvalidArgumentException;
+
+class HtmlPurifier
+{
+ /**
+ * The actual purifier instance
+ *
+ * @var \HTMLPurifier
+ */
+ protected $purifier;
+
+ /**
+ * Create a new HtmlPurifier
+ *
+ * @param array|Closure $config Additional configuration
+ */
+ public function __construct($config = null)
+ {
+ require_once 'HTMLPurifier/Bootstrap.php';
+ require_once 'HTMLPurifier.php';
+ require_once 'HTMLPurifier.autoload.php';
+
+ $purifierConfig = \HTMLPurifier_Config::createDefault();
+ $purifierConfig->set('Core.EscapeNonASCIICharacters', true);
+ $purifierConfig->set('Attr.AllowedFrameTargets', array('_blank'));
+
+ if (($cachePath = FileCache::instance()->directory('htmlpurifier.cache')) !== false) {
+ $purifierConfig->set('Cache.SerializerPath', $cachePath);
+ } else {
+ $purifierConfig->set('Cache.DefinitionImpl', null);
+ }
+
+ // This avoids permission problems:
+ // $purifierConfig->set('Core.DefinitionCache', null);
+
+ // $purifierConfig->set('URI.Base', 'http://www.example.com');
+ // $purifierConfig->set('URI.MakeAbsolute', true);
+
+ $this->configure($purifierConfig);
+
+ if ($config instanceof Closure) {
+ call_user_func($config, $purifierConfig);
+ } elseif (is_array($config)) {
+ $purifierConfig->loadArray($config);
+ } elseif ($config !== null) {
+ throw new InvalidArgumentException('$config must be either a Closure or array');
+ }
+
+ $this->purifier = new \HTMLPurifier($purifierConfig);
+ }
+
+ /**
+ * Apply additional default configuration
+ *
+ * May be overwritten by more concrete purifier implementations.
+ *
+ * @param \HTMLPurifier_Config $config
+ */
+ protected function configure($config)
+ {
+ }
+
+ /**
+ * Purify and return the given HTML string
+ *
+ * @param string $html
+ * @param array|Closure $config Configuration to use instead of the default
+ *
+ * @return string
+ */
+ public function purify($html, $config = null)
+ {
+ return $this->purifier->purify($html, $config);
+ }
+
+ /**
+ * Purify and return the given HTML string
+ *
+ * Convenience method to bypass object creation.
+ *
+ * @param string $html
+ * @param array|Closure $config Additional configuration
+ *
+ * @return string
+ */
+ public static function process($html, $config = null)
+ {
+ $purifier = new static($config);
+
+ return $purifier->purify($html);
+ }
+}
diff --git a/library/Icinga/Web/Helper/Markdown.php b/library/Icinga/Web/Helper/Markdown.php
new file mode 100644
index 0000000..e6509d0
--- /dev/null
+++ b/library/Icinga/Web/Helper/Markdown.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Helper;
+
+use Icinga\Web\Helper\Markdown\LinkTransformer;
+use Parsedown;
+
+class Markdown
+{
+ public static function line($content, $config = null)
+ {
+ require_once 'Parsedown/Parsedown.php';
+
+ if ($config === null) {
+ $config = function (\HTMLPurifier_Config $config) {
+ $config->set('HTML.Parent', 'span'); // Only allow inline elements
+
+ LinkTransformer::attachTo($config);
+ };
+ }
+
+ return HtmlPurifier::process(Parsedown::instance()->line($content), $config);
+ }
+
+ public static function text($content, $config = null)
+ {
+ require_once 'Parsedown/Parsedown.php';
+
+ if ($config === null) {
+ $config = function (\HTMLPurifier_Config $config) {
+ LinkTransformer::attachTo($config);
+ };
+ }
+
+ return HtmlPurifier::process(Parsedown::instance()->text($content), $config);
+ }
+}
diff --git a/library/Icinga/Web/Helper/Markdown/LinkTransformer.php b/library/Icinga/Web/Helper/Markdown/LinkTransformer.php
new file mode 100644
index 0000000..f323085
--- /dev/null
+++ b/library/Icinga/Web/Helper/Markdown/LinkTransformer.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Helper\Markdown;
+
+use HTMLPurifier_AttrTransform;
+use HTMLPurifier_Config;
+use ipl\Web\Url;
+
+class LinkTransformer extends HTMLPurifier_AttrTransform
+{
+ /**
+ * Link targets that are considered to have a thumbnail
+ *
+ * @var string[]
+ */
+ public static $IMAGE_FILES = [
+ 'jpg',
+ 'jpeg',
+ 'png',
+ 'bmp',
+ 'gif',
+ 'heif',
+ 'heic',
+ 'webp'
+ ];
+
+ public function transform($attr, $config, $context)
+ {
+ if (! isset($attr['href'])) {
+ return $attr;
+ }
+
+ $url = Url::fromPath($attr['href']);
+ $fileName = basename($url->getPath());
+
+ $ext = null;
+ if (($extAt = strrpos($fileName, '.')) !== false) {
+ $ext = substr($fileName, $extAt + 1);
+ }
+
+ $hasThumbnail = $ext !== null && in_array($ext, static::$IMAGE_FILES, true);
+ if ($hasThumbnail) {
+ // I would have liked to not only base this off of the extension, but also by
+ // whether there is an actual img tag inside the anchor. Seems not possible :(
+ $attr['class'] = 'with-thumbnail';
+ }
+
+ if (! isset($attr['target'])) {
+ if ($url->isExternal()) {
+ $attr['target'] = '_blank';
+ } else {
+ $attr['data-base-target'] = '_next';
+ }
+ }
+
+ return $attr;
+ }
+
+ public static function attachTo(HTMLPurifier_Config $config)
+ {
+ $module = $config->getHTMLDefinition(true)
+ ->getAnonymousModule();
+
+ if (isset($module->info['a'])) {
+ $a = $module->info['a'];
+ } else {
+ $a = $module->addBlankElement('a');
+ }
+
+ $a->attr_transform_post[] = new self();
+ }
+}
diff --git a/library/Icinga/Web/Hook.php b/library/Icinga/Web/Hook.php
new file mode 100644
index 0000000..b098518
--- /dev/null
+++ b/library/Icinga/Web/Hook.php
@@ -0,0 +1,16 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Hook as NewHookImplementation;
+
+/**
+ * Icinga Web Hook registry
+ *
+ * @deprecated It is highly recommended to use {@see Icinga\Application\Hook} instead. Though since this message
+ * (or rather the previous message) hasn't been visible for ages... This won't be removed anyway....
+ */
+class Hook extends NewHookImplementation
+{
+}
diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php
new file mode 100644
index 0000000..9a6a11b
--- /dev/null
+++ b/library/Icinga/Web/JavaScript.php
@@ -0,0 +1,269 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Exception\Json\JsonDecodeException;
+use Icinga\Util\Json;
+use JShrink\Minifier;
+
+class JavaScript
+{
+ /** @var string */
+ const DEFINE_RE =
+ '/(?<!\.)define\(\s*([\'"][^\'"]*[\'"])?[,\s]*(\[[^]]*\])?[,\s]*((?>function\s*\([^)]*\)|[^=]*=>|\w+).*)/';
+
+ protected static $jsFiles = [
+ 'js/helpers.js',
+ 'js/icinga.js',
+ 'js/icinga/logger.js',
+ 'js/icinga/storage.js',
+ 'js/icinga/utils.js',
+ 'js/icinga/ui.js',
+ 'js/icinga/timer.js',
+ 'js/icinga/loader.js',
+ 'js/icinga/eventlistener.js',
+ 'js/icinga/events.js',
+ 'js/icinga/history.js',
+ 'js/icinga/module.js',
+ 'js/icinga/timezone.js',
+ 'js/icinga/behavior/application-state.js',
+ 'js/icinga/behavior/autofocus.js',
+ 'js/icinga/behavior/collapsible.js',
+ 'js/icinga/behavior/detach.js',
+ 'js/icinga/behavior/dropdown.js',
+ 'js/icinga/behavior/navigation.js',
+ 'js/icinga/behavior/form.js',
+ 'js/icinga/behavior/actiontable.js',
+ 'js/icinga/behavior/flyover.js',
+ 'js/icinga/behavior/filtereditor.js',
+ 'js/icinga/behavior/selectable.js',
+ 'js/icinga/behavior/modal.js',
+ 'js/icinga/behavior/input-enrichment.js',
+ 'js/icinga/behavior/datetime-picker.js'
+ ];
+
+ protected static $vendorFiles = [];
+
+ protected static $baseFiles = [
+ 'js/define.js'
+ ];
+
+ public static function sendMinified()
+ {
+ self::send(true);
+ }
+
+ /**
+ * Send the client side script code to the client
+ *
+ * Does not cache the client side script code if the HTTP header Cache-Control or Pragma is set to no-cache.
+ *
+ * @param bool $minified Whether to compress the client side script code
+ */
+ public static function send($minified = false)
+ {
+ header('Content-Type: application/javascript');
+ $basedir = Icinga::app()->getBootstrapDirectory();
+ $moduleManager = Icinga::app()->getModuleManager();
+
+ $files = [];
+ $js = $out = '';
+ $min = $minified ? '.min' : '';
+
+ // Prepare vendor file list
+ $vendorFiles = [];
+ foreach (self::$vendorFiles as $file) {
+ $filePath = $basedir . '/' . $file . $min . '.js';
+ $vendorFiles[] = $filePath;
+ $files[] = $filePath;
+ }
+
+ // Prepare base file list
+ $baseFiles = [];
+ foreach (self::$baseFiles as $file) {
+ $filePath = $basedir . '/' . $file;
+ $baseFiles[] = $filePath;
+ $files[] = $filePath;
+ }
+
+ // Prepare library file list
+ foreach (Icinga::app()->getLibraries() as $library) {
+ $files = array_merge($files, $library->getJsAssets());
+ }
+
+ // Prepare core file list
+ $coreFiles = [];
+ foreach (self::$jsFiles as $file) {
+ $filePath = $basedir . '/' . $file;
+ $coreFiles[] = $filePath;
+ $files[] = $filePath;
+ }
+
+ $moduleFiles = [];
+ foreach ($moduleManager->getLoadedModules() as $name => $module) {
+ if ($module->hasJs()) {
+ $jsDir = $module->getJsDir();
+ foreach ($module->getJsFiles() as $path) {
+ if (file_exists($path)) {
+ $moduleFiles[$name][$jsDir][] = $path;
+ $files[] = $path;
+ }
+ }
+ }
+ }
+
+ $request = Icinga::app()->getRequest();
+ $noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
+
+ header('Cache-Control: public,no-cache,must-revalidate');
+
+ if (! $noCache && FileCache::etagMatchesFiles($files)) {
+ header("HTTP/1.1 304 Not Modified");
+ return;
+ } else {
+ $etag = FileCache::etagForFiles($files);
+ }
+
+ header('ETag: "' . $etag . '"');
+ header('Content-Type: application/javascript');
+
+ $cacheFile = 'icinga-' . $etag . $min . '.js';
+ $cache = FileCache::instance();
+ if (! $noCache && $cache->has($cacheFile)) {
+ $cache->send($cacheFile);
+ return;
+ }
+
+ // We do not minify vendor files
+ foreach ($vendorFiles as $file) {
+ $out .= ';' . ltrim(trim(file_get_contents($file)), ';') . "\n";
+ }
+
+ $baseJs = '';
+ foreach ($baseFiles as $file) {
+ $baseJs .= file_get_contents($file) . "\n\n\n";
+ }
+
+ // Library files need to be namespaced first before they can be included
+ foreach (Icinga::app()->getLibraries() as $library) {
+ foreach ($library->getJsAssets() as $file) {
+ $alreadyMinified = false;
+ if ($minified && file_exists(($minFile = substr($file, 0, -3) . '.min.js'))) {
+ $alreadyMinified = true;
+ $file = $minFile;
+ }
+
+ $content = self::optimizeDefine(
+ file_get_contents($file),
+ $file,
+ $library->getJsAssetPath(),
+ $library->getName()
+ );
+
+ if ($alreadyMinified) {
+ $out .= ';' . ltrim(trim($content), ';') . "\n";
+ } else {
+ $js .= $content . "\n\n\n";
+ }
+ }
+ }
+
+ foreach ($coreFiles as $file) {
+ $js .= file_get_contents($file) . "\n\n\n";
+ }
+
+ foreach ($moduleFiles as $name => $paths) {
+ foreach ($paths as $basePath => $filePaths) {
+ foreach ($filePaths as $file) {
+ $content = self::optimizeDefine(file_get_contents($file), $file, $basePath, $name);
+ if (substr($file, -7, 7) === '.min.js') {
+ $out .= ';' . ltrim(trim($content), ';') . "\n";
+ } else {
+ $js .= $content . "\n\n\n";
+ }
+ }
+ }
+ }
+
+ if ($minified) {
+ require_once 'JShrink/Minifier.php';
+ $out .= Minifier::minify($js, ['flaggedComments' => false]);
+ $baseOut = Minifier::minify($baseJs, ['flaggedComments' => false]);
+ $out = ';' . ltrim($baseOut, ';') . "\n" . $out;
+ } else {
+ $out = $baseJs . $out . $js;
+ }
+
+ $cache->store($cacheFile, $out);
+ echo $out;
+ }
+
+ /**
+ * Optimize define() calls in the given JS
+ *
+ * @param string $js
+ * @param string $filePath
+ * @param string $basePath
+ * @param string $packageName
+ *
+ * @return string
+ */
+ public static function optimizeDefine($js, $filePath, $basePath, $packageName)
+ {
+ if (! preg_match(self::DEFINE_RE, $js, $match) || strpos($js, 'define.amd') !== false) {
+ return $js;
+ }
+
+ try {
+ $assetName = $match[1] ? Json::decode($match[1]) : '';
+ if (! $assetName) {
+ $assetName = explode('.', basename($filePath))[0];
+ }
+
+ $assetName = join(DIRECTORY_SEPARATOR, array_filter([
+ $packageName,
+ ltrim(substr(dirname($filePath), strlen($basePath)), DIRECTORY_SEPARATOR),
+ $assetName
+ ]));
+
+ $assetName = Json::encode($assetName, JSON_UNESCAPED_SLASHES);
+ } catch (JsonDecodeException $_) {
+ $assetName = $match[1];
+ Logger::debug('Can\'t optimize name of "%s". Are single quotes used instead of double quotes?', $filePath);
+ }
+
+ try {
+ $dependencies = $match[2] ? Json::decode($match[2]) : [];
+ foreach ($dependencies as &$dependencyName) {
+ if ($dependencyName === 'exports') {
+ // exports is a special keyword and doesn't need optimization
+ continue;
+ }
+
+ if (preg_match('~^((?:\.\.?/)+)*(.*)~', $dependencyName, $natch)) {
+ $dependencyName = join(DIRECTORY_SEPARATOR, array_filter([
+ $packageName,
+ ltrim(substr(
+ realpath(join(DIRECTORY_SEPARATOR, [dirname($filePath), $natch[1]])),
+ strlen(realpath($basePath))
+ ), DIRECTORY_SEPARATOR),
+ $natch[2]
+ ]));
+ }
+ }
+
+ $dependencies = Json::encode($dependencies, JSON_UNESCAPED_SLASHES);
+ } catch (JsonDecodeException $_) {
+ $dependencies = $match[2];
+ Logger::debug(
+ 'Can\'t optimize dependencies of "%s". Are single quotes used instead of double quotes?',
+ $filePath
+ );
+ }
+
+ return str_replace($match[0], sprintf("define(%s, %s, %s", $assetName, $dependencies, $match[3]), $js);
+ }
+}
diff --git a/library/Icinga/Web/LessCompiler.php b/library/Icinga/Web/LessCompiler.php
new file mode 100644
index 0000000..1f72560
--- /dev/null
+++ b/library/Icinga/Web/LessCompiler.php
@@ -0,0 +1,257 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Logger;
+use Icinga\Util\LessParser;
+use Less_Exception_Parser;
+
+/**
+ * Compile LESS into CSS
+ *
+ * Comments will be removed always. lessc is messing them up.
+ */
+class LessCompiler
+{
+ /**
+ * lessphp compiler
+ *
+ * @var LessParser
+ */
+ protected $lessc;
+
+ /**
+ * Array of LESS files
+ *
+ * @var string[]
+ */
+ protected $lessFiles = array();
+
+ /**
+ * Array of module LESS files indexed by module names
+ *
+ * @var array[]
+ */
+ protected $moduleLessFiles = array();
+
+ /**
+ * LESS source
+ *
+ * @var string
+ */
+ protected $source;
+
+ /**
+ * Path of the LESS theme
+ *
+ * @var string
+ */
+ protected $theme;
+
+ /**
+ * Path of the LESS theme mode
+ *
+ * @var string
+ */
+ protected $themeMode;
+
+ /**
+ * Create a new LESS compiler
+ */
+ public function __construct()
+ {
+ $this->lessc = new LessParser();
+ // Discourage usage of import because we're caching based on an explicit list of LESS files to compile
+ $this->lessc->importDisabled = true;
+ }
+
+ /**
+ * Add a Web 2 LESS file
+ *
+ * @param string $lessFile Path to the LESS file
+ *
+ * @return $this
+ */
+ public function addLessFile($lessFile)
+ {
+ $this->lessFiles[] = realpath($lessFile);
+ return $this;
+ }
+
+ /**
+ * Add a module LESS file
+ *
+ * @param string $moduleName Name of the module
+ * @param string $lessFile Path to the LESS file
+ *
+ * @return $this
+ */
+ public function addModuleLessFile($moduleName, $lessFile)
+ {
+ if (! isset($this->moduleLessFiles[$moduleName])) {
+ $this->moduleLessFiles[$moduleName] = array();
+ }
+ $this->moduleLessFiles[$moduleName][] = realpath($lessFile);
+ return $this;
+ }
+
+ /**
+ * Get the list of LESS files added to the compiler
+ *
+ * @return string[]
+ */
+ public function getLessFiles()
+ {
+ $lessFiles = $this->lessFiles;
+
+ foreach ($this->moduleLessFiles as $moduleLessFiles) {
+ $lessFiles = array_merge($lessFiles, $moduleLessFiles);
+ }
+
+ if ($this->theme !== null) {
+ $lessFiles[] = $this->theme;
+ }
+
+ if ($this->themeMode !== null) {
+ $lessFiles[] = $this->themeMode;
+ }
+
+ return $lessFiles;
+ }
+
+ /**
+ * Set the path to the LESS theme
+ *
+ * @param ?string $theme Path to the LESS theme
+ *
+ * @return $this
+ */
+ public function setTheme($theme)
+ {
+ if ($theme === null || (is_file($theme) && is_readable($theme))) {
+ $this->theme = $theme;
+ } else {
+ Logger::error('Can\t load theme %s. Make sure that the theme exists and is readable', $theme);
+ }
+ return $this;
+ }
+
+ /**
+ * Set the path to the LESS theme mode
+ *
+ * @param string $themeMode Path to the LESS theme mode
+ *
+ * @return $this
+ */
+ public function setThemeMode($themeMode)
+ {
+ if (is_file($themeMode) && is_readable($themeMode)) {
+ $this->themeMode = $themeMode;
+ } else {
+ Logger::error('Can\t load theme mode %s. Make sure that the theme mode exists and is readable', $themeMode);
+ }
+ return $this;
+ }
+
+ /**
+ * Instruct the compiler to minify CSS
+ *
+ * @return $this
+ */
+ public function compress()
+ {
+ $this->lessc->setFormatter('compressed');
+ return $this;
+ }
+
+ /**
+ * Render to CSS
+ *
+ * @return string
+ */
+ public function render()
+ {
+ foreach ($this->lessFiles as $lessFile) {
+ $this->source .= file_get_contents($lessFile);
+ }
+
+ $moduleCss = '';
+ $exportedVars = [];
+ foreach ($this->moduleLessFiles as $moduleName => $moduleLessFiles) {
+ $moduleCss .= '.icinga-module.module-' . $moduleName . ' {';
+
+ foreach ($moduleLessFiles as $moduleLessFile) {
+ $content = file_get_contents($moduleLessFile);
+
+ $pattern = '/^@exports:\s*{((?:\s*@[^:}]+:[^;]*;\s+)+)};$/m';
+ if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $content = str_replace($match[0], '', $content);
+ foreach (explode("\n", trim($match[1])) as $line) {
+ list($name, $value) = explode(':', $line, 2);
+ $exportedVars[trim($name)] = trim($value, ' ;');
+ }
+ }
+ }
+
+ $moduleCss .= $content;
+ }
+
+ $moduleCss .= '}';
+ }
+
+ $this->source .= $moduleCss;
+
+ $varExports = '';
+ foreach ($exportedVars as $name => $value) {
+ $varExports .= sprintf("%s: %s;\n", $name, $value);
+ }
+
+ // exported vars are injected at the beginning to avoid that they are
+ // able to override other variables, that's what themes are for
+ $this->source = $varExports . "\n\n" . $this->source;
+
+ if ($this->theme !== null) {
+ $this->source .= file_get_contents($this->theme);
+ }
+
+ if ($this->themeMode !== null) {
+ $this->source .= file_get_contents($this->themeMode);
+ }
+
+ try {
+ return preg_replace(
+ '/(\.icinga-module\.module-[^\s]+) (#layout\.[^\s]+)/m',
+ '\2 \1',
+ $this->lessc->compile($this->source)
+ );
+ } catch (Less_Exception_Parser $e) {
+ $excerpt = substr($this->source, $e->index - 500, 1000);
+
+ $lines = [];
+ $found = false;
+ $pos = $e->index - 500;
+ foreach (explode("\n", $excerpt) as $i => $line) {
+ if ($i === 0) {
+ $pos += strlen($line);
+ $lines[] = '.. ' . $line;
+ } else {
+ $pos += strlen($line) + 1;
+ $sep = ' ';
+ if (! $found && $pos > $e->index) {
+ $found = true;
+ $sep = '!! ';
+ }
+
+ $lines[] = $sep . $line;
+ }
+ }
+
+ $lines[] = '..';
+ $excerpt = join("\n", $lines);
+
+ return sprintf("%s\n%s\n\n\n%s", $e->getMessage(), $e->getTraceAsString(), $excerpt);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php
new file mode 100644
index 0000000..dc1cdc8
--- /dev/null
+++ b/library/Icinga/Web/Menu.php
@@ -0,0 +1,152 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Web\Navigation\Navigation;
+
+/**
+ * Main menu for Icinga Web 2
+ */
+class Menu extends Navigation
+{
+ /**
+ * Create the main menu
+ */
+ public function __construct()
+ {
+ $this->init();
+ $this->load('menu-item');
+ }
+
+ /**
+ * Setup the main menu
+ */
+ public function init()
+ {
+ $this->addItem('dashboard', [
+ 'label' => t('Dashboard'),
+ 'url' => 'dashboard',
+ 'icon' => 'dashboard',
+ 'priority' => 10
+ ]);
+ $this->addItem('system', [
+ 'cssClass' => 'system-nav-item',
+ 'label' => t('System'),
+ 'icon' => 'services',
+ 'priority' => 700,
+ 'renderer' => [
+ 'SummaryNavigationItemRenderer',
+ 'state' => 'critical'
+ ],
+ 'children' => [
+ 'about' => [
+ 'icon' => 'info',
+ 'description' => t('Open about page'),
+ 'label' => t('About'),
+ 'url' => 'about',
+ 'priority' => 700
+ ],
+ 'health' => [
+ 'icon' => 'eye',
+ 'description' => t('Open health overview'),
+ 'label' => t('Health'),
+ 'url' => 'health',
+ 'priority' => 710,
+ 'renderer' => 'HealthNavigationRenderer'
+ ],
+ 'announcements' => [
+ 'icon' => 'megaphone',
+ 'description' => t('List announcements'),
+ 'label' => t('Announcements'),
+ 'url' => 'announcements',
+ 'priority' => 720
+ ],
+ 'sessions' => [
+ 'icon' => 'host',
+ 'description' => t('List of users who stay logged in'),
+ 'label' => t('User Sessions'),
+ 'permission' => 'application/sessions',
+ 'url' => 'manage-user-devices',
+ 'priority' => 730
+ ]
+ ]
+ ]);
+ $this->addItem('configuration', [
+ 'cssClass' => 'configuration-nav-item',
+ 'label' => t('Configuration'),
+ 'icon' => 'wrench',
+ 'permission' => 'config/*',
+ 'priority' => 800,
+ 'children' => [
+ 'application' => [
+ 'icon' => 'wrench',
+ 'description' => t('Open application configuration'),
+ 'label' => t('Application'),
+ 'url' => 'config',
+ 'priority' => 810
+ ],
+ 'authentication' => [
+ 'icon' => 'users',
+ 'description' => t('Open access control configuration'),
+ 'label' => t('Access Control'),
+ 'permission' => 'config/access-control/*',
+ 'priority' => 830,
+ 'url' => 'role'
+ ],
+ 'navigation' => [
+ 'icon' => 'sitemap',
+ 'description' => t('Open shared navigation configuration'),
+ 'label' => t('Shared Navigation'),
+ 'url' => 'navigation/shared',
+ 'permission' => 'config/navigation',
+ 'priority' => 840,
+ ],
+ 'modules' => [
+ 'icon' => 'cubes',
+ 'description' => t('Open module configuration'),
+ 'label' => t('Modules'),
+ 'url' => 'config/modules',
+ 'permission' => 'config/modules',
+ 'priority' => 890
+ ]
+ ]
+ ]);
+ $this->addItem('user', [
+ 'cssClass' => 'user-nav-item',
+ 'label' => Auth::getInstance()->getUser()->getUsername(),
+ 'icon' => 'user',
+ 'priority' => 900,
+ 'children' => [
+ 'account' => [
+ 'icon' => 'sliders',
+ 'description' => t('Open your account preferences'),
+ 'label' => t('My Account'),
+ 'priority' => 100,
+ 'url' => 'account'
+ ],
+ 'logout' => [
+ 'icon' => 'off',
+ 'description' => t('Log out'),
+ 'label' => t('Logout'),
+ 'priority' => 200,
+ 'attributes' => ['target' => '_self'],
+ 'url' => 'authentication/logout'
+ ]
+ ]
+ ]);
+
+ if (Logger::writesToFile()) {
+ $this->getItem('system')->addChild($this->createItem('application_log', [
+ 'icon' => 'doc-text',
+ 'description' => t('Open Application Log'),
+ 'label' => t('Application Log'),
+ 'url' => 'list/applicationlog',
+ 'permission' => 'application/log',
+ 'priority' => 900
+ ]));
+ }
+ }
+}
diff --git a/library/Icinga/Web/Navigation/ConfigMenu.php b/library/Icinga/Web/Navigation/ConfigMenu.php
new file mode 100644
index 0000000..db3f566
--- /dev/null
+++ b/library/Icinga/Web/Navigation/ConfigMenu.php
@@ -0,0 +1,302 @@
+<?php
+/* Icinga Web 2 | (c) 2022 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Navigation;
+
+use Icinga\Application\Hook\HealthHook;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Web\Url;
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\StateBadge;
+
+class ConfigMenu extends BaseHtmlElement
+{
+ const STATE_OK = 'ok';
+ const STATE_CRITICAL = 'critical';
+ const STATE_WARNING = 'warning';
+ const STATE_PENDING = 'pending';
+ const STATE_UNKNOWN = 'unknown';
+
+ protected $tag = 'ul';
+
+ protected $defaultAttributes = ['class' => 'nav'];
+
+ protected $children;
+
+ protected $selected;
+
+ protected $state;
+
+ public function __construct()
+ {
+ $this->children = [
+ 'system' => [
+ 'title' => t('System'),
+ 'items' => [
+ 'about' => [
+ 'label' => t('About'),
+ 'url' => 'about'
+ ],
+ 'health' => [
+ 'label' => t('Health'),
+ 'url' => 'health',
+ ],
+ 'announcements' => [
+ 'label' => t('Announcements'),
+ 'url' => 'announcements'
+ ],
+ 'sessions' => [
+ 'label' => t('User Sessions'),
+ 'permission' => 'application/sessions',
+ 'url' => 'manage-user-devices'
+ ]
+ ]
+ ],
+ 'configuration' => [
+ 'title' => t('Configuration'),
+ 'permission' => 'config/*',
+ 'items' => [
+ 'application' => [
+ 'label' => t('Application'),
+ 'url' => 'config/general'
+ ],
+ 'authentication' => [
+ 'label' => t('Access Control'),
+ 'permission' => 'config/access-control/*',
+ 'url' => 'role/list'
+ ],
+ 'navigation' => [
+ 'label' => t('Shared Navigation'),
+ 'permission' => 'config/navigation',
+ 'url' => 'navigation/shared'
+ ],
+ 'modules' => [
+ 'label' => t('Modules'),
+ 'permission' => 'config/modules',
+ 'url' => 'config/modules'
+ ]
+ ]
+ ],
+ 'logout' => [
+ 'items' => [
+ 'logout' => [
+ 'label' => t('Logout'),
+ 'atts' => [
+ 'target' => '_self',
+ 'class' => 'nav-item-logout'
+ ],
+ 'url' => 'authentication/logout'
+ ]
+ ]
+ ]
+ ];
+
+ if (Logger::writesToFile()) {
+ $this->children['system']['items']['application_log'] = [
+ 'label' => t('Application Log'),
+ 'url' => 'list/applicationlog',
+ 'permission' => 'application/log'
+ ];
+ }
+ }
+
+ protected function assembleUserMenuItem(BaseHtmlElement $userMenuItem)
+ {
+ $username = Auth::getInstance()->getUser()->getUsername();
+
+ $userMenuItem->add(
+ new HtmlElement(
+ 'a',
+ Attributes::create(['href' => Url::fromPath('account')]),
+ new HtmlElement(
+ 'i',
+ Attributes::create(['class' => 'user-ball']),
+ Text::create($username[0])
+ ),
+ Text::create($username)
+ )
+ );
+
+ if (Icinga::app()->getRequest()->getUrl()->matches('account')) {
+ $userMenuItem->addAttributes(['class' => 'selected active']);
+ }
+ }
+
+ protected function assembleCogMenuItem($cogMenuItem)
+ {
+ $cogMenuItem->add([
+ HtmlElement::create(
+ 'button',
+ null,
+ [
+ new Icon('cog'),
+ $this->createHealthBadge(),
+ ]
+ ),
+ $this->createLevel2Menu()
+ ]);
+ }
+
+ protected function assembleLevel2Nav(BaseHtmlElement $level2Nav)
+ {
+ $navContent = HtmlElement::create('div', ['class' => 'flyout-content']);
+ foreach ($this->children as $c) {
+ if (isset($c['permission']) && ! Auth::getInstance()->hasPermission($c['permission'])) {
+ continue;
+ }
+
+ if (isset($c['title'])) {
+ $navContent->add(HtmlElement::create(
+ 'h3',
+ null,
+ $c['title']
+ ));
+ }
+
+ $ul = HtmlElement::create('ul', ['class' => 'nav']);
+ foreach ($c['items'] as $key => $item) {
+ $ul->add($this->createLevel2MenuItem($item, $key));
+ }
+
+ $navContent->add($ul);
+ }
+
+ $level2Nav->add($navContent);
+ }
+
+ protected function getHealthCount()
+ {
+ $count = 0;
+ $title = null;
+ $worstState = null;
+ foreach (HealthHook::collectHealthData()->select() as $result) {
+ if ($worstState === null || $result->state > $worstState) {
+ $worstState = $result->state;
+ $title = $result->message;
+ $count = 1;
+ } elseif ($worstState === $result->state) {
+ $count++;
+ }
+ }
+
+ switch ($worstState) {
+ case HealthHook::STATE_OK:
+ $count = 0;
+ break;
+ case HealthHook::STATE_WARNING:
+ $this->state = self::STATE_WARNING;
+ break;
+ case HealthHook::STATE_CRITICAL:
+ $this->state = self::STATE_CRITICAL;
+ break;
+ case HealthHook::STATE_UNKNOWN:
+ $this->state = self::STATE_UNKNOWN;
+ break;
+ }
+
+ $this->title = $title;
+
+ return $count;
+ }
+
+ protected function isSelectedItem($item)
+ {
+ if ($item !== null && Icinga::app()->getRequest()->getUrl()->matches($item['url'])) {
+ $this->selected = $item;
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function createHealthBadge()
+ {
+ $stateBadge = null;
+ if ($this->getHealthCount() > 0) {
+ $stateBadge = new StateBadge($this->getHealthCount(), $this->state);
+ $stateBadge->addAttributes(['class' => 'disabled']);
+ }
+
+ return $stateBadge;
+ }
+
+ protected function createLevel2Menu()
+ {
+ $level2Nav = HtmlElement::create(
+ 'div',
+ Attributes::create(['class' => 'nav-level-1 flyout'])
+ );
+
+ $this->assembleLevel2Nav($level2Nav);
+
+ return $level2Nav;
+ }
+
+ protected function createLevel2MenuItem($item, $key)
+ {
+ if (isset($item['permission']) && ! Auth::getInstance()->hasPermission($item['permission'])) {
+ return null;
+ }
+
+ $healthBadge = null;
+ $class = null;
+ if ($key === 'health') {
+ $class = 'badge-nav-item';
+ $healthBadge = $this->createHealthBadge();
+ }
+
+ $li = HtmlElement::create(
+ 'li',
+ isset($item['atts']) ? $item['atts'] : [],
+ [
+ HtmlElement::create(
+ 'a',
+ Attributes::create(['href' => Url::fromPath($item['url'])]),
+ [
+ $item['label'],
+ isset($healthBadge) ? $healthBadge : ''
+ ]
+ ),
+ ]
+ );
+ $li->addAttributes(['class' => $class]);
+
+ if ($this->isSelectedItem($item)) {
+ $li->addAttributes(['class' => 'selected']);
+ }
+
+ return $li;
+ }
+
+ protected function createUserMenuItem()
+ {
+ $userMenuItem = HtmlElement::create('li', ['class' => 'user-nav-item']);
+
+ $this->assembleUserMenuItem($userMenuItem);
+
+ return $userMenuItem;
+ }
+
+ protected function createCogMenuItem()
+ {
+ $cogMenuItem = HtmlElement::create('li', ['class' => 'config-nav-item']);
+
+ $this->assembleCogMenuItem($cogMenuItem);
+
+ return $cogMenuItem;
+ }
+
+ protected function assemble()
+ {
+ $this->add([
+ $this->createUserMenuItem(),
+ $this->createCogMenuItem()
+ ]);
+ }
+}
diff --git a/library/Icinga/Web/Navigation/DashboardPane.php b/library/Icinga/Web/Navigation/DashboardPane.php
new file mode 100644
index 0000000..71b3215
--- /dev/null
+++ b/library/Icinga/Web/Navigation/DashboardPane.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation;
+
+use Icinga\Web\Url;
+
+/**
+ * A dashboard pane
+ */
+class DashboardPane extends NavigationItem
+{
+ /**
+ * This pane's dashlets
+ *
+ * @var array
+ */
+ protected $dashlets;
+
+ protected $disabled;
+
+ /**
+ * Set this pane's dashlets
+ *
+ * @param array $dashlets
+ *
+ * @return $this
+ */
+ public function setDashlets(array $dashlets)
+ {
+ $this->dashlets = $dashlets;
+ return $this;
+ }
+
+ /**
+ * Return this pane's dashlets
+ *
+ * @param bool $ordered Whether to order the dashlets first
+ *
+ * @return array
+ */
+ public function getDashlets($ordered = true)
+ {
+ if ($this->dashlets === null) {
+ return array();
+ }
+
+ if ($ordered) {
+ $dashlets = $this->dashlets;
+ ksort($dashlets);
+ return $dashlets;
+ }
+
+ return $this->dashlets;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setUrl(Url::fromPath('dashboard', array('pane' => $this->getName())));
+ }
+
+ /**
+ * Set disabled state for pane
+ *
+ * @param bool $disabled
+ */
+ public function setDisabled($disabled = true)
+ {
+ $this->disabled = (bool) $disabled;
+ }
+
+ /**
+ * Get disabled state for pane
+ *
+ * @return bool
+ */
+ public function getDisabled()
+ {
+ return $this->disabled;
+ }
+}
diff --git a/library/Icinga/Web/Navigation/DropdownItem.php b/library/Icinga/Web/Navigation/DropdownItem.php
new file mode 100644
index 0000000..2342b96
--- /dev/null
+++ b/library/Icinga/Web/Navigation/DropdownItem.php
@@ -0,0 +1,20 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation;
+
+/**
+ * Dropdown navigation item
+ *
+ * @see \Icinga\Web\Navigation\Navigation For a usage example.
+ */
+class DropdownItem extends NavigationItem
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->children->setLayout(Navigation::LAYOUT_DROPDOWN);
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Navigation.php b/library/Icinga/Web/Navigation/Navigation.php
new file mode 100644
index 0000000..8cf3f68
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Navigation.php
@@ -0,0 +1,571 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation;
+
+use ArrayAccess;
+use ArrayIterator;
+use Exception;
+use Countable;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Traversable;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Navigation\Renderer\RecursiveNavigationRenderer;
+
+/**
+ * Container for navigation items
+ */
+class Navigation implements ArrayAccess, Countable, IteratorAggregate
+{
+ /**
+ * The class namespace where to locate navigation type classes
+ *
+ * @var string
+ */
+ const NAVIGATION_NS = 'Web\\Navigation';
+
+ /**
+ * Flag for dropdown layout
+ *
+ * @var int
+ */
+ const LAYOUT_DROPDOWN = 1;
+
+ /**
+ * Flag for tabs layout
+ *
+ * @var int
+ */
+ const LAYOUT_TABS = 2;
+
+ /**
+ * Known navigation types
+ *
+ * @var array
+ */
+ protected static $types;
+
+ /**
+ * This navigation's items
+ *
+ * @var NavigationItem[]
+ */
+ protected $items = array();
+
+ /**
+ * This navigation's layout
+ *
+ * @var int
+ */
+ protected $layout;
+
+ public function offsetExists($offset): bool
+ {
+ return isset($this->items[$offset]);
+ }
+
+ public function offsetGet($offset): ?NavigationItem
+ {
+ return $this->items[$offset] ?? null;
+ }
+
+ public function offsetSet($offset, $value): void
+ {
+ $this->items[$offset] = $value;
+ }
+
+ public function offsetUnset($offset): void
+ {
+ unset($this->items[$offset]);
+ }
+
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ public function getIterator(): Traversable
+ {
+ $this->order();
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * Create and return a new navigation item for the given configuration
+ *
+ * @param string $name
+ * @param array|ConfigObject $properties
+ *
+ * @return NavigationItem
+ *
+ * @throws InvalidArgumentException If the $properties argument is neither an array nor a ConfigObject
+ */
+ public function createItem($name, $properties)
+ {
+ if ($properties instanceof ConfigObject) {
+ $properties = $properties->toArray();
+ } elseif (! is_array($properties)) {
+ throw new InvalidArgumentException('Argument $properties must be of type array or ConfigObject');
+ }
+
+ $itemType = isset($properties['type']) ? StringHelper::cname($properties['type'], '-') : 'NavigationItem';
+ if (! empty(static::$types) && isset(static::$types[$itemType])) {
+ return new static::$types[$itemType]($name, $properties);
+ }
+
+ $item = null;
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ $classPath = 'Icinga\\Module\\'
+ . ucfirst($module->getName())
+ . '\\'
+ . static::NAVIGATION_NS
+ . '\\'
+ . $itemType;
+ if (class_exists($classPath)) {
+ $item = new $classPath($name, $properties);
+ break;
+ }
+ }
+
+ if ($item === null) {
+ $classPath = 'Icinga\\' . static::NAVIGATION_NS . '\\' . $itemType;
+ if (class_exists($classPath)) {
+ $item = new $classPath($name, $properties);
+ }
+ }
+
+ if ($item === null) {
+ if ($itemType !== 'MenuItem') {
+ Logger::debug(
+ 'Failed to find custom navigation item class %s for item %s. Using base class NavigationItem now',
+ $itemType,
+ $name
+ );
+ }
+
+ $item = new NavigationItem($name, $properties);
+ static::$types[$itemType] = 'Icinga\\Web\\Navigation\\NavigationItem';
+ } elseif (! $item instanceof NavigationItem) {
+ throw new ProgrammingError('Class %s must inherit from NavigationItem', $classPath);
+ } else {
+ static::$types[$itemType] = $classPath;
+ }
+
+ return $item;
+ }
+
+ /**
+ * Add a navigation item
+ *
+ * If you do not pass an instance of NavigationItem, this will only add the item
+ * if it does not require a permission or the current user has the permission.
+ *
+ * @param string|NavigationItem $name The name of the item or an instance of NavigationItem
+ * @param array $properties The properties of the item to add (Ignored if $name is not a string)
+ *
+ * @return bool Whether the item was added or not
+ *
+ * @throws InvalidArgumentException In case $name is neither a string nor an instance of NavigationItem
+ */
+ public function addItem($name, array $properties = array())
+ {
+ if (is_string($name)) {
+ if (isset($properties['permission'])) {
+ if (! Auth::getInstance()->hasPermission($properties['permission'])) {
+ return false;
+ }
+
+ unset($properties['permission']);
+ }
+
+ $item = $this->createItem($name, $properties);
+ } elseif (! $name instanceof NavigationItem) {
+ throw new InvalidArgumentException('Argument $name must be of type string or NavigationItem');
+ } else {
+ $item = $name;
+ }
+
+ $this->items[$item->getName()] = $item;
+ return true;
+ }
+
+ /**
+ * Return the item with the given name
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return NavigationItem|mixed
+ */
+ public function getItem($name, $default = null)
+ {
+ return isset($this->items[$name]) ? $this->items[$name] : $default;
+ }
+
+ /**
+ * Return the currently active item or the first one if none is active
+ *
+ * @return NavigationItem
+ */
+ public function getActiveItem()
+ {
+ foreach ($this->items as $item) {
+ if ($item->getActive()) {
+ return $item;
+ }
+ }
+
+ $firstItem = reset($this->items);
+ return $firstItem ? $firstItem->setActive() : null;
+ }
+
+ /**
+ * Return this navigation's items
+ *
+ * @return array
+ */
+ public function getItems()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Return whether this navigation is empty
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->items);
+ }
+
+ /**
+ * Return whether this navigation has any renderable items
+ *
+ * @return bool
+ */
+ public function hasRenderableItems()
+ {
+ foreach ($this->getItems() as $item) {
+ if ($item->shouldRender()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Return this navigation's layout
+ *
+ * @return int
+ */
+ public function getLayout()
+ {
+ return $this->layout;
+ }
+
+ /**
+ * Set this navigation's layout
+ *
+ * @param int $layout
+ *
+ * @return $this
+ */
+ public function setLayout($layout)
+ {
+ $this->layout = (int) $layout;
+ return $this;
+ }
+
+ /**
+ * Create and return the renderer for this navigation
+ *
+ * @return RecursiveNavigationRenderer
+ */
+ public function getRenderer()
+ {
+ return new RecursiveNavigationRenderer($this);
+ }
+
+ /**
+ * Return this navigation rendered to HTML
+ *
+ * @return string
+ */
+ public function render()
+ {
+ return $this->getRenderer()->render();
+ }
+
+ /**
+ * Order this navigation's items
+ *
+ * @return $this
+ */
+ public function order()
+ {
+ uasort($this->items, array($this, 'compareItems'));
+ foreach ($this->items as $item) {
+ if ($item->hasChildren()) {
+ $item->getChildren()->order();
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether the first item is less than, more than or equal to the second one
+ *
+ * @param NavigationItem $a
+ * @param NavigationItem $b
+ *
+ * @return int
+ */
+ protected function compareItems(NavigationItem $a, NavigationItem $b)
+ {
+ if ($a->getPriority() === $b->getPriority()) {
+ return strcasecmp($a->getLabel(), $b->getLabel());
+ }
+
+ return $a->getPriority() > $b->getPriority() ? 1 : -1;
+ }
+
+ /**
+ * Try to find and return a item with the given or a similar name
+ *
+ * @param string $name
+ *
+ * @return NavigationItem
+ */
+ public function findItem($name)
+ {
+ $item = $this->getItem($name);
+ if ($item !== null) {
+ return $item;
+ }
+
+ $loweredName = strtolower($name);
+ foreach ($this->getItems() as $item) {
+ if (strtolower($item->getName()) === $loweredName) {
+ return $item;
+ }
+ }
+ }
+
+ /**
+ * Merge this navigation with the given one
+ *
+ * Any duplicate items of this navigation will be overwritten by the given navigation's items.
+ *
+ * @param Navigation $navigation
+ *
+ * @return $this
+ */
+ public function merge(Navigation $navigation)
+ {
+ foreach ($navigation as $item) {
+ /** @var $item NavigationItem */
+ if (($existingItem = $this->findItem($item->getName())) !== null) {
+ if ($existingItem->conflictsWith($item)) {
+ $name = $item->getName();
+ do {
+ if (preg_match('~_(\d+)$~', $name, $matches)) {
+ $name = preg_replace('~_\d+$~', $matches[1] + 1, $name);
+ } else {
+ $name .= '_2';
+ }
+ } while ($this->getItem($name) !== null);
+
+ $this->addItem($item->setName($name));
+ } else {
+ $existingItem->merge($item);
+ }
+ } else {
+ $this->addItem($item);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Extend this navigation set with all additional items of the given type
+ *
+ * This will fetch navigation items from the following sources:
+ * * User Shareables
+ * * User Preferences
+ * * Modules
+ * Any existing entry will be overwritten by one that is coming later in order.
+ *
+ * @param string $type
+ *
+ * @return $this
+ */
+ public function load($type)
+ {
+ $user = Auth::getInstance()->getUser();
+ if ($type !== 'dashboard-pane') {
+ // Shareables
+ $this->merge(Icinga::app()->getSharedNavigation($type));
+
+ // User Preferences
+ $this->merge($user->getNavigation($type));
+ }
+
+ // Modules
+ $moduleManager = Icinga::app()->getModuleManager();
+ foreach ($moduleManager->getLoadedModules() as $module) {
+ if ($user->can($moduleManager::MODULE_PERMISSION_NS . $module->getName())) {
+ if ($type === 'menu-item') {
+ $this->merge($module->getMenu());
+ } elseif ($type === 'dashboard-pane') {
+ $this->merge($module->getDashboard());
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the global navigation item type configuration
+ *
+ * @return array
+ */
+ public static function getItemTypeConfiguration()
+ {
+ $defaultItemTypes = array(
+ 'menu-item' => array(
+ 'label' => t('Menu Entry'),
+ 'config' => 'menu'
+ )/*, // Disabled, until it is able to fully replace the old implementation
+ 'dashlet' => array(
+ 'label' => 'Dashlet',
+ 'config' => 'dashboard'
+ )*/
+ );
+
+ $moduleItemTypes = array();
+ $moduleManager = Icinga::app()->getModuleManager();
+ foreach ($moduleManager->getLoadedModules() as $module) {
+ if (Auth::getInstance()->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) {
+ foreach ($module->getNavigationItems() as $type => $options) {
+ if (! isset($moduleItemTypes[$type])) {
+ $moduleItemTypes[$type] = $options;
+ }
+ }
+ }
+ }
+
+ return array_merge($defaultItemTypes, $moduleItemTypes);
+ }
+
+ /**
+ * Create and return a new set of navigation items for the given configuration
+ *
+ * Note that this is supposed to be utilized for one dimensional structures
+ * only. Multi dimensional structures can be processed by fromArray().
+ *
+ * @param Traversable|array $config
+ *
+ * @return Navigation
+ *
+ * @throws InvalidArgumentException In case the given configuration is invalid
+ * @throws ConfigurationError In case a referenced parent does not exist
+ */
+ public static function fromConfig($config)
+ {
+ if (! is_array($config) && !$config instanceof Traversable) {
+ throw new InvalidArgumentException('Argument $config must be an array or a instance of Traversable');
+ }
+
+ $flattened = $orphans = $topLevel = array();
+ foreach ($config as $sectionName => $sectionConfig) {
+ $parentName = $sectionConfig->parent;
+ unset($sectionConfig->parent);
+
+ if (! $parentName) {
+ $topLevel[$sectionName] = $sectionConfig->toArray();
+ $flattened[$sectionName] = & $topLevel[$sectionName];
+ } elseif (isset($flattened[$parentName])) {
+ $flattened[$parentName]['children'][$sectionName] = $sectionConfig->toArray();
+ $flattened[$sectionName] = & $flattened[$parentName]['children'][$sectionName];
+ } else {
+ $orphans[$parentName][$sectionName] = $sectionConfig->toArray();
+ $flattened[$sectionName] = & $orphans[$parentName][$sectionName];
+ }
+ }
+
+ do {
+ $match = false;
+ foreach ($orphans as $parentName => $children) {
+ if (isset($flattened[$parentName])) {
+ if (isset($flattened[$parentName]['children'])) {
+ $flattened[$parentName]['children'] = array_merge(
+ $flattened[$parentName]['children'],
+ $children
+ );
+ } else {
+ $flattened[$parentName]['children'] = $children;
+ }
+
+ unset($orphans[$parentName]);
+ $match = true;
+ }
+ }
+ } while ($match && !empty($orphans));
+
+ if (! empty($orphans)) {
+ throw new ConfigurationError(
+ t(
+ 'Failed to fully parse navigation configuration. Ensure that'
+ . ' all referenced parents are existing navigation items: %s'
+ ),
+ join(', ', array_keys($orphans))
+ );
+ }
+
+ return static::fromArray($topLevel);
+ }
+
+ /**
+ * Create and return a new set of navigation items for the given array
+ *
+ * @param array $array
+ *
+ * @return Navigation
+ */
+ public static function fromArray(array $array)
+ {
+ $navigation = new static();
+ foreach ($array as $name => $properties) {
+ $navigation->addItem((string) $name, $properties);
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Return this navigation rendered to HTML
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return IcingaException::describe($e);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Navigation/NavigationItem.php b/library/Icinga/Web/Navigation/NavigationItem.php
new file mode 100644
index 0000000..e262f5a
--- /dev/null
+++ b/library/Icinga/Web/Navigation/NavigationItem.php
@@ -0,0 +1,947 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation;
+
+use Exception;
+use Icinga\Authentication\Auth;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Navigation\Renderer\NavigationItemRenderer;
+use Icinga\Web\Url;
+use Traversable;
+
+/**
+ * A navigation item
+ */
+class NavigationItem implements IteratorAggregate
+{
+ /**
+ * Alternative markup element for items without a url
+ *
+ * @var string
+ */
+ const LINK_ALTERNATIVE = 'span';
+
+ /**
+ * The class namespace where to locate navigation type renderer classes
+ */
+ const RENDERER_NS = 'Web\\Navigation\\Renderer';
+
+ /**
+ * Whether this item is active
+ *
+ * @var bool
+ */
+ protected $active;
+
+ /**
+ * Whether this item is selected
+ *
+ * @var bool
+ */
+ protected $selected;
+
+ /**
+ * The CSS class used for the outer li element
+ *
+ * @var string
+ */
+ protected $cssClass;
+
+ /**
+ * This item's priority
+ *
+ * The priority defines when the item is rendered in relation to its parent's childs.
+ *
+ * @var int
+ */
+ protected $priority;
+
+ /**
+ * The attributes of this item's element
+ *
+ * @var array
+ */
+ protected $attributes;
+
+ /**
+ * This item's children
+ *
+ * @var Navigation
+ */
+ protected $children;
+
+ /**
+ * This item's icon
+ *
+ * @var string
+ */
+ protected $icon;
+
+ /**
+ * This item's name
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * This item's label
+ *
+ * @var string
+ */
+ protected $label;
+
+ /**
+ * The item's description
+ *
+ * @var string
+ */
+ protected $description;
+
+ /**
+ * This item's parent
+ *
+ * @var NavigationItem
+ */
+ protected $parent;
+
+ /**
+ * This item's url
+ *
+ * @var Url
+ */
+ protected $url;
+
+ /**
+ * This item's url target
+ *
+ * @var string
+ */
+ protected $target;
+
+ /**
+ * Additional parameters for this item's url
+ *
+ * @var array
+ */
+ protected $urlParameters;
+
+ /**
+ * This item's renderer
+ *
+ * @var NavigationItemRenderer
+ */
+ protected $renderer;
+
+ /**
+ * Whether to render this item
+ *
+ * @var bool
+ */
+ protected $render;
+
+ /**
+ * Create a new NavigationItem
+ *
+ * @param string $name
+ * @param array $properties
+ */
+ public function __construct($name, array $properties = null)
+ {
+ $this->setName($name);
+ $this->children = new Navigation();
+
+ if (! empty($properties)) {
+ $this->setProperties($properties);
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Initialize this NavigationItem
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * @return Navigation
+ */
+ public function getIterator(): Traversable
+ {
+ return $this->getChildren();
+ }
+
+ /**
+ * Return whether this item is active
+ *
+ * @return bool
+ */
+ public function getActive()
+ {
+ if ($this->active === null) {
+ $this->active = false;
+ if ($this->getUrl() !== null && Icinga::app()->getRequest()->getUrl()->matches($this->getUrl())) {
+ $this->setActive();
+ } elseif ($this->hasChildren()) {
+ foreach ($this->getChildren() as $item) {
+ /** @var NavigationItem $item */
+ if ($item->getActive()) {
+ // Do nothing, a true active state is automatically passed to all parents
+ }
+ }
+ }
+ }
+
+ return $this->active;
+ }
+
+ /**
+ * Set whether this item is active
+ *
+ * If it's active and has a parent, the parent gets activated as well.
+ *
+ * @param bool $active
+ *
+ * @return $this
+ */
+ public function setActive($active = true)
+ {
+ $this->active = (bool) $active;
+ if ($this->active && $this->getParent() !== null) {
+ $this->getParent()->setActive();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether this item is selected
+ *
+ * @return bool
+ */
+ public function getSelected()
+ {
+ if ($this->selected === null) {
+ $this->active = false;
+ if ($this->getUrl() !== null && Icinga::app()->getRequest()->getUrl()->matches($this->getUrl())) {
+ $this->setSelected();
+ }
+ }
+
+ return $this->selected;
+ }
+
+ /**
+ * Set whether this item is active
+ *
+ * If it's active and has a parent, the parent gets activated as well.
+ *
+ * @param bool $selected
+ *
+ * @return $this
+ */
+ public function setSelected($selected = true)
+ {
+ $this->selected = (bool) $selected;
+
+ return $this;
+ }
+
+ /**
+ * Get the CSS class used for the outer li element
+ *
+ * @return string
+ */
+ public function getCssClass()
+ {
+ return $this->cssClass;
+ }
+
+ /**
+ * Set the CSS class to use for the outer li element
+ *
+ * @param string $class
+ *
+ * @return $this
+ */
+ public function setCssClass($class)
+ {
+ $this->cssClass = (string) $class;
+ return $this;
+ }
+
+ /**
+ * Return this item's priority
+ *
+ * @return int
+ */
+ public function getPriority()
+ {
+ return $this->priority !== null ? $this->priority : 100;
+ }
+
+ /**
+ * Set this item's priority
+ *
+ * @param int $priority
+ *
+ * @return $this
+ */
+ public function setPriority($priority)
+ {
+ $this->priority = (int) $priority;
+ return $this;
+ }
+
+ /**
+ * Return the value of the given element attribute
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null)
+ {
+ $attributes = $this->getAttributes();
+ return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
+ }
+
+ /**
+ * Set the value of the given element attribute
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Return the attributes of this item's element
+ *
+ * @return array
+ */
+ public function getAttributes()
+ {
+ return $this->attributes ?: array();
+ }
+
+ /**
+ * Set the attributes of this item's element
+ *
+ * @param array $attributes
+ *
+ * @return $this
+ */
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+ return $this;
+ }
+
+ /**
+ * Add a child to this item
+ *
+ * If the child is active this item gets activated as well.
+ *
+ * @param NavigationItem $child
+ *
+ * @return $this
+ */
+ public function addChild(NavigationItem $child)
+ {
+ $this->getChildren()->addItem($child->setParent($this));
+ if ($child->getActive()) {
+ $this->setActive();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return this item's children
+ *
+ * @return Navigation
+ */
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ /**
+ * Return whether this item has any children
+ *
+ * @return bool
+ */
+ public function hasChildren()
+ {
+ return ! $this->getChildren()->isEmpty();
+ }
+
+ /**
+ * Set this item's children
+ *
+ * @param array|Navigation $children
+ *
+ * @return $this
+ */
+ public function setChildren($children)
+ {
+ if (is_array($children)) {
+ $children = Navigation::fromArray($children);
+ } elseif (! $children instanceof Navigation) {
+ throw new InvalidArgumentException('Argument $children must be of type array or Navigation');
+ }
+
+ foreach ($children as $item) {
+ $item->setParent($this);
+ }
+
+ $this->children = $children;
+ return $this;
+ }
+
+ /**
+ * Return this item's icon
+ *
+ * @return string
+ */
+ public function getIcon()
+ {
+ return $this->icon;
+ }
+
+ /**
+ * Set this item's icon
+ *
+ * @param string $icon
+ *
+ * @return $this
+ */
+ public function setIcon($icon)
+ {
+ $this->icon = $icon;
+ return $this;
+ }
+
+ /**
+ * Return this item's name escaped with only ASCII chars and/or digits
+ *
+ * @return string
+ */
+ protected function getEscapedName()
+ {
+ return preg_replace('~[^a-zA-Z0-9]~', '_', $this->getName());
+ }
+
+ /**
+ * Return a unique version of this item's name
+ *
+ * @return string
+ */
+ public function getUniqueName()
+ {
+ if ($this->getParent() === null) {
+ return 'navigation-' . $this->getEscapedName();
+ }
+
+ return $this->getParent()->getUniqueName() . '-' . $this->getEscapedName();
+ }
+
+ /**
+ * Return this item's name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set this item's name
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Set this item's parent
+ *
+ * @param NavigationItem $parent
+ *
+ * @return $this
+ */
+ public function setParent(NavigationItem $parent)
+ {
+ $this->parent = $parent;
+ return $this;
+ }
+
+ /**
+ * Return this item's parent
+ *
+ * @return NavigationItem
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Return this item's label
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ return $this->label !== null ? $this->label : $this->getName();
+ }
+
+ /**
+ * Set this item's label
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setLabel($label)
+ {
+ $this->label = $label;
+ return $this;
+ }
+
+ /**
+ * Get the item's description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set the item's description
+ *
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Set this item's url target
+ *
+ * @param string $target
+ *
+ * @return $this
+ */
+ public function setTarget($target)
+ {
+ $this->target = $target;
+ return $this;
+ }
+
+ /**
+ * Return this item's url target
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->target;
+ }
+
+ /**
+ * Return this item's url
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ if ($this->url === null && $this->hasChildren()) {
+ $this->setUrl(Url::fromPath('navigation/dashboard', array('name' => strtolower($this->getName()))));
+ }
+
+ return $this->url;
+ }
+
+ /**
+ * Set this item's url
+ *
+ * @param Url|string $url
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If the given url is neither of type
+ */
+ public function setUrl($url)
+ {
+ if (is_string($url)) {
+ $url = Url::fromPath($this->resolveMacros($url));
+ } elseif ($url instanceof Url) {
+ $url = Url::fromPath($this->resolveMacros($url->getAbsoluteUrl()));
+ } else {
+ throw new InvalidArgumentException('Argument $url must be of type string or Url');
+ }
+
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * Return the value of the given url parameter
+ *
+ * @param string $name
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getUrlParameter($name, $default = null)
+ {
+ $parameters = $this->getUrlParameters();
+ return isset($parameters[$name]) ? $parameters[$name] : $default;
+ }
+
+ /**
+ * Set the value of the given url parameter
+ *
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setUrlParameter($name, $value)
+ {
+ $this->urlParameters[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Return all additional parameters for this item's url
+ *
+ * @return array
+ */
+ public function getUrlParameters()
+ {
+ return $this->urlParameters ?: array();
+ }
+
+ /**
+ * Set additional parameters for this item's url
+ *
+ * @param array $urlParameters
+ *
+ * @return $this
+ */
+ public function setUrlParameters(array $urlParameters)
+ {
+ $this->urlParameters = $urlParameters;
+ return $this;
+ }
+
+ /**
+ * Set this item's properties
+ *
+ * Unknown properties (no matching setter) are considered as element attributes.
+ *
+ * @param array $properties
+ *
+ * @return $this
+ */
+ public function setProperties(array $properties)
+ {
+ foreach ($properties as $name => $value) {
+ $setter = 'set' . ucfirst($name);
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ } else {
+ $this->setAttribute($name, $value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Merge this item with the given one
+ *
+ * @param NavigationItem $item
+ *
+ * @return $this
+ */
+ public function merge(NavigationItem $item)
+ {
+ if ($this->conflictsWith($item)) {
+ throw new ProgrammingError('Cannot merge, conflict detected.');
+ }
+
+ if ($this->priority === null) {
+ $priority = $item->getPriority();
+ if ($priority !== 100) {
+ $this->setPriority($priority);
+ }
+ }
+
+ if (! $this->getIcon()) {
+ $this->setIcon($item->getIcon());
+ }
+
+ if ($this->getLabel() === $this->getName() && $item->getLabel() !== $item->getName()) {
+ $this->setLabel($item->getLabel());
+ }
+
+ if ($this->target === null && ($target = $item->getTarget()) !== null) {
+ $this->setTarget($target);
+ }
+
+ if ($this->renderer === null) {
+ $renderer = $item->getRenderer();
+ if (get_class($renderer) !== 'NavigationItemRenderer') {
+ $this->setRenderer($renderer);
+ }
+ }
+
+ foreach ($item->getAttributes() as $name => $value) {
+ $this->setAttribute($name, $value);
+ }
+
+ foreach ($item->getUrlParameters() as $name => $value) {
+ $this->setUrlParameter($name, $value);
+ }
+
+ if ($item->hasChildren()) {
+ $this->getChildren()->merge($item->getChildren());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether it's possible to merge this item with the given one
+ *
+ * @param NavigationItem $item
+ *
+ * @return bool
+ */
+ public function conflictsWith(NavigationItem $item)
+ {
+ if (! $item instanceof $this) {
+ return true;
+ }
+
+ if ($this->getUrl() === null || $item->getUrl() === null) {
+ return false;
+ }
+
+ return !$this->getUrl()->matches($item->getUrl());
+ }
+
+ /**
+ * Create and return the given renderer
+ *
+ * @param string|array $name
+ *
+ * @return NavigationItemRenderer
+ */
+ protected function createRenderer($name)
+ {
+ if (is_array($name)) {
+ $options = array_splice($name, 1);
+ $name = $name[0];
+ } else {
+ $options = array();
+ }
+
+ $renderer = null;
+ foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
+ $classPath = 'Icinga\\Module\\' . ucfirst($module->getName()) . '\\' . static::RENDERER_NS . '\\' . $name;
+ if (class_exists($classPath)) {
+ $renderer = new $classPath($options);
+ break;
+ }
+ }
+
+ if ($renderer === null) {
+ $classPath = 'Icinga\\' . static::RENDERER_NS . '\\' . $name;
+ if (class_exists($classPath)) {
+ $renderer = new $classPath($options);
+ }
+ }
+
+ if ($renderer === null) {
+ throw new ProgrammingError(
+ 'Cannot find renderer "%s" for navigation item "%s"',
+ $name,
+ $this->getName()
+ );
+ } elseif (! $renderer instanceof NavigationItemRenderer) {
+ throw new ProgrammingError('Class %s must inherit from NavigationItemRenderer', $classPath);
+ }
+
+ return $renderer;
+ }
+
+ /**
+ * Set this item's renderer
+ *
+ * @param string|array|NavigationItemRenderer $renderer
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException If the $renderer argument is neither a string nor a NavigationItemRenderer
+ */
+ public function setRenderer($renderer)
+ {
+ if (is_string($renderer) || is_array($renderer)) {
+ $renderer = $this->createRenderer($renderer);
+ } elseif (! $renderer instanceof NavigationItemRenderer) {
+ throw new InvalidArgumentException(
+ 'Argument $renderer must be of type string, array or NavigationItemRenderer'
+ );
+ }
+
+ $this->renderer = $renderer;
+ return $this;
+ }
+
+ /**
+ * Return this item's renderer
+ *
+ * @return NavigationItemRenderer
+ */
+ public function getRenderer()
+ {
+ if ($this->renderer === null) {
+ $this->setRenderer('NavigationItemRenderer');
+ }
+
+ return $this->renderer;
+ }
+
+ /**
+ * Set whether this item should be rendered
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setRender($state = true)
+ {
+ $this->render = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this item should be rendered
+ *
+ * @return bool
+ */
+ public function getRender()
+ {
+ if ($this->render === null) {
+ return $this->getUrl() !== null;
+ }
+
+ return $this->render;
+ }
+
+ /**
+ * Return whether this item should be rendered
+ *
+ * Alias for NavigationItem::getRender().
+ *
+ * @return bool
+ */
+ public function shouldRender()
+ {
+ return $this->getRender();
+ }
+
+ /**
+ * Return this item rendered to HTML
+ *
+ * @return string
+ */
+ public function render()
+ {
+ try {
+ return $this->getRenderer()->setItem($this)->render();
+ } catch (Exception $e) {
+ Logger::error(
+ 'Could not invoke custom navigation item renderer. %s in %s:%d with message: %s',
+ get_class($e),
+ $e->getFile(),
+ $e->getLine(),
+ $e->getMessage()
+ );
+
+ $renderer = new NavigationItemRenderer();
+ return $renderer->render($this);
+ }
+ }
+
+ /**
+ * Return this item rendered to HTML
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return IcingaException::describe($e);
+ }
+ }
+
+ /**
+ * Resolve all macros in the given URL
+ *
+ * @param string $url
+ *
+ * @return string
+ */
+ protected function resolveMacros($url)
+ {
+ if (strpos($url, '$') === false) {
+ return $url;
+ }
+
+ $macros = [];
+ if (Auth::getInstance()->isAuthenticated()) {
+ $macros['$user.local_name$'] = Auth::getInstance()->getUser()->getLocalUsername();
+ }
+ if (! empty($macros)) {
+ $url = str_replace(array_keys($macros), array_values($macros), $url);
+ }
+
+ return $url;
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php
new file mode 100644
index 0000000..8510f70
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/BadgeNavigationItemRenderer.php
@@ -0,0 +1,139 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+use Icinga\Web\Navigation\NavigationItem;
+
+/**
+ * Abstract base class for a NavigationItem with a status badge
+ */
+abstract class BadgeNavigationItemRenderer extends NavigationItemRenderer
+{
+ const STATE_OK = 'ok';
+ const STATE_CRITICAL = 'critical';
+ const STATE_WARNING = 'warning';
+ const STATE_PENDING = 'pending';
+ const STATE_UNKNOWN = 'unknown';
+
+ /**
+ * The tooltip text for the badge
+ *
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * The state identifier being used
+ *
+ * The state identifier defines the background color of the badge.
+ *
+ * @var string
+ */
+ protected $state;
+
+ /**
+ * Set the tooltip text for the badge
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * Return the tooltip text for the badge
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set the state identifier to use
+ *
+ * @param string $state
+ *
+ * @return $this
+ */
+ public function setState($state)
+ {
+ $this->state = $state;
+ return $this;
+ }
+
+ /**
+ * Return the state identifier to use
+ *
+ * @return string
+ */
+ public function getState()
+ {
+ return $this->state;
+ }
+
+ /**
+ * Return the amount of items represented by the badge
+ *
+ * @return int
+ */
+ abstract public function getCount();
+
+ /**
+ * Render the given navigation item as HTML anchor with a badge
+ *
+ * @param NavigationItem $item
+ *
+ * @return string
+ */
+ public function render(NavigationItem $item = null)
+ {
+ if ($item === null) {
+ $item = $this->getItem();
+ }
+
+ $cssClass = '';
+ if ($item->getCssClass() !== null) {
+ $cssClass = ' ' . $item->getCssClass();
+ }
+
+ $item->setCssClass('badge-nav-item' . $cssClass);
+ $this->setEscapeLabel(false);
+ $label = $this->view()->escape($item->getLabel());
+ $item->setLabel($this->renderBadge() . $label);
+ $html = parent::render($item);
+ return $html;
+ }
+
+ /**
+ * Render the badge
+ *
+ * @return string
+ */
+ protected function renderBadge()
+ {
+ if ($count = $this->getCount()) {
+ if ($count > 1000000) {
+ $count = round($count, -6) / 1000000 . 'M';
+ } elseif ($count > 1000) {
+ $count = round($count, -3) / 1000 . 'k';
+ }
+
+ $view = $this->view();
+ return sprintf(
+ '<span title="%s" class="badge state-%s">%s</span>',
+ $view->escape($this->getTitle()),
+ $view->escape($this->getState()),
+ $count
+ );
+ }
+
+ return '';
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/HealthNavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/HealthNavigationRenderer.php
new file mode 100644
index 0000000..577895b
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/HealthNavigationRenderer.php
@@ -0,0 +1,44 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+use Icinga\Application\Hook\HealthHook;
+
+class HealthNavigationRenderer extends BadgeNavigationItemRenderer
+{
+ public function getCount()
+ {
+ $count = 0;
+ $title = null;
+ $worstState = null;
+ foreach (HealthHook::collectHealthData()->select() as $result) {
+ if ($worstState === null || $result->state > $worstState) {
+ $worstState = $result->state;
+ $title = $result->message;
+ $count = 1;
+ } elseif ($worstState === $result->state) {
+ $count++;
+ }
+ }
+
+ switch ($worstState) {
+ case HealthHook::STATE_OK:
+ $count = 0;
+ break;
+ case HealthHook::STATE_WARNING:
+ $this->state = self::STATE_WARNING;
+ break;
+ case HealthHook::STATE_CRITICAL:
+ $this->state = self::STATE_CRITICAL;
+ break;
+ case HealthHook::STATE_UNKNOWN:
+ $this->state = self::STATE_UNKNOWN;
+ break;
+ }
+
+ $this->title = $title;
+
+ return $count;
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
new file mode 100644
index 0000000..74de8f6
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
@@ -0,0 +1,233 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Web\Url;
+use Icinga\Web\View;
+
+/**
+ * NavigationItemRenderer
+ */
+class NavigationItemRenderer
+{
+ /**
+ * View
+ *
+ * @var View
+ */
+ protected $view;
+
+ /**
+ * The item being rendered
+ *
+ * @var NavigationItem
+ */
+ protected $item;
+
+ /**
+ * Internal link targets provided by Icinga Web 2
+ *
+ * @var array
+ */
+ protected $internalLinkTargets;
+
+ /**
+ * Whether to escape the label
+ *
+ * @var bool
+ */
+ protected $escapeLabel;
+
+ /**
+ * Create a new NavigationItemRenderer
+ *
+ * @param array $options
+ */
+ public function __construct(array $options = null)
+ {
+ if (! empty($options)) {
+ $this->setOptions($options);
+ }
+
+ $this->internalLinkTargets = array('_main', '_self', '_next');
+ $this->init();
+ }
+
+ /**
+ * Initialize this renderer
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set the given options
+ *
+ * @param array $options
+ *
+ * @return $this
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $name => $value) {
+ $setter = 'set' . StringHelper::cname($name);
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ }
+ }
+ }
+
+ /**
+ * Set the view
+ *
+ * @param View $view
+ *
+ * @return $this
+ */
+ public function setView(View $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Return the view
+ *
+ * @return View
+ */
+ public function view()
+ {
+ if ($this->view === null) {
+ $this->setView(Icinga::app()->getViewRenderer()->view);
+ }
+
+ return $this->view;
+ }
+
+ /**
+ * Set the navigation item to render
+ *
+ * @param NavigationItem $item
+ *
+ * @return $this
+ */
+ public function setItem(NavigationItem $item)
+ {
+ $this->item = $item;
+ return $this;
+ }
+
+ /**
+ * Return the navigation item being rendered
+ *
+ * @return NavigationItem
+ */
+ public function getItem()
+ {
+ return $this->item;
+ }
+
+ /**
+ * Set whether to escape the label
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setEscapeLabel($state = true)
+ {
+ $this->escapeLabel = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether to escape the label
+ *
+ * @return bool
+ */
+ public function getEscapeLabel()
+ {
+ return $this->escapeLabel !== null ? $this->escapeLabel : true;
+ }
+
+ /**
+ * Render the given navigation item as HTML anchor
+ *
+ * @param NavigationItem $item
+ *
+ * @return string
+ */
+ public function render(NavigationItem $item = null)
+ {
+ if ($item !== null) {
+ $this->setItem($item);
+ } elseif (($item = $this->getItem()) === null) {
+ throw new ProgrammingError(
+ 'Cannot render nothing. Pass the item to render as part'
+ . ' of the call to render() or set it with setItem()'
+ );
+ }
+
+ $label = $this->getEscapeLabel()
+ ? $this->view()->escape($item->getLabel())
+ : $item->getLabel();
+ if (($icon = $item->getIcon()) !== null) {
+ $label = $this->view()->icon($icon) . $label;
+ } elseif ($item->getName()) {
+ $firstLetter = $item->getName()[0];
+ $label = $this->view()->icon('letter', null, ['data-letter' => strtolower($firstLetter)]) . $label;
+ }
+
+ if (($url = $item->getUrl()) !== null) {
+ $url->overwriteParams($item->getUrlParameters());
+
+ $target = $item->getTarget();
+ if ($url->isExternal() && (!$target || in_array($target, $this->internalLinkTargets, true))) {
+ $url = Url::fromPath('iframe', array('url' => $url));
+ }
+
+ $content = sprintf(
+ '<a%s href="%s"%s>%s</a>',
+ $this->view()->propertiesToString($item->getAttributes()),
+ $this->view()->escape($url->getAbsoluteUrl('&')),
+ $this->renderTargetAttribute(),
+ $label
+ );
+ } elseif ($label) {
+ $content = sprintf(
+ '<%1$s%2$s>%3$s</%1$s>',
+ $item::LINK_ALTERNATIVE,
+ $this->view()->propertiesToString($item->getAttributes()),
+ $label
+ );
+ } else {
+ $content = '';
+ }
+
+ return $content;
+ }
+
+ /**
+ * Render and return the attribute to provide a non-default target for the url
+ *
+ * @return string
+ */
+ protected function renderTargetAttribute()
+ {
+ $target = $this->getItem()->getTarget();
+ if ($target === null || $this->getItem()->getUrl()->getAbsoluteUrl() == '#') {
+ return '';
+ }
+
+ if (! in_array($target, $this->internalLinkTargets, true)) {
+ return ' target="' . $this->view()->escape($target) . '"';
+ }
+
+ return ' data-base-target="' . $target . '"';
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php
new file mode 100644
index 0000000..00c0f9a
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/NavigationRenderer.php
@@ -0,0 +1,356 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+use ArrayIterator;
+use Exception;
+use RecursiveIterator;
+use Icinga\Application\Icinga;
+use Icinga\Exception\IcingaException;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Web\View;
+
+/**
+ * Renderer for single level navigation
+ */
+class NavigationRenderer implements RecursiveIterator, NavigationRendererInterface
+{
+ /**
+ * The tag used for the outer element
+ *
+ * @var string
+ */
+ protected $elementTag;
+
+ /**
+ * The CSS class used for the outer element
+ *
+ * @var string
+ */
+ protected $cssClass;
+
+ /**
+ * The navigation's heading text
+ *
+ * @var string
+ */
+ protected $heading;
+
+ /**
+ * The content rendered so far
+ *
+ * @var array
+ */
+ protected $content;
+
+ /**
+ * Whether to skip rendering the outer element
+ *
+ * @var bool
+ */
+ protected $skipOuterElement;
+
+ /**
+ * The navigation's iterator
+ *
+ * @var ArrayIterator
+ */
+ protected $iterator;
+
+ /**
+ * The navigation
+ *
+ * @var Navigation
+ */
+ protected $navigation;
+
+ /**
+ * View
+ *
+ * @var View
+ */
+ protected $view;
+
+ /**
+ * Create a new NavigationRenderer
+ *
+ * @param Navigation $navigation
+ * @param bool $skipOuterElement
+ */
+ public function __construct(Navigation $navigation, $skipOuterElement = false)
+ {
+ $this->skipOuterElement = $skipOuterElement;
+ $this->iterator = $navigation->getIterator();
+ $this->navigation = $navigation;
+ $this->content = array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setElementTag($tag)
+ {
+ $this->elementTag = $tag;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getElementTag()
+ {
+ return $this->elementTag ?: static::OUTER_ELEMENT_TAG;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCssClass($class)
+ {
+ $this->cssClass = $class;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCssClass()
+ {
+ return $this->cssClass;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setHeading($heading)
+ {
+ $this->heading = $heading;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeading()
+ {
+ return $this->heading;
+ }
+
+ /**
+ * Return the view
+ *
+ * @return View
+ */
+ public function view()
+ {
+ if ($this->view === null) {
+ $this->setView(Icinga::app()->getViewRenderer()->view);
+ }
+
+ return $this->view;
+ }
+
+ /**
+ * Set the view
+ *
+ * @param View $view
+ *
+ * @return $this
+ */
+ public function setView(View $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ public function getChildren(): NavigationRenderer
+ {
+ return new static($this->current()->getChildren(), $this->skipOuterElement);
+ }
+
+ public function hasChildren(): bool
+ {
+ return $this->current()->hasChildren();
+ }
+
+ public function current(): NavigationItem
+ {
+ return $this->iterator->current();
+ }
+
+ public function key(): int
+ {
+ return $this->iterator->key();
+ }
+
+ public function next(): void
+ {
+ $this->iterator->next();
+ }
+
+ public function rewind(): void
+ {
+ $this->iterator->rewind();
+ if (! $this->skipOuterElement) {
+ $this->content[] = $this->beginMarkup();
+ }
+ }
+
+ public function valid(): bool
+ {
+ $valid = $this->iterator->valid();
+ if (! $this->skipOuterElement && !$valid) {
+ $this->content[] = $this->endMarkup();
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Return the opening markup for the navigation
+ *
+ * @return string
+ */
+ public function beginMarkup()
+ {
+ $content = array();
+ $content[] = sprintf(
+ '<%s%s role="navigation">',
+ $this->getElementTag(),
+ $this->getCssClass() !== null ? ' class="' . $this->getCssClass() . '"' : ''
+ );
+ if (($heading = $this->getHeading()) !== null) {
+ $content[] = sprintf(
+ '<h%1$d id="navigation" class="sr-only" tabindex="-1">%2$s</h%1$d>',
+ static::HEADING_RANK,
+ $this->view()->escape($heading)
+ );
+ }
+ $content[] = $this->beginChildrenMarkup();
+ return join("\n", $content);
+ }
+
+ /**
+ * Return the closing markup for the navigation
+ *
+ * @return string
+ */
+ public function endMarkup()
+ {
+ $content = array();
+ $content[] = $this->endChildrenMarkup();
+ $content[] = '</' . $this->getElementTag() . '>';
+ return join("\n", $content);
+ }
+
+ /**
+ * Return the opening markup for multiple navigation items
+ *
+ * @param int $level
+ *
+ * @return string
+ */
+ public function beginChildrenMarkup($level = 1)
+ {
+ $cssClass = array(static::CSS_CLASS_NAV);
+ if ($this->navigation->getLayout() === Navigation::LAYOUT_TABS) {
+ $cssClass[] = static::CSS_CLASS_NAV_TABS;
+ } elseif ($this->navigation->getLayout() === Navigation::LAYOUT_DROPDOWN) {
+ $cssClass[] = static::CSS_CLASS_NAV_DROPDOWN;
+ }
+
+ $cssClass[] = 'nav-level-' . $level;
+
+ return '<ul class="' . join(' ', $cssClass) . '">';
+ }
+
+ /**
+ * Return the closing markup for multiple navigation items
+ *
+ * @return string
+ */
+ public function endChildrenMarkup()
+ {
+ return '</ul>';
+ }
+
+ /**
+ * Return the opening markup for the given navigation item
+ *
+ * @param NavigationItem $item
+ *
+ * @return string
+ */
+ public function beginItemMarkup(NavigationItem $item)
+ {
+ $cssClasses = array(static::CSS_CLASS_ITEM);
+
+ if ($item->hasChildren() && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN) {
+ $cssClasses[] = static::CSS_CLASS_DROPDOWN;
+ $item
+ ->setAttribute('class', static::CSS_CLASS_DROPDOWN_TOGGLE)
+ ->setIcon(static::DROPDOWN_TOGGLE_ICON)
+ ->setUrl('#');
+ }
+
+ if ($item->getActive()) {
+ $cssClasses[] = static::CSS_CLASS_ACTIVE;
+ }
+
+ if ($item->getSelected()) {
+ $cssClasses[] = static::CSS_CLASS_SELECTED;
+ }
+
+ if ($cssClass = $item->getCssClass()) {
+ $cssClasses[] = $cssClass;
+ }
+
+ $content = sprintf(
+ '<li class="%s">',
+ join(' ', $cssClasses)
+ );
+ return $content;
+ }
+
+ /**
+ * Return the closing markup for a navigation item
+ *
+ * @return string
+ */
+ public function endItemMarkup()
+ {
+ return '</li>';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ foreach ($this as $item) {
+ /** @var NavigationItem $item */
+ if ($item->shouldRender()) {
+ $content = $item->render();
+ $this->content[] = $this->beginItemMarkup($item);
+ $this->content[] = $content;
+ $this->content[] = $this->endItemMarkup();
+ }
+ }
+
+ return join("\n", $this->content);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return IcingaException::describe($e);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php
new file mode 100644
index 0000000..4495b73
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/NavigationRendererInterface.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+/**
+ * Interface for navigation renderers
+ */
+interface NavigationRendererInterface
+{
+ /**
+ * CSS class for items
+ *
+ * @var string
+ */
+ const CSS_CLASS_ITEM = 'nav-item';
+
+ /**
+ * CSS class for active items
+ *
+ * @var string
+ */
+ const CSS_CLASS_ACTIVE = 'active';
+
+ /**
+ * CSS class for selected items
+ *
+ * @var string
+ */
+ const CSS_CLASS_SELECTED = 'selected';
+
+ /**
+ * CSS class for dropdown items
+ *
+ * @var string
+ */
+ const CSS_CLASS_DROPDOWN = 'dropdown-nav-item';
+
+ /**
+ * CSS class for a dropdown item's trigger
+ *
+ * @var string
+ */
+ const CSS_CLASS_DROPDOWN_TOGGLE = 'dropdown-toggle';
+
+ /**
+ * CSS class for the ul element
+ *
+ * @var string
+ */
+ const CSS_CLASS_NAV = 'nav';
+
+ /**
+ * CSS class for the ul element with dropdown layout
+ *
+ * @var string
+ */
+ const CSS_CLASS_NAV_DROPDOWN = 'dropdown-nav';
+
+ /**
+ * CSS class for the ul element with tabs layout
+ *
+ * @var string
+ */
+ const CSS_CLASS_NAV_TABS = 'tab-nav';
+
+ /**
+ * Icon for a dropdown item's trigger
+ *
+ * @var string
+ */
+ const DROPDOWN_TOGGLE_ICON = 'menu';
+
+ /**
+ * Default tag for the outer element the navigation will be wrapped with
+ *
+ * @var string
+ */
+ const OUTER_ELEMENT_TAG = 'div';
+
+ /**
+ * The heading's rank
+ *
+ * @var int
+ */
+ const HEADING_RANK = 1;
+
+ /**
+ * Set the tag for the outer element the navigation is wrapped with
+ *
+ * @param string $tag
+ *
+ * @return $this
+ */
+ public function setElementTag($tag);
+
+ /**
+ * Return the tag for the outer element the navigation is wrapped with
+ *
+ * @return string
+ */
+ public function getElementTag();
+
+ /**
+ * Set the CSS class to use for the outer element
+ *
+ * @param string $class
+ *
+ * @return $this
+ */
+ public function setCssClass($class);
+
+ /**
+ * Get the CSS class used for the outer element
+ *
+ * @return string
+ */
+ public function getCssClass();
+
+ /**
+ * Set the navigation's heading text
+ *
+ * @param string $heading
+ *
+ * @return $this
+ */
+ public function setHeading($heading);
+
+ /**
+ * Return the navigation's heading text
+ *
+ * @return string
+ */
+ public function getHeading();
+
+ /**
+ * Return the navigation rendered to HTML
+ *
+ * @return string
+ */
+ public function render();
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php b/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php
new file mode 100644
index 0000000..315c2aa
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/RecursiveNavigationRenderer.php
@@ -0,0 +1,186 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+use Exception;
+use RecursiveIteratorIterator;
+use Icinga\Exception\IcingaException;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Web\Navigation\Renderer\NavigationItemRenderer;
+
+/**
+ * Renderer for multi level navigation
+ *
+ * @method NavigationRenderer getInnerIterator() {
+ * {@inheritdoc}
+ * }
+ */
+class RecursiveNavigationRenderer extends RecursiveIteratorIterator implements NavigationRendererInterface
+{
+ /**
+ * The content rendered so far
+ *
+ * @var array
+ */
+ protected $content;
+
+ /**
+ * Whether to use the standard item renderer
+ *
+ * @var bool
+ */
+ protected $useStandardRenderer;
+
+ /**
+ * Create a new RecursiveNavigationRenderer
+ *
+ * @param Navigation $navigation
+ */
+ public function __construct(Navigation $navigation)
+ {
+ $this->content = array();
+ parent::__construct(
+ new NavigationRenderer($navigation, true),
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+ }
+
+ /**
+ * Set whether to use the standard navigation item renderer
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setUseStandardItemRenderer($state = true)
+ {
+ $this->useStandardRenderer = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether to use the standard navigation item renderer
+ *
+ * @return bool
+ */
+ public function getUseStandardItemRenderer()
+ {
+ return $this->useStandardRenderer;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setElementTag($tag)
+ {
+ $this->getInnerIterator()->setElementTag($tag);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getElementTag()
+ {
+ return $this->getInnerIterator()->getElementTag();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCssClass($class)
+ {
+ $this->getInnerIterator()->setCssClass($class);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCssClass()
+ {
+ return $this->getInnerIterator()->getCssClass();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setHeading($heading)
+ {
+ $this->getInnerIterator()->setHeading($heading);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeading()
+ {
+ return $this->getInnerIterator()->getHeading();
+ }
+
+ public function beginIteration(): void
+ {
+ $this->content[] = $this->getInnerIterator()->beginMarkup();
+ }
+
+ public function endIteration(): void
+ {
+ $this->content[] = $this->getInnerIterator()->endMarkup();
+ }
+
+ public function beginChildren(): void
+ {
+ $this->content[] = $this->getInnerIterator()->beginChildrenMarkup($this->getDepth() + 1);
+ }
+
+ public function endChildren(): void
+ {
+ $this->content[] = $this->getInnerIterator()->endChildrenMarkup();
+ $this->content[] = $this->getInnerIterator()->endItemMarkup();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ foreach ($this as $item) {
+ /** @var NavigationItem $item */
+ if ($item->shouldRender()) {
+ if ($this->getDepth() > 0) {
+ $item->setIcon(null);
+ }
+ if ($this->getUseStandardItemRenderer()) {
+ $renderer = new NavigationItemRenderer();
+ $content = $renderer->render($item);
+ } else {
+ $content = $item->render();
+ }
+ $this->content[] = $this->getInnerIterator()->beginItemMarkup($item);
+
+ $this->content[] = $content;
+
+ if (! $item->hasChildren()) {
+ $this->content[] = $this->getInnerIterator()->endItemMarkup();
+ }
+ }
+ }
+
+ return join("\n", $this->content);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return IcingaException::describe($e);
+ }
+ }
+}
diff --git a/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php
new file mode 100644
index 0000000..2916f4e
--- /dev/null
+++ b/library/Icinga/Web/Navigation/Renderer/SummaryNavigationItemRenderer.php
@@ -0,0 +1,72 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Navigation\Renderer;
+
+/**
+ * Badge renderer summing up the worst state of its children
+ */
+class SummaryNavigationItemRenderer extends BadgeNavigationItemRenderer
+{
+ /**
+ * Cached count
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * State to severity map
+ *
+ * @var array
+ */
+ protected static $stateSeverityMap = array(
+ self::STATE_OK => 0,
+ self::STATE_PENDING => 1,
+ self::STATE_UNKNOWN => 2,
+ self::STATE_WARNING => 3,
+ self::STATE_CRITICAL => 4,
+ );
+
+ /**
+ * Severity to state map
+ *
+ * @var array
+ */
+ protected static $severityStateMap = array(
+ self::STATE_OK,
+ self::STATE_PENDING,
+ self::STATE_UNKNOWN,
+ self::STATE_WARNING,
+ self::STATE_CRITICAL
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCount()
+ {
+ if ($this->count === null) {
+ $countMap = array_fill(0, 5, 0);
+ $maxSeverity = 0;
+ $titles = array();
+ foreach ($this->getItem()->getChildren() as $child) {
+ $renderer = $child->getRenderer();
+ if ($renderer instanceof BadgeNavigationItemRenderer) {
+ $count = $renderer->getCount();
+ if ($count) {
+ $severity = static::$stateSeverityMap[$renderer->getState()];
+ $countMap[$severity] += $count;
+ $titles[] = $renderer->getTitle();
+ $maxSeverity = max($maxSeverity, $severity);
+ }
+ }
+ }
+ $this->count = $countMap[$maxSeverity];
+ $this->state = static::$severityStateMap[$maxSeverity];
+ $this->title = implode('. ', $titles);
+ }
+
+ return $this->count;
+ }
+}
diff --git a/library/Icinga/Web/Notification.php b/library/Icinga/Web/Notification.php
new file mode 100644
index 0000000..6f33a32
--- /dev/null
+++ b/library/Icinga/Web/Notification.php
@@ -0,0 +1,220 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Exception\ProgrammingError;
+use Icinga\Application\Platform;
+use Icinga\Application\Logger;
+use Icinga\Web\Session;
+
+/**
+ * // @TODO(eL): Use Notification not as Singleton but within request:
+ * <code>
+ * <?php
+ * $request->[getUser()]->notify('some message', Notification::INFO);
+ * </code>
+ */
+class Notification
+{
+ /**
+ * Notification type info
+ *
+ * @var string
+ */
+ const INFO = 'info';
+
+ /**
+ * Notification type error
+ *
+ * @var string
+ */
+ const ERROR = 'error';
+
+ /**
+ * Notification type success
+ *
+ * @var string
+ */
+ const SUCCESS = 'success';
+
+ /**
+ * Notification type warning
+ *
+ * @var string
+ */
+ const WARNING = 'warning';
+
+ /**
+ * Name of the session key for notification messages
+ *
+ * @var string
+ */
+ const SESSION_KEY = 'session';
+
+ /**
+ * Singleton instance
+ *
+ * @var self
+ */
+ protected static $instance;
+
+ /**
+ * Whether the platform is CLI
+ *
+ * @var bool
+ */
+ protected $isCli = false;
+
+ /**
+ * Notification messages
+ *
+ * @var array
+ */
+ protected $messages = array();
+
+ /**
+ * Session
+ *
+ * @var Session
+ */
+ protected $session;
+
+ /**
+ * Create the notification instance
+ */
+ final private function __construct()
+ {
+ if (Platform::isCli()) {
+ $this->isCli = true;
+ return;
+ }
+
+ $this->session = Session::getSession();
+ $messages = $this->session->get(self::SESSION_KEY);
+ if (is_array($messages)) {
+ $this->messages = $messages;
+ $this->session->delete(self::SESSION_KEY);
+ $this->session->write();
+ }
+ }
+
+ /**
+ * Get the Notification instance
+ *
+ * @return Notification
+ */
+ public static function getInstance()
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Add info notification
+ *
+ * @param string $msg
+ */
+ public static function info($msg)
+ {
+ self::getInstance()->addMessage($msg, self::INFO);
+ }
+
+ /**
+ * Add error notification
+ *
+ * @param string $msg
+ */
+ public static function error($msg)
+ {
+ self::getInstance()->addMessage($msg, self::ERROR);
+ }
+
+ /**
+ * Add success notification
+ *
+ * @param string $msg
+ */
+ public static function success($msg)
+ {
+ self::getInstance()->addMessage($msg, self::SUCCESS);
+ }
+
+ /**
+ * Add warning notification
+ *
+ * @param string $msg
+ */
+ public static function warning($msg)
+ {
+ self::getInstance()->addMessage($msg, self::WARNING);
+ }
+
+ /**
+ * Add a notification message
+ *
+ * @param string $message
+ * @param string $type
+ */
+ protected function addMessage($message, $type = self::INFO)
+ {
+ if ($this->isCli) {
+ $msg = sprintf('[%s] %s', $type, $message);
+ switch ($type) {
+ case self::INFO:
+ case self::SUCCESS:
+ Logger::info($msg);
+ break;
+ case self::ERROR:
+ Logger::error($msg);
+ break;
+ case self::WARNING:
+ Logger::warning($msg);
+ break;
+ }
+ } else {
+ $this->messages[] = (object) array(
+ 'type' => $type,
+ 'message' => $message,
+ );
+ }
+ }
+
+ /**
+ * Pop the notification messages
+ *
+ * @return array
+ */
+ public function popMessages()
+ {
+ $messages = $this->messages;
+ $this->messages = array();
+ return $messages;
+ }
+
+ /**
+ * Get whether notification messages have been added
+ *
+ * @return bool
+ */
+ public function hasMessages()
+ {
+ return ! empty($this->messages);
+ }
+
+ /**
+ * Destroy the notification instance
+ */
+ final public function __destruct()
+ {
+ if ($this->isCli) {
+ return;
+ }
+ if ($this->hasMessages() && $this->session->get('messages') !== $this->messages) {
+ $this->session->set(self::SESSION_KEY, $this->messages);
+ $this->session->write();
+ }
+ }
+}
diff --git a/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php b/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php
new file mode 100644
index 0000000..6f103e5
--- /dev/null
+++ b/library/Icinga/Web/Paginator/Adapter/QueryAdapter.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Paginator\Adapter;
+
+use Zend_Paginator_Adapter_Interface;
+use Icinga\Data\QueryInterface;
+
+class QueryAdapter implements Zend_Paginator_Adapter_Interface
+{
+ /**
+ * The query being paginated
+ *
+ * @var QueryInterface
+ */
+ protected $query;
+
+ /**
+ * Item count
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * Create a new QueryAdapter
+ *
+ * @param QueryInterface $query The query to paginate
+ */
+ public function __construct(QueryInterface $query)
+ {
+ $this->setQuery($query);
+ }
+
+ /**
+ * Set the query to paginate
+ *
+ * @param QueryInterface $query
+ *
+ * @return $this
+ */
+ public function setQuery(QueryInterface $query)
+ {
+ $this->query = $query;
+ return $this;
+ }
+
+ /**
+ * Return the query being paginated
+ *
+ * @return QueryInterface
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * Fetch and return the rows in the given range of the query result
+ *
+ * @param int $offset Page offset
+ * @param int $itemCountPerPage Number of items per page
+ *
+ * @return array
+ */
+ public function getItems($offset, $itemCountPerPage)
+ {
+ return $this->query->limit($itemCountPerPage, $offset)->fetchAll();
+ }
+
+ /**
+ * Return the total number of items in the query result
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ if ($this->count === null) {
+ $this->count = $this->query->count();
+ }
+
+ return $this->count;
+ }
+}
diff --git a/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php b/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php
new file mode 100644
index 0000000..d9b2ed9
--- /dev/null
+++ b/library/Icinga/Web/Paginator/ScrollingStyle/SlidingWithBorder.php
@@ -0,0 +1,78 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * @see Zend_Paginator_ScrollingStyle_Interface
+ */
+class Icinga_Web_Paginator_ScrollingStyle_SlidingWithBorder implements Zend_Paginator_ScrollingStyle_Interface
+{
+ /**
+ * Returns an array of "local" pages given a page number and range.
+ *
+ * @param Zend_Paginator $paginator
+ * @param integer $pageRange (Optional) Page range
+ * @return array
+ */
+ public function getPages(Zend_Paginator $paginator, $pageRange = null)
+ {
+ // This is unused
+ if ($pageRange === null) {
+ $pageRange = $paginator->getPageRange();
+ }
+
+ $pageNumber = $paginator->getCurrentPageNumber();
+ $pageCount = count($paginator);
+ $range = array();
+
+ if ($pageCount < 10) {
+ // Show all pages if we have less than 10.
+
+ for ($i = 1; $i < 10; $i++) {
+ if ($i > $pageCount) {
+ break;
+ }
+ $range[$i] = $i;
+ }
+ } else {
+ // More than 10 pages:
+
+ foreach (array(1, 2) as $i) {
+ $range[$i] = $i;
+ }
+ if ($pageNumber < 6) {
+ // We are on page 1-5 from
+ for ($i = 1; $i <= 7; $i++) {
+ $range[$i] = $i;
+ }
+ } else {
+ // Current page > 5
+ $range[] = '...';
+
+ // Less than 5 pages left
+ if (($pageCount - $pageNumber) < 5) {
+ $start = 5 - ($pageCount - $pageNumber);
+ } else {
+ $start = 1;
+ }
+
+ for ($i = $pageNumber - $start; $i < ($pageNumber + (4 - $start)); $i++) {
+ if ($i > $pageCount) {
+ break;
+ }
+ $range[$i] = $i;
+ }
+ }
+ if ($pageNumber < ($pageCount - 2)) {
+ $range[] = '...';
+ }
+
+ foreach (array($pageCount - 1, $pageCount) as $i) {
+ $range[$i] = $i;
+ }
+ }
+ if (empty($range)) {
+ $range[] = 1;
+ }
+ return $range;
+ }
+}
diff --git a/library/Icinga/Web/RememberMe.php b/library/Icinga/Web/RememberMe.php
new file mode 100644
index 0000000..1002396
--- /dev/null
+++ b/library/Icinga/Web/RememberMe.php
@@ -0,0 +1,363 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Config;
+use Icinga\Authentication\Auth;
+use Icinga\Crypt\AesCrypt;
+use Icinga\Common\Database;
+use Icinga\User;
+use ipl\Sql\Expression;
+use ipl\Sql\Select;
+use RuntimeException;
+
+/**
+ * Remember me component
+ *
+ * Retains credentials for 30 days by default in order to stay signed in even after the session is closed.
+ */
+class RememberMe
+{
+ use Database;
+
+ /** @var string Cookie name */
+ const COOKIE = 'icingaweb2-remember-me';
+
+ /** @var string Database table name */
+ const TABLE = 'icingaweb_rememberme';
+
+ /** @var string Encrypted password of the user */
+ protected $encryptedPassword;
+
+ /** @var string */
+ protected $username;
+
+ /** @var AesCrypt Instance for encrypting/decrypting the credentials */
+ protected $aesCrypt;
+
+ /** @var int Timestamp when the remember me cookie expires */
+ protected $expiresAt;
+
+ /**
+ * Get whether staying logged in is possible
+ *
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ $self = new self();
+
+ if (! $self->hasDb()) {
+ return false;
+ }
+
+ try {
+ (new AesCrypt())->getMethod();
+ } catch (RuntimeException $_) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get whether the remember cookie is set
+ *
+ * @return bool
+ */
+ public static function hasCookie()
+ {
+ return isset($_COOKIE[static::COOKIE]);
+ }
+
+ /**
+ * Remove the database entry if exists and unset the remember me cookie from PHP's `$_COOKIE` superglobal
+ *
+ * @return Cookie The invalidation cookie which has to be sent to client in oder to remove the remember me cookie
+ */
+ public static function forget()
+ {
+ if (self::hasCookie()) {
+ $data = explode('|', $_COOKIE[static::COOKIE]);
+ $iv = base64_decode(array_pop($data));
+ (new self())->remove(bin2hex($iv));
+ }
+
+ unset($_COOKIE[static::COOKIE]);
+
+ return (new Cookie(static::COOKIE))
+ ->setHttpOnly(true)
+ ->forgetMe();
+ }
+
+ /**
+ * Create the remember me component from the remember me cookie
+ *
+ * @return static
+ */
+ public static function fromCookie()
+ {
+ $data = explode('|', $_COOKIE[static::COOKIE]);
+ $iv = base64_decode(array_pop($data));
+
+ $select = (new Select())
+ ->from(static::TABLE)
+ ->columns('*')
+ ->where(['random_iv = ?' => bin2hex($iv)]);
+
+ $rememberMe = new static();
+ $rs = $rememberMe->getDb()->select($select)->fetch();
+
+ if (! $rs) {
+ throw new RuntimeException(sprintf(
+ "No database entry found for IV '%s'",
+ bin2hex($iv)
+ ));
+ }
+
+ $rememberMe->aesCrypt = (new AesCrypt())
+ ->setKey(hex2bin($rs->passphrase))
+ ->setIV($iv);
+
+ if (count($data) > 1) {
+ $rememberMe->aesCrypt->setTag(
+ base64_decode(array_pop($data))
+ );
+ } elseif ($rememberMe->aesCrypt->isAuthenticatedEncryptionRequired()) {
+ throw new RuntimeException(
+ "The given decryption method needs a tag, but is not specified. "
+ . "You have probably updated the PHP version."
+ );
+ }
+
+ $rememberMe->username = $rs->username;
+ $rememberMe->encryptedPassword = $data[0];
+
+ return $rememberMe;
+ }
+
+ /**
+ * Create the remember me component from the given username and password
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return static
+ */
+ public static function fromCredentials($username, $password)
+ {
+ $aesCrypt = new AesCrypt();
+ $rememberMe = new static();
+ $rememberMe->encryptedPassword = $aesCrypt->encrypt($password);
+ $rememberMe->username = $username;
+ $rememberMe->aesCrypt = $aesCrypt;
+
+ return $rememberMe;
+ }
+
+ /**
+ * Remove expired remember me information from the database
+ */
+ public static function removeExpired()
+ {
+ $rememberMe = new static();
+ if (! $rememberMe->hasDb()) {
+ return;
+ }
+
+ $rememberMe->getDb()->delete(static::TABLE, [
+ 'expires_at < NOW()'
+ ]);
+ }
+
+ /**
+ * Get the remember me cookie
+ *
+ * @return Cookie
+ */
+ public function getCookie()
+ {
+ $values = [
+ $this->encryptedPassword,
+ base64_encode($this->aesCrypt->getIV()),
+ ];
+
+ if ($this->aesCrypt->isAuthenticatedEncryptionRequired()) {
+ array_splice($values, 1, 0, base64_encode($this->aesCrypt->getTag()));
+ }
+
+ return (new Cookie(static::COOKIE))
+ ->setExpire($this->getExpiresAt())
+ ->setHttpOnly(true)
+ ->setValue(implode('|', $values));
+ }
+
+ /**
+ * Get the timestamp when the cookie expires
+ *
+ * Defaults to now plus 30 days, if not set via {@link setExpiresAt()}.
+ *
+ * @return int
+ */
+ public function getExpiresAt()
+ {
+ if ($this->expiresAt === null) {
+ $this->expiresAt = time() + 60 * 60 * 24 * 30;
+ }
+
+ return $this->expiresAt;
+ }
+
+ /**
+ * Set the timestamp when the cookie expires
+ *
+ * @param int $expiresAt
+ *
+ * @return $this
+ */
+ public function setExpiresAt($expiresAt)
+ {
+ $this->expiresAt = $expiresAt;
+
+ return $this;
+ }
+
+ /**
+ * Authenticate via the remember me cookie
+ *
+ * @return bool
+ *
+ * @throws \Icinga\Exception\AuthenticationException
+ */
+ public function authenticate()
+ {
+ $auth = Auth::getInstance();
+ $authChain = $auth->getAuthChain();
+ $authChain->setSkipExternalBackends(true);
+ $user = new User($this->username);
+ if (! $user->hasDomain()) {
+ $user->setDomain(Config::app()->get('authentication', 'default_domain'));
+ }
+
+ $authenticated = $authChain->authenticate(
+ $user,
+ $this->aesCrypt->decrypt($this->encryptedPassword)
+ );
+
+ if ($authenticated) {
+ $auth->setAuthenticated($user);
+ }
+
+ return $authenticated;
+ }
+
+ /**
+ * Persist the remember me information into the database
+ *
+ * To remove any previous stored information, set the iv
+ *
+ * @param string|null $iv To remove a specific iv record from the database
+ *
+ * @return $this
+ */
+ public function persist($iv = null)
+ {
+ if ($iv) {
+ $this->remove(bin2hex($iv));
+ }
+
+ $this->getDb()->insert(static::TABLE, [
+ 'username' => $this->username,
+ 'passphrase' => bin2hex($this->aesCrypt->getKey()),
+ 'random_iv' => bin2hex($this->aesCrypt->getIV()),
+ 'http_user_agent' => (new UserAgent)->getAgent(),
+ 'expires_at' => date('Y-m-d H:i:s', $this->getExpiresAt()),
+ 'ctime' => new Expression('NOW()'),
+ 'mtime' => new Expression('NOW()')
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * Remove remember me information from the database on the basis of iv
+ *
+ * @param string $iv
+ *
+ * @return $this
+ */
+ public function remove($iv)
+ {
+ $this->getDb()->delete(static::TABLE, [
+ 'random_iv = ?' => $iv
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * Create renewed remember me cookie
+ *
+ * @return static New remember me cookie which has to be sent to the client
+ */
+ public function renew()
+ {
+ return static::fromCredentials(
+ $this->username,
+ $this->aesCrypt->decrypt($this->encryptedPassword)
+ );
+ }
+
+ /**
+ * Get all users using remember me cookie
+ *
+ * @return array Array of users
+ */
+ public static function getAllUser()
+ {
+ $rememberMe = new static();
+ if (! $rememberMe->hasDb()) {
+ return [];
+ }
+
+ $select = (new Select())
+ ->from(static::TABLE)
+ ->columns('username')
+ ->groupBy('username');
+
+ return $rememberMe->getDb()->select($select)->fetchAll();
+ }
+
+ /**
+ * Get all remember me entries from the database of the given user.
+ *
+ * @param $username
+ *
+ * @return array Array of database entries
+ */
+ public static function getAllByUsername($username)
+ {
+ $rememberMe = new static();
+ if (! $rememberMe->hasDb()) {
+ return [];
+ }
+
+ $select = (new Select())
+ ->from(static::TABLE)
+ ->columns(['http_user_agent', 'random_iv'])
+ ->where(['username = ?' => $username]);
+
+ return $rememberMe->getDb()->select($select)->fetchAll();
+ }
+
+ /**
+ * Get the AesCrypt instance
+ *
+ * @return AesCrypt
+ */
+ public function getAesCrypt()
+ {
+ return $this->aesCrypt;
+ }
+}
diff --git a/library/Icinga/Web/RememberMeUserDevicesList.php b/library/Icinga/Web/RememberMeUserDevicesList.php
new file mode 100644
index 0000000..66609de
--- /dev/null
+++ b/library/Icinga/Web/RememberMeUserDevicesList.php
@@ -0,0 +1,144 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Web\Url as iplWebUrl; //alias is needed for php5.6
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\Link;
+
+class RememberMeUserDevicesList extends BaseHtmlElement
+{
+ protected $tag = 'table';
+
+ protected $defaultAttributes = [
+ 'class' => 'common-table',
+ 'data-base-target' => '_self'
+ ];
+
+ /**
+ * @var array
+ */
+ protected $devicesList;
+
+ /**
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * @param string $url
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * @param string $username
+ *
+ * @return $this
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ /**
+ * @return array List of devices. Each device contains user agent and fingerprint string
+ */
+ public function getDevicesList()
+ {
+ return $this->devicesList;
+ }
+
+ /**
+ * @param $devicesList
+ *
+ * @return $this
+ */
+ public function setDevicesList($devicesList)
+ {
+ $this->devicesList = $devicesList;
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ $thead = Html::tag('thead');
+ $theadRow = Html::tag('tr')
+ ->add(Html::tag(
+ 'th',
+ sprintf(t('List of devices and browsers %s is currently logged in:'), $this->getUsername())
+ ));
+
+ $thead->add($theadRow);
+
+ $head = Html::tag('tr')
+ ->add(Html::tag('th', t('OS')))
+ ->add(Html::tag('th', t('Browser')))
+ ->add(Html::tag('th', t('Fingerprint')));
+
+ $thead->add($head);
+ $tbody = Html::tag('tbody');
+
+ if (empty($this->getDevicesList())) {
+ $tbody->add(Html::tag('td', t('No device found')));
+ } else {
+ foreach ($this->getDevicesList() as $device) {
+ $agent = new UserAgent($device);
+ $element = Html::tag('tr')
+ ->add(Html::tag('td', $agent->getOs()))
+ ->add(Html::tag('td', $agent->getBrowser()))
+ ->add(Html::tag('td', $device->random_iv));
+
+ $link = (new Link(
+ new Icon('trash'),
+ iplWebUrl::fromPath($this->getUrl())
+ ->addParams(
+ [
+ 'name' => $this->getUsername(),
+ 'fingerprint' => $device->random_iv,
+ ]
+ )
+ ));
+
+ $element->add(Html::tag('td', $link));
+ $tbody->add($element);
+ }
+ }
+
+ $this->add($thead);
+ $this->add($tbody);
+ }
+}
diff --git a/library/Icinga/Web/RememberMeUserList.php b/library/Icinga/Web/RememberMeUserList.php
new file mode 100644
index 0000000..bb95dc9
--- /dev/null
+++ b/library/Icinga/Web/RememberMeUserList.php
@@ -0,0 +1,106 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web;
+
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Web\Url as iplWebUrl; //alias is needed for php5.6
+use ipl\Web\Widget\Link;
+
+/**
+ * Class RememberMeUserList
+ *
+ * @package Icinga\Web
+ */
+class RememberMeUserList extends BaseHtmlElement
+{
+ protected $tag = 'table';
+
+ protected $defaultAttributes = [
+ 'class' => 'common-table table-row-selectable',
+ 'data-base-target' => '_next',
+ ];
+
+ /**
+ * @var array
+ */
+ protected $users;
+
+ /**
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * @param string $url
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getUsers()
+ {
+ return $this->users;
+ }
+
+ /**
+ * @param array $users
+ *
+ * @return $this
+ */
+ public function setUsers($users)
+ {
+ $this->users = $users;
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ $thead = Html::tag('thead');
+ $theadRow = Html::tag('tr')
+ ->add(Html::tag(
+ 'th',
+ t('List of users who stay logged in')
+ ));
+
+ $thead->add($theadRow);
+ $tbody = Html::tag('tbody');
+
+ if (empty($this->getUsers())) {
+ $tbody->add(Html::tag('td', t('No user found')));
+ } else {
+ foreach ($this->getUsers() as $user) {
+ $element = Html::tag('tr');
+ $link = new Link(
+ $user->username,
+ iplWebUrl::fromPath($this->getUrl())->addParams(['name' => $user->username]),
+ ['title' => sprintf(t('Device list of %s'), $user->username)]
+ );
+
+ $element->add(Html::tag('td', $link));
+ $tbody->add($element);
+ }
+ }
+
+ $this->add($thead);
+ $this->add($tbody);
+ }
+}
diff --git a/library/Icinga/Web/Request.php b/library/Icinga/Web/Request.php
new file mode 100644
index 0000000..064ce63
--- /dev/null
+++ b/library/Icinga/Web/Request.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Util\Json;
+use Zend_Controller_Request_Http;
+use Icinga\Application\Icinga;
+use Icinga\User;
+
+/**
+ * A request
+ */
+class Request extends Zend_Controller_Request_Http
+{
+ /**
+ * Response
+ *
+ * @var Response
+ */
+ protected $response;
+
+ /**
+ * Unique identifier
+ *
+ * @var string
+ */
+ protected $uniqueId;
+
+ /**
+ * Request URL
+ *
+ * @var Url
+ */
+ protected $url;
+
+ /**
+ * User if authenticated
+ *
+ * @var User|null
+ */
+ protected $user;
+
+ /**
+ * Get the response
+ *
+ * @return Response
+ */
+ public function getResponse()
+ {
+ if ($this->response === null) {
+ $this->response = Icinga::app()->getResponse();
+ }
+
+ return $this->response;
+ }
+
+ /**
+ * Get the request URL
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ if ($this->url === null) {
+ $this->url = Url::fromRequest($this);
+ }
+ return $this->url;
+ }
+
+ /**
+ * Get the user if authenticated
+ *
+ * @return User|null
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Set the authenticated user
+ *
+ * @param User $user
+ *
+ * @return $this
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ return $this;
+ }
+
+ /**
+ * Get whether the request seems to be an API request
+ *
+ * @return bool
+ */
+ public function isApiRequest()
+ {
+ return $this->getHeader('Accept') === 'application/json';
+ }
+
+ /**
+ * Makes an ID unique to this request, to prevent id collisions in different containers
+ *
+ * Call this whenever an ID might show up multiple times in different containers. This function is useful
+ * for ensuring unique ids on sites, even if we combine the HTML of different requests into one site,
+ * while still being able to reference elements uniquely in the same request.
+ *
+ * @param string $id
+ *
+ * @return string The id suffixed w/ an identifier unique to this request
+ */
+ public function protectId($id)
+ {
+ return $id . '-' . Window::getInstance()->getContainerId();
+ }
+
+ public function getPost($key = null, $default = null)
+ {
+ if ($key === null && $this->extractMediaType($this->getHeader('Content-Type')) === 'application/json') {
+ return Json::decode(file_get_contents('php://input'), true);
+ }
+
+ return parent::getPost($key, $default);
+ }
+
+ /**
+ * Extract and return the media type from the given header value
+ *
+ * @param string $headerValue
+ *
+ * @return string
+ */
+ protected function extractMediaType($headerValue)
+ {
+ // Pretty basic and does not care about parameters
+ $parts = explode(';', $headerValue, 2);
+ return strtolower(trim($parts[0]));
+ }
+}
diff --git a/library/Icinga/Web/Response.php b/library/Icinga/Web/Response.php
new file mode 100644
index 0000000..df0b842
--- /dev/null
+++ b/library/Icinga/Web/Response.php
@@ -0,0 +1,429 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Zend_Controller_Response_Http;
+use Icinga\Application\Icinga;
+use Icinga\Web\Response\JsonResponse;
+
+/**
+ * A HTTP response
+ */
+class Response extends Zend_Controller_Response_Http
+{
+ /**
+ * The default content type being used for responses
+ *
+ * @var string
+ */
+ const DEFAULT_CONTENT_TYPE = 'text/html; charset=UTF-8';
+
+ /**
+ * Auto-refresh interval
+ *
+ * @var int
+ */
+ protected $autoRefreshInterval;
+
+ /**
+ * Set of cookies which are to be sent to the client
+ *
+ * @var CookieSet
+ */
+ protected $cookies;
+
+ /**
+ * Redirect URL
+ *
+ * @var Url|null
+ */
+ protected $redirectUrl;
+
+ /**
+ * Request
+ *
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * Whether to instruct the client to reload the window
+ *
+ * @var bool
+ */
+ protected $reloadWindow;
+
+ /**
+ * Whether to instruct client side script code to reload CSS
+ *
+ * @var bool
+ */
+ protected $reloadCss;
+
+ /**
+ * Whether to send the rerender layout header on XHR
+ *
+ * @var bool
+ */
+ protected $rerenderLayout = false;
+
+ /**
+ * Whether to send the current window ID to the client
+ *
+ * @var bool
+ */
+ protected $overrideWindowId = false;
+
+ /**
+ * Get the auto-refresh interval
+ *
+ * @return int
+ */
+ public function getAutoRefreshInterval()
+ {
+ return $this->autoRefreshInterval;
+ }
+
+ /**
+ * Set the auto-refresh interval
+ *
+ * @param int $autoRefreshInterval
+ *
+ * @return $this
+ */
+ public function setAutoRefreshInterval($autoRefreshInterval)
+ {
+ $this->autoRefreshInterval = $autoRefreshInterval;
+ return $this;
+ }
+
+ /**
+ * Get the set of cookies which are to be sent to the client
+ *
+ * @return CookieSet
+ */
+ public function getCookies()
+ {
+ if ($this->cookies === null) {
+ $this->cookies = new CookieSet();
+ }
+ return $this->cookies;
+ }
+
+ /**
+ * Get the cookie with the given name from the set of cookies which are to be sent to the client
+ *
+ * @param string $name The name of the cookie
+ *
+ * @return Cookie|null The cookie with the given name or null if the cookie does not exist
+ */
+ public function getCookie($name)
+ {
+ return $this->getCookies()->get($name);
+ }
+
+ /**
+ * Set the given cookie for sending it to the client
+ *
+ * @param Cookie $cookie The cookie to send to the client
+ *
+ * @return $this
+ */
+ public function setCookie(Cookie $cookie)
+ {
+ $this->getCookies()->add($cookie);
+ return $this;
+ }
+
+ /**
+ * Get the redirect URL
+ *
+ * @return Url|null
+ */
+ protected function getRedirectUrl()
+ {
+ return $this->redirectUrl;
+ }
+
+ /**
+ * Set the redirect URL
+ *
+ * Unlike {@link setRedirect()} this method only sets a redirect URL on the response for later usage.
+ * {@link prepare()} will take care of the correct redirect handling and HTTP headers on XHR and "normal" browser
+ * requests.
+ *
+ * @param string|Url $redirectUrl
+ *
+ * @return $this
+ */
+ protected function setRedirectUrl($redirectUrl)
+ {
+ if (! $redirectUrl instanceof Url) {
+ $redirectUrl = Url::fromPath((string) $redirectUrl);
+ }
+ $redirectUrl->getParams()->setSeparator('&');
+ $this->redirectUrl = $redirectUrl;
+ return $this;
+ }
+
+ /**
+ * Get an array of all header values for the given name
+ *
+ * @param string $name The name of the header
+ * @param bool $lastOnly If this is true, the last value will be returned as a string
+ *
+ * @return null|array|string
+ */
+ public function getHeader($name, $lastOnly = false)
+ {
+ $result = ($lastOnly ? null : array());
+ $headers = $this->getHeaders();
+ foreach ($headers as $header) {
+ if ($header['name'] === $name) {
+ if ($lastOnly) {
+ $result = $header['value'];
+ } else {
+ $result[] = $header['value'];
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the request
+ *
+ * @return Request
+ */
+ public function getRequest()
+ {
+ if ($this->request === null) {
+ $this->request = Icinga::app()->getRequest();
+ }
+ return $this->request;
+ }
+
+ /**
+ * Get whether to instruct the client to reload the window
+ *
+ * @return bool
+ */
+ public function isWindowReloaded()
+ {
+ return $this->reloadWindow;
+ }
+
+ /**
+ * Set whether to instruct the client to reload the window
+ *
+ * @param bool $reloadWindow
+ *
+ * @return $this
+ */
+ public function setReloadWindow($reloadWindow)
+ {
+ $this->reloadWindow = $reloadWindow;
+
+ return $this;
+ }
+
+ /**
+ * Get whether to instruct client side script code to reload CSS
+ *
+ * @return bool
+ */
+ public function isReloadCss()
+ {
+ return $this->reloadCss;
+ }
+
+ /**
+ * Set whether to instruct client side script code to reload CSS
+ *
+ * @param bool $reloadCss
+ *
+ * @return $this
+ */
+ public function setReloadCss($reloadCss)
+ {
+ $this->reloadCss = $reloadCss;
+ return $this;
+ }
+
+ /**
+ * Get whether to send the rerender layout header on XHR
+ *
+ * @return bool
+ */
+ public function getRerenderLayout()
+ {
+ return $this->rerenderLayout;
+ }
+
+ /**
+ * Get whether to send the rerender layout header on XHR
+ *
+ * @param bool $rerenderLayout
+ *
+ * @return $this
+ */
+ public function setRerenderLayout($rerenderLayout = true)
+ {
+ $this->rerenderLayout = (bool) $rerenderLayout;
+ return $this;
+ }
+
+ /**
+ * Get whether to send the current window ID to the client
+ *
+ * @return bool
+ */
+ public function getOverrideWindowId()
+ {
+ return $this->overrideWindowId;
+ }
+
+ /**
+ * Set whether to send the current window ID to the client
+ *
+ * @param bool $overrideWindowId
+ *
+ * @return $this
+ */
+ public function setOverrideWindowId($overrideWindowId = true)
+ {
+ $this->overrideWindowId = $overrideWindowId;
+ return $this;
+ }
+
+ /**
+ * Entry point for HTTP responses in JSON format
+ *
+ * @return JsonResponse
+ */
+ public function json()
+ {
+ $response = new JsonResponse();
+ $response->copyMetaDataFrom($this);
+ return $response;
+ }
+
+ /**
+ * Prepare the request before sending
+ */
+ protected function prepare()
+ {
+ $redirectUrl = $this->getRedirectUrl();
+ if ($this->getRequest()->isXmlHttpRequest()) {
+ if ($redirectUrl !== null) {
+ if ($this->getRequest()->isGet() && Icinga::app()->getViewRenderer()->view->compact) {
+ $redirectUrl->getParams()->set('showCompact', true);
+ }
+
+ $this->setHeader('X-Icinga-Redirect', rawurlencode($redirectUrl->getAbsoluteUrl()), true);
+ if ($this->getRerenderLayout()) {
+ $this->setHeader('X-Icinga-Rerender-Layout', 'yes', true);
+ }
+ }
+ if ($this->getOverrideWindowId()) {
+ $this->setHeader('X-Icinga-WindowId', Window::getInstance()->getId(), true);
+ }
+ if ($this->getRerenderLayout()) {
+ $this->setHeader('X-Icinga-Container', 'layout', true);
+ }
+ if ($this->isWindowReloaded()) {
+ $this->setHeader('X-Icinga-Reload-Window', 'yes', true);
+ }
+ if ($this->isReloadCss()) {
+ $this->setHeader('X-Icinga-Reload-Css', 'now', true);
+ }
+ if (($autoRefreshInterval = $this->getAutoRefreshInterval()) !== null) {
+ $this->setHeader('X-Icinga-Refresh', $autoRefreshInterval, true);
+ }
+
+ $notifications = Notification::getInstance();
+ if ($notifications->hasMessages()) {
+ $notificationList = array();
+ foreach ($notifications->popMessages() as $m) {
+ $notificationList[] = rawurlencode($m->type . ' ' . $m->message);
+ }
+ $this->setHeader('X-Icinga-Notification', implode('&', $notificationList), true);
+ }
+ } else {
+ if ($redirectUrl !== null) {
+ $this->setRedirect($redirectUrl->getAbsoluteUrl());
+ }
+ }
+
+ if (! $this->getHeader('Content-Type', true)) {
+ $this->setHeader('Content-Type', static::DEFAULT_CONTENT_TYPE);
+ }
+ }
+
+ /**
+ * Redirect to the given URL and exit immediately
+ *
+ * @param string|Url $url
+ */
+ public function redirectAndExit($url)
+ {
+ $this->setRedirectUrl($url);
+
+ $session = Session::getSession();
+ if ($session->hasChanged()) {
+ $session->write();
+ }
+
+ $this->sendHeaders();
+ exit;
+ }
+
+ /**
+ * Send the cookies to the client
+ */
+ public function sendCookies()
+ {
+ foreach ($this->getCookies() as $cookie) {
+ /** @var Cookie $cookie */
+ setcookie(
+ $cookie->getName(),
+ $cookie->getValue() ?? '',
+ $cookie->getExpire() ?? 0,
+ $cookie->getPath(),
+ $cookie->getDomain() ?? '',
+ $cookie->isSecure(),
+ $cookie->isHttpOnly() ?? true
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function sendHeaders()
+ {
+ $this->prepare();
+ if (! $this->getRequest()->isApiRequest()) {
+ $this->sendCookies();
+ }
+ return parent::sendHeaders();
+ }
+
+ /**
+ * Copies non-body-related response data from $response
+ *
+ * @param Response $response
+ *
+ * @return $this
+ */
+ protected function copyMetaDataFrom(self $response)
+ {
+ $this->_headers = $response->_headers;
+ $this->_headersRaw = $response->_headersRaw;
+ $this->_httpResponseCode = $response->_httpResponseCode;
+ $this->headersSentThrowsException = $response->headersSentThrowsException;
+ return $this;
+ }
+}
diff --git a/library/Icinga/Web/Response/JsonResponse.php b/library/Icinga/Web/Response/JsonResponse.php
new file mode 100644
index 0000000..f914f2c
--- /dev/null
+++ b/library/Icinga/Web/Response/JsonResponse.php
@@ -0,0 +1,239 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Response;
+
+use Icinga\Util\Json;
+use Zend_Controller_Action_HelperBroker;
+use Icinga\Web\Response;
+
+/**
+ * HTTP response in JSON format
+ */
+class JsonResponse extends Response
+{
+ /**
+ * {@inheritdoc}
+ */
+ const DEFAULT_CONTENT_TYPE = 'application/json';
+
+ /**
+ * Status identifier for failed API calls due to an error on the server
+ *
+ * @var string
+ */
+ const STATUS_ERROR = 'error';
+
+ /**
+ * Status identifier for rejected API calls most due to invalid data or call conditions
+ *
+ * @var string
+ */
+ const STATUS_FAIL = 'fail';
+
+ /**
+ * Status identifier for successful API requests
+ *
+ * @var string
+ */
+ const STATUS_SUCCESS = 'success';
+
+ /**
+ * JSON encoding options
+ *
+ * @var int
+ */
+ protected $encodingOptions = 0;
+
+ /**
+ * Whether to automatically sanitize invalid UTF-8 (if any)
+ *
+ * @var bool
+ */
+ protected $autoSanitize = false;
+
+ /**
+ * Error message if the API call failed due to a server error
+ *
+ * @var string|null
+ */
+ protected $errorMessage;
+
+ /**
+ * Fail data for rejected API calls
+ *
+ * @var array|null
+ */
+ protected $failData;
+
+ /**
+ * API request status
+ *
+ * @var string
+ */
+ protected $status;
+
+ /**
+ * Success data for successful API requests
+ *
+ * @var array|null
+ */
+ protected $successData;
+
+ /**
+ * Get the JSON encoding options
+ *
+ * @return int
+ */
+ public function getEncodingOptions()
+ {
+ return $this->encodingOptions;
+ }
+
+ /**
+ * Set the JSON encoding options
+ *
+ * @param int $encodingOptions
+ *
+ * @return $this
+ */
+ public function setEncodingOptions($encodingOptions)
+ {
+ $this->encodingOptions = (int) $encodingOptions;
+ return $this;
+ }
+
+ /**
+ * Get whether to automatically sanitize invalid UTF-8 (if any)
+ *
+ * @return bool
+ */
+ public function getAutoSanitize()
+ {
+ return $this->autoSanitize;
+ }
+
+ /**
+ * Set whether to automatically sanitize invalid UTF-8 (if any)
+ *
+ * @param bool $autoSanitize
+ *
+ * @return $this
+ */
+ public function setAutoSanitize($autoSanitize = true)
+ {
+ $this->autoSanitize = $autoSanitize;
+
+ return $this;
+ }
+
+ /**
+ * Get the error message if the API call failed due to a server error
+ *
+ * @return string|null
+ */
+ public function getErrorMessage()
+ {
+ return $this->errorMessage;
+ }
+
+ /**
+ * Set the error message if the API call failed due to a server error
+ *
+ * @param string $errorMessage
+ *
+ * @return $this
+ */
+ public function setErrorMessage($errorMessage)
+ {
+ $this->errorMessage = (string) $errorMessage;
+ $this->status = static::STATUS_ERROR;
+ return $this;
+ }
+
+ /**
+ * Get the fail data for rejected API calls
+ *
+ * @return array|null
+ */
+ public function getFailData()
+ {
+ return (! is_array($this->failData) || empty($this->failData)) ? null : $this->failData;
+ }
+
+ /**
+ * Set the fail data for rejected API calls
+ *
+ * @param array $failData
+ *
+ * @return $this
+ */
+ public function setFailData(array $failData)
+ {
+ $this->failData = $failData;
+ $this->status = static::STATUS_FAIL;
+ return $this;
+ }
+
+ /**
+ * Get the data for successful API requests
+ *
+ * @return array|null
+ */
+ public function getSuccessData()
+ {
+ return (! is_array($this->successData) || empty($this->successData)) ? null : $this->successData;
+ }
+
+ /**
+ * Set the data for successful API requests
+ *
+ * @param array $successData
+ *
+ * @return $this
+ */
+ public function setSuccessData(array $successData = null)
+ {
+ $this->successData = $successData;
+ $this->status = static::STATUS_SUCCESS;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function outputBody()
+ {
+ $body = array(
+ 'status' => $this->status
+ );
+ switch ($this->status) {
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case static::STATUS_ERROR:
+ $body['message'] = $this->getErrorMessage();
+ // Fallthrough
+ case static::STATUS_FAIL:
+ $failData = $this->getFailData();
+ if ($failData !== null || $this->status === static::STATUS_FAIL) {
+ $body['data'] = $failData;
+ }
+ break;
+ case static::STATUS_SUCCESS:
+ $body['data'] = $this->getSuccessData();
+ break;
+ }
+ echo $this->getAutoSanitize()
+ ? Json::sanitize($body, $this->getEncodingOptions())
+ : Json::encode($body, $this->getEncodingOptions());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function sendResponse()
+ {
+ Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true);
+ parent::sendResponse();
+ exit;
+ }
+}
diff --git a/library/Icinga/Web/Session.php b/library/Icinga/Web/Session.php
new file mode 100644
index 0000000..40df89f
--- /dev/null
+++ b/library/Icinga/Web/Session.php
@@ -0,0 +1,54 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Web\Session\PhpSession;
+use Icinga\Web\Session\Session as BaseSession;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Session container
+ */
+class Session
+{
+ /**
+ * The current session
+ *
+ * @var BaseSession $session
+ */
+ private static $session;
+
+ /**
+ * Create the session
+ *
+ * @param BaseSession $session
+ *
+ * @return BaseSession
+ */
+ public static function create(BaseSession $session = null)
+ {
+ if ($session === null) {
+ self::$session = PhpSession::create();
+ } else {
+ self::$session = $session;
+ }
+
+ return self::$session;
+ }
+
+ /**
+ * Return the current session
+ *
+ * @return BaseSession
+ * @throws ProgrammingError
+ */
+ public static function getSession()
+ {
+ if (self::$session === null) {
+ self::create();
+ }
+
+ return self::$session;
+ }
+}
diff --git a/library/Icinga/Web/Session/Php72Session.php b/library/Icinga/Web/Session/Php72Session.php
new file mode 100644
index 0000000..e6a6b19
--- /dev/null
+++ b/library/Icinga/Web/Session/Php72Session.php
@@ -0,0 +1,37 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Cookie;
+
+/**
+ * Session implementation in PHP
+ */
+class Php72Session extends PhpSession
+{
+ /**
+ * Open a PHP session
+ */
+ protected function open()
+ {
+ session_name($this->sessionName);
+
+ $cookie = new Cookie('bogus');
+ session_set_cookie_params(
+ 0,
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $cookie->isSecure(),
+ true
+ );
+
+ session_start(array(
+ 'use_cookies' => true,
+ 'use_only_cookies' => true,
+ 'use_trans_sid' => false
+ ));
+ }
+}
diff --git a/library/Icinga/Web/Session/PhpSession.php b/library/Icinga/Web/Session/PhpSession.php
new file mode 100644
index 0000000..36dd84e
--- /dev/null
+++ b/library/Icinga/Web/Session/PhpSession.php
@@ -0,0 +1,256 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Cookie;
+
+/**
+ * Session implementation in PHP
+ */
+class PhpSession extends Session
+{
+ /**
+ * The namespace prefix
+ *
+ * Used to differentiate between standard session keys and namespace identifiers
+ */
+ const NAMESPACE_PREFIX = 'ns.';
+
+ /**
+ * Whether the session has already been closed
+ *
+ * @var bool
+ */
+ protected $hasBeenTouched = false;
+
+ /**
+ * Name of the session
+ *
+ * @var string
+ */
+ protected $sessionName = 'Icingaweb2';
+
+ /**
+ * Create a new PHPSession object using the provided options (if any)
+ *
+ * @param array $options An optional array of ini options to set
+ *
+ * @return static
+ *
+ * @throws ConfigurationError
+ * @see http://php.net/manual/en/session.configuration.php
+ */
+ public static function create(array $options = null)
+ {
+ return version_compare(PHP_VERSION, '7.2.0') < 0 ? new self($options) : new Php72Session($options);
+ }
+
+ /**
+ * Create a new PHPSession object using the provided options (if any)
+ *
+ * @param array $options An optional array of ini options to set
+ *
+ * @throws ConfigurationError
+ * @see http://php.net/manual/en/session.configuration.php
+ */
+ public function __construct(array $options = null)
+ {
+ $defaultCookieOptions = array(
+ 'use_trans_sid' => false,
+ 'use_cookies' => true,
+ 'cookie_httponly' => true,
+ 'use_only_cookies' => true
+ );
+
+ if (version_compare(PHP_VERSION, '7.1.0') < 0) {
+ $defaultCookieOptions['hash_function'] = true;
+ $defaultCookieOptions['hash_bits_per_character'] = 5;
+ } else {
+ $defaultCookieOptions['sid_bits_per_character'] = 5;
+ }
+
+ if ($options !== null) {
+ $options = array_merge($defaultCookieOptions, $options);
+ } else {
+ $options = $defaultCookieOptions;
+ }
+
+ if (array_key_exists('test_session_name', $options)) {
+ $this->sessionName = $options['test_session_name'];
+ unset($options['test_session_name']);
+ }
+
+ foreach ($options as $sessionVar => $value) {
+ if (ini_set("session." . $sessionVar, $value) === false) {
+ Logger::warning(
+ 'Could not set php.ini setting %s = %s. This might affect your sessions behaviour.',
+ $sessionVar,
+ $value
+ );
+ }
+ }
+
+ $sessionSavePath = session_save_path() ?: sys_get_temp_dir();
+ if (session_module_name() === 'files' && !is_writable($sessionSavePath)) {
+ throw new ConfigurationError("Can't save session, path '$sessionSavePath' is not writable.");
+ }
+
+ if ($this->exists()) {
+ // We do not want to start a new session here if there is not any
+ $this->read();
+ }
+ }
+
+ /**
+ * Open a PHP session
+ */
+ protected function open()
+ {
+ session_name($this->sessionName);
+
+ if ($this->hasBeenTouched) {
+ $cacheLimiter = ini_get('session.cache_limiter');
+ ini_set('session.use_cookies', false);
+ ini_set('session.use_only_cookies', false);
+ ini_set('session.cache_limiter', null);
+ }
+
+ $cookie = new Cookie('bogus');
+ session_set_cookie_params(
+ 0,
+ $cookie->getPath(),
+ $cookie->getDomain(),
+ $cookie->isSecure(),
+ true
+ );
+
+ session_start();
+
+ if ($this->hasBeenTouched) {
+ ini_set('session.use_cookies', true);
+ ini_set('session.use_only_cookies', true);
+ /** @noinspection PhpUndefinedVariableInspection */
+ ini_set('session.cache_limiter', $cacheLimiter);
+ }
+ }
+
+ /**
+ * Read all values written to the underling session and make them accessible.
+ */
+ public function read()
+ {
+ $this->clear();
+ $this->open();
+
+ foreach ($_SESSION as $key => $value) {
+ if (strpos($key, self::NAMESPACE_PREFIX) === 0) {
+ $namespace = new SessionNamespace();
+ $namespace->setAll($value);
+ $this->namespaces[substr($key, strlen(self::NAMESPACE_PREFIX))] = $namespace;
+ } else {
+ $this->set($key, $value);
+ }
+ }
+
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * Write all values of this session object to the underlying session implementation
+ */
+ public function write()
+ {
+ $this->open();
+
+ foreach ($this->removed as $key) {
+ unset($_SESSION[$key]);
+ }
+ foreach ($this->values as $key => $value) {
+ $_SESSION[$key] = $value;
+ }
+ foreach ($this->removedNamespaces as $identifier) {
+ unset($_SESSION[self::NAMESPACE_PREFIX . $identifier]);
+ }
+ foreach ($this->namespaces as $identifier => $namespace) {
+ $_SESSION[self::NAMESPACE_PREFIX . $identifier] = $namespace->getAll();
+ }
+
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * Delete the current session, causing all session information to be lost
+ */
+ public function purge()
+ {
+ $this->open();
+ $_SESSION = array();
+ $this->clear();
+ session_destroy();
+ $this->clearCookies();
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * Remove session cookies
+ */
+ protected function clearCookies()
+ {
+ if (ini_get('session.use_cookies')) {
+ Logger::debug('Clear session cookie');
+ $params = session_get_cookie_params();
+ setcookie(
+ session_name(),
+ '',
+ time() - 42000,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
+ }
+ }
+
+ /**
+ * @see Session::getId()
+ */
+ public function getId()
+ {
+ if (($id = session_id()) === '') {
+ // Make sure we actually get a id
+ $this->open();
+ session_write_close();
+ $this->hasBeenTouched = true;
+ $id = session_id();
+ }
+
+ return $id;
+ }
+
+ /**
+ * Assign a new sessionId to the currently active session
+ */
+ public function refreshId()
+ {
+ $this->open();
+ if ($this->exists()) {
+ session_regenerate_id();
+ }
+ session_write_close();
+ $this->hasBeenTouched = true;
+ }
+
+ /**
+ * @see Session::exists()
+ */
+ public function exists()
+ {
+ return isset($_COOKIE[$this->sessionName]);
+ }
+}
diff --git a/library/Icinga/Web/Session/Session.php b/library/Icinga/Web/Session/Session.php
new file mode 100644
index 0000000..e73e9b4
--- /dev/null
+++ b/library/Icinga/Web/Session/Session.php
@@ -0,0 +1,126 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Icinga\Exception\NotImplementedError;
+
+/**
+ * Base class for handling sessions
+ */
+abstract class Session extends SessionNamespace
+{
+ /**
+ * Container for session namespaces
+ *
+ * @var array
+ */
+ protected $namespaces = array();
+
+ /**
+ * The identifiers of all namespaces removed from this session
+ *
+ * @var array
+ */
+ protected $removedNamespaces = array();
+
+ /**
+ * Read all values from the underlying session implementation
+ */
+ abstract public function read();
+
+ /**
+ * Persists changes to the underlying session implementation
+ */
+ public function write()
+ {
+ throw new NotImplementedError('You are required to implement write() in your session implementation');
+ }
+
+ /**
+ * Return whether a session exists
+ *
+ * @return bool
+ */
+ abstract public function exists();
+
+ /**
+ * Purge session
+ */
+ abstract public function purge();
+
+ /**
+ * Assign a new session id to this session.
+ */
+ abstract public function refreshId();
+
+ /**
+ * Return the id of this session
+ *
+ * @return string
+ */
+ abstract public function getId();
+
+ /**
+ * Get or create a new session namespace
+ *
+ * @param string $identifier The namespace's identifier
+ *
+ * @return SessionNamespace
+ */
+ public function getNamespace($identifier)
+ {
+ if (!isset($this->namespaces[$identifier])) {
+ if (in_array($identifier, $this->removedNamespaces, true)) {
+ unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces, true)]);
+ }
+
+ $this->namespaces[$identifier] = new SessionNamespace();
+ }
+
+ return $this->namespaces[$identifier];
+ }
+
+ /**
+ * Return whether the given session namespace exists
+ *
+ * @param string $identifier The namespace's identifier to check
+ *
+ * @return bool
+ */
+ public function hasNamespace($identifier)
+ {
+ return isset($this->namespaces[$identifier]);
+ }
+
+ /**
+ * Remove the given session namespace
+ *
+ * @param string $identifier The identifier of the namespace to remove
+ */
+ public function removeNamespace($identifier)
+ {
+ unset($this->namespaces[$identifier]);
+ $this->removedNamespaces[] = $identifier;
+ }
+
+ /**
+ * Return whether the session has changed
+ *
+ * @return bool
+ */
+ public function hasChanged()
+ {
+ return parent::hasChanged() || false === empty($this->namespaces) || false === empty($this->removedNamespaces);
+ }
+
+ /**
+ * Clear all values and namespaces from the session cache
+ */
+ public function clear()
+ {
+ parent::clear();
+ $this->namespaces = array();
+ $this->removedNamespaces = array();
+ }
+}
diff --git a/library/Icinga/Web/Session/SessionNamespace.php b/library/Icinga/Web/Session/SessionNamespace.php
new file mode 100644
index 0000000..1c9c13f
--- /dev/null
+++ b/library/Icinga/Web/Session/SessionNamespace.php
@@ -0,0 +1,201 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Session;
+
+use Exception;
+use ArrayIterator;
+use Icinga\Exception\IcingaException;
+use IteratorAggregate;
+use Traversable;
+
+/**
+ * Container for session values
+ */
+class SessionNamespace implements IteratorAggregate
+{
+ /**
+ * The actual values stored in this container
+ *
+ * @var array
+ */
+ protected $values = array();
+
+ /**
+ * The names of all values removed from this container
+ *
+ * @var array
+ */
+ protected $removed = array();
+
+ /**
+ * Return an iterator for all values in this namespace
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->getAll());
+ }
+
+ /**
+ * Set a session value by property access
+ *
+ * @param string $key The value's name
+ * @param mixed $value The value
+ */
+ public function __set($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Return a session value by property access
+ *
+ * @param string $key The value's name
+ *
+ * @return mixed The value
+ * @throws Exception When the given value-name is not found
+ */
+ public function __get($key)
+ {
+ if (!array_key_exists($key, $this->values)) {
+ throw new IcingaException(
+ 'Cannot access non-existent session value "%s"',
+ $key
+ );
+ }
+
+ return $this->get($key);
+ }
+
+ /**
+ * Return whether the given session value is set
+ *
+ * @param string $key The value's name
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return isset($this->values[$key]);
+ }
+
+ /**
+ * Unset the given session value
+ *
+ * @param string $key The value's name
+ */
+ public function __unset($key)
+ {
+ $this->delete($key);
+ }
+
+ /**
+ * Setter for session values
+ *
+ * @param string $key Name of value
+ * @param mixed $value Value to set
+ *
+ * @return $this
+ */
+ public function set($key, $value)
+ {
+ $this->values[$key] = $value;
+
+ if (in_array($key, $this->removed, true)) {
+ unset($this->removed[array_search($key, $this->removed, true)]);
+ }
+
+ return $this;
+ }
+
+ public function setByRef($key, &$value)
+ {
+ $this->values[$key] = & $value;
+
+ if (in_array($key, $this->removed, true)) {
+ unset($this->removed[array_search($key, $this->removed, true)]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Getter for session values
+ *
+ * @param string $key Name of the value to return
+ * @param mixed $default Default value to return
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return isset($this->values[$key]) ? $this->values[$key] : $default;
+ }
+
+ public function & getByRef($key, $default = null)
+ {
+ $value = $default;
+ if (isset($this->values[$key])) {
+ $value = & $this->values[$key];
+ }
+
+ return $value;
+ }
+
+ /**
+ * Delete the given value from the session
+ *
+ * @param string $key The value's name
+ */
+ public function delete($key)
+ {
+ $this->removed[] = $key;
+ unset($this->values[$key]);
+ }
+
+ /**
+ * Getter for all session values
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Put an array into the session
+ *
+ * @param array $values Values to set
+ * @param bool $overwrite Overwrite existing values
+ */
+ public function setAll(array $values, $overwrite = false)
+ {
+ foreach ($values as $key => $value) {
+ if ($this->get($key, $value) !== $value && !$overwrite) {
+ continue;
+ }
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * Return whether the session namespace has been changed
+ *
+ * @return bool
+ */
+ public function hasChanged()
+ {
+ return false === empty($this->values) || false === empty($this->removed);
+ }
+
+ /**
+ * Clear all values from the session namespace
+ */
+ public function clear()
+ {
+ $this->values = array();
+ $this->removed = array();
+ }
+}
diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php
new file mode 100644
index 0000000..9ca6d9a
--- /dev/null
+++ b/library/Icinga/Web/StyleSheet.php
@@ -0,0 +1,341 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Exception;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\IcingaException;
+
+/**
+ * Send CSS for Web 2 and all loaded modules to the client
+ */
+class StyleSheet
+{
+ /**
+ * The name of the default theme
+ *
+ * @var string
+ */
+ const DEFAULT_THEME = 'Icinga';
+
+ /**
+ * The name of the default theme mode
+ *
+ * @var string
+ */
+ const DEFAULT_MODE = 'none';
+
+ /**
+ * The themes that are compatible with the default theme
+ *
+ * @var array
+ */
+ const THEME_WHITELIST = [
+ 'colorblind',
+ 'high-contrast',
+ 'Winter'
+ ];
+
+ /**
+ * Sequence that signals that a theme supports light mode
+ *
+ * @var string
+ */
+ const LIGHT_MODE_IDENTIFIER = '@light-mode:';
+
+ /**
+ * Array of core LESS files Web 2 sends to the client
+ *
+ * @var string[]
+ */
+ protected static $lessFiles = [
+ '../application/fonts/fontello-ifont/css/ifont-embedded.css',
+ 'css/vendor/normalize.css',
+ 'css/icinga/base.less',
+ 'css/icinga/badges.less',
+ 'css/icinga/configmenu.less',
+ 'css/icinga/mixins.less',
+ 'css/icinga/grid.less',
+ 'css/icinga/nav.less',
+ 'css/icinga/main.less',
+ 'css/icinga/animation.less',
+ 'css/icinga/layout.less',
+ 'css/icinga/layout-structure.less',
+ 'css/icinga/menu.less',
+ 'css/icinga/tabs.less',
+ 'css/icinga/forms.less',
+ 'css/icinga/setup.less',
+ 'css/icinga/widgets.less',
+ 'css/icinga/login.less',
+ 'css/icinga/about.less',
+ 'css/icinga/controls.less',
+ 'css/icinga/dev.less',
+ 'css/icinga/spinner.less',
+ 'css/icinga/compat.less',
+ 'css/icinga/print.less',
+ 'css/icinga/responsive.less',
+ 'css/icinga/modal.less',
+ 'css/icinga/audit.less',
+ 'css/icinga/health.less',
+ 'css/icinga/php-diff.less'
+ ];
+
+ /**
+ * Application instance
+ *
+ * @var \Icinga\Application\EmbeddedWeb
+ */
+ protected $app;
+
+ /** @var string[] Pre-compiled CSS files */
+ protected $cssFiles = [];
+
+ /**
+ * Less compiler
+ *
+ * @var LessCompiler
+ */
+ protected $lessCompiler;
+
+ /**
+ * Path to the public directory
+ *
+ * @var string
+ */
+ protected $pubPath;
+
+ /**
+ * Create the StyleSheet
+ */
+ public function __construct()
+ {
+ $app = Icinga::app();
+ $this->app = $app;
+ $this->lessCompiler = new LessCompiler();
+ $this->pubPath = $app->getBaseDir('public');
+ $this->collect();
+ }
+
+ /**
+ * Collect Web 2 and module LESS files and add them to the LESS compiler
+ */
+ protected function collect()
+ {
+ foreach ($this->app->getLibraries() as $library) {
+ foreach ($library->getCssAssets() as $lessFile) {
+ if (substr($lessFile, -4) === '.css') {
+ $this->cssFiles[] = $lessFile;
+ } else {
+ $this->lessCompiler->addLessFile($lessFile);
+ }
+ }
+ }
+
+ foreach (self::$lessFiles as $lessFile) {
+ $this->lessCompiler->addLessFile($this->pubPath . '/' . $lessFile);
+ }
+
+ $mm = $this->app->getModuleManager();
+
+ foreach ($mm->getLoadedModules() as $moduleName => $module) {
+ if ($module->hasCss()) {
+ foreach ($module->getCssFiles() as $lessFilePath) {
+ $this->lessCompiler->addModuleLessFile($moduleName, $lessFilePath);
+ }
+ }
+ }
+
+ $themingConfig = $this->app->getConfig()->getSection('themes');
+ $defaultTheme = $themingConfig->get('default');
+ $theme = null;
+ if ($defaultTheme !== null && $defaultTheme !== self::DEFAULT_THEME) {
+ $theme = $defaultTheme;
+ }
+
+ if (! (bool) $themingConfig->get('disabled', false)) {
+ $auth = Auth::getInstance();
+ if ($auth->isAuthenticated()) {
+ $userTheme = $auth->getUser()->getPreferences()->getValue('icingaweb', 'theme');
+ if ($userTheme !== null) {
+ $theme = $userTheme;
+ }
+ }
+ }
+
+ if ($themePath = self::getThemeFile($theme)) {
+ if ($this->app->isCli() || is_file($themePath) && is_readable($themePath)) {
+ $this->lessCompiler->setTheme($themePath);
+ } else {
+ $themePath = null;
+ Logger::warning(sprintf(
+ 'Theme "%s" set by user "%s" has not been found.',
+ $theme,
+ ($user = Auth::getInstance()->getUser()) !== null ? $user->getUsername() : 'anonymous'
+ ));
+ }
+ }
+
+ if (! $themePath || in_array($theme, self::THEME_WHITELIST, true)) {
+ $this->lessCompiler->addLessFile($this->pubPath . '/css/icinga/login-orbs.less');
+ }
+
+ $mode = 'none';
+ if ($user = Auth::getInstance()->getUser()) {
+ $file = $themePath !== null ? @file_get_contents($themePath) : false;
+ if (! $file || strpos($file, self::LIGHT_MODE_IDENTIFIER) !== false) {
+ $mode = $user->getPreferences()->getValue('icingaweb', 'theme_mode', self::DEFAULT_MODE);
+ }
+ }
+
+ $this->lessCompiler->setThemeMode($this->pubPath . '/css/modes/'. $mode . '.less');
+ }
+
+ /**
+ * Get all collected files
+ *
+ * @return string[]
+ */
+ protected function getFiles(): array
+ {
+ return array_merge($this->cssFiles, $this->lessCompiler->getLessFiles());
+ }
+
+ /**
+ * Get the stylesheet for PDF export
+ *
+ * @return $this
+ */
+ public static function forPdf()
+ {
+ $styleSheet = new self();
+ $styleSheet->lessCompiler->setTheme(null);
+ $styleSheet->lessCompiler->setThemeMode($styleSheet->pubPath . '/css/modes/none.less');
+ $styleSheet->lessCompiler->addLessFile($styleSheet->pubPath . '/css/pdf/pdfprint.less');
+ // TODO(el): Caching
+ return $styleSheet;
+ }
+
+ /**
+ * Render the stylesheet
+ *
+ * @param bool $minified Whether to compress the stylesheet
+ *
+ * @return string CSS
+ */
+ public function render($minified = false)
+ {
+ if ($minified) {
+ $this->lessCompiler->compress();
+ }
+
+ $css = '';
+ foreach ($this->cssFiles as $cssFile) {
+ $css .= file_get_contents($cssFile);
+ }
+
+ return $css . $this->lessCompiler->render();
+ }
+
+ /**
+ * Send the stylesheet to the client
+ *
+ * Does not cache the stylesheet if the HTTP header Cache-Control or Pragma is set to no-cache.
+ *
+ * @param bool $minified Whether to compress the stylesheet
+ */
+ public static function send($minified = false)
+ {
+ $styleSheet = new self();
+
+ $request = $styleSheet->app->getRequest();
+ $response = $styleSheet->app->getResponse();
+ $response->setHeader('Cache-Control', 'private,no-cache,must-revalidate', true);
+
+ $noCache = $request->getHeader('Cache-Control') === 'no-cache' || $request->getHeader('Pragma') === 'no-cache';
+
+ $collectedFiles = $styleSheet->getFiles();
+ if (! $noCache && FileCache::etagMatchesFiles($collectedFiles)) {
+ $response
+ ->setHttpResponseCode(304)
+ ->sendHeaders();
+ return;
+ }
+
+ $etag = FileCache::etagForFiles($collectedFiles);
+
+ $response->setHeader('ETag', $etag, true)
+ ->setHeader('Content-Type', 'text/css', true);
+
+ $cacheFile = 'icinga-' . $etag . ($minified ? '.min' : '') . '.css';
+ $cache = FileCache::instance();
+
+ if (! $noCache && $cache->has($cacheFile)) {
+ $response->setBody($cache->get($cacheFile));
+ } else {
+ $css = $styleSheet->render($minified);
+ $response->setBody($css);
+ $cache->store($cacheFile, $css);
+ }
+
+ $response->sendResponse();
+ }
+
+ /**
+ * Render the stylesheet
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ Logger::error($e);
+ return IcingaException::describe($e);
+ }
+ }
+
+ /**
+ * Get the path to the current LESS theme file
+ *
+ * @param $theme
+ *
+ * @return string|null Return null if self::DEFAULT_THEME is set as theme, path otherwise
+ */
+ public static function getThemeFile($theme)
+ {
+ $app = Icinga::app();
+
+ if ($theme && $theme !== self::DEFAULT_THEME) {
+ if (Hook::has('ThemeLoader')) {
+ try {
+ $path = Hook::first('ThemeLoader')->getThemeFile($theme);
+ } catch (Exception $e) {
+ Logger::error('Failed to call ThemeLoader hook: %s', $e);
+ $path = null;
+ }
+
+ if ($path !== null) {
+ return $path;
+ }
+ }
+
+ if (($pos = strpos($theme, '/')) !== false) {
+ $moduleName = substr($theme, 0, $pos);
+ $theme = substr($theme, $pos + 1);
+ if ($app->getModuleManager()->hasLoaded($moduleName)) {
+ $module = $app->getModuleManager()->getModule($moduleName);
+
+ return $module->getCssDir() . '/themes/' . $theme . '.less';
+ }
+ } else {
+ return $app->getBaseDir('public') . '/css/themes/' . $theme . '.less';
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php
new file mode 100644
index 0000000..f4fcba3
--- /dev/null
+++ b/library/Icinga/Web/Url.php
@@ -0,0 +1,806 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Url class that provides convenient access to parameters, allows to modify query parameters and
+ * returns Urls reflecting all changes made to the url and to the parameters.
+ *
+ * Direct instantiation is prohibited and should be done either with @see Url::fromRequest() or
+ * @see Url::fromPath()
+ */
+class Url
+{
+ /**
+ * Whether this url points to an external resource
+ *
+ * @var bool
+ */
+ protected $external;
+
+ /**
+ * An array of all parameters stored in this Url
+ *
+ * @var UrlParams
+ */
+ protected $params;
+
+ /**
+ * The site anchor after the '#'
+ *
+ * @var string
+ */
+ protected $anchor = '';
+
+ /**
+ * The relative path of this Url, without query parameters
+ *
+ * @var string
+ */
+ protected $path = '';
+
+ /**
+ * The basePath of this Url
+ *
+ * @var string
+ */
+ protected $basePath;
+
+ /**
+ * The host of this Url
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * The port of this Url
+ *
+ * @var string
+ */
+ protected $port;
+
+ /**
+ * The scheme of this Url
+ *
+ * @var string
+ */
+ protected $scheme;
+
+ /**
+ * The username passed with this Url
+ *
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * The password passed with this Url
+ *
+ * @var string
+ */
+ protected $password;
+
+ protected function __construct()
+ {
+ $this->params = UrlParams::fromQueryString(''); // TODO: ::create()
+ }
+
+ /**
+ * Create a new Url class representing the current request
+ *
+ * If $params are given, those will be added to the request's parameters
+ * and overwrite any existing parameters
+ *
+ * @param UrlParams|array $params Parameters that should additionally be considered for the url
+ * @param Zend_Request $request A request to use instead of the default one
+ *
+ * @return static
+ */
+ public static function fromRequest($params = array(), $request = null)
+ {
+ if ($request === null) {
+ $request = static::getRequest();
+ }
+
+ $url = new static();
+ $url->setPath(ltrim($request->getPathInfo(), '/'));
+
+ // $urlParams = UrlParams::fromQueryString($request->getQuery());
+ if (isset($_SERVER['QUERY_STRING'])) {
+ $urlParams = UrlParams::fromQueryString($_SERVER['QUERY_STRING']);
+ } else {
+ $urlParams = UrlParams::fromQueryString('');
+ foreach ($request->getQuery() as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ }
+
+ foreach ($params as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ $url->setParams($urlParams);
+ $url->setBasePath($request->getBaseUrl());
+ return $url;
+ }
+
+ /**
+ * Return a request object that should be used for determining the URL
+ *
+ * @return Zend_Abstract_Request
+ */
+ protected static function getRequest()
+ {
+ $app = Icinga::app();
+ if ($app->isCli()) {
+ throw new ProgrammingError(
+ 'Url::fromRequest and Url::fromPath are currently not supported for CLI operations'
+ );
+ } else {
+ return $app->getRequest();
+ }
+ }
+
+ /**
+ * Create a new Url class representing the given url
+ *
+ * If $params are given, those will be added to the urls parameters
+ * and overwrite any existing parameters
+ *
+ * @param string $url The string representation of the url to parse
+ * @param array $params An array of parameters that should additionally be considered for the url
+ * @param Zend_Request $request A request to use instead of the default one
+ *
+ * @return static
+ */
+ public static function fromPath($url, array $params = array(), $request = null)
+ {
+ if ($request === null) {
+ $request = static::getRequest();
+ }
+
+ if (! is_string($url)) {
+ throw new ProgrammingError(
+ 'url %s is not a string',
+ var_export($url, true)
+ );
+ }
+
+ $urlObject = new static();
+
+ if ($url === '#') {
+ $urlObject->setPath($url);
+ return $urlObject;
+ }
+
+ $urlParts = parse_url($url);
+ if (isset($urlParts['scheme']) && (
+ $urlParts['scheme'] !== $request->getScheme()
+ || (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME'))
+ || (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT')))
+ ) {
+ $urlObject->setIsExternal();
+ }
+
+ if (isset($urlParts['path'])) {
+ $urlPath = $urlParts['path'];
+ if ($urlPath && $urlPath[0] === '/') {
+ if ($urlObject->isExternal() || isset($urlParts['user'])) {
+ $urlPath = ltrim($urlPath, '/');
+ } else {
+ $requestBaseUrl = $request->getBaseUrl();
+ if ($requestBaseUrl && $requestBaseUrl !== '/' && strpos($urlPath, $requestBaseUrl) === 0) {
+ $urlPath = ltrim(substr($urlPath, strlen($requestBaseUrl)), '/');
+ $urlObject->setBasePath($requestBaseUrl);
+ }
+ }
+ } elseif (! $urlObject->isExternal()) {
+ $urlObject->setBasePath($request->getBaseUrl());
+ }
+
+ $urlObject->setPath($urlPath);
+ } elseif (! $urlObject->isExternal()) {
+ $urlObject->setBasePath($request->getBaseUrl());
+ }
+
+ // TODO: This has been used by former filter implementation, remove it:
+ if (isset($urlParts['query'])) {
+ $params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
+ }
+ if (isset($urlParts['fragment'])) {
+ $urlObject->setAnchor($urlParts['fragment']);
+ }
+
+ if (isset($urlParts['user']) || $urlObject->isExternal()) {
+ if (isset($urlParts['user'])) {
+ $urlObject->setUsername($urlParts['user']);
+ }
+ if (isset($urlParts['host'])) {
+ $urlObject->setHost($urlParts['host']);
+ }
+ if (isset($urlParts['port'])) {
+ $urlObject->setPort($urlParts['port']);
+ }
+ if (isset($urlParts['scheme'])) {
+ $urlObject->setScheme($urlParts['scheme']);
+ }
+ if (isset($urlParts['pass'])) {
+ $urlObject->setPassword($urlParts['pass']);
+ }
+ }
+
+ $urlObject->setParams($params);
+ return $urlObject;
+ }
+
+ /**
+ * Create a new filter that needs to fullfill the base filter and the optional filter (if it exists)
+ *
+ * @param string $url The url to apply the new filter to
+ * @param Filter $filter The base filter
+ * @param Filter $optional The optional filter
+ *
+ * @return static The altered URL containing the new filter
+ * @throws ProgrammingError
+ */
+ public static function urlAddFilterOptional($url, $filter, $optional)
+ {
+ $url = static::fromPath($url);
+ $f = $filter;
+ if (isset($optional)) {
+ $f = Filter::matchAll($filter, $optional);
+ }
+ return $url->setQueryString($f->toQueryString());
+ }
+
+ /**
+ * Add the given filter to the current filter of the URL
+ *
+ * @param Filter $and
+ *
+ * @return $this
+ */
+ public function addFilter($and)
+ {
+ $this->setQueryString(
+ Filter::fromQueryString($this->getQueryString())
+ ->andFilter($and)
+ ->toQueryString()
+ );
+ return $this;
+ }
+
+ /**
+ * Set the basePath for this url
+ *
+ * @param string $basePath New basePath of this url
+ *
+ * @return $this
+ */
+ public function setBasePath($basePath)
+ {
+ $this->basePath = rtrim($basePath, '/ ');
+ return $this;
+ }
+
+ /**
+ * Return the basePath set for this url
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ return $this->basePath;
+ }
+
+ /**
+ * Set the host for this url
+ *
+ * @param string $host New host of this Url
+ *
+ * @return $this
+ */
+ public function setHost($host)
+ {
+ $this->host = $host;
+ return $this;
+ }
+
+ /**
+ * Return the host set for this url
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Set the port for this url
+ *
+ * @param string $port New port of this url
+ *
+ * @return $this
+ */
+ public function setPort($port)
+ {
+ $this->port = $port;
+ return $this;
+ }
+
+ /**
+ * Return the port set for this url
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Set the scheme for this url
+ *
+ * @param string $scheme The scheme used for this url
+ *
+ * @return $this
+ */
+ public function setScheme($scheme)
+ {
+ $this->scheme = $scheme;
+ return $this;
+ }
+
+ /**
+ * Return the scheme set for this url
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return $this->scheme;
+ }
+
+ /**
+ * Set the relative path of this url, without query parameters
+ *
+ * @param string $path The path to set
+ *
+ * @return $this
+ */
+ public function setPath($path)
+ {
+ $this->path = $path;
+ return $this;
+ }
+
+ /**
+ * Return the relative path of this url, without query parameters
+ *
+ * If you want the relative path with query parameters use getRelativeUrl
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Set whether this url points to an external resource
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setIsExternal($state = true)
+ {
+ $this->external = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this url points to an external resource
+ *
+ * @return bool
+ */
+ public function isExternal()
+ {
+ return $this->external;
+ }
+
+ /**
+ * Set the username passed with this url
+ *
+ * @param string $username The username to set
+ *
+ * @return $this
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+ return $this;
+ }
+
+ /**
+ * Return the username passed with this url
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * Set the username passed with this url
+ *
+ * @param string $password The password to set
+ *
+ * @return $this
+ */
+ public function setPassword($password)
+ {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * Return the password passed with this url
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Return the relative url
+ *
+ * @return string
+ */
+ public function getRelativeUrl($separator = '&')
+ {
+ $path = $this->buildPathQueryAndFragment($separator);
+ if ($path && $path[0] === '/') {
+ return '';
+ }
+
+ return $path;
+ }
+
+ /**
+ * Return this url's path with its query parameters and fragment as string
+ *
+ * @return string
+ */
+ protected function buildPathQueryAndFragment($querySeparator)
+ {
+ $anchor = $this->getAnchor();
+ if ($anchor) {
+ $anchor = '#' . $anchor;
+ }
+
+ $query = $this->getQueryString($querySeparator);
+ if ($query) {
+ $query = '?' . $query;
+ }
+
+ return $this->getPath() . $query . $anchor;
+ }
+
+ public function setQueryString($queryString)
+ {
+ $this->params = UrlParams::fromQueryString($queryString);
+ return $this;
+ }
+
+ public function getQueryString($separator = null)
+ {
+ return $this->params->toString($separator);
+ }
+
+ /**
+ * Return the absolute url with query parameters as a string
+ *
+ * @return string
+ */
+ public function getAbsoluteUrl($separator = '&')
+ {
+ $path = $this->buildPathQueryAndFragment($separator);
+ if ($path && ($path === '#' || $path[0] === '/')) {
+ return $path;
+ }
+
+ $basePath = $this->getBasePath();
+ if (! $basePath) {
+ $basePath = '/';
+ }
+
+ if ($this->getUsername() || $this->isExternal()) {
+ $urlString = '';
+ if ($this->getScheme()) {
+ $urlString .= $this->getScheme() . '://';
+ }
+ if ($this->getPassword()) {
+ $urlString .= $this->getUsername() . ':' . $this->getPassword() . '@';
+ } elseif ($this->getUsername()) {
+ $urlString .= $this->getUsername() . '@';
+ }
+ if ($this->getHost()) {
+ $urlString .= $this->getHost();
+ }
+ if ($this->getPort()) {
+ $urlString .= ':' . $this->getPort();
+ }
+
+ return $urlString . $basePath . ($basePath !== '/' && $path ? '/' : '') . $path;
+ } else {
+ return $basePath . ($basePath !== '/' && $path ? '/' : '') . $path;
+ }
+ }
+
+ /**
+ * Add a set of parameters to the query part if the keys don't exist yet
+ *
+ * @param array $params The parameters to add
+ *
+ * @return $this
+ */
+ public function addParams(array $params)
+ {
+ foreach ($params as $k => $v) {
+ $this->params->add($k, $v);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set and overwrite the given params if one if the same key already exists
+ *
+ * @param array $params The parameters to set
+ *
+ * @return $this
+ */
+ public function overwriteParams(array $params)
+ {
+ foreach ($params as $k => $v) {
+ $this->params->set($k, $v);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overwrite the parameters used in the query part
+ *
+ * @param UrlParams|array $params The new parameters to use for the query part
+ *
+ * @return $this
+ */
+ public function setParams($params)
+ {
+ if ($params instanceof UrlParams) {
+ $this->params = $params;
+ } elseif (is_array($params)) {
+ $urlParams = UrlParams::fromQueryString('');
+ foreach ($params as $k => $v) {
+ $urlParams->set($k, $v);
+ }
+ $this->params = $urlParams;
+ } else {
+ throw new ProgrammingError(
+ 'Url params needs to be either an array or an UrlParams instance'
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Return all parameters that will be used in the query part
+ *
+ * @return UrlParams An instance of UrlParam containing all parameters
+ */
+ public function getParams()
+ {
+ return $this->params;
+ }
+
+ /**
+ * Return true if a urls' query parameter exists, otherwise false
+ *
+ * @param string $param The url parameter name to check
+ *
+ * @return bool
+ */
+ public function hasParam($param)
+ {
+ return $this->params->has($param);
+ }
+
+ /**
+ * Return a url's query parameter if it exists, otherwise $default
+ *
+ * @param string $param A query parameter name to return if existing
+ * @param mixed $default A value to return when the parameter doesn't exist
+ *
+ * @return mixed
+ */
+ public function getParam($param, $default = null)
+ {
+ return $this->params->get($param, $default);
+ }
+
+ /**
+ * Set a single parameter, overwriting any existing one with the same name
+ *
+ * @param string $param The query parameter name
+ * @param array|string $value An array or string to set as the parameter value
+ *
+ * @return $this
+ */
+ public function setParam($param, $value = true)
+ {
+ $this->params->set($param, $value);
+ return $this;
+ }
+
+ /**
+ * Set the url anchor-part
+ *
+ * @param string $anchor The site's anchor string without the '#'
+ *
+ * @return $this
+ */
+ public function setAnchor($anchor)
+ {
+ $this->anchor = $anchor;
+ return $this;
+ }
+
+ /**
+ * Return the url anchor-part
+ *
+ * @return string The site's anchor string without the '#'
+ */
+ public function getAnchor()
+ {
+ return $this->anchor;
+ }
+
+ /**
+ * Remove provided key (if string) or keys (if array of string) from the query parameter array
+ *
+ * @param string|array $keyOrArrayOfKeys An array of strings or a string representing the key(s)
+ * of the parameters to be removed
+ * @return $this
+ */
+ public function remove($keyOrArrayOfKeys)
+ {
+ $this->params->remove($keyOrArrayOfKeys);
+ return $this;
+ }
+
+ /**
+ * Shift a query parameter from this URL if it exists, otherwise $default
+ *
+ * @param string $param Parameter name
+ * @param mixed $default Default value in case $param does not exist
+ *
+ * @return mixed
+ */
+ public function shift($param, $default = null)
+ {
+ return $this->params->shift($param, $default);
+ }
+
+ /**
+ * Whether the given URL matches this URL object
+ *
+ * This does an exact match, parameters MUST be in the same order
+ *
+ * @param Url|string $url the URL to compare against
+ *
+ * @return bool whether the URL matches
+ */
+ public function matches($url)
+ {
+ if (! $url instanceof static) {
+ $url = static::fromPath($url);
+ }
+ return (string) $url === (string) $this;
+ }
+
+ /**
+ * Return a copy of this url without the parameter given
+ *
+ * The argument can be either a single query parameter name or an array of parameter names to
+ * remove from the query list
+ *
+ * @param string|array $keyOrArrayOfKeys A single string or an array containing parameter names
+ *
+ * @return static
+ */
+ public function getUrlWithout($keyOrArrayOfKeys)
+ {
+ return $this->without($keyOrArrayOfKeys);
+ }
+
+ public function without($keyOrArrayOfKeys)
+ {
+ $url = clone($this);
+ $url->remove($keyOrArrayOfKeys);
+ return $url;
+ }
+
+ /**
+ * Return a copy of this url with the given parameter(s)
+ *
+ * The argument can be either a single query parameter name or an array of parameter names to
+ * remove from the query list
+ *
+ * @param string|array $param A single string or an array containing parameter names
+ * @param array $values an optional values array
+ *
+ * @return static
+ */
+ public function with($param, $values = null)
+ {
+ $url = clone($this);
+ $url->params->mergeValues($param, $values);
+ return $url;
+ }
+
+ /**
+ * Return a copy of this url with only the given parameter(s)
+ *
+ * The argument can be either a single query parameter name or
+ * an array of parameter names to keep on on the query
+ *
+ * @param string|array $keyOrArrayOfKeys
+ *
+ * @return static
+ */
+ public function onlyWith($keyOrArrayOfKeys)
+ {
+ if (! is_array($keyOrArrayOfKeys)) {
+ $keyOrArrayOfKeys = [$keyOrArrayOfKeys];
+ }
+
+ $url = clone $this;
+ foreach ($url->getParams()->toArray(false) as $param => $value) {
+ if (is_int($param)) {
+ $param = $value;
+ }
+
+ if (! in_array($param, $keyOrArrayOfKeys, true)) {
+ $url->remove($param);
+ }
+ }
+
+ return $url;
+ }
+
+ public function __clone()
+ {
+ $this->params = clone $this->params;
+ }
+
+ /**
+ * Alias for @see Url::getAbsoluteUrl()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return htmlspecialchars($this->getAbsoluteUrl(), ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8', true);
+ }
+}
diff --git a/library/Icinga/Web/UrlParams.php b/library/Icinga/Web/UrlParams.php
new file mode 100644
index 0000000..6da1f30
--- /dev/null
+++ b/library/Icinga/Web/UrlParams.php
@@ -0,0 +1,433 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Exception\MissingParameterException;
+
+class UrlParams
+{
+ protected $separator = '&';
+
+ protected $params = array();
+
+ protected $index = array();
+
+ public function isEmpty()
+ {
+ return empty($this->index);
+ }
+
+ public function setSeparator($separator)
+ {
+ $this->separator = $separator;
+ return $this;
+ }
+
+ /**
+ * Get the given parameter
+ *
+ * Returns the last URL param if defined multiple times, $default if not
+ * given at all
+ *
+ * @param string $param The parameter you're interested in
+ * @param string $default An optional default value
+ *
+ * @return mixed
+ */
+ public function get($param, $default = null)
+ {
+ if (! $this->has($param)) {
+ return $default;
+ }
+
+ return rawurldecode($this->params[ end($this->index[$param]) ][ 1 ]);
+ }
+
+ /**
+ * Require a parameter
+ *
+ * @param string $name Name of the parameter
+ * @param bool $strict Whether the parameter's value must not be the empty string
+ *
+ * @return mixed
+ *
+ * @throws MissingParameterException If the parameter was not given
+ */
+ public function getRequired($name, $strict = true)
+ {
+ if ($this->has($name)) {
+ $value = $this->get($name);
+ if (! $strict || strlen($value) > 0) {
+ return $value;
+ }
+ }
+ $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name);
+ $e->setParameter($name);
+ throw $e;
+ }
+
+ /**
+ * Get all instances of the given parameter
+ *
+ * Returns an array containing all values defined for a given parameter,
+ * $default if none.
+ *
+ * @param string $param The parameter you're interested in
+ * @param string $default An optional default value
+ *
+ * @return mixed
+ */
+ public function getValues($param, $default = array())
+ {
+ if (! $this->has($param)) {
+ return $default;
+ }
+
+ $ret = array();
+ foreach ($this->index[$param] as $key) {
+ $ret[] = rawurldecode($this->params[$key][1]);
+ }
+ return $ret;
+ }
+
+ /**
+ * Whether the given parameter exists
+ *
+ * Returns true if such a parameter has been defined, false otherwise.
+ *
+ * @param string $param The parameter you're interested in
+ *
+ * @return boolean
+ */
+ public function has($param)
+ {
+ return array_key_exists($param, $this->index);
+ }
+
+ /**
+ * Get and remove the given parameter
+ *
+ * Returns the last URL param if defined multiple times, $default if not
+ * given at all. The parameter will be removed from this object.
+ *
+ * @param string $param The parameter you're interested in
+ * @param string $default An optional default value
+ *
+ * @return mixed
+ */
+ public function shift($param = null, $default = null)
+ {
+ if ($param === null) {
+ if (empty($this->params)) {
+ return $default;
+ }
+ $ret = array_shift($this->params);
+ $ret[0] = rawurldecode($ret[0]);
+ $ret[1] = rawurldecode($ret[1]);
+ } else {
+ if (! $this->has($param)) {
+ return $default;
+ }
+ $key = reset($this->index[$param]);
+ $ret = rawurldecode($this->params[$key][1]);
+ unset($this->params[$key]);
+ }
+
+ $this->reIndexAll();
+ return $ret;
+ }
+
+ /**
+ * Require and remove a parameter
+ *
+ * @param string $name Name of the parameter
+ * @param bool $strict Whether the parameter's value must not be the empty string
+ *
+ * @return mixed
+ *
+ * @throws MissingParameterException If the parameter was not given
+ */
+ public function shiftRequired($name, $strict = true)
+ {
+ if ($this->has($name)) {
+ $value = $this->get($name);
+ if (! $strict || strlen($value) > 0) {
+ $this->shift($name);
+ return $value;
+ }
+ }
+ $e = new MissingParameterException(t('Required parameter \'%s\' missing'), $name);
+ $e->setParameter($name);
+ throw $e;
+ }
+
+ public function addEncoded($param, $value = true)
+ {
+ $this->params[] = array($param, $this->cleanupValue($value));
+ $this->indexLastOne();
+ return $this;
+ }
+
+ protected function urlEncode($value)
+ {
+ return rawurlencode($value instanceof Url ? $value->getAbsoluteUrl() : (string) $value);
+ }
+
+ /**
+ * Add the given parameter with the given value
+ *
+ * This will add the given parameter, regardless of whether it already
+ * exists.
+ *
+ * @param string $param The parameter you're interested in
+ * @param string $value The value to be stored
+ *
+ * @return $this
+ */
+ public function add($param, $value = true)
+ {
+ return $this->addEncoded($this->urlEncode($param), $this->urlEncode($value));
+ }
+
+ /**
+ * Adds a list of parameters
+ *
+ * This may be used with either a list of values for a single parameter or
+ * with a list of parameter / value pairs.
+ *
+ * @param string $param Parameter name or param/value list
+ * @param string $value The value to be stored
+ *
+ * @return $this
+ */
+ public function addValues($param, $values = null)
+ {
+ if ($values === null && is_array($param)) {
+ foreach ($param as $k => $v) {
+ $this->add($k, $v);
+ }
+ } else {
+ foreach ($values as $value) {
+ $this->add($param, $value);
+ }
+ }
+
+ return $this;
+ }
+
+ protected function clearValues()
+ {
+ $this->params = array();
+ $this->index = array();
+ }
+
+ public function mergeValues($param, $values = null)
+ {
+ if ($values === null && is_array($param)) {
+ foreach ($param as $k => $v) {
+ $this->set($k, $v);
+ }
+ } else {
+ if (! is_array($values)) {
+ $values = array($values);
+ }
+ foreach ($values as $value) {
+ $this->set($param, $value);
+ }
+ }
+
+ return $this;
+ }
+
+ public function setValues($param, $values = null)
+ {
+ $this->clearValues();
+ return $this->addValues($param, $values);
+ }
+
+ /**
+ * Add the given parameter with the given value in front of all other values
+ *
+ * This will add the given parameter in front of all others, regardless of
+ * whether it already exists.
+ *
+ * @param string $param The parameter you're interested in
+ * @param string $value The value to be stored
+ *
+ * @return $this
+ */
+ public function unshift($param, $value)
+ {
+ array_unshift($this->params, array($this->urlEncode($param), $this->urlEncode($value)));
+ $this->reIndexAll();
+ return $this;
+ }
+
+ /**
+ * Set the given parameter with the given value
+ *
+ * This will set the given parameter, and override eventually existing ones.
+ *
+ * @param string $param The parameter you want to set
+ * @param string $value The value to be stored
+ *
+ * @return $this
+ */
+ public function set($param, $value)
+ {
+ if (! $this->has($param)) {
+ return $this->add($param, $value);
+ }
+
+ while (count($this->index[$param]) > 1) {
+ $remove = array_pop($this->index[$param]);
+ unset($this->params[$remove]);
+ }
+
+ $this->params[$this->index[$param][0]] = array(
+ $this->urlEncode($param),
+ $this->urlEncode($this->cleanupValue($value))
+ );
+ $this->reIndexAll();
+
+ return $this;
+ }
+
+ public function remove($param)
+ {
+ $changed = false;
+
+ if (! is_array($param)) {
+ $param = array($param);
+ }
+
+ foreach ($param as $p) {
+ if ($this->has($p)) {
+ foreach ($this->index[$p] as $key) {
+ unset($this->params[$key]);
+ }
+ $changed = true;
+ }
+ }
+
+ if ($changed) {
+ $this->reIndexAll();
+ }
+
+ return $this;
+ }
+
+ public function without($param)
+ {
+ $params = clone $this;
+ return $params->remove($param);
+ }
+
+ // TODO: push, pop?
+
+ protected function indexLastOne()
+ {
+ end($this->params);
+ $key = key($this->params);
+ $param = $this->params[$key][0];
+ $this->addParamToIndex($param, $key);
+ }
+
+ protected function addParamToIndex($param, $key)
+ {
+ if (! $this->has($param)) {
+ $this->index[$param] = array();
+ }
+ $this->index[$param][] = $key;
+ }
+
+ protected function reIndexAll()
+ {
+ $this->index = array();
+ $this->params = array_values($this->params);
+ foreach ($this->params as $key => & $param) {
+ $this->addParamToIndex($param[0], $key);
+ }
+ }
+
+ protected function cleanupValue($value)
+ {
+ return is_bool($value) ? $value : (string) $value;
+ }
+
+ protected function parseQueryString($queryString)
+ {
+ $parts = preg_split('~&~', $queryString, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($parts as $part) {
+ $this->parseQueryStringPart($part);
+ }
+ }
+
+ protected function parseQueryStringPart($part)
+ {
+ if (strpos($part, '=') === false) {
+ $this->addEncoded($part, true);
+ } else {
+ list($key, $val) = preg_split('/=/', $part, 2);
+ $this->addEncoded($key, $val);
+ }
+ }
+
+ /**
+ * Return the parameters of this url as sequenced or associative array
+ *
+ * @param bool $sequenced
+ *
+ * @return array
+ */
+ public function toArray($sequenced = true)
+ {
+ if ($sequenced) {
+ return $this->params;
+ }
+
+ $params = array();
+ foreach ($this->params as $param) {
+ if ($param[1] === true) {
+ $params[] = $param[0];
+ } else {
+ $params[$param[0]] = $param[1];
+ }
+ }
+
+ return $params;
+ }
+
+ public function toString($separator = null)
+ {
+ if ($separator === null) {
+ $separator = $this->separator;
+ }
+ $parts = array();
+ foreach ($this->params as $p) {
+ if ($p[1] === true) {
+ $parts[] = $p[0];
+ } else {
+ $parts[] = $p[0] . '=' . $p[1];
+ }
+ }
+ return implode($separator, $parts);
+ }
+
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ public static function fromQueryString($queryString = null)
+ {
+ if ($queryString === null) {
+ $queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
+ }
+ $params = new static();
+ $params->parseQueryString($queryString);
+
+ return $params;
+ }
+}
diff --git a/library/Icinga/Web/UserAgent.php b/library/Icinga/Web/UserAgent.php
new file mode 100644
index 0000000..71c1a8b
--- /dev/null
+++ b/library/Icinga/Web/UserAgent.php
@@ -0,0 +1,86 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web;
+
+/**
+ * Class UserAgent
+ *
+ * This class helps to get user agent information like OS type and browser name
+ *
+ * @package Icinga\Web
+ */
+class UserAgent
+{
+ /**
+ * $_SERVER['HTTP_USER_AGENT'] output string
+ *
+ * @var string|null
+ */
+ private $agent;
+
+ public function __construct($agent = null)
+ {
+ $this->agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
+
+ if ($agent) {
+ $this->agent = $agent->http_user_agent;
+ }
+ }
+
+ /**
+ * Return $_SERVER['HTTP_USER_AGENT'] output string of given or current device
+ *
+ * @return string
+ */
+ public function getAgent()
+ {
+ return $this->agent;
+ }
+
+ /**
+ * Get Browser name
+ *
+ * @return string Browser name or unknown if not found
+ */
+ public function getBrowser()
+ {
+ // key => regex value
+ $browsers = [
+ "Internet Explorer" => "/MSIE(.*)/i",
+ "Seamonkey" => "/Seamonkey(.*)/i",
+ "MS Edge" => "/Edg(.*)/i",
+ "Opera" => "/Opera(.*)/i",
+ "Opera Browser" => "/OPR(.*)/i",
+ "Chromium" => "/Chromium(.*)/i",
+ "Firefox" => "/Firefox(.*)/i",
+ "Google Chrome" => "/Chrome(.*)/i",
+ "Safari" => "/Safari(.*)/i"
+ ];
+ //TODO find a way to return also the version of the browser
+ foreach ($browsers as $browser => $regex) {
+ if (preg_match($regex, $this->agent)) {
+ return $browser;
+ }
+ }
+
+ return 'unknown';
+ }
+
+ /**
+ * Get Operating system information
+ *
+ * @return string os information
+ */
+ public function getOs()
+ {
+ // get string before the first appearance of ')'
+ $device = strstr($this->agent, ')', true);
+ if (! $device) {
+ return 'unknown';
+ }
+
+ // return string after the first appearance of '('
+ return substr($device, strpos($device, '(') + 1);
+ }
+}
diff --git a/library/Icinga/Web/View.php b/library/Icinga/Web/View.php
new file mode 100644
index 0000000..ec16940
--- /dev/null
+++ b/library/Icinga/Web/View.php
@@ -0,0 +1,254 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Closure;
+use Icinga\Application\Icinga;
+use ipl\I18n\Translation;
+use Zend_View_Abstract;
+use Icinga\Authentication\Auth;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Icinga view
+ *
+ * @method Url href($path = null, $params = null) {
+ * @param Url|string|null $path
+ * @param string[]|null $params
+ * }
+ *
+ * @method Url url($path = null, $params = null) {
+ * @param Url|string|null $path
+ * @param string[]|null $params
+ * }
+ *
+ * @method Url qlink($title, $url, $params = null, $properties = null, $escape = true) {
+ * @param string $title
+ * @param Url|string|null $url
+ * @param string[]|null $params
+ * @param string[]|null $properties
+ * @param bool $escape
+ * }
+ *
+ * @method string img($url, $params = null, array $properties = array()) {
+ * @param Url|string|null $url
+ * @param string[]|null $params
+ * @param string[] $properties
+ * }
+ *
+ * @method string icon($img, $title = null, array $properties = array()) {
+ * @param string $img
+ * @param string|null $title
+ * @param string[] $properties
+ * }
+ *
+ * @method string propertiesToString($properties) {
+ * @param string[] $properties
+ * }
+ *
+ * @method string attributeToString($key, $value) {
+ * @param string $key
+ * @param string $value
+ * }
+ */
+class View extends Zend_View_Abstract
+{
+ use Translation;
+
+ /**
+ * Charset to be used - we only support UTF-8
+ */
+ const CHARSET = 'UTF-8';
+
+ /**
+ * Registered helper functions
+ */
+ private $helperFunctions = array();
+
+ /**
+ * Authentication manager
+ *
+ * @var Auth|null
+ */
+ private $auth;
+
+ /**
+ * Create a new view object
+ *
+ * @param array $config
+ * @see Zend_View_Abstract::__construct
+ */
+ public function __construct($config = array())
+ {
+ $config['helperPath']['Icinga\\Web\\View\\Helper\\'] = Icinga::app()->getLibraryDir('Icinga/Web/View/Helper');
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Initialize the view
+ *
+ * @see Zend_View_Abstract::init
+ */
+ public function init()
+ {
+ $this->loadGlobalHelpers();
+ }
+
+ /**
+ * Escape the given value top be safely used in view scripts
+ *
+ * @param string $value The output to be escaped
+ * @return string
+ */
+ public function escape($value)
+ {
+ return htmlspecialchars($value ?? '', ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, self::CHARSET, true);
+ }
+
+ /**
+ * Whether a specific helper (closure) has been registered
+ *
+ * @param string $name The desired function name
+ * @return boolean
+ */
+ public function hasHelperFunction($name)
+ {
+ return array_key_exists($name, $this->helperFunctions);
+ }
+
+ /**
+ * Add a new helper function
+ *
+ * @param string $name The desired function name
+ * @param Closure $function An anonymous function
+ * @return $this
+ */
+ public function addHelperFunction($name, Closure $function)
+ {
+ if ($this->hasHelperFunction($name)) {
+ throw new ProgrammingError(
+ 'Cannot assign the same helper function twice: "%s"',
+ $name
+ );
+ }
+
+ $this->helperFunctions[$name] = $function;
+ return $this;
+ }
+
+ /**
+ * Set or overwrite a helper function
+ *
+ * @param string $name
+ * @param Closure $function
+ *
+ * @return $this
+ */
+ public function setHelperFunction($name, Closure $function)
+ {
+ $this->helperFunctions[$name] = $function;
+ return $this;
+ }
+
+ /**
+ * Drop a helper function
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function dropHelperFunction($name)
+ {
+ unset($this->helperFunctions[$name]);
+ return $this;
+ }
+
+ /**
+ * Call a helper function
+ *
+ * @param string $name The desired function name
+ * @param Array $args Function arguments
+ * @return mixed
+ */
+ public function callHelperFunction($name, $args)
+ {
+ return call_user_func_array(
+ $this->helperFunctions[$name],
+ $args
+ );
+ }
+
+ /**
+ * Load helpers
+ */
+ private function loadGlobalHelpers()
+ {
+ $pattern = dirname(__FILE__) . '/View/helpers/*.php';
+ $files = glob($pattern);
+ foreach ($files as $file) {
+ require_once $file;
+ }
+ }
+
+ /**
+ * Get the authentication manager
+ *
+ * @return Auth
+ */
+ public function Auth()
+ {
+ if ($this->auth === null) {
+ $this->auth = Auth::getInstance();
+ }
+ return $this->auth;
+ }
+
+ /**
+ * Whether the current user has the given permission
+ *
+ * @param string $permission Name of the permission
+ *
+ * @return bool
+ */
+ public function hasPermission($permission)
+ {
+ return $this->Auth()->hasPermission($permission);
+ }
+
+ /**
+ * Use to include the view script in a scope that only allows public
+ * members.
+ *
+ * @return mixed
+ *
+ * @see Zend_View_Abstract::run
+ */
+ protected function _run()
+ {
+ foreach ($this->getVars() as $k => $v) {
+ // Exporting global variables to view scripts:
+ $$k = $v;
+ }
+
+ include func_get_arg(0);
+ }
+
+ /**
+ * Accesses a helper object from within a script
+ *
+ * @param string $name
+ * @param array $args
+ *
+ * @return string
+ */
+ public function __call($name, $args)
+ {
+ if ($this->hasHelperFunction($name)) {
+ return $this->callHelperFunction($name, $args);
+ } else {
+ return parent::__call($name, $args);
+ }
+ }
+}
diff --git a/library/Icinga/Web/View/AppHealth.php b/library/Icinga/Web/View/AppHealth.php
new file mode 100644
index 0000000..c66ca05
--- /dev/null
+++ b/library/Icinga/Web/View/AppHealth.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\View;
+
+use Icinga\Application\Hook\HealthHook;
+use ipl\Html\FormattedString;
+use ipl\Html\HtmlElement;
+use ipl\Html\Table;
+use ipl\Web\Common\BaseTarget;
+use ipl\Web\Widget\Link;
+use Traversable;
+
+class AppHealth extends Table
+{
+ use BaseTarget;
+
+ protected $defaultAttributes = ['class' => ['app-health', 'common-table', 'table-row-selectable']];
+
+ /** @var Traversable */
+ protected $data;
+
+ public function __construct(Traversable $data)
+ {
+ $this->data = $data;
+
+ $this->setBaseTarget('_next');
+ }
+
+ protected function assemble()
+ {
+ foreach ($this->data as $row) {
+ $this->add(Table::tr([
+ Table::th(HtmlElement::create('span', ['class' => [
+ 'ball',
+ 'ball-size-xl',
+ $this->getStateClass($row->state)
+ ]])),
+ Table::td([
+ new HtmlElement('header', null, FormattedString::create(
+ t('%s by %s is %s', '<check> by <module> is <state-text>'),
+ $row->url
+ ? new Link(HtmlElement::create('span', null, $row->name), $row->url)
+ : HtmlElement::create('span', null, $row->name),
+ HtmlElement::create('span', null, $row->module),
+ HtmlElement::create('span', null, $this->getStateText($row->state))
+ )),
+ HtmlElement::create('section', null, $row->message)
+ ])
+ ]));
+ }
+ }
+
+ protected function getStateClass($state)
+ {
+ if ($state === null) {
+ $state = HealthHook::STATE_UNKNOWN;
+ }
+
+ switch ($state) {
+ case HealthHook::STATE_OK:
+ return 'state-ok';
+ case HealthHook::STATE_WARNING:
+ return 'state-warning';
+ case HealthHook::STATE_CRITICAL:
+ return 'state-critical';
+ case HealthHook::STATE_UNKNOWN:
+ return 'state-unknown';
+ }
+ }
+
+ protected function getStateText($state)
+ {
+ if ($state === null) {
+ $state = t('UNKNOWN');
+ }
+
+ switch ($state) {
+ case HealthHook::STATE_OK:
+ return t('OK');
+ case HealthHook::STATE_WARNING:
+ return t('WARNING');
+ case HealthHook::STATE_CRITICAL:
+ return t('CRITICAL');
+ case HealthHook::STATE_UNKNOWN:
+ return t('UNKNOWN');
+ }
+ }
+}
diff --git a/library/Icinga/Web/View/Helper/IcingaCheckbox.php b/library/Icinga/Web/View/Helper/IcingaCheckbox.php
new file mode 100644
index 0000000..07cf01f
--- /dev/null
+++ b/library/Icinga/Web/View/Helper/IcingaCheckbox.php
@@ -0,0 +1,30 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\View\Helper;
+
+class IcingaCheckbox extends \Zend_View_Helper_FormCheckbox
+{
+ public function icingaCheckbox($name, $value = null, $attribs = null, array $checkedOptions = null)
+ {
+ if (! isset($attribs['id'])) {
+ $attribs['id'] = $this->view->protectId('icingaCheckbox_' . $name);
+ }
+
+ $attribs['class'] = (isset($attribs['class']) ? $attribs['class'] . ' ' : '') . 'sr-only';
+ $html = parent::formCheckbox($name, $value, $attribs, $checkedOptions);
+
+ $class = 'toggle-switch';
+ if (isset($attribs['disabled'])) {
+ $class .= ' disabled';
+ }
+
+ return $html
+ . '<label for="'
+ . $attribs['id']
+ . '" aria-hidden="true"'
+ . ' class="'
+ . $class
+ . '"><span class="toggle-slider"></span></label>';
+ }
+}
diff --git a/library/Icinga/Web/View/PrivilegeAudit.php b/library/Icinga/Web/View/PrivilegeAudit.php
new file mode 100644
index 0000000..fcb4083
--- /dev/null
+++ b/library/Icinga/Web/View/PrivilegeAudit.php
@@ -0,0 +1,622 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\View;
+
+use Icinga\Authentication\Role;
+use Icinga\Forms\Security\RoleForm;
+use Icinga\Util\StringHelper;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlElement;
+use ipl\Html\HtmlString;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+use ipl\Web\Common\BaseTarget;
+use ipl\Web\Filter\QueryString;
+use ipl\Web\Url;
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\Link;
+
+class PrivilegeAudit extends BaseHtmlElement
+{
+ use BaseTarget;
+
+ /** @var string */
+ const UNRESTRICTED_PERMISSION = 'unrestricted';
+
+ protected $tag = 'ul';
+
+ protected $defaultAttributes = ['class' => 'privilege-audit'];
+
+ /** @var Role[] */
+ protected $roles;
+
+ public function __construct(array $roles)
+ {
+ $this->roles = $roles;
+ $this->setBaseTarget('_next');
+ }
+
+ protected function auditPermission($permission)
+ {
+ $grantedBy = [];
+ $refusedBy = [];
+ foreach ($this->roles as $role) {
+ if ($permission === self::UNRESTRICTED_PERMISSION) {
+ if ($role->isUnrestricted()) {
+ $grantedBy[] = $role->getName();
+ }
+ } elseif ($role->denies($permission)) {
+ $refusedBy[] = $role->getName();
+ } elseif ($role->grants($permission, false, false)) {
+ $grantedBy[] = $role->getName();
+ }
+ }
+
+ $header = new HtmlElement('summary');
+ if (! empty($refusedBy)) {
+ $header->add([
+ new Icon('times-circle', ['class' => 'refused']),
+ count($refusedBy) > 2
+ ? sprintf(
+ tp(
+ 'Refused by %s and %s as well as one other',
+ 'Refused by %s and %s as well as %d others',
+ count($refusedBy) - 2
+ ),
+ $refusedBy[0],
+ $refusedBy[1],
+ count($refusedBy) - 2
+ )
+ : sprintf(
+ tp('Refused by %s', 'Refused by %s and %s', count($refusedBy)),
+ ...$refusedBy
+ )
+ ]);
+ } elseif (! empty($grantedBy)) {
+ $header->add([
+ new Icon('check-circle', ['class' => 'granted']),
+ count($grantedBy) > 2
+ ? sprintf(
+ tp(
+ 'Granted by %s and %s as well as one other',
+ 'Granted by %s and %s as well as %d others',
+ count($grantedBy) - 2
+ ),
+ $grantedBy[0],
+ $grantedBy[1],
+ count($grantedBy) - 2
+ )
+ : sprintf(
+ tp('Granted by %s', 'Granted by %s and %s', count($grantedBy)),
+ ...$grantedBy
+ )
+ ]);
+ } else {
+ $header->add([new Icon('minus-circle'), t('Not granted or refused by any role')]);
+ }
+
+ $vClass = null;
+ $rolePaths = [];
+ foreach (array_reverse($this->roles) as $role) {
+ if (! in_array($role->getName(), $refusedBy, true) && ! in_array($role->getName(), $grantedBy, true)) {
+ continue;
+ }
+
+ /** @var Role[] $rolesReversed */
+ $rolesReversed = [];
+
+ do {
+ array_unshift($rolesReversed, $role);
+ } while (($role = $role->getParent()) !== null);
+
+ $path = new HtmlElement('ol');
+
+ $class = null;
+ $setInitiator = false;
+ foreach ($rolesReversed as $role) {
+ $granted = false;
+ $refused = false;
+ $icon = new Icon('minus-circle');
+ if ($permission === self::UNRESTRICTED_PERMISSION) {
+ if ($role->isUnrestricted()) {
+ $granted = true;
+ $icon = new Icon('check-circle', ['class' => 'granted']);
+ }
+ } elseif ($role->denies($permission, true)) {
+ $refused = true;
+ $icon = new Icon('times-circle', ['class' => 'refused']);
+ } elseif ($role->grants($permission, true, false)) {
+ $granted = true;
+ $icon = new Icon('check-circle', ['class' => 'granted']);
+ }
+
+ $connector = null;
+ if ($role->getParent() !== null) {
+ $connector = HtmlElement::create('li', ['class' => ['connector', $class]]);
+ if ($setInitiator) {
+ $setInitiator = false;
+ $connector->getAttributes()->add('class', 'initiator');
+ }
+
+ $path->prependHtml($connector);
+ }
+
+ $path->prependHtml(new HtmlElement('li', Attributes::create([
+ 'class' => ['role', $class],
+ 'title' => $role->getName()
+ ]), new Link([$icon, $role->getName()], Url::fromPath('role/edit', ['role' => $role->getName()]))));
+
+ if ($refused) {
+ $setInitiator = $class !== 'refused';
+ $class = 'refused';
+ } elseif ($granted) {
+ $setInitiator = $class === null;
+ $class = $class ?: 'granted';
+ }
+ }
+
+ if ($vClass === null || $vClass === 'granted') {
+ $vClass = $class;
+ }
+
+ array_unshift($rolePaths, $path->prepend([
+ empty($rolePaths) ? null : HtmlElement::create('li', ['class' => ['vertical-line', $vClass]]),
+ new HtmlElement('li', Attributes::create(['class' => [
+ 'connector',
+ $class,
+ $setInitiator ? 'initiator' : null
+ ]]))
+ ]));
+ }
+
+ if (empty($rolePaths)) {
+ return [
+ empty($refusedBy) ? (empty($grantedBy) ? null : true) : false,
+ new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'inheritance-paths']),
+ $header->setTag('div')
+ )
+ ];
+ }
+
+ return [
+ empty($refusedBy) ? (empty($grantedBy) ? null : true) : false,
+ HtmlElement::create('details', [
+ 'class' => ['collapsible', 'inheritance-paths'],
+ 'data-no-persistence' => true,
+ 'open' => getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf'
+ ], [
+ $header->addAttributes(['class' => 'collapsible-control']),
+ $rolePaths
+ ])
+ ];
+ }
+
+ protected function auditRestriction($restriction)
+ {
+ $restrictedBy = [];
+ $restrictions = [];
+ foreach ($this->roles as $role) {
+ if ($role->isUnrestricted()) {
+ $restrictedBy = [];
+ $restrictions = [];
+ break;
+ }
+
+ foreach ($this->collectRestrictions($role, $restriction) as $role => $roleRestriction) {
+ $restrictedBy[] = $role;
+ $restrictions[] = $roleRestriction;
+ }
+ }
+
+ $header = new HtmlElement('summary');
+ if (! empty($restrictedBy)) {
+ $header->add([
+ new Icon('filter', ['class' => 'restricted']),
+ count($restrictedBy) > 2
+ ? sprintf(
+ tp(
+ 'Restricted by %s and %s as well as one other',
+ 'Restricted by %s and %s as well as %d others',
+ count($restrictedBy) - 2
+ ),
+ $restrictedBy[0]->getName(),
+ $restrictedBy[1]->getName(),
+ count($restrictedBy) - 2
+ )
+ : sprintf(
+ tp('Restricted by %s', 'Restricted by %s and %s', count($restrictedBy)),
+ ...array_map(function ($role) {
+ return $role->getName();
+ }, $restrictedBy)
+ )
+ ]);
+ } else {
+ $header->add([new Icon('filter'), t('Not restricted by any role')]);
+ }
+
+ $roles = [];
+ if (! empty($restrictions) && count($restrictions) > 1) {
+ list($combinedRestrictions, $combinedLinks) = $this->createRestrictionLinks($restriction, $restrictions);
+ $roles[] = HtmlElement::create('li', null, [
+ new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'flex-overflow']),
+ HtmlElement::create('span', [
+ 'class' => 'role',
+ 'title' => t('All roles combined')
+ ], join(' | ', array_map(function ($role) {
+ return $role->getName();
+ }, $restrictedBy))),
+ HtmlElement::create('code', ['class' => 'restriction'], $combinedRestrictions)
+ ),
+ $combinedLinks ? new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'previews']),
+ HtmlElement::create('em', null, t('Previews:')),
+ $combinedLinks
+ ) : null
+ ]);
+ }
+
+ foreach ($restrictedBy as $role) {
+ list($roleRestriction, $restrictionLinks) = $this->createRestrictionLinks(
+ $restriction,
+ [$role->getRestrictions($restriction)]
+ );
+
+ $roles[] = HtmlElement::create('li', null, [
+ new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'flex-overflow']),
+ new Link($role->getName(), Url::fromPath('role/edit', ['role' => $role->getName()]), [
+ 'class' => 'role',
+ 'title' => $role->getName()
+ ]),
+ HtmlElement::create('code', ['class' => 'restriction'], $roleRestriction)
+ ),
+ $restrictionLinks ? new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'previews']),
+ HtmlElement::create('em', null, t('Previews:')),
+ $restrictionLinks
+ ) : null
+ ]);
+ }
+
+ if (empty($roles)) {
+ return [
+ ! empty($restrictedBy),
+ new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'restrictions']),
+ $header->setTag('div')
+ )
+ ];
+ }
+
+ return [
+ ! empty($restrictedBy),
+ new HtmlElement(
+ 'details',
+ Attributes::create([
+ 'class' => ['collapsible', 'restrictions'],
+ 'data-no-persistence' => true,
+ 'open' => getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf'
+ ]),
+ $header->addAttributes(['class' => 'collapsible-control']),
+ new HtmlElement('ul', null, ...$roles)
+ )
+ ];
+ }
+
+ protected function assemble()
+ {
+ list($permissions, $restrictions) = RoleForm::collectProvidedPrivileges();
+ list($wildcardState, $wildcardAudit) = $this->auditPermission('*');
+ list($unrestrictedState, $unrestrictedAudit) = $this->auditPermission(self::UNRESTRICTED_PERMISSION);
+
+ $this->addHtml(new HtmlElement(
+ 'li',
+ null,
+ new HtmlElement(
+ 'details',
+ Attributes::create([
+ 'class' => ['collapsible', 'privilege-section'],
+ 'open' => ($wildcardState || $unrestrictedState) && getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf'
+ ]),
+ new HtmlElement(
+ 'summary',
+ Attributes::create(['class' => [
+ 'collapsible-control', // Helps JS, improves performance a bit
+ ]]),
+ new HtmlElement('span', null, Text::create(t('Administrative Privileges'))),
+ HtmlElement::create(
+ 'span',
+ ['class' => 'audit-preview'],
+ $wildcardState || $unrestrictedState
+ ? new Icon('check-circle', ['class' => 'granted'])
+ : null
+ ),
+ new Icon('angles-down', ['class' => 'collapse-icon']),
+ new Icon('angles-left', ['class' => 'expand-icon'])
+ ),
+ new HtmlElement(
+ 'ol',
+ Attributes::create(['class' => 'privilege-list']),
+ new HtmlElement(
+ 'li',
+ null,
+ HtmlElement::create('p', ['class' => 'privilege-label'], t('Administrative Access')),
+ HtmlElement::create('div', ['class' => 'spacer']),
+ $wildcardAudit
+ ),
+ new HtmlElement(
+ 'li',
+ null,
+ HtmlElement::create('p', ['class' => 'privilege-label'], t('Unrestricted Access')),
+ HtmlElement::create('div', ['class' => 'spacer']),
+ $unrestrictedAudit
+ )
+ )
+ )
+ ));
+
+ $privilegeSources = array_unique(array_merge(array_keys($permissions), array_keys($restrictions)));
+ foreach ($privilegeSources as $source) {
+ $anythingGranted = false;
+ $anythingRefused = false;
+ $anythingRestricted = false;
+
+ $permissionList = new HtmlElement('ol', Attributes::create(['class' => 'privilege-list']));
+ foreach (isset($permissions[$source]) ? $permissions[$source] : [] as $permission => $metaData) {
+ list($permissionState, $permissionAudit) = $this->auditPermission($permission);
+ if ($permissionState !== null) {
+ if ($permissionState) {
+ $anythingGranted = true;
+ } else {
+ $anythingRefused = true;
+ }
+ }
+
+ $permissionList->addHtml(new HtmlElement(
+ 'li',
+ null,
+ HtmlElement::create(
+ 'p',
+ ['class' => 'privilege-label'],
+ isset($metaData['label'])
+ ? $metaData['label']
+ : array_map(function ($segment) {
+ return $segment[0] === '/' ? [
+ // Adds a zero-width char after each slash to help browsers break onto newlines
+ new HtmlString('/&#8203;'),
+ HtmlElement::create('span', ['class' => 'no-wrap'], substr($segment, 1))
+ ] : HtmlElement::create('em', null, $segment);
+ }, preg_split(
+ '~(/[^/]+)~',
+ $permission,
+ -1,
+ PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
+ ))
+ ),
+ new HtmlElement('div', Attributes::create(['class' => 'spacer'])),
+ $permissionAudit
+ ));
+ }
+
+ $restrictionList = new HtmlElement('ol', Attributes::create(['class' => 'privilege-list']));
+ foreach (isset($restrictions[$source]) ? $restrictions[$source] : [] as $restriction => $metaData) {
+ list($restrictionState, $restrictionAudit) = $this->auditRestriction($restriction);
+ if ($restrictionState) {
+ $anythingRestricted = true;
+ }
+
+ $restrictionList->addHtml(new HtmlElement(
+ 'li',
+ null,
+ HtmlElement::create(
+ 'p',
+ ['class' => 'privilege-label'],
+ isset($metaData['label'])
+ ? $metaData['label']
+ : array_map(function ($segment) {
+ return $segment[0] === '/' ? [
+ // Adds a zero-width char after each slash to help browsers break onto newlines
+ new HtmlString('/&#8203;'),
+ HtmlElement::create('span', ['class' => 'no-wrap'], substr($segment, 1))
+ ] : HtmlElement::create('em', null, $segment);
+ }, preg_split(
+ '~(/[^/]+)~',
+ $restriction,
+ -1,
+ PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY
+ ))
+ ),
+ new HtmlElement('div', Attributes::create(['class' => 'spacer'])),
+ $restrictionAudit
+ ));
+ }
+
+ if ($source === 'application') {
+ $label = 'Icinga Web 2';
+ } else {
+ $label = [$source, ' ', HtmlElement::create('em', null, t('Module'))];
+ }
+
+ $this->addHtml(new HtmlElement(
+ 'li',
+ null,
+ HtmlElement::create('details', [
+ 'class' => ['collapsible', 'privilege-section'],
+ 'open' => ($anythingGranted || $anythingRefused || $anythingRestricted)
+ && getenv('ICINGAWEB_EXPORT_FORMAT') === 'pdf'
+ ], [
+ new HtmlElement(
+ 'summary',
+ Attributes::create(['class' => [
+ 'collapsible-control', // Helps JS, improves performance a bit
+ ]]),
+ HtmlElement::create('span', null, $label),
+ HtmlElement::create('span', ['class' => 'audit-preview'], [
+ $anythingGranted ? new Icon('check-circle', ['class' => 'granted']) : null,
+ $anythingRefused ? new Icon('times-circle', ['class' => 'refused']) : null,
+ $anythingRestricted ? new Icon('filter', ['class' => 'restricted']) : null
+ ]),
+ new Icon('angles-down', ['class' => 'collapse-icon']),
+ new Icon('angles-left', ['class' => 'expand-icon'])
+ ),
+ $permissionList->isEmpty() ? null : [
+ HtmlElement::create('h4', null, t('Permissions')),
+ $permissionList
+ ],
+ $restrictionList->isEmpty() ? null : [
+ HtmlElement::create('h4', null, t('Restrictions')),
+ $restrictionList
+ ]
+ ])
+ ));
+ }
+ }
+
+ private function collectRestrictions(Role $role, $restrictionName)
+ {
+ do {
+ $restriction = $role->getRestrictions($restrictionName);
+ if ($restriction) {
+ yield $role => $restriction;
+ }
+ } while (($role = $role->getParent()) !== null);
+ }
+
+ private function createRestrictionLinks($restrictionName, array $restrictions)
+ {
+ // TODO: Remove this hardcoded mess. Do this based on the restriction's meta data
+ switch ($restrictionName) {
+ case 'icingadb/filter/objects':
+ $filterString = join('|', $restrictions);
+ $list = new HtmlElement(
+ 'ul',
+ Attributes::create(['class' => 'links']),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/hosts',
+ Url::fromPath('icingadb/hosts')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/services',
+ Url::fromPath('icingadb/services')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/hostgroups',
+ Url::fromPath('icingadb/hostgroups')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/servicegroups',
+ Url::fromPath('icingadb/servicegroups')->setQueryString($filterString)
+ ))
+ );
+
+ break;
+ case 'icingadb/filter/hosts':
+ $filterString = join('|', $restrictions);
+ $list = new HtmlElement(
+ 'ul',
+ Attributes::create(['class' => 'links']),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/hosts',
+ Url::fromPath('icingadb/hosts')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/services',
+ Url::fromPath('icingadb/services')->setQueryString($filterString)
+ ))
+ );
+
+ break;
+ case 'icingadb/filter/services':
+ $filterString = join('|', $restrictions);
+ $list = new HtmlElement(
+ 'ul',
+ Attributes::create(['class' => 'links']),
+ new HtmlElement('li', null, new Link(
+ 'icingadb/services',
+ Url::fromPath('icingadb/services')->setQueryString($filterString)
+ ))
+ );
+
+ break;
+ case 'monitoring/filter/objects':
+ $filterString = join('|', $restrictions);
+ $list = new HtmlElement(
+ 'ul',
+ Attributes::create(['class' => 'links']),
+ new HtmlElement('li', null, new Link(
+ 'monitoring/list/hosts',
+ Url::fromPath('monitoring/list/hosts')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'monitoring/list/services',
+ Url::fromPath('monitoring/list/services')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'monitoring/list/hostgroups',
+ Url::fromPath('monitoring/list/hostgroups')->setQueryString($filterString)
+ )),
+ new HtmlElement('li', null, new Link(
+ 'monitoring/list/servicegroups',
+ Url::fromPath('monitoring/list/servicegroups')->setQueryString($filterString)
+ ))
+ );
+
+ break;
+ case 'application/share/users':
+ $filter = Filter::any();
+ foreach ($restrictions as $roleRestriction) {
+ $userNames = StringHelper::trimSplit($roleRestriction);
+ foreach ($userNames as $userName) {
+ $filter->add(Filter::equal('user_name', $userName));
+ }
+ }
+
+ $filterString = QueryString::render($filter);
+ $list = new HtmlElement(
+ 'ul',
+ Attributes::create(['class' => 'links']),
+ new HtmlElement('li', null, new Link(
+ 'user/list',
+ Url::fromPath('user/list')->setQueryString($filterString)
+ ))
+ );
+
+ break;
+ case 'application/share/groups':
+ $filter = Filter::any();
+ foreach ($restrictions as $roleRestriction) {
+ $groupNames = StringHelper::trimSplit($roleRestriction);
+ foreach ($groupNames as $groupName) {
+ $filter->add(Filter::equal('group_name', $groupName));
+ }
+ }
+
+ $filterString = QueryString::render($filter);
+ $list = new HtmlElement(
+ 'ul',
+ Attributes::create(['class' => 'links']),
+ new HtmlElement('li', null, new Link(
+ 'group/list',
+ Url::fromPath('group/list')->setQueryString($filterString)
+ ))
+ );
+
+ break;
+ default:
+ $filterString = join(', ', $restrictions);
+ $list = null;
+ }
+
+ return [$filterString, $list];
+ }
+}
diff --git a/library/Icinga/Web/View/helpers/format.php b/library/Icinga/Web/View/helpers/format.php
new file mode 100644
index 0000000..4008583
--- /dev/null
+++ b/library/Icinga/Web/View/helpers/format.php
@@ -0,0 +1,72 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\View;
+
+use Icinga\Date\DateFormatter;
+use Icinga\Util\Format;
+
+$this->addHelperFunction('format', function () {
+ return Format::getInstance();
+});
+
+$this->addHelperFunction('formatDate', function ($date) {
+ if (! $date) {
+ return '';
+ }
+ return DateFormatter::formatDate($date);
+});
+
+$this->addHelperFunction('formatDateTime', function ($dateTime) {
+ if (! $dateTime) {
+ return '';
+ }
+ return DateFormatter::formatDateTime($dateTime);
+});
+
+$this->addHelperFunction('formatDuration', function ($seconds) {
+ if (! $seconds) {
+ return '';
+ }
+ return DateFormatter::formatDuration($seconds);
+});
+
+$this->addHelperFunction('formatTime', function ($time) {
+ if (! $time) {
+ return '';
+ }
+ return DateFormatter::formatTime($time);
+});
+
+$this->addHelperFunction('timeAgo', function ($time, $timeOnly = false, $requireTime = false) {
+ if (! $time) {
+ return '';
+ }
+ return sprintf(
+ '<span class="relative-time time-ago" title="%s">%s</span>',
+ DateFormatter::formatDateTime($time),
+ DateFormatter::timeAgo($time, $timeOnly, $requireTime)
+ );
+});
+
+$this->addHelperFunction('timeSince', function ($time, $timeOnly = false, $requireTime = false) {
+ if (! $time) {
+ return '';
+ }
+ return sprintf(
+ '<span class="relative-time time-since" title="%s">%s</span>',
+ DateFormatter::formatDateTime($time),
+ DateFormatter::timeSince($time, $timeOnly, $requireTime)
+ );
+});
+
+$this->addHelperFunction('timeUntil', function ($time, $timeOnly = false, $requireTime = false) {
+ if (! $time) {
+ return '';
+ }
+ return sprintf(
+ '<span class="relative-time time-until" title="%s">%s</span>',
+ DateFormatter::formatDateTime($time),
+ DateFormatter::timeUntil($time, $timeOnly, $requireTime)
+ );
+});
diff --git a/library/Icinga/Web/View/helpers/generic.php b/library/Icinga/Web/View/helpers/generic.php
new file mode 100644
index 0000000..bfd3f86
--- /dev/null
+++ b/library/Icinga/Web/View/helpers/generic.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\View;
+
+use Icinga\Authentication\Auth;
+use Icinga\Web\Widget;
+
+$this->addHelperFunction('auth', function () {
+ return Auth::getInstance();
+});
+
+$this->addHelperFunction('widget', function ($name, $options = null) {
+ return Widget::create($name, $options);
+});
diff --git a/library/Icinga/Web/View/helpers/string.php b/library/Icinga/Web/View/helpers/string.php
new file mode 100644
index 0000000..b3f667b
--- /dev/null
+++ b/library/Icinga/Web/View/helpers/string.php
@@ -0,0 +1,36 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\View;
+
+use Icinga\Util\StringHelper;
+use Icinga\Web\Helper\Markdown;
+
+$this->addHelperFunction('ellipsis', function ($string, $maxLength, $ellipsis = '...') {
+ return StringHelper::ellipsis($string, $maxLength, $ellipsis);
+});
+
+$this->addHelperFunction('nl2br', function ($string) {
+ return nl2br(str_replace(array('\r\n', '\r', '\n'), '<br>', $string), false);
+});
+
+$this->addHelperFunction('markdown', function ($content, $containerAttribs = null) {
+ if (! isset($containerAttribs['class'])) {
+ $containerAttribs['class'] = 'markdown';
+ } else {
+ $containerAttribs['class'] .= ' markdown';
+ }
+
+ return '<section' . $this->propertiesToString($containerAttribs) . '>' . Markdown::text($content) . '</section>';
+});
+
+$this->addHelperFunction('markdownLine', function ($content, $containerAttribs = null) {
+ if (! isset($containerAttribs['class'])) {
+ $containerAttribs['class'] = 'markdown inline';
+ } else {
+ $containerAttribs['class'] .= ' markdown inline';
+ }
+
+ return '<section' . $this->propertiesToString($containerAttribs) . '>' .
+ Markdown::line($content) . '</section>';
+});
diff --git a/library/Icinga/Web/View/helpers/url.php b/library/Icinga/Web/View/helpers/url.php
new file mode 100644
index 0000000..277c237
--- /dev/null
+++ b/library/Icinga/Web/View/helpers/url.php
@@ -0,0 +1,158 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\View;
+
+use Icinga\Web\Url;
+use Icinga\Exception\ProgrammingError;
+
+$view = $this;
+
+$this->addHelperFunction('href', function ($path = null, $params = null) use ($view) {
+ return $view->url($path, $params);
+});
+
+$this->addHelperFunction('url', function ($path = null, $params = null) {
+ if ($path === null) {
+ $url = Url::fromRequest();
+ } elseif ($path instanceof Url) {
+ $url = $path;
+ } else {
+ $url = Url::fromPath($path);
+ }
+
+ if ($params !== null) {
+ if ($url === $path) {
+ $url = clone $url;
+ }
+
+ $url->overwriteParams($params);
+ }
+
+ return $url;
+});
+
+$this->addHelperFunction(
+ 'qlink',
+ function ($title, $url, $params = null, $properties = null, $escape = true) use ($view) {
+ $icon = '';
+ if ($properties) {
+ if (array_key_exists('title', $properties) && !array_key_exists('aria-label', $properties)) {
+ $properties['aria-label'] = $properties['title'];
+ }
+
+ if (array_key_exists('icon', $properties)) {
+ $icon = $view->icon($properties['icon']);
+ unset($properties['icon']);
+ }
+
+ if (array_key_exists('img', $properties)) {
+ $icon = $view->img($properties['img']);
+ unset($properties['img']);
+ }
+ }
+
+ return sprintf(
+ '<a href="%s"%s>%s</a>',
+ $view->url($url, $params),
+ $view->propertiesToString($properties),
+ $icon . ($escape ? $view->escape($title) : $title)
+ );
+ }
+);
+
+$this->addHelperFunction('img', function ($url, $params = null, array $properties = array()) use ($view) {
+ if (! array_key_exists('alt', $properties)) {
+ $properties['alt'] = '';
+ }
+
+ $ariaHidden = array_key_exists('aria-hidden', $properties) ? $properties['aria-hidden'] : null;
+ if (array_key_exists('title', $properties)) {
+ if (! array_key_exists('aria-label', $properties) && $ariaHidden !== 'true') {
+ $properties['aria-label'] = $properties['title'];
+ }
+ } elseif ($ariaHidden === null) {
+ $properties['aria-hidden'] = 'true';
+ }
+
+ return sprintf(
+ '<img src="%s"%s />',
+ $view->escape($view->url($url, $params)->getAbsoluteUrl()),
+ $view->propertiesToString($properties)
+ );
+});
+
+$this->addHelperFunction('icon', function ($img, $title = null, array $properties = array()) use ($view) {
+ if (strpos($img, '.') !== false) {
+ if (array_key_exists('class', $properties)) {
+ $properties['class'] .= ' icon';
+ } else {
+ $properties['class'] = 'icon';
+ }
+ if (strpos($img, '/') === false) {
+ return $view->img('img/icons/' . $img, null, $properties);
+ } else {
+ return $view->img($img, null, $properties);
+ }
+ }
+
+ $ariaHidden = array_key_exists('aria-hidden', $properties) ? $properties['aria-hidden'] : null;
+ if ($title !== null) {
+ $properties['role'] = 'img';
+ $properties['title'] = $title;
+
+ if (! array_key_exists('aria-label', $properties) && $ariaHidden !== 'true') {
+ $properties['aria-label'] = $title;
+ }
+ } elseif ($ariaHidden === null) {
+ $properties['aria-hidden'] = 'true';
+ }
+
+ if (isset($properties['class'])) {
+ $properties['class'] .= ' icon-' . $img;
+ } else {
+ $properties['class'] = 'icon-' . $img;
+ }
+
+ return sprintf('<i %s></i>', $view->propertiesToString($properties));
+});
+
+$this->addHelperFunction('propertiesToString', function ($properties) use ($view) {
+ if (empty($properties)) {
+ return '';
+ }
+ $attributes = array();
+
+ foreach ($properties as $key => $val) {
+ if ($key === 'style' && is_array($val)) {
+ if (empty($val)) {
+ continue;
+ }
+ $parts = array();
+ foreach ($val as $k => $v) {
+ $parts[] = "$k: $v";
+ }
+ $val = implode('; ', $parts);
+ continue;
+ }
+
+ $attributes[] = $view->attributeToString($key, $val);
+ }
+ return ' ' . implode(' ', $attributes);
+});
+
+$this->addHelperFunction('attributeToString', function ($key, $value) use ($view) {
+ // TODO: Doublecheck this!
+ if (! preg_match('~^[a-zA-Z0-9-]+$~', $key)) {
+ throw new ProgrammingError(
+ 'Trying to set an invalid HTML attribute name: %s',
+ $key
+ );
+ }
+
+ return sprintf(
+ '%s="%s"',
+ $key,
+ $view->escape($value)
+ );
+});
diff --git a/library/Icinga/Web/Widget.php b/library/Icinga/Web/Widget.php
new file mode 100644
index 0000000..242138b
--- /dev/null
+++ b/library/Icinga/Web/Widget.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Web widgets make things easier for you!
+ *
+ * This class provides nothing but a static factory method for widget creation.
+ * Usually it will not be used directly as there are widget()-helpers available
+ * in your action controllers and view scripts.
+ *
+ * Usage example:
+ * <code>
+ * $tabs = Widget::create('tabs');
+ * </code>
+ *
+ * @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.com>
+ * @author Icinga-Web Team <info@icinga.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ */
+class Widget
+{
+ /**
+ * Create a new widget
+ *
+ * @param string $name Widget name
+ * @param array $options Widget constructor options
+ *
+ * @return Icinga\Web\Widget\AbstractWidget
+ */
+ public static function create($name, $options = array(), $module_name = null)
+ {
+ $class = 'Icinga\\Web\\Widget\\' . ucfirst($name);
+
+ if (! class_exists($class)) {
+ throw new ProgrammingError(
+ 'There is no such widget: %s',
+ $name
+ );
+ }
+
+ $widget = new $class($options, $module_name);
+ return $widget;
+ }
+}
diff --git a/library/Icinga/Web/Widget/AbstractWidget.php b/library/Icinga/Web/Widget/AbstractWidget.php
new file mode 100644
index 0000000..a3f5a35
--- /dev/null
+++ b/library/Icinga/Web/Widget/AbstractWidget.php
@@ -0,0 +1,120 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Exception\ProgrammingError;
+use Icinga\Application\Icinga;
+use Exception;
+
+/**
+ * Web widgets MUST extend this class
+ *
+ * AbstractWidget implements getters and setters for widget options stored in
+ * the protected options array. If you want to allow options for your own
+ * widget, you have to set a default value (may be null) for each single option
+ * in this array.
+ *
+ * Please have a look at the available widgets in this folder to get a better
+ * idea on what they should look like.
+ *
+ * @copyright Copyright (c) 2013 Icinga-Web Team <info@icinga.com>
+ * @author Icinga-Web Team <info@icinga.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ */
+abstract class AbstractWidget
+{
+ /**
+ * If you are going to access the current view with the view() function,
+ * its instance is stored here for performance reasons.
+ *
+ * @var Zend_View_Abstract
+ */
+ protected static $view;
+
+ // TODO: Should we kick this?
+ protected $properties = array();
+
+ /**
+ * Getter for widget properties
+ *
+ * @param string $key The option you're interested in
+ *
+ * @throws ProgrammingError for unknown property name
+ *
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (array_key_exists($key, $this->properties)) {
+ return $this->properties[$key];
+ }
+
+ throw new ProgrammingError(
+ 'Trying to get invalid "%s" property for %s',
+ $key,
+ get_class($this)
+ );
+ }
+
+ /**
+ * Setter for widget properties
+ *
+ * @param string $key The option you want to set
+ * @param string $val The new value going to be assigned to this option
+ *
+ * @throws ProgrammingError for unknown property name
+ *
+ * @return mixed
+ */
+ public function __set($key, $val)
+ {
+ if (array_key_exists($key, $this->properties)) {
+ $this->properties[$key] = $val;
+ return;
+ }
+
+ throw new ProgrammingError(
+ 'Trying to set invalid "%s" property in %s. Allowed are: %s',
+ $key,
+ get_class($this),
+ empty($this->properties)
+ ? 'none'
+ : implode(', ', array_keys($this->properties))
+ );
+ }
+
+ abstract public function render();
+
+ /**
+ * Access the current view
+ *
+ * Will instantiate a new one if none exists
+ * // TODO: App->getView
+ *
+ * @return Zend_View_Abstract
+ */
+ protected function view()
+ {
+ if (self::$view === null) {
+ self::$view = Icinga::app()->getViewRenderer()->view;
+ }
+
+ return self::$view;
+ }
+
+ /**
+ * Cast this widget to a string. Will call your render() function
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ $html = $this->render($this->view());
+ } catch (Exception $e) {
+ return htmlspecialchars($e->getMessage());
+ }
+ return (string) $html;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Announcements.php b/library/Icinga/Web/Widget/Announcements.php
new file mode 100644
index 0000000..5944eb4
--- /dev/null
+++ b/library/Icinga/Web/Widget/Announcements.php
@@ -0,0 +1,55 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Application\Icinga;
+use Icinga\Data\Filter\Filter;
+use Icinga\Forms\Announcement\AcknowledgeAnnouncementForm;
+use Icinga\Web\Announcement\AnnouncementCookie;
+use Icinga\Web\Announcement\AnnouncementIniRepository;
+use Icinga\Web\Helper\Markdown;
+
+/**
+ * Render announcements
+ */
+class Announcements extends AbstractWidget
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ $repo = new AnnouncementIniRepository();
+ $etag = $repo->getEtag();
+ $cookie = new AnnouncementCookie();
+ if ($cookie->getEtag() !== $etag) {
+ $cookie->setEtag($etag);
+ $cookie->setNextActive($repo->findNextActive());
+ Icinga::app()->getResponse()->setCookie($cookie);
+ }
+ $acked = array();
+ foreach ($cookie->getAcknowledged() as $hash) {
+ $acked[] = Filter::expression('hash', '!=', $hash);
+ }
+ $acked = Filter::matchAll($acked);
+ $announcements = $repo->findActive();
+ $announcements->applyFilter($acked);
+ if ($announcements->hasResult()) {
+ $html = '<ul role="alert">';
+ foreach ($announcements as $announcement) {
+ $ackForm = new AcknowledgeAnnouncementForm();
+ $ackForm->populate(array('hash' => $announcement->hash));
+ $html .= '<li><div class="message">'
+ . Markdown::text($announcement->message)
+ . '</div>'
+ . $ackForm
+ . '</li>';
+ }
+ $html .= '</ul>';
+ return $html;
+ }
+ // Force container update on XHR
+ return '<div style="display: none;"></div>';
+ }
+}
diff --git a/library/Icinga/Web/Widget/ApplicationStateMessages.php b/library/Icinga/Web/Widget/ApplicationStateMessages.php
new file mode 100644
index 0000000..e8f5e72
--- /dev/null
+++ b/library/Icinga/Web/Widget/ApplicationStateMessages.php
@@ -0,0 +1,74 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Application\Config;
+use Icinga\Application\Hook\ApplicationStateHook;
+use Icinga\Authentication\Auth;
+use Icinga\Forms\AcknowledgeApplicationStateMessageForm;
+use Icinga\Web\ApplicationStateCookie;
+use Icinga\Web\Helper\Markdown;
+
+/**
+ * Render application state messages
+ */
+class ApplicationStateMessages extends AbstractWidget
+{
+ protected function getMessages()
+ {
+ $cookie = new ApplicationStateCookie();
+
+ $acked = array_flip($cookie->getAcknowledgedMessages());
+ $messages = ApplicationStateHook::getAllMessages();
+
+ $active = array_diff_key($messages, $acked);
+
+ return $active;
+ }
+
+ public function render()
+ {
+ $enabled = Auth::getInstance()
+ ->getUser()
+ ->getPreferences()
+ ->getValue('icingaweb', 'show_application_state_messages', 'system');
+
+ if ($enabled === 'system') {
+ $enabled = Config::app()->get('global', 'show_application_state_messages', true);
+ }
+
+ if (! (bool) $enabled) {
+ return '<div style="display: none;"></div>';
+ }
+
+ $active = $this->getMessages();
+
+ if (empty($active)) {
+ // Force container update on XHR
+ return '<div style="display: none;"></div>';
+ }
+
+ $html = '<div>';
+
+ reset($active);
+
+ $id = key($active);
+ $spec = current($active);
+ $message = array_pop($spec); // We don't use state and timestamp here
+
+
+ $ackForm = new AcknowledgeApplicationStateMessageForm();
+ $ackForm->populate(['id' => $id]);
+
+ $html .= '<section class="markdown">';
+ $html .= Markdown::text($message);
+ $html .= '</section>';
+
+ $html .= $ackForm;
+
+ $html .= '</div>';
+
+ return $html;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
new file mode 100644
index 0000000..ed1e377
--- /dev/null
+++ b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
@@ -0,0 +1,373 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Chart;
+
+use DateInterval;
+use DateTime;
+use Icinga\Util\Color;
+use Icinga\Web\Widget\AbstractWidget;
+
+/**
+ * Display a colored grid that visualizes a set of values for each day
+ * on a given time-frame.
+ */
+class HistoryColorGrid extends AbstractWidget
+{
+ const CAL_GROW_INTO_PAST = 'past';
+ const CAL_GROW_INTO_PRESENT = 'present';
+
+ const ORIENTATION_VERTICAL = 'vertical';
+ const ORIENTATION_HORIZONTAL = 'horizontal';
+
+ public $weekFlow = self::CAL_GROW_INTO_PAST;
+ public $orientation = self::ORIENTATION_VERTICAL;
+ public $weekStartMonday = true;
+
+ private $maxValue = 1;
+
+ private $start = null;
+ private $end = null;
+ private $data = array();
+ private $color;
+ public $opacity = 1.0;
+
+ public function __construct($color = '#51e551', $start = null, $end = null)
+ {
+ $this->setColor($color);
+ if (isset($start)) {
+ $this->start = $this->tsToDateStr($start);
+ }
+ if (isset($end)) {
+ $this->end = $this->tsToDateStr($end);
+ }
+ }
+
+ /**
+ * Set the displayed data-set
+ *
+ * @param $events array The history events to display as an array of arrays:
+ * value: The value to display
+ * caption: The caption on mouse-over
+ * url: The url to open on click.
+ */
+ public function setData(array $events)
+ {
+ $this->data = $events;
+ $start = time();
+ $end = time();
+ foreach ($this->data as $entry) {
+ $entry['value'] = intval($entry['value']);
+ }
+ foreach ($this->data as $date => $entry) {
+ $time = strtotime($date);
+ if ($entry['value'] > $this->maxValue) {
+ $this->maxValue = $entry['value'];
+ }
+ if ($time > $end) {
+ $end = $time;
+ }
+ if ($time < $start) {
+ $start = $time;
+ }
+ }
+ if (!isset($this->start)) {
+ $this->start = $this->tsToDateStr($start);
+ }
+ if (!isset($this->end)) {
+ $this->end = $this->tsToDateStr($end);
+ }
+ }
+
+ /**
+ * Set the used color.
+ *
+ * @param $color
+ */
+ public function setColor($color)
+ {
+ $this->color = $color;
+ }
+
+ /**
+ * Set the used opacity
+ *
+ * @param $opacity
+ */
+ public function setOpacity($opacity)
+ {
+ $this->opacity = $opacity;
+ }
+
+ /**
+ * Calculate the color to display for the given value.
+ *
+ * @param $value integer
+ *
+ * @return string The color-string to use for this entry.
+ */
+ private function calculateColor($value)
+ {
+ $saturation = $value / $this->maxValue;
+ return Color::changeSaturation($this->color, $saturation);
+ }
+
+ /**
+ * Render the html to display the given $day
+ *
+ * @param $day string The day to display YYYY-MM-DD
+ *
+ * @return string The rendered html
+ */
+ private function renderDay($day)
+ {
+ if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) {
+ $entry = $this->data[$day];
+ return '<a ' .
+ 'style="background-color: ' . $this->calculateColor($entry['value']) . ';'
+ . ' opacity: ' . $this->opacity . ';" ' .
+ 'aria-label="' . $entry['caption'] . '" ' .
+ 'title="' . $entry['caption'] . '" ' .
+ 'href="' . $entry['url'] . '" ' .
+ '></a>';
+ } else {
+ return '<span ' .
+ 'style="background-color: ' . $this->calculateColor(0) . '; opacity: ' . $this->opacity . ';" ' .
+ 'title="No entries for ' . $day . '" ' .
+ '></span>';
+ }
+ }
+
+ /**
+ * Render the grid with an horizontal alignment.
+ *
+ * @param array $grid The values returned from the createGrid function
+ *
+ * @return string The rendered html
+ */
+ private function renderHorizontal($grid)
+ {
+ $weeks = $grid['weeks'];
+ $months = $grid['months'];
+ $years = $grid['years'];
+ $html = '<table class="historycolorgrid">';
+ $html .= '<tr><th></th>';
+ $old = -1;
+ foreach ($months as $week => $month) {
+ if ($old !== $month) {
+ $old = $month;
+ $txt = $this->monthName($month, $years[$week]);
+ } else {
+ $txt = '';
+ }
+ $html .= '<th>' . $txt . '</th>';
+ }
+ $html .= '</tr>';
+ for ($i = 0; $i < 7; $i++) {
+ $html .= $this->renderWeekdayHorizontal($i, $weeks);
+ }
+ $html .= '</table>';
+ return $html;
+ }
+
+ /**
+ * @param $grid
+ *
+ * @return string
+ */
+ private function renderVertical($grid)
+ {
+ $years = $grid['years'];
+ $weeks = $grid['weeks'];
+ $months = $grid['months'];
+ $html = '<table class="historycolorgrid">';
+ $html .= '<tr>';
+ for ($i = 0; $i < 7; $i++) {
+ $html .= '<th>' . $this->weekdayName($this->weekStartMonday ? $i + 1 : $i) . "</th>";
+ }
+ $html .= '</tr>';
+ $old = -1;
+ foreach ($weeks as $index => $week) {
+ for ($i = 0; $i < 7; $i++) {
+ if (array_key_exists($i, $week)) {
+ $html .= '<td>' . $this->renderDay($week[$i]) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ }
+ if ($old !== $months[$index]) {
+ $old = $months[$index];
+ $txt = $this->monthName($old, $years[$index]);
+ } else {
+ $txt = '';
+ }
+ $html .= '<td class="weekday">' . $txt . '</td></tr>';
+ }
+ $html .= '</table>';
+ return $html;
+ }
+
+ /**
+ * Render the row for the given weekday.
+ *
+ * @param integer $weekday The day to render (0-6)
+ * @param array $weeks The weeks
+ *
+ * @return string The formatted table-row
+ */
+ private function renderWeekdayHorizontal($weekday, &$weeks)
+ {
+ $html = '<tr><td class="weekday">'
+ . $this->weekdayName($this->weekStartMonday ? $weekday + 1 : $weekday)
+ . '</td>';
+ foreach ($weeks as $week) {
+ if (array_key_exists($weekday, $week)) {
+ $html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ }
+ $html .= '</tr>';
+ return $html;
+ }
+
+
+
+ /**
+ * @return array
+ */
+ private function createGrid()
+ {
+ $weeks = array(array());
+ $week = 0;
+ $months = array();
+ $years = array();
+ $start = strtotime($this->start);
+ $year = intval(date('Y', $start));
+ $month = intval(date('n', $start));
+ $day = intval(date('j', $start));
+ $weekday = intval(date('w', $start));
+ if ($this->weekStartMonday) {
+ // 0 => monday, 6 => sunday
+ $weekday = $weekday === 0 ? 6 : $weekday - 1;
+ }
+
+ $date = $this->toDateStr($day, $month, $year);
+ $weeks[0][$weekday] = $date;
+ $years[0] = $year;
+ $months[0] = $month;
+ while ($date !== $this->end) {
+ $day++;
+ $weekday++;
+ if ($weekday > 6) {
+ $weekday = 0;
+ $weeks[] = array();
+ // PRESENT => The last day of week determines the month
+ if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
+ $months[$week] = $month;
+ $years[$week] = $year;
+ }
+ $week++;
+ }
+ if ($day > date('t', mktime(0, 0, 0, $month, 1, $year))) {
+ $month++;
+ if ($month > 12) {
+ $year++;
+ $month = 1;
+ }
+ $day = 1;
+ }
+ if ($weekday === 0) {
+ // PAST => The first day of each week determines the month
+ if ($this->weekFlow === self::CAL_GROW_INTO_PAST) {
+ $months[$week] = $month;
+ $years[$week] = $year;
+ }
+ }
+ $date = $this->toDateStr($day, $month, $year);
+ $weeks[$week][$weekday] = $date;
+ };
+ $years[$week] = $year;
+ $months[$week] = $month;
+ if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
+ return array(
+ 'weeks' => array_reverse($weeks),
+ 'months' => array_reverse($months),
+ 'years' => array_reverse($years)
+ );
+ }
+ return array(
+ 'weeks' => $weeks,
+ 'months' => $months,
+ 'years' => $years
+ );
+ }
+
+ /**
+ * Get the localized month-name for the given month
+ *
+ * @param integer $month The month-number
+ *
+ * @return string The
+ */
+ private function monthName($month, $year)
+ {
+ // TODO: find a way to render years without messing up the layout
+ $dt = new DateTime($year . '-' . $month . '-01');
+ return $dt->format('M');
+ }
+
+ /**
+ * @param $weekday
+ *
+ * @return string
+ */
+ private function weekdayName($weekday)
+ {
+ $sun = new DateTime('last Sunday');
+ $interval = new DateInterval('P' . $weekday . 'D');
+ $sun->add($interval);
+ return substr($sun->format('D'), 0, 2);
+ }
+
+ /**
+ *
+ *
+ * @param $timestamp
+ *
+ * @return bool|string
+ */
+ private function tsToDateStr($timestamp)
+ {
+ return date('Y-m-d', $timestamp);
+ }
+
+ /**
+ * @param $day
+ * @param $mon
+ * @param $year
+ *
+ * @return string
+ */
+ private function toDateStr($day, $mon, $year)
+ {
+ $day = $day > 9 ? (string)$day : '0' . (string)$day;
+ $mon = $mon > 9 ? (string)$mon : '0' . (string)$mon;
+ return $year . '-' . $mon . '-' . $day;
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ if (empty($this->data)) {
+ return '<div>No entries</div>';
+ }
+ $grid = $this->createGrid();
+ if ($this->orientation === self::ORIENTATION_HORIZONTAL) {
+ return $this->renderHorizontal($grid);
+ }
+ return $this->renderVertical($grid);
+ }
+}
diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php
new file mode 100644
index 0000000..21b4ca4
--- /dev/null
+++ b/library/Icinga/Web/Widget/Chart/InlinePie.php
@@ -0,0 +1,257 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Chart;
+
+use Icinga\Chart\PieChart;
+use Icinga\Module\Monitoring\Plugin\PerfdataSet;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Widget\AbstractWidget;
+use Icinga\Web\Url;
+use Icinga\Util\Format;
+use Icinga\Application\Logger;
+use Icinga\Exception\IcingaException;
+use stdClass;
+
+/**
+ * A SVG-PieChart intended to be displayed as a small icon next to labels, to offer a better visualization of the
+ * shown data
+ *
+ * NOTE: When InlinePies are shown in a dynamically loaded view, like the side-bar or in the dashboard, the SVGs will
+ * be replaced with a jQuery-Sparkline to save resources @see loader.js
+ *
+ * @package Icinga\Web\Widget\Chart
+ */
+class InlinePie extends AbstractWidget
+{
+ const NUMBER_FORMAT_NONE = 'none';
+ const NUMBER_FORMAT_TIME = 'time';
+ const NUMBER_FORMAT_BYTES = 'bytes';
+ const NUMBER_FORMAT_RATIO = 'ratio';
+
+ public static $colorsHostStates = array(
+ '#44bb77', // up
+ '#ff99aa', // down
+ '#cc77ff', // unreachable
+ '#77aaff' // pending
+ );
+
+ public static $colorsHostStatesHandledUnhandled = array(
+ '#44bb77', // up
+ '#44bb77',
+ '#ff99aa', // down
+ '#ff5566',
+ '#cc77ff', // unreachable
+ '#aa44ff',
+ '#77aaff', // pending
+ '#77aaff'
+ );
+
+ public static $colorsServiceStates = array(
+ '#44bb77', // Ok
+ '#ffaa44', // Warning
+ '#ff99aa', // Critical
+ '#aa44ff', // Unknown
+ '#77aaff' // Pending
+ );
+
+ public static $colorsServiceStatesHandleUnhandled = array(
+ '#44bb77', // Ok
+ '#44bb77',
+ '#ffaa44', // Warning
+ '#ffcc66',
+ '#ff99aa', // Critical
+ '#ff5566',
+ '#cc77ff', // Unknown
+ '#aa44ff',
+ '#77aaff', // Pending
+ '#77aaff'
+ );
+
+ /**
+ * The template string used for rendering this widget
+ *
+ * @var string
+ */
+ private $template = '<div class="inline-pie {class}">{svg}</div>';
+
+ /**
+ * The colors used to display the slices of this pie-chart.
+ *
+ * @var array
+ */
+ private $colors = array('#049BAF', '#ffaa44', '#ff5566', '#ddccdd');
+
+ /**
+ * The title of the chart
+ *
+ * @var string
+ */
+ private $title;
+
+ /**
+ * @var int
+ */
+ private $size = 16;
+
+ /**
+ * The data displayed by the pie-chart
+ *
+ * @var array
+ */
+ private $data;
+
+ /**
+ * @var
+ */
+ private $class = '';
+
+ /**
+ * Set the data to be displayed.
+ *
+ * @param $data array
+ *
+ * @return $this
+ */
+ public function setData(array $data)
+ {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ /**
+ * Set the size of the inline pie
+ *
+ * @param int $size Sets both, the height and width
+ *
+ * @return $this
+ */
+ public function setSize($size = null)
+ {
+ $this->size = $size;
+
+ return $this;
+ }
+
+ /**
+ * Set the class to define the
+ *
+ * @param $class
+ *
+ * @return $this
+ */
+ public function setSparklineClass($class)
+ {
+ $this->class = $class;
+
+ return $this;
+ }
+
+ /**
+ * Set the colors used by the slices of the pie chart.
+ *
+ * @param array $colors
+ *
+ * @return $this
+ */
+ public function setColors(array $colors = null)
+ {
+ $this->colors = $colors;
+
+ return $this;
+ }
+
+ /**
+ * Set the title of the displayed Data
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $this->view()->escape($title);
+
+ return $this;
+ }
+
+ /**
+ * Create a new InlinePie
+ *
+ * @param array $data The data displayed by the slices
+ * @param string $title The title of this Pie
+ * @param array $colors An array of RGB-Color values to use
+ */
+ public function __construct(array $data, $title, $colors = null)
+ {
+ $this->setTitle($title);
+
+ if (array_key_exists('data', $data)) {
+ $this->data = $data['data'];
+ if (array_key_exists('colors', $data)) {
+ $this->colors = $data['colors'];
+ }
+ } else {
+ $this->setData($data);
+ }
+
+ if (isset($colors)) {
+ $this->setColors($colors);
+ } else {
+ $this->setColors($this->colors);
+ }
+ }
+
+ /**
+ * Renders this widget via the given view and returns the
+ * HTML as a string
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $pie = new PieChart();
+ $pie->alignTopLeft();
+ $pie->disableLegend();
+ $pie->drawPie([
+ 'data' => $this->data,
+ 'colors' => $this->colors
+ ]);
+
+ if ($this->view()->layout()->getLayout() === 'pdf') {
+ try {
+ $png = $pie->toPng($this->size, $this->size);
+ return '<img class="inlinepie" src="data:image/png;base64,' . base64_encode($png) . '" />';
+ } catch (IcingaException $_) {
+ return '';
+ }
+ }
+
+ $pie->title = $this->title;
+ $pie->description = $this->title;
+
+ $template = $this->template;
+ $template = str_replace('{class}', $this->class, $template);
+ $template = str_replace('{svg}', $pie->render(), $template);
+
+ return $template;
+ }
+
+ public static function createFromStateSummary(stdClass $states, $title, array $colors)
+ {
+ $handledUnhandledStates = [];
+ foreach ($states as $key => $value) {
+ if (StringHelper::endsWith($key, '_handled') || StringHelper::endsWith($key, '_unhandled')) {
+ $handledUnhandledStates[$key] = $value;
+ }
+ }
+
+ $chart = new self(array_values($handledUnhandledStates), $title, $colors);
+
+ return $chart
+ ->setSize(50)
+ ->setTitle('')
+ ->setSparklineClass('sparkline-multi');
+ }
+}
diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php
new file mode 100644
index 0000000..5a8796d
--- /dev/null
+++ b/library/Icinga/Web/Widget/Dashboard.php
@@ -0,0 +1,475 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Application\Config;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Legacy\DashboardConfig;
+use Icinga\User;
+use Icinga\Web\Navigation\DashboardPane;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Dashboard\Dashlet as DashboardDashlet;
+use Icinga\Web\Widget\Dashboard\Pane;
+
+/**
+ * Dashboards display multiple views on a single page
+ *
+ * The terminology is as follows:
+ * - Dashlet: A single view showing a specific url
+ * - Pane: Aggregates one or more dashlets on one page, displays its title as a tab
+ * - Dashboard: Shows all panes
+ *
+ */
+class Dashboard extends AbstractWidget
+{
+ /**
+ * An array containing all panes of this dashboard
+ *
+ * @var array
+ */
+ private $panes = array();
+
+ /**
+ * The @see Icinga\Web\Widget\Tabs object for displaying displayable panes
+ *
+ * @var Tabs
+ */
+ protected $tabs;
+
+ /**
+ * The parameter that will be added to identify panes
+ *
+ * @var string
+ */
+ private $tabParam = 'pane';
+
+ /**
+ * @var User
+ */
+ private $user;
+
+ /**
+ * Set the given tab name as active.
+ *
+ * @param string $name The tab name to activate
+ *
+ */
+ public function activate($name)
+ {
+ $this->getTabs()->activate($name);
+ }
+
+ /**
+ * Load Pane items provided by all enabled modules
+ *
+ * @return $this
+ */
+ public function load()
+ {
+ $navigation = new Navigation();
+ $navigation->load('dashboard-pane');
+
+ $panes = array();
+ foreach ($navigation as $dashboardPane) {
+ /** @var DashboardPane $dashboardPane */
+ $pane = new Pane($dashboardPane->getLabel());
+ foreach ($dashboardPane->getChildren() as $dashlet) {
+ $pane->addDashlet($dashlet->getLabel(), $dashlet->getUrl());
+ }
+
+ $panes[] = $pane;
+ }
+
+ $this->mergePanes($panes);
+ $this->loadUserDashboards($navigation);
+ return $this;
+ }
+
+ /**
+ * Create and return a Config object for this dashboard
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ $output = array();
+ foreach ($this->panes as $pane) {
+ if ($pane->isUserWidget()) {
+ $output[$pane->getName()] = $pane->toArray();
+ }
+ foreach ($pane->getDashlets() as $dashlet) {
+ if ($dashlet->isUserWidget()) {
+ $output[$pane->getName() . '.' . $dashlet->getName()] = $dashlet->toArray();
+ }
+ }
+ }
+
+ return DashboardConfig::fromArray($output)->setConfigFile($this->getConfigFile())->setUser($this->user);
+ }
+
+ /**
+ * Load user dashboards from all config files that match the username
+ */
+ protected function loadUserDashboards(Navigation $navigation)
+ {
+ foreach (DashboardConfig::listConfigFilesForUser($this->user) as $file) {
+ $this->loadUserDashboardsFromFile($file, $navigation);
+ }
+ }
+
+ /**
+ * Load user dashboards from the given config file
+ *
+ * @param string $file
+ *
+ * @return bool
+ */
+ protected function loadUserDashboardsFromFile($file, Navigation $dashboardNavigation)
+ {
+ try {
+ $config = Config::fromIni($file);
+ } catch (NotReadableError $e) {
+ return false;
+ }
+
+ if (! count($config)) {
+ return false;
+ }
+ $panes = array();
+ $dashlets = array();
+ foreach ($config as $key => $part) {
+ if (strpos($key, '.') === false) {
+ $dashboardPane = $dashboardNavigation->getItem($key);
+ if ($dashboardPane !== null) {
+ $key = $dashboardPane->getLabel();
+ }
+ if ($this->hasPane($key)) {
+ $panes[$key] = $this->getPane($key);
+ } else {
+ $panes[$key] = new Pane($key);
+ $panes[$key]->setTitle($part->title);
+ }
+ $panes[$key]->setUserWidget();
+ if ((bool) $part->get('disabled', false) === true) {
+ $panes[$key]->setDisabled();
+ }
+ } else {
+ list($paneName, $dashletName) = explode('.', $key, 2);
+ $dashboardPane = $dashboardNavigation->getItem($paneName);
+ if ($dashboardPane !== null) {
+ $paneName = $dashboardPane->getLabel();
+ $dashletItem = $dashboardPane->getChildren()->getItem($dashletName);
+ if ($dashletItem !== null) {
+ $dashletName = $dashletItem->getLabel();
+ }
+ }
+ $part->pane = $paneName;
+ $part->dashlet = $dashletName;
+ $dashlets[] = $part;
+ }
+ }
+ foreach ($dashlets as $dashletData) {
+ $pane = null;
+
+ if (array_key_exists($dashletData->pane, $panes) === true) {
+ $pane = $panes[$dashletData->pane];
+ } elseif (array_key_exists($dashletData->pane, $this->panes) === true) {
+ $pane = $this->panes[$dashletData->pane];
+ } else {
+ continue;
+ }
+ $dashlet = new DashboardDashlet(
+ $dashletData->title,
+ $dashletData->url,
+ $pane
+ );
+ $dashlet->setName($dashletData->dashlet);
+
+ if ((bool) $dashletData->get('disabled', false) === true) {
+ $dashlet->setDisabled(true);
+ }
+
+ $dashlet->setUserWidget();
+ $pane->addDashlet($dashlet);
+ }
+
+ $this->mergePanes($panes);
+
+ return true;
+ }
+
+ /**
+ * Merge panes with existing panes
+ *
+ * @param array $panes
+ *
+ * @return $this
+ */
+ public function mergePanes(array $panes)
+ {
+ /** @var $pane Pane */
+ foreach ($panes as $pane) {
+ if ($this->hasPane($pane->getName()) === true) {
+ /** @var $current Pane */
+ $current = $this->panes[$pane->getName()];
+ $current->addDashlets($pane->getDashlets());
+ } else {
+ $this->panes[$pane->getName()] = $pane;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the tab object used to navigate through this dashboard
+ *
+ * @return Tabs
+ */
+ public function getTabs()
+ {
+ $url = Url::fromPath('dashboard')->getUrlWithout($this->tabParam);
+ if ($this->tabs === null) {
+ $this->tabs = new Tabs();
+
+ foreach ($this->panes as $key => $pane) {
+ if ($pane->getDisabled()) {
+ continue;
+ }
+ $this->tabs->add(
+ $key,
+ array(
+ 'title' => sprintf(
+ t('Show %s', 'dashboard.pane.tooltip'),
+ $pane->getTitle()
+ ),
+ 'label' => $pane->getTitle(),
+ 'url' => clone($url),
+ 'urlParams' => array($this->tabParam => $key)
+ )
+ );
+ }
+ }
+ return $this->tabs;
+ }
+
+ /**
+ * Return all panes of this dashboard
+ *
+ * @return array
+ */
+ public function getPanes()
+ {
+ return $this->panes;
+ }
+
+
+ /**
+ * Creates a new empty pane with the given title
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ public function createPane($title)
+ {
+ $pane = new Pane($title);
+ $pane->setTitle($title);
+ $this->addPane($pane);
+
+ return $this;
+ }
+
+ /**
+ * Checks if the current dashboard has any panes
+ *
+ * @return bool
+ */
+ public function hasPanes()
+ {
+ return ! empty($this->panes);
+ }
+
+ /**
+ * Check if a panel exist
+ *
+ * @param string $pane
+ * @return bool
+ */
+ public function hasPane($pane)
+ {
+ return $pane && array_key_exists($pane, $this->panes);
+ }
+
+ /**
+ * Add a pane object to this dashboard
+ *
+ * @param Pane $pane The pane to add
+ *
+ * @return $this
+ */
+ public function addPane(Pane $pane)
+ {
+ $this->panes[$pane->getName()] = $pane;
+ return $this;
+ }
+
+ public function removePane($title)
+ {
+ if ($this->hasPane($title) === true) {
+ $pane = $this->getPane($title);
+ if ($pane->isUserWidget() === true) {
+ unset($this->panes[$pane->getName()]);
+ } else {
+ $pane->setDisabled();
+ $pane->setUserWidget();
+ }
+ } else {
+ throw new ProgrammingError('Pane not found: ' . $title);
+ }
+ }
+
+ /**
+ * Return the pane with the provided name
+ *
+ * @param string $name The name of the pane to return
+ *
+ * @return Pane The pane or null if no pane with the given name exists
+ * @throws ProgrammingError
+ */
+ public function getPane($name)
+ {
+ if (! array_key_exists($name, $this->panes)) {
+ throw new ProgrammingError(
+ 'Trying to retrieve invalid dashboard pane "%s"',
+ $name
+ );
+ }
+ return $this->panes[$name];
+ }
+
+ /**
+ * Return an array with pane name=>title format used for comboboxes
+ *
+ * @return array
+ */
+ public function getPaneKeyTitleArray()
+ {
+ $list = array();
+ foreach ($this->panes as $name => $pane) {
+ $list[$name] = $pane->getTitle();
+ }
+ return $list;
+ }
+
+ /**
+ * @see Icinga\Web\Widget::render
+ */
+ public function render()
+ {
+ if (empty($this->panes)) {
+ return '';
+ }
+
+ return $this->determineActivePane()->render();
+ }
+
+ /**
+ * Activates the default pane of this dashboard and returns its name
+ *
+ * @return mixed
+ */
+ private function setDefaultPane()
+ {
+ $active = null;
+
+ foreach ($this->panes as $key => $pane) {
+ if ($pane->getDisabled() === false) {
+ $active = $key;
+ break;
+ }
+ }
+
+ if ($active !== null) {
+ $this->activate($active);
+ }
+ return $active;
+ }
+
+ /**
+ * @see determineActivePane()
+ */
+ public function getActivePane()
+ {
+ return $this->determineActivePane();
+ }
+
+ /**
+ * Determine the active pane either by the selected tab or the current request
+ *
+ * @throws \Icinga\Exception\ConfigurationError
+ * @throws \Icinga\Exception\ProgrammingError
+ *
+ * @return Pane The currently active pane
+ */
+ public function determineActivePane()
+ {
+ $active = $this->getTabs()->getActiveName();
+ if (! $active) {
+ if ($active = Url::fromRequest()->getParam($this->tabParam)) {
+ if ($this->hasPane($active)) {
+ $this->activate($active);
+ } else {
+ throw new ProgrammingError(
+ 'Try to get an inexistent pane.'
+ );
+ }
+ } else {
+ $active = $this->setDefaultPane();
+ }
+ }
+
+ if (isset($this->panes[$active])) {
+ return $this->panes[$active];
+ }
+
+ throw new ConfigurationError('Could not determine active pane');
+ }
+
+ /**
+ * Setter for user object
+ *
+ * @param User $user
+ */
+ public function setUser(User $user)
+ {
+ $this->user = $user;
+ }
+
+ /**
+ * Getter for user object
+ *
+ * @return User
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Get config file
+ *
+ * @return string
+ */
+ public function getConfigFile()
+ {
+ if ($this->user === null) {
+ throw new ProgrammingError('Can\'t load dashboards. User is not set');
+ }
+ return Config::resolvePath('dashboards/' . strtolower($this->user->getUsername()) . '/dashboard.ini');
+ }
+}
diff --git a/library/Icinga/Web/Widget/Dashboard/Dashlet.php b/library/Icinga/Web/Widget/Dashboard/Dashlet.php
new file mode 100644
index 0000000..2ba26df
--- /dev/null
+++ b/library/Icinga/Web/Widget/Dashboard/Dashlet.php
@@ -0,0 +1,315 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Dashboard;
+
+use Icinga\Web\Url;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\IcingaException;
+
+/**
+ * A dashboard pane dashlet
+ *
+ * This is the element displaying a specific view in icinga2web
+ *
+ */
+class Dashlet extends UserWidget
+{
+ /**
+ * The url of this Dashlet
+ *
+ * @var Url|null
+ */
+ private $url;
+
+ private $name;
+
+ /**
+ * The title being displayed on top of the dashlet
+ * @var
+ */
+ private $title;
+
+ /**
+ * The pane containing this dashlet, needed for the 'remove button'
+ * @var Pane
+ */
+ private $pane;
+
+ /**
+ * The disabled option is used to "delete" default dashlets provided by modules
+ *
+ * @var bool
+ */
+ private $disabled = false;
+
+ /**
+ * The progress label being used
+ *
+ * @var string
+ */
+ private $progressLabel;
+
+ /**
+ * The template string used for rendering this widget
+ *
+ * @var string
+ */
+ private $template =<<<'EOD'
+
+ <div class="container" data-icinga-url="{URL}">
+ <h1><a href="{FULL_URL}" aria-label="{TOOLTIP}" title="{TOOLTIP}" data-base-target="col1">{TITLE}</a></h1>
+ <p class="progress-label">{PROGRESS_LABEL}<span>.</span><span>.</span><span>.</span></p>
+ <noscript>
+ <div class="iframe-container">
+ <iframe
+ src="{IFRAME_URL}"
+ frameborder="no"
+ title="{TITLE_PREFIX}{TITLE}">
+ </iframe>
+ </div>
+ </noscript>
+ </div>
+EOD;
+
+ /**
+ * The template string used for rendering this widget in case of an error
+ *
+ * @var string
+ */
+ private $errorTemplate = <<<'EOD'
+
+ <div class="container">
+ <h1 title="{TOOLTIP}">{TITLE}</h1>
+ <p class="error-message">{ERROR_MESSAGE}</p>
+ </div>
+EOD;
+
+ /**
+ * Create a new dashlet displaying the given url in the provided pane
+ *
+ * @param string $title The title to use for this dashlet
+ * @param Url|string $url The url this dashlet uses for displaying information
+ * @param Pane $pane The pane this Dashlet will be added to
+ */
+ public function __construct($title, $url, Pane $pane)
+ {
+ $this->name = $title;
+ $this->title = $title;
+ $this->pane = $pane;
+ $this->url = $url;
+ }
+
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Retrieve the dashlets title
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * @param string $title
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ }
+
+ /**
+ * Retrieve the dashlets url
+ *
+ * @return Url|null
+ */
+ public function getUrl()
+ {
+ if ($this->url !== null && ! $this->url instanceof Url) {
+ $this->url = Url::fromPath($this->url);
+ }
+ return $this->url;
+ }
+
+ /**
+ * Set the dashlets URL
+ *
+ * @param string|Url $url The url to use, either as an Url object or as a path
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ /**
+ * Set the disabled property
+ *
+ * @param boolean $disabled
+ */
+ public function setDisabled($disabled)
+ {
+ $this->disabled = $disabled;
+ }
+
+ /**
+ * Get the disabled property
+ *
+ * @return boolean
+ */
+ public function getDisabled()
+ {
+ return $this->disabled;
+ }
+
+ /**
+ * Set the progress label to use
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setProgressLabel($label)
+ {
+ $this->progressLabel = $label;
+ return $this;
+ }
+
+ /**
+ * Return the progress label to use
+ *
+ * @return string
+ */
+ public function getProgressLabe()
+ {
+ if ($this->progressLabel === null) {
+ return $this->view()->translate('Loading');
+ }
+
+ return $this->progressLabel;
+ }
+
+ /**
+ * Return this dashlet's structure as array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $array = array(
+ 'url' => $this->getUrl()->getRelativeUrl(),
+ 'title' => $this->getTitle()
+ );
+ if ($this->getDisabled() === true) {
+ $array['disabled'] = 1;
+ }
+ return $array;
+ }
+
+ /**
+ * @see Widget::render()
+ */
+ public function render()
+ {
+ if ($this->disabled === true) {
+ return '';
+ }
+
+ $view = $this->view();
+
+ if (! $this->url) {
+ $searchTokens = array(
+ '{TOOLTIP}',
+ '{TITLE}',
+ '{ERROR_MESSAGE}'
+ );
+
+ $replaceTokens = array(
+ sprintf($view->translate('Show %s', 'dashboard.dashlet.tooltip'), $view->escape($this->getTitle())),
+ $view->escape($this->getTitle()),
+ $view->escape(
+ sprintf($view->translate('Cannot create dashboard dashlet "%s" without valid URL'), $this->title)
+ )
+ );
+
+ return str_replace($searchTokens, $replaceTokens, $this->errorTemplate);
+ }
+
+ $url = $this->getUrl();
+ $url->setParam('showCompact', true);
+ $iframeUrl = clone $url;
+ $iframeUrl->setParam('isIframe');
+
+ $searchTokens = array(
+ '{URL}',
+ '{IFRAME_URL}',
+ '{FULL_URL}',
+ '{TOOLTIP}',
+ '{TITLE}',
+ '{TITLE_PREFIX}',
+ '{PROGRESS_LABEL}'
+ );
+
+ $replaceTokens = array(
+ $url,
+ $iframeUrl,
+ $url->getUrlWithout(['showCompact', 'limit', 'view']),
+ sprintf($view->translate('Show %s', 'dashboard.dashlet.tooltip'), $view->escape($this->getTitle())),
+ $view->escape($this->getTitle()),
+ $view->translate('Dashlet') . ': ',
+ $this->getProgressLabe()
+ );
+
+ return str_replace($searchTokens, $replaceTokens, $this->template);
+ }
+
+ /**
+ * Create a @see Dashlet instance from the given Zend config, using the provided title
+ *
+ * @param $title The title for this dashlet
+ * @param ConfigObject $config The configuration defining url, parameters, height, width, etc.
+ * @param Pane $pane The pane this dashlet belongs to
+ *
+ * @return Dashlet A newly created Dashlet for use in the Dashboard
+ */
+ public static function fromIni($title, ConfigObject $config, Pane $pane)
+ {
+ $height = null;
+ $width = null;
+ $url = $config->get('url');
+ $parameters = $config->toArray();
+ unset($parameters['url']); // otherwise there's an url = parameter in the Url
+
+ $cmp = new Dashlet($title, Url::fromPath($url, $parameters), $pane);
+ return $cmp;
+ }
+
+ /**
+ * @param \Icinga\Web\Widget\Dashboard\Pane $pane
+ */
+ public function setPane(Pane $pane)
+ {
+ $this->pane = $pane;
+ }
+
+ /**
+ * @return \Icinga\Web\Widget\Dashboard\Pane
+ */
+ public function getPane()
+ {
+ return $this->pane;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php
new file mode 100644
index 0000000..c8b14c5
--- /dev/null
+++ b/library/Icinga/Web/Widget/Dashboard/Pane.php
@@ -0,0 +1,335 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Dashboard;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Web\Widget\AbstractWidget;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * A pane, displaying different Dashboard dashlets
+ */
+class Pane extends UserWidget
+{
+ /**
+ * The name of this pane, as defined in the ini file
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * The title of this pane, as displayed in the dashboard tabs
+ *
+ * @var string
+ */
+ private $title;
+
+ /**
+ * An array of @see Dashlets that are displayed in this pane
+ *
+ * @var array
+ */
+ private $dashlets = array();
+
+ /**
+ * Disabled flag of a pane
+ *
+ * @var bool
+ */
+ private $disabled = false;
+
+ /**
+ * Create a new pane
+ *
+ * @param string $name The pane to create
+ */
+ public function __construct($name)
+ {
+ $this->name = $name;
+ $this->title = $name;
+ }
+
+ /**
+ * Set the name of this pane
+ *
+ * @param string $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Returns the name of this pane
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Returns the title of this pane
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Overwrite the title of this pane
+ *
+ * @param string $title The new title to use for this pane
+ *
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * Return true if a dashlet with the given title exists in this pane
+ *
+ * @param string $title The title of the dashlet to check for existence
+ *
+ * @return bool
+ */
+ public function hasDashlet($title)
+ {
+ return array_key_exists($title, $this->dashlets);
+ }
+
+ /**
+ * Checks if the current pane has any dashlets
+ *
+ * @return bool
+ */
+ public function hasDashlets()
+ {
+ return ! empty($this->dashlets);
+ }
+
+ /**
+ * Return a dashlet with the given name if existing
+ *
+ * @param string $title The title of the dashlet to return
+ *
+ * @return Dashlet The dashlet with the given title
+ * @throws ProgrammingError If the dashlet doesn't exist
+ */
+ public function getDashlet($title)
+ {
+ if ($this->hasDashlet($title)) {
+ return $this->dashlets[$title];
+ }
+ throw new ProgrammingError(
+ 'Trying to access invalid dashlet: %s',
+ $title
+ );
+ }
+
+ /**
+ * Removes the dashlet with the given title if it exists in this pane
+ *
+ * @param string $title The pane
+ * @return Pane $this
+ */
+ public function removeDashlet($title)
+ {
+ if ($this->hasDashlet($title)) {
+ $dashlet = $this->getDashlet($title);
+ if ($dashlet->isUserWidget() === true) {
+ unset($this->dashlets[$title]);
+ } else {
+ $dashlet->setDisabled(true);
+ $dashlet->setUserWidget();
+ }
+ } else {
+ throw new ProgrammingError('Dashlet does not exist: ' . $title);
+ }
+ return $this;
+ }
+
+ /**
+ * Removes all or a given list of dashlets from this pane
+ *
+ * @param array $dashlets Optional list of dashlet titles
+ * @return Pane $this
+ */
+ public function removeDashlets(array $dashlets = null)
+ {
+ if ($dashlets === null) {
+ $this->dashlets = array();
+ } else {
+ foreach ($dashlets as $dashlet) {
+ $this->removeDashlet($dashlet);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Return all dashlets added at this pane
+ *
+ * @return array
+ */
+ public function getDashlets()
+ {
+ return $this->dashlets;
+ }
+
+ /**
+ * @see Widget::render
+ */
+ public function render()
+ {
+ $dashlets = array_filter(
+ $this->dashlets,
+ function ($e) {
+ return ! $e->getDisabled();
+ }
+ );
+ return implode("\n", $dashlets) . "\n";
+ }
+
+ /**
+ * Create, add and return a new dashlet
+ *
+ * @param string $title
+ * @param string $url
+ *
+ * @return Dashlet
+ */
+ public function createDashlet($title, $url = null)
+ {
+ $dashlet = new Dashlet($title, $url, $this);
+ $this->addDashlet($dashlet);
+ return $dashlet;
+ }
+
+ /**
+ * Add a dashlet to this pane, optionally creating it if $dashlet is a string
+ *
+ * @param string|Dashlet $dashlet The dashlet object or title
+ * (if a new dashlet will be created)
+ * @param string|null $url An Url to be used when dashlet is a string
+ *
+ * @return $this
+ * @throws \Icinga\Exception\ConfigurationError
+ */
+ public function addDashlet($dashlet, $url = null)
+ {
+ if ($dashlet instanceof Dashlet) {
+ $this->dashlets[$dashlet->getName()] = $dashlet;
+ } elseif (is_string($dashlet) && $url !== null) {
+ $this->createDashlet($dashlet, $url);
+ } else {
+ throw new ConfigurationError('Invalid dashlet added: %s', $dashlet);
+ }
+ return $this;
+ }
+
+ /**
+ * Add new dashlets to existing dashlets
+ *
+ * @param array $dashlets
+ * @return $this
+ */
+ public function addDashlets(array $dashlets)
+ {
+ /* @var $dashlet Dashlet */
+ foreach ($dashlets as $dashlet) {
+ if (array_key_exists($dashlet->getName(), $this->dashlets)) {
+ if (preg_match('/_(\d+)$/', $dashlet->getName(), $m)) {
+ $name = preg_replace('/_\d+$/', $m[1]++, $dashlet->getName());
+ } else {
+ $name = $dashlet->getName() . '_2';
+ }
+ $this->dashlets[$name] = $dashlet;
+ } else {
+ $this->dashlets[$dashlet->getName()] = $dashlet;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a dashlet to the current pane
+ *
+ * @param $title
+ * @param $url
+ * @return Dashlet
+ *
+ * @see addDashlet()
+ */
+ public function add($title, $url = null)
+ {
+ $this->addDashlet($title, $url);
+
+ return $this->dashlets[$title];
+ }
+
+ /**
+ * Return the this pane's structure as array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $pane = array(
+ 'title' => $this->getTitle(),
+ );
+
+ if ($this->getDisabled() === true) {
+ $pane['disabled'] = 1;
+ }
+
+ return $pane;
+ }
+
+ /**
+ * Create a new pane with the title $title from the given configuration
+ *
+ * @param $title The title for this pane
+ * @param ConfigObject $config The configuration to use for setup
+ *
+ * @return Pane
+ */
+ public static function fromIni($title, ConfigObject $config)
+ {
+ $pane = new Pane($title);
+ if ($config->get('title', false)) {
+ $pane->setTitle($config->get('title'));
+ }
+ return $pane;
+ }
+
+ /**
+ * Setter for disabled
+ *
+ * @param boolean $disabled
+ */
+ public function setDisabled($disabled = true)
+ {
+ $this->disabled = (bool) $disabled;
+ }
+
+ /**
+ * Getter for disabled
+ *
+ * @return boolean
+ */
+ public function getDisabled()
+ {
+ return $this->disabled;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Dashboard/UserWidget.php b/library/Icinga/Web/Widget/Dashboard/UserWidget.php
new file mode 100644
index 0000000..164d58b
--- /dev/null
+++ b/library/Icinga/Web/Widget/Dashboard/UserWidget.php
@@ -0,0 +1,36 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Dashboard;
+
+use Icinga\Web\Widget\AbstractWidget;
+
+abstract class UserWidget extends AbstractWidget
+{
+ /**
+ * Flag if widget is created by an user
+ *
+ * @var bool
+ */
+ protected $userWidget = false;
+
+ /**
+ * Set the user widget flag
+ *
+ * @param boolean $userWidget
+ */
+ public function setUserWidget($userWidget = true)
+ {
+ $this->userWidget = (bool) $userWidget;
+ }
+
+ /**
+ * Getter for user widget flag
+ *
+ * @return boolean
+ */
+ public function isUserWidget()
+ {
+ return $this->userWidget;
+ }
+}
diff --git a/library/Icinga/Web/Widget/FilterEditor.php b/library/Icinga/Web/Widget/FilterEditor.php
new file mode 100644
index 0000000..f1efd9a
--- /dev/null
+++ b/library/Icinga/Web/Widget/FilterEditor.php
@@ -0,0 +1,811 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Data\Filterable;
+use Icinga\Data\FilterColumns;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Web\Url;
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Notification;
+use Exception;
+
+/**
+ * Filter
+ */
+class FilterEditor extends AbstractWidget
+{
+ /**
+ * The filter
+ *
+ * @var Filter
+ */
+ private $filter;
+
+ /**
+ * The query to filter
+ *
+ * @var Filterable
+ */
+ protected $query;
+
+ protected $url;
+
+ protected $addTo;
+
+ protected $cachedColumnSelect;
+
+ protected $preserveParams = array();
+
+ protected $preservedParams = array();
+
+ protected $preservedUrl;
+
+ protected $ignoreParams = array();
+
+ protected $searchColumns;
+
+ /**
+ * @var string
+ */
+ private $selectedIdx;
+
+ /**
+ * Whether the filter control is visible
+ *
+ * @var bool
+ */
+ protected $visible = true;
+
+ /**
+ * Create a new FilterWidget
+ *
+ * @param Filter $filter Your filter
+ */
+ public function __construct($props)
+ {
+ if (array_key_exists('filter', $props)) {
+ $this->setFilter($props['filter']);
+ }
+ if (array_key_exists('query', $props)) {
+ $this->setQuery($props['query']);
+ }
+ }
+
+ public function setFilter(Filter $filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ public function getFilter()
+ {
+ if ($this->filter === null) {
+ $this->filter = Filter::fromQueryString((string) $this->url()->getParams());
+ }
+ return $this->filter;
+ }
+
+ /**
+ * Set columns to search in
+ *
+ * @param array $searchColumns
+ *
+ * @return $this
+ */
+ public function setSearchColumns(array $searchColumns = null)
+ {
+ $this->searchColumns = $searchColumns;
+ return $this;
+ }
+
+ public function setUrl($url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ protected function url()
+ {
+ if ($this->url === null) {
+ $this->url = Url::fromRequest();
+ }
+ return $this->url;
+ }
+
+ protected function preservedUrl()
+ {
+ if ($this->preservedUrl === null) {
+ $this->preservedUrl = $this->url()->with($this->preservedParams);
+ }
+ return $this->preservedUrl;
+ }
+
+ /**
+ * Set the query to filter
+ *
+ * @param Filterable $query
+ *
+ * @return $this
+ */
+ public function setQuery(Filterable $query)
+ {
+ $this->query = $query;
+ return $this;
+ }
+
+ public function ignoreParams()
+ {
+ $this->ignoreParams = func_get_args();
+ return $this;
+ }
+
+ public function preserveParams()
+ {
+ $this->preserveParams = func_get_args();
+ return $this;
+ }
+
+ /**
+ * Get whether the filter control is visible
+ *
+ * @return bool
+ */
+ public function isVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set whether the filter control is visible
+ *
+ * @param bool $visible
+ *
+ * @return $this
+ */
+ public function setVisible($visible)
+ {
+ $this->visible = (bool) $visible;
+
+ return $this;
+ }
+
+ protected function redirectNow($url)
+ {
+ $response = Icinga::app()->getFrontController()->getResponse();
+ $response->redirectAndExit($url);
+ }
+
+ protected function mergeRootExpression($filter, $column, $sign, $expression)
+ {
+ $found = false;
+ if ($filter->isChain() && $filter->getOperatorName() === 'AND') {
+ foreach ($filter->filters() as $f) {
+ if ($f->isExpression()
+ && $f->getColumn() === $column
+ && $f->getSign() === $sign
+ ) {
+ $f->setExpression($expression);
+ $found = true;
+ break;
+ }
+ }
+ } elseif ($filter->isExpression()) {
+ if ($filter->getColumn() === $column && $filter->getSign() === $sign) {
+ $filter->setExpression($expression);
+ $found = true;
+ }
+ }
+ if (! $found) {
+ $filter = $filter->andFilter(
+ Filter::expression($column, $sign, $expression)
+ );
+ }
+ return $filter;
+ }
+
+ protected function resetSearchColumns(Filter &$filter)
+ {
+ if ($filter->isChain()) {
+ $filters = &$filter->filters();
+ if (!($empty = empty($filters))) {
+ foreach ($filters as $k => &$f) {
+ if (false === $this->resetSearchColumns($f)) {
+ unset($filters[$k]);
+ }
+ }
+ }
+ return $empty || !empty($filters);
+ }
+ return $filter->isExpression() ? !(
+ in_array($filter->getColumn(), $this->searchColumns)
+ &&
+ $filter->getSign() === '='
+ ) : true;
+ }
+
+ public function handleRequest($request)
+ {
+ $this->setUrl($request->getUrl()->without($this->ignoreParams));
+ $params = $this->url()->getParams();
+
+ $preserve = array();
+ foreach ($this->preserveParams as $key) {
+ if (null !== ($value = $params->shift($key))) {
+ $preserve[$key] = $value;
+ }
+ }
+ $this->preservedParams = $preserve;
+
+ $add = $params->shift('addFilter');
+ $remove = $params->shift('removeFilter');
+ $strip = $params->shift('stripFilter');
+ $modify = $params->shift('modifyFilter');
+
+
+
+ $search = null;
+ if ($request->isPost()) {
+ $search = $request->getPost('q');
+ }
+
+ if ($search === null) {
+ $search = $params->shift('q');
+ }
+
+ $filter = $this->getFilter();
+
+ if ($search !== null) {
+ if (strpos($search, '=') !== false) {
+ list($k, $v) = preg_split('/=/', $search);
+ $filter = $this->mergeRootExpression($filter, trim($k), '=', ltrim($v));
+ } else {
+ if ($this->searchColumns === null && $this->query instanceof FilterColumns) {
+ $this->searchColumns = $this->query->getSearchColumns($search);
+ }
+
+ if (! empty($this->searchColumns)) {
+ if (! $this->resetSearchColumns($filter)) {
+ $filter = Filter::matchAll();
+ }
+ $filters = array();
+ $search = trim($search);
+ foreach ($this->searchColumns as $searchColumn) {
+ $filters[] = Filter::expression($searchColumn, '=', "*$search*");
+ }
+ $filter = $filter->andFilter(new FilterOr($filters));
+ } else {
+ Notification::error(mt('monitoring', 'Cannot search here'));
+ return $this;
+ }
+ }
+
+ $url = Url::fromRequest()->onlyWith($this->preserveParams);
+ $urlParams = $url->getParams();
+ $url->setQueryString($filter->toQueryString());
+ foreach ($urlParams->toArray(false) as $key => $value) {
+ $url->getParams()->addEncoded($key, $value);
+ }
+
+ $this->redirectNow($url);
+ }
+
+ if ($remove) {
+ $redirect = $this->url();
+ if ($filter->getById($remove)->isRootNode()) {
+ $redirect->setQueryString('');
+ } else {
+ $filter->removeId($remove);
+ $redirect->setQueryString($filter->toQueryString())->getParams()->add('modifyFilter');
+ }
+ $this->redirectNow($redirect->addParams($preserve));
+ }
+
+ if ($strip) {
+ $redirect = $this->url();
+ $subId = $strip . '-1';
+ if ($filter->getId() === $strip) {
+ $filter = $filter->getById($strip . '-1');
+ } else {
+ $filter->replaceById($strip, $filter->getById($strip . '-1'));
+ }
+ $redirect->setQueryString($filter->toQueryString())->getParams()->add('modifyFilter');
+ $this->redirectNow($redirect->addParams($preserve));
+ }
+
+
+ if ($modify) {
+ if ($request->isPost()) {
+ if ($request->get('cancel') === 'Cancel') {
+ $this->redirectNow($this->preservedUrl()->without('modifyFilter'));
+ }
+ if ($request->get('formUID') === 'FilterEditor') {
+ $filter = $this->applyChanges($request->getPost());
+ $url = $this->url()->setQueryString($filter->toQueryString())->addParams($preserve);
+ $url->getParams()->add('modifyFilter');
+
+ $addFilter = $request->get('add_filter');
+ if ($addFilter !== null) {
+ $url->setParam('addFilter', $addFilter);
+ }
+
+ $removeFilter = $request->get('remove_filter');
+ if ($removeFilter !== null) {
+ $url->setParam('removeFilter', $removeFilter);
+ }
+
+ $this->redirectNow($url);
+ }
+ }
+ $this->url()->getParams()->add('modifyFilter');
+ }
+
+ if ($add) {
+ $this->addFilterToId($add);
+ }
+
+ if ($this->query !== null && $request->isGet()) {
+ $this->query->applyFilter($this->getFilter());
+ }
+
+ return $this;
+ }
+
+ protected function select($name, $list, $selected, $attributes = null)
+ {
+ $view = $this->view();
+ if ($attributes === null) {
+ $attributes = '';
+ } else {
+ $attributes = $view->propertiesToString($attributes);
+ }
+ $html = sprintf(
+ '<select name="%s"%s class="autosubmit">' . "\n",
+ $view->escape($name),
+ $attributes
+ );
+
+ foreach ($list as $k => $v) {
+ $active = '';
+ if ($k === $selected) {
+ $active = ' selected="selected"';
+ }
+ $html .= sprintf(
+ ' <option value="%s"%s>%s</option>' . "\n",
+ $view->escape($k),
+ $active,
+ $view->escape($v)
+ );
+ }
+ $html .= '</select>' . "\n\n";
+ return $html;
+ }
+
+ protected function addFilterToId($id)
+ {
+ $this->addTo = $id;
+ return $this;
+ }
+
+ protected function removeIndex($idx)
+ {
+ $this->selectedIdx = $idx;
+ return $this;
+ }
+
+ protected function removeLink(Filter $filter)
+ {
+ return "<button type='submit' name='remove_filter' value='{$filter->getId()}'>"
+ . $this->view()->icon('trash', t('Remove this part of your filter'))
+ . '</button>';
+ }
+
+ protected function addLink(Filter $filter)
+ {
+ return "<button type='submit' name='add_filter' value='{$filter->getId()}'>"
+ . $this->view()->icon('plus', t('Add another filter'))
+ . '</button>';
+ }
+
+ protected function stripLink(Filter $filter)
+ {
+ return $this->view()->qlink(
+ '',
+ $this->preservedUrl()->with('stripFilter', $filter->getId()),
+ null,
+ array(
+ 'icon' => 'minus',
+ 'title' => t('Strip this filter')
+ )
+ );
+ }
+
+ protected function cancelLink()
+ {
+ return $this->view()->qlink(
+ '',
+ $this->preservedUrl()->without('addFilter'),
+ null,
+ array(
+ 'icon' => 'cancel',
+ 'title' => t('Cancel this operation')
+ )
+ );
+ }
+
+ protected function renderFilter($filter, $level = 0)
+ {
+ if ($level === 0 && $filter->isChain() && $filter->isEmpty()) {
+ return '<ul class="datafilter"><li class="active">' . $this->renderNewFilter() . '</li></ul>';
+ }
+
+ if ($filter instanceof FilterChain) {
+ return $this->renderFilterChain($filter, $level);
+ } elseif ($filter instanceof FilterExpression) {
+ return $this->renderFilterExpression($filter);
+ } else {
+ throw new ProgrammingError('Got a Filter being neither expression nor chain');
+ }
+ }
+
+ protected function renderFilterChain(FilterChain $filter, $level)
+ {
+ $html = '<span class="handle"> </span>'
+ . $this->selectOperator($filter)
+ . $this->removeLink($filter)
+ . ($filter->count() === 1 ? $this->stripLink($filter) : '')
+ . $this->addLink($filter);
+
+ if ($filter->isEmpty() && ! $this->addTo) {
+ return $html;
+ }
+
+ $parts = array();
+ foreach ($filter->filters() as $f) {
+ $parts[] = '<li>' . $this->renderFilter($f, $level + 1) . '</li>';
+ }
+
+ if ($this->addTo && $this->addTo == $filter->getId()) {
+ $parts[] = '<li style="background: #ffb">' . $this->renderNewFilter() .$this->cancelLink(). '</li>';
+ }
+
+ $class = $level === 0 ? ' class="datafilter"' : '';
+ $html .= sprintf(
+ "<ul%s>\n%s</ul>\n",
+ $class,
+ implode("", $parts)
+ );
+ return $html;
+ }
+
+ protected function renderFilterExpression(FilterExpression $filter)
+ {
+ if ($this->addTo && $this->addTo === $filter->getId()) {
+ return
+ preg_replace(
+ '/ class="autosubmit"/',
+ ' class="autofocus"',
+ $this->selectOperator()
+ )
+ . '<ul><li>'
+ . $this->selectColumn($filter)
+ . $this->selectSign($filter)
+ . $this->text($filter)
+ . $this->removeLink($filter)
+ . $this->addLink($filter)
+ . '</li><li class="active">'
+ . $this->renderNewFilter() .$this->cancelLink()
+ . '</li></ul>'
+ ;
+ } else {
+ return $this->selectColumn($filter)
+ . $this->selectSign($filter)
+ . $this->text($filter)
+ . $this->removeLink($filter)
+ . $this->addLink($filter)
+ ;
+ }
+ }
+
+ protected function text(Filter $filter = null)
+ {
+ $value = $filter === null ? '' : $filter->getExpression();
+ if (is_array($value)) {
+ $value = '(' . implode('|', $value) . ')';
+ }
+ return sprintf(
+ '<input type="text" name="%s" value="%s" />',
+ $this->elementId('value', $filter),
+ $this->view()->escape($value)
+ );
+ }
+
+ protected function renderNewFilter()
+ {
+ $html = $this->selectColumn()
+ . $this->selectSign()
+ . $this->text();
+
+ return preg_replace(
+ '/ class="autosubmit"/',
+ '',
+ $html
+ );
+ }
+
+ protected function arrayForSelect($array, $flip = false)
+ {
+ $res = array();
+ foreach ($array as $k => $v) {
+ if (is_int($k)) {
+ $res[$v] = ucwords(str_replace('_', ' ', $v));
+ } elseif ($flip) {
+ $res[$v] = $k;
+ } else {
+ $res[$k] = $v;
+ }
+ }
+ // sort($res);
+ return $res;
+ }
+
+ protected function elementId($prefix, Filter $filter = null)
+ {
+ if ($filter === null) {
+ return $prefix . '_new_' . ($this->addTo ?: '0');
+ } else {
+ return $prefix . '_' . $filter->getId();
+ }
+ }
+
+ protected function selectOperator(Filter $filter = null)
+ {
+ $ops = array(
+ 'AND' => 'AND',
+ 'OR' => 'OR',
+ 'NOT' => 'NOT'
+ );
+
+ return $this->select(
+ $this->elementId('operator', $filter),
+ $ops,
+ $filter === null ? null : $filter->getOperatorName(),
+ array('style' => 'width: 5em')
+ );
+ }
+
+ protected function selectSign(Filter $filter = null)
+ {
+ $signs = array(
+ '=' => '=',
+ '!=' => '!=',
+ '>' => '>',
+ '<' => '<',
+ '>=' => '>=',
+ '<=' => '<=',
+ );
+
+ return $this->select(
+ $this->elementId('sign', $filter),
+ $signs,
+ $filter === null ? null : $filter->getSign(),
+ array('style' => 'width: 4em')
+ );
+ }
+
+ public function setColumns(array $columns = null)
+ {
+ $this->cachedColumnSelect = $columns ? $this->arrayForSelect($columns) : null;
+ return $this;
+ }
+
+ protected function selectColumn(Filter $filter = null)
+ {
+ $active = $filter === null ? null : $filter->getColumn();
+
+ if ($this->cachedColumnSelect === null && $this->query === null) {
+ return sprintf(
+ '<input type="text" name="%s" value="%s" />',
+ $this->elementId('column', $filter),
+ $this->view()->escape($active) // Escape attribute?
+ );
+ }
+
+ if ($this->cachedColumnSelect === null && $this->query instanceof FilterColumns) {
+ $this->cachedColumnSelect = $this->arrayForSelect($this->query->getFilterColumns(), true);
+ asort($this->cachedColumnSelect);
+ } elseif ($this->cachedColumnSelect === null) {
+ throw new ProgrammingError('No columns set nor does the query provide any');
+ }
+
+ $cols = $this->cachedColumnSelect;
+ if ($active && !isset($cols[$active])) {
+ $cols[$active] = str_replace('_', ' ', ucfirst(ltrim($active, '_')));
+ }
+
+ return $this->select($this->elementId('column', $filter), $cols, $active);
+ }
+
+ protected function applyChanges($changes)
+ {
+ $filter = $this->filter;
+ $pairs = array();
+ $addTo = null;
+ $add = array();
+ foreach ($changes as $k => $v) {
+ if (preg_match('/^(column|value|sign|operator)((?:_new)?)_([\d-]+)$/', $k, $m)) {
+ if ($m[2] === '_new') {
+ if ($addTo !== null && $addTo !== $m[3]) {
+ throw new \Exception('F...U');
+ }
+ $addTo = $m[3];
+ $add[$m[1]] = $v;
+ } else {
+ $pairs[$m[3]][$m[1]] = $v;
+ }
+ }
+ }
+
+ $operators = array();
+ foreach ($pairs as $id => $fs) {
+ if (array_key_exists('operator', $fs)) {
+ $operators[$id] = $fs['operator'];
+ } else {
+ $f = $filter->getById($id);
+ $f->setColumn($fs['column']);
+ if ($f->getSign() !== $fs['sign']) {
+ if ($f->isRootNode()) {
+ $filter = $f->setSign($fs['sign']);
+ } else {
+ $filter->replaceById($id, $f->setSign($fs['sign']));
+ }
+ }
+ $f->setExpression($fs['value']);
+ }
+ }
+
+ krsort($operators, SORT_NATURAL);
+ foreach ($operators as $id => $operator) {
+ $f = $filter->getById($id);
+ if ($f->getOperatorName() !== $operator) {
+ if ($f->isRootNode()) {
+ $filter = $f->setOperatorName($operator);
+ } else {
+ $filter->replaceById($id, $f->setOperatorName($operator));
+ }
+ }
+ }
+
+ if ($addTo !== null) {
+ if ($addTo === '0') {
+ $filter = Filter::expression($add['column'], $add['sign'], $add['value']);
+ } else {
+ $parent = $filter->getById($addTo);
+ $f = Filter::expression($add['column'], $add['sign'], $add['value']);
+ if (isset($add['operator'])) {
+ switch ($add['operator']) {
+ case 'AND':
+ if ($parent->isExpression()) {
+ if ($parent->isRootNode()) {
+ $filter = Filter::matchAll(clone $parent, $f);
+ } else {
+ $filter = $filter->replaceById($addTo, Filter::matchAll(clone $parent, $f));
+ }
+ } else {
+ $parent->addFilter(Filter::matchAll($f));
+ }
+ break;
+ case 'OR':
+ if ($parent->isExpression()) {
+ if ($parent->isRootNode()) {
+ $filter = Filter::matchAny(clone $parent, $f);
+ } else {
+ $filter = $filter->replaceById($addTo, Filter::matchAny(clone $parent, $f));
+ }
+ } else {
+ $parent->addFilter(Filter::matchAny($f));
+ }
+ break;
+ case 'NOT':
+ if ($parent->isExpression()) {
+ if ($parent->isRootNode()) {
+ $filter = Filter::not(Filter::matchAll($parent, $f));
+ } else {
+ $filter = $filter->replaceById($addTo, Filter::not(Filter::matchAll($parent, $f)));
+ }
+ } else {
+ $parent->addFilter(Filter::not($f));
+ }
+ break;
+ }
+ } else {
+ $parent->addFilter($f);
+ }
+ }
+ }
+
+ return $filter;
+ }
+
+ public function renderSearch()
+ {
+ $preservedUrl = $this->preservedUrl();
+
+ $html = ' <form method="post" class="search inline" action="'
+ . $preservedUrl
+ . '"><input type="text" name="q" style="width: 8em" class="search" value="" placeholder="'
+ . t('Search...')
+ . '" /></form>';
+
+ if ($this->filter->isEmpty()) {
+ $title = t('Filter this list');
+ } else {
+ $title = t('Modify this filter');
+ if (! $this->filter->isEmpty()) {
+ $title .= ': ' . $this->view()->escape($this->filter);
+ }
+ }
+
+ return $html
+ . '<a href="'
+ . $preservedUrl->with('modifyFilter', ! $preservedUrl->getParam('modifyFilter'))
+ . '" aria-label="'
+ . $title
+ . '" title="'
+ . $title
+ . '">'
+ . '<i aria-hidden="true" class="icon-filter"></i>'
+ . '</a>';
+ }
+
+ public function render()
+ {
+ if (! $this->visible) {
+ return '';
+ }
+ if (! $this->preservedUrl()->getParam('modifyFilter')) {
+ return '<div class="filter icinga-controls">'
+ . $this->renderSearch()
+ . $this->view()->escape($this->shorten($this->filter, 50))
+ . '</div>';
+ }
+ return '<div class="filter icinga-controls">'
+ . $this->renderSearch()
+ . '<form action="'
+ . Url::fromRequest()
+ . '" class="editor" method="POST">'
+ . '<input type="submit" name="submit" value="Apply" style="display:none;"/>'
+ . '<ul class="tree"><li>'
+ . $this->renderFilter($this->filter)
+ . '</li></ul>'
+ . '<div class="buttons">'
+ . '<input type="submit" name="cancel" value="Cancel" class="button btn-cancel" />'
+ . '<input type="submit" name="submit" value="Apply" class="button btn-primary"/>'
+ . '</div>'
+ . '<input type="hidden" name="formUID" value="FilterEditor">'
+ . '</form>'
+ . '</div>';
+ }
+
+ protected function shorten($string, $length)
+ {
+ if (strlen($string) > $length) {
+ return substr($string, 0, $length) . '...';
+ }
+ return $string;
+ }
+
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return 'ERROR in FilterEditor: ' . $e->getMessage();
+ }
+ }
+}
diff --git a/library/Icinga/Web/Widget/FilterWidget.php b/library/Icinga/Web/Widget/FilterWidget.php
new file mode 100644
index 0000000..11b1ecb
--- /dev/null
+++ b/library/Icinga/Web/Widget/FilterWidget.php
@@ -0,0 +1,122 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Web\Url;
+
+/**
+ * Filter
+ */
+class FilterWidget extends AbstractWidget
+{
+ /**
+ * The filter
+ *
+ * @var Filter
+ */
+ private $filter;
+
+ /**
+ * The domain of the filter, set in the data-icinga-filter-domain attribute
+ * @var string
+ */
+ private $domain;
+
+ /**
+ * Create a new FilterWidget
+ *
+ * @param Filter $filter Your filter
+ */
+ public function __construct(Filter $filter)
+ {
+ $this->filter = $filter;
+ }
+
+ protected function renderFilter($filter, $level = 0)
+ {
+ $html = '';
+ $url = Url::fromRequest();
+ if ($filter instanceof FilterChain) {
+ if ($level === 0) {
+ $op = '</li><li>)' . $filter->getOperatorName() . ' (';
+ } else {
+ $op = '</li><li>) ' . $filter->getOperatorName() . ' ( ';
+ }
+ $parts = array();
+ foreach ($filter->filters() as $f) {
+ $parts[] = $this->renderFilter($f, $level + 1);
+ }
+ if (empty($parts)) {
+ return $html;
+ }
+ if ($level === 0) {
+ $html .= '<ul class="datafilter"><li>( ' . implode($op, $parts) . ' )</li></ul>';
+ } else {
+ $html .= '<ul><li>( ' . implode($op, $parts) . ' )</li></ul>';
+ }
+ return $html;
+ } elseif ($filter instanceof FilterExpression) {
+ $u = $url->without($filter->getColumn());
+ } else {
+ $u = $url . '--';
+ }
+ $html .= '<a href="' . $url . '" title="'
+ . $this->view()->escape(t('Click to remove this part of your filter'))
+ . '">' . $filter . '</a> ';
+ return $html;
+ }
+
+ public function render()
+ {
+ $url = Url::fromRequest();
+ $view = $this->view();
+ $html = ' <form method="post" class="inline" action="'
+ . $url
+ . '"><input type="text" name="q" style="width: 8em" class="search" value="" placeholder="'
+ . t('Add filter...')
+ . '" /></form>';
+
+
+ // $html .= $this->renderFilter($this->filter);
+
+ $editorUrl = clone $url;
+ $editorUrl->setParam('modifyFilter', true);
+ if ($this->filter->isEmpty()) {
+ $title = t('Filter this list');
+ $txt = $view->icon('plus');
+ $remove = '';
+ } else {
+ $txt = t('Filtered');
+ $title = t('Modify this filter');
+ $remove = ' <a href="'
+ . Url::fromRequest()->setParams(array())
+ . '" title="'
+ . t('Remove this filter')
+ . '">'
+ . $view->icon('cancel')
+ . '</a>';
+ }
+ $filter = $this->filter->isEmpty() ? '' : ': ' . $this->filter;
+ $html .= ($filter ? '<p>' : ' ')
+ . '<a href="' . $editorUrl . '" title="' . $title . '">'
+ . $txt
+ . '</a>'
+ . $this->shorten($filter, 72)
+ . $remove
+ . ($filter ? '</p>' : '');
+
+ return $html;
+ }
+
+ protected function shorten($string, $length)
+ {
+ if (strlen($string) > $length) {
+ return substr($string, 0, $length) . '...';
+ }
+ return $string;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Limiter.php b/library/Icinga/Web/Widget/Limiter.php
new file mode 100644
index 0000000..d127aca
--- /dev/null
+++ b/library/Icinga/Web/Widget/Limiter.php
@@ -0,0 +1,54 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Forms\Control\LimiterControlForm;
+
+/**
+ * Limiter control widget
+ */
+class Limiter extends AbstractWidget
+{
+ /**
+ * Default limit for this instance
+ *
+ * @var int|null
+ */
+ protected $defaultLimit;
+
+ /**
+ * Get the default limit
+ *
+ * @return int|null
+ */
+ public function getDefaultLimit()
+ {
+ return $this->defaultLimit;
+ }
+
+ /**
+ * Set the default limit
+ *
+ * @param int $defaultLimit
+ *
+ * @return $this
+ */
+ public function setDefaultLimit($defaultLimit)
+ {
+ $this->defaultLimit = (int) $defaultLimit;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ $control = new LimiterControlForm();
+ $control
+ ->setDefaultLimit($this->defaultLimit)
+ ->handleRequest();
+ return (string)$control;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Paginator.php b/library/Icinga/Web/Widget/Paginator.php
new file mode 100644
index 0000000..5f3ef04
--- /dev/null
+++ b/library/Icinga/Web/Widget/Paginator.php
@@ -0,0 +1,167 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Data\Paginatable;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Paginator
+ */
+class Paginator extends AbstractWidget
+{
+ /**
+ * The query the paginator widget is created for
+ *
+ * @var Paginatable
+ */
+ protected $query;
+
+ /**
+ * The view script in use
+ *
+ * @var string|array
+ */
+ protected $viewScript = array('mixedPagination.phtml', 'default');
+
+ /**
+ * Set the query to create the paginator widget for
+ *
+ * @param Paginatable $query
+ *
+ * @return $this
+ */
+ public function setQuery(Paginatable $query)
+ {
+ $this->query = $query;
+ return $this;
+ }
+
+ /**
+ * Set the view script to use
+ *
+ * @param string|array $script
+ *
+ * @return $this
+ */
+ public function setViewScript($script)
+ {
+ $this->viewScript = $script;
+ return $this;
+ }
+
+ /**
+ * Render this paginator
+ */
+ public function render()
+ {
+ if ($this->query === null) {
+ throw new ProgrammingError('Need a query to create the paginator widget for');
+ }
+
+ $itemCountPerPage = $this->query->getLimit();
+ if (! $itemCountPerPage) {
+ return ''; // No pagination required
+ }
+
+ $totalItemCount = count($this->query);
+ $pageCount = (int) ceil($totalItemCount / $itemCountPerPage);
+ $currentPage = $this->query->hasOffset() ? ($this->query->getOffset() / $itemCountPerPage) + 1 : 1;
+ $pagesInRange = $this->getPages($pageCount, $currentPage);
+ $variables = array(
+ 'totalItemCount' => $totalItemCount,
+ 'pageCount' => $pageCount,
+ 'itemCountPerPage' => $itemCountPerPage,
+ 'first' => 1,
+ 'current' => $currentPage,
+ 'last' => $pageCount,
+ 'pagesInRange' => $pagesInRange,
+ 'firstPageInRange' => min($pagesInRange),
+ 'lastPageInRange' => max($pagesInRange)
+ );
+
+ if ($currentPage > 1) {
+ $variables['previous'] = $currentPage - 1;
+ }
+
+ if ($currentPage < $pageCount) {
+ $variables['next'] = $currentPage + 1;
+ }
+
+ if (is_array($this->viewScript)) {
+ if ($this->viewScript[1] !== null) {
+ return $this->view()->partial($this->viewScript[0], $this->viewScript[1], $variables);
+ }
+
+ return $this->view()->partial($this->viewScript[0], $variables);
+ }
+
+ return $this->view()->partial($this->viewScript, $variables);
+ }
+
+ /**
+ * Returns an array of "local" pages given the page count and current page number
+ *
+ * @return array
+ */
+ protected function getPages($pageCount, $currentPage)
+ {
+ $range = array();
+
+ if ($pageCount < 10) {
+ // Show all pages if we have less than 10
+ for ($i = 1; $i < 10; $i++) {
+ if ($i > $pageCount) {
+ break;
+ }
+
+ $range[$i] = $i;
+ }
+ } else {
+ // More than 10 pages:
+ foreach (array(1, 2) as $i) {
+ $range[$i] = $i;
+ }
+
+ if ($currentPage < 6) {
+ // We are on page 1-5 from
+ for ($i = 1; $i <= 7; $i++) {
+ $range[$i] = $i;
+ }
+ } else {
+ // Current page > 5
+ $range[] = '...';
+
+ if (($pageCount - $currentPage) < 5) {
+ // Less than 5 pages left
+ $start = 5 - ($pageCount - $currentPage);
+ } else {
+ $start = 1;
+ }
+
+ for ($i = $currentPage - $start; $i < ($currentPage + (4 - $start)); $i++) {
+ if ($i > $pageCount) {
+ break;
+ }
+
+ $range[$i] = $i;
+ }
+ }
+
+ if ($currentPage < ($pageCount - 2)) {
+ $range[] = '...';
+ }
+
+ foreach (array($pageCount - 1, $pageCount) as $i) {
+ $range[$i] = $i;
+ }
+ }
+
+ if (empty($range)) {
+ $range[] = 1;
+ }
+
+ return $range;
+ }
+}
diff --git a/library/Icinga/Web/Widget/SearchDashboard.php b/library/Icinga/Web/Widget/SearchDashboard.php
new file mode 100644
index 0000000..1ce4c46
--- /dev/null
+++ b/library/Icinga/Web/Widget/SearchDashboard.php
@@ -0,0 +1,111 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Zend_Controller_Action_Exception;
+use Icinga\Application\Icinga;
+use Icinga\Web\Url;
+
+/**
+ * Class SearchDashboard display multiple search views on a single search page
+ */
+class SearchDashboard extends Dashboard
+{
+ /**
+ * Name for the search pane
+ *
+ * @var string
+ */
+ const SEARCH_PANE = 'search';
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTabs()
+ {
+ if ($this->tabs === null) {
+ $this->tabs = new Tabs();
+ $this->tabs->add(
+ 'search',
+ array(
+ 'title' => t('Show Search', 'dashboard.pane.tooltip'),
+ 'label' => t('Search'),
+ 'url' => Url::fromRequest()
+ )
+ );
+ }
+ return $this->tabs;
+ }
+
+ /**
+ * Load all available search dashlets from modules
+ *
+ * @param string $searchString
+ *
+ * @return $this
+ */
+ public function search($searchString = '')
+ {
+ $pane = $this->createPane(self::SEARCH_PANE)->getPane(self::SEARCH_PANE)->setTitle(t('Search'));
+ $this->activate(self::SEARCH_PANE);
+
+ $manager = Icinga::app()->getModuleManager();
+ $searchUrls = array();
+
+ foreach ($manager->getLoadedModules() as $module) {
+ if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) {
+ $moduleSearchUrls = $module->getSearchUrls();
+ if (! empty($moduleSearchUrls)) {
+ if ($searchString === '') {
+ $pane->add(t('Ready to search'), 'search/hint');
+ return $this;
+ }
+ $searchUrls = array_merge($searchUrls, $moduleSearchUrls);
+ }
+ }
+ }
+
+ usort($searchUrls, array($this, 'compareSearchUrls'));
+
+ foreach (array_reverse($searchUrls) as $searchUrl) {
+ $pane->createDashlet(
+ $searchUrl->title . ': ' . $searchString,
+ Url::fromPath($searchUrl->url, array('q' => $searchString))
+ )->setProgressLabel(t('Searching'));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Renders the output
+ *
+ * @return string
+ *
+ * @throws Zend_Controller_Action_Exception
+ */
+ public function render()
+ {
+ if (! $this->getPane(self::SEARCH_PANE)->hasDashlets()) {
+ throw new Zend_Controller_Action_Exception(t('Page not found'), 404);
+ }
+ return parent::render();
+ }
+
+ /**
+ * Compare search URLs based on their priority
+ *
+ * @param object $a
+ * @param object $b
+ *
+ * @return int
+ */
+ private function compareSearchUrls($a, $b)
+ {
+ if ($a->priority === $b->priority) {
+ return 0;
+ }
+ return ($a->priority < $b->priority) ? -1 : 1;
+ }
+}
diff --git a/library/Icinga/Web/Widget/SingleValueSearchControl.php b/library/Icinga/Web/Widget/SingleValueSearchControl.php
new file mode 100644
index 0000000..470518c
--- /dev/null
+++ b/library/Icinga/Web/Widget/SingleValueSearchControl.php
@@ -0,0 +1,200 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Application\Icinga;
+use ipl\Html\Attributes;
+use ipl\Html\Form;
+use ipl\Html\FormElement\InputElement;
+use ipl\Html\HtmlElement;
+use ipl\Web\Control\SearchBar\Suggestions;
+use ipl\Web\Url;
+
+class SingleValueSearchControl extends Form
+{
+ /** @var string */
+ const DEFAULT_SEARCH_PARAMETER = 'q';
+
+ protected $defaultAttributes = ['class' => 'icinga-controls inline'];
+
+ /** @var string */
+ protected $searchParameter = self::DEFAULT_SEARCH_PARAMETER;
+
+ /** @var string */
+ protected $inputLabel;
+
+ /** @var string */
+ protected $submitLabel;
+
+ /** @var Url */
+ protected $suggestionUrl;
+
+ /** @var array */
+ protected $metaDataNames;
+
+ /**
+ * Set the search parameter to use
+ *
+ * @param string $name
+ * @return $this
+ */
+ public function setSearchParameter($name)
+ {
+ $this->searchParameter = $name;
+
+ return $this;
+ }
+
+ /**
+ * Set the input's label
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setInputLabel($label)
+ {
+ $this->inputLabel = $label;
+
+ return $this;
+ }
+
+ /**
+ * Set the submit button's label
+ *
+ * @param string $label
+ *
+ * @return $this
+ */
+ public function setSubmitLabel($label)
+ {
+ $this->submitLabel = $label;
+
+ return $this;
+ }
+
+ /**
+ * Set the suggestion url
+ *
+ * @param Url $url
+ *
+ * @return $this
+ */
+ public function setSuggestionUrl(Url $url)
+ {
+ $this->suggestionUrl = $url;
+
+ return $this;
+ }
+
+ /**
+ * Set names for which hidden meta data elements should be created
+ *
+ * @param string ...$names
+ *
+ * @return $this
+ */
+ public function setMetaDataNames(...$names)
+ {
+ $this->metaDataNames = $names;
+
+ return $this;
+ }
+
+ protected function assemble()
+ {
+ $suggestionsId = Icinga::app()->getRequest()->protectId('single-value-suggestions');
+
+ $this->addElement(
+ 'text',
+ $this->searchParameter,
+ [
+ 'required' => true,
+ 'minlength' => 1,
+ 'autocomplete' => 'off',
+ 'class' => 'search',
+ 'data-enrichment-type' => 'completion',
+ 'data-term-suggestions' => '#' . $suggestionsId,
+ 'data-suggest-url' => $this->suggestionUrl,
+ 'placeholder' => $this->inputLabel
+ ]
+ );
+
+ if (! empty($this->metaDataNames)) {
+ $fieldset = new HtmlElement('fieldset');
+ foreach ($this->metaDataNames as $name) {
+ $hiddenElement = $this->createElement('hidden', $this->searchParameter . '-' . $name);
+ $this->registerElement($hiddenElement);
+ $fieldset->addHtml($hiddenElement);
+ }
+
+ $this->getElement($this->searchParameter)->prependWrapper($fieldset);
+ }
+
+ $this->addElement(
+ 'submit',
+ 'btn_sumit',
+ [
+ 'label' => $this->submitLabel,
+ 'class' => 'btn-primary'
+ ]
+ );
+
+ $this->add(HtmlElement::create('div', [
+ 'id' => $suggestionsId,
+ 'class' => 'search-suggestions'
+ ]));
+ }
+
+ /**
+ * Create a list of search suggestions based on the given groups
+ *
+ * @param array $groups
+ *
+ * @return HtmlElement
+ */
+ public static function createSuggestions(array $groups)
+ {
+ $ul = new HtmlElement('ul');
+ foreach ($groups as list($name, $entries)) {
+ if ($name) {
+ if ($entries === false) {
+ $ul->addHtml(HtmlElement::create('li', ['class' => 'failure-message'], [
+ HtmlElement::create('em', null, t('Can\'t search:')),
+ $name
+ ]));
+ continue;
+ } elseif (empty($entries)) {
+ $ul->addHtml(HtmlElement::create('li', ['class' => 'failure-message'], [
+ HtmlElement::create('em', null, t('No results:')),
+ $name
+ ]));
+ continue;
+ } else {
+ $ul->addHtml(
+ HtmlElement::create('li', ['class' => Suggestions::SUGGESTION_TITLE_CLASS], $name)
+ );
+ }
+ }
+
+ $index = 0;
+ foreach ($entries as list($label, $metaData)) {
+ $attributes = [
+ 'value' => $label,
+ 'type' => 'button',
+ 'tabindex' => -1
+ ];
+ foreach ($metaData as $key => $value) {
+ $attributes['data-' . $key] = $value;
+ }
+
+ $liAtrs = ['class' => $index === 0 ? 'default' : null];
+ $ul->addHtml(new HtmlElement('li', Attributes::create($liAtrs), new InputElement(null, $attributes)));
+ $index++;
+ }
+ }
+
+ return $ul;
+ }
+}
diff --git a/library/Icinga/Web/Widget/SortBox.php b/library/Icinga/Web/Widget/SortBox.php
new file mode 100644
index 0000000..72b6f58
--- /dev/null
+++ b/library/Icinga/Web/Widget/SortBox.php
@@ -0,0 +1,260 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Application\Icinga;
+use Icinga\Data\Sortable;
+use Icinga\Data\SortRules;
+use Icinga\Web\Form;
+use Icinga\Web\Request;
+
+/**
+ * SortBox widget
+ *
+ * The "SortBox" Widget allows you to create a generic sort input for sortable views. It automatically creates a select
+ * box with all sort options and a dropbox with the sort direction. It also handles automatic submission of sorting
+ * changes and draws an additional submit button when JavaScript is disabled.
+ *
+ * The constructor takes a string for the component name and an array containing the select options, where the key is
+ * the value to be submitted and the value is the label that will be shown. You then should call setRequest in order
+ * to make sure the form is correctly populated when a request with a sort parameter is being made.
+ *
+ * Call setQuery in case you'll do not want to handle URL parameters manually, but to automatically apply the user's
+ * chosen sort rules on the given sortable query. This will also allow the SortBox to display the user the correct
+ * default sort rules if the given query provides already some sort rules.
+ */
+class SortBox extends AbstractWidget
+{
+ /**
+ * An array containing all sort columns with their associated labels
+ *
+ * @var array
+ */
+ protected $sortFields;
+
+ /**
+ * An array containing default sort directions for specific columns
+ *
+ * The first entry will be used as default sort column.
+ *
+ * @var array
+ */
+ protected $sortDefaults;
+
+ /**
+ * The name used to uniquely identfy the forms being created
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The request to fetch sort rules from
+ *
+ * @var Request
+ */
+ protected $request;
+
+ /**
+ * The query to apply sort rules on
+ *
+ * @var Sortable
+ */
+ protected $query;
+
+ /**
+ * Create a SortBox with the entries from $sortFields
+ *
+ * @param string $name The name for the SortBox
+ * @param array $sortFields An array containing the columns and their labels to be displayed in the SortBox
+ * @param array $sortDefaults An array containing default sort directions for specific columns
+ */
+ public function __construct($name, array $sortFields, array $sortDefaults = null)
+ {
+ $this->name = $name;
+ $this->sortFields = $sortFields;
+ $this->sortDefaults = $sortDefaults;
+ }
+
+ /**
+ * Create a SortBox
+ *
+ * @param string $name The name for the SortBox
+ * @param array $sortFields An array containing the columns and their labels to be displayed in the SortBox
+ * @param array $sortDefaults An array containing default sort directions for specific columns
+ *
+ * @return SortBox
+ */
+ public static function create($name, array $sortFields, array $sortDefaults = null)
+ {
+ return new static($name, $sortFields, $sortDefaults);
+ }
+
+ /**
+ * Set the request to fetch sort rules from
+ *
+ * @param Request $request
+ *
+ * @return $this
+ */
+ public function setRequest($request)
+ {
+ $this->request = $request;
+ return $this;
+ }
+
+ /**
+ * Set the query to apply sort rules on
+ *
+ * @param Sortable $query
+ *
+ * @return $this
+ */
+ public function setQuery(Sortable $query)
+ {
+ $this->query = $query;
+ return $this;
+ }
+
+ /**
+ * Return the default sort rule for the query
+ *
+ * @param string $column An optional column
+ *
+ * @return array An array of two values: $column, $direction
+ */
+ protected function getSortDefaults($column = null)
+ {
+ $direction = null;
+ if (! empty($this->sortDefaults) && ($column === null || isset($this->sortDefaults[$column]))) {
+ if ($column === null) {
+ reset($this->sortDefaults);
+ $column = key($this->sortDefaults);
+ }
+
+ $direction = $this->sortDefaults[$column];
+ } elseif ($this->query !== null && $this->query instanceof SortRules) {
+ $sortRules = $this->query->getSortRules();
+ if ($column === null) {
+ $column = key($sortRules);
+ }
+
+ if ($column !== null && isset($sortRules[$column]['order'])) {
+ $direction = strtoupper($sortRules[$column]['order']) === Sortable::SORT_DESC ? 'desc' : 'asc';
+ }
+ } elseif ($column === null) {
+ reset($this->sortFields);
+ $column = key($this->sortFields);
+ }
+
+ return array($column, $direction);
+ }
+
+ /**
+ * Apply the sort rules from the given or current request on the query
+ *
+ * @param Request $request
+ *
+ * @return $this
+ */
+ public function handleRequest(Request $request = null)
+ {
+ if ($this->query !== null) {
+ if ($request === null) {
+ $request = Icinga::app()->getRequest();
+ }
+
+ if (! ($sort = $request->getParam('sort'))) {
+ list($sort, $dir) = $this->getSortDefaults();
+ } else {
+ list($_, $dir) = $this->getSortDefaults($sort);
+ }
+
+ $this->query->order($sort, $request->getParam('dir', $dir));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Render this SortBox as HTML
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $columnForm = new Form();
+ $columnForm->setTokenDisabled();
+ $columnForm->setName($this->name . '-column');
+ $columnForm->setAttrib('class', 'icinga-controls inline');
+ $columnForm->addElement(
+ 'select',
+ 'sort',
+ array(
+ 'autosubmit' => true,
+ 'label' => $this->view()->translate('Sort by'),
+ 'multiOptions' => $this->sortFields,
+ 'decorators' => array(
+ array('ViewHelper'),
+ array('Label')
+ )
+ )
+ );
+
+ $column = null;
+ if ($this->request) {
+ $url = $this->request->getUrl();
+ if ($url->hasParam('sort')) {
+ $column = $url->getParam('sort');
+
+ if ($url->hasParam('dir')) {
+ $direction = $url->getParam('dir');
+ } else {
+ list($_, $direction) = $this->getSortDefaults($column);
+ }
+ } elseif ($url->hasParam('dir')) {
+ $direction = $url->getParam('dir');
+ list($column, $_) = $this->getSortDefaults();
+ }
+ }
+
+ if ($column === null) {
+ list($column, $direction) = $this->getSortDefaults();
+ }
+
+ // TODO(el): ToggleButton :)
+ $toggle = array('asc' => 'sort-name-down', 'desc' => 'sort-name-up');
+ unset($toggle[isset($direction) ? strtolower($direction) : 'asc']);
+ $newDirection = key($toggle);
+ $icon = current($toggle);
+
+ $orderForm = new Form();
+ $orderForm->setTokenDisabled();
+ $orderForm->setName($this->name . '-order');
+ $orderForm->setAttrib('class', 'inline sort-direction-control');
+ $orderForm->addElement(
+ 'hidden',
+ 'dir'
+ );
+ $orderForm->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'ignore' => true,
+ 'type' => 'submit',
+ 'label' => $this->view()->icon($icon),
+ 'decorators' => array('ViewHelper'),
+ 'escape' => false,
+ 'class' => 'link-button spinner',
+ 'value' => 'submit',
+ 'title' => t('Change sort direction'),
+ )
+ );
+
+
+ $columnForm->populate(array('sort' => $column));
+ $orderForm->populate(array('dir' => $newDirection));
+ return '<div class="sort-control">' . $columnForm . $orderForm . '</div>';
+ }
+}
diff --git a/library/Icinga/Web/Widget/Tab.php b/library/Icinga/Web/Widget/Tab.php
new file mode 100644
index 0000000..d2d3f32
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tab.php
@@ -0,0 +1,323 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Web\Url;
+
+/**
+ * A single tab, usually used through the tabs widget
+ *
+ * Will generate an &lt;li&gt; list item, with an optional link and icon
+ *
+ * @property string $name Tab identifier
+ * @property string $title Tab title
+ * @property string $icon Icon URL, preferrably relative to the Icinga
+ * base URL
+ * @property string $url Action URL, preferrably relative to the Icinga
+ * base URL
+ * @property string $urlParams Action URL Parameters
+ *
+ */
+class Tab extends AbstractWidget
+{
+ /**
+ * Whether this tab is currently active
+ *
+ * @var bool
+ */
+ private $active = false;
+
+ /**
+ * Default values for widget properties
+ *
+ * @var array
+ */
+ private $name = null;
+
+ /**
+ * The title displayed for this tab
+ *
+ * @var string
+ */
+ private $title = '';
+
+ /**
+ * The label displayed for this tab
+ *
+ * @var string
+ */
+ private $label = '';
+
+ /**
+ * The Url this tab points to
+ *
+ * @var string|null
+ */
+ private $url = null;
+
+ /**
+ * The parameters for this tab's Url
+ *
+ * @var array
+ */
+ private $urlParams = array();
+
+ /**
+ * The icon image to use for this tab or null if none
+ *
+ * @var string|null
+ */
+ private $icon = null;
+
+ /**
+ * The icon class to use if $icon is null
+ *
+ * @var string|null
+ */
+ private $iconCls = null;
+
+ /**
+ * Additional a tag attributes
+ *
+ * @var array
+ */
+ private $tagParams;
+
+ /**
+ * Whether to open the link target on a new page
+ *
+ * @var boolean
+ */
+ private $targetBlank = false;
+
+ /**
+ * Data base target that determines if the link will be opened in a side-bar or in the main container
+ *
+ * @var null
+ */
+ private $baseTarget = null;
+
+ /**
+ * Sets an icon image for this tab
+ *
+ * @param string $icon The url of the image to use
+ */
+ public function setIcon($icon)
+ {
+ if (is_string($icon) && strpos($icon, '.') !== false) {
+ $icon = Url::fromPath($icon);
+ }
+ $this->icon = $icon;
+ }
+
+ /**
+ * Set's an icon class that will be used in an <i> tag if no icon image is set
+ *
+ * @param string $iconCls The CSS class of the icon to use
+ */
+ public function setIconCls($iconCls)
+ {
+ $this->iconCls = $iconCls;
+ }
+
+ /**
+ * @param mixed $name
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the tab label
+ *
+ * @param string $label
+ */
+ public function setLabel($label)
+ {
+ $this->label = $label;
+ }
+
+ /**
+ * Get the tab label
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ if (! $this->label) {
+ return $this->title;
+ }
+
+ return $this->label;
+ }
+
+ /**
+ * @param mixed $title
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ }
+
+ /**
+ * Set the Url this tab points to
+ *
+ * @param string $url The Url to use for this tab
+ */
+ public function setUrl($url)
+ {
+ if (is_string($url)) {
+ $url = Url::fromPath($url);
+ }
+ $this->url = $url;
+ }
+
+ /**
+ * Get the tab's target URL
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set the parameters to be set for this tabs Url
+ *
+ * @param array $url The Url parameters to set
+ */
+ public function setUrlParams(array $urlParams)
+ {
+ $this->urlParams = $urlParams;
+ }
+
+ /**
+ * Set additional a tag attributes
+ *
+ * @param array $tagParams
+ */
+ public function setTagParams(array $tagParams)
+ {
+ $this->tagParams = $tagParams;
+ }
+
+ public function setTargetBlank($value = true)
+ {
+ $this->targetBlank = $value;
+ }
+
+ public function setBaseTarget($value)
+ {
+ $this->baseTarget = $value;
+ }
+
+ /**
+ * Create a new Tab with the given properties
+ *
+ * Allowed properties are all properties for which a setter exists
+ *
+ * @param array $properties An array of properties
+ */
+ public function __construct(array $properties = array())
+ {
+ foreach ($properties as $name => $value) {
+ $setter = 'set' . ucfirst($name);
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ }
+ }
+ }
+
+ /**
+ * Set this tab active (default) or inactive
+ *
+ * This is usually done through the tabs container widget, therefore it
+ * is not a good idea to directly call this function
+ *
+ * @param bool $active Whether the tab should be active
+ *
+ * @return $this
+ */
+ public function setActive($active = true)
+ {
+ $this->active = (bool) $active;
+ return $this;
+ }
+
+ /**
+ * @see Widget::render()
+ */
+ public function render()
+ {
+ $view = $this->view();
+ $classes = array();
+ if ($this->active) {
+ $classes[] = 'active';
+ }
+
+ $caption = $view->escape($this->getLabel());
+ $tagParams = $this->tagParams;
+ if ($this->targetBlank) {
+ // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
+ $caption .= '<span class="info-box display-on-hover"> opens in new window </span>';
+ $tagParams['target'] ='_blank';
+ }
+
+ if ($this->title) {
+ if ($tagParams !== null) {
+ $tagParams['title'] = $this->title;
+ $tagParams['aria-label'] = $this->title;
+ } else {
+ $tagParams = array(
+ 'title' => $this->title,
+ 'aria-label' => $this->title
+ );
+ }
+ }
+
+ if ($this->baseTarget !== null) {
+ $tagParams['data-base-target'] = $this->baseTarget;
+ }
+
+ if ($this->icon !== null) {
+ if (strpos($this->icon, '.') === false) {
+ $caption = $view->icon($this->icon) . $caption;
+ } else {
+ $caption = $view->img($this->icon, null, array('class' => 'icon')) . $caption;
+ }
+ }
+
+ if ($this->url !== null) {
+ $this->url->overwriteParams($this->urlParams);
+
+ if ($tagParams !== null) {
+ $params = $view->propertiesToString($tagParams);
+ } else {
+ $params = '';
+ }
+
+ $tab = sprintf(
+ '<a href="%s"%s>%s</a>',
+ $this->view()->escape($this->url->getAbsoluteUrl()),
+ $params,
+ $caption
+ );
+ } else {
+ $tab = $caption;
+ }
+
+ $class = empty($classes) ? '' : sprintf(' class="%s"', implode(' ', $classes));
+ return '<li ' . $class . '>' . $tab . "</li>\n";
+ }
+}
diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardAction.php b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php
new file mode 100644
index 0000000..a3e6c43
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabextension/DashboardAction.php
@@ -0,0 +1,35 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Tabextension;
+
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Tabextension that allows to add the current URL to a dashboard
+ *
+ * Displayed as a dropdown field in the tabs
+ */
+class DashboardAction implements Tabextension
+{
+ /**
+ * Applies the dashboard actions to the provided tabset
+ *
+ * @param Tabs $tabs The tabs object to extend with
+ */
+ public function apply(Tabs $tabs)
+ {
+ $tabs->addAsDropdown(
+ 'dashboard',
+ array(
+ 'icon' => 'dashboard',
+ 'label' => t('Add To Dashboard'),
+ 'url' => Url::fromPath('dashboard/new-dashlet'),
+ 'urlParams' => array(
+ 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl())
+ )
+ )
+ );
+ }
+}
diff --git a/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php b/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php
new file mode 100644
index 0000000..fc7412a
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php
@@ -0,0 +1,39 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Tabextension;
+
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Dashboard settings
+ */
+class DashboardSettings implements Tabextension
+{
+ /**
+ * Apply this tabextension to the provided tabs
+ *
+ * @param Tabs $tabs The tabbar to modify
+ */
+ public function apply(Tabs $tabs)
+ {
+ $tabs->addAsDropdown(
+ 'dashboard_add',
+ array(
+ 'icon' => 'dashboard',
+ 'label' => t('Add Dashlet'),
+ 'url' => Url::fromPath('dashboard/new-dashlet')
+ )
+ );
+
+ $tabs->addAsDropdown(
+ 'dashboard_settings',
+ array(
+ 'icon' => 'dashboard',
+ 'label' => t('Settings'),
+ 'url' => Url::fromPath('dashboard/settings')
+ )
+ );
+ }
+}
diff --git a/library/Icinga/Web/Widget/Tabextension/MenuAction.php b/library/Icinga/Web/Widget/Tabextension/MenuAction.php
new file mode 100644
index 0000000..d713892
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabextension/MenuAction.php
@@ -0,0 +1,35 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Tabextension;
+
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Tabextension that allows to add the current URL as menu entry
+ *
+ * Displayed as a dropdown field in the tabs
+ */
+class MenuAction implements Tabextension
+{
+ /**
+ * Applies the menu actions to the provided tabset
+ *
+ * @param Tabs $tabs The tabs object to extend with
+ */
+ public function apply(Tabs $tabs)
+ {
+ $tabs->addAsDropdown(
+ 'menu-entry',
+ array(
+ 'icon' => 'menu',
+ 'label' => t('Add To Menu'),
+ 'url' => Url::fromPath('navigation/add'),
+ 'urlParams' => array(
+ 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl())
+ )
+ )
+ );
+ }
+}
diff --git a/library/Icinga/Web/Widget/Tabextension/OutputFormat.php b/library/Icinga/Web/Widget/Tabextension/OutputFormat.php
new file mode 100644
index 0000000..d5d83af
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabextension/OutputFormat.php
@@ -0,0 +1,114 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Tabextension;
+
+use Icinga\Application\Platform;
+use Icinga\Application\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tab;
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Tabextension that offers different output formats for the user in the dropdown area
+ */
+class OutputFormat implements Tabextension
+{
+ /**
+ * PDF output type
+ */
+ const TYPE_PDF = 'pdf';
+
+ /**
+ * JSON output type
+ */
+ const TYPE_JSON = 'json';
+
+ /**
+ * CSV output type
+ */
+ const TYPE_CSV = 'csv';
+
+ /**
+ * An array of tabs to be added to the dropdown area
+ *
+ * @var array
+ */
+ private $tabs = array();
+
+ /**
+ * Create a new OutputFormat extender
+ *
+ * In general, it's assumed that all types are supported when an outputFormat extension
+ * is added, so this class offers to remove specific types instead of adding ones
+ *
+ * @param array $disabled An array of output types to <b>not</b> show.
+ */
+ public function __construct(array $disabled = array())
+ {
+ foreach ($this->getSupportedTypes() as $type => $tabConfig) {
+ if (!in_array($type, $disabled)) {
+ $tabConfig['url'] = Url::fromRequest();
+ $tab = new Tab($tabConfig);
+ $tab->setTargetBlank();
+ $this->tabs[] = $tab;
+ }
+ }
+ }
+
+ /**
+ * Applies the format selectio to the provided tabset
+ *
+ * @param Tabs $tabs The tabs object to extend with
+ *
+ * @see Tabextension::apply()
+ */
+ public function apply(Tabs $tabs)
+ {
+ foreach ($this->tabs as $tab) {
+ $tabs->addAsDropdown($tab->getName(), $tab);
+ }
+ }
+
+ /**
+ * Return an array containing the tab definitions for all supported types
+ *
+ * Using array_keys on this array or isset allows to check whether a
+ * requested type is supported
+ *
+ * @return array
+ */
+ public function getSupportedTypes()
+ {
+ $supportedTypes = array();
+
+ $pdfexport = Hook::has('Pdfexport');
+
+ if ($pdfexport || Platform::extensionLoaded('gd')) {
+ $supportedTypes[self::TYPE_PDF] = array(
+ 'name' => 'pdf',
+ 'label' => 'PDF',
+ 'icon' => 'file-pdf',
+ 'urlParams' => array('format' => 'pdf'),
+ );
+ }
+
+ $supportedTypes[self::TYPE_CSV] = array(
+ 'name' => 'csv',
+ 'label' => 'CSV',
+ 'icon' => 'file-excel',
+ 'urlParams' => array('format' => 'csv')
+ );
+
+ if (Platform::extensionLoaded('json')) {
+ $supportedTypes[self::TYPE_JSON] = array(
+ 'name' => 'json',
+ 'label' => 'JSON',
+ 'icon' => 'doc-text',
+ 'urlParams' => array('format' => 'json')
+ );
+ }
+
+ return $supportedTypes;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Tabextension/Tabextension.php b/library/Icinga/Web/Widget/Tabextension/Tabextension.php
new file mode 100644
index 0000000..ea49c4b
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabextension/Tabextension.php
@@ -0,0 +1,25 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Tabextension;
+
+use Icinga\Web\Widget\Tabs;
+
+/**
+ * Tabextension interface that allows to extend a tabbar with reusable components
+ *
+ * Tabs can be either extended by creating a `Tabextension` and calling the `apply()` method
+ * or by calling the `\Icinga\Web\Widget\Tabs` `extend()` method and providing
+ * a tab extension
+ *
+ * @see \Icinga\Web\Widget\Tabs::extend()
+ */
+interface Tabextension
+{
+ /**
+ * Apply this tabextension to the provided tabs
+ *
+ * @param Tabs $tabs The tabbar to modify
+ */
+ public function apply(Tabs $tabs);
+}
diff --git a/library/Icinga/Web/Widget/Tabs.php b/library/Icinga/Web/Widget/Tabs.php
new file mode 100644
index 0000000..5daf86a
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabs.php
@@ -0,0 +1,445 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Exception\Http\HttpNotFoundException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\Tabextension;
+use Icinga\Application\Icinga;
+use Countable;
+
+/**
+ * Navigation tab widget
+ */
+class Tabs extends AbstractWidget implements Countable
+{
+ /**
+ * Template used for the base tabs
+ *
+ * @var string
+ */
+ private $baseTpl = <<< 'EOT'
+<ul class="tabs primary-nav nav">
+ {TABS}
+ {DROPDOWN}
+ {REFRESH}
+ {CLOSE}
+</ul>
+EOT;
+
+ /**
+ * Template used for the tabs dropdown
+ *
+ * @var string
+ */
+ private $dropdownTpl = <<< 'EOT'
+<li class="dropdown-nav-item">
+ <a href="#" class="dropdown-toggle" title="{TITLE}" aria-label="{TITLE}">
+ <i aria-hidden="true" class="icon-down-open"></i>
+ </a>
+ <ul class="nav">
+ {TABS}
+ </ul>
+</li>
+EOT;
+
+ /**
+ * Template used for the close-button
+ *
+ * @var string
+ */
+ private $closeTpl = <<< 'EOT'
+<li style="float: right;">
+ <a href="#" title="{TITLE}" aria-label="{TITLE}" class="close-container-control">
+ <i aria-hidden="true" class="icon-cancel"></i>
+ </a>
+</li>
+EOT;
+
+ /**
+ * Template used for the refresh icon
+ *
+ * @var string
+ */
+ private $refreshTpl = <<< 'EOT'
+<li>
+ <a class="refresh-container-control spinner" href="{URL}" title="{TITLE}" aria-label="{LABEL}">
+ <i aria-hidden="true" class="icon-cw"></i>
+ </a>
+</li>
+EOT;
+
+ /**
+ * This is where single tabs added to this container will be stored
+ *
+ * @var array
+ */
+ private $tabs = array();
+
+ /**
+ * The name of the currently activated tab
+ *
+ * @var string
+ */
+ private $active;
+
+ /**
+ * Array of tab names which should be displayed in a dropdown
+ *
+ * @var array
+ */
+ private $dropdownTabs = array();
+
+ /**
+ * Whether only the close-button should by rendered for this tab
+ *
+ * @var bool
+ */
+ private $closeButtonOnly = false;
+
+ /**
+ * Whether the tabs should contain a close-button
+ *
+ * @var bool
+ */
+ private $closeTab = true;
+
+ /**
+ * Set whether the current tab is closable
+ */
+ public function hideCloseButton()
+ {
+ $this->closeTab = false;
+ }
+
+ /**
+ * Activate the tab with the given name
+ *
+ * If another tab is currently active it will be deactivated
+ *
+ * @param string $name Name of the tab going to be activated
+ *
+ * @return $this
+ *
+ * @throws HttpNotFoundException When the tab w/ the given name does not exist
+ *
+ */
+ public function activate($name)
+ {
+ if (! $this->has($name)) {
+ throw new HttpNotFoundException('Can\'t activate tab %s. Tab does not exist', $name);
+ }
+
+ if ($this->active !== null) {
+ $this->tabs[$this->active]->setActive(false);
+ }
+ $this->get($name)->setActive();
+ $this->active = $name;
+
+ return $this;
+ }
+
+ /**
+ * Return the name of the active tab
+ *
+ * @return string
+ */
+ public function getActiveName()
+ {
+ return $this->active;
+ }
+
+ /**
+ * Set the CSS class name(s) for the &lt;ul&gt; element
+ *
+ * @param string $name CSS class name(s)
+ *
+ * @return $this
+ */
+ public function setClass($name)
+ {
+ $this->tab_class = $name;
+ return $this;
+ }
+
+ /**
+ * Whether the given tab name exists
+ *
+ * @param string $name Tab name
+ *
+ * @return bool
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->tabs);
+ }
+
+ /**
+ * Whether the given tab name exists
+ *
+ * @param string $name The tab you're interested in
+ *
+ * @return Tab
+ *
+ * @throws ProgrammingError When the given tab name doesn't exist
+ */
+ public function get($name)
+ {
+ if (!$this->has($name)) {
+ return null;
+ }
+ return $this->tabs[$name];
+ }
+
+ /**
+ * Add a new tab
+ *
+ * A unique tab name is required, the Tab itself can either be an array
+ * with tab properties or an instance of an existing Tab
+ *
+ * @param string $name The new tab name
+ * @param array|Tab $tab The tab itself of its properties
+ *
+ * @return $this
+ *
+ * @throws ProgrammingError When the tab name already exists
+ */
+ public function add($name, $tab)
+ {
+ if ($this->has($name)) {
+ throw new ProgrammingError(
+ 'Cannot add a tab named "%s" twice"',
+ $name
+ );
+ }
+ return $this->set($name, $tab);
+ }
+
+ /**
+ * Set a tab
+ *
+ * A unique tab name is required, will be replaced in case it already
+ * exists. The tab can either be an array with tab properties or an instance
+ * of an existing Tab
+ *
+ * @param string $name The new tab name
+ * @param array|Tab $tab The tab itself of its properties
+ *
+ * @return $this
+ */
+ public function set($name, $tab)
+ {
+ if ($tab instanceof Tab) {
+ $this->tabs[$name] = $tab;
+ } else {
+ $this->tabs[$name] = new Tab($tab + array('name' => $name));
+ }
+ return $this;
+ }
+
+ /**
+ * Remove a tab
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function remove($name)
+ {
+ if ($this->has($name)) {
+ unset($this->tabs[$name]);
+ if (($dropdownIndex = array_search($name, $this->dropdownTabs, true)) !== false) {
+ array_splice($this->dropdownTabs, $dropdownIndex, 1);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a tab to the dropdown on the right side of the tab-bar.
+ *
+ * @param $name
+ * @param $tab
+ */
+ public function addAsDropdown($name, $tab)
+ {
+ $this->set($name, $tab);
+ $this->dropdownTabs[] = $name;
+ $this->dropdownTabs = array_unique($this->dropdownTabs);
+ }
+
+ /**
+ * Render the dropdown area with its tabs and return the resulting HTML
+ *
+ * @return mixed|string
+ */
+ private function renderDropdownTabs()
+ {
+ if (empty($this->dropdownTabs)) {
+ return '';
+ }
+ $tabs = '';
+ foreach ($this->dropdownTabs as $tabname) {
+ $tab = $this->get($tabname);
+ if ($tab === null) {
+ continue;
+ }
+ $tabs .= $tab;
+ }
+ return str_replace(array('{TABS}', '{TITLE}'), array($tabs, t('Dropdown menu')), $this->dropdownTpl);
+ }
+
+ /**
+ * Render all tabs, except the ones in dropdown area and return the resulting HTML
+ *
+ * @return string
+ */
+ private function renderTabs()
+ {
+ $tabs = '';
+ foreach ($this->tabs as $name => $tab) {
+ // ignore tabs added to dropdown
+ if (in_array($name, $this->dropdownTabs)) {
+ continue;
+ }
+ $tabs .= $tab;
+ }
+ return $tabs;
+ }
+
+ private function renderCloseTab()
+ {
+ return str_replace('{TITLE}', t('Close container'), $this->closeTpl);
+ }
+
+ private function renderRefreshTab()
+ {
+ $url = Url::fromRequest();
+ $tab = $this->get($this->getActiveName());
+
+ if ($tab !== null) {
+ $label = $this->view()->escape(
+ $tab->getLabel()
+ );
+ }
+
+ if (! empty($label)) {
+ $caption = $label;
+ } else {
+ $caption = t('Content');
+ }
+
+ $label = sprintf(t('Refresh the %s'), $caption);
+ $title = $label;
+
+ $tpl = str_replace(
+ array(
+ '{URL}',
+ '{TITLE}',
+ '{LABEL}'
+ ),
+ array(
+ $this->view()->escape($url->getAbsoluteUrl()),
+ $title,
+ $label
+ ),
+ $this->refreshTpl
+ );
+
+ return $tpl;
+ }
+
+ /**
+ * Render to HTML
+ *
+ * @see Widget::render
+ */
+ public function render()
+ {
+ if (empty($this->tabs) || true === $this->closeButtonOnly) {
+ $tabs = '';
+ $drop = '';
+ } else {
+ $tabs = $this->renderTabs();
+ $drop = $this->renderDropdownTabs();
+ }
+ $close = $this->closeTab ? $this->renderCloseTab() : '';
+ $refresh = $this->renderRefreshTab();
+
+ return str_replace(
+ array(
+ '{TABS}',
+ '{DROPDOWN}',
+ '{REFRESH}',
+ '{CLOSE}'
+ ),
+ array(
+ $tabs,
+ $drop,
+ $refresh,
+ $close
+ ),
+ $this->baseTpl
+ );
+ }
+
+ public function __toString()
+ {
+ try {
+ $html = $this->render(Icinga::app()->getViewRenderer()->view);
+ } catch (Exception $e) {
+ return htmlspecialchars($e->getMessage());
+ }
+ return $html;
+ }
+
+ /**
+ * Return the number of tabs
+ *
+ * @return int
+ *
+ * @see Countable
+ */
+ public function count(): int
+ {
+ return count($this->tabs);
+ }
+
+ /**
+ * Return all tabs contained in this tab panel
+ *
+ * @return array
+ */
+ public function getTabs()
+ {
+ return $this->tabs;
+ }
+
+ /**
+ * Whether to hide all elements except of the close button
+ *
+ * @param bool $value
+ * @return Tabs fluent interface
+ */
+ public function showOnlyCloseButton($value = true)
+ {
+ $this->closeButtonOnly = $value;
+ return $this;
+ }
+
+ /**
+ * Apply a Tabextension on this tabs object
+ *
+ * @param Tabextension $tabextension
+ *
+ * @return $this
+ */
+ public function extend(Tabextension $tabextension)
+ {
+ $tabextension->apply($this);
+ return $this;
+ }
+}
diff --git a/library/Icinga/Web/Widget/Widget.php b/library/Icinga/Web/Widget/Widget.php
new file mode 100644
index 0000000..879858a
--- /dev/null
+++ b/library/Icinga/Web/Widget/Widget.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget;
+
+use Icinga\Web\View;
+use Zend_View_Abstract;
+
+/**
+ * Abstract class for reusable view elements that can be
+ * rendered to a view
+ *
+ */
+interface Widget
+{
+ /**
+ * Renders this widget via the given view and returns the
+ * HTML as a string
+ *
+ * @param \Zend_View_Abstract $view
+ * @return string
+ */
+ // public function render(Zend_View_Abstract $view);
+}
diff --git a/library/Icinga/Web/Window.php b/library/Icinga/Web/Window.php
new file mode 100644
index 0000000..158483a
--- /dev/null
+++ b/library/Icinga/Web/Window.php
@@ -0,0 +1,125 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Session\SessionNamespace;
+
+class Window
+{
+ const UNDEFINED = 'undefined';
+
+ /** @var Window */
+ protected static $window;
+
+ /** @var string */
+ protected $id;
+
+ /** @var string */
+ protected $containerId;
+
+ public function __construct($id)
+ {
+ $parts = explode('_', $id, 2);
+ if (isset($parts[1])) {
+ $this->id = $parts[0];
+ $this->containerId = $id;
+ } else {
+ $this->id = $id;
+ }
+ }
+
+ /**
+ * Get whether the window's ID is undefined
+ *
+ * @return bool
+ */
+ public function isUndefined()
+ {
+ return $this->id === self::UNDEFINED;
+ }
+
+ /**
+ * Get the window's ID
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Get the container's ID
+ *
+ * @return string
+ */
+ public function getContainerId()
+ {
+ return $this->containerId ?: $this->id;
+ }
+
+ /**
+ * Return a window-aware session by using the given prefix
+ *
+ * @param string $prefix The prefix to use
+ * @param bool $reset Whether to reset any existing session-data
+ *
+ * @return SessionNamespace
+ */
+ public function getSessionNamespace($prefix, $reset = false)
+ {
+ $session = Session::getSession();
+
+ $identifier = $prefix . '_' . $this->getId();
+ if ($reset && $session->hasNamespace($identifier)) {
+ $session->removeNamespace($identifier);
+ }
+
+ $namespace = $session->getNamespace($identifier);
+ $nsUndef = $prefix . '_' . self::UNDEFINED;
+
+ if (! $reset && ! $this->isUndefined() && $session->hasNamespace($nsUndef)) {
+ // We may not have any window-id on the very first request. Now we add
+ // all values from the namespace, that has been created in this case,
+ // to the new one and remove it afterwards.
+ foreach ($session->getNamespace($nsUndef) as $name => $value) {
+ $namespace->set($name, $value);
+ }
+
+ $session->removeNamespace($nsUndef);
+ }
+
+ return $namespace;
+ }
+
+ /**
+ * Generate a random string
+ *
+ * @return string
+ */
+ public static function generateId()
+ {
+ $letters = 'abcefghijklmnopqrstuvwxyz';
+ return substr(str_shuffle($letters), 0, 12);
+ }
+
+ /**
+ * @return Window
+ */
+ public static function getInstance()
+ {
+ if (! isset(static::$window)) {
+ $id = Icinga::app()->getRequest()->getHeader('X-Icinga-WindowId');
+ if (empty($id) || $id === static::UNDEFINED) {
+ Icinga::app()->getResponse()->setOverrideWindowId();
+ $id = static::generateId();
+ }
+
+ static::$window = new Window($id);
+ }
+
+ return static::$window;
+ }
+}
diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php
new file mode 100644
index 0000000..6ba90bc
--- /dev/null
+++ b/library/Icinga/Web/Wizard.php
@@ -0,0 +1,719 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web;
+
+use Icinga\Forms\ConfigForm;
+use LogicException;
+use InvalidArgumentException;
+use Icinga\Web\Session\SessionNamespace;
+use Icinga\Web\Form\Decorator\ElementDoubler;
+
+/**
+ * Container and controller for form based wizards
+ */
+class Wizard
+{
+ /**
+ * An integer describing the wizard's forward direction
+ */
+ const FORWARD = 0;
+
+ /**
+ * An integer describing the wizard's backward direction
+ */
+ const BACKWARD = 1;
+
+ /**
+ * An integer describing that the wizard does not change its position
+ */
+ const NO_CHANGE = 2;
+
+ /**
+ * The name of the button to advance the wizard's position
+ */
+ const BTN_NEXT = 'btn_next';
+
+ /**
+ * The name of the button to rewind the wizard's position
+ */
+ const BTN_PREV = 'btn_prev';
+
+ /**
+ * The name and id of the element for showing the user an activity indicator when advancing the wizard
+ */
+ const PROGRESS_ELEMENT = 'wizard_progress';
+
+ /**
+ * This wizard's parent
+ *
+ * @var Wizard
+ */
+ protected $parent;
+
+ /**
+ * The name of the wizard's current page
+ *
+ * @var string
+ */
+ protected $currentPage;
+
+ /**
+ * The pages being part of this wizard
+ *
+ * @var array
+ */
+ protected $pages = array();
+
+ /**
+ * Initialize a new wizard
+ */
+ public function __construct()
+ {
+ $this->init();
+ }
+
+ /**
+ * Run additional initialization routines
+ *
+ * Should be implemented by subclasses to add pages to the wizard.
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Return this wizard's parent or null in case it has none
+ *
+ * @return Wizard|null
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set this wizard's parent
+ *
+ * @param Wizard $wizard The parent wizard
+ *
+ * @return $this
+ */
+ public function setParent(Wizard $wizard)
+ {
+ $this->parent = $wizard;
+ return $this;
+ }
+
+ /**
+ * Return the pages being part of this wizard
+ *
+ * In case this is a nested wizard a flattened array of all contained pages is returned.
+ *
+ * @return array
+ */
+ public function getPages()
+ {
+ $pages = array();
+ foreach ($this->pages as $page) {
+ if ($page instanceof self) {
+ $pages = array_merge($pages, $page->getPages());
+ } else {
+ $pages[] = $page;
+ }
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Return the page with the given name
+ *
+ * Note that it's also possible to retrieve a nested wizard's page by using this method.
+ *
+ * @param string $name The name of the page to return
+ *
+ * @return null|Form The page or null in case there is no page with the given name
+ */
+ public function getPage($name)
+ {
+ foreach ($this->getPages() as $page) {
+ if ($name === $page->getName()) {
+ return $page;
+ }
+ }
+ }
+
+ /**
+ * Add a new page or wizard to this wizard
+ *
+ * @param Form|Wizard $page The page or wizard to add to the wizard
+ *
+ * @return $this
+ */
+ public function addPage($page)
+ {
+ if (! $page instanceof Form && ! $page instanceof self) {
+ throw InvalidArgumentException(
+ 'The $page argument must be an instance of Icinga\Web\Form '
+ . 'or Icinga\Web\Wizard but is of type: ' . get_class($page)
+ );
+ } elseif ($page instanceof self) {
+ $page->setParent($this);
+ }
+
+ $this->pages[] = $page;
+ return $this;
+ }
+
+ /**
+ * Add multiple pages or wizards to this wizard
+ *
+ * @param array $pages The pages or wizards to add to the wizard
+ *
+ * @return $this
+ */
+ public function addPages(array $pages)
+ {
+ foreach ($pages as $page) {
+ $this->addPage($page);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Assert that this wizard has any pages
+ *
+ * @throws LogicException In case this wizard has no pages
+ */
+ protected function assertHasPages()
+ {
+ $pages = $this->getPages();
+ if (count($pages) < 2) {
+ throw new LogicException("Although Chuck Norris can advance a wizard with less than two pages, you can't.");
+ }
+ }
+
+ /**
+ * Return the current page of this wizard
+ *
+ * @return Form
+ *
+ * @throws LogicException In case the name of the current page currently being set is invalid
+ */
+ public function getCurrentPage()
+ {
+ if ($this->parent) {
+ return $this->parent->getCurrentPage();
+ }
+
+ if ($this->currentPage === null) {
+ $this->assertHasPages();
+ $pages = $this->getPages();
+ $this->currentPage = $this->getSession()->get('current_page', $pages[0]->getName());
+ }
+
+ if (($page = $this->getPage($this->currentPage)) === null) {
+ throw new LogicException(sprintf('No page found with name "%s"', $this->currentPage));
+ }
+
+ return $page;
+ }
+
+ /**
+ * Set the current page of this wizard
+ *
+ * @param Form $page The page to set as current page
+ *
+ * @return $this
+ */
+ public function setCurrentPage(Form $page)
+ {
+ $this->currentPage = $page->getName();
+ $this->getSession()->set('current_page', $this->currentPage);
+ return $this;
+ }
+
+ /**
+ * Setup the given page that is either going to be displayed or validated
+ *
+ * Implement this method in a subclass to populate default values and/or other data required to process the form.
+ *
+ * @param Form $page The page to setup
+ * @param Request $request The current request
+ */
+ public function setupPage(Form $page, Request $request)
+ {
+ }
+
+ /**
+ * Process the given request using this wizard
+ *
+ * Validate the request data using the current page, update the wizard's
+ * position and redirect to the page's redirect url upon success.
+ *
+ * @param Request $request The request to be processed
+ *
+ * @return Request The request supposed to be processed
+ */
+ public function handleRequest(Request $request = null)
+ {
+ $page = $this->getCurrentPage();
+
+ if (($wizard = $this->findWizard($page)) !== null) {
+ return $wizard->handleRequest($request);
+ }
+
+ if ($request === null) {
+ $request = $page->getRequest();
+ }
+
+ $this->setupPage($page, $request);
+ $requestData = $this->getRequestData($page, $request);
+ if ($page->wasSent($requestData)) {
+ if (($requestedPage = $this->getRequestedPage($requestData)) !== null) {
+ $isValid = false;
+ $direction = $this->getDirection($request);
+ if ($direction === static::FORWARD && $page->isValid($requestData)) {
+ $isValid = true;
+ if ($this->isLastPage($page)) {
+ $this->setIsFinished();
+ }
+ } elseif ($direction === static::BACKWARD) {
+ $page->populate($requestData);
+ $isValid = true;
+ }
+
+ if ($isValid) {
+ $pageData = & $this->getPageData();
+ $pageData[$page->getName()] = ConfigForm::transformEmptyValuesToNull($page->getValues());
+ $this->setCurrentPage($this->getNewPage($requestedPage, $page));
+ $page->getResponse()->redirectAndExit($page->getRedirectUrl());
+ }
+ } elseif ($page->getValidatePartial()) {
+ $page->isValidPartial($requestData);
+ } else {
+ $page->populate($requestData);
+ }
+ } elseif (($pageData = $this->getPageData($page->getName())) !== null) {
+ $page->populate($pageData);
+ }
+
+ return $request;
+ }
+
+ /**
+ * Return the wizard for the given page or null if its not part of a wizard
+ *
+ * @param Form $page The page to return its wizard for
+ *
+ * @return Wizard|null
+ */
+ protected function findWizard(Form $page)
+ {
+ foreach ($this->getWizards() as $wizard) {
+ if ($wizard->getPage($page->getName()) === $page) {
+ return $wizard;
+ }
+ }
+ }
+
+ /**
+ * Return this wizard's child wizards
+ *
+ * @return array
+ */
+ protected function getWizards()
+ {
+ $wizards = array();
+ foreach ($this->pages as $pageOrWizard) {
+ if ($pageOrWizard instanceof self) {
+ $wizards[] = $pageOrWizard;
+ }
+ }
+
+ return $wizards;
+ }
+
+ /**
+ * Return the request data based on given form's request method
+ *
+ * @param Form $page The page to fetch the data for
+ * @param Request $request The request to fetch the data from
+ *
+ * @return array
+ */
+ protected function getRequestData(Form $page, Request $request)
+ {
+ if (strtolower($request->getMethod()) === $page->getMethod()) {
+ return $request->{'get' . ($request->isPost() ? 'Post' : 'Query')}();
+ }
+
+ return array();
+ }
+
+ /**
+ * Return the name of the requested page
+ *
+ * @param array $requestData The request's data
+ *
+ * @return null|string The name of the requested page or null in case no page has been requested
+ */
+ protected function getRequestedPage(array $requestData)
+ {
+ if ($this->parent) {
+ return $this->parent->getRequestedPage($requestData);
+ }
+
+ if (isset($requestData[static::BTN_NEXT])) {
+ return $requestData[static::BTN_NEXT];
+ } elseif (isset($requestData[static::BTN_PREV])) {
+ return $requestData[static::BTN_PREV];
+ }
+ }
+
+ /**
+ * Return the direction of this wizard using the given request
+ *
+ * @param Request $request The request to use
+ *
+ * @return int The direction @see Wizard::FORWARD @see Wizard::BACKWARD @see Wizard::NO_CHANGE
+ */
+ protected function getDirection(Request $request = null)
+ {
+ if ($this->parent) {
+ return $this->parent->getDirection($request);
+ }
+
+ $currentPage = $this->getCurrentPage();
+
+ if ($request === null) {
+ $request = $currentPage->getRequest();
+ }
+
+ $requestData = $this->getRequestData($currentPage, $request);
+ if (isset($requestData[static::BTN_NEXT])) {
+ return static::FORWARD;
+ } elseif (isset($requestData[static::BTN_PREV])) {
+ return static::BACKWARD;
+ }
+
+ return static::NO_CHANGE;
+ }
+
+ /**
+ * Return the new page to set as current page
+ *
+ * Permission is checked by verifying that the requested page or its previous page has page data available.
+ * The requested page is automatically permitted without any checks if the origin page is its previous
+ * page or one that occurs later in order.
+ *
+ * @param string $requestedPage The name of the requested page
+ * @param Form $originPage The origin page
+ *
+ * @return Form The new page
+ *
+ * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet
+ */
+ protected function getNewPage($requestedPage, Form $originPage)
+ {
+ if ($this->parent) {
+ return $this->parent->getNewPage($requestedPage, $originPage);
+ }
+
+ if (($page = $this->getPage($requestedPage)) !== null) {
+ $permitted = true;
+
+ $pages = $this->getPages();
+ if (! $this->hasPageData($requestedPage) && ($index = array_search($page, $pages, true)) > 0) {
+ $previousPage = $pages[$index - 1];
+ if ($originPage === null || ($previousPage->getName() !== $originPage->getName()
+ && array_search($originPage, $pages, true) < $index)) {
+ $permitted = $this->hasPageData($previousPage->getName());
+ }
+ }
+
+ if ($permitted) {
+ return $page;
+ }
+ }
+
+ throw new InvalidArgumentException(
+ sprintf('"%s" is either an unknown page or one you are not permitted to view', $requestedPage)
+ );
+ }
+
+ /**
+ * Return the next or previous page based on the given one
+ *
+ * @param Form $page The page to skip
+ *
+ * @return Form
+ */
+ protected function skipPage(Form $page)
+ {
+ if ($this->parent) {
+ return $this->parent->skipPage($page);
+ }
+
+ if ($this->hasPageData($page->getName())) {
+ $pageData = & $this->getPageData();
+ unset($pageData[$page->getName()]);
+ }
+
+ $pages = $this->getPages();
+ if ($this->getDirection() === static::FORWARD) {
+ $nextPage = $pages[array_search($page, $pages, true) + 1];
+ $newPage = $this->getNewPage($nextPage->getName(), $page);
+ } else { // $this->getDirection() === static::BACKWARD
+ $previousPage = $pages[array_search($page, $pages, true) - 1];
+ $newPage = $this->getNewPage($previousPage->getName(), $page);
+ }
+
+ return $newPage;
+ }
+
+ /**
+ * Return whether the given page is this wizard's last page
+ *
+ * @param Form $page The page to check
+ *
+ * @return bool
+ */
+ protected function isLastPage(Form $page)
+ {
+ if ($this->parent) {
+ return $this->parent->isLastPage($page);
+ }
+
+ $pages = $this->getPages();
+ return $page->getName() === end($pages)->getName();
+ }
+
+ /**
+ * Return whether all of this wizard's pages were visited by the user
+ *
+ * The base implementation just verifies that the very last page has page data available.
+ *
+ * @return bool
+ */
+ public function isComplete()
+ {
+ $pages = $this->getPages();
+ return $this->hasPageData($pages[count($pages) - 1]->getName());
+ }
+
+ /**
+ * Set whether this wizard has been completed
+ *
+ * @param bool $state Whether this wizard has been completed
+ *
+ * @return $this
+ */
+ public function setIsFinished($state = true)
+ {
+ $this->getSession()->set('isFinished', $state);
+ return $this;
+ }
+
+ /**
+ * Return whether this wizard has been completed
+ *
+ * @return bool
+ */
+ public function isFinished()
+ {
+ return $this->getSession()->get('isFinished', false);
+ }
+
+ /**
+ * Return the overall page data or one for a particular page
+ *
+ * Note that this method returns by reference so in order to update the
+ * returned array set this method's return value also by reference.
+ *
+ * @param string $pageName The page for which to return the data
+ *
+ * @return array
+ */
+ public function & getPageData($pageName = null)
+ {
+ $session = $this->getSession();
+
+ if (false === isset($session->page_data)) {
+ $session->page_data = array();
+ }
+
+ $pageData = & $session->getByRef('page_data');
+ if ($pageName !== null) {
+ $data = null;
+ if (isset($pageData[$pageName])) {
+ $data = & $pageData[$pageName];
+ }
+
+ return $data;
+ }
+
+ return $pageData;
+ }
+
+ /**
+ * Return whether there is any data for the given page
+ *
+ * @param string $pageName The name of the page to check
+ *
+ * @return bool
+ */
+ public function hasPageData($pageName)
+ {
+ return $this->getPageData($pageName) !== null;
+ }
+
+ /**
+ * Return a session to be used by this wizard
+ *
+ * @return SessionNamespace
+ */
+ public function getSession()
+ {
+ if ($this->parent) {
+ return $this->parent->getSession();
+ }
+
+ return Session::getSession()->getNamespace(get_class($this));
+ }
+
+ /**
+ * Clear the session being used by this wizard
+ */
+ public function clearSession()
+ {
+ $this->getSession()->clear();
+ }
+
+ /**
+ * Add buttons to the given page based on its position in the page-chain
+ *
+ * @param Form $page The page to add the buttons to
+ */
+ protected function addButtons(Form $page)
+ {
+ $pages = $this->getPages();
+ $index = array_search($page, $pages, true);
+ if ($index === 0) {
+ $page->addElement(
+ 'button',
+ static::BTN_NEXT,
+ array(
+ 'class' => 'control-button btn-primary',
+ 'type' => 'submit',
+ 'value' => $pages[1]->getName(),
+ 'label' => t('Next'),
+ 'decorators' => array('ViewHelper', 'Spinner')
+ )
+ );
+ } elseif ($index < count($pages) - 1) {
+ $page->addElement(
+ 'button',
+ static::BTN_PREV,
+ array(
+ 'class' => 'control-button',
+ 'type' => 'submit',
+ 'value' => $pages[$index - 1]->getName(),
+ 'label' => t('Back'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->addElement(
+ 'button',
+ static::BTN_NEXT,
+ array(
+ 'class' => 'control-button btn-primary',
+ 'type' => 'submit',
+ 'value' => $pages[$index + 1]->getName(),
+ 'label' => t('Next'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ } else {
+ $page->addElement(
+ 'button',
+ static::BTN_PREV,
+ array(
+ 'class' => 'control-button',
+ 'type' => 'submit',
+ 'value' => $pages[$index - 1]->getName(),
+ 'label' => t('Back'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->addElement(
+ 'button',
+ static::BTN_NEXT,
+ array(
+ 'class' => 'control-button btn-primary',
+ 'type' => 'submit',
+ 'value' => $page->getName(),
+ 'label' => t('Finish'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ }
+
+ $page->setAttrib('data-progress-element', static::PROGRESS_ELEMENT);
+ $page->addElement(
+ 'note',
+ static::PROGRESS_ELEMENT,
+ array(
+ 'order' => 99, // Ensures that it's shown on the right even if a sub-class adds another button
+ 'decorators' => array(
+ 'ViewHelper',
+ array('Spinner', array('id' => static::PROGRESS_ELEMENT))
+ )
+ )
+ );
+
+ $page->addDisplayGroup(
+ array(static::BTN_PREV, static::BTN_NEXT, static::PROGRESS_ELEMENT),
+ 'buttons',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ new ElementDoubler(array(
+ 'double' => static::BTN_NEXT,
+ 'condition' => static::BTN_PREV,
+ 'placement' => ElementDoubler::PREPEND,
+ 'attributes' => array('tabindex' => -1, 'class' => 'double')
+ )),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'buttons'))
+ )
+ )
+ );
+ }
+
+ /**
+ * Return the current page of this wizard with appropriate buttons being added
+ *
+ * @return Form
+ */
+ public function getForm()
+ {
+ $form = $this->getCurrentPage();
+ $form->create(); // Make sure that buttons are displayed at the very bottom
+ $this->addButtons($form);
+ return $form;
+ }
+
+ /**
+ * Return the current page of this wizard rendered as HTML
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->getForm();
+ }
+}
diff --git a/library/vendor/HTMLPurifier.autoload.php b/library/vendor/HTMLPurifier.autoload.php
new file mode 100644
index 0000000..7a69113
--- /dev/null
+++ b/library/vendor/HTMLPurifier.autoload.php
@@ -0,0 +1,25 @@
+<?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';
+}
+
+// phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.zend_ze1_compatibility_modeRemoved
+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/library/vendor/HTMLPurifier.php b/library/vendor/HTMLPurifier.php
new file mode 100644
index 0000000..26f0612
--- /dev/null
+++ b/library/vendor/HTMLPurifier.php
@@ -0,0 +1,297 @@
+<?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.15.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.15.0';
+
+ /**
+ * Constant with version of HTML Purifier.
+ */
+ const VERSION = '4.15.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();
+ $array = array();
+ foreach($array_of_html as $key=>$value){
+ if (is_array($value)) {
+ $array[$key] = $this->purifyArray($value, $config);
+ } else {
+ $array[$key] = $this->purify($value, $config);
+ }
+ $context_array[$key] = $this->context;
+ }
+ $this->context = $context_array;
+ return $array;
+ }
+
+ /**
+ * 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/library/vendor/HTMLPurifier/Arborize.php b/library/vendor/HTMLPurifier/Arborize.php
new file mode 100644
index 0000000..d2e9d22
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrCollections.php b/library/vendor/HTMLPurifier/AttrCollections.php
new file mode 100644
index 0000000..c7b17cf
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef.php b/library/vendor/HTMLPurifier/AttrDef.php
new file mode 100644
index 0000000..739646f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS.php b/library/vendor/HTMLPurifier/AttrDef/CSS.php
new file mode 100644
index 0000000..ad2cb90
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/library/vendor/HTMLPurifier/AttrDef/CSS/AlphaValue.php
new file mode 100644
index 0000000..af2b83d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Background.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Background.php
new file mode 100644
index 0000000..28c4988
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrDef/CSS/Background.php
@@ -0,0 +1,113 @@
+<?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'];
+ $this->info['background-size'] = $def->info['background-size'];
+ }
+
+ /**
+ * @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;
+ $caught['size'] = 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/library/vendor/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/library/vendor/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
new file mode 100644
index 0000000..4580ef5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Border.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Border.php
new file mode 100644
index 0000000..16243ba
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Color.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Color.php
new file mode 100644
index 0000000..d7287a0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Composite.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Composite.php
new file mode 100644
index 0000000..9c17505
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/library/vendor/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
new file mode 100644
index 0000000..9d77cc9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Filter.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Filter.php
new file mode 100644
index 0000000..bde4c33
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Font.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Font.php
new file mode 100644
index 0000000..579b97e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/FontFamily.php b/library/vendor/HTMLPurifier/AttrDef/CSS/FontFamily.php
new file mode 100644
index 0000000..74e24c8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Ident.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Ident.php
new file mode 100644
index 0000000..973002c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php b/library/vendor/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
new file mode 100644
index 0000000..ffc989f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Length.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Length.php
new file mode 100644
index 0000000..f12453a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/ListStyle.php b/library/vendor/HTMLPurifier/AttrDef/CSS/ListStyle.php
new file mode 100644
index 0000000..e74d426
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Multiple.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Multiple.php
new file mode 100644
index 0000000..e707f87
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/Number.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Number.php
new file mode 100644
index 0000000..ef49d20
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrDef/CSS/Number.php
@@ -0,0 +1,90 @@
+<?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;
+ }
+
+ // Remove leading zeros until positive number or a zero stays left
+ if (ltrim($left, '0') != '') {
+ $left = ltrim($left, '0');
+ } else {
+ $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/library/vendor/HTMLPurifier/AttrDef/CSS/Percentage.php b/library/vendor/HTMLPurifier/AttrDef/CSS/Percentage.php
new file mode 100644
index 0000000..f0f25c5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/library/vendor/HTMLPurifier/AttrDef/CSS/TextDecoration.php
new file mode 100644
index 0000000..5fd4b7f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/CSS/URI.php b/library/vendor/HTMLPurifier/AttrDef/CSS/URI.php
new file mode 100644
index 0000000..6617aca
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/Clone.php b/library/vendor/HTMLPurifier/AttrDef/Clone.php
new file mode 100644
index 0000000..6698a00
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/Enum.php b/library/vendor/HTMLPurifier/AttrDef/Enum.php
new file mode 100644
index 0000000..8abda7f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/Bool.php b/library/vendor/HTMLPurifier/AttrDef/HTML/Bool.php
new file mode 100644
index 0000000..be3bbc8
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrDef/HTML/Bool.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Validates a boolean attribute
+ */
+class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
+{
+
+ /**
+ * @type string
+ */
+ protected $name;
+
+ /**
+ * @type bool
+ */
+ public $minimized = true;
+
+ /**
+ * @param bool|string $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/library/vendor/HTMLPurifier/AttrDef/HTML/Class.php b/library/vendor/HTMLPurifier/AttrDef/HTML/Class.php
new file mode 100644
index 0000000..d501348
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/Color.php b/library/vendor/HTMLPurifier/AttrDef/HTML/Color.php
new file mode 100644
index 0000000..946ebb7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/ContentEditable.php b/library/vendor/HTMLPurifier/AttrDef/HTML/ContentEditable.php
new file mode 100644
index 0000000..5b03d3e
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrDef/HTML/ContentEditable.php
@@ -0,0 +1,16 @@
+<?php
+
+class HTMLPurifier_AttrDef_HTML_ContentEditable extends HTMLPurifier_AttrDef
+{
+ public function validate($string, $config, $context)
+ {
+ $allowed = array('false');
+ if ($config->get('HTML.Trusted')) {
+ $allowed = array('', 'true', 'false');
+ }
+
+ $enum = new HTMLPurifier_AttrDef_Enum($allowed);
+
+ return $enum->validate($string, $config, $context);
+ }
+}
diff --git a/library/vendor/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/library/vendor/HTMLPurifier/AttrDef/HTML/FrameTarget.php
new file mode 100644
index 0000000..d79ba12
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/ID.php b/library/vendor/HTMLPurifier/AttrDef/HTML/ID.php
new file mode 100644
index 0000000..4ba4561
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/Length.php b/library/vendor/HTMLPurifier/AttrDef/HTML/Length.php
new file mode 100644
index 0000000..1c4006f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/library/vendor/HTMLPurifier/AttrDef/HTML/LinkTypes.php
new file mode 100644
index 0000000..63fa04c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/MultiLength.php b/library/vendor/HTMLPurifier/AttrDef/HTML/MultiLength.php
new file mode 100644
index 0000000..bbb20f2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/Nmtokens.php b/library/vendor/HTMLPurifier/AttrDef/HTML/Nmtokens.php
new file mode 100644
index 0000000..f79683b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/HTML/Pixels.php b/library/vendor/HTMLPurifier/AttrDef/HTML/Pixels.php
new file mode 100644
index 0000000..a1d019e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/Integer.php b/library/vendor/HTMLPurifier/AttrDef/Integer.php
new file mode 100644
index 0000000..400e707
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/Lang.php b/library/vendor/HTMLPurifier/AttrDef/Lang.php
new file mode 100644
index 0000000..2a55cea
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/Switch.php b/library/vendor/HTMLPurifier/AttrDef/Switch.php
new file mode 100644
index 0000000..c7eb319
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/Text.php b/library/vendor/HTMLPurifier/AttrDef/Text.php
new file mode 100644
index 0000000..4553a4e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/URI.php b/library/vendor/HTMLPurifier/AttrDef/URI.php
new file mode 100644
index 0000000..c1cd897
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/URI/Email.php b/library/vendor/HTMLPurifier/AttrDef/URI/Email.php
new file mode 100644
index 0000000..daf32b7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/library/vendor/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
new file mode 100644
index 0000000..52c0d59
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/URI/Host.php b/library/vendor/HTMLPurifier/AttrDef/URI/Host.php
new file mode 100644
index 0000000..1beeaa5
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrDef/URI/Host.php
@@ -0,0 +1,142 @@
+<?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')) {
+ if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) {
+ $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
+ } else {
+ $string = idn_to_ascii($string);
+ }
+
+ // 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/library/vendor/HTMLPurifier/AttrDef/URI/IPv4.php b/library/vendor/HTMLPurifier/AttrDef/URI/IPv4.php
new file mode 100644
index 0000000..30ac16c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrDef/URI/IPv6.php b/library/vendor/HTMLPurifier/AttrDef/URI/IPv6.php
new file mode 100644
index 0000000..f243793
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform.php b/library/vendor/HTMLPurifier/AttrTransform.php
new file mode 100644
index 0000000..b428331
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Background.php b/library/vendor/HTMLPurifier/AttrTransform/Background.php
new file mode 100644
index 0000000..2f72869
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/BdoDir.php b/library/vendor/HTMLPurifier/AttrTransform/BdoDir.php
new file mode 100644
index 0000000..d66c04a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/BgColor.php b/library/vendor/HTMLPurifier/AttrTransform/BgColor.php
new file mode 100644
index 0000000..0f51fd2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/BoolToCSS.php b/library/vendor/HTMLPurifier/AttrTransform/BoolToCSS.php
new file mode 100644
index 0000000..f25cd01
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Border.php b/library/vendor/HTMLPurifier/AttrTransform/Border.php
new file mode 100644
index 0000000..057dc01
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/EnumToCSS.php b/library/vendor/HTMLPurifier/AttrTransform/EnumToCSS.php
new file mode 100644
index 0000000..7ccd0e3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/ImgRequired.php b/library/vendor/HTMLPurifier/AttrTransform/ImgRequired.php
new file mode 100644
index 0000000..235ebb3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/ImgSpace.php b/library/vendor/HTMLPurifier/AttrTransform/ImgSpace.php
new file mode 100644
index 0000000..350b335
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Input.php b/library/vendor/HTMLPurifier/AttrTransform/Input.php
new file mode 100644
index 0000000..3ab47ed
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Lang.php b/library/vendor/HTMLPurifier/AttrTransform/Lang.php
new file mode 100644
index 0000000..5b0aff0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Length.php b/library/vendor/HTMLPurifier/AttrTransform/Length.php
new file mode 100644
index 0000000..853f335
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Name.php b/library/vendor/HTMLPurifier/AttrTransform/Name.php
new file mode 100644
index 0000000..63cce68
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/NameSync.php b/library/vendor/HTMLPurifier/AttrTransform/NameSync.php
new file mode 100644
index 0000000..5a1fdbb
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrTransform/NameSync.php
@@ -0,0 +1,46 @@
+<?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
+{
+
+ /**
+ * @type HTMLPurifier_AttrDef_HTML_ID
+ */
+ public $idDef;
+
+ 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/library/vendor/HTMLPurifier/AttrTransform/Nofollow.php b/library/vendor/HTMLPurifier/AttrTransform/Nofollow.php
new file mode 100644
index 0000000..1057ebe
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/SafeEmbed.php b/library/vendor/HTMLPurifier/AttrTransform/SafeEmbed.php
new file mode 100644
index 0000000..231c81a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/SafeObject.php b/library/vendor/HTMLPurifier/AttrTransform/SafeObject.php
new file mode 100644
index 0000000..d1f3a4d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/SafeParam.php b/library/vendor/HTMLPurifier/AttrTransform/SafeParam.php
new file mode 100644
index 0000000..1033106
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrTransform/SafeParam.php
@@ -0,0 +1,84 @@
+<?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;
+
+ /**
+ * @type HTMLPurifier_AttrDef_Enum
+ */
+ public $wmode;
+
+ 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/library/vendor/HTMLPurifier/AttrTransform/ScriptRequired.php b/library/vendor/HTMLPurifier/AttrTransform/ScriptRequired.php
new file mode 100644
index 0000000..b7057bb
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/TargetBlank.php b/library/vendor/HTMLPurifier/AttrTransform/TargetBlank.php
new file mode 100644
index 0000000..dd63ea8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/TargetNoopener.php b/library/vendor/HTMLPurifier/AttrTransform/TargetNoopener.php
new file mode 100644
index 0000000..1db3c6c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/TargetNoreferrer.php b/library/vendor/HTMLPurifier/AttrTransform/TargetNoreferrer.php
new file mode 100644
index 0000000..587dc2e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTransform/Textarea.php b/library/vendor/HTMLPurifier/AttrTransform/Textarea.php
new file mode 100644
index 0000000..6a9f33a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/AttrTypes.php b/library/vendor/HTMLPurifier/AttrTypes.php
new file mode 100644
index 0000000..e4429e8
--- /dev/null
+++ b/library/vendor/HTMLPurifier/AttrTypes.php
@@ -0,0 +1,97 @@
+<?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();
+ $this->info['ContentEditable'] = new HTMLPurifier_AttrDef_HTML_ContentEditable();
+
+ // 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/library/vendor/HTMLPurifier/AttrValidator.php b/library/vendor/HTMLPurifier/AttrValidator.php
new file mode 100644
index 0000000..f97dc93
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Bootstrap.php b/library/vendor/HTMLPurifier/Bootstrap.php
new file mode 100644
index 0000000..707122b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/CSSDefinition.php b/library/vendor/HTMLPurifier/CSSDefinition.php
new file mode 100644
index 0000000..3f08b81
--- /dev/null
+++ b/library/vendor/HTMLPurifier/CSSDefinition.php
@@ -0,0 +1,549 @@
+<?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();
+
+ $this->info['background-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'auto',
+ 'cover',
+ 'contain',
+ 'initial',
+ 'inherit',
+ )
+ ),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ )
+ );
+
+ $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', 'initial', 'inherit'))
+ )
+ );
+ $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true),
+ new HTMLPurifier_AttrDef_Enum(array('initial', 'inherit'))
+ )
+ );
+ $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true),
+ new HTMLPurifier_AttrDef_Enum(array('none', 'initial', 'inherit'))
+ )
+ );
+ $max = $config->get('CSS.MaxImgLength');
+
+ $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['min-width'] =
+ $this->info['min-height'] =
+ $max === null ?
+ $trusted_min_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('initial', 'inherit'))
+ )
+ ),
+ // For everyone else:
+ $trusted_min_wh
+ );
+ $this->info['max-width'] =
+ $this->info['max-height'] =
+ $max === null ?
+ $trusted_max_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('none', 'initial', 'inherit'))
+ )
+ ),
+ // For everyone else:
+ $trusted_max_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/library/vendor/HTMLPurifier/ChildDef.php b/library/vendor/HTMLPurifier/ChildDef.php
new file mode 100644
index 0000000..8eb17b8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/Chameleon.php b/library/vendor/HTMLPurifier/ChildDef/Chameleon.php
new file mode 100644
index 0000000..7439be2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/Custom.php b/library/vendor/HTMLPurifier/ChildDef/Custom.php
new file mode 100644
index 0000000..f515888
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/Empty.php b/library/vendor/HTMLPurifier/ChildDef/Empty.php
new file mode 100644
index 0000000..a8a6cbd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/List.php b/library/vendor/HTMLPurifier/ChildDef/List.php
new file mode 100644
index 0000000..3d584e7
--- /dev/null
+++ b/library/vendor/HTMLPurifier/ChildDef/List.php
@@ -0,0 +1,94 @@
+<?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);
+
+ public $whitespace;
+
+ /**
+ * @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/library/vendor/HTMLPurifier/ChildDef/Optional.php b/library/vendor/HTMLPurifier/ChildDef/Optional.php
new file mode 100644
index 0000000..b946806
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/Required.php b/library/vendor/HTMLPurifier/ChildDef/Required.php
new file mode 100644
index 0000000..0d1c8f5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/StrictBlockquote.php b/library/vendor/HTMLPurifier/ChildDef/StrictBlockquote.php
new file mode 100644
index 0000000..3270a46
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ChildDef/Table.php b/library/vendor/HTMLPurifier/ChildDef/Table.php
new file mode 100644
index 0000000..67c7e95
--- /dev/null
+++ b/library/vendor/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) && $thead === false && $tfoot === false) {
+ 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/library/vendor/HTMLPurifier/Config.php b/library/vendor/HTMLPurifier/Config.php
new file mode 100644
index 0000000..797d268
--- /dev/null
+++ b/library/vendor/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.15.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|null
+ */
+ 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|null
+ */
+ 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|null
+ */
+ 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|null
+ */
+ 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|null
+ */
+ public function maybeGetRawHTMLDefinition()
+ {
+ return $this->getDefinition('HTML', true, true);
+ }
+
+ /**
+ * @return HTMLPurifier_CSSDefinition|null
+ */
+ public function maybeGetRawCSSDefinition()
+ {
+ return $this->getDefinition('CSS', true, true);
+ }
+
+ /**
+ * @return HTMLPurifier_URIDefinition|null
+ */
+ 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 && version_compare(PHP_VERSION, '7.4.0', '<') && 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 (isset($trace[$i + 1]['class']) && $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/library/vendor/HTMLPurifier/ConfigSchema.php b/library/vendor/HTMLPurifier/ConfigSchema.php
new file mode 100644
index 0000000..c3fe8cd
--- /dev/null
+++ b/library/vendor/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_VarParser::$types 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/library/vendor/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/library/vendor/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
new file mode 100644
index 0000000..d5906cd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/Builder/Xml.php b/library/vendor/HTMLPurifier/ConfigSchema/Builder/Xml.php
new file mode 100644
index 0000000..5fa56f7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/Exception.php b/library/vendor/HTMLPurifier/ConfigSchema/Exception.php
new file mode 100644
index 0000000..2671516
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/Interchange.php b/library/vendor/HTMLPurifier/ConfigSchema/Interchange.php
new file mode 100644
index 0000000..0e08ae8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/library/vendor/HTMLPurifier/ConfigSchema/Interchange/Directive.php
new file mode 100644
index 0000000..127a39a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/Interchange/Id.php b/library/vendor/HTMLPurifier/ConfigSchema/Interchange/Id.php
new file mode 100644
index 0000000..126f09d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/library/vendor/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
new file mode 100644
index 0000000..655e6dd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/Validator.php b/library/vendor/HTMLPurifier/ConfigSchema/Validator.php
new file mode 100644
index 0000000..fb31277
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/library/vendor/HTMLPurifier/ConfigSchema/ValidatorAtom.php
new file mode 100644
index 0000000..c9aa364
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema.ser b/library/vendor/HTMLPurifier/ConfigSchema/schema.ser
new file mode 100644
index 0000000..a5426c7
--- /dev/null
+++ b/library/vendor/HTMLPurifier/ConfigSchema/schema.ser
@@ -0,0 +1 @@
+O:25:"HTMLPurifier_ConfigSchema":3:{s:8:"defaults";a:127:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:10:"HTML.Forms";b:0;s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:12:"defaultPlist";O:25:"HTMLPurifier_PropertyList":3:{s:7:"
diff --git a/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
new file mode 100644
index 0000000..0517fed
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
new file mode 100644
index 0000000..249edd6
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
new file mode 100644
index 0000000..9a8fa6a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
new file mode 100644
index 0000000..b017883
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
new file mode 100644
index 0000000..e774b82
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
new file mode 100644
index 0000000..533165e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
new file mode 100644
index 0000000..9eb7e38
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
new file mode 100644
index 0000000..2f17bf4
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
new file mode 100644
index 0000000..52654b5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
new file mode 100644
index 0000000..6440d21
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
new file mode 100644
index 0000000..f31d226
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
new file mode 100644
index 0000000..735d4b7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
new file mode 100644
index 0000000..5f2b5e3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
new file mode 100644
index 0000000..6f58245
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
new file mode 100644
index 0000000..cc49d43
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
new file mode 100644
index 0000000..2c5924a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
new file mode 100644
index 0000000..d5caa1b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
new file mode 100644
index 0000000..2a47648
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
new file mode 100644
index 0000000..663064a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
new file mode 100644
index 0000000..3a48ba9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
new file mode 100644
index 0000000..db58b13
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
new file mode 100644
index 0000000..7996488
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt
new file mode 100644
index 0000000..6367fe2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
new file mode 100644
index 0000000..35c393b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
new file mode 100644
index 0000000..9228dee
--- /dev/null
+++ b/library/vendor/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 %AutoFormat.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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
new file mode 100644
index 0000000..34657ba
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
new file mode 100644
index 0000000..dde990a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt
new file mode 100644
index 0000000..4d054b1
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
new file mode 100644
index 0000000..b324608
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
new file mode 100644
index 0000000..748be0e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
new file mode 100644
index 0000000..3fd4654
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
new file mode 100644
index 0000000..460112e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
new file mode 100644
index 0000000..5cb7dda
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt
new file mode 100644
index 0000000..f1f5c5f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
new file mode 100644
index 0000000..7a32914
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
new file mode 100644
index 0000000..148eedb
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
new file mode 100644
index 0000000..e733a61
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
new file mode 100644
index 0000000..c486724
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
new file mode 100644
index 0000000..5403650
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
new file mode 100644
index 0000000..2e0cc81
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
new file mode 100644
index 0000000..568cbf3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt
new file mode 100644
index 0000000..b2b6ab1
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt
new file mode 100644
index 0000000..2c910cc
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt
new file mode 100644
index 0000000..06278f8
--- /dev/null
+++ b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt
@@ -0,0 +1,12 @@
+Core.AllowParseManyTags
+TYPE: bool
+DEFAULT: false
+VERSION: 4.10.1
+--DESCRIPTION--
+<p>
+ This directive allows parsing of many nested tags.
+ If you set true, relaxes any hardcoded limit from the parser.
+ However, in that case it may cause a Dos attack.
+ Be careful when enabling it.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
new file mode 100644
index 0000000..d731791
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
new file mode 100644
index 0000000..a75844c
--- /dev/null
+++ b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
@@ -0,0 +1,160 @@
+Core.ColorKeywords
+TYPE: hash
+VERSION: 2.0.0
+--DEFAULT--
+array (
+ 'aliceblue' => '#F0F8FF',
+ 'antiquewhite' => '#FAEBD7',
+ 'aqua' => '#00FFFF',
+ 'aquamarine' => '#7FFFD4',
+ 'azure' => '#F0FFFF',
+ 'beige' => '#F5F5DC',
+ 'bisque' => '#FFE4C4',
+ 'black' => '#000000',
+ 'blanchedalmond' => '#FFEBCD',
+ 'blue' => '#0000FF',
+ 'blueviolet' => '#8A2BE2',
+ 'brown' => '#A52A2A',
+ 'burlywood' => '#DEB887',
+ 'cadetblue' => '#5F9EA0',
+ 'chartreuse' => '#7FFF00',
+ 'chocolate' => '#D2691E',
+ 'coral' => '#FF7F50',
+ 'cornflowerblue' => '#6495ED',
+ 'cornsilk' => '#FFF8DC',
+ 'crimson' => '#DC143C',
+ 'cyan' => '#00FFFF',
+ 'darkblue' => '#00008B',
+ 'darkcyan' => '#008B8B',
+ 'darkgoldenrod' => '#B8860B',
+ 'darkgray' => '#A9A9A9',
+ 'darkgrey' => '#A9A9A9',
+ 'darkgreen' => '#006400',
+ 'darkkhaki' => '#BDB76B',
+ 'darkmagenta' => '#8B008B',
+ 'darkolivegreen' => '#556B2F',
+ 'darkorange' => '#FF8C00',
+ 'darkorchid' => '#9932CC',
+ 'darkred' => '#8B0000',
+ 'darksalmon' => '#E9967A',
+ 'darkseagreen' => '#8FBC8F',
+ 'darkslateblue' => '#483D8B',
+ 'darkslategray' => '#2F4F4F',
+ 'darkslategrey' => '#2F4F4F',
+ 'darkturquoise' => '#00CED1',
+ 'darkviolet' => '#9400D3',
+ 'deeppink' => '#FF1493',
+ 'deepskyblue' => '#00BFFF',
+ 'dimgray' => '#696969',
+ 'dimgrey' => '#696969',
+ 'dodgerblue' => '#1E90FF',
+ 'firebrick' => '#B22222',
+ 'floralwhite' => '#FFFAF0',
+ 'forestgreen' => '#228B22',
+ 'fuchsia' => '#FF00FF',
+ 'gainsboro' => '#DCDCDC',
+ 'ghostwhite' => '#F8F8FF',
+ 'gold' => '#FFD700',
+ 'goldenrod' => '#DAA520',
+ 'gray' => '#808080',
+ 'grey' => '#808080',
+ 'green' => '#008000',
+ 'greenyellow' => '#ADFF2F',
+ 'honeydew' => '#F0FFF0',
+ 'hotpink' => '#FF69B4',
+ 'indianred' => '#CD5C5C',
+ 'indigo' => '#4B0082',
+ 'ivory' => '#FFFFF0',
+ 'khaki' => '#F0E68C',
+ 'lavender' => '#E6E6FA',
+ 'lavenderblush' => '#FFF0F5',
+ 'lawngreen' => '#7CFC00',
+ 'lemonchiffon' => '#FFFACD',
+ 'lightblue' => '#ADD8E6',
+ 'lightcoral' => '#F08080',
+ 'lightcyan' => '#E0FFFF',
+ 'lightgoldenrodyellow' => '#FAFAD2',
+ 'lightgray' => '#D3D3D3',
+ 'lightgrey' => '#D3D3D3',
+ 'lightgreen' => '#90EE90',
+ 'lightpink' => '#FFB6C1',
+ 'lightsalmon' => '#FFA07A',
+ 'lightseagreen' => '#20B2AA',
+ 'lightskyblue' => '#87CEFA',
+ 'lightslategray' => '#778899',
+ 'lightslategrey' => '#778899',
+ 'lightsteelblue' => '#B0C4DE',
+ 'lightyellow' => '#FFFFE0',
+ 'lime' => '#00FF00',
+ 'limegreen' => '#32CD32',
+ 'linen' => '#FAF0E6',
+ 'magenta' => '#FF00FF',
+ 'maroon' => '#800000',
+ 'mediumaquamarine' => '#66CDAA',
+ 'mediumblue' => '#0000CD',
+ 'mediumorchid' => '#BA55D3',
+ 'mediumpurple' => '#9370DB',
+ 'mediumseagreen' => '#3CB371',
+ 'mediumslateblue' => '#7B68EE',
+ 'mediumspringgreen' => '#00FA9A',
+ 'mediumturquoise' => '#48D1CC',
+ 'mediumvioletred' => '#C71585',
+ 'midnightblue' => '#191970',
+ 'mintcream' => '#F5FFFA',
+ 'mistyrose' => '#FFE4E1',
+ 'moccasin' => '#FFE4B5',
+ 'navajowhite' => '#FFDEAD',
+ 'navy' => '#000080',
+ 'oldlace' => '#FDF5E6',
+ 'olive' => '#808000',
+ 'olivedrab' => '#6B8E23',
+ 'orange' => '#FFA500',
+ 'orangered' => '#FF4500',
+ 'orchid' => '#DA70D6',
+ 'palegoldenrod' => '#EEE8AA',
+ 'palegreen' => '#98FB98',
+ 'paleturquoise' => '#AFEEEE',
+ 'palevioletred' => '#DB7093',
+ 'papayawhip' => '#FFEFD5',
+ 'peachpuff' => '#FFDAB9',
+ 'peru' => '#CD853F',
+ 'pink' => '#FFC0CB',
+ 'plum' => '#DDA0DD',
+ 'powderblue' => '#B0E0E6',
+ 'purple' => '#800080',
+ 'rebeccapurple' => '#663399',
+ 'red' => '#FF0000',
+ 'rosybrown' => '#BC8F8F',
+ 'royalblue' => '#4169E1',
+ 'saddlebrown' => '#8B4513',
+ 'salmon' => '#FA8072',
+ 'sandybrown' => '#F4A460',
+ 'seagreen' => '#2E8B57',
+ 'seashell' => '#FFF5EE',
+ 'sienna' => '#A0522D',
+ 'silver' => '#C0C0C0',
+ 'skyblue' => '#87CEEB',
+ 'slateblue' => '#6A5ACD',
+ 'slategray' => '#708090',
+ 'slategrey' => '#708090',
+ 'snow' => '#FFFAFA',
+ 'springgreen' => '#00FF7F',
+ 'steelblue' => '#4682B4',
+ 'tan' => '#D2B48C',
+ 'teal' => '#008080',
+ 'thistle' => '#D8BFD8',
+ 'tomato' => '#FF6347',
+ 'turquoise' => '#40E0D0',
+ 'violet' => '#EE82EE',
+ 'wheat' => '#F5DEB3',
+ 'white' => '#FFFFFF',
+ 'whitesmoke' => '#F5F5F5',
+ 'yellow' => '#FFFF00',
+ 'yellowgreen' => '#9ACD32'
+)
+--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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
new file mode 100644
index 0000000..64b114f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
new file mode 100644
index 0000000..36f16e0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt
new file mode 100644
index 0000000..1cd4c2c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt
new file mode 100644
index 0000000..ce243c3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
new file mode 100644
index 0000000..8bfb47c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
new file mode 100644
index 0000000..a3881be
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
new file mode 100644
index 0000000..a7a5b24
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
new file mode 100644
index 0000000..abb4999
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
new file mode 100644
index 0000000..915391e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
new file mode 100644
index 0000000..233fca1
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt
new file mode 100644
index 0000000..392b436
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
new file mode 100644
index 0000000..8983e2c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
new file mode 100644
index 0000000..eb841a7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt
new file mode 100644
index 0000000..d77f536
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
new file mode 100644
index 0000000..4070c2a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt
new file mode 100644
index 0000000..3397d9f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
new file mode 100644
index 0000000..a4cd966
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
new file mode 100644
index 0000000..3db50ef
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
new file mode 100644
index 0000000..16829bc
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
new file mode 100644
index 0000000..7f95f54
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
new file mode 100644
index 0000000..6c231b2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
new file mode 100644
index 0000000..078d087
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
new file mode 100644
index 0000000..321eaa2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
new file mode 100644
index 0000000..0b2c106
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
new file mode 100644
index 0000000..fcf093f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
new file mode 100644
index 0000000..140e214
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
new file mode 100644
index 0000000..f22e977
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
new file mode 100644
index 0000000..1d3fa79
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
new file mode 100644
index 0000000..5a59a55
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
new file mode 100644
index 0000000..151fb7b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
new file mode 100644
index 0000000..45ae469
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
new file mode 100644
index 0000000..5246188
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
new file mode 100644
index 0000000..6ed70b5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
new file mode 100644
index 0000000..103db75
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
new file mode 100644
index 0000000..229ae02
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
new file mode 100644
index 0000000..9dab497
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
new file mode 100644
index 0000000..7878dc0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
new file mode 100644
index 0000000..57358f9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
new file mode 100644
index 0000000..93a53e1
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt
new file mode 100644
index 0000000..4a432d8
--- /dev/null
+++ b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt
@@ -0,0 +1,11 @@
+HTML.Forms
+TYPE: bool
+VERSION: 4.13.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+ Whether or not to permit form elements in the user input, regardless of
+ %HTML.Trusted value. Please be very careful when using this functionality, as
+ enabling forms in untrusted documents may allow for phishing attacks.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
new file mode 100644
index 0000000..e424c38
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
new file mode 100644
index 0000000..700b309
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
new file mode 100644
index 0000000..62e8e16
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
new file mode 100644
index 0000000..dfb7204
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
new file mode 100644
index 0000000..cdda09a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt
new file mode 100644
index 0000000..5eb6ec2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
new file mode 100644
index 0000000..ceb342e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt
new file mode 100644
index 0000000..5ebc7a1
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
new file mode 100644
index 0000000..a8b1de5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
new file mode 100644
index 0000000..587a167
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
new file mode 100644
index 0000000..dd514c0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
new file mode 100644
index 0000000..cb5a0b0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
new file mode 100644
index 0000000..b4c271b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
new file mode 100644
index 0000000..4186ccd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
new file mode 100644
index 0000000..996762b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
new file mode 100644
index 0000000..1db9237
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
new file mode 100644
index 0000000..2a47e38
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
new file mode 100644
index 0000000..08921fd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
new file mode 100644
index 0000000..d6f0d9f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
new file mode 100644
index 0000000..93398e8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
new file mode 100644
index 0000000..79f8ad8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
new file mode 100644
index 0000000..232b023
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
new file mode 100644
index 0000000..06bab00
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
new file mode 100644
index 0000000..071bc02
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
new file mode 100644
index 0000000..eb97307
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
new file mode 100644
index 0000000..876f068
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
new file mode 100644
index 0000000..834bc08
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
new file mode 100644
index 0000000..f05312b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
new file mode 100644
index 0000000..80cfea9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
new file mode 100644
index 0000000..71ce025
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
new file mode 100644
index 0000000..13c122c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
new file mode 100644
index 0000000..abcc1ef
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
new file mode 100644
index 0000000..f891de4
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
new file mode 100644
index 0000000..ee83b12
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
new file mode 100644
index 0000000..0b6df76
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
new file mode 100644
index 0000000..4214900
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
new file mode 100644
index 0000000..58c81dc
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
new file mode 100644
index 0000000..6fce0fd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
new file mode 100644
index 0000000..1e17c1d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
new file mode 100644
index 0000000..23331a4
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/library/vendor/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
new file mode 100644
index 0000000..7908483
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ConfigSchema/schema/info.ini b/library/vendor/HTMLPurifier/ConfigSchema/schema/info.ini
new file mode 100644
index 0000000..5de4505
--- /dev/null
+++ b/library/vendor/HTMLPurifier/ConfigSchema/schema/info.ini
@@ -0,0 +1,3 @@
+name = "HTML Purifier"
+
+; vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/ContentSets.php b/library/vendor/HTMLPurifier/ContentSets.php
new file mode 100644
index 0000000..543e3f8
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Context.php b/library/vendor/HTMLPurifier/Context.php
new file mode 100644
index 0000000..00e509c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Definition.php b/library/vendor/HTMLPurifier/Definition.php
new file mode 100644
index 0000000..bc6d433
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache.php b/library/vendor/HTMLPurifier/DefinitionCache.php
new file mode 100644
index 0000000..9aa8ff3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Decorator.php b/library/vendor/HTMLPurifier/DefinitionCache/Decorator.php
new file mode 100644
index 0000000..b57a51b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/library/vendor/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
new file mode 100644
index 0000000..4991777
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/library/vendor/HTMLPurifier/DefinitionCache/Decorator/Memory.php
new file mode 100644
index 0000000..d529dce
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/library/vendor/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
new file mode 100644
index 0000000..b1fec8d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Null.php b/library/vendor/HTMLPurifier/DefinitionCache/Null.php
new file mode 100644
index 0000000..d9a75ce
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Serializer.php b/library/vendor/HTMLPurifier/DefinitionCache/Serializer.php
new file mode 100644
index 0000000..b82c6bb
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCache/Serializer/README b/library/vendor/HTMLPurifier/DefinitionCache/Serializer/README
new file mode 100644
index 0000000..2e35c1c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DefinitionCacheFactory.php b/library/vendor/HTMLPurifier/DefinitionCacheFactory.php
new file mode 100644
index 0000000..fd1cc9b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Doctype.php b/library/vendor/HTMLPurifier/Doctype.php
new file mode 100644
index 0000000..4acd06e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/DoctypeRegistry.php b/library/vendor/HTMLPurifier/DoctypeRegistry.php
new file mode 100644
index 0000000..acc1d64
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ElementDef.php b/library/vendor/HTMLPurifier/ElementDef.php
new file mode 100644
index 0000000..57cfd2b
--- /dev/null
+++ b/library/vendor/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", (string)$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/library/vendor/HTMLPurifier/Encoder.php b/library/vendor/HTMLPurifier/Encoder.php
new file mode 100644
index 0000000..d4791cc
--- /dev/null
+++ b/library/vendor/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' && function_exists('mb_convert_encoding')) {
+ $str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1');
+ 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' && function_exists('mb_convert_encoding')) {
+ $str = mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
+ 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/library/vendor/HTMLPurifier/EntityLookup.php b/library/vendor/HTMLPurifier/EntityLookup.php
new file mode 100644
index 0000000..f12ff13
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/EntityLookup/entities.ser b/library/vendor/HTMLPurifier/EntityLookup/entities.ser
new file mode 100644
index 0000000..e8b0812
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/EntityParser.php b/library/vendor/HTMLPurifier/EntityParser.php
new file mode 100644
index 0000000..3ef2d09
--- /dev/null
+++ b/library/vendor/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]) ? (empty($matches[4]) ? "" : $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/library/vendor/HTMLPurifier/ErrorCollector.php b/library/vendor/HTMLPurifier/ErrorCollector.php
new file mode 100644
index 0000000..d47e3f2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/ErrorStruct.php b/library/vendor/HTMLPurifier/ErrorStruct.php
new file mode 100644
index 0000000..cf869d3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Exception.php b/library/vendor/HTMLPurifier/Exception.php
new file mode 100644
index 0000000..be85b4c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Filter.php b/library/vendor/HTMLPurifier/Filter.php
new file mode 100644
index 0000000..c1f41ee
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Filter/ExtractStyleBlocks.php b/library/vendor/HTMLPurifier/Filter/ExtractStyleBlocks.php
new file mode 100644
index 0000000..66f70b0
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Filter/YouTube.php b/library/vendor/HTMLPurifier/Filter/YouTube.php
new file mode 100644
index 0000000..276d836
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Generator.php b/library/vendor/HTMLPurifier/Generator.php
new file mode 100644
index 0000000..eb56e2d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLDefinition.php b/library/vendor/HTMLPurifier/HTMLDefinition.php
new file mode 100644
index 0000000..9b7b334
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule.php b/library/vendor/HTMLPurifier/HTMLModule.php
new file mode 100644
index 0000000..9dbb987
--- /dev/null
+++ b/library/vendor/HTMLPurifier/HTMLModule.php
@@ -0,0 +1,285 @@
+<?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|HTMLPurifier_ChildDef $contents Allowed children in form of:
+ * "$content_model_type: $content_model"
+ * @param array|string $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)
+ {
+ $args = func_get_args();
+ if (is_string($list)) {
+ $list = $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/library/vendor/HTMLPurifier/HTMLModule/Bdo.php b/library/vendor/HTMLPurifier/HTMLModule/Bdo.php
new file mode 100644
index 0000000..1e67c79
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/CommonAttributes.php b/library/vendor/HTMLPurifier/HTMLModule/CommonAttributes.php
new file mode 100644
index 0000000..7220c14
--- /dev/null
+++ b/library/vendor/HTMLPurifier/HTMLModule/CommonAttributes.php
@@ -0,0 +1,32 @@
+<?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',
+ 'contenteditable' => 'ContentEditable',
+ ),
+ '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/library/vendor/HTMLPurifier/HTMLModule/Edit.php b/library/vendor/HTMLPurifier/HTMLModule/Edit.php
new file mode 100644
index 0000000..a9042a3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Forms.php b/library/vendor/HTMLPurifier/HTMLModule/Forms.php
new file mode 100644
index 0000000..eb0edcf
--- /dev/null
+++ b/library/vendor/HTMLPurifier/HTMLModule/Forms.php
@@ -0,0 +1,194 @@
+<?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)
+ {
+ if ($config->get('HTML.Forms')) {
+ $this->safe = true;
+ }
+
+ $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/library/vendor/HTMLPurifier/HTMLModule/Hypertext.php b/library/vendor/HTMLPurifier/HTMLModule/Hypertext.php
new file mode 100644
index 0000000..72d7a31
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Iframe.php b/library/vendor/HTMLPurifier/HTMLModule/Iframe.php
new file mode 100644
index 0000000..f7e7c91
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Image.php b/library/vendor/HTMLPurifier/HTMLModule/Image.php
new file mode 100644
index 0000000..0f5fdb3
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Legacy.php b/library/vendor/HTMLPurifier/HTMLModule/Legacy.php
new file mode 100644
index 0000000..86b5299
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/List.php b/library/vendor/HTMLPurifier/HTMLModule/List.php
new file mode 100644
index 0000000..7a20ff7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Name.php b/library/vendor/HTMLPurifier/HTMLModule/Name.php
new file mode 100644
index 0000000..60c0545
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Nofollow.php b/library/vendor/HTMLPurifier/HTMLModule/Nofollow.php
new file mode 100644
index 0000000..dc9410a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/library/vendor/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
new file mode 100644
index 0000000..da72225
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Object.php b/library/vendor/HTMLPurifier/HTMLModule/Object.php
new file mode 100644
index 0000000..2f9efc5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Presentation.php b/library/vendor/HTMLPurifier/HTMLModule/Presentation.php
new file mode 100644
index 0000000..6458ce9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Proprietary.php b/library/vendor/HTMLPurifier/HTMLModule/Proprietary.php
new file mode 100644
index 0000000..5ee3c8e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Ruby.php b/library/vendor/HTMLPurifier/HTMLModule/Ruby.php
new file mode 100644
index 0000000..a0d4892
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/SafeEmbed.php b/library/vendor/HTMLPurifier/HTMLModule/SafeEmbed.php
new file mode 100644
index 0000000..04e6689
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/SafeObject.php b/library/vendor/HTMLPurifier/HTMLModule/SafeObject.php
new file mode 100644
index 0000000..1297f80
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/SafeScripting.php b/library/vendor/HTMLPurifier/HTMLModule/SafeScripting.php
new file mode 100644
index 0000000..aea7584
--- /dev/null
+++ b/library/vendor/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',
+ 'Optional:', // Not `Empty` to not allow to autoclose the <script /> tag @see https://www.w3.org/TR/html4/interact/scripts.html
+ 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), /*case sensitive*/ true)
+ )
+ );
+ $script->attr_transform_pre[] =
+ $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/HTMLModule/Scripting.php b/library/vendor/HTMLPurifier/HTMLModule/Scripting.php
new file mode 100644
index 0000000..8b28a7b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/StyleAttribute.php b/library/vendor/HTMLPurifier/HTMLModule/StyleAttribute.php
new file mode 100644
index 0000000..497b832
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tables.php b/library/vendor/HTMLPurifier/HTMLModule/Tables.php
new file mode 100644
index 0000000..8a0b3b4
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Target.php b/library/vendor/HTMLPurifier/HTMLModule/Target.php
new file mode 100644
index 0000000..b188ac9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/TargetBlank.php b/library/vendor/HTMLPurifier/HTMLModule/TargetBlank.php
new file mode 100644
index 0000000..58ccc68
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/TargetNoopener.php b/library/vendor/HTMLPurifier/HTMLModule/TargetNoopener.php
new file mode 100644
index 0000000..b967ff5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/TargetNoreferrer.php b/library/vendor/HTMLPurifier/HTMLModule/TargetNoreferrer.php
new file mode 100644
index 0000000..32484d6
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Text.php b/library/vendor/HTMLPurifier/HTMLModule/Text.php
new file mode 100644
index 0000000..7a65e00
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tidy.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy.php
new file mode 100644
index 0000000..12173ba
--- /dev/null
+++ b/library/vendor/HTMLPurifier/HTMLModule/Tidy.php
@@ -0,0 +1,227 @@
+<?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;
+ }
+ $e->{$type}[$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/library/vendor/HTMLPurifier/HTMLModule/Tidy/Name.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy/Name.php
new file mode 100644
index 0000000..a995161
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tidy/Proprietary.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
new file mode 100644
index 0000000..3326438
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tidy/Strict.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy/Strict.php
new file mode 100644
index 0000000..803c44f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy/Transitional.php
new file mode 100644
index 0000000..c095ad9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTML.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTML.php
new file mode 100644
index 0000000..3ecddc4
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php b/library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
new file mode 100644
index 0000000..9ee3ffc
--- /dev/null
+++ b/library/vendor/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
@@ -0,0 +1,182 @@
+<?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['tr@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 table, hr, td, th, col ------------------------------------------
+ $r['table@width'] =
+ $r['td@width'] =
+ $r['th@width'] =
+ $r['col@width'] =
+ $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width');
+
+ return $r;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/library/vendor/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
new file mode 100644
index 0000000..01dbe9d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/HTMLModuleManager.php b/library/vendor/HTMLPurifier/HTMLModuleManager.php
new file mode 100644
index 0000000..38c058f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/IDAccumulator.php b/library/vendor/HTMLPurifier/IDAccumulator.php
new file mode 100644
index 0000000..65c902c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Injector.php b/library/vendor/HTMLPurifier/Injector.php
new file mode 100644
index 0000000..116b470
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Injector/AutoParagraph.php b/library/vendor/HTMLPurifier/Injector/AutoParagraph.php
new file mode 100644
index 0000000..4afdd12
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Injector/DisplayLinkURI.php b/library/vendor/HTMLPurifier/Injector/DisplayLinkURI.php
new file mode 100644
index 0000000..c19b1bc
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Injector/Linkify.php b/library/vendor/HTMLPurifier/Injector/Linkify.php
new file mode 100644
index 0000000..3b6d70f
--- /dev/null
+++ b/library/vendor/HTMLPurifier/Injector/Linkify.php
@@ -0,0 +1,67 @@
+<?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);
+
+ if ($bits === false) {
+ return;
+ }
+
+ $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/library/vendor/HTMLPurifier/Injector/PurifierLinkify.php b/library/vendor/HTMLPurifier/Injector/PurifierLinkify.php
new file mode 100644
index 0000000..cb9046f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Injector/RemoveEmpty.php b/library/vendor/HTMLPurifier/Injector/RemoveEmpty.php
new file mode 100644
index 0000000..0ebc477
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/library/vendor/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
new file mode 100644
index 0000000..42d5144
--- /dev/null
+++ b/library/vendor/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
@@ -0,0 +1,95 @@
+<?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;
+
+ /**
+ * @type SplObjectStorage
+ */
+ private $markForDeletion;
+
+ public function __construct()
+ {
+ $this->markForDeletion = new SplObjectStorage();
+ }
+
+ 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
+ $this->markForDeletion->attach($current);
+ // Delete open span tag
+ $token = false;
+ }
+ }
+
+ /**
+ * @param HTMLPurifier_Token $token
+ */
+ public function handleEnd(&$token)
+ {
+ if ($this->markForDeletion->contains($token)) {
+ $this->markForDeletion->detach($token);
+ $token = false;
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/Injector/SafeObject.php b/library/vendor/HTMLPurifier/Injector/SafeObject.php
new file mode 100644
index 0000000..317f786
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/LICENSE b/library/vendor/HTMLPurifier/LICENSE
new file mode 100644
index 0000000..8c88a20
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Language.php b/library/vendor/HTMLPurifier/Language.php
new file mode 100644
index 0000000..65277dd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Language/messages/en.php b/library/vendor/HTMLPurifier/Language/messages/en.php
new file mode 100644
index 0000000..c7f197e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/LanguageFactory.php b/library/vendor/HTMLPurifier/LanguageFactory.php
new file mode 100644
index 0000000..4e35272
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Length.php b/library/vendor/HTMLPurifier/Length.php
new file mode 100644
index 0000000..b6ea123
--- /dev/null
+++ b/library/vendor/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 ($this->unit === false || !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/library/vendor/HTMLPurifier/Lexer.php b/library/vendor/HTMLPurifier/Lexer.php
new file mode 100644
index 0000000..c21f364
--- /dev/null
+++ b/library/vendor/HTMLPurifier/Lexer.php
@@ -0,0 +1,387 @@
+<?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;
+
+ /**
+ * @type HTMLPurifier_EntityParser
+ */
+ private $_entity_parser;
+
+ // -- 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", (string)$html);
+ $html = str_replace("\r", "\n", (string)$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/library/vendor/HTMLPurifier/Lexer/DOMLex.php b/library/vendor/HTMLPurifier/Lexer/DOMLex.php
new file mode 100644
index 0000000..ca5f25b
--- /dev/null
+++ b/library/vendor/HTMLPurifier/Lexer/DOMLex.php
@@ -0,0 +1,338 @@
+<?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
+
+ $options = 0;
+ if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) {
+ $options |= LIBXML_PARSEHUGE;
+ }
+
+ set_error_handler(array($this, 'muteErrorHandler'));
+ // loadHTML() fails on PHP 5.3 when second parameter is given
+ if ($options) {
+ $doc->loadHTML($html, $options);
+ } else {
+ $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 (isset($node->tagName)) {
+ return $node->tagName;
+ } else if (isset($node->nodeName)) {
+ return $node->nodeName;
+ } else if (isset($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 (isset($node->data)) {
+ return $node->data;
+ } else if (isset($node->nodeValue)) {
+ return $node->nodeValue;
+ } else if (isset($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/library/vendor/HTMLPurifier/Lexer/DirectLex.php b/library/vendor/HTMLPurifier/Lexer/DirectLex.php
new file mode 100644
index 0000000..6f13089
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Lexer/PH5P.php b/library/vendor/HTMLPurifier/Lexer/PH5P.php
new file mode 100644
index 0000000..1564f28
--- /dev/null
+++ b/library/vendor/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'], (string)$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/library/vendor/HTMLPurifier/Node.php b/library/vendor/HTMLPurifier/Node.php
new file mode 100644
index 0000000..3995fec
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Node/Comment.php b/library/vendor/HTMLPurifier/Node/Comment.php
new file mode 100644
index 0000000..38ba193
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Node/Element.php b/library/vendor/HTMLPurifier/Node/Element.php
new file mode 100644
index 0000000..6cbf56d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Node/Text.php b/library/vendor/HTMLPurifier/Node/Text.php
new file mode 100644
index 0000000..aec9166
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/PercentEncoder.php b/library/vendor/HTMLPurifier/PercentEncoder.php
new file mode 100644
index 0000000..18c8bbb
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Printer.php b/library/vendor/HTMLPurifier/Printer.php
new file mode 100644
index 0000000..549e4ce
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Printer/CSSDefinition.php b/library/vendor/HTMLPurifier/Printer/CSSDefinition.php
new file mode 100644
index 0000000..29505fe
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Printer/ConfigForm.css b/library/vendor/HTMLPurifier/Printer/ConfigForm.css
new file mode 100644
index 0000000..3ff1a88
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Printer/ConfigForm.js b/library/vendor/HTMLPurifier/Printer/ConfigForm.js
new file mode 100644
index 0000000..cba00c9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Printer/ConfigForm.php b/library/vendor/HTMLPurifier/Printer/ConfigForm.php
new file mode 100644
index 0000000..33ae113
--- /dev/null
+++ b/library/vendor/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::C_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::C_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/library/vendor/HTMLPurifier/Printer/HTMLDefinition.php b/library/vendor/HTMLPurifier/Printer/HTMLDefinition.php
new file mode 100644
index 0000000..ae86391
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/PropertyList.php b/library/vendor/HTMLPurifier/PropertyList.php
new file mode 100644
index 0000000..189348f
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/PropertyListIterator.php b/library/vendor/HTMLPurifier/PropertyListIterator.php
new file mode 100644
index 0000000..f68fc8c
--- /dev/null
+++ b/library/vendor/HTMLPurifier/PropertyListIterator.php
@@ -0,0 +1,43 @@
+<?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
+ */
+ #[\ReturnTypeWillChange]
+ 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/library/vendor/HTMLPurifier/Queue.php b/library/vendor/HTMLPurifier/Queue.php
new file mode 100644
index 0000000..f58db90
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/SOURCE b/library/vendor/HTMLPurifier/SOURCE
new file mode 100644
index 0000000..56f423d
--- /dev/null
+++ b/library/vendor/HTMLPurifier/SOURCE
@@ -0,0 +1,10 @@
+GLOBIGNORE=$0; rm -rf *
+rm ../HTMLPurifier*.php
+
+curl https://codeload.github.com/ezyang/htmlpurifier/tar.gz/v4.16.0 -o htmlpurifier-4.16.0.tar.gz
+tar xzf htmlpurifier-4.16.0.tar.gz --strip-components 1 htmlpurifier-4.16.0/LICENSE
+tar xzf htmlpurifier-4.16.0.tar.gz --strip-components 1 htmlpurifier-4.16.0/VERSION
+tar xzf htmlpurifier-4.16.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.16.0/library/HTMLPurifier.php
+tar xzf htmlpurifier-4.16.0.tar.gz -C ../ --strip-components 2 htmlpurifier-4.16.0/library/HTMLPurifier.autoload.php
+tar xzf htmlpurifier-4.16.0.tar.gz --wildcards --strip-components 3 htmlpurifier-4.16.0/library/HTMLPurifier/*
+rm htmlpurifier-4.16.0.tar.gz
diff --git a/library/vendor/HTMLPurifier/Strategy.php b/library/vendor/HTMLPurifier/Strategy.php
new file mode 100644
index 0000000..e1ff3b7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Strategy/Composite.php b/library/vendor/HTMLPurifier/Strategy/Composite.php
new file mode 100644
index 0000000..d7d35ce
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Strategy/Core.php b/library/vendor/HTMLPurifier/Strategy/Core.php
new file mode 100644
index 0000000..4414c17
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Strategy/FixNesting.php b/library/vendor/HTMLPurifier/Strategy/FixNesting.php
new file mode 100644
index 0000000..6fa673d
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Strategy/MakeWellFormed.php b/library/vendor/HTMLPurifier/Strategy/MakeWellFormed.php
new file mode 100644
index 0000000..a6eb09e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Strategy/RemoveForeignElements.php b/library/vendor/HTMLPurifier/Strategy/RemoveForeignElements.php
new file mode 100644
index 0000000..1a8149e
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Strategy/ValidateAttributes.php b/library/vendor/HTMLPurifier/Strategy/ValidateAttributes.php
new file mode 100644
index 0000000..fbb3d27
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/StringHash.php b/library/vendor/HTMLPurifier/StringHash.php
new file mode 100644
index 0000000..c41ae3a
--- /dev/null
+++ b/library/vendor/HTMLPurifier/StringHash.php
@@ -0,0 +1,48 @@
+<?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
+ */
+ #[\ReturnTypeWillChange]
+ 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/library/vendor/HTMLPurifier/StringHashParser.php b/library/vendor/HTMLPurifier/StringHashParser.php
new file mode 100644
index 0000000..7c73f80
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/TagTransform.php b/library/vendor/HTMLPurifier/TagTransform.php
new file mode 100644
index 0000000..7b8d833
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/TagTransform/Font.php b/library/vendor/HTMLPurifier/TagTransform/Font.php
new file mode 100644
index 0000000..768c9b1
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/TagTransform/Simple.php b/library/vendor/HTMLPurifier/TagTransform/Simple.php
new file mode 100644
index 0000000..71bf10b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token.php b/library/vendor/HTMLPurifier/Token.php
new file mode 100644
index 0000000..84d3619
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token/Comment.php b/library/vendor/HTMLPurifier/Token/Comment.php
new file mode 100644
index 0000000..23453c7
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token/Empty.php b/library/vendor/HTMLPurifier/Token/Empty.php
new file mode 100644
index 0000000..78a95f5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token/End.php b/library/vendor/HTMLPurifier/Token/End.php
new file mode 100644
index 0000000..59b38fd
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token/Start.php b/library/vendor/HTMLPurifier/Token/Start.php
new file mode 100644
index 0000000..019f317
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token/Tag.php b/library/vendor/HTMLPurifier/Token/Tag.php
new file mode 100644
index 0000000..d643fa6
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Token/Text.php b/library/vendor/HTMLPurifier/Token/Text.php
new file mode 100644
index 0000000..f26a1c2
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/TokenFactory.php b/library/vendor/HTMLPurifier/TokenFactory.php
new file mode 100644
index 0000000..dea2446
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URI.php b/library/vendor/HTMLPurifier/URI.php
new file mode 100644
index 0000000..9c5be39
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIDefinition.php b/library/vendor/HTMLPurifier/URIDefinition.php
new file mode 100644
index 0000000..e0bd8bc
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIFilter.php b/library/vendor/HTMLPurifier/URIFilter.php
new file mode 100644
index 0000000..09724e9
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIFilter/DisableExternal.php b/library/vendor/HTMLPurifier/URIFilter/DisableExternal.php
new file mode 100644
index 0000000..ced1b13
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIFilter/DisableExternalResources.php b/library/vendor/HTMLPurifier/URIFilter/DisableExternalResources.php
new file mode 100644
index 0000000..c656216
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIFilter/DisableResources.php b/library/vendor/HTMLPurifier/URIFilter/DisableResources.php
new file mode 100644
index 0000000..d5c412c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIFilter/HostBlacklist.php b/library/vendor/HTMLPurifier/URIFilter/HostBlacklist.php
new file mode 100644
index 0000000..32197c0
--- /dev/null
+++ b/library/vendor/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 ($uri->host !== null && strpos($uri->host, $blacklisted_host_fragment) !== false) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/library/vendor/HTMLPurifier/URIFilter/MakeAbsolute.php b/library/vendor/HTMLPurifier/URIFilter/MakeAbsolute.php
new file mode 100644
index 0000000..c507bbf
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIFilter/Munge.php b/library/vendor/HTMLPurifier/URIFilter/Munge.php
new file mode 100644
index 0000000..e1393de
--- /dev/null
+++ b/library/vendor/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 : '';
+ $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/library/vendor/HTMLPurifier/URIFilter/SafeIframe.php b/library/vendor/HTMLPurifier/URIFilter/SafeIframe.php
new file mode 100644
index 0000000..f609c47
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIParser.php b/library/vendor/HTMLPurifier/URIParser.php
new file mode 100644
index 0000000..0e7381a
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme.php b/library/vendor/HTMLPurifier/URIScheme.php
new file mode 100644
index 0000000..fe9e82c
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/data.php b/library/vendor/HTMLPurifier/URIScheme/data.php
new file mode 100644
index 0000000..41c49d5
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/file.php b/library/vendor/HTMLPurifier/URIScheme/file.php
new file mode 100644
index 0000000..215be4b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/ftp.php b/library/vendor/HTMLPurifier/URIScheme/ftp.php
new file mode 100644
index 0000000..1eb43ee
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/http.php b/library/vendor/HTMLPurifier/URIScheme/http.php
new file mode 100644
index 0000000..ce69ec4
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/https.php b/library/vendor/HTMLPurifier/URIScheme/https.php
new file mode 100644
index 0000000..0e96882
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/mailto.php b/library/vendor/HTMLPurifier/URIScheme/mailto.php
new file mode 100644
index 0000000..c3a6b60
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/news.php b/library/vendor/HTMLPurifier/URIScheme/news.php
new file mode 100644
index 0000000..7490927
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/nntp.php b/library/vendor/HTMLPurifier/URIScheme/nntp.php
new file mode 100644
index 0000000..f211d71
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URIScheme/tel.php b/library/vendor/HTMLPurifier/URIScheme/tel.php
new file mode 100644
index 0000000..8cd1933
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/URISchemeRegistry.php b/library/vendor/HTMLPurifier/URISchemeRegistry.php
new file mode 100644
index 0000000..4ac8a0b
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/UnitConverter.php b/library/vendor/HTMLPurifier/UnitConverter.php
new file mode 100644
index 0000000..166f3bf
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/VERSION b/library/vendor/HTMLPurifier/VERSION
new file mode 100644
index 0000000..f029ee5
--- /dev/null
+++ b/library/vendor/HTMLPurifier/VERSION
@@ -0,0 +1 @@
+4.15.0 \ No newline at end of file
diff --git a/library/vendor/HTMLPurifier/VarParser.php b/library/vendor/HTMLPurifier/VarParser.php
new file mode 100644
index 0000000..0c97c82
--- /dev/null
+++ b/library/vendor/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 C_STRING = 1;
+ const ISTRING = 2;
+ const TEXT = 3;
+ const ITEXT = 4;
+ const C_INT = 5;
+ const C_FLOAT = 6;
+ const C_BOOL = 7;
+ const LOOKUP = 8;
+ const ALIST = 9;
+ const HASH = 10;
+ const C_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::C_STRING,
+ 'istring' => self::ISTRING,
+ 'text' => self::TEXT,
+ 'itext' => self::ITEXT,
+ 'int' => self::C_INT,
+ 'float' => self::C_FLOAT,
+ 'bool' => self::C_BOOL,
+ 'lookup' => self::LOOKUP,
+ 'list' => self::ALIST,
+ 'hash' => self::HASH,
+ 'mixed' => self::C_MIXED
+ );
+
+ /**
+ * Lookup table of types that are string, and can have aliases or
+ * allowed value lists.
+ */
+ public static $stringTypes = array(
+ self::C_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::C_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::C_INT):
+ if (!is_int($var)) {
+ break;
+ }
+ return $var;
+ case (self::C_FLOAT):
+ if (!is_float($var)) {
+ break;
+ }
+ return $var;
+ case (self::C_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::C_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/library/vendor/HTMLPurifier/VarParser/Flexible.php b/library/vendor/HTMLPurifier/VarParser/Flexible.php
new file mode 100644
index 0000000..3bfbe83
--- /dev/null
+++ b/library/vendor/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::C_MIXED:
+ case self::ISTRING:
+ case self::C_STRING:
+ case self::TEXT:
+ case self::ITEXT:
+ return $var;
+ case self::C_INT:
+ if (is_string($var) && ctype_digit($var)) {
+ $var = (int)$var;
+ }
+ return $var;
+ case self::C_FLOAT:
+ if ((is_string($var) && is_numeric($var)) || is_int($var)) {
+ $var = (float)$var;
+ }
+ return $var;
+ case self::C_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/library/vendor/HTMLPurifier/VarParser/Native.php b/library/vendor/HTMLPurifier/VarParser/Native.php
new file mode 100644
index 0000000..f11c318
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/VarParserException.php b/library/vendor/HTMLPurifier/VarParserException.php
new file mode 100644
index 0000000..5df3414
--- /dev/null
+++ b/library/vendor/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/library/vendor/HTMLPurifier/Zipper.php b/library/vendor/HTMLPurifier/Zipper.php
new file mode 100644
index 0000000..6e21ea0
--- /dev/null
+++ b/library/vendor/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/library/vendor/JShrink/LICENSE b/library/vendor/JShrink/LICENSE
new file mode 100644
index 0000000..c6597d8
--- /dev/null
+++ b/library/vendor/JShrink/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2009, Robert Hafner
+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.
+
+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/library/vendor/JShrink/Minifier.php b/library/vendor/JShrink/Minifier.php
new file mode 100644
index 0000000..fad43d2
--- /dev/null
+++ b/library/vendor/JShrink/Minifier.php
@@ -0,0 +1,613 @@
+<?php
+/*
+ * This file is part of the JShrink package.
+ *
+ * (c) Robert Hafner <tedivm@tedivm.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * JShrink
+ *
+ *
+ * @package JShrink
+ * @author Robert Hafner <tedivm@tedivm.com>
+ */
+
+namespace JShrink;
+
+/**
+ * Minifier
+ *
+ * Usage - Minifier::minify($js);
+ * Usage - Minifier::minify($js, $options);
+ * Usage - Minifier::minify($js, array('flaggedComments' => false));
+ *
+ * @package JShrink
+ * @author Robert Hafner <tedivm@tedivm.com>
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ */
+class Minifier
+{
+ /**
+ * The input javascript to be minified.
+ *
+ * @var string
+ */
+ protected $input;
+
+ /**
+ * Length of input javascript.
+ *
+ * @var int
+ */
+ protected $len = 0;
+
+ /**
+ * The location of the character (in the input string) that is next to be
+ * processed.
+ *
+ * @var int
+ */
+ protected $index = 0;
+
+ /**
+ * The first of the characters currently being looked at.
+ *
+ * @var string
+ */
+ protected $a = '';
+
+ /**
+ * The next character being looked at (after a);
+ *
+ * @var string
+ */
+ protected $b = '';
+
+ /**
+ * This character is only active when certain look ahead actions take place.
+ *
+ * @var string
+ */
+ protected $c;
+
+ /**
+ * Contains the options for the current minification process.
+ *
+ * @var array
+ */
+ protected $options;
+
+ /**
+ * These characters are used to define strings.
+ */
+ protected $stringDelimiters = ['\'' => true, '"' => true, '`' => true];
+
+ /**
+ * Contains the default options for minification. This array is merged with
+ * the one passed in by the user to create the request specific set of
+ * options (stored in the $options attribute).
+ *
+ * @var array
+ */
+ protected static $defaultOptions = ['flaggedComments' => true];
+
+ /**
+ * Contains lock ids which are used to replace certain code patterns and
+ * prevent them from being minified
+ *
+ * @var array
+ */
+ protected $locks = [];
+
+ /**
+ * Takes a string containing javascript and removes unneeded characters in
+ * order to shrink the code without altering it's functionality.
+ *
+ * @param string $js The raw javascript to be minified
+ * @param array $options Various runtime options in an associative array
+ * @throws \Exception
+ * @return bool|string
+ */
+ public static function minify($js, $options = [])
+ {
+ try {
+ ob_start();
+
+ $jshrink = new Minifier();
+ $js = $jshrink->lock($js);
+ $jshrink->minifyDirectToOutput($js, $options);
+
+ // Sometimes there's a leading new line, so we trim that out here.
+ $js = ltrim(ob_get_clean());
+ $js = $jshrink->unlock($js);
+ unset($jshrink);
+
+ return $js;
+ } catch (\Exception $e) {
+ if (isset($jshrink)) {
+ // Since the breakdownScript function probably wasn't finished
+ // we clean it out before discarding it.
+ $jshrink->clean();
+ unset($jshrink);
+ }
+
+ // without this call things get weird, with partially outputted js.
+ ob_end_clean();
+ throw $e;
+ }
+ }
+
+ /**
+ * Processes a javascript string and outputs only the required characters,
+ * stripping out all unneeded characters.
+ *
+ * @param string $js The raw javascript to be minified
+ * @param array $options Various runtime options in an associative array
+ */
+ protected function minifyDirectToOutput($js, $options)
+ {
+ $this->initialize($js, $options);
+ $this->loop();
+ $this->clean();
+ }
+
+ /**
+ * Initializes internal variables, normalizes new lines,
+ *
+ * @param string $js The raw javascript to be minified
+ * @param array $options Various runtime options in an associative array
+ */
+ protected function initialize($js, $options)
+ {
+ $this->options = array_merge(static::$defaultOptions, $options);
+ $this->input = str_replace(["\r\n", '/**/', "\r"], ["\n", "", "\n"], $js);
+
+ // We add a newline to the end of the script to make it easier to deal
+ // with comments at the bottom of the script- this prevents the unclosed
+ // comment error that can otherwise occur.
+ $this->input .= PHP_EOL;
+
+ // save input length to skip calculation every time
+ $this->len = strlen($this->input);
+
+ // Populate "a" with a new line, "b" with the first character, before
+ // entering the loop
+ $this->a = "\n";
+ $this->b = $this->getReal();
+ }
+
+ /**
+ * Characters that can't stand alone preserve the newline.
+ *
+ * @var array
+ */
+ protected $noNewLineCharacters = [
+ '(' => true,
+ '-' => true,
+ '+' => true,
+ '[' => true,
+ '@' => true];
+
+ /**
+ * The primary action occurs here. This function loops through the input string,
+ * outputting anything that's relevant and discarding anything that is not.
+ */
+ protected function loop()
+ {
+ while ($this->a !== false && !is_null($this->a) && $this->a !== '') {
+ switch ($this->a) {
+ // new lines
+ case "\n":
+ // if the next line is something that can't stand alone preserve the newline
+ if ($this->b !== false && isset($this->noNewLineCharacters[$this->b])) {
+ echo $this->a;
+ $this->saveString();
+ break;
+ }
+
+ // if B is a space we skip the rest of the switch block and go down to the
+ // string/regex check below, resetting $this->b with getReal
+ if ($this->b === ' ') {
+ break;
+ }
+
+ // otherwise we treat the newline like a space
+
+ // no break
+ case ' ':
+ if (static::isAlphaNumeric($this->b)) {
+ echo $this->a;
+ }
+
+ $this->saveString();
+ break;
+
+ default:
+ switch ($this->b) {
+ case "\n":
+ if (strpos('}])+-"\'', $this->a) !== false) {
+ echo $this->a;
+ $this->saveString();
+ break;
+ } else {
+ if (static::isAlphaNumeric($this->a)) {
+ echo $this->a;
+ $this->saveString();
+ }
+ }
+ break;
+
+ case ' ':
+ if (!static::isAlphaNumeric($this->a)) {
+ break;
+ }
+
+ // no break
+ default:
+ // check for some regex that breaks stuff
+ if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) {
+ $this->saveRegex();
+ continue 3;
+ }
+
+ echo $this->a;
+ $this->saveString();
+ break;
+ }
+ }
+
+ // do reg check of doom
+ $this->b = $this->getReal();
+
+ if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
+ $this->saveRegex();
+ }
+ }
+ }
+
+ /**
+ * Resets attributes that do not need to be stored between requests so that
+ * the next request is ready to go. Another reason for this is to make sure
+ * the variables are cleared and are not taking up memory.
+ */
+ protected function clean()
+ {
+ unset($this->input);
+ $this->len = 0;
+ $this->index = 0;
+ $this->a = $this->b = '';
+ unset($this->c);
+ unset($this->options);
+ }
+
+ /**
+ * Returns the next string for processing based off of the current index.
+ *
+ * @return string
+ */
+ protected function getChar()
+ {
+ // Check to see if we had anything in the look ahead buffer and use that.
+ if (isset($this->c)) {
+ $char = $this->c;
+ unset($this->c);
+ } else {
+ // Otherwise we start pulling from the input.
+ $char = $this->index < $this->len ? $this->input[$this->index] : false;
+
+ // If the next character doesn't exist return false.
+ if (isset($char) && $char === false) {
+ return false;
+ }
+
+ // Otherwise increment the pointer and use this char.
+ $this->index++;
+ }
+
+ // Normalize all whitespace except for the newline character into a
+ // standard space.
+ if ($char !== "\n" && $char < "\x20") {
+ return ' ';
+ }
+
+ return $char;
+ }
+
+ /**
+ * This function gets the next "real" character. It is essentially a wrapper
+ * around the getChar function that skips comments. This has significant
+ * performance benefits as the skipping is done using native functions (ie,
+ * c code) rather than in script php.
+ *
+ *
+ * @return string Next 'real' character to be processed.
+ * @throws \RuntimeException
+ */
+ protected function getReal()
+ {
+ $startIndex = $this->index;
+ $char = $this->getChar();
+
+ // Check to see if we're potentially in a comment
+ if ($char !== '/') {
+ return $char;
+ }
+
+ $this->c = $this->getChar();
+
+ if ($this->c === '/') {
+ $this->processOneLineComments($startIndex);
+
+ return $this->getReal();
+ } elseif ($this->c === '*') {
+ $this->processMultiLineComments($startIndex);
+
+ return $this->getReal();
+ }
+
+ return $char;
+ }
+
+ /**
+ * Removed one line comments, with the exception of some very specific types of
+ * conditional comments.
+ *
+ * @param int $startIndex The index point where "getReal" function started
+ * @return void
+ */
+ protected function processOneLineComments($startIndex)
+ {
+ $thirdCommentString = $this->index < $this->len ? $this->input[$this->index] : false;
+
+ // kill rest of line
+ $this->getNext("\n");
+
+ unset($this->c);
+
+ if ($thirdCommentString == '@') {
+ $endPoint = $this->index - $startIndex;
+ $this->c = "\n" . substr($this->input, $startIndex, $endPoint);
+ }
+ }
+
+ /**
+ * Skips multiline comments where appropriate, and includes them where needed.
+ * Conditional comments and "license" style blocks are preserved.
+ *
+ * @param int $startIndex The index point where "getReal" function started
+ * @return void
+ * @throws \RuntimeException Unclosed comments will throw an error
+ */
+ protected function processMultiLineComments($startIndex)
+ {
+ $this->getChar(); // current C
+ $thirdCommentString = $this->getChar();
+
+ // kill everything up to the next */ if it's there
+ if ($this->getNext('*/')) {
+ $this->getChar(); // get *
+ $this->getChar(); // get /
+ $char = $this->getChar(); // get next real character
+
+ // Now we reinsert conditional comments and YUI-style licensing comments
+ if (($this->options['flaggedComments'] && $thirdCommentString === '!')
+ || ($thirdCommentString === '@')) {
+
+ // If conditional comments or flagged comments are not the first thing in the script
+ // we need to echo a and fill it with a space before moving on.
+ if ($startIndex > 0) {
+ echo $this->a;
+ $this->a = " ";
+
+ // If the comment started on a new line we let it stay on the new line
+ if ($this->input[($startIndex - 1)] === "\n") {
+ echo "\n";
+ }
+ }
+
+ $endPoint = ($this->index - 1) - $startIndex;
+ echo substr($this->input, $startIndex, $endPoint);
+
+ $this->c = $char;
+
+ return;
+ }
+ } else {
+ $char = false;
+ }
+
+ if ($char === false) {
+ throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->index - 2));
+ }
+
+ // if we're here c is part of the comment and therefore tossed
+ $this->c = $char;
+ }
+
+ /**
+ * Pushes the index ahead to the next instance of the supplied string. If it
+ * is found the first character of the string is returned and the index is set
+ * to it's position.
+ *
+ * @param string $string
+ * @return string|false Returns the first character of the string or false.
+ */
+ protected function getNext($string)
+ {
+ // Find the next occurrence of "string" after the current position.
+ $pos = strpos($this->input, $string, $this->index);
+
+ // If it's not there return false.
+ if ($pos === false) {
+ return false;
+ }
+
+ // Adjust position of index to jump ahead to the asked for string
+ $this->index = $pos;
+
+ // Return the first character of that string.
+ return $this->index < $this->len ? $this->input[$this->index] : false;
+ }
+
+ /**
+ * When a javascript string is detected this function crawls for the end of
+ * it and saves the whole string.
+ *
+ * @throws \RuntimeException Unclosed strings will throw an error
+ */
+ protected function saveString()
+ {
+ $startpos = $this->index;
+
+ // saveString is always called after a gets cleared, so we push b into
+ // that spot.
+ $this->a = $this->b;
+
+ // If this isn't a string we don't need to do anything.
+ if (!isset($this->stringDelimiters[$this->a])) {
+ return;
+ }
+
+ // String type is the quote used, " or '
+ $stringType = $this->a;
+
+ // Echo out that starting quote
+ echo $this->a;
+
+ // Loop until the string is done
+ // Grab the very next character and load it into a
+ while (($this->a = $this->getChar()) !== false) {
+ switch ($this->a) {
+
+ // If the string opener (single or double quote) is used
+ // output it and break out of the while loop-
+ // The string is finished!
+ case $stringType:
+ break 2;
+
+ // New lines in strings without line delimiters are bad- actual
+ // new lines will be represented by the string \n and not the actual
+ // character, so those will be treated just fine using the switch
+ // block below.
+ case "\n":
+ if ($stringType === '`') {
+ echo $this->a;
+ } else {
+ throw new \RuntimeException('Unclosed string at position: ' . $startpos);
+ }
+ break;
+
+ // Escaped characters get picked up here. If it's an escaped new line it's not really needed
+ case '\\':
+
+ // a is a slash. We want to keep it, and the next character,
+ // unless it's a new line. New lines as actual strings will be
+ // preserved, but escaped new lines should be reduced.
+ $this->b = $this->getChar();
+
+ // If b is a new line we discard a and b and restart the loop.
+ if ($this->b === "\n") {
+ break;
+ }
+
+ // echo out the escaped character and restart the loop.
+ echo $this->a . $this->b;
+ break;
+
+
+ // Since we're not dealing with any special cases we simply
+ // output the character and continue our loop.
+ default:
+ echo $this->a;
+ }
+ }
+ }
+
+ /**
+ * When a regular expression is detected this function crawls for the end of
+ * it and saves the whole regex.
+ *
+ * @throws \RuntimeException Unclosed regex will throw an error
+ */
+ protected function saveRegex()
+ {
+ echo $this->a . $this->b;
+
+ while (($this->a = $this->getChar()) !== false) {
+ if ($this->a === '/') {
+ break;
+ }
+
+ if ($this->a === '\\') {
+ echo $this->a;
+ $this->a = $this->getChar();
+ }
+
+ if ($this->a === "\n") {
+ throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->index);
+ }
+
+ echo $this->a;
+ }
+ $this->b = $this->getReal();
+ }
+
+ /**
+ * Checks to see if a character is alphanumeric.
+ *
+ * @param string $char Just one character
+ * @return bool
+ */
+ protected static function isAlphaNumeric($char)
+ {
+ return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/';
+ }
+
+ /**
+ * Replace patterns in the given string and store the replacement
+ *
+ * @param string $js The string to lock
+ * @return bool
+ */
+ protected function lock($js)
+ {
+ /* lock things like <code>"asd" + ++x;</code> */
+ $lock = '"LOCK---' . crc32(time()) . '"';
+
+ $matches = [];
+ preg_match('/([+-])(\s+)([+-])/S', $js, $matches);
+ if (empty($matches)) {
+ return $js;
+ }
+
+ $this->locks[$lock] = $matches[2];
+
+ $js = preg_replace('/([+-])\s+([+-])/S', "$1{$lock}$2", $js);
+ /* -- */
+
+ return $js;
+ }
+
+ /**
+ * Replace "locks" with the original characters
+ *
+ * @param string $js The string to unlock
+ * @return bool
+ */
+ protected function unlock($js)
+ {
+ if (empty($this->locks)) {
+ return $js;
+ }
+
+ foreach ($this->locks as $lock => $replacement) {
+ $js = str_replace($lock, $replacement, $js);
+ }
+
+ return $js;
+ }
+}
diff --git a/library/vendor/JShrink/SOURCE b/library/vendor/JShrink/SOURCE
new file mode 100644
index 0000000..00ccc58
--- /dev/null
+++ b/library/vendor/JShrink/SOURCE
@@ -0,0 +1,7 @@
+#!/bin/bash
+set -eux
+VERSION=1.4.0
+curl -LsS https://github.com/tedious/JShrink/archive/v"$VERSION".tar.gz -o /tmp/JShrink.tar.gz
+tar xzf /tmp/JShrink.tar.gz --strip-components 1 JShrink-"$VERSION"/LICENSE
+tar xzf /tmp/JShrink.tar.gz --strip-components 3 JShrink-"$VERSION"/src/JShrink/Minifier.php
+rm /tmp/JShrink.tar.gz
diff --git a/library/vendor/Parsedown/LICENSE b/library/vendor/Parsedown/LICENSE
new file mode 100644
index 0000000..8e7c764
--- /dev/null
+++ b/library/vendor/Parsedown/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2018 Emanuil Rusev, erusev.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/library/vendor/Parsedown/Parsedown.php b/library/vendor/Parsedown/Parsedown.php
new file mode 100644
index 0000000..a34b44f
--- /dev/null
+++ b/library/vendor/Parsedown/Parsedown.php
@@ -0,0 +1,1693 @@
+<?php
+
+#
+#
+# Parsedown
+# http://parsedown.org
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class Parsedown
+{
+ # ~
+
+ const version = '1.7.3';
+
+ # ~
+
+ function text($text)
+ {
+ # make sure no definitions are set
+ $this->DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ $markup = $this->lines($lines);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ function setSafeMode($safeMode)
+ {
+ $this->safeMode = (bool) $safeMode;
+
+ return $this;
+ }
+
+ protected $safeMode;
+
+ protected $safeLinksWhitelist = array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'ftps://',
+ 'mailto:',
+ 'data:image/png;base64,',
+ 'data:image/gif;base64,',
+ 'data:image/jpeg;base64,',
+ 'irc:',
+ 'ircs:',
+ 'git:',
+ 'ssh:',
+ 'news:',
+ 'steam:',
+ );
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = true;
+ }
+
+ continue;
+ }
+
+ if (strpos($line, "\t") !== false)
+ {
+ $parts = explode("\t", $line);
+
+ $line = $parts[0];
+
+ unset($parts[0]);
+
+ foreach ($parts as $part)
+ {
+ $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
+
+ $line .= str_repeat(' ', $shortage);
+ $line .= $part;
+ }
+ }
+
+ $indent = 0;
+
+ while (isset($line[$indent]) and $line[$indent] === ' ')
+ {
+ $indent ++;
+ }
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ $Blocks []= $CurrentBlock;
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+ {
+ $CurrentBlock['element']['text'] .= "\n".$text;
+ }
+ else
+ {
+ $Blocks []= $CurrentBlock;
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+
+ # ~
+
+ $Blocks []= $CurrentBlock;
+
+ unset($Blocks[0]);
+
+ # ~
+
+ $markup = '';
+
+ foreach ($Blocks as $Block)
+ {
+ if (isset($Block['hidden']))
+ {
+ continue;
+ }
+
+ $markup .= "\n";
+ $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
+ }
+
+ $markup .= "\n";
+
+ # ~
+
+ return $markup;
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['text']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+ {
+ $Block = array(
+ 'markup' => $Line['body'],
+ );
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['markup'] .= "\n" . $Line['body'];
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
+ {
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if (isset($matches[1]))
+ {
+ /**
+ * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
+ * Every HTML element may have a class attribute specified.
+ * The attribute, if specified, must have a value that is a set
+ * of space-separated tokens representing the various classes
+ * that the element belongs to.
+ * [...]
+ * The space characters, for the purposes of this specification,
+ * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
+ * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
+ * U+000D CARRIAGE RETURN (CR).
+ */
+ $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
+
+ $class = 'language-'.$language;
+
+ $Element['attributes'] = array(
+ 'class' => $class,
+ );
+ }
+
+ $Block = array(
+ 'char' => $Line['text'][0],
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+ {
+ $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['text']['text'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ if (isset($Line['text'][1]))
+ {
+ $level = 1;
+
+ while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
+ {
+ $level ++;
+ }
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '# ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . min(6, $level),
+ 'text' => $text,
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # List
+
+ protected function blockList($Line)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+
+ if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'element' => array(
+ 'name' => $name,
+ 'handler' => 'elements',
+ ),
+ );
+
+ if($name === 'ol')
+ {
+ $listStart = stristr($matches[0], '.', true);
+
+ if($listStart !== '1')
+ {
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $matches[2],
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['text'] []= '';
+
+ $Block['loose'] = true;
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $text,
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ return $Block;
+ }
+
+ if ($Line['indent'] > 0)
+ {
+ $Block['li']['text'] []= '';
+
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ unset($Block['interrupted']);
+
+ return $Block;
+ }
+ }
+
+ protected function blockListComplete(array $Block)
+ {
+ if (isset($Block['loose']))
+ {
+ foreach ($Block['element']['text'] as &$li)
+ {
+ if (end($li['text']) !== '')
+ {
+ $li['text'] []= '';
+ }
+ }
+ }
+
+ return $Block;
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => 'lines',
+ 'text' => (array) $matches[1],
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr'
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (chop($Line['text'], $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped or $this->safeMode)
+ {
+ return;
+ }
+
+ if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'depth' => 0,
+ 'markup' => $Line['text'],
+ );
+
+ $length = strlen($matches[0]);
+
+ $remainder = substr($Line['text'], $length);
+
+ if (trim($remainder) === '')
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ $Block['closed'] = true;
+
+ $Block['void'] = true;
+ }
+ }
+ else
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ return;
+ }
+
+ if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+ {
+ $Block['depth'] ++;
+ }
+
+ if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+ {
+ if ($Block['depth'] > 0)
+ {
+ $Block['depth'] --;
+ }
+ else
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['markup'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['markup'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
+ {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => null,
+ );
+
+ if (isset($matches[3]))
+ {
+ $Data['title'] = $matches[3];
+ }
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+ {
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ continue;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['text'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'text' => $headerCell,
+ 'handler' => 'line',
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => 'text-align: '.$alignment.';',
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'handler' => 'elements',
+ ),
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'thead',
+ 'handler' => 'elements',
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'tbody',
+ 'handler' => 'elements',
+ 'text' => array(),
+ );
+
+ $Block['element']['text'][0]['text'] []= array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+
+ foreach ($matches[0] as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => 'line',
+ 'text' => $cell,
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: '.$Block['alignments'][$index].';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $Elements,
+ );
+
+ $Block['element']['text'][1]['text'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'p',
+ 'text' => $Line['text'],
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '"' => array('SpecialCharacter'),
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
+ '>' => array('SpecialCharacter'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!"*_&[:<>`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text, $nonNestables=array())
+ {
+ $markup = '';
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strpos($text, $marker);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ # check to see if the current inline type is nestable in the current context
+
+ if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
+ {
+ continue;
+ }
+
+ $Inline = $this->{'inline'.$inlineType}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # cause the new element to 'inherit' our non nestables
+
+ foreach ($nonNestables as $non_nestable)
+ {
+ $Inline['element']['nonNestables'][] = $non_nestable;
+ }
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ # compile the inline
+ $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $markup .= $this->unmarkedText($text);
+
+ return $markup;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+ {
+ $text = $matches[2];
+ $text = preg_replace("/[ ]*\n/", ' ', $text);
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = 'mailto:' . $url;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => 'line',
+ 'text' => $matches[1],
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'markup' => $Excerpt['text'][1],
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['text'],
+ ),
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => 'line',
+ 'nonNestables' => array('Url', 'Link'),
+ 'text' => null,
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+ {
+ $Element['text'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['text']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
+ {
+ return array(
+ 'markup' => '&amp;',
+ 'extent' => 1,
+ );
+ }
+
+ $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
+
+ if (isset($SpecialCharacter[$Excerpt['text'][0]]))
+ {
+ return array(
+ 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
+ 'extent' => 1,
+ );
+ }
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'text' => $matches[1],
+ 'handler' => 'line',
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $url = $matches[0][0];
+
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ if ($this->breaksEnabled)
+ {
+ $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ if ($this->safeMode)
+ {
+ $Element = $this->sanitiseElement($Element);
+ }
+
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.self::escape($value).'"';
+ }
+ }
+
+ if (isset($Element['text']))
+ {
+ $markup .= '>';
+
+ if (!isset($Element['nonNestables']))
+ {
+ $Element['nonNestables'] = array();
+ }
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
+ }
+ else
+ {
+ $markup .= self::escape($Element['text'], true);
+ }
+
+ $markup .= '</'.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, "</p>");
+
+ $markup = substr_replace($markup, '', $position, 4);
+ }
+
+ return $markup;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ protected function sanitiseElement(array $Element)
+ {
+ static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+ static $safeUrlNameToAtt = array(
+ 'a' => 'href',
+ 'img' => 'src',
+ );
+
+ if (isset($safeUrlNameToAtt[$Element['name']]))
+ {
+ $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+ }
+
+ if ( ! empty($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $att => $val)
+ {
+ # filter out badly parsed attribute
+ if ( ! preg_match($goodAttribute, $att))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ # dump onevent attribute
+ elseif (self::striAtStart($att, 'on'))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ }
+ }
+
+ return $Element;
+ }
+
+ protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+ {
+ foreach ($this->safeLinksWhitelist as $scheme)
+ {
+ if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+ {
+ return $Element;
+ }
+ }
+
+ $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+ return $Element;
+ }
+
+ #
+ # Static Methods
+ #
+
+ protected static function escape($text, $allowQuotes = false)
+ {
+ return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+ }
+
+ protected static function striAtStart($string, $needle)
+ {
+ $len = strlen($needle);
+
+ if ($len > strlen($string))
+ {
+ return false;
+ }
+ else
+ {
+ return strtolower(substr($string, 0, $len)) === strtolower($needle);
+ }
+ }
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
+ );
+}
diff --git a/library/vendor/Parsedown/SOURCE b/library/vendor/Parsedown/SOURCE
new file mode 100644
index 0000000..4570e91
--- /dev/null
+++ b/library/vendor/Parsedown/SOURCE
@@ -0,0 +1,7 @@
+RELEASE=1.7.3
+PARSEDOWN=parsedown-$RELEASE
+curl https://codeload.github.com/erusev/parsedown/tar.gz/${RELEASE} -o ${PARSEDOWN}.tar.gz
+tar xfz ${PARSEDOWN}.tar.gz --strip-components 1 ${PARSEDOWN}/Parsedown.php ${PARSEDOWN}/LICENSE.txt
+chmod 0644 Parsedown.php
+mv LICENSE.txt LICENSE
+rm ${PARSEDOWN}.tar.gz
diff --git a/library/vendor/Zend/Cache.php b/library/vendor/Zend/Cache.php
new file mode 100644
index 0000000..272cd0b
--- /dev/null
+++ b/library/vendor/Zend/Cache.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Cache
+{
+
+ /**
+ * Standard frontends
+ *
+ * @var array
+ */
+ public static $standardFrontends = array('Core', 'Output', 'Class', 'File', 'Function', 'Page');
+
+ /**
+ * Standard backends
+ *
+ * @var array
+ */
+ public static $standardBackends = array('File', 'Sqlite', 'Memcached', 'Libmemcached', 'Apc', 'ZendPlatform',
+ 'Xcache', 'TwoLevels', 'WinCache', 'ZendServer_Disk', 'ZendServer_ShMem');
+
+ /**
+ * Standard backends which implement the ExtendedInterface
+ *
+ * @var array
+ */
+ public static $standardExtendedBackends = array('File', 'Apc', 'TwoLevels', 'Memcached', 'Libmemcached', 'Sqlite', 'WinCache');
+
+ /**
+ * Only for backward compatibility (may be removed in next major release)
+ *
+ * @var array
+ * @deprecated
+ */
+ public static $availableFrontends = array('Core', 'Output', 'Class', 'File', 'Function', 'Page');
+
+ /**
+ * Only for backward compatibility (may be removed in next major release)
+ *
+ * @var array
+ * @deprecated
+ */
+ public static $availableBackends = array('File', 'Sqlite', 'Memcached', 'Libmemcached', 'Apc', 'ZendPlatform', 'Xcache', 'WinCache', 'TwoLevels');
+
+ /**
+ * Consts for clean() method
+ */
+ const CLEANING_MODE_ALL = 'all';
+ const CLEANING_MODE_OLD = 'old';
+ const CLEANING_MODE_MATCHING_TAG = 'matchingTag';
+ const CLEANING_MODE_NOT_MATCHING_TAG = 'notMatchingTag';
+ const CLEANING_MODE_MATCHING_ANY_TAG = 'matchingAnyTag';
+
+ /**
+ * Factory
+ *
+ * @param mixed $frontend frontend name (string) or Zend_Cache_Frontend_ object
+ * @param mixed $backend backend name (string) or Zend_Cache_Backend_ object
+ * @param array $frontendOptions associative array of options for the corresponding frontend constructor
+ * @param array $backendOptions associative array of options for the corresponding backend constructor
+ * @param boolean $customFrontendNaming if true, the frontend argument is used as a complete class name ; if false, the frontend argument is used as the end of "Zend_Cache_Frontend_[...]" class name
+ * @param boolean $customBackendNaming if true, the backend argument is used as a complete class name ; if false, the backend argument is used as the end of "Zend_Cache_Backend_[...]" class name
+ * @param boolean $autoload if true, there will no require_once for backend and frontend (useful only for custom backends/frontends)
+ * @throws Zend_Cache_Exception
+ * @return Zend_Cache_Core|Zend_Cache_Frontend
+ */
+ public static function factory($frontend, $backend, $frontendOptions = array(), $backendOptions = array(), $customFrontendNaming = false, $customBackendNaming = false, $autoload = false)
+ {
+ if (is_string($backend)) {
+ $backendObject = self::_makeBackend($backend, $backendOptions, $customBackendNaming, $autoload);
+ } else {
+ if ((is_object($backend)) && (in_array('Zend_Cache_Backend_Interface', class_implements($backend)))) {
+ $backendObject = $backend;
+ } else {
+ self::throwException('backend must be a backend name (string) or an object which implements Zend_Cache_Backend_Interface');
+ }
+ }
+ if (is_string($frontend)) {
+ $frontendObject = self::_makeFrontend($frontend, $frontendOptions, $customFrontendNaming, $autoload);
+ } else {
+ if (is_object($frontend)) {
+ $frontendObject = $frontend;
+ } else {
+ self::throwException('frontend must be a frontend name (string) or an object');
+ }
+ }
+ $frontendObject->setBackend($backendObject);
+ return $frontendObject;
+ }
+
+ /**
+ * Backend Constructor
+ *
+ * @param string $backend
+ * @param array $backendOptions
+ * @param boolean $customBackendNaming
+ * @param boolean $autoload
+ * @return Zend_Cache_Backend
+ */
+ public static function _makeBackend($backend, $backendOptions, $customBackendNaming = false, $autoload = false)
+ {
+ if (!$customBackendNaming) {
+ $backend = self::_normalizeName($backend);
+ }
+ if (in_array($backend, Zend_Cache::$standardBackends)) {
+ // we use a standard backend
+ $backendClass = 'Zend_Cache_Backend_' . $backend;
+ // security controls are explicit
+ } else {
+ // we use a custom backend
+ if (!preg_match('~^[\w\\\\]+$~D', $backend)) {
+ Zend_Cache::throwException("Invalid backend name [$backend]");
+ }
+ if (!$customBackendNaming) {
+ // we use this boolean to avoid an API break
+ $backendClass = 'Zend_Cache_Backend_' . $backend;
+ } else {
+ $backendClass = $backend;
+ }
+ if (!$autoload) {
+ $file = str_replace('_', DIRECTORY_SEPARATOR, $backendClass) . '.php';
+ if (!(self::_isReadable($file))) {
+ self::throwException("file $file not found in include_path");
+ }
+ }
+ }
+ return new $backendClass($backendOptions);
+ }
+
+ /**
+ * Frontend Constructor
+ *
+ * @param string $frontend
+ * @param array $frontendOptions
+ * @param boolean $customFrontendNaming
+ * @param boolean $autoload
+ * @return Zend_Cache_Core|Zend_Cache_Frontend
+ */
+ public static function _makeFrontend($frontend, $frontendOptions = array(), $customFrontendNaming = false, $autoload = false)
+ {
+ if (!$customFrontendNaming) {
+ $frontend = self::_normalizeName($frontend);
+ }
+ if (in_array($frontend, self::$standardFrontends)) {
+ // we use a standard frontend
+ // For perfs reasons, with frontend == 'Core', we can interact with the Core itself
+ $frontendClass = 'Zend_Cache_' . ($frontend != 'Core' ? 'Frontend_' : '') . $frontend;
+ // security controls are explicit
+ } else {
+ // we use a custom frontend
+ if (!preg_match('~^[\w\\\\]+$~D', $frontend)) {
+ Zend_Cache::throwException("Invalid frontend name [$frontend]");
+ }
+ if (!$customFrontendNaming) {
+ // we use this boolean to avoid an API break
+ $frontendClass = 'Zend_Cache_Frontend_' . $frontend;
+ } else {
+ $frontendClass = $frontend;
+ }
+ if (!$autoload) {
+ $file = str_replace('_', DIRECTORY_SEPARATOR, $frontendClass) . '.php';
+ if (!(self::_isReadable($file))) {
+ self::throwException("file $file not found in include_path");
+ }
+ }
+ }
+ return new $frontendClass($frontendOptions);
+ }
+
+ /**
+ * Throw an exception
+ *
+ * Note : for perf reasons, the "load" of Zend/Cache/Exception is dynamic
+ * @param string $msg Message for the exception
+ * @throws Zend_Cache_Exception
+ */
+ public static function throwException($msg, Exception $e = null)
+ {
+ // For perfs reasons, we use this dynamic inclusion
+ throw new Zend_Cache_Exception($msg, 0, $e);
+ }
+
+ /**
+ * Normalize frontend and backend names to allow multiple words TitleCased
+ *
+ * @param string $name Name to normalize
+ * @return string
+ */
+ protected static function _normalizeName($name)
+ {
+ $name = ucfirst(strtolower($name));
+ $name = str_replace(array('-', '_', '.'), ' ', $name);
+ $name = ucwords($name);
+ $name = str_replace(' ', '', $name);
+ if (stripos($name, 'ZendServer') === 0) {
+ $name = 'ZendServer_' . substr($name, strlen('ZendServer'));
+ }
+
+ return $name;
+ }
+
+ /**
+ * Returns TRUE if the $filename is readable, or FALSE otherwise.
+ * This function uses the PHP include_path, where PHP's is_readable()
+ * does not.
+ *
+ * Note : this method comes from Zend_Loader (see #ZF-2891 for details)
+ *
+ * @param string $filename
+ * @return boolean
+ */
+ private static function _isReadable($filename)
+ {
+ if (!$fh = @fopen($filename, 'r', true)) {
+ return false;
+ }
+ @fclose($fh);
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend.php b/library/vendor/Zend/Cache/Backend.php
new file mode 100644
index 0000000..6e1efde
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend.php
@@ -0,0 +1,285 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend
+{
+ /**
+ * Frontend or Core directives
+ *
+ * =====> (int) lifetime :
+ * - Cache lifetime (in seconds)
+ * - If null, the cache is valid forever
+ *
+ * =====> (int) logging :
+ * - if set to true, a logging is activated throw Zend_Log
+ *
+ * @var array directives
+ */
+ protected $_directives = array(
+ 'lifetime' => 3600,
+ 'logging' => false,
+ 'logger' => null
+ );
+
+ /**
+ * Available options
+ *
+ * @var array available options
+ */
+ protected $_options = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ */
+ public function __construct(array $options = array())
+ {
+ foreach ($options as $name => $value) {
+ $this->setOption($name, $value);
+ }
+ }
+
+ /**
+ * Set the frontend directives
+ *
+ * @param array $directives Assoc of directives
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setDirectives($directives)
+ {
+ if (!is_array($directives)) Zend_Cache::throwException('Directives parameter must be an array');
+ while (list($name, $value) = each($directives)) {
+ if (!is_string($name)) {
+ Zend_Cache::throwException("Incorrect option name : $name");
+ }
+ $name = strtolower($name);
+ if (array_key_exists($name, $this->_directives)) {
+ $this->_directives[$name] = $value;
+ }
+
+ }
+
+ $this->_loggerSanity();
+ }
+
+ /**
+ * Set an option
+ *
+ * @param string $name
+ * @param mixed $value
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setOption($name, $value)
+ {
+ if (!is_string($name)) {
+ Zend_Cache::throwException("Incorrect option name : $name");
+ }
+ $name = strtolower($name);
+ if (array_key_exists($name, $this->_options)) {
+ $this->_options[$name] = $value;
+ }
+ }
+
+ /**
+ * Returns an option
+ *
+ * @param string $name Optional, the options name to return
+ * @throws Zend_Cache_Exceptions
+ * @return mixed
+ */
+ public function getOption($name)
+ {
+ $name = strtolower($name);
+
+ if (array_key_exists($name, $this->_options)) {
+ return $this->_options[$name];
+ }
+
+ if (array_key_exists($name, $this->_directives)) {
+ return $this->_directives[$name];
+ }
+
+ Zend_Cache::throwException("Incorrect option name : {$name}");
+ }
+
+ /**
+ * Get the life time
+ *
+ * if $specificLifetime is not false, the given specific life time is used
+ * else, the global lifetime is used
+ *
+ * @param int $specificLifetime
+ * @return int Cache life time
+ */
+ public function getLifetime($specificLifetime)
+ {
+ if ($specificLifetime === false) {
+ return $this->_directives['lifetime'];
+ }
+ return $specificLifetime;
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * DEPRECATED : use getCapabilities() instead
+ *
+ * @deprecated
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return true;
+ }
+
+ /**
+ * Determine system TMP directory and detect if we have read access
+ *
+ * inspired from Zend_File_Transfer_Adapter_Abstract
+ *
+ * @return string
+ * @throws Zend_Cache_Exception if unable to determine directory
+ */
+ public function getTmpDir()
+ {
+ $tmpdir = array();
+ foreach (array($_ENV, $_SERVER) as $tab) {
+ foreach (array('TMPDIR', 'TEMP', 'TMP', 'windir', 'SystemRoot') as $key) {
+ if (isset($tab[$key]) && is_string($tab[$key])) {
+ if (($key == 'windir') or ($key == 'SystemRoot')) {
+ $dir = realpath($tab[$key] . '\\temp');
+ } else {
+ $dir = realpath($tab[$key]);
+ }
+ if ($this->_isGoodTmpDir($dir)) {
+ return $dir;
+ }
+ }
+ }
+ }
+ $upload = ini_get('upload_tmp_dir');
+ if ($upload) {
+ $dir = realpath($upload);
+ if ($this->_isGoodTmpDir($dir)) {
+ return $dir;
+ }
+ }
+ if (function_exists('sys_get_temp_dir')) {
+ $dir = sys_get_temp_dir();
+ if ($this->_isGoodTmpDir($dir)) {
+ return $dir;
+ }
+ }
+ // Attemp to detect by creating a temporary file
+ $tempFile = tempnam(md5(uniqid(rand(), TRUE)), '');
+ if ($tempFile) {
+ $dir = realpath(dirname($tempFile));
+ unlink($tempFile);
+ if ($this->_isGoodTmpDir($dir)) {
+ return $dir;
+ }
+ }
+ if ($this->_isGoodTmpDir('/tmp')) {
+ return '/tmp';
+ }
+ if ($this->_isGoodTmpDir('\\temp')) {
+ return '\\temp';
+ }
+ Zend_Cache::throwException('Could not determine temp directory, please specify a cache_dir manually');
+ }
+
+ /**
+ * Verify if the given temporary directory is readable and writable
+ *
+ * @param string $dir temporary directory
+ * @return boolean true if the directory is ok
+ */
+ protected function _isGoodTmpDir($dir)
+ {
+ if (is_readable($dir)) {
+ if (is_writable($dir)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Make sure if we enable logging that the Zend_Log class
+ * is available.
+ * Create a default log object if none is set.
+ *
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _loggerSanity()
+ {
+ if (!isset($this->_directives['logging']) || !$this->_directives['logging']) {
+ return;
+ }
+
+ if (isset($this->_directives['logger'])) {
+ if ($this->_directives['logger'] instanceof Zend_Log) {
+ return;
+ }
+ Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.');
+ }
+
+ // Create a default logger to the standard output stream
+ $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output'));
+ $logger->addFilter(new Zend_Log_Filter_Priority(Zend_Log::WARN, '<='));
+ $this->_directives['logger'] = $logger;
+ }
+
+ /**
+ * Log a message at the WARN (4) priority.
+ *
+ * @param string $message
+ * @param int $priority
+ * @return void
+ */
+ protected function _log($message, $priority = 4)
+ {
+ if (!$this->_directives['logging']) {
+ return;
+ }
+
+ if (!isset($this->_directives['logger'])) {
+ Zend_Cache::throwException('Logging is enabled but logger is not set.');
+ }
+ $logger = $this->_directives['logger'];
+ if (!$logger instanceof Zend_Log) {
+ Zend_Cache::throwException('Logger object is not an instance of Zend_Log class.');
+ }
+ $logger->log($message, $priority);
+ }
+}
diff --git a/library/vendor/Zend/Cache/Backend/Apc.php b/library/vendor/Zend/Cache/Backend/Apc.php
new file mode 100644
index 0000000..9605323
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Apc.php
@@ -0,0 +1,353 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Apc extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Log message
+ */
+ const TAGS_UNSUPPORTED_BY_CLEAN_OF_APC_BACKEND = 'Zend_Cache_Backend_Apc::clean() : tags are unsupported by the Apc backend';
+ const TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND = 'Zend_Cache_Backend_Apc::save() : tags are unsupported by the Apc backend';
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ if (!extension_loaded('apc')) {
+ Zend_Cache::throwException('The apc extension must be loaded for using this backend !');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * WARNING $doNotTestCacheValidity=true is unsupported by the Apc backend
+ *
+ * @param string $id cache id
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string cached datas (or false)
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $tmp = apc_fetch($id);
+ if (is_array($tmp)) {
+ return $tmp[0];
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $tmp = apc_fetch($id);
+ if (is_array($tmp)) {
+ return $tmp[1];
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data datas to cache
+ * @param string $id cache id
+ * @param array $tags array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $lifetime = $this->getLifetime($specificLifetime);
+ $result = apc_store($id, array($data, time(), $lifetime), $lifetime);
+ if (count($tags) > 0) {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND);
+ }
+ return $result;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id cache id
+ * @return boolean true if no problem
+ */
+ public function remove($id)
+ {
+ return apc_delete($id);
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => unsupported
+ * 'matchingTag' => unsupported
+ * 'notMatchingTag' => unsupported
+ * 'matchingAnyTag' => unsupported
+ *
+ * @param string $mode clean mode
+ * @param array $tags array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ return apc_clear_cache('user');
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_Apc::clean() : CLEANING_MODE_OLD is unsupported by the Apc backend");
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_APC_BACKEND);
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * DEPRECATED : use getCapabilities() instead
+ *
+ * @deprecated
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return false;
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ $mem = apc_sma_info(true);
+ $memSize = $mem['num_seg'] * $mem['seg_size'];
+ $memAvailable= $mem['avail_mem'];
+ $memUsed = $memSize - $memAvailable;
+ if ($memSize == 0) {
+ Zend_Cache::throwException('can\'t get apc memory size');
+ }
+ if ($memUsed > $memSize) {
+ return 100;
+ }
+ return ((int) (100. * ($memUsed / $memSize)));
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_APC_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ $ids = array();
+ $iterator = new APCIterator('user', null, APC_ITER_KEY);
+ foreach ($iterator as $item) {
+ $ids[] = $item['key'];
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ $tmp = apc_fetch($id);
+ if (is_array($tmp)) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ if (!isset($tmp[2])) {
+ // because this record is only with 1.7 release
+ // if old cache records are still there...
+ return false;
+ }
+ $lifetime = $tmp[2];
+ return array(
+ 'expire' => $mtime + $lifetime,
+ 'tags' => array(),
+ 'mtime' => $mtime
+ );
+ }
+ return false;
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ $tmp = apc_fetch($id);
+ if (is_array($tmp)) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ if (!isset($tmp[2])) {
+ // because this record is only with 1.7 release
+ // if old cache records are still there...
+ return false;
+ }
+ $lifetime = $tmp[2];
+ $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
+ if ($newLifetime <=0) {
+ return false;
+ }
+ apc_store($id, array($data, time(), $newLifetime), $newLifetime);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => false,
+ 'tags' => false,
+ 'expired_read' => false,
+ 'priority' => false,
+ 'infinite_lifetime' => false,
+ 'get_list' => true
+ );
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/BlackHole.php b/library/vendor/Zend/Cache/Backend/BlackHole.php
new file mode 100644
index 0000000..8732c19
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/BlackHole.php
@@ -0,0 +1,248 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_BlackHole
+ extends Zend_Cache_Backend
+ implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id cache id
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ return true;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id cache id
+ * @return boolean true if no problem
+ */
+ public function remove($id)
+ {
+ return true;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => remove too old cache entries ($tags is not used)
+ * 'matchingTag' => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * 'notMatchingTag' => remove cache entries not matching one of the given tags
+ * ($tags can be an array of strings or a single string)
+ * 'matchingAnyTag' => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode clean mode
+ * @param tags array $tags array of tags
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ return true;
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ return array();
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ return array();
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @return int integer between 0 and 100
+ * @throws Zend_Cache_Exception
+ */
+ public function getFillingPercentage()
+ {
+ return 0;
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ return false;
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ return false;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => true,
+ 'tags' => true,
+ 'expired_read' => true,
+ 'priority' => true,
+ 'infinite_lifetime' => true,
+ 'get_list' => true,
+ );
+ }
+
+ /**
+ * PUBLIC METHOD FOR UNIT TESTING ONLY !
+ *
+ * Force a cache record to expire
+ *
+ * @param string $id cache id
+ */
+ public function ___expire($id)
+ {
+ }
+}
diff --git a/library/vendor/Zend/Cache/Backend/ExtendedInterface.php b/library/vendor/Zend/Cache/Backend/ExtendedInterface.php
new file mode 100644
index 0000000..2f0f526
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/ExtendedInterface.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Cache_Backend_ExtendedInterface extends Zend_Cache_Backend_Interface
+{
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds();
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags();
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array());
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array());
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array());
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage();
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id);
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime);
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities();
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/File.php b/library/vendor/Zend/Cache/Backend/File.php
new file mode 100644
index 0000000..0fe5475
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/File.php
@@ -0,0 +1,1035 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Available options
+ *
+ * =====> (string) cache_dir :
+ * - Directory where to put the cache files
+ *
+ * =====> (boolean) file_locking :
+ * - Enable / disable file_locking
+ * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread
+ * webservers and on NFS filesystems for example
+ *
+ * =====> (boolean) read_control :
+ * - Enable / disable read control
+ * - If enabled, a control key is embeded in cache file and this key is compared with the one
+ * calculated after the reading.
+ *
+ * =====> (string) read_control_type :
+ * - Type of read control (only if read control is enabled). Available values are :
+ * 'md5' for a md5 hash control (best but slowest)
+ * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
+ * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32)
+ * 'strlen' for a length only test (fastest)
+ *
+ * =====> (int) hashed_directory_level :
+ * - Hashed directory level
+ * - Set the hashed directory structure level. 0 means "no hashed directory
+ * structure", 1 means "one level of directory", 2 means "two levels"...
+ * This option can speed up the cache only when you have many thousands of
+ * cache file. Only specific benchs can help you to choose the perfect value
+ * for you. Maybe, 1 or 2 is a good start.
+ *
+ * =====> (int) hashed_directory_umask :
+ * - deprecated
+ * - Permissions for hashed directory structure
+ *
+ * =====> (int) hashed_directory_perm :
+ * - Permissions for hashed directory structure
+ *
+ * =====> (string) file_name_prefix :
+ * - prefix for cache files
+ * - be really carefull with this option because a too generic value in a system cache dir
+ * (like /tmp) can cause disasters when cleaning the cache
+ *
+ * =====> (int) cache_file_umask :
+ * - deprecated
+ * - Permissions for cache files
+ *
+ * =====> (int) cache_file_perm :
+ * - Permissions for cache files
+ *
+ * =====> (int) metatadatas_array_max_size :
+ * - max size for the metadatas array (don't change this value unless you
+ * know what you are doing)
+ *
+ * @var array available options
+ */
+ protected $_options = array(
+ 'cache_dir' => null,
+ 'file_locking' => true,
+ 'read_control' => true,
+ 'read_control_type' => 'crc32',
+ 'hashed_directory_level' => 0,
+ 'hashed_directory_perm' => 0700,
+ 'file_name_prefix' => 'zend_cache',
+ 'cache_file_perm' => 0600,
+ 'metadatas_array_max_size' => 100
+ );
+
+ /**
+ * Array of metadatas (each item is an associative array)
+ *
+ * @var array
+ */
+ protected $_metadatasArray = array();
+
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ */
+ public function __construct(array $options = array())
+ {
+ parent::__construct($options);
+ if ($this->_options['cache_dir'] !== null) { // particular case for this option
+ $this->setCacheDir($this->_options['cache_dir']);
+ } else {
+ $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
+ }
+ if (isset($this->_options['file_name_prefix'])) { // particular case for this option
+ if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
+ Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
+ }
+ }
+ if ($this->_options['metadatas_array_max_size'] < 10) {
+ Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
+ }
+
+ if (isset($options['hashed_directory_umask'])) {
+ // See #ZF-12047
+ trigger_error("'hashed_directory_umask' is deprecated -> please use 'hashed_directory_perm' instead", E_USER_NOTICE);
+ if (!isset($options['hashed_directory_perm'])) {
+ $options['hashed_directory_perm'] = $options['hashed_directory_umask'];
+ }
+ }
+ if (isset($options['hashed_directory_perm']) && is_string($options['hashed_directory_perm'])) {
+ // See #ZF-4422
+ $this->_options['hashed_directory_perm'] = octdec($this->_options['hashed_directory_perm']);
+ }
+
+ if (isset($options['cache_file_umask'])) {
+ // See #ZF-12047
+ trigger_error("'cache_file_umask' is deprecated -> please use 'cache_file_perm' instead", E_USER_NOTICE);
+ if (!isset($options['cache_file_perm'])) {
+ $options['cache_file_perm'] = $options['cache_file_umask'];
+ }
+ }
+ if (isset($options['cache_file_perm']) && is_string($options['cache_file_perm'])) {
+ // See #ZF-4422
+ $this->_options['cache_file_perm'] = octdec($this->_options['cache_file_perm']);
+ }
+ }
+
+ /**
+ * Set the cache_dir (particular case of setOption() method)
+ *
+ * @param string $value
+ * @param boolean $trailingSeparator If true, add a trailing separator is necessary
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setCacheDir($value, $trailingSeparator = true)
+ {
+ if (!is_dir($value)) {
+ Zend_Cache::throwException(sprintf('cache_dir "%s" must be a directory', $value));
+ }
+ if (!is_writable($value)) {
+ Zend_Cache::throwException(sprintf('cache_dir "%s" is not writable', $value));
+ }
+ if ($trailingSeparator) {
+ // add a trailing DIRECTORY_SEPARATOR if necessary
+ $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
+ }
+ $this->_options['cache_dir'] = $value;
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id cache id
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ if (!($this->_test($id, $doNotTestCacheValidity))) {
+ // The cache is not hit !
+ return false;
+ }
+ $metadatas = $this->_getMetadatas($id);
+ $file = $this->_file($id);
+ $data = $this->_fileGetContents($file);
+ if ($this->_options['read_control']) {
+ $hashData = $this->_hash($data, $this->_options['read_control_type']);
+ $hashControl = $metadatas['hash'];
+ if ($hashData != $hashControl) {
+ // Problem detected by the read control !
+ $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
+ $this->remove($id);
+ return false;
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ clearstatcache();
+ return $this->_test($id, false);
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param boolean|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ clearstatcache();
+ $file = $this->_file($id);
+ $path = $this->_path($id);
+ if ($this->_options['hashed_directory_level'] > 0) {
+ if (!is_writable($path)) {
+ // maybe, we just have to build the directory structure
+ $this->_recursiveMkdirAndChmod($id);
+ }
+ if (!is_writable($path)) {
+ return false;
+ }
+ }
+ if ($this->_options['read_control']) {
+ $hash = $this->_hash($data, $this->_options['read_control_type']);
+ } else {
+ $hash = '';
+ }
+ $metadatas = array(
+ 'hash' => $hash,
+ 'mtime' => time(),
+ 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
+ 'tags' => $tags
+ );
+ $res = $this->_setMetadatas($id, $metadatas);
+ if (!$res) {
+ $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
+ return false;
+ }
+ $res = $this->_filePutContents($file, $data);
+ return $res;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id cache id
+ * @return boolean true if no problem
+ */
+ public function remove($id)
+ {
+ $file = $this->_file($id);
+ $boolRemove = $this->_remove($file);
+ $boolMetadata = $this->_delMetadatas($id);
+ return $boolMetadata && $boolRemove;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ *
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode clean mode
+ * @param array $tags array of tags
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ // We use this protected method to hide the recursive stuff
+ clearstatcache();
+ return $this->_clean($this->_options['cache_dir'], $mode, $tags);
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ return $this->_get($this->_options['cache_dir'], 'ids', array());
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ return $this->_get($this->_options['cache_dir'], 'tags', array());
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ return $this->_get($this->_options['cache_dir'], 'matching', $tags);
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ $free = disk_free_space($this->_options['cache_dir']);
+ $total = disk_total_space($this->_options['cache_dir']);
+ if ($total == 0) {
+ Zend_Cache::throwException('can\'t get disk_total_space');
+ } else {
+ if ($free >= $total) {
+ return 100;
+ }
+ return ((int) (100. * ($total - $free) / $total));
+ }
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ $metadatas = $this->_getMetadatas($id);
+ if (!$metadatas) {
+ return false;
+ }
+ if (time() > $metadatas['expire']) {
+ return false;
+ }
+ return array(
+ 'expire' => $metadatas['expire'],
+ 'tags' => $metadatas['tags'],
+ 'mtime' => $metadatas['mtime']
+ );
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ $metadatas = $this->_getMetadatas($id);
+ if (!$metadatas) {
+ return false;
+ }
+ if (time() > $metadatas['expire']) {
+ return false;
+ }
+ $newMetadatas = array(
+ 'hash' => $metadatas['hash'],
+ 'mtime' => time(),
+ 'expire' => $metadatas['expire'] + $extraLifetime,
+ 'tags' => $metadatas['tags']
+ );
+ $res = $this->_setMetadatas($id, $newMetadatas);
+ if (!$res) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => true,
+ 'tags' => true,
+ 'expired_read' => true,
+ 'priority' => false,
+ 'infinite_lifetime' => true,
+ 'get_list' => true
+ );
+ }
+
+ /**
+ * PUBLIC METHOD FOR UNIT TESTING ONLY !
+ *
+ * Force a cache record to expire
+ *
+ * @param string $id cache id
+ */
+ public function ___expire($id)
+ {
+ $metadatas = $this->_getMetadatas($id);
+ if ($metadatas) {
+ $metadatas['expire'] = 1;
+ $this->_setMetadatas($id, $metadatas);
+ }
+ }
+
+ /**
+ * Get a metadatas record
+ *
+ * @param string $id Cache id
+ * @return array|false Associative array of metadatas
+ */
+ protected function _getMetadatas($id)
+ {
+ if (isset($this->_metadatasArray[$id])) {
+ return $this->_metadatasArray[$id];
+ } else {
+ $metadatas = $this->_loadMetadatas($id);
+ if (!$metadatas) {
+ return false;
+ }
+ $this->_setMetadatas($id, $metadatas, false);
+ return $metadatas;
+ }
+ }
+
+ /**
+ * Set a metadatas record
+ *
+ * @param string $id Cache id
+ * @param array $metadatas Associative array of metadatas
+ * @param boolean $save optional pass false to disable saving to file
+ * @return boolean True if no problem
+ */
+ protected function _setMetadatas($id, $metadatas, $save = true)
+ {
+ if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
+ $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
+ $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
+ }
+ if ($save) {
+ $result = $this->_saveMetadatas($id, $metadatas);
+ if (!$result) {
+ return false;
+ }
+ }
+ $this->_metadatasArray[$id] = $metadatas;
+ return true;
+ }
+
+ /**
+ * Drop a metadata record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ protected function _delMetadatas($id)
+ {
+ if (isset($this->_metadatasArray[$id])) {
+ unset($this->_metadatasArray[$id]);
+ }
+ $file = $this->_metadatasFile($id);
+ return $this->_remove($file);
+ }
+
+ /**
+ * Clear the metadatas array
+ *
+ * @return void
+ */
+ protected function _cleanMetadatas()
+ {
+ $this->_metadatasArray = array();
+ }
+
+ /**
+ * Load metadatas from disk
+ *
+ * @param string $id Cache id
+ * @return array|false Metadatas associative array
+ */
+ protected function _loadMetadatas($id)
+ {
+ $file = $this->_metadatasFile($id);
+ $result = $this->_fileGetContents($file);
+ if (!$result) {
+ return false;
+ }
+ $tmp = @unserialize($result);
+ return $tmp;
+ }
+
+ /**
+ * Save metadatas to disk
+ *
+ * @param string $id Cache id
+ * @param array $metadatas Associative array
+ * @return boolean True if no problem
+ */
+ protected function _saveMetadatas($id, $metadatas)
+ {
+ $file = $this->_metadatasFile($id);
+ $result = $this->_filePutContents($file, serialize($metadatas));
+ if (!$result) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Make and return a file name (with path) for metadatas
+ *
+ * @param string $id Cache id
+ * @return string Metadatas file name (with path)
+ */
+ protected function _metadatasFile($id)
+ {
+ $path = $this->_path($id);
+ $fileName = $this->_idToFileName('internal-metadatas---' . $id);
+ return $path . $fileName;
+ }
+
+ /**
+ * Check if the given filename is a metadatas one
+ *
+ * @param string $fileName File name
+ * @return boolean True if it's a metadatas one
+ */
+ protected function _isMetadatasFile($fileName)
+ {
+ $id = $this->_fileNameToId($fileName);
+ if (substr($id, 0, 21) == 'internal-metadatas---') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Remove a file
+ *
+ * If we can't remove the file (because of locks or any problem), we will touch
+ * the file to invalidate it
+ *
+ * @param string $file Complete file path
+ * @return boolean True if ok
+ */
+ protected function _remove($file)
+ {
+ if (!is_file($file)) {
+ return false;
+ }
+ if (!@unlink($file)) {
+ # we can't remove the file (because of locks or any problem)
+ $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clean some cache records (protected method used for recursive stuff)
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $dir Directory to clean
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ if (!is_dir($dir)) {
+ return false;
+ }
+ $result = true;
+ $prefix = $this->_options['file_name_prefix'];
+ $glob = @glob($dir . $prefix . '--*');
+ if ($glob === false) {
+ // On some systems it is impossible to distinguish between empty match and an error.
+ return true;
+ }
+ $metadataFiles = array();
+ foreach ($glob as $file) {
+ if (is_file($file)) {
+ $fileName = basename($file);
+ if ($this->_isMetadatasFile($fileName)) {
+ // In CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files.
+ // To do that, we need to save the list of the metadata files first.
+ if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
+ $metadataFiles[] = $file;
+ }
+ continue;
+ }
+ $id = $this->_fileNameToId($fileName);
+ $metadatas = $this->_getMetadatas($id);
+ if ($metadatas === FALSE) {
+ $metadatas = array('expire' => 1, 'tags' => array());
+ }
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ $result = $result && $this->remove($id);
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ if (time() > $metadatas['expire']) {
+ $result = $this->remove($id) && $result;
+ }
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ $matching = true;
+ foreach ($tags as $tag) {
+ if (!in_array($tag, $metadatas['tags'])) {
+ $matching = false;
+ break;
+ }
+ }
+ if ($matching) {
+ $result = $this->remove($id) && $result;
+ }
+ break;
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ $matching = false;
+ foreach ($tags as $tag) {
+ if (in_array($tag, $metadatas['tags'])) {
+ $matching = true;
+ break;
+ }
+ }
+ if (!$matching) {
+ $result = $this->remove($id) && $result;
+ }
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $matching = false;
+ foreach ($tags as $tag) {
+ if (in_array($tag, $metadatas['tags'])) {
+ $matching = true;
+ break;
+ }
+ }
+ if ($matching) {
+ $result = $this->remove($id) && $result;
+ }
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+ if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
+ // Recursive call
+ $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
+ if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
+ // we try to drop the structure too
+ @rmdir($file);
+ }
+ }
+ }
+
+ // cycle through metadataFiles and delete orphaned ones
+ foreach ($metadataFiles as $file) {
+ if (file_exists($file)) {
+ $result = $this->_remove($file) && $result;
+ }
+ }
+
+ return $result;
+ }
+
+ protected function _get($dir, $mode, $tags = array())
+ {
+ if (!is_dir($dir)) {
+ return false;
+ }
+ $result = array();
+ $prefix = $this->_options['file_name_prefix'];
+ $glob = @glob($dir . $prefix . '--*');
+ if ($glob === false) {
+ // On some systems it is impossible to distinguish between empty match and an error.
+ return array();
+ }
+ foreach ($glob as $file) {
+ if (is_file($file)) {
+ $fileName = basename($file);
+ $id = $this->_fileNameToId($fileName);
+ $metadatas = $this->_getMetadatas($id);
+ if ($metadatas === FALSE) {
+ continue;
+ }
+ if (time() > $metadatas['expire']) {
+ continue;
+ }
+ switch ($mode) {
+ case 'ids':
+ $result[] = $id;
+ break;
+ case 'tags':
+ $result = array_unique(array_merge($result, $metadatas['tags']));
+ break;
+ case 'matching':
+ $matching = true;
+ foreach ($tags as $tag) {
+ if (!in_array($tag, $metadatas['tags'])) {
+ $matching = false;
+ break;
+ }
+ }
+ if ($matching) {
+ $result[] = $id;
+ }
+ break;
+ case 'notMatching':
+ $matching = false;
+ foreach ($tags as $tag) {
+ if (in_array($tag, $metadatas['tags'])) {
+ $matching = true;
+ break;
+ }
+ }
+ if (!$matching) {
+ $result[] = $id;
+ }
+ break;
+ case 'matchingAny':
+ $matching = false;
+ foreach ($tags as $tag) {
+ if (in_array($tag, $metadatas['tags'])) {
+ $matching = true;
+ break;
+ }
+ }
+ if ($matching) {
+ $result[] = $id;
+ }
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for _get() method');
+ break;
+ }
+ }
+ if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
+ // Recursive call
+ $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags);
+ if ($recursiveRs === false) {
+ $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"');
+ } else {
+ $result = array_unique(array_merge($result, $recursiveRs));
+ }
+ }
+ }
+ return array_unique($result);
+ }
+
+ /**
+ * Compute & return the expire time
+ *
+ * @param int $lifetime
+ * @return int expire time (unix timestamp)
+ */
+ protected function _expireTime($lifetime)
+ {
+ if ($lifetime === null) {
+ return 9999999999;
+ }
+ return time() + $lifetime;
+ }
+
+ /**
+ * Make a control key with the string containing datas
+ *
+ * @param string $data Data
+ * @param string $controlType Type of control 'md5', 'crc32' or 'strlen'
+ * @throws Zend_Cache_Exception
+ * @return string Control key
+ */
+ protected function _hash($data, $controlType)
+ {
+ switch ($controlType) {
+ case 'md5':
+ return md5($data);
+ case 'crc32':
+ return crc32($data);
+ case 'strlen':
+ return strlen($data);
+ case 'adler32':
+ return hash('adler32', $data);
+ default:
+ Zend_Cache::throwException("Incorrect hash function : $controlType");
+ }
+ }
+
+ /**
+ * Transform a cache id into a file name and return it
+ *
+ * @param string $id Cache id
+ * @return string File name
+ */
+ protected function _idToFileName($id)
+ {
+ $prefix = $this->_options['file_name_prefix'];
+ $result = $prefix . '---' . $id;
+ return $result;
+ }
+
+ /**
+ * Make and return a file name (with path)
+ *
+ * @param string $id Cache id
+ * @return string File name (with path)
+ */
+ protected function _file($id)
+ {
+ $path = $this->_path($id);
+ $fileName = $this->_idToFileName($id);
+ return $path . $fileName;
+ }
+
+ /**
+ * Return the complete directory path of a filename (including hashedDirectoryStructure)
+ *
+ * @param string $id Cache id
+ * @param boolean $parts if true, returns array of directory parts instead of single string
+ * @return string Complete directory path
+ */
+ protected function _path($id, $parts = false)
+ {
+ $partsArray = array();
+ $root = $this->_options['cache_dir'];
+ $prefix = $this->_options['file_name_prefix'];
+ if ($this->_options['hashed_directory_level']>0) {
+ $hash = hash('adler32', $id);
+ for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
+ $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
+ $partsArray[] = $root;
+ }
+ }
+ if ($parts) {
+ return $partsArray;
+ } else {
+ return $root;
+ }
+ }
+
+ /**
+ * Make the directory strucuture for the given id
+ *
+ * @param string $id cache id
+ * @return boolean true
+ */
+ protected function _recursiveMkdirAndChmod($id)
+ {
+ if ($this->_options['hashed_directory_level'] <=0) {
+ return true;
+ }
+ $partsArray = $this->_path($id, true);
+ foreach ($partsArray as $part) {
+ if (!is_dir($part)) {
+ @mkdir($part, $this->_options['hashed_directory_perm']);
+ @chmod($part, $this->_options['hashed_directory_perm']); // see #ZF-320 (this line is required in some configurations)
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Test if the given cache id is available (and still valid as a cache record)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ protected function _test($id, $doNotTestCacheValidity)
+ {
+ $metadatas = $this->_getMetadatas($id);
+ if (!$metadatas) {
+ return false;
+ }
+ if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
+ return $metadatas['mtime'];
+ }
+ return false;
+ }
+
+ /**
+ * Return the file content of the given file
+ *
+ * @param string $file File complete path
+ * @return string File content (or false if problem)
+ */
+ protected function _fileGetContents($file)
+ {
+ $result = false;
+ if (!is_file($file)) {
+ return false;
+ }
+ $f = @fopen($file, 'rb');
+ if ($f) {
+ if ($this->_options['file_locking']) @flock($f, LOCK_SH);
+ $result = stream_get_contents($f);
+ if ($this->_options['file_locking']) @flock($f, LOCK_UN);
+ @fclose($f);
+ }
+ return $result;
+ }
+
+ /**
+ * Put the given string into the given file
+ *
+ * @param string $file File complete path
+ * @param string $string String to put in file
+ * @return boolean true if no problem
+ */
+ protected function _filePutContents($file, $string)
+ {
+ $result = false;
+ $f = @fopen($file, 'ab+');
+ if ($f) {
+ if ($this->_options['file_locking']) @flock($f, LOCK_EX);
+ fseek($f, 0);
+ ftruncate($f, 0);
+ $tmp = @fwrite($f, $string);
+ if (!($tmp === FALSE)) {
+ $result = true;
+ }
+ @fclose($f);
+ }
+ @chmod($file, $this->_options['cache_file_perm']);
+ return $result;
+ }
+
+ /**
+ * Transform a file name into cache id and return it
+ *
+ * @param string $fileName File name
+ * @return string Cache id
+ */
+ protected function _fileNameToId($fileName)
+ {
+ $prefix = $this->_options['file_name_prefix'];
+ return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/Interface.php b/library/vendor/Zend/Cache/Backend/Interface.php
new file mode 100644
index 0000000..1bd72d8
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Interface.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Cache_Backend_Interface
+{
+ /**
+ * Set the frontend directives
+ *
+ * @param array $directives assoc of directives
+ */
+ public function setDirectives($directives);
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * Note : return value is always "string" (unserialization is done by the core not by the backend)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false);
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id);
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false);
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id);
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array());
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/Libmemcached.php b/library/vendor/Zend/Cache/Backend/Libmemcached.php
new file mode 100644
index 0000000..ca1f695
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Libmemcached.php
@@ -0,0 +1,482 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Libmemcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Default Server Values
+ */
+ const DEFAULT_HOST = '127.0.0.1';
+ const DEFAULT_PORT = 11211;
+ const DEFAULT_WEIGHT = 1;
+
+ /**
+ * Log message
+ */
+ const TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::clean() : tags are unsupported by the Libmemcached backend';
+ const TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::save() : tags are unsupported by the Libmemcached backend';
+
+ /**
+ * Available options
+ *
+ * =====> (array) servers :
+ * an array of memcached server ; each memcached server is described by an associative array :
+ * 'host' => (string) : the name of the memcached server
+ * 'port' => (int) : the port of the memcached server
+ * 'weight' => (int) : number of buckets to create for this server which in turn control its
+ * probability of it being selected. The probability is relative to the total
+ * weight of all servers.
+ * =====> (array) client :
+ * an array of memcached client options ; the memcached client is described by an associative array :
+ * @see http://php.net/manual/memcached.constants.php
+ * - The option name can be the name of the constant without the prefix 'OPT_'
+ * or the integer value of this option constant
+ *
+ * @var array available options
+ */
+ protected $_options = array(
+ 'servers' => array(array(
+ 'host' => self::DEFAULT_HOST,
+ 'port' => self::DEFAULT_PORT,
+ 'weight' => self::DEFAULT_WEIGHT,
+ )),
+ 'client' => array()
+ );
+
+ /**
+ * Memcached object
+ *
+ * @var mixed memcached object
+ */
+ protected $_memcache = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ if (!extension_loaded('memcached')) {
+ Zend_Cache::throwException('The memcached extension must be loaded for using this backend !');
+ }
+
+ // override default client options
+ $this->_options['client'] = array(
+ Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT,
+ Memcached::OPT_HASH => Memcached::HASH_MD5,
+ Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
+ );
+
+ parent::__construct($options);
+
+ if (isset($this->_options['servers'])) {
+ $value = $this->_options['servers'];
+ if (isset($value['host'])) {
+ // in this case, $value seems to be a simple associative array (one server only)
+ $value = array(0 => $value); // let's transform it into a classical array of associative arrays
+ }
+ $this->setOption('servers', $value);
+ }
+ $this->_memcache = new Memcached;
+
+ // setup memcached client options
+ foreach ($this->_options['client'] as $name => $value) {
+ $optId = null;
+ if (is_int($name)) {
+ $optId = $name;
+ } else {
+ $optConst = 'Memcached::OPT_' . strtoupper($name);
+ if (defined($optConst)) {
+ $optId = constant($optConst);
+ } else {
+ $this->_log("Unknown memcached client option '{$name}' ({$optConst})");
+ }
+ }
+ if (null !== $optId) {
+ if (!$this->_memcache->setOption($optId, $value)) {
+ $this->_log("Setting memcached client option '{$optId}' failed");
+ }
+ }
+ }
+
+ // setup memcached servers
+ $servers = array();
+ foreach ($this->_options['servers'] as $server) {
+ if (!array_key_exists('port', $server)) {
+ $server['port'] = self::DEFAULT_PORT;
+ }
+ if (!array_key_exists('weight', $server)) {
+ $server['weight'] = self::DEFAULT_WEIGHT;
+ }
+
+ $servers[] = array($server['host'], $server['port'], $server['weight']);
+ }
+ $this->_memcache->addServers($servers);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (isset($tmp[0])) {
+ return $tmp[0];
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id Cache id
+ * @return int|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (isset($tmp[0], $tmp[1])) {
+ return (int)$tmp[1];
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean True if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $lifetime = $this->getLifetime($specificLifetime);
+
+ // ZF-8856: using set because add needs a second request if item already exists
+ $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $lifetime);
+ if ($result === false) {
+ $rsCode = $this->_memcache->getResultCode();
+ $rsMsg = $this->_memcache->getResultMessage();
+ $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}");
+ }
+
+ if (count($tags) > 0) {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ return $this->_memcache->delete($id);
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => unsupported
+ * 'matchingTag' => unsupported
+ * 'notMatchingTag' => unsupported
+ * 'matchingAnyTag' => unsupported
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ return $this->_memcache->flush();
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_Libmemcached::clean() : CLEANING_MODE_OLD is unsupported by the Libmemcached backend");
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND);
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return false;
+ }
+
+ /**
+ * Set the frontend directives
+ *
+ * @param array $directives Assoc of directives
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setDirectives($directives)
+ {
+ parent::setDirectives($directives);
+ $lifetime = $this->getLifetime(false);
+ if ($lifetime > 2592000) {
+ // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
+ $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime');
+ }
+ if ($lifetime === null) {
+ // #ZF-4614 : we tranform null to zero to get the maximal lifetime
+ parent::setDirectives(array('lifetime' => 0));
+ }
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ $this->_log("Zend_Cache_Backend_Libmemcached::save() : getting the list of cache ids is unsupported by the Libmemcached backend");
+ return array();
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ $mems = $this->_memcache->getStats();
+ if ($mems === false) {
+ return 0;
+ }
+
+ $memSize = null;
+ $memUsed = null;
+ foreach ($mems as $key => $mem) {
+ if ($mem === false) {
+ $this->_log('can\'t get stat from ' . $key);
+ continue;
+ }
+
+ $eachSize = $mem['limit_maxbytes'];
+ $eachUsed = $mem['bytes'];
+ if ($eachUsed > $eachSize) {
+ $eachUsed = $eachSize;
+ }
+
+ $memSize += $eachSize;
+ $memUsed += $eachUsed;
+ }
+
+ if ($memSize === null || $memUsed === null) {
+ Zend_Cache::throwException('Can\'t get filling percentage');
+ }
+
+ return ((int) (100. * ($memUsed / $memSize)));
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (isset($tmp[0], $tmp[1], $tmp[2])) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ $lifetime = $tmp[2];
+ return array(
+ 'expire' => $mtime + $lifetime,
+ 'tags' => array(),
+ 'mtime' => $mtime
+ );
+ }
+
+ return false;
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (isset($tmp[0], $tmp[1], $tmp[2])) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ $lifetime = $tmp[2];
+ $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
+ if ($newLifetime <=0) {
+ return false;
+ }
+ // #ZF-5702 : we try replace() first becase set() seems to be slower
+ if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $newLifetime))) {
+ $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $newLifetime);
+ if ($result === false) {
+ $rsCode = $this->_memcache->getResultCode();
+ $rsMsg = $this->_memcache->getResultMessage();
+ $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}");
+ }
+ }
+ return $result;
+ }
+ return false;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => false,
+ 'tags' => false,
+ 'expired_read' => false,
+ 'priority' => false,
+ 'infinite_lifetime' => false,
+ 'get_list' => false
+ );
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/Memcached.php b/library/vendor/Zend/Cache/Backend/Memcached.php
new file mode 100644
index 0000000..61ddfdc
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Memcached.php
@@ -0,0 +1,507 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Memcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Default Values
+ */
+ const DEFAULT_HOST = '127.0.0.1';
+ const DEFAULT_PORT = 11211;
+ const DEFAULT_PERSISTENT = true;
+ const DEFAULT_WEIGHT = 1;
+ const DEFAULT_TIMEOUT = 1;
+ const DEFAULT_RETRY_INTERVAL = 15;
+ const DEFAULT_STATUS = true;
+ const DEFAULT_FAILURE_CALLBACK = null;
+
+ /**
+ * Log message
+ */
+ const TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::clean() : tags are unsupported by the Memcached backend';
+ const TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend';
+
+ /**
+ * Available options
+ *
+ * =====> (array) servers :
+ * an array of memcached server ; each memcached server is described by an associative array :
+ * 'host' => (string) : the name of the memcached server
+ * 'port' => (int) : the port of the memcached server
+ * 'persistent' => (bool) : use or not persistent connections to this memcached server
+ * 'weight' => (int) : number of buckets to create for this server which in turn control its
+ * probability of it being selected. The probability is relative to the total
+ * weight of all servers.
+ * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice
+ * before changing the default value of 1 second - you can lose all the
+ * advantages of caching if your connection is too slow.
+ * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value
+ * is 15 seconds. Setting this parameter to -1 disables automatic retry.
+ * 'status' => (bool) : controls if the server should be flagged as online.
+ * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon
+ * encountering an error. The callback is run before failover
+ * is attempted. The function takes two parameters, the hostname
+ * and port of the failed server.
+ *
+ * =====> (boolean) compression :
+ * true if you want to use on-the-fly compression
+ *
+ * =====> (boolean) compatibility :
+ * true if you use old memcache server or extension
+ *
+ * @var array available options
+ */
+ protected $_options = array(
+ 'servers' => array(array(
+ 'host' => self::DEFAULT_HOST,
+ 'port' => self::DEFAULT_PORT,
+ 'persistent' => self::DEFAULT_PERSISTENT,
+ 'weight' => self::DEFAULT_WEIGHT,
+ 'timeout' => self::DEFAULT_TIMEOUT,
+ 'retry_interval' => self::DEFAULT_RETRY_INTERVAL,
+ 'status' => self::DEFAULT_STATUS,
+ 'failure_callback' => self::DEFAULT_FAILURE_CALLBACK
+ )),
+ 'compression' => false,
+ 'compatibility' => false,
+ );
+
+ /**
+ * Memcache object
+ *
+ * @var mixed memcache object
+ */
+ protected $_memcache = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ if (!extension_loaded('memcache')) {
+ Zend_Cache::throwException('The memcache extension must be loaded for using this backend !');
+ }
+ parent::__construct($options);
+ if (isset($this->_options['servers'])) {
+ $value= $this->_options['servers'];
+ if (isset($value['host'])) {
+ // in this case, $value seems to be a simple associative array (one server only)
+ $value = array(0 => $value); // let's transform it into a classical array of associative arrays
+ }
+ $this->setOption('servers', $value);
+ }
+ $this->_memcache = new Memcache;
+ foreach ($this->_options['servers'] as $server) {
+ if (!array_key_exists('port', $server)) {
+ $server['port'] = self::DEFAULT_PORT;
+ }
+ if (!array_key_exists('persistent', $server)) {
+ $server['persistent'] = self::DEFAULT_PERSISTENT;
+ }
+ if (!array_key_exists('weight', $server)) {
+ $server['weight'] = self::DEFAULT_WEIGHT;
+ }
+ if (!array_key_exists('timeout', $server)) {
+ $server['timeout'] = self::DEFAULT_TIMEOUT;
+ }
+ if (!array_key_exists('retry_interval', $server)) {
+ $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL;
+ }
+ if (!array_key_exists('status', $server)) {
+ $server['status'] = self::DEFAULT_STATUS;
+ }
+ if (!array_key_exists('failure_callback', $server)) {
+ $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK;
+ }
+ if ($this->_options['compatibility']) {
+ // No status for compatibility mode (#ZF-5887)
+ $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'],
+ $server['weight'], $server['timeout'],
+ $server['retry_interval']);
+ } else {
+ $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'],
+ $server['weight'], $server['timeout'],
+ $server['retry_interval'],
+ $server['status'], $server['failure_callback']);
+ }
+ }
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (is_array($tmp) && isset($tmp[0])) {
+ return $tmp[0];
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id Cache id
+ * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (is_array($tmp)) {
+ return $tmp[1];
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean True if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $lifetime = $this->getLifetime($specificLifetime);
+ if ($this->_options['compression']) {
+ $flag = MEMCACHE_COMPRESSED;
+ } else {
+ $flag = 0;
+ }
+
+ // ZF-8856: using set because add needs a second request if item already exists
+ $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime);
+
+ if (count($tags) > 0) {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ return $this->_memcache->delete($id, 0);
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => unsupported
+ * 'matchingTag' => unsupported
+ * 'notMatchingTag' => unsupported
+ * 'matchingAnyTag' => unsupported
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ return $this->_memcache->flush();
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend");
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND);
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return false;
+ }
+
+ /**
+ * Set the frontend directives
+ *
+ * @param array $directives Assoc of directives
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setDirectives($directives)
+ {
+ parent::setDirectives($directives);
+ $lifetime = $this->getLifetime(false);
+ if ($lifetime > 2592000) {
+ // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
+ $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime');
+ }
+ if ($lifetime === null) {
+ // #ZF-4614 : we tranform null to zero to get the maximal lifetime
+ parent::setDirectives(array('lifetime' => 0));
+ }
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ $this->_log("Zend_Cache_Backend_Memcached::save() : getting the list of cache ids is unsupported by the Memcache backend");
+ return array();
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ $mems = $this->_memcache->getExtendedStats();
+
+ $memSize = null;
+ $memUsed = null;
+ foreach ($mems as $key => $mem) {
+ if ($mem === false) {
+ $this->_log('can\'t get stat from ' . $key);
+ continue;
+ }
+
+ $eachSize = $mem['limit_maxbytes'];
+
+ /**
+ * Couchbase 1.x uses 'mem_used' instead of 'bytes'
+ * @see https://www.couchbase.com/issues/browse/MB-3466
+ */
+ $eachUsed = isset($mem['bytes']) ? $mem['bytes'] : $mem['mem_used'];
+ if ($eachUsed > $eachSize) {
+ $eachUsed = $eachSize;
+ }
+
+ $memSize += $eachSize;
+ $memUsed += $eachUsed;
+ }
+
+ if ($memSize === null || $memUsed === null) {
+ Zend_Cache::throwException('Can\'t get filling percentage');
+ }
+
+ return ((int) (100. * ($memUsed / $memSize)));
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ $tmp = $this->_memcache->get($id);
+ if (is_array($tmp)) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ if (!isset($tmp[2])) {
+ // because this record is only with 1.7 release
+ // if old cache records are still there...
+ return false;
+ }
+ $lifetime = $tmp[2];
+ return array(
+ 'expire' => $mtime + $lifetime,
+ 'tags' => array(),
+ 'mtime' => $mtime
+ );
+ }
+ return false;
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ if ($this->_options['compression']) {
+ $flag = MEMCACHE_COMPRESSED;
+ } else {
+ $flag = 0;
+ }
+ $tmp = $this->_memcache->get($id);
+ if (is_array($tmp)) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ if (!isset($tmp[2])) {
+ // because this record is only with 1.7 release
+ // if old cache records are still there...
+ return false;
+ }
+ $lifetime = $tmp[2];
+ $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
+ if ($newLifetime <=0) {
+ return false;
+ }
+ // #ZF-5702 : we try replace() first becase set() seems to be slower
+ if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $flag, $newLifetime))) {
+ $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $newLifetime);
+ }
+ return $result;
+ }
+ return false;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => false,
+ 'tags' => false,
+ 'expired_read' => false,
+ 'priority' => false,
+ 'infinite_lifetime' => false,
+ 'get_list' => false
+ );
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/Sqlite.php b/library/vendor/Zend/Cache/Backend/Sqlite.php
new file mode 100644
index 0000000..fa6be7d
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Sqlite.php
@@ -0,0 +1,676 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Sqlite extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Available options
+ *
+ * =====> (string) cache_db_complete_path :
+ * - the complete path (filename included) of the SQLITE database
+ *
+ * ====> (int) automatic_vacuum_factor :
+ * - Disable / Tune the automatic vacuum process
+ * - The automatic vacuum process defragment the database file (and make it smaller)
+ * when a clean() or delete() is called
+ * 0 => no automatic vacuum
+ * 1 => systematic vacuum (when delete() or clean() methods are called)
+ * x (integer) > 1 => automatic vacuum randomly 1 times on x clean() or delete()
+ *
+ * @var array Available options
+ */
+ protected $_options = array(
+ 'cache_db_complete_path' => null,
+ 'automatic_vacuum_factor' => 10
+ );
+
+ /**
+ * DB ressource
+ *
+ * @var mixed $_db
+ */
+ private $_db = null;
+
+ /**
+ * Boolean to store if the structure has benn checked or not
+ *
+ * @var boolean $_structureChecked
+ */
+ private $_structureChecked = false;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @throws Zend_cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ parent::__construct($options);
+ if ($this->_options['cache_db_complete_path'] === null) {
+ Zend_Cache::throwException('cache_db_complete_path option has to set');
+ }
+ if (!extension_loaded('sqlite')) {
+ Zend_Cache::throwException("Cannot use SQLite storage because the 'sqlite' extension is not loaded in the current PHP environment");
+ }
+ $this->_getConnection();
+ }
+
+ /**
+ * Destructor
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ @sqlite_close($this->_getConnection());
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string|false Cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $this->_checkAndBuildStructure();
+ $sql = "SELECT content FROM cache WHERE id='$id'";
+ if (!$doNotTestCacheValidity) {
+ $sql = $sql . " AND (expire=0 OR expire>" . time() . ')';
+ }
+ $result = $this->_query($sql);
+ $row = @sqlite_fetch_array($result);
+ if ($row) {
+ return $row['content'];
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id Cache id
+ * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $this->_checkAndBuildStructure();
+ $sql = "SELECT lastModified FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')';
+ $result = $this->_query($sql);
+ $row = @sqlite_fetch_array($result);
+ if ($row) {
+ return ((int) $row['lastModified']);
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $this->_checkAndBuildStructure();
+ $lifetime = $this->getLifetime($specificLifetime);
+ $data = @sqlite_escape_string($data);
+ $mktime = time();
+ if ($lifetime === null) {
+ $expire = 0;
+ } else {
+ $expire = $mktime + $lifetime;
+ }
+ $this->_query("DELETE FROM cache WHERE id='$id'");
+ $sql = "INSERT INTO cache (id, content, lastModified, expire) VALUES ('$id', '$data', $mktime, $expire)";
+ $res = $this->_query($sql);
+ if (!$res) {
+ $this->_log("Zend_Cache_Backend_Sqlite::save() : impossible to store the cache id=$id");
+ return false;
+ }
+ $res = true;
+ foreach ($tags as $tag) {
+ $res = $this->_registerTag($id, $tag) && $res;
+ }
+ return $res;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ $this->_checkAndBuildStructure();
+ $res = $this->_query("SELECT COUNT(*) AS nbr FROM cache WHERE id='$id'");
+ $result1 = @sqlite_fetch_single($res);
+ $result2 = $this->_query("DELETE FROM cache WHERE id='$id'");
+ $result3 = $this->_query("DELETE FROM tag WHERE id='$id'");
+ $this->_automaticVacuum();
+ return ($result1 && $result2 && $result3);
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @return boolean True if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ $this->_checkAndBuildStructure();
+ $return = $this->_clean($mode, $tags);
+ $this->_automaticVacuum();
+ return $return;
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ $this->_checkAndBuildStructure();
+ $res = $this->_query("SELECT id FROM cache WHERE (expire=0 OR expire>" . time() . ")");
+ $result = array();
+ while ($id = @sqlite_fetch_single($res)) {
+ $result[] = $id;
+ }
+ return $result;
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ $this->_checkAndBuildStructure();
+ $res = $this->_query("SELECT DISTINCT(name) AS name FROM tag");
+ $result = array();
+ while ($id = @sqlite_fetch_single($res)) {
+ $result[] = $id;
+ }
+ return $result;
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ $first = true;
+ $ids = array();
+ foreach ($tags as $tag) {
+ $res = $this->_query("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'");
+ if (!$res) {
+ return array();
+ }
+ $rows = @sqlite_fetch_all($res, SQLITE_ASSOC);
+ $ids2 = array();
+ foreach ($rows as $row) {
+ $ids2[] = $row['id'];
+ }
+ if ($first) {
+ $ids = $ids2;
+ $first = false;
+ } else {
+ $ids = array_intersect($ids, $ids2);
+ }
+ }
+ $result = array();
+ foreach ($ids as $id) {
+ $result[] = $id;
+ }
+ return $result;
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ $res = $this->_query("SELECT id FROM cache");
+ $rows = @sqlite_fetch_all($res, SQLITE_ASSOC);
+ $result = array();
+ foreach ($rows as $row) {
+ $id = $row['id'];
+ $matching = false;
+ foreach ($tags as $tag) {
+ $res = $this->_query("SELECT COUNT(*) AS nbr FROM tag WHERE name='$tag' AND id='$id'");
+ if (!$res) {
+ return array();
+ }
+ $nbr = (int) @sqlite_fetch_single($res);
+ if ($nbr > 0) {
+ $matching = true;
+ }
+ }
+ if (!$matching) {
+ $result[] = $id;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ $first = true;
+ $ids = array();
+ foreach ($tags as $tag) {
+ $res = $this->_query("SELECT DISTINCT(id) AS id FROM tag WHERE name='$tag'");
+ if (!$res) {
+ return array();
+ }
+ $rows = @sqlite_fetch_all($res, SQLITE_ASSOC);
+ $ids2 = array();
+ foreach ($rows as $row) {
+ $ids2[] = $row['id'];
+ }
+ if ($first) {
+ $ids = $ids2;
+ $first = false;
+ } else {
+ $ids = array_merge($ids, $ids2);
+ }
+ }
+ $result = array();
+ foreach ($ids as $id) {
+ $result[] = $id;
+ }
+ return $result;
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ $dir = dirname($this->_options['cache_db_complete_path']);
+ $free = disk_free_space($dir);
+ $total = disk_total_space($dir);
+ if ($total == 0) {
+ Zend_Cache::throwException('can\'t get disk_total_space');
+ } else {
+ if ($free >= $total) {
+ return 100;
+ }
+ return ((int) (100. * ($total - $free) / $total));
+ }
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ $tags = array();
+ $res = $this->_query("SELECT name FROM tag WHERE id='$id'");
+ if ($res) {
+ $rows = @sqlite_fetch_all($res, SQLITE_ASSOC);
+ foreach ($rows as $row) {
+ $tags[] = $row['name'];
+ }
+ }
+ $this->_query('CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)');
+ $res = $this->_query("SELECT lastModified,expire FROM cache WHERE id='$id'");
+ if (!$res) {
+ return false;
+ }
+ $row = @sqlite_fetch_array($res, SQLITE_ASSOC);
+ return array(
+ 'tags' => $tags,
+ 'mtime' => $row['lastModified'],
+ 'expire' => $row['expire']
+ );
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ $sql = "SELECT expire FROM cache WHERE id='$id' AND (expire=0 OR expire>" . time() . ')';
+ $res = $this->_query($sql);
+ if (!$res) {
+ return false;
+ }
+ $expire = @sqlite_fetch_single($res);
+ $newExpire = $expire + $extraLifetime;
+ $res = $this->_query("UPDATE cache SET lastModified=" . time() . ", expire=$newExpire WHERE id='$id'");
+ if ($res) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => true,
+ 'tags' => true,
+ 'expired_read' => true,
+ 'priority' => false,
+ 'infinite_lifetime' => true,
+ 'get_list' => true
+ );
+ }
+
+ /**
+ * PUBLIC METHOD FOR UNIT TESTING ONLY !
+ *
+ * Force a cache record to expire
+ *
+ * @param string $id Cache id
+ */
+ public function ___expire($id)
+ {
+ $time = time() - 1;
+ $this->_query("UPDATE cache SET lastModified=$time, expire=$time WHERE id='$id'");
+ }
+
+ /**
+ * Return the connection resource
+ *
+ * If we are not connected, the connection is made
+ *
+ * @throws Zend_Cache_Exception
+ * @return resource Connection resource
+ */
+ private function _getConnection()
+ {
+ if (is_resource($this->_db)) {
+ return $this->_db;
+ } else {
+ $this->_db = @sqlite_open($this->_options['cache_db_complete_path']);
+ if (!(is_resource($this->_db))) {
+ Zend_Cache::throwException("Impossible to open " . $this->_options['cache_db_complete_path'] . " cache DB file");
+ }
+ return $this->_db;
+ }
+ }
+
+ /**
+ * Execute an SQL query silently
+ *
+ * @param string $query SQL query
+ * @return mixed|false query results
+ */
+ private function _query($query)
+ {
+ $db = $this->_getConnection();
+ if (is_resource($db)) {
+ $res = @sqlite_query($db, $query);
+ if ($res === false) {
+ return false;
+ } else {
+ return $res;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Deal with the automatic vacuum process
+ *
+ * @return void
+ */
+ private function _automaticVacuum()
+ {
+ if ($this->_options['automatic_vacuum_factor'] > 0) {
+ $rand = rand(1, $this->_options['automatic_vacuum_factor']);
+ if ($rand == 1) {
+ $this->_query('VACUUM');
+ }
+ }
+ }
+
+ /**
+ * Register a cache id with the given tag
+ *
+ * @param string $id Cache id
+ * @param string $tag Tag
+ * @return boolean True if no problem
+ */
+ private function _registerTag($id, $tag) {
+ $res = $this->_query("DELETE FROM TAG WHERE name='$tag' AND id='$id'");
+ $res = $this->_query("INSERT INTO tag (name, id) VALUES ('$tag', '$id')");
+ if (!$res) {
+ $this->_log("Zend_Cache_Backend_Sqlite::_registerTag() : impossible to register tag=$tag on id=$id");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Build the database structure
+ *
+ * @return false
+ */
+ private function _buildStructure()
+ {
+ $this->_query('DROP INDEX tag_id_index');
+ $this->_query('DROP INDEX tag_name_index');
+ $this->_query('DROP INDEX cache_id_expire_index');
+ $this->_query('DROP TABLE version');
+ $this->_query('DROP TABLE cache');
+ $this->_query('DROP TABLE tag');
+ $this->_query('CREATE TABLE version (num INTEGER PRIMARY KEY)');
+ $this->_query('CREATE TABLE cache (id TEXT PRIMARY KEY, content BLOB, lastModified INTEGER, expire INTEGER)');
+ $this->_query('CREATE TABLE tag (name TEXT, id TEXT)');
+ $this->_query('CREATE INDEX tag_id_index ON tag(id)');
+ $this->_query('CREATE INDEX tag_name_index ON tag(name)');
+ $this->_query('CREATE INDEX cache_id_expire_index ON cache(id, expire)');
+ $this->_query('INSERT INTO version (num) VALUES (1)');
+ }
+
+ /**
+ * Check if the database structure is ok (with the good version)
+ *
+ * @return boolean True if ok
+ */
+ private function _checkStructureVersion()
+ {
+ $result = $this->_query("SELECT num FROM version");
+ if (!$result) return false;
+ $row = @sqlite_fetch_array($result);
+ if (!$row) {
+ return false;
+ }
+ if (((int) $row['num']) != 1) {
+ // old cache structure
+ $this->_log('Zend_Cache_Backend_Sqlite::_checkStructureVersion() : old cache structure version detected => the cache is going to be dropped');
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @return boolean True if no problem
+ */
+ private function _clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ $res1 = $this->_query('DELETE FROM cache');
+ $res2 = $this->_query('DELETE FROM tag');
+ return $res1 && $res2;
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $mktime = time();
+ $res1 = $this->_query("DELETE FROM tag WHERE id IN (SELECT id FROM cache WHERE expire>0 AND expire<=$mktime)");
+ $res2 = $this->_query("DELETE FROM cache WHERE expire>0 AND expire<=$mktime");
+ return $res1 && $res2;
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ $ids = $this->getIdsMatchingTags($tags);
+ $result = true;
+ foreach ($ids as $id) {
+ $result = $this->remove($id) && $result;
+ }
+ return $result;
+ break;
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ $ids = $this->getIdsNotMatchingTags($tags);
+ $result = true;
+ foreach ($ids as $id) {
+ $result = $this->remove($id) && $result;
+ }
+ return $result;
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $ids = $this->getIdsMatchingAnyTags($tags);
+ $result = true;
+ foreach ($ids as $id) {
+ $result = $this->remove($id) && $result;
+ }
+ return $result;
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Check if the database structure is ok (with the good version), if no : build it
+ *
+ * @throws Zend_Cache_Exception
+ * @return boolean True if ok
+ */
+ private function _checkAndBuildStructure()
+ {
+ if (!($this->_structureChecked)) {
+ if (!$this->_checkStructureVersion()) {
+ $this->_buildStructure();
+ if (!$this->_checkStructureVersion()) {
+ Zend_Cache::throwException("Impossible to build cache structure in " . $this->_options['cache_db_complete_path']);
+ }
+ }
+ $this->_structureChecked = true;
+ }
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/Static.php b/library/vendor/Zend/Cache/Backend/Static.php
new file mode 100644
index 0000000..9203020
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Static.php
@@ -0,0 +1,577 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Static
+ extends Zend_Cache_Backend
+ implements Zend_Cache_Backend_Interface
+{
+ const INNER_CACHE_NAME = 'zend_cache_backend_static_tagcache';
+
+ /**
+ * Static backend options
+ * @var array
+ */
+ protected $_options = array(
+ 'public_dir' => null,
+ 'sub_dir' => 'html',
+ 'file_extension' => '.html',
+ 'index_filename' => 'index',
+ 'file_locking' => true,
+ 'cache_file_perm' => 0600,
+ 'cache_directory_perm' => 0700,
+ 'debug_header' => false,
+ 'tag_cache' => null,
+ 'disable_caching' => false
+ );
+
+ /**
+ * Cache for handling tags
+ * @var Zend_Cache_Core
+ */
+ protected $_tagCache = null;
+
+ /**
+ * Tagged items
+ * @var array
+ */
+ protected $_tagged = null;
+
+ /**
+ * Interceptor child method to handle the case where an Inner
+ * Cache object is being set since it's not supported by the
+ * standard backend interface
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Cache_Backend_Static
+ */
+ public function setOption($name, $value)
+ {
+ if ($name == 'tag_cache') {
+ $this->setInnerCache($value);
+ } else {
+ // See #ZF-12047 and #GH-91
+ if ($name == 'cache_file_umask') {
+ trigger_error(
+ "'cache_file_umask' is deprecated -> please use 'cache_file_perm' instead",
+ E_USER_NOTICE
+ );
+
+ $name = 'cache_file_perm';
+ }
+ if ($name == 'cache_directory_umask') {
+ trigger_error(
+ "'cache_directory_umask' is deprecated -> please use 'cache_directory_perm' instead",
+ E_USER_NOTICE
+ );
+
+ $name = 'cache_directory_perm';
+ }
+
+ parent::setOption($name, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve any option via interception of the parent's statically held
+ * options including the local option for a tag cache.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getOption($name)
+ {
+ $name = strtolower($name);
+
+ if ($name == 'tag_cache') {
+ return $this->getInnerCache();
+ }
+
+ return parent::getOption($name);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * Note : return value is always "string" (unserialization is done by the core not by the backend)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ if (($id = (string)$id) === '') {
+ $id = $this->_detectId();
+ } else {
+ $id = $this->_decodeId($id);
+ }
+ if (!$this->_verifyPath($id)) {
+ Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
+ }
+ if ($doNotTestCacheValidity) {
+ $this->_log("Zend_Cache_Backend_Static::load() : \$doNotTestCacheValidity=true is unsupported by the Static backend");
+ }
+
+ $fileName = basename($id);
+ if ($fileName === '') {
+ $fileName = $this->_options['index_filename'];
+ }
+ $pathName = $this->_options['public_dir'] . dirname($id);
+ $file = rtrim($pathName, '/') . '/' . $fileName . $this->_options['file_extension'];
+ if (file_exists($file)) {
+ $content = file_get_contents($file);
+ return $content;
+ }
+
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return bool
+ */
+ public function test($id)
+ {
+ $id = $this->_decodeId($id);
+ if (!$this->_verifyPath($id)) {
+ Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
+ }
+
+ $fileName = basename($id);
+ if ($fileName === '') {
+ $fileName = $this->_options['index_filename'];
+ }
+ if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
+ $this->_tagged = $tagged;
+ } elseif (!$this->_tagged) {
+ return false;
+ }
+ $pathName = $this->_options['public_dir'] . dirname($id);
+
+ // Switch extension if needed
+ if (isset($this->_tagged[$id])) {
+ $extension = $this->_tagged[$id]['extension'];
+ } else {
+ $extension = $this->_options['file_extension'];
+ }
+ $file = $pathName . '/' . $fileName . $extension;
+ if (file_exists($file)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ if ($this->_options['disable_caching']) {
+ return true;
+ }
+ $extension = null;
+ if ($this->_isSerialized($data)) {
+ $data = unserialize($data);
+ $extension = '.' . ltrim($data[1], '.');
+ $data = $data[0];
+ }
+
+ clearstatcache();
+ if (($id = (string)$id) === '') {
+ $id = $this->_detectId();
+ } else {
+ $id = $this->_decodeId($id);
+ }
+
+ $fileName = basename($id);
+ if ($fileName === '') {
+ $fileName = $this->_options['index_filename'];
+ }
+
+ $pathName = realpath($this->_options['public_dir']) . dirname($id);
+ $this->_createDirectoriesFor($pathName);
+
+ if ($id === null || strlen($id) == 0) {
+ $dataUnserialized = unserialize($data);
+ $data = $dataUnserialized['data'];
+ }
+ $ext = $this->_options['file_extension'];
+ if ($extension) $ext = $extension;
+ $file = rtrim($pathName, '/') . '/' . $fileName . $ext;
+ if ($this->_options['file_locking']) {
+ $result = file_put_contents($file, $data, LOCK_EX);
+ } else {
+ $result = file_put_contents($file, $data);
+ }
+ @chmod($file, $this->_octdec($this->_options['cache_file_perm']));
+
+ if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
+ $this->_tagged = $tagged;
+ } elseif ($this->_tagged === null) {
+ $this->_tagged = array();
+ }
+ if (!isset($this->_tagged[$id])) {
+ $this->_tagged[$id] = array();
+ }
+ if (!isset($this->_tagged[$id]['tags'])) {
+ $this->_tagged[$id]['tags'] = array();
+ }
+ $this->_tagged[$id]['tags'] = array_unique(array_merge($this->_tagged[$id]['tags'], $tags));
+ $this->_tagged[$id]['extension'] = $ext;
+ $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
+ return (bool) $result;
+ }
+
+ /**
+ * Recursively create the directories needed to write the static file
+ */
+ protected function _createDirectoriesFor($path)
+ {
+ if (!is_dir($path)) {
+ $oldUmask = umask(0);
+ if ( !@mkdir($path, $this->_octdec($this->_options['cache_directory_perm']), true)) {
+ $lastErr = error_get_last();
+ umask($oldUmask);
+ Zend_Cache::throwException("Can't create directory: {$lastErr['message']}");
+ }
+ umask($oldUmask);
+ }
+ }
+
+ /**
+ * Detect serialization of data (cannot predict since this is the only way
+ * to obey the interface yet pass in another parameter).
+ *
+ * In future, ZF 2.0, check if we can just avoid the interface restraints.
+ *
+ * This format is the only valid one possible for the class, so it's simple
+ * to just run a regular expression for the starting serialized format.
+ */
+ protected function _isSerialized($data)
+ {
+ return preg_match("/a:2:\{i:0;s:\d+:\"/", $data);
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ if (!$this->_verifyPath($id)) {
+ Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
+ }
+ $fileName = basename($id);
+ if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
+ $this->_tagged = $tagged;
+ } elseif (!$this->_tagged) {
+ return false;
+ }
+ if (isset($this->_tagged[$id])) {
+ $extension = $this->_tagged[$id]['extension'];
+ } else {
+ $extension = $this->_options['file_extension'];
+ }
+ if ($fileName === '') {
+ $fileName = $this->_options['index_filename'];
+ }
+ $pathName = $this->_options['public_dir'] . dirname($id);
+ $file = realpath($pathName) . '/' . $fileName . $extension;
+ if (!file_exists($file)) {
+ return false;
+ }
+ return unlink($file);
+ }
+
+ /**
+ * Remove a cache record recursively for the given directory matching a
+ * REQUEST_URI based relative path (deletes the actual file matching this
+ * in addition to the matching directory)
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function removeRecursively($id)
+ {
+ if (!$this->_verifyPath($id)) {
+ Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
+ }
+ $fileName = basename($id);
+ if ($fileName === '') {
+ $fileName = $this->_options['index_filename'];
+ }
+ $pathName = $this->_options['public_dir'] . dirname($id);
+ $file = $pathName . '/' . $fileName . $this->_options['file_extension'];
+ $directory = $pathName . '/' . $fileName;
+ if (file_exists($directory)) {
+ if (!is_writable($directory)) {
+ return false;
+ }
+ if (is_dir($directory)) {
+ foreach (new DirectoryIterator($directory) as $file) {
+ if (true === $file->isFile()) {
+ if (false === unlink($file->getPathName())) {
+ return false;
+ }
+ }
+ }
+ }
+ rmdir($directory);
+ }
+ if (file_exists($file)) {
+ if (!is_writable($file)) {
+ return false;
+ }
+ return unlink($file);
+ }
+ return true;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @return boolean true if no problem
+ * @throws Zend_Exception
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ $result = false;
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ if (empty($tags)) {
+ throw new Zend_Exception('Cannot use tag matching modes as no tags were defined');
+ }
+ if ($this->_tagged === null && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
+ $this->_tagged = $tagged;
+ } elseif (!$this->_tagged) {
+ return true;
+ }
+ foreach ($tags as $tag) {
+ $urls = array_keys($this->_tagged);
+ foreach ($urls as $url) {
+ if (isset($this->_tagged[$url]['tags']) && in_array($tag, $this->_tagged[$url]['tags'])) {
+ $this->remove($url);
+ unset($this->_tagged[$url]);
+ }
+ }
+ }
+ $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
+ $result = true;
+ break;
+ case Zend_Cache::CLEANING_MODE_ALL:
+ if ($this->_tagged === null) {
+ $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME);
+ $this->_tagged = $tagged;
+ }
+ if ($this->_tagged === null || empty($this->_tagged)) {
+ return true;
+ }
+ $urls = array_keys($this->_tagged);
+ foreach ($urls as $url) {
+ $this->remove($url);
+ unset($this->_tagged[$url]);
+ }
+ $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
+ $result = true;
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_Static : Selected Cleaning Mode Currently Unsupported By This Backend");
+ break;
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ if (empty($tags)) {
+ throw new Zend_Exception('Cannot use tag matching modes as no tags were defined');
+ }
+ if ($this->_tagged === null) {
+ $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME);
+ $this->_tagged = $tagged;
+ }
+ if ($this->_tagged === null || empty($this->_tagged)) {
+ return true;
+ }
+ $urls = array_keys($this->_tagged);
+ foreach ($urls as $url) {
+ $difference = array_diff($tags, $this->_tagged[$url]['tags']);
+ if (count($tags) == count($difference)) {
+ $this->remove($url);
+ unset($this->_tagged[$url]);
+ }
+ }
+ $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
+ $result = true;
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ return $result;
+ }
+
+ /**
+ * Set an Inner Cache, used here primarily to store Tags associated
+ * with caches created by this backend. Note: If Tags are lost, the cache
+ * should be completely cleaned as the mapping of tags to caches will
+ * have been irrevocably lost.
+ *
+ * @param Zend_Cache_Core
+ * @return void
+ */
+ public function setInnerCache(Zend_Cache_Core $cache)
+ {
+ $this->_tagCache = $cache;
+ $this->_options['tag_cache'] = $cache;
+ }
+
+ /**
+ * Get the Inner Cache if set
+ *
+ * @return Zend_Cache_Core
+ */
+ public function getInnerCache()
+ {
+ if ($this->_tagCache === null) {
+ Zend_Cache::throwException('An Inner Cache has not been set; use setInnerCache()');
+ }
+ return $this->_tagCache;
+ }
+
+ /**
+ * Verify path exists and is non-empty
+ *
+ * @param string $path
+ * @return bool
+ */
+ protected function _verifyPath($path)
+ {
+ $path = realpath($path);
+ $base = realpath($this->_options['public_dir']);
+ return strncmp($path, $base, strlen($base)) !== 0;
+ }
+
+ /**
+ * Determine the page to save from the request
+ *
+ * @return string
+ */
+ protected function _detectId()
+ {
+ return $_SERVER['REQUEST_URI'];
+ }
+
+ /**
+ * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...)
+ *
+ * Throw an exception if a problem is found
+ *
+ * @param string $string Cache id or tag
+ * @throws Zend_Cache_Exception
+ * @return void
+ * @deprecated Not usable until perhaps ZF 2.0
+ */
+ protected static function _validateIdOrTag($string)
+ {
+ if (!is_string($string)) {
+ Zend_Cache::throwException('Invalid id or tag : must be a string');
+ }
+
+ // Internal only checked in Frontend - not here!
+ if (substr($string, 0, 9) == 'internal-') {
+ return;
+ }
+
+ // Validation assumes no query string, fragments or scheme included - only the path
+ if (!preg_match(
+ '/^(?:\/(?:(?:%[[:xdigit:]]{2}|[A-Za-z0-9-_.!~*\'()\[\]:@&=+$,;])*)?)+$/',
+ $string
+ )
+ ) {
+ Zend_Cache::throwException("Invalid id or tag '$string' : must be a valid URL path");
+ }
+ }
+
+ /**
+ * Detect an octal string and return its octal value for file permission ops
+ * otherwise return the non-string (assumed octal or decimal int already)
+ *
+ * @param string $val The potential octal in need of conversion
+ * @return int
+ */
+ protected function _octdec($val)
+ {
+ if (is_string($val) && decoct(octdec($val)) == $val) {
+ return octdec($val);
+ }
+ return $val;
+ }
+
+ /**
+ * Decode a request URI from the provided ID
+ *
+ * @param string $id
+ * @return string
+ */
+ protected function _decodeId($id)
+ {
+ return pack('H*', $id);
+ }
+}
diff --git a/library/vendor/Zend/Cache/Backend/Test.php b/library/vendor/Zend/Cache/Backend/Test.php
new file mode 100644
index 0000000..c06f959
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Test.php
@@ -0,0 +1,414 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Test extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Available options
+ *
+ * @var array available options
+ */
+ protected $_options = array();
+
+ /**
+ * Frontend or Core directives
+ *
+ * @var array directives
+ */
+ protected $_directives = array();
+
+ /**
+ * Array to log actions
+ *
+ * @var array $_log
+ */
+ private $_log = array();
+
+ /**
+ * Current index for log array
+ *
+ * @var int $_index
+ */
+ private $_index = 0;
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @return void
+ */
+ public function __construct($options = array())
+ {
+ $this->_addLog('construct', array($options));
+ }
+
+ /**
+ * Set the frontend directives
+ *
+ * @param array $directives assoc of directives
+ * @return void
+ */
+ public function setDirectives($directives)
+ {
+ $this->_addLog('setDirectives', array($directives));
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * For this test backend only, if $id == 'false', then the method will return false
+ * if $id == 'serialized', the method will return a serialized array
+ * ('foo' else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string Cached datas (or false)
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $this->_addLog('get', array($id, $doNotTestCacheValidity));
+
+ if ( $id == 'false'
+ || $id == 'd8523b3ee441006261eeffa5c3d3a0a7'
+ || $id == 'e83249ea22178277d5befc2c5e2e9ace'
+ || $id == '40f649b94977c0a6e76902e2a0b43587'
+ || $id == '88161989b73a4cbfd0b701c446115a99'
+ || $id == '205fc79cba24f0f0018eb92c7c8b3ba4'
+ || $id == '170720e35f38150b811f68a937fb042d')
+ {
+ return false;
+ }
+ if ($id=='serialized') {
+ return serialize(array('foo'));
+ }
+ if ($id=='serialized2') {
+ return serialize(array('headers' => array(), 'data' => 'foo'));
+ }
+ if ( $id == '71769f39054f75894288e397df04e445' || $id == '615d222619fb20b527168340cebd0578'
+ || $id == '8a02d218a5165c467e7a5747cc6bd4b6' || $id == '648aca1366211d17cbf48e65dc570bee'
+ || $id == '4a923ef02d7f997ca14d56dfeae25ea7') {
+ return serialize(array('foo', 'bar'));
+ }
+ if ( $id == 'f53c7d912cc523d9a65834c8286eceb9') {
+ return serialize(array('foobar'));
+ }
+ return 'foo';
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * For this test backend only, if $id == 'false', then the method will return false
+ * (123456 else)
+ *
+ * @param string $id Cache id
+ * @return mixed|false false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $this->_addLog('test', array($id));
+ if ($id=='false') {
+ return false;
+ }
+ if (($id=='3c439c922209e2cb0b54d6deffccd75a')) {
+ return false;
+ }
+ return 123456;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * For this test backend only, if $id == 'false', then the method will return false
+ * (true else)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean True if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $this->_addLog('save', array($data, $id, $tags));
+ if (substr($id,-5)=='false') {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * For this test backend only, if $id == 'false', then the method will return false
+ * (true else)
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ $this->_addLog('remove', array($id));
+ if (substr($id,-5)=='false') {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * For this test backend only, if $mode == 'false', then the method will return false
+ * (true else)
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @return boolean True if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ $this->_addLog('clean', array($mode, $tags));
+ if ($mode=='false') {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Get the last log
+ *
+ * @return string The last log
+ */
+ public function getLastLog()
+ {
+ return $this->_log[$this->_index - 1];
+ }
+
+ /**
+ * Get the log index
+ *
+ * @return int Log index
+ */
+ public function getLogIndex()
+ {
+ return $this->_index;
+ }
+
+ /**
+ * Get the complete log array
+ *
+ * @return array Complete log array
+ */
+ public function getAllLogs()
+ {
+ return $this->_log;
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return true;
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ return array(
+ 'prefix_id1', 'prefix_id2'
+ );
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ return array(
+ 'tag1', 'tag2'
+ );
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ if ($tags == array('tag1', 'tag2')) {
+ return array('prefix_id1', 'prefix_id2');
+ }
+
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ if ($tags == array('tag3', 'tag4')) {
+ return array('prefix_id3', 'prefix_id4');
+ }
+
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ if ($tags == array('tag5', 'tag6')) {
+ return array('prefix_id5', 'prefix_id6');
+ }
+
+ return array();
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ return 50;
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ return false;
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ return true;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => true,
+ 'tags' => true,
+ 'expired_read' => false,
+ 'priority' => true,
+ 'infinite_lifetime' => true,
+ 'get_list' => true
+ );
+ }
+
+ /**
+ * Add an event to the log array
+ *
+ * @param string $methodName MethodName
+ * @param array $args Arguments
+ * @return void
+ */
+ private function _addLog($methodName, $args)
+ {
+ $this->_log[$this->_index] = array(
+ 'methodName' => $methodName,
+ 'args' => $args
+ );
+ $this->_index = $this->_index + 1;
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/TwoLevels.php b/library/vendor/Zend/Cache/Backend/TwoLevels.php
new file mode 100644
index 0000000..1e6792b
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/TwoLevels.php
@@ -0,0 +1,546 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_ExtendedInterface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Cache_Backend_TwoLevels extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Available options
+ *
+ * =====> (string) slow_backend :
+ * - Slow backend name
+ * - Must implement the Zend_Cache_Backend_ExtendedInterface
+ * - Should provide a big storage
+ *
+ * =====> (string) fast_backend :
+ * - Flow backend name
+ * - Must implement the Zend_Cache_Backend_ExtendedInterface
+ * - Must be much faster than slow_backend
+ *
+ * =====> (array) slow_backend_options :
+ * - Slow backend options (see corresponding backend)
+ *
+ * =====> (array) fast_backend_options :
+ * - Fast backend options (see corresponding backend)
+ *
+ * =====> (int) stats_update_factor :
+ * - Disable / Tune the computation of the fast backend filling percentage
+ * - When saving a record into cache :
+ * 1 => systematic computation of the fast backend filling percentage
+ * x (integer) > 1 => computation of the fast backend filling percentage randomly 1 times on x cache write
+ *
+ * =====> (boolean) slow_backend_custom_naming :
+ * =====> (boolean) fast_backend_custom_naming :
+ * =====> (boolean) slow_backend_autoload :
+ * =====> (boolean) fast_backend_autoload :
+ * - See Zend_Cache::factory() method
+ *
+ * =====> (boolean) auto_fill_fast_cache
+ * - If true, automatically fill the fast cache when a cache record was not found in fast cache, but did
+ * exist in slow cache. This can be usefull when a non-persistent cache like APC or Memcached got
+ * purged for whatever reason.
+ *
+ * =====> (boolean) auto_refresh_fast_cache
+ * - If true, auto refresh the fast cache when a cache record is hit
+ *
+ * @var array available options
+ */
+ protected $_options = array(
+ 'slow_backend' => 'File',
+ 'fast_backend' => 'Apc',
+ 'slow_backend_options' => array(),
+ 'fast_backend_options' => array(),
+ 'stats_update_factor' => 10,
+ 'slow_backend_custom_naming' => false,
+ 'fast_backend_custom_naming' => false,
+ 'slow_backend_autoload' => false,
+ 'fast_backend_autoload' => false,
+ 'auto_fill_fast_cache' => true,
+ 'auto_refresh_fast_cache' => true
+ );
+
+ /**
+ * Slow Backend
+ *
+ * @var Zend_Cache_Backend_ExtendedInterface
+ */
+ protected $_slowBackend;
+
+ /**
+ * Fast Backend
+ *
+ * @var Zend_Cache_Backend_ExtendedInterface
+ */
+ protected $_fastBackend;
+
+ /**
+ * Cache for the fast backend filling percentage
+ *
+ * @var int
+ */
+ protected $_fastBackendFillingPercentage = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ parent::__construct($options);
+
+ if ($this->_options['slow_backend'] === null) {
+ Zend_Cache::throwException('slow_backend option has to set');
+ } elseif ($this->_options['slow_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) {
+ $this->_slowBackend = $this->_options['slow_backend'];
+ } else {
+ $this->_slowBackend = Zend_Cache::_makeBackend(
+ $this->_options['slow_backend'],
+ $this->_options['slow_backend_options'],
+ $this->_options['slow_backend_custom_naming'],
+ $this->_options['slow_backend_autoload']
+ );
+ if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_slowBackend))) {
+ Zend_Cache::throwException('slow_backend must implement the Zend_Cache_Backend_ExtendedInterface interface');
+ }
+ }
+
+ if ($this->_options['fast_backend'] === null) {
+ Zend_Cache::throwException('fast_backend option has to set');
+ } elseif ($this->_options['fast_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) {
+ $this->_fastBackend = $this->_options['fast_backend'];
+ } else {
+ $this->_fastBackend = Zend_Cache::_makeBackend(
+ $this->_options['fast_backend'],
+ $this->_options['fast_backend_options'],
+ $this->_options['fast_backend_custom_naming'],
+ $this->_options['fast_backend_autoload']
+ );
+ if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_fastBackend))) {
+ Zend_Cache::throwException('fast_backend must implement the Zend_Cache_Backend_ExtendedInterface interface');
+ }
+ }
+
+ $this->_slowBackend->setDirectives($this->_directives);
+ $this->_fastBackend->setDirectives($this->_directives);
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $fastTest = $this->_fastBackend->test($id);
+ if ($fastTest) {
+ return $fastTest;
+ } else {
+ return $this->_slowBackend->test($id);
+ }
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Datas to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false, $priority = 8)
+ {
+ $usage = $this->_getFastFillingPercentage('saving');
+ $boolFast = true;
+ $lifetime = $this->getLifetime($specificLifetime);
+ $preparedData = $this->_prepareData($data, $lifetime, $priority);
+ if (($priority > 0) && (10 * $priority >= $usage)) {
+ $fastLifetime = $this->_getFastLifetime($lifetime, $priority);
+ $boolFast = $this->_fastBackend->save($preparedData, $id, array(), $fastLifetime);
+ $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime);
+ } else {
+ $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime);
+ if ($boolSlow === true) {
+ $boolFast = $this->_fastBackend->remove($id);
+ if (!$boolFast && !$this->_fastBackend->test($id)) {
+ // some backends return false on remove() even if the key never existed. (and it won't if fast is full)
+ // all we care about is that the key doesn't exist now
+ $boolFast = true;
+ }
+ }
+ }
+
+ return ($boolFast && $boolSlow);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * Note : return value is always "string" (unserialization is done by the core not by the backend)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string|false cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $resultFast = $this->_fastBackend->load($id, $doNotTestCacheValidity);
+ if ($resultFast === false) {
+ $resultSlow = $this->_slowBackend->load($id, $doNotTestCacheValidity);
+ if ($resultSlow === false) {
+ // there is no cache at all for this id
+ return false;
+ }
+ }
+ $array = $resultFast !== false ? unserialize($resultFast) : unserialize($resultSlow);
+
+ //In case no cache entry was found in the FastCache and auto-filling is enabled, copy data to FastCache
+ if ($resultFast === false && $this->_options['auto_fill_fast_cache']) {
+ $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']);
+ $this->_fastBackend->save($preparedData, $id, array(), $array['lifetime']);
+ }
+ // maybe, we have to refresh the fast cache ?
+ elseif ($this->_options['auto_refresh_fast_cache']) {
+ if ($array['priority'] == 10) {
+ // no need to refresh the fast cache with priority = 10
+ return $array['data'];
+ }
+ $newFastLifetime = $this->_getFastLifetime($array['lifetime'], $array['priority'], time() - $array['expire']);
+ // we have the time to refresh the fast cache
+ $usage = $this->_getFastFillingPercentage('loading');
+ if (($array['priority'] > 0) && (10 * $array['priority'] >= $usage)) {
+ // we can refresh the fast cache
+ $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']);
+ $this->_fastBackend->save($preparedData, $id, array(), $newFastLifetime);
+ }
+ }
+ return $array['data'];
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ $boolFast = $this->_fastBackend->remove($id);
+ $boolSlow = $this->_slowBackend->remove($id);
+ return $boolFast && $boolSlow;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ $boolFast = $this->_fastBackend->clean(Zend_Cache::CLEANING_MODE_ALL);
+ $boolSlow = $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_ALL);
+ return $boolFast && $boolSlow;
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ return $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_OLD);
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ $ids = $this->_slowBackend->getIdsMatchingTags($tags);
+ $res = true;
+ foreach ($ids as $id) {
+ $bool = $this->remove($id);
+ $res = $res && $bool;
+ }
+ return $res;
+ break;
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ $ids = $this->_slowBackend->getIdsNotMatchingTags($tags);
+ $res = true;
+ foreach ($ids as $id) {
+ $bool = $this->remove($id);
+ $res = $res && $bool;
+ }
+ return $res;
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $ids = $this->_slowBackend->getIdsMatchingAnyTags($tags);
+ $res = true;
+ foreach ($ids as $id) {
+ $bool = $this->remove($id);
+ $res = $res && $bool;
+ }
+ return $res;
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ return $this->_slowBackend->getIds();
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ return $this->_slowBackend->getTags();
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ return $this->_slowBackend->getIdsMatchingTags($tags);
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ return $this->_slowBackend->getIdsNotMatchingTags($tags);
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ return $this->_slowBackend->getIdsMatchingAnyTags($tags);
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ return $this->_slowBackend->getFillingPercentage();
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ return $this->_slowBackend->getMetadatas($id);
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ return $this->_slowBackend->touch($id, $extraLifetime);
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ $slowBackendCapabilities = $this->_slowBackend->getCapabilities();
+ return array(
+ 'automatic_cleaning' => $slowBackendCapabilities['automatic_cleaning'],
+ 'tags' => $slowBackendCapabilities['tags'],
+ 'expired_read' => $slowBackendCapabilities['expired_read'],
+ 'priority' => $slowBackendCapabilities['priority'],
+ 'infinite_lifetime' => $slowBackendCapabilities['infinite_lifetime'],
+ 'get_list' => $slowBackendCapabilities['get_list']
+ );
+ }
+
+ /**
+ * Prepare a serialized array to store datas and metadatas informations
+ *
+ * @param string $data data to store
+ * @param int $lifetime original lifetime
+ * @param int $priority priority
+ * @return string serialize array to store into cache
+ */
+ private function _prepareData($data, $lifetime, $priority)
+ {
+ $lt = $lifetime;
+ if ($lt === null) {
+ $lt = 9999999999;
+ }
+ return serialize(array(
+ 'data' => $data,
+ 'lifetime' => $lifetime,
+ 'expire' => time() + $lt,
+ 'priority' => $priority
+ ));
+ }
+
+ /**
+ * Compute and return the lifetime for the fast backend
+ *
+ * @param int $lifetime original lifetime
+ * @param int $priority priority
+ * @param int $maxLifetime maximum lifetime
+ * @return int lifetime for the fast backend
+ */
+ private function _getFastLifetime($lifetime, $priority, $maxLifetime = null)
+ {
+ if ($lifetime <= 0) {
+ // if no lifetime, we have an infinite lifetime
+ // we need to use arbitrary lifetimes
+ $fastLifetime = (int) (2592000 / (11 - $priority));
+ } else {
+ // prevent computed infinite lifetime (0) by ceil
+ $fastLifetime = (int) ceil($lifetime / (11 - $priority));
+ }
+
+ if ($maxLifetime >= 0 && $fastLifetime > $maxLifetime) {
+ return $maxLifetime;
+ }
+
+ return $fastLifetime;
+ }
+
+ /**
+ * PUBLIC METHOD FOR UNIT TESTING ONLY !
+ *
+ * Force a cache record to expire
+ *
+ * @param string $id cache id
+ */
+ public function ___expire($id)
+ {
+ $this->_fastBackend->remove($id);
+ $this->_slowBackend->___expire($id);
+ }
+
+ private function _getFastFillingPercentage($mode)
+ {
+
+ if ($mode == 'saving') {
+ // mode saving
+ if ($this->_fastBackendFillingPercentage === null) {
+ $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+ } else {
+ $rand = rand(1, $this->_options['stats_update_factor']);
+ if ($rand == 1) {
+ // we force a refresh
+ $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+ }
+ }
+ } else {
+ // mode loading
+ // we compute the percentage only if it's not available in cache
+ if ($this->_fastBackendFillingPercentage === null) {
+ $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
+ }
+ }
+ return $this->_fastBackendFillingPercentage;
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/WinCache.php b/library/vendor/Zend/Cache/Backend/WinCache.php
new file mode 100644
index 0000000..c922280
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/WinCache.php
@@ -0,0 +1,347 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_WinCache extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
+{
+ /**
+ * Log message
+ */
+ const TAGS_UNSUPPORTED_BY_CLEAN_OF_WINCACHE_BACKEND = 'Zend_Cache_Backend_WinCache::clean() : tags are unsupported by the WinCache backend';
+ const TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND = 'Zend_Cache_Backend_WinCache::save() : tags are unsupported by the WinCache backend';
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ if (!extension_loaded('wincache')) {
+ Zend_Cache::throwException('The wincache extension must be loaded for using this backend !');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * WARNING $doNotTestCacheValidity=true is unsupported by the WinCache backend
+ *
+ * @param string $id cache id
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string cached datas (or false)
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $tmp = wincache_ucache_get($id);
+ if (is_array($tmp)) {
+ return $tmp[0];
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $tmp = wincache_ucache_get($id);
+ if (is_array($tmp)) {
+ return $tmp[1];
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data datas to cache
+ * @param string $id cache id
+ * @param array $tags array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $lifetime = $this->getLifetime($specificLifetime);
+ $result = wincache_ucache_set($id, array($data, time(), $lifetime), $lifetime);
+ if (count($tags) > 0) {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND);
+ }
+ return $result;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id cache id
+ * @return boolean true if no problem
+ */
+ public function remove($id)
+ {
+ return wincache_ucache_delete($id);
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => unsupported
+ * 'matchingTag' => unsupported
+ * 'notMatchingTag' => unsupported
+ * 'matchingAnyTag' => unsupported
+ *
+ * @param string $mode clean mode
+ * @param array $tags array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ return wincache_ucache_clear();
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_WinCache::clean() : CLEANING_MODE_OLD is unsupported by the WinCache backend");
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_WINCACHE_BACKEND);
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * DEPRECATED : use getCapabilities() instead
+ *
+ * @deprecated
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return false;
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @throws Zend_Cache_Exception
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ $mem = wincache_ucache_meminfo();
+ $memSize = $mem['memory_total'];
+ $memUsed = $memSize - $mem['memory_free'];
+ if ($memSize == 0) {
+ Zend_Cache::throwException('can\'t get WinCache memory size');
+ }
+ if ($memUsed > $memSize) {
+ return 100;
+ }
+ return ((int) (100. * ($memUsed / $memSize)));
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of any matching cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_WINCACHE_BACKEND);
+ return array();
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ $res = array();
+ $array = wincache_ucache_info();
+ $records = $array['ucache_entries'];
+ foreach ($records as $record) {
+ $res[] = $record['key_name'];
+ }
+ return $res;
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array must include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ $tmp = wincache_ucache_get($id);
+ if (is_array($tmp)) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ if (!isset($tmp[2])) {
+ return false;
+ }
+ $lifetime = $tmp[2];
+ return array(
+ 'expire' => $mtime + $lifetime,
+ 'tags' => array(),
+ 'mtime' => $mtime
+ );
+ }
+ return false;
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ $tmp = wincache_ucache_get($id);
+ if (is_array($tmp)) {
+ $data = $tmp[0];
+ $mtime = $tmp[1];
+ if (!isset($tmp[2])) {
+ return false;
+ }
+ $lifetime = $tmp[2];
+ $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
+ if ($newLifetime <=0) {
+ return false;
+ }
+ return wincache_ucache_set($id, array($data, time(), $newLifetime), $newLifetime);
+ }
+ return false;
+ }
+
+ /**
+ * Return an associative array of capabilities (booleans) of the backend
+ *
+ * The array must include these keys :
+ * - automatic_cleaning (is automating cleaning necessary)
+ * - tags (are tags supported)
+ * - expired_read (is it possible to read expired cache records
+ * (for doNotTestCacheValidity option for example))
+ * - priority does the backend deal with priority when saving
+ * - infinite_lifetime (is infinite lifetime can work with this backend)
+ * - get_list (is it possible to get the list of cache ids and the complete list of tags)
+ *
+ * @return array associative of with capabilities
+ */
+ public function getCapabilities()
+ {
+ return array(
+ 'automatic_cleaning' => false,
+ 'tags' => false,
+ 'expired_read' => false,
+ 'priority' => false,
+ 'infinite_lifetime' => false,
+ 'get_list' => true
+ );
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/Xcache.php b/library/vendor/Zend/Cache/Backend/Xcache.php
new file mode 100644
index 0000000..d78d52d
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/Xcache.php
@@ -0,0 +1,219 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_Xcache extends Zend_Cache_Backend implements Zend_Cache_Backend_Interface
+{
+
+ /**
+ * Log message
+ */
+ const TAGS_UNSUPPORTED_BY_CLEAN_OF_XCACHE_BACKEND = 'Zend_Cache_Backend_Xcache::clean() : tags are unsupported by the Xcache backend';
+ const TAGS_UNSUPPORTED_BY_SAVE_OF_XCACHE_BACKEND = 'Zend_Cache_Backend_Xcache::save() : tags are unsupported by the Xcache backend';
+
+ /**
+ * Available options
+ *
+ * =====> (string) user :
+ * xcache.admin.user (necessary for the clean() method)
+ *
+ * =====> (string) password :
+ * xcache.admin.pass (clear, not MD5) (necessary for the clean() method)
+ *
+ * @var array available options
+ */
+ protected $_options = array(
+ 'user' => null,
+ 'password' => null
+ );
+
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ if (!extension_loaded('xcache')) {
+ Zend_Cache::throwException('The xcache extension must be loaded for using this backend !');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * WARNING $doNotTestCacheValidity=true is unsupported by the Xcache backend
+ *
+ * @param string $id cache id
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string cached datas (or false)
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ if ($doNotTestCacheValidity) {
+ $this->_log("Zend_Cache_Backend_Xcache::load() : \$doNotTestCacheValidity=true is unsupported by the Xcache backend");
+ }
+ $tmp = xcache_get($id);
+ if (is_array($tmp)) {
+ return $tmp[0];
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ if (xcache_isset($id)) {
+ $tmp = xcache_get($id);
+ if (is_array($tmp)) {
+ return $tmp[1];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data datas to cache
+ * @param string $id cache id
+ * @param array $tags array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $lifetime = $this->getLifetime($specificLifetime);
+ $result = xcache_set($id, array($data, time()), $lifetime);
+ if (count($tags) > 0) {
+ $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_XCACHE_BACKEND);
+ }
+ return $result;
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id cache id
+ * @return boolean true if no problem
+ */
+ public function remove($id)
+ {
+ return xcache_unset($id);
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => unsupported
+ * 'matchingTag' => unsupported
+ * 'notMatchingTag' => unsupported
+ * 'matchingAnyTag' => unsupported
+ *
+ * @param string $mode clean mode
+ * @param array $tags array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ // Necessary because xcache_clear_cache() need basic authentification
+ $backup = array();
+ if (isset($_SERVER['PHP_AUTH_USER'])) {
+ $backup['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER'];
+ }
+ if (isset($_SERVER['PHP_AUTH_PW'])) {
+ $backup['PHP_AUTH_PW'] = $_SERVER['PHP_AUTH_PW'];
+ }
+ if ($this->_options['user']) {
+ $_SERVER['PHP_AUTH_USER'] = $this->_options['user'];
+ }
+ if ($this->_options['password']) {
+ $_SERVER['PHP_AUTH_PW'] = $this->_options['password'];
+ }
+
+ $cnt = xcache_count(XC_TYPE_VAR);
+ for ($i=0; $i < $cnt; $i++) {
+ xcache_clear_cache(XC_TYPE_VAR, $i);
+ }
+
+ if (isset($backup['PHP_AUTH_USER'])) {
+ $_SERVER['PHP_AUTH_USER'] = $backup['PHP_AUTH_USER'];
+ $_SERVER['PHP_AUTH_PW'] = $backup['PHP_AUTH_PW'];
+ }
+ return true;
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_Xcache::clean() : CLEANING_MODE_OLD is unsupported by the Xcache backend");
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_XCACHE_BACKEND);
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Return true if the automatic cleaning is available for the backend
+ *
+ * @return boolean
+ */
+ public function isAutomaticCleaningAvailable()
+ {
+ return false;
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/ZendPlatform.php b/library/vendor/Zend/Cache/Backend/ZendPlatform.php
new file mode 100644
index 0000000..8e6f460
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/ZendPlatform.php
@@ -0,0 +1,315 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+/**
+ * @see Zend_Cache_Backend_Interface
+ */
+
+
+/**
+ * Impementation of Zend Cache Backend using the Zend Platform (Output Content Caching)
+ *
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_ZendPlatform extends Zend_Cache_Backend implements Zend_Cache_Backend_Interface
+{
+ /**
+ * internal ZP prefix
+ */
+ const TAGS_PREFIX = "internal_ZPtag:";
+
+ /**
+ * Constructor
+ * Validate that the Zend Platform is loaded and licensed
+ *
+ * @param array $options Associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ if (!function_exists('accelerator_license_info')) {
+ Zend_Cache::throwException('The Zend Platform extension must be loaded for using this backend !');
+ }
+ if (!function_exists('accelerator_get_configuration')) {
+ $licenseInfo = accelerator_license_info();
+ Zend_Cache::throwException('The Zend Platform extension is not loaded correctly: '.$licenseInfo['failure_reason']);
+ }
+ $accConf = accelerator_get_configuration();
+ if (@!$accConf['output_cache_licensed']) {
+ Zend_Cache::throwException('The Zend Platform extension does not have the proper license to use content caching features');
+ }
+ if (@!$accConf['output_cache_enabled']) {
+ Zend_Cache::throwException('The Zend Platform content caching feature must be enabled for using this backend, set the \'zend_accelerator.output_cache_enabled\' directive to On !');
+ }
+ if (!is_writable($accConf['output_cache_dir'])) {
+ Zend_Cache::throwException('The cache copies directory \''. ini_get('zend_accelerator.output_cache_dir') .'\' must be writable !');
+ }
+ parent:: __construct($options);
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @return string Cached data (or false)
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ // doNotTestCacheValidity implemented by giving zero lifetime to the cache
+ if ($doNotTestCacheValidity) {
+ $lifetime = 0;
+ } else {
+ $lifetime = $this->_directives['lifetime'];
+ }
+ $res = output_cache_get($id, $lifetime);
+ if($res) {
+ return $res[0];
+ } else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id Cache id
+ * @return mixed|false false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ */
+ public function test($id)
+ {
+ $result = output_cache_get($id, $this->_directives['lifetime']);
+ if ($result) {
+ return $result[1];
+ }
+ return false;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data Data to cache
+ * @param string $id Cache id
+ * @param array $tags Array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ if (!($specificLifetime === false)) {
+ $this->_log("Zend_Cache_Backend_ZendPlatform::save() : non false specifc lifetime is unsuported for this backend");
+ }
+
+ $lifetime = $this->_directives['lifetime'];
+ $result1 = output_cache_put($id, array($data, time()));
+ $result2 = (count($tags) == 0);
+
+ foreach ($tags as $tag) {
+ $tagid = self::TAGS_PREFIX.$tag;
+ $old_tags = output_cache_get($tagid, $lifetime);
+ if ($old_tags === false) {
+ $old_tags = array();
+ }
+ $old_tags[$id] = $id;
+ output_cache_remove_key($tagid);
+ $result2 = output_cache_put($tagid, $old_tags);
+ }
+
+ return $result1 && $result2;
+ }
+
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id Cache id
+ * @return boolean True if no problem
+ */
+ public function remove($id)
+ {
+ return output_cache_remove_key($id);
+ }
+
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
+ * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
+ * This mode is not supported in this backend
+ * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => unsupported
+ * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode Clean mode
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $cache_dir = ini_get('zend_accelerator.output_cache_dir');
+ if (!$cache_dir) {
+ return false;
+ }
+ $cache_dir .= '/.php_cache_api/';
+ return $this->_clean($cache_dir, $mode);
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ $idlist = null;
+ foreach ($tags as $tag) {
+ $next_idlist = output_cache_get(self::TAGS_PREFIX.$tag, $this->_directives['lifetime']);
+ if ($idlist) {
+ $idlist = array_intersect_assoc($idlist, $next_idlist);
+ } else {
+ $idlist = $next_idlist;
+ }
+ if (count($idlist) == 0) {
+ // if ID list is already empty - we may skip checking other IDs
+ $idlist = null;
+ break;
+ }
+ }
+ if ($idlist) {
+ foreach ($idlist as $id) {
+ output_cache_remove_key($id);
+ }
+ }
+ return true;
+ break;
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ $this->_log("Zend_Cache_Backend_ZendPlatform::clean() : CLEANING_MODE_NOT_MATCHING_TAG is not supported by the Zend Platform backend");
+ return false;
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $idlist = null;
+ foreach ($tags as $tag) {
+ $next_idlist = output_cache_get(self::TAGS_PREFIX.$tag, $this->_directives['lifetime']);
+ if ($idlist) {
+ $idlist = array_merge_recursive($idlist, $next_idlist);
+ } else {
+ $idlist = $next_idlist;
+ }
+ if (count($idlist) == 0) {
+ // if ID list is already empty - we may skip checking other IDs
+ $idlist = null;
+ break;
+ }
+ }
+ if ($idlist) {
+ foreach ($idlist as $id) {
+ output_cache_remove_key($id);
+ }
+ }
+ return true;
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+
+ /**
+ * Clean a directory and recursivly go over it's subdirectories
+ *
+ * Remove all the cached files that need to be cleaned (according to mode and files mtime)
+ *
+ * @param string $dir Path of directory ot clean
+ * @param string $mode The same parameter as in Zend_Cache_Backend_ZendPlatform::clean()
+ * @return boolean True if ok
+ */
+ private function _clean($dir, $mode)
+ {
+ $d = @dir($dir);
+ if (!$d) {
+ return false;
+ }
+ $result = true;
+ while (false !== ($file = $d->read())) {
+ if ($file == '.' || $file == '..') {
+ continue;
+ }
+ $file = $d->path . $file;
+ if (is_dir($file)) {
+ $result = ($this->_clean($file .'/', $mode)) && ($result);
+ } else {
+ if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
+ $result = ($this->_remove($file)) && ($result);
+ } else if ($mode == Zend_Cache::CLEANING_MODE_OLD) {
+ // Files older than lifetime get deleted from cache
+ if ($this->_directives['lifetime'] !== null) {
+ if ((time() - @filemtime($file)) > $this->_directives['lifetime']) {
+ $result = ($this->_remove($file)) && ($result);
+ }
+ }
+ }
+ }
+ }
+ $d->close();
+ return $result;
+ }
+
+ /**
+ * Remove a file
+ *
+ * If we can't remove the file (because of locks or any problem), we will touch
+ * the file to invalidate it
+ *
+ * @param string $file Complete file path
+ * @return boolean True if ok
+ */
+ private function _remove($file)
+ {
+ if (!@unlink($file)) {
+ # If we can't remove the file (because of locks or any problem), we will touch
+ # the file to invalidate it
+ $this->_log("Zend_Cache_Backend_ZendPlatform::_remove() : we can't remove $file => we are going to try to invalidate it");
+ if ($this->_directives['lifetime'] === null) {
+ return false;
+ }
+ if (!file_exists($file)) {
+ return false;
+ }
+ return @touch($file, time() - 2*abs($this->_directives['lifetime']));
+ }
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Backend/ZendServer.php b/library/vendor/Zend/Cache/Backend/ZendServer.php
new file mode 100644
index 0000000..6243d86
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/ZendServer.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** @see Zend_Cache_Backend_Interface */
+
+/** @see Zend_Cache_Backend */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Cache_Backend_ZendServer extends Zend_Cache_Backend implements Zend_Cache_Backend_Interface
+{
+ /**
+ * Available options
+ *
+ * =====> (string) namespace :
+ * Namespace to be used for chaching operations
+ *
+ * @var array available options
+ */
+ protected $_options = array(
+ 'namespace' => 'zendframework'
+ );
+
+ /**
+ * Store data
+ *
+ * @param mixed $data Object to store
+ * @param string $id Cache id
+ * @param int $timeToLive Time to live in seconds
+ * @throws Zend_Cache_Exception
+ */
+ abstract protected function _store($data, $id, $timeToLive);
+
+ /**
+ * Fetch data
+ *
+ * @param string $id Cache id
+ * @throws Zend_Cache_Exception
+ */
+ abstract protected function _fetch($id);
+
+ /**
+ * Unset data
+ *
+ * @param string $id Cache id
+ */
+ abstract protected function _unset($id);
+
+ /**
+ * Clear cache
+ */
+ abstract protected function _clear();
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id cache id
+ * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
+ * @return string cached datas (or false)
+ */
+ public function load($id, $doNotTestCacheValidity = false)
+ {
+ $tmp = $this->_fetch($id);
+ if ($tmp !== null) {
+ return $tmp;
+ }
+ return false;
+ }
+
+ /**
+ * Test if a cache is available or not (for the given id)
+ *
+ * @param string $id cache id
+ * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
+ * @throws Zend_Cache_Exception
+ */
+ public function test($id)
+ {
+ $tmp = $this->_fetch('internal-metadatas---' . $id);
+ if ($tmp !== false) {
+ if (!is_array($tmp) || !isset($tmp['mtime'])) {
+ Zend_Cache::throwException('Cache metadata for \'' . $id . '\' id is corrupted' );
+ }
+ return $tmp['mtime'];
+ }
+ return false;
+ }
+
+ /**
+ * Compute & return the expire time
+ *
+ * @return int expire time (unix timestamp)
+ */
+ private function _expireTime($lifetime)
+ {
+ if ($lifetime === null) {
+ return 9999999999;
+ }
+ return time() + $lifetime;
+ }
+
+ /**
+ * Save some string datas into a cache record
+ *
+ * Note : $data is always "string" (serialization is done by the
+ * core not by the backend)
+ *
+ * @param string $data datas to cache
+ * @param string $id cache id
+ * @param array $tags array of strings, the cache record will be tagged by each string entry
+ * @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @return boolean true if no problem
+ */
+ public function save($data, $id, $tags = array(), $specificLifetime = false)
+ {
+ $lifetime = $this->getLifetime($specificLifetime);
+ $metadatas = array(
+ 'mtime' => time(),
+ 'expire' => $this->_expireTime($lifetime),
+ );
+
+ if (count($tags) > 0) {
+ $this->_log('Zend_Cache_Backend_ZendServer::save() : tags are unsupported by the ZendServer backends');
+ }
+
+ return $this->_store($data, $id, $lifetime) &&
+ $this->_store($metadatas, 'internal-metadatas---' . $id, $lifetime);
+ }
+
+ /**
+ * Remove a cache record
+ *
+ * @param string $id cache id
+ * @return boolean true if no problem
+ */
+ public function remove($id)
+ {
+ $result1 = $this->_unset($id);
+ $result2 = $this->_unset('internal-metadatas---' . $id);
+
+ return $result1 && $result2;
+ }
+
+ /**
+ * Clean some cache records
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => unsupported
+ * 'matchingTag' => unsupported
+ * 'notMatchingTag' => unsupported
+ * 'matchingAnyTag' => unsupported
+ *
+ * @param string $mode clean mode
+ * @param array $tags array of tags
+ * @throws Zend_Cache_Exception
+ * @return boolean true if no problem
+ */
+ public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
+ {
+ switch ($mode) {
+ case Zend_Cache::CLEANING_MODE_ALL:
+ $this->_clear();
+ return true;
+ break;
+ case Zend_Cache::CLEANING_MODE_OLD:
+ $this->_log("Zend_Cache_Backend_ZendServer::clean() : CLEANING_MODE_OLD is unsupported by the Zend Server backends.");
+ break;
+ case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
+ case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
+ $this->_clear();
+ $this->_log('Zend_Cache_Backend_ZendServer::clean() : tags are unsupported by the Zend Server backends.');
+ break;
+ default:
+ Zend_Cache::throwException('Invalid mode for clean() method');
+ break;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Cache/Backend/ZendServer/Disk.php b/library/vendor/Zend/Cache/Backend/ZendServer/Disk.php
new file mode 100644
index 0000000..09cd126
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/ZendServer/Disk.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** @see Zend_Cache_Backend_Interface */
+
+/** @see Zend_Cache_Backend_ZendServer */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_ZendServer_Disk extends Zend_Cache_Backend_ZendServer implements Zend_Cache_Backend_Interface
+{
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ */
+ public function __construct(array $options = array())
+ {
+ if (!function_exists('zend_disk_cache_store')) {
+ Zend_Cache::throwException('Zend_Cache_ZendServer_Disk backend has to be used within Zend Server environment.');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Store data
+ *
+ * @param mixed $data Object to store
+ * @param string $id Cache id
+ * @param int $timeToLive Time to live in seconds
+ * @return boolean true if no problem
+ */
+ protected function _store($data, $id, $timeToLive)
+ {
+ if (zend_disk_cache_store($this->_options['namespace'] . '::' . $id,
+ $data,
+ $timeToLive) === false) {
+ $this->_log('Store operation failed.');
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Fetch data
+ *
+ * @param string $id Cache id
+ * @return mixed|null
+ */
+ protected function _fetch($id)
+ {
+ return zend_disk_cache_fetch($this->_options['namespace'] . '::' . $id);
+ }
+
+ /**
+ * Unset data
+ *
+ * @param string $id Cache id
+ * @return boolean true if no problem
+ */
+ protected function _unset($id)
+ {
+ return zend_disk_cache_delete($this->_options['namespace'] . '::' . $id);
+ }
+
+ /**
+ * Clear cache
+ */
+ protected function _clear()
+ {
+ zend_disk_cache_clear($this->_options['namespace']);
+ }
+}
diff --git a/library/vendor/Zend/Cache/Backend/ZendServer/ShMem.php b/library/vendor/Zend/Cache/Backend/ZendServer/ShMem.php
new file mode 100644
index 0000000..5fe1894
--- /dev/null
+++ b/library/vendor/Zend/Cache/Backend/ZendServer/ShMem.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** @see Zend_Cache_Backend_Interface */
+
+/** @see Zend_Cache_Backend_ZendServer */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Backend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Backend_ZendServer_ShMem extends Zend_Cache_Backend_ZendServer implements Zend_Cache_Backend_Interface
+{
+ /**
+ * Constructor
+ *
+ * @param array $options associative array of options
+ * @throws Zend_Cache_Exception
+ */
+ public function __construct(array $options = array())
+ {
+ if (!function_exists('zend_shm_cache_store')) {
+ Zend_Cache::throwException('Zend_Cache_ZendServer_ShMem backend has to be used within Zend Server environment.');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Store data
+ *
+ * @param mixed $data Object to store
+ * @param string $id Cache id
+ * @param int $timeToLive Time to live in seconds
+ * @return bool
+ */
+ protected function _store($data, $id, $timeToLive)
+ {
+ if (zend_shm_cache_store($this->_options['namespace'] . '::' . $id,
+ $data,
+ $timeToLive) === false) {
+ $this->_log('Store operation failed.');
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Fetch data
+ *
+ * @param string $id Cache id
+ * @return mixed|null
+ */
+ protected function _fetch($id)
+ {
+ return zend_shm_cache_fetch($this->_options['namespace'] . '::' . $id);
+ }
+
+ /**
+ * Unset data
+ *
+ * @param string $id Cache id
+ * @return boolean true if no problem
+ */
+ protected function _unset($id)
+ {
+ return zend_shm_cache_delete($this->_options['namespace'] . '::' . $id);
+ }
+
+ /**
+ * Clear cache
+ */
+ protected function _clear()
+ {
+ zend_shm_cache_clear($this->_options['namespace']);
+ }
+}
diff --git a/library/vendor/Zend/Cache/Core.php b/library/vendor/Zend/Cache/Core.php
new file mode 100644
index 0000000..833f247
--- /dev/null
+++ b/library/vendor/Zend/Cache/Core.php
@@ -0,0 +1,762 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Core
+{
+ /**
+ * Messages
+ */
+ const BACKEND_NOT_SUPPORTS_TAG = 'tags are not supported by the current backend';
+ const BACKEND_NOT_IMPLEMENTS_EXTENDED_IF = 'Current backend doesn\'t implement the Zend_Cache_Backend_ExtendedInterface, so this method is not available';
+
+ /**
+ * Backend Object
+ *
+ * @var Zend_Cache_Backend_Interface $_backend
+ */
+ protected $_backend = null;
+
+ /**
+ * Available options
+ *
+ * ====> (boolean) write_control :
+ * - Enable / disable write control (the cache is read just after writing to detect corrupt entries)
+ * - Enable write control will lightly slow the cache writing but not the cache reading
+ * Write control can detect some corrupt cache files but maybe it's not a perfect control
+ *
+ * ====> (boolean) caching :
+ * - Enable / disable caching
+ * (can be very useful for the debug of cached scripts)
+ *
+ * =====> (string) cache_id_prefix :
+ * - prefix for cache ids (namespace)
+ *
+ * ====> (boolean) automatic_serialization :
+ * - Enable / disable automatic serialization
+ * - It can be used to save directly datas which aren't strings (but it's slower)
+ *
+ * ====> (int) automatic_cleaning_factor :
+ * - Disable / Tune the automatic cleaning process
+ * - The automatic cleaning process destroy too old (for the given life time)
+ * cache files when a new cache file is written :
+ * 0 => no automatic cache cleaning
+ * 1 => systematic cache cleaning
+ * x (integer) > 1 => automatic cleaning randomly 1 times on x cache write
+ *
+ * ====> (int) lifetime :
+ * - Cache lifetime (in seconds)
+ * - If null, the cache is valid forever.
+ *
+ * ====> (boolean) logging :
+ * - If set to true, logging is activated (but the system is slower)
+ *
+ * ====> (boolean) ignore_user_abort
+ * - If set to true, the core will set the ignore_user_abort PHP flag inside the
+ * save() method to avoid cache corruptions in some cases (default false)
+ *
+ * @var array $_options available options
+ */
+ protected $_options = array(
+ 'write_control' => true,
+ 'caching' => true,
+ 'cache_id_prefix' => null,
+ 'automatic_serialization' => false,
+ 'automatic_cleaning_factor' => 10,
+ 'lifetime' => 3600,
+ 'logging' => false,
+ 'logger' => null,
+ 'ignore_user_abort' => false
+ );
+
+ /**
+ * Array of options which have to be transfered to backend
+ *
+ * @var array $_directivesList
+ */
+ protected static $_directivesList = array('lifetime', 'logging', 'logger');
+
+ /**
+ * Not used for the core, just a sort a hint to get a common setOption() method (for the core and for frontends)
+ *
+ * @var array $_specificOptions
+ */
+ protected $_specificOptions = array();
+
+ /**
+ * Last used cache id
+ *
+ * @var string $_lastId
+ */
+ private $_lastId = null;
+
+ /**
+ * True if the backend implements Zend_Cache_Backend_ExtendedInterface
+ *
+ * @var boolean $_extendedBackend
+ */
+ protected $_extendedBackend = false;
+
+ /**
+ * Array of capabilities of the backend (only if it implements Zend_Cache_Backend_ExtendedInterface)
+ *
+ * @var array
+ */
+ protected $_backendCapabilities = array();
+
+ /**
+ * Constructor
+ *
+ * @param array|Zend_Config $options Associative array of options or Zend_Config instance
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+ if (!is_array($options)) {
+ Zend_Cache::throwException("Options passed were not an array"
+ . " or Zend_Config instance.");
+ }
+ foreach ($options as $name => $value) {
+ $this->setOption($name, $value);
+ }
+ $this->_loggerSanity();
+ }
+
+ /**
+ * Set options using an instance of type Zend_Config
+ *
+ * @param Zend_Config $config
+ * @return Zend_Cache_Core
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ $options = $config->toArray();
+ foreach ($options as $name => $value) {
+ $this->setOption($name, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Set the backend
+ *
+ * @param Zend_Cache_Backend $backendObject
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setBackend(Zend_Cache_Backend $backendObject)
+ {
+ $this->_backend= $backendObject;
+ // some options (listed in $_directivesList) have to be given
+ // to the backend too (even if they are not "backend specific")
+ $directives = array();
+ foreach (Zend_Cache_Core::$_directivesList as $directive) {
+ $directives[$directive] = $this->_options[$directive];
+ }
+ $this->_backend->setDirectives($directives);
+ if (in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_backend))) {
+ $this->_extendedBackend = true;
+ $this->_backendCapabilities = $this->_backend->getCapabilities();
+ }
+
+ }
+
+ /**
+ * Returns the backend
+ *
+ * @return Zend_Cache_Backend backend object
+ */
+ public function getBackend()
+ {
+ return $this->_backend;
+ }
+
+ /**
+ * Public frontend to set an option
+ *
+ * There is an additional validation (relatively to the protected _setOption method)
+ *
+ * @param string $name Name of the option
+ * @param mixed $value Value of the option
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setOption($name, $value)
+ {
+ if (!is_string($name)) {
+ Zend_Cache::throwException("Incorrect option name!");
+ }
+ $name = strtolower($name);
+ if (array_key_exists($name, $this->_options)) {
+ // This is a Core option
+ $this->_setOption($name, $value);
+ return;
+ }
+ if (array_key_exists($name, $this->_specificOptions)) {
+ // This a specic option of this frontend
+ $this->_specificOptions[$name] = $value;
+ return;
+ }
+ }
+
+ /**
+ * Public frontend to get an option value
+ *
+ * @param string $name Name of the option
+ * @throws Zend_Cache_Exception
+ * @return mixed option value
+ */
+ public function getOption($name)
+ {
+ $name = strtolower($name);
+
+ if (array_key_exists($name, $this->_options)) {
+ // This is a Core option
+ return $this->_options[$name];
+ }
+
+ if (array_key_exists($name, $this->_specificOptions)) {
+ // This a specic option of this frontend
+ return $this->_specificOptions[$name];
+ }
+
+ Zend_Cache::throwException("Incorrect option name : $name");
+ }
+
+ /**
+ * Set an option
+ *
+ * @param string $name Name of the option
+ * @param mixed $value Value of the option
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ private function _setOption($name, $value)
+ {
+ if (!is_string($name) || !array_key_exists($name, $this->_options)) {
+ Zend_Cache::throwException("Incorrect option name : $name");
+ }
+ if ($name == 'lifetime' && empty($value)) {
+ $value = null;
+ }
+ $this->_options[$name] = $value;
+ }
+
+ /**
+ * Force a new lifetime
+ *
+ * The new value is set for the core/frontend but for the backend too (directive)
+ *
+ * @param int $newLifetime New lifetime (in seconds)
+ * @return void
+ */
+ public function setLifetime($newLifetime)
+ {
+ $this->_options['lifetime'] = $newLifetime;
+ $this->_backend->setDirectives(array(
+ 'lifetime' => $newLifetime
+ ));
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use
+ * @return mixed|false Cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false)
+ {
+ if (!$this->_options['caching']) {
+ return false;
+ }
+ $id = $this->_id($id); // cache id may need prefix
+ $this->_lastId = $id;
+ $this->_validateIdOrTag($id);
+
+ $this->_log("Zend_Cache_Core: load item '{$id}'", 7);
+ $data = $this->_backend->load($id, $doNotTestCacheValidity);
+ if ($data===false) {
+ // no cache available
+ return false;
+ }
+ if ((!$doNotUnserialize) && $this->_options['automatic_serialization']) {
+ // we need to unserialize before sending the result
+ return unserialize($data);
+ }
+ return $data;
+ }
+
+ /**
+ * Test if a cache is available for the given id
+ *
+ * @param string $id Cache id
+ * @return int|false Last modified time of cache entry if it is available, false otherwise
+ */
+ public function test($id)
+ {
+ if (!$this->_options['caching']) {
+ return false;
+ }
+ $id = $this->_id($id); // cache id may need prefix
+ $this->_validateIdOrTag($id);
+ $this->_lastId = $id;
+
+ $this->_log("Zend_Cache_Core: test item '{$id}'", 7);
+ return $this->_backend->test($id);
+ }
+
+ /**
+ * Save some data in a cache
+ *
+ * @param mixed $data Data to put in cache (can be another type than string if automatic_serialization is on)
+ * @param string $id Cache id (if not set, the last cache id will be used)
+ * @param array $tags Cache tags
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
+ * @throws Zend_Cache_Exception
+ * @return boolean True if no problem
+ */
+ public function save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8)
+ {
+ if (!$this->_options['caching']) {
+ return true;
+ }
+ if ($id === null) {
+ $id = $this->_lastId;
+ } else {
+ $id = $this->_id($id);
+ }
+ $this->_validateIdOrTag($id);
+ $this->_validateTagsArray($tags);
+ if ($this->_options['automatic_serialization']) {
+ // we need to serialize datas before storing them
+ $data = serialize($data);
+ } else {
+ if (!is_string($data)) {
+ Zend_Cache::throwException("Datas must be string or set automatic_serialization = true");
+ }
+ }
+
+ // automatic cleaning
+ if ($this->_options['automatic_cleaning_factor'] > 0) {
+ $rand = rand(1, $this->_options['automatic_cleaning_factor']);
+ if ($rand==1) {
+ // new way || deprecated way
+ if ($this->_extendedBackend || method_exists($this->_backend, 'isAutomaticCleaningAvailable')) {
+ $this->_log("Zend_Cache_Core::save(): automatic cleaning running", 7);
+ $this->clean(Zend_Cache::CLEANING_MODE_OLD);
+ } else {
+ $this->_log("Zend_Cache_Core::save(): automatic cleaning is not available/necessary with current backend", 4);
+ }
+ }
+ }
+
+ $this->_log("Zend_Cache_Core: save item '{$id}'", 7);
+ if ($this->_options['ignore_user_abort']) {
+ $abort = ignore_user_abort(true);
+ }
+ if (($this->_extendedBackend) && ($this->_backendCapabilities['priority'])) {
+ $result = $this->_backend->save($data, $id, $tags, $specificLifetime, $priority);
+ } else {
+ $result = $this->_backend->save($data, $id, $tags, $specificLifetime);
+ }
+ if ($this->_options['ignore_user_abort']) {
+ ignore_user_abort($abort);
+ }
+
+ if (!$result) {
+ // maybe the cache is corrupted, so we remove it !
+ $this->_log("Zend_Cache_Core::save(): failed to save item '{$id}' -> removing it", 4);
+ $this->_backend->remove($id);
+ return false;
+ }
+
+ if ($this->_options['write_control']) {
+ $data2 = $this->_backend->load($id, true);
+ if ($data!=$data2) {
+ $this->_log("Zend_Cache_Core::save(): write control of item '{$id}' failed -> removing it", 4);
+ $this->_backend->remove($id);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove a cache
+ *
+ * @param string $id Cache id to remove
+ * @return boolean True if ok
+ */
+ public function remove($id)
+ {
+ if (!$this->_options['caching']) {
+ return true;
+ }
+ $id = $this->_id($id); // cache id may need prefix
+ $this->_validateIdOrTag($id);
+
+ $this->_log("Zend_Cache_Core: remove item '{$id}'", 7);
+ return $this->_backend->remove($id);
+ }
+
+ /**
+ * Clean cache entries
+ *
+ * Available modes are :
+ * 'all' (default) => remove all cache entries ($tags is not used)
+ * 'old' => remove too old cache entries ($tags is not used)
+ * 'matchingTag' => remove cache entries matching all given tags
+ * ($tags can be an array of strings or a single string)
+ * 'notMatchingTag' => remove cache entries not matching one of the given tags
+ * ($tags can be an array of strings or a single string)
+ * 'matchingAnyTag' => remove cache entries matching any given tags
+ * ($tags can be an array of strings or a single string)
+ *
+ * @param string $mode
+ * @param array|string $tags
+ * @throws Zend_Cache_Exception
+ * @return boolean True if ok
+ */
+ public function clean($mode = 'all', $tags = array())
+ {
+ if (!$this->_options['caching']) {
+ return true;
+ }
+ if (!in_array($mode, array(Zend_Cache::CLEANING_MODE_ALL,
+ Zend_Cache::CLEANING_MODE_OLD,
+ Zend_Cache::CLEANING_MODE_MATCHING_TAG,
+ Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG,
+ Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG))) {
+ Zend_Cache::throwException('Invalid cleaning mode');
+ }
+ $this->_validateTagsArray($tags);
+
+ return $this->_backend->clean($mode, $tags);
+ }
+
+ /**
+ * Return an array of stored cache ids which match given tags
+ *
+ * In case of multiple tags, a logical AND is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching cache ids (string)
+ */
+ public function getIdsMatchingTags($tags = array())
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ if (!($this->_backendCapabilities['tags'])) {
+ Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG);
+ }
+
+ $ids = $this->_backend->getIdsMatchingTags($tags);
+
+ // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600)
+ if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') {
+ $prefix = & $this->_options['cache_id_prefix'];
+ $prefixLen = strlen($prefix);
+ foreach ($ids as &$id) {
+ if (strpos($id, $prefix) === 0) {
+ $id = substr($id, $prefixLen);
+ }
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Return an array of stored cache ids which don't match given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of not matching cache ids (string)
+ */
+ public function getIdsNotMatchingTags($tags = array())
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ if (!($this->_backendCapabilities['tags'])) {
+ Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG);
+ }
+
+ $ids = $this->_backend->getIdsNotMatchingTags($tags);
+
+ // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600)
+ if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') {
+ $prefix = & $this->_options['cache_id_prefix'];
+ $prefixLen = strlen($prefix);
+ foreach ($ids as &$id) {
+ if (strpos($id, $prefix) === 0) {
+ $id = substr($id, $prefixLen);
+ }
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Return an array of stored cache ids which match any given tags
+ *
+ * In case of multiple tags, a logical OR is made between tags
+ *
+ * @param array $tags array of tags
+ * @return array array of matching any cache ids (string)
+ */
+ public function getIdsMatchingAnyTags($tags = array())
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ if (!($this->_backendCapabilities['tags'])) {
+ Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG);
+ }
+
+ $ids = $this->_backend->getIdsMatchingAnyTags($tags);
+
+ // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600)
+ if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') {
+ $prefix = & $this->_options['cache_id_prefix'];
+ $prefixLen = strlen($prefix);
+ foreach ($ids as &$id) {
+ if (strpos($id, $prefix) === 0) {
+ $id = substr($id, $prefixLen);
+ }
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Return an array of stored cache ids
+ *
+ * @return array array of stored cache ids (string)
+ */
+ public function getIds()
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+
+ $ids = $this->_backend->getIds();
+
+ // we need to remove cache_id_prefix from ids (see #ZF-6178, #ZF-7600)
+ if (isset($this->_options['cache_id_prefix']) && $this->_options['cache_id_prefix'] !== '') {
+ $prefix = & $this->_options['cache_id_prefix'];
+ $prefixLen = strlen($prefix);
+ foreach ($ids as &$id) {
+ if (strpos($id, $prefix) === 0) {
+ $id = substr($id, $prefixLen);
+ }
+ }
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Return an array of stored tags
+ *
+ * @return array array of stored tags (string)
+ */
+ public function getTags()
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ if (!($this->_backendCapabilities['tags'])) {
+ Zend_Cache::throwException(self::BACKEND_NOT_SUPPORTS_TAG);
+ }
+ return $this->_backend->getTags();
+ }
+
+ /**
+ * Return the filling percentage of the backend storage
+ *
+ * @return int integer between 0 and 100
+ */
+ public function getFillingPercentage()
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ return $this->_backend->getFillingPercentage();
+ }
+
+ /**
+ * Return an array of metadatas for the given cache id
+ *
+ * The array will include these keys :
+ * - expire : the expire timestamp
+ * - tags : a string array of tags
+ * - mtime : timestamp of last modification time
+ *
+ * @param string $id cache id
+ * @return array array of metadatas (false if the cache id is not found)
+ */
+ public function getMetadatas($id)
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ $id = $this->_id($id); // cache id may need prefix
+ return $this->_backend->getMetadatas($id);
+ }
+
+ /**
+ * Give (if possible) an extra lifetime to the given cache id
+ *
+ * @param string $id cache id
+ * @param int $extraLifetime
+ * @return boolean true if ok
+ */
+ public function touch($id, $extraLifetime)
+ {
+ if (!$this->_extendedBackend) {
+ Zend_Cache::throwException(self::BACKEND_NOT_IMPLEMENTS_EXTENDED_IF);
+ }
+ $id = $this->_id($id); // cache id may need prefix
+
+ $this->_log("Zend_Cache_Core: touch item '{$id}'", 7);
+ return $this->_backend->touch($id, $extraLifetime);
+ }
+
+ /**
+ * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...)
+ *
+ * Throw an exception if a problem is found
+ *
+ * @param string $string Cache id or tag
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _validateIdOrTag($string)
+ {
+ if (!is_string($string)) {
+ Zend_Cache::throwException('Invalid id or tag : must be a string');
+ }
+ if (substr($string, 0, 9) == 'internal-') {
+ Zend_Cache::throwException('"internal-*" ids or tags are reserved');
+ }
+ if (!preg_match('~^[a-zA-Z0-9_]+$~D', $string)) {
+ Zend_Cache::throwException("Invalid id or tag '$string' : must use only [a-zA-Z0-9_]");
+ }
+ }
+
+ /**
+ * Validate a tags array (security, reliable filenames, reserved prefixes...)
+ *
+ * Throw an exception if a problem is found
+ *
+ * @param array $tags Array of tags
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _validateTagsArray($tags)
+ {
+ if (!is_array($tags)) {
+ Zend_Cache::throwException('Invalid tags array : must be an array');
+ }
+ foreach($tags as $tag) {
+ $this->_validateIdOrTag($tag);
+ }
+ reset($tags);
+ }
+
+ /**
+ * Make sure if we enable logging that the Zend_Log class
+ * is available.
+ * Create a default log object if none is set.
+ *
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _loggerSanity()
+ {
+ if (!isset($this->_options['logging']) || !$this->_options['logging']) {
+ return;
+ }
+
+ if (isset($this->_options['logger']) && $this->_options['logger'] instanceof Zend_Log) {
+ return;
+ }
+
+ // Create a default logger to the standard output stream
+ $logger = new Zend_Log(new Zend_Log_Writer_Stream('php://output'));
+ $logger->addFilter(new Zend_Log_Filter_Priority(Zend_Log::WARN, '<='));
+ $this->_options['logger'] = $logger;
+ }
+
+ /**
+ * Log a message at the WARN (4) priority.
+ *
+ * @param string $message
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _log($message, $priority = 4)
+ {
+ if (!$this->_options['logging']) {
+ return;
+ }
+ if (!(isset($this->_options['logger']) || $this->_options['logger'] instanceof Zend_Log)) {
+ Zend_Cache::throwException('Logging is enabled but logger is not set');
+ }
+ $logger = $this->_options['logger'];
+ $logger->log($message, $priority);
+ }
+
+ /**
+ * Make and return a cache id
+ *
+ * Checks 'cache_id_prefix' and returns new id with prefix or simply the id if null
+ *
+ * @param string $id Cache id
+ * @return string Cache id (with or without prefix)
+ */
+ protected function _id($id)
+ {
+ if (($id !== null) && isset($this->_options['cache_id_prefix'])) {
+ return $this->_options['cache_id_prefix'] . $id; // return with prefix
+ }
+ return $id; // no prefix, just return the $id passed
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Exception.php b/library/vendor/Zend/Cache/Exception.php
new file mode 100644
index 0000000..bc3c815
--- /dev/null
+++ b/library/vendor/Zend/Cache/Exception.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Exception extends Zend_Exception {}
diff --git a/library/vendor/Zend/Cache/Frontend/Capture.php b/library/vendor/Zend/Cache/Frontend/Capture.php
new file mode 100644
index 0000000..4b70da4
--- /dev/null
+++ b/library/vendor/Zend/Cache/Frontend/Capture.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Core
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Frontend_Capture extends Zend_Cache_Core
+{
+ /**
+ * Page identifiers
+ * @var array
+ */
+ protected $_idStack = array();
+
+ /**
+ * Tags
+ * @var array
+ */
+ protected $_tags = array();
+
+ protected $_extension = null;
+
+ /**
+ * Start the cache
+ *
+ * @param string $id Cache id
+ * @return mixed True if the cache is hit (false else) with $echoData=true (default) ; string else (datas)
+ */
+ public function start($id, array $tags, $extension = null)
+ {
+ $this->_tags = $tags;
+ $this->_extension = $extension;
+ ob_start(array($this, '_flush'));
+ ob_implicit_flush(false);
+ $this->_idStack[] = $id;
+ return false;
+ }
+
+ /**
+ * callback for output buffering
+ * (shouldn't really be called manually)
+ *
+ * @param string $data Buffered output
+ * @return string Data to send to browser
+ */
+ public function _flush($data)
+ {
+ $id = array_pop($this->_idStack);
+ if ($id === null) {
+ Zend_Cache::throwException('use of _flush() without a start()');
+ }
+ if ($this->_extension) {
+ $this->save(serialize(array($data, $this->_extension)), $id, $this->_tags);
+ } else {
+ $this->save($data, $id, $this->_tags);
+ }
+ return $data;
+ }
+}
diff --git a/library/vendor/Zend/Cache/Frontend/Class.php b/library/vendor/Zend/Cache/Frontend/Class.php
new file mode 100644
index 0000000..c7208a1
--- /dev/null
+++ b/library/vendor/Zend/Cache/Frontend/Class.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Cache_Core
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Frontend_Class extends Zend_Cache_Core
+{
+ /**
+ * Available options
+ *
+ * ====> (mixed) cached_entity :
+ * - if set to a class name, we will cache an abstract class and will use only static calls
+ * - if set to an object, we will cache this object methods
+ *
+ * ====> (boolean) cache_by_default :
+ * - if true, method calls will be cached by default
+ *
+ * ====> (array) cached_methods :
+ * - an array of method names which will be cached (even if cache_by_default = false)
+ *
+ * ====> (array) non_cached_methods :
+ * - an array of method names which won't be cached (even if cache_by_default = true)
+ *
+ * @var array available options
+ */
+ protected $_specificOptions = array(
+ 'cached_entity' => null,
+ 'cache_by_default' => true,
+ 'cached_methods' => array(),
+ 'non_cached_methods' => array()
+ );
+
+ /**
+ * Tags array
+ *
+ * @var array
+ */
+ protected $_tags = array();
+
+ /**
+ * SpecificLifetime value
+ *
+ * false => no specific life time
+ *
+ * @var bool|int
+ */
+ protected $_specificLifetime = false;
+
+ /**
+ * The cached object or the name of the cached abstract class
+ *
+ * @var mixed
+ */
+ protected $_cachedEntity = null;
+
+ /**
+ * The class name of the cached object or cached abstract class
+ *
+ * Used to differentiate between different classes with the same method calls.
+ *
+ * @var string
+ */
+ protected $_cachedEntityLabel = '';
+
+ /**
+ * Priority (used by some particular backends)
+ *
+ * @var int
+ */
+ protected $_priority = 8;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @throws Zend_Cache_Exception
+ */
+ public function __construct(array $options = array())
+ {
+ foreach ($options as $name => $value) {
+ $this->setOption($name, $value);
+ }
+ if ($this->_specificOptions['cached_entity'] === null) {
+ Zend_Cache::throwException('cached_entity must be set !');
+ }
+ $this->setCachedEntity($this->_specificOptions['cached_entity']);
+ $this->setOption('automatic_serialization', true);
+ }
+
+ /**
+ * Set a specific life time
+ *
+ * @param bool|int $specificLifetime
+ * @return void
+ */
+ public function setSpecificLifetime($specificLifetime = false)
+ {
+ $this->_specificLifetime = $specificLifetime;
+ }
+
+ /**
+ * Set the priority (used by some particular backends)
+ *
+ * @param int $priority integer between 0 (very low priority) and 10 (maximum priority)
+ */
+ public function setPriority($priority)
+ {
+ $this->_priority = $priority;
+ }
+
+ /**
+ * Public frontend to set an option
+ *
+ * Just a wrapper to get a specific behaviour for cached_entity
+ *
+ * @param string $name Name of the option
+ * @param mixed $value Value of the option
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setOption($name, $value)
+ {
+ if ($name == 'cached_entity') {
+ $this->setCachedEntity($value);
+ } else {
+ parent::setOption($name, $value);
+ }
+ }
+
+ /**
+ * Specific method to set the cachedEntity
+ *
+ * if set to a class name, we will cache an abstract class and will use only static calls
+ * if set to an object, we will cache this object methods
+ *
+ * @param mixed $cachedEntity
+ */
+ public function setCachedEntity($cachedEntity)
+ {
+ if (!is_string($cachedEntity) && !is_object($cachedEntity)) {
+ Zend_Cache::throwException(
+ 'cached_entity must be an object or a class name'
+ );
+ }
+
+ $this->_cachedEntity = $cachedEntity;
+ $this->_specificOptions['cached_entity'] = $cachedEntity;
+
+ if (is_string($this->_cachedEntity)) {
+ $this->_cachedEntityLabel = $this->_cachedEntity;
+ } else {
+ $ro = new ReflectionObject($this->_cachedEntity);
+ $this->_cachedEntityLabel = $ro->getName();
+ }
+ }
+
+ /**
+ * Set the cache array
+ *
+ * @param array $tags
+ * @return void
+ */
+ public function setTagsArray($tags = array())
+ {
+ $this->_tags = $tags;
+ }
+
+ /**
+ * Main method : call the specified method or get the result from cache
+ *
+ * @param string $name Method name
+ * @param array $parameters Method parameters
+ * @return mixed Result
+ * @throws Exception
+ */
+ public function __call($name, $parameters)
+ {
+ $callback = array($this->_cachedEntity, $name);
+
+ if (!is_callable($callback, false)) {
+ Zend_Cache::throwException('Invalid callback');
+ }
+
+ $cacheBool1 = $this->_specificOptions['cache_by_default'];
+ $cacheBool2 = in_array($name, $this->_specificOptions['cached_methods']);
+ $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_methods']);
+ $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3));
+
+ if (!$cache) {
+ // We do not have not cache
+ return call_user_func_array($callback, $parameters);
+ }
+
+ $id = $this->makeId($name, $parameters);
+ if (($rs = $this->load($id)) && (array_key_exists(0, $rs))
+ && (array_key_exists(1, $rs))
+ ) {
+ // A cache is available
+ $output = $rs[0];
+ $return = $rs[1];
+ } else {
+ // A cache is not available (or not valid for this frontend)
+ ob_start();
+ ob_implicit_flush(false);
+
+ try {
+ $return = call_user_func_array($callback, $parameters);
+ $output = ob_get_clean();
+ $data = array($output, $return);
+
+ $this->save(
+ $data, $id, $this->_tags, $this->_specificLifetime,
+ $this->_priority
+ );
+ } catch (Exception $e) {
+ ob_end_clean();
+ throw $e;
+ }
+ }
+
+ echo $output;
+ return $return;
+ }
+
+ /**
+ * ZF-9970
+ *
+ * @deprecated
+ */
+ private function _makeId($name, $args)
+ {
+ return $this->makeId($name, $args);
+ }
+
+ /**
+ * Make a cache id from the method name and parameters
+ *
+ * @param string $name Method name
+ * @param array $args Method parameters
+ * @return string Cache id
+ */
+ public function makeId($name, array $args = array())
+ {
+ return md5($this->_cachedEntityLabel . '__' . $name . '__' . serialize($args));
+ }
+}
diff --git a/library/vendor/Zend/Cache/Frontend/File.php b/library/vendor/Zend/Cache/Frontend/File.php
new file mode 100644
index 0000000..98cd5a3
--- /dev/null
+++ b/library/vendor/Zend/Cache/Frontend/File.php
@@ -0,0 +1,221 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Core
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Frontend_File extends Zend_Cache_Core
+{
+
+ /**
+ * Consts for master_files_mode
+ */
+ const MODE_AND = 'AND';
+ const MODE_OR = 'OR';
+
+ /**
+ * Available options
+ *
+ * ====> (string) master_file :
+ * - a complete path of the master file
+ * - deprecated (see master_files)
+ *
+ * ====> (array) master_files :
+ * - an array of complete path of master files
+ * - this option has to be set !
+ *
+ * ====> (string) master_files_mode :
+ * - Zend_Cache_Frontend_File::MODE_AND or Zend_Cache_Frontend_File::MODE_OR
+ * - if MODE_AND, then all master files have to be touched to get a cache invalidation
+ * - if MODE_OR (default), then a single touched master file is enough to get a cache invalidation
+ *
+ * ====> (boolean) ignore_missing_master_files
+ * - if set to true, missing master files are ignored silently
+ * - if set to false (default), an exception is thrown if there is a missing master file
+ * @var array available options
+ */
+ protected $_specificOptions = array(
+ 'master_file' => null,
+ 'master_files' => null,
+ 'master_files_mode' => 'OR',
+ 'ignore_missing_master_files' => false
+ );
+
+ /**
+ * Master file mtimes
+ *
+ * Array of int
+ *
+ * @var array
+ */
+ private $_masterFile_mtimes = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ foreach ($options as $name => $value) {
+ $this->setOption($name, $value);
+ }
+ if (!isset($this->_specificOptions['master_files'])) {
+ Zend_Cache::throwException('master_files option must be set');
+ }
+ }
+
+ /**
+ * Change the master_files option
+ *
+ * @param array $masterFiles the complete paths and name of the master files
+ */
+ public function setMasterFiles(array $masterFiles)
+ {
+ $this->_specificOptions['master_file'] = null; // to keep a compatibility
+ $this->_specificOptions['master_files'] = null;
+ $this->_masterFile_mtimes = array();
+
+ clearstatcache();
+ $i = 0;
+ foreach ($masterFiles as $masterFile) {
+ if (file_exists($masterFile)) {
+ $mtime = filemtime($masterFile);
+ } else {
+ $mtime = false;
+ }
+
+ if (!$this->_specificOptions['ignore_missing_master_files'] && !$mtime) {
+ Zend_Cache::throwException('Unable to read master_file : ' . $masterFile);
+ }
+
+ $this->_masterFile_mtimes[$i] = $mtime;
+ $this->_specificOptions['master_files'][$i] = $masterFile;
+ if ($i === 0) { // to keep a compatibility
+ $this->_specificOptions['master_file'] = $masterFile;
+ }
+
+ $i++;
+ }
+ }
+
+ /**
+ * Change the master_file option
+ *
+ * To keep the compatibility
+ *
+ * @deprecated
+ * @param string $masterFile the complete path and name of the master file
+ */
+ public function setMasterFile($masterFile)
+ {
+ $this->setMasterFiles(array($masterFile));
+ }
+
+ /**
+ * Public frontend to set an option
+ *
+ * Just a wrapper to get a specific behaviour for master_file
+ *
+ * @param string $name Name of the option
+ * @param mixed $value Value of the option
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function setOption($name, $value)
+ {
+ if ($name == 'master_file') {
+ $this->setMasterFile($value);
+ } else if ($name == 'master_files') {
+ $this->setMasterFiles($value);
+ } else {
+ parent::setOption($name, $value);
+ }
+ }
+
+ /**
+ * Test if a cache is available for the given id and (if yes) return it (false else)
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @param boolean $doNotUnserialize Do not serialize (even if automatic_serialization is true) => for internal use
+ * @return mixed|false Cached datas
+ */
+ public function load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false)
+ {
+ if (!$doNotTestCacheValidity) {
+ if ($this->test($id)) {
+ return parent::load($id, true, $doNotUnserialize);
+ }
+ return false;
+ }
+ return parent::load($id, true, $doNotUnserialize);
+ }
+
+ /**
+ * Test if a cache is available for the given id
+ *
+ * @param string $id Cache id
+ * @return int|false Last modified time of cache entry if it is available, false otherwise
+ */
+ public function test($id)
+ {
+ $lastModified = parent::test($id);
+ if ($lastModified) {
+ if ($this->_specificOptions['master_files_mode'] == self::MODE_AND) {
+ // MODE_AND
+ foreach($this->_masterFile_mtimes as $masterFileMTime) {
+ if ($masterFileMTime) {
+ if ($lastModified > $masterFileMTime) {
+ return $lastModified;
+ }
+ }
+ }
+ } else {
+ // MODE_OR
+ $res = true;
+ foreach($this->_masterFile_mtimes as $masterFileMTime) {
+ if ($masterFileMTime) {
+ if ($lastModified <= $masterFileMTime) {
+ return false;
+ }
+ }
+ }
+ return $lastModified;
+ }
+ }
+ return false;
+ }
+
+}
+
diff --git a/library/vendor/Zend/Cache/Frontend/Function.php b/library/vendor/Zend/Cache/Frontend/Function.php
new file mode 100644
index 0000000..3dc7fd8
--- /dev/null
+++ b/library/vendor/Zend/Cache/Frontend/Function.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Core
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Frontend_Function extends Zend_Cache_Core
+{
+ /**
+ * This frontend specific options
+ *
+ * ====> (boolean) cache_by_default :
+ * - if true, function calls will be cached by default
+ *
+ * ====> (array) cached_functions :
+ * - an array of function names which will be cached (even if cache_by_default = false)
+ *
+ * ====> (array) non_cached_functions :
+ * - an array of function names which won't be cached (even if cache_by_default = true)
+ *
+ * @var array options
+ */
+ protected $_specificOptions = array(
+ 'cache_by_default' => true,
+ 'cached_functions' => array(),
+ 'non_cached_functions' => array()
+ );
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ foreach ($options as $name => $value) {
+ $this->setOption($name, $value);
+ }
+ $this->setOption('automatic_serialization', true);
+ }
+
+ /**
+ * Main method : call the specified function or get the result from cache
+ *
+ * @param callback $callback A valid callback
+ * @param array $parameters Function parameters
+ * @param array $tags Cache tags
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
+ * @return mixed Result
+ */
+ public function call($callback, array $parameters = array(), $tags = array(), $specificLifetime = false, $priority = 8)
+ {
+ if (!is_callable($callback, true, $name)) {
+ Zend_Cache::throwException('Invalid callback');
+ }
+
+ $cacheBool1 = $this->_specificOptions['cache_by_default'];
+ $cacheBool2 = in_array($name, $this->_specificOptions['cached_functions']);
+ $cacheBool3 = in_array($name, $this->_specificOptions['non_cached_functions']);
+ $cache = (($cacheBool1 || $cacheBool2) && (!$cacheBool3));
+ if (!$cache) {
+ // Caching of this callback is disabled
+ return call_user_func_array($callback, $parameters);
+ }
+
+ $id = $this->_makeId($callback, $parameters);
+ if ( ($rs = $this->load($id)) && isset($rs[0], $rs[1])) {
+ // A cache is available
+ $output = $rs[0];
+ $return = $rs[1];
+ } else {
+ // A cache is not available (or not valid for this frontend)
+ ob_start();
+ ob_implicit_flush(false);
+ $return = call_user_func_array($callback, $parameters);
+ $output = ob_get_clean();
+ $data = array($output, $return);
+ $this->save($data, $id, $tags, $specificLifetime, $priority);
+ }
+
+ echo $output;
+ return $return;
+ }
+
+ /**
+ * ZF-9970
+ *
+ * @deprecated
+ */
+ private function _makeId($callback, array $args)
+ {
+ return $this->makeId($callback, $args);
+ }
+
+ /**
+ * Make a cache id from the function name and parameters
+ *
+ * @param callback $callback A valid callback
+ * @param array $args Function parameters
+ * @throws Zend_Cache_Exception
+ * @return string Cache id
+ */
+ public function makeId($callback, array $args = array())
+ {
+ if (!is_callable($callback, true, $name)) {
+ Zend_Cache::throwException('Invalid callback');
+ }
+
+ // functions, methods and classnames are case-insensitive
+ $name = strtolower($name);
+
+ // generate a unique id for object callbacks
+ if (is_object($callback)) { // Closures & __invoke
+ $object = $callback;
+ } elseif (isset($callback[0])) { // array($object, 'method')
+ $object = $callback[0];
+ }
+ if (isset($object)) {
+ try {
+ $tmp = @serialize($callback);
+ } catch (Exception $e) {
+ Zend_Cache::throwException($e->getMessage());
+ }
+ if (!$tmp) {
+ $lastErr = error_get_last();
+ Zend_Cache::throwException("Can't serialize callback object to generate id: {$lastErr['message']}");
+ }
+ $name.= '__' . $tmp;
+ }
+
+ // generate a unique id for arguments
+ $argsStr = '';
+ if ($args) {
+ try {
+ $argsStr = @serialize(array_values($args));
+ } catch (Exception $e) {
+ Zend_Cache::throwException($e->getMessage());
+ }
+ if (!$argsStr) {
+ $lastErr = error_get_last();
+ throw Zend_Cache::throwException("Can't serialize arguments to generate id: {$lastErr['message']}");
+ }
+ }
+
+ return md5($name . $argsStr);
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Frontend/Output.php b/library/vendor/Zend/Cache/Frontend/Output.php
new file mode 100644
index 0000000..dd8fe8d
--- /dev/null
+++ b/library/vendor/Zend/Cache/Frontend/Output.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Core
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Frontend_Output extends Zend_Cache_Core
+{
+
+ private $_idStack = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ parent::__construct($options);
+ $this->_idStack = array();
+ }
+
+ /**
+ * Start the cache
+ *
+ * @param string $id Cache id
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @param boolean $echoData If set to true, datas are sent to the browser if the cache is hit (simply returned else)
+ * @return mixed True if the cache is hit (false else) with $echoData=true (default) ; string else (datas)
+ */
+ public function start($id, $doNotTestCacheValidity = false, $echoData = true)
+ {
+ $data = $this->load($id, $doNotTestCacheValidity);
+ if ($data !== false) {
+ if ( $echoData ) {
+ echo($data);
+ return true;
+ } else {
+ return $data;
+ }
+ }
+ ob_start();
+ ob_implicit_flush(false);
+ $this->_idStack[] = $id;
+ return false;
+ }
+
+ /**
+ * Stop the cache
+ *
+ * @param array $tags Tags array
+ * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
+ * @param string $forcedDatas If not null, force written datas with this
+ * @param boolean $echoData If set to true, datas are sent to the browser
+ * @param int $priority integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
+ * @return void
+ */
+ public function end($tags = array(), $specificLifetime = false, $forcedDatas = null, $echoData = true, $priority = 8)
+ {
+ if ($forcedDatas === null) {
+ $data = ob_get_clean();
+ } else {
+ $data =& $forcedDatas;
+ }
+ $id = array_pop($this->_idStack);
+ if ($id === null) {
+ Zend_Cache::throwException('use of end() without a start()');
+ }
+ $this->save($data, $id, $tags, $specificLifetime, $priority);
+ if ($echoData) {
+ echo($data);
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Frontend/Page.php b/library/vendor/Zend/Cache/Frontend/Page.php
new file mode 100644
index 0000000..8326281
--- /dev/null
+++ b/library/vendor/Zend/Cache/Frontend/Page.php
@@ -0,0 +1,403 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Cache_Core
+ */
+
+
+/**
+ * @package Zend_Cache
+ * @subpackage Zend_Cache_Frontend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Frontend_Page extends Zend_Cache_Core
+{
+ /**
+ * This frontend specific options
+ *
+ * ====> (boolean) http_conditional :
+ * - if true, http conditional mode is on
+ * WARNING : http_conditional OPTION IS NOT IMPLEMENTED FOR THE MOMENT (TODO)
+ *
+ * ====> (boolean) debug_header :
+ * - if true, a debug text is added before each cached pages
+ *
+ * ====> (boolean) content_type_memorization :
+ * - deprecated => use memorize_headers instead
+ * - if the Content-Type header is sent after the cache was started, the
+ * corresponding value can be memorized and replayed when the cache is hit
+ * (if false (default), the frontend doesn't take care of Content-Type header)
+ *
+ * ====> (array) memorize_headers :
+ * - an array of strings corresponding to some HTTP headers name. Listed headers
+ * will be stored with cache datas and "replayed" when the cache is hit
+ *
+ * ====> (array) default_options :
+ * - an associative array of default options :
+ * - (boolean) cache : cache is on by default if true
+ * - (boolean) cacheWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') :
+ * if true, cache is still on even if there are some variables in this superglobal array
+ * if false, cache is off if there are some variables in this superglobal array
+ * - (boolean) makeIdWithXXXVariables (XXXX = 'Get', 'Post', 'Session', 'Files' or 'Cookie') :
+ * if true, we have to use the content of this superglobal array to make a cache id
+ * if false, the cache id won't be dependent of the content of this superglobal array
+ * - (int) specific_lifetime : cache specific lifetime
+ * (false => global lifetime is used, null => infinite lifetime,
+ * integer => this lifetime is used), this "lifetime" is probably only
+ * usefull when used with "regexps" array
+ * - (array) tags : array of tags (strings)
+ * - (int) priority : integer between 0 (very low priority) and 10 (maximum priority) used by
+ * some particular backends
+ *
+ * ====> (array) regexps :
+ * - an associative array to set options only for some REQUEST_URI
+ * - keys are (pcre) regexps
+ * - values are associative array with specific options to set if the regexp matchs on $_SERVER['REQUEST_URI']
+ * (see default_options for the list of available options)
+ * - if several regexps match the $_SERVER['REQUEST_URI'], only the last one will be used
+ *
+ * @var array options
+ */
+ protected $_specificOptions = array(
+ 'http_conditional' => false,
+ 'debug_header' => false,
+ 'content_type_memorization' => false,
+ 'memorize_headers' => array(),
+ 'default_options' => array(
+ 'cache_with_get_variables' => false,
+ 'cache_with_post_variables' => false,
+ 'cache_with_session_variables' => false,
+ 'cache_with_files_variables' => false,
+ 'cache_with_cookie_variables' => false,
+ 'make_id_with_get_variables' => true,
+ 'make_id_with_post_variables' => true,
+ 'make_id_with_session_variables' => true,
+ 'make_id_with_files_variables' => true,
+ 'make_id_with_cookie_variables' => true,
+ 'cache' => true,
+ 'specific_lifetime' => false,
+ 'tags' => array(),
+ 'priority' => null
+ ),
+ 'regexps' => array()
+ );
+
+ /**
+ * Internal array to store some options
+ *
+ * @var array associative array of options
+ */
+ protected $_activeOptions = array();
+
+ /**
+ * If true, the page won't be cached
+ *
+ * @var boolean
+ */
+ protected $_cancel = false;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Associative array of options
+ * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ public function __construct(array $options = array())
+ {
+ foreach ($options as $name => $value) {
+ $name = strtolower($name);
+ switch ($name) {
+ case 'regexps':
+ $this->_setRegexps($value);
+ break;
+ case 'default_options':
+ $this->_setDefaultOptions($value);
+ break;
+ case 'content_type_memorization':
+ $this->_setContentTypeMemorization($value);
+ break;
+ default:
+ $this->setOption($name, $value);
+ }
+ }
+ if (isset($this->_specificOptions['http_conditional'])) {
+ if ($this->_specificOptions['http_conditional']) {
+ Zend_Cache::throwException('http_conditional is not implemented for the moment !');
+ }
+ }
+ $this->setOption('automatic_serialization', true);
+ }
+
+ /**
+ * Specific setter for the 'default_options' option (with some additional tests)
+ *
+ * @param array $options Associative array
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _setDefaultOptions($options)
+ {
+ if (!is_array($options)) {
+ Zend_Cache::throwException('default_options must be an array !');
+ }
+ foreach ($options as $key=>$value) {
+ if (!is_string($key)) {
+ Zend_Cache::throwException("invalid option [$key] !");
+ }
+ $key = strtolower($key);
+ if (isset($this->_specificOptions['default_options'][$key])) {
+ $this->_specificOptions['default_options'][$key] = $value;
+ }
+ }
+ }
+
+ /**
+ * Set the deprecated contentTypeMemorization option
+ *
+ * @param boolean $value value
+ * @return void
+ * @deprecated
+ */
+ protected function _setContentTypeMemorization($value)
+ {
+ $found = null;
+ foreach ($this->_specificOptions['memorize_headers'] as $key => $value) {
+ if (strtolower($value) == 'content-type') {
+ $found = $key;
+ }
+ }
+ if ($value) {
+ if (!$found) {
+ $this->_specificOptions['memorize_headers'][] = 'Content-Type';
+ }
+ } else {
+ if ($found) {
+ unset($this->_specificOptions['memorize_headers'][$found]);
+ }
+ }
+ }
+
+ /**
+ * Specific setter for the 'regexps' option (with some additional tests)
+ *
+ * @param array $options Associative array
+ * @throws Zend_Cache_Exception
+ * @return void
+ */
+ protected function _setRegexps($regexps)
+ {
+ if (!is_array($regexps)) {
+ Zend_Cache::throwException('regexps option must be an array !');
+ }
+ foreach ($regexps as $regexp=>$conf) {
+ if (!is_array($conf)) {
+ Zend_Cache::throwException('regexps option must be an array of arrays !');
+ }
+ $validKeys = array_keys($this->_specificOptions['default_options']);
+ foreach ($conf as $key=>$value) {
+ if (!is_string($key)) {
+ Zend_Cache::throwException("unknown option [$key] !");
+ }
+ $key = strtolower($key);
+ if (!in_array($key, $validKeys)) {
+ unset($regexps[$regexp][$key]);
+ }
+ }
+ }
+ $this->setOption('regexps', $regexps);
+ }
+
+ /**
+ * Start the cache
+ *
+ * @param string $id (optional) A cache id (if you set a value here, maybe you have to use Output frontend instead)
+ * @param boolean $doNotDie For unit testing only !
+ * @return boolean True if the cache is hit (false else)
+ */
+ public function start($id = false, $doNotDie = false)
+ {
+ $this->_cancel = false;
+ $lastMatchingRegexp = null;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ foreach ($this->_specificOptions['regexps'] as $regexp => $conf) {
+ if (preg_match("`$regexp`", $_SERVER['REQUEST_URI'])) {
+ $lastMatchingRegexp = $regexp;
+ }
+ }
+ }
+ $this->_activeOptions = $this->_specificOptions['default_options'];
+ if ($lastMatchingRegexp !== null) {
+ $conf = $this->_specificOptions['regexps'][$lastMatchingRegexp];
+ foreach ($conf as $key=>$value) {
+ $this->_activeOptions[$key] = $value;
+ }
+ }
+ if (!($this->_activeOptions['cache'])) {
+ return false;
+ }
+ if (!$id) {
+ $id = $this->_makeId();
+ if (!$id) {
+ return false;
+ }
+ }
+ $array = $this->load($id);
+ if ($array !== false) {
+ $data = $array['data'];
+ $headers = $array['headers'];
+ if (!headers_sent()) {
+ foreach ($headers as $key=>$headerCouple) {
+ $name = $headerCouple[0];
+ $value = $headerCouple[1];
+ header("$name: $value");
+ }
+ }
+ if ($this->_specificOptions['debug_header']) {
+ echo 'DEBUG HEADER : This is a cached page !';
+ }
+ echo $data;
+ if ($doNotDie) {
+ return true;
+ }
+ die();
+ }
+ ob_start(array($this, '_flush'));
+ ob_implicit_flush(false);
+ return false;
+ }
+
+ /**
+ * Cancel the current caching process
+ */
+ public function cancel()
+ {
+ $this->_cancel = true;
+ }
+
+ /**
+ * callback for output buffering
+ * (shouldn't really be called manually)
+ *
+ * @param string $data Buffered output
+ * @return string Data to send to browser
+ */
+ public function _flush($data)
+ {
+ if ($this->_cancel) {
+ return $data;
+ }
+ $contentType = null;
+ $storedHeaders = array();
+ $headersList = headers_list();
+ foreach($this->_specificOptions['memorize_headers'] as $key=>$headerName) {
+ foreach ($headersList as $headerSent) {
+ $tmp = explode(':', $headerSent);
+ $headerSentName = trim(array_shift($tmp));
+ if (strtolower($headerName) == strtolower($headerSentName)) {
+ $headerSentValue = trim(implode(':', $tmp));
+ $storedHeaders[] = array($headerSentName, $headerSentValue);
+ }
+ }
+ }
+ $array = array(
+ 'data' => $data,
+ 'headers' => $storedHeaders
+ );
+ $this->save($array, null, $this->_activeOptions['tags'], $this->_activeOptions['specific_lifetime'], $this->_activeOptions['priority']);
+ return $data;
+ }
+
+ /**
+ * Make an id depending on REQUEST_URI and superglobal arrays (depending on options)
+ *
+ * @return mixed|false a cache id (string), false if the cache should have not to be used
+ */
+ protected function _makeId()
+ {
+ $tmp = $_SERVER['REQUEST_URI'];
+ $array = explode('?', $tmp, 2);
+ $tmp = $array[0];
+ foreach (array('Get', 'Post', 'Session', 'Files', 'Cookie') as $arrayName) {
+ $tmp2 = $this->_makePartialId($arrayName, $this->_activeOptions['cache_with_' . strtolower($arrayName) . '_variables'], $this->_activeOptions['make_id_with_' . strtolower($arrayName) . '_variables']);
+ if ($tmp2===false) {
+ return false;
+ }
+ $tmp = $tmp . $tmp2;
+ }
+ return md5($tmp);
+ }
+
+ /**
+ * Make a partial id depending on options
+ *
+ * @param string $arrayName Superglobal array name
+ * @param bool $bool1 If true, cache is still on even if there are some variables in the superglobal array
+ * @param bool $bool2 If true, we have to use the content of the superglobal array to make a partial id
+ * @return mixed|false Partial id (string) or false if the cache should have not to be used
+ */
+ protected function _makePartialId($arrayName, $bool1, $bool2)
+ {
+ switch ($arrayName) {
+ case 'Get':
+ $var = $_GET;
+ break;
+ case 'Post':
+ $var = $_POST;
+ break;
+ case 'Session':
+ if (isset($_SESSION)) {
+ $var = $_SESSION;
+ } else {
+ $var = null;
+ }
+ break;
+ case 'Cookie':
+ if (isset($_COOKIE)) {
+ $var = $_COOKIE;
+ } else {
+ $var = null;
+ }
+ break;
+ case 'Files':
+ $var = $_FILES;
+ break;
+ default:
+ return false;
+ }
+ if ($bool1) {
+ if ($bool2) {
+ return serialize($var);
+ }
+ return '';
+ }
+ if (count($var) > 0) {
+ return false;
+ }
+ return '';
+ }
+
+}
diff --git a/library/vendor/Zend/Cache/Manager.php b/library/vendor/Zend/Cache/Manager.php
new file mode 100644
index 0000000..5196c19
--- /dev/null
+++ b/library/vendor/Zend/Cache/Manager.php
@@ -0,0 +1,304 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** @see Zend_Cache_Exception */
+
+/** @see Zend_Cache */
+
+/**
+ * @category Zend
+ * @package Zend_Cache
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Cache_Manager
+{
+ /**
+ * Constant holding reserved name for default Page Cache
+ */
+ const PAGECACHE = 'page';
+
+ /**
+ * Constant holding reserved name for default Page Tag Cache
+ */
+ const PAGETAGCACHE = 'pagetag';
+
+ /**
+ * Array of caches stored by the Cache Manager instance
+ *
+ * @var array
+ */
+ protected $_caches = array();
+
+ /**
+ * Array of ready made configuration templates for lazy
+ * loading caches.
+ *
+ * @var array
+ */
+ protected $_optionTemplates = array(
+ // Simple Common Default
+ 'default' => array(
+ 'frontend' => array(
+ 'name' => 'Core',
+ 'options' => array(
+ 'automatic_serialization' => true,
+ ),
+ ),
+ 'backend' => array(
+ 'name' => 'File',
+ 'options' => array(
+ // use system temp dir by default of file backend
+ // 'cache_dir' => '../cache',
+ ),
+ ),
+ ),
+
+ // Static Page HTML Cache
+ 'page' => array(
+ 'frontend' => array(
+ 'name' => 'Capture',
+ 'options' => array(
+ 'ignore_user_abort' => true,
+ ),
+ ),
+ 'backend' => array(
+ 'name' => 'Static',
+ 'options' => array(
+ 'public_dir' => '../public',
+ ),
+ ),
+ ),
+
+ // Tag Cache
+ 'pagetag' => array(
+ 'frontend' => array(
+ 'name' => 'Core',
+ 'options' => array(
+ 'automatic_serialization' => true,
+ 'lifetime' => null
+ ),
+ ),
+ 'backend' => array(
+ 'name' => 'File',
+ 'options' => array(
+ // use system temp dir by default of file backend
+ // 'cache_dir' => '../cache',
+ // use default umask of file backend
+ // 'cache_file_umask' => 0644
+ ),
+ ),
+ ),
+ );
+
+ /**
+ * Set a new cache for the Cache Manager to contain
+ *
+ * @param string $name
+ * @param Zend_Cache_Core $cache
+ * @return Zend_Cache_Manager
+ */
+ public function setCache($name, Zend_Cache_Core $cache)
+ {
+ $this->_caches[$name] = $cache;
+ return $this;
+ }
+
+ /**
+ * Check if the Cache Manager contains the named cache object, or a named
+ * configuration template to lazy load the cache object
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasCache($name)
+ {
+ if (isset($this->_caches[$name])
+ || $this->hasCacheTemplate($name)
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Fetch the named cache object, or instantiate and return a cache object
+ * using a named configuration template
+ *
+ * @param string $name
+ * @return Zend_Cache_Core
+ */
+ public function getCache($name)
+ {
+ if (isset($this->_caches[$name])) {
+ return $this->_caches[$name];
+ }
+ if (isset($this->_optionTemplates[$name])) {
+ if ($name == self::PAGECACHE
+ && (!isset($this->_optionTemplates[$name]['backend']['options']['tag_cache'])
+ || !$this->_optionTemplates[$name]['backend']['options']['tag_cache'] instanceof Zend_Cache_Core)
+ ) {
+ $this->_optionTemplates[$name]['backend']['options']['tag_cache']
+ = $this->getCache(self::PAGETAGCACHE);
+ }
+
+ $this->_caches[$name] = Zend_Cache::factory(
+ $this->_optionTemplates[$name]['frontend']['name'],
+ $this->_optionTemplates[$name]['backend']['name'],
+ isset($this->_optionTemplates[$name]['frontend']['options']) ? $this->_optionTemplates[$name]['frontend']['options'] : array(),
+ isset($this->_optionTemplates[$name]['backend']['options']) ? $this->_optionTemplates[$name]['backend']['options'] : array(),
+ isset($this->_optionTemplates[$name]['frontend']['customFrontendNaming']) ? $this->_optionTemplates[$name]['frontend']['customFrontendNaming'] : false,
+ isset($this->_optionTemplates[$name]['backend']['customBackendNaming']) ? $this->_optionTemplates[$name]['backend']['customBackendNaming'] : false,
+ isset($this->_optionTemplates[$name]['frontendBackendAutoload']) ? $this->_optionTemplates[$name]['frontendBackendAutoload'] : false
+ );
+
+ return $this->_caches[$name];
+ }
+ }
+
+ /**
+ * Fetch all available caches
+ *
+ * @return array An array of all available caches with it's names as key
+ */
+ public function getCaches()
+ {
+ $caches = $this->_caches;
+ foreach ($this->_optionTemplates as $name => $tmp) {
+ if (!isset($caches[$name])) {
+ $caches[$name] = $this->getCache($name);
+ }
+ }
+ return $caches;
+ }
+
+ /**
+ * Set a named configuration template from which a cache object can later
+ * be lazy loaded
+ *
+ * @param string $name
+ * @param array $options
+ * @return Zend_Cache_Manager
+ * @throws Zend_Cache_Exception
+ */
+ public function setCacheTemplate($name, $options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (!is_array($options)) {
+ throw new Zend_Cache_Exception('Options passed must be in'
+ . ' an associative array or instance of Zend_Config');
+ }
+ $this->_optionTemplates[$name] = $options;
+ return $this;
+ }
+
+ /**
+ * Check if the named configuration template
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasCacheTemplate($name)
+ {
+ if (isset($this->_optionTemplates[$name])) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the named configuration template
+ *
+ * @param string $name
+ * @return array
+ */
+ public function getCacheTemplate($name)
+ {
+ if (isset($this->_optionTemplates[$name])) {
+ return $this->_optionTemplates[$name];
+ }
+ }
+
+ /**
+ * Pass an array containing changes to be applied to a named
+ * configuration
+ * template
+ *
+ * @param string $name
+ * @param array $options
+ * @return Zend_Cache_Manager
+ * @throws Zend_Cache_Exception for invalid options format or if option templates do not have $name
+ */
+ public function setTemplateOptions($name, $options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (!is_array($options)) {
+ throw new Zend_Cache_Exception('Options passed must be in'
+ . ' an associative array or instance of Zend_Config');
+ }
+ if (!isset($this->_optionTemplates[$name])) {
+ throw new Zend_Cache_Exception('A cache configuration template'
+ . 'does not exist with the name "' . $name . '"');
+ }
+ $this->_optionTemplates[$name]
+ = $this->_mergeOptions($this->_optionTemplates[$name], $options);
+ return $this;
+ }
+
+ /**
+ * Simple method to merge two configuration arrays
+ *
+ * @param array $current
+ * @param array $options
+ * @return array
+ */
+ protected function _mergeOptions(array $current, array $options)
+ {
+ if (isset($options['frontend']['name'])) {
+ $current['frontend']['name'] = $options['frontend']['name'];
+ }
+ if (isset($options['backend']['name'])) {
+ $current['backend']['name'] = $options['backend']['name'];
+ }
+ if (isset($options['frontend']['options'])) {
+ foreach ($options['frontend']['options'] as $key => $value) {
+ $current['frontend']['options'][$key] = $value;
+ }
+ }
+ if (isset($options['backend']['options'])) {
+ foreach ($options['backend']['options'] as $key => $value) {
+ $current['backend']['options'][$key] = $value;
+ }
+ }
+ if (isset($options['frontend']['customFrontendNaming'])) {
+ $current['frontend']['customFrontendNaming'] = $options['frontend']['customFrontendNaming'];
+ }
+ if (isset($options['backend']['customBackendNaming'])) {
+ $current['backend']['customBackendNaming'] = $options['backend']['customBackendNaming'];
+ }
+ if (isset($options['frontendBackendAutoload'])) {
+ $current['frontendBackendAutoload'] = $options['frontendBackendAutoload'];
+ }
+ return $current;
+ }
+}
diff --git a/library/vendor/Zend/Config.php b/library/vendor/Zend/Config.php
new file mode 100644
index 0000000..59c3265
--- /dev/null
+++ b/library/vendor/Zend/Config.php
@@ -0,0 +1,481 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Config implements Countable, Iterator
+{
+ /**
+ * Whether in-memory modifications to configuration data are allowed
+ *
+ * @var boolean
+ */
+ protected $_allowModifications;
+
+ /**
+ * Iteration index
+ *
+ * @var integer
+ */
+ protected $_index;
+
+ /**
+ * Number of elements in configuration data
+ *
+ * @var integer
+ */
+ protected $_count;
+
+ /**
+ * Contains array of configuration data
+ *
+ * @var array
+ */
+ protected $_data;
+
+ /**
+ * Used when unsetting values during iteration to ensure we do not skip
+ * the next element
+ *
+ * @var boolean
+ */
+ protected $_skipNextIteration;
+
+ /**
+ * Contains which config file sections were loaded. This is null
+ * if all sections were loaded, a string name if one section is loaded
+ * and an array of string names if multiple sections were loaded.
+ *
+ * @var mixed
+ */
+ protected $_loadedSection;
+
+ /**
+ * This is used to track section inheritance. The keys are names of sections that
+ * extend other sections, and the values are the extended sections.
+ *
+ * @var array
+ */
+ protected $_extends = array();
+
+ /**
+ * Load file error string.
+ *
+ * Is null if there was no error while file loading
+ *
+ * @var string
+ */
+ protected $_loadFileErrorStr = null;
+
+ /**
+ * Zend_Config provides a property based interface to
+ * an array. The data are read-only unless $allowModifications
+ * is set to true on construction.
+ *
+ * Zend_Config also implements Countable and Iterator to
+ * facilitate easy access to the data.
+ *
+ * @param array $array
+ * @param boolean $allowModifications
+ * @return void
+ */
+ public function __construct(array $array, $allowModifications = false)
+ {
+ $this->_allowModifications = (boolean) $allowModifications;
+ $this->_loadedSection = null;
+ $this->_index = 0;
+ $this->_data = array();
+ foreach ($array as $key => $value) {
+ if (is_array($value)) {
+ $this->_data[$key] = new self($value, $this->_allowModifications);
+ } else {
+ $this->_data[$key] = $value;
+ }
+ }
+ $this->_count = count($this->_data);
+ }
+
+ /**
+ * Retrieve a value and return $default if there is no element set.
+ *
+ * @param string $name
+ * @param mixed $default
+ * @return mixed
+ */
+ public function get($name, $default = null)
+ {
+ $result = $default;
+ if (array_key_exists($name, $this->_data)) {
+ $result = $this->_data[$name];
+ }
+ return $result;
+ }
+
+ /**
+ * Magic function so that $obj->value will work.
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Only allow setting of a property if $allowModifications
+ * was set to true on construction. Otherwise, throw an exception.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @throws Zend_Config_Exception
+ * @return void
+ */
+ public function __set($name, $value)
+ {
+ if ($this->_allowModifications) {
+ if (is_array($value)) {
+ $this->_data[$name] = new self($value, true);
+ } else {
+ $this->_data[$name] = $value;
+ }
+ $this->_count = count($this->_data);
+ } else {
+ /** @see Zend_Config_Exception */
+ throw new Zend_Config_Exception('Zend_Config is read only');
+ }
+ }
+
+ /**
+ * Deep clone of this instance to ensure that nested Zend_Configs
+ * are also cloned.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $array = array();
+ foreach ($this->_data as $key => $value) {
+ if ($value instanceof Zend_Config) {
+ $array[$key] = clone $value;
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ $this->_data = $array;
+ }
+
+ /**
+ * Return an associative array of the stored data.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $array = array();
+ $data = $this->_data;
+ foreach ($data as $key => $value) {
+ if ($value instanceof Zend_Config) {
+ $array[$key] = $value->toArray();
+ } else {
+ $array[$key] = $value;
+ }
+ }
+ return $array;
+ }
+
+ /**
+ * Support isset() overloading on PHP 5.1
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ return isset($this->_data[$name]);
+ }
+
+ /**
+ * Support unset() overloading on PHP 5.1
+ *
+ * @param string $name
+ * @throws Zend_Config_Exception
+ * @return void
+ */
+ public function __unset($name)
+ {
+ if ($this->_allowModifications) {
+ unset($this->_data[$name]);
+ $this->_count = count($this->_data);
+ $this->_skipNextIteration = true;
+ } else {
+ /** @see Zend_Config_Exception */
+ throw new Zend_Config_Exception('Zend_Config is read only');
+ }
+
+ }
+
+ /**
+ * Defined by Countable interface
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->_count;
+ }
+
+ /**
+ * Defined by Iterator interface
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ $this->_skipNextIteration = false;
+ return current($this->_data);
+ }
+
+ /**
+ * Defined by Iterator interface
+ *
+ * @return mixed
+ */
+ public function key()
+ {
+ return key($this->_data);
+ }
+
+ /**
+ * Defined by Iterator interface
+ *
+ */
+ public function next()
+ {
+ if ($this->_skipNextIteration) {
+ $this->_skipNextIteration = false;
+ return;
+ }
+ next($this->_data);
+ $this->_index++;
+ }
+
+ /**
+ * Defined by Iterator interface
+ *
+ */
+ public function rewind()
+ {
+ $this->_skipNextIteration = false;
+ reset($this->_data);
+ $this->_index = 0;
+ }
+
+ /**
+ * Defined by Iterator interface
+ *
+ * @return boolean
+ */
+ public function valid()
+ {
+ return $this->_index < $this->_count;
+ }
+
+ /**
+ * Returns the section name(s) loaded.
+ *
+ * @return mixed
+ */
+ public function getSectionName()
+ {
+ if(is_array($this->_loadedSection) && count($this->_loadedSection) == 1) {
+ $this->_loadedSection = $this->_loadedSection[0];
+ }
+ return $this->_loadedSection;
+ }
+
+ /**
+ * Returns true if all sections were loaded
+ *
+ * @return boolean
+ */
+ public function areAllSectionsLoaded()
+ {
+ return $this->_loadedSection === null;
+ }
+
+
+ /**
+ * Merge another Zend_Config with this one. The items
+ * in $merge will override the same named items in
+ * the current config.
+ *
+ * @param Zend_Config $merge
+ * @return Zend_Config
+ */
+ public function merge(Zend_Config $merge)
+ {
+ foreach($merge as $key => $item) {
+ if(array_key_exists($key, $this->_data)) {
+ if($item instanceof Zend_Config && $this->$key instanceof Zend_Config) {
+ $this->$key = $this->$key->merge(new Zend_Config($item->toArray(), !$this->readOnly()));
+ } else {
+ $this->$key = $item;
+ }
+ } else {
+ if($item instanceof Zend_Config) {
+ $this->$key = new Zend_Config($item->toArray(), !$this->readOnly());
+ } else {
+ $this->$key = $item;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Prevent any more modifications being made to this instance. Useful
+ * after merge() has been used to merge multiple Zend_Config objects
+ * into one object which should then not be modified again.
+ *
+ */
+ public function setReadOnly()
+ {
+ $this->_allowModifications = false;
+ foreach ($this->_data as $key => $value) {
+ if ($value instanceof Zend_Config) {
+ $value->setReadOnly();
+ }
+ }
+ }
+
+ /**
+ * Returns if this Zend_Config object is read only or not.
+ *
+ * @return boolean
+ */
+ public function readOnly()
+ {
+ return !$this->_allowModifications;
+ }
+
+ /**
+ * Get the current extends
+ *
+ * @return array
+ */
+ public function getExtends()
+ {
+ return $this->_extends;
+ }
+
+ /**
+ * Set an extend for Zend_Config_Writer
+ *
+ * @param string $extendingSection
+ * @param string $extendedSection
+ * @return void
+ */
+ public function setExtend($extendingSection, $extendedSection = null)
+ {
+ if ($extendedSection === null && isset($this->_extends[$extendingSection])) {
+ unset($this->_extends[$extendingSection]);
+ } else if ($extendedSection !== null) {
+ $this->_extends[$extendingSection] = $extendedSection;
+ }
+ }
+
+ /**
+ * Throws an exception if $extendingSection may not extend $extendedSection,
+ * and tracks the section extension if it is valid.
+ *
+ * @param string $extendingSection
+ * @param string $extendedSection
+ * @throws Zend_Config_Exception
+ * @return void
+ */
+ protected function _assertValidExtend($extendingSection, $extendedSection)
+ {
+ // detect circular section inheritance
+ $extendedSectionCurrent = $extendedSection;
+ while (array_key_exists($extendedSectionCurrent, $this->_extends)) {
+ if ($this->_extends[$extendedSectionCurrent] == $extendingSection) {
+ /** @see Zend_Config_Exception */
+ throw new Zend_Config_Exception('Illegal circular inheritance detected');
+ }
+ $extendedSectionCurrent = $this->_extends[$extendedSectionCurrent];
+ }
+ // remember that this section extends another section
+ $this->_extends[$extendingSection] = $extendedSection;
+ }
+
+ /**
+ * Handle any errors from simplexml_load_file or parse_ini_file
+ *
+ * @param integer $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param integer $errline
+ */
+ public function _loadFileErrorHandler($errno, $errstr, $errfile, $errline)
+ {
+ if ($this->_loadFileErrorStr === null) {
+ $this->_loadFileErrorStr = $errstr;
+ } else {
+ $this->_loadFileErrorStr .= (PHP_EOL . $errstr);
+ }
+ }
+
+ /**
+ * Merge two arrays recursively, overwriting keys of the same name
+ * in $firstArray with the value in $secondArray.
+ *
+ * @param mixed $firstArray First array
+ * @param mixed $secondArray Second array to merge into first array
+ * @return array
+ */
+ protected function _arrayMergeRecursive($firstArray, $secondArray)
+ {
+ if (is_array($firstArray) && is_array($secondArray)) {
+ foreach ($secondArray as $key => $value) {
+ if (isset($firstArray[$key])) {
+ $firstArray[$key] = $this->_arrayMergeRecursive($firstArray[$key], $value);
+ } else {
+ if($key === 0) {
+ $firstArray= array(0=>$this->_arrayMergeRecursive($firstArray, $value));
+ } else {
+ $firstArray[$key] = $value;
+ }
+ }
+ }
+ } else {
+ $firstArray = $secondArray;
+ }
+
+ return $firstArray;
+ }
+}
diff --git a/library/vendor/Zend/Config/Exception.php b/library/vendor/Zend/Config/Exception.php
new file mode 100644
index 0000000..abffe5b
--- /dev/null
+++ b/library/vendor/Zend/Config/Exception.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Config_Exception extends Zend_Exception {}
diff --git a/library/vendor/Zend/Config/Ini.php b/library/vendor/Zend/Config/Ini.php
new file mode 100644
index 0000000..55ca56f
--- /dev/null
+++ b/library/vendor/Zend/Config/Ini.php
@@ -0,0 +1,301 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Config
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Config_Ini extends Zend_Config
+{
+ /**
+ * String that separates nesting levels of configuration data identifiers
+ *
+ * @var string
+ */
+ protected $_nestSeparator = '.';
+
+ /**
+ * String that separates the parent section name
+ *
+ * @var string
+ */
+ protected $_sectionSeparator = ':';
+
+ /**
+ * Whether to skip extends or not
+ *
+ * @var boolean
+ */
+ protected $_skipExtends = false;
+
+ /**
+ * Loads the section $section from the config file $filename for
+ * access facilitated by nested object properties.
+ *
+ * If the section name contains a ":" then the section name to the right
+ * is loaded and included into the properties. Note that the keys in
+ * this $section will override any keys of the same
+ * name in the sections that have been included via ":".
+ *
+ * If the $section is null, then all sections in the ini file are loaded.
+ *
+ * If any key includes a ".", then this will act as a separator to
+ * create a sub-property.
+ *
+ * example ini file:
+ * [all]
+ * db.connection = database
+ * hostname = live
+ *
+ * [staging : all]
+ * hostname = staging
+ *
+ * after calling $data = new Zend_Config_Ini($file, 'staging'); then
+ * $data->hostname === "staging"
+ * $data->db->connection === "database"
+ *
+ * The $options parameter may be provided as either a boolean or an array.
+ * If provided as a boolean, this sets the $allowModifications option of
+ * Zend_Config. If provided as an array, there are three configuration
+ * directives that may be set. For example:
+ *
+ * $options = array(
+ * 'allowModifications' => false,
+ * 'nestSeparator' => ':',
+ * 'skipExtends' => false,
+ * );
+ *
+ * @param string $filename
+ * @param mixed $section
+ * @param boolean|array $options
+ * @throws Zend_Config_Exception
+ * @return void
+ */
+ public function __construct($filename, $section = null, $options = false)
+ {
+ if (empty($filename)) {
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception('Filename is not set');
+ }
+
+ $allowModifications = false;
+ if (is_bool($options)) {
+ $allowModifications = $options;
+ } elseif (is_array($options)) {
+ if (isset($options['allowModifications'])) {
+ $allowModifications = (bool) $options['allowModifications'];
+ }
+ if (isset($options['nestSeparator'])) {
+ $this->_nestSeparator = (string) $options['nestSeparator'];
+ }
+ if (isset($options['skipExtends'])) {
+ $this->_skipExtends = (bool) $options['skipExtends'];
+ }
+ }
+
+ $iniArray = $this->_loadIniFile($filename);
+
+ if (null === $section) {
+ // Load entire file
+ $dataArray = array();
+ foreach ($iniArray as $sectionName => $sectionData) {
+ if(!is_array($sectionData)) {
+ $dataArray = $this->_arrayMergeRecursive($dataArray, $this->_processKey(array(), $sectionName, $sectionData));
+ } else {
+ $dataArray[$sectionName] = $this->_processSection($iniArray, $sectionName);
+ }
+ }
+ parent::__construct($dataArray, $allowModifications);
+ } else {
+ // Load one or more sections
+ if (!is_array($section)) {
+ $section = array($section);
+ }
+ $dataArray = array();
+ foreach ($section as $sectionName) {
+ if (!isset($iniArray[$sectionName])) {
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception("Section '$sectionName' cannot be found in $filename");
+ }
+ $dataArray = $this->_arrayMergeRecursive($this->_processSection($iniArray, $sectionName), $dataArray);
+
+ }
+ parent::__construct($dataArray, $allowModifications);
+ }
+
+ $this->_loadedSection = $section;
+ }
+
+ /**
+ * Load the INI file from disk using parse_ini_file(). Use a private error
+ * handler to convert any loading errors into a Zend_Config_Exception
+ *
+ * @param string $filename
+ * @throws Zend_Config_Exception
+ * @return array
+ */
+ protected function _parseIniFile($filename)
+ {
+ set_error_handler(array($this, '_loadFileErrorHandler'));
+ $iniArray = parse_ini_file($filename, true); // Warnings and errors are suppressed
+ restore_error_handler();
+
+ // Check if there was a error while loading file
+ if ($this->_loadFileErrorStr !== null) {
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception($this->_loadFileErrorStr);
+ }
+
+ return $iniArray;
+ }
+
+ /**
+ * Load the ini file and preprocess the section separator (':' in the
+ * section name (that is used for section extension) so that the resultant
+ * array has the correct section names and the extension information is
+ * stored in a sub-key called ';extends'. We use ';extends' as this can
+ * never be a valid key name in an INI file that has been loaded using
+ * parse_ini_file().
+ *
+ * @param string $filename
+ * @throws Zend_Config_Exception
+ * @return array
+ */
+ protected function _loadIniFile($filename)
+ {
+ $loaded = $this->_parseIniFile($filename);
+ $iniArray = array();
+ foreach ($loaded as $key => $data)
+ {
+ $pieces = explode($this->_sectionSeparator, $key);
+ $thisSection = trim($pieces[0]);
+ switch (count($pieces)) {
+ case 1:
+ $iniArray[$thisSection] = $data;
+ break;
+
+ case 2:
+ $extendedSection = trim($pieces[1]);
+ $iniArray[$thisSection] = array_merge(array(';extends'=>$extendedSection), $data);
+ break;
+
+ default:
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception("Section '$thisSection' may not extend multiple sections in $filename");
+ }
+ }
+
+ return $iniArray;
+ }
+
+ /**
+ * Process each element in the section and handle the ";extends" inheritance
+ * key. Passes control to _processKey() to handle the nest separator
+ * sub-property syntax that may be used within the key name.
+ *
+ * @param array $iniArray
+ * @param string $section
+ * @param array $config
+ * @throws Zend_Config_Exception
+ * @return array
+ */
+ protected function _processSection($iniArray, $section, $config = array())
+ {
+ $thisSection = $iniArray[$section];
+
+ foreach ($thisSection as $key => $value) {
+ if (strtolower($key) == ';extends') {
+ if (isset($iniArray[$value])) {
+ $this->_assertValidExtend($section, $value);
+
+ if (!$this->_skipExtends) {
+ $config = $this->_processSection($iniArray, $value, $config);
+ }
+ } else {
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception("Parent section '$section' cannot be found");
+ }
+ } else {
+ $config = $this->_processKey($config, $key, $value);
+ }
+ }
+ return $config;
+ }
+
+ /**
+ * Assign the key's value to the property list. Handles the
+ * nest separator for sub-properties.
+ *
+ * @param array $config
+ * @param string $key
+ * @param string $value
+ * @throws Zend_Config_Exception
+ * @return array
+ */
+ protected function _processKey($config, $key, $value)
+ {
+ if (strpos($key, $this->_nestSeparator) !== false) {
+ $pieces = explode($this->_nestSeparator, $key, 2);
+ if (strlen($pieces[0]) && strlen($pieces[1])) {
+ if (!isset($config[$pieces[0]])) {
+ if ($pieces[0] === '0' && !empty($config)) {
+ // convert the current values in $config into an array
+ $config = array($pieces[0] => $config);
+ } else {
+ $config[$pieces[0]] = array();
+ }
+ } elseif (!is_array($config[$pieces[0]])) {
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception("Cannot create sub-key for '{$pieces[0]}' as key already exists");
+ }
+ $config[$pieces[0]] = $this->_processKey($config[$pieces[0]], $pieces[1], $value);
+ } else {
+ /**
+ * @see Zend_Config_Exception
+ */
+ throw new Zend_Config_Exception("Invalid key '$key'");
+ }
+ } else {
+ $config[$key] = $value;
+ }
+ return $config;
+ }
+}
diff --git a/library/vendor/Zend/Config/Writer.php b/library/vendor/Zend/Config/Writer.php
new file mode 100644
index 0000000..8c255b3
--- /dev/null
+++ b/library/vendor/Zend/Config/Writer.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Config_Writer
+{
+ /**
+ * Option keys to skip when calling setOptions()
+ *
+ * @var array
+ */
+ protected $_skipOptions = array(
+ 'options'
+ );
+
+ /**
+ * Config object to write
+ *
+ * @var Zend_Config
+ */
+ protected $_config = null;
+
+ /**
+ * Create a new adapter
+ *
+ * $options can only be passed as array or be omitted
+ *
+ * @param null|array $options
+ */
+ public function __construct(array $options = null)
+ {
+ if (is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set options via a Zend_Config instance
+ *
+ * @param Zend_Config $config
+ * @return Zend_Config_Writer
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ $this->_config = $config;
+
+ return $this;
+ }
+
+ /**
+ * Set options via an array
+ *
+ * @param array $options
+ * @return Zend_Config_Writer
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ if (in_array(strtolower($key), $this->_skipOptions)) {
+ continue;
+ }
+
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Write a Zend_Config object to it's target
+ *
+ * @return void
+ */
+ abstract public function write();
+}
diff --git a/library/vendor/Zend/Config/Writer/Array.php b/library/vendor/Zend/Config/Writer/Array.php
new file mode 100644
index 0000000..f7cd502
--- /dev/null
+++ b/library/vendor/Zend/Config/Writer/Array.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Config_Writer
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Config_Writer_Array extends Zend_Config_Writer_FileAbstract
+{
+ /**
+ * Render a Zend_Config into a PHP Array config string.
+ *
+ * @since 1.10
+ * @return string
+ */
+ public function render()
+ {
+ $data = $this->_config->toArray();
+ $sectionName = $this->_config->getSectionName();
+
+ if (is_string($sectionName)) {
+ $data = array($sectionName => $data);
+ }
+
+ $arrayString = "<?php\n"
+ . "return " . var_export($data, true) . ";\n";
+
+ return $arrayString;
+ }
+}
diff --git a/library/vendor/Zend/Config/Writer/FileAbstract.php b/library/vendor/Zend/Config/Writer/FileAbstract.php
new file mode 100644
index 0000000..242b49d
--- /dev/null
+++ b/library/vendor/Zend/Config/Writer/FileAbstract.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @package Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Abstract File Writer
+ *
+ * @category Zend
+ * @package Zend_package
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Config_Writer_FileAbstract extends Zend_Config_Writer
+{
+ /**
+ * Filename to write to
+ *
+ * @var string
+ */
+ protected $_filename = null;
+
+ /**
+ * Wether to exclusively lock the file or not
+ *
+ * @var boolean
+ */
+ protected $_exclusiveLock = false;
+
+ /**
+ * Set the target filename
+ *
+ * @param string $filename
+ * @return Zend_Config_Writer_Array
+ */
+ public function setFilename($filename)
+ {
+ $this->_filename = $filename;
+
+ return $this;
+ }
+
+ /**
+ * Set wether to exclusively lock the file or not
+ *
+ * @param boolean $exclusiveLock
+ * @return Zend_Config_Writer_Array
+ */
+ public function setExclusiveLock($exclusiveLock)
+ {
+ $this->_exclusiveLock = $exclusiveLock;
+
+ return $this;
+ }
+
+ /**
+ * Write configuration to file.
+ *
+ * @param string $filename
+ * @param Zend_Config $config
+ * @param bool $exclusiveLock
+ * @return void
+ */
+ public function write($filename = null, Zend_Config $config = null, $exclusiveLock = null)
+ {
+ if ($filename !== null) {
+ $this->setFilename($filename);
+ }
+
+ if ($config !== null) {
+ $this->setConfig($config);
+ }
+
+ if ($exclusiveLock !== null) {
+ $this->setExclusiveLock($exclusiveLock);
+ }
+
+ if ($this->_filename === null) {
+ throw new Zend_Config_Exception('No filename was set');
+ }
+
+ if ($this->_config === null) {
+ throw new Zend_Config_Exception('No config was set');
+ }
+
+ $configString = $this->render();
+
+ $flags = 0;
+
+ if ($this->_exclusiveLock) {
+ $flags |= LOCK_EX;
+ }
+
+ $result = @file_put_contents($this->_filename, $configString, $flags);
+
+ if ($result === false) {
+ throw new Zend_Config_Exception('Could not write to file "' . $this->_filename . '"');
+ }
+ }
+
+ /**
+ * Render a Zend_Config into a config file string.
+ *
+ * @since 1.10
+ * @todo For 2.0 this should be redone into an abstract method.
+ * @return string
+ */
+ public function render()
+ {
+ return "";
+ }
+}
diff --git a/library/vendor/Zend/Config/Writer/Ini.php b/library/vendor/Zend/Config/Writer/Ini.php
new file mode 100644
index 0000000..67aaf89
--- /dev/null
+++ b/library/vendor/Zend/Config/Writer/Ini.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Config_Writer
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Config
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Config_Writer_Ini extends Zend_Config_Writer_FileAbstract
+{
+ /**
+ * String that separates nesting levels of configuration data identifiers
+ *
+ * @var string
+ */
+ protected $_nestSeparator = '.';
+
+ /**
+ * If true the ini string is rendered in the global namespace without sections.
+ *
+ * @var bool
+ */
+ protected $_renderWithoutSections = false;
+
+ /**
+ * Set the nest separator
+ *
+ * @param string $filename
+ * @return Zend_Config_Writer_Ini
+ */
+ public function setNestSeparator($separator)
+ {
+ $this->_nestSeparator = $separator;
+
+ return $this;
+ }
+
+ /**
+ * Set if rendering should occour without sections or not.
+ *
+ * If set to true, the INI file is rendered without sections completely
+ * into the global namespace of the INI file.
+ *
+ * @param bool $withoutSections
+ * @return Zend_Config_Writer_Ini
+ */
+ public function setRenderWithoutSections($withoutSections=true)
+ {
+ $this->_renderWithoutSections = (bool)$withoutSections;
+ return $this;
+ }
+
+ /**
+ * Render a Zend_Config into a INI config string.
+ *
+ * @since 1.10
+ * @return string
+ */
+ public function render()
+ {
+ $iniString = '';
+ $extends = $this->_config->getExtends();
+ $sectionName = $this->_config->getSectionName();
+
+ if($this->_renderWithoutSections == true) {
+ $iniString .= $this->_addBranch($this->_config);
+ } else if (is_string($sectionName)) {
+ $iniString .= '[' . $sectionName . ']' . "\n"
+ . $this->_addBranch($this->_config)
+ . "\n";
+ } else {
+ $config = $this->_sortRootElements($this->_config);
+ foreach ($config as $sectionName => $data) {
+ if (!($data instanceof Zend_Config)) {
+ $iniString .= $sectionName
+ . ' = '
+ . $this->_prepareValue($data)
+ . "\n";
+ } else {
+ if (isset($extends[$sectionName])) {
+ $sectionName .= ' : ' . $extends[$sectionName];
+ }
+
+ $iniString .= '[' . $sectionName . ']' . "\n"
+ . $this->_addBranch($data)
+ . "\n";
+ }
+ }
+ }
+
+ return $iniString;
+ }
+
+ /**
+ * Add a branch to an INI string recursively
+ *
+ * @param Zend_Config $config
+ * @return void
+ */
+ protected function _addBranch(Zend_Config $config, $parents = array())
+ {
+ $iniString = '';
+
+ foreach ($config as $key => $value) {
+ $group = array_merge($parents, array($key));
+
+ if ($value instanceof Zend_Config) {
+ $iniString .= $this->_addBranch($value, $group);
+ } else {
+ $iniString .= implode($this->_nestSeparator, $group)
+ . ' = '
+ . $this->_prepareValue($value)
+ . "\n";
+ }
+ }
+
+ return $iniString;
+ }
+
+ /**
+ * Prepare a value for INI
+ *
+ * @param mixed $value
+ * @return string
+ */
+ protected function _prepareValue($value)
+ {
+ if (is_integer($value) || is_float($value)) {
+ return $value;
+ } elseif (is_bool($value)) {
+ return ($value ? 'true' : 'false');
+ } elseif (strpos($value, '"') === false) {
+ return '"' . $value . '"';
+ } else {
+ /** @see Zend_Config_Exception */
+ throw new Zend_Config_Exception('Value can not contain double quotes "');
+ }
+ }
+
+ /**
+ * Root elements that are not assigned to any section needs to be
+ * on the top of config.
+ *
+ * @see http://framework.zend.com/issues/browse/ZF-6289
+ * @param Zend_Config
+ * @return Zend_Config
+ */
+ protected function _sortRootElements(Zend_Config $config)
+ {
+ $configArray = $config->toArray();
+ $sections = array();
+
+ // remove sections from config array
+ foreach ($configArray as $key => $value) {
+ if (is_array($value)) {
+ $sections[$key] = $value;
+ unset($configArray[$key]);
+ }
+ }
+
+ // readd sections to the end
+ foreach ($sections as $key => $value) {
+ $configArray[$key] = $value;
+ }
+
+ return new Zend_Config($configArray);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action.php b/library/vendor/Zend/Controller/Action.php
new file mode 100644
index 0000000..35c450a
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action.php
@@ -0,0 +1,789 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_HelperBroker
+ */
+
+/**
+ * @see Zend_Controller_Action_Interface
+ */
+
+/**
+ * @see Zend_Controller_Front
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Action implements Zend_Controller_Action_Interface
+{
+ /**
+ * @var array of existing class methods
+ */
+ protected $_classMethods;
+
+ /**
+ * Word delimiters (used for normalizing view script paths)
+ * @var array
+ */
+ protected $_delimiters;
+
+ /**
+ * Array of arguments provided to the constructor, minus the
+ * {@link $_request Request object}.
+ * @var array
+ */
+ protected $_invokeArgs = array();
+
+ /**
+ * Front controller instance
+ * @var Zend_Controller_Front
+ */
+ protected $_frontController;
+
+ /**
+ * Zend_Controller_Request_Abstract object wrapping the request environment
+ * @var Zend_Controller_Request_Abstract
+ */
+ protected $_request = null;
+
+ /**
+ * Zend_Controller_Response_Abstract object wrapping the response
+ * @var Zend_Controller_Response_Abstract
+ */
+ protected $_response = null;
+
+ /**
+ * View script suffix; defaults to 'phtml'
+ * @see {render()}
+ * @var string
+ */
+ public $viewSuffix = 'phtml';
+
+ /**
+ * View object
+ * @var Zend_View_Interface
+ */
+ public $view;
+
+ /**
+ * Helper Broker to assist in routing help requests to the proper object
+ *
+ * @var Zend_Controller_Action_HelperBroker
+ */
+ protected $_helper = null;
+
+ /**
+ * Class constructor
+ *
+ * The request and response objects should be registered with the
+ * controller, as should be any additional optional arguments; these will be
+ * available via {@link getRequest()}, {@link getResponse()}, and
+ * {@link getInvokeArgs()}, respectively.
+ *
+ * When overriding the constructor, please consider this usage as a best
+ * practice and ensure that each is registered appropriately; the easiest
+ * way to do so is to simply call parent::__construct($request, $response,
+ * $invokeArgs).
+ *
+ * After the request, response, and invokeArgs are set, the
+ * {@link $_helper helper broker} is initialized.
+ *
+ * Finally, {@link init()} is called as the final action of
+ * instantiation, and may be safely overridden to perform initialization
+ * tasks; as a general rule, override {@link init()} instead of the
+ * constructor to customize an action controller's instantiation.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param Zend_Controller_Response_Abstract $response
+ * @param array $invokeArgs Any additional invocation arguments
+ * @return void
+ */
+ public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array())
+ {
+ $this->setRequest($request)
+ ->setResponse($response)
+ ->_setInvokeArgs($invokeArgs);
+ $this->_helper = new Zend_Controller_Action_HelperBroker($this);
+ $this->init();
+ }
+
+ /**
+ * Initialize object
+ *
+ * Called from {@link __construct()} as final step of object instantiation.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Initialize View object
+ *
+ * Initializes {@link $view} if not otherwise a Zend_View_Interface.
+ *
+ * If {@link $view} is not otherwise set, instantiates a new Zend_View
+ * object, using the 'views' subdirectory at the same level as the
+ * controller directory for the current module as the base directory.
+ * It uses this to set the following:
+ * - script path = views/scripts/
+ * - helper path = views/helpers/
+ * - filter path = views/filters/
+ *
+ * @return Zend_View_Interface
+ * @throws Zend_Controller_Exception if base view directory does not exist
+ */
+ public function initView()
+ {
+ if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
+ return $this->view;
+ }
+
+ if (isset($this->view) && ($this->view instanceof Zend_View_Interface)) {
+ return $this->view;
+ }
+
+ $request = $this->getRequest();
+ $module = $request->getModuleName();
+ $dirs = $this->getFrontController()->getControllerDirectory();
+ if (empty($module) || !isset($dirs[$module])) {
+ $module = $this->getFrontController()->getDispatcher()->getDefaultModule();
+ }
+ $baseDir = dirname($dirs[$module]) . DIRECTORY_SEPARATOR . 'views';
+ if (!file_exists($baseDir) || !is_dir($baseDir)) {
+ throw new Zend_Controller_Exception('Missing base view directory ("' . $baseDir . '")');
+ }
+
+ $this->view = new Zend_View(array('basePath' => $baseDir));
+
+ return $this->view;
+ }
+
+ /**
+ * Render a view
+ *
+ * Renders a view. By default, views are found in the view script path as
+ * <controller>/<action>.phtml. You may change the script suffix by
+ * resetting {@link $viewSuffix}. You may omit the controller directory
+ * prefix by specifying boolean true for $noController.
+ *
+ * By default, the rendered contents are appended to the response. You may
+ * specify the named body content segment to set by specifying a $name.
+ *
+ * @see Zend_Controller_Response_Abstract::appendBody()
+ * @param string|null $action Defaults to action registered in request object
+ * @param string|null $name Response object named path segment to use; defaults to null
+ * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script
+ * @return void
+ */
+ public function render($action = null, $name = null, $noController = false)
+ {
+ if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
+ return $this->_helper->viewRenderer->render($action, $name, $noController);
+ }
+
+ $view = $this->initView();
+ $script = $this->getViewScript($action, $noController);
+
+ $this->getResponse()->appendBody(
+ $view->render($script),
+ $name
+ );
+ }
+
+ /**
+ * Render a given view script
+ *
+ * Similar to {@link render()}, this method renders a view script. Unlike render(),
+ * however, it does not autodetermine the view script via {@link getViewScript()},
+ * but instead renders the script passed to it. Use this if you know the
+ * exact view script name and path you wish to use, or if using paths that do not
+ * conform to the spec defined with getViewScript().
+ *
+ * By default, the rendered contents are appended to the response. You may
+ * specify the named body content segment to set by specifying a $name.
+ *
+ * @param string $script
+ * @param string $name
+ * @return void
+ */
+ public function renderScript($script, $name = null)
+ {
+ if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
+ return $this->_helper->viewRenderer->renderScript($script, $name);
+ }
+
+ $view = $this->initView();
+ $this->getResponse()->appendBody(
+ $view->render($script),
+ $name
+ );
+ }
+
+ /**
+ * Construct view script path
+ *
+ * Used by render() to determine the path to the view script.
+ *
+ * @param string $action Defaults to action registered in request object
+ * @param bool $noController Defaults to false; i.e. use controller name as subdir in which to search for view script
+ * @return string
+ * @throws Zend_Controller_Exception with bad $action
+ */
+ public function getViewScript($action = null, $noController = null)
+ {
+ if (!$this->getInvokeArg('noViewRenderer') && $this->_helper->hasHelper('viewRenderer')) {
+ $viewRenderer = $this->_helper->getHelper('viewRenderer');
+ if (null !== $noController) {
+ $viewRenderer->setNoController($noController);
+ }
+ return $viewRenderer->getViewScript($action);
+ }
+
+ $request = $this->getRequest();
+ if (null === $action) {
+ $action = $request->getActionName();
+ } elseif (!is_string($action)) {
+ throw new Zend_Controller_Exception('Invalid action specifier for view render');
+ }
+
+ if (null === $this->_delimiters) {
+ $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
+ $wordDelimiters = $dispatcher->getWordDelimiter();
+ $pathDelimiters = $dispatcher->getPathDelimiter();
+ $this->_delimiters = array_unique(array_merge($wordDelimiters, (array) $pathDelimiters));
+ }
+
+ $action = str_replace($this->_delimiters, '-', $action);
+ $script = $action . '.' . $this->viewSuffix;
+
+ if (!$noController) {
+ $controller = $request->getControllerName();
+ $controller = str_replace($this->_delimiters, '-', $controller);
+ $script = $controller . DIRECTORY_SEPARATOR . $script;
+ }
+
+ return $script;
+ }
+
+ /**
+ * Return the Request object
+ *
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+ /**
+ * Set the Request object
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return Zend_Controller_Action
+ */
+ public function setRequest(Zend_Controller_Request_Abstract $request)
+ {
+ $this->_request = $request;
+ return $this;
+ }
+
+ /**
+ * Return the Response object
+ *
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Set the Response object
+ *
+ * @param Zend_Controller_Response_Abstract $response
+ * @return Zend_Controller_Action
+ */
+ public function setResponse(Zend_Controller_Response_Abstract $response)
+ {
+ $this->_response = $response;
+ return $this;
+ }
+
+ /**
+ * Set invocation arguments
+ *
+ * @param array $args
+ * @return Zend_Controller_Action
+ */
+ protected function _setInvokeArgs(array $args = array())
+ {
+ $this->_invokeArgs = $args;
+ return $this;
+ }
+
+ /**
+ * Return the array of constructor arguments (minus the Request object)
+ *
+ * @return array
+ */
+ public function getInvokeArgs()
+ {
+ return $this->_invokeArgs;
+ }
+
+ /**
+ * Return a single invocation argument
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getInvokeArg($key)
+ {
+ if (isset($this->_invokeArgs[$key])) {
+ return $this->_invokeArgs[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get a helper by name
+ *
+ * @param string $helperName
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function getHelper($helperName)
+ {
+ return $this->_helper->{$helperName};
+ }
+
+ /**
+ * Get a clone of a helper by name
+ *
+ * @param string $helperName
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function getHelperCopy($helperName)
+ {
+ return clone $this->_helper->{$helperName};
+ }
+
+ /**
+ * Set the front controller instance
+ *
+ * @param Zend_Controller_Front $front
+ * @return Zend_Controller_Action
+ */
+ public function setFrontController(Zend_Controller_Front $front)
+ {
+ $this->_frontController = $front;
+ return $this;
+ }
+
+ /**
+ * Retrieve Front Controller
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController()
+ {
+ // Used cache version if found
+ if (null !== $this->_frontController) {
+ return $this->_frontController;
+ }
+
+ // Grab singleton instance, if class has been loaded
+ if (class_exists('Zend_Controller_Front')) {
+ $this->_frontController = Zend_Controller_Front::getInstance();
+ return $this->_frontController;
+ }
+
+ // Throw exception in all other cases
+ throw new Zend_Controller_Exception('Front controller class has not been loaded');
+ }
+
+ /**
+ * Pre-dispatch routines
+ *
+ * Called before action method. If using class with
+ * {@link Zend_Controller_Front}, it may modify the
+ * {@link $_request Request object} and reset its dispatched flag in order
+ * to skip processing the current action.
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ }
+
+ /**
+ * Post-dispatch routines
+ *
+ * Called after action method execution. If using class with
+ * {@link Zend_Controller_Front}, it may modify the
+ * {@link $_request Request object} and reset its dispatched flag in order
+ * to process an additional action.
+ *
+ * Common usages for postDispatch() include rendering content in a sitewide
+ * template, link url correction, setting headers, etc.
+ *
+ * @return void
+ */
+ public function postDispatch()
+ {
+ }
+
+ /**
+ * Proxy for undefined methods. Default behavior is to throw an
+ * exception on undefined methods, however this function can be
+ * overridden to implement magic (dynamic) actions, or provide run-time
+ * dispatching.
+ *
+ * @param string $methodName
+ * @param array $args
+ * @return void
+ * @throws Zend_Controller_Action_Exception
+ */
+ public function __call($methodName, $args)
+ {
+ if ('Action' == substr($methodName, -6)) {
+ $action = substr($methodName, 0, strlen($methodName) - 6);
+ throw new Zend_Controller_Action_Exception(sprintf('Action "%s" does not exist and was not trapped in __call()', $action), 404);
+ }
+
+ throw new Zend_Controller_Action_Exception(sprintf('Method "%s" does not exist and was not trapped in __call()', $methodName), 500);
+ }
+
+ /**
+ * Dispatch the requested action
+ *
+ * @param string $action Method name of action
+ * @return void
+ */
+ public function dispatch($action)
+ {
+ // Notify helpers of action preDispatch state
+ $this->_helper->notifyPreDispatch();
+
+ $this->preDispatch();
+ if ($this->getRequest()->isDispatched()) {
+ if (null === $this->_classMethods) {
+ $this->_classMethods = get_class_methods($this);
+ }
+
+ // If pre-dispatch hooks introduced a redirect then stop dispatch
+ // @see ZF-7496
+ if (!($this->getResponse()->isRedirect())) {
+ // preDispatch() didn't change the action, so we can continue
+ if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) {
+ if ($this->getInvokeArg('useCaseSensitiveActions')) {
+ trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"');
+ }
+ $this->$action();
+ } else {
+ $this->__call($action, array());
+ }
+ }
+ $this->postDispatch();
+ }
+
+ // whats actually important here is that this action controller is
+ // shutting down, regardless of dispatching; notify the helpers of this
+ // state
+ $this->_helper->notifyPostDispatch();
+ }
+
+ /**
+ * Call the action specified in the request object, and return a response
+ *
+ * Not used in the Action Controller implementation, but left for usage in
+ * Page Controller implementations. Dispatches a method based on the
+ * request.
+ *
+ * Returns a Zend_Controller_Response_Abstract object, instantiating one
+ * prior to execution if none exists in the controller.
+ *
+ * {@link preDispatch()} is called prior to the action,
+ * {@link postDispatch()} is called following it.
+ *
+ * @param null|Zend_Controller_Request_Abstract $request Optional request
+ * object to use
+ * @param null|Zend_Controller_Response_Abstract $response Optional response
+ * object to use
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null)
+ {
+ if (null !== $request) {
+ $this->setRequest($request);
+ } else {
+ $request = $this->getRequest();
+ }
+
+ if (null !== $response) {
+ $this->setResponse($response);
+ }
+
+ $action = $request->getActionName();
+ if (empty($action)) {
+ $action = 'index';
+ }
+ $action = $action . 'Action';
+
+ $request->setDispatched(true);
+ $this->dispatch($action);
+
+ return $this->getResponse();
+ }
+
+ /**
+ * Gets a parameter from the {@link $_request Request object}. If the
+ * parameter does not exist, NULL will be returned.
+ *
+ * If the parameter does not exist and $default is set, then
+ * $default will be returned instead of NULL.
+ *
+ * @param string $paramName
+ * @param mixed $default
+ * @return mixed
+ */
+ protected function _getParam($paramName, $default = null)
+ {
+ return $this->getParam($paramName, $default);
+ }
+
+ /**
+ * Gets a parameter from the {@link $_request Request object}. If the
+ * parameter does not exist, NULL will be returned.
+ *
+ * If the parameter does not exist and $default is set, then
+ * $default will be returned instead of NULL.
+ *
+ * @param string $paramName
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getParam($paramName, $default = null)
+ {
+ $value = $this->getRequest()->getParam($paramName);
+ if ((null === $value || '' === $value) && (null !== $default)) {
+ $value = $default;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Set a parameter in the {@link $_request Request object}.
+ *
+ * @param string $paramName
+ * @param mixed $value
+ * @return Zend_Controller_Action
+ * @deprecated Deprecated as of Zend Framework 1.7. Use
+ * setParam() instead.
+ */
+ protected function _setParam($paramName, $value)
+ {
+ return $this->setParam($paramName, $value);
+ }
+
+ /**
+ * Set a parameter in the {@link $_request Request object}.
+ *
+ * @param string $paramName
+ * @param mixed $value
+ * @return Zend_Controller_Action
+ */
+ public function setParam($paramName, $value)
+ {
+ $this->getRequest()->setParam($paramName, $value);
+
+ return $this;
+ }
+
+ /**
+ * Determine whether a given parameter exists in the
+ * {@link $_request Request object}.
+ *
+ * @param string $paramName
+ * @return boolean
+ * @deprecated Deprecated as of Zend Framework 1.7. Use
+ * hasParam() instead.
+ */
+ protected function _hasParam($paramName)
+ {
+ return $this->hasParam($paramName);
+ }
+
+ /**
+ * Determine whether a given parameter exists in the
+ * {@link $_request Request object}.
+ *
+ * @param string $paramName
+ * @return boolean
+ */
+ public function hasParam($paramName)
+ {
+ return null !== $this->getRequest()->getParam($paramName);
+ }
+
+ /**
+ * Return all parameters in the {@link $_request Request object}
+ * as an associative array.
+ *
+ * @return array
+ * @deprecated Deprecated as of Zend Framework 1.7. Use
+ * getAllParams() instead.
+ */
+ protected function _getAllParams()
+ {
+ return $this->getAllParams();
+ }
+
+ /**
+ * Return all parameters in the {@link $_request Request object}
+ * as an associative array.
+ *
+ * @return array
+ */
+ public function getAllParams()
+ {
+ return $this->getRequest()->getParams();
+ }
+
+
+ /**
+ * Forward to another controller/action.
+ *
+ * It is important to supply the unformatted names, i.e. "article"
+ * rather than "ArticleController". The dispatcher will do the
+ * appropriate formatting when the request is received.
+ *
+ * If only an action name is provided, forwards to that action in this
+ * controller.
+ *
+ * If an action and controller are specified, forwards to that action and
+ * controller in this module.
+ *
+ * Specifying an action, controller, and module is the most specific way to
+ * forward.
+ *
+ * A fourth argument, $params, will be used to set the request parameters.
+ * If either the controller or module are unnecessary for forwarding,
+ * simply pass null values for them before specifying the parameters.
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return void
+ * @deprecated Deprecated as of Zend Framework 1.7. Use
+ * forward() instead.
+ */
+ final protected function _forward($action, $controller = null, $module = null, array $params = null)
+ {
+ $this->forward($action, $controller, $module, $params);
+ }
+
+ /**
+ * Forward to another controller/action.
+ *
+ * It is important to supply the unformatted names, i.e. "article"
+ * rather than "ArticleController". The dispatcher will do the
+ * appropriate formatting when the request is received.
+ *
+ * If only an action name is provided, forwards to that action in this
+ * controller.
+ *
+ * If an action and controller are specified, forwards to that action and
+ * controller in this module.
+ *
+ * Specifying an action, controller, and module is the most specific way to
+ * forward.
+ *
+ * A fourth argument, $params, will be used to set the request parameters.
+ * If either the controller or module are unnecessary for forwarding,
+ * simply pass null values for them before specifying the parameters.
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return void
+ */
+ final public function forward($action, $controller = null, $module = null, array $params = null)
+ {
+ $request = $this->getRequest();
+
+ if (null !== $params) {
+ $request->setParams($params);
+ }
+
+ if (null !== $controller) {
+ $request->setControllerName($controller);
+
+ // Module should only be reset if controller has been specified
+ if (null !== $module) {
+ $request->setModuleName($module);
+ }
+ }
+
+ $request->setActionName($action)
+ ->setDispatched(false);
+ }
+
+ /**
+ * Redirect to another URL
+ *
+ * Proxies to {@link Zend_Controller_Action_Helper_Redirector::gotoUrl()}.
+ *
+ * @param string $url
+ * @param array $options Options to be used when redirecting
+ * @return void
+ * @deprecated Deprecated as of Zend Framework 1.7. Use
+ * redirect() instead.
+ */
+ protected function _redirect($url, array $options = array())
+ {
+ $this->redirect($url, $options);
+ }
+
+ /**
+ * Redirect to another URL
+ *
+ * Proxies to {@link Zend_Controller_Action_Helper_Redirector::gotoUrl()}.
+ *
+ * @param string $url
+ * @param array $options Options to be used when redirecting
+ * @return void
+ */
+ public function redirect($url, array $options = array())
+ {
+ $this->_helper->redirector->gotoUrl($url, $options);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Exception.php b/library/vendor/Zend/Controller/Action/Exception.php
new file mode 100644
index 0000000..1c3438d
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Controller_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Exception extends Zend_Controller_Exception
+{}
diff --git a/library/vendor/Zend/Controller/Action/Helper/Abstract.php b/library/vendor/Zend/Controller/Action/Helper/Abstract.php
new file mode 100644
index 0000000..e630be3
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/Abstract.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * $_actionController
+ *
+ * @var Zend_Controller_Action $_actionController
+ */
+ protected $_actionController = null;
+
+ /**
+ * @var mixed $_frontController
+ */
+ protected $_frontController = null;
+
+ /**
+ * setActionController()
+ *
+ * @param Zend_Controller_Action $actionController
+ * @return Zend_Controller_ActionHelper_Abstract Provides a fluent interface
+ */
+ public function setActionController(Zend_Controller_Action $actionController = null)
+ {
+ $this->_actionController = $actionController;
+ return $this;
+ }
+
+ /**
+ * Retrieve current action controller
+ *
+ * @return Zend_Controller_Action
+ */
+ public function getActionController()
+ {
+ return $this->_actionController;
+ }
+
+ /**
+ * Retrieve front controller instance
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController()
+ {
+ return Zend_Controller_Front::getInstance();
+ }
+
+ /**
+ * Hook into action controller initialization
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Hook into action controller preDispatch() workflow
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ }
+
+ /**
+ * Hook into action controller postDispatch() workflow
+ *
+ * @return void
+ */
+ public function postDispatch()
+ {
+ }
+
+ /**
+ * getRequest() -
+ *
+ * @return Zend_Controller_Request_Abstract $request
+ */
+ public function getRequest()
+ {
+ $controller = $this->getActionController();
+ if (null === $controller) {
+ $controller = $this->getFrontController();
+ }
+
+ return $controller->getRequest();
+ }
+
+ /**
+ * getResponse() -
+ *
+ * @return Zend_Controller_Response_Abstract $response
+ */
+ public function getResponse()
+ {
+ $controller = $this->getActionController();
+ if (null === $controller) {
+ $controller = $this->getFrontController();
+ }
+
+ return $controller->getResponse();
+ }
+
+ /**
+ * getName()
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ $fullClassName = get_class($this);
+ if (strpos($fullClassName, '_') !== false) {
+ $helperName = strrchr($fullClassName, '_');
+ return ltrim($helperName, '_');
+ } elseif (strpos($fullClassName, '\\') !== false) {
+ $helperName = strrchr($fullClassName, '\\');
+ return ltrim($helperName, '\\');
+ } else {
+ return $fullClassName;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/ActionStack.php b/library/vendor/Zend/Controller/Action/Helper/ActionStack.php
new file mode 100644
index 0000000..30fb633
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/ActionStack.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * Add to action stack
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_ActionStack extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * @var Zend_Controller_Plugin_ActionStack
+ */
+ protected $_actionStack;
+
+ /**
+ * Constructor
+ *
+ * Register action stack plugin
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $front = Zend_Controller_Front::getInstance();
+ if (!$front->hasPlugin('Zend_Controller_Plugin_ActionStack')) {
+ /**
+ * @see Zend_Controller_Plugin_ActionStack
+ */
+ $this->_actionStack = new Zend_Controller_Plugin_ActionStack();
+ $front->registerPlugin($this->_actionStack, 97);
+ } else {
+ $this->_actionStack = $front->getPlugin('Zend_Controller_Plugin_ActionStack');
+ }
+ }
+
+ /**
+ * Push onto the stack
+ *
+ * @param Zend_Controller_Request_Abstract $next
+ * @return Zend_Controller_Action_Helper_ActionStack Provides a fluent interface
+ */
+ public function pushStack(Zend_Controller_Request_Abstract $next)
+ {
+ $this->_actionStack->pushStack($next);
+ return $this;
+ }
+
+ /**
+ * Push a new action onto the stack
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @throws Zend_Controller_Action_Exception
+ * @return Zend_Controller_Action_Helper_ActionStack
+ */
+ public function actionToStack($action, $controller = null, $module = null, array $params = array())
+ {
+ if ($action instanceof Zend_Controller_Request_Abstract) {
+ return $this->pushStack($action);
+ } elseif (!is_string($action)) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('ActionStack requires either a request object or minimally a string action');
+ }
+
+ $request = $this->getRequest();
+
+ if ($request instanceof Zend_Controller_Request_Abstract === false){
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('Request object not set yet');
+ }
+
+ $controller = (null === $controller) ? $request->getControllerName() : $controller;
+ $module = (null === $module) ? $request->getModuleName() : $module;
+
+ /**
+ * @see Zend_Controller_Request_Simple
+ */
+ $newRequest = new Zend_Controller_Request_Simple($action, $controller, $module, $params);
+
+ return $this->pushStack($newRequest);
+ }
+
+ /**
+ * Perform helper when called as $this->_helper->actionStack() from an action controller
+ *
+ * Proxies to {@link simple()}
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return boolean
+ */
+ public function direct($action, $controller = null, $module = null, array $params = array())
+ {
+ return $this->actionToStack($action, $controller, $module, $params);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/AjaxContext.php b/library/vendor/Zend/Controller/Action/Helper/AjaxContext.php
new file mode 100644
index 0000000..3074a40
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/AjaxContext.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_ContextSwitch
+ */
+
+/**
+ * Simplify AJAX context switching based on requested format
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_AjaxContext extends Zend_Controller_Action_Helper_ContextSwitch
+{
+ /**
+ * Controller property to utilize for context switching
+ * @var string
+ */
+ protected $_contextKey = 'ajaxable';
+
+ /**
+ * Constructor
+ *
+ * Add HTML context
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->addContext('html', array('suffix' => 'ajax'));
+ }
+
+ /**
+ * Initialize AJAX context switching
+ *
+ * Checks for XHR requests; if detected, attempts to perform context switch.
+ *
+ * @param string $format
+ * @return void
+ */
+ public function initContext($format = null)
+ {
+ $this->_currentContext = null;
+
+ $request = $this->getRequest();
+ if (!method_exists($request, 'isXmlHttpRequest') ||
+ !$this->getRequest()->isXmlHttpRequest())
+ {
+ return;
+ }
+
+ return parent::initContext($format);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/AutoComplete/Abstract.php b/library/vendor/Zend/Controller/Action/Helper/AutoComplete/Abstract.php
new file mode 100644
index 0000000..7cd4ad0
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/AutoComplete/Abstract.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * Create and send autocompletion lists
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Action_Helper_AutoComplete_Abstract extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * Suppress exit when sendJson() called
+ *
+ * @var boolean
+ */
+ public $suppressExit = false;
+
+ /**
+ * Validate autocompletion data
+ *
+ * @param mixed $data
+ * @return boolean
+ */
+ abstract public function validateData($data);
+
+ /**
+ * Prepare autocompletion data
+ *
+ * @param mixed $data
+ * @param boolean $keepLayouts
+ * @return mixed
+ */
+ abstract public function prepareAutoCompletion($data, $keepLayouts = false);
+
+ /**
+ * Disable layouts and view renderer
+ *
+ * @return Zend_Controller_Action_Helper_AutoComplete_Abstract Provides a fluent interface
+ */
+ public function disableLayouts()
+ {
+ /**
+ * @see Zend_Layout
+ */
+ if (null !== ($layout = Zend_Layout::getMvcInstance())) {
+ $layout->disableLayout();
+ }
+
+ Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true);
+
+ return $this;
+ }
+
+ /**
+ * Encode data to JSON
+ *
+ * @param mixed $data
+ * @param bool $keepLayouts
+ * @throws Zend_Controller_Action_Exception
+ * @return string
+ */
+ public function encodeJson($data, $keepLayouts = false)
+ {
+ if ($this->validateData($data)) {
+ return Zend_Controller_Action_HelperBroker::getStaticHelper('Json')->encodeJson($data, $keepLayouts);
+ }
+
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('Invalid data passed for autocompletion');
+ }
+
+ /**
+ * Send autocompletion data
+ *
+ * Calls prepareAutoCompletion, populates response body with this
+ * information, and sends response.
+ *
+ * @param mixed $data
+ * @param bool $keepLayouts
+ * @return string|void
+ */
+ public function sendAutoCompletion($data, $keepLayouts = false)
+ {
+ $data = $this->prepareAutoCompletion($data, $keepLayouts);
+
+ $response = $this->getResponse();
+ $response->setBody($data);
+
+ if (!$this->suppressExit) {
+ $response->sendResponse();
+ exit;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Strategy pattern: allow calling helper as broker method
+ *
+ * Prepares autocompletion data and, if $sendNow is true, immediately sends
+ * response.
+ *
+ * @param mixed $data
+ * @param bool $sendNow
+ * @param bool $keepLayouts
+ * @return string|void
+ */
+ public function direct($data, $sendNow = true, $keepLayouts = false)
+ {
+ if ($sendNow) {
+ return $this->sendAutoCompletion($data, $keepLayouts);
+ }
+
+ return $this->prepareAutoCompletion($data, $keepLayouts);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/Cache.php b/library/vendor/Zend/Controller/Action/Helper/Cache.php
new file mode 100644
index 0000000..32afc77
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/Cache.php
@@ -0,0 +1,286 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * @see Zend_Controller_Action_Exception
+ */
+
+/**
+ * @see Zend_Cache_Manager
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_Cache
+ extends Zend_Controller_Action_Helper_Abstract
+{
+
+ /**
+ * Local Cache Manager object used by Helper
+ *
+ * @var Zend_Cache_Manager
+ */
+ protected $_manager = null;
+
+ /**
+ * Indexed map of Actions to attempt Page caching on by Controller
+ *
+ * @var array
+ */
+ protected $_caching = array();
+
+ /**
+ * Indexed map of Tags by Controller and Action
+ *
+ * @var array
+ */
+ protected $_tags = array();
+
+ /**
+ * Indexed map of Extensions by Controller and Action
+ *
+ * @var array
+ */
+ protected $_extensions = array();
+
+ /**
+ * Track output buffering condition
+ */
+ protected $_obStarted = false;
+
+ /**
+ * Tell the helper which actions are cacheable and under which
+ * tags (if applicable) they should be recorded with
+ *
+ * @param array $actions
+ * @param array $tags
+ * @return void
+ */
+ public function direct(array $actions, array $tags = array(), $extension = null)
+ {
+ $controller = $this->getRequest()->getControllerName();
+ $actions = array_unique($actions);
+ if (!isset($this->_caching[$controller])) {
+ $this->_caching[$controller] = array();
+ }
+ if (!empty($tags)) {
+ $tags = array_unique($tags);
+ if (!isset($this->_tags[$controller])) {
+ $this->_tags[$controller] = array();
+ }
+ }
+ foreach ($actions as $action) {
+ $this->_caching[$controller][] = $action;
+ if (!empty($tags)) {
+ $this->_tags[$controller][$action] = array();
+ foreach ($tags as $tag) {
+ $this->_tags[$controller][$action][] = $tag;
+ }
+ }
+ }
+ if ($extension) {
+ if (!isset($this->_extensions[$controller])) {
+ $this->_extensions[$controller] = array();
+ }
+ foreach ($actions as $action) {
+ $this->_extensions[$controller][$action] = $extension;
+ }
+ }
+ }
+
+ /**
+ * Remove a specific page cache static file based on its
+ * relative URL from the application's public directory.
+ * The file extension is not required here; usually matches
+ * the original REQUEST_URI that was cached.
+ *
+ * @param string $relativeUrl
+ * @param bool $recursive
+ * @return mixed
+ */
+ public function removePage($relativeUrl, $recursive = false)
+ {
+ $cache = $this->getCache(Zend_Cache_Manager::PAGECACHE);
+ $encodedCacheId = $this->_encodeCacheId($relativeUrl);
+
+ if ($recursive) {
+ $backend = $cache->getBackend();
+ if (($backend instanceof Zend_Cache_Backend)
+ && method_exists($backend, 'removeRecursively')
+ ) {
+ $result = $backend->removeRecursively($encodedCacheId);
+ if (is_null($result) ) {
+ $result = $backend->removeRecursively($relativeUrl);
+ }
+ return $result;
+ }
+ }
+
+ $result = $cache->remove($encodedCacheId);
+ if (is_null($result) ) {
+ $result = $cache->remove($relativeUrl);
+ }
+ return $result;
+ }
+
+ /**
+ * Remove a specific page cache static file based on its
+ * relative URL from the application's public directory.
+ * The file extension is not required here; usually matches
+ * the original REQUEST_URI that was cached.
+ *
+ * @param array $tags
+ * @return mixed
+ */
+ public function removePagesTagged(array $tags)
+ {
+ return $this->getCache(Zend_Cache_Manager::PAGECACHE)
+ ->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags);
+ }
+
+ /**
+ * Commence page caching for any cacheable actions
+ *
+ * @return void
+ */
+ public function preDispatch()
+ {
+ $controller = $this->getRequest()->getControllerName();
+ $action = $this->getRequest()->getActionName();
+ $stats = ob_get_status(true);
+ foreach ($stats as $status) {
+ if ($status['name'] == 'Zend_Cache_Frontend_Page::_flush'
+ || $status['name'] == 'Zend_Cache_Frontend_Capture::_flush') {
+ $obStarted = true;
+ }
+ }
+ if (!isset($obStarted) && isset($this->_caching[$controller]) &&
+ in_array($action, $this->_caching[$controller])) {
+ $reqUri = $this->getRequest()->getRequestUri();
+ $tags = array();
+ if (isset($this->_tags[$controller][$action])
+ && !empty($this->_tags[$controller][$action])) {
+ $tags = array_unique($this->_tags[$controller][$action]);
+ }
+ $extension = null;
+ if (isset($this->_extensions[$controller][$action])) {
+ $extension = $this->_extensions[$controller][$action];
+ }
+ $this->getCache(Zend_Cache_Manager::PAGECACHE)
+ ->start($this->_encodeCacheId($reqUri), $tags, $extension);
+ }
+ }
+
+ /**
+ * Encode a Cache ID as hexadecimal. This is a workaround because Backend ID validation
+ * is trapped in the Frontend classes. Will try to get this reversed for ZF 2.0
+ * because it's a major annoyance to have IDs so restricted!
+ *
+ * @return string
+ * @param string $requestUri
+ */
+ protected function _encodeCacheId($requestUri)
+ {
+ return bin2hex($requestUri);
+ }
+
+ /**
+ * Set an instance of the Cache Manager for this helper
+ *
+ * @param Zend_Cache_Manager $manager
+ * @return void
+ */
+ public function setManager(Zend_Cache_Manager $manager)
+ {
+ $this->_manager = $manager;
+ return $this;
+ }
+
+ /**
+ * Get the Cache Manager instance or instantiate the object if not
+ * exists. Attempts to load from bootstrap if available.
+ *
+ * @return Zend_Cache_Manager
+ */
+ public function getManager()
+ {
+ if ($this->_manager !== null) {
+ return $this->_manager;
+ }
+ $front = Zend_Controller_Front::getInstance();
+ if ($front->getParam('bootstrap')
+ && $front->getParam('bootstrap')->getResource('CacheManager')) {
+ return $front->getParam('bootstrap')
+ ->getResource('CacheManager');
+ }
+ $this->_manager = new Zend_Cache_Manager;
+ return $this->_manager;
+ }
+
+ /**
+ * Return a list of actions for the current Controller marked for
+ * caching
+ *
+ * @return array
+ */
+ public function getCacheableActions()
+ {
+ return $this->_caching;
+ }
+
+ /**
+ * Return a list of tags set for all cacheable actions
+ *
+ * @return array
+ */
+ public function getCacheableTags()
+ {
+ return $this->_tags;
+ }
+
+ /**
+ * Proxy non-matched methods back to Zend_Cache_Manager where
+ * appropriate
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (method_exists($this->getManager(), $method)) {
+ return call_user_func_array(
+ array($this->getManager(), $method), $args
+ );
+ }
+ throw new Zend_Controller_Action_Exception('Method does not exist:'
+ . $method);
+ }
+
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/ContextSwitch.php b/library/vendor/Zend/Controller/Action/Helper/ContextSwitch.php
new file mode 100644
index 0000000..6caeec9
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/ContextSwitch.php
@@ -0,0 +1,1377 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * Simplify context switching based on requested format
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_ContextSwitch extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * Trigger type constants
+ */
+ const TRIGGER_INIT = 'TRIGGER_INIT';
+ const TRIGGER_POST = 'TRIGGER_POST';
+
+ /**
+ * Supported contexts
+ * @var array
+ */
+ protected $_contexts = array();
+
+ /**
+ * JSON auto-serialization flag
+ * @var boolean
+ */
+ protected $_autoJsonSerialization = true;
+
+ /**
+ * Controller property key to utilize for context switching
+ * @var string
+ */
+ protected $_contextKey = 'contexts';
+
+ /**
+ * Request parameter containing requested context
+ * @var string
+ */
+ protected $_contextParam = 'format';
+
+ /**
+ * Current context
+ * @var string
+ */
+ protected $_currentContext;
+
+ /**
+ * Default context (xml)
+ * @var string
+ */
+ protected $_defaultContext = 'xml';
+
+ /**
+ * Whether or not to disable layouts when switching contexts
+ * @var boolean
+ */
+ protected $_disableLayout = true;
+
+ /**
+ * Methods that require special configuration
+ * @var array
+ */
+ protected $_specialConfig = array(
+ 'setSuffix',
+ 'setHeaders',
+ 'setCallbacks',
+ );
+
+ /**
+ * Methods that are not configurable via setOptions and setConfig
+ * @var array
+ */
+ protected $_unconfigurable = array(
+ 'setOptions',
+ 'setConfig',
+ 'setHeader',
+ 'setCallback',
+ 'setContext',
+ 'setActionContext',
+ 'setActionContexts',
+ );
+
+ /**
+ * @var Zend_Controller_Action_Helper_ViewRenderer
+ */
+ protected $_viewRenderer;
+
+ /**
+ * Original view suffix prior to detecting context switch
+ * @var string
+ */
+ protected $_viewSuffixOrig;
+
+ /**
+ * Constructor
+ *
+ * @param array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ } elseif (is_array($options)) {
+ $this->setOptions($options);
+ }
+
+ if (empty($this->_contexts)) {
+ $this->addContexts(array(
+ 'json' => array(
+ 'suffix' => 'json',
+ 'headers' => array('Content-Type' => 'application/json'),
+ 'callbacks' => array(
+ 'init' => 'initJsonContext',
+ 'post' => 'postJsonContext'
+ )
+ ),
+ 'xml' => array(
+ 'suffix' => 'xml',
+ 'headers' => array('Content-Type' => 'application/xml'),
+ )
+ ));
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Initialize at start of action controller
+ *
+ * Reset the view script suffix to the original state, or store the
+ * original state.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ if (null === $this->_viewSuffixOrig) {
+ $this->_viewSuffixOrig = $this->_getViewRenderer()->getViewSuffix();
+ } else {
+ $this->_getViewRenderer()->setViewSuffix($this->_viewSuffixOrig);
+ }
+ }
+
+ /**
+ * Configure object from array of options
+ *
+ * @param array $options
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['contexts'])) {
+ $this->setContexts($options['contexts']);
+ unset($options['contexts']);
+ }
+
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (in_array($method, $this->_unconfigurable)) {
+ continue;
+ }
+
+ if (in_array($method, $this->_specialConfig)) {
+ $method = '_' . $method;
+ }
+
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set object state from config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ return $this->setOptions($config->toArray());
+ }
+
+ /**
+ * Strategy pattern: return object
+ *
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function direct()
+ {
+ return $this;
+ }
+
+ /**
+ * Initialize context detection and switching
+ *
+ * @param mixed $format
+ * @throws Zend_Controller_Action_Exception
+ * @return void
+ */
+ public function initContext($format = null)
+ {
+ $this->_currentContext = null;
+
+ $controller = $this->getActionController();
+ $request = $this->getRequest();
+ $action = $request->getActionName();
+
+ // Return if no context switching enabled, or no context switching
+ // enabled for this action
+ $contexts = $this->getActionContexts($action);
+ if (empty($contexts)) {
+ return;
+ }
+
+ // Return if no context parameter provided
+ if (!$context = $request->getParam($this->getContextParam())) {
+ if ($format === null) {
+ return;
+ }
+ $context = $format;
+ $format = null;
+ }
+
+ // Check if context allowed by action controller
+ if (!$this->hasActionContext($action, $context)) {
+ return;
+ }
+
+ // Return if invalid context parameter provided and no format or invalid
+ // format provided
+ if (!$this->hasContext($context)) {
+ if (empty($format) || !$this->hasContext($format)) {
+
+ return;
+ }
+ }
+
+ // Use provided format if passed
+ if (!empty($format) && $this->hasContext($format)) {
+ $context = $format;
+ }
+
+ $suffix = $this->getSuffix($context);
+
+ $this->_getViewRenderer()->setViewSuffix($suffix);
+
+ $headers = $this->getHeaders($context);
+ if (!empty($headers)) {
+ $response = $this->getResponse();
+ foreach ($headers as $header => $content) {
+ $response->setHeader($header, $content);
+ }
+ }
+
+ if ($this->getAutoDisableLayout()) {
+ /**
+ * @see Zend_Layout
+ */
+ $layout = Zend_Layout::getMvcInstance();
+ if (null !== $layout) {
+ $layout->disableLayout();
+ }
+ }
+
+ if (null !== ($callback = $this->getCallback($context, self::TRIGGER_INIT))) {
+ if (is_string($callback) && method_exists($this, $callback)) {
+ $this->$callback();
+ } elseif (is_string($callback) && function_exists($callback)) {
+ $callback();
+ } elseif (is_array($callback)) {
+ call_user_func($callback);
+ } else {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Invalid context callback registered for context "%s"', $context));
+ }
+ }
+
+ $this->_currentContext = $context;
+ }
+
+ /**
+ * JSON context extra initialization
+ *
+ * Turns off viewRenderer auto-rendering
+ *
+ * @return void
+ */
+ public function initJsonContext()
+ {
+ if (!$this->getAutoJsonSerialization()) {
+ return;
+ }
+
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ $view = $viewRenderer->view;
+ if ($view instanceof Zend_View_Interface) {
+ $viewRenderer->setNoRender(true);
+ }
+ }
+
+ /**
+ * Should JSON contexts auto-serialize?
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setAutoJsonSerialization($flag)
+ {
+ $this->_autoJsonSerialization = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get JSON context auto-serialization flag
+ *
+ * @return boolean
+ */
+ public function getAutoJsonSerialization()
+ {
+ return $this->_autoJsonSerialization;
+ }
+
+ /**
+ * Set suffix from array
+ *
+ * @param array $spec
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ protected function _setSuffix(array $spec)
+ {
+ foreach ($spec as $context => $suffixInfo) {
+ if (!is_string($context)) {
+ $context = null;
+ }
+
+ if (is_string($suffixInfo)) {
+ $this->setSuffix($context, $suffixInfo);
+ continue;
+ } elseif (is_array($suffixInfo)) {
+ if (isset($suffixInfo['suffix'])) {
+ $suffix = $suffixInfo['suffix'];
+ $prependViewRendererSuffix = true;
+
+ if ((null === $context) && isset($suffixInfo['context'])) {
+ $context = $suffixInfo['context'];
+ }
+
+ if (isset($suffixInfo['prependViewRendererSuffix'])) {
+ $prependViewRendererSuffix = $suffixInfo['prependViewRendererSuffix'];
+ }
+
+ $this->setSuffix($context, $suffix, $prependViewRendererSuffix);
+ continue;
+ }
+
+ $count = count($suffixInfo);
+ switch (true) {
+ case (($count < 2) && (null === $context)):
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('Invalid suffix information provided in config');
+ case ($count < 2):
+ $suffix = array_shift($suffixInfo);
+ $this->setSuffix($context, $suffix);
+ break;
+ case (($count < 3) && (null === $context)):
+ $context = array_shift($suffixInfo);
+ $suffix = array_shift($suffixInfo);
+ $this->setSuffix($context, $suffix);
+ break;
+ case (($count == 3) && (null === $context)):
+ $context = array_shift($suffixInfo);
+ $suffix = array_shift($suffixInfo);
+ $prependViewRendererSuffix = array_shift($suffixInfo);
+ $this->setSuffix($context, $suffix, $prependViewRendererSuffix);
+ break;
+ case ($count >= 2):
+ $suffix = array_shift($suffixInfo);
+ $prependViewRendererSuffix = array_shift($suffixInfo);
+ $this->setSuffix($context, $suffix, $prependViewRendererSuffix);
+ break;
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Customize view script suffix to use when switching context.
+ *
+ * Passing an empty suffix value to the setters disables the view script
+ * suffix change.
+ *
+ * @param string $context Context type for which to set suffix
+ * @param string $suffix Suffix to use
+ * @param boolean $prependViewRendererSuffix Whether or not to prepend the new suffix to the viewrenderer suffix
+ * @throws Zend_Controller_Action_Exception
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setSuffix($context, $suffix, $prependViewRendererSuffix = true)
+ {
+ if (!isset($this->_contexts[$context])) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Cannot set suffix; invalid context type "%s"', $context));
+ }
+
+ if (empty($suffix)) {
+ $suffix = '';
+ }
+
+ if (is_array($suffix)) {
+ if (isset($suffix['prependViewRendererSuffix'])) {
+ $prependViewRendererSuffix = $suffix['prependViewRendererSuffix'];
+ }
+ if (isset($suffix['suffix'])) {
+ $suffix = $suffix['suffix'];
+ } else {
+ $suffix = '';
+ }
+ }
+
+ $suffix = (string) $suffix;
+
+ if ($prependViewRendererSuffix) {
+ if (empty($suffix)) {
+ $suffix = $this->_getViewRenderer()->getViewSuffix();
+ } else {
+ $suffix .= '.' . $this->_getViewRenderer()->getViewSuffix();
+ }
+ }
+
+ $this->_contexts[$context]['suffix'] = $suffix;
+ return $this;
+ }
+
+ /**
+ * Retrieve suffix for given context type
+ *
+ * @param string $type Context type
+ * @throws Zend_Controller_Action_Exception
+ * @return string
+ */
+ public function getSuffix($type)
+ {
+ if (!isset($this->_contexts[$type])) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Cannot retrieve suffix; invalid context type "%s"', $type));
+ }
+
+ return $this->_contexts[$type]['suffix'];
+ }
+
+ /**
+ * Does the given context exist?
+ *
+ * @param string $context
+ * @param boolean $throwException
+ * @throws Zend_Controller_Action_Exception if context does not exist and throwException is true
+ * @return bool
+ */
+ public function hasContext($context, $throwException = false)
+ {
+ if (is_string($context)) {
+ if (isset($this->_contexts[$context])) {
+ return true;
+ }
+ } elseif (is_array($context)) {
+ $error = false;
+ foreach ($context as $test) {
+ if (!isset($this->_contexts[$test])) {
+ $error = (string) $test;
+ break;
+ }
+ }
+ if (false === $error) {
+ return true;
+ }
+ $context = $error;
+ } elseif (true === $context) {
+ return true;
+ }
+
+ if ($throwException) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Context "%s" does not exist', $context));
+ }
+
+ return false;
+ }
+
+ /**
+ * Add header to context
+ *
+ * @param string $context
+ * @param string $header
+ * @param string $content
+ * @throws Zend_Controller_Action_Exception
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function addHeader($context, $header, $content)
+ {
+ $context = (string) $context;
+ $this->hasContext($context, true);
+
+ $header = (string) $header;
+ $content = (string) $content;
+
+ if (isset($this->_contexts[$context]['headers'][$header])) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Cannot add "%s" header to context "%s": already exists', $header, $context));
+ }
+
+ $this->_contexts[$context]['headers'][$header] = $content;
+ return $this;
+ }
+
+ /**
+ * Customize response header to use when switching context
+ *
+ * Passing an empty header value to the setters disables the response
+ * header.
+ *
+ * @param string $type Context type for which to set suffix
+ * @param string $header Header to set
+ * @param string $content Header content
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setHeader($context, $header, $content)
+ {
+ $this->hasContext($context, true);
+ $context = (string) $context;
+ $header = (string) $header;
+ $content = (string) $content;
+
+ $this->_contexts[$context]['headers'][$header] = $content;
+ return $this;
+ }
+
+ /**
+ * Add multiple headers at once for a given context
+ *
+ * @param string $context
+ * @param array $headers
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function addHeaders($context, array $headers)
+ {
+ foreach ($headers as $header => $content) {
+ $this->addHeader($context, $header, $content);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set headers from context => headers pairs
+ *
+ * @param array $options
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ protected function _setHeaders(array $options)
+ {
+ foreach ($options as $context => $headers) {
+ if (!is_array($headers)) {
+ continue;
+ }
+ $this->setHeaders($context, $headers);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set multiple headers at once for a given context
+ *
+ * @param string $context
+ * @param array $headers
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setHeaders($context, array $headers)
+ {
+ $this->clearHeaders($context);
+ foreach ($headers as $header => $content) {
+ $this->setHeader($context, $header, $content);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve context header
+ *
+ * Returns the value of a given header for a given context type
+ *
+ * @param string $context
+ * @param string $header
+ * @return string|null
+ */
+ public function getHeader($context, $header)
+ {
+ $this->hasContext($context, true);
+ $context = (string) $context;
+ $header = (string) $header;
+ if (isset($this->_contexts[$context]['headers'][$header])) {
+ return $this->_contexts[$context]['headers'][$header];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve context headers
+ *
+ * Returns all headers for a context as key/value pairs
+ *
+ * @param string $context
+ * @return array
+ */
+ public function getHeaders($context)
+ {
+ $this->hasContext($context, true);
+ $context = (string) $context;
+ return $this->_contexts[$context]['headers'];
+ }
+
+ /**
+ * Remove a single header from a context
+ *
+ * @param string $context
+ * @param string $header
+ * @return boolean
+ */
+ public function removeHeader($context, $header)
+ {
+ $this->hasContext($context, true);
+ $context = (string) $context;
+ $header = (string) $header;
+ if (isset($this->_contexts[$context]['headers'][$header])) {
+ unset($this->_contexts[$context]['headers'][$header]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all headers for a given context
+ *
+ * @param string $context
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function clearHeaders($context)
+ {
+ $this->hasContext($context, true);
+ $context = (string) $context;
+ $this->_contexts[$context]['headers'] = array();
+ return $this;
+ }
+
+ /**
+ * Validate trigger and return in normalized form
+ *
+ * @param string $trigger
+ * @throws Zend_Controller_Action_Exception
+ * @return string
+ */
+ protected function _validateTrigger($trigger)
+ {
+ $trigger = strtoupper($trigger);
+ if ('TRIGGER_' !== substr($trigger, 0, 8)) {
+ $trigger = 'TRIGGER_' . $trigger;
+ }
+
+ if (!in_array($trigger, array(self::TRIGGER_INIT, self::TRIGGER_POST))) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Invalid trigger "%s"', $trigger));
+ }
+
+ return $trigger;
+ }
+
+ /**
+ * Set a callback for a given context and trigger
+ *
+ * @param string $context
+ * @param string $trigger
+ * @param string|array $callback
+ * @throws Zend_Controller_Action_Exception
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setCallback($context, $trigger, $callback)
+ {
+ $this->hasContext($context, true);
+ $trigger = $this->_validateTrigger($trigger);
+
+ if (!is_string($callback)) {
+ if (!is_array($callback) || (2 != count($callback))) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('Invalid callback specified');
+ }
+ }
+
+ $this->_contexts[$context]['callbacks'][$trigger] = $callback;
+ return $this;
+ }
+
+ /**
+ * Set callbacks from array of context => callbacks pairs
+ *
+ * @param array $options
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ protected function _setCallbacks(array $options)
+ {
+ foreach ($options as $context => $callbacks) {
+ if (!is_array($callbacks)) {
+ continue;
+ }
+
+ $this->setCallbacks($context, $callbacks);
+ }
+ return $this;
+ }
+
+ /**
+ * Set callbacks for a given context
+ *
+ * Callbacks should be in trigger/callback pairs.
+ *
+ * @param string $context
+ * @param array $callbacks
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setCallbacks($context, array $callbacks)
+ {
+ $this->hasContext($context, true);
+ $context = (string) $context;
+ if (!isset($this->_contexts[$context]['callbacks'])) {
+ $this->_contexts[$context]['callbacks'] = array();
+ }
+
+ foreach ($callbacks as $trigger => $callback) {
+ $this->setCallback($context, $trigger, $callback);
+ }
+ return $this;
+ }
+
+ /**
+ * Get a single callback for a given context and trigger
+ *
+ * @param string $context
+ * @param string $trigger
+ * @return string|array|null
+ */
+ public function getCallback($context, $trigger)
+ {
+ $this->hasContext($context, true);
+ $trigger = $this->_validateTrigger($trigger);
+ if (isset($this->_contexts[$context]['callbacks'][$trigger])) {
+ return $this->_contexts[$context]['callbacks'][$trigger];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all callbacks for a given context
+ *
+ * @param string $context
+ * @return array
+ */
+ public function getCallbacks($context)
+ {
+ $this->hasContext($context, true);
+ return $this->_contexts[$context]['callbacks'];
+ }
+
+ /**
+ * Clear a callback for a given context and trigger
+ *
+ * @param string $context
+ * @param string $trigger
+ * @return boolean
+ */
+ public function removeCallback($context, $trigger)
+ {
+ $this->hasContext($context, true);
+ $trigger = $this->_validateTrigger($trigger);
+ if (isset($this->_contexts[$context]['callbacks'][$trigger])) {
+ unset($this->_contexts[$context]['callbacks'][$trigger]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all callbacks for a given context
+ *
+ * @param string $context
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function clearCallbacks($context)
+ {
+ $this->hasContext($context, true);
+ $this->_contexts[$context]['callbacks'] = array();
+ return $this;
+ }
+
+ /**
+ * Set name of parameter to use when determining context format
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setContextParam($name)
+ {
+ $this->_contextParam = (string) $name;
+ return $this;
+ }
+
+ /**
+ * Return context format request parameter name
+ *
+ * @return string
+ */
+ public function getContextParam()
+ {
+ return $this->_contextParam;
+ }
+
+ /**
+ * Indicate default context to use when no context format provided
+ *
+ * @param string $type
+ * @throws Zend_Controller_Action_Exception
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setDefaultContext($type)
+ {
+ if (!isset($this->_contexts[$type])) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Cannot set default context; invalid context type "%s"', $type));
+ }
+
+ $this->_defaultContext = $type;
+ return $this;
+ }
+
+ /**
+ * Return default context
+ *
+ * @return string
+ */
+ public function getDefaultContext()
+ {
+ return $this->_defaultContext;
+ }
+
+ /**
+ * Set flag indicating if layout should be disabled
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setAutoDisableLayout($flag)
+ {
+ $this->_disableLayout = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve auto layout disable flag
+ *
+ * @return boolean
+ */
+ public function getAutoDisableLayout()
+ {
+ return $this->_disableLayout;
+ }
+
+ /**
+ * Add new context
+ *
+ * @param string $context Context type
+ * @param array $spec Context specification
+ * @throws Zend_Controller_Action_Exception
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function addContext($context, array $spec)
+ {
+ if ($this->hasContext($context)) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Cannot add context "%s"; already exists', $context));
+ }
+ $context = (string) $context;
+
+ $this->_contexts[$context] = array();
+
+ $this->setSuffix($context, (isset($spec['suffix']) ? $spec['suffix'] : ''))
+ ->setHeaders($context, (isset($spec['headers']) ? $spec['headers'] : array()))
+ ->setCallbacks($context, (isset($spec['callbacks']) ? $spec['callbacks'] : array()));
+ return $this;
+ }
+
+ /**
+ * Overwrite existing context
+ *
+ * @param string $context Context type
+ * @param array $spec Context specification
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setContext($context, array $spec)
+ {
+ $this->removeContext($context);
+ return $this->addContext($context, $spec);
+ }
+
+ /**
+ * Add multiple contexts
+ *
+ * @param array $contexts
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function addContexts(array $contexts)
+ {
+ foreach ($contexts as $context => $spec) {
+ $this->addContext($context, $spec);
+ }
+ return $this;
+ }
+
+ /**
+ * Set multiple contexts, after first removing all
+ *
+ * @param array $contexts
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setContexts(array $contexts)
+ {
+ $this->clearContexts();
+ foreach ($contexts as $context => $spec) {
+ $this->addContext($context, $spec);
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve context specification
+ *
+ * @param string $context
+ * @return array|null
+ */
+ public function getContext($context)
+ {
+ if ($this->hasContext($context)) {
+ return $this->_contexts[(string) $context];
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve context definitions
+ *
+ * @return array
+ */
+ public function getContexts()
+ {
+ return $this->_contexts;
+ }
+
+ /**
+ * Remove a context
+ *
+ * @param string $context
+ * @return boolean
+ */
+ public function removeContext($context)
+ {
+ if ($this->hasContext($context)) {
+ unset($this->_contexts[(string) $context]);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove all contexts
+ *
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function clearContexts()
+ {
+ $this->_contexts = array();
+ return $this;
+ }
+
+ /**
+ * Return current context, if any
+ *
+ * @return null|string
+ */
+ public function getCurrentContext()
+ {
+ return $this->_currentContext;
+ }
+
+ /**
+ * Post dispatch processing
+ *
+ * Execute postDispatch callback for current context, if available
+ *
+ * @throws Zend_Controller_Action_Exception
+ * @return void
+ */
+ public function postDispatch()
+ {
+ $context = $this->getCurrentContext();
+ if (null !== $context) {
+ if (null !== ($callback = $this->getCallback($context, self::TRIGGER_POST))) {
+ if (is_string($callback) && method_exists($this, $callback)) {
+ $this->$callback();
+ } elseif (is_string($callback) && function_exists($callback)) {
+ $callback();
+ } elseif (is_array($callback)) {
+ call_user_func($callback);
+ } else {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf('Invalid postDispatch context callback registered for context "%s"', $context));
+ }
+ }
+ }
+ }
+
+ /**
+ * JSON post processing
+ *
+ * JSON serialize view variables to response body
+ *
+ * @return void
+ */
+ public function postJsonContext()
+ {
+ if (!$this->getAutoJsonSerialization()) {
+ return;
+ }
+
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ $view = $viewRenderer->view;
+ if ($view instanceof Zend_View_Interface) {
+ /**
+ * @see Zend_Json
+ */
+ if(method_exists($view, 'getVars')) {
+ $vars = Zend_Json::encode($view->getVars());
+ $this->getResponse()->setBody($vars);
+ } else {
+ throw new Zend_Controller_Action_Exception('View does not implement the getVars() method needed to encode the view into JSON');
+ }
+ }
+ }
+
+ /**
+ * Add one or more contexts to an action
+ *
+ * @param string $action
+ * @param string|array $context
+ * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface
+ */
+ public function addActionContext($action, $context)
+ {
+ $this->hasContext($context, true);
+ $controller = $this->getActionController();
+ if (null === $controller) {
+ return;
+ }
+ $action = (string) $action;
+ $contextKey = $this->_contextKey;
+
+ if (!isset($controller->$contextKey)) {
+ $controller->$contextKey = array();
+ }
+
+ if (true === $context) {
+ $contexts = $this->getContexts();
+ $controller->{$contextKey}[$action] = array_keys($contexts);
+ return $this;
+ }
+
+ $context = (array) $context;
+ if (!isset($controller->{$contextKey}[$action])) {
+ $controller->{$contextKey}[$action] = $context;
+ } else {
+ $controller->{$contextKey}[$action] = array_merge(
+ $controller->{$contextKey}[$action],
+ $context
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a context as available for a given controller action
+ *
+ * @param string $action
+ * @param string|array $context
+ * @return Zend_Controller_Action_Helper_ContextSwitch|void Provides a fluent interface
+ */
+ public function setActionContext($action, $context)
+ {
+ $this->hasContext($context, true);
+ $controller = $this->getActionController();
+ if (null === $controller) {
+ return;
+ }
+ $action = (string) $action;
+ $contextKey = $this->_contextKey;
+
+ if (!isset($controller->$contextKey)) {
+ $controller->$contextKey = array();
+ }
+
+ if (true === $context) {
+ $contexts = $this->getContexts();
+ $controller->{$contextKey}[$action] = array_keys($contexts);
+ } else {
+ $controller->{$contextKey}[$action] = (array) $context;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add multiple action/context pairs at once
+ *
+ * @param array $contexts
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function addActionContexts(array $contexts)
+ {
+ foreach ($contexts as $action => $context) {
+ $this->addActionContext($action, $context);
+ }
+ return $this;
+ }
+
+ /**
+ * Overwrite and set multiple action contexts at once
+ *
+ * @param array $contexts
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function setActionContexts(array $contexts)
+ {
+ foreach ($contexts as $action => $context) {
+ $this->setActionContext($action, $context);
+ }
+ return $this;
+ }
+
+ /**
+ * Does a particular controller action have the given context(s)?
+ *
+ * @param string $action
+ * @param string|array $context
+ * @throws Zend_Controller_Action_Exception
+ * @return boolean
+ */
+ public function hasActionContext($action, $context)
+ {
+ $this->hasContext($context, true);
+ $controller = $this->getActionController();
+ if (null === $controller) {
+ return false;
+ }
+ $action = (string) $action;
+ $contextKey = $this->_contextKey;
+
+ if (!isset($controller->{$contextKey})) {
+ return false;
+ }
+
+ $allContexts = $controller->{$contextKey};
+
+ if (!is_array($allContexts)) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception("Invalid contexts found for controller");
+ }
+
+ if (!isset($allContexts[$action])) {
+ return false;
+ }
+
+ if (true === $allContexts[$action]) {
+ return true;
+ }
+
+ $contexts = $allContexts[$action];
+
+ if (!is_array($contexts)) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception(sprintf("Invalid contexts found for action '%s'", $action));
+ }
+
+ if (is_string($context) && in_array($context, $contexts)) {
+ return true;
+ } elseif (is_array($context)) {
+ $found = true;
+ foreach ($context as $test) {
+ if (!in_array($test, $contexts)) {
+ $found = false;
+ break;
+ }
+ }
+ return $found;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get contexts for a given action or all actions in the controller
+ *
+ * @param string $action
+ * @return array
+ */
+ public function getActionContexts($action = null)
+ {
+ $controller = $this->getActionController();
+ if (null === $controller) {
+ return array();
+ }
+ $contextKey = $this->_contextKey;
+
+ if (!isset($controller->$contextKey)) {
+ return array();
+ }
+
+ if (null !== $action) {
+ $action = (string) $action;
+ if (isset($controller->{$contextKey}[$action])) {
+ return $controller->{$contextKey}[$action];
+ } else {
+ return array();
+ }
+ }
+
+ return $controller->$contextKey;
+ }
+
+ /**
+ * Remove one or more contexts for a given controller action
+ *
+ * @param string $action
+ * @param string|array $context
+ * @return boolean
+ */
+ public function removeActionContext($action, $context)
+ {
+ if ($this->hasActionContext($action, $context)) {
+ $controller = $this->getActionController();
+ $contextKey = $this->_contextKey;
+ $action = (string) $action;
+ $contexts = $controller->$contextKey;
+ $actionContexts = $contexts[$action];
+ $contexts = (array) $context;
+ foreach ($contexts as $context) {
+ $index = array_search($context, $actionContexts);
+ if (false !== $index) {
+ unset($controller->{$contextKey}[$action][$index]);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Clear all contexts for a given controller action or all actions
+ *
+ * @param string $action
+ * @return Zend_Controller_Action_Helper_ContextSwitch Provides a fluent interface
+ */
+ public function clearActionContexts($action = null)
+ {
+ $controller = $this->getActionController();
+ $contextKey = $this->_contextKey;
+
+ if (!isset($controller->$contextKey) || empty($controller->$contextKey)) {
+ return $this;
+ }
+
+ if (null === $action) {
+ $controller->$contextKey = array();
+ return $this;
+ }
+
+ $action = (string) $action;
+ if (isset($controller->{$contextKey}[$action])) {
+ unset($controller->{$contextKey}[$action]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve ViewRenderer
+ *
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ protected function _getViewRenderer()
+ {
+ if (null === $this->_viewRenderer) {
+ $this->_viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ }
+
+ return $this->_viewRenderer;
+ }
+}
+
diff --git a/library/vendor/Zend/Controller/Action/Helper/Json.php b/library/vendor/Zend/Controller/Action/Helper/Json.php
new file mode 100644
index 0000000..5b77f12
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/Json.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * Simplify AJAX context switching based on requested format
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_Json extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * Suppress exit when sendJson() called
+ * @var boolean
+ */
+ public $suppressExit = false;
+
+ /**
+ * Create JSON response
+ *
+ * Encodes and returns data to JSON. Content-Type header set to
+ * 'application/json', and disables layouts and viewRenderer (if being
+ * used).
+ *
+ * @param mixed $data
+ * @param boolean $keepLayouts
+ * @param boolean|array $keepLayouts
+ * @param boolean $encodeData Provided data is already JSON
+ * NOTE: if boolean, establish $keepLayouts to true|false
+ * if array, admit params for Zend_Json::encode as enableJsonExprFinder=>true|false
+ * if $keepLayouts and parmas for Zend_Json::encode are required
+ * then, the array can contains a 'keepLayout'=>true|false and/or 'encodeData'=>true|false
+ * that will not be passed to Zend_Json::encode method but will be passed
+ * to Zend_View_Helper_Json
+ * @throws Zend_Controller_Action_Helper_Json
+ * @return string
+ */
+ public function encodeJson($data, $keepLayouts = false, $encodeData = true)
+ {
+ /**
+ * @see Zend_View_Helper_Json
+ */
+ $jsonHelper = new Zend_View_Helper_Json();
+ $data = $jsonHelper->json($data, $keepLayouts, $encodeData);
+
+ if (!$keepLayouts) {
+ /**
+ * @see Zend_Controller_Action_HelperBroker
+ */
+ Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer')->setNoRender(true);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Encode JSON response and immediately send
+ *
+ * @param mixed $data
+ * @param boolean|array $keepLayouts
+ * @param $encodeData Encode $data as JSON?
+ * NOTE: if boolean, establish $keepLayouts to true|false
+ * if array, admit params for Zend_Json::encode as enableJsonExprFinder=>true|false
+ * if $keepLayouts and parmas for Zend_Json::encode are required
+ * then, the array can contains a 'keepLayout'=>true|false and/or 'encodeData'=>true|false
+ * that will not be passed to Zend_Json::encode method but will be passed
+ * to Zend_View_Helper_Json
+ * @return string|void
+ */
+ public function sendJson($data, $keepLayouts = false, $encodeData = true)
+ {
+ $data = $this->encodeJson($data, $keepLayouts, $encodeData);
+ $response = $this->getResponse();
+ $response->setBody($data);
+
+ if (!$this->suppressExit) {
+ $response->sendResponse();
+ exit;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Strategy pattern: call helper as helper broker method
+ *
+ * Allows encoding JSON. If $sendNow is true, immediately sends JSON
+ * response.
+ *
+ * @param mixed $data
+ * @param boolean $sendNow
+ * @param boolean $keepLayouts
+ * @param boolean $encodeData Encode $data as JSON?
+ * @return string|void
+ */
+ public function direct($data, $sendNow = true, $keepLayouts = false, $encodeData = true)
+ {
+ if ($sendNow) {
+ return $this->sendJson($data, $keepLayouts, $encodeData);
+ }
+ return $this->encodeJson($data, $keepLayouts, $encodeData);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/Redirector.php b/library/vendor/Zend/Controller/Action/Helper/Redirector.php
new file mode 100644
index 0000000..8ff7c3e
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/Redirector.php
@@ -0,0 +1,531 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_Redirector extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * HTTP status code for redirects
+ * @var int
+ */
+ protected $_code = 302;
+
+ /**
+ * Whether or not calls to _redirect() should exit script execution
+ * @var boolean
+ */
+ protected $_exit = true;
+
+ /**
+ * Whether or not _redirect() should attempt to prepend the base URL to the
+ * passed URL (if it's a relative URL)
+ * @var boolean
+ */
+ protected $_prependBase = true;
+
+ /**
+ * Url to which to redirect
+ * @var string
+ */
+ protected $_redirectUrl = null;
+
+ /**
+ * Whether or not to use an absolute URI when redirecting
+ * @var boolean
+ */
+ protected $_useAbsoluteUri = false;
+
+ /**
+ * Whether or not to close the session before exiting
+ * @var boolean
+ */
+ protected $_closeSessionOnExit = true;
+
+ /**
+ * Retrieve HTTP status code to emit on {@link _redirect()} call
+ *
+ * @return int
+ */
+ public function getCode()
+ {
+ return $this->_code;
+ }
+
+ /**
+ * Validate HTTP status redirect code
+ *
+ * @param int $code
+ * @throws Zend_Controller_Action_Exception on invalid HTTP status code
+ * @return true
+ */
+ protected function _checkCode($code)
+ {
+ $code = (int)$code;
+ if ((300 > $code) || (307 < $code) || (304 == $code) || (306 == $code)) {
+ throw new Zend_Controller_Action_Exception('Invalid redirect HTTP status code (' . $code . ')');
+ }
+
+ return true;
+ }
+
+ /**
+ * Set HTTP status code for {@link _redirect()} behaviour
+ *
+ * @param int $code
+ * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface
+ */
+ public function setCode($code)
+ {
+ $this->_checkCode($code);
+ $this->_code = $code;
+ return $this;
+ }
+
+ /**
+ * Retrieve flag for whether or not {@link _redirect()} will exit when finished.
+ *
+ * @return boolean
+ */
+ public function getExit()
+ {
+ return $this->_exit;
+ }
+
+ /**
+ * Set exit flag for {@link _redirect()} behaviour
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface
+ */
+ public function setExit($flag)
+ {
+ $this->_exit = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve flag for whether or not {@link _redirect()} will prepend the
+ * base URL on relative URLs
+ *
+ * @return boolean
+ */
+ public function getPrependBase()
+ {
+ return $this->_prependBase;
+ }
+
+ /**
+ * Set 'prepend base' flag for {@link _redirect()} behaviour
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface
+ */
+ public function setPrependBase($flag)
+ {
+ $this->_prependBase = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve flag for whether or not {@link redirectAndExit()} shall close the session before
+ * exiting.
+ *
+ * @return boolean
+ */
+ public function getCloseSessionOnExit()
+ {
+ return $this->_closeSessionOnExit;
+ }
+
+ /**
+ * Set flag for whether or not {@link redirectAndExit()} shall close the session before exiting.
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface
+ */
+ public function setCloseSessionOnExit($flag)
+ {
+ $this->_closeSessionOnExit = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Return use absolute URI flag
+ *
+ * @return boolean
+ */
+ public function getUseAbsoluteUri()
+ {
+ return $this->_useAbsoluteUri;
+ }
+
+ /**
+ * Set use absolute URI flag
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_Redirector Provides a fluent interface
+ */
+ public function setUseAbsoluteUri($flag = true)
+ {
+ $this->_useAbsoluteUri = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Set redirect in response object
+ *
+ * @return void
+ */
+ protected function _redirect($url)
+ {
+ if ($this->getUseAbsoluteUri() && !preg_match('#^(https?|ftp)://#', $url)) {
+ $host = (isset($_SERVER['HTTP_HOST'])?$_SERVER['HTTP_HOST']:'');
+ $proto = (isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=="off") ? 'https' : 'http';
+ $port = (isset($_SERVER['SERVER_PORT'])?$_SERVER['SERVER_PORT']:80);
+ $uri = $proto . '://' . $host;
+ if ((('http' == $proto) && (80 != $port)) || (('https' == $proto) && (443 != $port))) {
+ // do not append if HTTP_HOST already contains port
+ if (strrchr($host, ':') === false) {
+ $uri .= ':' . $port;
+ }
+ }
+ $url = $uri . '/' . ltrim($url, '/');
+ }
+ $this->_redirectUrl = $url;
+ $this->getResponse()->setRedirect($url, $this->getCode());
+ }
+
+ /**
+ * Retrieve currently set URL for redirect
+ *
+ * @return string
+ */
+ public function getRedirectUrl()
+ {
+ return $this->_redirectUrl;
+ }
+
+ /**
+ * Determine if the baseUrl should be prepended, and prepend if necessary
+ *
+ * @param string $url
+ * @return string
+ */
+ protected function _prependBase($url)
+ {
+ if ($this->getPrependBase()) {
+ $request = $this->getRequest();
+ if ($request instanceof Zend_Controller_Request_Http) {
+ $base = rtrim($request->getBaseUrl(), '/');
+ if (!empty($base) && ('/' != $base)) {
+ $url = $base . '/' . ltrim($url, '/');
+ } else {
+ $url = '/' . ltrim($url, '/');
+ }
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ * Set a redirect URL of the form /module/controller/action/params
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return void
+ */
+ public function setGotoSimple($action, $controller = null, $module = null, array $params = array())
+ {
+ $dispatcher = $this->getFrontController()->getDispatcher();
+ $request = $this->getRequest();
+ $curModule = $request->getModuleName();
+ $useDefaultController = false;
+
+ if (null === $controller && null !== $module) {
+ $useDefaultController = true;
+ }
+
+ if (null === $module) {
+ $module = $curModule;
+ }
+
+ if ($module == $dispatcher->getDefaultModule()) {
+ $module = '';
+ }
+
+ if (null === $controller && !$useDefaultController) {
+ $controller = $request->getControllerName();
+ if (empty($controller)) {
+ $controller = $dispatcher->getDefaultControllerName();
+ }
+ }
+
+ $params[$request->getModuleKey()] = $module;
+ $params[$request->getControllerKey()] = $controller;
+ $params[$request->getActionKey()] = $action;
+
+ $router = $this->getFrontController()->getRouter();
+ $url = $router->assemble($params, 'default', true);
+
+ $this->_redirect($url);
+ }
+
+ /**
+ * Build a URL based on a route
+ *
+ * @param array $urlOptions
+ * @param string $name Route name
+ * @param boolean $reset
+ * @param boolean $encode
+ * @return void
+ */
+ public function setGotoRoute(array $urlOptions = array(), $name = null, $reset = false, $encode = true)
+ {
+ $router = $this->getFrontController()->getRouter();
+ $url = $router->assemble($urlOptions, $name, $reset, $encode);
+
+ $this->_redirect($url);
+ }
+
+ /**
+ * Set a redirect URL string
+ *
+ * By default, emits a 302 HTTP status header, prepends base URL as defined
+ * in request object if url is relative, and halts script execution by
+ * calling exit().
+ *
+ * $options is an optional associative array that can be used to control
+ * redirect behaviour. The available option keys are:
+ * - exit: boolean flag indicating whether or not to halt script execution when done
+ * - prependBase: boolean flag indicating whether or not to prepend the base URL when a relative URL is provided
+ * - code: integer HTTP status code to use with redirect. Should be between 300 and 307.
+ *
+ * _redirect() sets the Location header in the response object. If you set
+ * the exit flag to false, you can override this header later in code
+ * execution.
+ *
+ * If the exit flag is true (true by default), _redirect() will write and
+ * close the current session, if any.
+ *
+ * @param string $url
+ * @param array $options
+ * @return void
+ */
+ public function setGotoUrl($url, array $options = array())
+ {
+ // prevent header injections
+ $url = str_replace(array("\n", "\r"), '', $url);
+
+ if (null !== $options) {
+ if (isset($options['exit'])) {
+ $this->setExit(($options['exit']) ? true : false);
+ }
+ if (isset($options['prependBase'])) {
+ $this->setPrependBase(($options['prependBase']) ? true : false);
+ }
+ if (isset($options['code'])) {
+ $this->setCode($options['code']);
+ }
+ }
+
+ // If relative URL, decide if we should prepend base URL
+ if (!preg_match('|^[a-z]+://|', $url)) {
+ $url = $this->_prependBase($url);
+ }
+
+ $this->_redirect($url);
+ }
+
+ /**
+ * Perform a redirect to an action/controller/module with params
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return void
+ */
+ public function gotoSimple($action, $controller = null, $module = null, array $params = array())
+ {
+ $this->setGotoSimple($action, $controller, $module, $params);
+
+ if ($this->getExit()) {
+ $this->redirectAndExit();
+ }
+ }
+
+ /**
+ * Perform a redirect to an action/controller/module with params, forcing an immdiate exit
+ *
+ * @param mixed $action
+ * @param mixed $controller
+ * @param mixed $module
+ * @param array $params
+ * @return void
+ */
+ public function gotoSimpleAndExit($action, $controller = null, $module = null, array $params = array())
+ {
+ $this->setGotoSimple($action, $controller, $module, $params);
+ $this->redirectAndExit();
+ }
+
+ /**
+ * Redirect to a route-based URL
+ *
+ * Uses route's assemble method to build the URL; route is specified by $name;
+ * default route is used if none provided.
+ *
+ * @param array $urlOptions Array of key/value pairs used to assemble URL
+ * @param string $name
+ * @param boolean $reset
+ * @param boolean $encode
+ * @return void
+ */
+ public function gotoRoute(array $urlOptions = array(), $name = null, $reset = false, $encode = true)
+ {
+ $this->setGotoRoute($urlOptions, $name, $reset, $encode);
+
+ if ($this->getExit()) {
+ $this->redirectAndExit();
+ }
+ }
+
+ /**
+ * Redirect to a route-based URL, and immediately exit
+ *
+ * Uses route's assemble method to build the URL; route is specified by $name;
+ * default route is used if none provided.
+ *
+ * @param array $urlOptions Array of key/value pairs used to assemble URL
+ * @param string $name
+ * @param boolean $reset
+ * @return void
+ */
+ public function gotoRouteAndExit(array $urlOptions = array(), $name = null, $reset = false)
+ {
+ $this->setGotoRoute($urlOptions, $name, $reset);
+ $this->redirectAndExit();
+ }
+
+ /**
+ * Perform a redirect to a url
+ *
+ * @param string $url
+ * @param array $options
+ * @return void
+ */
+ public function gotoUrl($url, array $options = array())
+ {
+ $this->setGotoUrl($url, $options);
+
+ if ($this->getExit()) {
+ $this->redirectAndExit();
+ }
+ }
+
+ /**
+ * Set a URL string for a redirect, perform redirect, and immediately exit
+ *
+ * @param string $url
+ * @param array $options
+ * @return void
+ */
+ public function gotoUrlAndExit($url, array $options = array())
+ {
+ $this->setGotoUrl($url, $options);
+ $this->redirectAndExit();
+ }
+
+ /**
+ * exit(): Perform exit for redirector
+ *
+ * @return void
+ */
+ public function redirectAndExit()
+ {
+ if ($this->getCloseSessionOnExit()) {
+ // Close session, if started
+ if (class_exists('Zend_Session', false) && Zend_Session::isStarted()) {
+ Zend_Session::writeClose();
+ } elseif (isset($_SESSION)) {
+ session_write_close();
+ }
+ }
+
+ $this->getResponse()->sendHeaders();
+ exit();
+ }
+
+ /**
+ * direct(): Perform helper when called as
+ * $this->_helper->redirector($action, $controller, $module, $params)
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return void
+ */
+ public function direct($action, $controller = null, $module = null, array $params = array())
+ {
+ $this->gotoSimple($action, $controller, $module, $params);
+ }
+
+ /**
+ * Overloading
+ *
+ * Overloading for old 'goto', 'setGoto', and 'gotoAndExit' methods
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Zend_Controller_Action_Exception for invalid methods
+ */
+ public function __call($method, $args)
+ {
+ $method = strtolower($method);
+ if ('goto' == $method) {
+ return call_user_func_array(array($this, 'gotoSimple'), $args);
+ }
+ if ('setgoto' == $method) {
+ return call_user_func_array(array($this, 'setGotoSimple'), $args);
+ }
+ if ('gotoandexit' == $method) {
+ return call_user_func_array(array($this, 'gotoSimpleAndExit'), $args);
+ }
+
+ throw new Zend_Controller_Action_Exception(sprintf('Invalid method "%s" called on redirector', $method));
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/Url.php b/library/vendor/Zend/Controller/Action/Helper/Url.php
new file mode 100644
index 0000000..f98bfa5
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/Url.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * Helper for creating URLs for redirects and other tasks
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_Url extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * Create URL based on default route
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return string
+ */
+ public function simple($action, $controller = null, $module = null, array $params = null)
+ {
+ $request = $this->getRequest();
+
+ if (null === $controller) {
+ $controller = $request->getControllerName();
+ }
+
+ if (null === $module) {
+ $module = $request->getModuleName();
+ }
+
+ $url = $controller . '/' . $action;
+ if ($module != $this->getFrontController()->getDispatcher()->getDefaultModule()) {
+ $url = $module . '/' . $url;
+ }
+
+ if ('' !== ($baseUrl = $this->getFrontController()->getBaseUrl())) {
+ $url = $baseUrl . '/' . $url;
+ }
+
+ if (null !== $params) {
+ $paramPairs = array();
+ foreach ($params as $key => $value) {
+ $paramPairs[] = urlencode($key) . '/' . urlencode($value);
+ }
+ $paramString = implode('/', $paramPairs);
+ $url .= '/' . $paramString;
+ }
+
+ $url = '/' . ltrim($url, '/');
+
+ return $url;
+ }
+
+ /**
+ * Assembles a URL based on a given route
+ *
+ * This method will typically be used for more complex operations, as it
+ * ties into the route objects registered with the router.
+ *
+ * @param array $urlOptions Options passed to the assemble method of the Route object.
+ * @param mixed $name The name of a Route to use. If null it will use the current Route
+ * @param boolean $reset
+ * @param boolean $encode
+ * @return string Url for the link href attribute.
+ */
+ public function url($urlOptions = array(), $name = null, $reset = false, $encode = true)
+ {
+ $router = $this->getFrontController()->getRouter();
+ return $router->assemble($urlOptions, $name, $reset, $encode);
+ }
+
+ /**
+ * Perform helper when called as $this->_helper->url() from an action controller
+ *
+ * Proxies to {@link simple()}
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module
+ * @param array $params
+ * @return string
+ */
+ public function direct($action, $controller = null, $module = null, array $params = null)
+ {
+ return $this->simple($action, $controller, $module, $params);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/Helper/ViewRenderer.php b/library/vendor/Zend/Controller/Action/Helper/ViewRenderer.php
new file mode 100644
index 0000000..948ecc4
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Helper/ViewRenderer.php
@@ -0,0 +1,996 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_Helper_Abstract
+ */
+
+/**
+ * @see Zend_View
+ */
+
+/**
+ * View script integration
+ *
+ * Zend_Controller_Action_Helper_ViewRenderer provides transparent view
+ * integration for action controllers. It allows you to create a view object
+ * once, and populate it throughout all actions. Several global options may be
+ * set:
+ *
+ * - noController: if set true, render() will not look for view scripts in
+ * subdirectories named after the controller
+ * - viewSuffix: what view script filename suffix to use
+ *
+ * The helper autoinitializes the action controller view preDispatch(). It
+ * determines the path to the class file, and then determines the view base
+ * directory from there. It also uses the module name as a class prefix for
+ * helpers and views such that if your module name is 'Search', it will set the
+ * helper class prefix to 'Search_View_Helper' and the filter class prefix to ;
+ * 'Search_View_Filter'.
+ *
+ * Usage:
+ * <code>
+ * // In your bootstrap:
+ * Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_ViewRenderer());
+ *
+ * // In your action controller methods:
+ * $viewHelper = $this->_helper->getHelper('view');
+ *
+ * // Don't use controller subdirectories
+ * $viewHelper->setNoController(true);
+ *
+ * // Specify a different script to render:
+ * $this->_helper->viewRenderer('form');
+ *
+ * </code>
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action_Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_Helper_ViewRenderer extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * @var Zend_View_Interface
+ */
+ public $view;
+
+ /**
+ * Word delimiters
+ * @var array
+ */
+ protected $_delimiters;
+
+ /**
+ * @var Zend_Filter_Inflector
+ */
+ protected $_inflector;
+
+ /**
+ * Inflector target
+ * @var string
+ */
+ protected $_inflectorTarget = '';
+
+ /**
+ * Current module directory
+ * @var string
+ */
+ protected $_moduleDir = '';
+
+ /**
+ * Whether or not to autorender using controller name as subdirectory;
+ * global setting (not reset at next invocation)
+ * @var boolean
+ */
+ protected $_neverController = false;
+
+ /**
+ * Whether or not to autorender postDispatch; global setting (not reset at
+ * next invocation)
+ * @var boolean
+ */
+ protected $_neverRender = false;
+
+ /**
+ * Whether or not to use a controller name as a subdirectory when rendering
+ * @var boolean
+ */
+ protected $_noController = false;
+
+ /**
+ * Whether or not to autorender postDispatch; per controller/action setting (reset
+ * at next invocation)
+ * @var boolean
+ */
+ protected $_noRender = false;
+
+ /**
+ * Characters representing path delimiters in the controller
+ * @var string|array
+ */
+ protected $_pathDelimiters;
+
+ /**
+ * Which named segment of the response to utilize
+ * @var string
+ */
+ protected $_responseSegment = null;
+
+ /**
+ * Which action view script to render
+ * @var string
+ */
+ protected $_scriptAction = null;
+
+ /**
+ * View object basePath
+ * @var string
+ */
+ protected $_viewBasePathSpec = ':moduleDir/views';
+
+ /**
+ * View script path specification string
+ * @var string
+ */
+ protected $_viewScriptPathSpec = ':controller/:action.:suffix';
+
+ /**
+ * View script path specification string, minus controller segment
+ * @var string
+ */
+ protected $_viewScriptPathNoControllerSpec = ':action.:suffix';
+
+ /**
+ * View script suffix
+ * @var string
+ */
+ protected $_viewSuffix = 'phtml';
+
+ /**
+ * Constructor
+ *
+ * Optionally set view object and options.
+ *
+ * @param Zend_View_Interface $view
+ * @param array $options
+ * @return void
+ */
+ public function __construct(Zend_View_Interface $view = null, array $options = array())
+ {
+ if (null !== $view) {
+ $this->setView($view);
+ }
+
+ if (!empty($options)) {
+ $this->_setOptions($options);
+ }
+ }
+
+ /**
+ * Clone - also make sure the view is cloned.
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ if (isset($this->view) && $this->view instanceof Zend_View_Interface) {
+ $this->view = clone $this->view;
+
+ }
+ }
+
+ /**
+ * Set the view object
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setView(Zend_View_Interface $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Get current module name
+ *
+ * @return string
+ */
+ public function getModule()
+ {
+ $request = $this->getRequest();
+ $module = $request->getModuleName();
+ if (null === $module) {
+ $module = $this->getFrontController()->getDispatcher()->getDefaultModule();
+ }
+
+ return $module;
+ }
+
+ /**
+ * Get module directory
+ *
+ * @throws Zend_Controller_Action_Exception
+ * @return string
+ */
+ public function getModuleDirectory()
+ {
+ $module = $this->getModule();
+ $moduleDir = $this->getFrontController()->getControllerDirectory($module);
+ if ((null === $moduleDir) || is_array($moduleDir)) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('ViewRenderer cannot locate module directory for module "' . $module . '"');
+ }
+ $this->_moduleDir = dirname($moduleDir);
+ return $this->_moduleDir;
+ }
+
+ /**
+ * Get inflector
+ *
+ * @return Zend_Filter_Inflector
+ */
+ public function getInflector()
+ {
+ if (null === $this->_inflector) {
+ /**
+ * @see Zend_Filter_Inflector
+ */
+ /**
+ * @see Zend_Filter_PregReplace
+ */
+ /**
+ * @see Zend_Filter_Word_UnderscoreToSeparator
+ */
+ $this->_inflector = new Zend_Filter_Inflector();
+ $this->_inflector->setStaticRuleReference('moduleDir', $this->_moduleDir) // moduleDir must be specified before the less specific 'module'
+ ->addRules(array(
+ ':module' => array('Word_CamelCaseToDash', 'StringToLower'),
+ ':controller' => array('Word_CamelCaseToDash', new Zend_Filter_Word_UnderscoreToSeparator('/'), 'StringToLower', new Zend_Filter_PregReplace('/\./', '-')),
+ ':action' => array('Word_CamelCaseToDash', new Zend_Filter_PregReplace('#[^a-z0-9' . preg_quote('/', '#') . ']+#i', '-'), 'StringToLower'),
+ ))
+ ->setStaticRuleReference('suffix', $this->_viewSuffix)
+ ->setTargetReference($this->_inflectorTarget);
+ }
+
+ // Ensure that module directory is current
+ $this->getModuleDirectory();
+
+ return $this->_inflector;
+ }
+
+ /**
+ * Set inflector
+ *
+ * @param Zend_Filter_Inflector $inflector
+ * @param boolean $reference Whether the moduleDir, target, and suffix should be set as references to ViewRenderer properties
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setInflector(Zend_Filter_Inflector $inflector, $reference = false)
+ {
+ $this->_inflector = $inflector;
+ if ($reference) {
+ $this->_inflector->setStaticRuleReference('suffix', $this->_viewSuffix)
+ ->setStaticRuleReference('moduleDir', $this->_moduleDir)
+ ->setTargetReference($this->_inflectorTarget);
+ }
+ return $this;
+ }
+
+ /**
+ * Set inflector target
+ *
+ * @param string $target
+ * @return void
+ */
+ protected function _setInflectorTarget($target)
+ {
+ $this->_inflectorTarget = (string) $target;
+ }
+
+ /**
+ * Set internal module directory representation
+ *
+ * @param string $dir
+ * @return void
+ */
+ protected function _setModuleDir($dir)
+ {
+ $this->_moduleDir = (string) $dir;
+ }
+
+ /**
+ * Get internal module directory representation
+ *
+ * @return string
+ */
+ protected function _getModuleDir()
+ {
+ return $this->_moduleDir;
+ }
+
+ /**
+ * Generate a class prefix for helper and filter classes
+ *
+ * @return string
+ */
+ protected function _generateDefaultPrefix()
+ {
+ $default = 'Zend_View';
+ if (null === $this->_actionController) {
+ return $default;
+ }
+
+ $class = get_class($this->_actionController);
+
+ if (!strstr($class, '_')) {
+ return $default;
+ }
+
+ $module = $this->getModule();
+ if ('default' == $module) {
+ return $default;
+ }
+
+ $prefix = substr($class, 0, strpos($class, '_')) . '_View';
+
+ return $prefix;
+ }
+
+ /**
+ * Retrieve base path based on location of current action controller
+ *
+ * @return string
+ */
+ protected function _getBasePath()
+ {
+ if (null === $this->_actionController) {
+ return './views';
+ }
+
+ $inflector = $this->getInflector();
+ $this->_setInflectorTarget($this->getViewBasePathSpec());
+
+ $dispatcher = $this->getFrontController()->getDispatcher();
+ $request = $this->getRequest();
+
+ $parts = array(
+ 'module' => (($moduleName = $request->getModuleName()) != '') ? $dispatcher->formatModuleName($moduleName) : $moduleName,
+ 'controller' => $request->getControllerName(),
+ 'action' => $dispatcher->formatActionName($request->getActionName())
+ );
+
+ $path = $inflector->filter($parts);
+ return $path;
+ }
+
+ /**
+ * Set options
+ *
+ * @param array $options
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ protected function _setOptions(array $options)
+ {
+ foreach ($options as $key => $value)
+ {
+ switch ($key) {
+ case 'neverRender':
+ case 'neverController':
+ case 'noController':
+ case 'noRender':
+ $property = '_' . $key;
+ $this->{$property} = ($value) ? true : false;
+ break;
+ case 'responseSegment':
+ case 'scriptAction':
+ case 'viewBasePathSpec':
+ case 'viewScriptPathSpec':
+ case 'viewScriptPathNoControllerSpec':
+ case 'viewSuffix':
+ $property = '_' . $key;
+ $this->{$property} = (string) $value;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Initialize the view object
+ *
+ * $options may contain the following keys:
+ * - neverRender - flag dis/enabling postDispatch() autorender (affects all subsequent calls)
+ * - noController - flag indicating whether or not to look for view scripts in subdirectories named after the controller
+ * - noRender - flag indicating whether or not to autorender postDispatch()
+ * - responseSegment - which named response segment to render a view script to
+ * - scriptAction - what action script to render
+ * - viewBasePathSpec - specification to use for determining view base path
+ * - viewScriptPathSpec - specification to use for determining view script paths
+ * - viewScriptPathNoControllerSpec - specification to use for determining view script paths when noController flag is set
+ * - viewSuffix - what view script filename suffix to use
+ *
+ * @param string $path
+ * @param string $prefix
+ * @param array $options
+ * @throws Zend_Controller_Action_Exception
+ * @return void
+ */
+ public function initView($path = null, $prefix = null, array $options = array())
+ {
+ if (null === $this->view) {
+ $this->setView(new Zend_View());
+ }
+
+ // Reset some flags every time
+ $options['noController'] = (isset($options['noController'])) ? $options['noController'] : false;
+ $options['noRender'] = (isset($options['noRender'])) ? $options['noRender'] : false;
+ $this->_scriptAction = null;
+ $this->_responseSegment = null;
+
+ // Set options first; may be used to determine other initializations
+ $this->_setOptions($options);
+
+ // Get base view path
+ if (empty($path)) {
+ $path = $this->_getBasePath();
+ if (empty($path)) {
+ /**
+ * @see Zend_Controller_Action_Exception
+ */
+ throw new Zend_Controller_Action_Exception('ViewRenderer initialization failed: retrieved view base path is empty');
+ }
+ }
+
+ if (null === $prefix) {
+ $prefix = $this->_generateDefaultPrefix();
+ }
+
+ // Determine if this path has already been registered
+ $currentPaths = $this->view->getScriptPaths();
+ $path = str_replace(array('/', '\\'), '/', $path);
+ $pathExists = false;
+ foreach ($currentPaths as $tmpPath) {
+ $tmpPath = str_replace(array('/', '\\'), '/', $tmpPath);
+ if (strstr($tmpPath, $path)) {
+ $pathExists = true;
+ break;
+ }
+ }
+ if (!$pathExists) {
+ $this->view->addBasePath($path, $prefix);
+ }
+
+ // Register view with action controller (unless already registered)
+ if ((null !== $this->_actionController) && (null === $this->_actionController->view)) {
+ $this->_actionController->view = $this->view;
+ $this->_actionController->viewSuffix = $this->_viewSuffix;
+ }
+ }
+
+ /**
+ * init - initialize view
+ *
+ * @return void
+ */
+ public function init()
+ {
+ if ($this->getFrontController()->getParam('noViewRenderer')) {
+ return;
+ }
+
+ $this->initView();
+ }
+
+ /**
+ * Set view basePath specification
+ *
+ * Specification can contain one or more of the following:
+ * - :moduleDir - current module directory
+ * - :controller - name of current controller in the request
+ * - :action - name of current action in the request
+ * - :module - name of current module in the request
+ *
+ * @param string $path
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setViewBasePathSpec($path)
+ {
+ $this->_viewBasePathSpec = (string) $path;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current view basePath specification string
+ *
+ * @return string
+ */
+ public function getViewBasePathSpec()
+ {
+ return $this->_viewBasePathSpec;
+ }
+
+ /**
+ * Set view script path specification
+ *
+ * Specification can contain one or more of the following:
+ * - :moduleDir - current module directory
+ * - :controller - name of current controller in the request
+ * - :action - name of current action in the request
+ * - :module - name of current module in the request
+ *
+ * @param string $path
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setViewScriptPathSpec($path)
+ {
+ $this->_viewScriptPathSpec = (string) $path;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current view script path specification string
+ *
+ * @return string
+ */
+ public function getViewScriptPathSpec()
+ {
+ return $this->_viewScriptPathSpec;
+ }
+
+ /**
+ * Set view script path specification (no controller variant)
+ *
+ * Specification can contain one or more of the following:
+ * - :moduleDir - current module directory
+ * - :controller - name of current controller in the request
+ * - :action - name of current action in the request
+ * - :module - name of current module in the request
+ *
+ * :controller will likely be ignored in this variant.
+ *
+ * @param string $path
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setViewScriptPathNoControllerSpec($path)
+ {
+ $this->_viewScriptPathNoControllerSpec = (string) $path;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current view script path specification string (no controller variant)
+ *
+ * @return string
+ */
+ public function getViewScriptPathNoControllerSpec()
+ {
+ return $this->_viewScriptPathNoControllerSpec;
+ }
+
+ /**
+ * Get a view script based on an action and/or other variables
+ *
+ * Uses values found in current request if no values passed in $vars.
+ *
+ * If {@link $_noController} is set, uses {@link $_viewScriptPathNoControllerSpec};
+ * otherwise, uses {@link $_viewScriptPathSpec}.
+ *
+ * @param string $action
+ * @param array $vars
+ * @return string
+ */
+ public function getViewScript($action = null, array $vars = array())
+ {
+ $request = $this->getRequest();
+ if ((null === $action) && (!isset($vars['action']))) {
+ $action = $this->getScriptAction();
+ if (null === $action) {
+ $action = $request->getActionName();
+ }
+ $vars['action'] = $action;
+ } elseif (null !== $action) {
+ $vars['action'] = $action;
+ }
+
+ $replacePattern = array('/[^a-z0-9]+$/i', '/^[^a-z0-9]+/i');
+ $vars['action'] = preg_replace($replacePattern, '', $vars['action']);
+
+ $inflector = $this->getInflector();
+ if ($this->getNoController() || $this->getNeverController()) {
+ $this->_setInflectorTarget($this->getViewScriptPathNoControllerSpec());
+ } else {
+ $this->_setInflectorTarget($this->getViewScriptPathSpec());
+ }
+ return $this->_translateSpec($vars);
+ }
+
+ /**
+ * Set the neverRender flag (i.e., globally dis/enable autorendering)
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setNeverRender($flag = true)
+ {
+ $this->_neverRender = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve neverRender flag value
+ *
+ * @return boolean
+ */
+ public function getNeverRender()
+ {
+ return $this->_neverRender;
+ }
+
+ /**
+ * Set the noRender flag (i.e., whether or not to autorender)
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setNoRender($flag = true)
+ {
+ $this->_noRender = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve noRender flag value
+ *
+ * @return boolean
+ */
+ public function getNoRender()
+ {
+ return $this->_noRender;
+ }
+
+ /**
+ * Set the view script to use
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setScriptAction($name)
+ {
+ $this->_scriptAction = (string) $name;
+ return $this;
+ }
+
+ /**
+ * Retrieve view script name
+ *
+ * @return string
+ */
+ public function getScriptAction()
+ {
+ return $this->_scriptAction;
+ }
+
+ /**
+ * Set the response segment name
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setResponseSegment($name)
+ {
+ if (null === $name) {
+ $this->_responseSegment = null;
+ } else {
+ $this->_responseSegment = (string) $name;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve named response segment name
+ *
+ * @return string
+ */
+ public function getResponseSegment()
+ {
+ return $this->_responseSegment;
+ }
+
+ /**
+ * Set the noController flag (i.e., whether or not to render into controller subdirectories)
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setNoController($flag = true)
+ {
+ $this->_noController = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve noController flag value
+ *
+ * @return boolean
+ */
+ public function getNoController()
+ {
+ return $this->_noController;
+ }
+
+ /**
+ * Set the neverController flag (i.e., whether or not to render into controller subdirectories)
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setNeverController($flag = true)
+ {
+ $this->_neverController = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve neverController flag value
+ *
+ * @return boolean
+ */
+ public function getNeverController()
+ {
+ return $this->_neverController;
+ }
+
+ /**
+ * Set view script suffix
+ *
+ * @param string $suffix
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setViewSuffix($suffix)
+ {
+ $this->_viewSuffix = (string) $suffix;
+ return $this;
+ }
+
+ /**
+ * Get view script suffix
+ *
+ * @return string
+ */
+ public function getViewSuffix()
+ {
+ return $this->_viewSuffix;
+ }
+
+ /**
+ * Set options for rendering a view script
+ *
+ * @param string $action View script to render
+ * @param string $name Response named segment to render to
+ * @param boolean $noController Whether or not to render within a subdirectory named after the controller
+ * @return Zend_Controller_Action_Helper_ViewRenderer Provides a fluent interface
+ */
+ public function setRender($action = null, $name = null, $noController = null)
+ {
+ if (null !== $action) {
+ $this->setScriptAction($action);
+ }
+
+ if (null !== $name) {
+ $this->setResponseSegment($name);
+ }
+
+ if (null !== $noController) {
+ $this->setNoController($noController);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Inflect based on provided vars
+ *
+ * Allowed variables are:
+ * - :moduleDir - current module directory
+ * - :module - current module name
+ * - :controller - current controller name
+ * - :action - current action name
+ * - :suffix - view script file suffix
+ *
+ * @param array $vars
+ * @return string
+ */
+ protected function _translateSpec(array $vars = array())
+ {
+ $inflector = $this->getInflector();
+ $request = $this->getRequest();
+ $dispatcher = $this->getFrontController()->getDispatcher();
+
+ // Format module name
+ $module = $dispatcher->formatModuleName($request->getModuleName());
+
+ // Format controller name
+ $filter = new Zend_Filter_Word_CamelCaseToDash();
+ $controller = $filter->filter($request->getControllerName());
+ $controller = $dispatcher->formatControllerName($controller);
+ if ('Controller' == substr($controller, -10)) {
+ $controller = substr($controller, 0, -10);
+ }
+
+ // Format action name
+ $action = $dispatcher->formatActionName($request->getActionName());
+
+ $params = compact('module', 'controller', 'action');
+ foreach ($vars as $key => $value) {
+ switch ($key) {
+ case 'module':
+ case 'controller':
+ case 'action':
+ case 'moduleDir':
+ case 'suffix':
+ $params[$key] = (string) $value;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (isset($params['suffix'])) {
+ $origSuffix = $this->getViewSuffix();
+ $this->setViewSuffix($params['suffix']);
+ }
+ if (isset($params['moduleDir'])) {
+ $origModuleDir = $this->_getModuleDir();
+ $this->_setModuleDir($params['moduleDir']);
+ }
+
+ $filtered = $inflector->filter($params);
+
+ if (isset($params['suffix'])) {
+ $this->setViewSuffix($origSuffix);
+ }
+ if (isset($params['moduleDir'])) {
+ $this->_setModuleDir($origModuleDir);
+ }
+
+ return $filtered;
+ }
+
+ /**
+ * Render a view script (optionally to a named response segment)
+ *
+ * Sets the noRender flag to true when called.
+ *
+ * @param string $script
+ * @param string $name
+ * @return void
+ */
+ public function renderScript($script, $name = null)
+ {
+ if (null === $name) {
+ $name = $this->getResponseSegment();
+ }
+
+ $this->getResponse()->appendBody(
+ $this->view->render($script),
+ $name
+ );
+
+ $this->setNoRender();
+ }
+
+ /**
+ * Render a view based on path specifications
+ *
+ * Renders a view based on the view script path specifications.
+ *
+ * @param string $action
+ * @param string $name
+ * @param boolean $noController
+ * @return void
+ */
+ public function render($action = null, $name = null, $noController = null)
+ {
+ $this->setRender($action, $name, $noController);
+ $path = $this->getViewScript();
+ $this->renderScript($path, $name);
+ }
+
+ /**
+ * Render a script based on specification variables
+ *
+ * Pass an action, and one or more specification variables (view script suffix)
+ * to determine the view script path, and render that script.
+ *
+ * @param string $action
+ * @param array $vars
+ * @param string $name
+ * @return void
+ */
+ public function renderBySpec($action = null, array $vars = array(), $name = null)
+ {
+ if (null !== $name) {
+ $this->setResponseSegment($name);
+ }
+
+ $path = $this->getViewScript($action, $vars);
+
+ $this->renderScript($path);
+ }
+
+ /**
+ * postDispatch - auto render a view
+ *
+ * Only autorenders if:
+ * - _noRender is false
+ * - action controller is present
+ * - request has not been re-dispatched (i.e., _forward() has not been called)
+ * - response is not a redirect
+ *
+ * @return void
+ */
+ public function postDispatch()
+ {
+ if ($this->_shouldRender()) {
+ $this->render();
+ }
+ }
+
+ /**
+ * Should the ViewRenderer render a view script?
+ *
+ * @return boolean
+ */
+ protected function _shouldRender()
+ {
+ return (!$this->getFrontController()->getParam('noViewRenderer')
+ && !$this->_neverRender
+ && !$this->_noRender
+ && (null !== $this->_actionController)
+ && $this->getRequest()->isDispatched()
+ && !$this->getResponse()->isRedirect()
+ );
+ }
+
+ /**
+ * Use this helper as a method; proxies to setRender()
+ *
+ * @param string $action
+ * @param string $name
+ * @param boolean $noController
+ * @return void
+ */
+ public function direct($action = null, $name = null, $noController = null)
+ {
+ $this->setRender($action, $name, $noController);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/HelperBroker.php b/library/vendor/Zend/Controller/Action/HelperBroker.php
new file mode 100644
index 0000000..ff94dca
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/HelperBroker.php
@@ -0,0 +1,373 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Action_HelperBroker_PriorityStack
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_HelperBroker
+{
+ /**
+ * $_actionController - ActionController reference
+ *
+ * @var Zend_Controller_Action
+ */
+ protected $_actionController;
+
+ /**
+ * @var Zend_Loader_PluginLoader_Interface
+ */
+ protected static $_pluginLoader;
+
+ /**
+ * $_helpers - Helper array
+ *
+ * @var Zend_Controller_Action_HelperBroker_PriorityStack
+ */
+ protected static $_stack = null;
+
+ /**
+ * Set PluginLoader for use with broker
+ *
+ * @param Zend_Loader_PluginLoader_Interface $loader
+ * @return void
+ */
+ public static function setPluginLoader($loader)
+ {
+ if ((null !== $loader) && (!$loader instanceof Zend_Loader_PluginLoader_Interface)) {
+ throw new Zend_Controller_Action_Exception('Invalid plugin loader provided to HelperBroker');
+ }
+ self::$_pluginLoader = $loader;
+ }
+
+ /**
+ * Retrieve PluginLoader
+ *
+ * @return Zend_Loader_PluginLoader
+ */
+ public static function getPluginLoader()
+ {
+ if (null === self::$_pluginLoader) {
+ self::$_pluginLoader = new Zend_Loader_PluginLoader(array(
+ 'Zend_Controller_Action_Helper' => 'Zend/Controller/Action/Helper/',
+ ));
+ }
+ return self::$_pluginLoader;
+ }
+
+ /**
+ * addPrefix() - Add repository of helpers by prefix
+ *
+ * @param string $prefix
+ */
+ static public function addPrefix($prefix)
+ {
+ $prefix = rtrim($prefix, '_');
+ $path = str_replace('_', DIRECTORY_SEPARATOR, $prefix);
+ self::getPluginLoader()->addPrefixPath($prefix, $path);
+ }
+
+ /**
+ * addPath() - Add path to repositories where Action_Helpers could be found.
+ *
+ * @param string $path
+ * @param string $prefix Optional; defaults to 'Zend_Controller_Action_Helper'
+ * @return void
+ */
+ static public function addPath($path, $prefix = 'Zend_Controller_Action_Helper')
+ {
+ self::getPluginLoader()->addPrefixPath($prefix, $path);
+ }
+
+ /**
+ * addHelper() - Add helper objects
+ *
+ * @param Zend_Controller_Action_Helper_Abstract $helper
+ * @return void
+ */
+ static public function addHelper(Zend_Controller_Action_Helper_Abstract $helper)
+ {
+ self::getStack()->push($helper);
+ return;
+ }
+
+ /**
+ * resetHelpers()
+ *
+ * @return void
+ */
+ static public function resetHelpers()
+ {
+ self::$_stack = null;
+ return;
+ }
+
+ /**
+ * Retrieve or initialize a helper statically
+ *
+ * Retrieves a helper object statically, loading on-demand if the helper
+ * does not already exist in the stack. Always returns a helper, unless
+ * the helper class cannot be found.
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public static function getStaticHelper($name)
+ {
+ $name = self::_normalizeHelperName($name);
+ $stack = self::getStack();
+
+ if (!isset($stack->{$name})) {
+ self::_loadHelper($name);
+ }
+
+ return $stack->{$name};
+ }
+
+ /**
+ * getExistingHelper() - get helper by name
+ *
+ * Static method to retrieve helper object. Only retrieves helpers already
+ * initialized with the broker (either via addHelper() or on-demand loading
+ * via getHelper()).
+ *
+ * Throws an exception if the referenced helper does not exist in the
+ * stack; use {@link hasHelper()} to check if the helper is registered
+ * prior to retrieving it.
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_Abstract
+ * @throws Zend_Controller_Action_Exception
+ */
+ public static function getExistingHelper($name)
+ {
+ $name = self::_normalizeHelperName($name);
+ $stack = self::getStack();
+
+ if (!isset($stack->{$name})) {
+ throw new Zend_Controller_Action_Exception('Action helper "' . $name . '" has not been registered with the helper broker');
+ }
+
+ return $stack->{$name};
+ }
+
+ /**
+ * Return all registered helpers as helper => object pairs
+ *
+ * @return array
+ */
+ public static function getExistingHelpers()
+ {
+ return self::getStack()->getHelpersByName();
+ }
+
+ /**
+ * Is a particular helper loaded in the broker?
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public static function hasHelper($name)
+ {
+ $name = self::_normalizeHelperName($name);
+ return isset(self::getStack()->{$name});
+ }
+
+ /**
+ * Remove a particular helper from the broker
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public static function removeHelper($name)
+ {
+ $name = self::_normalizeHelperName($name);
+ $stack = self::getStack();
+ if (isset($stack->{$name})) {
+ unset($stack->{$name});
+ }
+
+ return false;
+ }
+
+ /**
+ * Lazy load the priority stack and return it
+ *
+ * @return Zend_Controller_Action_HelperBroker_PriorityStack
+ */
+ public static function getStack()
+ {
+ if (self::$_stack == null) {
+ self::$_stack = new Zend_Controller_Action_HelperBroker_PriorityStack();
+ }
+
+ return self::$_stack;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param Zend_Controller_Action $actionController
+ * @return void
+ */
+ public function __construct(Zend_Controller_Action $actionController)
+ {
+ $this->_actionController = $actionController;
+ foreach (self::getStack() as $helper) {
+ $helper->setActionController($actionController);
+ $helper->init();
+ }
+ }
+
+ /**
+ * notifyPreDispatch() - called by action controller dispatch method
+ *
+ * @return void
+ */
+ public function notifyPreDispatch()
+ {
+ foreach (self::getStack() as $helper) {
+ $helper->preDispatch();
+ }
+ }
+
+ /**
+ * notifyPostDispatch() - called by action controller dispatch method
+ *
+ * @return void
+ */
+ public function notifyPostDispatch()
+ {
+ foreach (self::getStack() as $helper) {
+ $helper->postDispatch();
+ }
+ }
+
+ /**
+ * getHelper() - get helper by name
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function getHelper($name)
+ {
+ $name = self::_normalizeHelperName($name);
+ $stack = self::getStack();
+
+ if (!isset($stack->{$name})) {
+ self::_loadHelper($name);
+ }
+
+ $helper = $stack->{$name};
+
+ $initialize = false;
+ if (null === ($actionController = $helper->getActionController())) {
+ $initialize = true;
+ } elseif ($actionController !== $this->_actionController) {
+ $initialize = true;
+ }
+
+ if ($initialize) {
+ $helper->setActionController($this->_actionController)
+ ->init();
+ }
+
+ return $helper;
+ }
+
+ /**
+ * Method overloading
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Zend_Controller_Action_Exception if helper does not have a direct() method
+ */
+ public function __call($method, $args)
+ {
+ $helper = $this->getHelper($method);
+ if (!method_exists($helper, 'direct')) {
+ throw new Zend_Controller_Action_Exception('Helper "' . $method . '" does not support overloading via direct()');
+ }
+ return call_user_func_array(array($helper, 'direct'), $args);
+ }
+
+ /**
+ * Retrieve helper by name as object property
+ *
+ * @param string $name
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function __get($name)
+ {
+ return $this->getHelper($name);
+ }
+
+ /**
+ * Normalize helper name for lookups
+ *
+ * @param string $name
+ * @return string
+ */
+ protected static function _normalizeHelperName($name)
+ {
+ if (strpos($name, '_') !== false) {
+ $name = str_replace(' ', '', ucwords(str_replace('_', ' ', $name)));
+ }
+
+ return ucfirst($name);
+ }
+
+ /**
+ * Load a helper
+ *
+ * @param string $name
+ * @return void
+ */
+ protected static function _loadHelper($name)
+ {
+ try {
+ $class = self::getPluginLoader()->load($name);
+ } catch (Zend_Loader_PluginLoader_Exception $e) {
+ throw new Zend_Controller_Action_Exception('Action Helper by name ' . $name . ' not found', 0, $e);
+ }
+
+ $helper = new $class();
+
+ if (!$helper instanceof Zend_Controller_Action_Helper_Abstract) {
+ throw new Zend_Controller_Action_Exception('Helper name ' . $name . ' -> class ' . $class . ' is not of type Zend_Controller_Action_Helper_Abstract');
+ }
+
+ self::getStack()->push($helper);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Action/HelperBroker/PriorityStack.php b/library/vendor/Zend/Controller/Action/HelperBroker/PriorityStack.php
new file mode 100644
index 0000000..c957b10
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/HelperBroker/PriorityStack.php
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Action_HelperBroker_PriorityStack implements IteratorAggregate, ArrayAccess, Countable
+{
+
+ protected $_helpersByPriority = array();
+ protected $_helpersByNameRef = array();
+ protected $_nextDefaultPriority = 1;
+
+ /**
+ * Magic property overloading for returning helper by name
+ *
+ * @param string $helperName The helper name
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function __get($helperName)
+ {
+ if (!array_key_exists($helperName, $this->_helpersByNameRef)) {
+ return false;
+ }
+
+ return $this->_helpersByNameRef[$helperName];
+ }
+
+ /**
+ * Magic property overloading for returning if helper is set by name
+ *
+ * @param string $helperName The helper name
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function __isset($helperName)
+ {
+ return array_key_exists($helperName, $this->_helpersByNameRef);
+ }
+
+ /**
+ * Magic property overloading for unsetting if helper is exists by name
+ *
+ * @param string $helperName The helper name
+ */
+ public function __unset($helperName)
+ {
+ $this->offsetUnset($helperName);
+ }
+
+ /**
+ * push helper onto the stack
+ *
+ * @param Zend_Controller_Action_Helper_Abstract $helper
+ * @return Zend_Controller_Action_HelperBroker_PriorityStack
+ */
+ public function push(Zend_Controller_Action_Helper_Abstract $helper)
+ {
+ $this->offsetSet($this->getNextFreeHigherPriority(), $helper);
+ return $this;
+ }
+
+ /**
+ * Return something iterable
+ *
+ * @return Traversable
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayObject($this->_helpersByPriority);
+ }
+
+ /**
+ * offsetExists()
+ *
+ * @param int|string $priorityOrHelperName
+ * @return bool
+ */
+ public function offsetExists($priorityOrHelperName): bool
+ {
+ if (is_string($priorityOrHelperName)) {
+ return array_key_exists($priorityOrHelperName, $this->_helpersByNameRef);
+ } else {
+ return array_key_exists($priorityOrHelperName, $this->_helpersByPriority);
+ }
+ }
+
+ /**
+ * offsetGet()
+ *
+ * @param int|string $priorityOrHelperName
+ * @return Zend_Controller_Action_Helper_Abstract
+ */
+ public function offsetGet($priorityOrHelperName): Zend_Controller_Action_Helper_Abstract
+ {
+ if (!$this->offsetExists($priorityOrHelperName)) {
+ throw new Zend_Controller_Action_Exception('A helper with priority ' . $priorityOrHelperName . ' does not exist.');
+ }
+
+ if (is_string($priorityOrHelperName)) {
+ return $this->_helpersByNameRef[$priorityOrHelperName];
+ } else {
+ return $this->_helpersByPriority[$priorityOrHelperName];
+ }
+ }
+
+ /**
+ * offsetSet()
+ *
+ * @param int $priority
+ * @param Zend_Controller_Action_Helper_Abstract $helper
+ */
+ public function offsetSet($priority, $helper): void
+ {
+ $priority = (int) $priority;
+
+ if (!$helper instanceof Zend_Controller_Action_Helper_Abstract) {
+ throw new Zend_Controller_Action_Exception('$helper must extend Zend_Controller_Action_Helper_Abstract.');
+ }
+
+ if (array_key_exists($helper->getName(), $this->_helpersByNameRef)) {
+ // remove any object with the same name to retain BC compailitbility
+ // @todo At ZF 2.0 time throw an exception here.
+ $this->offsetUnset($helper->getName());
+ }
+
+ if (array_key_exists($priority, $this->_helpersByPriority)) {
+ $priority = $this->getNextFreeHigherPriority($priority); // ensures LIFO
+ trigger_error("A helper with the same priority already exists, reassigning to $priority", E_USER_WARNING);
+ }
+
+ $this->_helpersByPriority[$priority] = $helper;
+ $this->_helpersByNameRef[$helper->getName()] = $helper;
+
+ if ($priority == ($nextFreeDefault = $this->getNextFreeHigherPriority($this->_nextDefaultPriority))) {
+ $this->_nextDefaultPriority = $nextFreeDefault;
+ }
+
+ krsort($this->_helpersByPriority); // always make sure priority and LIFO are both enforced
+ }
+
+ /**
+ * offsetUnset()
+ *
+ * @param int|string $priorityOrHelperName Priority integer or the helper name
+ */
+ public function offsetUnset($priorityOrHelperName): void
+ {
+ if (!$this->offsetExists($priorityOrHelperName)) {
+ throw new Zend_Controller_Action_Exception('A helper with priority or name ' . $priorityOrHelperName . ' does not exist.');
+ }
+
+ if (is_string($priorityOrHelperName)) {
+ $helperName = $priorityOrHelperName;
+ $helper = $this->_helpersByNameRef[$helperName];
+ $priority = array_search($helper, $this->_helpersByPriority, true);
+ } else {
+ $priority = $priorityOrHelperName;
+ $helperName = $this->_helpersByPriority[$priorityOrHelperName]->getName();
+ }
+
+ unset($this->_helpersByNameRef[$helperName]);
+ unset($this->_helpersByPriority[$priority]);
+ }
+
+ /**
+ * return the count of helpers
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return count($this->_helpersByPriority);
+ }
+
+ /**
+ * Find the next free higher priority. If an index is given, it will
+ * find the next free highest priority after it.
+ *
+ * @param int $indexPriority OPTIONAL
+ * @return int
+ */
+ public function getNextFreeHigherPriority($indexPriority = null)
+ {
+ if ($indexPriority == null) {
+ $indexPriority = $this->_nextDefaultPriority;
+ }
+
+ $priorities = array_keys($this->_helpersByPriority);
+
+ while (in_array($indexPriority, $priorities)) {
+ $indexPriority++;
+ }
+
+ return $indexPriority;
+ }
+
+ /**
+ * Find the next free lower priority. If an index is given, it will
+ * find the next free lower priority before it.
+ *
+ * @param int $indexPriority
+ * @return int
+ */
+ public function getNextFreeLowerPriority($indexPriority = null)
+ {
+ if ($indexPriority == null) {
+ $indexPriority = $this->_nextDefaultPriority;
+ }
+
+ $priorities = array_keys($this->_helpersByPriority);
+
+ while (in_array($indexPriority, $priorities)) {
+ $indexPriority--;
+ }
+
+ return $indexPriority;
+ }
+
+ /**
+ * return the highest priority
+ *
+ * @return int
+ */
+ public function getHighestPriority()
+ {
+ return max(array_keys($this->_helpersByPriority));
+ }
+
+ /**
+ * return the lowest priority
+ *
+ * @return int
+ */
+ public function getLowestPriority()
+ {
+ return min(array_keys($this->_helpersByPriority));
+ }
+
+ /**
+ * return the helpers referenced by name
+ *
+ * @return array
+ */
+ public function getHelpersByName()
+ {
+ return $this->_helpersByNameRef;
+ }
+
+}
diff --git a/library/vendor/Zend/Controller/Action/Interface.php b/library/vendor/Zend/Controller/Action/Interface.php
new file mode 100644
index 0000000..db35463
--- /dev/null
+++ b/library/vendor/Zend/Controller/Action/Interface.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Controller_Action_Interface
+{
+ /**
+ * Class constructor
+ *
+ * The request and response objects should be registered with the
+ * controller, as should be any additional optional arguments; these will be
+ * available via {@link getRequest()}, {@link getResponse()}, and
+ * {@link getInvokeArgs()}, respectively.
+ *
+ * When overriding the constructor, please consider this usage as a best
+ * practice and ensure that each is registered appropriately; the easiest
+ * way to do so is to simply call parent::__construct($request, $response,
+ * $invokeArgs).
+ *
+ * After the request, response, and invokeArgs are set, the
+ * {@link $_helper helper broker} is initialized.
+ *
+ * Finally, {@link init()} is called as the final action of
+ * instantiation, and may be safely overridden to perform initialization
+ * tasks; as a general rule, override {@link init()} instead of the
+ * constructor to customize an action controller's instantiation.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param Zend_Controller_Response_Abstract $response
+ * @param array $invokeArgs Any additional invocation arguments
+ * @return void
+ */
+ public function __construct(Zend_Controller_Request_Abstract $request,
+ Zend_Controller_Response_Abstract $response,
+ array $invokeArgs = array());
+
+ /**
+ * Dispatch the requested action
+ *
+ * @param string $action Method name of action
+ * @return void
+ */
+ public function dispatch($action);
+}
diff --git a/library/vendor/Zend/Controller/Dispatcher/Abstract.php b/library/vendor/Zend/Controller/Dispatcher/Abstract.php
new file mode 100644
index 0000000..7c87289
--- /dev/null
+++ b/library/vendor/Zend/Controller/Dispatcher/Abstract.php
@@ -0,0 +1,435 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Controller_Dispatcher_Interface */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Dispatcher_Abstract implements Zend_Controller_Dispatcher_Interface
+{
+ /**
+ * Default action
+ * @var string
+ */
+ protected $_defaultAction = 'index';
+
+ /**
+ * Default controller
+ * @var string
+ */
+ protected $_defaultController = 'index';
+
+ /**
+ * Default module
+ * @var string
+ */
+ protected $_defaultModule = 'default';
+
+ /**
+ * Front Controller instance
+ * @var Zend_Controller_Front
+ */
+ protected $_frontController;
+
+ /**
+ * Array of invocation parameters to use when instantiating action
+ * controllers
+ * @var array
+ */
+ protected $_invokeParams = array();
+
+ /**
+ * Path delimiter character
+ * @var string
+ */
+ protected $_pathDelimiter = '_';
+
+ /**
+ * Response object to pass to action controllers, if any
+ * @var Zend_Controller_Response_Abstract|null
+ */
+ protected $_response = null;
+
+ /**
+ * Word delimiter characters
+ * @var array
+ */
+ protected $_wordDelimiter = array('-', '.');
+
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct(array $params = array())
+ {
+ $this->setParams($params);
+ }
+
+ /**
+ * Formats a string into a controller name. This is used to take a raw
+ * controller name, such as one stored inside a Zend_Controller_Request_Abstract
+ * object, and reformat it to a proper class name that a class extending
+ * Zend_Controller_Action would use.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatControllerName($unformatted)
+ {
+ return ucfirst($this->_formatName($unformatted)) . 'Controller';
+ }
+
+ /**
+ * Formats a string into an action name. This is used to take a raw
+ * action name, such as one that would be stored inside a Zend_Controller_Request_Abstract
+ * object, and reformat into a proper method name that would be found
+ * inside a class extending Zend_Controller_Action.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatActionName($unformatted)
+ {
+ $formatted = $this->_formatName($unformatted, true);
+ return strtolower(substr($formatted, 0, 1)) . substr($formatted, 1) . 'Action';
+ }
+
+ /**
+ * Verify delimiter
+ *
+ * Verify a delimiter to use in controllers or actions. May be a single
+ * string or an array of strings.
+ *
+ * @param string|array $spec
+ * @return array
+ * @throws Zend_Controller_Dispatcher_Exception with invalid delimiters
+ */
+ public function _verifyDelimiter($spec)
+ {
+ if (is_string($spec)) {
+ return (array) $spec;
+ } elseif (is_array($spec)) {
+ $allStrings = true;
+ foreach ($spec as $delim) {
+ if (!is_string($delim)) {
+ $allStrings = false;
+ break;
+ }
+ }
+
+ if (!$allStrings) {
+ throw new Zend_Controller_Dispatcher_Exception('Word delimiter array must contain only strings');
+ }
+
+ return $spec;
+ }
+
+ throw new Zend_Controller_Dispatcher_Exception('Invalid word delimiter');
+ }
+
+ /**
+ * Retrieve the word delimiter character(s) used in
+ * controller or action names
+ *
+ * @return array
+ */
+ public function getWordDelimiter()
+ {
+ return $this->_wordDelimiter;
+ }
+
+ /**
+ * Set word delimiter
+ *
+ * Set the word delimiter to use in controllers and actions. May be a
+ * single string or an array of strings.
+ *
+ * @param string|array $spec
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setWordDelimiter($spec)
+ {
+ $spec = $this->_verifyDelimiter($spec);
+ $this->_wordDelimiter = $spec;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the path delimiter character(s) used in
+ * controller names
+ *
+ * @return array
+ */
+ public function getPathDelimiter()
+ {
+ return $this->_pathDelimiter;
+ }
+
+ /**
+ * Set path delimiter
+ *
+ * Set the path delimiter to use in controllers. May be a single string or
+ * an array of strings.
+ *
+ * @param string $spec
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setPathDelimiter($spec)
+ {
+ if (!is_string($spec)) {
+ throw new Zend_Controller_Dispatcher_Exception('Invalid path delimiter');
+ }
+ $this->_pathDelimiter = $spec;
+
+ return $this;
+ }
+
+ /**
+ * Formats a string from a URI into a PHP-friendly name.
+ *
+ * By default, replaces words separated by the word separator character(s)
+ * with camelCaps. If $isAction is false, it also preserves replaces words
+ * separated by the path separation character with an underscore, making
+ * the following word Title cased. All non-alphanumeric characters are
+ * removed.
+ *
+ * @param string $unformatted
+ * @param boolean $isAction Defaults to false
+ * @return string
+ */
+ protected function _formatName($unformatted, $isAction = false)
+ {
+ // preserve directories
+ if (!$isAction) {
+ $segments = explode($this->getPathDelimiter(), $unformatted);
+ } else {
+ $segments = (array) $unformatted;
+ }
+
+ foreach ($segments as $key => $segment) {
+ $segment = str_replace($this->getWordDelimiter(), ' ', strtolower($segment));
+ $segment = preg_replace('/[^a-z0-9 ]/', '', $segment);
+ $segments[$key] = str_replace(' ', '', ucwords($segment));
+ }
+
+ return implode('_', $segments);
+ }
+
+ /**
+ * Retrieve front controller instance
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController()
+ {
+ if (null === $this->_frontController) {
+ $this->_frontController = Zend_Controller_Front::getInstance();
+ }
+
+ return $this->_frontController;
+ }
+
+ /**
+ * Set front controller instance
+ *
+ * @param Zend_Controller_Front $controller
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setFrontController(Zend_Controller_Front $controller)
+ {
+ $this->_frontController = $controller;
+ return $this;
+ }
+
+ /**
+ * Add or modify a parameter to use when instantiating an action controller
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setParam($name, $value)
+ {
+ $name = (string) $name;
+ $this->_invokeParams[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Set parameters to pass to action controller constructors
+ *
+ * @param array $params
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setParams(array $params)
+ {
+ $this->_invokeParams = array_merge($this->_invokeParams, $params);
+ return $this;
+ }
+
+ /**
+ * Retrieve a single parameter from the controller parameter stack
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getParam($name)
+ {
+ if(isset($this->_invokeParams[$name])) {
+ return $this->_invokeParams[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve action controller instantiation parameters
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->_invokeParams;
+ }
+
+ /**
+ * Clear the controller parameter stack
+ *
+ * By default, clears all parameters. If a parameter name is given, clears
+ * only that parameter; if an array of parameter names is provided, clears
+ * each.
+ *
+ * @param null|string|array single key or array of keys for params to clear
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function clearParams($name = null)
+ {
+ if (null === $name) {
+ $this->_invokeParams = array();
+ } elseif (is_string($name) && isset($this->_invokeParams[$name])) {
+ unset($this->_invokeParams[$name]);
+ } elseif (is_array($name)) {
+ foreach ($name as $key) {
+ if (is_string($key) && isset($this->_invokeParams[$key])) {
+ unset($this->_invokeParams[$key]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set response object to pass to action controllers
+ *
+ * @param Zend_Controller_Response_Abstract|null $response
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setResponse(Zend_Controller_Response_Abstract $response = null)
+ {
+ $this->_response = $response;
+ return $this;
+ }
+
+ /**
+ * Return the registered response object
+ *
+ * @return Zend_Controller_Response_Abstract|null
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Set the default controller (minus any formatting)
+ *
+ * @param string $controller
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setDefaultControllerName($controller)
+ {
+ $this->_defaultController = (string) $controller;
+ return $this;
+ }
+
+ /**
+ * Retrieve the default controller name (minus formatting)
+ *
+ * @return string
+ */
+ public function getDefaultControllerName()
+ {
+ return $this->_defaultController;
+ }
+
+ /**
+ * Set the default action (minus any formatting)
+ *
+ * @param string $action
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setDefaultAction($action)
+ {
+ $this->_defaultAction = (string) $action;
+ return $this;
+ }
+
+ /**
+ * Retrieve the default action name (minus formatting)
+ *
+ * @return string
+ */
+ public function getDefaultAction()
+ {
+ return $this->_defaultAction;
+ }
+
+ /**
+ * Set the default module
+ *
+ * @param string $module
+ * @return Zend_Controller_Dispatcher_Abstract
+ */
+ public function setDefaultModule($module)
+ {
+ $this->_defaultModule = (string) $module;
+ return $this;
+ }
+
+ /**
+ * Retrieve the default module
+ *
+ * @return string
+ */
+ public function getDefaultModule()
+ {
+ return $this->_defaultModule;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Dispatcher/Exception.php b/library/vendor/Zend/Controller/Dispatcher/Exception.php
new file mode 100644
index 0000000..298dfdb
--- /dev/null
+++ b/library/vendor/Zend/Controller/Dispatcher/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Controller_Exception */
+
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Dispatcher_Exception extends Zend_Controller_Exception
+{}
+
diff --git a/library/vendor/Zend/Controller/Dispatcher/Interface.php b/library/vendor/Zend/Controller/Dispatcher/Interface.php
new file mode 100644
index 0000000..1f18df7
--- /dev/null
+++ b/library/vendor/Zend/Controller/Dispatcher/Interface.php
@@ -0,0 +1,204 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Controller_Request_Abstract
+ */
+
+/**
+ * Zend_Controller_Response_Abstract
+ */
+
+/**
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Controller_Dispatcher_Interface
+{
+ /**
+ * Formats a string into a controller name. This is used to take a raw
+ * controller name, such as one that would be packaged inside a request
+ * object, and reformat it to a proper class name that a class extending
+ * Zend_Controller_Action would use.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatControllerName($unformatted);
+
+ /**
+ * Formats a string into a module name. This is used to take a raw
+ * module name, such as one that would be packaged inside a request
+ * object, and reformat it to a proper directory/class name that a class extending
+ * Zend_Controller_Action would use.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatModuleName($unformatted);
+
+ /**
+ * Formats a string into an action name. This is used to take a raw
+ * action name, such as one that would be packaged inside a request
+ * object, and reformat into a proper method name that would be found
+ * inside a class extending Zend_Controller_Action.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatActionName($unformatted);
+
+ /**
+ * Returns TRUE if an action can be dispatched, or FALSE otherwise.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return boolean
+ */
+ public function isDispatchable(Zend_Controller_Request_Abstract $request);
+
+ /**
+ * Add or modify a parameter with which to instantiate an Action Controller
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Controller_Dispatcher_Interface
+ */
+ public function setParam($name, $value);
+
+ /**
+ * Set an array of a parameters to pass to the Action Controller constructor
+ *
+ * @param array $params
+ * @return Zend_Controller_Dispatcher_Interface
+ */
+ public function setParams(array $params);
+
+ /**
+ * Retrieve a single parameter from the controller parameter stack
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getParam($name);
+
+ /**
+ * Retrieve the parameters to pass to the Action Controller constructor
+ *
+ * @return array
+ */
+ public function getParams();
+
+ /**
+ * Clear the controller parameter stack
+ *
+ * By default, clears all parameters. If a parameter name is given, clears
+ * only that parameter; if an array of parameter names is provided, clears
+ * each.
+ *
+ * @param null|string|array single key or array of keys for params to clear
+ * @return Zend_Controller_Dispatcher_Interface
+ */
+ public function clearParams($name = null);
+
+ /**
+ * Set the response object to use, if any
+ *
+ * @param Zend_Controller_Response_Abstract|null $response
+ * @return void
+ */
+ public function setResponse(Zend_Controller_Response_Abstract $response = null);
+
+ /**
+ * Retrieve the response object, if any
+ *
+ * @return Zend_Controller_Response_Abstract|null
+ */
+ public function getResponse();
+
+ /**
+ * Add a controller directory to the controller directory stack
+ *
+ * @param string $path
+ * @param string $args
+ * @return Zend_Controller_Dispatcher_Interface
+ */
+ public function addControllerDirectory($path, $args = null);
+
+ /**
+ * Set the directory where controller files are stored
+ *
+ * Specify a string or an array; if an array is specified, all paths will be
+ * added.
+ *
+ * @param string|array $dir
+ * @return Zend_Controller_Dispatcher_Interface
+ */
+ public function setControllerDirectory($path);
+
+ /**
+ * Return the currently set directory(ies) for controller file lookup
+ *
+ * @return array
+ */
+ public function getControllerDirectory();
+
+ /**
+ * Dispatches a request object to a controller/action. If the action
+ * requests a forward to another action, a new request will be returned.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param Zend_Controller_Response_Abstract $response
+ * @return void
+ */
+ public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response);
+
+ /**
+ * Whether or not a given module is valid
+ *
+ * @param string $module
+ * @return boolean
+ */
+ public function isValidModule($module);
+
+ /**
+ * Retrieve the default module name
+ *
+ * @return string
+ */
+ public function getDefaultModule();
+
+ /**
+ * Retrieve the default controller name
+ *
+ * @return string
+ */
+ public function getDefaultControllerName();
+
+ /**
+ * Retrieve the default action
+ *
+ * @return string
+ */
+ public function getDefaultAction();
+}
diff --git a/library/vendor/Zend/Controller/Dispatcher/Standard.php b/library/vendor/Zend/Controller/Dispatcher/Standard.php
new file mode 100644
index 0000000..d9449c4
--- /dev/null
+++ b/library/vendor/Zend/Controller/Dispatcher/Standard.php
@@ -0,0 +1,504 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Loader */
+
+/** Zend_Controller_Dispatcher_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Dispatcher
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Abstract
+{
+ /**
+ * Current dispatchable directory
+ * @var string
+ */
+ protected $_curDirectory;
+
+ /**
+ * Current module (formatted)
+ * @var string
+ */
+ protected $_curModule;
+
+ /**
+ * Controller directory(ies)
+ * @var array
+ */
+ protected $_controllerDirectory = array();
+
+ /**
+ * Constructor: Set current module to default value
+ *
+ * @param array $params
+ * @return void
+ */
+ public function __construct(array $params = array())
+ {
+ parent::__construct($params);
+ $this->_curModule = $this->getDefaultModule();
+ }
+
+ /**
+ * Add a single path to the controller directory stack
+ *
+ * @param string $path
+ * @param string $module
+ * @return Zend_Controller_Dispatcher_Standard
+ */
+ public function addControllerDirectory($path, $module = null)
+ {
+ if (null === $module) {
+ $module = $this->_defaultModule;
+ }
+
+ $module = (string) $module;
+ $path = rtrim((string) $path, '/\\');
+
+ $this->_controllerDirectory[$module] = $path;
+ return $this;
+ }
+
+ /**
+ * Set controller directory
+ *
+ * @param array|string $directory
+ * @return Zend_Controller_Dispatcher_Standard
+ */
+ public function setControllerDirectory($directory, $module = null)
+ {
+ $this->_controllerDirectory = array();
+
+ if (is_string($directory)) {
+ $this->addControllerDirectory($directory, $module);
+ } elseif (is_array($directory)) {
+ foreach ((array) $directory as $module => $path) {
+ $this->addControllerDirectory($path, $module);
+ }
+ } else {
+ throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the currently set directories for Zend_Controller_Action class
+ * lookup
+ *
+ * If a module is specified, returns just that directory.
+ *
+ * @param string $module Module name
+ * @return array|string Returns array of all directories by default, single
+ * module directory if module argument provided
+ */
+ public function getControllerDirectory($module = null)
+ {
+ if (null === $module) {
+ return $this->_controllerDirectory;
+ }
+
+ $module = (string) $module;
+ if (array_key_exists($module, $this->_controllerDirectory)) {
+ return $this->_controllerDirectory[$module];
+ }
+
+ return null;
+ }
+
+ /**
+ * Remove a controller directory by module name
+ *
+ * @param string $module
+ * @return bool
+ */
+ public function removeControllerDirectory($module)
+ {
+ $module = (string) $module;
+ if (array_key_exists($module, $this->_controllerDirectory)) {
+ unset($this->_controllerDirectory[$module]);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Format the module name.
+ *
+ * @param string $unformatted
+ * @return string
+ */
+ public function formatModuleName($unformatted)
+ {
+ if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) {
+ return $unformatted;
+ }
+
+ return ucfirst($this->_formatName($unformatted));
+ }
+
+ /**
+ * Format action class name
+ *
+ * @param string $moduleName Name of the current module
+ * @param string $className Name of the action class
+ * @return string Formatted class name
+ */
+ public function formatClassName($moduleName, $className)
+ {
+ return $this->formatModuleName($moduleName) . '_' . $className;
+ }
+
+ /**
+ * Convert a class name to a filename
+ *
+ * @param string $class
+ * @return string
+ */
+ public function classToFilename($class)
+ {
+ return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
+ }
+
+ /**
+ * Returns TRUE if the Zend_Controller_Request_Abstract object can be
+ * dispatched to a controller.
+ *
+ * Use this method wisely. By default, the dispatcher will fall back to the
+ * default controller (either in the module specified or the global default)
+ * if a given controller does not exist. This method returning false does
+ * not necessarily indicate the dispatcher will not still dispatch the call.
+ *
+ * @param Zend_Controller_Request_Abstract $action
+ * @return boolean
+ */
+ public function isDispatchable(Zend_Controller_Request_Abstract $request)
+ {
+ $className = $this->getControllerClass($request);
+ if (!$className) {
+ return false;
+ }
+
+ $finalClass = $className;
+ if (($this->_defaultModule != $this->_curModule)
+ || $this->getParam('prefixDefaultModule'))
+ {
+ $finalClass = $this->formatClassName($this->_curModule, $className);
+ }
+ if (class_exists($finalClass, false)) {
+ return true;
+ }
+
+ $fileSpec = $this->classToFilename($className);
+ $dispatchDir = $this->getDispatchDirectory();
+ $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
+ return Zend_Loader::isReadable($test);
+ }
+
+ /**
+ * Dispatch to a controller/action
+ *
+ * By default, if a controller is not dispatchable, dispatch() will throw
+ * an exception. If you wish to use the default controller instead, set the
+ * param 'useDefaultControllerAlways' via {@link setParam()}.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param Zend_Controller_Response_Abstract $response
+ * @return void
+ * @throws Zend_Controller_Dispatcher_Exception
+ */
+ public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
+ {
+ $this->setResponse($response);
+
+ /**
+ * Get controller class
+ */
+ if (!$this->isDispatchable($request)) {
+ $controller = $request->getControllerName();
+ if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) {
+ throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
+ }
+
+ $className = $this->getDefaultControllerClass($request);
+ } else {
+ $className = $this->getControllerClass($request);
+ if (!$className) {
+ $className = $this->getDefaultControllerClass($request);
+ }
+ }
+
+ /**
+ * If we're in a module or prefixDefaultModule is on, we must add the module name
+ * prefix to the contents of $className, as getControllerClass does not do that automatically.
+ * We must keep a separate variable because modules are not strictly PSR-0: We need the no-module-prefix
+ * class name to do the class->file mapping, but the full class name to insantiate the controller
+ */
+ $moduleClassName = $className;
+ if (($this->_defaultModule != $this->_curModule)
+ || $this->getParam('prefixDefaultModule'))
+ {
+ $moduleClassName = $this->formatClassName($this->_curModule, $className);
+ }
+
+ /**
+ * Load the controller class file
+ */
+ $className = $this->loadClass($className);
+
+ /**
+ * Instantiate controller with request, response, and invocation
+ * arguments; throw exception if it's not an action controller
+ */
+ $controller = new $moduleClassName($request, $this->getResponse(), $this->getParams());
+ if (!($controller instanceof Zend_Controller_Action_Interface) &&
+ !($controller instanceof Zend_Controller_Action)) {
+ throw new Zend_Controller_Dispatcher_Exception(
+ 'Controller "' . $moduleClassName . '" is not an instance of Zend_Controller_Action_Interface'
+ );
+ }
+
+ /**
+ * Retrieve the action name
+ */
+ $action = $this->getActionMethod($request);
+
+ /**
+ * Dispatch the method call
+ */
+ $request->setDispatched(true);
+
+ // by default, buffer output
+ $disableOb = $this->getParam('disableOutputBuffering');
+ $obLevel = ob_get_level();
+ if (empty($disableOb)) {
+ ob_start();
+ }
+
+ try {
+ $controller->dispatch($action);
+ } catch (Exception $e) {
+ // Clean output buffer on error
+ $curObLevel = ob_get_level();
+ if ($curObLevel > $obLevel) {
+ do {
+ ob_get_clean();
+ $curObLevel = ob_get_level();
+ } while ($curObLevel > $obLevel);
+ }
+ throw $e;
+ }
+
+ if (empty($disableOb)) {
+ $content = ob_get_clean();
+ $response->appendBody($content);
+ }
+
+ // Destroy the page controller instance and reflection objects
+ $controller = null;
+ }
+
+ /**
+ * Load a controller class
+ *
+ * Attempts to load the controller class file from
+ * {@link getControllerDirectory()}. If the controller belongs to a
+ * module, looks for the module prefix to the controller class.
+ *
+ * @param string $className
+ * @return string Class name loaded
+ * @throws Zend_Controller_Dispatcher_Exception if class not loaded
+ */
+ public function loadClass($className)
+ {
+ $finalClass = $className;
+ if (($this->_defaultModule != $this->_curModule)
+ || $this->getParam('prefixDefaultModule'))
+ {
+ $finalClass = $this->formatClassName($this->_curModule, $className);
+ }
+ if (class_exists($finalClass, false)) {
+ return $finalClass;
+ }
+
+ $dispatchDir = $this->getDispatchDirectory();
+ $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className);
+
+ if (Zend_Loader::isReadable($loadFile)) {
+ include_once $loadFile;
+ } else {
+ throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'");
+ }
+
+ if (!class_exists($finalClass, false)) {
+ throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")');
+ }
+
+ return $finalClass;
+ }
+
+ /**
+ * Get controller class name
+ *
+ * Try request first; if not found, try pulling from request parameter;
+ * if still not found, fallback to default
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return string|false Returns class name on success
+ */
+ public function getControllerClass(Zend_Controller_Request_Abstract $request)
+ {
+ $controllerName = $request->getControllerName();
+ if (empty($controllerName)) {
+ if (!$this->getParam('useDefaultControllerAlways')) {
+ return false;
+ }
+ $controllerName = $this->getDefaultControllerName();
+ $request->setControllerName($controllerName);
+ }
+
+ $className = $this->formatControllerName($controllerName);
+
+ $controllerDirs = $this->getControllerDirectory();
+ $module = $request->getModuleName();
+ if ($this->isValidModule($module)) {
+ $this->_curModule = $module;
+ $this->_curDirectory = $controllerDirs[$module];
+ } elseif ($this->isValidModule($this->_defaultModule)) {
+ $request->setModuleName($this->_defaultModule);
+ $this->_curModule = $this->_defaultModule;
+ $this->_curDirectory = $controllerDirs[$this->_defaultModule];
+ } else {
+ throw new Zend_Controller_Exception('No default module defined for this application');
+ }
+
+ return $className;
+ }
+
+ /**
+ * Determine if a given module is valid
+ *
+ * @param string $module
+ * @return bool
+ */
+ public function isValidModule($module)
+ {
+ if (!is_string($module)) {
+ return false;
+ }
+
+ $module = strtolower($module);
+ $controllerDir = $this->getControllerDirectory();
+ foreach (array_keys($controllerDir) as $moduleName) {
+ if ($module == strtolower($moduleName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve default controller class
+ *
+ * Determines whether the default controller to use lies within the
+ * requested module, or if the global default should be used.
+ *
+ * By default, will only use the module default unless that controller does
+ * not exist; if this is the case, it falls back to the default controller
+ * in the default module.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return string
+ */
+ public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request)
+ {
+ $controller = $this->getDefaultControllerName();
+ $default = $this->formatControllerName($controller);
+ $request->setControllerName($controller)
+ ->setActionName(null);
+
+ $module = $request->getModuleName();
+ $controllerDirs = $this->getControllerDirectory();
+ $this->_curModule = $this->_defaultModule;
+ $this->_curDirectory = $controllerDirs[$this->_defaultModule];
+ if ($this->isValidModule($module)) {
+ $found = false;
+ if (class_exists($default, false)) {
+ $found = true;
+ } else {
+ $moduleDir = $controllerDirs[$module];
+ $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default);
+ if (Zend_Loader::isReadable($fileSpec)) {
+ $found = true;
+ $this->_curDirectory = $moduleDir;
+ }
+ }
+ if ($found) {
+ $request->setModuleName($module);
+ $this->_curModule = $this->formatModuleName($module);
+ }
+ } else {
+ $request->setModuleName($this->_defaultModule);
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return the value of the currently selected dispatch directory (as set by
+ * {@link getController()})
+ *
+ * @return string
+ */
+ public function getDispatchDirectory()
+ {
+ return $this->_curDirectory;
+ }
+
+ /**
+ * Determine the action name
+ *
+ * First attempt to retrieve from request; then from request params
+ * using action key; default to default action
+ *
+ * Returns formatted action name
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return string
+ */
+ public function getActionMethod(Zend_Controller_Request_Abstract $request)
+ {
+ $action = $request->getActionName();
+ if (empty($action)) {
+ $action = $this->getDefaultAction();
+ $request->setActionName($action);
+ }
+
+ return $this->formatActionName($action);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Exception.php b/library/vendor/Zend/Controller/Exception.php
new file mode 100644
index 0000000..861289f
--- /dev/null
+++ b/library/vendor/Zend/Controller/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Exception */
+
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Exception extends Zend_Exception
+{}
+
diff --git a/library/vendor/Zend/Controller/Front.php b/library/vendor/Zend/Controller/Front.php
new file mode 100644
index 0000000..e5bb40a
--- /dev/null
+++ b/library/vendor/Zend/Controller/Front.php
@@ -0,0 +1,977 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Loader */
+
+/** Zend_Controller_Action_HelperBroker */
+
+/** Zend_Controller_Plugin_Broker */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Front
+{
+ /**
+ * Base URL
+ * @var string
+ */
+ protected $_baseUrl = null;
+
+ /**
+ * Directory|ies where controllers are stored
+ *
+ * @var string|array
+ */
+ protected $_controllerDir = null;
+
+ /**
+ * Instance of Zend_Controller_Dispatcher_Interface
+ * @var Zend_Controller_Dispatcher_Interface
+ */
+ protected $_dispatcher = null;
+
+ /**
+ * Singleton instance
+ *
+ * Marked only as protected to allow extension of the class. To extend,
+ * simply override {@link getInstance()}.
+ *
+ * @var Zend_Controller_Front
+ */
+ protected static $_instance = null;
+
+ /**
+ * Array of invocation parameters to use when instantiating action
+ * controllers
+ * @var array
+ */
+ protected $_invokeParams = array();
+
+ /**
+ * Subdirectory within a module containing controllers; defaults to 'controllers'
+ * @var string
+ */
+ protected $_moduleControllerDirectoryName = 'controllers';
+
+ /**
+ * Instance of Zend_Controller_Plugin_Broker
+ * @var Zend_Controller_Plugin_Broker
+ */
+ protected $_plugins = null;
+
+ /**
+ * Instance of Zend_Controller_Request_Abstract
+ * @var Zend_Controller_Request_Abstract
+ */
+ protected $_request = null;
+
+ /**
+ * Instance of Zend_Controller_Response_Abstract
+ * @var Zend_Controller_Response_Abstract
+ */
+ protected $_response = null;
+
+ /**
+ * Whether or not to return the response prior to rendering output while in
+ * {@link dispatch()}; default is to send headers and render output.
+ * @var boolean
+ */
+ protected $_returnResponse = false;
+
+ /**
+ * Instance of Zend_Controller_Router_Interface
+ * @var Zend_Controller_Router_Interface
+ */
+ protected $_router = null;
+
+ /**
+ * Whether or not exceptions encountered in {@link dispatch()} should be
+ * thrown or trapped in the response object
+ * @var boolean
+ */
+ protected $_throwExceptions = false;
+
+ /**
+ * Constructor
+ *
+ * Instantiate using {@link getInstance()}; front controller is a singleton
+ * object.
+ *
+ * Instantiates the plugin broker.
+ *
+ * @return void
+ */
+ protected function __construct()
+ {
+ $this->_plugins = new Zend_Controller_Plugin_Broker();
+ }
+
+ /**
+ * Enforce singleton; disallow cloning
+ *
+ * @return void
+ */
+ private function __clone()
+ {
+ }
+
+ /**
+ * Singleton instance
+ *
+ * @return Zend_Controller_Front
+ */
+ public static function getInstance()
+ {
+ if (null === self::$_instance) {
+ self::$_instance = new self();
+ }
+
+ return self::$_instance;
+ }
+
+ /**
+ * Resets all object properties of the singleton instance
+ *
+ * Primarily used for testing; could be used to chain front controllers.
+ *
+ * Also resets action helper broker, clearing all registered helpers.
+ *
+ * @return void
+ */
+ public function resetInstance()
+ {
+ $reflection = new ReflectionObject($this);
+ foreach ($reflection->getProperties() as $property) {
+ $name = $property->getName();
+ switch ($name) {
+ case '_instance':
+ break;
+ case '_controllerDir':
+ case '_invokeParams':
+ $this->{$name} = array();
+ break;
+ case '_plugins':
+ $this->{$name} = new Zend_Controller_Plugin_Broker();
+ break;
+ case '_throwExceptions':
+ case '_returnResponse':
+ $this->{$name} = false;
+ break;
+ case '_moduleControllerDirectoryName':
+ $this->{$name} = 'controllers';
+ break;
+ default:
+ $this->{$name} = null;
+ break;
+ }
+ }
+ Zend_Controller_Action_HelperBroker::resetHelpers();
+ }
+
+ /**
+ * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch()
+ *
+ * In PHP 5.1.x, a call to a static method never populates $this -- so run()
+ * may actually be called after setting up your front controller.
+ *
+ * @param string|array $controllerDirectory Path to Zend_Controller_Action
+ * controller classes or array of such paths
+ * @return void
+ * @throws Zend_Controller_Exception if called from an object instance
+ */
+ public static function run($controllerDirectory)
+ {
+ self::getInstance()
+ ->setControllerDirectory($controllerDirectory)
+ ->dispatch();
+ }
+
+ /**
+ * Add a controller directory to the controller directory stack
+ *
+ * If $args is presented and is a string, uses it for the array key mapping
+ * to the directory specified.
+ *
+ * @param string $directory
+ * @param string $module Optional argument; module with which to associate directory. If none provided, assumes 'default'
+ * @return Zend_Controller_Front
+ * @throws Zend_Controller_Exception if directory not found or readable
+ */
+ public function addControllerDirectory($directory, $module = null)
+ {
+ $this->getDispatcher()->addControllerDirectory($directory, $module);
+ return $this;
+ }
+
+ /**
+ * Set controller directory
+ *
+ * Stores controller directory(ies) in dispatcher. May be an array of
+ * directories or a string containing a single directory.
+ *
+ * @param string|array $directory Path to Zend_Controller_Action controller
+ * classes or array of such paths
+ * @param string $module Optional module name to use with string $directory
+ * @return Zend_Controller_Front
+ */
+ public function setControllerDirectory($directory, $module = null)
+ {
+ $this->getDispatcher()->setControllerDirectory($directory, $module);
+ return $this;
+ }
+
+ /**
+ * Retrieve controller directory
+ *
+ * Retrieves:
+ * - Array of all controller directories if no $name passed
+ * - String path if $name passed and exists as a key in controller directory array
+ * - null if $name passed but does not exist in controller directory keys
+ *
+ * @param string $name Default null
+ * @return array|string|null
+ */
+ public function getControllerDirectory($name = null)
+ {
+ return $this->getDispatcher()->getControllerDirectory($name);
+ }
+
+ /**
+ * Remove a controller directory by module name
+ *
+ * @param string $module
+ * @return bool
+ */
+ public function removeControllerDirectory($module)
+ {
+ return $this->getDispatcher()->removeControllerDirectory($module);
+ }
+
+ /**
+ * Specify a directory as containing modules
+ *
+ * Iterates through the directory, adding any subdirectories as modules;
+ * the subdirectory within each module named after {@link $_moduleControllerDirectoryName}
+ * will be used as the controller directory path.
+ *
+ * @param string $path
+ * @return Zend_Controller_Front
+ */
+ public function addModuleDirectory($path)
+ {
+ try{
+ $dir = new DirectoryIterator($path);
+ } catch(Exception $e) {
+ throw new Zend_Controller_Exception("Directory $path not readable", 0, $e);
+ }
+ foreach ($dir as $file) {
+ if ($file->isDot() || !$file->isDir()) {
+ continue;
+ }
+
+ $module = $file->getFilename();
+
+ // Don't use SCCS directories as modules
+ if (preg_match('/^[^a-z]/i', $module) || ('CVS' == $module)) {
+ continue;
+ }
+
+ $moduleDir = $file->getPathname() . DIRECTORY_SEPARATOR . $this->getModuleControllerDirectoryName();
+ $this->addControllerDirectory($moduleDir, $module);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the path to a module directory (but not the controllers directory within)
+ *
+ * @param string $module
+ * @return string|null
+ */
+ public function getModuleDirectory($module = null)
+ {
+ if (null === $module) {
+ $request = $this->getRequest();
+ if (null !== $request) {
+ $module = $this->getRequest()->getModuleName();
+ }
+ if (empty($module)) {
+ $module = $this->getDispatcher()->getDefaultModule();
+ }
+ }
+
+ $controllerDir = $this->getControllerDirectory($module);
+
+ if ((null === $controllerDir) || !is_string($controllerDir)) {
+ return null;
+ }
+
+ return dirname($controllerDir);
+ }
+
+ /**
+ * Set the directory name within a module containing controllers
+ *
+ * @param string $name
+ * @return Zend_Controller_Front
+ */
+ public function setModuleControllerDirectoryName($name = 'controllers')
+ {
+ $this->_moduleControllerDirectoryName = (string) $name;
+
+ return $this;
+ }
+
+ /**
+ * Return the directory name within a module containing controllers
+ *
+ * @return string
+ */
+ public function getModuleControllerDirectoryName()
+ {
+ return $this->_moduleControllerDirectoryName;
+ }
+
+ /**
+ * Set the default controller (unformatted string)
+ *
+ * @param string $controller
+ * @return Zend_Controller_Front
+ */
+ public function setDefaultControllerName($controller)
+ {
+ $dispatcher = $this->getDispatcher();
+ $dispatcher->setDefaultControllerName($controller);
+ return $this;
+ }
+
+ /**
+ * Retrieve the default controller (unformatted string)
+ *
+ * @return string
+ */
+ public function getDefaultControllerName()
+ {
+ return $this->getDispatcher()->getDefaultControllerName();
+ }
+
+ /**
+ * Set the default action (unformatted string)
+ *
+ * @param string $action
+ * @return Zend_Controller_Front
+ */
+ public function setDefaultAction($action)
+ {
+ $dispatcher = $this->getDispatcher();
+ $dispatcher->setDefaultAction($action);
+ return $this;
+ }
+
+ /**
+ * Retrieve the default action (unformatted string)
+ *
+ * @return string
+ */
+ public function getDefaultAction()
+ {
+ return $this->getDispatcher()->getDefaultAction();
+ }
+
+ /**
+ * Set the default module name
+ *
+ * @param string $module
+ * @return Zend_Controller_Front
+ */
+ public function setDefaultModule($module)
+ {
+ $dispatcher = $this->getDispatcher();
+ $dispatcher->setDefaultModule($module);
+ return $this;
+ }
+
+ /**
+ * Retrieve the default module
+ *
+ * @return string
+ */
+ public function getDefaultModule()
+ {
+ return $this->getDispatcher()->getDefaultModule();
+ }
+
+ /**
+ * Set request class/object
+ *
+ * Set the request object. The request holds the request environment.
+ *
+ * If a class name is provided, it will instantiate it
+ *
+ * @param string|Zend_Controller_Request_Abstract $request
+ * @throws Zend_Controller_Exception if invalid request class
+ * @return Zend_Controller_Front
+ */
+ public function setRequest($request)
+ {
+ if (is_string($request)) {
+ if (!class_exists($request)) {
+ Zend_Loader::loadClass($request);
+ }
+ $request = new $request();
+ }
+ if (!$request instanceof Zend_Controller_Request_Abstract) {
+ throw new Zend_Controller_Exception('Invalid request class');
+ }
+
+ $this->_request = $request;
+
+ return $this;
+ }
+
+ /**
+ * Return the request object.
+ *
+ * @return null|Zend_Controller_Request_Abstract
+ */
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+ /**
+ * Set router class/object
+ *
+ * Set the router object. The router is responsible for mapping
+ * the request to a controller and action.
+ *
+ * If a class name is provided, instantiates router with any parameters
+ * registered via {@link setParam()} or {@link setParams()}.
+ *
+ * @param string|Zend_Controller_Router_Interface $router
+ * @throws Zend_Controller_Exception if invalid router class
+ * @return Zend_Controller_Front
+ */
+ public function setRouter($router)
+ {
+ if (is_string($router)) {
+ if (!class_exists($router)) {
+ Zend_Loader::loadClass($router);
+ }
+ $router = new $router();
+ }
+
+ if (!$router instanceof Zend_Controller_Router_Interface) {
+ throw new Zend_Controller_Exception('Invalid router class');
+ }
+
+ $router->setFrontController($this);
+ $this->_router = $router;
+
+ return $this;
+ }
+
+ /**
+ * Return the router object.
+ *
+ * Instantiates a Zend_Controller_Router_Rewrite object if no router currently set.
+ *
+ * @return Zend_Controller_Router_Interface
+ */
+ public function getRouter()
+ {
+ if (null == $this->_router) {
+ $this->setRouter(new Zend_Controller_Router_Rewrite());
+ }
+
+ return $this->_router;
+ }
+
+ /**
+ * Set the base URL used for requests
+ *
+ * Use to set the base URL segment of the REQUEST_URI to use when
+ * determining PATH_INFO, etc. Examples:
+ * - /admin
+ * - /myapp
+ * - /subdir/index.php
+ *
+ * Note that the URL should not include the full URI. Do not use:
+ * - http://example.com/admin
+ * - http://example.com/myapp
+ * - http://example.com/subdir/index.php
+ *
+ * If a null value is passed, this can be used as well for autodiscovery (default).
+ *
+ * @param string $base
+ * @return Zend_Controller_Front
+ * @throws Zend_Controller_Exception for non-string $base
+ */
+ public function setBaseUrl($base = null)
+ {
+ if (!is_string($base) && (null !== $base)) {
+ throw new Zend_Controller_Exception('Rewrite base must be a string');
+ }
+
+ $this->_baseUrl = $base;
+
+ if ((null !== ($request = $this->getRequest())) && (method_exists($request, 'setBaseUrl'))) {
+ $request->setBaseUrl($base);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve the currently set base URL
+ *
+ * @return string
+ */
+ public function getBaseUrl()
+ {
+ $request = $this->getRequest();
+ if ((null !== $request) && method_exists($request, 'getBaseUrl')) {
+ return $request->getBaseUrl();
+ }
+
+ return $this->_baseUrl;
+ }
+
+ /**
+ * Set the dispatcher object. The dispatcher is responsible for
+ * taking a Zend_Controller_Dispatcher_Token object, instantiating the controller, and
+ * call the action method of the controller.
+ *
+ * @param Zend_Controller_Dispatcher_Interface $dispatcher
+ * @return Zend_Controller_Front
+ */
+ public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher)
+ {
+ $this->_dispatcher = $dispatcher;
+ return $this;
+ }
+
+ /**
+ * Return the dispatcher object.
+ *
+ * @return Zend_Controller_Dispatcher_Interface
+ */
+ public function getDispatcher()
+ {
+ /**
+ * Instantiate the default dispatcher if one was not set.
+ */
+ if (!$this->_dispatcher instanceof Zend_Controller_Dispatcher_Interface) {
+ $this->_dispatcher = new Zend_Controller_Dispatcher_Standard();
+ }
+ return $this->_dispatcher;
+ }
+
+ /**
+ * Set response class/object
+ *
+ * Set the response object. The response is a container for action
+ * responses and headers. Usage is optional.
+ *
+ * If a class name is provided, instantiates a response object.
+ *
+ * @param string|Zend_Controller_Response_Abstract $response
+ * @throws Zend_Controller_Exception if invalid response class
+ * @return Zend_Controller_Front
+ */
+ public function setResponse($response)
+ {
+ if (is_string($response)) {
+ if (!class_exists($response)) {
+ Zend_Loader::loadClass($response);
+ }
+ $response = new $response();
+ }
+ if (!$response instanceof Zend_Controller_Response_Abstract) {
+ throw new Zend_Controller_Exception('Invalid response class');
+ }
+
+ $this->_response = $response;
+
+ return $this;
+ }
+
+ /**
+ * Return the response object.
+ *
+ * @return null|Zend_Controller_Response_Abstract
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Add or modify a parameter to use when instantiating an action controller
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Controller_Front
+ */
+ public function setParam($name, $value)
+ {
+ $name = (string) $name;
+ $this->_invokeParams[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Set parameters to pass to action controller constructors
+ *
+ * @param array $params
+ * @return Zend_Controller_Front
+ */
+ public function setParams(array $params)
+ {
+ $this->_invokeParams = array_merge($this->_invokeParams, $params);
+ return $this;
+ }
+
+ /**
+ * Retrieve a single parameter from the controller parameter stack
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getParam($name)
+ {
+ if(isset($this->_invokeParams[$name])) {
+ return $this->_invokeParams[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve action controller instantiation parameters
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->_invokeParams;
+ }
+
+ /**
+ * Clear the controller parameter stack
+ *
+ * By default, clears all parameters. If a parameter name is given, clears
+ * only that parameter; if an array of parameter names is provided, clears
+ * each.
+ *
+ * @param null|string|array single key or array of keys for params to clear
+ * @return Zend_Controller_Front
+ */
+ public function clearParams($name = null)
+ {
+ if (null === $name) {
+ $this->_invokeParams = array();
+ } elseif (is_string($name) && isset($this->_invokeParams[$name])) {
+ unset($this->_invokeParams[$name]);
+ } elseif (is_array($name)) {
+ foreach ($name as $key) {
+ if (is_string($key) && isset($this->_invokeParams[$key])) {
+ unset($this->_invokeParams[$key]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Register a plugin.
+ *
+ * @param Zend_Controller_Plugin_Abstract $plugin
+ * @param int $stackIndex Optional; stack index for plugin
+ * @return Zend_Controller_Front
+ */
+ public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin, $stackIndex = null)
+ {
+ $this->_plugins->registerPlugin($plugin, $stackIndex);
+ return $this;
+ }
+
+ /**
+ * Unregister a plugin.
+ *
+ * @param string|Zend_Controller_Plugin_Abstract $plugin Plugin class or object to unregister
+ * @return Zend_Controller_Front
+ */
+ public function unregisterPlugin($plugin)
+ {
+ $this->_plugins->unregisterPlugin($plugin);
+ return $this;
+ }
+
+ /**
+ * Is a particular plugin registered?
+ *
+ * @param string $class
+ * @return bool
+ */
+ public function hasPlugin($class)
+ {
+ return $this->_plugins->hasPlugin($class);
+ }
+
+ /**
+ * Retrieve a plugin or plugins by class
+ *
+ * @param string $class
+ * @return false|Zend_Controller_Plugin_Abstract|array
+ */
+ public function getPlugin($class)
+ {
+ return $this->_plugins->getPlugin($class);
+ }
+
+ /**
+ * Retrieve all plugins
+ *
+ * @return array
+ */
+ public function getPlugins()
+ {
+ return $this->_plugins->getPlugins();
+ }
+
+ /**
+ * Set the throwExceptions flag and retrieve current status
+ *
+ * Set whether exceptions encounted in the dispatch loop should be thrown
+ * or caught and trapped in the response object.
+ *
+ * Default behaviour is to trap them in the response object; call this
+ * method to have them thrown.
+ *
+ * Passing no value will return the current value of the flag; passing a
+ * boolean true or false value will set the flag and return the current
+ * object instance.
+ *
+ * @param boolean $flag Defaults to null (return flag state)
+ * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean
+ */
+ public function throwExceptions($flag = null)
+ {
+ if ($flag !== null) {
+ $this->_throwExceptions = (bool) $flag;
+ return $this;
+ }
+
+ return $this->_throwExceptions;
+ }
+
+ /**
+ * Set whether {@link dispatch()} should return the response without first
+ * rendering output. By default, output is rendered and dispatch() returns
+ * nothing.
+ *
+ * @param boolean $flag
+ * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean
+ */
+ public function returnResponse($flag = null)
+ {
+ if (true === $flag) {
+ $this->_returnResponse = true;
+ return $this;
+ } elseif (false === $flag) {
+ $this->_returnResponse = false;
+ return $this;
+ }
+
+ return $this->_returnResponse;
+ }
+
+ /**
+ * Dispatch an HTTP request to a controller/action.
+ *
+ * @param Zend_Controller_Request_Abstract|null $request
+ * @param Zend_Controller_Response_Abstract|null $response
+ * @return void|Zend_Controller_Response_Abstract Returns response object if returnResponse() is true
+ */
+ public function dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null)
+ {
+ if (!$this->getParam('noErrorHandler') && !$this->_plugins->hasPlugin('Zend_Controller_Plugin_ErrorHandler')) {
+ // Register with stack index of 100
+ $this->_plugins->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(), 100);
+ }
+
+ if (!$this->getParam('noViewRenderer') && !Zend_Controller_Action_HelperBroker::hasHelper('viewRenderer')) {
+ Zend_Controller_Action_HelperBroker::getStack()->offsetSet(-80, new Zend_Controller_Action_Helper_ViewRenderer());
+ }
+
+ /**
+ * Instantiate default request object (HTTP version) if none provided
+ */
+ if (null !== $request) {
+ $this->setRequest($request);
+ } elseif ((null === $request) && (null === ($request = $this->getRequest()))) {
+ $request = new Zend_Controller_Request_Http();
+ $this->setRequest($request);
+ }
+
+ /**
+ * Set base URL of request object, if available
+ */
+ if (is_callable(array($this->_request, 'setBaseUrl'))) {
+ if (null !== $this->_baseUrl) {
+ $this->_request->setBaseUrl($this->_baseUrl);
+ }
+ }
+
+ /**
+ * Instantiate default response object (HTTP version) if none provided
+ */
+ if (null !== $response) {
+ $this->setResponse($response);
+ } elseif ((null === $this->_response) && (null === ($this->_response = $this->getResponse()))) {
+ $response = new Zend_Controller_Response_Http();
+ $this->setResponse($response);
+ }
+
+ /**
+ * Register request and response objects with plugin broker
+ */
+ $this->_plugins
+ ->setRequest($this->_request)
+ ->setResponse($this->_response);
+
+ /**
+ * Initialize router
+ */
+ $router = $this->getRouter();
+ $router->setParams($this->getParams());
+
+ /**
+ * Initialize dispatcher
+ */
+ $dispatcher = $this->getDispatcher();
+ $dispatcher->setParams($this->getParams())
+ ->setResponse($this->_response);
+
+ // Begin dispatch
+ try {
+ /**
+ * Route request to controller/action, if a router is provided
+ */
+
+ /**
+ * Notify plugins of router startup
+ */
+ $this->_plugins->routeStartup($this->_request);
+
+ try {
+ $router->route($this->_request);
+ } catch (Exception $e) {
+ if ($this->throwExceptions()) {
+ throw $e;
+ }
+
+ $this->_response->setException($e);
+ }
+
+ /**
+ * Notify plugins of router completion
+ */
+ $this->_plugins->routeShutdown($this->_request);
+
+ /**
+ * Notify plugins of dispatch loop startup
+ */
+ $this->_plugins->dispatchLoopStartup($this->_request);
+
+ /**
+ * Attempt to dispatch the controller/action. If the $this->_request
+ * indicates that it needs to be dispatched, move to the next
+ * action in the request.
+ */
+ do {
+ $this->_request->setDispatched(true);
+
+ /**
+ * Notify plugins of dispatch startup
+ */
+ $this->_plugins->preDispatch($this->_request);
+
+ /**
+ * Skip requested action if preDispatch() has reset it
+ */
+ if (!$this->_request->isDispatched()) {
+ continue;
+ }
+
+ /**
+ * Dispatch request
+ */
+ try {
+ $dispatcher->dispatch($this->_request, $this->_response);
+ } catch (Exception $e) {
+ if ($this->throwExceptions()) {
+ throw $e;
+ }
+ $this->_response->setException($e);
+ }
+
+ /**
+ * Notify plugins of dispatch completion
+ */
+ $this->_plugins->postDispatch($this->_request);
+ } while (!$this->_request->isDispatched());
+ } catch (Exception $e) {
+ if ($this->throwExceptions()) {
+ throw $e;
+ }
+
+ $this->_response->setException($e);
+ }
+
+ /**
+ * Notify plugins of dispatch loop completion
+ */
+ try {
+ $this->_plugins->dispatchLoopShutdown();
+ } catch (Exception $e) {
+ if ($this->throwExceptions()) {
+ throw $e;
+ }
+
+ $this->_response->setException($e);
+ }
+
+ if ($this->returnResponse()) {
+ return $this->_response;
+ }
+
+ $this->_response->sendResponse();
+ }
+}
diff --git a/library/vendor/Zend/Controller/Plugin/Abstract.php b/library/vendor/Zend/Controller/Plugin/Abstract.php
new file mode 100644
index 0000000..7e590b7
--- /dev/null
+++ b/library/vendor/Zend/Controller/Plugin/Abstract.php
@@ -0,0 +1,151 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Plugin_Abstract
+{
+ /**
+ * @var Zend_Controller_Request_Abstract
+ */
+ protected $_request;
+
+ /**
+ * @var Zend_Controller_Response_Abstract
+ */
+ protected $_response;
+
+ /**
+ * Set request object
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return Zend_Controller_Plugin_Abstract
+ */
+ public function setRequest(Zend_Controller_Request_Abstract $request)
+ {
+ $this->_request = $request;
+ return $this;
+ }
+
+ /**
+ * Get request object
+ *
+ * @return Zend_Controller_Request_Abstract $request
+ */
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+ /**
+ * Set response object
+ *
+ * @param Zend_Controller_Response_Abstract $response
+ * @return Zend_Controller_Plugin_Abstract
+ */
+ public function setResponse(Zend_Controller_Response_Abstract $response)
+ {
+ $this->_response = $response;
+ return $this;
+ }
+
+ /**
+ * Get response object
+ *
+ * @return Zend_Controller_Response_Abstract $response
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Called before Zend_Controller_Front begins evaluating the
+ * request against its routes.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function routeStartup(Zend_Controller_Request_Abstract $request)
+ {}
+
+ /**
+ * Called after Zend_Controller_Router exits.
+ *
+ * Called after Zend_Controller_Front exits from the router.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function routeShutdown(Zend_Controller_Request_Abstract $request)
+ {}
+
+ /**
+ * Called before Zend_Controller_Front enters its dispatch loop.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
+ {}
+
+ /**
+ * Called before an action is dispatched by Zend_Controller_Dispatcher.
+ *
+ * This callback allows for proxy or filter behavior. By altering the
+ * request and resetting its dispatched flag (via
+ * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}),
+ * the current action may be skipped.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function preDispatch(Zend_Controller_Request_Abstract $request)
+ {}
+
+ /**
+ * Called after an action is dispatched by Zend_Controller_Dispatcher.
+ *
+ * This callback allows for proxy or filter behavior. By altering the
+ * request and resetting its dispatched flag (via
+ * {@link Zend_Controller_Request_Abstract::setDispatched() setDispatched(false)}),
+ * a new action may be specified for dispatching.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function postDispatch(Zend_Controller_Request_Abstract $request)
+ {}
+
+ /**
+ * Called before Zend_Controller_Front exits its dispatch loop.
+ *
+ * @return void
+ */
+ public function dispatchLoopShutdown()
+ {}
+}
diff --git a/library/vendor/Zend/Controller/Plugin/ActionStack.php b/library/vendor/Zend/Controller/Plugin/ActionStack.php
new file mode 100644
index 0000000..c654a6c
--- /dev/null
+++ b/library/vendor/Zend/Controller/Plugin/ActionStack.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Plugin_Abstract */
+
+/** Zend_Registry */
+
+/**
+ * Manage a stack of actions
+ *
+ * @uses Zend_Controller_Plugin_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Controller_Plugin_ActionStack extends Zend_Controller_Plugin_Abstract
+{
+ /** @var Zend_Registry */
+ protected $_registry;
+
+ /**
+ * Registry key under which actions are stored
+ * @var string
+ */
+ protected $_registryKey = 'Zend_Controller_Plugin_ActionStack';
+
+ /**
+ * Valid keys for stack items
+ * @var array
+ */
+ protected $_validKeys = array(
+ 'module',
+ 'controller',
+ 'action',
+ 'params'
+ );
+
+ /**
+ * Flag to determine whether request parameters are cleared between actions, or whether new parameters
+ * are added to existing request parameters.
+ *
+ * @var Bool
+ */
+ protected $_clearRequestParams = false;
+
+ /**
+ * Constructor
+ *
+ * @param Zend_Registry $registry
+ * @param string $key
+ * @return void
+ */
+ public function __construct(Zend_Registry $registry = null, $key = null)
+ {
+ if (null === $registry) {
+ $registry = Zend_Registry::getInstance();
+ }
+ $this->setRegistry($registry);
+
+ if (null !== $key) {
+ $this->setRegistryKey($key);
+ } else {
+ $key = $this->getRegistryKey();
+ }
+
+ $registry[$key] = array();
+ }
+
+ /**
+ * Set registry object
+ *
+ * @param Zend_Registry $registry
+ * @return Zend_Controller_Plugin_ActionStack
+ */
+ public function setRegistry(Zend_Registry $registry)
+ {
+ $this->_registry = $registry;
+ return $this;
+ }
+
+ /**
+ * Retrieve registry object
+ *
+ * @return Zend_Registry
+ */
+ public function getRegistry()
+ {
+ return $this->_registry;
+ }
+
+ /**
+ * Retrieve registry key
+ *
+ * @return string
+ */
+ public function getRegistryKey()
+ {
+ return $this->_registryKey;
+ }
+
+ /**
+ * Set registry key
+ *
+ * @param string $key
+ * @return Zend_Controller_Plugin_ActionStack
+ */
+ public function setRegistryKey($key)
+ {
+ $this->_registryKey = (string) $key;
+ return $this;
+ }
+
+ /**
+ * Set clearRequestParams flag
+ *
+ * @param bool $clearRequestParams
+ * @return Zend_Controller_Plugin_ActionStack
+ */
+ public function setClearRequestParams($clearRequestParams)
+ {
+ $this->_clearRequestParams = (bool) $clearRequestParams;
+ return $this;
+ }
+
+ /**
+ * Retrieve clearRequestParams flag
+ *
+ * @return bool
+ */
+ public function getClearRequestParams()
+ {
+ return $this->_clearRequestParams;
+ }
+
+ /**
+ * Retrieve action stack
+ *
+ * @return array
+ */
+ public function getStack()
+ {
+ $registry = $this->getRegistry();
+ $stack = $registry[$this->getRegistryKey()];
+ return $stack;
+ }
+
+ /**
+ * Save stack to registry
+ *
+ * @param array $stack
+ * @return Zend_Controller_Plugin_ActionStack
+ */
+ protected function _saveStack(array $stack)
+ {
+ $registry = $this->getRegistry();
+ $registry[$this->getRegistryKey()] = $stack;
+ return $this;
+ }
+
+ /**
+ * Push an item onto the stack
+ *
+ * @param Zend_Controller_Request_Abstract $next
+ * @return Zend_Controller_Plugin_ActionStack
+ */
+ public function pushStack(Zend_Controller_Request_Abstract $next)
+ {
+ $stack = $this->getStack();
+ array_push($stack, $next);
+ return $this->_saveStack($stack);
+ }
+
+ /**
+ * Pop an item off the action stack
+ *
+ * @return false|Zend_Controller_Request_Abstract
+ */
+ public function popStack()
+ {
+ $stack = $this->getStack();
+ if (0 == count($stack)) {
+ return false;
+ }
+
+ $next = array_pop($stack);
+ $this->_saveStack($stack);
+
+ if (!$next instanceof Zend_Controller_Request_Abstract) {
+ throw new Zend_Controller_Exception('ArrayStack should only contain request objects');
+ }
+ $action = $next->getActionName();
+ if (empty($action)) {
+ return $this->popStack($stack);
+ }
+
+ $request = $this->getRequest();
+ $controller = $next->getControllerName();
+ if (empty($controller)) {
+ $next->setControllerName($request->getControllerName());
+ }
+
+ $module = $next->getModuleName();
+ if (empty($module)) {
+ $next->setModuleName($request->getModuleName());
+ }
+
+ return $next;
+ }
+
+ /**
+ * postDispatch() plugin hook -- check for actions in stack, and dispatch if any found
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function postDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ // Don't move on to next request if this is already an attempt to
+ // forward
+ if (!$request->isDispatched()) {
+ return;
+ }
+
+ $this->setRequest($request);
+ $stack = $this->getStack();
+ if (empty($stack)) {
+ return;
+ }
+ $next = $this->popStack();
+ if (!$next) {
+ return;
+ }
+
+ $this->forward($next);
+ }
+
+ /**
+ * Forward request with next action
+ *
+ * @param array $next
+ * @return void
+ */
+ public function forward(Zend_Controller_Request_Abstract $next)
+ {
+ $request = $this->getRequest();
+ if ($this->getClearRequestParams()) {
+ $request->clearParams();
+ }
+
+ $request->setModuleName($next->getModuleName())
+ ->setControllerName($next->getControllerName())
+ ->setActionName($next->getActionName())
+ ->setParams($next->getParams())
+ ->setDispatched(false);
+ }
+}
diff --git a/library/vendor/Zend/Controller/Plugin/Broker.php b/library/vendor/Zend/Controller/Plugin/Broker.php
new file mode 100644
index 0000000..eec9809
--- /dev/null
+++ b/library/vendor/Zend/Controller/Plugin/Broker.php
@@ -0,0 +1,361 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Controller_Plugin_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Plugin_Broker extends Zend_Controller_Plugin_Abstract
+{
+
+ /**
+ * Array of instance of objects extending Zend_Controller_Plugin_Abstract
+ *
+ * @var array
+ */
+ protected $_plugins = array();
+
+
+ /**
+ * Register a plugin.
+ *
+ * @param Zend_Controller_Plugin_Abstract $plugin
+ * @param int $stackIndex
+ * @return Zend_Controller_Plugin_Broker
+ */
+ public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin, $stackIndex = null)
+ {
+ if (false !== array_search($plugin, $this->_plugins, true)) {
+ throw new Zend_Controller_Exception('Plugin already registered');
+ }
+
+ $stackIndex = (int) $stackIndex;
+
+ if ($stackIndex) {
+ if (isset($this->_plugins[$stackIndex])) {
+ throw new Zend_Controller_Exception('Plugin with stackIndex "' . $stackIndex . '" already registered');
+ }
+ $this->_plugins[$stackIndex] = $plugin;
+ } else {
+ $stackIndex = count($this->_plugins);
+ while (isset($this->_plugins[$stackIndex])) {
+ ++$stackIndex;
+ }
+ $this->_plugins[$stackIndex] = $plugin;
+ }
+
+ $request = $this->getRequest();
+ if ($request) {
+ $this->_plugins[$stackIndex]->setRequest($request);
+ }
+ $response = $this->getResponse();
+ if ($response) {
+ $this->_plugins[$stackIndex]->setResponse($response);
+ }
+
+ ksort($this->_plugins);
+
+ return $this;
+ }
+
+ /**
+ * Unregister a plugin.
+ *
+ * @param string|Zend_Controller_Plugin_Abstract $plugin Plugin object or class name
+ * @return Zend_Controller_Plugin_Broker
+ */
+ public function unregisterPlugin($plugin)
+ {
+ if ($plugin instanceof Zend_Controller_Plugin_Abstract) {
+ // Given a plugin object, find it in the array
+ $key = array_search($plugin, $this->_plugins, true);
+ if (false === $key) {
+ throw new Zend_Controller_Exception('Plugin never registered.');
+ }
+ unset($this->_plugins[$key]);
+ } elseif (is_string($plugin)) {
+ // Given a plugin class, find all plugins of that class and unset them
+ foreach ($this->_plugins as $key => $_plugin) {
+ $type = get_class($_plugin);
+ if ($plugin == $type) {
+ unset($this->_plugins[$key]);
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Is a plugin of a particular class registered?
+ *
+ * @param string $class
+ * @return bool
+ */
+ public function hasPlugin($class)
+ {
+ foreach ($this->_plugins as $plugin) {
+ $type = get_class($plugin);
+ if ($class == $type) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve a plugin or plugins by class
+ *
+ * @param string $class Class name of plugin(s) desired
+ * @return false|Zend_Controller_Plugin_Abstract|array Returns false if none found, plugin if only one found, and array of plugins if multiple plugins of same class found
+ */
+ public function getPlugin($class)
+ {
+ $found = array();
+ foreach ($this->_plugins as $plugin) {
+ $type = get_class($plugin);
+ if ($class == $type) {
+ $found[] = $plugin;
+ }
+ }
+
+ switch (count($found)) {
+ case 0:
+ return false;
+ case 1:
+ return $found[0];
+ default:
+ return $found;
+ }
+ }
+
+ /**
+ * Retrieve all plugins
+ *
+ * @return array
+ */
+ public function getPlugins()
+ {
+ return $this->_plugins;
+ }
+
+ /**
+ * Set request object, and register with each plugin
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return Zend_Controller_Plugin_Broker
+ */
+ public function setRequest(Zend_Controller_Request_Abstract $request)
+ {
+ $this->_request = $request;
+
+ foreach ($this->_plugins as $plugin) {
+ $plugin->setRequest($request);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get request object
+ *
+ * @return Zend_Controller_Request_Abstract $request
+ */
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+ /**
+ * Set response object
+ *
+ * @param Zend_Controller_Response_Abstract $response
+ * @return Zend_Controller_Plugin_Broker
+ */
+ public function setResponse(Zend_Controller_Response_Abstract $response)
+ {
+ $this->_response = $response;
+
+ foreach ($this->_plugins as $plugin) {
+ $plugin->setResponse($response);
+ }
+
+
+ return $this;
+ }
+
+ /**
+ * Get response object
+ *
+ * @return Zend_Controller_Response_Abstract $response
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+
+ /**
+ * Called before Zend_Controller_Front begins evaluating the
+ * request against its routes.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function routeStartup(Zend_Controller_Request_Abstract $request)
+ {
+ foreach ($this->_plugins as $plugin) {
+ try {
+ $plugin->routeStartup($request);
+ } catch (Exception $e) {
+ if (Zend_Controller_Front::getInstance()->throwExceptions()) {
+ throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e);
+ } else {
+ $this->getResponse()->setException($e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Called before Zend_Controller_Front exits its iterations over
+ * the route set.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function routeShutdown(Zend_Controller_Request_Abstract $request)
+ {
+ foreach ($this->_plugins as $plugin) {
+ try {
+ $plugin->routeShutdown($request);
+ } catch (Exception $e) {
+ if (Zend_Controller_Front::getInstance()->throwExceptions()) {
+ throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e);
+ } else {
+ $this->getResponse()->setException($e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Called before Zend_Controller_Front enters its dispatch loop.
+ *
+ * During the dispatch loop, Zend_Controller_Front keeps a
+ * Zend_Controller_Request_Abstract object, and uses
+ * Zend_Controller_Dispatcher to dispatch the
+ * Zend_Controller_Request_Abstract object to controllers/actions.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
+ {
+ foreach ($this->_plugins as $plugin) {
+ try {
+ $plugin->dispatchLoopStartup($request);
+ } catch (Exception $e) {
+ if (Zend_Controller_Front::getInstance()->throwExceptions()) {
+ throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e);
+ } else {
+ $this->getResponse()->setException($e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Called before an action is dispatched by Zend_Controller_Dispatcher.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function preDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ foreach ($this->_plugins as $plugin) {
+ try {
+ $plugin->preDispatch($request);
+ } catch (Exception $e) {
+ if (Zend_Controller_Front::getInstance()->throwExceptions()) {
+ throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e);
+ } else {
+ $this->getResponse()->setException($e);
+ // skip rendering of normal dispatch give the error handler a try
+ $this->getRequest()->setDispatched(false);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Called after an action is dispatched by Zend_Controller_Dispatcher.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function postDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ foreach ($this->_plugins as $plugin) {
+ try {
+ $plugin->postDispatch($request);
+ } catch (Exception $e) {
+ if (Zend_Controller_Front::getInstance()->throwExceptions()) {
+ throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e);
+ } else {
+ $this->getResponse()->setException($e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Called before Zend_Controller_Front exits its dispatch loop.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function dispatchLoopShutdown()
+ {
+ foreach ($this->_plugins as $plugin) {
+ try {
+ $plugin->dispatchLoopShutdown();
+ } catch (Exception $e) {
+ if (Zend_Controller_Front::getInstance()->throwExceptions()) {
+ throw new Zend_Controller_Exception($e->getMessage() . $e->getTraceAsString(), $e->getCode(), $e);
+ } else {
+ $this->getResponse()->setException($e);
+ }
+ }
+ }
+ }
+}
diff --git a/library/vendor/Zend/Controller/Plugin/ErrorHandler.php b/library/vendor/Zend/Controller/Plugin/ErrorHandler.php
new file mode 100644
index 0000000..cd0e4f7
--- /dev/null
+++ b/library/vendor/Zend/Controller/Plugin/ErrorHandler.php
@@ -0,0 +1,299 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Plugin_Abstract */
+
+/**
+ * Handle exceptions that bubble up based on missing controllers, actions, or
+ * application errors, and forward to an error handler.
+ *
+ * @uses Zend_Controller_Plugin_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Controller_Plugin_ErrorHandler extends Zend_Controller_Plugin_Abstract
+{
+ /**
+ * Const - No controller exception; controller does not exist
+ */
+ const EXCEPTION_NO_CONTROLLER = 'EXCEPTION_NO_CONTROLLER';
+
+ /**
+ * Const - No action exception; controller exists, but action does not
+ */
+ const EXCEPTION_NO_ACTION = 'EXCEPTION_NO_ACTION';
+
+ /**
+ * Const - No route exception; no routing was possible
+ */
+ const EXCEPTION_NO_ROUTE = 'EXCEPTION_NO_ROUTE';
+
+ /**
+ * Const - Other Exception; exceptions thrown by application controllers
+ */
+ const EXCEPTION_OTHER = 'EXCEPTION_OTHER';
+
+ /**
+ * Module to use for errors; defaults to default module in dispatcher
+ * @var string
+ */
+ protected $_errorModule;
+
+ /**
+ * Controller to use for errors; defaults to 'error'
+ * @var string
+ */
+ protected $_errorController = 'error';
+
+ /**
+ * Action to use for errors; defaults to 'error'
+ * @var string
+ */
+ protected $_errorAction = 'error';
+
+ /**
+ * Flag; are we already inside the error handler loop?
+ * @var bool
+ */
+ protected $_isInsideErrorHandlerLoop = false;
+
+ /**
+ * Exception count logged at first invocation of plugin
+ * @var int
+ */
+ protected $_exceptionCountAtFirstEncounter = 0;
+
+ /**
+ * Constructor
+ *
+ * Options may include:
+ * - module
+ * - controller
+ * - action
+ *
+ * @param Array $options
+ * @return void
+ */
+ public function __construct(Array $options = array())
+ {
+ $this->setErrorHandler($options);
+ }
+
+ /**
+ * setErrorHandler() - setup the error handling options
+ *
+ * @param array $options
+ * @return Zend_Controller_Plugin_ErrorHandler
+ */
+ public function setErrorHandler(Array $options = array())
+ {
+ if (isset($options['module'])) {
+ $this->setErrorHandlerModule($options['module']);
+ }
+ if (isset($options['controller'])) {
+ $this->setErrorHandlerController($options['controller']);
+ }
+ if (isset($options['action'])) {
+ $this->setErrorHandlerAction($options['action']);
+ }
+ return $this;
+ }
+
+ /**
+ * Set the module name for the error handler
+ *
+ * @param string $module
+ * @return Zend_Controller_Plugin_ErrorHandler
+ */
+ public function setErrorHandlerModule($module)
+ {
+ $this->_errorModule = (string) $module;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current error handler module
+ *
+ * @return string
+ */
+ public function getErrorHandlerModule()
+ {
+ if (null === $this->_errorModule) {
+ $this->_errorModule = Zend_Controller_Front::getInstance()->getDispatcher()->getDefaultModule();
+ }
+ return $this->_errorModule;
+ }
+
+ /**
+ * Set the controller name for the error handler
+ *
+ * @param string $controller
+ * @return Zend_Controller_Plugin_ErrorHandler
+ */
+ public function setErrorHandlerController($controller)
+ {
+ $this->_errorController = (string) $controller;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current error handler controller
+ *
+ * @return string
+ */
+ public function getErrorHandlerController()
+ {
+ return $this->_errorController;
+ }
+
+ /**
+ * Set the action name for the error handler
+ *
+ * @param string $action
+ * @return Zend_Controller_Plugin_ErrorHandler
+ */
+ public function setErrorHandlerAction($action)
+ {
+ $this->_errorAction = (string) $action;
+ return $this;
+ }
+
+ /**
+ * Retrieve the current error handler action
+ *
+ * @return string
+ */
+ public function getErrorHandlerAction()
+ {
+ return $this->_errorAction;
+ }
+
+ /**
+ * Route shutdown hook -- Ccheck for router exceptions
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ */
+ public function routeShutdown(Zend_Controller_Request_Abstract $request)
+ {
+ $this->_handleError($request);
+ }
+
+ /**
+ * Pre dispatch hook -- check for exceptions and dispatch error handler if
+ * necessary
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ */
+ public function preDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ $this->_handleError($request);
+ }
+
+ /**
+ * Post dispatch hook -- check for exceptions and dispatch error handler if
+ * necessary
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ */
+ public function postDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ $this->_handleError($request);
+ }
+
+ /**
+ * Handle errors and exceptions
+ *
+ * If the 'noErrorHandler' front controller flag has been set,
+ * returns early.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ protected function _handleError(Zend_Controller_Request_Abstract $request)
+ {
+ $frontController = Zend_Controller_Front::getInstance();
+ if ($frontController->getParam('noErrorHandler')) {
+ return;
+ }
+
+ $response = $this->getResponse();
+
+ if ($this->_isInsideErrorHandlerLoop) {
+ $exceptions = $response->getException();
+ if (count($exceptions) > $this->_exceptionCountAtFirstEncounter) {
+ // Exception thrown by error handler; tell the front controller to throw it
+ $frontController->throwExceptions(true);
+ throw array_pop($exceptions);
+ }
+ }
+
+ // check for an exception AND allow the error handler controller the option to forward
+ if (($response->isException()) && (!$this->_isInsideErrorHandlerLoop)) {
+ $this->_isInsideErrorHandlerLoop = true;
+
+ // Get exception information
+ $error = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
+ $exceptions = $response->getException();
+ $exception = $exceptions[0];
+ $exceptionType = get_class($exception);
+ $error->exception = $exception;
+ switch ($exceptionType) {
+ case 'Zend_Controller_Router_Exception':
+ if (404 == $exception->getCode()) {
+ $error->type = self::EXCEPTION_NO_ROUTE;
+ } else {
+ $error->type = self::EXCEPTION_OTHER;
+ }
+ break;
+ case 'Zend_Controller_Dispatcher_Exception':
+ $error->type = self::EXCEPTION_NO_CONTROLLER;
+ break;
+ case 'Zend_Controller_Action_Exception':
+ if (404 == $exception->getCode()) {
+ $error->type = self::EXCEPTION_NO_ACTION;
+ } else {
+ $error->type = self::EXCEPTION_OTHER;
+ }
+ break;
+ default:
+ $error->type = self::EXCEPTION_OTHER;
+ break;
+ }
+
+ // Keep a copy of the original request
+ $error->request = clone $request;
+
+ // get a count of the number of exceptions encountered
+ $this->_exceptionCountAtFirstEncounter = count($exceptions);
+
+ // Forward to the error handler
+ $request->setParam('error_handler', $error)
+ ->setModuleName($this->getErrorHandlerModule())
+ ->setControllerName($this->getErrorHandlerController())
+ ->setActionName($this->getErrorHandlerAction())
+ ->setDispatched(false);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Controller/Plugin/PutHandler.php b/library/vendor/Zend/Controller/Plugin/PutHandler.php
new file mode 100644
index 0000000..3bf7a67
--- /dev/null
+++ b/library/vendor/Zend/Controller/Plugin/PutHandler.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Plugin
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Plugin_Abstract
+ */
+
+/**
+ * @see Zend_Controller_Request_Http
+ */
+
+/**
+ * Plugin to digest PUT request body and make params available just like POST
+ *
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Plugin
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Plugin_PutHandler extends Zend_Controller_Plugin_Abstract
+{
+ /**
+ * Before dispatching, digest PUT request body and set params
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ */
+ public function preDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ if (!$request instanceof Zend_Controller_Request_Http) {
+ return;
+ }
+
+ if ($this->_request->isPut()) {
+ $putParams = array();
+ parse_str($this->_request->getRawBody(), $putParams);
+ $request->setParams($putParams);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Controller/Request/Abstract.php b/library/vendor/Zend/Controller/Request/Abstract.php
new file mode 100644
index 0000000..d57238f
--- /dev/null
+++ b/library/vendor/Zend/Controller/Request/Abstract.php
@@ -0,0 +1,356 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Request_Abstract
+{
+ /**
+ * Has the action been dispatched?
+ * @var boolean
+ */
+ protected $_dispatched = false;
+
+ /**
+ * Module
+ * @var string
+ */
+ protected $_module;
+
+ /**
+ * Module key for retrieving module from params
+ * @var string
+ */
+ protected $_moduleKey = 'module';
+
+ /**
+ * Controller
+ * @var string
+ */
+ protected $_controller;
+
+ /**
+ * Controller key for retrieving controller from params
+ * @var string
+ */
+ protected $_controllerKey = 'controller';
+
+ /**
+ * Action
+ * @var string
+ */
+ protected $_action;
+
+ /**
+ * Action key for retrieving action from params
+ * @var string
+ */
+ protected $_actionKey = 'action';
+
+ /**
+ * Request parameters
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Retrieve the module name
+ *
+ * @return string
+ */
+ public function getModuleName()
+ {
+ if (null === $this->_module) {
+ $this->_module = $this->getParam($this->getModuleKey());
+ }
+
+ return $this->_module;
+ }
+
+ /**
+ * Set the module name to use
+ *
+ * @param string $value
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setModuleName($value)
+ {
+ $this->_module = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve the controller name
+ *
+ * @return string
+ */
+ public function getControllerName()
+ {
+ if (null === $this->_controller) {
+ $this->_controller = $this->getParam($this->getControllerKey());
+ }
+
+ return $this->_controller;
+ }
+
+ /**
+ * Set the controller name to use
+ *
+ * @param string $value
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setControllerName($value)
+ {
+ $this->_controller = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve the action name
+ *
+ * @return string
+ */
+ public function getActionName()
+ {
+ if (null === $this->_action) {
+ $this->_action = $this->getParam($this->getActionKey());
+ }
+
+ return $this->_action;
+ }
+
+ /**
+ * Set the action name
+ *
+ * @param string $value
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setActionName($value)
+ {
+ $this->_action = $value;
+ /**
+ * @see ZF-3465
+ */
+ if (null === $value) {
+ $this->setParam($this->getActionKey(), $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve the module key
+ *
+ * @return string
+ */
+ public function getModuleKey()
+ {
+ return $this->_moduleKey;
+ }
+
+ /**
+ * Set the module key
+ *
+ * @param string $key
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setModuleKey($key)
+ {
+ $this->_moduleKey = (string) $key;
+ return $this;
+ }
+
+ /**
+ * Retrieve the controller key
+ *
+ * @return string
+ */
+ public function getControllerKey()
+ {
+ return $this->_controllerKey;
+ }
+
+ /**
+ * Set the controller key
+ *
+ * @param string $key
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setControllerKey($key)
+ {
+ $this->_controllerKey = (string) $key;
+ return $this;
+ }
+
+ /**
+ * Retrieve the action key
+ *
+ * @return string
+ */
+ public function getActionKey()
+ {
+ return $this->_actionKey;
+ }
+
+ /**
+ * Set the action key
+ *
+ * @param string $key
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setActionKey($key)
+ {
+ $this->_actionKey = (string) $key;
+ return $this;
+ }
+
+ /**
+ * Get an action parameter
+ *
+ * @param string $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed
+ */
+ public function getParam($key, $default = null)
+ {
+ $key = (string) $key;
+ if (isset($this->_params[$key])) {
+ return $this->_params[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Retrieve only user params (i.e, any param specific to the object and not the environment)
+ *
+ * @return array
+ */
+ public function getUserParams()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Retrieve a single user param (i.e, a param specific to the object and not the environment)
+ *
+ * @param string $key
+ * @param string $default Default value to use if key not found
+ * @return mixed
+ */
+ public function getUserParam($key, $default = null)
+ {
+ if (isset($this->_params[$key])) {
+ return $this->_params[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Set an action parameter
+ *
+ * A $value of null will unset the $key if it exists
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setParam($key, $value)
+ {
+ $key = (string) $key;
+
+ if ((null === $value) && isset($this->_params[$key])) {
+ unset($this->_params[$key]);
+ } elseif (null !== $value) {
+ $this->_params[$key] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get all action parameters
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->_params;
+ }
+
+ /**
+ * Set action parameters en masse; does not overwrite
+ *
+ * Null values will unset the associated key.
+ *
+ * @param array $array
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setParams(array $array)
+ {
+ $this->_params = $this->_params + (array) $array;
+
+ foreach ($array as $key => $value) {
+ if (null === $value) {
+ unset($this->_params[$key]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Unset all user parameters
+ *
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function clearParams()
+ {
+ $this->_params = array();
+ return $this;
+ }
+
+ /**
+ * Set flag indicating whether or not request has been dispatched
+ *
+ * @param boolean $flag
+ * @return Zend_Controller_Request_Abstract
+ */
+ public function setDispatched($flag = true)
+ {
+ $this->_dispatched = $flag ? true : false;
+ return $this;
+ }
+
+ /**
+ * Determine if the request has been dispatched
+ *
+ * @return boolean
+ */
+ public function isDispatched()
+ {
+ return $this->_dispatched;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Request/Apache404.php b/library/vendor/Zend/Controller/Request/Apache404.php
new file mode 100644
index 0000000..87d1d83
--- /dev/null
+++ b/library/vendor/Zend/Controller/Request/Apache404.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Controller_Request_Http */
+
+/** Zend_Uri */
+
+/**
+ * Zend_Controller_Request_Apache404
+ *
+ * HTTP request object for use with Zend_Controller family. Extends basic HTTP
+ * request object to allow for two edge cases when using Apache:
+ * - Using Apache's 404 handler instead of mod_rewrite to direct requests
+ * - Using the PT flag in rewrite rules
+ *
+ * In each case, the URL to check against is found in REDIRECT_URL, not
+ * REQUEST_URI.
+ *
+ * @uses Zend_Controller_Request_Http
+ * @package Zend_Controller
+ * @subpackage Request
+ */
+class Zend_Controller_Request_Apache404 extends Zend_Controller_Request_Http
+{
+ public function setRequestUri($requestUri = null)
+ {
+ $parseUriGetVars = false;
+ if ($requestUri === null) {
+ if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // check this first so IIS will catch
+ $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
+ } elseif (isset($_SERVER['REDIRECT_URL'])) { // Check if using mod_rewrite
+ $requestUri = $_SERVER['REDIRECT_URL'];
+ if (isset($_SERVER['REDIRECT_QUERY_STRING'])) {
+ $parseUriGetVars = $_SERVER['REDIRECT_QUERY_STRING'];
+ }
+ } elseif (isset($_SERVER['REQUEST_URI'])) {
+ $requestUri = $_SERVER['REQUEST_URI'];
+ } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI
+ $requestUri = $_SERVER['ORIG_PATH_INFO'];
+ if (!empty($_SERVER['QUERY_STRING'])) {
+ $requestUri .= '?' . $_SERVER['QUERY_STRING'];
+ }
+ } else {
+ return $this;
+ }
+ } elseif (!is_string($requestUri)) {
+ return $this;
+ } else {
+ if (false !== ($pos = strpos($requestUri, '?'))) {
+ $parseUriGetVars = substr($requestUri, $pos + 1);
+ }
+ }
+
+ if ($parseUriGetVars) {
+ // Set GET items, if available
+ parse_str($parseUriGetVars, $_GET);
+ }
+
+ $this->_requestUri = $requestUri;
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Request/Exception.php b/library/vendor/Zend/Controller/Request/Exception.php
new file mode 100644
index 0000000..89e8856
--- /dev/null
+++ b/library/vendor/Zend/Controller/Request/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Request
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Controller_Exception */
+
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Request
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Request_Exception extends Zend_Controller_Exception
+{}
+
diff --git a/library/vendor/Zend/Controller/Request/Http.php b/library/vendor/Zend/Controller/Request/Http.php
new file mode 100644
index 0000000..de61e36
--- /dev/null
+++ b/library/vendor/Zend/Controller/Request/Http.php
@@ -0,0 +1,1087 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** @see Zend_Controller_Request_Abstract */
+
+/** @see Zend_Uri */
+
+/**
+ * Zend_Controller_Request_Http
+ *
+ * HTTP request object for use with Zend_Controller family.
+ *
+ * @uses Zend_Controller_Request_Abstract
+ * @package Zend_Controller
+ * @subpackage Request
+ */
+class Zend_Controller_Request_Http extends Zend_Controller_Request_Abstract
+{
+ /**
+ * Scheme for http
+ *
+ */
+ const SCHEME_HTTP = 'http';
+
+ /**
+ * Scheme for https
+ *
+ */
+ const SCHEME_HTTPS = 'https';
+
+ /**
+ * Allowed parameter sources
+ * @var array
+ */
+ protected $_paramSources = array('_GET', '_POST');
+
+ /**
+ * REQUEST_URI
+ * @var string;
+ */
+ protected $_requestUri;
+
+ /**
+ * Base URL of request
+ * @var string
+ */
+ protected $_baseUrl = null;
+
+ /**
+ * Base path of request
+ * @var string
+ */
+ protected $_basePath = null;
+
+ /**
+ * PATH_INFO
+ * @var string
+ */
+ protected $_pathInfo = '';
+
+ /**
+ * Instance parameters
+ * @var array
+ */
+ protected $_params = array();
+
+ /**
+ * Raw request body
+ * @var string|false
+ */
+ protected $_rawBody;
+
+ /**
+ * Alias keys for request parameters
+ * @var array
+ */
+ protected $_aliases = array();
+
+ /**
+ * Constructor
+ *
+ * If a $uri is passed, the object will attempt to populate itself using
+ * that information.
+ *
+ * @param string|Zend_Uri $uri
+ * @return void
+ * @throws Zend_Controller_Request_Exception when invalid URI passed
+ */
+ public function __construct($uri = null)
+ {
+ if (null !== $uri) {
+ if (!$uri instanceof Zend_Uri) {
+ $uri = Zend_Uri::factory($uri);
+ }
+ if ($uri->valid()) {
+ $path = $uri->getPath();
+ $query = $uri->getQuery();
+ if (!empty($query)) {
+ $path .= '?' . $query;
+ }
+
+ $this->setRequestUri($path);
+ } else {
+ throw new Zend_Controller_Request_Exception('Invalid URI provided to constructor');
+ }
+ } else {
+ $this->setRequestUri();
+ }
+ }
+
+ /**
+ * Access values contained in the superglobals as public members
+ * Order of precedence: 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV
+ *
+ * @see http://msdn.microsoft.com/en-us/library/system.web.httprequest.item.aspx
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ switch (true) {
+ case isset($this->_params[$key]):
+ return $this->_params[$key];
+ case isset($_GET[$key]):
+ return $_GET[$key];
+ case isset($_POST[$key]):
+ return $_POST[$key];
+ case isset($_COOKIE[$key]):
+ return $_COOKIE[$key];
+ case ($key == 'REQUEST_URI'):
+ return $this->getRequestUri();
+ case ($key == 'PATH_INFO'):
+ return $this->getPathInfo();
+ case isset($_SERVER[$key]):
+ return $_SERVER[$key];
+ case isset($_ENV[$key]):
+ return $_ENV[$key];
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Alias to __get
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function get($key)
+ {
+ return $this->__get($key);
+ }
+
+ /**
+ * Set values
+ *
+ * In order to follow {@link __get()}, which operates on a number of
+ * superglobals, setting values through overloading is not allowed and will
+ * raise an exception. Use setParam() instead.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ * @throws Zend_Controller_Request_Exception
+ */
+ public function __set($key, $value)
+ {
+ throw new Zend_Controller_Request_Exception('Setting values in superglobals not allowed; please use setParam()');
+ }
+
+ /**
+ * Alias to __set()
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function set($key, $value)
+ {
+ return $this->__set($key, $value);
+ }
+
+ /**
+ * Check to see if a property is set
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function __isset($key)
+ {
+ switch (true) {
+ case isset($this->_params[$key]):
+ return true;
+ case isset($_GET[$key]):
+ return true;
+ case isset($_POST[$key]):
+ return true;
+ case isset($_COOKIE[$key]):
+ return true;
+ case isset($_SERVER[$key]):
+ return true;
+ case isset($_ENV[$key]):
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Alias to __isset()
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function has($key)
+ {
+ return $this->__isset($key);
+ }
+
+ /**
+ * Set GET values
+ *
+ * @param string|array $spec
+ * @param null|mixed $value
+ * @return Zend_Controller_Request_Http
+ */
+ public function setQuery($spec, $value = null)
+ {
+ if ((null === $value) && !is_array($spec)) {
+ throw new Zend_Controller_Exception('Invalid value passed to setQuery(); must be either array of values or key/value pair');
+ }
+ if ((null === $value) && is_array($spec)) {
+ foreach ($spec as $key => $value) {
+ $this->setQuery($key, $value);
+ }
+ return $this;
+ }
+ $_GET[(string) $spec] = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve a member of the $_GET superglobal
+ *
+ * If no $key is passed, returns the entire $_GET array.
+ *
+ * @todo How to retrieve from nested arrays
+ * @param string $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed Returns null if key does not exist
+ */
+ public function getQuery($key = null, $default = null)
+ {
+ if (null === $key) {
+ return $_GET;
+ }
+
+ return (isset($_GET[$key])) ? $_GET[$key] : $default;
+ }
+
+ /**
+ * Set POST values
+ *
+ * @param string|array $spec
+ * @param null|mixed $value
+ * @return Zend_Controller_Request_Http
+ */
+ public function setPost($spec, $value = null)
+ {
+ if ((null === $value) && !is_array($spec)) {
+ throw new Zend_Controller_Exception('Invalid value passed to setPost(); must be either array of values or key/value pair');
+ }
+ if ((null === $value) && is_array($spec)) {
+ foreach ($spec as $key => $value) {
+ $this->setPost($key, $value);
+ }
+ return $this;
+ }
+ $_POST[(string) $spec] = $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve a member of the $_POST superglobal
+ *
+ * If no $key is passed, returns the entire $_POST array.
+ *
+ * @todo How to retrieve from nested arrays
+ * @param string $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed Returns null if key does not exist
+ */
+ public function getPost($key = null, $default = null)
+ {
+ if (null === $key) {
+ return $_POST;
+ }
+
+ return (isset($_POST[$key])) ? $_POST[$key] : $default;
+ }
+
+ /**
+ * Retrieve a member of the $_COOKIE superglobal
+ *
+ * If no $key is passed, returns the entire $_COOKIE array.
+ *
+ * @todo How to retrieve from nested arrays
+ * @param string $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed Returns null if key does not exist
+ */
+ public function getCookie($key = null, $default = null)
+ {
+ if (null === $key) {
+ return $_COOKIE;
+ }
+
+ return (isset($_COOKIE[$key])) ? $_COOKIE[$key] : $default;
+ }
+
+ /**
+ * Retrieve a member of the $_SERVER superglobal
+ *
+ * If no $key is passed, returns the entire $_SERVER array.
+ *
+ * @param string $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed Returns null if key does not exist
+ */
+ public function getServer($key = null, $default = null)
+ {
+ if (null === $key) {
+ return $_SERVER;
+ }
+
+ return (isset($_SERVER[$key])) ? $_SERVER[$key] : $default;
+ }
+
+ /**
+ * Retrieve a member of the $_ENV superglobal
+ *
+ * If no $key is passed, returns the entire $_ENV array.
+ *
+ * @param string $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed Returns null if key does not exist
+ */
+ public function getEnv($key = null, $default = null)
+ {
+ if (null === $key) {
+ return $_ENV;
+ }
+
+ return (isset($_ENV[$key])) ? $_ENV[$key] : $default;
+ }
+
+ /**
+ * Set the REQUEST_URI on which the instance operates
+ *
+ * If no request URI is passed, uses the value in $_SERVER['REQUEST_URI'],
+ * $_SERVER['HTTP_X_REWRITE_URL'], or $_SERVER['ORIG_PATH_INFO'] + $_SERVER['QUERY_STRING'].
+ *
+ * @param string $requestUri
+ * @return Zend_Controller_Request_Http
+ */
+ public function setRequestUri($requestUri = null)
+ {
+ if ($requestUri === null) {
+ if (isset($_SERVER['HTTP_X_ORIGINAL_URL'])) {
+ // IIS with Microsoft Rewrite Module
+ $requestUri = $_SERVER['HTTP_X_ORIGINAL_URL'];
+ } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
+ // IIS with ISAPI_Rewrite
+ $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
+ } elseif (
+ // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
+ isset($_SERVER['IIS_WasUrlRewritten'])
+ && $_SERVER['IIS_WasUrlRewritten'] == '1'
+ && isset($_SERVER['UNENCODED_URL'])
+ && $_SERVER['UNENCODED_URL'] != ''
+ ) {
+ $requestUri = $_SERVER['UNENCODED_URL'];
+ } elseif (isset($_SERVER['REQUEST_URI'])) {
+ $requestUri = $_SERVER['REQUEST_URI'];
+ // Http proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path
+ $schemeAndHttpHost = $this->getScheme() . '://' . $this->getHttpHost();
+ if (strpos($requestUri, $schemeAndHttpHost) === 0) {
+ $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
+ }
+ } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI
+ $requestUri = $_SERVER['ORIG_PATH_INFO'];
+ if (!empty($_SERVER['QUERY_STRING'])) {
+ $requestUri .= '?' . $_SERVER['QUERY_STRING'];
+ }
+ } else {
+ return $this;
+ }
+ } elseif (!is_string($requestUri)) {
+ return $this;
+ } else {
+ // Set GET items, if available
+ if (false !== ($pos = strpos($requestUri, '?'))) {
+ // Get key => value pairs and set $_GET
+ $query = substr($requestUri, $pos + 1);
+ parse_str($query, $vars);
+ $this->setQuery($vars);
+ }
+ }
+
+ $this->_requestUri = $requestUri;
+ return $this;
+ }
+
+ /**
+ * Returns the REQUEST_URI taking into account
+ * platform differences between Apache and IIS
+ *
+ * @return string
+ */
+ public function getRequestUri()
+ {
+ if (empty($this->_requestUri)) {
+ $this->setRequestUri();
+ }
+
+ return $this->_requestUri;
+ }
+
+ /**
+ * Set the base URL of the request; i.e., the segment leading to the script name
+ *
+ * E.g.:
+ * - /admin
+ * - /myapp
+ * - /subdir/index.php
+ *
+ * Do not use the full URI when providing the base. The following are
+ * examples of what not to use:
+ * - http://example.com/admin (should be just /admin)
+ * - http://example.com/subdir/index.php (should be just /subdir/index.php)
+ *
+ * If no $baseUrl is provided, attempts to determine the base URL from the
+ * environment, using SCRIPT_FILENAME, SCRIPT_NAME, PHP_SELF, and
+ * ORIG_SCRIPT_NAME in its determination.
+ *
+ * @param mixed $baseUrl
+ * @return Zend_Controller_Request_Http
+ */
+ public function setBaseUrl($baseUrl = null)
+ {
+ if ((null !== $baseUrl) && !is_string($baseUrl)) {
+ return $this;
+ }
+
+ if ($baseUrl === null) {
+ $filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : '';
+
+ if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) {
+ $baseUrl = $_SERVER['SCRIPT_NAME'];
+ } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename) {
+ $baseUrl = $_SERVER['PHP_SELF'];
+ } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename) {
+ $baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; // 1and1 shared hosting compatibility
+ } else {
+ // Backtrack up the script_filename to find the portion matching
+ // php_self
+ $path = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
+ $file = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '';
+ $segs = explode('/', trim($file, '/'));
+ $segs = array_reverse($segs);
+ $index = 0;
+ $last = count($segs);
+ $baseUrl = '';
+ do {
+ $seg = $segs[$index];
+ $baseUrl = '/' . $seg . $baseUrl;
+ ++$index;
+ } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
+ }
+
+ // Does the baseUrl have anything in common with the request_uri?
+ $requestUri = $this->getRequestUri();
+
+ if (0 === strpos($requestUri, $baseUrl)) {
+ // full $baseUrl matches
+ $this->_baseUrl = $baseUrl;
+ return $this;
+ }
+
+ if (0 === strpos($requestUri, dirname($baseUrl))) {
+ // directory portion of $baseUrl matches
+ $this->_baseUrl = rtrim(dirname($baseUrl), '/');
+ return $this;
+ }
+
+ $truncatedRequestUri = $requestUri;
+ if (($pos = strpos($requestUri, '?')) !== false) {
+ $truncatedRequestUri = substr($requestUri, 0, $pos);
+ }
+
+ $basename = basename($baseUrl);
+ if (empty($basename) || !strpos($truncatedRequestUri, $basename)) {
+ // no match whatsoever; set it blank
+ $this->_baseUrl = '';
+ return $this;
+ }
+
+ // If using mod_rewrite or ISAPI_Rewrite strip the script filename
+ // out of baseUrl. $pos !== 0 makes sure it is not matching a value
+ // from PATH_INFO or QUERY_STRING
+ if ((strlen($requestUri) >= strlen($baseUrl))
+ && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0)))
+ {
+ $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
+ }
+ }
+
+ $this->_baseUrl = rtrim($baseUrl, '/');
+ return $this;
+ }
+
+ /**
+ * Everything in REQUEST_URI before PATH_INFO
+ * <form action="<?=$baseUrl?>/news/submit" method="POST"/>
+ *
+ * @return string
+ */
+ public function getBaseUrl($raw = false)
+ {
+ if (null === $this->_baseUrl) {
+ $this->setBaseUrl();
+ }
+
+ return (($raw == false) ? urldecode($this->_baseUrl) : $this->_baseUrl);
+ }
+
+ /**
+ * Set the base path for the URL
+ *
+ * @param string|null $basePath
+ * @return Zend_Controller_Request_Http
+ */
+ public function setBasePath($basePath = null)
+ {
+ if ($basePath === null) {
+ $filename = (isset($_SERVER['SCRIPT_FILENAME']))
+ ? basename($_SERVER['SCRIPT_FILENAME'])
+ : '';
+
+ $baseUrl = $this->getBaseUrl();
+ if (empty($baseUrl)) {
+ $this->_basePath = '';
+ return $this;
+ }
+
+ if (basename($baseUrl) === $filename) {
+ $basePath = dirname($baseUrl);
+ } else {
+ $basePath = $baseUrl;
+ }
+ }
+
+ if (substr(PHP_OS, 0, 3) === 'WIN') {
+ $basePath = str_replace('\\', '/', $basePath);
+ }
+
+ $this->_basePath = rtrim($basePath, '/');
+ return $this;
+ }
+
+ /**
+ * Everything in REQUEST_URI before PATH_INFO not including the filename
+ * <img src="<?=$basePath?>/images/zend.png"/>
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ if (null === $this->_basePath) {
+ $this->setBasePath();
+ }
+
+ return $this->_basePath;
+ }
+
+ /**
+ * Set the PATH_INFO string
+ *
+ * @param string|null $pathInfo
+ * @return Zend_Controller_Request_Http
+ */
+ public function setPathInfo($pathInfo = null)
+ {
+ if ($pathInfo === null) {
+ $baseUrl = $this->getBaseUrl(); // this actually calls setBaseUrl() & setRequestUri()
+ $baseUrlRaw = $this->getBaseUrl(false);
+ $baseUrlEncoded = urlencode($baseUrlRaw);
+
+ if (null === ($requestUri = $this->getRequestUri())) {
+ return $this;
+ }
+
+ // Remove the query string from REQUEST_URI
+ if ($pos = strpos($requestUri, '?')) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+
+ if (!empty($baseUrl) || !empty($baseUrlRaw)) {
+ if (strpos($requestUri, $baseUrl) === 0) {
+ $pathInfo = substr($requestUri, strlen($baseUrl));
+ } elseif (strpos($requestUri, $baseUrlRaw) === 0) {
+ $pathInfo = substr($requestUri, strlen($baseUrlRaw));
+ } elseif (strpos($requestUri, $baseUrlEncoded) === 0) {
+ $pathInfo = substr($requestUri, strlen($baseUrlEncoded));
+ } else {
+ $pathInfo = $requestUri;
+ }
+ } else {
+ $pathInfo = $requestUri;
+ }
+
+ }
+
+ $this->_pathInfo = (string) $pathInfo;
+ return $this;
+ }
+
+ /**
+ * Returns everything between the BaseUrl and QueryString.
+ * This value is calculated instead of reading PATH_INFO
+ * directly from $_SERVER due to cross-platform differences.
+ *
+ * @return string
+ */
+ public function getPathInfo()
+ {
+ if (empty($this->_pathInfo)) {
+ $this->setPathInfo();
+ }
+
+ return $this->_pathInfo;
+ }
+
+ /**
+ * Set allowed parameter sources
+ *
+ * Can be empty array, or contain one or more of '_GET' or '_POST'.
+ *
+ * @param array $paramSoures
+ * @return Zend_Controller_Request_Http
+ */
+ public function setParamSources(array $paramSources = array())
+ {
+ $this->_paramSources = $paramSources;
+ return $this;
+ }
+
+ /**
+ * Get list of allowed parameter sources
+ *
+ * @return array
+ */
+ public function getParamSources()
+ {
+ return $this->_paramSources;
+ }
+
+ /**
+ * Set a userland parameter
+ *
+ * Uses $key to set a userland parameter. If $key is an alias, the actual
+ * key will be retrieved and used to set the parameter.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return Zend_Controller_Request_Http
+ */
+ public function setParam($key, $value)
+ {
+ $key = (null !== ($alias = $this->getAlias($key))) ? $alias : $key;
+ parent::setParam($key, $value);
+ return $this;
+ }
+
+ /**
+ * Retrieve a parameter
+ *
+ * Retrieves a parameter from the instance. Priority is in the order of
+ * userland parameters (see {@link setParam()}), $_GET, $_POST. If a
+ * parameter matching the $key is not found, null is returned.
+ *
+ * If the $key is an alias, the actual key aliased will be used.
+ *
+ * @param mixed $key
+ * @param mixed $default Default value to use if key not found
+ * @return mixed
+ */
+ public function getParam($key, $default = null)
+ {
+ $keyName = (null !== ($alias = $this->getAlias($key))) ? $alias : $key;
+
+ $paramSources = $this->getParamSources();
+ if (isset($this->_params[$keyName])) {
+ return $this->_params[$keyName];
+ } elseif (in_array('_GET', $paramSources) && (isset($_GET[$keyName]))) {
+ return $_GET[$keyName];
+ } elseif (in_array('_POST', $paramSources) && (isset($_POST[$keyName]))) {
+ return $_POST[$keyName];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Retrieve an array of parameters
+ *
+ * Retrieves a merged array of parameters, with precedence of userland
+ * params (see {@link setParam()}), $_GET, $_POST (i.e., values in the
+ * userland params will take precedence over all others).
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ $return = $this->_params;
+ $paramSources = $this->getParamSources();
+ if (in_array('_GET', $paramSources)
+ && isset($_GET)
+ && is_array($_GET)
+ ) {
+ $return += $_GET;
+ }
+ if (in_array('_POST', $paramSources)
+ && isset($_POST)
+ && is_array($_POST)
+ ) {
+ $return += $_POST;
+ }
+ return $return;
+ }
+
+ /**
+ * Set parameters
+ *
+ * Set one or more parameters. Parameters are set as userland parameters,
+ * using the keys specified in the array.
+ *
+ * @param array $params
+ * @return Zend_Controller_Request_Http
+ */
+ public function setParams(array $params)
+ {
+ foreach ($params as $key => $value) {
+ $this->setParam($key, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Set a key alias
+ *
+ * Set an alias used for key lookups. $name specifies the alias, $target
+ * specifies the actual key to use.
+ *
+ * @param string $name
+ * @param string $target
+ * @return Zend_Controller_Request_Http
+ */
+ public function setAlias($name, $target)
+ {
+ $this->_aliases[$name] = $target;
+ return $this;
+ }
+
+ /**
+ * Retrieve an alias
+ *
+ * Retrieve the actual key represented by the alias $name.
+ *
+ * @param string $name
+ * @return string|null Returns null when no alias exists
+ */
+ public function getAlias($name)
+ {
+ if (isset($this->_aliases[$name])) {
+ return $this->_aliases[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve the list of all aliases
+ *
+ * @return array
+ */
+ public function getAliases()
+ {
+ return $this->_aliases;
+ }
+
+ /**
+ * Return the method by which the request was made
+ *
+ * @return string
+ */
+ public function getMethod()
+ {
+ return $this->getServer('REQUEST_METHOD');
+ }
+
+ /**
+ * Was the request made by POST?
+ *
+ * @return boolean
+ */
+ public function isPost()
+ {
+ if ('POST' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Was the request made by GET?
+ *
+ * @return boolean
+ */
+ public function isGet()
+ {
+ if ('GET' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Was the request made by PUT?
+ *
+ * @return boolean
+ */
+ public function isPut()
+ {
+ if ('PUT' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Was the request made by DELETE?
+ *
+ * @return boolean
+ */
+ public function isDelete()
+ {
+ if ('DELETE' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Was the request made by HEAD?
+ *
+ * @return boolean
+ */
+ public function isHead()
+ {
+ if ('HEAD' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Was the request made by OPTIONS?
+ *
+ * @return boolean
+ */
+ public function isOptions()
+ {
+ if ('OPTIONS' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Was the request made by PATCH?
+ *
+ * @return boolean
+ */
+ public function isPatch()
+ {
+ if ('PATCH' == $this->getMethod()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Is the request a Javascript XMLHttpRequest?
+ *
+ * Should work with Prototype/Script.aculo.us, possibly others.
+ *
+ * @return boolean
+ */
+ public function isXmlHttpRequest()
+ {
+ return ($this->getHeader('X_REQUESTED_WITH') == 'XMLHttpRequest');
+ }
+
+ /**
+ * Is this a Flash request?
+ *
+ * @return boolean
+ */
+ public function isFlashRequest()
+ {
+ $header = strtolower($this->getHeader('USER_AGENT'));
+ return (strstr($header, ' flash')) ? true : false;
+ }
+
+ /**
+ * Is https secure request
+ *
+ * @return boolean
+ */
+ public function isSecure()
+ {
+ return ($this->getScheme() === self::SCHEME_HTTPS);
+ }
+
+ /**
+ * Return the raw body of the request, if present
+ *
+ * @return string|false Raw body, or false if not present
+ */
+ public function getRawBody()
+ {
+ if (null === $this->_rawBody) {
+ $body = file_get_contents('php://input');
+
+ if (strlen(trim($body)) > 0) {
+ $this->_rawBody = $body;
+ } else {
+ $this->_rawBody = false;
+ }
+ }
+ return $this->_rawBody;
+ }
+
+ /**
+ * Return the value of the given HTTP header. Pass the header name as the
+ * plain, HTTP-specified header name. Ex.: Ask for 'Accept' to get the
+ * Accept header, 'Accept-Encoding' to get the Accept-Encoding header.
+ *
+ * @param string $header HTTP header name
+ * @return string|false HTTP header value, or false if not found
+ * @throws Zend_Controller_Request_Exception
+ */
+ public function getHeader($header)
+ {
+ if (empty($header)) {
+ throw new Zend_Controller_Request_Exception('An HTTP header name is required');
+ }
+
+ // Try to get it from the $_SERVER array first
+ $temp = strtoupper(str_replace('-', '_', $header));
+ if (isset($_SERVER['HTTP_' . $temp])) {
+ return $_SERVER['HTTP_' . $temp];
+ }
+
+ /*
+ * Try to get it from the $_SERVER array on POST request or CGI environment
+ * @see https://www.ietf.org/rfc/rfc3875 (4.1.2. and 4.1.3.)
+ */
+ if (isset($_SERVER[$temp])
+ && in_array($temp, array('CONTENT_TYPE', 'CONTENT_LENGTH'))
+ ) {
+ return $_SERVER[$temp];
+ }
+
+ // This seems to be the only way to get the Authorization header on
+ // Apache
+ if (function_exists('apache_request_headers')) {
+ $headers = apache_request_headers();
+ if (isset($headers[$header])) {
+ return $headers[$header];
+ }
+ $header = strtolower($header);
+ foreach ($headers as $key => $value) {
+ if (strtolower($key) == $header) {
+ return $value;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the request URI scheme
+ *
+ * @return string
+ */
+ public function getScheme()
+ {
+ return ($this->getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP;
+ }
+
+ /**
+ * Get the HTTP host.
+ *
+ * "Host" ":" host [ ":" port ] ; Section 3.2.2
+ * Note the HTTP Host header is not the same as the URI host.
+ * It includes the port while the URI host doesn't.
+ *
+ * @return string
+ */
+ public function getHttpHost()
+ {
+ $host = $this->getServer('HTTP_HOST');
+ if (!empty($host)) {
+ return $host;
+ }
+
+ $scheme = $this->getScheme();
+ $name = $this->getServer('SERVER_NAME');
+ $port = $this->getServer('SERVER_PORT');
+
+ if(null === $name) {
+ return '';
+ }
+ elseif (($scheme == self::SCHEME_HTTP && $port == 80) || ($scheme == self::SCHEME_HTTPS && $port == 443)) {
+ return $name;
+ } else {
+ return $name . ':' . $port;
+ }
+ }
+
+ /**
+ * Get the client's IP addres
+ *
+ * @param boolean $checkProxy
+ * @return string
+ */
+ public function getClientIp($checkProxy = true)
+ {
+ if ($checkProxy && $this->getServer('HTTP_CLIENT_IP') != null) {
+ $ip = $this->getServer('HTTP_CLIENT_IP');
+ } else if ($checkProxy && $this->getServer('HTTP_X_FORWARDED_FOR') != null) {
+ $ip = $this->getServer('HTTP_X_FORWARDED_FOR');
+ } else {
+ $ip = $this->getServer('REMOTE_ADDR');
+ }
+
+ return $ip;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Request/HttpTestCase.php b/library/vendor/Zend/Controller/Request/HttpTestCase.php
new file mode 100644
index 0000000..414c95e
--- /dev/null
+++ b/library/vendor/Zend/Controller/Request/HttpTestCase.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Request_Http
+ */
+
+/**
+ * Zend_Controller_Request_HttpTestCase
+ *
+ * HTTP request object for use with Zend_Controller family.
+ *
+ * @uses Zend_Controller_Request_Http
+ * @package Zend_Controller
+ * @subpackage Request
+ */
+class Zend_Controller_Request_HttpTestCase extends Zend_Controller_Request_Http
+{
+ /**
+ * Request headers
+ * @var array
+ */
+ protected $_headers = array();
+
+ /**
+ * Request method
+ * @var string
+ */
+ protected $_method = 'GET';
+
+ /**
+ * Raw POST body
+ * @var string|null
+ */
+ protected $_rawBody;
+
+ /**
+ * Valid request method types
+ * @var array
+ */
+ protected $_validMethodTypes = array(
+ 'DELETE',
+ 'GET',
+ 'HEAD',
+ 'OPTIONS',
+ 'PATCH',
+ 'POST',
+ 'PUT',
+ );
+
+ /**
+ * Clear GET values
+ *
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function clearQuery()
+ {
+ $_GET = array();
+ return $this;
+ }
+
+ /**
+ * Clear POST values
+ *
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function clearPost()
+ {
+ $_POST = array();
+ return $this;
+ }
+
+ /**
+ * Set raw POST body
+ *
+ * @param string $content
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function setRawBody($content)
+ {
+ $this->_rawBody = (string) $content;
+ return $this;
+ }
+
+ /**
+ * Get RAW POST body
+ *
+ * @return string|null
+ */
+ public function getRawBody()
+ {
+ return $this->_rawBody;
+ }
+
+ /**
+ * Clear raw POST body
+ *
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function clearRawBody()
+ {
+ $this->_rawBody = null;
+ return $this;
+ }
+
+ /**
+ * Set a cookie
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function setCookie($key, $value)
+ {
+ $_COOKIE[(string) $key] = $value;
+ return $this;
+ }
+
+ /**
+ * Set multiple cookies at once
+ *
+ * @param array $cookies
+ * @return void
+ */
+ public function setCookies(array $cookies)
+ {
+ foreach ($cookies as $key => $value) {
+ $_COOKIE[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Clear all cookies
+ *
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function clearCookies()
+ {
+ $_COOKIE = array();
+ return $this;
+ }
+
+ /**
+ * Set request method
+ *
+ * @param string $type
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function setMethod($type)
+ {
+ $type = strtoupper(trim((string) $type));
+ if (!in_array($type, $this->_validMethodTypes)) {
+ throw new Zend_Controller_Exception('Invalid request method specified');
+ }
+ $this->_method = $type;
+ return $this;
+ }
+
+ /**
+ * Get request method
+ *
+ * @return string|null
+ */
+ public function getMethod()
+ {
+ return $this->_method;
+ }
+
+ /**
+ * Set a request header
+ *
+ * @param string $key
+ * @param string $value
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function setHeader($key, $value)
+ {
+ $key = $this->_normalizeHeaderName($key);
+ $this->_headers[$key] = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Set request headers
+ *
+ * @param array $headers
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function setHeaders(array $headers)
+ {
+ foreach ($headers as $key => $value) {
+ $this->setHeader($key, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Get request header
+ *
+ * @param string $header
+ * @param mixed $default
+ * @return string|null
+ */
+ public function getHeader($header, $default = null)
+ {
+ $header = $this->_normalizeHeaderName($header);
+ if (array_key_exists($header, $this->_headers)) {
+ return $this->_headers[$header];
+ }
+ return $default;
+ }
+
+ /**
+ * Get all request headers
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Clear request headers
+ *
+ * @return Zend_Controller_Request_HttpTestCase
+ */
+ public function clearHeaders()
+ {
+ $this->_headers = array();
+ return $this;
+ }
+
+ /**
+ * Get REQUEST_URI
+ *
+ * @return null|string
+ */
+ public function getRequestUri()
+ {
+ return $this->_requestUri;
+ }
+
+ /**
+ * Normalize a header name for setting and retrieval
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _normalizeHeaderName($name)
+ {
+ $name = strtoupper((string) $name);
+ $name = str_replace('-', '_', $name);
+ return $name;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Request/Simple.php b/library/vendor/Zend/Controller/Request/Simple.php
new file mode 100644
index 0000000..f804107
--- /dev/null
+++ b/library/vendor/Zend/Controller/Request/Simple.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Request
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Controller_Request_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Request
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Request_Simple extends Zend_Controller_Request_Abstract
+{
+
+ public function __construct($action = null, $controller = null, $module = null, array $params = array())
+ {
+ if ($action) {
+ $this->setActionName($action);
+ }
+
+ if ($controller) {
+ $this->setControllerName($controller);
+ }
+
+ if ($module) {
+ $this->setModuleName($module);
+ }
+
+ if ($params) {
+ $this->setParams($params);
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Controller/Response/Abstract.php b/library/vendor/Zend/Controller/Response/Abstract.php
new file mode 100644
index 0000000..0649694
--- /dev/null
+++ b/library/vendor/Zend/Controller/Response/Abstract.php
@@ -0,0 +1,790 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Controller_Response_Abstract
+ *
+ * Base class for Zend_Controller responses
+ *
+ * @package Zend_Controller
+ * @subpackage Response
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Response_Abstract
+{
+ /**
+ * Body content
+ * @var array
+ */
+ protected $_body = array();
+
+ /**
+ * Exception stack
+ * @var Exception
+ */
+ protected $_exceptions = array();
+
+ /**
+ * Array of headers. Each header is an array with keys 'name' and 'value'
+ * @var array
+ */
+ protected $_headers = array();
+
+ /**
+ * Array of raw headers. Each header is a single string, the entire header to emit
+ * @var array
+ */
+ protected $_headersRaw = array();
+
+ /**
+ * HTTP response code to use in headers
+ * @var int
+ */
+ protected $_httpResponseCode = 200;
+
+ /**
+ * Flag; is this response a redirect?
+ * @var boolean
+ */
+ protected $_isRedirect = false;
+
+ /**
+ * Whether or not to render exceptions; off by default
+ * @var boolean
+ */
+ protected $_renderExceptions = false;
+
+ /**
+ * Flag; if true, when header operations are called after headers have been
+ * sent, an exception will be raised; otherwise, processing will continue
+ * as normal. Defaults to true.
+ *
+ * @see canSendHeaders()
+ * @var boolean
+ */
+ public $headersSentThrowsException = true;
+
+ /**
+ * Normalize a header name
+ *
+ * Normalizes a header name to X-Capitalized-Names
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _normalizeHeader($name)
+ {
+ $filtered = str_replace(array('-', '_'), ' ', (string) $name);
+ $filtered = ucwords(strtolower($filtered));
+ $filtered = str_replace(' ', '-', $filtered);
+ return $filtered;
+ }
+
+ /**
+ * Set a header
+ *
+ * If $replace is true, replaces any headers already defined with that
+ * $name.
+ *
+ * @param string $name
+ * @param string $value
+ * @param boolean $replace
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function setHeader($name, $value, $replace = false)
+ {
+ $this->canSendHeaders(true);
+ $name = $this->_normalizeHeader($name);
+ $value = (string) $value;
+
+ if ($replace) {
+ foreach ($this->_headers as $key => $header) {
+ if ($name == $header['name']) {
+ unset($this->_headers[$key]);
+ }
+ }
+ }
+
+ $this->_headers[] = array(
+ 'name' => $name,
+ 'value' => $value,
+ 'replace' => $replace
+ );
+
+ return $this;
+ }
+
+ /**
+ * Set redirect URL
+ *
+ * Sets Location header and response code. Forces replacement of any prior
+ * redirects.
+ *
+ * @param string $url
+ * @param int $code
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function setRedirect($url, $code = 302)
+ {
+ $this->canSendHeaders(true);
+ $this->setHeader('Location', $url, true)
+ ->setHttpResponseCode($code);
+
+ return $this;
+ }
+
+ /**
+ * Is this a redirect?
+ *
+ * @return boolean
+ */
+ public function isRedirect()
+ {
+ return $this->_isRedirect;
+ }
+
+ /**
+ * Return array of headers; see {@link $_headers} for format
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Clear headers
+ *
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function clearHeaders()
+ {
+ $this->_headers = array();
+
+ return $this;
+ }
+
+ /**
+ * Clears the specified HTTP header
+ *
+ * @param string $name
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function clearHeader($name)
+ {
+ if (! count($this->_headers)) {
+ return $this;
+ }
+
+ foreach ($this->_headers as $index => $header) {
+ if ($name == $header['name']) {
+ unset($this->_headers[$index]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set raw HTTP header
+ *
+ * Allows setting non key => value headers, such as status codes
+ *
+ * @param string $value
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function setRawHeader($value)
+ {
+ $this->canSendHeaders(true);
+ if ('Location' == substr($value, 0, 8)) {
+ $this->_isRedirect = true;
+ }
+ $this->_headersRaw[] = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve all {@link setRawHeader() raw HTTP headers}
+ *
+ * @return array
+ */
+ public function getRawHeaders()
+ {
+ return $this->_headersRaw;
+ }
+
+ /**
+ * Clear all {@link setRawHeader() raw HTTP headers}
+ *
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function clearRawHeaders()
+ {
+ $this->_headersRaw = array();
+ return $this;
+ }
+
+ /**
+ * Clears the specified raw HTTP header
+ *
+ * @param string $headerRaw
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function clearRawHeader($headerRaw)
+ {
+ if (! count($this->_headersRaw)) {
+ return $this;
+ }
+
+ $key = array_search($headerRaw, $this->_headersRaw);
+ if ($key !== false) {
+ unset($this->_headersRaw[$key]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Clear all headers, normal and raw
+ *
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function clearAllHeaders()
+ {
+ return $this->clearHeaders()
+ ->clearRawHeaders();
+ }
+
+ /**
+ * Set HTTP response code to use with headers
+ *
+ * @param int $code
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function setHttpResponseCode($code)
+ {
+ if (!is_int($code) || (100 > $code) || (599 < $code)) {
+ throw new Zend_Controller_Response_Exception('Invalid HTTP response code');
+ }
+
+ if ((300 <= $code) && (307 >= $code)) {
+ $this->_isRedirect = true;
+ } else {
+ $this->_isRedirect = false;
+ }
+
+ $this->_httpResponseCode = $code;
+ return $this;
+ }
+
+ /**
+ * Retrieve HTTP response code
+ *
+ * @return int
+ */
+ public function getHttpResponseCode()
+ {
+ return $this->_httpResponseCode;
+ }
+
+ /**
+ * Can we send headers?
+ *
+ * @param boolean $throw Whether or not to throw an exception if headers have been sent; defaults to false
+ * @return boolean
+ * @throws Zend_Controller_Response_Exception
+ */
+ public function canSendHeaders($throw = false)
+ {
+ $ok = headers_sent($file, $line);
+ if ($ok && $throw && $this->headersSentThrowsException) {
+ throw new Zend_Controller_Response_Exception('Cannot send headers; headers already sent in ' . $file . ', line ' . $line);
+ }
+
+ return !$ok;
+ }
+
+ /**
+ * Send all headers
+ *
+ * Sends any headers specified. If an {@link setHttpResponseCode() HTTP response code}
+ * has been specified, it is sent with the first header.
+ *
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function sendHeaders()
+ {
+ // Only check if we can send headers if we have headers to send
+ if (count($this->_headersRaw) || count($this->_headers) || (200 != $this->_httpResponseCode)) {
+ $this->canSendHeaders(true);
+ } elseif (200 == $this->_httpResponseCode) {
+ // Haven't changed the response code, and we have no headers
+ return $this;
+ }
+
+ $httpCodeSent = false;
+
+ foreach ($this->_headersRaw as $header) {
+ if (!$httpCodeSent && $this->_httpResponseCode) {
+ header($header, true, $this->_httpResponseCode);
+ $httpCodeSent = true;
+ } else {
+ header($header);
+ }
+ }
+
+ foreach ($this->_headers as $header) {
+ if (!$httpCodeSent && $this->_httpResponseCode) {
+ header($header['name'] . ': ' . $header['value'], $header['replace'], $this->_httpResponseCode);
+ $httpCodeSent = true;
+ } else {
+ header($header['name'] . ': ' . $header['value'], $header['replace']);
+ }
+ }
+
+ if (!$httpCodeSent) {
+ header('HTTP/1.1 ' . $this->_httpResponseCode);
+ $httpCodeSent = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set body content
+ *
+ * If $name is not passed, or is not a string, resets the entire body and
+ * sets the 'default' key to $content.
+ *
+ * If $name is a string, sets the named segment in the body array to
+ * $content.
+ *
+ * @param string $content
+ * @param null|string $name
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function setBody($content, $name = null)
+ {
+ if ((null === $name) || !is_string($name)) {
+ $this->_body = array('default' => (string) $content);
+ } else {
+ $this->_body[$name] = (string) $content;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Append content to the body content
+ *
+ * @param string $content
+ * @param null|string $name
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function appendBody($content, $name = null)
+ {
+ if ((null === $name) || !is_string($name)) {
+ if (isset($this->_body['default'])) {
+ $this->_body['default'] .= (string) $content;
+ } else {
+ return $this->append('default', $content);
+ }
+ } elseif (isset($this->_body[$name])) {
+ $this->_body[$name] .= (string) $content;
+ } else {
+ return $this->append($name, $content);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Clear body array
+ *
+ * With no arguments, clears the entire body array. Given a $name, clears
+ * just that named segment; if no segment matching $name exists, returns
+ * false to indicate an error.
+ *
+ * @param string $name Named segment to clear
+ * @return boolean
+ */
+ public function clearBody($name = null)
+ {
+ if (null !== $name) {
+ $name = (string) $name;
+ if (isset($this->_body[$name])) {
+ unset($this->_body[$name]);
+ return true;
+ }
+
+ return false;
+ }
+
+ $this->_body = array();
+ return true;
+ }
+
+ /**
+ * Return the body content
+ *
+ * If $spec is false, returns the concatenated values of the body content
+ * array. If $spec is boolean true, returns the body content array. If
+ * $spec is a string and matches a named segment, returns the contents of
+ * that segment; otherwise, returns null.
+ *
+ * @param boolean $spec
+ * @return string|array|null
+ */
+ public function getBody($spec = false)
+ {
+ if (false === $spec) {
+ ob_start();
+ $this->outputBody();
+ return ob_get_clean();
+ } elseif (true === $spec) {
+ return $this->_body;
+ } elseif (is_string($spec) && isset($this->_body[$spec])) {
+ return $this->_body[$spec];
+ }
+
+ return null;
+ }
+
+ /**
+ * Append a named body segment to the body content array
+ *
+ * If segment already exists, replaces with $content and places at end of
+ * array.
+ *
+ * @param string $name
+ * @param string $content
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function append($name, $content)
+ {
+ if (!is_string($name)) {
+ throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")');
+ }
+
+ if (isset($this->_body[$name])) {
+ unset($this->_body[$name]);
+ }
+ $this->_body[$name] = (string) $content;
+ return $this;
+ }
+
+ /**
+ * Prepend a named body segment to the body content array
+ *
+ * If segment already exists, replaces with $content and places at top of
+ * array.
+ *
+ * @param string $name
+ * @param string $content
+ * @return void
+ */
+ public function prepend($name, $content)
+ {
+ if (!is_string($name)) {
+ throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")');
+ }
+
+ if (isset($this->_body[$name])) {
+ unset($this->_body[$name]);
+ }
+
+ $new = array($name => (string) $content);
+ $this->_body = $new + $this->_body;
+
+ return $this;
+ }
+
+ /**
+ * Insert a named segment into the body content array
+ *
+ * @param string $name
+ * @param string $content
+ * @param string $parent
+ * @param boolean $before Whether to insert the new segment before or
+ * after the parent. Defaults to false (after)
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function insert($name, $content, $parent = null, $before = false)
+ {
+ if (!is_string($name)) {
+ throw new Zend_Controller_Response_Exception('Invalid body segment key ("' . gettype($name) . '")');
+ }
+
+ if ((null !== $parent) && !is_string($parent)) {
+ throw new Zend_Controller_Response_Exception('Invalid body segment parent key ("' . gettype($parent) . '")');
+ }
+
+ if (isset($this->_body[$name])) {
+ unset($this->_body[$name]);
+ }
+
+ if ((null === $parent) || !isset($this->_body[$parent])) {
+ return $this->append($name, $content);
+ }
+
+ $ins = array($name => (string) $content);
+ $keys = array_keys($this->_body);
+ $loc = array_search($parent, $keys);
+ if (!$before) {
+ // Increment location if not inserting before
+ ++$loc;
+ }
+
+ if (0 === $loc) {
+ // If location of key is 0, we're prepending
+ $this->_body = $ins + $this->_body;
+ } elseif ($loc >= (count($this->_body))) {
+ // If location of key is maximal, we're appending
+ $this->_body = $this->_body + $ins;
+ } else {
+ // Otherwise, insert at location specified
+ $pre = array_slice($this->_body, 0, $loc);
+ $post = array_slice($this->_body, $loc);
+ $this->_body = $pre + $ins + $post;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Echo the body segments
+ *
+ * @return void
+ */
+ public function outputBody()
+ {
+ $body = implode('', $this->_body);
+ echo $body;
+ }
+
+ /**
+ * Register an exception with the response
+ *
+ * @param Exception $e
+ * @return Zend_Controller_Response_Abstract
+ */
+ public function setException(Exception $e)
+ {
+ $this->_exceptions[] = $e;
+ return $this;
+ }
+
+ /**
+ * Retrieve the exception stack
+ *
+ * @return array
+ */
+ public function getException()
+ {
+ return $this->_exceptions;
+ }
+
+ /**
+ * Has an exception been registered with the response?
+ *
+ * @return boolean
+ */
+ public function isException()
+ {
+ return !empty($this->_exceptions);
+ }
+
+ /**
+ * Does the response object contain an exception of a given type?
+ *
+ * @param string $type
+ * @return boolean
+ */
+ public function hasExceptionOfType($type)
+ {
+ foreach ($this->_exceptions as $e) {
+ if ($e instanceof $type) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Does the response object contain an exception with a given message?
+ *
+ * @param string $message
+ * @return boolean
+ */
+ public function hasExceptionOfMessage($message)
+ {
+ foreach ($this->_exceptions as $e) {
+ if ($message == $e->getMessage()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Does the response object contain an exception with a given code?
+ *
+ * @param int $code
+ * @return boolean
+ */
+ public function hasExceptionOfCode($code)
+ {
+ $code = (int) $code;
+ foreach ($this->_exceptions as $e) {
+ if ($code == $e->getCode()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve all exceptions of a given type
+ *
+ * @param string $type
+ * @return false|array
+ */
+ public function getExceptionByType($type)
+ {
+ $exceptions = array();
+ foreach ($this->_exceptions as $e) {
+ if ($e instanceof $type) {
+ $exceptions[] = $e;
+ }
+ }
+
+ if (empty($exceptions)) {
+ $exceptions = false;
+ }
+
+ return $exceptions;
+ }
+
+ /**
+ * Retrieve all exceptions of a given message
+ *
+ * @param string $message
+ * @return false|array
+ */
+ public function getExceptionByMessage($message)
+ {
+ $exceptions = array();
+ foreach ($this->_exceptions as $e) {
+ if ($message == $e->getMessage()) {
+ $exceptions[] = $e;
+ }
+ }
+
+ if (empty($exceptions)) {
+ $exceptions = false;
+ }
+
+ return $exceptions;
+ }
+
+ /**
+ * Retrieve all exceptions of a given code
+ *
+ * @param mixed $code
+ * @return void
+ */
+ public function getExceptionByCode($code)
+ {
+ $code = (int) $code;
+ $exceptions = array();
+ foreach ($this->_exceptions as $e) {
+ if ($code == $e->getCode()) {
+ $exceptions[] = $e;
+ }
+ }
+
+ if (empty($exceptions)) {
+ $exceptions = false;
+ }
+
+ return $exceptions;
+ }
+
+ /**
+ * Whether or not to render exceptions (off by default)
+ *
+ * If called with no arguments or a null argument, returns the value of the
+ * flag; otherwise, sets it and returns the current value.
+ *
+ * @param boolean $flag Optional
+ * @return boolean
+ */
+ public function renderExceptions($flag = null)
+ {
+ if (null !== $flag) {
+ $this->_renderExceptions = $flag ? true : false;
+ }
+
+ return $this->_renderExceptions;
+ }
+
+ /**
+ * Send the response, including all headers, rendering exceptions if so
+ * requested.
+ *
+ * @return void
+ */
+ public function sendResponse()
+ {
+ $this->sendHeaders();
+
+ if ($this->isException() && $this->renderExceptions()) {
+ $exceptions = '';
+ foreach ($this->getException() as $e) {
+ $exceptions .= $e->__toString() . "\n";
+ }
+ echo $exceptions;
+ return;
+ }
+
+ $this->outputBody();
+ }
+
+ /**
+ * Magic __toString functionality
+ *
+ * Proxies to {@link sendResponse()} and returns response value as string
+ * using output buffering.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ ob_start();
+ $this->sendResponse();
+ return ob_get_clean();
+ }
+}
diff --git a/library/vendor/Zend/Controller/Response/Cli.php b/library/vendor/Zend/Controller/Response/Cli.php
new file mode 100644
index 0000000..5177d56
--- /dev/null
+++ b/library/vendor/Zend/Controller/Response/Cli.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Controller_Response_Abstract */
+
+
+/**
+ * Zend_Controller_Response_Cli
+ *
+ * CLI response for controllers
+ *
+ * @uses Zend_Controller_Response_Abstract
+ * @package Zend_Controller
+ * @subpackage Response
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Response_Cli extends Zend_Controller_Response_Abstract
+{
+ /**
+ * Flag; if true, when header operations are called after headers have been
+ * sent, an exception will be raised; otherwise, processing will continue
+ * as normal. Defaults to false.
+ *
+ * @see canSendHeaders()
+ * @var boolean
+ */
+ public $headersSentThrowsException = false;
+
+
+ /**
+ * Magic __toString functionality
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if ($this->isException() && $this->renderExceptions()) {
+ $exceptions = '';
+ foreach ($this->getException() as $e) {
+ $exceptions .= $e->__toString() . "\n";
+ }
+ return $exceptions;
+ }
+
+ return $this->_body;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Response/Exception.php b/library/vendor/Zend/Controller/Response/Exception.php
new file mode 100644
index 0000000..8750ef3
--- /dev/null
+++ b/library/vendor/Zend/Controller/Response/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Request
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Controller_Exception */
+
+
+/**
+ * @package Zend_Controller
+ * @subpackage Response
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Response_Exception extends Zend_Controller_Exception
+{}
+
diff --git a/library/vendor/Zend/Controller/Response/Http.php b/library/vendor/Zend/Controller/Response/Http.php
new file mode 100644
index 0000000..e7656c0
--- /dev/null
+++ b/library/vendor/Zend/Controller/Response/Http.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Controller_Response_Abstract */
+
+
+/**
+ * Zend_Controller_Response_Http
+ *
+ * HTTP response for controllers
+ *
+ * @uses Zend_Controller_Response_Abstract
+ * @package Zend_Controller
+ * @subpackage Response
+ */
+class Zend_Controller_Response_Http extends Zend_Controller_Response_Abstract
+{
+}
diff --git a/library/vendor/Zend/Controller/Response/HttpTestCase.php b/library/vendor/Zend/Controller/Response/HttpTestCase.php
new file mode 100644
index 0000000..f115af9
--- /dev/null
+++ b/library/vendor/Zend/Controller/Response/HttpTestCase.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Controller_Response_Http
+ */
+
+/**
+ * Zend_Controller_Response_HttpTestCase
+ *
+ * @uses Zend_Controller_Response_Http
+ * @package Zend_Controller
+ * @subpackage Response
+ */
+class Zend_Controller_Response_HttpTestCase extends Zend_Controller_Response_Http
+{
+ /**
+ * "send" headers by returning array of all headers that would be sent
+ *
+ * @return array
+ */
+ public function sendHeaders()
+ {
+ $headers = array();
+ foreach ($this->_headersRaw as $header) {
+ $headers[] = $header;
+ }
+ foreach ($this->_headers as $header) {
+ $name = $header['name'];
+ $key = strtolower($name);
+ if (array_key_exists($name, $headers)) {
+ if ($header['replace']) {
+ $headers[$key] = $header['name'] . ': ' . $header['value'];
+ }
+ } else {
+ $headers[$key] = $header['name'] . ': ' . $header['value'];
+ }
+ }
+ return $headers;
+ }
+
+ /**
+ * Can we send headers?
+ *
+ * @param bool $throw
+ * @return void
+ */
+ public function canSendHeaders($throw = false)
+ {
+ return true;
+ }
+
+ /**
+ * Return the concatenated body segments
+ *
+ * @return string
+ */
+ public function outputBody()
+ {
+ $fullContent = '';
+ foreach ($this->_body as $content) {
+ $fullContent .= $content;
+ }
+ return $fullContent;
+ }
+
+ /**
+ * Get body and/or body segments
+ *
+ * @param bool|string $spec
+ * @return string|array|null
+ */
+ public function getBody($spec = false)
+ {
+ if (false === $spec) {
+ return $this->outputBody();
+ } elseif (true === $spec) {
+ return $this->_body;
+ } elseif (is_string($spec) && isset($this->_body[$spec])) {
+ return $this->_body[$spec];
+ }
+
+ return null;
+ }
+
+ /**
+ * "send" Response
+ *
+ * Concats all response headers, and then final body (separated by two
+ * newlines)
+ *
+ * @return string
+ */
+ public function sendResponse()
+ {
+ $headers = $this->sendHeaders();
+ $content = implode("\n", $headers) . "\n\n";
+
+ if ($this->isException() && $this->renderExceptions()) {
+ $exceptions = '';
+ foreach ($this->getException() as $e) {
+ $exceptions .= $e->__toString() . "\n";
+ }
+ $content .= $exceptions;
+ } else {
+ $content .= $this->outputBody();
+ }
+
+ return $content;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Abstract.php b/library/vendor/Zend/Controller/Router/Abstract.php
new file mode 100644
index 0000000..7709caf
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Abstract.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Controller_Router_Interface */
+
+/**
+ * Simple first implementation of a router, to be replaced
+ * with rules-based URI processor.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Router_Abstract implements Zend_Controller_Router_Interface
+{
+ /**
+ * URI delimiter
+ */
+ const URI_DELIMITER = '/';
+
+ /**
+ * Front controller instance
+ *
+ * @var Zend_Controller_Front
+ */
+ protected $_frontController;
+
+ /**
+ * Array of invocation parameters to use when instantiating action
+ * controllers
+ *
+ * @var array
+ */
+ protected $_invokeParams = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $params
+ */
+ public function __construct(array $params = array())
+ {
+ $this->setParams($params);
+ }
+
+ /**
+ * Add or modify a parameter to use when instantiating an action controller
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Controller_Router_Abstract
+ */
+ public function setParam($name, $value)
+ {
+ $name = (string)$name;
+ $this->_invokeParams[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set parameters to pass to action controller constructors
+ *
+ * @param array $params
+ * @return Zend_Controller_Router_Abstract
+ */
+ public function setParams(array $params)
+ {
+ $this->_invokeParams = array_merge($this->_invokeParams, $params);
+
+ return $this;
+ }
+
+ /**
+ * Retrieve a single parameter from the controller parameter stack
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getParam($name)
+ {
+ if (isset($this->_invokeParams[$name])) {
+ return $this->_invokeParams[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve action controller instantiation parameters
+ *
+ * @return array
+ */
+ public function getParams()
+ {
+ return $this->_invokeParams;
+ }
+
+ /**
+ * Clear the controller parameter stack
+ *
+ * By default, clears all parameters. If a parameter name is given, clears
+ * only that parameter; if an array of parameter names is provided, clears
+ * each.
+ *
+ * @param null|string|array single key or array of keys for params to clear
+ * @return Zend_Controller_Router_Abstract
+ */
+ public function clearParams($name = null)
+ {
+ if (null === $name) {
+ $this->_invokeParams = array();
+ } elseif (is_string($name) && isset($this->_invokeParams[$name])) {
+ unset($this->_invokeParams[$name]);
+ } elseif (is_array($name)) {
+ foreach ($name as $key) {
+ if (is_string($key) && isset($this->_invokeParams[$key])) {
+ unset($this->_invokeParams[$key]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve Front Controller
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController()
+ {
+ // Used cache version if found
+ if (null !== $this->_frontController) {
+ return $this->_frontController;
+ }
+
+ $this->_frontController = Zend_Controller_Front::getInstance();
+
+ return $this->_frontController;
+ }
+
+ /**
+ * Set Front Controller
+ *
+ * @param Zend_Controller_Front $controller
+ * @return Zend_Controller_Router_Interface
+ */
+ public function setFrontController(Zend_Controller_Front $controller)
+ {
+ $this->_frontController = $controller;
+
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Exception.php b/library/vendor/Zend/Controller/Router/Exception.php
new file mode 100644
index 0000000..8bdb7ff
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Exception */
+
+/**
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Router_Exception extends Zend_Controller_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Controller/Router/Interface.php b/library/vendor/Zend/Controller/Router/Interface.php
new file mode 100644
index 0000000..920286d
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Interface.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Controller_Router_Interface
+{
+ /**
+ * Processes a request and sets its controller and action. If
+ * no route was possible, an exception is thrown.
+ *
+ * @param Zend_Controller_Request_Abstract
+ * @throws Zend_Controller_Router_Exception
+ * @return Zend_Controller_Request_Abstract|boolean
+ */
+ public function route(Zend_Controller_Request_Abstract $dispatcher);
+
+ /**
+ * Generates a URL path that can be used in URL creation, redirection, etc.
+ *
+ * May be passed user params to override ones from URI, Request or even defaults.
+ * If passed parameter has a value of null, it's URL variable will be reset to
+ * default.
+ *
+ * If null is passed as a route name assemble will use the current Route or 'default'
+ * if current is not yet set.
+ *
+ * Reset is used to signal that all parameters should be reset to it's defaults.
+ * Ignoring all URL specified values. User specified params still get precedence.
+ *
+ * Encode tells to url encode resulting path parts.
+ *
+ * @param array $userParams Options passed by a user used to override parameters
+ * @param mixed $name The name of a Route to use
+ * @param bool $reset Whether to reset to the route defaults ignoring URL params
+ * @param bool $encode Tells to encode URL parts on output
+ * @throws Zend_Controller_Router_Exception
+ * @return string Resulting URL path
+ */
+ public function assemble($userParams, $name = null, $reset = false, $encode = true);
+
+ /**
+ * Retrieve Front Controller
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController();
+
+ /**
+ * Set Front Controller
+ *
+ * @param Zend_Controller_Front $controller
+ * @return Zend_Controller_Router_Interface
+ */
+ public function setFrontController(Zend_Controller_Front $controller);
+
+ /**
+ * Add or modify a parameter with which to instantiate any helper objects
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Controller_Router_Interface
+ */
+ public function setParam($name, $value);
+
+ /**
+ * Set an array of a parameters to pass to helper object constructors
+ *
+ * @param array $params
+ * @return Zend_Controller_Router_Interface
+ */
+ public function setParams(array $params);
+
+ /**
+ * Retrieve a single parameter from the controller parameter stack
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getParam($name);
+
+ /**
+ * Retrieve the parameters to pass to helper object constructors
+ *
+ * @return array
+ */
+ public function getParams();
+
+ /**
+ * Clear the controller parameter stack
+ *
+ * By default, clears all parameters. If a parameter name is given, clears
+ * only that parameter; if an array of parameter names is provided, clears
+ * each.
+ *
+ * @param null|string|array single key or array of keys for params to clear
+ * @return Zend_Controller_Router_Interface
+ */
+ public function clearParams($name = null);
+}
diff --git a/library/vendor/Zend/Controller/Router/Rewrite.php b/library/vendor/Zend/Controller/Router/Rewrite.php
new file mode 100644
index 0000000..bfc3972
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Rewrite.php
@@ -0,0 +1,542 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Abstract */
+
+/** Zend_Controller_Router_Route */
+
+/**
+ * Ruby routing based Router.
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @see http://manuals.rubyonrails.com/read/chapter/65
+ */
+class Zend_Controller_Router_Rewrite extends Zend_Controller_Router_Abstract
+{
+
+ /**
+ * Whether or not to use default routes
+ *
+ * @var boolean
+ */
+ protected $_useDefaultRoutes = true;
+
+ /**
+ * Array of routes to match against
+ *
+ * @var array
+ */
+ protected $_routes = array();
+
+ /**
+ * Currently matched route
+ *
+ * @var string
+ */
+ protected $_currentRoute = null;
+
+ /**
+ * Global parameters given to all routes
+ *
+ * @var array
+ */
+ protected $_globalParams = array();
+
+ /**
+ * Separator to use with chain names
+ *
+ * @var string
+ */
+ protected $_chainNameSeparator = '-';
+
+ /**
+ * Determines if request parameters should be used as global parameters
+ * inside this router.
+ *
+ * @var boolean
+ */
+ protected $_useCurrentParamsAsGlobal = false;
+
+ /**
+ * Add default routes which are used to mimic basic router behaviour
+ *
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function addDefaultRoutes()
+ {
+ if (!$this->hasRoute('default')) {
+ $dispatcher = $this->getFrontController()->getDispatcher();
+ $request = $this->getFrontController()->getRequest();
+
+ $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request);
+
+ $this->_routes = array('default' => $compat) + $this->_routes;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add route to the route chain
+ *
+ * If route contains method setRequest(), it is initialized with a request object
+ *
+ * @param string $name Name of the route
+ * @param Zend_Controller_Router_Route_Interface $route Instance of the route
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function addRoute($name, Zend_Controller_Router_Route_Interface $route)
+ {
+ if (method_exists($route, 'setRequest')) {
+ $route->setRequest($this->getFrontController()->getRequest());
+ }
+
+ $this->_routes[$name] = $route;
+
+ return $this;
+ }
+
+ /**
+ * Add routes to the route chain
+ *
+ * @param array $routes Array of routes with names as keys and routes as values
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function addRoutes($routes)
+ {
+ foreach ($routes as $name => $route) {
+ $this->addRoute($name, $route);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create routes out of Zend_Config configuration
+ *
+ * Example INI:
+ * routes.archive.route = "archive/:year/*"
+ * routes.archive.defaults.controller = archive
+ * routes.archive.defaults.action = show
+ * routes.archive.defaults.year = 2000
+ * routes.archive.reqs.year = "\d+"
+ *
+ * routes.news.type = "Zend_Controller_Router_Route_Static"
+ * routes.news.route = "news"
+ * routes.news.defaults.controller = "news"
+ * routes.news.defaults.action = "list"
+ *
+ * And finally after you have created a Zend_Config with above ini:
+ * $router = new Zend_Controller_Router_Rewrite();
+ * $router->addConfig($config, 'routes');
+ *
+ * @param Zend_Config $config Configuration object
+ * @param string $section Name of the config section containing route's definitions
+ * @throws Zend_Controller_Router_Exception
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function addConfig(Zend_Config $config, $section = null)
+ {
+ if ($section !== null) {
+ if ($config->{$section} === null) {
+ throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'");
+ }
+
+ $config = $config->{$section};
+ }
+
+ foreach ($config as $name => $info) {
+ $route = $this->_getRouteFromConfig($info);
+
+ if ($route instanceof Zend_Controller_Router_Route_Chain) {
+ if (!isset($info->chain)) {
+ throw new Zend_Controller_Router_Exception("No chain defined");
+ }
+
+ if ($info->chain instanceof Zend_Config) {
+ $childRouteNames = $info->chain;
+ } else {
+ $childRouteNames = explode(',', $info->chain);
+ }
+
+ foreach ($childRouteNames as $childRouteName) {
+ $childRoute = $this->getRoute(trim($childRouteName));
+ $route->chain($childRoute);
+ }
+
+ $this->addRoute($name, $route);
+ } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) {
+ $this->_addChainRoutesFromConfig($name, $route, $info->chains);
+ } else {
+ $this->addRoute($name, $route);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a route frm a config instance
+ *
+ * @param Zend_Config $info
+ * @return Zend_Controller_Router_Route_Interface
+ */
+ protected function _getRouteFromConfig(Zend_Config $info)
+ {
+ $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route';
+ if (!class_exists($class)) {
+ Zend_Loader::loadClass($class);
+ }
+
+ $route = call_user_func(
+ array(
+ $class,
+ 'getInstance'
+ ), $info
+ );
+
+ if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) {
+ $route->isAbstract(true);
+ }
+
+ return $route;
+ }
+
+ /**
+ * Add chain routes from a config route
+ *
+ * @param string $name
+ * @param Zend_Controller_Router_Route_Interface $route
+ * @param Zend_Config $childRoutesInfo
+ * @return void
+ */
+ protected function _addChainRoutesFromConfig(
+ $name,
+ Zend_Controller_Router_Route_Interface $route,
+ Zend_Config $childRoutesInfo
+ )
+ {
+ foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) {
+ if (is_string($childRouteInfo)) {
+ $childRouteName = $childRouteInfo;
+ $childRoute = $this->getRoute($childRouteName);
+ } else {
+ $childRoute = $this->_getRouteFromConfig($childRouteInfo);
+ }
+
+ if ($route instanceof Zend_Controller_Router_Route_Chain) {
+ $chainRoute = clone $route;
+ $chainRoute->chain($childRoute);
+ } else {
+ $chainRoute = $route->chain($childRoute);
+ }
+
+ $chainName = $name . $this->_chainNameSeparator . $childRouteName;
+
+ if (isset($childRouteInfo->chains)) {
+ $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains);
+ } else {
+ $this->addRoute($chainName, $chainRoute);
+ }
+ }
+ }
+
+ /**
+ * Remove a route from the route chain
+ *
+ * @param string $name Name of the route
+ * @throws Zend_Controller_Router_Exception
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function removeRoute($name)
+ {
+ if (!isset($this->_routes[$name])) {
+ throw new Zend_Controller_Router_Exception("Route $name is not defined");
+ }
+
+ unset($this->_routes[$name]);
+
+ return $this;
+ }
+
+ /**
+ * Remove all standard default routes
+ *
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function removeDefaultRoutes()
+ {
+ $this->_useDefaultRoutes = false;
+
+ return $this;
+ }
+
+ /**
+ * Check if named route exists
+ *
+ * @param string $name Name of the route
+ * @return boolean
+ */
+ public function hasRoute($name)
+ {
+ return isset($this->_routes[$name]);
+ }
+
+ /**
+ * Retrieve a named route
+ *
+ * @param string $name Name of the route
+ * @throws Zend_Controller_Router_Exception
+ * @return Zend_Controller_Router_Route_Interface Route object
+ */
+ public function getRoute($name)
+ {
+ if (!isset($this->_routes[$name])) {
+ throw new Zend_Controller_Router_Exception("Route $name is not defined");
+ }
+
+ return $this->_routes[$name];
+ }
+
+ /**
+ * Retrieve a currently matched route
+ *
+ * @throws Zend_Controller_Router_Exception
+ * @return Zend_Controller_Router_Route_Interface Route object
+ */
+ public function getCurrentRoute()
+ {
+ if (!isset($this->_currentRoute)) {
+ throw new Zend_Controller_Router_Exception("Current route is not defined");
+ }
+
+ return $this->getRoute($this->_currentRoute);
+ }
+
+ /**
+ * Retrieve a name of currently matched route
+ *
+ * @throws Zend_Controller_Router_Exception
+ * @return string Route name
+ */
+ public function getCurrentRouteName()
+ {
+ if (!isset($this->_currentRoute)) {
+ throw new Zend_Controller_Router_Exception("Current route is not defined");
+ }
+
+ return $this->_currentRoute;
+ }
+
+ /**
+ * Retrieve an array of routes added to the route chain
+ *
+ * @return array All of the defined routes
+ */
+ public function getRoutes()
+ {
+ return $this->_routes;
+ }
+
+ /**
+ * Find a matching route to the current PATH_INFO and inject
+ * returning values to the Request object.
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @throws Zend_Controller_Router_Exception
+ * @return Zend_Controller_Request_Abstract Request object
+ */
+ public function route(Zend_Controller_Request_Abstract $request)
+ {
+ if (!$request instanceof Zend_Controller_Request_Http) {
+ throw new Zend_Controller_Router_Exception(
+ 'Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'
+ );
+ }
+
+ if ($this->_useDefaultRoutes) {
+ $this->addDefaultRoutes();
+ }
+
+ // Find the matching route
+ $routeMatched = false;
+
+ foreach (array_reverse($this->_routes, true) as $name => $route) {
+ // TODO: Should be an interface method. Hack for 1.0 BC
+ if (method_exists($route, 'isAbstract') && $route->isAbstract()) {
+ continue;
+ }
+
+ // TODO: Should be an interface method. Hack for 1.0 BC
+ if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
+ $match = $request->getPathInfo();
+ } else {
+ $match = $request;
+ }
+
+ if ($params = $route->match($match)) {
+ $this->_setRequestParams($request, $params);
+ $this->_currentRoute = $name;
+ $routeMatched = true;
+ break;
+ }
+ }
+
+ if (!$routeMatched) {
+ throw new Zend_Controller_Router_Exception('No route matched the request', 404);
+ }
+
+ if ($this->_useCurrentParamsAsGlobal) {
+ $params = $request->getParams();
+ foreach ($params as $param => $value) {
+ $this->setGlobalParam($param, $value);
+ }
+ }
+
+ return $request;
+ }
+
+ /**
+ * Sets parameters for request object
+ *
+ * Module name, controller name and action name
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @param array $params
+ */
+ protected function _setRequestParams($request, $params)
+ {
+ foreach ($params as $param => $value) {
+
+ $request->setParam($param, $value);
+
+ if ($param === $request->getModuleKey()) {
+ $request->setModuleName($value);
+ }
+ if ($param === $request->getControllerKey()) {
+ $request->setControllerName($value);
+ }
+ if ($param === $request->getActionKey()) {
+ $request->setActionName($value);
+ }
+ }
+ }
+
+ /**
+ * Generates a URL path that can be used in URL creation, redirection, etc.
+ *
+ * @param array $userParams Options passed by a user used to override parameters
+ * @param mixed $name The name of a Route to use
+ * @param bool $reset Whether to reset to the route defaults ignoring URL params
+ * @param bool $encode Tells to encode URL parts on output
+ * @throws Zend_Controller_Router_Exception
+ * @return string Resulting absolute URL path
+ */
+ public function assemble($userParams, $name = null, $reset = false, $encode = true)
+ {
+ if (!is_array($userParams)) {
+ throw new Zend_Controller_Router_Exception('userParams must be an array');
+ }
+
+ if ($name == null) {
+ try {
+ $name = $this->getCurrentRouteName();
+ } catch (Zend_Controller_Router_Exception $e) {
+ $name = 'default';
+ }
+ }
+
+ // Use UNION (+) in order to preserve numeric keys
+ $params = $userParams + $this->_globalParams;
+
+ $route = $this->getRoute($name);
+ $url = $route->assemble($params, $reset, $encode);
+
+ if (!preg_match('|^[a-z]+://|', $url)) {
+ $url = rtrim($this->getFrontController()->getBaseUrl(), self::URI_DELIMITER) . self::URI_DELIMITER . $url;
+ }
+
+ return $url;
+ }
+
+ /**
+ * Set a global parameter
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function setGlobalParam($name, $value)
+ {
+ $this->_globalParams[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set the separator to use with chain names
+ *
+ * @param string $separator The separator to use
+ * @return Zend_Controller_Router_Rewrite
+ */
+ public function setChainNameSeparator($separator)
+ {
+ $this->_chainNameSeparator = $separator;
+
+ return $this;
+ }
+
+ /**
+ * Get the separator to use for chain names
+ *
+ * @return string
+ */
+ public function getChainNameSeparator()
+ {
+ return $this->_chainNameSeparator;
+ }
+
+ /**
+ * Determines/returns whether to use the request parameters as global parameters.
+ *
+ * @param boolean|null $use
+ * Null/unset when you want to retrieve the current state.
+ * True when request parameters should be global, false otherwise
+ * @return boolean|Zend_Controller_Router_Rewrite
+ * Returns a boolean if first param isn't set, returns an
+ * instance of Zend_Controller_Router_Rewrite otherwise.
+ *
+ */
+ public function useRequestParametersAsGlobal($use = null)
+ {
+ if ($use === null) {
+ return $this->_useCurrentParamsAsGlobal;
+ }
+
+ $this->_useCurrentParamsAsGlobal = (bool)$use;
+
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route.php b/library/vendor/Zend/Controller/Router/Route.php
new file mode 100644
index 0000000..e001a49
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route.php
@@ -0,0 +1,603 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Route_Abstract */
+
+/**
+ * Route
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @see http://manuals.rubyonrails.com/read/chapter/65
+ */
+class Zend_Controller_Router_Route extends Zend_Controller_Router_Route_Abstract
+{
+
+ /**
+ * Default translator
+ *
+ * @var Zend_Translate
+ */
+ protected static $_defaultTranslator;
+
+ /**
+ * Translator
+ *
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Default locale
+ *
+ * @var mixed
+ */
+ protected static $_defaultLocale;
+
+ /**
+ * Locale
+ *
+ * @var mixed
+ */
+ protected $_locale;
+
+ /**
+ * Wether this is a translated route or not
+ *
+ * @var boolean
+ */
+ protected $_isTranslated = false;
+
+ /**
+ * Translatable variables
+ *
+ * @var array
+ */
+ protected $_translatable = array();
+
+ protected $_urlVariable = ':';
+
+ protected $_urlDelimiter = self::URI_DELIMITER;
+
+ protected $_regexDelimiter = '#';
+
+ protected $_defaultRegex = null;
+
+ /**
+ * Holds names of all route's pattern variable names. Array index holds a position in URL.
+ *
+ * @var array
+ */
+ protected $_variables = array();
+
+ /**
+ * Holds Route patterns for all URL parts. In case of a variable it stores it's regex
+ * requirement or null. In case of a static part, it holds only it's direct value.
+ * In case of a wildcard, it stores an asterisk (*)
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * Holds user submitted default values for route's variables. Name and value pairs.
+ *
+ * @var array
+ */
+ protected $_defaults = array();
+
+ /**
+ * Holds user submitted regular expression patterns for route's variables' values.
+ * Name and value pairs.
+ *
+ * @var array
+ */
+ protected $_requirements = array();
+
+ /**
+ * Associative array filled on match() that holds matched path values
+ * for given variable names.
+ *
+ * @var array
+ */
+ protected $_values = array();
+
+ /**
+ * Associative array filled on match() that holds wildcard variable
+ * names and values.
+ *
+ * @var array
+ */
+ protected $_wildcardData = array();
+
+ /**
+ * Helper var that holds a count of route pattern's static parts
+ * for validation
+ *
+ * @var int
+ */
+ protected $_staticCount = 0;
+
+ public function getVersion()
+ {
+ return 1;
+ }
+
+ /**
+ * Instantiates route based on passed Zend_Config structure
+ *
+ * @param Zend_Config $config Configuration object
+ * @return Zend_Controller_Router_Route
+ */
+ public static function getInstance(Zend_Config $config)
+ {
+ $reqs = ($config->reqs instanceof Zend_Config) ? $config->reqs->toArray() : array();
+ $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
+
+ return new self($config->route, $defs, $reqs);
+ }
+
+ /**
+ * Prepares the route for mapping by splitting (exploding) it
+ * to a corresponding atomic parts. These parts are assigned
+ * a position which is later used for matching and preparing values.
+ *
+ * @param string $route Map used to match with later submitted URL path
+ * @param array $defaults Defaults for map variables with keys as variable names
+ * @param array $reqs Regular expression requirements for variables (keys as variable names)
+ * @param Zend_Translate $translator Translator to use for this instance
+ * @param mixed|null $locale
+ */
+ public function __construct(
+ $route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null
+ )
+ {
+ $route = trim($route, $this->_urlDelimiter);
+ $this->_defaults = (array)$defaults;
+ $this->_requirements = (array)$reqs;
+ $this->_translator = $translator;
+ $this->_locale = $locale;
+
+ if ($route !== '') {
+ foreach (explode($this->_urlDelimiter, $route) as $pos => $part) {
+ if (substr($part, 0, 1) == $this->_urlVariable && substr($part, 1, 1) != $this->_urlVariable) {
+ $name = substr($part, 1);
+
+ if (substr($name, 0, 1) === '@' && substr($name, 1, 1) !== '@') {
+ $name = substr($name, 1);
+ $this->_translatable[] = $name;
+ $this->_isTranslated = true;
+ }
+
+ $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex);
+ $this->_variables[$pos] = $name;
+ } else {
+ if (substr($part, 0, 1) == $this->_urlVariable) {
+ $part = substr($part, 1);
+ }
+
+ if (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@') {
+ $this->_isTranslated = true;
+ }
+
+ $this->_parts[$pos] = $part;
+
+ if ($part !== '*') {
+ $this->_staticCount++;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Matches a user submitted path with parts defined by a map. Assigns and
+ * returns an array of variables on a successful match.
+ *
+ * @param string $path Path used to match against this routing map
+ * @param boolean $partial
+ * @throws Zend_Controller_Router_Exception
+ * @return array|false An array of assigned values or a false on a mismatch
+ */
+ public function match($path, $partial = false)
+ {
+ if ($this->_isTranslated) {
+ $translateMessages = $this->getTranslator()->getMessages();
+ }
+
+ $pathStaticCount = 0;
+ $values = array();
+ $matchedPath = '';
+
+ if (!$partial) {
+ $path = trim($path, $this->_urlDelimiter);
+ }
+
+ if ($path !== '') {
+ $path = explode($this->_urlDelimiter, $path);
+
+ foreach ($path as $pos => $pathPart) {
+ // Path is longer than a route, it's not a match
+ if (!array_key_exists($pos, $this->_parts)) {
+ if ($partial) {
+ break;
+ } else {
+ return false;
+ }
+ }
+
+ $matchedPath .= $pathPart . $this->_urlDelimiter;
+
+ // If it's a wildcard, get the rest of URL as wildcard data and stop matching
+ if ($this->_parts[$pos] == '*') {
+ $count = count($path);
+ for ($i = $pos; $i < $count; $i += 2) {
+ $var = urldecode($path[$i]);
+ if (!isset($this->_wildcardData[$var]) && !isset($this->_defaults[$var])
+ && !isset($values[$var])
+ ) {
+ $this->_wildcardData[$var] = (isset($path[$i + 1])) ? urldecode($path[$i + 1]) : null;
+ }
+ }
+
+ $matchedPath = implode($this->_urlDelimiter, $path);
+ break;
+ }
+
+ $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null;
+ $pathPart = urldecode($pathPart);
+
+ // Translate value if required
+ $part = $this->_parts[$pos];
+ if ($this->_isTranslated
+ && $part !== null
+ && (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@'
+ && $name === null)
+ || $name !== null && in_array($name, $this->_translatable)
+ ) {
+ if ($part && substr($part, 0, 1) === '@') {
+ $part = substr($part, 1);
+ }
+
+ if (($originalPathPart = array_search($pathPart, $translateMessages)) !== false) {
+ $pathPart = $originalPathPart;
+ }
+ }
+
+ if ($part && substr($part, 0, 2) === '@@') {
+ $part = substr($part, 1);
+ }
+
+ // If it's a static part, match directly
+ if ($name === null && $part != $pathPart) {
+ return false;
+ }
+
+ // If it's a variable with requirement, match a regex. If not - everything matches
+ if ($part !== null
+ && !preg_match(
+ $this->_regexDelimiter . '^' . $part . '$' . $this->_regexDelimiter . 'iu', $pathPart
+ )
+ ) {
+ return false;
+ }
+
+ // If it's a variable store it's value for later
+ if ($name !== null) {
+ $values[$name] = $pathPart;
+ } else {
+ $pathStaticCount++;
+ }
+ }
+ }
+
+ // Check if all static mappings have been matched
+ if ($this->_staticCount != $pathStaticCount) {
+ return false;
+ }
+
+ $return = $values + $this->_wildcardData + $this->_defaults;
+
+ // Check if all map variables have been initialized
+ foreach ($this->_variables as $var) {
+ if (!array_key_exists($var, $return)) {
+ return false;
+ } elseif ($return[$var] == '' || $return[$var] === null) {
+ // Empty variable? Replace with the default value.
+ $return[$var] = $this->_defaults[$var];
+ }
+ }
+
+ $this->setMatchedPath(rtrim($matchedPath, $this->_urlDelimiter));
+
+ $this->_values = $values;
+
+ return $return;
+ }
+
+ /**
+ * Assembles user submitted parameters forming a URL path defined by this route
+ *
+ * @param array $data An array of variable and value pairs used as parameters
+ * @param boolean $reset Whether or not to set route defaults with those provided in $data
+ * @param boolean $encode
+ * @param boolean $partial
+ * @throws Zend_Controller_Router_Exception
+ * @return string Route path with user submitted parameters
+ */
+ public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
+ {
+ if ($this->_isTranslated) {
+ $translator = $this->getTranslator();
+
+ if (isset($data['@locale'])) {
+ $locale = $data['@locale'];
+ unset($data['@locale']);
+ } else {
+ $locale = $this->getLocale();
+ }
+ }
+
+ $url = array();
+ $flag = false;
+
+ foreach ($this->_parts as $key => $part) {
+ $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null;
+
+ $useDefault = false;
+ if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) {
+ $useDefault = true;
+ }
+
+ if (isset($name)) {
+ if (isset($data[$name]) && !$useDefault) {
+ $value = $data[$name];
+ unset($data[$name]);
+ } elseif (!$reset && !$useDefault && isset($this->_values[$name])) {
+ $value = $this->_values[$name];
+ } elseif (!$reset && !$useDefault && isset($this->_wildcardData[$name])) {
+ $value = $this->_wildcardData[$name];
+ } elseif (array_key_exists($name, $this->_defaults)) {
+ $value = $this->_defaults[$name];
+ } else {
+ throw new Zend_Controller_Router_Exception($name . ' is not specified');
+ }
+
+ if ($this->_isTranslated && in_array($name, $this->_translatable)) {
+ $url[$key] = $translator->translate($value, $locale);
+ } else {
+ $url[$key] = $value;
+ }
+ } elseif ($part != '*') {
+ if ($this->_isTranslated && substr($part, 0, 1) === '@') {
+ if (substr($part, 1, 1) !== '@') {
+ $url[$key] = $translator->translate(substr($part, 1), $locale);
+ } else {
+ $url[$key] = substr($part, 1);
+ }
+ } else {
+ if (substr($part, 0, 2) === '@@') {
+ $part = substr($part, 1);
+ }
+
+ $url[$key] = $part;
+ }
+ } else {
+ if (!$reset) {
+ $data += $this->_wildcardData;
+ }
+ $defaults = $this->getDefaults();
+ foreach ($data as $var => $value) {
+ if ($value !== null && (!isset($defaults[$var]) || $value != $defaults[$var])) {
+ $url[$key++] = $var;
+ $url[$key++] = $value;
+ $flag = true;
+ }
+ }
+ }
+ }
+
+ $return = '';
+
+ foreach (array_reverse($url, true) as $key => $value) {
+ $defaultValue = null;
+
+ if (isset($this->_variables[$key])) {
+ $defaultValue = $this->getDefault($this->_variables[$key]);
+
+ if ($this->_isTranslated && $defaultValue !== null
+ && isset($this->_translatable[$this->_variables[$key]])
+ ) {
+ $defaultValue = $translator->translate($defaultValue, $locale);
+ }
+ }
+
+ if ($flag || $value !== $defaultValue || $partial) {
+ if ($encode) {
+ $value = urlencode($value);
+ }
+ $return = $this->_urlDelimiter . $value . $return;
+ $flag = true;
+ }
+ }
+
+ return trim($return, $this->_urlDelimiter);
+ }
+
+ /**
+ * Return a single parameter of route's defaults
+ *
+ * @param string $name Array key of the parameter
+ * @return string Previously set default
+ */
+ public function getDefault($name)
+ {
+ if (isset($this->_defaults[$name])) {
+ return $this->_defaults[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an array of defaults
+ *
+ * @return array Route defaults
+ */
+ public function getDefaults()
+ {
+ return $this->_defaults;
+ }
+
+ /**
+ * Get all variables which are used by the route
+ *
+ * @return array
+ */
+ public function getVariables()
+ {
+ return $this->_variables;
+ }
+
+ /**
+ * Set a default translator
+ *
+ * @param Zend_Translate $translator
+ * @return void
+ */
+ public static function setDefaultTranslator(Zend_Translate $translator = null)
+ {
+ self::$_defaultTranslator = $translator;
+ }
+
+ /**
+ * Get the default translator
+ *
+ * @return Zend_Translate
+ */
+ public static function getDefaultTranslator()
+ {
+ return self::$_defaultTranslator;
+ }
+
+ /**
+ * Set a translator
+ *
+ * @param Zend_Translate $translator
+ * @return void
+ */
+ public function setTranslator(Zend_Translate $translator)
+ {
+ $this->_translator = $translator;
+ }
+
+ /**
+ * Get the translator
+ *
+ * @throws Zend_Controller_Router_Exception When no translator can be found
+ * @return Zend_Translate
+ */
+ public function getTranslator()
+ {
+ if ($this->_translator !== null) {
+ return $this->_translator;
+ } else {
+ if (($translator = self::getDefaultTranslator()) !== null) {
+ return $translator;
+ } else {
+ try {
+ $translator = Zend_Registry::get('Zend_Translate');
+ } catch (Zend_Exception $e) {
+ $translator = null;
+ }
+
+ if ($translator instanceof Zend_Translate) {
+ return $translator;
+ }
+ }
+ }
+
+ throw new Zend_Controller_Router_Exception('Could not find a translator');
+ }
+
+ /**
+ * Set a default locale
+ *
+ * @param mixed $locale
+ * @return void
+ */
+ public static function setDefaultLocale($locale = null)
+ {
+ self::$_defaultLocale = $locale;
+ }
+
+ /**
+ * Get the default locale
+ *
+ * @return mixed
+ */
+ public static function getDefaultLocale()
+ {
+ return self::$_defaultLocale;
+ }
+
+ /**
+ * Set a locale
+ *
+ * @param mixed $locale
+ * @return void
+ */
+ public function setLocale($locale)
+ {
+ $this->_locale = $locale;
+ }
+
+ /**
+ * Get the locale
+ *
+ * @return mixed
+ */
+ public function getLocale()
+ {
+ if ($this->_locale !== null) {
+ return $this->_locale;
+ } else {
+ if (($locale = self::getDefaultLocale()) !== null) {
+ return $locale;
+ } else {
+ try {
+ $locale = Zend_Registry::get('Zend_Locale');
+ } catch (Zend_Exception $e) {
+ $locale = null;
+ }
+
+ if ($locale !== null) {
+ return $locale;
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route/Abstract.php b/library/vendor/Zend/Controller/Router/Route/Abstract.php
new file mode 100644
index 0000000..7ec1e30
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Abstract.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * @see Zend_Controller_Router_Route_Interface
+ */
+
+/**
+ * Abstract Route
+ *
+ * Implements interface and provides convenience methods
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Controller_Router_Route_Abstract implements Zend_Controller_Router_Route_Interface
+{
+ /**
+ * URI delimiter
+ */
+ const URI_DELIMITER = '/';
+
+ /**
+ * Wether this route is abstract or not
+ *
+ * @var boolean
+ */
+ protected $_isAbstract = false;
+
+ /**
+ * Path matched by this route
+ *
+ * @var string
+ */
+ protected $_matchedPath = null;
+
+ /**
+ * Get the version of the route
+ *
+ * @return integer
+ */
+ public function getVersion()
+ {
+ return 2;
+ }
+
+ /**
+ * Set partially matched path
+ *
+ * @param string $path
+ * @return void
+ */
+ public function setMatchedPath($path)
+ {
+ $this->_matchedPath = $path;
+ }
+
+ /**
+ * Get partially matched path
+ *
+ * @return string
+ */
+ public function getMatchedPath()
+ {
+ return $this->_matchedPath;
+ }
+
+ /**
+ * Check or set wether this is an abstract route or not
+ *
+ * @param boolean $flag
+ * @return boolean
+ */
+ public function isAbstract($flag = null)
+ {
+ if ($flag !== null) {
+ $this->_isAbstract = $flag;
+ }
+
+ return $this->_isAbstract;
+ }
+
+ /**
+ * Create a new chain
+ *
+ * @param Zend_Controller_Router_Route_Abstract $route
+ * @param string $separator
+ * @return Zend_Controller_Router_Route_Chain
+ */
+ public function chain(Zend_Controller_Router_Route_Abstract $route, $separator = '/')
+ {
+
+ $chain = new Zend_Controller_Router_Route_Chain();
+ $chain->chain($this)->chain($route, $separator);
+
+ return $chain;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route/Chain.php b/library/vendor/Zend/Controller/Router/Route/Chain.php
new file mode 100644
index 0000000..a4414bd
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Chain.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Route_Abstract */
+
+/**
+ * Chain route is used for managing route chaining.
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Router_Route_Chain extends Zend_Controller_Router_Route_Abstract
+{
+
+ /**
+ * Routes
+ *
+ * @var array
+ */
+ protected $_routes = array();
+
+ /**
+ * Separators
+ *
+ * @var array
+ */
+ protected $_separators = array();
+
+ /**
+ * Instantiates route based on passed Zend_Config structure
+ *
+ * @param Zend_Config $config Configuration object
+ * @return Zend_Controller_Router_Route_Chain
+ */
+ public static function getInstance(Zend_Config $config)
+ {
+ $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
+
+ return new self($config->route, $defs);
+ }
+
+ /**
+ * Add a route to this chain
+ *
+ * @param Zend_Controller_Router_Route_Abstract $route
+ * @param string $separator
+ * @return Zend_Controller_Router_Route_Chain
+ */
+ public function chain(Zend_Controller_Router_Route_Abstract $route, $separator = self::URI_DELIMITER)
+ {
+ $this->_routes[] = $route;
+ $this->_separators[] = $separator;
+
+ return $this;
+ }
+
+ /**
+ * Matches a user submitted path with a previously defined route.
+ * Assigns and returns an array of defaults on a successful match.
+ *
+ * @param Zend_Controller_Request_Http $request Request to get the path info from
+ * @param null $partial
+ * @return array|false An array of assigned values or a false on a mismatch
+ */
+ public function match($request, $partial = null)
+ {
+ $rawPath = $request->getPathInfo();
+ $path = trim($request->getPathInfo(), self::URI_DELIMITER);
+ $subPath = $path;
+ $values = array();
+ $matchedPath = null;
+
+ foreach ($this->_routes as $key => $route) {
+ if ($key > 0
+ && $matchedPath !== null
+ && $subPath !== ''
+ && $subPath !== false
+ ) {
+ $separator = substr($subPath, 0, strlen($this->_separators[$key]));
+
+ if ($separator !== $this->_separators[$key]) {
+ $request->setPathInfo($rawPath);
+ return false;
+ }
+
+ $subPath = substr($subPath, strlen($separator));
+ }
+ // TODO: Should be an interface method. Hack for 1.0 BC
+ if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
+ $match = $subPath;
+ } else {
+ $request->setPathInfo($subPath);
+ $match = $request;
+ }
+
+ $res = $route->match($match, true);
+
+ if ($res === false) {
+ $request->setPathInfo($rawPath);
+ return false;
+ }
+
+ $matchedPath = $route->getMatchedPath();
+
+ if ($matchedPath !== null) {
+ $subPath = substr($subPath, strlen($matchedPath));
+ }
+
+ $values = $res + $values;
+ }
+
+ $request->setPathInfo($path);
+
+ if ($subPath !== '' && $subPath !== false) {
+ return false;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Assembles a URL path defined by this route
+ *
+ * @param array $data An array of variable and value pairs used as parameters
+ * @param bool $reset
+ * @param bool $encode
+ * @return string Route path with user submitted parameters
+ */
+ public function assemble($data = array(), $reset = false, $encode = false)
+ {
+ $value = '';
+ $numRoutes = count($this->_routes);
+
+ foreach ($this->_routes as $key => $route) {
+ if ($key > 0) {
+ $value .= $this->_separators[$key];
+ }
+
+ $value .= $route->assemble($data, $reset, $encode, (($numRoutes - 1) > $key));
+
+ if (method_exists($route, 'getVariables')) {
+ $variables = $route->getVariables();
+
+ foreach ($variables as $variable) {
+ $data[$variable] = null;
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Set the request object for this and the child routes
+ *
+ * @param Zend_Controller_Request_Abstract|null $request
+ * @return void
+ */
+ public function setRequest(Zend_Controller_Request_Abstract $request = null)
+ {
+ $this->_request = $request;
+
+ foreach ($this->_routes as $route) {
+ if (method_exists($route, 'setRequest')) {
+ $route->setRequest($request);
+ }
+ }
+ }
+
+ /**
+ * Return a single parameter of route's defaults
+ *
+ * @param string $name Array key of the parameter
+ * @return string Previously set default
+ */
+ public function getDefault($name)
+ {
+ $default = null;
+ foreach ($this->_routes as $route) {
+ if (method_exists($route, 'getDefault')) {
+ $current = $route->getDefault($name);
+ if (null !== $current) {
+ $default = $current;
+ }
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return an array of defaults
+ *
+ * @return array Route defaults
+ */
+ public function getDefaults()
+ {
+ $defaults = array();
+ foreach ($this->_routes as $route) {
+ if (method_exists($route, 'getDefaults')) {
+ $defaults = array_merge($defaults, $route->getDefaults());
+ }
+ }
+
+ return $defaults;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route/Hostname.php b/library/vendor/Zend/Controller/Router/Route/Hostname.php
new file mode 100644
index 0000000..b2be1c1
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Hostname.php
@@ -0,0 +1,377 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Route_Abstract */
+
+/**
+ * Hostname Route
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @see http://manuals.rubyonrails.com/read/chapter/65
+ */
+class Zend_Controller_Router_Route_Hostname extends Zend_Controller_Router_Route_Abstract
+{
+
+ /**
+ * Host variable
+ *
+ * @var string
+ */
+ protected $_hostVariable = ':';
+
+ /**
+ * Regex delimiter
+ *
+ * @var string
+ */
+ protected $_regexDelimiter = '#';
+
+ /**
+ * Default regex string
+ *
+ * @var string|null
+ */
+ protected $_defaultRegex = null;
+
+ /**
+ * Holds names of all route's pattern variable names. Array index holds a position in host.
+ *
+ * @var array
+ */
+ protected $_variables = array();
+
+ /**
+ * Holds Route patterns for all host parts. In case of a variable it stores it's regex
+ * requirement or null. In case of a static part, it holds only it's direct value.
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * Holds user submitted default values for route's variables. Name and value pairs.
+ *
+ * @var array
+ */
+ protected $_defaults = array();
+
+ /**
+ * Holds user submitted regular expression patterns for route's variables' values.
+ * Name and value pairs.
+ *
+ * @var array
+ */
+ protected $_requirements = array();
+
+ /**
+ * Default scheme
+ *
+ * @var string
+ */
+ protected $_scheme = null;
+
+ /**
+ * Associative array filled on match() that holds matched path values
+ * for given variable names.
+ *
+ * @var array
+ */
+ protected $_values = array();
+
+ /**
+ * Current request object
+ *
+ * @var Zend_Controller_Request_Abstract
+ */
+ protected $_request;
+
+ /**
+ * Helper var that holds a count of route pattern's static parts
+ * for validation
+ *
+ * @var int
+ */
+ private $_staticCount = 0;
+
+ /**
+ * Set the request object
+ *
+ * @param Zend_Controller_Request_Abstract|null $request
+ */
+ public function setRequest(Zend_Controller_Request_Abstract $request = null)
+ {
+ $this->_request = $request;
+ }
+
+ /**
+ * Get the request object
+ *
+ * @return Zend_Controller_Request_Abstract $request
+ */
+ public function getRequest()
+ {
+ if ($this->_request === null) {
+ $this->_request = Zend_Controller_Front::getInstance()->getRequest();
+ }
+
+ return $this->_request;
+ }
+
+ /**
+ * Instantiates route based on passed Zend_Config structure
+ *
+ * @param Zend_Config $config Configuration object
+ * @return Zend_Controller_Router_Route_Hostname
+ */
+ public static function getInstance(Zend_Config $config)
+ {
+ $reqs = ($config->reqs instanceof Zend_Config) ? $config->reqs->toArray() : array();
+ $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
+ $scheme = (isset($config->scheme)) ? $config->scheme : null;
+
+ return new self($config->route, $defs, $reqs, $scheme);
+ }
+
+ /**
+ * Prepares the route for mapping by splitting (exploding) it
+ * to a corresponding atomic parts. These parts are assigned
+ * a position which is later used for matching and preparing values.
+ *
+ * @param string $route Map used to match with later submitted hostname
+ * @param array $defaults Defaults for map variables with keys as variable names
+ * @param array $reqs Regular expression requirements for variables (keys as variable names)
+ * @param string $scheme
+ */
+ public function __construct($route, $defaults = array(), $reqs = array(), $scheme = null)
+ {
+ $route = trim($route, '.');
+ $this->_defaults = (array) $defaults;
+ $this->_requirements = (array) $reqs;
+ $this->_scheme = $scheme;
+
+ if ($route != '') {
+ foreach (explode('.', $route) as $pos => $part) {
+ if (substr($part, 0, 1) == $this->_hostVariable) {
+ $name = substr($part, 1);
+ $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex);
+ $this->_variables[$pos] = $name;
+ } else {
+ $this->_parts[$pos] = $part;
+ $this->_staticCount++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Matches a user submitted path with parts defined by a map. Assigns and
+ * returns an array of variables on a successful match.
+ *
+ * @param Zend_Controller_Request_Http $request Request to get the host from
+ * @return array|false An array of assigned values or a false on a mismatch
+ */
+ public function match($request)
+ {
+ // Check the scheme if required
+ if ($this->_scheme !== null) {
+ $scheme = $request->getScheme();
+
+ if ($scheme !== $this->_scheme) {
+ return false;
+ }
+ }
+
+ // Get the host and remove unnecessary port information
+ $host = $request->getHttpHost();
+ if (preg_match('#:\d+$#', $host, $result) === 1) {
+ $host = substr($host, 0, -strlen($result[0]));
+ }
+
+ $hostStaticCount = 0;
+ $values = array();
+
+ $host = trim($host, '.');
+
+ if ($host != '') {
+ $host = explode('.', $host);
+
+ foreach ($host as $pos => $hostPart) {
+ // Host is longer than a route, it's not a match
+ if (!array_key_exists($pos, $this->_parts)) {
+ return false;
+ }
+
+ $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null;
+ $hostPart = urldecode($hostPart);
+
+ // If it's a static part, match directly
+ if ($name === null && $this->_parts[$pos] != $hostPart) {
+ return false;
+ }
+
+ // If it's a variable with requirement, match a regex. If not - everything matches
+ if ($this->_parts[$pos] !== null
+ && !preg_match(
+ $this->_regexDelimiter . '^' . $this->_parts[$pos] . '$' . $this->_regexDelimiter . 'iu',
+ $hostPart
+ )
+ ) {
+ return false;
+ }
+
+ // If it's a variable store it's value for later
+ if ($name !== null) {
+ $values[$name] = $hostPart;
+ } else {
+ $hostStaticCount++;
+ }
+ }
+ }
+
+ // Check if all static mappings have been matched
+ if ($this->_staticCount != $hostStaticCount) {
+ return false;
+ }
+
+ $return = $values + $this->_defaults;
+
+ // Check if all map variables have been initialized
+ foreach ($this->_variables as $var) {
+ if (!array_key_exists($var, $return)) {
+ return false;
+ }
+ }
+
+ $this->_values = $values;
+
+ return $return;
+ }
+
+ /**
+ * Assembles user submitted parameters forming a hostname defined by this route
+ *
+ * @param array $data An array of variable and value pairs used as parameters
+ * @param boolean $reset Whether or not to set route defaults with those provided in $data
+ * @param boolean $encode
+ * @param boolean $partial
+ * @throws Zend_Controller_Router_Exception
+ * @return string Route path with user submitted parameters
+ */
+ public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
+ {
+ $host = array();
+ $flag = false;
+
+ foreach ($this->_parts as $key => $part) {
+ $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null;
+
+ $useDefault = false;
+ if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) {
+ $useDefault = true;
+ }
+
+ if (isset($name)) {
+ if (isset($data[$name]) && !$useDefault) {
+ $host[$key] = $data[$name];
+ unset($data[$name]);
+ } elseif (!$reset && !$useDefault && isset($this->_values[$name])) {
+ $host[$key] = $this->_values[$name];
+ } elseif (isset($this->_defaults[$name])) {
+ $host[$key] = $this->_defaults[$name];
+ } else {
+ throw new Zend_Controller_Router_Exception($name . ' is not specified');
+ }
+ } else {
+ $host[$key] = $part;
+ }
+ }
+
+ $return = '';
+
+ foreach (array_reverse($host, true) as $key => $value) {
+ if ($flag || !isset($this->_variables[$key]) || $value !== $this->getDefault($this->_variables[$key])
+ || $partial
+ ) {
+ if ($encode) {
+ $value = urlencode($value);
+ }
+ $return = '.' . $value . $return;
+ $flag = true;
+ }
+ }
+
+ $url = trim($return, '.');
+
+ if ($this->_scheme !== null) {
+ $scheme = $this->_scheme;
+ } else {
+ $request = $this->getRequest();
+ if ($request instanceof Zend_Controller_Request_Http) {
+ $scheme = $request->getScheme();
+ } else {
+ $scheme = 'http';
+ }
+ }
+
+ $url = $scheme . '://' . $url;
+
+ return $url;
+ }
+
+ /**
+ * Return a single parameter of route's defaults
+ *
+ * @param string $name Array key of the parameter
+ * @return string Previously set default
+ */
+ public function getDefault($name)
+ {
+ if (isset($this->_defaults[$name])) {
+ return $this->_defaults[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an array of defaults
+ *
+ * @return array Route defaults
+ */
+ public function getDefaults()
+ {
+ return $this->_defaults;
+ }
+
+ /**
+ * Get all variables which are used by the route
+ *
+ * @return array
+ */
+ public function getVariables()
+ {
+ return $this->_variables;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route/Interface.php b/library/vendor/Zend/Controller/Router/Route/Interface.php
new file mode 100644
index 0000000..6a14f9d
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Interface.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Config */
+
+/**
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Controller_Router_Route_Interface
+{
+ public function match($path);
+
+ public function assemble($data = array(), $reset = false, $encode = false);
+
+ public static function getInstance(Zend_Config $config);
+} \ No newline at end of file
diff --git a/library/vendor/Zend/Controller/Router/Route/Module.php b/library/vendor/Zend/Controller/Router/Route/Module.php
new file mode 100644
index 0000000..2227495
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Module.php
@@ -0,0 +1,322 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Route_Abstract */
+
+/**
+ * Module Route
+ *
+ * Default route for module functionality
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @see http://manuals.rubyonrails.com/read/chapter/65
+ */
+class Zend_Controller_Router_Route_Module extends Zend_Controller_Router_Route_Abstract
+{
+
+ /**
+ * Default values for the route (ie. module, controller, action, params)
+ *
+ * @var array
+ */
+ protected $_defaults;
+
+ /**
+ * Default values for the route (ie. module, controller, action, params)
+ *
+ * @var array
+ */
+ protected $_values = array();
+
+ /**
+ * @var boolean
+ */
+ protected $_moduleValid = false;
+
+ /**
+ * @var boolean
+ */
+ protected $_keysSet = false;
+
+ /**#@+
+ * Array keys to use for module, controller, and action. Should be taken out of request.
+ *
+ * @var string
+ */
+ protected $_moduleKey = 'module';
+ protected $_controllerKey = 'controller';
+ protected $_actionKey = 'action';
+ /**#@-*/
+
+ /**
+ * @var Zend_Controller_Dispatcher_Interface
+ */
+ protected $_dispatcher;
+
+ /**
+ * @var Zend_Controller_Request_Abstract
+ */
+ protected $_request;
+
+ /**
+ * Get the version of the route
+ *
+ * @return int
+ */
+ public function getVersion()
+ {
+ return 1;
+ }
+
+ /**
+ * Instantiates route based on passed Zend_Config structure
+ *
+ * @param Zend_Config $config
+ * @return Zend_Controller_Router_Route_Module
+ */
+ public static function getInstance(Zend_Config $config)
+ {
+ $frontController = Zend_Controller_Front::getInstance();
+
+ $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
+ $dispatcher = $frontController->getDispatcher();
+ $request = $frontController->getRequest();
+
+ return new self($defs, $dispatcher, $request);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param array $defaults Defaults for map variables with keys as variable names
+ * @param Zend_Controller_Dispatcher_Interface $dispatcher Dispatcher object
+ * @param Zend_Controller_Request_Abstract $request Request object
+ */
+ public function __construct(
+ array $defaults = array(),
+ Zend_Controller_Dispatcher_Interface $dispatcher = null,
+ Zend_Controller_Request_Abstract $request = null
+ )
+ {
+ $this->_defaults = $defaults;
+
+ if (isset($request)) {
+ $this->_request = $request;
+ }
+
+ if (isset($dispatcher)) {
+ $this->_dispatcher = $dispatcher;
+ }
+ }
+
+ /**
+ * Set request keys based on values in request object
+ *
+ * @return void
+ */
+ protected function _setRequestKeys()
+ {
+ if (null !== $this->_request) {
+ $this->_moduleKey = $this->_request->getModuleKey();
+ $this->_controllerKey = $this->_request->getControllerKey();
+ $this->_actionKey = $this->_request->getActionKey();
+ }
+
+ if (null !== $this->_dispatcher) {
+ $this->_defaults += array(
+ $this->_controllerKey => $this->_dispatcher->getDefaultControllerName(),
+ $this->_actionKey => $this->_dispatcher->getDefaultAction(),
+ $this->_moduleKey => $this->_dispatcher->getDefaultModule()
+ );
+ }
+
+ $this->_keysSet = true;
+ }
+
+ /**
+ * Matches a user submitted path. Assigns and returns an array of variables
+ * on a successful match.
+ *
+ * If a request object is registered, it uses its setModuleName(),
+ * setControllerName(), and setActionName() accessors to set those values.
+ * Always returns the values as an array.
+ *
+ * @param string $path Path used to match against this routing map
+ * @param boolean $partial
+ * @return array An array of assigned values or a false on a mismatch
+ */
+ public function match($path, $partial = false)
+ {
+ $this->_setRequestKeys();
+
+ $values = array();
+ $params = array();
+
+ if (!$partial) {
+ $path = trim($path, self::URI_DELIMITER);
+ } else {
+ $matchedPath = $path;
+ }
+
+ if ($path != '') {
+ $path = explode(self::URI_DELIMITER, $path);
+
+ if ($this->_dispatcher && $this->_dispatcher->isValidModule($path[0])) {
+ $values[$this->_moduleKey] = array_shift($path);
+ $this->_moduleValid = true;
+ }
+
+ if (count($path) && !empty($path[0])) {
+ $values[$this->_controllerKey] = array_shift($path);
+ }
+
+ if (count($path) && !empty($path[0])) {
+ $values[$this->_actionKey] = array_shift($path);
+ }
+
+ if ($numSegs = count($path)) {
+ for ($i = 0; $i < $numSegs; $i = $i + 2) {
+ $key = urldecode($path[$i]);
+ $val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null;
+ $params[$key] = (isset($params[$key]) ? (array_merge((array)$params[$key], array($val))) : $val);
+ }
+ }
+ }
+
+ if ($partial) {
+ $this->setMatchedPath($matchedPath);
+ }
+
+ $this->_values = $values + $params;
+
+ return $this->_values + $this->_defaults;
+ }
+
+ /**
+ * Assembles user submitted parameters forming a URL path defined by this route
+ *
+ * @param array $data An array of variable and value pairs used as parameters
+ * @param boolean $reset Weither to reset the current params
+ * @param boolean $encode
+ * @param boolean $partial
+ * @return string Route path with user submitted parameters
+ */
+ public function assemble($data = array(), $reset = false, $encode = true, $partial = false)
+ {
+ if (!$this->_keysSet) {
+ $this->_setRequestKeys();
+ }
+
+ $params = (!$reset) ? $this->_values : array();
+
+ foreach ($data as $key => $value) {
+ if ($value !== null) {
+ $params[$key] = $value;
+ } elseif (isset($params[$key])) {
+ unset($params[$key]);
+ }
+ }
+
+ $params += $this->_defaults;
+
+ $url = '';
+
+ if ($this->_moduleValid || array_key_exists($this->_moduleKey, $data)) {
+ if ($params[$this->_moduleKey] != $this->_defaults[$this->_moduleKey]) {
+ $module = $params[$this->_moduleKey];
+ }
+ }
+ unset($params[$this->_moduleKey]);
+
+ $controller = $params[$this->_controllerKey];
+ unset($params[$this->_controllerKey]);
+
+ $action = $params[$this->_actionKey];
+ unset($params[$this->_actionKey]);
+
+ foreach ($params as $key => $value) {
+ $key = ($encode) ? urlencode($key) : $key;
+ if (is_array($value)) {
+ foreach ($value as $arrayValue) {
+ $arrayValue = ($encode) ? urlencode($arrayValue) : $arrayValue;
+ $url .= self::URI_DELIMITER . $key;
+ $url .= self::URI_DELIMITER . $arrayValue;
+ }
+ } else {
+ if ($encode) {
+ $value = urlencode($value);
+ }
+ $url .= self::URI_DELIMITER . $key;
+ $url .= self::URI_DELIMITER . $value;
+ }
+ }
+
+ if (!empty($url) || $action !== $this->_defaults[$this->_actionKey]) {
+ if ($encode) {
+ $action = urlencode($action);
+ }
+ $url = self::URI_DELIMITER . $action . $url;
+ }
+
+ if (!empty($url) || $controller !== $this->_defaults[$this->_controllerKey]) {
+ if ($encode) {
+ $controller = urlencode($controller);
+ }
+ $url = self::URI_DELIMITER . $controller . $url;
+ }
+
+ if (isset($module)) {
+ if ($encode) {
+ $module = urlencode($module);
+ }
+ $url = self::URI_DELIMITER . $module . $url;
+ }
+
+ return ltrim($url, self::URI_DELIMITER);
+ }
+
+ /**
+ * Return a single parameter of route's defaults
+ *
+ * @param string $name Array key of the parameter
+ * @return string Previously set default
+ */
+ public function getDefault($name)
+ {
+ if (isset($this->_defaults[$name])) {
+ return $this->_defaults[$name];
+ }
+ }
+
+ /**
+ * Return an array of defaults
+ *
+ * @return array Route defaults
+ */
+ public function getDefaults()
+ {
+ return $this->_defaults;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route/Regex.php b/library/vendor/Zend/Controller/Router/Route/Regex.php
new file mode 100644
index 0000000..8dea1c0
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Regex.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Route_Abstract */
+
+/**
+ * Regex Route
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Router_Route_Regex extends Zend_Controller_Router_Route_Abstract
+{
+
+ /**
+ * Regex string
+ *
+ * @var string|null
+ */
+ protected $_regex = null;
+
+ /**
+ * Default values for the route (ie. module, controller, action, params)
+ *
+ * @var array
+ */
+ protected $_defaults = array();
+
+ /**
+ * Reverse
+ *
+ * @var string|null
+ */
+ protected $_reverse = null;
+
+ /**
+ * Map
+ *
+ * @var array
+ */
+ protected $_map = array();
+
+ /**
+ * Values
+ *
+ * @var array
+ */
+ protected $_values = array();
+
+ /**
+ * Instantiates route based on passed Zend_Config structure
+ *
+ * @param Zend_Config $config Configuration object
+ * @return Zend_Controller_Router_Route_Regex
+ */
+ public static function getInstance(Zend_Config $config)
+ {
+ $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
+ $map = ($config->map instanceof Zend_Config) ? $config->map->toArray() : array();
+ $reverse = (isset($config->reverse)) ? $config->reverse : null;
+
+ return new self($config->route, $defs, $map, $reverse);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param $route
+ * @param array $defaults
+ * @param array $map
+ * @param null $reverse
+ */
+ public function __construct($route, $defaults = array(), $map = array(), $reverse = null)
+ {
+ $this->_regex = $route;
+ $this->_defaults = (array) $defaults;
+ $this->_map = (array) $map;
+ $this->_reverse = $reverse;
+ }
+
+ /**
+ * Get the version of the route
+ *
+ * @return int
+ */
+ public function getVersion()
+ {
+ return 1;
+ }
+
+ /**
+ * Matches a user submitted path with a previously defined route.
+ * Assigns and returns an array of defaults on a successful match.
+ *
+ * @param string $path Path used to match against this routing map
+ * @return array|false An array of assigned values or a false on a mismatch
+ */
+ public function match($path, $partial = false)
+ {
+ if (!$partial) {
+ $path = trim(urldecode($path), self::URI_DELIMITER);
+ $regex = '#^' . $this->_regex . '$#i';
+ } else {
+ $regex = '#^' . $this->_regex . '#i';
+ }
+
+ $res = preg_match($regex, $path, $values);
+
+ if ($res === 0) {
+ return false;
+ }
+
+ if ($partial) {
+ $this->setMatchedPath($values[0]);
+ }
+
+ // array_filter_key()? Why isn't this in a standard PHP function set yet? :)
+ foreach ($values as $i => $value) {
+ if (!is_int($i) || $i === 0) {
+ unset($values[$i]);
+ }
+ }
+
+ $this->_values = $values;
+
+ $values = $this->_getMappedValues($values);
+ $defaults = $this->_getMappedValues($this->_defaults, false, true);
+ $return = $values + $defaults;
+
+ return $return;
+ }
+
+ /**
+ * Maps numerically indexed array values to it's associative mapped counterpart.
+ * Or vice versa. Uses user provided map array which consists of index => name
+ * parameter mapping. If map is not found, it returns original array.
+ *
+ * Method strips destination type of keys form source array. Ie. if source array is
+ * indexed numerically then every associative key will be stripped. Vice versa if reversed
+ * is set to true.
+ *
+ * @param array $values Indexed or associative array of values to map
+ * @param boolean $reversed False means translation of index to association. True means reverse.
+ * @param boolean $preserve Should wrong type of keys be preserved or stripped.
+ * @return array An array of mapped values
+ */
+ protected function _getMappedValues($values, $reversed = false, $preserve = false)
+ {
+ if (count($this->_map) == 0) {
+ return $values;
+ }
+
+ $return = array();
+
+ foreach ($values as $key => $value) {
+ if (is_int($key) && !$reversed) {
+ if (array_key_exists($key, $this->_map)) {
+ $index = $this->_map[$key];
+ } elseif (false === ($index = array_search($key, $this->_map))) {
+ $index = $key;
+ }
+ $return[$index] = $values[$key];
+ } elseif ($reversed) {
+ $index = $key;
+ if (!is_int($key)) {
+ if (array_key_exists($key, $this->_map)) {
+ $index = $this->_map[$key];
+ } else {
+ $index = array_search($key, $this->_map, true);
+ }
+ }
+ if (false !== $index) {
+ $return[$index] = $values[$key];
+ }
+ } elseif ($preserve) {
+ $return[$key] = $value;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Assembles a URL path defined by this route
+ *
+ * @param array $data An array of name (or index) and value pairs used as parameters
+ * @param boolean $reset
+ * @param boolean $encode
+ * @param boolean $partial
+ * @throws Zend_Controller_Router_Exception
+ * @return string Route path with user submitted parameters
+ */
+ public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
+ {
+ if ($this->_reverse === null) {
+ throw new Zend_Controller_Router_Exception('Cannot assemble. Reversed route is not specified.');
+ }
+
+ $defaultValuesMapped = $this->_getMappedValues($this->_defaults, true, false);
+ $matchedValuesMapped = $this->_getMappedValues($this->_values, true, false);
+ $dataValuesMapped = $this->_getMappedValues($data, true, false);
+
+ // handle resets, if so requested (By null value) to do so
+ if (($resetKeys = array_search(null, $dataValuesMapped, true)) !== false) {
+ foreach ((array)$resetKeys as $resetKey) {
+ if (isset($matchedValuesMapped[$resetKey])) {
+ unset($matchedValuesMapped[$resetKey]);
+ unset($dataValuesMapped[$resetKey]);
+ }
+ }
+ }
+
+ // merge all the data together, first defaults, then values matched, then supplied
+ $mergedData = $defaultValuesMapped;
+ $mergedData = $this->_arrayMergeNumericKeys($mergedData, $matchedValuesMapped);
+ $mergedData = $this->_arrayMergeNumericKeys($mergedData, $dataValuesMapped);
+
+ if ($encode) {
+ foreach ($mergedData as $key => &$value) {
+ $value = urlencode($value);
+ }
+ }
+
+ ksort($mergedData);
+
+ $return = @vsprintf($this->_reverse, $mergedData);
+
+ if ($return === false) {
+ throw new Zend_Controller_Router_Exception('Cannot assemble. Too few arguments?');
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return a single parameter of route's defaults
+ *
+ * @param string $name Array key of the parameter
+ * @return string Previously set default
+ */
+ public function getDefault($name)
+ {
+ if (isset($this->_defaults[$name])) {
+ return $this->_defaults[$name];
+ }
+ }
+
+ /**
+ * Return an array of defaults
+ *
+ * @return array Route defaults
+ */
+ public function getDefaults()
+ {
+ return $this->_defaults;
+ }
+
+ /**
+ * Get all variables which are used by the route
+ *
+ * @return array
+ */
+ public function getVariables()
+ {
+ $variables = array();
+
+ foreach ($this->_map as $key => $value) {
+ if (is_numeric($key)) {
+ $variables[] = $value;
+ } else {
+ $variables[] = $key;
+ }
+ }
+
+ return $variables;
+ }
+
+ /**
+ * _arrayMergeNumericKeys() - allows for a strict key (numeric's included) array_merge.
+ * php's array_merge() lacks the ability to merge with numeric keys.
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ protected function _arrayMergeNumericKeys(Array $array1, Array $array2)
+ {
+ $returnArray = $array1;
+ foreach ($array2 as $array2Index => $array2Value) {
+ $returnArray[$array2Index] = $array2Value;
+ }
+
+ return $returnArray;
+ }
+}
diff --git a/library/vendor/Zend/Controller/Router/Route/Static.php b/library/vendor/Zend/Controller/Router/Route/Static.php
new file mode 100644
index 0000000..86363da
--- /dev/null
+++ b/library/vendor/Zend/Controller/Router/Route/Static.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Router_Route_Abstract */
+
+/**
+ * StaticRoute is used for managing static URIs.
+ *
+ * It's a lot faster compared to the standard Route implementation.
+ *
+ * @package Zend_Controller
+ * @subpackage Router
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Controller_Router_Route_Static extends Zend_Controller_Router_Route_Abstract
+{
+
+ /**
+ * Route
+ *
+ * @var string|null
+ */
+ protected $_route = null;
+
+ /**
+ * Default values for the route (ie. module, controller, action, params)
+ *
+ * @var array
+ */
+ protected $_defaults = array();
+
+ /**
+ * Get the version of the route
+ *
+ * @return int
+ */
+ public function getVersion()
+ {
+ return 1;
+ }
+
+ /**
+ * Instantiates route based on passed Zend_Config structure
+ *
+ * @param Zend_Config $config Configuration object
+ * @return Zend_Controller_Router_Route_Static
+ */
+ public static function getInstance(Zend_Config $config)
+ {
+ $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array();
+
+ return new self($config->route, $defs);
+ }
+
+ /**
+ * Prepares the route for mapping.
+ *
+ * @param string $route Map used to match with later submitted URL path
+ * @param array $defaults Defaults for map variables with keys as variable names
+ */
+ public function __construct($route, $defaults = array())
+ {
+ $this->_route = trim($route, self::URI_DELIMITER);
+ $this->_defaults = (array) $defaults;
+ }
+
+ /**
+ * Matches a user submitted path with a previously defined route.
+ * Assigns and returns an array of defaults on a successful match.
+ *
+ * @param string $path Path used to match against this routing map
+ * @return array|false An array of assigned values or a false on a mismatch
+ */
+ public function match($path, $partial = false)
+ {
+ if ($partial) {
+ if ((empty($path) && empty($this->_route))
+ || (substr($path, 0, strlen($this->_route)) === $this->_route)
+ ) {
+ $this->setMatchedPath($this->_route);
+
+ return $this->_defaults;
+ }
+ } else {
+ if (trim($path, self::URI_DELIMITER) == $this->_route) {
+ return $this->_defaults;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Assembles a URL path defined by this route
+ *
+ * @param array $data An array of variable and value pairs used as parameters
+ * @return string Route path with user submitted parameters
+ */
+ public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
+ {
+ return $this->_route;
+ }
+
+ /**
+ * Return a single parameter of route's defaults
+ *
+ * @param string $name Array key of the parameter
+ * @return string Previously set default
+ */
+ public function getDefault($name)
+ {
+ if (isset($this->_defaults[$name])) {
+ return $this->_defaults[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Return an array of defaults
+ *
+ * @return array Route defaults
+ */
+ public function getDefaults()
+ {
+ return $this->_defaults;
+ }
+}
diff --git a/library/vendor/Zend/Crypt.php b/library/vendor/Zend/Crypt.php
new file mode 100644
index 0000000..391a5fa
--- /dev/null
+++ b/library/vendor/Zend/Crypt.php
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt
+{
+
+ const TYPE_OPENSSL = 'openssl';
+ const TYPE_HASH = 'hash';
+ const TYPE_MHASH = 'mhash';
+
+ protected static $_type = null;
+
+ /**
+ * @var array
+ */
+ protected static $_supportedAlgosOpenssl = array(
+ 'md2',
+ 'md4',
+ 'mdc2',
+ 'rmd160',
+ 'sha',
+ 'sha1',
+ 'sha224',
+ 'sha256',
+ 'sha384',
+ 'sha512'
+ );
+
+ /**
+ * @var array
+ */
+ protected static $_supportedAlgosMhash = array(
+ 'adler32',
+ 'crc32',
+ 'crc32b',
+ 'gost',
+ 'haval128',
+ 'haval160',
+ 'haval192',
+ 'haval256',
+ 'md4',
+ 'md5',
+ 'ripemd160',
+ 'sha1',
+ 'sha256',
+ 'tiger',
+ 'tiger128',
+ 'tiger160'
+ );
+
+ /**
+ * @param string $algorithm
+ * @param string $data
+ * @param bool $binaryOutput
+ * @return unknown
+ */
+ public static function hash($algorithm, $data, $binaryOutput = false)
+ {
+ $algorithm = strtolower($algorithm);
+ if (function_exists($algorithm)) {
+ return $algorithm($data, $binaryOutput);
+ }
+ self::_detectHashSupport($algorithm);
+ $supportedMethod = '_digest' . ucfirst(self::$_type);
+ $result = self::$supportedMethod($algorithm, $data, $binaryOutput);
+ return $result;
+ }
+
+ /**
+ * @param string $algorithm
+ * @throws Zend_Crypt_Exception
+ */
+ protected static function _detectHashSupport($algorithm)
+ {
+ if (function_exists('hash')) {
+ self::$_type = self::TYPE_HASH;
+ if (in_array($algorithm, hash_algos())) {
+ return;
+ }
+ }
+ if (function_exists('mhash')) {
+ self::$_type = self::TYPE_MHASH;
+ if (in_array($algorithm, self::$_supportedAlgosMhash)) {
+ return;
+ }
+ }
+ if (function_exists('openssl_digest')) {
+ if ($algorithm == 'ripemd160') {
+ $algorithm = 'rmd160';
+ }
+ self::$_type = self::TYPE_OPENSSL;
+ if (in_array($algorithm, self::$_supportedAlgosOpenssl)) {
+ return;
+ }
+ }
+ /**
+ * @see Zend_Crypt_Exception
+ */
+ throw new Zend_Crypt_Exception('\'' . $algorithm . '\' is not supported by any available extension or native function');
+ }
+
+ /**
+ * @param string $algorithm
+ * @param string $data
+ * @param bool $binaryOutput
+ * @return string
+ */
+ protected static function _digestHash($algorithm, $data, $binaryOutput)
+ {
+ return hash($algorithm, $data, $binaryOutput);
+ }
+
+ /**
+ * @param string $algorithm
+ * @param string $data
+ * @param bool $binaryOutput
+ * @return string
+ */
+ protected static function _digestMhash($algorithm, $data, $binaryOutput)
+ {
+ $constant = constant('MHASH_' . strtoupper($algorithm));
+ $binary = mhash($constant, $data);
+ if ($binaryOutput) {
+ return $binary;
+ }
+ return bin2hex($binary);
+ }
+
+ /**
+ * @param string $algorithm
+ * @param string $data
+ * @param bool $binaryOutput
+ * @return string
+ */
+ protected static function _digestOpenssl($algorithm, $data, $binaryOutput)
+ {
+ if ($algorithm == 'ripemd160') {
+ $algorithm = 'rmd160';
+ }
+ return openssl_digest($data, $algorithm, $binaryOutput);
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/DiffieHellman.php b/library/vendor/Zend/Crypt/DiffieHellman.php
new file mode 100644
index 0000000..851f871
--- /dev/null
+++ b/library/vendor/Zend/Crypt/DiffieHellman.php
@@ -0,0 +1,378 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage DiffieHellman
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * PHP implementation of the Diffie-Hellman public key encryption algorithm.
+ * Allows two unassociated parties to establish a joint shared secret key
+ * to be used in encrypting subsequent communications.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_DiffieHellman
+{
+
+ /**
+ * Static flag to select whether to use PHP5.3's openssl extension
+ * if available.
+ *
+ * @var boolean
+ */
+ public static $useOpenssl = true;
+
+ /**
+ * Default large prime number; required by the algorithm.
+ *
+ * @var string
+ */
+ private $_prime = null;
+
+ /**
+ * The default generator number. This number must be greater than 0 but
+ * less than the prime number set.
+ *
+ * @var string
+ */
+ private $_generator = null;
+
+ /**
+ * A private number set by the local user. It's optional and will
+ * be generated if not set.
+ *
+ * @var string
+ */
+ private $_privateKey = null;
+
+ /**
+ * BigInteger support object courtesy of Zend_Crypt_Math
+ *
+ * @var Zend_Crypt_Math_BigInteger
+ */
+ private $_math = null;
+
+ /**
+ * The public key generated by this instance after calling generateKeys().
+ *
+ * @var string
+ */
+ private $_publicKey = null;
+
+ /**
+ * The shared secret key resulting from a completed Diffie Hellman
+ * exchange
+ *
+ * @var string
+ */
+ private $_secretKey = null;
+
+ /**
+ * Constants
+ */
+ const BINARY = 'binary';
+ const NUMBER = 'number';
+ const BTWOC = 'btwoc';
+
+ /**
+ * Constructor; if set construct the object using the parameter array to
+ * set values for Prime, Generator and Private.
+ * If a Private Key is not set, one will be generated at random.
+ *
+ * @param string $prime
+ * @param string $generator
+ * @param string $privateKey
+ * @param string $privateKeyType
+ */
+ public function __construct($prime, $generator, $privateKey = null, $privateKeyType = self::NUMBER)
+ {
+ $this->setPrime($prime);
+ $this->setGenerator($generator);
+ if ($privateKey !== null) {
+ $this->setPrivateKey($privateKey, $privateKeyType);
+ }
+ $this->setBigIntegerMath();
+ }
+
+ /**
+ * Generate own public key. If a private number has not already been
+ * set, one will be generated at this stage.
+ *
+ * @return Zend_Crypt_DiffieHellman
+ */
+ public function generateKeys()
+ {
+ if (function_exists('openssl_dh_compute_key') && self::$useOpenssl !== false) {
+ $details = array();
+ $details['p'] = $this->getPrime();
+ $details['g'] = $this->getGenerator();
+ if ($this->hasPrivateKey()) {
+ $details['priv_key'] = $this->getPrivateKey();
+ }
+ $opensslKeyResource = openssl_pkey_new( array('dh' => $details) );
+ $data = openssl_pkey_get_details($opensslKeyResource);
+ $this->setPrivateKey($data['dh']['priv_key'], self::BINARY);
+ $this->setPublicKey($data['dh']['pub_key'], self::BINARY);
+ } else {
+ // Private key is lazy generated in the absence of PHP 5.3's ext/openssl
+ $publicKey = $this->_math->powmod($this->getGenerator(), $this->getPrivateKey(), $this->getPrime());
+ $this->setPublicKey($publicKey);
+ }
+ return $this;
+ }
+
+ /**
+ * Setter for the value of the public number
+ *
+ * @param string $number
+ * @param string $type
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return Zend_Crypt_DiffieHellman
+ */
+ public function setPublicKey($number, $type = self::NUMBER)
+ {
+ if ($type == self::BINARY) {
+ $number = $this->_math->fromBinary($number);
+ }
+ if (!preg_match("/^\d+$/", $number)) {
+ throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number');
+ }
+ $this->_publicKey = (string) $number;
+ return $this;
+ }
+
+ /**
+ * Returns own public key for communication to the second party to this
+ * transaction.
+ *
+ * @param string $type
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return string
+ */
+ public function getPublicKey($type = self::NUMBER)
+ {
+ if ($this->_publicKey === null) {
+ throw new Zend_Crypt_DiffieHellman_Exception('A public key has not yet been generated using a prior call to generateKeys()');
+ }
+ if ($type == self::BINARY) {
+ return $this->_math->toBinary($this->_publicKey);
+ } elseif ($type == self::BTWOC) {
+ return $this->_math->btwoc($this->_math->toBinary($this->_publicKey));
+ }
+ return $this->_publicKey;
+ }
+
+ /**
+ * Compute the shared secret key based on the public key received from the
+ * the second party to this transaction. This should agree to the secret
+ * key the second party computes on our own public key.
+ * Once in agreement, the key is known to only to both parties.
+ * By default, the function expects the public key to be in binary form
+ * which is the typical format when being transmitted.
+ *
+ * If you need the binary form of the shared secret key, call
+ * getSharedSecretKey() with the optional parameter for Binary output.
+ *
+ * @param string $publicKey
+ * @param string $type
+ * @param string $output
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return mixed
+ */
+ public function computeSecretKey($publicKey, $type = self::NUMBER, $output = self::NUMBER)
+ {
+ if ($type == self::BINARY) {
+ $publicKey = $this->_math->fromBinary($publicKey);
+ }
+ if (!preg_match("/^\d+$/", $publicKey)) {
+ throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number');
+ }
+ if (function_exists('openssl_dh_compute_key') && self::$useOpenssl !== false) {
+ $this->_secretKey = openssl_dh_compute_key($publicKey, $this->getPublicKey());
+ } else {
+ $this->_secretKey = $this->_math->powmod($publicKey, $this->getPrivateKey(), $this->getPrime());
+ }
+ return $this->getSharedSecretKey($output);
+ }
+
+ /**
+ * Return the computed shared secret key from the DiffieHellman transaction
+ *
+ * @param string $type
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return string
+ */
+ public function getSharedSecretKey($type = self::NUMBER)
+ {
+ if (!isset($this->_secretKey)) {
+ throw new Zend_Crypt_DiffieHellman_Exception('A secret key has not yet been computed; call computeSecretKey()');
+ }
+ if ($type == self::BINARY) {
+ return $this->_math->toBinary($this->_secretKey);
+ } elseif ($type == self::BTWOC) {
+ return $this->_math->btwoc($this->_math->toBinary($this->_secretKey));
+ }
+ return $this->_secretKey;
+ }
+
+ /**
+ * Setter for the value of the prime number
+ *
+ * @param string $number
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return Zend_Crypt_DiffieHellman
+ */
+ public function setPrime($number)
+ {
+ if (!preg_match("/^\d+$/", $number) || $number < 11) {
+ throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number or too small: should be a large natural number prime');
+ }
+ $this->_prime = (string) $number;
+ return $this;
+ }
+
+ /**
+ * Getter for the value of the prime number
+ *
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return string
+ */
+ public function getPrime()
+ {
+ if (!isset($this->_prime)) {
+ throw new Zend_Crypt_DiffieHellman_Exception('No prime number has been set');
+ }
+ return $this->_prime;
+ }
+
+ /**
+ * Setter for the value of the generator number
+ *
+ * @param string $number
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return Zend_Crypt_DiffieHellman
+ */
+ public function setGenerator($number)
+ {
+ if (!preg_match("/^\d+$/", $number) || $number < 2) {
+ throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number greater than 1');
+ }
+ $this->_generator = (string) $number;
+ return $this;
+ }
+
+ /**
+ * Getter for the value of the generator number
+ *
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return string
+ */
+ public function getGenerator()
+ {
+ if (!isset($this->_generator)) {
+ throw new Zend_Crypt_DiffieHellman_Exception('No generator number has been set');
+ }
+ return $this->_generator;
+ }
+
+ /**
+ * Setter for the value of the private number
+ *
+ * @param string $number
+ * @param string $type
+ * @throws Zend_Crypt_DiffieHellman_Exception
+ * @return Zend_Crypt_DiffieHellman
+ */
+ public function setPrivateKey($number, $type = self::NUMBER)
+ {
+ if ($type == self::BINARY) {
+ $number = $this->_math->fromBinary($number);
+ }
+ if (!preg_match("/^\d+$/", $number)) {
+ throw new Zend_Crypt_DiffieHellman_Exception('invalid parameter; not a positive natural number');
+ }
+ $this->_privateKey = (string) $number;
+ return $this;
+ }
+
+ /**
+ * Getter for the value of the private number
+ *
+ * @param string $type
+ * @return string
+ */
+ public function getPrivateKey($type = self::NUMBER)
+ {
+ if (!$this->hasPrivateKey()) {
+ $this->setPrivateKey($this->_generatePrivateKey(), self::BINARY);
+ }
+ if ($type == self::BINARY) {
+ return $this->_math->toBinary($this->_privateKey);
+ } elseif ($type == self::BTWOC) {
+ return $this->_math->btwoc($this->_math->toBinary($this->_privateKey));
+ }
+ return $this->_privateKey;
+ }
+
+ /**
+ * Check whether a private key currently exists.
+ *
+ * @return boolean
+ */
+ public function hasPrivateKey()
+ {
+ return isset($this->_privateKey);
+ }
+
+ /**
+ * Setter to pass an extension parameter which is used to create
+ * a specific BigInteger instance for a specific extension type.
+ * Allows manual setting of the class in case of an extension
+ * problem or bug.
+ *
+ * @param string $extension
+ * @return void
+ */
+ public function setBigIntegerMath($extension = null)
+ {
+ /**
+ * @see Zend_Crypt_Math
+ */
+ $this->_math = new Zend_Crypt_Math($extension);
+ }
+
+ /**
+ * In the event a private number/key has not been set by the user,
+ * or generated by ext/openssl, a best attempt will be made to
+ * generate a random key. Having a random number generator installed
+ * on linux/bsd is highly recommended! The alternative is not recommended
+ * for production unless without any other option.
+ *
+ * @return string
+ */
+ protected function _generatePrivateKey()
+ {
+ $rand = $this->_math->rand($this->getGenerator(), $this->getPrime());
+ return $rand;
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/DiffieHellman/Exception.php b/library/vendor/Zend/Crypt/DiffieHellman/Exception.php
new file mode 100644
index 0000000..7a84526
--- /dev/null
+++ b/library/vendor/Zend/Crypt/DiffieHellman/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage DiffieHellman
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_DiffieHellman_Exception extends Zend_Crypt_Exception
+{
+}
diff --git a/library/vendor/Zend/Crypt/Exception.php b/library/vendor/Zend/Crypt/Exception.php
new file mode 100644
index 0000000..826f9f1
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Crypt/Hmac.php b/library/vendor/Zend/Crypt/Hmac.php
new file mode 100644
index 0000000..bd8814d
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Hmac.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Hmac
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt
+ */
+
+/**
+ * PHP implementation of the RFC 2104 Hash based Message Authentication Code
+ * algorithm.
+ *
+ * @todo Patch for refactoring failed tests (key block sizes >80 using internal algo)
+ * @todo Check if mhash() is a required alternative (will be PECL-only soon)
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Hmac extends Zend_Crypt
+{
+
+ /**
+ * The key to use for the hash
+ *
+ * @var string
+ */
+ protected static $_key = null;
+
+ /**
+ * pack() format to be used for current hashing method
+ *
+ * @var string
+ */
+ protected static $_packFormat = null;
+
+ /**
+ * Hashing algorithm; can be the md5/sha1 functions or any algorithm name
+ * listed in the output of PHP 5.1.2+ hash_algos().
+ *
+ * @var string
+ */
+ protected static $_hashAlgorithm = 'md5';
+
+ /**
+ * List of algorithms supported my mhash()
+ *
+ * @var array
+ */
+ protected static $_supportedMhashAlgorithms = array('adler32',' crc32', 'crc32b', 'gost',
+ 'haval128', 'haval160', 'haval192', 'haval256', 'md4', 'md5', 'ripemd160',
+ 'sha1', 'sha256', 'tiger', 'tiger128', 'tiger160');
+
+ /**
+ * Constants representing the output mode of the hash algorithm
+ */
+ const STRING = 'string';
+ const BINARY = 'binary';
+
+ /**
+ * Performs a HMAC computation given relevant details such as Key, Hashing
+ * algorithm, the data to compute MAC of, and an output format of String,
+ * Binary notation or BTWOC.
+ *
+ * @param string $key
+ * @param string $hash
+ * @param string $data
+ * @param string $output
+ * @throws Zend_Crypt_Hmac_Exception
+ * @return string
+ */
+ public static function compute($key, $hash, $data, $output = self::STRING)
+ {
+ // set the key
+ if (!isset($key) || empty($key)) {
+ throw new Zend_Crypt_Hmac_Exception('provided key is null or empty');
+ }
+ self::$_key = $key;
+
+ // set the hash
+ self::_setHashAlgorithm($hash);
+
+ // perform hashing and return
+ return self::_hash($data, $output);
+ }
+
+ /**
+ * Setter for the hash method.
+ *
+ * @param string $hash
+ * @throws Zend_Crypt_Hmac_Exception
+ * @return Zend_Crypt_Hmac
+ */
+ protected static function _setHashAlgorithm($hash)
+ {
+ if (!isset($hash) || empty($hash)) {
+ throw new Zend_Crypt_Hmac_Exception('provided hash string is null or empty');
+ }
+
+ $hash = strtolower($hash);
+ $hashSupported = false;
+
+ if (function_exists('hash_algos') && in_array($hash, hash_algos())) {
+ $hashSupported = true;
+ }
+
+ if ($hashSupported === false && function_exists('mhash') && in_array($hash, self::$_supportedAlgosMhash)) {
+ $hashSupported = true;
+ }
+
+ if ($hashSupported === false) {
+ throw new Zend_Crypt_Hmac_Exception('hash algorithm provided is not supported on this PHP installation; please enable the hash or mhash extensions');
+ }
+ self::$_hashAlgorithm = $hash;
+ }
+
+ /**
+ * Perform HMAC and return the keyed data
+ *
+ * @param string $data
+ * @param string $output
+ * @param bool $internal Option to not use hash() functions for testing
+ * @return string
+ */
+ protected static function _hash($data, $output = self::STRING, $internal = false)
+ {
+ if (function_exists('hash_hmac')) {
+ if ($output == self::BINARY) {
+ return hash_hmac(self::$_hashAlgorithm, $data, self::$_key, true);
+ }
+ return hash_hmac(self::$_hashAlgorithm, $data, self::$_key);
+ }
+
+ if (function_exists('mhash')) {
+ if ($output == self::BINARY) {
+ return mhash(self::_getMhashDefinition(self::$_hashAlgorithm), $data, self::$_key);
+ }
+ $bin = mhash(self::_getMhashDefinition(self::$_hashAlgorithm), $data, self::$_key);
+ return bin2hex($bin);
+ }
+ }
+
+ /**
+ * Since MHASH accepts an integer constant representing the hash algorithm
+ * we need to make a small detour to get the correct integer matching our
+ * algorithm's name.
+ *
+ * @param string $hashAlgorithm
+ * @return integer
+ */
+ protected static function _getMhashDefinition($hashAlgorithm)
+ {
+ for ($i = 0; $i <= mhash_count(); $i++)
+ {
+ $types[mhash_get_hash_name($i)] = $i;
+ }
+ return $types[strtoupper($hashAlgorithm)];
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/Hmac/Exception.php b/library/vendor/Zend/Crypt/Hmac/Exception.php
new file mode 100644
index 0000000..9f266e0
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Hmac/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Hmac
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Hmac_Exception extends Zend_Crypt_Exception
+{
+}
diff --git a/library/vendor/Zend/Crypt/Math.php b/library/vendor/Zend/Crypt/Math.php
new file mode 100644
index 0000000..f39ac16
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math.php
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Math_BigInteger
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Math extends Zend_Crypt_Math_BigInteger
+{
+
+ /**
+ * Generate a pseudorandom number within the given range.
+ * Will attempt to read from a systems RNG if it exists or else utilises
+ * a simple random character to maximum length process. Simplicity
+ * is a factor better left for development...
+ *
+ * @param string|int $minimum
+ * @param string|int $maximum
+ * @return string
+ */
+ public function rand($minimum, $maximum)
+ {
+ if (file_exists('/dev/urandom')) {
+ $frandom = fopen('/dev/urandom', 'r');
+ if ($frandom !== false) {
+ return fread($frandom, strlen($maximum) - 1);
+ }
+ }
+ if (strlen($maximum) < 4) {
+ return mt_rand($minimum, $maximum - 1);
+ }
+ $rand = '';
+ $i2 = strlen($maximum) - 1;
+ for ($i = 1; $i < $i2; $i++) {
+ $rand .= mt_rand(0, 9);
+ }
+ $rand .= mt_rand(0, 9);
+ return $rand;
+ }
+
+ /**
+ * Return a random strings of $length bytes
+ *
+ * @param integer $length
+ * @param boolean $strong
+ * @return string
+ */
+ public static function randBytes($length, $strong = false)
+ {
+ $length = (int) $length;
+ if ($length <= 0) {
+ return false;
+ }
+ if (function_exists('random_bytes')) { // available in PHP 7
+ return random_bytes($length);
+ }
+ if (function_exists('mcrypt_create_iv')) {
+ $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
+ if ($bytes !== false && strlen($bytes) === $length) {
+ return $bytes;
+ }
+ }
+ if (file_exists('/dev/urandom') && is_readable('/dev/urandom')) {
+ $frandom = fopen('/dev/urandom', 'r');
+ if ($frandom !== false) {
+ return fread($frandom, $length);
+ }
+ }
+ if (true === $strong) {
+ throw new Zend_Crypt_Exception(
+ 'This PHP environment doesn\'t support secure random number generation. ' .
+ 'Please consider installing the OpenSSL and/or Mcrypt extensions'
+ );
+ }
+ $rand = '';
+ for ($i = 0; $i < $length; $i++) {
+ $rand .= chr(mt_rand(0, 255));
+ }
+ return $rand;
+ }
+
+ /**
+ * Return a random integer between $min and $max
+ *
+ * @param integer $min
+ * @param integer $max
+ * @param boolean $strong
+ * @return integer
+ */
+ public static function randInteger($min, $max, $strong = false)
+ {
+ if ($min > $max) {
+ throw new Zend_Crypt_Exception(
+ 'The min parameter must be lower than max parameter'
+ );
+ }
+ $range = $max - $min;
+ if ($range == 0) {
+ return $max;
+ } elseif ($range > PHP_INT_MAX || is_float($range)) {
+ throw new Zend_Crypt_Exception(
+ 'The supplied range is too great to generate'
+ );
+ }
+ if (function_exists('random_int')) { // available in PHP 7
+ return random_int($min, $max);
+ }
+ // calculate number of bits required to store range on this machine
+ $r = $range;
+ $bits = 0;
+ while ($r) {
+ $bits++;
+ $r >>= 1;
+ }
+ $bits = (int) max($bits, 1);
+ $bytes = (int) max(ceil($bits / 8), 1);
+ $filter = (int) ((1 << $bits) - 1);
+ do {
+ $rnd = hexdec(bin2hex(self::randBytes($bytes, $strong)));
+ $rnd &= $filter;
+ } while ($rnd > $range);
+ return ($min + $rnd);
+ }
+
+ /**
+ * Get the big endian two's complement of a given big integer in
+ * binary notation
+ *
+ * @param string $long
+ * @return string
+ */
+ public function btwoc($long)
+ {
+ if (ord($long[0]) > 127) {
+ return "\x00" . $long;
+ }
+ return $long;
+ }
+
+ /**
+ * Translate a binary form into a big integer string
+ *
+ * @param string $binary
+ * @return string
+ */
+ public function fromBinary($binary)
+ {
+ return $this->_math->binaryToInteger($binary);
+ }
+
+ /**
+ * Translate a big integer string into a binary form
+ *
+ * @param string $integer
+ * @return string
+ */
+ public function toBinary($integer)
+ {
+ return $this->_math->integerToBinary($integer);
+ }
+}
diff --git a/library/vendor/Zend/Crypt/Math/BigInteger.php b/library/vendor/Zend/Crypt/Math/BigInteger.php
new file mode 100644
index 0000000..0b98cec
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math/BigInteger.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Support for arbitrary precision mathematics in PHP.
+ *
+ * Zend_Crypt_Math_BigInteger is a wrapper across three PHP extensions: bcmath, gmp
+ * and big_int. Since each offer similar functionality, but availability of
+ * each differs across installations of PHP, this wrapper attempts to select
+ * the fastest option available and encapsulate a subset of its functionality
+ * which all extensions share in common.
+ *
+ * This class requires one of the three extensions to be available. BCMATH
+ * while the slowest, is available by default under Windows, and under Unix
+ * if PHP is compiled with the flag "--enable-bcmath". GMP requires the gmp
+ * library from http://www.swox.com/gmp/ and PHP compiled with the "--with-gmp"
+ * flag. BIG_INT support is available from a big_int PHP library available from
+ * only from PECL (a Windows port is not available).
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Math_BigInteger
+{
+
+ /**
+ * Holds an instance of one of the three arbitrary precision wrappers.
+ *
+ * @var Zend_Crypt_Math_BigInteger_Interface
+ */
+ protected $_math = null;
+
+ /**
+ * Constructor; a Factory which detects a suitable PHP extension for
+ * arbitrary precision math and instantiates the suitable wrapper
+ * object.
+ *
+ * @param string $extension
+ * @throws Zend_Crypt_Math_BigInteger_Exception
+ */
+ public function __construct($extension = null)
+ {
+ if ($extension !== null && !in_array($extension, array('bcmath', 'gmp', 'bigint'))) {
+ throw new Zend_Crypt_Math_BigInteger_Exception('Invalid extension type; please use one of bcmath, gmp or bigint');
+ }
+ $this->_loadAdapter($extension);
+ }
+
+ /**
+ * Redirect all public method calls to the wrapped extension object.
+ *
+ * @param string $methodName
+ * @param array $args
+ * @return mixed
+ * @throws Zend_Crypt_Math_BigInteger_Exception
+ */
+ public function __call($methodName, $args)
+ {
+ if(!method_exists($this->_math, $methodName)) {
+ throw new Zend_Crypt_Math_BigInteger_Exception('invalid method call: ' . get_class($this->_math) . '::' . $methodName . '() does not exist');
+ }
+ return call_user_func_array(array($this->_math, $methodName), $args);
+ }
+
+ /**
+ * @param string $extension
+ * @throws Zend_Crypt_Math_BigInteger_Exception
+ */
+ protected function _loadAdapter($extension = null)
+ {
+ if ($extension === null) {
+ if (extension_loaded('gmp')) {
+ $extension = 'gmp';
+ //} elseif (extension_loaded('big_int')) {
+ // $extension = 'big_int';
+ } else {
+ $extension = 'bcmath';
+ }
+ }
+ if($extension == 'gmp' && extension_loaded('gmp')) {
+ $this->_math = new Zend_Crypt_Math_BigInteger_Gmp();
+ //} elseif($extension == 'bigint' && extension_loaded('big_int')) {
+ // require_once 'Zend/Crypt_Math/BigInteger/Bigint.php';
+ // $this->_math = new Zend_Crypt_Math_BigInteger_Bigint();
+ } elseif ($extension == 'bcmath' && extension_loaded('bcmath')) {
+ $this->_math = new Zend_Crypt_Math_BigInteger_Bcmath();
+ } else {
+ throw new Zend_Crypt_Math_BigInteger_Exception($extension . ' big integer precision math support not detected');
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/Math/BigInteger/Bcmath.php b/library/vendor/Zend/Crypt/Math/BigInteger/Bcmath.php
new file mode 100644
index 0000000..2fb4a67
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math/BigInteger/Bcmath.php
@@ -0,0 +1,226 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Math_BigInteger_Interface
+ */
+
+/**
+ * Support for arbitrary precision mathematics in PHP.
+ *
+ * Zend_Crypt_Math_BigInteger_Bcmath is a wrapper across the PHP BCMath
+ * extension.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Math_BigInteger_Bcmath implements Zend_Crypt_Math_BigInteger_Interface
+{
+
+ /**
+ * Initialise a big integer into an extension specific type. This is not
+ * applicable to BCMath.
+ *
+ * @param string $operand
+ * @param int $base
+ * @return string
+ */
+ public function init($operand, $base = 10)
+ {
+ return $operand;
+ }
+
+ /**
+ * Adds two arbitrary precision numbers
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @return string
+ */
+ public function add($left_operand, $right_operand)
+ {
+ return bcadd($left_operand, $right_operand);
+ }
+
+ /**
+ * Subtract one arbitrary precision number from another
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @return string
+ */
+ public function subtract($left_operand, $right_operand)
+ {
+ return bcsub($left_operand, $right_operand);
+ }
+
+ /**
+ * Compare two big integers and returns result as an integer where 0 means
+ * both are identical, 1 that left_operand is larger, or -1 that
+ * right_operand is larger.
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @return int
+ */
+ public function compare($left_operand, $right_operand)
+ {
+ return bccomp($left_operand, $right_operand);
+ }
+
+ /**
+ * Divide two big integers and return result or NULL if the denominator
+ * is zero.
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @return string|null
+ */
+ public function divide($left_operand, $right_operand)
+ {
+ return bcdiv($left_operand, $right_operand);
+ }
+
+ /**
+ * Get modulus of an arbitrary precision number
+ *
+ * @param string $left_operand
+ * @param string $modulus
+ * @return string
+ */
+ public function modulus($left_operand, $modulus)
+ {
+ return bcmod($left_operand, $modulus);
+ }
+
+ /**
+ * Multiply two arbitrary precision numbers
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @return string
+ */
+ public function multiply($left_operand, $right_operand)
+ {
+ return bcmul($left_operand, $right_operand);
+ }
+
+ /**
+ * Raise an arbitrary precision number to another
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @return string
+ */
+ public function pow($left_operand, $right_operand)
+ {
+ return bcpow($left_operand, $right_operand);
+ }
+
+ /**
+ * Raise an arbitrary precision number to another, reduced by a specified
+ * modulus
+ *
+ * @param string $left_operand
+ * @param string $right_operand
+ * @param string $modulus
+ * @return string
+ */
+ public function powmod($left_operand, $right_operand, $modulus)
+ {
+ return bcpowmod($left_operand, $right_operand, $modulus);
+ }
+
+ /**
+ * Get the square root of an arbitrary precision number
+ *
+ * @param string $operand
+ * @return string
+ */
+ public function sqrt($operand)
+ {
+ return bcsqrt($operand);
+ }
+
+ /**
+ * @param string $operand
+ * @return string
+ */
+ public function binaryToInteger($operand)
+ {
+ $result = '0';
+ while (strlen($operand)) {
+ $ord = ord(substr($operand, 0, 1));
+ $result = bcadd(bcmul($result, 256), $ord);
+ $operand = substr($operand, 1);
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $operand
+ * @return string
+ */
+ public function integerToBinary($operand)
+ {
+ $cmp = bccomp($operand, 0);
+ $return = '';
+ if ($cmp == 0) {
+ return "\0";
+ }
+ while (bccomp($operand, 0) > 0) {
+ $return = chr(bcmod($operand, 256)) . $return;
+ $operand = bcdiv($operand, 256);
+ }
+ if (ord($return[0]) > 127) {
+ $return = "\0" . $return;
+ }
+ return $return;
+ }
+
+ /**public function integerToBinary($operand)
+ {
+ $return = '';
+ while(bccomp($operand, '0')) {
+ $return .= chr(bcmod($operand, '256'));
+ $operand = bcdiv($operand, '256');
+ }
+ return $return;
+ }**/ // Prior version for referenced offset
+
+ /**
+ * @param string $operand
+ * @return string
+ */
+ public function hexToDecimal($operand)
+ {
+ $return = '0';
+ while(strlen($hex)) {
+ $hex = hexdec(substr($operand, 0, 4));
+ $dec = bcadd(bcmul($return, 65536), $hex);
+ $operand = substr($operand, 4);
+ }
+ return $return;
+ }
+}
diff --git a/library/vendor/Zend/Crypt/Math/BigInteger/Exception.php b/library/vendor/Zend/Crypt/Math/BigInteger/Exception.php
new file mode 100644
index 0000000..c1c865d
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math/BigInteger/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Math_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Math_BigInteger_Exception extends Zend_Crypt_Math_Exception
+{
+}
diff --git a/library/vendor/Zend/Crypt/Math/BigInteger/Gmp.php b/library/vendor/Zend/Crypt/Math/BigInteger/Gmp.php
new file mode 100644
index 0000000..1912be7
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math/BigInteger/Gmp.php
@@ -0,0 +1,219 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Math_BigInteger_Interface
+ */
+
+/**
+ * Support for arbitrary precision mathematics in PHP.
+ *
+ * Zend_Crypt_Math_BigInteger_Gmp is a wrapper across the PHP BCMath
+ * extension.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Math_BigInteger_Gmp implements Zend_Crypt_Math_BigInteger_Interface
+{
+
+ /**
+ * Initialise a big integer into an extension specific type.
+ * @param string $operand
+ * @param int $base
+ * @return string
+ */
+ public function init($operand, $base = 10)
+ {
+ return $operand;
+ }
+
+ /**
+ * Adds two arbitrary precision numbers
+ *
+ * @param resource $left_operand
+ * @param resource $right_operand
+ * @return string
+ */
+ public function add($left_operand, $right_operand)
+ {
+ $result = gmp_add($left_operand, $right_operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Subtract numbers
+ *
+ * @param resource $left_operand
+ * @param resource $right_operand
+ * @return string
+ */
+ public function subtract($left_operand, $right_operand)
+ {
+ $result = gmp_sub($left_operand, $right_operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Compare two big integers and returns result as an integer where 0 means
+ * both are identical, 1 that left_operand is larger, or -1 that
+ * right_operand is larger.
+ *
+ * @param resource $left_operand
+ * @param resource $right_operand
+ * @return int
+ */
+ public function compare($left_operand, $right_operand)
+ {
+ $result = gmp_cmp($left_operand, $right_operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Divide two big integers and return result or NULL if the denominator
+ * is zero.
+ *
+ * @param resource $left_operand
+ * @param resource $right_operand
+ * @return string|null
+ */
+ public function divide($left_operand, $right_operand)
+ {
+ $result = gmp_div($left_operand, $right_operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Modulo operation
+ *
+ * @param resource $left_operand
+ * @param resource $modulus
+ * @internal param string $right_operand
+ * @return string
+ */
+ public function modulus($left_operand, $modulus)
+ {
+ $result = gmp_mod($left_operand, $modulus);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Multiply numbers
+ *
+ * @param resource $left_operand
+ * @param resource $right_operand
+ * @return string
+ */
+ public function multiply($left_operand, $right_operand)
+ {
+ $result = gmp_mul($left_operand, $right_operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Raise number into power
+ *
+ * @param resource $left_operand
+ * @param int $right_operand
+ * @return string
+ */
+ public function pow($left_operand, $right_operand)
+ {
+ $result = gmp_pow($left_operand, $right_operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Raise number into power with modulo
+ *
+ * @param resource $left_operand
+ * @param resource $right_operand
+ * @param resource $modulus
+ * @return string
+ */
+ public function powmod($left_operand, $right_operand, $modulus)
+ {
+ $result = gmp_powm($left_operand, $right_operand, $modulus);
+ return gmp_strval($result);
+ }
+
+ /**
+ * Calculate square root
+ *
+ * @param $operand
+ * @return string
+ */
+ public function sqrt($operand)
+ {
+ $result = gmp_sqrt($operand);
+ return gmp_strval($result);
+ }
+
+ /**
+ * @param string $operand
+ * @return string
+ */
+ public function binaryToInteger($operand)
+ {
+ $result = '0';
+ while (strlen($operand)) {
+ $ord = ord(substr($operand, 0, 1));
+ $result = gmp_add(gmp_mul($result, 256), $ord);
+ $operand = substr($operand, 1);
+ }
+ return gmp_strval($result);
+ }
+
+ /**
+ * @param resource $operand GMP number resource
+ * @return string
+ */
+ public function integerToBinary($operand)
+ {
+ $bigInt = gmp_strval($operand, 16);
+ if (strlen($bigInt) % 2 != 0) {
+ $bigInt = '0' . $bigInt;
+ } else if ($bigInt[0] > '7') {
+ $bigInt = '00' . $bigInt;
+ }
+ $return = pack("H*", $bigInt);
+ return $return;
+ }
+
+ /**
+ * @param string $operand
+ * @return string
+ */
+ public function hexToDecimal($operand)
+ {
+ $return = '0';
+ while(strlen($hex)) {
+ $hex = hexdec(substr($operand, 0, 4));
+ $dec = gmp_add(gmp_mul($return, 65536), $hex);
+ $operand = substr($operand, 4);
+ }
+ return $return;
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/Math/BigInteger/Interface.php b/library/vendor/Zend/Crypt/Math/BigInteger/Interface.php
new file mode 100644
index 0000000..9fa9281
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math/BigInteger/Interface.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Support for arbitrary precision mathematics in PHP.
+ *
+ * Interface for a wrapper across any PHP extension supporting arbitrary
+ * precision maths.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Crypt_Math_BigInteger_Interface
+{
+
+ public function init($operand, $base = 10);
+ public function add($left_operand, $right_operand);
+ public function subtract($left_operand, $right_operand);
+ public function compare($left_operand, $right_operand);
+ public function divide($left_operand, $right_operand);
+ public function modulus($left_operand, $modulus);
+ public function multiply($left_operand, $right_operand);
+ public function pow($left_operand, $right_operand);
+ public function powmod($left_operand, $right_operand, $modulus);
+ public function sqrt($operand);
+ public function binaryToInteger($operand);
+ public function integerToBinary($operand);
+ public function hexToDecimal($operand);
+
+}
diff --git a/library/vendor/Zend/Crypt/Math/Exception.php b/library/vendor/Zend/Crypt/Math/Exception.php
new file mode 100644
index 0000000..066da03
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Math/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Math_Exception extends Zend_Crypt_Exception
+{
+}
diff --git a/library/vendor/Zend/Crypt/Rsa.php b/library/vendor/Zend/Crypt/Rsa.php
new file mode 100644
index 0000000..2523bbf
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Rsa.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Rsa
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Rsa_Key_Private
+ */
+
+/**
+ * @see Zend_Crypt_Rsa_Key_Public
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Rsa
+{
+
+ const BINARY = 'binary';
+ const BASE64 = 'base64';
+
+ protected $_privateKey;
+
+ protected $_publicKey;
+
+ /**
+ * @var string
+ */
+ protected $_pemString;
+
+ protected $_pemPath;
+
+ protected $_certificateString;
+
+ protected $_certificatePath;
+
+ protected $_hashAlgorithm;
+
+ protected $_passPhrase;
+
+ /**
+ * Class constructor
+ *
+ * @param array $options
+ * @throws Zend_Crypt_Rsa_Exception
+ */
+ public function __construct(array $options = null)
+ {
+ if (!extension_loaded('openssl')) {
+ throw new Zend_Crypt_Rsa_Exception('Zend_Crypt_Rsa requires openssl extension to be loaded.');
+ }
+
+ // Set _hashAlgorithm property when we are sure, that openssl extension is loaded
+ // and OPENSSL_ALGO_SHA1 constant is available
+ $this->_hashAlgorithm = OPENSSL_ALGO_SHA1;
+
+ if (isset($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ public function setOptions(array $options)
+ {
+ if (isset($options['passPhrase'])) {
+ $this->_passPhrase = $options['passPhrase'];
+ }
+ foreach ($options as $option=>$value) {
+ switch ($option) {
+ case 'pemString':
+ $this->setPemString($value);
+ break;
+ case 'pemPath':
+ $this->setPemPath($value);
+ break;
+ case 'certificateString':
+ $this->setCertificateString($value);
+ break;
+ case 'certificatePath':
+ $this->setCertificatePath($value);
+ break;
+ case 'hashAlgorithm':
+ $this->setHashAlgorithm($value);
+ break;
+ }
+ }
+ }
+
+ public function getPrivateKey()
+ {
+ return $this->_privateKey;
+ }
+
+ public function getPublicKey()
+ {
+ return $this->_publicKey;
+ }
+
+ /**
+ * @param string $data
+ * @param Zend_Crypt_Rsa_Key_Private $privateKey
+ * @param string $format
+ * @return string
+ */
+ public function sign($data, Zend_Crypt_Rsa_Key_Private $privateKey = null, $format = null)
+ {
+ $signature = '';
+ if (isset($privateKey)) {
+ $opensslKeyResource = $privateKey->getOpensslKeyResource();
+ } else {
+ $opensslKeyResource = $this->_privateKey->getOpensslKeyResource();
+ }
+ $result = openssl_sign(
+ $data, $signature,
+ $opensslKeyResource,
+ $this->getHashAlgorithm()
+ );
+ if ($format == self::BASE64) {
+ return base64_encode($signature);
+ }
+ return $signature;
+ }
+
+ /**
+ * @param string $data
+ * @param string $signature
+ * @param string $format
+ * @return string
+ */
+ public function verifySignature($data, $signature, $format = null)
+ {
+ if ($format == self::BASE64) {
+ $signature = base64_decode($signature);
+ }
+ $result = openssl_verify($data, $signature,
+ $this->getPublicKey()->getOpensslKeyResource(),
+ $this->getHashAlgorithm());
+ return $result;
+ }
+
+ /**
+ * @param string $data
+ * @param Zend_Crypt_Rsa_Key $key
+ * @param string $format
+ * @return string
+ */
+ public function encrypt($data, Zend_Crypt_Rsa_Key $key, $format = null)
+ {
+ $encrypted = '';
+ $function = 'openssl_public_encrypt';
+ if ($key instanceof Zend_Crypt_Rsa_Key_Private) {
+ $function = 'openssl_private_encrypt';
+ }
+ $function($data, $encrypted, $key->getOpensslKeyResource());
+ if ($format == self::BASE64) {
+ return base64_encode($encrypted);
+ }
+ return $encrypted;
+ }
+
+ /**
+ * @param string $data
+ * @param Zend_Crypt_Rsa_Key $key
+ * @param string $format
+ * @return string
+ */
+ public function decrypt($data, Zend_Crypt_Rsa_Key $key, $format = null)
+ {
+ $decrypted = '';
+ if ($format == self::BASE64) {
+ $data = base64_decode($data);
+ }
+ $function = 'openssl_private_decrypt';
+ if ($key instanceof Zend_Crypt_Rsa_Key_Public) {
+ $function = 'openssl_public_decrypt';
+ }
+ $function($data, $decrypted, $key->getOpensslKeyResource());
+ return $decrypted;
+ }
+
+ /**
+ * @param array $configargs
+ *
+ * @throws Zend_Crypt_Rsa_Exception
+ *
+ * @return ArrayObject
+ */
+ public function generateKeys(array $configargs = null)
+ {
+ $config = null;
+ $passPhrase = null;
+ if ($configargs !== null) {
+ if (isset($configargs['passPhrase'])) {
+ $passPhrase = $configargs['passPhrase'];
+ unset($configargs['passPhrase']);
+ }
+ $config = $this->_parseConfigArgs($configargs);
+ }
+ $privateKey = null;
+ $publicKey = null;
+ $resource = openssl_pkey_new($config);
+ if (!$resource) {
+ throw new Zend_Crypt_Rsa_Exception('Failed to generate a new private key');
+ }
+ // above fails on PHP 5.3
+ openssl_pkey_export($resource, $private, $passPhrase);
+ $privateKey = new Zend_Crypt_Rsa_Key_Private($private, $passPhrase);
+ $details = openssl_pkey_get_details($resource);
+ $publicKey = new Zend_Crypt_Rsa_Key_Public($details['key']);
+ $return = new ArrayObject(array(
+ 'privateKey'=>$privateKey,
+ 'publicKey'=>$publicKey
+ ), ArrayObject::ARRAY_AS_PROPS);
+ return $return;
+ }
+
+ /**
+ * @param string $value
+ */
+ public function setPemString($value)
+ {
+ $this->_pemString = $value;
+ try {
+ $this->_privateKey = new Zend_Crypt_Rsa_Key_Private($this->_pemString, $this->_passPhrase);
+ $this->_publicKey = $this->_privateKey->getPublicKey();
+ } catch (Zend_Crypt_Exception $e) {
+ $this->_privateKey = null;
+ $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_pemString);
+ }
+ }
+
+ public function setPemPath($value)
+ {
+ $this->_pemPath = $value;
+ $this->setPemString(file_get_contents($this->_pemPath));
+ }
+
+ public function setCertificateString($value)
+ {
+ $this->_certificateString = $value;
+ $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_certificateString, $this->_passPhrase);
+ }
+
+ public function setCertificatePath($value)
+ {
+ $this->_certificatePath = $value;
+ $this->setCertificateString(file_get_contents($this->_certificatePath));
+ }
+
+ public function setHashAlgorithm($name)
+ {
+ switch (strtolower($name)) {
+ case 'md2':
+ $this->_hashAlgorithm = OPENSSL_ALGO_MD2;
+ break;
+ case 'md4':
+ $this->_hashAlgorithm = OPENSSL_ALGO_MD4;
+ break;
+ case 'md5':
+ $this->_hashAlgorithm = OPENSSL_ALGO_MD5;
+ break;
+ case 'sha1':
+ $this->_hashAlgorithm = OPENSSL_ALGO_SHA1;
+ break;
+ case 'dss1':
+ $this->_hashAlgorithm = OPENSSL_ALGO_DSS1;
+ break;
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getPemString()
+ {
+ return $this->_pemString;
+ }
+
+ public function getPemPath()
+ {
+ return $this->_pemPath;
+ }
+
+ public function getCertificateString()
+ {
+ return $this->_certificateString;
+ }
+
+ public function getCertificatePath()
+ {
+ return $this->_certificatePath;
+ }
+
+ public function getHashAlgorithm()
+ {
+ return $this->_hashAlgorithm;
+ }
+
+ protected function _parseConfigArgs(array $config = null)
+ {
+ $configs = array();
+ if (isset($config['private_key_bits'])) {
+ $configs['private_key_bits'] = $config['private_key_bits'];
+ }
+ if (isset($config['privateKeyBits'])) {
+ $configs['private_key_bits'] = $config['privateKeyBits'];
+ }
+ if (!empty($configs)) {
+ return $configs;
+ }
+ return null;
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/Rsa/Exception.php b/library/vendor/Zend/Crypt/Rsa/Exception.php
new file mode 100644
index 0000000..3b3944f
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Rsa/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Math
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Exception.php 20096 2010-01-06 02:05:09Z bkarwin $
+ */
+
+/**
+ * @see Zend_Crypt_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Rsa_Exception extends Zend_Crypt_Exception
+{
+}
diff --git a/library/vendor/Zend/Crypt/Rsa/Key.php b/library/vendor/Zend/Crypt/Rsa/Key.php
new file mode 100644
index 0000000..cfd9d15
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Rsa/Key.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Rsa
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Rsa_Key implements Countable
+{
+ /**
+ * @var string
+ */
+ protected $_pemString = null;
+
+ /**
+ * Bits, key string and type of key
+ *
+ * @var array
+ */
+ protected $_details = array();
+
+ /**
+ * Key Resource
+ *
+ * @var resource
+ */
+ protected $_opensslKeyResource = null;
+
+ /**
+ * Retrieves key resource
+ *
+ * @return resource
+ */
+ public function getOpensslKeyResource()
+ {
+ return $this->_opensslKeyResource;
+ }
+
+ /**
+ * @return string
+ * @throws Zend_Crypt_Exception
+ */
+ public function toString()
+ {
+ if (!empty($this->_pemString)) {
+ return $this->_pemString;
+ } elseif (!empty($this->_certificateString)) {
+ return $this->_certificateString;
+ }
+ /**
+ * @see Zend_Crypt_Exception
+ */
+ throw new Zend_Crypt_Exception('No public key string representation is available');
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ public function count()
+ {
+ return $this->_details['bits'];
+ }
+
+ public function getType()
+ {
+ return $this->_details['type'];
+ }
+}
diff --git a/library/vendor/Zend/Crypt/Rsa/Key/Private.php b/library/vendor/Zend/Crypt/Rsa/Key/Private.php
new file mode 100644
index 0000000..b736b58
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Rsa/Key/Private.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Rsa
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Rsa_Key
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Rsa_Key_Private extends Zend_Crypt_Rsa_Key
+{
+
+ protected $_publicKey = null;
+
+ public function __construct($pemString, $passPhrase = null)
+ {
+ $this->_pemString = $pemString;
+ $this->_parse($passPhrase);
+ }
+
+ /**
+ * @param string $passPhrase
+ * @throws Zend_Crypt_Exception
+ */
+ protected function _parse($passPhrase)
+ {
+ $result = openssl_get_privatekey($this->_pemString, $passPhrase);
+ if (!$result) {
+ /**
+ * @see Zend_Crypt_Exception
+ */
+ throw new Zend_Crypt_Exception('Unable to load private key');
+ }
+ $this->_opensslKeyResource = $result;
+ $this->_details = openssl_pkey_get_details($this->_opensslKeyResource);
+ }
+
+ public function getPublicKey()
+ {
+ if ($this->_publicKey === null) {
+ /**
+ * @see Zend_Crypt_Rsa_Key_Public
+ */
+ $this->_publicKey = new Zend_Crypt_Rsa_Key_Public($this->_details['key']);
+ }
+ return $this->_publicKey;
+ }
+
+}
diff --git a/library/vendor/Zend/Crypt/Rsa/Key/Public.php b/library/vendor/Zend/Crypt/Rsa/Key/Public.php
new file mode 100644
index 0000000..85b2eb6
--- /dev/null
+++ b/library/vendor/Zend/Crypt/Rsa/Key/Public.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Crypt
+ * @subpackage Rsa
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Crypt_Rsa_Key
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Crypt
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Crypt_Rsa_Key_Public extends Zend_Crypt_Rsa_Key
+{
+
+ protected $_certificateString = null;
+
+ public function __construct($string)
+ {
+ $this->_parse($string);
+ }
+
+ /**
+ * @param string $string
+ * @throws Zend_Crypt_Exception
+ */
+ protected function _parse($string)
+ {
+ if (preg_match("/^-----BEGIN CERTIFICATE-----/", $string)) {
+ $this->_certificateString = $string;
+ } else {
+ $this->_pemString = $string;
+ }
+ $result = openssl_get_publickey($string);
+ if (!$result) {
+ /**
+ * @see Zend_Crypt_Exception
+ */
+ throw new Zend_Crypt_Exception('Unable to load public key');
+ }
+ //openssl_pkey_export($result, $public);
+ //$this->_pemString = $public;
+ $this->_opensslKeyResource = $result;
+ $this->_details = openssl_pkey_get_details($this->_opensslKeyResource);
+ }
+
+ public function getCertificate()
+ {
+ return $this->_certificateString;
+ }
+
+}
diff --git a/library/vendor/Zend/Date.php b/library/vendor/Zend/Date.php
new file mode 100644
index 0000000..6223f36
--- /dev/null
+++ b/library/vendor/Zend/Date.php
@@ -0,0 +1,4872 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Date
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Include needed Date classes
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Date
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Date extends Zend_Date_DateObject
+{
+ private $_locale = null;
+
+ // Fractional second variables
+ private $_fractional = 0;
+ private $_precision = 3;
+
+ private static $_options = array(
+ 'format_type' => 'iso', // format for date strings 'iso' or 'php'
+ 'fix_dst' => true, // fix dst on summer/winter time change
+ 'extend_month' => false, // false - addMonth like SQL, true like excel
+ 'cache' => null, // cache to set
+ 'timesync' => null // timesync server to set
+ );
+
+ // Class wide Date Constants
+ const DAY = 'dd';
+ const DAY_SHORT = 'd';
+ const DAY_SUFFIX = 'SS';
+ const DAY_OF_YEAR = 'D';
+ const WEEKDAY = 'EEEE';
+ const WEEKDAY_SHORT = 'EEE';
+ const WEEKDAY_NARROW = 'E';
+ const WEEKDAY_NAME = 'EE';
+ const WEEKDAY_8601 = 'eee';
+ const WEEKDAY_DIGIT = 'e';
+ const WEEK = 'ww';
+ const MONTH = 'MM';
+ const MONTH_SHORT = 'M';
+ const MONTH_DAYS = 'ddd';
+ const MONTH_NAME = 'MMMM';
+ const MONTH_NAME_SHORT = 'MMM';
+ const MONTH_NAME_NARROW = 'MMMMM';
+ const YEAR = 'y';
+ const YEAR_SHORT = 'yy';
+ const YEAR_8601 = 'Y';
+ const YEAR_SHORT_8601 = 'YY';
+ const LEAPYEAR = 'l';
+ const MERIDIEM = 'a';
+ const SWATCH = 'B';
+ const HOUR = 'HH';
+ const HOUR_SHORT = 'H';
+ const HOUR_AM = 'hh';
+ const HOUR_SHORT_AM = 'h';
+ const MINUTE = 'mm';
+ const MINUTE_SHORT = 'm';
+ const SECOND = 'ss';
+ const SECOND_SHORT = 's';
+ const MILLISECOND = 'S';
+ const TIMEZONE_NAME = 'zzzz';
+ const DAYLIGHT = 'I';
+ const GMT_DIFF = 'Z';
+ const GMT_DIFF_SEP = 'ZZZZ';
+ const TIMEZONE = 'z';
+ const TIMEZONE_SECS = 'X';
+ const ISO_8601 = 'c';
+ const RFC_2822 = 'r';
+ const TIMESTAMP = 'U';
+ const ERA = 'G';
+ const ERA_NAME = 'GGGG';
+ const ERA_NARROW = 'GGGGG';
+ const DATES = 'F';
+ const DATE_FULL = 'FFFFF';
+ const DATE_LONG = 'FFFF';
+ const DATE_MEDIUM = 'FFF';
+ const DATE_SHORT = 'FF';
+ const TIMES = 'WW';
+ const TIME_FULL = 'TTTTT';
+ const TIME_LONG = 'TTTT';
+ const TIME_MEDIUM = 'TTT';
+ const TIME_SHORT = 'TT';
+ const DATETIME = 'K';
+ const DATETIME_FULL = 'KKKKK';
+ const DATETIME_LONG = 'KKKK';
+ const DATETIME_MEDIUM = 'KKK';
+ const DATETIME_SHORT = 'KK';
+ const ATOM = 'OOO';
+ const COOKIE = 'CCC';
+ const RFC_822 = 'R';
+ const RFC_850 = 'RR';
+ const RFC_1036 = 'RRR';
+ const RFC_1123 = 'RRRR';
+ const RFC_3339 = 'RRRRR';
+ const RSS = 'SSS';
+ const W3C = 'WWW';
+
+ /**
+ * Generates the standard date object, could be a unix timestamp, localized date,
+ * string, integer, array and so on. Also parts of dates or time are supported
+ * Always set the default timezone: http://php.net/date_default_timezone_set
+ * For example, in your bootstrap: date_default_timezone_set('America/Los_Angeles');
+ * For detailed instructions please look in the docu.
+ *
+ * @param string|integer|Zend_Date|array $date OPTIONAL Date value or value of date part to set
+ * ,depending on $part. If null the actual time is set
+ * @param string $part OPTIONAL Defines the input format of $date
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ * @throws Zend_Date_Exception
+ */
+ public function __construct($date = null, $part = null, $locale = null)
+ {
+ if (is_object($date) and !($date instanceof Zend_TimeSync_Protocol) and
+ !($date instanceof Zend_Date)) {
+ if ($locale instanceof Zend_Locale) {
+ $locale = $date;
+ $date = null;
+ $part = null;
+ } else {
+ $date = (string) $date;
+ }
+ }
+
+ if (($date !== null) and !is_array($date) and !($date instanceof Zend_TimeSync_Protocol) and
+ !($date instanceof Zend_Date) and !defined($date) and Zend_Locale::isLocale($date, true, false)) {
+ $locale = $date;
+ $date = null;
+ $part = null;
+ } else if (($part !== null) and !defined($part) and Zend_Locale::isLocale($part, true, false)) {
+ $locale = $part;
+ $part = null;
+ }
+
+ $this->setLocale($locale);
+ if (is_string($date) && ($part === null) && (strlen($date) <= 5)) {
+ $part = $date;
+ $date = null;
+ }
+
+ if ($date === null) {
+ if ($part === null) {
+ $date = time();
+ } else if ($part !== self::TIMESTAMP) {
+ $date = self::now($locale);
+ $date = $date->get($part);
+ }
+ }
+
+ if ($date instanceof Zend_TimeSync_Protocol) {
+ $date = $date->getInfo();
+ $date = $this->_getTime($date['offset']);
+ $part = null;
+ } else if (parent::$_defaultOffset != 0) {
+ $date = $this->_getTime(parent::$_defaultOffset);
+ }
+
+ // set the timezone and offset for $this
+ $zone = @date_default_timezone_get();
+ $this->setTimezone($zone);
+
+ // try to get timezone from date-string
+ if (!is_int($date)) {
+ $zone = $this->getTimezoneFromString($date);
+ $this->setTimezone($zone);
+ }
+
+ // set datepart
+ if (($part !== null && $part !== self::TIMESTAMP) or (!is_numeric($date))) {
+ // switch off dst handling for value setting
+ $this->setUnixTimestamp($this->getGmtOffset());
+ $this->set($date, $part, $this->_locale);
+
+ // DST fix
+ if (is_array($date) === true) {
+ if (!isset($date['hour'])) {
+ $date['hour'] = 0;
+ }
+
+ $hour = $this->toString('H', 'iso', true);
+ $hour = $date['hour'] - $hour;
+ switch ($hour) {
+ case 1 :
+ case -23 :
+ $this->addTimestamp(3600);
+ break;
+ case -1 :
+ case 23 :
+ $this->subTimestamp(3600);
+ break;
+ case 2 :
+ case -22 :
+ $this->addTimestamp(7200);
+ break;
+ case -2 :
+ case 22 :
+ $this->subTimestamp(7200);
+ break;
+ }
+ }
+ } else {
+ $this->setUnixTimestamp($date);
+ }
+ }
+
+ /**
+ * Sets class wide options, if no option was given, the actual set options will be returned
+ *
+ * @param array $options Options to set
+ * @throws Zend_Date_Exception
+ * @return Options array if no option was given
+ */
+ public static function setOptions(array $options = array())
+ {
+ if (empty($options)) {
+ return self::$_options;
+ }
+
+ foreach ($options as $name => $value) {
+ $name = strtolower($name);
+
+ if (array_key_exists($name, self::$_options)) {
+ switch($name) {
+ case 'format_type' :
+ if ((strtolower($value) != 'php') && (strtolower($value) != 'iso')) {
+ throw new Zend_Date_Exception("Unknown format type ($value) for dates, only 'iso' and 'php' supported", 0, null, $value);
+ }
+ break;
+ case 'fix_dst' :
+ if (!is_bool($value)) {
+ throw new Zend_Date_Exception("'fix_dst' has to be boolean", 0, null, $value);
+ }
+ break;
+ case 'extend_month' :
+ if (!is_bool($value)) {
+ throw new Zend_Date_Exception("'extend_month' has to be boolean", 0, null, $value);
+ }
+ break;
+ case 'cache' :
+ if ($value === null) {
+ parent::$_cache = null;
+ } else {
+ if (!$value instanceof Zend_Cache_Core) {
+ throw new Zend_Date_Exception("Instance of Zend_Cache expected");
+ }
+
+ parent::$_cache = $value;
+ parent::$_cacheTags = Zend_Date_DateObject::_getTagSupportForCache();
+ Zend_Locale_Data::setCache($value);
+ }
+ break;
+ case 'timesync' :
+ if ($value === null) {
+ parent::$_defaultOffset = 0;
+ } else {
+ if (!$value instanceof Zend_TimeSync_Protocol) {
+ throw new Zend_Date_Exception("Instance of Zend_TimeSync expected");
+ }
+
+ $date = $value->getInfo();
+ parent::$_defaultOffset = $date['offset'];
+ }
+ break;
+ }
+ self::$_options[$name] = $value;
+ }
+ else {
+ throw new Zend_Date_Exception("Unknown option: $name = $value");
+ }
+ }
+ }
+
+ /**
+ * Returns this object's internal UNIX timestamp (equivalent to Zend_Date::TIMESTAMP).
+ * If the timestamp is too large for integers, then the return value will be a string.
+ * This function does not return the timestamp as an object.
+ * Use clone() or copyPart() instead.
+ *
+ * @return integer|string UNIX timestamp
+ */
+ public function getTimestamp()
+ {
+ return $this->getUnixTimestamp();
+ }
+
+ /**
+ * Returns the calculated timestamp
+ * HINT: timestamps are always GMT
+ *
+ * @param string $calc Type of calculation to make
+ * @param string|integer|array|Zend_Date $stamp Timestamp to calculate, when null the actual timestamp is calculated
+ * @return Zend_Date|integer
+ * @throws Zend_Date_Exception
+ */
+ private function _timestamp($calc, $stamp)
+ {
+ if ($stamp instanceof Zend_Date) {
+ // extract timestamp from object
+ $stamp = $stamp->getTimestamp();
+ }
+
+ if (is_array($stamp)) {
+ if (isset($stamp['timestamp']) === true) {
+ $stamp = $stamp['timestamp'];
+ } else {
+ throw new Zend_Date_Exception('no timestamp given in array');
+ }
+ }
+
+ if ($calc === 'set') {
+ $return = $this->setUnixTimestamp($stamp);
+ } else {
+ $return = $this->_calcdetail($calc, $stamp, self::TIMESTAMP, null);
+ }
+ if ($calc != 'cmp') {
+ return $this;
+ }
+ return $return;
+ }
+
+ /**
+ * Sets a new timestamp
+ *
+ * @param integer|string|array|Zend_Date $timestamp Timestamp to set
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setTimestamp($timestamp)
+ {
+ return $this->_timestamp('set', $timestamp);
+ }
+
+ /**
+ * Adds a timestamp
+ *
+ * @param integer|string|array|Zend_Date $timestamp Timestamp to add
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addTimestamp($timestamp)
+ {
+ return $this->_timestamp('add', $timestamp);
+ }
+
+ /**
+ * Subtracts a timestamp
+ *
+ * @param integer|string|array|Zend_Date $timestamp Timestamp to sub
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subTimestamp($timestamp)
+ {
+ return $this->_timestamp('sub', $timestamp);
+ }
+
+ /**
+ * Compares two timestamps, returning the difference as integer
+ *
+ * @param integer|string|array|Zend_Date $timestamp Timestamp to compare
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareTimestamp($timestamp)
+ {
+ return $this->_timestamp('cmp', $timestamp);
+ }
+
+ /**
+ * Returns a string representation of the object
+ * Supported format tokens are:
+ * G - era, y - year, Y - ISO year, M - month, w - week of year, D - day of year, d - day of month
+ * E - day of week, e - number of weekday (1-7), h - hour 1-12, H - hour 0-23, m - minute, s - second
+ * A - milliseconds of day, z - timezone, Z - timezone offset, S - fractional second, a - period of day
+ *
+ * Additionally format tokens but non ISO conform are:
+ * SS - day suffix, eee - php number of weekday(0-6), ddd - number of days per month
+ * l - Leap year, B - swatch internet time, I - daylight saving time, X - timezone offset in seconds
+ * r - RFC2822 format, U - unix timestamp
+ *
+ * Not supported ISO tokens are
+ * u - extended year, Q - quarter, q - quarter, L - stand alone month, W - week of month
+ * F - day of week of month, g - modified julian, c - stand alone weekday, k - hour 0-11, K - hour 1-24
+ * v - wall zone
+ *
+ * @param string $format OPTIONAL Rule for formatting output. If null the default date format is used
+ * @param string $type OPTIONAL Type for the format string which overrides the standard setting
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return string
+ */
+ public function toString($format = null, $type = null, $locale = null)
+ {
+ if (is_object($format)) {
+ if ($format instanceof Zend_Locale) {
+ $locale = $format;
+ $format = null;
+ } else {
+ $format = (string) $format;
+ }
+ }
+
+ if (is_object($type)) {
+ if ($type instanceof Zend_Locale) {
+ $locale = $type;
+ $type = null;
+ } else {
+ $type = (string) $type;
+ }
+ }
+
+ if (($format !== null) && !defined($format)
+ && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT')
+ && Zend_Locale::isLocale($format, null, false)) {
+ $locale = $format;
+ $format = null;
+ }
+
+ if (($type !== null) and ($type != 'php') and ($type != 'iso') and
+ Zend_Locale::isLocale($type, null, false)) {
+ $locale = $type;
+ $type = null;
+ }
+
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ if ($format === null) {
+ $format = Zend_Locale_Format::getDateFormat($locale) . ' ' . Zend_Locale_Format::getTimeFormat($locale);
+ } else if (((self::$_options['format_type'] == 'php') && ($type === null)) or ($type == 'php')) {
+ $format = Zend_Locale_Format::convertPhpToIsoFormat($format);
+ }
+
+ return $this->date($this->_toToken($format, $locale), $this->getUnixTimestamp(), false);
+ }
+
+ /**
+ * Returns a string representation of the date which is equal with the timestamp
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString(null, $this->_locale);
+ }
+
+ /**
+ * Returns a integer representation of the object
+ * But returns false when the given part is no value f.e. Month-Name
+ *
+ * @param string|integer|Zend_Date $part OPTIONAL Defines the date or datepart to return as integer
+ * @return integer|false
+ */
+ public function toValue($part = null)
+ {
+ $result = $this->get($part);
+ if (is_numeric($result)) {
+ return intval("$result");
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns an array representation of the object
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return array('day' => $this->toString(self::DAY_SHORT, 'iso'),
+ 'month' => $this->toString(self::MONTH_SHORT, 'iso'),
+ 'year' => $this->toString(self::YEAR, 'iso'),
+ 'hour' => $this->toString(self::HOUR_SHORT, 'iso'),
+ 'minute' => $this->toString(self::MINUTE_SHORT, 'iso'),
+ 'second' => $this->toString(self::SECOND_SHORT, 'iso'),
+ 'timezone' => $this->toString(self::TIMEZONE, 'iso'),
+ 'timestamp' => $this->toString(self::TIMESTAMP, 'iso'),
+ 'weekday' => $this->toString(self::WEEKDAY_8601, 'iso'),
+ 'dayofyear' => $this->toString(self::DAY_OF_YEAR, 'iso'),
+ 'week' => $this->toString(self::WEEK, 'iso'),
+ 'gmtsecs' => $this->toString(self::TIMEZONE_SECS, 'iso'));
+ }
+
+ /**
+ * Returns a representation of a date or datepart
+ * This could be for example a localized monthname, the time without date,
+ * the era or only the fractional seconds. There are about 50 different supported date parts.
+ * For a complete list of supported datepart values look into the docu
+ *
+ * @param string $part OPTIONAL Part of the date to return, if null the timestamp is returned
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return string date or datepart
+ */
+ public function get($part = null, $locale = null)
+ {
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ if (($part !== null) && !defined($part)
+ && ($part != 'ee') && ($part != 'ss') && ($part != 'GG') && ($part != 'MM') && ($part != 'EE') && ($part != 'TT')
+ && Zend_Locale::isLocale($part, null, false)) {
+ $locale = $part;
+ $part = null;
+ }
+
+ if ($part === null) {
+ $part = self::TIMESTAMP;
+ } else if (self::$_options['format_type'] == 'php') {
+ $part = Zend_Locale_Format::convertPhpToIsoFormat($part);
+ }
+
+ return $this->date($this->_toToken($part, $locale), $this->getUnixTimestamp(), false);
+ }
+
+ /**
+ * Internal method to apply tokens
+ *
+ * @param string $part
+ * @param string $locale
+ * @return string
+ */
+ private function _toToken($part, $locale) {
+ // get format tokens
+ $comment = false;
+ $format = '';
+ $orig = '';
+ for ($i = 0; isset($part[$i]); ++$i) {
+ if ($part[$i] == "'") {
+ $comment = $comment ? false : true;
+ if (isset($part[$i+1]) && ($part[$i+1] == "'")) {
+ $comment = $comment ? false : true;
+ $format .= "\\'";
+ ++$i;
+ }
+
+ $orig = '';
+ continue;
+ }
+
+ if ($comment) {
+ $format .= '\\' . $part[$i];
+ $orig = '';
+ } else {
+ $orig .= $part[$i];
+ if (!isset($part[$i+1]) || (isset($orig[0]) && ($orig[0] != $part[$i+1]))) {
+ $format .= $this->_parseIsoToDate($orig, $locale);
+ $orig = '';
+ }
+ }
+ }
+
+ return $format;
+ }
+
+ /**
+ * Internal parsing method
+ *
+ * @param string $token
+ * @param string $locale
+ * @return string
+ */
+ private function _parseIsoToDate($token, $locale) {
+ switch($token) {
+ case self::DAY :
+ return 'd';
+ break;
+
+ case self::WEEKDAY_SHORT :
+ $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false));
+ $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday));
+ return $this->_toComment(iconv_substr($day, 0, 3, 'UTF-8'));
+ break;
+
+ case self::DAY_SHORT :
+ return 'j';
+ break;
+
+ case self::WEEKDAY :
+ $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false));
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday)));
+ break;
+
+ case self::WEEKDAY_8601 :
+ return 'N';
+ break;
+
+ case 'ee' :
+ return $this->_toComment(str_pad($this->date('N', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT));
+ break;
+
+ case self::DAY_SUFFIX :
+ return 'S';
+ break;
+
+ case self::WEEKDAY_DIGIT :
+ return 'w';
+ break;
+
+ case self::DAY_OF_YEAR :
+ return 'z';
+ break;
+
+ case 'DDD' :
+ return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 3, '0', STR_PAD_LEFT));
+ break;
+
+ case 'DD' :
+ return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT));
+ break;
+
+ case self::WEEKDAY_NARROW :
+ case 'EEEEE' :
+ $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false));
+ $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday));
+ return $this->_toComment(iconv_substr($day, 0, 1, 'UTF-8'));
+ break;
+
+ case self::WEEKDAY_NAME :
+ $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false));
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday)));
+ break;
+
+ case 'w' :
+ $week = $this->date('W', $this->getUnixTimestamp(), false);
+ return $this->_toComment(($week[0] == '0') ? $week[1] : $week);
+ break;
+
+ case self::WEEK :
+ return 'W';
+ break;
+
+ case self::MONTH_NAME :
+ $month = $this->date('n', $this->getUnixTimestamp(), false);
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'wide', $month)));
+ break;
+
+ case self::MONTH :
+ return 'm';
+ break;
+
+ case self::MONTH_NAME_SHORT :
+ $month = $this->date('n', $this->getUnixTimestamp(), false);
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month)));
+ break;
+
+ case self::MONTH_SHORT :
+ return 'n';
+ break;
+
+ case self::MONTH_DAYS :
+ return 't';
+ break;
+
+ case self::MONTH_NAME_NARROW :
+ $month = $this->date('n', $this->getUnixTimestamp(), false);
+ $mon = Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month));
+ return $this->_toComment(iconv_substr($mon, 0, 1, 'UTF-8'));
+ break;
+
+ case self::LEAPYEAR :
+ return 'L';
+ break;
+
+ case self::YEAR_8601 :
+ return 'o';
+ break;
+
+ case self::YEAR :
+ return 'Y';
+ break;
+
+ case self::YEAR_SHORT :
+ return 'y';
+ break;
+
+ case self::YEAR_SHORT_8601 :
+ return $this->_toComment(substr($this->date('o', $this->getUnixTimestamp(), false), -2, 2));
+ break;
+
+ case self::MERIDIEM :
+ $am = $this->date('a', $this->getUnixTimestamp(), false);
+ if ($am == 'am') {
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'am'));
+ }
+
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'pm'));
+ break;
+
+ case self::SWATCH :
+ return 'B';
+ break;
+
+ case self::HOUR_SHORT_AM :
+ return 'g';
+ break;
+
+ case self::HOUR_SHORT :
+ return 'G';
+ break;
+
+ case self::HOUR_AM :
+ return 'h';
+ break;
+
+ case self::HOUR :
+ return 'H';
+ break;
+
+ case self::MINUTE :
+ return $this->_toComment(str_pad($this->date('i', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT));
+ break;
+
+ case self::SECOND :
+ return $this->_toComment(str_pad($this->date('s', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT));
+ break;
+
+ case self::MINUTE_SHORT :
+ return 'i';
+ break;
+
+ case self::SECOND_SHORT :
+ return 's';
+ break;
+
+ case self::MILLISECOND :
+ return $this->_toComment($this->getMilliSecond());
+ break;
+
+ case self::TIMEZONE_NAME :
+ case 'vvvv' :
+ return 'e';
+ break;
+
+ case self::DAYLIGHT :
+ return 'I';
+ break;
+
+ case self::GMT_DIFF :
+ case 'ZZ' :
+ case 'ZZZ' :
+ return 'O';
+ break;
+
+ case self::GMT_DIFF_SEP :
+ return 'P';
+ break;
+
+ case self::TIMEZONE :
+ case 'v' :
+ case 'zz' :
+ case 'zzz' :
+ return 'T';
+ break;
+
+ case self::TIMEZONE_SECS :
+ return 'Z';
+ break;
+
+ case self::ISO_8601 :
+ return 'c';
+ break;
+
+ case self::RFC_2822 :
+ return 'r';
+ break;
+
+ case self::TIMESTAMP :
+ return 'U';
+ break;
+
+ case self::ERA :
+ case 'GG' :
+ case 'GGG' :
+ $year = $this->date('Y', $this->getUnixTimestamp(), false);
+ if ($year < 0) {
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0')));
+ }
+
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1')));
+ break;
+
+ case self::ERA_NARROW :
+ $year = $this->date('Y', $this->getUnixTimestamp(), false);
+ if ($year < 0) {
+ return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0')), 0, 1, 'UTF-8')) . '.';
+ }
+
+ return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1')), 0, 1, 'UTF-8')) . '.';
+ break;
+
+ case self::ERA_NAME :
+ $year = $this->date('Y', $this->getUnixTimestamp(), false);
+ if ($year < 0) {
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '0')));
+ }
+
+ return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '1')));
+ break;
+
+ case self::DATES :
+ return $this->_toToken(Zend_Locale_Format::getDateFormat($locale), $locale);
+ break;
+
+ case self::DATE_FULL :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')), $locale);
+ break;
+
+ case self::DATE_LONG :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')), $locale);
+ break;
+
+ case self::DATE_MEDIUM :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')), $locale);
+ break;
+
+ case self::DATE_SHORT :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')), $locale);
+ break;
+
+ case self::TIMES :
+ return $this->_toToken(Zend_Locale_Format::getTimeFormat($locale), $locale);
+ break;
+
+ case self::TIME_FULL :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'full'), $locale);
+ break;
+
+ case self::TIME_LONG :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'long'), $locale);
+ break;
+
+ case self::TIME_MEDIUM :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'medium'), $locale);
+ break;
+
+ case self::TIME_SHORT :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'short'), $locale);
+ break;
+
+ case self::DATETIME :
+ return $this->_toToken(Zend_Locale_Format::getDateTimeFormat($locale), $locale);
+ break;
+
+ case self::DATETIME_FULL :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')), $locale);
+ break;
+
+ case self::DATETIME_LONG :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')), $locale);
+ break;
+
+ case self::DATETIME_MEDIUM :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')), $locale);
+ break;
+
+ case self::DATETIME_SHORT :
+ return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')), $locale);
+ break;
+
+ case self::ATOM :
+ return 'Y\-m\-d\TH\:i\:sP';
+ break;
+
+ case self::COOKIE :
+ return 'l\, d\-M\-y H\:i\:s e';
+ break;
+
+ case self::RFC_822 :
+ return 'D\, d M y H\:i\:s O';
+ break;
+
+ case self::RFC_850 :
+ return 'l\, d\-M\-y H\:i\:s e';
+ break;
+
+ case self::RFC_1036 :
+ return 'D\, d M y H\:i\:s O';
+ break;
+
+ case self::RFC_1123 :
+ return 'D\, d M Y H\:i\:s O';
+ break;
+
+ case self::RFC_3339 :
+ return 'Y\-m\-d\TH\:i\:sP';
+ break;
+
+ case self::RSS :
+ return 'D\, d M Y H\:i\:s O';
+ break;
+
+ case self::W3C :
+ return 'Y\-m\-d\TH\:i\:sP';
+ break;
+ }
+
+ if ($token == '') {
+ return '';
+ }
+
+ switch ($token[0]) {
+ case 'y' :
+ if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) {
+ return 'Y';
+ }
+
+ $length = iconv_strlen($token, 'UTF-8');
+ return $this->_toComment(str_pad($this->date('Y', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT));
+ break;
+
+ case 'Y' :
+ if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) {
+ return 'o';
+ }
+
+ $length = iconv_strlen($token, 'UTF-8');
+ return $this->_toComment(str_pad($this->date('o', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT));
+ break;
+
+ case 'A' :
+ $length = iconv_strlen($token, 'UTF-8');
+ $result = substr($this->getMilliSecond(), 0, 3);
+ $result += $this->date('s', $this->getUnixTimestamp(), false) * 1000;
+ $result += $this->date('i', $this->getUnixTimestamp(), false) * 60000;
+ $result += $this->date('H', $this->getUnixTimestamp(), false) * 3600000;
+
+ return $this->_toComment(str_pad($result, $length, '0', STR_PAD_LEFT));
+ break;
+ }
+
+ return $this->_toComment($token);
+ }
+
+ /**
+ * Private function to make a comment of a token
+ *
+ * @param string $token
+ * @return string
+ */
+ private function _toComment($token)
+ {
+ $token = str_split($token);
+ $result = '';
+ foreach ($token as $tok) {
+ $result .= '\\' . $tok;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return digit from standard names (english)
+ * Faster implementation than locale aware searching
+ *
+ * @param string $name
+ * @return integer Number of this month
+ * @throws Zend_Date_Exception
+ */
+ private function _getDigitFromName($name)
+ {
+ switch($name) {
+ case "Jan":
+ return 1;
+
+ case "Feb":
+ return 2;
+
+ case "Mar":
+ return 3;
+
+ case "Apr":
+ return 4;
+
+ case "May":
+ return 5;
+
+ case "Jun":
+ return 6;
+
+ case "Jul":
+ return 7;
+
+ case "Aug":
+ return 8;
+
+ case "Sep":
+ return 9;
+
+ case "Oct":
+ return 10;
+
+ case "Nov":
+ return 11;
+
+ case "Dec":
+ return 12;
+
+ default:
+ throw new Zend_Date_Exception('Month ($name) is not a known month');
+ }
+ }
+
+ /**
+ * Counts the exact year number
+ * < 70 - 2000 added, >70 < 100 - 1900, others just returned
+ *
+ * @param integer $value year number
+ * @return integer Number of year
+ */
+ public static function getFullYear($value)
+ {
+ if ($value >= 0) {
+ if ($value < 70) {
+ $value += 2000;
+ } else if ($value < 100) {
+ $value += 1900;
+ }
+ }
+ return $value;
+ }
+
+ /**
+ * Sets the given date as new date or a given datepart as new datepart returning the new datepart
+ * This could be for example a localized dayname, the date without time,
+ * the month or only the seconds. There are about 50 different supported date parts.
+ * For a complete list of supported datepart values look into the docu
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to set
+ * @param string $part OPTIONAL Part of the date to set, if null the timestamp is set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function set($date, $part = null, $locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $part = Zend_Locale_Format::convertPhpToIsoFormat($part);
+ }
+
+ $zone = $this->getTimezoneFromString($date);
+ $this->setTimezone($zone);
+
+ $this->_calculate('set', $date, $part, $locale);
+ return $this;
+ }
+
+ /**
+ * Adds a date or datepart to the existing date, by extracting $part from $date,
+ * and modifying this object by adding that part. The $part is then extracted from
+ * this object and returned as an integer or numeric string (for large values, or $part's
+ * corresponding to pre-defined formatted date strings).
+ * This could be for example a ISO 8601 date, the hour the monthname or only the minute.
+ * There are about 50 different supported date parts.
+ * For a complete list of supported datepart values look into the docu.
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to add
+ * @param string $part OPTIONAL Part of the date to add, if null the timestamp is added
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function add($date, $part = self::TIMESTAMP, $locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $part = Zend_Locale_Format::convertPhpToIsoFormat($part);
+ }
+
+ $this->_calculate('add', $date, $part, $locale);
+ return $this;
+ }
+
+ /**
+ * Subtracts a date from another date.
+ * This could be for example a RFC2822 date, the time,
+ * the year or only the timestamp. There are about 50 different supported date parts.
+ * For a complete list of supported datepart values look into the docu
+ * Be aware: Adding -2 Months is not equal to Subtracting 2 Months !!!
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to subtract
+ * @param string $part OPTIONAL Part of the date to sub, if null the timestamp is subtracted
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function sub($date, $part = self::TIMESTAMP, $locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $part = Zend_Locale_Format::convertPhpToIsoFormat($part);
+ }
+
+ $this->_calculate('sub', $date, $part, $locale);
+ return $this;
+ }
+
+ /**
+ * Compares a date or datepart with the existing one.
+ * Returns -1 if earlier, 0 if equal and 1 if later.
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to compare with the date object
+ * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is subtracted
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compare($date, $part = self::TIMESTAMP, $locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $part = Zend_Locale_Format::convertPhpToIsoFormat($part);
+ }
+
+ $compare = $this->_calculate('cmp', $date, $part, $locale);
+
+ if ($compare > 0) {
+ return 1;
+ } else if ($compare < 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns a new instance of Zend_Date with the selected part copied.
+ * To make an exact copy, use PHP's clone keyword.
+ * For a complete list of supported date part values look into the docu.
+ * If a date part is copied, all other date parts are set to standard values.
+ * For example: If only YEAR is copied, the returned date object is equal to
+ * 01-01-YEAR 00:00:00 (01-01-1970 00:00:00 is equal to timestamp 0)
+ * If only HOUR is copied, the returned date object is equal to
+ * 01-01-1970 HOUR:00:00 (so $this contains a timestamp equal to a timestamp of 0 plus HOUR).
+ *
+ * @param string $part Part of the date to compare, if null the timestamp is subtracted
+ * @param string|Zend_Locale $locale OPTIONAL New object's locale. No adjustments to timezone are made.
+ * @return Zend_Date New clone with requested part
+ */
+ public function copyPart($part, $locale = null)
+ {
+ $clone = clone $this; // copy all instance variables
+ $clone->setUnixTimestamp(0); // except the timestamp
+ if ($locale != null) {
+ $clone->setLocale($locale); // set an other locale if selected
+ }
+ $clone->set($this, $part);
+ return $clone;
+ }
+
+ /**
+ * Internal function, returns the offset of a given timezone
+ *
+ * @param string $zone
+ * @return integer
+ */
+ public function getTimezoneFromString($zone)
+ {
+ if (is_array($zone)) {
+ return $this->getTimezone();
+ }
+
+ if ($zone instanceof Zend_Date) {
+ return $zone->getTimezone();
+ }
+
+ $match = array();
+ preg_match('/\dZ$/', $zone, $match);
+ if (!empty($match)) {
+ return "Etc/UTC";
+ }
+
+ preg_match('/([+-]\d{2}):{0,1}\d{2}/', $zone, $match);
+ if (!empty($match) and ($match[count($match) - 1] <= 14) and ($match[count($match) - 1] >= -12)) {
+ $zone = "Etc/GMT";
+ $zone .= ($match[count($match) - 1] < 0) ? "+" : "-";
+ $zone .= (int) abs($match[count($match) - 1]);
+ return $zone;
+ }
+
+ preg_match('/([[:alpha:]\/_]{3,30})(?!.*([[:alpha:]\/]{3,30}))/', $zone, $match);
+ try {
+ if (!empty($match) and (!is_int($match[count($match) - 1]))) {
+ $oldzone = $this->getTimezone();
+ $this->setTimezone($match[count($match) - 1]);
+ $result = $this->getTimezone();
+ $this->setTimezone($oldzone);
+ if ($result !== $oldzone) {
+ return $match[count($match) - 1];
+ }
+ }
+ } catch (Exception $e) {
+ // fall through
+ }
+
+ return $this->getTimezone();
+ }
+
+ /**
+ * Calculates the date or object
+ *
+ * @param string $calc Calculation to make
+ * @param string|integer $date Date for calculation
+ * @param string|integer $comp Second date for calculation
+ * @param boolean|integer $dst Use dst correction if option is set
+ * @return integer|string|Zend_Date new timestamp or Zend_Date depending on calculation
+ */
+ private function _assign($calc, $date, $comp = 0, $dst = false)
+ {
+ switch ($calc) {
+ case 'set' :
+ if (!empty($comp)) {
+ $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $comp));
+ }
+ $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date));
+ $value = $this->getUnixTimestamp();
+ break;
+ case 'add' :
+ $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date));
+ $value = $this->getUnixTimestamp();
+ break;
+ case 'sub' :
+ $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $date));
+ $value = $this->getUnixTimestamp();
+ break;
+ default :
+ // cmp - compare
+ return call_user_func(Zend_Locale_Math::$comp, $comp, $date);
+ break;
+ }
+
+ // dst-correction if 'fix_dst' = true and dst !== false but only for non UTC and non GMT
+ if ((self::$_options['fix_dst'] === true) and ($dst !== false) and ($this->_dst === true)) {
+ $hour = $this->toString(self::HOUR, 'iso');
+ if ($hour != $dst) {
+ if (($dst == ($hour + 1)) or ($dst == ($hour - 23))) {
+ $value += 3600;
+ } else if (($dst == ($hour - 1)) or ($dst == ($hour + 23))) {
+ $value -= 3600;
+ }
+ $this->setUnixTimestamp($value);
+ }
+ }
+ return $this->getUnixTimestamp();
+ }
+
+
+ /**
+ * Calculates the date or object
+ *
+ * @param string $calc Calculation to make, one of: 'add'|'sub'|'cmp'|'copy'|'set'
+ * @param string|integer|array|Zend_Date $date Date or datepart to calculate with
+ * @param string $part Part of the date to calculate, if null the timestamp is used
+ * @param string|Zend_Locale $locale Locale for parsing input
+ * @return integer|string|Zend_Date new timestamp
+ * @throws Zend_Date_Exception
+ */
+ private function _calculate($calc, $date, $part, $locale)
+ {
+ if ($date === null) {
+ throw new Zend_Date_Exception('parameter $date must be set, null is not allowed');
+ }
+
+ if (($part !== null) && (strlen($part) !== 2) && (Zend_Locale::isLocale($part, null, false))) {
+ $locale = $part;
+ $part = null;
+ }
+
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ $locale = (string) $locale;
+
+ // Create date parts
+ $year = $this->toString(self::YEAR, 'iso');
+ $month = $this->toString(self::MONTH_SHORT, 'iso');
+ $day = $this->toString(self::DAY_SHORT, 'iso');
+ $hour = $this->toString(self::HOUR_SHORT, 'iso');
+ $minute = $this->toString(self::MINUTE_SHORT, 'iso');
+ $second = $this->toString(self::SECOND_SHORT, 'iso');
+ // If object extract value
+ if ($date instanceof Zend_Date) {
+ $date = $date->toString($part, 'iso', $locale);
+ }
+
+ if (is_array($date) === true) {
+ if (empty($part) === false) {
+ switch($part) {
+ // Fall through
+ case self::DAY:
+ case self::DAY_SHORT:
+ if (isset($date['day']) === true) {
+ $date = $date['day'];
+ }
+ break;
+ // Fall through
+ case self::WEEKDAY_SHORT:
+ case self::WEEKDAY:
+ case self::WEEKDAY_8601:
+ case self::WEEKDAY_DIGIT:
+ case self::WEEKDAY_NARROW:
+ case self::WEEKDAY_NAME:
+ if (isset($date['weekday']) === true) {
+ $date = $date['weekday'];
+ $part = self::WEEKDAY_DIGIT;
+ }
+ break;
+ case self::DAY_OF_YEAR:
+ if (isset($date['day_of_year']) === true) {
+ $date = $date['day_of_year'];
+ }
+ break;
+ // Fall through
+ case self::MONTH:
+ case self::MONTH_SHORT:
+ case self::MONTH_NAME:
+ case self::MONTH_NAME_SHORT:
+ case self::MONTH_NAME_NARROW:
+ if (isset($date['month']) === true) {
+ $date = $date['month'];
+ }
+ break;
+ // Fall through
+ case self::YEAR:
+ case self::YEAR_SHORT:
+ case self::YEAR_8601:
+ case self::YEAR_SHORT_8601:
+ if (isset($date['year']) === true) {
+ $date = $date['year'];
+ }
+ break;
+ // Fall through
+ case self::HOUR:
+ case self::HOUR_AM:
+ case self::HOUR_SHORT:
+ case self::HOUR_SHORT_AM:
+ if (isset($date['hour']) === true) {
+ $date = $date['hour'];
+ }
+ break;
+ // Fall through
+ case self::MINUTE:
+ case self::MINUTE_SHORT:
+ if (isset($date['minute']) === true) {
+ $date = $date['minute'];
+ }
+ break;
+ // Fall through
+ case self::SECOND:
+ case self::SECOND_SHORT:
+ if (isset($date['second']) === true) {
+ $date = $date['second'];
+ }
+ break;
+ // Fall through
+ case self::TIMEZONE:
+ case self::TIMEZONE_NAME:
+ if (isset($date['timezone']) === true) {
+ $date = $date['timezone'];
+ }
+ break;
+ case self::TIMESTAMP:
+ if (isset($date['timestamp']) === true) {
+ $date = $date['timestamp'];
+ }
+ break;
+ case self::WEEK:
+ if (isset($date['week']) === true) {
+ $date = $date['week'];
+ }
+ break;
+ case self::TIMEZONE_SECS:
+ if (isset($date['gmtsecs']) === true) {
+ $date = $date['gmtsecs'];
+ }
+ break;
+ default:
+ throw new Zend_Date_Exception("datepart for part ($part) not found in array");
+ break;
+ }
+ } else {
+ $hours = 0;
+ if (isset($date['hour']) === true) {
+ $hours = $date['hour'];
+ }
+ $minutes = 0;
+ if (isset($date['minute']) === true) {
+ $minutes = $date['minute'];
+ }
+ $seconds = 0;
+ if (isset($date['second']) === true) {
+ $seconds = $date['second'];
+ }
+ $months = 0;
+ if (isset($date['month']) === true) {
+ $months = $date['month'];
+ }
+ $days = 0;
+ if (isset($date['day']) === true) {
+ $days = $date['day'];
+ }
+ $years = 0;
+ if (isset($date['year']) === true) {
+ $years = $date['year'];
+ }
+ return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, $months, $days, $years, true),
+ $this->mktime($hour, $minute, $second, $month, $day, $year, true), $hour);
+ }
+ }
+
+ // $date as object, part of foreign date as own date
+ switch($part) {
+
+ // day formats
+ case self::DAY:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date);
+ break;
+
+ case self::WEEKDAY_SHORT:
+ $daylist = Zend_Locale_Data::getList($locale, 'day');
+ $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale);
+ $cnt = 0;
+
+ foreach ($daylist as $key => $value) {
+ if (strtoupper(iconv_substr($value, 0, 3, 'UTF-8')) == strtoupper($date)) {
+ $found = $cnt;
+ break;
+ }
+ ++$cnt;
+ }
+
+ // Weekday found
+ if ($cnt < 7) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour);
+ }
+
+ // Weekday not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date);
+ break;
+
+ case self::DAY_SHORT:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date);
+ break;
+
+ case self::WEEKDAY:
+ $daylist = Zend_Locale_Data::getList($locale, 'day');
+ $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale);
+ $cnt = 0;
+
+ foreach ($daylist as $key => $value) {
+ if (strtoupper($value) == strtoupper($date)) {
+ $found = $cnt;
+ break;
+ }
+ ++$cnt;
+ }
+
+ // Weekday found
+ if ($cnt < 7) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour);
+ }
+
+ // Weekday not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date);
+ break;
+
+ case self::WEEKDAY_8601:
+ $weekday = (int) $this->toString(self::WEEKDAY_8601, 'iso', $locale);
+ if ((intval($date) > 0) and (intval($date) < 8)) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour);
+ }
+
+ // Weekday not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date);
+ break;
+
+ case self::DAY_SUFFIX:
+ throw new Zend_Date_Exception('day suffix not supported', 0, null, $date);
+ break;
+
+ case self::WEEKDAY_DIGIT:
+ $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale);
+ if (is_numeric($date) and (intval($date) >= 0) and (intval($date) < 7)) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $date, 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour);
+ }
+
+ // Weekday not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date);
+ break;
+
+ case self::DAY_OF_YEAR:
+ if (is_numeric($date)) {
+ if (($calc == 'add') || ($calc == 'sub')) {
+ $year = 1970;
+ ++$date;
+ ++$day;
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, $date, $year, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), $hour);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date);
+ break;
+
+ case self::WEEKDAY_NARROW:
+ $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated'));
+ $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale);
+ $cnt = 0;
+ foreach ($daylist as $key => $value) {
+ if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($date)) {
+ $found = $cnt;
+ break;
+ }
+ ++$cnt;
+ }
+
+ // Weekday found
+ if ($cnt < 7) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour);
+ }
+
+ // Weekday not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date);
+ break;
+
+ case self::WEEKDAY_NAME:
+ $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated'));
+ $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale);
+ $cnt = 0;
+ foreach ($daylist as $key => $value) {
+ if (strtoupper($value) == strtoupper($date)) {
+ $found = $cnt;
+ break;
+ }
+ ++$cnt;
+ }
+
+ // Weekday found
+ if ($cnt < 7) {
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true),
+ $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour);
+ }
+
+ // Weekday not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date);
+ break;
+
+ // week formats
+ case self::WEEK:
+ if (is_numeric($date)) {
+ $week = (int) $this->toString(self::WEEK, 'iso', $locale);
+ return $this->_assign($calc, parent::mktime(0, 0, 0, 1, 1 + ($date * 7), 1970, true),
+ parent::mktime(0, 0, 0, 1, 1 + ($week * 7), 1970, true), $hour);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, week expected", 0, null, $date);
+ break;
+
+ // month formats
+ case self::MONTH_NAME:
+ $monthlist = Zend_Locale_Data::getList($locale, 'month');
+ $cnt = 0;
+ foreach ($monthlist as $key => $value) {
+ if (strtoupper($value) == strtoupper($date)) {
+ $found = $key;
+ break;
+ }
+ ++$cnt;
+ }
+ $date = array_search($date, $monthlist);
+
+ // Monthname found
+ if ($cnt < 12) {
+ $fixday = 0;
+ if ($calc == 'add') {
+ $date += $found;
+ $calc = 'set';
+ if (self::$_options['extend_month'] == false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ } else if ($calc == 'sub') {
+ $date = $month - $found;
+ $calc = 'set';
+ if (self::$_options['extend_month'] == false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), $hour);
+ }
+
+ // Monthname not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date);
+ break;
+
+ case self::MONTH:
+ if (is_numeric($date)) {
+ $fixday = 0;
+ if ($calc == 'add') {
+ $date += $month;
+ $calc = 'set';
+ if (self::$_options['extend_month'] == false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ } else if ($calc == 'sub') {
+ $date = $month - $date;
+ $calc = 'set';
+ if (self::$_options['extend_month'] == false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), $hour);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date);
+ break;
+
+ case self::MONTH_NAME_SHORT:
+ $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated'));
+ $cnt = 0;
+ foreach ($monthlist as $key => $value) {
+ if (strtoupper($value) == strtoupper($date)) {
+ $found = $key;
+ break;
+ }
+ ++$cnt;
+ }
+ $date = array_search($date, $monthlist);
+
+ // Monthname found
+ if ($cnt < 12) {
+ $fixday = 0;
+ if ($calc == 'add') {
+ $date += $found;
+ $calc = 'set';
+ if (self::$_options['extend_month'] === false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ } else if ($calc == 'sub') {
+ $date = $month - $found;
+ $calc = 'set';
+ if (self::$_options['extend_month'] === false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), $hour);
+ }
+
+ // Monthname not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date);
+ break;
+
+ case self::MONTH_SHORT:
+ if (is_numeric($date) === true) {
+ $fixday = 0;
+ if ($calc === 'add') {
+ $date += $month;
+ $calc = 'set';
+ if (self::$_options['extend_month'] === false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ } else if ($calc === 'sub') {
+ $date = $month - $date;
+ $calc = 'set';
+ if (self::$_options['extend_month'] === false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), $hour);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date);
+ break;
+
+ case self::MONTH_DAYS:
+ throw new Zend_Date_Exception('month days not supported', 0, null, $date);
+ break;
+
+ case self::MONTH_NAME_NARROW:
+ $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'stand-alone', 'narrow'));
+ $cnt = 0;
+ foreach ($monthlist as $key => $value) {
+ if (strtoupper($value) === strtoupper($date)) {
+ $found = $key;
+ break;
+ }
+ ++$cnt;
+ }
+ $date = array_search($date, $monthlist);
+
+ // Monthname found
+ if ($cnt < 12) {
+ $fixday = 0;
+ if ($calc === 'add') {
+ $date += $found;
+ $calc = 'set';
+ if (self::$_options['extend_month'] === false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ } else if ($calc === 'sub') {
+ $date = $month - $found;
+ $calc = 'set';
+ if (self::$_options['extend_month'] === false) {
+ $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false));
+ if ($parts['mday'] != $day) {
+ $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day);
+ }
+ }
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), $hour);
+ }
+
+ // Monthname not found
+ throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date);
+ break;
+
+ // year formats
+ case self::LEAPYEAR:
+ throw new Zend_Date_Exception('leap year not supported', 0, null, $date);
+ break;
+
+ case self::YEAR_8601:
+ if (is_numeric($date)) {
+ if ($calc === 'add') {
+ $date += $year;
+ $calc = 'set';
+ } else if ($calc === 'sub') {
+ $date = $year - $date;
+ $calc = 'set';
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date);
+ break;
+
+ case self::YEAR:
+ if (is_numeric($date)) {
+ if ($calc === 'add') {
+ $date += $year;
+ $calc = 'set';
+ } else if ($calc === 'sub') {
+ $date = $year - $date;
+ $calc = 'set';
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date);
+ break;
+
+ case self::YEAR_SHORT:
+ if (is_numeric($date)) {
+ $date = intval($date);
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ $date = self::getFullYear($date);
+ }
+ if ($calc === 'add') {
+ $date += $year;
+ $calc = 'set';
+ } else if ($calc === 'sub') {
+ $date = $year - $date;
+ $calc = 'set';
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date);
+ break;
+
+ case self::YEAR_SHORT_8601:
+ if (is_numeric($date)) {
+ $date = intval($date);
+ if (($calc === 'set') || ($calc === 'cmp')) {
+ $date = self::getFullYear($date);
+ }
+ if ($calc === 'add') {
+ $date += $year;
+ $calc = 'set';
+ } else if ($calc === 'sub') {
+ $date = $year - $date;
+ $calc = 'set';
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true),
+ $this->mktime(0, 0, 0, $month, $day, $year, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date);
+ break;
+
+ // time formats
+ case self::MERIDIEM:
+ throw new Zend_Date_Exception('meridiem not supported', 0, null, $date);
+ break;
+
+ case self::SWATCH:
+ if (is_numeric($date)) {
+ $rest = intval($date);
+ $hours = floor($rest * 24 / 1000);
+ $rest = $rest - ($hours * 1000 / 24);
+ $minutes = floor($rest * 1440 / 1000);
+ $rest = $rest - ($minutes * 1000 / 1440);
+ $seconds = floor($rest * 86400 / 1000);
+ return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, 1, 1, 1970, true),
+ $this->mktime($hour, $minute, $second, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, swatchstamp expected", 0, null, $date);
+ break;
+
+ case self::HOUR_SHORT_AM:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true),
+ $this->mktime($hour, 0, 0, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date);
+ break;
+
+ case self::HOUR_SHORT:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true),
+ $this->mktime($hour, 0, 0, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date);
+ break;
+
+ case self::HOUR_AM:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true),
+ $this->mktime($hour, 0, 0, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date);
+ break;
+
+ case self::HOUR:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true),
+ $this->mktime($hour, 0, 0, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date);
+ break;
+
+ case self::MINUTE:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true),
+ $this->mktime(0, $minute, 0, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date);
+ break;
+
+ case self::SECOND:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true),
+ $this->mktime(0, 0, $second, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date);
+ break;
+
+ case self::MILLISECOND:
+ if (is_numeric($date)) {
+ switch($calc) {
+ case 'set' :
+ return $this->setMillisecond($date);
+ break;
+ case 'add' :
+ return $this->addMillisecond($date);
+ break;
+ case 'sub' :
+ return $this->subMillisecond($date);
+ break;
+ }
+
+ return $this->compareMillisecond($date);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, milliseconds expected", 0, null, $date);
+ break;
+
+ case self::MINUTE_SHORT:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true),
+ $this->mktime(0, $minute, 0, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date);
+ break;
+
+ case self::SECOND_SHORT:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true),
+ $this->mktime(0, 0, $second, 1, 1, 1970, true), false);
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date);
+ break;
+
+ // timezone formats
+ // break intentionally omitted
+ case self::TIMEZONE_NAME:
+ case self::TIMEZONE:
+ case self::TIMEZONE_SECS:
+ throw new Zend_Date_Exception('timezone not supported', 0, null, $date);
+ break;
+
+ case self::DAYLIGHT:
+ throw new Zend_Date_Exception('daylight not supported', 0, null, $date);
+ break;
+
+ case self::GMT_DIFF:
+ case self::GMT_DIFF_SEP:
+ throw new Zend_Date_Exception('gmtdiff not supported', 0, null, $date);
+ break;
+
+ // date strings
+ case self::ISO_8601:
+ // (-)YYYY-MM-dd
+ preg_match('/^(-{0,1}\d{4})-(\d{2})-(\d{2})/', $date, $datematch);
+ // (-)YY-MM-dd
+ if (empty($datematch)) {
+ preg_match('/^(-{0,1}\d{2})-(\d{2})-(\d{2})/', $date, $datematch);
+ }
+ // (-)YYYYMMdd
+ if (empty($datematch)) {
+ preg_match('/^(-{0,1}\d{4})(\d{2})(\d{2})/', $date, $datematch);
+ }
+ // (-)YYMMdd
+ if (empty($datematch)) {
+ preg_match('/^(-{0,1}\d{2})(\d{2})(\d{2})/', $date, $datematch);
+ }
+ $tmpdate = $date;
+ if (!empty($datematch)) {
+ $dateMatchCharCount = iconv_strlen($datematch[0], 'UTF-8');
+ $tmpdate = iconv_substr($date,
+ $dateMatchCharCount,
+ iconv_strlen($date, 'UTF-8') - $dateMatchCharCount,
+ 'UTF-8');
+ }
+ // (T)hh:mm:ss
+ preg_match('/[T,\s]{0,1}(\d{2}):(\d{2}):(\d{2})/', $tmpdate, $timematch);
+ // (T)hhmmss
+ if (empty($timematch)) {
+ preg_match('/[T,\s]{0,1}(\d{2})(\d{2})(\d{2})/', $tmpdate, $timematch);
+ }
+ // (T)hh:mm
+ if (empty($timematch)) {
+ preg_match('/[T,\s]{0,1}(\d{2}):(\d{2})/', $tmpdate, $timematch);
+ }
+ // (T)hhmm
+ if (empty($timematch)) {
+ preg_match('/[T,\s]{0,1}(\d{2})(\d{2})/', $tmpdate, $timematch);
+ }
+ if (empty($datematch) and empty($timematch)) {
+ throw new Zend_Date_Exception("unsupported ISO8601 format ($date)", 0, null, $date);
+ }
+ if (!empty($timematch)) {
+ $timeMatchCharCount = iconv_strlen($timematch[0], 'UTF-8');
+ $tmpdate = iconv_substr($tmpdate,
+ $timeMatchCharCount,
+ iconv_strlen($tmpdate, 'UTF-8') - $timeMatchCharCount,
+ 'UTF-8');
+ }
+ if (empty($datematch)) {
+ $datematch[1] = 1970;
+ $datematch[2] = 1;
+ $datematch[3] = 1;
+ } else if (iconv_strlen($datematch[1], 'UTF-8') == 2) {
+ $datematch[1] = self::getFullYear($datematch[1]);
+ }
+ if (empty($timematch)) {
+ $timematch[1] = 0;
+ $timematch[2] = 0;
+ $timematch[3] = 0;
+ }
+ if (!isset($timematch[3])) {
+ $timematch[3] = 0;
+ }
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$datematch[2];
+ --$month;
+ --$datematch[3];
+ --$day;
+ $datematch[1] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($timematch[1], $timematch[2], $timematch[3], 1 + $datematch[2], 1 + $datematch[3], 1970 + $datematch[1], false),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false);
+ break;
+
+ case self::RFC_2822:
+ $result = preg_match('/^\w{3},\s(\d{1,2})\s(\w{3})\s(\d{4})\s'
+ . '(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]'
+ . '{1}\d{4}|\w{1,20})$/', $date, $match);
+
+ if (!$result) {
+ throw new Zend_Date_Exception("no RFC 2822 format ($date)", 0, null, $date);
+ }
+
+ $months = $this->_getDigitFromName($match[2]);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$months;
+ --$month;
+ --$match[1];
+ --$day;
+ $match[3] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false);
+ break;
+
+ case self::TIMESTAMP:
+ if (is_numeric($date)) {
+ return $this->_assign($calc, $date, $this->getUnixTimestamp());
+ }
+
+ throw new Zend_Date_Exception("invalid date ($date) operand, timestamp expected", 0, null, $date);
+ break;
+
+ // additional formats
+ // break intentionally omitted
+ case self::ERA:
+ case self::ERA_NAME:
+ throw new Zend_Date_Exception('era not supported', 0, null, $date);
+ break;
+
+ case self::DATES:
+ try {
+ $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true));
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATE_FULL:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full'));
+ $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATE_LONG:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long'));
+ $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ if (($calc == 'set') || ($calc == 'cmp')){
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATE_MEDIUM:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium'));
+ $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATE_SHORT:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short'));
+ $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ $parsed['year'] = self::getFullYear($parsed['year']);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::TIMES:
+ try {
+ if ($calc != 'set') {
+ $month = 1;
+ $day = 1;
+ $year = 1970;
+ }
+ $parsed = Zend_Locale_Format::getTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true));
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true),
+ $this->mktime($hour, $minute, $second, $month, $day, $year, true), false);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::TIME_FULL:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full'));
+ $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+ if ($calc != 'set') {
+ $month = 1;
+ $day = 1;
+ $year = 1970;
+ }
+
+ if (!isset($parsed['second'])) {
+ $parsed['second'] = 0;
+ }
+
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true),
+ $this->mktime($hour, $minute, $second, $month, $day, $year, true), false);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::TIME_LONG:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long'));
+ $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+ if ($calc != 'set') {
+ $month = 1;
+ $day = 1;
+ $year = 1970;
+ }
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true),
+ $this->mktime($hour, $minute, $second, $month, $day, $year, true), false);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::TIME_MEDIUM:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium'));
+ $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+ if ($calc != 'set') {
+ $month = 1;
+ $day = 1;
+ $year = 1970;
+ }
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true),
+ $this->mktime($hour, $minute, $second, $month, $day, $year, true), false);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::TIME_SHORT:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short'));
+ $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+ if ($calc != 'set') {
+ $month = 1;
+ $day = 1;
+ $year = 1970;
+ }
+
+ if (!isset($parsed['second'])) {
+ $parsed['second'] = 0;
+ }
+
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true),
+ $this->mktime($hour, $minute, $second, $month, $day, $year, true), false);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATETIME:
+ try {
+ $parsed = Zend_Locale_Format::getDateTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true));
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATETIME_FULL:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full'));
+ $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+
+ if (!isset($parsed['second'])) {
+ $parsed['second'] = 0;
+ }
+
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATETIME_LONG:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long'));
+ $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ if (($calc == 'set') || ($calc == 'cmp')){
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATETIME_MEDIUM:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium'));
+ $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ case self::DATETIME_SHORT:
+ try {
+ $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short'));
+ $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale));
+
+ $parsed['year'] = self::getFullYear($parsed['year']);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$parsed['month'];
+ --$month;
+ --$parsed['day'];
+ --$day;
+ $parsed['year'] -= 1970;
+ $year -= 1970;
+ }
+
+ if (!isset($parsed['second'])) {
+ $parsed['second'] = 0;
+ }
+
+ return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ break;
+
+ // ATOM and RFC_3339 are identical
+ case self::ATOM:
+ case self::RFC_3339:
+ $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\d{0,4}([+-]{1}\d{2}:\d{2}|Z)$/', $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, ATOM format expected", 0, null, $date);
+ }
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$match[2];
+ --$month;
+ --$match[3];
+ --$day;
+ $match[1] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false);
+ break;
+
+ case self::COOKIE:
+ $result = preg_match("/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,20}$/", $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, COOKIE format expected", 0, null, $date);
+ }
+ $matchStartPos = iconv_strpos($match[0], ' ', 0, 'UTF-8') + 1;
+ $match[0] = iconv_substr($match[0],
+ $matchStartPos,
+ iconv_strlen($match[0], 'UTF-8') - $matchStartPos,
+ 'UTF-8');
+
+ $months = $this->_getDigitFromName($match[2]);
+ $match[3] = self::getFullYear($match[3]);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$months;
+ --$month;
+ --$match[1];
+ --$day;
+ $match[3] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false);
+ break;
+
+ case self::RFC_822:
+ case self::RFC_1036:
+ // new RFC 822 format, identical to RFC 1036 standard
+ $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, RFC 822 date format expected", 0, null, $date);
+ }
+
+ $months = $this->_getDigitFromName($match[2]);
+ $match[3] = self::getFullYear($match[3]);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$months;
+ --$month;
+ --$match[1];
+ --$day;
+ $match[3] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false);
+ break;
+
+ case self::RFC_850:
+ $result = preg_match('/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,21}$/', $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, RFC 850 date format expected", 0, null, $date);
+ }
+
+ $months = $this->_getDigitFromName($match[2]);
+ $match[3] = self::getFullYear($match[3]);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$months;
+ --$month;
+ --$match[1];
+ --$day;
+ $match[3] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false);
+ break;
+
+ case self::RFC_1123:
+ $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, RFC 1123 date format expected", 0, null, $date);
+ }
+
+ $months = $this->_getDigitFromName($match[2]);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$months;
+ --$month;
+ --$match[1];
+ --$day;
+ $match[3] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false);
+ break;
+
+ case self::RSS:
+ $result = preg_match('/^\w{3},\s(\d{2})\s(\w{3})\s(\d{2,4})\s(\d{1,2}):(\d{2}):(\d{2})\s.{1,21}$/', $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, RSS date format expected", 0, null, $date);
+ }
+
+ $months = $this->_getDigitFromName($match[2]);
+ $match[3] = self::getFullYear($match[3]);
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$months;
+ --$month;
+ --$match[1];
+ --$day;
+ $match[3] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false);
+ break;
+
+ case self::W3C:
+ $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})[+-]{1}\d{2}:\d{2}$/', $date, $match);
+ if (!$result) {
+ throw new Zend_Date_Exception("invalid date ($date) operand, W3C date format expected", 0, null, $date);
+ }
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ --$match[2];
+ --$month;
+ --$match[3];
+ --$day;
+ $match[1] -= 1970;
+ $year -= 1970;
+ }
+ return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true),
+ $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false);
+ break;
+
+ default:
+ if (!is_numeric($date) || !empty($part)) {
+ try {
+ if (empty($part)) {
+ $part = Zend_Locale_Format::getDateFormat($locale) . " ";
+ $part .= Zend_Locale_Format::getTimeFormat($locale);
+ }
+
+ $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $part, 'locale' => $locale, 'fix_date' => true, 'format_type' => 'iso'));
+ if ((strpos(strtoupper($part), 'YY') !== false) and (strpos(strtoupper($part), 'YYYY') === false)) {
+ $parsed['year'] = self::getFullYear($parsed['year']);
+ }
+
+ if (($calc == 'set') || ($calc == 'cmp')) {
+ if (isset($parsed['month'])) {
+ --$parsed['month'];
+ } else {
+ $parsed['month'] = 0;
+ }
+
+ if (isset($parsed['day'])) {
+ --$parsed['day'];
+ } else {
+ $parsed['day'] = 0;
+ }
+
+ if (!isset($parsed['year'])) {
+ $parsed['year'] = 1970;
+ }
+ }
+
+ return $this->_assign($calc, $this->mktime(
+ isset($parsed['hour']) ? $parsed['hour'] : 0,
+ isset($parsed['minute']) ? $parsed['minute'] : 0,
+ isset($parsed['second']) ? $parsed['second'] : 0,
+ isset($parsed['month']) ? (1 + $parsed['month']) : 1,
+ isset($parsed['day']) ? (1 + $parsed['day']) : 1,
+ $parsed['year'],
+ false), $this->getUnixTimestamp(), false);
+ } catch (Zend_Locale_Exception $e) {
+ if (!is_numeric($date)) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date);
+ }
+ }
+ }
+
+ return $this->_assign($calc, $date, $this->getUnixTimestamp(), false);
+ break;
+ }
+ }
+
+ /**
+ * Returns true when both date objects or date parts are equal.
+ * For example:
+ * 15.May.2000 <-> 15.June.2000 Equals only for Day or Year... all other will return false
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to equal with
+ * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return boolean
+ * @throws Zend_Date_Exception
+ */
+ public function equals($date, $part = self::TIMESTAMP, $locale = null)
+ {
+ $result = $this->compare($date, $part, $locale);
+
+ if ($result == 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns if the given date or datepart is earlier
+ * For example:
+ * 15.May.2000 <-> 13.June.1999 will return true for day, year and date, but not for month
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to compare with
+ * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return boolean
+ * @throws Zend_Date_Exception
+ */
+ public function isEarlier($date, $part = null, $locale = null)
+ {
+ $result = $this->compare($date, $part, $locale);
+
+ if ($result == -1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns if the given date or datepart is later
+ * For example:
+ * 15.May.2000 <-> 13.June.1999 will return true for month but false for day, year and date
+ * Returns if the given date is later
+ *
+ * @param string|integer|array|Zend_Date $date Date or datepart to compare with
+ * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return boolean
+ * @throws Zend_Date_Exception
+ */
+ public function isLater($date, $part = null, $locale = null)
+ {
+ $result = $this->compare($date, $part, $locale);
+
+ if ($result == 1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns only the time of the date as new Zend_Date object
+ * For example:
+ * 15.May.2000 10:11:23 will return a dateobject equal to 01.Jan.1970 10:11:23
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getTime($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'H:i:s';
+ } else {
+ $format = self::TIME_MEDIUM;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+ /**
+ * Returns the calculated time
+ *
+ * @param string $calc Calculation to make
+ * @param string|integer|array|Zend_Date $time Time to calculate with, if null the actual time is taken
+ * @param string $format Timeformat for parsing input
+ * @param string|Zend_Locale $locale Locale for parsing input
+ * @return integer|Zend_Date new time
+ * @throws Zend_Date_Exception
+ */
+ private function _time($calc, $time, $format, $locale)
+ {
+ if ($time === null) {
+ throw new Zend_Date_Exception('parameter $time must be set, null is not allowed');
+ }
+
+ if ($time instanceof Zend_Date) {
+ // extract time from object
+ $time = $time->toString('HH:mm:ss', 'iso');
+ } else {
+ if (is_array($time)) {
+ if ((isset($time['hour']) === true) or (isset($time['minute']) === true) or
+ (isset($time['second']) === true)) {
+ $parsed = $time;
+ } else {
+ throw new Zend_Date_Exception("no hour, minute or second given in array");
+ }
+ } else {
+ if (self::$_options['format_type'] == 'php') {
+ $format = Zend_Locale_Format::convertPhpToIsoFormat($format);
+ }
+ try {
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ $parsed = Zend_Locale_Format::getTime($time, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso'));
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e);
+ }
+ }
+
+ if (!array_key_exists('hour', $parsed)) {
+ $parsed['hour'] = 0;
+ }
+
+ if (!array_key_exists('minute', $parsed)) {
+ $parsed['minute'] = 0;
+ }
+
+ if (!array_key_exists('second', $parsed)) {
+ $parsed['second'] = 0;
+ }
+
+ $time = str_pad($parsed['hour'], 2, '0', STR_PAD_LEFT) . ":";
+ $time .= str_pad($parsed['minute'], 2, '0', STR_PAD_LEFT) . ":";
+ $time .= str_pad($parsed['second'], 2, '0', STR_PAD_LEFT);
+ }
+
+ $return = $this->_calcdetail($calc, $time, self::TIMES, 'de');
+ if ($calc != 'cmp') {
+ return $this;
+ }
+
+ return $return;
+ }
+
+
+ /**
+ * Sets a new time for the date object. Format defines how to parse the time string.
+ * Also a complete date can be given, but only the time is used for setting.
+ * For example: dd.MMMM.yyTHH:mm' and 'ss sec'-> 10.May.07T25:11 and 44 sec => 1h11min44sec + 1 day
+ * Returned is the new date object and the existing date is left as it was before
+ *
+ * @param string|integer|array|Zend_Date $time Time to set
+ * @param string $format OPTIONAL Timeformat for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setTime($time, $format = null, $locale = null)
+ {
+ return $this->_time('set', $time, $format, $locale);
+ }
+
+
+ /**
+ * Adds a time to the existing date. Format defines how to parse the time string.
+ * If only parts are given the other parts are set to 0.
+ * If no format is given, the standardformat of this locale is used.
+ * For example: HH:mm:ss -> 10 -> +10 hours
+ *
+ * @param string|integer|array|Zend_Date $time Time to add
+ * @param string $format OPTIONAL Timeformat for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addTime($time, $format = null, $locale = null)
+ {
+ return $this->_time('add', $time, $format, $locale);
+ }
+
+
+ /**
+ * Subtracts a time from the existing date. Format defines how to parse the time string.
+ * If only parts are given the other parts are set to 0.
+ * If no format is given, the standardformat of this locale is used.
+ * For example: HH:mm:ss -> 10 -> -10 hours
+ *
+ * @param string|integer|array|Zend_Date $time Time to sub
+ * @param string $format OPTIONAL Timeformat for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent inteface
+ * @throws Zend_Date_Exception
+ */
+ public function subTime($time, $format = null, $locale = null)
+ {
+ return $this->_time('sub', $time, $format, $locale);
+ }
+
+
+ /**
+ * Compares the time from the existing date. Format defines how to parse the time string.
+ * If only parts are given the other parts are set to default.
+ * If no format us given, the standardformat of this locale is used.
+ * For example: HH:mm:ss -> 10 -> 10 hours
+ *
+ * @param string|integer|array|Zend_Date $time Time to compare
+ * @param string $format OPTIONAL Timeformat for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareTime($time, $format = null, $locale = null)
+ {
+ return $this->_time('cmp', $time, $format, $locale);
+ }
+
+ /**
+ * Returns a clone of $this, with the time part set to 00:00:00.
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getDate($locale = null)
+ {
+ $orig = self::$_options['format_type'];
+ if (self::$_options['format_type'] == 'php') {
+ self::$_options['format_type'] = 'iso';
+ }
+
+ $date = $this->copyPart(self::DATE_MEDIUM, $locale);
+ $date->addTimestamp($this->getGmtOffset());
+ self::$_options['format_type'] = $orig;
+
+ return $date;
+ }
+
+ /**
+ * Returns the calculated date
+ *
+ * @param string $calc Calculation to make
+ * @param string|integer|array|Zend_Date $date Date to calculate with, if null the actual date is taken
+ * @param string $format Date format for parsing
+ * @param string|Zend_Locale $locale Locale for parsing input
+ * @return integer|Zend_Date new date
+ * @throws Zend_Date_Exception
+ */
+ private function _date($calc, $date, $format, $locale)
+ {
+ if ($date === null) {
+ throw new Zend_Date_Exception('parameter $date must be set, null is not allowed');
+ }
+
+ if ($date instanceof Zend_Date) {
+ // extract date from object
+ $date = $date->toString('d.M.y', 'iso');
+ } else {
+ if (is_array($date)) {
+ if ((isset($date['year']) === true) or (isset($date['month']) === true) or
+ (isset($date['day']) === true)) {
+ $parsed = $date;
+ } else {
+ throw new Zend_Date_Exception("no day,month or year given in array");
+ }
+ } else {
+ if ((self::$_options['format_type'] == 'php') && !defined($format)) {
+ $format = Zend_Locale_Format::convertPhpToIsoFormat($format);
+ }
+ try {
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso'));
+ if ((strpos(strtoupper($format), 'YY') !== false) and (strpos(strtoupper($format), 'YYYY') === false)) {
+ $parsed['year'] = self::getFullYear($parsed['year']);
+ }
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e);
+ }
+ }
+
+ if (!array_key_exists('day', $parsed)) {
+ $parsed['day'] = 1;
+ }
+
+ if (!array_key_exists('month', $parsed)) {
+ $parsed['month'] = 1;
+ }
+
+ if (!array_key_exists('year', $parsed)) {
+ $parsed['year'] = 0;
+ }
+
+ $date = $parsed['day'] . "." . $parsed['month'] . "." . $parsed['year'];
+ }
+
+ $return = $this->_calcdetail($calc, $date, self::DATE_MEDIUM, 'de');
+ if ($calc != 'cmp') {
+ return $this;
+ }
+ return $return;
+ }
+
+
+ /**
+ * Sets a new date for the date object. Format defines how to parse the date string.
+ * Also a complete date with time can be given, but only the date is used for setting.
+ * For example: MMMM.yy HH:mm-> May.07 22:11 => 01.May.07 00:00
+ * Returned is the new date object and the existing time is left as it was before
+ *
+ * @param string|integer|array|Zend_Date $date Date to set
+ * @param string $format OPTIONAL Date format for parsing
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setDate($date, $format = null, $locale = null)
+ {
+ return $this->_date('set', $date, $format, $locale);
+ }
+
+
+ /**
+ * Adds a date to the existing date object. Format defines how to parse the date string.
+ * If only parts are given the other parts are set to 0.
+ * If no format is given, the standardformat of this locale is used.
+ * For example: MM.dd.YYYY -> 10 -> +10 months
+ *
+ * @param string|integer|array|Zend_Date $date Date to add
+ * @param string $format OPTIONAL Date format for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addDate($date, $format = null, $locale = null)
+ {
+ return $this->_date('add', $date, $format, $locale);
+ }
+
+
+ /**
+ * Subtracts a date from the existing date object. Format defines how to parse the date string.
+ * If only parts are given the other parts are set to 0.
+ * If no format is given, the standardformat of this locale is used.
+ * For example: MM.dd.YYYY -> 10 -> -10 months
+ * Be aware: Subtracting 2 months is not equal to Adding -2 months !!!
+ *
+ * @param string|integer|array|Zend_Date $date Date to sub
+ * @param string $format OPTIONAL Date format for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subDate($date, $format = null, $locale = null)
+ {
+ return $this->_date('sub', $date, $format, $locale);
+ }
+
+
+ /**
+ * Compares the date from the existing date object, ignoring the time.
+ * Format defines how to parse the date string.
+ * If only parts are given the other parts are set to 0.
+ * If no format is given, the standardformat of this locale is used.
+ * For example: 10.01.2000 => 10.02.1999 -> false
+ *
+ * @param string|integer|array|Zend_Date $date Date to compare
+ * @param string $format OPTIONAL Date format for parsing input
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareDate($date, $format = null, $locale = null)
+ {
+ return $this->_date('cmp', $date, $format, $locale);
+ }
+
+
+ /**
+ * Returns the full ISO 8601 date from the date object.
+ * Always the complete ISO 8601 specifiction is used. If an other ISO date is needed
+ * (ISO 8601 defines several formats) use toString() instead.
+ * This function does not return the ISO date as object. Use copy() instead.
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return string
+ */
+ public function getIso($locale = null)
+ {
+ return $this->toString(self::ISO_8601, 'iso', $locale);
+ }
+
+
+ /**
+ * Sets a new date for the date object. Not given parts are set to default.
+ * Only supported ISO 8601 formats are accepted.
+ * For example: 050901 -> 01.Sept.2005 00:00:00, 20050201T10:00:30 -> 01.Feb.2005 10h00m30s
+ * Returned is the new date object
+ *
+ * @param string|integer|Zend_Date $date ISO Date to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setIso($date, $locale = null)
+ {
+ return $this->_calcvalue('set', $date, 'iso', self::ISO_8601, $locale);
+ }
+
+
+ /**
+ * Adds a ISO date to the date object. Not given parts are set to default.
+ * Only supported ISO 8601 formats are accepted.
+ * For example: 050901 -> + 01.Sept.2005 00:00:00, 10:00:00 -> +10h
+ * Returned is the new date object
+ *
+ * @param string|integer|Zend_Date $date ISO Date to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addIso($date, $locale = null)
+ {
+ return $this->_calcvalue('add', $date, 'iso', self::ISO_8601, $locale);
+ }
+
+
+ /**
+ * Subtracts a ISO date from the date object. Not given parts are set to default.
+ * Only supported ISO 8601 formats are accepted.
+ * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h
+ * Returned is the new date object
+ *
+ * @param string|integer|Zend_Date $date ISO Date to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subIso($date, $locale = null)
+ {
+ return $this->_calcvalue('sub', $date, 'iso', self::ISO_8601, $locale);
+ }
+
+
+ /**
+ * Compares a ISO date with the date object. Not given parts are set to default.
+ * Only supported ISO 8601 formats are accepted.
+ * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|Zend_Date $date ISO Date to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareIso($date, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $date, 'iso', self::ISO_8601, $locale);
+ }
+
+
+ /**
+ * Returns a RFC 822 compilant datestring from the date object.
+ * This function does not return the RFC date as object. Use copy() instead.
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return string
+ */
+ public function getArpa($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'D\, d M y H\:i\:s O';
+ } else {
+ $format = self::RFC_822;
+ }
+
+ return $this->toString($format, 'iso', $locale);
+ }
+
+
+ /**
+ * Sets a RFC 822 date as new date for the date object.
+ * Only RFC 822 compilant date strings are accepted.
+ * For example: Sat, 14 Feb 09 00:31:30 +0100
+ * Returned is the new date object
+ *
+ * @param string|integer|Zend_Date $date RFC 822 to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setArpa($date, $locale = null)
+ {
+ return $this->_calcvalue('set', $date, 'arpa', self::RFC_822, $locale);
+ }
+
+
+ /**
+ * Adds a RFC 822 date to the date object.
+ * ARPA messages are used in emails or HTTP Headers.
+ * Only RFC 822 compilant date strings are accepted.
+ * For example: Sat, 14 Feb 09 00:31:30 +0100
+ * Returned is the new date object
+ *
+ * @param string|integer|Zend_Date $date RFC 822 Date to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addArpa($date, $locale = null)
+ {
+ return $this->_calcvalue('add', $date, 'arpa', self::RFC_822, $locale);
+ }
+
+
+ /**
+ * Subtracts a RFC 822 date from the date object.
+ * ARPA messages are used in emails or HTTP Headers.
+ * Only RFC 822 compilant date strings are accepted.
+ * For example: Sat, 14 Feb 09 00:31:30 +0100
+ * Returned is the new date object
+ *
+ * @param string|integer|Zend_Date $date RFC 822 Date to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subArpa($date, $locale = null)
+ {
+ return $this->_calcvalue('sub', $date, 'arpa', self::RFC_822, $locale);
+ }
+
+
+ /**
+ * Compares a RFC 822 compilant date with the date object.
+ * ARPA messages are used in emails or HTTP Headers.
+ * Only RFC 822 compilant date strings are accepted.
+ * For example: Sat, 14 Feb 09 00:31:30 +0100
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|Zend_Date $date RFC 822 Date to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareArpa($date, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $date, 'arpa', self::RFC_822, $locale);
+ }
+
+ /**
+ * Check if location is supported
+ *
+ * @param array $location locations array
+ * @throws Zend_Date_Exception
+ * @return float $horizon float
+ */
+ private function _checkLocation($location)
+ {
+ if (!isset($location['longitude']) or !isset($location['latitude'])) {
+ throw new Zend_Date_Exception('Location must include \'longitude\' and \'latitude\'', 0, null, $location);
+ }
+ if (($location['longitude'] > 180) or ($location['longitude'] < -180)) {
+ throw new Zend_Date_Exception('Longitude must be between -180 and 180', 0, null, $location);
+ }
+ if (($location['latitude'] > 90) or ($location['latitude'] < -90)) {
+ throw new Zend_Date_Exception('Latitude must be between -90 and 90', 0, null, $location);
+ }
+
+ if (!isset($location['horizon'])){
+ $location['horizon'] = 'effective';
+ }
+
+ switch ($location['horizon']) {
+ case 'civil' :
+ return -0.104528;
+ break;
+ case 'nautic' :
+ return -0.207912;
+ break;
+ case 'astronomic' :
+ return -0.309017;
+ break;
+ default :
+ return -0.0145439;
+ break;
+ }
+ }
+
+
+ /**
+ * Returns the time of sunrise for this date and a given location as new date object
+ * For a list of cities and correct locations use the class Zend_Date_Cities
+ *
+ * @param array $location location of sunrise
+ * ['horizon'] -> civil, nautic, astronomical, effective (default)
+ * ['longitude'] -> longitude of location
+ * ['latitude'] -> latitude of location
+ * @return Zend_Date
+ * @throws Zend_Date_Exception
+ */
+ public function getSunrise($location)
+ {
+ $horizon = $this->_checkLocation($location);
+ $result = clone $this;
+ $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP);
+ return $result;
+ }
+
+
+ /**
+ * Returns the time of sunset for this date and a given location as new date object
+ * For a list of cities and correct locations use the class Zend_Date_Cities
+ *
+ * @param array $location location of sunset
+ * ['horizon'] -> civil, nautic, astronomical, effective (default)
+ * ['longitude'] -> longitude of location
+ * ['latitude'] -> latitude of location
+ * @return Zend_Date
+ * @throws Zend_Date_Exception
+ */
+ public function getSunset($location)
+ {
+ $horizon = $this->_checkLocation($location);
+ $result = clone $this;
+ $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP);
+ return $result;
+ }
+
+
+ /**
+ * Returns an array with the sunset and sunrise dates for all horizon types
+ * For a list of cities and correct locations use the class Zend_Date_Cities
+ *
+ * @param array $location location of suninfo
+ * ['horizon'] -> civil, nautic, astronomical, effective (default)
+ * ['longitude'] -> longitude of location
+ * ['latitude'] -> latitude of location
+ * @return array - [sunset|sunrise][effective|civil|nautic|astronomic]
+ * @throws Zend_Date_Exception
+ */
+ public function getSunInfo($location)
+ {
+ $suninfo = array();
+ for ($i = 0; $i < 4; ++$i) {
+ switch ($i) {
+ case 0 :
+ $location['horizon'] = 'effective';
+ break;
+ case 1 :
+ $location['horizon'] = 'civil';
+ break;
+ case 2 :
+ $location['horizon'] = 'nautic';
+ break;
+ case 3 :
+ $location['horizon'] = 'astronomic';
+ break;
+ }
+ $horizon = $this->_checkLocation($location);
+ $result = clone $this;
+ $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP);
+ $suninfo['sunrise'][$location['horizon']] = $result;
+ $result = clone $this;
+ $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP);
+ $suninfo['sunset'][$location['horizon']] = $result;
+ }
+ return $suninfo;
+ }
+
+ /**
+ * Check a given year for leap year.
+ *
+ * @param integer|array|Zend_Date $year Year to check
+ * @throws Zend_Date_Exception
+ * @return boolean
+ */
+ public static function checkLeapYear($year)
+ {
+ if ($year instanceof Zend_Date) {
+ $year = (int) $year->toString(self::YEAR, 'iso');
+ }
+
+ if (is_array($year)) {
+ if (isset($year['year']) === true) {
+ $year = $year['year'];
+ } else {
+ throw new Zend_Date_Exception("no year given in array");
+ }
+ }
+
+ if (!is_numeric($year)) {
+ throw new Zend_Date_Exception("year ($year) has to be integer for checkLeapYear()", 0, null, $year);
+ }
+
+ return (bool) parent::isYearLeapYear($year);
+ }
+
+
+ /**
+ * Returns true, if the year is a leap year.
+ *
+ * @return boolean
+ */
+ public function isLeapYear()
+ {
+ return self::checkLeapYear($this);
+ }
+
+
+ /**
+ * Returns if the set date is todays date
+ *
+ * @return boolean
+ */
+ public function isToday()
+ {
+ $today = $this->date('Ymd', $this->_getTime());
+ $day = $this->date('Ymd', $this->getUnixTimestamp());
+ return ($today == $day);
+ }
+
+
+ /**
+ * Returns if the set date is yesterdays date
+ *
+ * @return boolean
+ */
+ public function isYesterday()
+ {
+ list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime()));
+ // adjusts for leap days and DST changes that are timezone specific
+ $yesterday = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day -1, $year));
+ $day = $this->date('Ymd', $this->getUnixTimestamp());
+ return $day == $yesterday;
+ }
+
+
+ /**
+ * Returns if the set date is tomorrows date
+ *
+ * @return boolean
+ */
+ public function isTomorrow()
+ {
+ list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime()));
+ // adjusts for leap days and DST changes that are timezone specific
+ $tomorrow = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day +1, $year));
+ $day = $this->date('Ymd', $this->getUnixTimestamp());
+ return $day == $tomorrow;
+ }
+
+ /**
+ * Returns the actual date as new date object
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public static function now($locale = null)
+ {
+ return new Zend_Date(time(), self::TIMESTAMP, $locale);
+ }
+
+ /**
+ * Calculate date details
+ *
+ * @param string $calc Calculation to make
+ * @param string|integer|array|Zend_Date $date Date or Part to calculate
+ * @param string $type Datepart for Calculation
+ * @param string|Zend_Locale $locale Locale for parsing input
+ * @return integer|string new date
+ * @throws Zend_Date_Exception
+ */
+ private function _calcdetail($calc, $date, $type, $locale)
+ {
+ $old = false;
+ if (self::$_options['format_type'] == 'php') {
+ self::$_options['format_type'] = 'iso';
+ $old = true;
+ }
+
+ switch($calc) {
+ case 'set' :
+ $return = $this->set($date, $type, $locale);
+ break;
+ case 'add' :
+ $return = $this->add($date, $type, $locale);
+ break;
+ case 'sub' :
+ $return = $this->sub($date, $type, $locale);
+ break;
+ default :
+ $return = $this->compare($date, $type, $locale);
+ break;
+ }
+
+ if ($old) {
+ self::$_options['format_type'] = 'php';
+ }
+
+ return $return;
+ }
+
+ /**
+ * Internal calculation, returns the requested date type
+ *
+ * @param string $calc Calculation to make
+ * @param string|integer|Zend_Date $value Datevalue to calculate with, if null the actual value is taken
+ * @param string $type
+ * @param string $parameter
+ * @param string|Zend_Locale $locale Locale for parsing input
+ * @throws Zend_Date_Exception
+ * @return integer|Zend_Date new date
+ */
+ private function _calcvalue($calc, $value, $type, $parameter, $locale)
+ {
+ if ($value === null) {
+ throw new Zend_Date_Exception("parameter $type must be set, null is not allowed");
+ }
+
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ if ($value instanceof Zend_Date) {
+ // extract value from object
+ $value = $value->toString($parameter, 'iso', $locale);
+ } else if (!is_array($value) && !is_numeric($value) && ($type != 'iso') && ($type != 'arpa')) {
+ throw new Zend_Date_Exception("invalid $type ($value) operand", 0, null, $value);
+ }
+
+ $return = $this->_calcdetail($calc, $value, $parameter, $locale);
+ if ($calc != 'cmp') {
+ return $this;
+ }
+ return $return;
+ }
+
+
+ /**
+ * Returns only the year from the date object as new object.
+ * For example: 10.May.2000 10:30:00 -> 01.Jan.2000 00:00:00
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getYear($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'Y';
+ } else {
+ $format = self::YEAR;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+
+ /**
+ * Sets a new year
+ * If the year is between 0 and 69, 2000 will be set (2000-2069)
+ * If the year if between 70 and 99, 1999 will be set (1970-1999)
+ * 3 or 4 digit years are set as expected. If you need to set year 0-99
+ * use set() instead.
+ * Returned is the new date object
+ *
+ * @param string|integer|array|Zend_Date $year Year to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setYear($year, $locale = null)
+ {
+ return $this->_calcvalue('set', $year, 'year', self::YEAR, $locale);
+ }
+
+
+ /**
+ * Adds the year to the existing date object
+ * If the year is between 0 and 69, 2000 will be added (2000-2069)
+ * If the year if between 70 and 99, 1999 will be added (1970-1999)
+ * 3 or 4 digit years are added as expected. If you need to add years from 0-99
+ * use add() instead.
+ * Returned is the new date object
+ *
+ * @param string|integer|array|Zend_Date $year Year to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addYear($year, $locale = null)
+ {
+ return $this->_calcvalue('add', $year, 'year', self::YEAR, $locale);
+ }
+
+
+ /**
+ * Subs the year from the existing date object
+ * If the year is between 0 and 69, 2000 will be subtracted (2000-2069)
+ * If the year if between 70 and 99, 1999 will be subtracted (1970-1999)
+ * 3 or 4 digit years are subtracted as expected. If you need to subtract years from 0-99
+ * use sub() instead.
+ * Returned is the new date object
+ *
+ * @param string|integer|array|Zend_Date $year Year to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subYear($year, $locale = null)
+ {
+ return $this->_calcvalue('sub', $year, 'year', self::YEAR, $locale);
+ }
+
+
+ /**
+ * Compares the year with the existing date object, ignoring other date parts.
+ * For example: 10.03.2000 -> 15.02.2000 -> true
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $year Year to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareYear($year, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $year, 'year', self::YEAR, $locale);
+ }
+
+
+ /**
+ * Returns only the month from the date object as new object.
+ * For example: 10.May.2000 10:30:00 -> 01.May.1970 00:00:00
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getMonth($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'm';
+ } else {
+ $format = self::MONTH;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+
+ /**
+ * Returns the calculated month
+ *
+ * @param string $calc Calculation to make
+ * @param string|integer|array|Zend_Date $month Month to calculate with, if null the actual month is taken
+ * @param string|Zend_Locale $locale Locale for parsing input
+ * @return integer|Zend_Date new time
+ * @throws Zend_Date_Exception
+ */
+ private function _month($calc, $month, $locale)
+ {
+ if ($month === null) {
+ throw new Zend_Date_Exception('parameter $month must be set, null is not allowed');
+ }
+
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ if ($month instanceof Zend_Date) {
+ // extract month from object
+ $found = $month->toString(self::MONTH_SHORT, 'iso', $locale);
+ } else {
+ if (is_numeric($month)) {
+ $found = $month;
+ } else if (is_array($month)) {
+ if (isset($month['month']) === true) {
+ $month = $month['month'];
+ } else {
+ throw new Zend_Date_Exception("no month given in array");
+ }
+ } else {
+ $monthlist = Zend_Locale_Data::getList($locale, 'month');
+ $monthlist2 = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated'));
+
+ $monthlist = array_merge($monthlist, $monthlist2);
+ $found = 0;
+ $cnt = 0;
+ foreach ($monthlist as $key => $value) {
+ if (strtoupper($value) == strtoupper($month)) {
+ $found = ($key % 12) + 1;
+ break;
+ }
+ ++$cnt;
+ }
+ if ($found == 0) {
+ foreach ($monthlist2 as $key => $value) {
+ if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($month)) {
+ $found = $key + 1;
+ break;
+ }
+ ++$cnt;
+ }
+ }
+ if ($found == 0) {
+ throw new Zend_Date_Exception("unknown month name ($month)", 0, null, $month);
+ }
+ }
+ }
+ $return = $this->_calcdetail($calc, $found, self::MONTH_SHORT, $locale);
+ if ($calc != 'cmp') {
+ return $this;
+ }
+ return $return;
+ }
+
+
+ /**
+ * Sets a new month
+ * The month can be a number or a string. Setting months lower then 0 and greater then 12
+ * will result in adding or subtracting the relevant year. (12 months equal one year)
+ * If a localized monthname is given it will be parsed with the default locale or the optional
+ * set locale.
+ * Returned is the new date object
+ *
+ * @param string|integer|array|Zend_Date $month Month to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setMonth($month, $locale = null)
+ {
+ return $this->_month('set', $month, $locale);
+ }
+
+
+ /**
+ * Adds months to the existing date object.
+ * The month can be a number or a string. Adding months lower then 0 and greater then 12
+ * will result in adding or subtracting the relevant year. (12 months equal one year)
+ * If a localized monthname is given it will be parsed with the default locale or the optional
+ * set locale.
+ * Returned is the new date object
+ *
+ * @param string|integer|array|Zend_Date $month Month to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addMonth($month, $locale = null)
+ {
+ return $this->_month('add', $month, $locale);
+ }
+
+
+ /**
+ * Subtracts months from the existing date object.
+ * The month can be a number or a string. Subtracting months lower then 0 and greater then 12
+ * will result in adding or subtracting the relevant year. (12 months equal one year)
+ * If a localized monthname is given it will be parsed with the default locale or the optional
+ * set locale.
+ * Returned is the new date object
+ *
+ * @param string|integer|array|Zend_Date $month Month to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subMonth($month, $locale = null)
+ {
+ return $this->_month('sub', $month, $locale);
+ }
+
+
+ /**
+ * Compares the month with the existing date object, ignoring other date parts.
+ * For example: 10.03.2000 -> 15.03.1950 -> true
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $month Month to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareMonth($month, $locale = null)
+ {
+ return $this->_month('cmp', $month, $locale);
+ }
+
+
+ /**
+ * Returns the day as new date object
+ * Example: 20.May.1986 -> 20.Jan.1970 00:00:00
+ *
+ * @param Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getDay($locale = null)
+ {
+ return $this->copyPart(self::DAY_SHORT, $locale);
+ }
+
+ /**
+ * Returns the calculated day
+ *
+ * @param string $calc Type of calculation to make
+ * @param Zend_Date $day Day to calculate, when null the actual day is calculated
+ * @param Zend_Locale $locale Locale for parsing input
+ * @throws Zend_Date_Exception
+ * @return Zend_Date|integer
+ */
+ private function _day($calc, $day, $locale)
+ {
+ if ($day === null) {
+ throw new Zend_Date_Exception('parameter $day must be set, null is not allowed');
+ }
+
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ if ($day instanceof Zend_Date) {
+ $day = $day->toString(self::DAY_SHORT, 'iso', $locale);
+ }
+
+ if (is_numeric($day)) {
+ $type = self::DAY_SHORT;
+ } else if (is_array($day)) {
+ if (isset($day['day']) === true) {
+ $day = $day['day'];
+ $type = self::WEEKDAY;
+ } else {
+ throw new Zend_Date_Exception("no day given in array");
+ }
+ } else {
+ switch (iconv_strlen($day, 'UTF-8')) {
+ case 1 :
+ $type = self::WEEKDAY_NARROW;
+ break;
+ case 2:
+ $type = self::WEEKDAY_NAME;
+ break;
+ case 3:
+ $type = self::WEEKDAY_SHORT;
+ break;
+ default:
+ $type = self::WEEKDAY;
+ break;
+ }
+ }
+ $return = $this->_calcdetail($calc, $day, $type, $locale);
+ if ($calc != 'cmp') {
+ return $this;
+ }
+ return $return;
+ }
+
+
+ /**
+ * Sets a new day
+ * The day can be a number or a string. Setting days lower then 0 or greater than the number of this months days
+ * will result in adding or subtracting the relevant month.
+ * If a localized dayname is given it will be parsed with the default locale or the optional
+ * set locale.
+ * Returned is the new date object
+ * Example: setDay('Montag', 'de_AT'); will set the monday of this week as day.
+ *
+ * @param string|integer|array|Zend_Date $day Day to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setDay($day, $locale = null)
+ {
+ return $this->_day('set', $day, $locale);
+ }
+
+
+ /**
+ * Adds days to the existing date object.
+ * The day can be a number or a string. Adding days lower then 0 or greater than the number of this months days
+ * will result in adding or subtracting the relevant month.
+ * If a localized dayname is given it will be parsed with the default locale or the optional
+ * set locale.
+ *
+ * @param string|integer|array|Zend_Date $day Day to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addDay($day, $locale = null)
+ {
+ return $this->_day('add', $day, $locale);
+ }
+
+
+ /**
+ * Subtracts days from the existing date object.
+ * The day can be a number or a string. Subtracting days lower then 0 or greater than the number of this months days
+ * will result in adding or subtracting the relevant month.
+ * If a localized dayname is given it will be parsed with the default locale or the optional
+ * set locale.
+ *
+ * @param string|integer|array|Zend_Date $day Day to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subDay($day, $locale = null)
+ {
+ return $this->_day('sub', $day, $locale);
+ }
+
+
+ /**
+ * Compares the day with the existing date object, ignoring other date parts.
+ * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $day Day to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareDay($day, $locale = null)
+ {
+ return $this->_day('cmp', $day, $locale);
+ }
+
+
+ /**
+ * Returns the weekday as new date object
+ * Weekday is always from 1-7
+ * Example: 09-Jan-2007 -> 2 = Tuesday -> 02-Jan-1970 (when 02.01.1970 is also Tuesday)
+ *
+ * @param Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getWeekday($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'l';
+ } else {
+ $format = self::WEEKDAY;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+
+ /**
+ * Returns the calculated weekday
+ *
+ * @param string $calc Type of calculation to make
+ * @param Zend_Date $weekday Weekday to calculate, when null the actual weekday is calculated
+ * @param Zend_Locale $locale Locale for parsing input
+ * @return Zend_Date|integer
+ * @throws Zend_Date_Exception
+ */
+ private function _weekday($calc, $weekday, $locale)
+ {
+ if ($weekday === null) {
+ throw new Zend_Date_Exception('parameter $weekday must be set, null is not allowed');
+ }
+
+ if ($locale === null) {
+ $locale = $this->getLocale();
+ }
+
+ if ($weekday instanceof Zend_Date) {
+ $weekday = $weekday->toString(self::WEEKDAY_8601, 'iso', $locale);
+ }
+
+ if (is_numeric($weekday)) {
+ $type = self::WEEKDAY_8601;
+ } else if (is_array($weekday)) {
+ if (isset($weekday['weekday']) === true) {
+ $weekday = $weekday['weekday'];
+ $type = self::WEEKDAY;
+ } else {
+ throw new Zend_Date_Exception("no weekday given in array");
+ }
+ } else {
+ switch(iconv_strlen($weekday, 'UTF-8')) {
+ case 1:
+ $type = self::WEEKDAY_NARROW;
+ break;
+ case 2:
+ $type = self::WEEKDAY_NAME;
+ break;
+ case 3:
+ $type = self::WEEKDAY_SHORT;
+ break;
+ default:
+ $type = self::WEEKDAY;
+ break;
+ }
+ }
+ $return = $this->_calcdetail($calc, $weekday, $type, $locale);
+ if ($calc != 'cmp') {
+ return $this;
+ }
+ return $return;
+ }
+
+
+ /**
+ * Sets a new weekday
+ * The weekday can be a number or a string. If a localized weekday name is given,
+ * then it will be parsed as a date in $locale (defaults to the same locale as $this).
+ * Returned is the new date object.
+ * Example: setWeekday(3); will set the wednesday of this week as day.
+ *
+ * @param string|integer|array|Zend_Date $weekday Weekday to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setWeekday($weekday, $locale = null)
+ {
+ return $this->_weekday('set', $weekday, $locale);
+ }
+
+
+ /**
+ * Adds weekdays to the existing date object.
+ * The weekday can be a number or a string.
+ * If a localized dayname is given it will be parsed with the default locale or the optional
+ * set locale.
+ * Returned is the new date object
+ * Example: addWeekday(3); will add the difference of days from the begining of the month until
+ * wednesday.
+ *
+ * @param string|integer|array|Zend_Date $weekday Weekday to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addWeekday($weekday, $locale = null)
+ {
+ return $this->_weekday('add', $weekday, $locale);
+ }
+
+
+ /**
+ * Subtracts weekdays from the existing date object.
+ * The weekday can be a number or a string.
+ * If a localized dayname is given it will be parsed with the default locale or the optional
+ * set locale.
+ * Returned is the new date object
+ * Example: subWeekday(3); will subtract the difference of days from the begining of the month until
+ * wednesday.
+ *
+ * @param string|integer|array|Zend_Date $weekday Weekday to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subWeekday($weekday, $locale = null)
+ {
+ return $this->_weekday('sub', $weekday, $locale);
+ }
+
+
+ /**
+ * Compares the weekday with the existing date object, ignoring other date parts.
+ * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $weekday Weekday to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareWeekday($weekday, $locale = null)
+ {
+ return $this->_weekday('cmp', $weekday, $locale);
+ }
+
+
+ /**
+ * Returns the day of year as new date object
+ * Example: 02.Feb.1986 10:00:00 -> 02.Feb.1970 00:00:00
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getDayOfYear($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'D';
+ } else {
+ $format = self::DAY_OF_YEAR;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+
+ /**
+ * Sets a new day of year
+ * The day of year is always a number.
+ * Returned is the new date object
+ * Example: 04.May.2004 -> setDayOfYear(10) -> 10.Jan.2004
+ *
+ * @param string|integer|array|Zend_Date $day Day of Year to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setDayOfYear($day, $locale = null)
+ {
+ return $this->_calcvalue('set', $day, 'day of year', self::DAY_OF_YEAR, $locale);
+ }
+
+
+ /**
+ * Adds a day of year to the existing date object.
+ * The day of year is always a number.
+ * Returned is the new date object
+ * Example: addDayOfYear(10); will add 10 days to the existing date object.
+ *
+ * @param string|integer|array|Zend_Date $day Day of Year to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addDayOfYear($day, $locale = null)
+ {
+ return $this->_calcvalue('add', $day, 'day of year', self::DAY_OF_YEAR, $locale);
+ }
+
+
+ /**
+ * Subtracts a day of year from the existing date object.
+ * The day of year is always a number.
+ * Returned is the new date object
+ * Example: subDayOfYear(10); will subtract 10 days from the existing date object.
+ *
+ * @param string|integer|array|Zend_Date $day Day of Year to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subDayOfYear($day, $locale = null)
+ {
+ return $this->_calcvalue('sub', $day, 'day of year', self::DAY_OF_YEAR, $locale);
+ }
+
+
+ /**
+ * Compares the day of year with the existing date object.
+ * For example: compareDayOfYear(33) -> 02.Feb.2007 -> 0
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $day Day of Year to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareDayOfYear($day, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $day, 'day of year', self::DAY_OF_YEAR, $locale);
+ }
+
+
+ /**
+ * Returns the hour as new date object
+ * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 10:00:00
+ *
+ * @param Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getHour($locale = null)
+ {
+ return $this->copyPart(self::HOUR, $locale);
+ }
+
+
+ /**
+ * Sets a new hour
+ * The hour is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> setHour(7); -> 04.May.1993 07:07:25
+ *
+ * @param string|integer|array|Zend_Date $hour Hour to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setHour($hour, $locale = null)
+ {
+ return $this->_calcvalue('set', $hour, 'hour', self::HOUR_SHORT, $locale);
+ }
+
+
+ /**
+ * Adds hours to the existing date object.
+ * The hour is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> addHour(12); -> 05.May.1993 01:07:25
+ *
+ * @param string|integer|array|Zend_Date $hour Hour to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addHour($hour, $locale = null)
+ {
+ return $this->_calcvalue('add', $hour, 'hour', self::HOUR_SHORT, $locale);
+ }
+
+
+ /**
+ * Subtracts hours from the existing date object.
+ * The hour is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> subHour(6); -> 05.May.1993 07:07:25
+ *
+ * @param string|integer|array|Zend_Date $hour Hour to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subHour($hour, $locale = null)
+ {
+ return $this->_calcvalue('sub', $hour, 'hour', self::HOUR_SHORT, $locale);
+ }
+
+
+ /**
+ * Compares the hour with the existing date object.
+ * For example: 10:30:25 -> compareHour(10) -> 0
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $hour Hour to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareHour($hour, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $hour, 'hour', self::HOUR_SHORT, $locale);
+ }
+
+
+ /**
+ * Returns the minute as new date object
+ * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:30:00
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getMinute($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'i';
+ } else {
+ $format = self::MINUTE;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+
+ /**
+ * Sets a new minute
+ * The minute is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> setMinute(29); -> 04.May.1993 13:29:25
+ *
+ * @param string|integer|array|Zend_Date $minute Minute to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setMinute($minute, $locale = null)
+ {
+ return $this->_calcvalue('set', $minute, 'minute', self::MINUTE_SHORT, $locale);
+ }
+
+
+ /**
+ * Adds minutes to the existing date object.
+ * The minute is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> addMinute(65); -> 04.May.1993 13:12:25
+ *
+ * @param string|integer|array|Zend_Date $minute Minute to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addMinute($minute, $locale = null)
+ {
+ return $this->_calcvalue('add', $minute, 'minute', self::MINUTE_SHORT, $locale);
+ }
+
+
+ /**
+ * Subtracts minutes from the existing date object.
+ * The minute is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> subMinute(9); -> 04.May.1993 12:58:25
+ *
+ * @param string|integer|array|Zend_Date $minute Minute to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subMinute($minute, $locale = null)
+ {
+ return $this->_calcvalue('sub', $minute, 'minute', self::MINUTE_SHORT, $locale);
+ }
+
+
+ /**
+ * Compares the minute with the existing date object.
+ * For example: 10:30:25 -> compareMinute(30) -> 0
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $minute Hour to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareMinute($minute, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $minute, 'minute', self::MINUTE_SHORT, $locale);
+ }
+
+
+ /**
+ * Returns the second as new date object
+ * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:00:25
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getSecond($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 's';
+ } else {
+ $format = self::SECOND;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+
+ /**
+ * Sets new seconds to the existing date object.
+ * The second is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> setSecond(100); -> 04.May.1993 13:08:40
+ *
+ * @param string|integer|array|Zend_Date $second Second to set
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setSecond($second, $locale = null)
+ {
+ return $this->_calcvalue('set', $second, 'second', self::SECOND_SHORT, $locale);
+ }
+
+
+ /**
+ * Adds seconds to the existing date object.
+ * The second is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> addSecond(65); -> 04.May.1993 13:08:30
+ *
+ * @param string|integer|array|Zend_Date $second Second to add
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addSecond($second, $locale = null)
+ {
+ return $this->_calcvalue('add', $second, 'second', self::SECOND_SHORT, $locale);
+ }
+
+
+ /**
+ * Subtracts seconds from the existing date object.
+ * The second is always a number.
+ * Returned is the new date object
+ * Example: 04.May.1993 13:07:25 -> subSecond(10); -> 04.May.1993 13:07:15
+ *
+ * @param string|integer|array|Zend_Date $second Second to sub
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subSecond($second, $locale = null)
+ {
+ return $this->_calcvalue('sub', $second, 'second', self::SECOND_SHORT, $locale);
+ }
+
+
+ /**
+ * Compares the second with the existing date object.
+ * For example: 10:30:25 -> compareSecond(25) -> 0
+ * Returns if equal, earlier or later
+ *
+ * @param string|integer|array|Zend_Date $second Second to compare
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ * @throws Zend_Date_Exception
+ */
+ public function compareSecond($second, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $second, 'second', self::SECOND_SHORT, $locale);
+ }
+
+
+ /**
+ * Returns the precision for fractional seconds
+ *
+ * @return integer
+ */
+ public function getFractionalPrecision()
+ {
+ return $this->_precision;
+ }
+
+
+ /**
+ * Sets a new precision for fractional seconds
+ *
+ * @param integer $precision Precision for the fractional datepart 3 = milliseconds
+ * @throws Zend_Date_Exception
+ * @return Zend_Date Provides a fluent interface
+ */
+ public function setFractionalPrecision($precision)
+ {
+ if (!intval($precision) or ($precision < 0) or ($precision > 9)) {
+ throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision);
+ }
+
+ $this->_precision = (int) $precision;
+ if ($this->_precision < strlen($this->_fractional)) {
+ $this->_fractional = substr($this->_fractional, 0, $this->_precision);
+ } else {
+ $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_RIGHT);
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Returns the milliseconds of the date object
+ *
+ * @return string
+ */
+ public function getMilliSecond()
+ {
+ return $this->_fractional;
+ }
+
+ /**
+ * Sets new milliseconds for the date object
+ * Example: setMilliSecond(550, 2) -> equals +5 Sec +50 MilliSec
+ *
+ * @param integer|Zend_Date $milli (Optional) Millisecond to set, when null the actual millisecond is set
+ * @param integer $precision (Optional) Fraction precision of the given milliseconds
+ * @throws Zend_Date_Exception
+ * @return Zend_Date Provides a fluent interface
+ */
+ public function setMilliSecond($milli = null, $precision = null)
+ {
+ if ($milli === null) {
+ list($milli, $time) = explode(" ", microtime());
+ $milli = intval($milli);
+ $precision = 6;
+ } else if (!is_numeric($milli)) {
+ throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli);
+ }
+
+ if ($precision === null) {
+ $precision = $this->_precision;
+ }
+
+ if (!is_int($precision) || $precision < 1 || $precision > 9) {
+ throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision);
+ }
+
+ $this->_fractional = 0;
+ $this->addMilliSecond($milli, $precision);
+ return $this;
+ }
+
+ /**
+ * Adds milliseconds to the date object
+ *
+ * @param integer|Zend_Date $milli (Optional) Millisecond to add, when null the actual millisecond is added
+ * @param integer $precision (Optional) Fractional precision for the given milliseconds
+ * @throws Zend_Date_Exception
+ * @return Zend_Date Provides a fluent interface
+ */
+ public function addMilliSecond($milli = null, $precision = null)
+ {
+ if ($milli === null) {
+ list($milli, $time) = explode(" ", microtime());
+ $milli = intval($milli);
+ } else if (!is_numeric($milli)) {
+ throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli);
+ }
+
+ if ($precision === null) {
+ // Use internal default precision
+ // Is not as logic as using the length of the input. But this would break tests and maybe other things
+ // as an input value of integer 10, which is used in tests, must be parsed as 10 milliseconds (real milliseconds, precision 3)
+ // but with auto-detect of precision, 100 milliseconds would be added.
+ $precision = $this->_precision;
+ }
+
+ if (!is_int($precision) || $precision < 1 || $precision > 9) {
+ throw new Zend_Date_Exception(
+ "precision ($precision) must be a positive integer less than 10", 0, null, $precision
+ );
+ }
+
+ if ($this->_precision > $precision) {
+ $milli = $milli * pow(10, $this->_precision - $precision);
+ } elseif ($this->_precision < $precision) {
+ $milli = round($milli / pow(10, $precision - $this->_precision));
+ }
+
+ $this->_fractional += $milli;
+
+ // Add/sub milliseconds + add/sub seconds
+ $max = pow(10, $this->_precision);
+ // Milli includes seconds
+ if ($this->_fractional >= $max) {
+ while ($this->_fractional >= $max) {
+ $this->addSecond(1);
+ $this->_fractional -= $max;
+ }
+ }
+
+ if ($this->_fractional < 0) {
+ while ($this->_fractional < 0) {
+ $this->subSecond(1);
+ $this->_fractional += $max;
+ }
+ }
+
+ if ($this->_precision > strlen($this->_fractional)) {
+ $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_LEFT);
+ }
+
+ return $this;
+ }
+
+
+ /**
+ * Subtracts a millisecond
+ *
+ * @param integer|Zend_Date $milli (Optional) Millisecond to sub, when null the actual millisecond is subtracted
+ * @param integer $precision (Optional) Fractional precision for the given milliseconds
+ * @return Zend_Date Provides a fluent interface
+ */
+ public function subMilliSecond($milli = null, $precision = null)
+ {
+ $this->addMilliSecond(0 - $milli, $precision);
+ return $this;
+ }
+
+ /**
+ * Compares only the millisecond part, returning the difference
+ *
+ * @param integer|Zend_Date $milli OPTIONAL Millisecond to compare, when null the actual millisecond is compared
+ * @param integer $precision OPTIONAL Fractional precision for the given milliseconds
+ * @throws Zend_Date_Exception On invalid input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ */
+ public function compareMilliSecond($milli = null, $precision = null)
+ {
+ if ($milli === null) {
+ list($milli, $time) = explode(" ", microtime());
+ $milli = intval($milli);
+ } else if (is_numeric($milli) === false) {
+ throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli);
+ }
+
+ if ($precision === null) {
+ $precision = strlen($milli);
+ } else if (!is_int($precision) || $precision < 1 || $precision > 9) {
+ throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision);
+ }
+
+ if ($precision === 0) {
+ throw new Zend_Date_Exception('precision is 0');
+ }
+
+ if ($precision != $this->_precision) {
+ if ($precision > $this->_precision) {
+ $diff = $precision - $this->_precision;
+ $milli = (int) ($milli / (10 * $diff));
+ } else {
+ $diff = $this->_precision - $precision;
+ $milli = (int) ($milli * (10 * $diff));
+ }
+ }
+
+ $comp = $this->_fractional - $milli;
+ if ($comp < 0) {
+ return -1;
+ } else if ($comp > 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the week as new date object using monday as begining of the week
+ * Example: 12.Jan.2007 -> 08.Jan.1970 00:00:00
+ *
+ * @param Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date
+ */
+ public function getWeek($locale = null)
+ {
+ if (self::$_options['format_type'] == 'php') {
+ $format = 'W';
+ } else {
+ $format = self::WEEK;
+ }
+
+ return $this->copyPart($format, $locale);
+ }
+
+ /**
+ * Sets a new week. The week is always a number. The day of week is not changed.
+ * Returned is the new date object
+ * Example: 09.Jan.2007 13:07:25 -> setWeek(1); -> 02.Jan.2007 13:07:25
+ *
+ * @param string|integer|array|Zend_Date $week Week to set
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setWeek($week, $locale = null)
+ {
+ return $this->_calcvalue('set', $week, 'week', self::WEEK, $locale);
+ }
+
+ /**
+ * Adds a week. The week is always a number. The day of week is not changed.
+ * Returned is the new date object
+ * Example: 09.Jan.2007 13:07:25 -> addWeek(1); -> 16.Jan.2007 13:07:25
+ *
+ * @param string|integer|array|Zend_Date $week Week to add
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function addWeek($week, $locale = null)
+ {
+ return $this->_calcvalue('add', $week, 'week', self::WEEK, $locale);
+ }
+
+ /**
+ * Subtracts a week. The week is always a number. The day of week is not changed.
+ * Returned is the new date object
+ * Example: 09.Jan.2007 13:07:25 -> subWeek(1); -> 02.Jan.2007 13:07:25
+ *
+ * @param string|integer|array|Zend_Date $week Week to sub
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return Zend_Date Provides a fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function subWeek($week, $locale = null)
+ {
+ return $this->_calcvalue('sub', $week, 'week', self::WEEK, $locale);
+ }
+
+ /**
+ * Compares only the week part, returning the difference
+ * Returned is the new date object
+ * Returns if equal, earlier or later
+ * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0
+ *
+ * @param string|integer|array|Zend_Date $week Week to compare
+ * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input
+ * @return integer 0 = equal, 1 = later, -1 = earlier
+ */
+ public function compareWeek($week, $locale = null)
+ {
+ return $this->_calcvalue('cmp', $week, 'week', self::WEEK, $locale);
+ }
+
+ /**
+ * Sets a new standard locale for the date object.
+ * This locale will be used for all functions
+ * Returned is the really set locale.
+ * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist
+ * 'xx_YY' will be set to 'root' because 'xx' does not exist
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing input
+ * @throws Zend_Date_Exception When the given locale does not exist
+ * @return Zend_Date Provides fluent interface
+ */
+ public function setLocale($locale = null)
+ {
+ try {
+ $this->_locale = Zend_Locale::findLocale($locale);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Date_Exception($e->getMessage(), 0, $e);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the actual set locale
+ *
+ * @return string
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Checks if the given date is a real date or datepart.
+ * Returns false if a expected datepart is missing or a datepart exceeds its possible border.
+ * But the check will only be done for the expected dateparts which are given by format.
+ * If no format is given the standard dateformat for the actual locale is used.
+ * f.e. 30.February.2007 will return false if format is 'dd.MMMM.YYYY'
+ *
+ * @param string|array|Zend_Date $date Date to parse for correctness
+ * @param string $format (Optional) Format for parsing the date string
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing date parts
+ * @return boolean True when all date parts are correct
+ */
+ public static function isDate($date, $format = null, $locale = null)
+ {
+ if (!is_string($date) && !is_numeric($date) && !($date instanceof Zend_Date) &&
+ !is_array($date)) {
+ return false;
+ }
+
+ if (($format !== null) && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT')
+ && (Zend_Locale::isLocale($format, null, false))) {
+ $locale = $format;
+ $format = null;
+ }
+
+ $locale = Zend_Locale::findLocale($locale);
+
+ if ($format === null) {
+ $format = Zend_Locale_Format::getDateFormat($locale);
+ } else if ((self::$_options['format_type'] == 'php') && !defined($format)) {
+ $format = Zend_Locale_Format::convertPhpToIsoFormat($format);
+ }
+
+ $format = self::_getLocalizedToken($format, $locale);
+ if (!is_array($date)) {
+ try {
+ $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale,
+ 'date_format' => $format, 'format_type' => 'iso',
+ 'fix_date' => false));
+ } catch (Zend_Locale_Exception $e) {
+ // Date can not be parsed
+ return false;
+ }
+ } else {
+ $parsed = $date;
+ }
+
+ if (((strpos($format, 'Y') !== false) or (strpos($format, 'y') !== false)) and
+ (!isset($parsed['year']))) {
+ // Year expected but not found
+ return false;
+ }
+
+ if ((strpos($format, 'M') !== false) and (!isset($parsed['month']))) {
+ // Month expected but not found
+ return false;
+ }
+
+ if ((strpos($format, 'd') !== false) and (!isset($parsed['day']))) {
+ // Day expected but not found
+ return false;
+ }
+
+ if (((strpos($format, 'H') !== false) or (strpos($format, 'h') !== false)) and
+ (!isset($parsed['hour']))) {
+ // Hour expected but not found
+ return false;
+ }
+
+ if ((strpos($format, 'm') !== false) and (!isset($parsed['minute']))) {
+ // Minute expected but not found
+ return false;
+ }
+
+ if ((strpos($format, 's') !== false) and (!isset($parsed['second']))) {
+ // Second expected but not found
+ return false;
+ }
+
+ // Set not given dateparts
+ if (isset($parsed['hour']) === false) {
+ $parsed['hour'] = 12;
+ }
+
+ if (isset($parsed['minute']) === false) {
+ $parsed['minute'] = 0;
+ }
+
+ if (isset($parsed['second']) === false) {
+ $parsed['second'] = 0;
+ }
+
+ if (isset($parsed['month']) === false) {
+ $parsed['month'] = 1;
+ }
+
+ if (isset($parsed['day']) === false) {
+ $parsed['day'] = 1;
+ }
+
+ if (isset($parsed['year']) === false) {
+ $parsed['year'] = 1970;
+ }
+
+ if (self::isYearLeapYear($parsed['year'])) {
+ $parsed['year'] = 1972;
+ } else {
+ $parsed['year'] = 1971;
+ }
+
+ $date = new self($parsed, null, $locale);
+ $timestamp = $date->mktime($parsed['hour'], $parsed['minute'], $parsed['second'],
+ $parsed['month'], $parsed['day'], $parsed['year']);
+
+ if ($parsed['year'] != $date->date('Y', $timestamp)) {
+ // Given year differs from parsed year
+ return false;
+ }
+
+ if ($parsed['month'] != $date->date('n', $timestamp)) {
+ // Given month differs from parsed month
+ return false;
+ }
+
+ if ($parsed['day'] != $date->date('j', $timestamp)) {
+ // Given day differs from parsed day
+ return false;
+ }
+
+ if ($parsed['hour'] != $date->date('G', $timestamp)) {
+ // Given hour differs from parsed hour
+ return false;
+ }
+
+ if ($parsed['minute'] != $date->date('i', $timestamp)) {
+ // Given minute differs from parsed minute
+ return false;
+ }
+
+ if ($parsed['second'] != $date->date('s', $timestamp)) {
+ // Given second differs from parsed second
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the ISO Token for all localized constants
+ *
+ * @param string $token Token to normalize
+ * @param string $locale Locale to search
+ * @return string
+ */
+ protected static function _getLocalizedToken($token, $locale)
+ {
+ switch($token) {
+ case self::ISO_8601 :
+ return "yyyy-MM-ddThh:mm:ss";
+ break;
+ case self::RFC_2822 :
+ return "EEE, dd MMM yyyy HH:mm:ss";
+ break;
+ case self::DATES :
+ return Zend_Locale_Data::getContent($locale, 'date');
+ break;
+ case self::DATE_FULL :
+ return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full'));
+ break;
+ case self::DATE_LONG :
+ return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long'));
+ break;
+ case self::DATE_MEDIUM :
+ return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium'));
+ break;
+ case self::DATE_SHORT :
+ return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short'));
+ break;
+ case self::TIMES :
+ return Zend_Locale_Data::getContent($locale, 'time');
+ break;
+ case self::TIME_FULL :
+ return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full'));
+ break;
+ case self::TIME_LONG :
+ return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long'));
+ break;
+ case self::TIME_MEDIUM :
+ return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium'));
+ break;
+ case self::TIME_SHORT :
+ return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short'));
+ break;
+ case self::DATETIME :
+ return Zend_Locale_Data::getContent($locale, 'datetime');
+ break;
+ case self::DATETIME_FULL :
+ return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full'));
+ break;
+ case self::DATETIME_LONG :
+ return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long'));
+ break;
+ case self::DATETIME_MEDIUM :
+ return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium'));
+ break;
+ case self::DATETIME_SHORT :
+ return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short'));
+ break;
+ case self::ATOM :
+ case self::RFC_3339 :
+ case self::W3C :
+ return "yyyy-MM-DD HH:mm:ss";
+ break;
+ case self::COOKIE :
+ case self::RFC_850 :
+ return "EEEE, dd-MM-yyyy HH:mm:ss";
+ break;
+ case self::RFC_822 :
+ case self::RFC_1036 :
+ case self::RFC_1123 :
+ case self::RSS :
+ return "EEE, dd MM yyyy HH:mm:ss";
+ break;
+ }
+
+ return $token;
+ }
+}
diff --git a/library/vendor/Zend/Date/Cities.php b/library/vendor/Zend/Date/Cities.php
new file mode 100644
index 0000000..7b5f59a
--- /dev/null
+++ b/library/vendor/Zend/Date/Cities.php
@@ -0,0 +1,321 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Date
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Additional data for sunset/sunrise calculations
+ *
+ * Holds the geographical data for all capital cities and many others worldwide
+ * Original data from http://www.fallingrain.com/world/
+ *
+ * @category Zend
+ * @package Zend_Date
+ * @subpackage Zend_Date_Cities
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Date_Cities
+{
+ /**
+ * Array Collection of known cities
+ *
+ * The array contains 'latitude' and 'longitude' for every known city
+ *
+ * @var Array
+ */
+ public static $cities = array(
+ 'Abidjan' => array('latitude' => 5.3411111, 'longitude' => -4.0280556),
+ 'Abu Dhabi' => array('latitude' => 24.4666667, 'longitude' => 54.3666667),
+ 'Abuja' => array('latitude' => 9.1758333, 'longitude' => 7.1808333),
+ 'Accra' => array('latitude' => 5.55, 'longitude' => -0.2166667),
+ 'Adamstown' => array('latitude' => -25.0666667, 'longitude' => -130.0833333),
+ 'Addis Ababa' => array('latitude' => 9.0333333, 'longitude' => 38.7),
+ 'Adelaide' => array('latitude' => -34.9333333, 'longitude' => 138.6),
+ 'Algiers' => array('latitude' => 36.7630556, 'longitude' => 3.0505556),
+ 'Alofi' => array('latitude' => -19.0166667, 'longitude' => -169.9166667),
+ 'Amman' => array('latitude' => 31.95, 'longitude' => 35.9333333),
+ 'Amsterdam' => array('latitude' => 52.35, 'longitude' => 4.9166667),
+ 'Andorra la Vella' => array('latitude' => 42.5, 'longitude' => 1.5166667),
+ 'Ankara' => array('latitude' => 39.9272222, 'longitude' => 32.8644444),
+ 'Antananarivo' => array('latitude' => -18.9166667, 'longitude' => 47.5166667),
+ 'Apia' => array('latitude' => -13.8333333, 'longitude' => -171.7333333),
+ 'Ashgabat' => array('latitude' => 37.95, 'longitude' => 58.3833333),
+ 'Asmara' => array('latitude' => 15.3333333, 'longitude' => 38.9333333),
+ 'Astana' => array('latitude' => 51.1811111, 'longitude' => 71.4277778),
+ 'Asunción' => array('latitude' => -25.2666667, 'longitude' => -57.6666667),
+ 'Athens' => array('latitude' => 37.9833333, 'longitude' => 23.7333333),
+ 'Auckland' => array('latitude' => -36.8666667, 'longitude' => 174.7666667),
+ 'Avarua' => array('latitude' => -21.2, 'longitude' => -159.7666667),
+ 'Baghdad' => array('latitude' => 33.3386111, 'longitude' => 44.3938889),
+ 'Baku' => array('latitude' => 40.3952778, 'longitude' => 49.8822222),
+ 'Bamako' => array('latitude' => 12.65, 'longitude' => -8),
+ 'Bandar Seri Begawan' => array('latitude' => 4.8833333, 'longitude' => 114.9333333),
+ 'Bankok' => array('latitude' => 13.5833333, 'longitude' => 100.2166667),
+ 'Bangui' => array('latitude' => 4.3666667, 'longitude' => 18.5833333),
+ 'Banjul' => array('latitude' => 13.4530556, 'longitude' => -16.5775),
+ 'Basel' => array('latitude' => 47.5666667, 'longitude' => 7.6),
+ 'Basseterre' => array('latitude' => 17.3, 'longitude' => -62.7166667),
+ 'Beijing' => array('latitude' => 39.9288889, 'longitude' => 116.3883333),
+ 'Beirut' => array('latitude' => 33.8719444, 'longitude' => 35.5097222),
+ 'Belgrade' => array('latitude' => 44.8186111, 'longitude' => 20.4680556),
+ 'Belmopan' => array('latitude' => 17.25, 'longitude' => -88.7666667),
+ 'Berlin' => array('latitude' => 52.5166667, 'longitude' => 13.4),
+ 'Bern' => array('latitude' => 46.9166667, 'longitude' => 7.4666667),
+ 'Bishkek' => array('latitude' => 42.8730556, 'longitude' => 74.6002778),
+ 'Bissau' => array('latitude' => 11.85, 'longitude' => -15.5833333),
+ 'Bloemfontein' => array('latitude' => -29.1333333, 'longitude' => 26.2),
+ 'Bogotá' => array('latitude' => 4.6, 'longitude' => -74.0833333),
+ 'Brasilia' => array('latitude' => -15.7833333, 'longitude' => -47.9166667),
+ 'Bratislava' => array('latitude' => 48.15, 'longitude' => 17.1166667),
+ 'Brazzaville' => array('latitude' => -4.2591667, 'longitude' => 15.2847222),
+ 'Bridgetown' => array('latitude' => 13.1, 'longitude' => -59.6166667),
+ 'Brisbane' => array('latitude' => -27.5, 'longitude' => 153.0166667),
+ 'Brussels' => array('latitude' => 50.8333333, 'longitude' => 4.3333333),
+ 'Bucharest' => array('latitude' => 44.4333333, 'longitude' => 26.1),
+ 'Budapest' => array('latitude' => 47.5, 'longitude' => 19.0833333),
+ 'Buenos Aires' => array('latitude' => -34.5875, 'longitude' => -58.6725),
+ 'Bujumbura' => array('latitude' => -3.3761111, 'longitude' => 29.36),
+ 'Cairo' => array('latitude' => 30.05, 'longitude' => 31.25),
+ 'Calgary' => array('latitude' => 51.0833333, 'longitude' => -114.0833333),
+ 'Canberra' => array('latitude' => -35.2833333, 'longitude' => 149.2166667),
+ 'Cape Town' => array('latitude' => -33.9166667, 'longitude' => 18.4166667),
+ 'Caracas' => array('latitude' => 10.5, 'longitude' => -66.9166667),
+ 'Castries' => array('latitude' => 14, 'longitude' => -61),
+ 'Charlotte Amalie' => array('latitude' => 18.34389, 'longitude' => -64.93111),
+ 'Chicago' => array('latitude' => 41.85, 'longitude' => -87.65),
+ 'Chisinau' => array('latitude' => 47.055556, 'longitude' => 28.8575),
+ 'Cockburn Town' => array('latitude' => 21.4666667, 'longitude' => -71.1333333),
+ 'Colombo' => array('latitude' => 6.9319444, 'longitude' => 79.8477778),
+ 'Conakry' => array('latitude' => 9.5091667, 'longitude' => -13.7122222),
+ 'Copenhagen' => array('latitude' => 55.6666667, 'longitude' => 12.5833333),
+ 'Cotonou' => array('latitude' => 6.35, 'longitude' => 2.4333333),
+ 'Dakar' => array('latitude' => 14.6708333, 'longitude' => -17.4380556),
+ 'Damascus' => array('latitude' => 33.5, 'longitude' => 36.3),
+ 'Dar es Salaam' => array('latitude' => -6.8, 'longitude' => 39.2833333),
+ 'Dhaka' => array('latitude' => 23.7230556, 'longitude' => 90.4086111),
+ 'Dili' => array('latitude' => -8.5586111, 'longitude' => 125.5736111),
+ 'Djibouti' => array('latitude' => 11.595, 'longitude' => 43.1480556),
+ 'Dodoma' => array('latitude' => -6.1833333, 'longitude' => 35.75),
+ 'Doha' => array('latitude' => 25.2866667, 'longitude' => 51.5333333),
+ 'Dubai' => array('latitude' => 25.2522222, 'longitude' => 55.28),
+ 'Dublin' => array('latitude' => 53.3330556, 'longitude' => -6.2488889),
+ 'Dushanbe' => array('latitude' => 38.56, 'longitude' => 68.7738889 ),
+ 'Fagatogo' => array('latitude' => -14.2825, 'longitude' => -170.69),
+ 'Fongafale' => array('latitude' => -8.5166667, 'longitude' => 179.2166667),
+ 'Freetown' => array('latitude' => 8.49, 'longitude' => -13.2341667),
+ 'Gaborone' => array('latitude' => -24.6463889, 'longitude' => 25.9119444),
+ 'Geneva' => array('latitude' => 46.2, 'longitude' => 6.1666667),
+ 'George Town' => array('latitude' => 19.3, 'longitude' => -81.3833333),
+ 'Georgetown' => array('latitude' => 6.8, 'longitude' => -58.1666667),
+ 'Gibraltar' => array('latitude' => 36.1333333, 'longitude' => -5.35),
+ 'Glasgow' => array('latitude' => 55.8333333, 'longitude' => -4.25),
+ 'Guatemala la Nueva' => array('latitude' => 14.6211111, 'longitude' => -90.5269444),
+ 'Hagatna' => array('latitude' => 13.47417, 'longitude' => 144.74778),
+ 'The Hague' => array('latitude' => 52.0833333, 'longitude' => 4.3),
+ 'Hamilton' => array('latitude' => 32.2941667, 'longitude' => -64.7838889),
+ 'Hanoi' => array('latitude' => 21.0333333, 'longitude' => 105.85),
+ 'Harare' => array('latitude' => -17.8177778, 'longitude' => 31.0447222),
+ 'Havana' => array('latitude' => 23.1319444, 'longitude' => -82.3641667),
+ 'Helsinki' => array('latitude' => 60.1755556, 'longitude' => 24.9341667),
+ 'Honiara' => array('latitude' => -9.4333333, 'longitude' => 159.95),
+ 'Islamabad' => array('latitude' => 30.8486111, 'longitude' => 72.4944444),
+ 'Istanbul' => array('latitude' => 41.0186111, 'longitude' => 28.9647222),
+ 'Jakarta' => array('latitude' => -6.1744444, 'longitude' => 106.8294444),
+ 'Jamestown' => array('latitude' => -15.9333333, 'longitude' => -5.7166667),
+ 'Jerusalem' => array('latitude' => 31.7666667, 'longitude' => 35.2333333),
+ 'Johannesburg' => array('latitude' => -26.2, 'longitude' => 28.0833333),
+ 'Kabul' => array('latitude' => 34.5166667, 'longitude' => 69.1833333),
+ 'Kampala' => array('latitude' => 0.3155556, 'longitude' => 32.5655556),
+ 'Kathmandu' => array('latitude' => 27.7166667, 'longitude' => 85.3166667),
+ 'Khartoum' => array('latitude' => 15.5880556, 'longitude' => 32.5341667),
+ 'Kigali' => array('latitude' => -1.9536111, 'longitude' => 30.0605556),
+ 'Kingston' => array('latitude' => -29.05, 'longitude' => 167.95),
+ 'Kingstown' => array('latitude' => 13.1333333, 'longitude' => -61.2166667),
+ 'Kinshasa' => array('latitude' => -4.3, 'longitude' => 15.3),
+ 'Kolkata' => array('latitude' => 22.5697222, 'longitude' => 88.3697222),
+ 'Kuala Lumpur' => array('latitude' => 3.1666667, 'longitude' => 101.7),
+ 'Kuwait City' => array('latitude' => 29.3697222, 'longitude' => 47.9783333),
+ 'Kiev' => array('latitude' => 50.4333333, 'longitude' => 30.5166667),
+ 'La Paz' => array('latitude' => -16.5, 'longitude' => -68.15),
+ 'Libreville' => array('latitude' => 0.3833333, 'longitude' => 9.45),
+ 'Lilongwe' => array('latitude' => -13.9833333, 'longitude' => 33.7833333),
+ 'Lima' => array('latitude' => -12.05, 'longitude' => -77.05),
+ 'Lisbon' => array('latitude' => 38.7166667, 'longitude' => -9.1333333),
+ 'Ljubljana' => array('latitude' => 46.0552778, 'longitude' => 14.5144444),
+ 'Lobamba' => array('latitude' => -26.4666667, 'longitude' => 31.2),
+ 'Lomé' => array('latitude' => 9.7166667, 'longitude' => 38.3),
+ 'London' => array('latitude' => 51.5, 'longitude' => -0.1166667),
+ 'Los Angeles' => array('latitude' => 34.05222, 'longitude' => -118.24278),
+ 'Luanda' => array('latitude' => -8.8383333, 'longitude' => 13.2344444),
+ 'Lusaka' => array('latitude' => -15.4166667, 'longitude' => 28.2833333),
+ 'Luxembourg' => array('latitude' => 49.6116667, 'longitude' => 6.13),
+ 'Madrid' => array('latitude' => 40.4, 'longitude' => -3.6833333),
+ 'Majuro' => array('latitude' => 7.1, 'longitude' => 171.3833333),
+ 'Malabo' => array('latitude' => 3.75, 'longitude' => 8.7833333),
+ 'Managua' => array('latitude' => 12.1508333, 'longitude' => -86.2683333),
+ 'Manama' => array('latitude' => 26.2361111, 'longitude' => 50.5830556),
+ 'Manila' => array('latitude' => 14.6041667, 'longitude' => 120.9822222),
+ 'Maputo' => array('latitude' => -25.9652778, 'longitude' => 32.5891667),
+ 'Maseru' => array('latitude' => -29.3166667, 'longitude' => 27.4833333),
+ 'Mbabane' => array('latitude' => -26.3166667, 'longitude' => 31.1333333),
+ 'Melbourne' => array('latitude' => -37.8166667, 'longitude' => 144.9666667),
+ 'Melekeok' => array('latitude' => 7.4933333, 'longitude' => 134.6341667),
+ 'Mexiko City' => array('latitude' => 19.4341667, 'longitude' => -99.1386111),
+ 'Minsk' => array('latitude' => 53.9, 'longitude' => 27.5666667),
+ 'Mogadishu' => array('latitude' => 2.0666667, 'longitude' => 45.3666667),
+ 'Monaco' => array('latitude' => 43.7333333, 'longitude' => 7.4166667),
+ 'Monrovia' => array('latitude' => 6.3105556, 'longitude' => -10.8047222),
+ 'Montevideo' => array('latitude' => -34.8580556, 'longitude' => -56.1708333),
+ 'Montreal' => array('latitude' => 45.5, 'longitude' => -73.5833333),
+ 'Moroni' => array('latitude' => -11.7041667, 'longitude' => 43.2402778),
+ 'Moscow' => array('latitude' => 55.7522222, 'longitude' => 37.6155556),
+ 'Muscat' => array('latitude' => 23.6133333, 'longitude' => 58.5933333),
+ 'Nairobi' => array('latitude' => -1.3166667, 'longitude' => 36.8333333),
+ 'Nassau' => array('latitude' => 25.0833333, 'longitude' => -77.35),
+ 'N´Djamena' => array('latitude' => 12.1130556, 'longitude' => 15.0491667),
+ 'New Dehli' => array('latitude' => 28.6, 'longitude' => 77.2),
+ 'New York' => array('latitude' => 40.71417, 'longitude' => -74.00639),
+ 'Newcastle' => array('latitude' => -32.9166667, 'longitude' => 151.75),
+ 'Niamey' => array('latitude' => 13.6666667, 'longitude' => 1.7833333),
+ 'Nicosia' => array('latitude' => 35.1666667, 'longitude' => 33.3666667),
+ 'Nouakchott' => array('latitude' => 18.0863889, 'longitude' => -15.9752778),
+ 'Noumea' => array('latitude' => -22.2666667, 'longitude' => 166.45),
+ 'Nuku´alofa' => array('latitude' => -21.1333333, 'longitude' => -175.2),
+ 'Nuuk' => array('latitude' => 64.1833333, 'longitude' => -51.75),
+ 'Oranjestad' => array('latitude' => 12.5166667, 'longitude' => -70.0333333),
+ 'Oslo' => array('latitude' => 59.9166667, 'longitude' => 10.75),
+ 'Ouagadougou' => array('latitude' => 12.3702778, 'longitude' => -1.5247222),
+ 'Palikir' => array('latitude' => 6.9166667, 'longitude' => 158.15),
+ 'Panama City' => array('latitude' => 8.9666667, 'longitude' => -79.5333333),
+ 'Papeete' => array('latitude' => -17.5333333, 'longitude' => -149.5666667),
+ 'Paramaribo' => array('latitude' => 5.8333333, 'longitude' => -55.1666667),
+ 'Paris' => array('latitude' => 48.8666667, 'longitude' => 2.3333333),
+ 'Perth' => array('latitude' => -31.9333333, 'longitude' => 115.8333333),
+ 'Phnom Penh' => array('latitude' => 11.55, 'longitude' => 104.9166667),
+ 'Podgorica' => array('latitude' => 43.7752778, 'longitude' => 19.6827778),
+ 'Port Louis' => array('latitude' => -20.1666667, 'longitude' => 57.5),
+ 'Port Moresby' => array('latitude' => -9.4647222, 'longitude' => 147.1925),
+ 'Port-au-Prince' => array('latitude' => 18.5391667, 'longitude' => -72.335),
+ 'Port of Spain' => array('latitude' => 10.6666667, 'longitude' => -61.5),
+ 'Porto-Novo' => array('latitude' => 6.4833333, 'longitude' => 2.6166667),
+ 'Prague' => array('latitude' => 50.0833333, 'longitude' => 14.4666667),
+ 'Praia' => array('latitude' => 14.9166667, 'longitude' => -23.5166667),
+ 'Pretoria' => array('latitude' => -25.7069444, 'longitude' => 28.2294444),
+ 'Pyongyang' => array('latitude' => 39.0194444, 'longitude' => 125.7547222),
+ 'Quito' => array('latitude' => -0.2166667, 'longitude' => -78.5),
+ 'Rabat' => array('latitude' => 34.0252778, 'longitude' => -6.8361111),
+ 'Reykjavik' => array('latitude' => 64.15, 'longitude' => -21.95),
+ 'Riga' => array('latitude' => 56.95, 'longitude' => 24.1),
+ 'Rio de Janero' => array('latitude' => -22.9, 'longitude' => -43.2333333),
+ 'Road Town' => array('latitude' => 18.4166667, 'longitude' => -64.6166667),
+ 'Rome' => array('latitude' => 41.9, 'longitude' => 12.4833333),
+ 'Roseau' => array('latitude' => 15.3, 'longitude' => -61.4),
+ 'Rotterdam' => array('latitude' => 51.9166667, 'longitude' => 4.5),
+ 'Salvador' => array('latitude' => -12.9833333, 'longitude' => -38.5166667),
+ 'San José' => array('latitude' => 9.9333333, 'longitude' => -84.0833333),
+ 'San Juan' => array('latitude' => 18.46833, 'longitude' => -66.10611),
+ 'San Marino' => array('latitude' => 43.5333333, 'longitude' => 12.9666667),
+ 'San Salvador' => array('latitude' => 13.7086111, 'longitude' => -89.2030556),
+ 'Sanaá' => array('latitude' => 15.3547222, 'longitude' => 44.2066667),
+ 'Santa Cruz' => array('latitude' => -17.8, 'longitude' => -63.1666667),
+ 'Santiago' => array('latitude' => -33.45, 'longitude' => -70.6666667),
+ 'Santo Domingo' => array('latitude' => 18.4666667, 'longitude' => -69.9),
+ 'Sao Paulo' => array('latitude' => -23.5333333, 'longitude' => -46.6166667),
+ 'Sarajevo' => array('latitude' => 43.85, 'longitude' => 18.3833333),
+ 'Seoul' => array('latitude' => 37.5663889, 'longitude' => 126.9997222),
+ 'Shanghai' => array('latitude' => 31.2222222, 'longitude' => 121.4580556),
+ 'Sydney' => array('latitude' => -33.8833333, 'longitude' => 151.2166667),
+ 'Singapore' => array('latitude' => 1.2930556, 'longitude' => 103.8558333),
+ 'Skopje' => array('latitude' => 42, 'longitude' => 21.4333333),
+ 'Sofia' => array('latitude' => 42.6833333, 'longitude' => 23.3166667),
+ 'St. George´s' => array('latitude' => 12.05, 'longitude' => -61.75),
+ 'St. John´s' => array('latitude' => 17.1166667, 'longitude' => -61.85),
+ 'Stanley' => array('latitude' => -51.7, 'longitude' => -57.85),
+ 'Stockholm' => array('latitude' => 59.3333333, 'longitude' => 18.05),
+ 'Suva' => array('latitude' => -18.1333333, 'longitude' => 178.4166667),
+ 'Taipei' => array('latitude' => 25.0166667, 'longitude' => 121.45),
+ 'Tallinn' => array('latitude' => 59.4338889, 'longitude' => 24.7280556),
+ 'Tashkent' => array('latitude' => 41.3166667, 'longitude' => 69.25),
+ 'Tbilisi' => array('latitude' => 41.725, 'longitude' => 44.7908333),
+ 'Tegucigalpa' => array('latitude' => 14.1, 'longitude' => -87.2166667),
+ 'Tehran' => array('latitude' => 35.6719444, 'longitude' => 51.4244444),
+ 'The Hague' => array('latitude' => 52.0833333, 'longitude' => 4.3),
+ 'Thimphu' => array('latitude' => 27.4833333, 'longitude' => 89.6),
+ 'Tirana' => array('latitude' => 41.3275, 'longitude' => 19.8188889),
+ 'Tiraspol' => array('latitude' => 46.8402778, 'longitude' => 29.6433333),
+ 'Tokyo' => array('latitude' => 35.685, 'longitude' => 139.7513889),
+ 'Toronto' => array('latitude' => 43.6666667, 'longitude' => -79.4166667),
+ 'Tórshavn' => array('latitude' => 62.0166667, 'longitude' => -6.7666667),
+ 'Tripoli' => array('latitude' => 32.8925, 'longitude' => 13.18),
+ 'Tunis' => array('latitude' => 36.8027778, 'longitude' => 10.1797222),
+ 'Ulaanbaatar' => array('latitude' => 47.9166667, 'longitude' => 106.9166667),
+ 'Vaduz' => array('latitude' => 47.1333333, 'longitude' => 9.5166667),
+ 'Valletta' => array('latitude' => 35.8997222, 'longitude' => 14.5147222),
+ 'Valparaiso' => array('latitude' => -33.0477778, 'longitude' => -71.6011111),
+ 'Vancouver' => array('latitude' => 49.25, 'longitude' => -123.1333333),
+ 'Vatican City' => array('latitude' => 41.9, 'longitude' => 12.4833333),
+ 'Victoria' => array('latitude' => -4.6166667, 'longitude' => 55.45),
+ 'Vienna' => array('latitude' => 48.2, 'longitude' => 16.3666667),
+ 'Vientaine' => array('latitude' => 17.9666667, 'longitude' => 102.6),
+ 'Vilnius' => array('latitude' => 54.6833333, 'longitude' => 25.3166667),
+ 'Warsaw' => array('latitude' => 52.25, 'longitude' => 21),
+ 'Washington dc' => array('latitude' => 38.895, 'longitude' => -77.03667),
+ 'Wellington' => array('latitude' => -41.3, 'longitude' => 174.7833333),
+ 'Willemstad' => array('latitude' => 12.1, 'longitude' => -68.9166667),
+ 'Windhoek' => array('latitude' => -22.57, 'longitude' => 17.0836111),
+ 'Yamoussoukro' => array('latitude' => 6.8166667, 'longitude' => -5.2833333),
+ 'Yaoundé' => array('latitude' => 3.8666667, 'longitude' => 11.5166667),
+ 'Yerevan' => array('latitude' => 40.1811111, 'longitude' => 44.5136111),
+ 'Zürich' => array('latitude' => 47.3666667, 'longitude' => 8.55),
+ 'Zagreb' => array('latitude' => 45.8, 'longitude' => 16)
+ );
+
+ /**
+ * Returns the location from the selected city
+ *
+ * @param string $city City to get location for
+ * @param string $horizon Horizon to use :
+ * default: effective
+ * others are civil, nautic, astronomic
+ * @return array
+ * @throws Zend_Date_Exception When city is unknown
+ */
+ public static function City($city, $horizon = false)
+ {
+ foreach (self::$cities as $key => $value) {
+ if (strtolower($key) === strtolower($city)) {
+ $return = $value;
+ $return['horizon'] = $horizon;
+ return $return;
+ }
+ }
+ throw new Zend_Date_Exception('unknown city');
+ }
+
+ /**
+ * Return a list with all known cities
+ *
+ * @return array
+ */
+ public static function getCityList()
+ {
+ return array_keys(self::$cities);
+ }
+}
diff --git a/library/vendor/Zend/Date/DateObject.php b/library/vendor/Zend/Date/DateObject.php
new file mode 100644
index 0000000..5c01d15
--- /dev/null
+++ b/library/vendor/Zend/Date/DateObject.php
@@ -0,0 +1,1094 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Date
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Date
+ * @subpackage Zend_Date_DateObject
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Date_DateObject {
+
+ /**
+ * UNIX Timestamp
+ */
+ private $_unixTimestamp;
+ protected static $_cache = null;
+ protected static $_cacheTags = false;
+ protected static $_defaultOffset = 0;
+
+ /**
+ * active timezone
+ */
+ private $_timezone = 'UTC';
+ private $_offset = 0;
+ private $_syncronised = 0;
+
+ // turn off DST correction if UTC or GMT
+ protected $_dst = true;
+
+ /**
+ * Table of Monthdays
+ */
+ private static $_monthTable = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
+
+ /**
+ * Table of Years
+ */
+ private static $_yearTable = 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);
+
+ /**
+ * Set this object to have a new UNIX timestamp.
+ *
+ * @param string|integer $timestamp OPTIONAL timestamp; defaults to local time using time()
+ * @return string|integer old timestamp
+ * @throws Zend_Date_Exception
+ */
+ protected function setUnixTimestamp($timestamp = null)
+ {
+ $old = $this->_unixTimestamp;
+
+ if (is_numeric($timestamp)) {
+ $this->_unixTimestamp = $timestamp;
+ } else if ($timestamp === null) {
+ $this->_unixTimestamp = time();
+ } else {
+ throw new Zend_Date_Exception('\'' . $timestamp . '\' is not a valid UNIX timestamp', 0, null, $timestamp);
+ }
+
+ return $old;
+ }
+
+ /**
+ * Returns this object's UNIX timestamp
+ * A timestamp greater then the integer range will be returned as string
+ * This function does not return the timestamp as object. Use copy() instead.
+ *
+ * @return integer|string timestamp
+ */
+ protected function getUnixTimestamp()
+ {
+ if ($this->_unixTimestamp === intval($this->_unixTimestamp)) {
+ return (int) $this->_unixTimestamp;
+ } else {
+ return (string) $this->_unixTimestamp;
+ }
+ }
+
+ /**
+ * Internal function.
+ * Returns time(). This method exists to allow unit tests to work-around methods that might otherwise
+ * be hard-coded to use time(). For example, this makes it possible to test isYesterday() in Date.php.
+ *
+ * @param integer $sync OPTIONAL time syncronisation value
+ * @return integer timestamp
+ */
+ protected function _getTime($sync = null)
+ {
+ if ($sync !== null) {
+ $this->_syncronised = round($sync);
+ }
+ return (time() + $this->_syncronised);
+ }
+
+ /**
+ * Internal mktime function used by Zend_Date.
+ * The timestamp returned by mktime() can exceed the precision of traditional UNIX timestamps,
+ * by allowing PHP to auto-convert to using a float value.
+ *
+ * Returns a timestamp relative to 1970/01/01 00:00:00 GMT/UTC.
+ * DST (Summer/Winter) is depriciated since php 5.1.0.
+ * Year has to be 4 digits otherwise it would be recognised as
+ * year 70 AD instead of 1970 AD as expected !!
+ *
+ * @param integer $hour
+ * @param integer $minute
+ * @param integer $second
+ * @param integer $month
+ * @param integer $day
+ * @param integer $year
+ * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date
+ * @return integer|float timestamp (number of seconds elapsed relative to 1970/01/01 00:00:00 GMT/UTC)
+ */
+ protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false)
+ {
+ // complete date but in 32bit timestamp - use PHP internal
+ if ((1901 < $year) and ($year < 2038)) {
+
+ $oldzone = @date_default_timezone_get();
+ // Timezone also includes DST settings, therefor substracting the GMT offset is not enough
+ // We have to set the correct timezone to get the right value
+ if (($this->_timezone != $oldzone) and ($gmt === false)) {
+ date_default_timezone_set($this->_timezone);
+ }
+ $result = ($gmt) ? @gmmktime($hour, $minute, $second, $month, $day, $year)
+ : @mktime($hour, $minute, $second, $month, $day, $year);
+ date_default_timezone_set($oldzone);
+
+ return $result;
+ }
+
+ if ($gmt !== true) {
+ $second += $this->_offset;
+ }
+
+ if (isset(self::$_cache)) {
+ $id = strtr('Zend_DateObject_mkTime_' . $this->_offset . '_' . $year.$month.$day.'_'.$hour.$minute.$second . '_'.(int)$gmt, '-','_');
+ if ($result = self::$_cache->load($id)) {
+ return unserialize($result);
+ }
+ }
+
+ // date to integer
+ $day = intval($day);
+ $month = intval($month);
+ $year = intval($year);
+
+ // correct months > 12 and months < 1
+ if ($month > 12) {
+ $overlap = floor($month / 12);
+ $year += $overlap;
+ $month -= $overlap * 12;
+ } else {
+ $overlap = ceil((1 - $month) / 12);
+ $year -= $overlap;
+ $month += $overlap * 12;
+ }
+
+ $date = 0;
+ if ($year >= 1970) {
+
+ // Date is after UNIX epoch
+ // go through leapyears
+ // add months from latest given year
+ for ($count = 1970; $count <= $year; $count++) {
+
+ $leapyear = self::isYearLeapYear($count);
+ if ($count < $year) {
+
+ $date += 365;
+ if ($leapyear === true) {
+ $date++;
+ }
+
+ } else {
+
+ for ($mcount = 0; $mcount < ($month - 1); $mcount++) {
+ $date += self::$_monthTable[$mcount];
+ if (($leapyear === true) and ($mcount == 1)) {
+ $date++;
+ }
+
+ }
+ }
+ }
+
+ $date += $day - 1;
+ $date = (($date * 86400) + ($hour * 3600) + ($minute * 60) + $second);
+ } else {
+
+ // Date is before UNIX epoch
+ // go through leapyears
+ // add months from latest given year
+ for ($count = 1969; $count >= $year; $count--) {
+
+ $leapyear = self::isYearLeapYear($count);
+ if ($count > $year)
+ {
+ $date += 365;
+ if ($leapyear === true)
+ $date++;
+ } else {
+
+ for ($mcount = 11; $mcount > ($month - 1); $mcount--) {
+ $date += self::$_monthTable[$mcount];
+ if (($leapyear === true) and ($mcount == 2)) {
+ $date++;
+ }
+
+ }
+ }
+ }
+
+ $date += (self::$_monthTable[$month - 1] - $day);
+ $date = -(($date * 86400) + (86400 - (($hour * 3600) + ($minute * 60) + $second)));
+
+ // gregorian correction for 5.Oct.1582
+ if ($date < -12220185600) {
+ $date += 864000;
+ } else if ($date < -12219321600) {
+ $date = -12219321600;
+ }
+ }
+
+ if (isset(self::$_cache)) {
+ if (self::$_cacheTags) {
+ self::$_cache->save( serialize($date), $id, array('Zend_Date'));
+ } else {
+ self::$_cache->save( serialize($date), $id);
+ }
+ }
+
+ return $date;
+ }
+
+ /**
+ * Returns true, if given $year is a leap year.
+ *
+ * @param integer $year
+ * @return boolean true, if year is leap year
+ */
+ protected static function isYearLeapYear($year)
+ {
+ // all leapyears can be divided through 4
+ if (($year % 4) != 0) {
+ return false;
+ }
+
+ // all leapyears can be divided through 400
+ if ($year % 400 == 0) {
+ return true;
+ } else if (($year > 1582) and ($year % 100 == 0)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Internal mktime function used by Zend_Date for handling 64bit timestamps.
+ *
+ * Returns a formatted date for a given timestamp.
+ *
+ * @param string $format format for output
+ * @param mixed $timestamp
+ * @param boolean $gmt OPTIONAL true = other arguments are for UTC time, false = arguments are for local time/date
+ * @return string
+ */
+ protected function date($format, $timestamp = null, $gmt = false)
+ {
+ $oldzone = @date_default_timezone_get();
+ if ($this->_timezone != $oldzone) {
+ date_default_timezone_set($this->_timezone);
+ }
+
+ if ($timestamp === null) {
+ $result = ($gmt) ? @gmdate($format) : @date($format);
+ date_default_timezone_set($oldzone);
+ return $result;
+ }
+
+ if (abs($timestamp) <= 0x7FFFFFFF) {
+ // See ZF-11992
+ // "o" will sometimes resolve to the previous year (see
+ // http://php.net/date ; it's part of the ISO 8601
+ // standard). However, this is not desired, so replacing
+ // all occurrences of "o" not preceded by a backslash
+ // with "Y"
+ $format = preg_replace('/(?<!\\\\)o/', 'Y', $format);
+ $result = ($gmt) ? @gmdate($format, $timestamp) : @date($format, $timestamp);
+ date_default_timezone_set($oldzone);
+ return $result;
+ }
+
+ $jump = false;
+ $origstamp = $timestamp;
+ if (isset(self::$_cache)) {
+ $idstamp = strtr('Zend_DateObject_date_' . $this->_offset . '_'. $timestamp . '_'.(int)$gmt, '-','_');
+ if ($result2 = self::$_cache->load($idstamp)) {
+ $timestamp = unserialize($result2);
+ $jump = true;
+ }
+ }
+
+ // check on false or null alone fails
+ if (empty($gmt) and empty($jump)) {
+ $tempstamp = $timestamp;
+ if ($tempstamp > 0) {
+ while (abs($tempstamp) > 0x7FFFFFFF) {
+ $tempstamp -= (86400 * 23376);
+ }
+
+ $dst = date("I", $tempstamp);
+ if ($dst === 1) {
+ $timestamp += 3600;
+ }
+
+ $temp = date('Z', $tempstamp);
+ $timestamp += $temp;
+ }
+
+ if (isset(self::$_cache)) {
+ if (self::$_cacheTags) {
+ self::$_cache->save( serialize($timestamp), $idstamp, array('Zend_Date'));
+ } else {
+ self::$_cache->save( serialize($timestamp), $idstamp);
+ }
+ }
+ }
+
+ if (($timestamp < 0) and ($gmt !== true)) {
+ $timestamp -= $this->_offset;
+ }
+
+ date_default_timezone_set($oldzone);
+ $date = $this->getDateParts($timestamp, true);
+ $length = strlen($format);
+ $output = '';
+
+ for ($i = 0; $i < $length; $i++) {
+ switch($format[$i]) {
+ // day formats
+ case 'd': // day of month, 2 digits, with leading zero, 01 - 31
+ $output .= (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']);
+ break;
+
+ case 'D': // day of week, 3 letters, Mon - Sun
+ $output .= date('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday'])));
+ break;
+
+ case 'j': // day of month, without leading zero, 1 - 31
+ $output .= $date['mday'];
+ break;
+
+ case 'l': // day of week, full string name, Sunday - Saturday
+ $output .= date('l', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday'])));
+ break;
+
+ case 'N': // ISO 8601 numeric day of week, 1 - 7
+ $day = self::dayOfWeek($date['year'], $date['mon'], $date['mday']);
+ if ($day == 0) {
+ $day = 7;
+ }
+ $output .= $day;
+ break;
+
+ case 'S': // english suffix for day of month, st nd rd th
+ if (($date['mday'] % 10) == 1) {
+ $output .= 'st';
+ } else if ((($date['mday'] % 10) == 2) and ($date['mday'] != 12)) {
+ $output .= 'nd';
+ } else if (($date['mday'] % 10) == 3) {
+ $output .= 'rd';
+ } else {
+ $output .= 'th';
+ }
+ break;
+
+ case 'w': // numeric day of week, 0 - 6
+ $output .= self::dayOfWeek($date['year'], $date['mon'], $date['mday']);
+ break;
+
+ case 'z': // day of year, 0 - 365
+ $output .= $date['yday'];
+ break;
+
+
+ // week formats
+ case 'W': // ISO 8601, week number of year
+ $output .= $this->weekNumber($date['year'], $date['mon'], $date['mday']);
+ break;
+
+
+ // month formats
+ case 'F': // string month name, january - december
+ $output .= date('F', mktime(0, 0, 0, $date['mon'], 2, 1971));
+ break;
+
+ case 'm': // number of month, with leading zeros, 01 - 12
+ $output .= (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']);
+ break;
+
+ case 'M': // 3 letter month name, Jan - Dec
+ $output .= date('M',mktime(0, 0, 0, $date['mon'], 2, 1971));
+ break;
+
+ case 'n': // number of month, without leading zeros, 1 - 12
+ $output .= $date['mon'];
+ break;
+
+ case 't': // number of day in month
+ $output .= self::$_monthTable[$date['mon'] - 1];
+ break;
+
+
+ // year formats
+ case 'L': // is leap year ?
+ $output .= (self::isYearLeapYear($date['year'])) ? '1' : '0';
+ break;
+
+ case 'o': // ISO 8601 year number
+ $week = $this->weekNumber($date['year'], $date['mon'], $date['mday']);
+ if (($week > 50) and ($date['mon'] == 1)) {
+ $output .= ($date['year'] - 1);
+ } else {
+ $output .= $date['year'];
+ }
+ break;
+
+ case 'Y': // year number, 4 digits
+ $output .= $date['year'];
+ break;
+
+ case 'y': // year number, 2 digits
+ $output .= substr($date['year'], strlen($date['year']) - 2, 2);
+ break;
+
+
+ // time formats
+ case 'a': // lower case am/pm
+ $output .= (($date['hours'] >= 12) ? 'pm' : 'am');
+ break;
+
+ case 'A': // upper case am/pm
+ $output .= (($date['hours'] >= 12) ? 'PM' : 'AM');
+ break;
+
+ case 'B': // swatch internet time
+ $dayseconds = ($date['hours'] * 3600) + ($date['minutes'] * 60) + $date['seconds'];
+ if ($gmt === true) {
+ $dayseconds += 3600;
+ }
+ $output .= (int) (($dayseconds % 86400) / 86.4);
+ break;
+
+ case 'g': // hours without leading zeros, 12h format
+ if ($date['hours'] > 12) {
+ $hour = $date['hours'] - 12;
+ } else {
+ if ($date['hours'] == 0) {
+ $hour = '12';
+ } else {
+ $hour = $date['hours'];
+ }
+ }
+ $output .= $hour;
+ break;
+
+ case 'G': // hours without leading zeros, 24h format
+ $output .= $date['hours'];
+ break;
+
+ case 'h': // hours with leading zeros, 12h format
+ if ($date['hours'] > 12) {
+ $hour = $date['hours'] - 12;
+ } else {
+ if ($date['hours'] == 0) {
+ $hour = '12';
+ } else {
+ $hour = $date['hours'];
+ }
+ }
+ $output .= (($hour < 10) ? '0'.$hour : $hour);
+ break;
+
+ case 'H': // hours with leading zeros, 24h format
+ $output .= (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']);
+ break;
+
+ case 'i': // minutes with leading zeros
+ $output .= (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']);
+ break;
+
+ case 's': // seconds with leading zeros
+ $output .= (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']);
+ break;
+
+
+ // timezone formats
+ case 'e': // timezone identifier
+ if ($gmt === true) {
+ $output .= gmdate('e', mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], 2000));
+ } else {
+ $output .= date('e', mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], 2000));
+ }
+ break;
+
+ case 'I': // daylight saving time or not
+ if ($gmt === true) {
+ $output .= gmdate('I', mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], 2000));
+ } else {
+ $output .= date('I', mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], 2000));
+ }
+ break;
+
+ case 'O': // difference to GMT in hours
+ $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset();
+ $output .= sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36);
+ break;
+
+ case 'P': // difference to GMT with colon
+ $gmtstr = ($gmt === true) ? 0 : $this->getGmtOffset();
+ $gmtstr = sprintf('%s%04d', ($gmtstr <= 0) ? '+' : '-', abs($gmtstr) / 36);
+ $output = $output . substr($gmtstr, 0, 3) . ':' . substr($gmtstr, 3);
+ break;
+
+ case 'T': // timezone settings
+ if ($gmt === true) {
+ $output .= gmdate('T', mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], 2000));
+ } else {
+ $output .= date('T', mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], 2000));
+ }
+ break;
+
+ case 'Z': // timezone offset in seconds
+ $output .= ($gmt === true) ? 0 : -$this->getGmtOffset();
+ break;
+
+
+ // complete time formats
+ case 'c': // ISO 8601 date format
+ $difference = $this->getGmtOffset();
+ $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36);
+ $difference = substr($difference, 0, 3) . ':' . substr($difference, 3);
+ $output .= $date['year'] . '-'
+ . (($date['mon'] < 10) ? '0' . $date['mon'] : $date['mon']) . '-'
+ . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . 'T'
+ . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':'
+ . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':'
+ . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds'])
+ . $difference;
+ break;
+
+ case 'r': // RFC 2822 date format
+ $difference = $this->getGmtOffset();
+ $difference = sprintf('%s%04d', ($difference <= 0) ? '+' : '-', abs($difference) / 36);
+ $output .= gmdate('D', 86400 * (3 + self::dayOfWeek($date['year'], $date['mon'], $date['mday']))) . ', '
+ . (($date['mday'] < 10) ? '0' . $date['mday'] : $date['mday']) . ' '
+ . date('M', mktime(0, 0, 0, $date['mon'], 2, 1971)) . ' '
+ . $date['year'] . ' '
+ . (($date['hours'] < 10) ? '0' . $date['hours'] : $date['hours']) . ':'
+ . (($date['minutes'] < 10) ? '0' . $date['minutes'] : $date['minutes']) . ':'
+ . (($date['seconds'] < 10) ? '0' . $date['seconds'] : $date['seconds']) . ' '
+ . $difference;
+ break;
+
+ case 'U': // Unix timestamp
+ $output .= $origstamp;
+ break;
+
+
+ // special formats
+ case "\\": // next letter to print with no format
+ $i++;
+ if ($i < $length) {
+ $output .= $format[$i];
+ }
+ break;
+
+ default: // letter is no format so add it direct
+ $output .= $format[$i];
+ break;
+ }
+ }
+
+ return (string) $output;
+ }
+
+ /**
+ * Returns the day of week for a Gregorian calendar date.
+ * 0 = sunday, 6 = saturday
+ *
+ * @param integer $year
+ * @param integer $month
+ * @param integer $day
+ * @return integer dayOfWeek
+ */
+ protected static function dayOfWeek($year, $month, $day)
+ {
+ if ((1901 < $year) and ($year < 2038)) {
+ return (int) date('w', mktime(0, 0, 0, $month, $day, $year));
+ }
+
+ // gregorian correction
+ $correction = 0;
+ if (($year < 1582) or (($year == 1582) and (($month < 10) or (($month == 10) && ($day < 15))))) {
+ $correction = 3;
+ }
+
+ if ($month > 2) {
+ $month -= 2;
+ } else {
+ $month += 10;
+ $year--;
+ }
+
+ $day = floor((13 * $month - 1) / 5) + $day + ($year % 100) + floor(($year % 100) / 4);
+ $day += floor(($year / 100) / 4) - 2 * floor($year / 100) + 77 + $correction;
+
+ return (int) ($day - 7 * floor($day / 7));
+ }
+
+ /**
+ * Internal getDateParts function for handling 64bit timestamps, similar to:
+ * http://www.php.net/getdate
+ *
+ * Returns an array of date parts for $timestamp, relative to 1970/01/01 00:00:00 GMT/UTC.
+ *
+ * $fast specifies ALL date parts should be returned (slower)
+ * Default is false, and excludes $dayofweek, weekday, month and timestamp from parts returned.
+ *
+ * @param mixed $timestamp
+ * @param boolean $fast OPTIONAL defaults to fast (false), resulting in fewer date parts
+ * @return array
+ */
+ protected function getDateParts($timestamp = null, $fast = null)
+ {
+
+ // actual timestamp
+ if (!is_numeric($timestamp)) {
+ return getdate();
+ }
+
+ // 32bit timestamp
+ if (abs($timestamp) <= 0x7FFFFFFF) {
+ return @getdate((int) $timestamp);
+ }
+
+ if (isset(self::$_cache)) {
+ $id = strtr('Zend_DateObject_getDateParts_' . $timestamp.'_'.(int)$fast, '-','_');
+ if ($result = self::$_cache->load($id)) {
+ return unserialize($result);
+ }
+ }
+
+ $otimestamp = $timestamp;
+ $numday = 0;
+ $month = 0;
+ // gregorian correction
+ if ($timestamp < -12219321600) {
+ $timestamp -= 864000;
+ }
+
+ // timestamp lower 0
+ if ($timestamp < 0) {
+ $sec = 0;
+ $act = 1970;
+
+ // iterate through 10 years table, increasing speed
+ foreach(self::$_yearTable as $year => $seconds) {
+ if ($timestamp >= $seconds) {
+ $i = $act;
+ break;
+ }
+ $sec = $seconds;
+ $act = $year;
+ }
+
+ $timestamp -= $sec;
+ if (!isset($i)) {
+ $i = $act;
+ }
+
+ // iterate the max last 10 years
+ do {
+ --$i;
+ $day = $timestamp;
+
+ $timestamp += 31536000;
+ $leapyear = self::isYearLeapYear($i);
+ if ($leapyear === true) {
+ $timestamp += 86400;
+ }
+
+ if ($timestamp >= 0) {
+ $year = $i;
+ break;
+ }
+ } while ($timestamp < 0);
+
+ $secondsPerYear = 86400 * ($leapyear ? 366 : 365) + $day;
+
+ $timestamp = $day;
+ // iterate through months
+ for ($i = 12; --$i >= 0;) {
+ $day = $timestamp;
+
+ $timestamp += self::$_monthTable[$i] * 86400;
+ if (($leapyear === true) and ($i == 1)) {
+ $timestamp += 86400;
+ }
+
+ if ($timestamp >= 0) {
+ $month = $i;
+ $numday = self::$_monthTable[$i];
+ if (($leapyear === true) and ($i == 1)) {
+ ++$numday;
+ }
+ break;
+ }
+ }
+
+ $timestamp = $day;
+ $numberdays = $numday + ceil(($timestamp + 1) / 86400);
+
+ $timestamp += ($numday - $numberdays + 1) * 86400;
+ $hours = floor($timestamp / 3600);
+ } else {
+
+ // iterate through years
+ for ($i = 1970;;$i++) {
+ $day = $timestamp;
+
+ $timestamp -= 31536000;
+ $leapyear = self::isYearLeapYear($i);
+ if ($leapyear === true) {
+ $timestamp -= 86400;
+ }
+
+ if ($timestamp < 0) {
+ $year = $i;
+ break;
+ }
+ }
+
+ $secondsPerYear = $day;
+
+ $timestamp = $day;
+ // iterate through months
+ for ($i = 0; $i <= 11; $i++) {
+ $day = $timestamp;
+ $timestamp -= self::$_monthTable[$i] * 86400;
+
+ if (($leapyear === true) and ($i == 1)) {
+ $timestamp -= 86400;
+ }
+
+ if ($timestamp < 0) {
+ $month = $i;
+ $numday = self::$_monthTable[$i];
+ if (($leapyear === true) and ($i == 1)) {
+ ++$numday;
+ }
+ break;
+ }
+ }
+
+ $timestamp = $day;
+ $numberdays = ceil(($timestamp + 1) / 86400);
+ $timestamp = $timestamp - ($numberdays - 1) * 86400;
+ $hours = floor($timestamp / 3600);
+ }
+
+ $timestamp -= $hours * 3600;
+
+ $month += 1;
+ $minutes = floor($timestamp / 60);
+ $seconds = $timestamp - $minutes * 60;
+
+ if ($fast === true) {
+ $array = array(
+ 'seconds' => $seconds,
+ 'minutes' => $minutes,
+ 'hours' => $hours,
+ 'mday' => $numberdays,
+ 'mon' => $month,
+ 'year' => $year,
+ 'yday' => floor($secondsPerYear / 86400),
+ );
+ } else {
+
+ $dayofweek = self::dayOfWeek($year, $month, $numberdays);
+ $array = array(
+ 'seconds' => $seconds,
+ 'minutes' => $minutes,
+ 'hours' => $hours,
+ 'mday' => $numberdays,
+ 'wday' => $dayofweek,
+ 'mon' => $month,
+ 'year' => $year,
+ 'yday' => floor($secondsPerYear / 86400),
+ 'weekday' => gmdate('l', 86400 * (3 + $dayofweek)),
+ 'month' => gmdate('F', mktime(0, 0, 0, $month, 1, 1971)),
+ 0 => $otimestamp
+ );
+ }
+
+ if (isset(self::$_cache)) {
+ if (self::$_cacheTags) {
+ self::$_cache->save( serialize($array), $id, array('Zend_Date'));
+ } else {
+ self::$_cache->save( serialize($array), $id);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Internal getWeekNumber function for handling 64bit timestamps
+ *
+ * Returns the ISO 8601 week number of a given date
+ *
+ * @param integer $year
+ * @param integer $month
+ * @param integer $day
+ * @return integer
+ */
+ protected function weekNumber($year, $month, $day)
+ {
+ if ((1901 < $year) and ($year < 2038)) {
+ return (int) date('W', mktime(0, 0, 0, $month, $day, $year));
+ }
+
+ $dayofweek = self::dayOfWeek($year, $month, $day);
+ $firstday = self::dayOfWeek($year, 1, 1);
+ if (($month == 1) and (($firstday < 1) or ($firstday > 4)) and ($day < 4)) {
+ $firstday = self::dayOfWeek($year - 1, 1, 1);
+ $month = 12;
+ $day = 31;
+
+ } else if (($month == 12) and ((self::dayOfWeek($year + 1, 1, 1) < 5) and
+ (self::dayOfWeek($year + 1, 1, 1) > 0))) {
+ return 1;
+ }
+
+ return intval (((self::dayOfWeek($year, 1, 1) < 5) and (self::dayOfWeek($year, 1, 1) > 0)) +
+ 4 * ($month - 1) + (2 * ($month - 1) + ($day - 1) + $firstday - $dayofweek + 6) * 36 / 256);
+ }
+
+ /**
+ * Internal _range function
+ * Sets the value $a to be in the range of [0, $b]
+ *
+ * @param float $a - value to correct
+ * @param float $b - maximum range to set
+ */
+ private function _range($a, $b) {
+ while ($a < 0) {
+ $a += $b;
+ }
+ while ($a >= $b) {
+ $a -= $b;
+ }
+ return $a;
+ }
+
+ /**
+ * Calculates the sunrise or sunset based on a location
+ *
+ * @param array $location Location for calculation MUST include 'latitude', 'longitude', 'horizon'
+ * @param bool $horizon true: sunrise; false: sunset
+ * @return mixed - false: midnight sun, integer:
+ */
+ protected function calcSun($location, $horizon, $rise = false)
+ {
+ // timestamp within 32bit
+ if (abs($this->_unixTimestamp) <= 0x7FFFFFFF) {
+ if ($rise === false) {
+ return date_sunset($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'],
+ $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600);
+ }
+ return date_sunrise($this->_unixTimestamp, SUNFUNCS_RET_TIMESTAMP, $location['latitude'],
+ $location['longitude'], 90 + $horizon, $this->getGmtOffset() / 3600);
+ }
+
+ // self calculation - timestamp bigger than 32bit
+ // fix circle values
+ $quarterCircle = 0.5 * M_PI;
+ $halfCircle = M_PI;
+ $threeQuarterCircle = 1.5 * M_PI;
+ $fullCircle = 2 * M_PI;
+
+ // radiant conversion for coordinates
+ $radLatitude = $location['latitude'] * $halfCircle / 180;
+ $radLongitude = $location['longitude'] * $halfCircle / 180;
+
+ // get solar coordinates
+ $tmpRise = $rise ? $quarterCircle : $threeQuarterCircle;
+ $radDay = $this->date('z',$this->_unixTimestamp) + ($tmpRise - $radLongitude) / $fullCircle;
+
+ // solar anomoly and longitude
+ $solAnomoly = $radDay * 0.017202 - 0.0574039;
+ $solLongitude = $solAnomoly + 0.0334405 * sin($solAnomoly);
+ $solLongitude += 4.93289 + 3.49066E-4 * sin(2 * $solAnomoly);
+
+ // get quadrant
+ $solLongitude = $this->_range($solLongitude, $fullCircle);
+
+ if (($solLongitude / $quarterCircle) - intval($solLongitude / $quarterCircle) == 0) {
+ $solLongitude += 4.84814E-6;
+ }
+
+ // solar ascension
+ $solAscension = sin($solLongitude) / cos($solLongitude);
+ $solAscension = atan2(0.91746 * $solAscension, 1);
+
+ // adjust quadrant
+ if ($solLongitude > $threeQuarterCircle) {
+ $solAscension += $fullCircle;
+ } else if ($solLongitude > $quarterCircle) {
+ $solAscension += $halfCircle;
+ }
+
+ // solar declination
+ $solDeclination = 0.39782 * sin($solLongitude);
+ $solDeclination /= sqrt(-$solDeclination * $solDeclination + 1);
+ $solDeclination = atan2($solDeclination, 1);
+
+ $solHorizon = $horizon - sin($solDeclination) * sin($radLatitude);
+ $solHorizon /= cos($solDeclination) * cos($radLatitude);
+
+ // midnight sun, always night
+ if (abs($solHorizon) > 1) {
+ return false;
+ }
+
+ $solHorizon /= sqrt(-$solHorizon * $solHorizon + 1);
+ $solHorizon = $quarterCircle - atan2($solHorizon, 1);
+
+ if ($rise) {
+ $solHorizon = $fullCircle - $solHorizon;
+ }
+
+ // time calculation
+ $localTime = $solHorizon + $solAscension - 0.0172028 * $radDay - 1.73364;
+ $universalTime = $localTime - $radLongitude;
+
+ // determinate quadrant
+ $universalTime = $this->_range($universalTime, $fullCircle);
+
+ // radiant to hours
+ $universalTime *= 24 / $fullCircle;
+
+ // convert to time
+ $hour = intval($universalTime);
+ $universalTime = ($universalTime - $hour) * 60;
+ $min = intval($universalTime);
+ $universalTime = ($universalTime - $min) * 60;
+ $sec = intval($universalTime);
+
+ return $this->mktime($hour, $min, $sec, $this->date('m', $this->_unixTimestamp),
+ $this->date('j', $this->_unixTimestamp), $this->date('Y', $this->_unixTimestamp),
+ -1, true);
+ }
+
+ /**
+ * Sets a new timezone for calculation of $this object's gmt offset.
+ * For a list of supported timezones look here: http://php.net/timezones
+ * If no timezone can be detected or the given timezone is wrong UTC will be set.
+ *
+ * @param string $zone OPTIONAL timezone for date calculation; defaults to date_default_timezone_get()
+ * @return Zend_Date_DateObject Provides fluent interface
+ * @throws Zend_Date_Exception
+ */
+ public function setTimezone($zone = null)
+ {
+ $oldzone = @date_default_timezone_get();
+ if ($zone === null) {
+ $zone = $oldzone;
+ }
+
+ // throw an error on false input, but only if the new date extension is available
+ if (function_exists('timezone_open')) {
+ if (!@timezone_open($zone)) {
+ throw new Zend_Date_Exception("timezone ($zone) is not a known timezone", 0, null, $zone);
+ }
+ }
+ // this can generate an error if the date extension is not available and a false timezone is given
+ $result = @date_default_timezone_set($zone);
+ if ($result === true) {
+ $this->_offset = mktime(0, 0, 0, 1, 2, 1970) - gmmktime(0, 0, 0, 1, 2, 1970);
+ $this->_timezone = $zone;
+ }
+ date_default_timezone_set($oldzone);
+
+ if (($zone == 'UTC') or ($zone == 'GMT')) {
+ $this->_dst = false;
+ } else {
+ $this->_dst = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the timezone of $this object.
+ * The timezone is initially set when the object is instantiated.
+ *
+ * @return string actual set timezone string
+ */
+ public function getTimezone()
+ {
+ return $this->_timezone;
+ }
+
+ /**
+ * Return the offset to GMT of $this object's timezone.
+ * The offset to GMT is initially set when the object is instantiated using the currently,
+ * in effect, default timezone for PHP functions.
+ *
+ * @return integer seconds difference between GMT timezone and timezone when object was instantiated
+ */
+ public function getGmtOffset()
+ {
+ $date = $this->getDateParts($this->getUnixTimestamp(), true);
+ $zone = @date_default_timezone_get();
+ $result = @date_default_timezone_set($this->_timezone);
+ if ($result === true) {
+ $offset = $this->mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], $date['year'], false)
+ - $this->mktime($date['hours'], $date['minutes'], $date['seconds'],
+ $date['mon'], $date['mday'], $date['year'], true);
+ }
+ date_default_timezone_set($zone);
+
+ return $offset;
+ }
+
+ /**
+ * Internal method to check if the given cache supports tags
+ *
+ * @param Zend_Cache $cache
+ */
+ protected static function _getTagSupportForCache()
+ {
+ $backend = self::$_cache->getBackend();
+ if ($backend instanceof Zend_Cache_Backend_ExtendedInterface) {
+ $cacheOptions = $backend->getCapabilities();
+ self::$_cacheTags = $cacheOptions['tags'];
+ } else {
+ self::$_cacheTags = false;
+ }
+
+ return self::$_cacheTags;
+ }
+}
diff --git a/library/vendor/Zend/Date/Exception.php b/library/vendor/Zend/Date/Exception.php
new file mode 100644
index 0000000..2585d0b
--- /dev/null
+++ b/library/vendor/Zend/Date/Exception.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Date
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Date
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Date_Exception extends Zend_Exception
+{
+ protected $operand = null;
+
+ public function __construct($message, $code = 0, $e = null, $op = null)
+ {
+ $this->operand = $op;
+ parent::__construct($message, $code, $e);
+ }
+
+ public function getOperand()
+ {
+ return $this->operand;
+ }
+}
diff --git a/library/vendor/Zend/Db.php b/library/vendor/Zend/Db.php
new file mode 100644
index 0000000..1712929
--- /dev/null
+++ b/library/vendor/Zend/Db.php
@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Class for connecting to SQL databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db
+{
+
+ /**
+ * Use the PROFILER constant in the config of a Zend_Db_Adapter.
+ */
+ const PROFILER = 'profiler';
+
+ /**
+ * Use the CASE_FOLDING constant in the config of a Zend_Db_Adapter.
+ */
+ const CASE_FOLDING = 'caseFolding';
+
+ /**
+ * Use the FETCH_MODE constant in the config of a Zend_Db_Adapter.
+ */
+ const FETCH_MODE = 'fetchMode';
+
+ /**
+ * Use the AUTO_QUOTE_IDENTIFIERS constant in the config of a Zend_Db_Adapter.
+ */
+ const AUTO_QUOTE_IDENTIFIERS = 'autoQuoteIdentifiers';
+
+ /**
+ * Use the ALLOW_SERIALIZATION constant in the config of a Zend_Db_Adapter.
+ */
+ const ALLOW_SERIALIZATION = 'allowSerialization';
+
+ /**
+ * Use the AUTO_RECONNECT_ON_UNSERIALIZE constant in the config of a Zend_Db_Adapter.
+ */
+ const AUTO_RECONNECT_ON_UNSERIALIZE = 'autoReconnectOnUnserialize';
+
+ /**
+ * Use the INT_TYPE, BIGINT_TYPE, and FLOAT_TYPE with the quote() method.
+ */
+ const INT_TYPE = 0;
+ const BIGINT_TYPE = 1;
+ const FLOAT_TYPE = 2;
+
+ /**
+ * PDO constant values discovered by this script result:
+ *
+ * $list = array(
+ * 'PARAM_BOOL', 'PARAM_NULL', 'PARAM_INT', 'PARAM_STR', 'PARAM_LOB',
+ * 'PARAM_STMT', 'PARAM_INPUT_OUTPUT', 'FETCH_LAZY', 'FETCH_ASSOC',
+ * 'FETCH_NUM', 'FETCH_BOTH', 'FETCH_OBJ', 'FETCH_BOUND',
+ * 'FETCH_COLUMN', 'FETCH_CLASS', 'FETCH_INTO', 'FETCH_FUNC',
+ * 'FETCH_GROUP', 'FETCH_UNIQUE', 'FETCH_CLASSTYPE', 'FETCH_SERIALIZE',
+ * 'FETCH_NAMED', 'ATTR_AUTOCOMMIT', 'ATTR_PREFETCH', 'ATTR_TIMEOUT',
+ * 'ATTR_ERRMODE', 'ATTR_SERVER_VERSION', 'ATTR_CLIENT_VERSION',
+ * 'ATTR_SERVER_INFO', 'ATTR_CONNECTION_STATUS', 'ATTR_CASE',
+ * 'ATTR_CURSOR_NAME', 'ATTR_CURSOR', 'ATTR_ORACLE_NULLS',
+ * 'ATTR_PERSISTENT', 'ATTR_STATEMENT_CLASS', 'ATTR_FETCH_TABLE_NAMES',
+ * 'ATTR_FETCH_CATALOG_NAMES', 'ATTR_DRIVER_NAME',
+ * 'ATTR_STRINGIFY_FETCHES', 'ATTR_MAX_COLUMN_LEN', 'ERRMODE_SILENT',
+ * 'ERRMODE_WARNING', 'ERRMODE_EXCEPTION', 'CASE_NATURAL',
+ * 'CASE_LOWER', 'CASE_UPPER', 'NULL_NATURAL', 'NULL_EMPTY_STRING',
+ * 'NULL_TO_STRING', 'ERR_NONE', 'FETCH_ORI_NEXT',
+ * 'FETCH_ORI_PRIOR', 'FETCH_ORI_FIRST', 'FETCH_ORI_LAST',
+ * 'FETCH_ORI_ABS', 'FETCH_ORI_REL', 'CURSOR_FWDONLY', 'CURSOR_SCROLL',
+ * 'ERR_CANT_MAP', 'ERR_SYNTAX', 'ERR_CONSTRAINT', 'ERR_NOT_FOUND',
+ * 'ERR_ALREADY_EXISTS', 'ERR_NOT_IMPLEMENTED', 'ERR_MISMATCH',
+ * 'ERR_TRUNCATED', 'ERR_DISCONNECTED', 'ERR_NO_PERM',
+ * );
+ *
+ * $const = array();
+ * foreach ($list as $name) {
+ * $const[$name] = constant("PDO::$name");
+ * }
+ * var_export($const);
+ */
+ const ATTR_AUTOCOMMIT = 0;
+ const ATTR_CASE = 8;
+ const ATTR_CLIENT_VERSION = 5;
+ const ATTR_CONNECTION_STATUS = 7;
+ const ATTR_CURSOR = 10;
+ const ATTR_CURSOR_NAME = 9;
+ const ATTR_DRIVER_NAME = 16;
+ const ATTR_ERRMODE = 3;
+ const ATTR_FETCH_CATALOG_NAMES = 15;
+ const ATTR_FETCH_TABLE_NAMES = 14;
+ const ATTR_MAX_COLUMN_LEN = 18;
+ const ATTR_ORACLE_NULLS = 11;
+ const ATTR_PERSISTENT = 12;
+ const ATTR_PREFETCH = 1;
+ const ATTR_SERVER_INFO = 6;
+ const ATTR_SERVER_VERSION = 4;
+ const ATTR_STATEMENT_CLASS = 13;
+ const ATTR_STRINGIFY_FETCHES = 17;
+ const ATTR_TIMEOUT = 2;
+ const CASE_LOWER = 2;
+ const CASE_NATURAL = 0;
+ const CASE_UPPER = 1;
+ const CURSOR_FWDONLY = 0;
+ const CURSOR_SCROLL = 1;
+ const ERR_ALREADY_EXISTS = NULL;
+ const ERR_CANT_MAP = NULL;
+ const ERR_CONSTRAINT = NULL;
+ const ERR_DISCONNECTED = NULL;
+ const ERR_MISMATCH = NULL;
+ const ERR_NO_PERM = NULL;
+ const ERR_NONE = '00000';
+ const ERR_NOT_FOUND = NULL;
+ const ERR_NOT_IMPLEMENTED = NULL;
+ const ERR_SYNTAX = NULL;
+ const ERR_TRUNCATED = NULL;
+ const ERRMODE_EXCEPTION = 2;
+ const ERRMODE_SILENT = 0;
+ const ERRMODE_WARNING = 1;
+ const FETCH_ASSOC = 2;
+ const FETCH_BOTH = 4;
+ const FETCH_BOUND = 6;
+ const FETCH_CLASS = 8;
+ const FETCH_CLASSTYPE = 262144;
+ const FETCH_COLUMN = 7;
+ const FETCH_FUNC = 10;
+ const FETCH_GROUP = 65536;
+ const FETCH_INTO = 9;
+ const FETCH_LAZY = 1;
+ const FETCH_NAMED = 11;
+ const FETCH_NUM = 3;
+ const FETCH_OBJ = 5;
+ const FETCH_ORI_ABS = 4;
+ const FETCH_ORI_FIRST = 2;
+ const FETCH_ORI_LAST = 3;
+ const FETCH_ORI_NEXT = 0;
+ const FETCH_ORI_PRIOR = 1;
+ const FETCH_ORI_REL = 5;
+ const FETCH_SERIALIZE = 524288;
+ const FETCH_UNIQUE = 196608;
+ const NULL_EMPTY_STRING = 1;
+ const NULL_NATURAL = 0;
+ const NULL_TO_STRING = NULL;
+ const PARAM_BOOL = 5;
+ const PARAM_INPUT_OUTPUT = -2147483648;
+ const PARAM_INT = 1;
+ const PARAM_LOB = 3;
+ const PARAM_NULL = 0;
+ const PARAM_STMT = 4;
+ const PARAM_STR = 2;
+
+ /**
+ * Factory for Zend_Db_Adapter_Abstract classes.
+ *
+ * First argument may be a string containing the base of the adapter class
+ * name, e.g. 'Mysqli' corresponds to class Zend_Db_Adapter_Mysqli. This
+ * name is currently case-insensitive, but is not ideal to rely on this behavior.
+ * If your class is named 'My_Company_Pdo_Mysql', where 'My_Company' is the namespace
+ * and 'Pdo_Mysql' is the adapter name, it is best to use the name exactly as it
+ * is defined in the class. This will ensure proper use of the factory API.
+ *
+ * First argument may alternatively be an object of type Zend_Config.
+ * The adapter class base name is read from the 'adapter' property.
+ * The adapter config parameters are read from the 'params' property.
+ *
+ * Second argument is optional and may be an associative array of key-value
+ * pairs. This is used as the argument to the adapter constructor.
+ *
+ * If the first argument is of type Zend_Config, it is assumed to contain
+ * all parameters, and the second argument is ignored.
+ *
+ * @param mixed $adapter String name of base adapter class, or Zend_Config object.
+ * @param mixed $config OPTIONAL; an array or Zend_Config object with adapter parameters.
+ * @return Zend_Db_Adapter_Abstract
+ * @throws Zend_Db_Exception
+ */
+ public static function factory($adapter, $config = array())
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ }
+
+ /*
+ * Convert Zend_Config argument to plain string
+ * adapter name and separate config object.
+ */
+ if ($adapter instanceof Zend_Config) {
+ if (isset($adapter->params)) {
+ $config = $adapter->params->toArray();
+ }
+ if (isset($adapter->adapter)) {
+ $adapter = (string) $adapter->adapter;
+ } else {
+ $adapter = null;
+ }
+ }
+
+ /*
+ * Verify that adapter parameters are in an array.
+ */
+ if (!is_array($config)) {
+ /**
+ * @see Zend_Db_Exception
+ */
+ throw new Zend_Db_Exception('Adapter parameters must be in an array or a Zend_Config object');
+ }
+
+ /*
+ * Verify that an adapter name has been specified.
+ */
+ if (!is_string($adapter) || empty($adapter)) {
+ /**
+ * @see Zend_Db_Exception
+ */
+ throw new Zend_Db_Exception('Adapter name must be specified in a string');
+ }
+
+ /*
+ * Form full adapter class name
+ */
+ $adapterNamespace = 'Zend_Db_Adapter';
+ if (isset($config['adapterNamespace'])) {
+ if ($config['adapterNamespace'] != '') {
+ $adapterNamespace = $config['adapterNamespace'];
+ }
+ unset($config['adapterNamespace']);
+ }
+
+ // Adapter no longer normalized- see http://framework.zend.com/issues/browse/ZF-5606
+ $adapterName = $adapterNamespace . '_';
+ $adapterName .= str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($adapter))));
+
+ /*
+ * Load the adapter class. This throws an exception
+ * if the specified class cannot be loaded.
+ */
+ if (!class_exists($adapterName)) {
+ Zend_Loader::loadClass($adapterName);
+ }
+
+ /*
+ * Create an instance of the adapter class.
+ * Pass the config to the adapter class constructor.
+ */
+ $dbAdapter = new $adapterName($config);
+
+ /*
+ * Verify that the object created is a descendent of the abstract adapter type.
+ */
+ if (! $dbAdapter instanceof Zend_Db_Adapter_Abstract) {
+ /**
+ * @see Zend_Db_Exception
+ */
+ throw new Zend_Db_Exception("Adapter class '$adapterName' does not extend Zend_Db_Adapter_Abstract");
+ }
+
+ return $dbAdapter;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Adapter/Abstract.php b/library/vendor/Zend/Db/Adapter/Abstract.php
new file mode 100644
index 0000000..cc00ea1
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Abstract.php
@@ -0,0 +1,1267 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * @see Zend_Db_Select
+ */
+
+/**
+ * Class for connecting to SQL databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Db_Adapter_Abstract
+{
+
+ /**
+ * User-provided configuration
+ *
+ * @var array
+ */
+ protected $_config = array();
+
+ /**
+ * Fetch mode
+ *
+ * @var integer
+ */
+ protected $_fetchMode = Zend_Db::FETCH_ASSOC;
+
+ /**
+ * Query profiler object, of type Zend_Db_Profiler
+ * or a subclass of that.
+ *
+ * @var Zend_Db_Profiler
+ */
+ protected $_profiler;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement';
+
+ /**
+ * Default class name for the profiler object.
+ *
+ * @var string
+ */
+ protected $_defaultProfilerClass = 'Zend_Db_Profiler';
+
+ /**
+ * Database connection
+ *
+ * @var object|resource|null
+ */
+ protected $_connection = null;
+
+ /**
+ * Specifies the case of column names retrieved in queries
+ * Options
+ * Zend_Db::CASE_NATURAL (default)
+ * Zend_Db::CASE_LOWER
+ * Zend_Db::CASE_UPPER
+ *
+ * @var integer
+ */
+ protected $_caseFolding = Zend_Db::CASE_NATURAL;
+
+ /**
+ * Specifies whether the adapter automatically quotes identifiers.
+ * If true, most SQL generated by Zend_Db classes applies
+ * identifier quoting automatically.
+ * If false, developer must quote identifiers themselves
+ * by calling quoteIdentifier().
+ *
+ * @var bool
+ */
+ protected $_autoQuoteIdentifiers = true;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE
+ );
+
+ /** Weither or not that object can get serialized
+ *
+ * @var bool
+ */
+ protected $_allowSerialization = true;
+
+ /**
+ * Weither or not the database should be reconnected
+ * to that adapter when waking up
+ *
+ * @var bool
+ */
+ protected $_autoReconnectOnUnserialize = false;
+
+ /**
+ * Constructor.
+ *
+ * $config is an array of key/value pairs or an instance of Zend_Config
+ * containing configuration options. These options are common to most adapters:
+ *
+ * dbname => (string) The name of the database to user
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * host => (string) What host to connect to, defaults to localhost
+ *
+ * Some options are used on a case-by-case basis by adapters:
+ *
+ * port => (string) The port of the database
+ * persistent => (boolean) Whether to use a persistent connection or not, defaults to false
+ * protocol => (string) The network protocol, defaults to TCPIP
+ * caseFolding => (int) style of case-alteration used for identifiers
+ * socket => (string) The socket or named pipe that should be used
+ *
+ * @param array|Zend_Config $config An array or instance of Zend_Config having configuration data
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function __construct($config)
+ {
+ /*
+ * Verify that adapter parameters are in an array.
+ */
+ if (!is_array($config)) {
+ /*
+ * Convert Zend_Config argument to a plain array.
+ */
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ } else {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception('Adapter parameters must be in an array or a Zend_Config object');
+ }
+ }
+
+ $this->_checkRequiredOptions($config);
+
+ $options = array(
+ Zend_Db::CASE_FOLDING => $this->_caseFolding,
+ Zend_Db::AUTO_QUOTE_IDENTIFIERS => $this->_autoQuoteIdentifiers,
+ Zend_Db::FETCH_MODE => $this->_fetchMode,
+ );
+ $driverOptions = array();
+
+ /*
+ * normalize the config and merge it with the defaults
+ */
+ if (array_key_exists('options', $config)) {
+ // can't use array_merge() because keys might be integers
+ foreach ((array) $config['options'] as $key => $value) {
+ $options[$key] = $value;
+ }
+ }
+ if (array_key_exists('driver_options', $config)) {
+ if (!empty($config['driver_options'])) {
+ // can't use array_merge() because keys might be integers
+ foreach ((array) $config['driver_options'] as $key => $value) {
+ $driverOptions[$key] = $value;
+ }
+ }
+ }
+
+ if (!isset($config['charset'])) {
+ $config['charset'] = null;
+ }
+
+ if (!isset($config['persistent'])) {
+ $config['persistent'] = false;
+ }
+
+ $this->_config = array_merge($this->_config, $config);
+ $this->_config['options'] = $options;
+ $this->_config['driver_options'] = $driverOptions;
+
+
+ // obtain the case setting, if there is one
+ if (array_key_exists(Zend_Db::CASE_FOLDING, $options)) {
+ $case = (int) $options[Zend_Db::CASE_FOLDING];
+ switch ($case) {
+ case Zend_Db::CASE_LOWER:
+ case Zend_Db::CASE_UPPER:
+ case Zend_Db::CASE_NATURAL:
+ $this->_caseFolding = $case;
+ break;
+ default:
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception('Case must be one of the following constants: '
+ . 'Zend_Db::CASE_NATURAL, Zend_Db::CASE_LOWER, Zend_Db::CASE_UPPER');
+ }
+ }
+
+ if (array_key_exists(Zend_Db::FETCH_MODE, $options)) {
+ if (is_string($options[Zend_Db::FETCH_MODE])) {
+ $constant = 'Zend_Db::FETCH_' . strtoupper($options[Zend_Db::FETCH_MODE]);
+ if(defined($constant)) {
+ $options[Zend_Db::FETCH_MODE] = constant($constant);
+ }
+ }
+ $this->setFetchMode((int) $options[Zend_Db::FETCH_MODE]);
+ }
+
+ // obtain quoting property if there is one
+ if (array_key_exists(Zend_Db::AUTO_QUOTE_IDENTIFIERS, $options)) {
+ $this->_autoQuoteIdentifiers = (bool) $options[Zend_Db::AUTO_QUOTE_IDENTIFIERS];
+ }
+
+ // obtain allow serialization property if there is one
+ if (array_key_exists(Zend_Db::ALLOW_SERIALIZATION, $options)) {
+ $this->_allowSerialization = (bool) $options[Zend_Db::ALLOW_SERIALIZATION];
+ }
+
+ // obtain auto reconnect on unserialize property if there is one
+ if (array_key_exists(Zend_Db::AUTO_RECONNECT_ON_UNSERIALIZE, $options)) {
+ $this->_autoReconnectOnUnserialize = (bool) $options[Zend_Db::AUTO_RECONNECT_ON_UNSERIALIZE];
+ }
+
+ // create a profiler object
+ $profiler = false;
+ if (array_key_exists(Zend_Db::PROFILER, $this->_config)) {
+ $profiler = $this->_config[Zend_Db::PROFILER];
+ unset($this->_config[Zend_Db::PROFILER]);
+ }
+ $this->setProfiler($profiler);
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance");
+ }
+
+ if (! array_key_exists('password', $config)) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'password' for login credentials");
+ }
+
+ if (! array_key_exists('username', $config)) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'username' for login credentials");
+ }
+ }
+
+ /**
+ * Returns the underlying database connection object or resource.
+ * If not presently connected, this initiates the connection.
+ *
+ * @return object|resource|null
+ */
+ public function getConnection()
+ {
+ $this->_connect();
+ return $this->_connection;
+ }
+
+ /**
+ * Returns the configuration variables in this adapter.
+ *
+ * @return array
+ */
+ public function getConfig()
+ {
+ return $this->_config;
+ }
+
+ /**
+ * Set the adapter's profiler object.
+ *
+ * The argument may be a boolean, an associative array, an instance of
+ * Zend_Db_Profiler, or an instance of Zend_Config.
+ *
+ * A boolean argument sets the profiler to enabled if true, or disabled if
+ * false. The profiler class is the adapter's default profiler class,
+ * Zend_Db_Profiler.
+ *
+ * An instance of Zend_Db_Profiler sets the adapter's instance to that
+ * object. The profiler is enabled and disabled separately.
+ *
+ * An associative array argument may contain any of the keys 'enabled',
+ * 'class', and 'instance'. The 'enabled' and 'instance' keys correspond to the
+ * boolean and object types documented above. The 'class' key is used to name a
+ * class to use for a custom profiler. The class must be Zend_Db_Profiler or a
+ * subclass. The class is instantiated with no constructor arguments. The 'class'
+ * option is ignored when the 'instance' option is supplied.
+ *
+ * An object of type Zend_Config may contain the properties 'enabled', 'class', and
+ * 'instance', just as if an associative array had been passed instead.
+ *
+ * @param Zend_Db_Profiler|Zend_Config|array|boolean $profiler
+ * @return Zend_Db_Adapter_Abstract Provides a fluent interface
+ * @throws Zend_Db_Profiler_Exception if the object instance or class specified
+ * is not Zend_Db_Profiler or an extension of that class.
+ */
+ public function setProfiler($profiler)
+ {
+ $enabled = null;
+ $profilerClass = $this->_defaultProfilerClass;
+ $profilerInstance = null;
+
+ if ($profilerIsObject = is_object($profiler)) {
+ if ($profiler instanceof Zend_Db_Profiler) {
+ $profilerInstance = $profiler;
+ } else if ($profiler instanceof Zend_Config) {
+ $profiler = $profiler->toArray();
+ } else {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ throw new Zend_Db_Profiler_Exception('Profiler argument must be an instance of either Zend_Db_Profiler'
+ . ' or Zend_Config when provided as an object');
+ }
+ }
+
+ if (is_array($profiler)) {
+ if (isset($profiler['enabled'])) {
+ $enabled = (bool) $profiler['enabled'];
+ }
+ if (isset($profiler['class'])) {
+ $profilerClass = $profiler['class'];
+ }
+ if (isset($profiler['instance'])) {
+ $profilerInstance = $profiler['instance'];
+ }
+ } else if (!$profilerIsObject) {
+ $enabled = (bool) $profiler;
+ }
+
+ if ($profilerInstance === null) {
+ if (!class_exists($profilerClass)) {
+ Zend_Loader::loadClass($profilerClass);
+ }
+ $profilerInstance = new $profilerClass();
+ }
+
+ if (!$profilerInstance instanceof Zend_Db_Profiler) {
+ /** @see Zend_Db_Profiler_Exception */
+ throw new Zend_Db_Profiler_Exception('Class ' . get_class($profilerInstance) . ' does not extend '
+ . 'Zend_Db_Profiler');
+ }
+
+ if (null !== $enabled) {
+ $profilerInstance->setEnabled($enabled);
+ }
+
+ $this->_profiler = $profilerInstance;
+
+ return $this;
+ }
+
+
+ /**
+ * Returns the profiler for this adapter.
+ *
+ * @return Zend_Db_Profiler
+ */
+ public function getProfiler()
+ {
+ return $this->_profiler;
+ }
+
+ /**
+ * Get the default statement class.
+ *
+ * @return string
+ */
+ public function getStatementClass()
+ {
+ return $this->_defaultStmtClass;
+ }
+
+ /**
+ * Set the default statement class.
+ *
+ * @return Zend_Db_Adapter_Abstract Fluent interface
+ */
+ public function setStatementClass($class)
+ {
+ $this->_defaultStmtClass = $class;
+ return $this;
+ }
+
+ /**
+ * Prepares and executes an SQL statement with bound data.
+ *
+ * @param mixed $sql The SQL statement with placeholders.
+ * May be a string or Zend_Db_Select.
+ * @param mixed $bind An array of data to bind to the placeholders.
+ * @return Zend_Db_Statement_Interface
+ */
+ public function query($sql, $bind = array())
+ {
+ // connect to the database if needed
+ $this->_connect();
+
+ // is the $sql a Zend_Db_Select object?
+ if ($sql instanceof Zend_Db_Select) {
+ if (empty($bind)) {
+ $bind = $sql->getBind();
+ }
+
+ $sql = $sql->assemble();
+ }
+
+ // make sure $bind to an array;
+ // don't use (array) typecasting because
+ // because $bind may be a Zend_Db_Expr object
+ if (!is_array($bind)) {
+ $bind = array($bind);
+ }
+
+ // prepare and execute the statement with profiling
+ $stmt = $this->prepare($sql);
+ $stmt->execute($bind);
+
+ // return the results embedded in the prepared statement object
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function beginTransaction()
+ {
+ $this->_connect();
+ $q = $this->_profiler->queryStart('begin', Zend_Db_Profiler::TRANSACTION);
+ $this->_beginTransaction();
+ $this->_profiler->queryEnd($q);
+ return $this;
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function commit()
+ {
+ $this->_connect();
+ $q = $this->_profiler->queryStart('commit', Zend_Db_Profiler::TRANSACTION);
+ $this->_commit();
+ $this->_profiler->queryEnd($q);
+ return $this;
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function rollBack()
+ {
+ $this->_connect();
+ $q = $this->_profiler->queryStart('rollback', Zend_Db_Profiler::TRANSACTION);
+ $this->_rollBack();
+ $this->_profiler->queryEnd($q);
+ return $this;
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function insert($table, array $bind)
+ {
+ // extract and quote col names from the array keys
+ $cols = array();
+ $vals = array();
+ $i = 0;
+ foreach ($bind as $col => $val) {
+ $cols[] = $this->quoteIdentifier($col, true);
+ if ($val instanceof Zend_Db_Expr) {
+ $vals[] = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ if ($this->supportsParameters('positional')) {
+ $vals[] = '?';
+ } else {
+ if ($this->supportsParameters('named')) {
+ unset($bind[$col]);
+ $bind[':col'.$i] = $val;
+ $vals[] = ':col'.$i;
+ $i++;
+ } else {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception(get_class($this) ." doesn't support positional or named binding");
+ }
+ }
+ }
+ }
+
+ // build the statement
+ $sql = "INSERT INTO "
+ . $this->quoteIdentifier($table, true)
+ . ' (' . implode(', ', $cols) . ') '
+ . 'VALUES (' . implode(', ', $vals) . ')';
+
+ // execute the statement and return the number of affected rows
+ if ($this->supportsParameters('positional')) {
+ $bind = array_values($bind);
+ }
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->rowCount();
+ return $result;
+ }
+
+ /**
+ * Updates table rows with specified data based on a WHERE clause.
+ *
+ * @param mixed $table The table to update.
+ * @param array $bind Column-value pairs.
+ * @param mixed $where UPDATE WHERE clause(s).
+ * @return int The number of affected rows.
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function update($table, array $bind, $where = '')
+ {
+ /**
+ * Build "col = ?" pairs for the statement,
+ * except for Zend_Db_Expr which is treated literally.
+ */
+ $set = array();
+ $i = 0;
+ foreach ($bind as $col => $val) {
+ if ($val instanceof Zend_Db_Expr) {
+ $val = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ if ($this->supportsParameters('positional')) {
+ $val = '?';
+ } else {
+ if ($this->supportsParameters('named')) {
+ unset($bind[$col]);
+ $bind[':col'.$i] = $val;
+ $val = ':col'.$i;
+ $i++;
+ } else {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception(get_class($this) ." doesn't support positional or named binding");
+ }
+ }
+ }
+ $set[] = $this->quoteIdentifier($col, true) . ' = ' . $val;
+ }
+
+ $where = $this->_whereExpr($where);
+
+ /**
+ * Build the UPDATE statement
+ */
+ $sql = "UPDATE "
+ . $this->quoteIdentifier($table, true)
+ . ' SET ' . implode(', ', $set)
+ . (($where) ? " WHERE $where" : '');
+
+ /**
+ * Execute the statement and return the number of affected rows
+ */
+ if ($this->supportsParameters('positional')) {
+ $stmt = $this->query($sql, array_values($bind));
+ } else {
+ $stmt = $this->query($sql, $bind);
+ }
+ $result = $stmt->rowCount();
+ return $result;
+ }
+
+ /**
+ * Deletes table rows based on a WHERE clause.
+ *
+ * @param mixed $table The table to update.
+ * @param mixed $where DELETE WHERE clause(s).
+ * @return int The number of affected rows.
+ */
+ public function delete($table, $where = '')
+ {
+ $where = $this->_whereExpr($where);
+
+ /**
+ * Build the DELETE statement
+ */
+ $sql = "DELETE FROM "
+ . $this->quoteIdentifier($table, true)
+ . (($where) ? " WHERE $where" : '');
+
+ /**
+ * Execute the statement and return the number of affected rows
+ */
+ $stmt = $this->query($sql);
+ $result = $stmt->rowCount();
+ return $result;
+ }
+
+ /**
+ * Convert an array, string, or Zend_Db_Expr object
+ * into a string to put in a WHERE clause.
+ *
+ * @param mixed $where
+ * @return string
+ */
+ protected function _whereExpr($where)
+ {
+ if (empty($where)) {
+ return $where;
+ }
+ if (!is_array($where)) {
+ $where = array($where);
+ }
+ foreach ($where as $cond => &$term) {
+ // is $cond an int? (i.e. Not a condition)
+ if (is_int($cond)) {
+ // $term is the full condition
+ if ($term instanceof Zend_Db_Expr) {
+ $term = $term->__toString();
+ }
+ } else {
+ // $cond is the condition with placeholder,
+ // and $term is quoted into the condition
+ $term = $this->quoteInto($cond, $term);
+ }
+ $term = '(' . $term . ')';
+ }
+
+ $where = implode(' AND ', $where);
+ return $where;
+ }
+
+ /**
+ * Creates and returns a new Zend_Db_Select object for this adapter.
+ *
+ * @return Zend_Db_Select
+ */
+ public function select()
+ {
+ return new Zend_Db_Select($this);
+ }
+
+ /**
+ * Get the fetch mode.
+ *
+ * @return int
+ */
+ public function getFetchMode()
+ {
+ return $this->_fetchMode;
+ }
+
+ /**
+ * Fetches all SQL result rows as a sequential array.
+ * Uses the current fetchMode for the adapter.
+ *
+ * @param string|Zend_Db_Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @param mixed $fetchMode Override current fetch mode.
+ * @return array
+ */
+ public function fetchAll($sql, $bind = array(), $fetchMode = null)
+ {
+ if ($fetchMode === null) {
+ $fetchMode = $this->_fetchMode;
+ }
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetchAll($fetchMode);
+ return $result;
+ }
+
+ /**
+ * Fetches the first row of the SQL result.
+ * Uses the current fetchMode for the adapter.
+ *
+ * @param string|Zend_Db_Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @param mixed $fetchMode Override current fetch mode.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ */
+ public function fetchRow($sql, $bind = array(), $fetchMode = null)
+ {
+ if ($fetchMode === null) {
+ $fetchMode = $this->_fetchMode;
+ }
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetch($fetchMode);
+ return $result;
+ }
+
+ /**
+ * Fetches all SQL result rows as an associative array.
+ *
+ * The first column is the key, the entire row array is the
+ * value. You should construct the query to be sure that
+ * the first column contains unique values, or else
+ * rows with duplicate values in the first column will
+ * overwrite previous data.
+ *
+ * @param string|Zend_Db_Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return array
+ */
+ public function fetchAssoc($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $data = array();
+ while ($row = $stmt->fetch(Zend_Db::FETCH_ASSOC)) {
+ $tmp = array_values(array_slice($row, 0, 1));
+ $data[$tmp[0]] = $row;
+ }
+ return $data;
+ }
+
+ /**
+ * Fetches the first column of all SQL result rows as an array.
+ *
+ * @param string|Zend_Db_Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return array
+ */
+ public function fetchCol($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetchAll(Zend_Db::FETCH_COLUMN, 0);
+ return $result;
+ }
+
+ /**
+ * Fetches all SQL result rows as an array of key-value pairs.
+ *
+ * The first column is the key, the second column is the
+ * value.
+ *
+ * @param string|Zend_Db_Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return array
+ */
+ public function fetchPairs($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $data = array();
+ while ($row = $stmt->fetch(Zend_Db::FETCH_NUM)) {
+ $data[$row[0]] = $row[1];
+ }
+ return $data;
+ }
+
+ /**
+ * Fetches the first column of the first row of the SQL result.
+ *
+ * @param string|Zend_Db_Select $sql An SQL SELECT statement.
+ * @param mixed $bind Data to bind into SELECT placeholders.
+ * @return string
+ */
+ public function fetchOne($sql, $bind = array())
+ {
+ $stmt = $this->query($sql, $bind);
+ $result = $stmt->fetchColumn(0);
+ return $result;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value)) {
+ return $value;
+ } elseif (is_float($value)) {
+ return sprintf('%F', $value);
+ }
+ return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'";
+ }
+
+ /**
+ * Safely quotes a value for an SQL statement.
+ *
+ * If an array is passed as the value, the array values are quoted
+ * and then returned as a comma-separated string.
+ *
+ * @param mixed $value The value to quote.
+ * @param mixed $type OPTIONAL the SQL datatype name, or constant, or null.
+ * @return mixed An SQL-safe quoted value (or string of separated values).
+ */
+ public function quote($value, $type = null)
+ {
+ $this->_connect();
+
+ if ($value instanceof Zend_Db_Select) {
+ return '(' . $value->assemble() . ')';
+ }
+
+ if ($value instanceof Zend_Db_Expr) {
+ return $value->__toString();
+ }
+
+ if (is_array($value)) {
+ foreach ($value as &$val) {
+ $val = $this->quote($val, $type);
+ }
+ return implode(', ', $value);
+ }
+
+ if ($type !== null && array_key_exists($type = strtoupper($type), $this->_numericDataTypes)) {
+ $quotedValue = '0';
+ switch ($this->_numericDataTypes[$type]) {
+ case Zend_Db::INT_TYPE: // 32-bit integer
+ $quotedValue = (string) intval($value);
+ break;
+ case Zend_Db::BIGINT_TYPE: // 64-bit integer
+ // ANSI SQL-style hex literals (e.g. x'[\dA-F]+')
+ // are not supported here, because these are string
+ // literals, not numeric literals.
+ if (preg_match('/^(
+ [+-]? # optional sign
+ (?:
+ 0[Xx][\da-fA-F]+ # ODBC-style hexadecimal
+ |\d+ # decimal or octal, or MySQL ZEROFILL decimal
+ (?:[eE][+-]?\d+)? # optional exponent on decimals or octals
+ )
+ )/x',
+ (string) $value, $matches)) {
+ $quotedValue = $matches[1];
+ }
+ break;
+ case Zend_Db::FLOAT_TYPE: // float or decimal
+ $quotedValue = sprintf('%F', $value);
+ }
+ return $quotedValue;
+ }
+
+ return $this->_quote($value);
+ }
+
+ /**
+ * Quotes a value and places into a piece of text at a placeholder.
+ *
+ * The placeholder is a question-mark; all placeholders will be replaced
+ * with the quoted value. For example:
+ *
+ * <code>
+ * $text = "WHERE date < ?";
+ * $date = "2005-01-02";
+ * $safe = $sql->quoteInto($text, $date);
+ * // $safe = "WHERE date < '2005-01-02'"
+ * </code>
+ *
+ * @param string $text The text with a placeholder.
+ * @param mixed $value The value to quote.
+ * @param string $type OPTIONAL SQL datatype
+ * @param integer $count OPTIONAL count of placeholders to replace
+ * @return string An SQL-safe quoted value placed into the original text.
+ */
+ public function quoteInto($text, $value, $type = null, $count = null)
+ {
+ if ($count === null) {
+ return str_replace('?', $this->quote($value, $type), $text);
+ } else {
+ return implode($this->quote($value, $type), explode('?', $text, $count + 1));
+ }
+ }
+
+ /**
+ * Quotes an identifier.
+ *
+ * Accepts a string representing a qualified indentifier. For Example:
+ * <code>
+ * $adapter->quoteIdentifier('myschema.mytable')
+ * </code>
+ * Returns: "myschema"."mytable"
+ *
+ * Or, an array of one or more identifiers that may form a qualified identifier:
+ * <code>
+ * $adapter->quoteIdentifier(array('myschema','my.table'))
+ * </code>
+ * Returns: "myschema"."my.table"
+ *
+ * The actual quote character surrounding the identifiers may vary depending on
+ * the adapter.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier.
+ */
+ public function quoteIdentifier($ident, $auto=false)
+ {
+ return $this->_quoteIdentifierAs($ident, null, $auto);
+ }
+
+ /**
+ * Quote a column identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the column.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteColumnAs($ident, $alias, $auto=false)
+ {
+ return $this->_quoteIdentifierAs($ident, $alias, $auto);
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ return $this->_quoteIdentifierAs($ident, $alias, $auto);
+ }
+
+ /**
+ * Quote an identifier and an optional alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An optional alias.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @param string $as The string to add between the identifier/expression and the alias.
+ * @return string The quoted identifier and alias.
+ */
+ protected function _quoteIdentifierAs($ident, $alias = null, $auto = false, $as = ' AS ')
+ {
+ if ($ident instanceof Zend_Db_Expr) {
+ $quoted = $ident->__toString();
+ } elseif ($ident instanceof Zend_Db_Select) {
+ $quoted = '(' . $ident->assemble() . ')';
+ } else {
+ if (is_string($ident)) {
+ $ident = explode('.', $ident);
+ }
+ if (is_array($ident)) {
+ $segments = array();
+ foreach ($ident as $segment) {
+ if ($segment instanceof Zend_Db_Expr) {
+ $segments[] = $segment->__toString();
+ } else {
+ $segments[] = $this->_quoteIdentifier($segment, $auto);
+ }
+ }
+ if ($alias !== null && end($ident) == $alias) {
+ $alias = null;
+ }
+ $quoted = implode('.', $segments);
+ } else {
+ $quoted = $this->_quoteIdentifier($ident, $auto);
+ }
+ }
+ if ($alias !== null) {
+ $quoted .= $as . $this->_quoteIdentifier($alias, $auto);
+ }
+ return $quoted;
+ }
+
+ /**
+ * Quote an identifier.
+ *
+ * @param string $value The identifier or expression.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ protected function _quoteIdentifier($value, $auto=false)
+ {
+ if ($auto === false || $this->_autoQuoteIdentifiers === true) {
+ $q = $this->getQuoteIdentifierSymbol();
+ return ($q . str_replace("$q", "$q$q", $value) . $q);
+ }
+ return $value;
+ }
+
+ /**
+ * Returns the symbol the adapter uses for delimited identifiers.
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return '"';
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ return null;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ return null;
+ }
+
+ /**
+ * Helper method to change the case of the strings used
+ * when returning result sets in FETCH_ASSOC and FETCH_BOTH
+ * modes.
+ *
+ * This is not intended to be used by application code,
+ * but the method must be public so the Statement class
+ * can invoke it.
+ *
+ * @param string $key
+ * @return string
+ */
+ public function foldCase($key)
+ {
+ switch ($this->_caseFolding) {
+ case Zend_Db::CASE_LOWER:
+ $value = strtolower((string) $key);
+ break;
+ case Zend_Db::CASE_UPPER:
+ $value = strtoupper((string) $key);
+ break;
+ case Zend_Db::CASE_NATURAL:
+ default:
+ $value = (string) $key;
+ }
+ return $value;
+ }
+
+ /**
+ * called when object is getting serialized
+ * This disconnects the DB object that cant be serialized
+ *
+ * @throws Zend_Db_Adapter_Exception
+ * @return array
+ */
+ public function __sleep()
+ {
+ if ($this->_allowSerialization == false) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception(
+ get_class($this) . ' is not allowed to be serialized'
+ );
+ }
+ $this->_connection = null;
+
+ return array_keys(
+ array_diff_key(get_object_vars($this), array('_connection' => null))
+ );
+ }
+
+ /**
+ * called when object is getting unserialized
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ if ($this->_autoReconnectOnUnserialize == true) {
+ $this->getConnection();
+ }
+ }
+
+ /**
+ * Abstract Methods
+ */
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ abstract public function listTables();
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ abstract public function describeTable($tableName, $schemaName = null);
+
+ /**
+ * Creates a connection to the database.
+ *
+ * @return void
+ */
+ abstract protected function _connect();
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ abstract public function isConnected();
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ abstract public function closeConnection();
+
+ /**
+ * Prepare a statement and return a PDOStatement-like object.
+ *
+ * @param string|Zend_Db_Select $sql SQL query
+ * @return Zend_Db_Statement|PDOStatement
+ */
+ abstract public function prepare($sql);
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ abstract public function lastInsertId($tableName = null, $primaryKey = null);
+
+ /**
+ * Begin a transaction.
+ */
+ abstract protected function _beginTransaction();
+
+ /**
+ * Commit a transaction.
+ */
+ abstract protected function _commit();
+
+ /**
+ * Roll-back a transaction.
+ */
+ abstract protected function _rollBack();
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ abstract public function setFetchMode($mode);
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param mixed $sql
+ * @param integer $count
+ * @param integer $offset
+ * @return string
+ */
+ abstract public function limit($sql, $count, $offset = 0);
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ abstract public function supportsParameters($type);
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ abstract public function getServerVersion();
+}
diff --git a/library/vendor/Zend/Db/Adapter/Db2.php b/library/vendor/Zend/Db/Adapter/Db2.php
new file mode 100644
index 0000000..28793d1
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Db2.php
@@ -0,0 +1,827 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ *
+ */
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db_Statement_Db2
+ */
+
+
+/**
+ * @package Zend_Db
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Db_Adapter_Db2 extends Zend_Db_Adapter_Abstract
+{
+ /**
+ * User-provided configuration.
+ *
+ * Basic keys are:
+ *
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * host => (string) What host to connect to (default 127.0.0.1)
+ * dbname => (string) The name of the database to user
+ * protocol => (string) Protocol to use, defaults to "TCPIP"
+ * port => (integer) Port number to use for TCP/IP if protocol is "TCPIP"
+ * persistent => (boolean) Set TRUE to use a persistent connection (db2_pconnect)
+ * os => (string) This should be set to 'i5' if the db is on an os400/i5
+ * schema => (string) The default schema the connection should use
+ *
+ * @var array
+ */
+ protected $_config = array(
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ 'host' => 'localhost',
+ 'port' => '50000',
+ 'protocol' => 'TCPIP',
+ 'persistent' => false,
+ 'os' => null,
+ 'schema' => null
+ );
+
+ /**
+ * Execution mode
+ *
+ * @var int execution flag (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF)
+ */
+ protected $_execute_mode = DB2_AUTOCOMMIT_ON;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Db2';
+ protected $_isI5 = false;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('ibm_db2')) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception('The IBM DB2 extension is required for this adapter but the extension is not loaded');
+ }
+
+ $this->_determineI5();
+ if ($this->_config['persistent']) {
+ // use persistent connection
+ $conn_func_name = 'db2_pconnect';
+ } else {
+ // use "normal" connection
+ $conn_func_name = 'db2_connect';
+ }
+
+ if (!isset($this->_config['driver_options']['autocommit'])) {
+ // set execution mode
+ $this->_config['driver_options']['autocommit'] = &$this->_execute_mode;
+ }
+
+ if (isset($this->_config['options'][Zend_Db::CASE_FOLDING])) {
+ $caseAttrMap = array(
+ Zend_Db::CASE_NATURAL => DB2_CASE_NATURAL,
+ Zend_Db::CASE_UPPER => DB2_CASE_UPPER,
+ Zend_Db::CASE_LOWER => DB2_CASE_LOWER
+ );
+ $this->_config['driver_options']['DB2_ATTR_CASE'] = $caseAttrMap[$this->_config['options'][Zend_Db::CASE_FOLDING]];
+ }
+
+ if ($this->_isI5 && isset($this->_config['driver_options']['i5_naming'])) {
+ if ($this->_config['driver_options']['i5_naming']) {
+ $this->_config['driver_options']['i5_naming'] = DB2_I5_NAMING_ON;
+ } else {
+ $this->_config['driver_options']['i5_naming'] = DB2_I5_NAMING_OFF;
+ }
+ }
+
+ if ($this->_config['host'] !== 'localhost' && !$this->_isI5) {
+ // if the host isn't localhost, use extended connection params
+ $dbname = 'DRIVER={IBM DB2 ODBC DRIVER}' .
+ ';DATABASE=' . $this->_config['dbname'] .
+ ';HOSTNAME=' . $this->_config['host'] .
+ ';PORT=' . $this->_config['port'] .
+ ';PROTOCOL=' . $this->_config['protocol'] .
+ ';UID=' . $this->_config['username'] .
+ ';PWD=' . $this->_config['password'] .';';
+ $this->_connection = $conn_func_name(
+ $dbname,
+ null,
+ null,
+ $this->_config['driver_options']
+ );
+ } else {
+ // host is localhost, so use standard connection params
+ $this->_connection = $conn_func_name(
+ $this->_config['dbname'],
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+ }
+
+ // check the connection
+ if (!$this->_connection) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception(db2_conn_errormsg(), db2_conn_error());
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) (is_resource($this->_connection)
+ && get_resource_type($this->_connection) == 'DB2 Connection'));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ db2_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Zend_Db_Statement_Db2
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Gets the execution mode
+ *
+ * @return int the execution mode (DB2_AUTOCOMMIT_ON or DB2_AUTOCOMMIT_OFF)
+ */
+ public function _getExecuteMode()
+ {
+ return $this->_execute_mode;
+ }
+
+ /**
+ * @param integer $mode
+ * @return void
+ */
+ public function _setExecuteMode($mode)
+ {
+ switch ($mode) {
+ case DB2_AUTOCOMMIT_OFF:
+ case DB2_AUTOCOMMIT_ON:
+ $this->_execute_mode = $mode;
+ db2_autocommit($this->_connection, $mode);
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception("execution mode not supported");
+ break;
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ /**
+ * Use db2_escape_string() if it is present in the IBM DB2 extension.
+ * But some supported versions of PHP do not include this function,
+ * so fall back to default quoting in the parent class.
+ */
+ if (function_exists('db2_escape_string')) {
+ return "'" . db2_escape_string($value) . "'";
+ }
+ return parent::_quote($value);
+ }
+
+ /**
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ $this->_connect();
+ $info = db2_server_info($this->_connection);
+ if ($info) {
+ $identQuote = $info->IDENTIFIER_QUOTE_CHAR;
+ } else {
+ // db2_server_info() does not return result on some i5 OS version
+ if ($this->_isI5) {
+ $identQuote ="'";
+ }
+ }
+ return $identQuote;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ * @param string $schema OPTIONAL
+ * @return array
+ */
+ public function listTables($schema = null)
+ {
+ $this->_connect();
+
+ if ($schema === null && $this->_config['schema'] != null) {
+ $schema = $this->_config['schema'];
+ }
+
+ $tables = array();
+
+ if (!$this->_isI5) {
+ if ($schema) {
+ $stmt = db2_tables($this->_connection, null, $schema);
+ } else {
+ $stmt = db2_tables($this->_connection);
+ }
+ while ($row = db2_fetch_assoc($stmt)) {
+ $tables[] = $row['TABLE_NAME'];
+ }
+ } else {
+ $tables = $this->_i5listTables($schema);
+ }
+
+ return $tables;
+ }
+
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * DB2 not supports UNSIGNED integer.
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // Ensure the connection is made so that _isI5 is set
+ $this->_connect();
+
+ if ($schemaName === null && $this->_config['schema'] != null) {
+ $schemaName = $this->_config['schema'];
+ }
+
+ if (!$this->_isI5) {
+
+ $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE "
+ . $this->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName);
+
+ if ($schemaName) {
+ $sql .= $this->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName);
+ }
+
+ $sql .= " ORDER BY c.colno";
+
+ } else {
+
+ // DB2 On I5 specific query
+ $sql = "SELECT DISTINCT C.TABLE_SCHEMA, C.TABLE_NAME, C.COLUMN_NAME, C.ORDINAL_POSITION,
+ C.DATA_TYPE, C.COLUMN_DEFAULT, C.NULLS ,C.LENGTH, C.SCALE, LEFT(C.IDENTITY,1),
+ LEFT(tc.TYPE, 1) AS tabconsttype, k.COLSEQ
+ FROM QSYS2.SYSCOLUMNS C
+ LEFT JOIN (QSYS2.syskeycst k JOIN QSYS2.SYSCST tc
+ ON (k.TABLE_SCHEMA = tc.TABLE_SCHEMA
+ AND k.TABLE_NAME = tc.TABLE_NAME
+ AND LEFT(tc.type,1) = 'P'))
+ ON (C.TABLE_SCHEMA = k.TABLE_SCHEMA
+ AND C.TABLE_NAME = k.TABLE_NAME
+ AND C.COLUMN_NAME = k.COLUMN_NAME)
+ WHERE "
+ . $this->quoteInto('UPPER(C.TABLE_NAME) = UPPER(?)', $tableName);
+
+ if ($schemaName) {
+ $sql .= $this->quoteInto(' AND UPPER(C.TABLE_SCHEMA) = UPPER(?)', $schemaName);
+ }
+
+ $sql .= " ORDER BY C.ORDINAL_POSITION FOR FETCH ONLY";
+ }
+
+ $desc = array();
+ $stmt = $this->query($sql);
+
+ /**
+ * To avoid case issues, fetch using FETCH_NUM
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $nulls = 6;
+ $length = 7;
+ $scale = 8;
+ $identityCol = 9;
+ $tabconstType = 10;
+ $colseq = 11;
+
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$tabconstType] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$colseq];
+ }
+ /**
+ * In IBM DB2, an column can be IDENTITY
+ * even if it is not part of the PRIMARY KEY.
+ */
+ if ($row[$identityCol] == 'Y') {
+ $identity = true;
+ }
+
+ // only colname needs to be case adjusted
+ $desc[$this->foldCase($row[$colname])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => (!$this->_isI5) ? $row[$colno]+1 : $row[$colno],
+ 'DATA_TYPE' => $row[$typename],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$nulls] == 'Y'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+
+ if (!$this->_isI5) {
+ $quotedSequenceName = $this->quoteIdentifier($sequenceName, true);
+ $sql = 'SELECT PREVVAL FOR ' . $quotedSequenceName . ' AS VAL FROM SYSIBM.SYSDUMMY1';
+ } else {
+ $quotedSequenceName = $sequenceName;
+ $sql = 'SELECT PREVVAL FOR ' . $this->quoteIdentifier($sequenceName, true) . ' AS VAL FROM QSYS2.QSQPTABL';
+ }
+
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT NEXTVAL FOR '.$this->quoteIdentifier($sequenceName, true).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * The IDENTITY_VAL_LOCAL() function gives the last generated identity value
+ * in the current process, even if it was for a GENERATED column.
+ *
+ * @param string $tableName OPTIONAL
+ * @param string $primaryKey OPTIONAL
+ * @param string $idType OPTIONAL used for i5 platform to define sequence/idenity unique value
+ * @return string
+ */
+
+ public function lastInsertId($tableName = null, $primaryKey = null, $idType = null)
+ {
+ $this->_connect();
+
+ if ($this->_isI5) {
+ return (string) $this->_i5LastInsertId($tableName, $idType);
+ }
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->fetchOne($sql);
+ return (string) $value;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_OFF);
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ protected function _commit()
+ {
+ if (!db2_commit($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception(
+ db2_conn_errormsg($this->_connection),
+ db2_conn_error($this->_connection));
+ }
+
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_ON);
+ }
+
+ /**
+ * Rollback a transaction.
+ *
+ * @return void
+ */
+ protected function _rollBack()
+ {
+ if (!db2_rollback($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception(
+ db2_conn_errormsg($this->_connection),
+ db2_conn_error($this->_connection));
+ }
+ $this->_setExecuteMode(DB2_AUTOCOMMIT_ON);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param integer $mode
+ * @return void
+ * @throws Zend_Db_Adapter_Db2_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM: // seq array
+ case Zend_Db::FETCH_ASSOC: // assoc array
+ case Zend_Db::FETCH_BOTH: // seq+assoc array
+ case Zend_Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Db2_Exception
+ */
+ throw new Zend_Db_Adapter_Db2_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0) {
+ $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY";
+ return $limit_sql;
+ }
+
+ /**
+ * DB2 does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.*
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ return $limit_sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ if ($type == 'positional') {
+ return true;
+ }
+
+ // if its 'named' or anything else
+ return false;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $server_info = db2_server_info($this->_connection);
+ if ($server_info !== false) {
+ $version = $server_info->DBMS_VER;
+ if ($this->_isI5) {
+ $version = (int) substr($version, 0, 2) . '.' . (int) substr($version, 2, 2) . '.' . (int) substr($version, 4);
+ }
+ return $version;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return whether or not this is running on i5
+ *
+ * @return bool
+ */
+ public function isI5()
+ {
+ if ($this->_isI5 === null) {
+ $this->_determineI5();
+ }
+
+ return (bool) $this->_isI5;
+ }
+
+ /**
+ * Check the connection parameters according to verify
+ * type of used OS
+ *
+ * @return void
+ */
+ protected function _determineI5()
+ {
+ // first us the compiled flag.
+ $this->_isI5 = (php_uname('s') == 'OS400') ? true : false;
+
+ // if this is set, then us it
+ if (isset($this->_config['os'])){
+ if (strtolower($this->_config['os']) === 'i5') {
+ $this->_isI5 = true;
+ } else {
+ // any other value passed in, its null
+ $this->_isI5 = false;
+ }
+ }
+
+ }
+
+ /**
+ * Db2 On I5 specific method
+ *
+ * Returns a list of the tables in the database .
+ * Used only for DB2/400.
+ *
+ * @return array
+ */
+ protected function _i5listTables($schema = null)
+ {
+ //list of i5 libraries.
+ $tables = array();
+ if ($schema) {
+ $tablesStatement = db2_tables($this->_connection, null, $schema);
+ while ($rowTables = db2_fetch_assoc($tablesStatement) ) {
+ if ($rowTables['TABLE_NAME'] !== null) {
+ $tables[] = $rowTables['TABLE_NAME'];
+ }
+ }
+ } else {
+ $schemaStatement = db2_tables($this->_connection);
+ while ($schema = db2_fetch_assoc($schemaStatement)) {
+ if ($schema['TABLE_SCHEM'] !== null) {
+ // list of the tables which belongs to the selected library
+ $tablesStatement = db2_tables($this->_connection, NULL, $schema['TABLE_SCHEM']);
+ if (is_resource($tablesStatement)) {
+ while ($rowTables = db2_fetch_assoc($tablesStatement) ) {
+ if ($rowTables['TABLE_NAME'] !== null) {
+ $tables[] = $rowTables['TABLE_NAME'];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $tables;
+ }
+
+ protected function _i5LastInsertId($objectName = null, $idType = null)
+ {
+
+ if ($objectName === null) {
+ $sql = 'SELECT IDENTITY_VAL_LOCAL() AS VAL FROM QSYS2.QSQPTABL';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ if (strtoupper($idType) === 'S'){
+ //check i5_lib option
+ $sequenceName = $objectName;
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ //returns last identity value for the specified table
+ //if (strtoupper($idType) === 'I') {
+ $tableName = $objectName;
+ return $this->fetchOne('SELECT IDENTITY_VAL_LOCAL() from ' . $this->quoteIdentifier($tableName));
+ }
+
+}
+
+
diff --git a/library/vendor/Zend/Db/Adapter/Db2/Exception.php b/library/vendor/Zend/Db/Adapter/Db2/Exception.php
new file mode 100644
index 0000000..d12dc48
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Db2/Exception.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Adapter_Exception
+ */
+
+/**
+ * Zend_Db_Adapter_Db2_Exception
+ *
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Db2_Exception extends Zend_Db_Adapter_Exception
+{
+ protected $code = '00000';
+ protected $message = 'unknown exception';
+
+ function __construct($message = 'unknown exception', $code = '00000', Exception $e = null)
+ {
+ parent::__construct($message, $code, $e);
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Exception.php b/library/vendor/Zend/Db/Adapter/Exception.php
new file mode 100644
index 0000000..c302bc7
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Exception.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Exception extends Zend_Db_Exception
+{
+ protected $_chainedException = null;
+
+ public function __construct($message = '', $code = 0, Exception $e = null)
+ {
+ if ($e && (0 === $code)) {
+ $code = $e->getCode();
+ }
+ parent::__construct($message, $code, $e);
+ }
+
+ public function hasChainedException()
+ {
+ return ($this->getPrevious() !== null);
+ }
+
+ public function getChainedException()
+ {
+ return $this->getPrevious();
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Adapter/Mysqli.php b/library/vendor/Zend/Db/Adapter/Mysqli.php
new file mode 100644
index 0000000..087d3ac
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Mysqli.php
@@ -0,0 +1,543 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db_Profiler
+ */
+
+/**
+ * @see Zend_Db_Select
+ */
+
+/**
+ * @see Zend_Db_Statement_Mysqli
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Mysqli extends Zend_Db_Adapter_Abstract
+{
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'MEDIUMINT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'SERIAL' => Zend_Db::BIGINT_TYPE,
+ 'DEC' => Zend_Db::FLOAT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'FIXED' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * @var Zend_Db_Statement_Mysqli
+ */
+ protected $_stmt = null;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Mysqli';
+
+ /**
+ * Quote a raw string.
+ *
+ * @param mixed $value Raw string
+ *
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $this->_connect();
+ return "'" . $this->_connection->real_escape_string($value) . "'";
+ }
+
+ /**
+ * Returns the symbol the adapter uses for delimiting identifiers.
+ *
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return "`";
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $result = array();
+ // Use mysqli extension API, because SHOW doesn't work
+ // well as a prepared statement on MySQL 4.1.
+ $sql = 'SHOW TABLES';
+ if ($queryResult = $this->getConnection()->query($sql)) {
+ while ($row = $queryResult->fetch_row()) {
+ $result[] = $row[0];
+ }
+ $queryResult->close();
+ } else {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ /**
+ * @todo use INFORMATION_SCHEMA someday when
+ * MySQL's implementation isn't too slow.
+ */
+
+ if ($schemaName) {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
+ } else {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
+ }
+
+ /**
+ * Use mysqli extension API, because DESCRIBE doesn't work
+ * well as a prepared statement on MySQL 4.1.
+ */
+ if ($queryResult = $this->getConnection()->query($sql)) {
+ while ($row = $queryResult->fetch_assoc()) {
+ $result[] = $row;
+ }
+ $queryResult->close();
+ } else {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception($this->getConnection()->error);
+ }
+
+ $desc = array();
+
+ $row_defaults = array(
+ 'Length' => null,
+ 'Scale' => null,
+ 'Precision' => null,
+ 'Unsigned' => null,
+ 'Primary' => false,
+ 'PrimaryPosition' => null,
+ 'Identity' => false
+ );
+ $i = 1;
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $row = array_merge($row_defaults, $row);
+ if (preg_match('/unsigned/', $row['Type'])) {
+ $row['Unsigned'] = true;
+ }
+ if (preg_match('/^((?:var)?char)\((\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = $matches[1];
+ $row['Length'] = $matches[2];
+ } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = 'decimal';
+ $row['Precision'] = $matches[1];
+ $row['Scale'] = $matches[2];
+ } else if (preg_match('/^float\((\d+),(\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = 'float';
+ $row['Precision'] = $matches[1];
+ $row['Scale'] = $matches[2];
+ } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row['Type'], $matches)) {
+ $row['Type'] = $matches[1];
+ /**
+ * The optional argument of a MySQL int type is not precision
+ * or length; it is only a hint for display width.
+ */
+ }
+ if (strtoupper($row['Key']) == 'PRI') {
+ $row['Primary'] = true;
+ $row['PrimaryPosition'] = $p;
+ if ($row['Extra'] == 'auto_increment') {
+ $row['Identity'] = true;
+ } else {
+ $row['Identity'] = false;
+ }
+ ++$p;
+ }
+ $desc[$this->foldCase($row['Field'])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row['Field']),
+ 'COLUMN_POSITION' => $i,
+ 'DATA_TYPE' => $row['Type'],
+ 'DEFAULT' => $row['Default'],
+ 'NULLABLE' => (bool) ($row['Null'] == 'YES'),
+ 'LENGTH' => $row['Length'],
+ 'SCALE' => $row['Scale'],
+ 'PRECISION' => $row['Precision'],
+ 'UNSIGNED' => $row['Unsigned'],
+ 'PRIMARY' => $row['Primary'],
+ 'PRIMARY_POSITION' => $row['PrimaryPosition'],
+ 'IDENTITY' => $row['Identity']
+ );
+ ++$i;
+ }
+ return $desc;
+ }
+
+ /**
+ * Creates a connection to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Mysqli_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ if (!extension_loaded('mysqli')) {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception('The Mysqli extension is required for this adapter but the extension is not loaded');
+ }
+
+ if (isset($this->_config['port'])) {
+ $port = (integer) $this->_config['port'];
+ } else {
+ $port = null;
+ }
+
+ if (isset($this->_config['socket'])) {
+ $socket = $this->_config['socket'];
+ } else {
+ $socket = null;
+ }
+
+ $this->_connection = mysqli_init();
+
+ if(!empty($this->_config['driver_options'])) {
+ foreach($this->_config['driver_options'] as $option=>$value) {
+ if(is_string($option)) {
+ // Suppress warnings here
+ // Ignore it if it's not a valid constant
+ $option = @constant(strtoupper($option));
+ if($option === null)
+ continue;
+ }
+ mysqli_options($this->_connection, $option, $value);
+ }
+ }
+
+ // Suppress connection warnings here.
+ // Throw an exception instead.
+ $_isConnected = @mysqli_real_connect(
+ $this->_connection,
+ $this->_config['host'],
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['dbname'],
+ $port,
+ $socket
+ );
+
+ if ($_isConnected === false || mysqli_connect_errno()) {
+
+ $this->closeConnection();
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception(mysqli_connect_error());
+ }
+
+ if (!empty($this->_config['charset'])) {
+ mysqli_set_charset($this->_connection, $this->_config['charset']);
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) ($this->_connection instanceof mysqli));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ $this->_connection->close();
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Prepare a statement and return a PDOStatement-like object.
+ *
+ * @param string $sql SQL query
+ * @return Zend_Db_Statement_Mysqli
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ if ($this->_stmt) {
+ $this->_stmt->close();
+ }
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ if ($stmt === false) {
+ return false;
+ }
+ $stmt->setFetchMode($this->_fetchMode);
+ $this->_stmt = $stmt;
+ return $stmt;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * MySQL does not support sequences, so $tableName and $primaryKey are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @todo Return value should be int?
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $mysqli = $this->_connection;
+ return (string) $mysqli->insert_id;
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->autocommit(false);
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * @return void
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->commit();
+ $this->_connection->autocommit(true);
+ }
+
+ /**
+ * Roll-back a transaction.
+ *
+ * @return void
+ */
+ protected function _rollBack()
+ {
+ $this->_connect();
+ $this->_connection->rollback();
+ $this->_connection->autocommit(true);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @param int $mode
+ * @return void
+ * @throws Zend_Db_Adapter_Mysqli_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_LAZY:
+ case Zend_Db::FETCH_ASSOC:
+ case Zend_Db::FETCH_NUM:
+ case Zend_Db::FETCH_BOTH:
+ case Zend_Db::FETCH_NAMED:
+ case Zend_Db::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception("Invalid fetch mode '$mode' specified");
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param int $count
+ * @param int $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Mysqli_Exception
+ */
+ throw new Zend_Db_Adapter_Mysqli_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'positional':
+ return true;
+ case 'named':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ *@return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $version = $this->_connection->server_version;
+ $major = (int) ($version / 10000);
+ $minor = (int) ($version % 10000 / 100);
+ $revision = (int) ($version % 100);
+ return $major . '.' . $minor . '.' . $revision;
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Mysqli/Exception.php b/library/vendor/Zend/Db/Adapter/Mysqli/Exception.php
new file mode 100644
index 0000000..9c94adc
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Mysqli/Exception.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ *
+ */
+
+/**
+ * Zend
+ */
+
+/**
+ * Zend_Db_Adapter_Mysqli_Exception
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Mysqli_Exception extends Zend_Db_Adapter_Exception
+{
+}
diff --git a/library/vendor/Zend/Db/Adapter/Oracle.php b/library/vendor/Zend/Db/Adapter/Oracle.php
new file mode 100644
index 0000000..9ed8fb6
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Oracle.php
@@ -0,0 +1,631 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db_Statement_Oracle
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Oracle extends Zend_Db_Adapter_Abstract
+{
+ /**
+ * User-provided configuration.
+ *
+ * Basic keys are:
+ *
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * dbname => Either the name of the local Oracle instance, or the
+ * name of the entry in tnsnames.ora to which you want to connect.
+ * persistent => (boolean) Set TRUE to use a persistent connection
+ * @var array
+ */
+ protected $_config = array(
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ 'persistent' => false
+ );
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'NUMBER' => Zend_Db::FLOAT_TYPE,
+ );
+
+ /**
+ * @var integer
+ */
+ protected $_execute_mode = null;
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Oracle';
+
+ /**
+ * Check if LOB field are returned as string
+ * instead of OCI-Lob object
+ *
+ * @var boolean
+ */
+ protected $_lobAsString = null;
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('oci8')) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception('The OCI8 extension is required for this adapter but the extension is not loaded');
+ }
+
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+
+ $connectionFuncName = ($this->_config['persistent'] == true) ? 'oci_pconnect' : 'oci_connect';
+
+ $this->_connection = @$connectionFuncName(
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['dbname'],
+ $this->_config['charset']);
+
+ // check the connection
+ if (!$this->_connection) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception(oci_error());
+ }
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) (is_resource($this->_connection)
+ && (get_resource_type($this->_connection) == 'oci8 connection'
+ || get_resource_type($this->_connection) == 'oci8 persistent connection')));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ oci_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Activate/deactivate return of LOB as string
+ *
+ * @param string $lob_as_string
+ * @return Zend_Db_Adapter_Oracle
+ */
+ public function setLobAsString($lobAsString)
+ {
+ $this->_lobAsString = (bool) $lobAsString;
+ return $this;
+ }
+
+ /**
+ * Return whether or not LOB are returned as string
+ *
+ * @return boolean
+ */
+ public function getLobAsString()
+ {
+ if ($this->_lobAsString === null) {
+ // if never set by user, we use driver option if it exists otherwise false
+ if (isset($this->_config['driver_options']) &&
+ isset($this->_config['driver_options']['lob_as_string'])) {
+ $this->_lobAsString = (bool) $this->_config['driver_options']['lob_as_string'];
+ } else {
+ $this->_lobAsString = false;
+ }
+ }
+ return $this->_lobAsString;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Zend_Db_Statement_Oracle
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ if ($stmt instanceof Zend_Db_Statement_Oracle) {
+ $stmt->setLobAsString($this->getLobAsString());
+ }
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $value = str_replace("'", "''", $value);
+ return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
+ return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sql = 'SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual';
+ $value = $this->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Oracle does not support IDENTITY columns, so if the sequence is not
+ * specified, this method returns null.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ // No support for IDENTITY columns; return null
+ return null;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ $data = $this->fetchCol('SELECT table_name FROM all_tables');
+ return $data;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $version = $this->getServerVersion();
+ if (($version === null) || version_compare($version, '9.0.0', '>=')) {
+ $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC
+ LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
+ ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
+ ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ } else {
+ $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
+ from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
+ WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
+ AND ACC.TABLE_NAME = AC.TABLE_NAME
+ AND ACC.OWNER = AC.OWNER
+ AND AC.CONSTRAINT_TYPE = 'P'
+ AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC, ($subSql) CC
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
+ AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ }
+
+ $stmt = $this->query($sql, $bind);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 0;
+ $owner = 1;
+ $column_name = 2;
+ $data_type = 3;
+ $data_default = 4;
+ $nullable = 5;
+ $column_id = 6;
+ $data_length = 7;
+ $data_scale = 8;
+ $data_precision = 9;
+ $constraint_type = 10;
+ $position = 11;
+
+ $desc = array();
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$constraint_type] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$position];
+ /**
+ * Oracle does not support auto-increment keys.
+ */
+ $identity = false;
+ }
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$owner]),
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => $row[$column_id],
+ 'DATA_TYPE' => $row[$data_type],
+ 'DEFAULT' => $row[$data_default],
+ 'NULLABLE' => (bool) ($row[$nullable] == 'Y'),
+ 'LENGTH' => $row[$data_length],
+ 'SCALE' => $row[$data_scale],
+ 'PRECISION' => $row[$data_precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return void
+ */
+ protected function _beginTransaction()
+ {
+ $this->_setExecuteMode(OCI_DEFAULT);
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ protected function _commit()
+ {
+ if (!oci_commit($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection));
+ }
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ protected function _rollBack()
+ {
+ if (!oci_rollback($this->_connection)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception(oci_error($this->_connection));
+ }
+ $this->_setExecuteMode(OCI_COMMIT_ON_SUCCESS);
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param integer $mode A fetch mode.
+ * @return void
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM: // seq array
+ case Zend_Db::FETCH_ASSOC: // assoc array
+ case Zend_Db::FETCH_BOTH: // seq+assoc array
+ case Zend_Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ /**
+ * Oracle does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ return $limit_sql;
+ }
+
+ /**
+ * @param integer $mode
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ private function _setExecuteMode($mode)
+ {
+ switch($mode) {
+ case OCI_COMMIT_ON_SUCCESS:
+ case OCI_DEFAULT:
+ case OCI_DESCRIBE_ONLY:
+ $this->_execute_mode = $mode;
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Adapter_Oracle_Exception("Invalid execution mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * @return int
+ */
+ public function _getExecuteMode()
+ {
+ return $this->_execute_mode;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'named':
+ return true;
+ case 'positional':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $version = oci_server_version($this->_connection);
+ if ($version !== false) {
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Oracle/Exception.php b/library/vendor/Zend/Db/Adapter/Oracle/Exception.php
new file mode 100644
index 0000000..6b7d914
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Oracle/Exception.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Adapter_Exception
+ */
+
+/**
+ * Zend_Db_Adapter_Oracle_Exception
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Oracle_Exception extends Zend_Db_Adapter_Exception
+{
+ protected $message = 'Unknown exception';
+ protected $code = 0;
+
+ function __construct($error = null, $code = 0) {
+ if (is_array($error)) {
+ if (!isset($error['offset'])) {
+ $this->message = $error['code'] .' '. $error['message'];
+ } else {
+ $this->message = $error['code'] .' '. $error['message']." "
+ . substr($error['sqltext'], 0, $error['offset'])
+ . "*"
+ . substr($error['sqltext'], $error['offset']);
+ }
+ $this->code = $error['code'];
+ } else if (is_string($error)) {
+ $this->message = $error;
+ }
+ if (!$this->code && $code) {
+ $this->code = $code;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Abstract.php b/library/vendor/Zend/Db/Adapter/Pdo/Abstract.php
new file mode 100644
index 0000000..3a0e13e
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Abstract.php
@@ -0,0 +1,397 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+
+/**
+ * @see Zend_Db_Statement_Pdo
+ */
+
+
+/**
+ * Class for connecting to SQL databases and performing common operations using PDO.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
+{
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Pdo';
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ // don't pass the username, password, charset, persistent and driver_options in the DSN
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['charset']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+
+ // use all remaining parts in the DSN
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ return $this->_pdoType . ':' . implode(';', $dsn);
+ }
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ // if we already have a PDO object, no need to re-connect.
+ if ($this->_connection) {
+ return;
+ }
+
+ // get the dsn first, because some adapters alter the $_pdoType
+ $dsn = $this->_dsn();
+
+ // check for PDO extension
+ if (!extension_loaded('pdo')) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
+ }
+
+ // check the PDO driver is available
+ if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed');
+ }
+
+ // create PDO connection
+ $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT);
+
+ // add the persistence flag if we find it in our config array
+ if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) {
+ $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
+ }
+
+ try {
+ $this->_connection = new PDO(
+ $dsn,
+ $this->_config['username'],
+ $this->_config['password'],
+ $this->_config['driver_options']
+ );
+
+ $this->_profiler->queryEnd($q);
+
+ // set the PDO connection to perform case-folding on array keys, or not
+ $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
+
+ // always use exceptions.
+ $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ } catch (PDOException $e) {
+ $message = $e->getMessage();
+ if ($e->getPrevious() !== null && preg_match('~^SQLSTATE\[HY000\] \[\d{1,4}\]\s$~', $message)) {
+ // See https://bugs.php.net/bug.php?id=76604
+ $message .= $e->getPrevious()->getMessage();
+ }
+
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception($message, $e->getCode(), $e);
+ }
+
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return ((bool) ($this->_connection instanceof PDO));
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ $this->_connection = null;
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PDOStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ if (!class_exists($stmtClass)) {
+ Zend_Loader::loadClass($stmtClass);
+ }
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * On RDBMS brands that don't support sequences, $tableName and $primaryKey
+ * are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+ return $this->_connection->lastInsertId();
+ }
+
+ /**
+ * Special handling for PDO query().
+ * All bind parameter names must begin with ':'
+ *
+ * @param string|Zend_Db_Select $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return Zend_Db_Statement_Pdo
+ * @throws Zend_Db_Adapter_Exception To re-throw PDOException.
+ */
+ public function query($sql, $bind = array())
+ {
+ if (empty($bind) && $sql instanceof Zend_Db_Select) {
+ $bind = $sql->getBind();
+ }
+
+ if (is_array($bind)) {
+ foreach ($bind as $name => $value) {
+ if (!is_int($name) && !preg_match('/^:/', $name)) {
+ $newName = ":$name";
+ unset($bind[$name]);
+ $bind[$newName] = $value;
+ }
+ }
+ }
+
+ try {
+ return parent::query($sql, $bind);
+ } catch (PDOException $e) {
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes an SQL statement and return the number of affected rows
+ *
+ * @param mixed $sql The SQL statement with placeholders.
+ * May be a string or Zend_Db_Select.
+ * @return integer Number of rows that were modified
+ * or deleted by the SQL statement
+ */
+ public function exec($sql)
+ {
+ if ($sql instanceof Zend_Db_Select) {
+ $sql = $sql->assemble();
+ }
+
+ try {
+ $affected = $this->getConnection()->exec($sql);
+
+ if ($affected === false) {
+ $errorInfo = $this->getConnection()->errorInfo();
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception($errorInfo[2]);
+ }
+
+ return $affected;
+ } catch (PDOException $e) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if ($value === null) {
+ $value = '';
+ } elseif (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $this->_connect();
+ return $this->_connection->quote($value);
+ }
+
+ /**
+ * Begin a transaction.
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->beginTransaction();
+ }
+
+ /**
+ * Commit a transaction.
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->commit();
+ }
+
+ /**
+ * Roll-back a transaction.
+ */
+ protected function _rollBack() {
+ $this->_connect();
+ $this->_connection->rollBack();
+ }
+
+ /**
+ * Set the PDO fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param int $mode A PDO fetch mode.
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ //check for PDO extension
+ if (!extension_loaded('pdo')) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
+ }
+ switch ($mode) {
+ case PDO::FETCH_LAZY:
+ case PDO::FETCH_ASSOC:
+ case PDO::FETCH_NUM:
+ case PDO::FETCH_BOTH:
+ case PDO::FETCH_NAMED:
+ case PDO::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ switch ($type) {
+ case 'positional':
+ case 'named':
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ try {
+ $version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
+ } catch (PDOException $e) {
+ // In case of the driver doesn't support getting attributes
+ return null;
+ }
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Ibm.php b/library/vendor/Zend/Db/Adapter/Pdo/Ibm.php
new file mode 100644
index 0000000..cfb11a3
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Ibm.php
@@ -0,0 +1,354 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** @see Zend_Db_Adapter_Pdo_Abstract */
+
+/** @see Zend_Db_Abstract_Pdo_Ibm_Db2 */
+
+/** @see Zend_Db_Abstract_Pdo_Ibm_Ids */
+
+/** @see Zend_Db_Statement_Pdo_Ibm */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Ibm extends Zend_Db_Adapter_Pdo_Abstract
+{
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'ibm';
+
+ /**
+ * The IBM data server connected to
+ *
+ * @var string
+ */
+ protected $_serverType = null;
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DEC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * The IBM data server is set.
+ * Current options are DB2 or IDS
+ * @todo also differentiate between z/OS and i/5
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+
+ $this->getConnection()->setAttribute(Zend_Db::ATTR_STRINGIFY_FETCHES, true);
+
+ try {
+ if ($this->_serverType === null) {
+ $server = substr($this->getConnection()->getAttribute(PDO::ATTR_SERVER_INFO), 0, 3);
+
+ switch ($server) {
+ case 'DB2':
+ $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Db2($this);
+
+ // Add DB2-specific numeric types
+ $this->_numericDataTypes['DECFLOAT'] = Zend_Db::FLOAT_TYPE;
+ $this->_numericDataTypes['DOUBLE'] = Zend_Db::FLOAT_TYPE;
+ $this->_numericDataTypes['NUM'] = Zend_Db::FLOAT_TYPE;
+
+ break;
+ case 'IDS':
+ $this->_serverType = new Zend_Db_Adapter_Pdo_Ibm_Ids($this);
+
+ // Add IDS-specific numeric types
+ $this->_numericDataTypes['SERIAL'] = Zend_Db::INT_TYPE;
+ $this->_numericDataTypes['SERIAL8'] = Zend_Db::BIGINT_TYPE;
+ $this->_numericDataTypes['INT8'] = Zend_Db::BIGINT_TYPE;
+ $this->_numericDataTypes['SMALLFLOAT'] = Zend_Db::FLOAT_TYPE;
+ $this->_numericDataTypes['MONEY'] = Zend_Db::FLOAT_TYPE;
+
+ break;
+ }
+ }
+ } catch (PDOException $e) {
+ /** @see Zend_Db_Adapter_Exception */
+ $error = strpos($e->getMessage(), 'driver does not support that attribute');
+ if ($error) {
+ throw new Zend_Db_Adapter_Exception("PDO_IBM driver extension is downlevel. Please use driver release version 1.2.1 or later", 0, $e);
+ } else {
+ throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ $this->_checkRequiredOptions($this->_config);
+
+ // check if using full connection string
+ if (array_key_exists('host', $this->_config)) {
+ $dsn = ';DATABASE=' . $this->_config['dbname']
+ . ';HOSTNAME=' . $this->_config['host']
+ . ';PORT=' . $this->_config['port']
+ // PDO_IBM supports only DB2 TCPIP protocol
+ . ';PROTOCOL=' . 'TCPIP;';
+ } else {
+ // catalogued connection
+ $dsn = $this->_config['dbname'];
+ }
+ return $this->_pdoType . ': ' . $dsn;
+ }
+
+ /**
+ * Checks required options
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ * @return void
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ parent::_checkRequiredOptions($config);
+
+ if (array_key_exists('host', $this->_config) &&
+ !array_key_exists('port', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("Configuration must have a key for 'port' when 'host' is specified");
+ }
+ }
+
+ /**
+ * Prepares an SQL statement.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @param array $bind An array of data to bind to the placeholders.
+ * @return PDOStatement
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ return $this->_serverType->listTables();
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $this->_connect();
+ return $this->_serverType->describeTable($tableName, $schemaName);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ * Special handling for PDO_IBM
+ * remove empty slots
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ */
+ public function insert($table, array $bind)
+ {
+ $this->_connect();
+ $newbind = array();
+ if (is_array($bind)) {
+ foreach ($bind as $name => $value) {
+ if($value !== null) {
+ $newbind[$name] = $value;
+ }
+ }
+ }
+
+ return parent::insert($table, $newbind);
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $this->_connect();
+ return $this->_serverType->limit($sql, $count, $offset);
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT
+ * column.
+ *
+ * @param string $tableName OPTIONAL
+ * @param string $primaryKey OPTIONAL
+ * @return integer
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $this->_connect();
+
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+
+ $id = $this->getConnection()->lastInsertId();
+
+ return $id;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ return $this->_serverType->lastSequenceId($sequenceName);
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database,
+ * and return it.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ return $this->_serverType->nextSequenceId($sequenceName);
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Idm doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query('SELECT service_level, fixpack_num FROM TABLE (sysproc.env_get_inst_info()) as INSTANCEINFO');
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result)) {
+ $matches = null;
+ if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $result[0][0], $matches)) {
+ return $matches[1];
+ } else {
+ return null;
+ }
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Ibm/Db2.php b/library/vendor/Zend/Db/Adapter/Pdo/Ibm/Db2.php
new file mode 100644
index 0000000..1c11c8b
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Ibm/Db2.php
@@ -0,0 +1,224 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** @see Zend_Db_Adapter_Pdo_Ibm */
+
+/** @see Zend_Db_Statement_Pdo_Ibm */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Ibm_Db2
+{
+ /**
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $_adapter = null;
+
+ /**
+ * Construct the data server class.
+ *
+ * It will be used to generate non-generic SQL
+ * for a particular data server
+ *
+ * @param Zend_Db_Adapter_Abstract $adapter
+ */
+ public function __construct($adapter)
+ {
+ $this->_adapter = $adapter;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT tabname "
+ . "FROM SYSCAT.TABLES ";
+ return $this->_adapter->fetchCol($sql);
+ }
+
+ /**
+ * DB2 catalog lookup for describe table
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno,
+ c.typename, c.default, c.nulls, c.length, c.scale,
+ c.identity, tc.type AS tabconsttype, k.colseq
+ FROM syscat.columns c
+ LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc
+ ON (k.tabschema = tc.tabschema
+ AND k.tabname = tc.tabname
+ AND tc.type = 'P'))
+ ON (c.tabschema = k.tabschema
+ AND c.tabname = k.tabname
+ AND c.colname = k.colname)
+ WHERE "
+ . $this->_adapter->quoteInto('UPPER(c.tabname) = UPPER(?)', $tableName);
+ if ($schemaName) {
+ $sql .= $this->_adapter->quoteInto(' AND UPPER(c.tabschema) = UPPER(?)', $schemaName);
+ }
+ $sql .= " ORDER BY c.colno";
+
+ $desc = array();
+ $stmt = $this->_adapter->query($sql);
+
+ /**
+ * To avoid case issues, fetch using FETCH_NUM
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $nulls = 6;
+ $length = 7;
+ $scale = 8;
+ $identityCol = 9;
+ $tabconstype = 10;
+ $colseq = 11;
+
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$tabconstype] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$colseq];
+ }
+ /**
+ * In IBM DB2, an column can be IDENTITY
+ * even if it is not part of the PRIMARY KEY.
+ */
+ if ($row[$identityCol] == 'Y') {
+ $identity = true;
+ }
+
+ $desc[$this->_adapter->foldCase($row[$colname])] = array(
+ 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$colno]+1,
+ 'DATA_TYPE' => $row[$typename],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$nulls] == 'Y'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => ($row[$typename] == 'DECIMAL' ? $row[$length] : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Adds a DB2-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ } else {
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0 && $count > 0) {
+ $limit_sql = $sql . " FETCH FIRST $count ROWS ONLY";
+ return $limit_sql;
+ }
+ /**
+ * DB2 does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT ROW_NUMBER() OVER() AS \"ZEND_DB_ROWNUM\", z1.*
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.zend_db_rownum BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ }
+ return $limit_sql;
+ }
+
+ /**
+ * DB2-specific last sequence id
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT PREVVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * DB2-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT NEXTVAL FOR '.$this->_adapter->quoteIdentifier($sequenceName).' AS VAL FROM SYSIBM.SYSDUMMY1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Ibm/Ids.php b/library/vendor/Zend/Db/Adapter/Pdo/Ibm/Ids.php
new file mode 100644
index 0000000..eeec43f
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Ibm/Ids.php
@@ -0,0 +1,297 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** @see Zend_Db_Adapter_Pdo_Ibm */
+
+/** @see Zend_Db_Statement_Pdo_Ibm */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Ibm_Ids
+{
+ /**
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $_adapter = null;
+
+ /**
+ * Construct the data server class.
+ *
+ * It will be used to generate non-generic SQL
+ * for a particular data server
+ *
+ * @param Zend_Db_Adapter_Abstract $adapter
+ */
+ public function __construct($adapter)
+ {
+ $this->_adapter = $adapter;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT tabname "
+ . "FROM systables ";
+
+ return $this->_adapter->fetchCol($sql);
+ }
+
+ /**
+ * IDS catalog lookup for describe table
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // this is still a work in progress
+
+ $sql= "SELECT DISTINCT t.owner, t.tabname, c.colname, c.colno, c.coltype,
+ d.default, c.collength, t.tabid
+ FROM syscolumns c
+ JOIN systables t ON c.tabid = t.tabid
+ LEFT JOIN sysdefaults d ON c.tabid = d.tabid AND c.colno = d.colno
+ WHERE "
+ . $this->_adapter->quoteInto('UPPER(t.tabname) = UPPER(?)', $tableName);
+ if ($schemaName) {
+ $sql .= $this->_adapter->quoteInto(' AND UPPER(t.owner) = UPPER(?)', $schemaName);
+ }
+ $sql .= " ORDER BY c.colno";
+
+ $desc = array();
+ $stmt = $this->_adapter->query($sql);
+
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ /**
+ * The ordering of columns is defined by the query so we can map
+ * to variables to improve readability
+ */
+ $tabschema = 0;
+ $tabname = 1;
+ $colname = 2;
+ $colno = 3;
+ $typename = 4;
+ $default = 5;
+ $length = 6;
+ $tabid = 7;
+
+ $primaryCols = null;
+
+ foreach ($result as $key => $row) {
+ $primary = false;
+ $primaryPosition = null;
+
+ if (!$primaryCols) {
+ $primaryCols = $this->_getPrimaryInfo($row[$tabid]);
+ }
+
+ if (array_key_exists($row[$colno], $primaryCols)) {
+ $primary = true;
+ $primaryPosition = $primaryCols[$row[$colno]];
+ }
+
+ $identity = false;
+ if ($row[$typename] == 6 + 256 ||
+ $row[$typename] == 18 + 256) {
+ $identity = true;
+ }
+
+ $desc[$this->_adapter->foldCase($row[$colname])] = array (
+ 'SCHEMA_NAME' => $this->_adapter->foldCase($row[$tabschema]),
+ 'TABLE_NAME' => $this->_adapter->foldCase($row[$tabname]),
+ 'COLUMN_NAME' => $this->_adapter->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$colno],
+ 'DATA_TYPE' => $this->_getDataType($row[$typename]),
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) !($row[$typename] - 256 >= 0),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => ($row[$typename] == 5 ? $row[$length]&255 : 0),
+ 'PRECISION' => ($row[$typename] == 5 ? (int)($row[$length]/256) : 0),
+ 'UNSIGNED' => false,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Map number representation of a data type
+ * to a string
+ *
+ * @param int $typeNo
+ * @return string
+ */
+ protected function _getDataType($typeNo)
+ {
+ $typemap = array(
+ 0 => "CHAR",
+ 1 => "SMALLINT",
+ 2 => "INTEGER",
+ 3 => "FLOAT",
+ 4 => "SMALLFLOAT",
+ 5 => "DECIMAL",
+ 6 => "SERIAL",
+ 7 => "DATE",
+ 8 => "MONEY",
+ 9 => "NULL",
+ 10 => "DATETIME",
+ 11 => "BYTE",
+ 12 => "TEXT",
+ 13 => "VARCHAR",
+ 14 => "INTERVAL",
+ 15 => "NCHAR",
+ 16 => "NVARCHAR",
+ 17 => "INT8",
+ 18 => "SERIAL8",
+ 19 => "SET",
+ 20 => "MULTISET",
+ 21 => "LIST",
+ 22 => "Unnamed ROW",
+ 40 => "Variable-length opaque type",
+ 4118 => "Named ROW"
+ );
+
+ if ($typeNo - 256 >= 0) {
+ $typeNo = $typeNo - 256;
+ }
+
+ return $typemap[$typeNo];
+ }
+
+ /**
+ * Helper method to retrieve primary key column
+ * and column location
+ *
+ * @param int $tabid
+ * @return array
+ */
+ protected function _getPrimaryInfo($tabid)
+ {
+ $sql = "SELECT i.part1, i.part2, i.part3, i.part4, i.part5, i.part6,
+ i.part7, i.part8, i.part9, i.part10, i.part11, i.part12,
+ i.part13, i.part14, i.part15, i.part16
+ FROM sysindexes i
+ JOIN sysconstraints c ON c.idxname = i.idxname
+ WHERE i.tabid = " . $tabid . " AND c.constrtype = 'P'";
+
+ $stmt = $this->_adapter->query($sql);
+ $results = $stmt->fetchAll();
+
+ $cols = array();
+
+ // this should return only 1 row
+ // unless there is no primary key,
+ // in which case, the empty array is returned
+ if ($results) {
+ $row = $results[0];
+ } else {
+ return $cols;
+ }
+
+ $position = 0;
+ foreach ($row as $key => $colno) {
+ $position++;
+ if ($colno == 0) {
+ return $cols;
+ } else {
+ $cols[$colno] = $position;
+ }
+ }
+ }
+
+ /**
+ * Adds an IDS-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ } else if ($count == 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT * FROM (SELECT", $sql);
+ $limit_sql .= ") WHERE 0 = 1";
+ } else {
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+ if ($offset == 0) {
+ $limit_sql = str_ireplace("SELECT", "SELECT FIRST $count", $sql);
+ } else {
+ $limit_sql = str_ireplace("SELECT", "SELECT SKIP $offset LIMIT $count", $sql);
+ }
+ }
+ return $limit_sql;
+ }
+
+ /**
+ * IDS-specific last sequence id
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.CURRVAL FROM '
+ .'systables WHERE tabid = 1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+
+ /**
+ * IDS-specific sequence id value
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $sql = 'SELECT '.$this->_adapter->quoteIdentifier($sequenceName).'.NEXTVAL FROM '
+ .'systables WHERE tabid = 1';
+ $value = $this->_adapter->fetchOne($sql);
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Mssql.php b/library/vendor/Zend/Db/Adapter/Pdo/Mssql.php
new file mode 100644
index 0000000..7c7c1cd
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Mssql.php
@@ -0,0 +1,435 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Pdo_Abstract
+ */
+
+
+/**
+ * Class for connecting to Microsoft SQL Server databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Mssql extends Zend_Db_Adapter_Pdo_Abstract
+{
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'mssql';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'MONEY' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ // don't pass the username and password in the DSN
+ unset($dsn['username']);
+ unset($dsn['password']);
+ unset($dsn['options']);
+ unset($dsn['persistent']);
+ unset($dsn['driver_options']);
+
+ if (isset($dsn['port'])) {
+ $seperator = ':';
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+ $seperator = ',';
+ }
+ $dsn['host'] .= $seperator . $dsn['port'];
+ unset($dsn['port']);
+ }
+
+ // this driver supports multiple DSN prefixes
+ // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
+ if (isset($dsn['pdoType'])) {
+ switch (strtolower($dsn['pdoType'])) {
+ case 'freetds':
+ case 'sybase':
+ $this->_pdoType = 'sybase';
+ break;
+ case 'mssql':
+ $this->_pdoType = 'mssql';
+ break;
+ case 'dblib':
+ default:
+ $this->_pdoType = 'dblib';
+ break;
+ }
+ unset($dsn['pdoType']);
+ }
+
+ // use all remaining parts in the DSN
+ foreach ($dsn as $key => $val) {
+ $dsn[$key] = "$key=$val";
+ }
+
+ $dsn = $this->_pdoType . ':' . implode(';', $dsn);
+ return $dsn;
+ }
+
+ /**
+ * @return void
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+ parent::_connect();
+ $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
+ }
+
+ /**
+ * Begin a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _beginTransaction()
+ {
+ $this->_connect();
+ $this->_connection->exec('BEGIN TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Commit a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _commit()
+ {
+ $this->_connect();
+ $this->_connection->exec('COMMIT TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Roll-back a transaction.
+ *
+ * It is necessary to override the abstract PDO transaction functions here, as
+ * the PDO driver for MSSQL does not support transactions.
+ */
+ protected function _rollBack() {
+ $this->_connect();
+ $this->_connection->exec('ROLLBACK TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * PRIMARY_AUTO => integer; position of auto-generated column in primary key
+ *
+ * @todo Discover column primary key position.
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ if ($schemaName != null) {
+ if (strpos($schemaName, '.') !== false) {
+ $result = explode('.', $schemaName);
+ $schemaName = $result[1];
+ }
+ }
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
+ if ($schemaName != null) {
+ $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
+ }
+
+ $stmt = $this->query($sql);
+ $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ $primaryKeyColumn = array();
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = array();
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql = preg_replace(
+ '/^SELECT\s+(DISTINCT\s)?/i',
+ 'SELECT $1TOP ' . ($count+$offset) . ' ',
+ $sql
+ );
+
+ if ($offset > 0) {
+ $orderby = stristr($sql, 'ORDER BY');
+
+ if ($orderby !== false) {
+ $orderParts = explode(',', substr($orderby, 8));
+ $pregReplaceCount = null;
+ $orderbyInverseParts = array();
+ foreach ($orderParts as $orderPart) {
+ $orderPart = rtrim($orderPart);
+ $inv = preg_replace('/\s+desc$/i', ' ASC', $orderPart, 1, $pregReplaceCount);
+ if ($pregReplaceCount) {
+ $orderbyInverseParts[] = $inv;
+ continue;
+ }
+ $inv = preg_replace('/\s+asc$/i', ' DESC', $orderPart, 1, $pregReplaceCount);
+ if ($pregReplaceCount) {
+ $orderbyInverseParts[] = $inv;
+ continue;
+ } else {
+ $orderbyInverseParts[] = $orderPart . ' DESC';
+ }
+ }
+
+ $orderbyInverse = 'ORDER BY ' . implode(', ', $orderbyInverseParts);
+ }
+
+
+
+
+ $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
+ if ($orderby !== false) {
+ $sql .= ' ' . $orderbyInverse . ' ';
+ }
+ $sql .= ') AS outer_tbl';
+ if ($orderby !== false) {
+ $sql .= ' ' . $orderby;
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Microsoft SQL Server does not support sequences, so the arguments to
+ * this method are ignored.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @throws Zend_Db_Adapter_Exception
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ $sql = 'SELECT SCOPE_IDENTITY()';
+ return (int)$this->fetchOne($sql);
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ * Pdo_Mssql doesn't support getAttribute(PDO::ATTR_SERVER_VERSION)
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ try {
+ $stmt = $this->query("SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR)");
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ if (count($result)) {
+ return $result[0][0];
+ }
+ return null;
+ } catch (PDOException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (!is_int($value) && !is_float($value)) {
+ // Fix for null-byte injection
+ $value = addcslashes($value, "\000\032");
+ }
+ return parent::_quote($value);
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Mysql.php b/library/vendor/Zend/Db/Adapter/Pdo/Mysql.php
new file mode 100644
index 0000000..951d665
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Mysql.php
@@ -0,0 +1,269 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Pdo_Abstract
+ */
+
+
+/**
+ * Class for connecting to MySQL databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Mysql extends Zend_Db_Adapter_Pdo_Abstract
+{
+
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'mysql';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'MEDIUMINT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'SERIAL' => Zend_Db::BIGINT_TYPE,
+ 'DEC' => Zend_Db::FLOAT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'FIXED' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Override _dsn() and ensure that charset is incorporated in mysql
+ * @see Zend_Db_Adapter_Pdo_Abstract::_dsn()
+ */
+ protected function _dsn()
+ {
+ $dsn = parent::_dsn();
+ if (isset($this->_config['charset'])) {
+ $dsn .= ';charset=' . $this->_config['charset'];
+ }
+ return $dsn;
+ }
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ if (!empty($this->_config['charset'])
+ && version_compare(PHP_VERSION, '5.3.6', '<')
+ ) {
+ $initCommand = "SET NAMES '" . $this->_config['charset'] . "'";
+ $this->_config['driver_options'][1002] = $initCommand; // 1002 = PDO::MYSQL_ATTR_INIT_COMMAND
+ }
+
+ parent::_connect();
+ }
+
+ /**
+ * @return string
+ */
+ public function getQuoteIdentifierSymbol()
+ {
+ return "`";
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ return $this->fetchCol('SHOW TABLES');
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ // @todo use INFORMATION_SCHEMA someday when MySQL's
+ // implementation has reasonably good performance and
+ // the version with this improvement is in wide use.
+
+ if ($schemaName) {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier("$schemaName.$tableName", true);
+ } else {
+ $sql = 'DESCRIBE ' . $this->quoteIdentifier($tableName, true);
+ }
+ $stmt = $this->query($sql);
+
+ // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $field = 0;
+ $type = 1;
+ $null = 2;
+ $key = 3;
+ $default = 4;
+ $extra = 5;
+
+ $desc = array();
+ $i = 1;
+ $p = 1;
+ foreach ($result as $row) {
+ list($length, $scale, $precision, $unsigned, $primary, $primaryPosition, $identity)
+ = array(null, null, null, null, false, null, false);
+ if (preg_match('/unsigned/', $row[$type])) {
+ $unsigned = true;
+ }
+ if (preg_match('/^((?:var)?char)\((\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ $length = $matches[2];
+ } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = 'decimal';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ } else if (preg_match('/^float\((\d+),(\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = 'float';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)/', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ // The optional argument of a MySQL int type is not precision
+ // or length; it is only a hint for display width.
+ }
+ if (strtoupper($row[$key]) == 'PRI') {
+ $primary = true;
+ $primaryPosition = $p;
+ if ($row[$extra] == 'auto_increment') {
+ $identity = true;
+ } else {
+ $identity = false;
+ }
+ ++$p;
+ }
+ $desc[$this->foldCase($row[$field])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row[$field]),
+ 'COLUMN_POSITION' => $i,
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $row[$default],
+ 'NULLABLE' => (bool) ($row[$null] == 'YES'),
+ 'LENGTH' => $length,
+ 'SCALE' => $scale,
+ 'PRECISION' => $precision,
+ 'UNSIGNED' => $unsigned,
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ ++$i;
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Oci.php b/library/vendor/Zend/Db/Adapter/Pdo/Oci.php
new file mode 100644
index 0000000..eb4e6fc
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Oci.php
@@ -0,0 +1,375 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Pdo_Abstract
+ */
+
+
+/**
+ * Class for connecting to Oracle databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Oci extends Zend_Db_Adapter_Pdo_Abstract
+{
+
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'oci';
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Pdo_Oci';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'BINARY_DOUBLE' => Zend_Db::FLOAT_TYPE,
+ 'BINARY_FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'NUMBER' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO DSN for the adapter from $this->_config settings.
+ *
+ * @return string
+ */
+ protected function _dsn()
+ {
+ // baseline of DSN parts
+ $dsn = $this->_config;
+
+ if (isset($dsn['host'])) {
+ $tns = 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' .
+ '(HOST=' . $dsn['host'] . ')';
+
+ if (isset($dsn['port'])) {
+ $tns .= '(PORT=' . $dsn['port'] . ')';
+ } else {
+ $tns .= '(PORT=1521)';
+ }
+
+ $tns .= '))(CONNECT_DATA=(SID=' . $dsn['dbname'] . ')))';
+ } else {
+ $tns = 'dbname=' . $dsn['dbname'];
+ }
+
+ if (isset($dsn['charset'])) {
+ $tns .= ';charset=' . $dsn['charset'];
+ }
+
+ return $this->_pdoType . ':' . $tns;
+ }
+
+ /**
+ * Quote a raw string.
+ * Most PDO drivers have an implementation for the quote() method,
+ * but the Oracle OCI driver must use the same implementation as the
+ * Zend_Db_Adapter_Abstract class.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value) || is_float($value)) {
+ return $value;
+ }
+ $value = str_replace("'", "''", $value);
+ return "'" . addcslashes($value, "\000\n\r\\\032") . "'";
+ }
+
+ /**
+ * Quote a table identifier and alias.
+ *
+ * @param string|array|Zend_Db_Expr $ident The identifier or expression.
+ * @param string $alias An alias for the table.
+ * @return string The quoted identifier and alias.
+ */
+ public function quoteTableAs($ident, $alias = null, $auto = false)
+ {
+ // Oracle doesn't allow the 'AS' keyword between the table identifier/expression and alias.
+ return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $data = $this->fetchCol('SELECT table_name FROM all_tables');
+ return $data;
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $version = $this->getServerVersion();
+ if (($version === null) || version_compare($version, '9.0.0', '>=')) {
+ $sql = "SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, C.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC
+ LEFT JOIN (ALL_CONS_COLUMNS CC JOIN ALL_CONSTRAINTS C
+ ON (CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME AND CC.TABLE_NAME = C.TABLE_NAME AND CC.OWNER = C.OWNER AND C.CONSTRAINT_TYPE = 'P'))
+ ON TC.TABLE_NAME = CC.TABLE_NAME AND TC.COLUMN_NAME = CC.COLUMN_NAME
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ } else {
+ $subSql="SELECT AC.OWNER, AC.TABLE_NAME, ACC.COLUMN_NAME, AC.CONSTRAINT_TYPE, ACC.POSITION
+ from ALL_CONSTRAINTS AC, ALL_CONS_COLUMNS ACC
+ WHERE ACC.CONSTRAINT_NAME = AC.CONSTRAINT_NAME
+ AND ACC.TABLE_NAME = AC.TABLE_NAME
+ AND ACC.OWNER = AC.OWNER
+ AND AC.CONSTRAINT_TYPE = 'P'
+ AND UPPER(AC.TABLE_NAME) = UPPER(:TBNAME)";
+ $bind[':TBNAME'] = $tableName;
+ if ($schemaName) {
+ $subSql .= ' AND UPPER(ACC.OWNER) = UPPER(:SCNAME)';
+ $bind[':SCNAME'] = $schemaName;
+ }
+ $sql="SELECT TC.TABLE_NAME, TC.OWNER, TC.COLUMN_NAME, TC.DATA_TYPE,
+ TC.DATA_DEFAULT, TC.NULLABLE, TC.COLUMN_ID, TC.DATA_LENGTH,
+ TC.DATA_SCALE, TC.DATA_PRECISION, CC.CONSTRAINT_TYPE, CC.POSITION
+ FROM ALL_TAB_COLUMNS TC, ($subSql) CC
+ WHERE UPPER(TC.TABLE_NAME) = UPPER(:TBNAME)
+ AND TC.OWNER = CC.OWNER(+) AND TC.TABLE_NAME = CC.TABLE_NAME(+) AND TC.COLUMN_NAME = CC.COLUMN_NAME(+)";
+ if ($schemaName) {
+ $sql .= ' AND UPPER(TC.OWNER) = UPPER(:SCNAME)';
+ }
+ $sql .= ' ORDER BY TC.COLUMN_ID';
+ }
+
+ $stmt = $this->query($sql, $bind);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $table_name = 0;
+ $owner = 1;
+ $column_name = 2;
+ $data_type = 3;
+ $data_default = 4;
+ $nullable = 5;
+ $column_id = 6;
+ $data_length = 7;
+ $data_scale = 8;
+ $data_precision = 9;
+ $constraint_type = 10;
+ $position = 11;
+
+ $desc = array();
+ foreach ($result as $key => $row) {
+ list ($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$constraint_type] == 'P') {
+ $primary = true;
+ $primaryPosition = $row[$position];
+ /**
+ * Oracle does not support auto-increment keys.
+ */
+ $identity = false;
+ }
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$owner]),
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => $row[$column_id],
+ 'DATA_TYPE' => $row[$data_type],
+ 'DEFAULT' => $row[$data_default],
+ 'NULLABLE' => (bool) ($row[$nullable] == 'Y'),
+ 'LENGTH' => $row[$data_length],
+ 'SCALE' => $row[$data_scale],
+ 'PRECISION' => $row[$data_precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.CURRVAL FROM dual');
+ return $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return integer
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $value = $this->fetchOne('SELECT '.$this->quoteIdentifier($sequenceName, true).'.NEXTVAL FROM dual');
+ return $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * Oracle does not support IDENTITY columns, so if the sequence is not
+ * specified, this method returns null.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ * @throws Zend_Db_Adapter_Oracle_Exception
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= $this->foldCase("_$primaryKey");
+ }
+ $sequenceName .= $this->foldCase('_seq');
+ return $this->lastSequenceId($sequenceName);
+ }
+ // No support for IDENTITY columns; return null
+ return null;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset
+ * @throws Zend_Db_Adapter_Exception
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ /**
+ * Oracle does not implement the LIMIT clause as some RDBMS do.
+ * We have to simulate it with subqueries and ROWNUM.
+ * Unfortunately because we use the column wildcard "*",
+ * this puts an extra column into the query result set.
+ */
+ $limit_sql = "SELECT z2.*
+ FROM (
+ SELECT z1.*, ROWNUM AS \"zend_db_rownum\"
+ FROM (
+ " . $sql . "
+ ) z1
+ ) z2
+ WHERE z2.\"zend_db_rownum\" BETWEEN " . ($offset+1) . " AND " . ($offset+$count);
+ return $limit_sql;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Pgsql.php b/library/vendor/Zend/Db/Adapter/Pdo/Pgsql.php
new file mode 100644
index 0000000..060ed28
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Pgsql.php
@@ -0,0 +1,333 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Pdo_Abstract
+ */
+
+
+/**
+ * Class for connecting to PostgreSQL databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Pgsql extends Zend_Db_Adapter_Pdo_Abstract
+{
+
+ /**
+ * PDO type.
+ *
+ * @var string
+ */
+ protected $_pdoType = 'pgsql';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::INT_TYPE,
+ 'SERIAL' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'BIGSERIAL' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Creates a PDO object and connects to the database.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ if ($this->_connection) {
+ return;
+ }
+
+ parent::_connect();
+
+ if (!empty($this->_config['charset'])) {
+ $sql = "SET NAMES '" . $this->_config['charset'] . "'";
+ $this->_connection->exec($sql);
+ }
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ // @todo use a better query with joins instead of subqueries
+ $sql = "SELECT c.relname AS table_name "
+ . "FROM pg_class c, pg_user u "
+ . "WHERE c.relowner = u.usesysid AND c.relkind = 'r' "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
+ . "AND c.relname !~ '^(pg_|sql_)' "
+ . "UNION "
+ . "SELECT c.relname AS table_name "
+ . "FROM pg_class c "
+ . "WHERE c.relkind = 'r' "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_views WHERE viewname = c.relname) "
+ . "AND NOT EXISTS (SELECT 1 FROM pg_user WHERE usesysid = c.relowner) "
+ . "AND c.relname !~ '^pg_'";
+
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = "SELECT
+ a.attnum,
+ n.nspname,
+ c.relname,
+ a.attname AS colname,
+ t.typname AS type,
+ a.atttypmod,
+ FORMAT_TYPE(a.atttypid, a.atttypmod) AS complete_type,
+ d.adsrc AS default_value,
+ a.attnotnull AS notnull,
+ a.attlen AS length,
+ co.contype,
+ ARRAY_TO_STRING(co.conkey, ',') AS conkey
+ FROM pg_attribute AS a
+ JOIN pg_class AS c ON a.attrelid = c.oid
+ JOIN pg_namespace AS n ON c.relnamespace = n.oid
+ JOIN pg_type AS t ON a.atttypid = t.oid
+ LEFT OUTER JOIN pg_constraint AS co ON (co.conrelid = c.oid
+ AND a.attnum = ANY(co.conkey) AND co.contype = 'p')
+ LEFT OUTER JOIN pg_attrdef AS d ON d.adrelid = c.oid AND d.adnum = a.attnum
+ WHERE a.attnum > 0 AND c.relname = ".$this->quote($tableName);
+ if ($schemaName) {
+ $sql .= " AND n.nspname = ".$this->quote($schemaName);
+ }
+ $sql .= ' ORDER BY a.attnum';
+
+ $stmt = $this->query($sql);
+
+ // Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $attnum = 0;
+ $nspname = 1;
+ $relname = 2;
+ $colname = 3;
+ $type = 4;
+ $atttypemod = 5;
+ $complete_type = 6;
+ $default_value = 7;
+ $notnull = 8;
+ $length = 9;
+ $contype = 10;
+ $conkey = 11;
+
+ $desc = array();
+ foreach ($result as $key => $row) {
+ $defaultValue = $row[$default_value];
+ if ($row[$type] == 'varchar' || $row[$type] == 'bpchar' ) {
+ if (preg_match('/character(?: varying)?(?:\((\d+)\))?/', $row[$complete_type], $matches)) {
+ if (isset($matches[1])) {
+ $row[$length] = $matches[1];
+ } else {
+ $row[$length] = null; // unlimited
+ }
+ }
+ if (preg_match("/^'(.*?)'::(?:character varying|bpchar)$/", $defaultValue, $matches)) {
+ $defaultValue = $matches[1];
+ }
+ }
+ list($primary, $primaryPosition, $identity) = array(false, null, false);
+ if ($row[$contype] == 'p') {
+ $primary = true;
+ $primaryPosition = array_search($row[$attnum], explode(',', $row[$conkey])) + 1;
+ $identity = (bool) (preg_match('/^nextval/', $row[$default_value]));
+ }
+ $desc[$this->foldCase($row[$colname])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($row[$nspname]),
+ 'TABLE_NAME' => $this->foldCase($row[$relname]),
+ 'COLUMN_NAME' => $this->foldCase($row[$colname]),
+ 'COLUMN_POSITION' => $row[$attnum],
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $defaultValue,
+ 'NULLABLE' => (bool) ($row[$notnull] != 't'),
+ 'LENGTH' => $row[$length],
+ 'SCALE' => null, // @todo
+ 'PRECISION' => null, // @todo
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Return the most recent value from the specified sequence in the database.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function lastSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+ $value = $this->fetchOne("SELECT CURRVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ return $value;
+ }
+
+ /**
+ * Generate a new value from the specified sequence in the database, and return it.
+ * This is supported only on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
+ *
+ * @param string $sequenceName
+ * @return string
+ */
+ public function nextSequenceId($sequenceName)
+ {
+ $this->_connect();
+ $sequenceName = str_replace($this->getQuoteIdentifierSymbol(), '', (string) $sequenceName);
+ $value = $this->fetchOne("SELECT NEXTVAL("
+ . $this->quote($this->quoteIdentifier($sequenceName, true))
+ . ")");
+ return $value;
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName !== null) {
+ $sequenceName = $tableName;
+ if ($primaryKey) {
+ $sequenceName .= "_$primaryKey";
+ }
+ $sequenceName .= '_seq';
+ return $this->lastSequenceId($sequenceName);
+ }
+ return $this->_connection->lastInsertId($tableName);
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Adapter/Pdo/Sqlite.php b/library/vendor/Zend/Db/Adapter/Pdo/Sqlite.php
new file mode 100644
index 0000000..6453339
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Pdo/Sqlite.php
@@ -0,0 +1,305 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Pdo_Abstract
+ */
+
+
+/**
+ * Class for connecting to SQLite2 and SQLite3 databases and performing common operations.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Pdo_Sqlite extends Zend_Db_Adapter_Pdo_Abstract
+{
+
+ /**
+ * PDO type
+ *
+ * @var string
+ */
+ protected $_pdoType = 'sqlite';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INTEGER' => Zend_Db::BIGINT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE
+ );
+
+ /**
+ * Constructor.
+ *
+ * $config is an array of key/value pairs containing configuration
+ * options. Note that the SQLite options are different than most of
+ * the other PDO adapters in that no username or password are needed.
+ * Also, an extra config key "sqlite2" specifies compatibility mode.
+ *
+ * dbname => (string) The name of the database to user (required,
+ * use :memory: for memory-based database)
+ *
+ * sqlite2 => (boolean) PDO_SQLITE defaults to SQLite 3. For compatibility
+ * with an older SQLite 2 database, set this to TRUE.
+ *
+ * @param array $config An array of configuration keys.
+ */
+ public function __construct(array $config = array())
+ {
+ if (isset($config['sqlite2']) && $config['sqlite2']) {
+ $this->_pdoType = 'sqlite2';
+ }
+
+ // SQLite uses no username/password. Stub to satisfy parent::_connect()
+ $this->_config['username'] = null;
+ $this->_config['password'] = null;
+
+ return parent::__construct($config);
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance");
+ }
+ }
+
+ /**
+ * DSN builder
+ */
+ protected function _dsn()
+ {
+ return $this->_pdoType .':'. $this->_config['dbname'];
+ }
+
+ /**
+ * Special configuration for SQLite behavior: make sure that result sets
+ * contain keys like 'column' instead of 'table.column'.
+ *
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _connect()
+ {
+ /**
+ * if we already have a PDO object, no need to re-connect.
+ */
+ if ($this->_connection) {
+ return;
+ }
+
+ parent::_connect();
+
+ $retval = $this->_connection->exec('PRAGMA full_column_names=0');
+ if ($retval === false) {
+ $error = $this->_connection->errorInfo();
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception($error[2]);
+ }
+
+ $retval = $this->_connection->exec('PRAGMA short_column_names=1');
+ if ($retval === false) {
+ $error = $this->_connection->errorInfo();
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception($error[2]);
+ }
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $sql = "SELECT name FROM sqlite_master WHERE type='table' "
+ . "UNION ALL SELECT name FROM sqlite_temp_master "
+ . "WHERE type='table' ORDER BY name";
+
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of database or schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ $sql = 'PRAGMA ';
+
+ if ($schemaName) {
+ $sql .= $this->quoteIdentifier($schemaName) . '.';
+ }
+
+ $sql .= 'table_info('.$this->quoteIdentifier($tableName).')';
+
+ $stmt = $this->query($sql);
+
+ /**
+ * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
+ */
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ $cid = 0;
+ $name = 1;
+ $type = 2;
+ $notnull = 3;
+ $dflt_value = 4;
+ $pk = 5;
+
+ $desc = array();
+
+ $p = 1;
+ foreach ($result as $key => $row) {
+ list($length, $scale, $precision, $primary, $primaryPosition, $identity) =
+ array(null, null, null, false, null, false);
+ if (preg_match('/^((?:var)?char)\((\d+)\)/i', $row[$type], $matches)) {
+ $row[$type] = $matches[1];
+ $length = $matches[2];
+ } else if (preg_match('/^decimal\((\d+),(\d+)\)/i', $row[$type], $matches)) {
+ $row[$type] = 'DECIMAL';
+ $precision = $matches[1];
+ $scale = $matches[2];
+ }
+ if ((bool) $row[$pk]) {
+ $primary = true;
+ $primaryPosition = $p;
+ /**
+ * SQLite INTEGER primary key is always auto-increment.
+ */
+ $identity = (bool) ($row[$type] == 'INTEGER');
+ ++$p;
+ }
+ $desc[$this->foldCase($row[$name])] = array(
+ 'SCHEMA_NAME' => $this->foldCase($schemaName),
+ 'TABLE_NAME' => $this->foldCase($tableName),
+ 'COLUMN_NAME' => $this->foldCase($row[$name]),
+ 'COLUMN_POSITION' => $row[$cid]+1,
+ 'DATA_TYPE' => $row[$type],
+ 'DEFAULT' => $row[$dflt_value],
+ 'NULLABLE' => ! (bool) $row[$notnull],
+ 'LENGTH' => $length,
+ 'SCALE' => $scale,
+ 'PRECISION' => $precision,
+ 'UNSIGNED' => null, // Sqlite3 does not support unsigned data
+ 'PRIMARY' => $primary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity
+ );
+ }
+ return $desc;
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ $sql .= " LIMIT $count";
+ if ($offset > 0) {
+ $sql .= " OFFSET $offset";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (!is_int($value) && !is_float($value)) {
+ // Fix for null-byte injection
+ $value = addcslashes($value, "\000\032");
+ }
+ return parent::_quote($value);
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Sqlsrv.php b/library/vendor/Zend/Db/Adapter/Sqlsrv.php
new file mode 100644
index 0000000..ce3324f
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Sqlsrv.php
@@ -0,0 +1,662 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db_Statement_Sqlsrv
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Sqlsrv extends Zend_Db_Adapter_Abstract
+{
+ /**
+ * User-provided configuration.
+ *
+ * Basic keys are:
+ *
+ * username => (string) Connect to the database as this username.
+ * password => (string) Password associated with the username.
+ * dbname => The name of the local SQL Server instance
+ *
+ * @var array
+ */
+ protected $_config = array(
+ 'dbname' => null,
+ 'username' => null,
+ 'password' => null,
+ );
+
+ /**
+ * Last insert id from INSERT query
+ *
+ * @var int
+ */
+ protected $_lastInsertId;
+
+ /**
+ * Query used to fetch last insert id
+ *
+ * @var string
+ */
+ protected $_lastInsertSQL = 'SELECT SCOPE_IDENTITY() as Current_Identity';
+
+ /**
+ * Keys are UPPERCASE SQL datatypes or the constants
+ * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
+ *
+ * Values are:
+ * 0 = 32-bit integer
+ * 1 = 64-bit integer
+ * 2 = float or decimal
+ *
+ * @var array Associative array of datatypes to values 0, 1, or 2.
+ */
+ protected $_numericDataTypes = array(
+ Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
+ Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
+ Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
+ 'INT' => Zend_Db::INT_TYPE,
+ 'SMALLINT' => Zend_Db::INT_TYPE,
+ 'TINYINT' => Zend_Db::INT_TYPE,
+ 'BIGINT' => Zend_Db::BIGINT_TYPE,
+ 'DECIMAL' => Zend_Db::FLOAT_TYPE,
+ 'FLOAT' => Zend_Db::FLOAT_TYPE,
+ 'MONEY' => Zend_Db::FLOAT_TYPE,
+ 'NUMERIC' => Zend_Db::FLOAT_TYPE,
+ 'REAL' => Zend_Db::FLOAT_TYPE,
+ 'SMALLMONEY' => Zend_Db::FLOAT_TYPE,
+ );
+
+ /**
+ * Default class name for a DB statement.
+ *
+ * @var string
+ */
+ protected $_defaultStmtClass = 'Zend_Db_Statement_Sqlsrv';
+
+ /**
+ * Creates a connection resource.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _connect()
+ {
+ if (is_resource($this->_connection)) {
+ // connection already exists
+ return;
+ }
+
+ if (!extension_loaded('sqlsrv')) {
+ /**
+ * @see Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ throw new Zend_Db_Adapter_Sqlsrv_Exception('The Sqlsrv extension is required for this adapter but the extension is not loaded');
+ }
+
+ $serverName = $this->_config['host'];
+ if (isset($this->_config['port'])) {
+ $port = (integer) $this->_config['port'];
+ $serverName .= ', ' . $port;
+ }
+
+ $connectionInfo = array(
+ 'Database' => $this->_config['dbname'],
+ );
+
+ if (isset($this->_config['username']) && isset($this->_config['password']))
+ {
+ $connectionInfo += array(
+ 'UID' => $this->_config['username'],
+ 'PWD' => $this->_config['password'],
+ );
+ }
+ // else - windows authentication
+
+ if (!empty($this->_config['driver_options'])) {
+ foreach ($this->_config['driver_options'] as $option => $value) {
+ // A value may be a constant.
+ if (is_string($value)) {
+ $constantName = strtoupper($value);
+ if (defined($constantName)) {
+ $connectionInfo[$option] = constant($constantName);
+ } else {
+ $connectionInfo[$option] = $value;
+ }
+ }
+ }
+ }
+
+ $this->_connection = sqlsrv_connect($serverName, $connectionInfo);
+
+ if (!$this->_connection) {
+ /**
+ * @see Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Check for config options that are mandatory.
+ * Throw exceptions if any are missing.
+ *
+ * @param array $config
+ * @throws Zend_Db_Adapter_Exception
+ */
+ protected function _checkRequiredOptions(array $config)
+ {
+ // we need at least a dbname
+ if (! array_key_exists('dbname', $config)) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'dbname' that names the database instance");
+ }
+
+ if (! array_key_exists('password', $config) && array_key_exists('username', $config)) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'password' for login credentials.
+ If Windows Authentication is desired, both keys 'username' and 'password' should be ommited from config.");
+ }
+
+ if (array_key_exists('password', $config) && !array_key_exists('username', $config)) {
+ /**
+ * @see Zend_Db_Adapter_Exception
+ */
+ throw new Zend_Db_Adapter_Exception("Configuration array must have a key for 'username' for login credentials.
+ If Windows Authentication is desired, both keys 'username' and 'password' should be ommited from config.");
+ }
+ }
+
+ /**
+ * Set the transaction isoltion level.
+ *
+ * @param integer|null $level A fetch mode from SQLSRV_TXN_*.
+ * @return true
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function setTransactionIsolationLevel($level = null)
+ {
+ $this->_connect();
+ $sql = null;
+
+ // Default transaction level in sql server
+ if ($level === null)
+ {
+ $level = SQLSRV_TXN_READ_COMMITTED;
+ }
+
+ switch ($level) {
+ case SQLSRV_TXN_READ_UNCOMMITTED:
+ $sql = "READ UNCOMMITTED";
+ break;
+ case SQLSRV_TXN_READ_COMMITTED:
+ $sql = "READ COMMITTED";
+ break;
+ case SQLSRV_TXN_REPEATABLE_READ:
+ $sql = "REPEATABLE READ";
+ break;
+ case SQLSRV_TXN_SNAPSHOT:
+ $sql = "SNAPSHOT";
+ break;
+ case SQLSRV_TXN_SERIALIZABLE:
+ $sql = "SERIALIZABLE";
+ break;
+ default:
+ throw new Zend_Db_Adapter_Sqlsrv_Exception("Invalid transaction isolation level mode '$level' specified");
+ }
+
+ if (!sqlsrv_query($this->_connection, "SET TRANSACTION ISOLATION LEVEL $sql;")) {
+ throw new Zend_Db_Adapter_Sqlsrv_Exception("Transaction cannot be changed to '$level'");
+ }
+
+ return true;
+ }
+
+ /**
+ * Test if a connection is active
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return (is_resource($this->_connection)
+ && (get_resource_type($this->_connection) == 'SQL Server Connection')
+ );
+ }
+
+ /**
+ * Force the connection to close.
+ *
+ * @return void
+ */
+ public function closeConnection()
+ {
+ if ($this->isConnected()) {
+ sqlsrv_close($this->_connection);
+ }
+ $this->_connection = null;
+ }
+
+ /**
+ * Returns an SQL statement for preparation.
+ *
+ * @param string $sql The SQL statement with placeholders.
+ * @return Zend_Db_Statement_Sqlsrv
+ */
+ public function prepare($sql)
+ {
+ $this->_connect();
+ $stmtClass = $this->_defaultStmtClass;
+
+ if (!class_exists($stmtClass)) {
+ /**
+ * @see Zend_Loader
+ */
+ Zend_Loader::loadClass($stmtClass);
+ }
+
+ $stmt = new $stmtClass($this, $sql);
+ $stmt->setFetchMode($this->_fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Quote a raw string.
+ *
+ * @param string $value Raw string
+ * @return string Quoted string
+ */
+ protected function _quote($value)
+ {
+ if (is_int($value)) {
+ return $value;
+ } elseif (is_float($value)) {
+ return sprintf('%F', $value);
+ }
+
+ $value = addcslashes($value, "\000\032");
+ return "'" . str_replace("'", "''", $value) . "'";
+ }
+
+ /**
+ * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
+ *
+ * As a convention, on RDBMS brands that support sequences
+ * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
+ * from the arguments and returns the last id generated by that sequence.
+ * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
+ * returns the last value generated for such a column, and the table name
+ * argument is disregarded.
+ *
+ * @param string $tableName OPTIONAL Name of table.
+ * @param string $primaryKey OPTIONAL Name of primary key column.
+ * @return string
+ */
+ public function lastInsertId($tableName = null, $primaryKey = null)
+ {
+ if ($tableName) {
+ $tableName = $this->quote($tableName);
+ $sql = 'SELECT IDENT_CURRENT (' . $tableName . ') as Current_Identity';
+ return (string) $this->fetchOne($sql);
+ }
+
+ if ($this->_lastInsertId > 0) {
+ return (string) $this->_lastInsertId;
+ }
+
+ $sql = $this->_lastInsertSQL;
+ return (string) $this->fetchOne($sql);
+ }
+
+ /**
+ * Inserts a table row with specified data.
+ *
+ * @param mixed $table The table to insert data into.
+ * @param array $bind Column-value pairs.
+ * @return int The number of affected rows.
+ */
+ public function insert($table, array $bind)
+ {
+ // extract and quote col names from the array keys
+ $cols = array();
+ $vals = array();
+ foreach ($bind as $col => $val) {
+ $cols[] = $this->quoteIdentifier($col, true);
+ if ($val instanceof Zend_Db_Expr) {
+ $vals[] = $val->__toString();
+ unset($bind[$col]);
+ } else {
+ $vals[] = '?';
+ }
+ }
+
+ // build the statement
+ $sql = "INSERT INTO "
+ . $this->quoteIdentifier($table, true)
+ . ' (' . implode(', ', $cols) . ') '
+ . 'VALUES (' . implode(', ', $vals) . ')'
+ . ' ' . $this->_lastInsertSQL;
+
+ // execute the statement and return the number of affected rows
+ $stmt = $this->query($sql, array_values($bind));
+ $result = $stmt->rowCount();
+
+ $stmt->nextRowset();
+
+ $this->_lastInsertId = $stmt->fetchColumn();
+
+ return $result;
+ }
+
+ /**
+ * Returns a list of the tables in the database.
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->_connect();
+ $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
+ return $this->fetchCol($sql);
+ }
+
+ /**
+ * Returns the column descriptions for a table.
+ *
+ * The return value is an associative array keyed by the column name,
+ * as returned by the RDBMS.
+ *
+ * The value of each array element is an associative array
+ * with the following keys:
+ *
+ * SCHEMA_NAME => string; name of schema
+ * TABLE_NAME => string;
+ * COLUMN_NAME => string; column name
+ * COLUMN_POSITION => number; ordinal position of column in table
+ * DATA_TYPE => string; SQL datatype name of column
+ * DEFAULT => string; default expression of column, null if none
+ * NULLABLE => boolean; true if column can have nulls
+ * LENGTH => number; length of CHAR/VARCHAR
+ * SCALE => number; scale of NUMERIC/DECIMAL
+ * PRECISION => number; precision of NUMERIC/DECIMAL
+ * UNSIGNED => boolean; unsigned property of an integer type
+ * PRIMARY => boolean; true if column is part of the primary key
+ * PRIMARY_POSITION => integer; position of column in primary key
+ * IDENTITY => integer; true if column is auto-generated with unique values
+ *
+ * @todo Discover integer unsigned property.
+ *
+ * @param string $tableName
+ * @param string $schemaName OPTIONAL
+ * @return array
+ */
+ public function describeTable($tableName, $schemaName = null)
+ {
+ /**
+ * Discover metadata information about this table.
+ */
+ $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
+ $stmt = $this->query($sql);
+ $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+
+ // ZF-7698
+ $stmt->closeCursor();
+
+ if (count($result) == 0) {
+ return array();
+ }
+
+ $owner = 1;
+ $table_name = 2;
+ $column_name = 3;
+ $type_name = 5;
+ $precision = 6;
+ $length = 7;
+ $scale = 8;
+ $nullable = 10;
+ $column_def = 12;
+ $column_position = 16;
+
+ /**
+ * Discover primary key column(s) for this table.
+ */
+ $tableOwner = $result[0][$owner];
+ $sql = "exec sp_pkeys @table_owner = " . $tableOwner
+ . ", @table_name = " . $this->quoteIdentifier($tableName, true);
+ $stmt = $this->query($sql);
+
+ $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
+ $primaryKeyColumn = array();
+
+ // Per http://msdn.microsoft.com/en-us/library/ms189813.aspx,
+ // results from sp_keys stored procedure are:
+ // 0=TABLE_QUALIFIER 1=TABLE_OWNER 2=TABLE_NAME 3=COLUMN_NAME 4=KEY_SEQ 5=PK_NAME
+
+ $pkey_column_name = 3;
+ $pkey_key_seq = 4;
+ foreach ($primaryKeysResult as $pkeysRow) {
+ $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
+ }
+
+ $desc = array();
+ $p = 1;
+ foreach ($result as $key => $row) {
+ $identity = false;
+ $words = explode(' ', $row[$type_name], 2);
+ if (isset($words[0])) {
+ $type = $words[0];
+ if (isset($words[1])) {
+ $identity = (bool) preg_match('/identity/', $words[1]);
+ }
+ }
+
+ $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
+ if ($isPrimary) {
+ $primaryPosition = $primaryKeyColumn[$row[$column_name]];
+ } else {
+ $primaryPosition = null;
+ }
+
+ $desc[$this->foldCase($row[$column_name])] = array(
+ 'SCHEMA_NAME' => null, // @todo
+ 'TABLE_NAME' => $this->foldCase($row[$table_name]),
+ 'COLUMN_NAME' => $this->foldCase($row[$column_name]),
+ 'COLUMN_POSITION' => (int) $row[$column_position],
+ 'DATA_TYPE' => $type,
+ 'DEFAULT' => $row[$column_def],
+ 'NULLABLE' => (bool) $row[$nullable],
+ 'LENGTH' => $row[$length],
+ 'SCALE' => $row[$scale],
+ 'PRECISION' => $row[$precision],
+ 'UNSIGNED' => null, // @todo
+ 'PRIMARY' => $isPrimary,
+ 'PRIMARY_POSITION' => $primaryPosition,
+ 'IDENTITY' => $identity,
+ );
+ }
+
+ return $desc;
+ }
+
+ /**
+ * Leave autocommit mode and begin a transaction.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _beginTransaction()
+ {
+ if (!sqlsrv_begin_transaction($this->_connection)) {
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Commit a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _commit()
+ {
+ if (!sqlsrv_commit($this->_connection)) {
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Roll back a transaction and return to autocommit mode.
+ *
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ protected function _rollBack()
+ {
+ if (!sqlsrv_rollback($this->_connection)) {
+ throw new Zend_Db_Adapter_Sqlsrv_Exception(sqlsrv_errors());
+ }
+ }
+
+ /**
+ * Set the fetch mode.
+ *
+ * @todo Support FETCH_CLASS and FETCH_INTO.
+ *
+ * @param integer $mode A fetch mode.
+ * @return void
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM: // seq array
+ case Zend_Db::FETCH_ASSOC: // assoc array
+ case Zend_Db::FETCH_BOTH: // seq+assoc array
+ case Zend_Db::FETCH_OBJ: // object
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND: // bound to PHP variable
+ throw new Zend_Db_Adapter_Sqlsrv_Exception('FETCH_BOUND is not supported yet');
+ break;
+ default:
+ throw new Zend_Db_Adapter_Sqlsrv_Exception("Invalid fetch mode '$mode' specified");
+ break;
+ }
+ }
+
+ /**
+ * Adds an adapter-specific LIMIT clause to the SELECT statement.
+ *
+ * @param string $sql
+ * @param integer $count
+ * @param integer $offset OPTIONAL
+ * @return string
+ * @throws Zend_Db_Adapter_Sqlsrv_Exception
+ */
+ public function limit($sql, $count, $offset = 0)
+ {
+ $count = intval($count);
+ if ($count <= 0) {
+ throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
+ }
+
+ $offset = intval($offset);
+ if ($offset < 0) {
+ /** @see Zend_Db_Adapter_Exception */
+ throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
+ }
+
+ if ($offset == 0) {
+ $sql = preg_replace('/^SELECT\s/i', 'SELECT TOP ' . $count . ' ', $sql);
+ } else {
+ $orderby = stristr($sql, 'ORDER BY');
+
+ if (!$orderby) {
+ $over = 'ORDER BY (SELECT 0)';
+ } else {
+ $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby);
+ }
+
+ // Remove ORDER BY clause from $sql
+ $sql = preg_replace('/\s+ORDER BY(.*)/', '', $sql);
+
+ // Add ORDER BY clause as an argument for ROW_NUMBER()
+ $sql = "SELECT ROW_NUMBER() OVER ($over) AS \"ZEND_DB_ROWNUM\", * FROM ($sql) AS inner_tbl";
+
+ $start = $offset + 1;
+
+ if ($count == PHP_INT_MAX) {
+ $sql = "WITH outer_tbl AS ($sql) SELECT * FROM outer_tbl WHERE \"ZEND_DB_ROWNUM\" >= $start";
+ }
+ else {
+ $end = $offset + $count;
+ $sql = "WITH outer_tbl AS ($sql) SELECT * FROM outer_tbl WHERE \"ZEND_DB_ROWNUM\" BETWEEN $start AND $end";
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Check if the adapter supports real SQL parameters.
+ *
+ * @param string $type 'positional' or 'named'
+ * @return bool
+ */
+ public function supportsParameters($type)
+ {
+ if ($type == 'positional') {
+ return true;
+ }
+
+ // if its 'named' or anything else
+ return false;
+ }
+
+ /**
+ * Retrieve server version in PHP style
+ *
+ * @return string
+ */
+ public function getServerVersion()
+ {
+ $this->_connect();
+ $serverInfo = sqlsrv_server_info($this->_connection);
+
+ if ($serverInfo !== false) {
+ return $serverInfo['SQLServerVersion'];
+ }
+
+ return null;
+ }
+}
diff --git a/library/vendor/Zend/Db/Adapter/Sqlsrv/Exception.php b/library/vendor/Zend/Db/Adapter/Sqlsrv/Exception.php
new file mode 100644
index 0000000..2bbc84d
--- /dev/null
+++ b/library/vendor/Zend/Db/Adapter/Sqlsrv/Exception.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Adapter_Exception
+ */
+
+/**
+ * Zend_Db_Adapter_Sqlsrv_Exception
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Adapter_Sqlsrv_Exception extends Zend_Db_Adapter_Exception
+{
+ /**
+ * Constructor
+ *
+ * If $message is an array, the assumption is that the return value of
+ * sqlsrv_errors() was provided. If so, it then retrieves the most recent
+ * error from that stack, and sets the message and code based on it.
+ *
+ * @param null|array|string $message
+ * @param null|int $code
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ if (is_array($message)) {
+ // Error should be array of errors
+ // We only need first one (?)
+ if (isset($message[0])) {
+ $message = $message[0];
+ }
+
+ $code = (int) $message['code'];
+ $message = (string) $message['message'];
+ }
+ parent::__construct($message, $code, new Exception($message, $code));
+ }
+}
diff --git a/library/vendor/Zend/Db/Exception.php b/library/vendor/Zend/Db/Exception.php
new file mode 100644
index 0000000..837c473
--- /dev/null
+++ b/library/vendor/Zend/Db/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Db/Expr.php b/library/vendor/Zend/Db/Expr.php
new file mode 100644
index 0000000..d8843a4
--- /dev/null
+++ b/library/vendor/Zend/Db/Expr.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Expr
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Class for SQL SELECT fragments.
+ *
+ * This class simply holds a string, so that fragments of SQL statements can be
+ * distinguished from identifiers and values that should be implicitly quoted
+ * when interpolated into SQL statements.
+ *
+ * For example, when specifying a primary key value when inserting into a new
+ * row, some RDBMS brands may require you to use an expression to generate the
+ * new value of a sequence. If this expression is treated as an identifier,
+ * it will be quoted and the expression will not be evaluated. Another example
+ * is that you can use Zend_Db_Expr in the Zend_Db_Select::order() method to
+ * order by an expression instead of simply a column name.
+ *
+ * The way this works is that in each context in which a column name can be
+ * specified to methods of Zend_Db classes, if the value is an instance of
+ * Zend_Db_Expr instead of a plain string, then the expression is not quoted.
+ * If it is a plain string, it is assumed to be a plain column name.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Expr
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Expr
+{
+ /**
+ * Storage for the SQL expression.
+ *
+ * @var string
+ */
+ protected $_expression;
+
+ /**
+ * Instantiate an expression, which is just a string stored as
+ * an instance member variable.
+ *
+ * @param string $expression The string containing a SQL expression.
+ */
+ public function __construct($expression)
+ {
+ $this->_expression = (string) $expression;
+ }
+
+ /**
+ * @return string The string of the SQL expression stored in this object.
+ */
+ public function __toString()
+ {
+ return $this->_expression;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Profiler.php b/library/vendor/Zend/Db/Profiler.php
new file mode 100644
index 0000000..c80ec5d
--- /dev/null
+++ b/library/vendor/Zend/Db/Profiler.php
@@ -0,0 +1,469 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Profiler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Profiler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Profiler
+{
+
+ /**
+ * A connection operation or selecting a database.
+ */
+ const CONNECT = 1;
+
+ /**
+ * Any general database query that does not fit into the other constants.
+ */
+ const QUERY = 2;
+
+ /**
+ * Adding new data to the database, such as SQL's INSERT.
+ */
+ const INSERT = 4;
+
+ /**
+ * Updating existing information in the database, such as SQL's UPDATE.
+ *
+ */
+ const UPDATE = 8;
+
+ /**
+ * An operation related to deleting data in the database,
+ * such as SQL's DELETE.
+ */
+ const DELETE = 16;
+
+ /**
+ * Retrieving information from the database, such as SQL's SELECT.
+ */
+ const SELECT = 32;
+
+ /**
+ * Transactional operation, such as start transaction, commit, or rollback.
+ */
+ const TRANSACTION = 64;
+
+ /**
+ * Inform that a query is stored (in case of filtering)
+ */
+ const STORED = 'stored';
+
+ /**
+ * Inform that a query is ignored (in case of filtering)
+ */
+ const IGNORED = 'ignored';
+
+ /**
+ * Array of Zend_Db_Profiler_Query objects.
+ *
+ * @var array
+ */
+ protected $_queryProfiles = array();
+
+ /**
+ * Stores enabled state of the profiler. If set to False, calls to
+ * queryStart() will simply be ignored.
+ *
+ * @var boolean
+ */
+ protected $_enabled = false;
+
+ /**
+ * Stores the number of seconds to filter. NULL if filtering by time is
+ * disabled. If an integer is stored here, profiles whose elapsed time
+ * is less than this value in seconds will be unset from
+ * the self::$_queryProfiles array.
+ *
+ * @var integer
+ */
+ protected $_filterElapsedSecs = null;
+
+ /**
+ * Logical OR of any of the filter constants. NULL if filtering by query
+ * type is disable. If an integer is stored here, it is the logical OR of
+ * any of the query type constants. When the query ends, if it is not
+ * one of the types specified, it will be unset from the
+ * self::$_queryProfiles array.
+ *
+ * @var integer
+ */
+ protected $_filterTypes = null;
+
+ /**
+ * Class constructor. The profiler is disabled by default unless it is
+ * specifically enabled by passing in $enabled here or calling setEnabled().
+ *
+ * @param boolean $enabled
+ * @return void
+ */
+ public function __construct($enabled = false)
+ {
+ $this->setEnabled($enabled);
+ }
+
+ /**
+ * Enable or disable the profiler. If $enable is false, the profiler
+ * is disabled and will not log any queries sent to it.
+ *
+ * @param boolean $enable
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setEnabled($enable)
+ {
+ $this->_enabled = (boolean) $enable;
+
+ return $this;
+ }
+
+ /**
+ * Get the current state of enable. If True is returned,
+ * the profiler is enabled.
+ *
+ * @return boolean
+ */
+ public function getEnabled()
+ {
+ return $this->_enabled;
+ }
+
+ /**
+ * Sets a minimum number of seconds for saving query profiles. If this
+ * is set, only those queries whose elapsed time is equal or greater than
+ * $minimumSeconds will be saved. To save all queries regardless of
+ * elapsed time, set $minimumSeconds to null.
+ *
+ * @param integer $minimumSeconds OPTIONAL
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setFilterElapsedSecs($minimumSeconds = null)
+ {
+ if (null === $minimumSeconds) {
+ $this->_filterElapsedSecs = null;
+ } else {
+ $this->_filterElapsedSecs = (integer) $minimumSeconds;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the minimum number of seconds for saving query profiles, or null if
+ * query profiles are saved regardless of elapsed time.
+ *
+ * @return integer|null
+ */
+ public function getFilterElapsedSecs()
+ {
+ return $this->_filterElapsedSecs;
+ }
+
+ /**
+ * Sets the types of query profiles to save. Set $queryType to one of
+ * the Zend_Db_Profiler::* constants to only save profiles for that type of
+ * query. To save more than one type, logical OR them together. To
+ * save all queries regardless of type, set $queryType to null.
+ *
+ * @param integer $queryTypes OPTIONAL
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function setFilterQueryType($queryTypes = null)
+ {
+ $this->_filterTypes = $queryTypes;
+
+ return $this;
+ }
+
+ /**
+ * Returns the types of query profiles saved, or null if queries are saved regardless
+ * of their types.
+ *
+ * @return integer|null
+ * @see Zend_Db_Profiler::setFilterQueryType()
+ */
+ public function getFilterQueryType()
+ {
+ return $this->_filterTypes;
+ }
+
+ /**
+ * Clears the history of any past query profiles. This is relentless
+ * and will even clear queries that were started and may not have
+ * been marked as ended.
+ *
+ * @return Zend_Db_Profiler Provides a fluent interface
+ */
+ public function clear()
+ {
+ $this->_queryProfiles = array();
+
+ return $this;
+ }
+
+ /**
+ * Clone a profiler query
+ *
+ * @param Zend_Db_Profiler_Query $query
+ * @return integer or null
+ */
+ public function queryClone(Zend_Db_Profiler_Query $query)
+ {
+ $this->_queryProfiles[] = clone $query;
+
+ end($this->_queryProfiles);
+
+ return key($this->_queryProfiles);
+ }
+
+ /**
+ * Starts a query. Creates a new query profile object (Zend_Db_Profiler_Query)
+ * and returns the "query profiler handle". Run the query, then call
+ * queryEnd() and pass it this handle to make the query as ended and
+ * record the time. If the profiler is not enabled, this takes no
+ * action and immediately returns null.
+ *
+ * @param string $queryText SQL statement
+ * @param integer $queryType OPTIONAL Type of query, one of the Zend_Db_Profiler::* constants
+ * @return integer|null
+ */
+ public function queryStart($queryText, $queryType = null)
+ {
+ if (!$this->_enabled) {
+ return null;
+ }
+
+ // make sure we have a query type
+ if (null === $queryType) {
+ switch (strtolower(substr(ltrim($queryText), 0, 6))) {
+ case 'insert':
+ $queryType = self::INSERT;
+ break;
+ case 'update':
+ $queryType = self::UPDATE;
+ break;
+ case 'delete':
+ $queryType = self::DELETE;
+ break;
+ case 'select':
+ $queryType = self::SELECT;
+ break;
+ default:
+ $queryType = self::QUERY;
+ break;
+ }
+ }
+
+ /**
+ * @see Zend_Db_Profiler_Query
+ */
+ $this->_queryProfiles[] = new Zend_Db_Profiler_Query($queryText, $queryType);
+
+ end($this->_queryProfiles);
+
+ return key($this->_queryProfiles);
+ }
+
+ /**
+ * Ends a query. Pass it the handle that was returned by queryStart().
+ * This will mark the query as ended and save the time.
+ *
+ * @param integer $queryId
+ * @throws Zend_Db_Profiler_Exception
+ * @return string Inform that a query is stored or ignored.
+ */
+ public function queryEnd($queryId)
+ {
+ // Don't do anything if the Zend_Db_Profiler is not enabled.
+ if (!$this->_enabled) {
+ return self::IGNORED;
+ }
+
+ // Check for a valid query handle.
+ if (!isset($this->_queryProfiles[$queryId])) {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ throw new Zend_Db_Profiler_Exception("Profiler has no query with handle '$queryId'.");
+ }
+
+ $qp = $this->_queryProfiles[$queryId];
+
+ // Ensure that the query profile has not already ended
+ if ($qp->hasEnded()) {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ throw new Zend_Db_Profiler_Exception("Query with profiler handle '$queryId' has already ended.");
+ }
+
+ // End the query profile so that the elapsed time can be calculated.
+ $qp->end();
+
+ /**
+ * If filtering by elapsed time is enabled, only keep the profile if
+ * it ran for the minimum time.
+ */
+ if (null !== $this->_filterElapsedSecs && $qp->getElapsedSecs() < $this->_filterElapsedSecs) {
+ unset($this->_queryProfiles[$queryId]);
+ return self::IGNORED;
+ }
+
+ /**
+ * If filtering by query type is enabled, only keep the query if
+ * it was one of the allowed types.
+ */
+ if (null !== $this->_filterTypes && !($qp->getQueryType() & $this->_filterTypes)) {
+ unset($this->_queryProfiles[$queryId]);
+ return self::IGNORED;
+ }
+
+ return self::STORED;
+ }
+
+ /**
+ * Get a profile for a query. Pass it the same handle that was returned
+ * by queryStart() and it will return a Zend_Db_Profiler_Query object.
+ *
+ * @param integer $queryId
+ * @throws Zend_Db_Profiler_Exception
+ * @return Zend_Db_Profiler_Query
+ */
+ public function getQueryProfile($queryId)
+ {
+ if (!array_key_exists($queryId, $this->_queryProfiles)) {
+ /**
+ * @see Zend_Db_Profiler_Exception
+ */
+ throw new Zend_Db_Profiler_Exception("Query handle '$queryId' not found in profiler log.");
+ }
+
+ return $this->_queryProfiles[$queryId];
+ }
+
+ /**
+ * Get an array of query profiles (Zend_Db_Profiler_Query objects). If $queryType
+ * is set to one of the Zend_Db_Profiler::* constants then only queries of that
+ * type will be returned. Normally, queries that have not yet ended will
+ * not be returned unless $showUnfinished is set to True. If no
+ * queries were found, False is returned. The returned array is indexed by the query
+ * profile handles.
+ *
+ * @param integer $queryType
+ * @param boolean $showUnfinished
+ * @return array|false
+ */
+ public function getQueryProfiles($queryType = null, $showUnfinished = false)
+ {
+ $queryProfiles = array();
+ foreach ($this->_queryProfiles as $key => $qp) {
+ if ($queryType === null) {
+ $condition = true;
+ } else {
+ $condition = ($qp->getQueryType() & $queryType);
+ }
+
+ if (($qp->hasEnded() || $showUnfinished) && $condition) {
+ $queryProfiles[$key] = $qp;
+ }
+ }
+
+ if (empty($queryProfiles)) {
+ $queryProfiles = false;
+ }
+
+ return $queryProfiles;
+ }
+
+ /**
+ * Get the total elapsed time (in seconds) of all of the profiled queries.
+ * Only queries that have ended will be counted. If $queryType is set to
+ * one or more of the Zend_Db_Profiler::* constants, the elapsed time will be calculated
+ * only for queries of the given type(s).
+ *
+ * @param integer $queryType OPTIONAL
+ * @return float
+ */
+ public function getTotalElapsedSecs($queryType = null)
+ {
+ $elapsedSecs = 0;
+ foreach ($this->_queryProfiles as $key => $qp) {
+ if (null === $queryType) {
+ $condition = true;
+ } else {
+ $condition = ($qp->getQueryType() & $queryType);
+ }
+ if (($qp->hasEnded()) && $condition) {
+ $elapsedSecs += $qp->getElapsedSecs();
+ }
+ }
+ return $elapsedSecs;
+ }
+
+ /**
+ * Get the total number of queries that have been profiled. Only queries that have ended will
+ * be counted. If $queryType is set to one of the Zend_Db_Profiler::* constants, only queries of
+ * that type will be counted.
+ *
+ * @param integer $queryType OPTIONAL
+ * @return integer
+ */
+ public function getTotalNumQueries($queryType = null)
+ {
+ if (null === $queryType) {
+ return count($this->_queryProfiles);
+ }
+
+ $numQueries = 0;
+ foreach ($this->_queryProfiles as $qp) {
+ if ($qp->hasEnded() && ($qp->getQueryType() & $queryType)) {
+ $numQueries++;
+ }
+ }
+
+ return $numQueries;
+ }
+
+ /**
+ * Get the Zend_Db_Profiler_Query object for the last query that was run, regardless if it has
+ * ended or not. If the query has not ended, its end time will be null. If no queries have
+ * been profiled, false is returned.
+ *
+ * @return Zend_Db_Profiler_Query|false
+ */
+ public function getLastQueryProfile()
+ {
+ if (empty($this->_queryProfiles)) {
+ return false;
+ }
+
+ end($this->_queryProfiles);
+
+ return current($this->_queryProfiles);
+ }
+
+}
+
diff --git a/library/vendor/Zend/Db/Profiler/Exception.php b/library/vendor/Zend/Db/Profiler/Exception.php
new file mode 100644
index 0000000..c2f76fa
--- /dev/null
+++ b/library/vendor/Zend/Db/Profiler/Exception.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Profiler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Profiler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Profiler_Exception extends Zend_Db_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Db/Profiler/Query.php b/library/vendor/Zend/Db/Profiler/Query.php
new file mode 100644
index 0000000..97a7fef
--- /dev/null
+++ b/library/vendor/Zend/Db/Profiler/Query.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Profiler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Profiler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Profiler_Query
+{
+
+ /**
+ * SQL query string or user comment, set by $query argument in constructor.
+ *
+ * @var string
+ */
+ protected $_query = '';
+
+ /**
+ * One of the Zend_Db_Profiler constants for query type, set by $queryType argument in constructor.
+ *
+ * @var integer
+ */
+ protected $_queryType = 0;
+
+ /**
+ * Unix timestamp with microseconds when instantiated.
+ *
+ * @var float
+ */
+ protected $_startedMicrotime = null;
+
+ /**
+ * Unix timestamp with microseconds when self::queryEnd() was called.
+ *
+ * @var integer
+ */
+ protected $_endedMicrotime = null;
+
+ /**
+ * @var array
+ */
+ protected $_boundParams = array();
+
+ /**
+ * @var array
+ */
+
+ /**
+ * Class constructor. A query is about to be started, save the query text ($query) and its
+ * type (one of the Zend_Db_Profiler::* constants).
+ *
+ * @param string $query
+ * @param integer $queryType
+ * @return void
+ */
+ public function __construct($query, $queryType)
+ {
+ $this->_query = $query;
+ $this->_queryType = $queryType;
+ // by default, and for backward-compatibility, start the click ticking
+ $this->start();
+ }
+
+ /**
+ * Clone handler for the query object.
+ * @return void
+ */
+ public function __clone()
+ {
+ $this->_boundParams = array();
+ $this->_endedMicrotime = null;
+ $this->start();
+ }
+
+ /**
+ * Starts the elapsed time click ticking.
+ * This can be called subsequent to object creation,
+ * to restart the clock. For instance, this is useful
+ * right before executing a prepared query.
+ *
+ * @return void
+ */
+ public function start()
+ {
+ $this->_startedMicrotime = microtime(true);
+ }
+
+ /**
+ * Ends the query and records the time so that the elapsed time can be determined later.
+ *
+ * @return void
+ */
+ public function end()
+ {
+ $this->_endedMicrotime = microtime(true);
+ }
+
+ /**
+ * Returns true if and only if the query has ended.
+ *
+ * @return boolean
+ */
+ public function hasEnded()
+ {
+ return $this->_endedMicrotime !== null;
+ }
+
+ /**
+ * Get the original SQL text of the query.
+ *
+ * @return string
+ */
+ public function getQuery()
+ {
+ return $this->_query;
+ }
+
+ /**
+ * Get the type of this query (one of the Zend_Db_Profiler::* constants)
+ *
+ * @return integer
+ */
+ public function getQueryType()
+ {
+ return $this->_queryType;
+ }
+
+ /**
+ * @param string $param
+ * @param mixed $variable
+ * @return void
+ */
+ public function bindParam($param, $variable)
+ {
+ $this->_boundParams[$param] = $variable;
+ }
+
+ /**
+ * @param array $param
+ * @return void
+ */
+ public function bindParams(array $params)
+ {
+ if (array_key_exists(0, $params)) {
+ array_unshift($params, null);
+ unset($params[0]);
+ }
+ foreach ($params as $param => $value) {
+ $this->bindParam($param, $value);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getQueryParams()
+ {
+ return $this->_boundParams;
+ }
+
+ /**
+ * Get the elapsed time (in seconds) that the query ran.
+ * If the query has not yet ended, false is returned.
+ *
+ * @return float|false
+ */
+ public function getElapsedSecs()
+ {
+ if (null === $this->_endedMicrotime) {
+ return false;
+ }
+
+ return $this->_endedMicrotime - $this->_startedMicrotime;
+ }
+
+ /**
+ * Get the time (in seconds) when the profiler started running.
+ *
+ * @return bool|float
+ */
+ public function getStartedMicrotime()
+ {
+ if(null === $this->_startedMicrotime) {
+ return false;
+ }
+
+ return $this->_startedMicrotime;
+ }
+}
+
diff --git a/library/vendor/Zend/Db/Select.php b/library/vendor/Zend/Db/Select.php
new file mode 100644
index 0000000..64908e3
--- /dev/null
+++ b/library/vendor/Zend/Db/Select.php
@@ -0,0 +1,1368 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Select
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db_Expr
+ */
+
+
+/**
+ * Class for SQL SELECT generation and results.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Select
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Select
+{
+
+ const DISTINCT = 'distinct';
+ const COLUMNS = 'columns';
+ const FROM = 'from';
+ const UNION = 'union';
+ const WHERE = 'where';
+ const GROUP = 'group';
+ const HAVING = 'having';
+ const ORDER = 'order';
+ const LIMIT_COUNT = 'limitcount';
+ const LIMIT_OFFSET = 'limitoffset';
+ const FOR_UPDATE = 'forupdate';
+
+ const INNER_JOIN = 'inner join';
+ const LEFT_JOIN = 'left join';
+ const RIGHT_JOIN = 'right join';
+ const FULL_JOIN = 'full join';
+ const CROSS_JOIN = 'cross join';
+ const NATURAL_JOIN = 'natural join';
+
+ const SQL_WILDCARD = '*';
+ const SQL_SELECT = 'SELECT';
+ const SQL_UNION = 'UNION';
+ const SQL_UNION_ALL = 'UNION ALL';
+ const SQL_FROM = 'FROM';
+ const SQL_WHERE = 'WHERE';
+ const SQL_DISTINCT = 'DISTINCT';
+ const SQL_GROUP_BY = 'GROUP BY';
+ const SQL_ORDER_BY = 'ORDER BY';
+ const SQL_HAVING = 'HAVING';
+ const SQL_FOR_UPDATE = 'FOR UPDATE';
+ const SQL_AND = 'AND';
+ const SQL_AS = 'AS';
+ const SQL_OR = 'OR';
+ const SQL_ON = 'ON';
+ const SQL_ASC = 'ASC';
+ const SQL_DESC = 'DESC';
+
+ const REGEX_COLUMN_EXPR = '/^([\w]*\s*\(([^\(\)]|(?1))*\))$/';
+ const REGEX_COLUMN_EXPR_ORDER = '/^([\w]+\s*\(([^\(\)]|(?1))*\))$/';
+ const REGEX_COLUMN_EXPR_GROUP = '/^([\w]+\s*\(([^\(\)]|(?1))*\))$/';
+
+ // @see http://stackoverflow.com/a/13823184/2028814
+ const REGEX_SQL_COMMENTS = '@
+ (([\'"]).*?[^\\\]\2) # $1 : Skip single & double quoted expressions
+ |( # $3 : Match comments
+ (?:\#|--).*?$ # - Single line comments
+ | # - Multi line (nested) comments
+ /\* # . comment open marker
+ (?: [^/*] # . non comment-marker characters
+ |/(?!\*) # . ! not a comment open
+ |\*(?!/) # . ! not a comment close
+ |(?R) # . recursive case
+ )* # . repeat eventually
+ \*\/ # . comment close marker
+ )\s* # Trim after comments
+ |(?<=;)\s+ # Trim after semi-colon
+ @msx';
+
+ /**
+ * Bind variables for query
+ *
+ * @var array
+ */
+ protected $_bind = array();
+
+ /**
+ * Zend_Db_Adapter_Abstract object.
+ *
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $_adapter;
+
+ /**
+ * The initial values for the $_parts array.
+ * NOTE: It is important for the 'FOR_UPDATE' part to be last to ensure
+ * meximum compatibility with database adapters.
+ *
+ * @var array
+ */
+ protected static $_partsInit = array(
+ self::DISTINCT => false,
+ self::COLUMNS => array(),
+ self::UNION => array(),
+ self::FROM => array(),
+ self::WHERE => array(),
+ self::GROUP => array(),
+ self::HAVING => array(),
+ self::ORDER => array(),
+ self::LIMIT_COUNT => null,
+ self::LIMIT_OFFSET => null,
+ self::FOR_UPDATE => false
+ );
+
+ /**
+ * Specify legal join types.
+ *
+ * @var array
+ */
+ protected static $_joinTypes = array(
+ self::INNER_JOIN,
+ self::LEFT_JOIN,
+ self::RIGHT_JOIN,
+ self::FULL_JOIN,
+ self::CROSS_JOIN,
+ self::NATURAL_JOIN,
+ );
+
+ /**
+ * Specify legal union types.
+ *
+ * @var array
+ */
+ protected static $_unionTypes = array(
+ self::SQL_UNION,
+ self::SQL_UNION_ALL
+ );
+
+ /**
+ * The component parts of a SELECT statement.
+ * Initialized to the $_partsInit array in the constructor.
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * Tracks which columns are being select from each table and join.
+ *
+ * @var array
+ */
+ protected $_tableCols = array();
+
+ /**
+ * Class constructor
+ *
+ * @param Zend_Db_Adapter_Abstract $adapter
+ */
+ public function __construct(Zend_Db_Adapter_Abstract $adapter)
+ {
+ $this->_adapter = $adapter;
+ $this->_parts = self::$_partsInit;
+ }
+
+ /**
+ * Get bind variables
+ *
+ * @return array
+ */
+ public function getBind()
+ {
+ return $this->_bind;
+ }
+
+ /**
+ * Set bind variables
+ *
+ * @param mixed $bind
+ * @return Zend_Db_Select
+ */
+ public function bind($bind)
+ {
+ $this->_bind = $bind;
+
+ return $this;
+ }
+
+ /**
+ * Makes the query SELECT DISTINCT.
+ *
+ * @param bool $flag Whether or not the SELECT is DISTINCT (default true).
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function distinct($flag = true)
+ {
+ $this->_parts[self::DISTINCT] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Adds a FROM table and optional columns to the query.
+ *
+ * The first parameter $name can be a simple string, in which case the
+ * correlation name is generated automatically. If you want to specify
+ * the correlation name, the first parameter must be an associative
+ * array in which the key is the correlation name, and the value is
+ * the physical table name. For example, array('alias' => 'table').
+ * The correlation name is prepended to all columns fetched for this
+ * table.
+ *
+ * The second parameter can be a single string or Zend_Db_Expr object,
+ * or else an array of strings or Zend_Db_Expr objects.
+ *
+ * The first parameter can be null or an empty string, in which case
+ * no correlation name is generated or prepended to the columns named
+ * in the second parameter.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name or an associative array
+ * relating correlation name to table name.
+ * @param array|string|Zend_Db_Expr $cols The columns to select from this table.
+ * @param string $schema The schema name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function from($name, $cols = '*', $schema = null)
+ {
+ return $this->_join(self::FROM, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Specifies the columns used in the FROM clause.
+ *
+ * The parameter can be a single string or Zend_Db_Expr object,
+ * or else an array of strings or Zend_Db_Expr objects.
+ *
+ * @param array|string|Zend_Db_Expr $cols The columns to select from this table.
+ * @param string $correlationName Correlation name of target table. OPTIONAL
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function columns($cols = '*', $correlationName = null)
+ {
+ if ($correlationName === null && count($this->_parts[self::FROM])) {
+ $correlationNameKeys = array_keys($this->_parts[self::FROM]);
+ $correlationName = current($correlationNameKeys);
+ }
+
+ if (!array_key_exists($correlationName, $this->_parts[self::FROM])) {
+ /**
+ * @see Zend_Db_Select_Exception
+ */
+ throw new Zend_Db_Select_Exception("No table has been specified for the FROM clause");
+ }
+
+ $this->_tableCols($correlationName, $cols);
+
+ return $this;
+ }
+
+ /**
+ * Adds a UNION clause to the query.
+ *
+ * The first parameter has to be an array of Zend_Db_Select or
+ * sql query strings.
+ *
+ * <code>
+ * $sql1 = $db->select();
+ * $sql2 = "SELECT ...";
+ * $select = $db->select()
+ * ->union(array($sql1, $sql2))
+ * ->order("id");
+ * </code>
+ *
+ * @param array $select Array of select clauses for the union.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function union($select = array(), $type = self::SQL_UNION)
+ {
+ if (!is_array($select)) {
+ throw new Zend_Db_Select_Exception(
+ "union() only accepts an array of Zend_Db_Select instances of sql query strings."
+ );
+ }
+
+ if (!in_array($type, self::$_unionTypes)) {
+ throw new Zend_Db_Select_Exception("Invalid union type '{$type}'");
+ }
+
+ foreach ($select as $target) {
+ $this->_parts[self::UNION][] = array($target, $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a JOIN table and columns to the query.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function join($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->joinInner($name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add an INNER JOIN table and colums to the query
+ * Rows in both tables are matched according to the expression
+ * in the $cond argument. The result set is comprised
+ * of all cases where rows from the left table match
+ * rows from the right table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function joinInner($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::INNER_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a LEFT OUTER JOIN table and colums to the query
+ * All rows from the left operand table are included,
+ * matching rows from the right operand table included,
+ * and the columns from the right operand table are filled
+ * with NULLs if no row exists matching the left table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function joinLeft($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::LEFT_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a RIGHT OUTER JOIN table and colums to the query.
+ * Right outer join is the complement of left outer join.
+ * All rows from the right operand table are included,
+ * matching rows from the left operand table included,
+ * and the columns from the left operand table are filled
+ * with NULLs if no row exists matching the right table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function joinRight($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::RIGHT_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a FULL OUTER JOIN table and colums to the query.
+ * A full outer join is like combining a left outer join
+ * and a right outer join. All rows from both tables are
+ * included, paired with each other on the same row of the
+ * result set if they satisfy the join condition, and otherwise
+ * paired with NULLs in place of columns from the other table.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param string $cond Join on this condition.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function joinFull($name, $cond, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::FULL_JOIN, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Add a CROSS JOIN table and colums to the query.
+ * A cross join is a cartesian product; there is no join condition.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function joinCross($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::CROSS_JOIN, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Add a NATURAL JOIN table and colums to the query.
+ * A natural join assumes an equi-join across any column(s)
+ * that appear with the same name in both tables.
+ * Only natural inner joins are supported by this API,
+ * even though SQL permits natural outer joins as well.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param array|string|Zend_Db_Expr $name The table name.
+ * @param array|string $cols The columns to select from the joined table.
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function joinNatural($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ return $this->_join(self::NATURAL_JOIN, $name, null, $cols, $schema);
+ }
+
+ /**
+ * Adds a WHERE condition to the query by AND.
+ *
+ * If a value is passed as the second param, it will be quoted
+ * and replaced into the condition wherever a question-mark
+ * appears. Array values are quoted and comma-separated.
+ *
+ * <code>
+ * // simplest but non-secure
+ * $select->where("id = $id");
+ *
+ * // secure (ID is quoted but matched anyway)
+ * $select->where('id = ?', $id);
+ *
+ * // alternatively, with named binding
+ * $select->where('id = :id');
+ * </code>
+ *
+ * Note that it is more correct to use named bindings in your
+ * queries for values other than strings. When you use named
+ * bindings, don't forget to pass the values when actually
+ * making a query:
+ *
+ * <code>
+ * $db->fetchAll($select, array('id' => 5));
+ * </code>
+ *
+ * @param string $cond The WHERE condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function where($cond, $value = null, $type = null)
+ {
+ $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, true);
+
+ return $this;
+ }
+
+ /**
+ * Adds a WHERE condition to the query by OR.
+ *
+ * Otherwise identical to where().
+ *
+ * @param string $cond The WHERE condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ *
+ * @see where()
+ */
+ public function orWhere($cond, $value = null, $type = null)
+ {
+ $this->_parts[self::WHERE][] = $this->_where($cond, $value, $type, false);
+
+ return $this;
+ }
+
+ /**
+ * Adds grouping to the query.
+ *
+ * @param array|string $spec The column(s) to group by.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function group($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = array($spec);
+ }
+
+ foreach ($spec as $val) {
+ // Remove comments from SQL statement
+ $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val);
+ if (preg_match(self::REGEX_COLUMN_EXPR_GROUP, $noComments)) {
+ $val = new Zend_Db_Expr($val);
+ }
+ $this->_parts[self::GROUP][] = $val;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a HAVING condition to the query by AND.
+ *
+ * If a value is passed as the second param, it will be quoted
+ * and replaced into the condition wherever a question-mark
+ * appears. See {@link where()} for an example
+ *
+ * @param string $cond The HAVING condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function having($cond, $value = null, $type = null)
+ {
+ if ($value !== null) {
+ $cond = $this->_adapter->quoteInto($cond, $value, $type);
+ }
+
+ if ($this->_parts[self::HAVING]) {
+ $this->_parts[self::HAVING][] = self::SQL_AND . " ($cond)";
+ } else {
+ $this->_parts[self::HAVING][] = "($cond)";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a HAVING condition to the query by OR.
+ *
+ * Otherwise identical to orHaving().
+ *
+ * @param string $cond The HAVING condition.
+ * @param mixed $value OPTIONAL The value to quote into the condition.
+ * @param int $type OPTIONAL The type of the given value
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ *
+ * @see having()
+ */
+ public function orHaving($cond, $value = null, $type = null)
+ {
+ if ($value !== null) {
+ $cond = $this->_adapter->quoteInto($cond, $value, $type);
+ }
+
+ if ($this->_parts[self::HAVING]) {
+ $this->_parts[self::HAVING][] = self::SQL_OR . " ($cond)";
+ } else {
+ $this->_parts[self::HAVING][] = "($cond)";
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a row order to the query.
+ *
+ * @param mixed $spec The column(s) and direction to order by.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function order($spec)
+ {
+ if (!is_array($spec)) {
+ $spec = array($spec);
+ }
+
+ // force 'ASC' or 'DESC' on each order spec, default is ASC.
+ foreach ($spec as $val) {
+ if ($val instanceof Zend_Db_Expr) {
+ $expr = $val->__toString();
+ if (empty($expr)) {
+ continue;
+ }
+ $this->_parts[self::ORDER][] = $val;
+ } else {
+ if (empty($val)) {
+ continue;
+ }
+ $direction = self::SQL_ASC;
+ if (preg_match('/(.*\W)(' . self::SQL_ASC . '|' . self::SQL_DESC . ')\b/si', $val, $matches)) {
+ $val = trim($matches[1]);
+ $direction = $matches[2];
+ }
+ // Remove comments from SQL statement
+ $noComments = preg_replace(self::REGEX_SQL_COMMENTS, '$1', (string) $val);
+ if (preg_match(self::REGEX_COLUMN_EXPR_ORDER, $noComments)) {
+ $val = new Zend_Db_Expr($val);
+ }
+ $this->_parts[self::ORDER][] = array($val, $direction);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a limit count and offset to the query.
+ *
+ * @param int $count OPTIONAL The number of rows to return.
+ * @param int $offset OPTIONAL Start returning after this many rows.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->_parts[self::LIMIT_COUNT] = (int) $count;
+ $this->_parts[self::LIMIT_OFFSET] = (int) $offset;
+ return $this;
+ }
+
+ /**
+ * Sets the limit and count by page number.
+ *
+ * @param int $page Limit results to this page number.
+ * @param int $rowCount Use this many rows per page.
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function limitPage($page, $rowCount)
+ {
+ $page = ($page > 0) ? $page : 1;
+ $rowCount = ($rowCount > 0) ? $rowCount : 1;
+ $this->_parts[self::LIMIT_COUNT] = (int) $rowCount;
+ $this->_parts[self::LIMIT_OFFSET] = (int) $rowCount * ($page - 1);
+ return $this;
+ }
+
+ /**
+ * Makes the query SELECT FOR UPDATE.
+ *
+ * @param bool $flag Whether or not the SELECT is FOR UPDATE (default true).
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function forUpdate($flag = true)
+ {
+ $this->_parts[self::FOR_UPDATE] = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get part of the structured information for the current query.
+ *
+ * @param string $part
+ * @return mixed
+ * @throws Zend_Db_Select_Exception
+ */
+ public function getPart($part)
+ {
+ $part = strtolower($part);
+ if (!array_key_exists($part, $this->_parts)) {
+ throw new Zend_Db_Select_Exception("Invalid Select part '$part'");
+ }
+ return $this->_parts[$part];
+ }
+
+ /**
+ * Executes the current select object and returns the result
+ *
+ * @param integer $fetchMode OPTIONAL
+ * @param mixed $bind An array of data to bind to the placeholders.
+ * @return PDO_Statement|Zend_Db_Statement
+ */
+ public function query($fetchMode = null, $bind = array())
+ {
+ if (!empty($bind)) {
+ $this->bind($bind);
+ }
+
+ $stmt = $this->_adapter->query($this);
+ if ($fetchMode == null) {
+ $fetchMode = $this->_adapter->getFetchMode();
+ }
+ $stmt->setFetchMode($fetchMode);
+ return $stmt;
+ }
+
+ /**
+ * Converts this object to an SQL SELECT string.
+ *
+ * @return string|null This object as a SELECT string. (or null if a string cannot be produced.)
+ */
+ public function assemble()
+ {
+ $sql = self::SQL_SELECT;
+ foreach (array_keys(self::$_partsInit) as $part) {
+ $method = '_render' . ucfirst($part);
+ if (method_exists($this, $method)) {
+ $sql = $this->$method($sql);
+ }
+ }
+ return $sql;
+ }
+
+ /**
+ * Clear parts of the Select object, or an individual part.
+ *
+ * @param string $part OPTIONAL
+ * @return Zend_Db_Select
+ */
+ public function reset($part = null)
+ {
+ if ($part == null) {
+ $this->_parts = self::$_partsInit;
+ } elseif (array_key_exists($part, self::$_partsInit)) {
+ $this->_parts[$part] = self::$_partsInit[$part];
+ }
+ return $this;
+ }
+
+ /**
+ * Gets the Zend_Db_Adapter_Abstract for this
+ * particular Zend_Db_Select object.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Populate the {@link $_parts} 'join' key
+ *
+ * Does the dirty work of populating the join key.
+ *
+ * The $name and $cols parameters follow the same logic
+ * as described in the from() method.
+ *
+ * @param null|string $type Type of join; inner, left, and null are currently supported
+ * @param array|string|Zend_Db_Expr $name Table name
+ * @param string $cond Join on this condition
+ * @param array|string $cols The columns to select from the joined table
+ * @param string $schema The database name to specify, if any.
+ * @return Zend_Db_Select This Zend_Db_Select object
+ * @throws Zend_Db_Select_Exception
+ */
+ protected function _join($type, $name, $cond, $cols, $schema = null)
+ {
+ if (!in_array($type, self::$_joinTypes) && $type != self::FROM) {
+ /**
+ * @see Zend_Db_Select_Exception
+ */
+ throw new Zend_Db_Select_Exception("Invalid join type '$type'");
+ }
+
+ if (count($this->_parts[self::UNION])) {
+ throw new Zend_Db_Select_Exception("Invalid use of table with " . self::SQL_UNION);
+ }
+
+ if (empty($name)) {
+ $correlationName = $tableName = '';
+ } elseif (is_array($name)) {
+ // Must be array($correlationName => $tableName) or array($ident, ...)
+ foreach ($name as $_correlationName => $_tableName) {
+ if (is_string($_correlationName)) {
+ // We assume the key is the correlation name and value is the table name
+ $tableName = $_tableName;
+ $correlationName = $_correlationName;
+ } else {
+ // We assume just an array of identifiers, with no correlation name
+ $tableName = $_tableName;
+ $correlationName = $this->_uniqueCorrelation($tableName);
+ }
+ break;
+ }
+ } elseif ($name instanceof Zend_Db_Expr|| $name instanceof Zend_Db_Select) {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation('t');
+ } elseif (preg_match('/^(.+)\s+AS\s+(.+)$/i', $name, $m)) {
+ $tableName = $m[1];
+ $correlationName = $m[2];
+ } else {
+ $tableName = $name;
+ $correlationName = $this->_uniqueCorrelation($tableName);
+ }
+
+ // Schema from table name overrides schema argument
+ if (!is_object($tableName) && false !== strpos($tableName, '.')) {
+ list($schema, $tableName) = explode('.', $tableName);
+ }
+
+ $lastFromCorrelationName = null;
+ if (!empty($correlationName)) {
+ if (array_key_exists($correlationName, $this->_parts[self::FROM])) {
+ /**
+ * @see Zend_Db_Select_Exception
+ */
+ throw new Zend_Db_Select_Exception("You cannot define a correlation name '$correlationName' more than once");
+ }
+
+ if ($type == self::FROM) {
+ // append this from after the last from joinType
+ $tmpFromParts = $this->_parts[self::FROM];
+ $this->_parts[self::FROM] = array();
+ // move all the froms onto the stack
+ while ($tmpFromParts) {
+ $currentCorrelationName = key($tmpFromParts);
+ if ($tmpFromParts[$currentCorrelationName]['joinType'] != self::FROM) {
+ break;
+ }
+ $lastFromCorrelationName = $currentCorrelationName;
+ $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts);
+ }
+ } else {
+ $tmpFromParts = array();
+ }
+ $this->_parts[self::FROM][$correlationName] = array(
+ 'joinType' => $type,
+ 'schema' => $schema,
+ 'tableName' => $tableName,
+ 'joinCondition' => $cond
+ );
+ while ($tmpFromParts) {
+ $currentCorrelationName = key($tmpFromParts);
+ $this->_parts[self::FROM][$currentCorrelationName] = array_shift($tmpFromParts);
+ }
+ }
+
+ // add to the columns from this joined table
+ if ($type == self::FROM && $lastFromCorrelationName == null) {
+ $lastFromCorrelationName = true;
+ }
+ $this->_tableCols($correlationName, $cols, $lastFromCorrelationName);
+
+ return $this;
+ }
+
+ /**
+ * Handle JOIN... USING... syntax
+ *
+ * This is functionality identical to the existing JOIN methods, however
+ * the join condition can be passed as a single column name. This method
+ * then completes the ON condition by using the same field for the FROM
+ * table and the JOIN table.
+ *
+ * <code>
+ * $select = $db->select()->from('table1')
+ * ->joinUsing('table2', 'column1');
+ *
+ * // SELECT * FROM table1 JOIN table2 ON table1.column1 = table2.column2
+ * </code>
+ *
+ * These joins are called by the developer simply by adding 'Using' to the
+ * method name. E.g.
+ * * joinUsing
+ * * joinInnerUsing
+ * * joinFullUsing
+ * * joinRightUsing
+ * * joinLeftUsing
+ *
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function _joinUsing($type, $name, $cond, $cols = '*', $schema = null)
+ {
+ if (empty($this->_parts[self::FROM])) {
+ throw new Zend_Db_Select_Exception("You can only perform a joinUsing after specifying a FROM table");
+ }
+
+ $join = $this->_adapter->quoteIdentifier(key($this->_parts[self::FROM]), true);
+ $from = $this->_adapter->quoteIdentifier($this->_uniqueCorrelation($name), true);
+
+ $joinCond = array();
+ foreach ((array)$cond as $fieldName) {
+ $cond1 = $from . '.' . $fieldName;
+ $cond2 = $join . '.' . $fieldName;
+ $joinCond[] = $cond1 . ' = ' . $cond2;
+ }
+ $cond = implode(' '.self::SQL_AND.' ', $joinCond);
+
+ return $this->_join($type, $name, $cond, $cols, $schema);
+ }
+
+ /**
+ * Generate a unique correlation name
+ *
+ * @param string|array $name A qualified identifier.
+ * @return string A unique correlation name.
+ */
+ private function _uniqueCorrelation($name)
+ {
+ if (is_array($name)) {
+ $k = key($name);
+ $c = is_string($k) ? $k : end($name);
+ } else {
+ // Extract just the last name of a qualified table name
+ $dot = strrpos($name,'.');
+ $c = ($dot === false) ? $name : substr($name, $dot+1);
+ }
+ for ($i = 2; array_key_exists($c, $this->_parts[self::FROM]); ++$i) {
+ $c = $name . '_' . (string) $i;
+ }
+ return $c;
+ }
+
+ /**
+ * Adds to the internal table-to-column mapping array.
+ *
+ * @param string $tbl The table/join the columns come from.
+ * @param array|string $cols The list of columns; preferably as
+ * an array, but possibly as a string containing one column.
+ * @param bool|string True if it should be prepended, a correlation name if it should be inserted
+ * @return void
+ */
+ protected function _tableCols($correlationName, $cols, $afterCorrelationName = null)
+ {
+ if (!is_array($cols)) {
+ $cols = array($cols);
+ }
+
+ if ($correlationName == null) {
+ $correlationName = '';
+ }
+
+ $columnValues = array();
+
+ foreach (array_filter($cols) as $alias => $col) {
+ $currentCorrelationName = $correlationName;
+ if (is_string($col)) {
+ // Check for a column matching "<column> AS <alias>" and extract the alias name
+ $col = trim(str_replace("\n", ' ', $col));
+ if (preg_match('/^(.+)\s+' . self::SQL_AS . '\s+(.+)$/i', $col, $m)) {
+ $col = $m[1];
+ $alias = $m[2];
+ }
+ // Check for columns that look like functions and convert to Zend_Db_Expr
+ if (preg_match(self::REGEX_COLUMN_EXPR, (string) $col)) {
+ $col = new Zend_Db_Expr($col);
+ } elseif (preg_match('/(.+)\.(.+)/', $col, $m)) {
+ $currentCorrelationName = $m[1];
+ $col = $m[2];
+ }
+ }
+ $columnValues[] = array($currentCorrelationName, $col, is_string($alias) ? $alias : null);
+ }
+
+ if ($columnValues) {
+
+ // should we attempt to prepend or insert these values?
+ if ($afterCorrelationName === true || is_string($afterCorrelationName)) {
+ $tmpColumns = $this->_parts[self::COLUMNS];
+ $this->_parts[self::COLUMNS] = array();
+ } else {
+ $tmpColumns = array();
+ }
+
+ // find the correlation name to insert after
+ if (is_string($afterCorrelationName)) {
+ while ($tmpColumns) {
+ $this->_parts[self::COLUMNS][] = $currentColumn = array_shift($tmpColumns);
+ if ($currentColumn[0] == $afterCorrelationName) {
+ break;
+ }
+ }
+ }
+
+ // apply current values to current stack
+ foreach ($columnValues as $columnValue) {
+ array_push($this->_parts[self::COLUMNS], $columnValue);
+ }
+
+ // finish ensuring that all previous values are applied (if they exist)
+ while ($tmpColumns) {
+ array_push($this->_parts[self::COLUMNS], array_shift($tmpColumns));
+ }
+ }
+ }
+
+ /**
+ * Internal function for creating the where clause
+ *
+ * @param string $condition
+ * @param mixed $value optional
+ * @param string $type optional
+ * @param boolean $bool true = AND, false = OR
+ * @return string clause
+ */
+ protected function _where($condition, $value = null, $type = null, $bool = true)
+ {
+ if (count($this->_parts[self::UNION])) {
+ throw new Zend_Db_Select_Exception("Invalid use of where clause with " . self::SQL_UNION);
+ }
+
+ if ($value !== null) {
+ $condition = $this->_adapter->quoteInto($condition, $value, $type);
+ }
+
+ $cond = "";
+ if ($this->_parts[self::WHERE]) {
+ if ($bool === true) {
+ $cond = self::SQL_AND . ' ';
+ } else {
+ $cond = self::SQL_OR . ' ';
+ }
+ }
+
+ return $cond . "($condition)";
+ }
+
+ /**
+ * @return array
+ */
+ protected function _getDummyTable()
+ {
+ return array();
+ }
+
+ /**
+ * Return a quoted schema name
+ *
+ * @param string $schema The schema name OPTIONAL
+ * @return string|null
+ */
+ protected function _getQuotedSchema($schema = null)
+ {
+ if ($schema === null) {
+ return null;
+ }
+ return $this->_adapter->quoteIdentifier($schema, true) . '.';
+ }
+
+ /**
+ * Return a quoted table name
+ *
+ * @param string $tableName The table name
+ * @param string $correlationName The correlation name OPTIONAL
+ * @return string
+ */
+ protected function _getQuotedTable($tableName, $correlationName = null)
+ {
+ return $this->_adapter->quoteTableAs($tableName, $correlationName, true);
+ }
+
+ /**
+ * Render DISTINCT clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderDistinct($sql)
+ {
+ if ($this->_parts[self::DISTINCT]) {
+ $sql .= ' ' . self::SQL_DISTINCT;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render DISTINCT clause
+ *
+ * @param string $sql SQL query
+ * @return string|null
+ */
+ protected function _renderColumns($sql)
+ {
+ if (!count($this->_parts[self::COLUMNS])) {
+ return null;
+ }
+
+ $columns = array();
+ foreach ($this->_parts[self::COLUMNS] as $columnEntry) {
+ list($correlationName, $column, $alias) = $columnEntry;
+ if ($column instanceof Zend_Db_Expr) {
+ $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true);
+ } else {
+ if ($column == self::SQL_WILDCARD) {
+ $column = new Zend_Db_Expr(self::SQL_WILDCARD);
+ $alias = null;
+ }
+ if (empty($correlationName)) {
+ $columns[] = $this->_adapter->quoteColumnAs($column, $alias, true);
+ } else {
+ $columns[] = $this->_adapter->quoteColumnAs(array($correlationName, $column), $alias, true);
+ }
+ }
+ }
+
+ return $sql . ' ' . implode(', ', $columns);
+ }
+
+ /**
+ * Render FROM clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderFrom($sql)
+ {
+ /*
+ * If no table specified, use RDBMS-dependent solution
+ * for table-less query. e.g. DUAL in Oracle.
+ */
+ if (empty($this->_parts[self::FROM])) {
+ $this->_parts[self::FROM] = $this->_getDummyTable();
+ }
+
+ $from = array();
+
+ foreach ($this->_parts[self::FROM] as $correlationName => $table) {
+ $tmp = '';
+
+ $joinType = ($table['joinType'] == self::FROM) ? self::INNER_JOIN : $table['joinType'];
+
+ // Add join clause (if applicable)
+ if (! empty($from)) {
+ $tmp .= ' ' . strtoupper($joinType) . ' ';
+ }
+
+ $tmp .= $this->_getQuotedSchema($table['schema']);
+ $tmp .= $this->_getQuotedTable($table['tableName'], $correlationName);
+
+ // Add join conditions (if applicable)
+ if (!empty($from) && ! empty($table['joinCondition'])) {
+ $tmp .= ' ' . self::SQL_ON . ' ' . $table['joinCondition'];
+ }
+
+ // Add the table name and condition add to the list
+ $from[] = $tmp;
+ }
+
+ // Add the list of all joins
+ if (!empty($from)) {
+ $sql .= ' ' . self::SQL_FROM . ' ' . implode("\n", $from);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render UNION query
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderUnion($sql)
+ {
+ if ($this->_parts[self::UNION]) {
+ $parts = count($this->_parts[self::UNION]);
+ foreach ($this->_parts[self::UNION] as $cnt => $union) {
+ list($target, $type) = $union;
+ if ($target instanceof Zend_Db_Select) {
+ $target = $target->assemble();
+ }
+ $sql .= $target;
+ if ($cnt < $parts - 1) {
+ $sql .= ' ' . $type . ' ';
+ }
+ }
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render WHERE clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderWhere($sql)
+ {
+ if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) {
+ $sql .= ' ' . self::SQL_WHERE . ' ' . implode(' ', $this->_parts[self::WHERE]);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render GROUP clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderGroup($sql)
+ {
+ if ($this->_parts[self::FROM] && $this->_parts[self::GROUP]) {
+ $group = array();
+ foreach ($this->_parts[self::GROUP] as $term) {
+ $group[] = $this->_adapter->quoteIdentifier($term, true);
+ }
+ $sql .= ' ' . self::SQL_GROUP_BY . ' ' . implode(",\n\t", $group);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render HAVING clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderHaving($sql)
+ {
+ if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) {
+ $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render ORDER clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderOrder($sql)
+ {
+ if ($this->_parts[self::ORDER]) {
+ $order = array();
+ foreach ($this->_parts[self::ORDER] as $term) {
+ if (is_array($term)) {
+ if(is_numeric($term[0]) && strval(intval($term[0])) == $term[0]) {
+ $order[] = (int)trim($term[0]) . ' ' . $term[1];
+ } else {
+ $order[] = $this->_adapter->quoteIdentifier($term[0], true) . ' ' . $term[1];
+ }
+ } elseif (is_numeric($term) && strval(intval($term)) == $term) {
+ $order[] = (int)trim($term);
+ } else {
+ $order[] = $this->_adapter->quoteIdentifier($term, true);
+ }
+ }
+ $sql .= ' ' . self::SQL_ORDER_BY . ' ' . implode(', ', $order);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render LIMIT OFFSET clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderLimitoffset($sql)
+ {
+ $count = 0;
+ $offset = 0;
+
+ if (!empty($this->_parts[self::LIMIT_OFFSET])) {
+ $offset = (int) $this->_parts[self::LIMIT_OFFSET];
+ $count = PHP_INT_MAX;
+ }
+
+ if (!empty($this->_parts[self::LIMIT_COUNT])) {
+ $count = (int) $this->_parts[self::LIMIT_COUNT];
+ }
+
+ /*
+ * Add limits clause
+ */
+ if ($count > 0) {
+ $sql = trim($this->_adapter->limit($sql, $count, $offset));
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Render FOR UPDATE clause
+ *
+ * @param string $sql SQL query
+ * @return string
+ */
+ protected function _renderForupdate($sql)
+ {
+ if ($this->_parts[self::FOR_UPDATE]) {
+ $sql .= ' ' . self::SQL_FOR_UPDATE;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Turn magic function calls into non-magic function calls
+ * for joinUsing syntax
+ *
+ * @param string $method
+ * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
+ * @return Zend_Db_Select
+ * @throws Zend_Db_Select_Exception If an invalid method is called.
+ */
+ public function __call($method, array $args)
+ {
+ $matches = array();
+
+ /**
+ * Recognize methods for Has-Many cases:
+ * findParent<Class>()
+ * findParent<Class>By<Rule>()
+ * Use the non-greedy pattern repeat modifier e.g. \w+?
+ */
+ if (preg_match('/^join([a-zA-Z]*?)Using$/', $method, $matches)) {
+ $type = strtolower($matches[1]);
+ if ($type) {
+ $type .= ' join';
+ if (!in_array($type, self::$_joinTypes)) {
+ throw new Zend_Db_Select_Exception("Unrecognized method '$method()'");
+ }
+ if (in_array($type, array(self::CROSS_JOIN, self::NATURAL_JOIN))) {
+ throw new Zend_Db_Select_Exception("Cannot perform a joinUsing with method '$method()'");
+ }
+ } else {
+ $type = self::INNER_JOIN;
+ }
+ array_unshift($args, $type);
+ return call_user_func_array(array($this, '_joinUsing'), $args);
+ }
+
+ throw new Zend_Db_Select_Exception("Unrecognized method '$method()'");
+ }
+
+ /**
+ * Implements magic method.
+ *
+ * @return string This object as a SELECT string.
+ */
+ public function __toString()
+ {
+ try {
+ $sql = $this->assemble();
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ $sql = '';
+ }
+ return (string)$sql;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Select/Exception.php b/library/vendor/Zend/Db/Select/Exception.php
new file mode 100644
index 0000000..6e7bfac
--- /dev/null
+++ b/library/vendor/Zend/Db/Select/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Select
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Select
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Db_Select_Exception extends Zend_Db_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Db/Statement.php b/library/vendor/Zend/Db/Statement.php
new file mode 100644
index 0000000..4eb761c
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement.php
@@ -0,0 +1,488 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * @see Zend_Db_Statement_Interface
+ */
+
+/**
+ * Abstract class to emulate a PDOStatement for native database adapters.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Db_Statement implements Zend_Db_Statement_Interface
+{
+
+ /**
+ * @var resource|object The driver level statement object/resource
+ */
+ protected $_stmt = null;
+
+ /**
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $_adapter = null;
+
+ /**
+ * The current fetch mode.
+ *
+ * @var integer
+ */
+ protected $_fetchMode = Zend_Db::FETCH_ASSOC;
+
+ /**
+ * Attributes.
+ *
+ * @var array
+ */
+ protected $_attribute = array();
+
+ /**
+ * Column result bindings.
+ *
+ * @var array
+ */
+ protected $_bindColumn = array();
+
+ /**
+ * Query parameter bindings; covers bindParam() and bindValue().
+ *
+ * @var array
+ */
+ protected $_bindParam = array();
+
+ /**
+ * SQL string split into an array at placeholders.
+ *
+ * @var array
+ */
+ protected $_sqlSplit = array();
+
+ /**
+ * Parameter placeholders in the SQL string by position in the split array.
+ *
+ * @var array
+ */
+ protected $_sqlParam = array();
+
+ /**
+ * @var Zend_Db_Profiler_Query
+ */
+ protected $_queryId = null;
+
+ /**
+ * Constructor for a statement.
+ *
+ * @param Zend_Db_Adapter_Abstract $adapter
+ * @param mixed $sql Either a string or Zend_Db_Select.
+ */
+ public function __construct($adapter, $sql)
+ {
+ $this->_adapter = $adapter;
+ if ($sql instanceof Zend_Db_Select) {
+ $sql = $sql->assemble();
+ }
+ $this->_parseParameters($sql);
+ $this->_prepare($sql);
+
+ $this->_queryId = $this->_adapter->getProfiler()->queryStart($sql);
+ }
+
+ /**
+ * Internal method called by abstract statment constructor to setup
+ * the driver level statement
+ *
+ * @return void
+ */
+ protected function _prepare($sql)
+ {
+ return;
+ }
+
+ /**
+ * @param string $sql
+ * @return void
+ */
+ protected function _parseParameters($sql)
+ {
+ $sql = $this->_stripQuoted($sql);
+
+ // split into text and params
+ $this->_sqlSplit = empty($sql) ? [] : preg_split('/(\?|\:[a-zA-Z0-9_]+)/',
+ $sql, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
+
+ // map params
+ $this->_sqlParam = array();
+ foreach ($this->_sqlSplit as $key => $val) {
+ if ($val == '?') {
+ if ($this->_adapter->supportsParameters('positional') === false) {
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$val'");
+ }
+ } else if ($val[0] == ':') {
+ if ($this->_adapter->supportsParameters('named') === false) {
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ throw new Zend_Db_Statement_Exception("Invalid bind-variable name '$val'");
+ }
+ }
+ $this->_sqlParam[] = $val;
+ }
+
+ // set up for binding
+ $this->_bindParam = array();
+ }
+
+ /**
+ * Remove parts of a SQL string that contain quoted strings
+ * of values or identifiers.
+ *
+ * @param string $sql
+ * @return string
+ */
+ protected function _stripQuoted($sql)
+ {
+
+ // get the character for value quoting
+ // this should be '
+ $q = $this->_adapter->quote('a');
+ $q = $q[0];
+ // get the value used as an escaped quote,
+ // e.g. \' or ''
+ $qe = $this->_adapter->quote($q);
+ $qe = substr($qe, 1, 2);
+ $qe = preg_quote($qe);
+ $escapeChar = substr($qe,0,1);
+ // remove 'foo\'bar'
+ if (!empty($q)) {
+ $escapeChar = preg_quote($escapeChar);
+ if ($sql !== null) {
+ // this segfaults only after 65,000 characters instead of 9,000
+ $sql = preg_replace("/$q([^$q{$escapeChar}]*|($qe)*)*$q/s", '', $sql);
+ }
+ }
+
+ // get a version of the SQL statement with all quoted
+ // values and delimited identifiers stripped out
+ // remove "foo\"bar"
+ if ($sql !== null) {
+ $sql = preg_replace("/\"(\\\\\"|[^\"])*\"/Us", '', $sql);
+ }
+
+ // get the character for delimited id quotes,
+ // this is usually " but in MySQL is `
+ $d = $this->_adapter->quoteIdentifier('a');
+ $d = $d[0];
+ // get the value used as an escaped delimited id quote,
+ // e.g. \" or "" or \`
+ $de = $this->_adapter->quoteIdentifier($d);
+ $de = substr($de, 1, 2);
+ $de = preg_quote($de);
+ // Note: $de and $d where never used..., now they are:
+ if ($sql !== null) {
+ $sql = preg_replace("/$d($de|\\\\{2}|[^$d])*$d/Us", '', $sql);
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Bind a column of the statement result set to a PHP variable.
+ *
+ * @param string $column Name the column in the result set, either by
+ * position or by name.
+ * @param mixed $param Reference to the PHP variable containing the value.
+ * @param mixed $type OPTIONAL
+ * @return bool
+ */
+ public function bindColumn($column, &$param, $type = null)
+ {
+ $this->_bindColumn[$column] =& $param;
+ return true;
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ */
+ public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ if (!is_int($parameter) && !is_string($parameter)) {
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ throw new Zend_Db_Statement_Exception('Invalid bind-variable position');
+ }
+
+ $position = null;
+ if (($intval = (int) $parameter) > 0 && $this->_adapter->supportsParameters('positional')) {
+ if ($intval >= 1 || $intval <= count($this->_sqlParam)) {
+ $position = $intval;
+ }
+ } else if ($this->_adapter->supportsParameters('named')) {
+ if ($parameter[0] != ':') {
+ $parameter = ':' . $parameter;
+ }
+ if (in_array($parameter, $this->_sqlParam) !== false) {
+ $position = $parameter;
+ }
+ }
+
+ if ($position === null) {
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ throw new Zend_Db_Statement_Exception("Invalid bind-variable position '$parameter'");
+ }
+
+ // Finally we are assured that $position is valid
+ $this->_bindParam[$position] =& $variable;
+ return $this->_bindParam($position, $variable, $type, $length, $options);
+ }
+
+ /**
+ * Binds a value to a parameter.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $value Scalar value to bind to the parameter.
+ * @param mixed $type OPTIONAL Datatype of the parameter.
+ * @return bool
+ */
+ public function bindValue($parameter, $value, $type = null)
+ {
+ return $this->bindParam($parameter, $value, $type);
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ */
+ public function execute(array $params = null)
+ {
+ /*
+ * Simple case - no query profiler to manage.
+ */
+ if ($this->_queryId === null) {
+ return $this->_execute($params);
+ }
+
+ /*
+ * Do the same thing, but with query profiler
+ * management before and after the execute.
+ */
+ $prof = $this->_adapter->getProfiler();
+ $qp = $prof->getQueryProfile($this->_queryId);
+ if ($qp->hasEnded()) {
+ $this->_queryId = $prof->queryClone($qp);
+ $qp = $prof->getQueryProfile($this->_queryId);
+ }
+ if ($params !== null) {
+ $qp->bindParams($params);
+ } else {
+ $qp->bindParams($this->_bindParam);
+ }
+ $qp->start($this->_queryId);
+
+ $retval = $this->_execute($params);
+
+ $prof->queryEnd($this->_queryId);
+
+ return $retval;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = array();
+ if ($style === Zend_Db::FETCH_COLUMN && $col === null) {
+ $col = 0;
+ }
+ if ($col === null) {
+ while ($row = $this->fetch($style)) {
+ $data[] = $row;
+ }
+ } else {
+ while (false !== ($val = $this->fetchColumn($col))) {
+ $data[] = $val;
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string One value from the next row of result set, or false.
+ */
+ public function fetchColumn($col = 0)
+ {
+ $data = array();
+ $col = (int) $col;
+ $row = $this->fetch(Zend_Db::FETCH_NUM);
+ if (!is_array($row)) {
+ return false;
+ }
+ return $row[$col];
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class, or false.
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ $obj = new $class($config);
+ $row = $this->fetch(Zend_Db::FETCH_ASSOC);
+ if (!is_array($row)) {
+ return false;
+ }
+ foreach ($row as $key => $val) {
+ $obj->$key = $val;
+ }
+ return $obj;
+ }
+
+ /**
+ * Retrieve a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @return mixed Attribute value.
+ */
+ public function getAttribute($key)
+ {
+ if (array_key_exists($key, $this->_attribute)) {
+ return $this->_attribute[$key];
+ }
+ }
+
+ /**
+ * Set a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @param mixed $val Attribute value.
+ * @return bool
+ */
+ public function setAttribute($key, $val)
+ {
+ $this->_attribute[$key] = $val;
+ }
+
+ /**
+ * Set the default fetch mode for this statement.
+ *
+ * @param int $mode The fetch mode.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ switch ($mode) {
+ case Zend_Db::FETCH_NUM:
+ case Zend_Db::FETCH_ASSOC:
+ case Zend_Db::FETCH_BOTH:
+ case Zend_Db::FETCH_OBJ:
+ $this->_fetchMode = $mode;
+ break;
+ case Zend_Db::FETCH_BOUND:
+ default:
+ $this->closeCursor();
+ /**
+ * @see Zend_Db_Statement_Exception
+ */
+ throw new Zend_Db_Statement_Exception('invalid fetch mode');
+ break;
+ }
+ }
+
+ /**
+ * Helper function to map retrieved row
+ * to bound column variables
+ *
+ * @param array $row
+ * @return bool True
+ */
+ public function _fetchBound($row)
+ {
+ foreach ($row as $key => $value) {
+ // bindColumn() takes 1-based integer positions
+ // but fetch() returns 0-based integer indexes
+ if (is_int($key)) {
+ $key++;
+ }
+ // set results only to variables that were bound previously
+ if (isset($this->_bindColumn[$key])) {
+ $this->_bindColumn[$key] = $value;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets the Zend_Db_Adapter_Abstract for this
+ * particular Zend_Db_Statement object.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Gets the resource or object setup by the
+ * _parse
+ * @return unknown_type
+ */
+ public function getDriverStatement()
+ {
+ return $this->_stmt;
+ }
+}
diff --git a/library/vendor/Zend/Db/Statement/Db2.php b/library/vendor/Zend/Db/Statement/Db2.php
new file mode 100644
index 0000000..b817864
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Db2.php
@@ -0,0 +1,354 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement
+ */
+
+/**
+ * Extends for DB2 native adapter.
+ *
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Db2 extends Zend_Db_Statement
+{
+
+ /**
+ * Column names.
+ */
+ protected $_keys;
+
+ /**
+ * Fetched result values.
+ */
+ protected $_values;
+
+ /**
+ * Prepare a statement handle.
+ *
+ * @param string $sql
+ * @return void
+ * @throws Zend_Db_Statement_Db2_Exception
+ */
+ public function _prepare($sql)
+ {
+ $connection = $this->_adapter->getConnection();
+
+ // db2_prepare on i5 emits errors, these need to be
+ // suppressed so that proper exceptions can be thrown
+ $this->_stmt = @db2_prepare($connection, $sql);
+
+ if (!$this->_stmt) {
+ /**
+ * @see Zend_Db_Statement_Db2_Exception
+ */
+ throw new Zend_Db_Statement_Db2_Exception(
+ db2_stmt_errormsg(),
+ db2_stmt_error()
+ );
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Db2_Exception
+ */
+ public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ if ($type === null) {
+ $type = DB2_PARAM_IN;
+ }
+
+ if (isset($options['data-type'])) {
+ $datatype = $options['data-type'];
+ } else {
+ $datatype = DB2_CHAR;
+ }
+
+ if (!db2_bind_param($this->_stmt, $parameter, "variable", $type, $datatype)) {
+ /**
+ * @see Zend_Db_Statement_Db2_Exception
+ */
+ throw new Zend_Db_Statement_Db2_Exception(
+ db2_stmt_errormsg(),
+ db2_stmt_error()
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ db2_free_stmt($this->_stmt);
+ $this->_stmt = false;
+ return true;
+ }
+
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return db2_num_fields($this->_stmt);
+ }
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = db2_stmt_error();
+ if ($error === '') {
+ return false;
+ }
+
+ return $error;
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ */
+ public function errorInfo()
+ {
+ $error = $this->errorCode();
+ if ($error === false){
+ return false;
+ }
+
+ /*
+ * Return three-valued array like PDO. But DB2 does not distinguish
+ * between SQLCODE and native RDBMS error code, so repeat the SQLCODE.
+ */
+ return array(
+ $error,
+ $error,
+ db2_stmt_errormsg()
+ );
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws Zend_Db_Statement_Db2_Exception
+ */
+ public function _execute(array $params = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $retval = true;
+ if ($params !== null) {
+ $retval = @db2_execute($this->_stmt, $params);
+ } else {
+ $retval = @db2_execute($this->_stmt);
+ }
+
+ if ($retval === false) {
+ /**
+ * @see Zend_Db_Statement_Db2_Exception
+ */
+ throw new Zend_Db_Statement_Db2_Exception(
+ db2_stmt_errormsg(),
+ db2_stmt_error());
+ }
+
+ $this->_keys = array();
+ if ($field_num = $this->columnCount()) {
+ for ($i = 0; $i < $field_num; $i++) {
+ $name = db2_field_name($this->_stmt, $i);
+ $this->_keys[] = $name;
+ }
+ }
+
+ $this->_values = array();
+ if ($this->_keys) {
+ $this->_values = array_fill(0, count($this->_keys), null);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Db2_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ switch ($style) {
+ case Zend_Db::FETCH_NUM :
+ $row = db2_fetch_array($this->_stmt);
+ break;
+ case Zend_Db::FETCH_ASSOC :
+ $row = db2_fetch_assoc($this->_stmt);
+ break;
+ case Zend_Db::FETCH_BOTH :
+ $row = db2_fetch_both($this->_stmt);
+ break;
+ case Zend_Db::FETCH_OBJ :
+ $row = db2_fetch_object($this->_stmt);
+ break;
+ case Zend_Db::FETCH_BOUND:
+ $row = db2_fetch_both($this->_stmt);
+ if ($row !== false) {
+ return $this->_fetchBound($row);
+ }
+ break;
+ default:
+ /**
+ * @see Zend_Db_Statement_Db2_Exception
+ */
+ throw new Zend_Db_Statement_Db2_Exception("Invalid fetch mode '$style' specified");
+ break;
+ }
+
+ return $row;
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ $obj = $this->fetch(Zend_Db::FETCH_OBJ);
+ return $obj;
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Db2_Exception
+ */
+ public function nextRowset()
+ {
+ /**
+ * @see Zend_Db_Statement_Db2_Exception
+ */
+ throw new Zend_Db_Statement_Db2_Exception(__FUNCTION__ . '() is not implemented');
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ */
+ public function rowCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $num = @db2_num_rows($this->_stmt);
+
+ if ($num === false) {
+ return 0;
+ }
+
+ return $num;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+}
diff --git a/library/vendor/Zend/Db/Statement/Db2/Exception.php b/library/vendor/Zend/Db/Statement/Db2/Exception.php
new file mode 100644
index 0000000..d598a85
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Db2/Exception.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Statement_Exception
+ */
+
+/**
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Db_Statement_Db2_Exception extends Zend_Db_Statement_Exception
+{
+ /**
+ * @var string
+ */
+ protected $code = '00000';
+
+ /**
+ * @var string
+ */
+ protected $message = 'unknown exception';
+
+ /**
+ * @param string $msg
+ * @param string $state
+ */
+ function __construct($msg = 'unknown exception', $state = '00000')
+ {
+ $this->message = $msg;
+ $this->code = $state;
+ }
+
+}
+
diff --git a/library/vendor/Zend/Db/Statement/Exception.php b/library/vendor/Zend/Db/Statement/Exception.php
new file mode 100644
index 0000000..af58699
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Exception.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Exception
+ */
+
+/**
+ * Zend_Db_Statement_Exception
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Exception extends Zend_Db_Exception
+{
+ /**
+ * Check if this general exception has a specific database driver specific exception nested inside.
+ *
+ * @return bool
+ */
+ public function hasChainedException()
+ {
+ return ($this->getPrevious() !== null);
+ }
+
+ /**
+ * @return Exception|null
+ */
+ public function getChainedException()
+ {
+ return $this->getPrevious();
+ }
+}
diff --git a/library/vendor/Zend/Db/Statement/Interface.php b/library/vendor/Zend/Db/Statement/Interface.php
new file mode 100644
index 0000000..7154d91
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Interface.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Emulates a PDOStatement for native database adapters.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Db_Statement_Interface
+{
+
+ /**
+ * Bind a column of the statement result set to a PHP variable.
+ *
+ * @param string $column Name the column in the result set, either by
+ * position or by name.
+ * @param mixed $param Reference to the PHP variable containing the value.
+ * @param mixed $type OPTIONAL
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function bindColumn($column, &$param, $type = null);
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function bindParam($parameter, &$variable, $type = null, $length = null, $options = null);
+
+ /**
+ * Binds a value to a parameter.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $value Scalar value to bind to the parameter.
+ * @param mixed $type OPTIONAL Datatype of the parameter.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function bindValue($parameter, $value, $type = null);
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function closeCursor();
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function columnCount();
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function errorCode();
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function errorInfo();
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function execute(array $params = array());
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null);
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchAll($style = null, $col = null);
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchColumn($col = 0);
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchObject($class = 'stdClass', array $config = array());
+
+ /**
+ * Retrieve a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @return mixed Attribute value.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function getAttribute($key);
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function nextRowset();
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function rowCount();
+
+ /**
+ * Set a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @param mixed $val Attribute value.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function setAttribute($key, $val);
+
+ /**
+ * Set the default fetch mode for this statement.
+ *
+ * @param int $mode The fetch mode.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function setFetchMode($mode);
+
+}
diff --git a/library/vendor/Zend/Db/Statement/Mysqli.php b/library/vendor/Zend/Db/Statement/Mysqli.php
new file mode 100644
index 0000000..366d9bf
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Mysqli.php
@@ -0,0 +1,356 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Statement
+ */
+
+
+/**
+ * Extends for Mysqli
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Mysqli extends Zend_Db_Statement
+{
+
+ /**
+ * Column names.
+ *
+ * @var array
+ */
+ protected $_keys;
+
+ /**
+ * Fetched result values.
+ *
+ * @var array
+ */
+ protected $_values;
+
+ /**
+ * @var array
+ */
+ protected $_meta = null;
+
+ /**
+ * @param string $sql
+ * @return void
+ * @throws Zend_Db_Statement_Mysqli_Exception
+ */
+ public function _prepare($sql)
+ {
+ $mysqli = $this->_adapter->getConnection();
+
+ $this->_stmt = $mysqli->prepare($sql);
+
+ if ($this->_stmt === false || $mysqli->errno) {
+ /**
+ * @see Zend_Db_Statement_Mysqli_Exception
+ */
+ throw new Zend_Db_Statement_Mysqli_Exception("Mysqli prepare error: " . $mysqli->error, $mysqli->errno);
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Mysqli_Exception
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ return true;
+ }
+
+ /**
+ * Closes the cursor and the statement.
+ *
+ * @return bool
+ */
+ public function close()
+ {
+ if ($this->_stmt) {
+ $r = $this->_stmt->close();
+ $this->_stmt = null;
+ return $r;
+ }
+ return false;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if ($stmt = $this->_stmt) {
+ $mysqli = $this->_adapter->getConnection();
+ while ($mysqli->more_results()) {
+ $mysqli->next_result();
+ }
+ $this->_stmt->free_result();
+ return $this->_stmt->reset();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if (isset($this->_meta) && $this->_meta) {
+ return $this->_meta->field_count;
+ }
+ return 0;
+ }
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return substr($this->_stmt->sqlstate, 0, 5);
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ */
+ public function errorInfo()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ return array(
+ substr($this->_stmt->sqlstate, 0, 5),
+ $this->_stmt->errno,
+ $this->_stmt->error,
+ );
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws Zend_Db_Statement_Mysqli_Exception
+ */
+ public function _execute(array $params = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ // if no params were given as an argument to execute(),
+ // then default to the _bindParam array
+ if ($params === null) {
+ $params = $this->_bindParam;
+ }
+ // send $params as input parameters to the statement
+ if ($params) {
+ array_unshift($params, str_repeat('s', count($params)));
+ $stmtParams = array();
+ foreach ($params as $k => &$value) {
+ $stmtParams[$k] = &$value;
+ }
+ call_user_func_array(
+ array($this->_stmt, 'bind_param'),
+ $stmtParams
+ );
+ }
+
+ // execute the statement
+ $retval = $this->_stmt->execute();
+ if ($retval === false) {
+ /**
+ * @see Zend_Db_Statement_Mysqli_Exception
+ */
+ throw new Zend_Db_Statement_Mysqli_Exception("Mysqli statement execute error : " . $this->_stmt->error, $this->_stmt->errno);
+ }
+
+
+ // retain metadata
+ if ($this->_meta === null) {
+ $this->_meta = $this->_stmt->result_metadata();
+ if ($this->_stmt->errno) {
+ /**
+ * @see Zend_Db_Statement_Mysqli_Exception
+ */
+ throw new Zend_Db_Statement_Mysqli_Exception("Mysqli statement metadata error: " . $this->_stmt->error, $this->_stmt->errno);
+ }
+ }
+
+ // statements that have no result set do not return metadata
+ if ($this->_meta !== false) {
+
+ // get the column names that will result
+ $this->_keys = array();
+ foreach ($this->_meta->fetch_fields() as $col) {
+ $this->_keys[] = $this->_adapter->foldCase($col->name);
+ }
+
+ // set up a binding space for result variables
+ $this->_values = array_fill(0, count($this->_keys), null);
+
+ // set up references to the result binding space.
+ // just passing $this->_values in the call_user_func_array()
+ // below won't work, you need references.
+ $refs = array();
+ foreach ($this->_values as $i => &$f) {
+ $refs[$i] = &$f;
+ }
+
+ $this->_stmt->store_result();
+ // bind to the result variables
+ call_user_func_array(
+ array($this->_stmt, 'bind_result'),
+ $this->_values
+ );
+ }
+ return $retval;
+ }
+
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Mysqli_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+ // fetch the next result
+ $retval = $this->_stmt->fetch();
+ switch ($retval) {
+ case null: // end of data
+ case false: // error occurred
+ $this->_stmt->reset();
+ return false;
+ default:
+ // fallthrough
+ }
+
+ // make sure we have a fetch mode
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ // dereference the result values, otherwise things like fetchAll()
+ // return the same values for every entry (because of the reference).
+ $values = array();
+ foreach ($this->_values as $key => $val) {
+ $values[] = $val;
+ }
+
+ $row = false;
+ switch ($style) {
+ case Zend_Db::FETCH_NUM:
+ $row = $values;
+ break;
+ case Zend_Db::FETCH_ASSOC:
+ $row = array_combine($this->_keys, $values);
+ break;
+ case Zend_Db::FETCH_BOTH:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ break;
+ case Zend_Db::FETCH_OBJ:
+ $row = (object) array_combine($this->_keys, $values);
+ break;
+ case Zend_Db::FETCH_BOUND:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ return $this->_fetchBound($row);
+ break;
+ default:
+ /**
+ * @see Zend_Db_Statement_Mysqli_Exception
+ */
+ throw new Zend_Db_Statement_Mysqli_Exception("Invalid fetch mode '$style' specified");
+ break;
+ }
+ return $row;
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Mysqli_Exception
+ */
+ public function nextRowset()
+ {
+ /**
+ * @see Zend_Db_Statement_Mysqli_Exception
+ */
+ throw new Zend_Db_Statement_Mysqli_Exception(__FUNCTION__.'() is not implemented');
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ */
+ public function rowCount()
+ {
+ if (!$this->_adapter) {
+ return false;
+ }
+ $mysqli = $this->_adapter->getConnection();
+ return $mysqli->affected_rows;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Statement/Mysqli/Exception.php b/library/vendor/Zend/Db/Statement/Mysqli/Exception.php
new file mode 100644
index 0000000..89c74ec
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Mysqli/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Statement_Exception
+ */
+
+/**
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Db_Statement_Mysqli_Exception extends Zend_Db_Statement_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Db/Statement/Oracle.php b/library/vendor/Zend/Db/Statement/Oracle.php
new file mode 100644
index 0000000..58a6c28
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Oracle.php
@@ -0,0 +1,561 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement
+ */
+
+/**
+ * Extends for Oracle.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Oracle extends Zend_Db_Statement
+{
+
+ /**
+ * Column names.
+ */
+ protected $_keys;
+
+ /**
+ * Fetched result values.
+ */
+ protected $_values;
+
+ /**
+ * Check if LOB field are returned as string
+ * instead of OCI-Lob object
+ *
+ * @var boolean
+ */
+ protected $_lobAsString = false;
+
+ /**
+ * Activate/deactivate return of LOB as string
+ *
+ * @param string $lob_as_string
+ * @return Zend_Db_Statement_Oracle
+ */
+ public function setLobAsString($lob_as_string)
+ {
+ $this->_lobAsString = (bool) $lob_as_string;
+ return $this;
+ }
+
+ /**
+ * Return whether or not LOB are returned as string
+ *
+ * @return boolean
+ */
+ public function getLobAsString()
+ {
+ return $this->_lobAsString;
+ }
+
+ /**
+ * Prepares statement handle
+ *
+ * @param string $sql
+ * @return void
+ * @throws Zend_Db_Statement_Oracle_Exception
+ */
+ protected function _prepare($sql)
+ {
+ $connection = $this->_adapter->getConnection();
+ $this->_stmt = @oci_parse($connection, $sql);
+ if (!$this->_stmt) {
+ /**
+ * @see Zend_Db_Statement_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(oci_error($connection));
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ // default value
+ if ($type === NULL) {
+ $type = SQLT_CHR;
+ }
+
+ // default value
+ if ($length === NULL) {
+ $length = -1;
+ }
+
+ $retval = @oci_bind_by_name($this->_stmt, $parameter, $variable, $length, $type);
+ if ($retval === false) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt));
+ }
+
+ return true;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ oci_free_statement($this->_stmt);
+ $this->_stmt = false;
+ return true;
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ return oci_num_fields($this->_stmt);
+ }
+
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = oci_error($this->_stmt);
+
+ if (!$error) {
+ return false;
+ }
+
+ return $error['code'];
+ }
+
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ */
+ public function errorInfo()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = oci_error($this->_stmt);
+ if (!$error) {
+ return false;
+ }
+
+ if (isset($error['sqltext'])) {
+ return array(
+ $error['code'],
+ $error['message'],
+ $error['offset'],
+ $error['sqltext'],
+ );
+ } else {
+ return array(
+ $error['code'],
+ $error['message'],
+ );
+ }
+ }
+
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function _execute(array $params = null)
+ {
+ $connection = $this->_adapter->getConnection();
+
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($params !== null) {
+ if (!is_array($params)) {
+ $params = array($params);
+ }
+ $error = false;
+ foreach (array_keys($params) as $name) {
+ if (!$this->bindParam($name, $params[$name], null, -1)) {
+ $error = true;
+ break;
+ }
+ }
+ if ($error) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt));
+ }
+ }
+
+ $retval = @oci_execute($this->_stmt, $this->_adapter->_getExecuteMode());
+ if ($retval === false) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt));
+ }
+
+ $this->_keys = Array();
+ if ($field_num = oci_num_fields($this->_stmt)) {
+ for ($i = 1; $i <= $field_num; $i++) {
+ $name = oci_field_name($this->_stmt, $i);
+ $this->_keys[] = $name;
+ }
+ }
+
+ $this->_values = Array();
+ if ($this->_keys) {
+ $this->_values = array_fill(0, count($this->_keys), null);
+ }
+
+ return $retval;
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ $lob_as_string = $this->getLobAsString() ? OCI_RETURN_LOBS : 0;
+
+ switch ($style) {
+ case Zend_Db::FETCH_NUM:
+ $row = oci_fetch_array($this->_stmt, OCI_NUM | OCI_RETURN_NULLS | $lob_as_string);
+ break;
+ case Zend_Db::FETCH_ASSOC:
+ $row = oci_fetch_array($this->_stmt, OCI_ASSOC | OCI_RETURN_NULLS | $lob_as_string);
+ break;
+ case Zend_Db::FETCH_BOTH:
+ $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string);
+ break;
+ case Zend_Db::FETCH_OBJ:
+ $row = oci_fetch_object($this->_stmt);
+ break;
+ case Zend_Db::FETCH_BOUND:
+ $row = oci_fetch_array($this->_stmt, OCI_BOTH | OCI_RETURN_NULLS | $lob_as_string);
+ if ($row !== false) {
+ return $this->_fetchBound($row);
+ }
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(
+ array(
+ 'code' => 'HYC00',
+ 'message' => "Invalid fetch mode '$style' specified"
+ )
+ );
+ break;
+ }
+
+ if (! $row && $error = oci_error($this->_stmt)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception($error);
+ }
+
+ if (is_array($row) && array_key_exists('zend_db_rownum', $row)) {
+ unset($row['zend_db_rownum']);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchAll($style = null, $col = 0)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ // make sure we have a fetch mode
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+
+ $flags = OCI_FETCHSTATEMENT_BY_ROW;
+
+ switch ($style) {
+ case Zend_Db::FETCH_BOTH:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(
+ array(
+ 'code' => 'HYC00',
+ 'message' => "OCI8 driver does not support fetchAll(FETCH_BOTH), use fetch() in a loop instead"
+ )
+ );
+ // notreached
+ $flags |= OCI_NUM;
+ $flags |= OCI_ASSOC;
+ break;
+ case Zend_Db::FETCH_NUM:
+ $flags |= OCI_NUM;
+ break;
+ case Zend_Db::FETCH_ASSOC:
+ $flags |= OCI_ASSOC;
+ break;
+ case Zend_Db::FETCH_OBJ:
+ break;
+ case Zend_Db::FETCH_COLUMN:
+ $flags = $flags &~ OCI_FETCHSTATEMENT_BY_ROW;
+ $flags |= OCI_FETCHSTATEMENT_BY_COLUMN;
+ $flags |= OCI_NUM;
+ break;
+ default:
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(
+ array(
+ 'code' => 'HYC00',
+ 'message' => "Invalid fetch mode '$style' specified"
+ )
+ );
+ break;
+ }
+
+ $result = Array();
+ if ($flags != OCI_FETCHSTATEMENT_BY_ROW) { /* not Zend_Db::FETCH_OBJ */
+ if (! ($rows = oci_fetch_all($this->_stmt, $result, 0, -1, $flags) )) {
+ if ($error = oci_error($this->_stmt)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception($error);
+ }
+ if (!$rows) {
+ return array();
+ }
+ }
+ if ($style == Zend_Db::FETCH_COLUMN) {
+ $result = $result[$col];
+ }
+ foreach ($result as &$row) {
+ if (is_array($row) && array_key_exists('zend_db_rownum', $row)) {
+ unset($row['zend_db_rownum']);
+ }
+ }
+ } else {
+ while (($row = oci_fetch_object($this->_stmt)) !== false) {
+ $result [] = $row;
+ }
+ if ($error = oci_error($this->_stmt)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception($error);
+ }
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchColumn($col = 0)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (!oci_fetch($this->_stmt)) {
+ // if no error, there is simply no record
+ if (!$error = oci_error($this->_stmt)) {
+ return false;
+ }
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception($error);
+ }
+
+ $data = oci_result($this->_stmt, $col+1); //1-based
+ if ($data === false) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt));
+ }
+
+ if ($this->getLobAsString()) {
+ // instanceof doesn't allow '-', we must use a temporary string
+ $type = 'OCI-Lob';
+ if ($data instanceof $type) {
+ $data = $data->read($data->size());
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $obj = oci_fetch_object($this->_stmt);
+
+ if ($error = oci_error($this->_stmt)) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception($error);
+ }
+
+ /* @todo XXX handle parameters */
+
+ return $obj;
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function nextRowset()
+ {
+ /**
+ * @see Zend_Db_Statement_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(
+ array(
+ 'code' => 'HYC00',
+ 'message' => 'Optional feature not implemented'
+ )
+ );
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function rowCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $num_rows = oci_num_rows($this->_stmt);
+
+ if ($num_rows === false) {
+ /**
+ * @see Zend_Db_Adapter_Oracle_Exception
+ */
+ throw new Zend_Db_Statement_Oracle_Exception(oci_error($this->_stmt));
+ }
+
+ return $num_rows;
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Statement/Oracle/Exception.php b/library/vendor/Zend/Db/Statement/Oracle/Exception.php
new file mode 100644
index 0000000..feaa4cb
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Oracle/Exception.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Statement_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Db_Statement_Oracle_Exception extends Zend_Db_Statement_Exception
+{
+ protected $message = 'Unknown exception';
+ protected $code = 0;
+
+ function __construct($error = null, $code = 0)
+ {
+ if (is_array($error)) {
+ if (!isset($error['offset'])) {
+ $this->message = $error['code']." ".$error['message'];
+ } else {
+ $this->message = $error['code']." ".$error['message']." ";
+ $this->message .= substr($error['sqltext'], 0, $error['offset']);
+ $this->message .= "*";
+ $this->message .= substr($error['sqltext'], $error['offset']);
+ }
+ $this->code = $error['code'];
+ }
+ if (!$this->code && $code) {
+ $this->code = $code;
+ }
+ }
+}
+
diff --git a/library/vendor/Zend/Db/Statement/Pdo.php b/library/vendor/Zend/Db/Statement/Pdo.php
new file mode 100644
index 0000000..47f9031
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Pdo.php
@@ -0,0 +1,426 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement
+ */
+
+/**
+ * Proxy class to wrap a PDOStatement object.
+ * Matches the interface of PDOStatement. All methods simply proxy to the
+ * matching method in PDOStatement. PDOExceptions thrown by PDOStatement
+ * are re-thrown as Zend_Db_Statement_Exception.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Pdo extends Zend_Db_Statement implements IteratorAggregate
+{
+
+ /**
+ * @var int
+ */
+ protected $_fetchMode = PDO::FETCH_ASSOC;
+
+ /**
+ * Prepare a string SQL statement and create a statement object.
+ *
+ * @param string $sql
+ * @return void
+ * @throws Zend_Db_Statement_Exception
+ */
+ protected function _prepare($sql)
+ {
+ try {
+ $this->_stmt = $this->_adapter->getConnection()->prepare($sql);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Bind a column of the statement result set to a PHP variable.
+ *
+ * @param string $column Name the column in the result set, either by
+ * position or by name.
+ * @param mixed $param Reference to the PHP variable containing the value.
+ * @param mixed $type OPTIONAL
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function bindColumn($column, &$param, $type = null)
+ {
+ try {
+ if ($type === null) {
+ return $this->_stmt->bindColumn($column, $param);
+ } else {
+ return $this->_stmt->bindColumn($column, $param, $type);
+ }
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ try {
+ if ($type === null) {
+ if (is_bool($variable)) {
+ $type = PDO::PARAM_BOOL;
+ } elseif ($variable === null) {
+ $type = PDO::PARAM_NULL;
+ } elseif (is_integer($variable)) {
+ $type = PDO::PARAM_INT;
+ } else {
+ $type = PDO::PARAM_STR;
+ }
+ }
+ return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Binds a value to a parameter.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $value Scalar value to bind to the parameter.
+ * @param mixed $type OPTIONAL Datatype of the parameter.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function bindValue($parameter, $value, $type = null)
+ {
+ if (is_string($parameter) && $parameter[0] != ':') {
+ $parameter = ":$parameter";
+ }
+
+ $this->_bindParam[$parameter] = $value;
+
+ try {
+ if ($type === null) {
+ return $this->_stmt->bindValue($parameter, $value);
+ } else {
+ return $this->_stmt->bindValue($parameter, $value, $type);
+ }
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function closeCursor()
+ {
+ try {
+ return $this->_stmt->closeCursor();
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function columnCount()
+ {
+ try {
+ return $this->_stmt->columnCount();
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function errorCode()
+ {
+ try {
+ return $this->_stmt->errorCode();
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function errorInfo()
+ {
+ try {
+ return $this->_stmt->errorInfo();
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function _execute(array $params = null)
+ {
+ try {
+ if ($params !== null) {
+ return $this->_stmt->execute($params);
+ } else {
+ return $this->_stmt->execute();
+ }
+ } catch (PDOException $e) {
+ $message = sprintf('%s, query was: %s', $e->getMessage(), $this->_stmt->queryString);
+ throw new Zend_Db_Statement_Exception($message, (int) $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+ if ($cursor === null) {
+ $cursor = PDO::FETCH_ORI_NEXT;
+ }
+ if ($offset === null) {
+ $offset = 0;
+ }
+ try {
+ return $this->_stmt->fetch($style, $cursor, $offset);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Required by IteratorAggregate interface
+ *
+ * @return IteratorIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new IteratorIterator($this->_stmt);
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ if ($style === null) {
+ $style = $this->_fetchMode;
+ }
+ try {
+ if ($style == PDO::FETCH_COLUMN) {
+ if ($col === null) {
+ $col = 0;
+ }
+ return $this->_stmt->fetchAll($style, $col);
+ } else {
+ return $this->_stmt->fetchAll($style);
+ }
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchColumn($col = 0)
+ {
+ try {
+ return $this->_stmt->fetchColumn($col);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ try {
+ return $this->_stmt->fetchObject($class, $config);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieve a statement attribute.
+ *
+ * @param integer $key Attribute name.
+ * @return mixed Attribute value.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function getAttribute($key)
+ {
+ try {
+ return $this->_stmt->getAttribute($key);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns metadata for a column in a result set.
+ *
+ * @param int $column
+ * @return mixed
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function getColumnMeta($column)
+ {
+ try {
+ return $this->_stmt->getColumnMeta($column);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function nextRowset()
+ {
+ try {
+ return $this->_stmt->nextRowset();
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function rowCount()
+ {
+ try {
+ return $this->_stmt->rowCount();
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Set a statement attribute.
+ *
+ * @param string $key Attribute name.
+ * @param mixed $val Attribute value.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function setAttribute($key, $val)
+ {
+ try {
+ return $this->_stmt->setAttribute($key, $val);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Set the default fetch mode for this statement.
+ *
+ * @param int $mode The fetch mode.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function setFetchMode($mode)
+ {
+ $this->_fetchMode = $mode;
+ try {
+ return $this->_stmt->setFetchMode($mode);
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Statement/Pdo/Ibm.php b/library/vendor/Zend/Db/Statement/Pdo/Ibm.php
new file mode 100644
index 0000000..84f265e
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Pdo/Ibm.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement_Pdo
+ */
+
+/**
+ * Proxy class to wrap a PDOStatement object for IBM Databases.
+ * Matches the interface of PDOStatement. All methods simply proxy to the
+ * matching method in PDOStatement. PDOExceptions thrown by PDOStatement
+ * are re-thrown as Zend_Db_Statement_Exception.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Pdo_Ibm extends Zend_Db_Statement_Pdo
+{
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ try {
+ if (($type === null) && ($length === null) && ($options === null)) {
+ return $this->_stmt->bindParam($parameter, $variable);
+ } else {
+ return $this->_stmt->bindParam($parameter, $variable, $type, $length, $options);
+ }
+ } catch (PDOException $e) {
+ throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Statement/Pdo/Oci.php b/library/vendor/Zend/Db/Statement/Pdo/Oci.php
new file mode 100644
index 0000000..79334ad
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Pdo/Oci.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement_Pdo
+ */
+
+/**
+ * Proxy class to wrap a PDOStatement object for IBM Databases.
+ * Matches the interface of PDOStatement. All methods simply proxy to the
+ * matching method in PDOStatement. PDOExceptions thrown by PDOStatement
+ * are re-thrown as Zend_Db_Statement_Exception.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Pdo_Oci extends Zend_Db_Statement_Pdo
+{
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('zend_db_rownum');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ $row = parent::fetch($style, $cursor, $offset);
+
+ $remove = $this->_adapter->foldCase('zend_db_rownum');
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+
+ return $row;
+ }
+}
diff --git a/library/vendor/Zend/Db/Statement/Sqlsrv.php b/library/vendor/Zend/Db/Statement/Sqlsrv.php
new file mode 100644
index 0000000..84294e8
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Sqlsrv.php
@@ -0,0 +1,430 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement
+ */
+
+/**
+ * Extends for Microsoft SQL Server Driver for PHP
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Sqlsrv extends Zend_Db_Statement
+{
+
+ /**
+ * The connection_stmt object original string.
+ */
+ protected $_originalSQL;
+
+ /**
+ * Column names.
+ */
+ protected $_keys;
+
+ /**
+ * Query executed
+ */
+ protected $_executed = false;
+
+ /**
+ * Prepares statement handle
+ *
+ * @param string $sql
+ * @return void
+ * @throws Zend_Db_Statement_Sqlsrv_Exception
+ */
+ protected function _prepare($sql)
+ {
+ $connection = $this->_adapter->getConnection();
+
+ $this->_stmt = sqlsrv_prepare($connection, $sql);
+
+ if (!$this->_stmt) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors());
+ }
+
+ $this->_originalSQL = $sql;
+ }
+
+ /**
+ * Binds a parameter to the specified variable name.
+ *
+ * @param mixed $parameter Name the parameter, either integer or string.
+ * @param mixed $variable Reference to PHP variable containing the value.
+ * @param mixed $type OPTIONAL Datatype of SQL parameter.
+ * @param mixed $length OPTIONAL Length of SQL parameter.
+ * @param mixed $options OPTIONAL Other options.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ protected function _bindParam($parameter, &$variable, $type = null, $length = null, $options = null)
+ {
+ //Sql server doesn't support bind by name
+ return true;
+ }
+
+ /**
+ * Closes the cursor, allowing the statement to be executed again.
+ *
+ * @return bool
+ */
+ public function closeCursor()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ sqlsrv_free_stmt($this->_stmt);
+ $this->_stmt = false;
+ return true;
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Returns null if the statement has no result set metadata.
+ *
+ * @return int The number of columns.
+ */
+ public function columnCount()
+ {
+ if ($this->_stmt && $this->_executed) {
+ return sqlsrv_num_fields($this->_stmt);
+ }
+
+ return 0;
+ }
+
+
+ /**
+ * Retrieves the error code, if any, associated with the last operation on
+ * the statement handle.
+ *
+ * @return string error code.
+ */
+ public function errorCode()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = sqlsrv_errors();
+ if (!$error) {
+ return false;
+ }
+
+ return $error[0]['code'];
+ }
+
+
+ /**
+ * Retrieves an array of error information, if any, associated with the
+ * last operation on the statement handle.
+ *
+ * @return array
+ */
+ public function errorInfo()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $error = sqlsrv_errors();
+ if (!$error) {
+ return false;
+ }
+
+ return array(
+ $error[0]['code'],
+ $error[0]['message'],
+ );
+ }
+
+
+ /**
+ * Executes a prepared statement.
+ *
+ * @param array $params OPTIONAL Values to bind to parameter placeholders.
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function _execute(array $params = null)
+ {
+ $connection = $this->_adapter->getConnection();
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if ($params !== null) {
+ if (!is_array($params)) {
+ $params = array($params);
+ }
+ $error = false;
+
+ // make all params passed by reference
+ $params_ = array();
+ $temp = array();
+ $i = 1;
+ foreach ($params as $param) {
+ $temp[$i] = $param;
+ $params_[] = &$temp[$i];
+ $i++;
+ }
+ $params = $params_;
+ }
+
+ $this->_stmt = sqlsrv_query($connection, $this->_originalSQL, $params);
+
+ if (!$this->_stmt) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors());
+ }
+
+ $this->_executed = true;
+
+ return (!$this->_stmt);
+ }
+
+ /**
+ * Fetches a row from the result set.
+ *
+ * @param int $style OPTIONAL Fetch mode for this fetch operation.
+ * @param int $cursor OPTIONAL Absolute, relative, or other.
+ * @param int $offset OPTIONAL Number for absolute or relative cursors.
+ * @return mixed Array, object, or scalar depending on fetch mode.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetch($style = null, $cursor = null, $offset = null)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (null === $style) {
+ $style = $this->_fetchMode;
+ }
+
+ $values = sqlsrv_fetch_array($this->_stmt, SQLSRV_FETCH_ASSOC);
+
+ if (!$values && (null !== $error = sqlsrv_errors())) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception($error);
+ }
+
+ if (null === $values) {
+ return null;
+ }
+
+ if (!$this->_keys) {
+ foreach ($values as $key => $value) {
+ $this->_keys[] = $this->_adapter->foldCase($key);
+ }
+ }
+
+ $values = array_values($values);
+
+ $row = false;
+ switch ($style) {
+ case Zend_Db::FETCH_NUM:
+ $row = $values;
+ break;
+ case Zend_Db::FETCH_ASSOC:
+ $row = array_combine($this->_keys, $values);
+ break;
+ case Zend_Db::FETCH_BOTH:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ break;
+ case Zend_Db::FETCH_OBJ:
+ $row = (object) array_combine($this->_keys, $values);
+ break;
+ case Zend_Db::FETCH_BOUND:
+ $assoc = array_combine($this->_keys, $values);
+ $row = array_merge($values, $assoc);
+ $row = $this->_fetchBound($row);
+ break;
+ default:
+ throw new Zend_Db_Statement_Sqlsrv_Exception("Invalid fetch mode '$style' specified");
+ break;
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ *
+ * @param int $col OPTIONAL Position of the column to fetch.
+ * @return string
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchColumn($col = 0)
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (!sqlsrv_fetch($this->_stmt)) {
+ if (null !== $error = sqlsrv_errors()) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception($error);
+ }
+
+ // If no error, there is simply no record
+ return false;
+ }
+
+ $data = sqlsrv_get_field($this->_stmt, $col); //0-based
+ if ($data === false) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors());
+ }
+
+ return $data;
+ }
+
+ /**
+ * Fetches the next row and returns it as an object.
+ *
+ * @param string $class OPTIONAL Name of the class to create.
+ * @param array $config OPTIONAL Constructor arguments for the class.
+ * @return mixed One object instance of the specified class.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function fetchObject($class = 'stdClass', array $config = array())
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ $obj = sqlsrv_fetch_object($this->_stmt);
+
+ if ($error = sqlsrv_errors()) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception($error);
+ }
+
+ /* @todo XXX handle parameters */
+
+ if (null === $obj) {
+ return false;
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Returns metadata for a column in a result set.
+ *
+ * @param int $column
+ * @return mixed
+ * @throws Zend_Db_Statement_Sqlsrv_Exception
+ */
+ public function getColumnMeta($column)
+ {
+ $fields = sqlsrv_field_metadata($this->_stmt);
+
+ if (!$fields) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception('Column metadata can not be fetched');
+ }
+
+ if (!isset($fields[$column])) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception('Column index does not exist in statement');
+ }
+
+ return $fields[$column];
+ }
+
+ /**
+ * Retrieves the next rowset (result set) for a SQL statement that has
+ * multiple result sets. An example is a stored procedure that returns
+ * the results of multiple queries.
+ *
+ * @return bool
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function nextRowset()
+ {
+ if (sqlsrv_next_result($this->_stmt) === false) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors());
+ }
+
+ // reset column keys
+ $this->_keys = null;
+
+ return true;
+ }
+
+ /**
+ * Returns the number of rows affected by the execution of the
+ * last INSERT, DELETE, or UPDATE statement executed by this
+ * statement object.
+ *
+ * @return int The number of rows affected.
+ * @throws Zend_Db_Statement_Exception
+ */
+ public function rowCount()
+ {
+ if (!$this->_stmt) {
+ return false;
+ }
+
+ if (!$this->_executed) {
+ return 0;
+ }
+
+ $num_rows = sqlsrv_rows_affected($this->_stmt);
+
+ // Strict check is necessary; 0 is a valid return value
+ if ($num_rows === false) {
+ throw new Zend_Db_Statement_Sqlsrv_Exception(sqlsrv_errors());
+ }
+
+ return $num_rows;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param int $style OPTIONAL Fetch mode.
+ * @param int $col OPTIONAL Column number, if fetch mode is by column.
+ * @return array Collection of rows, each in a format by the fetch mode.
+ *
+ * Behaves like parent, but if limit()
+ * is used, the final result removes the extra column
+ * 'zend_db_rownum'
+ */
+ public function fetchAll($style = null, $col = null)
+ {
+ $data = parent::fetchAll($style, $col);
+ $results = array();
+ $remove = $this->_adapter->foldCase('ZEND_DB_ROWNUM');
+
+ foreach ($data as $row) {
+ if (is_array($row) && array_key_exists($remove, $row)) {
+ unset($row[$remove]);
+ }
+ $results[] = $row;
+ }
+ return $results;
+ }
+}
diff --git a/library/vendor/Zend/Db/Statement/Sqlsrv/Exception.php b/library/vendor/Zend/Db/Statement/Sqlsrv/Exception.php
new file mode 100644
index 0000000..3358f3b
--- /dev/null
+++ b/library/vendor/Zend/Db/Statement/Sqlsrv/Exception.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Statement_Exception
+ */
+
+/**
+ * @package Zend_Db
+ * @subpackage Statement
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Statement_Sqlsrv_Exception extends Zend_Db_Statement_Exception
+{
+ /**
+ * Constructor
+ *
+ * If $message is an array, the assumption is that the return value of
+ * sqlsrv_errors() was provided. If so, it then retrieves the most recent
+ * error from that stack, and sets the message and code based on it.
+ *
+ * @param null|array|string $message
+ * @param null|int $code
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ if (is_array($message)) {
+ // Error should be array of errors
+ // We only need first one (?)
+ if (isset($message[0])) {
+ $message = $message[0];
+ }
+
+ $code = (int) $message['code'];
+ $message = (string) $message['message'];
+ }
+ parent::__construct($message, $code);
+ }
+}
+
diff --git a/library/vendor/Zend/Db/Table.php b/library/vendor/Zend/Db/Table.php
new file mode 100644
index 0000000..0dbfe8c
--- /dev/null
+++ b/library/vendor/Zend/Db/Table.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Table_Abstract
+ */
+
+/**
+ * @see Zend_Db_Table_Definition
+ */
+
+/**
+ * Class for SQL table interface.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table extends Zend_Db_Table_Abstract
+{
+
+ /**
+ * __construct() - For concrete implementation of Zend_Db_Table
+ *
+ * @param string|array $config string can reference a Zend_Registry key for a db adapter
+ * OR it can reference the name of a table
+ * @param array|Zend_Db_Table_Definition $definition
+ */
+ public function __construct($config = array(), $definition = null)
+ {
+ if ($definition !== null && is_array($definition)) {
+ $definition = new Zend_Db_Table_Definition($definition);
+ }
+
+ if (is_string($config)) {
+ if (Zend_Registry::isRegistered($config)) {
+ trigger_error(__CLASS__ . '::' . __METHOD__ . '(\'registryName\') is not valid usage of Zend_Db_Table, '
+ . 'try extending Zend_Db_Table_Abstract in your extending classes.',
+ E_USER_NOTICE
+ );
+ $config = array(self::ADAPTER => $config);
+ } else {
+ // process this as table with or without a definition
+ if ($definition instanceof Zend_Db_Table_Definition
+ && $definition->hasTableConfig($config)) {
+ // this will have DEFINITION_CONFIG_NAME & DEFINITION
+ $config = $definition->getTableConfig($config);
+ } else {
+ $config = array(self::NAME => $config);
+ }
+ }
+ }
+
+ parent::__construct($config);
+ }
+}
diff --git a/library/vendor/Zend/Db/Table/Abstract.php b/library/vendor/Zend/Db/Table/Abstract.php
new file mode 100644
index 0000000..ea393f2
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Abstract.php
@@ -0,0 +1,1599 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db_Adapter_Abstract
+ */
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * Class for SQL table interface.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Db_Table_Abstract
+{
+
+ const ADAPTER = 'db';
+ const DEFINITION = 'definition';
+ const DEFINITION_CONFIG_NAME = 'definitionConfigName';
+ const SCHEMA = 'schema';
+ const NAME = 'name';
+ const PRIMARY = 'primary';
+ const COLS = 'cols';
+ const METADATA = 'metadata';
+ const METADATA_CACHE = 'metadataCache';
+ const METADATA_CACHE_IN_CLASS = 'metadataCacheInClass';
+ const ROW_CLASS = 'rowClass';
+ const ROWSET_CLASS = 'rowsetClass';
+ const REFERENCE_MAP = 'referenceMap';
+ const DEPENDENT_TABLES = 'dependentTables';
+ const SEQUENCE = 'sequence';
+
+ const COLUMNS = 'columns';
+ const REF_TABLE_CLASS = 'refTableClass';
+ const REF_COLUMNS = 'refColumns';
+ const ON_DELETE = 'onDelete';
+ const ON_UPDATE = 'onUpdate';
+
+ const CASCADE = 'cascade';
+ const CASCADE_RECURSE = 'cascadeRecurse';
+ const RESTRICT = 'restrict';
+ const SET_NULL = 'setNull';
+
+ const DEFAULT_NONE = 'defaultNone';
+ const DEFAULT_CLASS = 'defaultClass';
+ const DEFAULT_DB = 'defaultDb';
+
+ const SELECT_WITH_FROM_PART = true;
+ const SELECT_WITHOUT_FROM_PART = false;
+
+ /**
+ * Default Zend_Db_Adapter_Abstract object.
+ *
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected static $_defaultDb;
+
+ /**
+ * Optional Zend_Db_Table_Definition object
+ *
+ * @var unknown_type
+ */
+ protected $_definition = null;
+
+ /**
+ * Optional definition config name used in concrete implementation
+ *
+ * @var string
+ */
+ protected $_definitionConfigName = null;
+
+ /**
+ * Default cache for information provided by the adapter's describeTable() method.
+ *
+ * @var Zend_Cache_Core
+ */
+ protected static $_defaultMetadataCache = null;
+
+ /**
+ * Zend_Db_Adapter_Abstract object.
+ *
+ * @var Zend_Db_Adapter_Abstract
+ */
+ protected $_db;
+
+ /**
+ * The schema name (default null means current schema)
+ *
+ * @var array
+ */
+ protected $_schema = null;
+
+ /**
+ * The table name.
+ *
+ * @var string
+ */
+ protected $_name = null;
+
+ /**
+ * The table column names derived from Zend_Db_Adapter_Abstract::describeTable().
+ *
+ * @var array
+ */
+ protected $_cols;
+
+ /**
+ * The primary key column or columns.
+ * A compound key should be declared as an array.
+ * You may declare a single-column primary key
+ * as a string.
+ *
+ * @var mixed
+ */
+ protected $_primary = null;
+
+ /**
+ * If your primary key is a compound key, and one of the columns uses
+ * an auto-increment or sequence-generated value, set _identity
+ * to the ordinal index in the $_primary array for that column.
+ * Note this index is the position of the column in the primary key,
+ * not the position of the column in the table. The primary key
+ * array is 1-based.
+ *
+ * @var integer
+ */
+ protected $_identity = 1;
+
+ /**
+ * Define the logic for new values in the primary key.
+ * May be a string, boolean true, or boolean false.
+ *
+ * @var mixed
+ */
+ protected $_sequence = true;
+
+ /**
+ * Information provided by the adapter's describeTable() method.
+ *
+ * @var array
+ */
+ protected $_metadata = array();
+
+ /**
+ * Cache for information provided by the adapter's describeTable() method.
+ *
+ * @var Zend_Cache_Core
+ */
+ protected $_metadataCache = null;
+
+ /**
+ * Flag: whether or not to cache metadata in the class
+ * @var bool
+ */
+ protected $_metadataCacheInClass = true;
+
+ /**
+ * Classname for row
+ *
+ * @var string
+ */
+ protected $_rowClass = 'Zend_Db_Table_Row';
+
+ /**
+ * Classname for rowset
+ *
+ * @var string
+ */
+ protected $_rowsetClass = 'Zend_Db_Table_Rowset';
+
+ /**
+ * Associative array map of declarative referential integrity rules.
+ * This array has one entry per foreign key in the current table.
+ * Each key is a mnemonic name for one reference rule.
+ *
+ * Each value is also an associative array, with the following keys:
+ * - columns = array of names of column(s) in the child table.
+ * - refTableClass = class name of the parent table.
+ * - refColumns = array of names of column(s) in the parent table,
+ * in the same order as those in the 'columns' entry.
+ * - onDelete = "cascade" means that a delete in the parent table also
+ * causes a delete of referencing rows in the child table.
+ * - onUpdate = "cascade" means that an update of primary key values in
+ * the parent table also causes an update of referencing
+ * rows in the child table.
+ *
+ * @var array
+ */
+ protected $_referenceMap = array();
+
+ /**
+ * Simple array of class names of tables that are "children" of the current
+ * table, in other words tables that contain a foreign key to this one.
+ * Array elements are not table names; they are class names of classes that
+ * extend Zend_Db_Table_Abstract.
+ *
+ * @var array
+ */
+ protected $_dependentTables = array();
+
+
+ protected $_defaultSource = self::DEFAULT_NONE;
+ protected $_defaultValues = array();
+
+ /**
+ * Constructor.
+ *
+ * Supported params for $config are:
+ * - db = user-supplied instance of database connector,
+ * or key name of registry instance.
+ * - name = table name.
+ * - primary = string or array of primary key(s).
+ * - rowClass = row class name.
+ * - rowsetClass = rowset class name.
+ * - referenceMap = array structure to declare relationship
+ * to parent tables.
+ * - dependentTables = array of child tables.
+ * - metadataCache = cache for information from adapter describeTable().
+ *
+ * @param mixed $config Array of user-specified config options, or just the Db Adapter.
+ * @return void
+ */
+ public function __construct($config = array())
+ {
+ /**
+ * Allow a scalar argument to be the Adapter object or Registry key.
+ */
+ if (!is_array($config)) {
+ $config = array(self::ADAPTER => $config);
+ }
+
+ if ($config) {
+ $this->setOptions($config);
+ }
+
+ $this->_setup();
+ $this->init();
+ }
+
+ /**
+ * setOptions()
+ *
+ * @param array $options
+ * @return Zend_Db_Table_Abstract
+ */
+ public function setOptions(Array $options)
+ {
+ foreach ($options as $key => $value) {
+ switch ($key) {
+ case self::ADAPTER:
+ $this->_setAdapter($value);
+ break;
+ case self::DEFINITION:
+ $this->setDefinition($value);
+ break;
+ case self::DEFINITION_CONFIG_NAME:
+ $this->setDefinitionConfigName($value);
+ break;
+ case self::SCHEMA:
+ $this->_schema = (string) $value;
+ break;
+ case self::NAME:
+ $this->_name = (string) $value;
+ break;
+ case self::PRIMARY:
+ $this->_primary = (array) $value;
+ break;
+ case self::ROW_CLASS:
+ $this->setRowClass($value);
+ break;
+ case self::ROWSET_CLASS:
+ $this->setRowsetClass($value);
+ break;
+ case self::REFERENCE_MAP:
+ $this->setReferences($value);
+ break;
+ case self::DEPENDENT_TABLES:
+ $this->setDependentTables($value);
+ break;
+ case self::METADATA_CACHE:
+ $this->_setMetadataCache($value);
+ break;
+ case self::METADATA_CACHE_IN_CLASS:
+ $this->setMetadataCacheInClass($value);
+ break;
+ case self::SEQUENCE:
+ $this->_setSequence($value);
+ break;
+ default:
+ // ignore unrecognized configuration directive
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * setDefinition()
+ *
+ * @param Zend_Db_Table_Definition $definition
+ * @return Zend_Db_Table_Abstract
+ */
+ public function setDefinition(Zend_Db_Table_Definition $definition)
+ {
+ $this->_definition = $definition;
+ return $this;
+ }
+
+ /**
+ * getDefinition()
+ *
+ * @return Zend_Db_Table_Definition|null
+ */
+ public function getDefinition()
+ {
+ return $this->_definition;
+ }
+
+ /**
+ * setDefinitionConfigName()
+ *
+ * @param string $definition
+ * @return Zend_Db_Table_Abstract
+ */
+ public function setDefinitionConfigName($definitionConfigName)
+ {
+ $this->_definitionConfigName = $definitionConfigName;
+ return $this;
+ }
+
+ /**
+ * getDefinitionConfigName()
+ *
+ * @return string
+ */
+ public function getDefinitionConfigName()
+ {
+ return $this->_definitionConfigName;
+ }
+
+ /**
+ * @param string $classname
+ * @return Zend_Db_Table_Abstract Provides a fluent interface
+ */
+ public function setRowClass($classname)
+ {
+ $this->_rowClass = (string) $classname;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRowClass()
+ {
+ return $this->_rowClass;
+ }
+
+ /**
+ * @param string $classname
+ * @return Zend_Db_Table_Abstract Provides a fluent interface
+ */
+ public function setRowsetClass($classname)
+ {
+ $this->_rowsetClass = (string) $classname;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRowsetClass()
+ {
+ return $this->_rowsetClass;
+ }
+
+ /**
+ * Add a reference to the reference map
+ *
+ * @param string $ruleKey
+ * @param string|array $columns
+ * @param string $refTableClass
+ * @param string|array $refColumns
+ * @param string $onDelete
+ * @param string $onUpdate
+ * @return Zend_Db_Table_Abstract
+ */
+ public function addReference($ruleKey, $columns, $refTableClass, $refColumns,
+ $onDelete = null, $onUpdate = null)
+ {
+ $reference = array(self::COLUMNS => (array) $columns,
+ self::REF_TABLE_CLASS => $refTableClass,
+ self::REF_COLUMNS => (array) $refColumns);
+
+ if (!empty($onDelete)) {
+ $reference[self::ON_DELETE] = $onDelete;
+ }
+
+ if (!empty($onUpdate)) {
+ $reference[self::ON_UPDATE] = $onUpdate;
+ }
+
+ $this->_referenceMap[$ruleKey] = $reference;
+
+ return $this;
+ }
+
+ /**
+ * @param array $referenceMap
+ * @return Zend_Db_Table_Abstract Provides a fluent interface
+ */
+ public function setReferences(array $referenceMap)
+ {
+ $this->_referenceMap = $referenceMap;
+
+ return $this;
+ }
+
+ /**
+ * @param string $tableClassname
+ * @param string $ruleKey OPTIONAL
+ * @return array
+ * @throws Zend_Db_Table_Exception
+ */
+ public function getReference($tableClassname, $ruleKey = null)
+ {
+ $thisClass = get_class($this);
+ if ($thisClass === 'Zend_Db_Table') {
+ $thisClass = $this->_definitionConfigName;
+ }
+ $refMap = $this->_getReferenceMapNormalized();
+ if ($ruleKey !== null) {
+ if (!isset($refMap[$ruleKey])) {
+ throw new Zend_Db_Table_Exception("No reference rule \"$ruleKey\" from table $thisClass to table $tableClassname");
+ }
+ if ($refMap[$ruleKey][self::REF_TABLE_CLASS] != $tableClassname) {
+ throw new Zend_Db_Table_Exception("Reference rule \"$ruleKey\" does not reference table $tableClassname");
+ }
+ return $refMap[$ruleKey];
+ }
+ foreach ($refMap as $reference) {
+ if ($reference[self::REF_TABLE_CLASS] == $tableClassname) {
+ return $reference;
+ }
+ }
+ throw new Zend_Db_Table_Exception("No reference from table $thisClass to table $tableClassname");
+ }
+
+ /**
+ * @param array $dependentTables
+ * @return Zend_Db_Table_Abstract Provides a fluent interface
+ */
+ public function setDependentTables(array $dependentTables)
+ {
+ $this->_dependentTables = $dependentTables;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getDependentTables()
+ {
+ return $this->_dependentTables;
+ }
+
+ /**
+ * set the defaultSource property - this tells the table class where to find default values
+ *
+ * @param string $defaultSource
+ * @return Zend_Db_Table_Abstract
+ */
+ public function setDefaultSource($defaultSource = self::DEFAULT_NONE)
+ {
+ if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) {
+ $defaultSource = self::DEFAULT_NONE;
+ }
+
+ $this->_defaultSource = $defaultSource;
+ return $this;
+ }
+
+ /**
+ * returns the default source flag that determines where defaultSources come from
+ *
+ * @return unknown
+ */
+ public function getDefaultSource()
+ {
+ return $this->_defaultSource;
+ }
+
+ /**
+ * set the default values for the table class
+ *
+ * @param array $defaultValues
+ * @return Zend_Db_Table_Abstract
+ */
+ public function setDefaultValues(Array $defaultValues)
+ {
+ foreach ($defaultValues as $defaultName => $defaultValue) {
+ if (array_key_exists($defaultName, $this->_metadata)) {
+ $this->_defaultValues[$defaultName] = $defaultValue;
+ }
+ }
+ return $this;
+ }
+
+ public function getDefaultValues()
+ {
+ return $this->_defaultValues;
+ }
+
+
+ /**
+ * Sets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects.
+ *
+ * @param mixed $db Either an Adapter object, or a string naming a Registry key
+ * @return void
+ */
+ public static function setDefaultAdapter($db = null)
+ {
+ self::$_defaultDb = self::_setupAdapter($db);
+ }
+
+ /**
+ * Gets the default Zend_Db_Adapter_Abstract for all Zend_Db_Table objects.
+ *
+ * @return Zend_Db_Adapter_Abstract or null
+ */
+ public static function getDefaultAdapter()
+ {
+ return self::$_defaultDb;
+ }
+
+ /**
+ * @param mixed $db Either an Adapter object, or a string naming a Registry key
+ * @return Zend_Db_Table_Abstract Provides a fluent interface
+ */
+ protected function _setAdapter($db)
+ {
+ $this->_db = self::_setupAdapter($db);
+ return $this;
+ }
+
+ /**
+ * Gets the Zend_Db_Adapter_Abstract for this particular Zend_Db_Table object.
+ *
+ * @return Zend_Db_Adapter_Abstract
+ */
+ public function getAdapter()
+ {
+ return $this->_db;
+ }
+
+ /**
+ * @param mixed $db Either an Adapter object, or a string naming a Registry key
+ * @return Zend_Db_Adapter_Abstract
+ * @throws Zend_Db_Table_Exception
+ */
+ protected static function _setupAdapter($db)
+ {
+ if ($db === null) {
+ return null;
+ }
+ if (is_string($db)) {
+ $db = Zend_Registry::get($db);
+ }
+ if (!$db instanceof Zend_Db_Adapter_Abstract) {
+ throw new Zend_Db_Table_Exception('Argument must be of type Zend_Db_Adapter_Abstract, or a Registry key where a Zend_Db_Adapter_Abstract object is stored');
+ }
+ return $db;
+ }
+
+ /**
+ * Sets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
+ *
+ * If $defaultMetadataCache is null, then no metadata cache is used by default.
+ *
+ * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
+ * @return void
+ */
+ public static function setDefaultMetadataCache($metadataCache = null)
+ {
+ self::$_defaultMetadataCache = self::_setupMetadataCache($metadataCache);
+ }
+
+ /**
+ * Gets the default metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
+ *
+ * @return Zend_Cache_Core or null
+ */
+ public static function getDefaultMetadataCache()
+ {
+ return self::$_defaultMetadataCache;
+ }
+
+ /**
+ * Sets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
+ *
+ * If $metadataCache is null, then no metadata cache is used. Since there is no opportunity to reload metadata
+ * after instantiation, this method need not be public, particularly because that it would have no effect
+ * results in unnecessary API complexity. To configure the metadata cache, use the metadataCache configuration
+ * option for the class constructor upon instantiation.
+ *
+ * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
+ * @return Zend_Db_Table_Abstract Provides a fluent interface
+ */
+ protected function _setMetadataCache($metadataCache)
+ {
+ $this->_metadataCache = self::_setupMetadataCache($metadataCache);
+ return $this;
+ }
+
+ /**
+ * Gets the metadata cache for information returned by Zend_Db_Adapter_Abstract::describeTable().
+ *
+ * @return Zend_Cache_Core or null
+ */
+ public function getMetadataCache()
+ {
+ return $this->_metadataCache;
+ }
+
+ /**
+ * Indicate whether metadata should be cached in the class for the duration
+ * of the instance
+ *
+ * @param bool $flag
+ * @return Zend_Db_Table_Abstract
+ */
+ public function setMetadataCacheInClass($flag)
+ {
+ $this->_metadataCacheInClass = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Retrieve flag indicating if metadata should be cached for duration of
+ * instance
+ *
+ * @return bool
+ */
+ public function metadataCacheInClass()
+ {
+ return $this->_metadataCacheInClass;
+ }
+
+ /**
+ * @param mixed $metadataCache Either a Cache object, or a string naming a Registry key
+ * @return Zend_Cache_Core
+ * @throws Zend_Db_Table_Exception
+ */
+ protected static function _setupMetadataCache($metadataCache)
+ {
+ if ($metadataCache === null) {
+ return null;
+ }
+ if (is_string($metadataCache)) {
+ $metadataCache = Zend_Registry::get($metadataCache);
+ }
+ if (!$metadataCache instanceof Zend_Cache_Core) {
+ throw new Zend_Db_Table_Exception('Argument must be of type Zend_Cache_Core, or a Registry key where a Zend_Cache_Core object is stored');
+ }
+ return $metadataCache;
+ }
+
+ /**
+ * Sets the sequence member, which defines the behavior for generating
+ * primary key values in new rows.
+ * - If this is a string, then the string names the sequence object.
+ * - If this is boolean true, then the key uses an auto-incrementing
+ * or identity mechanism.
+ * - If this is boolean false, then the key is user-defined.
+ * Use this for natural keys, for example.
+ *
+ * @param mixed $sequence
+ * @return Zend_Db_Table_Adapter_Abstract Provides a fluent interface
+ */
+ protected function _setSequence($sequence)
+ {
+ $this->_sequence = $sequence;
+
+ return $this;
+ }
+
+ /**
+ * Turnkey for initialization of a table object.
+ * Calls other protected methods for individual tasks, to make it easier
+ * for a subclass to override part of the setup logic.
+ *
+ * @return void
+ */
+ protected function _setup()
+ {
+ $this->_setupDatabaseAdapter();
+ $this->_setupTableName();
+ }
+
+ /**
+ * Initialize database adapter.
+ *
+ * @return void
+ * @throws Zend_Db_Table_Exception
+ */
+ protected function _setupDatabaseAdapter()
+ {
+ if (! $this->_db) {
+ $this->_db = self::getDefaultAdapter();
+ if (!$this->_db instanceof Zend_Db_Adapter_Abstract) {
+ throw new Zend_Db_Table_Exception('No adapter found for ' . get_class($this));
+ }
+ }
+ }
+
+ /**
+ * Initialize table and schema names.
+ *
+ * If the table name is not set in the class definition,
+ * use the class name itself as the table name.
+ *
+ * A schema name provided with the table name (e.g., "schema.table") overrides
+ * any existing value for $this->_schema.
+ *
+ * @return void
+ */
+ protected function _setupTableName()
+ {
+ if (! $this->_name) {
+ $this->_name = get_class($this);
+ } else if (strpos($this->_name, '.')) {
+ list($this->_schema, $this->_name) = explode('.', $this->_name);
+ }
+ }
+
+ /**
+ * Initializes metadata.
+ *
+ * If metadata cannot be loaded from cache, adapter's describeTable() method is called to discover metadata
+ * information. Returns true if and only if the metadata are loaded from cache.
+ *
+ * @return boolean
+ * @throws Zend_Db_Table_Exception
+ */
+ protected function _setupMetadata()
+ {
+ if ($this->metadataCacheInClass() && (count($this->_metadata) > 0)) {
+ return true;
+ }
+
+ // Assume that metadata will be loaded from cache
+ $isMetadataFromCache = true;
+
+ // If $this has no metadata cache but the class has a default metadata cache
+ if (null === $this->_metadataCache && null !== self::$_defaultMetadataCache) {
+ // Make $this use the default metadata cache of the class
+ $this->_setMetadataCache(self::$_defaultMetadataCache);
+ }
+
+ // If $this has a metadata cache
+ if (null !== $this->_metadataCache) {
+ // Define the cache identifier where the metadata are saved
+
+ //get db configuration
+ $dbConfig = $this->_db->getConfig();
+
+ $port = isset($dbConfig['options']['port'])
+ ? ':'.$dbConfig['options']['port']
+ : (isset($dbConfig['port'])
+ ? ':'.$dbConfig['port']
+ : null);
+
+ $host = isset($dbConfig['options']['host'])
+ ? ':'.$dbConfig['options']['host']
+ : (isset($dbConfig['host'])
+ ? ':'.$dbConfig['host']
+ : null);
+
+ // Define the cache identifier where the metadata are saved
+ $cacheId = md5( // port:host/dbname:schema.table (based on availabilty)
+ $port . $host . '/'. $dbConfig['dbname'] . ':'
+ . $this->_schema. '.' . $this->_name
+ );
+ }
+
+ // If $this has no metadata cache or metadata cache misses
+ if (null === $this->_metadataCache || !($metadata = $this->_metadataCache->load($cacheId))) {
+ // Metadata are not loaded from cache
+ $isMetadataFromCache = false;
+ // Fetch metadata from the adapter's describeTable() method
+ $metadata = $this->_db->describeTable($this->_name, $this->_schema);
+ // If $this has a metadata cache, then cache the metadata
+ if (null !== $this->_metadataCache && !$this->_metadataCache->save($metadata, $cacheId)) {
+ trigger_error('Failed saving metadata to metadataCache', E_USER_NOTICE);
+ }
+ }
+
+ // Assign the metadata to $this
+ $this->_metadata = $metadata;
+
+ // Return whether the metadata were loaded from cache
+ return $isMetadataFromCache;
+ }
+
+ /**
+ * Retrieve table columns
+ *
+ * @return array
+ */
+ protected function _getCols()
+ {
+ if (null === $this->_cols) {
+ $this->_setupMetadata();
+ $this->_cols = array_keys($this->_metadata);
+ }
+ return $this->_cols;
+ }
+
+ /**
+ * Initialize primary key from metadata.
+ * If $_primary is not defined, discover primary keys
+ * from the information returned by describeTable().
+ *
+ * @return void
+ * @throws Zend_Db_Table_Exception
+ */
+ protected function _setupPrimaryKey()
+ {
+ if (!$this->_primary) {
+ $this->_setupMetadata();
+ $this->_primary = array();
+ foreach ($this->_metadata as $col) {
+ if ($col['PRIMARY']) {
+ $this->_primary[ $col['PRIMARY_POSITION'] ] = $col['COLUMN_NAME'];
+ if ($col['IDENTITY']) {
+ $this->_identity = $col['PRIMARY_POSITION'];
+ }
+ }
+ }
+ // if no primary key was specified and none was found in the metadata
+ // then throw an exception.
+ if (empty($this->_primary)) {
+ throw new Zend_Db_Table_Exception("A table must have a primary key, but none was found for table '{$this->_name}'");
+ }
+ } else if (!is_array($this->_primary)) {
+ $this->_primary = array(1 => $this->_primary);
+ } else if (isset($this->_primary[0])) {
+ array_unshift($this->_primary, null);
+ unset($this->_primary[0]);
+ }
+
+ $cols = $this->_getCols();
+ if (! array_intersect((array) $this->_primary, $cols) == (array) $this->_primary) {
+ throw new Zend_Db_Table_Exception("Primary key column(s) ("
+ . implode(',', (array) $this->_primary)
+ . ") are not columns in this table ("
+ . implode(',', $cols)
+ . ")");
+ }
+
+ $primary = (array) $this->_primary;
+ $pkIdentity = $primary[(int) $this->_identity];
+
+ /**
+ * Special case for PostgreSQL: a SERIAL key implicitly uses a sequence
+ * object whose name is "<table>_<column>_seq".
+ */
+ if ($this->_sequence === true && $this->_db instanceof Zend_Db_Adapter_Pdo_Pgsql) {
+ $this->_sequence = $this->_db->quoteIdentifier("{$this->_name}_{$pkIdentity}_seq");
+ if ($this->_schema) {
+ $this->_sequence = $this->_db->quoteIdentifier($this->_schema) . '.' . $this->_sequence;
+ }
+ }
+ }
+
+ /**
+ * Returns a normalized version of the reference map
+ *
+ * @return array
+ */
+ protected function _getReferenceMapNormalized()
+ {
+ $referenceMapNormalized = array();
+
+ foreach ($this->_referenceMap as $rule => $map) {
+
+ $referenceMapNormalized[$rule] = array();
+
+ foreach ($map as $key => $value) {
+ switch ($key) {
+
+ // normalize COLUMNS and REF_COLUMNS to arrays
+ case self::COLUMNS:
+ case self::REF_COLUMNS:
+ if (!is_array($value)) {
+ $referenceMapNormalized[$rule][$key] = array($value);
+ } else {
+ $referenceMapNormalized[$rule][$key] = $value;
+ }
+ break;
+
+ // other values are copied as-is
+ default:
+ $referenceMapNormalized[$rule][$key] = $value;
+ break;
+ }
+ }
+ }
+
+ return $referenceMapNormalized;
+ }
+
+ /**
+ * Initialize object
+ *
+ * Called from {@link __construct()} as final step of object instantiation.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Returns table information.
+ *
+ * You can elect to return only a part of this information by supplying its key name,
+ * otherwise all information is returned as an array.
+ *
+ * @param string $key The specific info part to return OPTIONAL
+ * @return mixed
+ * @throws Zend_Db_Table_Exception
+ */
+ public function info($key = null)
+ {
+ $this->_setupPrimaryKey();
+
+ $info = array(
+ self::SCHEMA => $this->_schema,
+ self::NAME => $this->_name,
+ self::COLS => $this->_getCols(),
+ self::PRIMARY => (array) $this->_primary,
+ self::METADATA => $this->_metadata,
+ self::ROW_CLASS => $this->getRowClass(),
+ self::ROWSET_CLASS => $this->getRowsetClass(),
+ self::REFERENCE_MAP => $this->_referenceMap,
+ self::DEPENDENT_TABLES => $this->_dependentTables,
+ self::SEQUENCE => $this->_sequence
+ );
+
+ if ($key === null) {
+ return $info;
+ }
+
+ if (!array_key_exists($key, $info)) {
+ throw new Zend_Db_Table_Exception('There is no table information for the key "' . $key . '"');
+ }
+
+ return $info[$key];
+ }
+
+ /**
+ * Returns an instance of a Zend_Db_Table_Select object.
+ *
+ * @param bool $withFromPart Whether or not to include the from part of the select based on the table
+ * @return Zend_Db_Table_Select
+ */
+ public function select($withFromPart = self::SELECT_WITHOUT_FROM_PART)
+ {
+ $select = new Zend_Db_Table_Select($this);
+ if ($withFromPart == self::SELECT_WITH_FROM_PART) {
+ $select->from($this->info(self::NAME), Zend_Db_Table_Select::SQL_WILDCARD, $this->info(self::SCHEMA));
+ }
+ return $select;
+ }
+
+ /**
+ * Inserts a new row.
+ *
+ * @param array $data Column-value pairs.
+ * @return mixed The primary key of the row inserted.
+ */
+ public function insert(array $data)
+ {
+ $this->_setupPrimaryKey();
+
+ /**
+ * Zend_Db_Table assumes that if you have a compound primary key
+ * and one of the columns in the key uses a sequence,
+ * it's the _first_ column in the compound key.
+ */
+ $primary = (array) $this->_primary;
+ $pkIdentity = $primary[(int)$this->_identity];
+
+
+ /**
+ * If the primary key can be generated automatically, and no value was
+ * specified in the user-supplied data, then omit it from the tuple.
+ *
+ * Note: this checks for sensible values in the supplied primary key
+ * position of the data. The following values are considered empty:
+ * null, false, true, '', array()
+ */
+ if (array_key_exists($pkIdentity, $data)) {
+ if ($data[$pkIdentity] === null // null
+ || $data[$pkIdentity] === '' // empty string
+ || is_bool($data[$pkIdentity]) // boolean
+ || (is_array($data[$pkIdentity]) && empty($data[$pkIdentity]))) { // empty array
+ unset($data[$pkIdentity]);
+ }
+ }
+
+ /**
+ * If this table uses a database sequence object and the data does not
+ * specify a value, then get the next ID from the sequence and add it
+ * to the row. We assume that only the first column in a compound
+ * primary key takes a value from a sequence.
+ */
+ if (is_string($this->_sequence) && !isset($data[$pkIdentity])) {
+ $data[$pkIdentity] = $this->_db->nextSequenceId($this->_sequence);
+ }
+
+ /**
+ * INSERT the new row.
+ */
+ $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
+ $this->_db->insert($tableSpec, $data);
+
+ /**
+ * Fetch the most recent ID generated by an auto-increment
+ * or IDENTITY column, unless the user has specified a value,
+ * overriding the auto-increment mechanism.
+ */
+ if ($this->_sequence === true && !isset($data[$pkIdentity])) {
+ $data[$pkIdentity] = $this->_db->lastInsertId();
+ }
+
+ /**
+ * Return the primary key value if the PK is a single column,
+ * else return an associative array of the PK column/value pairs.
+ */
+ $pkData = array_intersect_key($data, array_flip($primary));
+ if (count($primary) == 1) {
+ reset($pkData);
+ return current($pkData);
+ }
+
+ return $pkData;
+ }
+
+ /**
+ * Check if the provided column is an identity of the table
+ *
+ * @param string $column
+ * @throws Zend_Db_Table_Exception
+ * @return boolean
+ */
+ public function isIdentity($column)
+ {
+ $this->_setupPrimaryKey();
+
+ if (!isset($this->_metadata[$column])) {
+ /**
+ * @see Zend_Db_Table_Exception
+ */
+
+ throw new Zend_Db_Table_Exception('Column "' . $column . '" not found in table.');
+ }
+
+ return (bool) $this->_metadata[$column]['IDENTITY'];
+ }
+
+ /**
+ * Updates existing rows.
+ *
+ * @param array $data Column-value pairs.
+ * @param array|string $where An SQL WHERE clause, or an array of SQL WHERE clauses.
+ * @return int The number of rows updated.
+ */
+ public function update(array $data, $where)
+ {
+ $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
+ return $this->_db->update($tableSpec, $data, $where);
+ }
+
+ /**
+ * Called by a row object for the parent table's class during save() method.
+ *
+ * @param string $parentTableClassname
+ * @param array $oldPrimaryKey
+ * @param array $newPrimaryKey
+ * @return int
+ */
+ public function _cascadeUpdate($parentTableClassname, array $oldPrimaryKey, array $newPrimaryKey)
+ {
+ $this->_setupMetadata();
+ $rowsAffected = 0;
+ foreach ($this->_getReferenceMapNormalized() as $map) {
+ if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_UPDATE])) {
+ switch ($map[self::ON_UPDATE]) {
+ case self::CASCADE:
+ $newRefs = array();
+ $where = array();
+ for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
+ $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
+ $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
+ if (array_key_exists($refCol, $newPrimaryKey)) {
+ $newRefs[$col] = $newPrimaryKey[$refCol];
+ }
+ $type = $this->_metadata[$col]['DATA_TYPE'];
+ $where[] = $this->_db->quoteInto(
+ $this->_db->quoteIdentifier($col, true) . ' = ?',
+ $oldPrimaryKey[$refCol], $type);
+ }
+ $rowsAffected += $this->update($newRefs, $where);
+ break;
+ default:
+ // no action
+ break;
+ }
+ }
+ }
+ return $rowsAffected;
+ }
+
+ /**
+ * Deletes existing rows.
+ *
+ * @param array|string $where SQL WHERE clause(s).
+ * @return int The number of rows deleted.
+ */
+ public function delete($where)
+ {
+ $depTables = $this->getDependentTables();
+ if (!empty($depTables)) {
+ $resultSet = $this->fetchAll($where);
+ if (count($resultSet) > 0 ) {
+ foreach ($resultSet as $row) {
+ /**
+ * Execute cascading deletes against dependent tables
+ */
+ foreach ($depTables as $tableClass) {
+ $t = self::getTableFromString($tableClass, $this);
+ $t->_cascadeDelete(
+ get_class($this), $row->getPrimaryKey()
+ );
+ }
+ }
+ }
+ }
+
+ $tableSpec = ($this->_schema ? $this->_schema . '.' : '') . $this->_name;
+ return $this->_db->delete($tableSpec, $where);
+ }
+
+ /**
+ * Called by parent table's class during delete() method.
+ *
+ * @param string $parentTableClassname
+ * @param array $primaryKey
+ * @return int Number of affected rows
+ */
+ public function _cascadeDelete($parentTableClassname, array $primaryKey)
+ {
+ // setup metadata
+ $this->_setupMetadata();
+
+ // get this class name
+ $thisClass = get_class($this);
+ if ($thisClass === 'Zend_Db_Table') {
+ $thisClass = $this->_definitionConfigName;
+ }
+
+ $rowsAffected = 0;
+
+ foreach ($this->_getReferenceMapNormalized() as $map) {
+ if ($map[self::REF_TABLE_CLASS] == $parentTableClassname && isset($map[self::ON_DELETE])) {
+
+ $where = array();
+
+ // CASCADE or CASCADE_RECURSE
+ if (in_array($map[self::ON_DELETE], array(self::CASCADE, self::CASCADE_RECURSE))) {
+ for ($i = 0; $i < count($map[self::COLUMNS]); ++$i) {
+ $col = $this->_db->foldCase($map[self::COLUMNS][$i]);
+ $refCol = $this->_db->foldCase($map[self::REF_COLUMNS][$i]);
+ $type = $this->_metadata[$col]['DATA_TYPE'];
+ $where[] = $this->_db->quoteInto(
+ $this->_db->quoteIdentifier($col, true) . ' = ?',
+ $primaryKey[$refCol], $type);
+ }
+ }
+
+ // CASCADE_RECURSE
+ if ($map[self::ON_DELETE] == self::CASCADE_RECURSE) {
+
+ /**
+ * Execute cascading deletes against dependent tables
+ */
+ $depTables = $this->getDependentTables();
+ if (!empty($depTables)) {
+ foreach ($depTables as $tableClass) {
+ $t = self::getTableFromString($tableClass, $this);
+ foreach ($this->fetchAll($where) as $depRow) {
+ $rowsAffected += $t->_cascadeDelete($thisClass, $depRow->getPrimaryKey());
+ }
+ }
+ }
+ }
+
+ // CASCADE or CASCADE_RECURSE
+ if (in_array($map[self::ON_DELETE], array(self::CASCADE, self::CASCADE_RECURSE))) {
+ $rowsAffected += $this->delete($where);
+ }
+
+ }
+ }
+ return $rowsAffected;
+ }
+
+ /**
+ * Fetches rows by primary key. The argument specifies one or more primary
+ * key value(s). To find multiple rows by primary key, the argument must
+ * be an array.
+ *
+ * This method accepts a variable number of arguments. If the table has a
+ * multi-column primary key, the number of arguments must be the same as
+ * the number of columns in the primary key. To find multiple rows in a
+ * table with a multi-column primary key, each argument must be an array
+ * with the same number of elements.
+ *
+ * The find() method always returns a Rowset object, even if only one row
+ * was found.
+ *
+ * @param mixed $key The value(s) of the primary keys.
+ * @return Zend_Db_Table_Rowset_Abstract Row(s) matching the criteria.
+ * @throws Zend_Db_Table_Exception
+ */
+ public function find()
+ {
+ $this->_setupPrimaryKey();
+ $args = func_get_args();
+ $keyNames = array_values((array) $this->_primary);
+
+ if (count($args) < count($keyNames)) {
+ throw new Zend_Db_Table_Exception("Too few columns for the primary key");
+ }
+
+ if (count($args) > count($keyNames)) {
+ throw new Zend_Db_Table_Exception("Too many columns for the primary key");
+ }
+
+ $whereList = array();
+ $numberTerms = 0;
+ foreach ($args as $keyPosition => $keyValues) {
+ $keyValuesCount = count($keyValues);
+ // Coerce the values to an array.
+ // Don't simply typecast to array, because the values
+ // might be Zend_Db_Expr objects.
+ if (!is_array($keyValues)) {
+ $keyValues = array($keyValues);
+ }
+ if ($numberTerms == 0) {
+ $numberTerms = $keyValuesCount;
+ } else if ($keyValuesCount != $numberTerms) {
+ throw new Zend_Db_Table_Exception("Missing value(s) for the primary key");
+ }
+ $keyValues = array_values($keyValues);
+ for ($i = 0; $i < $keyValuesCount; ++$i) {
+ if (!isset($whereList[$i])) {
+ $whereList[$i] = array();
+ }
+ $whereList[$i][$keyPosition] = $keyValues[$i];
+ }
+ }
+
+ $whereClause = null;
+ if (count($whereList)) {
+ $whereOrTerms = array();
+ $tableName = $this->_db->quoteTableAs($this->_name, null, true);
+ foreach ($whereList as $keyValueSets) {
+ $whereAndTerms = array();
+ foreach ($keyValueSets as $keyPosition => $keyValue) {
+ $type = $this->_metadata[$keyNames[$keyPosition]]['DATA_TYPE'];
+ $columnName = $this->_db->quoteIdentifier($keyNames[$keyPosition], true);
+ $whereAndTerms[] = $this->_db->quoteInto(
+ $tableName . '.' . $columnName . ' = ?',
+ $keyValue, $type);
+ }
+ $whereOrTerms[] = '(' . implode(' AND ', $whereAndTerms) . ')';
+ }
+ $whereClause = '(' . implode(' OR ', $whereOrTerms) . ')';
+ }
+
+ // issue ZF-5775 (empty where clause should return empty rowset)
+ if ($whereClause == null) {
+ $rowsetClass = $this->getRowsetClass();
+ if (!class_exists($rowsetClass)) {
+ Zend_Loader::loadClass($rowsetClass);
+ }
+ return new $rowsetClass(array('table' => $this, 'rowClass' => $this->getRowClass(), 'stored' => true));
+ }
+
+ return $this->fetchAll($whereClause);
+ }
+
+ /**
+ * Fetches all rows.
+ *
+ * Honors the Zend_Db_Adapter fetch mode.
+ *
+ * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
+ * @param string|array $order OPTIONAL An SQL ORDER clause.
+ * @param int $count OPTIONAL An SQL LIMIT count.
+ * @param int $offset OPTIONAL An SQL LIMIT offset.
+ * @return Zend_Db_Table_Rowset_Abstract The row results per the Zend_Db_Adapter fetch mode.
+ */
+ public function fetchAll($where = null, $order = null, $count = null, $offset = null)
+ {
+ if (!($where instanceof Zend_Db_Table_Select)) {
+ $select = $this->select();
+
+ if ($where !== null) {
+ $this->_where($select, $where);
+ }
+
+ if ($order !== null) {
+ $this->_order($select, $order);
+ }
+
+ if ($count !== null || $offset !== null) {
+ $select->limit($count, $offset);
+ }
+
+ } else {
+ $select = $where;
+ }
+
+ $rows = $this->_fetch($select);
+
+ $data = array(
+ 'table' => $this,
+ 'data' => $rows,
+ 'readOnly' => $select->isReadOnly(),
+ 'rowClass' => $this->getRowClass(),
+ 'stored' => true
+ );
+
+ $rowsetClass = $this->getRowsetClass();
+ if (!class_exists($rowsetClass)) {
+ Zend_Loader::loadClass($rowsetClass);
+ }
+ return new $rowsetClass($data);
+ }
+
+ /**
+ * Fetches one row in an object of type Zend_Db_Table_Row_Abstract,
+ * or returns null if no row matches the specified criteria.
+ *
+ * @param string|array|Zend_Db_Table_Select $where OPTIONAL An SQL WHERE clause or Zend_Db_Table_Select object.
+ * @param string|array $order OPTIONAL An SQL ORDER clause.
+ * @param int $offset OPTIONAL An SQL OFFSET value.
+ * @return Zend_Db_Table_Row_Abstract|null The row results per the
+ * Zend_Db_Adapter fetch mode, or null if no row found.
+ */
+ public function fetchRow($where = null, $order = null, $offset = null)
+ {
+ if (!($where instanceof Zend_Db_Table_Select)) {
+ $select = $this->select();
+
+ if ($where !== null) {
+ $this->_where($select, $where);
+ }
+
+ if ($order !== null) {
+ $this->_order($select, $order);
+ }
+
+ $select->limit(1, ((is_numeric($offset)) ? (int) $offset : null));
+
+ } else {
+ $select = $where->limit(1, $where->getPart(Zend_Db_Select::LIMIT_OFFSET));
+ }
+
+ $rows = $this->_fetch($select);
+
+ if (count($rows) == 0) {
+ return null;
+ }
+
+ $data = array(
+ 'table' => $this,
+ 'data' => $rows[0],
+ 'readOnly' => $select->isReadOnly(),
+ 'stored' => true
+ );
+
+ $rowClass = $this->getRowClass();
+ if (!class_exists($rowClass)) {
+ Zend_Loader::loadClass($rowClass);
+ }
+ return new $rowClass($data);
+ }
+
+ /**
+ * Fetches a new blank row (not from the database).
+ *
+ * @return Zend_Db_Table_Row_Abstract
+ * @deprecated since 0.9.3 - use createRow() instead.
+ */
+ public function fetchNew()
+ {
+ return $this->createRow();
+ }
+
+ /**
+ * Fetches a new blank row (not from the database).
+ *
+ * @param array $data OPTIONAL data to populate in the new row.
+ * @param string $defaultSource OPTIONAL flag to force default values into new row
+ * @return Zend_Db_Table_Row_Abstract
+ */
+ public function createRow(array $data = array(), $defaultSource = null)
+ {
+ $cols = $this->_getCols();
+ $defaults = array_combine($cols, array_fill(0, count($cols), null));
+
+ // nothing provided at call-time, take the class value
+ if ($defaultSource == null) {
+ $defaultSource = $this->_defaultSource;
+ }
+
+ if (!in_array($defaultSource, array(self::DEFAULT_CLASS, self::DEFAULT_DB, self::DEFAULT_NONE))) {
+ $defaultSource = self::DEFAULT_NONE;
+ }
+
+ if ($defaultSource == self::DEFAULT_DB) {
+ foreach ($this->_metadata as $metadataName => $metadata) {
+ if (($metadata['DEFAULT'] != null) &&
+ ($metadata['NULLABLE'] !== true || ($metadata['NULLABLE'] === true && isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === true)) &&
+ (!(isset($this->_defaultValues[$metadataName]) && $this->_defaultValues[$metadataName] === false))) {
+ $defaults[$metadataName] = $metadata['DEFAULT'];
+ }
+ }
+ } elseif ($defaultSource == self::DEFAULT_CLASS && $this->_defaultValues) {
+ foreach ($this->_defaultValues as $defaultName => $defaultValue) {
+ if (array_key_exists($defaultName, $defaults)) {
+ $defaults[$defaultName] = $defaultValue;
+ }
+ }
+ }
+
+ $config = array(
+ 'table' => $this,
+ 'data' => $defaults,
+ 'readOnly' => false,
+ 'stored' => false
+ );
+
+ $rowClass = $this->getRowClass();
+ if (!class_exists($rowClass)) {
+ Zend_Loader::loadClass($rowClass);
+ }
+ $row = new $rowClass($config);
+ $row->setFromArray($data);
+ return $row;
+ }
+
+ /**
+ * Generate WHERE clause from user-supplied string or array
+ *
+ * @param string|array $where OPTIONAL An SQL WHERE clause.
+ * @return Zend_Db_Table_Select
+ */
+ protected function _where(Zend_Db_Table_Select $select, $where)
+ {
+ $where = (array) $where;
+
+ foreach ($where as $key => $val) {
+ // is $key an int?
+ if (is_int($key)) {
+ // $val is the full condition
+ $select->where($val);
+ } else {
+ // $key is the condition with placeholder,
+ // and $val is quoted into the condition
+ $select->where($key, $val);
+ }
+ }
+
+ return $select;
+ }
+
+ /**
+ * Generate ORDER clause from user-supplied string or array
+ *
+ * @param string|array $order OPTIONAL An SQL ORDER clause.
+ * @return Zend_Db_Table_Select
+ */
+ protected function _order(Zend_Db_Table_Select $select, $order)
+ {
+ if (!is_array($order)) {
+ $order = array($order);
+ }
+
+ foreach ($order as $val) {
+ $select->order($val);
+ }
+
+ return $select;
+ }
+
+ /**
+ * Support method for fetching rows.
+ *
+ * @param Zend_Db_Table_Select $select query options.
+ * @return array An array containing the row results in FETCH_ASSOC mode.
+ */
+ protected function _fetch(Zend_Db_Table_Select $select)
+ {
+ $stmt = $this->_db->query($select);
+ $data = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
+ return $data;
+ }
+
+ /**
+ * Get table gateway object from string
+ *
+ * @param string $tableName
+ * @param Zend_Db_Table_Abstract $referenceTable
+ * @throws Zend_Db_Table_Row_Exception
+ * @return Zend_Db_Table_Abstract
+ */
+ public static function getTableFromString($tableName, Zend_Db_Table_Abstract $referenceTable = null)
+ {
+ if ($referenceTable instanceof Zend_Db_Table_Abstract) {
+ $tableDefinition = $referenceTable->getDefinition();
+
+ if ($tableDefinition !== null && $tableDefinition->hasTableConfig($tableName)) {
+ return new Zend_Db_Table($tableName, $tableDefinition);
+ }
+ }
+
+ // assume the tableName is the class name
+ if (!class_exists($tableName)) {
+ try {
+ Zend_Loader::loadClass($tableName);
+ } catch (Zend_Exception $e) {
+ throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+
+ $options = array();
+
+ if ($referenceTable instanceof Zend_Db_Table_Abstract) {
+ $options['db'] = $referenceTable->getAdapter();
+ }
+
+ if (isset($tableDefinition) && $tableDefinition !== null) {
+ $options[Zend_Db_Table_Abstract::DEFINITION] = $tableDefinition;
+ }
+
+ return new $tableName($options);
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Table/Definition.php b/library/vendor/Zend/Db/Table/Definition.php
new file mode 100644
index 0000000..76265ae
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Definition.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Class for SQL table interface.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Definition
+{
+
+ /**
+ * @var array
+ */
+ protected $_tableConfigs = array();
+
+ /**
+ * __construct()
+ *
+ * @param array|Zend_Config $options
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ } elseif (is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * setConfig()
+ *
+ * @param Zend_Config $config
+ * @return Zend_Db_Table_Definition
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ $this->setOptions($config->toArray());
+ return $this;
+ }
+
+ /**
+ * setOptions()
+ *
+ * @param array $options
+ * @return Zend_Db_Table_Definition
+ */
+ public function setOptions(Array $options)
+ {
+ foreach ($options as $optionName => $optionValue) {
+ $this->setTableConfig($optionName, $optionValue);
+ }
+ return $this;
+ }
+
+ /**
+ * @param string $tableName
+ * @param array $tableConfig
+ * @return Zend_Db_Table_Definition
+ */
+ public function setTableConfig($tableName, array $tableConfig)
+ {
+ // @todo logic here
+ $tableConfig[Zend_Db_Table::DEFINITION_CONFIG_NAME] = $tableName;
+ $tableConfig[Zend_Db_Table::DEFINITION] = $this;
+
+ if (!isset($tableConfig[Zend_Db_Table::NAME])) {
+ $tableConfig[Zend_Db_Table::NAME] = $tableName;
+ }
+
+ $this->_tableConfigs[$tableName] = $tableConfig;
+ return $this;
+ }
+
+ /**
+ * getTableConfig()
+ *
+ * @param string $tableName
+ * @return array
+ */
+ public function getTableConfig($tableName)
+ {
+ return $this->_tableConfigs[$tableName];
+ }
+
+ /**
+ * removeTableConfig()
+ *
+ * @param string $tableName
+ */
+ public function removeTableConfig($tableName)
+ {
+ unset($this->_tableConfigs[$tableName]);
+ }
+
+ /**
+ * hasTableConfig()
+ *
+ * @param string $tableName
+ * @return bool
+ */
+ public function hasTableConfig($tableName)
+ {
+ return (isset($this->_tableConfigs[$tableName]));
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Table/Exception.php b/library/vendor/Zend/Db/Table/Exception.php
new file mode 100644
index 0000000..e1599a0
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Exception extends Zend_Db_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Db/Table/Row.php b/library/vendor/Zend/Db/Table/Row.php
new file mode 100644
index 0000000..adf00d4
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Row.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Table_Row_Abstract
+ */
+
+
+/**
+ * Reference concrete class that extends Zend_Db_Table_Row_Abstract.
+ * Developers may also create their own classes that extend the abstract class.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Row extends Zend_Db_Table_Row_Abstract
+{
+}
diff --git a/library/vendor/Zend/Db/Table/Row/Abstract.php b/library/vendor/Zend/Db/Table/Row/Abstract.php
new file mode 100644
index 0000000..e9c60b0
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Row/Abstract.php
@@ -0,0 +1,1160 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Db_Table_Row_Abstract implements ArrayAccess, IteratorAggregate
+{
+
+ /**
+ * The data for each column in the row (column_name => value).
+ * The keys must match the physical names of columns in the
+ * table for which this row is defined.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * This is set to a copy of $_data when the data is fetched from
+ * a database, specified as a new tuple in the constructor, or
+ * when dirty data is posted to the database with save().
+ *
+ * @var array
+ */
+ protected $_cleanData = array();
+
+ /**
+ * Tracks columns where data has been updated. Allows more specific insert and
+ * update operations.
+ *
+ * @var array
+ */
+ protected $_modifiedFields = array();
+
+ /**
+ * Zend_Db_Table_Abstract parent class or instance.
+ *
+ * @var Zend_Db_Table_Abstract
+ */
+ protected $_table = null;
+
+ /**
+ * Connected is true if we have a reference to a live
+ * Zend_Db_Table_Abstract object.
+ * This is false after the Rowset has been deserialized.
+ *
+ * @var boolean
+ */
+ protected $_connected = true;
+
+ /**
+ * A row is marked read only if it contains columns that are not physically represented within
+ * the database schema (e.g. evaluated columns/Zend_Db_Expr columns). This can also be passed
+ * as a run-time config options as a means of protecting row data.
+ *
+ * @var boolean
+ */
+ protected $_readOnly = false;
+
+ /**
+ * Name of the class of the Zend_Db_Table_Abstract object.
+ *
+ * @var string
+ */
+ protected $_tableClass = null;
+
+ /**
+ * Primary row key(s).
+ *
+ * @var array
+ */
+ protected $_primary;
+
+ /**
+ * Constructor.
+ *
+ * Supported params for $config are:-
+ * - table = class name or object of type Zend_Db_Table_Abstract
+ * - data = values of columns in this row.
+ *
+ * @param array $config OPTIONAL Array of user-specified config options.
+ * @return void
+ * @throws Zend_Db_Table_Row_Exception
+ */
+ public function __construct(array $config = array())
+ {
+ if (isset($config['table']) && $config['table'] instanceof Zend_Db_Table_Abstract) {
+ $this->_table = $config['table'];
+ $this->_tableClass = get_class($this->_table);
+ } elseif ($this->_tableClass !== null) {
+ $this->_table = $this->_getTableFromString($this->_tableClass);
+ }
+
+ if (isset($config['data'])) {
+ if (!is_array($config['data'])) {
+ throw new Zend_Db_Table_Row_Exception('Data must be an array');
+ }
+ $this->_data = $config['data'];
+ }
+ if (isset($config['stored']) && $config['stored'] === true) {
+ $this->_cleanData = $this->_data;
+ }
+
+ if (isset($config['readOnly']) && $config['readOnly'] === true) {
+ $this->setReadOnly(true);
+ }
+
+ // Retrieve primary keys from table schema
+ if (($table = $this->_getTable())) {
+ $info = $table->info();
+ $this->_primary = (array) $info['primary'];
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Transform a column name from the user-specified form
+ * to the physical form used in the database.
+ * You can override this method in a custom Row class
+ * to implement column name mappings, for example inflection.
+ *
+ * @param string $columnName Column name given.
+ * @return string The column name after transformation applied (none by default).
+ * @throws Zend_Db_Table_Row_Exception if the $columnName is not a string.
+ */
+ protected function _transformColumn($columnName)
+ {
+ if (!is_string($columnName)) {
+ throw new Zend_Db_Table_Row_Exception('Specified column is not a string');
+ }
+ // Perform no transformation by default
+ return $columnName;
+ }
+
+ /**
+ * Retrieve row field value
+ *
+ * @param string $columnName The user-specified column name.
+ * @return string The corresponding column value.
+ * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row.
+ */
+ public function __get($columnName)
+ {
+ $columnName = $this->_transformColumn($columnName);
+ if (!array_key_exists($columnName, $this->_data)) {
+ throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
+ }
+ return $this->_data[$columnName];
+ }
+
+ /**
+ * Set row field value
+ *
+ * @param string $columnName The column key.
+ * @param mixed $value The value for the property.
+ * @return void
+ * @throws Zend_Db_Table_Row_Exception
+ */
+ public function __set($columnName, $value)
+ {
+ $columnName = $this->_transformColumn($columnName);
+ if (!array_key_exists($columnName, $this->_data)) {
+ throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
+ }
+ $this->_data[$columnName] = $value;
+ $this->_modifiedFields[$columnName] = true;
+ }
+
+ /**
+ * Unset row field value
+ *
+ * @param string $columnName The column key.
+ * @return Zend_Db_Table_Row_Abstract
+ * @throws Zend_Db_Table_Row_Exception
+ */
+ public function __unset($columnName)
+ {
+ $columnName = $this->_transformColumn($columnName);
+ if (!array_key_exists($columnName, $this->_data)) {
+ throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
+ }
+ if ($this->isConnected() && in_array($columnName, $this->_table->info('primary'))) {
+ throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is a primary key and should not be unset");
+ }
+ unset($this->_data[$columnName]);
+ return $this;
+ }
+
+ /**
+ * Test existence of row field
+ *
+ * @param string $columnName The column key.
+ * @return boolean
+ */
+ public function __isset($columnName)
+ {
+ $columnName = $this->_transformColumn($columnName);
+ return array_key_exists($columnName, $this->_data);
+ }
+
+ /**
+ * Store table, primary key and data in serialized object
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly' ,'_modifiedFields');
+ }
+
+ /**
+ * Setup to do on wakeup.
+ * A de-serialized Row should not be assumed to have access to a live
+ * database connection, so set _connected = false.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ $this->_connected = false;
+ }
+
+ /**
+ * Proxy to __isset
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return $this->__isset($offset);
+ }
+
+ /**
+ * Proxy to __get
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ * @return string
+ */
+ public function offsetGet($offset)
+ {
+ return $this->__get($offset);
+ }
+
+ /**
+ * Proxy to __set
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ $this->__set($offset, $value);
+ }
+
+ /**
+ * Proxy to __unset
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ */
+ public function offsetUnset($offset)
+ {
+ return $this->__unset($offset);
+ }
+
+ /**
+ * Initialize object
+ *
+ * Called from {@link __construct()} as final step of object instantiation.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Returns the table object, or null if this is disconnected row
+ *
+ * @return Zend_Db_Table_Abstract|null
+ */
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ /**
+ * Set the table object, to re-establish a live connection
+ * to the database for a Row that has been de-serialized.
+ *
+ * @param Zend_Db_Table_Abstract $table
+ * @return boolean
+ * @throws Zend_Db_Table_Row_Exception
+ */
+ public function setTable(Zend_Db_Table_Abstract $table = null)
+ {
+ if ($table == null) {
+ $this->_table = null;
+ $this->_connected = false;
+ return false;
+ }
+
+ $tableClass = get_class($table);
+ if (! $table instanceof $this->_tableClass) {
+ throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass");
+ }
+
+ $this->_table = $table;
+ $this->_tableClass = $tableClass;
+
+ $info = $this->_table->info();
+
+ if ($info['cols'] != array_keys($this->_data)) {
+ throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row');
+ }
+
+ if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) {
+
+ throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row");
+ }
+
+ $this->_connected = true;
+ return true;
+ }
+
+ /**
+ * Query the class name of the Table object for which this
+ * Row was created.
+ *
+ * @return string
+ */
+ public function getTableClass()
+ {
+ return $this->_tableClass;
+ }
+
+ /**
+ * Test the connected status of the row.
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return $this->_connected;
+ }
+
+ /**
+ * Test the read-only status of the row.
+ *
+ * @return boolean
+ */
+ public function isReadOnly()
+ {
+ return $this->_readOnly;
+ }
+
+ /**
+ * Set the read-only status of the row.
+ *
+ * @param boolean $flag
+ * @return boolean
+ */
+ public function setReadOnly($flag)
+ {
+ $this->_readOnly = (bool) $flag;
+ }
+
+ /**
+ * Returns an instance of the parent table's Zend_Db_Table_Select object.
+ *
+ * @return Zend_Db_Table_Select
+ */
+ public function select()
+ {
+ return $this->getTable()->select();
+ }
+
+ /**
+ * Saves the properties to the database.
+ *
+ * This performs an intelligent insert/update, and reloads the
+ * properties with fresh data from the table on success.
+ *
+ * @return mixed The primary key value(s), as an associative array if the
+ * key is compound, or a scalar if the key is single-column.
+ */
+ public function save()
+ {
+ /**
+ * If the _cleanData array is empty,
+ * this is an INSERT of a new row.
+ * Otherwise it is an UPDATE.
+ */
+ if (empty($this->_cleanData)) {
+ return $this->_doInsert();
+ } else {
+ return $this->_doUpdate();
+ }
+ }
+
+ /**
+ * @return mixed The primary key value(s), as an associative array if the
+ * key is compound, or a scalar if the key is single-column.
+ */
+ protected function _doInsert()
+ {
+ /**
+ * A read-only row cannot be saved.
+ */
+ if ($this->_readOnly === true) {
+ throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
+ }
+
+ /**
+ * Run pre-INSERT logic
+ */
+ $this->_insert();
+
+ /**
+ * Execute the INSERT (this may throw an exception)
+ */
+ $data = array_intersect_key($this->_data, $this->_modifiedFields);
+ $primaryKey = $this->_getTable()->insert($data);
+
+ /**
+ * Normalize the result to an array indexed by primary key column(s).
+ * The table insert() method may return a scalar.
+ */
+ if (is_array($primaryKey)) {
+ $newPrimaryKey = $primaryKey;
+ } else {
+ //ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails.
+ $tempPrimaryKey = (array) $this->_primary;
+ $newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey);
+ }
+
+ /**
+ * Save the new primary key value in _data. The primary key may have
+ * been generated by a sequence or auto-increment mechanism, and this
+ * merge should be done before the _postInsert() method is run, so the
+ * new values are available for logging, etc.
+ */
+ $this->_data = array_merge($this->_data, $newPrimaryKey);
+
+ /**
+ * Run post-INSERT logic
+ */
+ $this->_postInsert();
+
+ /**
+ * Update the _cleanData to reflect that the data has been inserted.
+ */
+ $this->_refresh();
+
+ return $primaryKey;
+ }
+
+ /**
+ * @return mixed The primary key value(s), as an associative array if the
+ * key is compound, or a scalar if the key is single-column.
+ */
+ protected function _doUpdate()
+ {
+ /**
+ * A read-only row cannot be saved.
+ */
+ if ($this->_readOnly === true) {
+ throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
+ }
+
+ /**
+ * Get expressions for a WHERE clause
+ * based on the primary key value(s).
+ */
+ $where = $this->_getWhereQuery(false);
+
+ /**
+ * Run pre-UPDATE logic
+ */
+ $this->_update();
+
+ /**
+ * Compare the data to the modified fields array to discover
+ * which columns have been changed.
+ */
+ $diffData = array_intersect_key($this->_data, $this->_modifiedFields);
+
+ /**
+ * Were any of the changed columns part of the primary key?
+ */
+ $pkDiffData = array_intersect_key($diffData, array_flip((array)$this->_primary));
+
+ /**
+ * Execute cascading updates against dependent tables.
+ * Do this only if primary key value(s) were changed.
+ */
+ if (count($pkDiffData) > 0) {
+ $depTables = $this->_getTable()->getDependentTables();
+ if (!empty($depTables)) {
+ $pkNew = $this->_getPrimaryKey(true);
+ $pkOld = $this->_getPrimaryKey(false);
+ foreach ($depTables as $tableClass) {
+ $t = $this->_getTableFromString($tableClass);
+ $t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
+ }
+ }
+ }
+
+ /**
+ * Execute the UPDATE (this may throw an exception)
+ * Do this only if data values were changed.
+ * Use the $diffData variable, so the UPDATE statement
+ * includes SET terms only for data values that changed.
+ */
+ if (count($diffData) > 0) {
+ $this->_getTable()->update($diffData, $where);
+ }
+
+ /**
+ * Run post-UPDATE logic. Do this before the _refresh()
+ * so the _postUpdate() function can tell the difference
+ * between changed data and clean (pre-changed) data.
+ */
+ $this->_postUpdate();
+
+ /**
+ * Refresh the data just in case triggers in the RDBMS changed
+ * any columns. Also this resets the _cleanData.
+ */
+ $this->_refresh();
+
+ /**
+ * Return the primary key value(s) as an array
+ * if the key is compound or a scalar if the key
+ * is a scalar.
+ */
+ $primaryKey = $this->_getPrimaryKey(true);
+ if (count($primaryKey) == 1) {
+ return current($primaryKey);
+ }
+
+ return $primaryKey;
+ }
+
+ /**
+ * Deletes existing rows.
+ *
+ * @return int The number of rows deleted.
+ */
+ public function delete()
+ {
+ /**
+ * A read-only row cannot be deleted.
+ */
+ if ($this->_readOnly === true) {
+ throw new Zend_Db_Table_Row_Exception('This row has been marked read-only');
+ }
+
+ $where = $this->_getWhereQuery();
+
+ /**
+ * Execute pre-DELETE logic
+ */
+ $this->_delete();
+
+ /**
+ * Execute cascading deletes against dependent tables
+ */
+ $depTables = $this->_getTable()->getDependentTables();
+ if (!empty($depTables)) {
+ $pk = $this->_getPrimaryKey();
+ foreach ($depTables as $tableClass) {
+ $t = $this->_getTableFromString($tableClass);
+ $t->_cascadeDelete($this->getTableClass(), $pk);
+ }
+ }
+
+ /**
+ * Execute the DELETE (this may throw an exception)
+ */
+ $result = $this->_getTable()->delete($where);
+
+ /**
+ * Execute post-DELETE logic
+ */
+ $this->_postDelete();
+
+ /**
+ * Reset all fields to null to indicate that the row is not there
+ */
+ $this->_data = array_combine(
+ array_keys($this->_data),
+ array_fill(0, count($this->_data), null)
+ );
+
+ return $result;
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator((array) $this->_data);
+ }
+
+ /**
+ * Returns the column/value data as an array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return (array)$this->_data;
+ }
+
+ /**
+ * Sets all data in the row from an array.
+ *
+ * @param array $data
+ * @return Zend_Db_Table_Row_Abstract Provides a fluent interface
+ */
+ public function setFromArray(array $data)
+ {
+ $data = array_intersect_key($data, $this->_data);
+
+ foreach ($data as $columnName => $value) {
+ $this->__set($columnName, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Refreshes properties from the database.
+ *
+ * @return void
+ */
+ public function refresh()
+ {
+ return $this->_refresh();
+ }
+
+ /**
+ * Retrieves an instance of the parent table.
+ *
+ * @return Zend_Db_Table_Abstract
+ */
+ protected function _getTable()
+ {
+ if (!$this->_connected) {
+ throw new Zend_Db_Table_Row_Exception('Cannot save a Row unless it is connected');
+ }
+ return $this->_table;
+ }
+
+ /**
+ * Retrieves an associative array of primary keys.
+ *
+ * @param bool $useDirty
+ * @return array
+ */
+ protected function _getPrimaryKey($useDirty = true)
+ {
+ if (!is_array($this->_primary)) {
+ throw new Zend_Db_Table_Row_Exception("The primary key must be set as an array");
+ }
+
+ $primary = array_flip($this->_primary);
+ if ($useDirty) {
+ $array = array_intersect_key($this->_data, $primary);
+ } else {
+ $array = array_intersect_key($this->_cleanData, $primary);
+ }
+ if (count($primary) != count($array)) {
+ throw new Zend_Db_Table_Row_Exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row");
+ }
+ return $array;
+ }
+
+ /**
+ * Retrieves an associative array of primary keys.
+ *
+ * @param bool $useDirty
+ * @return array
+ */
+ public function getPrimaryKey($useDirty = true)
+ {
+ return $this->_getPrimaryKey($useDirty);
+ }
+
+ /**
+ * Constructs where statement for retrieving row(s).
+ *
+ * @param bool $useDirty
+ * @return array
+ */
+ protected function _getWhereQuery($useDirty = true)
+ {
+ $where = array();
+ $db = $this->_getTable()->getAdapter();
+ $primaryKey = $this->_getPrimaryKey($useDirty);
+ $info = $this->_getTable()->info();
+ $metadata = $info[Zend_Db_Table_Abstract::METADATA];
+
+ // retrieve recently updated row using primary keys
+ $where = array();
+ foreach ($primaryKey as $column => $value) {
+ $tableName = $db->quoteIdentifier($info[Zend_Db_Table_Abstract::NAME], true);
+ $type = $metadata[$column]['DATA_TYPE'];
+ $columnName = $db->quoteIdentifier($column, true);
+ $where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
+ }
+ return $where;
+ }
+
+ /**
+ * Refreshes properties from the database.
+ *
+ * @return void
+ */
+ protected function _refresh()
+ {
+ $where = $this->_getWhereQuery();
+ $row = $this->_getTable()->fetchRow($where);
+
+ if (null === $row) {
+ throw new Zend_Db_Table_Row_Exception('Cannot refresh row as parent is missing');
+ }
+
+ $this->_data = $row->toArray();
+ $this->_cleanData = $this->_data;
+ $this->_modifiedFields = array();
+ }
+
+ /**
+ * Allows pre-insert logic to be applied to row.
+ * Subclasses may override this method.
+ *
+ * @return void
+ */
+ protected function _insert()
+ {
+ }
+
+ /**
+ * Allows post-insert logic to be applied to row.
+ * Subclasses may override this method.
+ *
+ * @return void
+ */
+ protected function _postInsert()
+ {
+ }
+
+ /**
+ * Allows pre-update logic to be applied to row.
+ * Subclasses may override this method.
+ *
+ * @return void
+ */
+ protected function _update()
+ {
+ }
+
+ /**
+ * Allows post-update logic to be applied to row.
+ * Subclasses may override this method.
+ *
+ * @return void
+ */
+ protected function _postUpdate()
+ {
+ }
+
+ /**
+ * Allows pre-delete logic to be applied to row.
+ * Subclasses may override this method.
+ *
+ * @return void
+ */
+ protected function _delete()
+ {
+ }
+
+ /**
+ * Allows post-delete logic to be applied to row.
+ * Subclasses may override this method.
+ *
+ * @return void
+ */
+ protected function _postDelete()
+ {
+ }
+
+ /**
+ * Prepares a table reference for lookup.
+ *
+ * Ensures all reference keys are set and properly formatted.
+ *
+ * @param Zend_Db_Table_Abstract $dependentTable
+ * @param Zend_Db_Table_Abstract $parentTable
+ * @param string $ruleKey
+ * @return array
+ */
+ protected function _prepareReference(Zend_Db_Table_Abstract $dependentTable, Zend_Db_Table_Abstract $parentTable, $ruleKey)
+ {
+ $parentTableName = (get_class($parentTable) === 'Zend_Db_Table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
+ $map = $dependentTable->getReference($parentTableName, $ruleKey);
+
+ if (!isset($map[Zend_Db_Table_Abstract::REF_COLUMNS])) {
+ $parentInfo = $parentTable->info();
+ $map[Zend_Db_Table_Abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
+ }
+
+ $map[Zend_Db_Table_Abstract::COLUMNS] = (array) $map[Zend_Db_Table_Abstract::COLUMNS];
+ $map[Zend_Db_Table_Abstract::REF_COLUMNS] = (array) $map[Zend_Db_Table_Abstract::REF_COLUMNS];
+
+ return $map;
+ }
+
+ /**
+ * Query a dependent table to retrieve rows matching the current row.
+ *
+ * @param string|Zend_Db_Table_Abstract $dependentTable
+ * @param string OPTIONAL $ruleKey
+ * @param Zend_Db_Table_Select OPTIONAL $select
+ * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
+ * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
+ */
+ public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
+ {
+ $db = $this->_getTable()->getAdapter();
+
+ if (is_string($dependentTable)) {
+ $dependentTable = $this->_getTableFromString($dependentTable);
+ }
+
+ if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
+ $type = gettype($dependentTable);
+ if ($type == 'object') {
+ $type = get_class($dependentTable);
+ }
+ throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
+ }
+
+ // even if we are interacting between a table defined in a class and a
+ // table via extension, ensure to persist the definition
+ if (($tableDefinition = $this->_table->getDefinition()) !== null
+ && ($dependentTable->getDefinition() == null)) {
+ $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
+ }
+
+ if ($select === null) {
+ $select = $dependentTable->select();
+ } else {
+ $select->setTable($dependentTable);
+ }
+
+ $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
+
+ for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
+ $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
+ $value = $this->_data[$parentColumnName];
+ // Use adapter from dependent table to ensure correct query construction
+ $dependentDb = $dependentTable->getAdapter();
+ $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
+ $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
+ $dependentInfo = $dependentTable->info();
+ $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
+ $select->where("$dependentColumn = ?", $value, $type);
+ }
+
+ return $dependentTable->fetchAll($select);
+ }
+
+ /**
+ * Query a parent table to retrieve the single row matching the current row.
+ *
+ * @param string|Zend_Db_Table_Abstract $parentTable
+ * @param string OPTIONAL $ruleKey
+ * @param Zend_Db_Table_Select OPTIONAL $select
+ * @return Zend_Db_Table_Row_Abstract Query result from $parentTable
+ * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
+ */
+ public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
+ {
+ $db = $this->_getTable()->getAdapter();
+
+ if (is_string($parentTable)) {
+ $parentTable = $this->_getTableFromString($parentTable);
+ }
+
+ if (!$parentTable instanceof Zend_Db_Table_Abstract) {
+ $type = gettype($parentTable);
+ if ($type == 'object') {
+ $type = get_class($parentTable);
+ }
+ throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
+ }
+
+ // even if we are interacting between a table defined in a class and a
+ // table via extension, ensure to persist the definition
+ if (($tableDefinition = $this->_table->getDefinition()) !== null
+ && ($parentTable->getDefinition() == null)) {
+ $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
+ }
+
+ if ($select === null) {
+ $select = $parentTable->select();
+ } else {
+ $select->setTable($parentTable);
+ }
+
+ $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
+
+ // iterate the map, creating the proper wheres
+ for ($i = 0; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
+ $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
+ $value = $this->_data[$dependentColumnName];
+ // Use adapter from parent table to ensure correct query construction
+ $parentDb = $parentTable->getAdapter();
+ $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
+ $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
+ $parentInfo = $parentTable->info();
+
+ // determine where part
+ $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
+ $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
+ if ($value === null && $nullable == true) {
+ $select->where("$parentColumn IS NULL");
+ } elseif ($value === null && $nullable == false) {
+ return null;
+ } else {
+ $select->where("$parentColumn = ?", $value, $type);
+ }
+
+ }
+
+ return $parentTable->fetchRow($select);
+ }
+
+ /**
+ * @param string|Zend_Db_Table_Abstract $matchTable
+ * @param string|Zend_Db_Table_Abstract $intersectionTable
+ * @param string OPTIONAL $callerRefRule
+ * @param string OPTIONAL $matchRefRule
+ * @param Zend_Db_Table_Select OPTIONAL $select
+ * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
+ * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
+ */
+ public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null,
+ $matchRefRule = null, Zend_Db_Table_Select $select = null)
+ {
+ $db = $this->_getTable()->getAdapter();
+
+ if (is_string($intersectionTable)) {
+ $intersectionTable = $this->_getTableFromString($intersectionTable);
+ }
+
+ if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
+ $type = gettype($intersectionTable);
+ if ($type == 'object') {
+ $type = get_class($intersectionTable);
+ }
+ throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
+ }
+
+ // even if we are interacting between a table defined in a class and a
+ // table via extension, ensure to persist the definition
+ if (($tableDefinition = $this->_table->getDefinition()) !== null
+ && ($intersectionTable->getDefinition() == null)) {
+ $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
+ }
+
+ if (is_string($matchTable)) {
+ $matchTable = $this->_getTableFromString($matchTable);
+ }
+
+ if (! $matchTable instanceof Zend_Db_Table_Abstract) {
+ $type = gettype($matchTable);
+ if ($type == 'object') {
+ $type = get_class($matchTable);
+ }
+ throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
+ }
+
+ // even if we are interacting between a table defined in a class and a
+ // table via extension, ensure to persist the definition
+ if (($tableDefinition = $this->_table->getDefinition()) !== null
+ && ($matchTable->getDefinition() == null)) {
+ $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
+ }
+
+ if ($select === null) {
+ $select = $matchTable->select();
+ } else {
+ $select->setTable($matchTable);
+ }
+
+ // Use adapter from intersection table to ensure correct query construction
+ $interInfo = $intersectionTable->info();
+ $interDb = $intersectionTable->getAdapter();
+ $interName = $interInfo['name'];
+ $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
+ $matchInfo = $matchTable->info();
+ $matchName = $matchInfo['name'];
+ $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
+
+ $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
+
+ for ($i = 0; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
+ $interCol = $interDb->quoteIdentifier('i' . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true);
+ $matchCol = $interDb->quoteIdentifier('m' . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true);
+ $joinCond[] = "$interCol = $matchCol";
+ }
+ $joinCond = implode(' AND ', $joinCond);
+
+ $select->from(array('i' => $interName), array(), $interSchema)
+ ->joinInner(array('m' => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema)
+ ->setIntegrityCheck(false);
+
+ $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
+
+ for ($i = 0; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]); ++$i) {
+ $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
+ $value = $this->_data[$callerColumnName];
+ $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]);
+ $interCol = $interDb->quoteIdentifier("i.$interColumnName", true);
+ $interInfo = $intersectionTable->info();
+ $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE'];
+ $select->where($interDb->quoteInto("$interCol = ?", $value, $type));
+ }
+
+ $stmt = $select->query();
+
+ $config = array(
+ 'table' => $matchTable,
+ 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC),
+ 'rowClass' => $matchTable->getRowClass(),
+ 'readOnly' => false,
+ 'stored' => true
+ );
+
+ $rowsetClass = $matchTable->getRowsetClass();
+ if (!class_exists($rowsetClass)) {
+ try {
+ Zend_Loader::loadClass($rowsetClass);
+ } catch (Zend_Exception $e) {
+ throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
+ }
+ }
+ $rowset = new $rowsetClass($config);
+ return $rowset;
+ }
+
+ /**
+ * Turn magic function calls into non-magic function calls
+ * to the above methods.
+ *
+ * @param string $method
+ * @param array $args OPTIONAL Zend_Db_Table_Select query modifier
+ * @return Zend_Db_Table_Row_Abstract|Zend_Db_Table_Rowset_Abstract
+ * @throws Zend_Db_Table_Row_Exception If an invalid method is called.
+ */
+ public function __call($method, array $args)
+ {
+ $matches = array();
+
+ if (count($args) && $args[0] instanceof Zend_Db_Table_Select) {
+ $select = $args[0];
+ } else {
+ $select = null;
+ }
+
+ /**
+ * Recognize methods for Has-Many cases:
+ * findParent<Class>()
+ * findParent<Class>By<Rule>()
+ * Use the non-greedy pattern repeat modifier e.g. \w+?
+ */
+ if (preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
+ $class = $matches[1];
+ $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
+ return $this->findParentRow($class, $ruleKey1, $select);
+ }
+
+ /**
+ * Recognize methods for Many-to-Many cases:
+ * find<Class1>Via<Class2>()
+ * find<Class1>Via<Class2>By<Rule>()
+ * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
+ * Use the non-greedy pattern repeat modifier e.g. \w+?
+ */
+ if (preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
+ $class = $matches[1];
+ $viaClass = $matches[2];
+ $ruleKey1 = isset($matches[3]) ? $matches[3] : null;
+ $ruleKey2 = isset($matches[4]) ? $matches[4] : null;
+ return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
+ }
+
+ /**
+ * Recognize methods for Belongs-To cases:
+ * find<Class>()
+ * find<Class>By<Rule>()
+ * Use the non-greedy pattern repeat modifier e.g. \w+?
+ */
+ if (preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
+ $class = $matches[1];
+ $ruleKey1 = isset($matches[2]) ? $matches[2] : null;
+ return $this->findDependentRowset($class, $ruleKey1, $select);
+ }
+
+ throw new Zend_Db_Table_Row_Exception("Unrecognized method '$method()'");
+ }
+
+
+ /**
+ * _getTableFromString
+ *
+ * @param string $tableName
+ * @return Zend_Db_Table_Abstract
+ */
+ protected function _getTableFromString($tableName)
+ {
+ return Zend_Db_Table_Abstract::getTableFromString($tableName, $this->_table);
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Table/Row/Exception.php b/library/vendor/Zend/Db/Table/Row/Exception.php
new file mode 100644
index 0000000..fe23d66
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Row/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Table_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Row_Exception extends Zend_Db_Table_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Db/Table/Rowset.php b/library/vendor/Zend/Db/Table/Rowset.php
new file mode 100644
index 0000000..ce20a58
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Rowset.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Table_Rowset_Abstract
+ */
+
+
+/**
+ * Reference concrete class that extends Zend_Db_Table_Rowset_Abstract.
+ * Developers may also create their own classes that extend the abstract class.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Rowset extends Zend_Db_Table_Rowset_Abstract
+{
+}
diff --git a/library/vendor/Zend/Db/Table/Rowset/Abstract.php b/library/vendor/Zend/Db/Table/Rowset/Abstract.php
new file mode 100644
index 0000000..205fe1b
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Rowset/Abstract.php
@@ -0,0 +1,443 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Db_Table_Rowset_Abstract implements SeekableIterator, Countable, ArrayAccess
+{
+ /**
+ * The original data for each row.
+ *
+ * @var array
+ */
+ protected $_data = array();
+
+ /**
+ * Zend_Db_Table_Abstract object.
+ *
+ * @var Zend_Db_Table_Abstract
+ */
+ protected $_table;
+
+ /**
+ * Connected is true if we have a reference to a live
+ * Zend_Db_Table_Abstract object.
+ * This is false after the Rowset has been deserialized.
+ *
+ * @var boolean
+ */
+ protected $_connected = true;
+
+ /**
+ * Zend_Db_Table_Abstract class name.
+ *
+ * @var string
+ */
+ protected $_tableClass;
+
+ /**
+ * Zend_Db_Table_Row_Abstract class name.
+ *
+ * @var string
+ */
+ protected $_rowClass = 'Zend_Db_Table_Row';
+
+ /**
+ * Iterator pointer.
+ *
+ * @var integer
+ */
+ protected $_pointer = 0;
+
+ /**
+ * How many data rows there are.
+ *
+ * @var integer
+ */
+ protected $_count;
+
+ /**
+ * Collection of instantiated Zend_Db_Table_Row objects.
+ *
+ * @var array
+ */
+ protected $_rows = array();
+
+ /**
+ * @var boolean
+ */
+ protected $_stored = false;
+
+ /**
+ * @var boolean
+ */
+ protected $_readOnly = false;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config
+ */
+ public function __construct(array $config)
+ {
+ if (isset($config['table'])) {
+ $this->_table = $config['table'];
+ $this->_tableClass = get_class($this->_table);
+ }
+ if (isset($config['rowClass'])) {
+ $this->_rowClass = $config['rowClass'];
+ }
+ if (!class_exists($this->_rowClass)) {
+ Zend_Loader::loadClass($this->_rowClass);
+ }
+ if (isset($config['data'])) {
+ $this->_data = $config['data'];
+ }
+ if (isset($config['readOnly'])) {
+ $this->_readOnly = $config['readOnly'];
+ }
+ if (isset($config['stored'])) {
+ $this->_stored = $config['stored'];
+ }
+
+ // set the count of rows
+ $this->_count = count($this->_data);
+
+ $this->init();
+ }
+
+ /**
+ * Store data, class names, and state in serialized object
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ return array('_data', '_tableClass', '_rowClass', '_pointer', '_count', '_rows', '_stored',
+ '_readOnly');
+ }
+
+ /**
+ * Setup to do on wakeup.
+ * A de-serialized Rowset should not be assumed to have access to a live
+ * database connection, so set _connected = false.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ $this->_connected = false;
+ }
+
+ /**
+ * Initialize object
+ *
+ * Called from {@link __construct()} as final step of object instantiation.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Return the connected state of the rowset.
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return $this->_connected;
+ }
+
+ /**
+ * Returns the table object, or null if this is disconnected rowset
+ *
+ * @return Zend_Db_Table_Abstract
+ */
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ /**
+ * Set the table object, to re-establish a live connection
+ * to the database for a Rowset that has been de-serialized.
+ *
+ * @param Zend_Db_Table_Abstract $table
+ * @return boolean
+ * @throws Zend_Db_Table_Row_Exception
+ */
+ public function setTable(Zend_Db_Table_Abstract $table)
+ {
+ $this->_table = $table;
+ $this->_connected = false;
+ // @todo This works only if we have iterated through
+ // the result set once to instantiate the rows.
+ foreach ($this as $row) {
+ $connected = $row->setTable($table);
+ if ($connected == true) {
+ $this->_connected = true;
+ }
+ }
+ $this->rewind();
+ return $this->_connected;
+ }
+
+ /**
+ * Query the class name of the Table object for which this
+ * Rowset was created.
+ *
+ * @return string
+ */
+ public function getTableClass()
+ {
+ return $this->_tableClass;
+ }
+
+ /**
+ * Rewind the Iterator to the first element.
+ * Similar to the reset() function for arrays in PHP.
+ * Required by interface Iterator.
+ *
+ * @return Zend_Db_Table_Rowset_Abstract Fluent interface.
+ */
+ public function rewind()
+ {
+ $this->_pointer = 0;
+ return $this;
+ }
+
+ /**
+ * Return the current element.
+ * Similar to the current() function for arrays in PHP
+ * Required by interface Iterator.
+ *
+ * @return Zend_Db_Table_Row_Abstract current element from the collection
+ */
+ public function current()
+ {
+ if ($this->valid() === false) {
+ return null;
+ }
+
+ // return the row object
+ return $this->_loadAndReturnRow($this->_pointer);
+ }
+
+ /**
+ * Return the identifying key of the current element.
+ * Similar to the key() function for arrays in PHP.
+ * Required by interface Iterator.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->_pointer;
+ }
+
+ /**
+ * Move forward to next element.
+ * Similar to the next() function for arrays in PHP.
+ * Required by interface Iterator.
+ *
+ * @return void
+ */
+ public function next()
+ {
+ ++$this->_pointer;
+ }
+
+ /**
+ * Check if there is a current element after calls to rewind() or next().
+ * Used to check if we've iterated to the end of the collection.
+ * Required by interface Iterator.
+ *
+ * @return bool False if there's nothing more to iterate over
+ */
+ public function valid()
+ {
+ return $this->_pointer >= 0 && $this->_pointer < $this->_count;
+ }
+
+ /**
+ * Returns the number of elements in the collection.
+ *
+ * Implements Countable::count()
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->_count;
+ }
+
+ /**
+ * Take the Iterator to position $position
+ * Required by interface SeekableIterator.
+ *
+ * @param int $position the position to seek to
+ * @return Zend_Db_Table_Rowset_Abstract
+ * @throws Zend_Db_Table_Rowset_Exception
+ */
+ public function seek($position)
+ {
+ $position = (int) $position;
+ if ($position < 0 || $position >= $this->_count) {
+ throw new Zend_Db_Table_Rowset_Exception("Illegal index $position");
+ }
+ $this->_pointer = $position;
+ return $this;
+ }
+
+ /**
+ * Check if an offset exists
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->_data[(int) $offset]);
+ }
+
+ /**
+ * Get the row for the given offset
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ * @return Zend_Db_Table_Row_Abstract
+ */
+ public function offsetGet($offset)
+ {
+ $offset = (int) $offset;
+ if ($offset < 0 || $offset >= $this->_count) {
+ throw new Zend_Db_Table_Rowset_Exception("Illegal index $offset");
+ }
+ $this->_pointer = $offset;
+
+ return $this->current();
+ }
+
+ /**
+ * Does nothing
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value)
+ {
+ }
+
+ /**
+ * Does nothing
+ * Required by the ArrayAccess implementation
+ *
+ * @param string $offset
+ */
+ public function offsetUnset($offset)
+ {
+ }
+
+ /**
+ * Returns a Zend_Db_Table_Row from a known position into the Iterator
+ *
+ * @param int $position the position of the row expected
+ * @param bool $seek wether or not seek the iterator to that position after
+ * @return Zend_Db_Table_Row
+ * @throws Zend_Db_Table_Rowset_Exception
+ */
+ public function getRow($position, $seek = false)
+ {
+ try {
+ $row = $this->_loadAndReturnRow($position);
+ } catch (Zend_Db_Table_Rowset_Exception $e) {
+ throw new Zend_Db_Table_Rowset_Exception('No row could be found at position ' . (int) $position, 0, $e);
+ }
+
+ if ($seek == true) {
+ $this->seek($position);
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns all data as an array.
+ *
+ * Updates the $_data property with current row object values.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ // @todo This works only if we have iterated through
+ // the result set once to instantiate the rows.
+ foreach ($this->_rows as $i => $row) {
+ $this->_data[$i] = $row->toArray();
+ }
+ return $this->_data;
+ }
+
+ protected function _loadAndReturnRow($position)
+ {
+ if (!isset($this->_data[$position])) {
+ throw new Zend_Db_Table_Rowset_Exception("Data for provided position does not exist");
+ }
+
+ // do we already have a row object for this position?
+ if (empty($this->_rows[$position])) {
+ $this->_rows[$position] = new $this->_rowClass(
+ array(
+ 'table' => $this->_table,
+ 'data' => $this->_data[$position],
+ 'stored' => $this->_stored,
+ 'readOnly' => $this->_readOnly
+ )
+ );
+
+ if ( $this->_table instanceof Zend_Db_Table_Abstract ) {
+ $info = $this->_table->info();
+
+ if ( $this->_rows[$position] instanceof Zend_Db_Table_Row_Abstract ) {
+ if ($info['cols'] == array_keys($this->_data[$position])) {
+ $this->_rows[$position]->setTable($this->getTable());
+ }
+ }
+ } else {
+ $this->_rows[$position]->setTable(null);
+ }
+ }
+
+ // return the row object
+ return $this->_rows[$position];
+ }
+
+}
diff --git a/library/vendor/Zend/Db/Table/Rowset/Exception.php b/library/vendor/Zend/Db/Table/Rowset/Exception.php
new file mode 100644
index 0000000..9879113
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Rowset/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Table_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Rowset_Exception extends Zend_Db_Table_Exception
+{
+}
diff --git a/library/vendor/Zend/Db/Table/Select.php b/library/vendor/Zend/Db/Table/Select.php
new file mode 100644
index 0000000..fade302
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Select.php
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Select
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Db_Select
+ */
+
+
+/**
+ * @see Zend_Db_Table_Abstract
+ */
+
+
+/**
+ * Class for SQL SELECT query manipulation for the Zend_Db_Table component.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Db_Table_Select extends Zend_Db_Select
+{
+ /**
+ * Table schema for parent Zend_Db_Table.
+ *
+ * @var array
+ */
+ protected $_info;
+
+ /**
+ * Table integrity override.
+ *
+ * @var array
+ */
+ protected $_integrityCheck = true;
+
+ /**
+ * Table instance that created this select object
+ *
+ * @var Zend_Db_Table_Abstract
+ */
+ protected $_table;
+
+ /**
+ * Class constructor
+ *
+ * @param Zend_Db_Table_Abstract $adapter
+ */
+ public function __construct(Zend_Db_Table_Abstract $table)
+ {
+ parent::__construct($table->getAdapter());
+
+ $this->setTable($table);
+ }
+
+ /**
+ * Return the table that created this select object
+ *
+ * @return Zend_Db_Table_Abstract
+ */
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ /**
+ * Sets the primary table name and retrieves the table schema.
+ *
+ * @param Zend_Db_Table_Abstract $adapter
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function setTable(Zend_Db_Table_Abstract $table)
+ {
+ $this->_adapter = $table->getAdapter();
+ $this->_info = $table->info();
+ $this->_table = $table;
+
+ return $this;
+ }
+
+ /**
+ * Sets the integrity check flag.
+ *
+ * Setting this flag to false skips the checks for table joins, allowing
+ * 'hybrid' table rows to be created.
+ *
+ * @param Zend_Db_Table_Abstract $adapter
+ * @return Zend_Db_Select This Zend_Db_Select object.
+ */
+ public function setIntegrityCheck($flag = true)
+ {
+ $this->_integrityCheck = $flag;
+ return $this;
+ }
+
+ /**
+ * Tests query to determine if expressions or aliases columns exist.
+ *
+ * @return boolean
+ */
+ public function isReadOnly()
+ {
+ $readOnly = false;
+ $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS);
+ $cols = $this->_info[Zend_Db_Table_Abstract::COLS];
+
+ if (!count($fields)) {
+ return $readOnly;
+ }
+
+ foreach ($fields as $columnEntry) {
+ $column = $columnEntry[1];
+ $alias = $columnEntry[2];
+
+ if ($alias !== null) {
+ $column = $alias;
+ }
+
+ switch (true) {
+ case ($column == self::SQL_WILDCARD):
+ break;
+
+ case ($column instanceof Zend_Db_Expr):
+ case (!in_array($column, $cols)):
+ $readOnly = true;
+ break 2;
+ }
+ }
+
+ return $readOnly;
+ }
+
+ /**
+ * Adds a FROM table and optional columns to the query.
+ *
+ * The table name can be expressed
+ *
+ * @param array|string|Zend_Db_Expr|Zend_Db_Table_Abstract $name The table name or an
+ associative array relating
+ table name to correlation
+ name.
+ * @param array|string|Zend_Db_Expr $cols The columns to select from this table.
+ * @param string $schema The schema name to specify, if any.
+ * @return Zend_Db_Table_Select This Zend_Db_Table_Select object.
+ */
+ public function from($name, $cols = self::SQL_WILDCARD, $schema = null)
+ {
+ if ($name instanceof Zend_Db_Table_Abstract) {
+ $info = $name->info();
+ $name = $info[Zend_Db_Table_Abstract::NAME];
+ if (isset($info[Zend_Db_Table_Abstract::SCHEMA])) {
+ $schema = $info[Zend_Db_Table_Abstract::SCHEMA];
+ }
+ }
+
+ return $this->joinInner($name, null, $cols, $schema);
+ }
+
+ /**
+ * Performs a validation on the select query before passing back to the parent class.
+ * Ensures that only columns from the primary Zend_Db_Table are returned in the result.
+ *
+ * @return string|null This object as a SELECT string (or null if a string cannot be produced)
+ */
+ public function assemble()
+ {
+ $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS);
+ $primary = $this->_info[Zend_Db_Table_Abstract::NAME];
+ $schema = $this->_info[Zend_Db_Table_Abstract::SCHEMA];
+
+
+ if (count($this->_parts[self::UNION]) == 0) {
+
+ // If no fields are specified we assume all fields from primary table
+ if (!count($fields)) {
+ $this->from($primary, self::SQL_WILDCARD, $schema);
+ $fields = $this->getPart(Zend_Db_Table_Select::COLUMNS);
+ }
+
+ $from = $this->getPart(Zend_Db_Table_Select::FROM);
+
+ if ($this->_integrityCheck !== false) {
+ foreach ($fields as $columnEntry) {
+ list($table, $column) = $columnEntry;
+
+ // Check each column to ensure it only references the primary table
+ if ($column) {
+ if (!isset($from[$table]) || $from[$table]['tableName'] != $primary) {
+ throw new Zend_Db_Table_Select_Exception('Select query cannot join with another table');
+ }
+ }
+ }
+ }
+ }
+
+ return parent::assemble();
+ }
+}
diff --git a/library/vendor/Zend/Db/Table/Select/Exception.php b/library/vendor/Zend/Db/Table/Select/Exception.php
new file mode 100644
index 0000000..7ed15be
--- /dev/null
+++ b/library/vendor/Zend/Db/Table/Select/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Select
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Db_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Db
+ * @subpackage Table
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Db_Table_Select_Exception extends Zend_Db_Select_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Exception.php b/library/vendor/Zend/Exception.php
new file mode 100644
index 0000000..d97acb0
--- /dev/null
+++ b/library/vendor/Zend/Exception.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+* @category Zend
+* @package Zend
+* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+* @license http://framework.zend.com/license/new-bsd New BSD License
+*/
+class Zend_Exception extends Exception
+{
+ /**
+ * @var null|Exception
+ */
+ private $_previous = null;
+
+ /**
+ * Construct the exception
+ *
+ * @param string $msg
+ * @param int $code
+ * @param Exception $previous
+ * @return void
+ */
+ public function __construct($msg = '', $code = 0, Exception $previous = null)
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ parent::__construct($msg, (int) $code);
+ $this->_previous = $previous;
+ } else {
+ parent::__construct($msg, (int) $code, $previous);
+ }
+ }
+
+ /**
+ * Overloading
+ *
+ * For PHP < 5.3.0, provides access to the getPrevious() method.
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ if ('getprevious' == strtolower($method)) {
+ return $this->_getPrevious();
+ }
+ return null;
+ }
+
+ /**
+ * String representation of the exception
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ if (null !== ($e = $this->getPrevious())) {
+ return $e->__toString()
+ . "\n\nNext "
+ . parent::__toString();
+ }
+ }
+ return parent::__toString();
+ }
+
+ /**
+ * Returns previous Exception
+ *
+ * @return Exception|null
+ */
+ protected function _getPrevious()
+ {
+ return $this->_previous;
+ }
+}
diff --git a/library/vendor/Zend/File/ClassFileLocator.php b/library/vendor/Zend/File/ClassFileLocator.php
new file mode 100644
index 0000000..24e0541
--- /dev/null
+++ b/library/vendor/Zend/File/ClassFileLocator.php
@@ -0,0 +1,177 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_File
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Locate files containing PHP classes, interfaces, or abstracts
+ *
+ * @package Zend_File
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license New BSD {@link http://framework.zend.com/license/new-bsd}
+ */
+class Zend_File_ClassFileLocator extends FilterIterator
+{
+ /**
+ * Create an instance of the locator iterator
+ *
+ * Expects either a directory, or a DirectoryIterator (or its recursive variant)
+ * instance.
+ *
+ * @param string|DirectoryIterator $dirOrIterator
+ */
+ public function __construct($dirOrIterator = '.')
+ {
+ if (is_string($dirOrIterator)) {
+ if (!is_dir($dirOrIterator)) {
+ throw new InvalidArgumentException('Expected a valid directory name');
+ }
+
+ $dirOrIterator = new RecursiveDirectoryIterator($dirOrIterator);
+ }
+ if (!$dirOrIterator instanceof DirectoryIterator) {
+ throw new InvalidArgumentException('Expected a DirectoryIterator');
+ }
+
+ if ($dirOrIterator instanceof RecursiveIterator) {
+ $iterator = new RecursiveIteratorIterator($dirOrIterator);
+ } else {
+ $iterator = $dirOrIterator;
+ }
+
+ parent::__construct($iterator);
+ $this->setInfoClass('Zend_File_PhpClassFile');
+
+ // Forward-compat with PHP 5.3
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ if (!defined('T_NAMESPACE')) {
+ define('T_NAMESPACE', 'namespace');
+ }
+ if (!defined('T_NS_SEPARATOR')) {
+ define('T_NS_SEPARATOR', '\\');
+ }
+ }
+ }
+
+ /**
+ * Filter for files containing PHP classes, interfaces, or abstracts
+ *
+ * @return bool
+ */
+ public function accept()
+ {
+ $file = $this->getInnerIterator()->current();
+ // If we somehow have something other than an SplFileInfo object, just
+ // return false
+ if (!$file instanceof SplFileInfo) {
+ return false;
+ }
+
+ // If we have a directory, it's not a file, so return false
+ if (!$file->isFile()) {
+ return false;
+ }
+
+ // If not a PHP file, skip
+ if ($file->getBasename('.php') == $file->getBasename()) {
+ return false;
+ }
+
+ $contents = file_get_contents($file->getRealPath());
+ $tokens = token_get_all($contents);
+ $count = count($tokens);
+ $t_trait = defined('T_TRAIT') ? T_TRAIT : -1; // For preserve PHP 5.3 compatibility
+ for ($i = 0; $i < $count; $i++) {
+ $token = $tokens[$i];
+ if (!is_array($token)) {
+ // single character token found; skip
+ $i++;
+ continue;
+ }
+ switch ($token[0]) {
+ case T_NAMESPACE:
+ // Namespace found; grab it for later
+ $namespace = '';
+ for ($i++; $i < $count; $i++) {
+ $token = $tokens[$i];
+ if (is_string($token)) {
+ if (';' === $token) {
+ $saveNamespace = false;
+ break;
+ }
+ if ('{' === $token) {
+ $saveNamespace = true;
+ break;
+ }
+ continue;
+ }
+ list($type, $content, $line) = $token;
+ switch ($type) {
+ case T_STRING:
+ case T_NS_SEPARATOR:
+ $namespace .= $content;
+ break;
+ }
+ }
+ if ($saveNamespace) {
+ $savedNamespace = $namespace;
+ }
+ break;
+ case $t_trait:
+ case T_CLASS:
+ case T_INTERFACE:
+ // Abstract class, class, interface or trait found
+
+ // Get the classname
+ for ($i++; $i < $count; $i++) {
+ $token = $tokens[$i];
+ if (is_string($token)) {
+ continue;
+ }
+ list($type, $content, $line) = $token;
+ if (T_STRING == $type) {
+ // If a classname was found, set it in the object, and
+ // return boolean true (found)
+ if (!isset($namespace) || null === $namespace) {
+ if (isset($saveNamespace) && $saveNamespace) {
+ $namespace = $savedNamespace;
+ } else {
+ $namespace = null;
+ }
+
+ }
+ $class = (null === $namespace) ? $content : $namespace . '\\' . $content;
+ $file->addClass($class);
+ $namespace = null;
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ $classes = $file->getClasses();
+ if (!empty($classes)) {
+ return true;
+ }
+ // No class-type tokens found; return false
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/File/PhpClassFile.php b/library/vendor/Zend/File/PhpClassFile.php
new file mode 100644
index 0000000..0edf340
--- /dev/null
+++ b/library/vendor/Zend/File/PhpClassFile.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_File
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Locate files containing PHP classes, interfaces, abstracts or traits
+ *
+ * @package Zend_File
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license New BSD {@link http://framework.zend.com/license/new-bsd}
+ */
+class Zend_File_PhpClassFile extends SplFileInfo
+{
+ /**
+ * @var array
+ */
+ protected $classes;
+
+ /**
+ * Get classes
+ *
+ * @return array
+ */
+ public function getClasses()
+ {
+ return $this->classes;
+ }
+
+ /**
+ * Add class
+ *
+ * @param string $class
+ * @return Zend_File_PhpClassFile
+ */
+ public function addClass($class)
+ {
+ $this->classes[] = $class;
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/File/Transfer.php b/library/vendor/Zend/File/Transfer.php
new file mode 100644
index 0000000..4ff0763
--- /dev/null
+++ b/library/vendor/Zend/File/Transfer.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * Base class for all protocols supporting file transfers
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_File_Transfer
+{
+ /**
+ * Array holding all directions
+ *
+ * @var array
+ */
+ protected $_adapter = array();
+
+ /**
+ * Creates a file processing handler
+ *
+ * @param string $adapter Adapter to use
+ * @param boolean $direction OPTIONAL False means Download, true means upload
+ * @param array $options OPTIONAL Options to set for this adapter
+ * @throws Zend_File_Transfer_Exception
+ */
+ public function __construct($adapter = 'Http', $direction = false, $options = array())
+ {
+ $this->setAdapter($adapter, $direction, $options);
+ }
+
+ /**
+ * Sets a new adapter
+ *
+ * @param string $adapter Adapter to use
+ * @param boolean $direction OPTIONAL False means Download, true means upload
+ * @param array $options OPTIONAL Options to set for this adapter
+ * @throws Zend_File_Transfer_Exception
+ */
+ public function setAdapter($adapter, $direction = false, $options = array())
+ {
+ if (Zend_Loader::isReadable('Zend/File/Transfer/Adapter/' . ucfirst($adapter). '.php')) {
+ $adapter = 'Zend_File_Transfer_Adapter_' . ucfirst($adapter);
+ }
+
+ if (!class_exists($adapter)) {
+ Zend_Loader::loadClass($adapter);
+ }
+
+ $direction = (integer) $direction;
+ $this->_adapter[$direction] = new $adapter($options);
+ if (!$this->_adapter[$direction] instanceof Zend_File_Transfer_Adapter_Abstract) {
+ throw new Zend_File_Transfer_Exception("Adapter " . $adapter . " does not extend Zend_File_Transfer_Adapter_Abstract");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns all set adapters
+ *
+ * @param boolean $direction On null, all directions are returned
+ * On false, download direction is returned
+ * On true, upload direction is returned
+ * @return array|Zend_File_Transfer_Adapter
+ */
+ public function getAdapter($direction = null)
+ {
+ if ($direction === null) {
+ return $this->_adapter;
+ }
+
+ $direction = (integer) $direction;
+ return $this->_adapter[$direction];
+ }
+
+ /**
+ * Calls all methods from the adapter
+ *
+ * @param string $method Method to call
+ * @param array $options Options for this method
+ * @return mixed
+ */
+ public function __call($method, array $options)
+ {
+ if (array_key_exists('direction', $options)) {
+ $direction = (integer) $options['direction'];
+ } else {
+ $direction = 0;
+ }
+
+ if (method_exists($this->_adapter[$direction], $method)) {
+ return call_user_func_array(array($this->_adapter[$direction], $method), $options);
+ }
+
+ throw new Zend_File_Transfer_Exception("Unknown method '" . $method . "' called!");
+ }
+}
diff --git a/library/vendor/Zend/File/Transfer/Adapter/Abstract.php b/library/vendor/Zend/File/Transfer/Adapter/Abstract.php
new file mode 100644
index 0000000..0a6b76c
--- /dev/null
+++ b/library/vendor/Zend/File/Transfer/Adapter/Abstract.php
@@ -0,0 +1,1548 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Abstract class for file transfers (Downloads and Uploads)
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_File_Transfer_Adapter_Abstract
+{
+ /**@+
+ * Plugin loader Constants
+ */
+ const FILTER = 'FILTER';
+ const VALIDATE = 'VALIDATE';
+ /**@-*/
+
+ /**
+ * Internal list of breaks
+ *
+ * @var array
+ */
+ protected $_break = array();
+
+ /**
+ * Internal list of filters
+ *
+ * @var array
+ */
+ protected $_filters = array();
+
+ /**
+ * Plugin loaders for filter and validation chains
+ *
+ * @var array
+ */
+ protected $_loaders = array();
+
+ /**
+ * Internal list of messages
+ *
+ * @var array
+ */
+ protected $_messages = array();
+
+ /**
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Is translation disabled?
+ *
+ * @var bool
+ */
+ protected $_translatorDisabled = false;
+
+ /**
+ * Internal list of validators
+ * @var array
+ */
+ protected $_validators = array();
+
+ /**
+ * Internal list of files
+ * This array looks like this:
+ * array(form => array( - Form is the name within the form or, if not set the filename
+ * name, - Original name of this file
+ * type, - Mime type of this file
+ * size, - Filesize in bytes
+ * tmp_name, - Internalally temporary filename for uploaded files
+ * error, - Error which has occured
+ * destination, - New destination for this file
+ * validators, - Set validator names for this file
+ * files - Set file names for this file
+ * ))
+ *
+ * @var array
+ */
+ protected $_files = array();
+
+ /**
+ * TMP directory
+ * @var string
+ */
+ protected $_tmpDir;
+
+ /**
+ * Available options for file transfers
+ */
+ protected $_options = array(
+ 'ignoreNoFile' => false,
+ 'useByteString' => true,
+ 'magicFile' => null,
+ 'detectInfos' => true,
+ );
+
+ /**
+ * Send file
+ *
+ * @param mixed $options
+ * @return bool
+ */
+ abstract public function send($options = null);
+
+ /**
+ * Receive file
+ *
+ * @param mixed $options
+ * @return bool
+ */
+ abstract public function receive($options = null);
+
+ /**
+ * Is file sent?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isSent($files = null);
+
+ /**
+ * Is file received?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isReceived($files = null);
+
+ /**
+ * Has a file been uploaded ?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isUploaded($files = null);
+
+ /**
+ * Has the file been filtered ?
+ *
+ * @param array|string|null $files
+ * @return bool
+ */
+ abstract public function isFiltered($files = null);
+
+ /**
+ * Retrieve progress of transfer
+ *
+ * @return float
+ */
+ public static function getProgress()
+ {
+ throw new Zend_File_Transfer_Exception('Method must be implemented within the adapter');
+ }
+
+ /**
+ * Set plugin loader to use for validator or filter chain
+ *
+ * @param Zend_Loader_PluginLoader_Interface $loader
+ * @param string $type 'filter', or 'validate'
+ * @return Zend_File_Transfer_Adapter_Abstract
+ * @throws Zend_File_Transfer_Exception on invalid type
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::FILTER:
+ case self::VALIDATE:
+ $this->_loaders[$type] = $loader;
+ return $this;
+ default:
+ throw new Zend_File_Transfer_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Retrieve plugin loader for validator or filter chain
+ *
+ * Instantiates with default rules if none available for that type. Use
+ * 'filter' or 'validate' for $type.
+ *
+ * @param string $type
+ * @return Zend_Loader_PluginLoader
+ * @throws Zend_File_Transfer_Exception on invalid type.
+ */
+ public function getPluginLoader($type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::FILTER:
+ case self::VALIDATE:
+ $prefixSegment = ucfirst(strtolower($type));
+ $pathSegment = $prefixSegment;
+ if (!isset($this->_loaders[$type])) {
+ $paths = array(
+ 'Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/',
+ 'Zend_' . $prefixSegment . '_File' => 'Zend/' . $pathSegment . '/File',
+ );
+
+ $this->_loaders[$type] = new Zend_Loader_PluginLoader($paths);
+ } else {
+ $loader = $this->_loaders[$type];
+ $prefix = 'Zend_' . $prefixSegment . '_File_';
+ if (!$loader->getPaths($prefix)) {
+ $loader->addPrefixPath($prefix, str_replace('_', '/', $prefix));
+ }
+ }
+ return $this->_loaders[$type];
+ default:
+ throw new Zend_File_Transfer_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Add prefix path for plugin loader
+ *
+ * If no $type specified, assumes it is a base path for both filters and
+ * validators, and sets each according to the following rules:
+ * - filters: $prefix = $prefix . '_Filter'
+ * - validators: $prefix = $prefix . '_Validate'
+ *
+ * Otherwise, the path prefix is set on the appropriate plugin loader.
+ *
+ * @param string $prefix
+ * @param string $path
+ * @param string $type
+ * @return Zend_File_Transfer_Adapter_Abstract
+ * @throws Zend_File_Transfer_Exception for invalid type
+ */
+ public function addPrefixPath($prefix, $path, $type = null)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::FILTER:
+ case self::VALIDATE:
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($prefix, $path);
+ return $this;
+ case null:
+ $prefix = rtrim($prefix, '_');
+ $path = rtrim($path, DIRECTORY_SEPARATOR);
+ foreach (array(self::FILTER, self::VALIDATE) as $type) {
+ $cType = ucfirst(strtolower($type));
+ $pluginPath = $path . DIRECTORY_SEPARATOR . $cType . DIRECTORY_SEPARATOR;
+ $pluginPrefix = $prefix . '_' . $cType;
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($pluginPrefix, $pluginPath);
+ }
+ return $this;
+ default:
+ throw new Zend_File_Transfer_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Add many prefix paths at once
+ *
+ * @param array $spec
+ * @return Zend_File_Transfer_Exception
+ */
+ public function addPrefixPaths(array $spec)
+ {
+ if (isset($spec['prefix']) && isset($spec['path'])) {
+ return $this->addPrefixPath($spec['prefix'], $spec['path']);
+ }
+ foreach ($spec as $type => $paths) {
+ if (is_numeric($type) && is_array($paths)) {
+ $type = null;
+ if (isset($paths['prefix']) && isset($paths['path'])) {
+ if (isset($paths['type'])) {
+ $type = $paths['type'];
+ }
+ $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
+ }
+ } elseif (!is_numeric($type)) {
+ if (!isset($paths['prefix']) || !isset($paths['path'])) {
+ foreach ($paths as $prefix => $spec) {
+ if (is_array($spec)) {
+ foreach ($spec as $path) {
+ if (!is_string($path)) {
+ continue;
+ }
+ $this->addPrefixPath($prefix, $path, $type);
+ }
+ } elseif (is_string($spec)) {
+ $this->addPrefixPath($prefix, $spec, $type);
+ }
+ }
+ } else {
+ $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Adds a new validator for this class
+ *
+ * @param string|array $validator Type of validator to add
+ * @param boolean $breakChainOnFailure If the validation chain should stop an failure
+ * @param string|array $options Options to set for the validator
+ * @param string|array $files Files to limit this validator to
+ * @return Zend_File_Transfer_Adapter
+ */
+ public function addValidator($validator, $breakChainOnFailure = false, $options = null, $files = null)
+ {
+ if ($validator instanceof Zend_Validate_Interface) {
+ $name = get_class($validator);
+ } elseif (is_string($validator)) {
+ $name = $this->getPluginLoader(self::VALIDATE)->load($validator);
+ $validator = new $name($options);
+ if (is_array($options) && isset($options['messages'])) {
+ if (is_array($options['messages'])) {
+ $validator->setMessages($options['messages']);
+ } elseif (is_string($options['messages'])) {
+ $validator->setMessage($options['messages']);
+ }
+
+ unset($options['messages']);
+ }
+ } else {
+ throw new Zend_File_Transfer_Exception('Invalid validator provided to addValidator; must be string or Zend_Validate_Interface');
+ }
+
+ $this->_validators[$name] = $validator;
+ $this->_break[$name] = $breakChainOnFailure;
+ $files = $this->_getFiles($files, true, true);
+ foreach ($files as $file) {
+ if ($name == 'NotEmpty') {
+ $temp = $this->_files[$file]['validators'];
+ $this->_files[$file]['validators'] = array($name);
+ $this->_files[$file]['validators'] += $temp;
+ } else {
+ $this->_files[$file]['validators'][] = $name;
+ }
+
+ $this->_files[$file]['validated'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add Multiple validators at once
+ *
+ * @param array $validators
+ * @param string|array $files
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function addValidators(array $validators, $files = null)
+ {
+ foreach ($validators as $name => $validatorInfo) {
+ if ($validatorInfo instanceof Zend_Validate_Interface) {
+ $this->addValidator($validatorInfo, null, null, $files);
+ } else if (is_string($validatorInfo)) {
+ if (!is_int($name)) {
+ $this->addValidator($name, null, $validatorInfo, $files);
+ } else {
+ $this->addValidator($validatorInfo, null, null, $files);
+ }
+ } else if (is_array($validatorInfo)) {
+ $argc = count($validatorInfo);
+ $breakChainOnFailure = false;
+ $options = array();
+ if (isset($validatorInfo['validator'])) {
+ $validator = $validatorInfo['validator'];
+ if (isset($validatorInfo['breakChainOnFailure'])) {
+ $breakChainOnFailure = $validatorInfo['breakChainOnFailure'];
+ }
+
+ if (isset($validatorInfo['options'])) {
+ $options = $validatorInfo['options'];
+ }
+
+ $this->addValidator($validator, $breakChainOnFailure, $options, $files);
+ } else {
+ if (is_string($name)) {
+ $validator = $name;
+ $options = $validatorInfo;
+ $this->addValidator($validator, $breakChainOnFailure, $options, $files);
+ } else {
+ $file = $files;
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $validator = array_shift($validatorInfo);
+ case (2 <= $argc):
+ $breakChainOnFailure = array_shift($validatorInfo);
+ case (3 <= $argc):
+ $options = array_shift($validatorInfo);
+ case (4 <= $argc):
+ if (!empty($validatorInfo)) {
+ $file = array_shift($validatorInfo);
+ }
+ default:
+ $this->addValidator($validator, $breakChainOnFailure, $options, $file);
+ break;
+ }
+ }
+ }
+ } else {
+ throw new Zend_File_Transfer_Exception('Invalid validator passed to addValidators()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a validator for the class, erasing all previous set
+ *
+ * @param string|array $validator Validator to set
+ * @param string|array $files Files to limit this validator to
+ * @return Zend_File_Transfer_Adapter
+ */
+ public function setValidators(array $validators, $files = null)
+ {
+ $this->clearValidators();
+ return $this->addValidators($validators, $files);
+ }
+
+ /**
+ * Determine if a given validator has already been registered
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasValidator($name)
+ {
+ return (false !== $this->_getValidatorIdentifier($name));
+ }
+
+ /**
+ * Retrieve individual validator
+ *
+ * @param string $name
+ * @return Zend_Validate_Interface|null
+ */
+ public function getValidator($name)
+ {
+ if (false === ($identifier = $this->_getValidatorIdentifier($name))) {
+ return null;
+ }
+ return $this->_validators[$identifier];
+ }
+
+ /**
+ * Returns all set validators
+ *
+ * @param string|array $files (Optional) Returns the validator for this files
+ * @return null|array List of set validators
+ */
+ public function getValidators($files = null)
+ {
+ if ($files == null) {
+ return $this->_validators;
+ }
+
+ $files = $this->_getFiles($files, true, true);
+ $validators = array();
+ foreach ($files as $file) {
+ if (!empty($this->_files[$file]['validators'])) {
+ $validators += $this->_files[$file]['validators'];
+ }
+ }
+
+ $validators = array_unique($validators);
+ $result = array();
+ foreach ($validators as $validator) {
+ $result[$validator] = $this->_validators[$validator];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove an individual validator
+ *
+ * @param string $name
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function removeValidator($name)
+ {
+ if (false === ($key = $this->_getValidatorIdentifier($name))) {
+ return $this;
+ }
+
+ unset($this->_validators[$key]);
+ foreach (array_keys($this->_files) as $file) {
+ if (empty($this->_files[$file]['validators'])) {
+ continue;
+ }
+
+ $index = array_search($key, $this->_files[$file]['validators']);
+ if ($index === false) {
+ continue;
+ }
+
+ unset($this->_files[$file]['validators'][$index]);
+ $this->_files[$file]['validated'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove all validators
+ *
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function clearValidators()
+ {
+ $this->_validators = array();
+ foreach (array_keys($this->_files) as $file) {
+ $this->_files[$file]['validators'] = array();
+ $this->_files[$file]['validated'] = false;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets Options for adapters
+ *
+ * @param array $options Options to set
+ * @param array $files (Optional) Files to set the options for
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function setOptions($options = array(), $files = null) {
+ $file = $this->_getFiles($files, false, true);
+
+ if (is_array($options)) {
+ if (empty($file)) {
+ $this->_options = array_merge($this->_options, $options);
+ }
+
+ foreach ($options as $name => $value) {
+ foreach ($file as $key => $content) {
+ switch ($name) {
+ case 'magicFile' :
+ $this->_files[$key]['options'][$name] = (string) $value;
+ break;
+
+ case 'ignoreNoFile' :
+ case 'useByteString' :
+ case 'detectInfos' :
+ $this->_files[$key]['options'][$name] = (boolean) $value;
+ break;
+
+ default:
+ throw new Zend_File_Transfer_Exception("Unknown option: $name = $value");
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns set options for adapters or files
+ *
+ * @param array $files (Optional) Files to return the options for
+ * @return array Options for given files
+ */
+ public function getOptions($files = null) {
+ $file = $this->_getFiles($files, false, true);
+
+ foreach ($file as $key => $content) {
+ if (isset($this->_files[$key]['options'])) {
+ $options[$key] = $this->_files[$key]['options'];
+ } else {
+ $options[$key] = array();
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Checks if the files are valid
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return boolean True if all checks are valid
+ */
+ public function isValid($files = null)
+ {
+ $check = $this->_getFiles($files, false, true);
+ if (empty($check)) {
+ return false;
+ }
+
+ $translator = $this->getTranslator();
+ $this->_messages = array();
+ $break = false;
+ foreach($check as $key => $content) {
+ if (array_key_exists('validators', $content) &&
+ in_array('Zend_Validate_File_Count', $content['validators'])) {
+ $validator = $this->_validators['Zend_Validate_File_Count'];
+ $count = $content;
+ if (empty($content['tmp_name'])) {
+ continue;
+ }
+
+ if (array_key_exists('destination', $content)) {
+ $checkit = $content['destination'];
+ } else {
+ $checkit = dirname($content['tmp_name']);
+ }
+
+ $checkit .= DIRECTORY_SEPARATOR . $content['name'];
+ $validator->addFile($checkit);
+ }
+ }
+
+ if (isset($count)) {
+ if (!$validator->isValid($count['tmp_name'], $count)) {
+ $this->_messages += $validator->getMessages();
+ }
+ }
+
+ foreach ($check as $key => $content) {
+ $fileerrors = array();
+ if (array_key_exists('validators', $content) && $content['validated']) {
+ continue;
+ }
+
+ if (array_key_exists('validators', $content)) {
+ foreach ($content['validators'] as $class) {
+ $validator = $this->_validators[$class];
+ if (method_exists($validator, 'setTranslator')) {
+ $validator->setTranslator($translator);
+ }
+
+ if (($class === 'Zend_Validate_File_Upload') and (empty($content['tmp_name']))) {
+ $tocheck = $key;
+ } else {
+ $tocheck = $content['tmp_name'];
+ }
+
+ if (!$validator->isValid($tocheck, $content)) {
+ $fileerrors += $validator->getMessages();
+ }
+
+ if (!empty($content['options']['ignoreNoFile']) and (isset($fileerrors['fileUploadErrorNoFile']))) {
+ unset($fileerrors['fileUploadErrorNoFile']);
+ break;
+ }
+
+ if (($class === 'Zend_Validate_File_Upload') and (count($fileerrors) > 0)) {
+ break;
+ }
+
+ if (($this->_break[$class]) and (count($fileerrors) > 0)) {
+ $break = true;
+ break;
+ }
+ }
+ }
+
+ if (count($fileerrors) > 0) {
+ $this->_files[$key]['validated'] = false;
+ } else {
+ $this->_files[$key]['validated'] = true;
+ }
+
+ $this->_messages += $fileerrors;
+ if ($break) {
+ break;
+ }
+ }
+
+ if (count($this->_messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns found validation messages
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return $this->_messages;
+ }
+
+ /**
+ * Retrieve error codes
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return array_keys($this->_messages);
+ }
+
+ /**
+ * Are there errors registered?
+ *
+ * @return boolean
+ */
+ public function hasErrors()
+ {
+ return (!empty($this->_messages));
+ }
+
+ /**
+ * Adds a new filter for this class
+ *
+ * @param string|array $filter Type of filter to add
+ * @param string|array $options Options to set for the filter
+ * @param string|array $files Files to limit this filter to
+ * @return Zend_File_Transfer_Adapter
+ */
+ public function addFilter($filter, $options = null, $files = null)
+ {
+ if ($filter instanceof Zend_Filter_Interface) {
+ $class = get_class($filter);
+ } elseif (is_string($filter)) {
+ $class = $this->getPluginLoader(self::FILTER)->load($filter);
+ $filter = new $class($options);
+ } else {
+ throw new Zend_File_Transfer_Exception('Invalid filter specified');
+ }
+
+ $this->_filters[$class] = $filter;
+ $files = $this->_getFiles($files, true, true);
+ foreach ($files as $file) {
+ $this->_files[$file]['filters'][] = $class;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add Multiple filters at once
+ *
+ * @param array $filters
+ * @param string|array $files
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function addFilters(array $filters, $files = null)
+ {
+ foreach ($filters as $key => $spec) {
+ if ($spec instanceof Zend_Filter_Interface) {
+ $this->addFilter($spec, null, $files);
+ continue;
+ }
+
+ if (is_string($key)) {
+ $this->addFilter($key, $spec, $files);
+ continue;
+ }
+
+ if (is_int($key)) {
+ if (is_string($spec)) {
+ $this->addFilter($spec, null, $files);
+ continue;
+ }
+
+ if (is_array($spec)) {
+ if (!array_key_exists('filter', $spec)) {
+ continue;
+ }
+
+ $filter = $spec['filter'];
+ unset($spec['filter']);
+ $this->addFilter($filter, $spec, $files);
+ continue;
+ }
+
+ continue;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets a filter for the class, erasing all previous set
+ *
+ * @param string|array $filter Filter to set
+ * @param string|array $files Files to limit this filter to
+ * @return Zend_File_Transfer_Adapter
+ */
+ public function setFilters(array $filters, $files = null)
+ {
+ $this->clearFilters();
+ return $this->addFilters($filters, $files);
+ }
+
+ /**
+ * Determine if a given filter has already been registered
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function hasFilter($name)
+ {
+ return (false !== $this->_getFilterIdentifier($name));
+ }
+
+ /**
+ * Retrieve individual filter
+ *
+ * @param string $name
+ * @return Zend_Filter_Interface|null
+ */
+ public function getFilter($name)
+ {
+ if (false === ($identifier = $this->_getFilterIdentifier($name))) {
+ return null;
+ }
+ return $this->_filters[$identifier];
+ }
+
+ /**
+ * Returns all set filters
+ *
+ * @param string|array $files (Optional) Returns the filter for this files
+ * @return array List of set filters
+ * @throws Zend_File_Transfer_Exception When file not found
+ */
+ public function getFilters($files = null)
+ {
+ if ($files === null) {
+ return $this->_filters;
+ }
+
+ $files = $this->_getFiles($files, true, true);
+ $filters = array();
+ foreach ($files as $file) {
+ if (!empty($this->_files[$file]['filters'])) {
+ $filters += $this->_files[$file]['filters'];
+ }
+ }
+
+ $filters = array_unique($filters);
+ $result = array();
+ foreach ($filters as $filter) {
+ $result[] = $this->_filters[$filter];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove an individual filter
+ *
+ * @param string $name
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function removeFilter($name)
+ {
+ if (false === ($key = $this->_getFilterIdentifier($name))) {
+ return $this;
+ }
+
+ unset($this->_filters[$key]);
+ foreach (array_keys($this->_files) as $file) {
+ if (empty($this->_files[$file]['filters'])) {
+ continue;
+ }
+
+ $index = array_search($key, $this->_files[$file]['filters']);
+ if ($index === false) {
+ continue;
+ }
+
+ unset($this->_files[$file]['filters'][$index]);
+ }
+ return $this;
+ }
+
+ /**
+ * Remove all filters
+ *
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function clearFilters()
+ {
+ $this->_filters = array();
+ foreach (array_keys($this->_files) as $file) {
+ $this->_files[$file]['filters'] = array();
+ }
+ return $this;
+ }
+
+ /**
+ * Returns all set files
+ *
+ * @return array List of set files
+ * @throws Zend_File_Transfer_Exception Not implemented
+ */
+ public function getFile()
+ {
+ throw new Zend_File_Transfer_Exception('Method not implemented');
+ }
+
+ /**
+ * Retrieves the filename of transferred files.
+ *
+ * @param string|null $file
+ * @param boolean $path (Optional) Should the path also be returned ?
+ * @return string|array
+ */
+ public function getFileName($file = null, $path = true)
+ {
+ $files = $this->_getFiles($file, true, true);
+ $result = array();
+ $directory = "";
+ foreach($files as $file) {
+ if (empty($this->_files[$file]['name'])) {
+ continue;
+ }
+
+ if ($path === true) {
+ $directory = $this->getDestination($file) . DIRECTORY_SEPARATOR;
+ }
+
+ $result[$file] = $directory . $this->_files[$file]['name'];
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Retrieve additional internal file informations for files
+ *
+ * @param string $file (Optional) File to get informations for
+ * @return array
+ */
+ public function getFileInfo($file = null)
+ {
+ return $this->_getFiles($file);
+ }
+
+ /**
+ * Adds one or more files
+ *
+ * @param string|array $file File to add
+ * @param string|array $validator Validators to use for this file, must be set before
+ * @param string|array $filter Filters to use for this file, must be set before
+ * @return Zend_File_Transfer_Adapter_Abstract
+ * @throws Zend_File_Transfer_Exception Not implemented
+ */
+ public function addFile($file, $validator = null, $filter = null)
+ {
+ throw new Zend_File_Transfer_Exception('Method not implemented');
+ }
+
+ /**
+ * Returns all set types
+ *
+ * @return array List of set types
+ * @throws Zend_File_Transfer_Exception Not implemented
+ */
+ public function getType()
+ {
+ throw new Zend_File_Transfer_Exception('Method not implemented');
+ }
+
+ /**
+ * Adds one or more type of files
+ *
+ * @param string|array $type Type of files to add
+ * @param string|array $validator Validators to use for this file, must be set before
+ * @param string|array $filter Filters to use for this file, must be set before
+ * @return Zend_File_Transfer_Adapter_Abstract
+ * @throws Zend_File_Transfer_Exception Not implemented
+ */
+ public function addType($type, $validator = null, $filter = null)
+ {
+ throw new Zend_File_Transfer_Exception('Method not implemented');
+ }
+
+ /**
+ * Sets a new destination for the given files
+ *
+ * @deprecated Will be changed to be a filter!!!
+ * @param string $destination New destination directory
+ * @param string|array $files Files to set the new destination for
+ * @return Zend_File_Transfer_Abstract
+ * @throws Zend_File_Transfer_Exception when the given destination is not a directory or does not exist
+ */
+ public function setDestination($destination, $files = null)
+ {
+ $orig = $files;
+ $destination = rtrim($destination, "/\\");
+ if (!is_dir($destination)) {
+ throw new Zend_File_Transfer_Exception(
+ 'The given destination is not a directory or does not exist'
+ );
+ }
+
+ if (!$this->_isPathWriteable($destination)) {
+ throw new Zend_File_Transfer_Exception(
+ 'The given destination is not writable'
+ );
+ }
+
+ if ($files === null) {
+ foreach ($this->_files as $file => $content) {
+ $this->_files[$file]['destination'] = $destination;
+ }
+ } else {
+ $files = $this->_getFiles($files, true, true);
+ if (empty($files) and is_string($orig)) {
+ $this->_files[$orig]['destination'] = $destination;
+ }
+
+ foreach ($files as $file) {
+ $this->_files[$file]['destination'] = $destination;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve destination directory value
+ *
+ * @param null|string|array $files
+ * @return null|string|array
+ * @throws Zend_File_Transfer_Exception
+ */
+ public function getDestination($files = null)
+ {
+ $orig = $files;
+ $files = $this->_getFiles($files, false, true);
+ $destinations = array();
+ if (empty($files) and is_string($orig)) {
+ if (isset($this->_files[$orig]['destination'])) {
+ $destinations[$orig] = $this->_files[$orig]['destination'];
+ } else {
+ throw new Zend_File_Transfer_Exception(sprintf('The file transfer adapter can not find "%s"', $orig));
+ }
+ }
+
+ foreach ($files as $key => $content) {
+ if (isset($this->_files[$key]['destination'])) {
+ $destinations[$key] = $this->_files[$key]['destination'];
+ } else {
+ $tmpdir = $this->_getTmpDir();
+ $this->setDestination($tmpdir, $key);
+ $destinations[$key] = $tmpdir;
+ }
+ }
+
+ if (empty($destinations)) {
+ $destinations = $this->_getTmpDir();
+ } else if (count($destinations) == 1) {
+ $destinations = current($destinations);
+ }
+
+ return $destinations;
+ }
+
+ /**
+ * Set translator object for localization
+ *
+ * @param Zend_Translate|null $translator
+ * @return Zend_File_Transfer_Abstract
+ * @throws Zend_File_Transfer_Exception
+ */
+ public function setTranslator($translator = null)
+ {
+ if (null === $translator) {
+ $this->_translator = null;
+ } elseif ($translator instanceof Zend_Translate_Adapter) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ throw new Zend_File_Transfer_Exception('Invalid translator specified');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve localization translator object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ return $this->_translator;
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_File_Transfer_Abstract
+ */
+ public function setDisableTranslator($flag)
+ {
+ $this->_translatorDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ return $this->_translatorDisabled;
+ }
+
+ /**
+ * Returns the hash for a given file
+ *
+ * @param string $hash Hash algorithm to use
+ * @param string|array $files Files to return the hash for
+ * @return string|array Hashstring
+ * @throws Zend_File_Transfer_Exception On unknown hash algorithm
+ */
+ public function getHash($hash = 'crc32', $files = null)
+ {
+ if (!in_array($hash, hash_algos())) {
+ throw new Zend_File_Transfer_Exception('Unknown hash algorithm');
+ }
+
+ $files = $this->_getFiles($files);
+ $result = array();
+ foreach($files as $key => $value) {
+ if (file_exists($value['name'])) {
+ $result[$key] = hash_file($hash, $value['name']);
+ } else if (file_exists($value['tmp_name'])) {
+ $result[$key] = hash_file($hash, $value['tmp_name']);
+ } else if (empty($value['options']['ignoreNoFile'])) {
+ throw new Zend_File_Transfer_Exception("The file '{$value['name']}' does not exist");
+ }
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the real filesize of the file
+ *
+ * @param string|array $files Files to get the filesize from
+ * @throws Zend_File_Transfer_Exception When the file does not exist
+ * @return string|array Filesize
+ */
+ public function getFileSize($files = null)
+ {
+ $files = $this->_getFiles($files);
+ $result = array();
+ foreach($files as $key => $value) {
+ if (file_exists($value['name']) || file_exists($value['tmp_name'])) {
+ if ($value['options']['useByteString']) {
+ $result[$key] = self::_toByteString($value['size']);
+ } else {
+ $result[$key] = $value['size'];
+ }
+ } else if (empty($value['options']['ignoreNoFile'])) {
+ throw new Zend_File_Transfer_Exception("The file '{$value['name']}' does not exist");
+ } else {
+ continue;
+ }
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to detect the size of a file
+ *
+ * @param array $value File infos
+ * @return string Filesize of given file
+ */
+ protected function _detectFileSize($value)
+ {
+ if (file_exists($value['name'])) {
+ $result = sprintf("%u", @filesize($value['name']));
+ } else if (file_exists($value['tmp_name'])) {
+ $result = sprintf("%u", @filesize($value['tmp_name']));
+ } else {
+ return null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the real mimetype of the file
+ * Uses fileinfo, when not available mime_magic and as last fallback a manual given mimetype
+ *
+ * @param string|array $files Files to get the mimetype from
+ * @throws Zend_File_Transfer_Exception When the file does not exist
+ * @return string|array MimeType
+ */
+ public function getMimeType($files = null)
+ {
+ $files = $this->_getFiles($files);
+ $result = array();
+ foreach($files as $key => $value) {
+ if (file_exists($value['name']) || file_exists($value['tmp_name'])) {
+ $result[$key] = $value['type'];
+ } else if (empty($value['options']['ignoreNoFile'])) {
+ throw new Zend_File_Transfer_Exception("The file '{$value['name']}' does not exist");
+ } else {
+ continue;
+ }
+ }
+
+ if (count($result) == 1) {
+ return current($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to detect the mime type of a file
+ *
+ * @param array $value File infos
+ * @return string Mimetype of given file
+ */
+ protected function _detectMimeType($value)
+ {
+ if (file_exists($value['name'])) {
+ $file = $value['name'];
+ } else if (file_exists($value['tmp_name'])) {
+ $file = $value['tmp_name'];
+ } else {
+ return null;
+ }
+
+ if (class_exists('finfo', false)) {
+ $const = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
+ if (!empty($value['options']['magicFile'])) {
+ $mime = @finfo_open($const, $value['options']['magicFile']);
+ }
+
+ if (empty($mime)) {
+ $mime = @finfo_open($const);
+ }
+
+ if (!empty($mime)) {
+ $result = finfo_file($mime, $file);
+ }
+
+ unset($mime);
+ }
+
+ if (empty($result) && (function_exists('mime_content_type')
+ && ini_get('mime_magic.magicfile'))) {
+ $result = mime_content_type($file);
+ }
+
+ if (empty($result)) {
+ $result = 'application/octet-stream';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the formatted size
+ *
+ * @param integer $size
+ * @return string
+ */
+ protected static function _toByteString($size)
+ {
+ $sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
+ for ($i=0; $size >= 1024 && $i < 9; $i++) {
+ $size /= 1024;
+ }
+
+ return round($size, 2) . $sizes[$i];
+ }
+
+ /**
+ * Internal function to filter all given files
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return boolean False on error
+ */
+ protected function _filter($files = null)
+ {
+ $check = $this->_getFiles($files);
+ foreach ($check as $name => $content) {
+ if (array_key_exists('filters', $content)) {
+ foreach ($content['filters'] as $class) {
+ $filter = $this->_filters[$class];
+ try {
+ $result = $filter->filter($this->getFileName($name));
+
+ $this->_files[$name]['destination'] = dirname($result);
+ $this->_files[$name]['name'] = basename($result);
+ } catch (Zend_Filter_Exception $e) {
+ $this->_messages += array($e->getMessage());
+ }
+ }
+ }
+ }
+
+ if (count($this->_messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine system TMP directory and detect if we have read access
+ *
+ * @return string
+ * @throws Zend_File_Transfer_Exception if unable to determine directory
+ */
+ protected function _getTmpDir()
+ {
+ if (null === $this->_tmpDir) {
+ $tmpdir = array();
+ if (function_exists('sys_get_temp_dir')) {
+ $tmpdir[] = sys_get_temp_dir();
+ }
+
+ if (!empty($_ENV['TMP'])) {
+ $tmpdir[] = realpath($_ENV['TMP']);
+ }
+
+ if (!empty($_ENV['TMPDIR'])) {
+ $tmpdir[] = realpath($_ENV['TMPDIR']);
+ }
+
+ if (!empty($_ENV['TEMP'])) {
+ $tmpdir[] = realpath($_ENV['TEMP']);
+ }
+
+ $upload = ini_get('upload_tmp_dir');
+ if ($upload) {
+ $tmpdir[] = realpath($upload);
+ }
+
+ foreach($tmpdir as $directory) {
+ if ($this->_isPathWriteable($directory)) {
+ $this->_tmpDir = $directory;
+ }
+ }
+
+ if (empty($this->_tmpDir)) {
+ // Attemp to detect by creating a temporary file
+ $tempFile = tempnam(md5(uniqid(rand(), TRUE)), '');
+ if ($tempFile) {
+ $this->_tmpDir = realpath(dirname($tempFile));
+ unlink($tempFile);
+ } else {
+ throw new Zend_File_Transfer_Exception('Could not determine a temporary directory');
+ }
+ }
+
+ $this->_tmpDir = rtrim($this->_tmpDir, "/\\");
+ }
+ return $this->_tmpDir;
+ }
+
+ /**
+ * Tries to detect if we can read and write to the given path
+ *
+ * @param string $path
+ * @return bool
+ */
+ protected function _isPathWriteable($path)
+ {
+ $tempFile = rtrim($path, "/\\");
+ $tempFile .= '/' . 'test.1';
+
+ $result = @file_put_contents($tempFile, 'TEST');
+
+ if ($result == false) {
+ return false;
+ }
+
+ $result = @unlink($tempFile);
+
+ if ($result == false) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns found files based on internal file array and given files
+ *
+ * @param string|array $files (Optional) Files to return
+ * @param boolean $names (Optional) Returns only names on true, else complete info
+ * @param boolean $noexception (Optional) Allows throwing an exception, otherwise returns an empty array
+ * @return array Found files
+ * @throws Zend_File_Transfer_Exception On false filename
+ */
+ protected function _getFiles($files, $names = false, $noexception = false)
+ {
+ $check = array();
+
+ if (is_string($files)) {
+ $files = array($files);
+ }
+
+ if (is_array($files)) {
+ foreach ($files as $find) {
+ $found = array();
+ foreach ($this->_files as $file => $content) {
+ if (!isset($content['name'])) {
+ continue;
+ }
+
+ if (($content['name'] === $find) && isset($content['multifiles'])) {
+ foreach ($content['multifiles'] as $multifile) {
+ $found[] = $multifile;
+ }
+ break;
+ }
+
+ if ($file === $find) {
+ $found[] = $file;
+ break;
+ }
+
+ if ($content['name'] === $find) {
+ $found[] = $file;
+ break;
+ }
+ }
+
+ if (empty($found)) {
+ if ($noexception !== false) {
+ return array();
+ }
+
+ throw new Zend_File_Transfer_Exception(sprintf('The file transfer adapter can not find "%s"', $find));
+ }
+
+ foreach ($found as $checked) {
+ $check[$checked] = $this->_files[$checked];
+ }
+ }
+ }
+
+ if ($files === null) {
+ $check = $this->_files;
+ $keys = array_keys($check);
+ foreach ($keys as $key) {
+ if (isset($check[$key]['multifiles'])) {
+ unset($check[$key]);
+ }
+ }
+ }
+
+ if ($names) {
+ $check = array_keys($check);
+ }
+
+ return $check;
+ }
+
+ /**
+ * Retrieve internal identifier for a named validator
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _getValidatorIdentifier($name)
+ {
+ if (array_key_exists($name, $this->_validators)) {
+ return $name;
+ }
+
+ foreach (array_keys($this->_validators) as $test) {
+ if (preg_match('/' . preg_quote($name) . '$/i', $test)) {
+ return $test;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve internal identifier for a named filter
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _getFilterIdentifier($name)
+ {
+ if (array_key_exists($name, $this->_filters)) {
+ return $name;
+ }
+
+ foreach (array_keys($this->_filters) as $test) {
+ if (preg_match('/' . preg_quote($name) . '$/i', $test)) {
+ return $test;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/File/Transfer/Adapter/Http.php b/library/vendor/Zend/File/Transfer/Adapter/Http.php
new file mode 100644
index 0000000..08e56c0
--- /dev/null
+++ b/library/vendor/Zend/File/Transfer/Adapter/Http.php
@@ -0,0 +1,480 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_File_Transfer_Adapter_Abstract
+ */
+
+/**
+ * File transfer adapter class for the HTTP protocol
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_File_Transfer_Adapter_Http extends Zend_File_Transfer_Adapter_Abstract
+{
+ protected static $_callbackApc = 'apc_fetch';
+ protected static $_callbackUploadProgress = 'uploadprogress_get_info';
+
+ /**
+ * Constructor for Http File Transfers
+ *
+ * @param array $options OPTIONAL Options to set
+ */
+ public function __construct($options = array())
+ {
+ if (ini_get('file_uploads') == false) {
+ throw new Zend_File_Transfer_Exception('File uploads are not allowed in your php config!');
+ }
+
+ $this->setOptions($options);
+ $this->_prepareFiles();
+ $this->addValidator('Upload', false, $this->_files);
+ }
+
+ /**
+ * Sets a validator for the class, erasing all previous set
+ *
+ * @param string|array $validator Validator to set
+ * @param string|array $files Files to limit this validator to
+ * @return Zend_File_Transfer_Adapter
+ */
+ public function setValidators(array $validators, $files = null)
+ {
+ $this->clearValidators();
+ return $this->addValidators($validators, $files);
+ }
+
+ /**
+ * Remove an individual validator
+ *
+ * @param string $name
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function removeValidator($name)
+ {
+ if ($name == 'Upload') {
+ return $this;
+ }
+
+ return parent::removeValidator($name);
+ }
+
+ /**
+ * Remove an individual validator
+ *
+ * @param string $name
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function clearValidators()
+ {
+ parent::clearValidators();
+ $this->addValidator('Upload', false, $this->_files);
+
+ return $this;
+ }
+
+ /**
+ * Send the file to the client (Download)
+ *
+ * @param string|array $options Options for the file(s) to send
+ * @return void
+ * @throws Zend_File_Transfer_Exception Not implemented
+ */
+ public function send($options = null)
+ {
+ throw new Zend_File_Transfer_Exception('Method not implemented');
+ }
+
+ /**
+ * Checks if the files are valid
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return boolean True if all checks are valid
+ */
+ public function isValid($files = null)
+ {
+ // Workaround for WebServer not conforming HTTP and omitting CONTENT_LENGTH
+ $content = 0;
+ if (isset($_SERVER['CONTENT_LENGTH'])) {
+ $content = $_SERVER['CONTENT_LENGTH'];
+ } else if (!empty($_POST)) {
+ $content = serialize($_POST);
+ }
+
+ // Workaround for a PHP error returning empty $_FILES when form data exceeds php settings
+ if (empty($this->_files) && ($content > 0)) {
+ if (is_array($files)) {
+ if (0 === count($files)) {
+ return false;
+ }
+
+ $files = current($files);
+ }
+
+ $temp = array($files => array(
+ 'name' => $files,
+ 'error' => 1));
+ $validator = $this->_validators['Zend_Validate_File_Upload'];
+ $validator->setFiles($temp)
+ ->isValid($files, null);
+ $this->_messages += $validator->getMessages();
+ return false;
+ }
+
+ return parent::isValid($files);
+ }
+
+ /**
+ * Receive the file from the client (Upload)
+ *
+ * @param string|array $files (Optional) Files to receive
+ * @return bool
+ */
+ public function receive($files = null)
+ {
+ if (!$this->isValid($files)) {
+ return false;
+ }
+
+ $check = $this->_getFiles($files);
+ foreach ($check as $file => $content) {
+ if (!$content['received']) {
+ $directory = '';
+ $destination = $this->getDestination($file);
+ if ($destination !== null) {
+ $directory = $destination . DIRECTORY_SEPARATOR;
+ }
+
+ $filename = $directory . $content['name'];
+ $rename = $this->getFilter('Rename');
+ if ($rename !== null) {
+ $tmp = $rename->getNewName($content['tmp_name']);
+ if ($tmp != $content['tmp_name']) {
+ $filename = $tmp;
+ }
+
+ if (dirname($filename) == '.') {
+ $filename = $directory . $filename;
+ }
+
+ $key = array_search(get_class($rename), $this->_files[$file]['filters']);
+ unset($this->_files[$file]['filters'][$key]);
+ }
+
+ // Should never return false when it's tested by the upload validator
+ if (!move_uploaded_file($content['tmp_name'], $filename)) {
+ if ($content['options']['ignoreNoFile']) {
+ $this->_files[$file]['received'] = true;
+ $this->_files[$file]['filtered'] = true;
+ continue;
+ }
+
+ $this->_files[$file]['received'] = false;
+ return false;
+ }
+
+ if ($rename !== null) {
+ $this->_files[$file]['destination'] = dirname($filename);
+ $this->_files[$file]['name'] = basename($filename);
+ }
+
+ $this->_files[$file]['tmp_name'] = $filename;
+ $this->_files[$file]['received'] = true;
+ }
+
+ if (!$content['filtered']) {
+ if (!$this->_filter($file)) {
+ $this->_files[$file]['filtered'] = false;
+ return false;
+ }
+
+ $this->_files[$file]['filtered'] = true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the file was already sent
+ *
+ * @param string|array $file Files to check
+ * @return bool
+ * @throws Zend_File_Transfer_Exception Not implemented
+ */
+ public function isSent($files = null)
+ {
+ throw new Zend_File_Transfer_Exception('Method not implemented');
+ }
+
+ /**
+ * Checks if the file was already received
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool
+ */
+ public function isReceived($files = null)
+ {
+ $files = $this->_getFiles($files, false, true);
+ if (empty($files)) {
+ return false;
+ }
+
+ foreach ($files as $content) {
+ if ($content['received'] !== true) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the file was already filtered
+ *
+ * @param string|array $files (Optional) Files to check
+ * @return bool
+ */
+ public function isFiltered($files = null)
+ {
+ $files = $this->_getFiles($files, false, true);
+ if (empty($files)) {
+ return false;
+ }
+
+ foreach ($files as $content) {
+ if ($content['filtered'] !== true) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Has a file been uploaded ?
+ *
+ * @param array|string|null $file
+ * @return bool
+ */
+ public function isUploaded($files = null)
+ {
+ $files = $this->_getFiles($files, false, true);
+ if (empty($files)) {
+ return false;
+ }
+
+ foreach ($files as $file) {
+ if (empty($file['name'])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the actual progress of file up-/downloads
+ *
+ * @param string $id The upload to get the progress for
+ * @return array|null
+ */
+ public static function getProgress($id = null)
+ {
+ if (!function_exists('apc_fetch') and !function_exists('uploadprogress_get_info')) {
+ throw new Zend_File_Transfer_Exception('Neither APC nor uploadprogress extension installed');
+ }
+
+ $session = 'Zend_File_Transfer_Adapter_Http_ProgressBar';
+ $status = array(
+ 'total' => 0,
+ 'current' => 0,
+ 'rate' => 0,
+ 'message' => '',
+ 'done' => false
+ );
+
+ if (is_array($id)) {
+ if (isset($id['progress'])) {
+ $adapter = $id['progress'];
+ }
+
+ if (isset($id['session'])) {
+ $session = $id['session'];
+ }
+
+ if (isset($id['id'])) {
+ $id = $id['id'];
+ } else {
+ unset($id);
+ }
+ }
+
+ if (!empty($id) && (($id instanceof Zend_ProgressBar_Adapter) || ($id instanceof Zend_ProgressBar))) {
+ $adapter = $id;
+ unset($id);
+ }
+
+ if (empty($id)) {
+ if (!isset($_GET['progress_key'])) {
+ $status['message'] = 'No upload in progress';
+ $status['done'] = true;
+ } else {
+ $id = $_GET['progress_key'];
+ }
+ }
+
+ if (!empty($id)) {
+ if (self::isApcAvailable()) {
+
+ $call = call_user_func(self::$_callbackApc, ini_get('apc.rfc1867_prefix') . $id);
+ if (is_array($call)) {
+ $status = $call + $status;
+ }
+ } else if (self::isUploadProgressAvailable()) {
+ $call = call_user_func(self::$_callbackUploadProgress, $id);
+ if (is_array($call)) {
+ $status = $call + $status;
+ $status['total'] = $status['bytes_total'];
+ $status['current'] = $status['bytes_uploaded'];
+ $status['rate'] = $status['speed_average'];
+ if ($status['total'] == $status['current']) {
+ $status['done'] = true;
+ }
+ }
+ }
+
+ if (!is_array($call)) {
+ $status['done'] = true;
+ $status['message'] = 'Failure while retrieving the upload progress';
+ } else if (!empty($status['cancel_upload'])) {
+ $status['done'] = true;
+ $status['message'] = 'The upload has been canceled';
+ } else {
+ $status['message'] = self::_toByteString($status['current']) . " - " . self::_toByteString($status['total']);
+ }
+
+ $status['id'] = $id;
+ }
+
+ if (isset($adapter) && isset($status['id'])) {
+ if ($adapter instanceof Zend_ProgressBar_Adapter) {
+ $adapter = new Zend_ProgressBar($adapter, 0, $status['total'], $session);
+ }
+
+ if (!($adapter instanceof Zend_ProgressBar)) {
+ throw new Zend_File_Transfer_Exception('Unknown Adapter given');
+ }
+
+ if ($status['done']) {
+ $adapter->finish();
+ } else {
+ $adapter->update($status['current'], $status['message']);
+ }
+
+ $status['progress'] = $adapter;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Checks the APC extension for progress information
+ *
+ * @return boolean
+ */
+ public static function isApcAvailable()
+ {
+ return (bool) ini_get('apc.enabled') && (bool) ini_get('apc.rfc1867') && is_callable(self::$_callbackApc);
+ }
+
+ /**
+ * Checks the UploadProgress extension for progress information
+ *
+ * @return boolean
+ */
+ public static function isUploadProgressAvailable()
+ {
+ return is_callable(self::$_callbackUploadProgress);
+ }
+
+ /**
+ * Prepare the $_FILES array to match the internal syntax of one file per entry
+ *
+ * @param array $files
+ * @return array
+ */
+ protected function _prepareFiles()
+ {
+ $this->_files = array();
+ foreach ($_FILES as $form => $content) {
+ if (is_array($content['name'])) {
+ foreach ($content as $param => $file) {
+ foreach ($file as $number => $target) {
+ $this->_files[$form . '_' . $number . '_'][$param] = $target;
+ $this->_files[$form]['multifiles'][$number] = $form . '_' . $number . '_';
+ }
+ }
+
+ $this->_files[$form]['name'] = $form;
+ foreach($this->_files[$form]['multifiles'] as $key => $value) {
+ $this->_files[$value]['options'] = $this->_options;
+ $this->_files[$value]['validated'] = false;
+ $this->_files[$value]['received'] = false;
+ $this->_files[$value]['filtered'] = false;
+
+ $mimetype = $this->_detectMimeType($this->_files[$value]);
+ $this->_files[$value]['type'] = $mimetype;
+
+ $filesize = $this->_detectFileSize($this->_files[$value]);
+ $this->_files[$value]['size'] = $filesize;
+
+ if ($this->_options['detectInfos']) {
+ $_FILES[$form]['type'][$key] = $mimetype;
+ $_FILES[$form]['size'][$key] = $filesize;
+ }
+ }
+ } else {
+ $this->_files[$form] = $content;
+ $this->_files[$form]['options'] = $this->_options;
+ $this->_files[$form]['validated'] = false;
+ $this->_files[$form]['received'] = false;
+ $this->_files[$form]['filtered'] = false;
+
+ $mimetype = $this->_detectMimeType($this->_files[$form]);
+ $this->_files[$form]['type'] = $mimetype;
+
+ $filesize = $this->_detectFileSize($this->_files[$form]);
+ $this->_files[$form]['size'] = $filesize;
+
+ if ($this->_options['detectInfos']) {
+ $_FILES[$form]['type'] = $mimetype;
+ $_FILES[$form]['size'] = $filesize;
+ }
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/File/Transfer/Exception.php b/library/vendor/Zend/File/Transfer/Exception.php
new file mode 100644
index 0000000..27f030c
--- /dev/null
+++ b/library/vendor/Zend/File/Transfer/Exception.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Exception
+ */
+
+/**
+ * Exception class for Zend_File_Transfer
+ *
+ * @category Zend
+ * @package Zend_File_Transfer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_File_Transfer_Exception extends Zend_Exception
+{
+ protected $_fileerror = null;
+
+ public function __construct($message, $fileerror = 0)
+ {
+ $this->_fileerror = $fileerror;
+ parent::__construct($message);
+ }
+
+ /**
+ * Returns the transfer error code for the exception
+ * This is not the exception code !!!
+ *
+ * @return integer
+ */
+ public function getFileError()
+ {
+ return $this->_fileerror;
+ }
+}
diff --git a/library/vendor/Zend/Filter.php b/library/vendor/Zend/Filter.php
new file mode 100644
index 0000000..d05bdf8
--- /dev/null
+++ b/library/vendor/Zend/Filter.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter implements Zend_Filter_Interface
+{
+
+ const CHAIN_APPEND = 'append';
+ const CHAIN_PREPEND = 'prepend';
+
+ /**
+ * Filter chain
+ *
+ * @var array
+ */
+ protected $_filters = array();
+
+ /**
+ * Default Namespaces
+ *
+ * @var array
+ */
+ protected static $_defaultNamespaces = array();
+
+ /**
+ * Adds a filter to the chain
+ *
+ * @param Zend_Filter_Interface $filter
+ * @param string $placement
+ * @return Zend_Filter Provides a fluent interface
+ */
+ public function addFilter(Zend_Filter_Interface $filter, $placement = self::CHAIN_APPEND)
+ {
+ if ($placement == self::CHAIN_PREPEND) {
+ array_unshift($this->_filters, $filter);
+ } else {
+ $this->_filters[] = $filter;
+ }
+ return $this;
+ }
+
+ /**
+ * Add a filter to the end of the chain
+ *
+ * @param Zend_Filter_Interface $filter
+ * @return Zend_Filter Provides a fluent interface
+ */
+ public function appendFilter(Zend_Filter_Interface $filter)
+ {
+ return $this->addFilter($filter, self::CHAIN_APPEND);
+ }
+
+ /**
+ * Add a filter to the start of the chain
+ *
+ * @param Zend_Filter_Interface $filter
+ * @return Zend_Filter Provides a fluent interface
+ */
+ public function prependFilter(Zend_Filter_Interface $filter)
+ {
+ return $this->addFilter($filter, self::CHAIN_PREPEND);
+ }
+
+ /**
+ * Get all the filters
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->_filters;
+ }
+
+ /**
+ * Returns $value filtered through each filter in the chain
+ *
+ * Filters are run in the order in which they were added to the chain (FIFO)
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public function filter($value)
+ {
+ $valueFiltered = $value;
+ foreach ($this->_filters as $filter) {
+ $valueFiltered = $filter->filter($valueFiltered);
+ }
+ return $valueFiltered;
+ }
+
+ /**
+ * Returns the set default namespaces
+ *
+ * @return array
+ */
+ public static function getDefaultNamespaces()
+ {
+ return self::$_defaultNamespaces;
+ }
+
+ /**
+ * Sets new default namespaces
+ *
+ * @param array|string $namespace
+ * @return null
+ */
+ public static function setDefaultNamespaces($namespace)
+ {
+ if (!is_array($namespace)) {
+ $namespace = array((string) $namespace);
+ }
+
+ self::$_defaultNamespaces = $namespace;
+ }
+
+ /**
+ * Adds a new default namespace
+ *
+ * @param array|string $namespace
+ * @return null
+ */
+ public static function addDefaultNamespaces($namespace)
+ {
+ if (!is_array($namespace)) {
+ $namespace = array((string) $namespace);
+ }
+
+ self::$_defaultNamespaces = array_unique(array_merge(self::$_defaultNamespaces, $namespace));
+ }
+
+ /**
+ * Returns true when defaultNamespaces are set
+ *
+ * @return boolean
+ */
+ public static function hasDefaultNamespaces()
+ {
+ return (!empty(self::$_defaultNamespaces));
+ }
+
+ /**
+ * @deprecated
+ * @see Zend_Filter::filterStatic()
+ *
+ * @param mixed $value
+ * @param string $classBaseName
+ * @param array $args OPTIONAL
+ * @param array|string $namespaces OPTIONAL
+ * @return mixed
+ * @throws Zend_Filter_Exception
+ */
+ public static function get($value, $classBaseName, array $args = array(), $namespaces = array())
+ {
+ trigger_error(
+ 'Zend_Filter::get() is deprecated as of 1.9.0; please update your code to utilize Zend_Filter::filterStatic()',
+ E_USER_NOTICE
+ );
+
+ return self::filterStatic($value, $classBaseName, $args, $namespaces);
+ }
+
+ /**
+ * Returns a value filtered through a specified filter class, without requiring separate
+ * instantiation of the filter object.
+ *
+ * The first argument of this method is a data input value, that you would have filtered.
+ * The second argument is a string, which corresponds to the basename of the filter class,
+ * relative to the Zend_Filter namespace. This method automatically loads the class,
+ * creates an instance, and applies the filter() method to the data input. You can also pass
+ * an array of constructor arguments, if they are needed for the filter class.
+ *
+ * @param mixed $value
+ * @param string $classBaseName
+ * @param array $args OPTIONAL
+ * @param array|string $namespaces OPTIONAL
+ * @return mixed
+ * @throws Zend_Filter_Exception
+ */
+ public static function filterStatic($value, $classBaseName, array $args = array(), $namespaces = array())
+ {
+ $namespaces = array_merge((array) $namespaces, self::$_defaultNamespaces, array('Zend_Filter'));
+ foreach ($namespaces as $namespace) {
+ $className = $namespace . '_' . ucfirst($classBaseName);
+ if (!class_exists($className, false)) {
+ try {
+ $file = str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
+ if (Zend_Loader::isReadable($file)) {
+ Zend_Loader::loadClass($className);
+ } else {
+ continue;
+ }
+ } catch (Zend_Exception $ze) {
+ continue;
+ }
+ }
+
+ $class = new ReflectionClass($className);
+ if ($class->implementsInterface('Zend_Filter_Interface')) {
+ if ($class->hasMethod('__construct')) {
+ $object = $class->newInstanceArgs(array_values($args));
+ } else {
+ $object = $class->newInstance();
+ }
+ return $object->filter($value);
+ }
+ }
+ throw new Zend_Filter_Exception("Filter class not found from basename '$classBaseName'");
+ }
+}
diff --git a/library/vendor/Zend/Filter/Alnum.php b/library/vendor/Zend/Filter/Alnum.php
new file mode 100644
index 0000000..c3928a0
--- /dev/null
+++ b/library/vendor/Zend/Filter/Alnum.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+/**
+ * @see Zend_Locale
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Alnum implements Zend_Filter_Interface
+{
+ /**
+ * Whether to allow white space characters; off by default
+ *
+ * @var boolean
+ * @deprecated
+ */
+ public $allowWhiteSpace;
+
+ /**
+ * Is PCRE is compiled with UTF-8 and Unicode support
+ *
+ * @var mixed
+ **/
+ protected static $_unicodeEnabled;
+
+ /**
+ * Locale in browser.
+ *
+ * @var Zend_Locale object
+ */
+ protected $_locale;
+
+ /**
+ * The Alphabet means english alphabet.
+ *
+ * @var boolean
+ */
+ protected static $_meansEnglishAlphabet;
+
+ /**
+ * Sets default option values for this instance
+ *
+ * @param boolean $allowWhiteSpace
+ * @return void
+ */
+ public function __construct($allowWhiteSpace = false)
+ {
+ if ($allowWhiteSpace instanceof Zend_Config) {
+ $allowWhiteSpace = $allowWhiteSpace->toArray();
+ } else if (is_array($allowWhiteSpace)) {
+ if (array_key_exists('allowwhitespace', $allowWhiteSpace)) {
+ $allowWhiteSpace = $allowWhiteSpace['allowwhitespace'];
+ } else {
+ $allowWhiteSpace = false;
+ }
+ }
+
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ if (null === self::$_unicodeEnabled) {
+ self::$_unicodeEnabled = (@preg_match('/\pL/u', 'a')) ? true : false;
+ }
+
+ if (null === self::$_meansEnglishAlphabet) {
+ $this->_locale = new Zend_Locale('auto');
+ self::$_meansEnglishAlphabet = in_array($this->_locale->getLanguage(),
+ array('ja', 'ko', 'zh')
+ );
+ }
+
+ }
+
+ /**
+ * Returns the allowWhiteSpace option
+ *
+ * @return boolean
+ */
+ public function getAllowWhiteSpace()
+ {
+ return $this->allowWhiteSpace;
+ }
+
+ /**
+ * Sets the allowWhiteSpace option
+ *
+ * @param boolean $allowWhiteSpace
+ * @return Zend_Filter_Alnum Provides a fluent interface
+ */
+ public function setAllowWhiteSpace($allowWhiteSpace)
+ {
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, removing all but alphabetic and digit characters
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $whiteSpace = $this->allowWhiteSpace ? '\s' : '';
+ if (!self::$_unicodeEnabled) {
+ // POSIX named classes are not supported, use alternative a-zA-Z0-9 match
+ $pattern = '/[^a-zA-Z0-9' . $whiteSpace . ']/';
+ } else if (self::$_meansEnglishAlphabet) {
+ //The Alphabet means english alphabet.
+ $pattern = '/[^a-zA-Z0-9' . $whiteSpace . ']/u';
+ } else {
+ //The Alphabet means each language's alphabet.
+ $pattern = '/[^\p{L}\p{N}' . $whiteSpace . ']/u';
+ }
+
+ return preg_replace($pattern, '', (string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Alpha.php b/library/vendor/Zend/Filter/Alpha.php
new file mode 100644
index 0000000..5c1a22c
--- /dev/null
+++ b/library/vendor/Zend/Filter/Alpha.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+/**
+ * @see Zend_Locale
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Alpha implements Zend_Filter_Interface
+{
+ /**
+ * Whether to allow white space characters; off by default
+ *
+ * @var boolean
+ * @deprecated
+ */
+ public $allowWhiteSpace;
+
+ /**
+ * Is PCRE is compiled with UTF-8 and Unicode support
+ *
+ * @var mixed
+ **/
+ protected static $_unicodeEnabled;
+
+ /**
+ * Locale in browser.
+ *
+ * @var Zend_Locale object
+ */
+ protected $_locale;
+
+ /**
+ * The Alphabet means english alphabet.
+ *
+ * @var boolean
+ */
+ protected static $_meansEnglishAlphabet;
+
+ /**
+ * Sets default option values for this instance
+ *
+ * @param boolean $allowWhiteSpace
+ * @return void
+ */
+ public function __construct($allowWhiteSpace = false)
+ {
+ if ($allowWhiteSpace instanceof Zend_Config) {
+ $allowWhiteSpace = $allowWhiteSpace->toArray();
+ } else if (is_array($allowWhiteSpace)) {
+ if (array_key_exists('allowwhitespace', $allowWhiteSpace)) {
+ $allowWhiteSpace = $allowWhiteSpace['allowwhitespace'];
+ } else {
+ $allowWhiteSpace = false;
+ }
+ }
+
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ if (null === self::$_unicodeEnabled) {
+ self::$_unicodeEnabled = (@preg_match('/\pL/u', 'a')) ? true : false;
+ }
+
+ if (null === self::$_meansEnglishAlphabet) {
+ $this->_locale = new Zend_Locale('auto');
+ self::$_meansEnglishAlphabet = in_array($this->_locale->getLanguage(),
+ array('ja', 'ko', 'zh')
+ );
+ }
+
+ }
+
+ /**
+ * Returns the allowWhiteSpace option
+ *
+ * @return boolean
+ */
+ public function getAllowWhiteSpace()
+ {
+ return $this->allowWhiteSpace;
+ }
+
+ /**
+ * Sets the allowWhiteSpace option
+ *
+ * @param boolean $allowWhiteSpace
+ * @return Zend_Filter_Alpha Provides a fluent interface
+ */
+ public function setAllowWhiteSpace($allowWhiteSpace)
+ {
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, removing all but alphabetic characters
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $whiteSpace = $this->allowWhiteSpace ? '\s' : '';
+ if (!self::$_unicodeEnabled) {
+ // POSIX named classes are not supported, use alternative a-zA-Z match
+ $pattern = '/[^a-zA-Z' . $whiteSpace . ']/';
+ } else if (self::$_meansEnglishAlphabet) {
+ //The Alphabet means english alphabet.
+ $pattern = '/[^a-zA-Z' . $whiteSpace . ']/u';
+ } else {
+ //The Alphabet means each language's alphabet.
+ $pattern = '/[^\p{L}' . $whiteSpace . ']/u';
+ }
+
+ return preg_replace($pattern, '', (string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/BaseName.php b/library/vendor/Zend/Filter/BaseName.php
new file mode 100644
index 0000000..41ab68a
--- /dev/null
+++ b/library/vendor/Zend/Filter/BaseName.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_BaseName implements Zend_Filter_Interface
+{
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns basename($value)
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ return basename((string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Boolean.php b/library/vendor/Zend/Filter/Boolean.php
new file mode 100644
index 0000000..c48a3d6
--- /dev/null
+++ b/library/vendor/Zend/Filter/Boolean.php
@@ -0,0 +1,369 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Boolean implements Zend_Filter_Interface
+{
+ const BOOLEAN = 1;
+ const INTEGER = 2;
+ const FLOAT = 4;
+ const STRING = 8;
+ const ZERO = 16;
+ const EMPTY_ARRAY = 32;
+ const NULL = 64;
+ const PHP = 127;
+ const FALSE_STRING = 128;
+ const YES = 256;
+ const ALL = 511;
+
+ protected $_constants = array(
+ self::BOOLEAN => 'boolean',
+ self::INTEGER => 'integer',
+ self::FLOAT => 'float',
+ self::STRING => 'string',
+ self::ZERO => 'zero',
+ self::EMPTY_ARRAY => 'array',
+ self::NULL => 'null',
+ self::PHP => 'php',
+ self::FALSE_STRING => 'false',
+ self::YES => 'yes',
+ self::ALL => 'all',
+ );
+
+ /**
+ * Internal type to detect
+ *
+ * @var integer
+ */
+ protected $_type = self::PHP;
+
+ /**
+ * Internal locale
+ *
+ * @var array
+ */
+ protected $_locale = array('auto');
+
+ /**
+ * Internal mode
+ *
+ * @var boolean
+ */
+ protected $_casting = true;
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $options OPTIONAL
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+ if (!empty($options)) {
+ $temp['type'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['casting'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['locale'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (array_key_exists('type', $options)) {
+ $this->setType($options['type']);
+ }
+
+ if (array_key_exists('casting', $options)) {
+ $this->setCasting($options['casting']);
+ }
+
+ if (array_key_exists('locale', $options)) {
+ $this->setLocale($options['locale']);
+ }
+ }
+
+ /**
+ * Returns the set null types
+ *
+ * @return int
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Set the null types
+ *
+ * @param integer|array $type
+ * @throws Zend_Filter_Exception
+ * @return Zend_Filter_Boolean
+ */
+ public function setType($type = null)
+ {
+ if (is_array($type)) {
+ $detected = 0;
+ foreach($type as $value) {
+ if (is_int($value)) {
+ $detected += $value;
+ } elseif (in_array($value, $this->_constants)) {
+ $detected += array_search($value, $this->_constants);
+ }
+ }
+
+ $type = $detected;
+ } elseif (is_string($type) && in_array($type, $this->_constants)) {
+ $type = array_search($type, $this->_constants);
+ }
+
+ if (!is_int($type) || ($type < 0) || ($type > self::ALL)) {
+ throw new Zend_Filter_Exception('Unknown type');
+ }
+
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * Returns the set locale
+ *
+ * @return array
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Set the locales which are accepted
+ *
+ * @param string|array|Zend_Locale $locale
+ * @throws Zend_Filter_Exception
+ * @return Zend_Filter_Boolean
+ */
+ public function setLocale($locale = null)
+ {
+ if (is_string($locale)) {
+ $locale = array($locale);
+ } elseif ($locale instanceof Zend_Locale) {
+ $locale = array($locale->toString());
+ } elseif (!is_array($locale)) {
+ throw new Zend_Filter_Exception('Locale has to be string, array or an instance of Zend_Locale');
+ }
+
+ foreach ($locale as $single) {
+ if (!Zend_Locale::isLocale($single)) {
+ throw new Zend_Filter_Exception("Unknown locale '$single'");
+ }
+ }
+
+ $this->_locale = $locale;
+ return $this;
+ }
+
+ /**
+ * Returns the casting option
+ *
+ * @return boolean
+ */
+ public function getCasting()
+ {
+ return $this->_casting;
+ }
+
+ /**
+ * Set the working mode
+ *
+ * @param boolean $locale When true this filter works like cast
+ * When false it recognises only true and false
+ * and all other values are returned as is
+ * @throws Zend_Filter_Exception
+ * @return Zend_Filter_Boolean
+ */
+ public function setCasting($casting = true)
+ {
+ $this->_casting = (boolean) $casting;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns a boolean representation of $value
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $type = $this->getType();
+ $casting = $this->getCasting();
+
+ // STRING YES (Localized)
+ if ($type >= self::YES) {
+ $type -= self::YES;
+ if (is_string($value)) {
+ $locales = $this->getLocale();
+ foreach ($locales as $locale) {
+ if ($this->_getLocalizedQuestion($value, false, $locale) === false) {
+ return false;
+ }
+
+ if (!$casting && ($this->_getLocalizedQuestion($value, true, $locale) === true)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ // STRING FALSE ('false')
+ if ($type >= self::FALSE_STRING) {
+ $type -= self::FALSE_STRING;
+ if (is_string($value) && (strtolower($value) == 'false')) {
+ return false;
+ }
+
+ if ((!$casting) && is_string($value) && (strtolower($value) == 'true')) {
+ return true;
+ }
+ }
+
+ // NULL (null)
+ if ($type >= self::NULL) {
+ $type -= self::NULL;
+ if ($value === null) {
+ return false;
+ }
+ }
+
+ // EMPTY_ARRAY (array())
+ if ($type >= self::EMPTY_ARRAY) {
+ $type -= self::EMPTY_ARRAY;
+ if (is_array($value) && ($value == array())) {
+ return false;
+ }
+ }
+
+ // ZERO ('0')
+ if ($type >= self::ZERO) {
+ $type -= self::ZERO;
+ if (is_string($value) && ($value == '0')) {
+ return false;
+ }
+
+ if ((!$casting) && (is_string($value)) && ($value == '1')) {
+ return true;
+ }
+ }
+
+ // STRING ('')
+ if ($type >= self::STRING) {
+ $type -= self::STRING;
+ if (is_string($value) && ($value == '')) {
+ return false;
+ }
+ }
+
+ // FLOAT (0.0)
+ if ($type >= self::FLOAT) {
+ $type -= self::FLOAT;
+ if (is_float($value) && ($value == 0.0)) {
+ return false;
+ }
+
+ if ((!$casting) && is_float($value) && ($value == 1.0)) {
+ return true;
+ }
+ }
+
+ // INTEGER (0)
+ if ($type >= self::INTEGER) {
+ $type -= self::INTEGER;
+ if (is_int($value) && ($value == 0)) {
+ return false;
+ }
+
+ if ((!$casting) && is_int($value) && ($value == 1)) {
+ return true;
+ }
+ }
+
+ // BOOLEAN (false)
+ if ($type >= self::BOOLEAN) {
+ $type -= self::BOOLEAN;
+ if (is_bool($value)) {
+ return $value;
+ }
+ }
+
+ if ($casting) {
+ return true;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Determine the value of a localized string, and compare it to a given value
+ *
+ * @param string $value
+ * @param boolean $yes
+ * @param array $locale
+ * @return boolean
+ */
+ protected function _getLocalizedQuestion($value, $yes, $locale)
+ {
+ if ($yes == true) {
+ $question = 'yes';
+ $return = true;
+ } else {
+ $question = 'no';
+ $return = false;
+ }
+ $str = Zend_Locale::getTranslation($question, 'question', $locale);
+ $str = explode(':', $str);
+ if (!empty($str)) {
+ foreach($str as $no) {
+ if (($no == $value) || (strtolower($no) == strtolower($value))) {
+ return $return;
+ }
+ }
+ }
+ }
+}
diff --git a/library/vendor/Zend/Filter/Callback.php b/library/vendor/Zend/Filter/Callback.php
new file mode 100644
index 0000000..0553edc
--- /dev/null
+++ b/library/vendor/Zend/Filter/Callback.php
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Callback implements Zend_Filter_Interface
+{
+ /**
+ * Callback in a call_user_func format
+ *
+ * @var string|array
+ */
+ protected $_callback = null;
+
+ /**
+ * Default options to set for the filter
+ *
+ * @var mixed
+ */
+ protected $_options = null;
+
+ /**
+ * Constructor
+ *
+ * @param string|array $callback Callback in a call_user_func format
+ * @param mixed $options (Optional) Default options for this filter
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options) || !array_key_exists('callback', $options)) {
+ $options = func_get_args();
+ $temp['callback'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['options'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (!array_key_exists('callback', $options)) {
+ throw new Zend_Filter_Exception('Missing callback to use');
+ }
+
+ $this->setCallback($options['callback']);
+ if (array_key_exists('options', $options)) {
+ $this->setOptions($options['options']);
+ }
+ }
+
+ /**
+ * Returns the set callback
+ *
+ * @return string|array Set callback
+ */
+ public function getCallback()
+ {
+ return $this->_callback;
+ }
+
+ /**
+ * Sets a new callback for this filter
+ *
+ * @param unknown_type $callback
+ * @return unknown
+ */
+ public function setCallback($callback, $options = null)
+ {
+ if (!is_callable($callback)) {
+ throw new Zend_Filter_Exception('Callback can not be accessed');
+ }
+
+ $this->_callback = $callback;
+ $this->setOptions($options);
+ return $this;
+ }
+
+ /**
+ * Returns the set default options
+ *
+ * @return mixed
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets new default options to the callback filter
+ *
+ * @param mixed $options Default options to set
+ * @return Zend_Filter_Callback
+ */
+ public function setOptions($options)
+ {
+ $this->_options = $options;
+ return $this;
+ }
+
+ /**
+ * Calls the filter per callback
+ *
+ * @param mixed $value Options for the set callback
+ * @return mixed Result from the filter which was callbacked
+ */
+ public function filter($value)
+ {
+ $options = array();
+
+ if ($this->_options !== null) {
+ if (!is_array($this->_options)) {
+ $options = array($this->_options);
+ } else {
+ $options = $this->_options;
+ }
+ }
+
+ array_unshift($options, $value);
+
+ return call_user_func_array($this->_callback, $options);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress.php b/library/vendor/Zend/Filter/Compress.php
new file mode 100644
index 0000000..7502966
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * Compresses a given string
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress implements Zend_Filter_Interface
+{
+ /**
+ * Compression adapter
+ */
+ protected $_adapter = 'Gz';
+
+ /**
+ * Compression adapter constructor options
+ */
+ protected $_adapterOptions = array();
+
+ /**
+ * Class constructor
+ *
+ * @param string|array $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+ if (is_string($options)) {
+ $this->setAdapter($options);
+ } elseif ($options instanceof Zend_Filter_Compress_CompressInterface) {
+ $this->setAdapter($options);
+ } elseif (is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set filter setate
+ *
+ * @param array $options
+ * @return Zend_Filter_Compress
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ if ($key == 'options') {
+ $key = 'adapterOptions';
+ }
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the current adapter, instantiating it if necessary
+ *
+ * @return string
+ */
+ public function getAdapter()
+ {
+ if ($this->_adapter instanceof Zend_Filter_Compress_CompressInterface) {
+ return $this->_adapter;
+ }
+
+ $adapter = $this->_adapter;
+ $options = $this->getAdapterOptions();
+ if (!class_exists($adapter)) {
+ if (Zend_Loader::isReadable('Zend/Filter/Compress/' . ucfirst($adapter) . '.php')) {
+ $adapter = 'Zend_Filter_Compress_' . ucfirst($adapter);
+ }
+ Zend_Loader::loadClass($adapter);
+ }
+
+ $this->_adapter = new $adapter($options);
+ if (!$this->_adapter instanceof Zend_Filter_Compress_CompressInterface) {
+ throw new Zend_Filter_Exception("Compression adapter '" . $adapter . "' does not implement Zend_Filter_Compress_CompressInterface");
+ }
+ return $this->_adapter;
+ }
+
+ /**
+ * Retrieve adapter name
+ *
+ * @return string
+ */
+ public function getAdapterName()
+ {
+ return $this->getAdapter()->toString();
+ }
+
+ /**
+ * Sets compression adapter
+ *
+ * @param string|Zend_Filter_Compress_CompressInterface $adapter Adapter to use
+ * @return Zend_Filter_Compress
+ */
+ public function setAdapter($adapter)
+ {
+ if ($adapter instanceof Zend_Filter_Compress_CompressInterface) {
+ $this->_adapter = $adapter;
+ return $this;
+ }
+ if (!is_string($adapter)) {
+ throw new Zend_Filter_Exception('Invalid adapter provided; must be string or instance of Zend_Filter_Compress_CompressInterface');
+ }
+ $this->_adapter = $adapter;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve adapter options
+ *
+ * @return array
+ */
+ public function getAdapterOptions()
+ {
+ return $this->_adapterOptions;
+ }
+
+ /**
+ * Set adapter options
+ *
+ * @param array $options
+ * @return void
+ */
+ public function setAdapterOptions(array $options)
+ {
+ $this->_adapterOptions = $options;
+ return $this;
+ }
+
+ /**
+ * Calls adapter methods
+ *
+ * @param string $method Method to call
+ * @param string|array $options Options for this method
+ */
+ public function __call($method, $options)
+ {
+ $adapter = $this->getAdapter();
+ if (!method_exists($adapter, $method)) {
+ throw new Zend_Filter_Exception("Unknown method '{$method}'");
+ }
+
+ return call_user_func_array(array($adapter, $method), $options);
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Compresses the content $value with the defined settings
+ *
+ * @param string $value Content to compress
+ * @return string The compressed content
+ */
+ public function filter($value)
+ {
+ return $this->getAdapter()->compress($value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/Bz2.php b/library/vendor/Zend/Filter/Compress/Bz2.php
new file mode 100644
index 0000000..d1aab17
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/Bz2.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressAbstract
+ */
+
+/**
+ * Compression adapter for Bz2
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress_Bz2 extends Zend_Filter_Compress_CompressAbstract
+{
+ /**
+ * Compression Options
+ * array(
+ * 'blocksize' => Blocksize to use from 0-9
+ * 'archive' => Archive to use
+ * )
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'blocksize' => 4,
+ 'archive' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array|Zend_Config $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('bz2')) {
+ throw new Zend_Filter_Exception('This filter needs the bz2 extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set blocksize
+ *
+ * @return integer
+ */
+ public function getBlocksize()
+ {
+ return $this->_options['blocksize'];
+ }
+
+ /**
+ * Sets a new blocksize
+ *
+ * @param integer $level
+ * @return Zend_Filter_Compress_Bz2
+ */
+ public function setBlocksize($blocksize)
+ {
+ if (($blocksize < 0) || ($blocksize > 9)) {
+ throw new Zend_Filter_Exception('Blocksize must be between 0 and 9');
+ }
+
+ $this->_options['blocksize'] = (int) $blocksize;
+ return $this;
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->_options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return Zend_Filter_Compress_Bz2
+ */
+ public function setArchive($archive)
+ {
+ $this->_options['archive'] = (string) $archive;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function compress($content)
+ {
+ $archive = $this->getArchive();
+ if (!empty($archive)) {
+ $file = bzopen($archive, 'w');
+ if (!$file) {
+ throw new Zend_Filter_Exception("Error opening the archive '" . $archive . "'");
+ }
+
+ bzwrite($file, $content);
+ bzclose($file);
+ $compressed = true;
+ } else {
+ $compressed = bzcompress($content, $this->getBlocksize());
+ }
+
+ if (is_int($compressed)) {
+ throw new Zend_Filter_Exception('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ if (@file_exists($content)) {
+ $archive = $content;
+ }
+
+ if (@file_exists($archive)) {
+ $file = bzopen($archive, 'r');
+ if (!$file) {
+ throw new Zend_Filter_Exception("Error opening the archive '" . $content . "'");
+ }
+
+ $compressed = bzread($file);
+ bzclose($file);
+ } else {
+ $compressed = bzdecompress($content);
+ }
+
+ if (is_int($compressed)) {
+ throw new Zend_Filter_Exception('Error during decompression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Bz2';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/CompressAbstract.php b/library/vendor/Zend/Filter/Compress/CompressAbstract.php
new file mode 100644
index 0000000..4b0813c
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/CompressAbstract.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressInterface
+ */
+
+/**
+ * Abstract compression adapter
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Filter_Compress_CompressAbstract implements Zend_Filter_Compress_CompressInterface
+{
+ /**
+ * Class constructor
+ *
+ * @param array|Zend_Config $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Returns one or all set options
+ *
+ * @param string $option (Optional) Option to return
+ * @return mixed
+ */
+ public function getOptions($option = null)
+ {
+ if ($option === null) {
+ return $this->_options;
+ }
+
+ if (!array_key_exists($option, $this->_options)) {
+ return null;
+ }
+
+ return $this->_options[$option];
+ }
+
+ /**
+ * Sets all or one option
+ *
+ * @param array $options
+ * @return Zend_Filter_Compress_Bz2
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $option) {
+ $method = 'set' . $key;
+ if (method_exists($this, $method)) {
+ $this->$method($option);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/CompressInterface.php b/library/vendor/Zend/Filter/Compress/CompressInterface.php
new file mode 100644
index 0000000..75f0a40
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/CompressInterface.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Compression interface
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Filter_Compress_CompressInterface
+{
+ /**
+ * Compresses $value with the defined settings
+ *
+ * @param string $value Data to compress
+ * @return string The compressed data
+ */
+ public function compress($value);
+
+ /**
+ * Decompresses $value with the defined settings
+ *
+ * @param string $value Data to decompress
+ * @return string The decompressed data
+ */
+ public function decompress($value);
+
+ /**
+ * Return the adapter name
+ *
+ * @return string
+ */
+ public function toString();
+}
diff --git a/library/vendor/Zend/Filter/Compress/Gz.php b/library/vendor/Zend/Filter/Compress/Gz.php
new file mode 100644
index 0000000..e799a90
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/Gz.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressAbstract
+ */
+
+/**
+ * Compression adapter for Gzip (ZLib)
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress_Gz extends Zend_Filter_Compress_CompressAbstract
+{
+ /**
+ * Compression Options
+ * array(
+ * 'level' => Compression level 0-9
+ * 'mode' => Compression mode, can be 'compress', 'deflate'
+ * 'archive' => Archive to use
+ * )
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'level' => 9,
+ 'mode' => 'compress',
+ 'archive' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array|Zend_Config|null $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('zlib')) {
+ throw new Zend_Filter_Exception('This filter needs the zlib extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set compression level
+ *
+ * @return integer
+ */
+ public function getLevel()
+ {
+ return $this->_options['level'];
+ }
+
+ /**
+ * Sets a new compression level
+ *
+ * @param integer $level
+ * @return Zend_Filter_Compress_Gz
+ */
+ public function setLevel($level)
+ {
+ if (($level < 0) || ($level > 9)) {
+ throw new Zend_Filter_Exception('Level must be between 0 and 9');
+ }
+
+ $this->_options['level'] = (int) $level;
+ return $this;
+ }
+
+ /**
+ * Returns the set compression mode
+ *
+ * @return string
+ */
+ public function getMode()
+ {
+ return $this->_options['mode'];
+ }
+
+ /**
+ * Sets a new compression mode
+ *
+ * @param string $mode Supported are 'compress', 'deflate' and 'file'
+ */
+ public function setMode($mode)
+ {
+ if (($mode != 'compress') && ($mode != 'deflate')) {
+ throw new Zend_Filter_Exception('Given compression mode not supported');
+ }
+
+ $this->_options['mode'] = $mode;
+ return $this;
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->_options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return Zend_Filter_Compress_Gz
+ */
+ public function setArchive($archive)
+ {
+ $this->_options['archive'] = (string) $archive;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function compress($content)
+ {
+ $archive = $this->getArchive();
+ if (!empty($archive)) {
+ $file = gzopen($archive, 'w' . $this->getLevel());
+ if (!$file) {
+ throw new Zend_Filter_Exception("Error opening the archive '" . $this->_options['archive'] . "'");
+ }
+
+ gzwrite($file, $content);
+ gzclose($file);
+ $compressed = true;
+ } else if ($this->_options['mode'] == 'deflate') {
+ $compressed = gzdeflate($content, $this->getLevel());
+ } else {
+ $compressed = gzcompress($content, $this->getLevel());
+ }
+
+ if (!$compressed) {
+ throw new Zend_Filter_Exception('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ $mode = $this->getMode();
+ if (@file_exists($content)) {
+ $archive = $content;
+ }
+
+ if (@file_exists($archive)) {
+ $handler = fopen($archive, "rb");
+ if (!$handler) {
+ throw new Zend_Filter_Exception("Error opening the archive '" . $archive . "'");
+ }
+
+ fseek($handler, -4, SEEK_END);
+ $packet = fread($handler, 4);
+ $bytes = unpack("V", $packet);
+ $size = end($bytes);
+ fclose($handler);
+
+ $file = gzopen($archive, 'r');
+ $compressed = gzread($file, $size);
+ gzclose($file);
+ } else if ($mode == 'deflate') {
+ $compressed = gzinflate($content);
+ } else {
+ $compressed = gzuncompress($content);
+ }
+
+ if (!$compressed) {
+ throw new Zend_Filter_Exception('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Gz';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/Lzf.php b/library/vendor/Zend/Filter/Compress/Lzf.php
new file mode 100644
index 0000000..15ecb47
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/Lzf.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressInterface
+ */
+
+/**
+ * Compression adapter for Lzf
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress_Lzf implements Zend_Filter_Compress_CompressInterface
+{
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ if (!extension_loaded('lzf')) {
+ throw new Zend_Filter_Exception('This filter needs the lzf extension');
+ }
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function compress($content)
+ {
+ $compressed = lzf_compress($content);
+ if (!$compressed) {
+ throw new Zend_Filter_Exception('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function decompress($content)
+ {
+ $compressed = lzf_decompress($content);
+ if (!$compressed) {
+ throw new Zend_Filter_Exception('Error during compression');
+ }
+
+ return $compressed;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Lzf';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/Rar.php b/library/vendor/Zend/Filter/Compress/Rar.php
new file mode 100644
index 0000000..7a76af7
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/Rar.php
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressAbstract
+ */
+
+/**
+ * Compression adapter for Rar
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress_Rar extends Zend_Filter_Compress_CompressAbstract
+{
+ /**
+ * Compression Options
+ * array(
+ * 'callback' => Callback for compression
+ * 'archive' => Archive to use
+ * 'password' => Password to use
+ * 'target' => Target to write the files to
+ * )
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'callback' => null,
+ 'archive' => null,
+ 'password' => null,
+ 'target' => '.',
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('rar')) {
+ throw new Zend_Filter_Exception('This filter needs the rar extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set callback for compression
+ *
+ * @return string
+ */
+ public function getCallback()
+ {
+ return $this->_options['callback'];
+ }
+
+ /**
+ * Sets the callback to use
+ *
+ * @param string $callback
+ * @return Zend_Filter_Compress_Rar
+ */
+ public function setCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Zend_Filter_Exception('Callback can not be accessed');
+ }
+
+ $this->_options['callback'] = $callback;
+ return $this;
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->_options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return Zend_Filter_Compress_Rar
+ */
+ public function setArchive($archive)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $archive);
+ $this->_options['archive'] = (string) $archive;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set password
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->_options['password'];
+ }
+
+ /**
+ * Sets the password to use
+ *
+ * @param string $password
+ * @return Zend_Filter_Compress_Rar
+ */
+ public function setPassword($password)
+ {
+ $this->_options['password'] = (string) $password;
+ return $this;
+ }
+
+ /**
+ * Returns the set targetpath
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->_options['target'];
+ }
+
+ /**
+ * Sets the targetpath to use
+ *
+ * @param string $target
+ * @return Zend_Filter_Compress_Rar
+ */
+ public function setTarget($target)
+ {
+ if (!file_exists(dirname($target))) {
+ throw new Zend_Filter_Exception("The directory '$target' does not exist");
+ }
+
+ $target = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $target);
+ $this->_options['target'] = (string) $target;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string|array $content
+ * @return string
+ */
+ public function compress($content)
+ {
+ $callback = $this->getCallback();
+ if ($callback === null) {
+ throw new Zend_Filter_Exception('No compression callback available');
+ }
+
+ $options = $this->getOptions();
+ unset($options['callback']);
+
+ $result = call_user_func($callback, $options, $content);
+ if ($result !== true) {
+ throw new Zend_Filter_Exception('Error compressing the RAR Archive');
+ }
+
+ return $this->getArchive();
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return boolean
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ if (file_exists($content)) {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ } elseif (empty($archive) || !file_exists($archive)) {
+ throw new Zend_Filter_Exception('RAR Archive not found');
+ }
+
+ $password = $this->getPassword();
+ if ($password !== null) {
+ $archive = rar_open($archive, $password);
+ } else {
+ $archive = rar_open($archive);
+ }
+
+ if (!$archive) {
+ throw new Zend_Filter_Exception("Error opening the RAR Archive");
+ }
+
+ $target = $this->getTarget();
+ if (!is_dir($target)) {
+ $target = dirname($target);
+ }
+
+ $filelist = rar_list($archive);
+ if (!$filelist) {
+ throw new Zend_Filter_Exception("Error reading the RAR Archive");
+ }
+
+ foreach($filelist as $file) {
+ $file->extract($target);
+ }
+
+ rar_close($archive);
+ return true;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Rar';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/Tar.php b/library/vendor/Zend/Filter/Compress/Tar.php
new file mode 100644
index 0000000..4359b64
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/Tar.php
@@ -0,0 +1,234 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressAbstract
+ */
+
+/**
+ * Compression adapter for Tar
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress_Tar extends Zend_Filter_Compress_CompressAbstract
+{
+ /**
+ * Compression Options
+ * array(
+ * 'archive' => Archive to use
+ * 'target' => Target to write the files to
+ * )
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'archive' => null,
+ 'target' => '.',
+ 'mode' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param array $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if (!class_exists('Archive_Tar')) {
+ try {
+ Zend_Loader::loadClass('Archive_Tar');
+ } catch (Zend_Exception $e) {
+ throw new Zend_Filter_Exception('This filter needs PEARs Archive_Tar', 0, $e);
+ }
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->_options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return Zend_Filter_Compress_Tar
+ */
+ public function setArchive($archive)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $archive);
+ $this->_options['archive'] = (string) $archive;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set targetpath
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->_options['target'];
+ }
+
+ /**
+ * Sets the targetpath to use
+ *
+ * @param string $target
+ * @return Zend_Filter_Compress_Tar
+ */
+ public function setTarget($target)
+ {
+ if (!file_exists(dirname($target))) {
+ throw new Zend_Filter_Exception("The directory '$target' does not exist");
+ }
+
+ $target = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $target);
+ $this->_options['target'] = (string) $target;
+ return $this;
+ }
+
+ /**
+ * Returns the set compression mode
+ */
+ public function getMode()
+ {
+ return $this->_options['mode'];
+ }
+
+ /**
+ * Compression mode to use
+ * Eighter Gz or Bz2
+ *
+ * @param string $mode
+ */
+ public function setMode($mode)
+ {
+ $mode = ucfirst(strtolower($mode));
+ if (($mode != 'Bz2') && ($mode != 'Gz')) {
+ throw new Zend_Filter_Exception("The mode '$mode' is unknown");
+ }
+
+ if (($mode == 'Bz2') && (!extension_loaded('bz2'))) {
+ throw new Zend_Filter_Exception('This mode needs the bz2 extension');
+ }
+
+ if (($mode == 'Gz') && (!extension_loaded('zlib'))) {
+ throw new Zend_Filter_Exception('This mode needs the zlib extension');
+ }
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function compress($content)
+ {
+ $archive = new Archive_Tar($this->getArchive(), $this->getMode());
+ if (!file_exists($content)) {
+ $file = $this->getTarget();
+ if (is_dir($file)) {
+ $file .= DIRECTORY_SEPARATOR . "tar.tmp";
+ }
+
+ $result = file_put_contents($file, $content);
+ if ($result === false) {
+ throw new Zend_Filter_Exception('Error creating the temporary file');
+ }
+
+ $content = $file;
+ }
+
+ if (is_dir($content)) {
+ // collect all file infos
+ foreach (new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($content, RecursiveDirectoryIterator::KEY_AS_PATHNAME),
+ RecursiveIteratorIterator::SELF_FIRST
+ ) as $directory => $info
+ ) {
+ if ($info->isFile()) {
+ $file[] = $directory;
+ }
+ }
+
+ $content = $file;
+ }
+
+ $result = $archive->create($content);
+ if ($result === false) {
+ throw new Zend_Filter_Exception('Error creating the Tar archive');
+ }
+
+ return $this->getArchive();
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return boolean
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ if (file_exists($content)) {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ } elseif (empty($archive) || !file_exists($archive)) {
+ throw new Zend_Filter_Exception('Tar Archive not found');
+ }
+
+ $archive = new Archive_Tar($archive, $this->getMode());
+ $target = $this->getTarget();
+ if (!is_dir($target)) {
+ $target = dirname($target);
+ }
+
+ $result = $archive->extract($target);
+ if ($result === false) {
+ throw new Zend_Filter_Exception('Error while extracting the Tar archive');
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Tar';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Compress/Zip.php b/library/vendor/Zend/Filter/Compress/Zip.php
new file mode 100644
index 0000000..055645d
--- /dev/null
+++ b/library/vendor/Zend/Filter/Compress/Zip.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress_CompressAbstract
+ */
+
+/**
+ * Compression adapter for zip
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Compress_Zip extends Zend_Filter_Compress_CompressAbstract
+{
+ /**
+ * Compression Options
+ * array(
+ * 'archive' => Archive to use
+ * 'password' => Password to use
+ * 'target' => Target to write the files to
+ * )
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'archive' => null,
+ 'target' => null,
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param string|array $options (Optional) Options to set
+ */
+ public function __construct($options = null)
+ {
+ if (!extension_loaded('zip')) {
+ throw new Zend_Filter_Exception('This filter needs the zip extension');
+ }
+ parent::__construct($options);
+ }
+
+ /**
+ * Returns the set archive
+ *
+ * @return string
+ */
+ public function getArchive()
+ {
+ return $this->_options['archive'];
+ }
+
+ /**
+ * Sets the archive to use for de-/compression
+ *
+ * @param string $archive Archive to use
+ * @return Zend_Filter_Compress_Rar
+ */
+ public function setArchive($archive)
+ {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $archive);
+ $this->_options['archive'] = (string) $archive;
+
+ return $this;
+ }
+
+ /**
+ * Returns the set targetpath
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->_options['target'];
+ }
+
+ /**
+ * Sets the target to use
+ *
+ * @param string $target
+ * @return Zend_Filter_Compress_Rar
+ */
+ public function setTarget($target)
+ {
+ if (!file_exists(dirname($target))) {
+ throw new Zend_Filter_Exception("The directory '$target' does not exist");
+ }
+
+ $target = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $target);
+ $this->_options['target'] = (string) $target;
+ return $this;
+ }
+
+ /**
+ * Compresses the given content
+ *
+ * @param string $content
+ * @return string Compressed archive
+ */
+ public function compress($content)
+ {
+ $zip = new ZipArchive();
+ $res = $zip->open($this->getArchive(), ZipArchive::CREATE | ZipArchive::OVERWRITE);
+
+ if ($res !== true) {
+ throw new Zend_Filter_Exception($this->_errorString($res));
+ }
+
+ if (file_exists($content)) {
+ $content = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ $basename = substr($content, strrpos($content, DIRECTORY_SEPARATOR) + 1);
+ if (is_dir($content)) {
+ $index = strrpos($content, DIRECTORY_SEPARATOR) + 1;
+ $content .= DIRECTORY_SEPARATOR;
+ $stack = array($content);
+ while (!empty($stack)) {
+ $current = array_pop($stack);
+ $files = array();
+
+ $dir = dir($current);
+ while (false !== ($node = $dir->read())) {
+ if (($node == '.') || ($node == '..')) {
+ continue;
+ }
+
+ if (is_dir($current . $node)) {
+ array_push($stack, $current . $node . DIRECTORY_SEPARATOR);
+ }
+
+ if (is_file($current . $node)) {
+ $files[] = $node;
+ }
+ }
+
+ $local = substr($current, $index);
+ $zip->addEmptyDir(substr($local, 0, -1));
+
+ foreach ($files as $file) {
+ $zip->addFile($current . $file, $local . $file);
+ if ($res !== true) {
+ throw new Zend_Filter_Exception($this->_errorString($res));
+ }
+ }
+ }
+ } else {
+ $res = $zip->addFile($content, $basename);
+ if ($res !== true) {
+ throw new Zend_Filter_Exception($this->_errorString($res));
+ }
+ }
+ } else {
+ $file = $this->getTarget();
+ if (!is_dir($file)) {
+ $file = basename($file);
+ } else {
+ $file = "zip.tmp";
+ }
+
+ $res = $zip->addFromString($file, $content);
+ if ($res !== true) {
+ throw new Zend_Filter_Exception($this->_errorString($res));
+ }
+ }
+
+ $zip->close();
+ return $this->_options['archive'];
+ }
+
+ /**
+ * Decompresses the given content
+ *
+ * @param string $content
+ * @return string
+ */
+ public function decompress($content)
+ {
+ $archive = $this->getArchive();
+ if (file_exists($content)) {
+ $archive = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, realpath($content));
+ } elseif (empty($archive) || !file_exists($archive)) {
+ throw new Zend_Filter_Exception('ZIP Archive not found');
+ }
+
+ $zip = new ZipArchive();
+ $res = $zip->open($archive);
+
+ $target = $this->getTarget();
+
+ if (!empty($target) && !is_dir($target)) {
+ $target = dirname($target);
+ }
+
+ if (!empty($target)) {
+ $target = rtrim($target, '/\\') . DIRECTORY_SEPARATOR;
+ }
+
+ if (empty($target) || !is_dir($target)) {
+ throw new Zend_Filter_Exception('No target for ZIP decompression set');
+ }
+
+ if ($res !== true) {
+ throw new Zend_Filter_Exception($this->_errorString($res));
+ }
+
+ if (version_compare(PHP_VERSION, '5.2.8', '<')) {
+ for ($i = 0; $i < $zip->numFiles; $i++) {
+ $statIndex = $zip->statIndex($i);
+ $currName = $statIndex['name'];
+ if (($currName[0] == '/') ||
+ (substr($currName, 0, 2) == '..') ||
+ (substr($currName, 0, 4) == './..')
+ )
+ {
+ throw new Zend_Filter_Exception('Upward directory traversal was detected inside ' . $archive
+ . ' please use PHP 5.2.8 or greater to take advantage of path resolution features of '
+ . 'the zip extension in this decompress() method.'
+ );
+ }
+ }
+ }
+
+ $res = @$zip->extractTo($target);
+ if ($res !== true) {
+ throw new Zend_Filter_Exception($this->_errorString($res));
+ }
+
+ $zip->close();
+ return $target;
+ }
+
+ /**
+ * Returns the proper string based on the given error constant
+ *
+ * @param string $error
+ */
+ protected function _errorString($error)
+ {
+ switch($error) {
+ case ZipArchive::ER_MULTIDISK :
+ return 'Multidisk ZIP Archives not supported';
+
+ case ZipArchive::ER_RENAME :
+ return 'Failed to rename the temporary file for ZIP';
+
+ case ZipArchive::ER_CLOSE :
+ return 'Failed to close the ZIP Archive';
+
+ case ZipArchive::ER_SEEK :
+ return 'Failure while seeking the ZIP Archive';
+
+ case ZipArchive::ER_READ :
+ return 'Failure while reading the ZIP Archive';
+
+ case ZipArchive::ER_WRITE :
+ return 'Failure while writing the ZIP Archive';
+
+ case ZipArchive::ER_CRC :
+ return 'CRC failure within the ZIP Archive';
+
+ case ZipArchive::ER_ZIPCLOSED :
+ return 'ZIP Archive already closed';
+
+ case ZipArchive::ER_NOENT :
+ return 'No such file within the ZIP Archive';
+
+ case ZipArchive::ER_EXISTS :
+ return 'ZIP Archive already exists';
+
+ case ZipArchive::ER_OPEN :
+ return 'Can not open ZIP Archive';
+
+ case ZipArchive::ER_TMPOPEN :
+ return 'Failure creating temporary ZIP Archive';
+
+ case ZipArchive::ER_ZLIB :
+ return 'ZLib Problem';
+
+ case ZipArchive::ER_MEMORY :
+ return 'Memory allocation problem while working on a ZIP Archive';
+
+ case ZipArchive::ER_CHANGED :
+ return 'ZIP Entry has been changed';
+
+ case ZipArchive::ER_COMPNOTSUPP :
+ return 'Compression method not supported within ZLib';
+
+ case ZipArchive::ER_EOF :
+ return 'Premature EOF within ZIP Archive';
+
+ case ZipArchive::ER_INVAL :
+ return 'Invalid argument for ZLIB';
+
+ case ZipArchive::ER_NOZIP :
+ return 'Given file is no zip archive';
+
+ case ZipArchive::ER_INTERNAL :
+ return 'Internal error while working on a ZIP Archive';
+
+ case ZipArchive::ER_INCONS :
+ return 'Inconsistent ZIP archive';
+
+ case ZipArchive::ER_REMOVE :
+ return 'Can not remove ZIP Archive';
+
+ case ZipArchive::ER_DELETED :
+ return 'ZIP Entry has been deleted';
+
+ default :
+ return 'Unknown error within ZIP Archive';
+ }
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Zip';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Decompress.php b/library/vendor/Zend/Filter/Decompress.php
new file mode 100644
index 0000000..553c677
--- /dev/null
+++ b/library/vendor/Zend/Filter/Decompress.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Compress
+ */
+
+/**
+ * Decompresses a given string
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Decompress extends Zend_Filter_Compress
+{
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Decompresses the content $value with the defined settings
+ *
+ * @param string $value Content to decompress
+ * @return string The decompressed content
+ */
+ public function filter($value)
+ {
+ return $this->getAdapter()->decompress($value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Decrypt.php b/library/vendor/Zend/Filter/Decrypt.php
new file mode 100644
index 0000000..a17dc46
--- /dev/null
+++ b/library/vendor/Zend/Filter/Decrypt.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Encrypt
+ */
+
+/**
+ * Decrypts a given string
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Decrypt extends Zend_Filter_Encrypt
+{
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Decrypts the content $value with the defined settings
+ *
+ * @param string $value Content to decrypt
+ * @return string The decrypted content
+ */
+ public function filter($value)
+ {
+ return $this->_adapter->decrypt($value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Digits.php b/library/vendor/Zend/Filter/Digits.php
new file mode 100644
index 0000000..83881d7
--- /dev/null
+++ b/library/vendor/Zend/Filter/Digits.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Digits implements Zend_Filter_Interface
+{
+ /**
+ * Is PCRE is compiled with UTF-8 and Unicode support
+ *
+ * @var mixed
+ **/
+ protected static $_unicodeEnabled;
+
+ /**
+ * Class constructor
+ *
+ * Checks if PCRE is compiled with UTF-8 and Unicode support
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if (null === self::$_unicodeEnabled) {
+ self::$_unicodeEnabled = (@preg_match('/\pL/u', 'a')) ? true : false;
+ }
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, removing all but digit characters
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ if (!self::$_unicodeEnabled) {
+ // POSIX named classes are not supported, use alternative 0-9 match
+ $pattern = '/[^0-9]/';
+ } else if (extension_loaded('mbstring')) {
+ // Filter for the value with mbstring
+ $pattern = '/[^[:digit:]]/';
+ } else {
+ // Filter for the value without mbstring
+ $pattern = '/[\p{^N}]/';
+ }
+
+ return preg_replace($pattern, '', (string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Dir.php b/library/vendor/Zend/Filter/Dir.php
new file mode 100644
index 0000000..96b180f
--- /dev/null
+++ b/library/vendor/Zend/Filter/Dir.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Dir implements Zend_Filter_Interface
+{
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns dirname($value)
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ return dirname((string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Encrypt.php b/library/vendor/Zend/Filter/Encrypt.php
new file mode 100644
index 0000000..0dca4c1
--- /dev/null
+++ b/library/vendor/Zend/Filter/Encrypt.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * Encrypts a given string
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Encrypt implements Zend_Filter_Interface
+{
+ /**
+ * Encryption adapter
+ */
+ protected $_adapter;
+
+ /**
+ * Class constructor
+ *
+ * @param string|array $options (Optional) Options to set, if null mcrypt is used
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ $this->setAdapter($options);
+ }
+
+ /**
+ * Returns the name of the set adapter
+ *
+ * @return string
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter->toString();
+ }
+
+ /**
+ * Sets new encryption options
+ *
+ * @param string|array $options (Optional) Encryption options
+ * @return Zend_Filter_Encrypt
+ */
+ public function setAdapter($options = null)
+ {
+ if (is_string($options)) {
+ $adapter = $options;
+ } else if (isset($options['adapter'])) {
+ $adapter = $options['adapter'];
+ unset($options['adapter']);
+ } else {
+ $adapter = 'Mcrypt';
+ }
+
+ if (!is_array($options)) {
+ $options = array();
+ }
+
+ if (Zend_Loader::isReadable('Zend/Filter/Encrypt/' . ucfirst($adapter). '.php')) {
+ $adapter = 'Zend_Filter_Encrypt_' . ucfirst($adapter);
+ }
+
+ if (!class_exists($adapter)) {
+ Zend_Loader::loadClass($adapter);
+ }
+
+ $this->_adapter = new $adapter($options);
+ if (!$this->_adapter instanceof Zend_Filter_Encrypt_Interface) {
+ throw new Zend_Filter_Exception("Encoding adapter '" . $adapter . "' does not implement Zend_Filter_Encrypt_Interface");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Calls adapter methods
+ *
+ * @param string $method Method to call
+ * @param string|array $options Options for this method
+ */
+ public function __call($method, $options)
+ {
+ $part = substr($method, 0, 3);
+ if ((($part != 'get') and ($part != 'set')) or !method_exists($this->_adapter, $method)) {
+ throw new Zend_Filter_Exception("Unknown method '{$method}'");
+ }
+
+ return call_user_func_array(array($this->_adapter, $method), $options);
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Encrypts the content $value with the defined settings
+ *
+ * @param string $value Content to encrypt
+ * @return string The encrypted content
+ */
+ public function filter($value)
+ {
+ return $this->_adapter->encrypt($value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Encrypt/Interface.php b/library/vendor/Zend/Filter/Encrypt/Interface.php
new file mode 100644
index 0000000..1510e7f
--- /dev/null
+++ b/library/vendor/Zend/Filter/Encrypt/Interface.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Encryption interface
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Filter_Encrypt_Interface
+{
+ /**
+ * Encrypts $value with the defined settings
+ *
+ * @param string $value Data to encrypt
+ * @return string The encrypted data
+ */
+ public function encrypt($value);
+
+ /**
+ * Decrypts $value with the defined settings
+ *
+ * @param string $value Data to decrypt
+ * @return string The decrypted data
+ */
+ public function decrypt($value);
+}
diff --git a/library/vendor/Zend/Filter/Encrypt/Mcrypt.php b/library/vendor/Zend/Filter/Encrypt/Mcrypt.php
new file mode 100644
index 0000000..ee7a45b
--- /dev/null
+++ b/library/vendor/Zend/Filter/Encrypt/Mcrypt.php
@@ -0,0 +1,352 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Encrypt_Interface
+ */
+
+/** @see Zend_Crypt_Math */
+
+/**
+ * Encryption adapter for mcrypt
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Encrypt_Mcrypt implements Zend_Filter_Encrypt_Interface
+{
+ /**
+ * Definitions for encryption
+ * array(
+ * 'key' => encryption key string
+ * 'algorithm' => algorithm to use
+ * 'algorithm_directory' => directory where to find the algorithm
+ * 'mode' => encryption mode to use
+ * 'modedirectory' => directory where to find the mode
+ * )
+ */
+ protected $_encryption = array(
+ 'key' => 'ZendFramework',
+ 'algorithm' => 'blowfish',
+ 'algorithm_directory' => '',
+ 'mode' => 'cbc',
+ 'mode_directory' => '',
+ 'vector' => null,
+ 'salt' => false
+ );
+
+ /**
+ * Internal compression
+ *
+ * @var array
+ */
+ protected $_compression;
+
+ protected static $_srandCalled = false;
+
+ /**
+ * Class constructor
+ *
+ * @param string|array|Zend_Config $options Cryption Options
+ */
+ public function __construct($options)
+ {
+ if (!extension_loaded('mcrypt')) {
+ throw new Zend_Filter_Exception('This filter needs the mcrypt extension');
+ }
+
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_string($options)) {
+ $options = array('key' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Filter_Exception('Invalid options argument provided to filter');
+ }
+
+ if (array_key_exists('compression', $options)) {
+ $this->setCompression($options['compression']);
+ unset($options['compress']);
+ }
+
+ $this->setEncryption($options);
+ }
+
+ /**
+ * Returns the set encryption options
+ *
+ * @return array
+ */
+ public function getEncryption()
+ {
+ return $this->_encryption;
+ }
+
+ /**
+ * Sets new encryption options
+ *
+ * @param string|array $options Encryption options
+ * @return Zend_Filter_File_Encryption
+ */
+ public function setEncryption($options)
+ {
+ if (is_string($options)) {
+ $options = array('key' => $options);
+ }
+
+ if (!is_array($options)) {
+ throw new Zend_Filter_Exception('Invalid options argument provided to filter');
+ }
+
+ $options = $options + $this->getEncryption();
+ $algorithms = mcrypt_list_algorithms($options['algorithm_directory']);
+ if (!in_array($options['algorithm'], $algorithms)) {
+ throw new Zend_Filter_Exception("The algorithm '{$options['algorithm']}' is not supported");
+ }
+
+ $modes = mcrypt_list_modes($options['mode_directory']);
+ if (!in_array($options['mode'], $modes)) {
+ throw new Zend_Filter_Exception("The mode '{$options['mode']}' is not supported");
+ }
+
+ if (!mcrypt_module_self_test($options['algorithm'], $options['algorithm_directory'])) {
+ throw new Zend_Filter_Exception('The given algorithm can not be used due an internal mcrypt problem');
+ }
+
+ if (!isset($options['vector'])) {
+ $options['vector'] = null;
+ }
+
+ $this->_encryption = $options;
+ $this->setVector($options['vector']);
+
+ return $this;
+ }
+
+ /**
+ * Returns the set vector
+ *
+ * @return string
+ */
+ public function getVector()
+ {
+ return $this->_encryption['vector'];
+ }
+
+ /**
+ * Sets the initialization vector
+ *
+ * @param string $vector (Optional) Vector to set
+ * @return Zend_Filter_Encrypt_Mcrypt
+ */
+ public function setVector($vector = null)
+ {
+ $cipher = $this->_openCipher();
+ $size = mcrypt_enc_get_iv_size($cipher);
+ if (empty($vector)) {
+ $this->_srand();
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
+ $method = MCRYPT_RAND;
+ } else {
+ if (file_exists('/dev/urandom') || (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')) {
+ $method = MCRYPT_DEV_URANDOM;
+ } elseif (file_exists('/dev/random')) {
+ $method = MCRYPT_DEV_RANDOM;
+ } else {
+ $method = MCRYPT_RAND;
+ }
+ }
+ $vector = mcrypt_create_iv($size, $method);
+ } else if (strlen($vector) != $size) {
+ throw new Zend_Filter_Exception('The given vector has a wrong size for the set algorithm');
+ }
+
+ $this->_encryption['vector'] = $vector;
+ $this->_closeCipher($cipher);
+
+ return $this;
+ }
+
+ /**
+ * Returns the compression
+ *
+ * @return array
+ */
+ public function getCompression()
+ {
+ return $this->_compression;
+ }
+
+ /**
+ * Sets a internal compression for values to encrypt
+ *
+ * @param string|array $compression
+ * @return Zend_Filter_Encrypt_Mcrypt
+ */
+ public function setCompression($compression)
+ {
+ if (is_string($this->_compression)) {
+ $compression = array('adapter' => $compression);
+ }
+
+ $this->_compression = $compression;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Encrypts $value with the defined settings
+ *
+ * @param string $value The content to encrypt
+ * @return string The encrypted content
+ */
+ public function encrypt($value)
+ {
+ // compress prior to encryption
+ if (!empty($this->_compression)) {
+ $compress = new Zend_Filter_Compress($this->_compression);
+ $value = $compress->filter($value);
+ }
+
+ $cipher = $this->_openCipher();
+ $this->_initCipher($cipher);
+ $encrypted = mcrypt_generic($cipher, $value);
+ mcrypt_generic_deinit($cipher);
+ $this->_closeCipher($cipher);
+
+ return $encrypted;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Decrypts $value with the defined settings
+ *
+ * @param string $value Content to decrypt
+ * @return string The decrypted content
+ */
+ public function decrypt($value)
+ {
+ $cipher = $this->_openCipher();
+ $this->_initCipher($cipher);
+ $decrypted = mdecrypt_generic($cipher, $value);
+ mcrypt_generic_deinit($cipher);
+ $this->_closeCipher($cipher);
+
+ // decompress after decryption
+ if (!empty($this->_compression)) {
+ $decompress = new Zend_Filter_Decompress($this->_compression);
+ $decrypted = $decompress->filter($decrypted);
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Mcrypt';
+ }
+
+ /**
+ * Open a cipher
+ *
+ * @throws Zend_Filter_Exception When the cipher can not be opened
+ * @return resource Returns the opened cipher
+ */
+ protected function _openCipher()
+ {
+ $cipher = mcrypt_module_open(
+ $this->_encryption['algorithm'],
+ $this->_encryption['algorithm_directory'],
+ $this->_encryption['mode'],
+ $this->_encryption['mode_directory']);
+
+ if ($cipher === false) {
+ throw new Zend_Filter_Exception('Mcrypt can not be opened with your settings');
+ }
+
+ return $cipher;
+ }
+
+ /**
+ * Close a cipher
+ *
+ * @param resource $cipher Cipher to close
+ * @return Zend_Filter_Encrypt_Mcrypt
+ */
+ protected function _closeCipher($cipher)
+ {
+ mcrypt_module_close($cipher);
+
+ return $this;
+ }
+
+ /**
+ * Initialises the cipher with the set key
+ *
+ * @param resource $cipher
+ * @throws
+ * @return resource
+ */
+ protected function _initCipher($cipher)
+ {
+ $key = $this->_encryption['key'];
+
+ $keysizes = mcrypt_enc_get_supported_key_sizes($cipher);
+ if (empty($keysizes) || ($this->_encryption['salt'] == true)) {
+ $this->_srand();
+ $keysize = mcrypt_enc_get_key_size($cipher);
+ $key = substr(md5($key), 0, $keysize);
+ } else if (!in_array(strlen($key), $keysizes)) {
+ throw new Zend_Filter_Exception('The given key has a wrong size for the set algorithm');
+ }
+
+ $result = mcrypt_generic_init($cipher, $key, $this->_encryption['vector']);
+ if ($result < 0) {
+ throw new Zend_Filter_Exception('Mcrypt could not be initialize with the given setting');
+ }
+
+ return $this;
+ }
+
+ /**
+ * _srand() interception
+ *
+ * @see ZF-8742
+ */
+ protected function _srand()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ return;
+ }
+ if (!self::$_srandCalled) {
+ srand(Zend_Crypt_Math::randInteger(0, PHP_INT_MAX));
+ self::$_srandCalled = true;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Filter/Encrypt/Openssl.php b/library/vendor/Zend/Filter/Encrypt/Openssl.php
new file mode 100644
index 0000000..7a46b8e
--- /dev/null
+++ b/library/vendor/Zend/Filter/Encrypt/Openssl.php
@@ -0,0 +1,480 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Encrypt_Interface
+ */
+
+/**
+ * Encryption adapter for openssl
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Encrypt_Openssl implements Zend_Filter_Encrypt_Interface
+{
+ /**
+ * Definitions for encryption
+ * array(
+ * 'public' => public keys
+ * 'private' => private keys
+ * 'envelope' => resulting envelope keys
+ * )
+ */
+ protected $_keys = array(
+ 'public' => array(),
+ 'private' => array(),
+ 'envelope' => array()
+ );
+
+ /**
+ * Internal passphrase
+ *
+ * @var string
+ */
+ protected $_passphrase;
+
+ /**
+ * Internal compression
+ *
+ * @var array
+ */
+ protected $_compression;
+
+ /**
+ * Internal create package
+ *
+ * @var boolean
+ */
+ protected $_package = false;
+
+ /**
+ * Class constructor
+ * Available options
+ * 'public' => public key
+ * 'private' => private key
+ * 'envelope' => envelope key
+ * 'passphrase' => passphrase
+ * 'compression' => compress value with this compression adapter
+ * 'package' => pack envelope keys into encrypted string, simplifies decryption
+ *
+ * @param string|array $options Options for this adapter
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('openssl')) {
+ throw new Zend_Filter_Exception('This filter needs the openssl extension');
+ }
+
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (!is_array($options)) {
+ $options = array('public' => $options);
+ }
+
+ if (array_key_exists('passphrase', $options)) {
+ $this->setPassphrase($options['passphrase']);
+ unset($options['passphrase']);
+ }
+
+ if (array_key_exists('compression', $options)) {
+ $this->setCompression($options['compression']);
+ unset($options['compress']);
+ }
+
+ if (array_key_exists('package', $options)) {
+ $this->setPackage($options['package']);
+ unset($options['package']);
+ }
+
+ $this->_setKeys($options);
+ }
+
+ /**
+ * Sets the encryption keys
+ *
+ * @param string|array $keys Key with type association
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ protected function _setKeys($keys)
+ {
+ if (!is_array($keys)) {
+ throw new Zend_Filter_Exception('Invalid options argument provided to filter');
+ }
+
+ foreach ($keys as $type => $key) {
+ if (ctype_print($key) && is_file(realpath($key)) && is_readable($key)) {
+ $file = fopen($key, 'r');
+ $cert = fread($file, 8192);
+ fclose($file);
+ } else {
+ $cert = $key;
+ $key = count($this->_keys[$type]);
+ }
+
+ switch ($type) {
+ case 'public':
+ $test = openssl_pkey_get_public($cert);
+ if ($test === false) {
+ throw new Zend_Filter_Exception("Public key '{$cert}' not valid");
+ }
+
+ openssl_free_key($test);
+ $this->_keys['public'][$key] = $cert;
+ break;
+ case 'private':
+ $test = openssl_pkey_get_private($cert, $this->_passphrase);
+ if ($test === false) {
+ throw new Zend_Filter_Exception("Private key '{$cert}' not valid");
+ }
+
+ openssl_free_key($test);
+ $this->_keys['private'][$key] = $cert;
+ break;
+ case 'envelope':
+ $this->_keys['envelope'][$key] = $cert;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns all public keys
+ *
+ * @return array
+ */
+ public function getPublicKey()
+ {
+ $key = $this->_keys['public'];
+ return $key;
+ }
+
+ /**
+ * Sets public keys
+ *
+ * @param string|array $key Public keys
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ public function setPublicKey($key)
+ {
+ if (is_array($key)) {
+ foreach($key as $type => $option) {
+ if ($type !== 'public') {
+ $key['public'] = $option;
+ unset($key[$type]);
+ }
+ }
+ } else {
+ $key = array('public' => $key);
+ }
+
+ return $this->_setKeys($key);
+ }
+
+ /**
+ * Returns all private keys
+ *
+ * @return array
+ */
+ public function getPrivateKey()
+ {
+ $key = $this->_keys['private'];
+ return $key;
+ }
+
+ /**
+ * Sets private keys
+ *
+ * @param string $key Private key
+ * @param string $passphrase
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ public function setPrivateKey($key, $passphrase = null)
+ {
+ if (is_array($key)) {
+ foreach($key as $type => $option) {
+ if ($type !== 'private') {
+ $key['private'] = $option;
+ unset($key[$type]);
+ }
+ }
+ } else {
+ $key = array('private' => $key);
+ }
+
+ if ($passphrase !== null) {
+ $this->setPassphrase($passphrase);
+ }
+
+ return $this->_setKeys($key);
+ }
+
+ /**
+ * Returns all envelope keys
+ *
+ * @return array
+ */
+ public function getEnvelopeKey()
+ {
+ $key = $this->_keys['envelope'];
+ return $key;
+ }
+
+ /**
+ * Sets envelope keys
+ *
+ * @param string|array $options Envelope keys
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ public function setEnvelopeKey($key)
+ {
+ if (is_array($key)) {
+ foreach($key as $type => $option) {
+ if ($type !== 'envelope') {
+ $key['envelope'] = $option;
+ unset($key[$type]);
+ }
+ }
+ } else {
+ $key = array('envelope' => $key);
+ }
+
+ return $this->_setKeys($key);
+ }
+
+ /**
+ * Returns the passphrase
+ *
+ * @return string
+ */
+ public function getPassphrase()
+ {
+ return $this->_passphrase;
+ }
+
+ /**
+ * Sets a new passphrase
+ *
+ * @param string $passphrase
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ public function setPassphrase($passphrase)
+ {
+ $this->_passphrase = $passphrase;
+ return $this;
+ }
+
+ /**
+ * Returns the compression
+ *
+ * @return array
+ */
+ public function getCompression()
+ {
+ return $this->_compression;
+ }
+
+ /**
+ * Sets a internal compression for values to encrypt
+ *
+ * @param string|array $compression
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ public function setCompression($compression)
+ {
+ if (is_string($this->_compression)) {
+ $compression = array('adapter' => $compression);
+ }
+
+ $this->_compression = $compression;
+ return $this;
+ }
+
+ /**
+ * Returns if header should be packaged
+ *
+ * @return boolean
+ */
+ public function getPackage()
+ {
+ return $this->_package;
+ }
+
+ /**
+ * Sets if the envelope keys should be included in the encrypted value
+ *
+ * @param boolean $package
+ * @return Zend_Filter_Encrypt_Openssl
+ */
+ public function setPackage($package)
+ {
+ $this->_package = (boolean) $package;
+ return $this;
+ }
+
+ /**
+ * Encrypts $value with the defined settings
+ * Note that you also need the "encrypted" keys to be able to decrypt
+ *
+ * @param string $value Content to encrypt
+ * @return string The encrypted content
+ * @throws Zend_Filter_Exception
+ */
+ public function encrypt($value)
+ {
+ $encrypted = array();
+ $encryptedkeys = array();
+
+ if (count($this->_keys['public']) == 0) {
+ throw new Zend_Filter_Exception('Openssl can not encrypt without public keys');
+ }
+
+ $keys = array();
+ $fingerprints = array();
+ $count = -1;
+ foreach($this->_keys['public'] as $key => $cert) {
+ $keys[$key] = openssl_pkey_get_public($cert);
+ if ($this->_package) {
+ $details = openssl_pkey_get_details($keys[$key]);
+ if ($details === false) {
+ $details = array('key' => 'ZendFramework');
+ }
+
+ ++$count;
+ $fingerprints[$count] = md5($details['key']);
+ }
+ }
+
+ // compress prior to encryption
+ if (!empty($this->_compression)) {
+ $compress = new Zend_Filter_Compress($this->_compression);
+ $value = $compress->filter($value);
+ }
+
+ $crypt = openssl_seal($value, $encrypted, $encryptedkeys, $keys);
+ foreach ($keys as $key) {
+ openssl_free_key($key);
+ }
+
+ if ($crypt === false) {
+ throw new Zend_Filter_Exception('Openssl was not able to encrypt your content with the given options');
+ }
+
+ $this->_keys['envelope'] = $encryptedkeys;
+
+ // Pack data and envelope keys into single string
+ if ($this->_package) {
+ $header = pack('n', count($this->_keys['envelope']));
+ foreach($this->_keys['envelope'] as $key => $envKey) {
+ $header .= pack('H32n', $fingerprints[$key], strlen($envKey)) . $envKey;
+ }
+
+ $encrypted = $header . $encrypted;
+ }
+
+ return $encrypted;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Decrypts $value with the defined settings
+ *
+ * @param string $value Content to decrypt
+ * @return string The decrypted content
+ * @throws Zend_Filter_Exception
+ */
+ public function decrypt($value)
+ {
+ $decrypted = "";
+ $envelope = current($this->getEnvelopeKey());
+
+ if (count($this->_keys['private']) !== 1) {
+ throw new Zend_Filter_Exception('Please give a private key for decryption with Openssl');
+ }
+
+ if (!$this->_package && empty($envelope)) {
+ throw new Zend_Filter_Exception('Please give a envelope key for decryption with Openssl');
+ }
+
+ foreach($this->_keys['private'] as $key => $cert) {
+ $keys = openssl_pkey_get_private($cert, $this->getPassphrase());
+ }
+
+ if ($this->_package) {
+ $details = openssl_pkey_get_details($keys);
+ if ($details !== false) {
+ $fingerprint = md5($details['key']);
+ } else {
+ $fingerprint = md5("ZendFramework");
+ }
+
+ $count = unpack('ncount', $value);
+ $count = $count['count'];
+ $length = 2;
+ for($i = $count; $i > 0; --$i) {
+ $header = unpack('H32print/nsize', substr($value, $length, 18));
+ $length += 18;
+ if ($header['print'] == $fingerprint) {
+ $envelope = substr($value, $length, $header['size']);
+ }
+
+ $length += $header['size'];
+ }
+
+ // remainder of string is the value to decrypt
+ $value = substr($value, $length);
+ }
+
+ $crypt = openssl_open($value, $decrypted, $envelope, $keys);
+ openssl_free_key($keys);
+
+ if ($crypt === false) {
+ throw new Zend_Filter_Exception('Openssl was not able to decrypt you content with the given options');
+ }
+
+ // decompress after decryption
+ if (!empty($this->_compression)) {
+ $decompress = new Zend_Filter_Decompress($this->_compression);
+ $decrypted = $decompress->filter($decrypted);
+ }
+
+ return $decrypted;
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Openssl';
+ }
+}
diff --git a/library/vendor/Zend/Filter/Exception.php b/library/vendor/Zend/Filter/Exception.php
new file mode 100644
index 0000000..d56145a
--- /dev/null
+++ b/library/vendor/Zend/Filter/Exception.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Exception extends Zend_Exception
+{}
diff --git a/library/vendor/Zend/Filter/File/Decrypt.php b/library/vendor/Zend/Filter/File/Decrypt.php
new file mode 100644
index 0000000..a7468aa
--- /dev/null
+++ b/library/vendor/Zend/Filter/File/Decrypt.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Decrypt
+ */
+
+/**
+ * Decrypts a given file and stores the decrypted file content
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_File_Decrypt extends Zend_Filter_Decrypt
+{
+ /**
+ * New filename to set
+ *
+ * @var string
+ */
+ protected $_filename;
+
+ /**
+ * Returns the new filename where the content will be stored
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_filename;
+ }
+
+ /**
+ * Sets the new filename where the content will be stored
+ *
+ * @param string $filename (Optional) New filename to set
+ * @return Zend_Filter_File_Encryt
+ */
+ public function setFilename($filename = null)
+ {
+ $this->_filename = $filename;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Decrypts the file $value with the defined settings
+ *
+ * @param string $value Full path of file to change
+ * @return string The filename which has been set, or false when there were errors
+ */
+ public function filter($value)
+ {
+ if (!file_exists($value)) {
+ throw new Zend_Filter_Exception("File '$value' not found");
+ }
+
+ if (!isset($this->_filename)) {
+ $this->_filename = $value;
+ }
+
+ if (file_exists($this->_filename) and !is_writable($this->_filename)) {
+ throw new Zend_Filter_Exception("File '{$this->_filename}' is not writable");
+ }
+
+ $content = file_get_contents($value);
+ if (!$content) {
+ throw new Zend_Filter_Exception("Problem while reading file '$value'");
+ }
+
+ $decrypted = parent::filter($content);
+ $result = file_put_contents($this->_filename, $decrypted);
+
+ if (!$result) {
+ throw new Zend_Filter_Exception("Problem while writing file '{$this->_filename}'");
+ }
+
+ return $this->_filename;
+ }
+}
diff --git a/library/vendor/Zend/Filter/File/Encrypt.php b/library/vendor/Zend/Filter/File/Encrypt.php
new file mode 100644
index 0000000..16be9df
--- /dev/null
+++ b/library/vendor/Zend/Filter/File/Encrypt.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Encrypt
+ */
+
+/**
+ * Encrypts a given file and stores the encrypted file content
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_File_Encrypt extends Zend_Filter_Encrypt
+{
+ /**
+ * New filename to set
+ *
+ * @var string
+ */
+ protected $_filename;
+
+ /**
+ * Returns the new filename where the content will be stored
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return $this->_filename;
+ }
+
+ /**
+ * Sets the new filename where the content will be stored
+ *
+ * @param string $filename (Optional) New filename to set
+ * @return Zend_Filter_File_Encryt
+ */
+ public function setFilename($filename = null)
+ {
+ $this->_filename = $filename;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Encrypts the file $value with the defined settings
+ *
+ * @param string $value Full path of file to change
+ * @return string The filename which has been set, or false when there were errors
+ */
+ public function filter($value)
+ {
+ if (!file_exists($value)) {
+ throw new Zend_Filter_Exception("File '$value' not found");
+ }
+
+ if (!isset($this->_filename)) {
+ $this->_filename = $value;
+ }
+
+ if (file_exists($this->_filename) and !is_writable($this->_filename)) {
+ throw new Zend_Filter_Exception("File '{$this->_filename}' is not writable");
+ }
+
+ $content = file_get_contents($value);
+ if (!$content) {
+ throw new Zend_Filter_Exception("Problem while reading file '$value'");
+ }
+
+ $encrypted = parent::filter($content);
+ $result = file_put_contents($this->_filename, $encrypted);
+
+ if (!$result) {
+ throw new Zend_Filter_Exception("Problem while writing file '{$this->_filename}'");
+ }
+
+ return $this->_filename;
+ }
+}
diff --git a/library/vendor/Zend/Filter/File/LowerCase.php b/library/vendor/Zend/Filter/File/LowerCase.php
new file mode 100644
index 0000000..681332b
--- /dev/null
+++ b/library/vendor/Zend/Filter/File/LowerCase.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_StringToLower
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_File_LowerCase extends Zend_Filter_StringToLower
+{
+ /**
+ * Adds options to the filter at initiation
+ *
+ * @param string $options
+ */
+ public function __construct($options = null)
+ {
+ if (!empty($options)) {
+ $this->setEncoding($options);
+ }
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Does a lowercase on the content of the given file
+ *
+ * @param string $value Full path of file to change
+ * @return string The given $value
+ * @throws Zend_Filter_Exception
+ */
+ public function filter($value)
+ {
+ if (!file_exists($value)) {
+ throw new Zend_Filter_Exception("File '$value' not found");
+ }
+
+ if (!is_writable($value)) {
+ throw new Zend_Filter_Exception("File '$value' is not writable");
+ }
+
+ $content = file_get_contents($value);
+ if (!$content) {
+ throw new Zend_Filter_Exception("Problem while reading file '$value'");
+ }
+
+ $content = parent::filter($content);
+ $result = file_put_contents($value, $content);
+
+ if (!$result) {
+ throw new Zend_Filter_Exception("Problem while writing file '$value'");
+ }
+
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/Filter/File/Rename.php b/library/vendor/Zend/Filter/File/Rename.php
new file mode 100644
index 0000000..1bf5650
--- /dev/null
+++ b/library/vendor/Zend/Filter/File/Rename.php
@@ -0,0 +1,304 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_File_Rename implements Zend_Filter_Interface
+{
+ /**
+ * Internal array of array(source, target, overwrite)
+ */
+ protected $_files = array();
+
+ /**
+ * Class constructor
+ *
+ * Options argument may be either a string, a Zend_Config object, or an array.
+ * If an array or Zend_Config object, it accepts the following keys:
+ * 'source' => Source filename or directory which will be renamed
+ * 'target' => Target filename or directory, the new name of the sourcefile
+ * 'overwrite' => Shall existing files be overwritten ?
+ *
+ * @param string|array $options Target file or directory to be renamed
+ * @param string $target Source filename or directory (deprecated)
+ * @param bool $overwrite Should existing files be overwritten (deprecated)
+ * @return void
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_string($options)) {
+ $options = array('target' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Filter_Exception('Invalid options argument provided to filter');
+ }
+
+ if (1 < func_num_args()) {
+ $argv = func_get_args();
+ array_shift($argv);
+ $source = array_shift($argv);
+ $overwrite = false;
+ if (!empty($argv)) {
+ $overwrite = array_shift($argv);
+ }
+ $options['source'] = $source;
+ $options['overwrite'] = $overwrite;
+ }
+
+ $this->setFile($options);
+ }
+
+ /**
+ * Returns the files to rename and their new name and location
+ *
+ * @return array
+ */
+ public function getFile()
+ {
+ return $this->_files;
+ }
+
+ /**
+ * Sets a new file or directory as target, deleting existing ones
+ *
+ * Array accepts the following keys:
+ * 'source' => Source filename or directory which will be renamed
+ * 'target' => Target filename or directory, the new name of the sourcefile
+ * 'overwrite' => Shall existing files be overwritten ?
+ *
+ * @param string|array $options Old file or directory to be rewritten
+ * @return Zend_Filter_File_Rename
+ */
+ public function setFile($options)
+ {
+ $this->_files = array();
+ $this->addFile($options);
+
+ return $this;
+ }
+
+ /**
+ * Adds a new file or directory as target to the existing ones
+ *
+ * Array accepts the following keys:
+ * 'source' => Source filename or directory which will be renamed
+ * 'target' => Target filename or directory, the new name of the sourcefile
+ * 'overwrite' => Shall existing files be overwritten ?
+ *
+ * @param string|array $options Old file or directory to be rewritten
+ * @return Zend_Filter_File_Rename
+ */
+ public function addFile($options)
+ {
+ if (is_string($options)) {
+ $options = array('target' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Filter_Exception ('Invalid options to rename filter provided');
+ }
+
+ $this->_convertOptions($options);
+
+ return $this;
+ }
+
+ /**
+ * Returns only the new filename without moving it
+ * But existing files will be erased when the overwrite option is true
+ *
+ * @param string $value Full path of file to change
+ * @param boolean $source Return internal informations
+ * @return string The new filename which has been set
+ */
+ public function getNewName($value, $source = false)
+ {
+ $file = $this->_getFileName($value);
+
+ if (!is_array($file) || !array_key_exists('source', $file) || !array_key_exists('target', $file)) {
+ return $value;
+ }
+
+ if ($file['source'] == $file['target']) {
+ return $value;
+ }
+
+ if (!file_exists($file['source'])) {
+ return $value;
+ }
+
+ if (($file['overwrite'] == true) && (file_exists($file['target']))) {
+ unlink($file['target']);
+ }
+
+ if (file_exists($file['target'])) {
+ throw new Zend_Filter_Exception(sprintf("File '%s' could not be renamed. It already exists.", $value));
+ }
+
+ if ($source) {
+ return $file;
+ }
+
+ return $file['target'];
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Renames the file $value to the new name set before
+ * Returns the file $value, removing all but digit characters
+ *
+ * @param string $value Full path of file to change
+ * @throws Zend_Filter_Exception
+ * @return string The new filename which has been set, or false when there were errors
+ */
+ public function filter($value)
+ {
+ $file = $this->getNewName($value, true);
+ if (is_string($file)) {
+ return $file;
+ }
+
+ $result = rename($file['source'], $file['target']);
+
+ if ($result === true) {
+ return $file['target'];
+ }
+
+ throw new Zend_Filter_Exception(sprintf("File '%s' could not be renamed. An error occured while processing the file.", $value));
+ }
+
+ /**
+ * Internal method for creating the file array
+ * Supports single and nested arrays
+ *
+ * @param array $options
+ * @return array
+ */
+ protected function _convertOptions($options) {
+ $files = array();
+ foreach ($options as $key => $value) {
+ if (is_array($value)) {
+ $this->_convertOptions($value);
+ continue;
+ }
+
+ switch ($key) {
+ case "source":
+ $files['source'] = (string) $value;
+ break;
+
+ case 'target' :
+ $files['target'] = (string) $value;
+ break;
+
+ case 'overwrite' :
+ $files['overwrite'] = (boolean) $value;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (empty($files)) {
+ return $this;
+ }
+
+ if (empty($files['source'])) {
+ $files['source'] = '*';
+ }
+
+ if (empty($files['target'])) {
+ $files['target'] = '*';
+ }
+
+ if (empty($files['overwrite'])) {
+ $files['overwrite'] = false;
+ }
+
+ $found = false;
+ foreach ($this->_files as $key => $value) {
+ if ($value['source'] == $files['source']) {
+ $this->_files[$key] = $files;
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ $count = count($this->_files);
+ $this->_files[$count] = $files;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Internal method to resolve the requested source
+ * and return all other related parameters
+ *
+ * @param string $file Filename to get the informations for
+ * @return array
+ */
+ protected function _getFileName($file)
+ {
+ $rename = array();
+ foreach ($this->_files as $value) {
+ if ($value['source'] == '*') {
+ if (!isset($rename['source'])) {
+ $rename = $value;
+ $rename['source'] = $file;
+ }
+ }
+
+ if ($value['source'] == $file) {
+ $rename = $value;
+ }
+ }
+
+ if (!isset($rename['source'])) {
+ return $file;
+ }
+
+ if (!isset($rename['target']) or ($rename['target'] == '*')) {
+ $rename['target'] = $rename['source'];
+ }
+
+ if (is_dir($rename['target'])) {
+ $name = basename($rename['source']);
+ $last = $rename['target'][strlen($rename['target']) - 1];
+ if (($last != '/') and ($last != '\\')) {
+ $rename['target'] .= DIRECTORY_SEPARATOR;
+ }
+
+ $rename['target'] .= $name;
+ }
+
+ return $rename;
+ }
+}
diff --git a/library/vendor/Zend/Filter/File/UpperCase.php b/library/vendor/Zend/Filter/File/UpperCase.php
new file mode 100644
index 0000000..318e33e
--- /dev/null
+++ b/library/vendor/Zend/Filter/File/UpperCase.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_StringToUpper
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_File_UpperCase extends Zend_Filter_StringToUpper
+{
+ /**
+ * Adds options to the filter at initiation
+ *
+ * @param string $options
+ */
+ public function __construct($options = null)
+ {
+ if (!empty($options)) {
+ $this->setEncoding($options);
+ }
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Does a lowercase on the content of the given file
+ *
+ * @param string $value Full path of file to change
+ * @return string The given $value
+ * @throws Zend_Filter_Exception
+ */
+ public function filter($value)
+ {
+ if (!file_exists($value)) {
+ throw new Zend_Filter_Exception("File '$value' not found");
+ }
+
+ if (!is_writable($value)) {
+ throw new Zend_Filter_Exception("File '$value' is not writable");
+ }
+
+ $content = file_get_contents($value);
+ if (!$content) {
+ throw new Zend_Filter_Exception("Problem while reading file '$value'");
+ }
+
+ $content = parent::filter($content);
+ $result = file_put_contents($value, $content);
+
+ if (!$result) {
+ throw new Zend_Filter_Exception("Problem while writing file '$value'");
+ }
+
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/Filter/HtmlEntities.php b/library/vendor/Zend/Filter/HtmlEntities.php
new file mode 100644
index 0000000..5ba7ad8
--- /dev/null
+++ b/library/vendor/Zend/Filter/HtmlEntities.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_HtmlEntities implements Zend_Filter_Interface
+{
+ /**
+ * Corresponds to the second htmlentities() argument
+ *
+ * @var integer
+ */
+ protected $_quoteStyle;
+
+ /**
+ * Corresponds to the third htmlentities() argument
+ *
+ * @var string
+ */
+ protected $_encoding;
+
+ /**
+ * Corresponds to the forth htmlentities() argument
+ *
+ * @var unknown_type
+ */
+ protected $_doubleQuote;
+
+ /**
+ * Sets filter options
+ *
+ * @param integer|array $quoteStyle
+ * @param string $charSet
+ * @return void
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['quotestyle'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['charset'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (!isset($options['quotestyle'])) {
+ $options['quotestyle'] = ENT_COMPAT;
+ }
+
+ if (!isset($options['encoding'])) {
+ $options['encoding'] = 'UTF-8';
+ }
+ if (isset($options['charset'])) {
+ $options['encoding'] = $options['charset'];
+ }
+
+ if (!isset($options['doublequote'])) {
+ $options['doublequote'] = true;
+ }
+
+ $this->setQuoteStyle($options['quotestyle']);
+ $this->setEncoding($options['encoding']);
+ $this->setDoubleQuote($options['doublequote']);
+ }
+
+ /**
+ * Returns the quoteStyle option
+ *
+ * @return integer
+ */
+ public function getQuoteStyle()
+ {
+ return $this->_quoteStyle;
+ }
+
+ /**
+ * Sets the quoteStyle option
+ *
+ * @param integer $quoteStyle
+ * @return Zend_Filter_HtmlEntities Provides a fluent interface
+ */
+ public function setQuoteStyle($quoteStyle)
+ {
+ $this->_quoteStyle = $quoteStyle;
+ return $this;
+ }
+
+
+ /**
+ * Get encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Set encoding
+ *
+ * @param string $value
+ * @return Zend_Filter_HtmlEntities
+ */
+ public function setEncoding($value)
+ {
+ $this->_encoding = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Returns the charSet option
+ *
+ * Proxies to {@link getEncoding()}
+ *
+ * @return string
+ */
+ public function getCharSet()
+ {
+ return $this->getEncoding();
+ }
+
+ /**
+ * Sets the charSet option
+ *
+ * Proxies to {@link setEncoding()}
+ *
+ * @param string $charSet
+ * @return Zend_Filter_HtmlEntities Provides a fluent interface
+ */
+ public function setCharSet($charSet)
+ {
+ return $this->setEncoding($charSet);
+ }
+
+ /**
+ * Returns the doubleQuote option
+ *
+ * @return boolean
+ */
+ public function getDoubleQuote()
+ {
+ return $this->_doubleQuote;
+ }
+
+ /**
+ * Sets the doubleQuote option
+ *
+ * @param boolean $doubleQuote
+ * @return Zend_Filter_HtmlEntities Provides a fluent interface
+ */
+ public function setDoubleQuote($doubleQuote)
+ {
+ $this->_doubleQuote = (boolean) $doubleQuote;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, converting characters to their corresponding HTML entity
+ * equivalents where they exist
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $filtered = htmlentities((string) $value, $this->getQuoteStyle(), $this->getEncoding(), $this->getDoubleQuote());
+ if (strlen((string) $value) && !strlen($filtered)) {
+ if (!function_exists('iconv')) {
+ throw new Zend_Filter_Exception('Encoding mismatch has resulted in htmlentities errors');
+ }
+ $enc = $this->getEncoding();
+ $value = iconv('', $enc . '//IGNORE', (string) $value);
+ $filtered = htmlentities($value, $this->getQuoteStyle(), $enc, $this->getDoubleQuote());
+ if (!strlen($filtered)) {
+ throw new Zend_Filter_Exception('Encoding mismatch has resulted in htmlentities errors');
+ }
+ }
+ return $filtered;
+ }
+}
diff --git a/library/vendor/Zend/Filter/Inflector.php b/library/vendor/Zend/Filter/Inflector.php
new file mode 100644
index 0000000..2192f84
--- /dev/null
+++ b/library/vendor/Zend/Filter/Inflector.php
@@ -0,0 +1,523 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @see Zend_Loader_PluginLoader
+ */
+
+/**
+ * Filter chain for string inflection
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Inflector implements Zend_Filter_Interface
+{
+ /**
+ * @var Zend_Loader_PluginLoader_Interface
+ */
+ protected $_pluginLoader = null;
+
+ /**
+ * @var string
+ */
+ protected $_target = null;
+
+ /**
+ * @var bool
+ */
+ protected $_throwTargetExceptionsOn = true;
+
+ /**
+ * @var string
+ */
+ protected $_targetReplacementIdentifier = ':';
+
+ /**
+ * @var array
+ */
+ protected $_rules = array();
+
+ /**
+ * Constructor
+ *
+ * @param string|array $options Options to set
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+
+ if (!empty($options)) {
+ $temp['target'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['rules'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['throwTargetExceptionsOn'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['targetReplacementIdentifier'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ $this->setOptions($options);
+ }
+
+ /**
+ * Retreive PluginLoader
+ *
+ * @return Zend_Loader_PluginLoader_Interface
+ */
+ public function getPluginLoader()
+ {
+ if (!$this->_pluginLoader instanceof Zend_Loader_PluginLoader_Interface) {
+ $this->_pluginLoader = new Zend_Loader_PluginLoader(array('Zend_Filter_' => 'Zend/Filter/'), __CLASS__);
+ }
+
+ return $this->_pluginLoader;
+ }
+
+ /**
+ * Set PluginLoader
+ *
+ * @param Zend_Loader_PluginLoader_Interface $pluginLoader
+ * @return Zend_Filter_Inflector
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader_Interface $pluginLoader)
+ {
+ $this->_pluginLoader = $pluginLoader;
+ return $this;
+ }
+
+ /**
+ * Use Zend_Config object to set object state
+ *
+ * @deprecated Use setOptions() instead
+ * @param Zend_Config $config
+ * @return Zend_Filter_Inflector
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ return $this->setOptions($config);
+ }
+
+ /**
+ * Set options
+ *
+ * @param array $options
+ * @return Zend_Filter_Inflector
+ */
+ public function setOptions($options) {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ // Set Präfix Path
+ if (array_key_exists('filterPrefixPath', $options)) {
+ if (!is_scalar($options['filterPrefixPath'])) {
+ foreach ($options['filterPrefixPath'] as $prefix => $path) {
+ $this->addFilterPrefixPath($prefix, $path);
+ }
+ }
+ }
+
+ if (array_key_exists('throwTargetExceptionsOn', $options)) {
+ $this->setThrowTargetExceptionsOn($options['throwTargetExceptionsOn']);
+ }
+
+ if (array_key_exists('targetReplacementIdentifier', $options)) {
+ $this->setTargetReplacementIdentifier($options['targetReplacementIdentifier']);
+ }
+
+ if (array_key_exists('target', $options)) {
+ $this->setTarget($options['target']);
+ }
+
+ if (array_key_exists('rules', $options)) {
+ $this->addRules($options['rules']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Convienence method to add prefix and path to PluginLoader
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Filter_Inflector
+ */
+ public function addFilterPrefixPath($prefix, $path)
+ {
+ $this->getPluginLoader()->addPrefixPath($prefix, $path);
+ return $this;
+ }
+
+ /**
+ * Set Whether or not the inflector should throw an exception when a replacement
+ * identifier is still found within an inflected target.
+ *
+ * @param bool $throwTargetExceptions
+ * @return Zend_Filter_Inflector
+ */
+ public function setThrowTargetExceptionsOn($throwTargetExceptionsOn)
+ {
+ $this->_throwTargetExceptionsOn = ($throwTargetExceptionsOn == true) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Will exceptions be thrown?
+ *
+ * @return bool
+ */
+ public function isThrowTargetExceptionsOn()
+ {
+ return $this->_throwTargetExceptionsOn;
+ }
+
+ /**
+ * Set the Target Replacement Identifier, by default ':'
+ *
+ * @param string $targetReplacementIdentifier
+ * @return Zend_Filter_Inflector
+ */
+ public function setTargetReplacementIdentifier($targetReplacementIdentifier)
+ {
+ if ($targetReplacementIdentifier) {
+ $this->_targetReplacementIdentifier = (string) $targetReplacementIdentifier;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Target Replacement Identifier
+ *
+ * @return string
+ */
+ public function getTargetReplacementIdentifier()
+ {
+ return $this->_targetReplacementIdentifier;
+ }
+
+ /**
+ * Set a Target
+ * ex: 'scripts/:controller/:action.:suffix'
+ *
+ * @param string
+ * @return Zend_Filter_Inflector
+ */
+ public function setTarget($target)
+ {
+ $this->_target = (string) $target;
+ return $this;
+ }
+
+ /**
+ * Retrieve target
+ *
+ * @return string
+ */
+ public function getTarget()
+ {
+ return $this->_target;
+ }
+
+ /**
+ * Set Target Reference
+ *
+ * @param reference $target
+ * @return Zend_Filter_Inflector
+ */
+ public function setTargetReference(&$target)
+ {
+ $this->_target =& $target;
+ return $this;
+ }
+
+ /**
+ * SetRules() is the same as calling addRules() with the exception that it
+ * clears the rules before adding them.
+ *
+ * @param array $rules
+ * @return Zend_Filter_Inflector
+ */
+ public function setRules(Array $rules)
+ {
+ $this->clearRules();
+ $this->addRules($rules);
+ return $this;
+ }
+
+ /**
+ * AddRules(): multi-call to setting filter rules.
+ *
+ * If prefixed with a ":" (colon), a filter rule will be added. If not
+ * prefixed, a static replacement will be added.
+ *
+ * ex:
+ * array(
+ * ':controller' => array('CamelCaseToUnderscore','StringToLower'),
+ * ':action' => array('CamelCaseToUnderscore','StringToLower'),
+ * 'suffix' => 'phtml'
+ * );
+ *
+ * @param array
+ * @return Zend_Filter_Inflector
+ */
+ public function addRules(Array $rules)
+ {
+ $keys = array_keys($rules);
+ foreach ($keys as $spec) {
+ if ($spec[0] == ':') {
+ $this->addFilterRule($spec, $rules[$spec]);
+ } else {
+ $this->setStaticRule($spec, $rules[$spec]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get rules
+ *
+ * By default, returns all rules. If a $spec is provided, will return those
+ * rules if found, false otherwise.
+ *
+ * @param string $spec
+ * @return array|false
+ */
+ public function getRules($spec = null)
+ {
+ if (null !== $spec) {
+ $spec = $this->_normalizeSpec($spec);
+ if (isset($this->_rules[$spec])) {
+ return $this->_rules[$spec];
+ }
+ return false;
+ }
+
+ return $this->_rules;
+ }
+
+ /**
+ * getRule() returns a rule set by setFilterRule(), a numeric index must be provided
+ *
+ * @param string $spec
+ * @param int $index
+ * @return Zend_Filter_Interface|false
+ */
+ public function getRule($spec, $index)
+ {
+ $spec = $this->_normalizeSpec($spec);
+ if (isset($this->_rules[$spec]) && is_array($this->_rules[$spec])) {
+ if (isset($this->_rules[$spec][$index])) {
+ return $this->_rules[$spec][$index];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * ClearRules() clears the rules currently in the inflector
+ *
+ * @return Zend_Filter_Inflector
+ */
+ public function clearRules()
+ {
+ $this->_rules = array();
+ return $this;
+ }
+
+ /**
+ * Set a filtering rule for a spec. $ruleSet can be a string, Filter object
+ * or an array of strings or filter objects.
+ *
+ * @param string $spec
+ * @param array|string|Zend_Filter_Interface $ruleSet
+ * @return Zend_Filter_Inflector
+ */
+ public function setFilterRule($spec, $ruleSet)
+ {
+ $spec = $this->_normalizeSpec($spec);
+ $this->_rules[$spec] = array();
+ return $this->addFilterRule($spec, $ruleSet);
+ }
+
+ /**
+ * Add a filter rule for a spec
+ *
+ * @param mixed $spec
+ * @param mixed $ruleSet
+ * @return void
+ */
+ public function addFilterRule($spec, $ruleSet)
+ {
+ $spec = $this->_normalizeSpec($spec);
+ if (!isset($this->_rules[$spec])) {
+ $this->_rules[$spec] = array();
+ }
+
+ if (!is_array($ruleSet)) {
+ $ruleSet = array($ruleSet);
+ }
+
+ if (is_string($this->_rules[$spec])) {
+ $temp = $this->_rules[$spec];
+ $this->_rules[$spec] = array();
+ $this->_rules[$spec][] = $temp;
+ }
+
+ foreach ($ruleSet as $rule) {
+ $this->_rules[$spec][] = $this->_getRule($rule);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a static rule for a spec. This is a single string value
+ *
+ * @param string $name
+ * @param string $value
+ * @return Zend_Filter_Inflector
+ */
+ public function setStaticRule($name, $value)
+ {
+ $name = $this->_normalizeSpec($name);
+ $this->_rules[$name] = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Set Static Rule Reference.
+ *
+ * This allows a consuming class to pass a property or variable
+ * in to be referenced when its time to build the output string from the
+ * target.
+ *
+ * @param string $name
+ * @param mixed $reference
+ * @return Zend_Filter_Inflector
+ */
+ public function setStaticRuleReference($name, &$reference)
+ {
+ $name = $this->_normalizeSpec($name);
+ $this->_rules[$name] =& $reference;
+ return $this;
+ }
+
+ /**
+ * Inflect
+ *
+ * @param string|array $source
+ * @return string
+ */
+ public function filter($source)
+ {
+ // clean source
+ foreach ( (array) $source as $sourceName => $sourceValue) {
+ $source[ltrim($sourceName, ':')] = $sourceValue;
+ }
+
+ $pregQuotedTargetReplacementIdentifier = preg_quote($this->_targetReplacementIdentifier, '#');
+ $processedParts = array();
+
+ foreach ($this->_rules as $ruleName => $ruleValue) {
+ if (isset($source[$ruleName])) {
+ if (is_string($ruleValue)) {
+ // overriding the set rule
+ $processedParts['#'.$pregQuotedTargetReplacementIdentifier.$ruleName.'#'] = str_replace('\\', '\\\\', $source[$ruleName]);
+ } elseif (is_array($ruleValue)) {
+ $processedPart = $source[$ruleName];
+ foreach ($ruleValue as $ruleFilter) {
+ $processedPart = $ruleFilter->filter($processedPart);
+ }
+ $processedParts['#'.$pregQuotedTargetReplacementIdentifier.$ruleName.'#'] = str_replace('\\', '\\\\', $processedPart);
+ }
+ } elseif (is_string($ruleValue)) {
+ $processedParts['#'.$pregQuotedTargetReplacementIdentifier.$ruleName.'#'] = str_replace('\\', '\\\\', $ruleValue);
+ }
+ }
+
+ // all of the values of processedParts would have been str_replace('\\', '\\\\', ..)'d to disable preg_replace backreferences
+ $inflectedTarget = preg_replace(array_keys($processedParts), array_values($processedParts), $this->_target);
+
+ if ($this->_throwTargetExceptionsOn && (preg_match('#(?='.$pregQuotedTargetReplacementIdentifier.'[A-Za-z]{1})#', $inflectedTarget) == true)) {
+ throw new Zend_Filter_Exception('A replacement identifier ' . $this->_targetReplacementIdentifier . ' was found inside the inflected target, perhaps a rule was not satisfied with a target source? Unsatisfied inflected target: ' . $inflectedTarget);
+ }
+
+ return $inflectedTarget;
+ }
+
+ /**
+ * Normalize spec string
+ *
+ * @param string $spec
+ * @return string
+ */
+ protected function _normalizeSpec($spec)
+ {
+ return ltrim((string) $spec, ':&');
+ }
+
+ /**
+ * Resolve named filters and convert them to filter objects.
+ *
+ * @param string $rule
+ * @return Zend_Filter_Interface
+ */
+ protected function _getRule($rule)
+ {
+ if ($rule instanceof Zend_Filter_Interface) {
+ return $rule;
+ }
+
+ $rule = (string) $rule;
+
+ $className = $this->getPluginLoader()->load($rule);
+ $ruleObject = new $className();
+ if (!$ruleObject instanceof Zend_Filter_Interface) {
+ throw new Zend_Filter_Exception('No class named ' . $rule . ' implementing Zend_Filter_Interface could be found');
+ }
+
+ return $ruleObject;
+ }
+}
diff --git a/library/vendor/Zend/Filter/Input.php b/library/vendor/Zend/Filter/Input.php
new file mode 100644
index 0000000..0c245a1
--- /dev/null
+++ b/library/vendor/Zend/Filter/Input.php
@@ -0,0 +1,1196 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * @see Zend_Filter
+ */
+
+/**
+ * @see Zend_Validate
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Input
+{
+
+ const ALLOW_EMPTY = 'allowEmpty';
+ const BREAK_CHAIN = 'breakChainOnFailure';
+ const DEFAULT_VALUE = 'default';
+ const MESSAGES = 'messages';
+ const ESCAPE_FILTER = 'escapeFilter';
+ const FIELDS = 'fields';
+ const FILTER = 'filter';
+ const FILTER_CHAIN = 'filterChain';
+ const MISSING_MESSAGE = 'missingMessage';
+ const INPUT_NAMESPACE = 'inputNamespace';
+ const VALIDATOR_NAMESPACE = 'validatorNamespace';
+ const FILTER_NAMESPACE = 'filterNamespace';
+ const NOT_EMPTY_MESSAGE = 'notEmptyMessage';
+ const PRESENCE = 'presence';
+ const PRESENCE_OPTIONAL = 'optional';
+ const PRESENCE_REQUIRED = 'required';
+ const RULE = 'rule';
+ const RULE_WILDCARD = '*';
+ const VALIDATE = 'validate';
+ const VALIDATOR = 'validator';
+ const VALIDATOR_CHAIN = 'validatorChain';
+ const VALIDATOR_CHAIN_COUNT = 'validatorChainCount';
+
+ /**
+ * @var array Input data, before processing.
+ */
+ protected $_data = array();
+
+ /**
+ * @var array Association of rules to filters.
+ */
+ protected $_filterRules = array();
+
+ /**
+ * @var array Association of rules to validators.
+ */
+ protected $_validatorRules = array();
+
+ /**
+ * @var array After processing data, this contains mapping of valid fields
+ * to field values.
+ */
+ protected $_validFields = array();
+
+ /**
+ * @var array After processing data, this contains mapping of validation
+ * rules that did not pass validation to the array of messages returned
+ * by the validator chain.
+ */
+ protected $_invalidMessages = array();
+
+ /**
+ * @var array After processing data, this contains mapping of validation
+ * rules that did not pass validation to the array of error identifiers
+ * returned by the validator chain.
+ */
+ protected $_invalidErrors = array();
+
+ /**
+ * @var array After processing data, this contains mapping of validation
+ * rules in which some fields were missing to the array of messages
+ * indicating which fields were missing.
+ */
+ protected $_missingFields = array();
+
+ /**
+ * @var array After processing, this contains a copy of $_data elements
+ * that were not mentioned in any validation rule.
+ */
+ protected $_unknownFields = array();
+
+ /**
+ * @var Zend_Filter_Interface The filter object that is run on values
+ * returned by the getEscaped() method.
+ */
+ protected $_defaultEscapeFilter = null;
+
+ /**
+ * Plugin loaders
+ * @var array
+ */
+ protected $_loaders = array();
+
+ /**
+ * @var array Default values to use when processing filters and validators.
+ */
+ protected $_defaults = array(
+ self::ALLOW_EMPTY => false,
+ self::BREAK_CHAIN => false,
+ self::ESCAPE_FILTER => 'HtmlEntities',
+ self::MISSING_MESSAGE => "Field '%field%' is required by rule '%rule%', but the field is missing",
+ self::NOT_EMPTY_MESSAGE => "You must give a non-empty value for field '%field%'",
+ self::PRESENCE => self::PRESENCE_OPTIONAL
+ );
+
+ /**
+ * @var boolean Set to False initially, this is set to True after the
+ * input data have been processed. Reset to False in setData() method.
+ */
+ protected $_processed = false;
+
+ /**
+ * Translation object
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Is translation disabled?
+ * @var Boolean
+ */
+ protected $_translatorDisabled = false;
+
+ /**
+ * @param array $filterRules
+ * @param array $validatorRules
+ * @param array $data OPTIONAL
+ * @param array $options OPTIONAL
+ */
+ public function __construct($filterRules, $validatorRules, array $data = null, array $options = null)
+ {
+ if ($options) {
+ $this->setOptions($options);
+ }
+
+ $this->_filterRules = (array) $filterRules;
+ $this->_validatorRules = (array) $validatorRules;
+
+ if ($data) {
+ $this->setData($data);
+ }
+ }
+
+ /**
+ * @param mixed $namespaces
+ * @return Zend_Filter_Input
+ * @deprecated since 1.5.0RC1 - use addFilterPrefixPath() or addValidatorPrefixPath instead.
+ */
+ public function addNamespace($namespaces)
+ {
+ if (!is_array($namespaces)) {
+ $namespaces = array($namespaces);
+ }
+
+ foreach ($namespaces as $namespace) {
+ $prefix = $namespace;
+ $path = str_replace('_', DIRECTORY_SEPARATOR, $prefix);
+ $this->addFilterPrefixPath($prefix, $path);
+ $this->addValidatorPrefixPath($prefix, $path);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add prefix path for all elements
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Filter_Input
+ */
+ public function addFilterPrefixPath($prefix, $path)
+ {
+ $this->getPluginLoader(self::FILTER)->addPrefixPath($prefix, $path);
+
+ return $this;
+ }
+
+ /**
+ * Add prefix path for all elements
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Filter_Input
+ */
+ public function addValidatorPrefixPath($prefix, $path)
+ {
+ $this->getPluginLoader(self::VALIDATE)->addPrefixPath($prefix, $path);
+
+ return $this;
+ }
+
+ /**
+ * Set plugin loaders for use with decorators and elements
+ *
+ * @param Zend_Loader_PluginLoader_Interface $loader
+ * @param string $type 'filter' or 'validate'
+ * @return Zend_Filter_Input
+ * @throws Zend_Filter_Exception on invalid type
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
+ {
+ $type = strtolower($type);
+ switch ($type) {
+ case self::FILTER:
+ case self::VALIDATE:
+ $this->_loaders[$type] = $loader;
+ return $this;
+ default:
+ throw new Zend_Filter_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve plugin loader for given type
+ *
+ * $type may be one of:
+ * - filter
+ * - validator
+ *
+ * If a plugin loader does not exist for the given type, defaults are
+ * created.
+ *
+ * @param string $type 'filter' or 'validate'
+ * @return Zend_Loader_PluginLoader_Interface
+ * @throws Zend_Filter_Exception on invalid type
+ */
+ public function getPluginLoader($type)
+ {
+ $type = strtolower($type);
+ if (!isset($this->_loaders[$type])) {
+ switch ($type) {
+ case self::FILTER:
+ $prefixSegment = 'Zend_Filter_';
+ $pathSegment = 'Zend/Filter/';
+ break;
+ case self::VALIDATE:
+ $prefixSegment = 'Zend_Validate_';
+ $pathSegment = 'Zend/Validate/';
+ break;
+ default:
+ throw new Zend_Filter_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+
+ $this->_loaders[$type] = new Zend_Loader_PluginLoader(
+ array($prefixSegment => $pathSegment)
+ );
+ }
+
+ return $this->_loaders[$type];
+ }
+
+ /**
+ * @return array
+ */
+ public function getMessages()
+ {
+ $this->_process();
+ return array_merge($this->_invalidMessages, $this->_missingFields);
+ }
+
+ /**
+ * @return array
+ */
+ public function getErrors()
+ {
+ $this->_process();
+ return $this->_invalidErrors;
+ }
+
+ /**
+ * @return array
+ */
+ public function getInvalid()
+ {
+ $this->_process();
+ return $this->_invalidMessages;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMissing()
+ {
+ $this->_process();
+ return $this->_missingFields;
+ }
+
+ /**
+ * @return array
+ */
+ public function getUnknown()
+ {
+ $this->_process();
+ return $this->_unknownFields;
+ }
+
+ /**
+ * @param string $fieldName OPTIONAL
+ * @return mixed
+ */
+ public function getEscaped($fieldName = null)
+ {
+ $this->_process();
+ $this->_getDefaultEscapeFilter();
+
+ if ($fieldName === null) {
+ return $this->_escapeRecursive($this->_validFields);
+ }
+ if (array_key_exists($fieldName, $this->_validFields)) {
+ return $this->_escapeRecursive($this->_validFields[$fieldName]);
+ }
+ return null;
+ }
+
+ /**
+ * @param mixed $value
+ * @return mixed
+ */
+ protected function _escapeRecursive($data)
+ {
+ if($data === null) {
+ return $data;
+ }
+
+ if (!is_array($data)) {
+ return $this->_getDefaultEscapeFilter()->filter($data);
+ }
+ foreach ($data as &$element) {
+ $element = $this->_escapeRecursive($element);
+ }
+ return $data;
+ }
+
+ /**
+ * @param string $fieldName OPTIONAL
+ * @return mixed
+ */
+ public function getUnescaped($fieldName = null)
+ {
+ $this->_process();
+ if ($fieldName === null) {
+ return $this->_validFields;
+ }
+ if (array_key_exists($fieldName, $this->_validFields)) {
+ return $this->_validFields[$fieldName];
+ }
+ return null;
+ }
+
+ /**
+ * @param string $fieldName
+ * @return mixed
+ */
+ public function __get($fieldName)
+ {
+ return $this->getEscaped($fieldName);
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasInvalid()
+ {
+ $this->_process();
+ return !(empty($this->_invalidMessages));
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasMissing()
+ {
+ $this->_process();
+ return !(empty($this->_missingFields));
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasUnknown()
+ {
+ $this->_process();
+ return !(empty($this->_unknownFields));
+ }
+
+ /**
+ * @return boolean
+ */
+ public function hasValid()
+ {
+ $this->_process();
+ return !(empty($this->_validFields));
+ }
+
+ /**
+ * @param string $fieldName
+ * @return boolean
+ */
+ public function isValid($fieldName = null)
+ {
+ $this->_process();
+ if ($fieldName === null) {
+ return !($this->hasMissing() || $this->hasInvalid());
+ }
+ return array_key_exists($fieldName, $this->_validFields);
+ }
+
+ /**
+ * @param string $fieldName
+ * @return boolean
+ */
+ public function __isset($fieldName)
+ {
+ $this->_process();
+ return isset($this->_validFields[$fieldName]);
+ }
+
+ /**
+ * @return Zend_Filter_Input
+ * @throws Zend_Filter_Exception
+ */
+ public function process()
+ {
+ $this->_process();
+ if ($this->hasInvalid()) {
+ throw new Zend_Filter_Exception("Input has invalid fields");
+ }
+ if ($this->hasMissing()) {
+ throw new Zend_Filter_Exception("Input has missing fields");
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $data
+ * @return Zend_Filter_Input
+ */
+ public function setData(array $data)
+ {
+ $this->_data = $data;
+
+ /**
+ * Reset to initial state
+ */
+ $this->_validFields = array();
+ $this->_invalidMessages = array();
+ $this->_invalidErrors = array();
+ $this->_missingFields = array();
+ $this->_unknownFields = array();
+
+ $this->_processed = false;
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $escapeFilter
+ * @return Zend_Filter_Interface
+ */
+ public function setDefaultEscapeFilter($escapeFilter)
+ {
+ if (is_string($escapeFilter) || is_array($escapeFilter)) {
+ $escapeFilter = $this->_getFilter($escapeFilter);
+ }
+ if (!$escapeFilter instanceof Zend_Filter_Interface) {
+ throw new Zend_Filter_Exception('Escape filter specified does not implement Zend_Filter_Interface');
+ }
+ $this->_defaultEscapeFilter = $escapeFilter;
+ return $escapeFilter;
+ }
+
+ /**
+ * @param array $options
+ * @return Zend_Filter_Input
+ * @throws Zend_Filter_Exception if an unknown option is given
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $option => $value) {
+ switch ($option) {
+ case self::ESCAPE_FILTER:
+ $this->setDefaultEscapeFilter($value);
+ break;
+ case self::INPUT_NAMESPACE:
+ $this->addNamespace($value);
+ break;
+ case self::VALIDATOR_NAMESPACE:
+ if(is_string($value)) {
+ $value = array($value);
+ }
+
+ foreach($value AS $prefix) {
+ $this->addValidatorPrefixPath(
+ $prefix,
+ str_replace('_', DIRECTORY_SEPARATOR, $prefix)
+ );
+ }
+ break;
+ case self::FILTER_NAMESPACE:
+ if(is_string($value)) {
+ $value = array($value);
+ }
+
+ foreach($value AS $prefix) {
+ $this->addFilterPrefixPath(
+ $prefix,
+ str_replace('_', DIRECTORY_SEPARATOR, $prefix)
+ );
+ }
+ break;
+ case self::ALLOW_EMPTY:
+ case self::BREAK_CHAIN:
+ case self::MISSING_MESSAGE:
+ case self::NOT_EMPTY_MESSAGE:
+ case self::PRESENCE:
+ $this->_defaults[$option] = $value;
+ break;
+ default:
+ throw new Zend_Filter_Exception("Unknown option '$option'");
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set translation object
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @return Zend_Filter_Input
+ */
+ public function setTranslator($translator = null)
+ {
+ if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ throw new Zend_Validate_Exception('Invalid translator specified');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return translation object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ if ($this->_translator === null) {
+ if (Zend_Registry::isRegistered('Zend_Translate')) {
+ $translator = Zend_Registry::get('Zend_Translate');
+ if ($translator instanceof Zend_Translate_Adapter) {
+ return $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ return $translator->getAdapter();
+ }
+ }
+ }
+
+ return $this->_translator;
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_Filter_Input
+ */
+ public function setDisableTranslator($flag)
+ {
+ $this->_translatorDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ return $this->_translatorDisabled;
+ }
+
+ /*
+ * Protected methods
+ */
+
+ /**
+ * @return void
+ */
+ protected function _filter()
+ {
+ foreach ($this->_filterRules as $ruleName => &$filterRule) {
+ /**
+ * Make sure we have an array representing this filter chain.
+ * Don't typecast to (array) because it might be a Zend_Filter object
+ */
+ if (!is_array($filterRule)) {
+ $filterRule = array($filterRule);
+ }
+
+ /**
+ * Filters are indexed by integer, metacommands are indexed by string.
+ * Pick out the filters.
+ */
+ $filterList = array();
+ foreach ($filterRule as $key => $value) {
+ if (is_int($key)) {
+ $filterList[] = $value;
+ }
+ }
+
+ /**
+ * Use defaults for filter metacommands.
+ */
+ $filterRule[self::RULE] = $ruleName;
+ if (!isset($filterRule[self::FIELDS])) {
+ $filterRule[self::FIELDS] = $ruleName;
+ }
+
+ /**
+ * Load all the filter classes and add them to the chain.
+ */
+ if (!isset($filterRule[self::FILTER_CHAIN])) {
+ $filterRule[self::FILTER_CHAIN] = new Zend_Filter();
+ foreach ($filterList as $filter) {
+ if (is_string($filter) || is_array($filter)) {
+ $filter = $this->_getFilter($filter);
+ }
+ $filterRule[self::FILTER_CHAIN]->addFilter($filter);
+ }
+ }
+
+ /**
+ * If the ruleName is the special wildcard rule,
+ * then apply the filter chain to all input data.
+ * Else just process the field named by the rule.
+ */
+ if ($ruleName == self::RULE_WILDCARD) {
+ foreach (array_keys($this->_data) as $field) {
+ $this->_filterRule(array_merge($filterRule, array(self::FIELDS => $field)));
+ }
+ } else {
+ $this->_filterRule($filterRule);
+ }
+ }
+ }
+
+ /**
+ * @param array $filterRule
+ * @return void
+ */
+ protected function _filterRule(array $filterRule)
+ {
+ $field = $filterRule[self::FIELDS];
+ if (!array_key_exists($field, $this->_data)) {
+ return;
+ }
+ if (is_array($this->_data[$field])) {
+ foreach ($this->_data[$field] as $key => $value) {
+ $this->_data[$field][$key] = $filterRule[self::FILTER_CHAIN]->filter($value);
+ }
+ } else {
+ $this->_data[$field] =
+ $filterRule[self::FILTER_CHAIN]->filter($this->_data[$field]);
+ }
+ }
+
+ /**
+ * @return Zend_Filter_Interface
+ */
+ protected function _getDefaultEscapeFilter()
+ {
+ if ($this->_defaultEscapeFilter !== null) {
+ return $this->_defaultEscapeFilter;
+ }
+ return $this->setDefaultEscapeFilter($this->_defaults[self::ESCAPE_FILTER]);
+ }
+
+ /**
+ * @param string $rule
+ * @param string $field
+ * @return string
+ */
+ protected function _getMissingMessage($rule, $field)
+ {
+ $message = $this->_defaults[self::MISSING_MESSAGE];
+
+ if (null !== ($translator = $this->getTranslator())) {
+ if ($translator->isTranslated(self::MISSING_MESSAGE)) {
+ $message = $translator->translate(self::MISSING_MESSAGE);
+ } else {
+ $message = $translator->translate($message);
+ }
+ }
+
+ $message = str_replace('%rule%', $rule, $message);
+ $message = str_replace('%field%', $field, $message);
+ return $message;
+ }
+
+ /**
+ * @return string
+ */
+ protected function _getNotEmptyMessage($rule, $field)
+ {
+ $message = $this->_defaults[self::NOT_EMPTY_MESSAGE];
+
+ if (null !== ($translator = $this->getTranslator())) {
+ if ($translator->isTranslated(self::NOT_EMPTY_MESSAGE)) {
+ $message = $translator->translate(self::NOT_EMPTY_MESSAGE);
+ } else {
+ $message = $translator->translate($message);
+ }
+ }
+
+ $message = str_replace('%rule%', $rule, $message);
+ $message = str_replace('%field%', $field, $message);
+ return $message;
+ }
+
+ /**
+ * @return void
+ */
+ protected function _process()
+ {
+ if ($this->_processed === false) {
+ $this->_filter();
+ $this->_validate();
+ $this->_processed = true;
+ }
+ }
+
+ /**
+ * @return void
+ */
+ protected function _validate()
+ {
+ /**
+ * Special case: if there are no validators, treat all fields as valid.
+ */
+ if (!$this->_validatorRules) {
+ $this->_validFields = $this->_data;
+ $this->_data = array();
+ return;
+ }
+
+ // remember the default not empty message in case we want to temporarily change it
+ $preserveDefaultNotEmptyMessage = $this->_defaults[self::NOT_EMPTY_MESSAGE];
+
+ foreach ($this->_validatorRules as $ruleName => &$validatorRule) {
+ /**
+ * Make sure we have an array representing this validator chain.
+ * Don't typecast to (array) because it might be a Zend_Validate object
+ */
+ if (!is_array($validatorRule)) {
+ $validatorRule = array($validatorRule);
+ }
+
+ /**
+ * Validators are indexed by integer, metacommands are indexed by string.
+ * Pick out the validators.
+ */
+ $validatorList = array();
+ foreach ($validatorRule as $key => $value) {
+ if (is_int($key)) {
+ $validatorList[$key] = $value;
+ }
+ }
+
+ /**
+ * Use defaults for validation metacommands.
+ */
+ $validatorRule[self::RULE] = $ruleName;
+ if (!isset($validatorRule[self::FIELDS])) {
+ $validatorRule[self::FIELDS] = $ruleName;
+ }
+ if (!isset($validatorRule[self::BREAK_CHAIN])) {
+ $validatorRule[self::BREAK_CHAIN] = $this->_defaults[self::BREAK_CHAIN];
+ }
+ if (!isset($validatorRule[self::PRESENCE])) {
+ $validatorRule[self::PRESENCE] = $this->_defaults[self::PRESENCE];
+ }
+ if (!isset($validatorRule[self::ALLOW_EMPTY])) {
+ $foundNotEmptyValidator = false;
+
+ foreach ($validatorRule as $rule) {
+ if ($rule === 'NotEmpty') {
+ $foundNotEmptyValidator = true;
+ // field may not be empty, we are ready
+ break 1;
+ }
+
+ if (is_array($rule)) {
+ $keys = array_keys($rule);
+ $classKey = array_shift($keys);
+ if (isset($rule[$classKey])) {
+ $ruleClass = $rule[$classKey];
+ if ($ruleClass === 'NotEmpty') {
+ $foundNotEmptyValidator = true;
+ // field may not be empty, we are ready
+ break 1;
+ }
+ }
+ }
+
+ // we must check if it is an object before using instanceof
+ if (!is_object($rule)) {
+ // it cannot be a NotEmpty validator, skip this one
+ continue;
+ }
+
+ if($rule instanceof Zend_Validate_NotEmpty) {
+ $foundNotEmptyValidator = true;
+ // field may not be empty, we are ready
+ break 1;
+ }
+ }
+
+ if (!$foundNotEmptyValidator) {
+ $validatorRule[self::ALLOW_EMPTY] = $this->_defaults[self::ALLOW_EMPTY];
+ } else {
+ $validatorRule[self::ALLOW_EMPTY] = false;
+ }
+ }
+
+ if (!isset($validatorRule[self::MESSAGES])) {
+ $validatorRule[self::MESSAGES] = array();
+ } else if (!is_array($validatorRule[self::MESSAGES])) {
+ $validatorRule[self::MESSAGES] = array($validatorRule[self::MESSAGES]);
+ } else if (array_intersect_key($validatorList, $validatorRule[self::MESSAGES])) {
+ // this seems pointless... it just re-adds what it already has...
+ // I can disable all this and not a single unit test fails...
+ // There are now corresponding numeric keys in the validation rule messages array
+ // Treat it as a named messages list for all rule validators
+ $unifiedMessages = $validatorRule[self::MESSAGES];
+ $validatorRule[self::MESSAGES] = array();
+
+ foreach ($validatorList as $key => $validator) {
+ if (array_key_exists($key, $unifiedMessages)) {
+ $validatorRule[self::MESSAGES][$key] = $unifiedMessages[$key];
+ }
+ }
+ }
+
+ /**
+ * Load all the validator classes and add them to the chain.
+ */
+ if (!isset($validatorRule[self::VALIDATOR_CHAIN])) {
+ $validatorRule[self::VALIDATOR_CHAIN] = new Zend_Validate();
+
+ foreach ($validatorList as $key => $validator) {
+ if (is_string($validator) || is_array($validator)) {
+ $validator = $this->_getValidator($validator);
+ }
+
+ if (isset($validatorRule[self::MESSAGES][$key])) {
+ $value = $validatorRule[self::MESSAGES][$key];
+ if (is_array($value)) {
+ $validator->setMessages($value);
+ } else {
+ $validator->setMessage($value);
+ }
+
+ if ($validator instanceof Zend_Validate_NotEmpty) {
+ /** we are changing the defaults here, this is alright if all subsequent validators are also a not empty
+ * validator, but it goes wrong if one of them is not AND is required!!!
+ * that is why we restore the default value at the end of this loop
+ */
+ if (is_array($value)) {
+ $temp = $value; // keep the original value
+ $this->_defaults[self::NOT_EMPTY_MESSAGE] = array_pop($temp);
+ unset($temp);
+ } else {
+ $this->_defaults[self::NOT_EMPTY_MESSAGE] = $value;
+ }
+ }
+ }
+
+ $validatorRule[self::VALIDATOR_CHAIN]->addValidator($validator, $validatorRule[self::BREAK_CHAIN]);
+ }
+ $validatorRule[self::VALIDATOR_CHAIN_COUNT] = count($validatorList);
+ }
+
+ /**
+ * If the ruleName is the special wildcard rule,
+ * then apply the validator chain to all input data.
+ * Else just process the field named by the rule.
+ */
+ if ($ruleName == self::RULE_WILDCARD) {
+ foreach (array_keys($this->_data) as $field) {
+ $this->_validateRule(array_merge($validatorRule, array(self::FIELDS => $field)));
+ }
+ } else {
+ $this->_validateRule($validatorRule);
+ }
+
+ // reset the default not empty message
+ $this->_defaults[self::NOT_EMPTY_MESSAGE] = $preserveDefaultNotEmptyMessage;
+ }
+
+
+
+ /**
+ * Unset fields in $_data that have been added to other arrays.
+ * We have to wait until all rules have been processed because
+ * a given field may be referenced by multiple rules.
+ */
+ foreach (array_merge(array_keys($this->_missingFields), array_keys($this->_invalidMessages)) as $rule) {
+ foreach ((array) $this->_validatorRules[$rule][self::FIELDS] as $field) {
+ unset($this->_data[$field]);
+ }
+ }
+ foreach ($this->_validFields as $field => $value) {
+ unset($this->_data[$field]);
+ }
+
+ /**
+ * Anything left over in $_data is an unknown field.
+ */
+ $this->_unknownFields = $this->_data;
+ }
+
+ /**
+ * @param array $validatorRule
+ * @return void
+ */
+ protected function _validateRule(array $validatorRule)
+ {
+ /**
+ * Get one or more data values from input, and check for missing fields.
+ * Apply defaults if fields are missing.
+ */
+ $data = array();
+ foreach ((array) $validatorRule[self::FIELDS] as $key => $field) {
+ if (array_key_exists($field, $this->_data)) {
+ $data[$field] = $this->_data[$field];
+ } else if (isset($validatorRule[self::DEFAULT_VALUE])) {
+ /** @todo according to this code default value can't be an array. It has to be reviewed */
+ if (!is_array($validatorRule[self::DEFAULT_VALUE])) {
+ // Default value is a scalar
+ $data[$field] = $validatorRule[self::DEFAULT_VALUE];
+ } else {
+ // Default value is an array. Search for corresponding key
+ if (isset($validatorRule[self::DEFAULT_VALUE][$key])) {
+ $data[$field] = $validatorRule[self::DEFAULT_VALUE][$key];
+ } else if ($validatorRule[self::PRESENCE] == self::PRESENCE_REQUIRED) {
+ // Default value array is provided, but it doesn't have an entry for current field
+ // and presence is required
+ $this->_missingFields[$validatorRule[self::RULE]][] =
+ $this->_getMissingMessage($validatorRule[self::RULE], $field);
+ }
+ }
+ } else if ($validatorRule[self::PRESENCE] == self::PRESENCE_REQUIRED) {
+ $this->_missingFields[$validatorRule[self::RULE]][] =
+ $this->_getMissingMessage($validatorRule[self::RULE], $field);
+ }
+ }
+
+ /**
+ * If any required fields are missing, break the loop.
+ */
+ if (isset($this->_missingFields[$validatorRule[self::RULE]]) && count($this->_missingFields[$validatorRule[self::RULE]]) > 0) {
+ return;
+ }
+
+ /**
+ * Evaluate the inputs against the validator chain.
+ */
+ if (count((array) $validatorRule[self::FIELDS]) > 1) {
+ if (!$validatorRule[self::ALLOW_EMPTY]) {
+ $emptyFieldsFound = false;
+ $errorsList = array();
+ $messages = array();
+
+ foreach ($data as $fieldKey => $field) {
+ // if there is no Zend_Validate_NotEmpty instance in the rules, we will use the default
+ if (!($notEmptyValidator = $this->_getNotEmptyValidatorInstance($validatorRule))) {
+ $notEmptyValidator = $this->_getValidator('NotEmpty');
+ $notEmptyValidator->setMessage($this->_getNotEmptyMessage($validatorRule[self::RULE], $fieldKey));
+ }
+
+ if (!$notEmptyValidator->isValid($field)) {
+ foreach ($notEmptyValidator->getMessages() as $messageKey => $message) {
+ if (!isset($messages[$messageKey])) {
+ $messages[$messageKey] = $message;
+ } else {
+ $messages[] = $message;
+ }
+ }
+ $errorsList[] = $notEmptyValidator->getErrors();
+ $emptyFieldsFound = true;
+ }
+ }
+
+ if ($emptyFieldsFound) {
+ $this->_invalidMessages[$validatorRule[self::RULE]] = $messages;
+ $this->_invalidErrors[$validatorRule[self::RULE]] = array_unique(call_user_func_array('array_merge', $errorsList));
+ return;
+ }
+ }
+
+ if (!$validatorRule[self::VALIDATOR_CHAIN]->isValid($data)) {
+ $this->_invalidMessages[$validatorRule[self::RULE]] = $validatorRule[self::VALIDATOR_CHAIN]->getMessages();
+ $this->_invalidErrors[$validatorRule[self::RULE]] = $validatorRule[self::VALIDATOR_CHAIN]->getErrors();
+ return;
+ }
+ } else if (count($data) > 0) {
+ // $data is actually a one element array
+ $fieldNames = array_keys($data);
+ $fieldName = reset($fieldNames);
+ $field = reset($data);
+
+ $failed = false;
+ if (!is_array($field)) {
+ $field = array($field);
+ }
+
+ // if there is no Zend_Validate_NotEmpty instance in the rules, we will use the default
+ if (!($notEmptyValidator = $this->_getNotEmptyValidatorInstance($validatorRule))) {
+ $notEmptyValidator = $this->_getValidator('NotEmpty');
+ $notEmptyValidator->setMessage($this->_getNotEmptyMessage($validatorRule[self::RULE], $fieldName));
+ }
+
+ if ($validatorRule[self::ALLOW_EMPTY]) {
+ $validatorChain = $validatorRule[self::VALIDATOR_CHAIN];
+ } else {
+ $validatorChain = new Zend_Validate();
+ $validatorChain->addValidator($notEmptyValidator, true /* Always break on failure */);
+ $validatorChain->addValidator($validatorRule[self::VALIDATOR_CHAIN]);
+ }
+
+ foreach ($field as $key => $value) {
+ if ($validatorRule[self::ALLOW_EMPTY] && !$notEmptyValidator->isValid($value)) {
+ // Field is empty AND it's allowed. Do nothing.
+ continue;
+ }
+
+ if (!$validatorChain->isValid($value)) {
+ if (isset($this->_invalidMessages[$validatorRule[self::RULE]])) {
+ $collectedMessages = $this->_invalidMessages[$validatorRule[self::RULE]];
+ } else {
+ $collectedMessages = array();
+ }
+
+ foreach ($validatorChain->getMessages() as $messageKey => $message) {
+ if (!isset($collectedMessages[$messageKey])) {
+ $collectedMessages[$messageKey] = $message;
+ } else {
+ $collectedMessages[] = $message;
+ }
+ }
+
+ $this->_invalidMessages[$validatorRule[self::RULE]] = $collectedMessages;
+ if (isset($this->_invalidErrors[$validatorRule[self::RULE]])) {
+ $this->_invalidErrors[$validatorRule[self::RULE]] = array_merge($this->_invalidErrors[$validatorRule[self::RULE]],
+ $validatorChain->getErrors());
+ } else {
+ $this->_invalidErrors[$validatorRule[self::RULE]] = $validatorChain->getErrors();
+ }
+ unset($this->_validFields[$fieldName]);
+ $failed = true;
+ if ($validatorRule[self::BREAK_CHAIN]) {
+ return;
+ }
+ }
+ }
+ if ($failed) {
+ return;
+ }
+ }
+
+ /**
+ * If we got this far, the inputs for this rule pass validation.
+ */
+ foreach ((array) $validatorRule[self::FIELDS] as $field) {
+ if (array_key_exists($field, $data)) {
+ $this->_validFields[$field] = $data[$field];
+ }
+ }
+ }
+
+ /**
+ * Check a validatorRule for the presence of a NotEmpty validator instance.
+ * The purpose is to preserve things like a custom message, that may have been
+ * set on the validator outside Zend_Filter_Input.
+ * @param array $validatorRule
+ * @return mixed false if none is found, Zend_Validate_NotEmpty instance if found
+ */
+ protected function _getNotEmptyValidatorInstance($validatorRule) {
+ foreach ($validatorRule as $rule => $value) {
+ if (is_object($value) and $value instanceof Zend_Validate_NotEmpty) {
+ return $value;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param mixed $classBaseName
+ * @return Zend_Filter_Interface
+ */
+ protected function _getFilter($classBaseName)
+ {
+ return $this->_getFilterOrValidator(self::FILTER, $classBaseName);
+ }
+
+ /**
+ * @param mixed $classBaseName
+ * @return Zend_Validate_Interface
+ */
+ protected function _getValidator($classBaseName)
+ {
+ return $this->_getFilterOrValidator(self::VALIDATE, $classBaseName);
+ }
+
+ /**
+ * @param string $type
+ * @param mixed $classBaseName
+ * @return Zend_Filter_Interface|Zend_Validate_Interface
+ * @throws Zend_Filter_Exception
+ */
+ protected function _getFilterOrValidator($type, $classBaseName)
+ {
+ $args = array();
+
+ if (is_array($classBaseName)) {
+ $args = $classBaseName;
+ $classBaseName = array_shift($args);
+ }
+
+ $interfaceName = 'Zend_' . ucfirst($type) . '_Interface';
+ $className = $this->getPluginLoader($type)->load(ucfirst($classBaseName));
+
+ $class = new ReflectionClass($className);
+
+ if (!$class->implementsInterface($interfaceName)) {
+ throw new Zend_Filter_Exception("Class '$className' based on basename '$classBaseName' must implement the '$interfaceName' interface");
+ }
+
+ if ($class->hasMethod('__construct')) {
+ $object = $class->newInstanceArgs(array_values($args));
+ } else {
+ $object = $class->newInstance();
+ }
+
+ return $object;
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/Int.php b/library/vendor/Zend/Filter/Int.php
new file mode 100644
index 0000000..ed7f9e8
--- /dev/null
+++ b/library/vendor/Zend/Filter/Int.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Int implements Zend_Filter_Interface
+{
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns (int) $value
+ *
+ * @param string $value
+ * @return integer
+ */
+ public function filter($value)
+ {
+ return (int) ((string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Interface.php b/library/vendor/Zend/Filter/Interface.php
new file mode 100644
index 0000000..3d9e65a
--- /dev/null
+++ b/library/vendor/Zend/Filter/Interface.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Filter_Interface
+{
+ /**
+ * Returns the result of filtering $value
+ *
+ * @param mixed $value
+ * @throws Zend_Filter_Exception If filtering $value is impossible
+ * @return mixed
+ */
+ public function filter($value);
+}
diff --git a/library/vendor/Zend/Filter/LocalizedToNormalized.php b/library/vendor/Zend/Filter/LocalizedToNormalized.php
new file mode 100644
index 0000000..ca6b045
--- /dev/null
+++ b/library/vendor/Zend/Filter/LocalizedToNormalized.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * Normalizes given localized input
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_LocalizedToNormalized implements Zend_Filter_Interface
+{
+ /**
+ * Set options
+ * @var array
+ */
+ protected $_options = array(
+ 'locale' => null,
+ 'date_format' => null,
+ 'precision' => null
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale to set
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Returns the set options
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets options to use
+ *
+ * @param array $options (Optional) Options to use
+ * @return Zend_Filter_LocalizedToNormalized
+ */
+ public function setOptions(array $options = null)
+ {
+ $this->_options = $options + $this->_options;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Normalizes the given input
+ *
+ * @param string $value Value to normalized
+ * @return string|array The normalized value
+ */
+ public function filter($value)
+ {
+ if (Zend_Locale_Format::isNumber($value, $this->_options)) {
+ return Zend_Locale_Format::getNumber($value, $this->_options);
+ } else if (($this->_options['date_format'] === null) && (strpos($value, ':') !== false)) {
+ // Special case, no date format specified, detect time input
+ return Zend_Locale_Format::getTime($value, $this->_options);
+ } else if (Zend_Locale_Format::checkDateFormat($value, $this->_options)) {
+ // Detect date or time input
+ return Zend_Locale_Format::getDate($value, $this->_options);
+ }
+
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/Filter/NormalizedToLocalized.php b/library/vendor/Zend/Filter/NormalizedToLocalized.php
new file mode 100644
index 0000000..54224ca
--- /dev/null
+++ b/library/vendor/Zend/Filter/NormalizedToLocalized.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * Localizes given normalized input
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_NormalizedToLocalized implements Zend_Filter_Interface
+{
+ /**
+ * Set options
+ */
+ protected $_options = array(
+ 'locale' => null,
+ 'date_format' => null,
+ 'precision' => null
+ );
+
+ /**
+ * Class constructor
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale to set
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Returns the set options
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets options to use
+ *
+ * @param array $options (Optional) Options to use
+ * @return Zend_Filter_LocalizedToNormalized
+ */
+ public function setOptions(array $options = null)
+ {
+ $this->_options = $options + $this->_options;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Normalizes the given input
+ *
+ * @param string $value Value to normalized
+ * @return string|array The normalized value
+ */
+ public function filter($value)
+ {
+ if (is_array($value)) {
+ $date = new Zend_Date($value, $this->_options['locale']);
+ return $date->toString($this->_options['date_format']);
+ } else if ($this->_options['precision'] === 0) {
+ return Zend_Locale_Format::toInteger($value, $this->_options);
+ } else if ($this->_options['precision'] === null) {
+ return Zend_Locale_Format::toFloat($value, $this->_options);
+ }
+
+ return Zend_Locale_Format::toNumber($value, $this->_options);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Null.php b/library/vendor/Zend/Filter/Null.php
new file mode 100644
index 0000000..14a79f2
--- /dev/null
+++ b/library/vendor/Zend/Filter/Null.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Null implements Zend_Filter_Interface
+{
+ const BOOLEAN = 1;
+ const INTEGER = 2;
+ const EMPTY_ARRAY = 4;
+ const STRING = 8;
+ const ZERO = 16;
+ const ALL = 31;
+
+ protected $_constants = array(
+ self::BOOLEAN => 'boolean',
+ self::INTEGER => 'integer',
+ self::EMPTY_ARRAY => 'array',
+ self::STRING => 'string',
+ self::ZERO => 'zero',
+ self::ALL => 'all'
+ );
+
+ /**
+ * Internal type to detect
+ *
+ * @var integer
+ */
+ protected $_type = self::ALL;
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $options OPTIONAL
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+ if (!empty($options)) {
+ $temp = array_shift($options);
+ }
+ $options = $temp;
+ } else if (is_array($options) && array_key_exists('type', $options)) {
+ $options = $options['type'];
+ }
+
+ if (!empty($options)) {
+ $this->setType($options);
+ }
+ }
+
+ /**
+ * Returns the set null types
+ *
+ * @return array
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Set the null types
+ *
+ * @param integer|array $type
+ * @throws Zend_Filter_Exception
+ * @return Zend_Filter_Null
+ */
+ public function setType($type = null)
+ {
+ if (is_array($type)) {
+ $detected = 0;
+ foreach($type as $value) {
+ if (is_int($value)) {
+ $detected += $value;
+ } else if (in_array($value, $this->_constants)) {
+ $detected += array_search($value, $this->_constants);
+ }
+ }
+
+ $type = $detected;
+ } else if (is_string($type)) {
+ if (in_array($type, $this->_constants)) {
+ $type = array_search($type, $this->_constants);
+ }
+ }
+
+ if (!is_int($type) || ($type < 0) || ($type > self::ALL)) {
+ throw new Zend_Filter_Exception('Unknown type');
+ }
+
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns null representation of $value, if value is empty and matches
+ * types that should be considered null.
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $type = $this->getType();
+
+ // STRING ZERO ('0')
+ if ($type >= self::ZERO) {
+ $type -= self::ZERO;
+ if (is_string($value) && ($value == '0')) {
+ return null;
+ }
+ }
+
+ // STRING ('')
+ if ($type >= self::STRING) {
+ $type -= self::STRING;
+ if (is_string($value) && ($value == '')) {
+ return null;
+ }
+ }
+
+ // EMPTY_ARRAY (array())
+ if ($type >= self::EMPTY_ARRAY) {
+ $type -= self::EMPTY_ARRAY;
+ if (is_array($value) && ($value == array())) {
+ return null;
+ }
+ }
+
+ // INTEGER (0)
+ if ($type >= self::INTEGER) {
+ $type -= self::INTEGER;
+ if (is_int($value) && ($value == 0)) {
+ return null;
+ }
+ }
+
+ // BOOLEAN (false)
+ if ($type >= self::BOOLEAN) {
+ $type -= self::BOOLEAN;
+ if (is_bool($value) && ($value == false)) {
+ return null;
+ }
+ }
+
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/Filter/PregReplace.php b/library/vendor/Zend/Filter/PregReplace.php
new file mode 100644
index 0000000..a4714d6
--- /dev/null
+++ b/library/vendor/Zend/Filter/PregReplace.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_PregReplace implements Zend_Filter_Interface
+{
+ /**
+ * Pattern to match
+ * @var mixed
+ */
+ protected $_matchPattern = null;
+
+ /**
+ * Replacement pattern
+ * @var mixed
+ */
+ protected $_replacement = '';
+
+ /**
+ * Is unicode enabled?
+ *
+ * @var bool
+ */
+ static protected $_unicodeSupportEnabled = null;
+
+ /**
+ * Is Unicode Support Enabled Utility function
+ *
+ * @return bool
+ */
+ static public function isUnicodeSupportEnabled()
+ {
+ if (self::$_unicodeSupportEnabled === null) {
+ self::_determineUnicodeSupport();
+ }
+
+ return self::$_unicodeSupportEnabled;
+ }
+
+ /**
+ * Method to cache the regex needed to determine if unicode support is available
+ *
+ * @return bool
+ */
+ static protected function _determineUnicodeSupport()
+ {
+ self::$_unicodeSupportEnabled = (@preg_match('/\pL/u', 'a')) ? true : false;
+ }
+
+ /**
+ * Constructor
+ * Supported options are
+ * 'match' => matching pattern
+ * 'replace' => replace with this
+ *
+ * @param string|array $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+ if (!empty($options)) {
+ $temp['match'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['replace'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (array_key_exists('match', $options)) {
+ $this->setMatchPattern($options['match']);
+ }
+
+ if (array_key_exists('replace', $options)) {
+ $this->setReplacement($options['replace']);
+ }
+ }
+
+ /**
+ * Set the match pattern for the regex being called within filter()
+ *
+ * @param mixed $match - same as the first argument of preg_replace
+ * @return Zend_Filter_PregReplace
+ */
+ public function setMatchPattern($match)
+ {
+ $this->_matchPattern = $match;
+ return $this;
+ }
+
+ /**
+ * Get currently set match pattern
+ *
+ * @return string
+ */
+ public function getMatchPattern()
+ {
+ return $this->_matchPattern;
+ }
+
+ /**
+ * Set the Replacement pattern/string for the preg_replace called in filter
+ *
+ * @param mixed $replacement - same as the second argument of preg_replace
+ * @return Zend_Filter_PregReplace
+ */
+ public function setReplacement($replacement)
+ {
+ $this->_replacement = $replacement;
+ return $this;
+ }
+
+ /**
+ * Get currently set replacement value
+ *
+ * @return string
+ */
+ public function getReplacement()
+ {
+ return $this->_replacement;
+ }
+
+ /**
+ * Perform regexp replacement as filter
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ if ($this->_matchPattern == null) {
+ throw new Zend_Filter_Exception(get_class($this) . ' does not have a valid MatchPattern set.');
+ }
+
+ return preg_replace($this->_matchPattern, $this->_replacement, $value);
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/RealPath.php b/library/vendor/Zend/Filter/RealPath.php
new file mode 100644
index 0000000..3cc3c99
--- /dev/null
+++ b/library/vendor/Zend/Filter/RealPath.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_RealPath implements Zend_Filter_Interface
+{
+ /**
+ * @var boolean $_pathExists
+ */
+ protected $_exists = true;
+
+ /**
+ * Class constructor
+ *
+ * @param boolean|Zend_Config $options Options to set
+ */
+ public function __construct($options = true)
+ {
+ $this->setExists($options);
+ }
+
+ /**
+ * Returns true if the filtered path must exist
+ *
+ * @return boolean
+ */
+ public function getExists()
+ {
+ return $this->_exists;
+ }
+
+ /**
+ * Sets if the path has to exist
+ * TRUE when the path must exist
+ * FALSE when not existing paths can be given
+ *
+ * @param boolean|Zend_Config $exists Path must exist
+ * @return Zend_Filter_RealPath
+ */
+ public function setExists($exists)
+ {
+ if ($exists instanceof Zend_Config) {
+ $exists = $exists->toArray();
+ }
+
+ if (is_array($exists)) {
+ if (isset($exists['exists'])) {
+ $exists = (boolean) $exists['exists'];
+ }
+ }
+
+ $this->_exists = (boolean) $exists;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns realpath($value)
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $path = (string) $value;
+ if ($this->_exists) {
+ return realpath($path);
+ }
+
+ $realpath = @realpath($path);
+ if ($realpath) {
+ return $realpath;
+ }
+
+ $drive = '';
+ if (substr(PHP_OS, 0, 3) == 'WIN') {
+ $path = preg_replace('/[\\\\\/]/', DIRECTORY_SEPARATOR, $path);
+ if (preg_match('/([a-zA-Z]\:)(.*)/', $path, $matches)) {
+ list($fullMatch, $drive, $path) = $matches;
+ } else {
+ $cwd = getcwd();
+ $drive = substr($cwd, 0, 2);
+ if (substr($path, 0, 1) != DIRECTORY_SEPARATOR) {
+ $path = substr($cwd, 3) . DIRECTORY_SEPARATOR . $path;
+ }
+ }
+ } elseif (substr($path, 0, 1) != DIRECTORY_SEPARATOR) {
+ $path = getcwd() . DIRECTORY_SEPARATOR . $path;
+ }
+
+ $stack = array();
+ $parts = explode(DIRECTORY_SEPARATOR, $path);
+ foreach ($parts as $dir) {
+ if (strlen($dir) && $dir !== '.') {
+ if ($dir == '..') {
+ array_pop($stack);
+ } else {
+ array_push($stack, $dir);
+ }
+ }
+ }
+
+ return $drive . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $stack);
+ }
+}
diff --git a/library/vendor/Zend/Filter/StringToLower.php b/library/vendor/Zend/Filter/StringToLower.php
new file mode 100644
index 0000000..c91072b
--- /dev/null
+++ b/library/vendor/Zend/Filter/StringToLower.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_StringToLower implements Zend_Filter_Interface
+{
+ /**
+ * Encoding for the input string
+ *
+ * @var string
+ */
+ protected $_encoding = null;
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $options OPTIONAL
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+ if (!empty($options)) {
+ $temp['encoding'] = array_shift($options);
+ }
+ $options = $temp;
+ }
+
+ if (!array_key_exists('encoding', $options) && function_exists('mb_internal_encoding')) {
+ $options['encoding'] = mb_internal_encoding();
+ }
+
+ if (array_key_exists('encoding', $options)) {
+ $this->setEncoding($options['encoding']);
+ }
+ }
+
+ /**
+ * Returns the set encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Set the input encoding for the given string
+ *
+ * @param string $encoding
+ * @return Zend_Filter_StringToLower Provides a fluent interface
+ * @throws Zend_Filter_Exception
+ */
+ public function setEncoding($encoding = null)
+ {
+ if ($encoding !== null) {
+ if (!function_exists('mb_strtolower')) {
+ throw new Zend_Filter_Exception('mbstring is required for this feature');
+ }
+
+ $encoding = (string) $encoding;
+ if (!in_array(strtolower($encoding), array_map('strtolower', mb_list_encodings()))) {
+ throw new Zend_Filter_Exception("The given encoding '$encoding' is not supported by mbstring");
+ }
+ }
+
+ $this->_encoding = $encoding;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, converting characters to lowercase as necessary
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ if ($this->_encoding !== null) {
+ return mb_strtolower((string) $value, $this->_encoding);
+ }
+
+ return strtolower((string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/StringToUpper.php b/library/vendor/Zend/Filter/StringToUpper.php
new file mode 100644
index 0000000..1276c8e
--- /dev/null
+++ b/library/vendor/Zend/Filter/StringToUpper.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_StringToUpper implements Zend_Filter_Interface
+{
+ /**
+ * Encoding for the input string
+ *
+ * @var string
+ */
+ protected $_encoding = null;
+
+ /**
+ * Constructor
+ *
+ * @param string|array $options OPTIONAL
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+ if (!empty($options)) {
+ $temp['encoding'] = array_shift($options);
+ }
+ $options = $temp;
+ }
+
+ if (!array_key_exists('encoding', $options) && function_exists('mb_internal_encoding')) {
+ $options['encoding'] = mb_internal_encoding();
+ }
+
+ if (array_key_exists('encoding', $options)) {
+ $this->setEncoding($options['encoding']);
+ }
+ }
+
+ /**
+ * Returns the set encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Set the input encoding for the given string
+ *
+ * @param string $encoding
+ * @return Zend_Filter_StringToUpper Provides a fluent interface
+ * @throws Zend_Filter_Exception
+ */
+ public function setEncoding($encoding = null)
+ {
+ if ($encoding !== null) {
+ if (!function_exists('mb_strtoupper')) {
+ throw new Zend_Filter_Exception('mbstring is required for this feature');
+ }
+
+ $encoding = (string) $encoding;
+ if (!in_array(strtolower($encoding), array_map('strtolower', mb_list_encodings()))) {
+ throw new Zend_Filter_Exception("The given encoding '$encoding' is not supported by mbstring");
+ }
+ }
+
+ $this->_encoding = $encoding;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, converting characters to uppercase as necessary
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ if ($this->_encoding) {
+ return mb_strtoupper((string) $value, $this->_encoding);
+ }
+
+ return strtoupper((string) $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/StringTrim.php b/library/vendor/Zend/Filter/StringTrim.php
new file mode 100644
index 0000000..8006ec9
--- /dev/null
+++ b/library/vendor/Zend/Filter/StringTrim.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_StringTrim implements Zend_Filter_Interface
+{
+ /**
+ * List of characters provided to the trim() function
+ *
+ * If this is null, then trim() is called with no specific character list,
+ * and its default behavior will be invoked, trimming whitespace.
+ *
+ * @var string|null
+ */
+ protected $_charList;
+
+ /**
+ * Sets filter options
+ *
+ * @param string|array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['charlist'] = array_shift($options);
+ $options = $temp;
+ }
+
+ if (array_key_exists('charlist', $options)) {
+ $this->setCharList($options['charlist']);
+ }
+ }
+
+ /**
+ * Returns the charList option
+ *
+ * @return string|null
+ */
+ public function getCharList()
+ {
+ return $this->_charList;
+ }
+
+ /**
+ * Sets the charList option
+ *
+ * @param string|null $charList
+ * @return Zend_Filter_StringTrim Provides a fluent interface
+ */
+ public function setCharList($charList)
+ {
+ $this->_charList = $charList;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value with characters stripped from the beginning and end
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ if (null === $this->_charList) {
+ return $this->_unicodeTrim((string) $value);
+ } else {
+ return $this->_unicodeTrim((string) $value, $this->_charList);
+ }
+ }
+
+ /**
+ * Unicode aware trim method
+ * Fixes a PHP problem
+ *
+ * @param string $value
+ * @param string $charlist
+ * @return string
+ */
+ protected function _unicodeTrim($value, $charlist = '\\\\s')
+ {
+ $chars = preg_replace(
+ array( '/[\^\-\]\\\]/S', '/\\\{4}/S', '/\//'),
+ array( '\\\\\\0', '\\', '\/' ),
+ $charlist
+ );
+
+ $pattern = '^[' . $chars . ']*|[' . $chars . ']*$';
+ return preg_replace("/$pattern/sSD", '', $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/StripNewlines.php b/library/vendor/Zend/Filter/StripNewlines.php
new file mode 100644
index 0000000..91f6342
--- /dev/null
+++ b/library/vendor/Zend/Filter/StripNewlines.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_StripNewlines implements Zend_Filter_Interface
+{
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns $value without newline control characters
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter ($value)
+ {
+ return str_replace(array("\n", "\r"), '', $value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/StripTags.php b/library/vendor/Zend/Filter/StripTags.php
new file mode 100644
index 0000000..99d7a86
--- /dev/null
+++ b/library/vendor/Zend/Filter/StripTags.php
@@ -0,0 +1,351 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_StripTags implements Zend_Filter_Interface
+{
+ /**
+ * Unique ID prefix used for allowing comments
+ */
+ const UNIQUE_ID_PREFIX = '__Zend_Filter_StripTags__';
+
+ /**
+ * Whether comments are allowed
+ *
+ * If false (the default), then comments are removed from the input string.
+ *
+ * This setting is now deprecated, and ignored internally.
+ *
+ * @deprecated
+ * @var boolean
+ */
+ public $commentsAllowed = false;
+
+ /**
+ * Array of allowed tags and allowed attributes for each allowed tag
+ *
+ * Tags are stored in the array keys, and the array values are themselves
+ * arrays of the attributes allowed for the corresponding tag.
+ *
+ * @var array
+ */
+ protected $_tagsAllowed = array();
+
+ /**
+ * Array of allowed attributes for all allowed tags
+ *
+ * Attributes stored here are allowed for all of the allowed tags.
+ *
+ * @var array
+ */
+ protected $_attributesAllowed = array();
+
+ /**
+ * Sets the filter options
+ * Allowed options are
+ * 'allowTags' => Tags which are allowed
+ * 'allowAttribs' => Attributes which are allowed
+ * 'allowComments' => Are comments allowed ?
+ *
+ * @param string|array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if ((!is_array($options)) || (is_array($options) && !array_key_exists('allowTags', $options) &&
+ !array_key_exists('allowAttribs', $options) && !array_key_exists('allowComments', $options))) {
+ $options = func_get_args();
+ $temp['allowTags'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['allowAttribs'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['allowComments'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (array_key_exists('allowTags', $options)) {
+ $this->setTagsAllowed($options['allowTags']);
+ }
+
+ if (array_key_exists('allowAttribs', $options)) {
+ $this->setAttributesAllowed($options['allowAttribs']);
+ }
+
+ if (array_key_exists('allowComments', $options)) {
+ $this->setCommentsAllowed($options['allowComments']);
+ }
+ }
+
+ /**
+ * Returns the commentsAllowed option
+ *
+ * This setting is now deprecated and ignored internally.
+ *
+ * @deprecated
+ * @return bool
+ */
+ public function getCommentsAllowed()
+ {
+ return $this->commentsAllowed;
+ }
+
+ /**
+ * Sets the commentsAllowed option
+ *
+ * This setting is now deprecated and ignored internally.
+ *
+ * @deprecated
+ * @param boolean $commentsAllowed
+ * @return Zend_Filter_StripTags Provides a fluent interface
+ */
+ public function setCommentsAllowed($commentsAllowed)
+ {
+ $this->commentsAllowed = (boolean) $commentsAllowed;
+ return $this;
+ }
+
+ /**
+ * Returns the tagsAllowed option
+ *
+ * @return array
+ */
+ public function getTagsAllowed()
+ {
+ return $this->_tagsAllowed;
+ }
+
+ /**
+ * Sets the tagsAllowed option
+ *
+ * @param array|string $tagsAllowed
+ * @return Zend_Filter_StripTags Provides a fluent interface
+ */
+ public function setTagsAllowed($tagsAllowed)
+ {
+ if (!is_array($tagsAllowed)) {
+ $tagsAllowed = array($tagsAllowed);
+ }
+
+ foreach ($tagsAllowed as $index => $element) {
+ // If the tag was provided without attributes
+ if (is_int($index) && is_string($element)) {
+ // Canonicalize the tag name
+ $tagName = strtolower($element);
+ // Store the tag as allowed with no attributes
+ $this->_tagsAllowed[$tagName] = array();
+ }
+ // Otherwise, if a tag was provided with attributes
+ else if (is_string($index) && (is_array($element) || is_string($element))) {
+ // Canonicalize the tag name
+ $tagName = strtolower($index);
+ // Canonicalize the attributes
+ if (is_string($element)) {
+ $element = array($element);
+ }
+ // Store the tag as allowed with the provided attributes
+ $this->_tagsAllowed[$tagName] = array();
+ foreach ($element as $attribute) {
+ if (is_string($attribute)) {
+ // Canonicalize the attribute name
+ $attributeName = strtolower($attribute);
+ $this->_tagsAllowed[$tagName][$attributeName] = null;
+ }
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the attributesAllowed option
+ *
+ * @return array
+ */
+ public function getAttributesAllowed()
+ {
+ return $this->_attributesAllowed;
+ }
+
+ /**
+ * Sets the attributesAllowed option
+ *
+ * @param array|string $attributesAllowed
+ * @return Zend_Filter_StripTags Provides a fluent interface
+ */
+ public function setAttributesAllowed($attributesAllowed)
+ {
+ if (!is_array($attributesAllowed)) {
+ $attributesAllowed = array($attributesAllowed);
+ }
+
+ // Store each attribute as allowed
+ foreach ($attributesAllowed as $attribute) {
+ if (is_string($attribute)) {
+ // Canonicalize the attribute name
+ $attributeName = strtolower($attribute);
+ $this->_attributesAllowed[$attributeName] = null;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * @todo improve docblock descriptions
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ $value = (string) $value;
+
+ // Strip HTML comments first
+ while (strpos($value, '<!--') !== false) {
+ $pos = strrpos($value, '<!--');
+ $start = substr($value, 0, $pos);
+ $value = substr($value, $pos);
+
+ // If there is no comment closing tag, strip whole text
+ if (!preg_match('/--\s*>/s', $value)) {
+ $value = '';
+ } else {
+ $value = preg_replace('/<(?:!(?:--[\s\S]*?--\s*)?(>))/s', '', $value);
+ }
+
+ $value = $start . $value;
+ }
+
+ // Initialize accumulator for filtered data
+ $dataFiltered = '';
+ // Parse the input data iteratively as regular pre-tag text followed by a
+ // tag; either may be empty strings
+ preg_match_all('/([^<]*)(<?[^>]*>?)/', (string) $value, $matches);
+
+ // Iterate over each set of matches
+ foreach ($matches[1] as $index => $preTag) {
+ // If the pre-tag text is non-empty, strip any ">" characters from it
+ if (strlen($preTag)) {
+ $preTag = str_replace('>', '', $preTag);
+ }
+ // If a tag exists in this match, then filter the tag
+ $tag = $matches[2][$index];
+ if (strlen($tag)) {
+ $tagFiltered = $this->_filterTag($tag);
+ } else {
+ $tagFiltered = '';
+ }
+ // Add the filtered pre-tag text and filtered tag to the data buffer
+ $dataFiltered .= $preTag . $tagFiltered;
+ }
+
+ // Return the filtered data
+ return $dataFiltered;
+ }
+
+ /**
+ * Filters a single tag against the current option settings
+ *
+ * @param string $tag
+ * @return string
+ */
+ protected function _filterTag($tag)
+ {
+ // Parse the tag into:
+ // 1. a starting delimiter (mandatory)
+ // 2. a tag name (if available)
+ // 3. a string of attributes (if available)
+ // 4. an ending delimiter (if available)
+ $isMatch = preg_match('~(</?)(\w*)((/(?!>)|[^/>])*)(/?>)~', $tag, $matches);
+
+ // If the tag does not match, then strip the tag entirely
+ if (!$isMatch) {
+ return '';
+ }
+
+ // Save the matches to more meaningfully named variables
+ $tagStart = $matches[1];
+ $tagName = strtolower($matches[2]);
+ $tagAttributes = $matches[3];
+ $tagEnd = $matches[5];
+
+ // If the tag is not an allowed tag, then remove the tag entirely
+ if (!isset($this->_tagsAllowed[$tagName])) {
+ return '';
+ }
+
+ // Trim the attribute string of whitespace at the ends
+ $tagAttributes = trim($tagAttributes);
+
+ // If there are non-whitespace characters in the attribute string
+ if (strlen($tagAttributes)) {
+ // Parse iteratively for well-formed attributes
+ preg_match_all('/([\w-]+)\s*=\s*(?:(")(.*?)"|(\')(.*?)\')/s', $tagAttributes, $matches);
+
+ // Initialize valid attribute accumulator
+ $tagAttributes = '';
+
+ // Iterate over each matched attribute
+ foreach ($matches[1] as $index => $attributeName) {
+ $attributeName = strtolower($attributeName);
+ $attributeDelimiter = empty($matches[2][$index]) ? $matches[4][$index] : $matches[2][$index];
+ $attributeValue = empty($matches[3][$index]) ? $matches[5][$index] : $matches[3][$index];
+
+ // If the attribute is not allowed, then remove it entirely
+ if (!array_key_exists($attributeName, $this->_tagsAllowed[$tagName])
+ && !array_key_exists($attributeName, $this->_attributesAllowed)) {
+ continue;
+ }
+ // Add the attribute to the accumulator
+ $tagAttributes .= " $attributeName=" . $attributeDelimiter
+ . $attributeValue . $attributeDelimiter;
+ }
+ }
+
+ // Reconstruct tags ending with "/>" as backwards-compatible XHTML tag
+ if (strpos($tagEnd, '/') !== false) {
+ $tagEnd = " $tagEnd";
+ }
+
+ // Return the filtered tag
+ return $tagStart . $tagName . $tagAttributes . $tagEnd;
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/CamelCaseToDash.php b/library/vendor/Zend/Filter/Word/CamelCaseToDash.php
new file mode 100644
index 0000000..4698947
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/CamelCaseToDash.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_CamelCaseToDash extends Zend_Filter_Word_CamelCaseToSeparator
+{
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct('-');
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/CamelCaseToSeparator.php b/library/vendor/Zend/Filter/Word/CamelCaseToSeparator.php
new file mode 100644
index 0000000..6592c45
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/CamelCaseToSeparator.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_CamelCaseToSeparator extends Zend_Filter_Word_Separator_Abstract
+{
+
+ public function filter($value)
+ {
+ if (self::isUnicodeSupportEnabled()) {
+ parent::setMatchPattern(array('#(?<=(?:\p{Lu}))(\p{Lu}\p{Ll})#','#(?<=(?:\p{Ll}|\p{Nd}))(\p{Lu})#'));
+ parent::setReplacement(array($this->_separator . '\1', $this->_separator . '\1'));
+ } else {
+ parent::setMatchPattern(array('#(?<=(?:[A-Z]))([A-Z]+)([A-Z][A-z])#', '#(?<=(?:[a-z0-9]))([A-Z])#'));
+ parent::setReplacement(array('\1' . $this->_separator . '\2', $this->_separator . '\1'));
+ }
+
+ return parent::filter($value);
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/Word/CamelCaseToUnderscore.php b/library/vendor/Zend/Filter/Word/CamelCaseToUnderscore.php
new file mode 100644
index 0000000..fc2a5d7
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/CamelCaseToUnderscore.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_CamelCaseToSeparator
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_CamelCaseToUnderscore extends Zend_Filter_Word_CamelCaseToSeparator
+{
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct('_');
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/DashToCamelCase.php b/library/vendor/Zend/Filter/Word/DashToCamelCase.php
new file mode 100644
index 0000000..c6ff5c1
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/DashToCamelCase.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_DashToCamelCase extends Zend_Filter_Word_SeparatorToCamelCase
+{
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct('-');
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/DashToSeparator.php b/library/vendor/Zend/Filter/Word/DashToSeparator.php
new file mode 100644
index 0000000..fe86149
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/DashToSeparator.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_DashToSeparator extends Zend_Filter_Word_Separator_Abstract
+{
+
+ public function filter($value)
+ {
+ $this->setMatchPattern('#-#');
+ $this->setReplacement($this->_separator);
+ return parent::filter($value);
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/DashToUnderscore.php b/library/vendor/Zend/Filter/Word/DashToUnderscore.php
new file mode 100644
index 0000000..19527dd
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/DashToUnderscore.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_DashToUnderscore extends Zend_Filter_Word_SeparatorToSeparator
+{
+ /**
+ * Constructor
+ *
+ * @param string $separator Space by default
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct('-', '_');
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/Separator/Abstract.php b/library/vendor/Zend/Filter/Word/Separator/Abstract.php
new file mode 100644
index 0000000..7a95034
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/Separator/Abstract.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @uses Zend_Filter_PregReplace
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Filter_Word_Separator_Abstract extends Zend_Filter_PregReplace
+{
+
+ protected $_separator = null;
+
+ /**
+ * Constructor
+ *
+ * @param string $separator Space by default
+ * @return void
+ */
+ public function __construct($separator = ' ')
+ {
+ $this->setSeparator($separator);
+ }
+
+ /**
+ * Sets a new seperator
+ *
+ * @param string $separator Seperator
+ * @return $this
+ */
+ public function setSeparator($separator)
+ {
+ if ($separator == null) {
+ throw new Zend_Filter_Exception('"' . $separator . '" is not a valid separator.');
+ }
+ $this->_separator = $separator;
+ return $this;
+ }
+
+ /**
+ * Returns the actual set seperator
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ return $this->_separator;
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/Word/SeparatorToCamelCase.php b/library/vendor/Zend/Filter/Word/SeparatorToCamelCase.php
new file mode 100644
index 0000000..7f53eb7
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/SeparatorToCamelCase.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_SeparatorToCamelCase extends Zend_Filter_Word_Separator_Abstract
+{
+
+ public function filter($value)
+ {
+ // a unicode safe way of converting characters to \x00\x00 notation
+ $pregQuotedSeparator = preg_quote($this->_separator, '#');
+
+ if (self::isUnicodeSupportEnabled()) {
+ parent::setMatchPattern(array('#('.$pregQuotedSeparator.')(\p{L}{1})#','#(^\p{Ll}{1})#'));
+ parent::setReplacement(array('Zend_Filter_Word_SeparatorToCamelCase', '_strtoupperArray'));
+ } else {
+ parent::setMatchPattern(array('#('.$pregQuotedSeparator.')([A-Za-z]{1})#','#(^[A-Za-z]{1})#'));
+ parent::setReplacement(array('Zend_Filter_Word_SeparatorToCamelCase', '_strtoupperArray'));
+ }
+
+ return preg_replace_callback($this->_matchPattern, $this->_replacement, $value);
+ }
+
+ /**
+ * @param array $matches
+ * @return string
+ */
+ private static function _strtoupperArray(array $matches)
+ {
+ if (array_key_exists(2, $matches)) {
+ return strtoupper($matches[2]);
+ }
+ return strtoupper($matches[1]);
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/Word/SeparatorToDash.php b/library/vendor/Zend/Filter/Word/SeparatorToDash.php
new file mode 100644
index 0000000..aeab938
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/SeparatorToDash.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_SeperatorToSeparator
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_SeparatorToDash extends Zend_Filter_Word_SeparatorToSeparator
+{
+ /**
+ * Constructor
+ *
+ * @param string $searchSeparator Seperator to search for change
+ * @return void
+ */
+ public function __construct($searchSeparator = ' ')
+ {
+ parent::__construct($searchSeparator, '-');
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/Word/SeparatorToSeparator.php b/library/vendor/Zend/Filter/Word/SeparatorToSeparator.php
new file mode 100644
index 0000000..d1ce7e9
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/SeparatorToSeparator.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_SeparatorToSeparator extends Zend_Filter_PregReplace
+{
+
+ protected $_searchSeparator = null;
+ protected $_replacementSeparator = null;
+
+ /**
+ * Constructor
+ *
+ * @param string $searchSeparator Seperator to search for
+ * @param string $replacementSeperator Seperator to replace with
+ * @return void
+ */
+ public function __construct($searchSeparator = ' ', $replacementSeparator = '-')
+ {
+ $this->setSearchSeparator($searchSeparator);
+ $this->setReplacementSeparator($replacementSeparator);
+ }
+
+ /**
+ * Sets a new seperator to search for
+ *
+ * @param string $separator Seperator to search for
+ * @return $this
+ */
+ public function setSearchSeparator($separator)
+ {
+ $this->_searchSeparator = $separator;
+ return $this;
+ }
+
+ /**
+ * Returns the actual set seperator to search for
+ *
+ * @return string
+ */
+ public function getSearchSeparator()
+ {
+ return $this->_searchSeparator;
+ }
+
+ /**
+ * Sets a new seperator which replaces the searched one
+ *
+ * @param string $separator Seperator which replaces the searched one
+ * @return $this
+ */
+ public function setReplacementSeparator($separator)
+ {
+ $this->_replacementSeparator = $separator;
+ return $this;
+ }
+
+ /**
+ * Returns the actual set seperator which replaces the searched one
+ *
+ * @return string
+ */
+ public function getReplacementSeparator()
+ {
+ return $this->_replacementSeparator;
+ }
+
+ /**
+ * Defined by Zend_Filter_Interface
+ *
+ * Returns the string $value, replacing the searched seperators with the defined ones
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filter($value)
+ {
+ return $this->_separatorToSeparatorFilter($value);
+ }
+
+ /**
+ * Do the real work, replaces the seperator to search for with the replacement seperator
+ *
+ * Returns the replaced string
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function _separatorToSeparatorFilter($value)
+ {
+ if ($this->_searchSeparator == null) {
+ throw new Zend_Filter_Exception('You must provide a search separator for this filter to work.');
+ }
+
+ $this->setMatchPattern('#' . preg_quote($this->_searchSeparator, '#') . '#');
+ $this->setReplacement($this->_replacementSeparator);
+ return parent::filter($value);
+ }
+
+}
diff --git a/library/vendor/Zend/Filter/Word/UnderscoreToCamelCase.php b/library/vendor/Zend/Filter/Word/UnderscoreToCamelCase.php
new file mode 100644
index 0000000..0c44700
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/UnderscoreToCamelCase.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_UnderscoreToCamelCase extends Zend_Filter_Word_SeparatorToCamelCase
+{
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct('_');
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/UnderscoreToDash.php b/library/vendor/Zend/Filter/Word/UnderscoreToDash.php
new file mode 100644
index 0000000..b63fd16
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/UnderscoreToDash.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_UnderscoreToDash extends Zend_Filter_Word_SeparatorToSeparator
+{
+ /**
+ * Constructor
+ *
+ * @param string $separator Space by default
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct('_', '-');
+ }
+}
diff --git a/library/vendor/Zend/Filter/Word/UnderscoreToSeparator.php b/library/vendor/Zend/Filter/Word/UnderscoreToSeparator.php
new file mode 100644
index 0000000..cfd7ed8
--- /dev/null
+++ b/library/vendor/Zend/Filter/Word/UnderscoreToSeparator.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Filter_PregReplace
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Filter_Word_UnderscoreToSeparator extends Zend_Filter_Word_SeparatorToSeparator
+{
+ /**
+ * Constructor
+ *
+ * @param string $separator Space by default
+ * @return void
+ */
+ public function __construct($replacementSeparator = ' ')
+ {
+ parent::__construct('_', $replacementSeparator);
+ }
+}
diff --git a/library/vendor/Zend/Form.php b/library/vendor/Zend/Form.php
new file mode 100644
index 0000000..91987ac
--- /dev/null
+++ b/library/vendor/Zend/Form.php
@@ -0,0 +1,3472 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** @see Zend_Validate_Interface */
+
+/**
+ * Zend_Form
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form implements Iterator, Countable, Zend_Validate_Interface
+{
+ /**#@+
+ * Plugin loader type constants
+ */
+ const DECORATOR = 'DECORATOR';
+ const ELEMENT = 'ELEMENT';
+ /**#@-*/
+
+ /**#@+
+ * Method type constants
+ */
+ const METHOD_DELETE = 'delete';
+ const METHOD_GET = 'get';
+ const METHOD_POST = 'post';
+ const METHOD_PUT = 'put';
+ /**#@-*/
+
+ /**#@+
+ * Encoding type constants
+ */
+ const ENCTYPE_URLENCODED = 'application/x-www-form-urlencoded';
+ const ENCTYPE_MULTIPART = 'multipart/form-data';
+ /**#@-*/
+
+ /**
+ * Form metadata and attributes
+ * @var array
+ */
+ protected $_attribs = array();
+
+ /**
+ * Decorators for rendering
+ * @var array
+ */
+ protected $_decorators = array();
+
+ /**
+ * Default display group class
+ * @var string
+ */
+ protected $_defaultDisplayGroupClass = 'Zend_Form_DisplayGroup';
+
+ /**
+ * Form description
+ * @var string
+ */
+ protected $_description;
+
+ /**
+ * Should we disable loading the default decorators?
+ * @var bool
+ */
+ protected $_disableLoadDefaultDecorators = false;
+
+ /**
+ * Display group prefix paths
+ * @var array
+ */
+ protected $_displayGroupPrefixPaths = array();
+
+ /**
+ * Groups of elements grouped for display purposes
+ * @var array
+ */
+ protected $_displayGroups = array();
+
+ /**
+ * Global decorators to apply to all elements
+ * @var null|array
+ */
+ protected $_elementDecorators;
+
+ /**
+ * Prefix paths to use when creating elements
+ * @var array
+ */
+ protected $_elementPrefixPaths = array();
+
+ /**
+ * Form elements
+ * @var array
+ */
+ protected $_elements = array();
+
+ /**
+ * Array to which elements belong (if any)
+ * @var string
+ */
+ protected $_elementsBelongTo;
+
+ /**
+ * Custom form-level error messages
+ * @var array
+ */
+ protected $_errorMessages = array();
+
+ /**
+ * Are there errors in the form?
+ * @var bool
+ */
+ protected $_errorsExist = false;
+
+ /**
+ * Has the form been manually flagged as an error?
+ * @var bool
+ */
+ protected $_errorsForced = false;
+
+ /**
+ * Form order
+ * @var int|null
+ */
+ protected $_formOrder;
+
+ /**
+ * Whether or not form elements are members of an array
+ * @var bool
+ */
+ protected $_isArray = false;
+
+ /**
+ * Form legend
+ * @var string
+ */
+ protected $_legend;
+
+ /**
+ * Plugin loaders
+ * @var array
+ */
+ protected $_loaders = array();
+
+ /**
+ * Allowed form methods
+ * @var array
+ */
+ protected $_methods = array('delete', 'get', 'post', 'put');
+
+ /**
+ * Order in which to display and iterate elements
+ * @var array
+ */
+ protected $_order = array();
+
+ /**
+ * Whether internal order has been updated or not
+ * @var bool
+ */
+ protected $_orderUpdated = false;
+
+ /**
+ * Sub form prefix paths
+ * @var array
+ */
+ protected $_subFormPrefixPaths = array();
+
+ /**
+ * Sub forms
+ * @var array
+ */
+ protected $_subForms = array();
+
+ /**
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Global default translation adapter
+ * @var Zend_Translate
+ */
+ protected static $_translatorDefault;
+
+ /**
+ * is the translator disabled?
+ * @var bool
+ */
+ protected $_translatorDisabled = false;
+
+ /**
+ * @var Zend_View_Interface
+ */
+ protected $_view;
+
+ /**
+ * @var bool
+ */
+ protected $_isRendered = false;
+
+ /**
+ * Constructor
+ *
+ * Registers form view helper as decorator
+ *
+ * @param mixed $options
+ */
+ public function __construct($options = null)
+ {
+ if (is_array($options)) {
+ $this->setOptions($options);
+ } elseif ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ }
+
+ // Extensions...
+ $this->init();
+
+ $this->loadDefaultDecorators();
+ }
+
+ /**
+ * Clone form object and all children
+ *
+ * @return void
+ */
+ public function __clone()
+ {
+ $elements = array();
+ foreach ($this->getElements() as $name => $element) {
+ $elements[] = clone $element;
+ }
+ $this->setElements($elements);
+
+ $subForms = array();
+ foreach ($this->getSubForms() as $name => $subForm) {
+ $subForms[$name] = clone $subForm;
+ }
+ $this->setSubForms($subForms);
+
+ $displayGroups = array();
+ foreach ($this->_displayGroups as $group) {
+ /** @var Zend_Form_DisplayGroup $clone */
+ $clone = clone $group;
+ $elements = array();
+ foreach ($clone->getElements() as $name => $e) {
+ $elements[] = $this->getElement($name);
+ }
+ $clone->setElements($elements);
+ $displayGroups[] = $clone;
+ }
+ $this->setDisplayGroups($displayGroups);
+ }
+
+ /**
+ * Reset values of form
+ *
+ * @return Zend_Form
+ */
+ public function reset()
+ {
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $element) {
+ $element->setValue(null);
+ }
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $subForm) {
+ $subForm->reset();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Initialize form (used by extending classes)
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set form state from options array
+ *
+ * @param array $options
+ * @return Zend_Form
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['prefixPath'])) {
+ $this->addPrefixPaths($options['prefixPath']);
+ unset($options['prefixPath']);
+ }
+
+ if (isset($options['elementPrefixPath'])) {
+ $this->addElementPrefixPaths($options['elementPrefixPath']);
+ unset($options['elementPrefixPath']);
+ }
+
+ if (isset($options['displayGroupPrefixPath'])) {
+ $this->addDisplayGroupPrefixPaths($options['displayGroupPrefixPath']);
+ unset($options['displayGroupPrefixPath']);
+ }
+
+ if (isset($options['elementDecorators'])) {
+ $this->_elementDecorators = $options['elementDecorators'];
+ unset($options['elementDecorators']);
+ }
+
+ if (isset($options['elements'])) {
+ $this->setElements($options['elements']);
+ unset($options['elements']);
+ }
+
+ if (isset($options['defaultDisplayGroupClass'])) {
+ $this->setDefaultDisplayGroupClass($options['defaultDisplayGroupClass']);
+ unset($options['defaultDisplayGroupClass']);
+ }
+
+ if (isset($options['displayGroupDecorators'])) {
+ $displayGroupDecorators = $options['displayGroupDecorators'];
+ unset($options['displayGroupDecorators']);
+ }
+
+ if (isset($options['elementsBelongTo'])) {
+ $elementsBelongTo = $options['elementsBelongTo'];
+ unset($options['elementsBelongTo']);
+ }
+
+ if (isset($options['attribs'])) {
+ $this->addAttribs($options['attribs']);
+ unset($options['attribs']);
+ }
+
+ if (isset($options['subForms'])) {
+ $this->addSubForms($options['subForms']);
+ unset($options['subForms']);
+ }
+
+ $forbidden = array(
+ 'Options', 'Config', 'PluginLoader', 'SubForms', 'Translator',
+ 'Attrib', 'Default',
+ );
+
+ foreach ($options as $key => $value) {
+ $normalized = ucfirst($key);
+ if (in_array($normalized, $forbidden)) {
+ continue;
+ }
+
+ $method = 'set' . $normalized;
+ if (method_exists($this, $method)) {
+ if($normalized == 'View' && !($value instanceof Zend_View_Interface)) {
+ continue;
+ }
+ $this->$method($value);
+ } else {
+ $this->setAttrib($key, $value);
+ }
+ }
+
+ if (isset($displayGroupDecorators)) {
+ $this->setDisplayGroupDecorators($displayGroupDecorators);
+ }
+
+ if (isset($elementsBelongTo)) {
+ $this->setElementsBelongTo($elementsBelongTo);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set form state from config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Form
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ return $this->setOptions($config->toArray());
+ }
+
+
+ // Loaders
+
+ /**
+ * Set plugin loaders for use with decorators and elements
+ *
+ * @param Zend_Loader_PluginLoader_Interface $loader
+ * @param string $type 'decorator' or 'element'
+ * @return Zend_Form
+ * @throws Zend_Form_Exception on invalid type
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type = null)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::DECORATOR:
+ case self::ELEMENT:
+ $this->_loaders[$type] = $loader;
+ return $this;
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Retrieve plugin loader for given type
+ *
+ * $type may be one of:
+ * - decorator
+ * - element
+ *
+ * If a plugin loader does not exist for the given type, defaults are
+ * created.
+ *
+ * @param string $type
+ * @return Zend_Loader_PluginLoader_Interface
+ * @throws Zend_Form_Exception
+ */
+ public function getPluginLoader($type = null)
+ {
+ $type = strtoupper($type);
+ if (!isset($this->_loaders[$type])) {
+ switch ($type) {
+ case self::DECORATOR:
+ $prefixSegment = 'Form_Decorator';
+ $pathSegment = 'Form/Decorator';
+ break;
+ case self::ELEMENT:
+ $prefixSegment = 'Form_Element';
+ $pathSegment = 'Form/Element';
+ break;
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+
+ $this->_loaders[$type] = new Zend_Loader_PluginLoader(
+ array('Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/')
+ );
+ }
+
+ return $this->_loaders[$type];
+ }
+
+ /**
+ * Add prefix path for plugin loader
+ *
+ * If no $type specified, assumes it is a base path for both filters and
+ * validators, and sets each according to the following rules:
+ * - decorators: $prefix = $prefix . '_Decorator'
+ * - elements: $prefix = $prefix . '_Element'
+ *
+ * Otherwise, the path prefix is set on the appropriate plugin loader.
+ *
+ * If $type is 'decorator', sets the path in the decorator plugin loader
+ * for all elements. Additionally, if no $type is provided,
+ * the prefix and path is added to both decorator and element
+ * plugin loader with following settings:
+ * $prefix . '_Decorator', $path . '/Decorator/'
+ * $prefix . '_Element', $path . '/Element/'
+ *
+ * @param string $prefix
+ * @param string $path
+ * @param string $type
+ * @return Zend_Form
+ * @throws Zend_Form_Exception for invalid type
+ */
+ public function addPrefixPath($prefix, $path, $type = null)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::DECORATOR:
+ case self::ELEMENT:
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($prefix, $path);
+ return $this;
+ case null:
+ $nsSeparator = (false !== strpos($prefix, '\\'))?'\\':'_';
+ $prefix = rtrim($prefix, $nsSeparator);
+ $path = rtrim($path, DIRECTORY_SEPARATOR);
+ foreach (array(self::DECORATOR, self::ELEMENT) as $type) {
+ $cType = ucfirst(strtolower($type));
+ $pluginPath = $path . DIRECTORY_SEPARATOR . $cType . DIRECTORY_SEPARATOR;
+ $pluginPrefix = $prefix . $nsSeparator . $cType;
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($pluginPrefix, $pluginPath);
+ }
+ return $this;
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Add many prefix paths at once
+ *
+ * @param array $spec
+ * @return Zend_Form
+ */
+ public function addPrefixPaths(array $spec)
+ {
+ if (isset($spec['prefix']) && isset($spec['path'])) {
+ return $this->addPrefixPath($spec['prefix'], $spec['path']);
+ }
+ foreach ($spec as $type => $paths) {
+ if (is_numeric($type) && is_array($paths)) {
+ $type = null;
+ if (isset($paths['prefix']) && isset($paths['path'])) {
+ if (isset($paths['type'])) {
+ $type = $paths['type'];
+ }
+ $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
+ }
+ } elseif (!is_numeric($type)) {
+ if (!isset($paths['prefix']) || !isset($paths['path'])) {
+ continue;
+ }
+ $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Add prefix path for all elements
+ *
+ * @param string $prefix
+ * @param string $path
+ * @param string $type
+ * @return Zend_Form
+ */
+ public function addElementPrefixPath($prefix, $path, $type = null)
+ {
+ $this->_elementPrefixPaths[] = array(
+ 'prefix' => $prefix,
+ 'path' => $path,
+ 'type' => $type,
+ );
+
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $element) {
+ $element->addPrefixPath($prefix, $path, $type);
+ }
+
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $subForm) {
+ $subForm->addElementPrefixPath($prefix, $path, $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add prefix paths for all elements
+ *
+ * @param array $spec
+ * @return Zend_Form
+ */
+ public function addElementPrefixPaths(array $spec)
+ {
+ $this->_elementPrefixPaths = $this->_elementPrefixPaths + $spec;
+
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $element) {
+ $element->addPrefixPaths($spec);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add prefix path for all display groups
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Form
+ */
+ public function addDisplayGroupPrefixPath($prefix, $path)
+ {
+ $this->_displayGroupPrefixPaths[] = array(
+ 'prefix' => $prefix,
+ 'path' => $path,
+ );
+
+ /** @var Zend_Form_DisplayGroup $group */
+ foreach ($this->getDisplayGroups() as $group) {
+ $group->addPrefixPath($prefix, $path);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add multiple display group prefix paths at once
+ *
+ * @param array $spec
+ * @return Zend_Form
+ */
+ public function addDisplayGroupPrefixPaths(array $spec)
+ {
+ foreach ($spec as $key => $value) {
+ if (is_string($value) && !is_numeric($key)) {
+ $this->addDisplayGroupPrefixPath($key, $value);
+ continue;
+ }
+
+ if (is_string($value) && is_numeric($key)) {
+ continue;
+ }
+
+ if (is_array($value)) {
+ $count = count($value);
+ if (array_keys($value) === range(0, $count - 1)) {
+ if ($count < 2) {
+ continue;
+ }
+ $prefix = array_shift($value);
+ $path = array_shift($value);
+ $this->addDisplayGroupPrefixPath($prefix, $path);
+ continue;
+ }
+ if (array_key_exists('prefix', $value) && array_key_exists('path', $value)) {
+ $this->addDisplayGroupPrefixPath($value['prefix'], $value['path']);
+ }
+ }
+ }
+ return $this;
+ }
+
+ // Form metadata:
+
+ /**
+ * Set form attribute
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return Zend_Form
+ */
+ public function setAttrib($key, $value)
+ {
+ $key = (string) $key;
+ $this->_attribs[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Add multiple form attributes at once
+ *
+ * @param array $attribs
+ * @return Zend_Form
+ */
+ public function addAttribs(array $attribs)
+ {
+ foreach ($attribs as $key => $value) {
+ $this->setAttrib($key, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Set multiple form attributes at once
+ *
+ * Overwrites any previously set attributes.
+ *
+ * @param array $attribs
+ * @return Zend_Form
+ */
+ public function setAttribs(array $attribs)
+ {
+ $this->clearAttribs();
+ return $this->addAttribs($attribs);
+ }
+
+ /**
+ * Retrieve a single form attribute
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getAttrib($key)
+ {
+ $key = (string) $key;
+ if (!isset($this->_attribs[$key])) {
+ return null;
+ }
+
+ return $this->_attribs[$key];
+ }
+
+ /**
+ * Retrieve all form attributes/metadata
+ *
+ * @return array
+ */
+ public function getAttribs()
+ {
+ return $this->_attribs;
+ }
+
+ /**
+ * Remove attribute
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function removeAttrib($key)
+ {
+ if (isset($this->_attribs[$key])) {
+ unset($this->_attribs[$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all form attributes
+ *
+ * @return Zend_Form
+ */
+ public function clearAttribs()
+ {
+ $this->_attribs = array();
+ return $this;
+ }
+
+ /**
+ * Set form action
+ *
+ * @param string $action
+ * @return Zend_Form
+ */
+ public function setAction($action)
+ {
+ return $this->setAttrib('action', (string) $action);
+ }
+
+ /**
+ * Get form action
+ *
+ * Sets default to '' if not set.
+ *
+ * @return string
+ */
+ public function getAction()
+ {
+ $action = $this->getAttrib('action');
+ if (null === $action) {
+ $action = '';
+ $this->setAction($action);
+ }
+ return $action;
+ }
+
+ /**
+ * Set form method
+ *
+ * Only values in {@link $_methods()} allowed
+ *
+ * @param string $method
+ * @return Zend_Form
+ * @throws Zend_Form_Exception
+ */
+ public function setMethod($method)
+ {
+ $method = strtolower($method);
+ if (!in_array($method, $this->_methods)) {
+ throw new Zend_Form_Exception(sprintf('"%s" is an invalid form method', $method));
+ }
+ $this->setAttrib('method', $method);
+ return $this;
+ }
+
+ /**
+ * Retrieve form method
+ *
+ * @return string
+ */
+ public function getMethod()
+ {
+ if (null === ($method = $this->getAttrib('method'))) {
+ $method = self::METHOD_POST;
+ $this->setAttrib('method', $method);
+ }
+ return strtolower($method);
+ }
+
+ /**
+ * Set encoding type
+ *
+ * @param string $value
+ * @return Zend_Form
+ */
+ public function setEnctype($value)
+ {
+ $this->setAttrib('enctype', $value);
+ return $this;
+ }
+
+ /**
+ * Get encoding type
+ *
+ * @return string
+ */
+ public function getEnctype()
+ {
+ if (null === ($enctype = $this->getAttrib('enctype'))) {
+ $enctype = self::ENCTYPE_URLENCODED;
+ $this->setAttrib('enctype', $enctype);
+ }
+ return $this->getAttrib('enctype');
+ }
+
+ /**
+ * Filter a name to only allow valid variable characters
+ *
+ * @param string $value
+ * @param bool $allowBrackets
+ * @return string
+ */
+ public function filterName($value, $allowBrackets = false)
+ {
+ $charset = '^a-zA-Z0-9_\x7f-\xff';
+ if ($allowBrackets) {
+ $charset .= '\[\]';
+ }
+ return preg_replace('/[' . $charset . ']/', '', (string) $value);
+ }
+
+ /**
+ * Set form name
+ *
+ * @param string $name
+ * @return Zend_Form
+ * @throws Zend_Form_Exception
+ */
+ public function setName($name)
+ {
+ $name = $this->filterName($name);
+ if ('' === (string)$name) {
+ throw new Zend_Form_Exception('Invalid name provided; must contain only valid variable characters and be non-empty');
+ }
+
+ return $this->setAttrib('name', $name);
+ }
+
+ /**
+ * Get name attribute
+ *
+ * @return null|string
+ */
+ public function getName()
+ {
+ return $this->getAttrib('name');
+ }
+
+ /**
+ * Get fully qualified name
+ *
+ * Places name as subitem of array and/or appends brackets.
+ *
+ * @return string
+ */
+ public function getFullyQualifiedName()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * Get element id
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (null !== ($id = $this->getAttrib('id'))) {
+ return $id;
+ }
+
+ $id = $this->getFullyQualifiedName();
+
+ // Bail early if no array notation detected
+ if (!strstr($id, '[')) {
+ return $id;
+ }
+
+ // Strip array notation
+ if ('[]' == substr($id, -2)) {
+ $id = substr($id, 0, strlen($id) - 2);
+ }
+ $id = str_replace('][', '-', $id);
+ $id = str_replace(array(']', '['), '-', $id);
+ $id = trim($id, '-');
+
+ return $id;
+ }
+
+ /**
+ * Set form legend
+ *
+ * @param string $value
+ * @return Zend_Form
+ */
+ public function setLegend($value)
+ {
+ $this->_legend = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Get form legend
+ *
+ * @return string
+ */
+ public function getLegend()
+ {
+ return $this->_legend;
+ }
+
+ /**
+ * Set form description
+ *
+ * @param string $value
+ * @return Zend_Form
+ */
+ public function setDescription($value)
+ {
+ $this->_description = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Retrieve form description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Set form order
+ *
+ * @param int $index
+ * @return Zend_Form
+ */
+ public function setOrder($index)
+ {
+ $this->_formOrder = (int) $index;
+ return $this;
+ }
+
+ /**
+ * Get form order
+ *
+ * @return int|null
+ */
+ public function getOrder()
+ {
+ return $this->_formOrder;
+ }
+
+ /**
+ * When calling renderFormElements or render this method
+ * is used to set $_isRendered member to prevent repeatedly
+ * merging belongsTo setting
+ */
+ protected function _setIsRendered()
+ {
+ $this->_isRendered = true;
+ return $this;
+ }
+
+ /**
+ * Get the value of $_isRendered member
+ */
+ protected function _getIsRendered()
+ {
+ return (bool)$this->_isRendered;
+ }
+
+ // Element interaction:
+
+ /**
+ * Add a new element
+ *
+ * $element may be either a string element type, or an object of type
+ * Zend_Form_Element. If a string element type is provided, $name must be
+ * provided, and $options may be optionally provided for configuring the
+ * element.
+ *
+ * If a Zend_Form_Element is provided, $name may be optionally provided,
+ * and any provided $options will be ignored.
+ *
+ * @param string|Zend_Form_Element $element
+ * @param string $name
+ * @param array|Zend_Config $options
+ * @throws Zend_Form_Exception on invalid element
+ * @return Zend_Form
+ */
+ public function addElement($element, $name = null, $options = null)
+ {
+ if (is_string($element)) {
+ if (null === $name) {
+ throw new Zend_Form_Exception(
+ 'Elements specified by string must have an accompanying name'
+ );
+ }
+
+ $this->_elements[$name] = $this->createElement($element, $name, $options);
+ } elseif ($element instanceof Zend_Form_Element) {
+ $prefixPaths = array();
+ $prefixPaths['decorator'] = $this->getPluginLoader('decorator')->getPaths();
+ if (!empty($this->_elementPrefixPaths)) {
+ $prefixPaths = array_merge($prefixPaths, $this->_elementPrefixPaths);
+ }
+
+ if (is_array($this->_elementDecorators)
+ && 0 == count($element->getDecorators())
+ ) {
+ $element->setDecorators($this->_elementDecorators);
+ }
+
+ if (null === $name) {
+ $name = $element->getName();
+ }
+
+ $this->_elements[$name] = $element;
+ $this->_elements[$name]->addPrefixPaths($prefixPaths);
+ } else {
+ throw new Zend_Form_Exception(
+ 'Element must be specified by string or Zend_Form_Element instance'
+ );
+ }
+
+ $this->_order[$name] = $this->_elements[$name]->getOrder();
+ $this->_orderUpdated = true;
+ $this->_setElementsBelongTo($name);
+
+ return $this;
+ }
+
+ /**
+ * Create an element
+ *
+ * Acts as a factory for creating elements. Elements created with this
+ * method will not be attached to the form, but will contain element
+ * settings as specified in the form object (including plugin loader
+ * prefix paths, default decorators, etc.).
+ *
+ * @param string $type
+ * @param string $name
+ * @param array|Zend_Config $options
+ * @throws Zend_Form_Exception
+ * @return Zend_Form_Element
+ */
+ public function createElement($type, $name, $options = null)
+ {
+ if (!is_string($type)) {
+ throw new Zend_Form_Exception('Element type must be a string indicating type');
+ }
+
+ if (!is_string($name)) {
+ throw new Zend_Form_Exception('Element name must be a string');
+ }
+
+ $prefixPaths = array();
+ $prefixPaths['decorator'] = $this->getPluginLoader('decorator')->getPaths();
+ if (!empty($this->_elementPrefixPaths)) {
+ $prefixPaths = array_merge($prefixPaths, $this->_elementPrefixPaths);
+ }
+
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if ((null === $options) || !is_array($options)) {
+ $options = array('prefixPath' => $prefixPaths);
+
+ if (is_array($this->_elementDecorators)) {
+ $options['decorators'] = $this->_elementDecorators;
+ }
+ } elseif (is_array($options)) {
+ if (array_key_exists('prefixPath', $options)) {
+ $options['prefixPath'] = array_merge($prefixPaths, $options['prefixPath']);
+ } else {
+ $options['prefixPath'] = $prefixPaths;
+ }
+
+ if (is_array($this->_elementDecorators)
+ && !array_key_exists('decorators', $options)
+ ) {
+ $options['decorators'] = $this->_elementDecorators;
+ }
+ }
+
+ $class = $this->getPluginLoader(self::ELEMENT)->load($type);
+ $element = new $class($name, $options);
+
+ return $element;
+ }
+
+ /**
+ * Add multiple elements at once
+ *
+ * @param array $elements
+ * @return Zend_Form
+ */
+ public function addElements(array $elements)
+ {
+ foreach ($elements as $key => $spec) {
+ $name = null;
+ if (!is_numeric($key)) {
+ $name = $key;
+ }
+
+ if (is_string($spec) || ($spec instanceof Zend_Form_Element)) {
+ $this->addElement($spec, $name);
+ continue;
+ }
+
+ if (is_array($spec)) {
+ $argc = count($spec);
+ $options = array();
+ if (isset($spec['type'])) {
+ $type = $spec['type'];
+ if (isset($spec['name'])) {
+ $name = $spec['name'];
+ }
+ if (isset($spec['options'])) {
+ $options = $spec['options'];
+ }
+ $this->addElement($type, $name, $options);
+ } else {
+ switch ($argc) {
+ case 0:
+ continue 2;
+ case (1 <= $argc):
+ $type = array_shift($spec);
+ case (2 <= $argc):
+ if (null === $name) {
+ $name = array_shift($spec);
+ } else {
+ $options = array_shift($spec);
+ }
+ case (3 <= $argc):
+ if (empty($options)) {
+ $options = array_shift($spec);
+ }
+ default:
+ $this->addElement($type, $name, $options);
+ }
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set form elements (overwrites existing elements)
+ *
+ * @param array $elements
+ * @return Zend_Form
+ */
+ public function setElements(array $elements)
+ {
+ $this->clearElements();
+ return $this->addElements($elements);
+ }
+
+ /**
+ * Retrieve a single element
+ *
+ * @param string $name
+ * @return Zend_Form_Element|null
+ */
+ public function getElement($name)
+ {
+ if (array_key_exists($name, $this->_elements)) {
+ return $this->_elements[$name];
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve all elements
+ *
+ * @return array
+ */
+ public function getElements()
+ {
+ return $this->_elements;
+ }
+
+ /**
+ * Remove element
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function removeElement($name)
+ {
+ $name = (string) $name;
+ if (isset($this->_elements[$name])) {
+ unset($this->_elements[$name]);
+ if (array_key_exists($name, $this->_order)) {
+ unset($this->_order[$name]);
+ $this->_orderUpdated = true;
+ } else {
+ /** @var Zend_Form_DisplayGroup $group */
+ foreach ($this->_displayGroups as $group) {
+ if (null !== $group->getElement($name)) {
+ $group->removeElement($name);
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove all form elements
+ *
+ * @return Zend_Form
+ */
+ public function clearElements()
+ {
+ foreach (array_keys($this->_elements) as $key) {
+ if (array_key_exists($key, $this->_order)) {
+ unset($this->_order[$key]);
+ }
+ }
+ $this->_elements = array();
+ $this->_orderUpdated = true;
+ return $this;
+ }
+
+ /**
+ * Set default values for elements
+ *
+ * Sets values for all elements specified in the array of $defaults.
+ *
+ * @param array $defaults
+ * @return Zend_Form
+ */
+ public function setDefaults(array $defaults)
+ {
+ $eBelongTo = null;
+
+ if ($this->isArray()) {
+ $eBelongTo = $this->getElementsBelongTo();
+ $defaults = $this->_dissolveArrayValue($defaults, $eBelongTo);
+ }
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $name => $element) {
+ $check = $defaults;
+ if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
+ $check = $this->_dissolveArrayValue($defaults, $belongsTo);
+ }
+ if (array_key_exists($name, (array)$check)) {
+ $this->setDefault($name, $check[$name]);
+ $defaults = $this->_dissolveArrayUnsetKey($defaults, $belongsTo, $name);
+ }
+ }
+ /** @var Zend_Form_SubForm $form */
+ foreach ($this->getSubForms() as $name => $form) {
+ if (!$form->isArray() && array_key_exists($name, $defaults)) {
+ $form->setDefaults($defaults[$name]);
+ } else {
+ $form->setDefaults($defaults);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set default value for an element
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Form
+ */
+ public function setDefault($name, $value)
+ {
+ $name = (string) $name;
+ if ($element = $this->getElement($name)) {
+ $element->setValue($value);
+ } else {
+ if (is_scalar($value)) {
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $subForm) {
+ $subForm->setDefault($name, $value);
+ }
+ } elseif (is_array($value) && ($subForm = $this->getSubForm($name))) {
+ $subForm->setDefaults($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve value for single element
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getValue($name)
+ {
+ if ($element = $this->getElement($name)) {
+ return $element->getValue();
+ }
+
+ if ($subForm = $this->getSubForm($name)) {
+ return $subForm->getValues(true);
+ }
+
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $subForm) {
+ if ($name == $subForm->getElementsBelongTo()) {
+ return $subForm->getValues(true);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve all form element values
+ *
+ * @param bool $suppressArrayNotation
+ * @return array
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = array();
+ $eBelongTo = null;
+
+ if ($this->isArray()) {
+ $eBelongTo = $this->getElementsBelongTo();
+ }
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $key => $element) {
+ if (!$element->getIgnore()) {
+ $merge = array();
+ if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
+ if ('' !== (string)$belongsTo) {
+ $key = $belongsTo . '[' . $key . ']';
+ }
+ }
+ $merge = $this->_attachToArray($element->getValue(), $key);
+ $values = $this->_array_replace_recursive($values, $merge);
+ }
+ }
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $key => $subForm) {
+ $merge = array();
+ if (!$subForm->isArray()) {
+ $merge[$key] = $subForm->getValues();
+ } else {
+ $merge = $this->_attachToArray($subForm->getValues(true),
+ $subForm->getElementsBelongTo());
+ }
+ $values = $this->_array_replace_recursive($values, $merge);
+ }
+
+ if (!$suppressArrayNotation &&
+ $this->isArray() &&
+ !$this->_getIsRendered()) {
+ $values = $this->_attachToArray($values, $this->getElementsBelongTo());
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns only the valid values from the given form input.
+ *
+ * For models that can be saved in a partially valid state, for example when following the builder,
+ * prototype or state patterns it is particularly interessting to retrieve all the current valid
+ * values to persist them.
+ *
+ * @param array $data
+ * @param bool $suppressArrayNotation
+ * @return array
+ */
+ public function getValidValues($data, $suppressArrayNotation = false)
+ {
+ $values = array();
+ $eBelongTo = null;
+
+ if ($this->isArray()) {
+ $eBelongTo = $this->getElementsBelongTo();
+ $data = $this->_dissolveArrayValue($data, $eBelongTo);
+ }
+ $context = $data;
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $key => $element) {
+ if (!$element->getIgnore()) {
+ $check = $data;
+ if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
+ $check = $this->_dissolveArrayValue($data, $belongsTo);
+ }
+ if (isset($check[$key])) {
+ if($element->isValid($check[$key], $context)) {
+ $merge = array();
+ if ($belongsTo !== $eBelongTo && '' !== (string)$belongsTo) {
+ $key = $belongsTo . '[' . $key . ']';
+ }
+ $merge = $this->_attachToArray($element->getValue(), $key);
+ $values = $this->_array_replace_recursive($values, $merge);
+ }
+ $data = $this->_dissolveArrayUnsetKey($data, $belongsTo, $key);
+ }
+ }
+ }
+ /** @var Zend_Form_SubForm $form */
+ foreach ($this->getSubForms() as $key => $form) {
+ $merge = array();
+ if (isset($data[$key]) && !$form->isArray()) {
+ $tmp = $form->getValidValues($data[$key]);
+ if (!empty($tmp)) {
+ $merge[$key] = $tmp;
+ }
+ } else {
+ $tmp = $form->getValidValues($data, true);
+ if (!empty($tmp)) {
+ $merge = $this->_attachToArray($tmp, $form->getElementsBelongTo());
+ }
+ }
+ $values = $this->_array_replace_recursive($values, $merge);
+ }
+ if (!$suppressArrayNotation &&
+ $this->isArray() &&
+ !empty($values) &&
+ !$this->_getIsRendered()) {
+ $values = $this->_attachToArray($values, $this->getElementsBelongTo());
+ }
+
+ return $values;
+ }
+
+ /**
+ * Get unfiltered element value
+ *
+ * @param string $name
+ * @return mixed
+ */
+ public function getUnfilteredValue($name)
+ {
+ if ($element = $this->getElement($name)) {
+ return $element->getUnfilteredValue();
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve all unfiltered element values
+ *
+ * @return array
+ */
+ public function getUnfilteredValues()
+ {
+ $values = array();
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $key => $element) {
+ $values[$key] = $element->getUnfilteredValue();
+ }
+
+ return $values;
+ }
+
+ /**
+ * Set all elements' filters
+ *
+ * @param array $filters
+ * @return Zend_Form
+ */
+ public function setElementFilters(array $filters)
+ {
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $element) {
+ $element->setFilters($filters);
+ }
+ return $this;
+ }
+
+ /**
+ * Set name of array elements belong to
+ *
+ * @param string $array
+ * @return Zend_Form
+ */
+ public function setElementsBelongTo($array)
+ {
+ $origName = $this->getElementsBelongTo();
+ $name = $this->filterName($array, true);
+ if ('' === $name) {
+ $name = null;
+ }
+ $this->_elementsBelongTo = $name;
+
+ if (null === $name) {
+ $this->setIsArray(false);
+ if (null !== $origName) {
+ $this->_setElementsBelongTo();
+ }
+ } else {
+ $this->setIsArray(true);
+ $this->_setElementsBelongTo();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set array to which elements belong
+ *
+ * @param string $name Element name
+ * @return void
+ */
+ protected function _setElementsBelongTo($name = null)
+ {
+ $array = $this->getElementsBelongTo();
+
+ if (null === $array) {
+ return;
+ }
+
+ if (null === $name) {
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $element) {
+ $element->setBelongsTo($array);
+ }
+ } else {
+ if (null !== ($element = $this->getElement($name))) {
+ $element->setBelongsTo($array);
+ }
+ }
+ }
+
+ /**
+ * Get name of array elements belong to
+ *
+ * @return string|null
+ */
+ public function getElementsBelongTo()
+ {
+ if ((null === $this->_elementsBelongTo) && $this->isArray()) {
+ $name = $this->getName();
+ if ('' !== (string)$name) {
+ return $name;
+ }
+ }
+ return $this->_elementsBelongTo;
+ }
+
+ /**
+ * Set flag indicating elements belong to array
+ *
+ * @param bool $flag Value of flag
+ * @return Zend_Form
+ */
+ public function setIsArray($flag)
+ {
+ $this->_isArray = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get flag indicating if elements belong to an array
+ *
+ * @return bool
+ */
+ public function isArray()
+ {
+ return $this->_isArray;
+ }
+
+ // Element groups:
+
+ /**
+ * Add a form group/subform
+ *
+ * @param Zend_Form $form
+ * @param string $name
+ * @param int $order
+ * @return Zend_Form
+ */
+ public function addSubForm(Zend_Form $form, $name, $order = null)
+ {
+ $name = (string) $name;
+ /** @var Zend_Loader_PluginLoader $loader */
+ foreach ($this->_loaders as $type => $loader) {
+ $loaderPaths = $loader->getPaths();
+ foreach ($loaderPaths as $prefix => $paths) {
+ foreach ($paths as $path) {
+ $form->addPrefixPath($prefix, $path, $type);
+ }
+ }
+ }
+
+ if (!empty($this->_elementPrefixPaths)) {
+ foreach ($this->_elementPrefixPaths as $spec) {
+ list($prefix, $path, $type) = array_values($spec);
+ $form->addElementPrefixPath($prefix, $path, $type);
+ }
+ }
+
+ if (!empty($this->_displayGroupPrefixPaths)) {
+ foreach ($this->_displayGroupPrefixPaths as $spec) {
+ list($prefix, $path) = array_values($spec);
+ $form->addDisplayGroupPrefixPath($prefix, $path);
+ }
+ }
+
+ if (null !== $order) {
+ $form->setOrder($order);
+ }
+
+ if (($oldName = $form->getName()) &&
+ $oldName !== $name &&
+ $oldName === $form->getElementsBelongTo()) {
+ $form->setElementsBelongTo($name);
+ }
+
+ $form->setName($name);
+ $this->_subForms[$name] = $form;
+ $this->_order[$name] = $order;
+ $this->_orderUpdated = true;
+ return $this;
+ }
+
+ /**
+ * Add multiple form subForms/subforms at once
+ *
+ * @param array $subForms
+ * @return Zend_Form
+ */
+ public function addSubForms(array $subForms)
+ {
+ foreach ($subForms as $key => $spec) {
+ $name = (string) $key;
+ if ($spec instanceof Zend_Form) {
+ $this->addSubForm($spec, $name);
+ continue;
+ }
+
+ if (is_array($spec)) {
+ $argc = count($spec);
+ $order = null;
+ switch ($argc) {
+ case 0:
+ continue 2;
+ case (1 <= $argc):
+ $subForm = array_shift($spec);
+
+ if (!$subForm instanceof Zend_Form) {
+ $subForm = new Zend_Form_SubForm($subForm);
+ }
+ case (2 <= $argc):
+ $name = array_shift($spec);
+ case (3 <= $argc):
+ $order = array_shift($spec);
+ default:
+ $this->addSubForm($subForm, $name, $order);
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set multiple form subForms/subforms (overwrites)
+ *
+ * @param array $subForms
+ * @return Zend_Form
+ */
+ public function setSubForms(array $subForms)
+ {
+ $this->clearSubForms();
+ return $this->addSubForms($subForms);
+ }
+
+ /**
+ * Retrieve a form subForm/subform
+ *
+ * @param string $name
+ * @return Zend_Form|null
+ */
+ public function getSubForm($name)
+ {
+ $name = (string) $name;
+ if (isset($this->_subForms[$name])) {
+ return $this->_subForms[$name];
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve all form subForms/subforms
+ *
+ * @return array
+ */
+ public function getSubForms()
+ {
+ return $this->_subForms;
+ }
+
+ /**
+ * Remove form subForm/subform
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function removeSubForm($name)
+ {
+ $name = (string) $name;
+ if (array_key_exists($name, $this->_subForms)) {
+ unset($this->_subForms[$name]);
+ if (array_key_exists($name, $this->_order)) {
+ unset($this->_order[$name]);
+ $this->_orderUpdated = true;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove all form subForms/subforms
+ *
+ * @return Zend_Form
+ */
+ public function clearSubForms()
+ {
+ foreach (array_keys($this->_subForms) as $key) {
+ if (array_key_exists($key, $this->_order)) {
+ unset($this->_order[$key]);
+ }
+ }
+ $this->_subForms = array();
+ $this->_orderUpdated = true;
+ return $this;
+ }
+
+
+ // Display groups:
+
+ /**
+ * Set default display group class
+ *
+ * @param string $class
+ * @return Zend_Form
+ */
+ public function setDefaultDisplayGroupClass($class)
+ {
+ $this->_defaultDisplayGroupClass = (string) $class;
+ return $this;
+ }
+
+ /**
+ * Retrieve default display group class
+ *
+ * @return string
+ */
+ public function getDefaultDisplayGroupClass()
+ {
+ return $this->_defaultDisplayGroupClass;
+ }
+
+ /**
+ * Add a display group
+ *
+ * Groups named elements for display purposes.
+ *
+ * If a referenced element does not yet exist in the form, it is omitted.
+ *
+ * @param array $elements
+ * @param string $name
+ * @param array|Zend_Config $options
+ * @return Zend_Form
+ * @throws Zend_Form_Exception if no valid elements provided
+ */
+ public function addDisplayGroup(array $elements, $name, $options = null)
+ {
+ $group = array();
+ foreach ($elements as $element) {
+ if($element instanceof Zend_Form_Element) {
+ $elementName = $element->getName();
+ if (!isset($this->_elements[$elementName])) {
+ $this->addElement($element);
+ }
+ $element = $elementName;
+ }
+
+ if (isset($this->_elements[$element])) {
+ $add = $this->getElement($element);
+ if (null !== $add) {
+ $group[] = $add;
+ }
+ }
+ }
+ if (empty($group)) {
+ throw new Zend_Form_Exception('No valid elements specified for display group');
+ }
+
+ $name = (string) $name;
+
+ if (is_array($options)) {
+ $options['form'] = $this;
+ $options['elements'] = $group;
+ } elseif ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ $options['form'] = $this;
+ $options['elements'] = $group;
+ } else {
+ $options = array(
+ 'form' => $this,
+ 'elements' => $group,
+ );
+ }
+
+ if (isset($options['displayGroupClass'])) {
+ $class = $options['displayGroupClass'];
+ unset($options['displayGroupClass']);
+ } else {
+ $class = $this->getDefaultDisplayGroupClass();
+ }
+
+ if (!class_exists($class)) {
+ Zend_Loader::loadClass($class);
+ }
+ $this->_displayGroups[$name] = new $class(
+ $name,
+ $this->getPluginLoader(self::DECORATOR),
+ $options
+ );
+
+ if (!empty($this->_displayGroupPrefixPaths)) {
+ $this->_displayGroups[$name]->addPrefixPaths($this->_displayGroupPrefixPaths);
+ }
+
+ $this->_order[$name] = $this->_displayGroups[$name]->getOrder();
+ $this->_orderUpdated = true;
+ return $this;
+ }
+
+ /**
+ * Add a display group object (used with cloning)
+ *
+ * @param Zend_Form_DisplayGroup $group
+ * @param string|null $name
+ * @throws Zend_Form_Exception
+ * @return Zend_Form
+ */
+ protected function _addDisplayGroupObject(Zend_Form_DisplayGroup $group, $name = null)
+ {
+ if (null === $name) {
+ $name = $group->getName();
+ if ('' === (string)$name) {
+ throw new Zend_Form_Exception('Invalid display group added; requires name');
+ }
+ }
+
+ $this->_displayGroups[$name] = $group;
+ $group->setForm($this);
+
+ if (!empty($this->_displayGroupPrefixPaths)) {
+ $this->_displayGroups[$name]->addPrefixPaths($this->_displayGroupPrefixPaths);
+ }
+
+ $this->_order[$name] = $this->_displayGroups[$name]->getOrder();
+ $this->_orderUpdated = true;
+ return $this;
+ }
+
+ /**
+ * Add multiple display groups at once
+ *
+ * @param array $groups
+ * @return Zend_Form
+ */
+ public function addDisplayGroups(array $groups)
+ {
+ foreach ($groups as $key => $spec) {
+ $name = null;
+ if (!is_numeric($key)) {
+ $name = $key;
+ }
+
+ if ($spec instanceof Zend_Form_DisplayGroup) {
+ $this->_addDisplayGroupObject($spec);
+ }
+
+ if (!is_array($spec) || empty($spec)) {
+ continue;
+ }
+
+ $argc = count($spec);
+ $options = array();
+
+ if (isset($spec['elements'])) {
+ $elements = $spec['elements'];
+ if (isset($spec['name'])) {
+ $name = $spec['name'];
+ }
+ if (isset($spec['options'])) {
+ $options = $spec['options'];
+ }
+ $this->addDisplayGroup($elements, $name, $options);
+ } else {
+ switch ($argc) {
+ case (1 <= $argc):
+ $elements = array_shift($spec);
+ if (!is_array($elements) && (null !== $name)) {
+ $elements = array_merge((array) $elements, $spec);
+ $this->addDisplayGroup($elements, $name);
+ break;
+ }
+ case (2 <= $argc):
+ if (null !== $name) {
+ $options = array_shift($spec);
+ $this->addDisplayGroup($elements, $name, $options);
+ break;
+ }
+ $name = array_shift($spec);
+ case (3 <= $argc):
+ $options = array_shift($spec);
+ default:
+ $this->addDisplayGroup($elements, $name, $options);
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Add multiple display groups (overwrites)
+ *
+ * @param array $groups
+ * @return Zend_Form
+ */
+ public function setDisplayGroups(array $groups)
+ {
+ return $this->clearDisplayGroups()
+ ->addDisplayGroups($groups);
+ }
+
+ /**
+ * Return a display group
+ *
+ * @param string $name
+ * @return Zend_Form_DisplayGroup|null
+ */
+ public function getDisplayGroup($name)
+ {
+ $name = (string) $name;
+ if (isset($this->_displayGroups[$name])) {
+ return $this->_displayGroups[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Return all display groups
+ *
+ * @return array
+ */
+ public function getDisplayGroups()
+ {
+ return $this->_displayGroups;
+ }
+
+ /**
+ * Remove a display group by name
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function removeDisplayGroup($name)
+ {
+ $name = (string) $name;
+ if (array_key_exists($name, $this->_displayGroups)) {
+ /** @var Zend_Form_Element $element */
+ foreach ($this->_displayGroups[$name] as $key => $element) {
+ if (array_key_exists($key, $this->_elements)) {
+ $this->_order[$key] = $element->getOrder();
+ $this->_orderUpdated = true;
+ }
+ }
+ unset($this->_displayGroups[$name]);
+
+ if (array_key_exists($name, $this->_order)) {
+ unset($this->_order[$name]);
+ $this->_orderUpdated = true;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove all display groups
+ *
+ * @return Zend_Form
+ */
+ public function clearDisplayGroups()
+ {
+ foreach ($this->_displayGroups as $key => $group) {
+ if (array_key_exists($key, $this->_order)) {
+ unset($this->_order[$key]);
+ }
+ /** @var Zend_Form_Element $element */
+ foreach ($group as $name => $element) {
+ if (isset($this->_elements[$name])) {
+ $this->_order[$name] = $element->getOrder();
+ }
+ $this->_order[$name] = $element->getOrder();
+ }
+ }
+ $this->_displayGroups = array();
+ $this->_orderUpdated = true;
+ return $this;
+ }
+
+
+ // Processing
+
+ /**
+ * Populate form
+ *
+ * Proxies to {@link setDefaults()}
+ *
+ * @param array $values
+ * @return Zend_Form
+ */
+ public function populate(array $values)
+ {
+ return $this->setDefaults($values);
+ }
+
+ /**
+ * Determine array key name from given value
+ *
+ * Given a value such as foo[bar][baz], returns the last element (in this case, 'baz').
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function _getArrayName($value)
+ {
+ if (!is_string($value) || '' === $value) {
+ return $value;
+ }
+
+ if (!strstr($value, '[')) {
+ return $value;
+ }
+
+ $endPos = strlen($value) - 1;
+ if (']' != $value[$endPos]) {
+ return $value;
+ }
+
+ $start = strrpos($value, '[') + 1;
+ $name = substr($value, $start, $endPos - $start);
+ return $name;
+ }
+
+ /**
+ * Extract the value by walking the array using given array path.
+ *
+ * Given an array path such as foo[bar][baz], returns the value of the last
+ * element (in this case, 'baz').
+ *
+ * @param array $value Array to walk
+ * @param string $arrayPath Array notation path of the part to extract
+ * @return string
+ */
+ protected function _dissolveArrayValue($value, $arrayPath)
+ {
+ // As long as we have more levels
+ while ($arrayPos = strpos($arrayPath ?? '', '[')) {
+ // Get the next key in the path
+ $arrayKey = trim(substr($arrayPath, 0, $arrayPos), ']');
+
+ // Set the potentially final value or the next search point in the array
+ if (isset($value[$arrayKey])) {
+ $value = $value[$arrayKey];
+ }
+
+ // Set the next search point in the path
+ $arrayPath = trim(substr($arrayPath, $arrayPos + 1), ']');
+ }
+
+ if (isset($value[$arrayPath])) {
+ $value = $value[$arrayPath];
+ }
+
+ return $value;
+ }
+
+ /**
+ * Given an array, an optional arrayPath and a key this method
+ * dissolves the arrayPath and unsets the key within the array
+ * if it exists.
+ *
+ * @param array $array
+ * @param string|null $arrayPath
+ * @param string $key
+ * @return array
+ */
+ protected function _dissolveArrayUnsetKey($array, $arrayPath, $key)
+ {
+ $unset =& $array;
+ $path = trim(strtr((string)$arrayPath, array('[' => '/', ']' => '')), '/');
+ $segs = ('' !== $path) ? explode('/', $path) : array();
+
+ foreach ($segs as $seg) {
+ if (!array_key_exists($seg, (array)$unset)) {
+ return $array;
+ }
+ $unset =& $unset[$seg];
+ }
+ if (array_key_exists($key, (array)$unset)) {
+ unset($unset[$key]);
+ }
+ return $array;
+ }
+
+ /**
+ * Converts given arrayPath to an array and attaches given value at the end of it.
+ *
+ * @param mixed $value The value to attach
+ * @param string $arrayPath Given array path to convert and attach to.
+ * @return array
+ */
+ protected function _attachToArray($value, $arrayPath)
+ {
+ // As long as we have more levels
+ while ($arrayPos = strrpos($arrayPath, '[')) {
+ // Get the next key in the path
+ $arrayKey = trim(substr($arrayPath, $arrayPos + 1), ']');
+
+ // Attach
+ $value = array($arrayKey => $value);
+
+ // Set the next search point in the path
+ $arrayPath = trim(substr($arrayPath, 0, $arrayPos), ']');
+ }
+
+ $value = array($arrayPath => $value);
+
+ return $value;
+ }
+
+ /**
+ * Returns a one dimensional numerical indexed array with the
+ * Elements, SubForms and Elements from DisplayGroups as Values.
+ *
+ * Subitems are inserted based on their order Setting if set,
+ * otherwise they are appended, the resulting numerical index
+ * may differ from the order value.
+ *
+ * @access protected
+ * @return array
+ */
+ public function getElementsAndSubFormsOrdered()
+ {
+ $ordered = array();
+ foreach ($this->_order as $name => $order) {
+ $order = isset($order) ? $order : count($ordered);
+ if ($this->$name instanceof Zend_Form_Element ||
+ $this->$name instanceof Zend_Form) {
+ array_splice($ordered, $order, 0, array($this->$name));
+ } else if ($this->$name instanceof Zend_Form_DisplayGroup) {
+ $subordered = array();
+ /** @var Zend_Form_Element $element */
+ foreach ($this->$name->getElements() as $element) {
+ $suborder = $element->getOrder();
+ $suborder = (null !== $suborder) ? $suborder : count($subordered);
+ array_splice($subordered, $suborder, 0, array($element));
+ }
+ if (!empty($subordered)) {
+ array_splice($ordered, $order, 0, $subordered);
+ }
+ }
+ }
+ return $ordered;
+ }
+
+ /**
+ * This is a helper function until php 5.3 is widespreaded
+ *
+ * @param array $into
+ * @return array
+ */
+ protected function _array_replace_recursive(array $into)
+ {
+ $fromArrays = array_slice(func_get_args(),1);
+
+ foreach ($fromArrays as $from) {
+ foreach ($from as $key => $value) {
+ if (is_array($value)) {
+ if (!isset($into[$key])) {
+ $into[$key] = array();
+ }
+ $into[$key] = $this->_array_replace_recursive($into[$key], $from[$key]);
+ } else {
+ $into[$key] = $value;
+ }
+ }
+ }
+ return $into;
+ }
+
+ /**
+ * Validate the form
+ *
+ * @param array $data
+ * @throws Zend_Form_Exception
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (!is_array($data)) {
+ throw new Zend_Form_Exception(__METHOD__ . ' expects an array');
+ }
+ $translator = $this->getTranslator();
+ $valid = true;
+ $eBelongTo = null;
+
+ if ($this->isArray()) {
+ $eBelongTo = $this->getElementsBelongTo();
+ $data = $this->_dissolveArrayValue($data, $eBelongTo);
+ }
+ $context = $data;
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $key => $element) {
+ if (null !== $translator && $this->hasTranslator()
+ && !$element->hasTranslator()) {
+ $element->setTranslator($translator);
+ }
+ $check = $data;
+ if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
+ $check = $this->_dissolveArrayValue($data, $belongsTo);
+ }
+ if (!isset($check[$key])) {
+ $valid = $element->isValid(null, $context) && $valid;
+ } else {
+ $valid = $element->isValid($check[$key], $context) && $valid;
+ $data = $this->_dissolveArrayUnsetKey($data, $belongsTo, $key);
+ }
+ }
+ /** @var Zend_Form_SubForm $form */
+ foreach ($this->getSubForms() as $key => $form) {
+ if (null !== $translator && $this->hasTranslator()
+ && !$form->hasTranslator()) {
+ $form->setTranslator($translator);
+ }
+ if (isset($data[$key]) && !$form->isArray()) {
+ $valid = $form->isValid($data[$key]) && $valid;
+ } else {
+ $valid = $form->isValid($data) && $valid;
+ }
+ }
+
+ $this->_errorsExist = !$valid;
+
+ // If manually flagged as an error, return invalid status
+ if ($this->_errorsForced) {
+ return false;
+ }
+
+ return $valid;
+ }
+
+ /**
+ * Validate a partial form
+ *
+ * Does not check for required flags.
+ *
+ * @param array $data
+ * @return boolean
+ */
+ public function isValidPartial(array $data)
+ {
+ $eBelongTo = null;
+
+ if ($this->isArray()) {
+ $eBelongTo = $this->getElementsBelongTo();
+ $data = $this->_dissolveArrayValue($data, $eBelongTo);
+ }
+
+ $translator = $this->getTranslator();
+ $valid = true;
+ $context = $data;
+
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $key => $element) {
+ $check = $data;
+ if (($belongsTo = $element->getBelongsTo()) !== $eBelongTo) {
+ $check = $this->_dissolveArrayValue($data, $belongsTo);
+ }
+ if (isset($check[$key])) {
+ if (null !== $translator && !$element->hasTranslator()) {
+ $element->setTranslator($translator);
+ }
+ $valid = $element->isValid($check[$key], $context) && $valid;
+ $data = $this->_dissolveArrayUnsetKey($data, $belongsTo, $key);
+ }
+ }
+ /** @var Zend_Form_SubForm $form */
+ foreach ($this->getSubForms() as $key => $form) {
+ if (null !== $translator && !$form->hasTranslator()) {
+ $form->setTranslator($translator);
+ }
+ if (isset($data[$key]) && !$form->isArray()) {
+ $valid = $form->isValidPartial($data[$key]) && $valid;
+ } else {
+ $valid = $form->isValidPartial($data) && $valid;
+ }
+ }
+
+ $this->_errorsExist = !$valid;
+ return $valid;
+ }
+
+ /**
+ * Process submitted AJAX data
+ *
+ * Checks if provided $data is valid, via {@link isValidPartial()}. If so,
+ * it returns JSON-encoded boolean true. If not, it returns JSON-encoded
+ * error messages (as returned by {@link getMessages()}).
+ *
+ * @param array $data
+ * @return string JSON-encoded boolean true or error messages
+ */
+ public function processAjax(array $data)
+ {
+ if ($this->isValidPartial($data)) {
+ return Zend_Json::encode(true);
+ }
+ $messages = $this->getMessages();
+ return Zend_Json::encode($messages);
+ }
+
+ /**
+ * Add a custom error message to return in the event of failed validation
+ *
+ * @param string $message
+ * @return Zend_Form
+ */
+ public function addErrorMessage($message)
+ {
+ $this->_errorMessages[] = (string) $message;
+ return $this;
+ }
+
+ /**
+ * Add multiple custom error messages to return in the event of failed validation
+ *
+ * @param array $messages
+ * @return Zend_Form
+ */
+ public function addErrorMessages(array $messages)
+ {
+ foreach ($messages as $message) {
+ $this->addErrorMessage($message);
+ }
+ return $this;
+ }
+
+ /**
+ * Same as addErrorMessages(), but clears custom error message stack first
+ *
+ * @param array $messages
+ * @return Zend_Form
+ */
+ public function setErrorMessages(array $messages)
+ {
+ $this->clearErrorMessages();
+ return $this->addErrorMessages($messages);
+ }
+
+ /**
+ * Retrieve custom error messages
+ *
+ * @return array
+ */
+ public function getErrorMessages()
+ {
+ return $this->_errorMessages;
+ }
+
+ /**
+ * Clear custom error messages stack
+ *
+ * @return Zend_Form
+ */
+ public function clearErrorMessages()
+ {
+ $this->_errorMessages = array();
+ return $this;
+ }
+
+ /**
+ * Mark the element as being in a failed validation state
+ *
+ * @return Zend_Form
+ */
+ public function markAsError()
+ {
+ $this->_errorsExist = true;
+ $this->_errorsForced = true;
+ return $this;
+ }
+
+ /**
+ * Add an error message and mark element as failed validation
+ *
+ * @param string $message
+ * @return Zend_Form
+ */
+ public function addError($message)
+ {
+ $this->addErrorMessage($message);
+ $this->markAsError();
+ return $this;
+ }
+
+ /**
+ * Add multiple error messages and flag element as failed validation
+ *
+ * @param array $messages
+ * @return Zend_Form
+ */
+ public function addErrors(array $messages)
+ {
+ foreach ($messages as $message) {
+ $this->addError($message);
+ }
+ return $this;
+ }
+
+ /**
+ * Overwrite any previously set error messages and flag as failed validation
+ *
+ * @param array $messages
+ * @return Zend_Form
+ */
+ public function setErrors(array $messages)
+ {
+ $this->clearErrorMessages();
+ return $this->addErrors($messages);
+ }
+
+
+ public function persistData()
+ {
+ }
+
+ /**
+ * Are there errors in the form?
+ *
+ * @deprecated since 1.11.1 - use hasErrors() instead
+ * @return bool
+ */
+ public function isErrors()
+ {
+ return $this->hasErrors();
+ }
+
+ /**
+ * Are there errors in the form?
+ *
+ * @return bool
+ */
+ public function hasErrors()
+ {
+ $errors = $this->_errorsExist;
+
+ if (!$errors) {
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $element) {
+ if ($element->hasErrors()) {
+ $errors = true;
+ break;
+ }
+ }
+
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $subForm) {
+ if ($subForm->hasErrors()) {
+ $errors = true;
+ break;
+ }
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Get error codes for all elements failing validation
+ *
+ * @param string $name
+ * @param bool $suppressArrayNotation
+ * @return array
+ */
+ public function getErrors($name = null, $suppressArrayNotation = false)
+ {
+ $errors = array();
+ if (null !== $name) {
+ if (isset($this->_elements[$name])) {
+ return $this->getElement($name)->getErrors();
+ } else if (isset($this->_subForms[$name])) {
+ return $this->getSubForm($name)->getErrors(null, true);
+ }
+ }
+
+ /** @var Zend_Form_Element $element */
+ foreach ($this->_elements as $key => $element) {
+ $errors[$key] = $element->getErrors();
+ }
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $key => $subForm) {
+ $merge = array();
+ if (!$subForm->isArray()) {
+ $merge[$key] = $subForm->getErrors();
+ } else {
+ $merge = $this->_attachToArray($subForm->getErrors(null, true),
+ $subForm->getElementsBelongTo());
+ }
+ $errors = $this->_array_replace_recursive($errors, $merge);
+ }
+
+ if (!$suppressArrayNotation &&
+ $this->isArray() &&
+ !$this->_getIsRendered()) {
+ $errors = $this->_attachToArray($errors, $this->getElementsBelongTo());
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Retrieve error messages from elements failing validations
+ *
+ * @param string $name
+ * @param bool $suppressArrayNotation
+ * @return array
+ */
+ public function getMessages($name = null, $suppressArrayNotation = false)
+ {
+ if (null !== $name) {
+ if (isset($this->_elements[$name])) {
+ return $this->getElement($name)->getMessages();
+ } else if (isset($this->_subForms[$name])) {
+ return $this->getSubForm($name)->getMessages(null, true);
+ }
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $key => $subForm) {
+ if ($subForm->isArray()) {
+ $belongTo = $subForm->getElementsBelongTo();
+ if ($name == $this->_getArrayName($belongTo)) {
+ return $subForm->getMessages(null, true);
+ }
+ }
+ }
+ }
+
+ $customMessages = $this->_getErrorMessages();
+ if ($this->isErrors() && !empty($customMessages)) {
+ return $customMessages;
+ }
+
+ $messages = array();
+
+ /** @var Zend_Form_Element $element */
+ foreach ($this->getElements() as $name => $element) {
+ $eMessages = $element->getMessages();
+ if (!empty($eMessages)) {
+ $messages[$name] = $eMessages;
+ }
+ }
+
+ /** @var Zend_Form_SubForm $subForm */
+ foreach ($this->getSubForms() as $key => $subForm) {
+ $merge = $subForm->getMessages(null, true);
+ if (!empty($merge)) {
+ if (!$subForm->isArray()) {
+ $merge = array($key => $merge);
+ } else {
+ $merge = $this->_attachToArray($merge,
+ $subForm->getElementsBelongTo());
+ }
+ $messages = $this->_array_replace_recursive($messages, $merge);
+ }
+ }
+
+ if (!$suppressArrayNotation &&
+ $this->isArray() &&
+ !$this->_getIsRendered()) {
+ $messages = $this->_attachToArray($messages, $this->getElementsBelongTo());
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Retrieve translated custom error messages
+ * Proxies to {@link _getErrorMessages()}.
+ *
+ * @return array
+ */
+ public function getCustomMessages()
+ {
+ return $this->_getErrorMessages();
+ }
+
+
+ // Rendering
+
+ /**
+ * Set view object
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_Form
+ */
+ public function setView(Zend_View_Interface $view = null)
+ {
+ $this->_view = $view;
+ return $this;
+ }
+
+ /**
+ * Retrieve view object
+ *
+ * If none registered, attempts to pull from ViewRenderer.
+ *
+ * @return Zend_View_Interface|null
+ */
+ public function getView()
+ {
+ if (null === $this->_view) {
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ $this->setView($viewRenderer->view);
+ }
+
+ return $this->_view;
+ }
+
+ /**
+ * Instantiate a decorator based on class name or class name fragment
+ *
+ * @param string $name
+ * @param null|array $options
+ * @return Zend_Form_Decorator_Interface
+ */
+ protected function _getDecorator($name, $options)
+ {
+ $class = $this->getPluginLoader(self::DECORATOR)->load($name);
+ if (null === $options) {
+ $decorator = new $class;
+ } else {
+ $decorator = new $class($options);
+ }
+
+ return $decorator;
+ }
+
+ /**
+ * Add a decorator for rendering the element
+ *
+ * @param string|Zend_Form_Decorator_Interface $decorator
+ * @param array|Zend_Config $options Options with which to initialize decorator
+ * @throws Zend_Form_Exception
+ * @return Zend_Form
+ */
+ public function addDecorator($decorator, $options = null)
+ {
+ if ($decorator instanceof Zend_Form_Decorator_Interface) {
+ $name = get_class($decorator);
+ } elseif (is_string($decorator)) {
+ $name = $decorator;
+ $decorator = array(
+ 'decorator' => $name,
+ 'options' => $options,
+ );
+ } elseif (is_array($decorator)) {
+ foreach ($decorator as $name => $spec) {
+ break;
+ }
+ if (is_numeric($name)) {
+ throw new Zend_Form_Exception('Invalid alias provided to addDecorator; must be alphanumeric string');
+ }
+ if (is_string($spec)) {
+ $decorator = array(
+ 'decorator' => $spec,
+ 'options' => $options,
+ );
+ } elseif ($spec instanceof Zend_Form_Decorator_Interface) {
+ $decorator = $spec;
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface');
+ }
+
+ $this->_decorators[$name] = $decorator;
+
+ return $this;
+ }
+
+ /**
+ * Add many decorators at once
+ *
+ * @param array $decorators
+ * @throws Zend_Form_Exception
+ * @return Zend_Form
+ */
+ public function addDecorators(array $decorators)
+ {
+ foreach ($decorators as $decoratorName => $decoratorInfo) {
+ if (is_string($decoratorInfo) ||
+ $decoratorInfo instanceof Zend_Form_Decorator_Interface) {
+ if (!is_numeric($decoratorName)) {
+ $this->addDecorator(array($decoratorName => $decoratorInfo));
+ } else {
+ $this->addDecorator($decoratorInfo);
+ }
+ } elseif (is_array($decoratorInfo)) {
+ $argc = count($decoratorInfo);
+ $options = array();
+ if (isset($decoratorInfo['decorator'])) {
+ $decorator = $decoratorInfo['decorator'];
+ if (isset($decoratorInfo['options'])) {
+ $options = $decoratorInfo['options'];
+ }
+ $this->addDecorator($decorator, $options);
+ } else {
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $decorator = array_shift($decoratorInfo);
+ case (2 <= $argc):
+ $options = array_shift($decoratorInfo);
+ default:
+ $this->addDecorator($decorator, $options);
+ break;
+ }
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid decorator passed to addDecorators()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overwrite all decorators
+ *
+ * @param array $decorators
+ * @return Zend_Form
+ */
+ public function setDecorators(array $decorators)
+ {
+ $this->clearDecorators();
+ return $this->addDecorators($decorators);
+ }
+
+ /**
+ * Retrieve a registered decorator
+ *
+ * @param string $name
+ * @return false|Zend_Form_Decorator_Abstract
+ */
+ public function getDecorator($name)
+ {
+ if (!isset($this->_decorators[$name])) {
+ $len = strlen($name);
+ foreach ($this->_decorators as $localName => $decorator) {
+ if ($len > strlen($localName)) {
+ continue;
+ }
+
+ if (0 === substr_compare($localName, $name, -$len, $len, true)) {
+ if (is_array($decorator)) {
+ return $this->_loadDecorator($decorator, $localName);
+ }
+ return $decorator;
+ }
+ }
+ return false;
+ }
+
+ if (is_array($this->_decorators[$name])) {
+ return $this->_loadDecorator($this->_decorators[$name], $name);
+ }
+
+ return $this->_decorators[$name];
+ }
+
+ /**
+ * Retrieve all decorators
+ *
+ * @return array
+ */
+ public function getDecorators()
+ {
+ foreach ($this->_decorators as $key => $value) {
+ if (is_array($value)) {
+ $this->_loadDecorator($value, $key);
+ }
+ }
+ return $this->_decorators;
+ }
+
+ /**
+ * Remove a single decorator
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function removeDecorator($name)
+ {
+ $decorator = $this->getDecorator($name);
+ if ($decorator) {
+ if (array_key_exists($name, $this->_decorators)) {
+ unset($this->_decorators[$name]);
+ } else {
+ $class = get_class($decorator);
+ if (!array_key_exists($class, $this->_decorators)) {
+ return false;
+ }
+ unset($this->_decorators[$class]);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all decorators
+ *
+ * @return Zend_Form
+ */
+ public function clearDecorators()
+ {
+ $this->_decorators = array();
+ return $this;
+ }
+
+ /**
+ * Set all element decorators as specified
+ *
+ * @param array $decorators
+ * @param array|null $elements Specific elements to decorate or exclude from decoration
+ * @param bool $include Whether $elements is an inclusion or exclusion list
+ * @return Zend_Form
+ */
+ public function setElementDecorators(array $decorators, array $elements = null, $include = true)
+ {
+ if (is_array($elements)) {
+ if ($include) {
+ $elementObjs = array();
+ foreach ($elements as $name) {
+ if (null !== ($element = $this->getElement($name))) {
+ $elementObjs[] = $element;
+ }
+ }
+ } else {
+ $elementObjs = $this->getElements();
+ foreach ($elements as $name) {
+ if (array_key_exists($name, $elementObjs)) {
+ unset($elementObjs[$name]);
+ }
+ }
+ }
+ } else {
+ $elementObjs = $this->getElements();
+ }
+
+ /** @var Zend_Form_Element $element */
+ foreach ($elementObjs as $element) {
+ $element->setDecorators($decorators);
+ }
+
+ $this->_elementDecorators = $decorators;
+
+ return $this;
+ }
+
+ /**
+ * Set all display group decorators as specified
+ *
+ * @param array $decorators
+ * @return Zend_Form
+ */
+ public function setDisplayGroupDecorators(array $decorators)
+ {
+ /** @var Zend_Form_DisplayGroup $group */
+ foreach ($this->getDisplayGroups() as $group) {
+ $group->setDecorators($decorators);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set all subform decorators as specified
+ *
+ * @param array $decorators
+ * @return Zend_Form
+ */
+ public function setSubFormDecorators(array $decorators)
+ {
+ /** @var Zend_Form_SubForm $form */
+ foreach ($this->getSubForms() as $form) {
+ $form->setDecorators($decorators);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Render form
+ *
+ * @param Zend_View_Interface $view
+ * @return string
+ */
+ public function render(Zend_View_Interface $view = null)
+ {
+ if (null !== $view) {
+ $this->setView($view);
+ }
+
+ $content = '';
+ /** @var Zend_Form_Decorator_Abstract $decorator */
+ foreach ($this->getDecorators() as $decorator) {
+ $decorator->setElement($this);
+ $content = $decorator->render($content);
+ }
+ $this->_setIsRendered();
+ return $content;
+ }
+
+ /**
+ * Serialize as string
+ *
+ * Proxies to {@link render()}.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ $return = $this->render();
+ return $return;
+ } catch (Exception $e) {
+ $message = "Exception caught by form: " . $e->getMessage()
+ . "\nStack Trace:\n" . $e->getTraceAsString();
+ trigger_error($message, E_USER_WARNING);
+ return '';
+ }
+ }
+
+
+ // Localization:
+
+ /**
+ * Set translator object
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @throws Zend_Form_Exception
+ * @return Zend_Form
+ */
+ public function setTranslator($translator = null)
+ {
+ if (null === $translator) {
+ $this->_translator = null;
+ } elseif ($translator instanceof Zend_Translate_Adapter) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ throw new Zend_Form_Exception('Invalid translator specified');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set global default translator object
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @throws Zend_Form_Exception
+ * @return void
+ */
+ public static function setDefaultTranslator($translator = null)
+ {
+ if (null === $translator) {
+ self::$_translatorDefault = null;
+ } elseif ($translator instanceof Zend_Translate_Adapter) {
+ self::$_translatorDefault = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ self::$_translatorDefault = $translator->getAdapter();
+ } else {
+ throw new Zend_Form_Exception('Invalid translator specified');
+ }
+ }
+
+ /**
+ * Retrieve translator object
+ *
+ * @return Zend_Translate|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ if (null === $this->_translator) {
+ return self::getDefaultTranslator();
+ }
+
+ return $this->_translator;
+ }
+
+ /**
+ * Does this form have its own specific translator?
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool)$this->_translator;
+ }
+
+ /**
+ * Get global default translator object
+ *
+ * @return null|Zend_Translate
+ */
+ public static function getDefaultTranslator()
+ {
+ if (null === self::$_translatorDefault) {
+ if (Zend_Registry::isRegistered('Zend_Translate')) {
+ $translator = Zend_Registry::get('Zend_Translate');
+ if ($translator instanceof Zend_Translate_Adapter) {
+ return $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ return $translator->getAdapter();
+ }
+ }
+ }
+ return self::$_translatorDefault;
+ }
+
+ /**
+ * Is there a default translation object set?
+ *
+ * @return boolean
+ */
+ public static function hasDefaultTranslator()
+ {
+ return (bool)self::$_translatorDefault;
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_Form
+ */
+ public function setDisableTranslator($flag)
+ {
+ $this->_translatorDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ return $this->_translatorDisabled;
+ }
+
+ /**
+ * Overloading: access to elements, form groups, and display groups
+ *
+ * @param string $name
+ * @return Zend_Form_Element|Zend_Form|null
+ */
+ public function __get($name)
+ {
+ if (isset($this->_elements[$name])) {
+ return $this->_elements[$name];
+ } elseif (isset($this->_subForms[$name])) {
+ return $this->_subForms[$name];
+ } elseif (isset($this->_displayGroups[$name])) {
+ return $this->_displayGroups[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Overloading: access to elements, form groups, and display groups
+ *
+ * @param string $name
+ * @param Zend_Form_Element|Zend_Form $value
+ * @return void
+ * @throws Zend_Form_Exception for invalid $value
+ */
+ public function __set($name, $value)
+ {
+ if ($value instanceof Zend_Form_Element) {
+ $this->addElement($value, $name);
+ return;
+ } elseif ($value instanceof Zend_Form) {
+ $this->addSubForm($value, $name);
+ return;
+ } elseif (is_array($value)) {
+ $this->addDisplayGroup($value, $name);
+ return;
+ }
+
+ if (is_object($value)) {
+ $type = get_class($value);
+ } else {
+ $type = gettype($value);
+ }
+ throw new Zend_Form_Exception('Only form elements and groups may be overloaded; variable of type "' . $type . '" provided');
+ }
+
+ /**
+ * Overloading: access to elements, form groups, and display groups
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ if (isset($this->_elements[$name])
+ || isset($this->_subForms[$name])
+ || isset($this->_displayGroups[$name]))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Overloading: access to elements, form groups, and display groups
+ *
+ * @param string $name
+ * @return void
+ */
+ public function __unset($name)
+ {
+ if (isset($this->_elements[$name])) {
+ unset($this->_elements[$name]);
+ } elseif (isset($this->_subForms[$name])) {
+ unset($this->_subForms[$name]);
+ } elseif (isset($this->_displayGroups[$name])) {
+ unset($this->_displayGroups[$name]);
+ }
+ }
+
+ /**
+ * Overloading: allow rendering specific decorators
+ *
+ * Call renderDecoratorName() to render a specific decorator.
+ *
+ * @param string $method
+ * @param array $args
+ * @return string
+ * @throws Zend_Form_Exception for invalid decorator or invalid method call
+ */
+ public function __call($method, $args)
+ {
+ if ('render' == substr($method, 0, 6)) {
+ $decoratorName = substr($method, 6);
+ if (false !== ($decorator = $this->getDecorator($decoratorName))) {
+ $decorator->setElement($this);
+ $seed = '';
+ if (0 < count($args)) {
+ $seed = array_shift($args);
+ }
+ if ($decoratorName === 'FormElements' ||
+ $decoratorName === 'PrepareElements') {
+ $this->_setIsRendered();
+ }
+ return $decorator->render($seed);
+ }
+
+ throw new Zend_Form_Exception(sprintf('Decorator by name %s does not exist', $decoratorName));
+ }
+
+ throw new Zend_Form_Exception(sprintf('Method %s does not exist', $method));
+ }
+
+ // Interfaces: Iterator, Countable
+
+ /**
+ * Current element/subform/display group
+ *
+ * @throws Zend_Form_Exception
+ * @return Zend_Form_Element|Zend_Form_DisplayGroup|Zend_Form
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ $this->_sort();
+ current($this->_order);
+ $key = key($this->_order);
+
+ if (isset($this->_elements[$key])) {
+ return $this->getElement($key);
+ } elseif (isset($this->_subForms[$key])) {
+ return $this->getSubForm($key);
+ } elseif (isset($this->_displayGroups[$key])) {
+ return $this->getDisplayGroup($key);
+ } else {
+ throw new Zend_Form_Exception(sprintf('Corruption detected in form; invalid key ("%s") found in internal iterator', (string) $key));
+ }
+ }
+
+ /**
+ * Current element/subform/display group name
+ *
+ * @return string
+ */
+ public function key(): string
+ {
+ $this->_sort();
+ return key($this->_order);
+ }
+
+ /**
+ * Move pointer to next element/subform/display group
+ *
+ * @return void
+ */
+ public function next(): void
+ {
+ $this->_sort();
+ next($this->_order);
+ }
+
+ /**
+ * Move pointer to beginning of element/subform/display group loop
+ *
+ * @return void
+ */
+ public function rewind(): void
+ {
+ $this->_sort();
+ reset($this->_order);
+ }
+
+ /**
+ * Determine if current element/subform/display group is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ $this->_sort();
+ return (current($this->_order) !== false);
+ }
+
+ /**
+ * Count of elements/subforms that are iterable
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return count($this->_order);
+ }
+
+ /**
+ * Set flag to disable loading default decorators
+ *
+ * @param bool $flag
+ * @return Zend_Form
+ */
+ public function setDisableLoadDefaultDecorators($flag)
+ {
+ $this->_disableLoadDefaultDecorators = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Should we load the default decorators?
+ *
+ * @return bool
+ */
+ public function loadDefaultDecoratorsIsDisabled()
+ {
+ return $this->_disableLoadDefaultDecorators;
+ }
+
+ /**
+ * Load the default decorators
+ *
+ * @return Zend_Form
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ $this->addDecorator('FormElements')
+ ->addDecorator('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form'))
+ ->addDecorator('Form');
+ }
+ return $this;
+ }
+
+ /**
+ * Remove an element from iteration
+ *
+ * @param string $name Element/group/form name
+ * @return void
+ */
+ public function removeFromIteration($name)
+ {
+ if (array_key_exists($name, $this->_order)) {
+ unset($this->_order[$name]);
+ $this->_orderUpdated = true;
+ }
+ }
+
+ /**
+ * Sort items according to their order
+ *
+ * @throws Zend_Form_Exception
+ * @return void
+ */
+ protected function _sort()
+ {
+ if ($this->_orderUpdated) {
+ $items = array();
+ $index = 0;
+ foreach ($this->_order as $key => $order) {
+ if (null === $order) {
+ if (null === ($order = $this->{$key}->getOrder())) {
+ while (array_search($index, $this->_order, true)) {
+ ++$index;
+ }
+ $items[$index] = $key;
+ ++$index;
+ } else {
+ $items[$order] = $key;
+ }
+ } elseif (isset($items[$order]) && $items[$order] !== $key) {
+ throw new Zend_Form_Exception('Form elements ' .
+ $items[$order] . ' and ' . $key .
+ ' have the same order (' .
+ $order . ') - ' .
+ 'this would result in only the last added element to be rendered'
+ );
+ } else {
+ $items[$order] = $key;
+ }
+ }
+
+ $items = array_flip($items);
+ asort($items);
+ $this->_order = $items;
+ $this->_orderUpdated = false;
+ }
+ }
+
+ /**
+ * Lazy-load a decorator
+ *
+ * @param array $decorator Decorator type and options
+ * @param mixed $name Decorator name or alias
+ * @return Zend_Form_Decorator_Interface
+ */
+ protected function _loadDecorator(array $decorator, $name)
+ {
+ $sameName = false;
+ if ($name == $decorator['decorator']) {
+ $sameName = true;
+ }
+
+ $instance = $this->_getDecorator($decorator['decorator'], $decorator['options']);
+ if ($sameName) {
+ $newName = get_class($instance);
+ $decoratorNames = array_keys($this->_decorators);
+ $order = array_flip($decoratorNames);
+ $order[$newName] = $order[$name];
+ $decoratorsExchange = array();
+ unset($order[$name]);
+ asort($order);
+ foreach ($order as $key => $index) {
+ if ($key == $newName) {
+ $decoratorsExchange[$key] = $instance;
+ continue;
+ }
+ $decoratorsExchange[$key] = $this->_decorators[$key];
+ }
+ $this->_decorators = $decoratorsExchange;
+ } else {
+ $this->_decorators[$name] = $instance;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Retrieve optionally translated custom error messages
+ *
+ * @return array
+ */
+ protected function _getErrorMessages()
+ {
+ $messages = $this->getErrorMessages();
+ $translator = $this->getTranslator();
+ if (null !== $translator) {
+ foreach ($messages as $key => $message) {
+ $messages[$key] = $translator->translate($message);
+ }
+ }
+ return $messages;
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Abstract.php b/library/vendor/Zend/Form/Decorator/Abstract.php
new file mode 100644
index 0000000..93c5a05
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Abstract.php
@@ -0,0 +1,251 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Interface */
+
+/**
+ * Zend_Form_Decorator_Abstract
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Form_Decorator_Abstract implements Zend_Form_Decorator_Interface
+{
+ /**
+ * Placement constants
+ */
+ const APPEND = 'APPEND';
+ const PREPEND = 'PREPEND';
+
+ /**
+ * Default placement: append
+ * @var string
+ */
+ protected $_placement = 'APPEND';
+
+ /**
+ * @var Zend_Form_Element|Zend_Form
+ */
+ protected $_element;
+
+ /**
+ * Decorator options
+ * @var array
+ */
+ protected $_options = array();
+
+ /**
+ * Separator between new content and old
+ * @var string
+ */
+ protected $_separator = PHP_EOL;
+
+ /**
+ * Constructor
+ *
+ * @param array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if (is_array($options)) {
+ $this->setOptions($options);
+ } elseif ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ }
+ }
+
+ /**
+ * Set options
+ *
+ * @param array $options
+ * @return Zend_Form_Decorator_Abstract
+ */
+ public function setOptions(array $options)
+ {
+ $this->_options = $options;
+ return $this;
+ }
+
+ /**
+ * Set options from config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Form_Decorator_Abstract
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ return $this->setOptions($config->toArray());
+ }
+
+ /**
+ * Set option
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return Zend_Form_Decorator_Abstract
+ */
+ public function setOption($key, $value)
+ {
+ $this->_options[(string) $key] = $value;
+ return $this;
+ }
+
+ /**
+ * Get option
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getOption($key)
+ {
+ $key = (string) $key;
+ if (isset($this->_options[$key])) {
+ return $this->_options[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve options
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Remove single option
+ *
+ * @param mixed $key
+ * @return void
+ */
+ public function removeOption($key)
+ {
+ if (null !== $this->getOption($key)) {
+ unset($this->_options[$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all options
+ *
+ * @return Zend_Form_Decorator_Abstract
+ */
+ public function clearOptions()
+ {
+ $this->_options = array();
+ return $this;
+ }
+
+ /**
+ * Set current form element
+ *
+ * @param Zend_Form_Element|Zend_Form $element
+ * @return Zend_Form_Decorator_Abstract
+ * @throws Zend_Form_Decorator_Exception on invalid element type
+ */
+ public function setElement($element)
+ {
+ if ((!$element instanceof Zend_Form_Element)
+ && (!$element instanceof Zend_Form)
+ && (!$element instanceof Zend_Form_DisplayGroup))
+ {
+ throw new Zend_Form_Decorator_Exception('Invalid element type passed to decorator');
+ }
+
+ $this->_element = $element;
+ return $this;
+ }
+
+ /**
+ * Retrieve current element
+ *
+ * @return Zend_Form_Element|Zend_Form
+ */
+ public function getElement()
+ {
+ return $this->_element;
+ }
+
+ /**
+ * Determine if decorator should append or prepend content
+ *
+ * @return string
+ */
+ public function getPlacement()
+ {
+ $placement = $this->_placement;
+ if (null !== ($placementOpt = $this->getOption('placement'))) {
+ $placementOpt = strtoupper($placementOpt);
+ switch ($placementOpt) {
+ case self::APPEND:
+ case self::PREPEND:
+ $placement = $this->_placement = $placementOpt;
+ break;
+ case false:
+ $placement = $this->_placement = null;
+ break;
+ default:
+ break;
+ }
+ $this->removeOption('placement');
+ }
+
+ return $placement;
+ }
+
+ /**
+ * Retrieve separator to use between old and new content
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ $separator = $this->_separator;
+ if (null !== ($separatorOpt = $this->getOption('separator'))) {
+ $separator = $this->_separator = (string) $separatorOpt;
+ $this->removeOption('separator');
+ }
+ return $separator;
+ }
+
+ /**
+ * Decorate content and/or element
+ *
+ * @param string $content
+ * @return string
+ * @throws Zend_Form_Decorator_Exception when unimplemented
+ */
+ public function render($content)
+ {
+ throw new Zend_Form_Decorator_Exception('render() not implemented');
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Callback.php b/library/vendor/Zend/Form/Decorator/Callback.php
new file mode 100644
index 0000000..fb759fd
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Callback.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Callback
+ *
+ * Execute an arbitrary callback to decorate an element. Callbacks should take
+ * three arguments, $content, $element, and $options:
+ *
+ * function mycallback($content, $element, array $options)
+ * {
+ * }
+ *
+ * and should return a string. ($options are whatever options were provided to
+ * the decorator.)
+ *
+ * To specify a callback, pass a valid callback as the 'callback' option.
+ *
+ * Callback results will be either appended, prepended, or replace the provided
+ * content. To replace the content, specify a placement of boolean false;
+ * defaults to append content.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Callback extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Callback
+ * @var string|array
+ */
+ protected $_callback;
+
+ /**
+ * Set callback
+ *
+ * @param callback $callback
+ * @return Zend_Form_Decorator_Callback
+ * @throws Zend_Form_Exception
+ */
+ public function setCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Zend_Form_Exception('Invalid callback provided to callback decorator');
+ }
+ $this->_callback = $callback;
+ return $this;
+ }
+
+ /**
+ * Get registered callback
+ *
+ * If not previously registered, checks to see if it exists in registered
+ * options.
+ *
+ * @return null|string|array
+ */
+ public function getCallback()
+ {
+ if (null === $this->_callback) {
+ if (null !== ($callback = $this->getOption('callback'))) {
+ $this->setCallback($callback);
+ $this->removeOption('callback');
+ }
+ }
+
+ return $this->_callback;
+ }
+
+ /**
+ * Render
+ *
+ * If no callback registered, returns callback. Otherwise, gets return
+ * value of callback and either appends, prepends, or replaces passed in
+ * content.
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $callback = $this->getCallback();
+ if (null === $callback) {
+ return $content;
+ }
+
+ $placement = $this->getPlacement();
+ $separator = $this->getSeparator();
+
+ $response = call_user_func($callback, $content, $this->getElement(), $this->getOptions());
+
+ switch ($placement) {
+ case self::APPEND:
+ return $content . $separator . $response;
+ case self::PREPEND:
+ return $response . $separator . $content;
+ default:
+ // replace content
+ return $response;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Captcha.php b/library/vendor/Zend/Form/Decorator/Captcha.php
new file mode 100644
index 0000000..914189e
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Captcha.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** @see Zend_Form_Decorator_Abstract */
+
+/**
+ * Captcha generic decorator
+ *
+ * Adds captcha adapter output
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Captcha extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Render captcha
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ if (!method_exists($element, 'getCaptcha')) {
+ return $content;
+ }
+
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $placement = $this->getPlacement();
+ $separator = $this->getSeparator();
+
+ $captcha = $element->getCaptcha();
+ $markup = $captcha->render($view, $element);
+ switch ($placement) {
+ case 'PREPEND':
+ $content = $markup . $separator . $content;
+ break;
+ case 'APPEND':
+ default:
+ $content = $content . $separator . $markup;
+ }
+ return $content;
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Description.php b/library/vendor/Zend/Form/Decorator/Description.php
new file mode 100644
index 0000000..63dfdbc
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Description.php
@@ -0,0 +1,197 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Description
+ *
+ * Accepts the options:
+ * - separator: separator to use between label and content (defaults to PHP_EOL)
+ * - placement: whether to append or prepend label to content (defaults to prepend)
+ * - tag: if set, used to wrap the label in an additional HTML tag
+ * - class: if set, override default class used with HTML tag
+ * - escape: whether or not to escape description (true by default)
+ *
+ * Any other options passed will be used as HTML attributes of the HTML tag used.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Description extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Whether or not to escape the description
+ * @var bool
+ */
+ protected $_escape;
+
+ /**
+ * Default placement: append
+ * @var string
+ */
+ protected $_placement = 'APPEND';
+
+ /**
+ * HTML tag with which to surround description
+ * @var string
+ */
+ protected $_tag;
+
+ /**
+ * Set HTML tag with which to surround description
+ *
+ * @param string $tag
+ * @return Zend_Form_Decorator_Description
+ */
+ public function setTag($tag)
+ {
+ $this->_tag = (string) $tag;
+ return $this;
+ }
+
+ /**
+ * Get HTML tag, if any, with which to surround description
+ *
+ * @return string
+ */
+ public function getTag()
+ {
+ if (null === $this->_tag) {
+ $tag = $this->getOption('tag');
+ if (null !== $tag) {
+ $this->removeOption('tag');
+ } else {
+ $tag = 'p';
+ }
+
+ $this->setTag($tag);
+ return $tag;
+ }
+
+ return $this->_tag;
+ }
+
+ /**
+ * Get class with which to define description
+ *
+ * Defaults to 'hint'
+ *
+ * @return string
+ */
+ public function getClass()
+ {
+ $class = $this->getOption('class');
+ if (null === $class) {
+ $class = 'hint';
+ $this->setOption('class', $class);
+ }
+
+ return $class;
+ }
+
+ /**
+ * Set whether or not to escape description
+ *
+ * @param bool $flag
+ * @return Zend_Form_Decorator_Description
+ */
+ public function setEscape($flag)
+ {
+ $this->_escape = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get escape flag
+ *
+ * @return true
+ */
+ public function getEscape()
+ {
+ if (null === $this->_escape) {
+ if (null !== ($escape = $this->getOption('escape'))) {
+ $this->setEscape($escape);
+ $this->removeOption('escape');
+ } else {
+ $this->setEscape(true);
+ }
+ }
+
+ return $this->_escape;
+ }
+
+ /**
+ * Render a description
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $description = $element->getDescription();
+ $description = trim($description ?? '');
+
+ if (!empty($description) && (null !== ($translator = $element->getTranslator()))) {
+ $description = $translator->translate($description);
+ }
+
+ if (empty($description)) {
+ return $content;
+ }
+
+ $separator = $this->getSeparator();
+ $placement = $this->getPlacement();
+ $tag = $this->getTag();
+ $class = $this->getClass();
+ $escape = $this->getEscape();
+
+ $options = $this->getOptions();
+
+ if ($escape) {
+ $description = $view->escape($description);
+ }
+
+ if (!empty($tag)) {
+ $options['tag'] = $tag;
+ $decorator = new Zend_Form_Decorator_HtmlTag($options);
+ $description = $decorator->render($description);
+ }
+
+ switch ($placement) {
+ case self::PREPEND:
+ return $description . $separator . $content;
+ case self::APPEND:
+ default:
+ return $content . $separator . $description;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/DtDdWrapper.php b/library/vendor/Zend/Form/Decorator/DtDdWrapper.php
new file mode 100644
index 0000000..b569fab
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/DtDdWrapper.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_DtDdWrapper
+ *
+ * Creates an empty <dt> item, and wraps the content in a <dd>. Used as a
+ * default decorator for subforms and display groups.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_DtDdWrapper extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Default placement: surround content
+ * @var string
+ */
+ protected $_placement = null;
+
+ /**
+ * Render
+ *
+ * Renders as the following:
+ * <dt>$dtLabel</dt>
+ * <dd>$content</dd>
+ *
+ * $dtLabel can be set via 'dtLabel' option, defaults to '\&#160;'
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $elementName = $this->getElement()->getName();
+
+ $dtLabel = $this->getOption('dtLabel');
+ if( null === $dtLabel ) {
+ $dtLabel = '&#160;';
+ }
+
+ return '<dt id="' . $elementName . '-label">' . $dtLabel . '</dt>' .
+ '<dd id="' . $elementName . '-element">' . $content . '</dd>';
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Errors.php b/library/vendor/Zend/Form/Decorator/Errors.php
new file mode 100644
index 0000000..24fc3d9
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Errors.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Errors
+ *
+ * Any options passed will be used as HTML attributes of the ul tag for the errors.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Errors extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Render errors
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ // Get error messages
+ if ($element instanceof Zend_Form
+ && null !== $element->getElementsBelongTo()
+ ) {
+ $errors = $element->getMessages(null, true);
+ } else {
+ $errors = $element->getMessages();
+ }
+
+ if (empty($errors)) {
+ return $content;
+ }
+
+ $separator = $this->getSeparator();
+ $placement = $this->getPlacement();
+ $errors = $view->formErrors($errors, $this->getOptions());
+
+ switch ($placement) {
+ case self::APPEND:
+ return $content . $separator . $errors;
+ case self::PREPEND:
+ return $errors . $separator . $content;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Exception.php b/library/vendor/Zend/Form/Decorator/Exception.php
new file mode 100644
index 0000000..6d86f32
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Exception */
+
+/**
+ * Exception for Zend_Form component.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Form_Decorator_Exception extends Zend_Form_Exception
+{
+}
diff --git a/library/vendor/Zend/Form/Decorator/Fieldset.php b/library/vendor/Zend/Form/Decorator/Fieldset.php
new file mode 100644
index 0000000..036df2c
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Fieldset.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Fieldset
+ *
+ * Any options passed will be used as HTML attributes of the fieldset tag.
+ *
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Fieldset extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Attribs that should be removed prior to rendering
+ * @var array
+ */
+ public $stripAttribs = array(
+ 'action',
+ 'enctype',
+ 'helper',
+ 'method',
+ 'name',
+ 'accept-charset',
+ );
+
+ /**
+ * Fieldset legend
+ * @var string
+ */
+ protected $_legend;
+
+ /**
+ * Default placement: surround content
+ * @var string
+ */
+ protected $_placement = null;
+
+ /**
+ * Get options
+ *
+ * Merges in element attributes as well.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ $options = parent::getOptions();
+ if (null !== ($element = $this->getElement())) {
+ $attribs = $element->getAttribs();
+ $options = array_merge($attribs, $options);
+ $this->setOptions($options);
+ }
+ return $options;
+ }
+
+ /**
+ * Set legend
+ *
+ * @param string $value
+ * @return Zend_Form_Decorator_Fieldset
+ */
+ public function setLegend($value)
+ {
+ $this->_legend = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Get legend
+ *
+ * @return string
+ */
+ public function getLegend()
+ {
+ $legend = $this->_legend;
+ if ((null === $legend) && (null !== ($element = $this->getElement()))) {
+ if (method_exists($element, 'getLegend')) {
+ $legend = $element->getLegend();
+ $this->setLegend($legend);
+ }
+ }
+ if ((null === $legend) && (null !== ($legend = $this->getOption('legend')))) {
+ $this->setLegend($legend);
+ $this->removeOption('legend');
+ }
+
+ return $legend;
+ }
+
+ /**
+ * Render a fieldset
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $legend = $this->getLegend();
+ $attribs = $this->getOptions();
+ $name = $element->getFullyQualifiedName();
+ $id = (string)$element->getId();
+
+ if ((!array_key_exists('id', $attribs) || $attribs['id'] == $id) && '' !== $id) {
+ $attribs['id'] = 'fieldset-' . $id;
+ }
+
+ if (null !== $legend) {
+ if (null !== ($translator = $element->getTranslator())) {
+ $legend = $translator->translate($legend);
+ }
+
+ $attribs['legend'] = $legend;
+ }
+
+ foreach (array_keys($attribs) as $attrib) {
+ $testAttrib = strtolower($attrib);
+ if (in_array($testAttrib, $this->stripAttribs)) {
+ unset($attribs[$attrib]);
+ }
+ }
+
+ return $view->fieldset($name, $content, $attribs);
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/File.php b/library/vendor/Zend/Form/Decorator/File.php
new file mode 100644
index 0000000..d7141de
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/File.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/** Zend_Form_Decorator_Marker_File_Interface */
+
+/** Zend_File_Transfer_Adapter_Http */
+
+/**
+ * Zend_Form_Decorator_File
+ *
+ * Fixes the rendering for all subform and multi file elements
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_File
+ extends Zend_Form_Decorator_Abstract
+ implements Zend_Form_Decorator_Marker_File_Interface
+{
+ /**
+ * Attributes that should not be passed to helper
+ * @var array
+ */
+ protected $_attribBlacklist = array('helper', 'placement', 'separator', 'value');
+
+ /**
+ * Default placement: append
+ * @var string
+ */
+ protected $_placement = 'APPEND';
+
+ /**
+ * Get attributes to pass to file helper
+ *
+ * @return array
+ */
+ public function getAttribs()
+ {
+ $attribs = $this->getOptions();
+
+ if (null !== ($element = $this->getElement())) {
+ $attribs = array_merge($attribs, $element->getAttribs());
+ }
+
+ foreach ($this->_attribBlacklist as $key) {
+ if (array_key_exists($key, $attribs)) {
+ unset($attribs[$key]);
+ }
+ }
+
+ return $attribs;
+ }
+
+ /**
+ * Render a form file
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ if (!$element instanceof Zend_Form_Element) {
+ return $content;
+ }
+
+ $view = $element->getView();
+ if (!$view instanceof Zend_View_Interface) {
+ return $content;
+ }
+
+ $name = $element->getName();
+ $attribs = $this->getAttribs();
+ if (!array_key_exists('id', $attribs)) {
+ $attribs['id'] = $name;
+ }
+
+ $separator = $this->getSeparator();
+ $placement = $this->getPlacement();
+ $markup = array();
+ $size = $element->getMaxFileSize();
+ if ($size > 0) {
+ $element->setMaxFileSize(0);
+ $markup[] = $view->formHidden('MAX_FILE_SIZE', $size);
+ }
+
+ if (Zend_File_Transfer_Adapter_Http::isApcAvailable()) {
+ $markup[] = $view->formHidden(ini_get('apc.rfc1867_name'), uniqid(), array('id' => 'progress_key'));
+ } else if (Zend_File_Transfer_Adapter_Http::isUploadProgressAvailable()) {
+ $markup[] = $view->formHidden('UPLOAD_IDENTIFIER', uniqid(), array('id' => 'progress_key'));
+ }
+
+ $helper = $element->helper;
+ if ($element->isArray()) {
+ $name .= "[]";
+ $count = $element->getMultiFile();
+ for ($i = 0; $i < $count; ++$i) {
+ $htmlAttribs = $attribs;
+ $htmlAttribs['id'] .= '-' . $i;
+ $markup[] = $view->$helper($name, $htmlAttribs);
+ }
+ } else {
+ $markup[] = $view->$helper($name, $attribs);
+ }
+
+ $markup = implode($separator, $markup);
+
+ switch ($placement) {
+ case self::PREPEND:
+ return $markup . $separator . $content;
+ case self::APPEND:
+ default:
+ return $content . $separator . $markup;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Form.php b/library/vendor/Zend/Form/Decorator/Form.php
new file mode 100644
index 0000000..c885731
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Form.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Form
+ *
+ * Render a Zend_Form object.
+ *
+ * Accepts following options:
+ * - separator: Separator to use between elements
+ * - helper: which view helper to use when rendering form. Should accept three
+ * arguments, string content, a name, and an array of attributes.
+ *
+ * Any other options passed will be used as HTML attributes of the form tag.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Form extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Default view helper
+ * @var string
+ */
+ protected $_helper = 'form';
+
+ /**
+ * Set view helper for rendering form
+ *
+ * @param string $helper
+ * @return Zend_Form_Decorator_Form
+ */
+ public function setHelper($helper)
+ {
+ $this->_helper = (string) $helper;
+ return $this;
+ }
+
+ /**
+ * Get view helper for rendering form
+ *
+ * @return string
+ */
+ public function getHelper()
+ {
+ if (null !== ($helper = $this->getOption('helper'))) {
+ $this->setHelper($helper);
+ $this->removeOption('helper');
+ }
+ return $this->_helper;
+ }
+
+ /**
+ * Retrieve decorator options
+ *
+ * Assures that form action and method are set, and sets appropriate
+ * encoding type if current method is POST.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ if (null !== ($element = $this->getElement())) {
+ if ($element instanceof Zend_Form) {
+ $element->getAction();
+ $method = $element->getMethod();
+ if ($method == Zend_Form::METHOD_POST) {
+ $this->setOption('enctype', 'application/x-www-form-urlencoded');
+ }
+ foreach ($element->getAttribs() as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ } elseif ($element instanceof Zend_Form_DisplayGroup) {
+ foreach ($element->getAttribs() as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+ }
+
+ if (isset($this->_options['method'])) {
+ $this->_options['method'] = strtolower($this->_options['method']);
+ }
+
+ return $this->_options;
+ }
+
+ /**
+ * Render a form
+ *
+ * Replaces $content entirely from currently set element.
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $form = $this->getElement();
+ $view = $form->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $helper = $this->getHelper();
+ $attribs = $this->getOptions();
+ $name = $form->getFullyQualifiedName();
+ $attribs['id'] = $form->getId();
+ return $view->$helper($name, $attribs, $content);
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/FormElements.php b/library/vendor/Zend/Form/Decorator/FormElements.php
new file mode 100644
index 0000000..c2fb136
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/FormElements.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_FormElements
+ *
+ * Render all form elements registered with current form
+ *
+ * Accepts following options:
+ * - separator: Separator to use between elements
+ *
+ * Any other options passed will be used as HTML attributes of the form tag.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_FormElements extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Merges given two belongsTo (array notation) strings
+ *
+ * @param string $baseBelongsTo
+ * @param string $belongsTo
+ * @return string
+ */
+ public function mergeBelongsTo($baseBelongsTo, $belongsTo)
+ {
+ $endOfArrayName = strpos($belongsTo, '[');
+
+ if ($endOfArrayName === false) {
+ return $baseBelongsTo . '[' . $belongsTo . ']';
+ }
+
+ $arrayName = substr($belongsTo, 0, $endOfArrayName);
+
+ return $baseBelongsTo . '[' . $arrayName . ']' . substr($belongsTo, $endOfArrayName);
+ }
+
+ /**
+ * Render form elements
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $form = $this->getElement();
+ if ((!$form instanceof Zend_Form) && (!$form instanceof Zend_Form_DisplayGroup)) {
+ return $content;
+ }
+
+ $belongsTo = ($form instanceof Zend_Form) ? $form->getElementsBelongTo() : null;
+ $elementContent = '';
+ $displayGroups = ($form instanceof Zend_Form) ? $form->getDisplayGroups() : array();
+ $separator = $this->getSeparator();
+ $translator = $form->getTranslator();
+ $items = array();
+ $view = $form->getView();
+ foreach ($form as $item) {
+ $item->setView($view);
+
+ // Set translator
+ if (!$item->hasTranslator()) {
+ $item->setTranslator($translator);
+ }
+
+ if ($item instanceof Zend_Form_Element) {
+ foreach ($displayGroups as $group) {
+ $elementName = $item->getName();
+ $element = $group->getElement($elementName);
+ if ($element) {
+ // Element belongs to display group; only render in that
+ // context.
+ continue 2;
+ }
+ }
+ $item->setBelongsTo($belongsTo);
+ } elseif (!empty($belongsTo) && ($item instanceof Zend_Form)) {
+ if ($item->isArray()) {
+ $name = $this->mergeBelongsTo($belongsTo, $item->getElementsBelongTo());
+ $item->setElementsBelongTo($name, true);
+ } else {
+ $item->setElementsBelongTo($belongsTo, true);
+ }
+ } elseif (!empty($belongsTo) && ($item instanceof Zend_Form_DisplayGroup)) {
+ foreach ($item as $element) {
+ $element->setBelongsTo($belongsTo);
+ }
+ }
+
+ $items[] = $item->render();
+
+ if (($item instanceof Zend_Form_Element_File)
+ || (($item instanceof Zend_Form)
+ && (Zend_Form::ENCTYPE_MULTIPART == $item->getEnctype()))
+ || (($item instanceof Zend_Form_DisplayGroup)
+ && (Zend_Form::ENCTYPE_MULTIPART == $item->getAttrib('enctype')))
+ ) {
+ if ($form instanceof Zend_Form) {
+ $form->setEnctype(Zend_Form::ENCTYPE_MULTIPART);
+ } elseif ($form instanceof Zend_Form_DisplayGroup) {
+ $form->setAttrib('enctype', Zend_Form::ENCTYPE_MULTIPART);
+ }
+ }
+ }
+ $elementContent = implode($separator, $items);
+
+ switch ($this->getPlacement()) {
+ case self::PREPEND:
+ return $elementContent . $separator . $content;
+ case self::APPEND:
+ default:
+ return $content . $separator . $elementContent;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/FormErrors.php b/library/vendor/Zend/Form/Decorator/FormErrors.php
new file mode 100644
index 0000000..70c9353
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/FormErrors.php
@@ -0,0 +1,514 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_FormErrors
+ *
+ * Displays all form errors in one view.
+ *
+ * Any options passed will be used as HTML attributes of the ul tag for the errors.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_FormErrors extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Default values for markup options
+ * @var array
+ */
+ protected $_defaults = array(
+ 'ignoreSubForms' => false,
+ 'showCustomFormErrors' => true,
+ 'onlyCustomFormErrors' => false,
+ 'markupElementLabelEnd' => '</b>',
+ 'markupElementLabelStart' => '<b>',
+ 'markupListEnd' => '</ul>',
+ 'markupListItemEnd' => '</li>',
+ 'markupListItemStart' => '<li>',
+ 'markupListStart' => '<ul class="form-errors">',
+ );
+
+ /**#@+
+ * Markup options
+ * @var string
+ */
+ protected $_ignoreSubForms;
+ protected $_showCustomFormErrors;
+ protected $_onlyCustomFormErrors;
+ protected $_markupElementLabelEnd;
+ protected $_markupElementLabelStart;
+ protected $_markupListEnd;
+ protected $_markupListItemEnd;
+ protected $_markupListItemStart;
+ protected $_markupListStart;
+ /**#@-*/
+
+ /**
+ * Whether or not to escape error label and error message
+ * @var bool
+ */
+ protected $_escape;
+
+ /**
+ * Render errors
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $form = $this->getElement();
+ if (!$form instanceof Zend_Form) {
+ return $content;
+ }
+
+ $view = $form->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $this->initOptions();
+ $markup = $this->_recurseForm($form, $view);
+
+ if (empty($markup)) {
+ return $content;
+ }
+
+ $markup = $this->getMarkupListStart()
+ . $markup
+ . $this->getMarkupListEnd();
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $this->getSeparator() . $markup;
+ case self::PREPEND:
+ return $markup . $this->getSeparator() . $content;
+ }
+ }
+
+ /**
+ * Initialize options
+ *
+ * @return void
+ */
+ public function initOptions()
+ {
+ $this->getMarkupElementLabelEnd();
+ $this->getMarkupElementLabelStart();
+ $this->getMarkupListEnd();
+ $this->getMarkupListItemEnd();
+ $this->getMarkupListItemStart();
+ $this->getMarkupListStart();
+ $this->getPlacement();
+ $this->getSeparator();
+ $this->ignoreSubForms();
+ $this->getShowCustomFormErrors();
+ $this->getOnlyCustomFormErrors();
+ }
+
+ /**
+ * Retrieve markupElementLabelStart
+ *
+ * @return string
+ */
+ public function getMarkupElementLabelStart()
+ {
+ if (null === $this->_markupElementLabelStart) {
+ if (null === ($markupElementLabelStart = $this->getOption('markupElementLabelStart'))) {
+ $this->setMarkupElementLabelStart($this->_defaults['markupElementLabelStart']);
+ } else {
+ $this->setMarkupElementLabelStart($markupElementLabelStart);
+ $this->removeOption('markupElementLabelStart');
+ }
+ }
+
+ return $this->_markupElementLabelStart;
+ }
+
+ /**
+ * Set markupElementLabelStart
+ *
+ * @param string $markupElementLabelStart
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setMarkupElementLabelStart($markupElementLabelStart)
+ {
+ $this->_markupElementLabelStart = $markupElementLabelStart;
+ return $this;
+ }
+
+ /**
+ * Retrieve markupElementLabelEnd
+ *
+ * @return string
+ */
+ public function getMarkupElementLabelEnd()
+ {
+ if (null === $this->_markupElementLabelEnd) {
+ if (null === ($markupElementLabelEnd = $this->getOption('markupElementLabelEnd'))) {
+ $this->setMarkupElementLabelEnd($this->_defaults['markupElementLabelEnd']);
+ } else {
+ $this->setMarkupElementLabelEnd($markupElementLabelEnd);
+ $this->removeOption('markupElementLabelEnd');
+ }
+ }
+
+ return $this->_markupElementLabelEnd;
+ }
+
+ /**
+ * Set markupElementLabelEnd
+ *
+ * @param string $markupElementLabelEnd
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setMarkupElementLabelEnd($markupElementLabelEnd)
+ {
+ $this->_markupElementLabelEnd = $markupElementLabelEnd;
+ return $this;
+ }
+
+ /**
+ * Retrieve markupListStart
+ *
+ * @return string
+ */
+ public function getMarkupListStart()
+ {
+ if (null === $this->_markupListStart) {
+ if (null === ($markupListStart = $this->getOption('markupListStart'))) {
+ $this->setMarkupListStart($this->_defaults['markupListStart']);
+ } else {
+ $this->setMarkupListStart($markupListStart);
+ $this->removeOption('markupListStart');
+ }
+ }
+
+ return $this->_markupListStart;
+ }
+
+ /**
+ * Set markupListStart
+ *
+ * @param string $markupListStart
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setMarkupListStart($markupListStart)
+ {
+ $this->_markupListStart = $markupListStart;
+ return $this;
+ }
+
+ /**
+ * Retrieve markupListEnd
+ *
+ * @return string
+ */
+ public function getMarkupListEnd()
+ {
+ if (null === $this->_markupListEnd) {
+ if (null === ($markupListEnd = $this->getOption('markupListEnd'))) {
+ $this->setMarkupListEnd($this->_defaults['markupListEnd']);
+ } else {
+ $this->setMarkupListEnd($markupListEnd);
+ $this->removeOption('markupListEnd');
+ }
+ }
+
+ return $this->_markupListEnd;
+ }
+
+ /**
+ * Set markupListEnd
+ *
+ * @param string $markupListEnd
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setMarkupListEnd($markupListEnd)
+ {
+ $this->_markupListEnd = $markupListEnd;
+ return $this;
+ }
+
+ /**
+ * Retrieve markupListItemStart
+ *
+ * @return string
+ */
+ public function getMarkupListItemStart()
+ {
+ if (null === $this->_markupListItemStart) {
+ if (null === ($markupListItemStart = $this->getOption('markupListItemStart'))) {
+ $this->setMarkupListItemStart($this->_defaults['markupListItemStart']);
+ } else {
+ $this->setMarkupListItemStart($markupListItemStart);
+ $this->removeOption('markupListItemStart');
+ }
+ }
+
+ return $this->_markupListItemStart;
+ }
+
+ /**
+ * Set markupListItemStart
+ *
+ * @param string $markupListItemStart
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setMarkupListItemStart($markupListItemStart)
+ {
+ $this->_markupListItemStart = $markupListItemStart;
+ return $this;
+ }
+
+ /**
+ * Retrieve markupListItemEnd
+ *
+ * @return string
+ */
+ public function getMarkupListItemEnd()
+ {
+ if (null === $this->_markupListItemEnd) {
+ if (null === ($markupListItemEnd = $this->getOption('markupListItemEnd'))) {
+ $this->setMarkupListItemEnd($this->_defaults['markupListItemEnd']);
+ } else {
+ $this->setMarkupListItemEnd($markupListItemEnd);
+ $this->removeOption('markupListItemEnd');
+ }
+ }
+
+ return $this->_markupListItemEnd;
+ }
+
+ /**
+ * Set markupListItemEnd
+ *
+ * @param string $markupListItemEnd
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setMarkupListItemEnd($markupListItemEnd)
+ {
+ $this->_markupListItemEnd = $markupListItemEnd;
+ return $this;
+ }
+
+ /**
+ * Retrieve ignoreSubForms
+ *
+ * @return bool
+ */
+ public function ignoreSubForms()
+ {
+ if (null === $this->_ignoreSubForms) {
+ if (null === ($ignoreSubForms = $this->getOption('ignoreSubForms'))) {
+ $this->setIgnoreSubForms($this->_defaults['ignoreSubForms']);
+ } else {
+ $this->setIgnoreSubForms($ignoreSubForms);
+ $this->removeOption('ignoreSubForms');
+ }
+ }
+
+ return $this->_ignoreSubForms;
+ }
+
+ /**
+ * Set ignoreSubForms
+ *
+ * @param bool $ignoreSubForms
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setIgnoreSubForms($ignoreSubForms)
+ {
+ $this->_ignoreSubForms = (bool) $ignoreSubForms;
+ return $this;
+ }
+
+ /**
+ * Get showCustomFormErrors
+ *
+ * @return bool
+ */
+ public function getShowCustomFormErrors()
+ {
+ if (null === $this->_showCustomFormErrors) {
+ if (null === ($show = $this->getOption('showCustomFormErrors'))) {
+ $this->setShowCustomFormErrors($this->_defaults['showCustomFormErrors']);
+ } else {
+ $this->setShowCustomFormErrors($show);
+ $this->removeOption('showCustomFormErrors');
+ }
+ }
+ return $this->_showCustomFormErrors;
+ }
+
+ /**
+ * Set showCustomFormErrors
+ *
+ * @param bool $showCustomFormErrors
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setShowCustomFormErrors($showCustomFormErrors)
+ {
+ $this->_showCustomFormErrors = (bool)$showCustomFormErrors;
+ return $this;
+ }
+
+ /**
+ * Get onlyCustomFormErrors
+ *
+ * @return bool
+ */
+ public function getOnlyCustomFormErrors()
+ {
+ if (null === $this->_onlyCustomFormErrors) {
+ if (null === ($show = $this->getOption('onlyCustomFormErrors'))) {
+ $this->setOnlyCustomFormErrors($this->_defaults['onlyCustomFormErrors']);
+ } else {
+ $this->setOnlyCustomFormErrors($show);
+ $this->removeOption('onlyCustomFormErrors');
+ }
+ }
+ return $this->_onlyCustomFormErrors;
+ }
+
+ /**
+ * Set onlyCustomFormErrors, whether to display elements messages
+ * in addition to custom form messages.
+ *
+ * @param bool $onlyCustomFormErrors
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setOnlyCustomFormErrors($onlyCustomFormErrors)
+ {
+ $this->_onlyCustomFormErrors = (bool)$onlyCustomFormErrors;
+ return $this;
+ }
+
+ /**
+ * Set whether or not to escape error label and error message
+ *
+ * Sets also the 'escape' option for the view helper
+ *
+ * @param bool $flag
+ * @return Zend_Form_Decorator_FormErrors
+ */
+ public function setEscape($flag)
+ {
+ $this->_escape = (bool) $flag;
+
+ // Set also option for view helper
+ $this->setOption('escape', $this->_escape);
+ return $this;
+ }
+
+ /**
+ * Get escape flag
+ *
+ * @return bool
+ */
+ public function getEscape()
+ {
+ if (null === $this->_escape) {
+ if (null !== ($escape = $this->getOption('escape'))) {
+ $this->setEscape($escape);
+ } else {
+ $this->setEscape(true);
+ }
+ }
+
+ return $this->_escape;
+ }
+
+ /**
+ * Render element label
+ *
+ * @param Zend_Form_Element $element
+ * @param Zend_View_Interface $view
+ * @return string
+ */
+ public function renderLabel(Zend_Form_Element $element, Zend_View_Interface $view)
+ {
+ $label = $element->getLabel();
+ if (empty($label)) {
+ $label = $element->getName();
+
+ // Translate element name
+ if (null !== ($translator = $element->getTranslator())) {
+ $label = $translator->translate($label);
+ }
+ }
+
+ if ($this->getEscape()) {
+ $label = $view->escape($label);
+ }
+
+ return $this->getMarkupElementLabelStart()
+ . $label
+ . $this->getMarkupElementLabelEnd();
+ }
+
+ /**
+ * Recurse through a form object, rendering errors
+ *
+ * @param Zend_Form $form
+ * @param Zend_View_Interface $view
+ * @return string
+ */
+ protected function _recurseForm(Zend_Form $form, Zend_View_Interface $view)
+ {
+ $content = '';
+
+ $custom = $form->getCustomMessages();
+ if ($this->getShowCustomFormErrors() && count($custom)) {
+ $content .= $this->getMarkupListItemStart()
+ . $view->formErrors($custom, $this->getOptions())
+ . $this->getMarkupListItemEnd();
+ }
+ foreach ($form->getElementsAndSubFormsOrdered() as $subitem) {
+ if ($subitem instanceof Zend_Form_Element && !$this->getOnlyCustomFormErrors()) {
+ $messages = $subitem->getMessages();
+ if (count($messages)) {
+ $subitem->setView($view);
+ $content .= $this->getMarkupListItemStart()
+ . $this->renderLabel($subitem, $view)
+ . $view->formErrors($messages, $this->getOptions())
+ . $this->getMarkupListItemEnd();
+ }
+ } else if ($subitem instanceof Zend_Form && !$this->ignoreSubForms()) {
+ $markup = $this->_recurseForm($subitem, $view);
+
+ if (!empty($markup)) {
+ $content .= $this->getMarkupListStart()
+ . $markup
+ . $this->getMarkupListEnd();
+ }
+ }
+ }
+ return $content;
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/HtmlTag.php b/library/vendor/Zend/Form/Decorator/HtmlTag.php
new file mode 100644
index 0000000..47b9e21
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/HtmlTag.php
@@ -0,0 +1,253 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * @see Zend_Form_Decorator_Abstract
+ */
+
+/**
+ * Zend_Form_Decorator_Element_HtmlTag
+ *
+ * Wraps content in an HTML block tag.
+ *
+ * Options accepted are:
+ * - tag: tag to use in decorator
+ * - noAttribs: do not render attributes in the opening tag
+ * - placement: 'append' or 'prepend'. If 'append', renders opening and
+ * closing tag after content; if prepend, renders opening and closing tag
+ * before content.
+ * - openOnly: render opening tag only
+ * - closeOnly: render closing tag only
+ *
+ * Any other options passed are processed as HTML attributes of the tag.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_HtmlTag extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Character encoding to use when escaping attributes
+ * @var string
+ */
+ protected $_encoding;
+
+ /**
+ * Placement; default to surround content
+ * @var string
+ */
+ protected $_placement = null;
+
+ /**
+ * HTML tag to use
+ * @var string
+ */
+ protected $_tag;
+
+ /**
+ * @var Zend_Filter
+ */
+ protected $_tagFilter;
+
+ /**
+ * Convert options to tag attributes
+ *
+ * @return string
+ */
+ protected function _htmlAttribs(array $attribs)
+ {
+ $xhtml = '';
+ $enc = $this->_getEncoding();
+ foreach ((array) $attribs as $key => $val) {
+ $key = htmlspecialchars($key, ENT_COMPAT, $enc);
+ if (is_array($val)) {
+ if (array_key_exists('callback', $val)
+ && is_callable($val['callback'])
+ ) {
+ $val = call_user_func($val['callback'], $this);
+ } else {
+ $val = implode(' ', $val);
+ }
+ }
+ $val = htmlspecialchars($val ?? '', ENT_COMPAT, $enc);
+ $xhtml .= " $key=\"$val\"";
+ }
+ return $xhtml;
+ }
+
+ /**
+ * Normalize tag
+ *
+ * Ensures tag is alphanumeric characters only, and all lowercase.
+ *
+ * @param string $tag
+ * @return string
+ */
+ public function normalizeTag($tag)
+ {
+ if (!isset($this->_tagFilter)) {
+ $this->_tagFilter = new Zend_Filter();
+ $this->_tagFilter->addFilter(new Zend_Filter_Alnum())
+ ->addFilter(new Zend_Filter_StringToLower());
+ }
+ return $this->_tagFilter->filter($tag);
+ }
+
+ /**
+ * Set tag to use
+ *
+ * @param string $tag
+ * @return Zend_Form_Decorator_HtmlTag
+ */
+ public function setTag($tag)
+ {
+ $this->_tag = $this->normalizeTag($tag);
+ return $this;
+ }
+
+ /**
+ * Get tag
+ *
+ * If no tag is registered, either via setTag() or as an option, uses 'div'.
+ *
+ * @return string
+ */
+ public function getTag()
+ {
+ if (null === $this->_tag) {
+ if (null === ($tag = $this->getOption('tag'))) {
+ $this->setTag('div');
+ } else {
+ $this->setTag($tag);
+ $this->removeOption('tag');
+ }
+ }
+
+ return $this->_tag;
+ }
+
+ /**
+ * Get the formatted open tag
+ *
+ * @param string $tag
+ * @param array $attribs
+ * @return string
+ */
+ protected function _getOpenTag($tag, array $attribs = null)
+ {
+ $html = '<' . $tag;
+ if (null !== $attribs) {
+ $html .= $this->_htmlAttribs($attribs);
+ }
+ $html .= '>';
+ return $html;
+ }
+
+ /**
+ * Get formatted closing tag
+ *
+ * @param string $tag
+ * @return string
+ */
+ protected function _getCloseTag($tag)
+ {
+ return '</' . $tag . '>';
+ }
+
+ /**
+ * Render content wrapped in an HTML tag
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $tag = $this->getTag();
+ $placement = $this->getPlacement();
+ $noAttribs = $this->getOption('noAttribs');
+ $openOnly = $this->getOption('openOnly');
+ $closeOnly = $this->getOption('closeOnly');
+ $this->removeOption('noAttribs');
+ $this->removeOption('openOnly');
+ $this->removeOption('closeOnly');
+
+ $attribs = null;
+ if (!$noAttribs) {
+ $attribs = $this->getOptions();
+ }
+
+ switch ($placement) {
+ case self::APPEND:
+ if ($closeOnly) {
+ return $content . $this->_getCloseTag($tag);
+ }
+ if ($openOnly) {
+ return $content . $this->_getOpenTag($tag, $attribs);
+ }
+ return $content
+ . $this->_getOpenTag($tag, $attribs)
+ . $this->_getCloseTag($tag);
+ case self::PREPEND:
+ if ($closeOnly) {
+ return $this->_getCloseTag($tag) . $content;
+ }
+ if ($openOnly) {
+ return $this->_getOpenTag($tag, $attribs) . $content;
+ }
+ return $this->_getOpenTag($tag, $attribs)
+ . $this->_getCloseTag($tag)
+ . $content;
+ default:
+ return (($openOnly || !$closeOnly) ? $this->_getOpenTag($tag, $attribs) : '')
+ . $content
+ . (($closeOnly || !$openOnly) ? $this->_getCloseTag($tag) : '');
+ }
+ }
+
+ /**
+ * Get encoding for use with htmlspecialchars()
+ *
+ * @return string
+ */
+ protected function _getEncoding()
+ {
+ if (null !== $this->_encoding) {
+ return $this->_encoding;
+ }
+
+ if (null === ($element = $this->getElement())) {
+ $this->_encoding = 'UTF-8';
+ } elseif (null === ($view = $element->getView())) {
+ $this->_encoding = 'UTF-8';
+ } elseif (!$view instanceof Zend_View_Abstract
+ && !method_exists($view, 'getEncoding')
+ ) {
+ $this->_encoding = 'UTF-8';
+ } else {
+ $this->_encoding = $view->getEncoding();
+ }
+ return $this->_encoding;
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Image.php b/library/vendor/Zend/Form/Decorator/Image.php
new file mode 100644
index 0000000..4ec01c1
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Image.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Image
+ *
+ * Accepts the options:
+ * - separator: separator to use between image and content (defaults to PHP_EOL)
+ * - placement: whether to append or prepend label to content (defaults to append)
+ * - tag: if set, used to wrap the label in an additional HTML tag
+ *
+ * Any other options passed will be used as HTML attributes of the image tag.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Image extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Attributes that should not be passed to helper
+ * @var array
+ */
+ protected $_attribBlacklist = array('helper', 'placement', 'separator', 'tag');
+
+ /**
+ * Default placement: append
+ * @var string
+ */
+ protected $_placement = 'APPEND';
+
+ /**
+ * HTML tag with which to surround image
+ * @var string
+ */
+ protected $_tag;
+
+ /**
+ * Set HTML tag with which to surround label
+ *
+ * @param string $tag
+ * @return Zend_Form_Decorator_Image
+ */
+ public function setTag($tag)
+ {
+ $this->_tag = (string) $tag;
+ return $this;
+ }
+
+ /**
+ * Get HTML tag, if any, with which to surround label
+ *
+ * @return void
+ */
+ public function getTag()
+ {
+ if (null === $this->_tag) {
+ $tag = $this->getOption('tag');
+ if (null !== $tag) {
+ $this->removeOption('tag');
+ $this->setTag($tag);
+ }
+ return $tag;
+ }
+
+ return $this->_tag;
+ }
+
+ /**
+ * Get attributes to pass to image helper
+ *
+ * @return array
+ */
+ public function getAttribs()
+ {
+ $attribs = $this->getOptions();
+
+ if (null !== ($element = $this->getElement())) {
+ $attribs['alt'] = $element->getLabel();
+ $attribs = array_merge($attribs, $element->getAttribs());
+ }
+
+ foreach ($this->_attribBlacklist as $key) {
+ if (array_key_exists($key, $attribs)) {
+ unset($attribs[$key]);
+ }
+ }
+
+ return $attribs;
+ }
+
+ /**
+ * Render a form image
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $tag = $this->getTag();
+ $placement = $this->getPlacement();
+ $separator = $this->getSeparator();
+ $name = $element->getFullyQualifiedName();
+ $attribs = $this->getAttribs();
+ $attribs['id'] = $element->getId();
+
+ $image = $view->formImage($name, $element->getImageValue(), $attribs);
+
+ if (null !== $tag) {
+ $decorator = new Zend_Form_Decorator_HtmlTag();
+ $decorator->setOptions(array('tag' => $tag));
+ $image = $decorator->render($image);
+ }
+
+ switch ($placement) {
+ case self::PREPEND:
+ return $image . $separator . $content;
+ case self::APPEND:
+ default:
+ return $content . $separator . $image;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Interface.php b/library/vendor/Zend/Form/Decorator/Interface.php
new file mode 100644
index 0000000..5cc1171
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Interface.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Form_Decorator_Interface
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+interface Zend_Form_Decorator_Interface
+{
+ /**
+ * Constructor
+ *
+ * Accept options during initialization.
+ *
+ * @param array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = null);
+
+ /**
+ * Set an element to decorate
+ *
+ * While the name is "setElement", a form decorator could decorate either
+ * an element or a form object.
+ *
+ * @param mixed $element
+ * @return Zend_Form_Decorator_Interface
+ */
+ public function setElement($element);
+
+ /**
+ * Retrieve current element
+ *
+ * @return mixed
+ */
+ public function getElement();
+
+ /**
+ * Set decorator options from an array
+ *
+ * @param array $options
+ * @return Zend_Form_Decorator_Interface
+ */
+ public function setOptions(array $options);
+
+ /**
+ * Set decorator options from a config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Form_Decorator_Interface
+ */
+ public function setConfig(Zend_Config $config);
+
+ /**
+ * Set a single option
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return Zend_Form_Decorator_Interface
+ */
+ public function setOption($key, $value);
+
+ /**
+ * Retrieve a single option
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getOption($key);
+
+ /**
+ * Retrieve decorator options
+ *
+ * @return array
+ */
+ public function getOptions();
+
+ /**
+ * Delete a single option
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function removeOption($key);
+
+ /**
+ * Clear all options
+ *
+ * @return Zend_Form_Decorator_Interface
+ */
+ public function clearOptions();
+
+ /**
+ * Render the element
+ *
+ * @param string $content Content to decorate
+ * @return string
+ */
+ public function render($content);
+}
diff --git a/library/vendor/Zend/Form/Decorator/Label.php b/library/vendor/Zend/Form/Decorator/Label.php
new file mode 100644
index 0000000..c70c61e
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Label.php
@@ -0,0 +1,461 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Label
+ *
+ * Accepts the options:
+ * - separator: separator to use between label and content (defaults to PHP_EOL)
+ * - placement: whether to append or prepend label to content (defaults to prepend)
+ * - tag: if set, used to wrap the label in an additional HTML tag
+ * - tagClass: if tag option is set, used to add a class to the label wrapper
+ * - opt(ional)Prefix: a prefix to the label to use when the element is optional
+ * - opt(ional)Suffix: a suffix to the label to use when the element is optional
+ * - req(uired)Prefix: a prefix to the label to use when the element is required
+ * - req(uired)Suffix: a suffix to the label to use when the element is required
+ *
+ * Any other options passed will be used as HTML attributes of the label tag.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_Label extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Placement constants
+ */
+ const IMPLICIT = 'IMPLICIT';
+ const IMPLICIT_PREPEND = 'IMPLICIT_PREPEND';
+ const IMPLICIT_APPEND = 'IMPLICIT_APPEND';
+
+ /**
+ * Default placement: prepend
+ * @var string
+ */
+ protected $_placement = 'PREPEND';
+
+ /**
+ * HTML tag with which to surround label
+ * @var string
+ */
+ protected $_tag;
+
+ /**
+ * Class for the HTML tag with which to surround label
+ * @var string
+ */
+ protected $_tagClass;
+
+ /**
+ * Set element ID
+ *
+ * @param string $id
+ * @return Zend_Form_Decorator_Label
+ */
+ public function setId($id)
+ {
+ $this->setOption('id', $id);
+ return $this;
+ }
+
+ /**
+ * Retrieve element ID (used in 'for' attribute)
+ *
+ * If none set in decorator, looks first for element 'id' attribute, and
+ * defaults to element name.
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ $id = $this->getOption('id');
+ if (null === $id) {
+ if (null !== ($element = $this->getElement())) {
+ $id = $element->getId();
+ $this->setId($id);
+ }
+ }
+
+ return $id;
+ }
+
+ /**
+ * Set HTML tag with which to surround label
+ *
+ * @param string $tag
+ * @return Zend_Form_Decorator_Label
+ */
+ public function setTag($tag)
+ {
+ if (empty($tag)) {
+ $this->_tag = null;
+ } else {
+ $this->_tag = (string) $tag;
+ }
+
+ $this->removeOption('tag');
+
+ return $this;
+ }
+
+ /**
+ * Get HTML tag, if any, with which to surround label
+ *
+ * @return string
+ */
+ public function getTag()
+ {
+ if (null === $this->_tag) {
+ $tag = $this->getOption('tag');
+ if (null !== $tag) {
+ $this->removeOption('tag');
+ $this->setTag($tag);
+ }
+ return $tag;
+ }
+
+ return $this->_tag;
+ }
+
+ /**
+ * Set the class to apply to the HTML tag with which to surround label
+ *
+ * @param string $tagClass
+ * @return Zend_Form_Decorator_Label
+ */
+ public function setTagClass($tagClass)
+ {
+ if (empty($tagClass)) {
+ $this->_tagClass = null;
+ } else {
+ $this->_tagClass = (string) $tagClass;
+ }
+
+ $this->removeOption('tagClass');
+
+ return $this;
+ }
+
+ /**
+ * Get the class to apply to the HTML tag, if any, with which to surround label
+ *
+ * @return void
+ */
+ public function getTagClass()
+ {
+ if (null === $this->_tagClass) {
+ $tagClass = $this->getOption('tagClass');
+ if (null !== $tagClass) {
+ $this->removeOption('tagClass');
+ $this->setTagClass($tagClass);
+ }
+ }
+
+ return $this->_tagClass;
+ }
+
+ /**
+ * Get class with which to define label
+ *
+ * Appends either 'optional' or 'required' to class, depending on whether
+ * or not the element is required.
+ *
+ * @return string
+ */
+ public function getClass()
+ {
+ $class = '';
+ $element = $this->getElement();
+
+ $decoratorClass = $this->getOption('class');
+ if (!empty($decoratorClass)) {
+ $class .= ' ' . $decoratorClass;
+ }
+
+ $type = $element->isRequired() ? 'required' : 'optional';
+
+ if (!strstr($class, $type)) {
+ $class .= ' ' . $type;
+ $class = trim($class);
+ }
+
+ return $class;
+ }
+
+ /**
+ * Load an optional/required suffix/prefix key
+ *
+ * @param string $key
+ * @return void
+ */
+ protected function _loadOptReqKey($key)
+ {
+ if (!isset($this->$key)) {
+ $value = $this->getOption($key);
+ $this->$key = (string) $value;
+ if (null !== $value) {
+ $this->removeOption($key);
+ }
+ }
+ }
+
+ /**
+ * Overloading
+ *
+ * Currently overloads:
+ *
+ * - getOpt(ional)Prefix()
+ * - getOpt(ional)Suffix()
+ * - getReq(uired)Prefix()
+ * - getReq(uired)Suffix()
+ * - setOpt(ional)Prefix()
+ * - setOpt(ional)Suffix()
+ * - setReq(uired)Prefix()
+ * - setReq(uired)Suffix()
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ * @throws Zend_Form_Exception for unsupported methods
+ */
+ public function __call($method, $args)
+ {
+ $tail = substr($method, -6);
+ $head = substr($method, 0, 3);
+ if (in_array($head, array('get', 'set'))
+ && (('Prefix' == $tail) || ('Suffix' == $tail))
+ ) {
+ $position = substr($method, -6);
+ $type = strtolower(substr($method, 3, 3));
+ switch ($type) {
+ case 'req':
+ $key = 'required' . $position;
+ break;
+ case 'opt':
+ $key = 'optional' . $position;
+ break;
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid method "%s" called in Label decorator, and detected as type %s', $method, $type));
+ }
+
+ switch ($head) {
+ case 'set':
+ if (0 === count($args)) {
+ throw new Zend_Form_Exception(sprintf('Method "%s" requires at least one argument; none provided', $method));
+ }
+ $value = array_shift($args);
+ $this->$key = $value;
+ return $this;
+ case 'get':
+ default:
+ if (null === ($element = $this->getElement())) {
+ $this->_loadOptReqKey($key);
+ } elseif (isset($element->$key)) {
+ $this->$key = (string) $element->$key;
+ } else {
+ $this->_loadOptReqKey($key);
+ }
+ return $this->$key;
+ }
+ }
+
+ throw new Zend_Form_Exception(sprintf('Invalid method "%s" called in Label decorator', $method));
+ }
+
+ /**
+ * Get label to render
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ if (null === ($element = $this->getElement())) {
+ return '';
+ }
+
+ $label = $element->getLabel();
+ $label = trim($label ?? '');
+
+ if (empty($label)) {
+ return '';
+ }
+
+ $optPrefix = $this->getOptPrefix();
+ $optSuffix = $this->getOptSuffix();
+ $reqPrefix = $this->getReqPrefix();
+ $reqSuffix = $this->getReqSuffix();
+ $separator = $this->getSeparator();
+
+ if (!empty($label)) {
+ if ($element->isRequired()) {
+ $label = $reqPrefix . $label . $reqSuffix;
+ } else {
+ $label = $optPrefix . $label . $optSuffix;
+ }
+ }
+
+ return $label;
+ }
+
+ /**
+ * Determine if label should append, prepend or implicit content
+ *
+ * @return string
+ */
+ public function getPlacement()
+ {
+ $placement = $this->_placement;
+ if (null !== ($placementOpt = $this->getOption('placement'))) {
+ $placementOpt = strtoupper($placementOpt);
+ switch ($placementOpt) {
+ case self::APPEND:
+ case self::PREPEND:
+ case self::IMPLICIT:
+ case self::IMPLICIT_PREPEND:
+ case self::IMPLICIT_APPEND:
+ $placement = $this->_placement = $placementOpt;
+ break;
+ case false:
+ $placement = $this->_placement = null;
+ break;
+ default:
+ break;
+ }
+ $this->removeOption('placement');
+ }
+
+ return $placement;
+ }
+
+ /**
+ * Render a label
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $label = $this->getLabel();
+ $separator = $this->getSeparator();
+ $placement = $this->getPlacement();
+ $tag = $this->getTag();
+ $tagClass = $this->getTagClass();
+ $id = $this->getId();
+ $class = $this->getClass();
+ $options = $this->getOptions();
+
+
+ if (empty($label) && empty($tag)) {
+ return $content;
+ }
+
+ if (!empty($label)) {
+ $options['class'] = $class;
+ $label = trim($label);
+
+ switch ($placement) {
+ case self::IMPLICIT:
+ // Break was intentionally omitted
+
+ case self::IMPLICIT_PREPEND:
+ $options['escape'] = false;
+ $options['disableFor'] = true;
+
+ $label = $view->formLabel(
+ $element->getFullyQualifiedName(),
+ $label . $separator . $content,
+ $options
+ );
+ break;
+
+ case self::IMPLICIT_APPEND:
+ $options['escape'] = false;
+ $options['disableFor'] = true;
+
+ $label = $view->formLabel(
+ $element->getFullyQualifiedName(),
+ $content . $separator . $label,
+ $options
+ );
+ break;
+
+ case self::APPEND:
+ // Break was intentionally omitted
+
+ case self::PREPEND:
+ // Break was intentionally omitted
+
+ default:
+ $label = $view->formLabel(
+ $element->getFullyQualifiedName(),
+ $label,
+ $options
+ );
+ break;
+ }
+ } else {
+ $label = '&#160;';
+ }
+
+ if (null !== $tag) {
+ $decorator = new Zend_Form_Decorator_HtmlTag();
+ if (null !== $this->_tagClass) {
+ $decorator->setOptions(array('tag' => $tag,
+ 'id' => $id . '-label',
+ 'class' => $tagClass));
+ } else {
+ $decorator->setOptions(array('tag' => $tag,
+ 'id' => $id . '-label'));
+ }
+
+ $label = $decorator->render($label);
+ }
+
+ switch ($placement) {
+ case self::APPEND:
+ return $content . $separator . $label;
+
+ case self::PREPEND:
+ return $label . $separator . $content;
+
+ case self::IMPLICIT:
+ // Break was intentionally omitted
+
+ case self::IMPLICIT_PREPEND:
+ // Break was intentionally omitted
+
+ case self::IMPLICIT_APPEND:
+ return $label;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Marker/File/Interface.php b/library/vendor/Zend/Form/Decorator/Marker/File/Interface.php
new file mode 100644
index 0000000..40a4fe7
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Marker/File/Interface.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Form_Decorator_Marker_File_Interface
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+interface Zend_Form_Decorator_Marker_File_Interface
+{
+}
diff --git a/library/vendor/Zend/Form/Decorator/PrepareElements.php b/library/vendor/Zend/Form/Decorator/PrepareElements.php
new file mode 100644
index 0000000..52ed93c
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/PrepareElements.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_FormElements */
+
+/**
+ * Zend_Form_Decorator_PrepareElements
+ *
+ * Render all form elements registered with current form
+ *
+ * Accepts following options:
+ * - separator: Separator to use between elements
+ *
+ * Any other options passed will be used as HTML attributes of the form tag.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_PrepareElements extends Zend_Form_Decorator_FormElements
+{
+ /**
+ * Render form elements
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $form = $this->getElement();
+ if ((!$form instanceof Zend_Form) && (!$form instanceof Zend_Form_DisplayGroup)) {
+ return $content;
+ }
+
+ $this->_recursivelyPrepareForm($form);
+
+ return $content;
+ }
+
+ protected function _recursivelyPrepareForm(Zend_Form $form)
+ {
+ $belongsTo = ($form instanceof Zend_Form) ? $form->getElementsBelongTo() : null;
+ $elementContent = '';
+ $separator = $this->getSeparator();
+ $translator = $form->getTranslator();
+ $view = $form->getView();
+
+ foreach ($form as $item) {
+ $item->setView($view)
+ ->setTranslator($translator);
+ if ($item instanceof Zend_Form_Element) {
+ $item->setBelongsTo($belongsTo);
+ } elseif (!empty($belongsTo) && ($item instanceof Zend_Form)) {
+ if ($item->isArray()) {
+ $name = $this->mergeBelongsTo($belongsTo, $item->getElementsBelongTo());
+ $item->setElementsBelongTo($name, true);
+ } else {
+ $item->setElementsBelongTo($belongsTo, true);
+ }
+ $this->_recursivelyPrepareForm($item);
+ } elseif (!empty($belongsTo) && ($item instanceof Zend_Form_DisplayGroup)) {
+ foreach ($item as $element) {
+ $element->setBelongsTo($belongsTo);
+ }
+ }
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/Tooltip.php b/library/vendor/Zend/Form/Decorator/Tooltip.php
new file mode 100644
index 0000000..2448b61
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/Tooltip.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_Tooltip
+ *
+ * Will translate the title attribute, if available
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id: Tooltip.php$
+ */
+class Zend_Form_Decorator_Tooltip extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Translates the title attribute if it is available, if the translator is available
+ * and if the translator is not disable on the element being rendered.
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ if (null !== ($title = $this->getElement()->getAttrib('title'))) {
+ if (null !== ($translator = $this->getElement()->getTranslator())) {
+ $title = $translator->translate($title);
+ }
+ }
+
+ $this->getElement()->setAttrib('title', $title);
+ return $content;
+ }
+
+}
diff --git a/library/vendor/Zend/Form/Decorator/ViewHelper.php b/library/vendor/Zend/Form/Decorator/ViewHelper.php
new file mode 100644
index 0000000..a0f71af
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/ViewHelper.php
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_ViewHelper
+ *
+ * Decorate an element by using a view helper to render it.
+ *
+ * Accepts the following options:
+ * - separator: string with which to separate passed in content and generated content
+ * - placement: whether to append or prepend the generated content to the passed in content
+ * - helper: the name of the view helper to use
+ *
+ * Assumes the view helper accepts three parameters, the name, value, and
+ * optional attributes; these will be provided by the element.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_ViewHelper extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Element types that represent buttons
+ * @var array
+ */
+ protected $_buttonTypes = array(
+ 'Zend_Form_Element_Button',
+ 'Zend_Form_Element_Reset',
+ 'Zend_Form_Element_Submit',
+ );
+
+ /**
+ * View helper to use when rendering
+ * @var string
+ */
+ protected $_helper;
+
+ /**
+ * Set view helper to use when rendering
+ *
+ * @param string $helper
+ * @return Zend_Form_Decorator_Element_ViewHelper
+ */
+ public function setHelper($helper)
+ {
+ $this->_helper = (string) $helper;
+ return $this;
+ }
+
+ /**
+ * Retrieve view helper for rendering element
+ *
+ * @return string
+ */
+ public function getHelper()
+ {
+ if (null === $this->_helper) {
+ $options = $this->getOptions();
+ if (isset($options['helper'])) {
+ $this->setHelper($options['helper']);
+ $this->removeOption('helper');
+ } else {
+ $element = $this->getElement();
+ if (null !== $element) {
+ if (null !== ($helper = $element->getAttrib('helper'))) {
+ $this->setHelper($helper);
+ } else {
+ $type = $element->getType();
+ if ($pos = strrpos($type, '_')) {
+ $type = substr($type, $pos + 1);
+ }
+ $this->setHelper('form' . ucfirst($type));
+ }
+ }
+ }
+ }
+
+ return $this->_helper;
+ }
+
+ /**
+ * Get name
+ *
+ * If element is a Zend_Form_Element, will attempt to namespace it if the
+ * element belongs to an array.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ if (null === ($element = $this->getElement())) {
+ return '';
+ }
+
+ $name = $element->getName();
+
+ if (!$element instanceof Zend_Form_Element) {
+ return $name;
+ }
+
+ if (null !== ($belongsTo = $element->getBelongsTo())) {
+ $name = $belongsTo . '['
+ . $name
+ . ']';
+ }
+
+ if ($element->isArray()) {
+ $name .= '[]';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Retrieve element attributes
+ *
+ * Set id to element name and/or array item.
+ *
+ * @return array
+ */
+ public function getElementAttribs()
+ {
+ if (null === ($element = $this->getElement())) {
+ return null;
+ }
+
+ $attribs = $element->getAttribs();
+ if (isset($attribs['helper'])) {
+ unset($attribs['helper']);
+ }
+
+ if (method_exists($element, 'getSeparator')) {
+ if (null !== ($listsep = $element->getSeparator())) {
+ $attribs['listsep'] = $listsep;
+ }
+ }
+
+ if (isset($attribs['id'])) {
+ return $attribs;
+ }
+
+ $id = $element->getName();
+
+ if ($element instanceof Zend_Form_Element) {
+ if (null !== ($belongsTo = $element->getBelongsTo())) {
+ $belongsTo = preg_replace('/\[([^\]]+)\]/', '-$1', $belongsTo);
+ $id = $belongsTo . '-' . $id;
+ }
+ }
+
+ $element->setAttrib('id', $id);
+ $attribs['id'] = $id;
+
+ return $attribs;
+ }
+
+ /**
+ * Get value
+ *
+ * If element type is one of the button types, returns the label.
+ *
+ * @param Zend_Form_Element $element
+ * @return string|null
+ */
+ public function getValue($element)
+ {
+ if (!$element instanceof Zend_Form_Element) {
+ return null;
+ }
+
+ foreach ($this->_buttonTypes as $type) {
+ if ($element instanceof $type) {
+ if (stristr($type, 'button')) {
+ $element->content = $element->getLabel();
+
+ return $element->getValue();
+ }
+ return $element->getLabel();
+ }
+ }
+
+ return $element->getValue();
+ }
+
+ /**
+ * Render an element using a view helper
+ *
+ * Determine view helper from 'viewHelper' option, or, if none set, from
+ * the element type. Then call as
+ * helper($element->getName(), $element->getValue(), $element->getAttribs())
+ *
+ * @param string $content
+ * @return string
+ * @throws Zend_Form_Decorator_Exception if element or view are not registered
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+
+ $view = $element->getView();
+ if (null === $view) {
+ throw new Zend_Form_Decorator_Exception('ViewHelper decorator cannot render without a registered view object');
+ }
+
+ if (method_exists($element, 'getMultiOptions')) {
+ $element->getMultiOptions();
+ }
+
+ $helper = $this->getHelper();
+ $separator = $this->getSeparator();
+ $value = $this->getValue($element);
+ $attribs = $this->getElementAttribs();
+ $name = $element->getFullyQualifiedName();
+ $id = $element->getId();
+ $attribs['id'] = $id;
+
+ $helperObject = $view->getHelper($helper);
+ if (method_exists($helperObject, 'setTranslator')) {
+ $helperObject->setTranslator($element->getTranslator());
+ }
+
+ // Check list separator
+ if (isset($attribs['listsep'])
+ && in_array($helper, array('formMultiCheckbox', 'formRadio', 'formSelect'))
+ ) {
+ $listsep = $attribs['listsep'];
+ unset($attribs['listsep']);
+
+ $elementContent = $view->$helper($name, $value, $attribs, $element->options, $listsep);
+ } else {
+ $elementContent = $view->$helper($name, $value, $attribs, $element->options);
+ }
+
+ switch ($this->getPlacement()) {
+ case self::APPEND:
+ return $content . $separator . $elementContent;
+ case self::PREPEND:
+ return $elementContent . $separator . $content;
+ default:
+ return $elementContent;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Decorator/ViewScript.php b/library/vendor/Zend/Form/Decorator/ViewScript.php
new file mode 100644
index 0000000..0a1a5a1
--- /dev/null
+++ b/library/vendor/Zend/Form/Decorator/ViewScript.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Decorator_Abstract */
+
+/**
+ * Zend_Form_Decorator_ViewScript
+ *
+ * Render a view script as a decorator
+ *
+ * Accepts the options:
+ * - separator: separator to use between view script content and provided content (defaults to PHP_EOL)
+ * - placement: whether to append or prepend view script content to provided content (defaults to prepend)
+ * - viewScript: view script to use
+ * - viewModule: module that view script is in (optional)
+ *
+ * The view script is rendered as a partial; the element being decorated is
+ * passed in as the 'element' variable:
+ * <code>
+ * // in view script:
+ * echo $this->element->getLabel();
+ * </code>
+ *
+ * Any options other than separator, placement, viewScript, and viewModule are passed to
+ * the partial as local variables.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Decorator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Decorator_ViewScript extends Zend_Form_Decorator_Abstract
+{
+ /**
+ * Default placement: append
+ * @var string
+ */
+ protected $_placement = 'APPEND';
+
+ /**
+ * View script to render
+ * @var string
+ */
+ protected $_viewScript;
+
+ /**
+ * View script module
+ * @var string
+ */
+ protected $_viewModule;
+
+ /**
+ * Set view script
+ *
+ * @param string $script
+ * @return Zend_Form_Decorator_ViewScript
+ */
+ public function setViewScript($script)
+ {
+ $this->_viewScript = (string) $script;
+ return $this;
+ }
+
+ /**
+ * Get view script
+ *
+ * @return string|null
+ */
+ public function getViewScript()
+ {
+ if (null === $this->_viewScript) {
+ if (null !== ($element = $this->getElement())) {
+ if (null !== ($viewScript = $element->getAttrib('viewScript'))) {
+ $this->setViewScript($viewScript);
+ return $viewScript;
+ }
+ }
+
+ if (null !== ($viewScript = $this->getOption('viewScript'))) {
+ $this->setViewScript($viewScript)
+ ->removeOption('viewScript');
+ }
+ }
+
+ return $this->_viewScript;
+ }
+
+ /**
+ * Set view script module
+ *
+ * @param string $module
+ * @return Zend_Form_Decorator_ViewScript
+ */
+ public function setViewModule($viewModule)
+ {
+ $this->_viewModule = (string) $viewModule;
+ return $this;
+ }
+
+ /**
+ * Get view script module
+ *
+ * @return string|null
+ */
+ public function getViewModule()
+ {
+ if (null === $this->_viewModule) {
+ if (null !== ($element = $this->getElement())) {
+ if (null !== ($viewModule = $element->getAttrib('viewModule'))) {
+ $this->setViewModule($viewModule);
+ return $viewModule;
+ }
+ }
+
+ if (null !== ($viewModule = $this->getOption('viewModule'))) {
+ $this->setViewModule($viewModule)
+ ->removeOption('viewModule');
+ }
+ }
+
+ return $this->_viewModule;
+ }
+
+ /**
+ * Render a view script
+ *
+ * @param string $content
+ * @return string
+ */
+ public function render($content)
+ {
+ $element = $this->getElement();
+ $view = $element->getView();
+ if (null === $view) {
+ return $content;
+ }
+
+ $viewScript = $this->getViewScript();
+ if (empty($viewScript)) {
+ throw new Zend_Form_Exception('No view script registered with ViewScript decorator');
+ }
+
+ $separator = $this->getSeparator();
+ $placement = $this->getPlacement();
+
+ $vars = $this->getOptions();
+ $vars['element'] = $element;
+ $vars['content'] = $content;
+ $vars['decorator'] = $this;
+
+ $viewModule = $this->getViewModule();
+ if (empty($viewModule)) {
+ $renderedContent = $view->partial($viewScript, $vars);
+ } else {
+ $renderedContent = $view->partial($viewScript, $viewModule, $vars);
+ }
+
+ // Get placement again to see if it has changed
+ $placement = $this->getPlacement();
+
+ switch ($placement) {
+ case self::PREPEND:
+ return $renderedContent . $separator . $content;
+ case self::APPEND:
+ return $content . $separator . $renderedContent;
+ default:
+ return $renderedContent;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/DisplayGroup.php b/library/vendor/Zend/Form/DisplayGroup.php
new file mode 100644
index 0000000..73a4cde
--- /dev/null
+++ b/library/vendor/Zend/Form/DisplayGroup.php
@@ -0,0 +1,1172 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Form_DisplayGroup
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_DisplayGroup implements Iterator,Countable
+{
+ /**
+ * Group attributes
+ * @var array
+ */
+ protected $_attribs = array();
+
+ /**
+ * Display group decorators
+ * @var array
+ */
+ protected $_decorators = array();
+
+ /**
+ * Description
+ * @var string
+ */
+ protected $_description;
+
+ /**
+ * Should we disable loading the default decorators?
+ * @var bool
+ */
+ protected $_disableLoadDefaultDecorators = false;
+
+ /**
+ * Element order
+ * @var array
+ */
+ protected $_elementOrder = array();
+
+ /**
+ * Elements
+ * @var array
+ */
+ protected $_elements = array();
+
+ /**
+ * Form object to which the display group is currently registered
+ *
+ * @var Zend_Form
+ */
+ protected $_form;
+
+ /**
+ * Whether or not a new element has been added to the group
+ * @var bool
+ */
+ protected $_groupUpdated = false;
+
+ /**
+ * Plugin loader for decorators
+ * @var Zend_Loader_PluginLoader
+ */
+ protected $_loader;
+
+ /**
+ * Group name
+ * @var string
+ */
+ protected $_name;
+
+ /**
+ * Group order
+ * @var int
+ */
+ protected $_order;
+
+ /**
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Is translation disabled?
+ * @var bool
+ */
+ protected $_translatorDisabled = false;
+
+ /**
+ * @var Zend_View_Interface
+ */
+ protected $_view;
+
+ /**
+ * Constructor
+ *
+ * @param string $name
+ * @param Zend_Loader_PluginLoader $loader
+ * @param array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($name, Zend_Loader_PluginLoader $loader, $options = null)
+ {
+ $this->setName($name);
+
+ $this->setPluginLoader($loader);
+
+ if (is_array($options)) {
+ $this->setOptions($options);
+ } elseif ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ }
+
+ // Extensions...
+ $this->init();
+
+ $this->loadDefaultDecorators();
+ }
+
+ /**
+ * Initialize object; used by extending classes
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set options
+ *
+ * @param array $options
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setOptions(array $options)
+ {
+ $forbidden = array(
+ 'Options', 'Config', 'PluginLoader', 'View',
+ 'Translator', 'Attrib'
+ );
+ foreach ($options as $key => $value) {
+ $normalized = ucfirst($key);
+
+ if (in_array($normalized, $forbidden)) {
+ continue;
+ }
+
+ $method = 'set' . $normalized;
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ } else {
+ $this->setAttrib($key, $value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set options from config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ return $this->setOptions($config->toArray());
+ }
+
+ /**
+ * Set group attribute
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setAttrib($key, $value)
+ {
+ $key = (string) $key;
+ $this->_attribs[$key] = $value;
+ return $this;
+ }
+
+ /**
+ * Add multiple form attributes at once
+ *
+ * @param array $attribs
+ * @return Zend_Form_DisplayGroup
+ */
+ public function addAttribs(array $attribs)
+ {
+ foreach ($attribs as $key => $value) {
+ $this->setAttrib($key, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * Set multiple form attributes at once
+ *
+ * Overwrites any previously set attributes.
+ *
+ * @param array $attribs
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setAttribs(array $attribs)
+ {
+ $this->clearAttribs();
+ return $this->addAttribs($attribs);
+ }
+
+ /**
+ * Retrieve a single form attribute
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function getAttrib($key)
+ {
+ $key = (string) $key;
+ if (!isset($this->_attribs[$key])) {
+ return null;
+ }
+
+ return $this->_attribs[$key];
+ }
+
+ /**
+ * Retrieve all form attributes/metadata
+ *
+ * @return array
+ */
+ public function getAttribs()
+ {
+ return $this->_attribs;
+ }
+
+ /**
+ * Remove attribute
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function removeAttrib($key)
+ {
+ if (array_key_exists($key, $this->_attribs)) {
+ unset($this->_attribs[$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all form attributes
+ *
+ * @return Zend_Form
+ */
+ public function clearAttribs()
+ {
+ $this->_attribs = array();
+ return $this;
+ }
+
+ /**
+ * Set form object to which the display group is attached
+ *
+ * @param Zend_Form $form
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setForm(Zend_Form $form)
+ {
+ $this->_form = $form;
+
+ // Ensure any elements attached prior to setting the form are now
+ // removed from iteration by the form
+ foreach ($this->getElements() as $element) {
+ $form->removeFromIteration($element->getName());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get form object to which the group is attached
+ *
+ * @return Zend_Form|null
+ */
+ public function getForm()
+ {
+ return $this->_form;
+ }
+
+ /**
+ * Filter a name to only allow valid variable characters
+ *
+ * @param string $value
+ * @return string
+ */
+ public function filterName($value)
+ {
+ return preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', (string) $value);
+ }
+
+ /**
+ * Set group name
+ *
+ * @param string $name
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setName($name)
+ {
+ $name = $this->filtername($name);
+ if (('0' !== $name) && empty($name)) {
+ throw new Zend_Form_Exception('Invalid name provided; must contain only valid variable characters and be non-empty');
+ }
+
+ $this->_name = $name;
+ return $this;
+ }
+
+ /**
+ * Retrieve group name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Get fully qualified name
+ *
+ * Places name as subitem of array and/or appends brackets.
+ *
+ * @return string
+ */
+ public function getFullyQualifiedName()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * Get element id
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (isset($this->id)) {
+ return $this->id;
+ }
+
+ $id = $this->getFullyQualifiedName();
+
+ // Bail early if no array notation detected
+ if (!strstr($id, '[')) {
+ return $id;
+ }
+
+ // Strip array notation
+ if ('[]' == substr($id, -2)) {
+ $id = substr($id, 0, strlen($id) - 2);
+ }
+ $id = str_replace('][', '-', $id);
+ $id = str_replace(array(']', '['), '-', $id);
+ $id = trim($id, '-');
+
+ return $id;
+ }
+
+ /**
+ * Set group legend
+ *
+ * @param string $legend
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setLegend($legend)
+ {
+ return $this->setAttrib('legend', (string) $legend);
+ }
+
+ /**
+ * Retrieve group legend
+ *
+ * @return string
+ */
+ public function getLegend()
+ {
+ return $this->getAttrib('legend');
+ }
+
+ /**
+ * Set description
+ *
+ * @param string $value
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setDescription($value)
+ {
+ $this->_description = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Get description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Set group order
+ *
+ * @param int $order
+ * @return Zend_Form_Element
+ */
+ public function setOrder($order)
+ {
+ $this->_order = (int) $order;
+ return $this;
+ }
+
+ /**
+ * Retrieve group order
+ *
+ * @return int
+ */
+ public function getOrder()
+ {
+ return $this->_order;
+ }
+
+ // Elements
+
+ /**
+ * Add element to stack
+ *
+ * @param Zend_Form_Element $element
+ * @return Zend_Form_DisplayGroup
+ */
+ public function addElement(Zend_Form_Element $element)
+ {
+ $this->_elements[$element->getName()] = $element;
+ $this->_groupUpdated = true;
+
+ // Display group will now handle display of element
+ if (null !== ($form = $this->getForm())) {
+ $form->removeFromIteration($element->getName());
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add multiple elements at once
+ *
+ * @param array $elements
+ * @return Zend_Form_DisplayGroup
+ * @throws Zend_Form_Exception if any element is not a Zend_Form_Element
+ */
+ public function addElements(array $elements)
+ {
+ foreach ($elements as $element) {
+ if (!$element instanceof Zend_Form_Element) {
+ throw new Zend_Form_Exception('elements passed via array to addElements() must be Zend_Form_Elements only');
+ }
+ $this->addElement($element);
+ }
+ return $this;
+ }
+
+ /**
+ * Set multiple elements at once (overwrites)
+ *
+ * @param array $elements
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setElements(array $elements)
+ {
+ $this->clearElements();
+ return $this->addElements($elements);
+ }
+
+ /**
+ * Retrieve element
+ *
+ * @param string $name
+ * @return Zend_Form_Element|null
+ */
+ public function getElement($name)
+ {
+ $name = (string) $name;
+ if (isset($this->_elements[$name])) {
+ return $this->_elements[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve elements
+ * @return array
+ */
+ public function getElements()
+ {
+ return $this->_elements;
+ }
+
+ /**
+ * Remove a single element
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function removeElement($name)
+ {
+ $name = (string) $name;
+ if (array_key_exists($name, $this->_elements)) {
+ unset($this->_elements[$name]);
+ $this->_groupUpdated = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Remove all elements
+ *
+ * @return Zend_Form_DisplayGroup
+ */
+ public function clearElements()
+ {
+ $this->_elements = array();
+ $this->_groupUpdated = true;
+ return $this;
+ }
+
+ // Plugin loader (for decorators)
+
+ /**
+ * Set plugin loader
+ *
+ * @param Zend_Loader_PluginLoader $loader
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader $loader)
+ {
+ $this->_loader = $loader;
+ return $this;
+ }
+
+ /**
+ * Retrieve plugin loader
+ *
+ * @return Zend_Loader_PluginLoader
+ */
+ public function getPluginLoader()
+ {
+ return $this->_loader;
+ }
+
+ /**
+ * Add a prefix path for the plugin loader
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Form_DisplayGroup
+ */
+ public function addPrefixPath($prefix, $path)
+ {
+ $this->getPluginLoader()->addPrefixPath($prefix, $path);
+ return $this;
+ }
+
+ /**
+ * Add several prefix paths at once
+ *
+ * @param array $spec
+ * @return Zend_Form_DisplayGroup
+ */
+ public function addPrefixPaths(array $spec)
+ {
+ if (isset($spec['prefix']) && isset($spec['path'])) {
+ return $this->addPrefixPath($spec['prefix'], $spec['path']);
+ }
+ foreach ($spec as $prefix => $paths) {
+ if (is_numeric($prefix) && is_array($paths)) {
+ $prefix = null;
+ if (isset($paths['prefix']) && isset($paths['path'])) {
+ $this->addPrefixPath($paths['prefix'], $paths['path']);
+ }
+ } elseif (!is_numeric($prefix)) {
+ if (is_string($paths)) {
+ $this->addPrefixPath($prefix, $paths);
+ } elseif (is_array($paths)) {
+ foreach ($paths as $path) {
+ $this->addPrefixPath($prefix, $path);
+ }
+ }
+ }
+ }
+ return $this;
+ }
+
+ // Decorators
+
+ /**
+ * Set flag to disable loading default decorators
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setDisableLoadDefaultDecorators($flag)
+ {
+ $this->_disableLoadDefaultDecorators = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Should we load the default decorators?
+ *
+ * @return bool
+ */
+ public function loadDefaultDecoratorsIsDisabled()
+ {
+ return $this->_disableLoadDefaultDecorators;
+ }
+
+ /**
+ * Load default decorators
+ *
+ * @return Zend_Form_DisplayGroup
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ $this->addDecorator('FormElements')
+ ->addDecorator('HtmlTag', array('tag' => 'dl'))
+ ->addDecorator('Fieldset')
+ ->addDecorator('DtDdWrapper');
+ }
+ return $this;
+ }
+
+ /**
+ * Instantiate a decorator based on class name or class name fragment
+ *
+ * @param string $name
+ * @param null|array $options
+ * @return Zend_Form_Decorator_Interface
+ */
+ protected function _getDecorator($name, $options = null)
+ {
+ $class = $this->getPluginLoader()->load($name);
+ if (null === $options) {
+ $decorator = new $class;
+ } else {
+ $decorator = new $class($options);
+ }
+
+ return $decorator;
+ }
+
+ /**
+ * Add a decorator for rendering the group
+ *
+ * @param string|Zend_Form_Decorator_Interface $decorator
+ * @param array|Zend_Config $options Options with which to initialize decorator
+ * @return Zend_Form_DisplayGroup
+ */
+ public function addDecorator($decorator, $options = null)
+ {
+ if ($decorator instanceof Zend_Form_Decorator_Interface) {
+ $name = get_class($decorator);
+ } elseif (is_string($decorator)) {
+ $name = $decorator;
+ $decorator = array(
+ 'decorator' => $name,
+ 'options' => $options,
+ );
+ } elseif (is_array($decorator)) {
+ foreach ($decorator as $name => $spec) {
+ break;
+ }
+ if (is_numeric($name)) {
+ throw new Zend_Form_Exception('Invalid alias provided to addDecorator; must be alphanumeric string');
+ }
+ if (is_string($spec)) {
+ $decorator = array(
+ 'decorator' => $spec,
+ 'options' => $options,
+ );
+ } elseif ($spec instanceof Zend_Form_Decorator_Interface) {
+ $decorator = $spec;
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface');
+ }
+
+ $this->_decorators[$name] = $decorator;
+
+ return $this;
+ }
+
+ /**
+ * Add many decorators at once
+ *
+ * @param array $decorators
+ * @return Zend_Form_DisplayGroup
+ */
+ public function addDecorators(array $decorators)
+ {
+ foreach ($decorators as $decoratorName => $decoratorInfo) {
+ if (is_string($decoratorInfo) ||
+ $decoratorInfo instanceof Zend_Form_Decorator_Interface) {
+ if (!is_numeric($decoratorName)) {
+ $this->addDecorator(array($decoratorName => $decoratorInfo));
+ } else {
+ $this->addDecorator($decoratorInfo);
+ }
+ } elseif (is_array($decoratorInfo)) {
+ $argc = count($decoratorInfo);
+ $options = array();
+ if (isset($decoratorInfo['decorator'])) {
+ $decorator = $decoratorInfo['decorator'];
+ if (isset($decoratorInfo['options'])) {
+ $options = $decoratorInfo['options'];
+ }
+ $this->addDecorator($decorator, $options);
+ } else {
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $decorator = array_shift($decoratorInfo);
+ case (2 <= $argc):
+ $options = array_shift($decoratorInfo);
+ default:
+ $this->addDecorator($decorator, $options);
+ break;
+ }
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid decorator passed to addDecorators()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overwrite all decorators
+ *
+ * @param array $decorators
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setDecorators(array $decorators)
+ {
+ $this->clearDecorators();
+ return $this->addDecorators($decorators);
+ }
+
+ /**
+ * Retrieve a registered decorator
+ *
+ * @param string $name
+ * @return false|Zend_Form_Decorator_Abstract
+ */
+ public function getDecorator($name)
+ {
+ if (!isset($this->_decorators[$name])) {
+ $len = strlen($name);
+ foreach ($this->_decorators as $localName => $decorator) {
+ if ($len > strlen($localName)) {
+ continue;
+ }
+
+ if (0 === substr_compare($localName, $name, -$len, $len, true)) {
+ if (is_array($decorator)) {
+ return $this->_loadDecorator($decorator, $localName);
+ }
+ return $decorator;
+ }
+ }
+ return false;
+ }
+
+ if (is_array($this->_decorators[$name])) {
+ return $this->_loadDecorator($this->_decorators[$name], $name);
+ }
+
+ return $this->_decorators[$name];
+ }
+
+ /**
+ * Retrieve all decorators
+ *
+ * @return array
+ */
+ public function getDecorators()
+ {
+ foreach ($this->_decorators as $key => $value) {
+ if (is_array($value)) {
+ $this->_loadDecorator($value, $key);
+ }
+ }
+ return $this->_decorators;
+ }
+
+ /**
+ * Remove a single decorator
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function removeDecorator($name)
+ {
+ $decorator = $this->getDecorator($name);
+ if ($decorator) {
+ if (array_key_exists($name, $this->_decorators)) {
+ unset($this->_decorators[$name]);
+ } else {
+ $class = get_class($decorator);
+ unset($this->_decorators[$class]);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all decorators
+ *
+ * @return Zend_Form_DisplayGroup
+ */
+ public function clearDecorators()
+ {
+ $this->_decorators = array();
+ return $this;
+ }
+
+ /**
+ * Set view
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setView(Zend_View_Interface $view = null)
+ {
+ $this->_view = $view;
+ return $this;
+ }
+
+ /**
+ * Retrieve view
+ *
+ * @return Zend_View_Interface
+ */
+ public function getView()
+ {
+ if (null === $this->_view) {
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ $this->setView($viewRenderer->view);
+ }
+
+ return $this->_view;
+ }
+
+ /**
+ * Render display group
+ *
+ * @return string
+ */
+ public function render(Zend_View_Interface $view = null)
+ {
+ if (null !== $view) {
+ $this->setView($view);
+ }
+ $content = '';
+ foreach ($this->getDecorators() as $decorator) {
+ $decorator->setElement($this);
+ $content = $decorator->render($content);
+ }
+ return $content;
+ }
+
+ /**
+ * String representation of group
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ $return = $this->render();
+ return $return;
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ return '';
+ }
+ }
+
+ /**
+ * Set translator object
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setTranslator($translator = null)
+ {
+ if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ throw new Zend_Form_Exception('Invalid translator specified');
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve translator object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ if (null === $this->_translator) {
+ return Zend_Form::getDefaultTranslator();
+ }
+
+ return $this->_translator;
+ }
+
+ /**
+ * Does this display group have its own specific translator?
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool) $this->getTranslator();
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_Form_DisplayGroup
+ */
+ public function setDisableTranslator($flag)
+ {
+ $this->_translatorDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ return $this->_translatorDisabled;
+ }
+
+ /**
+ * Overloading: allow rendering specific decorators
+ *
+ * Call renderDecoratorName() to render a specific decorator.
+ *
+ * @param string $method
+ * @param array $args
+ * @return string
+ * @throws Zend_Form_Exception for invalid decorator or invalid method call
+ */
+ public function __call($method, $args)
+ {
+ if ('render' == substr($method, 0, 6)) {
+ $decoratorName = substr($method, 6);
+ if (false !== ($decorator = $this->getDecorator($decoratorName))) {
+ $decorator->setElement($this);
+ $seed = '';
+ if (0 < count($args)) {
+ $seed = array_shift($args);
+ }
+ return $decorator->render($seed);
+ }
+
+ throw new Zend_Form_Exception(sprintf('Decorator by name %s does not exist', $decoratorName));
+ }
+
+ throw new Zend_Form_Exception(sprintf('Method %s does not exist', $method));
+ }
+
+ // Interfaces: Iterator, Countable
+
+ /**
+ * Current element
+ *
+ * @return Zend_Form_Element
+ */
+ public function current(): Zend_Form_Element
+ {
+ $this->_sort();
+ current($this->_elementOrder);
+ $key = key($this->_elementOrder);
+ return $this->getElement($key);
+ }
+
+ /**
+ * Current element
+ *
+ * @return string
+ */
+ public function key(): string
+ {
+ $this->_sort();
+ return key($this->_elementOrder);
+ }
+
+ /**
+ * Move pointer to next element
+ *
+ * @return void
+ */
+ public function next(): void
+ {
+ $this->_sort();
+ next($this->_elementOrder);
+ }
+
+ /**
+ * Move pointer to beginning of element loop
+ *
+ * @return void
+ */
+ public function rewind(): void
+ {
+ $this->_sort();
+ reset($this->_elementOrder);
+ }
+
+ /**
+ * Determine if current element/subform/display group is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ $this->_sort();
+ return (current($this->_elementOrder) !== false);
+ }
+
+ /**
+ * Count of elements/subforms that are iterable
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return count($this->_elements);
+ }
+
+ /**
+ * Sort items according to their order
+ *
+ * @return void
+ */
+ protected function _sort()
+ {
+ if ($this->_groupUpdated || !is_array($this->_elementOrder)) {
+ $elementOrder = array();
+ foreach ($this->getElements() as $key => $element) {
+ $elementOrder[$key] = $element->getOrder();
+ }
+
+ $items = array();
+ $index = 0;
+ foreach ($elementOrder as $key => $order) {
+ if (null === $order) {
+ while (array_search($index, $elementOrder, true)) {
+ ++$index;
+ }
+ $items[$index] = $key;
+ ++$index;
+ } else {
+ $items[$order] = $key;
+ }
+ }
+
+ $items = array_flip($items);
+ asort($items);
+ $this->_elementOrder = $items;
+ $this->_groupUpdated = false;
+ }
+ }
+
+ /**
+ * Lazy-load a decorator
+ *
+ * @param array $decorator Decorator type and options
+ * @param mixed $name Decorator name or alias
+ * @return Zend_Form_Decorator_Interface
+ */
+ protected function _loadDecorator(array $decorator, $name)
+ {
+ $sameName = false;
+ if ($name == $decorator['decorator']) {
+ $sameName = true;
+ }
+
+ $instance = $this->_getDecorator($decorator['decorator'], $decorator['options']);
+ if ($sameName) {
+ $newName = get_class($instance);
+ $decoratorNames = array_keys($this->_decorators);
+ $order = array_flip($decoratorNames);
+ $order[$newName] = $order[$name];
+ $decoratorsExchange = array();
+ unset($order[$name]);
+ asort($order);
+ foreach ($order as $key => $index) {
+ if ($key == $newName) {
+ $decoratorsExchange[$key] = $instance;
+ continue;
+ }
+ $decoratorsExchange[$key] = $this->_decorators[$key];
+ }
+ $this->_decorators = $decoratorsExchange;
+ } else {
+ $this->_decorators[$name] = $instance;
+ }
+
+ return $instance;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element.php b/library/vendor/Zend/Form/Element.php
new file mode 100644
index 0000000..6e81ea6
--- /dev/null
+++ b/library/vendor/Zend/Form/Element.php
@@ -0,0 +1,2280 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** @see Zend_Filter */
+
+/** @see Zend_Form */
+
+/** @see Zend_Validate_Interface */
+
+/** @see Zend_Validate_Abstract */
+
+/**
+ * Zend_Form_Element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element implements Zend_Validate_Interface
+{
+ /**
+ * Element Constants
+ */
+ const DECORATOR = 'DECORATOR';
+ const FILTER = 'FILTER';
+ const VALIDATE = 'VALIDATE';
+
+ /**
+ * Default view helper to use
+ * @var string
+ */
+ public $helper = 'formText';
+
+ /**
+ * 'Allow empty' flag
+ * @var bool
+ */
+ protected $_allowEmpty = true;
+
+ /**
+ * Flag indicating whether or not to insert NotEmpty validator when element is required
+ * @var bool
+ */
+ protected $_autoInsertNotEmptyValidator = true;
+
+ /**
+ * Array to which element belongs
+ * @var string
+ */
+ protected $_belongsTo;
+
+ /**
+ * Element decorators
+ * @var array
+ */
+ protected $_decorators = array();
+
+ /**
+ * Element description
+ * @var string
+ */
+ protected $_description;
+
+ /**
+ * Should we disable loading the default decorators?
+ * @var bool
+ */
+ protected $_disableLoadDefaultDecorators = false;
+
+ /**
+ * Custom error messages
+ * @var array
+ */
+ protected $_errorMessages = array();
+
+ /**
+ * Validation errors
+ * @var array
+ */
+ protected $_errors = array();
+
+ /**
+ * Separator to use when concatenating aggregate error messages (for
+ * elements having array values)
+ * @var string
+ */
+ protected $_errorMessageSeparator = '; ';
+
+ /**
+ * Element filters
+ * @var array
+ */
+ protected $_filters = array();
+
+ /**
+ * Ignore flag (used when retrieving values at form level)
+ * @var bool
+ */
+ protected $_ignore = false;
+
+ /**
+ * Does the element represent an array?
+ * @var bool
+ */
+ protected $_isArray = false;
+
+ /**
+ * Is the error marked as in an invalid state?
+ * @var bool
+ */
+ protected $_isError = false;
+
+ /**
+ * Has the element been manually marked as invalid?
+ * @var bool
+ */
+ protected $_isErrorForced = false;
+
+ /**
+ * Element label
+ * @var string
+ */
+ protected $_label;
+
+ /**
+ * Plugin loaders for filter and validator chains
+ * @var array
+ */
+ protected $_loaders = array();
+
+ /**
+ * Formatted validation error messages
+ * @var array
+ */
+ protected $_messages = array();
+
+ /**
+ * Element name
+ * @var string
+ */
+ protected $_name;
+
+ /**
+ * Order of element
+ * @var int
+ */
+ protected $_order;
+
+ /**
+ * Required flag
+ * @var bool
+ */
+ protected $_required = false;
+
+ /**
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Is translation disabled?
+ * @var bool
+ */
+ protected $_translatorDisabled = false;
+
+ /**
+ * Element type
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Array of initialized validators
+ * @var array Validators
+ */
+ protected $_validators = array();
+
+ /**
+ * Array of un-initialized validators
+ * @var array
+ */
+ protected $_validatorRules = array();
+
+ /**
+ * Element value
+ * @var mixed
+ */
+ protected $_value;
+
+ /**
+ * @var Zend_View_Interface
+ */
+ protected $_view;
+
+ /**
+ * Is a specific decorator being rendered via the magic renderDecorator()?
+ *
+ * This is to allow execution of logic inside the render() methods of child
+ * elements during the magic call while skipping the parent render() method.
+ *
+ * @var bool
+ */
+ protected $_isPartialRendering = false;
+
+ /**
+ * Use one error message for array elements with concatenated values
+ *
+ * @var bool
+ */
+ protected $_concatJustValuesInErrorMessage = false;
+
+ /**
+ * Constructor
+ *
+ * $spec may be:
+ * - string: name of element
+ * - array: options with which to configure element
+ * - Zend_Config: Zend_Config with options for configuring element
+ *
+ * @param string|array|Zend_Config $spec
+ * @param array|Zend_Config $options
+ * @return void
+ * @throws Zend_Form_Exception if no element name after initialization
+ */
+ public function __construct($spec, $options = null)
+ {
+ if (is_string($spec)) {
+ $this->setName($spec);
+ } elseif (is_array($spec)) {
+ $this->setOptions($spec);
+ } elseif ($spec instanceof Zend_Config) {
+ $this->setConfig($spec);
+ }
+
+ if (is_string($spec) && is_array($options)) {
+ $this->setOptions($options);
+ } elseif (is_string($spec) && ($options instanceof Zend_Config)) {
+ $this->setConfig($options);
+ }
+
+ if (null === $this->getName()) {
+ throw new Zend_Form_Exception('Zend_Form_Element requires each element to have a name');
+ }
+
+ /**
+ * Extensions
+ */
+ $this->init();
+
+ /**
+ * Register ViewHelper decorator by default
+ */
+ $this->loadDefaultDecorators();
+ }
+
+ /**
+ * Initialize object; used by extending classes
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Set flag to disable loading default decorators
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setDisableLoadDefaultDecorators($flag)
+ {
+ $this->_disableLoadDefaultDecorators = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Should we load the default decorators?
+ *
+ * @return bool
+ */
+ public function loadDefaultDecoratorsIsDisabled()
+ {
+ return $this->_disableLoadDefaultDecorators;
+ }
+
+ /**
+ * Load default decorators
+ *
+ * @return Zend_Form_Element
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ $this->addDecorator('ViewHelper')
+ ->addDecorator('Errors')
+ ->addDecorator('Description', array('tag' => 'p', 'class' => 'description'))
+ ->addDecorator('HtmlTag', array(
+ 'tag' => 'dd',
+ 'id' => array('callback' => array(get_class($this), 'resolveElementId'))
+ ))
+ ->addDecorator('Label', array('tag' => 'dt'));
+ }
+ return $this;
+ }
+
+ /**
+ * Used to resolve and return an element ID
+ *
+ * Passed to the HtmlTag decorator as a callback in order to provide an ID.
+ *
+ * @param Zend_Form_Decorator_Interface $decorator
+ * @return string
+ */
+ public static function resolveElementId(Zend_Form_Decorator_Interface $decorator)
+ {
+ return $decorator->getElement()->getId() . '-element';
+ }
+
+ /**
+ * Set object state from options array
+ *
+ * @param array $options
+ * @return Zend_Form_Element
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['prefixPath'])) {
+ $this->addPrefixPaths($options['prefixPath']);
+ unset($options['prefixPath']);
+ }
+
+ if (isset($options['disableTranslator'])) {
+ $this->setDisableTranslator($options['disableTranslator']);
+ unset($options['disableTranslator']);
+ }
+
+ unset($options['options']);
+ unset($options['config']);
+
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+
+ if (in_array($method, array('setTranslator', 'setPluginLoader', 'setView'))) {
+ if (!is_object($value)) {
+ continue;
+ }
+ }
+
+ if (method_exists($this, $method)) {
+ // Setter exists; use it
+ $this->$method($value);
+ } else {
+ // Assume it's metadata
+ $this->setAttrib($key, $value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set object state from Zend_Config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Form_Element
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ return $this->setOptions($config->toArray());
+ }
+
+
+ // Localization:
+
+ /**
+ * Set translator object for localization
+ *
+ * @param Zend_Translate|null $translator
+ * @return Zend_Form_Element
+ */
+ public function setTranslator($translator = null)
+ {
+ if (null === $translator) {
+ $this->_translator = null;
+ } elseif ($translator instanceof Zend_Translate_Adapter) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ throw new Zend_Form_Exception('Invalid translator specified');
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve localization translator object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ if (null === $this->_translator) {
+ return Zend_Form::getDefaultTranslator();
+ }
+ return $this->_translator;
+ }
+
+ /**
+ * Does this element have its own specific translator?
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool)$this->_translator;
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setDisableTranslator($flag)
+ {
+ $this->_translatorDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ return $this->_translatorDisabled;
+ }
+
+ // Metadata
+
+ /**
+ * Filter a name to only allow valid variable characters
+ *
+ * @param string $value
+ * @param bool $allowBrackets
+ * @return string
+ */
+ public function filterName($value, $allowBrackets = false)
+ {
+ $charset = '^a-zA-Z0-9_\x7f-\xff';
+ if ($allowBrackets) {
+ $charset .= '\[\]';
+ }
+ return preg_replace('/[' . $charset . ']/', '', (string) $value);
+ }
+
+ /**
+ * Set element name
+ *
+ * @param string $name
+ * @return Zend_Form_Element
+ */
+ public function setName($name)
+ {
+ $name = $this->filterName($name);
+ if ('' === $name) {
+ throw new Zend_Form_Exception('Invalid name provided; must contain only valid variable characters and be non-empty');
+ }
+
+ $this->_name = $name;
+ return $this;
+ }
+
+ /**
+ * Return element name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Get fully qualified name
+ *
+ * Places name as subitem of array and/or appends brackets.
+ *
+ * @return string
+ */
+ public function getFullyQualifiedName()
+ {
+ $name = $this->getName();
+
+ if (null !== ($belongsTo = $this->getBelongsTo())) {
+ $name = $belongsTo . '[' . $name . ']';
+ }
+
+ if ($this->isArray()) {
+ $name .= '[]';
+ }
+
+ return $name;
+ }
+
+ /**
+ * Get element id
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ if (isset($this->id)) {
+ return $this->id;
+ }
+
+ $id = $this->getFullyQualifiedName();
+
+ // Bail early if no array notation detected
+ if (!strstr($id, '[')) {
+ return $id;
+ }
+
+ // Strip array notation
+ if ('[]' == substr($id, -2)) {
+ $id = substr($id, 0, strlen($id) - 2);
+ }
+ $id = str_replace('][', '-', $id);
+ $id = str_replace(array(']', '['), '-', $id);
+ $id = trim($id, '-');
+
+ return $id;
+ }
+
+ /**
+ * Set element value
+ *
+ * @param mixed $value
+ * @return Zend_Form_Element
+ */
+ public function setValue($value)
+ {
+ $this->_value = $value;
+ return $this;
+ }
+
+ /**
+ * Filter a value
+ *
+ * @param string $value
+ * @param string $key
+ * @return void
+ */
+ protected function _filterValue(&$value, &$key)
+ {
+ foreach ($this->getFilters() as $filter) {
+ $value = $filter->filter($value);
+ }
+ }
+
+ /**
+ * Retrieve filtered element value
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ $valueFiltered = $this->_value;
+
+ if ($this->isArray() && is_array($valueFiltered)) {
+ array_walk_recursive($valueFiltered, function (&$val, $key) {
+ $this->_filterValue($val, $key);
+ });
+ } else {
+ $this->_filterValue($valueFiltered, $valueFiltered);
+ }
+
+ return $valueFiltered;
+ }
+
+ /**
+ * Retrieve unfiltered element value
+ *
+ * @return mixed
+ */
+ public function getUnfilteredValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Set element label
+ *
+ * @param string $label
+ * @return Zend_Form_Element
+ */
+ public function setLabel($label)
+ {
+ $this->_label = (string) $label;
+ return $this;
+ }
+
+ /**
+ * Retrieve element label
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ $translator = $this->getTranslator();
+ if (null !== $translator) {
+ return $translator->translate($this->_label);
+ }
+
+ return $this->_label;
+ }
+
+ /**
+ * Set element order
+ *
+ * @param int $order
+ * @return Zend_Form_Element
+ */
+ public function setOrder($order)
+ {
+ $this->_order = (int) $order;
+ return $this;
+ }
+
+ /**
+ * Retrieve element order
+ *
+ * @return int
+ */
+ public function getOrder()
+ {
+ return $this->_order;
+ }
+
+ /**
+ * Set required flag
+ *
+ * @param bool $flag Default value is true
+ * @return Zend_Form_Element
+ */
+ public function setRequired($flag = true)
+ {
+ $this->_required = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is the element required?
+ *
+ * @return bool
+ */
+ public function isRequired()
+ {
+ return $this->_required;
+ }
+
+ /**
+ * Set flag indicating whether a NotEmpty validator should be inserted when element is required
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setAutoInsertNotEmptyValidator($flag)
+ {
+ $this->_autoInsertNotEmptyValidator = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get flag indicating whether a NotEmpty validator should be inserted when element is required
+ *
+ * @return bool
+ */
+ public function autoInsertNotEmptyValidator()
+ {
+ return $this->_autoInsertNotEmptyValidator;
+ }
+
+ /**
+ * Set element description
+ *
+ * @param string $description
+ * @return Zend_Form_Element
+ */
+ public function setDescription($description)
+ {
+ $this->_description = (string) $description;
+ return $this;
+ }
+
+ /**
+ * Retrieve element description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Set 'allow empty' flag
+ *
+ * When the allow empty flag is enabled and the required flag is false, the
+ * element will validate with empty values.
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setAllowEmpty($flag)
+ {
+ $this->_allowEmpty = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get 'allow empty' flag
+ *
+ * @return bool
+ */
+ public function getAllowEmpty()
+ {
+ return $this->_allowEmpty;
+ }
+
+ /**
+ * Set ignore flag (used when retrieving values at form level)
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setIgnore($flag)
+ {
+ $this->_ignore = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get ignore flag (used when retrieving values at form level)
+ *
+ * @return bool
+ */
+ public function getIgnore()
+ {
+ return $this->_ignore;
+ }
+
+ /**
+ * Set flag indicating if element represents an array
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element
+ */
+ public function setIsArray($flag)
+ {
+ $this->_isArray = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is the element representing an array?
+ *
+ * @return bool
+ */
+ public function isArray()
+ {
+ return $this->_isArray;
+ }
+
+ /**
+ * Set array to which element belongs
+ *
+ * @param string $array
+ * @return Zend_Form_Element
+ */
+ public function setBelongsTo($array)
+ {
+ $array = $this->filterName($array, true);
+ if (!empty($array)) {
+ $this->_belongsTo = $array;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return array name to which element belongs
+ *
+ * @return string
+ */
+ public function getBelongsTo()
+ {
+ return $this->_belongsTo;
+ }
+
+ /**
+ * Return element type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ if (null === $this->_type) {
+ $this->_type = get_class($this);
+ }
+
+ return $this->_type;
+ }
+
+ /**
+ * Set element attribute
+ *
+ * @param string $name
+ * @param mixed $value
+ * @return Zend_Form_Element
+ * @throws Zend_Form_Exception for invalid $name values
+ */
+ public function setAttrib($name, $value)
+ {
+ $name = (string) $name;
+ if ('_' == $name[0]) {
+ throw new Zend_Form_Exception(sprintf('Invalid attribute "%s"; must not contain a leading underscore', $name));
+ }
+
+ if (null === $value) {
+ unset($this->$name);
+ } else {
+ $this->$name = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set multiple attributes at once
+ *
+ * @param array $attribs
+ * @return Zend_Form_Element
+ */
+ public function setAttribs(array $attribs)
+ {
+ foreach ($attribs as $key => $value) {
+ $this->setAttrib($key, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve element attribute
+ *
+ * @param string $name
+ * @return string
+ */
+ public function getAttrib($name)
+ {
+ $name = (string) $name;
+ if (isset($this->$name)) {
+ return $this->$name;
+ }
+
+ return null;
+ }
+
+ /**
+ * Return all attributes
+ *
+ * @return array
+ */
+ public function getAttribs()
+ {
+ $attribs = get_object_vars($this);
+ unset($attribs['helper']);
+ foreach ($attribs as $key => $value) {
+ if ('_' == substr($key, 0, 1)) {
+ unset($attribs[$key]);
+ }
+ }
+
+ return $attribs;
+ }
+
+ /**
+ * Use one error message for array elements with concatenated values
+ *
+ * @param boolean $concatJustValuesInErrorMessage
+ * @return Zend_Form_Element
+ */
+ public function setConcatJustValuesInErrorMessage($concatJustValuesInErrorMessage)
+ {
+ $this->_concatJustValuesInErrorMessage = $concatJustValuesInErrorMessage;
+ return $this;
+ }
+
+ /**
+ * Use one error message for array elements with concatenated values
+ *
+ * @return boolean
+ */
+ public function getConcatJustValuesInErrorMessage()
+ {
+ return $this->_concatJustValuesInErrorMessage;
+ }
+
+ /**
+ * Overloading: retrieve object property
+ *
+ * Prevents access to properties beginning with '_'.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if ('_' == $key[0]) {
+ throw new Zend_Form_Exception(sprintf('Cannot retrieve value for protected/private property "%s"', $key));
+ }
+
+ if (!isset($this->$key)) {
+ return null;
+ }
+
+ return $this->$key;
+ }
+
+ /**
+ * Overloading: set object property
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return voide
+ */
+ public function __set($key, $value)
+ {
+ $this->setAttrib($key, $value);
+ }
+
+ /**
+ * Overloading: allow rendering specific decorators
+ *
+ * Call renderDecoratorName() to render a specific decorator.
+ *
+ * @param string $method
+ * @param array $args
+ * @return string
+ * @throws Zend_Form_Exception for invalid decorator or invalid method call
+ */
+ public function __call($method, $args)
+ {
+ if ('render' == substr($method, 0, 6)) {
+ $this->_isPartialRendering = true;
+ $this->render();
+ $this->_isPartialRendering = false;
+ $decoratorName = substr($method, 6);
+ if (false !== ($decorator = $this->getDecorator($decoratorName))) {
+ $decorator->setElement($this);
+ $seed = '';
+ if (0 < count($args)) {
+ $seed = array_shift($args);
+ }
+ return $decorator->render($seed);
+ }
+
+ throw new Zend_Form_Element_Exception(sprintf('Decorator by name %s does not exist', $decoratorName));
+ }
+
+ throw new Zend_Form_Element_Exception(sprintf('Method %s does not exist', $method));
+ }
+
+ // Loaders
+
+ /**
+ * Set plugin loader to use for validator or filter chain
+ *
+ * @param Zend_Loader_PluginLoader_Interface $loader
+ * @param string $type 'decorator', 'filter', or 'validate'
+ * @return Zend_Form_Element
+ * @throws Zend_Form_Exception on invalid type
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::DECORATOR:
+ case self::FILTER:
+ case self::VALIDATE:
+ $this->_loaders[$type] = $loader;
+ return $this;
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Retrieve plugin loader for validator or filter chain
+ *
+ * Instantiates with default rules if none available for that type. Use
+ * 'decorator', 'filter', or 'validate' for $type.
+ *
+ * @param string $type
+ * @return Zend_Loader_PluginLoader
+ * @throws Zend_Loader_Exception on invalid type.
+ */
+ public function getPluginLoader($type)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::FILTER:
+ case self::VALIDATE:
+ $prefixSegment = ucfirst(strtolower($type));
+ $pathSegment = $prefixSegment;
+ case self::DECORATOR:
+ if (!isset($prefixSegment)) {
+ $prefixSegment = 'Form_Decorator';
+ $pathSegment = 'Form/Decorator';
+ }
+ if (!isset($this->_loaders[$type])) {
+ $this->_loaders[$type] = new Zend_Loader_PluginLoader(
+ array('Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/')
+ );
+ }
+ return $this->_loaders[$type];
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Add prefix path for plugin loader
+ *
+ * If no $type specified, assumes it is a base path for both filters and
+ * validators, and sets each according to the following rules:
+ * - decorators: $prefix = $prefix . '_Decorator'
+ * - filters: $prefix = $prefix . '_Filter'
+ * - validators: $prefix = $prefix . '_Validate'
+ *
+ * Otherwise, the path prefix is set on the appropriate plugin loader.
+ *
+ * @param string $prefix
+ * @param string $path
+ * @param string $type
+ * @return Zend_Form_Element
+ * @throws Zend_Form_Exception for invalid type
+ */
+ public function addPrefixPath($prefix, $path, $type = null)
+ {
+ $type = strtoupper($type);
+ switch ($type) {
+ case self::DECORATOR:
+ case self::FILTER:
+ case self::VALIDATE:
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($prefix, $path);
+ return $this;
+ case null:
+ $nsSeparator = (false !== strpos($prefix, '\\'))?'\\':'_';
+ $prefix = rtrim($prefix, $nsSeparator) . $nsSeparator;
+ $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+ foreach (array(self::DECORATOR, self::FILTER, self::VALIDATE) as $type) {
+ $cType = ucfirst(strtolower($type));
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($prefix . $cType, $path . $cType . DIRECTORY_SEPARATOR);
+ }
+ return $this;
+ default:
+ throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
+ }
+ }
+
+ /**
+ * Add many prefix paths at once
+ *
+ * @param array $spec
+ * @return Zend_Form_Element
+ */
+ public function addPrefixPaths(array $spec)
+ {
+ if (isset($spec['prefix']) && isset($spec['path'])) {
+ return $this->addPrefixPath($spec['prefix'], $spec['path']);
+ }
+ foreach ($spec as $type => $paths) {
+ if (is_numeric($type) && is_array($paths)) {
+ $type = null;
+ if (isset($paths['prefix']) && isset($paths['path'])) {
+ if (isset($paths['type'])) {
+ $type = $paths['type'];
+ }
+ $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
+ }
+ } elseif (!is_numeric($type)) {
+ if (!isset($paths['prefix']) || !isset($paths['path'])) {
+ foreach ($paths as $prefix => $spec) {
+ if (is_array($spec)) {
+ foreach ($spec as $path) {
+ if (!is_string($path)) {
+ continue;
+ }
+ $this->addPrefixPath($prefix, $path, $type);
+ }
+ } elseif (is_string($spec)) {
+ $this->addPrefixPath($prefix, $spec, $type);
+ }
+ }
+ } else {
+ $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
+ }
+ }
+ }
+ return $this;
+ }
+
+ // Validation
+
+ /**
+ * Add validator to validation chain
+ *
+ * Note: will overwrite existing validators if they are of the same class.
+ *
+ * @param string|Zend_Validate_Interface $validator
+ * @param bool $breakChainOnFailure
+ * @param array $options
+ * @return Zend_Form_Element
+ * @throws Zend_Form_Exception if invalid validator type
+ */
+ public function addValidator($validator, $breakChainOnFailure = false, $options = array())
+ {
+ if ($validator instanceof Zend_Validate_Interface) {
+ $name = get_class($validator);
+
+ if (!isset($validator->zfBreakChainOnFailure)) {
+ $validator->zfBreakChainOnFailure = $breakChainOnFailure;
+ }
+ } elseif (is_string($validator)) {
+ $name = $validator;
+ $validator = array(
+ 'validator' => $validator,
+ 'breakChainOnFailure' => $breakChainOnFailure,
+ 'options' => $options,
+ );
+ } else {
+ throw new Zend_Form_Exception('Invalid validator provided to addValidator; must be string or Zend_Validate_Interface');
+ }
+
+
+ $this->_validators[$name] = $validator;
+
+ return $this;
+ }
+
+ /**
+ * Add multiple validators
+ *
+ * @param array $validators
+ * @return Zend_Form_Element
+ */
+ public function addValidators(array $validators)
+ {
+ foreach ($validators as $validatorInfo) {
+ if (is_string($validatorInfo)) {
+ $this->addValidator($validatorInfo);
+ } elseif ($validatorInfo instanceof Zend_Validate_Interface) {
+ $this->addValidator($validatorInfo);
+ } elseif (is_array($validatorInfo)) {
+ $argc = count($validatorInfo);
+ $breakChainOnFailure = false;
+ $options = array();
+ if (isset($validatorInfo['validator'])) {
+ $validator = $validatorInfo['validator'];
+ if (isset($validatorInfo['breakChainOnFailure'])) {
+ $breakChainOnFailure = $validatorInfo['breakChainOnFailure'];
+ }
+ if (isset($validatorInfo['options'])) {
+ $options = $validatorInfo['options'];
+ }
+ $this->addValidator($validator, $breakChainOnFailure, $options);
+ } else {
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $validator = array_shift($validatorInfo);
+ case (2 <= $argc):
+ $breakChainOnFailure = array_shift($validatorInfo);
+ case (3 <= $argc):
+ $options = array_shift($validatorInfo);
+ default:
+ $this->addValidator($validator, $breakChainOnFailure, $options);
+ break;
+ }
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid validator passed to addValidators()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set multiple validators, overwriting previous validators
+ *
+ * @param array $validators
+ * @return Zend_Form_Element
+ */
+ public function setValidators(array $validators)
+ {
+ $this->clearValidators();
+ return $this->addValidators($validators);
+ }
+
+ /**
+ * Retrieve a single validator by name
+ *
+ * @param string $name
+ * @return Zend_Validate_Interface|false False if not found, validator otherwise
+ */
+ public function getValidator($name)
+ {
+ if (!isset($this->_validators[$name])) {
+ $len = strlen($name);
+ foreach ($this->_validators as $localName => $validator) {
+ if ($len > strlen($localName)) {
+ continue;
+ }
+ if (0 === substr_compare($localName, $name, -$len, $len, true)) {
+ if (is_array($validator)) {
+ return $this->_loadValidator($validator);
+ }
+ return $validator;
+ }
+ }
+ return false;
+ }
+
+ if (is_array($this->_validators[$name])) {
+ return $this->_loadValidator($this->_validators[$name]);
+ }
+
+ return $this->_validators[$name];
+ }
+
+ /**
+ * Retrieve all validators
+ *
+ * @return array
+ */
+ public function getValidators()
+ {
+ $validators = array();
+ foreach ($this->_validators as $key => $value) {
+ if ($value instanceof Zend_Validate_Interface) {
+ $validators[$key] = $value;
+ continue;
+ }
+ $validator = $this->_loadValidator($value);
+ $validators[get_class($validator)] = $validator;
+ }
+ return $validators;
+ }
+
+ /**
+ * Remove a single validator by name
+ *
+ * @param string $name
+ * @return bool
+ */
+ public function removeValidator($name)
+ {
+ if (isset($this->_validators[$name])) {
+ unset($this->_validators[$name]);
+ } else {
+ $len = strlen($name);
+ foreach (array_keys($this->_validators) as $validator) {
+ if ($len > strlen($validator)) {
+ continue;
+ }
+ if (0 === substr_compare($validator, $name, -$len, $len, true)) {
+ unset($this->_validators[$validator]);
+ break;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Clear all validators
+ *
+ * @return Zend_Form_Element
+ */
+ public function clearValidators()
+ {
+ $this->_validators = array();
+ return $this;
+ }
+
+ /**
+ * Validate element value
+ *
+ * If a translation adapter is registered, any error messages will be
+ * translated according to the current locale, using the given error code;
+ * if no matching translation is found, the original message will be
+ * utilized.
+ *
+ * Note: The *filtered* value is validated.
+ *
+ * @param mixed $value
+ * @param mixed $context
+ * @return boolean
+ */
+ public function isValid($value, $context = null)
+ {
+ $this->setValue($value);
+ $value = $this->getValue();
+
+ if ((('' === $value) || (null === $value))
+ && !$this->isRequired()
+ && $this->getAllowEmpty()
+ ) {
+ return true;
+ }
+
+ if ($this->isRequired()
+ && $this->autoInsertNotEmptyValidator()
+ && !$this->getValidator('NotEmpty'))
+ {
+ $validators = $this->getValidators();
+ $notEmpty = array('validator' => 'NotEmpty', 'breakChainOnFailure' => true);
+ array_unshift($validators, $notEmpty);
+ $this->setValidators($validators);
+ }
+
+ // Find the correct translator. Zend_Validate_Abstract::getDefaultTranslator()
+ // will get either the static translator attached to Zend_Validate_Abstract
+ // or the 'Zend_Translate' from Zend_Registry.
+ if (Zend_Validate_Abstract::hasDefaultTranslator() &&
+ !Zend_Form::hasDefaultTranslator())
+ {
+ $translator = Zend_Validate_Abstract::getDefaultTranslator();
+ if ($this->hasTranslator()) {
+ // only pick up this element's translator if it was attached directly.
+ $translator = $this->getTranslator();
+ }
+ } else {
+ $translator = $this->getTranslator();
+ }
+
+ $this->_messages = array();
+ $this->_errors = array();
+ $result = true;
+ $isArray = $this->isArray();
+ foreach ($this->getValidators() as $key => $validator) {
+ if (method_exists($validator, 'setTranslator')) {
+ if (method_exists($validator, 'hasTranslator')) {
+ if (!$validator->hasTranslator()) {
+ $validator->setTranslator($translator);
+ }
+ } else {
+ $validator->setTranslator($translator);
+ }
+ }
+
+ if (method_exists($validator, 'setDisableTranslator')) {
+ $validator->setDisableTranslator($this->translatorIsDisabled());
+ }
+
+ if ($isArray && is_array($value)) {
+ $messages = array();
+ $errors = array();
+ if (empty($value)) {
+ if ($this->isRequired()
+ || (!$this->isRequired() && !$this->getAllowEmpty())
+ ) {
+ $value = '';
+ }
+ }
+ foreach ((array)$value as $val) {
+ if (!$validator->isValid($val, $context)) {
+ $result = false;
+ if ($this->_hasErrorMessages()) {
+ $messages = $this->_getErrorMessages();
+ $errors = $messages;
+ } else {
+ $messages = array_merge($messages, $validator->getMessages());
+ $errors = array_merge($errors, $validator->getErrors());
+ }
+ }
+ }
+ if ($result) {
+ continue;
+ }
+ } elseif ($validator->isValid($value, $context)) {
+ continue;
+ } else {
+ $result = false;
+ if ($this->_hasErrorMessages()) {
+ $messages = $this->_getErrorMessages();
+ $errors = $messages;
+ } else {
+ $messages = $validator->getMessages();
+ $errors = array_keys($messages);
+ }
+ }
+
+ $result = false;
+ $this->_messages = array_merge($this->_messages, $messages);
+ $this->_errors = array_merge($this->_errors, $errors);
+
+ if ($validator->zfBreakChainOnFailure) {
+ break;
+ }
+ }
+
+ // If element manually flagged as invalid, return false
+ if ($this->_isErrorForced) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add a custom error message to return in the event of failed validation
+ *
+ * @param string $message
+ * @return Zend_Form_Element
+ */
+ public function addErrorMessage($message)
+ {
+ $this->_errorMessages[] = (string) $message;
+ return $this;
+ }
+
+ /**
+ * Add multiple custom error messages to return in the event of failed validation
+ *
+ * @param array $messages
+ * @return Zend_Form_Element
+ */
+ public function addErrorMessages(array $messages)
+ {
+ foreach ($messages as $message) {
+ $this->addErrorMessage($message);
+ }
+ return $this;
+ }
+
+ /**
+ * Same as addErrorMessages(), but clears custom error message stack first
+ *
+ * @param array $messages
+ * @return Zend_Form_Element
+ */
+ public function setErrorMessages(array $messages)
+ {
+ $this->clearErrorMessages();
+ return $this->addErrorMessages($messages);
+ }
+
+ /**
+ * Retrieve custom error messages
+ *
+ * @return array
+ */
+ public function getErrorMessages()
+ {
+ return $this->_errorMessages;
+ }
+
+ /**
+ * Clear custom error messages stack
+ *
+ * @return Zend_Form_Element
+ */
+ public function clearErrorMessages()
+ {
+ $this->_errorMessages = array();
+ return $this;
+ }
+
+ /**
+ * Get errorMessageSeparator
+ *
+ * @return string
+ */
+ public function getErrorMessageSeparator()
+ {
+ return $this->_errorMessageSeparator;
+ }
+
+ /**
+ * Set errorMessageSeparator
+ *
+ * @param string $separator
+ * @return Zend_Form_Element
+ */
+ public function setErrorMessageSeparator($separator)
+ {
+ $this->_errorMessageSeparator = $separator;
+ return $this;
+ }
+
+ /**
+ * Mark the element as being in a failed validation state
+ *
+ * @return Zend_Form_Element
+ */
+ public function markAsError()
+ {
+ $messages = $this->getMessages();
+ $customMessages = $this->_getErrorMessages();
+ $messages = $messages + $customMessages;
+ if (empty($messages)) {
+ $this->_isError = true;
+ } else {
+ $this->_messages = $messages;
+ }
+ $this->_isErrorForced = true;
+ return $this;
+ }
+
+ /**
+ * Add an error message and mark element as failed validation
+ *
+ * @param string $message
+ * @return Zend_Form_Element
+ */
+ public function addError($message)
+ {
+ $this->addErrorMessage($message);
+ $this->markAsError();
+ return $this;
+ }
+
+ /**
+ * Add multiple error messages and flag element as failed validation
+ *
+ * @param array $messages
+ * @return Zend_Form_Element
+ */
+ public function addErrors(array $messages)
+ {
+ foreach ($messages as $message) {
+ $this->addError($message);
+ }
+ return $this;
+ }
+
+ /**
+ * Overwrite any previously set error messages and flag as failed validation
+ *
+ * @param array $messages
+ * @return Zend_Form_Element
+ */
+ public function setErrors(array $messages)
+ {
+ $this->clearErrorMessages();
+ return $this->addErrors($messages);
+ }
+
+ /**
+ * Are there errors registered?
+ *
+ * @return bool
+ */
+ public function hasErrors()
+ {
+ return (!empty($this->_messages) || $this->_isError);
+ }
+
+ /**
+ * Retrieve validator chain errors
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->_errors;
+ }
+
+ /**
+ * Retrieve error messages
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return $this->_messages;
+ }
+
+
+ // Filtering
+
+ /**
+ * Add a filter to the element
+ *
+ * @param string|Zend_Filter_Interface $filter
+ * @return Zend_Form_Element
+ */
+ public function addFilter($filter, $options = array())
+ {
+ if ($filter instanceof Zend_Filter_Interface) {
+ $name = get_class($filter);
+ } elseif (is_string($filter)) {
+ $name = $filter;
+ $filter = array(
+ 'filter' => $filter,
+ 'options' => $options,
+ );
+ $this->_filters[$name] = $filter;
+ } else {
+ throw new Zend_Form_Exception('Invalid filter provided to addFilter; must be string or Zend_Filter_Interface');
+ }
+
+ $this->_filters[$name] = $filter;
+
+ return $this;
+ }
+
+ /**
+ * Add filters to element
+ *
+ * @param array $filters
+ * @return Zend_Form_Element
+ */
+ public function addFilters(array $filters)
+ {
+ foreach ($filters as $filterInfo) {
+ if (is_string($filterInfo)) {
+ $this->addFilter($filterInfo);
+ } elseif ($filterInfo instanceof Zend_Filter_Interface) {
+ $this->addFilter($filterInfo);
+ } elseif (is_array($filterInfo)) {
+ $argc = count($filterInfo);
+ $options = array();
+ if (isset($filterInfo['filter'])) {
+ $filter = $filterInfo['filter'];
+ if (isset($filterInfo['options'])) {
+ $options = $filterInfo['options'];
+ }
+ $this->addFilter($filter, $options);
+ } else {
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $filter = array_shift($filterInfo);
+ case (2 <= $argc):
+ $options = array_shift($filterInfo);
+ default:
+ $this->addFilter($filter, $options);
+ break;
+ }
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid filter passed to addFilters()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add filters to element, overwriting any already existing
+ *
+ * @param array $filters
+ * @return Zend_Form_Element
+ */
+ public function setFilters(array $filters)
+ {
+ $this->clearFilters();
+ return $this->addFilters($filters);
+ }
+
+ /**
+ * Retrieve a single filter by name
+ *
+ * @param string $name
+ * @return Zend_Filter_Interface
+ */
+ public function getFilter($name)
+ {
+ if (!isset($this->_filters[$name])) {
+ $len = strlen($name);
+ foreach ($this->_filters as $localName => $filter) {
+ if ($len > strlen($localName)) {
+ continue;
+ }
+
+ if (0 === substr_compare($localName, $name, -$len, $len, true)) {
+ if (is_array($filter)) {
+ return $this->_loadFilter($filter);
+ }
+ return $filter;
+ }
+ }
+ return false;
+ }
+
+ if (is_array($this->_filters[$name])) {
+ return $this->_loadFilter($this->_filters[$name]);
+ }
+
+ return $this->_filters[$name];
+ }
+
+ /**
+ * Get all filters
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ $filters = array();
+ foreach ($this->_filters as $key => $value) {
+ if ($value instanceof Zend_Filter_Interface) {
+ $filters[$key] = $value;
+ continue;
+ }
+ $filter = $this->_loadFilter($value);
+ $filters[get_class($filter)] = $filter;
+ }
+ return $filters;
+ }
+
+ /**
+ * Remove a filter by name
+ *
+ * @param string $name
+ * @return Zend_Form_Element
+ */
+ public function removeFilter($name)
+ {
+ if (isset($this->_filters[$name])) {
+ unset($this->_filters[$name]);
+ } else {
+ $len = strlen($name);
+ foreach (array_keys($this->_filters) as $filter) {
+ if ($len > strlen($filter)) {
+ continue;
+ }
+ if (0 === substr_compare($filter, $name, -$len, $len, true)) {
+ unset($this->_filters[$filter]);
+ break;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Clear all filters
+ *
+ * @return Zend_Form_Element
+ */
+ public function clearFilters()
+ {
+ $this->_filters = array();
+ return $this;
+ }
+
+ // Rendering
+
+ /**
+ * Set view object
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_Form_Element
+ */
+ public function setView(Zend_View_Interface $view = null)
+ {
+ $this->_view = $view;
+ return $this;
+ }
+
+ /**
+ * Retrieve view object
+ *
+ * Retrieves from ViewRenderer if none previously set.
+ *
+ * @return null|Zend_View_Interface
+ */
+ public function getView()
+ {
+ if (null === $this->_view) {
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ $this->setView($viewRenderer->view);
+ }
+ return $this->_view;
+ }
+
+ /**
+ * Instantiate a decorator based on class name or class name fragment
+ *
+ * @param string $name
+ * @param null|array $options
+ * @return Zend_Form_Decorator_Interface
+ */
+ protected function _getDecorator($name, $options)
+ {
+ $class = $this->getPluginLoader(self::DECORATOR)->load($name);
+ if (null === $options) {
+ $decorator = new $class;
+ } else {
+ $decorator = new $class($options);
+ }
+
+ return $decorator;
+ }
+
+ /**
+ * Add a decorator for rendering the element
+ *
+ * @param string|Zend_Form_Decorator_Interface $decorator
+ * @param array|Zend_Config $options Options with which to initialize decorator
+ * @return Zend_Form_Element
+ */
+ public function addDecorator($decorator, $options = null)
+ {
+ if ($decorator instanceof Zend_Form_Decorator_Interface) {
+ $name = get_class($decorator);
+ } elseif (is_string($decorator)) {
+ $name = $decorator;
+ $decorator = array(
+ 'decorator' => $name,
+ 'options' => $options,
+ );
+ } elseif (is_array($decorator)) {
+ foreach ($decorator as $name => $spec) {
+ break;
+ }
+ if (is_numeric($name)) {
+ throw new Zend_Form_Exception('Invalid alias provided to addDecorator; must be alphanumeric string');
+ }
+ if (is_string($spec)) {
+ $decorator = array(
+ 'decorator' => $spec,
+ 'options' => $options,
+ );
+ } elseif ($spec instanceof Zend_Form_Decorator_Interface) {
+ $decorator = $spec;
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid decorator provided to addDecorator; must be string or Zend_Form_Decorator_Interface');
+ }
+
+ $this->_decorators[$name] = $decorator;
+
+ return $this;
+ }
+
+ /**
+ * Add many decorators at once
+ *
+ * @param array $decorators
+ * @return Zend_Form_Element
+ */
+ public function addDecorators(array $decorators)
+ {
+ foreach ($decorators as $decoratorName => $decoratorInfo) {
+ if (is_string($decoratorInfo) ||
+ $decoratorInfo instanceof Zend_Form_Decorator_Interface) {
+ if (!is_numeric($decoratorName)) {
+ $this->addDecorator(array($decoratorName => $decoratorInfo));
+ } else {
+ $this->addDecorator($decoratorInfo);
+ }
+ } elseif (is_array($decoratorInfo)) {
+ $argc = count($decoratorInfo);
+ $options = array();
+ if (isset($decoratorInfo['decorator'])) {
+ $decorator = $decoratorInfo['decorator'];
+ if (isset($decoratorInfo['options'])) {
+ $options = $decoratorInfo['options'];
+ }
+ $this->addDecorator($decorator, $options);
+ } else {
+ switch (true) {
+ case (0 == $argc):
+ break;
+ case (1 <= $argc):
+ $decorator = array_shift($decoratorInfo);
+ case (2 <= $argc):
+ $options = array_shift($decoratorInfo);
+ default:
+ $this->addDecorator($decorator, $options);
+ break;
+ }
+ }
+ } else {
+ throw new Zend_Form_Exception('Invalid decorator passed to addDecorators()');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overwrite all decorators
+ *
+ * @param array $decorators
+ * @return Zend_Form_Element
+ */
+ public function setDecorators(array $decorators)
+ {
+ $this->clearDecorators();
+ return $this->addDecorators($decorators);
+ }
+
+ /**
+ * Retrieve a registered decorator
+ *
+ * @param string $name
+ * @return false|Zend_Form_Decorator_Abstract
+ */
+ public function getDecorator($name)
+ {
+ if (!isset($this->_decorators[$name])) {
+ $len = strlen($name);
+ foreach ($this->_decorators as $localName => $decorator) {
+ if ($len > strlen($localName)) {
+ continue;
+ }
+
+ if (0 === substr_compare($localName, $name, -$len, $len, true)) {
+ if (is_array($decorator)) {
+ return $this->_loadDecorator($decorator, $localName);
+ }
+ return $decorator;
+ }
+ }
+ return false;
+ }
+
+ if (is_array($this->_decorators[$name])) {
+ return $this->_loadDecorator($this->_decorators[$name], $name);
+ }
+
+ return $this->_decorators[$name];
+ }
+
+ /**
+ * Retrieve all decorators
+ *
+ * @return array
+ */
+ public function getDecorators()
+ {
+ foreach ($this->_decorators as $key => $value) {
+ if (is_array($value)) {
+ $this->_loadDecorator($value, $key);
+ }
+ }
+ return $this->_decorators;
+ }
+
+ /**
+ * Remove a single decorator
+ *
+ * @param string $name
+ * @return Zend_Form_Element
+ */
+ public function removeDecorator($name)
+ {
+ if (isset($this->_decorators[$name])) {
+ unset($this->_decorators[$name]);
+ } else {
+ $len = strlen($name);
+ foreach (array_keys($this->_decorators) as $decorator) {
+ if ($len > strlen($decorator)) {
+ continue;
+ }
+ if (0 === substr_compare($decorator, $name, -$len, $len, true)) {
+ unset($this->_decorators[$decorator]);
+ break;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Clear all decorators
+ *
+ * @return Zend_Form_Element
+ */
+ public function clearDecorators()
+ {
+ $this->_decorators = array();
+ return $this;
+ }
+
+ /**
+ * Render form element
+ *
+ * @param Zend_View_Interface $view
+ * @return string
+ */
+ public function render(Zend_View_Interface $view = null)
+ {
+ if ($this->_isPartialRendering) {
+ return '';
+ }
+
+ if (null !== $view) {
+ $this->setView($view);
+ }
+
+ $content = '';
+ foreach ($this->getDecorators() as $decorator) {
+ $decorator->setElement($this);
+ $content = $decorator->render($content);
+ }
+ return $content;
+ }
+
+ /**
+ * String representation of form element
+ *
+ * Proxies to {@link render()}.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ $return = $this->render();
+ return $return;
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ return '';
+ }
+ }
+
+ /**
+ * Lazy-load a filter
+ *
+ * @param array $filter
+ * @return Zend_Filter_Interface
+ */
+ protected function _loadFilter(array $filter)
+ {
+ $origName = $filter['filter'];
+ $name = $this->getPluginLoader(self::FILTER)->load($filter['filter']);
+
+ if (array_key_exists($name, $this->_filters)) {
+ throw new Zend_Form_Exception(sprintf('Filter instance already exists for filter "%s"', $origName));
+ }
+
+ if (empty($filter['options'])) {
+ $instance = new $name;
+ } else {
+ $r = new ReflectionClass($name);
+ if ($r->hasMethod('__construct')) {
+ $instance = $r->newInstanceArgs(array_values((array) $filter['options']));
+ } else {
+ $instance = $r->newInstance();
+ }
+ }
+
+ if ($origName != $name) {
+ $filterNames = array_keys($this->_filters);
+ $order = array_flip($filterNames);
+ $order[$name] = $order[$origName];
+ $filtersExchange = array();
+ unset($order[$origName]);
+ asort($order);
+ foreach ($order as $key => $index) {
+ if ($key == $name) {
+ $filtersExchange[$key] = $instance;
+ continue;
+ }
+ $filtersExchange[$key] = $this->_filters[$key];
+ }
+ $this->_filters = $filtersExchange;
+ } else {
+ $this->_filters[$name] = $instance;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Lazy-load a validator
+ *
+ * @param array $validator Validator definition
+ * @return Zend_Validate_Interface
+ */
+ protected function _loadValidator(array $validator)
+ {
+ $origName = $validator['validator'];
+ $name = $this->getPluginLoader(self::VALIDATE)->load($validator['validator']);
+
+ if (array_key_exists($name, $this->_validators)) {
+ throw new Zend_Form_Exception(sprintf('Validator instance already exists for validator "%s"', $origName));
+ }
+
+ $messages = false;
+ if (isset($validator['options']) && array_key_exists('messages', (array)$validator['options'])) {
+ $messages = $validator['options']['messages'];
+ unset($validator['options']['messages']);
+ }
+
+ if (empty($validator['options'])) {
+ $instance = new $name;
+ } else {
+ $r = new ReflectionClass($name);
+ if ($r->hasMethod('__construct')) {
+ $numeric = false;
+ if (is_array($validator['options'])) {
+ $keys = array_keys($validator['options']);
+ foreach($keys as $key) {
+ if (is_numeric($key)) {
+ $numeric = true;
+ break;
+ }
+ }
+ }
+
+ if ($numeric) {
+ $instance = $r->newInstanceArgs(array_values((array) $validator['options']));
+ } else {
+ $instance = $r->newInstance($validator['options']);
+ }
+ } else {
+ $instance = $r->newInstance();
+ }
+ }
+
+ if ($messages) {
+ if (is_array($messages)) {
+ $instance->setMessages($messages);
+ } elseif (is_string($messages)) {
+ $instance->setMessage($messages);
+ }
+ }
+ $instance->zfBreakChainOnFailure = $validator['breakChainOnFailure'];
+
+ if ($origName != $name) {
+ $validatorNames = array_keys($this->_validators);
+ $order = array_flip($validatorNames);
+ $order[$name] = $order[$origName];
+ $validatorsExchange = array();
+ unset($order[$origName]);
+ asort($order);
+ foreach ($order as $key => $index) {
+ if ($key == $name) {
+ $validatorsExchange[$key] = $instance;
+ continue;
+ }
+ $validatorsExchange[$key] = $this->_validators[$key];
+ }
+ $this->_validators = $validatorsExchange;
+ } else {
+ $this->_validators[$name] = $instance;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Lazy-load a decorator
+ *
+ * @param array $decorator Decorator type and options
+ * @param mixed $name Decorator name or alias
+ * @return Zend_Form_Decorator_Interface
+ */
+ protected function _loadDecorator(array $decorator, $name)
+ {
+ $sameName = false;
+ if ($name == $decorator['decorator']) {
+ $sameName = true;
+ }
+
+ $instance = $this->_getDecorator($decorator['decorator'], $decorator['options']);
+ if ($sameName) {
+ $newName = get_class($instance);
+ $decoratorNames = array_keys($this->_decorators);
+ $order = array_flip($decoratorNames);
+ $order[$newName] = $order[$name];
+ $decoratorsExchange = array();
+ unset($order[$name]);
+ asort($order);
+ foreach ($order as $key => $index) {
+ if ($key == $newName) {
+ $decoratorsExchange[$key] = $instance;
+ continue;
+ }
+ $decoratorsExchange[$key] = $this->_decorators[$key];
+ }
+ $this->_decorators = $decoratorsExchange;
+ } else {
+ $this->_decorators[$name] = $instance;
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Retrieve error messages and perform translation and value substitution
+ *
+ * @return array
+ */
+ protected function _getErrorMessages()
+ {
+ $translator = $this->getTranslator();
+ $messages = $this->getErrorMessages();
+ $value = $this->getValue();
+ foreach ($messages as $key => $message) {
+ if (null !== $translator) {
+ $message = $translator->translate($message);
+ }
+ if ($this->isArray() || is_array($value)) {
+ $aggregateMessages = array();
+ foreach ($value as $val) {
+ $aggregateMessages[] = str_replace('%value%', $val, $message);
+ }
+ if (count($aggregateMessages)) {
+ if ($this->_concatJustValuesInErrorMessage) {
+ $values = implode($this->getErrorMessageSeparator(), $value);
+ $messages[$key] = str_replace('%value%', $values, $message);
+ } else {
+ $messages[$key] = implode($this->getErrorMessageSeparator(), $aggregateMessages);
+ }
+ }
+ } else {
+ $messages[$key] = str_replace('%value%', $value, $message);
+ }
+ }
+ return $messages;
+ }
+
+ /**
+ * Are there custom error messages registered?
+ *
+ * @return bool
+ */
+ protected function _hasErrorMessages()
+ {
+ return !empty($this->_errorMessages);
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Button.php b/library/vendor/Zend/Form/Element/Button.php
new file mode 100644
index 0000000..89de056
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Button.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Submit */
+
+/**
+ * Button form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Button extends Zend_Form_Element_Submit
+{
+ /**
+ * Use formButton view helper by default
+ * @var string
+ */
+ public $helper = 'formButton';
+
+ /**
+ * Validate element value (pseudo)
+ *
+ * There is no need to reset the value
+ *
+ * @param mixed $value Is always ignored
+ * @param mixed $context Is always ignored
+ * @return boolean Returns always TRUE
+ */
+ public function isValid($value, $context = null)
+ {
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Checkbox.php b/library/vendor/Zend/Form/Element/Checkbox.php
new file mode 100644
index 0000000..65cdccc
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Checkbox.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Checkbox form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Checkbox extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Is the checkbox checked?
+ * @var bool
+ */
+ public $checked = false;
+
+ /**
+ * Use formCheckbox view helper by default
+ * @var string
+ */
+ public $helper = 'formCheckbox';
+
+ /**
+ * Options that will be passed to the view helper
+ * @var array
+ */
+ public $options = array(
+ 'checkedValue' => '1',
+ 'uncheckedValue' => '0',
+ );
+
+ /**
+ * Value when checked
+ * @var string
+ */
+ protected $_checkedValue = '1';
+
+ /**
+ * Value when not checked
+ * @var string
+ */
+ protected $_uncheckedValue = '0';
+
+ /**
+ * Current value
+ * @var string 0 or 1
+ */
+ protected $_value = '0';
+
+ /**
+ * Set options
+ *
+ * Intercept checked and unchecked values and set them early; test stored
+ * value against checked and unchecked values after configuration.
+ *
+ * @param array $options
+ * @return Zend_Form_Element_Checkbox
+ */
+ public function setOptions(array $options)
+ {
+ if (array_key_exists('checkedValue', $options)) {
+ $this->setCheckedValue($options['checkedValue']);
+ unset($options['checkedValue']);
+ }
+ if (array_key_exists('uncheckedValue', $options)) {
+ $this->setUncheckedValue($options['uncheckedValue']);
+ unset($options['uncheckedValue']);
+ }
+ parent::setOptions($options);
+
+ $curValue = $this->getValue();
+ $test = array($this->getCheckedValue(), $this->getUncheckedValue());
+ if (!in_array($curValue, $test)) {
+ $this->setValue($curValue);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set value
+ *
+ * If value matches checked value, sets to that value, and sets the checked
+ * flag to true.
+ *
+ * Any other value causes the unchecked value to be set as the current
+ * value, and the checked flag to be set as false.
+ *
+ *
+ * @param mixed $value
+ * @return Zend_Form_Element_Checkbox
+ */
+ public function setValue($value)
+ {
+ if ($value == $this->getCheckedValue()) {
+ parent::setValue($value);
+ $this->checked = true;
+ } else {
+ parent::setValue($this->getUncheckedValue());
+ $this->checked = false;
+ }
+ return $this;
+ }
+
+ /**
+ * Set checked value
+ *
+ * @param string $value
+ * @return Zend_Form_Element_Checkbox
+ */
+ public function setCheckedValue($value)
+ {
+ $this->_checkedValue = (string) $value;
+ $this->options['checkedValue'] = $value;
+ return $this;
+ }
+
+ /**
+ * Get value when checked
+ *
+ * @return string
+ */
+ public function getCheckedValue()
+ {
+ return $this->_checkedValue;
+ }
+
+ /**
+ * Set unchecked value
+ *
+ * @param string $value
+ * @return Zend_Form_Element_Checkbox
+ */
+ public function setUncheckedValue($value)
+ {
+ $this->_uncheckedValue = (string) $value;
+ $this->options['uncheckedValue'] = $value;
+ return $this;
+ }
+
+ /**
+ * Get value when not checked
+ *
+ * @return string
+ */
+ public function getUncheckedValue()
+ {
+ return $this->_uncheckedValue;
+ }
+
+ /**
+ * Set checked flag
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element_Checkbox
+ */
+ public function setChecked($flag)
+ {
+ $this->checked = (bool) $flag;
+ if ($this->checked) {
+ $this->setValue($this->getCheckedValue());
+ } else {
+ $this->setValue($this->getUncheckedValue());
+ }
+ return $this;
+ }
+
+ /**
+ * Get checked flag
+ *
+ * @return bool
+ */
+ public function isChecked()
+ {
+ return $this->checked;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Exception.php b/library/vendor/Zend/Form/Element/Exception.php
new file mode 100644
index 0000000..b39e053
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Exception */
+
+/**
+ * Exception for Zend_Form component.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Form_Element_Exception extends Zend_Form_Exception
+{
+}
diff --git a/library/vendor/Zend/Form/Element/File.php b/library/vendor/Zend/Form/Element/File.php
new file mode 100644
index 0000000..1b336da
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/File.php
@@ -0,0 +1,910 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Zend_Form_Element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_File extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Plugin loader type
+ */
+ const TRANSFER_ADAPTER = 'TRANSFER_ADAPTER';
+
+ /**
+ * @var string Default view helper
+ */
+ public $helper = 'formFile';
+
+ /**
+ * @var Zend_File_Transfer_Adapter_Abstract
+ */
+ protected $_adapter;
+
+ /**
+ * @var boolean Already validated ?
+ */
+ protected $_validated = false;
+
+ /**
+ * @var boolean Disable value to be equal to file content
+ */
+ protected $_valueDisabled = false;
+
+ /**
+ * @var integer Internal multifile counter
+ */
+ protected $_counter = 1;
+
+ /**
+ * @var integer Maximum file size for MAX_FILE_SIZE attribut of form
+ */
+ protected static $_maxFileSize = -1;
+
+ /**
+ * Load default decorators
+ *
+ * @return Zend_Form_Element_File
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ parent::loadDefaultDecorators();
+
+ // This element needs the File decorator and not the ViewHelper decorator
+ if (false !== $this->getDecorator('ViewHelper')) {
+ $this->removeDecorator('ViewHelper');
+ }
+ if (false === $this->getDecorator('File')) {
+ // Add File decorator to the beginning
+ $decorators = $this->getDecorators();
+ array_unshift($decorators, 'File');
+ $this->setDecorators($decorators);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set plugin loader
+ *
+ * @param Zend_Loader_PluginLoader_Interface $loader
+ * @param string $type
+ * @return Zend_Form_Element_File
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
+ {
+ $type = strtoupper($type);
+
+ if ($type != self::TRANSFER_ADAPTER) {
+ return parent::setPluginLoader($loader, $type);
+ }
+
+ $this->_loaders[$type] = $loader;
+ return $this;
+ }
+
+ /**
+ * Get Plugin Loader
+ *
+ * @param string $type
+ * @return Zend_Loader_PluginLoader_Interface
+ */
+ public function getPluginLoader($type)
+ {
+ $type = strtoupper($type);
+
+ if ($type != self::TRANSFER_ADAPTER) {
+ return parent::getPluginLoader($type);
+ }
+
+ if (!array_key_exists($type, $this->_loaders)) {
+ $loader = new Zend_Loader_PluginLoader(array(
+ 'Zend_File_Transfer_Adapter' => 'Zend/File/Transfer/Adapter/',
+ ));
+ $this->setPluginLoader($loader, self::TRANSFER_ADAPTER);
+ }
+
+ return $this->_loaders[$type];
+ }
+
+ /**
+ * Add prefix path for plugin loader
+ *
+ * @param string $prefix
+ * @param string $path
+ * @param string $type
+ * @return Zend_Form_Element_File
+ */
+ public function addPrefixPath($prefix, $path, $type = null)
+ {
+ $type = strtoupper($type);
+ if (!empty($type) && ($type != self::TRANSFER_ADAPTER)) {
+ return parent::addPrefixPath($prefix, $path, $type);
+ }
+
+ if (empty($type)) {
+ $nsSeparator = (false !== strpos($prefix, '\\'))?'\\':'_';
+ $pluginPrefix = rtrim($prefix, $nsSeparator) . $nsSeparator . 'Transfer' . $nsSeparator . 'Adapter';
+ $pluginPath = rtrim($path, DIRECTORY_SEPARATOR) . '/Transfer/Adapter/';
+ $loader = $this->getPluginLoader(self::TRANSFER_ADAPTER);
+ $loader->addPrefixPath($pluginPrefix, $pluginPath);
+ return parent::addPrefixPath($prefix, $path, null);
+ }
+
+ $loader = $this->getPluginLoader($type);
+ $loader->addPrefixPath($prefix, $path);
+ return $this;
+ }
+
+ /**
+ * Set transfer adapter
+ *
+ * @param string|Zend_File_Transfer_Adapter_Abstract $adapter
+ * @return Zend_Form_Element_File
+ * @throws Zend_Form_Element_Exception
+ */
+ public function setTransferAdapter($adapter)
+ {
+ if ($adapter instanceof Zend_File_Transfer_Adapter_Abstract) {
+ $this->_adapter = $adapter;
+ } elseif (is_string($adapter)) {
+ $loader = $this->getPluginLoader(self::TRANSFER_ADAPTER);
+ $class = $loader->load($adapter);
+ $this->_adapter = new $class;
+ } else {
+ throw new Zend_Form_Element_Exception('Invalid adapter specified');
+ }
+
+ foreach (array('filter', 'validate') as $type) {
+ $loader = $this->getPluginLoader($type);
+ $this->_adapter->setPluginLoader($loader, $type);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get transfer adapter
+ *
+ * Lazy loads HTTP transfer adapter when no adapter registered.
+ *
+ * @return Zend_File_Transfer_Adapter_Abstract
+ */
+ public function getTransferAdapter()
+ {
+ if (null === $this->_adapter) {
+ $this->setTransferAdapter('Http');
+ }
+ return $this->_adapter;
+ }
+
+ /**
+ * Add Validator; proxy to adapter
+ *
+ * @param string|Zend_Validate_Interface $validator
+ * @param bool $breakChainOnFailure
+ * @param mixed $options
+ * @return Zend_Form_Element_File
+ */
+ public function addValidator($validator, $breakChainOnFailure = false, $options = array())
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->addValidator($validator, $breakChainOnFailure, $options, $this->getName());
+ $this->_validated = false;
+
+ return $this;
+ }
+
+ /**
+ * Add multiple validators at once; proxy to adapter
+ *
+ * @param array $validators
+ * @return Zend_Form_Element_File
+ */
+ public function addValidators(array $validators)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->addValidators($validators, $this->getName());
+ $this->_validated = false;
+
+ return $this;
+ }
+
+ /**
+ * Add multiple validators at once, overwriting; proxy to adapter
+ *
+ * @param array $validators
+ * @return Zend_Form_Element_File
+ */
+ public function setValidators(array $validators)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->setValidators($validators, $this->getName());
+ $this->_validated = false;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve validator by name; proxy to adapter
+ *
+ * @param string $name
+ * @return Zend_Validate_Interface|null
+ */
+ public function getValidator($name)
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->getValidator($name);
+ }
+
+ /**
+ * Retrieve all validators; proxy to adapter
+ *
+ * @return array
+ */
+ public function getValidators()
+ {
+ $adapter = $this->getTransferAdapter();
+ $validators = $adapter->getValidators($this->getName());
+ if ($validators === null) {
+ $validators = array();
+ }
+
+ return $validators;
+ }
+
+ /**
+ * Remove validator by name; proxy to adapter
+ *
+ * @param string $name
+ * @return Zend_Form_Element_File
+ */
+ public function removeValidator($name)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->removeValidator($name);
+ $this->_validated = false;
+
+ return $this;
+ }
+
+ /**
+ * Remove all validators; proxy to adapter
+ *
+ * @return Zend_Form_Element_File
+ */
+ public function clearValidators()
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->clearValidators();
+ $this->_validated = false;
+
+ return $this;
+ }
+
+ /**
+ * Add Filter; proxy to adapter
+ *
+ * @param string|array $filter Type of filter to add
+ * @param string|array $options Options to set for the filter
+ * @return Zend_Form_Element_File
+ */
+ public function addFilter($filter, $options = null)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->addFilter($filter, $options, $this->getName());
+
+ return $this;
+ }
+
+ /**
+ * Add Multiple filters at once; proxy to adapter
+ *
+ * @param array $filters
+ * @return Zend_Form_Element_File
+ */
+ public function addFilters(array $filters)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->addFilters($filters, $this->getName());
+
+ return $this;
+ }
+
+ /**
+ * Sets a filter for the class, erasing all previous set; proxy to adapter
+ *
+ * @param array $filters Filters to set
+ * @return Zend_Form_Element_File
+ */
+ public function setFilters(array $filters)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->setFilters($filters, $this->getName());
+
+ return $this;
+ }
+
+ /**
+ * Retrieve individual filter; proxy to adapter
+ *
+ * @param string $name
+ * @return Zend_Filter_Interface|null
+ */
+ public function getFilter($name)
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->getFilter($name);
+ }
+
+ /**
+ * Returns all set filters; proxy to adapter
+ *
+ * @return array List of set filters
+ */
+ public function getFilters()
+ {
+ $adapter = $this->getTransferAdapter();
+ $filters = $adapter->getFilters($this->getName());
+
+ if ($filters === null) {
+ $filters = array();
+ }
+ return $filters;
+ }
+
+ /**
+ * Remove an individual filter; proxy to adapter
+ *
+ * @param string $name
+ * @return Zend_Form_Element_File
+ */
+ public function removeFilter($name)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->removeFilter($name);
+
+ return $this;
+ }
+
+ /**
+ * Remove all filters; proxy to adapter
+ *
+ * @return Zend_Form_Element_File
+ */
+ public function clearFilters()
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->clearFilters();
+
+ return $this;
+ }
+
+ /**
+ * Validate upload
+ *
+ * @param string $value File, can be optional, give null to validate all files
+ * @param mixed $context
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ if ($this->_validated) {
+ return true;
+ }
+
+ $adapter = $this->getTransferAdapter();
+ $translator = $this->getTranslator();
+ if ($translator !== null) {
+ $adapter->setTranslator($translator);
+ }
+
+ if (!$this->isRequired()) {
+ $adapter->setOptions(array('ignoreNoFile' => true), $this->getName());
+ } else {
+ $adapter->setOptions(array('ignoreNoFile' => false), $this->getName());
+ if ($this->autoInsertNotEmptyValidator() && !$this->getValidator('NotEmpty')) {
+ $this->addValidator('NotEmpty', true);
+ }
+ }
+
+ if($adapter->isValid($this->getName())) {
+ $this->_validated = true;
+ return true;
+ }
+
+ $this->_validated = false;
+ return false;
+ }
+
+ /**
+ * Receive the uploaded file
+ *
+ * @return boolean
+ */
+ public function receive()
+ {
+ if (!$this->_validated) {
+ if (!$this->isValid($this->getName())) {
+ return false;
+ }
+ }
+
+ $adapter = $this->getTransferAdapter();
+ if ($adapter->receive($this->getName())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve error codes; proxy to transfer adapter
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return parent::getErrors() + $this->getTransferAdapter()->getErrors();
+ }
+
+ /**
+ * Are there errors registered?
+ *
+ * @return bool
+ */
+ public function hasErrors()
+ {
+ return (parent::hasErrors() || $this->getTransferAdapter()->hasErrors());
+ }
+
+ /**
+ * Retrieve error messages; proxy to transfer adapter
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return parent::getMessages() + $this->getTransferAdapter()->getMessages();
+ }
+
+ /**
+ * Set the upload destination
+ *
+ * @param string $path
+ * @return Zend_Form_Element_File
+ */
+ public function setDestination($path)
+ {
+ $this->getTransferAdapter()->setDestination($path, $this->getName());
+ return $this;
+ }
+
+ /**
+ * Get the upload destination
+ *
+ * @return string
+ */
+ public function getDestination()
+ {
+ return $this->getTransferAdapter()->getDestination($this->getName());
+ }
+
+ /**
+ * Get the final filename
+ *
+ * @param string $value (Optional) Element or file to return
+ * @param boolean $path (Optional) Return also the path, defaults to true
+ * @return string
+ */
+ public function getFileName($value = null, $path = true)
+ {
+ if (empty($value)) {
+ $value = $this->getName();
+ }
+
+ return $this->getTransferAdapter()->getFileName($value, $path);
+ }
+
+ /**
+ * Get internal file informations
+ *
+ * @param string $value (Optional) Element or file to return
+ * @return array
+ */
+ public function getFileInfo($value = null)
+ {
+ if (empty($value)) {
+ $value = $this->getName();
+ }
+
+ return $this->getTransferAdapter()->getFileInfo($value);
+ }
+
+ /**
+ * Set a multifile element
+ *
+ * @param integer $count Number of file elements
+ * @return Zend_Form_Element_File Provides fluent interface
+ */
+ public function setMultiFile($count)
+ {
+ if ((integer) $count < 2) {
+ $this->setIsArray(false);
+ $this->_counter = 1;
+ } else {
+ $this->setIsArray(true);
+ $this->_counter = (integer) $count;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the multifile element number
+ *
+ * @return integer
+ */
+ public function getMultiFile()
+ {
+ return $this->_counter;
+ }
+
+ /**
+ * Sets the maximum file size of the form
+ *
+ * @return integer
+ */
+ public function getMaxFileSize()
+ {
+ if (self::$_maxFileSize < 0) {
+ $ini = $this->_convertIniToInteger(trim(ini_get('post_max_size')));
+ $max = $this->_convertIniToInteger(trim(ini_get('upload_max_filesize')));
+ $min = max($ini, $max);
+ if ($ini > 0) {
+ $min = min($min, $ini);
+ }
+
+ if ($max > 0) {
+ $min = min($min, $max);
+ }
+
+ self::$_maxFileSize = $min;
+ }
+
+ return self::$_maxFileSize;
+ }
+
+ /**
+ * Sets the maximum file size of the form
+ *
+ * @param integer $size
+ * @return integer
+ */
+ public function setMaxFileSize($size)
+ {
+ $ini = $this->_convertIniToInteger(trim(ini_get('post_max_size')));
+ $max = $this->_convertIniToInteger(trim(ini_get('upload_max_filesize')));
+
+ if (($max > -1) && ($size > $max)) {
+ trigger_error("Your 'upload_max_filesize' config setting limits the maximum filesize to '$max'. You tried to set '$size'.", E_USER_NOTICE);
+ $size = $max;
+ }
+
+ if (($ini > -1) && ($size > $ini)) {
+ trigger_error("Your 'post_max_size' config setting limits the maximum filesize to '$ini'. You tried to set '$size'.", E_USER_NOTICE);
+ $size = $ini;
+ }
+
+ self::$_maxFileSize = $size;
+ return $this;
+ }
+
+ /**
+ * Converts a ini setting to a integer value
+ *
+ * @param string $setting
+ * @return integer
+ */
+ private function _convertIniToInteger($setting)
+ {
+ if (!is_numeric($setting)) {
+ $type = strtoupper(substr($setting, -1));
+ $setting = (integer) substr($setting, 0, -1);
+
+ switch ($type) {
+ case 'K' :
+ $setting *= 1024;
+ break;
+
+ case 'M' :
+ $setting *= 1024 * 1024;
+ break;
+
+ case 'G' :
+ $setting *= 1024 * 1024 * 1024;
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ return (integer) $setting;
+ }
+
+ /**
+ * Set if the file will be uploaded when getting the value
+ * This defaults to false which will force receive() when calling getValues()
+ *
+ * @param boolean $flag Sets if the file is handled as the elements value
+ * @return Zend_Form_Element_File
+ */
+ public function setValueDisabled($flag)
+ {
+ $this->_valueDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Returns if the file will be uploaded when calling getValues()
+ *
+ * @return boolean Receive the file on calling getValues()?
+ */
+ public function isValueDisabled()
+ {
+ return $this->_valueDisabled;
+ }
+
+ /**
+ * Processes the file, returns null or the filename only
+ * For the complete path, use getFileName
+ *
+ * @return null|string
+ */
+ public function getValue()
+ {
+ if ($this->_value !== null) {
+ return $this->_value;
+ }
+
+ $content = $this->getTransferAdapter()->getFileName($this->getName());
+ if (empty($content)) {
+ return null;
+ }
+
+ if (!$this->isValid(null)) {
+ return null;
+ }
+
+ if (!$this->_valueDisabled && !$this->receive()) {
+ return null;
+ }
+
+ return $this->getFileName(null, false);
+ }
+
+ /**
+ * Disallow setting the value
+ *
+ * @param mixed $value
+ * @return Zend_Form_Element_File
+ */
+ public function setValue($value)
+ {
+ return $this;
+ }
+
+ /**
+ * Set translator object for localization
+ *
+ * @param Zend_Translate|null $translator
+ * @return Zend_Form_Element_File
+ */
+ public function setTranslator($translator = null)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->setTranslator($translator);
+ parent::setTranslator($translator);
+
+ return $this;
+ }
+
+ /**
+ * Retrieve localization translator object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ $translator = $this->getTransferAdapter()->getTranslator();
+ if (null === $translator) {
+ return Zend_Form::getDefaultTranslator();
+ }
+
+ return $translator;
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element_File
+ */
+ public function setDisableTranslator($flag)
+ {
+ $adapter = $this->getTransferAdapter();
+ $adapter->setDisableTranslator($flag);
+ $this->_translatorDisabled = (bool) $flag;
+
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->translatorIsDisabled();
+ }
+
+ /**
+ * Was the file received?
+ *
+ * @return bool
+ */
+ public function isReceived()
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->isReceived($this->getName());
+ }
+
+ /**
+ * Was the file uploaded?
+ *
+ * @return bool
+ */
+ public function isUploaded()
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->isUploaded($this->getName());
+ }
+
+ /**
+ * Has the file been filtered?
+ *
+ * @return bool
+ */
+ public function isFiltered()
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->isFiltered($this->getName());
+ }
+
+ /**
+ * Returns the hash for this file element
+ *
+ * @param string $hash (Optional) Hash algorithm to use
+ * @return string|array Hashstring
+ */
+ public function getHash($hash = 'crc32')
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->getHash($hash, $this->getName());
+ }
+
+ /**
+ * Returns the filesize for this file element
+ *
+ * @return string|array Filesize
+ */
+ public function getFileSize()
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->getFileSize($this->getName());
+ }
+
+ /**
+ * Returns the mimetype for this file element
+ *
+ * @return string|array Mimetype
+ */
+ public function getMimeType()
+ {
+ $adapter = $this->getTransferAdapter();
+ return $adapter->getMimeType($this->getName());
+ }
+
+ /**
+ * Render form element
+ * Checks for decorator interface to prevent errors
+ *
+ * @param Zend_View_Interface $view
+ * @return string
+ * @throws Zend_Form_Element_Exception
+ */
+ public function render(Zend_View_Interface $view = null)
+ {
+ $marker = false;
+ foreach ($this->getDecorators() as $decorator) {
+ if ($decorator instanceof Zend_Form_Decorator_Marker_File_Interface) {
+ $marker = true;
+ }
+ }
+
+ if (!$marker) {
+ throw new Zend_Form_Element_Exception('No file decorator found... unable to render file element');
+ }
+
+ return parent::render($view);
+ }
+
+ /**
+ * Retrieve error messages and perform translation and value substitution
+ *
+ * @return array
+ */
+ protected function _getErrorMessages()
+ {
+ $translator = $this->getTranslator();
+ $messages = $this->getErrorMessages();
+ $value = $this->getFileName();
+ foreach ($messages as $key => $message) {
+ if (null !== $translator) {
+ $message = $translator->translate($message);
+ }
+
+ if ($this->isArray() || is_array($value)) {
+ $aggregateMessages = array();
+ foreach ($value as $val) {
+ $aggregateMessages[] = str_replace('%value%', $val, $message);
+ }
+
+ if (!empty($aggregateMessages)) {
+ $messages[$key] = $aggregateMessages;
+ }
+ } else {
+ $messages[$key] = str_replace('%value%', $value, $message);
+ }
+ }
+
+ return $messages;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Hidden.php b/library/vendor/Zend/Form/Element/Hidden.php
new file mode 100644
index 0000000..c512684
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Hidden.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Hidden form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Hidden extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Use formHidden view helper by default
+ * @var string
+ */
+ public $helper = 'formHidden';
+}
diff --git a/library/vendor/Zend/Form/Element/Image.php b/library/vendor/Zend/Form/Element/Image.php
new file mode 100644
index 0000000..46c914a
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Image.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Image form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Image extends Zend_Form_Element_Xhtml
+{
+ /**
+ * What view helper to use when using view helper decorator
+ * @var string
+ */
+ public $helper = 'formImage';
+
+ /**
+ * Image source
+ * @var string
+ */
+ public $src;
+
+ /**
+ * Image value
+ * @var mixed
+ */
+ protected $_imageValue;
+
+ /**
+ * Load default decorators
+ *
+ * @return Zend_Form_Element_Image
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ $this->addDecorator('Tooltip')
+ ->addDecorator('Image')
+ ->addDecorator('Errors')
+ ->addDecorator('HtmlTag', array('tag' => 'dd'))
+ ->addDecorator('Label', array('tag' => 'dt'));
+ }
+ return $this;
+ }
+
+ /**
+ * Set image path
+ *
+ * @param string $path
+ * @return Zend_Form_Element_Image
+ */
+ public function setImage($path)
+ {
+ $this->src = (string) $path;
+ return $this;
+ }
+
+ /**
+ * Get image path
+ *
+ * @return string
+ */
+ public function getImage()
+ {
+ return $this->src;
+ }
+
+ /**
+ * Set image value to use when submitted
+ *
+ * @param mixed $value
+ * @return Zend_Form_Element_Image
+ */
+ public function setImageValue($value)
+ {
+ $this->_imageValue = $value;
+ return $this;
+ }
+
+ /**
+ * Get image value to use when submitted
+ *
+ * @return mixed
+ */
+ public function getImageValue()
+ {
+ return $this->_imageValue;
+ }
+
+ /**
+ * Was this element used to submit the form?
+ *
+ * @return bool
+ */
+ public function isChecked()
+ {
+ $imageValue = $this->getImageValue();
+ return ((null !== $imageValue) && ($this->getValue() == $imageValue));
+ }
+
+}
diff --git a/library/vendor/Zend/Form/Element/Multi.php b/library/vendor/Zend/Form/Element/Multi.php
new file mode 100644
index 0000000..3de2033
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Multi.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Base class for multi-option form elements
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Form_Element_Multi extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Array of options for multi-item
+ * @var array
+ */
+ public $options = array();
+
+ /**
+ * Flag: autoregister inArray validator?
+ * @var bool
+ */
+ protected $_registerInArrayValidator = true;
+
+ /**
+ * Separator to use between options; defaults to '<br />'.
+ * @var string
+ */
+ protected $_separator = '<br />';
+
+ /**
+ * Which values are translated already?
+ * @var array
+ */
+ protected $_translated = array();
+
+ /**
+ * Retrieve separator
+ *
+ * @return mixed
+ */
+ public function getSeparator()
+ {
+ return $this->_separator;
+ }
+
+ /**
+ * Set separator
+ *
+ * @param mixed $separator
+ * @return self
+ */
+ public function setSeparator($separator)
+ {
+ $this->_separator = $separator;
+ return $this;
+ }
+
+ /**
+ * Retrieve options array
+ *
+ * @return array
+ */
+ protected function _getMultiOptions()
+ {
+ if (null === $this->options || !is_array($this->options)) {
+ $this->options = array();
+ }
+
+ return $this->options;
+ }
+
+ /**
+ * Add an option
+ *
+ * @param string $option
+ * @param string $value
+ * @return Zend_Form_Element_Multi
+ */
+ public function addMultiOption($option, $value = '')
+ {
+ $option = (string) $option;
+ $this->_getMultiOptions();
+ if (!$this->_translateOption($option, $value)) {
+ $this->options[$option] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add many options at once
+ *
+ * @param array $options
+ * @return Zend_Form_Element_Multi
+ */
+ public function addMultiOptions(array $options)
+ {
+ foreach ($options as $option => $value) {
+ if (is_array($value)
+ && array_key_exists('key', $value)
+ && array_key_exists('value', $value)
+ ) {
+ $this->addMultiOption($value['key'], $value['value']);
+ } else {
+ $this->addMultiOption($option, $value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set all options at once (overwrites)
+ *
+ * @param array $options
+ * @return Zend_Form_Element_Multi
+ */
+ public function setMultiOptions(array $options)
+ {
+ $this->clearMultiOptions();
+ return $this->addMultiOptions($options);
+ }
+
+ /**
+ * Retrieve single multi option
+ *
+ * @param string $option
+ * @return mixed
+ */
+ public function getMultiOption($option)
+ {
+ $option = (string) $option;
+ $this->_getMultiOptions();
+ if (isset($this->options[$option])) {
+ $this->_translateOption($option, $this->options[$option]);
+ return $this->options[$option];
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieve options
+ *
+ * @return array
+ */
+ public function getMultiOptions()
+ {
+ $this->_getMultiOptions();
+ foreach ($this->options as $option => $value) {
+ $this->_translateOption($option, $value);
+ }
+ return $this->options;
+ }
+
+ /**
+ * Remove a single multi option
+ *
+ * @param string $option
+ * @return bool
+ */
+ public function removeMultiOption($option)
+ {
+ $option = (string) $option;
+ $this->_getMultiOptions();
+ if (isset($this->options[$option])) {
+ unset($this->options[$option]);
+ if (isset($this->_translated[$option])) {
+ unset($this->_translated[$option]);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Clear all options
+ *
+ * @return Zend_Form_Element_Multi
+ */
+ public function clearMultiOptions()
+ {
+ $this->options = array();
+ $this->_translated = array();
+ return $this;
+ }
+
+ /**
+ * Set flag indicating whether or not to auto-register inArray validator
+ *
+ * @param bool $flag
+ * @return Zend_Form_Element_Multi
+ */
+ public function setRegisterInArrayValidator($flag)
+ {
+ $this->_registerInArrayValidator = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get status of auto-register inArray validator flag
+ *
+ * @return bool
+ */
+ public function registerInArrayValidator()
+ {
+ return $this->_registerInArrayValidator;
+ }
+
+ /**
+ * Is the value provided valid?
+ *
+ * Autoregisters InArray validator if necessary.
+ *
+ * @param string $value
+ * @param mixed $context
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ if ($this->registerInArrayValidator()) {
+ if (!$this->getValidator('InArray')) {
+ $multiOptions = $this->getMultiOptions();
+ $options = array();
+
+ foreach ($multiOptions as $opt_value => $opt_label) {
+ // optgroup instead of option label
+ if (is_array($opt_label)) {
+ $options = array_merge($options, array_keys($opt_label));
+ }
+ else {
+ $options[] = $opt_value;
+ }
+ }
+
+ $this->addValidator(
+ 'InArray',
+ true,
+ array($options)
+ );
+ }
+ }
+ return parent::isValid($value, $context);
+ }
+
+ /**
+ * Translate an option
+ *
+ * @param string $option
+ * @param string $value
+ * @return bool
+ */
+ protected function _translateOption($option, $value)
+ {
+ if ($this->translatorIsDisabled()) {
+ return false;
+ }
+
+ if (!isset($this->_translated[$option]) && !empty($value)) {
+ $this->options[$option] = $this->_translateValue($value);
+ if ($this->options[$option] === $value) {
+ return false;
+ }
+ $this->_translated[$option] = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Translate a multi option value
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function _translateValue($value)
+ {
+ if (is_array($value)) {
+ foreach ($value as $key => $val) {
+ $value[$key] = $this->_translateValue($val);
+ }
+ return $value;
+ } else {
+ if (null !== ($translator = $this->getTranslator())) {
+ return $translator->translate($value);
+ }
+
+ return $value;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/MultiCheckbox.php b/library/vendor/Zend/Form/Element/MultiCheckbox.php
new file mode 100644
index 0000000..24fe938
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/MultiCheckbox.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Multi */
+
+/**
+ * MultiCheckbox form element
+ *
+ * Allows specifyinc a (multi-)dimensional associative array of values to use
+ * as labelled checkboxes; these will return an array of values for those
+ * checkboxes selected.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_MultiCheckbox extends Zend_Form_Element_Multi
+{
+ /**
+ * Use formMultiCheckbox view helper by default
+ * @var string
+ */
+ public $helper = 'formMultiCheckbox';
+
+ /**
+ * MultiCheckbox is an array of values by default
+ * @var bool
+ */
+ protected $_isArray = true;
+
+ /**
+ * Load default decorators
+ *
+ * @return Zend_Form_Element_MultiCheckbox
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ parent::loadDefaultDecorators();
+
+ // Disable 'for' attribute
+ if (false !== $decorator = $this->getDecorator('label')) {
+ $decorator->setOption('disableFor', true);
+ }
+
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Multiselect.php b/library/vendor/Zend/Form/Element/Multiselect.php
new file mode 100644
index 0000000..5e94d56
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Multiselect.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Select */
+
+/**
+ * Multiselect form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Multiselect extends Zend_Form_Element_Select
+{
+ /**
+ * 'multiple' attribute
+ * @var string
+ */
+ public $multiple = 'multiple';
+
+ /**
+ * Use formSelect view helper by default
+ * @var string
+ */
+ public $helper = 'formSelect';
+
+ /**
+ * Multiselect is an array of values by default
+ * @var bool
+ */
+ protected $_isArray = true;
+}
diff --git a/library/vendor/Zend/Form/Element/Note.php b/library/vendor/Zend/Form/Element/Note.php
new file mode 100644
index 0000000..77dee53
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Note.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Element to show an HTML note
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Note extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Default form view helper to use for rendering
+ *
+ * @var string
+ */
+ public $helper = 'formNote';
+
+ /**
+ * Ignore flag (used when retrieving values at form level)
+ *
+ * @var bool
+ */
+ protected $_ignore = true;
+
+ /**
+ * Validate element value (pseudo)
+ *
+ * There is no need to reset the value
+ *
+ * @param mixed $value Is always ignored
+ * @param mixed $context Is always ignored
+ * @return boolean Returns always TRUE
+ */
+ public function isValid($value, $context = null)
+ {
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Password.php b/library/vendor/Zend/Form/Element/Password.php
new file mode 100644
index 0000000..8d84c72
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Password.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Password form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Password extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Use formPassword view helper by default
+ * @var string
+ */
+ public $helper = 'formPassword';
+
+ /**
+ * Whether or not to render the password
+ * @var bool
+ */
+ public $renderPassword = false;
+
+ /**
+ * Set flag indicating whether or not to render the password
+ * @param bool $flag
+ * @return Zend_Form_Element_Password
+ */
+ public function setRenderPassword($flag)
+ {
+ $this->renderPassword = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get value of renderPassword flag
+ *
+ * @return bool
+ */
+ public function renderPassword()
+ {
+ return $this->renderPassword;
+ }
+
+ /**
+ * Override isValid()
+ *
+ * Ensure that validation error messages mask password value.
+ *
+ * @param string $value
+ * @param mixed $context
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ foreach ($this->getValidators() as $validator) {
+ if ($validator instanceof Zend_Validate_Abstract) {
+ $validator->setObscureValue(true);
+ }
+ }
+ return parent::isValid($value, $context);
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Radio.php b/library/vendor/Zend/Form/Element/Radio.php
new file mode 100644
index 0000000..883a00d
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Radio.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Multi */
+
+/**
+ * Radio form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Radio extends Zend_Form_Element_Multi
+{
+ /**
+ * Use formRadio view helper by default
+ * @var string
+ */
+ public $helper = 'formRadio';
+
+ /**
+ * Load default decorators
+ *
+ * Disables "for" attribute of label if label decorator enabled.
+ *
+ * @return Zend_Form_Element_Radio
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+ parent::loadDefaultDecorators();
+
+ // Disable 'for' attribute
+ if (isset($this->_decorators['Label'])
+ && !isset($this->_decorators['Label']['options']['disableFor']))
+ {
+ $this->_decorators['Label']['options']['disableFor'] = true;
+ }
+
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Reset.php b/library/vendor/Zend/Form/Element/Reset.php
new file mode 100644
index 0000000..8a27805
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Reset.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Submit */
+
+/**
+ * Reset form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Reset extends Zend_Form_Element_Submit
+{
+ /**
+ * Use formReset view helper by default
+ * @var string
+ */
+ public $helper = 'formReset';
+}
diff --git a/library/vendor/Zend/Form/Element/Select.php b/library/vendor/Zend/Form/Element/Select.php
new file mode 100644
index 0000000..acf009a
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Select.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Multi */
+
+/**
+ * Select.php form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Select extends Zend_Form_Element_Multi
+{
+ /**
+ * 'multiple' attribute
+ * @var string
+ */
+ public $multiple = false;
+
+ /**
+ * Use formSelect view helper by default
+ * @var string
+ */
+ public $helper = 'formSelect';
+}
diff --git a/library/vendor/Zend/Form/Element/Submit.php b/library/vendor/Zend/Form/Element/Submit.php
new file mode 100644
index 0000000..ad564e0
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Submit.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Submit form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Submit extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Default view helper to use
+ * @var string
+ */
+ public $helper = 'formSubmit';
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $spec Element name or configuration
+ * @param string|array|Zend_Config $options Element value or configuration
+ * @return void
+ */
+ public function __construct($spec, $options = null)
+ {
+ if (is_string($spec) && ((null !== $options) && is_string($options))) {
+ $options = array('label' => $options);
+ }
+
+ if (!isset($options['ignore'])) {
+ $options['ignore'] = true;
+ }
+
+ parent::__construct($spec, $options);
+ }
+
+ /**
+ * Return label
+ *
+ * If no label is present, returns the currently set name.
+ *
+ * If a translator is present, returns the translated label.
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ $value = parent::getLabel();
+
+ if (null === $value) {
+ $value = $this->getName();
+
+ if (null !== ($translator = $this->getTranslator())) {
+ return $translator->translate($value);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Has this submit button been selected?
+ *
+ * @return bool
+ */
+ public function isChecked()
+ {
+ $value = $this->getValue();
+
+ if (empty($value)) {
+ return false;
+ }
+ if ($value != $this->getLabel()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Default decorators
+ *
+ * Uses only 'Submit' and 'DtDdWrapper' decorators by default.
+ *
+ * @return Zend_Form_Element_Submit
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ $this->addDecorator('Tooltip')
+ ->addDecorator('ViewHelper')
+ ->addDecorator('DtDdWrapper');
+ }
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Form/Element/Text.php b/library/vendor/Zend/Form/Element/Text.php
new file mode 100644
index 0000000..1a99b8e
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Text.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Text form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Text extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Default form view helper to use for rendering
+ * @var string
+ */
+ public $helper = 'formText';
+}
diff --git a/library/vendor/Zend/Form/Element/Textarea.php b/library/vendor/Zend/Form/Element/Textarea.php
new file mode 100644
index 0000000..ff4ddd5
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Textarea.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element_Xhtml */
+
+/**
+ * Textarea form element
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_Element_Textarea extends Zend_Form_Element_Xhtml
+{
+ /**
+ * Use formTextarea view helper by default
+ * @var string
+ */
+ public $helper = 'formTextarea';
+}
diff --git a/library/vendor/Zend/Form/Element/Xhtml.php b/library/vendor/Zend/Form/Element/Xhtml.php
new file mode 100644
index 0000000..6ef15e9
--- /dev/null
+++ b/library/vendor/Zend/Form/Element/Xhtml.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form_Element */
+
+/**
+ * Base element for XHTML elements
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @subpackage Element
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Form_Element_Xhtml extends Zend_Form_Element
+{
+}
diff --git a/library/vendor/Zend/Form/Exception.php b/library/vendor/Zend/Form/Exception.php
new file mode 100644
index 0000000..61a1bd5
--- /dev/null
+++ b/library/vendor/Zend/Form/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Exception */
+
+/**
+ * Exception for Zend_Form component.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Form_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Form/SubForm.php b/library/vendor/Zend/Form/SubForm.php
new file mode 100644
index 0000000..36d961e
--- /dev/null
+++ b/library/vendor/Zend/Form/SubForm.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Form */
+
+/**
+ * Zend_Form_SubForm
+ *
+ * @category Zend
+ * @package Zend_Form
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Form_SubForm extends Zend_Form
+{
+ /**
+ * Whether or not form elements are members of an array
+ * @var bool
+ */
+ protected $_isArray = true;
+
+ /**
+ * Load the default decorators
+ *
+ * @return Zend_Form_SubForm
+ */
+ public function loadDefaultDecorators()
+ {
+ if ($this->loadDefaultDecoratorsIsDisabled()) {
+ return $this;
+ }
+
+ $decorators = $this->getDecorators();
+ if (empty($decorators)) {
+ $this->addDecorator('FormElements')
+ ->addDecorator('HtmlTag', array('tag' => 'dl'))
+ ->addDecorator('Fieldset')
+ ->addDecorator('DtDdWrapper');
+ }
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Json.php b/library/vendor/Zend/Json.php
new file mode 100644
index 0000000..b7b97f2
--- /dev/null
+++ b/library/vendor/Zend/Json.php
@@ -0,0 +1,433 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Json_Expr.
+ *
+ * @see Zend_Json_Expr
+ */
+
+/** @see Zend_Xml_Security */
+
+/**
+ * Class for encoding to and decoding from JSON.
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @uses Zend_Json_Expr
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Json
+{
+ /**
+ * How objects should be encoded -- arrays or as StdClass. TYPE_ARRAY is 1
+ * so that it is a boolean true value, allowing it to be used with
+ * ext/json's functions.
+ */
+ const TYPE_ARRAY = 1;
+ const TYPE_OBJECT = 0;
+
+ /**
+ * To check the allowed nesting depth of the XML tree during xml2json conversion.
+ *
+ * @var int
+ */
+ public static $maxRecursionDepthAllowed=25;
+
+ /**
+ * @var bool
+ */
+ public static $useBuiltinEncoderDecoder = false;
+
+ /**
+ * Decodes the given $encodedValue string which is
+ * encoded in the JSON format
+ *
+ * Uses ext/json's json_decode if available.
+ *
+ * @param string $encodedValue Encoded in JSON format
+ * @param int $objectDecodeType Optional; flag indicating how to decode
+ * objects. See {@link Zend_Json_Decoder::decode()} for details.
+ * @return mixed
+ */
+ public static function decode($encodedValue, $objectDecodeType = Zend_Json::TYPE_ARRAY)
+ {
+ $encodedValue = (string) $encodedValue;
+ if (function_exists('json_decode') && self::$useBuiltinEncoderDecoder !== true) {
+ $decode = json_decode($encodedValue, $objectDecodeType);
+
+ // php < 5.3
+ if (!function_exists('json_last_error')) {
+ if (strtolower($encodedValue) === 'null') {
+ return null;
+ } elseif ($decode === null) {
+ throw new Zend_Json_Exception('Decoding failed');
+ }
+ // php >= 5.3
+ } elseif (($jsonLastErr = json_last_error()) != JSON_ERROR_NONE) {
+ switch ($jsonLastErr) {
+ case JSON_ERROR_DEPTH:
+ throw new Zend_Json_Exception('Decoding failed: Maximum stack depth exceeded');
+ case JSON_ERROR_CTRL_CHAR:
+ throw new Zend_Json_Exception('Decoding failed: Unexpected control character found');
+ case JSON_ERROR_SYNTAX:
+ throw new Zend_Json_Exception('Decoding failed: Syntax error');
+ default:
+ throw new Zend_Json_Exception('Decoding failed');
+ }
+ }
+
+ return $decode;
+ }
+
+ return Zend_Json_Decoder::decode($encodedValue, $objectDecodeType);
+ }
+
+ /**
+ * Encode the mixed $valueToEncode into the JSON format
+ *
+ * Encodes using ext/json's json_encode() if available.
+ *
+ * NOTE: Object should not contain cycles; the JSON format
+ * does not allow object reference.
+ *
+ * NOTE: Only public variables will be encoded
+ *
+ * NOTE: Encoding native javascript expressions are possible using Zend_Json_Expr.
+ * You can enable this by setting $options['enableJsonExprFinder'] = true
+ *
+ * @see Zend_Json_Expr
+ *
+ * @param mixed $valueToEncode
+ * @param boolean $cycleCheck Optional; whether or not to check for object recursion; off by default
+ * @param array $options Additional options used during encoding
+ * @return string JSON encoded object
+ */
+ public static function encode($valueToEncode, $cycleCheck = false, $options = array())
+ {
+ if (is_object($valueToEncode)) {
+ if (method_exists($valueToEncode, 'toJson')) {
+ return $valueToEncode->toJson();
+ } elseif (method_exists($valueToEncode, 'toArray')) {
+ return self::encode($valueToEncode->toArray(), $cycleCheck, $options);
+ }
+ }
+
+ // Pre-encoding look for Zend_Json_Expr objects and replacing by tmp ids
+ $javascriptExpressions = array();
+ if(isset($options['enableJsonExprFinder'])
+ && ($options['enableJsonExprFinder'] == true)
+ ) {
+ /**
+ * @see Zend_Json_Encoder
+ */
+ $valueToEncode = self::_recursiveJsonExprFinder($valueToEncode, $javascriptExpressions);
+ }
+
+ // Encoding
+ if (function_exists('json_encode') && self::$useBuiltinEncoderDecoder !== true) {
+ $encodedResult = json_encode($valueToEncode);
+ } else {
+ $encodedResult = Zend_Json_Encoder::encode($valueToEncode, $cycleCheck, $options);
+ }
+
+ //only do post-proccessing to revert back the Zend_Json_Expr if any.
+ if (count($javascriptExpressions) > 0) {
+ $count = count($javascriptExpressions);
+ for($i = 0; $i < $count; $i++) {
+ $magicKey = $javascriptExpressions[$i]['magicKey'];
+ $value = $javascriptExpressions[$i]['value'];
+
+ $encodedResult = str_replace(
+ //instead of replacing "key:magicKey", we replace directly magicKey by value because "key" never changes.
+ '"' . $magicKey . '"',
+ $value,
+ $encodedResult
+ );
+ }
+ }
+
+ return $encodedResult;
+ }
+
+ /**
+ * Check & Replace Zend_Json_Expr for tmp ids in the valueToEncode
+ *
+ * Check if the value is a Zend_Json_Expr, and if replace its value
+ * with a magic key and save the javascript expression in an array.
+ *
+ * NOTE this method is recursive.
+ *
+ * NOTE: This method is used internally by the encode method.
+ *
+ * @see encode
+ * @param array|object|Zend_Json_Expr $value a string - object property to be encoded
+ * @param array $javascriptExpressions
+ * @param null $currentKey
+ *
+ * @internal param mixed $valueToCheck
+ * @return void
+ */
+ protected static function _recursiveJsonExprFinder(&$value, array &$javascriptExpressions, $currentKey = null)
+ {
+ if ($value instanceof Zend_Json_Expr) {
+ // TODO: Optimize with ascii keys, if performance is bad
+ $magicKey = "____" . $currentKey . "_" . (count($javascriptExpressions));
+ $javascriptExpressions[] = array(
+
+ //if currentKey is integer, encodeUnicodeString call is not required.
+ "magicKey" => (is_int($currentKey)) ? $magicKey : Zend_Json_Encoder::encodeUnicodeString($magicKey),
+ "value" => $value->__toString(),
+ );
+ $value = $magicKey;
+ } elseif (is_array($value)) {
+ foreach ($value as $k => $v) {
+ $value[$k] = self::_recursiveJsonExprFinder($value[$k], $javascriptExpressions, $k);
+ }
+ } elseif (is_object($value)) {
+ foreach ($value as $k => $v) {
+ $value->$k = self::_recursiveJsonExprFinder($value->$k, $javascriptExpressions, $k);
+ }
+ }
+ return $value;
+ }
+
+ /**
+ * Return the value of an XML attribute text or the text between
+ * the XML tags
+ *
+ * In order to allow Zend_Json_Expr from xml, we check if the node
+ * matchs the pattern that try to detect if it is a new Zend_Json_Expr
+ * if it matches, we return a new Zend_Json_Expr instead of a text node
+ *
+ * @param SimpleXMLElement $simpleXmlElementObject
+ * @return Zend_Json_Expr|string
+ */
+ protected static function _getXmlValue($simpleXmlElementObject) {
+ $pattern = '/^[\s]*new Zend_Json_Expr[\s]*\([\s]*[\"\']{1}(.*)[\"\']{1}[\s]*\)[\s]*$/';
+ $matchings = array();
+ $match = preg_match ($pattern, $simpleXmlElementObject, $matchings);
+ if ($match) {
+ return new Zend_Json_Expr($matchings[1]);
+ } else {
+ return (trim(strval($simpleXmlElementObject)));
+ }
+ }
+ /**
+ * _processXml - Contains the logic for xml2json
+ *
+ * The logic in this function is a recursive one.
+ *
+ * The main caller of this function (i.e. fromXml) needs to provide
+ * only the first two parameters i.e. the SimpleXMLElement object and
+ * the flag for ignoring or not ignoring XML attributes. The third parameter
+ * will be used internally within this function during the recursive calls.
+ *
+ * This function converts the SimpleXMLElement object into a PHP array by
+ * calling a recursive (protected static) function in this class. Once all
+ * the XML elements are stored in the PHP array, it is returned to the caller.
+ *
+ * Throws a Zend_Json_Exception if the XML tree is deeper than the allowed limit.
+ *
+ * @param SimpleXMLElement $simpleXmlElementObject
+ * @param boolean $ignoreXmlAttributes
+ * @param integer $recursionDepth
+ * @return array
+ */
+ protected static function _processXml($simpleXmlElementObject, $ignoreXmlAttributes, $recursionDepth=0)
+ {
+ // Keep an eye on how deeply we are involved in recursion.
+ if ($recursionDepth > self::$maxRecursionDepthAllowed) {
+ // XML tree is too deep. Exit now by throwing an exception.
+ throw new Zend_Json_Exception(
+ "Function _processXml exceeded the allowed recursion depth of " .
+ self::$maxRecursionDepthAllowed);
+ } // End of if ($recursionDepth > self::$maxRecursionDepthAllowed)
+
+ $children = $simpleXmlElementObject->children();
+ $name = $simpleXmlElementObject->getName();
+ $value = self::_getXmlValue($simpleXmlElementObject);
+ $attributes = (array) $simpleXmlElementObject->attributes();
+
+ if (count($children) == 0) {
+ if (!empty($attributes) && !$ignoreXmlAttributes) {
+ foreach ($attributes['@attributes'] as $k => $v) {
+ $attributes['@attributes'][$k]= self::_getXmlValue($v);
+ }
+ if (!empty($value)) {
+ $attributes['@text'] = $value;
+ }
+ return array($name => $attributes);
+ } else {
+ return array($name => $value);
+ }
+ } else {
+ $childArray= array();
+ foreach ($children as $child) {
+ $childname = $child->getName();
+ $element = self::_processXml($child,$ignoreXmlAttributes,$recursionDepth+1);
+ if (array_key_exists($childname, $childArray)) {
+ if (empty($subChild[$childname])) {
+ $childArray[$childname] = array($childArray[$childname]);
+ $subChild[$childname] = true;
+ }
+ $childArray[$childname][] = $element[$childname];
+ } else {
+ $childArray[$childname] = $element[$childname];
+ }
+ }
+ if (!empty($attributes) && !$ignoreXmlAttributes) {
+ foreach ($attributes['@attributes'] as $k => $v) {
+ $attributes['@attributes'][$k] = self::_getXmlValue($v);
+ }
+ $childArray['@attributes'] = $attributes['@attributes'];
+ }
+ if (!empty($value)) {
+ $childArray['@text'] = $value;
+ }
+ return array($name => $childArray);
+ }
+ }
+
+ /**
+ * fromXml - Converts XML to JSON
+ *
+ * Converts a XML formatted string into a JSON formatted string.
+ * The value returned will be a string in JSON format.
+ *
+ * The caller of this function needs to provide only the first parameter,
+ * which is an XML formatted String. The second parameter is optional, which
+ * lets the user to select if the XML attributes in the input XML string
+ * should be included or ignored in xml2json conversion.
+ *
+ * This function converts the XML formatted string into a PHP array by
+ * calling a recursive (protected static) function in this class. Then, it
+ * converts that PHP array into JSON by calling the "encode" static funcion.
+ *
+ * Throws a Zend_Json_Exception if the input not a XML formatted string.
+ * NOTE: Encoding native javascript expressions via Zend_Json_Expr is not possible.
+ *
+ * @static
+ * @access public
+ * @param string $xmlStringContents XML String to be converted
+ * @param boolean $ignoreXmlAttributes Include or exclude XML attributes in
+ * the xml2json conversion process.
+ * @return mixed - JSON formatted string on success
+ * @throws Zend_Json_Exception
+ */
+ public static function fromXml($xmlStringContents, $ignoreXmlAttributes=true)
+ {
+ // Load the XML formatted string into a Simple XML Element object.
+ $simpleXmlElementObject = Zend_Xml_Security::scan($xmlStringContents);
+
+ // If it is not a valid XML content, throw an exception.
+ if ($simpleXmlElementObject == null) {
+ throw new Zend_Json_Exception('Function fromXml was called with an invalid XML formatted string.');
+ } // End of if ($simpleXmlElementObject == null)
+
+ $resultArray = null;
+
+ // Call the recursive function to convert the XML into a PHP array.
+ $resultArray = self::_processXml($simpleXmlElementObject, $ignoreXmlAttributes);
+
+ // Convert the PHP array to JSON using Zend_Json encode method.
+ // It is just that simple.
+ $jsonStringOutput = self::encode($resultArray);
+ return($jsonStringOutput);
+ }
+
+
+
+ /**
+ * Pretty-print JSON string
+ *
+ * Use 'format' option to select output format - currently html and txt supported, txt is default
+ * Use 'indent' option to override the indentation string set in the format - by default for the 'txt' format it's a tab
+ *
+ * @param string $json Original JSON string
+ * @param array $options Encoding options
+ * @return string
+ */
+ public static function prettyPrint($json, $options = array())
+ {
+ $tokens = preg_split('|([\{\}\]\[,])|', $json, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $result = '';
+ $indent = 0;
+
+ $format= 'txt';
+
+ $ind = "\t";
+
+ if (isset($options['format'])) {
+ $format = $options['format'];
+ }
+
+ switch ($format) {
+ case 'html':
+ $lineBreak = '<br />';
+ $ind = '&nbsp;&nbsp;&nbsp;&nbsp;';
+ break;
+ default:
+ case 'txt':
+ $lineBreak = "\n";
+ $ind = "\t";
+ break;
+ }
+
+ // override the defined indent setting with the supplied option
+ if (isset($options['indent'])) {
+ $ind = $options['indent'];
+ }
+
+ $inLiteral = false;
+ foreach($tokens as $token) {
+ if($token == '') {
+ continue;
+ }
+
+ $prefix = str_repeat($ind, $indent);
+ if (!$inLiteral && ($token == '{' || $token == '[')) {
+ $indent++;
+ if (($result != '') && ($result[(strlen($result)-1)] == $lineBreak)) {
+ $result .= $prefix;
+ }
+ $result .= $token . $lineBreak;
+ } elseif (!$inLiteral && ($token == '}' || $token == ']')) {
+ $indent--;
+ $prefix = str_repeat($ind, $indent);
+ $result .= $lineBreak . $prefix . $token;
+ } elseif (!$inLiteral && $token == ',') {
+ $result .= $token . $lineBreak;
+ } else {
+ $result .= ( $inLiteral ? '' : $prefix ) . $token;
+
+ // Count # of unescaped double-quotes in token, subtract # of
+ // escaped double-quotes and if the result is odd then we are
+ // inside a string literal
+ if ((substr_count($token, "\"")-substr_count($token, "\\\"")) % 2 != 0) {
+ $inLiteral = !$inLiteral;
+ }
+ }
+ }
+ return $result;
+ }
+}
diff --git a/library/vendor/Zend/Json/Decoder.php b/library/vendor/Zend/Json/Decoder.php
new file mode 100644
index 0000000..0a06016
--- /dev/null
+++ b/library/vendor/Zend/Json/Decoder.php
@@ -0,0 +1,570 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Json
+ */
+
+/**
+ * Decode JSON encoded string to PHP variable constructs
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Json_Decoder
+{
+ /**
+ * Parse tokens used to decode the JSON object. These are not
+ * for public consumption, they are just used internally to the
+ * class.
+ */
+ const EOF = 0;
+ const DATUM = 1;
+ const LBRACE = 2;
+ const LBRACKET = 3;
+ const RBRACE = 4;
+ const RBRACKET = 5;
+ const COMMA = 6;
+ const COLON = 7;
+
+ /**
+ * Use to maintain a "pointer" to the source being decoded
+ *
+ * @var string
+ */
+ protected $_source;
+
+ /**
+ * Caches the source length
+ *
+ * @var int
+ */
+ protected $_sourceLength;
+
+ /**
+ * The offset within the souce being decoded
+ *
+ * @var int
+ *
+ */
+ protected $_offset;
+
+ /**
+ * The current token being considered in the parser cycle
+ *
+ * @var int
+ */
+ protected $_token;
+
+ /**
+ * Flag indicating how objects should be decoded
+ *
+ * @var int
+ * @access protected
+ */
+ protected $_decodeType;
+
+ /**
+ * Constructor
+ *
+ * @param string $source String source to decode
+ * @param int $decodeType How objects should be decoded -- see
+ * {@link Zend_Json::TYPE_ARRAY} and {@link Zend_Json::TYPE_OBJECT} for
+ * valid values
+ * @return void
+ */
+ protected function __construct($source, $decodeType)
+ {
+ // Set defaults
+ $this->_source = self::decodeUnicodeString($source);
+ $this->_sourceLength = strlen($this->_source);
+ $this->_token = self::EOF;
+ $this->_offset = 0;
+
+ // Normalize and set $decodeType
+ if (!in_array($decodeType, array(Zend_Json::TYPE_ARRAY, Zend_Json::TYPE_OBJECT)))
+ {
+ $decodeType = Zend_Json::TYPE_ARRAY;
+ }
+ $this->_decodeType = $decodeType;
+
+ // Set pointer at first token
+ $this->_getNextToken();
+ }
+
+ /**
+ * Decode a JSON source string
+ *
+ * Decodes a JSON encoded string. The value returned will be one of the
+ * following:
+ * - integer
+ * - float
+ * - boolean
+ * - null
+ * - StdClass
+ * - array
+ * - array of one or more of the above types
+ *
+ * By default, decoded objects will be returned as associative arrays; to
+ * return a StdClass object instead, pass {@link Zend_Json::TYPE_OBJECT} to
+ * the $objectDecodeType parameter.
+ *
+ * Throws a Zend_Json_Exception if the source string is null.
+ *
+ * @static
+ * @access public
+ * @param string $source String to be decoded
+ * @param int $objectDecodeType How objects should be decoded; should be
+ * either or {@link Zend_Json::TYPE_ARRAY} or
+ * {@link Zend_Json::TYPE_OBJECT}; defaults to TYPE_ARRAY
+ * @return mixed
+ * @throws Zend_Json_Exception
+ */
+ public static function decode($source = null, $objectDecodeType = Zend_Json::TYPE_ARRAY)
+ {
+ if (null === $source) {
+ throw new Zend_Json_Exception('Must specify JSON encoded source for decoding');
+ } elseif (!is_string($source)) {
+ throw new Zend_Json_Exception('Can only decode JSON encoded strings');
+ }
+
+ $decoder = new self($source, $objectDecodeType);
+
+ return $decoder->_decodeValue();
+ }
+
+
+ /**
+ * Recursive driving rountine for supported toplevel tops
+ *
+ * @return mixed
+ */
+ protected function _decodeValue()
+ {
+ switch ($this->_token) {
+ case self::DATUM:
+ $result = $this->_tokenValue;
+ $this->_getNextToken();
+ return($result);
+ break;
+ case self::LBRACE:
+ return($this->_decodeObject());
+ break;
+ case self::LBRACKET:
+ return($this->_decodeArray());
+ break;
+ default:
+ return null;
+ break;
+ }
+ }
+
+ /**
+ * Decodes an object of the form:
+ * { "attribute: value, "attribute2" : value,...}
+ *
+ * If Zend_Json_Encoder was used to encode the original object then
+ * a special attribute called __className which specifies a class
+ * name that should wrap the data contained within the encoded source.
+ *
+ * Decodes to either an array or StdClass object, based on the value of
+ * {@link $_decodeType}. If invalid $_decodeType present, returns as an
+ * array.
+ *
+ * @return array|StdClass
+ */
+ protected function _decodeObject()
+ {
+ $members = array();
+ $tok = $this->_getNextToken();
+
+ while ($tok && $tok != self::RBRACE) {
+ if ($tok != self::DATUM || ! is_string($this->_tokenValue)) {
+ throw new Zend_Json_Exception('Missing key in object encoding: ' . $this->_source);
+ }
+
+ $key = $this->_tokenValue;
+ $tok = $this->_getNextToken();
+
+ if ($tok != self::COLON) {
+ throw new Zend_Json_Exception('Missing ":" in object encoding: ' . $this->_source);
+ }
+
+ $tok = $this->_getNextToken();
+ $members[$key] = $this->_decodeValue();
+ $tok = $this->_token;
+
+ if ($tok == self::RBRACE) {
+ break;
+ }
+
+ if ($tok != self::COMMA) {
+ throw new Zend_Json_Exception('Missing "," in object encoding: ' . $this->_source);
+ }
+
+ $tok = $this->_getNextToken();
+ }
+
+ switch ($this->_decodeType) {
+ case Zend_Json::TYPE_OBJECT:
+ // Create new StdClass and populate with $members
+ $result = new StdClass();
+ foreach ($members as $key => $value) {
+ if ($key === '') {
+ $key = '_empty_';
+ }
+ $result->$key = $value;
+ }
+ break;
+ case Zend_Json::TYPE_ARRAY:
+ default:
+ $result = $members;
+ break;
+ }
+
+ $this->_getNextToken();
+ return $result;
+ }
+
+ /**
+ * Decodes a JSON array format:
+ * [element, element2,...,elementN]
+ *
+ * @return array
+ */
+ protected function _decodeArray()
+ {
+ $result = array();
+ $starttok = $tok = $this->_getNextToken(); // Move past the '['
+ $index = 0;
+
+ while ($tok && $tok != self::RBRACKET) {
+ $result[$index++] = $this->_decodeValue();
+
+ $tok = $this->_token;
+
+ if ($tok == self::RBRACKET || !$tok) {
+ break;
+ }
+
+ if ($tok != self::COMMA) {
+ throw new Zend_Json_Exception('Missing "," in array encoding: ' . $this->_source);
+ }
+
+ $tok = $this->_getNextToken();
+ }
+
+ $this->_getNextToken();
+ return($result);
+ }
+
+
+ /**
+ * Removes whitepsace characters from the source input
+ */
+ protected function _eatWhitespace()
+ {
+ if (preg_match(
+ '/([\t\b\f\n\r ])*/s',
+ $this->_source,
+ $matches,
+ PREG_OFFSET_CAPTURE,
+ $this->_offset)
+ && $matches[0][1] == $this->_offset)
+ {
+ $this->_offset += strlen($matches[0][0]);
+ }
+ }
+
+
+ /**
+ * Retrieves the next token from the source stream
+ *
+ * @return int Token constant value specified in class definition
+ */
+ protected function _getNextToken()
+ {
+ $this->_token = self::EOF;
+ $this->_tokenValue = null;
+ $this->_eatWhitespace();
+
+ if ($this->_offset >= $this->_sourceLength) {
+ return(self::EOF);
+ }
+
+ $str = $this->_source;
+ $str_length = $this->_sourceLength;
+ $i = $this->_offset;
+ $start = $i;
+
+ switch ($str[$i]) {
+ case '{':
+ $this->_token = self::LBRACE;
+ break;
+ case '}':
+ $this->_token = self::RBRACE;
+ break;
+ case '[':
+ $this->_token = self::LBRACKET;
+ break;
+ case ']':
+ $this->_token = self::RBRACKET;
+ break;
+ case ',':
+ $this->_token = self::COMMA;
+ break;
+ case ':':
+ $this->_token = self::COLON;
+ break;
+ case '"':
+ $result = '';
+ do {
+ $i++;
+ if ($i >= $str_length) {
+ break;
+ }
+
+ $chr = $str[$i];
+
+ if ($chr == '\\') {
+ $i++;
+ if ($i >= $str_length) {
+ break;
+ }
+ $chr = $str[$i];
+ switch ($chr) {
+ case '"' :
+ $result .= '"';
+ break;
+ case '\\':
+ $result .= '\\';
+ break;
+ case '/' :
+ $result .= '/';
+ break;
+ case 'b' :
+ $result .= "\x08";
+ break;
+ case 'f' :
+ $result .= "\x0c";
+ break;
+ case 'n' :
+ $result .= "\x0a";
+ break;
+ case 'r' :
+ $result .= "\x0d";
+ break;
+ case 't' :
+ $result .= "\x09";
+ break;
+ case '\'' :
+ $result .= '\'';
+ break;
+ default:
+ throw new Zend_Json_Exception("Illegal escape "
+ . "sequence '" . $chr . "'");
+ }
+ } elseif($chr == '"') {
+ break;
+ } else {
+ $result .= $chr;
+ }
+ } while ($i < $str_length);
+
+ $this->_token = self::DATUM;
+ //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1);
+ $this->_tokenValue = $result;
+ break;
+ case 't':
+ if (($i+ 3) < $str_length && substr($str, $start, 4) == "true") {
+ $this->_token = self::DATUM;
+ }
+ $this->_tokenValue = true;
+ $i += 3;
+ break;
+ case 'f':
+ if (($i+ 4) < $str_length && substr($str, $start, 5) == "false") {
+ $this->_token = self::DATUM;
+ }
+ $this->_tokenValue = false;
+ $i += 4;
+ break;
+ case 'n':
+ if (($i+ 3) < $str_length && substr($str, $start, 4) == "null") {
+ $this->_token = self::DATUM;
+ }
+ $this->_tokenValue = NULL;
+ $i += 3;
+ break;
+ }
+
+ if ($this->_token != self::EOF) {
+ $this->_offset = $i + 1; // Consume the last token character
+ return($this->_token);
+ }
+
+ $chr = $str[$i];
+ if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) {
+ if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s',
+ $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) {
+
+ $datum = $matches[0][0];
+
+ if (is_numeric($datum)) {
+ if (preg_match('/^0\d+$/', $datum)) {
+ throw new Zend_Json_Exception("Octal notation not supported by JSON (value: $datum)");
+ } else {
+ $val = intval($datum);
+ $fVal = floatval($datum);
+ $this->_tokenValue = ($val == $fVal ? $val : $fVal);
+ }
+ } else {
+ throw new Zend_Json_Exception("Illegal number format: $datum");
+ }
+
+ $this->_token = self::DATUM;
+ $this->_offset = $start + strlen($datum);
+ }
+ } else {
+ throw new Zend_Json_Exception('Illegal Token');
+ }
+
+ return($this->_token);
+ }
+
+ /**
+ * Decode Unicode Characters from \u0000 ASCII syntax.
+ *
+ * This algorithm was originally developed for the
+ * Solar Framework by Paul M. Jones
+ *
+ * @link http://solarphp.com/
+ * @link http://svn.solarphp.com/core/trunk/Solar/Json.php
+ * @param string $value
+ * @return string
+ */
+ public static function decodeUnicodeString($chrs)
+ {
+ $delim = substr($chrs, 0, 1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for($i = 0; $i < $strlen_chrs; $i++) {
+
+ $substr_chrs_c_2 = substr($chrs, $i, 2);
+ $ord_chrs_c = ord($chrs[$i]);
+
+ switch (true) {
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $i, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($i + 2), 2)))
+ . chr(hexdec(substr($chrs, ($i + 4), 2)));
+ $utf8 .= self::_utf162utf8($utf16);
+ $i += 5;
+ break;
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs[$i];
+ break;
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $i, 2);
+ ++$i;
+ break;
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $i, 3);
+ $i += 2;
+ break;
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $i, 4);
+ $i += 3;
+ break;
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $i, 5);
+ $i += 4;
+ break;
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $i, 6);
+ $i += 5;
+ break;
+ }
+ }
+
+ return $utf8;
+ }
+
+ /**
+ * Convert a string from one UTF-16 char to one UTF-8 char.
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * This method is from the Solar Framework by Paul M. Jones
+ *
+ * @link http://solarphp.com
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ */
+ protected static function _utf162utf8($utf16)
+ {
+ // Check for mb extension otherwise do by hand.
+ if( function_exists('mb_convert_encoding') ) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
+
+ switch (true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+}
+
diff --git a/library/vendor/Zend/Json/Encoder.php b/library/vendor/Zend/Json/Encoder.php
new file mode 100644
index 0000000..57acb35
--- /dev/null
+++ b/library/vendor/Zend/Json/Encoder.php
@@ -0,0 +1,576 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Encode PHP constructs to JSON
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Json_Encoder
+{
+ /**
+ * Whether or not to check for possible cycling
+ *
+ * @var boolean
+ */
+ protected $_cycleCheck;
+
+ /**
+ * Additional options used during encoding
+ *
+ * @var array
+ */
+ protected $_options = array();
+
+ /**
+ * Array of visited objects; used to prevent cycling.
+ *
+ * @var array
+ */
+ protected $_visited = array();
+
+ /**
+ * Constructor
+ *
+ * @param boolean $cycleCheck Whether or not to check for recursion when encoding
+ * @param array $options Additional options used during encoding
+ * @return void
+ */
+ protected function __construct($cycleCheck = false, $options = array())
+ {
+ $this->_cycleCheck = $cycleCheck;
+ $this->_options = $options;
+ }
+
+ /**
+ * Use the JSON encoding scheme for the value specified
+ *
+ * @param mixed $value The value to be encoded
+ * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding
+ * @param array $options Additional options used during encoding
+ * @return string The encoded value
+ */
+ public static function encode($value, $cycleCheck = false, $options = array())
+ {
+ $encoder = new self(($cycleCheck) ? true : false, $options);
+ return $encoder->_encodeValue($value);
+ }
+
+ /**
+ * Recursive driver which determines the type of value to be encoded
+ * and then dispatches to the appropriate method. $values are either
+ * - objects (returns from {@link _encodeObject()})
+ * - arrays (returns from {@link _encodeArray()})
+ * - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
+ *
+ * @param mixed $value The value to be encoded
+ * @return string Encoded value
+ */
+ protected function _encodeValue(&$value)
+ {
+ if (is_object($value)) {
+ return $this->_encodeObject($value);
+ } else if (is_array($value)) {
+ return $this->_encodeArray($value);
+ }
+
+ return $this->_encodeDatum($value);
+ }
+
+
+
+ /**
+ * Encode an object to JSON by encoding each of the public properties
+ *
+ * A special property is added to the JSON object called '__className'
+ * that contains the name of the class of $value. This is used to decode
+ * the object on the client into a specific class.
+ *
+ * @param object $value
+ * @return string
+ * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously
+ */
+ protected function _encodeObject(&$value)
+ {
+ if ($this->_cycleCheck) {
+ if ($this->_wasVisited($value)) {
+
+ if (isset($this->_options['silenceCyclicalExceptions'])
+ && $this->_options['silenceCyclicalExceptions']===true) {
+
+ return '"* RECURSION (' . get_class($value) . ') *"';
+
+ } else {
+ throw new Zend_Json_Exception(
+ 'Cycles not supported in JSON encoding, cycle introduced by '
+ . 'class "' . get_class($value) . '"'
+ );
+ }
+ }
+
+ $this->_visited[] = $value;
+ }
+
+ $props = '';
+ if (method_exists($value, 'toJson')) {
+ $props =',' . preg_replace("/^\{(.*)\}$/","\\1",$value->toJson());
+ } else {
+ if ($value instanceof IteratorAggregate) {
+ $propCollection = $value->getIterator();
+ } elseif ($value instanceof Iterator) {
+ $propCollection = $value;
+ } else {
+ $propCollection = get_object_vars($value);
+ }
+
+ foreach ($propCollection as $name => $propValue) {
+ if (isset($propValue)) {
+ $props .= ','
+ . $this->_encodeString($name)
+ . ':'
+ . $this->_encodeValue($propValue);
+ }
+ }
+ }
+ $className = get_class($value);
+ return '{"__className":' . $this->_encodeString($className)
+ . $props . '}';
+ }
+
+
+ /**
+ * Determine if an object has been serialized already
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ protected function _wasVisited(&$value)
+ {
+ if (in_array($value, $this->_visited, true)) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * JSON encode an array value
+ *
+ * Recursively encodes each value of an array and returns a JSON encoded
+ * array string.
+ *
+ * Arrays are defined as integer-indexed arrays starting at index 0, where
+ * the last index is (count($array) -1); any deviation from that is
+ * considered an associative array, and will be encoded as such.
+ *
+ * @param array& $array
+ * @return string
+ */
+ protected function _encodeArray(&$array)
+ {
+ $tmpArray = array();
+
+ // Check for associative array
+ if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
+ // Associative array
+ $result = '{';
+ foreach ($array as $key => $value) {
+ $key = (string) $key;
+ $tmpArray[] = $this->_encodeString($key)
+ . ':'
+ . $this->_encodeValue($value);
+ }
+ $result .= implode(',', $tmpArray);
+ $result .= '}';
+ } else {
+ // Indexed array
+ $result = '[';
+ $length = count($array);
+ for ($i = 0; $i < $length; $i++) {
+ $tmpArray[] = $this->_encodeValue($array[$i]);
+ }
+ $result .= implode(',', $tmpArray);
+ $result .= ']';
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * JSON encode a basic data type (string, number, boolean, null)
+ *
+ * If value type is not a string, number, boolean, or null, the string
+ * 'null' is returned.
+ *
+ * @param mixed& $value
+ * @return string
+ */
+ protected function _encodeDatum(&$value)
+ {
+ $result = 'null';
+
+ if (is_int($value) || is_float($value)) {
+ $result = (string) $value;
+ $result = str_replace(",", ".", $result);
+ } elseif (is_string($value)) {
+ $result = $this->_encodeString($value);
+ } elseif (is_bool($value)) {
+ $result = $value ? 'true' : 'false';
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * JSON encode a string value by escaping characters as necessary
+ *
+ * @param string& $value
+ * @return string
+ */
+ protected function _encodeString(&$string)
+ {
+ // Escape these characters with a backslash:
+ // " \ / \n \r \t \b \f
+ $search = array('\\', "\n", "\t", "\r", "\b", "\f", '"', '/');
+ $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"', '\\/');
+ $string = str_replace($search, $replace, $string);
+
+ // Escape certain ASCII characters:
+ // 0x08 => \b
+ // 0x0c => \f
+ $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);
+ $string = self::encodeUnicodeString($string);
+
+ return '"' . $string . '"';
+ }
+
+
+ /**
+ * Encode the constants associated with the ReflectionClass
+ * parameter. The encoding format is based on the class2 format
+ *
+ * @param ReflectionClass $cls
+ * @return string Encoded constant block in class2 format
+ */
+ private static function _encodeConstants(ReflectionClass $cls)
+ {
+ $result = "constants : {";
+ $constants = $cls->getConstants();
+
+ $tmpArray = array();
+ if (!empty($constants)) {
+ foreach ($constants as $key => $value) {
+ $tmpArray[] = "$key: " . self::encode($value);
+ }
+
+ $result .= implode(', ', $tmpArray);
+ }
+
+ return $result . "}";
+ }
+
+
+ /**
+ * Encode the public methods of the ReflectionClass in the
+ * class2 format
+ *
+ * @param ReflectionClass $cls
+ * @return string Encoded method fragment
+ *
+ */
+ private static function _encodeMethods(ReflectionClass $cls)
+ {
+ $methods = $cls->getMethods();
+ $result = 'methods:{';
+
+ $started = false;
+ foreach ($methods as $method) {
+ if (! $method->isPublic() || !$method->isUserDefined()) {
+ continue;
+ }
+
+ if ($started) {
+ $result .= ',';
+ }
+ $started = true;
+
+ $result .= '' . $method->getName(). ':function(';
+
+ if ('__construct' != $method->getName()) {
+ $parameters = $method->getParameters();
+ $paramCount = count($parameters);
+ $argsStarted = false;
+
+ $argNames = "var argNames=[";
+ foreach ($parameters as $param) {
+ if ($argsStarted) {
+ $result .= ',';
+ }
+
+ $result .= $param->getName();
+
+ if ($argsStarted) {
+ $argNames .= ',';
+ }
+
+ $argNames .= '"' . $param->getName() . '"';
+
+ $argsStarted = true;
+ }
+ $argNames .= "];";
+
+ $result .= "){"
+ . $argNames
+ . 'var result = ZAjaxEngine.invokeRemoteMethod('
+ . "this, '" . $method->getName()
+ . "',argNames,arguments);"
+ . 'return(result);}';
+ } else {
+ $result .= "){}";
+ }
+ }
+
+ return $result . "}";
+ }
+
+
+ /**
+ * Encode the public properties of the ReflectionClass in the class2
+ * format.
+ *
+ * @param ReflectionClass $cls
+ * @return string Encode properties list
+ *
+ */
+ private static function _encodeVariables(ReflectionClass $cls)
+ {
+ $properties = $cls->getProperties();
+ $propValues = get_class_vars($cls->getName());
+ $result = "variables:{";
+ $cnt = 0;
+
+ $tmpArray = array();
+ foreach ($properties as $prop) {
+ if (! $prop->isPublic()) {
+ continue;
+ }
+
+ $tmpArray[] = $prop->getName()
+ . ':'
+ . self::encode($propValues[$prop->getName()]);
+ }
+ $result .= implode(',', $tmpArray);
+
+ return $result . "}";
+ }
+
+ /**
+ * Encodes the given $className into the class2 model of encoding PHP
+ * classes into JavaScript class2 classes.
+ * NOTE: Currently only public methods and variables are proxied onto
+ * the client machine
+ *
+ * @param string $className The name of the class, the class must be
+ * instantiable using a null constructor
+ * @param string $package Optional package name appended to JavaScript
+ * proxy class name
+ * @return string The class2 (JavaScript) encoding of the class
+ * @throws Zend_Json_Exception
+ */
+ public static function encodeClass($className, $package = '')
+ {
+ $cls = new ReflectionClass($className);
+ if (! $cls->isInstantiable()) {
+ throw new Zend_Json_Exception("$className must be instantiable");
+ }
+
+ return "Class.create('$package$className',{"
+ . self::_encodeConstants($cls) .","
+ . self::_encodeMethods($cls) .","
+ . self::_encodeVariables($cls) .'});';
+ }
+
+
+ /**
+ * Encode several classes at once
+ *
+ * Returns JSON encoded classes, using {@link encodeClass()}.
+ *
+ * @param array $classNames
+ * @param string $package
+ * @return string
+ */
+ public static function encodeClasses(array $classNames, $package = '')
+ {
+ $result = '';
+ foreach ($classNames as $className) {
+ $result .= self::encodeClass($className, $package);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Encode Unicode Characters to \u0000 ASCII syntax.
+ *
+ * This algorithm was originally developed for the
+ * Solar Framework by Paul M. Jones
+ *
+ * @link http://solarphp.com/
+ * @link http://svn.solarphp.com/core/trunk/Solar/Json.php
+ * @param string $value
+ * @return string
+ */
+ public static function encodeUnicodeString($value)
+ {
+ $strlen_var = strlen($value);
+ $ascii = "";
+
+ /**
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for($i = 0; $i < $strlen_var; $i++) {
+ $ord_var_c = ord($value[$i]);
+
+ switch (true) {
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $value[$i];
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($value[$i + 1]));
+ $i += 1;
+ $utf16 = self::_utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($value[$i + 1]),
+ ord($value[$i + 2]));
+ $i += 2;
+ $utf16 = self::_utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($value[$i + 1]),
+ ord($value[$i + 2]),
+ ord($value[$i + 3]));
+ $i += 3;
+ $utf16 = self::_utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($value[$i + 1]),
+ ord($value[$i + 2]),
+ ord($value[$i + 3]),
+ ord($value[$i + 4]));
+ $i += 4;
+ $utf16 = self::_utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($value[$i + 1]),
+ ord($value[$i + 2]),
+ ord($value[$i + 3]),
+ ord($value[$i + 4]),
+ ord($value[$i + 5]));
+ $i += 5;
+ $utf16 = self::_utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ return $ascii;
+ }
+
+ /**
+ * Convert a string from one UTF-8 char to one UTF-16 char.
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * This method is from the Solar Framework by Paul M. Jones
+ *
+ * @link http://solarphp.com
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ */
+ protected static function _utf82utf16($utf8)
+ {
+ // Check for mb extension otherwise do by hand.
+ if( function_exists('mb_convert_encoding') ) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch (strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8[0]) >> 2))
+ . chr((0xC0 & (ord($utf8[0]) << 6))
+ | (0x3F & ord($utf8[1])));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8[0]) << 4))
+ | (0x0F & (ord($utf8[1]) >> 2)))
+ . chr((0xC0 & (ord($utf8[1]) << 6))
+ | (0x7F & ord($utf8[2])));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+}
+
diff --git a/library/vendor/Zend/Json/Exception.php b/library/vendor/Zend/Json/Exception.php
new file mode 100644
index 0000000..5b4a0db
--- /dev/null
+++ b/library/vendor/Zend/Json/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Json
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Json_Exception extends Zend_Exception
+{}
+
diff --git a/library/vendor/Zend/Json/Expr.php b/library/vendor/Zend/Json/Expr.php
new file mode 100644
index 0000000..1fd5f23
--- /dev/null
+++ b/library/vendor/Zend/Json/Expr.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @subpackage Expr
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Class for Zend_Json encode method.
+ *
+ * This class simply holds a string with a native Javascript Expression,
+ * so objects | arrays to be encoded with Zend_Json can contain native
+ * Javascript Expressions.
+ *
+ * Example:
+ * <code>
+ * $foo = array(
+ * 'integer' =>9,
+ * 'string' =>'test string',
+ * 'function' => Zend_Json_Expr(
+ * 'function(){ window.alert("javascript function encoded by Zend_Json") }'
+ * ),
+ * );
+ *
+ * Zend_Json::encode($foo, false, array('enableJsonExprFinder' => true));
+ * // it will returns json encoded string:
+ * // {"integer":9,"string":"test string","function":function(){window.alert("javascript function encoded by Zend_Json")}}
+ * </code>
+ *
+ * @category Zend
+ * @package Zend_Json
+ * @subpackage Expr
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Json_Expr
+{
+ /**
+ * Storage for javascript expression.
+ *
+ * @var string
+ */
+ protected $_expression;
+
+ /**
+ * Constructor
+ *
+ * @param string $expression the expression to hold.
+ * @return void
+ */
+ public function __construct($expression)
+ {
+ $this->_expression = (string) $expression;
+ }
+
+ /**
+ * Cast to string
+ *
+ * @return string holded javascript expression.
+ */
+ public function __toString()
+ {
+ return $this->_expression;
+ }
+}
diff --git a/library/vendor/Zend/LICENSE.txt b/library/vendor/Zend/LICENSE.txt
new file mode 100644
index 0000000..6eab5aa
--- /dev/null
+++ b/library/vendor/Zend/LICENSE.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2005-2015, Zend Technologies USA, Inc.
+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 Zend Technologies USA, Inc. 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 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 OWNER 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/library/vendor/Zend/Layout.php b/library/vendor/Zend/Layout.php
new file mode 100644
index 0000000..a123e44
--- /dev/null
+++ b/library/vendor/Zend/Layout.php
@@ -0,0 +1,788 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Layout
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Provide Layout support for MVC applications
+ *
+ * @category Zend
+ * @package Zend_Layout
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Layout
+{
+ /**
+ * Placeholder container for layout variables
+ * @var Zend_View_Helper_Placeholder_Container
+ */
+ protected $_container;
+
+ /**
+ * Key used to store content from 'default' named response segment
+ * @var string
+ */
+ protected $_contentKey = 'content';
+
+ /**
+ * Are layouts enabled?
+ * @var bool
+ */
+ protected $_enabled = true;
+
+ /**
+ * Helper class
+ * @var string
+ */
+ protected $_helperClass = 'Zend_Layout_Controller_Action_Helper_Layout';
+
+ /**
+ * Inflector used to resolve layout script
+ * @var Zend_Filter_Inflector
+ */
+ protected $_inflector;
+
+ /**
+ * Flag: is inflector enabled?
+ * @var bool
+ */
+ protected $_inflectorEnabled = true;
+
+ /**
+ * Inflector target
+ * @var string
+ */
+ protected $_inflectorTarget = ':script.:suffix';
+
+ /**
+ * Layout view
+ * @var string
+ */
+ protected $_layout = 'layout';
+
+ /**
+ * Layout view script path
+ * @var string
+ */
+ protected $_viewScriptPath = null;
+
+ protected $_viewBasePath = null;
+ protected $_viewBasePrefix = 'Layout_View';
+
+ /**
+ * Flag: is MVC integration enabled?
+ * @var bool
+ */
+ protected $_mvcEnabled = true;
+
+ /**
+ * Instance registered with MVC, if any
+ * @var Zend_Layout
+ */
+ protected static $_mvcInstance;
+
+ /**
+ * Flag: is MVC successful action only flag set?
+ * @var bool
+ */
+ protected $_mvcSuccessfulActionOnly = true;
+
+ /**
+ * Plugin class
+ * @var string
+ */
+ protected $_pluginClass = 'Zend_Layout_Controller_Plugin_Layout';
+
+ /**
+ * @var Zend_View_Interface
+ */
+ protected $_view;
+
+ /**
+ * View script suffix for layout script
+ * @var string
+ */
+ protected $_viewSuffix = 'phtml';
+
+ /**
+ * Constructor
+ *
+ * Accepts either:
+ * - A string path to layouts
+ * - An array of options
+ * - A Zend_Config object with options
+ *
+ * Layout script path, either as argument or as key in options, is
+ * required.
+ *
+ * If mvcEnabled flag is false from options, simply sets layout script path.
+ * Otherwise, also instantiates and registers action helper and controller
+ * plugin.
+ *
+ * @param string|array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = null, $initMvc = false)
+ {
+ if (null !== $options) {
+ if (is_string($options)) {
+ $this->setLayoutPath($options);
+ } elseif (is_array($options)) {
+ $this->setOptions($options);
+ } elseif ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ } else {
+ throw new Zend_Layout_Exception('Invalid option provided to constructor');
+ }
+ }
+
+ $this->_initVarContainer();
+
+ if ($initMvc) {
+ $this->_setMvcEnabled(true);
+ $this->_initMvc();
+ } else {
+ $this->_setMvcEnabled(false);
+ }
+ }
+
+ /**
+ * Static method for initialization with MVC support
+ *
+ * @param string|array|Zend_Config $options
+ * @return Zend_Layout
+ */
+ public static function startMvc($options = null)
+ {
+ if (null === self::$_mvcInstance) {
+ self::$_mvcInstance = new self($options, true);
+ } else {
+ if (is_string($options)) {
+ self::$_mvcInstance->setLayoutPath($options);
+ } elseif (is_array($options) || $options instanceof Zend_Config) {
+ self::$_mvcInstance->setOptions($options);
+ }
+ }
+
+ return self::$_mvcInstance;
+ }
+
+ /**
+ * Retrieve MVC instance of Zend_Layout object
+ *
+ * @return Zend_Layout|null
+ */
+ public static function getMvcInstance()
+ {
+ return self::$_mvcInstance;
+ }
+
+ /**
+ * Reset MVC instance
+ *
+ * Unregisters plugins and helpers, and destroys MVC layout instance.
+ *
+ * @return void
+ */
+ public static function resetMvcInstance()
+ {
+ if (null !== self::$_mvcInstance) {
+ $layout = self::$_mvcInstance;
+ $pluginClass = $layout->getPluginClass();
+ $front = Zend_Controller_Front::getInstance();
+ if ($front->hasPlugin($pluginClass)) {
+ $front->unregisterPlugin($pluginClass);
+ }
+
+ if (Zend_Controller_Action_HelperBroker::hasHelper('layout')) {
+ Zend_Controller_Action_HelperBroker::removeHelper('layout');
+ }
+
+ unset($layout);
+ self::$_mvcInstance = null;
+ }
+ }
+
+ /**
+ * Set options en masse
+ *
+ * @param array|Zend_Config $options
+ * @return void
+ */
+ public function setOptions($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (!is_array($options)) {
+ throw new Zend_Layout_Exception('setOptions() expects either an array or a Zend_Config object');
+ }
+
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ }
+
+ /**
+ * Initialize MVC integration
+ *
+ * @return void
+ */
+ protected function _initMvc()
+ {
+ $this->_initPlugin();
+ $this->_initHelper();
+ }
+
+ /**
+ * Initialize front controller plugin
+ *
+ * @return void
+ */
+ protected function _initPlugin()
+ {
+ $pluginClass = $this->getPluginClass();
+ $front = Zend_Controller_Front::getInstance();
+ if (!$front->hasPlugin($pluginClass)) {
+ if (!class_exists($pluginClass)) {
+ Zend_Loader::loadClass($pluginClass);
+ }
+ $front->registerPlugin(
+ // register to run last | BUT before the ErrorHandler (if its available)
+ new $pluginClass($this),
+ 99
+ );
+ }
+ }
+
+ /**
+ * Initialize action helper
+ *
+ * @return void
+ */
+ protected function _initHelper()
+ {
+ $helperClass = $this->getHelperClass();
+ if (!Zend_Controller_Action_HelperBroker::hasHelper('layout')) {
+ if (!class_exists($helperClass)) {
+ Zend_Loader::loadClass($helperClass);
+ }
+ Zend_Controller_Action_HelperBroker::getStack()->offsetSet(-90, new $helperClass($this));
+ }
+ }
+
+ /**
+ * Set options from a config object
+ *
+ * @param Zend_Config $config
+ * @return Zend_Layout
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ $this->setOptions($config->toArray());
+ return $this;
+ }
+
+ /**
+ * Initialize placeholder container for layout vars
+ *
+ * @return Zend_View_Helper_Placeholder_Container
+ */
+ protected function _initVarContainer()
+ {
+ if (null === $this->_container) {
+ $this->_container = Zend_View_Helper_Placeholder_Registry::getRegistry()->getContainer(__CLASS__);
+ }
+
+ return $this->_container;
+ }
+
+ /**
+ * Set layout script to use
+ *
+ * Note: enables layout by default, can be disabled
+ *
+ * @param string $name
+ * @param boolean $enabled
+ * @return Zend_Layout
+ */
+ public function setLayout($name, $enabled = true)
+ {
+ $this->_layout = (string) $name;
+ if ($enabled) {
+ $this->enableLayout();
+ }
+ return $this;
+ }
+
+ /**
+ * Get current layout script
+ *
+ * @return string
+ */
+ public function getLayout()
+ {
+ return $this->_layout;
+ }
+
+ /**
+ * Disable layout
+ *
+ * @return Zend_Layout
+ */
+ public function disableLayout()
+ {
+ $this->_enabled = false;
+ return $this;
+ }
+
+ /**
+ * Enable layout
+ *
+ * @return Zend_Layout
+ */
+ public function enableLayout()
+ {
+ $this->_enabled = true;
+ return $this;
+ }
+
+ /**
+ * Is layout enabled?
+ *
+ * @return bool
+ */
+ public function isEnabled()
+ {
+ return $this->_enabled;
+ }
+
+
+ public function setViewBasePath($path, $prefix = 'Layout_View')
+ {
+ $this->_viewBasePath = $path;
+ $this->_viewBasePrefix = $prefix;
+ return $this;
+ }
+
+ public function getViewBasePath()
+ {
+ return $this->_viewBasePath;
+ }
+
+ public function setViewScriptPath($path)
+ {
+ $this->_viewScriptPath = $path;
+ return $this;
+ }
+
+ public function getViewScriptPath()
+ {
+ return $this->_viewScriptPath;
+ }
+
+ /**
+ * Set layout script path
+ *
+ * @param string $path
+ * @return Zend_Layout
+ */
+ public function setLayoutPath($path)
+ {
+ return $this->setViewScriptPath($path);
+ }
+
+ /**
+ * Get current layout script path
+ *
+ * @return string
+ */
+ public function getLayoutPath()
+ {
+ return $this->getViewScriptPath();
+ }
+
+ /**
+ * Set content key
+ *
+ * Key in namespace container denoting default content
+ *
+ * @param string $contentKey
+ * @return Zend_Layout
+ */
+ public function setContentKey($contentKey)
+ {
+ $this->_contentKey = (string) $contentKey;
+ return $this;
+ }
+
+ /**
+ * Retrieve content key
+ *
+ * @return string
+ */
+ public function getContentKey()
+ {
+ return $this->_contentKey;
+ }
+
+ /**
+ * Set MVC enabled flag
+ *
+ * @param bool $mvcEnabled
+ * @return Zend_Layout
+ */
+ protected function _setMvcEnabled($mvcEnabled)
+ {
+ $this->_mvcEnabled = ($mvcEnabled) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve MVC enabled flag
+ *
+ * @return bool
+ */
+ public function getMvcEnabled()
+ {
+ return $this->_mvcEnabled;
+ }
+
+ /**
+ * Set MVC Successful Action Only flag
+ *
+ * @param bool $successfulActionOnly
+ * @return Zend_Layout
+ */
+ public function setMvcSuccessfulActionOnly($successfulActionOnly)
+ {
+ $this->_mvcSuccessfulActionOnly = ($successfulActionOnly) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Get MVC Successful Action Only Flag
+ *
+ * @return bool
+ */
+ public function getMvcSuccessfulActionOnly()
+ {
+ return $this->_mvcSuccessfulActionOnly;
+ }
+
+ /**
+ * Set view object
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_Layout
+ */
+ public function setView(Zend_View_Interface $view)
+ {
+ $this->_view = $view;
+ return $this;
+ }
+
+ /**
+ * Retrieve helper class
+ *
+ * @return string
+ */
+ public function getHelperClass()
+ {
+ return $this->_helperClass;
+ }
+
+ /**
+ * Set helper class
+ *
+ * @param string $helperClass
+ * @return Zend_Layout
+ */
+ public function setHelperClass($helperClass)
+ {
+ $this->_helperClass = (string) $helperClass;
+ return $this;
+ }
+
+ /**
+ * Retrieve plugin class
+ *
+ * @return string
+ */
+ public function getPluginClass()
+ {
+ return $this->_pluginClass;
+ }
+
+ /**
+ * Set plugin class
+ *
+ * @param string $pluginClass
+ * @return Zend_Layout
+ */
+ public function setPluginClass($pluginClass)
+ {
+ $this->_pluginClass = (string) $pluginClass;
+ return $this;
+ }
+
+ /**
+ * Get current view object
+ *
+ * If no view object currently set, retrieves it from the ViewRenderer.
+ *
+ * @todo Set inflector from view renderer at same time
+ * @return Zend_View_Interface
+ */
+ public function getView()
+ {
+ if (null === $this->_view) {
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ if (null === $viewRenderer->view) {
+ $viewRenderer->initView();
+ }
+ $this->setView($viewRenderer->view);
+ }
+ return $this->_view;
+ }
+
+ /**
+ * Set layout view script suffix
+ *
+ * @param string $viewSuffix
+ * @return Zend_Layout
+ */
+ public function setViewSuffix($viewSuffix)
+ {
+ $this->_viewSuffix = (string) $viewSuffix;
+ return $this;
+ }
+
+ /**
+ * Retrieve layout view script suffix
+ *
+ * @return string
+ */
+ public function getViewSuffix()
+ {
+ return $this->_viewSuffix;
+ }
+
+ /**
+ * Retrieve inflector target
+ *
+ * @return string
+ */
+ public function getInflectorTarget()
+ {
+ return $this->_inflectorTarget;
+ }
+
+ /**
+ * Set inflector target
+ *
+ * @param string $inflectorTarget
+ * @return Zend_Layout
+ */
+ public function setInflectorTarget($inflectorTarget)
+ {
+ $this->_inflectorTarget = (string) $inflectorTarget;
+ return $this;
+ }
+
+ /**
+ * Set inflector to use when resolving layout names
+ *
+ * @param Zend_Filter_Inflector $inflector
+ * @return Zend_Layout
+ */
+ public function setInflector(Zend_Filter_Inflector $inflector)
+ {
+ $this->_inflector = $inflector;
+ return $this;
+ }
+
+ /**
+ * Retrieve inflector
+ *
+ * @return Zend_Filter_Inflector
+ */
+ public function getInflector()
+ {
+ if (null === $this->_inflector) {
+ $inflector = new Zend_Filter_Inflector();
+ $inflector->setTargetReference($this->_inflectorTarget)
+ ->addRules(array(':script' => array('Word_CamelCaseToDash', 'StringToLower')))
+ ->setStaticRuleReference('suffix', $this->_viewSuffix);
+ $this->setInflector($inflector);
+ }
+
+ return $this->_inflector;
+ }
+
+ /**
+ * Enable inflector
+ *
+ * @return Zend_Layout
+ */
+ public function enableInflector()
+ {
+ $this->_inflectorEnabled = true;
+ return $this;
+ }
+
+ /**
+ * Disable inflector
+ *
+ * @return Zend_Layout
+ */
+ public function disableInflector()
+ {
+ $this->_inflectorEnabled = false;
+ return $this;
+ }
+
+ /**
+ * Return status of inflector enabled flag
+ *
+ * @return bool
+ */
+ public function inflectorEnabled()
+ {
+ return $this->_inflectorEnabled;
+ }
+
+ /**
+ * Set layout variable
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->_container[$key] = $value;
+ }
+
+ /**
+ * Get layout variable
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (isset($this->_container[$key])) {
+ return $this->_container[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Is a layout variable set?
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return (isset($this->_container[$key]));
+ }
+
+ /**
+ * Unset a layout variable?
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key)
+ {
+ if (isset($this->_container[$key])) {
+ unset($this->_container[$key]);
+ }
+ }
+
+ /**
+ * Assign one or more layout variables
+ *
+ * @param mixed $spec Assoc array or string key; if assoc array, sets each
+ * key as a layout variable
+ * @param mixed $value Value if $spec is a key
+ * @return Zend_Layout
+ * @throws Zend_Layout_Exception if non-array/string value passed to $spec
+ */
+ public function assign($spec, $value = null)
+ {
+ if (is_array($spec)) {
+ $orig = $this->_container->getArrayCopy();
+ $merged = array_merge($orig, $spec);
+ $this->_container->exchangeArray($merged);
+ return $this;
+ }
+
+ if (is_string($spec)) {
+ $this->_container[$spec] = $value;
+ return $this;
+ }
+
+ throw new Zend_Layout_Exception('Invalid values passed to assign()');
+ }
+
+ /**
+ * Render layout
+ *
+ * Sets internal script path as last path on script path stack, assigns
+ * layout variables to view, determines layout name using inflector, and
+ * renders layout view script.
+ *
+ * $name will be passed to the inflector as the key 'script'.
+ *
+ * @param mixed $name
+ * @return mixed
+ */
+ public function render($name = null)
+ {
+ if (null === $name) {
+ $name = $this->getLayout();
+ }
+
+ if ($this->inflectorEnabled() && (null !== ($inflector = $this->getInflector())))
+ {
+ $name = $this->_inflector->filter(array('script' => $name));
+ }
+
+ $view = $this->getView();
+
+ if (null !== ($path = $this->getViewScriptPath())) {
+ if (method_exists($view, 'addScriptPath')) {
+ $view->addScriptPath($path);
+ } else {
+ $view->setScriptPath($path);
+ }
+ } elseif (null !== ($path = $this->getViewBasePath())) {
+ $view->addBasePath($path, $this->_viewBasePrefix);
+ }
+
+ return $view->render($name);
+ }
+}
diff --git a/library/vendor/Zend/Layout/Controller/Action/Helper/Layout.php b/library/vendor/Zend/Layout/Controller/Action/Helper/Layout.php
new file mode 100644
index 0000000..cd3f48c
--- /dev/null
+++ b/library/vendor/Zend/Layout/Controller/Action/Helper/Layout.php
@@ -0,0 +1,181 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Action_Helper_Abstract */
+
+/**
+ * Helper for interacting with Zend_Layout objects
+ *
+ * @uses Zend_Controller_Action_Helper_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Zend_Controller_Action
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Layout_Controller_Action_Helper_Layout extends Zend_Controller_Action_Helper_Abstract
+{
+ /**
+ * @var Zend_Controller_Front
+ */
+ protected $_frontController;
+
+ /**
+ * @var Zend_Layout
+ */
+ protected $_layout;
+
+ /**
+ * @var bool
+ */
+ protected $_isActionControllerSuccessful = false;
+
+ /**
+ * Constructor
+ *
+ * @param Zend_Layout $layout
+ * @return void
+ */
+ public function __construct(Zend_Layout $layout = null)
+ {
+ if (null !== $layout) {
+ $this->setLayoutInstance($layout);
+ } else {
+ /**
+ * @see Zend_Layout
+ */
+ $layout = Zend_Layout::getMvcInstance();
+ }
+
+ if (null !== $layout) {
+ $pluginClass = $layout->getPluginClass();
+ $front = $this->getFrontController();
+ if ($front->hasPlugin($pluginClass)) {
+ $plugin = $front->getPlugin($pluginClass);
+ $plugin->setLayoutActionHelper($this);
+ }
+ }
+ }
+
+ public function init()
+ {
+ $this->_isActionControllerSuccessful = false;
+ }
+
+ /**
+ * Get front controller instance
+ *
+ * @return Zend_Controller_Front
+ */
+ public function getFrontController()
+ {
+ if (null === $this->_frontController) {
+ /**
+ * @see Zend_Controller_Front
+ */
+ $this->_frontController = Zend_Controller_Front::getInstance();
+ }
+
+ return $this->_frontController;
+ }
+
+ /**
+ * Get layout object
+ *
+ * @return Zend_Layout
+ */
+ public function getLayoutInstance()
+ {
+ if (null === $this->_layout) {
+ /**
+ * @see Zend_Layout
+ */
+ if (null === ($this->_layout = Zend_Layout::getMvcInstance())) {
+ $this->_layout = new Zend_Layout();
+ }
+ }
+
+ return $this->_layout;
+ }
+
+ /**
+ * Set layout object
+ *
+ * @param Zend_Layout $layout
+ * @return Zend_Layout_Controller_Action_Helper_Layout
+ */
+ public function setLayoutInstance(Zend_Layout $layout)
+ {
+ $this->_layout = $layout;
+ return $this;
+ }
+
+ /**
+ * Mark Action Controller (according to this plugin) as Running successfully
+ *
+ * @return Zend_Layout_Controller_Action_Helper_Layout
+ */
+ public function postDispatch()
+ {
+ $this->_isActionControllerSuccessful = true;
+ return $this;
+ }
+
+ /**
+ * Did the previous action successfully complete?
+ *
+ * @return bool
+ */
+ public function isActionControllerSuccessful()
+ {
+ return $this->_isActionControllerSuccessful;
+ }
+
+ /**
+ * Strategy pattern; call object as method
+ *
+ * Returns layout object
+ *
+ * @return Zend_Layout
+ */
+ public function direct()
+ {
+ return $this->getLayoutInstance();
+ }
+
+ /**
+ * Proxy method calls to layout object
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ $layout = $this->getLayoutInstance();
+ if (method_exists($layout, $method)) {
+ return call_user_func_array(array($layout, $method), $args);
+ }
+
+ throw new Zend_Layout_Exception(sprintf("Invalid method '%s' called on layout action helper", $method));
+ }
+}
diff --git a/library/vendor/Zend/Layout/Controller/Plugin/Layout.php b/library/vendor/Zend/Layout/Controller/Plugin/Layout.php
new file mode 100644
index 0000000..d4855e7
--- /dev/null
+++ b/library/vendor/Zend/Layout/Controller/Plugin/Layout.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Controller_Plugin_Abstract */
+
+/**
+ * Render layouts
+ *
+ * @uses Zend_Controller_Plugin_Abstract
+ * @category Zend
+ * @package Zend_Controller
+ * @subpackage Plugins
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Layout_Controller_Plugin_Layout extends Zend_Controller_Plugin_Abstract
+{
+ protected $_layoutActionHelper = null;
+
+ /**
+ * @var Zend_Layout
+ */
+ protected $_layout;
+
+ /**
+ * Constructor
+ *
+ * @param Zend_Layout $layout
+ * @return void
+ */
+ public function __construct(Zend_Layout $layout = null)
+ {
+ if (null !== $layout) {
+ $this->setLayout($layout);
+ }
+ }
+
+ /**
+ * Retrieve layout object
+ *
+ * @return Zend_Layout
+ */
+ public function getLayout()
+ {
+ return $this->_layout;
+ }
+
+ /**
+ * Set layout object
+ *
+ * @param Zend_Layout $layout
+ * @return Zend_Layout_Controller_Plugin_Layout
+ */
+ public function setLayout(Zend_Layout $layout)
+ {
+ $this->_layout = $layout;
+ return $this;
+ }
+
+ /**
+ * Set layout action helper
+ *
+ * @param Zend_Layout_Controller_Action_Helper_Layout $layoutActionHelper
+ * @return Zend_Layout_Controller_Plugin_Layout
+ */
+ public function setLayoutActionHelper(Zend_Layout_Controller_Action_Helper_Layout $layoutActionHelper)
+ {
+ $this->_layoutActionHelper = $layoutActionHelper;
+ return $this;
+ }
+
+ /**
+ * Retrieve layout action helper
+ *
+ * @return Zend_Layout_Controller_Action_Helper_Layout
+ */
+ public function getLayoutActionHelper()
+ {
+ return $this->_layoutActionHelper;
+ }
+
+ /**
+ * postDispatch() plugin hook -- render layout
+ *
+ * @param Zend_Controller_Request_Abstract $request
+ * @return void
+ */
+ public function postDispatch(Zend_Controller_Request_Abstract $request)
+ {
+ $layout = $this->getLayout();
+ $helper = $this->getLayoutActionHelper();
+
+ // Return early if forward detected
+ if (!$request->isDispatched()
+ || $this->getResponse()->isRedirect()
+ || ($layout->getMvcSuccessfulActionOnly()
+ && (!empty($helper) && !$helper->isActionControllerSuccessful())))
+ {
+ return;
+ }
+
+ // Return early if layout has been disabled
+ if (!$layout->isEnabled()) {
+ return;
+ }
+
+ $response = $this->getResponse();
+ $content = $response->getBody(true);
+ $contentKey = $layout->getContentKey();
+
+ if (isset($content['default'])) {
+ $content[$contentKey] = $content['default'];
+ }
+ if ('default' != $contentKey) {
+ unset($content['default']);
+ }
+
+ $layout->assign($content);
+
+ $fullContent = null;
+ $obStartLevel = ob_get_level();
+ try {
+ $fullContent = $layout->render();
+ $response->setBody($fullContent);
+ } catch (Exception $e) {
+ while (ob_get_level() > $obStartLevel) {
+ $fullContent .= ob_get_clean();
+ }
+ $request->setParam('layoutFullContent', $fullContent);
+ $request->setParam('layoutContent', $layout->content);
+ $response->setBody(null);
+ throw $e;
+ }
+
+ }
+}
diff --git a/library/vendor/Zend/Layout/Exception.php b/library/vendor/Zend/Layout/Exception.php
new file mode 100644
index 0000000..c589e1a
--- /dev/null
+++ b/library/vendor/Zend/Layout/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Layout
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_Exception */
+
+
+/**
+ * @category Zend
+ * @package Zend_Layout
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Layout_Exception extends Zend_Exception
+{}
+
diff --git a/library/vendor/Zend/Loader.php b/library/vendor/Zend/Loader.php
new file mode 100644
index 0000000..2952e81
--- /dev/null
+++ b/library/vendor/Zend/Loader.php
@@ -0,0 +1,338 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Static methods for loading classes and files.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Loader
+{
+ /**
+ * Loads a class from a PHP file. The filename must be formatted
+ * as "$class.php".
+ *
+ * If $dirs is a string or an array, it will search the directories
+ * in the order supplied, and attempt to load the first matching file.
+ *
+ * If $dirs is null, it will split the class name at underscores to
+ * generate a path hierarchy (e.g., "Zend_Example_Class" will map
+ * to "Zend/Example/Class.php").
+ *
+ * If the file was not found in the $dirs, or if no $dirs were specified,
+ * it will attempt to load it from PHP's include_path.
+ *
+ * @param string $class - The full class name of a Zend component.
+ * @param string|array $dirs - OPTIONAL Either a path or an array of paths
+ * to search.
+ * @return void
+ * @throws Zend_Exception
+ */
+ public static function loadClass($class, $dirs = null)
+ {
+ if (class_exists($class, false) || interface_exists($class, false)) {
+ return;
+ }
+
+ if ((null !== $dirs) && !is_string($dirs) && !is_array($dirs)) {
+ throw new Zend_Exception('Directory argument must be a string or an array');
+ }
+
+ $file = self::standardiseFile($class);
+
+ if (!empty($dirs)) {
+ // use the autodiscovered path
+ $dirPath = dirname($file);
+ if (is_string($dirs)) {
+ $dirs = explode(PATH_SEPARATOR, $dirs);
+ }
+ foreach ($dirs as $key => $dir) {
+ if ($dir == '.') {
+ $dirs[$key] = $dirPath;
+ } else {
+ $dir = rtrim($dir, '\\/');
+ $dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath;
+ }
+ }
+ $file = basename($file);
+ self::loadFile($file, $dirs, true);
+ } else {
+ self::loadFile($file, null, true);
+ }
+
+ if (!class_exists($class, false) && !interface_exists($class, false)) {
+ throw new Zend_Exception("File \"$file\" does not exist or class \"$class\" was not found in the file");
+ }
+ }
+
+ /**
+ * Loads a PHP file. This is a wrapper for PHP's include() function.
+ *
+ * $filename must be the complete filename, including any
+ * extension such as ".php". Note that a security check is performed that
+ * does not permit extended characters in the filename. This method is
+ * intended for loading Zend Framework files.
+ *
+ * If $dirs is a string or an array, it will search the directories
+ * in the order supplied, and attempt to load the first matching file.
+ *
+ * If the file was not found in the $dirs, or if no $dirs were specified,
+ * it will attempt to load it from PHP's include_path.
+ *
+ * If $once is TRUE, it will use include_once() instead of include().
+ *
+ * @param string $filename
+ * @param string|array $dirs - OPTIONAL either a path or array of paths
+ * to search.
+ * @param boolean $once
+ * @return boolean
+ * @throws Zend_Exception
+ */
+ public static function loadFile($filename, $dirs = null, $once = false)
+ {
+ self::_securityCheck($filename);
+
+ /**
+ * Search in provided directories, as well as include_path
+ */
+ $incPath = false;
+ if (!empty($dirs) && (is_array($dirs) || is_string($dirs))) {
+ if (is_array($dirs)) {
+ $dirs = implode(PATH_SEPARATOR, $dirs);
+ }
+ $incPath = get_include_path();
+ set_include_path($dirs . PATH_SEPARATOR . $incPath);
+ }
+
+ /**
+ * Try finding for the plain filename in the include_path.
+ */
+ if ($once) {
+ include_once $filename;
+ } else {
+ include $filename;
+ }
+
+ /**
+ * If searching in directories, reset include_path
+ */
+ if ($incPath) {
+ set_include_path($incPath);
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns TRUE if the $filename is readable, or FALSE otherwise.
+ * This function uses the PHP include_path, where PHP's is_readable()
+ * does not.
+ *
+ * Note from ZF-2900:
+ * If you use custom error handler, please check whether return value
+ * from error_reporting() is zero or not.
+ * At mark of fopen() can not suppress warning if the handler is used.
+ *
+ * @param string $filename
+ * @return boolean
+ */
+ public static function isReadable($filename)
+ {
+ if (is_readable($filename)) {
+ // Return early if the filename is readable without needing the
+ // include_path
+ return true;
+ }
+
+ if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN'
+ && preg_match('/^[a-z]:/i', $filename)
+ ) {
+ // If on windows, and path provided is clearly an absolute path,
+ // return false immediately
+ return false;
+ }
+
+ foreach (self::explodeIncludePath() as $path) {
+ if ($path == '.') {
+ if (is_readable($filename)) {
+ return true;
+ }
+ continue;
+ }
+ $file = $path . '/' . $filename;
+ if (is_readable($file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Explode an include path into an array
+ *
+ * If no path provided, uses current include_path. Works around issues that
+ * occur when the path includes stream schemas.
+ *
+ * @param string|null $path
+ * @return array
+ */
+ public static function explodeIncludePath($path = null)
+ {
+ if (null === $path) {
+ $path = get_include_path();
+ }
+
+ if (PATH_SEPARATOR == ':') {
+ // On *nix systems, include_paths which include paths with a stream
+ // schema cannot be safely explode'd, so we have to be a bit more
+ // intelligent in the approach.
+ $paths = preg_split('#:(?!//)#', $path);
+ } else {
+ $paths = explode(PATH_SEPARATOR, $path);
+ }
+ return $paths;
+ }
+
+ /**
+ * spl_autoload() suitable implementation for supporting class autoloading.
+ *
+ * Attach to spl_autoload() using the following:
+ * <code>
+ * spl_autoload_register(array('Zend_Loader', 'autoload'));
+ * </code>
+ *
+ * @deprecated Since 1.8.0
+ * @param string $class
+ * @return string|false Class name on success; false on failure
+ */
+ public static function autoload($class)
+ {
+ trigger_error(__CLASS__ . '::' . __METHOD__ . ' is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead', E_USER_NOTICE);
+ try {
+ @self::loadClass($class);
+ return $class;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Register {@link autoload()} with spl_autoload()
+ *
+ * @deprecated Since 1.8.0
+ * @param string $class (optional)
+ * @param boolean $enabled (optional)
+ * @return void
+ * @throws Zend_Exception if spl_autoload() is not found
+ * or if the specified class does not have an autoload() method.
+ */
+ public static function registerAutoload($class = 'Zend_Loader', $enabled = true)
+ {
+ trigger_error(__CLASS__ . '::' . __METHOD__ . ' is deprecated as of 1.8.0 and will be removed with 2.0.0; use Zend_Loader_Autoloader instead', E_USER_NOTICE);
+ $autoloader = Zend_Loader_Autoloader::getInstance();
+ $autoloader->setFallbackAutoloader(true);
+
+ if ('Zend_Loader' != $class) {
+ self::loadClass($class);
+ $methods = get_class_methods($class);
+ if (!in_array('autoload', (array) $methods)) {
+ throw new Zend_Exception("The class \"$class\" does not have an autoload() method");
+ }
+
+ $callback = array($class, 'autoload');
+
+ if ($enabled) {
+ $autoloader->pushAutoloader($callback);
+ } else {
+ $autoloader->removeAutoloader($callback);
+ }
+ }
+ }
+
+ /**
+ * Ensure that filename does not contain exploits
+ *
+ * @param string $filename
+ * @return void
+ * @throws Zend_Exception
+ */
+ protected static function _securityCheck($filename)
+ {
+ /**
+ * Security check
+ */
+ if (preg_match('/[^a-z0-9\\/\\\\_.:-]/i', $filename)) {
+ throw new Zend_Exception('Security check: Illegal character in filename');
+ }
+ }
+
+ /**
+ * Attempt to include() the file.
+ *
+ * include() is not prefixed with the @ operator because if
+ * the file is loaded and contains a parse error, execution
+ * will halt silently and this is difficult to debug.
+ *
+ * Always set display_errors = Off on production servers!
+ *
+ * @param string $filespec
+ * @param boolean $once
+ * @return boolean
+ * @deprecated Since 1.5.0; use loadFile() instead
+ */
+ protected static function _includeFile($filespec, $once = false)
+ {
+ if ($once) {
+ return include_once $filespec;
+ } else {
+ return include $filespec ;
+ }
+ }
+
+ /**
+ * Standardise the filename.
+ *
+ * Convert the supplied filename into the namespace-aware standard,
+ * based on the Framework Interop Group reference implementation:
+ * http://groups.google.com/group/php-standards/web/psr-0-final-proposal
+ *
+ * The filename must be formatted as "$file.php".
+ *
+ * @param string $file - The file name to be loaded.
+ * @return string
+ */
+ public static function standardiseFile($file)
+ {
+ $fileName = ltrim($file, '\\');
+ $file = '';
+ $namespace = '';
+ if ($lastNsPos = strripos($fileName, '\\')) {
+ $namespace = substr($fileName, 0, $lastNsPos);
+ $fileName = substr($fileName, $lastNsPos + 1);
+ $file = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
+ }
+ $file .= str_replace('_', DIRECTORY_SEPARATOR, $fileName) . '.php';
+ return $file;
+ }
+}
diff --git a/library/vendor/Zend/Loader/Autoloader.php b/library/vendor/Zend/Loader/Autoloader.php
new file mode 100644
index 0000000..06e7023
--- /dev/null
+++ b/library/vendor/Zend/Loader/Autoloader.php
@@ -0,0 +1,589 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage Autoloader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Loader */
+require_once 'Zend/Loader.php';
+
+/**
+ * Autoloader stack and namespace autoloader
+ *
+ * @uses Zend_Loader_Autoloader
+ * @package Zend_Loader
+ * @subpackage Autoloader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Loader_Autoloader
+{
+ /**
+ * @var Zend_Loader_Autoloader Singleton instance
+ */
+ protected static $_instance;
+
+ /**
+ * @var array Concrete autoloader callback implementations
+ */
+ protected $_autoloaders = array();
+
+ /**
+ * @var array Default autoloader callback
+ */
+ protected $_defaultAutoloader = array('Zend_Loader', 'loadClass');
+
+ /**
+ * @var bool Whether or not to act as a fallback autoloader
+ */
+ protected $_fallbackAutoloader = false;
+
+ /**
+ * @var array Callback for internal autoloader implementation
+ */
+ protected $_internalAutoloader;
+
+ /**
+ * @var array Supported namespaces 'Zend' and 'ZendX' by default.
+ */
+ protected $_namespaces = array(
+ 'Zend_' => true,
+ 'ZendX_' => true,
+ );
+
+ /**
+ * @var array Namespace-specific autoloaders
+ */
+ protected $_namespaceAutoloaders = array();
+
+ /**
+ * @var bool Whether or not to suppress file not found warnings
+ */
+ protected $_suppressNotFoundWarnings = false;
+
+ /**
+ * @var null|string
+ */
+ protected $_zfPath;
+
+ /**
+ * Retrieve singleton instance
+ *
+ * @return Zend_Loader_Autoloader
+ */
+ public static function getInstance()
+ {
+ if (null === self::$_instance) {
+ self::$_instance = new self();
+ }
+ return self::$_instance;
+ }
+
+ /**
+ * Reset the singleton instance
+ *
+ * @return void
+ */
+ public static function resetInstance()
+ {
+ self::$_instance = null;
+ }
+
+ /**
+ * Autoload a class
+ *
+ * @param string $class
+ * @return bool
+ */
+ public static function autoload($class)
+ {
+ $self = self::getInstance();
+
+ foreach ($self->getClassAutoloaders($class) as $autoloader) {
+ if ($autoloader instanceof Zend_Loader_Autoloader_Interface) {
+ if ($autoloader->autoload($class)) {
+ return true;
+ }
+ } elseif (is_array($autoloader)) {
+ if (call_user_func($autoloader, $class)) {
+ return true;
+ }
+ } elseif (is_string($autoloader) || is_callable($autoloader)) {
+ if ($autoloader($class)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the default autoloader implementation
+ *
+ * @param string|array $callback PHP callback
+ * @return void
+ */
+ public function setDefaultAutoloader($callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Zend_Loader_Exception('Invalid callback specified for default autoloader');
+ }
+
+ $this->_defaultAutoloader = $callback;
+ return $this;
+ }
+
+ /**
+ * Retrieve the default autoloader callback
+ *
+ * @return string|array PHP Callback
+ */
+ public function getDefaultAutoloader()
+ {
+ return $this->_defaultAutoloader;
+ }
+
+ /**
+ * Set several autoloader callbacks at once
+ *
+ * @param array $autoloaders Array of PHP callbacks (or Zend_Loader_Autoloader_Interface implementations) to act as autoloaders
+ * @return Zend_Loader_Autoloader
+ */
+ public function setAutoloaders(array $autoloaders)
+ {
+ $this->_autoloaders = $autoloaders;
+ return $this;
+ }
+
+ /**
+ * Get attached autoloader implementations
+ *
+ * @return array
+ */
+ public function getAutoloaders()
+ {
+ return $this->_autoloaders;
+ }
+
+ /**
+ * Return all autoloaders for a given namespace
+ *
+ * @param string $namespace
+ * @return array
+ */
+ public function getNamespaceAutoloaders($namespace)
+ {
+ $namespace = (string) $namespace;
+ if (!array_key_exists($namespace, $this->_namespaceAutoloaders)) {
+ return array();
+ }
+ return $this->_namespaceAutoloaders[$namespace];
+ }
+
+ /**
+ * Register a namespace to autoload
+ *
+ * @param string|array $namespace
+ * @return Zend_Loader_Autoloader
+ */
+ public function registerNamespace($namespace)
+ {
+ if (is_string($namespace)) {
+ $namespace = (array) $namespace;
+ } elseif (!is_array($namespace)) {
+ throw new Zend_Loader_Exception('Invalid namespace provided');
+ }
+
+ foreach ($namespace as $ns) {
+ if (!isset($this->_namespaces[$ns])) {
+ $this->_namespaces[$ns] = true;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Unload a registered autoload namespace
+ *
+ * @param string|array $namespace
+ * @return Zend_Loader_Autoloader
+ */
+ public function unregisterNamespace($namespace)
+ {
+ if (is_string($namespace)) {
+ $namespace = (array) $namespace;
+ } elseif (!is_array($namespace)) {
+ throw new Zend_Loader_Exception('Invalid namespace provided');
+ }
+
+ foreach ($namespace as $ns) {
+ if (isset($this->_namespaces[$ns])) {
+ unset($this->_namespaces[$ns]);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Get a list of registered autoload namespaces
+ *
+ * @return array
+ */
+ public function getRegisteredNamespaces()
+ {
+ return array_keys($this->_namespaces);
+ }
+
+ public function setZfPath($spec, $version = 'latest')
+ {
+ $path = $spec;
+ if (is_array($spec)) {
+ if (!isset($spec['path'])) {
+ throw new Zend_Loader_Exception('No path specified for ZF');
+ }
+ $path = $spec['path'];
+ if (isset($spec['version'])) {
+ $version = $spec['version'];
+ }
+ }
+
+ $this->_zfPath = $this->_getVersionPath($path, $version);
+ set_include_path(implode(PATH_SEPARATOR, array(
+ $this->_zfPath,
+ get_include_path(),
+ )));
+ return $this;
+ }
+
+ public function getZfPath()
+ {
+ return $this->_zfPath;
+ }
+
+ /**
+ * Get or set the value of the "suppress not found warnings" flag
+ *
+ * @param null|bool $flag
+ * @return bool|Zend_Loader_Autoloader Returns boolean if no argument is passed, object instance otherwise
+ */
+ public function suppressNotFoundWarnings($flag = null)
+ {
+ if (null === $flag) {
+ return $this->_suppressNotFoundWarnings;
+ }
+ $this->_suppressNotFoundWarnings = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Indicate whether or not this autoloader should be a fallback autoloader
+ *
+ * @param bool $flag
+ * @return Zend_Loader_Autoloader
+ */
+ public function setFallbackAutoloader($flag)
+ {
+ $this->_fallbackAutoloader = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is this instance acting as a fallback autoloader?
+ *
+ * @return bool
+ */
+ public function isFallbackAutoloader()
+ {
+ return $this->_fallbackAutoloader;
+ }
+
+ /**
+ * Get autoloaders to use when matching class
+ *
+ * Determines if the class matches a registered namespace, and, if so,
+ * returns only the autoloaders for that namespace. Otherwise, it returns
+ * all non-namespaced autoloaders.
+ *
+ * @param string $class
+ * @return array Array of autoloaders to use
+ */
+ public function getClassAutoloaders($class)
+ {
+ $namespace = false;
+ $autoloaders = array();
+
+ // Add concrete namespaced autoloaders
+ foreach (array_keys($this->_namespaceAutoloaders) as $ns) {
+ if ('' == $ns) {
+ continue;
+ }
+ if (0 === strpos($class, $ns)) {
+ if ((false === $namespace) || (strlen($ns) > strlen($namespace))) {
+ $namespace = $ns;
+ $autoloaders = $this->getNamespaceAutoloaders($ns);
+ }
+ }
+ }
+
+ // Add internal namespaced autoloader
+ foreach ($this->getRegisteredNamespaces() as $ns) {
+ if (0 === strpos($class, $ns)) {
+ $namespace = $ns;
+ $autoloaders[] = $this->_internalAutoloader;
+ break;
+ }
+ }
+
+ // Add non-namespaced autoloaders
+ $autoloadersNonNamespace = $this->getNamespaceAutoloaders('');
+ if (count($autoloadersNonNamespace)) {
+ foreach ($autoloadersNonNamespace as $ns) {
+ $autoloaders[] = $ns;
+ }
+ unset($autoloadersNonNamespace);
+ }
+
+ // Add fallback autoloader
+ if (!$namespace && $this->isFallbackAutoloader()) {
+ $autoloaders[] = $this->_internalAutoloader;
+ }
+
+ return $autoloaders;
+ }
+
+ /**
+ * Add an autoloader to the beginning of the stack
+ *
+ * @param object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
+ * @param string|array $namespace Specific namespace(s) under which to register callback
+ * @return Zend_Loader_Autoloader
+ */
+ public function unshiftAutoloader($callback, $namespace = '')
+ {
+ $autoloaders = $this->getAutoloaders();
+ array_unshift($autoloaders, $callback);
+ $this->setAutoloaders($autoloaders);
+
+ $namespace = (array) $namespace;
+ foreach ($namespace as $ns) {
+ $autoloaders = $this->getNamespaceAutoloaders($ns);
+ array_unshift($autoloaders, $callback);
+ $this->_setNamespaceAutoloaders($autoloaders, $ns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Append an autoloader to the autoloader stack
+ *
+ * @param object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
+ * @param string|array $namespace Specific namespace(s) under which to register callback
+ * @return Zend_Loader_Autoloader
+ */
+ public function pushAutoloader($callback, $namespace = '')
+ {
+ $autoloaders = $this->getAutoloaders();
+ array_push($autoloaders, $callback);
+ $this->setAutoloaders($autoloaders);
+
+ $namespace = (array) $namespace;
+ foreach ($namespace as $ns) {
+ $autoloaders = $this->getNamespaceAutoloaders($ns);
+ array_push($autoloaders, $callback);
+ $this->_setNamespaceAutoloaders($autoloaders, $ns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove an autoloader from the autoloader stack
+ *
+ * @param object|array|string $callback PHP callback or Zend_Loader_Autoloader_Interface implementation
+ * @param null|string|array $namespace Specific namespace(s) from which to remove autoloader
+ * @return Zend_Loader_Autoloader
+ */
+ public function removeAutoloader($callback, $namespace = null)
+ {
+ if (null === $namespace) {
+ $autoloaders = $this->getAutoloaders();
+ if (false !== ($index = array_search($callback, $autoloaders, true))) {
+ unset($autoloaders[$index]);
+ $this->setAutoloaders($autoloaders);
+ }
+
+ foreach ($this->_namespaceAutoloaders as $ns => $autoloaders) {
+ if (false !== ($index = array_search($callback, $autoloaders, true))) {
+ unset($autoloaders[$index]);
+ $this->_setNamespaceAutoloaders($autoloaders, $ns);
+ }
+ }
+ } else {
+ $namespace = (array) $namespace;
+ foreach ($namespace as $ns) {
+ $autoloaders = $this->getNamespaceAutoloaders($ns);
+ if (false !== ($index = array_search($callback, $autoloaders, true))) {
+ unset($autoloaders[$index]);
+ $this->_setNamespaceAutoloaders($autoloaders, $ns);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Constructor
+ *
+ * Registers instance with spl_autoload stack
+ *
+ * @return void
+ */
+ protected function __construct()
+ {
+ spl_autoload_register(array(__CLASS__, 'autoload'));
+ $this->_internalAutoloader = array($this, '_autoload');
+ }
+
+ /**
+ * Internal autoloader implementation
+ *
+ * @param string $class
+ * @return bool
+ */
+ protected function _autoload($class)
+ {
+ $callback = $this->getDefaultAutoloader();
+ try {
+ if ($this->suppressNotFoundWarnings()) {
+ @call_user_func($callback, $class);
+ } else {
+ call_user_func($callback, $class);
+ }
+ return $class;
+ } catch (Zend_Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Set autoloaders for a specific namespace
+ *
+ * @param array $autoloaders
+ * @param string $namespace
+ * @return Zend_Loader_Autoloader
+ */
+ protected function _setNamespaceAutoloaders(array $autoloaders, $namespace = '')
+ {
+ $namespace = (string) $namespace;
+ $this->_namespaceAutoloaders[$namespace] = $autoloaders;
+ return $this;
+ }
+
+ /**
+ * Retrieve the filesystem path for the requested ZF version
+ *
+ * @param string $path
+ * @param string $version
+ * @return void
+ */
+ protected function _getVersionPath($path, $version)
+ {
+ $type = $this->_getVersionType($version);
+
+ if ($type == 'latest') {
+ $version = 'latest';
+ }
+
+ $availableVersions = $this->_getAvailableVersions($path, $version);
+ if (empty($availableVersions)) {
+ throw new Zend_Loader_Exception('No valid ZF installations discovered');
+ }
+
+ $matchedVersion = array_pop($availableVersions);
+ return $matchedVersion;
+ }
+
+ /**
+ * Retrieve the ZF version type
+ *
+ * @param string $version
+ * @return string "latest", "major", "minor", or "specific"
+ * @throws Zend_Loader_Exception if version string contains too many dots
+ */
+ protected function _getVersionType($version)
+ {
+ if (strtolower($version) == 'latest') {
+ return 'latest';
+ }
+
+ $parts = explode('.', $version);
+ $count = count($parts);
+ if (1 == $count) {
+ return 'major';
+ }
+ if (2 == $count) {
+ return 'minor';
+ }
+ if (3 < $count) {
+ throw new Zend_Loader_Exception('Invalid version string provided');
+ }
+ return 'specific';
+ }
+
+ /**
+ * Get available versions for the version type requested
+ *
+ * @param string $path
+ * @param string $version
+ * @return array
+ */
+ protected function _getAvailableVersions($path, $version)
+ {
+ if (!is_dir($path)) {
+ throw new Zend_Loader_Exception('Invalid ZF path provided');
+ }
+
+ $path = rtrim($path, '/');
+ $path = rtrim($path, '\\');
+ $versionLen = strlen($version);
+ $versions = array();
+ $dirs = glob("$path/*", GLOB_ONLYDIR);
+ foreach ((array) $dirs as $dir) {
+ $dirName = substr($dir, strlen($path) + 1);
+ if (!preg_match('/^(?:ZendFramework-)?(\d+\.\d+\.\d+((a|b|pl|pr|p|rc)\d+)?)(?:-minimal)?$/i', $dirName, $matches)) {
+ continue;
+ }
+
+ $matchedVersion = $matches[1];
+
+ if (('latest' == $version)
+ || ((strlen($matchedVersion) >= $versionLen)
+ && (0 === strpos($matchedVersion, $version)))
+ ) {
+ $versions[$matchedVersion] = $dir . '/library';
+ }
+ }
+
+ uksort($versions, 'version_compare');
+ return $versions;
+ }
+}
diff --git a/library/vendor/Zend/Loader/Autoloader/Interface.php b/library/vendor/Zend/Loader/Autoloader/Interface.php
new file mode 100644
index 0000000..64985e2
--- /dev/null
+++ b/library/vendor/Zend/Loader/Autoloader/Interface.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage Autoloader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Autoloader interface
+ *
+ * @package Zend_Loader
+ * @subpackage Autoloader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Loader_Autoloader_Interface
+{
+ /**
+ * Autoload a class
+ *
+ * @abstract
+ * @param string $class
+ * @return mixed
+ * False [if unable to load $class]
+ * get_class($class) [if $class is successfully loaded]
+ */
+ public function autoload($class);
+}
diff --git a/library/vendor/Zend/Loader/Exception.php b/library/vendor/Zend/Loader/Exception.php
new file mode 100644
index 0000000..c170e21
--- /dev/null
+++ b/library/vendor/Zend/Loader/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Loader
+ * @uses Zend_Exception
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Loader_Exception extends Zend_Exception
+{}
diff --git a/library/vendor/Zend/Loader/Exception/InvalidArgumentException.php b/library/vendor/Zend/Loader/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..0c9e09b
--- /dev/null
+++ b/library/vendor/Zend/Loader/Exception/InvalidArgumentException.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage Exception
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage Exception
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Loader_Exception_InvalidArgumentException
+ extends Zend_Loader_Exception
+{
+}
diff --git a/library/vendor/Zend/Loader/PluginLoader.php b/library/vendor/Zend/Loader/PluginLoader.php
new file mode 100644
index 0000000..5e985b0
--- /dev/null
+++ b/library/vendor/Zend/Loader/PluginLoader.php
@@ -0,0 +1,497 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage PluginLoader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Loader_PluginLoader_Interface */
+
+/** Zend_Loader */
+
+/**
+ * Generic plugin class loader
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage PluginLoader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Loader_PluginLoader implements Zend_Loader_PluginLoader_Interface
+{
+ /**
+ * Class map cache file
+ * @var string
+ */
+ protected static $_includeFileCache;
+
+ /**
+ * Class map cache file handler
+ * @var resource
+ */
+ protected static $_includeFileCacheHandler;
+
+ /**
+ * Instance loaded plugin paths
+ *
+ * @var array
+ */
+ protected $_loadedPluginPaths = array();
+
+ /**
+ * Instance loaded plugins
+ *
+ * @var array
+ */
+ protected $_loadedPlugins = array();
+
+ /**
+ * Instance registry property
+ *
+ * @var array
+ */
+ protected $_prefixToPaths = array();
+
+ /**
+ * Statically loaded plugin path mappings
+ *
+ * @var array
+ */
+ protected static $_staticLoadedPluginPaths = array();
+
+ /**
+ * Statically loaded plugins
+ *
+ * @var array
+ */
+ protected static $_staticLoadedPlugins = array();
+
+ /**
+ * Static registry property
+ *
+ * @var array
+ */
+ protected static $_staticPrefixToPaths = array();
+
+ /**
+ * Whether to use a statically named registry for loading plugins
+ *
+ * @var string|null
+ */
+ protected $_useStaticRegistry = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $prefixToPaths
+ * @param string $staticRegistryName OPTIONAL
+ */
+ public function __construct(Array $prefixToPaths = array(), $staticRegistryName = null)
+ {
+ if (is_string($staticRegistryName) && !empty($staticRegistryName)) {
+ $this->_useStaticRegistry = $staticRegistryName;
+ if(!isset(self::$_staticPrefixToPaths[$staticRegistryName])) {
+ self::$_staticPrefixToPaths[$staticRegistryName] = array();
+ }
+ if(!isset(self::$_staticLoadedPlugins[$staticRegistryName])) {
+ self::$_staticLoadedPlugins[$staticRegistryName] = array();
+ }
+ }
+
+ foreach ($prefixToPaths as $prefix => $path) {
+ $this->addPrefixPath($prefix, $path);
+ }
+ }
+
+ /**
+ * Format prefix for internal use
+ *
+ * @param string $prefix
+ * @return string
+ */
+ protected function _formatPrefix($prefix)
+ {
+ if($prefix == "") {
+ return $prefix;
+ }
+
+ $nsSeparator = (false !== strpos($prefix, '\\'))?'\\':'_';
+ $prefix = rtrim($prefix, $nsSeparator) . $nsSeparator;
+ //if $nsSeprator == "\" and the prefix ends in "_\" remove trailing \
+ //https://github.com/zendframework/zf1/issues/152
+ if(($nsSeparator == "\\") && (substr($prefix,-2) == "_\\")) {
+ $prefix = substr($prefix, 0, -1);
+ }
+ return $prefix;
+ }
+
+ /**
+ * Add prefixed paths to the registry of paths
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Loader_PluginLoader
+ */
+ public function addPrefixPath($prefix, $path)
+ {
+ if (!is_string($prefix) || !is_string($path)) {
+ throw new Zend_Loader_PluginLoader_Exception('Zend_Loader_PluginLoader::addPrefixPath() method only takes strings for prefix and path.');
+ }
+
+ $prefix = $this->_formatPrefix($prefix);
+ $path = rtrim($path, '/\\') . '/';
+
+ if ($this->_useStaticRegistry) {
+ self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix][] = $path;
+ } else {
+ if (!isset($this->_prefixToPaths[$prefix])) {
+ $this->_prefixToPaths[$prefix] = array();
+ }
+ if (!in_array($path, $this->_prefixToPaths[$prefix])) {
+ $this->_prefixToPaths[$prefix][] = $path;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Get path stack
+ *
+ * @param string $prefix
+ * @return false|array False if prefix does not exist, array otherwise
+ */
+ public function getPaths($prefix = null)
+ {
+ if ((null !== $prefix) && is_string($prefix)) {
+ $prefix = $this->_formatPrefix($prefix);
+ if ($this->_useStaticRegistry) {
+ if (isset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix])) {
+ return self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix];
+ }
+
+ return false;
+ }
+
+ if (isset($this->_prefixToPaths[$prefix])) {
+ return $this->_prefixToPaths[$prefix];
+ }
+
+ return false;
+ }
+
+ if ($this->_useStaticRegistry) {
+ return self::$_staticPrefixToPaths[$this->_useStaticRegistry];
+ }
+
+ return $this->_prefixToPaths;
+ }
+
+ /**
+ * Clear path stack
+ *
+ * @param string $prefix
+ * @return bool False only if $prefix does not exist
+ */
+ public function clearPaths($prefix = null)
+ {
+ if ((null !== $prefix) && is_string($prefix)) {
+ $prefix = $this->_formatPrefix($prefix);
+ if ($this->_useStaticRegistry) {
+ if (isset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix])) {
+ unset(self::$_staticPrefixToPaths[$this->_useStaticRegistry][$prefix]);
+ return true;
+ }
+
+ return false;
+ }
+
+ if (isset($this->_prefixToPaths[$prefix])) {
+ unset($this->_prefixToPaths[$prefix]);
+ return true;
+ }
+
+ return false;
+ }
+
+ if ($this->_useStaticRegistry) {
+ self::$_staticPrefixToPaths[$this->_useStaticRegistry] = array();
+ } else {
+ $this->_prefixToPaths = array();
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove a prefix (or prefixed-path) from the registry
+ *
+ * @param string $prefix
+ * @param string $path OPTIONAL
+ * @return Zend_Loader_PluginLoader
+ */
+ public function removePrefixPath($prefix, $path = null)
+ {
+ $prefix = $this->_formatPrefix($prefix);
+ if ($this->_useStaticRegistry) {
+ $registry =& self::$_staticPrefixToPaths[$this->_useStaticRegistry];
+ } else {
+ $registry =& $this->_prefixToPaths;
+ }
+
+ if (!isset($registry[$prefix])) {
+ throw new Zend_Loader_PluginLoader_Exception('Prefix ' . $prefix . ' was not found in the PluginLoader.');
+ }
+
+ if ($path != null) {
+ $pos = array_search($path, $registry[$prefix]);
+ if (false === $pos) {
+ throw new Zend_Loader_PluginLoader_Exception('Prefix ' . $prefix . ' / Path ' . $path . ' was not found in the PluginLoader.');
+ }
+ unset($registry[$prefix][$pos]);
+ } else {
+ unset($registry[$prefix]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Normalize plugin name
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _formatName($name)
+ {
+ return ucfirst((string) $name);
+ }
+
+ /**
+ * Whether or not a Plugin by a specific name is loaded
+ *
+ * @param string $name
+ * @return Zend_Loader_PluginLoader
+ */
+ public function isLoaded($name)
+ {
+ $name = $this->_formatName($name);
+ if ($this->_useStaticRegistry) {
+ return isset(self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name]);
+ }
+
+ return isset($this->_loadedPlugins[$name]);
+ }
+
+ /**
+ * Return full class name for a named plugin
+ *
+ * @param string $name
+ * @return string|false False if class not found, class name otherwise
+ */
+ public function getClassName($name)
+ {
+ $name = $this->_formatName($name);
+ if ($this->_useStaticRegistry
+ && isset(self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name])
+ ) {
+ return self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name];
+ } elseif (isset($this->_loadedPlugins[$name])) {
+ return $this->_loadedPlugins[$name];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get path to plugin class
+ *
+ * @param mixed $name
+ * @return string|false False if not found
+ */
+ public function getClassPath($name)
+ {
+ $name = $this->_formatName($name);
+ if ($this->_useStaticRegistry
+ && !empty(self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name])
+ ) {
+ return self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name];
+ } elseif (!empty($this->_loadedPluginPaths[$name])) {
+ return $this->_loadedPluginPaths[$name];
+ }
+
+ if ($this->isLoaded($name)) {
+ $class = $this->getClassName($name);
+ $r = new ReflectionClass($class);
+ $path = $r->getFileName();
+ if ($this->_useStaticRegistry) {
+ self::$_staticLoadedPluginPaths[$this->_useStaticRegistry][$name] = $path;
+ } else {
+ $this->_loadedPluginPaths[$name] = $path;
+ }
+ return $path;
+ }
+
+ return false;
+ }
+
+ /**
+ * Load a plugin via the name provided
+ *
+ * @param string $name
+ * @param bool $throwExceptions Whether or not to throw exceptions if the
+ * class is not resolved
+ * @return string|false Class name of loaded class; false if $throwExceptions
+ * if false and no class found
+ * @throws Zend_Loader_Exception if class not found
+ */
+ public function load($name, $throwExceptions = true)
+ {
+ $name = $this->_formatName($name);
+ if ($this->isLoaded($name)) {
+ return $this->getClassName($name);
+ }
+
+ if ($this->_useStaticRegistry) {
+ $registry = self::$_staticPrefixToPaths[$this->_useStaticRegistry];
+ } else {
+ $registry = $this->_prefixToPaths;
+ }
+
+ $registry = array_reverse($registry, true);
+ $found = false;
+ if (false !== strpos($name, '\\')) {
+ $classFile = str_replace('\\', DIRECTORY_SEPARATOR, $name) . '.php';
+ } else {
+ $classFile = str_replace('_', DIRECTORY_SEPARATOR, $name) . '.php';
+ }
+ $incFile = self::getIncludeFileCache();
+ foreach ($registry as $prefix => $paths) {
+ $className = $prefix . $name;
+
+ if (class_exists($className, false)) {
+ $found = true;
+ break;
+ }
+
+ $paths = array_reverse($paths, true);
+
+ foreach ($paths as $path) {
+ $loadFile = $path . $classFile;
+ if (Zend_Loader::isReadable($loadFile)) {
+ include_once $loadFile;
+ if (class_exists($className, false)) {
+ if (null !== $incFile) {
+ self::_appendIncFile($loadFile);
+ }
+ $found = true;
+ break 2;
+ }
+ }
+ }
+ }
+
+ if (!$found) {
+ if (!$throwExceptions) {
+ return false;
+ }
+
+ $message = "Plugin by name '$name' was not found in the registry; used paths:";
+ foreach ($registry as $prefix => $paths) {
+ $message .= "\n$prefix: " . implode(PATH_SEPARATOR, $paths);
+ }
+ throw new Zend_Loader_PluginLoader_Exception($message);
+ }
+
+ if ($this->_useStaticRegistry) {
+ self::$_staticLoadedPlugins[$this->_useStaticRegistry][$name] = $className;
+ } else {
+ $this->_loadedPlugins[$name] = $className;
+ }
+ return $className;
+ }
+
+ /**
+ * Set path to class file cache
+ *
+ * Specify a path to a file that will add include_once statements for each
+ * plugin class loaded. This is an opt-in feature for performance purposes.
+ *
+ * @param string $file
+ * @return void
+ * @throws Zend_Loader_PluginLoader_Exception if file is not writeable or path does not exist
+ */
+ public static function setIncludeFileCache($file)
+ {
+ if (!empty(self::$_includeFileCacheHandler)) {
+ flock(self::$_includeFileCacheHandler, LOCK_UN);
+ fclose(self::$_includeFileCacheHandler);
+ }
+
+ self::$_includeFileCacheHandler = null;
+
+ if (null === $file) {
+ self::$_includeFileCache = null;
+ return;
+ }
+
+ if (!file_exists($file) && !file_exists(dirname($file))) {
+ throw new Zend_Loader_PluginLoader_Exception('Specified file does not exist and/or directory does not exist (' . $file . ')');
+ }
+ if (file_exists($file) && !is_writable($file)) {
+ throw new Zend_Loader_PluginLoader_Exception('Specified file is not writeable (' . $file . ')');
+ }
+ if (!file_exists($file) && file_exists(dirname($file)) && !is_writable(dirname($file))) {
+ throw new Zend_Loader_PluginLoader_Exception('Specified file is not writeable (' . $file . ')');
+ }
+
+ self::$_includeFileCache = $file;
+ }
+
+ /**
+ * Retrieve class file cache path
+ *
+ * @return string|null
+ */
+ public static function getIncludeFileCache()
+ {
+ return self::$_includeFileCache;
+ }
+
+ /**
+ * Append an include_once statement to the class file cache
+ *
+ * @param string $incFile
+ * @return void
+ */
+ protected static function _appendIncFile($incFile)
+ {
+ if (!isset(self::$_includeFileCacheHandler)) {
+ self::$_includeFileCacheHandler = fopen(self::$_includeFileCache, 'ab');
+
+ if (!flock(self::$_includeFileCacheHandler, LOCK_EX | LOCK_NB, $wouldBlock) || $wouldBlock) {
+ self::$_includeFileCacheHandler = false;
+ }
+ }
+
+ if (false !== self::$_includeFileCacheHandler) {
+ $line = "<?php include_once '$incFile'?>\n";
+ fwrite(self::$_includeFileCacheHandler, $line, strlen($line));
+ }
+ }
+}
diff --git a/library/vendor/Zend/Loader/PluginLoader/Exception.php b/library/vendor/Zend/Loader/PluginLoader/Exception.php
new file mode 100644
index 0000000..3138391
--- /dev/null
+++ b/library/vendor/Zend/Loader/PluginLoader/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage PluginLoader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Loader_Exception
+ */
+
+/**
+ * Plugin class loader exceptions
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage PluginLoader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Loader_PluginLoader_Exception extends Zend_Loader_Exception
+{
+}
diff --git a/library/vendor/Zend/Loader/PluginLoader/Interface.php b/library/vendor/Zend/Loader/PluginLoader/Interface.php
new file mode 100644
index 0000000..2733191
--- /dev/null
+++ b/library/vendor/Zend/Loader/PluginLoader/Interface.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage PluginLoader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Plugin class loader interface
+ *
+ * @category Zend
+ * @package Zend_Loader
+ * @subpackage PluginLoader
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Loader_PluginLoader_Interface
+{
+ /**
+ * Add prefixed paths to the registry of paths
+ *
+ * @param string $prefix
+ * @param string $path
+ * @return Zend_Loader_PluginLoader
+ */
+ public function addPrefixPath($prefix, $path);
+
+ /**
+ * Remove a prefix (or prefixed-path) from the registry
+ *
+ * @param string $prefix
+ * @param string $path OPTIONAL
+ * @return Zend_Loader_PluginLoader
+ */
+ public function removePrefixPath($prefix, $path = null);
+
+ /**
+ * Whether or not a Helper by a specific name
+ *
+ * @param string $name
+ * @return Zend_Loader_PluginLoader
+ */
+ public function isLoaded($name);
+
+ /**
+ * Return full class name for a named helper
+ *
+ * @param string $name
+ * @return string
+ */
+ public function getClassName($name);
+
+ /**
+ * Load a helper via the name provided
+ *
+ * @param string $name
+ * @return string
+ */
+ public function load($name);
+}
diff --git a/library/vendor/Zend/Locale.php b/library/vendor/Zend/Locale.php
new file mode 100644
index 0000000..94a77b3
--- /dev/null
+++ b/library/vendor/Zend/Locale.php
@@ -0,0 +1,1996 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Base class for localization
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale
+{
+ /**
+ * List of locales that are no longer part of CLDR along with a
+ * mapping to an appropriate alternative.
+ *
+ * @var array
+ */
+ private static $_localeAliases = array(
+ 'az_AZ' => 'az_Latn_AZ',
+ 'bs_BA' => 'bs_Latn_BA',
+ 'ha_GH' => 'ha_Latn_GH',
+ 'ha_NE' => 'ha_Latn_NE',
+ 'ha_NG' => 'ha_Latn_NG',
+ 'kk_KZ' => 'kk_Cyrl_KZ',
+ 'ks_IN' => 'ks_Arab_IN',
+ 'mn_MN' => 'mn_Cyrl_MN',
+ 'ms_BN' => 'ms_Latn_BN',
+ 'ms_MY' => 'ms_Latn_MY',
+ 'ms_SG' => 'ms_Latn_SG',
+ 'pa_IN' => 'pa_Guru_IN',
+ 'pa_PK' => 'pa_Arab_PK',
+ 'shi_MA' => 'shi_Latn_MA',
+ 'sr_BA' => 'sr_Latn_BA',
+ 'sr_ME' => 'sr_Latn_ME',
+ 'sr_RS' => 'sr_Latn_RS',
+ 'sr_XK' => 'sr_Latn_XK',
+ 'tg_TJ' => 'tg_Cyrl_TJ',
+ 'tzm_MA' => 'tzm_Latn_MA',
+ 'uz_AF' => 'uz_Arab_AF',
+ 'uz_UZ' => 'uz_Latn_UZ',
+ 'vai_LR' => 'vai_Latn_LR',
+ 'zh_CN' => 'zh_Hans_CN',
+ 'zh_HK' => 'zh_Hant_HK',
+ 'zh_MO' => 'zh_Hans_MO',
+ 'zh_SG' => 'zh_Hans_SG',
+ 'zh_TW' => 'zh_Hant_TW',
+ );
+
+ /**
+ * Class wide Locale Constants
+ *
+ * @var array $_localeData
+ */
+ private static $_localeData = array(
+ 'root' => true,
+ 'aa' => true,
+ 'aa_DJ' => true,
+ 'aa_ER' => true,
+ 'aa_ET' => true,
+ 'af' => true,
+ 'af_NA' => true,
+ 'af_ZA' => true,
+ 'agq' => true,
+ 'agq_CM' => true,
+ 'ak' => true,
+ 'ak_GH' => true,
+ 'am' => true,
+ 'am_ET' => true,
+ 'ar' => true,
+ 'ar_001' => true,
+ 'ar_AE' => true,
+ 'ar_BH' => true,
+ 'ar_DJ' => true,
+ 'ar_DZ' => true,
+ 'ar_EG' => true,
+ 'ar_EH' => true,
+ 'ar_ER' => true,
+ 'ar_IL' => true,
+ 'ar_IQ' => true,
+ 'ar_JO' => true,
+ 'ar_KM' => true,
+ 'ar_KW' => true,
+ 'ar_LB' => true,
+ 'ar_LY' => true,
+ 'ar_MA' => true,
+ 'ar_MR' => true,
+ 'ar_OM' => true,
+ 'ar_PS' => true,
+ 'ar_QA' => true,
+ 'ar_SA' => true,
+ 'ar_SD' => true,
+ 'ar_SO' => true,
+ 'ar_SS' => true,
+ 'ar_SY' => true,
+ 'ar_TD' => true,
+ 'ar_TN' => true,
+ 'ar_YE' => true,
+ 'as' => true,
+ 'as_IN' => true,
+ 'asa' => true,
+ 'asa_TZ' => true,
+ 'ast' => true,
+ 'ast_ES' => true,
+ 'az' => true,
+ 'az_Cyrl' => true,
+ 'az_Cyrl_AZ' => true,
+ 'az_Latn' => true,
+ 'az_Latn_AZ' => true,
+ 'bas' => true,
+ 'bas_CM' => true,
+ 'be' => true,
+ 'be_BY' => true,
+ 'bem' => true,
+ 'bem_ZM' => true,
+ 'bez' => true,
+ 'bez_TZ' => true,
+ 'bg' => true,
+ 'bg_BG' => true,
+ 'bm' => true,
+ 'bm_ML' => true,
+ 'bn' => true,
+ 'bn_BD' => true,
+ 'bn_IN' => true,
+ 'bo' => true,
+ 'bo_CN' => true,
+ 'bo_IN' => true,
+ 'br' => true,
+ 'br_FR' => true,
+ 'brx' => true,
+ 'brx_IN' => true,
+ 'bs' => true,
+ 'bs_Cyrl' => true,
+ 'bs_Cyrl_BA' => true,
+ 'bs_Latn' => true,
+ 'bs_Latn_BA' => true,
+ 'byn' => true,
+ 'byn_ER' => true,
+ 'ca' => true,
+ 'ca_AD' => true,
+ 'ca_ES' => true,
+ 'ca_ES_VALENCIA' => true,
+ 'ca_FR' => true,
+ 'ca_IT' => true,
+ 'cgg' => true,
+ 'cgg_UG' => true,
+ 'chr' => true,
+ 'chr_US' => true,
+ 'cs' => true,
+ 'cs_CZ' => true,
+ 'cy' => true,
+ 'cy_GB' => true,
+ 'da' => true,
+ 'da_DK' => true,
+ 'da_GL' => true,
+ 'dav' => true,
+ 'dav_KE' => true,
+ 'de' => true,
+ 'de_AT' => true,
+ 'de_BE' => true,
+ 'de_CH' => true,
+ 'de_DE' => true,
+ 'de_LI' => true,
+ 'de_LU' => true,
+ 'dje' => true,
+ 'dje_NE' => true,
+ 'dua' => true,
+ 'dua_CM' => true,
+ 'dyo' => true,
+ 'dyo_SN' => true,
+ 'dz' => true,
+ 'dz_BT' => true,
+ 'ebu' => true,
+ 'ebu_KE' => true,
+ 'ee' => true,
+ 'ee_GH' => true,
+ 'ee_TG' => true,
+ 'el' => true,
+ 'el_CY' => true,
+ 'el_GR' => true,
+ 'en' => true,
+ 'en_001' => true,
+ 'en_150' => true,
+ 'en_AG' => true,
+ 'en_AI' => true,
+ 'en_AS' => true,
+ 'en_AU' => true,
+ 'en_BB' => true,
+ 'en_BE' => true,
+ 'en_BM' => true,
+ 'en_BS' => true,
+ 'en_BW' => true,
+ 'en_BZ' => true,
+ 'en_CA' => true,
+ 'en_CC' => true,
+ 'en_CK' => true,
+ 'en_CM' => true,
+ 'en_CX' => true,
+ 'en_DG' => true,
+ 'en_DM' => true,
+ 'en_Dsrt' => true,
+ 'en_Dsrt_US' => true,
+ 'en_ER' => true,
+ 'en_FJ' => true,
+ 'en_FK' => true,
+ 'en_FM' => true,
+ 'en_GB' => true,
+ 'en_GD' => true,
+ 'en_GG' => true,
+ 'en_GH' => true,
+ 'en_GI' => true,
+ 'en_GM' => true,
+ 'en_GU' => true,
+ 'en_GY' => true,
+ 'en_HK' => true,
+ 'en_IE' => true,
+ 'en_IM' => true,
+ 'en_IN' => true,
+ 'en_IO' => true,
+ 'en_JE' => true,
+ 'en_JM' => true,
+ 'en_KE' => true,
+ 'en_KI' => true,
+ 'en_KN' => true,
+ 'en_KY' => true,
+ 'en_LC' => true,
+ 'en_LR' => true,
+ 'en_LS' => true,
+ 'en_MG' => true,
+ 'en_MH' => true,
+ 'en_MO' => true,
+ 'en_MP' => true,
+ 'en_MS' => true,
+ 'en_MT' => true,
+ 'en_MU' => true,
+ 'en_MW' => true,
+ 'en_NA' => true,
+ 'en_NF' => true,
+ 'en_NG' => true,
+ 'en_NR' => true,
+ 'en_NU' => true,
+ 'en_NZ' => true,
+ 'en_PG' => true,
+ 'en_PH' => true,
+ 'en_PK' => true,
+ 'en_PN' => true,
+ 'en_PR' => true,
+ 'en_PW' => true,
+ 'en_RW' => true,
+ 'en_SB' => true,
+ 'en_SC' => true,
+ 'en_SD' => true,
+ 'en_SG' => true,
+ 'en_SH' => true,
+ 'en_SL' => true,
+ 'en_SS' => true,
+ 'en_SX' => true,
+ 'en_SZ' => true,
+ 'en_TC' => true,
+ 'en_TK' => true,
+ 'en_TO' => true,
+ 'en_TT' => true,
+ 'en_TV' => true,
+ 'en_TZ' => true,
+ 'en_UG' => true,
+ 'en_UM' => true,
+ 'en_US' => true,
+ 'en_US_POSIX' => true,
+ 'en_VC' => true,
+ 'en_VG' => true,
+ 'en_VI' => true,
+ 'en_VU' => true,
+ 'en_WS' => true,
+ 'en_ZA' => true,
+ 'en_ZM' => true,
+ 'en_ZW' => true,
+ 'eo' => true,
+ 'eo_001' => true,
+ 'es' => true,
+ 'es_419' => true,
+ 'es_AR' => true,
+ 'es_BO' => true,
+ 'es_CL' => true,
+ 'es_CO' => true,
+ 'es_CR' => true,
+ 'es_CU' => true,
+ 'es_DO' => true,
+ 'es_EA' => true,
+ 'es_EC' => true,
+ 'es_ES' => true,
+ 'es_GQ' => true,
+ 'es_GT' => true,
+ 'es_HN' => true,
+ 'es_IC' => true,
+ 'es_MX' => true,
+ 'es_NI' => true,
+ 'es_PA' => true,
+ 'es_PE' => true,
+ 'es_PH' => true,
+ 'es_PR' => true,
+ 'es_PY' => true,
+ 'es_SV' => true,
+ 'es_US' => true,
+ 'es_UY' => true,
+ 'es_VE' => true,
+ 'et' => true,
+ 'et_EE' => true,
+ 'eu' => true,
+ 'eu_ES' => true,
+ 'ewo' => true,
+ 'ewo_CM' => true,
+ 'fa' => true,
+ 'fa_AF' => true,
+ 'fa_IR' => true,
+ 'ff' => true,
+ 'ff_CM' => true,
+ 'ff_GN' => true,
+ 'ff_MR' => true,
+ 'fr_PM' => true,
+ 'ff_SN' => true,
+ 'fr_WF' => true,
+ 'fi' => true,
+ 'fi_FI' => true,
+ 'fil' => true,
+ 'fil_PH' => true,
+ 'fo' => true,
+ 'fo_FO' => true,
+ 'fr' => true,
+ 'fr_BE' => true,
+ 'fr_BF' => true,
+ 'fr_BI' => true,
+ 'fr_BJ' => true,
+ 'fr_BL' => true,
+ 'fr_CA' => true,
+ 'fr_CD' => true,
+ 'fr_CF' => true,
+ 'fr_CG' => true,
+ 'fr_CH' => true,
+ 'fr_CI' => true,
+ 'fr_CM' => true,
+ 'fr_DJ' => true,
+ 'fr_DZ' => true,
+ 'fr_FR' => true,
+ 'fr_GA' => true,
+ 'fr_GF' => true,
+ 'fr_GN' => true,
+ 'fr_GP' => true,
+ 'fr_GQ' => true,
+ 'fr_HT' => true,
+ 'fr_KM' => true,
+ 'fr_LU' => true,
+ 'fr_MA' => true,
+ 'fr_MC' => true,
+ 'fr_MF' => true,
+ 'fr_MG' => true,
+ 'fr_ML' => true,
+ 'fr_MQ' => true,
+ 'fr_MR' => true,
+ 'fr_MU' => true,
+ 'fr_NC' => true,
+ 'fr_NE' => true,
+ 'fr_PF' => true,
+ 'fr_RE' => true,
+ 'fr_RW' => true,
+ 'fr_SC' => true,
+ 'fr_SN' => true,
+ 'fr_SY' => true,
+ 'fr_TD' => true,
+ 'fr_TG' => true,
+ 'fr_TN' => true,
+ 'fr_VU' => true,
+ 'fr_YT' => true,
+ 'fur' => true,
+ 'fur_IT' => true,
+ 'fy' => true,
+ 'fy_NL' => true,
+ 'ga' => true,
+ 'ga_IE' => true,
+ 'gd' => true,
+ 'gd_GB' => true,
+ 'gl' => true,
+ 'gl_ES' => true,
+ 'gsw' => true,
+ 'gsw_CH' => true,
+ 'gsw_LI' => true,
+ 'gu' => true,
+ 'gu_IN' => true,
+ 'guz' => true,
+ 'guz_KE' => true,
+ 'gv' => true,
+ 'gv_IM' => true,
+ 'ha' => true,
+ 'ha_Latn' => true,
+ 'ha_Latn_GH' => true,
+ 'ha_Latn_NE' => true,
+ 'ha_Latn_NG' => true,
+ 'haw' => true,
+ 'haw_US' => true,
+ 'he' => true,
+ 'he_IL' => true,
+ 'hi' => true,
+ 'hi_IN' => true,
+ 'hr' => true,
+ 'hr_BA' => true,
+ 'hr_HR' => true,
+ 'hu' => true,
+ 'hu_HU' => true,
+ 'hy' => true,
+ 'hy_AM' => true,
+ 'ia' => true,
+ 'ia_FR' => true,
+ 'id' => true,
+ 'id_ID' => true,
+ 'ig' => true,
+ 'ig_NG' => true,
+ 'ii' => true,
+ 'ii_CN' => true,
+ 'is' => true,
+ 'is_IS' => true,
+ 'it' => true,
+ 'it_CH' => true,
+ 'it_IT' => true,
+ 'it_SM' => true,
+ 'ja' => true,
+ 'ja_JP' => true,
+ 'jgo' => true,
+ 'jgo_CM' => true,
+ 'jmc' => true,
+ 'jmc_TZ' => true,
+ 'ka' => true,
+ 'ka_GE' => true,
+ 'kab' => true,
+ 'kab_DZ' => true,
+ 'kam' => true,
+ 'kam_KE' => true,
+ 'kde' => true,
+ 'kde_TZ' => true,
+ 'kea' => true,
+ 'kea_CV' => true,
+ 'khq' => true,
+ 'khq_ML' => true,
+ 'ki' => true,
+ 'ki_KE' => true,
+ 'kk' => true,
+ 'kk_Cyrl' => true,
+ 'kk_Cyrl_KZ' => true,
+ 'kkj' => true,
+ 'kkj_CM' => true,
+ 'kl' => true,
+ 'kl_GL' => true,
+ 'kln' => true,
+ 'kln_KE' => true,
+ 'km' => true,
+ 'km_KH' => true,
+ 'kn' => true,
+ 'kn_IN' => true,
+ 'ko' => true,
+ 'ko_KP' => true,
+ 'ko_KR' => true,
+ 'kok' => true,
+ 'kok_IN' => true,
+ 'ks' => true,
+ 'ks_Arab' => true,
+ 'ks_Arab_IN' => true,
+ 'ksb' => true,
+ 'ksb_TZ' => true,
+ 'ksf' => true,
+ 'ksf_CM' => true,
+ 'ksh' => true,
+ 'ksh_DE' => true,
+ 'kw' => true,
+ 'kw_GB' => true,
+ 'ky' => true,
+ 'ky_Cyrl' => true,
+ 'ky_Cyrl_KG' => true,
+ 'lag' => true,
+ 'lag_TZ' => true,
+ 'lg' => true,
+ 'lg_UG' => true,
+ 'lkt' => true,
+ 'lkt_US' => true,
+ 'ln' => true,
+ 'ln_AO' => true,
+ 'ln_CD' => true,
+ 'ln_CF' => true,
+ 'ln_CG' => true,
+ 'lo' => true,
+ 'lo_LA' => true,
+ 'lt' => true,
+ 'lt_LT' => true,
+ 'lu' => true,
+ 'lu_CD' => true,
+ 'luo' => true,
+ 'luo_KE' => true,
+ 'luy' => true,
+ 'luy_KE' => true,
+ 'lv' => true,
+ 'lv_LV' => true,
+ 'mas' => true,
+ 'mas_KE' => true,
+ 'mas_TZ' => true,
+ 'mer' => true,
+ 'mer_KE' => true,
+ 'mfe' => true,
+ 'mfe_MU' => true,
+ 'mg' => true,
+ 'mg_MG' => true,
+ 'mgh' => true,
+ 'mgh_MZ' => true,
+ 'mgo' => true,
+ 'mgo_CM' => true,
+ 'mk' => true,
+ 'mk_MK' => true,
+ 'ml' => true,
+ 'ml_IN' => true,
+ 'mn' => true,
+ 'mn_Cyrl' => true,
+ 'mn_Cyrl_MN' => true,
+ 'mr' => true,
+ 'mr_IN' => true,
+ 'ms' => true,
+ 'ms_Latn' => true,
+ 'ms_Latn_BN' => true,
+ 'ms_Latn_MY' => true,
+ 'ms_Latn_SG' => true,
+ 'mt' => true,
+ 'mt_MT' => true,
+ 'mua' => true,
+ 'mua_CM' => true,
+ 'my' => true,
+ 'my_MM' => true,
+ 'naq' => true,
+ 'naq_NA' => true,
+ 'nb' => true,
+ 'nb_NO' => true,
+ 'nb_SJ' => true,
+ 'nd' => true,
+ 'nd_ZW' => true,
+ 'ne' => true,
+ 'ne_IN' => true,
+ 'ne_NP' => true,
+ 'nl' => true,
+ 'nl_AW' => true,
+ 'nl_BE' => true,
+ 'nl_BQ' => true,
+ 'nl_CW' => true,
+ 'nl_NL' => true,
+ 'nl_SR' => true,
+ 'nl_SX' => true,
+ 'nmg' => true,
+ 'nmg_CM' => true,
+ 'nn' => true,
+ 'nn_NO' => true,
+ 'nnh' => true,
+ 'nnh_CM' => true,
+ 'nr' => true,
+ 'nr_ZA' => true,
+ 'nso' => true,
+ 'nso_ZA' => true,
+ 'nus' => true,
+ 'nus_SD' => true,
+ 'nyn' => true,
+ 'nyn_UG' => true,
+ 'om' => true,
+ 'om_ET' => true,
+ 'om_KE' => true,
+ 'or' => true,
+ 'or_IN' => true,
+ 'ordinals' => true,
+ 'os' => true,
+ 'os_GE' => true,
+ 'os_RU' => true,
+ 'pa' => true,
+ 'pa_Arab' => true,
+ 'pa_Arab_PK' => true,
+ 'pa_Guru' => true,
+ 'pa_Guru_IN' => true,
+ 'pl' => true,
+ 'pl_PL' => true,
+ 'plurals' => true,
+ 'ps' => true,
+ 'ps_AF' => true,
+ 'pt' => true,
+ 'pt_AO' => true,
+ 'pt_BR' => true,
+ 'pt_CV' => true,
+ 'pt_GW' => true,
+ 'pt_MO' => true,
+ 'pt_MZ' => true,
+ 'pt_PT' => true,
+ 'pt_ST' => true,
+ 'pt_TL' => true,
+ 'rm' => true,
+ 'rm_CH' => true,
+ 'rn' => true,
+ 'rn_BI' => true,
+ 'ro' => true,
+ 'ro_MD' => true,
+ 'ro_RO' => true,
+ 'rof' => true,
+ 'rof_TZ' => true,
+ 'ru' => true,
+ 'ru_BY' => true,
+ 'ru_KG' => true,
+ 'ru_KZ' => true,
+ 'ru_MD' => true,
+ 'ru_RU' => true,
+ 'ru_UA' => true,
+ 'rw' => true,
+ 'rw_RW' => true,
+ 'rwk' => true,
+ 'rwk_TZ' => true,
+ 'sah' => true,
+ 'sah_RU' => true,
+ 'saq' => true,
+ 'saq_KE' => true,
+ 'sbp' => true,
+ 'sbp_TZ' => true,
+ 'se' => true,
+ 'se_FI' => true,
+ 'se_NO' => true,
+ 'seh' => true,
+ 'seh_MZ' => true,
+ 'ses' => true,
+ 'ses_ML' => true,
+ 'sg' => true,
+ 'sg_CF' => true,
+ 'shi' => true,
+ 'shi_Latn' => true,
+ 'shi_Latn_MA' => true,
+ 'shi_Tfng' => true,
+ 'shi_Tfng_MA' => true,
+ 'si' => true,
+ 'si_LK' => true,
+ 'sk' => true,
+ 'sk_SK' => true,
+ 'sl' => true,
+ 'sl_SI' => true,
+ 'sn' => true,
+ 'sn_ZW' => true,
+ 'so' => true,
+ 'so_DJ' => true,
+ 'so_ET' => true,
+ 'so_KE' => true,
+ 'so_SO' => true,
+ 'sq' => true,
+ 'sq_AL' => true,
+ 'sq_MK' => true,
+ 'sq_XK' => true,
+ 'sr' => true,
+ 'sr_Cyrl' => true,
+ 'sr_Cyrl_BA' => true,
+ 'sr_Cyrl_ME' => true,
+ 'sr_Cyrl_RS' => true,
+ 'sr_Cyrl_XK' => true,
+ 'sr_Latn' => true,
+ 'sr_Latn_BA' => true,
+ 'sr_Latn_ME' => true,
+ 'sr_Latn_RS' => true,
+ 'sr_Latn_XK' => true,
+ 'ss' => true,
+ 'ss_SZ' => true,
+ 'ss_ZA' => true,
+ 'ssy' => true,
+ 'ssy_ER' => true,
+ 'st' => true,
+ 'st_LS' => true,
+ 'st_ZA' => true,
+ 'sv' => true,
+ 'sv_AX' => true,
+ 'sv_FI' => true,
+ 'sv_SE' => true,
+ 'sw' => true,
+ 'sw_KE' => true,
+ 'sw_TZ' => true,
+ 'sw_UG' => true,
+ 'swc' => true,
+ 'swc_CD' => true,
+ 'ta' => true,
+ 'ta_IN' => true,
+ 'ta_LK' => true,
+ 'ta_MY' => true,
+ 'ta_SG' => true,
+ 'te' => true,
+ 'te_IN' => true,
+ 'teo' => true,
+ 'teo_KE' => true,
+ 'teo_UG' => true,
+ 'tg' => true,
+ 'tg_Cyrl' => true,
+ 'tg_Cyrl_TJ' => true,
+ 'th' => true,
+ 'th_TH' => true,
+ 'ti' => true,
+ 'ti_ER' => true,
+ 'ti_ET' => true,
+ 'tig' => true,
+ 'tig_ER' => true,
+ 'tn' => true,
+ 'tn_BW' => true,
+ 'tn_ZA' => true,
+ 'to' => true,
+ 'to_TO' => true,
+ 'tr' => true,
+ 'tr_CY' => true,
+ 'tr_TR' => true,
+ 'ts' => true,
+ 'ts_ZA' => true,
+ 'twq' => true,
+ 'twq_NE' => true,
+ 'tzm' => true,
+ 'tzm_Latn' => true,
+ 'tzm_Latn_MA' => true,
+ 'ug' => true,
+ 'ug_Arab' => true,
+ 'ug_Arab_CN' => true,
+ 'uk' => true,
+ 'uk_UA' => true,
+ 'ur' => true,
+ 'ur_IN' => true,
+ 'ur_PK' => true,
+ 'uz' => true,
+ 'uz_Arab' => true,
+ 'uz_Arab_AF' => true,
+ 'uz_Cyrl' => true,
+ 'uz_Cyrl_UZ' => true,
+ 'uz_Latn' => true,
+ 'uz_Latn_UZ' => true,
+ 'vai' => true,
+ 'vai_Latn' => true,
+ 'vai_Latn_LR' => true,
+ 'vai_Vaii' => true,
+ 'vai_Vaii_LR' => true,
+ 've' => true,
+ 've_ZA' => true,
+ 'vi' => true,
+ 'vi_VN' => true,
+ 'vo' => true,
+ 'vo_001' => true,
+ 'vun' => true,
+ 'vun_TZ' => true,
+ 'wae' => true,
+ 'wae_CH' => true,
+ 'wal' => true,
+ 'wal_ET' => true,
+ 'xh' => true,
+ 'xh_ZA' => true,
+ 'xog' => true,
+ 'xog_UG' => true,
+ 'yav' => true,
+ 'yav_CM' => true,
+ 'yo' => true,
+ 'yo_BJ' => true,
+ 'yo_NG' => true,
+ 'zgh' => true,
+ 'zgh_MA' => true,
+ 'zh' => true,
+ 'zh_Hans' => true,
+ 'zh_Hans_CN' => true,
+ 'zh_Hans_HK' => true,
+ 'zh_Hans_MO' => true,
+ 'zh_Hans_SG' => true,
+ 'zh_Hant' => true,
+ 'zh_Hant_HK' => true,
+ 'zh_Hant_MO' => true,
+ 'zh_Hant_TW' => true,
+ 'zu' => true,
+ 'zu_ZA' => true,
+ );
+
+ /**
+ * Class wide Locale Constants
+ *
+ * @var array $_territoryData
+ */
+ private static $_territoryData = array(
+ 'AD' => 'ca_AD',
+ 'AE' => 'ar_AE',
+ 'AF' => 'fa_AF',
+ 'AG' => 'en_AG',
+ 'AI' => 'en_AI',
+ 'AL' => 'sq_AL',
+ 'AM' => 'hy_AM',
+ 'AN' => 'pap_AN',
+ 'AO' => 'pt_AO',
+ 'AQ' => 'und_AQ',
+ 'AR' => 'es_AR',
+ 'AS' => 'sm_AS',
+ 'AT' => 'de_AT',
+ 'AU' => 'en_AU',
+ 'AW' => 'nl_AW',
+ 'AX' => 'sv_AX',
+ 'AZ' => 'az_Latn_AZ',
+ 'BA' => 'bs_BA',
+ 'BB' => 'en_BB',
+ 'BD' => 'bn_BD',
+ 'BE' => 'nl_BE',
+ 'BF' => 'mos_BF',
+ 'BG' => 'bg_BG',
+ 'BH' => 'ar_BH',
+ 'BI' => 'rn_BI',
+ 'BJ' => 'fr_BJ',
+ 'BL' => 'fr_BL',
+ 'BM' => 'en_BM',
+ 'BN' => 'ms_BN',
+ 'BO' => 'es_BO',
+ 'BR' => 'pt_BR',
+ 'BS' => 'en_BS',
+ 'BT' => 'dz_BT',
+ 'BV' => 'und_BV',
+ 'BW' => 'en_BW',
+ 'BY' => 'be_BY',
+ 'BZ' => 'en_BZ',
+ 'CA' => 'en_CA',
+ 'CC' => 'ms_CC',
+ 'CD' => 'sw_CD',
+ 'CF' => 'fr_CF',
+ 'CG' => 'fr_CG',
+ 'CH' => 'de_CH',
+ 'CI' => 'fr_CI',
+ 'CK' => 'en_CK',
+ 'CL' => 'es_CL',
+ 'CM' => 'fr_CM',
+ 'CN' => 'zh_Hans_CN',
+ 'CO' => 'es_CO',
+ 'CR' => 'es_CR',
+ 'CU' => 'es_CU',
+ 'CV' => 'kea_CV',
+ 'CX' => 'en_CX',
+ 'CY' => 'el_CY',
+ 'CZ' => 'cs_CZ',
+ 'DE' => 'de_DE',
+ 'DJ' => 'aa_DJ',
+ 'DK' => 'da_DK',
+ 'DM' => 'en_DM',
+ 'DO' => 'es_DO',
+ 'DZ' => 'ar_DZ',
+ 'EC' => 'es_EC',
+ 'EE' => 'et_EE',
+ 'EG' => 'ar_EG',
+ 'EH' => 'ar_EH',
+ 'ER' => 'ti_ER',
+ 'ES' => 'es_ES',
+ 'ET' => 'en_ET',
+ 'FI' => 'fi_FI',
+ 'FJ' => 'hi_FJ',
+ 'FK' => 'en_FK',
+ 'FM' => 'chk_FM',
+ 'FO' => 'fo_FO',
+ 'FR' => 'fr_FR',
+ 'GA' => 'fr_GA',
+ 'GB' => 'en_GB',
+ 'GD' => 'en_GD',
+ 'GE' => 'ka_GE',
+ 'GF' => 'fr_GF',
+ 'GG' => 'en_GG',
+ 'GH' => 'ak_GH',
+ 'GI' => 'en_GI',
+ 'GL' => 'iu_GL',
+ 'GM' => 'en_GM',
+ 'GN' => 'fr_GN',
+ 'GP' => 'fr_GP',
+ 'GQ' => 'fan_GQ',
+ 'GR' => 'el_GR',
+ 'GS' => 'und_GS',
+ 'GT' => 'es_GT',
+ 'GU' => 'en_GU',
+ 'GW' => 'pt_GW',
+ 'GY' => 'en_GY',
+ 'HK' => 'zh_Hant_HK',
+ 'HM' => 'und_HM',
+ 'HN' => 'es_HN',
+ 'HR' => 'hr_HR',
+ 'HT' => 'ht_HT',
+ 'HU' => 'hu_HU',
+ 'ID' => 'id_ID',
+ 'IE' => 'en_IE',
+ 'IL' => 'he_IL',
+ 'IM' => 'en_IM',
+ 'IN' => 'hi_IN',
+ 'IO' => 'und_IO',
+ 'IQ' => 'ar_IQ',
+ 'IR' => 'fa_IR',
+ 'IS' => 'is_IS',
+ 'IT' => 'it_IT',
+ 'JE' => 'en_JE',
+ 'JM' => 'en_JM',
+ 'JO' => 'ar_JO',
+ 'JP' => 'ja_JP',
+ 'KE' => 'en_KE',
+ 'KG' => 'ky_Cyrl_KG',
+ 'KH' => 'km_KH',
+ 'KI' => 'en_KI',
+ 'KM' => 'ar_KM',
+ 'KN' => 'en_KN',
+ 'KP' => 'ko_KP',
+ 'KR' => 'ko_KR',
+ 'KW' => 'ar_KW',
+ 'KY' => 'en_KY',
+ 'KZ' => 'ru_KZ',
+ 'LA' => 'lo_LA',
+ 'LB' => 'ar_LB',
+ 'LC' => 'en_LC',
+ 'LI' => 'de_LI',
+ 'LK' => 'si_LK',
+ 'LR' => 'en_LR',
+ 'LS' => 'st_LS',
+ 'LT' => 'lt_LT',
+ 'LU' => 'fr_LU',
+ 'LV' => 'lv_LV',
+ 'LY' => 'ar_LY',
+ 'MA' => 'ar_MA',
+ 'MC' => 'fr_MC',
+ 'MD' => 'ro_MD',
+ 'ME' => 'sr_Latn_ME',
+ 'MF' => 'fr_MF',
+ 'MG' => 'mg_MG',
+ 'MH' => 'mh_MH',
+ 'MK' => 'mk_MK',
+ 'ML' => 'bm_ML',
+ 'MM' => 'my_MM',
+ 'MN' => 'mn_Cyrl_MN',
+ 'MO' => 'zh_Hant_MO',
+ 'MP' => 'en_MP',
+ 'MQ' => 'fr_MQ',
+ 'MR' => 'ar_MR',
+ 'MS' => 'en_MS',
+ 'MT' => 'mt_MT',
+ 'MU' => 'mfe_MU',
+ 'MV' => 'dv_MV',
+ 'MW' => 'ny_MW',
+ 'MX' => 'es_MX',
+ 'MY' => 'ms_MY',
+ 'MZ' => 'pt_MZ',
+ 'NA' => 'kj_NA',
+ 'NC' => 'fr_NC',
+ 'NE' => 'ha_Latn_NE',
+ 'NF' => 'en_NF',
+ 'NG' => 'en_NG',
+ 'NI' => 'es_NI',
+ 'NL' => 'nl_NL',
+ 'NO' => 'nb_NO',
+ 'NP' => 'ne_NP',
+ 'NR' => 'en_NR',
+ 'NU' => 'niu_NU',
+ 'NZ' => 'en_NZ',
+ 'OM' => 'ar_OM',
+ 'PA' => 'es_PA',
+ 'PE' => 'es_PE',
+ 'PF' => 'fr_PF',
+ 'PG' => 'tpi_PG',
+ 'PH' => 'fil_PH',
+ 'PK' => 'ur_PK',
+ 'PL' => 'pl_PL',
+ 'PM' => 'fr_PM',
+ 'PN' => 'en_PN',
+ 'PR' => 'es_PR',
+ 'PS' => 'ar_PS',
+ 'PT' => 'pt_PT',
+ 'PW' => 'pau_PW',
+ 'PY' => 'gn_PY',
+ 'QA' => 'ar_QA',
+ 'RE' => 'fr_RE',
+ 'RO' => 'ro_RO',
+ 'RS' => 'sr_Cyrl_RS',
+ 'RU' => 'ru_RU',
+ 'RW' => 'rw_RW',
+ 'SA' => 'ar_SA',
+ 'SB' => 'en_SB',
+ 'SC' => 'crs_SC',
+ 'SD' => 'ar_SD',
+ 'SE' => 'sv_SE',
+ 'SG' => 'en_SG',
+ 'SH' => 'en_SH',
+ 'SI' => 'sl_SI',
+ 'SJ' => 'nb_SJ',
+ 'SK' => 'sk_SK',
+ 'SL' => 'kri_SL',
+ 'SM' => 'it_SM',
+ 'SN' => 'fr_SN',
+ 'SO' => 'sw_SO',
+ 'SR' => 'srn_SR',
+ 'ST' => 'pt_ST',
+ 'SV' => 'es_SV',
+ 'SY' => 'ar_SY',
+ 'SZ' => 'en_SZ',
+ 'TC' => 'en_TC',
+ 'TD' => 'fr_TD',
+ 'TF' => 'und_TF',
+ 'TG' => 'fr_TG',
+ 'TH' => 'th_TH',
+ 'TJ' => 'tg_Cyrl_TJ',
+ 'TK' => 'tkl_TK',
+ 'TL' => 'pt_TL',
+ 'TM' => 'tk_TM',
+ 'TN' => 'ar_TN',
+ 'TO' => 'to_TO',
+ 'TR' => 'tr_TR',
+ 'TT' => 'en_TT',
+ 'TV' => 'tvl_TV',
+ 'TW' => 'zh_Hant_TW',
+ 'TZ' => 'sw_TZ',
+ 'UA' => 'uk_UA',
+ 'UG' => 'sw_UG',
+ 'UM' => 'en_UM',
+ 'US' => 'en_US',
+ 'UY' => 'es_UY',
+ 'UZ' => 'uz_Cyrl_UZ',
+ 'VA' => 'it_VA',
+ 'VC' => 'en_VC',
+ 'VE' => 'es_VE',
+ 'VG' => 'en_VG',
+ 'VI' => 'en_VI',
+ 'VN' => 'vi_VN',
+ 'VU' => 'bi_VU',
+ 'WF' => 'wls_WF',
+ 'WS' => 'sm_WS',
+ 'YE' => 'ar_YE',
+ 'YT' => 'swb_YT',
+ 'ZA' => 'en_ZA',
+ 'ZM' => 'en_ZM',
+ 'ZW' => 'sn_ZW'
+ );
+
+ /**
+ * Autosearch constants
+ */
+ const BROWSER = 'browser';
+ const ENVIRONMENT = 'environment';
+ const ZFDEFAULT = 'default';
+
+ /**
+ * Defines if old behaviour should be supported
+ * Old behaviour throws notices and will be deleted in future releases
+ *
+ * @var boolean
+ */
+ public static $compatibilityMode = false;
+
+ /**
+ * Internal variable
+ *
+ * @var boolean
+ */
+ private static $_breakChain = false;
+
+ /**
+ * Actual set locale
+ *
+ * @var string Locale
+ */
+ protected $_locale;
+
+ /**
+ * Automatic detected locale
+ *
+ * @var string Locales
+ */
+ protected static $_auto;
+
+ /**
+ * Browser detected locale
+ *
+ * @var string Locales
+ */
+ protected static $_browser;
+
+ /**
+ * Environment detected locale
+ *
+ * @var string Locales
+ */
+ protected static $_environment;
+
+ /**
+ * Default locale
+ *
+ * @var string Locales
+ */
+ protected static $_default = array('en' => true);
+
+ /**
+ * Generates a locale object
+ * If no locale is given a automatic search is done
+ * Then the most probable locale will be automatically set
+ * Search order is
+ * 1. Given Locale
+ * 2. HTTP Client
+ * 3. Server Environment
+ * 4. Framework Standard
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for parsing input
+ * @throws Zend_Locale_Exception When autodetection has been failed
+ */
+ public function __construct($locale = null)
+ {
+ $this->setLocale($locale);
+ }
+
+ /**
+ * Serialization Interface
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ return serialize($this);
+ }
+
+ /**
+ * Returns a string representation of the object
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return (string) $this->_locale;
+ }
+
+ /**
+ * Returns a string representation of the object
+ * Alias for toString
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Return the default locale
+ *
+ * @return array Returns an array of all locale string
+ */
+ public static function getDefault()
+ {
+ if ((self::$compatibilityMode === true) or (func_num_args() > 0)) {
+ if (!self::$_breakChain) {
+ self::$_breakChain = true;
+ trigger_error('You are running Zend_Locale in compatibility mode... please migrate your scripts', E_USER_NOTICE);
+ $params = func_get_args();
+ $param = null;
+ if (isset($params[0])) {
+ $param = $params[0];
+ }
+ return self::getOrder($param);
+ }
+
+ self::$_breakChain = false;
+ }
+
+ return self::$_default;
+ }
+
+ /**
+ * Sets a new default locale which will be used when no locale can be detected
+ * If provided you can set a quality between 0 and 1 (or 2 and 100)
+ * which represents the percent of quality the browser
+ * requested within HTTP
+ *
+ * @param string|Zend_Locale $locale Locale to set
+ * @param float $quality The quality to set from 0 to 1
+ * @throws Zend_Locale_Exception When a autolocale was given
+ * @throws Zend_Locale_Exception When a unknown locale was given
+ * @return void
+ */
+ public static function setDefault($locale, $quality = 1)
+ {
+ if (($locale === 'auto') or ($locale === 'root') or ($locale === 'default') or
+ ($locale === 'environment') or ($locale === 'browser')) {
+ throw new Zend_Locale_Exception('Only full qualified locales can be used as default!');
+ }
+
+ if (($quality < 0.1) or ($quality > 100)) {
+ throw new Zend_Locale_Exception("Quality must be between 0.1 and 100");
+ }
+
+ if ($quality > 1) {
+ $quality /= 100;
+ }
+
+ $locale = self::_prepareLocale($locale);
+ if (isset(self::$_localeData[(string) $locale]) === true) {
+ self::$_default = array((string) $locale => $quality);
+ } else {
+ $elocale = explode('_', (string) $locale);
+ if (isset(self::$_localeData[$elocale[0]]) === true) {
+ self::$_default = array($elocale[0] => $quality);
+ } else {
+ throw new Zend_Locale_Exception("Unknown locale '" . (string) $locale . "' can not be set as default!");
+ }
+ }
+
+ self::$_auto = self::getBrowser() + self::getEnvironment() + self::getDefault();
+ }
+
+ /**
+ * Expects the Systems standard locale
+ *
+ * For Windows:
+ * f.e.: LC_COLLATE=C;LC_CTYPE=German_Austria.1252;LC_MONETARY=C
+ * would be recognised as de_AT
+ *
+ * @return array
+ */
+ public static function getEnvironment()
+ {
+ if (self::$_environment !== null) {
+ return self::$_environment;
+ }
+
+
+ $language = setlocale(LC_ALL, 0);
+ $languages = explode(';', $language);
+ $languagearray = array();
+
+ foreach ($languages as $locale) {
+ if (strpos($locale, '=') !== false) {
+ $language = substr($locale, strpos($locale, '='));
+ $language = substr($language, 1);
+ }
+
+ if ($language !== 'C') {
+ if (strpos($language, '.') !== false) {
+ $language = substr($language, 0, strpos($language, '.'));
+ } else if (strpos($language, '@') !== false) {
+ $language = substr($language, 0, strpos($language, '@'));
+ }
+
+ $language = str_ireplace(
+ array_keys(Zend_Locale_Data_Translation::$languageTranslation),
+ array_values(Zend_Locale_Data_Translation::$languageTranslation),
+ (string) $language
+ );
+
+ $language = str_ireplace(
+ array_keys(Zend_Locale_Data_Translation::$regionTranslation),
+ array_values(Zend_Locale_Data_Translation::$regionTranslation),
+ $language
+ );
+
+ if (isset(self::$_localeData[$language]) === true) {
+ $languagearray[$language] = 1;
+ if (strpos($language, '_') !== false) {
+ $languagearray[substr($language, 0, strpos($language, '_'))] = 1;
+ }
+ }
+ }
+ }
+
+ self::$_environment = $languagearray;
+ return $languagearray;
+ }
+
+ /**
+ * Return an array of all accepted languages of the client
+ * Expects RFC compilant Header !!
+ *
+ * The notation can be :
+ * de,en-UK-US;q=0.5,fr-FR;q=0.2
+ *
+ * @return array - list of accepted languages including quality
+ */
+ public static function getBrowser()
+ {
+ if (self::$_browser !== null) {
+ return self::$_browser;
+ }
+
+ $httplanguages = getenv('HTTP_ACCEPT_LANGUAGE');
+ if (empty($httplanguages) && array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER)) {
+ $httplanguages = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+ }
+
+ $languages = array();
+ if (empty($httplanguages)) {
+ return $languages;
+ }
+
+ $accepted = preg_split('/,\s*/', $httplanguages);
+
+ foreach ($accepted as $accept) {
+ $match = null;
+ $result = preg_match('/^([a-z]{1,8}(?:[-_][a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i',
+ $accept, $match);
+
+ if ($result < 1) {
+ continue;
+ }
+
+ if (isset($match[2]) === true) {
+ $quality = (float) $match[2];
+ } else {
+ $quality = 1.0;
+ }
+
+ $countrys = explode('-', $match[1]);
+ $region = array_shift($countrys);
+
+ $country2 = explode('_', $region);
+ $region = array_shift($country2);
+
+ foreach ($countrys as $country) {
+ $languages[$region . '_' . strtoupper($country)] = $quality;
+ }
+
+ foreach ($country2 as $country) {
+ $languages[$region . '_' . strtoupper($country)] = $quality;
+ }
+
+ if ((isset($languages[$region]) === false) || ($languages[$region] < $quality)) {
+ $languages[$region] = $quality;
+ }
+ }
+
+ self::$_browser = $languages;
+ return $languages;
+ }
+
+ /**
+ * Sets a new locale
+ *
+ * @param string|Zend_Locale $locale (Optional) New locale to set
+ * @return void
+ */
+ public function setLocale($locale = null)
+ {
+ $locale = self::_prepareLocale($locale);
+
+ if (isset(self::$_localeData[(string) $locale]) === false) {
+ // Is it an alias? If so, we can use this locale
+ if (isset(self::$_localeAliases[$locale]) === true) {
+ $this->_locale = $locale;
+ return;
+ }
+
+ $region = substr((string) $locale, 0, 3);
+ if (isset($region[2]) === true) {
+ if (($region[2] === '_') or ($region[2] === '-')) {
+ $region = substr($region, 0, 2);
+ }
+ }
+
+ if (isset(self::$_localeData[(string) $region]) === true) {
+ $this->_locale = $region;
+ } else {
+ $this->_locale = 'root';
+ }
+ } else {
+ $this->_locale = $locale;
+ }
+ }
+
+ /**
+ * Returns the language part of the locale
+ *
+ * @return string
+ */
+ public function getLanguage()
+ {
+ $locale = explode('_', $this->_locale);
+ return $locale[0];
+ }
+
+ /**
+ * Returns the region part of the locale if available
+ *
+ * @return string|false - Regionstring
+ */
+ public function getRegion()
+ {
+ $locale = explode('_', $this->_locale);
+ if (isset($locale[1]) === true) {
+ return $locale[1];
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the accepted charset of the client
+ *
+ * @return string
+ */
+ public static function getHttpCharset()
+ {
+ $httpcharsets = getenv('HTTP_ACCEPT_CHARSET');
+
+ $charsets = array();
+ if ($httpcharsets === false) {
+ return $charsets;
+ }
+
+ $accepted = preg_split('/,\s*/', $httpcharsets);
+ foreach ($accepted as $accept) {
+ if (empty($accept) === true) {
+ continue;
+ }
+
+ if (strpos($accept, ';') !== false) {
+ $quality = (float) substr($accept, (strpos($accept, '=') + 1));
+ $pos = substr($accept, 0, strpos($accept, ';'));
+ $charsets[$pos] = $quality;
+ } else {
+ $quality = 1.0;
+ $charsets[$accept] = $quality;
+ }
+ }
+
+ return $charsets;
+ }
+
+ /**
+ * Returns true if both locales are equal
+ *
+ * @param Zend_Locale $object Locale to check for equality
+ * @return boolean
+ */
+ public function equals(Zend_Locale $object)
+ {
+ if ($object->toString() === $this->toString()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns localized informations as array, supported are several
+ * types of informations.
+ * For detailed information about the types look into the documentation
+ *
+ * @param string $path (Optional) Type of information to return
+ * @param string|Zend_Locale $locale (Optional) Locale|Language for which this informations should be returned
+ * @param string $value (Optional) Value for detail list
+ * @return array Array with the wished information in the given language
+ */
+ public static function getTranslationList($path = null, $locale = null, $value = null)
+ {
+ $locale = self::findLocale($locale);
+ $result = Zend_Locale_Data::getList($locale, $path, $value);
+ if (empty($result) === true) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns an array with the name of all languages translated to the given language
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for language translation
+ * @return array
+ * @deprecated
+ */
+ public static function getLanguageTranslationList($locale = null)
+ {
+ trigger_error("The method getLanguageTranslationList is deprecated. Use getTranslationList('language', $locale) instead", E_USER_NOTICE);
+ return self::getTranslationList('language', $locale);
+ }
+
+ /**
+ * Returns an array with the name of all scripts translated to the given language
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for script translation
+ * @return array
+ * @deprecated
+ */
+ public static function getScriptTranslationList($locale = null)
+ {
+ trigger_error("The method getScriptTranslationList is deprecated. Use getTranslationList('script', $locale) instead", E_USER_NOTICE);
+ return self::getTranslationList('script', $locale);
+ }
+
+ /**
+ * Returns an array with the name of all countries translated to the given language
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for country translation
+ * @return array
+ * @deprecated
+ */
+ public static function getCountryTranslationList($locale = null)
+ {
+ trigger_error("The method getCountryTranslationList is deprecated. Use getTranslationList('territory', $locale, 2) instead", E_USER_NOTICE);
+ return self::getTranslationList('territory', $locale, 2);
+ }
+
+ /**
+ * Returns an array with the name of all territories translated to the given language
+ * All territories contains other countries.
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for territory translation
+ * @return array
+ * @deprecated
+ */
+ public static function getTerritoryTranslationList($locale = null)
+ {
+ trigger_error("The method getTerritoryTranslationList is deprecated. Use getTranslationList('territory', $locale, 1) instead", E_USER_NOTICE);
+ return self::getTranslationList('territory', $locale, 1);
+ }
+
+ /**
+ * Returns a localized information string, supported are several types of informations.
+ * For detailed information about the types look into the documentation
+ *
+ * @param string $value Name to get detailed information about
+ * @param string $path (Optional) Type of information to return
+ * @param string|Zend_Locale $locale (Optional) Locale|Language for which this informations should be returned
+ * @return string|false The wished information in the given language
+ */
+ public static function getTranslation($value = null, $path = null, $locale = null)
+ {
+ $locale = self::findLocale($locale);
+ $result = Zend_Locale_Data::getContent($locale, $path, $value);
+ if (empty($result) === true && '0' !== $result) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns the localized language name
+ *
+ * @param string $value Name to get detailed information about
+ * @param string $locale (Optional) Locale for language translation
+ * @return array
+ * @deprecated
+ */
+ public static function getLanguageTranslation($value, $locale = null)
+ {
+ trigger_error("The method getLanguageTranslation is deprecated. Use getTranslation($value, 'language', $locale) instead", E_USER_NOTICE);
+ return self::getTranslation($value, 'language', $locale);
+ }
+
+ /**
+ * Returns the localized script name
+ *
+ * @param string $value Name to get detailed information about
+ * @param string $locale (Optional) locale for script translation
+ * @return array
+ * @deprecated
+ */
+ public static function getScriptTranslation($value, $locale = null)
+ {
+ trigger_error("The method getScriptTranslation is deprecated. Use getTranslation($value, 'script', $locale) instead", E_USER_NOTICE);
+ return self::getTranslation($value, 'script', $locale);
+ }
+
+ /**
+ * Returns the localized country name
+ *
+ * @param string $value Name to get detailed information about
+ * @param string|Zend_Locale $locale (Optional) Locale for country translation
+ * @return array
+ * @deprecated
+ */
+ public static function getCountryTranslation($value, $locale = null)
+ {
+ trigger_error("The method getCountryTranslation is deprecated. Use getTranslation($value, 'country', $locale) instead", E_USER_NOTICE);
+ return self::getTranslation($value, 'country', $locale);
+ }
+
+ /**
+ * Returns the localized territory name
+ * All territories contains other countries.
+ *
+ * @param string $value Name to get detailed information about
+ * @param string|Zend_Locale $locale (Optional) Locale for territory translation
+ * @return array
+ * @deprecated
+ */
+ public static function getTerritoryTranslation($value, $locale = null)
+ {
+ trigger_error("The method getTerritoryTranslation is deprecated. Use getTranslation($value, 'territory', $locale) instead", E_USER_NOTICE);
+ return self::getTranslation($value, 'territory', $locale);
+ }
+
+ /**
+ * Returns an array with translated yes strings
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale for language translation (defaults to $this locale)
+ * @return array
+ */
+ public static function getQuestion($locale = null)
+ {
+ $locale = self::findLocale($locale);
+ $quest = Zend_Locale_Data::getList($locale, 'question');
+ $yes = explode(':', $quest['yes']);
+ $no = explode(':', $quest['no']);
+ $quest['yes'] = $yes[0];
+ $quest['yesarray'] = $yes;
+ $quest['no'] = $no[0];
+ $quest['noarray'] = $no;
+ $quest['yesexpr'] = self::_prepareQuestionString($yes);
+ $quest['noexpr'] = self::_prepareQuestionString($no);
+
+ return $quest;
+ }
+
+ /**
+ * Internal function for preparing the returned question regex string
+ *
+ * @param string $input Regex to parse
+ * @return string
+ */
+ private static function _prepareQuestionString($input)
+ {
+ $regex = '';
+ if (is_array($input) === true) {
+ $regex = '^';
+ $start = true;
+ foreach ($input as $row) {
+ if ($start === false) {
+ $regex .= '|';
+ }
+
+ $start = false;
+ $regex .= '(';
+ $one = null;
+ if (strlen($row) > 2) {
+ $one = true;
+ }
+
+ foreach (str_split($row, 1) as $char) {
+ $regex .= '[' . $char;
+ $regex .= strtoupper($char) . ']';
+ if ($one === true) {
+ $one = false;
+ $regex .= '(';
+ }
+ }
+
+ if ($one === false) {
+ $regex .= ')';
+ }
+
+ $regex .= '?)';
+ }
+ }
+
+ return $regex;
+ }
+
+ /**
+ * Checks if a locale identifier is a real locale or not
+ * Examples:
+ * "en_XX" refers to "en", which returns true
+ * "XX_yy" refers to "root", which returns false
+ *
+ * @param string|Zend_Locale $locale Locale to check for
+ * @param boolean $strict (Optional) If true, no rerouting will be done when checking
+ * @param boolean $compatible (DEPRECATED) Only for internal usage, brakes compatibility mode
+ * @return boolean If the locale is known dependend on the settings
+ */
+ public static function isLocale($locale, $strict = false, $compatible = true)
+ {
+ if (($locale instanceof Zend_Locale)
+ || (is_string($locale) && array_key_exists($locale, self::$_localeData))
+ ) {
+ return true;
+ }
+
+ // Is it an alias?
+ if (is_string($locale) && array_key_exists($locale, self::$_localeAliases)) {
+ return true;
+ }
+
+ if (($locale === null) || (!is_string($locale) and !is_array($locale))) {
+ return false;
+ }
+
+ try {
+ $locale = self::_prepareLocale($locale, $strict);
+ } catch (Zend_Locale_Exception $e) {
+ return false;
+ }
+
+ if (($compatible === true) and (self::$compatibilityMode === true)) {
+ trigger_error('You are running Zend_Locale in compatibility mode... please migrate your scripts', E_USER_NOTICE);
+ if (isset(self::$_localeData[$locale]) === true) {
+ return $locale;
+ } else if (!$strict) {
+ $locale = explode('_', $locale);
+ if (isset(self::$_localeData[$locale[0]]) === true) {
+ return $locale[0];
+ }
+ }
+ } else {
+ if (isset(self::$_localeData[$locale]) === true) {
+ return true;
+ } else if (!$strict) {
+ $locale = explode('_', $locale);
+ if (isset(self::$_localeData[$locale[0]]) === true) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Finds the proper locale based on the input
+ * Checks if it exists, degrades it when necessary
+ * Detects registry locale and when all fails tries to detect a automatic locale
+ * Returns the found locale as string
+ *
+ * @param string $locale
+ * @throws Zend_Locale_Exception When the given locale is no locale or the autodetection fails
+ * @return string
+ */
+ public static function findLocale($locale = null)
+ {
+ if ($locale === null) {
+ if (Zend_Registry::isRegistered('Zend_Locale')) {
+ $locale = Zend_Registry::get('Zend_Locale');
+ }
+ }
+
+ if ($locale === null) {
+ $locale = new Zend_Locale();
+ }
+
+ if (!Zend_Locale::isLocale($locale, true, false)) {
+ if (!Zend_Locale::isLocale($locale, false, false)) {
+ $locale = Zend_Locale::getLocaleToTerritory($locale);
+
+ if (empty($locale)) {
+ throw new Zend_Locale_Exception("The locale '$locale' is no known locale");
+ }
+ } else {
+ $locale = new Zend_Locale($locale);
+ }
+ }
+
+ $locale = self::_prepareLocale($locale);
+ return $locale;
+ }
+
+ /**
+ * Returns the expected locale for a given territory
+ *
+ * @param string $territory Territory for which the locale is being searched
+ * @return string|null Locale string or null when no locale has been found
+ */
+ public static function getLocaleToTerritory($territory)
+ {
+ $territory = strtoupper($territory);
+ if (array_key_exists($territory, self::$_territoryData)) {
+ return self::$_territoryData[$territory];
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a list of all known locales where the locale is the key
+ * Only real locales are returned, the internal locales 'root', 'auto', 'browser'
+ * and 'environment' are suppressed
+ *
+ * @return array List of all Locales
+ */
+ public static function getLocaleList()
+ {
+ $list = self::$_localeData;
+ unset($list['root']);
+ unset($list['auto']);
+ unset($list['browser']);
+ unset($list['environment']);
+ return $list;
+ }
+
+ /**
+ * Returns the set cache
+ *
+ * @return Zend_Cache_Core The set cache
+ */
+ public static function getCache()
+ {
+ return Zend_Locale_Data::getCache();
+ }
+
+ /**
+ * Sets a cache
+ *
+ * @param Zend_Cache_Core $cache Cache to set
+ * @return void
+ */
+ public static function setCache(Zend_Cache_Core $cache)
+ {
+ Zend_Locale_Data::setCache($cache);
+ }
+
+ /**
+ * Returns true when a cache is set
+ *
+ * @return boolean
+ */
+ public static function hasCache()
+ {
+ return Zend_Locale_Data::hasCache();
+ }
+
+ /**
+ * Removes any set cache
+ *
+ * @return void
+ */
+ public static function removeCache()
+ {
+ Zend_Locale_Data::removeCache();
+ }
+
+ /**
+ * Clears all set cache data
+ *
+ * @param string $tag Tag to clear when the default tag name is not used
+ * @return void
+ */
+ public static function clearCache($tag = null)
+ {
+ Zend_Locale_Data::clearCache($tag);
+ }
+
+ /**
+ * Disables the set cache
+ *
+ * @param boolean $flag True disables any set cache, default is false
+ * @return void
+ */
+ public static function disableCache($flag)
+ {
+ Zend_Locale_Data::disableCache($flag);
+ }
+
+ /**
+ * Internal function, returns a single locale on detection
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale to work on
+ * @param boolean $strict (Optional) Strict preparation
+ * @throws Zend_Locale_Exception When no locale is set which is only possible when the class was wrong extended
+ * @return string
+ */
+ private static function _prepareLocale($locale, $strict = false)
+ {
+ if ($locale instanceof Zend_Locale) {
+ $locale = $locale->toString();
+ }
+
+ if (is_array($locale)) {
+ return '';
+ }
+
+ if (empty(self::$_auto) === true) {
+ self::$_browser = self::getBrowser();
+ self::$_environment = self::getEnvironment();
+ self::$_breakChain = true;
+ self::$_auto = self::getBrowser() + self::getEnvironment() + self::getDefault();
+ }
+
+ if (!$strict) {
+ if ($locale === 'browser') {
+ $locale = self::$_browser;
+ }
+
+ if ($locale === 'environment') {
+ $locale = self::$_environment;
+ }
+
+ if ($locale === 'default') {
+ $locale = self::$_default;
+ }
+
+ if (($locale === 'auto') or ($locale === null)) {
+ $locale = self::$_auto;
+ }
+
+ if (is_array($locale) === true) {
+ $locale = key($locale);
+ }
+ }
+
+ // This can only happen when someone extends Zend_Locale and erases the default
+ if ($locale === null) {
+ throw new Zend_Locale_Exception('Autodetection of Locale has been failed!');
+ }
+
+ if (strpos($locale, '-') !== false) {
+ $locale = strtr($locale, '-', '_');
+ }
+
+ $parts = explode('_', $locale);
+ if (!isset(self::$_localeData[$parts[0]])) {
+ if ((count($parts) == 1) && array_key_exists($parts[0], self::$_territoryData)) {
+ return self::$_territoryData[$parts[0]];
+ }
+
+ return '';
+ }
+
+ foreach($parts as $key => $value) {
+ if ((strlen($value) < 2) || (strlen($value) > 3)) {
+ unset($parts[$key]);
+ }
+ }
+
+ $locale = implode('_', $parts);
+ return (string) $locale;
+ }
+
+ /**
+ * Search the locale automatically and return all used locales
+ * ordered by quality
+ *
+ * Standard Searchorder is Browser, Environment, Default
+ *
+ * @param string $searchorder (Optional) Searchorder
+ * @return array Returns an array of all detected locales
+ */
+ public static function getOrder($order = null)
+ {
+ switch ($order) {
+ case self::ENVIRONMENT:
+ self::$_breakChain = true;
+ $languages = self::getEnvironment() + self::getBrowser() + self::getDefault();
+ break;
+
+ case self::ZFDEFAULT:
+ self::$_breakChain = true;
+ $languages = self::getDefault() + self::getEnvironment() + self::getBrowser();
+ break;
+
+ default:
+ self::$_breakChain = true;
+ $languages = self::getBrowser() + self::getEnvironment() + self::getDefault();
+ break;
+ }
+
+ return $languages;
+ }
+
+ /**
+ * Is the given locale in the list of aliases?
+ *
+ * @param string|Zend_Locale $locale Locale to work on
+ * @return boolean
+ */
+ public static function isAlias($locale)
+ {
+ if ($locale instanceof Zend_Locale) {
+ $locale = $locale->toString();
+ }
+
+ return isset(self::$_localeAliases[$locale]);
+ }
+
+ /**
+ * Return an alias' actual locale.
+ *
+ * @param string|Zend_Locale $locale Locale to work on
+ * @return string
+ */
+ public static function getAlias($locale)
+ {
+ if ($locale instanceof Zend_Locale) {
+ $locale = $locale->toString();
+ }
+
+ if (isset(self::$_localeAliases[$locale]) === true) {
+ return self::$_localeAliases[$locale];
+ }
+
+ return (string) $locale;
+ }
+}
diff --git a/library/vendor/Zend/Locale/Data.php b/library/vendor/Zend/Locale/Data.php
new file mode 100644
index 0000000..a6a435a
--- /dev/null
+++ b/library/vendor/Zend/Locale/Data.php
@@ -0,0 +1,1609 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @subpackage Data
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * include needed classes
+ */
+
+/** @see Zend_Xml_Security */
+
+/**
+ * Locale data reader, handles the CLDR
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @subpackage Data
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale_Data
+{
+ /**
+ * Locale files
+ *
+ * @var array
+ */
+ private static $_ldml = array();
+
+ /**
+ * List of values which are collected
+ *
+ * @var array
+ */
+ private static $_list = array();
+
+ /**
+ * Internal cache for ldml values
+ *
+ * @var Zend_Cache_Core
+ */
+ private static $_cache = null;
+
+ /**
+ * Internal value to remember if cache supports tags
+ *
+ * @var boolean
+ */
+ private static $_cacheTags = false;
+
+ /**
+ * Internal option, cache disabled
+ *
+ * @var boolean
+ */
+ private static $_cacheDisabled = false;
+
+ /**
+ * Read the content from locale
+ *
+ * Can be called like:
+ * <ldml>
+ * <delimiter>test</delimiter>
+ * <second type='myone'>content</second>
+ * <second type='mysecond'>content2</second>
+ * <third type='mythird' />
+ * </ldml>
+ *
+ * Case 1: _readFile('ar','/ldml/delimiter') -> returns [] = test
+ * Case 1: _readFile('ar','/ldml/second[@type=myone]') -> returns [] = content
+ * Case 2: _readFile('ar','/ldml/second','type') -> returns [myone] = content; [mysecond] = content2
+ * Case 3: _readFile('ar','/ldml/delimiter',,'right') -> returns [right] = test
+ * Case 4: _readFile('ar','/ldml/third','type','myone') -> returns [myone] = mythird
+ *
+ * @param string $locale
+ * @param string $path
+ * @param string $attribute
+ * @param string $value
+ * @access private
+ * @return array
+ */
+ private static function _readFile($locale, $path, $attribute, $value, $temp)
+ {
+ // without attribute - read all values
+ // with attribute - read only this value
+ if (!empty(self::$_ldml[(string) $locale])) {
+
+ $result = self::$_ldml[(string) $locale]->xpath($path);
+ if (!empty($result)) {
+ foreach ($result as &$found) {
+
+ if (empty($value)) {
+
+ if (empty($attribute)) {
+ // Case 1
+ $temp[] = (string) $found;
+ } else if (empty($temp[(string) $found[$attribute]])){
+ // Case 2
+ $temp[(string) $found[$attribute]] = (string) $found;
+ }
+
+ } else if (empty ($temp[$value])) {
+
+ if (empty($attribute)) {
+ // Case 3
+ $temp[$value] = (string) $found;
+ } else {
+ // Case 4
+ $temp[$value] = (string) $found[$attribute];
+ }
+
+ }
+ }
+ }
+ }
+ return $temp;
+ }
+
+ /**
+ * Find possible routing to other path or locale
+ *
+ * @param string $locale
+ * @param string $path
+ * @param string $attribute
+ * @param string $value
+ * @param array $temp
+ * @return bool
+ * @throws Zend_Locale_Exception
+ */
+ private static function _findRoute($locale, $path, $attribute, $value, &$temp)
+ {
+ // load locale file if not already in cache
+ // needed for alias tag when referring to other locale
+ if (empty(self::$_ldml[(string) $locale])) {
+ $filename = dirname(__FILE__) . '/Data/' . $locale . '.xml';
+ if (!file_exists($filename)) {
+ throw new Zend_Locale_Exception("Missing locale file '$filename' for '$locale' locale.");
+ }
+
+ self::$_ldml[(string) $locale] = Zend_Xml_Security::scanFile($filename);
+ }
+
+ // search for 'alias' tag in the search path for redirection
+ $search = '';
+ $tok = strtok($path, '/');
+
+ // parse the complete path
+ if (!empty(self::$_ldml[(string) $locale])) {
+ while ($tok !== false) {
+ $search .= '/' . $tok;
+ if (strpos($search, '[@') !== false) {
+ while (strrpos($search, '[@') > strrpos($search, ']')) {
+ $tok = strtok('/');
+ if (empty($tok)) {
+ $search .= '/';
+ }
+ $search = $search . '/' . $tok;
+ }
+ }
+ $result = self::$_ldml[(string) $locale]->xpath($search . '/alias');
+
+ // alias found
+ if (!empty($result)) {
+
+ $source = $result[0]['source'];
+ $newpath = $result[0]['path'];
+
+ // new path - path //ldml is to ignore
+ if ($newpath != '//ldml') {
+ // other path - parse to make real path
+
+ while (substr($newpath,0,3) == '../') {
+ $newpath = substr($newpath, 3);
+ $search = substr($search, 0, strrpos($search, '/'));
+ }
+
+ // truncate ../ to realpath otherwise problems with alias
+ $path = $search . '/' . $newpath;
+ while (($tok = strtok('/'))!== false) {
+ $path = $path . '/' . $tok;
+ }
+ }
+
+ // reroute to other locale
+ if ($source != 'locale') {
+ $locale = $source;
+ }
+
+ $temp = self::_getFile($locale, $path, $attribute, $value, $temp);
+ return false;
+ }
+
+ $tok = strtok('/');
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Read the right LDML file
+ *
+ * @param string $locale
+ * @param string $path
+ * @param string|bool $attribute
+ * @param string|bool $value
+ * @param array $temp
+ * @return array
+ * @throws Zend_Locale_Exception
+ */
+ private static function _getFile($locale, $path, $attribute = false, $value = false, $temp = array())
+ {
+ $result = self::_findRoute($locale, $path, $attribute, $value, $temp);
+ if ($result) {
+ $temp = self::_readFile($locale, $path, $attribute, $value, $temp);
+ }
+
+ // parse required locales reversive
+ // example: when given zh_Hans_CN
+ // 1. -> zh_Hans_CN
+ // 2. -> zh_Hans
+ // 3. -> zh
+ // 4. -> root
+ if (($locale != 'root') && ($result)) {
+ // Search for parent locale
+ if (false !== strpos($locale, '_')) {
+ $parentLocale = self::getContent($locale, 'parentlocale');
+ if ($parentLocale) {
+ $temp = self::_getFile($parentLocale, $path, $attribute, $value, $temp);
+ }
+ }
+
+ $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
+ if (!empty($locale)) {
+ $temp = self::_getFile($locale, $path, $attribute, $value, $temp);
+ } else {
+ $temp = self::_getFile('root', $path, $attribute, $value, $temp);
+ }
+ }
+ return $temp;
+ }
+
+ /**
+ * Find the details for supplemental calendar datas
+ *
+ * @param string $locale Locale for Detaildata
+ * @param array $list List to search
+ * @return string Key for Detaildata
+ */
+ private static function _calendarDetail($locale, $list)
+ {
+ $ret = "001";
+ foreach ($list as $key => $value) {
+ if (strpos($locale, '_') !== false) {
+ $locale = substr($locale, strpos($locale, '_') + 1);
+ }
+ if (strpos($key, $locale) !== false) {
+ $ret = $key;
+ break;
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Internal function for checking the locale
+ *
+ * @param string|Zend_Locale $locale Locale to check
+ * @return string
+ * @throws Zend_Locale_Exception
+ */
+ private static function _checkLocale($locale)
+ {
+ if (empty($locale)) {
+ $locale = new Zend_Locale();
+ }
+
+ if (!(Zend_Locale::isLocale((string) $locale, null, false))) {
+ throw new Zend_Locale_Exception("Locale (" . (string) $locale . ") is a unknown locale");
+ }
+
+ if (Zend_Locale::isAlias($locale)) {
+ // Return a valid CLDR locale so that the XML file can be loaded.
+ return Zend_Locale::getAlias($locale);
+ }
+ return (string) $locale;
+ }
+
+ /**
+ * Read the LDML file, get a array of multipath defined value
+ *
+ * @param string $locale
+ * @param string $path
+ * @param bool|string $value
+ * @return array
+ * @throws Zend_Locale_Exception
+ */
+ public static function getList($locale, $path, $value = false)
+ {
+ $locale = self::_checkLocale($locale);
+
+ if (!isset(self::$_cache) && !self::$_cacheDisabled) {
+ self::$_cache = Zend_Cache::factory(
+ 'Core',
+ 'File',
+ array('automatic_serialization' => true),
+ array());
+ }
+
+ $val = $value;
+ if (is_array($value)) {
+ $val = implode('_' , $value);
+ }
+
+ $val = urlencode($val);
+ $id = self::_filterCacheId('Zend_LocaleL_' . $locale . '_' . $path . '_' . $val);
+ if (!self::$_cacheDisabled && ($result = self::$_cache->load($id))) {
+ return unserialize($result);
+ }
+
+ $temp = array();
+ switch(strtolower($path)) {
+ case 'language':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/languages/language', 'type');
+ break;
+
+ case 'script':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/scripts/script', 'type');
+ break;
+
+ case 'territory':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/territories/territory', 'type');
+ if ($value === 1) {
+ foreach($temp as $key => $value) {
+ if ((is_numeric($key) === false) and ($key != 'QO') and ($key != 'EU')) {
+ unset($temp[$key]);
+ }
+ }
+ } else if ($value === 2) {
+ foreach($temp as $key => $value) {
+ if (is_numeric($key) or ($key == 'QO') or ($key == 'EU')) {
+ unset($temp[$key]);
+ }
+ }
+ }
+ break;
+
+ case 'variant':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/variants/variant', 'type');
+ break;
+
+ case 'key':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/keys/key', 'type');
+ break;
+
+ case 'type':
+ if (empty($value)) {
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/types/type', 'type');
+ } else {
+ if (($value == 'calendar') or
+ ($value == 'collation') or
+ ($value == 'currency')) {
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/types/type[@key=\'' . $value . '\']', 'type');
+ } else {
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/types/type[@type=\'' . $value . '\']', 'type');
+ }
+ }
+ break;
+
+ case 'layout':
+ $temp = self::_getFile($locale, '/ldml/layout/orientation/characterOrder', '', 'characterOrder');
+ $temp += self::_getFile($locale, '/ldml/layout/orientation/lineOrder', '', 'lineOrder');
+ break;
+
+ case 'contexttransform':
+ if (empty($value)) {
+ $value = 'uiListOrMenu';
+ }
+ $temp = self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'languages\']/contextTransform[@type=\''.$value.'\']', '', 'languages');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'day-format-except-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'day-format-except-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'day-standalone-except-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'day-standalone-except-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'day-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'day-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'month-format-except-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'month-format-except-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'month-standalone-except-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'month-standalone-except-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'month-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'month-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'script\']/contextTransform[@type=\''.$value.'\']', '', 'script');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'territory\']/contextTransform[@type=\''.$value.'\']', '', 'territory');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'variant\']/contextTransform[@type=\''.$value.'\']', '', 'variant');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'key\']/contextTransform[@type=\''.$value.'\']', '', 'key');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'type\']/contextTransform[@type=\''.$value.'\']', '', 'type');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'era-name\']/contextTransform[@type=\''.$value.'\']', '', 'era-name');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'era-abbr\']/contextTransform[@type=\''.$value.'\']', '', 'era-abbr');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'era-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'era-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'quater-format-wide\']/contextTransform[@type=\''.$value.'\']', '', 'quater-format-wide');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'quater-standalone-wide\']/contextTransform[@type=\''.$value.'\']', '', 'quater-standalone-wide');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'quater-abbreviated\']/contextTransform[@type=\''.$value.'\']', '', 'quater-abbreviated');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'quater-narrow\']/contextTransform[@type=\''.$value.'\']', '', 'quater-narrow');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'calendar-field\']/contextTransform[@type=\''.$value.'\']', '', 'calendar-field');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'symbol\']/contextTransform[@type=\''.$value.'\']', '', 'symbol');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'tense\']/contextTransform[@type=\''.$value.'\']', '', 'tense');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'zone-exemplarCity\']/contextTransform[@type=\''.$value.'\']', '', 'zone-exemplarCity');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'zone-long\']/contextTransform[@type=\''.$value.'\']', '', 'zone-long');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'zone-short\']/contextTransform[@type=\''.$value.'\']', '', 'zone-short');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'metazone-long\']/contextTransform[@type=\''.$value.'\']', '', 'metazone-long');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'metazone-short\']/contextTransform[@type=\''.$value.'\']', '', 'metazone-short');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'displayName-count\']/contextTransform[@type=\''.$value.'\']', '', 'displayName-count');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'displayName\']/contextTransform[@type=\''.$value.'\']', '', 'displayName');
+ $temp += self::_getFile($locale, '/ldml/contextTransforms/contextTransformUsage[@type=\'unit-pattern\']/contextTransform[@type=\''.$value.'\']', '', 'unit-pattern');
+ break;
+
+ case 'characters':
+ $temp = self::_getFile($locale, '/ldml/characters/exemplarCharacters', '', 'characters');
+ $temp += self::_getFile($locale, '/ldml/characters/exemplarCharacters[@type=\'auxiliary\']', '', 'auxiliary');
+ // $temp += self::_getFile($locale, '/ldml/characters/exemplarCharacters[@type=\'currencySymbol\']', '', 'currencySymbol');
+ break;
+
+ case 'delimiters':
+ $temp = self::_getFile($locale, '/ldml/delimiters/quotationStart', '', 'quoteStart');
+ $temp += self::_getFile($locale, '/ldml/delimiters/quotationEnd', '', 'quoteEnd');
+ $temp += self::_getFile($locale, '/ldml/delimiters/alternateQuotationStart', '', 'quoteStartAlt');
+ $temp += self::_getFile($locale, '/ldml/delimiters/alternateQuotationEnd', '', 'quoteEndAlt');
+ break;
+
+ case 'measurement':
+ $temp = self::_getFile('supplementalData', '/supplementalData/measurementData/measurementSystem[@type=\'metric\']', 'territories', 'metric');
+ $temp += self::_getFile('supplementalData', '/supplementalData/measurementData/measurementSystem[@type=\'US\']', 'territories', 'US');
+ $temp += self::_getFile('supplementalData', '/supplementalData/measurementData/paperSize[@type=\'A4\']', 'territories', 'A4');
+ $temp += self::_getFile('supplementalData', '/supplementalData/measurementData/paperSize[@type=\'US-Letter\']', 'territories', 'US-Letter');
+ break;
+
+ case 'months':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp['context'] = "format";
+ $temp['default'] = "wide";
+ $temp['format']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/monthWidth[@type=\'abbreviated\']/month', 'type');
+ $temp['format']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/monthWidth[@type=\'narrow\']/month', 'type');
+ $temp['format']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/monthWidth[@type=\'wide\']/month', 'type');
+ $temp['stand-alone']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'stand-alone\']/monthWidth[@type=\'abbreviated\']/month', 'type');
+ $temp['stand-alone']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'stand-alone\']/monthWidth[@type=\'narrow\']/month', 'type');
+ $temp['stand-alone']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'stand-alone\']/monthWidth[@type=\'wide\']/month', 'type');
+ break;
+
+ case 'month':
+ if (empty($value)) {
+ $value = array("gregorian", "format", "wide");
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/months/monthContext[@type=\'' . $value[1] . '\']/monthWidth[@type=\'' . $value[2] . '\']/month', 'type');
+ break;
+
+ case 'days':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp['context'] = "format";
+ $temp['default'] = "wide";
+ $temp['format']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/dayWidth[@type=\'abbreviated\']/day', 'type');
+ $temp['format']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/dayWidth[@type=\'narrow\']/day', 'type');
+ $temp['format']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/dayWidth[@type=\'wide\']/day', 'type');
+ $temp['stand-alone']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'stand-alone\']/dayWidth[@type=\'abbreviated\']/day', 'type');
+ $temp['stand-alone']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'stand-alone\']/dayWidth[@type=\'narrow\']/day', 'type');
+ $temp['stand-alone']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'stand-alone\']/dayWidth[@type=\'wide\']/day', 'type');
+ break;
+
+ case 'day':
+ if (empty($value)) {
+ $value = array("gregorian", "format", "wide");
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/days/dayContext[@type=\'' . $value[1] . '\']/dayWidth[@type=\'' . $value[2] . '\']/day', 'type');
+ break;
+
+ case 'week':
+ $minDays = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/minDays', 'territories'));
+ $firstDay = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/firstDay', 'territories'));
+ $weekStart = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/weekendStart', 'territories'));
+ $weekEnd = self::_calendarDetail($locale, self::_getFile('supplementalData', '/supplementalData/weekData/weekendEnd', 'territories'));
+
+ $temp = self::_getFile('supplementalData', "/supplementalData/weekData/minDays[@territories='" . $minDays . "']", 'count', 'minDays');
+ $temp += self::_getFile('supplementalData', "/supplementalData/weekData/firstDay[@territories='" . $firstDay . "']", 'day', 'firstDay');
+ $temp += self::_getFile('supplementalData', "/supplementalData/weekData/weekendStart[@territories='" . $weekStart . "']", 'day', 'weekendStart');
+ $temp += self::_getFile('supplementalData', "/supplementalData/weekData/weekendEnd[@territories='" . $weekEnd . "']", 'day', 'weekendEnd');
+ break;
+
+ case 'quarters':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp['format']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'format\']/quarterWidth[@type=\'abbreviated\']/quarter', 'type');
+ $temp['format']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'format\']/quarterWidth[@type=\'narrow\']/quarter', 'type');
+ $temp['format']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'format\']/quarterWidth[@type=\'wide\']/quarter', 'type');
+ $temp['stand-alone']['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'stand-alone\']/quarterWidth[@type=\'abbreviated\']/quarter', 'type');
+ $temp['stand-alone']['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'stand-alone\']/quarterWidth[@type=\'narrow\']/quarter', 'type');
+ $temp['stand-alone']['wide'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/quarters/quarterContext[@type=\'stand-alone\']/quarterWidth[@type=\'wide\']/quarter', 'type');
+ break;
+
+ case 'quarter':
+ if (empty($value)) {
+ $value = array("gregorian", "format", "wide");
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/quarters/quarterContext[@type=\'' . $value[1] . '\']/quarterWidth[@type=\'' . $value[2] . '\']/quarter', 'type');
+ break;
+
+ case 'eras':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp['names'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/eras/eraNames/era', 'type');
+ $temp['abbreviated'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/eras/eraAbbr/era', 'type');
+ $temp['narrow'] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/eras/eraNarrow/era', 'type');
+ break;
+
+ case 'era':
+ if (empty($value)) {
+ $value = array("gregorian", "Abbr");
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/eras/era' . $value[1] . '/era', 'type');
+ break;
+
+ case 'date':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'full\']/dateFormat/pattern', '', 'full');
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'long\']/dateFormat/pattern', '', 'long');
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'medium\']/dateFormat/pattern', '', 'medium');
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'short\']/dateFormat/pattern', '', 'short');
+ break;
+
+ case 'time':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'full\']/timeFormat/pattern', '', 'full');
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'long\']/timeFormat/pattern', '', 'long');
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'medium\']/timeFormat/pattern', '', 'medium');
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'short\']/timeFormat/pattern', '', 'short');
+ break;
+
+ case 'datetime':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+
+ $timefull = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'full\']/timeFormat/pattern', '', 'full');
+ $timelong = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'long\']/timeFormat/pattern', '', 'long');
+ $timemedi = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'medium\']/timeFormat/pattern', '', 'medi');
+ $timeshor = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/timeFormatLength[@type=\'short\']/timeFormat/pattern', '', 'shor');
+
+ $datefull = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'full\']/dateFormat/pattern', '', 'full');
+ $datelong = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'long\']/dateFormat/pattern', '', 'long');
+ $datemedi = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'medium\']/dateFormat/pattern', '', 'medi');
+ $dateshor = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/dateFormatLength[@type=\'short\']/dateFormat/pattern', '', 'shor');
+
+ $full = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'full\']/dateTimeFormat/pattern', '', 'full');
+ $long = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'long\']/dateTimeFormat/pattern', '', 'long');
+ $medi = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'medium\']/dateTimeFormat/pattern', '', 'medi');
+ $shor = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'short\']/dateTimeFormat/pattern', '', 'shor');
+
+ $temp['full'] = str_replace(array('{0}', '{1}'), array($timefull['full'], $datefull['full']), $full['full']);
+ $temp['long'] = str_replace(array('{0}', '{1}'), array($timelong['long'], $datelong['long']), $long['long']);
+ $temp['medium'] = str_replace(array('{0}', '{1}'), array($timemedi['medi'], $datemedi['medi']), $medi['medi']);
+ $temp['short'] = str_replace(array('{0}', '{1}'), array($timeshor['shor'], $dateshor['shor']), $shor['shor']);
+ break;
+
+ case 'dateitem':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $_temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/availableFormats/dateFormatItem', 'id');
+ foreach($_temp as $key => $found) {
+ $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/availableFormats/dateFormatItem[@id=\'' . $key . '\']', '', $key);
+ }
+ break;
+
+ case 'dateinterval':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $_temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/intervalFormats/intervalFormatItem', 'id');
+ foreach($_temp as $key => $found) {
+ $temp[$key] = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\'' . $key . '\']/greatestDifference', 'id');
+ }
+ break;
+
+ case 'field':
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp2 = self::_getFile($locale, '/ldml/dates/fields/field', 'type');
+ // $temp2 = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/fields/field', 'type');
+ foreach ($temp2 as $key => $keyvalue) {
+ $temp += self::_getFile($locale, '/ldml/dates/fields/field[@type=\'' . $key . '\']/displayName', '', $key);
+ // $temp += self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/fields/field[@type=\'' . $key . '\']/displayName', '', $key);
+ }
+ break;
+
+ case 'relative':
+ if (empty($value)) {
+ $value = "day";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/fields/field[@type=\'' . $value . '\']/relative', 'type');
+ break;
+
+ case 'symbols':
+ $temp = self::_getFile($locale, '/ldml/numbers/symbols/decimal', '', 'decimal');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/group', '', 'group');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/list', '', 'list');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/percentSign', '', 'percent');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/nativeZeroDigit', '', 'zero');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/patternDigit', '', 'pattern');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/plusSign', '', 'plus');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/minusSign', '', 'minus');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/exponential', '', 'exponent');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/perMille', '', 'mille');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/infinity', '', 'infinity');
+ $temp += self::_getFile($locale, '/ldml/numbers/symbols/nan', '', 'nan');
+ break;
+
+ case 'nametocurrency':
+ $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/displayName', '', $key);
+ }
+ break;
+
+ case 'currencytoname':
+ $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type');
+ foreach ($_temp as $key => $keyvalue) {
+ $val = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/displayName', '', $key);
+ if (!isset($val[$key])) {
+ continue;
+ }
+ if (!isset($temp[$val[$key]])) {
+ $temp[$val[$key]] = $key;
+ } else {
+ $temp[$val[$key]] .= " " . $key;
+ }
+ }
+ break;
+
+ case 'currencysymbol':
+ $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/symbol', '', $key);
+ }
+ break;
+
+ case 'question':
+ $temp = self::_getFile($locale, '/ldml/posix/messages/yesstr', '', 'yes');
+ $temp += self::_getFile($locale, '/ldml/posix/messages/nostr', '', 'no');
+ break;
+
+ case 'currencyfraction':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info', 'iso4217');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $key . '\']', 'digits', $key);
+ }
+ break;
+
+ case 'currencyrounding':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info', 'iso4217');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $key . '\']', 'rounding', $key);
+ }
+ break;
+
+ case 'currencytoregion':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region', 'iso3166');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $key . '\']/currency', 'iso4217', $key);
+ }
+ break;
+
+ case 'regiontocurrency':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region', 'iso3166');
+ foreach ($_temp as $key => $keyvalue) {
+ $val = self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $key . '\']/currency', 'iso4217', $key);
+ if (!isset($val[$key])) {
+ continue;
+ }
+ if (!isset($temp[$val[$key]])) {
+ $temp[$val[$key]] = $key;
+ } else {
+ $temp[$val[$key]] .= " " . $key;
+ }
+ }
+ break;
+
+ case 'regiontoterritory':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $key . '\']', 'contains', $key);
+ }
+ break;
+
+ case 'territorytoregion':
+ $_temp2 = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group', 'type');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $key . '\']', 'contains', $key);
+ }
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'scripttolanguage':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'scripts', $key);
+ if (empty($temp[$key])) {
+ unset($temp[$key]);
+ }
+ }
+ break;
+
+ case 'languagetoscript':
+ $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'scripts', $key);
+ }
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if (empty($found3)) {
+ continue;
+ }
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'territorytolanguage':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'territories', $key);
+ if (empty($temp[$key])) {
+ unset($temp[$key]);
+ }
+ }
+ break;
+
+ case 'languagetoterritory':
+ $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'territories', $key);
+ }
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if (empty($found3)) {
+ continue;
+ }
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'timezonetowindows':
+ $_temp = self::_getFile('windowsZones', '/supplementalData/windowsZones/mapTimezones/mapZone', 'other');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('windowsZones', '/supplementalData/windowsZones/mapTimezones/mapZone[@other=\'' . $key . '\']', 'type', $key);
+ }
+ break;
+
+ case 'windowstotimezone':
+ $_temp = self::_getFile('windowsZones', '/supplementalData/windowsZones/mapTimezones/mapZone', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('windowsZones', '/supplementalData/windowsZones/mapTimezones/mapZone[@type=\'' .$key . '\']', 'other', $key);
+ }
+ break;
+
+ case 'territorytotimezone':
+ $_temp = self::_getFile('metaZones', '/supplementalData/metaZones/mapTimezones/mapZone', 'type');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('metaZones', '/supplementalData/metaZones/mapTimezones/mapZone[@type=\'' . $key . '\']', 'territory', $key);
+ }
+ break;
+
+ case 'timezonetoterritory':
+ $_temp = self::_getFile('metaZones', '/supplementalData/metaZones/mapTimezones/mapZone', 'territory');
+ foreach ($_temp as $key => $found) {
+ $temp += self::_getFile('metaZones', '/supplementalData/metaZones/mapTimezones/mapZone[@territory=\'' . $key . '\']', 'type', $key);
+ }
+ break;
+
+ case 'citytotimezone':
+ $_temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone', 'type');
+ foreach($_temp as $key => $found) {
+ $temp += self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $key . '\']/exemplarCity', '', $key);
+ }
+ break;
+
+ case 'timezonetocity':
+ $_temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone', 'type');
+ $temp = array();
+ foreach($_temp as $key => $found) {
+ $temp += self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $key . '\']/exemplarCity', '', $key);
+ if (!empty($temp[$key])) {
+ $temp[$temp[$key]] = $key;
+ }
+ unset($temp[$key]);
+ }
+ break;
+
+ case 'phonetoterritory':
+ $_temp = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory', 'territory');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $key . '\']/telephoneCountryCode', 'code', $key);
+ }
+ break;
+
+ case 'territorytophone':
+ $_temp = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory', 'territory');
+ foreach ($_temp as $key => $keyvalue) {
+ $val = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $key . '\']/telephoneCountryCode', 'code', $key);
+ if (!isset($val[$key])) {
+ continue;
+ }
+ if (!isset($temp[$val[$key]])) {
+ $temp[$val[$key]] = $key;
+ } else {
+ $temp[$val[$key]] .= " " . $key;
+ }
+ }
+ break;
+
+ case 'numerictoterritory':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'type');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\'' . $key . '\']', 'numeric', $key);
+ }
+ break;
+
+ case 'territorytonumeric':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'numeric');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@numeric=\'' . $key . '\']', 'type', $key);
+ }
+ break;
+
+ case 'alpha3toterritory':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'type');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\'' . $key . '\']', 'alpha3', $key);
+ }
+ break;
+
+ case 'territorytoalpha3':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes', 'alpha3');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@alpha3=\'' . $key . '\']', 'type', $key);
+ }
+ break;
+
+ case 'postaltoterritory':
+ $_temp = self::_getFile('postalCodeData', '/supplementalData/postalCodeData/postCodeRegex', 'territoryId');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('postalCodeData', '/supplementalData/postalCodeData/postCodeRegex[@territoryId=\'' . $key . '\']', 'territoryId');
+ }
+ break;
+
+ case 'numberingsystem':
+ $_temp = self::_getFile('numberingSystems', '/supplementalData/numberingSystems/numberingSystem', 'id');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('numberingSystems', '/supplementalData/numberingSystems/numberingSystem[@id=\'' . $key . '\']', 'digits', $key);
+ if (empty($temp[$key])) {
+ unset($temp[$key]);
+ }
+ }
+ break;
+
+ case 'chartofallback':
+ $_temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character', 'value');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp2 = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $key . '\']/substitute', '', $key);
+ $temp[current($temp2)] = $key;
+ }
+ break;
+
+ case 'fallbacktochar':
+ $_temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character', 'value');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $key . '\']/substitute', '', $key);
+ }
+ break;
+
+ case 'localeupgrade':
+ $_temp = self::_getFile('likelySubtags', '/supplementalData/likelySubtags/likelySubtag', 'from');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp += self::_getFile('likelySubtags', '/supplementalData/likelySubtags/likelySubtag[@from=\'' . $key . '\']', 'to', $key);
+ }
+ break;
+
+ case 'unit':
+ $_temp = self::_getFile($locale, '/ldml/units/unitLength/unit', 'type');
+ foreach($_temp as $key => $keyvalue) {
+ $_temp2 = self::_getFile($locale, '/ldml/units/unitLength/unit[@type=\'' . $key . '\']/unitPattern', 'count');
+ $temp[$key] = $_temp2;
+ }
+ break;
+
+ default :
+ throw new Zend_Locale_Exception("Unknown list ($path) for parsing locale data.");
+ break;
+ }
+
+ if (isset(self::$_cache)) {
+ if (self::$_cacheTags) {
+ self::$_cache->save( serialize($temp), $id, array('Zend_Locale'));
+ } else {
+ self::$_cache->save( serialize($temp), $id);
+ }
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Read the LDML file, get a single path defined value
+ *
+ * @param string $locale
+ * @param string $path
+ * @param bool|string $value
+ * @return string
+ * @throws Zend_Locale_Exception
+ */
+ public static function getContent($locale, $path, $value = false)
+ {
+ $locale = self::_checkLocale($locale);
+
+ if (!isset(self::$_cache) && !self::$_cacheDisabled) {
+ self::$_cache = Zend_Cache::factory(
+ 'Core',
+ 'File',
+ array('automatic_serialization' => true),
+ array());
+ }
+
+ $val = $value;
+ if (is_array($value)) {
+ $val = implode('_' , $value);
+ }
+ $val = urlencode($val);
+ $id = self::_filterCacheId('Zend_LocaleC_' . $locale . '_' . $path . '_' . $val);
+ if (!self::$_cacheDisabled && ($result = self::$_cache->load($id))) {
+ return unserialize($result);
+ }
+
+ switch(strtolower($path)) {
+ case 'language':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/languages/language[@type=\'' . $value . '\']', 'type');
+ break;
+
+ case 'script':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/scripts/script[@type=\'' . $value . '\']', 'type');
+ break;
+
+ case 'country':
+ case 'territory':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/territories/territory[@type=\'' . $value . '\']', 'type');
+ break;
+
+ case 'variant':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/variants/variant[@type=\'' . $value . '\']', 'type');
+ break;
+
+ case 'key':
+ $temp = self::_getFile($locale, '/ldml/localeDisplayNames/keys/key[@type=\'' . $value . '\']', 'type');
+ break;
+
+ case 'defaultcalendar':
+ $givenLocale = new Zend_Locale($locale);
+ $territory = $givenLocale->getRegion();
+ unset($givenLocale);
+ $temp = self::_getFile('supplementalData', '/supplementalData/calendarPreferenceData/calendarPreference[contains(@territories,\'' . $territory . '\')]', 'ordering', 'ordering');
+ if (isset($temp['ordering'])) {
+ list($temp) = explode(' ', $temp['ordering']);
+ } else {
+ $temp = 'gregorian';
+ }
+ break;
+
+ case 'monthcontext':
+ /* default context is always 'format'
+ if (empty ($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/default', 'choice', 'context');
+ */
+ $temp = 'format';
+ break;
+
+ case 'defaultmonth':
+ /* default width is always 'wide'
+ if (empty ($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/months/monthContext[@type=\'format\']/default', 'choice', 'default');
+ */
+ $temp = 'wide';
+ break;
+
+ case 'month':
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", "format", "wide", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/months/monthContext[@type=\'' . $value[1] . '\']/monthWidth[@type=\'' . $value[2] . '\']/month[@type=\'' . $value[3] . '\']', 'type');
+ break;
+
+ case 'daycontext':
+ /* default context is always 'format'
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/default', 'choice', 'context');
+ */
+ $temp = 'format';
+ break;
+
+ case 'defaultday':
+ /* default width is always 'wide'
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/days/dayContext[@type=\'format\']/default', 'choice', 'default');
+ */
+ $temp = 'wide';
+ break;
+
+ case 'day':
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", "format", "wide", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/days/dayContext[@type=\'' . $value[1] . '\']/dayWidth[@type=\'' . $value[2] . '\']/day[@type=\'' . $value[3] . '\']', 'type');
+ break;
+
+ case 'quarter':
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", "format", "wide", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/quarters/quarterContext[@type=\'' . $value[1] . '\']/quarterWidth[@type=\'' . $value[2] . '\']/quarter[@type=\'' . $value[3] . '\']', 'type');
+ break;
+
+ case 'am':
+ if (empty($value)) {
+ $value = array("gregorian", "format", "wide");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array($temp, "format", "wide");
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dayPeriods/dayPeriodContext[@type=\'' . $value[1] . '\']/dayPeriodWidth[@type=\'' . $value[2] . '\']/dayPeriod[@type=\'am\']', '', 'dayPeriod');
+ break;
+
+ case 'pm':
+ if (empty($value)) {
+ $value = array("gregorian", "format", "wide");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array($temp, "format", "wide");
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dayPeriods/dayPeriodContext[@type=\'' . $value[1] . '\']/dayPeriodWidth[@type=\'' . $value[2] . '\']/dayPeriod[@type=\'pm\']', '', 'dayPeriod');
+ break;
+
+ case 'era':
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", "Abbr", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/eras/era' . $value[1] . '/era[@type=\'' . $value[2] . '\']', 'type');
+ break;
+
+ case 'defaultdate':
+ /* default choice is deprecated in CDLR - should be always medium here
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/dateFormats/default', 'choice', 'default');
+ */
+ $temp = 'medium';
+ break;
+
+ case 'date':
+ if (empty($value)) {
+ $value = array("gregorian", "medium");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateFormats/dateFormatLength[@type=\'' . $value[1] . '\']/dateFormat/pattern', '', 'pattern');
+ break;
+
+ case 'defaulttime':
+ /* default choice is deprecated in CDLR - should be always medium here
+ if (empty($value)) {
+ $value = "gregorian";
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value . '\']/timeFormats/default', 'choice', 'default');
+ */
+ $temp = 'medium';
+ break;
+
+ case 'time':
+ if (empty($value)) {
+ $value = array("gregorian", "medium");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/timeFormats/timeFormatLength[@type=\'' . $value[1] . '\']/timeFormat/pattern', '', 'pattern');
+ break;
+
+ case 'datetime':
+ if (empty($value)) {
+ $value = array("gregorian", "medium");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp);
+ }
+
+ $date = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateFormats/dateFormatLength[@type=\'' . $value[1] . '\']/dateFormat/pattern', '', 'pattern');
+ $time = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/timeFormats/timeFormatLength[@type=\'' . $value[1] . '\']/timeFormat/pattern', '', 'pattern');
+ $datetime = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateTimeFormats/dateTimeFormatLength[@type=\'' . $value[1] . '\']/dateTimeFormat/pattern', '', 'pattern');
+ $temp = str_replace(array('{0}', '{1}'), array(current($time), current($date)), current($datetime));
+ break;
+
+ case 'dateitem':
+ if (empty($value)) {
+ $value = array("gregorian", "yyMMdd");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateTimeFormats/availableFormats/dateFormatItem[@id=\'' . $value[1] . '\']', '');
+ break;
+
+ case 'dateinterval':
+ if (empty($value)) {
+ $value = array("gregorian", "yMd", "y");
+ }
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp, $temp[0]);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/dateTimeFormats/intervalFormats/intervalFormatItem[@id=\'' . $value[1] . '\']/greatestDifference[@id=\'' . $value[2] . '\']', '');
+ break;
+
+ case 'field':
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/fields/field[@type=\'' . $value[1] . '\']/displayName', '', $value[1]);
+ break;
+
+ case 'relative':
+ if (!is_array($value)) {
+ $temp = $value;
+ $value = array("gregorian", $temp);
+ }
+ $temp = self::_getFile($locale, '/ldml/dates/fields/field[@type=\'day\']/relative[@type=\'' . $value[1] . '\']', '', $value[1]);
+ // $temp = self::_getFile($locale, '/ldml/dates/calendars/calendar[@type=\'' . $value[0] . '\']/fields/field/relative[@type=\'' . $value[1] . '\']', '', $value[1]);
+ break;
+
+ case 'defaultnumberingsystem':
+ $temp = self::_getFile($locale, '/ldml/numbers/defaultNumberingSystem', '', 'default');
+ break;
+
+ case 'decimalnumber':
+ $temp = self::_getFile($locale, '/ldml/numbers/decimalFormats/decimalFormatLength/decimalFormat/pattern', '', 'default');
+ break;
+
+ case 'scientificnumber':
+ $temp = self::_getFile($locale, '/ldml/numbers/scientificFormats/scientificFormatLength/scientificFormat/pattern', '', 'default');
+ break;
+
+ case 'percentnumber':
+ $temp = self::_getFile($locale, '/ldml/numbers/percentFormats/percentFormatLength/percentFormat/pattern', '', 'default');
+ break;
+
+ case 'currencynumber':
+ $temp = self::_getFile($locale, '/ldml/numbers/currencyFormats/currencyFormatLength/currencyFormat/pattern', '', 'default');
+ break;
+
+ case 'nametocurrency':
+ $temp = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $value . '\']/displayName', '', $value);
+ break;
+
+ case 'currencytoname':
+ $temp = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $value . '\']/displayName', '', $value);
+ $_temp = self::_getFile($locale, '/ldml/numbers/currencies/currency', 'type');
+ $temp = array();
+ foreach ($_temp as $key => $keyvalue) {
+ $val = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $key . '\']/displayName', '', $key);
+ if (!isset($val[$key]) or ($val[$key] != $value)) {
+ continue;
+ }
+ if (!isset($temp[$val[$key]])) {
+ $temp[$val[$key]] = $key;
+ } else {
+ $temp[$val[$key]] .= " " . $key;
+ }
+ }
+ break;
+
+ case 'currencysymbol':
+ $temp = self::_getFile($locale, '/ldml/numbers/currencies/currency[@type=\'' . $value . '\']/symbol', '', $value);
+ break;
+
+ case 'question':
+ $temp = self::_getFile($locale, '/ldml/posix/messages/' . $value . 'str', '', $value);
+ break;
+
+ case 'currencyfraction':
+ if (empty($value)) {
+ $value = "DEFAULT";
+ }
+ $temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $value . '\']', 'digits', 'digits');
+ break;
+
+ case 'currencyrounding':
+ if (empty($value)) {
+ $value = "DEFAULT";
+ }
+ $temp = self::_getFile('supplementalData', '/supplementalData/currencyData/fractions/info[@iso4217=\'' . $value . '\']', 'rounding', 'rounding');
+ break;
+
+ case 'currencytoregion':
+ $temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $value . '\']/currency', 'iso4217', $value);
+ break;
+
+ case 'regiontocurrency':
+ $_temp = self::_getFile('supplementalData', '/supplementalData/currencyData/region', 'iso3166');
+ $temp = array();
+ foreach ($_temp as $key => $keyvalue) {
+ $val = self::_getFile('supplementalData', '/supplementalData/currencyData/region[@iso3166=\'' . $key . '\']/currency', 'iso4217', $key);
+ if (!isset($val[$key]) or ($val[$key] != $value)) {
+ continue;
+ }
+ if (!isset($temp[$val[$key]])) {
+ $temp[$val[$key]] = $key;
+ } else {
+ $temp[$val[$key]] .= " " . $key;
+ }
+ }
+ break;
+
+ case 'regiontoterritory':
+ $temp = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $value . '\']', 'contains', $value);
+ break;
+
+ case 'territorytoregion':
+ $_temp2 = self::_getFile('supplementalData', '/supplementalData/territoryContainment/group', 'type');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('supplementalData', '/supplementalData/territoryContainment/group[@type=\'' . $key . '\']', 'contains', $key);
+ }
+ $temp = array();
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if ($found3 !== $value) {
+ continue;
+ }
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'scripttolanguage':
+ $temp = self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $value . '\']', 'scripts', $value);
+ break;
+
+ case 'languagetoscript':
+ $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'scripts', $key);
+ }
+ $temp = array();
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if ($found3 !== $value) {
+ continue;
+ }
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'territorytolanguage':
+ $temp = self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $value . '\']', 'territories', $value);
+ break;
+
+ case 'languagetoterritory':
+ $_temp2 = self::_getFile('supplementalData', '/supplementalData/languageData/language', 'type');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('supplementalData', '/supplementalData/languageData/language[@type=\'' . $key . '\']', 'territories', $key);
+ }
+ $temp = array();
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if ($found3 !== $value) {
+ continue;
+ }
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'timezonetowindows':
+ $temp = self::_getFile('windowsZones', '/supplementalData/windowsZones/mapTimezones/mapZone[@other=\''.$value.'\']', 'type', $value);
+ break;
+
+ case 'windowstotimezone':
+ $temp = self::_getFile('windowsZones', '/supplementalData/windowsZones/mapTimezones/mapZone[@type=\''.$value.'\']', 'other', $value);
+ break;
+
+ case 'territorytotimezone':
+ $temp = self::_getFile('metaZones', '/supplementalData/metaZones/mapTimezones/mapZone[@type=\'' . $value . '\']', 'territory', $value);
+ break;
+
+ case 'timezonetoterritory':
+ $temp = self::_getFile('metaZones', '/supplementalData/metaZones/mapTimezones/mapZone[@territory=\'' . $value . '\']', 'type', $value);
+ break;
+
+ case 'citytotimezone':
+ $temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $value . '\']/exemplarCity', '', $value);
+ break;
+
+ case 'timezonetocity':
+ $_temp = self::_getFile($locale, '/ldml/dates/timeZoneNames/zone', 'type');
+ $temp = array();
+ foreach($_temp as $key => $found) {
+ $temp += self::_getFile($locale, '/ldml/dates/timeZoneNames/zone[@type=\'' . $key . '\']/exemplarCity', '', $key);
+ if (!empty($temp[$key])) {
+ if ($temp[$key] == $value) {
+ $temp[$temp[$key]] = $key;
+ }
+ }
+ unset($temp[$key]);
+ }
+ break;
+
+ case 'phonetoterritory':
+ $temp = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $value . '\']/telephoneCountryCode', 'code', $value);
+ break;
+
+ case 'territorytophone':
+ $_temp2 = self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory', 'territory');
+ $_temp = array();
+ foreach ($_temp2 as $key => $found) {
+ $_temp += self::_getFile('telephoneCodeData', '/supplementalData/telephoneCodeData/codesByTerritory[@territory=\'' . $key . '\']/telephoneCountryCode', 'code', $key);
+ }
+ $temp = array();
+ foreach($_temp as $key => $found) {
+ $_temp3 = explode(" ", $found);
+ foreach($_temp3 as $found3) {
+ if ($found3 !== $value) {
+ continue;
+ }
+ if (!isset($temp[$found3])) {
+ $temp[$found3] = (string) $key;
+ } else {
+ $temp[$found3] .= " " . $key;
+ }
+ }
+ }
+ break;
+
+ case 'numerictoterritory':
+ $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\''.$value.'\']', 'numeric', $value);
+ break;
+
+ case 'territorytonumeric':
+ $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@numeric=\''.$value.'\']', 'type', $value);
+ break;
+
+ case 'alpha3toterritory':
+ $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@type=\''.$value.'\']', 'alpha3', $value);
+ break;
+
+ case 'territorytoalpha3':
+ $temp = self::_getFile('supplementalData', '/supplementalData/codeMappings/territoryCodes[@alpha3=\''.$value.'\']', 'type', $value);
+ break;
+
+ case 'postaltoterritory':
+ $temp = self::_getFile('postalCodeData', '/supplementalData/postalCodeData/postCodeRegex[@territoryId=\'' . $value . '\']', 'territoryId');
+ break;
+
+ case 'numberingsystem':
+ $temp = self::_getFile('numberingSystems', '/supplementalData/numberingSystems/numberingSystem[@id=\'' . strtolower($value) . '\']', 'digits', $value);
+ break;
+
+ case 'chartofallback':
+ $_temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character', 'value');
+ foreach ($_temp as $key => $keyvalue) {
+ $temp2 = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $key . '\']/substitute', '', $key);
+ if (current($temp2) == $value) {
+ $temp = $key;
+ }
+ }
+ break;
+
+ $temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $value . '\']/substitute', '', $value);
+ break;
+
+ case 'fallbacktochar':
+ $temp = self::_getFile('characters', '/supplementalData/characters/character-fallback/character[@value=\'' . $value . '\']/substitute', '');
+ break;
+
+ case 'localeupgrade':
+ $temp = self::_getFile('likelySubtags', '/supplementalData/likelySubtags/likelySubtag[@from=\'' . $value . '\']', 'to', $value);
+ break;
+
+ case 'unit':
+ $temp = self::_getFile($locale, '/ldml/units/unitLength/unit[@type=\'' . $value[0] . '\']/unitPattern[@count=\'' . $value[1] . '\']', '');
+ break;
+
+ case 'parentlocale':
+ if (false === $value) {
+ $value = $locale;
+ }
+ $temp = self::_getFile('supplementalData', "/supplementalData/parentLocales/parentLocale[contains(@locales, '" . $value . "')]", 'parent', 'parent');
+ break;
+
+ default :
+ throw new Zend_Locale_Exception("Unknown detail ($path) for parsing locale data.");
+ break;
+ }
+
+ if (is_array($temp)) {
+ $temp = current($temp);
+ }
+ if (isset(self::$_cache)) {
+ if (self::$_cacheTags) {
+ self::$_cache->save( serialize($temp), $id, array('Zend_Locale'));
+ } else {
+ self::$_cache->save( serialize($temp), $id);
+ }
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Returns the set cache
+ *
+ * @return Zend_Cache_Core The set cache
+ */
+ public static function getCache()
+ {
+ return self::$_cache;
+ }
+
+ /**
+ * Set a cache for Zend_Locale_Data
+ *
+ * @param Zend_Cache_Core $cache A cache frontend
+ */
+ public static function setCache(Zend_Cache_Core $cache)
+ {
+ self::$_cache = $cache;
+ self::_getTagSupportForCache();
+ }
+
+ /**
+ * Returns true when a cache is set
+ *
+ * @return boolean
+ */
+ public static function hasCache()
+ {
+ if (self::$_cache !== null) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes any set cache
+ *
+ * @return void
+ */
+ public static function removeCache()
+ {
+ self::$_cache = null;
+ }
+
+ /**
+ * Clears all set cache data
+ *
+ * @return void
+ */
+ public static function clearCache()
+ {
+ if (self::$_cacheTags) {
+ self::$_cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('Zend_Locale'));
+ } else {
+ self::$_cache->clean(Zend_Cache::CLEANING_MODE_ALL);
+ }
+ }
+
+ /**
+ * Disables the cache
+ *
+ * @param bool $flag
+ */
+ public static function disableCache($flag)
+ {
+ self::$_cacheDisabled = (boolean) $flag;
+ }
+
+ /**
+ * Internal method to check if the given cache supports tags
+ *
+ * @return bool
+ */
+ private static function _getTagSupportForCache()
+ {
+ $backend = self::$_cache->getBackend();
+ if ($backend instanceof Zend_Cache_Backend_ExtendedInterface) {
+ $cacheOptions = $backend->getCapabilities();
+ self::$_cacheTags = $cacheOptions['tags'];
+ } else {
+ self::$_cacheTags = false;
+ }
+
+ return self::$_cacheTags;
+ }
+
+ /**
+ * Filter an ID to only allow valid variable characters
+ *
+ * @param string $value
+ * @return string
+ */
+ protected static function _filterCacheId($value)
+ {
+ return strtr(
+ $value,
+ array(
+ '-' => '_',
+ '%' => '_',
+ '+' => '_',
+ '.' => '_',
+ )
+ );
+ }
+}
diff --git a/library/vendor/Zend/Locale/Data/Translation.php b/library/vendor/Zend/Locale/Data/Translation.php
new file mode 100644
index 0000000..ceb16a9
--- /dev/null
+++ b/library/vendor/Zend/Locale/Data/Translation.php
@@ -0,0 +1,285 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Definition class for all Windows locales
+ *
+ * Based on this two lists:
+ * @link http://msdn.microsoft.com/en-us/library/39cwe7zf.aspx
+ * @link http://msdn.microsoft.com/en-us/library/cdax410z.aspx
+ * @link http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx
+ * @link http://msdn.microsoft.com/en-us/goglobal/bb895996.aspx
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale_Data_Translation
+{
+ /**
+ * Locale Translation for Full Named Locales
+ *
+ * @var array $localeTranslation
+ */
+ public static $languageTranslation = array(
+ 'Afrikaans' => 'af',
+ 'Albanian' => 'sq',
+ 'Amharic' => 'am',
+ 'Arabic' => 'ar',
+ 'Armenian' => 'hy',
+ 'Assamese' => 'as',
+ 'Azeri' => 'az',
+ 'Azeri Latin' => 'az_Latn',
+ 'Azeri Cyrillic' => 'az_Cyrl',
+ 'Basque' => 'eu',
+ 'Belarusian' => 'be',
+ 'Bengali' => 'bn',
+ 'Bengali Latin' => 'bn_Latn',
+ 'Bosnian' => 'bs',
+ 'Bulgarian' => 'bg',
+ 'Burmese' => 'my',
+ 'Catalan' => 'ca',
+ 'Cherokee' => 'chr',
+ 'Chinese' => 'zh',
+ 'Croatian' => 'hr',
+ 'Czech' => 'cs',
+ 'Danish' => 'da',
+ 'Divehi' => 'dv',
+ 'Dutch' => 'nl',
+ 'English' => 'en',
+ 'Estonian' => 'et',
+ 'Faroese' => 'fo',
+ 'Faeroese' => 'fo',
+ 'Farsi' => 'fa',
+ 'Filipino' => 'fil',
+ 'Finnish' => 'fi',
+ 'French' => 'fr',
+ 'Frisian' => 'fy',
+ 'Gaelic' => 'gd',
+ 'Galician' => 'gl',
+ 'Georgian' => 'ka',
+ 'German' => 'de',
+ 'Greek' => 'el',
+ 'Guarani' => 'gn',
+ 'Gujarati' => 'gu',
+ 'Hausa' => 'ha',
+ 'Hawaiian' => 'haw',
+ 'Hebrew' => 'he',
+ 'Hindi' => 'hi',
+ 'Hungarian' => 'hu',
+ 'Icelandic' => 'is',
+ 'Igbo' => 'ig',
+ 'Indonesian' => 'id',
+ 'Inuktitut' => 'iu',
+ 'Italian' => 'it',
+ 'Japanese' => 'ja',
+ 'Kannada' => 'kn',
+ 'Kanuri' => 'kr',
+ 'Kashmiri' => 'ks',
+ 'Kazakh' => 'kk',
+ 'Khmer' => 'km',
+ 'Konkani' => 'kok',
+ 'Korean' => 'ko',
+ 'Kyrgyz' => 'ky',
+ 'Lao' => 'lo',
+ 'Latin' => 'la',
+ 'Latvian' => 'lv',
+ 'Lithuanian' => 'lt',
+ 'Macedonian' => 'mk',
+ 'Malay' => 'ms',
+ 'Malayalam' => 'ml',
+ 'Maltese' => 'mt',
+ 'Manipuri' => 'mni',
+ 'Maori' => 'mi',
+ 'Marathi' => 'mr',
+ 'Mongolian' => 'mn',
+ 'Nepali' => 'ne',
+ 'Norwegian' => 'no',
+ 'Norwegian Bokmal' => 'nb',
+ 'Norwegian Nynorsk' => 'nn',
+ 'Oriya' => 'or',
+ 'Oromo' => 'om',
+ 'Papiamentu' => 'pap',
+ 'Pashto' => 'ps',
+ 'Polish' => 'pl',
+ 'Portuguese' => 'pt',
+ 'Punjabi' => 'pa',
+ 'Quecha' => 'qu',
+ 'Quechua' => 'qu',
+ 'Rhaeto-Romanic' => 'rm',
+ 'Romanian' => 'ro',
+ 'Russian' => 'ru',
+ 'Sami' => 'smi',
+ 'Sami Inari' => 'smn',
+ 'Sami Lule' => 'smj',
+ 'Sami Northern' => 'se',
+ 'Sami Skolt' => 'sms',
+ 'Sami Southern' => 'sma',
+ 'Sanskrit' => 'sa',
+ 'Serbian' => 'sr',
+ 'Serbian Latin' => 'sr_Latn',
+ 'Serbian Cyrillic' => 'sr_Cyrl',
+ 'Sindhi' => 'sd',
+ 'Sinhalese' => 'si',
+ 'Slovak' => 'sk',
+ 'Slovenian' => 'sl',
+ 'Somali' => 'so',
+ 'Sorbian' => 'wen',
+ 'Spanish' => 'es',
+ 'Swahili' => 'sw',
+ 'Swedish' => 'sv',
+ 'Syriac' => 'syr',
+ 'Tajik' => 'tg',
+ 'Tamazight' => 'tmh',
+ 'Tamil' => 'ta',
+ 'Tatar' => 'tt',
+ 'Telugu' => 'te',
+ 'Thai' => 'th',
+ 'Tibetan' => 'bo',
+ 'Tigrigna' => 'ti',
+ 'Tsonga' => 'ts',
+ 'Tswana' => 'tn',
+ 'Turkish' => 'tr',
+ 'Turkmen' => 'tk',
+ 'Uighur' => 'ug',
+ 'Ukrainian' => 'uk',
+ 'Urdu' => 'ur',
+ 'Uzbek' => 'uz',
+ 'Uzbek Latin' => 'uz_Latn',
+ 'Uzbek Cyrillic' => 'uz_Cyrl',
+ 'Venda' => 've',
+ 'Vietnamese' => 'vi',
+ 'Welsh' => 'cy',
+ 'Xhosa' => 'xh',
+ 'Yiddish' => 'yi',
+ 'Yoruba' => 'yo',
+ 'Zulu' => 'zu',
+ );
+
+ public static $regionTranslation = array(
+ 'Albania' => 'AL',
+ 'Algeria' => 'DZ',
+ 'Argentina' => 'AR',
+ 'Armenia' => 'AM',
+ 'Australia' => 'AU',
+ 'Austria' => 'AT',
+ 'Bahrain' => 'BH',
+ 'Bangladesh' => 'BD',
+ 'Belgium' => 'BE',
+ 'Belize' => 'BZ',
+ 'Bhutan' => 'BT',
+ 'Bolivia' => 'BO',
+ 'Bosnia Herzegovina' => 'BA',
+ 'Brazil' => 'BR',
+ 'Brazilian' => 'BR',
+ 'Brunei Darussalam' => 'BN',
+ 'Cameroon' => 'CM',
+ 'Canada' => 'CA',
+ 'Chile' => 'CL',
+ 'China' => 'CN',
+ 'Colombia' => 'CO',
+ 'Costa Rica' => 'CR',
+ "Cote d'Ivoire" => 'CI',
+ 'Czech Republic' => 'CZ',
+ 'Dominican Republic' => 'DO',
+ 'Denmark' => 'DK',
+ 'Ecuador' => 'EC',
+ 'Egypt' => 'EG',
+ 'El Salvador' => 'SV',
+ 'Eritrea' => 'ER',
+ 'Ethiopia' => 'ET',
+ 'Finland' => 'FI',
+ 'France' => 'FR',
+ 'Germany' => 'DE',
+ 'Greece' => 'GR',
+ 'Guatemala' => 'GT',
+ 'Haiti' => 'HT',
+ 'Honduras' => 'HN',
+ 'Hong Kong' => 'HK',
+ 'Hong Kong SAR' => 'HK',
+ 'Hungary' => 'HU',
+ 'Iceland' => 'IS',
+ 'India' => 'IN',
+ 'Indonesia' => 'ID',
+ 'Iran' => 'IR',
+ 'Iraq' => 'IQ',
+ 'Ireland' => 'IE',
+ 'Italy' => 'IT',
+ 'Jamaica' => 'JM',
+ 'Japan' => 'JP',
+ 'Jordan' => 'JO',
+ 'Korea' => 'KR',
+ 'Kuwait' => 'KW',
+ 'Lebanon' => 'LB',
+ 'Libya' => 'LY',
+ 'Liechtenstein' => 'LI',
+ 'Luxembourg' => 'LU',
+ 'Macau' => 'MO',
+ 'Macao SAR' => 'MO',
+ 'Malaysia' => 'MY',
+ 'Mali' => 'ML',
+ 'Mexico' => 'MX',
+ 'Moldava' => 'MD',
+ 'Monaco' => 'MC',
+ 'Morocco' => 'MA',
+ 'Netherlands' => 'NL',
+ 'New Zealand' => 'NZ',
+ 'Nicaragua' => 'NI',
+ 'Nigeria' => 'NG',
+ 'Norway' => 'NO',
+ 'Oman' => 'OM',
+ 'Pakistan' => 'PK',
+ 'Panama' => 'PA',
+ 'Paraguay' => 'PY',
+ "People's Republic of China" => 'CN',
+ 'Peru' => 'PE',
+ 'Philippines' => 'PH',
+ 'Poland' => 'PL',
+ 'Portugal' => 'PT',
+ 'PRC' => 'CN',
+ 'Puerto Rico' => 'PR',
+ 'Qatar' => 'QA',
+ 'Reunion' => 'RE',
+ 'Russia' => 'RU',
+ 'Saudi Arabia' => 'SA',
+ 'Senegal' => 'SN',
+ 'Singapore' => 'SG',
+ 'Slovakia' => 'SK',
+ 'South Africa' => 'ZA',
+ 'Spain' => 'ES',
+ 'Sri Lanka' => 'LK',
+ 'Sweden' => 'SE',
+ 'Switzerland' => 'CH',
+ 'Syria' => 'SY',
+ 'Taiwan' => 'TW',
+ 'The Netherlands' => 'NL',
+ 'Trinidad' => 'TT',
+ 'Tunisia' => 'TN',
+ 'UAE' => 'AE',
+ 'United Kingdom' => 'GB',
+ 'United States' => 'US',
+ 'Uruguay' => 'UY',
+ 'Venezuela' => 'VE',
+ 'Yemen' => 'YE',
+ 'Zimbabwe' => 'ZW',
+ );
+}
diff --git a/library/vendor/Zend/Locale/Exception.php b/library/vendor/Zend/Locale/Exception.php
new file mode 100644
index 0000000..05c543f
--- /dev/null
+++ b/library/vendor/Zend/Locale/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Locale/Format.php b/library/vendor/Zend/Locale/Format.php
new file mode 100644
index 0000000..a1baf6e
--- /dev/null
+++ b/library/vendor/Zend/Locale/Format.php
@@ -0,0 +1,1321 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @subpackage Format
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * include needed classes
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Locale
+ * @subpackage Format
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale_Format
+{
+ const STANDARD = 'auto';
+
+ private static $_options = array('date_format' => null,
+ 'number_format' => null,
+ 'format_type' => 'iso',
+ 'fix_date' => false,
+ 'locale' => null,
+ 'cache' => null,
+ 'disableCache' => null,
+ 'precision' => null);
+
+ /**
+ * Sets class wide options, if no option was given, the actual set options will be returned
+ * The 'precision' option of a value is used to truncate or stretch extra digits. -1 means not to touch the extra digits.
+ * The 'locale' option helps when parsing numbers and dates using separators and month names.
+ * The date format 'format_type' option selects between CLDR/ISO date format specifier tokens and PHP's date() tokens.
+ * The 'fix_date' option enables or disables heuristics that attempt to correct invalid dates.
+ * The 'number_format' option can be used to specify a default number format string
+ * The 'date_format' option can be used to specify a default date format string, but beware of using getDate(),
+ * checkDateFormat() and getTime() after using setOptions() with a 'format'. To use these four methods
+ * with the default date format for a locale, use array('date_format' => null, 'locale' => $locale) for their options.
+ *
+ * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false,
+ * locale = Zend_Locale | locale string, precision = whole number between -1 and 30
+ * @throws Zend_Locale_Exception
+ * @return array if no option was given
+ */
+ public static function setOptions(array $options = array())
+ {
+ self::$_options = self::_checkOptions($options) + self::$_options;
+ return self::$_options;
+ }
+
+ /**
+ * Internal function for checking the options array of proper input values
+ * See {@link setOptions()} for details.
+ *
+ * @param array $options Array of options, keyed by option name: format_type = 'iso' | 'php', fix_date = true | false,
+ * locale = Zend_Locale | locale string, precision = whole number between -1 and 30
+ * @throws Zend_Locale_Exception
+ * @return array if no option was given
+ */
+ private static function _checkOptions(array $options = array())
+ {
+ if (count($options) == 0) {
+ return self::$_options;
+ }
+ foreach ($options as $name => $value) {
+ $name = strtolower($name);
+ if ($name !== 'locale') {
+ if (gettype($value) === 'string') {
+ $value = strtolower($value);
+ }
+ }
+
+ switch($name) {
+ case 'number_format' :
+ if ($value == Zend_Locale_Format::STANDARD) {
+ $locale = self::$_options['locale'];
+ if (isset($options['locale'])) {
+ $locale = $options['locale'];
+ }
+ $options['number_format'] = Zend_Locale_Data::getContent($locale, 'decimalnumber');
+ } else if ((gettype($value) !== 'string') and ($value !== NULL)) {
+ $stringValue = (string)(is_array($value) ? implode(' ', $value) : $value);
+ throw new Zend_Locale_Exception("Unknown number format type '" . gettype($value) . "'. "
+ . "Format '$stringValue' must be a valid number format string.");
+ }
+ break;
+
+ case 'date_format' :
+ if ($value == Zend_Locale_Format::STANDARD) {
+ $locale = self::$_options['locale'];
+ if (isset($options['locale'])) {
+ $locale = $options['locale'];
+ }
+ $options['date_format'] = Zend_Locale_Format::getDateFormat($locale);
+ } else if ((gettype($value) !== 'string') and ($value !== NULL)) {
+ $stringValue = (string)(is_array($value) ? implode(' ', $value) : $value);
+ throw new Zend_Locale_Exception("Unknown dateformat type '" . gettype($value) . "'. "
+ . "Format '$stringValue' must be a valid ISO or PHP date format string.");
+ } else {
+ if (((isset($options['format_type']) === true) and ($options['format_type'] == 'php')) or
+ ((isset($options['format_type']) === false) and (self::$_options['format_type'] == 'php'))) {
+ $options['date_format'] = Zend_Locale_Format::convertPhpToIsoFormat($value);
+ }
+ }
+ break;
+
+ case 'format_type' :
+ if (($value != 'php') && ($value != 'iso')) {
+ throw new Zend_Locale_Exception("Unknown date format type '$value'. Only 'iso' and 'php'"
+ . " are supported.");
+ }
+ break;
+
+ case 'fix_date' :
+ if (($value !== true) && ($value !== false)) {
+ throw new Zend_Locale_Exception("Enabling correction of dates must be either true or false"
+ . "(fix_date='$value').");
+ }
+ break;
+
+ case 'locale' :
+ $options['locale'] = Zend_Locale::findLocale($value);
+ break;
+
+ case 'cache' :
+ if ($value instanceof Zend_Cache_Core) {
+ Zend_Locale_Data::setCache($value);
+ }
+ break;
+
+ case 'disablecache' :
+ if (null !== $value) {
+ Zend_Locale_Data::disableCache($value);
+ }
+ break;
+
+ case 'precision' :
+ if ($value === NULL) {
+ $value = -1;
+ }
+
+ if (($value < -1) || ($value > 30)) {
+ throw new Zend_Locale_Exception("'$value' precision is not a whole number less than 30.");
+ }
+ break;
+
+ default:
+ throw new Zend_Locale_Exception("Unknown option: '$name' = '$value'");
+ break;
+
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Changes the numbers/digits within a given string from one script to another
+ * 'Decimal' representated the stardard numbers 0-9, if a script does not exist
+ * an exception will be thrown.
+ *
+ * Examples for conversion from Arabic to Latin numerals:
+ * convertNumerals('١١٠ Tests', 'Arab'); -> returns '100 Tests'
+ * Example for conversion from Latin to Arabic numerals:
+ * convertNumerals('100 Tests', 'Latn', 'Arab'); -> returns '١١٠ Tests'
+ *
+ * @param string $input String to convert
+ * @param string $from Script to parse, see {@link Zend_Locale::getScriptList()} for details.
+ * @param string $to OPTIONAL Script to convert to
+ * @return string Returns the converted input
+ * @throws Zend_Locale_Exception
+ */
+ public static function convertNumerals($input, $from, $to = null)
+ {
+ if (!self::_getUniCodeSupport()) {
+ trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE);
+ }
+
+ $from = strtolower($from);
+ $source = Zend_Locale_Data::getContent('en', 'numberingsystem', $from);
+ if (empty($source)) {
+ throw new Zend_Locale_Exception("Unknown script '$from'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9.");
+ }
+
+ if ($to !== null) {
+ $to = strtolower($to);
+ $target = Zend_Locale_Data::getContent('en', 'numberingsystem', $to);
+ if (empty($target)) {
+ throw new Zend_Locale_Exception("Unknown script '$to'. Use 'Latn' for digits 0,1,2,3,4,5,6,7,8,9.");
+ }
+ } else {
+ $target = '0123456789';
+ }
+
+ for ($x = 0; $x < 10; ++$x) {
+ $asource[$x] = "/" . iconv_substr($source, $x, 1, 'UTF-8') . "/u";
+ $atarget[$x] = iconv_substr($target, $x, 1, 'UTF-8');
+ }
+
+ return preg_replace($asource, $atarget, $input);
+ }
+
+ /**
+ * Returns the normalized number from a localized one
+ * Parsing depends on given locale (grouping and decimal)
+ *
+ * Examples for input:
+ * '2345.4356,1234' = 23455456.1234
+ * '+23,3452.123' = 233452.123
+ * '12343 ' = 12343
+ * '-9456' = -9456
+ * '0' = 0
+ *
+ * @param string $input Input string to parse for numbers
+ * @param array $options Options: locale, precision. See {@link setOptions()} for details.
+ * @return string Returns the extracted number
+ * @throws Zend_Locale_Exception
+ */
+ public static function getNumber($input, array $options = array())
+ {
+ $options = self::_checkOptions($options) + self::$_options;
+ if (!is_string($input)) {
+ return $input;
+ }
+
+ if (!self::isNumber($input, $options)) {
+ throw new Zend_Locale_Exception('No localized value in ' . $input . ' found, or the given number does not match the localized format');
+ }
+
+ // Get correct signs for this locale
+ $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
+ // Change locale input to be default number
+ if (($input[0] == $symbols['minus']) && ('-' != $input[0])) {
+ $input = '-' . substr($input, 1);
+ }
+
+ $input = str_replace($symbols['group'],'', $input);
+ if (strpos($input, $symbols['decimal']) !== false) {
+ if ($symbols['decimal'] != '.') {
+ $input = str_replace($symbols['decimal'], ".", $input);
+ }
+
+ $pre = substr($input, strpos($input, '.') + 1);
+ if ($options['precision'] === null) {
+ $options['precision'] = strlen($pre);
+ }
+
+ if (strlen($pre) >= $options['precision']) {
+ $input = substr($input, 0, strlen($input) - strlen($pre) + $options['precision']);
+ }
+
+ if (($options['precision'] == 0) && ($input[strlen($input) - 1] == '.')) {
+ $input = substr($input, 0, -1);
+ }
+ }
+
+ return $input;
+ }
+
+ /**
+ * Returns a locale formatted number depending on the given options.
+ * The seperation and fraction sign is used from the set locale.
+ * ##0.# -> 12345.12345 -> 12345.12345
+ * ##0.00 -> 12345.12345 -> 12345.12
+ * ##,##0.00 -> 12345.12345 -> 12,345.12
+ *
+ * @param string $value Localized number string
+ * @param array $options Options: number_format, locale, precision. See {@link setOptions()} for details.
+ * @return string locale formatted number
+ * @throws Zend_Locale_Exception
+ */
+ public static function toNumber($value, array $options = array())
+ {
+ // load class within method for speed
+
+ $value = Zend_Locale_Math::floatalize($value);
+ $value = Zend_Locale_Math::normalize($value);
+ $options = self::_checkOptions($options) + self::$_options;
+ $options['locale'] = (string) $options['locale'];
+
+ // Get correct signs for this locale
+ $symbols = Zend_Locale_Data::getList($options['locale'], 'symbols');
+ $oenc = self::_getEncoding();
+ self::_setEncoding('UTF-8');
+
+ // Get format
+ $format = $options['number_format'];
+ if ($format === null) {
+ $format = Zend_Locale_Data::getContent($options['locale'], 'decimalnumber');
+ $format = self::_seperateFormat($format, $value, $options['precision']);
+
+ if ($options['precision'] !== null) {
+ $value = Zend_Locale_Math::normalize(Zend_Locale_Math::round($value, $options['precision']));
+ }
+ } else {
+ // seperate negative format pattern when available
+ $format = self::_seperateFormat($format, $value, $options['precision']);
+ if (strpos($format, '.')) {
+ if (is_numeric($options['precision'])) {
+ $value = Zend_Locale_Math::round($value, $options['precision']);
+ } else {
+ if (substr($format, iconv_strpos($format, '.') + 1, 3) == '###') {
+ $options['precision'] = null;
+ } else {
+ $options['precision'] = iconv_strlen(iconv_substr($format, iconv_strpos($format, '.') + 1,
+ iconv_strrpos($format, '0') - iconv_strpos($format, '.')));
+ $format = iconv_substr($format, 0, iconv_strpos($format, '.') + 1) . '###'
+ . iconv_substr($format, iconv_strrpos($format, '0') + 1);
+ }
+ }
+ } else {
+ $value = Zend_Locale_Math::round($value, 0);
+ $options['precision'] = 0;
+ }
+ $value = Zend_Locale_Math::normalize($value);
+ }
+
+ if (iconv_strpos($format, '0') === false) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception('Wrong format... missing 0');
+ }
+
+ // get number parts
+ $pos = iconv_strpos($value, '.');
+ if ($pos !== false) {
+ if ($options['precision'] === null) {
+ $precstr = iconv_substr($value, $pos + 1);
+ } else {
+ $precstr = iconv_substr($value, $pos + 1, $options['precision']);
+ if (iconv_strlen($precstr) < $options['precision']) {
+ $precstr = $precstr . str_pad("0", ($options['precision'] - iconv_strlen($precstr)), "0");
+ }
+ }
+ } else {
+ if ($options['precision'] > 0) {
+ $precstr = str_pad("0", ($options['precision']), "0");
+ }
+ }
+
+ if ($options['precision'] === null) {
+ if (isset($precstr)) {
+ $options['precision'] = iconv_strlen($precstr);
+ } else {
+ $options['precision'] = 0;
+ }
+ }
+
+ // get fraction and format lengths
+ if (strpos($value, '.') !== false) {
+ $number = substr((string) $value, 0, strpos($value, '.'));
+ } else {
+ $number = $value;
+ }
+
+ $prec = call_user_func(Zend_Locale_Math::$sub, $value, $number, $options['precision']);
+ $prec = Zend_Locale_Math::floatalize($prec);
+ $prec = Zend_Locale_Math::normalize($prec);
+ if (iconv_strpos($prec, '-') !== false) {
+ $prec = iconv_substr($prec, 1);
+ }
+
+ if (($prec == 0) and ($options['precision'] > 0)) {
+ $prec = "0.0";
+ }
+
+ if (($options['precision'] + 2) > iconv_strlen($prec)) {
+ $prec = str_pad((string) $prec, $options['precision'] + 2, "0", STR_PAD_RIGHT);
+ }
+
+ if (iconv_strpos($number, '-') !== false) {
+ $number = iconv_substr($number, 1);
+ }
+ $group = iconv_strrpos($format, ',');
+ $group2 = iconv_strpos ($format, ',');
+ $point = iconv_strpos ($format, '0');
+ // Add fraction
+ $rest = "";
+ if (iconv_strpos($format, '.')) {
+ $rest = iconv_substr($format, iconv_strpos($format, '.') + 1);
+ $length = iconv_strlen($rest);
+ for($x = 0; $x < $length; ++$x) {
+ if (($rest[0] == '0') || ($rest[0] == '#')) {
+ $rest = iconv_substr($rest, 1);
+ }
+ }
+ $format = iconv_substr($format, 0, iconv_strlen($format) - iconv_strlen($rest));
+ }
+
+ if ($options['precision'] == '0') {
+ if (iconv_strrpos($format, '-') != 0) {
+ $format = iconv_substr($format, 0, $point)
+ . iconv_substr($format, iconv_strrpos($format, '#') + 2);
+ } else {
+ $format = iconv_substr($format, 0, $point);
+ }
+ } else {
+ $format = iconv_substr($format, 0, $point) . $symbols['decimal']
+ . iconv_substr($prec, 2);
+ }
+
+ $format .= $rest;
+ // Add seperation
+ if ($group == 0) {
+ // no seperation
+ $format = $number . iconv_substr($format, $point);
+ } else if ($group == $group2) {
+ // only 1 seperation
+ $seperation = ($point - $group);
+ for ($x = iconv_strlen($number); $x > $seperation; $x -= $seperation) {
+ if (iconv_substr($number, 0, $x - $seperation) !== "") {
+ $number = iconv_substr($number, 0, $x - $seperation) . $symbols['group']
+ . iconv_substr($number, $x - $seperation);
+ }
+ }
+ $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point);
+ } else {
+
+ // 2 seperations
+ if (iconv_strlen($number) > ($point - $group)) {
+ $seperation = ($point - $group);
+ $number = iconv_substr($number, 0, iconv_strlen($number) - $seperation) . $symbols['group']
+ . iconv_substr($number, iconv_strlen($number) - $seperation);
+
+ if ((iconv_strlen($number) - 1) > ($point - $group + 1)) {
+ $seperation2 = ($group - $group2 - 1);
+ for ($x = iconv_strlen($number) - $seperation2 - 2; $x > $seperation2; $x -= $seperation2) {
+ $number = iconv_substr($number, 0, $x - $seperation2) . $symbols['group']
+ . iconv_substr($number, $x - $seperation2);
+ }
+ }
+
+ }
+ $format = iconv_substr($format, 0, iconv_strpos($format, '#')) . $number . iconv_substr($format, $point);
+ }
+ // set negative sign
+ if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $options['precision']) < 0) {
+ if (iconv_strpos($format, '-') === false) {
+ $format = $symbols['minus'] . $format;
+ } else {
+ $format = str_replace('-', $symbols['minus'], $format);
+ }
+ }
+
+ self::_setEncoding($oenc);
+ return (string) $format;
+ }
+
+ /**
+ * @param string $format
+ * @param string $value
+ * @param int $precision
+ * @return string
+ */
+ private static function _seperateFormat($format, $value, $precision)
+ {
+ if (iconv_strpos($format, ';') !== false) {
+ if (call_user_func(Zend_Locale_Math::$comp, $value, 0, $precision) < 0) {
+ $tmpformat = iconv_substr($format, iconv_strpos($format, ';') + 1);
+ if ($tmpformat[0] == '(') {
+ $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
+ } else {
+ $format = $tmpformat;
+ }
+ } else {
+ $format = iconv_substr($format, 0, iconv_strpos($format, ';'));
+ }
+ }
+
+ return $format;
+ }
+
+
+ /**
+ * Checks if the input contains a normalized or localized number
+ *
+ * @param string $input Localized number string
+ * @param array $options Options: locale. See {@link setOptions()} for details.
+ * @return boolean Returns true if a number was found
+ */
+ public static function isNumber($input, array $options = array())
+ {
+ if (!self::_getUniCodeSupport()) {
+ trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE);
+ }
+
+ $options = self::_checkOptions($options) + self::$_options;
+
+ // Get correct signs for this locale
+ $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
+
+ $regexs = Zend_Locale_Format::_getRegexForType('decimalnumber', $options);
+ $regexs = array_merge($regexs, Zend_Locale_Format::_getRegexForType('scientificnumber', $options));
+ if (!empty($input) && ($input[0] == $symbols['decimal'])) {
+ $input = 0 . $input;
+ }
+ foreach ($regexs as $regex) {
+ preg_match($regex, $input, $found);
+ if (isset($found[0])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Internal method to convert cldr number syntax into regex
+ *
+ * @param string $type
+ * @param array $options Options: locale. See {@link setOptions()} for details.
+ * @return string
+ * @throws Zend_Locale_Exception
+ */
+ private static function _getRegexForType($type, $options)
+ {
+ $decimal = Zend_Locale_Data::getContent($options['locale'], $type);
+ $decimal = preg_replace('/[^#0,;\.\-Ee]/u', '',$decimal);
+ $patterns = explode(';', $decimal);
+
+ if (count($patterns) == 1) {
+ $patterns[1] = '-' . $patterns[0];
+ }
+
+ $symbols = Zend_Locale_Data::getList($options['locale'],'symbols');
+
+ foreach($patterns as $pkey => $pattern) {
+ $regex[$pkey] = '/^';
+ $rest = 0;
+ $end = null;
+ if (strpos($pattern, '.') !== false) {
+ $end = substr($pattern, strpos($pattern, '.') + 1);
+ $pattern = substr($pattern, 0, -strlen($end) - 1);
+ }
+
+ if (strpos($pattern, ',') !== false) {
+ $parts = explode(',', $pattern);
+ $count = count($parts);
+ foreach($parts as $key => $part) {
+ switch ($part) {
+ case '#':
+ case '-#':
+ if ($part[0] == '-') {
+ $regex[$pkey] .= '[' . $symbols['minus'] . '-]{0,1}';
+ } else {
+ $regex[$pkey] .= '[' . $symbols['plus'] . '+]{0,1}';
+ }
+
+ if (($parts[$key + 1]) == '##0') {
+ $regex[$pkey] .= '[0-9]{1,3}';
+ } else if (($parts[$key + 1]) == '##') {
+ $regex[$pkey] .= '[0-9]{1,2}';
+ } else {
+ throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 1):"' . $pattern . '"');
+ }
+ break;
+ case '##':
+ if ($parts[$key + 1] == '##0') {
+ $regex[$pkey] .= '(\\' . $symbols['group'] . '{0,1}[0-9]{2})*';
+ } else {
+ throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 2):"' . $pattern . '"');
+ }
+ break;
+ case '##0':
+ if ($parts[$key - 1] == '##') {
+ $regex[$pkey] .= '[0-9]';
+ } else if (($parts[$key - 1] == '#') || ($parts[$key - 1] == '-#')) {
+ $regex[$pkey] .= '(\\' . $symbols['group'] . '{0,1}[0-9]{3})*';
+ } else {
+ throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 3):"' . $pattern . '"');
+ }
+ break;
+ case '#0':
+ if ($key == 0) {
+ $regex[$pkey] .= '[0-9]*';
+ } else {
+ throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 4):"' . $pattern . '"');
+ }
+ break;
+ }
+ }
+ }
+
+ if (strpos($pattern, 'E') !== false) {
+ if (($pattern == '#E0') || ($pattern == '#E00')) {
+ $regex[$pkey] .= '[' . $symbols['plus']. '+]{0,1}[0-9]{1,}(\\' . $symbols['decimal'] . '[0-9]{1,})*[eE][' . $symbols['plus']. '+]{0,1}[0-9]{1,}';
+ } else if (($pattern == '-#E0') || ($pattern == '-#E00')) {
+ $regex[$pkey] .= '[' . $symbols['minus']. '-]{0,1}[0-9]{1,}(\\' . $symbols['decimal'] . '[0-9]{1,})*[eE][' . $symbols['minus']. '-]{0,1}[0-9]{1,}';
+ } else {
+ throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 5):"' . $pattern . '"');
+ }
+ }
+
+ if (!empty($end)) {
+ if ($end == '###') {
+ $regex[$pkey] .= '(\\' . $symbols['decimal'] . '{1}[0-9]{1,}){0,1}';
+ } else if ($end == '###-') {
+ $regex[$pkey] .= '(\\' . $symbols['decimal'] . '{1}[0-9]{1,}){0,1}[' . $symbols['minus']. '-]';
+ } else {
+ throw new Zend_Locale_Exception('Unsupported token for numberformat (Pos 6):"' . $pattern . '"');
+ }
+ }
+
+ $regex[$pkey] .= '$/u';
+ }
+
+ return $regex;
+ }
+
+ /**
+ * Alias for getNumber
+ *
+ * @param string $input Number to localize
+ * @param array $options Options: locale, precision. See {@link setOptions()} for details.
+ * @return float
+ */
+ public static function getFloat($input, array $options = array())
+ {
+ return floatval(self::getNumber($input, $options));
+ }
+
+ /**
+ * Returns a locale formatted integer number
+ * Alias for toNumber()
+ *
+ * @param string $value Number to normalize
+ * @param array $options Options: locale, precision. See {@link setOptions()} for details.
+ * @return string Locale formatted number
+ */
+ public static function toFloat($value, array $options = array())
+ {
+ $options['number_format'] = Zend_Locale_Format::STANDARD;
+ return self::toNumber($value, $options);
+ }
+
+ /**
+ * Returns if a float was found
+ * Alias for isNumber()
+ *
+ * @param string $value Localized number string
+ * @param array $options Options: locale. See {@link setOptions()} for details.
+ * @return boolean Returns true if a number was found
+ */
+ public static function isFloat($value, array $options = array())
+ {
+ return self::isNumber($value, $options);
+ }
+
+ /**
+ * Returns the first found integer from an string
+ * Parsing depends on given locale (grouping and decimal)
+ *
+ * Examples for input:
+ * ' 2345.4356,1234' = 23455456
+ * '+23,3452.123' = 233452
+ * ' 12343 ' = 12343
+ * '-9456km' = -9456
+ * '0' = 0
+ * '(-){0,1}(\d+(\.){0,1})*(\,){0,1})\d+'
+ *
+ * @param string $input Input string to parse for numbers
+ * @param array $options Options: locale. See {@link setOptions()} for details.
+ * @return integer Returns the extracted number
+ */
+ public static function getInteger($input, array $options = array())
+ {
+ $options['precision'] = 0;
+ return intval(self::getFloat($input, $options));
+ }
+
+ /**
+ * Returns a localized number
+ *
+ * @param string $value Number to normalize
+ * @param array $options Options: locale. See {@link setOptions()} for details.
+ * @return string Locale formatted number
+ */
+ public static function toInteger($value, array $options = array())
+ {
+ $options['precision'] = 0;
+ $options['number_format'] = Zend_Locale_Format::STANDARD;
+ return self::toNumber($value, $options);
+ }
+
+ /**
+ * Returns if a integer was found
+ *
+ * @param string $value Localized number string
+ * @param array $options Options: locale. See {@link setOptions()} for details.
+ * @return boolean Returns true if a integer was found
+ */
+ public static function isInteger($value, array $options = array())
+ {
+ if (!self::isNumber($value, $options)) {
+ return false;
+ }
+
+ if (self::getInteger($value, $options) == self::getFloat($value, $options)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts a format string from PHP's date format to ISO format
+ * Remember that Zend Date always returns localized string, so a month name which returns the english
+ * month in php's date() will return the translated month name with this function... use 'en' as locale
+ * if you are in need of the original english names
+ *
+ * The conversion has the following restrictions:
+ * 'a', 'A' - Meridiem is not explicit upper/lowercase, you have to upper/lowercase the translated value yourself
+ *
+ * @param string $format Format string in PHP's date format
+ * @return string Format string in ISO format
+ */
+ public static function convertPhpToIsoFormat($format)
+ {
+ if ($format === null) {
+ return null;
+ }
+
+ $convert = array(
+ 'd' => 'dd' , 'D' => 'EE' , 'j' => 'd' , 'l' => 'EEEE',
+ 'N' => 'eee' , 'S' => 'SS' , 'w' => 'e' , 'z' => 'D' ,
+ 'W' => 'ww' , 'F' => 'MMMM', 'm' => 'MM' , 'M' => 'MMM' ,
+ 'n' => 'M' , 't' => 'ddd' , 'L' => 'l' , 'o' => 'YYYY',
+ 'Y' => 'yyyy', 'y' => 'yy' , 'a' => 'a' , 'A' => 'a' ,
+ 'B' => 'B' , 'g' => 'h' , 'G' => 'H' , 'h' => 'hh' ,
+ 'H' => 'HH' , 'i' => 'mm' , 's' => 'ss' , 'e' => 'zzzz',
+ 'I' => 'I' , 'O' => 'Z' , 'P' => 'ZZZZ', 'T' => 'z' ,
+ 'Z' => 'X' , 'c' => 'yyyy-MM-ddTHH:mm:ssZZZZ', 'r' => 'r',
+ 'U' => 'U',
+ );
+ $escaped = false;
+ $inEscapedString = false;
+ $converted = array();
+ foreach (str_split($format) as $char) {
+ if (!$escaped && $char == '\\') {
+ // Next char will be escaped: let's remember it
+ $escaped = true;
+ } elseif ($escaped) {
+ if (!$inEscapedString) {
+ // First escaped string: start the quoted chunk
+ $converted[] = "'";
+ $inEscapedString = true;
+ }
+ // Since the previous char was a \ and we are in the quoted
+ // chunk, let's simply add $char as it is
+ $converted[] = $char;
+ $escaped = false;
+ } elseif ($char == "'") {
+ // Single quotes need to be escaped like this
+ $converted[] = "''";
+ } else {
+ if ($inEscapedString) {
+ // Close the single-quoted chunk
+ $converted[] = "'";
+ $inEscapedString = false;
+ }
+ // Convert the unescaped char if needed
+ if (isset($convert[$char])) {
+ $converted[] = $convert[$char];
+ } else {
+ $converted[] = $char;
+ }
+ }
+ }
+
+ return implode($converted);
+ }
+
+ /**
+ * Parse date and split in named array fields
+ *
+ * @param string $date Date string to parse
+ * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
+ * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
+ * @throws Zend_Locale_Exception
+ */
+ private static function _parseDate($date, $options)
+ {
+ if (!self::_getUniCodeSupport()) {
+ trigger_error("Sorry, your PCRE extension does not support UTF8 which is needed for the I18N core", E_USER_NOTICE);
+ }
+
+ $options = self::_checkOptions($options) + self::$_options;
+ $test = array('h', 'H', 'm', 's', 'y', 'Y', 'M', 'd', 'D', 'E', 'S', 'l', 'B', 'I',
+ 'X', 'r', 'U', 'G', 'w', 'e', 'a', 'A', 'Z', 'z', 'v');
+
+ $format = $options['date_format'];
+ $number = $date; // working copy
+ $result['date_format'] = $format; // save the format used to normalize $number (convenience)
+ $result['locale'] = $options['locale']; // save the locale used to normalize $number (convenience)
+
+ $oenc = self::_getEncoding();
+ self::_setEncoding('UTF-8');
+ $day = iconv_strpos($format, 'd');
+ $month = iconv_strpos($format, 'M');
+ $year = iconv_strpos($format, 'y');
+ $hour = iconv_strpos($format, 'H');
+ $min = iconv_strpos($format, 'm');
+ $sec = iconv_strpos($format, 's');
+ $am = null;
+ if ($hour === false) {
+ $hour = iconv_strpos($format, 'h');
+ }
+ if ($year === false) {
+ $year = iconv_strpos($format, 'Y');
+ }
+ if ($day === false) {
+ $day = iconv_strpos($format, 'E');
+ if ($day === false) {
+ $day = iconv_strpos($format, 'D');
+ }
+ }
+
+ if ($day !== false) {
+ $parse[$day] = 'd';
+ if (!empty($options['locale']) && ($options['locale'] !== 'root') &&
+ (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) {
+ // erase day string
+ $daylist = Zend_Locale_Data::getList($options['locale'], 'day');
+ foreach($daylist as $key => $name) {
+ if (iconv_strpos($number, $name) !== false) {
+ $number = str_replace($name, "EEEE", $number);
+ break;
+ }
+ }
+ }
+ }
+ $position = false;
+
+ if ($month !== false) {
+ $parse[$month] = 'M';
+ if (!empty($options['locale']) && ($options['locale'] !== 'root') &&
+ (!is_object($options['locale']) || ((string) $options['locale'] !== 'root'))) {
+ // prepare to convert month name to their numeric equivalents, if requested,
+ // and we have a $options['locale']
+ $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'],
+ 'month'));
+ if ($position === false) {
+ $position = self::_replaceMonth($number, Zend_Locale_Data::getList($options['locale'],
+ 'month', array('gregorian', 'format', 'abbreviated')));
+ }
+ }
+ }
+ if ($year !== false) {
+ $parse[$year] = 'y';
+ }
+ if ($hour !== false) {
+ $parse[$hour] = 'H';
+ }
+ if ($min !== false) {
+ $parse[$min] = 'm';
+ }
+ if ($sec !== false) {
+ $parse[$sec] = 's';
+ }
+
+ if (empty($parse)) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception("Unknown date format, neither date nor time in '" . $format . "' found");
+ }
+ ksort($parse);
+
+ // get daytime
+ if (iconv_strpos($format, 'a') !== false) {
+ if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'am'))) !== false) {
+ $am = true;
+ } else if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'pm'))) !== false) {
+ $am = false;
+ }
+ }
+
+ // split number parts
+ $split = false;
+ preg_match_all('/\d+/u', $number, $splitted);
+
+ if (count($splitted[0]) == 0) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception("No date part in '$date' found.");
+ }
+ if (count($splitted[0]) == 1) {
+ $split = 0;
+ }
+ $cnt = 0;
+ foreach($parse as $key => $value) {
+
+ switch($value) {
+ case 'd':
+ if ($split === false) {
+ if (count($splitted[0]) > $cnt) {
+ $result['day'] = $splitted[0][$cnt];
+ }
+ } else {
+ $result['day'] = iconv_substr($splitted[0][0], $split, 2);
+ $split += 2;
+ }
+ ++$cnt;
+ break;
+ case 'M':
+ if ($split === false) {
+ if (count($splitted[0]) > $cnt) {
+ $result['month'] = $splitted[0][$cnt];
+ }
+ } else {
+ $result['month'] = iconv_substr($splitted[0][0], $split, 2);
+ $split += 2;
+ }
+ ++$cnt;
+ break;
+ case 'y':
+ $length = 2;
+ if ((iconv_substr($format, $year, 4) == 'yyyy')
+ || (iconv_substr($format, $year, 4) == 'YYYY')) {
+ $length = 4;
+ }
+
+ if ($split === false) {
+ if (count($splitted[0]) > $cnt) {
+ $result['year'] = $splitted[0][$cnt];
+ }
+ } else {
+ $result['year'] = iconv_substr($splitted[0][0], $split, $length);
+ $split += $length;
+ }
+
+ ++$cnt;
+ break;
+ case 'H':
+ if ($split === false) {
+ if (count($splitted[0]) > $cnt) {
+ $result['hour'] = $splitted[0][$cnt];
+ }
+ } else {
+ $result['hour'] = iconv_substr($splitted[0][0], $split, 2);
+ $split += 2;
+ }
+ ++$cnt;
+ break;
+ case 'm':
+ if ($split === false) {
+ if (count($splitted[0]) > $cnt) {
+ $result['minute'] = $splitted[0][$cnt];
+ }
+ } else {
+ $result['minute'] = iconv_substr($splitted[0][0], $split, 2);
+ $split += 2;
+ }
+ ++$cnt;
+ break;
+ case 's':
+ if ($split === false) {
+ if (count($splitted[0]) > $cnt) {
+ $result['second'] = $splitted[0][$cnt];
+ }
+ } else {
+ $result['second'] = iconv_substr($splitted[0][0], $split, 2);
+ $split += 2;
+ }
+ ++$cnt;
+ break;
+ }
+ }
+
+ // AM/PM correction
+ if ($hour !== false) {
+ if (($am === true) and ($result['hour'] == 12)){
+ $result['hour'] = 0;
+ } else if (($am === false) and ($result['hour'] != 12)) {
+ $result['hour'] += 12;
+ }
+ }
+
+ if ($options['fix_date'] === true) {
+ $result['fixed'] = 0; // nothing has been "fixed" by swapping date parts around (yet)
+ }
+
+ if ($day !== false) {
+ // fix false month
+ if (isset($result['day']) and isset($result['month'])) {
+ if (($position !== false) and ((iconv_strpos($date, $result['day']) === false) or
+ (isset($result['year']) and (iconv_strpos($date, $result['year']) === false)))) {
+ if ($options['fix_date'] !== true) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception("Unable to parse date '$date' using '" . $format
+ . "' (false month, $position, $month)");
+ }
+ $temp = $result['day'];
+ $result['day'] = $result['month'];
+ $result['month'] = $temp;
+ $result['fixed'] = 1;
+ }
+ }
+
+ // fix switched values d <> y
+ if (isset($result['day']) and isset($result['year'])) {
+ if ($result['day'] > 31) {
+ if ($options['fix_date'] !== true) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
+ . $format . "' (d <> y)");
+ }
+ $temp = $result['year'];
+ $result['year'] = $result['day'];
+ $result['day'] = $temp;
+ $result['fixed'] = 2;
+ }
+ }
+
+ // fix switched values M <> y
+ if (isset($result['month']) and isset($result['year'])) {
+ if ($result['month'] > 31) {
+ if ($options['fix_date'] !== true) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
+ . $format . "' (M <> y)");
+ }
+ $temp = $result['year'];
+ $result['year'] = $result['month'];
+ $result['month'] = $temp;
+ $result['fixed'] = 3;
+ }
+ }
+
+ // fix switched values M <> d
+ if (isset($result['month']) and isset($result['day'])) {
+ if ($result['month'] > 12) {
+ if ($options['fix_date'] !== true || $result['month'] > 31) {
+ self::_setEncoding($oenc);
+ throw new Zend_Locale_Exception("Unable to parse date '$date' using '"
+ . $format . "' (M <> d)");
+ }
+ $temp = $result['day'];
+ $result['day'] = $result['month'];
+ $result['month'] = $temp;
+ $result['fixed'] = 4;
+ }
+ }
+ }
+
+ if (isset($result['year'])) {
+ if (((iconv_strlen($result['year']) == 2) && ($result['year'] < 10)) ||
+ (((iconv_strpos($format, 'yy') !== false) && (iconv_strpos($format, 'yyyy') === false)) ||
+ ((iconv_strpos($format, 'YY') !== false) && (iconv_strpos($format, 'YYYY') === false)))) {
+ if (($result['year'] >= 0) && ($result['year'] < 100)) {
+ if ($result['year'] < 70) {
+ $result['year'] = (int) $result['year'] + 100;
+ }
+
+ $result['year'] = (int) $result['year'] + 1900;
+ }
+ }
+ }
+
+ self::_setEncoding($oenc);
+ return $result;
+ }
+
+ /**
+ * Search $number for a month name found in $monthlist, and replace if found.
+ *
+ * @param string $number Date string (modified)
+ * @param array $monthlist List of month names
+ *
+ * @return int|false Position of replaced string (false if nothing replaced)
+ */
+ protected static function _replaceMonth(&$number, $monthlist)
+ {
+ // If $locale was invalid, $monthlist will default to a "root" identity
+ // mapping for each month number from 1 to 12.
+ // If no $locale was given, or $locale was invalid, do not use this identity mapping to normalize.
+ // Otherwise, translate locale aware month names in $number to their numeric equivalents.
+ $position = false;
+ if ($monthlist && $monthlist[1] != 1) {
+ foreach($monthlist as $key => $name) {
+ if (($position = iconv_strpos($number, $name, 0, 'UTF-8')) !== false) {
+ $number = str_ireplace($name, $key, $number);
+ return $position;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the default date format for $locale.
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT')
+ * @return string format
+ * @throws Zend_Locale_Exception throws an exception when locale data is broken
+ */
+ public static function getDateFormat($locale = null)
+ {
+ $format = Zend_Locale_Data::getContent($locale, 'date');
+ if (empty($format)) {
+ throw new Zend_Locale_Exception("failed to receive data from locale $locale");
+ }
+
+ return $format;
+ }
+
+ /**
+ * Returns an array with the normalized date from an locale date
+ * a input of 10.01.2006 without a $locale would return:
+ * array ('day' => 10, 'month' => 1, 'year' => 2006)
+ * The 'locale' option is only used to convert human readable day
+ * and month names to their numeric equivalents.
+ * The 'format' option allows specification of self-defined date formats,
+ * when not using the default format for the 'locale'.
+ *
+ * @param string $date Date string
+ * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
+ * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
+ */
+ public static function getDate($date, array $options = array())
+ {
+ $options = self::_checkOptions($options) + self::$_options;
+ if (empty($options['date_format'])) {
+ $options['format_type'] = 'iso';
+ $options['date_format'] = self::getDateFormat($options['locale']);
+ }
+
+ return self::_parseDate($date, $options);
+ }
+
+ /**
+ * Returns if the given datestring contains all date parts from the given format.
+ * If no format is given, the default date format from the locale is used
+ * If you want to check if the date is a proper date you should use Zend_Date::isDate()
+ *
+ * @param string $date Date string
+ * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
+ * @return boolean
+ */
+ public static function checkDateFormat($date, array $options = array())
+ {
+ try {
+ $date = self::getDate($date, $options);
+ } catch (Exception $e) {
+ return false;
+ }
+
+ if (empty($options['date_format'])) {
+ $options['format_type'] = 'iso';
+ $options['date_format'] = self::getDateFormat(isset($options['locale']) ? $options['locale'] : null);
+ }
+ $options = self::_checkOptions($options) + self::$_options;
+
+ // day expected but not parsed
+ if ((iconv_strpos($options['date_format'], 'd', 0, 'UTF-8') !== false) and (!isset($date['day']) or ($date['day'] === ""))) {
+ return false;
+ }
+
+ // month expected but not parsed
+ if ((iconv_strpos($options['date_format'], 'M', 0, 'UTF-8') !== false) and (!isset($date['month']) or ($date['month'] === ""))) {
+ return false;
+ }
+
+ // year expected but not parsed
+ if (((iconv_strpos($options['date_format'], 'Y', 0, 'UTF-8') !== false) or
+ (iconv_strpos($options['date_format'], 'y', 0, 'UTF-8') !== false)) and (!isset($date['year']) or ($date['year'] === ""))) {
+ return false;
+ }
+
+ // second expected but not parsed
+ if ((iconv_strpos($options['date_format'], 's', 0, 'UTF-8') !== false) and (!isset($date['second']) or ($date['second'] === ""))) {
+ return false;
+ }
+
+ // minute expected but not parsed
+ if ((iconv_strpos($options['date_format'], 'm', 0, 'UTF-8') !== false) and (!isset($date['minute']) or ($date['minute'] === ""))) {
+ return false;
+ }
+
+ // hour expected but not parsed
+ if (((iconv_strpos($options['date_format'], 'H', 0, 'UTF-8') !== false) or
+ (iconv_strpos($options['date_format'], 'h', 0, 'UTF-8') !== false)) and (!isset($date['hour']) or ($date['hour'] === ""))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the default time format for $locale.
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT')
+ * @return string format
+ * @throws Zend_Locale_Exception
+ */
+ public static function getTimeFormat($locale = null)
+ {
+ $format = Zend_Locale_Data::getContent($locale, 'time');
+ if (empty($format)) {
+ throw new Zend_Locale_Exception("failed to receive data from locale $locale");
+ }
+ return $format;
+ }
+
+ /**
+ * Returns an array with 'hour', 'minute', and 'second' elements extracted from $time
+ * according to the order described in $format. For a format of 'H:i:s', and
+ * an input of 11:20:55, getTime() would return:
+ * array ('hour' => 11, 'minute' => 20, 'second' => 55)
+ * The optional $locale parameter may be used to help extract times from strings
+ * containing both a time and a day or month name.
+ *
+ * @param string $time Time string
+ * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
+ * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
+ */
+ public static function getTime($time, array $options = array())
+ {
+ $options = self::_checkOptions($options) + self::$_options;
+ if (empty($options['date_format'])) {
+ $options['format_type'] = 'iso';
+ $options['date_format'] = self::getTimeFormat($options['locale']);
+ }
+ return self::_parseDate($time, $options);
+ }
+
+ /**
+ * Returns the default datetime format for $locale.
+ *
+ * @param string|Zend_Locale $locale OPTIONAL Locale of $number, possibly in string form (e.g. 'de_AT')
+ * @return string format
+ * @throws Zend_Locale_Exception
+ */
+ public static function getDateTimeFormat($locale = null)
+ {
+ $format = Zend_Locale_Data::getContent($locale, 'datetime');
+ if (empty($format)) {
+ throw new Zend_Locale_Exception("failed to receive data from locale $locale");
+ }
+ return $format;
+ }
+
+ /**
+ * Returns an array with 'year', 'month', 'day', 'hour', 'minute', and 'second' elements
+ * extracted from $datetime according to the order described in $format. For a format of 'd.M.y H:i:s',
+ * and an input of 10.05.1985 11:20:55, getDateTime() would return:
+ * array ('year' => 1985, 'month' => 5, 'day' => 10, 'hour' => 11, 'minute' => 20, 'second' => 55)
+ * The optional $locale parameter may be used to help extract times from strings
+ * containing both a time and a day or month name.
+ *
+ * @param string $datetime DateTime string
+ * @param array $options Options: format_type, fix_date, locale, date_format. See {@link setOptions()} for details.
+ * @return array Possible array members: day, month, year, hour, minute, second, fixed, format
+ */
+ public static function getDateTime($datetime, array $options = array())
+ {
+ $options = self::_checkOptions($options) + self::$_options;
+ if (empty($options['date_format'])) {
+ $options['format_type'] = 'iso';
+ $options['date_format'] = self::getDateTimeFormat($options['locale']);
+ }
+ return self::_parseDate($datetime, $options);
+ }
+
+ /**
+ * Internal method to detect of Unicode supports UTF8
+ * which should be enabled within vanilla php installations
+ *
+ * @return boolean
+ */
+ protected static function _getUniCodeSupport()
+ {
+ return (@preg_match('/\pL/u', 'a')) ? true : false;
+ }
+
+ /**
+ * Internal method to retrieve the current encoding via the ini setting
+ * default_charset for PHP >= 5.6 or iconv_get_encoding otherwise.
+ *
+ * @return string
+ */
+ protected static function _getEncoding()
+ {
+ $oenc = PHP_VERSION_ID < 50600
+ ? iconv_get_encoding('internal_encoding')
+ : ini_get('default_charset');
+
+ return $oenc;
+ }
+
+ /**
+ * Internal method to set the encoding via the ini setting
+ * default_charset for PHP >= 5.6 or iconv_set_encoding otherwise.
+ *
+ * @param string $encoding
+ * @return void
+ */
+ protected static function _setEncoding($encoding)
+ {
+ if (PHP_VERSION_ID < 50600) {
+ iconv_set_encoding('internal_encoding', $encoding);
+ } else {
+ ini_set('default_charset', $encoding);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Locale/Math.php b/library/vendor/Zend/Locale/Math.php
new file mode 100644
index 0000000..1b61b07
--- /dev/null
+++ b/library/vendor/Zend/Locale/Math.php
@@ -0,0 +1,354 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Utility class for proxying math function to bcmath functions, if present,
+ * otherwise to PHP builtin math operators, with limited detection of overflow conditions.
+ * Sampling of PHP environments and platforms suggests that at least 80% to 90% support bcmath.
+ * Thus, this file should be as light as possible.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_Locale_Math
+{
+ // support unit testing without using bcmath functions
+ public static $_bcmathDisabled = false;
+
+ public static $add = array('Zend_Locale_Math', 'Add');
+ public static $sub = array('Zend_Locale_Math', 'Sub');
+ public static $pow = array('Zend_Locale_Math', 'Pow');
+ public static $mul = array('Zend_Locale_Math', 'Mul');
+ public static $div = array('Zend_Locale_Math', 'Div');
+ public static $comp = array('Zend_Locale_Math', 'Comp');
+ public static $sqrt = array('Zend_Locale_Math', 'Sqrt');
+ public static $mod = array('Zend_Locale_Math', 'Mod');
+ public static $scale = 'bcscale';
+
+ public static function isBcmathDisabled()
+ {
+ return self::$_bcmathDisabled;
+ }
+
+ /**
+ * Surprisingly, the results of this implementation of round()
+ * prove better than the native PHP round(). For example, try:
+ * round(639.795, 2);
+ * round(267.835, 2);
+ * round(0.302515, 5);
+ * round(0.36665, 4);
+ * then try:
+ * Zend_Locale_Math::round('639.795', 2);
+ */
+ public static function round($op1, $precision = 0)
+ {
+ if (self::$_bcmathDisabled) {
+ $op1 = round($op1, $precision);
+ if (strpos((string) $op1, 'E') === false) {
+ return self::normalize(round($op1, $precision));
+ }
+ }
+
+ if (strpos($op1, 'E') !== false) {
+ $op1 = self::floatalize($op1);
+ }
+
+ $op1 = trim(self::normalize($op1));
+ $length = strlen($op1);
+ if (($decPos = strpos($op1, '.')) === false) {
+ $op1 .= '.0';
+ $decPos = $length;
+ $length += 2;
+ }
+ if ($precision < 0 && abs($precision) > $decPos) {
+ return '0';
+ }
+
+ $digitsBeforeDot = $length - ($decPos + 1);
+ if ($precision >= ($length - ($decPos + 1))) {
+ return $op1;
+ }
+
+ if ($precision === 0) {
+ $triggerPos = 1;
+ $roundPos = -1;
+ } elseif ($precision > 0) {
+ $triggerPos = $precision + 1;
+ $roundPos = $precision;
+ } else {
+ $triggerPos = $precision;
+ $roundPos = $precision -1;
+ }
+
+ $triggerDigit = $op1[$triggerPos + $decPos];
+ if ($precision < 0) {
+ // zero fill digits to the left of the decimal place
+ $op1 = substr($op1, 0, $decPos + $precision) . str_pad('', abs($precision), '0');
+ }
+
+ if ($triggerDigit >= '5') {
+ if ($roundPos + $decPos == -1) {
+ return str_pad('1', $decPos + 1, '0');
+ }
+
+ $roundUp = str_pad('', $length, '0');
+ $roundUp[$decPos] = '.';
+ $roundUp[$roundPos + $decPos] = '1';
+
+ if ($op1 > 0) {
+ if (self::$_bcmathDisabled) {
+ return Zend_Locale_Math_PhpMath::Add($op1, $roundUp, $precision);
+ }
+ return self::Add($op1, $roundUp, $precision);
+ } else {
+ if (self::$_bcmathDisabled) {
+ return Zend_Locale_Math_PhpMath::Sub($op1, $roundUp, $precision);
+ }
+ return self::Sub($op1, $roundUp, $precision);
+ }
+ } elseif ($precision >= 0) {
+ return substr($op1, 0, $decPos + ($precision ? $precision + 1: 0));
+ }
+
+ return (string) $op1;
+ }
+
+ /**
+ * Convert a scientific notation to float
+ * Additionally fixed a problem with PHP <= 5.2.x with big integers
+ *
+ * @param string $value
+ */
+ public static function floatalize($value)
+ {
+ $value = strtoupper($value);
+ if (strpos($value, 'E') === false) {
+ return $value;
+ }
+
+ $number = substr($value, 0, strpos($value, 'E'));
+ if (strpos($number, '.') !== false) {
+ $post = strlen(substr($number, strpos($number, '.') + 1));
+ $mantis = substr($value, strpos($value, 'E') + 1);
+ if ($mantis < 0) {
+ $post += abs((int) $mantis);
+ }
+
+ $value = number_format($value, $post, '.', '');
+ } else {
+ $value = number_format($value, 0, '.', '');
+ }
+
+ return $value;
+ }
+
+ /**
+ * Normalizes an input to standard english notation
+ * Fixes a problem of BCMath with setLocale which is PHP related
+ *
+ * @param integer $value Value to normalize
+ * @return string Normalized string without BCMath problems
+ */
+ public static function normalize($value)
+ {
+ $convert = localeconv();
+ $value = str_replace($convert['thousands_sep'], "",(string) $value);
+ $value = str_replace($convert['positive_sign'], "", $value);
+ $value = str_replace($convert['decimal_point'], ".",$value);
+ if (!empty($convert['negative_sign']) and (strpos($value, $convert['negative_sign']))) {
+ $value = str_replace($convert['negative_sign'], "", $value);
+ $value = "-" . $value;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Localizes an input from standard english notation
+ * Fixes a problem of BCMath with setLocale which is PHP related
+ *
+ * @param integer $value Value to normalize
+ * @return string Normalized string without BCMath problems
+ */
+ public static function localize($value)
+ {
+ $convert = localeconv();
+ $value = str_replace(".", $convert['decimal_point'], (string) $value);
+ if (!empty($convert['negative_sign']) and (strpos($value, "-"))) {
+ $value = str_replace("-", $convert['negative_sign'], $value);
+ }
+ return $value;
+ }
+
+ /**
+ * Changes exponential numbers to plain string numbers
+ * Fixes a problem of BCMath with numbers containing exponents
+ *
+ * @param integer $value Value to erase the exponent
+ * @param integer $scale (Optional) Scale to use
+ * @return string
+ */
+ public static function exponent($value, $scale = null)
+ {
+ if (!extension_loaded('bcmath')) {
+ return $value;
+ }
+
+ $split = explode('e', $value);
+ if (count($split) == 1) {
+ $split = explode('E', $value);
+ }
+
+ if (count($split) > 1) {
+ $value = bcmul($split[0], bcpow(10, $split[1], $scale), $scale);
+ }
+
+ return $value;
+ }
+
+ /**
+ * BCAdd - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @param integer $scale
+ * @return string
+ */
+ public static function Add($op1, $op2, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ $op2 = self::exponent($op2, $scale);
+
+ return bcadd($op1, $op2, $scale);
+ }
+
+ /**
+ * BCSub - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @param integer $scale
+ * @return string
+ */
+ public static function Sub($op1, $op2, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ $op2 = self::exponent($op2, $scale);
+ return bcsub($op1, $op2, $scale);
+ }
+
+ /**
+ * BCPow - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @param integer $scale
+ * @return string
+ */
+ public static function Pow($op1, $op2, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ $op2 = self::exponent($op2, $scale);
+ return bcpow($op1, $op2, $scale);
+ }
+
+ /**
+ * BCMul - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @param integer $scale
+ * @return string
+ */
+ public static function Mul($op1, $op2, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ $op2 = self::exponent($op2, $scale);
+ return bcmul($op1, $op2, $scale);
+ }
+
+ /**
+ * BCDiv - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @param integer $scale
+ * @return string
+ */
+ public static function Div($op1, $op2, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ $op2 = self::exponent($op2, $scale);
+ return bcdiv($op1, $op2, $scale);
+ }
+
+ /**
+ * BCSqrt - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param integer $scale
+ * @return string
+ */
+ public static function Sqrt($op1, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ return bcsqrt($op1, $scale);
+ }
+
+ /**
+ * BCMod - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @return string
+ */
+ public static function Mod($op1, $op2)
+ {
+ $op1 = self::exponent($op1);
+ $op2 = self::exponent($op2);
+ return bcmod($op1, $op2);
+ }
+
+ /**
+ * BCComp - fixes a problem of BCMath and exponential numbers
+ *
+ * @param string $op1
+ * @param string $op2
+ * @param integer $scale
+ * @return string
+ */
+ public static function Comp($op1, $op2, $scale = null)
+ {
+ $op1 = self::exponent($op1, $scale);
+ $op2 = self::exponent($op2, $scale);
+ return bccomp($op1, $op2, $scale);
+ }
+}
+
+if (!extension_loaded('bcmath')
+ || (defined('TESTS_ZEND_LOCALE_BCMATH_ENABLED') && !TESTS_ZEND_LOCALE_BCMATH_ENABLED)
+) {
+ Zend_Locale_Math_PhpMath::disable();
+}
diff --git a/library/vendor/Zend/Locale/Math/Exception.php b/library/vendor/Zend/Locale/Math/Exception.php
new file mode 100644
index 0000000..fbf6c55
--- /dev/null
+++ b/library/vendor/Zend/Locale/Math/Exception.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale_Math_Exception extends Zend_Locale_Exception
+{
+ protected $op1 = null;
+ protected $op2 = null;
+ protected $result = null;
+
+ public function __construct($message, $op1 = null, $op2 = null, $result = null)
+ {
+ $this->op1 = $op1;
+ $this->op2 = $op2;
+ $this->result = $result;
+ parent::__construct($message);
+ }
+
+ public function getResults()
+ {
+ return array($this->op1, $this->op2, $this->result);
+ }
+}
diff --git a/library/vendor/Zend/Locale/Math/PhpMath.php b/library/vendor/Zend/Locale/Math/PhpMath.php
new file mode 100644
index 0000000..6d774c2
--- /dev/null
+++ b/library/vendor/Zend/Locale/Math/PhpMath.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Utility class for proxying math function to bcmath functions, if present,
+ * otherwise to PHP builtin math operators, with limited detection of overflow conditions.
+ * Sampling of PHP environments and platforms suggests that at least 80% to 90% support bcmath.
+ * This file should only be loaded for the 10% to 20% lacking access to the bcmath extension.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Locale_Math_PhpMath extends Zend_Locale_Math
+{
+ public static function disable()
+ {
+ self::$_bcmathDisabled = true;
+ self::$add = array('Zend_Locale_Math_PhpMath', 'Add');
+ self::$sub = array('Zend_Locale_Math_PhpMath', 'Sub');
+ self::$pow = array('Zend_Locale_Math_PhpMath', 'Pow');
+ self::$mul = array('Zend_Locale_Math_PhpMath', 'Mul');
+ self::$div = array('Zend_Locale_Math_PhpMath', 'Div');
+ self::$comp = array('Zend_Locale_Math_PhpMath', 'Comp');
+ self::$sqrt = array('Zend_Locale_Math_PhpMath', 'Sqrt');
+ self::$mod = array('Zend_Locale_Math_PhpMath', 'Mod');
+ self::$scale = array('Zend_Locale_Math_PhpMath', 'Scale');
+
+ self::$defaultScale = 0;
+ self::$defaultPrecision = 1;
+ }
+
+ public static $defaultScale;
+ public static $defaultPrecision;
+
+
+ public static function Add($op1, $op2, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ $precision = Zend_Locale_Math_PhpMath::$defaultPrecision;
+ } else {
+ $precision = pow(10, -$scale);
+ }
+
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+ $result = $op1 + $op2;
+ if (is_infinite($result) or (abs($result - $op2 - $op1) > $precision)) {
+ throw new Zend_Locale_Math_Exception("addition overflow: $op1 + $op2 != $result", $op1, $op2, $result);
+ }
+
+ return self::round(self::normalize($result), $scale);
+ }
+
+ public static function Sub($op1, $op2, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ $precision = Zend_Locale_Math_PhpMath::$defaultPrecision;
+ } else {
+ $precision = pow(10, -$scale);
+ }
+
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+ $result = $op1 - $op2;
+ if (is_infinite($result) or (abs($result + $op2 - $op1) > $precision)) {
+ throw new Zend_Locale_Math_Exception("subtraction overflow: $op1 - $op2 != $result", $op1, $op2, $result);
+ }
+
+ return self::round(self::normalize($result), $scale);
+ }
+
+ public static function Pow($op1, $op2, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ }
+
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+
+ // BCMath extension doesn't use decimal part of the power
+ // Provide the same behavior
+ $op2 = ($op2 > 0) ? floor($op2) : ceil($op2);
+
+ $result = pow($op1, $op2);
+ if (is_infinite($result) or is_nan($result)) {
+ throw new Zend_Locale_Math_Exception("power overflow: $op1 ^ $op2", $op1, $op2, $result);
+ }
+
+ return self::round(self::normalize($result), $scale);
+ }
+
+ public static function Mul($op1, $op2, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ }
+
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+ $result = $op1 * $op2;
+ if (is_infinite($result) or is_nan($result)) {
+ throw new Zend_Locale_Math_Exception("multiplication overflow: $op1 * $op2 != $result", $op1, $op2, $result);
+ }
+
+ return self::round(self::normalize($result), $scale);
+ }
+
+ public static function Div($op1, $op2, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ }
+
+ if (empty($op2)) {
+ throw new Zend_Locale_Math_Exception("can not divide by zero", $op1, $op2, null);
+ }
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+ $result = $op1 / $op2;
+ if (is_infinite($result) or is_nan($result)) {
+ throw new Zend_Locale_Math_Exception("division overflow: $op1 / $op2 != $result", $op1, $op2, $result);
+ }
+
+ return self::round(self::normalize($result), $scale);
+ }
+
+ public static function Sqrt($op1, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ }
+
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ $op1 = self::normalize($op1);
+ $result = sqrt($op1);
+ if (is_nan($result)) {
+ return NULL;
+ }
+
+ return self::round(self::normalize($result), $scale);
+ }
+
+ public static function Mod($op1, $op2)
+ {
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ if (empty($op2)) {
+ return NULL;
+ }
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+ if ((int)$op2 == 0) {
+ return NULL;
+ }
+ $result = $op1 % $op2;
+ if (is_nan($result) or (($op1 - $result) % $op2 != 0)) {
+ throw new Zend_Locale_Math_Exception("modulus calculation error: $op1 % $op2 != $result", $op1, $op2, $result);
+ }
+
+ return self::normalize($result);
+ }
+
+ public static function Comp($op1, $op2, $scale = null)
+ {
+ if ($scale === null) {
+ $scale = Zend_Locale_Math_PhpMath::$defaultScale;
+ }
+
+ if (empty($op1)) {
+ $op1 = 0;
+ }
+ $op1 = self::normalize($op1);
+ $op2 = self::normalize($op2);
+ if ($scale <> 0) {
+ $op1 = self::round($op1, $scale);
+ $op2 = self::round($op2, $scale);
+ } else {
+ $op1 = ($op1 > 0) ? floor($op1) : ceil($op1);
+ $op2 = ($op2 > 0) ? floor($op2) : ceil($op2);
+ }
+ if ($op1 > $op2) {
+ return 1;
+ } else if ($op1 < $op2) {
+ return -1;
+ }
+ return 0;
+ }
+
+ public static function Scale($scale)
+ {
+ if ($scale > 9) {
+ throw new Zend_Locale_Math_Exception("can not scale to precision $scale", $scale, null, null);
+ }
+ self::$defaultScale = $scale;
+ self::$defaultPrecision = pow(10, -$scale);
+ return true;
+ }
+}
+
+Zend_Locale_Math_PhpMath::disable(); // disable use of bcmath functions
diff --git a/library/vendor/Zend/Log.php b/library/vendor/Zend/Log.php
new file mode 100644
index 0000000..e9d14a9
--- /dev/null
+++ b/library/vendor/Zend/Log.php
@@ -0,0 +1,645 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ *
+ * Convenience methods for log [@see Zend_Log::__call()]:
+ *
+ * @method emerg(string $message, $extras = null)
+ * @method alert(string $message, $extras = null)
+ * @method crit(string $message, $extras = null)
+ * @method err(string $message, $extras = null)
+ * @method warn(string $message, $extras = null)
+ * @method notice(string $message, $extras = null)
+ * @method info(string $message, $extras = null)
+ * @method debug(string $message, $extras = null)
+ */
+class Zend_Log
+{
+ const EMERG = 0; // Emergency: system is unusable
+ const ALERT = 1; // Alert: action must be taken immediately
+ const CRIT = 2; // Critical: critical conditions
+ const ERR = 3; // Error: error conditions
+ const WARN = 4; // Warning: warning conditions
+ const NOTICE = 5; // Notice: normal but significant condition
+ const INFO = 6; // Informational: informational messages
+ const DEBUG = 7; // Debug: debug messages
+
+ /**
+ * @var array of priorities where the keys are the
+ * priority numbers and the values are the priority names
+ */
+ protected $_priorities = array();
+
+ /**
+ * @var array of Zend_Log_Writer_Abstract
+ */
+ protected $_writers = array();
+
+ /**
+ * @var array of Zend_Log_Filter_Interface
+ */
+ protected $_filters = array();
+
+ /**
+ * @var array of extra log event
+ */
+ protected $_extras = array();
+
+ /**
+ *
+ * @var string
+ */
+ protected $_defaultWriterNamespace = 'Zend_Log_Writer';
+
+ /**
+ *
+ * @var string
+ */
+ protected $_defaultFilterNamespace = 'Zend_Log_Filter';
+
+ /**
+ *
+ * @var string
+ */
+ protected $_defaultFormatterNamespace = 'Zend_Log_Formatter';
+
+ /**
+ *
+ * @var callback
+ */
+ protected $_origErrorHandler = null;
+
+ /**
+ *
+ * @var boolean
+ */
+ protected $_registeredErrorHandler = false;
+
+ /**
+ *
+ * @var array|boolean
+ */
+ protected $_errorHandlerMap = false;
+
+ /**
+ *
+ * @var string
+ */
+ protected $_timestampFormat = 'c';
+
+ /**
+ * Class constructor. Create a new logger
+ *
+ * @param Zend_Log_Writer_Abstract|null $writer default writer
+ */
+ public function __construct(Zend_Log_Writer_Abstract $writer = null)
+ {
+ $r = new ReflectionClass($this);
+ $this->_priorities = array_flip($r->getConstants());
+
+ if ($writer !== null) {
+ $this->addWriter($writer);
+ }
+ }
+
+ /**
+ * Factory to construct the logger and one or more writers
+ * based on the configuration array
+ *
+ * @param array|Zend_Config Array or instance of Zend_Config
+ * @return Zend_Log
+ * @throws Zend_Log_Exception
+ */
+ static public function factory($config = array())
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ }
+
+ if (!is_array($config) || empty($config)) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Configuration must be an array or instance of Zend_Config');
+ }
+
+ if (array_key_exists('className', $config)) {
+ $class = $config['className'];
+ unset($config['className']);
+ } else {
+ $class = __CLASS__;
+ }
+
+ $log = new $class;
+
+ if (!$log instanceof Zend_Log) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Passed className does not belong to a descendant of Zend_Log');
+ }
+
+ if (array_key_exists('timestampFormat', $config)) {
+ if (null != $config['timestampFormat'] && '' != $config['timestampFormat']) {
+ $log->setTimestampFormat($config['timestampFormat']);
+ }
+ unset($config['timestampFormat']);
+ }
+
+ if (!is_array(current($config))) {
+ $log->addWriter(current($config));
+ } else {
+ foreach($config as $writer) {
+ $log->addWriter($writer);
+ }
+ }
+
+ return $log;
+ }
+
+
+ /**
+ * Construct a writer object based on a configuration array
+ *
+ * @param array $config config array with writer spec
+ * @return Zend_Log_Writer_Abstract
+ * @throws Zend_Log_Exception
+ */
+ protected function _constructWriterFromConfig($config)
+ {
+ $writer = $this->_constructFromConfig('writer', $config, $this->_defaultWriterNamespace);
+
+ if (!$writer instanceof Zend_Log_Writer_Abstract) {
+ $writerName = is_object($writer)
+ ? get_class($writer)
+ : 'The specified writer';
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception("{$writerName} does not extend Zend_Log_Writer_Abstract!");
+ }
+
+ if (isset($config['filterName'])) {
+ $filter = $this->_constructFilterFromConfig($config);
+ $writer->addFilter($filter);
+ }
+
+ if (isset($config['formatterName'])) {
+ $formatter = $this->_constructFormatterFromConfig($config);
+ $writer->setFormatter($formatter);
+ }
+
+ return $writer;
+ }
+
+ /**
+ * Construct filter object from configuration array or Zend_Config object
+ *
+ * @param array|Zend_Config $config Zend_Config or Array
+ * @return Zend_Log_Filter_Interface
+ * @throws Zend_Log_Exception
+ */
+ protected function _constructFilterFromConfig($config)
+ {
+ $filter = $this->_constructFromConfig('filter', $config, $this->_defaultFilterNamespace);
+
+ if (!$filter instanceof Zend_Log_Filter_Interface) {
+ $filterName = is_object($filter)
+ ? get_class($filter)
+ : 'The specified filter';
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception("{$filterName} does not implement Zend_Log_Filter_Interface");
+ }
+
+ return $filter;
+ }
+
+ /**
+ * Construct formatter object from configuration array or Zend_Config object
+ *
+ * @param array|Zend_Config $config Zend_Config or Array
+ * @return Zend_Log_Formatter_Interface
+ * @throws Zend_Log_Exception
+ */
+ protected function _constructFormatterFromConfig($config)
+ {
+ $formatter = $this->_constructFromConfig('formatter', $config, $this->_defaultFormatterNamespace);
+
+ if (!$formatter instanceof Zend_Log_Formatter_Interface) {
+ $formatterName = is_object($formatter)
+ ? get_class($formatter)
+ : 'The specified formatter';
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception($formatterName . ' does not implement Zend_Log_Formatter_Interface');
+ }
+
+ return $formatter;
+ }
+
+ /**
+ * Construct a filter or writer from config
+ *
+ * @param string $type 'writer' of 'filter'
+ * @param mixed $config Zend_Config or Array
+ * @param string $namespace
+ * @return object
+ * @throws Zend_Log_Exception
+ */
+ protected function _constructFromConfig($type, $config, $namespace)
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ }
+
+ if (!is_array($config) || empty($config)) {
+ throw new Zend_Log_Exception(
+ 'Configuration must be an array or instance of Zend_Config'
+ );
+ }
+
+ $params = isset($config[ $type .'Params' ]) ? $config[ $type .'Params' ] : array();
+ $className = $this->getClassName($config, $type, $namespace);
+ if (!class_exists($className)) {
+ Zend_Loader::loadClass($className);
+ }
+
+ $reflection = new ReflectionClass($className);
+ if (!$reflection->implementsInterface('Zend_Log_FactoryInterface')) {
+ throw new Zend_Log_Exception(
+ $className . ' does not implement Zend_Log_FactoryInterface and can not be constructed from config.'
+ );
+ }
+
+ return call_user_func(array($className, 'factory'), $params);
+ }
+
+ /**
+ * Get the writer or filter full classname
+ *
+ * @param array $config
+ * @param string $type filter|writer
+ * @param string $defaultNamespace
+ * @return string full classname
+ * @throws Zend_Log_Exception
+ */
+ protected function getClassName($config, $type, $defaultNamespace)
+ {
+ if (!isset($config[$type . 'Name'])) {
+ throw new Zend_Log_Exception("Specify {$type}Name in the configuration array");
+ }
+
+ $className = $config[$type . 'Name'];
+ $namespace = $defaultNamespace;
+
+ if (isset($config[$type . 'Namespace'])) {
+ $namespace = $config[$type . 'Namespace'];
+ }
+
+ // PHP >= 5.3.0 namespace given?
+ if (substr($namespace, -1) == '\\') {
+ return $namespace . $className;
+ }
+
+ // empty namespace given?
+ if (strlen($namespace) === 0) {
+ return $className;
+ }
+
+ return $namespace . '_' . $className;
+ }
+
+ /**
+ * Packs message and priority into Event array
+ *
+ * @param string $message Message to log
+ * @param integer $priority Priority of message
+ * @return array Event array
+ */
+ protected function _packEvent($message, $priority)
+ {
+ return array_merge(array(
+ 'timestamp' => date($this->_timestampFormat),
+ 'message' => $message,
+ 'priority' => $priority,
+ 'priorityName' => $this->_priorities[$priority]
+ ),
+ $this->_extras
+ );
+ }
+
+ /**
+ * Class destructor. Shutdown log writers
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ /** @var Zend_Log_Writer_Abstract $writer */
+ foreach($this->_writers as $writer) {
+ $writer->shutdown();
+ }
+ }
+
+ /**
+ * Undefined method handler allows a shortcut:
+ * $log->priorityName('message')
+ * instead of
+ * $log->log('message', Zend_Log::PRIORITY_NAME)
+ *
+ * @param string $method priority name
+ * @param string $params message to log
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function __call($method, $params)
+ {
+ $priority = strtoupper($method);
+ if (($priority = array_search($priority, $this->_priorities)) !== false) {
+ switch (count($params)) {
+ case 0:
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Missing log message');
+ case 1:
+ $message = array_shift($params);
+ $extras = null;
+ break;
+ default:
+ $message = array_shift($params);
+ $extras = array_shift($params);
+ break;
+ }
+ $this->log($message, $priority, $extras);
+ } else {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Bad log priority');
+ }
+ }
+
+ /**
+ * Log a message at a priority
+ *
+ * @param string $message Message to log
+ * @param integer $priority Priority of message
+ * @param mixed $extras Extra information to log in event
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function log($message, $priority, $extras = null)
+ {
+ // sanity checks
+ if (empty($this->_writers)) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('No writers were added');
+ }
+
+ if (! isset($this->_priorities[$priority])) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Bad log priority');
+ }
+
+ // pack into event required by filters and writers
+ $event = $this->_packEvent($message, $priority);
+
+ // Check to see if any extra information was passed
+ if (!empty($extras)) {
+ $info = array();
+ if (is_array($extras)) {
+ foreach ($extras as $key => $value) {
+ if (is_string($key)) {
+ $event[$key] = $value;
+ } else {
+ $info[] = $value;
+ }
+ }
+ } else {
+ $info = $extras;
+ }
+ if (!empty($info)) {
+ $event['info'] = $info;
+ }
+ }
+
+ // abort if rejected by the global filters
+ /** @var Zend_Log_Filter_Interface $filter */
+ foreach ($this->_filters as $filter) {
+ if (! $filter->accept($event)) {
+ return;
+ }
+ }
+
+ // send to each writer
+ /** @var Zend_Log_Writer_Abstract $writer */
+ foreach ($this->_writers as $writer) {
+ $writer->write($event);
+ }
+ }
+
+ /**
+ * Add a custom priority
+ *
+ * @param string $name Name of priority
+ * @param integer $priority Numeric priority
+ * @return $this
+ * @throws Zend_Log_Exception
+ */
+ public function addPriority($name, $priority)
+ {
+ // Priority names must be uppercase for predictability.
+ $name = strtoupper($name);
+
+ if (isset($this->_priorities[$priority])
+ || false !== array_search($name, $this->_priorities)) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Existing priorities cannot be overwritten');
+ }
+
+ $this->_priorities[$priority] = $name;
+ return $this;
+ }
+
+ /**
+ * Add a filter that will be applied before all log writers.
+ * Before a message will be received by any of the writers, it
+ * must be accepted by all filters added with this method.
+ *
+ * @param int|Zend_Config|array|Zend_Log_Filter_Interface $filter
+ * @return $this
+ * @throws Zend_Log_Exception
+ */
+ public function addFilter($filter)
+ {
+ if (is_int($filter)) {
+ /** @see Zend_Log_Filter_Priority */
+ $filter = new Zend_Log_Filter_Priority($filter);
+
+ } elseif ($filter instanceof Zend_Config || is_array($filter)) {
+ $filter = $this->_constructFilterFromConfig($filter);
+
+ } elseif(! $filter instanceof Zend_Log_Filter_Interface) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Invalid filter provided');
+ }
+
+ $this->_filters[] = $filter;
+ return $this;
+ }
+
+ /**
+ * Add a writer. A writer is responsible for taking a log
+ * message and writing it out to storage.
+ *
+ * @param mixed $writer Zend_Log_Writer_Abstract or Config array
+ * @return Zend_Log
+ * @throws Zend_Log_Exception
+ */
+ public function addWriter($writer)
+ {
+ if (is_array($writer) || $writer instanceof Zend_Config) {
+ $writer = $this->_constructWriterFromConfig($writer);
+ }
+
+ if (!$writer instanceof Zend_Log_Writer_Abstract) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception(
+ 'Writer must be an instance of Zend_Log_Writer_Abstract'
+ . ' or you should pass a configuration array'
+ );
+ }
+
+ $this->_writers[] = $writer;
+ return $this;
+ }
+
+ /**
+ * Set an extra item to pass to the log writers.
+ *
+ * @param string $name Name of the field
+ * @param string $value Value of the field
+ * @return Zend_Log
+ */
+ public function setEventItem($name, $value)
+ {
+ $this->_extras = array_merge($this->_extras, array($name => $value));
+ return $this;
+ }
+
+ /**
+ * Register Logging system as an error handler to log php errors
+ * Note: it still calls the original error handler if set_error_handler is able to return it.
+ *
+ * Errors will be mapped as:
+ * E_NOTICE, E_USER_NOTICE => NOTICE
+ * E_WARNING, E_CORE_WARNING, E_USER_WARNING => WARN
+ * E_ERROR, E_USER_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR => ERR
+ * E_DEPRECATED, E_STRICT, E_USER_DEPRECATED => DEBUG
+ * (unknown/other) => INFO
+ *
+ * @link http://www.php.net/manual/en/function.set-error-handler.php Custom error handler
+ *
+ * @return Zend_Log
+ */
+ public function registerErrorHandler()
+ {
+ // Only register once. Avoids loop issues if it gets registered twice.
+ if ($this->_registeredErrorHandler) {
+ return $this;
+ }
+
+ $this->_origErrorHandler = set_error_handler(array($this, 'errorHandler'));
+
+ // Contruct a default map of phpErrors to Zend_Log priorities.
+ // Some of the errors are uncatchable, but are included for completeness
+ $this->_errorHandlerMap = array(
+ E_NOTICE => Zend_Log::NOTICE,
+ E_USER_NOTICE => Zend_Log::NOTICE,
+ E_WARNING => Zend_Log::WARN,
+ E_CORE_WARNING => Zend_Log::WARN,
+ E_USER_WARNING => Zend_Log::WARN,
+ E_ERROR => Zend_Log::ERR,
+ E_USER_ERROR => Zend_Log::ERR,
+ E_CORE_ERROR => Zend_Log::ERR,
+ E_RECOVERABLE_ERROR => Zend_Log::ERR,
+ E_STRICT => Zend_Log::DEBUG,
+ );
+ // PHP 5.3.0+
+ if (defined('E_DEPRECATED')) {
+ $this->_errorHandlerMap['E_DEPRECATED'] = Zend_Log::DEBUG;
+ }
+ if (defined('E_USER_DEPRECATED')) {
+ $this->_errorHandlerMap['E_USER_DEPRECATED'] = Zend_Log::DEBUG;
+ }
+
+ $this->_registeredErrorHandler = true;
+ return $this;
+ }
+
+ /**
+ * Error Handler will convert error into log message, and then call the original error handler
+ *
+ * @link http://www.php.net/manual/en/function.set-error-handler.php Custom error handler
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @param array $errcontext
+ * @return boolean
+ */
+ public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext)
+ {
+ $errorLevel = error_reporting();
+
+ if ($errorLevel & $errno) {
+ if (isset($this->_errorHandlerMap[$errno])) {
+ $priority = $this->_errorHandlerMap[$errno];
+ } else {
+ $priority = Zend_Log::INFO;
+ }
+ $this->log($errstr, $priority, array('errno'=>$errno, 'file'=>$errfile, 'line'=>$errline, 'context'=>$errcontext));
+ }
+
+ if ($this->_origErrorHandler !== null) {
+ return call_user_func($this->_origErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext);
+ }
+ return false;
+ }
+
+ /**
+ * Set timestamp format for log entries.
+ *
+ * @param string $format
+ * @return Zend_Log
+ */
+ public function setTimestampFormat($format)
+ {
+ $this->_timestampFormat = $format;
+ return $this;
+ }
+
+ /**
+ * Get timestamp format used for log entries.
+ *
+ * @return string
+ */
+ public function getTimestampFormat()
+ {
+ return $this->_timestampFormat;
+ }
+}
diff --git a/library/vendor/Zend/Log/Exception.php b/library/vendor/Zend/Log/Exception.php
new file mode 100644
index 0000000..84c7633
--- /dev/null
+++ b/library/vendor/Zend/Log/Exception.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Exception */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Exception extends Zend_Exception
+{}
diff --git a/library/vendor/Zend/Log/FactoryInterface.php b/library/vendor/Zend/Log/FactoryInterface.php
new file mode 100644
index 0000000..eb67e2c
--- /dev/null
+++ b/library/vendor/Zend/Log/FactoryInterface.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+interface Zend_Log_FactoryInterface
+{
+ /**
+ * Construct a Zend_Log driver
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_FactoryInterface
+ */
+ static public function factory($config);
+}
diff --git a/library/vendor/Zend/Log/Filter/Abstract.php b/library/vendor/Zend/Log/Filter/Abstract.php
new file mode 100644
index 0000000..53cd40c
--- /dev/null
+++ b/library/vendor/Zend/Log/Filter/Abstract.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** @see Zend_Log_Filter_Interface */
+
+/** @see Zend_Log_FactoryInterface */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Log_Filter_Abstract
+ implements Zend_Log_Filter_Interface, Zend_Log_FactoryInterface
+{
+ /**
+ * Validate and optionally convert the config to array
+ *
+ * @param array|Zend_Config $config Zend_Config or Array
+ * @return array
+ * @throws Zend_Log_Exception
+ */
+ static protected function _parseConfig($config)
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ }
+
+ if (!is_array($config)) {
+ throw new Zend_Log_Exception('Configuration must be an array or instance of Zend_Config');
+ }
+
+ return $config;
+ }
+}
diff --git a/library/vendor/Zend/Log/Filter/Interface.php b/library/vendor/Zend/Log/Filter/Interface.php
new file mode 100644
index 0000000..dc0cbad
--- /dev/null
+++ b/library/vendor/Zend/Log/Filter/Interface.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+interface Zend_Log_Filter_Interface
+{
+ /**
+ * Returns TRUE to accept the message, FALSE to block it.
+ *
+ * @param array $event event data
+ * @return boolean accepted?
+ */
+ public function accept($event);
+}
diff --git a/library/vendor/Zend/Log/Filter/Message.php b/library/vendor/Zend/Log/Filter/Message.php
new file mode 100644
index 0000000..3b98f92
--- /dev/null
+++ b/library/vendor/Zend/Log/Filter/Message.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Filter_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Filter_Message extends Zend_Log_Filter_Abstract
+{
+ /**
+ * @var string
+ */
+ protected $_regexp;
+
+ /**
+ * Filter out any log messages not matching $regexp.
+ *
+ * @param string $regexp Regular expression to test the log message
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function __construct($regexp)
+ {
+ if (@preg_match($regexp, '') === false) {
+ throw new Zend_Log_Exception("Invalid regular expression '$regexp'");
+ }
+ $this->_regexp = $regexp;
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Filter_Message
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Filter_Message
+ */
+ static public function factory($config)
+ {
+ $config = self::_parseConfig($config);
+ $config = array_merge(array(
+ 'regexp' => null
+ ), $config);
+
+ return new self(
+ $config['regexp']
+ );
+ }
+
+ /**
+ * Returns TRUE to accept the message, FALSE to block it.
+ *
+ * @param array $event event data
+ * @return boolean accepted?
+ */
+ public function accept($event)
+ {
+ return preg_match($this->_regexp, $event['message']) > 0;
+ }
+}
diff --git a/library/vendor/Zend/Log/Filter/Priority.php b/library/vendor/Zend/Log/Filter/Priority.php
new file mode 100644
index 0000000..f1dbd93
--- /dev/null
+++ b/library/vendor/Zend/Log/Filter/Priority.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Filter_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Filter_Priority extends Zend_Log_Filter_Abstract
+{
+ /**
+ * @var integer
+ */
+ protected $_priority;
+
+ /**
+ * @var string
+ */
+ protected $_operator;
+
+ /**
+ * Filter logging by $priority. By default, it will accept any log
+ * event whose priority value is less than or equal to $priority.
+ *
+ * @param integer $priority Priority
+ * @param string $operator Comparison operator
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function __construct($priority, $operator = null)
+ {
+ if (! is_int($priority)) {
+ throw new Zend_Log_Exception('Priority must be an integer');
+ }
+
+ $this->_priority = $priority;
+ $this->_operator = $operator === null ? '<=' : $operator;
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Filter_Priority
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Filter_Priority
+ */
+ static public function factory($config)
+ {
+ $config = self::_parseConfig($config);
+ $config = array_merge(array(
+ 'priority' => null,
+ 'operator' => null,
+ ), $config);
+
+ // Add support for constants
+ if (!is_numeric($config['priority']) && isset($config['priority']) && defined($config['priority'])) {
+ $config['priority'] = constant($config['priority']);
+ }
+
+ return new self(
+ (int) $config['priority'],
+ $config['operator']
+ );
+ }
+
+ /**
+ * Returns TRUE to accept the message, FALSE to block it.
+ *
+ * @param array $event event data
+ * @return boolean accepted?
+ */
+ public function accept($event)
+ {
+ return version_compare($event['priority'], $this->_priority, $this->_operator);
+ }
+}
diff --git a/library/vendor/Zend/Log/Filter/Suppress.php b/library/vendor/Zend/Log/Filter/Suppress.php
new file mode 100644
index 0000000..3766077
--- /dev/null
+++ b/library/vendor/Zend/Log/Filter/Suppress.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Filter_Interface */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Filter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Filter_Suppress extends Zend_Log_Filter_Abstract
+{
+ /**
+ * @var boolean
+ */
+ protected $_accept = true;
+
+ /**
+ * This is a simple boolean filter.
+ *
+ * Call suppress(true) to suppress all log events.
+ * Call suppress(false) to accept all log events.
+ *
+ * @param boolean $suppress Should all log events be suppressed?
+ * @return void
+ */
+ public function suppress($suppress)
+ {
+ $this->_accept = (! $suppress);
+ }
+
+ /**
+ * Returns TRUE to accept the message, FALSE to block it.
+ *
+ * @param array $event event data
+ * @return boolean accepted?
+ */
+ public function accept($event)
+ {
+ return $this->_accept;
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Filter_Suppress
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Filter_Suppress
+ * @throws Zend_Log_Exception
+ */
+ static public function factory($config)
+ {
+ return new self();
+ }
+}
diff --git a/library/vendor/Zend/Log/Formatter/Abstract.php b/library/vendor/Zend/Log/Formatter/Abstract.php
new file mode 100644
index 0000000..6fa402a
--- /dev/null
+++ b/library/vendor/Zend/Log/Formatter/Abstract.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** @see Zend_Log_Formatter_Interface */
+
+/** @see Zend_Log_FactoryInterface */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Log_Formatter_Abstract
+ implements Zend_Log_Formatter_Interface, Zend_Log_FactoryInterface
+{
+}
diff --git a/library/vendor/Zend/Log/Formatter/Interface.php b/library/vendor/Zend/Log/Formatter/Interface.php
new file mode 100644
index 0000000..9b21fa7
--- /dev/null
+++ b/library/vendor/Zend/Log/Formatter/Interface.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+interface Zend_Log_Formatter_Interface
+{
+ /**
+ * Formats data into a single line to be written by the writer.
+ *
+ * @param array $event event data
+ * @return string formatted line to write to the log
+ */
+ public function format($event);
+
+}
diff --git a/library/vendor/Zend/Log/Formatter/Simple.php b/library/vendor/Zend/Log/Formatter/Simple.php
new file mode 100644
index 0000000..b2c2376
--- /dev/null
+++ b/library/vendor/Zend/Log/Formatter/Simple.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Formatter_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Formatter_Simple extends Zend_Log_Formatter_Abstract
+{
+ /**
+ * @var string
+ */
+ protected $_format;
+
+ const DEFAULT_FORMAT = '%timestamp% %priorityName% (%priority%): %message%';
+
+ /**
+ * Class constructor
+ *
+ * @param null|string $format Format specifier for log messages
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function __construct($format = null)
+ {
+ if ($format === null) {
+ $format = self::DEFAULT_FORMAT . PHP_EOL;
+ }
+
+ if (!is_string($format)) {
+ throw new Zend_Log_Exception('Format must be a string');
+ }
+
+ $this->_format = $format;
+ }
+
+ /**
+ * Factory for Zend_Log_Formatter_Simple classe
+ *
+ * @param array|Zend_Config $options
+ * @return Zend_Log_Formatter_Simple
+ */
+ public static function factory($options)
+ {
+ $format = null;
+ if (null !== $options) {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (array_key_exists('format', $options)) {
+ $format = $options['format'];
+ }
+ }
+
+ return new self($format);
+ }
+
+ /**
+ * Formats data into a single line to be written by the writer.
+ *
+ * @param array $event event data
+ * @return string formatted line to write to the log
+ */
+ public function format($event)
+ {
+ $output = $this->_format;
+
+ foreach ($event as $name => $value) {
+ if ((is_object($value) && !method_exists($value,'__toString'))
+ || is_array($value)
+ ) {
+ $value = gettype($value);
+ }
+
+ $output = str_replace("%$name%", $value, $output);
+ }
+
+ return $output;
+ }
+}
diff --git a/library/vendor/Zend/Log/Formatter/Xml.php b/library/vendor/Zend/Log/Formatter/Xml.php
new file mode 100644
index 0000000..21fddb6
--- /dev/null
+++ b/library/vendor/Zend/Log/Formatter/Xml.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Formatter_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Formatter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Formatter_Xml extends Zend_Log_Formatter_Abstract
+{
+ /**
+ * @var string Name of root element
+ */
+ protected $_rootElement;
+
+ /**
+ * @var array Relates XML elements to log data field keys.
+ */
+ protected $_elementMap;
+
+ /**
+ * @var string Encoding to use in XML
+ */
+ protected $_encoding;
+
+ /**
+ * Class constructor
+ * (the default encoding is UTF-8)
+ *
+ * @param array|Zend_Config $options
+ * @return void
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (!is_array($options)) {
+ $args = func_get_args();
+
+ $options = array(
+ 'rootElement' => array_shift($args)
+ );
+
+ if (count($args)) {
+ $options['elementMap'] = array_shift($args);
+ }
+
+ if (count($args)) {
+ $options['encoding'] = array_shift($args);
+ }
+ }
+
+ if (!array_key_exists('rootElement', $options)) {
+ $options['rootElement'] = 'logEntry';
+ }
+
+ if (!array_key_exists('encoding', $options)) {
+ $options['encoding'] = 'UTF-8';
+ }
+
+ $this->_rootElement = $options['rootElement'];
+ $this->setEncoding($options['encoding']);
+
+ if (array_key_exists('elementMap', $options)) {
+ $this->_elementMap = $options['elementMap'];
+ }
+ }
+
+ /**
+ * Factory for Zend_Log_Formatter_Xml classe
+ *
+ * @param array|Zend_Config $options
+ * @return Zend_Log_Formatter_Xml
+ */
+ public static function factory($options)
+ {
+ return new self($options);
+ }
+
+ /**
+ * Get encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Set encoding
+ *
+ * @param string $value
+ * @return Zend_Log_Formatter_Xml
+ */
+ public function setEncoding($value)
+ {
+ $this->_encoding = (string) $value;
+ return $this;
+ }
+
+ /**
+ * Formats data into a single line to be written by the writer.
+ *
+ * @param array $event event data
+ * @return string formatted line to write to the log
+ */
+ public function format($event)
+ {
+ if ($this->_elementMap === null) {
+ $dataToInsert = $event;
+ } else {
+ $dataToInsert = array();
+ foreach ($this->_elementMap as $elementName => $fieldKey) {
+ $dataToInsert[$elementName] = $event[$fieldKey];
+ }
+ }
+
+ $enc = $this->getEncoding();
+ $dom = new DOMDocument('1.0', $enc);
+ $elt = $dom->appendChild(new DOMElement($this->_rootElement));
+
+ foreach ($dataToInsert as $key => $value) {
+ if (empty($value)
+ || is_scalar($value)
+ || (is_object($value) && method_exists($value,'__toString'))
+ ) {
+ if($key == "message") {
+ $value = htmlspecialchars($value, ENT_COMPAT, $enc);
+ }
+ $elt->appendChild(new DOMElement($key, (string)$value));
+ }
+ }
+
+ $xml = $dom->saveXML();
+ $xml = preg_replace('/<\?xml version="1.0"( encoding="[^\"]*")?\?>\n/u', '', $xml);
+
+ return $xml . PHP_EOL;
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Abstract.php b/library/vendor/Zend/Log/Writer/Abstract.php
new file mode 100644
index 0000000..c39be13
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Abstract.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Filter_Priority */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Log_Writer_Abstract implements Zend_Log_FactoryInterface
+{
+ /**
+ * @var array of Zend_Log_Filter_Interface
+ */
+ protected $_filters = array();
+
+ /**
+ * Formats the log message before writing.
+ *
+ * @var Zend_Log_Formatter_Interface
+ */
+ protected $_formatter;
+
+ /**
+ * Add a filter specific to this writer.
+ *
+ * @param Zend_Log_Filter_Interface|int $filter Filter class or filter
+ * priority
+ * @return Zend_Log_Writer_Abstract
+ * @throws Zend_Log_Exception
+ */
+ public function addFilter($filter)
+ {
+ if (is_int($filter)) {
+ $filter = new Zend_Log_Filter_Priority($filter);
+ }
+
+ if (!$filter instanceof Zend_Log_Filter_Interface) {
+ /** @see Zend_Log_Exception */
+ throw new Zend_Log_Exception('Invalid filter provided');
+ }
+
+ $this->_filters[] = $filter;
+ return $this;
+ }
+
+ /**
+ * Log a message to this writer.
+ *
+ * @param array $event log data event
+ * @return void
+ */
+ public function write($event)
+ {
+ /** @var Zend_Log_Filter_Interface $filter */
+ foreach ($this->_filters as $filter) {
+ if (!$filter->accept($event)) {
+ return;
+ }
+ }
+
+ // exception occurs on error
+ $this->_write($event);
+ }
+
+ /**
+ * Set a new formatter for this writer
+ *
+ * @param Zend_Log_Formatter_Interface $formatter
+ * @return Zend_Log_Writer_Abstract
+ */
+ public function setFormatter(Zend_Log_Formatter_Interface $formatter)
+ {
+ $this->_formatter = $formatter;
+ return $this;
+ }
+
+ /**
+ * Perform shutdown activites such as closing open resources
+ *
+ * @return void
+ */
+ public function shutdown()
+ {}
+
+ /**
+ * Write a message to the log.
+ *
+ * @param array $event log data event
+ * @return void
+ */
+ abstract protected function _write($event);
+
+ /**
+ * Validate and optionally convert the config to array
+ *
+ * @param array|Zend_Config $config Zend_Config or Array
+ * @return array
+ * @throws Zend_Log_Exception
+ */
+ static protected function _parseConfig($config)
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ }
+
+ if (!is_array($config)) {
+ throw new Zend_Log_Exception(
+ 'Configuration must be an array or instance of Zend_Config'
+ );
+ }
+
+ return $config;
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Db.php b/library/vendor/Zend/Log/Writer/Db.php
new file mode 100644
index 0000000..1c61c12
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Db.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Writer_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Writer_Db extends Zend_Log_Writer_Abstract
+{
+ /**
+ * Database adapter instance
+ *
+ * @var Zend_Db_Adapter
+ */
+ protected $_db;
+
+ /**
+ * Name of the log table in the database
+ *
+ * @var string
+ */
+ protected $_table;
+
+ /**
+ * Relates database columns names to log data field keys.
+ *
+ * @var null|array
+ */
+ protected $_columnMap;
+
+ /**
+ * Class constructor
+ *
+ * @param Zend_Db_Adapter $db Database adapter instance
+ * @param string $table Log table in database
+ * @param array $columnMap
+ * @return void
+ */
+ public function __construct($db, $table, $columnMap = null)
+ {
+ $this->_db = $db;
+ $this->_table = $table;
+ $this->_columnMap = $columnMap;
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_Db
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_Db
+ */
+ static public function factory($config)
+ {
+ $config = self::_parseConfig($config);
+ $config = array_merge(array(
+ 'db' => null,
+ 'table' => null,
+ 'columnMap' => null,
+ ), $config);
+
+ if (isset($config['columnmap'])) {
+ $config['columnMap'] = $config['columnmap'];
+ }
+
+ return new self(
+ $config['db'],
+ $config['table'],
+ $config['columnMap']
+ );
+ }
+
+ /**
+ * Formatting is not possible on this writer
+ *
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function setFormatter(Zend_Log_Formatter_Interface $formatter)
+ {
+ throw new Zend_Log_Exception(get_class($this) . ' does not support formatting');
+ }
+
+ /**
+ * Remove reference to database adapter
+ *
+ * @return void
+ */
+ public function shutdown()
+ {
+ $this->_db = null;
+ }
+
+ /**
+ * Write a message to the log.
+ *
+ * @param array $event event data
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ protected function _write($event)
+ {
+ if ($this->_db === null) {
+ throw new Zend_Log_Exception('Database adapter is null');
+ }
+
+ if ($this->_columnMap === null) {
+ $dataToInsert = $event;
+ } else {
+ $dataToInsert = array();
+ foreach ($this->_columnMap as $columnName => $fieldKey) {
+ if (isset($event[$fieldKey])) {
+ $dataToInsert[$columnName] = $event[$fieldKey];
+ }
+ }
+ }
+
+ $this->_db->insert($this->_table, $dataToInsert);
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Mail.php b/library/vendor/Zend/Log/Writer/Mail.php
new file mode 100644
index 0000000..571d893
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Mail.php
@@ -0,0 +1,426 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Writer_Abstract */
+
+/** Zend_Log_Exception */
+
+/** Zend_Log_Formatter_Simple*/
+
+/**
+ * Class used for writing log messages to email via Zend_Mail.
+ *
+ * Allows for emailing log messages at and above a certain level via a
+ * Zend_Mail object. Note that this class only sends the email upon
+ * completion, so any log entries accumulated are sent in a single email.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Writer_Mail extends Zend_Log_Writer_Abstract
+{
+ /**
+ * Array of formatted events to include in message body.
+ *
+ * @var array
+ */
+ protected $_eventsToMail = array();
+
+ /**
+ * Array of formatted lines for use in an HTML email body; these events
+ * are formatted with an optional formatter if the caller is using
+ * Zend_Layout.
+ *
+ * @var array
+ */
+ protected $_layoutEventsToMail = array();
+
+ /**
+ * Zend_Mail instance to use
+ *
+ * @var Zend_Mail
+ */
+ protected $_mail;
+
+ /**
+ * Zend_Layout instance to use; optional.
+ *
+ * @var Zend_Layout
+ */
+ protected $_layout;
+
+ /**
+ * Optional formatter for use when rendering with Zend_Layout.
+ *
+ * @var Zend_Log_Formatter_Interface
+ */
+ protected $_layoutFormatter;
+
+ /**
+ * Array keeping track of the number of entries per priority level.
+ *
+ * @var array
+ */
+ protected $_numEntriesPerPriority = array();
+
+ /**
+ * Subject prepend text.
+ *
+ * Can only be used of the Zend_Mail object has not already had its
+ * subject line set. Using this will cause the subject to have the entry
+ * counts per-priority level appended to it.
+ *
+ * @var string|null
+ */
+ protected $_subjectPrependText;
+
+ /**
+ * MethodMap for Zend_Mail's headers
+ *
+ * @var array
+ */
+ protected static $_methodMapHeaders = array(
+ 'from' => 'setFrom',
+ 'to' => 'addTo',
+ 'cc' => 'addCc',
+ 'bcc' => 'addBcc',
+ );
+
+ /**
+ * Class constructor.
+ *
+ * Constructs the mail writer; requires a Zend_Mail instance, and takes an
+ * optional Zend_Layout instance. If Zend_Layout is being used,
+ * $this->_layout->events will be set for use in the layout template.
+ *
+ * @param Zend_Mail $mail Mail instance
+ * @param Zend_Layout $layout Layout instance; optional
+ * @return void
+ */
+ public function __construct(Zend_Mail $mail, Zend_Layout $layout = null)
+ {
+ $this->_mail = $mail;
+ if (null !== $layout) {
+ $this->setLayout($layout);
+ }
+ $this->_formatter = new Zend_Log_Formatter_Simple();
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_Mail
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_Mail
+ */
+ static public function factory($config)
+ {
+ $config = self::_parseConfig($config);
+ $mail = self::_constructMailFromConfig($config);
+ $writer = new self($mail);
+
+ if (isset($config['layout']) || isset($config['layoutOptions'])) {
+ $writer->setLayout($config);
+ }
+ if (isset($config['layoutFormatter'])) {
+ $layoutFormatter = new $config['layoutFormatter'];
+ $writer->setLayoutFormatter($layoutFormatter);
+ }
+ if (isset($config['subjectPrependText'])) {
+ $writer->setSubjectPrependText($config['subjectPrependText']);
+ }
+
+ return $writer;
+ }
+
+ /**
+ * Set the layout
+ *
+ * @param Zend_Layout|array $layout
+ * @return Zend_Log_Writer_Mail
+ * @throws Zend_Log_Exception
+ */
+ public function setLayout($layout)
+ {
+ if (is_array($layout)) {
+ $layout = $this->_constructLayoutFromConfig($layout);
+ }
+
+ if (!$layout instanceof Zend_Layout) {
+ throw new Zend_Log_Exception('Mail must be an instance of Zend_Layout or an array');
+ }
+ $this->_layout = $layout;
+
+ return $this;
+ }
+
+ /**
+ * Construct a Zend_Mail instance based on a configuration array
+ *
+ * @param array $config
+ * @return Zend_Mail
+ * @throws Zend_Log_Exception
+ */
+ protected static function _constructMailFromConfig(array $config)
+ {
+ $mailClass = 'Zend_Mail';
+ if (isset($config['mail'])) {
+ $mailClass = $config['mail'];
+ }
+
+ if (!array_key_exists('charset', $config)) {
+ $config['charset'] = null;
+ }
+ $mail = new $mailClass($config['charset']);
+ if (!$mail instanceof Zend_Mail) {
+ throw new Zend_Log_Exception($mail . 'must extend Zend_Mail');
+ }
+
+ if (isset($config['subject'])) {
+ $mail->setSubject($config['subject']);
+ }
+
+ $headerAddresses = array_intersect_key($config, self::$_methodMapHeaders);
+ if (count($headerAddresses)) {
+ foreach ($headerAddresses as $header => $address) {
+ $method = self::$_methodMapHeaders[$header];
+ if (is_array($address) && isset($address['name'])
+ && !is_numeric($address['name'])
+ ) {
+ $params = array(
+ $address['email'],
+ $address['name']
+ );
+ } else if (is_array($address) && isset($address['email'])) {
+ $params = array($address['email']);
+ } else {
+ $params = array($address);
+ }
+ call_user_func_array(array($mail, $method), $params);
+ }
+ }
+
+ return $mail;
+ }
+
+ /**
+ * Construct a Zend_Layout instance based on a configuration array
+ *
+ * @param array $config
+ * @return Zend_Layout
+ * @throws Zend_Log_Exception
+ */
+ protected function _constructLayoutFromConfig(array $config)
+ {
+ $config = array_merge(array(
+ 'layout' => 'Zend_Layout',
+ 'layoutOptions' => null
+ ), $config);
+
+ $layoutClass = $config['layout'];
+ $layout = new $layoutClass($config['layoutOptions']);
+ if (!$layout instanceof Zend_Layout) {
+ throw new Zend_Log_Exception($layout . 'must extend Zend_Layout');
+ }
+
+ return $layout;
+ }
+
+ /**
+ * Places event line into array of lines to be used as message body.
+ *
+ * Handles the formatting of both plaintext entries, as well as those
+ * rendered with Zend_Layout.
+ *
+ * @param array $event Event data
+ * @return void
+ */
+ protected function _write($event)
+ {
+ // Track the number of entries per priority level.
+ if (!isset($this->_numEntriesPerPriority[$event['priorityName']])) {
+ $this->_numEntriesPerPriority[$event['priorityName']] = 1;
+ } else {
+ $this->_numEntriesPerPriority[$event['priorityName']]++;
+ }
+
+ $formattedEvent = $this->_formatter->format($event);
+
+ // All plaintext events are to use the standard formatter.
+ $this->_eventsToMail[] = $formattedEvent;
+
+ // If we have a Zend_Layout instance, use a specific formatter for the
+ // layout if one exists. Otherwise, just use the event with its
+ // default format.
+ if ($this->_layout) {
+ if ($this->_layoutFormatter) {
+ $this->_layoutEventsToMail[] =
+ $this->_layoutFormatter->format($event);
+ } else {
+ $this->_layoutEventsToMail[] = $formattedEvent;
+ }
+ }
+ }
+
+ /**
+ * Gets instance of Zend_Log_Formatter_Instance used for formatting a
+ * message using Zend_Layout, if applicable.
+ *
+ * @return Zend_Log_Formatter_Interface|null The formatter, or null.
+ */
+ public function getLayoutFormatter()
+ {
+ return $this->_layoutFormatter;
+ }
+
+ /**
+ * Sets a specific formatter for use with Zend_Layout events.
+ *
+ * Allows use of a second formatter on lines that will be rendered with
+ * Zend_Layout. In the event that Zend_Layout is not being used, this
+ * formatter cannot be set, so an exception will be thrown.
+ *
+ * @param Zend_Log_Formatter_Interface $formatter
+ * @return Zend_Log_Writer_Mail
+ * @throws Zend_Log_Exception
+ */
+ public function setLayoutFormatter(Zend_Log_Formatter_Interface $formatter)
+ {
+ if (!$this->_layout) {
+ throw new Zend_Log_Exception(
+ 'cannot set formatter for layout; ' .
+ 'a Zend_Layout instance is not in use');
+ }
+
+ $this->_layoutFormatter = $formatter;
+ return $this;
+ }
+
+ /**
+ * Allows caller to have the mail subject dynamically set to contain the
+ * entry counts per-priority level.
+ *
+ * Sets the text for use in the subject, with entry counts per-priority
+ * level appended to the end. Since a Zend_Mail subject can only be set
+ * once, this method cannot be used if the Zend_Mail object already has a
+ * subject set.
+ *
+ * @param string $subject Subject prepend text.
+ * @return Zend_Log_Writer_Mail
+ * @throws Zend_Log_Exception
+ */
+ public function setSubjectPrependText($subject)
+ {
+ if ($this->_mail->getSubject()) {
+ throw new Zend_Log_Exception(
+ 'subject already set on mail; ' .
+ 'cannot set subject prepend text');
+ }
+
+ $this->_subjectPrependText = (string) $subject;
+ return $this;
+ }
+
+ /**
+ * Sends mail to recipient(s) if log entries are present. Note that both
+ * plaintext and HTML portions of email are handled here.
+ *
+ * @return void
+ */
+ public function shutdown()
+ {
+ // If there are events to mail, use them as message body. Otherwise,
+ // there is no mail to be sent.
+ if (empty($this->_eventsToMail)) {
+ return;
+ }
+
+ if ($this->_subjectPrependText !== null) {
+ // Tack on the summary of entries per-priority to the subject
+ // line and set it on the Zend_Mail object.
+ $numEntries = $this->_getFormattedNumEntriesPerPriority();
+ $this->_mail->setSubject(
+ "{$this->_subjectPrependText} ({$numEntries})");
+ }
+
+
+ // Always provide events to mail as plaintext.
+ $this->_mail->setBodyText(implode('', $this->_eventsToMail));
+
+ // If a Zend_Layout instance is being used, set its "events"
+ // value to the lines formatted for use with the layout.
+ if ($this->_layout) {
+ // Set the required "messages" value for the layout. Here we
+ // are assuming that the layout is for use with HTML.
+ $this->_layout->events =
+ implode('', $this->_layoutEventsToMail);
+
+ // If an exception occurs during rendering, convert it to a notice
+ // so we can avoid an exception thrown without a stack frame.
+ try {
+ $this->_mail->setBodyHtml($this->_layout->render());
+ } catch (Exception $e) {
+ trigger_error(
+ "exception occurred when rendering layout; " .
+ "unable to set html body for message; " .
+ "message = {$e->getMessage()}; " .
+ "code = {$e->getCode()}; " .
+ "exception class = " . get_class($e),
+ E_USER_NOTICE);
+ }
+ }
+
+ // Finally, send the mail. If an exception occurs, convert it into a
+ // warning-level message so we can avoid an exception thrown without a
+ // stack frame.
+ try {
+ $this->_mail->send();
+ } catch (Exception $e) {
+ trigger_error(
+ "unable to send log entries via email; " .
+ "message = {$e->getMessage()}; " .
+ "code = {$e->getCode()}; " .
+ "exception class = " . get_class($e),
+ E_USER_WARNING);
+ }
+ }
+
+ /**
+ * Gets a string of number of entries per-priority level that occurred, or
+ * an emptry string if none occurred.
+ *
+ * @return string
+ */
+ protected function _getFormattedNumEntriesPerPriority()
+ {
+ $strings = array();
+
+ foreach ($this->_numEntriesPerPriority as $priority => $numEntries) {
+ $strings[] = "{$priority}={$numEntries}";
+ }
+
+ return implode(', ', $strings);
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Mock.php b/library/vendor/Zend/Log/Writer/Mock.php
new file mode 100644
index 0000000..8551123
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Mock.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Writer_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Writer_Mock extends Zend_Log_Writer_Abstract
+{
+ /**
+ * array of log events
+ *
+ * @var array
+ */
+ public $events = array();
+
+ /**
+ * shutdown called?
+ *
+ * @var boolean
+ */
+ public $shutdown = false;
+
+ /**
+ * Write a message to the log.
+ *
+ * @param array $event event data
+ * @return void
+ */
+ public function _write($event)
+ {
+ $this->events[] = $event;
+ }
+
+ /**
+ * Record shutdown
+ *
+ * @return void
+ */
+ public function shutdown()
+ {
+ $this->shutdown = true;
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_Mock
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_Mock
+ */
+ static public function factory($config)
+ {
+ return new self();
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Null.php b/library/vendor/Zend/Log/Writer/Null.php
new file mode 100644
index 0000000..2dc3103
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Null.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Writer_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Writer_Null extends Zend_Log_Writer_Abstract
+{
+ /**
+ * Write a message to the log.
+ *
+ * @param array $event event data
+ * @return void
+ */
+ protected function _write($event)
+ {
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_Null
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_Null
+ */
+ static public function factory($config)
+ {
+ return new self();
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Stream.php b/library/vendor/Zend/Log/Writer/Stream.php
new file mode 100644
index 0000000..95771e8
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Stream.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Writer_Abstract */
+
+/** Zend_Log_Formatter_Simple */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Writer_Stream extends Zend_Log_Writer_Abstract
+{
+ /**
+ * Holds the PHP stream to log to.
+ *
+ * @var null|stream
+ */
+ protected $_stream = null;
+
+ /**
+ * Class Constructor
+ *
+ * @param array|string|resource $streamOrUrl Stream or URL to open as a stream
+ * @param string|null $mode Mode, only applicable if a URL is given
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ public function __construct($streamOrUrl, $mode = null)
+ {
+ // Setting the default
+ if (null === $mode) {
+ $mode = 'a';
+ }
+
+ if (is_resource($streamOrUrl)) {
+ if (get_resource_type($streamOrUrl) != 'stream') {
+ throw new Zend_Log_Exception('Resource is not a stream');
+ }
+
+ if ($mode != 'a') {
+ throw new Zend_Log_Exception('Mode cannot be changed on existing streams');
+ }
+
+ $this->_stream = $streamOrUrl;
+ } else {
+ if (is_array($streamOrUrl) && isset($streamOrUrl['stream'])) {
+ $streamOrUrl = $streamOrUrl['stream'];
+ }
+
+ if (! $this->_stream = @fopen($streamOrUrl, $mode, false)) {
+ $msg = "\"$streamOrUrl\" cannot be opened with mode \"$mode\"";
+ throw new Zend_Log_Exception($msg);
+ }
+ }
+
+ $this->_formatter = new Zend_Log_Formatter_Simple();
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_Stream
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_Stream
+ */
+ static public function factory($config)
+ {
+ $config = self::_parseConfig($config);
+ $config = array_merge(array(
+ 'stream' => null,
+ 'mode' => null,
+ ), $config);
+
+ $streamOrUrl = isset($config['url']) ? $config['url'] : $config['stream'];
+
+ return new self(
+ $streamOrUrl,
+ $config['mode']
+ );
+ }
+
+ /**
+ * Close the stream resource.
+ *
+ * @return void
+ */
+ public function shutdown()
+ {
+ if (is_resource($this->_stream)) {
+ fclose($this->_stream);
+ }
+ }
+
+ /**
+ * Write a message to the log.
+ *
+ * @param array $event event data
+ * @return void
+ * @throws Zend_Log_Exception
+ */
+ protected function _write($event)
+ {
+ $line = $this->_formatter->format($event);
+
+ if (false === @fwrite($this->_stream, $line)) {
+ throw new Zend_Log_Exception("Unable to write to stream");
+ }
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/Syslog.php b/library/vendor/Zend/Log/Writer/Syslog.php
new file mode 100644
index 0000000..7a569d7
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/Syslog.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log */
+
+/** Zend_Log_Writer_Abstract */
+
+/**
+ * Writes log messages to syslog
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Log_Writer_Syslog extends Zend_Log_Writer_Abstract
+{
+ /**
+ * Maps Zend_Log priorities to PHP's syslog priorities
+ *
+ * @var array
+ */
+ protected $_priorities = array(
+ Zend_Log::EMERG => LOG_EMERG,
+ Zend_Log::ALERT => LOG_ALERT,
+ Zend_Log::CRIT => LOG_CRIT,
+ Zend_Log::ERR => LOG_ERR,
+ Zend_Log::WARN => LOG_WARNING,
+ Zend_Log::NOTICE => LOG_NOTICE,
+ Zend_Log::INFO => LOG_INFO,
+ Zend_Log::DEBUG => LOG_DEBUG,
+ );
+
+ /**
+ * The default log priority - for unmapped custom priorities
+ *
+ * @var string
+ */
+ protected $_defaultPriority = LOG_NOTICE;
+
+ /**
+ * Last application name set by a syslog-writer instance
+ *
+ * @var string
+ */
+ protected static $_lastApplication;
+
+ /**
+ * Last facility name set by a syslog-writer instance
+ *
+ * @var string
+ */
+ protected static $_lastFacility;
+
+ /**
+ * Application name used by this syslog-writer instance
+ *
+ * @var string
+ */
+ protected $_application = 'Zend_Log';
+
+ /**
+ * Facility used by this syslog-writer instance
+ *
+ * @var int
+ */
+ protected $_facility = LOG_USER;
+
+ /**
+ * Types of program available to logging of message
+ *
+ * @var array
+ */
+ protected $_validFacilities = array();
+
+ /**
+ * Class constructor
+ *
+ * @param array $params Array of options; may include "application" and "facility" keys
+ * @return void
+ */
+ public function __construct(array $params = array())
+ {
+ if (isset($params['application'])) {
+ $this->_application = $params['application'];
+ }
+
+ $runInitializeSyslog = true;
+ if (isset($params['facility'])) {
+ $this->setFacility($params['facility']);
+ $runInitializeSyslog = false;
+ }
+
+ if ($runInitializeSyslog) {
+ $this->_initializeSyslog();
+ }
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_Syslog
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_Syslog
+ */
+ static public function factory($config)
+ {
+ return new self(self::_parseConfig($config));
+ }
+
+ /**
+ * Initialize values facilities
+ *
+ * @return void
+ */
+ protected function _initializeValidFacilities()
+ {
+ $constants = array(
+ 'LOG_AUTH',
+ 'LOG_AUTHPRIV',
+ 'LOG_CRON',
+ 'LOG_DAEMON',
+ 'LOG_KERN',
+ 'LOG_LOCAL0',
+ 'LOG_LOCAL1',
+ 'LOG_LOCAL2',
+ 'LOG_LOCAL3',
+ 'LOG_LOCAL4',
+ 'LOG_LOCAL5',
+ 'LOG_LOCAL6',
+ 'LOG_LOCAL7',
+ 'LOG_LPR',
+ 'LOG_MAIL',
+ 'LOG_NEWS',
+ 'LOG_SYSLOG',
+ 'LOG_USER',
+ 'LOG_UUCP'
+ );
+
+ foreach ($constants as $constant) {
+ if (defined($constant)) {
+ $this->_validFacilities[] = constant($constant);
+ }
+ }
+ }
+
+ /**
+ * Initialize syslog / set application name and facility
+ *
+ * @return void
+ */
+ protected function _initializeSyslog()
+ {
+ self::$_lastApplication = $this->_application;
+ self::$_lastFacility = $this->_facility;
+ openlog($this->_application, LOG_PID, $this->_facility);
+ }
+
+ /**
+ * Set syslog facility
+ *
+ * @param int $facility Syslog facility
+ * @return Zend_Log_Writer_Syslog
+ * @throws Zend_Log_Exception for invalid log facility
+ */
+ public function setFacility($facility)
+ {
+ if ($this->_facility === $facility) {
+ return $this;
+ }
+
+ if (!count($this->_validFacilities)) {
+ $this->_initializeValidFacilities();
+ }
+
+ if (!in_array($facility, $this->_validFacilities)) {
+ throw new Zend_Log_Exception('Invalid log facility provided; please see http://php.net/openlog for a list of valid facility values');
+ }
+
+ if ('WIN' == strtoupper(substr(PHP_OS, 0, 3))
+ && ($facility !== LOG_USER)
+ ) {
+ throw new Zend_Log_Exception('Only LOG_USER is a valid log facility on Windows');
+ }
+
+ $this->_facility = $facility;
+ $this->_initializeSyslog();
+ return $this;
+ }
+
+ /**
+ * Set application name
+ *
+ * @param string $application Application name
+ * @return Zend_Log_Writer_Syslog
+ */
+ public function setApplicationName($application)
+ {
+ if ($this->_application === $application) {
+ return $this;
+ }
+ $this->_application = $application;
+ $this->_initializeSyslog();
+ return $this;
+ }
+
+ /**
+ * Close syslog.
+ *
+ * @return void
+ */
+ public function shutdown()
+ {
+ closelog();
+ }
+
+ /**
+ * Write a message to syslog.
+ *
+ * @param array $event event data
+ * @return void
+ */
+ protected function _write($event)
+ {
+ if (array_key_exists($event['priority'], $this->_priorities)) {
+ $priority = $this->_priorities[$event['priority']];
+ } else {
+ $priority = $this->_defaultPriority;
+ }
+
+ if ($this->_application !== self::$_lastApplication
+ || $this->_facility !== self::$_lastFacility)
+ {
+ $this->_initializeSyslog();
+ }
+
+ $message = $event['message'];
+ if ($this->_formatter instanceof Zend_Log_Formatter_Interface) {
+ $message = $this->_formatter->format($event);
+ }
+
+ syslog($priority, $message);
+ }
+}
diff --git a/library/vendor/Zend/Log/Writer/ZendMonitor.php b/library/vendor/Zend/Log/Writer/ZendMonitor.php
new file mode 100644
index 0000000..b59db93
--- /dev/null
+++ b/library/vendor/Zend/Log/Writer/ZendMonitor.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Log_Writer_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Log
+ * @subpackage Writer
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Log_Writer_ZendMonitor extends Zend_Log_Writer_Abstract
+{
+ /**
+ * Is Zend Monitor enabled?
+ *
+ * @var boolean
+ */
+ protected $_isEnabled = true;
+
+ /**
+ * Is this for a Zend Server intance?
+ *
+ * @var boolean
+ */
+ protected $_isZendServer = false;
+
+ /**
+ * @return void
+ */
+ public function __construct()
+ {
+ if (!function_exists('monitor_custom_event')) {
+ $this->_isEnabled = false;
+ }
+ if (function_exists('zend_monitor_custom_event')) {
+ $this->_isZendServer = true;
+ }
+ }
+
+ /**
+ * Create a new instance of Zend_Log_Writer_ZendMonitor
+ *
+ * @param array|Zend_Config $config
+ * @return Zend_Log_Writer_ZendMonitor
+ */
+ static public function factory($config)
+ {
+ return new self();
+ }
+
+ /**
+ * Is logging to this writer enabled?
+ *
+ * If the Zend Monitor extension is not enabled, this log writer will
+ * fail silently. You can query this method to determine if the log
+ * writer is enabled.
+ *
+ * @return boolean
+ */
+ public function isEnabled()
+ {
+ return $this->_isEnabled;
+ }
+
+ /**
+ * Log a message to this writer.
+ *
+ * @param array $event log data event
+ * @return void
+ */
+ public function write($event)
+ {
+ if (!$this->isEnabled()) {
+ return;
+ }
+
+ parent::write($event);
+ }
+
+ /**
+ * Write a message to the log.
+ *
+ * @param array $event log data event
+ * @return void
+ */
+ protected function _write($event)
+ {
+ $priority = $event['priority'];
+ $message = $event['message'];
+ unset($event['priority'], $event['message']);
+
+ if (!empty($event)) {
+ if ($this->_isZendServer) {
+ // On Zend Server; third argument should be the event
+ zend_monitor_custom_event($priority, $message, $event);
+ } else {
+ // On Zend Platform; third argument is severity -- either
+ // 0 or 1 -- and fourth is optional (event)
+ // Severity is either 0 (normal) or 1 (severe); classifying
+ // notice, info, and debug as "normal", and all others as
+ // "severe"
+ monitor_custom_event($priority, $message, ($priority > 4) ? 0 : 1, $event);
+ }
+ } else {
+ monitor_custom_event($priority, $message);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Mail.php b/library/vendor/Zend/Mail.php
new file mode 100644
index 0000000..8a1173c
--- /dev/null
+++ b/library/vendor/Zend/Mail.php
@@ -0,0 +1,1265 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Transport_Abstract
+ */
+
+/**
+ * @see Zend_Mime
+ */
+
+/**
+ * @see Zend_Mime_Message
+ */
+
+/**
+ * @see Zend_Mime_Part
+ */
+
+
+/**
+ * Class for sending an email.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail extends Zend_Mime_Message
+{
+ /**#@+
+ * @access protected
+ */
+
+ /**
+ * @var Zend_Mail_Transport_Abstract
+ * @static
+ */
+ protected static $_defaultTransport = null;
+
+ /**
+ * @var array
+ * @static
+ */
+ protected static $_defaultFrom;
+
+ /**
+ * @var array
+ * @static
+ */
+ protected static $_defaultReplyTo;
+
+ /**
+ * Mail character set
+ * @var string
+ */
+ protected $_charset = 'iso-8859-1';
+
+ /**
+ * Mail headers
+ * @var array
+ */
+ protected $_headers = array();
+
+ /**
+ * Encoding of Mail headers
+ * @var string
+ */
+ protected $_headerEncoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE;
+
+ /**
+ * From: address
+ * @var string
+ */
+ protected $_from = null;
+
+ /**
+ * To: addresses
+ * @var array
+ */
+ protected $_to = array();
+
+ /**
+ * Array of all recipients
+ * @var array
+ */
+ protected $_recipients = array();
+
+ /**
+ * Reply-To header
+ * @var string
+ */
+ protected $_replyTo = null;
+
+ /**
+ * Return-Path header
+ * @var string
+ */
+ protected $_returnPath = null;
+
+ /**
+ * Subject: header
+ * @var string
+ */
+ protected $_subject = null;
+
+ /**
+ * Date: header
+ * @var string
+ */
+ protected $_date = null;
+
+ /**
+ * Message-ID: header
+ * @var string
+ */
+ protected $_messageId = null;
+
+ /**
+ * text/plain MIME part
+ * @var false|Zend_Mime_Part
+ */
+ protected $_bodyText = false;
+
+ /**
+ * text/html MIME part
+ * @var false|Zend_Mime_Part
+ */
+ protected $_bodyHtml = false;
+
+ /**
+ * MIME boundary string
+ * @var string
+ */
+ protected $_mimeBoundary = null;
+
+ /**
+ * Content type of the message
+ * @var string
+ */
+ protected $_type = null;
+
+ /**#@-*/
+
+ /**
+ * Flag: whether or not email has attachments
+ * @var boolean
+ */
+ public $hasAttachments = false;
+
+
+ /**
+ * Sets the default mail transport for all following uses of
+ * Zend_Mail::send();
+ *
+ * @todo Allow passing a string to indicate the transport to load
+ * @todo Allow passing in optional options for the transport to load
+ * @param Zend_Mail_Transport_Abstract $transport
+ */
+ public static function setDefaultTransport(Zend_Mail_Transport_Abstract $transport)
+ {
+ self::$_defaultTransport = $transport;
+ }
+
+ /**
+ * Gets the default mail transport for all following uses of
+ * unittests
+ *
+ * @todo Allow passing a string to indicate the transport to load
+ * @todo Allow passing in optional options for the transport to load
+ */
+ public static function getDefaultTransport()
+ {
+ return self::$_defaultTransport;
+ }
+
+ /**
+ * Clear the default transport property
+ */
+ public static function clearDefaultTransport()
+ {
+ self::$_defaultTransport = null;
+ }
+
+ /**
+ * Public constructor
+ *
+ * @param string $charset
+ */
+ public function __construct($charset = null)
+ {
+ if ($charset != null) {
+ $this->_charset = $charset;
+ }
+ }
+
+ /**
+ * Return charset string
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->_charset;
+ }
+
+ /**
+ * Set content type
+ *
+ * Should only be used for manually setting multipart content types.
+ *
+ * @param string $type Content type
+ * @return Zend_Mail Implements fluent interface
+ * @throws Zend_Mail_Exception for types not supported by Zend_Mime
+ */
+ public function setType($type)
+ {
+ $allowed = array(
+ Zend_Mime::MULTIPART_ALTERNATIVE,
+ Zend_Mime::MULTIPART_MIXED,
+ Zend_Mime::MULTIPART_RELATED,
+ );
+ if (!in_array($type, $allowed)) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Invalid content type "' . $type . '"');
+ }
+
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * Get content type of the message
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Set an arbitrary mime boundary for the message
+ *
+ * If not set, Zend_Mime will generate one.
+ *
+ * @param string $boundary
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function setMimeBoundary($boundary)
+ {
+ $this->_mimeBoundary = $boundary;
+
+ return $this;
+ }
+
+ /**
+ * Return the boundary string used for the message
+ *
+ * @return string
+ */
+ public function getMimeBoundary()
+ {
+ return $this->_mimeBoundary;
+ }
+
+ /**
+ * Return encoding of mail headers
+ *
+ * @deprecated use {@link getHeaderEncoding()} instead
+ * @return string
+ */
+ public function getEncodingOfHeaders()
+ {
+ return $this->getHeaderEncoding();
+ }
+
+ /**
+ * Return the encoding of mail headers
+ *
+ * Either Zend_Mime::ENCODING_QUOTEDPRINTABLE or Zend_Mime::ENCODING_BASE64
+ *
+ * @return string
+ */
+ public function getHeaderEncoding()
+ {
+ return $this->_headerEncoding;
+ }
+
+ /**
+ * Set the encoding of mail headers
+ *
+ * @deprecated Use {@link setHeaderEncoding()} instead.
+ * @param string $encoding
+ * @return Zend_Mail
+ */
+ public function setEncodingOfHeaders($encoding)
+ {
+ return $this->setHeaderEncoding($encoding);
+ }
+
+ /**
+ * Set the encoding of mail headers
+ *
+ * @param string $encoding Zend_Mime::ENCODING_QUOTEDPRINTABLE or
+ * Zend_Mime::ENCODING_BASE64
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception
+ */
+ public function setHeaderEncoding($encoding)
+ {
+ $allowed = array(
+ Zend_Mime::ENCODING_BASE64,
+ Zend_Mime::ENCODING_QUOTEDPRINTABLE
+ );
+ if (!in_array($encoding, $allowed)) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Invalid encoding "' . $encoding . '"');
+ }
+ $this->_headerEncoding = $encoding;
+
+ return $this;
+ }
+
+ /**
+ * Sets the text body for the message.
+ *
+ * @param string $txt
+ * @param string $charset
+ * @param string $encoding
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function setBodyText($txt, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
+ {
+ if ($charset === null) {
+ $charset = $this->_charset;
+ }
+
+ $mp = new Zend_Mime_Part($txt);
+ $mp->encoding = $encoding;
+ $mp->type = Zend_Mime::TYPE_TEXT;
+ $mp->disposition = Zend_Mime::DISPOSITION_INLINE;
+ $mp->charset = $charset;
+
+ $this->_bodyText = $mp;
+
+ return $this;
+ }
+
+ /**
+ * Return text body Zend_Mime_Part or string
+ *
+ * @param bool $textOnly Whether to return just the body text content or
+ * the MIME part; defaults to false, the MIME part
+ * @return false|Zend_Mime_Part|string
+ */
+ public function getBodyText($textOnly = false)
+ {
+ if ($textOnly && $this->_bodyText) {
+ $body = $this->_bodyText;
+ return $body->getContent();
+ }
+
+ return $this->_bodyText;
+ }
+
+ /**
+ * Sets the HTML body for the message
+ *
+ * @param string $html
+ * @param string $charset
+ * @param string $encoding
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function setBodyHtml($html, $charset = null, $encoding = Zend_Mime::ENCODING_QUOTEDPRINTABLE)
+ {
+ if ($charset === null) {
+ $charset = $this->_charset;
+ }
+
+ $mp = new Zend_Mime_Part($html);
+ $mp->encoding = $encoding;
+ $mp->type = Zend_Mime::TYPE_HTML;
+ $mp->disposition = Zend_Mime::DISPOSITION_INLINE;
+ $mp->charset = $charset;
+
+ $this->_bodyHtml = $mp;
+
+ return $this;
+ }
+
+ /**
+ * Return Zend_Mime_Part representing body HTML
+ *
+ * @param bool $htmlOnly Whether to return the body HTML only, or the MIME part; defaults to false, the MIME part
+ * @return false|Zend_Mime_Part|string
+ */
+ public function getBodyHtml($htmlOnly = false)
+ {
+ if ($htmlOnly && $this->_bodyHtml) {
+ $body = $this->_bodyHtml;
+ return $body->getContent();
+ }
+
+ return $this->_bodyHtml;
+ }
+
+ /**
+ * Adds an existing attachment to the mail message
+ *
+ * @param Zend_Mime_Part $attachment
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function addAttachment(Zend_Mime_Part $attachment)
+ {
+ $this->addPart($attachment);
+ $this->hasAttachments = true;
+
+ return $this;
+ }
+
+ /**
+ * Creates a Zend_Mime_Part attachment
+ *
+ * Attachment is automatically added to the mail object after creation. The
+ * attachment object is returned to allow for further manipulation.
+ *
+ * @param string $body
+ * @param string $mimeType
+ * @param string $disposition
+ * @param string $encoding
+ * @param string $filename OPTIONAL A filename for the attachment
+ * @return Zend_Mime_Part Newly created Zend_Mime_Part object (to allow
+ * advanced settings)
+ */
+ public function createAttachment($body,
+ $mimeType = Zend_Mime::TYPE_OCTETSTREAM,
+ $disposition = Zend_Mime::DISPOSITION_ATTACHMENT,
+ $encoding = Zend_Mime::ENCODING_BASE64,
+ $filename = null)
+ {
+
+ $mp = new Zend_Mime_Part($body);
+ $mp->encoding = $encoding;
+ $mp->type = $mimeType;
+ $mp->disposition = $disposition;
+ $mp->filename = $filename;
+
+ $this->addAttachment($mp);
+
+ return $mp;
+ }
+
+ /**
+ * Return a count of message parts
+ *
+ * @return integer
+ */
+ public function getPartCount()
+ {
+ return count($this->_parts);
+ }
+
+ /**
+ * Encode header fields
+ *
+ * Encodes header content according to RFC1522 if it contains non-printable
+ * characters.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function _encodeHeader($value)
+ {
+ if (Zend_Mime::isPrintable($value) === false) {
+ if ($this->getHeaderEncoding() === Zend_Mime::ENCODING_QUOTEDPRINTABLE) {
+ $value = Zend_Mime::encodeQuotedPrintableHeader($value, $this->getCharset(), Zend_Mime::LINELENGTH, Zend_Mime::LINEEND);
+ } else {
+ $value = Zend_Mime::encodeBase64Header($value, $this->getCharset(), Zend_Mime::LINELENGTH, Zend_Mime::LINEEND);
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Add a header to the message
+ *
+ * Adds a header to this message. If append is true and the header already
+ * exists, raises a flag indicating that the header should be appended.
+ *
+ * @param string $headerName
+ * @param string $value
+ * @param bool $append
+ */
+ protected function _storeHeader($headerName, $value, $append = false)
+ {
+ if (isset($this->_headers[$headerName])) {
+ $this->_headers[$headerName][] = $value;
+ } else {
+ $this->_headers[$headerName] = array($value);
+ }
+
+ if ($append) {
+ $this->_headers[$headerName]['append'] = true;
+ }
+
+ }
+
+ /**
+ * Clear header from the message
+ *
+ * @param string $headerName
+ * @deprecated use public method directly
+ */
+ protected function _clearHeader($headerName)
+ {
+ $this->clearHeader($headerName);
+ }
+
+ /**
+ * Helper function for adding a recipient and the corresponding header
+ *
+ * @param string $headerName
+ * @param string $email
+ * @param string $name
+ */
+ protected function _addRecipientAndHeader($headerName, $email, $name)
+ {
+ $email = $this->_filterEmail($email);
+ $name = $this->_filterName($name);
+ // prevent duplicates
+ $this->_recipients[$email] = 1;
+ $this->_storeHeader($headerName, $this->_formatAddress($email, $name), true);
+ }
+
+ /**
+ * Adds To-header and recipient, $email can be an array, or a single string
+ * address
+ *
+ * @param string|array $email
+ * @param string $name
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function addTo($email, $name='')
+ {
+ if (!is_array($email)) {
+ $email = array($name => $email);
+ }
+
+ foreach ($email as $n => $recipient) {
+ $this->_addRecipientAndHeader('To', $recipient, is_int($n) ? '' : $n);
+ $this->_to[] = $recipient;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds Cc-header and recipient, $email can be an array, or a single string
+ * address
+ *
+ * @param string|array $email
+ * @param string $name
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function addCc($email, $name='')
+ {
+ if (!is_array($email)) {
+ $email = array($name => $email);
+ }
+
+ foreach ($email as $n => $recipient) {
+ $this->_addRecipientAndHeader('Cc', $recipient, is_int($n) ? '' : $n);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds Bcc recipient, $email can be an array, or a single string address
+ *
+ * @param string|array $email
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function addBcc($email)
+ {
+ if (!is_array($email)) {
+ $email = array($email);
+ }
+
+ foreach ($email as $recipient) {
+ $this->_addRecipientAndHeader('Bcc', $recipient, '');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return list of recipient email addresses
+ *
+ * @return array (of strings)
+ */
+ public function getRecipients()
+ {
+ return array_keys($this->_recipients);
+ }
+
+ /**
+ * Clear header from the message
+ *
+ * @param string $headerName
+ * @return Zend_Mail Provides fluent inter
+ */
+ public function clearHeader($headerName)
+ {
+ if (isset($this->_headers[$headerName])){
+ unset($this->_headers[$headerName]);
+ }
+ return $this;
+ }
+
+ /**
+ * Clears list of recipient email addresses
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearRecipients()
+ {
+ $this->_recipients = array();
+ $this->_to = array();
+
+ $this->clearHeader('To');
+ $this->clearHeader('Cc');
+ $this->clearHeader('Bcc');
+
+ return $this;
+ }
+
+ /**
+ * Sets From-header and sender of the message
+ *
+ * @param string $email
+ * @param string $name
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception if called subsequent times
+ */
+ public function setFrom($email, $name = null)
+ {
+ if (null !== $this->_from) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('From Header set twice');
+ }
+
+ $email = $this->_filterEmail($email);
+ $name = $this->_filterName($name);
+ $this->_from = $email;
+ $this->_storeHeader('From', $this->_formatAddress($email, $name), true);
+
+ return $this;
+ }
+
+ /**
+ * Set Reply-To Header
+ *
+ * @param string $email
+ * @param string $name
+ * @return Zend_Mail
+ * @throws Zend_Mail_Exception if called more than one time
+ */
+ public function setReplyTo($email, $name = null)
+ {
+ if (null !== $this->_replyTo) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Reply-To Header set twice');
+ }
+
+ $email = $this->_filterEmail($email);
+ $name = $this->_filterName($name);
+ $this->_replyTo = $email;
+ $this->_storeHeader('Reply-To', $this->_formatAddress($email, $name), true);
+
+ return $this;
+ }
+
+ /**
+ * Returns the sender of the mail
+ *
+ * @return string
+ */
+ public function getFrom()
+ {
+ return $this->_from;
+ }
+
+ /**
+ * Returns the current Reply-To address of the message
+ *
+ * @return string|null Reply-To address, null when not set
+ */
+ public function getReplyTo()
+ {
+ return $this->_replyTo;
+ }
+
+ /**
+ * Clears the sender from the mail
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearFrom()
+ {
+ $this->_from = null;
+ $this->clearHeader('From');
+
+ return $this;
+ }
+
+ /**
+ * Clears the current Reply-To address from the message
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearReplyTo()
+ {
+ $this->_replyTo = null;
+ $this->clearHeader('Reply-To');
+
+ return $this;
+ }
+
+ /**
+ * Sets Default From-email and name of the message
+ *
+ * @param string $email
+ * @param string $name optional
+ * @return void
+ */
+ public static function setDefaultFrom($email, $name = null)
+ {
+ self::$_defaultFrom = array('email' => $email, 'name' => $name);
+ }
+
+ /**
+ * Returns the default sender of the mail
+ *
+ * @return null|array Null if none was set.
+ */
+ public static function getDefaultFrom()
+ {
+ return self::$_defaultFrom;
+ }
+
+ /**
+ * Clears the default sender from the mail
+ *
+ * @return void
+ */
+ public static function clearDefaultFrom()
+ {
+ self::$_defaultFrom = null;
+ }
+
+ /**
+ * Sets From-name and -email based on the defaults
+ *
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception
+ */
+ public function setFromToDefaultFrom() {
+ $from = self::getDefaultFrom();
+ if($from === null) {
+ throw new Zend_Mail_Exception(
+ 'No default From Address set to use');
+ }
+
+ $this->setFrom($from['email'], $from['name']);
+
+ return $this;
+ }
+
+ /**
+ * Sets Default ReplyTo-address and -name of the message
+ *
+ * @param string $email
+ * @param string $name optional
+ * @return void
+ */
+ public static function setDefaultReplyTo($email, $name = null)
+ {
+ self::$_defaultReplyTo = array('email' => $email, 'name' => $name);
+ }
+
+ /**
+ * Returns the default Reply-To Address and Name of the mail
+ *
+ * @return null|array Null if none was set.
+ */
+ public static function getDefaultReplyTo()
+ {
+ return self::$_defaultReplyTo;
+ }
+
+ /**
+ * Clears the default ReplyTo-address and -name from the mail
+ *
+ * @return void
+ */
+ public static function clearDefaultReplyTo()
+ {
+ self::$_defaultReplyTo = null;
+ }
+
+ /**
+ * Sets ReplyTo-name and -email based on the defaults
+ *
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception
+ */
+ public function setReplyToFromDefault() {
+ $replyTo = self::getDefaultReplyTo();
+ if($replyTo === null) {
+ throw new Zend_Mail_Exception(
+ 'No default Reply-To Address set to use');
+ }
+
+ $this->setReplyTo($replyTo['email'], $replyTo['name']);
+
+ return $this;
+ }
+
+ /**
+ * Sets the Return-Path header of the message
+ *
+ * @param string $email
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception if set multiple times
+ */
+ public function setReturnPath($email)
+ {
+ if ($this->_returnPath === null) {
+ $email = $this->_filterEmail($email);
+ $this->_returnPath = $email;
+ $this->_storeHeader('Return-Path', $email, false);
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Return-Path Header set twice');
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the current Return-Path address of the message
+ *
+ * If no Return-Path header is set, returns the value of {@link $_from}.
+ *
+ * @return string
+ */
+ public function getReturnPath()
+ {
+ if (null !== $this->_returnPath) {
+ return $this->_returnPath;
+ }
+
+ return $this->_from;
+ }
+
+ /**
+ * Clears the current Return-Path address from the message
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearReturnPath()
+ {
+ $this->_returnPath = null;
+ $this->clearHeader('Return-Path');
+
+ return $this;
+ }
+
+ /**
+ * Sets the subject of the message
+ *
+ * @param string $subject
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception
+ */
+ public function setSubject($subject)
+ {
+ if ($this->_subject === null) {
+ $subject = $this->_filterOther($subject);
+ $this->_subject = $this->_encodeHeader($subject);
+ $this->_storeHeader('Subject', $this->_subject);
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Subject set twice');
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the encoded subject of the message
+ *
+ * @return string
+ */
+ public function getSubject()
+ {
+ return $this->_subject;
+ }
+
+ /**
+ * Clears the encoded subject from the message
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearSubject()
+ {
+ $this->_subject = null;
+ $this->clearHeader('Subject');
+
+ return $this;
+ }
+
+ /**
+ * Sets Date-header
+ *
+ * @param int|string|Zend_Date $date
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception if called subsequent times or wrong date
+ * format.
+ */
+ public function setDate($date = null)
+ {
+ if ($this->_date === null) {
+ if ($date === null) {
+ $date = date('r');
+ } else if (is_int($date)) {
+ $date = date('r', $date);
+ } else if (is_string($date)) {
+ $date = strtotime($date);
+ if ($date === false || $date < 0) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('String representations of Date Header must be ' .
+ 'strtotime()-compatible');
+ }
+ $date = date('r', $date);
+ } else if ($date instanceof Zend_Date) {
+ $date = $date->get(Zend_Date::RFC_2822);
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception(__METHOD__ . ' only accepts UNIX timestamps, Zend_Date objects, ' .
+ ' and strtotime()-compatible strings');
+ }
+ $this->_date = $date;
+ $this->_storeHeader('Date', $date);
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Date Header set twice');
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the formatted date of the message
+ *
+ * @return string
+ */
+ public function getDate()
+ {
+ return $this->_date;
+ }
+
+ /**
+ * Clears the formatted date from the message
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearDate()
+ {
+ $this->_date = null;
+ $this->clearHeader('Date');
+
+ return $this;
+ }
+
+ /**
+ * Sets the Message-ID of the message
+ *
+ * @param boolean|string $id
+ * true :Auto
+ * false :No set
+ * null :No set
+ * string:Sets given string (Angle brackets is not necessary)
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception
+ */
+ public function setMessageId($id = true)
+ {
+ if ($id === null || $id === false) {
+ return $this;
+ } elseif ($id === true) {
+ $id = $this->createMessageId();
+ }
+
+ if ($this->_messageId === null) {
+ $id = $this->_filterOther($id);
+ $this->_messageId = $id;
+ $this->_storeHeader('Message-Id', '<' . $this->_messageId . '>');
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Message-ID set twice');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the Message-ID of the message
+ *
+ * @return string
+ */
+ public function getMessageId()
+ {
+ return $this->_messageId;
+ }
+
+
+ /**
+ * Clears the Message-ID from the message
+ *
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function clearMessageId()
+ {
+ $this->_messageId = null;
+ $this->clearHeader('Message-Id');
+
+ return $this;
+ }
+
+ /**
+ * Creates the Message-ID
+ *
+ * @return string
+ */
+ public function createMessageId() {
+
+ $time = time();
+
+ if ($this->_from !== null) {
+ $user = $this->_from;
+ } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+ $user = $_SERVER['REMOTE_ADDR'];
+ } else {
+ $user = getmypid();
+ }
+
+ $rand = mt_rand();
+
+ if ($this->_recipients !== array()) {
+ $recipient = array_rand($this->_recipients);
+ } else {
+ $recipient = 'unknown';
+ }
+
+ if (isset($_SERVER["SERVER_NAME"])) {
+ $hostName = $_SERVER["SERVER_NAME"];
+ } else {
+ $hostName = php_uname('n');
+ }
+
+ return sha1($time . $user . $rand . $recipient) . '@' . $hostName;
+ }
+
+ /**
+ * Add a custom header to the message
+ *
+ * @param string $name
+ * @param string $value
+ * @param boolean $append
+ * @return Zend_Mail Provides fluent interface
+ * @throws Zend_Mail_Exception on attempts to create standard headers
+ */
+ public function addHeader($name, $value, $append = false)
+ {
+ $prohibit = array('to', 'cc', 'bcc', 'from', 'subject',
+ 'reply-to', 'return-path',
+ 'date', 'message-id',
+ );
+ if (in_array(strtolower($name), $prohibit)) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('Cannot set standard header from addHeader()');
+ }
+
+ $value = $this->_filterOther($value);
+ $value = $this->_encodeHeader($value);
+ $this->_storeHeader($name, $value, $append);
+
+ return $this;
+ }
+
+ /**
+ * Return mail headers
+ *
+ * @return array
+ */
+ public function getHeaders()
+ {
+ return $this->_headers;
+ }
+
+ /**
+ * Sends this email using the given transport or a previously
+ * set DefaultTransport or the internal mail function if no
+ * default transport had been set.
+ *
+ * @param Zend_Mail_Transport_Abstract $transport
+ * @return Zend_Mail Provides fluent interface
+ */
+ public function send($transport = null)
+ {
+ if ($transport === null) {
+ if (! self::$_defaultTransport instanceof Zend_Mail_Transport_Abstract) {
+ $transport = new Zend_Mail_Transport_Sendmail();
+ } else {
+ $transport = self::$_defaultTransport;
+ }
+ }
+
+ if ($this->_date === null) {
+ $this->setDate();
+ }
+
+ if(null === $this->_from && null !== self::getDefaultFrom()) {
+ $this->setFromToDefaultFrom();
+ }
+
+ if(null === $this->_replyTo && null !== self::getDefaultReplyTo()) {
+ $this->setReplyToFromDefault();
+ }
+
+ $transport->send($this);
+
+ return $this;
+ }
+
+ /**
+ * Filter of email data
+ *
+ * @param string $email
+ * @return string
+ */
+ protected function _filterEmail($email)
+ {
+ $rule = array("\r" => '',
+ "\n" => '',
+ "\t" => '',
+ '"' => '',
+ ',' => '',
+ '<' => '',
+ '>' => '',
+ );
+
+ return strtr($email, $rule);
+ }
+
+ /**
+ * Filter of name data
+ *
+ * @param string $name
+ * @return string
+ */
+ protected function _filterName($name)
+ {
+ $rule = array("\r" => '',
+ "\n" => '',
+ "\t" => '',
+ '"' => "'",
+ '<' => '[',
+ '>' => ']',
+ );
+
+ return trim(strtr($name, $rule));
+ }
+
+ /**
+ * Filter of other data
+ *
+ * @param string $data
+ * @return string
+ */
+ protected function _filterOther($data)
+ {
+ $rule = array("\r" => '',
+ "\n" => '',
+ "\t" => '',
+ );
+
+ return strtr($data, $rule);
+ }
+
+ /**
+ * Formats e-mail address
+ *
+ * @param string $email
+ * @param string $name
+ * @return string
+ */
+ protected function _formatAddress($email, $name)
+ {
+ if ($name === '' || $name === null || $name === $email) {
+ return $email;
+ } else {
+ $encodedName = $this->_encodeHeader($name);
+ if ($encodedName === $name && strcspn($name, '()<>[]:;@\\,.') != strlen($name)) {
+ $format = '"%s" <%s>';
+ } else {
+ $format = '%s <%s>';
+ }
+ return sprintf($format, $encodedName, $email);
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Mail/Exception.php b/library/vendor/Zend/Mail/Exception.php
new file mode 100644
index 0000000..eeae2ab
--- /dev/null
+++ b/library/vendor/Zend/Mail/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Exception extends Zend_Exception
+{}
+
diff --git a/library/vendor/Zend/Mail/Header/HeaderName.php b/library/vendor/Zend/Mail/Header/HeaderName.php
new file mode 100644
index 0000000..1284c3c
--- /dev/null
+++ b/library/vendor/Zend/Mail/Header/HeaderName.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+final class Zend_Mail_Header_HeaderName
+{
+ /**
+ * No public constructor.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Filter the header name according to RFC 2822
+ *
+ * @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2)
+ * @param string $name
+ * @return string
+ */
+ public static function filter($name)
+ {
+ $result = '';
+ $tot = strlen($name);
+ for ($i = 0; $i < $tot; $i += 1) {
+ $ord = ord($name[$i]);
+ if ($ord > 32 && $ord < 127 && $ord !== 58) {
+ $result .= $name[$i];
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Determine if the header name contains any invalid characters.
+ *
+ * @param string $name
+ * @return bool
+ */
+ public static function isValid($name)
+ {
+ $tot = strlen($name);
+ for ($i = 0; $i < $tot; $i += 1) {
+ $ord = ord($name[$i]);
+ if ($ord < 33 || $ord > 126 || $ord === 58) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Assert that the header name is valid.
+ *
+ * Raises an exception if invalid.
+ *
+ * @param string $name
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public static function assertValid($name)
+ {
+ if (! self::isValid($name)) {
+ throw new Zend_Mail_Exception('Invalid header name detected');
+ }
+ }
+}
diff --git a/library/vendor/Zend/Mail/Header/HeaderValue.php b/library/vendor/Zend/Mail/Header/HeaderValue.php
new file mode 100644
index 0000000..7828e8b
--- /dev/null
+++ b/library/vendor/Zend/Mail/Header/HeaderValue.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+final class Zend_Mail_Header_HeaderValue
+{
+ /**
+ * No public constructor.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Filter the header value according to RFC 2822
+ *
+ * @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2)
+ * @param string $value
+ * @return string
+ */
+ public static function filter($value)
+ {
+ $result = '';
+ $tot = strlen($value);
+
+ // Filter for CR and LF characters, leaving CRLF + WSP sequences for
+ // Long Header Fields (section 2.2.3 of RFC 2822)
+ for ($i = 0; $i < $tot; $i += 1) {
+ $ord = ord($value[$i]);
+ if (($ord < 32 || $ord > 126)
+ && $ord !== 13
+ ) {
+ continue;
+ }
+
+ if ($ord === 13) {
+ if ($i + 2 >= $tot) {
+ continue;
+ }
+
+ $lf = ord($value[$i + 1]);
+ $sp = ord($value[$i + 2]);
+
+ if ($lf !== 10 || $sp !== 32) {
+ continue;
+ }
+
+ $result .= "\r\n ";
+ $i += 2;
+ continue;
+ }
+
+ $result .= $value[$i];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Determine if the header value contains any invalid characters.
+ *
+ * @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2)
+ * @param string $value
+ * @return bool
+ */
+ public static function isValid($value)
+ {
+ $tot = strlen($value);
+ for ($i = 0; $i < $tot; $i += 1) {
+ $ord = ord($value[$i]);
+ if (($ord < 32 || $ord > 126)
+ && $ord !== 13
+ ) {
+ return false;
+ }
+
+ if ($ord === 13) {
+ if ($i + 2 >= $tot) {
+ return false;
+ }
+
+ $lf = ord($value[$i + 1]);
+ $sp = ord($value[$i + 2]);
+
+ if ($lf !== 10 || $sp !== 32) {
+ return false;
+ }
+
+ $i += 2;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Assert that the header value is valid.
+ *
+ * Raises an exception if invalid.
+ *
+ * @param string $value
+ * @throws Exception\RuntimeException
+ * @return void
+ */
+ public static function assertValid($value)
+ {
+ if (! self::isValid($value)) {
+ throw new Zend_Mail_Exception('Invalid header value detected');
+ }
+ }
+}
diff --git a/library/vendor/Zend/Mail/Message.php b/library/vendor/Zend/Mail/Message.php
new file mode 100644
index 0000000..b9ac013
--- /dev/null
+++ b/library/vendor/Zend/Mail/Message.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Zend_Mail_Part
+ */
+
+/**
+ * Zend_Mail_Message_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Message extends Zend_Mail_Part implements Zend_Mail_Message_Interface
+{
+ /**
+ * flags for this message
+ * @var array
+ */
+ protected $_flags = array();
+
+ /**
+ * Public constructor
+ *
+ * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports:
+ * - file filename or file handle of a file with raw message content
+ * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage
+ *
+ * @param string $rawMessage full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (isset($params['file'])) {
+ if (!is_resource($params['file'])) {
+ $params['raw'] = @file_get_contents($params['file']);
+ if ($params['raw'] === false) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('could not open file');
+ }
+ } else {
+ $params['raw'] = stream_get_contents($params['file']);
+ }
+ $params['raw'] = preg_replace("/(?<!\r)\n/", "\r\n", $params['raw']);
+ }
+
+ if (!empty($params['flags'])) {
+ // set key and value to the same value for easy lookup
+ $this->_flags = array_merge($this->_flags, array_combine($params['flags'],$params['flags']));
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * return toplines as found after headers
+ *
+ * @return string toplines
+ */
+ public function getTopLines()
+ {
+ return $this->_topLines;
+ }
+
+ /**
+ * check if flag is set
+ *
+ * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage
+ * @return bool true if set, otherwise false
+ */
+ public function hasFlag($flag)
+ {
+ return isset($this->_flags[$flag]);
+ }
+
+ /**
+ * get all set flags
+ *
+ * @return array array with flags, key and value are the same for easy lookup
+ */
+ public function getFlags()
+ {
+ return $this->_flags;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Message/File.php b/library/vendor/Zend/Mail/Message/File.php
new file mode 100644
index 0000000..f123092
--- /dev/null
+++ b/library/vendor/Zend/Mail/Message/File.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Zend_Mail_Part
+ */
+
+/**
+ * Zend_Mail_Message_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Message_File extends Zend_Mail_Part_File implements Zend_Mail_Message_Interface
+{
+ /**
+ * flags for this message
+ * @var array
+ */
+ protected $_flags = array();
+
+ /**
+ * Public constructor
+ *
+ * In addition to the parameters of Zend_Mail_Part::__construct() this constructor supports:
+ * - flags array with flags for message, keys are ignored, use constants defined in Zend_Mail_Storage
+ *
+ * @param string $rawMessage full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (!empty($params['flags'])) {
+ // set key and value to the same value for easy lookup
+ $this->_flags = array_combine($params['flags'], $params['flags']);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * return toplines as found after headers
+ *
+ * @return string toplines
+ */
+ public function getTopLines()
+ {
+ return $this->_topLines;
+ }
+
+ /**
+ * check if flag is set
+ *
+ * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage
+ * @return bool true if set, otherwise false
+ */
+ public function hasFlag($flag)
+ {
+ return isset($this->_flags[$flag]);
+ }
+
+ /**
+ * get all set flags
+ *
+ * @return array array with flags, key and value are the same for easy lookup
+ */
+ public function getFlags()
+ {
+ return $this->_flags;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Message/Interface.php b/library/vendor/Zend/Mail/Message/Interface.php
new file mode 100644
index 0000000..dad7964
--- /dev/null
+++ b/library/vendor/Zend/Mail/Message/Interface.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+interface Zend_Mail_Message_Interface
+{
+ /**
+ * return toplines as found after headers
+ *
+ * @return string toplines
+ */
+ public function getTopLines();
+
+ /**
+ * check if flag is set
+ *
+ * @param mixed $flag a flag name, use constants defined in Zend_Mail_Storage
+ * @return bool true if set, otherwise false
+ */
+ public function hasFlag($flag);
+
+ /**
+ * get all set flags
+ *
+ * @return array array with flags, key and value are the same for easy lookup
+ */
+ public function getFlags();
+}
diff --git a/library/vendor/Zend/Mail/Part.php b/library/vendor/Zend/Mail/Part.php
new file mode 100644
index 0000000..060689b
--- /dev/null
+++ b/library/vendor/Zend/Mail/Part.php
@@ -0,0 +1,590 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mime_Decode
+ */
+
+/**
+ * @see Zend_Mail_Header_HeaderName
+ */
+
+/**
+ * @see Zend_Mail_Header_HeaderValue
+ */
+
+/**
+ * @see Zend_Mail_Part_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Part implements RecursiveIterator, Zend_Mail_Part_Interface
+{
+ /**
+ * headers of part as array
+ * @var null|array
+ */
+ protected $_headers;
+
+ /**
+ * raw part body
+ * @var null|string
+ */
+ protected $_content;
+
+ /**
+ * toplines as fetched with headers
+ * @var string
+ */
+ protected $_topLines = '';
+
+ /**
+ * parts of multipart message
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * count of parts of a multipart message
+ * @var null|int
+ */
+ protected $_countParts;
+
+ /**
+ * current position of iterator
+ * @var int
+ */
+ protected $_iterationPos = 1;
+
+ /**
+ * mail handler, if late fetch is active
+ * @var null|Zend_Mail_Storage_Abstract
+ */
+ protected $_mail;
+
+ /**
+ * message number for mail handler
+ * @var int
+ */
+ protected $_messageNum = 0;
+
+ /**
+ * Class to use when creating message parts
+ * @var string
+ */
+ protected $_partClass;
+
+ /**
+ * Public constructor
+ *
+ * Zend_Mail_Part supports different sources for content. The possible params are:
+ * - handler a instance of Zend_Mail_Storage_Abstract for late fetch
+ * - id number of message for handler
+ * - raw raw content with header and body as string
+ * - headers headers as array (name => value) or string, if a content part is found it's used as toplines
+ * - noToplines ignore content found after headers in param 'headers'
+ * - content content as string
+ *
+ * @param array $params full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (isset($params['handler'])) {
+ if (!$params['handler'] instanceof Zend_Mail_Storage_Abstract) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('handler is not a valid mail handler');
+ }
+ if (!isset($params['id'])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('need a message id with a handler');
+ }
+
+ $this->_mail = $params['handler'];
+ $this->_messageNum = $params['id'];
+ }
+
+ if (isset($params['partclass'])) {
+ $this->setPartClass($params['partclass']);
+ }
+
+ if (isset($params['raw'])) {
+ Zend_Mime_Decode::splitMessage($params['raw'], $this->_headers, $this->_content, "\r\n");
+ } else if (isset($params['headers'])) {
+ if (is_array($params['headers'])) {
+ $this->_headers = $params['headers'];
+ $this->_validateHeaders($this->_headers);
+ } else {
+ if (!empty($params['noToplines'])) {
+ Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $null, "\r\n");
+ } else {
+ Zend_Mime_Decode::splitMessage($params['headers'], $this->_headers, $this->_topLines, "\r\n");
+ }
+ }
+
+ if (isset($params['content'])) {
+ $this->_content = $params['content'];
+ }
+ }
+ }
+
+ /**
+ * Set name pf class used to encapsulate message parts
+ * @param string $class
+ * @return Zend_Mail_Part
+ */
+ public function setPartClass($class)
+ {
+ if ( !class_exists($class) ) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception("Class '{$class}' does not exist");
+ }
+ if ( !is_subclass_of($class, 'Zend_Mail_Part_Interface') ) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception("Class '{$class}' must implement Zend_Mail_Part_Interface");
+ }
+
+ $this->_partClass = $class;
+ return $this;
+ }
+
+ /**
+ * Retrieve the class name used to encapsulate message parts
+ * @return string
+ */
+ public function getPartClass()
+ {
+ if ( !$this->_partClass ) {
+ $this->_partClass = __CLASS__;
+ }
+ return $this->_partClass;
+ }
+
+ /**
+ * Check if part is a multipart message
+ *
+ * @return bool if part is multipart
+ */
+ public function isMultipart()
+ {
+ try {
+ return stripos($this->contentType, 'multipart/') === 0;
+ } catch(Zend_Mail_Exception $e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Body of part
+ *
+ * If part is multipart the raw content of this part with all sub parts is returned
+ *
+ * @return string body
+ * @throws Zend_Mail_Exception
+ */
+ public function getContent()
+ {
+ if ($this->_content !== null) {
+ return $this->_content;
+ }
+
+ if ($this->_mail) {
+ return $this->_mail->getRawContent($this->_messageNum);
+ } else {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('no content');
+ }
+ }
+
+ /**
+ * Return size of part
+ *
+ * Quite simple implemented currently (not decoding). Handle with care.
+ *
+ * @return int size
+ */
+ public function getSize() {
+ return strlen($this->getContent());
+ }
+
+
+ /**
+ * Cache content and split in parts if multipart
+ *
+ * @return null
+ * @throws Zend_Mail_Exception
+ */
+ protected function _cacheContent()
+ {
+ // caching content if we can't fetch parts
+ if ($this->_content === null && $this->_mail) {
+ $this->_content = $this->_mail->getRawContent($this->_messageNum);
+ }
+
+ if (!$this->isMultipart()) {
+ return;
+ }
+
+ // split content in parts
+ $boundary = $this->getHeaderField('content-type', 'boundary');
+ if (!$boundary) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('no boundary found in content type to split message');
+ }
+ $parts = Zend_Mime_Decode::splitMessageStruct($this->_content, $boundary);
+ if ($parts === null) {
+ return;
+ }
+ $partClass = $this->getPartClass();
+ $counter = 1;
+ foreach ($parts as $part) {
+ $this->_parts[$counter++] = new $partClass(array('headers' => $part['header'], 'content' => $part['body']));
+ }
+ }
+
+ /**
+ * Get part of multipart message
+ *
+ * @param int $num number of part starting with 1 for first part
+ * @return Zend_Mail_Part wanted part
+ * @throws Zend_Mail_Exception
+ */
+ public function getPart($num)
+ {
+ if (isset($this->_parts[$num])) {
+ return $this->_parts[$num];
+ }
+
+ if (!$this->_mail && $this->_content === null) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('part not found');
+ }
+
+ if ($this->_mail && $this->_mail->hasFetchPart) {
+ // TODO: fetch part
+ // return
+ }
+
+ $this->_cacheContent();
+
+ if (!isset($this->_parts[$num])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('part not found');
+ }
+
+ return $this->_parts[$num];
+ }
+
+ /**
+ * Count parts of a multipart part
+ *
+ * @return int number of sub-parts
+ */
+ public function countParts()
+ {
+ if ($this->_countParts) {
+ return $this->_countParts;
+ }
+
+ $this->_countParts = count($this->_parts);
+ if ($this->_countParts) {
+ return $this->_countParts;
+ }
+
+ if ($this->_mail && $this->_mail->hasFetchPart) {
+ // TODO: fetch part
+ // return
+ }
+
+ $this->_cacheContent();
+
+ $this->_countParts = count($this->_parts);
+ return $this->_countParts;
+ }
+
+
+ /**
+ * Get all headers
+ *
+ * The returned headers are as saved internally. All names are lowercased. The value is a string or an array
+ * if a header with the same name occurs more than once.
+ *
+ * @return array headers as array(name => value)
+ */
+ public function getHeaders()
+ {
+ if ($this->_headers === null) {
+ if (!$this->_mail) {
+ $this->_headers = array();
+ } else {
+ $part = $this->_mail->getRawHeader($this->_messageNum);
+ Zend_Mime_Decode::splitMessage($part, $this->_headers, $null);
+ }
+ }
+
+ return $this->_headers;
+ }
+
+ /**
+ * Get a header in specificed format
+ *
+ * Internally headers that occur more than once are saved as array, all other as string. If $format
+ * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim).
+ *
+ * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes
+ * @param string $format change type of return value to 'string' or 'array'
+ * @return string|array value of header in wanted or internal format
+ * @throws Zend_Mail_Exception
+ */
+ public function getHeader($name, $format = null)
+ {
+ if ($this->_headers === null) {
+ $this->getHeaders();
+ }
+
+ $lowerName = strtolower($name);
+
+ if ($this->headerExists($name) == false) {
+ $lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name));
+ if($this->headerExists($lowerName) == false) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception("no Header with Name $name or $lowerName found");
+ }
+ }
+ $name = $lowerName;
+
+ $header = $this->_headers[$name];
+
+ switch ($format) {
+ case 'string':
+ if (is_array($header)) {
+ $header = implode(Zend_Mime::LINEEND, $header);
+ }
+ break;
+ case 'array':
+ $header = (array)$header;
+ default:
+ // do nothing
+ }
+
+ return $header;
+ }
+
+ /**
+ * Check wheater the Mail part has a specific header.
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function headerExists($name)
+ {
+ $name = strtolower($name);
+ if(isset($this->_headers[$name])) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get a specific field from a header like content type or all fields as array
+ *
+ * If the header occurs more than once, only the value from the first header
+ * is returned.
+ *
+ * Throws a Zend_Mail_Exception if the requested header does not exist. If
+ * the specific header field does not exist, returns null.
+ *
+ * @param string $name name of header, like in getHeader()
+ * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned
+ * @param string $firstName key name for the first part
+ * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
+ * @throws Zend_Exception, Zend_Mail_Exception
+ */
+ public function getHeaderField($name, $wantedPart = 0, $firstName = 0) {
+ return Zend_Mime_Decode::splitHeaderField(current($this->getHeader($name, 'array')), $wantedPart, $firstName);
+ }
+
+
+ /**
+ * Getter for mail headers - name is matched in lowercase
+ *
+ * This getter is short for Zend_Mail_Part::getHeader($name, 'string')
+ *
+ * @see Zend_Mail_Part::getHeader()
+ *
+ * @param string $name header name
+ * @return string value of header
+ * @throws Zend_Mail_Exception
+ */
+ public function __get($name)
+ {
+ return $this->getHeader($name, 'string');
+ }
+
+ /**
+ * Isset magic method proxy to hasHeader
+ *
+ * This method is short syntax for Zend_Mail_Part::hasHeader($name);
+ *
+ * @see Zend_Mail_Part::hasHeader
+ *
+ * @param string
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ return $this->headerExists($name);
+ }
+
+ /**
+ * magic method to get content of part
+ *
+ * @return string content
+ */
+ public function __toString()
+ {
+ return $this->getContent();
+ }
+
+ /**
+ * implements RecursiveIterator::hasChildren()
+ *
+ * @return bool current element has children/is multipart
+ */
+ public function hasChildren()
+ {
+ $current = $this->current();
+ return $current && $current instanceof Zend_Mail_Part && $current->isMultipart();
+ }
+
+ /**
+ * implements RecursiveIterator::getChildren()
+ *
+ * @return Zend_Mail_Part same as self::current()
+ */
+ public function getChildren()
+ {
+ return $this->current();
+ }
+
+ /**
+ * implements Iterator::valid()
+ *
+ * @return bool check if there's a current element
+ */
+ public function valid()
+ {
+ if ($this->_countParts === null) {
+ $this->countParts();
+ }
+ return $this->_iterationPos && $this->_iterationPos <= $this->_countParts;
+ }
+
+ /**
+ * implements Iterator::next()
+ *
+ * @return null
+ */
+ public function next()
+ {
+ ++$this->_iterationPos;
+ }
+
+ /**
+ * implements Iterator::key()
+ *
+ * @return string key/number of current part
+ */
+ public function key()
+ {
+ return $this->_iterationPos;
+ }
+
+ /**
+ * implements Iterator::current()
+ *
+ * @return Zend_Mail_Part current part
+ */
+ public function current()
+ {
+ return $this->getPart($this->_iterationPos);
+ }
+
+ /**
+ * implements Iterator::rewind()
+ *
+ * @return null
+ */
+ public function rewind()
+ {
+ $this->countParts();
+ $this->_iterationPos = 1;
+ }
+
+ /**
+ * Ensure headers do not contain invalid characters
+ *
+ * @param array $headers
+ * @param bool $assertNames
+ */
+ protected function _validateHeaders(array $headers, $assertNames = true)
+ {
+ foreach ($headers as $name => $value) {
+ if ($assertNames) {
+ Zend_Mail_Header_HeaderName::assertValid($name);
+ }
+
+ if (is_array($value)) {
+ $this->_validateHeaders($value, false);
+ continue;
+ }
+
+ Zend_Mail_Header_HeaderValue::assertValid($value);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Mail/Part/File.php b/library/vendor/Zend/Mail/Part/File.php
new file mode 100644
index 0000000..da81b7d
--- /dev/null
+++ b/library/vendor/Zend/Mail/Part/File.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mime_Decode
+ */
+
+/**
+ * @see Zend_Mail_Part
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Part_File extends Zend_Mail_Part
+{
+ protected $_contentPos = array();
+ protected $_partPos = array();
+ protected $_fh;
+
+ /**
+ * Public constructor
+ *
+ * This handler supports the following params:
+ * - file filename or open file handler with message content (required)
+ * - startPos start position of message or part in file (default: current position)
+ * - endPos end position of message or part in file (default: end of file)
+ *
+ * @param array $params full message with or without headers
+ * @throws Zend_Mail_Exception
+ */
+ public function __construct(array $params)
+ {
+ if (empty($params['file'])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('no file given in params');
+ }
+
+ if (!is_resource($params['file'])) {
+ $this->_fh = fopen($params['file'], 'r');
+ } else {
+ $this->_fh = $params['file'];
+ }
+ if (!$this->_fh) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('could not open file');
+ }
+ if (isset($params['startPos'])) {
+ fseek($this->_fh, $params['startPos']);
+ }
+ $header = '';
+ $endPos = isset($params['endPos']) ? $params['endPos'] : null;
+ while (($endPos === null || ftell($this->_fh) < $endPos) && trim($line = fgets($this->_fh))) {
+ $header .= $line;
+ }
+
+ Zend_Mime_Decode::splitMessage($header, $this->_headers, $null);
+
+ $this->_contentPos[0] = ftell($this->_fh);
+ if ($endPos !== null) {
+ $this->_contentPos[1] = $endPos;
+ } else {
+ fseek($this->_fh, 0, SEEK_END);
+ $this->_contentPos[1] = ftell($this->_fh);
+ }
+ if (!$this->isMultipart()) {
+ return;
+ }
+
+ $boundary = $this->getHeaderField('content-type', 'boundary');
+ if (!$boundary) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('no boundary found in content type to split message');
+ }
+
+ $part = array();
+ $pos = $this->_contentPos[0];
+ fseek($this->_fh, $pos);
+ while (!feof($this->_fh) && ($endPos === null || $pos < $endPos)) {
+ $line = fgets($this->_fh);
+ if ($line === false) {
+ if (feof($this->_fh)) {
+ break;
+ }
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('error reading file');
+ }
+
+ $lastPos = $pos;
+ $pos = ftell($this->_fh);
+ $line = trim($line);
+
+ if ($line == '--' . $boundary) {
+ if ($part) {
+ // not first part
+ $part[1] = $lastPos;
+ $this->_partPos[] = $part;
+ }
+ $part = array($pos);
+ } else if ($line == '--' . $boundary . '--') {
+ $part[1] = $lastPos;
+ $this->_partPos[] = $part;
+ break;
+ }
+ }
+ $this->_countParts = count($this->_partPos);
+
+ }
+
+
+ /**
+ * Body of part
+ *
+ * If part is multipart the raw content of this part with all sub parts is returned
+ *
+ * @return string body
+ * @throws Zend_Mail_Exception
+ */
+ public function getContent($stream = null)
+ {
+ fseek($this->_fh, $this->_contentPos[0]);
+ if ($stream !== null) {
+ return stream_copy_to_stream($this->_fh, $stream, $this->_contentPos[1] - $this->_contentPos[0]);
+ }
+ $length = $this->_contentPos[1] - $this->_contentPos[0];
+ return $length < 1 ? '' : fread($this->_fh, $length);
+ }
+
+ /**
+ * Return size of part
+ *
+ * Quite simple implemented currently (not decoding). Handle with care.
+ *
+ * @return int size
+ */
+ public function getSize() {
+ return $this->_contentPos[1] - $this->_contentPos[0];
+ }
+
+ /**
+ * Get part of multipart message
+ *
+ * @param int $num number of part starting with 1 for first part
+ * @return Zend_Mail_Part wanted part
+ * @throws Zend_Mail_Exception
+ */
+ public function getPart($num)
+ {
+ --$num;
+ if (!isset($this->_partPos[$num])) {
+ /**
+ * @see Zend_Mail_Exception
+ */
+ throw new Zend_Mail_Exception('part not found');
+ }
+
+ return new self(array('file' => $this->_fh, 'startPos' => $this->_partPos[$num][0],
+ 'endPos' => $this->_partPos[$num][1]));
+ }
+}
diff --git a/library/vendor/Zend/Mail/Part/Interface.php b/library/vendor/Zend/Mail/Part/Interface.php
new file mode 100644
index 0000000..0d9275d
--- /dev/null
+++ b/library/vendor/Zend/Mail/Part/Interface.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+interface Zend_Mail_Part_Interface extends RecursiveIterator
+{
+ /**
+ * Check if part is a multipart message
+ *
+ * @return bool if part is multipart
+ */
+ public function isMultipart();
+
+
+ /**
+ * Body of part
+ *
+ * If part is multipart the raw content of this part with all sub parts is returned
+ *
+ * @return string body
+ * @throws Zend_Mail_Exception
+ */
+ public function getContent();
+
+ /**
+ * Return size of part
+ *
+ * @return int size
+ */
+ public function getSize();
+
+ /**
+ * Get part of multipart message
+ *
+ * @param int $num number of part starting with 1 for first part
+ * @return Zend_Mail_Part wanted part
+ * @throws Zend_Mail_Exception
+ */
+ public function getPart($num);
+
+ /**
+ * Count parts of a multipart part
+ *
+ * @return int number of sub-parts
+ */
+ public function countParts();
+
+
+ /**
+ * Get all headers
+ *
+ * The returned headers are as saved internally. All names are lowercased. The value is a string or an array
+ * if a header with the same name occurs more than once.
+ *
+ * @return array headers as array(name => value)
+ */
+ public function getHeaders();
+
+ /**
+ * Get a header in specificed format
+ *
+ * Internally headers that occur more than once are saved as array, all other as string. If $format
+ * is set to string implode is used to concat the values (with Zend_Mime::LINEEND as delim).
+ *
+ * @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes
+ * @param string $format change type of return value to 'string' or 'array'
+ * @return string|array value of header in wanted or internal format
+ * @throws Zend_Mail_Exception
+ */
+ public function getHeader($name, $format = null);
+
+ /**
+ * Get a specific field from a header like content type or all fields as array
+ *
+ * If the header occurs more than once, only the value from the first header
+ * is returned.
+ *
+ * Throws a Zend_Mail_Exception if the requested header does not exist. If
+ * the specific header field does not exist, returns null.
+ *
+ * @param string $name name of header, like in getHeader()
+ * @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned
+ * @param string $firstName key name for the first part
+ * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
+ * @throws Zend_Exception, Zend_Mail_Exception
+ */
+ public function getHeaderField($name, $wantedPart = 0, $firstName = 0);
+
+
+ /**
+ * Getter for mail headers - name is matched in lowercase
+ *
+ * This getter is short for Zend_Mail_Part::getHeader($name, 'string')
+ *
+ * @see Zend_Mail_Part::getHeader()
+ *
+ * @param string $name header name
+ * @return string value of header
+ * @throws Zend_Mail_Exception
+ */
+ public function __get($name);
+
+ /**
+ * magic method to get content of part
+ *
+ * @return string content
+ */
+ public function __toString();
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Abstract.php b/library/vendor/Zend/Mail/Protocol/Abstract.php
new file mode 100644
index 0000000..57c5999
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Abstract.php
@@ -0,0 +1,436 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Validate
+ */
+
+
+/**
+ * @see Zend_Validate_Hostname
+ */
+
+
+/**
+ * Zend_Mail_Protocol_Abstract
+ *
+ * Provides low-level methods for concrete adapters to communicate with a remote mail server and track requests and responses.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @todo Implement proxy settings
+ */
+abstract class Zend_Mail_Protocol_Abstract
+{
+ /**
+ * Mail default EOL string
+ */
+ const EOL = "\r\n";
+
+
+ /**
+ * Default timeout in seconds for initiating session
+ */
+ const TIMEOUT_CONNECTION = 30;
+
+ /**
+ * Maximum of the transaction log
+ * @var integer
+ */
+ protected $_maximumLog = 64;
+
+
+ /**
+ * Hostname or IP address of remote server
+ * @var string
+ */
+ protected $_host;
+
+
+ /**
+ * Port number of connection
+ * @var integer
+ */
+ protected $_port;
+
+
+ /**
+ * Instance of Zend_Validate to check hostnames
+ * @var Zend_Validate
+ */
+ protected $_validHost;
+
+
+ /**
+ * Socket connection resource
+ * @var resource
+ */
+ protected $_socket;
+
+
+ /**
+ * Last request sent to server
+ * @var string
+ */
+ protected $_request;
+
+
+ /**
+ * Array of server responses to last request
+ * @var array
+ */
+ protected $_response;
+
+
+ /**
+ * String template for parsing server responses using sscanf (default: 3 digit code and response string)
+ * @var resource
+ * @deprecated Since 1.10.3
+ */
+ protected $_template = '%d%s';
+
+
+ /**
+ * Log of mail requests and server responses for a session
+ * @var array
+ */
+ private $_log = array();
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host OPTIONAL Hostname of remote connection (default: 127.0.0.1)
+ * @param integer $port OPTIONAL Port number (default: null)
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null)
+ {
+ $this->_validHost = new Zend_Validate();
+ $this->_validHost->addValidator(new Zend_Validate_Hostname(Zend_Validate_Hostname::ALLOW_ALL));
+
+ if (!$this->_validHost->isValid($host)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception(join(', ', $this->_validHost->getMessages()));
+ }
+
+ $this->_host = $host;
+ $this->_port = $port;
+ }
+
+
+ /**
+ * Class destructor to cleanup open resources
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ $this->_disconnect();
+ }
+
+ /**
+ * Set the maximum log size
+ *
+ * @param integer $maximumLog Maximum log size
+ * @return void
+ */
+ public function setMaximumLog($maximumLog)
+ {
+ $this->_maximumLog = (int) $maximumLog;
+ }
+
+
+ /**
+ * Get the maximum log size
+ *
+ * @return int the maximum log size
+ */
+ public function getMaximumLog()
+ {
+ return $this->_maximumLog;
+ }
+
+
+ /**
+ * Create a connection to the remote host
+ *
+ * Concrete adapters for this class will implement their own unique connect scripts, using the _connect() method to create the socket resource.
+ */
+ abstract public function connect();
+
+
+ /**
+ * Retrieve the last client request
+ *
+ * @return string
+ */
+ public function getRequest()
+ {
+ return $this->_request;
+ }
+
+
+ /**
+ * Retrieve the last server response
+ *
+ * @return array
+ */
+ public function getResponse()
+ {
+ return $this->_response;
+ }
+
+
+ /**
+ * Retrieve the transaction log
+ *
+ * @return string
+ */
+ public function getLog()
+ {
+ return implode('', $this->_log);
+ }
+
+
+ /**
+ * Reset the transaction log
+ *
+ * @return void
+ */
+ public function resetLog()
+ {
+ $this->_log = array();
+ }
+
+ /**
+ * Add the transaction log
+ *
+ * @param string new transaction
+ * @return void
+ */
+ protected function _addLog($value)
+ {
+ if ($this->_maximumLog >= 0 && count($this->_log) >= $this->_maximumLog) {
+ array_shift($this->_log);
+ }
+
+ $this->_log[] = $value;
+ }
+
+ /**
+ * Connect to the server using the supplied transport and target
+ *
+ * An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222'
+ *
+ * @param string $remote Remote
+ * @throws Zend_Mail_Protocol_Exception
+ * @return boolean
+ */
+ protected function _connect($remote)
+ {
+ $errorNum = 0;
+ $errorStr = '';
+
+ // open connection
+ $this->_socket = @stream_socket_client($remote, $errorNum, $errorStr, self::TIMEOUT_CONNECTION);
+
+ if ($this->_socket === false) {
+ if ($errorNum == 0) {
+ $errorStr = 'Could not open socket';
+ }
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception($errorStr);
+ }
+
+ if (($result = $this->_setStreamTimeout(self::TIMEOUT_CONNECTION)) === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('Could not set stream timeout');
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Disconnect from remote host and free resource
+ *
+ * @return void
+ */
+ protected function _disconnect()
+ {
+ if (is_resource($this->_socket)) {
+ fclose($this->_socket);
+ }
+ }
+
+
+ /**
+ * Send the given request followed by a LINEEND to the server.
+ *
+ * @param string $request
+ * @throws Zend_Mail_Protocol_Exception
+ * @return integer|boolean Number of bytes written to remote host
+ */
+ protected function _send($request)
+ {
+ if (!is_resource($this->_socket)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('No connection has been established to ' . $this->_host);
+ }
+
+ $this->_request = $request;
+
+ $result = fwrite($this->_socket, $request . self::EOL);
+
+ // Save request to internal log
+ $this->_addLog($request . self::EOL);
+
+ if ($result === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('Could not send request to ' . $this->_host);
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Get a line from the stream.
+ *
+ * @var integer $timeout Per-request timeout value if applicable
+ * @throws Zend_Mail_Protocol_Exception
+ * @return string
+ */
+ protected function _receive($timeout = null)
+ {
+ if (!is_resource($this->_socket)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('No connection has been established to ' . $this->_host);
+ }
+
+ // Adapters may wish to supply per-commend timeouts according to appropriate RFC
+ if ($timeout !== null) {
+ $this->_setStreamTimeout($timeout);
+ }
+
+ // Retrieve response
+ $reponse = fgets($this->_socket, 1024);
+
+ // Save request to internal log
+ $this->_addLog($reponse);
+
+ // Check meta data to ensure connection is still valid
+ $info = stream_get_meta_data($this->_socket);
+
+ if (!empty($info['timed_out'])) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception($this->_host . ' has timed out');
+ }
+
+ if ($reponse === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('Could not read from ' . $this->_host);
+ }
+
+ return $reponse;
+ }
+
+
+ /**
+ * Parse server response for successful codes
+ *
+ * Read the response from the stream and check for expected return code.
+ * Throws a Zend_Mail_Protocol_Exception if an unexpected code is returned.
+ *
+ * @param string|array $code One or more codes that indicate a successful response
+ * @throws Zend_Mail_Protocol_Exception
+ * @return string Last line of response string
+ */
+ protected function _expect($code, $timeout = null)
+ {
+ $this->_response = array();
+ $cmd = '';
+ $more = '';
+ $msg = '';
+ $errMsg = '';
+
+ if (!is_array($code)) {
+ $code = array($code);
+ }
+
+ do {
+ $this->_response[] = $result = $this->_receive($timeout);
+ list($cmd, $more, $msg) = preg_split('/([\s-]+)/', $result, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ if ($errMsg !== '') {
+ $errMsg .= ' ' . $msg;
+ } elseif ($cmd === null || !in_array($cmd, $code)) {
+ $errMsg = $msg;
+ }
+
+ } while (strpos($more, '-') === 0); // The '-' message prefix indicates an information string instead of a response string.
+
+ if ($errMsg !== '') {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception($errMsg, $cmd);
+ }
+
+ return $msg;
+ }
+
+ /**
+ * Set stream timeout
+ *
+ * @param integer $timeout
+ * @return boolean
+ */
+ protected function _setStreamTimeout($timeout)
+ {
+ return stream_set_timeout($this->_socket, $timeout);
+ }
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Exception.php b/library/vendor/Zend/Mail/Protocol/Exception.php
new file mode 100644
index 0000000..c7f565d
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Exception extends Zend_Mail_Exception
+{}
+
diff --git a/library/vendor/Zend/Mail/Protocol/Imap.php b/library/vendor/Zend/Mail/Protocol/Imap.php
new file mode 100644
index 0000000..067e04f
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Imap.php
@@ -0,0 +1,830 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Imap
+{
+ /**
+ * Default timeout in seconds for initiating session
+ */
+ const TIMEOUT_CONNECTION = 30;
+
+ /**
+ * socket to imap server
+ * @var resource|null
+ */
+ protected $_socket;
+
+ /**
+ * counter for request tag
+ * @var int
+ */
+ protected $_tagCount = 0;
+
+ /**
+ * Public constructor
+ *
+ * @param string $host hostname or IP address of IMAP server, if given connect() is called
+ * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl)
+ * @param bool $ssl use ssl? 'SSL', 'TLS' or false
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ function __construct($host = '', $port = null, $ssl = false)
+ {
+ if ($host) {
+ $this->connect($host, $port, $ssl);
+ }
+ }
+
+ /**
+ * Public destructor
+ */
+ public function __destruct()
+ {
+ $this->logout();
+ }
+
+ /**
+ * Open connection to IMAP server
+ *
+ * @param string $host hostname or IP address of IMAP server
+ * @param int|null $port of IMAP server, default is 143 (993 for ssl)
+ * @param string|bool $ssl use 'SSL', 'TLS' or false
+ * @return string welcome message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function connect($host, $port = null, $ssl = false)
+ {
+ if ($ssl == 'SSL') {
+ $host = 'ssl://' . $host;
+ }
+
+ if ($port === null) {
+ $port = $ssl === 'SSL' ? 993 : 143;
+ }
+
+ $errno = 0;
+ $errstr = '';
+ $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
+ if (!$this->_socket) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr .
+ ' (errno = ' . $errno . ' )');
+ }
+
+ if (!$this->_assumedNextLine('* OK')) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection');
+ }
+
+ if ($ssl === 'TLS') {
+ $result = $this->requestAndResponse('STARTTLS');
+ $result = $result && stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
+ if (!$result) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
+ }
+ }
+ }
+
+ /**
+ * get the next line from socket with error checking, but nothing else
+ *
+ * @return string next line
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _nextLine()
+ {
+ $line = @fgets($this->_socket);
+ if ($line === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?');
+ }
+
+ return $line;
+ }
+
+ /**
+ * get next line and assume it starts with $start. some requests give a simple
+ * feedback so we can quickly check if we can go on.
+ *
+ * @param string $start the first bytes we assume to be in the next line
+ * @return bool line starts with $start
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _assumedNextLine($start)
+ {
+ $line = $this->_nextLine();
+ return strpos($line, $start) === 0;
+ }
+
+ /**
+ * get next line and split the tag. that's the normal case for a response line
+ *
+ * @param string $tag tag of line is returned by reference
+ * @return string next line
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _nextTaggedLine(&$tag)
+ {
+ $line = $this->_nextLine();
+
+ // seperate tag from line
+ list($tag, $line) = explode(' ', $line, 2);
+
+ return $line;
+ }
+
+ /**
+ * split a given line in tokens. a token is literal of any form or a list
+ *
+ * @param string $line line to decode
+ * @return array tokens, literals are returned as string, lists as array
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ protected function _decodeLine($line)
+ {
+ $tokens = array();
+ $stack = array();
+
+ /*
+ We start to decode the response here. The unterstood tokens are:
+ literal
+ "literal" or also "lit\\er\"al"
+ {bytes}<NL>literal
+ (literals*)
+ All tokens are returned in an array. Literals in braces (the last unterstood
+ token in the list) are returned as an array of tokens. I.e. the following response:
+ "foo" baz {3}<NL>bar ("f\\\"oo" bar)
+ would be returned as:
+ array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
+
+ // TODO: add handling of '[' and ']' to parser for easier handling of response text
+ */
+ // replace any trailling <NL> including spaces with a single space
+ $line = rtrim($line) . ' ';
+ while (($pos = strpos($line, ' ')) !== false) {
+ $token = substr($line, 0, $pos);
+ while ($token[0] == '(') {
+ array_push($stack, $tokens);
+ $tokens = array();
+ $token = substr($token, 1);
+ }
+ if ($token[0] == '"') {
+ if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
+ $tokens[] = $matches[1];
+ $line = substr($line, strlen($matches[0]));
+ continue;
+ }
+ }
+ if ($token[0] == '{') {
+ $endPos = strpos($token, '}');
+ $chars = substr($token, 1, $endPos - 1);
+ if (is_numeric($chars)) {
+ $token = '';
+ while (strlen($token) < $chars) {
+ $token .= $this->_nextLine();
+ }
+ $line = '';
+ if (strlen($token) > $chars) {
+ $line = substr($token, $chars);
+ $token = substr($token, 0, $chars);
+ } else {
+ $line .= $this->_nextLine();
+ }
+ $tokens[] = $token;
+ $line = trim($line) . ' ';
+ continue;
+ }
+ }
+ if ($stack && $token[strlen($token) - 1] == ')') {
+ // closing braces are not seperated by spaces, so we need to count them
+ $braces = strlen($token);
+ $token = rtrim($token, ')');
+ // only count braces if more than one
+ $braces -= strlen($token) + 1;
+ // only add if token had more than just closing braces
+ if (rtrim($token) != '') {
+ $tokens[] = rtrim($token);
+ }
+ $token = $tokens;
+ $tokens = array_pop($stack);
+ // special handline if more than one closing brace
+ while ($braces-- > 0) {
+ $tokens[] = $token;
+ $token = $tokens;
+ $tokens = array_pop($stack);
+ }
+ }
+ $tokens[] = $token;
+ $line = substr($line, $pos + 1);
+ }
+
+ // maybe the server forgot to send some closing braces
+ while ($stack) {
+ $child = $tokens;
+ $tokens = array_pop($stack);
+ $tokens[] = $child;
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * read a response "line" (could also be more than one real line if response has {..}<NL>)
+ * and do a simple decode
+ *
+ * @param array|string $tokens decoded tokens are returned by reference, if $dontParse
+ * is true the unparsed line is returned here
+ * @param string $wantedTag check for this tag for response code. Default '*' is
+ * continuation tag.
+ * @param bool $dontParse if true only the unparsed line is returned $tokens
+ * @return bool if returned tag matches wanted tag
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false)
+ {
+ $line = $this->_nextTaggedLine($tag);
+ if (!$dontParse) {
+ $tokens = $this->_decodeLine($line);
+ } else {
+ $tokens = $line;
+ }
+
+ // if tag is wanted tag we might be at the end of a multiline response
+ return $tag == $wantedTag;
+ }
+
+ /**
+ * read all lines of response until given tag is found (last line of response)
+ *
+ * @param string $tag the tag of your request
+ * @param string|array $filter you can filter the response so you get only the
+ * given response lines
+ * @param bool $dontParse if true every line is returned unparsed instead of
+ * the decoded tokens
+ * @return null|bool|array tokens if success, false if error, null if bad request
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function readResponse($tag, $dontParse = false)
+ {
+ $lines = array();
+ while (!$this->readLine($tokens, $tag, $dontParse)) {
+ $lines[] = $tokens;
+ }
+
+ if ($dontParse) {
+ // last to chars are still needed for response code
+ $tokens = array(substr($tokens, 0, 2));
+ }
+ // last line has response code
+ if ($tokens[0] == 'OK') {
+ return $lines ? $lines : true;
+ } else if ($tokens[0] == 'NO'){
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * send a request
+ *
+ * @param string $command your request command
+ * @param array $tokens additional parameters to command, use escapeString() to prepare
+ * @param string $tag provide a tag otherwise an autogenerated is returned
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function sendRequest($command, $tokens = array(), &$tag = null)
+ {
+ if (!$tag) {
+ ++$this->_tagCount;
+ $tag = 'TAG' . $this->_tagCount;
+ }
+
+ $line = $tag . ' ' . $command;
+
+ foreach ($tokens as $token) {
+ if (is_array($token)) {
+ if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
+ }
+ if (!$this->_assumedNextLine('+ ')) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot send literal string');
+ }
+ $line = $token[1];
+ } else {
+ $line .= ' ' . $token;
+ }
+ }
+
+ if (@fputs($this->_socket, $line . "\r\n") === false) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
+ }
+ }
+
+ /**
+ * send a request and get response at once
+ *
+ * @param string $command command as in sendRequest()
+ * @param array $tokens parameters as in sendRequest()
+ * @param bool $dontParse if true unparsed lines are returned instead of tokens
+ * @return mixed response as in readResponse()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function requestAndResponse($command, $tokens = array(), $dontParse = false)
+ {
+ $this->sendRequest($command, $tokens, $tag);
+ $response = $this->readResponse($tag, $dontParse);
+
+ return $response;
+ }
+
+ /**
+ * escape one or more literals i.e. for sendRequest
+ *
+ * @param string|array $string the literal/-s
+ * @return string|array escape literals, literals with newline ar returned
+ * as array('{size}', 'string');
+ */
+ public function escapeString($string)
+ {
+ if (func_num_args() < 2) {
+ if (strpos($string, "\n") !== false) {
+ return array('{' . strlen($string) . '}', $string);
+ } else {
+ return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"';
+ }
+ }
+ $result = array();
+ foreach (func_get_args() as $string) {
+ $result[] = $this->escapeString($string);
+ }
+ return $result;
+ }
+
+ /**
+ * escape a list with literals or lists
+ *
+ * @param array $list list with literals or lists as PHP array
+ * @return string escaped list for imap
+ */
+ public function escapeList($list)
+ {
+ $result = array();
+ foreach ($list as $k => $v) {
+ if (!is_array($v)) {
+// $result[] = $this->escapeString($v);
+ $result[] = $v;
+ continue;
+ }
+ $result[] = $this->escapeList($v);
+ }
+ return '(' . implode(' ', $result) . ')';
+ }
+
+ /**
+ * Login to IMAP server.
+ *
+ * @param string $user username
+ * @param string $password password
+ * @return bool success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function login($user, $password)
+ {
+ return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
+ }
+
+ /**
+ * logout of imap server
+ *
+ * @return bool success
+ */
+ public function logout()
+ {
+ $result = false;
+ if ($this->_socket) {
+ try {
+ $result = $this->requestAndResponse('LOGOUT', array(), true);
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ // ignoring exception
+ }
+ fclose($this->_socket);
+ $this->_socket = null;
+ }
+ return $result;
+ }
+
+
+ /**
+ * Get capabilities from IMAP server
+ *
+ * @return array list of capabilities
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function capability()
+ {
+ $response = $this->requestAndResponse('CAPABILITY');
+
+ if (!$response) {
+ return $response;
+ }
+
+ $capabilities = array();
+ foreach ($response as $line) {
+ $capabilities = array_merge($capabilities, $line);
+ }
+ return $capabilities;
+ }
+
+ /**
+ * Examine and select have the same response. The common code for both
+ * is in this method
+ *
+ * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command
+ * @param string $box which folder to change to or examine
+ * @return bool|array false if error, array with returned information
+ * otherwise (flags, exists, recent, uidvalidity)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
+ {
+ $this->sendRequest($command, array($this->escapeString($box)), $tag);
+
+ $result = array();
+ while (!$this->readLine($tokens, $tag)) {
+ if ($tokens[0] == 'FLAGS') {
+ array_shift($tokens);
+ $result['flags'] = $tokens;
+ continue;
+ }
+ switch ($tokens[1]) {
+ case 'EXISTS':
+ case 'RECENT':
+ $result[strtolower($tokens[1])] = $tokens[0];
+ break;
+ case '[UIDVALIDITY':
+ $result['uidvalidity'] = (int)$tokens[2];
+ break;
+ default:
+ // ignore
+ }
+ }
+
+ if ($tokens[0] != 'OK') {
+ return false;
+ }
+ return $result;
+ }
+
+ /**
+ * change folder
+ *
+ * @param string $box change to this folder
+ * @return bool|array see examineOrselect()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function select($box = 'INBOX')
+ {
+ return $this->examineOrSelect('SELECT', $box);
+ }
+
+ /**
+ * examine folder
+ *
+ * @param string $box examine this folder
+ * @return bool|array see examineOrselect()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function examine($box = 'INBOX')
+ {
+ return $this->examineOrSelect('EXAMINE', $box);
+ }
+
+ /**
+ * fetch one or more items of one or more messages
+ *
+ * @param string|array $items items to fetch from message(s) as string (if only one item)
+ * or array of strings
+ * @param int $from message for items or start message if $to !== null
+ * @param int|null $to if null only one message ($from) is fetched, else it's the
+ * last message, INF means last message avaible
+ * @return string|array if only one item of one message is fetched it's returned as string
+ * if items of one message are fetched it's returned as (name => value)
+ * if one items of messages are fetched it's returned as (msgno => value)
+ * if items of messages are fetchted it's returned as (msgno => (name => value))
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function fetch($items, $from, $to = null)
+ {
+ if (is_array($from)) {
+ $set = implode(',', $from);
+ } else if ($to === null) {
+ $set = (int)$from;
+ } else if ($to === INF) {
+ $set = (int)$from . ':*';
+ } else {
+ $set = (int)$from . ':' . (int)$to;
+ }
+
+ $items = (array)$items;
+ $itemList = $this->escapeList($items);
+
+ $this->sendRequest('FETCH', array($set, $itemList), $tag);
+
+ $result = array();
+ while (!$this->readLine($tokens, $tag)) {
+ // ignore other responses
+ if ($tokens[1] != 'FETCH') {
+ continue;
+ }
+ // ignore other messages
+ if ($to === null && !is_array($from) && $tokens[0] != $from) {
+ continue;
+ }
+ // if we only want one item we return that one directly
+ if (count($items) == 1) {
+ if ($tokens[2][0] == $items[0]) {
+ $data = $tokens[2][1];
+ } else {
+ // maybe the server send an other field we didn't wanted
+ $count = count($tokens[2]);
+ // we start with 2, because 0 was already checked
+ for ($i = 2; $i < $count; $i += 2) {
+ if ($tokens[2][$i] != $items[0]) {
+ continue;
+ }
+ $data = $tokens[2][$i + 1];
+ break;
+ }
+ }
+ } else {
+ $data = array();
+ while (key($tokens[2]) !== null) {
+ $data[current($tokens[2])] = next($tokens[2]);
+ next($tokens[2]);
+ }
+ }
+ // if we want only one message we can ignore everything else and just return
+ if ($to === null && !is_array($from) && $tokens[0] == $from) {
+ // we still need to read all lines
+ while (!$this->readLine($tokens, $tag));
+ return $data;
+ }
+ $result[$tokens[0]] = $data;
+ }
+
+ if ($to === null && !is_array($from)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('the single id was not found in response');
+ }
+
+ return $result;
+ }
+
+ /**
+ * get mailbox list
+ *
+ * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword
+ *
+ * @param string $reference mailbox reference for list
+ * @param string $mailbox mailbox name match with wildcards
+ * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..))
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function listMailbox($reference = '', $mailbox = '*')
+ {
+ $result = array();
+ $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
+ if (!$list || $list === true) {
+ return $result;
+ }
+
+ foreach ($list as $item) {
+ if (count($item) != 4 || $item[0] != 'LIST') {
+ continue;
+ }
+ $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * set flags
+ *
+ * @param array $flags flags to set, add or remove - see $mode
+ * @param int $from message for items or start message if $to !== null
+ * @param int|null $to if null only one message ($from) is fetched, else it's the
+ * last message, INF means last message avaible
+ * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given
+ * @param bool $silent if false the return values are the new flags for the wanted messages
+ * @return bool|array new flags if $silent is false, else true or false depending on success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
+ {
+ $item = 'FLAGS';
+ if ($mode == '+' || $mode == '-') {
+ $item = $mode . $item;
+ }
+ if ($silent) {
+ $item .= '.SILENT';
+ }
+
+ $flags = $this->escapeList($flags);
+ $set = (int)$from;
+ if ($to != null) {
+ $set .= ':' . ($to == INF ? '*' : (int)$to);
+ }
+
+ $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent);
+
+ if ($silent) {
+ return $result ? true : false;
+ }
+
+ $tokens = $result;
+ $result = array();
+ foreach ($tokens as $token) {
+ if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
+ continue;
+ }
+ $result[$token[0]] = $token[2][1];
+ }
+
+ return $result;
+ }
+
+ /**
+ * append a new message to given folder
+ *
+ * @param string $folder name of target folder
+ * @param string $message full message content
+ * @param array $flags flags for new message
+ * @param string $date date for new message
+ * @return bool success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function append($folder, $message, $flags = null, $date = null)
+ {
+ $tokens = array();
+ $tokens[] = $this->escapeString($folder);
+ if ($flags !== null) {
+ $tokens[] = $this->escapeList($flags);
+ }
+ if ($date !== null) {
+ $tokens[] = $this->escapeString($date);
+ }
+ $tokens[] = $this->escapeString($message);
+
+ return $this->requestAndResponse('APPEND', $tokens, true);
+ }
+
+ /**
+ * copy message set from current folder to other folder
+ *
+ * @param string $folder destination folder
+ * @param int|null $to if null only one message ($from) is fetched, else it's the
+ * last message, INF means last message avaible
+ * @return bool success
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function copy($folder, $from, $to = null)
+ {
+ $set = (int)$from;
+ if ($to != null) {
+ $set .= ':' . ($to == INF ? '*' : (int)$to);
+ }
+
+ return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true);
+ }
+
+ /**
+ * create a new folder (and parent folders if needed)
+ *
+ * @param string $folder folder name
+ * @return bool success
+ */
+ public function create($folder)
+ {
+ return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true);
+ }
+
+ /**
+ * rename an existing folder
+ *
+ * @param string $old old name
+ * @param string $new new name
+ * @return bool success
+ */
+ public function rename($old, $new)
+ {
+ return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
+ }
+
+ /**
+ * remove a folder
+ *
+ * @param string $folder folder name
+ * @return bool success
+ */
+ public function delete($folder)
+ {
+ return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true);
+ }
+
+ /**
+ * permanently remove messages
+ *
+ * @return bool success
+ */
+ public function expunge()
+ {
+ // TODO: parse response?
+ return $this->requestAndResponse('EXPUNGE');
+ }
+
+ /**
+ * send noop
+ *
+ * @return bool success
+ */
+ public function noop()
+ {
+ // TODO: parse response
+ return $this->requestAndResponse('NOOP');
+ }
+
+ /**
+ * do a search request
+ *
+ * This method is currently marked as internal as the API might change and is not
+ * safe if you don't take precautions.
+ *
+ * @internal
+ * @return array message ids
+ */
+ public function search(array $params)
+ {
+ $response = $this->requestAndResponse('SEARCH', $params);
+ if (!$response) {
+ return $response;
+ }
+
+ foreach ($response as $ids) {
+ if ($ids[0] == 'SEARCH') {
+ array_shift($ids);
+ return $ids;
+ }
+ }
+ return array();
+ }
+
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Pop3.php b/library/vendor/Zend/Mail/Protocol/Pop3.php
new file mode 100644
index 0000000..d6c6e26
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Pop3.php
@@ -0,0 +1,466 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Pop3
+{
+ /**
+ * Default timeout in seconds for initiating session
+ */
+ const TIMEOUT_CONNECTION = 30;
+
+ /**
+ * saves if server supports top
+ * @var null|bool
+ */
+ public $hasTop = null;
+
+ /**
+ * socket to pop3
+ * @var null|resource
+ */
+ protected $_socket;
+
+ /**
+ * greeting timestamp for apop
+ * @var null|string
+ */
+ protected $_timestamp;
+
+
+ /**
+ * Public constructor
+ *
+ * @param string $host hostname or IP address of POP3 server, if given connect() is called
+ * @param int|null $port port of POP3 server, null for default (110 or 995 for ssl)
+ * @param bool|string $ssl use ssl? 'SSL', 'TLS' or false
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($host = '', $port = null, $ssl = false)
+ {
+ if ($host) {
+ $this->connect($host, $port, $ssl);
+ }
+ }
+
+
+ /**
+ * Public destructor
+ */
+ public function __destruct()
+ {
+ $this->logout();
+ }
+
+
+ /**
+ * Open connection to POP3 server
+ *
+ * @param string $host hostname or IP address of POP3 server
+ * @param int|null $port of POP3 server, default is 110 (995 for ssl)
+ * @param string|bool $ssl use 'SSL', 'TLS' or false
+ * @return string welcome message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function connect($host, $port = null, $ssl = false)
+ {
+ if ($ssl == 'SSL') {
+ $host = 'ssl://' . $host;
+ }
+
+ if ($port === null) {
+ $port = $ssl == 'SSL' ? 995 : 110;
+ }
+
+ $errno = 0;
+ $errstr = '';
+ $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
+ if (!$this->_socket) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr .
+ ' (errno = ' . $errno . ' )');
+ }
+
+ $welcome = $this->readResponse();
+
+ strtok($welcome, '<');
+ $this->_timestamp = strtok('>');
+ if (!strpos($this->_timestamp, '@')) {
+ $this->_timestamp = null;
+ } else {
+ $this->_timestamp = '<' . $this->_timestamp . '>';
+ }
+
+ if ($ssl === 'TLS') {
+ $this->request('STLS');
+ $result = stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
+ if (!$result) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
+ }
+ }
+
+ return $welcome;
+ }
+
+
+ /**
+ * Send a request
+ *
+ * @param string $request your request without newline
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function sendRequest($request)
+ {
+ $result = @fputs($this->_socket, $request . "\r\n");
+ if (!$result) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('send failed - connection closed?');
+ }
+ }
+
+
+ /**
+ * read a response
+ *
+ * @param boolean $multiline response has multiple lines and should be read until "<nl>.<nl>"
+ * @return string response
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function readResponse($multiline = false)
+ {
+ $result = @fgets($this->_socket);
+ if (!is_string($result)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('read failed - connection closed?');
+ }
+
+ $result = trim($result);
+ if (strpos($result, ' ')) {
+ list($status, $message) = explode(' ', $result, 2);
+ } else {
+ $status = $result;
+ $message = '';
+ }
+
+ if ($status != '+OK') {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('last request failed');
+ }
+
+ if ($multiline) {
+ $message = '';
+ $line = fgets($this->_socket);
+ while ($line && rtrim($line, "\r\n") != '.') {
+ if ($line[0] == '.') {
+ $line = substr($line, 1);
+ }
+ $message .= $line;
+ $line = fgets($this->_socket);
+ };
+ }
+
+ return $message;
+ }
+
+
+ /**
+ * Send request and get resposne
+ *
+ * @see sendRequest(), readResponse()
+ *
+ * @param string $request request
+ * @param bool $multiline multiline response?
+ * @return string result from readResponse()
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function request($request, $multiline = false)
+ {
+ $this->sendRequest($request);
+ return $this->readResponse($multiline);
+ }
+
+
+ /**
+ * End communication with POP3 server (also closes socket)
+ *
+ * @return null
+ */
+ public function logout()
+ {
+ if (!$this->_socket) {
+ return;
+ }
+
+ try {
+ $this->request('QUIT');
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ // ignore error - we're closing the socket anyway
+ }
+
+ fclose($this->_socket);
+ $this->_socket = null;
+ }
+
+
+ /**
+ * Get capabilities from POP3 server
+ *
+ * @return array list of capabilities
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function capa()
+ {
+ $result = $this->request('CAPA', true);
+ return explode("\n", $result);
+ }
+
+
+ /**
+ * Login to POP3 server. Can use APOP
+ *
+ * @param string $user username
+ * @param string $password password
+ * @param bool $try_apop should APOP be tried?
+ * @return void
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function login($user, $password, $tryApop = true)
+ {
+ if ($tryApop && $this->_timestamp) {
+ try {
+ $this->request("APOP $user " . md5($this->_timestamp . $password));
+ return;
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ // ignore
+ }
+ }
+
+ $result = $this->request("USER $user");
+ $result = $this->request("PASS $password");
+ }
+
+
+ /**
+ * Make STAT call for message count and size sum
+ *
+ * @param int $messages out parameter with count of messages
+ * @param int $octets out parameter with size in octects of messages
+ * @return void
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function status(&$messages, &$octets)
+ {
+ $messages = 0;
+ $octets = 0;
+ $result = $this->request('STAT');
+
+ list($messages, $octets) = explode(' ', $result);
+ }
+
+
+ /**
+ * Make LIST call for size of message(s)
+ *
+ * @param int|null $msgno number of message, null for all
+ * @return int|array size of given message or list with array(num => size)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getList($msgno = null)
+ {
+ if ($msgno !== null) {
+ $result = $this->request("LIST $msgno");
+
+ list(, $result) = explode(' ', $result);
+ return (int)$result;
+ }
+
+ $result = $this->request('LIST', true);
+ $messages = array();
+ $line = strtok($result, "\n");
+ while ($line) {
+ list($no, $size) = explode(' ', trim($line));
+ $messages[(int)$no] = (int)$size;
+ $line = strtok("\n");
+ }
+
+ return $messages;
+ }
+
+
+ /**
+ * Make UIDL call for getting a uniqueid
+ *
+ * @param int|null $msgno number of message, null for all
+ * @return string|array uniqueid of message or list with array(num => uniqueid)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function uniqueid($msgno = null)
+ {
+ if ($msgno !== null) {
+ $result = $this->request("UIDL $msgno");
+
+ list(, $result) = explode(' ', $result);
+ return $result;
+ }
+
+ $result = $this->request('UIDL', true);
+
+ $result = explode("\n", $result);
+ $messages = array();
+ foreach ($result as $line) {
+ if (!$line) {
+ continue;
+ }
+ list($no, $id) = explode(' ', trim($line), 2);
+ $messages[(int)$no] = $id;
+ }
+
+ return $messages;
+
+ }
+
+
+ /**
+ * Make TOP call for getting headers and maybe some body lines
+ * This method also sets hasTop - before it it's not known if top is supported
+ *
+ * The fallback makes normale RETR call, which retrieves the whole message. Additional
+ * lines are not removed.
+ *
+ * @param int $msgno number of message
+ * @param int $lines number of wanted body lines (empty line is inserted after header lines)
+ * @param bool $fallback fallback with full retrieve if top is not supported
+ * @return string message headers with wanted body lines
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function top($msgno, $lines = 0, $fallback = false)
+ {
+ if ($this->hasTop === false) {
+ if ($fallback) {
+ return $this->retrieve($msgno);
+ } else {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('top not supported and no fallback wanted');
+ }
+ }
+ $this->hasTop = true;
+
+ $lines = (!$lines || $lines < 1) ? 0 : (int)$lines;
+
+ try {
+ $result = $this->request("TOP $msgno $lines", true);
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ $this->hasTop = false;
+ if ($fallback) {
+ $result = $this->retrieve($msgno);
+ } else {
+ throw $e;
+ }
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Make a RETR call for retrieving a full message with headers and body
+ *
+ * @deprecated since 1.1.0; this method has a typo - please use retrieve()
+ * @param int $msgno message number
+ * @return string message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function retrive($msgno)
+ {
+ return $this->retrieve($msgno);
+ }
+
+
+ /**
+ * Make a RETR call for retrieving a full message with headers and body
+ *
+ * @param int $msgno message number
+ * @return string message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function retrieve($msgno)
+ {
+ $result = $this->request("RETR $msgno", true);
+ return $result;
+ }
+
+ /**
+ * Make a NOOP call, maybe needed for keeping the server happy
+ *
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function noop()
+ {
+ $this->request('NOOP');
+ }
+
+
+ /**
+ * Make a DELE count to remove a message
+ *
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function delete($msgno)
+ {
+ $this->request("DELE $msgno");
+ }
+
+
+ /**
+ * Make RSET call, which rollbacks delete requests
+ *
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function undelete()
+ {
+ $this->request('RSET');
+ }
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Smtp.php b/library/vendor/Zend/Mail/Protocol/Smtp.php
new file mode 100644
index 0000000..403ae82
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Smtp.php
@@ -0,0 +1,433 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mime
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Abstract
+ */
+
+
+/**
+ * Smtp implementation of Zend_Mail_Protocol_Abstract
+ *
+ * Minimum implementation according to RFC2821: EHLO, MAIL FROM, RCPT TO, DATA, RSET, NOOP, QUIT
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp extends Zend_Mail_Protocol_Abstract
+{
+ /**
+ * The transport method for the socket
+ *
+ * @var string
+ */
+ protected $_transport = 'tcp';
+
+
+ /**
+ * Indicates that a session is requested to be secure
+ *
+ * @var string
+ */
+ protected $_secure;
+
+
+ /**
+ * Indicates an smtp session has been started by the HELO command
+ *
+ * @var boolean
+ */
+ protected $_sess = false;
+
+
+ /**
+ * Indicates the HELO command has been issues
+ *
+ * @var unknown_type
+ */
+ protected $_helo = false;
+
+
+ /**
+ * Indicates an smtp AUTH has been issued and authenticated
+ *
+ * @var unknown_type
+ */
+ protected $_auth = false;
+
+
+ /**
+ * Indicates a MAIL command has been issued
+ *
+ * @var unknown_type
+ */
+ protected $_mail = false;
+
+
+ /**
+ * Indicates one or more RCTP commands have been issued
+ *
+ * @var unknown_type
+ */
+ protected $_rcpt = false;
+
+
+ /**
+ * Indicates that DATA has been issued and sent
+ *
+ * @var unknown_type
+ */
+ protected $_data = null;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host
+ * @param integer $port
+ * @param array $config
+ * @return void
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($host = '127.0.0.1', $port = null, array $config = array())
+ {
+ if (isset($config['ssl'])) {
+ switch (strtolower($config['ssl'])) {
+ case 'tls':
+ $this->_secure = 'tls';
+ break;
+
+ case 'ssl':
+ $this->_transport = 'ssl';
+ $this->_secure = 'ssl';
+ if ($port == null) {
+ $port = 465;
+ }
+ break;
+
+ default:
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception($config['ssl'] . ' is unsupported SSL type');
+ break;
+ }
+ }
+
+ // If no port has been specified then check the master PHP ini file. Defaults to 25 if the ini setting is null.
+ if ($port == null) {
+ if (($port = ini_get('smtp_port')) == '') {
+ $port = 25;
+ }
+ }
+
+ parent::__construct($host, $port);
+ }
+
+
+ /**
+ * Connect to the server with the parameters given in the constructor.
+ *
+ * @return boolean
+ */
+ public function connect()
+ {
+ return $this->_connect($this->_transport . '://' . $this->_host . ':'. $this->_port);
+ }
+
+
+ /**
+ * Initiate HELO/EHLO sequence and set flag to indicate valid smtp session
+ *
+ * @param string $host The client hostname or IP address (default: 127.0.0.1)
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function helo($host = '127.0.0.1')
+ {
+ // Respect RFC 2821 and disallow HELO attempts if session is already initiated.
+ if ($this->_sess === true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('Cannot issue HELO to existing session');
+ }
+
+ // Validate client hostname
+ if (!$this->_validHost->isValid($host)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception(join(', ', $this->_validHost->getMessages()));
+ }
+
+ // Initiate helo sequence
+ $this->_expect(220, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ $this->_ehlo($host);
+
+ // If a TLS session is required, commence negotiation
+ if ($this->_secure == 'tls') {
+ $this->_send('STARTTLS');
+ $this->_expect(220, 180);
+ if (!stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('Unable to connect via TLS');
+ }
+ $this->_ehlo($host);
+ }
+
+ $this->_startSession();
+ $this->auth();
+ }
+
+
+ /**
+ * Send EHLO or HELO depending on capabilities of smtp host
+ *
+ * @param string $host The client hostname or IP address (default: 127.0.0.1)
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ protected function _ehlo($host)
+ {
+ // Support for older, less-compliant remote servers. Tries multiple attempts of EHLO or HELO.
+ try {
+ $this->_send('EHLO ' . $host);
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ $this->_send('HELO ' . $host);
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ throw $e;
+ }
+ }
+
+
+ /**
+ * Issues MAIL command
+ *
+ * @param string $from Sender mailbox
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function mail($from)
+ {
+ if ($this->_sess !== true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('A valid session has not been started');
+ }
+
+ $this->_send('MAIL FROM:<' . $from . '>');
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+
+ // Set mail to true, clear recipients and any existing data flags as per 4.1.1.2 of RFC 2821
+ $this->_mail = true;
+ $this->_rcpt = false;
+ $this->_data = false;
+ }
+
+
+ /**
+ * Issues RCPT command
+ *
+ * @param string $to Receiver(s) mailbox
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function rcpt($to)
+ {
+ if ($this->_mail !== true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('No sender reverse path has been supplied');
+ }
+
+ // Set rcpt to true, as per 4.1.1.3 of RFC 2821
+ $this->_send('RCPT TO:<' . $to . '>');
+ $this->_expect(array(250, 251), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ $this->_rcpt = true;
+ }
+
+
+ /**
+ * Issues DATA command
+ *
+ * @param string $data
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function data($data)
+ {
+ // Ensure recipients have been set
+ if ($this->_rcpt !== true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('No recipient forward path has been supplied');
+ }
+
+ $this->_send('DATA');
+ $this->_expect(354, 120); // Timeout set for 2 minutes as per RFC 2821 4.5.3.2
+
+ foreach (explode(Zend_Mime::LINEEND, $data) as $line) {
+ if (strpos($line, '.') === 0) {
+ // Escape lines prefixed with a '.'
+ $line = '.' . $line;
+ }
+ $this->_send($line);
+ }
+
+ $this->_send('.');
+ $this->_expect(250, 600); // Timeout set for 10 minutes as per RFC 2821 4.5.3.2
+ $this->_data = true;
+ }
+
+
+ /**
+ * Issues the RSET command and validates answer
+ *
+ * Can be used to restore a clean smtp communication state when a transaction has been cancelled or commencing a new transaction.
+ *
+ * @return void
+ */
+ public function rset()
+ {
+ $this->_send('RSET');
+ // MS ESMTP doesn't follow RFC, see [ZF-1377]
+ $this->_expect(array(250, 220));
+
+ $this->_mail = false;
+ $this->_rcpt = false;
+ $this->_data = false;
+ }
+
+
+ /**
+ * Issues the NOOP command and validates answer
+ *
+ * Not used by Zend_Mail, could be used to keep a connection alive or check if it is still open.
+ *
+ * @return void
+ */
+ public function noop()
+ {
+ $this->_send('NOOP');
+ $this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ }
+
+
+ /**
+ * Issues the VRFY command and validates answer
+ *
+ * Not used by Zend_Mail.
+ *
+ * @param string $user User Name or eMail to verify
+ * @return void
+ */
+ public function vrfy($user)
+ {
+ $this->_send('VRFY ' . $user);
+ $this->_expect(array(250, 251, 252), 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ }
+
+
+ /**
+ * Issues the QUIT command and clears the current session
+ *
+ * @return void
+ */
+ public function quit()
+ {
+ if ($this->_sess) {
+ $this->_send('QUIT');
+ $this->_expect(221, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
+ $this->_stopSession();
+ }
+ }
+
+
+ /**
+ * Default authentication method
+ *
+ * This default method is implemented by AUTH adapters to properly authenticate to a remote host.
+ *
+ * @throws Zend_Mail_Protocol_Exception
+ * @return void
+ */
+ public function auth()
+ {
+ if ($this->_auth === true) {
+ /**
+ * @see Zend_Mail_Protocol_Exception
+ */
+ throw new Zend_Mail_Protocol_Exception('Already authenticated for this session');
+ }
+ }
+
+
+ /**
+ * Closes connection
+ *
+ * @return void
+ */
+ public function disconnect()
+ {
+ $this->_disconnect();
+ }
+
+
+ /**
+ * Start mail session
+ *
+ * @return void
+ */
+ protected function _startSession()
+ {
+ $this->_sess = true;
+ }
+
+
+ /**
+ * Stop mail session
+ *
+ * @return void
+ */
+ protected function _stopSession()
+ {
+ $this->_sess = false;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php b/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php
new file mode 100644
index 0000000..2a5ca3d
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Crammd5.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+
+
+/**
+ * Performs CRAM-MD5 authentication
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp_Auth_Crammd5 extends Zend_Mail_Protocol_Smtp
+{
+ /**
+ * Constructor.
+ *
+ * @param string $host (Default: 127.0.0.1)
+ * @param int $port (Default: null)
+ * @param array $config Auth-specific parameters
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null, $config = null)
+ {
+ if (is_array($config)) {
+ if (isset($config['username'])) {
+ $this->_username = $config['username'];
+ }
+ if (isset($config['password'])) {
+ $this->_password = $config['password'];
+ }
+ }
+
+ parent::__construct($host, $port, $config);
+ }
+
+
+ /**
+ * @todo Perform CRAM-MD5 authentication with supplied credentials
+ *
+ * @return void
+ */
+ public function auth()
+ {
+ // Ensure AUTH has not already been initiated.
+ parent::auth();
+
+ $this->_send('AUTH CRAM-MD5');
+ $challenge = $this->_expect(334);
+ $challenge = base64_decode($challenge);
+ $digest = $this->_hmacMd5($this->_password, $challenge);
+ $this->_send(base64_encode($this->_username . ' ' . $digest));
+ $this->_expect(235);
+ $this->_auth = true;
+ }
+
+
+ /**
+ * Prepare CRAM-MD5 response to server's ticket
+ *
+ * @param string $key Challenge key (usually password)
+ * @param string $data Challenge data
+ * @param string $block Length of blocks
+ * @return string
+ */
+ protected function _hmacMd5($key, $data, $block = 64)
+ {
+ if (strlen($key) > 64) {
+ $key = pack('H32', md5($key));
+ } elseif (strlen($key) < 64) {
+ $key = str_pad($key, $block, "\0");
+ }
+
+ $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
+ $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);
+
+ $inner = pack('H32', md5($k_ipad . $data));
+ $digest = md5($k_opad . $inner);
+
+ return $digest;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Login.php b/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Login.php
new file mode 100644
index 0000000..5295b65
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Login.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+
+
+/**
+ * Performs LOGIN authentication
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp_Auth_Login extends Zend_Mail_Protocol_Smtp
+{
+ /**
+ * LOGIN username
+ *
+ * @var string
+ */
+ protected $_username;
+
+
+ /**
+ * LOGIN password
+ *
+ * @var string
+ */
+ protected $_password;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host (Default: 127.0.0.1)
+ * @param int $port (Default: null)
+ * @param array $config Auth-specific parameters
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null, $config = null)
+ {
+ if (is_array($config)) {
+ if (isset($config['username'])) {
+ $this->_username = $config['username'];
+ }
+ if (isset($config['password'])) {
+ $this->_password = $config['password'];
+ }
+ }
+
+ parent::__construct($host, $port, $config);
+ }
+
+
+ /**
+ * Perform LOGIN authentication with supplied credentials
+ *
+ * @return void
+ */
+ public function auth()
+ {
+ // Ensure AUTH has not already been initiated.
+ parent::auth();
+
+ $this->_send('AUTH LOGIN');
+ $this->_expect(334);
+ $this->_send(base64_encode($this->_username));
+ $this->_expect(334);
+ $this->_send(base64_encode($this->_password));
+ $this->_expect(235);
+ $this->_auth = true;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Plain.php b/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Plain.php
new file mode 100644
index 0000000..abd788e
--- /dev/null
+++ b/library/vendor/Zend/Mail/Protocol/Smtp/Auth/Plain.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+
+
+/**
+ * Performs PLAIN authentication
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Protocol
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Protocol_Smtp_Auth_Plain extends Zend_Mail_Protocol_Smtp
+{
+ /**
+ * PLAIN username
+ *
+ * @var string
+ */
+ protected $_username;
+
+
+ /**
+ * PLAIN password
+ *
+ * @var string
+ */
+ protected $_password;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host (Default: 127.0.0.1)
+ * @param int $port (Default: null)
+ * @param array $config Auth-specific parameters
+ * @return void
+ */
+ public function __construct($host = '127.0.0.1', $port = null, $config = null)
+ {
+ if (is_array($config)) {
+ if (isset($config['username'])) {
+ $this->_username = $config['username'];
+ }
+ if (isset($config['password'])) {
+ $this->_password = $config['password'];
+ }
+ }
+
+ parent::__construct($host, $port, $config);
+ }
+
+
+ /**
+ * Perform PLAIN authentication with supplied credentials
+ *
+ * @return void
+ */
+ public function auth()
+ {
+ // Ensure AUTH has not already been initiated.
+ parent::auth();
+
+ $this->_send('AUTH PLAIN');
+ $this->_expect(334);
+ $this->_send(base64_encode("\0" . $this->_username . "\0" . $this->_password));
+ $this->_expect(235);
+ $this->_auth = true;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Storage.php b/library/vendor/Zend/Mail/Storage.php
new file mode 100644
index 0000000..b0eed12
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage
+{
+ // maildir and IMAP flags, using IMAP names, where possible to be able to distinguish between IMAP
+ // system flags and other flags
+ const FLAG_PASSED = 'Passed';
+ const FLAG_SEEN = '\Seen';
+ const FLAG_UNSEEN = '\Unseen';
+ const FLAG_ANSWERED = '\Answered';
+ const FLAG_FLAGGED = '\Flagged';
+ const FLAG_DELETED = '\Deleted';
+ const FLAG_DRAFT = '\Draft';
+ const FLAG_RECENT = '\Recent';
+}
diff --git a/library/vendor/Zend/Mail/Storage/Abstract.php b/library/vendor/Zend/Mail/Storage/Abstract.php
new file mode 100644
index 0000000..d522789
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Abstract.php
@@ -0,0 +1,364 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Mail_Storage_Abstract implements Countable, ArrayAccess, SeekableIterator
+{
+ /**
+ * class capabilities with default values
+ * @var array
+ */
+ protected $_has = array('uniqueid' => true,
+ 'delete' => false,
+ 'create' => false,
+ 'top' => false,
+ 'fetchPart' => true,
+ 'flags' => false);
+
+ /**
+ * current iteration position
+ * @var int
+ */
+ protected $_iterationPos = 0;
+
+ /**
+ * maximum iteration position (= message count)
+ * @var null|int
+ */
+ protected $_iterationMax = null;
+
+ /**
+ * used message class, change it in an extened class to extend the returned message class
+ * @var string
+ */
+ protected $_messageClass = 'Zend_Mail_Message';
+
+ /**
+ * Getter for has-properties. The standard has properties
+ * are: hasFolder, hasUniqueid, hasDelete, hasCreate, hasTop
+ *
+ * The valid values for the has-properties are:
+ * - true if a feature is supported
+ * - false if a feature is not supported
+ * - null is it's not yet known or it can't be know if a feature is supported
+ *
+ * @param string $var property name
+ * @return bool supported or not
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __get($var)
+ {
+ if (strpos($var, 'has') === 0) {
+ $var = strtolower(substr($var, 3));
+ return isset($this->_has[$var]) ? $this->_has[$var] : null;
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception($var . ' not found');
+ }
+
+
+ /**
+ * Get a full list of features supported by the specific mail lib and the server
+ *
+ * @return array list of features as array(featurename => true|false[|null])
+ */
+ public function getCapabilities()
+ {
+ return $this->_has;
+ }
+
+
+ /**
+ * Count messages messages in current box/folder
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function countMessages();
+
+
+ /**
+ * Get a list of messages with number and size
+ *
+ * @param int $id number of message
+ * @return int|array size of given message of list with all messages as array(num => size)
+ */
+ abstract public function getSize($id = 0);
+
+
+ /**
+ * Get a message with headers and body
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message
+ */
+ abstract public function getMessage($id);
+
+
+ /**
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ */
+ abstract public function getRawHeader($id, $part = null, $topLines = 0);
+
+ /**
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ */
+ abstract public function getRawContent($id, $part = null);
+
+ /**
+ * Create instance with parameters
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function __construct($params);
+
+
+ /**
+ * Destructor calls close() and therefore closes the resource.
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return null
+ */
+ abstract public function close();
+
+
+ /**
+ * Keep the resource alive.
+ *
+ * @return null
+ */
+ abstract public function noop();
+
+ /**
+ * delete a message from current box/folder
+ *
+ * @return null
+ */
+ abstract public function removeMessage($id);
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function getUniqueId($id = null);
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ abstract public function getNumberByUniqueId($id);
+
+ // interface implementations follows
+
+ /**
+ * Countable::count()
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->countMessages();
+ }
+
+
+ /**
+ * ArrayAccess::offsetExists()
+ *
+ * @param int $id
+ * @return boolean
+ */
+ public function offsetExists($id)
+ {
+ try {
+ if ($this->getMessage($id)) {
+ return true;
+ }
+ } catch(Zend_Mail_Storage_Exception $e) {}
+
+ return false;
+ }
+
+
+ /**
+ * ArrayAccess::offsetGet()
+ *
+ * @param int $id
+ * @return Zend_Mail_Message message object
+ */
+ public function offsetGet($id)
+ {
+ return $this->getMessage($id);
+ }
+
+
+ /**
+ * ArrayAccess::offsetSet()
+ *
+ * @param id $id
+ * @param mixed $value
+ * @throws Zend_Mail_Storage_Exception
+ * @return void
+ */
+ public function offsetSet($id, $value)
+ {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot write mail messages via array access');
+ }
+
+
+ /**
+ * ArrayAccess::offsetUnset()
+ *
+ * @param int $id
+ * @return boolean success
+ */
+ public function offsetUnset($id)
+ {
+ return $this->removeMessage($id);
+ }
+
+
+ /**
+ * Iterator::rewind()
+ *
+ * Rewind always gets the new count from the storage. Thus if you use
+ * the interfaces and your scripts take long you should use reset()
+ * from time to time.
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ $this->_iterationMax = $this->countMessages();
+ $this->_iterationPos = 1;
+ }
+
+
+ /**
+ * Iterator::current()
+ *
+ * @return Zend_Mail_Message current message
+ */
+ public function current()
+ {
+ return $this->getMessage($this->_iterationPos);
+ }
+
+
+ /**
+ * Iterator::key()
+ *
+ * @return int id of current position
+ */
+ public function key()
+ {
+ return $this->_iterationPos;
+ }
+
+
+ /**
+ * Iterator::next()
+ *
+ * @return void
+ */
+ public function next()
+ {
+ ++$this->_iterationPos;
+ }
+
+
+ /**
+ * Iterator::valid()
+ *
+ * @return boolean
+ */
+ public function valid()
+ {
+ if ($this->_iterationMax === null) {
+ $this->_iterationMax = $this->countMessages();
+ }
+ return $this->_iterationPos && $this->_iterationPos <= $this->_iterationMax;
+ }
+
+
+ /**
+ * SeekableIterator::seek()
+ *
+ * @param int $pos
+ * @return void
+ * @throws OutOfBoundsException
+ */
+ public function seek($pos)
+ {
+ if ($this->_iterationMax === null) {
+ $this->_iterationMax = $this->countMessages();
+ }
+
+ if ($pos > $this->_iterationMax) {
+ throw new OutOfBoundsException('this position does not exist');
+ }
+ $this->_iterationPos = $pos;
+ }
+
+}
diff --git a/library/vendor/Zend/Mail/Storage/Exception.php b/library/vendor/Zend/Mail/Storage/Exception.php
new file mode 100644
index 0000000..a9c9021
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Exception extends Zend_Mail_Exception
+{}
+
diff --git a/library/vendor/Zend/Mail/Storage/Folder.php b/library/vendor/Zend/Mail/Storage/Folder.php
new file mode 100644
index 0000000..19d0269
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Folder.php
@@ -0,0 +1,235 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Folder implements RecursiveIterator
+{
+ /**
+ * subfolders of folder array(localName => Zend_Mail_Storage_Folder folder)
+ * @var array
+ */
+ protected $_folders;
+
+ /**
+ * local name (name of folder in parent folder)
+ * @var string
+ */
+ protected $_localName;
+
+ /**
+ * global name (absolute name of folder)
+ * @var string
+ */
+ protected $_globalName;
+
+ /**
+ * folder is selectable if folder is able to hold messages, else it's just a parent folder
+ * @var bool
+ */
+ protected $_selectable = true;
+
+ /**
+ * create a new mail folder instance
+ *
+ * @param string $localName name of folder in current subdirectory
+ * @param string $globalName absolute name of folder
+ * @param bool $selectable if true folder holds messages, if false it's just a parent for subfolders
+ * @param array $folders init with given instances of Zend_Mail_Storage_Folder as subfolders
+ */
+ public function __construct($localName, $globalName = '', $selectable = true, array $folders = array())
+ {
+ $this->_localName = $localName;
+ $this->_globalName = $globalName ? $globalName : $localName;
+ $this->_selectable = $selectable;
+ $this->_folders = $folders;
+ }
+
+ /**
+ * implements RecursiveIterator::hasChildren()
+ *
+ * @return bool current element has children
+ */
+ public function hasChildren()
+ {
+ $current = $this->current();
+ return $current && $current instanceof Zend_Mail_Storage_Folder && !$current->isLeaf();
+ }
+
+ /**
+ * implements RecursiveIterator::getChildren()
+ *
+ * @return Zend_Mail_Storage_Folder same as self::current()
+ */
+ public function getChildren()
+ {
+ return $this->current();
+ }
+
+ /**
+ * implements Iterator::valid()
+ *
+ * @return bool check if there's a current element
+ */
+ public function valid()
+ {
+ return key($this->_folders) !== null;
+ }
+
+ /**
+ * implements Iterator::next()
+ *
+ * @return null
+ */
+ public function next()
+ {
+ next($this->_folders);
+ }
+
+ /**
+ * implements Iterator::key()
+ *
+ * @return string key/local name of current element
+ */
+ public function key()
+ {
+ return key($this->_folders);
+ }
+
+ /**
+ * implements Iterator::current()
+ *
+ * @return Zend_Mail_Storage_Folder current folder
+ */
+ public function current()
+ {
+ return current($this->_folders);
+ }
+
+ /**
+ * implements Iterator::rewind()
+ *
+ * @return null
+ */
+ public function rewind()
+ {
+ reset($this->_folders);
+ }
+
+ /**
+ * get subfolder named $name
+ *
+ * @param string $name wanted subfolder
+ * @return Zend_Mail_Storage_Folder folder named $folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __get($name)
+ {
+ if (!isset($this->_folders[$name])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("no subfolder named $name");
+ }
+
+ return $this->_folders[$name];
+ }
+
+ /**
+ * add or replace subfolder named $name
+ *
+ * @param string $name local name of subfolder
+ * @param Zend_Mail_Storage_Folder $folder instance for new subfolder
+ * @return null
+ */
+ public function __set($name, Zend_Mail_Storage_Folder $folder)
+ {
+ $this->_folders[$name] = $folder;
+ }
+
+ /**
+ * remove subfolder named $name
+ *
+ * @param string $name local name of subfolder
+ * @return null
+ */
+ public function __unset($name)
+ {
+ unset($this->_folders[$name]);
+ }
+
+ /**
+ * magic method for easy output of global name
+ *
+ * @return string global name of folder
+ */
+ public function __toString()
+ {
+ return (string)$this->getGlobalName();
+ }
+
+ /**
+ * get local name
+ *
+ * @return string local name
+ */
+ public function getLocalName()
+ {
+ return $this->_localName;
+ }
+
+ /**
+ * get global name
+ *
+ * @return string global name
+ */
+ public function getGlobalName()
+ {
+ return $this->_globalName;
+ }
+
+ /**
+ * is this folder selectable?
+ *
+ * @return bool selectable
+ */
+ public function isSelectable()
+ {
+ return $this->_selectable;
+ }
+
+ /**
+ * check if folder has no subfolder
+ *
+ * @return bool true if no subfolders
+ */
+ public function isLeaf()
+ {
+ return empty($this->_folders);
+ }
+}
diff --git a/library/vendor/Zend/Mail/Storage/Folder/Interface.php b/library/vendor/Zend/Mail/Storage/Folder/Interface.php
new file mode 100644
index 0000000..dc09205
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Folder/Interface.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Mail_Storage_Folder_Interface
+{
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ */
+ public function getFolders($rootFolder = null);
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function selectFolder($globalName);
+
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder();
+}
diff --git a/library/vendor/Zend/Mail/Storage/Folder/Maildir.php b/library/vendor/Zend/Mail/Storage/Folder/Maildir.php
new file mode 100644
index 0000000..9c9838e
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Folder/Maildir.php
@@ -0,0 +1,255 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Folder
+ */
+
+/**
+ * @see Zend_Mail_Storage_Folder_Interface
+ */
+
+/**
+ * @see Zend_Mail_Storage_Maildir
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Folder_Maildir extends Zend_Mail_Storage_Maildir implements Zend_Mail_Storage_Folder_Interface
+{
+ /**
+ * Zend_Mail_Storage_Folder root folder for folder structure
+ * @var Zend_Mail_Storage_Folder
+ */
+ protected $_rootFolder;
+
+ /**
+ * rootdir of folder structure
+ * @var string
+ */
+ protected $_rootdir;
+
+ /**
+ * name of current folder
+ * @var string
+ */
+ protected $_currentFolder;
+
+ /**
+ * delim char for subfolders
+ * @var string
+ */
+ protected $_delim;
+
+ /**
+ * Create instance with parameters
+ * Supported parameters are:
+ * - dirname rootdir of maildir structure
+ * - delim delim char for folder structur, default is '.'
+ * - folder intial selected folder, default is 'INBOX'
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!isset($params->dirname) || !is_dir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
+ }
+
+ $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+ $this->_delim = isset($params->delim) ? $params->delim : '.';
+
+ $this->_buildFolderTree();
+ $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX');
+ $this->_has['top'] = true;
+ $this->_has['flags'] = true;
+ }
+
+ /**
+ * find all subfolders and mbox files for folder structure
+ *
+ * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder.
+ * $parentFolder and $parentGlobalName are only used internally for recursion.
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _buildFolderTree()
+ {
+ $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false);
+ $this->_rootFolder->INBOX = new Zend_Mail_Storage_Folder('INBOX', 'INBOX', true);
+
+ $dh = @opendir($this->_rootdir);
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("can't read folders in maildir");
+ }
+ $dirs = array();
+ while (($entry = readdir($dh)) !== false) {
+ // maildir++ defines folders must start with .
+ if ($entry[0] != '.' || $entry == '.' || $entry == '..') {
+ continue;
+ }
+ if ($this->_isMaildir($this->_rootdir . $entry)) {
+ $dirs[] = $entry;
+ }
+ }
+ closedir($dh);
+
+ sort($dirs);
+ $stack = array(null);
+ $folderStack = array(null);
+ $parentFolder = $this->_rootFolder;
+ $parent = '.';
+
+ foreach ($dirs as $dir) {
+ do {
+ if (strpos($dir, $parent) === 0) {
+ $local = substr($dir, strlen($parent));
+ if (strpos($local, $this->_delim) !== false) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('error while reading maildir');
+ }
+ array_push($stack, $parent);
+ $parent = $dir . $this->_delim;
+ $folder = new Zend_Mail_Storage_Folder($local, substr($dir, 1), true);
+ $parentFolder->$local = $folder;
+ array_push($folderStack, $parentFolder);
+ $parentFolder = $folder;
+ break;
+ } else if ($stack) {
+ $parent = array_pop($stack);
+ $parentFolder = array_pop($folderStack);
+ }
+ } while ($stack);
+ if (!$stack) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('error while reading maildir');
+ }
+ }
+ }
+
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getFolders($rootFolder = null)
+ {
+ if (!$rootFolder || $rootFolder == 'INBOX') {
+ return $this->_rootFolder;
+ }
+
+ // rootdir is same as INBOX in maildir
+ if (strpos($rootFolder, 'INBOX' . $this->_delim) === 0) {
+ $rootFolder = substr($rootFolder, 6);
+ }
+ $currentFolder = $this->_rootFolder;
+ $subname = trim($rootFolder, $this->_delim);
+ while ($currentFolder) {
+ @list($entry, $subname) = @explode($this->_delim, $subname, 2);
+ $currentFolder = $currentFolder->$entry;
+ if (!$subname) {
+ break;
+ }
+ }
+
+ if ($currentFolder->getGlobalName() != rtrim($rootFolder, $this->_delim)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("folder $rootFolder not found");
+ }
+ return $currentFolder;
+ }
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function selectFolder($globalName)
+ {
+ $this->_currentFolder = (string)$globalName;
+
+ // getting folder from folder tree for validation
+ $folder = $this->getFolders($this->_currentFolder);
+
+ try {
+ $this->_openMaildir($this->_rootdir . '.' . $folder->getGlobalName());
+ } catch(Zend_Mail_Storage_Exception $e) {
+ // check what went wrong
+ if (!$folder->isSelectable()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable", 0, $e);
+ }
+ // seems like file has vanished; rebuilding folder tree - but it's still an exception
+ $this->_buildFolderTree($this->_rootdir);
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('seems like the maildir has vanished, I\'ve rebuild the ' .
+ 'folder tree, search for an other folder and try again', 0, $e);
+ }
+ }
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder()
+ {
+ return $this->_currentFolder;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Storage/Folder/Mbox.php b/library/vendor/Zend/Mail/Storage/Folder/Mbox.php
new file mode 100644
index 0000000..670a6ff
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Folder/Mbox.php
@@ -0,0 +1,255 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Folder
+ */
+
+/**
+ * @see Zend_Mail_Storage_Folder_Interface
+ */
+
+/**
+ * @see Zend_Mail_Storage_Mbox
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Folder_Mbox extends Zend_Mail_Storage_Mbox implements Zend_Mail_Storage_Folder_Interface
+{
+ /**
+ * Zend_Mail_Storage_Folder root folder for folder structure
+ * @var Zend_Mail_Storage_Folder
+ */
+ protected $_rootFolder;
+
+ /**
+ * rootdir of folder structure
+ * @var string
+ */
+ protected $_rootdir;
+
+ /**
+ * name of current folder
+ * @var string
+ */
+ protected $_currentFolder;
+
+ /**
+ * Create instance with parameters
+ *
+ * Disallowed parameters are:
+ * - filename use Zend_Mail_Storage_Mbox for a single file
+ * Supported parameters are:
+ * - dirname rootdir of mbox structure
+ * - folder intial selected folder, default is 'INBOX'
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (isset($params->filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('use Zend_Mail_Storage_Mbox for a single file');
+ }
+
+ if (!isset($params->dirname) || !is_dir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
+ }
+
+ $this->_rootdir = rtrim($params->dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+ $this->_buildFolderTree($this->_rootdir);
+ $this->selectFolder(!empty($params->folder) ? $params->folder : 'INBOX');
+ $this->_has['top'] = true;
+ $this->_has['uniqueid'] = false;
+ }
+
+ /**
+ * find all subfolders and mbox files for folder structure
+ *
+ * Result is save in Zend_Mail_Storage_Folder instances with the root in $this->_rootFolder.
+ * $parentFolder and $parentGlobalName are only used internally for recursion.
+ *
+ * @param string $currentDir call with root dir, also used for recursion.
+ * @param Zend_Mail_Storage_Folder|null $parentFolder used for recursion
+ * @param string $parentGlobalName used for rescursion
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _buildFolderTree($currentDir, $parentFolder = null, $parentGlobalName = '')
+ {
+ if (!$parentFolder) {
+ $this->_rootFolder = new Zend_Mail_Storage_Folder('/', '/', false);
+ $parentFolder = $this->_rootFolder;
+ }
+
+ $dh = @opendir($currentDir);
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("can't read dir $currentDir");
+ }
+ while (($entry = readdir($dh)) !== false) {
+ // ignore hidden files for mbox
+ if ($entry[0] == '.') {
+ continue;
+ }
+ $absoluteEntry = $currentDir . $entry;
+ $globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry;
+ if (is_file($absoluteEntry) && $this->_isMboxFile($absoluteEntry)) {
+ $parentFolder->$entry = new Zend_Mail_Storage_Folder($entry, $globalName);
+ continue;
+ }
+ if (!is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) {
+ continue;
+ }
+ $folder = new Zend_Mail_Storage_Folder($entry, $globalName, false);
+ $parentFolder->$entry = $folder;
+ $this->_buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName);
+ }
+
+ closedir($dh);
+ }
+
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getFolders($rootFolder = null)
+ {
+ if (!$rootFolder) {
+ return $this->_rootFolder;
+ }
+
+ $currentFolder = $this->_rootFolder;
+ $subname = trim($rootFolder, DIRECTORY_SEPARATOR);
+ while ($currentFolder) {
+ @list($entry, $subname) = @explode(DIRECTORY_SEPARATOR, $subname, 2);
+ $currentFolder = $currentFolder->$entry;
+ if (!$subname) {
+ break;
+ }
+ }
+
+ if ($currentFolder->getGlobalName() != DIRECTORY_SEPARATOR . trim($rootFolder, DIRECTORY_SEPARATOR)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("folder $rootFolder not found");
+ }
+ return $currentFolder;
+ }
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function selectFolder($globalName)
+ {
+ $this->_currentFolder = (string)$globalName;
+
+ // getting folder from folder tree for validation
+ $folder = $this->getFolders($this->_currentFolder);
+
+ try {
+ $this->_openMboxFile($this->_rootdir . $folder->getGlobalName());
+ } catch(Zend_Mail_Storage_Exception $e) {
+ // check what went wrong
+ if (!$folder->isSelectable()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("{$this->_currentFolder} is not selectable", 0, $e);
+ }
+ // seems like file has vanished; rebuilding folder tree - but it's still an exception
+ $this->_buildFolderTree($this->_rootdir);
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('seems like the mbox file has vanished, I\'ve rebuild the ' .
+ 'folder tree, search for an other folder and try again', 0, $e);
+ }
+ }
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder()
+ {
+ return $this->_currentFolder;
+ }
+
+ /**
+ * magic method for serialize()
+ *
+ * with this method you can cache the mbox class
+ *
+ * @return array name of variables
+ */
+ public function __sleep()
+ {
+ return array_merge(parent::__sleep(), array('_currentFolder', '_rootFolder', '_rootdir'));
+ }
+
+ /**
+ * magic method for unserialize()
+ *
+ * with this method you can cache the mbox class
+ *
+ * @return null
+ */
+ public function __wakeup()
+ {
+ // if cache is stall selectFolder() rebuilds the tree on error
+ parent::__wakeup();
+ }
+}
diff --git a/library/vendor/Zend/Mail/Storage/Imap.php b/library/vendor/Zend/Mail/Storage/Imap.php
new file mode 100644
index 0000000..2678025
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Imap.php
@@ -0,0 +1,620 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+
+/**
+ * @see Zend_Mail_Protocol_Imap
+ */
+
+/**
+ * @see Zend_Mail_Storage_Writable_Interface
+ */
+
+/**
+ * @see Zend_Mail_Storage_Folder_Interface
+ */
+
+/**
+ * @see Zend_Mail_Storage_Folder
+ */
+
+/**
+ * @see Zend_Mail_Message
+ */
+
+/**
+ * @see Zend_Mail_Storage
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Imap extends Zend_Mail_Storage_Abstract
+ implements Zend_Mail_Storage_Folder_Interface, Zend_Mail_Storage_Writable_Interface
+{
+ // TODO: with an internal cache we could optimize this class, or create an extra class with
+ // such optimizations. Especially the various fetch calls could be combined to one cache call
+
+ /**
+ * protocol handler
+ * @var null|Zend_Mail_Protocol_Imap
+ */
+ protected $_protocol;
+
+ /**
+ * name of current folder
+ * @var string
+ */
+ protected $_currentFolder = '';
+
+ /**
+ * imap flags to constants translation
+ * @var array
+ */
+ protected static $_knownFlags = array('\Passed' => Zend_Mail_Storage::FLAG_PASSED,
+ '\Answered' => Zend_Mail_Storage::FLAG_ANSWERED,
+ '\Seen' => Zend_Mail_Storage::FLAG_SEEN,
+ '\Unseen' => Zend_Mail_Storage::FLAG_UNSEEN,
+ '\Deleted' => Zend_Mail_Storage::FLAG_DELETED,
+ '\Draft' => Zend_Mail_Storage::FLAG_DRAFT,
+ '\Flagged' => Zend_Mail_Storage::FLAG_FLAGGED);
+
+ /**
+ * map flags to search criterias
+ * @var array
+ */
+ protected static $_searchFlags = array('\Recent' => 'RECENT',
+ '\Answered' => 'ANSWERED',
+ '\Seen' => 'SEEN',
+ '\Unseen' => 'UNSEEN',
+ '\Deleted' => 'DELETED',
+ '\Draft' => 'DRAFT',
+ '\Flagged' => 'FLAGGED');
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function countMessages($flags = null)
+ {
+ if (!$this->_currentFolder) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('No selected folder to count');
+ }
+
+ if ($flags === null) {
+ return count($this->_protocol->search(array('ALL')));
+ }
+
+ $params = array();
+ foreach ((array)$flags as $flag) {
+ if (isset(self::$_searchFlags[$flag])) {
+ $params[] = self::$_searchFlags[$flag];
+ } else {
+ $params[] = 'KEYWORD';
+ $params[] = $this->_protocol->escapeString($flag);
+ }
+ }
+ return count($this->_protocol->search($params));
+ }
+
+ /**
+ * get a list of messages with number and size
+ *
+ * @param int $id number of message
+ * @return int|array size of given message of list with all messages as array(num => size)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getSize($id = 0)
+ {
+ if ($id) {
+ return $this->_protocol->fetch('RFC822.SIZE', $id);
+ }
+ return $this->_protocol->fetch('RFC822.SIZE', 1, INF);
+ }
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getMessage($id)
+ {
+ $data = $this->_protocol->fetch(array('FLAGS', 'RFC822.HEADER'), $id);
+ $header = $data['RFC822.HEADER'];
+
+ $flags = array();
+ foreach ($data['FLAGS'] as $flag) {
+ $flags[] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
+ }
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ // TODO: toplines
+ return $this->_protocol->fetch('RFC822.HEADER', $id);
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ return $this->_protocol->fetch('RFC822.TEXT', $id);
+ }
+
+ /**
+ * create instance with parameters
+ * Supported paramters are
+ * - user username
+ * - host hostname or ip address of IMAP server [optional, default = 'localhost']
+ * - password password for user 'username' [optional, default = '']
+ * - port port for IMAP server [optional, default = 110]
+ * - ssl 'SSL' or 'TLS' for secure sockets
+ * - folder select this folder [optional, default = 'INBOX']
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ $this->_has['flags'] = true;
+
+ if ($params instanceof Zend_Mail_Protocol_Imap) {
+ $this->_protocol = $params;
+ try {
+ $this->selectFolder('INBOX');
+ } catch(Zend_Mail_Storage_Exception $e) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot select INBOX, is this a valid transport?', 0, $e);
+ }
+ return;
+ }
+
+ if (!isset($params->user)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('need at least user in params');
+ }
+
+ $host = isset($params->host) ? $params->host : 'localhost';
+ $password = isset($params->password) ? $params->password : '';
+ $port = isset($params->port) ? $params->port : null;
+ $ssl = isset($params->ssl) ? $params->ssl : false;
+
+ $this->_protocol = new Zend_Mail_Protocol_Imap();
+ $this->_protocol->connect($host, $port, $ssl);
+ if (!$this->_protocol->login($params->user, $password)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot login, user or password wrong');
+ }
+ $this->selectFolder(isset($params->folder) ? $params->folder : 'INBOX');
+ }
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return null
+ */
+ public function close()
+ {
+ $this->_currentFolder = '';
+ $this->_protocol->logout();
+ }
+
+ /**
+ * Keep the server busy.
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function noop()
+ {
+ if (!$this->_protocol->noop()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('could not do nothing');
+ }
+ }
+
+ /**
+ * Remove a message from server. If you're doing that from a web enviroment
+ * you should be careful and use a uniqueid as parameter if possible to
+ * identify the message.
+ *
+ * @param int $id number of message
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ if (!$this->_protocol->store(array(Zend_Mail_Storage::FLAG_DELETED), $id, null, '+')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot set deleted flag');
+ }
+ // TODO: expunge here or at close? we can handle an error here better and are more fail safe
+ if (!$this->_protocol->expunge()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('message marked as deleted, but could not expunge');
+ }
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if ($id) {
+ return $this->_protocol->fetch('UID', $id);
+ }
+
+ return $this->_protocol->fetch('UID', 1, INF);
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ // TODO: use search to find number directly
+ $ids = $this->getUniqueId();
+ foreach ($ids as $k => $v) {
+ if ($v == $id) {
+ return $k;
+ }
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('unique id not found');
+ }
+
+
+ /**
+ * get root folder or given folder
+ *
+ * @param string $rootFolder get folder structure for given folder, else root
+ * @return Zend_Mail_Storage_Folder root or wanted folder
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getFolders($rootFolder = null)
+ {
+ $folders = $this->_protocol->listMailbox((string)$rootFolder);
+ if (!$folders) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('folder not found');
+ }
+
+ ksort($folders, SORT_STRING);
+ $root = new Zend_Mail_Storage_Folder('/', '/', false);
+ $stack = array(null);
+ $folderStack = array(null);
+ $parentFolder = $root;
+ $parent = '';
+
+ foreach ($folders as $globalName => $data) {
+ do {
+ if (!$parent || strpos($globalName, $parent) === 0) {
+ $pos = strrpos($globalName, $data['delim']);
+ if ($pos === false) {
+ $localName = $globalName;
+ } else {
+ $localName = substr($globalName, $pos + 1);
+ }
+ $selectable = !$data['flags'] || !in_array('\\Noselect', $data['flags']);
+
+ array_push($stack, $parent);
+ $parent = $globalName . $data['delim'];
+ $folder = new Zend_Mail_Storage_Folder($localName, $globalName, $selectable);
+ $parentFolder->$localName = $folder;
+ array_push($folderStack, $parentFolder);
+ $parentFolder = $folder;
+ break;
+ } else if ($stack) {
+ $parent = array_pop($stack);
+ $parentFolder = array_pop($folderStack);
+ }
+ } while ($stack);
+ if (!$stack) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('error while constructing folder tree');
+ }
+ }
+
+ return $root;
+ }
+
+ /**
+ * select given folder
+ *
+ * folder must be selectable!
+ *
+ * @param Zend_Mail_Storage_Folder|string $globalName global name of folder or instance for subfolder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function selectFolder($globalName)
+ {
+ $this->_currentFolder = $globalName;
+ if (!$this->_protocol->select($this->_currentFolder)) {
+ $this->_currentFolder = '';
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot change folder, maybe it does not exist');
+ }
+ }
+
+
+ /**
+ * get Zend_Mail_Storage_Folder instance for current folder
+ *
+ * @return Zend_Mail_Storage_Folder instance of current folder
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getCurrentFolder()
+ {
+ return $this->_currentFolder;
+ }
+
+ /**
+ * create a new folder
+ *
+ * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
+ * may be used as parent or which chars may be used in the folder name
+ *
+ * @param string $name global name of folder, local name if $parentFolder is set
+ * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function createFolder($name, $parentFolder = null)
+ {
+ // TODO: we assume / as the hierarchy delim - need to get that from the folder class!
+ if ($parentFolder instanceof Zend_Mail_Storage_Folder) {
+ $folder = $parentFolder->getGlobalName() . '/' . $name;
+ } else if ($parentFolder != null) {
+ $folder = $parentFolder . '/' . $name;
+ } else {
+ $folder = $name;
+ }
+
+ if (!$this->_protocol->create($folder)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot create folder');
+ }
+ }
+
+ /**
+ * remove a folder
+ *
+ * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeFolder($name)
+ {
+ if ($name instanceof Zend_Mail_Storage_Folder) {
+ $name = $name->getGlobalName();
+ }
+
+ if (!$this->_protocol->delete($name)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot delete folder');
+ }
+ }
+
+ /**
+ * rename and/or move folder
+ *
+ * The new name has the same restrictions as in createFolder()
+ *
+ * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
+ * @param string $newName new global name of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function renameFolder($oldName, $newName)
+ {
+ if ($oldName instanceof Zend_Mail_Storage_Folder) {
+ $oldName = $oldName->getGlobalName();
+ }
+
+ if (!$this->_protocol->rename($oldName, $newName)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot rename folder');
+ }
+ }
+
+ /**
+ * append a new message to mail storage
+ *
+ * @param string $message message as string or instance of message class
+ * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
+ * @param null|array $flags set flags for new message, else a default set is used
+ * @throws Zend_Mail_Storage_Exception
+ */
+ // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
+ public function appendMessage($message, $folder = null, $flags = null)
+ {
+ if ($folder === null) {
+ $folder = $this->_currentFolder;
+ }
+
+ if ($flags === null) {
+ $flags = array(Zend_Mail_Storage::FLAG_SEEN);
+ }
+
+ // TODO: handle class instances for $message
+ if (!$this->_protocol->append($folder, $message, $flags)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot create message, please check if the folder exists and your flags');
+ }
+ }
+
+ /**
+ * copy an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function copyMessage($id, $folder)
+ {
+ if (!$this->_protocol->copy($folder, $id)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot copy message, does the folder exist?');
+ }
+ }
+
+ /**
+ * move an existing message
+ *
+ * NOTE: imap has no native move command, thus it's emulated with copy and delete
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function moveMessage($id, $folder) {
+ $this->copyMessage($id, $folder);
+ $this->removeMessage($id);
+ }
+
+ /**
+ * set flags for message
+ *
+ * NOTE: this method can't set the recent flag.
+ *
+ * @param int $id number of message
+ * @param array $flags new flags for message
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function setFlags($id, $flags)
+ {
+ if (!$this->_protocol->store($flags, $id)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot set flags, have you tried to set the recent flag or special chars?');
+ }
+ }
+}
+
diff --git a/library/vendor/Zend/Mail/Storage/Maildir.php b/library/vendor/Zend/Mail/Storage/Maildir.php
new file mode 100644
index 0000000..78c983b
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Maildir.php
@@ -0,0 +1,462 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+
+/**
+ * @see Zend_Mail_Message_File
+ */
+
+/**
+ * @see Zend_Mail_Storage
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Maildir extends Zend_Mail_Storage_Abstract
+{
+ /**
+ * used message class, change it in an extened class to extend the returned message class
+ * @var string
+ */
+ protected $_messageClass = 'Zend_Mail_Message_File';
+
+ /**
+ * data of found message files in maildir dir
+ * @var array
+ */
+ protected $_files = array();
+
+ /**
+ * known flag chars in filenames
+ *
+ * This list has to be in alphabetical order for setFlags()
+ *
+ * @var array
+ */
+ protected static $_knownFlags = array('D' => Zend_Mail_Storage::FLAG_DRAFT,
+ 'F' => Zend_Mail_Storage::FLAG_FLAGGED,
+ 'P' => Zend_Mail_Storage::FLAG_PASSED,
+ 'R' => Zend_Mail_Storage::FLAG_ANSWERED,
+ 'S' => Zend_Mail_Storage::FLAG_SEEN,
+ 'T' => Zend_Mail_Storage::FLAG_DELETED);
+
+ // TODO: getFlags($id) for fast access if headers are not needed (i.e. just setting flags)?
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function countMessages($flags = null)
+ {
+ if ($flags === null) {
+ return count($this->_files);
+ }
+
+ $count = 0;
+ if (!is_array($flags)) {
+ foreach ($this->_files as $file) {
+ if (isset($file['flaglookup'][$flags])) {
+ ++$count;
+ }
+ }
+ return $count;
+ }
+
+ $flags = array_flip($flags);
+ foreach ($this->_files as $file) {
+ foreach ($flags as $flag => $v) {
+ if (!isset($file['flaglookup'][$flag])) {
+ continue 2;
+ }
+ }
+ ++$count;
+ }
+ return $count;
+ }
+
+ /**
+ * Get one or all fields from file structure. Also checks if message is valid
+ *
+ * @param int $id message number
+ * @param string|null $field wanted field
+ * @return string|array wanted field or all fields as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _getFileData($id, $field = null)
+ {
+ if (!isset($this->_files[$id - 1])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('id does not exist');
+ }
+
+ if (!$field) {
+ return $this->_files[$id - 1];
+ }
+
+ if (!isset($this->_files[$id - 1][$field])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('field does not exist');
+ }
+
+ return $this->_files[$id - 1][$field];
+ }
+
+ /**
+ * Get a list of messages with number and size
+ *
+ * @param int|null $id number of message or null for all messages
+ * @return int|array size of given message of list with all messages as array(num => size)
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getSize($id = null)
+ {
+ if ($id !== null) {
+ $filedata = $this->_getFileData($id);
+ return isset($filedata['size']) ? $filedata['size'] : filesize($filedata['filename']);
+ }
+
+ $result = array();
+ foreach ($this->_files as $num => $data) {
+ $result[$num + 1] = isset($data['size']) ? $data['size'] : filesize($data['filename']);
+ }
+
+ return $result;
+ }
+
+
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message_File
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getMessage($id)
+ {
+ // TODO that's ugly, would be better to let the message class decide
+ if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) {
+ return new $this->_messageClass(array('file' => $this->_getFileData($id, 'filename'),
+ 'flags' => $this->_getFileData($id, 'flags')));
+ }
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $this->getRawHeader($id),
+ 'flags' => $this->_getFileData($id, 'flags')));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ $fh = fopen($this->_getFileData($id, 'filename'), 'r');
+
+ $content = '';
+ while (!feof($fh)) {
+ $line = fgets($fh);
+ if (!trim($line)) {
+ break;
+ }
+ $content .= $line;
+ }
+
+ fclose($fh);
+ return $content;
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ $fh = fopen($this->_getFileData($id, 'filename'), 'r');
+
+ while (!feof($fh)) {
+ $line = fgets($fh);
+ if (!trim($line)) {
+ break;
+ }
+ }
+
+ $content = stream_get_contents($fh);
+ fclose($fh);
+ return $content;
+ }
+
+ /**
+ * Create instance with parameters
+ * Supported parameters are:
+ * - dirname dirname of mbox file
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!isset($params->dirname) || !is_dir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('no valid dirname given in params');
+ }
+
+ if (!$this->_isMaildir($params->dirname)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('invalid maildir given');
+ }
+
+ $this->_has['top'] = true;
+ $this->_has['flags'] = true;
+ $this->_openMaildir($params->dirname);
+ }
+
+ /**
+ * check if a given dir is a valid maildir
+ *
+ * @param string $dirname name of dir
+ * @return bool dir is valid maildir
+ */
+ protected function _isMaildir($dirname)
+ {
+ if (file_exists($dirname . '/new') && !is_dir($dirname . '/new')) {
+ return false;
+ }
+ if (file_exists($dirname . '/tmp') && !is_dir($dirname . '/tmp')) {
+ return false;
+ }
+ return is_dir($dirname . '/cur');
+ }
+
+ /**
+ * open given dir as current maildir
+ *
+ * @param string $dirname name of maildir
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _openMaildir($dirname)
+ {
+ if ($this->_files) {
+ $this->close();
+ }
+
+ $dh = @opendir($dirname . '/cur/');
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot open maildir');
+ }
+ $this->_getMaildirFiles($dh, $dirname . '/cur/');
+ closedir($dh);
+
+ $dh = @opendir($dirname . '/new/');
+ if ($dh) {
+ $this->_getMaildirFiles($dh, $dirname . '/new/', array(Zend_Mail_Storage::FLAG_RECENT));
+ closedir($dh);
+ } else if (file_exists($dirname . '/new/')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot read recent mails in maildir');
+ }
+ }
+
+ /**
+ * find all files in opened dir handle and add to maildir files
+ *
+ * @param resource $dh dir handle used for search
+ * @param string $dirname dirname of dir in $dh
+ * @param array $default_flags default flags for given dir
+ * @return null
+ */
+ protected function _getMaildirFiles($dh, $dirname, $default_flags = array())
+ {
+ while (($entry = readdir($dh)) !== false) {
+ if ($entry[0] == '.' || !is_file($dirname . $entry)) {
+ continue;
+ }
+
+ @list($uniq, $info) = explode(':', $entry, 2);
+ @list(,$size) = explode(',', $uniq, 2);
+ if ($size && $size[0] == 'S' && $size[1] == '=') {
+ $size = substr($size, 2);
+ }
+ if (!ctype_digit($size)) {
+ $size = null;
+ }
+ @list($version, $flags) = explode(',', $info, 2);
+ if ($version != 2) {
+ $flags = '';
+ }
+
+ $named_flags = $default_flags;
+ $length = strlen($flags);
+ for ($i = 0; $i < $length; ++$i) {
+ $flag = $flags[$i];
+ $named_flags[$flag] = isset(self::$_knownFlags[$flag]) ? self::$_knownFlags[$flag] : $flag;
+ }
+
+ $data = array('uniq' => $uniq,
+ 'flags' => $named_flags,
+ 'flaglookup' => array_flip($named_flags),
+ 'filename' => $dirname . $entry);
+ if ($size !== null) {
+ $data['size'] = (int)$size;
+ }
+ $this->_files[] = $data;
+ }
+ }
+
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return void
+ */
+ public function close()
+ {
+ $this->_files = array();
+ }
+
+
+ /**
+ * Waste some CPU cycles doing nothing.
+ *
+ * @return void
+ */
+ public function noop()
+ {
+ return true;
+ }
+
+
+ /**
+ * stub for not supported message deletion
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('maildir is (currently) read-only');
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if ($id) {
+ return $this->_getFileData($id, 'uniq');
+ }
+
+ $ids = array();
+ foreach ($this->_files as $num => $file) {
+ $ids[$num + 1] = $file['uniq'];
+ }
+ return $ids;
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ foreach ($this->_files as $num => $file) {
+ if ($file['uniq'] == $id) {
+ return $num + 1;
+ }
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('unique id not found');
+ }
+}
diff --git a/library/vendor/Zend/Mail/Storage/Mbox.php b/library/vendor/Zend/Mail/Storage/Mbox.php
new file mode 100644
index 0000000..5f5bffb
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Mbox.php
@@ -0,0 +1,437 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Loader
+ * May be used in constructor, but commented out for now
+ */
+// require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+
+/**
+ * @see Zend_Mail_Message_File
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Mbox extends Zend_Mail_Storage_Abstract
+{
+ /**
+ * file handle to mbox file
+ * @var null|resource
+ */
+ protected $_fh;
+
+ /**
+ * filename of mbox file for __wakeup
+ * @var string
+ */
+ protected $_filename;
+
+ /**
+ * modification date of mbox file for __wakeup
+ * @var int
+ */
+ protected $_filemtime;
+
+ /**
+ * start and end position of messages as array('start' => start, 'seperator' => headersep, 'end' => end)
+ * @var array
+ */
+ protected $_positions;
+
+ /**
+ * used message class, change it in an extened class to extend the returned message class
+ * @var string
+ */
+ protected $_messageClass = 'Zend_Mail_Message_File';
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function countMessages()
+ {
+ return count($this->_positions);
+ }
+
+
+ /**
+ * Get a list of messages with number and size
+ *
+ * @param int|null $id number of message or null for all messages
+ * @return int|array size of given message of list with all messages as array(num => size)
+ */
+ public function getSize($id = 0)
+ {
+ if ($id) {
+ $pos = $this->_positions[$id - 1];
+ return $pos['end'] - $pos['start'];
+ }
+
+ $result = array();
+ foreach ($this->_positions as $num => $pos) {
+ $result[$num + 1] = $pos['end'] - $pos['start'];
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Get positions for mail message or throw exeption if id is invalid
+ *
+ * @param int $id number of message
+ * @return array positions as in _positions
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _getPos($id)
+ {
+ if (!isset($this->_positions[$id - 1])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('id does not exist');
+ }
+
+ return $this->_positions[$id - 1];
+ }
+
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message_File
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getMessage($id)
+ {
+ // TODO that's ugly, would be better to let the message class decide
+ if (strtolower($this->_messageClass) == 'zend_mail_message_file' || is_subclass_of($this->_messageClass, 'zend_mail_message_file')) {
+ // TODO top/body lines
+ $messagePos = $this->_getPos($id);
+ return new $this->_messageClass(array('file' => $this->_fh, 'startPos' => $messagePos['start'],
+ 'endPos' => $messagePos['end']));
+ }
+
+ $bodyLines = 0; // TODO: need a way to change that
+
+ $message = $this->getRawHeader($id);
+ // file pointer is after headers now
+ if ($bodyLines) {
+ $message .= "\n";
+ while ($bodyLines-- && ftell($this->_fh) < $this->_positions[$id - 1]['end']) {
+ $message .= fgets($this->_fh);
+ }
+ }
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+ $messagePos = $this->_getPos($id);
+ // TODO: toplines
+ return stream_get_contents($this->_fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']);
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+ $messagePos = $this->_getPos($id);
+ return stream_get_contents($this->_fh, $messagePos['end'] - $messagePos['separator'], $messagePos['separator']);
+ }
+
+ /**
+ * Create instance with parameters
+ * Supported parameters are:
+ * - filename filename of mbox file
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!isset($params->filename) /* || Zend_Loader::isReadable($params['filename']) */) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('no valid filename given in params');
+ }
+
+ $this->_openMboxFile($params->filename);
+ $this->_has['top'] = true;
+ $this->_has['uniqueid'] = false;
+ }
+
+ /**
+ * check if given file is a mbox file
+ *
+ * if $file is a resource its file pointer is moved after the first line
+ *
+ * @param resource|string $file stream resource of name of file
+ * @param bool $fileIsString file is string or resource
+ * @return bool file is mbox file
+ */
+ protected function _isMboxFile($file, $fileIsString = true)
+ {
+ if ($fileIsString) {
+ $file = @fopen($file, 'r');
+ if (!$file) {
+ return false;
+ }
+ } else {
+ fseek($file, 0);
+ }
+
+ $result = false;
+
+ $line = fgets($file);
+ if (strpos($line, 'From ') === 0) {
+ $result = true;
+ }
+
+ if ($fileIsString) {
+ @fclose($file);
+ }
+
+ return $result;
+ }
+
+ /**
+ * open given file as current mbox file
+ *
+ * @param string $filename filename of mbox file
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _openMboxFile($filename)
+ {
+ if ($this->_fh) {
+ $this->close();
+ }
+
+ $this->_fh = @fopen($filename, 'r');
+ if (!$this->_fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot open mbox file');
+ }
+ $this->_filename = $filename;
+ $this->_filemtime = filemtime($this->_filename);
+
+ if (!$this->_isMboxFile($this->_fh, false)) {
+ @fclose($this->_fh);
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('file is not a valid mbox format');
+ }
+
+ $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0);
+ while (($line = fgets($this->_fh)) !== false) {
+ if (strpos($line, 'From ') === 0) {
+ $messagePos['end'] = ftell($this->_fh) - strlen($line) - 2; // + newline
+ if (!$messagePos['separator']) {
+ $messagePos['separator'] = $messagePos['end'];
+ }
+ $this->_positions[] = $messagePos;
+ $messagePos = array('start' => ftell($this->_fh), 'separator' => 0, 'end' => 0);
+ }
+ if (!$messagePos['separator'] && !trim($line)) {
+ $messagePos['separator'] = ftell($this->_fh);
+ }
+ }
+
+ $messagePos['end'] = ftell($this->_fh);
+ if (!$messagePos['separator']) {
+ $messagePos['separator'] = $messagePos['end'];
+ }
+ $this->_positions[] = $messagePos;
+ }
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return void
+ */
+ public function close()
+ {
+ @fclose($this->_fh);
+ $this->_positions = array();
+ }
+
+
+ /**
+ * Waste some CPU cycles doing nothing.
+ *
+ * @return void
+ */
+ public function noop()
+ {
+ return true;
+ }
+
+
+ /**
+ * stub for not supported message deletion
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('mbox is read-only');
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * Mbox does not support unique ids (yet) - it's always the same as the message number.
+ * That shouldn't be a problem, because we can't change mbox files. Therefor the message
+ * number is save enough.
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if ($id) {
+ // check if id exists
+ $this->_getPos($id);
+ return $id;
+ }
+
+ $range = range(1, $this->countMessages());
+ return array_combine($range, $range);
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ // check if id exists
+ $this->_getPos($id);
+ return $id;
+ }
+
+ /**
+ * magic method for serialize()
+ *
+ * with this method you can cache the mbox class
+ *
+ * @return array name of variables
+ */
+ public function __sleep()
+ {
+ return array('_filename', '_positions', '_filemtime');
+ }
+
+ /**
+ * magic method for unserialize()
+ *
+ * with this method you can cache the mbox class
+ * for cache validation the mtime of the mbox file is used
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __wakeup()
+ {
+ if ($this->_filemtime != @filemtime($this->_filename)) {
+ $this->close();
+ $this->_openMboxFile($this->_filename);
+ } else {
+ $this->_fh = @fopen($this->_filename, 'r');
+ if (!$this->_fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot open mbox file');
+ }
+ }
+ }
+
+}
diff --git a/library/vendor/Zend/Mail/Storage/Pop3.php b/library/vendor/Zend/Mail/Storage/Pop3.php
new file mode 100644
index 0000000..5da6974
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Pop3.php
@@ -0,0 +1,321 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Abstract
+ */
+
+/**
+ * @see Zend_Mail_Protocol_Pop3
+ */
+
+/**
+ * @see Zend_Mail_Message
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Pop3 extends Zend_Mail_Storage_Abstract
+{
+ /**
+ * protocol handler
+ * @var null|Zend_Mail_Protocol_Pop3
+ */
+ protected $_protocol;
+
+
+ /**
+ * Count messages all messages in current box
+ *
+ * @return int number of messages
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function countMessages()
+ {
+ $this->_protocol->status($count, $null);
+ return (int)$count;
+ }
+
+ /**
+ * get a list of messages with number and size
+ *
+ * @param int $id number of message
+ * @return int|array size of given message of list with all messages as array(num => size)
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getSize($id = 0)
+ {
+ $id = $id ? $id : null;
+ return $this->_protocol->getList($id);
+ }
+
+ /**
+ * Fetch a message
+ *
+ * @param int $id number of message
+ * @return Zend_Mail_Message
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function getMessage($id)
+ {
+ $bodyLines = 0;
+ $message = $this->_protocol->top($id, $bodyLines, true);
+
+ return new $this->_messageClass(array('handler' => $this, 'id' => $id, 'headers' => $message,
+ 'noToplines' => $bodyLines < 1));
+ }
+
+ /*
+ * Get raw header of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage header
+ * @param int $topLines include this many lines with header (after an empty line)
+ * @return string raw header
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawHeader($id, $part = null, $topLines = 0)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ return $this->_protocol->top($id, 0, true);
+ }
+
+ /*
+ * Get raw content of message or part
+ *
+ * @param int $id number of message
+ * @param null|array|string $part path to part or null for messsage content
+ * @return string raw content
+ * @throws Zend_Mail_Protocol_Exception
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getRawContent($id, $part = null)
+ {
+ if ($part !== null) {
+ // TODO: implement
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('not implemented');
+ }
+
+ $content = $this->_protocol->retrieve($id);
+ // TODO: find a way to avoid decoding the headers
+ Zend_Mime_Decode::splitMessage($content, $null, $body);
+ return $body;
+ }
+
+ /**
+ * create instance with parameters
+ * Supported paramters are
+ * - host hostname or ip address of POP3 server
+ * - user username
+ * - password password for user 'username' [optional, default = '']
+ * - port port for POP3 server [optional, default = 110]
+ * - ssl 'SSL' or 'TLS' for secure sockets
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function __construct($params)
+ {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ $this->_has['fetchPart'] = false;
+ $this->_has['top'] = null;
+ $this->_has['uniqueid'] = null;
+
+ if ($params instanceof Zend_Mail_Protocol_Pop3) {
+ $this->_protocol = $params;
+ return;
+ }
+
+ if (!isset($params->user)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('need at least user in params');
+ }
+
+ $host = isset($params->host) ? $params->host : 'localhost';
+ $password = isset($params->password) ? $params->password : '';
+ $port = isset($params->port) ? $params->port : null;
+ $ssl = isset($params->ssl) ? $params->ssl : false;
+
+ $this->_protocol = new Zend_Mail_Protocol_Pop3();
+ $this->_protocol->connect($host, $port, $ssl);
+ $this->_protocol->login($params->user, $password);
+ }
+
+ /**
+ * Close resource for mail lib. If you need to control, when the resource
+ * is closed. Otherwise the destructor would call this.
+ *
+ * @return null
+ */
+ public function close()
+ {
+ $this->_protocol->logout();
+ }
+
+ /**
+ * Keep the server busy.
+ *
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function noop()
+ {
+ return $this->_protocol->noop();
+ }
+
+ /**
+ * Remove a message from server. If you're doing that from a web enviroment
+ * you should be careful and use a uniqueid as parameter if possible to
+ * identify the message.
+ *
+ * @param int $id number of message
+ * @return null
+ * @throws Zend_Mail_Protocol_Exception
+ */
+ public function removeMessage($id)
+ {
+ $this->_protocol->delete($id);
+ }
+
+ /**
+ * get unique id for one or all messages
+ *
+ * if storage does not support unique ids it's the same as the message number
+ *
+ * @param int|null $id message number
+ * @return array|string message number for given message or all messages as array
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getUniqueId($id = null)
+ {
+ if (!$this->hasUniqueid) {
+ if ($id) {
+ return $id;
+ }
+ $count = $this->countMessages();
+ if ($count < 1) {
+ return array();
+ }
+ $range = range(1, $count);
+ return array_combine($range, $range);
+ }
+
+ return $this->_protocol->uniqueid($id);
+ }
+
+ /**
+ * get a message number from a unique id
+ *
+ * I.e. if you have a webmailer that supports deleting messages you should use unique ids
+ * as parameter and use this method to translate it to message number right before calling removeMessage()
+ *
+ * @param string $id unique id
+ * @return int message number
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function getNumberByUniqueId($id)
+ {
+ if (!$this->hasUniqueid) {
+ return $id;
+ }
+
+ $ids = $this->getUniqueId();
+ foreach ($ids as $k => $v) {
+ if ($v == $id) {
+ return $k;
+ }
+ }
+
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('unique id not found');
+ }
+
+ /**
+ * Special handling for hasTop and hasUniqueid. The headers of the first message is
+ * retrieved if Top wasn't needed/tried yet.
+ *
+ * @see Zend_Mail_Storage_Abstract:__get()
+ * @param string $var
+ * @return string
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __get($var)
+ {
+ $result = parent::__get($var);
+ if ($result !== null) {
+ return $result;
+ }
+
+ if (strtolower($var) == 'hastop') {
+ if ($this->_protocol->hasTop === null) {
+ // need to make a real call, because not all server are honest in their capas
+ try {
+ $this->_protocol->top(1, 0, false);
+ } catch(Zend_Mail_Exception $e) {
+ // ignoring error
+ }
+ }
+ $this->_has['top'] = $this->_protocol->hasTop;
+ return $this->_protocol->hasTop;
+ }
+
+ if (strtolower($var) == 'hasuniqueid') {
+ $id = null;
+ try {
+ $id = $this->_protocol->uniqueid(1);
+ } catch(Zend_Mail_Exception $e) {
+ // ignoring error
+ }
+ $this->_has['uniqueid'] = $id ? true : false;
+ return $this->_has['uniqueid'];
+ }
+
+ return $result;
+ }
+}
diff --git a/library/vendor/Zend/Mail/Storage/Writable/Interface.php b/library/vendor/Zend/Mail/Storage/Writable/Interface.php
new file mode 100644
index 0000000..aad8e5e
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Writable/Interface.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+interface Zend_Mail_Storage_Writable_Interface
+{
+ /**
+ * create a new folder
+ *
+ * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
+ * may be used as parent or which chars may be used in the folder name
+ *
+ * @param string $name global name of folder, local name if $parentFolder is set
+ * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function createFolder($name, $parentFolder = null);
+
+ /**
+ * remove a folder
+ *
+ * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeFolder($name);
+
+ /**
+ * rename and/or move folder
+ *
+ * The new name has the same restrictions as in createFolder()
+ *
+ * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
+ * @param string $newName new global name of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function renameFolder($oldName, $newName);
+
+ /**
+ * append a new message to mail storage
+ *
+ * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
+ * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
+ * @param null|array $flags set flags for new message, else a default set is used
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function appendMessage($message, $folder = null, $flags = null);
+
+ /**
+ * copy an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function copyMessage($id, $folder);
+
+ /**
+ * move an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function moveMessage($id, $folder);
+
+ /**
+ * set flags for message
+ *
+ * NOTE: this method can't set the recent flag.
+ *
+ * @param int $id number of message
+ * @param array $flags new flags for message
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function setFlags($id, $flags);
+}
diff --git a/library/vendor/Zend/Mail/Storage/Writable/Maildir.php b/library/vendor/Zend/Mail/Storage/Writable/Maildir.php
new file mode 100644
index 0000000..e4f468e
--- /dev/null
+++ b/library/vendor/Zend/Mail/Storage/Writable/Maildir.php
@@ -0,0 +1,1014 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Storage_Folder_Maildir
+ */
+
+/**
+ * @see Zend_Mail_Storage_Writable_Interface
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Storage
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Storage_Writable_Maildir extends Zend_Mail_Storage_Folder_Maildir
+ implements Zend_Mail_Storage_Writable_Interface
+{
+ // TODO: init maildir (+ constructor option create if not found)
+
+ /**
+ * use quota and size of quota if given
+ * @var bool|int
+ */
+ protected $_quota;
+
+ /**
+ * create a new maildir
+ *
+ * If the given dir is already a valid maildir this will not fail.
+ *
+ * @param string $dir directory for the new maildir (may already exist)
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public static function initMaildir($dir)
+ {
+ if (file_exists($dir)) {
+ if (!is_dir($dir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('maildir must be a directory if already exists');
+ }
+ } else {
+ if (!mkdir($dir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ $dir = dirname($dir);
+ if (!file_exists($dir)) {
+ throw new Zend_Mail_Storage_Exception("parent $dir not found");
+ } else if (!is_dir($dir)) {
+ throw new Zend_Mail_Storage_Exception("parent $dir not a directory");
+ } else {
+ throw new Zend_Mail_Storage_Exception('cannot create maildir');
+ }
+ }
+ }
+
+ foreach (array('cur', 'tmp', 'new') as $subdir) {
+ if (!@mkdir($dir . DIRECTORY_SEPARATOR . $subdir)) {
+ // ignore if dir exists (i.e. was already valid maildir or two processes try to create one)
+ if (!file_exists($dir . DIRECTORY_SEPARATOR . $subdir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('could not create subdir ' . $subdir);
+ }
+ }
+ }
+ }
+
+ /**
+ * Create instance with parameters
+ * Additional parameters are (see parent for more):
+ * - create if true a new maildir is create if none exists
+ *
+ * @param array $params mail reader specific parameters
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function __construct($params) {
+ if (is_array($params)) {
+ $params = (object)$params;
+ }
+
+ if (!empty($params->create) && isset($params->dirname) && !file_exists($params->dirname . DIRECTORY_SEPARATOR . 'cur')) {
+ self::initMaildir($params->dirname);
+ }
+
+ parent::__construct($params);
+ }
+
+ /**
+ * create a new folder
+ *
+ * This method also creates parent folders if necessary. Some mail storages may restrict, which folder
+ * may be used as parent or which chars may be used in the folder name
+ *
+ * @param string $name global name of folder, local name if $parentFolder is set
+ * @param string|Zend_Mail_Storage_Folder $parentFolder parent folder for new folder, else root folder is parent
+ * @return string only used internally (new created maildir)
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function createFolder($name, $parentFolder = null)
+ {
+ if ($parentFolder instanceof Zend_Mail_Storage_Folder) {
+ $folder = $parentFolder->getGlobalName() . $this->_delim . $name;
+ } else if ($parentFolder != null) {
+ $folder = rtrim($parentFolder, $this->_delim) . $this->_delim . $name;
+ } else {
+ $folder = $name;
+ }
+
+ $folder = trim($folder, $this->_delim);
+
+ // first we check if we try to create a folder that does exist
+ $exists = null;
+ try {
+ $exists = $this->getFolders($folder);
+ } catch (Zend_Mail_Exception $e) {
+ // ok
+ }
+ if ($exists) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('folder already exists');
+ }
+
+ if (strpos($folder, $this->_delim . $this->_delim) !== false) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('invalid name - folder parts may not be empty');
+ }
+
+ if (strpos($folder, 'INBOX' . $this->_delim) === 0) {
+ $folder = substr($folder, 6);
+ }
+
+ $fulldir = $this->_rootdir . '.' . $folder;
+
+ // check if we got tricked and would create a dir outside of the rootdir or not as direct child
+ if (strpos($folder, DIRECTORY_SEPARATOR) !== false || strpos($folder, '/') !== false
+ || dirname($fulldir) . DIRECTORY_SEPARATOR != $this->_rootdir) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('invalid name - no directory seprator allowed in folder name');
+ }
+
+ // has a parent folder?
+ $parent = null;
+ if (strpos($folder, $this->_delim)) {
+ // let's see if the parent folder exists
+ $parent = substr($folder, 0, strrpos($folder, $this->_delim));
+ try {
+ $this->getFolders($parent);
+ } catch (Zend_Mail_Exception $e) {
+ // does not - create parent folder
+ $this->createFolder($parent);
+ }
+ }
+
+ if (!@mkdir($fulldir) || !@mkdir($fulldir . DIRECTORY_SEPARATOR . 'cur')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('error while creating new folder, may be created incompletly');
+ }
+
+ mkdir($fulldir . DIRECTORY_SEPARATOR . 'new');
+ mkdir($fulldir . DIRECTORY_SEPARATOR . 'tmp');
+
+ $localName = $parent ? substr($folder, strlen($parent) + 1) : $folder;
+ $this->getFolders($parent)->$localName = new Zend_Mail_Storage_Folder($localName, $folder, true);
+
+ return $fulldir;
+ }
+
+ /**
+ * remove a folder
+ *
+ * @param string|Zend_Mail_Storage_Folder $name name or instance of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeFolder($name)
+ {
+ // TODO: This could fail in the middle of the task, which is not optimal.
+ // But there is no defined standard way to mark a folder as removed and there is no atomar fs-op
+ // to remove a directory. Also moving the folder to a/the trash folder is not possible, as
+ // all parent folders must be created. What we could do is add a dash to the front of the
+ // directory name and it should be ignored as long as other processes obey the standard.
+
+ if ($name instanceof Zend_Mail_Storage_Folder) {
+ $name = $name->getGlobalName();
+ }
+
+ $name = trim($name, $this->_delim);
+ if (strpos($name, 'INBOX' . $this->_delim) === 0) {
+ $name = substr($name, 6);
+ }
+
+ // check if folder exists and has no children
+ if (!$this->getFolders($name)->isLeaf()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('delete children first');
+ }
+
+ if ($name == 'INBOX' || $name == DIRECTORY_SEPARATOR || $name == '/') {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('wont delete INBOX');
+ }
+
+ if ($name == $this->getCurrentFolder()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('wont delete selected folder');
+ }
+
+ foreach (array('tmp', 'new', 'cur', '.') as $subdir) {
+ $dir = $this->_rootdir . '.' . $name . DIRECTORY_SEPARATOR . $subdir;
+ if (!file_exists($dir)) {
+ continue;
+ }
+ $dh = opendir($dir);
+ if (!$dh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("error opening $subdir");
+ }
+ while (($entry = readdir($dh)) !== false) {
+ if ($entry == '.' || $entry == '..') {
+ continue;
+ }
+ if (!unlink($dir . DIRECTORY_SEPARATOR . $entry)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("error cleaning $subdir");
+ }
+ }
+ closedir($dh);
+ if ($subdir !== '.') {
+ if (!rmdir($dir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("error removing $subdir");
+ }
+ }
+ }
+
+ if (!rmdir($this->_rootdir . '.' . $name)) {
+ // at least we should try to make it a valid maildir again
+ mkdir($this->_rootdir . '.' . $name . DIRECTORY_SEPARATOR . 'cur');
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("error removing maindir");
+ }
+
+ $parent = strpos($name, $this->_delim) ? substr($name, 0, strrpos($name, $this->_delim)) : null;
+ $localName = $parent ? substr($name, strlen($parent) + 1) : $name;
+ unset($this->getFolders($parent)->$localName);
+ }
+
+ /**
+ * rename and/or move folder
+ *
+ * The new name has the same restrictions as in createFolder()
+ *
+ * @param string|Zend_Mail_Storage_Folder $oldName name or instance of folder
+ * @param string $newName new global name of folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function renameFolder($oldName, $newName)
+ {
+ // TODO: This is also not atomar and has similar problems as removeFolder()
+
+ if ($oldName instanceof Zend_Mail_Storage_Folder) {
+ $oldName = $oldName->getGlobalName();
+ }
+
+ $oldName = trim($oldName, $this->_delim);
+ if (strpos($oldName, 'INBOX' . $this->_delim) === 0) {
+ $oldName = substr($oldName, 6);
+ }
+
+ $newName = trim($newName, $this->_delim);
+ if (strpos($newName, 'INBOX' . $this->_delim) === 0) {
+ $newName = substr($newName, 6);
+ }
+
+ if (strpos($newName, $oldName . $this->_delim) === 0) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('new folder cannot be a child of old folder');
+ }
+
+ // check if folder exists and has no children
+ $folder = $this->getFolders($oldName);
+
+ if ($oldName == 'INBOX' || $oldName == DIRECTORY_SEPARATOR || $oldName == '/') {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('wont rename INBOX');
+ }
+
+ if ($oldName == $this->getCurrentFolder()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('wont rename selected folder');
+ }
+
+ $newdir = $this->createFolder($newName);
+
+ if (!$folder->isLeaf()) {
+ foreach ($folder as $k => $v) {
+ $this->renameFolder($v->getGlobalName(), $newName . $this->_delim . $k);
+ }
+ }
+
+ $olddir = $this->_rootdir . '.' . $folder;
+ foreach (array('tmp', 'new', 'cur') as $subdir) {
+ $subdir = DIRECTORY_SEPARATOR . $subdir;
+ if (!file_exists($olddir . $subdir)) {
+ continue;
+ }
+ // using copy or moving files would be even better - but also much slower
+ if (!rename($olddir . $subdir, $newdir . $subdir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('error while moving ' . $subdir);
+ }
+ }
+ // create a dummy if removing fails - otherwise we can't read it next time
+ mkdir($olddir . DIRECTORY_SEPARATOR . 'cur');
+ $this->removeFolder($oldName);
+ }
+
+ /**
+ * create a uniqueid for maildir filename
+ *
+ * This is nearly the format defined in the maildir standard. The microtime() call should already
+ * create a uniqueid, the pid is for multicore/-cpu machine that manage to call this function at the
+ * exact same time, and uname() gives us the hostname for multiple machines accessing the same storage.
+ *
+ * If someone disables posix we create a random number of the same size, so this method should also
+ * work on Windows - if you manage to get maildir working on Windows.
+ * Microtime could also be disabled, altough I've never seen it.
+ *
+ * @return string new uniqueid
+ */
+ protected function _createUniqueId()
+ {
+ $id = '';
+ $id .= function_exists('microtime') ? microtime(true) : (time() . ' ' . rand(0, 100000));
+ $id .= '.' . (function_exists('posix_getpid') ? posix_getpid() : rand(50, 65535));
+ $id .= '.' . php_uname('n');
+
+ return $id;
+ }
+
+ /**
+ * open a temporary maildir file
+ *
+ * makes sure tmp/ exists and create a file with a unique name
+ * you should close the returned filehandle!
+ *
+ * @param string $folder name of current folder without leading .
+ * @return array array('dirname' => dir of maildir folder, 'uniq' => unique id, 'filename' => name of create file
+ * 'handle' => file opened for writing)
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _createTmpFile($folder = 'INBOX')
+ {
+ if ($folder == 'INBOX') {
+ $tmpdir = $this->_rootdir . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+ } else {
+ $tmpdir = $this->_rootdir . '.' . $folder . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
+ }
+ if (!file_exists($tmpdir)) {
+ if (!mkdir($tmpdir)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('problems creating tmp dir');
+ }
+ }
+
+ // we should retry to create a unique id if a file with the same name exists
+ // to avoid a script timeout we only wait 1 second (instead of 2) and stop
+ // after a defined retry count
+ // if you change this variable take into account that it can take up to $max_tries seconds
+ // normally we should have a valid unique name after the first try, we're just following the "standard" here
+ $max_tries = 5;
+ for ($i = 0; $i < $max_tries; ++$i) {
+ $uniq = $this->_createUniqueId();
+ if (!file_exists($tmpdir . $uniq)) {
+ // here is the race condition! - as defined in the standard
+ // to avoid having a long time between stat()ing the file and creating it we're opening it here
+ // to mark the filename as taken
+ $fh = fopen($tmpdir . $uniq, 'w');
+ if (!$fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('could not open temp file');
+ }
+ break;
+ }
+ sleep(1);
+ }
+
+ if (!$fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception("tried $max_tries unique ids for a temp file, but all were taken"
+ . ' - giving up');
+ }
+
+ return array('dirname' => $this->_rootdir . '.' . $folder, 'uniq' => $uniq, 'filename' => $tmpdir . $uniq,
+ 'handle' => $fh);
+ }
+
+ /**
+ * create an info string for filenames with given flags
+ *
+ * @param array $flags wanted flags, with the reference you'll get the set flags with correct key (= char for flag)
+ * @return string info string for version 2 filenames including the leading colon
+ * @throws Zend_Mail_Storage_Exception
+ */
+ protected function _getInfoString(&$flags)
+ {
+ // accessing keys is easier, faster and it removes duplicated flags
+ $wanted_flags = array_flip($flags);
+ if (isset($wanted_flags[Zend_Mail_Storage::FLAG_RECENT])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('recent flag may not be set');
+ }
+
+ $info = ':2,';
+ $flags = array();
+ foreach (Zend_Mail_Storage_Maildir::$_knownFlags as $char => $flag) {
+ if (!isset($wanted_flags[$flag])) {
+ continue;
+ }
+ $info .= $char;
+ $flags[$char] = $flag;
+ unset($wanted_flags[$flag]);
+ }
+
+ if (!empty($wanted_flags)) {
+ $wanted_flags = implode(', ', array_keys($wanted_flags));
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('unknown flag(s): ' . $wanted_flags);
+ }
+
+ return $info;
+ }
+
+ /**
+ * append a new message to mail storage
+ *
+ * @param string|stream $message message as string or stream resource
+ * @param null|string|Zend_Mail_Storage_Folder $folder folder for new message, else current folder is taken
+ * @param null|array $flags set flags for new message, else a default set is used
+ * @param bool $recent handle this mail as if recent flag has been set,
+ * should only be used in delivery
+ * @throws Zend_Mail_Storage_Exception
+ */
+ // not yet * @param string|Zend_Mail_Message|Zend_Mime_Message $message message as string or instance of message class
+
+ public function appendMessage($message, $folder = null, $flags = null, $recent = false)
+ {
+ if ($this->_quota && $this->checkQuota()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('storage is over quota!');
+ }
+
+ if ($folder === null) {
+ $folder = $this->_currentFolder;
+ }
+
+ if (!($folder instanceof Zend_Mail_Storage_Folder)) {
+ $folder = $this->getFolders($folder);
+ }
+
+ if ($flags === null) {
+ $flags = array(Zend_Mail_Storage::FLAG_SEEN);
+ }
+ $info = $this->_getInfoString($flags);
+ $temp_file = $this->_createTmpFile($folder->getGlobalName());
+
+ // TODO: handle class instances for $message
+ if (is_resource($message) && get_resource_type($message) == 'stream') {
+ stream_copy_to_stream($message, $temp_file['handle']);
+ } else {
+ fputs($temp_file['handle'], $message);
+ }
+ fclose($temp_file['handle']);
+
+ // we're adding the size to the filename for maildir++
+ $size = filesize($temp_file['filename']);
+ if ($size !== false) {
+ $info = ',S=' . $size . $info;
+ }
+ $new_filename = $temp_file['dirname'] . DIRECTORY_SEPARATOR;
+ $new_filename .= $recent ? 'new' : 'cur';
+ $new_filename .= DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info;
+
+ // we're throwing any exception after removing our temp file and saving it to this variable instead
+ $exception = null;
+
+ if (!link($temp_file['filename'], $new_filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ $exception = new Zend_Mail_Storage_Exception('cannot link message file to final dir');
+ }
+ @unlink($temp_file['filename']);
+
+ if ($exception) {
+ throw $exception;
+ }
+
+ $this->_files[] = array('uniq' => $temp_file['uniq'],
+ 'flags' => $flags,
+ 'filename' => $new_filename);
+ if ($this->_quota) {
+ $this->_addQuotaEntry((int)$size, 1);
+ }
+ }
+
+ /**
+ * copy an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function copyMessage($id, $folder)
+ {
+ if ($this->_quota && $this->checkQuota()) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('storage is over quota!');
+ }
+
+ if (!($folder instanceof Zend_Mail_Storage_Folder)) {
+ $folder = $this->getFolders($folder);
+ }
+
+ $filedata = $this->_getFileData($id);
+ $old_file = $filedata['filename'];
+ $flags = $filedata['flags'];
+
+ // copied message can't be recent
+ while (($key = array_search(Zend_Mail_Storage::FLAG_RECENT, $flags)) !== false) {
+ unset($flags[$key]);
+ }
+ $info = $this->_getInfoString($flags);
+
+ // we're creating the copy as temp file before moving to cur/
+ $temp_file = $this->_createTmpFile($folder->getGlobalName());
+ // we don't write directly to the file
+ fclose($temp_file['handle']);
+
+ // we're adding the size to the filename for maildir++
+ $size = filesize($old_file);
+ if ($size !== false) {
+ $info = ',S=' . $size . $info;
+ }
+
+ $new_file = $temp_file['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info;
+
+ // we're throwing any exception after removing our temp file and saving it to this variable instead
+ $exception = null;
+
+ if (!copy($old_file, $temp_file['filename'])) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ $exception = new Zend_Mail_Storage_Exception('cannot copy message file');
+ } else if (!link($temp_file['filename'], $new_file)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ $exception = new Zend_Mail_Storage_Exception('cannot link message file to final dir');
+ }
+ @unlink($temp_file['filename']);
+
+ if ($exception) {
+ throw $exception;
+ }
+
+ if ($folder->getGlobalName() == $this->_currentFolder
+ || ($this->_currentFolder == 'INBOX' && $folder->getGlobalName() == '/')) {
+ $this->_files[] = array('uniq' => $temp_file['uniq'],
+ 'flags' => $flags,
+ 'filename' => $new_file);
+ }
+
+ if ($this->_quota) {
+ $this->_addQuotaEntry((int)$size, 1);
+ }
+ }
+
+ /**
+ * move an existing message
+ *
+ * @param int $id number of message
+ * @param string|Zend_Mail_Storage_Folder $folder name or instance of targer folder
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function moveMessage($id, $folder) {
+ if (!($folder instanceof Zend_Mail_Storage_Folder)) {
+ $folder = $this->getFolders($folder);
+ }
+
+ if ($folder->getGlobalName() == $this->_currentFolder
+ || ($this->_currentFolder == 'INBOX' && $folder->getGlobalName() == '/')) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('target is current folder');
+ }
+
+ $filedata = $this->_getFileData($id);
+ $old_file = $filedata['filename'];
+ $flags = $filedata['flags'];
+
+ // moved message can't be recent
+ while (($key = array_search(Zend_Mail_Storage::FLAG_RECENT, $flags)) !== false) {
+ unset($flags[$key]);
+ }
+ $info = $this->_getInfoString($flags);
+
+ // reserving a new name
+ $temp_file = $this->_createTmpFile($folder->getGlobalName());
+ fclose($temp_file['handle']);
+
+ // we're adding the size to the filename for maildir++
+ $size = filesize($old_file);
+ if ($size !== false) {
+ $info = ',S=' . $size . $info;
+ }
+
+ $new_file = $temp_file['dirname'] . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . $temp_file['uniq'] . $info;
+
+ // we're throwing any exception after removing our temp file and saving it to this variable instead
+ $exception = null;
+
+ if (!rename($old_file, $new_file)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ $exception = new Zend_Mail_Storage_Exception('cannot move message file');
+ }
+ @unlink($temp_file['filename']);
+
+ if ($exception) {
+ throw $exception;
+ }
+
+ unset($this->_files[$id - 1]);
+ // remove the gap
+ $this->_files = array_values($this->_files);
+ }
+
+
+ /**
+ * set flags for message
+ *
+ * NOTE: this method can't set the recent flag.
+ *
+ * @param int $id number of message
+ * @param array $flags new flags for message
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function setFlags($id, $flags)
+ {
+ $info = $this->_getInfoString($flags);
+ $filedata = $this->_getFileData($id);
+
+ // NOTE: double dirname to make sure we always move to cur. if recent flag has been set (message is in new) it will be moved to cur.
+ $new_filename = dirname(dirname($filedata['filename'])) . DIRECTORY_SEPARATOR . 'cur' . DIRECTORY_SEPARATOR . "$filedata[uniq]$info";
+
+ if (!@rename($filedata['filename'], $new_filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot rename file');
+ }
+
+ $filedata['flags'] = $flags;
+ $filedata['filename'] = $new_filename;
+
+ $this->_files[$id - 1] = $filedata;
+ }
+
+
+ /**
+ * stub for not supported message deletion
+ *
+ * @return null
+ * @throws Zend_Mail_Storage_Exception
+ */
+ public function removeMessage($id)
+ {
+ $filename = $this->_getFileData($id, 'filename');
+
+ if ($this->_quota) {
+ $size = filesize($filename);
+ }
+
+ if (!@unlink($filename)) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot remove message');
+ }
+ unset($this->_files[$id - 1]);
+ // remove the gap
+ $this->_files = array_values($this->_files);
+ if ($this->_quota) {
+ $this->_addQuotaEntry(0 - (int)$size, -1);
+ }
+ }
+
+ /**
+ * enable/disable quota and set a quota value if wanted or needed
+ *
+ * You can enable/disable quota with true/false. If you don't have
+ * a MDA or want to enforce a quota value you can also set this value
+ * here. Use array('size' => SIZE_QUOTA, 'count' => MAX_MESSAGE) do
+ * define your quota. Order of these fields does matter!
+ *
+ * @param bool|array $value new quota value
+ * @return null
+ */
+ public function setQuota($value) {
+ $this->_quota = $value;
+ }
+
+ /**
+ * get currently set quota
+ *
+ * @see Zend_Mail_Storage_Writable_Maildir::setQuota()
+ *
+ * @return bool|array
+ */
+ public function getQuota($fromStorage = false) {
+ if ($fromStorage) {
+ $fh = @fopen($this->_rootdir . 'maildirsize', 'r');
+ if (!$fh) {
+ /**
+ * @see Zend_Mail_Storage_Exception
+ */
+ throw new Zend_Mail_Storage_Exception('cannot open maildirsize');
+ }
+ $definition = fgets($fh);
+ fclose($fh);
+ $definition = explode(',', trim($definition));
+ $quota = array();
+ foreach ($definition as $member) {
+ $key = $member[strlen($member) - 1];
+ if ($key == 'S' || $key == 'C') {
+ $key = $key == 'C' ? 'count' : 'size';
+ }
+ $quota[$key] = substr($member, 0, -1);
+ }
+ return $quota;
+ }
+
+ return $this->_quota;
+ }
+
+ /**
+ * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating maildirsize"
+ */
+ protected function _calculateMaildirsize() {
+ $timestamps = array();
+ $messages = 0;
+ $total_size = 0;
+
+ if (is_array($this->_quota)) {
+ $quota = $this->_quota;
+ } else {
+ try {
+ $quota = $this->getQuota(true);
+ } catch (Zend_Mail_Storage_Exception $e) {
+ throw new Zend_Mail_Storage_Exception('no quota definition found', 0, $e);
+ }
+ }
+
+ $folders = new RecursiveIteratorIterator($this->getFolders(), RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($folders as $folder) {
+ $subdir = $folder->getGlobalName();
+ if ($subdir == 'INBOX') {
+ $subdir = '';
+ } else {
+ $subdir = '.' . $subdir;
+ }
+ if ($subdir == 'Trash') {
+ continue;
+ }
+
+ foreach (array('cur', 'new') as $subsubdir) {
+ $dirname = $this->_rootdir . $subdir . DIRECTORY_SEPARATOR . $subsubdir . DIRECTORY_SEPARATOR;
+ if (!file_exists($dirname)) {
+ continue;
+ }
+ // NOTE: we are using mtime instead of "the latest timestamp". The latest would be atime
+ // and as we are accessing the directory it would make the whole calculation useless.
+ $timestamps[$dirname] = filemtime($dirname);
+
+ $dh = opendir($dirname);
+ // NOTE: Should have been checked in constructor. Not throwing an exception here, quotas will
+ // therefore not be fully enforeced, but next request will fail anyway, if problem persists.
+ if (!$dh) {
+ continue;
+ }
+
+
+ while (($entry = readdir()) !== false) {
+ if ($entry[0] == '.' || !is_file($dirname . $entry)) {
+ continue;
+ }
+
+ if (strpos($entry, ',S=')) {
+ strtok($entry, '=');
+ $filesize = strtok(':');
+ if (is_numeric($filesize)) {
+ $total_size += $filesize;
+ ++$messages;
+ continue;
+ }
+ }
+ $size = filesize($dirname . $entry);
+ if ($size === false) {
+ // ignore, as we assume file got removed
+ continue;
+ }
+ $total_size += $size;
+ ++$messages;
+ }
+ }
+ }
+
+ $tmp = $this->_createTmpFile();
+ $fh = $tmp['handle'];
+ $definition = array();
+ foreach ($quota as $type => $value) {
+ if ($type == 'size' || $type == 'count') {
+ $type = $type == 'count' ? 'C' : 'S';
+ }
+ $definition[] = $value . $type;
+ }
+ $definition = implode(',', $definition);
+ fputs($fh, "$definition\n");
+ fputs($fh, "$total_size $messages\n");
+ fclose($fh);
+ rename($tmp['filename'], $this->_rootdir . 'maildirsize');
+ foreach ($timestamps as $dir => $timestamp) {
+ if ($timestamp < filemtime($dir)) {
+ unlink($this->_rootdir . 'maildirsize');
+ break;
+ }
+ }
+
+ return array('size' => $total_size, 'count' => $messages, 'quota' => $quota);
+ }
+
+ /**
+ * @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating the quota for a Maildir++"
+ */
+ protected function _calculateQuota($forceRecalc = false) {
+ $fh = null;
+ $total_size = 0;
+ $messages = 0;
+ $maildirsize = '';
+ if (!$forceRecalc && file_exists($this->_rootdir . 'maildirsize') && filesize($this->_rootdir . 'maildirsize') < 5120) {
+ $fh = fopen($this->_rootdir . 'maildirsize', 'r');
+ }
+ if ($fh) {
+ $maildirsize = fread($fh, 5120);
+ if (strlen($maildirsize) >= 5120) {
+ fclose($fh);
+ $fh = null;
+ $maildirsize = '';
+ }
+ }
+ if (!$fh) {
+ $result = $this->_calculateMaildirsize();
+ $total_size = $result['size'];
+ $messages = $result['count'];
+ $quota = $result['quota'];
+ } else {
+ $maildirsize = explode("\n", $maildirsize);
+ if (is_array($this->_quota)) {
+ $quota = $this->_quota;
+ } else {
+ $definition = explode(',', $maildirsize[0]);
+ $quota = array();
+ foreach ($definition as $member) {
+ $key = $member[strlen($member) - 1];
+ if ($key == 'S' || $key == 'C') {
+ $key = $key == 'C' ? 'count' : 'size';
+ }
+ $quota[$key] = substr($member, 0, -1);
+ }
+ }
+ unset($maildirsize[0]);
+ foreach ($maildirsize as $line) {
+ list($size, $count) = explode(' ', trim($line));
+ $total_size += $size;
+ $messages += $count;
+ }
+ }
+
+ $over_quota = false;
+ $over_quota = $over_quota || (isset($quota['size']) && $total_size > $quota['size']);
+ $over_quota = $over_quota || (isset($quota['count']) && $messages > $quota['count']);
+ // NOTE: $maildirsize equals false if it wasn't set (AKA we recalculated) or it's only
+ // one line, because $maildirsize[0] gets unsetted.
+ // Also we're using local time to calculate the 15 minute offset. Touching a file just for known the
+ // local time of the file storage isn't worth the hassle.
+ if ($over_quota && ($maildirsize || filemtime($this->_rootdir . 'maildirsize') > time() - 900)) {
+ $result = $this->_calculateMaildirsize();
+ $total_size = $result['size'];
+ $messages = $result['count'];
+ $quota = $result['quota'];
+ $over_quota = false;
+ $over_quota = $over_quota || (isset($quota['size']) && $total_size > $quota['size']);
+ $over_quota = $over_quota || (isset($quota['count']) && $messages > $quota['count']);
+ }
+
+ if ($fh) {
+ // TODO is there a safe way to keep the handle open for writing?
+ fclose($fh);
+ }
+
+ return array('size' => $total_size, 'count' => $messages, 'quota' => $quota, 'over_quota' => $over_quota);
+ }
+
+ protected function _addQuotaEntry($size, $count = 1) {
+ if (!file_exists($this->_rootdir . 'maildirsize')) {
+ // TODO: should get file handler from _calculateQuota
+ }
+ $size = (int)$size;
+ $count = (int)$count;
+ file_put_contents($this->_rootdir . 'maildirsize', "$size $count\n", FILE_APPEND);
+ }
+
+ /**
+ * check if storage is currently over quota
+ *
+ * @param bool $detailedResponse return known data of quota and current size and message count @see _calculateQuota()
+ * @return bool|array over quota state or detailed response
+ */
+ public function checkQuota($detailedResponse = false, $forceRecalc = false) {
+ $result = $this->_calculateQuota($forceRecalc);
+ return $detailedResponse ? $result : $result['over_quota'];
+ }
+}
diff --git a/library/vendor/Zend/Mail/Transport/Abstract.php b/library/vendor/Zend/Mail/Transport/Abstract.php
new file mode 100644
index 0000000..3656060
--- /dev/null
+++ b/library/vendor/Zend/Mail/Transport/Abstract.php
@@ -0,0 +1,345 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mime
+ */
+
+
+/**
+ * Abstract for sending eMails through different
+ * ways of transport
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Mail_Transport_Abstract
+{
+ /**
+ * Mail body
+ * @var string
+ * @access public
+ */
+ public $body = '';
+
+ /**
+ * MIME boundary
+ * @var string
+ * @access public
+ */
+ public $boundary = '';
+
+ /**
+ * Mail header string
+ * @var string
+ * @access public
+ */
+ public $header = '';
+
+ /**
+ * Array of message headers
+ * @var array
+ * @access protected
+ */
+ protected $_headers = array();
+
+ /**
+ * Message is a multipart message
+ * @var boolean
+ * @access protected
+ */
+ protected $_isMultipart = false;
+
+ /**
+ * Zend_Mail object
+ * @var false|Zend_Mail
+ * @access protected
+ */
+ protected $_mail = false;
+
+ /**
+ * Array of message parts
+ * @var array
+ * @access protected
+ */
+ protected $_parts = array();
+
+ /**
+ * Recipients string
+ * @var string
+ * @access public
+ */
+ public $recipients = '';
+
+ /**
+ * EOL character string used by transport
+ * @var string
+ * @access public
+ */
+ public $EOL = "\r\n";
+
+ /**
+ * Send an email independent from the used transport
+ *
+ * The requisite information for the email will be found in the following
+ * properties:
+ *
+ * - {@link $recipients} - list of recipients (string)
+ * - {@link $header} - message header
+ * - {@link $body} - message body
+ */
+ abstract protected function _sendMail();
+
+ /**
+ * Return all mail headers as an array
+ *
+ * If a boundary is given, a multipart header is generated with a
+ * Content-Type of either multipart/alternative or multipart/mixed depending
+ * on the mail parts present in the {@link $_mail Zend_Mail object} present.
+ *
+ * @param string $boundary
+ * @return array
+ */
+ protected function _getHeaders($boundary)
+ {
+ if (null !== $boundary) {
+ // Build multipart mail
+ $type = $this->_mail->getType();
+ if (!$type) {
+ if ($this->_mail->hasAttachments) {
+ $type = Zend_Mime::MULTIPART_MIXED;
+ } elseif ($this->_mail->getBodyText() && $this->_mail->getBodyHtml()) {
+ $type = Zend_Mime::MULTIPART_ALTERNATIVE;
+ } else {
+ $type = Zend_Mime::MULTIPART_MIXED;
+ }
+ }
+
+ $this->_headers['Content-Type'] = array(
+ $type . ';'
+ . $this->EOL
+ . " " . 'boundary="' . $boundary . '"'
+ );
+ $this->boundary = $boundary;
+ }
+
+ $this->_headers['MIME-Version'] = array('1.0');
+
+ return $this->_headers;
+ }
+
+ /**
+ * Prepend header name to header value
+ *
+ * @param string $item
+ * @param string $key
+ * @param string $prefix
+ * @static
+ * @access protected
+ * @return void
+ */
+ protected static function _formatHeader(&$item, $key, $prefix)
+ {
+ $item = $prefix . ': ' . $item;
+ }
+
+ /**
+ * Prepare header string for use in transport
+ *
+ * Prepares and generates {@link $header} based on the headers provided.
+ *
+ * @param mixed $headers
+ * @access protected
+ * @return void
+ * @throws Zend_Mail_Transport_Exception if any header lines exceed 998
+ * characters
+ */
+ protected function _prepareHeaders($headers)
+ {
+ if (!$this->_mail) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('Missing Zend_Mail object in _mail property');
+ }
+
+ $this->header = '';
+
+ foreach ($headers as $header => $content) {
+ if (isset($content['append'])) {
+ unset($content['append']);
+ $value = implode(',' . $this->EOL . ' ', $content);
+ $this->header .= $header . ': ' . $value . $this->EOL;
+ } else {
+ array_walk($content, array(get_class($this), '_formatHeader'), $header);
+ $this->header .= implode($this->EOL, $content) . $this->EOL;
+ }
+ }
+
+ // Sanity check on headers -- should not be > 998 characters
+ $sane = true;
+ foreach (explode($this->EOL, $this->header) as $line) {
+ if (strlen(trim($line)) > 998) {
+ $sane = false;
+ break;
+ }
+ }
+ if (!$sane) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Exception('At least one mail header line is too long');
+ }
+ }
+
+ /**
+ * Generate MIME compliant message from the current configuration
+ *
+ * If both a text and HTML body are present, generates a
+ * multipart/alternative Zend_Mime_Part containing the headers and contents
+ * of each. Otherwise, uses whichever of the text or HTML parts present.
+ *
+ * The content part is then prepended to the list of Zend_Mime_Parts for
+ * this message.
+ *
+ * @return void
+ */
+ protected function _buildBody()
+ {
+ if (($text = $this->_mail->getBodyText())
+ && ($html = $this->_mail->getBodyHtml()))
+ {
+ // Generate unique boundary for multipart/alternative
+ $mime = new Zend_Mime(null);
+ $boundaryLine = $mime->boundaryLine($this->EOL);
+ $boundaryEnd = $mime->mimeEnd($this->EOL);
+
+ $text->disposition = false;
+ $html->disposition = false;
+
+ $body = $boundaryLine
+ . $text->getHeaders($this->EOL)
+ . $this->EOL
+ . $text->getContent($this->EOL)
+ . $this->EOL
+ . $boundaryLine
+ . $html->getHeaders($this->EOL)
+ . $this->EOL
+ . $html->getContent($this->EOL)
+ . $this->EOL
+ . $boundaryEnd;
+
+ $mp = new Zend_Mime_Part($body);
+ $mp->type = Zend_Mime::MULTIPART_ALTERNATIVE;
+ $mp->boundary = $mime->boundary();
+
+ $this->_isMultipart = true;
+
+ // Ensure first part contains text alternatives
+ array_unshift($this->_parts, $mp);
+
+ // Get headers
+ $this->_headers = $this->_mail->getHeaders();
+ return;
+ }
+
+ // If not multipart, then get the body
+ if (false !== ($body = $this->_mail->getBodyHtml())) {
+ array_unshift($this->_parts, $body);
+ } elseif (false !== ($body = $this->_mail->getBodyText())) {
+ array_unshift($this->_parts, $body);
+ }
+
+ if (!$body) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('No body specified');
+ }
+
+ // Get headers
+ $this->_headers = $this->_mail->getHeaders();
+ $headers = $body->getHeadersArray($this->EOL);
+ foreach ($headers as $header) {
+ // Headers in Zend_Mime_Part are kept as arrays with two elements, a
+ // key and a value
+ $this->_headers[$header[0]] = array($header[1]);
+ }
+ }
+
+ /**
+ * Send a mail using this transport
+ *
+ * @param Zend_Mail $mail
+ * @access public
+ * @return void
+ * @throws Zend_Mail_Transport_Exception if mail is empty
+ */
+ public function send(Zend_Mail $mail)
+ {
+ $this->_isMultipart = false;
+ $this->_mail = $mail;
+ $this->_parts = $mail->getParts();
+ $mime = $mail->getMime();
+
+ // Build body content
+ $this->_buildBody();
+
+ // Determine number of parts and boundary
+ $count = count($this->_parts);
+ $boundary = null;
+ if ($count < 1) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('Empty mail cannot be sent');
+ }
+
+ if ($count > 1) {
+ // Multipart message; create new MIME object and boundary
+ $mime = new Zend_Mime($this->_mail->getMimeBoundary());
+ $boundary = $mime->boundary();
+ } elseif ($this->_isMultipart) {
+ // multipart/alternative -- grab boundary
+ $boundary = $this->_parts[0]->boundary;
+ }
+
+ // Determine recipients, and prepare headers
+ $this->recipients = implode(',', $mail->getRecipients());
+ $this->_prepareHeaders($this->_getHeaders($boundary));
+
+ // Create message body
+ // This is done so that the same Zend_Mail object can be used in
+ // multiple transports
+ $message = new Zend_Mime_Message();
+ $message->setParts($this->_parts);
+ $message->setMime($mime);
+ $this->body = $message->generateMessage($this->EOL);
+
+ // Send to transport!
+ $this->_sendMail();
+ }
+}
diff --git a/library/vendor/Zend/Mail/Transport/Exception.php b/library/vendor/Zend/Mail/Transport/Exception.php
new file mode 100644
index 0000000..655a07e
--- /dev/null
+++ b/library/vendor/Zend/Mail/Transport/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Transport_Exception extends Zend_Mail_Exception
+{}
+
diff --git a/library/vendor/Zend/Mail/Transport/File.php b/library/vendor/Zend/Mail/Transport/File.php
new file mode 100644
index 0000000..3584a89
--- /dev/null
+++ b/library/vendor/Zend/Mail/Transport/File.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Mail_Transport_Abstract
+ */
+
+
+/**
+ * File transport
+ *
+ * Class for saving outgoing emails in filesystem
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Transport_File extends Zend_Mail_Transport_Abstract
+{
+ /**
+ * Target directory for saving sent email messages
+ *
+ * @var string
+ */
+ protected $_path;
+
+ /**
+ * Callback function generating a file name
+ *
+ * @var string|array
+ */
+ protected $_callback;
+
+ /**
+ * Constructor
+ *
+ * @param array|Zend_Config $options OPTIONAL (Default: null)
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (!is_array($options)) {
+ $options = array();
+ }
+
+ // Making sure we have some defaults to work with
+ if (!isset($options['path'])) {
+ $options['path'] = sys_get_temp_dir();
+ }
+ if (!isset($options['callback'])) {
+ $options['callback'] = array($this, 'defaultCallback');
+ }
+
+ $this->setOptions($options);
+ }
+
+ /**
+ * Sets options
+ *
+ * @param array $options
+ * @return void
+ */
+ public function setOptions(array $options)
+ {
+ if (isset($options['path']) && is_dir($options['path'])) {
+ $this->_path = $options['path'];
+ }
+ if (isset($options['callback']) && is_callable($options['callback'])) {
+ $this->_callback = $options['callback'];
+ }
+ }
+
+ /**
+ * Saves e-mail message to a file
+ *
+ * @return void
+ * @throws Zend_Mail_Transport_Exception on not writable target directory
+ * @throws Zend_Mail_Transport_Exception on file_put_contents() failure
+ */
+ protected function _sendMail()
+ {
+ $file = $this->_path . DIRECTORY_SEPARATOR . call_user_func($this->_callback, $this);
+
+ if (!is_writable(dirname($file))) {
+ throw new Zend_Mail_Transport_Exception(sprintf(
+ 'Target directory "%s" does not exist or is not writable',
+ dirname($file)
+ ));
+ }
+
+ $email = $this->header . $this->EOL . $this->body;
+
+ if (!file_put_contents($file, $email)) {
+ throw new Zend_Mail_Transport_Exception('Unable to send mail');
+ }
+ }
+
+ /**
+ * Default callback for generating filenames
+ *
+ * @param Zend_Mail_Transport_File File transport instance
+ * @return string
+ */
+ public function defaultCallback($transport)
+ {
+ return 'ZendMail_' . $_SERVER['REQUEST_TIME'] . '_' . mt_rand() . '.tmp';
+ }
+}
diff --git a/library/vendor/Zend/Mail/Transport/Sendmail.php b/library/vendor/Zend/Mail/Transport/Sendmail.php
new file mode 100644
index 0000000..30d6011
--- /dev/null
+++ b/library/vendor/Zend/Mail/Transport/Sendmail.php
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mail_Transport_Abstract
+ */
+
+
+/**
+ * Class for sending eMails via the PHP internal mail() function
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Transport_Sendmail extends Zend_Mail_Transport_Abstract
+{
+ /**
+ * Subject
+ * @var string
+ * @access public
+ */
+ public $subject = null;
+
+
+ /**
+ * Config options for sendmail parameters
+ *
+ * @var string
+ */
+ public $parameters;
+
+ /**
+ * EOL character string
+ * @var string
+ * @access public
+ */
+ public $EOL = PHP_EOL;
+
+ /**
+ * error information
+ * @var string
+ */
+ protected $_errstr;
+
+ /**
+ * Constructor.
+ *
+ * @param string|array|Zend_Config $parameters OPTIONAL (Default: null)
+ * @return void
+ */
+ public function __construct($parameters = null)
+ {
+ if ($parameters instanceof Zend_Config) {
+ $parameters = $parameters->toArray();
+ }
+
+ if (is_array($parameters)) {
+ $parameters = implode(' ', $parameters);
+ }
+
+ $this->parameters = $parameters;
+ }
+
+
+ /**
+ * Send mail using PHP native mail()
+ *
+ * @access public
+ * @return void
+ * @throws Zend_Mail_Transport_Exception if parameters is set
+ * but not a string
+ * @throws Zend_Mail_Transport_Exception on mail() failure
+ */
+ public function _sendMail()
+ {
+ if ($this->parameters === null) {
+ set_error_handler(array($this, '_handleMailErrors'));
+ $result = mail(
+ $this->recipients,
+ $this->_mail->getSubject(),
+ $this->body,
+ $this->header);
+ restore_error_handler();
+ } else {
+ if(!is_string($this->parameters)) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ *
+ * Exception is thrown here because
+ * $parameters is a public property
+ */
+ throw new Zend_Mail_Transport_Exception(
+ 'Parameters were set but are not a string'
+ );
+ }
+
+ set_error_handler(array($this, '_handleMailErrors'));
+ $result = mail(
+ $this->recipients,
+ $this->_mail->getSubject(),
+ $this->body,
+ $this->header,
+ $this->parameters);
+ restore_error_handler();
+ }
+
+ if ($this->_errstr !== null || !$result) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('Unable to send mail. ' . $this->_errstr);
+ }
+ }
+
+
+ /**
+ * Format and fix headers
+ *
+ * mail() uses its $to and $subject arguments to set the To: and Subject:
+ * headers, respectively. This method strips those out as a sanity check to
+ * prevent duplicate header entries.
+ *
+ * @access protected
+ * @param array $headers
+ * @return void
+ * @throws Zend_Mail_Transport_Exception
+ */
+ protected function _prepareHeaders($headers)
+ {
+ if (!$this->_mail) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('_prepareHeaders requires a registered Zend_Mail object');
+ }
+
+ // mail() uses its $to parameter to set the To: header, and the $subject
+ // parameter to set the Subject: header. We need to strip them out.
+ if (0 === strpos(PHP_OS, 'WIN')) {
+ // If the current recipients list is empty, throw an error
+ if (empty($this->recipients)) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('Missing To addresses');
+ }
+ } else {
+ // All others, simply grab the recipients and unset the To: header
+ if (!isset($headers['To'])) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('Missing To header');
+ }
+
+ unset($headers['To']['append']);
+ $this->recipients = implode(',', $headers['To']);
+ }
+
+ // Remove recipient header
+ unset($headers['To']);
+
+ // Remove subject header, if present
+ if (isset($headers['Subject'])) {
+ unset($headers['Subject']);
+ }
+
+ // Prepare headers
+ parent::_prepareHeaders($headers);
+
+ // Fix issue with empty blank line ontop when using Sendmail Trnasport
+ $this->header = rtrim($this->header);
+ }
+
+ /**
+ * Temporary error handler for PHP native mail().
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param string $errline
+ * @param array $errcontext
+ * @return true
+ */
+ public function _handleMailErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null)
+ {
+ $this->_errstr = $errstr;
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Mail/Transport/Smtp.php b/library/vendor/Zend/Mail/Transport/Smtp.php
new file mode 100644
index 0000000..b9c0ac9
--- /dev/null
+++ b/library/vendor/Zend/Mail/Transport/Smtp.php
@@ -0,0 +1,238 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Mime
+ */
+
+/**
+ * @see Zend_Mail_Protocol_Smtp
+ */
+
+/**
+ * @see Zend_Mail_Transport_Abstract
+ */
+
+
+/**
+ * SMTP connection object
+ *
+ * Loads an instance of Zend_Mail_Protocol_Smtp and forwards smtp transactions
+ *
+ * @category Zend
+ * @package Zend_Mail
+ * @subpackage Transport
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mail_Transport_Smtp extends Zend_Mail_Transport_Abstract
+{
+ /**
+ * EOL character string used by transport
+ * @var string
+ * @access public
+ */
+ public $EOL = "\n";
+
+ /**
+ * Remote smtp hostname or i.p.
+ *
+ * @var string
+ */
+ protected $_host;
+
+
+ /**
+ * Port number
+ *
+ * @var integer|null
+ */
+ protected $_port;
+
+
+ /**
+ * Local client hostname or i.p.
+ *
+ * @var string
+ */
+ protected $_name = 'localhost';
+
+
+ /**
+ * Authentication type OPTIONAL
+ *
+ * @var string
+ */
+ protected $_auth;
+
+
+ /**
+ * Config options for authentication
+ *
+ * @var array
+ */
+ protected $_config;
+
+
+ /**
+ * Instance of Zend_Mail_Protocol_Smtp
+ *
+ * @var Zend_Mail_Protocol_Smtp
+ */
+ protected $_connection;
+
+
+ /**
+ * Constructor.
+ *
+ * @param string $host OPTIONAL (Default: 127.0.0.1)
+ * @param array|null $config OPTIONAL (Default: null)
+ * @return void
+ *
+ * @todo Someone please make this compatible
+ * with the SendMail transport class.
+ */
+ public function __construct($host = '127.0.0.1', Array $config = array())
+ {
+ if (isset($config['name'])) {
+ $this->_name = $config['name'];
+ }
+ if (isset($config['port'])) {
+ $this->_port = $config['port'];
+ }
+ if (isset($config['auth'])) {
+ $this->_auth = $config['auth'];
+ }
+
+ $this->_host = $host;
+ $this->_config = $config;
+ }
+
+
+ /**
+ * Class destructor to ensure all open connections are closed
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ if ($this->_connection instanceof Zend_Mail_Protocol_Smtp) {
+ try {
+ $this->_connection->quit();
+ } catch (Zend_Mail_Protocol_Exception $e) {
+ // ignore
+ }
+ $this->_connection->disconnect();
+ }
+ }
+
+
+ /**
+ * Sets the connection protocol instance
+ *
+ * @param Zend_Mail_Protocol_Abstract $client
+ *
+ * @return void
+ */
+ public function setConnection(Zend_Mail_Protocol_Abstract $connection)
+ {
+ $this->_connection = $connection;
+ }
+
+
+ /**
+ * Gets the connection protocol instance
+ *
+ * @return Zend_Mail_Protocol|null
+ */
+ public function getConnection()
+ {
+ return $this->_connection;
+ }
+
+ /**
+ * Send an email via the SMTP connection protocol
+ *
+ * The connection via the protocol adapter is made just-in-time to allow a
+ * developer to add a custom adapter if required before mail is sent.
+ *
+ * @return void
+ * @todo Rename this to sendMail, it's a public method...
+ */
+ public function _sendMail()
+ {
+ // If sending multiple messages per session use existing adapter
+ if (!($this->_connection instanceof Zend_Mail_Protocol_Smtp)) {
+ // Check if authentication is required and determine required class
+ $connectionClass = 'Zend_Mail_Protocol_Smtp';
+ if ($this->_auth) {
+ $connectionClass .= '_Auth_' . ucwords($this->_auth);
+ }
+ if (!class_exists($connectionClass)) {
+ Zend_Loader::loadClass($connectionClass);
+ }
+ $this->setConnection(new $connectionClass($this->_host, $this->_port, $this->_config));
+ $this->_connection->connect();
+ $this->_connection->helo($this->_name);
+ } else {
+ // Reset connection to ensure reliable transaction
+ $this->_connection->rset();
+ }
+
+ // Set sender email address
+ $this->_connection->mail($this->_mail->getReturnPath());
+
+ // Set recipient forward paths
+ foreach ($this->_mail->getRecipients() as $recipient) {
+ $this->_connection->rcpt($recipient);
+ }
+
+ // Issue DATA command to client
+ $this->_connection->data($this->header . Zend_Mime::LINEEND . $this->body);
+ }
+
+ /**
+ * Format and fix headers
+ *
+ * Some SMTP servers do not strip BCC headers. Most clients do it themselves as do we.
+ *
+ * @access protected
+ * @param array $headers
+ * @return void
+ * @throws Zend_Transport_Exception
+ */
+ protected function _prepareHeaders($headers)
+ {
+ if (!$this->_mail) {
+ /**
+ * @see Zend_Mail_Transport_Exception
+ */
+ throw new Zend_Mail_Transport_Exception('_prepareHeaders requires a registered Zend_Mail object');
+ }
+
+ unset($headers['Bcc']);
+
+ // Prepare headers
+ parent::_prepareHeaders($headers);
+ }
+}
diff --git a/library/vendor/Zend/Mime.php b/library/vendor/Zend/Mime.php
new file mode 100644
index 0000000..5530b6c
--- /dev/null
+++ b/library/vendor/Zend/Mime.php
@@ -0,0 +1,670 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Support class for MultiPart Mime Messages
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mime
+{
+ const TYPE_OCTETSTREAM = 'application/octet-stream';
+ const TYPE_TEXT = 'text/plain';
+ const TYPE_HTML = 'text/html';
+ const ENCODING_7BIT = '7bit';
+ const ENCODING_8BIT = '8bit';
+ const ENCODING_QUOTEDPRINTABLE = 'quoted-printable';
+ const ENCODING_BASE64 = 'base64';
+ const DISPOSITION_ATTACHMENT = 'attachment';
+ const DISPOSITION_INLINE = 'inline';
+ const LINELENGTH = 72;
+ const LINEEND = "\n";
+ const MULTIPART_ALTERNATIVE = 'multipart/alternative';
+ const MULTIPART_MIXED = 'multipart/mixed';
+ const MULTIPART_RELATED = 'multipart/related';
+
+ /**
+ * Boundary
+ *
+ * @var null|string
+ */
+ protected $_boundary;
+
+ /**
+ * @var int
+ */
+ protected static $makeUnique = 0;
+
+ /**
+ * Lookup-Tables for QuotedPrintable
+ *
+ * @var array
+ */
+ public static $qpKeys = array(
+ "\x00",
+ "\x01",
+ "\x02",
+ "\x03",
+ "\x04",
+ "\x05",
+ "\x06",
+ "\x07",
+ "\x08",
+ "\x09",
+ "\x0A",
+ "\x0B",
+ "\x0C",
+ "\x0D",
+ "\x0E",
+ "\x0F",
+ "\x10",
+ "\x11",
+ "\x12",
+ "\x13",
+ "\x14",
+ "\x15",
+ "\x16",
+ "\x17",
+ "\x18",
+ "\x19",
+ "\x1A",
+ "\x1B",
+ "\x1C",
+ "\x1D",
+ "\x1E",
+ "\x1F",
+ "\x7F",
+ "\x80",
+ "\x81",
+ "\x82",
+ "\x83",
+ "\x84",
+ "\x85",
+ "\x86",
+ "\x87",
+ "\x88",
+ "\x89",
+ "\x8A",
+ "\x8B",
+ "\x8C",
+ "\x8D",
+ "\x8E",
+ "\x8F",
+ "\x90",
+ "\x91",
+ "\x92",
+ "\x93",
+ "\x94",
+ "\x95",
+ "\x96",
+ "\x97",
+ "\x98",
+ "\x99",
+ "\x9A",
+ "\x9B",
+ "\x9C",
+ "\x9D",
+ "\x9E",
+ "\x9F",
+ "\xA0",
+ "\xA1",
+ "\xA2",
+ "\xA3",
+ "\xA4",
+ "\xA5",
+ "\xA6",
+ "\xA7",
+ "\xA8",
+ "\xA9",
+ "\xAA",
+ "\xAB",
+ "\xAC",
+ "\xAD",
+ "\xAE",
+ "\xAF",
+ "\xB0",
+ "\xB1",
+ "\xB2",
+ "\xB3",
+ "\xB4",
+ "\xB5",
+ "\xB6",
+ "\xB7",
+ "\xB8",
+ "\xB9",
+ "\xBA",
+ "\xBB",
+ "\xBC",
+ "\xBD",
+ "\xBE",
+ "\xBF",
+ "\xC0",
+ "\xC1",
+ "\xC2",
+ "\xC3",
+ "\xC4",
+ "\xC5",
+ "\xC6",
+ "\xC7",
+ "\xC8",
+ "\xC9",
+ "\xCA",
+ "\xCB",
+ "\xCC",
+ "\xCD",
+ "\xCE",
+ "\xCF",
+ "\xD0",
+ "\xD1",
+ "\xD2",
+ "\xD3",
+ "\xD4",
+ "\xD5",
+ "\xD6",
+ "\xD7",
+ "\xD8",
+ "\xD9",
+ "\xDA",
+ "\xDB",
+ "\xDC",
+ "\xDD",
+ "\xDE",
+ "\xDF",
+ "\xE0",
+ "\xE1",
+ "\xE2",
+ "\xE3",
+ "\xE4",
+ "\xE5",
+ "\xE6",
+ "\xE7",
+ "\xE8",
+ "\xE9",
+ "\xEA",
+ "\xEB",
+ "\xEC",
+ "\xED",
+ "\xEE",
+ "\xEF",
+ "\xF0",
+ "\xF1",
+ "\xF2",
+ "\xF3",
+ "\xF4",
+ "\xF5",
+ "\xF6",
+ "\xF7",
+ "\xF8",
+ "\xF9",
+ "\xFA",
+ "\xFB",
+ "\xFC",
+ "\xFD",
+ "\xFE",
+ "\xFF"
+ );
+
+ /**
+ * @var array
+ */
+ public static $qpReplaceValues = array(
+ "=00",
+ "=01",
+ "=02",
+ "=03",
+ "=04",
+ "=05",
+ "=06",
+ "=07",
+ "=08",
+ "=09",
+ "=0A",
+ "=0B",
+ "=0C",
+ "=0D",
+ "=0E",
+ "=0F",
+ "=10",
+ "=11",
+ "=12",
+ "=13",
+ "=14",
+ "=15",
+ "=16",
+ "=17",
+ "=18",
+ "=19",
+ "=1A",
+ "=1B",
+ "=1C",
+ "=1D",
+ "=1E",
+ "=1F",
+ "=7F",
+ "=80",
+ "=81",
+ "=82",
+ "=83",
+ "=84",
+ "=85",
+ "=86",
+ "=87",
+ "=88",
+ "=89",
+ "=8A",
+ "=8B",
+ "=8C",
+ "=8D",
+ "=8E",
+ "=8F",
+ "=90",
+ "=91",
+ "=92",
+ "=93",
+ "=94",
+ "=95",
+ "=96",
+ "=97",
+ "=98",
+ "=99",
+ "=9A",
+ "=9B",
+ "=9C",
+ "=9D",
+ "=9E",
+ "=9F",
+ "=A0",
+ "=A1",
+ "=A2",
+ "=A3",
+ "=A4",
+ "=A5",
+ "=A6",
+ "=A7",
+ "=A8",
+ "=A9",
+ "=AA",
+ "=AB",
+ "=AC",
+ "=AD",
+ "=AE",
+ "=AF",
+ "=B0",
+ "=B1",
+ "=B2",
+ "=B3",
+ "=B4",
+ "=B5",
+ "=B6",
+ "=B7",
+ "=B8",
+ "=B9",
+ "=BA",
+ "=BB",
+ "=BC",
+ "=BD",
+ "=BE",
+ "=BF",
+ "=C0",
+ "=C1",
+ "=C2",
+ "=C3",
+ "=C4",
+ "=C5",
+ "=C6",
+ "=C7",
+ "=C8",
+ "=C9",
+ "=CA",
+ "=CB",
+ "=CC",
+ "=CD",
+ "=CE",
+ "=CF",
+ "=D0",
+ "=D1",
+ "=D2",
+ "=D3",
+ "=D4",
+ "=D5",
+ "=D6",
+ "=D7",
+ "=D8",
+ "=D9",
+ "=DA",
+ "=DB",
+ "=DC",
+ "=DD",
+ "=DE",
+ "=DF",
+ "=E0",
+ "=E1",
+ "=E2",
+ "=E3",
+ "=E4",
+ "=E5",
+ "=E6",
+ "=E7",
+ "=E8",
+ "=E9",
+ "=EA",
+ "=EB",
+ "=EC",
+ "=ED",
+ "=EE",
+ "=EF",
+ "=F0",
+ "=F1",
+ "=F2",
+ "=F3",
+ "=F4",
+ "=F5",
+ "=F6",
+ "=F7",
+ "=F8",
+ "=F9",
+ "=FA",
+ "=FB",
+ "=FC",
+ "=FD",
+ "=FE",
+ "=FF"
+ );
+
+ /**
+ * @var string
+ */
+ public static $qpKeysString =
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7F\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB\xBC\xBD\xBE\xBF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD7\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF";
+
+ /**
+ * Check if the given string is "printable"
+ *
+ * Checks that a string contains no unprintable characters. If this returns
+ * false, encode the string for secure delivery.
+ *
+ * @param string $str
+ * @return boolean
+ */
+ public static function isPrintable($str)
+ {
+ return (strcspn($str, self::$qpKeysString) == strlen($str));
+ }
+
+ /**
+ * Encode a given string with the QUOTED_PRINTABLE mechanism and wrap the lines.
+ *
+ * @param string $str
+ * @param int $lineLength Line length; defaults to {@link LINELENGTH}
+ * @param string $lineEnd Line end; defaults to {@link LINEEND}
+ * @return string
+ */
+ public static function encodeQuotedPrintable(
+ $str,
+ $lineLength = self::LINELENGTH,
+ $lineEnd = self::LINEEND
+ )
+ {
+ $out = '';
+ $str = self::_encodeQuotedPrintable($str);
+
+ // Split encoded text into separate lines
+ while (strlen($str) > 0) {
+ $ptr = strlen($str);
+ if ($ptr > $lineLength) {
+ $ptr = $lineLength;
+ }
+
+ // Ensure we are not splitting across an encoded character
+ $pos = strrpos(substr($str, 0, $ptr), '=');
+ if ($pos !== false && $pos >= $ptr - 2) {
+ $ptr = $pos;
+ }
+
+ // Check if there is a space at the end of the line and rewind
+ if ($ptr > 0 && $str[$ptr - 1] == ' ') {
+ --$ptr;
+ }
+
+ // Add string and continue
+ $out .= substr($str, 0, $ptr) . '=' . $lineEnd;
+ $str = substr($str, $ptr);
+ }
+
+ $out = rtrim($out, $lineEnd);
+ $out = rtrim($out, '=');
+
+ return $out;
+ }
+
+ /**
+ * Converts a string into quoted printable format.
+ *
+ * @param string $str
+ * @return string
+ */
+ private static function _encodeQuotedPrintable($str)
+ {
+ $str = str_replace('=', '=3D', $str);
+ $str = str_replace(self::$qpKeys, self::$qpReplaceValues, $str);
+ $str = rtrim($str);
+
+ return $str;
+ }
+
+ /**
+ * Encode a given string with the QUOTED_PRINTABLE mechanism for Mail Headers.
+ *
+ * Mail headers depend on an extended quoted printable algorithm otherwise
+ * a range of bugs can occur.
+ *
+ * @param string $str
+ * @param string $charset
+ * @param int $lineLength Line length; defaults to {@link LINELENGTH}
+ * @param string $lineEnd Line end; defaults to {@link LINEEND}
+ * @return string
+ */
+ public static function encodeQuotedPrintableHeader(
+ $str, $charset, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
+ )
+ {
+ // Reduce line-length by the length of the required delimiter, charsets and encoding
+ $prefix = sprintf('=?%s?Q?', $charset);
+ $lineLength = $lineLength - strlen($prefix) - 3;
+
+ $str = self::_encodeQuotedPrintable($str);
+
+ // Mail-Header required chars have to be encoded also:
+ $str = str_replace(
+ array('?', ' ', '_', ','), array('=3F', '=20', '=5F', '=2C'), $str
+ );
+
+ // initialize first line, we need it anyways
+ $lines = array(0 => "");
+
+ // Split encoded text into separate lines
+ $tmp = "";
+ while (strlen($str) > 0) {
+ $currentLine = max(count($lines) - 1, 0);
+ $token = self::getNextQuotedPrintableToken($str);
+ $str = substr($str, strlen($token));
+
+ $tmp .= $token;
+ if ($token == '=20') {
+ // only if we have a single char token or space, we can append the
+ // tempstring it to the current line or start a new line if necessary.
+ if (strlen($lines[$currentLine] . $tmp) > $lineLength) {
+ $lines[$currentLine + 1] = $tmp;
+ } else {
+ $lines[$currentLine] .= $tmp;
+ }
+ $tmp = "";
+ }
+ // don't forget to append the rest to the last line
+ if (strlen($str) == 0) {
+ $lines[$currentLine] .= $tmp;
+ }
+ }
+
+ // assemble the lines together by pre- and appending delimiters, charset, encoding.
+ for ($i = 0; $i < count($lines); $i++) {
+ $lines[$i] = " " . $prefix . $lines[$i] . "?=";
+ }
+ $str = trim(implode($lineEnd, $lines));
+
+ return $str;
+ }
+
+ /**
+ * Retrieves the first token from a quoted printable string.
+ *
+ * @param string $str
+ * @return string
+ */
+ private static function getNextQuotedPrintableToken($str)
+ {
+ if (substr($str, 0, 1) == "=") {
+ $token = substr($str, 0, 3);
+ } else {
+ $token = substr($str, 0, 1);
+ }
+
+ return $token;
+ }
+
+ /**
+ * Encode a given string in mail header compatible base64 encoding.
+ *
+ * @param string $str
+ * @param string $charset
+ * @param int $lineLength Line length; defaults to {@link LINELENGTH}
+ * @param string $lineEnd Line end; defaults to {@link LINEEND}
+ * @return string
+ */
+ public static function encodeBase64Header(
+ $str, $charset, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
+ )
+ {
+ $prefix = '=?' . $charset . '?B?';
+ $suffix = '?=';
+ $remainingLength = $lineLength - strlen($prefix) - strlen($suffix);
+
+ $encodedValue = self::encodeBase64($str, $remainingLength, $lineEnd);
+ $encodedValue = str_replace(
+ $lineEnd, $suffix . $lineEnd . ' ' . $prefix, $encodedValue
+ );
+ $encodedValue = $prefix . $encodedValue . $suffix;
+
+ return $encodedValue;
+ }
+
+ /**
+ * Encode a given string in base64 encoding and break lines
+ * according to the maximum linelength.
+ *
+ * @param string $str
+ * @param int $lineLength Line length; defaults to {@link LINELENGTH}
+ * @param string $lineEnd Line end; defaults to {@link LINEEND}
+ * @return string
+ */
+ public static function encodeBase64(
+ $str, $lineLength = self::LINELENGTH, $lineEnd = self::LINEEND
+ )
+ {
+ return rtrim(chunk_split(base64_encode($str), $lineLength, $lineEnd));
+ }
+
+ /**
+ * Constructor
+ *
+ * @param null|string $boundary
+ */
+ public function __construct($boundary = null)
+ {
+ // This string needs to be somewhat unique
+ if ($boundary === null) {
+ $this->_boundary = '=_' . md5(microtime(1) . self::$makeUnique++);
+ } else {
+ $this->_boundary = $boundary;
+ }
+ }
+
+ /**
+ * Encode the given string with the given encoding.
+ *
+ * @param string $str
+ * @param string $encoding
+ * @param string $EOL Line end; defaults to {@link Zend_Mime::LINEEND}
+ * @return string
+ */
+ public static function encode($str, $encoding, $EOL = self::LINEEND)
+ {
+ switch ($encoding) {
+ case self::ENCODING_BASE64:
+ return self::encodeBase64($str, self::LINELENGTH, $EOL);
+
+ case self::ENCODING_QUOTEDPRINTABLE:
+ return self::encodeQuotedPrintable($str, self::LINELENGTH, $EOL);
+
+ default:
+ /**
+ * @todo 7Bit and 8Bit is currently handled the same way.
+ */
+ return $str;
+ }
+ }
+
+ /**
+ * Return a MIME boundary
+ *
+ * @access public
+ * @return string
+ */
+ public function boundary()
+ {
+ return $this->_boundary;
+ }
+
+ /**
+ * Return a MIME boundary line
+ *
+ * @param string $EOL Line end; defaults to {@link LINEEND}
+ * @return string
+ */
+ public function boundaryLine($EOL = self::LINEEND)
+ {
+ return $EOL . '--' . $this->_boundary . $EOL;
+ }
+
+ /**
+ * Return MIME ending
+ *
+ * @param string $EOL Line end; defaults to {@link LINEEND}
+ * @return string
+ */
+ public function mimeEnd($EOL = self::LINEEND)
+ {
+ return $EOL . '--' . $this->_boundary . '--' . $EOL;
+ }
+}
diff --git a/library/vendor/Zend/Mime/Decode.php b/library/vendor/Zend/Mime/Decode.php
new file mode 100644
index 0000000..a63f861
--- /dev/null
+++ b/library/vendor/Zend/Mime/Decode.php
@@ -0,0 +1,275 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Mime
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mime_Decode
+{
+ /**
+ * Explode MIME multipart string into seperate parts
+ *
+ * Parts consist of the header and the body of each MIME part.
+ *
+ * @param string $body raw body of message
+ * @param string $boundary boundary as found in content-type
+ * @return array parts with content of each part, empty if no parts found
+ * @throws Zend_Exception
+ */
+ public static function splitMime($body, $boundary)
+ {
+ // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r?
+ $body = str_replace("\r", '', $body);
+
+ $start = 0;
+ $res = array();
+ // find every mime part limiter and cut out the
+ // string before it.
+ // the part before the first boundary string is discarded:
+ $p = strpos($body, '--' . $boundary . "\n", $start);
+ if ($p === false) {
+ // no parts found!
+ return array();
+ }
+
+ // position after first boundary line
+ $start = $p + 3 + strlen($boundary);
+
+ while (($p = strpos($body, '--' . $boundary . "\n", $start)) !== false) {
+ $res[] = substr($body, $start, $p-$start);
+ $start = $p + 3 + strlen($boundary);
+ }
+
+ // no more parts, find end boundary
+ $p = strpos($body, '--' . $boundary . '--', $start);
+ if ($p === false) {
+ throw new Zend_Exception('Not a valid Mime Message: End Missing');
+ }
+
+ // the remaining part also needs to be parsed:
+ $res[] = substr($body, $start, $p - $start);
+
+ return $res;
+ }
+
+ /**
+ * decodes a mime encoded String and returns a
+ * struct of parts with header and body
+ *
+ * @param string $message raw message content
+ * @param string $boundary boundary as found in content-type
+ * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND}
+ * @return array|null parts as array('header' => array(name => value), 'body' => content), null if no parts found
+ * @throws Zend_Exception
+ */
+ public static function splitMessageStruct(
+ $message, $boundary, $EOL = Zend_Mime::LINEEND
+ )
+ {
+ $parts = self::splitMime($message, $boundary);
+ if (count($parts) <= 0) {
+ return null;
+ }
+ $result = array();
+ foreach ($parts as $part) {
+ self::splitMessage($part, $headers, $body, $EOL);
+ $result[] = array(
+ 'header' => $headers,
+ 'body' => $body
+ );
+ }
+
+ return $result;
+ }
+
+ /**
+ * split a message in header and body part, if no header or an
+ * invalid header is found $headers is empty
+ *
+ * The charset of the returned headers depend on your iconv settings.
+ *
+ * @param string $message raw message with header and optional content
+ * @param array $headers output param, array with headers as array(name => value)
+ * @param string $body output param, content of message
+ * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND}
+ * @return null
+ */
+ public static function splitMessage(
+ $message, &$headers, &$body, $EOL = Zend_Mime::LINEEND
+ )
+ {
+ // check for valid header at first line
+ $firstline = strtok($message, "\n");
+ if (!preg_match('%^[^\s]+[^:]*:%', $firstline)) {
+ $headers = array();
+ // TODO: we're ignoring \r for now - is this function fast enough and is it safe to asume noone needs \r?
+ $body = str_replace(
+ array(
+ "\r",
+ "\n"
+ ), array(
+ '',
+ $EOL
+ ), $message
+ );
+
+ return;
+ }
+
+ // find an empty line between headers and body
+ // default is set new line
+ if (strpos($message, $EOL . $EOL)) {
+ list($headers, $body) = explode($EOL . $EOL, $message, 2);
+ // next is the standard new line
+ } else {
+ if ($EOL != "\r\n" && strpos($message, "\r\n\r\n")) {
+ list($headers, $body) = explode("\r\n\r\n", $message, 2);
+ // next is the other "standard" new line
+ } else {
+ if ($EOL != "\n" && strpos($message, "\n\n")) {
+ list($headers, $body) = explode("\n\n", $message, 2);
+ // at last resort find anything that looks like a new line
+ } else {
+ @list($headers, $body) =
+ @preg_split("%([\r\n]+)\\1%U", $message, 2);
+ }
+ }
+ }
+
+ $headers = iconv_mime_decode_headers(
+ $headers, ICONV_MIME_DECODE_CONTINUE_ON_ERROR
+ );
+
+ if ($headers === false) {
+ // an error occurs during the decoding
+ return;
+ }
+
+ // normalize header names
+ foreach ($headers as $name => $header) {
+ $lower = strtolower($name);
+ if ($lower == $name) {
+ continue;
+ }
+ unset($headers[$name]);
+ if (!isset($headers[$lower])) {
+ $headers[$lower] = $header;
+ continue;
+ }
+ if (is_array($headers[$lower])) {
+ $headers[$lower][] = $header;
+ continue;
+ }
+ $headers[$lower] = array(
+ $headers[$lower],
+ $header
+ );
+ }
+ }
+
+ /**
+ * split a content type in its different parts
+ *
+ * @param string $type content-type
+ * @param string $wantedPart the wanted part, else an array with all parts is returned
+ * @return string|array wanted part or all parts as array('type' => content-type, partname => value)
+ */
+ public static function splitContentType($type, $wantedPart = null)
+ {
+ return self::splitHeaderField($type, $wantedPart, 'type');
+ }
+
+ /**
+ * split a header field like content type in its different parts
+ *
+ * @param string $field
+ * @param string $wantedPart the wanted part, else an array with all parts is returned
+ * @param int|string $firstName key name for the first part
+ * @throws Zend_Exception
+ * @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
+ */
+ public static function splitHeaderField(
+ $field, $wantedPart = null, $firstName = 0
+ )
+ {
+ $wantedPart = strtolower($wantedPart);
+ $firstName = strtolower($firstName);
+
+ // special case - a bit optimized
+ if ($firstName === $wantedPart) {
+ $field = strtok($field, ';');
+
+ return $field[0] == '"' ? substr($field, 1, -1) : $field;
+ }
+
+ $field = $firstName . '=' . $field;
+ if (!preg_match_all('%([^=\s]+)\s*=\s*("[^"]+"|[^;]+)(;\s*|$)%', $field, $matches)) {
+ throw new Zend_Exception('not a valid header field');
+ }
+
+ if ($wantedPart) {
+ foreach ($matches[1] as $key => $name) {
+ if (strcasecmp($name, $wantedPart)) {
+ continue;
+ }
+ if ($matches[2][$key][0] != '"') {
+ return $matches[2][$key];
+ }
+
+ return substr($matches[2][$key], 1, -1);
+ }
+
+ return null;
+ }
+
+ $split = array();
+ foreach ($matches[1] as $key => $name) {
+ $name = strtolower($name);
+ if ($matches[2][$key][0] == '"') {
+ $split[$name] = substr($matches[2][$key], 1, -1);
+ } else {
+ $split[$name] = $matches[2][$key];
+ }
+ }
+
+ return $split;
+ }
+
+ /**
+ * decode a quoted printable encoded string
+ *
+ * The charset of the returned string depends on your iconv settings.
+ *
+ * @param string $string Encoded string
+ * @return string Decoded string
+ */
+ public static function decodeQuotedPrintable($string)
+ {
+ return quoted_printable_decode($string);
+ }
+}
diff --git a/library/vendor/Zend/Mime/Exception.php b/library/vendor/Zend/Mime/Exception.php
new file mode 100644
index 0000000..a803058
--- /dev/null
+++ b/library/vendor/Zend/Mime/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mime_Exception extends Zend_Exception
+{
+}
+
diff --git a/library/vendor/Zend/Mime/Message.php b/library/vendor/Zend/Mime/Message.php
new file mode 100644
index 0000000..6082890
--- /dev/null
+++ b/library/vendor/Zend/Mime/Message.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Mime
+ */
+
+/**
+ * Zend_Mime_Part
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mime_Message
+{
+ /**
+ * The Zend_Mime_Parts of the message
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * The Zend_Mime object for the message
+ *
+ * @var Zend_Mime|null
+ */
+ protected $_mime = null;
+
+ /**
+ * Returns the list of all Zend_Mime_Parts in the message
+ *
+ * @return array of Zend_Mime_Part
+ */
+ public function getParts()
+ {
+ return $this->_parts;
+ }
+
+ /**
+ * Sets the given array of Zend_Mime_Parts as the array for the message
+ *
+ * @param array $parts
+ */
+ public function setParts($parts)
+ {
+ $this->_parts = $parts;
+ }
+
+ /**
+ * Append a new Zend_Mime_Part to the current message
+ *
+ * @param Zend_Mime_Part $part
+ */
+ public function addPart(Zend_Mime_Part $part)
+ {
+ /**
+ * @todo check for duplicate object handle
+ */
+ $this->_parts[] = $part;
+ }
+
+ /**
+ * Check if message needs to be sent as multipart
+ * MIME message or if it has only one part.
+ *
+ * @return boolean
+ */
+ public function isMultiPart()
+ {
+ return (count($this->_parts) > 1);
+ }
+
+ /**
+ * Set Zend_Mime object for the message
+ *
+ * This can be used to set the boundary specifically or to use a subclass of
+ * Zend_Mime for generating the boundary.
+ *
+ * @param Zend_Mime $mime
+ */
+ public function setMime(Zend_Mime $mime)
+ {
+ $this->_mime = $mime;
+ }
+
+ /**
+ * Returns the Zend_Mime object in use by the message
+ *
+ * If the object was not present, it is created and returned. Can be used to
+ * determine the boundary used in this message.
+ *
+ * @return Zend_Mime
+ */
+ public function getMime()
+ {
+ if ($this->_mime === null) {
+ $this->_mime = new Zend_Mime();
+ }
+
+ return $this->_mime;
+ }
+
+ /**
+ * Generate MIME-compliant message from the current configuration
+ *
+ * This can be a multipart message if more than one MIME part was added. If
+ * only one part is present, the content of this part is returned. If no
+ * part had been added, an empty string is returned.
+ *
+ * Parts are seperated by the mime boundary as defined in Zend_Mime. If
+ * {@link setMime()} has been called before this method, the Zend_Mime
+ * object set by this call will be used. Otherwise, a new Zend_Mime object
+ * is generated and used.
+ *
+ * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND}
+ * @return string
+ */
+ public function generateMessage($EOL = Zend_Mime::LINEEND)
+ {
+ if (!$this->isMultiPart()) {
+ $body = array_shift($this->_parts);
+ $body = $body->getContent($EOL);
+ } else {
+ $mime = $this->getMime();
+
+ $boundaryLine = $mime->boundaryLine($EOL);
+ $body = 'This is a message in Mime Format. If you see this, '
+ . "your mail reader does not support this format." . $EOL;
+
+ foreach (array_keys($this->_parts) as $p) {
+ $body .= $boundaryLine
+ . $this->getPartHeaders($p, $EOL)
+ . $EOL
+ . $this->getPartContent($p, $EOL);
+ }
+
+ $body .= $mime->mimeEnd($EOL);
+ }
+
+ return trim($body);
+ }
+
+ /**
+ * Get the headers of a given part as an array
+ *
+ * @param int $partnum
+ * @return array
+ */
+ public function getPartHeadersArray($partnum)
+ {
+ return $this->_parts[$partnum]->getHeadersArray();
+ }
+
+ /**
+ * Get the headers of a given part as a string
+ *
+ * @param int $partnum
+ * @param string $EOL
+ * @return string
+ */
+ public function getPartHeaders($partnum, $EOL = Zend_Mime::LINEEND)
+ {
+ return $this->_parts[$partnum]->getHeaders($EOL);
+ }
+
+ /**
+ * Get the (encoded) content of a given part as a string
+ *
+ * @param int $partnum
+ * @param string $EOL
+ * @return string
+ */
+ public function getPartContent($partnum, $EOL = Zend_Mime::LINEEND)
+ {
+ return $this->_parts[$partnum]->getContent($EOL);
+ }
+
+ /**
+ * Explode MIME multipart string into seperate parts
+ *
+ * Parts consist of the header and the body of each MIME part.
+ *
+ * @param string $body
+ * @param string $boundary
+ * @throws Zend_Exception
+ * @return array
+ */
+ protected static function _disassembleMime($body, $boundary)
+ {
+ $start = 0;
+ $res = array();
+ // find every mime part limiter and cut out the
+ // string before it.
+ // the part before the first boundary string is discarded:
+ $p = strpos($body, '--' . $boundary . "\n", $start);
+ if ($p === false) {
+ // no parts found!
+ return array();
+ }
+
+ // position after first boundary line
+ $start = $p + 3 + strlen($boundary);
+
+ while (($p = strpos($body, '--' . $boundary . "\n", $start))
+ !== false) {
+ $res[] = substr($body, $start, $p - $start);
+ $start = $p + 3 + strlen($boundary);
+ }
+
+ // no more parts, find end boundary
+ $p = strpos($body, '--' . $boundary . '--', $start);
+ if ($p === false) {
+ throw new Zend_Exception('Not a valid Mime Message: End Missing');
+ }
+
+ // the remaining part also needs to be parsed:
+ $res[] = substr($body, $start, $p - $start);
+
+ return $res;
+ }
+
+ /**
+ * Decodes a MIME encoded string and returns a Zend_Mime_Message object with
+ * all the MIME parts set according to the given string
+ *
+ * @param string $message
+ * @param string $boundary
+ * @param string $EOL EOL string; defaults to {@link Zend_Mime::LINEEND}
+ * @throws Zend_Exception
+ * @return Zend_Mime_Message
+ */
+ public static function createFromMessage(
+ $message, $boundary, $EOL = Zend_Mime::LINEEND
+ )
+ {
+ $parts = Zend_Mime_Decode::splitMessageStruct($message, $boundary, $EOL);
+
+ $res = new self();
+ foreach ($parts as $part) {
+ // now we build a new MimePart for the current Message Part:
+ $newPart = new Zend_Mime_Part($part['body']);
+ foreach ($part['header'] as $key => $value) {
+ /**
+ * @todo check for characterset and filename
+ */
+ switch (strtolower($key)) {
+ case 'content-type':
+ $newPart->type = $value;
+ break;
+ case 'content-transfer-encoding':
+ $newPart->encoding = $value;
+ break;
+ case 'content-id':
+ $newPart->id = trim($value, '<>');
+ break;
+ case 'content-disposition':
+ $newPart->disposition = $value;
+ break;
+ case 'content-description':
+ $newPart->description = $value;
+ break;
+ case 'content-location':
+ $newPart->location = $value;
+ break;
+ case 'content-language':
+ $newPart->language = $value;
+ break;
+ default:
+ throw new Zend_Exception(
+ 'Unknown header ignored for MimePart:' . $key
+ );
+ }
+ }
+ $res->addPart($newPart);
+ }
+
+ return $res;
+ }
+}
diff --git a/library/vendor/Zend/Mime/Part.php b/library/vendor/Zend/Mime/Part.php
new file mode 100644
index 0000000..852fb56
--- /dev/null
+++ b/library/vendor/Zend/Mime/Part.php
@@ -0,0 +1,329 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Mime
+ */
+
+/**
+ * Class representing a MIME part.
+ *
+ * @category Zend
+ * @package Zend_Mime
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Mime_Part
+{
+
+ /**
+ * Type
+ *
+ * @var string
+ */
+ public $type = Zend_Mime::TYPE_OCTETSTREAM;
+
+ /**
+ * Encoding
+ *
+ * @var string
+ */
+ public $encoding = Zend_Mime::ENCODING_8BIT;
+
+ /**
+ * ID
+ *
+ * @var string
+ */
+ public $id;
+
+ /**
+ * Disposition
+ *
+ * @var string
+ */
+ public $disposition;
+
+ /**
+ * Filename
+ *
+ * @var string
+ */
+ public $filename;
+
+ /**
+ * Description
+ *
+ * @var string
+ */
+ public $description;
+
+ /**
+ * Character set
+ *
+ * @var string
+ */
+ public $charset;
+
+ /**
+ * Boundary
+ *
+ * @var string
+ */
+ public $boundary;
+
+ /**
+ * Location
+ *
+ * @var string
+ */
+ public $location;
+
+ /**
+ * Language
+ *
+ * @var string
+ */
+ public $language;
+
+ /**
+ * Content
+ *
+ * @var mixed
+ */
+ protected $_content;
+
+ /**
+ * @var bool
+ */
+ protected $_isStream = false;
+
+ /**
+ * create a new Mime Part.
+ * The (unencoded) content of the Part as passed
+ * as a string or stream
+ *
+ * @param mixed $content String or Stream containing the content
+ */
+ public function __construct($content)
+ {
+ $this->_content = $content;
+ if (is_resource($content)) {
+ $this->_isStream = true;
+ }
+ }
+
+ /**
+ * @todo setters/getters
+ * @todo error checking for setting $type
+ * @todo error checking for setting $encoding
+ */
+
+ /**
+ * check if this part can be read as a stream.
+ * if true, getEncodedStream can be called, otherwise
+ * only getContent can be used to fetch the encoded
+ * content of the part
+ *
+ * @return bool
+ */
+ public function isStream()
+ {
+ return $this->_isStream;
+ }
+
+ /**
+ * if this was created with a stream, return a filtered stream for
+ * reading the content. very useful for large file attachments.
+ *
+ * @return mixed Stream
+ * @throws Zend_Mime_Exception if not a stream or unable to append filter
+ */
+ public function getEncodedStream()
+ {
+ if (!$this->_isStream) {
+ throw new Zend_Mime_Exception(
+ 'Attempt to get a stream from a string part'
+ );
+ }
+
+ //stream_filter_remove(); // ??? is that right?
+ switch ($this->encoding) {
+ case Zend_Mime::ENCODING_QUOTEDPRINTABLE:
+ $filter = stream_filter_append(
+ $this->_content,
+ 'convert.quoted-printable-encode',
+ STREAM_FILTER_READ,
+ array(
+ 'line-length' => 76,
+ 'line-break-chars' => Zend_Mime::LINEEND
+ )
+ );
+ if (!is_resource($filter)) {
+ throw new Zend_Mime_Exception(
+ 'Failed to append quoted-printable filter'
+ );
+ }
+ break;
+
+ case Zend_Mime::ENCODING_BASE64:
+ $filter = stream_filter_append(
+ $this->_content,
+ 'convert.base64-encode',
+ STREAM_FILTER_READ,
+ array(
+ 'line-length' => 76,
+ 'line-break-chars' => Zend_Mime::LINEEND
+ )
+ );
+ if (!is_resource($filter)) {
+ throw new Zend_Mime_Exception(
+ 'Failed to append base64 filter'
+ );
+ }
+ break;
+
+ default:
+ }
+
+ return $this->_content;
+ }
+
+ /**
+ * Get the Content of the current Mime Part in the given encoding.
+ *
+ * @param string $EOL Line end; defaults to {@link Zend_Mime::LINEEND}
+ * @throws Zend_Mime_Exception
+ * @return string
+ */
+ public function getContent($EOL = Zend_Mime::LINEEND)
+ {
+ if ($this->_isStream) {
+ return stream_get_contents($this->getEncodedStream());
+ } else {
+ return Zend_Mime::encode($this->_content, $this->encoding, $EOL);
+ }
+ }
+
+ /**
+ * Get the RAW unencoded content from this part
+ *
+ * @return string
+ */
+ public function getRawContent()
+ {
+ if ($this->_isStream) {
+ return stream_get_contents($this->_content);
+ } else {
+ return $this->_content;
+ }
+ }
+
+ /**
+ * Create and return the array of headers for this MIME part
+ *
+ * @param string $EOL Line end; defaults to {@link Zend_Mime::LINEEND}
+ * @return array
+ */
+ public function getHeadersArray($EOL = Zend_Mime::LINEEND)
+ {
+ $headers = array();
+
+ $contentType = $this->type;
+ if ($this->charset) {
+ $contentType .= '; charset=' . $this->charset;
+ }
+
+ if ($this->boundary) {
+ $contentType .= ';' . $EOL
+ . " boundary=\"" . $this->boundary . '"';
+ }
+
+ $headers[] = array(
+ 'Content-Type',
+ $contentType
+ );
+
+ if ($this->encoding) {
+ $headers[] = array(
+ 'Content-Transfer-Encoding',
+ $this->encoding
+ );
+ }
+
+ if ($this->id) {
+ $headers[] = array(
+ 'Content-ID',
+ '<' . $this->id . '>'
+ );
+ }
+
+ if ($this->disposition) {
+ $disposition = $this->disposition;
+ if ($this->filename) {
+ $disposition .= '; filename="' . $this->filename . '"';
+ }
+ $headers[] = array(
+ 'Content-Disposition',
+ $disposition
+ );
+ }
+
+ if ($this->description) {
+ $headers[] = array(
+ 'Content-Description',
+ $this->description
+ );
+ }
+
+ if ($this->location) {
+ $headers[] = array(
+ 'Content-Location',
+ $this->location
+ );
+ }
+
+ if ($this->language) {
+ $headers[] = array(
+ 'Content-Language',
+ $this->language
+ );
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Return the headers for this part as a string
+ *
+ * @param string $EOL Line end; defaults to {@link Zend_Mime::LINEEND}
+ * @return string
+ */
+ public function getHeaders($EOL = Zend_Mime::LINEEND)
+ {
+ $res = '';
+ foreach ($this->getHeadersArray($EOL) as $header) {
+ $res .= $header[0] . ': ' . $header[1] . $EOL;
+ }
+
+ return $res;
+ }
+}
diff --git a/library/vendor/Zend/Paginator.php b/library/vendor/Zend/Paginator.php
new file mode 100644
index 0000000..e96f882
--- /dev/null
+++ b/library/vendor/Zend/Paginator.php
@@ -0,0 +1,1164 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Loader_PluginLoader
+ */
+
+/**
+ * @see Zend_Json
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator implements Countable, IteratorAggregate
+{
+ /**
+ * Specifies that the factory should try to detect the proper adapter type first
+ *
+ * @var string
+ */
+ const INTERNAL_ADAPTER = 'Zend_Paginator_Adapter_Internal';
+
+ /**
+ * The cache tag prefix used to namespace Paginator results in the cache
+ *
+ */
+ const CACHE_TAG_PREFIX = 'Zend_Paginator_';
+
+ /**
+ * Adapter plugin loader
+ *
+ * @var Zend_Loader_PluginLoader
+ */
+ protected static $_adapterLoader = null;
+
+ /**
+ * Configuration file
+ *
+ * @var Zend_Config
+ */
+ protected static $_config = null;
+
+ /**
+ * Default scrolling style
+ *
+ * @var string
+ */
+ protected static $_defaultScrollingStyle = 'Sliding';
+
+ /**
+ * Default item count per page
+ *
+ * @var int
+ */
+ protected static $_defaultItemCountPerPage = 10;
+
+ /**
+ * Default number of local pages (i.e., the number of discretes
+ * page numbers that will be displayed, including the current
+ * page number)
+ *
+ * @var int
+ */
+ protected static $_defaultPageRange = 10;
+
+ /**
+ * Scrolling style plugin loader
+ *
+ * @var Zend_Loader_PluginLoader
+ */
+ protected static $_scrollingStyleLoader = null;
+
+ /**
+ * Cache object
+ *
+ * @var Zend_Cache_Core
+ */
+ protected static $_cache;
+
+ /**
+ * Enable or disable the cache by Zend_Paginator instance
+ *
+ * @var bool
+ */
+ protected $_cacheEnabled = true;
+
+ /**
+ * Adapter
+ *
+ * @var Zend_Paginator_Adapter_Interface
+ */
+ protected $_adapter = null;
+
+ /**
+ * Number of items in the current page
+ *
+ * @var integer
+ */
+ protected $_currentItemCount = null;
+
+ /**
+ * Current page items
+ *
+ * @var Traversable
+ */
+ protected $_currentItems = null;
+
+ /**
+ * Current page number (starting from 1)
+ *
+ * @var integer
+ */
+ protected $_currentPageNumber = 1;
+
+ /**
+ * Result filter
+ *
+ * @var Zend_Filter_Interface
+ */
+ protected $_filter = null;
+
+ /**
+ * Number of items per page
+ *
+ * @var integer
+ */
+ protected $_itemCountPerPage = null;
+
+ /**
+ * Number of pages
+ *
+ * @var integer
+ */
+ protected $_pageCount = null;
+
+ /**
+ * Number of local pages (i.e., the number of discrete page numbers
+ * that will be displayed, including the current page number)
+ *
+ * @var integer
+ */
+ protected $_pageRange = null;
+
+ /**
+ * Pages
+ *
+ * @var array
+ */
+ protected $_pages = null;
+
+ /**
+ * View instance used for self rendering
+ *
+ * @var Zend_View_Interface
+ */
+ protected $_view = null;
+
+ /**
+ * Adds an adapter prefix path to the plugin loader.
+ *
+ * @param string $prefix
+ * @param string $path
+ */
+ public static function addAdapterPrefixPath($prefix, $path)
+ {
+ self::getAdapterLoader()->addPrefixPath($prefix, $path);
+ }
+
+ /**
+ * Adds an array of adapter prefix paths to the plugin
+ * loader.
+ *
+ * <code>
+ * $prefixPaths = array(
+ * 'My_Paginator_Adapter' => 'My/Paginator/Adapter/',
+ * 'Your_Paginator_Adapter' => 'Your/Paginator/Adapter/'
+ * );
+ * </code>
+ *
+ * @param array $prefixPaths
+ */
+ public static function addAdapterPrefixPaths(array $prefixPaths)
+ {
+ if (isset($prefixPaths['prefix']) && isset($prefixPaths['path'])) {
+ self::addAdapterPrefixPath($prefixPaths['prefix'], $prefixPaths['path']);
+ } else {
+ foreach ($prefixPaths as $prefix => $path) {
+ if (is_array($path) && isset($path['prefix']) && isset($path['path'])) {
+ $prefix = $path['prefix'];
+ $path = $path['path'];
+ }
+
+ self::addAdapterPrefixPath($prefix, $path);
+ }
+ }
+ }
+
+ /**
+ * Adds a scrolling style prefix path to the plugin loader.
+ *
+ * @param string $prefix
+ * @param string $path
+ */
+ public static function addScrollingStylePrefixPath($prefix, $path)
+ {
+ self::getScrollingStyleLoader()->addPrefixPath($prefix, $path);
+ }
+
+ /**
+ * Adds an array of scrolling style prefix paths to the plugin
+ * loader.
+ *
+ * <code>
+ * $prefixPaths = array(
+ * 'My_Paginator_ScrollingStyle' => 'My/Paginator/ScrollingStyle/',
+ * 'Your_Paginator_ScrollingStyle' => 'Your/Paginator/ScrollingStyle/'
+ * );
+ * </code>
+ *
+ * @param array $prefixPaths
+ */
+ public static function addScrollingStylePrefixPaths(array $prefixPaths)
+ {
+ if (isset($prefixPaths['prefix']) && isset($prefixPaths['path'])) {
+ self::addScrollingStylePrefixPath($prefixPaths['prefix'], $prefixPaths['path']);
+ } else {
+ foreach ($prefixPaths as $prefix => $path) {
+ if (is_array($path) && isset($path['prefix']) && isset($path['path'])) {
+ $prefix = $path['prefix'];
+ $path = $path['path'];
+ }
+
+ self::addScrollingStylePrefixPath($prefix, $path);
+ }
+ }
+ }
+
+ /**
+ * Factory.
+ *
+ * @param mixed $data
+ * @param string $adapter
+ * @param array $prefixPaths
+ * @return Zend_Paginator
+ */
+ public static function factory($data, $adapter = self::INTERNAL_ADAPTER,
+ array $prefixPaths = null)
+ {
+ if ($data instanceof Zend_Paginator_AdapterAggregate) {
+ return new self($data->getPaginatorAdapter());
+ } else {
+ if ($adapter == self::INTERNAL_ADAPTER) {
+ if (is_array($data)) {
+ $adapter = 'Array';
+ } else if ($data instanceof Zend_Db_Table_Select) {
+ $adapter = 'DbTableSelect';
+ } else if ($data instanceof Zend_Db_Select) {
+ $adapter = 'DbSelect';
+ } else if ($data instanceof Iterator) {
+ $adapter = 'Iterator';
+ } else if (is_integer($data)) {
+ $adapter = 'Null';
+ } else {
+ $type = (is_object($data)) ? get_class($data) : gettype($data);
+
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception('No adapter for type ' . $type);
+ }
+ }
+
+ $pluginLoader = self::getAdapterLoader();
+
+ if (null !== $prefixPaths) {
+ foreach ($prefixPaths as $prefix => $path) {
+ $pluginLoader->addPrefixPath($prefix, $path);
+ }
+ }
+
+ $adapterClassName = $pluginLoader->load($adapter);
+
+ return new self(new $adapterClassName($data));
+ }
+ }
+
+ /**
+ * Returns the adapter loader. If it doesn't exist it's created.
+ *
+ * @return Zend_Loader_PluginLoader
+ */
+ public static function getAdapterLoader()
+ {
+ if (self::$_adapterLoader === null) {
+ self::$_adapterLoader = new Zend_Loader_PluginLoader(
+ array('Zend_Paginator_Adapter' => 'Zend/Paginator/Adapter')
+ );
+ }
+
+ return self::$_adapterLoader;
+ }
+
+ /**
+ * Set a global config
+ *
+ * @param Zend_Config $config
+ */
+ public static function setConfig(Zend_Config $config)
+ {
+ self::$_config = $config;
+
+ $adapterPaths = $config->get('adapterpaths');
+
+ if ($adapterPaths != null) {
+ self::addAdapterPrefixPaths($adapterPaths->adapterpath->toArray());
+ }
+
+ $prefixPaths = $config->get('prefixpaths');
+
+ if ($prefixPaths != null) {
+ self::addScrollingStylePrefixPaths($prefixPaths->prefixpath->toArray());
+ }
+
+ $scrollingStyle = $config->get('scrollingstyle');
+
+ if ($scrollingStyle != null) {
+ self::setDefaultScrollingStyle($scrollingStyle);
+ }
+ }
+
+ /**
+ * Returns the default scrolling style.
+ *
+ * @return string
+ */
+ public static function getDefaultScrollingStyle()
+ {
+ return self::$_defaultScrollingStyle;
+ }
+
+ /**
+ * Get the default item count per page
+ *
+ * @return int
+ */
+ public static function getDefaultItemCountPerPage()
+ {
+ return self::$_defaultItemCountPerPage;
+ }
+
+ /**
+ * Set the default item count per page
+ *
+ * @param int $count
+ */
+ public static function setDefaultItemCountPerPage($count)
+ {
+ self::$_defaultItemCountPerPage = (int) $count;
+ }
+
+ /**
+ * Get the default page range
+ *
+ * @return int
+ */
+ public static function getDefaultPageRange()
+ {
+ return self::$_defaultPageRange;
+ }
+
+ /**
+ * Set the default page range
+ *
+ * @param int $count
+ */
+ public static function setDefaultPageRange($count)
+ {
+ self::$_defaultPageRange = (int) $count;
+ }
+
+ /**
+ * Sets a cache object
+ *
+ * @param Zend_Cache_Core $cache
+ */
+ public static function setCache(Zend_Cache_Core $cache)
+ {
+ self::$_cache = $cache;
+ }
+
+ /**
+ * Sets the default scrolling style.
+ *
+ * @param string $scrollingStyle
+ */
+ public static function setDefaultScrollingStyle($scrollingStyle = 'Sliding')
+ {
+ self::$_defaultScrollingStyle = $scrollingStyle;
+ }
+
+ /**
+ * Returns the scrolling style loader. If it doesn't exist it's
+ * created.
+ *
+ * @return Zend_Loader_PluginLoader
+ */
+ public static function getScrollingStyleLoader()
+ {
+ if (self::$_scrollingStyleLoader === null) {
+ self::$_scrollingStyleLoader = new Zend_Loader_PluginLoader(
+ array('Zend_Paginator_ScrollingStyle' => 'Zend/Paginator/ScrollingStyle')
+ );
+ }
+
+ return self::$_scrollingStyleLoader;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param Zend_Paginator_Adapter_Interface|Zend_Paginator_AdapterAggregate $adapter
+ */
+ public function __construct($adapter)
+ {
+ if ($adapter instanceof Zend_Paginator_Adapter_Interface) {
+ $this->_adapter = $adapter;
+ } else if ($adapter instanceof Zend_Paginator_AdapterAggregate) {
+ $this->_adapter = $adapter->getPaginatorAdapter();
+ } else {
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception(
+ 'Zend_Paginator only accepts instances of the type ' .
+ 'Zend_Paginator_Adapter_Interface or Zend_Paginator_AdapterAggregate.'
+ );
+ }
+
+ $config = self::$_config;
+
+ if ($config != null) {
+ $setupMethods = array('ItemCountPerPage', 'PageRange');
+
+ foreach ($setupMethods as $setupMethod) {
+ $value = $config->get(strtolower($setupMethod));
+
+ if ($value != null) {
+ $setupMethod = 'set' . $setupMethod;
+ $this->$setupMethod($value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Serializes the object as a string. Proxies to {@link render()}.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ $return = $this->render();
+ return $return;
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ }
+
+ return '';
+ }
+
+ /**
+ * Enables/Disables the cache for this instance
+ *
+ * @param bool $enable
+ * @return Zend_Paginator
+ */
+ public function setCacheEnabled($enable)
+ {
+ $this->_cacheEnabled = (bool)$enable;
+ return $this;
+ }
+
+ /**
+ * Returns the number of pages.
+ *
+ * @return integer
+ */
+ public function count(): int
+ {
+ if (!$this->_pageCount) {
+ $this->_pageCount = $this->_calculatePageCount();
+ }
+
+ return $this->_pageCount;
+ }
+
+ /**
+ * Returns the total number of items available. Uses cache if caching is enabled.
+ *
+ * @return integer
+ */
+ public function getTotalItemCount()
+ {
+ if (!$this->_cacheEnabled()) {
+ return count($this->getAdapter());
+ } else {
+ $cacheId = md5($this->_getCacheInternalId(). '_itemCount');
+ $itemCount = self::$_cache->load($cacheId);
+
+ if ($itemCount === false) {
+ $itemCount = count($this->getAdapter());
+
+ self::$_cache->save($itemCount, $cacheId, array($this->_getCacheInternalId()));
+ }
+
+ return $itemCount;
+ }
+ }
+
+ /**
+ * Clear the page item cache.
+ *
+ * @param int $pageNumber
+ * @return Zend_Paginator
+ */
+ public function clearPageItemCache($pageNumber = null)
+ {
+ if (!$this->_cacheEnabled()) {
+ return $this;
+ }
+
+ if (null === $pageNumber) {
+ foreach (self::$_cache->getIdsMatchingTags(array($this->_getCacheInternalId())) as $id) {
+ if (preg_match('|'.self::CACHE_TAG_PREFIX."(\d+)_.*|", $id, $page)) {
+ self::$_cache->remove($this->_getCacheId($page[1]));
+ }
+ }
+ } else {
+ $cleanId = $this->_getCacheId($pageNumber);
+ self::$_cache->remove($cleanId);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the absolute item number for the specified item.
+ *
+ * @param integer $relativeItemNumber Relative item number
+ * @param integer $pageNumber Page number
+ * @return integer
+ */
+ public function getAbsoluteItemNumber($relativeItemNumber, $pageNumber = null)
+ {
+ $relativeItemNumber = $this->normalizeItemNumber($relativeItemNumber);
+
+ if ($pageNumber == null) {
+ $pageNumber = $this->getCurrentPageNumber();
+ }
+
+ $pageNumber = $this->normalizePageNumber($pageNumber);
+
+ return (($pageNumber - 1) * $this->getItemCountPerPage()) + $relativeItemNumber;
+ }
+
+ /**
+ * Returns the adapter.
+ *
+ * @return Zend_Paginator_Adapter_Interface
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Returns the number of items for the current page.
+ *
+ * @return integer
+ */
+ public function getCurrentItemCount()
+ {
+ if ($this->_currentItemCount === null) {
+ $this->_currentItemCount = $this->getItemCount($this->getCurrentItems());
+ }
+
+ return $this->_currentItemCount;
+ }
+
+ /**
+ * Returns the items for the current page.
+ *
+ * @return Traversable
+ */
+ public function getCurrentItems()
+ {
+ if ($this->_currentItems === null) {
+ $this->_currentItems = $this->getItemsByPage($this->getCurrentPageNumber());
+ }
+
+ return $this->_currentItems;
+ }
+
+ /**
+ * Returns the current page number.
+ *
+ * @return integer
+ */
+ public function getCurrentPageNumber()
+ {
+ return $this->normalizePageNumber($this->_currentPageNumber);
+ }
+
+ /**
+ * Sets the current page number.
+ *
+ * @param integer $pageNumber Page number
+ * @return Zend_Paginator $this
+ */
+ public function setCurrentPageNumber($pageNumber)
+ {
+ $this->_currentPageNumber = (integer) $pageNumber;
+ $this->_currentItems = null;
+ $this->_currentItemCount = null;
+
+ return $this;
+ }
+
+ /**
+ * Get the filter
+ *
+ * @return Zend_Filter_Interface
+ */
+ public function getFilter()
+ {
+ return $this->_filter;
+ }
+
+ /**
+ * Set a filter chain
+ *
+ * @param Zend_Filter_Interface $filter
+ * @return Zend_Paginator
+ */
+ public function setFilter(Zend_Filter_Interface $filter)
+ {
+ $this->_filter = $filter;
+
+ return $this;
+ }
+
+ /**
+ * Returns an item from a page. The current page is used if there's no
+ * page sepcified.
+ *
+ * @param integer $itemNumber Item number (1 to itemCountPerPage)
+ * @param integer $pageNumber
+ * @return mixed
+ */
+ public function getItem($itemNumber, $pageNumber = null)
+ {
+ if ($pageNumber == null) {
+ $pageNumber = $this->getCurrentPageNumber();
+ } else if ($pageNumber < 0) {
+ $pageNumber = ($this->count() + 1) + $pageNumber;
+ }
+
+ $page = $this->getItemsByPage($pageNumber);
+ $itemCount = $this->getItemCount($page);
+
+ if ($itemCount == 0) {
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception('Page ' . $pageNumber . ' does not exist');
+ }
+
+ if ($itemNumber < 0) {
+ $itemNumber = ($itemCount + 1) + $itemNumber;
+ }
+
+ $itemNumber = $this->normalizeItemNumber($itemNumber);
+
+ if ($itemNumber > $itemCount) {
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception('Page ' . $pageNumber . ' does not'
+ . ' contain item number ' . $itemNumber);
+ }
+
+ return $page[$itemNumber - 1];
+ }
+
+ /**
+ * Returns the number of items per page.
+ *
+ * @return integer
+ */
+ public function getItemCountPerPage()
+ {
+ if (empty($this->_itemCountPerPage)) {
+ $this->_itemCountPerPage = self::getDefaultItemCountPerPage();
+ }
+
+ return $this->_itemCountPerPage;
+ }
+
+ /**
+ * Sets the number of items per page.
+ *
+ * @param integer $itemCountPerPage
+ * @return Zend_Paginator $this
+ */
+ public function setItemCountPerPage($itemCountPerPage = -1)
+ {
+ $this->_itemCountPerPage = (integer) $itemCountPerPage;
+ if ($this->_itemCountPerPage < 1) {
+ $this->_itemCountPerPage = $this->getTotalItemCount();
+ }
+ $this->_pageCount = $this->_calculatePageCount();
+ $this->_currentItems = null;
+ $this->_currentItemCount = null;
+
+ return $this;
+ }
+
+ /**
+ * Returns the number of items in a collection.
+ *
+ * @param mixed $items Items
+ * @return integer
+ */
+ public function getItemCount($items)
+ {
+ $itemCount = 0;
+
+ if (is_array($items) || $items instanceof Countable) {
+ $itemCount = count($items);
+ } else { // $items is something like LimitIterator
+ $itemCount = iterator_count($items);
+ }
+
+ return $itemCount;
+ }
+
+ /**
+ * Returns the items for a given page.
+ *
+ * @return Traversable
+ */
+ public function getItemsByPage($pageNumber)
+ {
+ $pageNumber = $this->normalizePageNumber($pageNumber);
+
+ if ($this->_cacheEnabled()) {
+ $data = self::$_cache->load($this->_getCacheId($pageNumber));
+ if ($data !== false) {
+ return $data;
+ }
+ }
+
+ $offset = ($pageNumber - 1) * $this->getItemCountPerPage();
+
+ $items = $this->_adapter->getItems($offset, $this->getItemCountPerPage());
+
+ $filter = $this->getFilter();
+
+ if ($filter !== null) {
+ $items = $filter->filter($items);
+ }
+
+ if (!$items instanceof Traversable) {
+ $items = new ArrayIterator($items);
+ }
+
+ if ($this->_cacheEnabled()) {
+ self::$_cache->save($items, $this->_getCacheId($pageNumber), array($this->_getCacheInternalId()));
+ }
+
+ return $items;
+ }
+
+ /**
+ * Returns a foreach-compatible iterator.
+ *
+ * @return Traversable
+ */
+ public function getIterator(): Traversable
+ {
+ return $this->getCurrentItems();
+ }
+
+ /**
+ * Returns the page range (see property declaration above).
+ *
+ * @return integer
+ */
+ public function getPageRange()
+ {
+ if (null === $this->_pageRange) {
+ $this->_pageRange = self::getDefaultPageRange();
+ }
+
+ return $this->_pageRange;
+ }
+
+ /**
+ * Sets the page range (see property declaration above).
+ *
+ * @param integer $pageRange
+ * @return Zend_Paginator $this
+ */
+ public function setPageRange($pageRange)
+ {
+ $this->_pageRange = (integer) $pageRange;
+
+ return $this;
+ }
+
+ /**
+ * Returns the page collection.
+ *
+ * @param string $scrollingStyle Scrolling style
+ * @return array
+ */
+ public function getPages($scrollingStyle = null)
+ {
+ if ($this->_pages === null) {
+ $this->_pages = $this->_createPages($scrollingStyle);
+ }
+
+ return $this->_pages;
+ }
+
+ /**
+ * Returns a subset of pages within a given range.
+ *
+ * @param integer $lowerBound Lower bound of the range
+ * @param integer $upperBound Upper bound of the range
+ * @return array
+ */
+ public function getPagesInRange($lowerBound, $upperBound)
+ {
+ $lowerBound = $this->normalizePageNumber($lowerBound);
+ $upperBound = $this->normalizePageNumber($upperBound);
+
+ $pages = array();
+
+ for ($pageNumber = $lowerBound; $pageNumber <= $upperBound; $pageNumber++) {
+ $pages[$pageNumber] = $pageNumber;
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Returns the page item cache.
+ *
+ * @return array
+ */
+ public function getPageItemCache()
+ {
+ $data = array();
+ if ($this->_cacheEnabled()) {
+ foreach (self::$_cache->getIdsMatchingTags(array($this->_getCacheInternalId())) as $id) {
+ if (preg_match('|'.self::CACHE_TAG_PREFIX."(\d+)_.*|", $id, $page)) {
+ $data[$page[1]] = self::$_cache->load($this->_getCacheId($page[1]));
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Retrieves the view instance. If none registered, attempts to pull f
+ * rom ViewRenderer.
+ *
+ * @return Zend_View_Interface|null
+ */
+ public function getView()
+ {
+ if ($this->_view === null) {
+ /**
+ * @see Zend_Controller_Action_HelperBroker
+ */
+
+ $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ if ($viewRenderer->view === null) {
+ $viewRenderer->initView();
+ }
+ $this->_view = $viewRenderer->view;
+ }
+
+ return $this->_view;
+ }
+
+ /**
+ * Sets the view object.
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_Paginator
+ */
+ public function setView(Zend_View_Interface $view = null)
+ {
+ $this->_view = $view;
+
+ return $this;
+ }
+
+ /**
+ * Brings the item number in range of the page.
+ *
+ * @param integer $itemNumber
+ * @return integer
+ */
+ public function normalizeItemNumber($itemNumber)
+ {
+ $itemNumber = (integer) $itemNumber;
+
+ if ($itemNumber < 1) {
+ $itemNumber = 1;
+ }
+
+ if ($itemNumber > $this->getItemCountPerPage()) {
+ $itemNumber = $this->getItemCountPerPage();
+ }
+
+ return $itemNumber;
+ }
+
+ /**
+ * Brings the page number in range of the paginator.
+ *
+ * @param integer $pageNumber
+ * @return integer
+ */
+ public function normalizePageNumber($pageNumber)
+ {
+ $pageNumber = (integer) $pageNumber;
+
+ if ($pageNumber < 1) {
+ $pageNumber = 1;
+ }
+
+ $pageCount = $this->count();
+
+ if ($pageCount > 0 && $pageNumber > $pageCount) {
+ $pageNumber = $pageCount;
+ }
+
+ return $pageNumber;
+ }
+
+ /**
+ * Renders the paginator.
+ *
+ * @param Zend_View_Interface $view
+ * @return string
+ */
+ public function render(Zend_View_Interface $view = null)
+ {
+ if (null !== $view) {
+ $this->setView($view);
+ }
+
+ $view = $this->getView();
+
+ return $view->paginationControl($this);
+ }
+
+ /**
+ * Returns the items of the current page as JSON.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ $currentItems = $this->getCurrentItems();
+
+ if ($currentItems instanceof Zend_Db_Table_Rowset_Abstract) {
+ return Zend_Json::encode($currentItems->toArray());
+ } else {
+ return Zend_Json::encode($currentItems);
+ }
+ }
+
+ /**
+ * Tells if there is an active cache object
+ * and if the cache has not been desabled
+ *
+ * @return bool
+ */
+ protected function _cacheEnabled()
+ {
+ return ((self::$_cache !== null) && $this->_cacheEnabled);
+ }
+
+ /**
+ * Makes an Id for the cache
+ * Depends on the adapter object and the page number
+ *
+ * Used to store item in cache from that Paginator instance
+ * and that current page
+ *
+ * @param int $page
+ * @return string
+ */
+ protected function _getCacheId($page = null)
+ {
+ if ($page === null) {
+ $page = $this->getCurrentPageNumber();
+ }
+ return self::CACHE_TAG_PREFIX . $page . '_' . $this->_getCacheInternalId();
+ }
+
+ /**
+ * Get the internal cache id
+ * Depends on the adapter and the item count per page
+ *
+ * Used to tag that unique Paginator instance in cache
+ *
+ * @return string
+ */
+ protected function _getCacheInternalId()
+ {
+ $adapter = $this->getAdapter();
+
+ if (method_exists($adapter, 'getCacheIdentifier')) {
+ return md5(serialize(array(
+ $adapter->getCacheIdentifier(), $this->getItemCountPerPage()
+ )));
+ } else {
+ return md5(serialize(array(
+ $adapter,
+ $this->getItemCountPerPage()
+ )));
+ }
+ }
+
+ /**
+ * Calculates the page count.
+ *
+ * @return integer
+ */
+ protected function _calculatePageCount()
+ {
+ return (integer) ceil($this->getTotalItemCount() / $this->getItemCountPerPage());
+ }
+
+ /**
+ * Creates the page collection.
+ *
+ * @param string $scrollingStyle Scrolling style
+ * @return stdClass
+ */
+ protected function _createPages($scrollingStyle = null)
+ {
+ $pageCount = $this->count();
+ $currentPageNumber = $this->getCurrentPageNumber();
+
+ $pages = new stdClass();
+ $pages->pageCount = $pageCount;
+ $pages->itemCountPerPage = $this->getItemCountPerPage();
+ $pages->first = 1;
+ $pages->current = $currentPageNumber;
+ $pages->last = $pageCount;
+
+ // Previous and next
+ if ($currentPageNumber - 1 > 0) {
+ $pages->previous = $currentPageNumber - 1;
+ }
+
+ if ($currentPageNumber + 1 <= $pageCount) {
+ $pages->next = $currentPageNumber + 1;
+ }
+
+ // Pages in range
+ $scrollingStyle = $this->_loadScrollingStyle($scrollingStyle);
+ $pages->pagesInRange = $scrollingStyle->getPages($this);
+ $pages->firstPageInRange = min($pages->pagesInRange);
+ $pages->lastPageInRange = max($pages->pagesInRange);
+
+ // Item numbers
+ if ($this->getCurrentItems() !== null) {
+ $pages->currentItemCount = $this->getCurrentItemCount();
+ $pages->itemCountPerPage = $this->getItemCountPerPage();
+ $pages->totalItemCount = $this->getTotalItemCount();
+ $pages->firstItemNumber = (($currentPageNumber - 1) * $this->getItemCountPerPage()) + 1;
+ $pages->lastItemNumber = $pages->firstItemNumber + $pages->currentItemCount - 1;
+ }
+
+ return $pages;
+ }
+
+ /**
+ * Loads a scrolling style.
+ *
+ * @param string $scrollingStyle
+ * @return Zend_Paginator_ScrollingStyle_Interface
+ */
+ protected function _loadScrollingStyle($scrollingStyle = null)
+ {
+ if ($scrollingStyle === null) {
+ $scrollingStyle = self::$_defaultScrollingStyle;
+ }
+
+ switch (strtolower(gettype($scrollingStyle))) {
+ case 'object':
+ if (!$scrollingStyle instanceof Zend_Paginator_ScrollingStyle_Interface) {
+ /**
+ * @see Zend_View_Exception
+ */
+
+ throw new Zend_View_Exception('Scrolling style must implement ' .
+ 'Zend_Paginator_ScrollingStyle_Interface');
+ }
+
+ return $scrollingStyle;
+
+ case 'string':
+ $className = self::getScrollingStyleLoader()->load($scrollingStyle);
+
+ return new $className();
+
+ case 'null':
+ // Fall through to default case
+
+ default:
+ /**
+ * @see Zend_View_Exception
+ */
+
+ throw new Zend_View_Exception('Scrolling style must be a class ' .
+ 'name or object implementing Zend_Paginator_ScrollingStyle_Interface');
+ }
+ }
+}
diff --git a/library/vendor/Zend/Paginator/Adapter/Array.php b/library/vendor/Zend/Paginator/Adapter/Array.php
new file mode 100644
index 0000000..b4d0c09
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Adapter/Array.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_Adapter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_Adapter_Array implements Zend_Paginator_Adapter_Interface
+{
+ /**
+ * Array
+ *
+ * @var array
+ */
+ protected $_array = null;
+
+ /**
+ * Item count
+ *
+ * @var integer
+ */
+ protected $_count = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $array Array to paginate
+ */
+ public function __construct(array $array)
+ {
+ $this->_array = $array;
+ $this->_count = count($array);
+ }
+
+ /**
+ * Returns an array of items for a page.
+ *
+ * @param integer $offset Page offset
+ * @param integer $itemCountPerPage Number of items per page
+ * @return array
+ */
+ public function getItems($offset, $itemCountPerPage)
+ {
+ return array_slice($this->_array, $offset, $itemCountPerPage);
+ }
+
+ /**
+ * Returns the total number of rows in the array.
+ *
+ * @return integer
+ */
+ public function count()
+ {
+ return $this->_count;
+ }
+}
diff --git a/library/vendor/Zend/Paginator/Adapter/DbSelect.php b/library/vendor/Zend/Paginator/Adapter/DbSelect.php
new file mode 100644
index 0000000..ebd7142
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Adapter/DbSelect.php
@@ -0,0 +1,285 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_Adapter_Interface
+ */
+
+/**
+ * @see Zend_Db
+ */
+
+/**
+ * @see Zend_Db_Select
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_Adapter_DbSelect implements Zend_Paginator_Adapter_Interface
+{
+ /**
+ * Name of the row count column
+ *
+ * @var string
+ */
+ const ROW_COUNT_COLUMN = 'zend_paginator_row_count';
+
+ /**
+ * The COUNT query
+ *
+ * @var Zend_Db_Select
+ */
+ protected $_countSelect = null;
+
+ /**
+ * Database query
+ *
+ * @var Zend_Db_Select
+ */
+ protected $_select = null;
+
+ /**
+ * Total item count
+ *
+ * @var integer
+ */
+ protected $_rowCount = null;
+
+ /**
+ * Identifies this adapter for caching purposes. This value will remain constant for
+ * the entire life of this adapter regardless of how many different pages are queried.
+ *
+ * @var string
+ */
+ protected $_cacheIdentifier = null;
+
+ /**
+ * Constructor.
+ *
+ * @param Zend_Db_Select $select The select query
+ */
+ public function __construct(Zend_Db_Select $select)
+ {
+ $this->_select = $select;
+ $this->_cacheIdentifier = md5($select->assemble());
+ }
+
+ /**
+ * Returns the cache identifier.
+ *
+ * @return string
+ */
+ public function getCacheIdentifier()
+ {
+ return $this->_cacheIdentifier;
+ }
+
+ /**
+ * Sets the total row count, either directly or through a supplied
+ * query. Without setting this, {@link getPages()} selects the count
+ * as a subquery (SELECT COUNT ... FROM (SELECT ...)). While this
+ * yields an accurate count even with queries containing clauses like
+ * LIMIT, it can be slow in some circumstances. For example, in MySQL,
+ * subqueries are generally slow when using the InnoDB storage engine.
+ * Users are therefore encouraged to profile their queries to find
+ * the solution that best meets their needs.
+ *
+ * @param Zend_Db_Select|integer $totalRowCount Total row count integer
+ * or query
+ * @return Zend_Paginator_Adapter_DbSelect $this
+ * @throws Zend_Paginator_Exception
+ */
+ public function setRowCount($rowCount)
+ {
+ if ($rowCount instanceof Zend_Db_Select) {
+ $columns = $rowCount->getPart(Zend_Db_Select::COLUMNS);
+
+ $countColumnPart = empty($columns[0][2])
+ ? $columns[0][1]
+ : $columns[0][2];
+
+ if ($countColumnPart instanceof Zend_Db_Expr) {
+ $countColumnPart = $countColumnPart->__toString();
+ }
+
+ $rowCountColumn = $this->_select->getAdapter()->foldCase(self::ROW_COUNT_COLUMN);
+
+ // The select query can contain only one column, which should be the row count column
+ if (false === strpos($countColumnPart, $rowCountColumn)) {
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception('Row count column not found');
+ }
+
+ $result = $rowCount->query(Zend_Db::FETCH_ASSOC)->fetch();
+
+ $this->_rowCount = count($result) > 0 ? $result[$rowCountColumn] : 0;
+ } else if (is_integer($rowCount)) {
+ $this->_rowCount = $rowCount;
+ } else {
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception('Invalid row count');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns an array of items for a page.
+ *
+ * @param integer $offset Page offset
+ * @param integer $itemCountPerPage Number of items per page
+ * @return array
+ */
+ public function getItems($offset, $itemCountPerPage)
+ {
+ $this->_select->limit($itemCountPerPage, $offset);
+
+ return $this->_select->query()->fetchAll();
+ }
+
+ /**
+ * Returns the total number of rows in the result set.
+ *
+ * @return integer
+ */
+ public function count()
+ {
+ if ($this->_rowCount === null) {
+ $this->setRowCount(
+ $this->getCountSelect()
+ );
+ }
+
+ return $this->_rowCount;
+ }
+
+ /**
+ * Get the COUNT select object for the provided query
+ *
+ * TODO: Have a look at queries that have both GROUP BY and DISTINCT specified.
+ * In that use-case I'm expecting problems when either GROUP BY or DISTINCT
+ * has one column.
+ *
+ * @return Zend_Db_Select
+ */
+ public function getCountSelect()
+ {
+ /**
+ * We only need to generate a COUNT query once. It will not change for
+ * this instance.
+ */
+ if ($this->_countSelect !== null) {
+ return $this->_countSelect;
+ }
+
+ $rowCount = clone $this->_select;
+ $rowCount->__toString(); // Workaround for ZF-3719 and related
+
+ $db = $rowCount->getAdapter();
+
+ $countColumn = $db->quoteIdentifier($db->foldCase(self::ROW_COUNT_COLUMN));
+ $countPart = 'COUNT(1) AS ';
+ $groupPart = null;
+ $unionParts = $rowCount->getPart(Zend_Db_Select::UNION);
+
+ /**
+ * If we're dealing with a UNION query, execute the UNION as a subquery
+ * to the COUNT query.
+ */
+ if (!empty($unionParts)) {
+ $expression = new Zend_Db_Expr($countPart . $countColumn);
+
+ $rowCount = $db
+ ->select()
+ ->bind($rowCount->getBind())
+ ->from($rowCount, $expression);
+ } else {
+ $columnParts = $rowCount->getPart(Zend_Db_Select::COLUMNS);
+ $groupParts = $rowCount->getPart(Zend_Db_Select::GROUP);
+ $havingParts = $rowCount->getPart(Zend_Db_Select::HAVING);
+ $isDistinct = $rowCount->getPart(Zend_Db_Select::DISTINCT);
+
+ /**
+ * If there is more than one column AND it's a DISTINCT query, more
+ * than one group, or if the query has a HAVING clause, then take
+ * the original query and use it as a subquery os the COUNT query.
+ */
+ if (($isDistinct && ((count($columnParts) == 1 && $columnParts[0][1] == Zend_Db_Select::SQL_WILDCARD)
+ || count($columnParts) > 1)) || count($groupParts) > 1 || !empty($havingParts)) {
+ $rowCount->reset(Zend_Db_Select::ORDER);
+ $rowCount = $db
+ ->select()
+ ->bind($rowCount->getBind())
+ ->from($rowCount);
+ } else if ($isDistinct) {
+ $part = $columnParts[0];
+
+ if ($part[1] !== Zend_Db_Select::SQL_WILDCARD && !($part[1] instanceof Zend_Db_Expr)) {
+ $column = $db->quoteIdentifier($part[1], true);
+
+ if (!empty($part[0])) {
+ $column = $db->quoteIdentifier($part[0], true) . '.' . $column;
+ }
+
+ $groupPart = $column;
+ }
+ } else if (!empty($groupParts)) {
+ $groupPart = $db->quoteIdentifier($groupParts[0], true);
+ }
+
+ /**
+ * If the original query had a GROUP BY or a DISTINCT part and only
+ * one column was specified, create a COUNT(DISTINCT ) query instead
+ * of a regular COUNT query.
+ */
+ if (!empty($groupPart)) {
+ $countPart = 'COUNT(DISTINCT ' . $groupPart . ') AS ';
+ }
+
+ /**
+ * Create the COUNT part of the query
+ */
+ $expression = new Zend_Db_Expr($countPart . $countColumn);
+
+ $rowCount->reset(Zend_Db_Select::COLUMNS)
+ ->reset(Zend_Db_Select::ORDER)
+ ->reset(Zend_Db_Select::LIMIT_OFFSET)
+ ->reset(Zend_Db_Select::GROUP)
+ ->reset(Zend_Db_Select::DISTINCT)
+ ->reset(Zend_Db_Select::HAVING)
+ ->columns($expression);
+ }
+
+ $this->_countSelect = $rowCount;
+
+ return $rowCount;
+ }
+}
diff --git a/library/vendor/Zend/Paginator/Adapter/DbTableSelect.php b/library/vendor/Zend/Paginator/Adapter/DbTableSelect.php
new file mode 100644
index 0000000..dac467f
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Adapter/DbTableSelect.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_Adapter_DbSelect
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_Adapter_DbTableSelect extends Zend_Paginator_Adapter_DbSelect
+{
+ /**
+ * Returns a Zend_Db_Table_Rowset_Abstract of items for a page.
+ *
+ * @param integer $offset Page offset
+ * @param integer $itemCountPerPage Number of items per page
+ * @return Zend_Db_Table_Rowset_Abstract
+ */
+ public function getItems($offset, $itemCountPerPage)
+ {
+ $this->_select->limit($itemCountPerPage, $offset);
+
+ return $this->_select->getTable()->fetchAll($this->_select);
+ }
+}
diff --git a/library/vendor/Zend/Paginator/Adapter/Interface.php b/library/vendor/Zend/Paginator/Adapter/Interface.php
new file mode 100644
index 0000000..7ec1ee2
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Adapter/Interface.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Interface for pagination adapters.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Paginator_Adapter_Interface extends Countable
+{
+ /**
+ * Returns an collection of items for a page.
+ *
+ * @param integer $offset Page offset
+ * @param integer $itemCountPerPage Number of items per page
+ * @return array
+ */
+ public function getItems($offset, $itemCountPerPage);
+}
diff --git a/library/vendor/Zend/Paginator/Adapter/Iterator.php b/library/vendor/Zend/Paginator/Adapter/Iterator.php
new file mode 100644
index 0000000..ad10da9
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Adapter/Iterator.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_Adapter_Interface
+ */
+
+/**
+ * @see Zend_Paginator_SerializableLimitIterator
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_Adapter_Iterator implements Zend_Paginator_Adapter_Interface
+{
+ /**
+ * Iterator which implements Countable
+ *
+ * @var Iterator
+ */
+ protected $_iterator = null;
+
+ /**
+ * Item count
+ *
+ * @var integer
+ */
+ protected $_count = null;
+
+ /**
+ * Constructor.
+ *
+ * @param Iterator $iterator Iterator to paginate
+ * @throws Zend_Paginator_Exception
+ */
+ public function __construct(Iterator $iterator)
+ {
+ if (!$iterator instanceof Countable) {
+ /**
+ * @see Zend_Paginator_Exception
+ */
+
+ throw new Zend_Paginator_Exception('Iterator must implement Countable');
+ }
+
+ $this->_iterator = $iterator;
+ $this->_count = count($iterator);
+ }
+
+ /**
+ * Returns an iterator of items for a page, or an empty array.
+ *
+ * @param integer $offset Page offset
+ * @param integer $itemCountPerPage Number of items per page
+ * @return LimitIterator|array
+ */
+ public function getItems($offset, $itemCountPerPage)
+ {
+ if ($this->_count == 0) {
+ return array();
+ }
+
+ // @link http://bugs.php.net/bug.php?id=49906 | ZF-8084
+ // return new LimitIterator($this->_iterator, $offset, $itemCountPerPage);
+ return new Zend_Paginator_SerializableLimitIterator($this->_iterator, $offset, $itemCountPerPage);
+ }
+
+ /**
+ * Returns the total number of rows in the collection.
+ *
+ * @return integer
+ */
+ public function count()
+ {
+ return $this->_count;
+ }
+}
diff --git a/library/vendor/Zend/Paginator/Adapter/Null.php b/library/vendor/Zend/Paginator/Adapter/Null.php
new file mode 100644
index 0000000..da91d4d
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Adapter/Null.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_Adapter_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_Adapter_Null implements Zend_Paginator_Adapter_Interface
+{
+ /**
+ * Item count
+ *
+ * @var integer
+ */
+ protected $_count = null;
+
+ /**
+ * Constructor.
+ *
+ * @param array $count Total item count
+ */
+ public function __construct($count = 0)
+ {
+ $this->_count = $count;
+ }
+
+ /**
+ * Returns an array of items for a page.
+ *
+ * @param integer $offset Page offset
+ * @param integer $itemCountPerPage Number of items per page
+ * @return array
+ */
+ public function getItems($offset, $itemCountPerPage)
+ {
+ if ($offset >= $this->count()) {
+ return array();
+ }
+
+ $remainItemCount = $this->count() - $offset;
+ $currentItemCount = $remainItemCount > $itemCountPerPage ? $itemCountPerPage : $remainItemCount;
+
+ return array_fill(0, $currentItemCount, null);
+ }
+
+ /**
+ * Returns the total number of rows in the array.
+ *
+ * @return integer
+ */
+ public function count()
+ {
+ return $this->_count;
+ }
+}
diff --git a/library/vendor/Zend/Paginator/AdapterAggregate.php b/library/vendor/Zend/Paginator/AdapterAggregate.php
new file mode 100644
index 0000000..fe209d4
--- /dev/null
+++ b/library/vendor/Zend/Paginator/AdapterAggregate.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Interface that aggregates a Zend_Paginator_Adapter_Abstract just like IteratorAggregate does for Iterators.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @subpackage Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Paginator_AdapterAggregate
+{
+ /**
+ * Return a fully configured Paginator Adapter from this method.
+ *
+ * @return Zend_Paginator_Adapter_Interface
+ */
+ public function getPaginatorAdapter();
+}
diff --git a/library/vendor/Zend/Paginator/Exception.php b/library/vendor/Zend/Paginator/Exception.php
new file mode 100644
index 0000000..85513fa
--- /dev/null
+++ b/library/vendor/Zend/Paginator/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Paginator/ScrollingStyle/All.php b/library/vendor/Zend/Paginator/ScrollingStyle/All.php
new file mode 100644
index 0000000..6ef5427
--- /dev/null
+++ b/library/vendor/Zend/Paginator/ScrollingStyle/All.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_ScrollingStyle_Interface
+ */
+
+/**
+ * A scrolling style that returns every page in the collection.
+ * Useful when it is necessary to make every page available at
+ * once--for example, when using a dropdown menu pagination control.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_ScrollingStyle_All implements Zend_Paginator_ScrollingStyle_Interface
+{
+ /**
+ * Returns an array of all pages given a page number and range.
+ *
+ * @param Zend_Paginator $paginator
+ * @param integer $pageRange Unused
+ * @return array
+ */
+ public function getPages(Zend_Paginator $paginator, $pageRange = null)
+ {
+ return $paginator->getPagesInRange(1, $paginator->count());
+ }
+}
diff --git a/library/vendor/Zend/Paginator/ScrollingStyle/Elastic.php b/library/vendor/Zend/Paginator/ScrollingStyle/Elastic.php
new file mode 100644
index 0000000..6e103f6
--- /dev/null
+++ b/library/vendor/Zend/Paginator/ScrollingStyle/Elastic.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_ScrollingStyle_Sliding
+ */
+
+/**
+ * A Google-like scrolling style. Incrementally expands the range to about
+ * twice the given page range, then behaves like a slider. See the example
+ * link.
+ *
+ * @link http://www.google.com/search?q=Zend+Framework
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_ScrollingStyle_Elastic extends Zend_Paginator_ScrollingStyle_Sliding
+{
+ /**
+ * Returns an array of "local" pages given a page number and range.
+ *
+ * @param Zend_Paginator $paginator
+ * @param integer $pageRange Unused
+ * @return array
+ */
+ public function getPages(Zend_Paginator $paginator, $pageRange = null)
+ {
+ $pageRange = $paginator->getPageRange();
+ $pageNumber = $paginator->getCurrentPageNumber();
+
+ $originalPageRange = $pageRange;
+ $pageRange = $pageRange * 2 - 1;
+
+ if ($originalPageRange + $pageNumber - 1 < $pageRange) {
+ $pageRange = $originalPageRange + $pageNumber - 1;
+ } else if ($originalPageRange + $pageNumber - 1 > count($paginator)) {
+ $pageRange = $originalPageRange + count($paginator) - $pageNumber;
+ }
+
+ return parent::getPages($paginator, $pageRange);
+ }
+}
diff --git a/library/vendor/Zend/Paginator/ScrollingStyle/Interface.php b/library/vendor/Zend/Paginator/ScrollingStyle/Interface.php
new file mode 100644
index 0000000..7871d89
--- /dev/null
+++ b/library/vendor/Zend/Paginator/ScrollingStyle/Interface.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Paginator_ScrollingStyle_Interface
+{
+ /**
+ * Returns an array of "local" pages given a page number and range.
+ *
+ * @param Zend_Paginator $paginator
+ * @param integer $pageRange (Optional) Page range
+ * @return array
+ */
+ public function getPages(Zend_Paginator $paginator, $pageRange = null);
+}
diff --git a/library/vendor/Zend/Paginator/ScrollingStyle/Jumping.php b/library/vendor/Zend/Paginator/ScrollingStyle/Jumping.php
new file mode 100644
index 0000000..93c1b79
--- /dev/null
+++ b/library/vendor/Zend/Paginator/ScrollingStyle/Jumping.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_ScrollingStyle_Interface
+ */
+
+/**
+ * A scrolling style in which the cursor advances to the upper bound
+ * of the page range, the page range "jumps" to the next section, and
+ * the cursor moves back to the beginning of the range.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_ScrollingStyle_Jumping implements Zend_Paginator_ScrollingStyle_Interface
+{
+ /**
+ * Returns an array of "local" pages given a page number and range.
+ *
+ * @param Zend_Paginator $paginator
+ * @param integer $pageRange Unused
+ * @return array
+ */
+ public function getPages(Zend_Paginator $paginator, $pageRange = null)
+ {
+ $pageRange = $paginator->getPageRange();
+ $pageNumber = $paginator->getCurrentPageNumber();
+
+ $delta = $pageNumber % $pageRange;
+
+ if ($delta == 0) {
+ $delta = $pageRange;
+ }
+
+ $offset = $pageNumber - $delta;
+ $lowerBound = $offset + 1;
+ $upperBound = $offset + $pageRange;
+
+ return $paginator->getPagesInRange($lowerBound, $upperBound);
+ }
+}
diff --git a/library/vendor/Zend/Paginator/ScrollingStyle/Sliding.php b/library/vendor/Zend/Paginator/ScrollingStyle/Sliding.php
new file mode 100644
index 0000000..8bc6962
--- /dev/null
+++ b/library/vendor/Zend/Paginator/ScrollingStyle/Sliding.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Paginator_ScrollingStyle_Interface
+ */
+
+/**
+ * A Yahoo! Search-like scrolling style. The cursor will advance to
+ * the middle of the range, then remain there until the user reaches
+ * the end of the page set, at which point it will continue on to
+ * the end of the range and the last page in the set.
+ *
+ * @link http://search.yahoo.com/search?p=Zend+Framework
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_ScrollingStyle_Sliding implements Zend_Paginator_ScrollingStyle_Interface
+{
+ /**
+ * Returns an array of "local" pages given a page number and range.
+ *
+ * @param Zend_Paginator $paginator
+ * @param integer $pageRange (Optional) Page range
+ * @return array
+ */
+ public function getPages(Zend_Paginator $paginator, $pageRange = null)
+ {
+ if ($pageRange === null) {
+ $pageRange = $paginator->getPageRange();
+ }
+
+ $pageNumber = $paginator->getCurrentPageNumber();
+ $pageCount = count($paginator);
+
+ if ($pageRange > $pageCount) {
+ $pageRange = $pageCount;
+ }
+
+ $delta = ceil($pageRange / 2);
+
+ if ($pageNumber - $delta > $pageCount - $pageRange) {
+ $lowerBound = $pageCount - $pageRange + 1;
+ $upperBound = $pageCount;
+ } else {
+ if ($pageNumber - $delta < 0) {
+ $delta = $pageNumber;
+ }
+
+ $offset = $pageNumber - $delta;
+ $lowerBound = $offset + 1;
+ $upperBound = $offset + $pageRange;
+ }
+
+ return $paginator->getPagesInRange($lowerBound, $upperBound);
+ }
+}
diff --git a/library/vendor/Zend/Paginator/SerializableLimitIterator.php b/library/vendor/Zend/Paginator/SerializableLimitIterator.php
new file mode 100644
index 0000000..0e2e3d0
--- /dev/null
+++ b/library/vendor/Zend/Paginator/SerializableLimitIterator.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Paginator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Paginator_SerializableLimitIterator extends LimitIterator implements Serializable, ArrayAccess
+{
+
+ /**
+ * Offset to first element
+ *
+ * @var int
+ */
+ private $_offset;
+
+ /**
+ * Maximum number of elements to show or -1 for all
+ *
+ * @var int
+ */
+ private $_count;
+
+ /**
+ * Construct a Zend_Paginator_SerializableLimitIterator
+ *
+ * @param Iterator $it Iterator to limit (must be serializable by un-/serialize)
+ * @param int $offset Offset to first element
+ * @param int $count Maximum number of elements to show or -1 for all
+ * @see LimitIterator::__construct
+ */
+ public function __construct (Iterator $it, $offset=0, $count=-1)
+ {
+ parent::__construct($it, $offset, $count);
+ $this->_offset = $offset;
+ $this->_count = $count;
+ }
+
+ /**
+ * @return string representation of the instance
+ */
+ public function serialize()
+ {
+ return serialize(array(
+ 'it' => $this->getInnerIterator(),
+ 'offset' => $this->_offset,
+ 'count' => $this->_count,
+ 'pos' => $this->getPosition(),
+ ));
+ }
+
+ public function __serialize(): array
+ {
+ return array(
+ 'it' => $this->getInnerIterator(),
+ 'offset' => $this->_offset,
+ 'count' => $this->_count,
+ 'pos' => $this->getPosition(),
+ );
+ }
+
+ /**
+ * @param string $data representation of the instance
+ */
+ public function unserialize($data)
+ {
+ $dataArr = unserialize($data);
+ $this->__construct($dataArr['it'], $dataArr['offset'], $dataArr['count']);
+ $this->seek($dataArr['pos']+$dataArr['offset']);
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $this->__construct($data['it'], $data['offset'], $data['count']);
+ $this->seek($data['pos']+$data['offset']);
+ }
+
+ /**
+ * Returns value of the Iterator
+ *
+ * @param int $offset
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ $currentOffset = $this->key();
+ $this->seek($offset);
+ $current = $this->current();
+ $this->seek($currentOffset);
+ return $current;
+ }
+
+ /**
+ * Does nothing
+ * Required by the ArrayAccess implementation
+ *
+ * @param int $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value): void
+ {
+ }
+
+ /**
+ * Determine if a value of Iterator is set and is not NULL
+ *
+ * @param int $offset
+ */
+ public function offsetExists($offset): bool
+ {
+ if ($offset > 0 && $offset < $this->_count) {
+ try {
+ $currentOffset = $this->key();
+ $this->seek($offset);
+ $current = $this->current();
+ $this->seek($currentOffset);
+ return null !== $current;
+ } catch (OutOfBoundsException $e) {
+ // reset position in case of exception is assigned null
+ $this->rewind();
+ $this->seek($currentOffset);
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Does nothing
+ * Required by the ArrayAccess implementation
+ *
+ * @param int $offset
+ */
+ public function offsetUnset($offset): void
+ {
+ }
+}
diff --git a/library/vendor/Zend/ProgressBar.php b/library/vendor/Zend/ProgressBar.php
new file mode 100644
index 0000000..60804ee
--- /dev/null
+++ b/library/vendor/Zend/ProgressBar.php
@@ -0,0 +1,207 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_ProgressBar offers an interface for multiple enviroments.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_ProgressBar
+{
+ /**
+ * Min value
+ *
+ * @var float
+ */
+ protected $_min;
+
+ /**
+ * Max value
+ *
+ * @var float
+ */
+ protected $_max;
+
+ /**
+ * Current value
+ *
+ * @var float
+ */
+ protected $_current;
+
+ /**
+ * Start time of the progressbar, required for ETA
+ *
+ * @var integer
+ */
+ protected $_startTime;
+
+ /**
+ * Current status text
+ *
+ * @var string
+ */
+ protected $_statusText = null;
+
+ /**
+ * Adapter for the output
+ *
+ * @var Zend_ProgressBar_Adapter
+ */
+ protected $_adapter;
+
+ /**
+ * Namespace for keeping the progressbar persistent
+ *
+ * @var string
+ */
+ protected $_persistenceNamespace = null;
+
+ /**
+ * Create a new progressbar backend.
+ *
+ * @param Zend_ProgressBar_Adapter $adapter
+ * @param float $min
+ * @param float $max
+ * @param string $persistenceNamespace
+ * @throws Zend_ProgressBar_Exception When $min is greater than $max
+ */
+ public function __construct(Zend_ProgressBar_Adapter $adapter, $min = 0, $max = 100, $persistenceNamespace = null)
+ {
+ // Check min/max values and set them
+ if ($min > $max) {
+ throw new Zend_ProgressBar_Exception('$max must be greater than $min');
+ }
+
+ $this->_min = (float) $min;
+ $this->_max = (float) $max;
+ $this->_current = (float) $min;
+
+ // See if we have to open a session namespace
+ if ($persistenceNamespace !== null) {
+
+ $this->_persistenceNamespace = new Zend_Session_Namespace($persistenceNamespace);
+ }
+
+ // Set adapter
+ $this->_adapter = $adapter;
+
+ // Track the start time
+ $this->_startTime = time();
+
+ // See If a persistenceNamespace exists and handle accordingly
+ if ($this->_persistenceNamespace !== null) {
+ if (isset($this->_persistenceNamespace->isSet)) {
+ $this->_startTime = $this->_persistenceNamespace->startTime;
+ $this->_current = $this->_persistenceNamespace->current;
+ $this->_statusText = $this->_persistenceNamespace->statusText;
+ } else {
+ $this->_persistenceNamespace->isSet = true;
+ $this->_persistenceNamespace->startTime = $this->_startTime;
+ $this->_persistenceNamespace->current = $this->_current;
+ $this->_persistenceNamespace->statusText = $this->_statusText;
+ }
+ } else {
+ $this->update();
+ }
+ }
+
+ /**
+ * Get the current adapter
+ *
+ * @return Zend_ProgressBar_Adapter
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Update the progressbar
+ *
+ * @param float $value
+ * @param string $text
+ * @return void
+ */
+ public function update($value = null, $text = null)
+ {
+ // Update value if given
+ if ($value !== null) {
+ $this->_current = min($this->_max, max($this->_min, $value));
+ }
+
+ // Update text if given
+ if ($text !== null) {
+ $this->_statusText = $text;
+ }
+
+ // See if we have to update a namespace
+ if ($this->_persistenceNamespace !== null) {
+ $this->_persistenceNamespace->current = $this->_current;
+ $this->_persistenceNamespace->statusText = $this->_statusText;
+ }
+
+ // Calculate percent
+ if ($this->_min === $this->_max) {
+ $percent = false;
+ } else {
+ $percent = (float) ($this->_current - $this->_min) / ($this->_max - $this->_min);
+ }
+
+ // Calculate ETA
+ $timeTaken = time() - $this->_startTime;
+
+ if ($percent === .0 || $percent === false) {
+ $timeRemaining = null;
+ } else {
+ $timeRemaining = round(((1 / $percent) * $timeTaken) - $timeTaken);
+ }
+
+ // Poll the adapter
+ $this->_adapter->notify($this->_current, $this->_max, $percent, $timeTaken, $timeRemaining, $this->_statusText);
+ }
+
+ /**
+ * Update the progressbar to the next value
+ *
+ * @param string $text
+ * @return void
+ */
+ public function next($diff = 1, $text = null)
+ {
+ $this->update(max($this->_min, min($this->_max, $this->_current + $diff)), $text);
+ }
+
+ /**
+ * Call the adapters finish() behaviour
+ *
+ * @return void
+ */
+ public function finish()
+ {
+ if ($this->_persistenceNamespace !== null) {
+ unset($this->_persistenceNamespace->isSet);
+ }
+
+ $this->_adapter->finish();
+ }
+}
diff --git a/library/vendor/Zend/ProgressBar/Adapter.php b/library/vendor/Zend/ProgressBar/Adapter.php
new file mode 100644
index 0000000..89e5918
--- /dev/null
+++ b/library/vendor/Zend/ProgressBar/Adapter.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Abstract class for Zend_ProgressBar_Adapters
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_ProgressBar_Adapter
+{
+ /**
+ * Option keys to skip when calling setOptions()
+ *
+ * @var array
+ */
+ protected $_skipOptions = array(
+ 'options',
+ 'config',
+ );
+
+ /**
+ * Create a new adapter
+ *
+ * $options may be either be an array or a Zend_Config object which
+ * specifies adapter related options.
+ *
+ * @param null|array|Zend_Config $options
+ */
+ public function __construct($options = null)
+ {
+ if (is_array($options)) {
+ $this->setOptions($options);
+ } elseif ($options instanceof Zend_Config) {
+ $this->setConfig($options);
+ }
+ }
+
+ /**
+ * Set options via a Zend_Config instance
+ *
+ * @param Zend_Config $config
+ * @return Zend_ProgressBar_Adapter
+ */
+ public function setConfig(Zend_Config $config)
+ {
+ $this->setOptions($config->toArray());
+
+ return $this;
+ }
+
+ /**
+ * Set options via an array
+ *
+ * @param array $options
+ * @return Zend_ProgressBar_Adapter
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ if (in_array(strtolower($key), $this->_skipOptions)) {
+ continue;
+ }
+
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Notify the adapter about an update
+ *
+ * @param float $current Current progress value
+ * @param float $max Max progress value
+ * @param float $percent Current percent value
+ * @param integer $timeTaken Taken time in seconds
+ * @param integer $timeRemaining Remaining time in seconds
+ * @param string $text Status text
+ * @return void
+ */
+ abstract public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text);
+
+ /**
+ * Called when the progress is explicitly finished
+ *
+ * @return void
+ */
+ abstract public function finish();
+}
diff --git a/library/vendor/Zend/ProgressBar/Adapter/Exception.php b/library/vendor/Zend/ProgressBar/Adapter/Exception.php
new file mode 100644
index 0000000..110fced
--- /dev/null
+++ b/library/vendor/Zend/ProgressBar/Adapter/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_ProgressBar_Exception
+ */
+
+/**
+ * Exception class for Zend_ProgressBar_Adapter
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @uses Zend_ProgressBar_Exception
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_ProgressBar_Adapter_Exception extends Zend_ProgressBar_Exception
+{
+}
diff --git a/library/vendor/Zend/ProgressBar/Adapter/JsPull.php b/library/vendor/Zend/ProgressBar/Adapter/JsPull.php
new file mode 100644
index 0000000..cb9b5bd
--- /dev/null
+++ b/library/vendor/Zend/ProgressBar/Adapter/JsPull.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Json
+ */
+
+/**
+ * @see Zend_ProgressBar_Adapter
+ */
+
+/**
+ * Zend_ProgressBar_Adapter_JsPull offers a simple method for updating a
+ * progressbar in a browser.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @uses Zend_ProgressBar_Adapter_Interface
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_ProgressBar_Adapter_JsPull extends Zend_ProgressBar_Adapter
+{
+ /**
+ * Wether to exit after json data send or not
+ *
+ * @var boolean
+ */
+ protected $_exitAfterSend = true;
+
+ /**
+ * Set wether to exit after json data send or not
+ *
+ * @param boolean $exitAfterSend
+ * @return Zend_ProgressBar_Adapter_JsPull
+ */
+ public function setExitAfterSend($exitAfterSend)
+ {
+ $this->_exitAfterSend = $exitAfterSend;
+ }
+
+ /**
+ * Defined by Zend_ProgressBar_Adapter_Interface
+ *
+ * @param float $current Current progress value
+ * @param float $max Max progress value
+ * @param float $percent Current percent value
+ * @param integer $timeTaken Taken time in seconds
+ * @param integer $timeRemaining Remaining time in seconds
+ * @param string $text Status text
+ * @return void
+ */
+ public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
+ {
+ $arguments = array(
+ 'current' => $current,
+ 'max' => $max,
+ 'percent' => ($percent * 100),
+ 'timeTaken' => $timeTaken,
+ 'timeRemaining' => $timeRemaining,
+ 'text' => $text,
+ 'finished' => false
+ );
+
+ $data = Zend_Json::encode($arguments);
+
+ // Output the data
+ $this->_outputData($data);
+ }
+
+ /**
+ * Defined by Zend_ProgressBar_Adapter_Interface
+ *
+ * @return void
+ */
+ public function finish()
+ {
+ $data = Zend_Json::encode(array('finished' => true));
+
+ $this->_outputData($data);
+ }
+
+ /**
+ * Outputs given data the user agent.
+ *
+ * This split-off is required for unit-testing.
+ *
+ * @param string $data
+ * @return void
+ */
+ protected function _outputData($data)
+ {
+ echo $data;
+
+ if ($this->_exitAfterSend) {
+ exit;
+ }
+ }
+}
diff --git a/library/vendor/Zend/ProgressBar/Adapter/JsPush.php b/library/vendor/Zend/ProgressBar/Adapter/JsPush.php
new file mode 100644
index 0000000..f43d85e
--- /dev/null
+++ b/library/vendor/Zend/ProgressBar/Adapter/JsPush.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Json
+ */
+
+/**
+ * @see Zend_ProgressBar_Adapter
+ */
+
+/**
+ * Zend_ProgressBar_Adapter_JsPush offers a simple method for updating a
+ * progressbar in a browser.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @uses Zend_ProgressBar_Adapter_Interface
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_ProgressBar_Adapter_JsPush extends Zend_ProgressBar_Adapter
+{
+ /**
+ * Name of the JavaScript method to call on update
+ *
+ * @var string
+ */
+ protected $_updateMethodName = 'Zend_ProgressBar_Update';
+
+ /**
+ * Name of the JavaScript method to call on finish
+ *
+ * @var string
+ */
+ protected $_finishMethodName;
+
+ /**
+ * Set the update method name
+ *
+ * @param string $methodName
+ * @return Zend_ProgressBar_Adapter_JsPush
+ */
+ public function setUpdateMethodName($methodName)
+ {
+ $this->_updateMethodName = $methodName;
+
+ return $this;
+ }
+
+ /**
+ * Set the finish method name
+ *
+ * @param string $methodName
+ * @return Zend_ProgressBar_Adapter_JsPush
+ */
+ public function setFinishMethodName($methodName)
+ {
+ $this->_finishMethodName = $methodName;
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_ProgressBar_Adapter_Interface
+ *
+ * @param float $current Current progress value
+ * @param float $max Max progress value
+ * @param float $percent Current percent value
+ * @param integer $timeTaken Taken time in seconds
+ * @param integer $timeRemaining Remaining time in seconds
+ * @param string $text Status text
+ * @return void
+ */
+ public function notify($current, $max, $percent, $timeTaken, $timeRemaining, $text)
+ {
+ $arguments = array(
+ 'current' => $current,
+ 'max' => $max,
+ 'percent' => ($percent * 100),
+ 'timeTaken' => $timeTaken,
+ 'timeRemaining' => $timeRemaining,
+ 'text' => $text
+ );
+
+ $data = '<script type="text/javascript">'
+ . 'parent.' . $this->_updateMethodName . '(' . Zend_Json::encode($arguments) . ');'
+ . '</script>';
+
+ // Output the data
+ $this->_outputData($data);
+ }
+
+ /**
+ * Defined by Zend_ProgressBar_Adapter_Interface
+ *
+ * @return void
+ */
+ public function finish()
+ {
+ if ($this->_finishMethodName === null) {
+ return;
+ }
+
+ $data = '<script type="text/javascript">'
+ . 'parent.' . $this->_finishMethodName . '();'
+ . '</script>';
+
+ $this->_outputData($data);
+ }
+
+ /**
+ * Outputs given data the user agent.
+ *
+ * This split-off is required for unit-testing.
+ *
+ * @param string $data
+ * @return void
+ */
+ protected function _outputData($data)
+ {
+ // 1024 padding is required for Safari, while 256 padding is required
+ // for Internet Explorer. The <br /> is required so Safari actually
+ // executes the <script />
+ echo str_pad($data . '<br />', 1024, ' ', STR_PAD_RIGHT) . "\n";
+
+ flush();
+ ob_flush();
+ }
+}
diff --git a/library/vendor/Zend/ProgressBar/Exception.php b/library/vendor/Zend/ProgressBar/Exception.php
new file mode 100644
index 0000000..b002520
--- /dev/null
+++ b/library/vendor/Zend/ProgressBar/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * Exception class for Zend_ProgressBar
+ *
+ * @category Zend
+ * @package Zend_ProgressBar
+ * @uses Zend_Exception
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_ProgressBar_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/README.md b/library/vendor/Zend/README.md
new file mode 100644
index 0000000..6c5445b
--- /dev/null
+++ b/library/vendor/Zend/README.md
@@ -0,0 +1,8 @@
+# icingaweb2-vendor-zf1
+
+icingaweb2-vendor-zf1 is [Icinga Web 2](https://www.icinga.org/products/icinga-web-2/)'s fork of
+[Zend Framework](https://framework.zend.com/) 1 which is
+[end-of-life](https://framework.zend.com/blog/2016-06-28-zf1-eol.html) since Sep 28, 2016.
+We've reduced the library to the minimum required by Icinga Web 2 and its modules.
+The Icinga Project will provide security and bug fixes.
+icingaweb2-vendor-zf1 is based on Zend Framework 1 version 1.12.20.
diff --git a/library/vendor/Zend/Registry.php b/library/vendor/Zend/Registry.php
new file mode 100644
index 0000000..59b971e
--- /dev/null
+++ b/library/vendor/Zend/Registry.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Registry
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Generic storage class helps to manage global data.
+ *
+ * @category Zend
+ * @package Zend_Registry
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Registry extends ArrayObject
+{
+ /**
+ * Class name of the singleton registry object.
+ * @var string
+ */
+ private static $_registryClassName = 'Zend_Registry';
+
+ /**
+ * Registry object provides storage for shared objects.
+ * @var Zend_Registry
+ */
+ private static $_registry = null;
+
+ /**
+ * Retrieves the default registry instance.
+ *
+ * @return Zend_Registry
+ */
+ public static function getInstance()
+ {
+ if (self::$_registry === null) {
+ self::init();
+ }
+
+ return self::$_registry;
+ }
+
+ /**
+ * Set the default registry instance to a specified instance.
+ *
+ * @param Zend_Registry $registry An object instance of type Zend_Registry,
+ * or a subclass.
+ * @return void
+ * @throws Zend_Exception if registry is already initialized.
+ */
+ public static function setInstance(Zend_Registry $registry)
+ {
+ if (self::$_registry !== null) {
+ throw new Zend_Exception('Registry is already initialized');
+ }
+
+ self::setClassName(get_class($registry));
+ self::$_registry = $registry;
+ }
+
+ /**
+ * Initialize the default registry instance.
+ *
+ * @return void
+ */
+ protected static function init()
+ {
+ self::setInstance(new self::$_registryClassName());
+ }
+
+ /**
+ * Set the class name to use for the default registry instance.
+ * Does not affect the currently initialized instance, it only applies
+ * for the next time you instantiate.
+ *
+ * @param string $registryClassName
+ * @return void
+ * @throws Zend_Exception if the registry is initialized or if the
+ * class name is not valid.
+ */
+ public static function setClassName($registryClassName = 'Zend_Registry')
+ {
+ if (self::$_registry !== null) {
+ throw new Zend_Exception('Registry is already initialized');
+ }
+
+ if (!is_string($registryClassName)) {
+ throw new Zend_Exception("Argument is not a class name");
+ }
+
+ /**
+ * @see Zend_Loader
+ */
+ if (!class_exists($registryClassName)) {
+ Zend_Loader::loadClass($registryClassName);
+ }
+
+ self::$_registryClassName = $registryClassName;
+ }
+
+ /**
+ * Unset the default registry instance.
+ * Primarily used in tearDown() in unit tests.
+ * @returns void
+ */
+ public static function _unsetInstance()
+ {
+ self::$_registry = null;
+ }
+
+ /**
+ * getter method, basically same as offsetGet().
+ *
+ * This method can be called from an object of type Zend_Registry, or it
+ * can be called statically. In the latter case, it uses the default
+ * static instance stored in the class.
+ *
+ * @param string $index - get the value associated with $index
+ * @return mixed
+ * @throws Zend_Exception if no entry is registered for $index.
+ */
+ public static function get($index)
+ {
+ $instance = self::getInstance();
+
+ if (!$instance->offsetExists($index)) {
+ throw new Zend_Exception("No entry is registered for key '$index'");
+ }
+
+ return $instance->offsetGet($index);
+ }
+
+ /**
+ * setter method, basically same as offsetSet().
+ *
+ * This method can be called from an object of type Zend_Registry, or it
+ * can be called statically. In the latter case, it uses the default
+ * static instance stored in the class.
+ *
+ * @param string $index The location in the ArrayObject in which to store
+ * the value.
+ * @param mixed $value The object to store in the ArrayObject.
+ * @return void
+ */
+ public static function set($index, $value)
+ {
+ $instance = self::getInstance();
+ $instance->offsetSet($index, $value);
+ }
+
+ /**
+ * Returns TRUE if the $index is a named value in the registry,
+ * or FALSE if $index was not found in the registry.
+ *
+ * @param string $index
+ * @return boolean
+ */
+ public static function isRegistered($index)
+ {
+ if (self::$_registry === null) {
+ return false;
+ }
+ return self::$_registry->offsetExists($index);
+ }
+
+ /**
+ * Constructs a parent ArrayObject with default
+ * ARRAY_AS_PROPS to allow acces as an object
+ *
+ * @param array $array data array
+ * @param integer $flags ArrayObject flags
+ */
+ public function __construct($array = array(), $flags = parent::ARRAY_AS_PROPS)
+ {
+ parent::__construct($array, $flags);
+ }
+}
diff --git a/library/vendor/Zend/Server/Abstract.php b/library/vendor/Zend/Server/Abstract.php
new file mode 100644
index 0000000..7f3d1eb
--- /dev/null
+++ b/library/vendor/Zend/Server/Abstract.php
@@ -0,0 +1,235 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Server_Interface */
+
+/**
+ * Zend_Server_Definition
+ */
+
+/**
+ * Zend_Server_Method_Definition
+ */
+
+/**
+ * Zend_Server_Method_Callback
+ */
+
+/**
+ * Zend_Server_Method_Prototype
+ */
+
+/**
+ * Zend_Server_Method_Parameter
+ */
+
+/**
+ * Zend_Server_Abstract
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Server_Abstract implements Zend_Server_Interface
+{
+ /**
+ * @deprecated
+ * @var array List of PHP magic methods (lowercased)
+ */
+ protected static $magic_methods = array(
+ '__call',
+ '__clone',
+ '__construct',
+ '__destruct',
+ '__get',
+ '__isset',
+ '__set',
+ '__set_state',
+ '__sleep',
+ '__tostring',
+ '__unset',
+ '__wakeup',
+ );
+
+ /**
+ * @var bool Flag; whether or not overwriting existing methods is allowed
+ */
+ protected $_overwriteExistingMethods = false;
+
+ /**
+ * @var Zend_Server_Definition
+ */
+ protected $_table;
+
+ /**
+ * Constructor
+ *
+ * Setup server description
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->_table = new Zend_Server_Definition();
+ $this->_table->setOverwriteExistingMethods($this->_overwriteExistingMethods);
+ }
+
+ /**
+ * Returns a list of registered methods
+ *
+ * Returns an array of method definitions.
+ *
+ * @return Zend_Server_Definition
+ */
+ public function getFunctions()
+ {
+ return $this->_table;
+ }
+
+ /**
+ * Lowercase a string
+ *
+ * Lowercase's a string by reference
+ *
+ * @deprecated
+ * @param string $string value
+ * @param string $key
+ * @return string Lower cased string
+ */
+ public static function lowerCase(&$value, &$key)
+ {
+ trigger_error(__CLASS__ . '::' . __METHOD__ . '() is deprecated and will be removed in a future version', E_USER_NOTICE);
+ return $value = strtolower($value);
+ }
+
+ /**
+ * Build callback for method signature
+ *
+ * @param Zend_Server_Reflection_Function_Abstract $reflection
+ * @return Zend_Server_Method_Callback
+ */
+ protected function _buildCallback(Zend_Server_Reflection_Function_Abstract $reflection)
+ {
+ $callback = new Zend_Server_Method_Callback();
+ if ($reflection instanceof Zend_Server_Reflection_Method) {
+ $callback->setType($reflection->isStatic() ? 'static' : 'instance')
+ ->setClass($reflection->getDeclaringClass()->getName())
+ ->setMethod($reflection->getName());
+ } elseif ($reflection instanceof Zend_Server_Reflection_Function) {
+ $callback->setType('function')
+ ->setFunction($reflection->getName());
+ }
+ return $callback;
+ }
+
+ /**
+ * Build a method signature
+ *
+ * @param Zend_Server_Reflection_Function_Abstract $reflection
+ * @param null|string|object $class
+ * @return Zend_Server_Method_Definition
+ * @throws Zend_Server_Exception on duplicate entry
+ */
+ protected function _buildSignature(Zend_Server_Reflection_Function_Abstract $reflection, $class = null)
+ {
+ $ns = $reflection->getNamespace();
+ $name = $reflection->getName();
+ $method = empty($ns) ? $name : $ns . '.' . $name;
+
+ if (!$this->_overwriteExistingMethods && $this->_table->hasMethod($method)) {
+ throw new Zend_Server_Exception('Duplicate method registered: ' . $method);
+ }
+
+ $definition = new Zend_Server_Method_Definition();
+ $definition->setName($method)
+ ->setCallback($this->_buildCallback($reflection))
+ ->setMethodHelp($reflection->getDescription())
+ ->setInvokeArguments($reflection->getInvokeArguments());
+
+ foreach ($reflection->getPrototypes() as $proto) {
+ $prototype = new Zend_Server_Method_Prototype();
+ $prototype->setReturnType($this->_fixType($proto->getReturnType()));
+ foreach ($proto->getParameters() as $parameter) {
+ $param = new Zend_Server_Method_Parameter(array(
+ 'type' => $this->_fixType($parameter->getType()),
+ 'name' => $parameter->getName(),
+ 'optional' => $parameter->isOptional(),
+ ));
+ if ($parameter->isDefaultValueAvailable()) {
+ $param->setDefaultValue($parameter->getDefaultValue());
+ }
+ $prototype->addParameter($param);
+ }
+ $definition->addPrototype($prototype);
+ }
+ if (is_object($class)) {
+ $definition->setObject($class);
+ }
+ $this->_table->addMethod($definition);
+ return $definition;
+ }
+
+ /**
+ * Dispatch method
+ *
+ * @param Zend_Server_Method_Definition $invocable
+ * @param array $params
+ * @return mixed
+ */
+ protected function _dispatch(Zend_Server_Method_Definition $invocable, array $params)
+ {
+ $callback = $invocable->getCallback();
+ $type = $callback->getType();
+
+ if ('function' == $type) {
+ $function = $callback->getFunction();
+ return call_user_func_array($function, $params);
+ }
+
+ $class = $callback->getClass();
+ $method = $callback->getMethod();
+
+ if ('static' == $type) {
+ return call_user_func_array(array($class, $method), $params);
+ }
+
+ $object = $invocable->getObject();
+ if (!is_object($object)) {
+ $invokeArgs = $invocable->getInvokeArguments();
+ if (!empty($invokeArgs)) {
+ $reflection = new ReflectionClass($class);
+ $object = $reflection->newInstanceArgs(array_values($invokeArgs));
+ } else {
+ $object = new $class;
+ }
+ }
+ return call_user_func_array(array($object, $method), $params);
+ }
+
+ /**
+ * Map PHP type to protocol type
+ *
+ * @param string $type
+ * @return string
+ */
+ abstract protected function _fixType($type);
+}
diff --git a/library/vendor/Zend/Server/Cache.php b/library/vendor/Zend/Server/Cache.php
new file mode 100644
index 0000000..97264df
--- /dev/null
+++ b/library/vendor/Zend/Server/Cache.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Server_Cache: cache server definitions
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Cache
+{
+ /**
+ * @var array Methods to skip when caching server
+ */
+ protected static $_skipMethods = array();
+
+ /**
+ * Cache a file containing the dispatch list.
+ *
+ * Serializes the server definition stores the information
+ * in $filename.
+ *
+ * Returns false on any error (typically, inability to write to file), true
+ * on success.
+ *
+ * @param string $filename
+ * @param Zend_Server_Interface $server
+ * @return bool
+ */
+ public static function save($filename, Zend_Server_Interface $server)
+ {
+ if (!is_string($filename)
+ || (!file_exists($filename) && !is_writable(dirname($filename))))
+ {
+ return false;
+ }
+
+ $methods = $server->getFunctions();
+
+ if ($methods instanceof Zend_Server_Definition) {
+ $definition = new Zend_Server_Definition();
+ foreach ($methods as $method) {
+ if (in_array($method->getName(), self::$_skipMethods)) {
+ continue;
+ }
+ $definition->addMethod($method);
+ }
+ $methods = $definition;
+ }
+
+ if (0 === @file_put_contents($filename, serialize($methods))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Load server definition from a file
+ *
+ * Unserializes a stored server definition from $filename. Returns false if
+ * it fails in any way, true on success.
+ *
+ * Useful to prevent needing to build the server definition on each
+ * request. Sample usage:
+ *
+ * <code>
+ * if (!Zend_Server_Cache::get($filename, $server)) {
+ * require_once 'Some/Service/Class.php';
+ * require_once 'Another/Service/Class.php';
+ *
+ * // Attach Some_Service_Class with namespace 'some'
+ * $server->attach('Some_Service_Class', 'some');
+ *
+ * // Attach Another_Service_Class with namespace 'another'
+ * $server->attach('Another_Service_Class', 'another');
+ *
+ * Zend_Server_Cache::save($filename, $server);
+ * }
+ *
+ * $response = $server->handle();
+ * echo $response;
+ * </code>
+ *
+ * @param string $filename
+ * @param Zend_Server_Interface $server
+ * @return bool
+ */
+ public static function get($filename, Zend_Server_Interface $server)
+ {
+ if (!is_string($filename)
+ || !file_exists($filename)
+ || !is_readable($filename))
+ {
+ return false;
+ }
+
+
+ if (false === ($dispatch = @file_get_contents($filename))) {
+ return false;
+ }
+
+ if (false === ($dispatchArray = @unserialize($dispatch))) {
+ return false;
+ }
+
+ $server->loadFunctions($dispatchArray);
+
+ return true;
+ }
+
+ /**
+ * Remove a cache file
+ *
+ * @param string $filename
+ * @return boolean
+ */
+ public static function delete($filename)
+ {
+ if (is_string($filename) && file_exists($filename)) {
+ unlink($filename);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Server/Definition.php b/library/vendor/Zend/Server/Definition.php
new file mode 100644
index 0000000..fb8d24a
--- /dev/null
+++ b/library/vendor/Zend/Server/Definition.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Server methods metadata
+ *
+ * @todo Implement iterator
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Definition implements Countable, Iterator
+{
+ /**
+ * @var array Array of Zend_Server_Method_Definition objects
+ */
+ protected $_methods = array();
+
+ /**
+ * @var bool Whether or not overwriting existing methods is allowed
+ */
+ protected $_overwriteExistingMethods = false;
+
+ /**
+ * Constructor
+ *
+ * @param null|array $methods
+ * @return void
+ */
+ public function __construct($methods = null)
+ {
+ if (is_array($methods)) {
+ $this->setMethods($methods);
+ }
+ }
+
+ /**
+ * Set flag indicating whether or not overwriting existing methods is allowed
+ *
+ * @param mixed $flag
+ * @return void
+ */
+ public function setOverwriteExistingMethods($flag)
+ {
+ $this->_overwriteExistingMethods = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Add method to definition
+ *
+ * @param array|Zend_Server_Method_Definition $method
+ * @param null|string $name
+ * @return Zend_Server_Definition
+ * @throws Zend_Server_Exception if duplicate or invalid method provided
+ */
+ public function addMethod($method, $name = null)
+ {
+ if (is_array($method)) {
+ $method = new Zend_Server_Method_Definition($method);
+ } elseif (!$method instanceof Zend_Server_Method_Definition) {
+ throw new Zend_Server_Exception('Invalid method provided');
+ }
+
+ if (is_numeric($name)) {
+ $name = null;
+ }
+ if (null !== $name) {
+ $method->setName($name);
+ } else {
+ $name = $method->getName();
+ }
+ if (null === $name) {
+ throw new Zend_Server_Exception('No method name provided');
+ }
+
+ if (!$this->_overwriteExistingMethods && array_key_exists($name, $this->_methods)) {
+ throw new Zend_Server_Exception(sprintf('Method by name of "%s" already exists', $name));
+ }
+ $this->_methods[$name] = $method;
+ return $this;
+ }
+
+ /**
+ * Add multiple methods
+ *
+ * @param array $methods Array of Zend_Server_Method_Definition objects or arrays
+ * @return Zend_Server_Definition
+ */
+ public function addMethods(array $methods)
+ {
+ foreach ($methods as $key => $method) {
+ $this->addMethod($method, $key);
+ }
+ return $this;
+ }
+
+ /**
+ * Set all methods at once (overwrite)
+ *
+ * @param array $methods Array of Zend_Server_Method_Definition objects or arrays
+ * @return Zend_Server_Definition
+ */
+ public function setMethods(array $methods)
+ {
+ $this->clearMethods();
+ $this->addMethods($methods);
+ return $this;
+ }
+
+ /**
+ * Does the definition have the given method?
+ *
+ * @param string $method
+ * @return bool
+ */
+ public function hasMethod($method)
+ {
+ return array_key_exists($method, $this->_methods);
+ }
+
+ /**
+ * Get a given method definition
+ *
+ * @param string $method
+ * @return null|Zend_Server_Method_Definition
+ */
+ public function getMethod($method)
+ {
+ if ($this->hasMethod($method)) {
+ return $this->_methods[$method];
+ }
+ return false;
+ }
+
+ /**
+ * Get all method definitions
+ *
+ * @return array Array of Zend_Server_Method_Definition objects
+ */
+ public function getMethods()
+ {
+ return $this->_methods;
+ }
+
+ /**
+ * Remove a method definition
+ *
+ * @param string $method
+ * @return Zend_Server_Definition
+ */
+ public function removeMethod($method)
+ {
+ if ($this->hasMethod($method)) {
+ unset($this->_methods[$method]);
+ }
+ return $this;
+ }
+
+ /**
+ * Clear all method definitions
+ *
+ * @return Zend_Server_Definition
+ */
+ public function clearMethods()
+ {
+ $this->_methods = array();
+ return $this;
+ }
+
+ /**
+ * Cast definition to an array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $methods = array();
+ foreach ($this->getMethods() as $key => $method) {
+ $methods[$key] = $method->toArray();
+ }
+ return $methods;
+ }
+
+ /**
+ * Countable: count of methods
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->_methods);
+ }
+
+ /**
+ * Iterator: current item
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ return current($this->_methods);
+ }
+
+ /**
+ * Iterator: current item key
+ *
+ * @return int|string
+ */
+ public function key()
+ {
+ return key($this->_methods);
+ }
+
+ /**
+ * Iterator: advance to next method
+ *
+ * @return void
+ */
+ public function next()
+ {
+ return next($this->_methods);
+ }
+
+ /**
+ * Iterator: return to first method
+ *
+ * @return void
+ */
+ public function rewind()
+ {
+ return reset($this->_methods);
+ }
+
+ /**
+ * Iterator: is the current index valid?
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return (bool) $this->current();
+ }
+}
diff --git a/library/vendor/Zend/Server/Exception.php b/library/vendor/Zend/Server/Exception.php
new file mode 100644
index 0000000..c3b9c9f
--- /dev/null
+++ b/library/vendor/Zend/Server/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Exception
+ */
+
+/**
+ * Zend_Server_Reflection exceptions
+ *
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @version $Id$
+ */
+class Zend_Server_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Server/Interface.php b/library/vendor/Zend/Server/Interface.php
new file mode 100644
index 0000000..cc60bc5
--- /dev/null
+++ b/library/vendor/Zend/Server/Interface.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Interface
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+interface Zend_Server_Interface
+{
+ /**
+ * Attach a function as a server method
+ *
+ * Namespacing is primarily for xmlrpc, but may be used with other
+ * implementations to prevent naming collisions.
+ *
+ * @param string $function
+ * @param string $namespace
+ * @param null|array Optional array of arguments to pass to callbacks at
+ * dispatch.
+ * @return void
+ */
+ public function addFunction($function, $namespace = '');
+
+ /**
+ * Attach a class to a server
+ *
+ * The individual implementations should probably allow passing a variable
+ * number of arguments in, so that developers may define custom runtime
+ * arguments to pass to server methods.
+ *
+ * Namespacing is primarily for xmlrpc, but could be used for other
+ * implementations as well.
+ *
+ * @param mixed $class Class name or object instance to examine and attach
+ * to the server.
+ * @param string $namespace Optional namespace with which to prepend method
+ * names in the dispatch table.
+ * methods in the class will be valid callbacks.
+ * @param null|array Optional array of arguments to pass to callbacks at
+ * dispatch.
+ * @return void
+ */
+ public function setClass($class, $namespace = '', $argv = null);
+
+ /**
+ * Generate a server fault
+ *
+ * @param mixed $fault
+ * @param int $code
+ * @return mixed
+ */
+ public function fault($fault = null, $code = 404);
+
+ /**
+ * Handle a request
+ *
+ * Requests may be passed in, or the server may automagically determine the
+ * request based on defaults. Dispatches server request to appropriate
+ * method and returns a response
+ *
+ * @param mixed $request
+ * @return mixed
+ */
+ public function handle($request = false);
+
+ /**
+ * Return a server definition array
+ *
+ * Returns a server definition array as created using
+ * {@link * Zend_Server_Reflection}. Can be used for server introspection,
+ * documentation, or persistence.
+ *
+ * @access public
+ * @return array
+ */
+ public function getFunctions();
+
+ /**
+ * Load server definition
+ *
+ * Used for persistence; loads a construct as returned by {@link getFunctions()}.
+ *
+ * @param array $array
+ * @return void
+ */
+ public function loadFunctions($definition);
+
+ /**
+ * Set server persistence
+ *
+ * @todo Determine how to implement this
+ * @param int $mode
+ * @return void
+ */
+ public function setPersistence($mode);
+}
diff --git a/library/vendor/Zend/Server/Method/Callback.php b/library/vendor/Zend/Server/Method/Callback.php
new file mode 100644
index 0000000..3f81197
--- /dev/null
+++ b/library/vendor/Zend/Server/Method/Callback.php
@@ -0,0 +1,204 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Method callback metadata
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Method_Callback
+{
+ /**
+ * @var string Class name for class method callback
+ */
+ protected $_class;
+
+ /**
+ * @var string Function name for function callback
+ */
+ protected $_function;
+
+ /**
+ * @var string Method name for class method callback
+ */
+ protected $_method;
+
+ /**
+ * @var string Callback type
+ */
+ protected $_type;
+
+ /**
+ * @var array Valid callback types
+ */
+ protected $_types = array('function', 'static', 'instance');
+
+ /**
+ * Constructor
+ *
+ * @param null|array $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ((null !== $options) && is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set object state from array of options
+ *
+ * @param array $options
+ * @return Zend_Server_Method_Callback
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set callback class
+ *
+ * @param string $class
+ * @return Zend_Server_Method_Callback
+ */
+ public function setClass($class)
+ {
+ if (is_object($class)) {
+ $class = get_class($class);
+ }
+ $this->_class = $class;
+ return $this;
+ }
+
+ /**
+ * Get callback class
+ *
+ * @return string|null
+ */
+ public function getClass()
+ {
+ return $this->_class;
+ }
+
+ /**
+ * Set callback function
+ *
+ * @param string $function
+ * @return Zend_Server_Method_Callback
+ */
+ public function setFunction($function)
+ {
+ $this->_function = (string) $function;
+ $this->setType('function');
+ return $this;
+ }
+
+ /**
+ * Get callback function
+ *
+ * @return null|string
+ */
+ public function getFunction()
+ {
+ return $this->_function;
+ }
+
+ /**
+ * Set callback class method
+ *
+ * @param string $method
+ * @return Zend_Server_Method_Callback
+ */
+ public function setMethod($method)
+ {
+ $this->_method = $method;
+ return $this;
+ }
+
+ /**
+ * Get callback class method
+ *
+ * @return null|string
+ */
+ public function getMethod()
+ {
+ return $this->_method;
+ }
+
+ /**
+ * Set callback type
+ *
+ * @param string $type
+ * @return Zend_Server_Method_Callback
+ * @throws Zend_Server_Exception
+ */
+ public function setType($type)
+ {
+ if (!in_array($type, $this->_types)) {
+ throw new Zend_Server_Exception('Invalid method callback type passed to ' . __CLASS__ . '::' . __METHOD__);
+ }
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * Get callback type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Cast callback to array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $type = $this->getType();
+ $array = array(
+ 'type' => $type,
+ );
+ if ('function' == $type) {
+ $array['function'] = $this->getFunction();
+ } else {
+ $array['class'] = $this->getClass();
+ $array['method'] = $this->getMethod();
+ }
+ return $array;
+ }
+}
diff --git a/library/vendor/Zend/Server/Method/Definition.php b/library/vendor/Zend/Server/Method/Definition.php
new file mode 100644
index 0000000..5635ae6
--- /dev/null
+++ b/library/vendor/Zend/Server/Method/Definition.php
@@ -0,0 +1,288 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Method definition metadata
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Method_Definition
+{
+ /**
+ * @var Zend_Server_Method_Callback
+ */
+ protected $_callback;
+
+ /**
+ * @var array
+ */
+ protected $_invokeArguments = array();
+
+ /**
+ * @var string
+ */
+ protected $_methodHelp = '';
+
+ /**
+ * @var string
+ */
+ protected $_name;
+
+ /**
+ * @var null|object
+ */
+ protected $_object;
+
+ /**
+ * @var array Array of Zend_Server_Method_Prototype objects
+ */
+ protected $_prototypes = array();
+
+ /**
+ * Constructor
+ *
+ * @param null|array $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ((null !== $options) && is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set object state from options
+ *
+ * @param array $options
+ * @return Zend_Server_Method_Definition
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set method name
+ *
+ * @param string $name
+ * @return Zend_Server_Method_Definition
+ */
+ public function setName($name)
+ {
+ $this->_name = (string) $name;
+ return $this;
+ }
+
+ /**
+ * Get method name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Set method callback
+ *
+ * @param array|Zend_Server_Method_Callback $callback
+ * @return Zend_Server_Method_Definition
+ */
+ public function setCallback($callback)
+ {
+ if (is_array($callback)) {
+ $callback = new Zend_Server_Method_Callback($callback);
+ } elseif (!$callback instanceof Zend_Server_Method_Callback) {
+ throw new Zend_Server_Exception('Invalid method callback provided');
+ }
+ $this->_callback = $callback;
+ return $this;
+ }
+
+ /**
+ * Get method callback
+ *
+ * @return Zend_Server_Method_Callback
+ */
+ public function getCallback()
+ {
+ return $this->_callback;
+ }
+
+ /**
+ * Add prototype to method definition
+ *
+ * @param array|Zend_Server_Method_Prototype $prototype
+ * @return Zend_Server_Method_Definition
+ */
+ public function addPrototype($prototype)
+ {
+ if (is_array($prototype)) {
+ $prototype = new Zend_Server_Method_Prototype($prototype);
+ } elseif (!$prototype instanceof Zend_Server_Method_Prototype) {
+ throw new Zend_Server_Exception('Invalid method prototype provided');
+ }
+ $this->_prototypes[] = $prototype;
+ return $this;
+ }
+
+ /**
+ * Add multiple prototypes at once
+ *
+ * @param array $prototypes Array of Zend_Server_Method_Prototype objects or arrays
+ * @return Zend_Server_Method_Definition
+ */
+ public function addPrototypes(array $prototypes)
+ {
+ foreach ($prototypes as $prototype) {
+ $this->addPrototype($prototype);
+ }
+ return $this;
+ }
+
+ /**
+ * Set all prototypes at once (overwrites)
+ *
+ * @param array $prototypes Array of Zend_Server_Method_Prototype objects or arrays
+ * @return Zend_Server_Method_Definition
+ */
+ public function setPrototypes(array $prototypes)
+ {
+ $this->_prototypes = array();
+ $this->addPrototypes($prototypes);
+ return $this;
+ }
+
+ /**
+ * Get all prototypes
+ *
+ * @return array $prototypes Array of Zend_Server_Method_Prototype objects or arrays
+ */
+ public function getPrototypes()
+ {
+ return $this->_prototypes;
+ }
+
+ /**
+ * Set method help
+ *
+ * @param string $methodHelp
+ * @return Zend_Server_Method_Definition
+ */
+ public function setMethodHelp($methodHelp)
+ {
+ $this->_methodHelp = (string) $methodHelp;
+ return $this;
+ }
+
+ /**
+ * Get method help
+ *
+ * @return string
+ */
+ public function getMethodHelp()
+ {
+ return $this->_methodHelp;
+ }
+
+ /**
+ * Set object to use with method calls
+ *
+ * @param object $object
+ * @return Zend_Server_Method_Definition
+ */
+ public function setObject($object)
+ {
+ if (!is_object($object) && (null !== $object)) {
+ throw new Zend_Server_Exception('Invalid object passed to ' . __CLASS__ . '::' . __METHOD__);
+ }
+ $this->_object = $object;
+ return $this;
+ }
+
+ /**
+ * Get object to use with method calls
+ *
+ * @return null|object
+ */
+ public function getObject()
+ {
+ return $this->_object;
+ }
+
+ /**
+ * Set invoke arguments
+ *
+ * @param array $invokeArguments
+ * @return Zend_Server_Method_Definition
+ */
+ public function setInvokeArguments(array $invokeArguments)
+ {
+ $this->_invokeArguments = $invokeArguments;
+ return $this;
+ }
+
+ /**
+ * Retrieve invoke arguments
+ *
+ * @return array
+ */
+ public function getInvokeArguments()
+ {
+ return $this->_invokeArguments;
+ }
+
+ /**
+ * Serialize to array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $prototypes = $this->getPrototypes();
+ $signatures = array();
+ foreach ($prototypes as $prototype) {
+ $signatures[] = $prototype->toArray();
+ }
+
+ return array(
+ 'name' => $this->getName(),
+ 'callback' => $this->getCallback()->toArray(),
+ 'prototypes' => $signatures,
+ 'methodHelp' => $this->getMethodHelp(),
+ 'invokeArguments' => $this->getInvokeArguments(),
+ 'object' => $this->getObject(),
+ );
+ }
+}
diff --git a/library/vendor/Zend/Server/Method/Parameter.php b/library/vendor/Zend/Server/Method/Parameter.php
new file mode 100644
index 0000000..63e14a4
--- /dev/null
+++ b/library/vendor/Zend/Server/Method/Parameter.php
@@ -0,0 +1,214 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Method parameter metadata
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Method_Parameter
+{
+ /**
+ * @var mixed Default parameter value
+ */
+ protected $_defaultValue;
+
+ /**
+ * @var string Parameter description
+ */
+ protected $_description = '';
+
+ /**
+ * @var string Parameter variable name
+ */
+ protected $_name;
+
+ /**
+ * @var bool Is parameter optional?
+ */
+ protected $_optional = false;
+
+ /**
+ * @var string Parameter type
+ */
+ protected $_type = 'mixed';
+
+ /**
+ * Constructor
+ *
+ * @param null|array $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if (is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set object state from array of options
+ *
+ * @param array $options
+ * @return Zend_Server_Method_Parameter
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Set default value
+ *
+ * @param mixed $defaultValue
+ * @return Zend_Server_Method_Parameter
+ */
+ public function setDefaultValue($defaultValue)
+ {
+ $this->_defaultValue = $defaultValue;
+ return $this;
+ }
+
+ /**
+ * Retrieve default value
+ *
+ * @return mixed
+ */
+ public function getDefaultValue()
+ {
+ return $this->_defaultValue;
+ }
+
+ /**
+ * Set description
+ *
+ * @param string $description
+ * @return Zend_Server_Method_Parameter
+ */
+ public function setDescription($description)
+ {
+ $this->_description = (string) $description;
+ return $this;
+ }
+
+ /**
+ * Retrieve description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Set name
+ *
+ * @param string $name
+ * @return Zend_Server_Method_Parameter
+ */
+ public function setName($name)
+ {
+ $this->_name = (string) $name;
+ return $this;
+ }
+
+ /**
+ * Retrieve name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+ /**
+ * Set optional flag
+ *
+ * @param bool $flag
+ * @return Zend_Server_Method_Parameter
+ */
+ public function setOptional($flag)
+ {
+ $this->_optional = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is the parameter optional?
+ *
+ * @return bool
+ */
+ public function isOptional()
+ {
+ return $this->_optional;
+ }
+
+ /**
+ * Set parameter type
+ *
+ * @param string $type
+ * @return Zend_Server_Method_Parameter
+ */
+ public function setType($type)
+ {
+ $this->_type = (string) $type;
+ return $this;
+ }
+
+ /**
+ * Retrieve parameter type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Cast to array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return array(
+ 'type' => $this->getType(),
+ 'name' => $this->getName(),
+ 'optional' => $this->isOptional(),
+ 'defaultValue' => $this->getDefaultValue(),
+ 'description' => $this->getDescription(),
+ );
+ }
+}
diff --git a/library/vendor/Zend/Server/Method/Prototype.php b/library/vendor/Zend/Server/Method/Prototype.php
new file mode 100644
index 0000000..33c5a14
--- /dev/null
+++ b/library/vendor/Zend/Server/Method/Prototype.php
@@ -0,0 +1,207 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Method prototype metadata
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Method
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Method_Prototype
+{
+ /**
+ * @var string Return type
+ */
+ protected $_returnType = 'void';
+
+ /**
+ * @var array Map parameter names to parameter index
+ */
+ protected $_parameterNameMap = array();
+
+ /**
+ * @var array Method parameters
+ */
+ protected $_parameters = array();
+
+ /**
+ * Constructor
+ *
+ * @param null|array $options
+ * @return void
+ */
+ public function __construct($options = null)
+ {
+ if ((null !== $options) && is_array($options)) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set return value
+ *
+ * @param string $returnType
+ * @return Zend_Server_Method_Prototype
+ */
+ public function setReturnType($returnType)
+ {
+ $this->_returnType = $returnType;
+ return $this;
+ }
+
+ /**
+ * Retrieve return type
+ *
+ * @return string
+ */
+ public function getReturnType()
+ {
+ return $this->_returnType;
+ }
+
+ /**
+ * Add a parameter
+ *
+ * @param string $parameter
+ * @return Zend_Server_Method_Prototype
+ */
+ public function addParameter($parameter)
+ {
+ if ($parameter instanceof Zend_Server_Method_Parameter) {
+ $this->_parameters[] = $parameter;
+ if (null !== ($name = $parameter->getName())) {
+ $this->_parameterNameMap[$name] = count($this->_parameters) - 1;
+ }
+ } else {
+ $parameter = new Zend_Server_Method_Parameter(array(
+ 'type' => (string) $parameter,
+ ));
+ $this->_parameters[] = $parameter;
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters
+ *
+ * @param array $parameter
+ * @return Zend_Server_Method_Prototype
+ */
+ public function addParameters(array $parameters)
+ {
+ foreach ($parameters as $parameter) {
+ $this->addParameter($parameter);
+ }
+ return $this;
+ }
+
+ /**
+ * Set parameters
+ *
+ * @param array $parameters
+ * @return Zend_Server_Method_Prototype
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->_parameters = array();
+ $this->_parameterNameMap = array();
+ $this->addParameters($parameters);
+ return $this;
+ }
+
+ /**
+ * Retrieve parameters as list of types
+ *
+ * @return array
+ */
+ public function getParameters()
+ {
+ $types = array();
+ foreach ($this->_parameters as $parameter) {
+ $types[] = $parameter->getType();
+ }
+ return $types;
+ }
+
+ /**
+ * Get parameter objects
+ *
+ * @return array
+ */
+ public function getParameterObjects()
+ {
+ return $this->_parameters;
+ }
+
+ /**
+ * Retrieve a single parameter by name or index
+ *
+ * @param string|int $index
+ * @return null|Zend_Server_Method_Parameter
+ */
+ public function getParameter($index)
+ {
+ if (!is_string($index) && !is_numeric($index)) {
+ return null;
+ }
+ if (array_key_exists($index, $this->_parameterNameMap)) {
+ $index = $this->_parameterNameMap[$index];
+ }
+ if (array_key_exists($index, $this->_parameters)) {
+ return $this->_parameters[$index];
+ }
+ return null;
+ }
+
+ /**
+ * Set object state from array
+ *
+ * @param array $options
+ * @return Zend_Server_Method_Prototype
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $this->$method($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Serialize to array
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return array(
+ 'returnType' => $this->getReturnType(),
+ 'parameters' => $this->getParameters(),
+ );
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection.php b/library/vendor/Zend/Server/Reflection.php
new file mode 100644
index 0000000..5d95c64
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_Function
+ */
+
+/**
+ * Zend_Server_Reflection_Class
+ */
+
+/**
+ * Reflection for determining method signatures to use with server classes
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection
+{
+ /**
+ * Perform class reflection to create dispatch signatures
+ *
+ * Creates a {@link Zend_Server_Reflection_Class} object for the class or
+ * object provided.
+ *
+ * If extra arguments should be passed to dispatchable methods, these may
+ * be provided as an array to $argv.
+ *
+ * @param string|object $class Class name or object
+ * @param null|array $argv Optional arguments to be used during the method call
+ * @param string $namespace Optional namespace with which to prefix the
+ * method name (used for the signature key). Primarily to avoid collisions,
+ * also for XmlRpc namespacing
+ * @return Zend_Server_Reflection_Class
+ * @throws Zend_Server_Reflection_Exception
+ */
+ public static function reflectClass($class, $argv = false, $namespace = '')
+ {
+ if (is_object($class)) {
+ $reflection = new ReflectionObject($class);
+ } elseif (class_exists($class)) {
+ $reflection = new ReflectionClass($class);
+ } else {
+ throw new Zend_Server_Reflection_Exception('Invalid class or object passed to attachClass()');
+ }
+
+ if ($argv && !is_array($argv)) {
+ throw new Zend_Server_Reflection_Exception('Invalid argv argument passed to reflectClass');
+ }
+
+ return new Zend_Server_Reflection_Class($reflection, $namespace, $argv);
+ }
+
+ /**
+ * Perform function reflection to create dispatch signatures
+ *
+ * Creates dispatch prototypes for a function. It returns a
+ * {@link Zend_Server_Reflection_Function} object.
+ *
+ * If extra arguments should be passed to the dispatchable function, these
+ * may be provided as an array to $argv.
+ *
+ * @param string $function Function name
+ * @param null|array $argv Optional arguments to be used during the method call
+ * @param string $namespace Optional namespace with which to prefix the
+ * function name (used for the signature key). Primarily to avoid
+ * collisions, also for XmlRpc namespacing
+ * @return Zend_Server_Reflection_Function
+ * @throws Zend_Server_Reflection_Exception
+ */
+ public static function reflectFunction($function, $argv = false, $namespace = '')
+ {
+ if (!is_string($function) || !function_exists($function)) {
+ throw new Zend_Server_Reflection_Exception('Invalid function "' . $function . '" passed to reflectFunction');
+ }
+
+
+ if ($argv && !is_array($argv)) {
+ throw new Zend_Server_Reflection_Exception('Invalid argv argument passed to reflectClass');
+ }
+
+ return new Zend_Server_Reflection_Function(new ReflectionFunction($function), $namespace, $argv);
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection/Class.php b/library/vendor/Zend/Server/Reflection/Class.php
new file mode 100644
index 0000000..9456f0a
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Class.php
@@ -0,0 +1,195 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_Method
+ */
+
+/**
+ * Class/Object reflection
+ *
+ * Proxies calls to a ReflectionClass object, and decorates getMethods() by
+ * creating its own list of {@link Zend_Server_Reflection_Method}s.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_Class
+{
+ /**
+ * Optional configuration parameters; accessible via {@link __get} and
+ * {@link __set()}
+ * @var array
+ */
+ protected $_config = array();
+
+ /**
+ * Array of {@link Zend_Server_Reflection_Method}s
+ * @var array
+ */
+ protected $_methods = array();
+
+ /**
+ * Namespace
+ * @var string
+ */
+ protected $_namespace = null;
+
+ /**
+ * ReflectionClass object
+ * @var ReflectionClass
+ */
+ protected $_reflection;
+
+ /**
+ * Constructor
+ *
+ * Create array of dispatchable methods, each a
+ * {@link Zend_Server_Reflection_Method}. Sets reflection object property.
+ *
+ * @param ReflectionClass $reflection
+ * @param string $namespace
+ * @param mixed $argv
+ * @return void
+ */
+ public function __construct(ReflectionClass $reflection, $namespace = null, $argv = false)
+ {
+ $this->_reflection = $reflection;
+ $this->setNamespace($namespace);
+
+ foreach ($reflection->getMethods() as $method) {
+ // Don't aggregate magic methods
+ if ('__' == substr($method->getName(), 0, 2)) {
+ continue;
+ }
+
+ if ($method->isPublic()) {
+ // Get signatures and description
+ $this->_methods[] = new Zend_Server_Reflection_Method($this, $method, $this->getNamespace(), $argv);
+ }
+ }
+ }
+
+ /**
+ * Proxy reflection calls
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (method_exists($this->_reflection, $method)) {
+ return call_user_func_array(array($this->_reflection, $method), $args);
+ }
+
+ throw new Zend_Server_Reflection_Exception('Invalid reflection method');
+ }
+
+ /**
+ * Retrieve configuration parameters
+ *
+ * Values are retrieved by key from {@link $_config}. Returns null if no
+ * value found.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (isset($this->_config[$key])) {
+ return $this->_config[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Set configuration parameters
+ *
+ * Values are stored by $key in {@link $_config}.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->_config[$key] = $value;
+ }
+
+ /**
+ * Return array of dispatchable {@link Zend_Server_Reflection_Method}s.
+ *
+ * @access public
+ * @return array
+ */
+ public function getMethods()
+ {
+ return $this->_methods;
+ }
+
+ /**
+ * Get namespace for this class
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->_namespace;
+ }
+
+ /**
+ * Set namespace for this class
+ *
+ * @param string $namespace
+ * @return void
+ */
+ public function setNamespace($namespace)
+ {
+ if (empty($namespace)) {
+ $this->_namespace = '';
+ return;
+ }
+
+ if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) {
+ throw new Zend_Server_Reflection_Exception('Invalid namespace');
+ }
+
+ $this->_namespace = $namespace;
+ }
+
+ /**
+ * Wakeup from serialization
+ *
+ * Reflection needs explicit instantiation to work correctly. Re-instantiate
+ * reflection object on wakeup.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ $this->_reflection = new ReflectionClass($this->getName());
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection/Exception.php b/library/vendor/Zend/Server/Reflection/Exception.php
new file mode 100644
index 0000000..e264768
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Exception.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * @see Zend_Server_Exception
+ */
+
+/**
+ * Zend_Server_Reflection exceptions
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_Exception extends Zend_Server_Exception
+{
+}
diff --git a/library/vendor/Zend/Server/Reflection/Function.php b/library/vendor/Zend/Server/Reflection/Function.php
new file mode 100644
index 0000000..cd76afa
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Function.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_Function_Abstract
+ */
+
+/**
+ * Function Reflection
+ *
+ * @uses Zend_Server_Reflection_Function_Abstract
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_Function extends Zend_Server_Reflection_Function_Abstract
+{
+}
diff --git a/library/vendor/Zend/Server/Reflection/Function/Abstract.php b/library/vendor/Zend/Server/Reflection/Function/Abstract.php
new file mode 100644
index 0000000..f0557f8
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Function/Abstract.php
@@ -0,0 +1,506 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_Node
+ */
+
+/**
+ * Zend_Server_Reflection_Parameter
+ */
+
+/**
+ * Zend_Server_Reflection_Prototype
+ */
+
+/**
+ * Function/Method Reflection
+ *
+ * Decorates a ReflectionFunction. Allows setting and retrieving an alternate
+ * 'service' name (i.e., the name to be used when calling via a service),
+ * setting and retrieving the description (originally set using the docblock
+ * contents), retrieving the callback and callback type, retrieving additional
+ * method invocation arguments, and retrieving the
+ * method {@link Zend_Server_Reflection_Prototype prototypes}.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+abstract class Zend_Server_Reflection_Function_Abstract
+{
+ /**
+ * @var ReflectionFunction
+ */
+ protected $_reflection;
+
+ /**
+ * Additional arguments to pass to method on invocation
+ * @var array
+ */
+ protected $_argv = array();
+
+ /**
+ * Used to store extra configuration for the method (typically done by the
+ * server class, e.g., to indicate whether or not to instantiate a class).
+ * Associative array; access is as properties via {@link __get()} and
+ * {@link __set()}
+ * @var array
+ */
+ protected $_config = array();
+
+ /**
+ * Declaring class (needed for when serialization occurs)
+ * @var string
+ */
+ protected $_class;
+
+ /**
+ * Function/method description
+ * @var string
+ */
+ protected $_description = '';
+
+ /**
+ * Namespace with which to prefix function/method name
+ * @var string
+ */
+ protected $_namespace;
+
+ /**
+ * Prototypes
+ * @var array
+ */
+ protected $_prototypes = array();
+
+ private $_return;
+ private $_returnDesc;
+ private $_paramDesc;
+ private $_sigParams;
+ private $_sigParamsDepth;
+
+ /**
+ * Constructor
+ *
+ * @param ReflectionFunction $r
+ */
+ public function __construct(Reflector $r, $namespace = null, $argv = array())
+ {
+ // In PHP 5.1.x, ReflectionMethod extends ReflectionFunction. In 5.2.x,
+ // both extend ReflectionFunctionAbstract. So, we can't do normal type
+ // hinting in the prototype, but instead need to do some explicit
+ // testing here.
+ if ((!$r instanceof ReflectionFunction)
+ && (!$r instanceof ReflectionMethod)) {
+ throw new Zend_Server_Reflection_Exception('Invalid reflection class');
+ }
+ $this->_reflection = $r;
+
+ // Determine namespace
+ if (null !== $namespace){
+ $this->setNamespace($namespace);
+ }
+
+ // Determine arguments
+ if (is_array($argv)) {
+ $this->_argv = $argv;
+ }
+
+ // If method call, need to store some info on the class
+ if ($r instanceof ReflectionMethod) {
+ $this->_class = $r->getDeclaringClass()->getName();
+ }
+
+ // Perform some introspection
+ $this->_reflect();
+ }
+
+ /**
+ * Create signature node tree
+ *
+ * Recursive method to build the signature node tree. Increments through
+ * each array in {@link $_sigParams}, adding every value of the next level
+ * to the current value (unless the current value is null).
+ *
+ * @param Zend_Server_Reflection_Node $parent
+ * @param int $level
+ * @return void
+ */
+ protected function _addTree(Zend_Server_Reflection_Node $parent, $level = 0)
+ {
+ if ($level >= $this->_sigParamsDepth) {
+ return;
+ }
+
+ foreach ($this->_sigParams[$level] as $value) {
+ $node = new Zend_Server_Reflection_Node($value, $parent);
+ if ((null !== $value) && ($this->_sigParamsDepth > $level + 1)) {
+ $this->_addTree($node, $level + 1);
+ }
+ }
+ }
+
+ /**
+ * Build the signature tree
+ *
+ * Builds a signature tree starting at the return values and descending
+ * through each method argument. Returns an array of
+ * {@link Zend_Server_Reflection_Node}s.
+ *
+ * @return array
+ */
+ protected function _buildTree()
+ {
+ $returnTree = array();
+ foreach ((array) $this->_return as $value) {
+ $node = new Zend_Server_Reflection_Node($value);
+ $this->_addTree($node);
+ $returnTree[] = $node;
+ }
+
+ return $returnTree;
+ }
+
+ /**
+ * Build method signatures
+ *
+ * Builds method signatures using the array of return types and the array of
+ * parameters types
+ *
+ * @param array $return Array of return types
+ * @param string $returnDesc Return value description
+ * @param array $params Array of arguments (each an array of types)
+ * @param array $paramDesc Array of parameter descriptions
+ * @return array
+ */
+ protected function _buildSignatures($return, $returnDesc, $paramTypes, $paramDesc)
+ {
+ $this->_return = $return;
+ $this->_returnDesc = $returnDesc;
+ $this->_paramDesc = $paramDesc;
+ $this->_sigParams = $paramTypes;
+ $this->_sigParamsDepth = count($paramTypes);
+ $signatureTrees = $this->_buildTree();
+ $signatures = array();
+
+ $endPoints = array();
+ foreach ($signatureTrees as $root) {
+ $tmp = $root->getEndPoints();
+ if (empty($tmp)) {
+ $endPoints = array_merge($endPoints, array($root));
+ } else {
+ $endPoints = array_merge($endPoints, $tmp);
+ }
+ }
+
+ foreach ($endPoints as $node) {
+ if (!$node instanceof Zend_Server_Reflection_Node) {
+ continue;
+ }
+
+ $signature = array();
+ do {
+ array_unshift($signature, $node->getValue());
+ $node = $node->getParent();
+ } while ($node instanceof Zend_Server_Reflection_Node);
+
+ $signatures[] = $signature;
+ }
+
+ // Build prototypes
+ $params = $this->_reflection->getParameters();
+ foreach ($signatures as $signature) {
+ $return = new Zend_Server_Reflection_ReturnValue(array_shift($signature), $this->_returnDesc);
+ $tmp = array();
+ foreach ($signature as $key => $type) {
+ $param = new Zend_Server_Reflection_Parameter($params[$key], $type, (isset($this->_paramDesc[$key]) ? $this->_paramDesc[$key] : null));
+ $param->setPosition($key);
+ $tmp[] = $param;
+ }
+
+ $this->_prototypes[] = new Zend_Server_Reflection_Prototype($return, $tmp);
+ }
+ }
+
+ /**
+ * Use code reflection to create method signatures
+ *
+ * Determines the method help/description text from the function DocBlock
+ * comment. Determines method signatures using a combination of
+ * ReflectionFunction and parsing of DocBlock @param and @return values.
+ *
+ * @param ReflectionFunction $function
+ * @return array
+ */
+ protected function _reflect()
+ {
+ $function = $this->_reflection;
+ $helpText = '';
+ $signatures = array();
+ $returnDesc = '';
+ $paramCount = $function->getNumberOfParameters();
+ $paramCountRequired = $function->getNumberOfRequiredParameters();
+ $parameters = $function->getParameters();
+ $docBlock = $function->getDocComment();
+
+ if (!empty($docBlock)) {
+ // Get help text
+ if (preg_match(':/\*\*\s*\r?\n\s*\*\s(.*?)\r?\n\s*\*(\s@|/):s', $docBlock, $matches))
+ {
+ $helpText = $matches[1];
+ $helpText = preg_replace('/(^\s*\*\s)/m', '', $helpText);
+ $helpText = preg_replace('/\r?\n\s*\*\s*(\r?\n)*/s', "\n", $helpText);
+ $helpText = trim($helpText);
+ }
+
+ // Get return type(s) and description
+ $return = 'void';
+ if (preg_match('/@return\s+(\S+)/', $docBlock, $matches)) {
+ $return = explode('|', $matches[1]);
+ if (preg_match('/@return\s+\S+\s+(.*?)(@|\*\/)/s', $docBlock, $matches))
+ {
+ $value = $matches[1];
+ $value = preg_replace('/\s?\*\s/m', '', $value);
+ $value = preg_replace('/\s{2,}/', ' ', $value);
+ $returnDesc = trim($value);
+ }
+ }
+
+ // Get param types and description
+ if (preg_match_all('/@param\s+([^\s]+)/m', $docBlock, $matches)) {
+ $paramTypesTmp = $matches[1];
+ if (preg_match_all('/@param\s+\S+\s+(\$\S+)\s+(.*?)(?=@|\*\/)/s', $docBlock, $matches))
+ {
+ $paramDesc = $matches[2];
+ foreach ($paramDesc as $key => $value) {
+ $value = preg_replace('/\s?\*\s/m', '', $value);
+ $value = preg_replace('/\s{2,}/', ' ', $value);
+ $paramDesc[$key] = trim($value);
+ }
+ }
+ }
+ } else {
+ $helpText = $function->getName();
+ $return = 'void';
+
+ // Try and auto-determine type, based on reflection
+ $paramTypesTmp = array();
+ foreach ($parameters as $i => $param) {
+ $paramType = 'mixed';
+ if ($param->isArray()) {
+ $paramType = 'array';
+ }
+ $paramTypesTmp[$i] = $paramType;
+ }
+ }
+
+ // Set method description
+ $this->setDescription($helpText);
+
+ // Get all param types as arrays
+ if (!isset($paramTypesTmp) && (0 < $paramCount)) {
+ $paramTypesTmp = array_fill(0, $paramCount, 'mixed');
+ } elseif (!isset($paramTypesTmp)) {
+ $paramTypesTmp = array();
+ } elseif (count($paramTypesTmp) < $paramCount) {
+ $start = $paramCount - count($paramTypesTmp);
+ for ($i = $start; $i < $paramCount; ++$i) {
+ $paramTypesTmp[$i] = 'mixed';
+ }
+ }
+
+ // Get all param descriptions as arrays
+ if (!isset($paramDesc) && (0 < $paramCount)) {
+ $paramDesc = array_fill(0, $paramCount, '');
+ } elseif (!isset($paramDesc)) {
+ $paramDesc = array();
+ } elseif (count($paramDesc) < $paramCount) {
+ $start = $paramCount - count($paramDesc);
+ for ($i = $start; $i < $paramCount; ++$i) {
+ $paramDesc[$i] = '';
+ }
+ }
+
+ if (count($paramTypesTmp) != $paramCount) {
+ throw new Zend_Server_Reflection_Exception(
+ 'Variable number of arguments is not supported for services (except optional parameters). '
+ . 'Number of function arguments in ' . $function->getDeclaringClass()->getName() . '::'
+ . $function->getName() . '() must correspond to actual number of arguments described in the '
+ . 'docblock.');
+ }
+
+ $paramTypes = array();
+ foreach ($paramTypesTmp as $i => $param) {
+ $tmp = explode('|', $param);
+ if ($parameters[$i]->isOptional()) {
+ array_unshift($tmp, null);
+ }
+ $paramTypes[] = $tmp;
+ }
+
+ $this->_buildSignatures($return, $returnDesc, $paramTypes, $paramDesc);
+ }
+
+
+ /**
+ * Proxy reflection calls
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (method_exists($this->_reflection, $method)) {
+ return call_user_func_array(array($this->_reflection, $method), $args);
+ }
+
+ throw new Zend_Server_Reflection_Exception('Invalid reflection method ("' .$method. '")');
+ }
+
+ /**
+ * Retrieve configuration parameters
+ *
+ * Values are retrieved by key from {@link $_config}. Returns null if no
+ * value found.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ if (isset($this->_config[$key])) {
+ return $this->_config[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Set configuration parameters
+ *
+ * Values are stored by $key in {@link $_config}.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $this->_config[$key] = $value;
+ }
+
+ /**
+ * Set method's namespace
+ *
+ * @param string $namespace
+ * @return void
+ */
+ public function setNamespace($namespace)
+ {
+ if (empty($namespace)) {
+ $this->_namespace = '';
+ return;
+ }
+
+ if (!is_string($namespace) || !preg_match('/[a-z0-9_\.]+/i', $namespace)) {
+ throw new Zend_Server_Reflection_Exception('Invalid namespace');
+ }
+
+ $this->_namespace = $namespace;
+ }
+
+ /**
+ * Return method's namespace
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->_namespace;
+ }
+
+ /**
+ * Set the description
+ *
+ * @param string $string
+ * @return void
+ */
+ public function setDescription($string)
+ {
+ if (!is_string($string)) {
+ throw new Zend_Server_Reflection_Exception('Invalid description');
+ }
+
+ $this->_description = $string;
+ }
+
+ /**
+ * Retrieve the description
+ *
+ * @return void
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Retrieve all prototypes as array of
+ * {@link Zend_Server_Reflection_Prototype Zend_Server_Reflection_Prototypes}
+ *
+ * @return array
+ */
+ public function getPrototypes()
+ {
+ return $this->_prototypes;
+ }
+
+ /**
+ * Retrieve additional invocation arguments
+ *
+ * @return array
+ */
+ public function getInvokeArguments()
+ {
+ return $this->_argv;
+ }
+
+ /**
+ * Wakeup from serialization
+ *
+ * Reflection needs explicit instantiation to work correctly. Re-instantiate
+ * reflection object on wakeup.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ if ($this->_reflection instanceof ReflectionMethod) {
+ $class = new ReflectionClass($this->_class);
+ $this->_reflection = new ReflectionMethod($class->newInstance(), $this->getName());
+ } else {
+ $this->_reflection = new ReflectionFunction($this->getName());
+ }
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection/Method.php b/library/vendor/Zend/Server/Reflection/Method.php
new file mode 100644
index 0000000..305b783
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Method.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_Function_Abstract
+ */
+
+/**
+ * Method Reflection
+ *
+ * @uses Zend_Server_Reflection_Function_Abstract
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_Method extends Zend_Server_Reflection_Function_Abstract
+{
+ /**
+ * Parent class name
+ * @var string
+ */
+ protected $_class;
+
+ /**
+ * Parent class reflection
+ * @var Zend_Server_Reflection_Class
+ */
+ protected $_classReflection;
+
+ /**
+ * Constructor
+ *
+ * @param Zend_Server_Reflection_Class $class
+ * @param ReflectionMethod $r
+ * @param string $namespace
+ * @param array $argv
+ * @return void
+ */
+ public function __construct(Zend_Server_Reflection_Class $class, ReflectionMethod $r, $namespace = null, $argv = array())
+ {
+ $this->_classReflection = $class;
+ $this->_reflection = $r;
+
+ $classNamespace = $class->getNamespace();
+
+ // Determine namespace
+ if (!empty($namespace)) {
+ $this->setNamespace($namespace);
+ } elseif (!empty($classNamespace)) {
+ $this->setNamespace($classNamespace);
+ }
+
+ // Determine arguments
+ if (is_array($argv)) {
+ $this->_argv = $argv;
+ }
+
+ // If method call, need to store some info on the class
+ $this->_class = $class->getName();
+
+ // Perform some introspection
+ $this->_reflect();
+ }
+
+ /**
+ * Return the reflection for the class that defines this method
+ *
+ * @return Zend_Server_Reflection_Class
+ */
+ public function getDeclaringClass()
+ {
+ return $this->_classReflection;
+ }
+
+ /**
+ * Wakeup from serialization
+ *
+ * Reflection needs explicit instantiation to work correctly. Re-instantiate
+ * reflection object on wakeup.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ $this->_classReflection = new Zend_Server_Reflection_Class(new ReflectionClass($this->_class), $this->getNamespace(), $this->getInvokeArguments());
+ $this->_reflection = new ReflectionMethod($this->_classReflection->getName(), $this->getName());
+ }
+
+}
diff --git a/library/vendor/Zend/Server/Reflection/Node.php b/library/vendor/Zend/Server/Reflection/Node.php
new file mode 100644
index 0000000..d80abf3
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Node.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Node Tree class for Zend_Server reflection operations
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @version $Id$
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Server_Reflection_Node
+{
+ /**
+ * Node value
+ * @var mixed
+ */
+ protected $_value = null;
+
+ /**
+ * Array of child nodes (if any)
+ * @var array
+ */
+ protected $_children = array();
+
+ /**
+ * Parent node (if any)
+ * @var Zend_Server_Reflection_Node
+ */
+ protected $_parent = null;
+
+ /**
+ * Constructor
+ *
+ * @param mixed $value
+ * @param Zend_Server_Reflection_Node $parent Optional
+ * @return Zend_Server_Reflection_Node
+ */
+ public function __construct($value, Zend_Server_Reflection_Node $parent = null)
+ {
+ $this->_value = $value;
+ if (null !== $parent) {
+ $this->setParent($parent, true);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set parent node
+ *
+ * @param Zend_Server_Reflection_Node $node
+ * @param boolean $new Whether or not the child node is newly created
+ * and should always be attached
+ * @return void
+ */
+ public function setParent(Zend_Server_Reflection_Node $node, $new = false)
+ {
+ $this->_parent = $node;
+
+ if ($new) {
+ $node->attachChild($this);
+ return;
+ }
+ }
+
+ /**
+ * Create and attach a new child node
+ *
+ * @param mixed $value
+ * @access public
+ * @return Zend_Server_Reflection_Node New child node
+ */
+ public function createChild($value)
+ {
+ $child = new self($value, $this);
+
+ return $child;
+ }
+
+ /**
+ * Attach a child node
+ *
+ * @param Zend_Server_Reflection_Node $node
+ * @return void
+ */
+ public function attachChild(Zend_Server_Reflection_Node $node)
+ {
+ $this->_children[] = $node;
+
+ if ($node->getParent() !== $this) {
+ $node->setParent($this);
+ }
+ }
+
+ /**
+ * Return an array of all child nodes
+ *
+ * @return array
+ */
+ public function getChildren()
+ {
+ return $this->_children;
+ }
+
+ /**
+ * Does this node have children?
+ *
+ * @return boolean
+ */
+ public function hasChildren()
+ {
+ return count($this->_children) > 0;
+ }
+
+ /**
+ * Return the parent node
+ *
+ * @return null|Zend_Server_Reflection_Node
+ */
+ public function getParent()
+ {
+ return $this->_parent;
+ }
+
+ /**
+ * Return the node's current value
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ /**
+ * Set the node value
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function setValue($value)
+ {
+ $this->_value = $value;
+ }
+
+ /**
+ * Retrieve the bottommost nodes of this node's tree
+ *
+ * Retrieves the bottommost nodes of the tree by recursively calling
+ * getEndPoints() on all children. If a child is null, it returns the parent
+ * as an end point.
+ *
+ * @return array
+ */
+ public function getEndPoints()
+ {
+ $endPoints = array();
+ if (!$this->hasChildren()) {
+ return $endPoints;
+ }
+
+ foreach ($this->_children as $child) {
+ $value = $child->getValue();
+
+ if (null === $value) {
+ $endPoints[] = $this;
+ } elseif ((null !== $value)
+ && $child->hasChildren())
+ {
+ $childEndPoints = $child->getEndPoints();
+ if (!empty($childEndPoints)) {
+ $endPoints = array_merge($endPoints, $childEndPoints);
+ }
+ } elseif ((null !== $value) && !$child->hasChildren()) {
+ $endPoints[] = $child;
+ }
+ }
+
+ return $endPoints;
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection/Parameter.php b/library/vendor/Zend/Server/Reflection/Parameter.php
new file mode 100644
index 0000000..681824a
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Parameter.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Parameter Reflection
+ *
+ * Decorates a ReflectionParameter to allow setting the parameter type
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_Parameter
+{
+ /**
+ * @var ReflectionParameter
+ */
+ protected $_reflection;
+
+ /**
+ * Parameter position
+ * @var int
+ */
+ protected $_position;
+
+ /**
+ * Parameter type
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Parameter description
+ * @var string
+ */
+ protected $_description;
+
+ /**
+ * Constructor
+ *
+ * @param ReflectionParameter $r
+ * @param string $type Parameter type
+ * @param string $description Parameter description
+ */
+ public function __construct(ReflectionParameter $r, $type = 'mixed', $description = '')
+ {
+ $this->_reflection = $r;
+ $this->setType($type);
+ $this->setDescription($description);
+ }
+
+ /**
+ * Proxy reflection calls
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (method_exists($this->_reflection, $method)) {
+ return call_user_func_array(array($this->_reflection, $method), $args);
+ }
+
+ throw new Zend_Server_Reflection_Exception('Invalid reflection method');
+ }
+
+ /**
+ * Retrieve parameter type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Set parameter type
+ *
+ * @param string|null $type
+ * @return void
+ */
+ public function setType($type)
+ {
+ if (!is_string($type) && (null !== $type)) {
+ throw new Zend_Server_Reflection_Exception('Invalid parameter type');
+ }
+
+ $this->_type = $type;
+ }
+
+ /**
+ * Retrieve parameter description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Set parameter description
+ *
+ * @param string|null $description
+ * @return void
+ */
+ public function setDescription($description)
+ {
+ if (!is_string($description) && (null !== $description)) {
+ throw new Zend_Server_Reflection_Exception('Invalid parameter description');
+ }
+
+ $this->_description = $description;
+ }
+
+ /**
+ * Set parameter position
+ *
+ * @param int $index
+ * @return void
+ */
+ public function setPosition($index)
+ {
+ $this->_position = (int) $index;
+ }
+
+ /**
+ * Return parameter position
+ *
+ * @return int
+ */
+ public function getPosition()
+ {
+ return $this->_position;
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection/Prototype.php b/library/vendor/Zend/Server/Reflection/Prototype.php
new file mode 100644
index 0000000..e780623
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/Prototype.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Server_Reflection_ReturnValue
+ */
+
+/**
+ * Zend_Server_Reflection_Parameter
+ */
+
+/**
+ * Method/Function prototypes
+ *
+ * Contains accessors for the return value and all method arguments.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_Prototype
+{
+ /**
+ * Constructor
+ *
+ * @param Zend_Server_Reflection_ReturnValue $return
+ * @param array $params
+ * @return void
+ */
+ public function __construct(Zend_Server_Reflection_ReturnValue $return, $params = null)
+ {
+ $this->_return = $return;
+
+ if (!is_array($params) && (null !== $params)) {
+ throw new Zend_Server_Reflection_Exception('Invalid parameters');
+ }
+
+ if (is_array($params)) {
+ foreach ($params as $param) {
+ if (!$param instanceof Zend_Server_Reflection_Parameter) {
+ throw new Zend_Server_Reflection_Exception('One or more params are invalid');
+ }
+ }
+ }
+
+ $this->_params = $params;
+ }
+
+ /**
+ * Retrieve return type
+ *
+ * @return string
+ */
+ public function getReturnType()
+ {
+ return $this->_return->getType();
+ }
+
+ /**
+ * Retrieve the return value object
+ *
+ * @access public
+ * @return Zend_Server_Reflection_ReturnValue
+ */
+ public function getReturnValue()
+ {
+ return $this->_return;
+ }
+
+ /**
+ * Retrieve method parameters
+ *
+ * @return array Array of {@link Zend_Server_Reflection_Parameter}s
+ */
+ public function getParameters()
+ {
+ return $this->_params;
+ }
+}
diff --git a/library/vendor/Zend/Server/Reflection/ReturnValue.php b/library/vendor/Zend/Server/Reflection/ReturnValue.php
new file mode 100644
index 0000000..dcc5132
--- /dev/null
+++ b/library/vendor/Zend/Server/Reflection/ReturnValue.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Return value reflection
+ *
+ * Stores the return value type and description
+ *
+ * @category Zend
+ * @package Zend_Server
+ * @subpackage Reflection
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Server_Reflection_ReturnValue
+{
+ /**
+ * Return value type
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Return value description
+ * @var string
+ */
+ protected $_description;
+
+ /**
+ * Constructor
+ *
+ * @param string $type Return value type
+ * @param string $description Return value type
+ */
+ public function __construct($type = 'mixed', $description = '')
+ {
+ $this->setType($type);
+ $this->setDescription($description);
+ }
+
+ /**
+ * Retrieve parameter type
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Set parameter type
+ *
+ * @param string|null $type
+ * @return void
+ */
+ public function setType($type)
+ {
+ if (!is_string($type) && (null !== $type)) {
+ throw new Zend_Server_Reflection_Exception('Invalid parameter type');
+ }
+
+ $this->_type = $type;
+ }
+
+ /**
+ * Retrieve parameter description
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->_description;
+ }
+
+ /**
+ * Set parameter description
+ *
+ * @param string|null $description
+ * @return void
+ */
+ public function setDescription($description)
+ {
+ if (!is_string($description) && (null !== $description)) {
+ throw new Zend_Server_Reflection_Exception('Invalid parameter description');
+ }
+
+ $this->_description = $description;
+ }
+}
diff --git a/library/vendor/Zend/Session.php b/library/vendor/Zend/Session.php
new file mode 100644
index 0000000..a1ed797
--- /dev/null
+++ b/library/vendor/Zend/Session.php
@@ -0,0 +1,895 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+
+/**
+ * @see Zend_Session_Abstract
+ */
+
+/**
+ * @see Zend_Session_Namespace
+ */
+
+/**
+ * @see Zend_Session_SaveHandler_Interface
+ */
+
+
+/**
+ * Zend_Session
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session extends Zend_Session_Abstract
+{
+ /**
+ * Whether or not Zend_Session is being used with unit tests
+ *
+ * @internal
+ * @var bool
+ */
+ public static $_unitTestEnabled = false;
+
+ /**
+ * $_throwStartupException
+ *
+ * @var bool|bitset This could also be a combiniation of error codes to catch
+ */
+ protected static $_throwStartupExceptions = true;
+
+ /**
+ * Check whether or not the session was started
+ *
+ * @var bool
+ */
+ private static $_sessionStarted = false;
+
+ /**
+ * Whether or not the session id has been regenerated this request.
+ *
+ * Id regeneration state
+ * <0 - regenerate requested when session is started
+ * 0 - do nothing
+ * >0 - already called session_regenerate_id()
+ *
+ * @var int
+ */
+ private static $_regenerateIdState = 0;
+
+ /**
+ * Private list of php's ini values for ext/session
+ * null values will default to the php.ini value, otherwise
+ * the value below will overwrite the default ini value, unless
+ * the user has set an option explicity with setOptions()
+ *
+ * @var array
+ */
+ private static $_defaultOptions = array(
+ 'save_path' => null,
+ 'name' => null, /* this should be set to a unique value for each application */
+ 'save_handler' => null,
+ //'auto_start' => null, /* intentionally excluded (see manual) */
+ 'gc_probability' => null,
+ 'gc_divisor' => null,
+ 'gc_maxlifetime' => null,
+ 'serialize_handler' => null,
+ 'cookie_lifetime' => null,
+ 'cookie_path' => null,
+ 'cookie_domain' => null,
+ 'cookie_secure' => null,
+ 'cookie_httponly' => null,
+ 'use_cookies' => null,
+ 'use_only_cookies' => 'on',
+ 'referer_check' => null,
+ 'entropy_file' => null,
+ 'entropy_length' => null,
+ 'cache_limiter' => null,
+ 'cache_expire' => null,
+ 'use_trans_sid' => null,
+ 'bug_compat_42' => null,
+ 'bug_compat_warn' => null,
+ 'hash_function' => null,
+ 'hash_bits_per_character' => null
+ );
+
+ /**
+ * List of options pertaining to Zend_Session that can be set by developers
+ * using Zend_Session::setOptions(). This list intentionally duplicates
+ * the individual declaration of static "class" variables by the same names.
+ *
+ * @var array
+ */
+ private static $_localOptions = array(
+ 'strict' => '_strict',
+ 'remember_me_seconds' => '_rememberMeSeconds',
+ 'throw_startup_exceptions' => '_throwStartupExceptions'
+ );
+
+ /**
+ * Whether or not write close has been performed.
+ *
+ * @var bool
+ */
+ private static $_writeClosed = false;
+
+ /**
+ * Whether or not session id cookie has been deleted
+ *
+ * @var bool
+ */
+ private static $_sessionCookieDeleted = false;
+
+ /**
+ * Whether or not session has been destroyed via session_destroy()
+ *
+ * @var bool
+ */
+ private static $_destroyed = false;
+
+ /**
+ * Whether or not session must be initiated before usage
+ *
+ * @var bool
+ */
+ private static $_strict = false;
+
+ /**
+ * Default number of seconds the session will be remembered for when asked to be remembered
+ *
+ * @var int
+ */
+ private static $_rememberMeSeconds = 1209600; // 2 weeks
+
+ /**
+ * Whether the default options listed in Zend_Session::$_localOptions have been set
+ *
+ * @var bool
+ */
+ private static $_defaultOptionsSet = false;
+
+ /**
+ * A reference to the set session save handler
+ *
+ * @var Zend_Session_SaveHandler_Interface
+ */
+ private static $_saveHandler = null;
+
+
+ /**
+ * Constructor overriding - make sure that a developer cannot instantiate
+ */
+ protected function __construct()
+ {
+ }
+
+
+ /**
+ * setOptions - set both the class specified
+ *
+ * @param array $userOptions - pass-by-keyword style array of <option name, option value> pairs
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ public static function setOptions(array $userOptions = array())
+ {
+ // set default options on first run only (before applying user settings)
+ if (!self::$_defaultOptionsSet) {
+ foreach (self::$_defaultOptions as $defaultOptionName => $defaultOptionValue) {
+ if (isset(self::$_defaultOptions[$defaultOptionName])) {
+ ini_set("session.$defaultOptionName", $defaultOptionValue);
+ }
+ }
+
+ self::$_defaultOptionsSet = true;
+ }
+
+ // set the options the user has requested to set
+ foreach ($userOptions as $userOptionName => $userOptionValue) {
+
+ $userOptionName = strtolower($userOptionName);
+
+ // set the ini based values
+ if (array_key_exists($userOptionName, self::$_defaultOptions)) {
+ ini_set("session.$userOptionName", $userOptionValue);
+ }
+ elseif (isset(self::$_localOptions[$userOptionName])) {
+ self::${self::$_localOptions[$userOptionName]} = $userOptionValue;
+ }
+ else {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception("Unknown option: $userOptionName = $userOptionValue");
+ }
+ }
+ }
+
+ /**
+ * getOptions()
+ *
+ * @param string $optionName OPTIONAL
+ * @return array|string
+ */
+ public static function getOptions($optionName = null)
+ {
+ $options = array();
+ foreach (ini_get_all('session') as $sysOptionName => $sysOptionValues) {
+ $options[substr($sysOptionName, 8)] = $sysOptionValues['local_value'];
+ }
+ foreach (self::$_localOptions as $localOptionName => $localOptionMemberName) {
+ $options[$localOptionName] = self::${$localOptionMemberName};
+ }
+
+ if ($optionName) {
+ if (array_key_exists($optionName, $options)) {
+ return $options[$optionName];
+ }
+ return null;
+ }
+
+ return $options;
+ }
+
+ /**
+ * setSaveHandler() - Session Save Handler assignment
+ *
+ * @param Zend_Session_SaveHandler_Interface $interface
+ * @throws Zend_Session_Exception When the session_set_save_handler call fails
+ * @return void
+ */
+ public static function setSaveHandler(Zend_Session_SaveHandler_Interface $saveHandler)
+ {
+ self::$_saveHandler = $saveHandler;
+
+ if (self::$_unitTestEnabled) {
+ return;
+ }
+
+ $result = session_set_save_handler(
+ array(&$saveHandler, 'open'),
+ array(&$saveHandler, 'close'),
+ array(&$saveHandler, 'read'),
+ array(&$saveHandler, 'write'),
+ array(&$saveHandler, 'destroy'),
+ array(&$saveHandler, 'gc')
+ );
+
+ if (!$result) {
+ throw new Zend_Session_Exception('Unable to set session handler');
+ }
+ }
+
+
+ /**
+ * getSaveHandler() - Get the session Save Handler
+ *
+ * @return Zend_Session_SaveHandler_Interface
+ */
+ public static function getSaveHandler()
+ {
+ return self::$_saveHandler;
+ }
+
+
+ /**
+ * regenerateId() - Regenerate the session id. Best practice is to call this after
+ * session is started. If called prior to session starting, session id will be regenerated
+ * at start time.
+ *
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ public static function regenerateId()
+ {
+ if (!self::$_unitTestEnabled && headers_sent($filename, $linenum)) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception("You must call " . __CLASS__ . '::' . __FUNCTION__ .
+ "() before any output has been sent to the browser; output started in {$filename}/{$linenum}");
+ }
+
+ if ( !self::$_sessionStarted ) {
+ self::$_regenerateIdState = -1;
+ } else {
+ if (!self::$_unitTestEnabled) {
+ session_regenerate_id(true);
+ }
+ self::$_regenerateIdState = 1;
+ }
+ }
+
+
+ /**
+ * rememberMe() - Write a persistent cookie that expires after a number of seconds in the future. If no number of
+ * seconds is specified, then this defaults to self::$_rememberMeSeconds. Due to clock errors on end users' systems,
+ * large values are recommended to avoid undesirable expiration of session cookies.
+ *
+ * @param int $seconds OPTIONAL specifies TTL for cookie in seconds from present time
+ * @return void
+ */
+ public static function rememberMe($seconds = null)
+ {
+ $seconds = (int) $seconds;
+ $seconds = ($seconds > 0) ? $seconds : self::$_rememberMeSeconds;
+
+ self::rememberUntil($seconds);
+ }
+
+
+ /**
+ * forgetMe() - Write a volatile session cookie, removing any persistent cookie that may have existed. The session
+ * would end upon, for example, termination of a web browser program.
+ *
+ * @return void
+ */
+ public static function forgetMe()
+ {
+ self::rememberUntil(0);
+ }
+
+
+ /**
+ * rememberUntil() - This method does the work of changing the state of the session cookie and making
+ * sure that it gets resent to the browser via regenerateId()
+ *
+ * @param int $seconds
+ * @return void
+ */
+ public static function rememberUntil($seconds = 0)
+ {
+ if (self::$_unitTestEnabled) {
+ self::regenerateId();
+ return;
+ }
+
+ $cookieParams = session_get_cookie_params();
+
+ session_set_cookie_params(
+ $seconds,
+ $cookieParams['path'],
+ $cookieParams['domain'],
+ $cookieParams['secure']
+ );
+
+ // normally "rememberMe()" represents a security context change, so should use new session id
+ self::regenerateId();
+ }
+
+
+ /**
+ * sessionExists() - whether or not a session exists for the current request
+ *
+ * @return bool
+ */
+ public static function sessionExists()
+ {
+ if ((bool)ini_get('session.use_cookies') == true && isset($_COOKIE[session_name()])) {
+ return true;
+ } elseif ((bool)ini_get('session.use_only_cookies') == false && isset($_REQUEST[session_name()])) {
+ return true;
+ } elseif (self::$_unitTestEnabled) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Whether or not session has been destroyed via session_destroy()
+ *
+ * @return bool
+ */
+ public static function isDestroyed()
+ {
+ return self::$_destroyed;
+ }
+
+
+ /**
+ * start() - Start the session.
+ *
+ * @param bool|array $options OPTIONAL Either user supplied options, or flag indicating if start initiated automatically
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ public static function start($options = false)
+ {
+ // Check to see if we've been passed an invalid session ID
+ if ( self::getId() && !self::_checkId(self::getId()) ) {
+ // Generate a valid, temporary replacement
+ self::setId(md5(self::getId()));
+ // Force a regenerate after session is started
+ self::$_regenerateIdState = -1;
+ }
+
+ if (self::$_sessionStarted && self::$_destroyed) {
+ throw new Zend_Session_Exception('The session was explicitly destroyed during this request, attempting to re-start is not allowed.');
+ }
+
+ if (self::$_sessionStarted) {
+ return; // already started
+ }
+
+ // make sure our default options (at the least) have been set
+ if (!self::$_defaultOptionsSet) {
+ self::setOptions(is_array($options) ? $options : array());
+ }
+
+ // In strict mode, do not allow auto-starting Zend_Session, such as via "new Zend_Session_Namespace()"
+ if (self::$_strict && $options === true) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception('You must explicitly start the session with Zend_Session::start() when session options are set to strict.');
+ }
+
+ $filename = $linenum = null;
+ if (!self::$_unitTestEnabled && headers_sent($filename, $linenum)) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception("Session must be started before any output has been sent to the browser;"
+ . " output started in {$filename}/{$linenum}");
+ }
+
+ // See http://www.php.net/manual/en/ref.session.php for explanation
+ if (!self::$_unitTestEnabled && defined('SID')) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception('session has already been started by session.auto-start or session_start()');
+ }
+
+ /**
+ * Hack to throw exceptions on start instead of php errors
+ * @see http://framework.zend.com/issues/browse/ZF-1325
+ */
+
+ $errorLevel = (is_int(self::$_throwStartupExceptions)) ? self::$_throwStartupExceptions : E_ALL;
+
+ /** @see Zend_Session_Exception */
+ if (!self::$_unitTestEnabled) {
+
+ if (self::$_throwStartupExceptions) {
+ set_error_handler(array('Zend_Session_Exception', 'handleSessionStartError'), $errorLevel);
+ }
+
+ $startedCleanly = session_start();
+
+ if (self::$_throwStartupExceptions) {
+ restore_error_handler();
+ }
+
+ if (!$startedCleanly || Zend_Session_Exception::$sessionStartError != null) {
+ if (self::$_throwStartupExceptions) {
+ set_error_handler(array('Zend_Session_Exception', 'handleSilentWriteClose'), $errorLevel);
+ }
+ session_write_close();
+ if (self::$_throwStartupExceptions) {
+ restore_error_handler();
+ throw new Zend_Session_Exception(__CLASS__ . '::' . __FUNCTION__ . '() - ' . Zend_Session_Exception::$sessionStartError);
+ }
+ }
+ }
+
+ parent::$_readable = true;
+ parent::$_writable = true;
+ self::$_sessionStarted = true;
+ if (self::$_regenerateIdState === -1) {
+ self::regenerateId();
+ }
+
+ // run validators if they exist
+ if (isset($_SESSION['__ZF']['VALID'])) {
+ self::_processValidators();
+ }
+
+ self::_processStartupMetadataGlobal();
+ }
+
+ /**
+ * Perform a hash-bits check on the session ID
+ *
+ * @param string $id Session ID
+ * @return bool
+ */
+ protected static function _checkId($id)
+ {
+ $saveHandler = ini_get('session.save_handler');
+ if ($saveHandler == 'cluster') { // Zend Server SC, validate only after last dash
+ $dashPos = strrpos($id, '-');
+ if ($dashPos) {
+ $id = substr($id, $dashPos + 1);
+ }
+ }
+
+ $hashBitsPerChar = ini_get('session.hash_bits_per_character');
+ if (!$hashBitsPerChar) {
+ $hashBitsPerChar = 5; // the default value
+ }
+ switch($hashBitsPerChar) {
+ case 4: $pattern = '^[0-9a-f]*$'; break;
+ case 5: $pattern = '^[0-9a-v]*$'; break;
+ case 6: $pattern = '^[0-9a-zA-Z-,]*$'; break;
+ }
+ return preg_match('#'.$pattern.'#', $id);
+ }
+
+
+ /**
+ * _processGlobalMetadata() - this method initizes the sessions GLOBAL
+ * metadata, mostly global data expiration calculations.
+ *
+ * @return void
+ */
+ private static function _processStartupMetadataGlobal()
+ {
+ // process global metadata
+ if (isset($_SESSION['__ZF'])) {
+
+ // expire globally expired values
+ foreach ($_SESSION['__ZF'] as $namespace => $namespace_metadata) {
+
+ // Expire Namespace by Time (ENT)
+ if (isset($namespace_metadata['ENT']) && ($namespace_metadata['ENT'] > 0) && (time() > $namespace_metadata['ENT']) ) {
+ unset($_SESSION[$namespace]);
+ unset($_SESSION['__ZF'][$namespace]);
+ }
+
+ // Expire Namespace by Global Hop (ENGH) if it wasnt expired above
+ if (isset($_SESSION['__ZF'][$namespace]) && isset($namespace_metadata['ENGH']) && $namespace_metadata['ENGH'] >= 1) {
+
+ $_SESSION['__ZF'][$namespace]['ENGH']--;
+
+ if ($_SESSION['__ZF'][$namespace]['ENGH'] === 0) {
+ if (isset($_SESSION[$namespace])) {
+ parent::$_expiringData[$namespace] = $_SESSION[$namespace];
+ unset($_SESSION[$namespace]);
+ }
+ unset($_SESSION['__ZF'][$namespace]);
+ }
+ }
+
+ // Expire Namespace Variables by Time (ENVT)
+ if (isset($namespace_metadata['ENVT'])) {
+ foreach ($namespace_metadata['ENVT'] as $variable => $time) {
+ if (time() > $time) {
+ unset($_SESSION[$namespace][$variable]);
+ unset($_SESSION['__ZF'][$namespace]['ENVT'][$variable]);
+ }
+ }
+ if (empty($_SESSION['__ZF'][$namespace]['ENVT'])) {
+ unset($_SESSION['__ZF'][$namespace]['ENVT']);
+ }
+ }
+
+ // Expire Namespace Variables by Global Hop (ENVGH)
+ if (isset($namespace_metadata['ENVGH'])) {
+ foreach ($namespace_metadata['ENVGH'] as $variable => $hops) {
+ $_SESSION['__ZF'][$namespace]['ENVGH'][$variable]--;
+
+ if ($_SESSION['__ZF'][$namespace]['ENVGH'][$variable] === 0) {
+ if (isset($_SESSION[$namespace][$variable])) {
+ parent::$_expiringData[$namespace][$variable] = $_SESSION[$namespace][$variable];
+ unset($_SESSION[$namespace][$variable]);
+ }
+ unset($_SESSION['__ZF'][$namespace]['ENVGH'][$variable]);
+ }
+ }
+ if (empty($_SESSION['__ZF'][$namespace]['ENVGH'])) {
+ unset($_SESSION['__ZF'][$namespace]['ENVGH']);
+ }
+ }
+
+ if (isset($namespace) && empty($_SESSION['__ZF'][$namespace])) {
+ unset($_SESSION['__ZF'][$namespace]);
+ }
+ }
+ }
+
+ if (isset($_SESSION['__ZF']) && empty($_SESSION['__ZF'])) {
+ unset($_SESSION['__ZF']);
+ }
+ }
+
+
+ /**
+ * isStarted() - convenience method to determine if the session is already started.
+ *
+ * @return bool
+ */
+ public static function isStarted()
+ {
+ return self::$_sessionStarted;
+ }
+
+
+ /**
+ * isRegenerated() - convenience method to determine if session_regenerate_id()
+ * has been called during this request by Zend_Session.
+ *
+ * @return bool
+ */
+ public static function isRegenerated()
+ {
+ return ( (self::$_regenerateIdState > 0) ? true : false );
+ }
+
+
+ /**
+ * getId() - get the current session id
+ *
+ * @return string
+ */
+ public static function getId()
+ {
+ return session_id();
+ }
+
+
+ /**
+ * setId() - set an id to a user specified id
+ *
+ * @throws Zend_Session_Exception
+ * @param string $id
+ * @return void
+ */
+ public static function setId($id)
+ {
+ if (!self::$_unitTestEnabled && defined('SID')) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception('The session has already been started. The session id must be set first.');
+ }
+
+ if (!self::$_unitTestEnabled && headers_sent($filename, $linenum)) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception("You must call ".__CLASS__.'::'.__FUNCTION__.
+ "() before any output has been sent to the browser; output started in {$filename}/{$linenum}");
+ }
+
+ if (!is_string($id) || $id === '') {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception('You must provide a non-empty string as a session identifier.');
+ }
+
+ session_id($id);
+ }
+
+
+ /**
+ * registerValidator() - register a validator that will attempt to validate this session for
+ * every future request
+ *
+ * @param Zend_Session_Validator_Interface $validator
+ * @return void
+ */
+ public static function registerValidator(Zend_Session_Validator_Interface $validator)
+ {
+ $validator->setup();
+ }
+
+
+ /**
+ * stop() - Disable write access. Optionally disable read (not implemented).
+ *
+ * @return void
+ */
+ public static function stop()
+ {
+ parent::$_writable = false;
+ }
+
+
+ /**
+ * writeClose() - Shutdown the sesssion, close writing and detach $_SESSION from the back-end storage mechanism.
+ * This will complete the internal data transformation on this request.
+ *
+ * @param bool $readonly - OPTIONAL remove write access (i.e. throw error if Zend_Session's attempt writes)
+ * @return void
+ */
+ public static function writeClose($readonly = true)
+ {
+ if (self::$_unitTestEnabled) {
+ return;
+ }
+
+ if (self::$_writeClosed) {
+ return;
+ }
+
+ if ($readonly) {
+ parent::$_writable = false;
+ }
+
+ session_write_close();
+ self::$_writeClosed = true;
+ }
+
+
+ /**
+ * destroy() - This is used to destroy session data, and optionally, the session cookie itself
+ *
+ * @param bool $remove_cookie - OPTIONAL remove session id cookie, defaults to true (remove cookie)
+ * @param bool $readonly - OPTIONAL remove write access (i.e. throw error if Zend_Session's attempt writes)
+ * @return void
+ */
+ public static function destroy($remove_cookie = true, $readonly = true)
+ {
+ if (self::$_unitTestEnabled) {
+ return;
+ }
+
+ if (self::$_destroyed) {
+ return;
+ }
+
+ if ($readonly) {
+ parent::$_writable = false;
+ }
+
+ session_destroy();
+ self::$_destroyed = true;
+
+ if ($remove_cookie) {
+ self::expireSessionCookie();
+ }
+ }
+
+
+ /**
+ * expireSessionCookie() - Sends an expired session id cookie, causing the client to delete the session cookie
+ *
+ * @return void
+ */
+ public static function expireSessionCookie()
+ {
+ if (self::$_unitTestEnabled) {
+ return;
+ }
+
+ if (self::$_sessionCookieDeleted) {
+ return;
+ }
+
+ self::$_sessionCookieDeleted = true;
+
+ if (isset($_COOKIE[session_name()])) {
+ $cookie_params = session_get_cookie_params();
+
+ setcookie(
+ session_name(),
+ false,
+ 315554400, // strtotime('1980-01-01'),
+ $cookie_params['path'],
+ $cookie_params['domain'],
+ $cookie_params['secure']
+ );
+ }
+ }
+
+
+ /**
+ * _processValidator() - internal function that is called in the existence of VALID metadata
+ *
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ private static function _processValidators()
+ {
+ foreach ($_SESSION['__ZF']['VALID'] as $validator_name => $valid_data) {
+ if (!class_exists($validator_name)) {
+ Zend_Loader::loadClass($validator_name);
+ }
+ $validator = new $validator_name;
+ if ($validator->validate() === false) {
+ /** @see Zend_Session_Validator_Exception */
+ throw new Zend_Session_Validator_Exception("This session is not valid according to {$validator_name}.");
+ }
+ }
+ }
+
+
+ /**
+ * namespaceIsset() - check to see if a namespace is set
+ *
+ * @param string $namespace
+ * @return bool
+ */
+ public static function namespaceIsset($namespace)
+ {
+ return parent::_namespaceIsset($namespace);
+ }
+
+
+ /**
+ * namespaceUnset() - unset a namespace or a variable within a namespace
+ *
+ * @param string $namespace
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ public static function namespaceUnset($namespace)
+ {
+ parent::_namespaceUnset($namespace);
+ Zend_Session_Namespace::resetSingleInstance($namespace);
+ }
+
+
+ /**
+ * namespaceGet() - get all variables in a namespace
+ * Deprecated: Use getIterator() in Zend_Session_Namespace.
+ *
+ * @param string $namespace
+ * @return array
+ */
+ public static function namespaceGet($namespace)
+ {
+ return parent::_namespaceGetAll($namespace);
+ }
+
+
+ /**
+ * getIterator() - return an iteratable object for use in foreach and the like,
+ * this completes the IteratorAggregate interface
+ *
+ * @throws Zend_Session_Exception
+ * @return ArrayObject
+ */
+ public static function getIterator()
+ {
+ if (parent::$_readable === false) {
+ /** @see Zend_Session_Exception */
+ throw new Zend_Session_Exception(parent::_THROW_NOT_READABLE_MSG);
+ }
+
+ $spaces = array();
+ if (isset($_SESSION)) {
+ $spaces = array_keys($_SESSION);
+ foreach($spaces as $key => $space) {
+ if (!strncmp($space, '__', 2) || !is_array($_SESSION[$space])) {
+ unset($spaces[$key]);
+ }
+ }
+ }
+
+ return new ArrayObject(array_merge($spaces, array_keys(parent::$_expiringData)));
+ }
+
+
+ /**
+ * isWritable() - returns a boolean indicating if namespaces can write (use setters)
+ *
+ * @return bool
+ */
+ public static function isWritable()
+ {
+ return parent::$_writable;
+ }
+
+
+ /**
+ * isReadable() - returns a boolean indicating if namespaces can write (use setters)
+ *
+ * @return bool
+ */
+ public static function isReadable()
+ {
+ return parent::$_readable;
+ }
+
+}
diff --git a/library/vendor/Zend/Session/Abstract.php b/library/vendor/Zend/Session/Abstract.php
new file mode 100644
index 0000000..15ffde0
--- /dev/null
+++ b/library/vendor/Zend/Session/Abstract.php
@@ -0,0 +1,182 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+
+/**
+ * Zend_Session_Abstract
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Session_Abstract
+{
+ /**
+ * Whether or not session permits writing (modification of $_SESSION[])
+ *
+ * @var bool
+ */
+ protected static $_writable = false;
+
+ /**
+ * Whether or not session permits reading (reading data in $_SESSION[])
+ *
+ * @var bool
+ */
+ protected static $_readable = false;
+
+ /**
+ * Since expiring data is handled at startup to avoid __destruct difficulties,
+ * the data that will be expiring at end of this request is held here
+ *
+ * @var array
+ */
+ protected static $_expiringData = array();
+
+
+ /**
+ * Error message thrown when an action requires modification,
+ * but current Zend_Session has been marked as read-only.
+ */
+ const _THROW_NOT_WRITABLE_MSG = 'Zend_Session is currently marked as read-only.';
+
+
+ /**
+ * Error message thrown when an action requires reading session data,
+ * but current Zend_Session is not marked as readable.
+ */
+ const _THROW_NOT_READABLE_MSG = 'Zend_Session is not marked as readable.';
+
+
+ /**
+ * namespaceIsset() - check to see if a namespace or a variable within a namespace is set
+ *
+ * @param string $namespace
+ * @param string $name
+ * @return bool
+ */
+ protected static function _namespaceIsset($namespace, $name = null)
+ {
+ if (self::$_readable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(self::_THROW_NOT_READABLE_MSG);
+ }
+
+ if ($name === null) {
+ return ( isset($_SESSION[$namespace]) || isset(self::$_expiringData[$namespace]) );
+ } else {
+ return ( isset($_SESSION[$namespace][$name]) || isset(self::$_expiringData[$namespace][$name]) );
+ }
+ }
+
+
+ /**
+ * namespaceUnset() - unset a namespace or a variable within a namespace
+ *
+ * @param string $namespace
+ * @param string $name
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ protected static function _namespaceUnset($namespace, $name = null)
+ {
+ if (self::$_writable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(self::_THROW_NOT_WRITABLE_MSG);
+ }
+
+ $name = (string) $name;
+
+ // check to see if the api wanted to remove a var from a namespace or a namespace
+ if ($name === '') {
+ unset($_SESSION[$namespace]);
+ unset(self::$_expiringData[$namespace]);
+ } else {
+ unset($_SESSION[$namespace][$name]);
+ unset(self::$_expiringData[$namespace][$name]);
+ }
+
+ // if we remove the last value, remove namespace.
+ if (empty($_SESSION[$namespace])) {
+ unset($_SESSION[$namespace]);
+ }
+ }
+
+
+ /**
+ * namespaceGet() - Get $name variable from $namespace, returning by reference.
+ *
+ * @param string $namespace
+ * @param string $name
+ * @return mixed
+ */
+ protected static function & _namespaceGet($namespace, $name = null)
+ {
+ if (self::$_readable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(self::_THROW_NOT_READABLE_MSG);
+ }
+
+ if ($name === null) {
+ if (isset($_SESSION[$namespace])) { // check session first for data requested
+ return $_SESSION[$namespace];
+ } elseif (isset(self::$_expiringData[$namespace])) { // check expiring data for data reqeusted
+ return self::$_expiringData[$namespace];
+ } else {
+ return $_SESSION[$namespace]; // satisfy return by reference
+ }
+ } else {
+ if (isset($_SESSION[$namespace][$name])) { // check session first
+ return $_SESSION[$namespace][$name];
+ } elseif (isset(self::$_expiringData[$namespace][$name])) { // check expiring data
+ return self::$_expiringData[$namespace][$name];
+ } else {
+ return $_SESSION[$namespace][$name]; // satisfy return by reference
+ }
+ }
+ }
+
+
+ /**
+ * namespaceGetAll() - Get an array containing $namespace, including expiring data.
+ *
+ * @param string $namespace
+ * @param string $name
+ * @return mixed
+ */
+ protected static function _namespaceGetAll($namespace)
+ {
+ $currentData = (isset($_SESSION[$namespace]) && is_array($_SESSION[$namespace])) ?
+ $_SESSION[$namespace] : array();
+ $expiringData = (isset(self::$_expiringData[$namespace]) && is_array(self::$_expiringData[$namespace])) ?
+ self::$_expiringData[$namespace] : array();
+ return array_merge($currentData, $expiringData);
+ }
+}
diff --git a/library/vendor/Zend/Session/Exception.php b/library/vendor/Zend/Session/Exception.php
new file mode 100644
index 0000000..52672ec
--- /dev/null
+++ b/library/vendor/Zend/Session/Exception.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+
+/**
+ * @see Zend_Exception
+ */
+
+
+/**
+ * Zend_Session_Exception
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session_Exception extends Zend_Exception
+{
+ /**
+ * sessionStartError
+ *
+ * @see http://framework.zend.com/issues/browse/ZF-1325
+ * @var string PHP Error Message
+ */
+ static public $sessionStartError = null;
+
+ /**
+ * handleSessionStartError() - interface for set_error_handler()
+ *
+ * @see http://framework.zend.com/issues/browse/ZF-1325
+ * @param int $errno
+ * @param string $errstr
+ * @return void
+ */
+ static public function handleSessionStartError($errno, $errstr, $errfile, $errline, $errcontext)
+ {
+ self::$sessionStartError = $errfile . '(Line:' . $errline . '): Error #' . $errno . ' ' . $errstr;
+ }
+
+ /**
+ * handleSilentWriteClose() - interface for set_error_handler()
+ *
+ * @see http://framework.zend.com/issues/browse/ZF-1325
+ * @param int $errno
+ * @param string $errstr
+ * @return void
+ */
+ static public function handleSilentWriteClose($errno, $errstr, $errfile, $errline, $errcontext)
+ {
+ self::$sessionStartError .= PHP_EOL . $errfile . '(Line:' . $errline . '): Error #' . $errno . ' ' . $errstr;
+ }
+}
+
diff --git a/library/vendor/Zend/Session/Namespace.php b/library/vendor/Zend/Session/Namespace.php
new file mode 100644
index 0000000..52d7945
--- /dev/null
+++ b/library/vendor/Zend/Session/Namespace.php
@@ -0,0 +1,511 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+
+/**
+ * @see Zend_Session
+ */
+
+
+/**
+ * @see Zend_Session_Abstract
+ */
+
+
+/**
+ * Zend_Session_Namespace
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session_Namespace extends Zend_Session_Abstract implements IteratorAggregate
+{
+
+ /**
+ * used as option to constructor to prevent additional instances to the same namespace
+ */
+ const SINGLE_INSTANCE = true;
+
+ /**
+ * Namespace - which namespace this instance of zend-session is saving-to/getting-from
+ *
+ * @var string
+ */
+ protected $_namespace = "Default";
+
+ /**
+ * Namespace locking mechanism
+ *
+ * @var array
+ */
+ protected static $_namespaceLocks = array();
+
+ /**
+ * Single instance namespace array to ensure data security.
+ *
+ * @var array
+ */
+ protected static $_singleInstances = array();
+
+ /**
+ * resetSingleInstance()
+ *
+ * @param string $namespaceName
+ * @return null
+ */
+ public static function resetSingleInstance($namespaceName = null)
+ {
+ if ($namespaceName != null) {
+ if (array_key_exists($namespaceName, self::$_singleInstances)) {
+ unset(self::$_singleInstances[$namespaceName]);
+ }
+ return;
+ }
+
+ self::$_singleInstances = array();
+ return;
+ }
+
+ /**
+ * __construct() - Returns an instance object bound to a particular, isolated section
+ * of the session, identified by $namespace name (defaulting to 'Default').
+ * The optional argument $singleInstance will prevent construction of additional
+ * instance objects acting as accessors to this $namespace.
+ *
+ * @param string $namespace - programmatic name of the requested namespace
+ * @param bool $singleInstance - prevent creation of additional accessor instance objects for this namespace
+ * @return void
+ */
+ public function __construct($namespace = 'Default', $singleInstance = false)
+ {
+ if ($namespace === '') {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('Session namespace must be a non-empty string.');
+ }
+
+ if ($namespace[0] == "_") {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('Session namespace must not start with an underscore.');
+ }
+
+ if (preg_match('#(^[0-9])#i', $namespace[0])) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('Session namespace must not start with a number.');
+ }
+
+ if (isset(self::$_singleInstances[$namespace])) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception("A session namespace object already exists for this namespace ('$namespace'), and no additional accessors (session namespace objects) for this namespace are permitted.");
+ }
+
+ if ($singleInstance === true) {
+ self::$_singleInstances[$namespace] = true;
+ }
+
+ $this->_namespace = $namespace;
+
+ // Process metadata specific only to this namespace.
+ Zend_Session::start(true); // attempt auto-start (throws exception if strict option set)
+
+ if (self::$_readable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(self::_THROW_NOT_READABLE_MSG);
+ }
+
+ if (!isset($_SESSION['__ZF'])) {
+ return; // no further processing needed
+ }
+
+ // do not allow write access to namespaces, after stop() or writeClose()
+ if (parent::$_writable === true) {
+ if (isset($_SESSION['__ZF'][$namespace])) {
+
+ // Expire Namespace by Namespace Hop (ENNH)
+ if (isset($_SESSION['__ZF'][$namespace]['ENNH'])) {
+ $_SESSION['__ZF'][$namespace]['ENNH']--;
+
+ if ($_SESSION['__ZF'][$namespace]['ENNH'] === 0) {
+ if (isset($_SESSION[$namespace])) {
+ self::$_expiringData[$namespace] = $_SESSION[$namespace];
+ unset($_SESSION[$namespace]);
+ }
+ unset($_SESSION['__ZF'][$namespace]);
+ }
+ }
+
+ // Expire Namespace Variables by Namespace Hop (ENVNH)
+ if (isset($_SESSION['__ZF'][$namespace]['ENVNH'])) {
+ foreach ($_SESSION['__ZF'][$namespace]['ENVNH'] as $variable => $hops) {
+ $_SESSION['__ZF'][$namespace]['ENVNH'][$variable]--;
+
+ if ($_SESSION['__ZF'][$namespace]['ENVNH'][$variable] === 0) {
+ if (isset($_SESSION[$namespace][$variable])) {
+ self::$_expiringData[$namespace][$variable] = $_SESSION[$namespace][$variable];
+ unset($_SESSION[$namespace][$variable]);
+ }
+ unset($_SESSION['__ZF'][$namespace]['ENVNH'][$variable]);
+ }
+ }
+ if(empty($_SESSION['__ZF'][$namespace]['ENVNH'])) {
+ unset($_SESSION['__ZF'][$namespace]['ENVNH']);
+ }
+ }
+ }
+
+ if (empty($_SESSION['__ZF'][$namespace])) {
+ unset($_SESSION['__ZF'][$namespace]);
+ }
+
+ if (empty($_SESSION['__ZF'])) {
+ unset($_SESSION['__ZF']);
+ }
+ }
+ }
+
+
+ /**
+ * getIterator() - return an iteratable object for use in foreach and the like,
+ * this completes the IteratorAggregate interface
+ *
+ * @return ArrayObject - iteratable container of the namespace contents
+ */
+ public function getIterator()
+ {
+ return new ArrayObject(parent::_namespaceGetAll($this->_namespace));
+ }
+
+
+ /**
+ * lock() - mark a session/namespace as readonly
+ *
+ * @return void
+ */
+ public function lock()
+ {
+ self::$_namespaceLocks[$this->_namespace] = true;
+ }
+
+
+ /**
+ * unlock() - unmark a session/namespace to enable read & write
+ *
+ * @return void
+ */
+ public function unlock()
+ {
+ unset(self::$_namespaceLocks[$this->_namespace]);
+ }
+
+
+ /**
+ * unlockAll() - unmark all session/namespaces to enable read & write
+ *
+ * @return void
+ */
+ public static function unlockAll()
+ {
+ self::$_namespaceLocks = array();
+ }
+
+
+ /**
+ * isLocked() - return lock status, true if, and only if, read-only
+ *
+ * @return bool
+ */
+ public function isLocked()
+ {
+ return isset(self::$_namespaceLocks[$this->_namespace]);
+ }
+
+
+ /**
+ * unsetAll() - unset all variables in this namespace
+ *
+ * @return true
+ */
+ public function unsetAll()
+ {
+ return parent::_namespaceUnset($this->_namespace);
+ }
+
+
+ /**
+ * __get() - method to get a variable in this object's current namespace
+ *
+ * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
+ * @return mixed
+ */
+ public function & __get($name)
+ {
+ if ($name === '') {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
+ }
+
+ return parent::_namespaceGet($this->_namespace, $name);
+ }
+
+
+ /**
+ * __set() - method to set a variable/value in this object's namespace
+ *
+ * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
+ * @param mixed $value - value in the <key,value> pair to assign to the $name key
+ * @throws Zend_Session_Exception
+ * @return true
+ */
+ public function __set($name, $value)
+ {
+ if (isset(self::$_namespaceLocks[$this->_namespace])) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('This session/namespace has been marked as read-only.');
+ }
+
+ if ($name === '') {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
+ }
+
+ if (parent::$_writable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(parent::_THROW_NOT_WRITABLE_MSG);
+ }
+
+ $name = (string) $name;
+
+ $_SESSION[$this->_namespace][$name] = $value;
+ }
+
+
+ /**
+ * apply() - enables applying user-selected function, such as array_merge() to the namespace
+ * Parameters following the $callback argument are passed to the callback function.
+ * Caveat: ignores members expiring now.
+ *
+ * Example:
+ * $namespace->apply('array_merge', array('tree' => 'apple', 'fruit' => 'peach'), array('flower' => 'rose'));
+ * $namespace->apply('count');
+ *
+ * @param string|array $callback - callback function
+ */
+ public function apply($callback)
+ {
+ $arg_list = func_get_args();
+ $arg_list[0] = $_SESSION[$this->_namespace];
+ return call_user_func_array($callback, $arg_list);
+ }
+
+
+ /**
+ * applySet() - enables applying user-selected function, and sets entire namespace to the result
+ * Result of $callback must be an array.
+ * Parameters following the $callback argument are passed to the callback function.
+ * Caveat: ignores members expiring now.
+ *
+ * Example:
+ * $namespace->applySet('array_merge', array('tree' => 'apple', 'fruit' => 'peach'), array('flower' => 'rose'));
+ *
+ * @param string|array $callback - callback function
+ */
+ public function applySet($callback)
+ {
+ $arg_list = func_get_args();
+ $arg_list[0] = $_SESSION[$this->_namespace];
+ $result = call_user_func_array($callback, $arg_list);
+ if (!is_array($result)) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('Result must be an array. Got: ' . gettype($result));
+ }
+ $_SESSION[$this->_namespace] = $result;
+ return $result;
+ }
+
+
+ /**
+ * __isset() - determine if a variable in this object's namespace is set
+ *
+ * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ if ($name === '') {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
+ }
+
+ return parent::_namespaceIsset($this->_namespace, $name);
+ }
+
+
+ /**
+ * __unset() - unset a variable in this object's namespace.
+ *
+ * @param string $name - programmatic name of a key, in a <key,value> pair in the current namespace
+ * @return true
+ */
+ public function __unset($name)
+ {
+ if ($name === '') {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception("The '$name' key must be a non-empty string");
+ }
+
+ return parent::_namespaceUnset($this->_namespace, $name);
+ }
+
+
+ /**
+ * setExpirationSeconds() - expire the namespace, or specific variables after a specified
+ * number of seconds
+ *
+ * @param int $seconds - expires in this many seconds
+ * @param mixed $variables - OPTIONAL list of variables to expire (defaults to all)
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ public function setExpirationSeconds($seconds, $variables = null)
+ {
+ if (parent::$_writable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(parent::_THROW_NOT_WRITABLE_MSG);
+ }
+
+ if ($seconds <= 0) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('Seconds must be positive.');
+ }
+
+ if ($variables === null) {
+
+ // apply expiration to entire namespace
+ $_SESSION['__ZF'][$this->_namespace]['ENT'] = time() + $seconds;
+
+ } else {
+
+ if (is_string($variables)) {
+ $variables = array($variables);
+ }
+
+ foreach ($variables as $variable) {
+ if (!empty($variable)) {
+ $_SESSION['__ZF'][$this->_namespace]['ENVT'][$variable] = time() + $seconds;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * setExpirationHops() - expire the namespace, or specific variables after a specified
+ * number of page hops
+ *
+ * @param int $hops - how many "hops" (number of subsequent requests) before expiring
+ * @param mixed $variables - OPTIONAL list of variables to expire (defaults to all)
+ * @param boolean $hopCountOnUsageOnly - OPTIONAL if set, only count a hop/request if this namespace is used
+ * @throws Zend_Session_Exception
+ * @return void
+ */
+ public function setExpirationHops($hops, $variables = null, $hopCountOnUsageOnly = false)
+ {
+ if (parent::$_writable === false) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception(parent::_THROW_NOT_WRITABLE_MSG);
+ }
+
+ if ($hops <= 0) {
+ /**
+ * @see Zend_Session_Exception
+ */
+ throw new Zend_Session_Exception('Hops must be positive number.');
+ }
+
+ if ($variables === null) {
+
+ // apply expiration to entire namespace
+ if ($hopCountOnUsageOnly === false) {
+ $_SESSION['__ZF'][$this->_namespace]['ENGH'] = $hops;
+ } else {
+ $_SESSION['__ZF'][$this->_namespace]['ENNH'] = $hops;
+ }
+
+ } else {
+
+ if (is_string($variables)) {
+ $variables = array($variables);
+ }
+
+ foreach ($variables as $variable) {
+ if (!empty($variable)) {
+ if ($hopCountOnUsageOnly === false) {
+ $_SESSION['__ZF'][$this->_namespace]['ENVGH'][$variable] = $hops;
+ } else {
+ $_SESSION['__ZF'][$this->_namespace]['ENVNH'][$variable] = $hops;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the namespace name
+ *
+ * @return string
+ */
+ public function getNamespace()
+ {
+ return $this->_namespace;
+ }
+}
diff --git a/library/vendor/Zend/Session/SaveHandler/DbTable.php b/library/vendor/Zend/Session/SaveHandler/DbTable.php
new file mode 100644
index 0000000..a4b1bf7
--- /dev/null
+++ b/library/vendor/Zend/Session/SaveHandler/DbTable.php
@@ -0,0 +1,579 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-webat this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Session
+ */
+
+/**
+ * @see Zend_Db_Table_Abstract
+ */
+
+/**
+ * @see Zend_Db_Table_Row_Abstract
+ */
+
+/**
+ * @see Zend_Config
+ */
+
+/**
+ * Zend_Session_SaveHandler_DbTable
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @subpackage SaveHandler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session_SaveHandler_DbTable
+ extends Zend_Db_Table_Abstract
+ implements Zend_Session_SaveHandler_Interface
+{
+ const PRIMARY_ASSIGNMENT = 'primaryAssignment';
+ const PRIMARY_ASSIGNMENT_SESSION_SAVE_PATH = 'sessionSavePath';
+ const PRIMARY_ASSIGNMENT_SESSION_NAME = 'sessionName';
+ const PRIMARY_ASSIGNMENT_SESSION_ID = 'sessionId';
+
+ const MODIFIED_COLUMN = 'modifiedColumn';
+ const LIFETIME_COLUMN = 'lifetimeColumn';
+ const DATA_COLUMN = 'dataColumn';
+
+ const LIFETIME = 'lifetime';
+ const OVERRIDE_LIFETIME = 'overrideLifetime';
+
+ const PRIMARY_TYPE_NUM = 'PRIMARY_TYPE_NUM';
+ const PRIMARY_TYPE_PRIMARYNUM = 'PRIMARY_TYPE_PRIMARYNUM';
+ const PRIMARY_TYPE_ASSOC = 'PRIMARY_TYPE_ASSOC';
+ const PRIMARY_TYPE_WHERECLAUSE = 'PRIMARY_TYPE_WHERECLAUSE';
+
+ /**
+ * Session table primary key value assignment
+ *
+ * @var array
+ */
+ protected $_primaryAssignment = null;
+
+ /**
+ * Session table last modification time column
+ *
+ * @var string
+ */
+ protected $_modifiedColumn = null;
+
+ /**
+ * Session table lifetime column
+ *
+ * @var string
+ */
+ protected $_lifetimeColumn = null;
+
+ /**
+ * Session table data column
+ *
+ * @var string
+ */
+ protected $_dataColumn = null;
+
+ /**
+ * Session lifetime
+ *
+ * @var int
+ */
+ protected $_lifetime = false;
+
+ /**
+ * Whether or not the lifetime of an existing session should be overridden
+ *
+ * @var boolean
+ */
+ protected $_overrideLifetime = false;
+
+ /**
+ * Session save path
+ *
+ * @var string
+ */
+ protected $_sessionSavePath;
+
+ /**
+ * Session name
+ *
+ * @var string
+ */
+ protected $_sessionName;
+
+ /**
+ * Constructor
+ *
+ * $config is an instance of Zend_Config or an array of key/value pairs containing configuration options for
+ * Zend_Session_SaveHandler_DbTable and Zend_Db_Table_Abstract. These are the configuration options for
+ * Zend_Session_SaveHandler_DbTable:
+ *
+ * primaryAssignment => (string|array) Session table primary key value assignment
+ * (optional; default: 1 => sessionId) You have to assign a value to each primary key of your session table.
+ * The value of this configuration option is either a string if you have only one primary key or an array if
+ * you have multiple primary keys. The array consists of numeric keys starting at 1 and string values. There
+ * are some values which will be replaced by session information:
+ *
+ * sessionId => The id of the current session
+ * sessionName => The name of the current session
+ * sessionSavePath => The save path of the current session
+ *
+ * NOTE: One of your assignments MUST contain 'sessionId' as value!
+ *
+ * modifiedColumn => (string) Session table last modification time column
+ *
+ * lifetimeColumn => (string) Session table lifetime column
+ *
+ * dataColumn => (string) Session table data column
+ *
+ * lifetime => (integer) Session lifetime (optional; default: ini_get('session.gc_maxlifetime'))
+ *
+ * overrideLifetime => (boolean) Whether or not the lifetime of an existing session should be overridden
+ * (optional; default: false)
+ *
+ * @param Zend_Config|array $config User-provided configuration
+ * @return void
+ * @throws Zend_Session_SaveHandler_Exception
+ */
+ public function __construct($config)
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ } else if (!is_array($config)) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception(
+ '$config must be an instance of Zend_Config or array of key/value pairs containing '
+ . 'configuration options for Zend_Session_SaveHandler_DbTable and Zend_Db_Table_Abstract.');
+ }
+
+ foreach ($config as $key => $value) {
+ do {
+ switch ($key) {
+ case self::PRIMARY_ASSIGNMENT:
+ $this->_primaryAssignment = $value;
+ break;
+ case self::MODIFIED_COLUMN:
+ $this->_modifiedColumn = (string) $value;
+ break;
+ case self::LIFETIME_COLUMN:
+ $this->_lifetimeColumn = (string) $value;
+ break;
+ case self::DATA_COLUMN:
+ $this->_dataColumn = (string) $value;
+ break;
+ case self::LIFETIME:
+ $this->setLifetime($value);
+ break;
+ case self::OVERRIDE_LIFETIME:
+ $this->setOverrideLifetime($value);
+ break;
+ default:
+ // unrecognized options passed to parent::__construct()
+ break 2;
+ }
+ unset($config[$key]);
+ } while (false);
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Destructor
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ Zend_Session::writeClose();
+ }
+
+ /**
+ * Set session lifetime and optional whether or not the lifetime of an existing session should be overridden
+ *
+ * $lifetime === false resets lifetime to session.gc_maxlifetime
+ *
+ * @param int $lifetime
+ * @param boolean $overrideLifetime (optional)
+ * @return Zend_Session_SaveHandler_DbTable
+ */
+ public function setLifetime($lifetime, $overrideLifetime = null)
+ {
+ if ($lifetime < 0) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+ throw new Zend_Session_SaveHandler_Exception();
+ } else if (empty($lifetime)) {
+ $this->_lifetime = (int) ini_get('session.gc_maxlifetime');
+ } else {
+ $this->_lifetime = (int) $lifetime;
+ }
+
+ if ($overrideLifetime != null) {
+ $this->setOverrideLifetime($overrideLifetime);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve session lifetime
+ *
+ * @return int
+ */
+ public function getLifetime()
+ {
+ return $this->_lifetime;
+ }
+
+ /**
+ * Set whether or not the lifetime of an existing session should be overridden
+ *
+ * @param boolean $overrideLifetime
+ * @return Zend_Session_SaveHandler_DbTable
+ */
+ public function setOverrideLifetime($overrideLifetime)
+ {
+ $this->_overrideLifetime = (boolean) $overrideLifetime;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve whether or not the lifetime of an existing session should be overridden
+ *
+ * @return boolean
+ */
+ public function getOverrideLifetime()
+ {
+ return $this->_overrideLifetime;
+ }
+
+ /**
+ * Open Session
+ *
+ * @param string $save_path
+ * @param string $name
+ * @return boolean
+ */
+ public function open($save_path, $name)
+ {
+ $this->_sessionSavePath = $save_path;
+ $this->_sessionName = $name;
+
+ return true;
+ }
+
+ /**
+ * Close session
+ *
+ * @return boolean
+ */
+ public function close()
+ {
+ return true;
+ }
+
+ /**
+ * Read session data
+ *
+ * @param string $id
+ * @return string
+ */
+ public function read($id)
+ {
+ $return = '';
+
+ $rows = call_user_func_array(array(&$this, 'find'), $this->_getPrimary($id));
+
+ if (count($rows)) {
+ if ($this->_getExpirationTime($row = $rows->current()) > time()) {
+ $return = $row->{$this->_dataColumn};
+ } else {
+ $this->destroy($id);
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Write session data
+ *
+ * @param string $id
+ * @param string $data
+ * @return boolean
+ */
+ public function write($id, $data)
+ {
+ $return = false;
+
+ $data = array($this->_modifiedColumn => time(),
+ $this->_dataColumn => (string) $data);
+
+ $rows = call_user_func_array(array(&$this, 'find'), $this->_getPrimary($id));
+
+ if (count($rows)) {
+ $data[$this->_lifetimeColumn] = $this->_getLifetime($rows->current());
+
+ if ($this->update($data, $this->_getPrimary($id, self::PRIMARY_TYPE_WHERECLAUSE))) {
+ $return = true;
+ }
+ } else {
+ $data[$this->_lifetimeColumn] = $this->_lifetime;
+
+ if ($this->insert(array_merge($this->_getPrimary($id, self::PRIMARY_TYPE_ASSOC), $data))) {
+ $return = true;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Destroy session
+ *
+ * @param string $id
+ * @return boolean
+ */
+ public function destroy($id)
+ {
+ $return = false;
+
+ if ($this->delete($this->_getPrimary($id, self::PRIMARY_TYPE_WHERECLAUSE))) {
+ $return = true;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Garbage Collection
+ *
+ * @param int $maxlifetime
+ * @return true
+ */
+ public function gc($maxlifetime)
+ {
+ $this->delete($this->getAdapter()->quoteIdentifier($this->_modifiedColumn, true) . ' + '
+ . $this->getAdapter()->quoteIdentifier($this->_lifetimeColumn, true) . ' < '
+ . $this->getAdapter()->quote(time()));
+
+ return true;
+ }
+
+ /**
+ * Calls other protected methods for individual setup tasks and requirement checks
+ *
+ * @return void
+ */
+ protected function _setup()
+ {
+ parent::_setup();
+
+ $this->_setupPrimaryAssignment();
+ $this->setLifetime($this->_lifetime);
+
+ $this->_checkRequiredColumns();
+ }
+
+ /**
+ * Initialize table and schema names
+ *
+ * @return void
+ * @throws Zend_Session_SaveHandler_Exception
+ */
+ protected function _setupTableName()
+ {
+ if (empty($this->_name) && basename(($this->_name = session_save_path())) != $this->_name) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception('session.save_path is a path and not a table name.');
+ }
+
+ if (strpos($this->_name, '.')) {
+ list($this->_schema, $this->_name) = explode('.', $this->_name);
+ }
+ }
+
+ /**
+ * Initialize session table primary key value assignment
+ *
+ * @return void
+ * @throws Zend_Session_SaveHandler_Exception
+ */
+ protected function _setupPrimaryAssignment()
+ {
+ if ($this->_primaryAssignment === null) {
+ $this->_primaryAssignment = array(1 => self::PRIMARY_ASSIGNMENT_SESSION_ID);
+ } else if (!is_array($this->_primaryAssignment)) {
+ $this->_primaryAssignment = array(1 => (string) $this->_primaryAssignment);
+ } else if (isset($this->_primaryAssignment[0])) {
+ array_unshift($this->_primaryAssignment, null);
+
+ unset($this->_primaryAssignment[0]);
+ }
+
+ if (count($this->_primaryAssignment) !== count($this->_primary)) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception(
+ "Value for configuration option '" . self::PRIMARY_ASSIGNMENT . "' must have an assignment "
+ . "for each session table primary key.");
+ } else if (!in_array(self::PRIMARY_ASSIGNMENT_SESSION_ID, $this->_primaryAssignment)) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception(
+ "Value for configuration option '" . self::PRIMARY_ASSIGNMENT . "' must have an assignment "
+ . "for the session id ('" . self::PRIMARY_ASSIGNMENT_SESSION_ID . "').");
+ }
+ }
+
+ /**
+ * Check for required session table columns
+ *
+ * @return void
+ * @throws Zend_Session_SaveHandler_Exception
+ */
+ protected function _checkRequiredColumns()
+ {
+ if ($this->_modifiedColumn === null) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception(
+ "Configuration must define '" . self::MODIFIED_COLUMN . "' which names the "
+ . "session table last modification time column.");
+ } else if ($this->_lifetimeColumn === null) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception(
+ "Configuration must define '" . self::LIFETIME_COLUMN . "' which names the "
+ . "session table lifetime column.");
+ } else if ($this->_dataColumn === null) {
+ /**
+ * @see Zend_Session_SaveHandler_Exception
+ */
+
+ throw new Zend_Session_SaveHandler_Exception(
+ "Configuration must define '" . self::DATA_COLUMN . "' which names the "
+ . "session table data column.");
+ }
+ }
+
+ /**
+ * Retrieve session table primary key values
+ *
+ * @param string $id
+ * @param string $type (optional; default: self::PRIMARY_TYPE_NUM)
+ * @return array
+ */
+ protected function _getPrimary($id, $type = null)
+ {
+ $this->_setupPrimaryKey();
+
+ if ($type === null) {
+ $type = self::PRIMARY_TYPE_NUM;
+ }
+
+ $primaryArray = array();
+
+ foreach ($this->_primary as $index => $primary) {
+ switch ($this->_primaryAssignment[$index]) {
+ case self::PRIMARY_ASSIGNMENT_SESSION_SAVE_PATH:
+ $value = $this->_sessionSavePath;
+ break;
+ case self::PRIMARY_ASSIGNMENT_SESSION_NAME:
+ $value = $this->_sessionName;
+ break;
+ case self::PRIMARY_ASSIGNMENT_SESSION_ID:
+ $value = (string) $id;
+ break;
+ default:
+ $value = (string) $this->_primaryAssignment[$index];
+ break;
+ }
+
+ switch ((string) $type) {
+ case self::PRIMARY_TYPE_PRIMARYNUM:
+ $primaryArray[$index] = $value;
+ break;
+ case self::PRIMARY_TYPE_ASSOC:
+ $primaryArray[$primary] = $value;
+ break;
+ case self::PRIMARY_TYPE_WHERECLAUSE:
+ $primaryArray[] = $this->getAdapter()->quoteIdentifier($primary, true) . ' = '
+ . $this->getAdapter()->quote($value);
+ break;
+ case self::PRIMARY_TYPE_NUM:
+ default:
+ $primaryArray[] = $value;
+ break;
+ }
+ }
+
+ return $primaryArray;
+ }
+
+ /**
+ * Retrieve session lifetime considering Zend_Session_SaveHandler_DbTable::OVERRIDE_LIFETIME
+ *
+ * @param Zend_Db_Table_Row_Abstract $row
+ * @return int
+ */
+ protected function _getLifetime(Zend_Db_Table_Row_Abstract $row)
+ {
+ $return = $this->_lifetime;
+
+ if (!$this->_overrideLifetime) {
+ $return = (int) $row->{$this->_lifetimeColumn};
+ }
+
+ return $return;
+ }
+
+ /**
+ * Retrieve session expiration time
+ *
+ * @param Zend_Db_Table_Row_Abstract $row
+ * @return int
+ */
+ protected function _getExpirationTime(Zend_Db_Table_Row_Abstract $row)
+ {
+ return (int) $row->{$this->_modifiedColumn} + $this->_getLifetime($row);
+ }
+}
diff --git a/library/vendor/Zend/Session/SaveHandler/Exception.php b/library/vendor/Zend/Session/SaveHandler/Exception.php
new file mode 100644
index 0000000..2b1876f
--- /dev/null
+++ b/library/vendor/Zend/Session/SaveHandler/Exception.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Session_Exception
+ */
+
+/**
+ * Zend_Session_SaveHandler_Exception
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session_SaveHandler_Exception extends Zend_Session_Exception
+{}
diff --git a/library/vendor/Zend/Session/SaveHandler/Interface.php b/library/vendor/Zend/Session/SaveHandler/Interface.php
new file mode 100644
index 0000000..18ed5fe
--- /dev/null
+++ b/library/vendor/Zend/Session/SaveHandler/Interface.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+/**
+ * Zend_Session_SaveHandler_Interface
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @subpackage SaveHandler
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @see http://php.net/session_set_save_handler
+ */
+interface Zend_Session_SaveHandler_Interface
+{
+
+ /**
+ * Open Session - retrieve resources
+ *
+ * @param string $save_path
+ * @param string $name
+ */
+ public function open($save_path, $name);
+
+ /**
+ * Close Session - free resources
+ *
+ */
+ public function close();
+
+ /**
+ * Read session data
+ *
+ * @param string $id
+ */
+ public function read($id);
+
+ /**
+ * Write Session - commit data to resource
+ *
+ * @param string $id
+ * @param mixed $data
+ */
+ public function write($id, $data);
+
+ /**
+ * Destroy Session - remove data from resource for
+ * given session id
+ *
+ * @param string $id
+ */
+ public function destroy($id);
+
+ /**
+ * Garbage Collection - remove old session data older
+ * than $maxlifetime (in seconds)
+ *
+ * @param int $maxlifetime
+ */
+ public function gc($maxlifetime);
+
+}
diff --git a/library/vendor/Zend/Session/Validator/Abstract.php b/library/vendor/Zend/Session/Validator/Abstract.php
new file mode 100644
index 0000000..f39d20e
--- /dev/null
+++ b/library/vendor/Zend/Session/Validator/Abstract.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+/**
+ * @see Zend_Session_Validator_Interface
+ */
+
+/**
+ * Zend_Session_Validator_Abstract
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @subpackage Validator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Session_Validator_Abstract implements Zend_Session_Validator_Interface
+{
+
+ /**
+ * SetValidData() - This method should be used to store the environment variables that
+ * will be needed in order to validate the session later in the validate() method.
+ * These values are stored in the session in the __ZF namespace, in an array named VALID
+ *
+ * @param mixed $data
+ * @return void
+ */
+ protected function setValidData($data)
+ {
+ $validatorName = get_class($this);
+
+ $_SESSION['__ZF']['VALID'][$validatorName] = $data;
+ }
+
+
+ /**
+ * GetValidData() - This method should be used to retrieve the environment variables that
+ * will be needed to 'validate' a session.
+ *
+ * @return mixed
+ */
+ protected function getValidData()
+ {
+ $validatorName = get_class($this);
+ if (isset($_SESSION['__ZF']['VALID'][$validatorName])) {
+ return $_SESSION['__ZF']['VALID'][$validatorName];
+ }
+ return null;
+ }
+
+}
diff --git a/library/vendor/Zend/Session/Validator/Exception.php b/library/vendor/Zend/Session/Validator/Exception.php
new file mode 100644
index 0000000..03c2a03
--- /dev/null
+++ b/library/vendor/Zend/Session/Validator/Exception.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+
+/**
+ * @see Zend_Session_Exception
+ */
+
+
+/**
+ * Zend_Session_Validator_Exception
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @subpackage Validator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session_Validator_Exception extends Zend_Session_Exception
+{
+
+}
+
diff --git a/library/vendor/Zend/Session/Validator/HttpUserAgent.php b/library/vendor/Zend/Session/Validator/HttpUserAgent.php
new file mode 100644
index 0000000..8431aa2
--- /dev/null
+++ b/library/vendor/Zend/Session/Validator/HttpUserAgent.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+/**
+ * @see Zend_Session_Validator_Abstract
+ */
+
+/**
+ * Zend_Session_Validator_HttpUserAgent
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @subpackage Validator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Session_Validator_HttpUserAgent extends Zend_Session_Validator_Abstract
+{
+
+ /**
+ * Setup() - this method will get the current user agent and store it in the session
+ * as 'valid data'
+ *
+ * @return void
+ */
+ public function setup()
+ {
+ $this->setValidData( (isset($_SERVER['HTTP_USER_AGENT'])
+ ? $_SERVER['HTTP_USER_AGENT'] : null) );
+ }
+
+ /**
+ * Validate() - this method will determine if the current user agent matches the
+ * user agent we stored when we initialized this variable.
+ *
+ * @return bool
+ */
+ public function validate()
+ {
+ $currentBrowser = (isset($_SERVER['HTTP_USER_AGENT'])
+ ? $_SERVER['HTTP_USER_AGENT'] : null);
+
+ return $currentBrowser === $this->getValidData();
+ }
+
+}
diff --git a/library/vendor/Zend/Session/Validator/Interface.php b/library/vendor/Zend/Session/Validator/Interface.php
new file mode 100644
index 0000000..ab4eb3e
--- /dev/null
+++ b/library/vendor/Zend/Session/Validator/Interface.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ * @since Preview Release 0.2
+ */
+
+/**
+ * Zend_Session_Validator_Interface
+ *
+ * @category Zend
+ * @package Zend_Session
+ * @subpackage Validator
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Session_Validator_Interface
+{
+
+ /**
+ * Setup() - this method will store the environment variables
+ * necessary to be able to validate against in future requests.
+ *
+ * @return void
+ */
+ public function setup();
+
+ /**
+ * Validate() - this method will be called at the beginning of
+ * every session to determine if the current environment matches
+ * that which was store in the setup() procedure.
+ *
+ * @return boolean
+ */
+ public function validate();
+
+}
diff --git a/library/vendor/Zend/Soap/AutoDiscover.php b/library/vendor/Zend/Soap/AutoDiscover.php
new file mode 100644
index 0000000..794d2a1
--- /dev/null
+++ b/library/vendor/Zend/Soap/AutoDiscover.php
@@ -0,0 +1,597 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage AutoDiscover
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Server_Interface
+ */
+/**
+ * @see Zend_Soap_Wsdl
+ */
+/**
+ * @see Zend_Server_Reflection
+ */
+/**
+ * @see Zend_Server_Abstract
+ */
+/**
+ * @see Zend_Uri
+ */
+
+/**
+ * Zend_Soap_AutoDiscover
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage AutoDiscover
+ */
+class Zend_Soap_AutoDiscover implements Zend_Server_Interface
+{
+ /**
+ * @var Zend_Soap_Wsdl
+ */
+ protected $_wsdl = null;
+
+ /**
+ * @var Zend_Server_Reflection
+ */
+ protected $_reflection = null;
+
+ /**
+ * @var array
+ */
+ protected $_functions = array();
+
+ /**
+ * @var boolean
+ */
+ protected $_strategy;
+
+ /**
+ * Url where the WSDL file will be available at.
+ *
+ * @var WSDL Uri
+ */
+ protected $_uri;
+
+ /**
+ * soap:body operation style options
+ *
+ * @var array
+ */
+ protected $_operationBodyStyle = array('use' => 'encoded', 'encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/");
+
+ /**
+ * soap:operation style
+ *
+ * @var array
+ */
+ protected $_bindingStyle = array('style' => 'rpc', 'transport' => 'http://schemas.xmlsoap.org/soap/http');
+
+ /**
+ * Name of the class to handle the WSDL creation.
+ *
+ * @var string
+ */
+ protected $_wsdlClass = 'Zend_Soap_Wsdl';
+
+ /**
+ * Constructor
+ *
+ * @param boolean|string|Zend_Soap_Wsdl_Strategy_Interface $strategy
+ * @param string|Zend_Uri $uri
+ * @param string $wsdlClass
+ */
+ public function __construct($strategy = true, $uri=null, $wsdlClass=null)
+ {
+ $this->_reflection = new Zend_Server_Reflection();
+ $this->setComplexTypeStrategy($strategy);
+
+ if($uri !== null) {
+ $this->setUri($uri);
+ }
+
+ if($wsdlClass !== null) {
+ $this->setWsdlClass($wsdlClass);
+ }
+ }
+
+ /**
+ * Set the location at which the WSDL file will be availabe.
+ *
+ * @see Zend_Soap_Exception
+ * @param Zend_Uri|string $uri
+ * @return Zend_Soap_AutoDiscover
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function setUri($uri)
+ {
+ if (!is_string($uri) && !($uri instanceof Zend_Uri)) {
+ throw new Zend_Soap_AutoDiscover_Exception("No uri given to Zend_Soap_AutoDiscover::setUri as string or Zend_Uri instance.");
+ }
+ $this->_uri = $uri;
+
+ // change uri in WSDL file also if existant
+ if ($this->_wsdl instanceof Zend_Soap_Wsdl) {
+ $this->_wsdl->setUri($uri);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return the current Uri that the SOAP WSDL Service will be located at.
+ *
+ * @return Zend_Uri
+ */
+ public function getUri()
+ {
+ if($this->_uri !== null) {
+ $uri = $this->_uri;
+ } else {
+ $schema = $this->getSchema();
+ $host = $this->getHostName();
+ $scriptName = $this->getRequestUriWithoutParameters();
+ $uri = Zend_Uri::factory($schema . '://' . $host . $scriptName);
+ $this->setUri($uri);
+ }
+ return $uri;
+ }
+
+ /**
+ * Set the name of the WSDL handling class.
+ *
+ * @see Zend_Soap_Exception
+ * @see Zend_Soap_Exception
+ * @param string $wsdlClass
+ * @return Zend_Soap_AutoDiscover
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function setWsdlClass($wsdlClass)
+ {
+ if (!is_string($wsdlClass) && !is_subclass_of($wsdlClass, 'Zend_Soap_Wsdl')) {
+ throw new Zend_Soap_AutoDiscover_Exception("No Zend_Soap_Wsdl subclass given to Zend_Soap_AutoDiscover::setWsdlClass as string.");
+ }
+ $this->_wsdlClass = $wsdlClass;
+
+ return $this;
+ }
+
+ /**
+ * Return the name of the WSDL handling class.
+ *
+ * @return string
+ */
+ public function getWsdlClass()
+ {
+ return $this->_wsdlClass;
+ }
+
+ /**
+ * Set options for all the binding operations soap:body elements.
+ *
+ * By default the options are set to 'use' => 'encoded' and
+ * 'encodingStyle' => "http://schemas.xmlsoap.org/soap/encoding/".
+ *
+ * @see Zend_Soap_AutoDiscover_Exception
+ * @param array $operationStyle
+ * @return Zend_Soap_AutoDiscover
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function setOperationBodyStyle(array $operationStyle=array())
+ {
+ if(!isset($operationStyle['use'])) {
+ throw new Zend_Soap_AutoDiscover_Exception("Key 'use' is required in Operation soap:body style.");
+ }
+ $this->_operationBodyStyle = $operationStyle;
+ return $this;
+ }
+
+ /**
+ * Set Binding soap:binding style.
+ *
+ * By default 'style' is 'rpc' and 'transport' is 'http://schemas.xmlsoap.org/soap/http'.
+ *
+ * @param array $bindingStyle
+ * @return Zend_Soap_AutoDiscover
+ */
+ public function setBindingStyle(array $bindingStyle=array())
+ {
+ if(isset($bindingStyle['style'])) {
+ $this->_bindingStyle['style'] = $bindingStyle['style'];
+ }
+ if(isset($bindingStyle['transport'])) {
+ $this->_bindingStyle['transport'] = $bindingStyle['transport'];
+ }
+ return $this;
+ }
+
+ /**
+ * Detect and returns the current HTTP/HTTPS Schema
+ *
+ * @return string
+ */
+ protected function getSchema()
+ {
+ $schema = "http";
+ if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
+ $schema = 'https';
+ }
+ return $schema;
+ }
+
+ /**
+ * Detect and return the current hostname
+ *
+ * @return string
+ */
+ protected function getHostName()
+ {
+ if(isset($_SERVER['HTTP_HOST'])) {
+ $host = $_SERVER['HTTP_HOST'];
+ } else {
+ $host = $_SERVER['SERVER_NAME'];
+ }
+ return $host;
+ }
+
+ /**
+ * Detect and return the current script name without parameters
+ *
+ * @return string
+ */
+ protected function getRequestUriWithoutParameters()
+ {
+ if (isset($_SERVER['HTTP_X_ORIGINAL_URL'])) { // IIS with Microsoft Rewrite Module
+ $requestUri = $_SERVER['HTTP_X_ORIGINAL_URL'];
+ } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // check this first so IIS will catch
+ $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
+ } elseif (isset($_SERVER['REQUEST_URI'])) {
+ $requestUri = $_SERVER['REQUEST_URI'];
+ } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0, PHP as CGI
+ $requestUri = $_SERVER['ORIG_PATH_INFO'];
+ } else {
+ $requestUri = $_SERVER['SCRIPT_NAME'];
+ }
+ if( ($pos = strpos($requestUri, "?")) !== false) {
+ $requestUri = substr($requestUri, 0, $pos);
+ }
+
+ return $requestUri;
+ }
+
+ /**
+ * Set the strategy that handles functions and classes that are added AFTER this call.
+ *
+ * @param boolean|string|Zend_Soap_Wsdl_Strategy_Interface $strategy
+ * @return Zend_Soap_AutoDiscover
+ */
+ public function setComplexTypeStrategy($strategy)
+ {
+ $this->_strategy = $strategy;
+ if($this->_wsdl instanceof Zend_Soap_Wsdl) {
+ $this->_wsdl->setComplexTypeStrategy($strategy);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the Class the SOAP server will use
+ *
+ * @param string $class Class Name
+ * @param string $namespace Class Namspace - Not Used
+ * @param array $argv Arguments to instantiate the class - Not Used
+ * @return Zend_Soap_AutoDiscover
+ */
+ public function setClass($class, $namespace = '', $argv = null)
+ {
+ $uri = $this->getUri();
+
+ $wsdl = new $this->_wsdlClass($class, $uri, $this->_strategy);
+
+ // The wsdl:types element must precede all other elements (WS-I Basic Profile 1.1 R2023)
+ $wsdl->addSchemaTypeSection();
+
+ $port = $wsdl->addPortType($class . 'Port');
+ $binding = $wsdl->addBinding($class . 'Binding', 'tns:' .$class. 'Port');
+
+ $wsdl->addSoapBinding($binding, $this->_bindingStyle['style'], $this->_bindingStyle['transport']);
+ $wsdl->addService($class . 'Service', $class . 'Port', 'tns:' . $class . 'Binding', $uri);
+ foreach ($this->_reflection->reflectClass($class)->getMethods() as $method) {
+ $this->_addFunctionToWsdl($method, $wsdl, $port, $binding);
+ }
+ $this->_wsdl = $wsdl;
+
+ return $this;
+ }
+
+ /**
+ * Add a Single or Multiple Functions to the WSDL
+ *
+ * @param string $function Function Name
+ * @param string $namespace Function namespace - Not Used
+ * @return Zend_Soap_AutoDiscover
+ */
+ public function addFunction($function, $namespace = '')
+ {
+ static $port;
+ static $operation;
+ static $binding;
+
+ if (!is_array($function)) {
+ $function = (array) $function;
+ }
+
+ $uri = $this->getUri();
+
+ if (!($this->_wsdl instanceof Zend_Soap_Wsdl)) {
+ $parts = explode('.', basename($_SERVER['SCRIPT_NAME']));
+ $name = $parts[0];
+ $wsdl = new Zend_Soap_Wsdl($name, $uri, $this->_strategy);
+
+ // The wsdl:types element must precede all other elements (WS-I Basic Profile 1.1 R2023)
+ $wsdl->addSchemaTypeSection();
+
+ $port = $wsdl->addPortType($name . 'Port');
+ $binding = $wsdl->addBinding($name . 'Binding', 'tns:' .$name. 'Port');
+
+ $wsdl->addSoapBinding($binding, $this->_bindingStyle['style'], $this->_bindingStyle['transport']);
+ $wsdl->addService($name . 'Service', $name . 'Port', 'tns:' . $name . 'Binding', $uri);
+ } else {
+ $wsdl = $this->_wsdl;
+ }
+
+ foreach ($function as $func) {
+ $method = $this->_reflection->reflectFunction($func);
+ $this->_addFunctionToWsdl($method, $wsdl, $port, $binding);
+ }
+ $this->_wsdl = $wsdl;
+
+ return $this;
+ }
+
+ /**
+ * Add a function to the WSDL document.
+ *
+ * @param Zend_Server_Reflection_Function_Abstract $function function to add
+ * @param Zend_Soap_Wsdl $wsdl WSDL document
+ * @param object $port wsdl:portType
+ * @param object $binding wsdl:binding
+ * @return void
+ */
+ protected function _addFunctionToWsdl($function, $wsdl, $port, $binding)
+ {
+ $uri = $this->getUri();
+
+ // We only support one prototype: the one with the maximum number of arguments
+ $prototype = null;
+ $maxNumArgumentsOfPrototype = -1;
+ foreach ($function->getPrototypes() as $tmpPrototype) {
+ $numParams = count($tmpPrototype->getParameters());
+ if ($numParams > $maxNumArgumentsOfPrototype) {
+ $maxNumArgumentsOfPrototype = $numParams;
+ $prototype = $tmpPrototype;
+ }
+ }
+ if ($prototype === null) {
+ throw new Zend_Soap_AutoDiscover_Exception("No prototypes could be found for the '" . $function->getName() . "' function");
+ }
+
+ // Add the input message (parameters)
+ $args = array();
+ if ($this->_bindingStyle['style'] == 'document') {
+ // Document style: wrap all parameters in a sequence element
+ $sequence = array();
+ foreach ($prototype->getParameters() as $param) {
+ $sequenceElement = array(
+ 'name' => $param->getName(),
+ 'type' => $wsdl->getType($param->getType())
+ );
+ if ($param->isOptional()) {
+ $sequenceElement['nillable'] = 'true';
+ }
+ $sequence[] = $sequenceElement;
+ }
+ $element = array(
+ 'name' => $function->getName(),
+ 'sequence' => $sequence
+ );
+ // Add the wrapper element part, which must be named 'parameters'
+ $args['parameters'] = array('element' => $wsdl->addElement($element));
+ } else {
+ // RPC style: add each parameter as a typed part
+ foreach ($prototype->getParameters() as $param) {
+ $args[$param->getName()] = array('type' => $wsdl->getType($param->getType()));
+ }
+ }
+ $wsdl->addMessage($function->getName() . 'In', $args);
+
+ $isOneWayMessage = false;
+ if($prototype->getReturnType() == "void") {
+ $isOneWayMessage = true;
+ }
+
+ if($isOneWayMessage == false) {
+ // Add the output message (return value)
+ $args = array();
+ if ($this->_bindingStyle['style'] == 'document') {
+ // Document style: wrap the return value in a sequence element
+ $sequence = array();
+ if ($prototype->getReturnType() != "void") {
+ $sequence[] = array(
+ 'name' => $function->getName() . 'Result',
+ 'type' => $wsdl->getType($prototype->getReturnType())
+ );
+ }
+ $element = array(
+ 'name' => $function->getName() . 'Response',
+ 'sequence' => $sequence
+ );
+ // Add the wrapper element part, which must be named 'parameters'
+ $args['parameters'] = array('element' => $wsdl->addElement($element));
+ } else if ($prototype->getReturnType() != "void") {
+ // RPC style: add the return value as a typed part
+ $args['return'] = array('type' => $wsdl->getType($prototype->getReturnType()));
+ }
+ $wsdl->addMessage($function->getName() . 'Out', $args);
+ }
+
+ // Add the portType operation
+ if($isOneWayMessage == false) {
+ $portOperation = $wsdl->addPortOperation($port, $function->getName(), 'tns:' . $function->getName() . 'In', 'tns:' . $function->getName() . 'Out');
+ } else {
+ $portOperation = $wsdl->addPortOperation($port, $function->getName(), 'tns:' . $function->getName() . 'In', false);
+ }
+ $desc = $function->getDescription();
+ if (strlen($desc) > 0) {
+ $wsdl->addDocumentation($portOperation, $desc);
+ }
+
+ // When using the RPC style, make sure the operation style includes a 'namespace' attribute (WS-I Basic Profile 1.1 R2717)
+ if ($this->_bindingStyle['style'] == 'rpc' && !isset($this->_operationBodyStyle['namespace'])) {
+ $this->_operationBodyStyle['namespace'] = ''.$uri;
+ }
+
+ // Add the binding operation
+ if($isOneWayMessage == false) {
+ $operation = $wsdl->addBindingOperation($binding, $function->getName(), $this->_operationBodyStyle, $this->_operationBodyStyle);
+ } else {
+ $operation = $wsdl->addBindingOperation($binding, $function->getName(), $this->_operationBodyStyle);
+ }
+ $wsdl->addSoapOperation($operation, $uri . '#' .$function->getName());
+
+ // Add the function name to the list
+ $this->_functions[] = $function->getName();
+ }
+
+ /**
+ * Action to take when an error occurs
+ *
+ * @param string $fault
+ * @param string|int $code
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function fault($fault = null, $code = null)
+ {
+ throw new Zend_Soap_AutoDiscover_Exception("Function has no use in AutoDiscover.");
+ }
+
+ /**
+ * Handle the Request
+ *
+ * @param string $request A non-standard request - Not Used
+ */
+ public function handle($request = false)
+ {
+ if (!headers_sent()) {
+ header('Content-Type: text/xml');
+ }
+ $this->_wsdl->dump();
+ }
+
+ /**
+ * Proxy to WSDL dump function
+ *
+ * @param string $filename
+ * @return boolean
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function dump($filename)
+ {
+ if($this->_wsdl !== null) {
+ return $this->_wsdl->dump($filename);
+ } else {
+ /**
+ * @see Zend_Soap_AutoDiscover_Exception
+ */
+ throw new Zend_Soap_AutoDiscover_Exception("Cannot dump autodiscovered contents, WSDL file has not been generated yet.");
+ }
+ }
+
+ /**
+ * Proxy to WSDL toXml() function
+ *
+ * @return string
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function toXml()
+ {
+ if($this->_wsdl !== null) {
+ return $this->_wsdl->toXml();
+ } else {
+ /**
+ * @see Zend_Soap_AutoDiscover_Exception
+ */
+ throw new Zend_Soap_AutoDiscover_Exception("Cannot return autodiscovered contents, WSDL file has not been generated yet.");
+ }
+ }
+
+ /**
+ * Return an array of functions in the WSDL
+ *
+ * @return array
+ */
+ public function getFunctions()
+ {
+ return $this->_functions;
+ }
+
+ /**
+ * Load Functions
+ *
+ * @param unknown_type $definition
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function loadFunctions($definition)
+ {
+ throw new Zend_Soap_AutoDiscover_Exception("Function has no use in AutoDiscover.");
+ }
+
+ /**
+ * Set Persistance
+ *
+ * @param int $mode
+ * @throws Zend_Soap_AutoDiscover_Exception
+ */
+ public function setPersistence($mode)
+ {
+ throw new Zend_Soap_AutoDiscover_Exception("Function has no use in AutoDiscover.");
+ }
+
+ /**
+ * Returns an XSD Type for the given PHP type
+ *
+ * @param string $type PHP Type to get the XSD type for
+ * @return string
+ */
+ public function getType($type)
+ {
+ if (!($this->_wsdl instanceof Zend_Soap_Wsdl)) {
+ /** @todo Exception throwing may be more correct */
+
+ // WSDL is not defined yet, so we can't recognize type in context of current service
+ return '';
+ } else {
+ return $this->_wsdl->getType($type);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Soap/AutoDiscover/Exception.php b/library/vendor/Zend/Soap/AutoDiscover/Exception.php
new file mode 100644
index 0000000..6b6491e
--- /dev/null
+++ b/library/vendor/Zend/Soap/AutoDiscover/Exception.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage AutoDiscover
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @package Zend_Soap
+ * @subpackage AutoDiscover
+ */
+class Zend_Soap_AutoDiscover_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Soap/Client.php b/library/vendor/Zend/Soap/Client.php
new file mode 100644
index 0000000..e58223f
--- /dev/null
+++ b/library/vendor/Zend/Soap/Client.php
@@ -0,0 +1,1223 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Server
+ */
+
+/**
+ * @see Zend_Soap_Client_Local
+ */
+
+/**
+ * @see Zend_Soap_Client_Common
+ */
+
+/**
+ * Zend_Soap_Client
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Client
+{
+ /**
+ * Encoding
+ * @var string
+ */
+ protected $_encoding = 'UTF-8';
+
+ /**
+ * Array of SOAP type => PHP class pairings for handling return/incoming values
+ * @var array
+ */
+ protected $_classmap = null;
+
+ /**
+ * Registered fault exceptions
+ * @var array
+ */
+ protected $_faultExceptions = array();
+
+ /**
+ * SOAP version to use; SOAP_1_2 by default, to allow processing of headers
+ * @var int
+ */
+ protected $_soapVersion = SOAP_1_2;
+
+ /** Set of other SoapClient options */
+ protected $_uri = null;
+ protected $_location = null;
+ protected $_style = null;
+ protected $_use = null;
+ protected $_login = null;
+ protected $_password = null;
+ protected $_proxy_host = null;
+ protected $_proxy_port = null;
+ protected $_proxy_login = null;
+ protected $_proxy_password = null;
+ protected $_local_cert = null;
+ protected $_passphrase = null;
+ protected $_compression = null;
+ protected $_connection_timeout = null;
+ protected $_stream_context = null;
+ protected $_features = null;
+ protected $_cache_wsdl = null;
+ protected $_user_agent = null;
+ protected $_exceptions = null;
+
+ /**
+ * WSDL used to access server
+ * It also defines Zend_Soap_Client working mode (WSDL vs non-WSDL)
+ *
+ * @var string
+ */
+ protected $_wsdl = null;
+
+ /**
+ * SoapClient object
+ *
+ * @var SoapClient
+ */
+ protected $_soapClient;
+
+ /**
+ * Last invoked method
+ *
+ * @var string
+ */
+ protected $_lastMethod = '';
+
+ /**
+ * SOAP request headers.
+ *
+ * Array of SoapHeader objects
+ *
+ * @var array
+ */
+ protected $_soapInputHeaders = array();
+
+ /**
+ * Permanent SOAP request headers (shared between requests).
+ *
+ * Array of SoapHeader objects
+ *
+ * @var array
+ */
+ protected $_permanentSoapInputHeaders = array();
+
+ /**
+ * Output SOAP headers.
+ *
+ * Array of SoapHeader objects
+ *
+ * @var array
+ */
+ protected $_soapOutputHeaders = array();
+
+ /**
+ * Constructor
+ *
+ * @param string $wsdl
+ * @param array $options
+ */
+ public function __construct($wsdl = null, $options = null)
+ {
+ if (!extension_loaded('soap')) {
+ throw new Zend_Soap_Client_Exception('SOAP extension is not loaded.');
+ }
+
+ if ($wsdl !== null) {
+ $this->setWsdl($wsdl);
+ }
+ if ($options !== null) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set wsdl
+ *
+ * @param string $wsdl
+ * @return Zend_Soap_Client
+ */
+ public function setWsdl($wsdl)
+ {
+ $this->_wsdl = $wsdl;
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get wsdl
+ *
+ * @return string
+ */
+ public function getWsdl()
+ {
+ return $this->_wsdl;
+ }
+
+ /**
+ * Set Options
+ *
+ * Allows setting options as an associative array of option => value pairs.
+ *
+ * @param array|Zend_Config $options
+ * @return Zend_Soap_Client
+ * @throws Zend_SoapClient_Exception
+ */
+ public function setOptions($options)
+ {
+ if($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ foreach ($options as $key => $value) {
+ switch ($key) {
+ case 'classmap':
+ case 'classMap':
+ $this->setClassmap($value);
+ break;
+ case 'encoding':
+ $this->setEncoding($value);
+ break;
+ case 'soapVersion':
+ case 'soap_version':
+ $this->setSoapVersion($value);
+ break;
+ case 'wsdl':
+ $this->setWsdl($value);
+ break;
+ case 'uri':
+ $this->setUri($value);
+ break;
+ case 'location':
+ $this->setLocation($value);
+ break;
+ case 'style':
+ $this->setStyle($value);
+ break;
+ case 'use':
+ $this->setEncodingMethod($value);
+ break;
+ case 'login':
+ $this->setHttpLogin($value);
+ break;
+ case 'password':
+ $this->setHttpPassword($value);
+ break;
+ case 'proxy_host':
+ $this->setProxyHost($value);
+ break;
+ case 'proxy_port':
+ $this->setProxyPort($value);
+ break;
+ case 'proxy_login':
+ $this->setProxyLogin($value);
+ break;
+ case 'proxy_password':
+ $this->setProxyPassword($value);
+ break;
+ case 'local_cert':
+ $this->setHttpsCertificate($value);
+ break;
+ case 'passphrase':
+ $this->setHttpsCertPassphrase($value);
+ break;
+ case 'compression':
+ $this->setCompressionOptions($value);
+ break;
+ case 'stream_context':
+ $this->setStreamContext($value);
+ break;
+ case 'features':
+ $this->setSoapFeatures($value);
+ break;
+ case 'cache_wsdl':
+ $this->setWsdlCache($value);
+ break;
+ case 'useragent':
+ case 'userAgent':
+ case 'user_agent':
+ $this->setUserAgent($value);
+ break;
+ case 'exceptions':
+ $this->setExceptions($value);
+ break;
+
+ // Not used now
+ // case 'connection_timeout':
+ // $this->_connection_timeout = $value;
+ // break;
+
+ default:
+ throw new Zend_Soap_Client_Exception('Unknown SOAP client option');
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return array of options suitable for using with SoapClient constructor
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ $options = array();
+
+ $options['classmap'] = $this->getClassmap();
+ $options['encoding'] = $this->getEncoding();
+ $options['soap_version'] = $this->getSoapVersion();
+ $options['wsdl'] = $this->getWsdl();
+ $options['uri'] = $this->getUri();
+ $options['location'] = $this->getLocation();
+ $options['style'] = $this->getStyle();
+ $options['use'] = $this->getEncodingMethod();
+ $options['login'] = $this->getHttpLogin();
+ $options['password'] = $this->getHttpPassword();
+ $options['proxy_host'] = $this->getProxyHost();
+ $options['proxy_port'] = $this->getProxyPort();
+ $options['proxy_login'] = $this->getProxyLogin();
+ $options['proxy_password'] = $this->getProxyPassword();
+ $options['local_cert'] = $this->getHttpsCertificate();
+ $options['passphrase'] = $this->getHttpsCertPassphrase();
+ $options['compression'] = $this->getCompressionOptions();
+ //$options['connection_timeout'] = $this->_connection_timeout;
+ $options['stream_context'] = $this->getStreamContext();
+ $options['cache_wsdl'] = $this->getWsdlCache();
+ $options['features'] = $this->getSoapFeatures();
+ $options['user_agent'] = $this->getUserAgent();
+ $options['exceptions'] = $this->getExceptions();
+
+ foreach ($options as $key => $value) {
+ /*
+ * ugly hack as I don't know if checking for '=== null'
+ * breaks some other option
+ */
+ if (in_array($key, array('user_agent', 'cache_wsdl', 'compression', 'exceptions'))) {
+ if ($value === null) {
+ unset($options[$key]);
+ }
+ } else {
+ if ($value == null) {
+ unset($options[$key]);
+ }
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * Set SOAP version
+ *
+ * @param int $version One of the SOAP_1_1 or SOAP_1_2 constants
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid soap version argument
+ */
+ public function setSoapVersion($version)
+ {
+ if (!in_array($version, array(SOAP_1_1, SOAP_1_2))) {
+ throw new Zend_Soap_Client_Exception('Invalid soap version specified. Use SOAP_1_1 or SOAP_1_2 constants.');
+ }
+ $this->_soapVersion = $version;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get SOAP version
+ *
+ * @return int
+ */
+ public function getSoapVersion()
+ {
+ return $this->_soapVersion;
+ }
+
+ /**
+ * Set classmap
+ *
+ * @param array $classmap
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception for any invalid class in the class map
+ */
+ public function setClassmap(array $classmap)
+ {
+ foreach ($classmap as $type => $class) {
+ if (!class_exists($class)) {
+ throw new Zend_Soap_Client_Exception('Invalid class in class map');
+ }
+ }
+
+ $this->_classmap = $classmap;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve classmap
+ *
+ * @return mixed
+ */
+ public function getClassmap()
+ {
+ return $this->_classmap;
+ }
+
+ /**
+ * Set encoding
+ *
+ * @param string $encoding
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid encoding argument
+ */
+ public function setEncoding($encoding)
+ {
+ if (!is_string($encoding)) {
+ throw new Zend_Soap_Client_Exception('Invalid encoding specified');
+ }
+
+ $this->_encoding = $encoding;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Check for valid URN
+ *
+ * @param string $urn
+ * @return true
+ * @throws Zend_Soap_Client_Exception on invalid URN
+ */
+ public function validateUrn($urn)
+ {
+ $scheme = parse_url($urn, PHP_URL_SCHEME);
+ if ($scheme === false || $scheme === null) {
+ throw new Zend_Soap_Client_Exception('Invalid URN');
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Set URI
+ *
+ * URI in Web Service the target namespace
+ *
+ * @param string $uri
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid uri argument
+ */
+ public function setUri($uri)
+ {
+ $this->validateUrn($uri);
+ $this->_uri = $uri;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve URI
+ *
+ * @return string
+ */
+ public function getUri()
+ {
+ return $this->_uri;
+ }
+
+ /**
+ * Set Location
+ *
+ * URI in Web Service the target namespace
+ *
+ * @param string $location
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid uri argument
+ */
+ public function setLocation($location)
+ {
+ $this->validateUrn($location);
+ $this->_location = $location;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve URI
+ *
+ * @return string
+ */
+ public function getLocation()
+ {
+ return $this->_location;
+ }
+
+ /**
+ * Set request style
+ *
+ * @param int $style One of the SOAP_RPC or SOAP_DOCUMENT constants
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid style argument
+ */
+ public function setStyle($style)
+ {
+ if (!in_array($style, array(SOAP_RPC, SOAP_DOCUMENT))) {
+ throw new Zend_Soap_Client_Exception('Invalid request style specified. Use SOAP_RPC or SOAP_DOCUMENT constants.');
+ }
+
+ $this->_style = $style;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get request style
+ *
+ * @return int
+ */
+ public function getStyle()
+ {
+ return $this->_style;
+ }
+
+ /**
+ * Set message encoding method
+ *
+ * @param int $use One of the SOAP_ENCODED or SOAP_LITERAL constants
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid message encoding method argument
+ */
+ public function setEncodingMethod($use)
+ {
+ if (!in_array($use, array(SOAP_ENCODED, SOAP_LITERAL))) {
+ throw new Zend_Soap_Client_Exception('Invalid message encoding method. Use SOAP_ENCODED or SOAP_LITERAL constants.');
+ }
+
+ $this->_use = $use;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get message encoding method
+ *
+ * @return int
+ */
+ public function getEncodingMethod()
+ {
+ return $this->_use;
+ }
+
+ /**
+ * Set HTTP login
+ *
+ * @param string $login
+ * @return Zend_Soap_Client
+ */
+ public function setHttpLogin($login)
+ {
+ $this->_login = $login;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve HTTP Login
+ *
+ * @return string
+ */
+ public function getHttpLogin()
+ {
+ return $this->_login;
+ }
+
+ /**
+ * Set HTTP password
+ *
+ * @param string $password
+ * @return Zend_Soap_Client
+ */
+ public function setHttpPassword($password)
+ {
+ $this->_password = $password;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve HTTP Password
+ *
+ * @return string
+ */
+ public function getHttpPassword()
+ {
+ return $this->_password;
+ }
+
+ /**
+ * Set proxy host
+ *
+ * @param string $proxyHost
+ * @return Zend_Soap_Client
+ */
+ public function setProxyHost($proxyHost)
+ {
+ $this->_proxy_host = $proxyHost;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve proxy host
+ *
+ * @return string
+ */
+ public function getProxyHost()
+ {
+ return $this->_proxy_host;
+ }
+
+ /**
+ * Set proxy port
+ *
+ * @param int $proxyPort
+ * @return Zend_Soap_Client
+ */
+ public function setProxyPort($proxyPort)
+ {
+ $this->_proxy_port = (int)$proxyPort;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve proxy port
+ *
+ * @return int
+ */
+ public function getProxyPort()
+ {
+ return $this->_proxy_port;
+ }
+
+ /**
+ * Set proxy login
+ *
+ * @param string $proxyLogin
+ * @return Zend_Soap_Client
+ */
+ public function setProxyLogin($proxyLogin)
+ {
+ $this->_proxy_login = $proxyLogin;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Retrieve proxy login
+ *
+ * @return string
+ */
+ public function getProxyLogin()
+ {
+ return $this->_proxy_login;
+ }
+
+ /**
+ * Set proxy password
+ *
+ * @param string $proxyLogin
+ * @return Zend_Soap_Client
+ */
+ public function setProxyPassword($proxyPassword)
+ {
+ $this->_proxy_password = $proxyPassword;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Set HTTPS client certificate path
+ *
+ * @param string $localCert local certificate path
+ * @return Zend_Soap_Client
+ * @throws Zend_Soap_Client_Exception with invalid local certificate path argument
+ */
+ public function setHttpsCertificate($localCert)
+ {
+ if (!is_readable($localCert)) {
+ throw new Zend_Soap_Client_Exception('Invalid HTTPS client certificate path.');
+ }
+
+ $this->_local_cert = $localCert;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get HTTPS client certificate path
+ *
+ * @return string
+ */
+ public function getHttpsCertificate()
+ {
+ return $this->_local_cert;
+ }
+
+ /**
+ * Set HTTPS client certificate passphrase
+ *
+ * @param string $passphrase
+ * @return Zend_Soap_Client
+ */
+ public function setHttpsCertPassphrase($passphrase)
+ {
+ $this->_passphrase = $passphrase;
+
+ $this->_soapClient = null;
+
+ return $this;
+ }
+
+ /**
+ * Get HTTPS client certificate passphrase
+ *
+ * @return string
+ */
+ public function getHttpsCertPassphrase()
+ {
+ return $this->_passphrase;
+ }
+
+ /**
+ * Set compression options
+ *
+ * @param int|null $compressionOptions
+ * @return Zend_Soap_Client
+ */
+ public function setCompressionOptions($compressionOptions)
+ {
+ if ($compressionOptions === null) {
+ $this->_compression = null;
+ } else {
+ $this->_compression = (int)$compressionOptions;
+ }
+ $this->_soapClient = null;
+ return $this;
+ }
+
+ /**
+ * Get Compression options
+ *
+ * @return int
+ */
+ public function getCompressionOptions()
+ {
+ return $this->_compression;
+ }
+
+ /**
+ * Retrieve proxy password
+ *
+ * @return string
+ */
+ public function getProxyPassword()
+ {
+ return $this->_proxy_password;
+ }
+
+ /**
+ * Set Stream Context
+ *
+ * @return Zend_Soap_Client
+ */
+ public function setStreamContext($context)
+ {
+ if(!is_resource($context) || get_resource_type($context) !== "stream-context") {
+ /**
+ * @see Zend_Soap_Client_Exception
+ */
+ throw new Zend_Soap_Client_Exception(
+ "Invalid stream context resource given."
+ );
+ }
+
+ $this->_stream_context = $context;
+ return $this;
+ }
+
+ /**
+ * Get Stream Context
+ *
+ * @return resource
+ */
+ public function getStreamContext()
+ {
+ return $this->_stream_context;
+ }
+
+ /**
+ * Set the SOAP Feature options.
+ *
+ * @param string|int $feature
+ * @return Zend_Soap_Client
+ */
+ public function setSoapFeatures($feature)
+ {
+ $this->_features = $feature;
+
+ $this->_soapClient = null;
+ return $this;
+ }
+
+ /**
+ * Return current SOAP Features options
+ *
+ * @return int
+ */
+ public function getSoapFeatures()
+ {
+ return $this->_features;
+ }
+
+ /**
+ * Set the SOAP Wsdl Caching Options
+ *
+ * @param string|int|boolean|null $caching
+ * @return Zend_Soap_Client
+ */
+ public function setWsdlCache($caching)
+ {
+ if ($caching === null) {
+ $this->_cache_wsdl = null;
+ } else {
+ $this->_cache_wsdl = (int)$caching;
+ }
+ return $this;
+ }
+
+ /**
+ * Get current SOAP Wsdl Caching option
+ *
+ * @return int
+ */
+ public function getWsdlCache()
+ {
+ return $this->_cache_wsdl;
+ }
+
+ /**
+ * Set the string to use in User-Agent header
+ *
+ * @param string|null $userAgent
+ * @return Zend_Soap_Client
+ */
+ public function setUserAgent($userAgent)
+ {
+ if ($userAgent === null) {
+ $this->_user_agent = null;
+ } else {
+ $this->_user_agent = (string)$userAgent;
+ }
+ return $this;
+ }
+
+ /**
+ * Get current string to use in User-Agent header
+ *
+ * @return string|null
+ */
+ public function getUserAgent()
+ {
+ return $this->_user_agent;
+ }
+
+ /**
+ * Set the exceptions option
+ *
+ * The exceptions option is a boolean value defining whether soap errors
+ * throw exceptions.
+ *
+ * @see http://php.net/manual/soapclient.soapclient.php#refsect1-soapclient.soapclient-parameters
+ *
+ * @param bool $exceptions
+ * @return $this
+ */
+ public function setExceptions($exceptions)
+ {
+ $this->_exceptions = (bool) $exceptions;
+
+ return $this;
+ }
+
+ /**
+ * Get the exceptions option
+ *
+ * The exceptions option is a boolean value defining whether soap errors
+ * throw exceptions.
+ *
+ * @see http://php.net/manual/soapclient.soapclient.php#refsect1-soapclient.soapclient-parameters
+ *
+ * @return bool|null
+ */
+ public function getExceptions()
+ {
+ return $this->_exceptions;
+ }
+
+ /**
+ * Retrieve request XML
+ *
+ * @return string
+ */
+ public function getLastRequest()
+ {
+ if ($this->_soapClient !== null) {
+ return $this->_soapClient->__getLastRequest();
+ }
+
+ return '';
+ }
+
+ /**
+ * Get response XML
+ *
+ * @return string
+ */
+ public function getLastResponse()
+ {
+ if ($this->_soapClient !== null) {
+ return $this->_soapClient->__getLastResponse();
+ }
+
+ return '';
+ }
+
+ /**
+ * Retrieve request headers
+ *
+ * @return string
+ */
+ public function getLastRequestHeaders()
+ {
+ if ($this->_soapClient !== null) {
+ return $this->_soapClient->__getLastRequestHeaders();
+ }
+
+ return '';
+ }
+
+ /**
+ * Retrieve response headers (as string)
+ *
+ * @return string
+ */
+ public function getLastResponseHeaders()
+ {
+ if ($this->_soapClient !== null) {
+ return $this->_soapClient->__getLastResponseHeaders();
+ }
+
+ return '';
+ }
+
+ /**
+ * Retrieve last invoked method
+ *
+ * @return string
+ */
+ public function getLastMethod()
+ {
+ return $this->_lastMethod;
+ }
+
+ /**
+ * Do request proxy method.
+ *
+ * May be overridden in subclasses
+ *
+ * @internal
+ * @param Zend_Soap_Client_Common $client
+ * @param string $request
+ * @param string $location
+ * @param string $action
+ * @param int $version
+ * @param int $one_way
+ * @return mixed
+ */
+ public function _doRequest(Zend_Soap_Client_Common $client, $request, $location, $action, $version, $one_way = null)
+ {
+ // Perform request as is
+ if ($one_way == null) {
+ return call_user_func(array($client,'SoapClient::__doRequest'), $request, $location, $action, $version);
+ } else {
+ return call_user_func(array($client,'SoapClient::__doRequest'), $request, $location, $action, $version, $one_way);
+ }
+ }
+
+ /**
+ * Initialize SOAP Client object
+ *
+ * @throws Zend_Soap_Client_Exception
+ */
+ protected function _initSoapClientObject()
+ {
+ $wsdl = $this->getWsdl();
+ $options = array_merge($this->getOptions(), array('trace' => true));
+
+ if ($wsdl == null) {
+ if (!isset($options['location'])) {
+ throw new Zend_Soap_Client_Exception('\'location\' parameter is required in non-WSDL mode.');
+ }
+ if (!isset($options['uri'])) {
+ throw new Zend_Soap_Client_Exception('\'uri\' parameter is required in non-WSDL mode.');
+ }
+ } else {
+ if (isset($options['use'])) {
+ throw new Zend_Soap_Client_Exception('\'use\' parameter only works in non-WSDL mode.');
+ }
+ if (isset($options['style'])) {
+ throw new Zend_Soap_Client_Exception('\'style\' parameter only works in non-WSDL mode.');
+ }
+ }
+ unset($options['wsdl']);
+
+ $this->_soapClient = new Zend_Soap_Client_Common(array($this, '_doRequest'), $wsdl, $options);
+ }
+
+
+ /**
+ * Perform arguments pre-processing
+ *
+ * My be overridden in descendant classes
+ *
+ * @param array $arguments
+ */
+ protected function _preProcessArguments($arguments)
+ {
+ // Do nothing
+ return $arguments;
+ }
+
+ /**
+ * Perform result pre-processing
+ *
+ * My be overridden in descendant classes
+ *
+ * @param array $arguments
+ */
+ protected function _preProcessResult($result)
+ {
+ // Do nothing
+ return $result;
+ }
+
+ /**
+ * Add SOAP input header
+ *
+ * @param SoapHeader $header
+ * @param boolean $permanent
+ * @return Zend_Soap_Client
+ */
+ public function addSoapInputHeader(SoapHeader $header, $permanent = false)
+ {
+ if ($permanent) {
+ $this->_permanentSoapInputHeaders[] = $header;
+ } else {
+ $this->_soapInputHeaders[] = $header;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Reset SOAP input headers
+ *
+ * @return Zend_Soap_Client
+ */
+ public function resetSoapInputHeaders()
+ {
+ $this->_permanentSoapInputHeaders = array();
+ $this->_soapInputHeaders = array();
+
+ return $this;
+ }
+
+ /**
+ * Get last SOAP output headers
+ *
+ * @return array
+ */
+ public function getLastSoapOutputHeaderObjects()
+ {
+ return $this->_soapOutputHeaders;
+ }
+
+ /**
+ * Perform a SOAP call
+ *
+ * @param string $name
+ * @param array $arguments
+ * @return mixed
+ */
+ public function __call($name, $arguments)
+ {
+ $soapClient = $this->getSoapClient();
+
+ $this->_lastMethod = $name;
+
+ $soapHeaders = array_merge($this->_permanentSoapInputHeaders, $this->_soapInputHeaders);
+ $result = $soapClient->__soapCall($name,
+ $this->_preProcessArguments($arguments),
+ null, /* Options are already set to the SOAP client object */
+ (count($soapHeaders) > 0)? $soapHeaders : null,
+ $this->_soapOutputHeaders);
+
+ // Reset non-permanent input headers
+ $this->_soapInputHeaders = array();
+
+ return $this->_preProcessResult($result);
+ }
+
+
+ /**
+ * Return a list of available functions
+ *
+ * @return array
+ * @throws Zend_Soap_Client_Exception
+ */
+ public function getFunctions()
+ {
+ if ($this->getWsdl() == null) {
+ throw new Zend_Soap_Client_Exception('\'getFunctions\' method is available only in WSDL mode.');
+ }
+
+ $soapClient = $this->getSoapClient();
+ return $soapClient->__getFunctions();
+ }
+
+
+ /**
+ * Get used types.
+ *
+ * @return array
+ */
+
+ /**
+ * Return a list of SOAP types
+ *
+ * @return array
+ * @throws Zend_Soap_Client_Exception
+ */
+ public function getTypes()
+ {
+ if ($this->getWsdl() == null) {
+ throw new Zend_Soap_Client_Exception('\'getTypes\' method is available only in WSDL mode.');
+ }
+
+ $soapClient = $this->getSoapClient();
+
+ return $soapClient->__getTypes();
+ }
+
+ /**
+ * @param SoapClient $soapClient
+ * @return Zend_Soap_Client
+ */
+ public function setSoapClient(SoapClient $soapClient)
+ {
+ $this->_soapClient = $soapClient;
+ return $this;
+ }
+
+ /**
+ * @return SoapClient
+ */
+ public function getSoapClient()
+ {
+ if ($this->_soapClient == null) {
+ $this->_initSoapClientObject();
+ }
+ return $this->_soapClient;
+ }
+
+ /**
+ * @param string $name
+ * @param string $value
+ * @return Zend_Soap_Client
+ */
+ public function setCookie($cookieName, $cookieValue=null)
+ {
+ $soapClient = $this->getSoapClient();
+ $soapClient->__setCookie($cookieName, $cookieValue);
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/Soap/Client/Common.php b/library/vendor/Zend/Soap/Client/Common.php
new file mode 100644
index 0000000..7880115
--- /dev/null
+++ b/library/vendor/Zend/Soap/Client/Common.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+if (extension_loaded('soap')) {
+
+/**
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ */
+class Zend_Soap_Client_Common extends SoapClient
+{
+ /**
+ * doRequest() pre-processing method
+ *
+ * @var callback
+ */
+ protected $_doRequestCallback;
+
+ /**
+ * Common Soap Client constructor
+ *
+ * @param callback $doRequestMethod
+ * @param string $wsdl
+ * @param array $options
+ */
+ function __construct($doRequestCallback, $wsdl, $options)
+ {
+ $this->_doRequestCallback = $doRequestCallback;
+
+ parent::__construct($wsdl, $options);
+ }
+
+ /**
+ * Performs SOAP request over HTTP.
+ * Overridden to implement different transport layers, perform additional XML processing or other purpose.
+ *
+ * @param string $request
+ * @param string $location
+ * @param string $action
+ * @param int $version
+ * @param int $one_way
+ * @return mixed
+ */
+ function __doRequest($request, $location, $action, $version, $one_way = null)
+ {
+ if ($one_way === null) {
+ return call_user_func($this->_doRequestCallback, $this, $request, $location, $action, $version);
+ } else {
+ return call_user_func($this->_doRequestCallback, $this, $request, $location, $action, $version, $one_way);
+ }
+ }
+
+}
+
+} // end if (extension_loaded('soap')
diff --git a/library/vendor/Zend/Soap/Client/DotNet.php b/library/vendor/Zend/Soap/Client/DotNet.php
new file mode 100644
index 0000000..0e6d121
--- /dev/null
+++ b/library/vendor/Zend/Soap/Client/DotNet.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Soap_Client */
+
+if (extension_loaded('soap')) {
+
+/**
+ * Zend_Soap_Client_Local
+ *
+ * Class is intended to be used with .Net Web Services.
+ *
+ * Important! Class is at experimental stage now.
+ * Please leave your notes, compatiblity issues reports or
+ * suggestions in fw-webservices@lists.zend.com or fw-general@lists.com
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ */
+class Zend_Soap_Client_DotNet extends Zend_Soap_Client
+{
+ /**
+ * Constructor
+ *
+ * @param string $wsdl
+ * @param array $options
+ */
+ public function __construct($wsdl = null, $options = null)
+ {
+ // Use SOAP 1.1 as default
+ $this->setSoapVersion(SOAP_1_1);
+
+ parent::__construct($wsdl, $options);
+ }
+
+
+ /**
+ * Perform arguments pre-processing
+ *
+ * My be overridden in descendant classes
+ *
+ * @param array $arguments
+ * @throws Zend_Soap_Client_Exception
+ */
+ protected function _preProcessArguments($arguments)
+ {
+ if (count($arguments) > 1 ||
+ (count($arguments) == 1 && !is_array(reset($arguments)))
+ ) {
+ throw new Zend_Soap_Client_Exception('.Net webservice arguments have to be grouped into array: array(\'a\' => $a, \'b\' => $b, ...).');
+ }
+
+ // Do nothing
+ return $arguments;
+ }
+
+ /**
+ * Perform result pre-processing
+ *
+ * My be overridden in descendant classes
+ *
+ * @param array $arguments
+ */
+ protected function _preProcessResult($result)
+ {
+ $resultProperty = $this->getLastMethod() . 'Result';
+
+ return $result->$resultProperty;
+ }
+
+}
+
+} // end if (extension_loaded('soap')
diff --git a/library/vendor/Zend/Soap/Client/Exception.php b/library/vendor/Zend/Soap/Client/Exception.php
new file mode 100644
index 0000000..2c210ed
--- /dev/null
+++ b/library/vendor/Zend/Soap/Client/Exception.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Exception */
+
+/**
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Soap_Client_Exception extends Zend_Exception
+{}
+
diff --git a/library/vendor/Zend/Soap/Client/Local.php b/library/vendor/Zend/Soap/Client/Local.php
new file mode 100644
index 0000000..c060700
--- /dev/null
+++ b/library/vendor/Zend/Soap/Client/Local.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Soap_Server */
+
+/** Zend_Soap_Client */
+
+if (extension_loaded('soap')) {
+
+/**
+ * Zend_Soap_Client_Local
+ *
+ * Class is intended to be used as local SOAP client which works
+ * with a provided Server object.
+ *
+ * Could be used for development or testing purposes.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Client
+ */
+class Zend_Soap_Client_Local extends Zend_Soap_Client
+{
+ /**
+ * Server object
+ *
+ * @var Zend_Soap_Server
+ */
+ protected $_server;
+
+ /**
+ * Local client constructor
+ *
+ * @param Zend_Soap_Server $server
+ * @param string $wsdl
+ * @param array $options
+ */
+ function __construct(Zend_Soap_Server $server, $wsdl, $options = null)
+ {
+ $this->_server = $server;
+
+ // Use Server specified SOAP version as default
+ $this->setSoapVersion($server->getSoapVersion());
+
+ parent::__construct($wsdl, $options);
+ }
+
+ /**
+ * Actual "do request" method.
+ *
+ * @internal
+ * @param Zend_Soap_Client_Common $client
+ * @param string $request
+ * @param string $location
+ * @param string $action
+ * @param int $version
+ * @param int $one_way
+ * @return mixed
+ */
+ public function _doRequest(Zend_Soap_Client_Common $client, $request, $location, $action, $version, $one_way = null)
+ {
+ // Perform request as is
+ ob_start();
+ $this->_server->handle($request);
+ $response = ob_get_clean();
+
+ if ($response === null || $response === '') {
+ $serverResponse = $this->server->getResponse();
+ if ($serverResponse !== null) {
+ $response = $serverResponse;
+ }
+ }
+
+ return $response;
+ }
+}
+
+} // end if (extension_loaded('soap')
diff --git a/library/vendor/Zend/Soap/Server.php b/library/vendor/Zend/Soap/Server.php
new file mode 100644
index 0000000..9b36815
--- /dev/null
+++ b/library/vendor/Zend/Soap/Server.php
@@ -0,0 +1,999 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * @see Zend_Server_Interface
+ */
+
+/** @see Zend_Xml_Security */
+
+/** @see Zend_Xml_Exception */
+
+/**
+ * Zend_Soap_Server
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Server
+ * @uses Zend_Server_Interface
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Soap_Server implements Zend_Server_Interface
+{
+ /**
+ * Actor URI
+ * @var string URI
+ */
+ protected $_actor;
+
+ /**
+ * Class registered with this server
+ * @var string
+ */
+ protected $_class;
+
+ /**
+ * Arguments to pass to {@link $_class} constructor
+ * @var array
+ */
+ protected $_classArgs = array();
+
+ /**
+ * Object registered with this server
+ */
+ protected $_object;
+
+ /**
+ * Array of SOAP type => PHP class pairings for handling return/incoming values
+ * @var array
+ */
+ protected $_classmap;
+
+ /**
+ * Encoding
+ * @var string
+ */
+ protected $_encoding;
+
+ /**
+ * SOAP Server Features
+ *
+ * @var int
+ */
+ protected $_features;
+
+ /**
+ * WSDL Caching Options of SOAP Server
+ *
+ * @var mixed
+ */
+ protected $_wsdlCache;
+
+ /**
+ * WS-I compliant
+ *
+ * @var boolean
+ */
+ protected $_wsiCompliant;
+
+ /**
+ * Registered fault exceptions
+ * @var array
+ */
+ protected $_faultExceptions = array();
+
+ /**
+ * Functions registered with this server; may be either an array or the SOAP_FUNCTIONS_ALL
+ * constant
+ * @var array|int
+ */
+ protected $_functions = array();
+
+ /**
+ * Persistence mode; should be one of the SOAP persistence constants
+ * @var int
+ */
+ protected $_persistence;
+
+ /**
+ * Request XML
+ * @var string
+ */
+ protected $_request;
+
+ /**
+ * Response XML
+ * @var string
+ */
+ protected $_response;
+
+ /**
+ * Flag: whether or not {@link handle()} should return a response instead
+ * of automatically emitting it.
+ * @var boolean
+ */
+ protected $_returnResponse = false;
+
+ /**
+ * SOAP version to use; SOAP_1_2 by default, to allow processing of headers
+ * @var int
+ */
+ protected $_soapVersion = SOAP_1_2;
+
+ /**
+ * URI or path to WSDL
+ * @var string
+ */
+ protected $_wsdl;
+
+ /**
+ * URI namespace for SOAP server
+ * @var string URI
+ */
+ protected $_uri;
+
+ /**
+ * Constructor
+ *
+ * Sets display_errors INI setting to off (prevent client errors due to bad
+ * XML in response). Registers {@link handlePhpErrors()} as error handler
+ * for E_USER_ERROR.
+ *
+ * If $wsdl is provided, it is passed on to {@link setWsdl()}; if any
+ * options are specified, they are passed on to {@link setOptions()}.
+ *
+ * @param string $wsdl
+ * @param array $options
+ * @return void
+ */
+ public function __construct($wsdl = null, array $options = null)
+ {
+ if (!extension_loaded('soap')) {
+ throw new Zend_Soap_Server_Exception('SOAP extension is not loaded.');
+ }
+
+ if (null !== $wsdl) {
+ $this->setWsdl($wsdl);
+ }
+
+ if (null !== $options) {
+ $this->setOptions($options);
+ }
+ }
+
+ /**
+ * Set Options
+ *
+ * Allows setting options as an associative array of option => value pairs.
+ *
+ * @param array|Zend_Config $options
+ * @return Zend_Soap_Server
+ */
+ public function setOptions($options)
+ {
+ if($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ foreach ($options as $key => $value) {
+ switch ($key) {
+ case 'actor':
+ $this->setActor($value);
+ break;
+ case 'classmap':
+ case 'classMap':
+ $this->setClassmap($value);
+ break;
+ case 'encoding':
+ $this->setEncoding($value);
+ break;
+ case 'soapVersion':
+ case 'soap_version':
+ $this->setSoapVersion($value);
+ break;
+ case 'uri':
+ $this->setUri($value);
+ break;
+ case 'wsdl':
+ $this->setWsdl($value);
+ break;
+ case 'featues':
+ trigger_error(__METHOD__ . ': the option "featues" is deprecated as of 1.10.x and will be removed with 2.0.0; use "features" instead', E_USER_NOTICE);
+ case 'features':
+ $this->setSoapFeatures($value);
+ break;
+ case 'cache_wsdl':
+ $this->setWsdlCache($value);
+ break;
+ case 'wsi_compliant':
+ $this->setWsiCompliant($value);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return array of options suitable for using with SoapServer constructor
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ $options = array();
+ if (null !== $this->_actor) {
+ $options['actor'] = $this->_actor;
+ }
+
+ if (null !== $this->_classmap) {
+ $options['classmap'] = $this->_classmap;
+ }
+
+ if (null !== $this->_encoding) {
+ $options['encoding'] = $this->_encoding;
+ }
+
+ if (null !== $this->_soapVersion) {
+ $options['soap_version'] = $this->_soapVersion;
+ }
+
+ if (null !== $this->_uri) {
+ $options['uri'] = $this->_uri;
+ }
+
+ if (null !== $this->_features) {
+ $options['features'] = $this->_features;
+ }
+
+ if (null !== $this->_wsdlCache) {
+ $options['cache_wsdl'] = $this->_wsdlCache;
+ }
+
+ if (null !== $this->_wsiCompliant) {
+ $options['wsi_compliant'] = $this->_wsiCompliant;
+ }
+
+ return $options;
+ }
+ /**
+ * Set WS-I compliant
+ *
+ * @param boolean $value
+ * @return Zend_Soap_Server
+ */
+ public function setWsiCompliant($value)
+ {
+ if (is_bool($value)) {
+ $this->_wsiCompliant = $value;
+ }
+ return $this;
+ }
+ /**
+ * Gt WS-I compliant
+ *
+ * @return boolean
+ */
+ public function getWsiCompliant()
+ {
+ return $this->_wsiCompliant;
+ }
+ /**
+ * Set encoding
+ *
+ * @param string $encoding
+ * @return Zend_Soap_Server
+ * @throws Zend_Soap_Server_Exception with invalid encoding argument
+ */
+ public function setEncoding($encoding)
+ {
+ if (!is_string($encoding)) {
+ throw new Zend_Soap_Server_Exception('Invalid encoding specified');
+ }
+
+ $this->_encoding = $encoding;
+ return $this;
+ }
+
+ /**
+ * Get encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Set SOAP version
+ *
+ * @param int $version One of the SOAP_1_1 or SOAP_1_2 constants
+ * @return Zend_Soap_Server
+ * @throws Zend_Soap_Server_Exception with invalid soap version argument
+ */
+ public function setSoapVersion($version)
+ {
+ if (!in_array($version, array(SOAP_1_1, SOAP_1_2))) {
+ throw new Zend_Soap_Server_Exception('Invalid soap version specified');
+ }
+
+ $this->_soapVersion = $version;
+ return $this;
+ }
+
+ /**
+ * Get SOAP version
+ *
+ * @return int
+ */
+ public function getSoapVersion()
+ {
+ return $this->_soapVersion;
+ }
+
+ /**
+ * Check for valid URN
+ *
+ * @param string $urn
+ * @return true
+ * @throws Zend_Soap_Server_Exception on invalid URN
+ */
+ public function validateUrn($urn)
+ {
+ $scheme = parse_url($urn, PHP_URL_SCHEME);
+ if ($scheme === false || $scheme === null) {
+ throw new Zend_Soap_Server_Exception('Invalid URN');
+ }
+
+ return true;
+ }
+
+ /**
+ * Set actor
+ *
+ * Actor is the actor URI for the server.
+ *
+ * @param string $actor
+ * @return Zend_Soap_Server
+ */
+ public function setActor($actor)
+ {
+ $this->validateUrn($actor);
+ $this->_actor = $actor;
+ return $this;
+ }
+
+ /**
+ * Retrieve actor
+ *
+ * @return string
+ */
+ public function getActor()
+ {
+ return $this->_actor;
+ }
+
+ /**
+ * Set URI
+ *
+ * URI in SoapServer is actually the target namespace, not a URI; $uri must begin with 'urn:'.
+ *
+ * @param string $uri
+ * @return Zend_Soap_Server
+ * @throws Zend_Soap_Server_Exception with invalid uri argument
+ */
+ public function setUri($uri)
+ {
+ $this->validateUrn($uri);
+ $this->_uri = $uri;
+ return $this;
+ }
+
+ /**
+ * Retrieve URI
+ *
+ * @return string
+ */
+ public function getUri()
+ {
+ return $this->_uri;
+ }
+
+ /**
+ * Set classmap
+ *
+ * @param array $classmap
+ * @return Zend_Soap_Server
+ * @throws Zend_Soap_Server_Exception for any invalid class in the class map
+ */
+ public function setClassmap($classmap)
+ {
+ if (!is_array($classmap)) {
+ /**
+ * @see Zend_Soap_Server_Exception
+ */
+ throw new Zend_Soap_Server_Exception('Classmap must be an array');
+ }
+ foreach ($classmap as $type => $class) {
+ if (!class_exists($class)) {
+ /**
+ * @see Zend_Soap_Server_Exception
+ */
+ throw new Zend_Soap_Server_Exception('Invalid class in class map');
+ }
+ }
+
+ $this->_classmap = $classmap;
+ return $this;
+ }
+
+ /**
+ * Retrieve classmap
+ *
+ * @return mixed
+ */
+ public function getClassmap()
+ {
+ return $this->_classmap;
+ }
+
+ /**
+ * Set wsdl
+ *
+ * @param string $wsdl URI or path to a WSDL
+ * @return Zend_Soap_Server
+ */
+ public function setWsdl($wsdl)
+ {
+ $this->_wsdl = $wsdl;
+ return $this;
+ }
+
+ /**
+ * Retrieve wsdl
+ *
+ * @return string
+ */
+ public function getWsdl()
+ {
+ return $this->_wsdl;
+ }
+
+ /**
+ * Set the SOAP Feature options.
+ *
+ * @param string|int $feature
+ * @return Zend_Soap_Server
+ */
+ public function setSoapFeatures($feature)
+ {
+ $this->_features = $feature;
+ return $this;
+ }
+
+ /**
+ * Return current SOAP Features options
+ *
+ * @return int
+ */
+ public function getSoapFeatures()
+ {
+ return $this->_features;
+ }
+
+ /**
+ * Set the SOAP Wsdl Caching Options
+ *
+ * @param string|int|boolean $caching
+ * @return Zend_Soap_Server
+ */
+ public function setWsdlCache($options)
+ {
+ $this->_wsdlCache = $options;
+ return $this;
+ }
+
+ /**
+ * Get current SOAP Wsdl Caching option
+ */
+ public function getWsdlCache()
+ {
+ return $this->_wsdlCache;
+ }
+
+ /**
+ * Attach a function as a server method
+ *
+ * @param array|string $function Function name, array of function names to attach,
+ * or SOAP_FUNCTIONS_ALL to attach all functions
+ * @param string $namespace Ignored
+ * @return Zend_Soap_Server
+ * @throws Zend_Soap_Server_Exception on invalid functions
+ */
+ public function addFunction($function, $namespace = '')
+ {
+ // Bail early if set to SOAP_FUNCTIONS_ALL
+ if ($this->_functions == SOAP_FUNCTIONS_ALL) {
+ return $this;
+ }
+
+ if (is_array($function)) {
+ foreach ($function as $func) {
+ if (is_string($func) && function_exists($func)) {
+ $this->_functions[] = $func;
+ } else {
+ throw new Zend_Soap_Server_Exception('One or more invalid functions specified in array');
+ }
+ }
+ $this->_functions = array_merge($this->_functions, $function);
+ } elseif (is_string($function) && function_exists($function)) {
+ $this->_functions[] = $function;
+ } elseif ($function == SOAP_FUNCTIONS_ALL) {
+ $this->_functions = SOAP_FUNCTIONS_ALL;
+ } else {
+ throw new Zend_Soap_Server_Exception('Invalid function specified');
+ }
+
+ if (is_array($this->_functions)) {
+ $this->_functions = array_unique($this->_functions);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Attach a class to a server
+ *
+ * Accepts a class name to use when handling requests. Any additional
+ * arguments will be passed to that class' constructor when instantiated.
+ *
+ * See {@link setObject()} to set preconfigured object instances as request handlers.
+ *
+ * @param string $class Class Name which executes SOAP Requests at endpoint.
+ * @return Zend_Soap_Server
+ * @throws Zend_Soap_Server_Exception if called more than once, or if class
+ * does not exist
+ */
+ public function setClass($class, $namespace = '', $argv = null)
+ {
+ if (isset($this->_class)) {
+ throw new Zend_Soap_Server_Exception('A class has already been registered with this soap server instance');
+ }
+
+ if (!is_string($class)) {
+ throw new Zend_Soap_Server_Exception('Invalid class argument (' . gettype($class) . ')');
+ }
+
+ if (!class_exists($class)) {
+ throw new Zend_Soap_Server_Exception('Class "' . $class . '" does not exist');
+ }
+
+ $this->_class = $class;
+ if (1 < func_num_args()) {
+ $argv = func_get_args();
+ array_shift($argv);
+ $this->_classArgs = $argv;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Attach an object to a server
+ *
+ * Accepts an instanciated object to use when handling requests.
+ *
+ * @param object $object
+ * @return Zend_Soap_Server
+ */
+ public function setObject($object)
+ {
+ if(!is_object($object)) {
+ throw new Zend_Soap_Server_Exception('Invalid object argument ('.gettype($object).')');
+ }
+
+ if(isset($this->_object)) {
+ throw new Zend_Soap_Server_Exception('An object has already been registered with this soap server instance');
+ }
+
+ if ($this->_wsiCompliant) {
+ $this->_object = new Zend_Soap_Server_Proxy($object);
+ } else {
+ $this->_object = $object;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return a server definition array
+ *
+ * Returns a list of all functions registered with {@link addFunction()},
+ * merged with all public methods of the class set with {@link setClass()}
+ * (if any).
+ *
+ * @access public
+ * @return array
+ */
+ public function getFunctions()
+ {
+ $functions = array();
+ if (null !== $this->_class) {
+ $functions = get_class_methods($this->_class);
+ } elseif (null !== $this->_object) {
+ $functions = get_class_methods($this->_object);
+ }
+
+ return array_merge((array) $this->_functions, $functions);
+ }
+
+ /**
+ * Unimplemented: Load server definition
+ *
+ * @param array $array
+ * @return void
+ * @throws Zend_Soap_Server_Exception Unimplemented
+ */
+ public function loadFunctions($definition)
+ {
+ throw new Zend_Soap_Server_Exception('Unimplemented');
+ }
+
+ /**
+ * Set server persistence
+ *
+ * @param int $mode
+ * @return Zend_Soap_Server
+ */
+ public function setPersistence($mode)
+ {
+ if (!in_array($mode, array(SOAP_PERSISTENCE_SESSION, SOAP_PERSISTENCE_REQUEST))) {
+ throw new Zend_Soap_Server_Exception('Invalid persistence mode specified');
+ }
+
+ $this->_persistence = $mode;
+ return $this;
+ }
+
+ /**
+ * Get server persistence
+ *
+ * @return Zend_Soap_Server
+ */
+ public function getPersistence()
+ {
+ return $this->_persistence;
+ }
+
+ /**
+ * Set request
+ *
+ * $request may be any of:
+ * - DOMDocument; if so, then cast to XML
+ * - DOMNode; if so, then grab owner document and cast to XML
+ * - SimpleXMLElement; if so, then cast to XML
+ * - stdClass; if so, calls __toString() and verifies XML
+ * - string; if so, verifies XML
+ *
+ * @param DOMDocument|DOMNode|SimpleXMLElement|stdClass|string $request
+ * @return Zend_Soap_Server
+ */
+ protected function _setRequest($request)
+ {
+ if ($request instanceof DOMDocument) {
+ $xml = $request->saveXML();
+ } elseif ($request instanceof DOMNode) {
+ $xml = $request->ownerDocument->saveXML();
+ } elseif ($request instanceof SimpleXMLElement) {
+ $xml = $request->asXML();
+ } elseif (is_object($request) || is_string($request)) {
+ if (is_object($request)) {
+ $xml = $request->__toString();
+ } else {
+ $xml = $request;
+ }
+
+ $dom = new DOMDocument();
+ try {
+ if(strlen($xml) == 0 || (!$dom = Zend_Xml_Security::scan($xml, $dom))) {
+ throw new Zend_Soap_Server_Exception('Invalid XML');
+ }
+ } catch (Zend_Xml_Exception $e) {
+ throw new Zend_Soap_Server_Exception(
+ $e->getMessage()
+ );
+ }
+ }
+ $this->_request = $xml;
+ return $this;
+ }
+
+ /**
+ * Retrieve request XML
+ *
+ * @return string
+ */
+ public function getLastRequest()
+ {
+ return $this->_request;
+ }
+
+ /**
+ * Set return response flag
+ *
+ * If true, {@link handle()} will return the response instead of
+ * automatically sending it back to the requesting client.
+ *
+ * The response is always available via {@link getResponse()}.
+ *
+ * @param boolean $flag
+ * @return Zend_Soap_Server
+ */
+ public function setReturnResponse($flag)
+ {
+ $this->_returnResponse = ($flag) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Retrieve return response flag
+ *
+ * @return boolean
+ */
+ public function getReturnResponse()
+ {
+ return $this->_returnResponse;
+ }
+
+ /**
+ * Get response XML
+ *
+ * @return string
+ */
+ public function getLastResponse()
+ {
+ return $this->_response;
+ }
+
+ /**
+ * Get SoapServer object
+ *
+ * Uses {@link $_wsdl} and return value of {@link getOptions()} to instantiate
+ * SoapServer object, and then registers any functions or class with it, as
+ * well as peristence.
+ *
+ * @return SoapServer
+ */
+ protected function _getSoap()
+ {
+ $options = $this->getOptions();
+ $server = new SoapServer($this->_wsdl, $options);
+
+ if (!empty($this->_functions)) {
+ $server->addFunction($this->_functions);
+ }
+
+ if (!empty($this->_class)) {
+ $args = $this->_classArgs;
+ array_unshift($args, $this->_class);
+ if ($this->_wsiCompliant) {
+ array_unshift($args, 'Zend_Soap_Server_Proxy');
+ }
+ call_user_func_array(array($server, 'setClass'), $args);
+ }
+
+ if (!empty($this->_object)) {
+ $server->setObject($this->_object);
+ }
+
+ if (null !== $this->_persistence) {
+ $server->setPersistence($this->_persistence);
+ }
+
+ return $server;
+ }
+
+ /**
+ * Handle a request
+ *
+ * Instantiates SoapServer object with options set in object, and
+ * dispatches its handle() method.
+ *
+ * $request may be any of:
+ * - DOMDocument; if so, then cast to XML
+ * - DOMNode; if so, then grab owner document and cast to XML
+ * - SimpleXMLElement; if so, then cast to XML
+ * - stdClass; if so, calls __toString() and verifies XML
+ * - string; if so, verifies XML
+ *
+ * If no request is passed, pulls request using php:://input (for
+ * cross-platform compatability purposes).
+ *
+ * @param DOMDocument|DOMNode|SimpleXMLElement|stdClass|string $request Optional request
+ * @return void|string
+ */
+ public function handle($request = null)
+ {
+ if (null === $request) {
+ $request = file_get_contents('php://input');
+ }
+
+ // Set Zend_Soap_Server error handler
+ $displayErrorsOriginalState = $this->_initializeSoapErrorContext();
+
+ $setRequestException = null;
+ /**
+ * @see Zend_Soap_Server_Exception
+ */
+ try {
+ $this->_setRequest($request);
+ } catch (Zend_Soap_Server_Exception $e) {
+ $setRequestException = $e;
+ }
+
+ $soap = $this->_getSoap();
+
+ $fault = false;
+ ob_start();
+ if ($setRequestException instanceof Exception) {
+ // Create SOAP fault message if we've caught a request exception
+ $fault = $this->fault($setRequestException->getMessage(), 'Sender');
+ } else {
+ try {
+ $soap->handle($this->_request);
+ } catch (Exception $e) {
+ $fault = $this->fault($e);
+ }
+ }
+ $this->_response = ob_get_clean();
+
+ // Restore original error handler
+ restore_error_handler();
+ ini_set('display_errors', $displayErrorsOriginalState);
+
+ // Send a fault, if we have one
+ if ($fault) {
+ $soap->fault($fault->faultcode, $fault->faultstring);
+ }
+
+ if (!$this->_returnResponse) {
+ echo $this->_response;
+ return;
+ }
+
+ return $this->_response;
+ }
+
+ /**
+ * Method initalizes the error context that the SOAPServer enviroment will run in.
+ *
+ * @return boolean display_errors original value
+ */
+ protected function _initializeSoapErrorContext()
+ {
+ $displayErrorsOriginalState = ini_get('display_errors');
+ ini_set('display_errors', false);
+ set_error_handler(array($this, 'handlePhpErrors'), E_USER_ERROR);
+ return $displayErrorsOriginalState;
+ }
+
+ /**
+ * Register a valid fault exception
+ *
+ * @param string|array $class Exception class or array of exception classes
+ * @return Zend_Soap_Server
+ */
+ public function registerFaultException($class)
+ {
+ $this->_faultExceptions = array_merge($this->_faultExceptions, (array) $class);
+ return $this;
+ }
+
+ /**
+ * Deregister a fault exception from the fault exception stack
+ *
+ * @param string $class
+ * @return boolean
+ */
+ public function deregisterFaultException($class)
+ {
+ if (in_array($class, $this->_faultExceptions, true)) {
+ $index = array_search($class, $this->_faultExceptions);
+ unset($this->_faultExceptions[$index]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return fault exceptions list
+ *
+ * @return array
+ */
+ public function getFaultExceptions()
+ {
+ return $this->_faultExceptions;
+ }
+
+ /**
+ * Generate a server fault
+ *
+ * Note that the arguments are reverse to those of SoapFault.
+ *
+ * If an exception is passed as the first argument, its message and code
+ * will be used to create the fault object if it has been registered via
+ * {@Link registerFaultException()}.
+ *
+ * @link http://www.w3.org/TR/soap12-part1/#faultcodes
+ * @param string|Exception $fault
+ * @param string $code SOAP Fault Codes
+ * @return SoapFault
+ */
+ public function fault($fault = null, $code = "Receiver")
+ {
+ if ($fault instanceof Exception) {
+ $class = get_class($fault);
+ if (in_array($class, $this->_faultExceptions)) {
+ $message = $fault->getMessage();
+ $eCode = $fault->getCode();
+ $code = empty($eCode) ? $code : $eCode;
+ } else {
+ $message = 'Unknown error';
+ }
+ } elseif(is_string($fault)) {
+ $message = $fault;
+ } else {
+ $message = 'Unknown error';
+ }
+
+ $allowedFaultModes = array(
+ 'VersionMismatch', 'MustUnderstand', 'DataEncodingUnknown',
+ 'Sender', 'Receiver', 'Server'
+ );
+ if(!in_array($code, $allowedFaultModes)) {
+ $code = "Receiver";
+ }
+
+ return new SoapFault($code, $message);
+ }
+
+ /**
+ * Throw PHP errors as SoapFaults
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param int $errline
+ * @param array $errcontext
+ * @return void
+ * @throws SoapFault
+ */
+ public function handlePhpErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null)
+ {
+ throw $this->fault($errstr, "Receiver");
+ }
+}
diff --git a/library/vendor/Zend/Soap/Server/Exception.php b/library/vendor/Zend/Soap/Server/Exception.php
new file mode 100644
index 0000000..29e4554
--- /dev/null
+++ b/library/vendor/Zend/Soap/Server/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/** Zend_Exception */
+
+
+/**
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Server
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+class Zend_Soap_Server_Exception extends Zend_Exception
+{}
+
diff --git a/library/vendor/Zend/Soap/Server/Proxy.php b/library/vendor/Zend/Soap/Server/Proxy.php
new file mode 100644
index 0000000..addecd1
--- /dev/null
+++ b/library/vendor/Zend/Soap/Server/Proxy.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage AutoDiscover
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id:$
+ */
+
+class Zend_Soap_Server_Proxy
+{
+ /**
+ * @var object
+ */
+ protected $_classInstance;
+ /**
+ * @var string
+ */
+ protected $_className;
+ /**
+ * Constructor
+ *
+ * @param object $service
+ */
+ public function __construct($className, $classArgs = array())
+ {
+ $class = new ReflectionClass($className);
+ $constructor = $class->getConstructor();
+ if ($constructor === null) {
+ $this->_classInstance = $class->newInstance();
+ } else {
+ $this->_classInstance = $class->newInstanceArgs(array_values($classArgs));
+ }
+ $this->_className = $className;
+ }
+ /**
+ * Proxy for the WS-I compliant call
+ *
+ * @param string $name
+ * @param string $arguments
+ * @return array
+ */
+ public function __call($name, $arguments)
+ {
+ $result = call_user_func_array(array($this->_classInstance, $name), $this->_preProcessArguments($arguments));
+ return array("{$name}Result"=>$result);
+ }
+ /**
+ * Pre process arguments
+ *
+ * @param mixed $arguments
+ * @return array
+ */
+ protected function _preProcessArguments($arguments)
+ {
+ if (count($arguments) == 1 && is_object($arguments[0])) {
+ return get_object_vars($arguments[0]);
+ } else {
+ return $arguments;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl.php b/library/vendor/Zend/Soap/Wsdl.php
new file mode 100644
index 0000000..95f2c20
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl.php
@@ -0,0 +1,661 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_Interface
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_Abstract
+ */
+
+/** @see Zend_Xml_Security */
+
+/**
+ * Zend_Soap_Wsdl
+ *
+ * @category Zend
+ * @package Zend_Soap
+ */
+class Zend_Soap_Wsdl
+{
+ /**
+ * @var object DomDocument Instance
+ */
+ private $_dom;
+
+ /**
+ * @var object WSDL Root XML_Tree_Node
+ */
+ private $_wsdl;
+
+ /**
+ * @var string URI where the WSDL will be available
+ */
+ private $_uri;
+
+ /**
+ * @var DOMElement
+ */
+ private $_schema = null;
+
+ /**
+ * Types defined on schema
+ *
+ * @var array
+ */
+ private $_includedTypes = array();
+
+ /**
+ * Strategy for detection of complex types
+ */
+ protected $_strategy = null;
+
+
+ /**
+ * Constructor
+ *
+ * @param string $name Name of the Web Service being Described
+ * @param string $uri URI where the WSDL will be available
+ * @param boolean|string|Zend_Soap_Wsdl_Strategy_Interface $strategy
+ */
+ public function __construct($name, $uri, $strategy = true)
+ {
+ if ($uri instanceof Zend_Uri_Http) {
+ $uri = $uri->getUri();
+ }
+ $this->_uri = $uri;
+
+ /**
+ * @todo change DomDocument object creation from cparsing to construxting using API
+ * It also should authomatically escape $name and $uri values if necessary
+ */
+ $wsdl = "<?xml version='1.0' ?>
+ <definitions name='$name' targetNamespace='$uri'
+ xmlns='http://schemas.xmlsoap.org/wsdl/'
+ xmlns:tns='$uri'
+ xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
+ xmlns:xsd='http://www.w3.org/2001/XMLSchema'
+ xmlns:soap-enc='http://schemas.xmlsoap.org/soap/encoding/'
+ xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'></definitions>";
+ $this->_dom = new DOMDocument();
+ if (!$this->_dom = Zend_Xml_Security::scan($wsdl, $this->_dom)) {
+ throw new Zend_Server_Exception('Unable to create DomDocument');
+ }
+ $this->_wsdl = $this->_dom->documentElement;
+
+ $this->setComplexTypeStrategy($strategy);
+ }
+
+ /**
+ * Set a new uri for this WSDL
+ *
+ * @param string|Zend_Uri_Http $uri
+ * @return Zend_Server_Wsdl
+ */
+ public function setUri($uri)
+ {
+ if ($uri instanceof Zend_Uri_Http) {
+ $uri = $uri->getUri();
+ }
+ $oldUri = $this->_uri;
+ $this->_uri = $uri;
+
+ if($this->_dom !== null) {
+ // @todo: This is the worst hack ever, but its needed due to design and non BC issues of WSDL generation
+ $xml = $this->_dom->saveXML();
+ $xml = str_replace($oldUri, $uri, $xml);
+ $this->_dom = new DOMDocument();
+ $this->_dom = Zend_Xml_Security::scan($xml, $this->_dom);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a strategy for complex type detection and handling
+ *
+ * @todo Boolean is for backwards compability with extractComplexType object var. Remove it in later versions.
+ * @param boolean|string|Zend_Soap_Wsdl_Strategy_Interface $strategy
+ * @return Zend_Soap_Wsdl
+ */
+ public function setComplexTypeStrategy($strategy)
+ {
+ if($strategy === true) {
+ $strategy = new Zend_Soap_Wsdl_Strategy_DefaultComplexType();
+ } else if($strategy === false) {
+ $strategy = new Zend_Soap_Wsdl_Strategy_AnyType();
+ } else if(is_string($strategy)) {
+ if(class_exists($strategy)) {
+ $strategy = new $strategy();
+ } else {
+ throw new Zend_Soap_Wsdl_Exception(
+ sprintf("Strategy with name '%s does not exist.", $strategy
+ ));
+ }
+ }
+
+ if(!($strategy instanceof Zend_Soap_Wsdl_Strategy_Interface)) {
+ throw new Zend_Soap_Wsdl_Exception("Set a strategy that is not of type 'Zend_Soap_Wsdl_Strategy_Interface'");
+ }
+ $this->_strategy = $strategy;
+ return $this;
+ }
+
+ /**
+ * Get the current complex type strategy
+ *
+ * @return Zend_Soap_Wsdl_Strategy_Interface
+ */
+ public function getComplexTypeStrategy()
+ {
+ return $this->_strategy;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_messages message} element to the WSDL
+ *
+ * @param string $name Name for the {@link http://www.w3.org/TR/wsdl#_messages message}
+ * @param array $parts An array of {@link http://www.w3.org/TR/wsdl#_message parts}
+ * The array is constructed like: 'name of part' => 'part xml schema data type'
+ * or 'name of part' => array('type' => 'part xml schema type')
+ * or 'name of part' => array('element' => 'part xml element name')
+ * @return object The new message's XML_Tree_Node for use in {@link function addDocumentation}
+ */
+ public function addMessage($name, $parts)
+ {
+ $message = $this->_dom->createElement('message');
+
+ $message->setAttribute('name', $name);
+
+ if (sizeof($parts) > 0) {
+ foreach ($parts as $name => $type) {
+ $part = $this->_dom->createElement('part');
+ $part->setAttribute('name', $name);
+ if (is_array($type)) {
+ foreach ($type as $key => $value) {
+ $part->setAttribute($key, $value);
+ }
+ } else {
+ $part->setAttribute('type', $type);
+ }
+ $message->appendChild($part);
+ }
+ }
+
+ $this->_wsdl->appendChild($message);
+
+ return $message;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_porttypes portType} element to the WSDL
+ *
+ * @param string $name portType element's name
+ * @return object The new portType's XML_Tree_Node for use in {@link function addPortOperation} and {@link function addDocumentation}
+ */
+ public function addPortType($name)
+ {
+ $portType = $this->_dom->createElement('portType');
+ $portType->setAttribute('name', $name);
+ $this->_wsdl->appendChild($portType);
+
+ return $portType;
+ }
+
+ /**
+ * Add an {@link http://www.w3.org/TR/wsdl#_request-response operation} element to a portType element
+ *
+ * @param object $portType a portType XML_Tree_Node, from {@link function addPortType}
+ * @param string $name Operation name
+ * @param string $input Input Message
+ * @param string $output Output Message
+ * @param string $fault Fault Message
+ * @return object The new operation's XML_Tree_Node for use in {@link function addDocumentation}
+ */
+ public function addPortOperation($portType, $name, $input = false, $output = false, $fault = false)
+ {
+ $operation = $this->_dom->createElement('operation');
+ $operation->setAttribute('name', $name);
+
+ if (is_string($input) && (strlen(trim($input)) >= 1)) {
+ $node = $this->_dom->createElement('input');
+ $node->setAttribute('message', $input);
+ $operation->appendChild($node);
+ }
+ if (is_string($output) && (strlen(trim($output)) >= 1)) {
+ $node= $this->_dom->createElement('output');
+ $node->setAttribute('message', $output);
+ $operation->appendChild($node);
+ }
+ if (is_string($fault) && (strlen(trim($fault)) >= 1)) {
+ $node = $this->_dom->createElement('fault');
+ $node->setAttribute('message', $fault);
+ $operation->appendChild($node);
+ }
+
+ $portType->appendChild($operation);
+
+ return $operation;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_bindings binding} element to WSDL
+ *
+ * @param string $name Name of the Binding
+ * @param string $type name of the portType to bind
+ * @return object The new binding's XML_Tree_Node for use with {@link function addBindingOperation} and {@link function addDocumentation}
+ */
+ public function addBinding($name, $portType)
+ {
+ $binding = $this->_dom->createElement('binding');
+ $binding->setAttribute('name', $name);
+ $binding->setAttribute('type', $portType);
+
+ $this->_wsdl->appendChild($binding);
+
+ return $binding;
+ }
+
+ /**
+ * Add an operation to a binding element
+ *
+ * @param object $binding A binding XML_Tree_Node returned by {@link function addBinding}
+ * @param array $input An array of attributes for the input element, allowed keys are: 'use', 'namespace', 'encodingStyle'. {@link http://www.w3.org/TR/wsdl#_soap:body More Information}
+ * @param array $output An array of attributes for the output element, allowed keys are: 'use', 'namespace', 'encodingStyle'. {@link http://www.w3.org/TR/wsdl#_soap:body More Information}
+ * @param array $fault An array of attributes for the fault element, allowed keys are: 'name', 'use', 'namespace', 'encodingStyle'. {@link http://www.w3.org/TR/wsdl#_soap:body More Information}
+ * @return object The new Operation's XML_Tree_Node for use with {@link function addSoapOperation} and {@link function addDocumentation}
+ */
+ public function addBindingOperation($binding, $name, $input = false, $output = false, $fault = false)
+ {
+ $operation = $this->_dom->createElement('operation');
+ $operation->setAttribute('name', $name);
+
+ if (is_array($input)) {
+ $node = $this->_dom->createElement('input');
+ $soap_node = $this->_dom->createElement('soap:body');
+ foreach ($input as $name => $value) {
+ $soap_node->setAttribute($name, $value);
+ }
+ $node->appendChild($soap_node);
+ $operation->appendChild($node);
+ }
+
+ if (is_array($output)) {
+ $node = $this->_dom->createElement('output');
+ $soap_node = $this->_dom->createElement('soap:body');
+ foreach ($output as $name => $value) {
+ $soap_node->setAttribute($name, $value);
+ }
+ $node->appendChild($soap_node);
+ $operation->appendChild($node);
+ }
+
+ if (is_array($fault)) {
+ $node = $this->_dom->createElement('fault');
+ /**
+ * Note. Do we really need name attribute to be also set at wsdl:fault node???
+ * W3C standard doesn't mention it (http://www.w3.org/TR/wsdl#_soap:fault)
+ * But some real world WSDLs use it, so it may be required for compatibility reasons.
+ */
+ if (isset($fault['name'])) {
+ $node->setAttribute('name', $fault['name']);
+ }
+
+ $soap_node = $this->_dom->createElement('soap:fault');
+ foreach ($fault as $name => $value) {
+ $soap_node->setAttribute($name, $value);
+ }
+ $node->appendChild($soap_node);
+ $operation->appendChild($node);
+ }
+
+ $binding->appendChild($operation);
+
+ return $operation;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_soap:binding SOAP binding} element to a Binding element
+ *
+ * @param object $binding A binding XML_Tree_Node returned by {@link function addBinding}
+ * @param string $style binding style, possible values are "rpc" (the default) and "document"
+ * @param string $transport Transport method (defaults to HTTP)
+ * @return boolean
+ */
+ public function addSoapBinding($binding, $style = 'document', $transport = 'http://schemas.xmlsoap.org/soap/http')
+ {
+ $soap_binding = $this->_dom->createElement('soap:binding');
+ $soap_binding->setAttribute('style', $style);
+ $soap_binding->setAttribute('transport', $transport);
+
+ $binding->appendChild($soap_binding);
+
+ return $soap_binding;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_soap:operation SOAP operation} to an operation element
+ *
+ * @param object $operation An operation XML_Tree_Node returned by {@link function addBindingOperation}
+ * @param string $soap_action SOAP Action
+ * @return boolean
+ */
+ public function addSoapOperation($binding, $soap_action)
+ {
+ if ($soap_action instanceof Zend_Uri_Http) {
+ $soap_action = $soap_action->getUri();
+ }
+ $soap_operation = $this->_dom->createElement('soap:operation');
+ $soap_operation->setAttribute('soapAction', $soap_action);
+
+ $binding->insertBefore($soap_operation, $binding->firstChild);
+
+ return $soap_operation;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_services service} element to the WSDL
+ *
+ * @param string $name Service Name
+ * @param string $port_name Name of the port for the service
+ * @param string $binding Binding for the port
+ * @param string $location SOAP Address for the service
+ * @return object The new service's XML_Tree_Node for use with {@link function addDocumentation}
+ */
+ public function addService($name, $port_name, $binding, $location)
+ {
+ if ($location instanceof Zend_Uri_Http) {
+ $location = $location->getUri();
+ }
+ $service = $this->_dom->createElement('service');
+ $service->setAttribute('name', $name);
+
+ $port = $this->_dom->createElement('port');
+ $port->setAttribute('name', $port_name);
+ $port->setAttribute('binding', $binding);
+
+ $soap_address = $this->_dom->createElement('soap:address');
+ $soap_address->setAttribute('location', $location);
+
+ $port->appendChild($soap_address);
+ $service->appendChild($port);
+
+ $this->_wsdl->appendChild($service);
+
+ return $service;
+ }
+
+ /**
+ * Add a documentation element to any element in the WSDL.
+ *
+ * Note that the WSDL {@link http://www.w3.org/TR/wsdl#_documentation specification} uses 'document',
+ * but the WSDL {@link http://schemas.xmlsoap.org/wsdl/ schema} uses 'documentation' instead.
+ * The {@link http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html#WSDL_documentation_Element WS-I Basic Profile 1.1} recommends using 'documentation'.
+ *
+ * @param object $input_node An XML_Tree_Node returned by another method to add the documentation to
+ * @param string $documentation Human readable documentation for the node
+ * @return DOMElement The documentation element
+ */
+ public function addDocumentation($input_node, $documentation)
+ {
+ if ($input_node === $this) {
+ $node = $this->_dom->documentElement;
+ } else {
+ $node = $input_node;
+ }
+
+ $doc = $this->_dom->createElement('documentation');
+ $doc_cdata = $this->_dom->createTextNode(str_replace(array("\r\n", "\r"), "\n", $documentation));
+ $doc->appendChild($doc_cdata);
+
+ if($node->hasChildNodes()) {
+ $node->insertBefore($doc, $node->firstChild);
+ } else {
+ $node->appendChild($doc);
+ }
+
+ return $doc;
+ }
+
+ /**
+ * Add WSDL Types element
+ *
+ * @param object $types A DomDocument|DomNode|DomElement|DomDocumentFragment with all the XML Schema types defined in it
+ */
+ public function addTypes($types)
+ {
+ if ($types instanceof DomDocument) {
+ $dom = $this->_dom->importNode($types->documentElement);
+ $this->_wsdl->appendChild($types->documentElement);
+ } elseif ($types instanceof DomNode || $types instanceof DomElement || $types instanceof DomDocumentFragment ) {
+ $dom = $this->_dom->importNode($types);
+ $this->_wsdl->appendChild($dom);
+ }
+ }
+
+ /**
+ * Add a complex type name that is part of this WSDL and can be used in signatures.
+ *
+ * @param string $type
+ * @return Zend_Soap_Wsdl
+ */
+ public function addType($type)
+ {
+ if(!in_array($type, $this->_includedTypes)) {
+ $this->_includedTypes[] = $type;
+ }
+ return $this;
+ }
+
+ /**
+ * Return an array of all currently included complex types
+ *
+ * @return array
+ */
+ public function getTypes()
+ {
+ return $this->_includedTypes;
+ }
+
+ /**
+ * Return the Schema node of the WSDL
+ *
+ * @return DOMElement
+ */
+ public function getSchema()
+ {
+ if($this->_schema == null) {
+ $this->addSchemaTypeSection();
+ }
+
+ return $this->_schema;
+ }
+
+ /**
+ * Return the WSDL as XML
+ *
+ * @return string WSDL as XML
+ */
+ public function toXML()
+ {
+ return $this->_dom->saveXML();
+ }
+
+ /**
+ * Return DOM Document
+ *
+ * @return object DomDocum ent
+ */
+ public function toDomDocument()
+ {
+ return $this->_dom;
+ }
+
+ /**
+ * Echo the WSDL as XML
+ *
+ * @return boolean
+ */
+ public function dump($filename = false)
+ {
+ if (!$filename) {
+ echo $this->toXML();
+ return true;
+ } else {
+ return file_put_contents($filename, $this->toXML());
+ }
+ }
+
+ /**
+ * Returns an XSD Type for the given PHP type
+ *
+ * @param string $type PHP Type to get the XSD type for
+ * @return string
+ */
+ public function getType($type)
+ {
+ switch (strtolower($type)) {
+ case 'string':
+ case 'str':
+ return 'xsd:string';
+ case 'long':
+ return 'xsd:long';
+ case 'int':
+ case 'integer':
+ return 'xsd:int';
+ case 'float':
+ return 'xsd:float';
+ case 'double':
+ return 'xsd:double';
+ case 'boolean':
+ case 'bool':
+ return 'xsd:boolean';
+ case 'array':
+ return 'soap-enc:Array';
+ case 'object':
+ return 'xsd:struct';
+ case 'mixed':
+ return 'xsd:anyType';
+ case 'void':
+ return '';
+ default:
+ // delegate retrieval of complex type to current strategy
+ return $this->addComplexType($type);
+ }
+ }
+
+ /**
+ * This function makes sure a complex types section and schema additions are set.
+ *
+ * @return Zend_Soap_Wsdl
+ */
+ public function addSchemaTypeSection()
+ {
+ if ($this->_schema === null) {
+ $this->_schema = $this->_dom->createElement('xsd:schema');
+ $this->_schema->setAttribute('targetNamespace', $this->_uri);
+ $types = $this->_dom->createElement('types');
+ $types->appendChild($this->_schema);
+ $this->_wsdl->appendChild($types);
+ }
+ return $this;
+ }
+
+ /**
+ * Add a {@link http://www.w3.org/TR/wsdl#_types types} data type definition
+ *
+ * @param string $type Name of the class to be specified
+ * @return string XSD Type for the given PHP type
+ */
+ public function addComplexType($type)
+ {
+ if (in_array($type, $this->getTypes())) {
+ return "tns:$type";
+ }
+ $this->addSchemaTypeSection();
+
+ $strategy = $this->getComplexTypeStrategy();
+ $strategy->setContext($this);
+ // delegates the detection of a complex type to the current strategy
+ return $strategy->addComplexType($type);
+ }
+
+ /**
+ * Parse an xsd:element represented as an array into a DOMElement.
+ *
+ * @param array $element an xsd:element represented as an array
+ * @return DOMElement parsed element
+ */
+ private function _parseElement($element)
+ {
+ if (!is_array($element)) {
+ throw new Zend_Soap_Wsdl_Exception("The 'element' parameter needs to be an associative array.");
+ }
+
+ $elementXml = $this->_dom->createElement('xsd:element');
+ foreach ($element as $key => $value) {
+ if (in_array($key, array('sequence', 'all', 'choice'))) {
+ if (is_array($value)) {
+ $complexType = $this->_dom->createElement('xsd:complexType');
+ if (count($value) > 0) {
+ $container = $this->_dom->createElement('xsd:' . $key);
+ foreach ($value as $subelement) {
+ $subelementXml = $this->_parseElement($subelement);
+ $container->appendChild($subelementXml);
+ }
+ $complexType->appendChild($container);
+ }
+ $elementXml->appendChild($complexType);
+ }
+ } else {
+ $elementXml->setAttribute($key, $value);
+ }
+ }
+ return $elementXml;
+ }
+
+ /**
+ * Add an xsd:element represented as an array to the schema.
+ *
+ * Array keys represent attribute names and values their respective value.
+ * The 'sequence', 'all' and 'choice' keys must have an array of elements as their value,
+ * to add them to a nested complexType.
+ *
+ * Example: array( 'name' => 'MyElement',
+ * 'sequence' => array( array('name' => 'myString', 'type' => 'string'),
+ * array('name' => 'myInteger', 'type' => 'int') ) );
+ * Resulting XML: <xsd:element name="MyElement"><xsd:complexType><xsd:sequence>
+ * <xsd:element name="myString" type="string"/>
+ * <xsd:element name="myInteger" type="int"/>
+ * </xsd:sequence></xsd:complexType></xsd:element>
+ *
+ * @param array $element an xsd:element represented as an array
+ * @return string xsd:element for the given element array
+ */
+ public function addElement($element)
+ {
+ $schema = $this->getSchema();
+ $elementXml = $this->_parseElement($element);
+ $schema->appendChild($elementXml);
+ return 'tns:' . $element['name'];
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Exception.php b/library/vendor/Zend/Soap/Wsdl/Exception.php
new file mode 100644
index 0000000..11397f0
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * Zend_Soap_Wsdl_Exception
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Wsdl_Exception extends Zend_Exception { }
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/Abstract.php b/library/vendor/Zend/Soap/Wsdl/Strategy/Abstract.php
new file mode 100644
index 0000000..35f198f
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/Abstract.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_Interface
+ */
+
+/**
+ * Abstract class for Zend_Soap_Wsdl_Strategy.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Soap_Wsdl_Strategy_Abstract implements Zend_Soap_Wsdl_Strategy_Interface
+{
+ /**
+ * Context object
+ *
+ * @var Zend_Soap_Wsdl
+ */
+ protected $_context;
+
+ /**
+ * Set the Zend_Soap_Wsdl Context object this strategy resides in.
+ *
+ * @param Zend_Soap_Wsdl $context
+ * @return void
+ */
+ public function setContext(Zend_Soap_Wsdl $context)
+ {
+ $this->_context = $context;
+ }
+
+ /**
+ * Return the current Zend_Soap_Wsdl context object
+ *
+ * @return Zend_Soap_Wsdl
+ */
+ public function getContext()
+ {
+ return $this->_context;
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/AnyType.php b/library/vendor/Zend/Soap/Wsdl/Strategy/AnyType.php
new file mode 100644
index 0000000..13f352e
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/AnyType.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_Interface
+ */
+
+/**
+ * Zend_Soap_Wsdl_Strategy_AnyType
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Wsdl_Strategy_AnyType implements Zend_Soap_Wsdl_Strategy_Interface
+{
+ /**
+ * Not needed in this strategy.
+ *
+ * @param Zend_Soap_Wsdl $context
+ */
+ public function setContext(Zend_Soap_Wsdl $context)
+ {
+
+ }
+
+ /**
+ * Returns xsd:anyType regardless of the input.
+ *
+ * @param string $type
+ * @return string
+ */
+ public function addComplexType($type)
+ {
+ return 'xsd:anyType';
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeComplex.php b/library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeComplex.php
new file mode 100644
index 0000000..a830840
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeComplex.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_DefaultComplexType
+ */
+
+/**
+ * Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex extends Zend_Soap_Wsdl_Strategy_DefaultComplexType
+{
+ protected $_inProcess = array();
+
+ /**
+ * Add an ArrayOfType based on the xsd:complexType syntax if type[] is detected in return value doc comment.
+ *
+ * @param string $type
+ * @return string tns:xsd-type
+ */
+ public function addComplexType($type)
+ {
+ if (in_array($type, $this->_inProcess)) {
+ return "tns:" . $type;
+ }
+ $this->_inProcess[$type] = $type;
+
+ $nestingLevel = $this->_getNestedCount($type);
+
+ if($nestingLevel > 1) {
+ throw new Zend_Soap_Wsdl_Exception(
+ "ArrayOfTypeComplex cannot return nested ArrayOfObject deeper than ".
+ "one level. Use array object properties to return deep nested data.
+ ");
+ }
+
+ $singularType = $this->_getSingularPhpType($type);
+
+ if(!class_exists($singularType)) {
+ throw new Zend_Soap_Wsdl_Exception(sprintf(
+ "Cannot add a complex type %s that is not an object or where ".
+ "class could not be found in 'DefaultComplexType' strategy.", $type
+ ));
+ }
+
+ if($nestingLevel == 1) {
+ // The following blocks define the Array of Object structure
+ $xsdComplexTypeName = $this->_addArrayOfComplexType($singularType, $type);
+ } else {
+ $xsdComplexTypeName = $singularType;
+ }
+
+ // The array for the objects has been created, now build the object definition:
+ if(!in_array($singularType, $this->getContext()->getTypes())) {
+ parent::addComplexType($singularType);
+ }
+
+ unset($this->_inProcess[$type]);
+ return "tns:".$xsdComplexTypeName;
+ }
+
+ protected function _addArrayOfComplexType($singularType, $type)
+ {
+ $dom = $this->getContext()->toDomDocument();
+
+ $xsdComplexTypeName = $this->_getXsdComplexTypeName($singularType);
+
+ if(!in_array($xsdComplexTypeName, $this->getContext()->getTypes())) {
+ $complexType = $dom->createElement('xsd:complexType');
+ $complexType->setAttribute('name', $xsdComplexTypeName);
+
+ $complexContent = $dom->createElement("xsd:complexContent");
+ $complexType->appendChild($complexContent);
+
+ $xsdRestriction = $dom->createElement("xsd:restriction");
+ $xsdRestriction->setAttribute('base', 'soap-enc:Array');
+ $complexContent->appendChild($xsdRestriction);
+
+ $xsdAttribute = $dom->createElement("xsd:attribute");
+ $xsdAttribute->setAttribute("ref", "soap-enc:arrayType");
+ $xsdAttribute->setAttribute("wsdl:arrayType", sprintf("tns:%s[]", $singularType));
+ $xsdRestriction->appendChild($xsdAttribute);
+
+ $this->getContext()->getSchema()->appendChild($complexType);
+ $this->getContext()->addType($xsdComplexTypeName);
+ }
+
+ return $xsdComplexTypeName;
+ }
+
+ protected function _getXsdComplexTypeName($type)
+ {
+ return sprintf('ArrayOf%s', $type);
+ }
+
+ /**
+ * From a nested definition with type[], get the singular PHP Type
+ *
+ * @param string $type
+ * @return string
+ */
+ protected function _getSingularPhpType($type)
+ {
+ return str_replace("[]", "", $type);
+ }
+
+ /**
+ * Return the array nesting level based on the type name
+ *
+ * @param string $type
+ * @return integer
+ */
+ protected function _getNestedCount($type)
+ {
+ return substr_count($type, "[]");
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeSequence.php b/library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeSequence.php
new file mode 100644
index 0000000..e82ea50
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/ArrayOfTypeSequence.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_DefaultComplexType
+ */
+
+/**
+ * Zend_Soap_Wsdl_Strategy_ArrayOfTypeSequence
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Wsdl_Strategy_ArrayOfTypeSequence extends Zend_Soap_Wsdl_Strategy_DefaultComplexType
+{
+ /**
+ * Add an unbounded ArrayOfType based on the xsd:sequence syntax if type[] is detected in return value doc comment.
+ *
+ * @param string $type
+ * @return string tns:xsd-type
+ */
+ public function addComplexType($type)
+ {
+ $nestedCounter = $this->_getNestedCount($type);
+
+ if($nestedCounter > 0) {
+ $singularType = $this->_getSingularType($type);
+
+ for($i = 1; $i <= $nestedCounter; $i++) {
+ $complexTypeName = substr($this->_getTypeNameBasedOnNestingLevel($singularType, $i), 4);
+ $childTypeName = $this->_getTypeNameBasedOnNestingLevel($singularType, $i-1);
+
+ $this->_addElementFromWsdlAndChildTypes($complexTypeName, $childTypeName);
+ }
+ // adding the PHP type which is resolved to a nested XSD type. therefore add only once.
+ $this->getContext()->addType($complexTypeName);
+
+ return "tns:$complexTypeName";
+ } else if (!in_array($type, $this->getContext()->getTypes())) {
+ // New singular complex type
+ return parent::addComplexType($type);
+ } else {
+ // Existing complex type
+ return $this->getContext()->getType($type);
+ }
+ }
+
+ /**
+ * Return the ArrayOf or simple type name based on the singular xsdtype and the nesting level
+ *
+ * @param string $singularType
+ * @param int $level
+ * @return string
+ */
+ protected function _getTypeNameBasedOnNestingLevel($singularType, $level)
+ {
+ if($level == 0) {
+ // This is not an Array anymore, return the xsd simple type
+ return $singularType;
+ } else {
+ $prefix = str_repeat("ArrayOf", $level);
+ $xsdType = $this->_getStrippedXsdType($singularType);
+ $arrayType = $prefix.$xsdType;
+ return "tns:$arrayType";
+ }
+ }
+
+ /**
+ * Strip the xsd: from a singularType and Format it nice for ArrayOf<Type> naming
+ *
+ * @param string $singularType
+ * @return string
+ */
+ protected function _getStrippedXsdType($singularType)
+ {
+ return ucfirst(substr(strtolower($singularType), 4));
+ }
+
+ /**
+ * From a nested defintion with type[], get the singular xsd:type
+ *
+ * @throws Zend_Soap_Wsdl_Exception When no xsd:simpletype can be detected.
+ * @param string $type
+ * @return string
+ */
+ protected function _getSingularType($type)
+ {
+ $singulartype = $this->getContext()->getType(str_replace("[]", "", $type));
+ return $singulartype;
+ }
+
+ /**
+ * Return the array nesting level based on the type name
+ *
+ * @param string $type
+ * @return integer
+ */
+ protected function _getNestedCount($type)
+ {
+ return substr_count($type, "[]");
+ }
+
+ /**
+ * Append the complex type definition to the WSDL via the context access
+ *
+ * @param string $arrayType
+ * @param string $childTypeName
+ * @return void
+ */
+ protected function _addElementFromWsdlAndChildTypes($arrayType, $childTypeName)
+ {
+ if (!in_array($arrayType, $this->getContext()->getTypes())) {
+ $dom = $this->getContext()->toDomDocument();
+
+ $complexType = $dom->createElement('xsd:complexType');
+ $complexType->setAttribute('name', $arrayType);
+
+ $sequence = $dom->createElement('xsd:sequence');
+
+ $element = $dom->createElement('xsd:element');
+ $element->setAttribute('name', 'item');
+ $element->setAttribute('type', $childTypeName);
+ $element->setAttribute('minOccurs', 0);
+ $element->setAttribute('maxOccurs', 'unbounded');
+ $sequence->appendChild($element);
+
+ $complexType->appendChild($sequence);
+
+ $this->getContext()->getSchema()->appendChild($complexType);
+ $this->getContext()->addType($arrayType);
+ }
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/Composite.php b/library/vendor/Zend/Soap/Wsdl/Strategy/Composite.php
new file mode 100644
index 0000000..a80120e
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/Composite.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_Interface
+ */
+
+/**
+ * Zend_Soap_Wsdl_Strategy_Composite
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Wsdl_Strategy_Composite implements Zend_Soap_Wsdl_Strategy_Interface
+{
+ /**
+ * Typemap of Complex Type => Strategy pairs.
+ *
+ * @var array
+ */
+ protected $_typeMap = array();
+
+ /**
+ * Default Strategy of this composite
+ *
+ * @var string|Zend_Soap_Wsdl_Strategy_Interface
+ */
+ protected $_defaultStrategy;
+
+ /**
+ * Context WSDL file that this composite serves
+ *
+ * @var Zend_Soap_Wsdl|null
+ */
+ protected $_context;
+
+ /**
+ * Construct Composite WSDL Strategy.
+ *
+ * @throws Zend_Soap_Wsdl_Exception
+ * @param array $typeMap
+ * @param string|Zend_Soap_Wsdl_Strategy_Interface $defaultStrategy
+ */
+ public function __construct(array $typeMap=array(), $defaultStrategy="Zend_Soap_Wsdl_Strategy_DefaultComplexType")
+ {
+ foreach($typeMap AS $type => $strategy) {
+ $this->connectTypeToStrategy($type, $strategy);
+ }
+ $this->_defaultStrategy = $defaultStrategy;
+ }
+
+ /**
+ * Connect a complex type to a given strategy.
+ *
+ * @throws Zend_Soap_Wsdl_Exception
+ * @param string $type
+ * @param string|Zend_Soap_Wsdl_Strategy_Interface $strategy
+ * @return Zend_Soap_Wsdl_Strategy_Composite
+ */
+ public function connectTypeToStrategy($type, $strategy)
+ {
+ if(!is_string($type)) {
+ /**
+ * @see Zend_Soap_Wsdl_Exception
+ */
+ throw new Zend_Soap_Wsdl_Exception("Invalid type given to Composite Type Map.");
+ }
+ $this->_typeMap[$type] = $strategy;
+ return $this;
+ }
+
+ /**
+ * Return default strategy of this composite
+ *
+ * @throws Zend_Soap_Wsdl_Exception
+ * @param string $type
+ * @return Zend_Soap_Wsdl_Strategy_Interface
+ */
+ public function getDefaultStrategy()
+ {
+ $strategy = $this->_defaultStrategy;
+ if(is_string($strategy) && class_exists($strategy)) {
+ $strategy = new $strategy;
+ }
+ if( !($strategy instanceof Zend_Soap_Wsdl_Strategy_Interface) ) {
+ /**
+ * @see Zend_Soap_Wsdl_Exception
+ */
+ throw new Zend_Soap_Wsdl_Exception(
+ "Default Strategy for Complex Types is not a valid strategy object."
+ );
+ }
+ $this->_defaultStrategy = $strategy;
+ return $strategy;
+ }
+
+ /**
+ * Return specific strategy or the default strategy of this type.
+ *
+ * @throws Zend_Soap_Wsdl_Exception
+ * @param string $type
+ * @return Zend_Soap_Wsdl_Strategy_Interface
+ */
+ public function getStrategyOfType($type)
+ {
+ if(isset($this->_typeMap[$type])) {
+ $strategy = $this->_typeMap[$type];
+
+ if(is_string($strategy) && class_exists($strategy)) {
+ $strategy = new $strategy();
+ }
+
+ if( !($strategy instanceof Zend_Soap_Wsdl_Strategy_Interface) ) {
+ /**
+ * @see Zend_Soap_Wsdl_Exception
+ */
+ throw new Zend_Soap_Wsdl_Exception(
+ "Strategy for Complex Type '".$type."' is not a valid strategy object."
+ );
+ }
+ $this->_typeMap[$type] = $strategy;
+ } else {
+ $strategy = $this->getDefaultStrategy();
+ }
+ return $strategy;
+ }
+
+ /**
+ * Method accepts the current WSDL context file.
+ *
+ * @param Zend_Soap_Wsdl $context
+ */
+ public function setContext(Zend_Soap_Wsdl $context)
+ {
+ $this->_context = $context;
+ return $this;
+ }
+
+ /**
+ * Create a complex type based on a strategy
+ *
+ * @throws Zend_Soap_Wsdl_Exception
+ * @param string $type
+ * @return string XSD type
+ */
+ public function addComplexType($type)
+ {
+ if(!($this->_context instanceof Zend_Soap_Wsdl) ) {
+ /**
+ * @see Zend_Soap_Wsdl_Exception
+ */
+ throw new Zend_Soap_Wsdl_Exception(
+ "Cannot add complex type '".$type."', no context is set for this composite strategy."
+ );
+ }
+
+ $strategy = $this->getStrategyOfType($type);
+ $strategy->setContext($this->_context);
+ return $strategy->addComplexType($type);
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/DefaultComplexType.php b/library/vendor/Zend/Soap/Wsdl/Strategy/DefaultComplexType.php
new file mode 100644
index 0000000..3baf804
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/DefaultComplexType.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Soap_Wsdl_Strategy_Abstract
+ */
+
+/**
+ * Zend_Soap_Wsdl_Strategy_DefaultComplexType
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Soap_Wsdl_Strategy_DefaultComplexType extends Zend_Soap_Wsdl_Strategy_Abstract
+{
+ /**
+ * Add a complex type by recursivly using all the class properties fetched via Reflection.
+ *
+ * @param string $type Name of the class to be specified
+ * @return string XSD Type for the given PHP type
+ */
+ public function addComplexType($type)
+ {
+ if(!class_exists($type)) {
+ throw new Zend_Soap_Wsdl_Exception(sprintf(
+ "Cannot add a complex type %s that is not an object or where ".
+ "class could not be found in 'DefaultComplexType' strategy.", $type
+ ));
+ }
+
+ $dom = $this->getContext()->toDomDocument();
+ $class = new ReflectionClass($type);
+
+ $defaultProperties = $class->getDefaultProperties();
+
+ $complexType = $dom->createElement('xsd:complexType');
+ $complexType->setAttribute('name', $type);
+
+ $all = $dom->createElement('xsd:all');
+
+ foreach ($class->getProperties() as $property) {
+ if ($property->isPublic() && preg_match_all('/@var\s+([^\s]+)/m', $property->getDocComment(), $matches)) {
+
+ /**
+ * @todo check if 'xsd:element' must be used here (it may not be compatible with using 'complexType'
+ * node for describing other classes used as attribute types for current class
+ */
+ $element = $dom->createElement('xsd:element');
+ $element->setAttribute('name', $propertyName = $property->getName());
+ $element->setAttribute('type', $this->getContext()->getType(trim($matches[1][0])));
+
+ // If the default value is null, then this property is nillable.
+ if ($defaultProperties[$propertyName] === null) {
+ $element->setAttribute('nillable', 'true');
+ }
+
+ $all->appendChild($element);
+ }
+ }
+
+ $complexType->appendChild($all);
+ $this->getContext()->getSchema()->appendChild($complexType);
+ $this->getContext()->addType($type);
+
+ return "tns:$type";
+ }
+}
diff --git a/library/vendor/Zend/Soap/Wsdl/Strategy/Interface.php b/library/vendor/Zend/Soap/Wsdl/Strategy/Interface.php
new file mode 100644
index 0000000..9a57605
--- /dev/null
+++ b/library/vendor/Zend/Soap/Wsdl/Strategy/Interface.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Interface for Zend_Soap_Wsdl_Strategy.
+ *
+ * @category Zend
+ * @package Zend_Soap
+ * @subpackage Wsdl
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Soap_Wsdl_Strategy_Interface
+{
+ /**
+ * Method accepts the current WSDL context file.
+ *
+ * @param <type> $context
+ */
+ public function setContext(Zend_Soap_Wsdl $context);
+
+ /**
+ * Create a complex type based on a strategy
+ *
+ * @param string $type
+ * @return string XSD type
+ */
+ public function addComplexType($type);
+}
diff --git a/library/vendor/Zend/TimeSync.php b/library/vendor/Zend/TimeSync.php
new file mode 100644
index 0000000..5b05c4b
--- /dev/null
+++ b/library/vendor/Zend/TimeSync.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Zend_Date
+ */
+
+/**
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_TimeSync implements IteratorAggregate
+{
+ /**
+ * Set the default timeserver protocol to "Ntp". This will be called
+ * when no protocol is specified
+ */
+ const DEFAULT_PROTOCOL = 'Ntp';
+
+ /**
+ * Contains array of timeserver objects
+ *
+ * @var array
+ */
+ protected $_timeservers = array();
+
+ /**
+ * Holds a reference to the timeserver that is currently being used
+ *
+ * @var object
+ */
+ protected $_current;
+
+ /**
+ * Allowed timeserver schemes
+ *
+ * @var array
+ */
+ protected $_allowedSchemes = array(
+ 'Ntp',
+ 'Sntp'
+ );
+
+ /**
+ * Configuration array, set using the constructor or using
+ * ::setOptions() or ::setOption()
+ *
+ * @var array
+ */
+ public static $options = array(
+ 'timeout' => 1
+ );
+
+ /**
+ * Zend_TimeSync constructor
+ *
+ * @param string|array $target - OPTIONAL single timeserver, or an array of timeservers.
+ * @param string $alias - OPTIONAL an alias for this timeserver
+ * @return object
+ */
+ public function __construct($target = null, $alias = null)
+ {
+ if ($target !== null) {
+ $this->addServer($target, $alias);
+ }
+ }
+
+ /**
+ * getIterator() - return an iteratable object for use in foreach and the like,
+ * this completes the IteratorAggregate interface
+ *
+ * @return ArrayObject
+ */
+ public function getIterator()
+ {
+ return new ArrayObject($this->_timeservers);
+ }
+
+ /**
+ * Add a timeserver or multiple timeservers
+ *
+ * Server should be a single string representation of a timeserver,
+ * or a structured array listing multiple timeservers.
+ *
+ * If you provide an array of timeservers in the $target variable,
+ * $alias will be ignored. you can enter these as the array key
+ * in the provided array, which should be structured as follows:
+ *
+ * <code>
+ * $example = array(
+ * 'server_a' => 'ntp://127.0.0.1',
+ * 'server_b' => 'ntp://127.0.0.1:123',
+ * 'server_c' => 'ntp://[2000:364:234::2.5]',
+ * 'server_d' => 'ntp://[2000:364:234::2.5]:123'
+ * );
+ * </code>
+ *
+ * If no port number has been suplied, the default matching port
+ * number will be used.
+ *
+ * Supported protocols are:
+ * - ntp
+ * - sntp
+ *
+ * @param string|array $target - Single timeserver, or an array of timeservers.
+ * @param string $alias - OPTIONAL an alias for this timeserver
+ * @throws Zend_TimeSync_Exception
+ */
+ public function addServer($target, $alias = null)
+ {
+ if (is_array($target)) {
+ foreach ($target as $key => $server) {
+ $this->_addServer($server, $key);
+ }
+ } else {
+ $this->_addServer($target, $alias);
+ }
+ }
+
+ /**
+ * Sets the value for the given options
+ *
+ * This will replace any currently defined options.
+ *
+ * @param array $options - An array of options to be set
+ */
+ public static function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ Zend_TimeSync::$options[$key] = $value;
+ }
+ }
+
+ /**
+ * Marks a nameserver as current
+ *
+ * @param string|integer $alias - The alias from the timeserver to set as current
+ * @throws Zend_TimeSync_Exception
+ */
+ public function setServer($alias)
+ {
+ if (isset($this->_timeservers[$alias]) === true) {
+ $this->_current = $this->_timeservers[$alias];
+ } else {
+ throw new Zend_TimeSync_Exception("'$alias' does not point to valid timeserver");
+ }
+ }
+
+ /**
+ * Returns the value to the option
+ *
+ * @param string $key - The option's identifier
+ * @return mixed
+ * @throws Zend_TimeSync_Exception
+ */
+ public static function getOptions($key = null)
+ {
+ if ($key == null) {
+ return Zend_TimeSync::$options;
+ }
+
+ if (isset(Zend_TimeSync::$options[$key]) === true) {
+ return Zend_TimeSync::$options[$key];
+ } else {
+ throw new Zend_TimeSync_Exception("'$key' does not point to valid option");
+ }
+ }
+
+ /**
+ * Return a specified timeserver by alias
+ * If no alias is given it will return the current timeserver
+ *
+ * @param string|integer $alias - The alias from the timeserver to return
+ * @return object
+ * @throws Zend_TimeSync_Exception
+ */
+ public function getServer($alias = null)
+ {
+ if ($alias === null) {
+ if (isset($this->_current) && $this->_current !== false) {
+ return $this->_current;
+ } else {
+ throw new Zend_TimeSync_Exception('there is no timeserver set');
+ }
+ }
+ if (isset($this->_timeservers[$alias]) === true) {
+ return $this->_timeservers[$alias];
+ } else {
+ throw new Zend_TimeSync_Exception("'$alias' does not point to valid timeserver");
+ }
+ }
+
+ /**
+ * Returns information sent/returned from the current timeserver
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ return $this->getServer()->getInfo();
+ }
+
+ /**
+ * Query the timeserver list using the fallback mechanism
+ *
+ * If there are multiple servers listed, this method will act as a
+ * facade and will try to return the date from the first server that
+ * returns a valid result.
+ *
+ * @param Zend_Locale $locale - OPTIONAL locale
+ * @return object
+ * @throws Zend_TimeSync_Exception
+ */
+ public function getDate($locale = null)
+ {
+ foreach ($this->_timeservers as $alias => $server) {
+ $this->_current = $server;
+ try {
+ return $server->getDate($locale);
+ } catch (Zend_TimeSync_Exception $e) {
+ if (!isset($masterException)) {
+ $masterException = new Zend_TimeSync_Exception('all timeservers are bogus');
+ }
+ $masterException->addException($e);
+ }
+ }
+
+ throw $masterException;
+ }
+
+ /**
+ * Adds a timeserver object to the timeserver list
+ *
+ * @param string|array $target - Single timeserver, or an array of timeservers.
+ * @param string $alias - An alias for this timeserver
+ */
+ protected function _addServer($target, $alias)
+ {
+ if ($pos = strpos($target, '://')) {
+ $protocol = substr($target, 0, $pos);
+ $adress = substr($target, $pos + 3);
+ } else {
+ $adress = $target;
+ $protocol = self::DEFAULT_PROTOCOL;
+ }
+
+ if ($pos = strrpos($adress, ':')) {
+ $posbr = strpos($adress, ']');
+ if ($posbr and ($pos > $posbr)) {
+ $port = substr($adress, $pos + 1);
+ $adress = substr($adress, 0, $pos);
+ } else if (!$posbr and $pos) {
+ $port = substr($adress, $pos + 1);
+ $adress = substr($adress, 0, $pos);
+ } else {
+ $port = null;
+ }
+ } else {
+ $port = null;
+ }
+
+ $protocol = ucfirst(strtolower($protocol));
+ if (!in_array($protocol, $this->_allowedSchemes)) {
+ throw new Zend_TimeSync_Exception("'$protocol' is not a supported protocol");
+ }
+
+ $className = 'Zend_TimeSync_' . $protocol;
+ if (!class_exists($className)) {
+ Zend_Loader::loadClass($className);
+ }
+ $timeServerObj = new $className($adress, $port);
+
+ $this->_timeservers[$alias] = $timeServerObj;
+ }
+}
diff --git a/library/vendor/Zend/TimeSync/Exception.php b/library/vendor/Zend/TimeSync/Exception.php
new file mode 100644
index 0000000..c1a33e4
--- /dev/null
+++ b/library/vendor/Zend/TimeSync/Exception.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_Exception
+ */
+
+/**
+ * Exception class for Zend_TimeSync
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_TimeSync_Exception extends Zend_Exception
+{
+ /**
+ * Contains array of exceptions thrown in queried server
+ *
+ * @var array
+ */
+ protected $_exceptions;
+
+ /**
+ * Adds an exception to the exception list
+ *
+ * @param Zend_TimeSync_Exception $exception New exteption to throw
+ * @return void
+ */
+ public function addException(Zend_TimeSync_Exception $exception)
+ {
+ $this->_exceptions[] = $exception;
+ }
+
+ /**
+ * Returns an array of exceptions that were thrown
+ *
+ * @return array
+ */
+ public function get()
+ {
+ return $this->_exceptions;
+ }
+}
diff --git a/library/vendor/Zend/TimeSync/Ntp.php b/library/vendor/Zend/TimeSync/Ntp.php
new file mode 100644
index 0000000..0c52530
--- /dev/null
+++ b/library/vendor/Zend/TimeSync/Ntp.php
@@ -0,0 +1,430 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_TimeSync_Protocol
+ */
+
+/**
+ * NTP Protocol handling class
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_TimeSync_Ntp extends Zend_TimeSync_Protocol
+{
+ /**
+ * NTP port number (123) assigned by the Internet Assigned Numbers Authority
+ *
+ * @var integer
+ */
+ protected $_port = 123;
+
+ /**
+ * NTP class constructor, sets the timeserver and port number
+ *
+ * @param string $timeserver Adress of the timeserver to connect to
+ * @param integer $port (Optional) Port for this timeserver
+ */
+ public function __construct($timeserver, $port = 123)
+ {
+ $this->_timeserver = 'udp://' . $timeserver;
+ if ($port !== null) {
+ $this->_port = $port;
+ }
+ }
+
+ /**
+ * Prepare local timestamp for transmission in our request packet
+ *
+ * NTP timestamps are represented as a 64-bit fixed-point number, in
+ * seconds relative to 0000 UT on 1 January 1900. The integer part is
+ * in the first 32 bits and the fraction part in the last 32 bits
+ *
+ * @return string
+ */
+ protected function _prepare()
+ {
+ $frac = microtime();
+ $fracba = ($frac & 0xff000000) >> 24;
+ $fracbb = ($frac & 0x00ff0000) >> 16;
+ $fracbc = ($frac & 0x0000ff00) >> 8;
+ $fracbd = ($frac & 0x000000ff);
+
+ $sec = (time() + 2208988800);
+ $secba = ($sec & 0xff000000) >> 24;
+ $secbb = ($sec & 0x00ff0000) >> 16;
+ $secbc = ($sec & 0x0000ff00) >> 8;
+ $secbd = ($sec & 0x000000ff);
+
+ // Flags
+ $nul = chr(0x00);
+ $nulbyte = $nul . $nul . $nul . $nul;
+ $ntppacket = chr(0xd9) . $nul . chr(0x0a) . chr(0xfa);
+
+ /*
+ * Root delay
+ *
+ * Indicates the total roundtrip delay to the primary reference
+ * source at the root of the synchronization subnet, in seconds
+ */
+ $ntppacket .= $nul . $nul . chr(0x1c) . chr(0x9b);
+
+ /*
+ * Clock Dispersion
+ *
+ * Indicates the maximum error relative to the primary reference source at the
+ * root of the synchronization subnet, in seconds
+ */
+ $ntppacket .= $nul . chr(0x08) . chr(0xd7) . chr(0xff);
+
+ /*
+ * ReferenceClockID
+ *
+ * Identifying the particular reference clock
+ */
+ $ntppacket .= $nulbyte;
+
+ /*
+ * The local time, in timestamp format, at the peer when its latest NTP message
+ * was sent. Contanis an integer and a fractional part
+ */
+ $ntppacket .= chr($secba) . chr($secbb) . chr($secbc) . chr($secbd);
+ $ntppacket .= chr($fracba) . chr($fracbb) . chr($fracbc) . chr($fracbd);
+
+ /*
+ * The local time, in timestamp format, at the peer. Contains an integer
+ * and a fractional part.
+ */
+ $ntppacket .= $nulbyte;
+ $ntppacket .= $nulbyte;
+
+ /*
+ * This is the local time, in timestamp format, when the latest NTP message from
+ * the peer arrived. Contanis an integer and a fractional part.
+ */
+ $ntppacket .= $nulbyte;
+ $ntppacket .= $nulbyte;
+
+ /*
+ * The local time, in timestamp format, at which the
+ * NTP message departed the sender. Contanis an integer
+ * and a fractional part.
+ */
+ $ntppacket .= chr($secba) . chr($secbb) . chr($secbc) . chr($secbd);
+ $ntppacket .= chr($fracba) . chr($fracbb) . chr($fracbc) . chr($fracbd);
+
+ return $ntppacket;
+ }
+
+ /**
+ * Calculates a 32bit integer
+ *
+ * @param string $input
+ * @return integer
+ */
+ protected function _getInteger($input)
+ {
+ $f1 = str_pad(ord($input[0]), 2, '0', STR_PAD_LEFT);
+ $f1 .= str_pad(ord($input[1]), 2, '0', STR_PAD_LEFT);
+ $f1 .= str_pad(ord($input[2]), 2, '0', STR_PAD_LEFT);
+ $f1 .= str_pad(ord($input[3]), 2, '0', STR_PAD_LEFT);
+ return (int) $f1;
+ }
+
+ /**
+ * Calculates a 32bit signed fixed point number
+ *
+ * @param string $input
+ * @return float
+ */
+ protected function _getFloat($input)
+ {
+ $f1 = str_pad(ord($input[0]), 2, '0', STR_PAD_LEFT);
+ $f1 .= str_pad(ord($input[1]), 2, '0', STR_PAD_LEFT);
+ $f1 .= str_pad(ord($input[2]), 2, '0', STR_PAD_LEFT);
+ $f1 .= str_pad(ord($input[3]), 2, '0', STR_PAD_LEFT);
+ $f2 = $f1 >> 17;
+ $f3 = ($f1 & 0x0001FFFF);
+ $f1 = $f2 . '.' . $f3;
+ return (float) $f1;
+ }
+
+ /**
+ * Calculates a 64bit timestamp
+ *
+ * @param string $input
+ * @return float
+ */
+ protected function _getTimestamp($input)
+ {
+ $f1 = (ord($input[0]) * pow(256, 3));
+ $f1 += (ord($input[1]) * pow(256, 2));
+ $f1 += (ord($input[2]) * pow(256, 1));
+ $f1 += (ord($input[3]));
+ $f1 -= 2208988800;
+
+ $f2 = (ord($input[4]) * pow(256, 3));
+ $f2 += (ord($input[5]) * pow(256, 2));
+ $f2 += (ord($input[6]) * pow(256, 1));
+ $f2 += (ord($input[7]));
+
+ return (float) ($f1 . "." . $f2);
+ }
+
+ /**
+ * Reads the data returned from the timeserver
+ *
+ * This will return an array with binary data listing:
+ *
+ * @return array
+ * @throws Zend_TimeSync_Exception When timeserver can not be connected
+ */
+ protected function _read()
+ {
+ $flags = ord(fread($this->_socket, 1));
+ $info = stream_get_meta_data($this->_socket);
+
+ if ($info['timed_out'] === true) {
+ fclose($this->_socket);
+ throw new Zend_TimeSync_Exception('could not connect to ' .
+ "'$this->_timeserver' on port '$this->_port', reason: 'server timed out'");
+ }
+
+ $result = array(
+ 'flags' => $flags,
+ 'stratum' => ord(fread($this->_socket, 1)),
+ 'poll' => ord(fread($this->_socket, 1)),
+ 'precision' => ord(fread($this->_socket, 1)),
+ 'rootdelay' => $this->_getFloat(fread($this->_socket, 4)),
+ 'rootdispersion' => $this->_getFloat(fread($this->_socket, 4)),
+ 'referenceid' => fread($this->_socket, 4),
+ 'referencestamp' => $this->_getTimestamp(fread($this->_socket, 8)),
+ 'originatestamp' => $this->_getTimestamp(fread($this->_socket, 8)),
+ 'receivestamp' => $this->_getTimestamp(fread($this->_socket, 8)),
+ 'transmitstamp' => $this->_getTimestamp(fread($this->_socket, 8)),
+ 'clientreceived' => microtime(true)
+ );
+
+ $this->_disconnect();
+ return $result;
+ }
+
+ /**
+ * Sends the NTP packet to the server
+ *
+ * @param string $data Data to send to the timeserver
+ * @return void
+ */
+ protected function _write($data)
+ {
+ $this->_connect();
+
+ fwrite($this->_socket, $data);
+ stream_set_timeout($this->_socket, Zend_TimeSync::$options['timeout']);
+ }
+
+ /**
+ * Extracts the binary data returned from the timeserver
+ *
+ * @param string|array $binary Data returned from the timeserver
+ * @return integer Difference in seconds
+ */
+ protected function _extract($binary)
+ {
+ /*
+ * Leap Indicator bit 1100 0000
+ *
+ * Code warning of impending leap-second to be inserted at the end of
+ * the last day of the current month.
+ */
+ $leap = ($binary['flags'] & 0xc0) >> 6;
+ switch($leap) {
+ case 0:
+ $this->_info['leap'] = '0 - no warning';
+ break;
+
+ case 1:
+ $this->_info['leap'] = '1 - last minute has 61 seconds';
+ break;
+
+ case 2:
+ $this->_info['leap'] = '2 - last minute has 59 seconds';
+ break;
+
+ default:
+ $this->_info['leap'] = '3 - not syncronised';
+ break;
+ }
+
+ /*
+ * Version Number bit 0011 1000
+ *
+ * This should be 3 (RFC 1305)
+ */
+ $this->_info['version'] = ($binary['flags'] & 0x38) >> 3;
+
+ /*
+ * Mode bit 0000 0111
+ *
+ * Except in broadcast mode, an NTP association is formed when two peers
+ * exchange messages and one or both of them create and maintain an
+ * instantiation of the protocol machine, called an association.
+ */
+ $mode = ($binary['flags'] & 0x07);
+ switch($mode) {
+ case 1:
+ $this->_info['mode'] = 'symetric active';
+ break;
+
+ case 2:
+ $this->_info['mode'] = 'symetric passive';
+ break;
+
+ case 3:
+ $this->_info['mode'] = 'client';
+ break;
+
+ case 4:
+ $this->_info['mode'] = 'server';
+ break;
+
+ case 5:
+ $this->_info['mode'] = 'broadcast';
+ break;
+
+ default:
+ $this->_info['mode'] = 'reserved';
+ break;
+ }
+
+ $ntpserviceid = 'Unknown Stratum ' . $binary['stratum'] . ' Service';
+
+ /*
+ * Reference Clock Identifier
+ *
+ * Identifies the particular reference clock.
+ */
+ $refid = strtoupper($binary['referenceid']);
+ switch($binary['stratum']) {
+ case 0:
+ if (substr($refid, 0, 3) === 'DCN') {
+ $ntpserviceid = 'DCN routing protocol';
+ } else if (substr($refid, 0, 4) === 'NIST') {
+ $ntpserviceid = 'NIST public modem';
+ } else if (substr($refid, 0, 3) === 'TSP') {
+ $ntpserviceid = 'TSP time protocol';
+ } else if (substr($refid, 0, 3) === 'DTS') {
+ $ntpserviceid = 'Digital Time Service';
+ }
+ break;
+
+ case 1:
+ if (substr($refid, 0, 4) === 'ATOM') {
+ $ntpserviceid = 'Atomic Clock (calibrated)';
+ } else if (substr($refid, 0, 3) === 'VLF') {
+ $ntpserviceid = 'VLF radio';
+ } else if ($refid === 'CALLSIGN') {
+ $ntpserviceid = 'Generic radio';
+ } else if (substr($refid, 0, 4) === 'LORC') {
+ $ntpserviceid = 'LORAN-C radionavigation';
+ } else if (substr($refid, 0, 4) === 'GOES') {
+ $ntpserviceid = 'GOES UHF environment satellite';
+ } else if (substr($refid, 0, 3) === 'GPS') {
+ $ntpserviceid = 'GPS UHF satellite positioning';
+ }
+ break;
+
+ default:
+ $ntpserviceid = ord(substr($binary['referenceid'], 0, 1));
+ $ntpserviceid .= '.';
+ $ntpserviceid .= ord(substr($binary['referenceid'], 1, 1));
+ $ntpserviceid .= '.';
+ $ntpserviceid .= ord(substr($binary['referenceid'], 2, 1));
+ $ntpserviceid .= '.';
+ $ntpserviceid .= ord(substr($binary['referenceid'], 3, 1));
+ break;
+ }
+
+ $this->_info['ntpid'] = $ntpserviceid;
+
+ /*
+ * Stratum
+ *
+ * Indicates the stratum level of the local clock
+ */
+ switch($binary['stratum']) {
+ case 0:
+ $this->_info['stratum'] = 'undefined';
+ break;
+
+ case 1:
+ $this->_info['stratum'] = 'primary reference';
+ break;
+
+ default:
+ $this->_info['stratum'] = 'secondary reference';
+ break;
+ }
+
+ /*
+ * Indicates the total roundtrip delay to the primary reference source at the
+ * root of the synchronization subnet, in seconds.
+ *
+ * Both positive and negative values, depending on clock precision and skew, are
+ * possible.
+ */
+ $this->_info['rootdelay'] = $binary['rootdelay'];
+
+ /*
+ * Indicates the maximum error relative to the primary reference source at the
+ * root of the synchronization subnet, in seconds.
+ *
+ * Only positive values greater than zero are possible.
+ */
+ $this->_info['rootdispersion'] = $binary['rootdispersion'];
+
+ /*
+ * The roundtrip delay of the peer clock relative to the local clock
+ * over the network path between them, in seconds.
+ *
+ * Note that this variable can take on both positive and negative values,
+ * depending on clock precision and skew-error accumulation.
+ */
+ $this->_info['roundtrip'] = $binary['receivestamp'];
+ $this->_info['roundtrip'] -= $binary['originatestamp'];
+ $this->_info['roundtrip'] -= $binary['transmitstamp'];
+ $this->_info['roundtrip'] += $binary['clientreceived'];
+ $this->_info['roundtrip'] /= 2;
+
+ // The offset of the peer clock relative to the local clock, in seconds.
+ $this->_info['offset'] = $binary['receivestamp'];
+ $this->_info['offset'] -= $binary['originatestamp'];
+ $this->_info['offset'] += $binary['transmitstamp'];
+ $this->_info['offset'] -= $binary['clientreceived'];
+ $this->_info['offset'] /= 2;
+ $time = (time() - $this->_info['offset']);
+
+ return $time;
+ }
+}
diff --git a/library/vendor/Zend/TimeSync/Protocol.php b/library/vendor/Zend/TimeSync/Protocol.php
new file mode 100644
index 0000000..12dc6d9
--- /dev/null
+++ b/library/vendor/Zend/TimeSync/Protocol.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Abstract class definition for all timeserver protocols
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_TimeSync_Protocol
+{
+ /**
+ * Holds the current socket connection
+ *
+ * @var array
+ */
+ protected $_socket;
+
+ /**
+ * Exceptions that might have occured
+ *
+ * @var array
+ */
+ protected $_exceptions;
+
+ /**
+ * Hostname for timeserver
+ *
+ * @var string
+ */
+ protected $_timeserver;
+
+ /**
+ * Holds information passed/returned from timeserver
+ *
+ * @var array
+ */
+ protected $_info = array();
+
+ /**
+ * Abstract method that prepares the data to send to the timeserver
+ *
+ * @return mixed
+ */
+ abstract protected function _prepare();
+
+ /**
+ * Abstract method that reads the data returned from the timeserver
+ *
+ * @return mixed
+ */
+ abstract protected function _read();
+
+ /**
+ * Abstract method that writes data to to the timeserver
+ *
+ * @param string $data Data to write
+ * @return void
+ */
+ abstract protected function _write($data);
+
+ /**
+ * Abstract method that extracts the binary data returned from the timeserver
+ *
+ * @param string|array $data Data returned from the timeserver
+ * @return integer
+ */
+ abstract protected function _extract($data);
+
+ /**
+ * Connect to the specified timeserver.
+ *
+ * @return void
+ * @throws Zend_TimeSync_Exception When the connection failed
+ */
+ protected function _connect()
+ {
+ $socket = @fsockopen($this->_timeserver, $this->_port, $errno, $errstr,
+ Zend_TimeSync::$options['timeout']);
+ if ($socket === false) {
+ throw new Zend_TimeSync_Exception('could not connect to ' .
+ "'$this->_timeserver' on port '$this->_port', reason: '$errstr'");
+ }
+
+ $this->_socket = $socket;
+ }
+
+ /**
+ * Disconnects from the peer, closes the socket.
+ *
+ * @return void
+ */
+ protected function _disconnect()
+ {
+ @fclose($this->_socket);
+ $this->_socket = null;
+ }
+
+ /**
+ * Return information sent/returned from the timeserver
+ *
+ * @return array
+ */
+ public function getInfo()
+ {
+ if (empty($this->_info) === true) {
+ $this->_write($this->_prepare());
+ $timestamp = $this->_extract($this->_read());
+ }
+
+ return $this->_info;
+ }
+
+ /**
+ * Query this timeserver without using the fallback mechanism
+ *
+ * @param string|Zend_Locale $locale (Optional) Locale
+ * @return Zend_Date
+ */
+ public function getDate($locale = null)
+ {
+ $this->_write($this->_prepare());
+ $timestamp = $this->_extract($this->_read());
+
+ $date = new Zend_Date($this, null, $locale);
+ return $date;
+ }
+}
diff --git a/library/vendor/Zend/TimeSync/Sntp.php b/library/vendor/Zend/TimeSync/Sntp.php
new file mode 100644
index 0000000..6ad0a5e
--- /dev/null
+++ b/library/vendor/Zend/TimeSync/Sntp.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Zend_TimeSync_Protocol
+ */
+
+/**
+ * SNTP Protocol handling class
+ *
+ * @category Zend
+ * @package Zend_TimeSync
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_TimeSync_Sntp extends Zend_TimeSync_Protocol
+{
+ /**
+ * Port number for this timeserver
+ *
+ * @var integer
+ */
+ protected $_port = 37;
+
+ /**
+ * Socket delay
+ *
+ * @var integer
+ */
+ private $_delay;
+
+ /**
+ * Class constructor, sets the timeserver and port number
+ *
+ * @param string $timeserver Timeserver to connect to
+ * @param integer $port Port of the timeserver when it differs from the default port
+ */
+ public function __construct($timeserver, $port)
+ {
+ $this->_timeserver = 'udp://' . $timeserver;
+ if ($port !== null) {
+ $this->_port = $port;
+ }
+ }
+
+ /**
+ * Prepares the data that will be send to the timeserver
+ *
+ * @return array
+ */
+ protected function _prepare()
+ {
+ return "\n";
+ }
+
+ /**
+ * Reads the data returned from the timeserver
+ *
+ * @return string
+ */
+ protected function _read()
+ {
+ $result = fread($this->_socket, 49);
+ $this->_delay = (($this->_delay - time()) / 2);
+
+ return $result;
+ }
+
+ /**
+ * Writes data to to the timeserver
+ *
+ * @param string $data Data to write to the timeserver
+ * @return void
+ */
+ protected function _write($data)
+ {
+ $this->_connect();
+ $this->_delay = time();
+ fputs($this->_socket, $data);
+ }
+
+ /**
+ * Extracts the data returned from the timeserver
+ *
+ * @param string $result Data to extract
+ * @return integer
+ */
+ protected function _extract($result)
+ {
+ $dec = hexdec('7fffffff');
+ $time = abs(($dec - hexdec(bin2hex($result))) - $dec);
+ $time -= 2208988800;
+ // Socket delay
+ $time -= $this->_delay;
+
+ $this->_info['offset'] = $this->_delay;
+
+ return $time;
+ }
+}
diff --git a/library/vendor/Zend/Translate.php b/library/vendor/Zend/Translate.php
new file mode 100644
index 0000000..de9f530
--- /dev/null
+++ b/library/vendor/Zend/Translate.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Translate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * @see Zend_Translate_Adapter
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Translate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Translate {
+ /**
+ * Adapter names constants
+ */
+ const AN_ARRAY = 'Array';
+ const AN_CSV = 'Csv';
+ const AN_GETTEXT = 'Gettext';
+ const AN_INI = 'Ini';
+ const AN_QT = 'Qt';
+ const AN_TBX = 'Tbx';
+ const AN_TMX = 'Tmx';
+ const AN_XLIFF = 'Xliff';
+ const AN_XMLTM = 'XmlTm';
+
+ const LOCALE_DIRECTORY = 'directory';
+ const LOCALE_FILENAME = 'filename';
+
+ /**
+ * Adapter
+ *
+ * @var Zend_Translate_Adapter
+ */
+ private $_adapter;
+
+ /**
+ * Generates the standard translation object
+ *
+ * @param array|Zend_Config|Zend_Translate_Adapter $options Options to use
+ * @param string|array [$content] Path to content, or content itself
+ * @param string|Zend_Locale [$locale]
+ * @throws Zend_Translate_Exception
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (func_num_args() > 1) {
+ $args = func_get_args();
+ $options = array();
+ $options['adapter'] = array_shift($args);
+ if (!empty($args)) {
+ $options['content'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $options['locale'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $opt = array_shift($args);
+ $options = array_merge($opt, $options);
+ }
+ } else if (!is_array($options)) {
+ $options = array('adapter' => $options);
+ }
+
+ $this->setAdapter($options);
+ }
+
+ /**
+ * Sets a new adapter
+ *
+ * @param array|Zend_Config|Zend_Translate_Adapter $options Options to use
+ * @param string|array [$content] Path to content, or content itself
+ * @param string|Zend_Locale [$locale]
+ * @throws Zend_Translate_Exception
+ */
+ public function setAdapter($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (func_num_args() > 1) {
+ $args = func_get_args();
+ $options = array();
+ $options['adapter'] = array_shift($args);
+ if (!empty($args)) {
+ $options['content'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $options['locale'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $opt = array_shift($args);
+ $options = array_merge($opt, $options);
+ }
+ } else if (!is_array($options)) {
+ $options = array('adapter' => $options);
+ }
+
+ if (Zend_Loader::isReadable('Zend/Translate/Adapter/' . ucfirst($options['adapter']). '.php')) {
+ $options['adapter'] = 'Zend_Translate_Adapter_' . ucfirst($options['adapter']);
+ }
+
+ if (!class_exists($options['adapter'])) {
+ Zend_Loader::loadClass($options['adapter']);
+ }
+
+ if (array_key_exists('cache', $options)) {
+ Zend_Translate_Adapter::setCache($options['cache']);
+ }
+
+ $adapter = $options['adapter'];
+ unset($options['adapter']);
+ $this->_adapter = new $adapter($options);
+ if (!$this->_adapter instanceof Zend_Translate_Adapter) {
+ throw new Zend_Translate_Exception("Adapter " . $adapter . " does not extend Zend_Translate_Adapter");
+ }
+ }
+
+ /**
+ * Returns the adapters name and it's options
+ *
+ * @return Zend_Translate_Adapter
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Returns the set cache
+ *
+ * @return Zend_Cache_Core The set cache
+ */
+ public static function getCache()
+ {
+ return Zend_Translate_Adapter::getCache();
+ }
+
+ /**
+ * Sets a cache for all instances of Zend_Translate
+ *
+ * @param Zend_Cache_Core $cache Cache to store to
+ * @return void
+ */
+ public static function setCache(Zend_Cache_Core $cache)
+ {
+ Zend_Translate_Adapter::setCache($cache);
+ }
+
+ /**
+ * Returns true when a cache is set
+ *
+ * @return boolean
+ */
+ public static function hasCache()
+ {
+ return Zend_Translate_Adapter::hasCache();
+ }
+
+ /**
+ * Removes any set cache
+ *
+ * @return void
+ */
+ public static function removeCache()
+ {
+ Zend_Translate_Adapter::removeCache();
+ }
+
+ /**
+ * Clears all set cache data
+ *
+ * @param string $tag Tag to clear when the default tag name is not used
+ * @return void
+ */
+ public static function clearCache($tag = null)
+ {
+ Zend_Translate_Adapter::clearCache($tag);
+ }
+
+ /**
+ * Calls all methods from the adapter
+ */
+ public function __call($method, array $options)
+ {
+ if (method_exists($this->_adapter, $method)) {
+ return call_user_func_array(array($this->_adapter, $method), $options);
+ }
+ throw new Zend_Translate_Exception("Unknown method '" . $method . "' called!");
+ }
+}
diff --git a/library/vendor/Zend/Translate/Adapter.php b/library/vendor/Zend/Translate/Adapter.php
new file mode 100644
index 0000000..f21ef3c
--- /dev/null
+++ b/library/vendor/Zend/Translate/Adapter.php
@@ -0,0 +1,988 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Translate
+ * @subpackage Zend_Translate_Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Locale
+ */
+
+/**
+ * @see Zend_Translate_Plural
+ */
+
+/**
+ * Basic adapter class for each translation source adapter
+ *
+ * @category Zend
+ * @package Zend_Translate
+ * @subpackage Zend_Translate_Adapter
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Translate_Adapter {
+ /**
+ * Shows if locale detection is in automatic level
+ * @var boolean
+ */
+ private $_automatic = true;
+
+ /**
+ * Internal value to see already routed languages
+ * @var array()
+ */
+ private $_routed = array();
+
+ /**
+ * Internal cache for all adapters
+ * @var Zend_Cache_Core
+ */
+ protected static $_cache = null;
+
+ /**
+ * Internal value to remember if cache supports tags
+ *
+ * @var boolean
+ */
+ private static $_cacheTags = false;
+
+ /**
+ * Scans for the locale within the name of the directory
+ * @constant integer
+ */
+ const LOCALE_DIRECTORY = 'directory';
+
+ /**
+ * Scans for the locale within the name of the file
+ * @constant integer
+ */
+ const LOCALE_FILENAME = 'filename';
+
+ /**
+ * Array with all options, each adapter can have own additional options
+ * 'clear' => when true, clears already loaded translations when adding new files
+ * 'content' => content to translate or file or directory with content
+ * 'disableNotices' => when true, omits notices from being displayed
+ * 'ignore' => a prefix for files and directories which are not being added
+ * 'locale' => the actual set locale to use
+ * 'log' => a instance of Zend_Log where logs are written to
+ * 'logMessage' => message to be logged
+ * 'logPriority' => priority which is used to write the log message
+ * 'logUntranslated' => when true, untranslated messages are not logged
+ * 'reload' => reloads the cache by reading the content again
+ * 'scan' => searches for translation files using the LOCALE constants
+ * 'tag' => tag to use for the cache
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'clear' => false,
+ 'content' => null,
+ 'disableNotices' => false,
+ 'ignore' => '.',
+ 'locale' => 'auto',
+ 'log' => null,
+ 'logMessage' => "Untranslated message within '%locale%': %message%",
+ 'logPriority' => 5,
+ 'logUntranslated' => false,
+ 'reload' => false,
+ 'route' => null,
+ 'scan' => null,
+ 'tag' => 'Zend_Translate'
+ );
+
+ /**
+ * Translation table
+ * @var array
+ */
+ protected $_translate = array();
+
+ /**
+ * Generates the adapter
+ *
+ * @param string|array|Zend_Config $options Translation options for this adapter
+ * @param string|array [$content]
+ * @param string|Zend_Locale [$locale]
+ * @throws Zend_Translate_Exception
+ * @return void
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (func_num_args() > 1) {
+ $args = func_get_args();
+ $options = array();
+ $options['content'] = array_shift($args);
+
+ if (!empty($args)) {
+ $options['locale'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $opt = array_shift($args);
+ $options = array_merge($opt, $options);
+ }
+ } else if (!is_array($options)) {
+ $options = array('content' => $options);
+ }
+
+ if (array_key_exists('cache', $options)) {
+ self::setCache($options['cache']);
+ unset($options['cache']);
+ }
+
+ if (isset(self::$_cache)) {
+ $id = 'Zend_Translate_' . $this->toString() . '_Options';
+ $result = self::$_cache->load($id);
+ if ($result) {
+ $this->_options = $result;
+ }
+ }
+
+ if (empty($options['locale']) || ($options['locale'] === "auto")) {
+ $this->_automatic = true;
+ } else {
+ $this->_automatic = false;
+ }
+
+ $locale = null;
+ if (!empty($options['locale'])) {
+ $locale = $options['locale'];
+ unset($options['locale']);
+ }
+
+ $this->setOptions($options);
+ $options['locale'] = $locale;
+
+ if (!empty($options['content'])) {
+ $this->addTranslation($options);
+ }
+
+ if ($this->getLocale() !== (string) $options['locale']) {
+ $this->setLocale($options['locale']);
+ }
+ }
+
+ /**
+ * Add translations
+ *
+ * This may be a new language or additional content for an existing language
+ * If the key 'clear' is true, then translations for the specified
+ * language will be replaced and added otherwise
+ *
+ * @param array|Zend_Config $options Options and translations to be added
+ * @throws Zend_Translate_Exception
+ * @return Zend_Translate_Adapter Provides fluent interface
+ */
+ public function addTranslation($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (func_num_args() > 1) {
+ $args = func_get_args();
+ $options = array();
+ $options['content'] = array_shift($args);
+
+ if (!empty($args)) {
+ $options['locale'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $opt = array_shift($args);
+ $options = array_merge($opt, $options);
+ }
+ } else if (!is_array($options)) {
+ $options = array('content' => $options);
+ }
+
+ if (!isset($options['content']) || empty($options['content'])) {
+ throw new Zend_Translate_Exception("Required option 'content' is missing");
+ }
+
+ $originate = null;
+ if (!empty($options['locale'])) {
+ $originate = (string) $options['locale'];
+ }
+
+ if ((array_key_exists('log', $options)) && !($options['log'] instanceof Zend_Log)) {
+ throw new Zend_Translate_Exception('Instance of Zend_Log expected for option log');
+ }
+
+ try {
+ if (!($options['content'] instanceof Zend_Translate) && !($options['content'] instanceof Zend_Translate_Adapter)) {
+ if (empty($options['locale'])) {
+ $options['locale'] = null;
+ }
+
+ $options['locale'] = Zend_Locale::findLocale($options['locale']);
+ }
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Translate_Exception("The given Language '{$options['locale']}' does not exist", 0, $e);
+ }
+
+ $options = $options + $this->_options;
+ if (is_string($options['content']) and is_dir($options['content'])) {
+ $options['content'] = realpath($options['content']);
+ $prev = '';
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveRegexIterator(
+ new RecursiveDirectoryIterator($options['content'], RecursiveDirectoryIterator::KEY_AS_PATHNAME),
+ '/^(?!.*(\.svn|\.cvs)).*$/', RecursiveRegexIterator::MATCH
+ ),
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+
+ foreach ($iterator as $directory => $info) {
+ $file = $info->getFilename();
+ if (is_array($options['ignore'])) {
+ foreach ($options['ignore'] as $key => $ignore) {
+ if (strpos($key, 'regex') !== false) {
+ if (preg_match($ignore, $directory)) {
+ // ignore files matching the given regex from option 'ignore' and all files below
+ continue 2;
+ }
+ } else if (strpos($directory, DIRECTORY_SEPARATOR . $ignore) !== false) {
+ // ignore files matching first characters from option 'ignore' and all files below
+ continue 2;
+ }
+ }
+ } else {
+ if (strpos($directory, DIRECTORY_SEPARATOR . $options['ignore']) !== false) {
+ // ignore files matching first characters from option 'ignore' and all files below
+ continue;
+ }
+ }
+
+ if ($info->isDir()) {
+ // pathname as locale
+ if (($options['scan'] === self::LOCALE_DIRECTORY) and (Zend_Locale::isLocale($file, true, false))) {
+ $options['locale'] = $file;
+ $prev = (string) $options['locale'];
+ }
+ } else if ($info->isFile()) {
+ // filename as locale
+ if ($options['scan'] === self::LOCALE_FILENAME) {
+ $filename = explode('.', $file);
+ array_pop($filename);
+ $filename = implode('.', $filename);
+ if (Zend_Locale::isLocale((string) $filename, true, false)) {
+ $options['locale'] = (string) $filename;
+ } else {
+ $parts = explode('.', $file);
+ $parts2 = array();
+ foreach($parts as $token) {
+ $parts2 += explode('_', $token);
+ }
+ $parts = array_merge($parts, $parts2);
+ $parts2 = array();
+ foreach($parts as $token) {
+ $parts2 += explode('-', $token);
+ }
+ $parts = array_merge($parts, $parts2);
+ $parts = array_unique($parts);
+ $prev = '';
+ foreach($parts as $token) {
+ if (Zend_Locale::isLocale($token, true, false)) {
+ if (strlen($prev) <= strlen($token)) {
+ $options['locale'] = $token;
+ $prev = $token;
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ $options['content'] = $info->getPathname();
+ $this->_addTranslationData($options);
+ } catch (Zend_Translate_Exception $e) {
+ // ignore failed sources while scanning
+ }
+ }
+ }
+
+ unset($iterator);
+ } else {
+ $this->_addTranslationData($options);
+ }
+
+ if ((isset($this->_translate[$originate]) === true) and (count($this->_translate[$originate]) > 0)) {
+ $this->setLocale($originate);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets new adapter options
+ *
+ * @param array $options Adapter options
+ * @throws Zend_Translate_Exception
+ * @return Zend_Translate_Adapter Provides fluent interface
+ */
+ public function setOptions(array $options = array())
+ {
+ $change = false;
+ $locale = null;
+ foreach ($options as $key => $option) {
+ if ($key == 'locale') {
+ $locale = $option;
+ } else if ((isset($this->_options[$key]) and ($this->_options[$key] != $option)) or
+ !isset($this->_options[$key])) {
+ if (($key == 'log') && !($option instanceof Zend_Log)) {
+ throw new Zend_Translate_Exception('Instance of Zend_Log expected for option log');
+ }
+
+ if ($key == 'cache') {
+ self::setCache($option);
+ continue;
+ }
+
+ $this->_options[$key] = $option;
+ $change = true;
+ }
+ }
+
+ if ($locale !== null) {
+ $this->setLocale($locale);
+ }
+
+ if (isset(self::$_cache) and ($change == true)) {
+ $id = 'Zend_Translate_' . $this->toString() . '_Options';
+ if (self::$_cacheTags) {
+ self::$_cache->save($this->_options, $id, array($this->_options['tag']));
+ } else {
+ self::$_cache->save($this->_options, $id);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the adapters name and it's options
+ *
+ * @param string|null $optionKey String returns this option
+ * null returns all options
+ * @return integer|string|array|null
+ */
+ public function getOptions($optionKey = null)
+ {
+ if ($optionKey === null) {
+ return $this->_options;
+ }
+
+ if (isset($this->_options[$optionKey]) === true) {
+ return $this->_options[$optionKey];
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets locale
+ *
+ * @return Zend_Locale|string|null
+ */
+ public function getLocale()
+ {
+ return $this->_options['locale'];
+ }
+
+ /**
+ * Sets locale
+ *
+ * @param string|Zend_Locale $locale Locale to set
+ * @throws Zend_Translate_Exception
+ * @return Zend_Translate_Adapter Provides fluent interface
+ */
+ public function setLocale($locale)
+ {
+ if (($locale === "auto") or ($locale === null)) {
+ $this->_automatic = true;
+ } else {
+ $this->_automatic = false;
+ }
+
+ try {
+ $locale = Zend_Locale::findLocale($locale);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Translate_Exception("The given Language ({$locale}) does not exist", 0, $e);
+ }
+
+ if (!isset($this->_translate[$locale])) {
+ $temp = explode('_', $locale);
+ if (!isset($this->_translate[$temp[0]]) and !isset($this->_translate[$locale])) {
+ if (!$this->_options['disableNotices']) {
+ if ($this->_options['log']) {
+ $this->_options['log']->log("The language '{$locale}' has to be added before it can be used.", $this->_options['logPriority']);
+ } else {
+ trigger_error("The language '{$locale}' has to be added before it can be used.", E_USER_NOTICE);
+ }
+ }
+ }
+
+ $locale = $temp[0];
+ }
+
+ if (empty($this->_translate[$locale])) {
+ if (!$this->_options['disableNotices']) {
+ if ($this->_options['log']) {
+ $this->_options['log']->log("No translation for the language '{$locale}' available.", $this->_options['logPriority']);
+ } else {
+ trigger_error("No translation for the language '{$locale}' available.", E_USER_NOTICE);
+ }
+ }
+ }
+
+ if ($this->_options['locale'] != $locale) {
+ $this->_options['locale'] = $locale;
+
+ if (isset(self::$_cache)) {
+ $id = 'Zend_Translate_' . $this->toString() . '_Options';
+ if (self::$_cacheTags) {
+ self::$_cache->save($this->_options, $id, array($this->_options['tag']));
+ } else {
+ self::$_cache->save($this->_options, $id);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the available languages from this adapter
+ *
+ * @return array|null
+ */
+ public function getList()
+ {
+ $list = array_keys($this->_translate);
+ $result = null;
+ foreach($list as $value) {
+ if (!empty($this->_translate[$value])) {
+ $result[$value] = $value;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the message id for a given translation
+ * If no locale is given, the actual language will be used
+ *
+ * @param string $message Message to get the key for
+ * @param string|Zend_Locale $locale (optional) Language to return the message ids from
+ * @return string|array|false
+ */
+ public function getMessageId($message, $locale = null)
+ {
+ if (empty($locale) or !$this->isAvailable($locale)) {
+ $locale = $this->_options['locale'];
+ }
+
+ return array_search($message, $this->_translate[(string) $locale]);
+ }
+
+ /**
+ * Returns all available message ids from this adapter
+ * If no locale is given, the actual language will be used
+ *
+ * @param string|Zend_Locale $locale (optional) Language to return the message ids from
+ * @return array
+ */
+ public function getMessageIds($locale = null)
+ {
+ if (empty($locale) or !$this->isAvailable($locale)) {
+ $locale = $this->_options['locale'];
+ }
+
+ return array_keys($this->_translate[(string) $locale]);
+ }
+
+ /**
+ * Returns all available translations from this adapter
+ * If no locale is given, the actual language will be used
+ * If 'all' is given the complete translation dictionary will be returned
+ *
+ * @param string|Zend_Locale $locale (optional) Language to return the messages from
+ * @return array
+ */
+ public function getMessages($locale = null)
+ {
+ if ($locale === 'all') {
+ return $this->_translate;
+ }
+
+ if ((empty($locale) === true) or ($this->isAvailable($locale) === false)) {
+ $locale = $this->_options['locale'];
+ }
+
+ return $this->_translate[(string) $locale];
+ }
+
+ /**
+ * Is the wished language available ?
+ *
+ * @see Zend_Locale
+ * @param string|Zend_Locale $locale Language to search for, identical with locale identifier,
+ * @see Zend_Locale for more information
+ * @return boolean
+ */
+ public function isAvailable($locale)
+ {
+ $return = isset($this->_translate[(string) $locale]);
+ return $return;
+ }
+
+ /**
+ * Load translation data
+ *
+ * @param mixed $data
+ * @param string|Zend_Locale $locale
+ * @param array $options (optional)
+ * @return array
+ */
+ abstract protected function _loadTranslationData($data, $locale, array $options = array());
+
+ /**
+ * Internal function for adding translation data
+ *
+ * This may be a new language or additional data for an existing language
+ * If the options 'clear' is true, then the translation data for the specified
+ * language is replaced and added otherwise
+ *
+ * @see Zend_Locale
+ * @param array|Zend_Config $content Translation data to add
+ * @throws Zend_Translate_Exception
+ * @return Zend_Translate_Adapter Provides fluent interface
+ */
+ private function _addTranslationData($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (func_num_args() > 1) {
+ $args = func_get_args();
+ $options['content'] = array_shift($args);
+
+ if (!empty($args)) {
+ $options['locale'] = array_shift($args);
+ }
+
+ if (!empty($args)) {
+ $options += array_shift($args);
+ }
+ }
+
+ if (($options['content'] instanceof Zend_Translate) || ($options['content'] instanceof Zend_Translate_Adapter)) {
+ $options['usetranslateadapter'] = true;
+ if (!empty($options['locale']) && ($options['locale'] !== 'auto')) {
+ $options['content'] = $options['content']->getMessages($options['locale']);
+ } else {
+ $content = $options['content'];
+ $locales = $content->getList();
+ foreach ($locales as $locale) {
+ $options['locale'] = $locale;
+ $options['content'] = $content->getMessages($locale);
+ $this->_addTranslationData($options);
+ }
+
+ return $this;
+ }
+ }
+
+ try {
+ $options['locale'] = Zend_Locale::findLocale($options['locale']);
+ } catch (Zend_Locale_Exception $e) {
+ throw new Zend_Translate_Exception("The given Language '{$options['locale']}' does not exist", 0, $e);
+ }
+
+ if ($options['clear'] || !isset($this->_translate[$options['locale']])) {
+ $this->_translate[$options['locale']] = array();
+ }
+
+ $read = true;
+ if (isset(self::$_cache)) {
+ $id = 'Zend_Translate_' . md5(serialize($options['content'])) . '_' . $this->toString();
+ $temp = self::$_cache->load($id);
+ if ($temp) {
+ $read = false;
+ }
+ }
+
+ if ($options['reload']) {
+ $read = true;
+ }
+
+ if ($read) {
+ if (!empty($options['usetranslateadapter'])) {
+ $temp = array($options['locale'] => $options['content']);
+ } else {
+ $temp = $this->_loadTranslationData($options['content'], $options['locale'], $options);
+ }
+ }
+
+ if (empty($temp)) {
+ $temp = array();
+ }
+
+ $keys = array_keys($temp);
+ foreach($keys as $key) {
+ if (!isset($this->_translate[$key])) {
+ $this->_translate[$key] = array();
+ }
+
+ if (array_key_exists($key, $temp) && is_array($temp[$key])) {
+ $this->_translate[$key] = $temp[$key] + $this->_translate[$key];
+ }
+ }
+
+ if ($this->_automatic === true) {
+ $find = new Zend_Locale($options['locale']);
+ $browser = $find->getEnvironment() + $find->getBrowser();
+ arsort($browser);
+ foreach($browser as $language => $quality) {
+ if (isset($this->_translate[$language])) {
+ $this->_options['locale'] = $language;
+ break;
+ }
+ }
+ }
+
+ if (($read) and (isset(self::$_cache))) {
+ $id = 'Zend_Translate_' . md5(serialize($options['content'])) . '_' . $this->toString();
+ if (self::$_cacheTags) {
+ self::$_cache->save($temp, $id, array($this->_options['tag']));
+ } else {
+ self::$_cache->save($temp, $id);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Translates the given string
+ * returns the translation
+ *
+ * @see Zend_Locale
+ * @param string|array $messageId Translation string, or Array for plural translations
+ * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with
+ * locale identifier, @see Zend_Locale for more information
+ * @return string
+ */
+ public function translate($messageId, $locale = null)
+ {
+ if ($locale === null) {
+ $locale = $this->_options['locale'];
+ }
+
+ $plural = null;
+ if (is_array($messageId)) {
+ if (count($messageId) > 2) {
+ $number = array_pop($messageId);
+ if (!is_numeric($number)) {
+ $plocale = $number;
+ $number = array_pop($messageId);
+ } else {
+ $plocale = 'en';
+ }
+
+ $plural = $messageId;
+ $messageId = $messageId[0];
+ } else {
+ $messageId = $messageId[0];
+ }
+ }
+
+ if (!Zend_Locale::isLocale($locale, true, false)) {
+ if (!Zend_Locale::isLocale($locale, false, false)) {
+ // language does not exist, return original string
+ $this->_log($messageId, $locale);
+ // use rerouting when enabled
+ if (!empty($this->_options['route'])) {
+ if (array_key_exists($locale, $this->_options['route']) &&
+ !array_key_exists($locale, $this->_routed)) {
+ $this->_routed[$locale] = true;
+ return $this->translate($messageId, $this->_options['route'][$locale]);
+ }
+ }
+
+ $this->_routed = array();
+ if ($plural === null) {
+ return $messageId;
+ }
+
+ $rule = Zend_Translate_Plural::getPlural($number, $plocale);
+ if (!isset($plural[$rule])) {
+ $rule = 0;
+ }
+
+ return $plural[$rule];
+ }
+
+ $locale = new Zend_Locale($locale);
+ }
+
+ $locale = (string) $locale;
+ if ((is_string($messageId) || is_int($messageId)) && isset($this->_translate[$locale][$messageId])) {
+ // return original translation
+ if ($plural === null) {
+ $this->_routed = array();
+ return $this->_translate[$locale][$messageId];
+ }
+
+ $rule = Zend_Translate_Plural::getPlural($number, $locale);
+ if (isset($this->_translate[$locale][$plural[0]][$rule])) {
+ $this->_routed = array();
+ return $this->_translate[$locale][$plural[0]][$rule];
+ }
+ } else if (strlen($locale) != 2) {
+ // faster than creating a new locale and separate the leading part
+ $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
+
+ if ((is_string($messageId) || is_int($messageId)) && isset($this->_translate[$locale][$messageId])) {
+ // return regionless translation (en_US -> en)
+ if ($plural === null) {
+ $this->_routed = array();
+ return $this->_translate[$locale][$messageId];
+ }
+
+ $rule = Zend_Translate_Plural::getPlural($number, $locale);
+ if (isset($this->_translate[$locale][$plural[0]][$rule])) {
+ $this->_routed = array();
+ return $this->_translate[$locale][$plural[0]][$rule];
+ }
+ }
+ }
+
+ $this->_log($messageId, $locale);
+ // use rerouting when enabled
+ if (!empty($this->_options['route'])) {
+ if (array_key_exists($locale, $this->_options['route']) &&
+ !array_key_exists($locale, $this->_routed)) {
+ $this->_routed[$locale] = true;
+ return $this->translate($messageId, $this->_options['route'][$locale]);
+ }
+ }
+
+ $this->_routed = array();
+ if ($plural === null) {
+ return $messageId;
+ }
+
+ $rule = Zend_Translate_Plural::getPlural($number, $plocale);
+ if (!isset($plural[$rule])) {
+ $rule = 0;
+ }
+
+ return $plural[$rule];
+ }
+
+ /**
+ * Translates the given string using plural notations
+ * Returns the translated string
+ *
+ * @see Zend_Locale
+ * @param string $singular Singular translation string
+ * @param string $plural Plural translation string
+ * @param integer $number Number for detecting the correct plural
+ * @param string|Zend_Locale $locale (Optional) Locale/Language to use, identical with
+ * locale identifier, @see Zend_Locale for more information
+ * @return string
+ */
+ public function plural($singular, $plural, $number, $locale = null)
+ {
+ return $this->translate(array($singular, $plural, $number), $locale);
+ }
+
+ /**
+ * Logs a message when the log option is set
+ *
+ * @param string $message Message to log
+ * @param String $locale Locale to log
+ */
+ protected function _log($message, $locale) {
+ if ($this->_options['logUntranslated']) {
+ $message = str_replace('%message%', $message, $this->_options['logMessage']);
+ $message = str_replace('%locale%', $locale, $message);
+ if ($this->_options['log']) {
+ $this->_options['log']->log($message, $this->_options['logPriority']);
+ } else {
+ trigger_error($message, E_USER_NOTICE);
+ }
+ }
+ }
+
+ /**
+ * Translates the given string
+ * returns the translation
+ *
+ * @param string $messageId Translation string
+ * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with locale
+ * identifier, @see Zend_Locale for more information
+ * @return string
+ */
+ public function _($messageId, $locale = null)
+ {
+ return $this->translate($messageId, $locale);
+ }
+
+ /**
+ * Checks if a string is translated within the source or not
+ * returns boolean
+ *
+ * @param string $messageId Translation string
+ * @param boolean $original (optional) Allow translation only for original language
+ * when true, a translation for 'en_US' would give false when it can
+ * be translated with 'en' only
+ * @param string|Zend_Locale $locale (optional) Locale/Language to use, identical with locale identifier,
+ * see Zend_Locale for more information
+ * @return boolean
+ */
+ public function isTranslated($messageId, $original = false, $locale = null)
+ {
+ if (($original !== false) and ($original !== true)) {
+ $locale = $original;
+ $original = false;
+ }
+
+ if ($locale === null) {
+ $locale = $this->_options['locale'];
+ }
+
+ if (!Zend_Locale::isLocale($locale, true, false)) {
+ if (!Zend_Locale::isLocale($locale, false, false)) {
+ // language does not exist, return original string
+ return false;
+ }
+
+ $locale = new Zend_Locale($locale);
+ }
+
+ $locale = (string) $locale;
+ if ((is_string($messageId) || is_int($messageId)) && isset($this->_translate[$locale][$messageId])) {
+ // return original translation
+ return true;
+ } else if ((strlen($locale) != 2) and ($original === false)) {
+ // faster than creating a new locale and separate the leading part
+ $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
+
+ if ((is_string($messageId) || is_int($messageId)) && isset($this->_translate[$locale][$messageId])) {
+ // return regionless translation (en_US -> en)
+ return true;
+ }
+ }
+
+ // No translation found, return original
+ return false;
+ }
+
+ /**
+ * Returns the set cache
+ *
+ * @return Zend_Cache_Core The set cache
+ */
+ public static function getCache()
+ {
+ return self::$_cache;
+ }
+
+ /**
+ * Sets a cache for all Zend_Translate_Adapters
+ *
+ * @param Zend_Cache_Core $cache Cache to store to
+ */
+ public static function setCache(Zend_Cache_Core $cache)
+ {
+ self::$_cache = $cache;
+ self::_getTagSupportForCache();
+ }
+
+ /**
+ * Returns true when a cache is set
+ *
+ * @return boolean
+ */
+ public static function hasCache()
+ {
+ if (self::$_cache !== null) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Removes any set cache
+ *
+ * @return void
+ */
+ public static function removeCache()
+ {
+ self::$_cache = null;
+ }
+
+ /**
+ * Clears all set cache data
+ *
+ * @param string $tag Tag to clear when the default tag name is not used
+ * @return void
+ */
+ public static function clearCache($tag = null)
+ {
+ if (self::$_cacheTags) {
+ if ($tag == null) {
+ $tag = 'Zend_Translate';
+ }
+
+ self::$_cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array($tag));
+ } else {
+ self::$_cache->clean(Zend_Cache::CLEANING_MODE_ALL);
+ }
+ }
+
+ /**
+ * Returns the adapter name
+ *
+ * @return string
+ */
+ abstract public function toString();
+
+ /**
+ * Internal method to check if the given cache supports tags
+ *
+ * @param Zend_Cache $cache
+ */
+ private static function _getTagSupportForCache()
+ {
+ $backend = self::$_cache->getBackend();
+ if ($backend instanceof Zend_Cache_Backend_ExtendedInterface) {
+ $cacheOptions = $backend->getCapabilities();
+ self::$_cacheTags = $cacheOptions['tags'];
+ } else {
+ self::$_cacheTags = false;
+ }
+
+ return self::$_cacheTags;
+ }
+}
diff --git a/library/vendor/Zend/Translate/Exception.php b/library/vendor/Zend/Translate/Exception.php
new file mode 100644
index 0000000..8502ec1
--- /dev/null
+++ b/library/vendor/Zend/Translate/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Translate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Translate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Translate_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Translate/Plural.php b/library/vendor/Zend/Translate/Plural.php
new file mode 100644
index 0000000..9920828
--- /dev/null
+++ b/library/vendor/Zend/Translate/Plural.php
@@ -0,0 +1,223 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Utility class for returning the plural rules according to the given locale
+ *
+ * @category Zend
+ * @package Zend_Locale
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Translate_Plural
+{
+ /**
+ * Manual rule to use
+ *
+ * @var string
+ */
+ protected static $_plural = array();
+
+ /**
+ * Returns the plural definition to use
+ *
+ * @param integer $number Number for plural selection
+ * @param string $locale Locale to use
+ * @return integer Plural number to use
+ */
+ public static function getPlural($number, $locale)
+ {
+ if ($locale == "pt_BR") {
+ // temporary set a locale for brasilian
+ $locale = "xbr";
+ }
+
+ if (strlen($locale) > 3) {
+ $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
+ }
+
+ if (isset(self::$_plural[$locale])) {
+ $return = call_user_func(self::$_plural[$locale], $number);
+
+ if (!is_int($return) || ($return < 0)) {
+ $return = 0;
+ }
+
+ return $return;
+ }
+
+ switch($locale) {
+ case 'az':
+ case 'bo':
+ case 'dz':
+ case 'id':
+ case 'ja':
+ case 'jv':
+ case 'ka':
+ case 'km':
+ case 'kn':
+ case 'ko':
+ case 'ms':
+ case 'th':
+ case 'tr':
+ case 'vi':
+ case 'zh':
+ return 0;
+ break;
+
+ case 'af':
+ case 'bn':
+ case 'bg':
+ case 'ca':
+ case 'da':
+ case 'de':
+ case 'el':
+ case 'en':
+ case 'eo':
+ case 'es':
+ case 'et':
+ case 'eu':
+ case 'fa':
+ case 'fi':
+ case 'fo':
+ case 'fur':
+ case 'fy':
+ case 'gl':
+ case 'gu':
+ case 'ha':
+ case 'he':
+ case 'hu':
+ case 'is':
+ case 'it':
+ case 'ku':
+ case 'lb':
+ case 'ml':
+ case 'mn':
+ case 'mr':
+ case 'nah':
+ case 'nb':
+ case 'ne':
+ case 'nl':
+ case 'nn':
+ case 'no':
+ case 'om':
+ case 'or':
+ case 'pa':
+ case 'pap':
+ case 'ps':
+ case 'pt':
+ case 'so':
+ case 'sq':
+ case 'sv':
+ case 'sw':
+ case 'ta':
+ case 'te':
+ case 'tk':
+ case 'ur':
+ case 'zu':
+ return ($number == 1) ? 0 : 1;
+
+ case 'am':
+ case 'bh':
+ case 'fil':
+ case 'fr':
+ case 'gun':
+ case 'hi':
+ case 'ln':
+ case 'mg':
+ case 'nso':
+ case 'xbr':
+ case 'ti':
+ case 'wa':
+ return (($number == 0) || ($number == 1)) ? 0 : 1;
+
+ case 'be':
+ case 'bs':
+ case 'hr':
+ case 'ru':
+ case 'sr':
+ case 'uk':
+ return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+
+ case 'cs':
+ case 'sk':
+ return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
+
+ case 'ga':
+ return ($number == 1) ? 0 : (($number == 2) ? 1 : 2);
+
+ case 'lt':
+ return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
+
+ case 'sl':
+ return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3));
+
+ case 'mk':
+ return ($number % 10 == 1) ? 0 : 1;
+
+ case 'mt':
+ return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3));
+
+ case 'lv':
+ return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2);
+
+ case 'pl':
+ return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2);
+
+ case 'cy':
+ return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3));
+
+ case 'ro':
+ return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2);
+
+ case 'ar':
+ return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number >= 3) && ($number <= 10)) ? 3 : ((($number >= 11) && ($number <= 99)) ? 4 : 5))));
+
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Set's a new plural rule
+ *
+ * @param string $rule Callback which acts as rule
+ * @param string $locale Locale which is used for this callback
+ * @return null
+ */
+ public static function setPlural($rule, $locale)
+ {
+ if ($locale == "pt_BR") {
+ // temporary set a locale for brasilian
+ $locale = "xbr";
+ }
+
+ if (strlen($locale) > 3) {
+ $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
+ }
+
+ if (!is_callable($rule)) {
+ throw new Zend_Translate_Exception('The given rule can not be called');
+ }
+
+ self::$_plural[$locale] = $rule;
+ }
+}
diff --git a/library/vendor/Zend/Uri.php b/library/vendor/Zend/Uri.php
new file mode 100644
index 0000000..ca152b5
--- /dev/null
+++ b/library/vendor/Zend/Uri.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Uri
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Abstract class for all Zend_Uri handlers
+ *
+ * @category Zend
+ * @package Zend_Uri
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Uri
+{
+ /**
+ * Scheme of this URI (http, ftp, etc.)
+ *
+ * @var string
+ */
+ protected $_scheme = '';
+
+ /**
+ * Global configuration array
+ *
+ * @var array
+ */
+ static protected $_config = array(
+ 'allow_unwise' => false
+ );
+
+ /**
+ * Return a string representation of this URI.
+ *
+ * @see getUri()
+ * @return string
+ */
+ public function __toString()
+ {
+ try {
+ return $this->getUri();
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ return '';
+ }
+ }
+
+ /**
+ * Convenience function, checks that a $uri string is well-formed
+ * by validating it but not returning an object. Returns TRUE if
+ * $uri is a well-formed URI, or FALSE otherwise.
+ *
+ * @param string $uri The URI to check
+ * @return boolean
+ */
+ public static function check($uri)
+ {
+ try {
+ $uri = self::factory($uri);
+ } catch (Exception $e) {
+ return false;
+ }
+
+ return $uri->valid();
+ }
+
+ /**
+ * Create a new Zend_Uri object for a URI. If building a new URI, then $uri should contain
+ * only the scheme (http, ftp, etc). Otherwise, supply $uri with the complete URI.
+ *
+ * @param string $uri The URI form which a Zend_Uri instance is created
+ * @param string $className The name of the class to use in order to manipulate URI
+ * @throws Zend_Uri_Exception When an empty string was supplied for the scheme
+ * @throws Zend_Uri_Exception When an illegal scheme is supplied
+ * @throws Zend_Uri_Exception When the scheme is not supported
+ * @throws Zend_Uri_Exception When $className doesn't exist or doesn't implements Zend_Uri
+ * @return Zend_Uri
+ * @link http://www.faqs.org/rfcs/rfc2396.html
+ */
+ public static function factory($uri = 'http', $className = null)
+ {
+ // Separate the scheme from the scheme-specific parts
+ $uri = explode(':', $uri, 2);
+ $scheme = strtolower($uri[0]);
+ $schemeSpecific = isset($uri[1]) === true ? $uri[1] : '';
+
+ if (strlen($scheme) === 0) {
+ throw new Zend_Uri_Exception('An empty string was supplied for the scheme');
+ }
+
+ // Security check: $scheme is used to load a class file, so only alphanumerics are allowed.
+ if (ctype_alnum($scheme) === false) {
+ throw new Zend_Uri_Exception('Illegal scheme supplied, only alphanumeric characters are permitted');
+ }
+
+ if ($className === null) {
+ /**
+ * Create a new Zend_Uri object for the $uri. If a subclass of Zend_Uri exists for the
+ * scheme, return an instance of that class. Otherwise, a Zend_Uri_Exception is thrown.
+ */
+ switch ($scheme) {
+ case 'http':
+ // Break intentionally omitted
+ case 'https':
+ $className = 'Zend_Uri_Http';
+ break;
+
+ case 'mailto':
+ // TODO
+ default:
+ throw new Zend_Uri_Exception("Scheme \"$scheme\" is not supported");
+ break;
+ }
+ }
+
+ try {
+ Zend_Loader::loadClass($className);
+ } catch (Exception $e) {
+ throw new Zend_Uri_Exception("\"$className\" not found");
+ }
+
+ $schemeHandler = new $className($scheme, $schemeSpecific);
+
+ if (! $schemeHandler instanceof Zend_Uri) {
+ throw new Zend_Uri_Exception("\"$className\" is not an instance of Zend_Uri");
+ }
+
+ return $schemeHandler;
+ }
+
+ /**
+ * Get the URI's scheme
+ *
+ * @return string|false Scheme or false if no scheme is set.
+ */
+ public function getScheme()
+ {
+ if (empty($this->_scheme) === false) {
+ return $this->_scheme;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set global configuration options
+ *
+ * @param Zend_Config|array $config
+ */
+ static public function setConfig($config)
+ {
+ if ($config instanceof Zend_Config) {
+ $config = $config->toArray();
+ } elseif (!is_array($config)) {
+ throw new Zend_Uri_Exception("Config must be an array or an instance of Zend_Config.");
+ }
+
+ foreach ($config as $k => $v) {
+ self::$_config[$k] = $v;
+ }
+ }
+
+ /**
+ * Zend_Uri and its subclasses cannot be instantiated directly.
+ * Use Zend_Uri::factory() to return a new Zend_Uri object.
+ *
+ * @param string $scheme The scheme of the URI
+ * @param string $schemeSpecific The scheme-specific part of the URI
+ */
+ abstract protected function __construct($scheme, $schemeSpecific = '');
+
+ /**
+ * Return a string representation of this URI.
+ *
+ * @return string
+ */
+ abstract public function getUri();
+
+ /**
+ * Returns TRUE if this URI is valid, or FALSE otherwise.
+ *
+ * @return boolean
+ */
+ abstract public function valid();
+}
diff --git a/library/vendor/Zend/Uri/Exception.php b/library/vendor/Zend/Uri/Exception.php
new file mode 100644
index 0000000..780ffbc
--- /dev/null
+++ b/library/vendor/Zend/Uri/Exception.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Uri
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * Exceptions for Zend_Uri
+ *
+ * @category Zend
+ * @package Zend_Uri
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Uri_Exception extends Zend_Exception
+{
+}
diff --git a/library/vendor/Zend/Uri/Http.php b/library/vendor/Zend/Uri/Http.php
new file mode 100644
index 0000000..a964a25
--- /dev/null
+++ b/library/vendor/Zend/Uri/Http.php
@@ -0,0 +1,745 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Uri
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Uri
+ */
+
+/**
+ * @see Zend_Validate_Hostname
+ */
+
+/**
+ * HTTP(S) URI handler
+ *
+ * @category Zend
+ * @package Zend_Uri
+ * @uses Zend_Uri
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Uri_Http extends Zend_Uri
+{
+ /**
+ * Character classes for validation regular expressions
+ */
+ const CHAR_ALNUM = 'A-Za-z0-9';
+ const CHAR_MARK = '-_.!~*\'()\[\]';
+ const CHAR_RESERVED = ';\/?:@&=+$,';
+ const CHAR_SEGMENT = ':@&=+$,;';
+ const CHAR_UNWISE = '{}|\\\\^`';
+
+ /**
+ * HTTP username
+ *
+ * @var string
+ */
+ protected $_username = '';
+
+ /**
+ * HTTP password
+ *
+ * @var string
+ */
+ protected $_password = '';
+
+ /**
+ * HTTP host
+ *
+ * @var string
+ */
+ protected $_host = '';
+
+ /**
+ * HTTP post
+ *
+ * @var string
+ */
+ protected $_port = '';
+
+ /**
+ * HTTP part
+ *
+ * @var string
+ */
+ protected $_path = '';
+
+ /**
+ * HTTP query
+ *
+ * @var string
+ */
+ protected $_query = '';
+
+ /**
+ * HTTP fragment
+ *
+ * @var string
+ */
+ protected $_fragment = '';
+
+ /**
+ * Regular expression grammar rules for validation; values added by constructor
+ *
+ * @var array
+ */
+ protected $_regex = array();
+
+ /**
+ * Constructor accepts a string $scheme (e.g., http, https) and a scheme-specific part of the URI
+ * (e.g., example.com/path/to/resource?query=param#fragment)
+ *
+ * @param string $scheme The scheme of the URI
+ * @param string $schemeSpecific The scheme-specific part of the URI
+ * @throws Zend_Uri_Exception When the URI is not valid
+ */
+ protected function __construct($scheme, $schemeSpecific = '')
+ {
+ // Set the scheme
+ $this->_scheme = $scheme;
+
+ // Set up grammar rules for validation via regular expressions. These
+ // are to be used with slash-delimited regular expression strings.
+
+ // Escaped special characters (eg. '%25' for '%')
+ $this->_regex['escaped'] = '%[[:xdigit:]]{2}';
+
+ // Unreserved characters
+ $this->_regex['unreserved'] = '[' . self::CHAR_ALNUM . self::CHAR_MARK . ']';
+
+ // Segment can use escaped, unreserved or a set of additional chars
+ $this->_regex['segment'] = '(?:' . $this->_regex['escaped'] . '|[' .
+ self::CHAR_ALNUM . self::CHAR_MARK . self::CHAR_SEGMENT . '])*';
+
+ // Path can be a series of segmets char strings seperated by '/'
+ $this->_regex['path'] = '(?:\/(?:' . $this->_regex['segment'] . ')?)+';
+
+ // URI characters can be escaped, alphanumeric, mark or reserved chars
+ $this->_regex['uric'] = '(?:' . $this->_regex['escaped'] . '|[' .
+ self::CHAR_ALNUM . self::CHAR_MARK . self::CHAR_RESERVED .
+
+ // If unwise chars are allowed, add them to the URI chars class
+ (self::$_config['allow_unwise'] ? self::CHAR_UNWISE : '') . '])';
+
+ // If no scheme-specific part was supplied, the user intends to create
+ // a new URI with this object. No further parsing is required.
+ if (strlen($schemeSpecific) === 0) {
+ return;
+ }
+
+ // Parse the scheme-specific URI parts into the instance variables.
+ $this->_parseUri($schemeSpecific);
+
+ // Validate the URI
+ if ($this->valid() === false) {
+ throw new Zend_Uri_Exception('Invalid URI supplied');
+ }
+ }
+
+ /**
+ * Creates a Zend_Uri_Http from the given string
+ *
+ * @param string $uri String to create URI from, must start with
+ * 'http://' or 'https://'
+ * @throws InvalidArgumentException When the given $uri is not a string or
+ * does not start with http:// or https://
+ * @throws Zend_Uri_Exception When the given $uri is invalid
+ * @return Zend_Uri_Http
+ */
+ public static function fromString($uri)
+ {
+ if (is_string($uri) === false) {
+ throw new Zend_Uri_Exception('$uri is not a string');
+ }
+
+ $uri = explode(':', $uri, 2);
+ $scheme = strtolower($uri[0]);
+ $schemeSpecific = isset($uri[1]) === true ? $uri[1] : '';
+
+ if (in_array($scheme, array('http', 'https')) === false) {
+ throw new Zend_Uri_Exception("Invalid scheme: '$scheme'");
+ }
+
+ $schemeHandler = new Zend_Uri_Http($scheme, $schemeSpecific);
+ return $schemeHandler;
+ }
+
+ /**
+ * Parse the scheme-specific portion of the URI and place its parts into instance variables.
+ *
+ * @param string $schemeSpecific The scheme-specific portion to parse
+ * @throws Zend_Uri_Exception When scheme-specific decoposition fails
+ * @throws Zend_Uri_Exception When authority decomposition fails
+ * @return void
+ */
+ protected function _parseUri($schemeSpecific)
+ {
+ // High-level decomposition parser
+ $pattern = '~^((//)([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))?$~';
+ $status = @preg_match($pattern, $schemeSpecific, $matches);
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: scheme-specific decomposition failed');
+ }
+
+ // Failed decomposition; no further processing needed
+ if ($status === false) {
+ return;
+ }
+
+ // Save URI components that need no further decomposition
+ $this->_path = isset($matches[4]) === true ? $matches[4] : '';
+ $this->_query = isset($matches[6]) === true ? $matches[6] : '';
+ $this->_fragment = isset($matches[8]) === true ? $matches[8] : '';
+
+ // Additional decomposition to get username, password, host, and port
+ $combo = isset($matches[3]) === true ? $matches[3] : '';
+ $pattern = '~^(([^:@]*)(:([^@]*))?@)?((?(?=[[])[[][^]]+[]]|[^:]+))(:(.*))?$~';
+ $status = @preg_match($pattern, $combo, $matches);
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: authority decomposition failed');
+ }
+
+ // Save remaining URI components
+ $this->_username = isset($matches[2]) === true ? $matches[2] : '';
+ $this->_password = isset($matches[4]) === true ? $matches[4] : '';
+ $this->_host = isset($matches[5]) === true
+ ? preg_replace('~^\[([^]]+)\]$~', '\1', $matches[5]) // Strip wrapper [] from IPv6 literal
+ : '';
+ $this->_port = isset($matches[7]) === true ? $matches[7] : '';
+ }
+
+ /**
+ * Returns a URI based on current values of the instance variables. If any
+ * part of the URI does not pass validation, then an exception is thrown.
+ *
+ * @throws Zend_Uri_Exception When one or more parts of the URI are invalid
+ * @return string
+ */
+ public function getUri()
+ {
+ if ($this->valid() === false) {
+ throw new Zend_Uri_Exception('One or more parts of the URI are invalid');
+ }
+
+ $password = strlen($this->_password) > 0 ? ":$this->_password" : '';
+ $auth = strlen($this->_username) > 0 ? "$this->_username$password@" : '';
+ $port = strlen($this->_port) > 0 ? ":$this->_port" : '';
+ $query = strlen($this->_query) > 0 ? "?$this->_query" : '';
+ $fragment = strlen($this->_fragment) > 0 ? "#$this->_fragment" : '';
+
+ return $this->_scheme
+ . '://'
+ . $auth
+ . $this->_host
+ . $port
+ . $this->_path
+ . $query
+ . $fragment;
+ }
+
+ /**
+ * Validate the current URI from the instance variables. Returns true if and only if all
+ * parts pass validation.
+ *
+ * @return boolean
+ */
+ public function valid()
+ {
+ // Return true if and only if all parts of the URI have passed validation
+ return $this->validateUsername()
+ and $this->validatePassword()
+ and $this->validateHost()
+ and $this->validatePort()
+ and $this->validatePath()
+ and $this->validateQuery()
+ and $this->validateFragment();
+ }
+
+ /**
+ * Returns the username portion of the URL, or FALSE if none.
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return strlen($this->_username) > 0 ? $this->_username : false;
+ }
+
+ /**
+ * Returns true if and only if the username passes validation. If no username is passed,
+ * then the username contained in the instance variable is used.
+ *
+ * @param string $username The HTTP username
+ * @throws Zend_Uri_Exception When username validation fails
+ * @return boolean
+ * @link http://www.faqs.org/rfcs/rfc2396.html
+ */
+ public function validateUsername($username = null)
+ {
+ if ($username === null) {
+ $username = $this->_username;
+ }
+
+ // If the username is empty, then it is considered valid
+ if (strlen($username) === 0) {
+ return true;
+ }
+
+ // Check the username against the allowed values
+ $status = @preg_match('/^(?:' . $this->_regex['escaped'] . '|[' .
+ self::CHAR_ALNUM . self::CHAR_MARK . ';:&=+$,' . '])+$/', $username);
+
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: username validation failed');
+ }
+
+ return $status === 1;
+ }
+
+ /**
+ * Sets the username for the current URI, and returns the old username
+ *
+ * @param string $username The HTTP username
+ * @throws Zend_Uri_Exception When $username is not a valid HTTP username
+ * @return string
+ */
+ public function setUsername($username)
+ {
+ if ($this->validateUsername($username) === false) {
+ throw new Zend_Uri_Exception("Username \"$username\" is not a valid HTTP username");
+ }
+
+ $oldUsername = $this->_username;
+ $this->_username = $username;
+
+ return $oldUsername;
+ }
+
+ /**
+ * Returns the password portion of the URL, or FALSE if none.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return strlen($this->_password) > 0 ? $this->_password : false;
+ }
+
+ /**
+ * Returns true if and only if the password passes validation. If no password is passed,
+ * then the password contained in the instance variable is used.
+ *
+ * @param string $password The HTTP password
+ * @throws Zend_Uri_Exception When password validation fails
+ * @return boolean
+ * @link http://www.faqs.org/rfcs/rfc2396.html
+ */
+ public function validatePassword($password = null)
+ {
+ if ($password === null) {
+ $password = $this->_password;
+ }
+
+ // If the password is empty, then it is considered valid
+ if (strlen($password) === 0) {
+ return true;
+ }
+
+ // If the password is nonempty, but there is no username, then it is considered invalid
+ if (strlen($password) > 0 and strlen($this->_username) === 0) {
+ return false;
+ }
+
+ // Check the password against the allowed values
+ $status = @preg_match('/^(?:' . $this->_regex['escaped'] . '|[' .
+ self::CHAR_ALNUM . self::CHAR_MARK . ';:&=+$,' . '])+$/', $password);
+
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: password validation failed.');
+ }
+
+ return $status == 1;
+ }
+
+ /**
+ * Sets the password for the current URI, and returns the old password
+ *
+ * @param string $password The HTTP password
+ * @throws Zend_Uri_Exception When $password is not a valid HTTP password
+ * @return string
+ */
+ public function setPassword($password)
+ {
+ if ($this->validatePassword($password) === false) {
+ throw new Zend_Uri_Exception("Password \"$password\" is not a valid HTTP password.");
+ }
+
+ $oldPassword = $this->_password;
+ $this->_password = $password;
+
+ return $oldPassword;
+ }
+
+ /**
+ * Returns the domain or host IP portion of the URL, or FALSE if none.
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return strlen($this->_host) > 0 ? $this->_host : false;
+ }
+
+ /**
+ * Returns true if and only if the host string passes validation. If no host is passed,
+ * then the host contained in the instance variable is used.
+ *
+ * @param string $host The HTTP host
+ * @return boolean
+ * @uses Zend_Filter
+ */
+ public function validateHost($host = null)
+ {
+ if ($host === null) {
+ $host = $this->_host;
+ }
+
+ // If the host is empty, then it is considered invalid
+ if (strlen($host) === 0) {
+ return false;
+ }
+
+ // Check the host against the allowed values; delegated to Zend_Filter.
+ $validate = new Zend_Validate_Hostname(Zend_Validate_Hostname::ALLOW_ALL);
+
+ return $validate->isValid($host);
+ }
+
+ /**
+ * Sets the host for the current URI, and returns the old host
+ *
+ * @param string $host The HTTP host
+ * @throws Zend_Uri_Exception When $host is nota valid HTTP host
+ * @return string
+ */
+ public function setHost($host)
+ {
+ if ($this->validateHost($host) === false) {
+ throw new Zend_Uri_Exception("Host \"$host\" is not a valid HTTP host");
+ }
+
+ $oldHost = $this->_host;
+ $this->_host = $host;
+
+ return $oldHost;
+ }
+
+ /**
+ * Returns the TCP port, or FALSE if none.
+ *
+ * @return string
+ */
+ public function getPort()
+ {
+ return strlen($this->_port) > 0 ? $this->_port : false;
+ }
+
+ /**
+ * Returns true if and only if the TCP port string passes validation. If no port is passed,
+ * then the port contained in the instance variable is used.
+ *
+ * @param string $port The HTTP port
+ * @return boolean
+ */
+ public function validatePort($port = null)
+ {
+ if ($port === null) {
+ $port = $this->_port;
+ }
+
+ // If the port is empty, then it is considered valid
+ if (strlen($port) === 0) {
+ return true;
+ }
+
+ // Check the port against the allowed values
+ return ctype_digit((string) $port) and 1 <= $port and $port <= 65535;
+ }
+
+ /**
+ * Sets the port for the current URI, and returns the old port
+ *
+ * @param string $port The HTTP port
+ * @throws Zend_Uri_Exception When $port is not a valid HTTP port
+ * @return string
+ */
+ public function setPort($port)
+ {
+ if ($this->validatePort($port) === false) {
+ throw new Zend_Uri_Exception("Port \"$port\" is not a valid HTTP port.");
+ }
+
+ $oldPort = $this->_port;
+ $this->_port = $port;
+
+ return $oldPort;
+ }
+
+ /**
+ * Returns the path and filename portion of the URL.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return strlen($this->_path) > 0 ? $this->_path : '/';
+ }
+
+ /**
+ * Returns true if and only if the path string passes validation. If no path is passed,
+ * then the path contained in the instance variable is used.
+ *
+ * @param string $path The HTTP path
+ * @throws Zend_Uri_Exception When path validation fails
+ * @return boolean
+ */
+ public function validatePath($path = null)
+ {
+ if ($path === null) {
+ $path = $this->_path;
+ }
+
+ // If the path is empty, then it is considered valid
+ if (strlen($path) === 0) {
+ return true;
+ }
+
+ // Determine whether the path is well-formed
+ $pattern = '/^' . $this->_regex['path'] . '$/';
+ $status = @preg_match($pattern, $path);
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: path validation failed');
+ }
+
+ return (boolean) $status;
+ }
+
+ /**
+ * Sets the path for the current URI, and returns the old path
+ *
+ * @param string $path The HTTP path
+ * @throws Zend_Uri_Exception When $path is not a valid HTTP path
+ * @return string
+ */
+ public function setPath($path)
+ {
+ if ($this->validatePath($path) === false) {
+ throw new Zend_Uri_Exception("Path \"$path\" is not a valid HTTP path");
+ }
+
+ $oldPath = $this->_path;
+ $this->_path = $path;
+
+ return $oldPath;
+ }
+
+ /**
+ * Returns the query portion of the URL (after ?), or FALSE if none.
+ *
+ * @return string
+ */
+ public function getQuery()
+ {
+ return strlen($this->_query) > 0 ? $this->_query : false;
+ }
+
+ /**
+ * Returns the query portion of the URL (after ?) as a
+ * key-value-array. If the query is empty an empty array
+ * is returned
+ *
+ * @return array
+ */
+ public function getQueryAsArray()
+ {
+ $query = $this->getQuery();
+ $querryArray = array();
+ if ($query !== false) {
+ parse_str($query, $querryArray);
+ }
+ return $querryArray;
+ }
+
+ /**
+ * Returns true if and only if the query string passes validation. If no query is passed,
+ * then the query string contained in the instance variable is used.
+ *
+ * @param string $query The query to validate
+ * @throws Zend_Uri_Exception When query validation fails
+ * @return boolean
+ * @link http://www.faqs.org/rfcs/rfc2396.html
+ */
+ public function validateQuery($query = null)
+ {
+ if ($query === null) {
+ $query = $this->_query;
+ }
+
+ // If query is empty, it is considered to be valid
+ if (strlen($query) === 0) {
+ return true;
+ }
+
+ // Determine whether the query is well-formed
+ $pattern = '/^' . $this->_regex['uric'] . '*$/';
+ $status = @preg_match($pattern, $query);
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: query validation failed');
+ }
+
+ return $status == 1;
+ }
+
+ /**
+ * Add or replace params in the query string for the current URI, and
+ * return the old query.
+ *
+ * @param array $queryParams
+ * @return string Old query string
+ */
+ public function addReplaceQueryParameters(array $queryParams)
+ {
+ $queryParams = array_merge($this->getQueryAsArray(), $queryParams);
+ return $this->setQuery($queryParams);
+ }
+
+ /**
+ * Remove params in the query string for the current URI, and
+ * return the old query.
+ *
+ * @param array $queryParamKeys
+ * @return string Old query string
+ */
+ public function removeQueryParameters(array $queryParamKeys)
+ {
+ $queryParams = array_diff_key($this->getQueryAsArray(), array_fill_keys($queryParamKeys, 0));
+ return $this->setQuery($queryParams);
+ }
+
+ /**
+ * Set the query string for the current URI, and return the old query
+ * string This method accepts both strings and arrays.
+ *
+ * @param string|array $query The query string or array
+ * @throws Zend_Uri_Exception When $query is not a valid query string
+ * @return string Old query string
+ */
+ public function setQuery($query)
+ {
+ $oldQuery = $this->_query;
+
+ // If query is empty, set an empty string
+ if (empty($query) === true) {
+ $this->_query = '';
+ return $oldQuery;
+ }
+
+ // If query is an array, make a string out of it
+ if (is_array($query) === true) {
+ $query = http_build_query($query, '', '&');
+ } else {
+ // If it is a string, make sure it is valid. If not parse and encode it
+ $query = (string) $query;
+ if ($this->validateQuery($query) === false) {
+ parse_str($query, $queryArray);
+ $query = http_build_query($queryArray, '', '&');
+ }
+ }
+
+ // Make sure the query is valid, and set it
+ if ($this->validateQuery($query) === false) {
+ throw new Zend_Uri_Exception("'$query' is not a valid query string");
+ }
+
+ $this->_query = $query;
+
+ return $oldQuery;
+ }
+
+ /**
+ * Returns the fragment portion of the URL (after #), or FALSE if none.
+ *
+ * @return string|false
+ */
+ public function getFragment()
+ {
+ return strlen($this->_fragment) > 0 ? $this->_fragment : false;
+ }
+
+ /**
+ * Returns true if and only if the fragment passes validation. If no fragment is passed,
+ * then the fragment contained in the instance variable is used.
+ *
+ * @param string $fragment Fragment of an URI
+ * @throws Zend_Uri_Exception When fragment validation fails
+ * @return boolean
+ * @link http://www.faqs.org/rfcs/rfc2396.html
+ */
+ public function validateFragment($fragment = null)
+ {
+ if ($fragment === null) {
+ $fragment = $this->_fragment;
+ }
+
+ // If fragment is empty, it is considered to be valid
+ if (strlen($fragment) === 0) {
+ return true;
+ }
+
+ // Determine whether the fragment is well-formed
+ $pattern = '/^' . $this->_regex['uric'] . '*$/';
+ $status = @preg_match($pattern, $fragment);
+ if ($status === false) {
+ throw new Zend_Uri_Exception('Internal error: fragment validation failed');
+ }
+
+ return (boolean) $status;
+ }
+
+ /**
+ * Sets the fragment for the current URI, and returns the old fragment
+ *
+ * @param string $fragment Fragment of the current URI
+ * @throws Zend_Uri_Exception When $fragment is not a valid HTTP fragment
+ * @return string
+ */
+ public function setFragment($fragment)
+ {
+ if ($this->validateFragment($fragment) === false) {
+ throw new Zend_Uri_Exception("Fragment \"$fragment\" is not a valid HTTP fragment");
+ }
+
+ $oldFragment = $this->_fragment;
+ $this->_fragment = $fragment;
+
+ return $oldFragment;
+ }
+}
diff --git a/library/vendor/Zend/VERSION b/library/vendor/Zend/VERSION
new file mode 100644
index 0000000..2e06b1a
--- /dev/null
+++ b/library/vendor/Zend/VERSION
@@ -0,0 +1 @@
+v1.12.20-23-gc1f645550
diff --git a/library/vendor/Zend/Validate.php b/library/vendor/Zend/Validate.php
new file mode 100644
index 0000000..34f72be
--- /dev/null
+++ b/library/vendor/Zend/Validate.php
@@ -0,0 +1,283 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate implements Zend_Validate_Interface
+{
+ /**
+ * Validator chain
+ *
+ * @var array
+ */
+ protected $_validators = array();
+
+ /**
+ * Array of validation failure messages
+ *
+ * @var array
+ */
+ protected $_messages = array();
+
+ /**
+ * Default Namespaces
+ *
+ * @var array
+ */
+ protected static $_defaultNamespaces = array();
+
+ /**
+ * Array of validation failure message codes
+ *
+ * @var array
+ * @deprecated Since 1.5.0
+ */
+ protected $_errors = array();
+
+ /**
+ * Adds a validator to the end of the chain
+ *
+ * If $breakChainOnFailure is true, then if the validator fails, the next validator in the chain,
+ * if one exists, will not be executed.
+ *
+ * @param Zend_Validate_Interface $validator
+ * @param boolean $breakChainOnFailure
+ * @return Zend_Validate Provides a fluent interface
+ */
+ public function addValidator(Zend_Validate_Interface $validator, $breakChainOnFailure = false)
+ {
+ $this->_validators[] = array(
+ 'instance' => $validator,
+ 'breakChainOnFailure' => (boolean) $breakChainOnFailure
+ );
+ return $this;
+ }
+
+ /**
+ * Returns true if and only if $value passes all validations in the chain
+ *
+ * Validators are run in the order in which they were added to the chain (FIFO).
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_messages = array();
+ $this->_errors = array();
+ $result = true;
+ foreach ($this->_validators as $element) {
+ $validator = $element['instance'];
+ if ($validator->isValid($value)) {
+ continue;
+ }
+ $result = false;
+ $messages = $validator->getMessages();
+ $this->_messages = array_merge($this->_messages, $messages);
+ $this->_errors = array_merge($this->_errors, array_keys($messages));
+ if ($element['breakChainOnFailure']) {
+ break;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns array of validation failure messages
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return $this->_messages;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns array of validation failure message codes
+ *
+ * @return array
+ * @deprecated Since 1.5.0
+ */
+ public function getErrors()
+ {
+ return $this->_errors;
+ }
+
+ /**
+ * Returns the set default namespaces
+ *
+ * @return array
+ */
+ public static function getDefaultNamespaces()
+ {
+ return self::$_defaultNamespaces;
+ }
+
+ /**
+ * Sets new default namespaces
+ *
+ * @param array|string $namespace
+ * @return null
+ */
+ public static function setDefaultNamespaces($namespace)
+ {
+ if (!is_array($namespace)) {
+ $namespace = array((string) $namespace);
+ }
+
+ self::$_defaultNamespaces = $namespace;
+ }
+
+ /**
+ * Adds a new default namespace
+ *
+ * @param array|string $namespace
+ * @return null
+ */
+ public static function addDefaultNamespaces($namespace)
+ {
+ if (!is_array($namespace)) {
+ $namespace = array((string) $namespace);
+ }
+
+ self::$_defaultNamespaces = array_unique(array_merge(self::$_defaultNamespaces, $namespace));
+ }
+
+ /**
+ * Returns true when defaultNamespaces are set
+ *
+ * @return boolean
+ */
+ public static function hasDefaultNamespaces()
+ {
+ return (!empty(self::$_defaultNamespaces));
+ }
+
+ /**
+ * @param mixed $value
+ * @param string $classBaseName
+ * @param array $args OPTIONAL
+ * @param mixed $namespaces OPTIONAL
+ * @return boolean
+ * @throws Zend_Validate_Exception
+ */
+ public static function is($value, $classBaseName, array $args = array(), $namespaces = array())
+ {
+ $namespaces = array_merge((array) $namespaces, self::$_defaultNamespaces, array('Zend_Validate'));
+ $className = ucfirst($classBaseName);
+ try {
+ if (!class_exists($className, false)) {
+ foreach($namespaces as $namespace) {
+ $class = $namespace . '_' . $className;
+ $file = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
+ if (Zend_Loader::isReadable($file)) {
+ Zend_Loader::loadClass($class);
+ $className = $class;
+ break;
+ }
+ }
+ }
+
+ $class = new ReflectionClass($className);
+ if ($class->implementsInterface('Zend_Validate_Interface')) {
+ if ($class->hasMethod('__construct')) {
+ $keys = array_keys($args);
+ $numeric = false;
+ foreach($keys as $key) {
+ if (is_numeric($key)) {
+ $numeric = true;
+ break;
+ }
+ }
+
+ if ($numeric) {
+ $object = $class->newInstanceArgs(array_values($args));
+ } else {
+ $object = $class->newInstance($args);
+ }
+ } else {
+ $object = $class->newInstance();
+ }
+
+ return $object->isValid($value);
+ }
+ } catch (Zend_Validate_Exception $ze) {
+ // if there is an exception while validating throw it
+ throw $ze;
+ } catch (Exception $e) {
+ // fallthrough and continue for missing validation classes
+ }
+
+ throw new Zend_Validate_Exception("Validate class not found from basename '$classBaseName'");
+ }
+
+ /**
+ * Returns the maximum allowed message length
+ *
+ * @return integer
+ */
+ public static function getMessageLength()
+ {
+ return Zend_Validate_Abstract::getMessageLength();
+ }
+
+ /**
+ * Sets the maximum allowed message length
+ *
+ * @param integer $length
+ */
+ public static function setMessageLength($length = -1)
+ {
+ Zend_Validate_Abstract::setMessageLength($length);
+ }
+
+ /**
+ * Returns the default translation object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public static function getDefaultTranslator($translator = null)
+ {
+ return Zend_Validate_Abstract::getDefaultTranslator();
+ }
+
+ /**
+ * Sets a default translation object for all validation objects
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ */
+ public static function setDefaultTranslator($translator = null)
+ {
+ Zend_Validate_Abstract::setDefaultTranslator($translator);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Abstract.php b/library/vendor/Zend/Validate/Abstract.php
new file mode 100644
index 0000000..f3ba551
--- /dev/null
+++ b/library/vendor/Zend/Validate/Abstract.php
@@ -0,0 +1,477 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Validate_Abstract implements Zend_Validate_Interface
+{
+ /**
+ * The value to be validated
+ *
+ * @var mixed
+ */
+ protected $_value;
+
+ /**
+ * Additional variables available for validation failure messages
+ *
+ * @var array
+ */
+ protected $_messageVariables = array();
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array();
+
+ /**
+ * Array of validation failure messages
+ *
+ * @var array
+ */
+ protected $_messages = array();
+
+ /**
+ * Flag indidcating whether or not value should be obfuscated in error
+ * messages
+ * @var bool
+ */
+ protected $_obscureValue = false;
+
+ /**
+ * Array of validation failure message codes
+ *
+ * @var array
+ * @deprecated Since 1.5.0
+ */
+ protected $_errors = array();
+
+ /**
+ * Translation object
+ * @var Zend_Translate
+ */
+ protected $_translator;
+
+ /**
+ * Default translation object for all validate objects
+ * @var Zend_Translate
+ */
+ protected static $_defaultTranslator;
+
+ /**
+ * Is translation disabled?
+ * @var Boolean
+ */
+ protected $_translatorDisabled = false;
+
+ /**
+ * Limits the maximum returned length of a error message
+ *
+ * @var Integer
+ */
+ protected static $_messageLength = -1;
+
+ /**
+ * Returns array of validation failure messages
+ *
+ * @return array
+ */
+ public function getMessages()
+ {
+ return $this->_messages;
+ }
+
+ /**
+ * Returns an array of the names of variables that are used in constructing validation failure messages
+ *
+ * @return array
+ */
+ public function getMessageVariables()
+ {
+ return array_keys($this->_messageVariables);
+ }
+
+ /**
+ * Returns the message templates from the validator
+ *
+ * @return array
+ */
+ public function getMessageTemplates()
+ {
+ return $this->_messageTemplates;
+ }
+
+ /**
+ * Sets the validation failure message template for a particular key
+ *
+ * @param string $messageString
+ * @param string $messageKey OPTIONAL
+ * @return Zend_Validate_Abstract Provides a fluent interface
+ * @throws Zend_Validate_Exception
+ */
+ public function setMessage($messageString, $messageKey = null)
+ {
+ if ($messageKey === null) {
+ $keys = array_keys($this->_messageTemplates);
+ foreach($keys as $key) {
+ $this->setMessage($messageString, $key);
+ }
+ return $this;
+ }
+
+ if (!isset($this->_messageTemplates[$messageKey])) {
+ throw new Zend_Validate_Exception("No message template exists for key '$messageKey'");
+ }
+
+ $this->_messageTemplates[$messageKey] = $messageString;
+ return $this;
+ }
+
+ /**
+ * Sets validation failure message templates given as an array, where the array keys are the message keys,
+ * and the array values are the message template strings.
+ *
+ * @param array $messages
+ * @return Zend_Validate_Abstract
+ */
+ public function setMessages(array $messages)
+ {
+ foreach ($messages as $key => $message) {
+ $this->setMessage($message, $key);
+ }
+ return $this;
+ }
+
+ /**
+ * Magic function returns the value of the requested property, if and only if it is the value or a
+ * message variable.
+ *
+ * @param string $property
+ * @return mixed
+ * @throws Zend_Validate_Exception
+ */
+ public function __get($property)
+ {
+ if ($property == 'value') {
+ return $this->_value;
+ }
+ if (array_key_exists($property, $this->_messageVariables)) {
+ return $this->{$this->_messageVariables[$property]};
+ }
+ /**
+ * @see Zend_Validate_Exception
+ */
+ throw new Zend_Validate_Exception("No property exists by the name '$property'");
+ }
+
+ /**
+ * Constructs and returns a validation failure message with the given message key and value.
+ *
+ * Returns null if and only if $messageKey does not correspond to an existing template.
+ *
+ * If a translator is available and a translation exists for $messageKey,
+ * the translation will be used.
+ *
+ * @param string $messageKey
+ * @param string $value
+ * @return string
+ */
+ protected function _createMessage($messageKey, $value)
+ {
+ if (!isset($this->_messageTemplates[$messageKey])) {
+ return null;
+ }
+
+ $message = $this->_messageTemplates[$messageKey];
+
+ if (null !== ($translator = $this->getTranslator())) {
+ if ($translator->isTranslated($messageKey)) {
+ $message = $translator->translate($messageKey);
+ } else {
+ $message = $translator->translate($message);
+ }
+ }
+
+ if (is_object($value)) {
+ if (!in_array('__toString', get_class_methods($value))) {
+ $value = get_class($value) . ' object';
+ } else {
+ $value = $value->__toString();
+ }
+ } elseif (is_array($value)) {
+ $value = $this->_implodeRecursive($value);
+ } else {
+ $value = implode((array) $value);
+ }
+
+ if ($this->getObscureValue()) {
+ $value = str_repeat('*', strlen($value));
+ }
+
+ $message = str_replace('%value%', $value, $message);
+ foreach ($this->_messageVariables as $ident => $property) {
+ $message = str_replace(
+ "%$ident%",
+ implode(' ', (array) $this->$property),
+ $message
+ );
+ }
+
+ $length = self::getMessageLength();
+ if (($length > -1) && (strlen($message) > $length)) {
+ $message = substr($message, 0, (self::getMessageLength() - 3)) . '...';
+ }
+
+ return $message;
+ }
+
+ /**
+ * Joins elements of a multidimensional array
+ *
+ * @param array $pieces
+ * @return string
+ */
+ protected function _implodeRecursive(array $pieces)
+ {
+ $values = array();
+ foreach ($pieces as $item) {
+ if (is_array($item)) {
+ $values[] = $this->_implodeRecursive($item);
+ } else {
+ $values[] = $item;
+ }
+ }
+
+ return implode(', ', $values);
+ }
+
+ /**
+ * @param string $messageKey
+ * @param string $value OPTIONAL
+ * @return void
+ */
+ protected function _error($messageKey, $value = null)
+ {
+ if ($messageKey === null) {
+ $keys = array_keys($this->_messageTemplates);
+ $messageKey = current($keys);
+ }
+ if ($value === null) {
+ $value = $this->_value;
+ }
+ $this->_errors[] = $messageKey;
+ $this->_messages[$messageKey] = $this->_createMessage($messageKey, $value);
+ }
+
+ /**
+ * Sets the value to be validated and clears the messages and errors arrays
+ *
+ * @param mixed $value
+ * @return void
+ */
+ protected function _setValue($value)
+ {
+ $this->_value = $value;
+ $this->_messages = array();
+ $this->_errors = array();
+ }
+
+ /**
+ * Returns array of validation failure message codes
+ *
+ * @return array
+ * @deprecated Since 1.5.0
+ */
+ public function getErrors()
+ {
+ return $this->_errors;
+ }
+
+ /**
+ * Set flag indicating whether or not value should be obfuscated in messages
+ *
+ * @param bool $flag
+ * @return Zend_Validate_Abstract
+ */
+ public function setObscureValue($flag)
+ {
+ $this->_obscureValue = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Retrieve flag indicating whether or not value should be obfuscated in
+ * messages
+ *
+ * @return bool
+ */
+ public function getObscureValue()
+ {
+ return $this->_obscureValue;
+ }
+
+ /**
+ * Set translation object
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_Abstract
+ */
+ public function setTranslator($translator = null)
+ {
+ if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ throw new Zend_Validate_Exception('Invalid translator specified');
+ }
+ return $this;
+ }
+
+ /**
+ * Return translation object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->translatorIsDisabled()) {
+ return null;
+ }
+
+ if (null === $this->_translator) {
+ return self::getDefaultTranslator();
+ }
+
+ return $this->_translator;
+ }
+
+ /**
+ * Does this validator have its own specific translator?
+ *
+ * @return bool
+ */
+ public function hasTranslator()
+ {
+ return (bool)$this->_translator;
+ }
+
+ /**
+ * Set default translation object for all validate objects
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @throws Zend_Validate_Exception
+ */
+ public static function setDefaultTranslator($translator = null)
+ {
+ if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
+ self::$_defaultTranslator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ self::$_defaultTranslator = $translator->getAdapter();
+ } else {
+ throw new Zend_Validate_Exception('Invalid translator specified');
+ }
+ }
+
+ /**
+ * Get default translation object for all validate objects
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public static function getDefaultTranslator()
+ {
+ if (null === self::$_defaultTranslator) {
+ if (Zend_Registry::isRegistered('Zend_Translate')) {
+ $translator = Zend_Registry::get('Zend_Translate');
+ if ($translator instanceof Zend_Translate_Adapter) {
+ return $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ return $translator->getAdapter();
+ }
+ }
+ }
+
+ return self::$_defaultTranslator;
+ }
+
+ /**
+ * Is there a default translation object set?
+ *
+ * @return boolean
+ */
+ public static function hasDefaultTranslator()
+ {
+ return (bool)self::$_defaultTranslator;
+ }
+
+ /**
+ * Indicate whether or not translation should be disabled
+ *
+ * @param bool $flag
+ * @return Zend_Validate_Abstract
+ */
+ public function setDisableTranslator($flag)
+ {
+ $this->_translatorDisabled = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Is translation disabled?
+ *
+ * @return bool
+ */
+ public function translatorIsDisabled()
+ {
+ return $this->_translatorDisabled;
+ }
+
+ /**
+ * Returns the maximum allowed message length
+ *
+ * @return integer
+ */
+ public static function getMessageLength()
+ {
+ return self::$_messageLength;
+ }
+
+ /**
+ * Sets the maximum allowed message length
+ *
+ * @param integer $length
+ */
+ public static function setMessageLength($length = -1)
+ {
+ self::$_messageLength = $length;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Alnum.php b/library/vendor/Zend/Validate/Alnum.php
new file mode 100644
index 0000000..190ac44
--- /dev/null
+++ b/library/vendor/Zend/Validate/Alnum.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Alnum extends Zend_Validate_Abstract
+{
+ const INVALID = 'alnumInvalid';
+ const NOT_ALNUM = 'notAlnum';
+ const STRING_EMPTY = 'alnumStringEmpty';
+
+ /**
+ * Whether to allow white space characters; off by default
+ *
+ * @var boolean
+ * @deprecated
+ */
+ public $allowWhiteSpace;
+
+ /**
+ * Alphanumeric filter used for validation
+ *
+ * @var Zend_Filter_Alnum
+ */
+ protected static $_filter = null;
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String, integer or float expected",
+ self::NOT_ALNUM => "'%value%' contains characters which are non alphabetic and no digits",
+ self::STRING_EMPTY => "'%value%' is an empty string",
+ );
+
+ /**
+ * Sets default option values for this instance
+ *
+ * @param boolean|Zend_Config $allowWhiteSpace
+ */
+ public function __construct($allowWhiteSpace = false)
+ {
+ if ($allowWhiteSpace instanceof Zend_Config) {
+ $allowWhiteSpace = $allowWhiteSpace->toArray();
+ }
+
+ if (is_array($allowWhiteSpace)) {
+ if (array_key_exists('allowWhiteSpace', $allowWhiteSpace)) {
+ $allowWhiteSpace = $allowWhiteSpace['allowWhiteSpace'];
+ } else {
+ $allowWhiteSpace = false;
+ }
+ }
+
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ }
+
+ /**
+ * Returns the allowWhiteSpace option
+ *
+ * @return boolean
+ */
+ public function getAllowWhiteSpace()
+ {
+ return $this->allowWhiteSpace;
+ }
+
+ /**
+ * Sets the allowWhiteSpace option
+ *
+ * @param boolean $allowWhiteSpace
+ * @return Zend_Filter_Alnum Provides a fluent interface
+ */
+ public function setAllowWhiteSpace($allowWhiteSpace)
+ {
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value contains only alphabetic and digit characters
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value) && !is_float($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+
+ if ('' === $value) {
+ $this->_error(self::STRING_EMPTY);
+ return false;
+ }
+
+ if (null === self::$_filter) {
+ /**
+ * @see Zend_Filter_Alnum
+ */
+ self::$_filter = new Zend_Filter_Alnum();
+ }
+
+ self::$_filter->allowWhiteSpace = $this->allowWhiteSpace;
+
+ if ($value != self::$_filter->filter($value)) {
+ $this->_error(self::NOT_ALNUM);
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Validate/Alpha.php b/library/vendor/Zend/Validate/Alpha.php
new file mode 100644
index 0000000..a5dbe40
--- /dev/null
+++ b/library/vendor/Zend/Validate/Alpha.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Alpha extends Zend_Validate_Abstract
+{
+ const INVALID = 'alphaInvalid';
+ const NOT_ALPHA = 'notAlpha';
+ const STRING_EMPTY = 'alphaStringEmpty';
+
+ /**
+ * Whether to allow white space characters; off by default
+ *
+ * @var boolean
+ * @deprecated
+ */
+ public $allowWhiteSpace;
+
+ /**
+ * Alphabetic filter used for validation
+ *
+ * @var Zend_Filter_Alpha
+ */
+ protected static $_filter = null;
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String expected",
+ self::NOT_ALPHA => "'%value%' contains non alphabetic characters",
+ self::STRING_EMPTY => "'%value%' is an empty string"
+ );
+
+ /**
+ * Sets default option values for this instance
+ *
+ * @param boolean|Zend_Config $allowWhiteSpace
+ */
+ public function __construct($allowWhiteSpace = false)
+ {
+ if ($allowWhiteSpace instanceof Zend_Config) {
+ $allowWhiteSpace = $allowWhiteSpace->toArray();
+ }
+
+ if (is_array($allowWhiteSpace)) {
+ if (array_key_exists('allowWhiteSpace', $allowWhiteSpace)) {
+ $allowWhiteSpace = $allowWhiteSpace['allowWhiteSpace'];
+ } else {
+ $allowWhiteSpace = false;
+ }
+ }
+
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ }
+
+ /**
+ * Returns the allowWhiteSpace option
+ *
+ * @return boolean
+ */
+ public function getAllowWhiteSpace()
+ {
+ return $this->allowWhiteSpace;
+ }
+
+ /**
+ * Sets the allowWhiteSpace option
+ *
+ * @param boolean $allowWhiteSpace
+ * @return Zend_Filter_Alpha Provides a fluent interface
+ */
+ public function setAllowWhiteSpace($allowWhiteSpace)
+ {
+ $this->allowWhiteSpace = (boolean) $allowWhiteSpace;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value contains only alphabetic characters
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+
+ if ('' === $value) {
+ $this->_error(self::STRING_EMPTY);
+ return false;
+ }
+
+ if (null === self::$_filter) {
+ /**
+ * @see Zend_Filter_Alpha
+ */
+ self::$_filter = new Zend_Filter_Alpha();
+ }
+
+ self::$_filter->allowWhiteSpace = $this->allowWhiteSpace;
+
+ if ($value !== self::$_filter->filter($value)) {
+ $this->_error(self::NOT_ALPHA);
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Validate/Barcode.php b/library/vendor/Zend/Validate/Barcode.php
new file mode 100644
index 0000000..2cc6f11
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode.php
@@ -0,0 +1,222 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Loader
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode extends Zend_Validate_Abstract
+{
+ const INVALID = 'barcodeInvalid';
+ const FAILED = 'barcodeFailed';
+ const INVALID_CHARS = 'barcodeInvalidChars';
+ const INVALID_LENGTH = 'barcodeInvalidLength';
+
+ protected $_messageTemplates = array(
+ self::FAILED => "'%value%' failed checksum validation",
+ self::INVALID_CHARS => "'%value%' contains invalid characters",
+ self::INVALID_LENGTH => "'%value%' should have a length of %length% characters",
+ self::INVALID => "Invalid type given. String expected",
+ );
+
+ /**
+ * Additional variables available for validation failure messages
+ *
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'length' => '_length'
+ );
+
+ /**
+ * Length for the set subtype
+ *
+ * @var integer
+ */
+ protected $_length;
+
+ /**
+ * Barcode adapter
+ *
+ * @var Zend_Validate_Barcode_BarcodeAdapter
+ */
+ protected $_adapter;
+
+ /**
+ * Generates the standard validator object
+ *
+ * @param string|Zend_Config|
+ * Zend_Validate_Barcode_BarcodeAdapter $adapter Barcode adapter to use
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($adapter)
+ {
+ if ($adapter instanceof Zend_Config) {
+ $adapter = $adapter->toArray();
+ }
+
+ $options = null;
+ $checksum = null;
+ if (is_array($adapter)) {
+ if (array_key_exists('options', $adapter)) {
+ $options = $adapter['options'];
+ }
+
+ if (array_key_exists('checksum', $adapter)) {
+ $checksum = $adapter['checksum'];
+ }
+
+ if (array_key_exists('adapter', $adapter)) {
+ $adapter = $adapter['adapter'];
+ } else {
+ throw new Zend_Validate_Exception("Missing option 'adapter'");
+ }
+ }
+
+ $this->setAdapter($adapter, $options);
+ if ($checksum !== null) {
+ $this->setChecksum($checksum);
+ }
+ }
+
+ /**
+ * Returns the set adapter
+ *
+ * @return Zend_Validate_Barcode_BarcodeAdapter
+ */
+ public function getAdapter()
+ {
+ return $this->_adapter;
+ }
+
+ /**
+ * Sets a new barcode adapter
+ *
+ * @param string|Zend_Validate_Barcode $adapter Barcode adapter to use
+ * @param array $options Options for this adapter
+ * @return $this
+ * @throws Zend_Validate_Exception
+ */
+ public function setAdapter($adapter, $options = null)
+ {
+ $adapter = ucfirst(strtolower($adapter));
+ if (Zend_Loader::isReadable('Zend/Validate/Barcode/' . $adapter. '.php')) {
+ $adapter = 'Zend_Validate_Barcode_' . $adapter;
+ }
+
+ if (!class_exists($adapter)) {
+ Zend_Loader::loadClass($adapter);
+ }
+
+ $this->_adapter = new $adapter($options);
+ if (!$this->_adapter instanceof Zend_Validate_Barcode_AdapterInterface) {
+ throw new Zend_Validate_Exception(
+ "Adapter " . $adapter . " does not implement Zend_Validate_Barcode_AdapterInterface"
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the checksum option
+ *
+ * @return boolean
+ */
+ public function getChecksum()
+ {
+ return $this->getAdapter()->getCheck();
+ }
+
+ /**
+ * Sets the checksum option
+ *
+ * @param boolean $checksum
+ * @return Zend_Validate_Barcode
+ */
+ public function setChecksum($checksum)
+ {
+ $this->getAdapter()->setCheck($checksum);
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value contains a valid barcode
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ $adapter = $this->getAdapter();
+ $this->_length = $adapter->getLength();
+ $result = $adapter->checkLength($value);
+ if (!$result) {
+ if (is_array($this->_length)) {
+ $temp = $this->_length;
+ $this->_length = "";
+ foreach($temp as $length) {
+ $this->_length .= "/";
+ $this->_length .= $length;
+ }
+
+ $this->_length = substr($this->_length, 1);
+ }
+
+ $this->_error(self::INVALID_LENGTH);
+ return false;
+ }
+
+ $result = $adapter->checkChars($value);
+ if (!$result) {
+ $this->_error(self::INVALID_CHARS);
+ return false;
+ }
+
+ if ($this->getChecksum()) {
+ $result = $adapter->checksum($value);
+ if (!$result) {
+ $this->_error(self::FAILED);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/AdapterAbstract.php b/library/vendor/Zend/Validate/Barcode/AdapterAbstract.php
new file mode 100644
index 0000000..01e2409
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/AdapterAbstract.php
@@ -0,0 +1,314 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterInterface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Validate_Barcode_AdapterAbstract
+ implements Zend_Validate_Barcode_AdapterInterface
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer|array|string
+ */
+ protected $_length;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters;
+
+ /**
+ * Callback to checksum function
+ * @var string|array
+ */
+ protected $_checksum;
+
+ /**
+ * Is a checksum value included?
+ * @var boolean
+ */
+ protected $_hasChecksum = true;
+
+ /**
+ * Checks the length of a barcode
+ *
+ * @param string $value The barcode to check for proper length
+ * @return boolean
+ */
+ public function checkLength($value)
+ {
+ if (!is_string($value)) {
+ return false;
+ }
+
+ $fixum = strlen($value);
+ $found = false;
+ $length = $this->getLength();
+ if (is_array($length)) {
+ foreach ($length as $value) {
+ if ($fixum == $value) {
+ $found = true;
+ }
+
+ if ($value == -1) {
+ $found = true;
+ }
+ }
+ } elseif ($fixum == $length) {
+ $found = true;
+ } elseif ($length == -1) {
+ $found = true;
+ } elseif ($length == 'even') {
+ $count = $fixum % 2;
+ $found = ($count == 0) ? true : false;
+ } elseif ($length == 'odd') {
+ $count = $fixum % 2;
+ $found = ($count == 1) ? true : false;
+ }
+
+ return $found;
+ }
+
+ /**
+ * Checks for allowed characters within the barcode
+ *
+ * @param string $value The barcode to check for allowed characters
+ * @return boolean
+ */
+ public function checkChars($value)
+ {
+ if (!is_string($value)) {
+ return false;
+ }
+
+ $characters = $this->getCharacters();
+ if ($characters == 128) {
+ for ($x = 0; $x < 128; ++$x) {
+ $value = str_replace(chr($x), '', $value);
+ }
+ } else {
+ $chars = str_split($characters);
+ foreach ($chars as $char) {
+ $value = str_replace($char, '', $value);
+ }
+ }
+
+ if (strlen($value) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the checksum
+ *
+ * @param string $value The barcode to check the checksum for
+ * @return boolean
+ */
+ public function checksum($value)
+ {
+ $checksum = $this->getChecksum();
+ if (!empty($checksum)) {
+ if (method_exists($this, $checksum)) {
+ return call_user_func(array($this, $checksum), $value);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the allowed barcode length
+ *
+ * @return string
+ */
+ public function getLength()
+ {
+ return $this->_length;
+ }
+
+ /**
+ * Returns the allowed characters
+ *
+ * @return integer|string
+ */
+ public function getCharacters()
+ {
+ return $this->_characters;
+ }
+
+ /**
+ * Returns the checksum function name
+ *
+ */
+ public function getChecksum()
+ {
+ return $this->_checksum;
+ }
+
+ /**
+ * Returns if barcode uses checksum
+ *
+ * @return boolean
+ */
+ public function getCheck()
+ {
+ return $this->_hasChecksum;
+ }
+
+ /**
+ * Sets the checksum validation
+ *
+ * @param boolean $check
+ * @return Zend_Validate_Barcode_AdapterAbstract
+ */
+ public function setCheck($check)
+ {
+ $this->_hasChecksum = (boolean) $check;
+ return $this;
+ }
+
+ /**
+ * Validates the checksum (Modulo 10)
+ * GTIN implementation factor 3
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _gtin($value)
+ {
+ $barcode = substr($value, 0, -1);
+ $sum = 0;
+ $length = strlen($barcode) - 1;
+
+ for ($i = 0; $i <= $length; $i++) {
+ if (($i % 2) === 0) {
+ $sum += $barcode[$length - $i] * 3;
+ } else {
+ $sum += $barcode[$length - $i];
+ }
+ }
+
+ $calc = $sum % 10;
+ $checksum = ($calc === 0) ? 0 : (10 - $calc);
+ if ($value[$length + 1] != $checksum) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the checksum (Modulo 10)
+ * IDENTCODE implementation factors 9 and 4
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _identcode($value)
+ {
+ $barcode = substr($value, 0, -1);
+ $sum = 0;
+ $length = strlen($value) - 2;
+
+ for ($i = 0; $i <= $length; $i++) {
+ if (($i % 2) === 0) {
+ $sum += $barcode[$length - $i] * 4;
+ } else {
+ $sum += $barcode[$length - $i] * 9;
+ }
+ }
+
+ $calc = $sum % 10;
+ $checksum = ($calc === 0) ? 0 : (10 - $calc);
+ if ($value[$length + 1] != $checksum) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the checksum (Modulo 10)
+ * CODE25 implementation factor 3
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _code25($value)
+ {
+ $barcode = substr($value, 0, -1);
+ $sum = 0;
+ $length = strlen($barcode) - 1;
+
+ for ($i = 0; $i <= $length; $i++) {
+ if (($i % 2) === 0) {
+ $sum += $barcode[$i] * 3;
+ } else {
+ $sum += $barcode[$i];
+ }
+ }
+
+ $calc = $sum % 10;
+ $checksum = ($calc === 0) ? 0 : (10 - $calc);
+ if ($value[$length + 1] != $checksum) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates the checksum ()
+ * POSTNET implementation
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _postnet($value)
+ {
+ $checksum = substr($value, -1, 1);
+ $values = str_split(substr($value, 0, -1));
+
+ $check = 0;
+ foreach($values as $row) {
+ $check += $row;
+ }
+
+ $check %= 10;
+ $check = 10 - $check;
+ if ($check == $checksum) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/AdapterInterface.php b/library/vendor/Zend/Validate/Barcode/AdapterInterface.php
new file mode 100644
index 0000000..8e96eff
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/AdapterInterface.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Validate_Barcode_AdapterInterface
+{
+ /**
+ * Checks the length of a barcode
+ *
+ * @param string $value The barcode to check for proper length
+ * @return boolean
+ */
+ public function checkLength($value);
+
+ /**
+ * Checks for allowed characters within the barcode
+ *
+ * @param string $value The barcode to check for allowed characters
+ * @return boolean
+ */
+ public function checkChars($value);
+
+ /**
+ * Validates the checksum
+ *
+ * @param string $value The barcode to check the checksum for
+ * @return boolean
+ */
+ public function checksum($value);
+
+ /**
+ * Returns if barcode uses a checksum
+ *
+ * @return boolean
+ */
+ public function getCheck();
+
+ /**
+ * Sets the checksum validation
+ *
+ * @param boolean $check
+ * @return Zend_Validate_Barcode_Adapter Provides a fluent interface
+ */
+ public function setCheck($check);
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Code25.php b/library/vendor/Zend/Validate/Barcode/Code25.php
new file mode 100644
index 0000000..e0e540a
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Code25.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Code25 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = -1;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_code25';
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Code25interleaved.php b/library/vendor/Zend/Validate/Barcode/Code25interleaved.php
new file mode 100644
index 0000000..c952781
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Code25interleaved.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Code25interleaved extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 'even';
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_code25';
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Code39.php b/library/vendor/Zend/Validate/Barcode/Code39.php
new file mode 100644
index 0000000..e1554a1
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Code39.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Code39 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = -1;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -.$/+%';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_code39';
+
+ /**
+ * @var array
+ */
+ protected $_check = array(
+ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6,
+ '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13,
+ 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20,
+ 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27,
+ 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34,
+ 'Z' => 35, '-' => 36, '.' => 37, ' ' => 38, '$' => 39, '/' => 40, '+' => 41,
+ '%' => 42,
+ );
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+
+ /**
+ * Validates the checksum (Modulo 43)
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _code39($value)
+ {
+ $checksum = substr($value, -1, 1);
+ $value = str_split(substr($value, 0, -1));
+ $count = 0;
+ foreach($value as $char) {
+ $count += $this->_check[$char];
+ }
+
+ $mod = $count % 43;
+ if ($mod == $this->_check[$checksum]) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Code39ext.php b/library/vendor/Zend/Validate/Barcode/Code39ext.php
new file mode 100644
index 0000000..a295694
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Code39ext.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Code39ext extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = -1;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = 128;
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Code93.php b/library/vendor/Zend/Validate/Barcode/Code93.php
new file mode 100644
index 0000000..2f83ee8
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Code93.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Code93 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = -1;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ -.$/+%';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_code93';
+
+ /**
+ * Note that the characters !"§& are only synonyms
+ * @var array
+ */
+ protected $_check = array(
+ '0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6,
+ '7' => 7, '8' => 8, '9' => 9, 'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13,
+ 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20,
+ 'L' => 21, 'M' => 22, 'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27,
+ 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34,
+ 'Z' => 35, '-' => 36, '.' => 37, ' ' => 38, '$' => 39, '/' => 40, '+' => 41,
+ '%' => 42, '!' => 43, '"' => 44, '§' => 45, '&' => 46,
+ );
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+
+ /**
+ * Validates the checksum (Modulo CK)
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _code93($value)
+ {
+ $checksum = substr($value, -2, 2);
+ $value = str_split(substr($value, 0, -2));
+ $count = 0;
+ $length = count($value) % 20;
+ foreach($value as $char) {
+ if ($length == 0) {
+ $length = 20;
+ }
+
+ $count += $this->_check[$char] * $length;
+ --$length;
+ }
+
+ $check = array_search(($count % 47), $this->_check);
+ $value[] = $check;
+ $count = 0;
+ $length = count($value) % 15;
+ foreach($value as $char) {
+ if ($length == 0) {
+ $length = 15;
+ }
+
+ $count += $this->_check[$char] * $length;
+ --$length;
+ }
+ $check .= array_search(($count % 47), $this->_check);
+
+ if ($check == $checksum) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Code93ext.php b/library/vendor/Zend/Validate/Barcode/Code93ext.php
new file mode 100644
index 0000000..8795f00
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Code93ext.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Code93ext extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = -1;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = 128;
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean12.php b/library/vendor/Zend/Validate/Barcode/Ean12.php
new file mode 100644
index 0000000..67bb14c
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean12.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean12 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 12;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean13.php b/library/vendor/Zend/Validate/Barcode/Ean13.php
new file mode 100644
index 0000000..7554885
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean13.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean13 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 13;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean14.php b/library/vendor/Zend/Validate/Barcode/Ean14.php
new file mode 100644
index 0000000..af62b0e
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean14.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean14 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 14;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean18.php b/library/vendor/Zend/Validate/Barcode/Ean18.php
new file mode 100644
index 0000000..d2fe756
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean18.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean18 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 18;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean2.php b/library/vendor/Zend/Validate/Barcode/Ean2.php
new file mode 100644
index 0000000..7124fd0
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean2.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean2 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 2;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean5.php b/library/vendor/Zend/Validate/Barcode/Ean5.php
new file mode 100644
index 0000000..74654a7
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean5.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean5 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 5;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Ean8.php b/library/vendor/Zend/Validate/Barcode/Ean8.php
new file mode 100644
index 0000000..7f61dfb
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Ean8.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Ean8 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = array(7, 8);
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+
+ /**
+ * Overrides parent checkLength
+ *
+ * @param string $value Value
+ * @return boolean
+ */
+ public function checkLength($value)
+ {
+ if (strlen($value) == 7) {
+ $this->setCheck(false);
+ } else {
+ $this->setCheck(true);
+ }
+
+ return parent::checkLength($value);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Gtin12.php b/library/vendor/Zend/Validate/Barcode/Gtin12.php
new file mode 100644
index 0000000..d94a1b5
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Gtin12.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Gtin12 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 12;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Gtin13.php b/library/vendor/Zend/Validate/Barcode/Gtin13.php
new file mode 100644
index 0000000..ea66ce1
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Gtin13.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Gtin13 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 13;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Gtin14.php b/library/vendor/Zend/Validate/Barcode/Gtin14.php
new file mode 100644
index 0000000..a2354cb
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Gtin14.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Gtin14 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 14;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Identcode.php b/library/vendor/Zend/Validate/Barcode/Identcode.php
new file mode 100644
index 0000000..f02cab7
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Identcode.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Identcode extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 12;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_identcode';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Intelligentmail.php b/library/vendor/Zend/Validate/Barcode/Intelligentmail.php
new file mode 100644
index 0000000..d14ac37
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Intelligentmail.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_IntelligentMail extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = array(20, 25, 29, 31);
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Constructor
+ *
+ * Sets check flag to false.
+ */
+ public function __construct()
+ {
+ $this->setCheck(false);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Issn.php b/library/vendor/Zend/Validate/Barcode/Issn.php
new file mode 100644
index 0000000..49c705d
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Issn.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Issn extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = array(8, 13);
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789X';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+
+ /**
+ * Allows X on length of 8 chars
+ *
+ * @param string $value The barcode to check for allowed characters
+ * @return boolean
+ */
+ public function checkChars($value)
+ {
+ if (strlen($value) != 8) {
+ if (strpos($value, 'X') !== false) {
+ return false;
+ }
+ }
+
+ return parent::checkChars($value);
+ }
+
+ /**
+ * Validates the checksum
+ *
+ * @param string $value The barcode to check the checksum for
+ * @return boolean
+ */
+ public function checksum($value)
+ {
+ if (strlen($value) == 8) {
+ $this->_checksum = '_issn';
+ } else {
+ $this->_checksum = '_gtin';
+ }
+
+ return parent::checksum($value);
+ }
+
+ /**
+ * Validates the checksum ()
+ * ISSN implementation (reversed mod11)
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _issn($value)
+ {
+ $checksum = substr($value, -1, 1);
+ $values = str_split(substr($value, 0, -1));
+ $check = 0;
+ $multi = 8;
+ foreach($values as $token) {
+ if ($token == 'X') {
+ $token = 10;
+ }
+
+ $check += ($token * $multi);
+ --$multi;
+ }
+
+ $check %= 11;
+ $check = 11 - $check;
+ if ($check == $checksum) {
+ return true;
+ } else if (($check == 10) && ($checksum == 'X')) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Itf14.php b/library/vendor/Zend/Validate/Barcode/Itf14.php
new file mode 100644
index 0000000..0ed97d1
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Itf14.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Itf14 extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 14;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Leitcode.php b/library/vendor/Zend/Validate/Barcode/Leitcode.php
new file mode 100644
index 0000000..a79acac
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Leitcode.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Leitcode extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 14;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_identcode';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Planet.php b/library/vendor/Zend/Validate/Barcode/Planet.php
new file mode 100644
index 0000000..96ed881
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Planet.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Planet extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = array(12, 14);
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_postnet';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Postnet.php b/library/vendor/Zend/Validate/Barcode/Postnet.php
new file mode 100644
index 0000000..a0b0ca3
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Postnet.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Postnet extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = array(6, 7, 10, 12);
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_postnet';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Royalmail.php b/library/vendor/Zend/Validate/Barcode/Royalmail.php
new file mode 100644
index 0000000..c4c2685
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Royalmail.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Royalmail extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = -1;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ protected $_rows = array(
+ '0' => 1, '1' => 1, '2' => 1, '3' => 1, '4' => 1, '5' => 1,
+ '6' => 2, '7' => 2, '8' => 2, '9' => 2, 'A' => 2, 'B' => 2,
+ 'C' => 3, 'D' => 3, 'E' => 3, 'F' => 3, 'G' => 3, 'H' => 3,
+ 'I' => 4, 'J' => 4, 'K' => 4, 'L' => 4, 'M' => 4, 'N' => 4,
+ 'O' => 5, 'P' => 5, 'Q' => 5, 'R' => 5, 'S' => 5, 'T' => 5,
+ 'U' => 0, 'V' => 0, 'W' => 0, 'X' => 0, 'Y' => 0, 'Z' => 0,
+ );
+
+ protected $_columns = array(
+ '0' => 1, '1' => 2, '2' => 3, '3' => 4, '4' => 5, '5' => 0,
+ '6' => 1, '7' => 2, '8' => 3, '9' => 4, 'A' => 5, 'B' => 0,
+ 'C' => 1, 'D' => 2, 'E' => 3, 'F' => 4, 'G' => 5, 'H' => 0,
+ 'I' => 1, 'J' => 2, 'K' => 3, 'L' => 4, 'M' => 5, 'N' => 0,
+ 'O' => 1, 'P' => 2, 'Q' => 3, 'R' => 4, 'S' => 5, 'T' => 0,
+ 'U' => 1, 'V' => 2, 'W' => 3, 'X' => 4, 'Y' => 5, 'Z' => 0,
+ );
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_royalmail';
+
+ /**
+ * Validates the checksum ()
+ *
+ * @param string $value The barcode to validate
+ * @return boolean
+ */
+ protected function _royalmail($value)
+ {
+ $checksum = substr($value, -1, 1);
+ $values = str_split(substr($value, 0, -1));
+ $rowvalue = 0;
+ $colvalue = 0;
+ foreach($values as $row) {
+ $rowvalue += $this->_rows[$row];
+ $colvalue += $this->_columns[$row];
+ }
+
+ $rowvalue %= 6;
+ $colvalue %= 6;
+
+ $rowchkvalue = array_keys($this->_rows, $rowvalue);
+ $colchkvalue = array_keys($this->_columns, $colvalue);
+ $chkvalue = current(array_intersect($rowchkvalue, $colchkvalue));
+ if ($chkvalue == $checksum) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Allows start and stop tag within checked chars
+ *
+ * @param string $value The barcode to check for allowed characters
+ * @return boolean
+ */
+ public function checkChars($value)
+ {
+ if ($value[0] == '(') {
+ $value = substr($value, 1);
+
+ if ($value[strlen($value) - 1] == ')') {
+ $value = substr($value, 0, -1);
+ } else {
+ return false;
+ }
+ }
+
+ return parent::checkChars($value);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Sscc.php b/library/vendor/Zend/Validate/Barcode/Sscc.php
new file mode 100644
index 0000000..e234bf1
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Sscc.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Sscc extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 18;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Upca.php b/library/vendor/Zend/Validate/Barcode/Upca.php
new file mode 100644
index 0000000..4b92bd6
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Upca.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Upca extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = 12;
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+}
diff --git a/library/vendor/Zend/Validate/Barcode/Upce.php b/library/vendor/Zend/Validate/Barcode/Upce.php
new file mode 100644
index 0000000..8d8dd2a
--- /dev/null
+++ b/library/vendor/Zend/Validate/Barcode/Upce.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Barcode_AdapterAbstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Barcode_Upce extends Zend_Validate_Barcode_AdapterAbstract
+{
+ /**
+ * Allowed barcode lengths
+ * @var integer
+ */
+ protected $_length = array(6, 7, 8);
+
+ /**
+ * Allowed barcode characters
+ * @var string
+ */
+ protected $_characters = '0123456789';
+
+ /**
+ * Checksum function
+ * @var string
+ */
+ protected $_checksum = '_gtin';
+
+ /**
+ * Overrides parent checkLength
+ *
+ * @param string $value Value
+ * @return boolean
+ */
+ public function checkLength($value)
+ {
+ if (strlen($value) != 8) {
+ $this->setCheck(false);
+ } else {
+ $this->setCheck(true);
+ }
+
+ return parent::checkLength($value);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Between.php b/library/vendor/Zend/Validate/Between.php
new file mode 100644
index 0000000..09ba84d
--- /dev/null
+++ b/library/vendor/Zend/Validate/Between.php
@@ -0,0 +1,222 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Between extends Zend_Validate_Abstract
+{
+ /**
+ * Validation failure message key for when the value is not between the min and max, inclusively
+ */
+ const NOT_BETWEEN = 'notBetween';
+
+ /**
+ * Validation failure message key for when the value is not strictly between the min and max
+ */
+ const NOT_BETWEEN_STRICT = 'notBetweenStrict';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_BETWEEN => "'%value%' is not between '%min%' and '%max%', inclusively",
+ self::NOT_BETWEEN_STRICT => "'%value%' is not strictly between '%min%' and '%max%'"
+ );
+
+ /**
+ * Additional variables available for validation failure messages
+ *
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'min' => '_min',
+ 'max' => '_max'
+ );
+
+ /**
+ * Minimum value
+ *
+ * @var mixed
+ */
+ protected $_min;
+
+ /**
+ * Maximum value
+ *
+ * @var mixed
+ */
+ protected $_max;
+
+ /**
+ * Whether to do inclusive comparisons, allowing equivalence to min and/or max
+ *
+ * If false, then strict comparisons are done, and the value may equal neither
+ * the min nor max options
+ *
+ * @var boolean
+ */
+ protected $_inclusive;
+
+ /**
+ * Sets validator options
+ * Accepts the following option keys:
+ * 'min' => scalar, minimum border
+ * 'max' => scalar, maximum border
+ * 'inclusive' => boolean, inclusive border values
+ *
+ * @param array|Zend_Config $options
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['min'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['max'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['inclusive'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (!array_key_exists('min', $options) || !array_key_exists('max', $options)) {
+ throw new Zend_Validate_Exception("Missing option. 'min' and 'max' has to be given");
+ }
+
+ if (!array_key_exists('inclusive', $options)) {
+ $options['inclusive'] = true;
+ }
+
+ $this->setMin($options['min'])
+ ->setMax($options['max'])
+ ->setInclusive($options['inclusive']);
+ }
+
+ /**
+ * Returns the min option
+ *
+ * @return mixed
+ */
+ public function getMin()
+ {
+ return $this->_min;
+ }
+
+ /**
+ * Sets the min option
+ *
+ * @param mixed $min
+ * @return Zend_Validate_Between Provides a fluent interface
+ */
+ public function setMin($min)
+ {
+ $this->_min = $min;
+ return $this;
+ }
+
+ /**
+ * Returns the max option
+ *
+ * @return mixed
+ */
+ public function getMax()
+ {
+ return $this->_max;
+ }
+
+ /**
+ * Sets the max option
+ *
+ * @param mixed $max
+ * @return Zend_Validate_Between Provides a fluent interface
+ */
+ public function setMax($max)
+ {
+ $this->_max = $max;
+ return $this;
+ }
+
+ /**
+ * Returns the inclusive option
+ *
+ * @return boolean
+ */
+ public function getInclusive()
+ {
+ return $this->_inclusive;
+ }
+
+ /**
+ * Sets the inclusive option
+ *
+ * @param boolean $inclusive
+ * @return Zend_Validate_Between Provides a fluent interface
+ */
+ public function setInclusive($inclusive)
+ {
+ $this->_inclusive = $inclusive;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is between min and max options, inclusively
+ * if inclusive option is true.
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+
+ if ($this->_inclusive) {
+ if ($this->_min > $value || $value > $this->_max) {
+ $this->_error(self::NOT_BETWEEN);
+ return false;
+ }
+ } else {
+ if ($this->_min >= $value || $value >= $this->_max) {
+ $this->_error(self::NOT_BETWEEN_STRICT);
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Validate/Callback.php b/library/vendor/Zend/Validate/Callback.php
new file mode 100644
index 0000000..e333cad
--- /dev/null
+++ b/library/vendor/Zend/Validate/Callback.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Callback extends Zend_Validate_Abstract
+{
+ /**
+ * Invalid callback
+ */
+ const INVALID_CALLBACK = 'callbackInvalid';
+
+ /**
+ * Invalid value
+ */
+ const INVALID_VALUE = 'callbackValue';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID_VALUE => "'%value%' is not valid",
+ self::INVALID_CALLBACK => "An exception has been raised within the callback",
+ );
+
+ /**
+ * Callback in a call_user_func format
+ *
+ * @var string|array
+ */
+ protected $_callback = null;
+
+ /**
+ * Default options to set for the filter
+ *
+ * @var mixed
+ */
+ protected $_options = array();
+
+ /**
+ * Sets validator options
+ *
+ * @param mixed $callback
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($callback = null)
+ {
+ if (is_callable($callback)) {
+ $this->setCallback($callback);
+ } elseif (is_array($callback)) {
+ if (isset($callback['callback'])) {
+ $this->setCallback($callback['callback']);
+ }
+ if (isset($callback['options'])) {
+ $this->setOptions($callback['options']);
+ }
+ }
+
+ if (null === ($initializedCallack = $this->getCallback())) {
+ throw new Zend_Validate_Exception('No callback registered');
+ }
+ }
+
+ /**
+ * Returns the set callback
+ *
+ * @return mixed
+ */
+ public function getCallback()
+ {
+ return $this->_callback;
+ }
+
+ /**
+ * Sets the callback
+ *
+ * @param string|array $callback
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_Callback Provides a fluent interface
+ */
+ public function setCallback($callback)
+ {
+ if (!is_callable($callback)) {
+ throw new Zend_Validate_Exception('Invalid callback given');
+ }
+ $this->_callback = $callback;
+ return $this;
+ }
+
+ /**
+ * Returns the set options for the callback
+ *
+ * @return mixed
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets options for the callback
+ *
+ * @param mixed $options
+ * @return Zend_Validate_Callback Provides a fluent interface
+ */
+ public function setOptions($options)
+ {
+ $this->_options = (array) $options;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the set callback returns
+ * for the provided $value
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+
+ $options = $this->getOptions();
+ $callback = $this->getCallback();
+ $args = func_get_args();
+ $options = array_merge($args, $options);
+
+ try {
+ if (!call_user_func_array($callback, $options)) {
+ $this->_error(self::INVALID_VALUE);
+ return false;
+ }
+ } catch (Exception $e) {
+ $this->_error(self::INVALID_CALLBACK);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Ccnum.php b/library/vendor/Zend/Validate/Ccnum.php
new file mode 100644
index 0000000..150f64c
--- /dev/null
+++ b/library/vendor/Zend/Validate/Ccnum.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Ccnum extends Zend_Validate_Abstract
+{
+ /**
+ * Validation failure message key for when the value is not of valid length
+ */
+ const LENGTH = 'ccnumLength';
+
+ /**
+ * Validation failure message key for when the value fails the mod-10 checksum
+ */
+ const CHECKSUM = 'ccnumChecksum';
+
+ /**
+ * Digits filter for input
+ *
+ * @var Zend_Filter_Digits
+ */
+ protected static $_filter = null;
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::LENGTH => "'%value%' must contain between 13 and 19 digits",
+ self::CHECKSUM => "Luhn algorithm (mod-10 checksum) failed on '%value%'"
+ );
+
+ public function __construct()
+ {
+ trigger_error('Using the Ccnum validator is deprecated in favor of the CreditCard validator');
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum)
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+
+ if (null === self::$_filter) {
+ /**
+ * @see Zend_Filter_Digits
+ */
+ self::$_filter = new Zend_Filter_Digits();
+ }
+
+ $valueFiltered = self::$_filter->filter($value);
+
+ $length = strlen($valueFiltered);
+
+ if ($length < 13 || $length > 19) {
+ $this->_error(self::LENGTH);
+ return false;
+ }
+
+ $sum = 0;
+ $weight = 2;
+
+ for ($i = $length - 2; $i >= 0; $i--) {
+ $digit = $weight * $valueFiltered[$i];
+ $sum += floor($digit / 10) + $digit % 10;
+ $weight = $weight % 2 + 1;
+ }
+
+ if ((10 - $sum % 10) % 10 != $valueFiltered[$length - 1]) {
+ $this->_error(self::CHECKSUM, $valueFiltered);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/CreditCard.php b/library/vendor/Zend/Validate/CreditCard.php
new file mode 100644
index 0000000..1302626
--- /dev/null
+++ b/library/vendor/Zend/Validate/CreditCard.php
@@ -0,0 +1,316 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_CreditCard extends Zend_Validate_Abstract
+{
+ /**
+ * Detected CCI list
+ *
+ * @var string
+ */
+ const ALL = 'All';
+ const AMERICAN_EXPRESS = 'American_Express';
+ const UNIONPAY = 'Unionpay';
+ const DINERS_CLUB = 'Diners_Club';
+ const DINERS_CLUB_US = 'Diners_Club_US';
+ const DISCOVER = 'Discover';
+ const JCB = 'JCB';
+ const LASER = 'Laser';
+ const MAESTRO = 'Maestro';
+ const MASTERCARD = 'Mastercard';
+ const SOLO = 'Solo';
+ const VISA = 'Visa';
+
+ const CHECKSUM = 'creditcardChecksum';
+ const CONTENT = 'creditcardContent';
+ const INVALID = 'creditcardInvalid';
+ const LENGTH = 'creditcardLength';
+ const PREFIX = 'creditcardPrefix';
+ const SERVICE = 'creditcardService';
+ const SERVICEFAILURE = 'creditcardServiceFailure';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::CHECKSUM => "'%value%' seems to contain an invalid checksum",
+ self::CONTENT => "'%value%' must contain only digits",
+ self::INVALID => "Invalid type given. String expected",
+ self::LENGTH => "'%value%' contains an invalid amount of digits",
+ self::PREFIX => "'%value%' is not from an allowed institute",
+ self::SERVICE => "'%value%' seems to be an invalid creditcard number",
+ self::SERVICEFAILURE => "An exception has been raised while validating '%value%'",
+ );
+
+ /**
+ * List of allowed CCV lengths
+ *
+ * @var array
+ */
+ protected $_cardLength = array(
+ self::AMERICAN_EXPRESS => array(15),
+ self::DINERS_CLUB => array(14),
+ self::DINERS_CLUB_US => array(16),
+ self::DISCOVER => array(16),
+ self::JCB => array(16),
+ self::LASER => array(16, 17, 18, 19),
+ self::MAESTRO => array(12, 13, 14, 15, 16, 17, 18, 19),
+ self::MASTERCARD => array(16),
+ self::SOLO => array(16, 18, 19),
+ self::UNIONPAY => array(16, 17, 18, 19),
+ self::VISA => array(16),
+ );
+
+ /**
+ * List of accepted CCV provider tags
+ *
+ * @var array
+ */
+ protected $_cardType = array(
+ self::AMERICAN_EXPRESS => array('34', '37'),
+ self::DINERS_CLUB => array('300', '301', '302', '303', '304', '305', '36'),
+ self::DINERS_CLUB_US => array('54', '55'),
+ self::DISCOVER => array('6011', '622126', '622127', '622128', '622129', '62213',
+ '62214', '62215', '62216', '62217', '62218', '62219',
+ '6222', '6223', '6224', '6225', '6226', '6227', '6228',
+ '62290', '62291', '622920', '622921', '622922', '622923',
+ '622924', '622925', '644', '645', '646', '647', '648',
+ '649', '65'),
+ self::JCB => array('3528', '3529', '353', '354', '355', '356', '357', '358'),
+ self::LASER => array('6304', '6706', '6771', '6709'),
+ self::MAESTRO => array('5018', '5020', '5038', '6304', '6759', '6761', '6763'),
+ self::MASTERCARD => array('51', '52', '53', '54', '55'),
+ self::SOLO => array('6334', '6767'),
+ self::UNIONPAY => array('622126', '622127', '622128', '622129', '62213', '62214',
+ '62215', '62216', '62217', '62218', '62219', '6222', '6223',
+ '6224', '6225', '6226', '6227', '6228', '62290', '62291',
+ '622920', '622921', '622922', '622923', '622924', '622925'),
+ self::VISA => array('4'),
+ );
+
+ /**
+ * CCIs which are accepted by validation
+ *
+ * @var array
+ */
+ protected $_type = array();
+
+ /**
+ * Service callback for additional validation
+ *
+ * @var callback
+ */
+ protected $_service;
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $options OPTIONAL Type of CCI to allow
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['type'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['service'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (!array_key_exists('type', $options)) {
+ $options['type'] = self::ALL;
+ }
+
+ $this->setType($options['type']);
+ if (array_key_exists('service', $options)) {
+ $this->setService($options['service']);
+ }
+ }
+
+ /**
+ * Returns a list of accepted CCIs
+ *
+ * @return array
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Sets CCIs which are accepted by validation
+ *
+ * @param string|array $type Type to allow for validation
+ * @return Zend_Validate_CreditCard Provides a fluent interface
+ */
+ public function setType($type)
+ {
+ $this->_type = array();
+ return $this->addType($type);
+ }
+
+ /**
+ * Adds a CCI to be accepted by validation
+ *
+ * @param string|array $type Type to allow for validation
+ * @return Zend_Validate_CreditCard Provides a fluent interface
+ */
+ public function addType($type)
+ {
+ if (is_string($type)) {
+ $type = array($type);
+ }
+
+ foreach($type as $typ) {
+ if (defined('self::' . strtoupper($typ)) && !in_array($typ, $this->_type)) {
+ $this->_type[] = $typ;
+ }
+
+ if (($typ == self::ALL)) {
+ $this->_type = array_keys($this->_cardLength);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the actual set service
+ *
+ * @return callback
+ */
+ public function getService()
+ {
+ return $this->_service;
+ }
+
+ /**
+ * Sets a new callback for service validation
+ *
+ * @param mixed $service
+ * @throws Zend_Validate_Exception
+ * @return $this
+ */
+ public function setService($service)
+ {
+ if (!is_callable($service)) {
+ throw new Zend_Validate_Exception('Invalid callback given');
+ }
+
+ $this->_service = $service;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum)
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+
+ if (!is_string($value)) {
+ $this->_error(self::INVALID, $value);
+ return false;
+ }
+
+ if (!ctype_digit($value)) {
+ $this->_error(self::CONTENT, $value);
+ return false;
+ }
+
+ $length = strlen($value);
+ $types = $this->getType();
+ $foundp = false;
+ $foundl = false;
+ foreach ($types as $type) {
+ foreach ($this->_cardType[$type] as $prefix) {
+ if (substr($value, 0, strlen($prefix)) == $prefix) {
+ $foundp = true;
+ if (in_array($length, $this->_cardLength[$type])) {
+ $foundl = true;
+ break 2;
+ }
+ }
+ }
+ }
+
+ if ($foundp == false){
+ $this->_error(self::PREFIX, $value);
+ return false;
+ }
+
+ if ($foundl == false) {
+ $this->_error(self::LENGTH, $value);
+ return false;
+ }
+
+ $sum = 0;
+ $weight = 2;
+
+ for ($i = $length - 2; $i >= 0; $i--) {
+ $digit = $weight * $value[$i];
+ $sum += floor($digit / 10) + $digit % 10;
+ $weight = $weight % 2 + 1;
+ }
+
+ if ((10 - $sum % 10) % 10 != $value[$length - 1]) {
+ $this->_error(self::CHECKSUM, $value);
+ return false;
+ }
+
+ if (!empty($this->_service)) {
+ try {
+ $callback = new Zend_Validate_Callback($this->_service);
+ $callback->setOptions($this->_type);
+ if (!$callback->isValid($value)) {
+ $this->_error(self::SERVICE, $value);
+ return false;
+ }
+ } catch (Zend_Exception $e) {
+ $this->_error(self::SERVICEFAILURE, $value);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Date.php b/library/vendor/Zend/Validate/Date.php
new file mode 100644
index 0000000..3851a8e
--- /dev/null
+++ b/library/vendor/Zend/Validate/Date.php
@@ -0,0 +1,253 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Date extends Zend_Validate_Abstract
+{
+ const INVALID = 'dateInvalid';
+ const INVALID_DATE = 'dateInvalidDate';
+ const FALSEFORMAT = 'dateFalseFormat';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String, integer, array or Zend_Date expected",
+ self::INVALID_DATE => "'%value%' does not appear to be a valid date",
+ self::FALSEFORMAT => "'%value%' does not fit the date format '%format%'",
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'format' => '_format'
+ );
+
+ /**
+ * Optional format
+ *
+ * @var string|null
+ */
+ protected $_format;
+
+ /**
+ * Optional locale
+ *
+ * @var string|Zend_Locale|null
+ */
+ protected $_locale;
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array|Zend_Config $options OPTIONAL
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['format'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['locale'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (array_key_exists('format', $options)) {
+ $this->setFormat($options['format']);
+ }
+
+ if (!array_key_exists('locale', $options)) {
+ if (Zend_Registry::isRegistered('Zend_Locale')) {
+ $options['locale'] = Zend_Registry::get('Zend_Locale');
+ }
+ }
+
+ if (array_key_exists('locale', $options)) {
+ $this->setLocale($options['locale']);
+ }
+ }
+
+ /**
+ * Returns the locale option
+ *
+ * @return string|Zend_Locale|null
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Sets the locale option
+ *
+ * @param string|Zend_Locale $locale
+ * @return Zend_Validate_Date provides a fluent interface
+ */
+ public function setLocale($locale = null)
+ {
+ $this->_locale = Zend_Locale::findLocale($locale);
+ return $this;
+ }
+
+ /**
+ * Returns the locale option
+ *
+ * @return string|null
+ */
+ public function getFormat()
+ {
+ return $this->_format;
+ }
+
+ /**
+ * Sets the format option
+ *
+ * @param string $format
+ * @return Zend_Validate_Date provides a fluent interface
+ */
+ public function setFormat($format = null)
+ {
+ $this->_format = $format;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if $value is a valid date of the format YYYY-MM-DD
+ * If optional $format or $locale is set the date format is checked
+ * according to Zend_Date, see Zend_Date::isDate()
+ *
+ * @param string|array|Zend_Date $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value) && !is_float($value) &&
+ !is_array($value) && !($value instanceof Zend_Date)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+
+ if (($this->_format !== null) || ($this->_locale !== null) || is_array($value) ||
+ $value instanceof Zend_Date) {
+ if (!Zend_Date::isDate($value, $this->_format, $this->_locale)) {
+ if ($this->_checkFormat($value) === false) {
+ $this->_error(self::FALSEFORMAT);
+ } else {
+ $this->_error(self::INVALID_DATE);
+ }
+ return false;
+ }
+ } else {
+ if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
+ $this->_format = 'yyyy-MM-dd';
+ $this->_error(self::FALSEFORMAT);
+ $this->_format = null;
+ return false;
+ }
+
+ list($year, $month, $day) = sscanf($value, '%d-%d-%d');
+
+ if (!checkdate($month, $day, $year)) {
+ $this->_error(self::INVALID_DATE);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if the given date fits the given format
+ *
+ * @param string $value Date to check
+ * @return boolean False when date does not fit the format
+ */
+ private function _checkFormat($value)
+ {
+ try {
+ $parsed = Zend_Locale_Format::getDate($value, array(
+ 'date_format' => $this->_format, 'format_type' => 'iso',
+ 'fix_date' => false));
+ if (isset($parsed['year']) and ((strpos(strtoupper($this->_format), 'YY') !== false) and
+ (strpos(strtoupper($this->_format), 'YYYY') === false))) {
+ $parsed['year'] = Zend_Date::getFullYear($parsed['year']);
+ }
+ } catch (Exception $e) {
+ // Date can not be parsed
+ return false;
+ }
+
+ if (((strpos($this->_format, 'Y') !== false) or (strpos($this->_format, 'y') !== false)) and
+ (!isset($parsed['year']))) {
+ // Year expected but not found
+ return false;
+ }
+
+ if ((strpos($this->_format, 'M') !== false) and (!isset($parsed['month']))) {
+ // Month expected but not found
+ return false;
+ }
+
+ if ((strpos($this->_format, 'd') !== false) and (!isset($parsed['day']))) {
+ // Day expected but not found
+ return false;
+ }
+
+ if (((strpos($this->_format, 'H') !== false) or (strpos($this->_format, 'h') !== false)) and
+ (!isset($parsed['hour']))) {
+ // Hour expected but not found
+ return false;
+ }
+
+ if ((strpos($this->_format, 'm') !== false) and (!isset($parsed['minute']))) {
+ // Minute expected but not found
+ return false;
+ }
+
+ if ((strpos($this->_format, 's') !== false) and (!isset($parsed['second']))) {
+ // Second expected but not found
+ return false;
+ }
+
+ // Date fits the format
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Db/Abstract.php b/library/vendor/Zend/Validate/Db/Abstract.php
new file mode 100644
index 0000000..b08b1ca
--- /dev/null
+++ b/library/vendor/Zend/Validate/Db/Abstract.php
@@ -0,0 +1,350 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Class for Database record validation
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @uses Zend_Validate_Abstract
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_Validate_Db_Abstract extends Zend_Validate_Abstract
+{
+ /**
+ * Error constants
+ */
+ const ERROR_NO_RECORD_FOUND = 'noRecordFound';
+ const ERROR_RECORD_FOUND = 'recordFound';
+
+ /**
+ * @var array Message templates
+ */
+ protected $_messageTemplates = array(
+ self::ERROR_NO_RECORD_FOUND => "No record matching '%value%' was found",
+ self::ERROR_RECORD_FOUND => "A record matching '%value%' was found",
+ );
+
+ /**
+ * @var string
+ */
+ protected $_schema = null;
+
+ /**
+ * @var string
+ */
+ protected $_table = '';
+
+ /**
+ * @var string
+ */
+ protected $_field = '';
+
+ /**
+ * @var mixed
+ */
+ protected $_exclude = null;
+
+ /**
+ * Database adapter to use. If null isValid() will use Zend_Db::getInstance instead
+ *
+ * @var unknown_type
+ */
+ protected $_adapter = null;
+
+ /**
+ * Select object to use. can be set, or will be auto-generated
+ * @var Zend_Db_Select
+ */
+ protected $_select;
+
+ /**
+ * Provides basic configuration for use with Zend_Validate_Db Validators
+ * Setting $exclude allows a single record to be excluded from matching.
+ * Exclude can either be a String containing a where clause, or an array with `field` and `value` keys
+ * to define the where clause added to the sql.
+ * A database adapter may optionally be supplied to avoid using the registered default adapter.
+ *
+ * The following option keys are supported:
+ * 'table' => The database table to validate against
+ * 'schema' => The schema keys
+ * 'field' => The field to check for a match
+ * 'exclude' => An optional where clause or field/value pair to exclude from the query
+ * 'adapter' => An optional database adapter to use
+ *
+ * @param array|Zend_Config $options Options to use for this validator
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Db_Select) {
+ $this->setSelect($options);
+ return;
+ }
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (func_num_args() > 1) {
+ $options = func_get_args();
+ $temp['table'] = array_shift($options);
+ $temp['field'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['exclude'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['adapter'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (!array_key_exists('table', $options) && !array_key_exists('schema', $options)) {
+ throw new Zend_Validate_Exception('Table or Schema option missing!');
+ }
+
+ if (!array_key_exists('field', $options)) {
+ throw new Zend_Validate_Exception('Field option missing!');
+ }
+
+ if (array_key_exists('adapter', $options)) {
+ $this->setAdapter($options['adapter']);
+ }
+
+ if (array_key_exists('exclude', $options)) {
+ $this->setExclude($options['exclude']);
+ }
+
+ $this->setField($options['field']);
+ if (array_key_exists('table', $options)) {
+ $this->setTable($options['table']);
+ }
+
+ if (array_key_exists('schema', $options)) {
+ $this->setSchema($options['schema']);
+ }
+ }
+
+ /**
+ * Returns the set adapter
+ *
+ * @throws Zend_Validate_Exception
+ * @return Zend_Db_Adapter
+ */
+ public function getAdapter()
+ {
+ /**
+ * Check for an adapter being defined. if not, fetch the default adapter.
+ */
+ if ($this->_adapter === null) {
+ $this->_adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+ if (null === $this->_adapter) {
+ throw new Zend_Validate_Exception('No database adapter present');
+ }
+ }
+ return $this->_adapter;
+ }
+
+ /**
+ * Sets a new database adapter
+ *
+ * @param Zend_Db_Adapter_Abstract $adapter
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_Db_Abstract
+ */
+ public function setAdapter($adapter)
+ {
+ if (!($adapter instanceof Zend_Db_Adapter_Abstract)) {
+ throw new Zend_Validate_Exception('Adapter option must be a database adapter!');
+ }
+
+ $this->_adapter = $adapter;
+ return $this;
+ }
+
+ /**
+ * Returns the set exclude clause
+ *
+ * @return string|array
+ */
+ public function getExclude()
+ {
+ return $this->_exclude;
+ }
+
+ /**
+ * Sets a new exclude clause
+ *
+ * @param string|array $exclude
+ * @return Zend_Validate_Db_Abstract
+ */
+ public function setExclude($exclude)
+ {
+ $this->_exclude = $exclude;
+ return $this;
+ }
+
+ /**
+ * Returns the set field
+ *
+ * @return string|array
+ */
+ public function getField()
+ {
+ return $this->_field;
+ }
+
+ /**
+ * Sets a new field
+ *
+ * @param string $field
+ * @return Zend_Validate_Db_Abstract
+ */
+ public function setField($field)
+ {
+ $this->_field = (string) $field;
+ return $this;
+ }
+
+ /**
+ * Returns the set table
+ *
+ * @return string
+ */
+ public function getTable()
+ {
+ return $this->_table;
+ }
+
+ /**
+ * Sets a new table
+ *
+ * @param string $table
+ * @return Zend_Validate_Db_Abstract
+ */
+ public function setTable($table)
+ {
+ $this->_table = (string) $table;
+ return $this;
+ }
+
+ /**
+ * Returns the set schema
+ *
+ * @return string
+ */
+ public function getSchema()
+ {
+ return $this->_schema;
+ }
+
+ /**
+ * Sets a new schema
+ *
+ * @param string $schema
+ * @return Zend_Validate_Db_Abstract
+ */
+ public function setSchema($schema)
+ {
+ $this->_schema = $schema;
+ return $this;
+ }
+
+ /**
+ * Sets the select object to be used by the validator
+ *
+ * @param Zend_Db_Select $select
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_Db_Abstract
+ */
+ public function setSelect($select)
+ {
+ if (!$select instanceof Zend_Db_Select) {
+ throw new Zend_Validate_Exception('Select option must be a valid ' .
+ 'Zend_Db_Select object');
+ }
+ $this->_select = $select;
+ return $this;
+ }
+
+ /**
+ * Gets the select object to be used by the validator.
+ * If no select object was supplied to the constructor,
+ * then it will auto-generate one from the given table,
+ * schema, field, and adapter options.
+ *
+ * @return Zend_Db_Select The Select object which will be used
+ */
+ public function getSelect()
+ {
+ if (null === $this->_select) {
+ $db = $this->getAdapter();
+ /**
+ * Build select object
+ */
+ $select = new Zend_Db_Select($db);
+ $select->from($this->_table, array($this->_field), $this->_schema);
+ if ($db->supportsParameters('named')) {
+ $select->where($db->quoteIdentifier($this->_field, true).' = :value'); // named
+ } else {
+ $select->where($db->quoteIdentifier($this->_field, true).' = ?'); // positional
+ }
+ if ($this->_exclude !== null) {
+ if (is_array($this->_exclude)) {
+ $select->where(
+ $db->quoteIdentifier($this->_exclude['field'], true) .
+ ' != ?', $this->_exclude['value']
+ );
+ } else {
+ $select->where($this->_exclude);
+ }
+ }
+ $select->limit(1);
+ $this->_select = $select;
+ }
+ return $this->_select;
+ }
+
+ /**
+ * Run query and returns matches, or null if no matches are found.
+ *
+ * @param String $value
+ * @return Array when matches are found.
+ */
+ protected function _query($value)
+ {
+ $select = $this->getSelect();
+ /**
+ * Run query
+ */
+ $result = $select->getAdapter()->fetchRow(
+ $select,
+ array('value' => $value), // this should work whether db supports positional or named params
+ Zend_Db::FETCH_ASSOC
+ );
+
+ return $result;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Db/NoRecordExists.php b/library/vendor/Zend/Validate/Db/NoRecordExists.php
new file mode 100644
index 0000000..3d1628a
--- /dev/null
+++ b/library/vendor/Zend/Validate/Db/NoRecordExists.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Db_Abstract
+ */
+
+/**
+ * Confirms a record does not exist in a table.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @uses Zend_Validate_Db_Abstract
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Db_NoRecordExists extends Zend_Validate_Db_Abstract
+{
+ public function isValid($value)
+ {
+ $valid = true;
+ $this->_setValue($value);
+
+ $result = $this->_query($value);
+ if ($result) {
+ $valid = false;
+ $this->_error(self::ERROR_RECORD_FOUND);
+ }
+
+ return $valid;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Db/RecordExists.php b/library/vendor/Zend/Validate/Db/RecordExists.php
new file mode 100644
index 0000000..fb65f70
--- /dev/null
+++ b/library/vendor/Zend/Validate/Db/RecordExists.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Db_Abstract
+ */
+
+/**
+ * Confirms a record exists in a table.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @uses Zend_Validate_Db_Abstract
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Db_RecordExists extends Zend_Validate_Db_Abstract
+{
+ public function isValid($value)
+ {
+ $valid = true;
+ $this->_setValue($value);
+
+ $result = $this->_query($value);
+ if (!$result) {
+ $valid = false;
+ $this->_error(self::ERROR_NO_RECORD_FOUND);
+ }
+
+ return $valid;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Digits.php b/library/vendor/Zend/Validate/Digits.php
new file mode 100644
index 0000000..b18b590
--- /dev/null
+++ b/library/vendor/Zend/Validate/Digits.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Digits extends Zend_Validate_Abstract
+{
+ const NOT_DIGITS = 'notDigits';
+ const STRING_EMPTY = 'digitsStringEmpty';
+ const INVALID = 'digitsInvalid';
+
+ /**
+ * Digits filter used for validation
+ *
+ * @var Zend_Filter_Digits
+ */
+ protected static $_filter = null;
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_DIGITS => "'%value%' must contain only digits",
+ self::STRING_EMPTY => "'%value%' is an empty string",
+ self::INVALID => "Invalid type given. String, integer or float expected",
+ );
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value only contains digit characters
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value) && !is_float($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue((string) $value);
+
+ if ('' === $this->_value) {
+ $this->_error(self::STRING_EMPTY);
+ return false;
+ }
+
+ if (null === self::$_filter) {
+ self::$_filter = new Zend_Filter_Digits();
+ }
+
+ if ($this->_value !== self::$_filter->filter($this->_value)) {
+ $this->_error(self::NOT_DIGITS);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/EmailAddress.php b/library/vendor/Zend/Validate/EmailAddress.php
new file mode 100644
index 0000000..de7726a
--- /dev/null
+++ b/library/vendor/Zend/Validate/EmailAddress.php
@@ -0,0 +1,571 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Validate_Hostname
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_EmailAddress extends Zend_Validate_Abstract
+{
+ const INVALID = 'emailAddressInvalid';
+ const INVALID_FORMAT = 'emailAddressInvalidFormat';
+ const INVALID_HOSTNAME = 'emailAddressInvalidHostname';
+ const INVALID_MX_RECORD = 'emailAddressInvalidMxRecord';
+ const INVALID_SEGMENT = 'emailAddressInvalidSegment';
+ const DOT_ATOM = 'emailAddressDotAtom';
+ const QUOTED_STRING = 'emailAddressQuotedString';
+ const INVALID_LOCAL_PART = 'emailAddressInvalidLocalPart';
+ const LENGTH_EXCEEDED = 'emailAddressLengthExceeded';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String expected",
+ self::INVALID_FORMAT => "'%value%' is not a valid email address in the basic format local-part@hostname",
+ self::INVALID_HOSTNAME => "'%hostname%' is not a valid hostname for email address '%value%'",
+ self::INVALID_MX_RECORD => "'%hostname%' does not appear to have a valid MX record for the email address '%value%'",
+ self::INVALID_SEGMENT => "'%hostname%' is not in a routable network segment. The email address '%value%' should not be resolved from public network",
+ self::DOT_ATOM => "'%localPart%' can not be matched against dot-atom format",
+ self::QUOTED_STRING => "'%localPart%' can not be matched against quoted-string format",
+ self::INVALID_LOCAL_PART => "'%localPart%' is not a valid local part for email address '%value%'",
+ self::LENGTH_EXCEEDED => "'%value%' exceeds the allowed length",
+ );
+
+ /**
+ * As of RFC5753 (JAN 2010), the following blocks are no longer reserved:
+ * - 128.0.0.0/16
+ * - 191.255.0.0/16
+ * - 223.255.255.0/24
+ * @see http://tools.ietf.org/html/rfc5735#page-6
+ *
+ * As of RFC6598 (APR 2012), the following blocks are now reserved:
+ * - 100.64.0.0/10
+ * @see http://tools.ietf.org/html/rfc6598#section-7
+ *
+ * @see http://en.wikipedia.org/wiki/IPv4
+ * @var array
+ */
+ protected $_invalidIp = array(
+ '0' => '0.0.0.0/8',
+ '10' => '10.0.0.0/8',
+ '100' => '100.64.0.0/10',
+ '127' => '127.0.0.0/8',
+ '169' => '169.254.0.0/16',
+ '172' => '172.16.0.0/12',
+ '192' => array(
+ '192.0.0.0/24',
+ '192.0.2.0/24',
+ '192.88.99.0/24',
+ '192.168.0.0/16'
+ ),
+ '198' => '198.18.0.0/15',
+ '224' => '224.0.0.0/4',
+ '240' => '240.0.0.0/4'
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'hostname' => '_hostname',
+ 'localPart' => '_localPart'
+ );
+
+ /**
+ * @var string
+ */
+ protected $_hostname;
+
+ /**
+ * @var string
+ */
+ protected $_localPart;
+
+ /**
+ * Internal options array
+ */
+ protected $_options = array(
+ 'mx' => false,
+ 'deep' => false,
+ 'domain' => true,
+ 'allow' => Zend_Validate_Hostname::ALLOW_DNS,
+ 'hostname' => null
+ );
+
+ /**
+ * Instantiates hostname validator for local use
+ *
+ * The following option keys are supported:
+ * 'hostname' => A hostname validator, see Zend_Validate_Hostname
+ * 'allow' => Options for the hostname validator, see Zend_Validate_Hostname::ALLOW_*
+ * 'mx' => If MX check should be enabled, boolean
+ * 'deep' => If a deep MX check should be done, boolean
+ *
+ * @param array|string|Zend_Config $options OPTIONAL
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['allow'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['mx'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['hostname'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ $options += $this->_options;
+ $this->setOptions($options);
+ }
+
+ /**
+ * Returns all set Options
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Set options for the email validator
+ *
+ * @param array $options
+ * @return Zend_Validate_EmailAddress Provides a fluent inteface
+ */
+ public function setOptions(array $options = array())
+ {
+ if (array_key_exists('messages', $options)) {
+ $this->setMessages($options['messages']);
+ }
+
+ if (array_key_exists('hostname', $options)) {
+ if (array_key_exists('allow', $options)) {
+ $this->setHostnameValidator($options['hostname'], $options['allow']);
+ } else {
+ $this->setHostnameValidator($options['hostname']);
+ }
+ } elseif ($this->_options['hostname'] == null) {
+ $this->setHostnameValidator();
+ }
+
+ if (array_key_exists('mx', $options)) {
+ $this->setValidateMx($options['mx']);
+ }
+
+ if (array_key_exists('deep', $options)) {
+ $this->setDeepMxCheck($options['deep']);
+ }
+
+ if (array_key_exists('domain', $options)) {
+ $this->setDomainCheck($options['domain']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the validation failure message template for a particular key
+ * Adds the ability to set messages to the attached hostname validator
+ *
+ * @param string $messageString
+ * @param string $messageKey OPTIONAL
+ * @return Zend_Validate_Abstract Provides a fluent interface
+ * @throws Zend_Validate_Exception
+ */
+ public function setMessage($messageString, $messageKey = null)
+ {
+ if ($messageKey === null) {
+ $this->_options['hostname']->setMessage($messageString);
+ parent::setMessage($messageString);
+ return $this;
+ }
+
+ if (!isset($this->_messageTemplates[$messageKey])) {
+ $this->_options['hostname']->setMessage($messageString, $messageKey);
+ }
+
+ $this->_messageTemplates[$messageKey] = $messageString;
+ return $this;
+ }
+
+ /**
+ * Returns the set hostname validator
+ *
+ * @return Zend_Validate_Hostname
+ */
+ public function getHostnameValidator()
+ {
+ return $this->_options['hostname'];
+ }
+
+ /**
+ * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL
+ * @param int $allow OPTIONAL
+ * @return $this
+ */
+ public function setHostnameValidator(Zend_Validate_Hostname $hostnameValidator = null, $allow = Zend_Validate_Hostname::ALLOW_DNS)
+ {
+ if (!$hostnameValidator) {
+ $hostnameValidator = new Zend_Validate_Hostname($allow);
+ }
+
+ $this->_options['hostname'] = $hostnameValidator;
+ $this->_options['allow'] = $allow;
+ return $this;
+ }
+
+ /**
+ * Whether MX checking via getmxrr is supported or not
+ *
+ * This currently only works on UNIX systems
+ *
+ * @return boolean
+ */
+ public function validateMxSupported()
+ {
+ return function_exists('getmxrr');
+ }
+
+ /**
+ * Returns the set validateMx option
+ *
+ * @return boolean
+ */
+ public function getValidateMx()
+ {
+ return $this->_options['mx'];
+ }
+
+ /**
+ * Set whether we check for a valid MX record via DNS
+ *
+ * This only applies when DNS hostnames are validated
+ *
+ * @param boolean $mx Set allowed to true to validate for MX records, and false to not validate them
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_EmailAddress Provides a fluent inteface
+ */
+ public function setValidateMx($mx)
+ {
+ if ((bool) $mx && !$this->validateMxSupported()) {
+ throw new Zend_Validate_Exception('MX checking not available on this system');
+ }
+
+ $this->_options['mx'] = (bool) $mx;
+ return $this;
+ }
+
+ /**
+ * Returns the set deepMxCheck option
+ *
+ * @return boolean
+ */
+ public function getDeepMxCheck()
+ {
+ return $this->_options['deep'];
+ }
+
+ /**
+ * Set whether we check MX record should be a deep validation
+ *
+ * @param boolean $deep Set deep to true to perform a deep validation process for MX records
+ * @return Zend_Validate_EmailAddress Provides a fluent inteface
+ */
+ public function setDeepMxCheck($deep)
+ {
+ $this->_options['deep'] = (bool) $deep;
+ return $this;
+ }
+
+ /**
+ * Returns the set domainCheck option
+ *
+ * @return unknown
+ */
+ public function getDomainCheck()
+ {
+ return $this->_options['domain'];
+ }
+
+ /**
+ * Sets if the domain should also be checked
+ * or only the local part of the email address
+ *
+ * @param boolean $domain
+ * @return Zend_Validate_EmailAddress Provides a fluent inteface
+ */
+ public function setDomainCheck($domain = true)
+ {
+ $this->_options['domain'] = (boolean) $domain;
+ return $this;
+ }
+
+ /**
+ * Returns if the given host is reserved
+ *
+ * @param string $host
+ * @return boolean
+ */
+ private function _isReserved($host){
+ if (!preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $host)) {
+ $host = gethostbyname($host);
+ }
+
+ $octet = explode('.',$host);
+ if ((int)$octet[0] >= 224) {
+ return true;
+ } else if (array_key_exists($octet[0], $this->_invalidIp)) {
+ foreach ((array)$this->_invalidIp[$octet[0]] as $subnetData) {
+ // we skip the first loop as we already know that octet matches
+ for ($i = 1; $i < 4; $i++) {
+ if (strpos($subnetData, $octet[$i]) !== $i * 4) {
+ break;
+ }
+ }
+
+ $host = explode("/", $subnetData);
+ $binaryHost = "";
+ $tmp = explode(".", $host[0]);
+ for ($i = 0; $i < 4 ; $i++) {
+ $binaryHost .= str_pad(decbin($tmp[$i]), 8, "0", STR_PAD_LEFT);
+ }
+
+ $segmentData = array(
+ 'network' => (int)$this->_toIp(str_pad(substr($binaryHost, 0, $host[1]), 32, 0)),
+ 'broadcast' => (int)$this->_toIp(str_pad(substr($binaryHost, 0, $host[1]), 32, 1))
+ );
+
+ for ($j = $i; $j < 4; $j++) {
+ if ((int)$octet[$j] < $segmentData['network'][$j] ||
+ (int)$octet[$j] > $segmentData['broadcast'][$j]) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Converts a binary string to an IP address
+ *
+ * @param string $binary
+ * @return mixed
+ */
+ private function _toIp($binary)
+ {
+ $ip = array();
+ $tmp = explode(".", chunk_split($binary, 8, "."));
+ for ($i = 0; $i < 4 ; $i++) {
+ $ip[$i] = bindec($tmp[$i]);
+ }
+
+ return $ip;
+ }
+
+ /**
+ * Internal method to validate the local part of the email address
+ *
+ * @return boolean
+ */
+ private function _validateLocalPart()
+ {
+ // First try to match the local part on the common dot-atom format
+ $result = false;
+
+ // Dot-atom characters are: 1*atext *("." 1*atext)
+ // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*",
+ // "+", "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~"
+ $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d\x7e';
+ if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->_localPart)) {
+ $result = true;
+ } else {
+ // Try quoted string format (RFC 5321 Chapter 4.1.2)
+
+ // Quoted-string characters are: DQUOTE *(qtext/quoted-pair) DQUOTE
+ $qtext = '\x20-\x21\x23-\x5b\x5d-\x7e'; // %d32-33 / %d35-91 / %d93-126
+ $quotedPair = '\x20-\x7e'; // %d92 %d32-126
+ if (preg_match('/^"(['. $qtext .']|\x5c[' . $quotedPair . '])*"$/', $this->localPart)) {
+ $result = true;
+ } else {
+ $this->_error(self::DOT_ATOM);
+ $this->_error(self::QUOTED_STRING);
+ $this->_error(self::INVALID_LOCAL_PART);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to validate the servers MX records
+ *
+ * @return boolean
+ */
+ private function _validateMXRecords()
+ {
+ $mxHosts = array();
+ $hostname = $this->_hostname;
+
+ //decode IDN domain name if possible
+ if (function_exists('idn_to_ascii')) {
+ $hostname = idn_to_ascii($this->_hostname);
+ }
+
+ $result = getmxrr($hostname, $mxHosts);
+ if (!$result) {
+ $this->_error(self::INVALID_MX_RECORD);
+ } else if ($this->_options['deep'] && function_exists('checkdnsrr')) {
+ $validAddress = false;
+ $reserved = true;
+ foreach ($mxHosts as $hostname) {
+ $res = $this->_isReserved($hostname);
+ if (!$res) {
+ $reserved = false;
+ }
+
+ if (!$res
+ && (checkdnsrr($hostname, "A")
+ || checkdnsrr($hostname, "AAAA")
+ || checkdnsrr($hostname, "A6"))) {
+ $validAddress = true;
+ break;
+ }
+ }
+
+ if (!$validAddress) {
+ $result = false;
+ if ($reserved) {
+ $this->_error(self::INVALID_SEGMENT);
+ } else {
+ $this->_error(self::INVALID_MX_RECORD);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Internal method to validate the hostname part of the email address
+ *
+ * @return boolean
+ */
+ private function _validateHostnamePart()
+ {
+ $hostname = $this->_options['hostname']->setTranslator($this->getTranslator())
+ ->isValid($this->_hostname);
+ if (!$hostname) {
+ $this->_error(self::INVALID_HOSTNAME);
+
+ // Get messages and errors from hostnameValidator
+ foreach ($this->_options['hostname']->getMessages() as $code => $message) {
+ $this->_messages[$code] = $message;
+ }
+
+ foreach ($this->_options['hostname']->getErrors() as $error) {
+ $this->_errors[] = $error;
+ }
+ } else if ($this->_options['mx']) {
+ // MX check on hostname
+ $hostname = $this->_validateMXRecords();
+ }
+
+ return $hostname;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is a valid email address
+ * according to RFC2822
+ *
+ * @link http://www.ietf.org/rfc/rfc2822.txt RFC2822
+ * @link http://www.columbia.edu/kermit/ascii.html US-ASCII characters
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $matches = array();
+ $length = true;
+ $this->_setValue($value);
+
+ // Split email address up and disallow '..'
+ if ((strpos($value, '..') !== false) or
+ (!preg_match('/^(.+)@([^@]+)$/', $value, $matches))) {
+ $this->_error(self::INVALID_FORMAT);
+ return false;
+ }
+
+ $this->_localPart = $matches[1];
+ $this->_hostname = $matches[2];
+
+ if ((strlen($this->_localPart) > 64) || (strlen($this->_hostname) > 255)) {
+ $length = false;
+ $this->_error(self::LENGTH_EXCEEDED);
+ }
+
+ // Match hostname part
+ if ($this->_options['domain']) {
+ $hostname = $this->_validateHostnamePart();
+ }
+
+ $local = $this->_validateLocalPart();
+
+ // If both parts valid, return true
+ if ($local && $length) {
+ if (($this->_options['domain'] && $hostname) || !$this->_options['domain']) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Exception.php b/library/vendor/Zend/Validate/Exception.php
new file mode 100644
index 0000000..ca88a8b
--- /dev/null
+++ b/library/vendor/Zend/Validate/Exception.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Exception
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Exception extends Zend_Exception
+{}
diff --git a/library/vendor/Zend/Validate/File/Count.php b/library/vendor/Zend/Validate/File/Count.php
new file mode 100644
index 0000000..eaa7480
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Count.php
@@ -0,0 +1,279 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for counting all given files
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Count extends Zend_Validate_Abstract
+{
+ /**#@+
+ * @const string Error constants
+ */
+ const TOO_MANY = 'fileCountTooMany';
+ const TOO_FEW = 'fileCountTooFew';
+ /**#@-*/
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::TOO_MANY => "Too many files, maximum '%max%' are allowed but '%count%' are given",
+ self::TOO_FEW => "Too few files, minimum '%min%' are expected but '%count%' are given",
+ );
+
+ /**
+ * @var array Error message template variables
+ */
+ protected $_messageVariables = array(
+ 'min' => '_min',
+ 'max' => '_max',
+ 'count' => '_count'
+ );
+
+ /**
+ * Minimum file count
+ *
+ * If null, there is no minimum file count
+ *
+ * @var integer
+ */
+ protected $_min;
+
+ /**
+ * Maximum file count
+ *
+ * If null, there is no maximum file count
+ *
+ * @var integer|null
+ */
+ protected $_max;
+
+ /**
+ * Actual filecount
+ *
+ * @var integer
+ */
+ protected $_count;
+
+ /**
+ * Internal file array
+ * @var array
+ */
+ protected $_files;
+
+ /**
+ * Sets validator options
+ *
+ * Min limits the file count, when used with max=null it is the maximum file count
+ * It also accepts an array with the keys 'min' and 'max'
+ *
+ * If $options is a integer, it will be used as maximum file count
+ * As Array is accepts the following keys:
+ * 'min': Minimum filecount
+ * 'max': Maximum filecount
+ *
+ * @param integer|array|Zend_Config $options Options for the adapter
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_string($options) || is_numeric($options)) {
+ $options = array('max' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ if (1 < func_num_args()) {
+ $options['min'] = func_get_arg(0);
+ $options['max'] = func_get_arg(1);
+ }
+
+ if (isset($options['min'])) {
+ $this->setMin($options);
+ }
+
+ if (isset($options['max'])) {
+ $this->setMax($options);
+ }
+ }
+
+ /**
+ * Returns the minimum file count
+ *
+ * @return integer
+ */
+ public function getMin()
+ {
+ return $this->_min;
+ }
+
+ /**
+ * Sets the minimum file count
+ *
+ * @param integer|array $min The minimum file count
+ * @return Zend_Validate_File_Count Provides a fluent interface
+ * @throws Zend_Validate_Exception When min is greater than max
+ */
+ public function setMin($min)
+ {
+ if (is_array($min) and isset($min['min'])) {
+ $min = $min['min'];
+ }
+
+ if (!is_string($min) and !is_numeric($min)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ $min = (integer) $min;
+ if (($this->_max !== null) && ($min > $this->_max)) {
+ throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum file count, but $min >"
+ . " {$this->_max}");
+ }
+
+ $this->_min = $min;
+ return $this;
+ }
+
+ /**
+ * Returns the maximum file count
+ *
+ * @return integer
+ */
+ public function getMax()
+ {
+ return $this->_max;
+ }
+
+ /**
+ * Sets the maximum file count
+ *
+ * @param integer|array $max The maximum file count
+ * @return Zend_Validate_StringLength Provides a fluent interface
+ * @throws Zend_Validate_Exception When max is smaller than min
+ */
+ public function setMax($max)
+ {
+ if (is_array($max) and isset($max['max'])) {
+ $max = $max['max'];
+ }
+
+ if (!is_string($max) and !is_numeric($max)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ $max = (integer) $max;
+ if (($this->_min !== null) && ($max < $this->_min)) {
+ throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum file count, but "
+ . "$max < {$this->_min}");
+ }
+
+ $this->_max = $max;
+ return $this;
+ }
+
+ /**
+ * Adds a file for validation
+ *
+ * @param string|array $file
+ * @return $this
+ */
+ public function addFile($file)
+ {
+ if (is_string($file)) {
+ $file = array($file);
+ }
+
+ if (is_array($file)) {
+ foreach ($file as $name) {
+ if (!isset($this->_files[$name]) && !empty($name)) {
+ $this->_files[$name] = $name;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the file count of all checked files is at least min and
+ * not bigger than max (when max is not null). Attention: When checking with set min you
+ * must give all files with the first call, otherwise you will get an false.
+ *
+ * @param string|array $value Filenames to check for count
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ if (($file !== null) && !array_key_exists('destination', $file)) {
+ $file['destination'] = dirname($value);
+ }
+
+ if (($file !== null) && array_key_exists('tmp_name', $file)) {
+ $value = $file['destination'] . DIRECTORY_SEPARATOR . $file['name'];
+ }
+
+ if (($file === null) || !empty($file['tmp_name'])) {
+ $this->addFile($value);
+ }
+
+ $this->_count = count($this->_files);
+ if (($this->_max !== null) && ($this->_count > $this->_max)) {
+ return $this->_throw($file, self::TOO_MANY);
+ }
+
+ if (($this->_min !== null) && ($this->_count < $this->_min)) {
+ return $this->_throw($file, self::TOO_FEW);
+ }
+
+ return true;
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Crc32.php b/library/vendor/Zend/Validate/File/Crc32.php
new file mode 100644
index 0000000..b577c84
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Crc32.php
@@ -0,0 +1,177 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_Hash
+ */
+
+/**
+ * Validator for the crc32 hash of given files
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Crc32 extends Zend_Validate_File_Hash
+{
+ /**
+ * @const string Error constants
+ */
+ const DOES_NOT_MATCH = 'fileCrc32DoesNotMatch';
+ const NOT_DETECTED = 'fileCrc32NotDetected';
+ const NOT_FOUND = 'fileCrc32NotFound';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::DOES_NOT_MATCH => "File '%value%' does not match the given crc32 hashes",
+ self::NOT_DETECTED => "A crc32 hash could not be evaluated for the given file",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Hash of the file
+ *
+ * @var string
+ */
+ protected $_hash;
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array|Zend_Config $options
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_File_Crc32
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_scalar($options)) {
+ $options = array('hash1' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception('Invalid options to validator provided');
+ }
+
+ $this->setCrc32($options);
+ }
+
+ /**
+ * Returns all set crc32 hashes
+ *
+ * @return array
+ */
+ public function getCrc32()
+ {
+ return $this->getHash();
+ }
+
+ /**
+ * Sets the crc32 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setHash($options)
+ {
+ if (!is_array($options)) {
+ $options = array($options);
+ }
+
+ $options['algorithm'] = 'crc32';
+ parent::setHash($options);
+ return $this;
+ }
+
+ /**
+ * Sets the crc32 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setCrc32($options)
+ {
+ $this->setHash($options);
+ return $this;
+ }
+
+ /**
+ * Adds the crc32 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addHash($options)
+ {
+ if (!is_array($options)) {
+ $options = array($options);
+ }
+
+ $options['algorithm'] = 'crc32';
+ parent::addHash($options);
+ return $this;
+ }
+
+ /**
+ * Adds the crc32 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addCrc32($options)
+ {
+ $this->addHash($options);
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the given file confirms the set hash
+ *
+ * @param string $value Filename to check for hash
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ $hashes = array_unique(array_keys($this->_hash));
+ $filehash = hash_file('crc32', $value);
+ if ($filehash === false) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ foreach($hashes as $hash) {
+ if ($filehash === $hash) {
+ return true;
+ }
+ }
+
+ return $this->_throw($file, self::DOES_NOT_MATCH);
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/ExcludeExtension.php b/library/vendor/Zend/Validate/File/ExcludeExtension.php
new file mode 100644
index 0000000..605b71e
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/ExcludeExtension.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the excluding file extensions
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_ExcludeExtension extends Zend_Validate_File_Extension
+{
+ /**
+ * @const string Error constants
+ */
+ const FALSE_EXTENSION = 'fileExcludeExtensionFalse';
+ const NOT_FOUND = 'fileExcludeExtensionNotFound';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::FALSE_EXTENSION => "File '%value%' has a false extension",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the fileextension of $value is not included in the
+ * set extension list
+ *
+ * @param string $value Real file to check for extension
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ if ($file !== null) {
+ $info['extension'] = substr($file['name'], strrpos($file['name'], '.') + 1);
+ } else {
+ $info = pathinfo($value);
+ }
+
+ $extensions = $this->getExtension();
+
+ if ($this->_case and (!in_array($info['extension'], $extensions))) {
+ return true;
+ } else if (!$this->_case) {
+ $found = false;
+ foreach ($extensions as $extension) {
+ if (strtolower($extension) == strtolower($info['extension'])) {
+ $found = true;
+ }
+ }
+
+ if (!$found) {
+ return true;
+ }
+ }
+
+ return $this->_throw($file, self::FALSE_EXTENSION);
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/ExcludeMimeType.php b/library/vendor/Zend/Validate/File/ExcludeMimeType.php
new file mode 100644
index 0000000..5b031ce
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/ExcludeMimeType.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_MimeType
+ */
+
+/**
+ * Validator for the mime type of a file
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_ExcludeMimeType extends Zend_Validate_File_MimeType
+{
+ const FALSE_TYPE = 'fileExcludeMimeTypeFalse';
+ const NOT_DETECTED = 'fileExcludeMimeTypeNotDetected';
+ const NOT_READABLE = 'fileExcludeMimeTypeNotReadable';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::FALSE_TYPE => "File '%value%' has a false mimetype of '%type%'",
+ self::NOT_DETECTED => "The mimetype of file '%value%' could not be detected",
+ self::NOT_READABLE => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if the mimetype of the file does not matche the given ones. Also parts
+ * of mimetypes can be checked. If you give for example "image" all image
+ * mime types will not be accepted like "image/gif", "image/jpeg" and so on.
+ *
+ * @param string $value Real file to check for mimetype
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ if ($file === null) {
+ $file = array(
+ 'type' => null,
+ 'name' => $value
+ );
+ }
+
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_READABLE);
+ }
+
+ $this->_type = $this->_detectMimeType($value);
+
+ if (empty($this->_type) && $this->_headerCheck) {
+ $this->_type = $file['type'];
+ }
+
+ if (empty($this->_type)) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ $mimetype = $this->getMimeType(true);
+ if (in_array($this->_type, $mimetype)) {
+ return $this->_throw($file, self::FALSE_TYPE);
+ }
+
+ $types = explode('/', $this->_type);
+ $types = array_merge($types, explode('-', $this->_type));
+ foreach($mimetype as $mime) {
+ if (in_array($mime, $types)) {
+ return $this->_throw($file, self::FALSE_TYPE);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Exists.php b/library/vendor/Zend/Validate/File/Exists.php
new file mode 100644
index 0000000..d22cfcf
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Exists.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator which checks if the file already exists in the directory
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Exists extends Zend_Validate_Abstract
+{
+ /**
+ * @const string Error constants
+ */
+ const DOES_NOT_EXIST = 'fileExistsDoesNotExist';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::DOES_NOT_EXIST => "File '%value%' does not exist",
+ );
+
+ /**
+ * Internal list of directories
+ * @var string
+ */
+ protected $_directory = '';
+
+ /**
+ * @var array Error message template variables
+ */
+ protected $_messageVariables = array(
+ 'directory' => '_directory'
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array|Zend_Config $directory
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($directory = array())
+ {
+ if ($directory instanceof Zend_Config) {
+ $directory = $directory->toArray();
+ } else if (is_string($directory)) {
+ $directory = explode(',', $directory);
+ } else if (!is_array($directory)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ $this->setDirectory($directory);
+ }
+
+ /**
+ * Returns the set file directories which are checked
+ *
+ * @param boolean $asArray Returns the values as array, when false an concated string is returned
+ * @return string
+ */
+ public function getDirectory($asArray = false)
+ {
+ $asArray = (bool) $asArray;
+ $directory = (string) $this->_directory;
+ if ($asArray) {
+ $directory = explode(',', $directory);
+ }
+
+ return $directory;
+ }
+
+ /**
+ * Sets the file directory which will be checked
+ *
+ * @param string|array $directory The directories to validate
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function setDirectory($directory)
+ {
+ $this->_directory = null;
+ $this->addDirectory($directory);
+ return $this;
+ }
+
+ /**
+ * Adds the file directory which will be checked
+ *
+ * @param string|array $directory The directory to add for validation
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function addDirectory($directory)
+ {
+ $directories = $this->getDirectory(true);
+
+ if (is_string($directory)) {
+ $directory = explode(',', $directory);
+ } else if (!is_array($directory)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ foreach ($directory as $content) {
+ if (empty($content) || !is_string($content)) {
+ continue;
+ }
+
+ $directories[] = trim($content);
+ }
+ $directories = array_unique($directories);
+
+ // Sanity check to ensure no empty values
+ foreach ($directories as $key => $dir) {
+ if (empty($dir)) {
+ unset($directories[$key]);
+ }
+ }
+
+ $this->_directory = implode(',', $directories);
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the file already exists in the set directories
+ *
+ * @param string $value Real file to check for existance
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ $directories = $this->getDirectory(true);
+ if (($file !== null) and (!empty($file['destination']))) {
+ $directories[] = $file['destination'];
+ } else if (!isset($file['name'])) {
+ $file['name'] = $value;
+ }
+
+ $check = false;
+ foreach ($directories as $directory) {
+ if (empty($directory)) {
+ continue;
+ }
+
+ $check = true;
+ if (!file_exists($directory . DIRECTORY_SEPARATOR . $file['name'])) {
+ return $this->_throw($file, self::DOES_NOT_EXIST);
+ }
+ }
+
+ if (!$check) {
+ return $this->_throw($file, self::DOES_NOT_EXIST);
+ }
+
+ return true;
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Extension.php b/library/vendor/Zend/Validate/File/Extension.php
new file mode 100644
index 0000000..e2d057a
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Extension.php
@@ -0,0 +1,235 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the file extension of a file
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Extension extends Zend_Validate_Abstract
+{
+ /**
+ * @const string Error constants
+ */
+ const FALSE_EXTENSION = 'fileExtensionFalse';
+ const NOT_FOUND = 'fileExtensionNotFound';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::FALSE_EXTENSION => "File '%value%' has a false extension",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Internal list of extensions
+ * @var string
+ */
+ protected $_extension = '';
+
+ /**
+ * Validate case sensitive
+ *
+ * @var boolean
+ */
+ protected $_case = false;
+
+ /**
+ * @var array Error message template variables
+ */
+ protected $_messageVariables = array(
+ 'extension' => '_extension'
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array|Zend_Config $options
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (1 < func_num_args()) {
+ $case = func_get_arg(1);
+ $this->setCase($case);
+ }
+
+ if (is_array($options) and isset($options['case'])) {
+ $this->setCase($options['case']);
+ unset($options['case']);
+ }
+
+ $this->setExtension($options);
+ }
+
+ /**
+ * Returns the case option
+ *
+ * @return boolean
+ */
+ public function getCase()
+ {
+ return $this->_case;
+ }
+
+ /**
+ * Sets the case to use
+ *
+ * @param boolean $case
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function setCase($case)
+ {
+ $this->_case = (boolean) $case;
+ return $this;
+ }
+
+ /**
+ * Returns the set file extension
+ *
+ * @return array
+ */
+ public function getExtension()
+ {
+ $extension = explode(',', $this->_extension);
+
+ return $extension;
+ }
+
+ /**
+ * Sets the file extensions
+ *
+ * @param string|array $extension The extensions to validate
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function setExtension($extension)
+ {
+ $this->_extension = null;
+ $this->addExtension($extension);
+ return $this;
+ }
+
+ /**
+ * Adds the file extensions
+ *
+ * @param string|array $extension The extensions to add for validation
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function addExtension($extension)
+ {
+ $extensions = $this->getExtension();
+ if (is_string($extension)) {
+ $extension = explode(',', $extension);
+ }
+
+ foreach ($extension as $content) {
+ if (empty($content) || !is_string($content)) {
+ continue;
+ }
+
+ $extensions[] = trim($content);
+ }
+ $extensions = array_unique($extensions);
+
+ // Sanity check to ensure no empty values
+ foreach ($extensions as $key => $ext) {
+ if (empty($ext)) {
+ unset($extensions[$key]);
+ }
+ }
+
+ $this->_extension = implode(',', $extensions);
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the fileextension of $value is included in the
+ * set extension list
+ *
+ * @param string $value Real file to check for extension
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ if ($file !== null) {
+ $info['extension'] = substr($file['name'], strrpos($file['name'], '.') + 1);
+ } else {
+ $info = pathinfo($value);
+ if (!array_key_exists('extension', $info)) {
+ // From the manual at http://php.net/pathinfo:
+ // "If the path does not have an extension, no extension element
+ // will be returned (see second example below)."
+ return false;
+ }
+ }
+
+ $extensions = $this->getExtension();
+
+ if ($this->_case && (in_array($info['extension'], $extensions))) {
+ return true;
+ } else if (!$this->getCase()) {
+ foreach ($extensions as $extension) {
+ if (strtolower($extension) == strtolower($info['extension'])) {
+ return true;
+ }
+ }
+ }
+
+ return $this->_throw($file, self::FALSE_EXTENSION);
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if (null !== $file) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/FilesSize.php b/library/vendor/Zend/Validate/File/FilesSize.php
new file mode 100644
index 0000000..e7b4930
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/FilesSize.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_Size
+ */
+
+/**
+ * Validator for the size of all files which will be validated in sum
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_FilesSize extends Zend_Validate_File_Size
+{
+ /**
+ * @const string Error constants
+ */
+ const TOO_BIG = 'fileFilesSizeTooBig';
+ const TOO_SMALL = 'fileFilesSizeTooSmall';
+ const NOT_READABLE = 'fileFilesSizeNotReadable';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::TOO_BIG => "All files in sum should have a maximum size of '%max%' but '%size%' were detected",
+ self::TOO_SMALL => "All files in sum should have a minimum size of '%min%' but '%size%' were detected",
+ self::NOT_READABLE => "One or more files can not be read",
+ );
+
+ /**
+ * Internal file array
+ *
+ * @var array
+ */
+ protected $_files;
+
+ /**
+ * Sets validator options
+ *
+ * Min limits the used diskspace for all files, when used with max=null it is the maximum filesize
+ * It also accepts an array with the keys 'min' and 'max'
+ *
+ * @param integer|array|Zend_Config $options Options for this validator
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ $this->_files = array();
+ $this->_setSize(0);
+
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_scalar($options)) {
+ $options = array('max' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception('Invalid options to validator provided');
+ }
+
+ if (1 < func_num_args()) {
+ $argv = func_get_args();
+ array_shift($argv);
+ $options['max'] = array_shift($argv);
+ if (!empty($argv)) {
+ $options['bytestring'] = array_shift($argv);
+ }
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the disk usage of all files is at least min and
+ * not bigger than max (when max is not null).
+ *
+ * @param string|array $value Real file to check for size
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ if (is_string($value)) {
+ $value = array($value);
+ }
+
+ $min = $this->getMin(true);
+ $max = $this->getMax(true);
+ $size = $this->_getSize();
+ foreach ($value as $files) {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($files)) {
+ $this->_throw($file, self::NOT_READABLE);
+ continue;
+ }
+
+ if (!isset($this->_files[$files])) {
+ $this->_files[$files] = $files;
+ } else {
+ // file already counted... do not count twice
+ continue;
+ }
+
+ // limited to 2GB files
+ $size += @filesize($files);
+ $this->_size = $size;
+ if (($max !== null) && ($max < $size)) {
+ if ($this->useByteString()) {
+ $this->_max = $this->_toByteString($max);
+ $this->_size = $this->_toByteString($size);
+ $this->_throw($file, self::TOO_BIG);
+ $this->_max = $max;
+ $this->_size = $size;
+ } else {
+ $this->_throw($file, self::TOO_BIG);
+ }
+ }
+ }
+
+ // Check that aggregate files are >= minimum size
+ if (($min !== null) && ($size < $min)) {
+ if ($this->useByteString()) {
+ $this->_min = $this->_toByteString($min);
+ $this->_size = $this->_toByteString($size);
+ $this->_throw($file, self::TOO_SMALL);
+ $this->_min = $min;
+ $this->_size = $size;
+ } else {
+ $this->_throw($file, self::TOO_SMALL);
+ }
+ }
+
+ if (count($this->_messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Hash.php b/library/vendor/Zend/Validate/File/Hash.php
new file mode 100644
index 0000000..55a0347
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Hash.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the hash of given files
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Hash extends Zend_Validate_Abstract
+{
+ /**
+ * @const string Error constants
+ */
+ const DOES_NOT_MATCH = 'fileHashDoesNotMatch';
+ const NOT_DETECTED = 'fileHashHashNotDetected';
+ const NOT_FOUND = 'fileHashNotFound';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::DOES_NOT_MATCH => "File '%value%' does not match the given hashes",
+ self::NOT_DETECTED => "A hash could not be evaluated for the given file",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist"
+ );
+
+ /**
+ * Hash of the file
+ *
+ * @var string
+ */
+ protected $_hash;
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array $options
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_scalar($options)) {
+ $options = array('hash1' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception('Invalid options to validator provided');
+ }
+
+ if (1 < func_num_args()) {
+ $options['algorithm'] = func_get_arg(1);
+ }
+
+ $this->setHash($options);
+ }
+
+ /**
+ * Returns the set hash values as array, the hash as key and the algorithm the value
+ *
+ * @return array
+ */
+ public function getHash()
+ {
+ return $this->_hash;
+ }
+
+ /**
+ * Sets the hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setHash($options)
+ {
+ $this->_hash = null;
+ $this->addHash($options);
+
+ return $this;
+ }
+
+ /**
+ * Adds the hash for one or multiple files
+ *
+ * @param string|array $options
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addHash($options)
+ {
+ if (is_string($options)) {
+ $options = array($options);
+ } else if (!is_array($options)) {
+ throw new Zend_Validate_Exception("False parameter given");
+ }
+
+ $known = hash_algos();
+ if (!isset($options['algorithm'])) {
+ $algorithm = 'crc32';
+ } else {
+ $algorithm = $options['algorithm'];
+ unset($options['algorithm']);
+ }
+
+ if (!in_array($algorithm, $known)) {
+ throw new Zend_Validate_Exception("Unknown algorithm '{$algorithm}'");
+ }
+
+ foreach ($options as $value) {
+ $this->_hash[$value] = $algorithm;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the given file confirms the set hash
+ *
+ * @param string $value Filename to check for hash
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ $algos = array_unique(array_values($this->_hash));
+ $hashes = array_unique(array_keys($this->_hash));
+ foreach ($algos as $algorithm) {
+ $filehash = hash_file($algorithm, $value);
+ if ($filehash === false) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ foreach($hashes as $hash) {
+ if ($filehash === $hash) {
+ return true;
+ }
+ }
+ }
+
+ return $this->_throw($file, self::DOES_NOT_MATCH);
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/ImageSize.php b/library/vendor/Zend/Validate/File/ImageSize.php
new file mode 100644
index 0000000..686a9cb
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/ImageSize.php
@@ -0,0 +1,357 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the image size of a image file
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_ImageSize extends Zend_Validate_Abstract
+{
+ /**
+ * @const string Error constants
+ */
+ const WIDTH_TOO_BIG = 'fileImageSizeWidthTooBig';
+ const WIDTH_TOO_SMALL = 'fileImageSizeWidthTooSmall';
+ const HEIGHT_TOO_BIG = 'fileImageSizeHeightTooBig';
+ const HEIGHT_TOO_SMALL = 'fileImageSizeHeightTooSmall';
+ const NOT_DETECTED = 'fileImageSizeNotDetected';
+ const NOT_READABLE = 'fileImageSizeNotReadable';
+
+ /**
+ * @var array Error message template
+ */
+ protected $_messageTemplates = array(
+ self::WIDTH_TOO_BIG => "Maximum allowed width for image '%value%' should be '%maxwidth%' but '%width%' detected",
+ self::WIDTH_TOO_SMALL => "Minimum expected width for image '%value%' should be '%minwidth%' but '%width%' detected",
+ self::HEIGHT_TOO_BIG => "Maximum allowed height for image '%value%' should be '%maxheight%' but '%height%' detected",
+ self::HEIGHT_TOO_SMALL => "Minimum expected height for image '%value%' should be '%minheight%' but '%height%' detected",
+ self::NOT_DETECTED => "The size of image '%value%' could not be detected",
+ self::NOT_READABLE => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * @var array Error message template variables
+ */
+ protected $_messageVariables = array(
+ 'minwidth' => '_minwidth',
+ 'maxwidth' => '_maxwidth',
+ 'minheight' => '_minheight',
+ 'maxheight' => '_maxheight',
+ 'width' => '_width',
+ 'height' => '_height'
+ );
+
+ /**
+ * Minimum image width
+ *
+ * @var integer
+ */
+ protected $_minwidth;
+
+ /**
+ * Maximum image width
+ *
+ * @var integer
+ */
+ protected $_maxwidth;
+
+ /**
+ * Minimum image height
+ *
+ * @var integer
+ */
+ protected $_minheight;
+
+ /**
+ * Maximum image height
+ *
+ * @var integer
+ */
+ protected $_maxheight;
+
+ /**
+ * Detected width
+ *
+ * @var integer
+ */
+ protected $_width;
+
+ /**
+ * Detected height
+ *
+ * @var integer
+ */
+ protected $_height;
+
+ /**
+ * Sets validator options
+ *
+ * Accepts the following option keys:
+ * - minheight
+ * - minwidth
+ * - maxheight
+ * - maxwidth
+ *
+ * @param Zend_Config|array $options
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (1 < func_num_args()) {
+ if (!is_array($options)) {
+ $options = array('minwidth' => $options);
+ }
+ $argv = func_get_args();
+ array_shift($argv);
+ $options['minheight'] = array_shift($argv);
+ if (!empty($argv)) {
+ $options['maxwidth'] = array_shift($argv);
+ if (!empty($argv)) {
+ $options['maxheight'] = array_shift($argv);
+ }
+ }
+ } else if (!is_array($options)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ if (isset($options['minheight']) || isset($options['minwidth'])) {
+ $this->setImageMin($options);
+ }
+
+ if (isset($options['maxheight']) || isset($options['maxwidth'])) {
+ $this->setImageMax($options);
+ }
+ }
+
+ /**
+ * Returns the set minimum image sizes
+ *
+ * @return array
+ */
+ public function getImageMin()
+ {
+ return array('minwidth' => $this->_minwidth, 'minheight' => $this->_minheight);
+ }
+
+ /**
+ * Returns the set maximum image sizes
+ *
+ * @return array
+ */
+ public function getImageMax()
+ {
+ return array('maxwidth' => $this->_maxwidth, 'maxheight' => $this->_maxheight);
+ }
+
+ /**
+ * Returns the set image width sizes
+ *
+ * @return array
+ */
+ public function getImageWidth()
+ {
+ return array('minwidth' => $this->_minwidth, 'maxwidth' => $this->_maxwidth);
+ }
+
+ /**
+ * Returns the set image height sizes
+ *
+ * @return array
+ */
+ public function getImageHeight()
+ {
+ return array('minheight' => $this->_minheight, 'maxheight' => $this->_maxheight);
+ }
+
+ /**
+ * Sets the minimum image size
+ *
+ * @param array $options The minimum image dimensions
+ * @throws Zend_Validate_Exception When minwidth is greater than maxwidth
+ * @throws Zend_Validate_Exception When minheight is greater than maxheight
+ * @return Zend_Validate_File_ImageSize Provides a fluent interface
+ */
+ public function setImageMin($options)
+ {
+ if (isset($options['minwidth'])) {
+ if (($this->_maxwidth !== null) and ($options['minwidth'] > $this->_maxwidth)) {
+ throw new Zend_Validate_Exception("The minimum image width must be less than or equal to the "
+ . " maximum image width, but {$options['minwidth']} > {$this->_maxwidth}");
+ }
+ }
+
+ if (isset($options['maxheight'])) {
+ if (($this->_maxheight !== null) and ($options['minheight'] > $this->_maxheight)) {
+ throw new Zend_Validate_Exception("The minimum image height must be less than or equal to the "
+ . " maximum image height, but {$options['minheight']} > {$this->_maxheight}");
+ }
+ }
+
+ if (isset($options['minwidth'])) {
+ $this->_minwidth = (int) $options['minwidth'];
+ }
+
+ if (isset($options['minheight'])) {
+ $this->_minheight = (int) $options['minheight'];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the maximum image size
+ *
+ * @param array $options The maximum image dimensions
+ * @throws Zend_Validate_Exception When maxwidth is smaller than minwidth
+ * @throws Zend_Validate_Exception When maxheight is smaller than minheight
+ * @return Zend_Validate_StringLength Provides a fluent interface
+ */
+ public function setImageMax($options)
+ {
+ if (isset($options['maxwidth'])) {
+ if (($this->_minwidth !== null) and ($options['maxwidth'] < $this->_minwidth)) {
+ throw new Zend_Validate_Exception("The maximum image width must be greater than or equal to the "
+ . "minimum image width, but {$options['maxwidth']} < {$this->_minwidth}");
+ }
+ }
+
+ if (isset($options['maxheight'])) {
+ if (($this->_minheight !== null) and ($options['maxheight'] < $this->_minheight)) {
+ throw new Zend_Validate_Exception("The maximum image height must be greater than or equal to the "
+ . "minimum image height, but {$options['maxheight']} < {$this->_minwidth}");
+ }
+ }
+
+ if (isset($options['maxwidth'])) {
+ $this->_maxwidth = (int) $options['maxwidth'];
+ }
+
+ if (isset($options['maxheight'])) {
+ $this->_maxheight = (int) $options['maxheight'];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the mimimum and maximum image width
+ *
+ * @param array $options The image width dimensions
+ * @return Zend_Validate_File_ImageSize Provides a fluent interface
+ */
+ public function setImageWidth($options)
+ {
+ $this->setImageMin($options);
+ $this->setImageMax($options);
+
+ return $this;
+ }
+
+ /**
+ * Sets the mimimum and maximum image height
+ *
+ * @param array $options The image height dimensions
+ * @return Zend_Validate_File_ImageSize Provides a fluent interface
+ */
+ public function setImageHeight($options)
+ {
+ $this->setImageMin($options);
+ $this->setImageMax($options);
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the imagesize of $value is at least min and
+ * not bigger than max
+ *
+ * @param string $value Real file to check for image size
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_READABLE);
+ }
+
+ $size = @getimagesize($value);
+ $this->_setValue($file);
+
+ if (empty($size) or ($size[0] === 0) or ($size[1] === 0)) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ $this->_width = $size[0];
+ $this->_height = $size[1];
+ if ($this->_width < $this->_minwidth) {
+ $this->_throw($file, self::WIDTH_TOO_SMALL);
+ }
+
+ if (($this->_maxwidth !== null) and ($this->_maxwidth < $this->_width)) {
+ $this->_throw($file, self::WIDTH_TOO_BIG);
+ }
+
+ if ($this->_height < $this->_minheight) {
+ $this->_throw($file, self::HEIGHT_TOO_SMALL);
+ }
+
+ if (($this->_maxheight !== null) and ($this->_maxheight < $this->_height)) {
+ $this->_throw($file, self::HEIGHT_TOO_BIG);
+ }
+
+ if (count($this->_messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/IsCompressed.php b/library/vendor/Zend/Validate/File/IsCompressed.php
new file mode 100644
index 0000000..9902353
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/IsCompressed.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_MimeType
+ */
+
+/**
+ * Validator which checks if the file already exists in the directory
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_IsCompressed extends Zend_Validate_File_MimeType
+{
+ /**
+ * @const string Error constants
+ */
+ const FALSE_TYPE = 'fileIsCompressedFalseType';
+ const NOT_DETECTED = 'fileIsCompressedNotDetected';
+ const NOT_READABLE = 'fileIsCompressedNotReadable';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::FALSE_TYPE => "File '%value%' is not compressed, '%type%' detected",
+ self::NOT_DETECTED => "The mimetype of file '%value%' could not be detected",
+ self::NOT_READABLE => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array|Zend_Config $mimetype
+ */
+ public function __construct($mimetype = array())
+ {
+ if ($mimetype instanceof Zend_Config) {
+ $mimetype = $mimetype->toArray();
+ }
+
+ $temp = array();
+ // http://de.wikipedia.org/wiki/Liste_von_Dateiendungen
+ $default = array(
+ 'application/arj',
+ 'application/gnutar',
+ 'application/lha',
+ 'application/lzx',
+ 'application/vnd.ms-cab-compressed',
+ 'application/x-ace-compressed',
+ 'application/x-arc',
+ 'application/x-archive',
+ 'application/x-arj',
+ 'application/x-bzip',
+ 'application/x-bzip2',
+ 'application/x-cab-compressed',
+ 'application/x-compress',
+ 'application/x-compressed',
+ 'application/x-cpio',
+ 'application/x-debian-package',
+ 'application/x-eet',
+ 'application/x-gzip',
+ 'application/x-java-pack200',
+ 'application/x-lha',
+ 'application/x-lharc',
+ 'application/x-lzh',
+ 'application/x-lzma',
+ 'application/x-lzx',
+ 'application/x-rar',
+ 'application/x-sit',
+ 'application/x-stuffit',
+ 'application/x-tar',
+ 'application/zip',
+ 'application/x-zip',
+ 'application/zoo',
+ 'multipart/x-gzip',
+ );
+
+ if (is_array($mimetype)) {
+ $temp = $mimetype;
+ if (array_key_exists('magicfile', $temp)) {
+ unset($temp['magicfile']);
+ }
+
+ if (array_key_exists('headerCheck', $temp)) {
+ unset($temp['headerCheck']);
+ }
+
+ if (empty($temp)) {
+ $mimetype += $default;
+ }
+ }
+
+ if (empty($mimetype)) {
+ $mimetype = $default;
+ }
+
+ parent::__construct($mimetype);
+ }
+
+ /**
+ * Throws an error of the given type
+ * Duplicates parent method due to OOP Problem with late static binding in PHP 5.2
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ $this->_value = $file['name'];
+ switch($errorType) {
+ case Zend_Validate_File_MimeType::FALSE_TYPE :
+ $errorType = self::FALSE_TYPE;
+ break;
+ case Zend_Validate_File_MimeType::NOT_DETECTED :
+ $errorType = self::NOT_DETECTED;
+ break;
+ case Zend_Validate_File_MimeType::NOT_READABLE :
+ $errorType = self::NOT_READABLE;
+ break;
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/IsImage.php b/library/vendor/Zend/Validate/File/IsImage.php
new file mode 100644
index 0000000..a87f1a6
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/IsImage.php
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_MimeType
+ */
+
+/**
+ * Validator which checks if the file already exists in the directory
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_IsImage extends Zend_Validate_File_MimeType
+{
+ /**
+ * @const string Error constants
+ */
+ const FALSE_TYPE = 'fileIsImageFalseType';
+ const NOT_DETECTED = 'fileIsImageNotDetected';
+ const NOT_READABLE = 'fileIsImageNotReadable';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::FALSE_TYPE => "File '%value%' is no image, '%type%' detected",
+ self::NOT_DETECTED => "The mimetype of file '%value%' could not be detected",
+ self::NOT_READABLE => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @param string|array|Zend_Config $mimetype
+ */
+ public function __construct($mimetype = array())
+ {
+ if ($mimetype instanceof Zend_Config) {
+ $mimetype = $mimetype->toArray();
+ }
+
+ $temp = array();
+ // http://de.wikipedia.org/wiki/Liste_von_Dateiendungen
+ // http://www.iana.org/assignments/media-types/image/
+ $default = array(
+ 'application/cdf',
+ 'application/dicom',
+ 'application/fractals',
+ 'application/postscript',
+ 'application/vnd.hp-hpgl',
+ 'application/vnd.oasis.opendocument.graphics',
+ 'application/x-cdf',
+ 'application/x-cmu-raster',
+ 'application/x-ima',
+ 'application/x-inventor',
+ 'application/x-koan',
+ 'application/x-portable-anymap',
+ 'application/x-world-x-3dmf',
+ 'image/bmp',
+ 'image/c',
+ 'image/cgm',
+ 'image/fif',
+ 'image/gif',
+ 'image/jpeg',
+ 'image/jpm',
+ 'image/jpx',
+ 'image/jp2',
+ 'image/naplps',
+ 'image/pjpeg',
+ 'image/png',
+ 'image/svg',
+ 'image/svg+xml',
+ 'image/tiff',
+ 'image/vnd.adobe.photoshop',
+ 'image/vnd.djvu',
+ 'image/vnd.fpx',
+ 'image/vnd.net-fpx',
+ 'image/x-cmu-raster',
+ 'image/x-cmx',
+ 'image/x-coreldraw',
+ 'image/x-cpi',
+ 'image/x-emf',
+ 'image/x-ico',
+ 'image/x-icon',
+ 'image/x-jg',
+ 'image/x-ms-bmp',
+ 'image/x-niff',
+ 'image/x-pict',
+ 'image/x-pcx',
+ 'image/x-portable-anymap',
+ 'image/x-portable-bitmap',
+ 'image/x-portable-greymap',
+ 'image/x-portable-pixmap',
+ 'image/x-quicktime',
+ 'image/x-rgb',
+ 'image/x-tiff',
+ 'image/x-unknown',
+ 'image/x-windows-bmp',
+ 'image/x-xpmi',
+ );
+
+ if (is_array($mimetype)) {
+ $temp = $mimetype;
+ if (array_key_exists('magicfile', $temp)) {
+ unset($temp['magicfile']);
+ }
+
+ if (array_key_exists('headerCheck', $temp)) {
+ unset($temp['headerCheck']);
+ }
+
+ if (empty($temp)) {
+ $mimetype += $default;
+ }
+ }
+
+ if (empty($mimetype)) {
+ $mimetype = $default;
+ }
+
+ parent::__construct($mimetype);
+ }
+
+ /**
+ * Throws an error of the given type
+ * Duplicates parent method due to OOP Problem with late static binding in PHP 5.2
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ $this->_value = $file['name'];
+ switch($errorType) {
+ case Zend_Validate_File_MimeType::FALSE_TYPE :
+ $errorType = self::FALSE_TYPE;
+ break;
+ case Zend_Validate_File_MimeType::NOT_DETECTED :
+ $errorType = self::NOT_DETECTED;
+ break;
+ case Zend_Validate_File_MimeType::NOT_READABLE :
+ $errorType = self::NOT_READABLE;
+ break;
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Md5.php b/library/vendor/Zend/Validate/File/Md5.php
new file mode 100644
index 0000000..1205a26
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Md5.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_Hash
+ */
+
+/**
+ * Validator for the md5 hash of given files
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Md5 extends Zend_Validate_File_Hash
+{
+ /**
+ * @const string Error constants
+ */
+ const DOES_NOT_MATCH = 'fileMd5DoesNotMatch';
+ const NOT_DETECTED = 'fileMd5NotDetected';
+ const NOT_FOUND = 'fileMd5NotFound';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::DOES_NOT_MATCH => "File '%value%' does not match the given md5 hashes",
+ self::NOT_DETECTED => "A md5 hash could not be evaluated for the given file",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Hash of the file
+ *
+ * @var string
+ */
+ protected $_hash;
+
+ /**
+ * Sets validator options
+ *
+ * $hash is the hash we accept for the file $file
+ *
+ * @param string|array $options
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_File_Md5
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_scalar($options)) {
+ $options = array('hash1' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception('Invalid options to validator provided');
+ }
+
+ $this->setMd5($options);
+ }
+
+ /**
+ * Returns all set md5 hashes
+ *
+ * @return array
+ */
+ public function getMd5()
+ {
+ return $this->getHash();
+ }
+
+ /**
+ * Sets the md5 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setHash($options)
+ {
+ if (!is_array($options)) {
+ $options = (array) $options;
+ }
+
+ $options['algorithm'] = 'md5';
+ parent::setHash($options);
+ return $this;
+ }
+
+ /**
+ * Sets the md5 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setMd5($options)
+ {
+ $this->setHash($options);
+ return $this;
+ }
+
+ /**
+ * Adds the md5 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addHash($options)
+ {
+ if (!is_array($options)) {
+ $options = (array) $options;
+ }
+
+ $options['algorithm'] = 'md5';
+ parent::addHash($options);
+ return $this;
+ }
+
+ /**
+ * Adds the md5 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addMd5($options)
+ {
+ $this->addHash($options);
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the given file confirms the set hash
+ *
+ * @param string $value Filename to check for hash
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ $hashes = array_unique(array_keys($this->_hash));
+ $filehash = hash_file('md5', $value);
+ if ($filehash === false) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ foreach($hashes as $hash) {
+ if ($filehash === $hash) {
+ return true;
+ }
+ }
+
+ return $this->_throw($file, self::DOES_NOT_MATCH);
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/MimeType.php b/library/vendor/Zend/Validate/File/MimeType.php
new file mode 100644
index 0000000..407ec72
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/MimeType.php
@@ -0,0 +1,468 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the mime type of a file
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_MimeType extends Zend_Validate_Abstract
+{
+ /**
+ * @const Error type constants
+ */
+ const FALSE_TYPE = 'fileMimeTypeFalse';
+ const NOT_DETECTED = 'fileMimeTypeNotDetected';
+ const NOT_READABLE = 'fileMimeTypeNotReadable';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::FALSE_TYPE => "File '%value%' has a false mimetype of '%type%'",
+ self::NOT_DETECTED => "The mimetype of file '%value%' could not be detected",
+ self::NOT_READABLE => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'type' => '_type'
+ );
+
+ /**
+ * @var string
+ */
+ protected $_type;
+
+ /**
+ * Mimetypes
+ *
+ * If null, there is no mimetype
+ *
+ * @var string|null
+ */
+ protected $_mimetype;
+
+ /**
+ * Magicfile to use
+ *
+ * @var string|null
+ */
+ protected $_magicfile;
+
+ /**
+ * Finfo object to use
+ *
+ * @var resource
+ */
+ protected $_finfo;
+
+ /**
+ * If no $_ENV['MAGIC'] is set, try and autodiscover it based on common locations
+ * @var array
+ */
+ protected $_magicFiles = array(
+ '/usr/share/misc/magic',
+ '/usr/share/misc/magic.mime',
+ '/usr/share/misc/magic.mgc',
+ '/usr/share/mime/magic',
+ '/usr/share/mime/magic.mime',
+ '/usr/share/mime/magic.mgc',
+ '/usr/share/file/magic',
+ '/usr/share/file/magic.mime',
+ '/usr/share/file/magic.mgc',
+ );
+
+ /**
+ * Indicates whether use of $_magicFiles should be attempted.
+ * @var boolean
+ */
+ protected $_tryCommonMagicFiles = true;
+
+ /**
+ * Option to allow header check
+ *
+ * @var boolean
+ */
+ protected $_headerCheck = false;
+
+ /**
+ * Holds error information returned by finfo_open
+ *
+ * @var array
+ */
+ protected $_finfoError;
+
+ /**
+ * Sets validator options
+ *
+ * Mimetype to accept
+ *
+ * @param string|array $mimetype MimeType
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($mimetype)
+ {
+ if ($mimetype instanceof Zend_Config) {
+ $mimetype = $mimetype->toArray();
+ } elseif (is_string($mimetype)) {
+ $mimetype = explode(',', $mimetype);
+ } elseif (!is_array($mimetype)) {
+ throw new Zend_Validate_Exception("Invalid options to validator provided");
+ }
+
+ if (isset($mimetype['magicfile'])) {
+ $this->setMagicFile($mimetype['magicfile']);
+ unset($mimetype['magicfile']);
+ }
+
+ if (isset($mimetype['headerCheck'])) {
+ $this->enableHeaderCheck($mimetype['headerCheck']);
+ unset($mimetype['headerCheck']);
+ }
+
+ $this->setMimeType($mimetype);
+ }
+
+ /**
+ * Returns the actual set magicfile
+ *
+ * Note that for PHP 5.3.0 or higher, we don't use $_ENV['MAGIC'] or try to
+ * find a magic file in a common location as PHP now has a built-in internal
+ * magic file.
+ *
+ * @return string
+ */
+ public function getMagicFile()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')
+ && null === $this->_magicfile) {
+ if (!empty($_ENV['MAGIC'])) {
+ $this->setMagicFile($_ENV['MAGIC']);
+ } elseif (
+ !(@ini_get("safe_mode") == 'On' || @ini_get("safe_mode") === 1)
+ && $this->shouldTryCommonMagicFiles() // @see ZF-11784
+ ) {
+ foreach ($this->_magicFiles as $file) {
+ // supressing errors which are thrown due to openbase_dir restrictions
+ try {
+ $this->setMagicFile($file);
+ if ($this->_magicfile !== null) {
+ break;
+ }
+ } catch (Zend_Validate_Exception $e) {
+ // Intentionally, catch and fall through
+ }
+ }
+ }
+
+ if ($this->_magicfile === null) {
+ $this->_magicfile = false;
+ }
+ }
+
+ return $this->_magicfile;
+ }
+
+ /**
+ * Sets the magicfile to use
+ * if null, the MAGIC constant from php is used
+ * if the MAGIC file is errorous, no file will be set
+ *
+ * @param string $file
+ * @throws Zend_Validate_Exception When finfo can not read the magicfile
+ * @return Zend_Validate_File_MimeType Provides a fluent interface
+ */
+ public function setMagicFile($file)
+ {
+ if (empty($file)) {
+ $this->_magicfile = null;
+ } else if (!(class_exists('finfo', false))) {
+ $this->_magicfile = null;
+ throw new Zend_Validate_Exception('Magicfile can not be set. There is no finfo extension installed');
+ } else if (!is_file($file) || !is_readable($file)) {
+ throw new Zend_Validate_Exception('The given magicfile can not be read');
+ } else {
+ $const = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
+ set_error_handler(array($this, '_errorHandler'), E_NOTICE | E_WARNING);
+ $this->_finfo = finfo_open($const, $file);
+ restore_error_handler();
+ if (empty($this->_finfo)) {
+ $this->_finfo = null;
+ throw new Zend_Validate_Exception(
+ sprintf('The given magicfile ("%s") is not accepted by finfo', $file),
+ null,
+ $this->_finfoError
+ );
+ } else {
+ $this->_magicfile = $file;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables attempts to try the common magic file locations
+ * specified by Zend_Validate_File_MimeType::_magicFiles
+ *
+ * @param boolean $flag
+ * @return Zend_Validate_File_MimeType Provides fluent interface
+ * @see http://framework.zend.com/issues/browse/ZF-11784
+ */
+ public function setTryCommonMagicFilesFlag($flag = true)
+ {
+ $this->_tryCommonMagicFiles = (boolean) $flag;
+
+ return $this;
+ }
+
+ /**
+ * Accessor for Zend_Validate_File_MimeType::_magicFiles
+ *
+ * @return boolean
+ * @see http://framework.zend.com/issues/browse/ZF-11784
+ */
+ public function shouldTryCommonMagicFiles()
+ {
+ return $this->_tryCommonMagicFiles;
+ }
+
+ /**
+ * Returns the Header Check option
+ *
+ * @return boolean
+ */
+ public function getHeaderCheck()
+ {
+ return $this->_headerCheck;
+ }
+
+ /**
+ * Defines if the http header should be used
+ * Note that this is unsave and therefor the default value is false
+ *
+ * @param boolean $headerCheck
+ * @return Zend_Validate_File_MimeType Provides a fluent interface
+ */
+ public function enableHeaderCheck($headerCheck = true)
+ {
+ $this->_headerCheck = (boolean) $headerCheck;
+ return $this;
+ }
+
+ /**
+ * Returns the set mimetypes
+ *
+ * @param boolean $asArray Returns the values as array, when false an concated string is returned
+ * @return string|array
+ */
+ public function getMimeType($asArray = false)
+ {
+ $asArray = (bool) $asArray;
+ $mimetype = (string) $this->_mimetype;
+ if ($asArray) {
+ $mimetype = explode(',', $mimetype);
+ }
+
+ return $mimetype;
+ }
+
+ /**
+ * Sets the mimetypes
+ *
+ * @param string|array $mimetype The mimetypes to validate
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function setMimeType($mimetype)
+ {
+ $this->_mimetype = null;
+ $this->addMimeType($mimetype);
+ return $this;
+ }
+
+ /**
+ * Adds the mimetypes
+ *
+ * @param string|array $mimetype The mimetypes to add for validation
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_File_Extension Provides a fluent interface
+ */
+ public function addMimeType($mimetype)
+ {
+ $mimetypes = $this->getMimeType(true);
+
+ if (is_string($mimetype)) {
+ $mimetype = explode(',', $mimetype);
+ } elseif (!is_array($mimetype)) {
+ throw new Zend_Validate_Exception("Invalid options to validator provided");
+ }
+
+ if (isset($mimetype['magicfile'])) {
+ unset($mimetype['magicfile']);
+ }
+
+ foreach ($mimetype as $content) {
+ if (empty($content) || !is_string($content)) {
+ continue;
+ }
+ $mimetypes[] = trim($content);
+ }
+ $mimetypes = array_unique($mimetypes);
+
+ // Sanity check to ensure no empty values
+ foreach ($mimetypes as $key => $mt) {
+ if (empty($mt)) {
+ unset($mimetypes[$key]);
+ }
+ }
+
+ $this->_mimetype = implode(',', $mimetypes);
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if the mimetype of the file matches the given ones. Also parts
+ * of mimetypes can be checked. If you give for example "image" all image
+ * mime types will be accepted like "image/gif", "image/jpeg" and so on.
+ *
+ * @param string $value Real file to check for mimetype
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ if ($file === null) {
+ $file = array(
+ 'type' => null,
+ 'name' => $value
+ );
+ }
+
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_READABLE);
+ }
+
+ $this->_type = $this->_detectMimeType($value);
+
+ if (empty($this->_type) && $this->_headerCheck) {
+ $this->_type = $file['type'];
+ }
+
+ if (empty($this->_type)) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ $mimetype = $this->getMimeType(true);
+ if (in_array($this->_type, $mimetype)) {
+ return true;
+ }
+
+ $types = explode('/', $this->_type);
+ $types = array_merge($types, explode('-', $this->_type));
+ $types = array_merge($types, explode(';', $this->_type));
+ foreach($mimetype as $mime) {
+ if (in_array($mime, $types)) {
+ return true;
+ }
+ }
+
+ return $this->_throw($file, self::FALSE_TYPE);
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ $this->_value = $file['name'];
+ $this->_error($errorType);
+ return false;
+ }
+
+ /**
+ * Try to detect mime type of given file.
+ * @param string $file File which mime type should be detected
+ * @return string File mime type or null if not detected
+ */
+ protected function _detectMimeType($file)
+ {
+ $mimefile = $this->getMagicFile();
+ $type = null;
+
+ if (class_exists('finfo', false)) {
+ $const = defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME;
+
+ if (!empty($mimefile) && empty($this->_finfo)) {
+ set_error_handler(array($this, '_errorHandler'), E_NOTICE | E_WARNING);
+ $this->_finfo = finfo_open($const, $mimefile);
+ restore_error_handler();
+ }
+
+ if (empty($this->_finfo)) {
+ set_error_handler(array($this, '_errorHandler'), E_NOTICE | E_WARNING);
+ $this->_finfo = finfo_open($const);
+ restore_error_handler();
+ }
+
+ if (!empty($this->_finfo)) {
+ $type = finfo_file($this->_finfo, $file);
+ }
+ }
+
+ if (empty($type) &&
+ (function_exists('mime_content_type') && ini_get('mime_magic.magicfile'))) {
+ $type = mime_content_type($file);
+ }
+
+ return $type;
+ }
+
+ /**
+ * Saves the provided error information by finfo_open to this instance
+ *
+ * @param integer $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param integer $errline
+ */
+ protected function _errorHandler($errno, $errstr, $errfile, $errline)
+ {
+ $this->_finfoError = new ErrorException($errstr, $errno, 0, $errfile, $errline);
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/NotExists.php b/library/vendor/Zend/Validate/File/NotExists.php
new file mode 100644
index 0000000..0599aa8
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/NotExists.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_Exists
+ */
+
+/**
+ * Validator which checks if the destination file does not exist
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_NotExists extends Zend_Validate_File_Exists
+{
+ /**
+ * @const string Error constants
+ */
+ const DOES_EXIST = 'fileNotExistsDoesExist';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::DOES_EXIST => "File '%value%' exists",
+ );
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the file does not exist in the set destinations
+ *
+ * @param string $value Real file to check for
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ $directories = $this->getDirectory(true);
+ if (($file !== null) and (!empty($file['destination']))) {
+ $directories[] = $file['destination'];
+ } else if (!isset($file['name'])) {
+ $file['name'] = $value;
+ }
+
+ foreach ($directories as $directory) {
+ if (empty($directory)) {
+ continue;
+ }
+
+ $check = true;
+ if (file_exists($directory . DIRECTORY_SEPARATOR . $file['name'])) {
+ return $this->_throw($file, self::DOES_EXIST);
+ }
+ }
+
+ if (!isset($check)) {
+ return $this->_throw($file, self::DOES_EXIST);
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Sha1.php b/library/vendor/Zend/Validate/File/Sha1.php
new file mode 100644
index 0000000..0451807
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Sha1.php
@@ -0,0 +1,179 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_Hash
+ */
+
+/**
+ * Validator for the sha1 hash of given files
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Sha1 extends Zend_Validate_File_Hash
+{
+ /**
+ * @const string Error constants
+ */
+ const DOES_NOT_MATCH = 'fileSha1DoesNotMatch';
+ const NOT_DETECTED = 'fileSha1NotDetected';
+ const NOT_FOUND = 'fileSha1NotFound';
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::DOES_NOT_MATCH => "File '%value%' does not match the given sha1 hashes",
+ self::NOT_DETECTED => "A sha1 hash could not be evaluated for the given file",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Hash of the file
+ *
+ * @var string
+ */
+ protected $_hash;
+
+ /**
+ * Sets validator options
+ *
+ * $hash is the hash we accept for the file $file
+ *
+ * @param string|array $options
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_File_Sha1
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_scalar($options)) {
+ $options = array('hash1' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception('Invalid options to validator provided');
+ }
+
+ $this->setHash($options);
+ }
+
+ /**
+ * Returns all set sha1 hashes
+ *
+ * @return array
+ */
+ public function getSha1()
+ {
+ return $this->getHash();
+ }
+
+ /**
+ * Sets the sha1 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setHash($options)
+ {
+ if (!is_array($options)) {
+ $options = (array) $options;
+ }
+
+ $options['algorithm'] = 'sha1';
+ parent::setHash($options);
+ return $this;
+ }
+
+ /**
+ * Sets the sha1 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function setSha1($options)
+ {
+ $this->setHash($options);
+ return $this;
+ }
+
+ /**
+ * Adds the sha1 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addHash($options)
+ {
+ if (!is_array($options)) {
+ $options = (array) $options;
+ }
+
+ $options['algorithm'] = 'sha1';
+ parent::addHash($options);
+ return $this;
+ }
+
+ /**
+ * Adds the sha1 hash for one or multiple files
+ *
+ * @param string|array $options
+ * @return Zend_Validate_File_Hash Provides a fluent interface
+ */
+ public function addSha1($options)
+ {
+ $this->addHash($options);
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the given file confirms the set hash
+ *
+ * @param string $value Filename to check for hash
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ $hashes = array_unique(array_keys($this->_hash));
+ $filehash = hash_file('sha1', $value);
+ if ($filehash === false) {
+ return $this->_throw($file, self::NOT_DETECTED);
+ }
+
+ foreach ($hashes as $hash) {
+ if ($filehash === $hash) {
+ return true;
+ }
+ }
+
+ return $this->_throw($file, self::DOES_NOT_MATCH);
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Size.php b/library/vendor/Zend/Validate/File/Size.php
new file mode 100644
index 0000000..69034a9
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Size.php
@@ -0,0 +1,398 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the maximum size of a file up to a max of 2GB
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Size extends Zend_Validate_Abstract
+{
+ /**#@+
+ * @const string Error constants
+ */
+ const TOO_BIG = 'fileSizeTooBig';
+ const TOO_SMALL = 'fileSizeTooSmall';
+ const NOT_FOUND = 'fileSizeNotFound';
+ /**#@-*/
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::TOO_BIG => "Maximum allowed size for file '%value%' is '%max%' but '%size%' detected",
+ self::TOO_SMALL => "Minimum expected size for file '%value%' is '%min%' but '%size%' detected",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * @var array Error message template variables
+ */
+ protected $_messageVariables = array(
+ 'min' => '_min',
+ 'max' => '_max',
+ 'size' => '_size',
+ );
+
+ /**
+ * Minimum filesize
+ * @var integer
+ */
+ protected $_min;
+
+ /**
+ * Maximum filesize
+ *
+ * If null, there is no maximum filesize
+ *
+ * @var integer|null
+ */
+ protected $_max;
+
+ /**
+ * Detected size
+ *
+ * @var integer
+ */
+ protected $_size;
+
+ /**
+ * Use bytestring ?
+ *
+ * @var boolean
+ */
+ protected $_useByteString = true;
+
+ /**
+ * Sets validator options
+ *
+ * If $options is a integer, it will be used as maximum filesize
+ * As Array is accepts the following keys:
+ * 'min': Minimum filesize
+ * 'max': Maximum filesize
+ * 'bytestring': Use bytestring or real size for messages
+ *
+ * @param integer|array $options Options for the adapter
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } elseif (is_string($options) || is_numeric($options)) {
+ $options = array('max' => $options);
+ } elseif (!is_array($options)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ if (1 < func_num_args()) {
+ $argv = func_get_args();
+ array_shift($argv);
+ $options['max'] = array_shift($argv);
+ if (!empty($argv)) {
+ $options['bytestring'] = array_shift($argv);
+ }
+ }
+
+ if (isset($options['bytestring'])) {
+ $this->setUseByteString($options['bytestring']);
+ }
+
+ if (isset($options['min'])) {
+ $this->setMin($options['min']);
+ }
+
+ if (isset($options['max'])) {
+ $this->setMax($options['max']);
+ }
+ }
+
+ /**
+ * Returns the minimum filesize
+ *
+ * @param boolean $byteString Use bytestring ?
+ * @return integer
+ */
+ public function setUseByteString($byteString = true)
+ {
+ $this->_useByteString = (bool) $byteString;
+ return $this;
+ }
+
+ /**
+ * Will bytestring be used?
+ *
+ * @return boolean
+ */
+ public function useByteString()
+ {
+ return $this->_useByteString;
+ }
+
+ /**
+ * Returns the minimum filesize
+ *
+ * @param bool $raw Whether or not to force return of the raw value (defaults off)
+ * @return integer|string
+ */
+ public function getMin($raw = false)
+ {
+ $min = $this->_min;
+ if (!$raw && $this->useByteString()) {
+ $min = $this->_toByteString($min);
+ }
+
+ return $min;
+ }
+
+ /**
+ * Sets the minimum filesize
+ *
+ * @param integer $min The minimum filesize
+ * @throws Zend_Validate_Exception When min is greater than max
+ * @return Zend_Validate_File_Size Provides a fluent interface
+ */
+ public function setMin($min)
+ {
+ if (!is_string($min) and !is_numeric($min)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ $min = (integer) $this->_fromByteString($min);
+ $max = $this->getMax(true);
+ if (($max !== null) && ($min > $max)) {
+ throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum filesize, but $min >"
+ . " $max");
+ }
+
+ $this->_min = $min;
+ return $this;
+ }
+
+ /**
+ * Returns the maximum filesize
+ *
+ * @param bool $raw Whether or not to force return of the raw value (defaults off)
+ * @return integer|string
+ */
+ public function getMax($raw = false)
+ {
+ $max = $this->_max;
+ if (!$raw && $this->useByteString()) {
+ $max = $this->_toByteString($max);
+ }
+
+ return $max;
+ }
+
+ /**
+ * Sets the maximum filesize
+ *
+ * @param integer $max The maximum filesize
+ * @throws Zend_Validate_Exception When max is smaller than min
+ * @return Zend_Validate_StringLength Provides a fluent interface
+ */
+ public function setMax($max)
+ {
+ if (!is_string($max) && !is_numeric($max)) {
+ throw new Zend_Validate_Exception ('Invalid options to validator provided');
+ }
+
+ $max = (integer) $this->_fromByteString($max);
+ $min = $this->getMin(true);
+ if (($min !== null) && ($max < $min)) {
+ throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum filesize, but "
+ . "$max < $min");
+ }
+
+ $this->_max = $max;
+ return $this;
+ }
+
+ /**
+ * Retrieve current detected file size
+ *
+ * @return int
+ */
+ protected function _getSize()
+ {
+ return $this->_size;
+ }
+
+ /**
+ * Set current size
+ *
+ * @param int $size
+ * @return Zend_Validate_File_Size
+ */
+ protected function _setSize($size)
+ {
+ $this->_size = $size;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the filesize of $value is at least min and
+ * not bigger than max (when max is not null).
+ *
+ * @param string $value Real file to check for size
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ // limited to 4GB files
+ $size = sprintf("%u", @filesize($value));
+ $this->_size = $size;
+
+ // Check to see if it's smaller than min size
+ $min = $this->getMin(true);
+ $max = $this->getMax(true);
+ if (($min !== null) && ($size < $min)) {
+ if ($this->useByteString()) {
+ $this->_min = $this->_toByteString($min);
+ $this->_size = $this->_toByteString($size);
+ $this->_throw($file, self::TOO_SMALL);
+ $this->_min = $min;
+ $this->_size = $size;
+ } else {
+ $this->_throw($file, self::TOO_SMALL);
+ }
+ }
+
+ // Check to see if it's larger than max size
+ if (($max !== null) && ($max < $size)) {
+ if ($this->useByteString()) {
+ $this->_max = $this->_toByteString($max);
+ $this->_size = $this->_toByteString($size);
+ $this->_throw($file, self::TOO_BIG);
+ $this->_max = $max;
+ $this->_size = $size;
+ } else {
+ $this->_throw($file, self::TOO_BIG);
+ }
+ }
+
+ if (count($this->_messages) > 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the formatted size
+ *
+ * @param integer $size
+ * @return string
+ */
+ protected function _toByteString($size)
+ {
+ $sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
+ for ($i=0; $size >= 1024 && $i < 9; $i++) {
+ $size /= 1024;
+ }
+
+ return round($size, 2) . $sizes[$i];
+ }
+
+ /**
+ * Returns the unformatted size
+ *
+ * @param string $size
+ * @return integer
+ */
+ protected function _fromByteString($size)
+ {
+ if (is_numeric($size)) {
+ return (integer) $size;
+ }
+
+ $type = trim(substr($size, -2, 1));
+
+ $value = substr($size, 0, -1);
+ if (!is_numeric($value)) {
+ $value = substr($value, 0, -1);
+ }
+
+ switch (strtoupper($type)) {
+ case 'Y':
+ $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024);
+ break;
+ case 'Z':
+ $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024);
+ break;
+ case 'E':
+ $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024);
+ break;
+ case 'P':
+ $value *= (1024 * 1024 * 1024 * 1024 * 1024);
+ break;
+ case 'T':
+ $value *= (1024 * 1024 * 1024 * 1024);
+ break;
+ case 'G':
+ $value *= (1024 * 1024 * 1024);
+ break;
+ case 'M':
+ $value *= (1024 * 1024);
+ break;
+ case 'K':
+ $value *= 1024;
+ break;
+ default:
+ break;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/Upload.php b/library/vendor/Zend/Validate/File/Upload.php
new file mode 100644
index 0000000..b86c1c7
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/Upload.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validator for the maximum size of a file up to a max of 2GB
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_Upload extends Zend_Validate_Abstract
+{
+ /**@#+
+ * @const string Error constants
+ */
+ const INI_SIZE = 'fileUploadErrorIniSize';
+ const FORM_SIZE = 'fileUploadErrorFormSize';
+ const PARTIAL = 'fileUploadErrorPartial';
+ const NO_FILE = 'fileUploadErrorNoFile';
+ const NO_TMP_DIR = 'fileUploadErrorNoTmpDir';
+ const CANT_WRITE = 'fileUploadErrorCantWrite';
+ const EXTENSION = 'fileUploadErrorExtension';
+ const ATTACK = 'fileUploadErrorAttack';
+ const FILE_NOT_FOUND = 'fileUploadErrorFileNotFound';
+ const UNKNOWN = 'fileUploadErrorUnknown';
+ /**@#-*/
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::INI_SIZE => "File '%value%' exceeds the defined ini size",
+ self::FORM_SIZE => "File '%value%' exceeds the defined form size",
+ self::PARTIAL => "File '%value%' was only partially uploaded",
+ self::NO_FILE => "File '%value%' was not uploaded",
+ self::NO_TMP_DIR => "No temporary directory was found for file '%value%'",
+ self::CANT_WRITE => "File '%value%' can't be written",
+ self::EXTENSION => "A PHP extension returned an error while uploading the file '%value%'",
+ self::ATTACK => "File '%value%' was illegally uploaded. This could be a possible attack",
+ self::FILE_NOT_FOUND => "File '%value%' was not found",
+ self::UNKNOWN => "Unknown error while uploading file '%value%'"
+ );
+
+ /**
+ * Internal array of files
+ * @var array
+ */
+ protected $_files = array();
+
+ /**
+ * Sets validator options
+ *
+ * The array $files must be given in syntax of Zend_File_Transfer to be checked
+ * If no files are given the $_FILES array will be used automatically.
+ * NOTE: This validator will only work with HTTP POST uploads!
+ *
+ * @param array|Zend_Config $files Array of files in syntax of Zend_File_Transfer
+ */
+ public function __construct($files = array())
+ {
+ if ($files instanceof Zend_Config) {
+ $files = $files->toArray();
+ }
+
+ $this->setFiles($files);
+ }
+
+ /**
+ * Returns the array of set files
+ *
+ * @param string $file (Optional) The file to return in detail
+ * @return array
+ * @throws Zend_Validate_Exception If file is not found
+ */
+ public function getFiles($file = null)
+ {
+ if ($file !== null) {
+ $return = array();
+ foreach ($this->_files as $name => $content) {
+ if ($name === $file) {
+ $return[$file] = $this->_files[$name];
+ }
+
+ if ($content['name'] === $file) {
+ $return[$name] = $this->_files[$name];
+ }
+ }
+
+ if (count($return) === 0) {
+ throw new Zend_Validate_Exception("The file '$file' was not found");
+ }
+
+ return $return;
+ }
+
+ return $this->_files;
+ }
+
+ /**
+ * Sets the files to be checked
+ *
+ * @param array $files The files to check in syntax of Zend_File_Transfer
+ * @return Zend_Validate_File_Upload Provides a fluent interface
+ */
+ public function setFiles($files = array())
+ {
+ if (count($files) === 0) {
+ $this->_files = $_FILES;
+ } else {
+ $this->_files = $files;
+ }
+
+ // see ZF-10738
+ if (is_null($this->_files)) {
+ $this->_files = array();
+ }
+
+ foreach($this->_files as $file => $content) {
+ if (!isset($content['error'])) {
+ unset($this->_files[$file]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the file was uploaded without errors
+ *
+ * @param string $value Single file to check for upload errors, when giving null the $_FILES array
+ * from initialization will be used
+ * @param string|null $file
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ $this->_messages = [];
+ if (array_key_exists($value, $this->_files)) {
+ $files[$value] = $this->_files[$value];
+ } else {
+ foreach ($this->_files as $file => $content) {
+ if (isset($content['name']) && ($content['name'] === $value)) {
+ $files[$file] = $this->_files[$file];
+ }
+
+ if (isset($content['tmp_name']) && ($content['tmp_name'] === $value)) {
+ $files[$file] = $this->_files[$file];
+ }
+ }
+ }
+
+ if (empty($files)) {
+ return $this->_throw($file, self::FILE_NOT_FOUND);
+ }
+
+ foreach ($files as $file => $content) {
+ $this->_value = $file;
+ switch($content['error']) {
+ case 0:
+ if (!is_uploaded_file($content['tmp_name'])) {
+ $this->_throw($content, self::ATTACK);
+ }
+ break;
+
+ case 1:
+ $this->_throw($content, self::INI_SIZE);
+ break;
+
+ case 2:
+ $this->_throw($content, self::FORM_SIZE);
+ break;
+
+ case 3:
+ $this->_throw($content, self::PARTIAL);
+ break;
+
+ case 4:
+ $this->_throw($content, self::NO_FILE);
+ break;
+
+ case 6:
+ $this->_throw($content, self::NO_TMP_DIR);
+ break;
+
+ case 7:
+ $this->_throw($content, self::CANT_WRITE);
+ break;
+
+ case 8:
+ $this->_throw($content, self::EXTENSION);
+ break;
+
+ default:
+ $this->_throw($content, self::UNKNOWN);
+ break;
+ }
+ }
+
+ if (count($this->_messages) > 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ if (is_array($file) and !empty($file['name'])) {
+ $this->_value = $file['name'];
+ }
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/File/WordCount.php b/library/vendor/Zend/Validate/File/WordCount.php
new file mode 100644
index 0000000..09b3fd1
--- /dev/null
+++ b/library/vendor/Zend/Validate/File/WordCount.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_File_Count
+ */
+
+/**
+ * Validator for counting all words in a file
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_File_WordCount extends Zend_Validate_File_Count
+{
+ /**#@+
+ * @const string Error constants
+ */
+ const TOO_MUCH = 'fileWordCountTooMuch';
+ const TOO_LESS = 'fileWordCountTooLess';
+ const NOT_FOUND = 'fileWordCountNotFound';
+ /**#@-*/
+
+ /**
+ * @var array Error message templates
+ */
+ protected $_messageTemplates = array(
+ self::TOO_MUCH => "Too much words, maximum '%max%' are allowed but '%count%' were counted",
+ self::TOO_LESS => "Too less words, minimum '%min%' are expected but '%count%' were counted",
+ self::NOT_FOUND => "File '%value%' is not readable or does not exist",
+ );
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the counted words are at least min and
+ * not bigger than max (when max is not null).
+ *
+ * @param string $value Filename to check for word count
+ * @param array $file File data from Zend_File_Transfer
+ * @return boolean
+ */
+ public function isValid($value, $file = null)
+ {
+ // Is file readable ?
+ if (!Zend_Loader::isReadable($value)) {
+ return $this->_throw($file, self::NOT_FOUND);
+ }
+
+ $content = file_get_contents($value);
+ $this->_count = str_word_count($content);
+ if (($this->_max !== null) && ($this->_count > $this->_max)) {
+ return $this->_throw($file, self::TOO_MUCH);
+ }
+
+ if (($this->_min !== null) && ($this->_count < $this->_min)) {
+ return $this->_throw($file, self::TOO_LESS);
+ }
+
+ return true;
+ }
+
+ /**
+ * Throws an error of the given type
+ *
+ * @param string $file
+ * @param string $errorType
+ * @return false
+ */
+ protected function _throw($file, $errorType)
+ {
+ if ($file !== null) {
+ $this->_value = $file['name'];
+ }
+
+ $this->_error($errorType);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Float.php b/library/vendor/Zend/Validate/Float.php
new file mode 100644
index 0000000..95b0097
--- /dev/null
+++ b/library/vendor/Zend/Validate/Float.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Locale_Format
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Float extends Zend_Validate_Abstract
+{
+ const INVALID = 'floatInvalid';
+ const NOT_FLOAT = 'notFloat';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String, integer or float expected",
+ self::NOT_FLOAT => "'%value%' does not appear to be a float",
+ );
+
+ protected $_locale;
+
+ /**
+ * Constructor for the float validator
+ *
+ * @param string|Zend_Config|Zend_Locale $locale
+ */
+ public function __construct($locale = null)
+ {
+ if ($locale instanceof Zend_Config) {
+ $locale = $locale->toArray();
+ }
+
+ if (is_array($locale)) {
+ if (array_key_exists('locale', $locale)) {
+ $locale = $locale['locale'];
+ } else {
+ $locale = null;
+ }
+ }
+
+ if (empty($locale)) {
+ if (Zend_Registry::isRegistered('Zend_Locale')) {
+ $locale = Zend_Registry::get('Zend_Locale');
+ }
+ }
+
+ $this->setLocale($locale);
+ }
+
+ /**
+ * Returns the set locale
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Sets the locale to use
+ *
+ * @param string|Zend_Locale $locale
+ * @return $this
+ */
+ public function setLocale($locale = null)
+ {
+ $this->_locale = Zend_Locale::findLocale($locale);
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is a floating-point value
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value) && !is_float($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ if (is_float($value)) {
+ return true;
+ }
+
+ $this->_setValue($value);
+ try {
+ if (!Zend_Locale_Format::isFloat($value, array('locale' => $this->_locale))) {
+ $this->_error(self::NOT_FLOAT);
+ return false;
+ }
+ } catch (Zend_Locale_Exception $e) {
+ $this->_error(self::NOT_FLOAT);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/GreaterThan.php b/library/vendor/Zend/Validate/GreaterThan.php
new file mode 100644
index 0000000..ae71917
--- /dev/null
+++ b/library/vendor/Zend/Validate/GreaterThan.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_GreaterThan extends Zend_Validate_Abstract
+{
+
+ const NOT_GREATER = 'notGreaterThan';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_GREATER => "'%value%' is not greater than '%min%'",
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'min' => '_min'
+ );
+
+ /**
+ * Minimum value
+ *
+ * @var mixed
+ */
+ protected $_min;
+
+ /**
+ * Sets validator options
+ *
+ * @param mixed|Zend_Config $min
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($min)
+ {
+ if ($min instanceof Zend_Config) {
+ $min = $min->toArray();
+ }
+
+ if (is_array($min)) {
+ if (array_key_exists('min', $min)) {
+ $min = $min['min'];
+ } else {
+ throw new Zend_Validate_Exception("Missing option 'min'");
+ }
+ }
+
+ $this->setMin($min);
+ }
+
+ /**
+ * Returns the min option
+ *
+ * @return mixed
+ */
+ public function getMin()
+ {
+ return $this->_min;
+ }
+
+ /**
+ * Sets the min option
+ *
+ * @param mixed $min
+ * @return Zend_Validate_GreaterThan Provides a fluent interface
+ */
+ public function setMin($min)
+ {
+ $this->_min = $min;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is greater than min option
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+
+ if ($this->_min >= $value) {
+ $this->_error(self::NOT_GREATER);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Validate/Hex.php b/library/vendor/Zend/Validate/Hex.php
new file mode 100644
index 0000000..d8fcab2
--- /dev/null
+++ b/library/vendor/Zend/Validate/Hex.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Hex extends Zend_Validate_Abstract
+{
+ const INVALID = 'hexInvalid';
+ const NOT_HEX = 'notHex';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String expected",
+ self::NOT_HEX => "'%value%' has not only hexadecimal digit characters",
+ );
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value contains only hexadecimal digit characters
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ if (!ctype_xdigit((string) $value)) {
+ $this->_error(self::NOT_HEX);
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Validate/Hostname.php b/library/vendor/Zend/Validate/Hostname.php
new file mode 100644
index 0000000..afddd76
--- /dev/null
+++ b/library/vendor/Zend/Validate/Hostname.php
@@ -0,0 +1,1987 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Validate_Ip
+ */
+
+/**
+ * Please note there are two standalone test scripts for testing IDN characters due to problems
+ * with file encoding.
+ *
+ * The first is tests/Zend/Validate/HostnameTestStandalone.php which is designed to be run on
+ * the command line.
+ *
+ * The second is tests/Zend/Validate/HostnameTestForm.php which is designed to be run via HTML
+ * to allow users to test entering UTF-8 characters in a form.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Hostname extends Zend_Validate_Abstract
+{
+ const CANNOT_DECODE_PUNYCODE = 'hostnameCannotDecodePunycode';
+ const INVALID = 'hostnameInvalid';
+ const INVALID_DASH = 'hostnameDashCharacter';
+ const INVALID_HOSTNAME = 'hostnameInvalidHostname';
+ const INVALID_HOSTNAME_SCHEMA = 'hostnameInvalidHostnameSchema';
+ const INVALID_LOCAL_NAME = 'hostnameInvalidLocalName';
+ const INVALID_URI = 'hostnameInvalidUri';
+ const IP_ADDRESS_NOT_ALLOWED = 'hostnameIpAddressNotAllowed';
+ const LOCAL_NAME_NOT_ALLOWED = 'hostnameLocalNameNotAllowed';
+ const UNDECIPHERABLE_TLD = 'hostnameUndecipherableTld';
+ const UNKNOWN_TLD = 'hostnameUnknownTld';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::CANNOT_DECODE_PUNYCODE => "'%value%' appears to be a DNS hostname but the given punycode notation cannot be decoded",
+ self::INVALID => "Invalid type given. String expected",
+ self::INVALID_DASH => "'%value%' appears to be a DNS hostname but contains a dash in an invalid position",
+ self::INVALID_HOSTNAME => "'%value%' does not match the expected structure for a DNS hostname",
+ self::INVALID_HOSTNAME_SCHEMA => "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'",
+ self::INVALID_LOCAL_NAME => "'%value%' does not appear to be a valid local network name",
+ self::INVALID_URI => "'%value%' does not appear to be a valid URI hostname",
+ self::IP_ADDRESS_NOT_ALLOWED => "'%value%' appears to be an IP address, but IP addresses are not allowed",
+ self::LOCAL_NAME_NOT_ALLOWED => "'%value%' appears to be a local network name but local network names are not allowed",
+ self::UNDECIPHERABLE_TLD => "'%value%' appears to be a DNS hostname but cannot extract TLD part",
+ self::UNKNOWN_TLD => "'%value%' appears to be a DNS hostname but cannot match TLD against known list",
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'tld' => '_tld'
+ );
+
+ /**
+ * Allows Internet domain names (e.g., example.com)
+ */
+ const ALLOW_DNS = 1;
+
+ /**
+ * Allows IP addresses
+ */
+ const ALLOW_IP = 2;
+
+ /**
+ * Allows local network names (e.g., localhost, www.localdomain)
+ */
+ const ALLOW_LOCAL = 4;
+
+ /**
+ * Allows all types of hostnames
+ */
+ const ALLOW_URI = 8;
+
+ /**
+ * Allows all types of hostnames
+ */
+ const ALLOW_ALL = 15;
+
+ /**
+ * Array of valid top-level-domains
+ *
+ * Version 2015091800, Last Updated Fri Sep 18 07:07:01 2015 UTC
+ *
+ * @see http://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain
+ * @see http://www.iana.org/domains/root/db/ Official list of supported TLDs
+ * @var array
+ */
+ protected $_validTlds = array(
+ 'aaa',
+ 'abb',
+ 'abbott',
+ 'abogado',
+ 'ac',
+ 'academy',
+ 'accenture',
+ 'accountant',
+ 'accountants',
+ 'aco',
+ 'active',
+ 'actor',
+ 'ad',
+ 'ads',
+ 'adult',
+ 'ae',
+ 'aeg',
+ 'aero',
+ 'af',
+ 'afl',
+ 'ag',
+ 'agency',
+ 'ai',
+ 'aig',
+ 'airforce',
+ 'airtel',
+ 'al',
+ 'allfinanz',
+ 'alsace',
+ 'am',
+ 'amica',
+ 'amsterdam',
+ 'android',
+ 'ao',
+ 'apartments',
+ 'app',
+ 'aq',
+ 'aquarelle',
+ 'ar',
+ 'aramco',
+ 'archi',
+ 'army',
+ 'arpa',
+ 'arte',
+ 'as',
+ 'asia',
+ 'associates',
+ 'at',
+ 'attorney',
+ 'au',
+ 'auction',
+ 'audio',
+ 'auto',
+ 'autos',
+ 'aw',
+ 'ax',
+ 'axa',
+ 'az',
+ 'azure',
+ 'ba',
+ 'band',
+ 'bank',
+ 'bar',
+ 'barcelona',
+ 'barclaycard',
+ 'barclays',
+ 'bargains',
+ 'bauhaus',
+ 'bayern',
+ 'bb',
+ 'bbc',
+ 'bbva',
+ 'bcn',
+ 'bd',
+ 'be',
+ 'beer',
+ 'bentley',
+ 'berlin',
+ 'best',
+ 'bet',
+ 'bf',
+ 'bg',
+ 'bh',
+ 'bharti',
+ 'bi',
+ 'bible',
+ 'bid',
+ 'bike',
+ 'bing',
+ 'bingo',
+ 'bio',
+ 'biz',
+ 'bj',
+ 'black',
+ 'blackfriday',
+ 'bloomberg',
+ 'blue',
+ 'bm',
+ 'bms',
+ 'bmw',
+ 'bn',
+ 'bnl',
+ 'bnpparibas',
+ 'bo',
+ 'boats',
+ 'bom',
+ 'bond',
+ 'boo',
+ 'boots',
+ 'boutique',
+ 'br',
+ 'bradesco',
+ 'bridgestone',
+ 'broker',
+ 'brother',
+ 'brussels',
+ 'bs',
+ 'bt',
+ 'budapest',
+ 'build',
+ 'builders',
+ 'business',
+ 'buzz',
+ 'bv',
+ 'bw',
+ 'by',
+ 'bz',
+ 'bzh',
+ 'ca',
+ 'cab',
+ 'cafe',
+ 'cal',
+ 'camera',
+ 'camp',
+ 'cancerresearch',
+ 'canon',
+ 'capetown',
+ 'capital',
+ 'car',
+ 'caravan',
+ 'cards',
+ 'care',
+ 'career',
+ 'careers',
+ 'cars',
+ 'cartier',
+ 'casa',
+ 'cash',
+ 'casino',
+ 'cat',
+ 'catering',
+ 'cba',
+ 'cbn',
+ 'cc',
+ 'cd',
+ 'ceb',
+ 'center',
+ 'ceo',
+ 'cern',
+ 'cf',
+ 'cfa',
+ 'cfd',
+ 'cg',
+ 'ch',
+ 'chanel',
+ 'channel',
+ 'chat',
+ 'cheap',
+ 'chloe',
+ 'christmas',
+ 'chrome',
+ 'church',
+ 'ci',
+ 'cipriani',
+ 'cisco',
+ 'citic',
+ 'city',
+ 'ck',
+ 'cl',
+ 'claims',
+ 'cleaning',
+ 'click',
+ 'clinic',
+ 'clothing',
+ 'cloud',
+ 'club',
+ 'clubmed',
+ 'cm',
+ 'cn',
+ 'co',
+ 'coach',
+ 'codes',
+ 'coffee',
+ 'college',
+ 'cologne',
+ 'com',
+ 'commbank',
+ 'community',
+ 'company',
+ 'computer',
+ 'condos',
+ 'construction',
+ 'consulting',
+ 'contractors',
+ 'cooking',
+ 'cool',
+ 'coop',
+ 'corsica',
+ 'country',
+ 'coupons',
+ 'courses',
+ 'cr',
+ 'credit',
+ 'creditcard',
+ 'cricket',
+ 'crown',
+ 'crs',
+ 'cruises',
+ 'csc',
+ 'cu',
+ 'cuisinella',
+ 'cv',
+ 'cw',
+ 'cx',
+ 'cy',
+ 'cymru',
+ 'cyou',
+ 'cz',
+ 'dabur',
+ 'dad',
+ 'dance',
+ 'date',
+ 'dating',
+ 'datsun',
+ 'day',
+ 'dclk',
+ 'de',
+ 'deals',
+ 'degree',
+ 'delivery',
+ 'dell',
+ 'delta',
+ 'democrat',
+ 'dental',
+ 'dentist',
+ 'desi',
+ 'design',
+ 'dev',
+ 'diamonds',
+ 'diet',
+ 'digital',
+ 'direct',
+ 'directory',
+ 'discount',
+ 'dj',
+ 'dk',
+ 'dm',
+ 'dnp',
+ 'do',
+ 'docs',
+ 'dog',
+ 'doha',
+ 'domains',
+ 'doosan',
+ 'download',
+ 'drive',
+ 'durban',
+ 'dvag',
+ 'dz',
+ 'earth',
+ 'eat',
+ 'ec',
+ 'edu',
+ 'education',
+ 'ee',
+ 'eg',
+ 'email',
+ 'emerck',
+ 'energy',
+ 'engineer',
+ 'engineering',
+ 'enterprises',
+ 'epson',
+ 'equipment',
+ 'er',
+ 'erni',
+ 'es',
+ 'esq',
+ 'estate',
+ 'et',
+ 'eu',
+ 'eurovision',
+ 'eus',
+ 'events',
+ 'everbank',
+ 'exchange',
+ 'expert',
+ 'exposed',
+ 'express',
+ 'fage',
+ 'fail',
+ 'faith',
+ 'family',
+ 'fan',
+ 'fans',
+ 'farm',
+ 'fashion',
+ 'feedback',
+ 'fi',
+ 'film',
+ 'final',
+ 'finance',
+ 'financial',
+ 'firmdale',
+ 'fish',
+ 'fishing',
+ 'fit',
+ 'fitness',
+ 'fj',
+ 'fk',
+ 'flights',
+ 'florist',
+ 'flowers',
+ 'flsmidth',
+ 'fly',
+ 'fm',
+ 'fo',
+ 'foo',
+ 'football',
+ 'forex',
+ 'forsale',
+ 'forum',
+ 'foundation',
+ 'fr',
+ 'frl',
+ 'frogans',
+ 'fund',
+ 'furniture',
+ 'futbol',
+ 'fyi',
+ 'ga',
+ 'gal',
+ 'gallery',
+ 'game',
+ 'garden',
+ 'gb',
+ 'gbiz',
+ 'gd',
+ 'gdn',
+ 'ge',
+ 'gea',
+ 'gent',
+ 'genting',
+ 'gf',
+ 'gg',
+ 'ggee',
+ 'gh',
+ 'gi',
+ 'gift',
+ 'gifts',
+ 'gives',
+ 'giving',
+ 'gl',
+ 'glass',
+ 'gle',
+ 'global',
+ 'globo',
+ 'gm',
+ 'gmail',
+ 'gmo',
+ 'gmx',
+ 'gn',
+ 'gold',
+ 'goldpoint',
+ 'golf',
+ 'goo',
+ 'goog',
+ 'google',
+ 'gop',
+ 'gov',
+ 'gp',
+ 'gq',
+ 'gr',
+ 'graphics',
+ 'gratis',
+ 'green',
+ 'gripe',
+ 'group',
+ 'gs',
+ 'gt',
+ 'gu',
+ 'gucci',
+ 'guge',
+ 'guide',
+ 'guitars',
+ 'guru',
+ 'gw',
+ 'gy',
+ 'hamburg',
+ 'hangout',
+ 'haus',
+ 'healthcare',
+ 'help',
+ 'here',
+ 'hermes',
+ 'hiphop',
+ 'hitachi',
+ 'hiv',
+ 'hk',
+ 'hm',
+ 'hn',
+ 'hockey',
+ 'holdings',
+ 'holiday',
+ 'homedepot',
+ 'homes',
+ 'honda',
+ 'horse',
+ 'host',
+ 'hosting',
+ 'hoteles',
+ 'hotmail',
+ 'house',
+ 'how',
+ 'hr',
+ 'hsbc',
+ 'ht',
+ 'hu',
+ 'hyundai',
+ 'ibm',
+ 'icbc',
+ 'ice',
+ 'icu',
+ 'id',
+ 'ie',
+ 'ifm',
+ 'iinet',
+ 'il',
+ 'im',
+ 'immo',
+ 'immobilien',
+ 'in',
+ 'industries',
+ 'infiniti',
+ 'info',
+ 'ing',
+ 'ink',
+ 'institute',
+ 'insure',
+ 'int',
+ 'international',
+ 'investments',
+ 'io',
+ 'ipiranga',
+ 'iq',
+ 'ir',
+ 'irish',
+ 'is',
+ 'ist',
+ 'istanbul',
+ 'it',
+ 'itau',
+ 'iwc',
+ 'jaguar',
+ 'java',
+ 'jcb',
+ 'je',
+ 'jetzt',
+ 'jewelry',
+ 'jlc',
+ 'jll',
+ 'jm',
+ 'jo',
+ 'jobs',
+ 'joburg',
+ 'jp',
+ 'jprs',
+ 'juegos',
+ 'kaufen',
+ 'kddi',
+ 'ke',
+ 'kg',
+ 'kh',
+ 'ki',
+ 'kia',
+ 'kim',
+ 'kinder',
+ 'kitchen',
+ 'kiwi',
+ 'km',
+ 'kn',
+ 'koeln',
+ 'komatsu',
+ 'kp',
+ 'kr',
+ 'krd',
+ 'kred',
+ 'kw',
+ 'ky',
+ 'kyoto',
+ 'kz',
+ 'la',
+ 'lacaixa',
+ 'lancaster',
+ 'land',
+ 'landrover',
+ 'lasalle',
+ 'lat',
+ 'latrobe',
+ 'law',
+ 'lawyer',
+ 'lb',
+ 'lc',
+ 'lds',
+ 'lease',
+ 'leclerc',
+ 'legal',
+ 'lexus',
+ 'lgbt',
+ 'li',
+ 'liaison',
+ 'lidl',
+ 'life',
+ 'lighting',
+ 'limited',
+ 'limo',
+ 'linde',
+ 'link',
+ 'live',
+ 'lixil',
+ 'lk',
+ 'loan',
+ 'loans',
+ 'lol',
+ 'london',
+ 'lotte',
+ 'lotto',
+ 'love',
+ 'lr',
+ 'ls',
+ 'lt',
+ 'ltd',
+ 'ltda',
+ 'lu',
+ 'lupin',
+ 'luxe',
+ 'luxury',
+ 'lv',
+ 'ly',
+ 'ma',
+ 'madrid',
+ 'maif',
+ 'maison',
+ 'man',
+ 'management',
+ 'mango',
+ 'market',
+ 'marketing',
+ 'markets',
+ 'marriott',
+ 'mba',
+ 'mc',
+ 'md',
+ 'me',
+ 'media',
+ 'meet',
+ 'melbourne',
+ 'meme',
+ 'memorial',
+ 'men',
+ 'menu',
+ 'mg',
+ 'mh',
+ 'miami',
+ 'microsoft',
+ 'mil',
+ 'mini',
+ 'mk',
+ 'ml',
+ 'mm',
+ 'mma',
+ 'mn',
+ 'mo',
+ 'mobi',
+ 'moda',
+ 'moe',
+ 'moi',
+ 'mom',
+ 'monash',
+ 'money',
+ 'montblanc',
+ 'mormon',
+ 'mortgage',
+ 'moscow',
+ 'motorcycles',
+ 'mov',
+ 'movie',
+ 'movistar',
+ 'mp',
+ 'mq',
+ 'mr',
+ 'ms',
+ 'mt',
+ 'mtn',
+ 'mtpc',
+ 'mtr',
+ 'mu',
+ 'museum',
+ 'mutuelle',
+ 'mv',
+ 'mw',
+ 'mx',
+ 'my',
+ 'mz',
+ 'na',
+ 'nadex',
+ 'nagoya',
+ 'name',
+ 'navy',
+ 'nc',
+ 'ne',
+ 'nec',
+ 'net',
+ 'netbank',
+ 'network',
+ 'neustar',
+ 'new',
+ 'news',
+ 'nexus',
+ 'nf',
+ 'ng',
+ 'ngo',
+ 'nhk',
+ 'ni',
+ 'nico',
+ 'ninja',
+ 'nissan',
+ 'nl',
+ 'no',
+ 'nokia',
+ 'np',
+ 'nr',
+ 'nra',
+ 'nrw',
+ 'ntt',
+ 'nu',
+ 'nyc',
+ 'nz',
+ 'obi',
+ 'office',
+ 'okinawa',
+ 'om',
+ 'omega',
+ 'one',
+ 'ong',
+ 'onl',
+ 'online',
+ 'ooo',
+ 'oracle',
+ 'orange',
+ 'org',
+ 'organic',
+ 'osaka',
+ 'otsuka',
+ 'ovh',
+ 'pa',
+ 'page',
+ 'panerai',
+ 'paris',
+ 'partners',
+ 'parts',
+ 'party',
+ 'pe',
+ 'pet',
+ 'pf',
+ 'pg',
+ 'ph',
+ 'pharmacy',
+ 'philips',
+ 'photo',
+ 'photography',
+ 'photos',
+ 'physio',
+ 'piaget',
+ 'pics',
+ 'pictet',
+ 'pictures',
+ 'pink',
+ 'pizza',
+ 'pk',
+ 'pl',
+ 'place',
+ 'play',
+ 'plumbing',
+ 'plus',
+ 'pm',
+ 'pn',
+ 'pohl',
+ 'poker',
+ 'porn',
+ 'post',
+ 'pr',
+ 'praxi',
+ 'press',
+ 'pro',
+ 'prod',
+ 'productions',
+ 'prof',
+ 'properties',
+ 'property',
+ 'protection',
+ 'ps',
+ 'pt',
+ 'pub',
+ 'pw',
+ 'py',
+ 'qa',
+ 'qpon',
+ 'quebec',
+ 'racing',
+ 're',
+ 'realtor',
+ 'realty',
+ 'recipes',
+ 'red',
+ 'redstone',
+ 'rehab',
+ 'reise',
+ 'reisen',
+ 'reit',
+ 'ren',
+ 'rent',
+ 'rentals',
+ 'repair',
+ 'report',
+ 'republican',
+ 'rest',
+ 'restaurant',
+ 'review',
+ 'reviews',
+ 'rich',
+ 'ricoh',
+ 'rio',
+ 'rip',
+ 'ro',
+ 'rocks',
+ 'rodeo',
+ 'rs',
+ 'rsvp',
+ 'ru',
+ 'ruhr',
+ 'run',
+ 'rw',
+ 'rwe',
+ 'ryukyu',
+ 'sa',
+ 'saarland',
+ 'sakura',
+ 'sale',
+ 'samsung',
+ 'sandvik',
+ 'sandvikcoromant',
+ 'sanofi',
+ 'sap',
+ 'sarl',
+ 'saxo',
+ 'sb',
+ 'sc',
+ 'sca',
+ 'scb',
+ 'schmidt',
+ 'scholarships',
+ 'school',
+ 'schule',
+ 'schwarz',
+ 'science',
+ 'scor',
+ 'scot',
+ 'sd',
+ 'se',
+ 'seat',
+ 'security',
+ 'seek',
+ 'sener',
+ 'services',
+ 'seven',
+ 'sew',
+ 'sex',
+ 'sexy',
+ 'sg',
+ 'sh',
+ 'shiksha',
+ 'shoes',
+ 'show',
+ 'shriram',
+ 'si',
+ 'singles',
+ 'site',
+ 'sj',
+ 'sk',
+ 'ski',
+ 'sky',
+ 'skype',
+ 'sl',
+ 'sm',
+ 'sn',
+ 'sncf',
+ 'so',
+ 'soccer',
+ 'social',
+ 'software',
+ 'sohu',
+ 'solar',
+ 'solutions',
+ 'sony',
+ 'soy',
+ 'space',
+ 'spiegel',
+ 'spreadbetting',
+ 'sr',
+ 'srl',
+ 'st',
+ 'stada',
+ 'starhub',
+ 'statoil',
+ 'stc',
+ 'stcgroup',
+ 'stockholm',
+ 'studio',
+ 'study',
+ 'style',
+ 'su',
+ 'sucks',
+ 'supplies',
+ 'supply',
+ 'support',
+ 'surf',
+ 'surgery',
+ 'suzuki',
+ 'sv',
+ 'swatch',
+ 'swiss',
+ 'sx',
+ 'sy',
+ 'sydney',
+ 'systems',
+ 'sz',
+ 'taipei',
+ 'tatamotors',
+ 'tatar',
+ 'tattoo',
+ 'tax',
+ 'taxi',
+ 'tc',
+ 'td',
+ 'team',
+ 'tech',
+ 'technology',
+ 'tel',
+ 'telefonica',
+ 'temasek',
+ 'tennis',
+ 'tf',
+ 'tg',
+ 'th',
+ 'thd',
+ 'theater',
+ 'theatre',
+ 'tickets',
+ 'tienda',
+ 'tips',
+ 'tires',
+ 'tirol',
+ 'tj',
+ 'tk',
+ 'tl',
+ 'tm',
+ 'tn',
+ 'to',
+ 'today',
+ 'tokyo',
+ 'tools',
+ 'top',
+ 'toray',
+ 'toshiba',
+ 'tours',
+ 'town',
+ 'toyota',
+ 'toys',
+ 'tr',
+ 'trade',
+ 'trading',
+ 'training',
+ 'travel',
+ 'trust',
+ 'tt',
+ 'tui',
+ 'tv',
+ 'tw',
+ 'tz',
+ 'ua',
+ 'ubs',
+ 'ug',
+ 'uk',
+ 'university',
+ 'uno',
+ 'uol',
+ 'us',
+ 'uy',
+ 'uz',
+ 'va',
+ 'vacations',
+ 'vc',
+ 've',
+ 'vegas',
+ 'ventures',
+ 'versicherung',
+ 'vet',
+ 'vg',
+ 'vi',
+ 'viajes',
+ 'video',
+ 'villas',
+ 'vin',
+ 'virgin',
+ 'vision',
+ 'vista',
+ 'vistaprint',
+ 'viva',
+ 'vlaanderen',
+ 'vn',
+ 'vodka',
+ 'vote',
+ 'voting',
+ 'voto',
+ 'voyage',
+ 'vu',
+ 'wales',
+ 'walter',
+ 'wang',
+ 'watch',
+ 'webcam',
+ 'website',
+ 'wed',
+ 'wedding',
+ 'weir',
+ 'wf',
+ 'whoswho',
+ 'wien',
+ 'wiki',
+ 'williamhill',
+ 'win',
+ 'windows',
+ 'wine',
+ 'wme',
+ 'work',
+ 'works',
+ 'world',
+ 'ws',
+ 'wtc',
+ 'wtf',
+ 'xbox',
+ 'xerox',
+ 'xin',
+ 'xn--11b4c3d',
+ 'xn--1qqw23a',
+ 'xn--30rr7y',
+ 'xn--3bst00m',
+ 'xn--3ds443g',
+ 'xn--3e0b707e',
+ 'xn--3pxu8k',
+ 'xn--42c2d9a',
+ 'xn--45brj9c',
+ 'xn--45q11c',
+ 'xn--4gbrim',
+ 'xn--55qw42g',
+ 'xn--55qx5d',
+ 'xn--6frz82g',
+ 'xn--6qq986b3xl',
+ 'xn--80adxhks',
+ 'xn--80ao21a',
+ 'xn--80asehdb',
+ 'xn--80aswg',
+ 'xn--90a3ac',
+ 'xn--90ais',
+ 'xn--9dbq2a',
+ 'xn--9et52u',
+ 'xn--b4w605ferd',
+ 'xn--c1avg',
+ 'xn--c2br7g',
+ 'xn--cg4bki',
+ 'xn--clchc0ea0b2g2a9gcd',
+ 'xn--czr694b',
+ 'xn--czrs0t',
+ 'xn--czru2d',
+ 'xn--d1acj3b',
+ 'xn--d1alf',
+ 'xn--efvy88h',
+ 'xn--estv75g',
+ 'xn--fhbei',
+ 'xn--fiq228c5hs',
+ 'xn--fiq64b',
+ 'xn--fiqs8s',
+ 'xn--fiqz9s',
+ 'xn--fjq720a',
+ 'xn--flw351e',
+ 'xn--fpcrj9c3d',
+ 'xn--fzc2c9e2c',
+ 'xn--gecrj9c',
+ 'xn--h2brj9c',
+ 'xn--hxt814e',
+ 'xn--i1b6b1a6a2e',
+ 'xn--imr513n',
+ 'xn--io0a7i',
+ 'xn--j1aef',
+ 'xn--j1amh',
+ 'xn--j6w193g',
+ 'xn--kcrx77d1x4a',
+ 'xn--kprw13d',
+ 'xn--kpry57d',
+ 'xn--kput3i',
+ 'xn--l1acc',
+ 'xn--lgbbat1ad8j',
+ 'xn--mgb9awbf',
+ 'xn--mgba3a3ejt',
+ 'xn--mgba3a4f16a',
+ 'xn--mgbaam7a8h',
+ 'xn--mgbab2bd',
+ 'xn--mgbayh7gpa',
+ 'xn--mgbbh1a71e',
+ 'xn--mgbc0a9azcg',
+ 'xn--mgberp4a5d4ar',
+ 'xn--mgbpl2fh',
+ 'xn--mgbx4cd0ab',
+ 'xn--mk1bu44c',
+ 'xn--mxtq1m',
+ 'xn--ngbc5azd',
+ 'xn--node',
+ 'xn--nqv7f',
+ 'xn--nqv7fs00ema',
+ 'xn--nyqy26a',
+ 'xn--o3cw4h',
+ 'xn--ogbpf8fl',
+ 'xn--p1acf',
+ 'xn--p1ai',
+ 'xn--pgbs0dh',
+ 'xn--pssy2u',
+ 'xn--q9jyb4c',
+ 'xn--qcka1pmc',
+ 'xn--rhqv96g',
+ 'xn--s9brj9c',
+ 'xn--ses554g',
+ 'xn--t60b56a',
+ 'xn--tckwe',
+ 'xn--unup4y',
+ 'xn--vermgensberater-ctb',
+ 'xn--vermgensberatung-pwb',
+ 'xn--vhquv',
+ 'xn--vuq861b',
+ 'xn--wgbh1c',
+ 'xn--wgbl6a',
+ 'xn--xhq521b',
+ 'xn--xkc2al3hye2a',
+ 'xn--xkc2dl3a5ee0h',
+ 'xn--y9a3aq',
+ 'xn--yfro4i67o',
+ 'xn--ygbi2ammx',
+ 'xn--zfr164b',
+ 'xperia',
+ 'xxx',
+ 'xyz',
+ 'yachts',
+ 'yamaxun',
+ 'yandex',
+ 'ye',
+ 'yodobashi',
+ 'yoga',
+ 'yokohama',
+ 'youtube',
+ 'yt',
+ 'za',
+ 'zara',
+ 'zip',
+ 'zm',
+ 'zone',
+ 'zuerich',
+ 'zw',
+ '测试',
+ 'परीकà¥à¤·à¤¾',
+ '佛山',
+ '集团',
+ '在线',
+ '한국',
+ 'ভারত',
+ 'å…«å¦',
+ 'موقع',
+ 'বাংলা',
+ '公益',
+ 'å…¬å¸',
+ '移动',
+ '我爱你',
+ 'моÑква',
+ 'иÑпытание',
+ 'қаз',
+ 'онлайн',
+ 'Ñайт',
+ 'Ñрб',
+ 'бел',
+ '테스트',
+ 'орг',
+ '삼성',
+ 'சிஙà¯à®•à®ªà¯à®ªà¯‚à®°à¯',
+ '商标',
+ '商城',
+ 'дети',
+ 'мкд',
+ 'טעסט',
+ '中文网',
+ '中信',
+ '中国',
+ '中國',
+ '谷歌',
+ 'భారతà±',
+ 'ලංකà·',
+ '測試',
+ 'ભારત',
+ 'भारत',
+ 'آزمایشی',
+ 'பரிடà¯à®šà¯ˆ',
+ 'संगठन',
+ '网络',
+ 'укр',
+ '香港',
+ 'δοκιμή',
+ 'إختبار',
+ 'å°æ¹¾',
+ 'å°ç£',
+ '手机',
+ 'мон',
+ 'الجزائر',
+ 'عمان',
+ 'ایران',
+ 'امارات',
+ 'بازار',
+ 'پاکستان',
+ 'الاردن',
+ 'بھارت',
+ 'المغرب',
+ 'السعودية',
+ 'سودان',
+ 'عراق',
+ 'مليسيا',
+ 'شبكة',
+ 'გე',
+ '机构',
+ '组织机构',
+ 'ไทย',
+ 'سورية',
+ 'руÑ',
+ 'рф',
+ 'تونس',
+ 'ã¿ã‚“ãª',
+ 'グーグル',
+ '世界',
+ 'ਭਾਰਤ',
+ '网å€',
+ '游æˆ',
+ 'vermögensberater',
+ 'vermögensberatung',
+ 'ä¼ä¸š',
+ 'مصر',
+ 'قطر',
+ '广东',
+ 'இலஙà¯à®•à¯ˆ',
+ 'இநà¯à®¤à®¿à®¯à®¾',
+ 'Õ°Õ¡Õµ',
+ '新加å¡',
+ 'Ùلسطين',
+ 'テスト',
+ '政务',
+ );
+
+ /**
+ * @var string
+ */
+ protected $_tld;
+
+ /**
+ * Array for valid Idns
+ * @see http://www.iana.org/domains/idn-tables/ Official list of supported IDN Chars
+ * (.AC) Ascension Island http://www.nic.ac/pdf/AC-IDN-Policy.pdf
+ * (.AR) Argentinia http://www.nic.ar/faqidn.html
+ * (.AS) American Samoa http://www.nic.as/idn/chars.cfm
+ * (.AT) Austria http://www.nic.at/en/service/technical_information/idn/charset_converter/
+ * (.BIZ) International http://www.iana.org/domains/idn-tables/
+ * (.BR) Brazil http://registro.br/faq/faq6.html
+ * (.BV) Bouvett Island http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
+ * (.CA) Canada http://www.iana.org/domains/idn-tables/tables/ca_fr_1.0.html
+ * (.CAT) Catalan http://www.iana.org/domains/idn-tables/tables/cat_ca_1.0.html
+ * (.CH) Switzerland https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1
+ * (.CL) Chile http://www.iana.org/domains/idn-tables/tables/cl_latn_1.0.html
+ * (.COM) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html
+ * (.DE) Germany http://www.denic.de/en/domains/idns/liste.html
+ * (.DK) Danmark http://www.dk-hostmaster.dk/index.php?id=151
+ * (.ES) Spain https://www.nic.es/media/2008-05/1210147705287.pdf
+ * (.FI) Finland http://www.ficora.fi/en/index/palvelut/fiverkkotunnukset/aakkostenkaytto.html
+ * (.GR) Greece https://grweb.ics.forth.gr/CharacterTable1_en.jsp
+ * (.HU) Hungary http://www.domain.hu/domain/English/szabalyzat/szabalyzat.html
+ * (.INFO) International http://www.nic.info/info/idn
+ * (.IO) British Indian Ocean Territory http://www.nic.io/IO-IDN-Policy.pdf
+ * (.IR) Iran http://www.nic.ir/Allowable_Characters_dot-iran
+ * (.IS) Iceland http://www.isnic.is/domain/rules.php
+ * (.KR) Korea http://www.iana.org/domains/idn-tables/tables/kr_ko-kr_1.0.html
+ * (.LI) Liechtenstein https://nic.switch.ch/reg/ocView.action?res=EF6GW2JBPVTG67DLNIQXU234MN6SC33JNQQGI7L6#anhang1
+ * (.LT) Lithuania http://www.domreg.lt/static/doc/public/idn_symbols-en.pdf
+ * (.MD) Moldova http://www.register.md/
+ * (.MUSEUM) International http://www.iana.org/domains/idn-tables/tables/museum_latn_1.0.html
+ * (.NET) International http://www.verisign.com/information-services/naming-services/internationalized-domain-names/index.html
+ * (.NO) Norway http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
+ * (.NU) Niue http://www.worldnames.net/
+ * (.ORG) International http://www.pir.org/index.php?db=content/FAQs&tbl=FAQs_Registrant&id=2
+ * (.PE) Peru https://www.nic.pe/nuevas_politicas_faq_2.php
+ * (.PL) Poland http://www.dns.pl/IDN/allowed_character_sets.pdf
+ * (.PR) Puerto Rico http://www.nic.pr/idn_rules.asp
+ * (.PT) Portugal https://online.dns.pt/dns_2008/do?com=DS;8216320233;111;+PAGE(4000058)+K-CAT-CODIGO(C.125)+RCNT(100);
+ * (.RU) Russia http://www.iana.org/domains/idn-tables/tables/ru_ru-ru_1.0.html
+ * (.RS) Serbia http://www.iana.org/domains/idn-tables/tables/rs_sr-rs_1.0.pdf
+ * (.SA) Saudi Arabia http://www.iana.org/domains/idn-tables/tables/sa_ar_1.0.html
+ * (.SE) Sweden http://www.iis.se/english/IDN_campaignsite.shtml?lang=en
+ * (.SH) Saint Helena http://www.nic.sh/SH-IDN-Policy.pdf
+ * (.SJ) Svalbard and Jan Mayen http://www.norid.no/domeneregistrering/idn/idn_nyetegn.en.html
+ * (.TH) Thailand http://www.iana.org/domains/idn-tables/tables/th_th-th_1.0.html
+ * (.TM) Turkmenistan http://www.nic.tm/TM-IDN-Policy.pdf
+ * (.TR) Turkey https://www.nic.tr/index.php
+ * (.UA) Ukraine http://www.iana.org/domains/idn-tables/tables/ua_cyrl_1.2.html
+ * (.VE) Venice http://www.iana.org/domains/idn-tables/tables/ve_es_1.0.html
+ * (.VN) Vietnam http://www.vnnic.vn/english/5-6-300-2-2-04-20071115.htm#1.%20Introduction
+ *
+ * @var array
+ */
+ protected $_validIdns = array(
+ 'AC' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿÄăąćĉċÄÄđēėęěÄġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśÅşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'),
+ 'AR' => array(1 => '/^[\x{002d}0-9a-zà-ãç-êìíñ-õü]{1,63}$/iu'),
+ 'AS' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįıĵķĸĺļľłńņňŋÅÅőœŕŗřśÅşšţťŧũūŭůűųŵŷźż]{1,63}$/iu'),
+ 'AT' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœšž]{1,63}$/iu'),
+ 'BIZ' => 'Hostname/Biz.php',
+ 'BR' => array(1 => '/^[\x{002d}0-9a-zà-ãçéíó-õúü]{1,63}$/iu'),
+ 'BV' => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüÄđńŋšŧž]{1,63}$/iu'),
+ 'CA' => array(1 => '/^[\x{002d}0-9a-zàâæçéèêëîïôœùûüÿ\x{00E0}\x{00E2}\x{00E7}\x{00E8}\x{00E9}\x{00EA}\x{00EB}\x{00EE}\x{00EF}\x{00F4}\x{00F9}\x{00FB}\x{00FC}\x{00E6}\x{0153}\x{00FF}]{1,63}$/iu'),
+ 'CAT' => array(1 => '/^[\x{002d}0-9a-z·àç-éíïòóúü]{1,63}$/iu'),
+ 'CH' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'),
+ 'CL' => array(1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'),
+ 'CN' => 'Hostname/Cn.php',
+ 'COM' => 'Hostname/Com.php',
+ 'DE' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąÄćĉÄÄ‹ÄđĕěėęēğÄġģĥħĭĩįīıĵķĺľļłńňņŋÅÅ‘ÅœĸŕřŗśÅšşťßţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
+ 'DK' => array(1 => '/^[\x{002d}0-9a-zäéöüæøå]{1,63}$/iu'),
+ 'ES' => array(1 => '/^[\x{002d}0-9a-zàáçèéíïñòóúü·]{1,63}$/iu'),
+ 'EU' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9a-zÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋÅÅőœŕŗřśÅšťŧũūŭůűųŵŷźżž]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu',
+ 4 => '/^[\x{002d}0-9a-zÎάέήίΰαβγδεζηθικλμνξοπÏςστυφχψωϊϋόÏÏŽ]{1,63}$/iu',
+ 5 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрÑтуфхцчшщъыьÑÑŽÑ]{1,63}$/iu',
+ 6 => '/^[\x{002d}0-9a-zá¼€-ἇá¼-ἕἠ-ἧἰ-á¼·á½€-á½…á½-á½—á½ -ὧὰ-ὼώᾀ-ᾇá¾-á¾—á¾ -ᾧᾰ-ᾴᾶᾷῂῃῄῆῇá¿-á¿’Îá¿–á¿—á¿ -ῧῲῳῴῶῷ]{1,63}$/iu'),
+ 'FI' => array(1 => '/^[\x{002d}0-9a-zäåö]{1,63}$/iu'),
+ 'GR' => array(1 => '/^[\x{002d}0-9a-zΆΈΉΊΌΎ-ΡΣ-ÏŽá¼€-ἕἘ-á¼á¼ -ὅὈ-á½á½-ὗὙὛá½á½Ÿ-ώᾀ-ᾴᾶ-ᾼῂῃῄῆ-á¿Œá¿-á¿“á¿–-Ίῠ-Ῥῲῳῴῶ-ῼ]{1,63}$/iu'),
+ 'HK' => 'Hostname/Cn.php',
+ 'HU' => array(1 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu'),
+ 'IL' => array(1 => '/^[\x{002d}0-9\x{05D0}-\x{05EA}]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9a-z]{1,63}$/i'),
+ 'INFO'=> array(1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu',
+ 4 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
+ 5 => '/^[\x{002d}0-9a-zÄÄēģīķļņÅŗšūž]{1,63}$/iu',
+ 6 => '/^[\x{002d}0-9a-zÄ…Äėęįšūųž]{1,63}$/iu',
+ 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
+ 8 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu'),
+ 'IO' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąÄćĉÄÄ‹ÄđĕěėęēğÄġģĥħĭĩįīıĵķĺľļłńňņŋÅÅ‘ÅœĸŕřŗśÅšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
+ 'IS' => array(1 => '/^[\x{002d}0-9a-záéýúíóþæöð]{1,63}$/iu'),
+ 'IT' => array(1 => '/^[\x{002d}0-9a-zàâäèéêëìîïòôöùûüæœçÿß-]{1,63}$/iu'),
+ 'JP' => 'Hostname/Jp.php',
+ 'KR' => array(1 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu'),
+ 'LI' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿœ]{1,63}$/iu'),
+ 'LT' => array(1 => '/^[\x{002d}0-9Ä…Äęėįšųūž]{1,63}$/iu'),
+ 'MD' => array(1 => '/^[\x{002d}0-9ăâîşţ]{1,63}$/iu'),
+ 'MUSEUM' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿÄăąćċÄÄđēėęěğġģħīįıķĺļľłńņňŋÅőœŕŗřśşšţťŧūůűųŵŷźżžǎÇÇ’Ç”\x{01E5}\x{01E7}\x{01E9}\x{01EF}É™\x{0292}áºáºƒáº…ỳ]{1,63}$/iu'),
+ 'NET' => 'Hostname/Com.php',
+ 'NO' => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüÄđńŋšŧž]{1,63}$/iu'),
+ 'NU' => 'Hostname/Com.php',
+ 'ORG' => array(1 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu',
+ 4 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
+ 5 => '/^[\x{002d}0-9a-zÄ…Äėęįšūųž]{1,63}$/iu',
+ 6 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
+ 7 => '/^[\x{002d}0-9a-zÄÄēģīķļņÅŗšūž]{1,63}$/iu'),
+ 'PE' => array(1 => '/^[\x{002d}0-9a-zñáéíóúü]{1,63}$/iu'),
+ 'PL' => array(1 => '/^[\x{002d}0-9a-zÄÄēģīķļņÅŗšūž]{1,63}$/iu',
+ 2 => '/^[\x{002d}а-ик-ш\x{0450}ѓѕјљњќџ]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu',
+ 4 => '/^[\x{002d}0-9а-ÑÑ‘\x{04C2}]{1,63}$/iu',
+ 5 => '/^[\x{002d}0-9a-zàáâèéêìíîòóôùúûċġħż]{1,63}$/iu',
+ 6 => '/^[\x{002d}0-9a-zàäåæéêòóôöøü]{1,63}$/iu',
+ 7 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
+ 8 => '/^[\x{002d}0-9a-zàáâãçéêíòóôõúü]{1,63}$/iu',
+ 9 => '/^[\x{002d}0-9a-zâîăşţ]{1,63}$/iu',
+ 10=> '/^[\x{002d}0-9a-záäéíóôúýÄÄĺľňŕšťž]{1,63}$/iu',
+ 11=> '/^[\x{002d}0-9a-zçë]{1,63}$/iu',
+ 12=> '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu',
+ 13=> '/^[\x{002d}0-9a-zćÄđšž]{1,63}$/iu',
+ 14=> '/^[\x{002d}0-9a-zâçöûüğış]{1,63}$/iu',
+ 15=> '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
+ 16=> '/^[\x{002d}0-9a-zäõöüšž]{1,63}$/iu',
+ 17=> '/^[\x{002d}0-9a-zĉÄĥĵÅÅ­]{1,63}$/iu',
+ 18=> '/^[\x{002d}0-9a-zâäéëîô]{1,63}$/iu',
+ 19=> '/^[\x{002d}0-9a-zàáâäåæçèéêëìíîïðñòôöøùúûüýćÄłńřśš]{1,63}$/iu',
+ 20=> '/^[\x{002d}0-9a-zäåæõöøüšž]{1,63}$/iu',
+ 21=> '/^[\x{002d}0-9a-zàáçèéìíòóùú]{1,63}$/iu',
+ 22=> '/^[\x{002d}0-9a-zàáéíóöúüőű]{1,63}$/iu',
+ 23=> '/^[\x{002d}0-9Îά-ÏŽ]{1,63}$/iu',
+ 24=> '/^[\x{002d}0-9a-zàáâåæçèéêëðóôöøüþœ]{1,63}$/iu',
+ 25=> '/^[\x{002d}0-9a-záäéíóöúüýÄÄěňřšťůž]{1,63}$/iu',
+ 26=> '/^[\x{002d}0-9a-z·àçèéíïòóúü]{1,63}$/iu',
+ 27=> '/^[\x{002d}0-9а-ъьюÑ\x{0450}\x{045D}]{1,63}$/iu',
+ 28=> '/^[\x{002d}0-9а-ÑÑ‘Ñ–Ñž]{1,63}$/iu',
+ 29=> '/^[\x{002d}0-9a-zÄ…Äėęįšūųž]{1,63}$/iu',
+ 30=> '/^[\x{002d}0-9a-záäåæéëíðóöøúüýþ]{1,63}$/iu',
+ 31=> '/^[\x{002d}0-9a-zàâæçèéêëîïñôùûüÿœ]{1,63}$/iu',
+ 32=> '/^[\x{002d}0-9а-щъыьÑÑŽÑёєіїґ]{1,63}$/iu',
+ 33=> '/^[\x{002d}0-9×-ת]{1,63}$/iu'),
+ 'PR' => array(1 => '/^[\x{002d}0-9a-záéíóúñäëïüöâêîôûàèùæçœãõ]{1,63}$/iu'),
+ 'PT' => array(1 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu'),
+ 'RS' => array(1 => '/^[\x{002D}\x{0030}-\x{0039}\x{0061}-\x{007A}\x{0107}\x{010D}\x{0111}\x{0161}\x{017E}]{1,63}$/iu)'),
+ 'RU' => array(1 => '/^[\x{002d}0-9а-ÑÑ‘]{1,63}$/iu'),
+ 'SA' => array(1 => '/^[\x{002d}.0-9\x{0621}-\x{063A}\x{0641}-\x{064A}\x{0660}-\x{0669}]{1,63}$/iu'),
+ 'SE' => array(1 => '/^[\x{002d}0-9a-zäåéöü]{1,63}$/iu'),
+ 'SH' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿăąÄćĉÄÄ‹ÄđĕěėęēğÄġģĥħĭĩįīıĵķĺľļłńňņŋÅÅ‘ÅœĸŕřŗśÅšşťţŧŭůűũųūŵŷźžż]{1,63}$/iu'),
+ 'SI' => array(
+ 1 => '/^[\x{002d}0-9a-zà-öø-ÿ]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9a-zÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįıĵķĺļľŀłńņňʼnŋÅÅőœŕŗřśÅšťŧũūŭůűųŵŷźżž]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-zșț]{1,63}$/iu'),
+ 'SJ' => array(1 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüÄđńŋšŧž]{1,63}$/iu'),
+ 'TH' => array(1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'),
+ 'TM' => array(1 => '/^[\x{002d}0-9a-zà-öø-ÿÄăąćĉċÄÄđēėęěÄġģĥħīįĵķĺļľŀłńņňŋőœŕŗřśÅşšţťŧūŭůűųŵŷźżž]{1,63}$/iu'),
+ 'TW' => 'Hostname/Cn.php',
+ 'TR' => array(1 => '/^[\x{002d}0-9a-zğıüşöç]{1,63}$/iu'),
+ 'UA' => array(1 => '/^[\x{002d}0-9a-zабвгдежзийклмнопрÑтуфхцчшщъыьÑÑŽÑÑёђѓєѕіїјљњћќÑўџґӂʼ]{1,63}$/iu'),
+ 'VE' => array(1 => '/^[\x{002d}0-9a-záéíóúüñ]{1,63}$/iu'),
+ 'VN' => array(1 => '/^[ÀÃÂÃÈÉÊÌÃÒÓÔÕÙÚÃàáâãèéêìíòóôõùúýĂăÄđĨĩŨũƠơƯư\x{1EA0}-\x{1EF9}]{1,63}$/iu'),
+ 'мон' => array(1 => '/^[\x{002d}0-9\x{0430}-\x{044F}]{1,63}$/iu'),
+ 'Ñрб' => array(1 => '/^[\x{002d}0-9а-ик-шђјљњћџ]{1,63}$/iu'),
+ 'Ñайт' => array(1 => '/^[\x{002d}0-9а-ÑÑ‘Ñ–Ñ—Ñйўґг]{1,63}$/iu'),
+ 'онлайн' => array(1 => '/^[\x{002d}0-9а-ÑÑ‘Ñ–Ñ—Ñйўґг]{1,63}$/iu'),
+ '中国' => 'Hostname/Cn.php',
+ '中國' => 'Hostname/Cn.php',
+ 'ලංකà·' => array(1 => '/^[\x{0d80}-\x{0dff}]{1,63}$/iu'),
+ '香港' => 'Hostname/Cn.php',
+ 'å°æ¹¾' => 'Hostname/Cn.php',
+ 'å°ç£' => 'Hostname/Cn.php',
+ 'امارات' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ 'الاردن' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ 'السعودية' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ 'ไทย' => array(1 => '/^[\x{002d}0-9a-z\x{0E01}-\x{0E3A}\x{0E40}-\x{0E4D}\x{0E50}-\x{0E59}]{1,63}$/iu'),
+ 'рф' => array(1 => '/^[\x{002d}0-9а-ÑÑ‘]{1,63}$/iu'),
+ 'تونس' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ 'مصر' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ 'இலஙà¯à®•à¯ˆ' => array(1 => '/^[\x{0b80}-\x{0bff}]{1,63}$/iu'),
+ 'Ùلسطين' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ 'شبكة' => array(1 => '/^[\x{0621}-\x{0624}\x{0626}-\x{063A}\x{0641}\x{0642}\x{0644}-\x{0648}\x{067E}\x{0686}\x{0698}\x{06A9}\x{06AF}\x{06CC}\x{06F0}-\x{06F9}]{1,30}$/iu'),
+ );
+
+ protected $_idnLength = array(
+ 'BIZ' => array(5 => 17, 11 => 15, 12 => 20),
+ 'CN' => array(1 => 20),
+ 'COM' => array(3 => 17, 5 => 20),
+ 'HK' => array(1 => 15),
+ 'INFO'=> array(4 => 17),
+ 'KR' => array(1 => 17),
+ 'NET' => array(3 => 17, 5 => 20),
+ 'ORG' => array(6 => 17),
+ 'TW' => array(1 => 20),
+ 'ایران' => array(1 => 30),
+ '中国' => array(1 => 20),
+ 'å…¬å¸' => array(1 => 20),
+ '网络' => array(1 => 20),
+ );
+
+ protected $_options = array(
+ 'allow' => self::ALLOW_DNS,
+ 'idn' => true,
+ 'tld' => true,
+ 'ip' => null
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs
+ * @param array $options Validator options
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['allow'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['idn'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['tld'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['ip'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ $options += $this->_options;
+ $this->setOptions($options);
+ }
+
+ /**
+ * Returns all set options
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets the options for this validator
+ *
+ * @param array $options
+ * @return Zend_Validate_Hostname
+ */
+ public function setOptions($options)
+ {
+ if (array_key_exists('allow', $options)) {
+ $this->setAllow($options['allow']);
+ }
+
+ if (array_key_exists('idn', $options)) {
+ $this->setValidateIdn($options['idn']);
+ }
+
+ if (array_key_exists('tld', $options)) {
+ $this->setValidateTld($options['tld']);
+ }
+
+ if (array_key_exists('ip', $options)) {
+ $this->setIpValidator($options['ip']);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the set ip validator
+ *
+ * @return Zend_Validate_Ip
+ */
+ public function getIpValidator()
+ {
+ return $this->_options['ip'];
+ }
+
+ /**
+ * @param Zend_Validate_Ip $ipValidator OPTIONAL
+ * @return Zend_Validate_Hostname
+ */
+ public function setIpValidator(Zend_Validate_Ip $ipValidator = null)
+ {
+ if ($ipValidator === null) {
+ $ipValidator = new Zend_Validate_Ip();
+ }
+
+ $this->_options['ip'] = $ipValidator;
+ return $this;
+ }
+
+ /**
+ * Returns the allow option
+ *
+ * @return integer
+ */
+ public function getAllow()
+ {
+ return $this->_options['allow'];
+ }
+
+ /**
+ * Sets the allow option
+ *
+ * @param integer $allow
+ * @return Zend_Validate_Hostname Provides a fluent interface
+ */
+ public function setAllow($allow)
+ {
+ $this->_options['allow'] = $allow;
+ return $this;
+ }
+
+ /**
+ * Returns the set idn option
+ *
+ * @return boolean
+ */
+ public function getValidateIdn()
+ {
+ return $this->_options['idn'];
+ }
+
+ /**
+ * Set whether IDN domains are validated
+ *
+ * This only applies when DNS hostnames are validated
+ *
+ * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them
+ * @return $this
+ */
+ public function setValidateIdn ($allowed)
+ {
+ $this->_options['idn'] = (bool) $allowed;
+ return $this;
+ }
+
+ /**
+ * Returns the set tld option
+ *
+ * @return boolean
+ */
+ public function getValidateTld()
+ {
+ return $this->_options['tld'];
+ }
+
+ /**
+ * Set whether the TLD element of a hostname is validated
+ *
+ * This only applies when DNS hostnames are validated
+ *
+ * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them
+ * @return $this
+ */
+ public function setValidateTld ($allowed)
+ {
+ $this->_options['tld'] = (bool) $allowed;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the $value is a valid hostname with respect to the current allow option
+ *
+ * @param string $value
+ * @throws Zend_Validate_Exception if a fatal error occurs for validation process
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ // Check input against IP address schema
+ if (preg_match('/^[0-9a-f:.]*$/i', $value) &&
+ $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
+ if (!($this->_options['allow'] & self::ALLOW_IP)) {
+ $this->_error(self::IP_ADDRESS_NOT_ALLOWED);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ // RFC3986 3.2.2 states:
+ //
+ // The rightmost domain label of a fully qualified domain name
+ // in DNS may be followed by a single "." and should be if it is
+ // necessary to distinguish between the complete domain name and
+ // some local domain.
+ //
+ // (see ZF-6363)
+
+ // Local hostnames are allowed to be partitial (ending '.')
+ if ($this->_options['allow'] & self::ALLOW_LOCAL) {
+ if (substr($value, -1) === '.') {
+ $value = substr($value, 0, -1);
+ if (substr($value, -1) === '.') {
+ // Empty hostnames (ending '..') are not allowed
+ $this->_error(self::INVALID_LOCAL_NAME);
+ return false;
+ }
+ }
+ }
+
+ $domainParts = explode('.', $value);
+
+ // Prevent partitial IP V4 adresses (ending '.')
+ if ((count($domainParts) == 4) && preg_match('/^[0-9.a-e:.]*$/i', $value) &&
+ $this->_options['ip']->setTranslator($this->getTranslator())->isValid($value)) {
+ $this->_error(self::INVALID_LOCAL_NAME);
+ }
+
+ // Check input against DNS hostname schema
+ if ((count($domainParts) > 1) && (strlen($value) >= 4) && (strlen($value) <= 254)) {
+ $status = false;
+
+ $origenc = PHP_VERSION_ID < 50600
+ ? iconv_get_encoding('internal_encoding')
+ : ini_get('default_charset');
+ if (PHP_VERSION_ID < 50600) {
+ iconv_set_encoding('internal_encoding', 'UTF-8');
+ } else {
+ ini_set('default_charset', 'UTF-8');
+ }
+ do {
+ // First check TLD
+ $matches = array();
+ if (preg_match('/([^.]{2,63})$/iu', end($domainParts), $matches)
+ || (array_key_exists(end($domainParts), $this->_validIdns))) {
+ reset($domainParts);
+
+ // Hostname characters are: *(label dot)(label dot label); max 254 chars
+ // label: id-prefix [*ldh{61} id-prefix]; max 63 chars
+ // id-prefix: alpha / digit
+ // ldh: alpha / digit / dash
+
+ // Match TLD against known list
+ $this->_tld = $matches[1];
+ if ($this->_options['tld']) {
+ if (!in_array(strtolower($this->_tld), $this->_validTlds)
+ && !in_array($this->_tld, $this->_validTlds)) {
+ $this->_error(self::UNKNOWN_TLD);
+ $status = false;
+ break;
+ }
+ // We have already validated that the TLD is fine. We don't want it to go through the below
+ // checks as new UTF-8 TLDs will incorrectly fail if there is no IDN regex for it.
+ array_pop($domainParts);
+ }
+
+ /**
+ * Match against IDN hostnames
+ * Note: Keep label regex short to avoid issues with long patterns when matching IDN hostnames
+ * @see Zend_Validate_Hostname_Interface
+ */
+ $regexChars = array(0 => '/^[a-z0-9\x2d]{1,63}$/i');
+ if ($this->_options['idn'] && isset($this->_validIdns[strtoupper($this->_tld)])) {
+ if (is_string($this->_validIdns[strtoupper($this->_tld)])) {
+ $regexChars += include($this->_validIdns[strtoupper($this->_tld)]);
+ } else {
+ $regexChars += $this->_validIdns[strtoupper($this->_tld)];
+ }
+ }
+
+ // Check each hostname part
+ $check = 0;
+ foreach ($domainParts as $domainPart) {
+ // If some domain part is empty (i.e. zend..com), it's invalid
+ if (empty($domainPart) && $domainPart !== '0') {
+ $this->_error(self::INVALID_HOSTNAME);
+ return false;
+ }
+
+ // Decode Punycode domainnames to IDN
+ if (strpos($domainPart, 'xn--') === 0) {
+ $domainPart = $this->decodePunycode(substr($domainPart, 4));
+ if ($domainPart === false) {
+ return false;
+ }
+ }
+
+ // Check dash (-) does not start, end or appear in 3rd and 4th positions
+ if ((strpos($domainPart, '-') === 0)
+ || ((strlen($domainPart) > 2) && (strpos($domainPart, '-', 2) == 2) && (strpos($domainPart, '-', 3) == 3))
+ || (strpos($domainPart, '-') === (strlen($domainPart) - 1))) {
+ $this->_error(self::INVALID_DASH);
+ $status = false;
+ break 2;
+ }
+
+ // Check each domain part
+ $checked = false;
+ foreach($regexChars as $regexKey => $regexChar) {
+ $status = preg_match($regexChar, $domainPart);
+ if ($status > 0) {
+ $length = 63;
+ if (array_key_exists(strtoupper($this->_tld), $this->_idnLength)
+ && (array_key_exists($regexKey, $this->_idnLength[strtoupper($this->_tld)]))) {
+ $length = $this->_idnLength[strtoupper($this->_tld)];
+ }
+
+ if (iconv_strlen($domainPart, 'UTF-8') > $length) {
+ $this->_error(self::INVALID_HOSTNAME);
+ } else {
+ $checked = true;
+ break;
+ }
+ }
+ }
+
+ if ($checked) {
+ ++$check;
+ }
+ }
+
+ // If one of the labels doesn't match, the hostname is invalid
+ if ($check !== count($domainParts)) {
+ $this->_error(self::INVALID_HOSTNAME_SCHEMA);
+ $status = false;
+ }
+ } else {
+ // Hostname not long enough
+ $this->_error(self::UNDECIPHERABLE_TLD);
+ $status = false;
+ }
+ } while (false);
+
+ if (PHP_VERSION_ID < 50600) {
+ iconv_set_encoding('internal_encoding', $origenc);
+ } else {
+ ini_set('default_charset', $origenc);
+ }
+ // If the input passes as an Internet domain name, and domain names are allowed, then the hostname
+ // passes validation
+ if ($status && ($this->_options['allow'] & self::ALLOW_DNS)) {
+ return true;
+ }
+ } else if ($this->_options['allow'] & self::ALLOW_DNS) {
+ $this->_error(self::INVALID_HOSTNAME);
+ }
+
+ // Check for URI Syntax (RFC3986)
+ if ($this->_options['allow'] & self::ALLOW_URI) {
+ if (preg_match("/^([a-zA-Z0-9-._~!$&\'()*+,;=]|%[[:xdigit:]]{2}){1,254}$/i", $value)) {
+ return true;
+ } else {
+ $this->_error(self::INVALID_URI);
+ }
+ }
+
+ // Check input against local network name schema; last chance to pass validation
+ $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}[\x2e]{0,1}){1,254}$/';
+ $status = @preg_match($regexLocal, $value);
+
+ // If the input passes as a local network name, and local network names are allowed, then the
+ // hostname passes validation
+ $allowLocal = $this->_options['allow'] & self::ALLOW_LOCAL;
+ if ($status && $allowLocal) {
+ return true;
+ }
+
+ // If the input does not pass as a local network name, add a message
+ if (!$status) {
+ $this->_error(self::INVALID_LOCAL_NAME);
+ }
+
+ // If local network names are not allowed, add a message
+ if ($status && !$allowLocal) {
+ $this->_error(self::LOCAL_NAME_NOT_ALLOWED);
+ }
+
+ return false;
+ }
+
+ /**
+ * Decodes a punycode encoded string to it's original utf8 string
+ * In case of a decoding failure the original string is returned
+ *
+ * @param string $encoded Punycode encoded string to decode
+ * @return string
+ */
+ protected function decodePunycode($encoded)
+ {
+ if (!preg_match('/^[a-z0-9-]+$/i', $encoded)) {
+ // no punycode encoded string
+ $this->_error(self::CANNOT_DECODE_PUNYCODE);
+ return false;
+ }
+
+ $decoded = array();
+ $separator = strrpos($encoded, '-');
+ if ($separator > 0) {
+ for ($x = 0; $x < $separator; ++$x) {
+ // prepare decoding matrix
+ $decoded[] = ord($encoded[$x]);
+ }
+ }
+
+ $lengthd = count($decoded);
+ $lengthe = strlen($encoded);
+
+ // decoding
+ $init = true;
+ $base = 72;
+ $index = 0;
+ $char = 0x80;
+
+ for ($indexe = ($separator) ? ($separator + 1) : 0; $indexe < $lengthe; ++$lengthd) {
+ for ($old_index = $index, $pos = 1, $key = 36; 1 ; $key += 36) {
+ $hex = ord($encoded[$indexe++]);
+ $digit = ($hex - 48 < 10) ? $hex - 22
+ : (($hex - 65 < 26) ? $hex - 65
+ : (($hex - 97 < 26) ? $hex - 97
+ : 36));
+
+ $index += $digit * $pos;
+ $tag = ($key <= $base) ? 1 : (($key >= $base + 26) ? 26 : ($key - $base));
+ if ($digit < $tag) {
+ break;
+ }
+
+ $pos = (int) ($pos * (36 - $tag));
+ }
+
+ $delta = intval($init ? (($index - $old_index) / 700) : (($index - $old_index) / 2));
+ $delta += intval($delta / ($lengthd + 1));
+ for ($key = 0; $delta > 910 / 2; $key += 36) {
+ $delta = intval($delta / 35);
+ }
+
+ $base = intval($key + 36 * $delta / ($delta + 38));
+ $init = false;
+ $char += (int) ($index / ($lengthd + 1));
+ $index %= ($lengthd + 1);
+ if ($lengthd > 0) {
+ for ($i = $lengthd; $i > $index; $i--) {
+ $decoded[$i] = $decoded[($i - 1)];
+ }
+ }
+
+ $decoded[$index++] = $char;
+ }
+
+ // convert decoded ucs4 to utf8 string
+ foreach ($decoded as $key => $value) {
+ if ($value < 128) {
+ $decoded[$key] = chr($value);
+ } elseif ($value < (1 << 11)) {
+ $decoded[$key] = chr(192 + ($value >> 6));
+ $decoded[$key] .= chr(128 + ($value & 63));
+ } elseif ($value < (1 << 16)) {
+ $decoded[$key] = chr(224 + ($value >> 12));
+ $decoded[$key] .= chr(128 + (($value >> 6) & 63));
+ $decoded[$key] .= chr(128 + ($value & 63));
+ } elseif ($value < (1 << 21)) {
+ $decoded[$key] = chr(240 + ($value >> 18));
+ $decoded[$key] .= chr(128 + (($value >> 12) & 63));
+ $decoded[$key] .= chr(128 + (($value >> 6) & 63));
+ $decoded[$key] .= chr(128 + ($value & 63));
+ } else {
+ $this->_error(self::CANNOT_DECODE_PUNYCODE);
+ return false;
+ }
+ }
+
+ return implode($decoded);
+ }
+}
diff --git a/library/vendor/Zend/Validate/Hostname/Biz.php b/library/vendor/Zend/Validate/Hostname/Biz.php
new file mode 100644
index 0000000..eb1bea2
--- /dev/null
+++ b/library/vendor/Zend/Validate/Hostname/Biz.php
@@ -0,0 +1,2917 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Ressource file for biz idn validation
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+return array(
+ 1 => '/^[\x{002d}0-9a-zäåæéöøü]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9a-záéíñóúü]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-záéíóöúüőű]{1,63}$/iu',
+ 4 => '/^[\x{002d}0-9a-záæéíðóöúýþ]{1,63}$/iu',
+ 5 => '/^[\x{AC00}-\x{D7A3}]{1,17}$/iu',
+ 6 => '/^[\x{002d}0-9a-zÄ…Äėęįšūųž]{1,63}$/iu',
+ 7 => '/^[\x{002d}0-9a-zÄÄēģīķļņÅŗšūž]{1,63}$/iu',
+ 8 => '/^[\x{002d}0-9a-zàáä-éêñ-ôöøüÄđńŋšŧž]{1,63}$/iu',
+ 9 => '/^[\x{002d}0-9a-zóąćęłńśźż]{1,63}$/iu',
+ 10 => '/^[\x{002d}0-9a-záàâãçéêíóôõú]{1,63}$/iu',
+ 11 => '/^[\x{002d}0-9a-z\x{3005}-\x{3007}\x{3041}-\x{3093}\x{309D}\x{309E}\x{30A1}-\x{30F6}\x{30FC}' .
+'\x{30FD}\x{30FE}\x{4E00}\x{4E01}\x{4E03}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' .
+'\x{4E0B}\x{4E0D}\x{4E0E}\x{4E10}\x{4E11}\x{4E14}\x{4E15}\x{4E16}\x{4E17}' .
+'\x{4E18}\x{4E19}\x{4E1E}\x{4E21}\x{4E26}\x{4E2A}\x{4E2D}\x{4E31}\x{4E32}' .
+'\x{4E36}\x{4E38}\x{4E39}\x{4E3B}\x{4E3C}\x{4E3F}\x{4E42}\x{4E43}\x{4E45}' .
+'\x{4E4B}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E55}\x{4E56}\x{4E57}\x{4E58}\x{4E59}' .
+'\x{4E5D}\x{4E5E}\x{4E5F}\x{4E62}\x{4E71}\x{4E73}\x{4E7E}\x{4E80}\x{4E82}' .
+'\x{4E85}\x{4E86}\x{4E88}\x{4E89}\x{4E8A}\x{4E8B}\x{4E8C}\x{4E8E}\x{4E91}' .
+'\x{4E92}\x{4E94}\x{4E95}\x{4E98}\x{4E99}\x{4E9B}\x{4E9C}\x{4E9E}\x{4E9F}' .
+'\x{4EA0}\x{4EA1}\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA8}\x{4EAB}\x{4EAC}' .
+'\x{4EAD}\x{4EAE}\x{4EB0}\x{4EB3}\x{4EB6}\x{4EBA}\x{4EC0}\x{4EC1}\x{4EC2}' .
+'\x{4EC4}\x{4EC6}\x{4EC7}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED4}' .
+'\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE3}' .
+'\x{4EE4}\x{4EE5}\x{4EED}\x{4EEE}\x{4EF0}\x{4EF2}\x{4EF6}\x{4EF7}\x{4EFB}' .
+'\x{4F01}\x{4F09}\x{4F0A}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F1A}' .
+'\x{4F1C}\x{4F1D}\x{4F2F}\x{4F30}\x{4F34}\x{4F36}\x{4F38}\x{4F3A}\x{4F3C}' .
+'\x{4F3D}\x{4F43}\x{4F46}\x{4F47}\x{4F4D}\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}' .
+'\x{4F53}\x{4F55}\x{4F57}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}' .
+'\x{4F69}\x{4F6F}\x{4F70}\x{4F73}\x{4F75}\x{4F76}\x{4F7B}\x{4F7C}\x{4F7F}' .
+'\x{4F83}\x{4F86}\x{4F88}\x{4F8B}\x{4F8D}\x{4F8F}\x{4F91}\x{4F96}\x{4F98}' .
+'\x{4F9B}\x{4F9D}\x{4FA0}\x{4FA1}\x{4FAB}\x{4FAD}\x{4FAE}\x{4FAF}\x{4FB5}' .
+'\x{4FB6}\x{4FBF}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FCA}\x{4FCE}\x{4FD0}\x{4FD1}' .
+'\x{4FD4}\x{4FD7}\x{4FD8}\x{4FDA}\x{4FDB}\x{4FDD}\x{4FDF}\x{4FE1}\x{4FE3}' .
+'\x{4FE4}\x{4FE5}\x{4FEE}\x{4FEF}\x{4FF3}\x{4FF5}\x{4FF6}\x{4FF8}\x{4FFA}' .
+'\x{4FFE}\x{5005}\x{5006}\x{5009}\x{500B}\x{500D}\x{500F}\x{5011}\x{5012}' .
+'\x{5014}\x{5016}\x{5019}\x{501A}\x{501F}\x{5021}\x{5023}\x{5024}\x{5025}' .
+'\x{5026}\x{5028}\x{5029}\x{502A}\x{502B}\x{502C}\x{502D}\x{5036}\x{5039}' .
+'\x{5043}\x{5047}\x{5048}\x{5049}\x{504F}\x{5050}\x{5055}\x{5056}\x{505A}' .
+'\x{505C}\x{5065}\x{506C}\x{5072}\x{5074}\x{5075}\x{5076}\x{5078}\x{507D}' .
+'\x{5080}\x{5085}\x{508D}\x{5091}\x{5098}\x{5099}\x{509A}\x{50AC}\x{50AD}' .
+'\x{50B2}\x{50B3}\x{50B4}\x{50B5}\x{50B7}\x{50BE}\x{50C2}\x{50C5}\x{50C9}' .
+'\x{50CA}\x{50CD}\x{50CF}\x{50D1}\x{50D5}\x{50D6}\x{50DA}\x{50DE}\x{50E3}' .
+'\x{50E5}\x{50E7}\x{50ED}\x{50EE}\x{50F5}\x{50F9}\x{50FB}\x{5100}\x{5101}' .
+'\x{5102}\x{5104}\x{5109}\x{5112}\x{5114}\x{5115}\x{5116}\x{5118}\x{511A}' .
+'\x{511F}\x{5121}\x{512A}\x{5132}\x{5137}\x{513A}\x{513B}\x{513C}\x{513F}' .
+'\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' .
+'\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5152}\x{5154}\x{515A}\x{515C}' .
+'\x{5162}\x{5165}\x{5168}\x{5169}\x{516A}\x{516B}\x{516C}\x{516D}\x{516E}' .
+'\x{5171}\x{5175}\x{5176}\x{5177}\x{5178}\x{517C}\x{5180}\x{5182}\x{5185}' .
+'\x{5186}\x{5189}\x{518A}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}' .
+'\x{5193}\x{5195}\x{5196}\x{5197}\x{5199}\x{51A0}\x{51A2}\x{51A4}\x{51A5}' .
+'\x{51A6}\x{51A8}\x{51A9}\x{51AA}\x{51AB}\x{51AC}\x{51B0}\x{51B1}\x{51B2}' .
+'\x{51B3}\x{51B4}\x{51B5}\x{51B6}\x{51B7}\x{51BD}\x{51C4}\x{51C5}\x{51C6}' .
+'\x{51C9}\x{51CB}\x{51CC}\x{51CD}\x{51D6}\x{51DB}\x{51DC}\x{51DD}\x{51E0}' .
+'\x{51E1}\x{51E6}\x{51E7}\x{51E9}\x{51EA}\x{51ED}\x{51F0}\x{51F1}\x{51F5}' .
+'\x{51F6}\x{51F8}\x{51F9}\x{51FA}\x{51FD}\x{51FE}\x{5200}\x{5203}\x{5204}' .
+'\x{5206}\x{5207}\x{5208}\x{520A}\x{520B}\x{520E}\x{5211}\x{5214}\x{5217}' .
+'\x{521D}\x{5224}\x{5225}\x{5227}\x{5229}\x{522A}\x{522E}\x{5230}\x{5233}' .
+'\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{5243}\x{5244}\x{5247}' .
+'\x{524A}\x{524B}\x{524C}\x{524D}\x{524F}\x{5254}\x{5256}\x{525B}\x{525E}' .
+'\x{5263}\x{5264}\x{5265}\x{5269}\x{526A}\x{526F}\x{5270}\x{5271}\x{5272}' .
+'\x{5273}\x{5274}\x{5275}\x{527D}\x{527F}\x{5283}\x{5287}\x{5288}\x{5289}' .
+'\x{528D}\x{5291}\x{5292}\x{5294}\x{529B}\x{529F}\x{52A0}\x{52A3}\x{52A9}' .
+'\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52B1}\x{52B4}\x{52B5}\x{52B9}\x{52BC}' .
+'\x{52BE}\x{52C1}\x{52C3}\x{52C5}\x{52C7}\x{52C9}\x{52CD}\x{52D2}\x{52D5}' .
+'\x{52D7}\x{52D8}\x{52D9}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}' .
+'\x{52E4}\x{52E6}\x{52E7}\x{52F2}\x{52F3}\x{52F5}\x{52F8}\x{52F9}\x{52FA}' .
+'\x{52FE}\x{52FF}\x{5301}\x{5302}\x{5305}\x{5306}\x{5308}\x{530D}\x{530F}' .
+'\x{5310}\x{5315}\x{5316}\x{5317}\x{5319}\x{531A}\x{531D}\x{5320}\x{5321}' .
+'\x{5323}\x{532A}\x{532F}\x{5331}\x{5333}\x{5338}\x{5339}\x{533A}\x{533B}' .
+'\x{533F}\x{5340}\x{5341}\x{5343}\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}' .
+'\x{534A}\x{534D}\x{5351}\x{5352}\x{5353}\x{5354}\x{5357}\x{5358}\x{535A}' .
+'\x{535C}\x{535E}\x{5360}\x{5366}\x{5369}\x{536E}\x{536F}\x{5370}\x{5371}' .
+'\x{5373}\x{5374}\x{5375}\x{5377}\x{5378}\x{537B}\x{537F}\x{5382}\x{5384}' .
+'\x{5396}\x{5398}\x{539A}\x{539F}\x{53A0}\x{53A5}\x{53A6}\x{53A8}\x{53A9}' .
+'\x{53AD}\x{53AE}\x{53B0}\x{53B3}\x{53B6}\x{53BB}\x{53C2}\x{53C3}\x{53C8}' .
+'\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}\x{53D4}\x{53D6}\x{53D7}' .
+'\x{53D9}\x{53DB}\x{53DF}\x{53E1}\x{53E2}\x{53E3}\x{53E4}\x{53E5}\x{53E8}' .
+'\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}\x{53EF}\x{53F0}\x{53F1}' .
+'\x{53F2}\x{53F3}\x{53F6}\x{53F7}\x{53F8}\x{53FA}\x{5401}\x{5403}\x{5404}' .
+'\x{5408}\x{5409}\x{540A}\x{540B}\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}' .
+'\x{5411}\x{541B}\x{541D}\x{541F}\x{5420}\x{5426}\x{5429}\x{542B}\x{542C}' .
+'\x{542D}\x{542E}\x{5436}\x{5438}\x{5439}\x{543B}\x{543C}\x{543D}\x{543E}' .
+'\x{5440}\x{5442}\x{5446}\x{5448}\x{5449}\x{544A}\x{544E}\x{5451}\x{545F}' .
+'\x{5468}\x{546A}\x{5470}\x{5471}\x{5473}\x{5475}\x{5476}\x{5477}\x{547B}' .
+'\x{547C}\x{547D}\x{5480}\x{5484}\x{5486}\x{548B}\x{548C}\x{548E}\x{548F}' .
+'\x{5490}\x{5492}\x{54A2}\x{54A4}\x{54A5}\x{54A8}\x{54AB}\x{54AC}\x{54AF}' .
+'\x{54B2}\x{54B3}\x{54B8}\x{54BC}\x{54BD}\x{54BE}\x{54C0}\x{54C1}\x{54C2}' .
+'\x{54C4}\x{54C7}\x{54C8}\x{54C9}\x{54D8}\x{54E1}\x{54E2}\x{54E5}\x{54E6}' .
+'\x{54E8}\x{54E9}\x{54ED}\x{54EE}\x{54F2}\x{54FA}\x{54FD}\x{5504}\x{5506}' .
+'\x{5507}\x{550F}\x{5510}\x{5514}\x{5516}\x{552E}\x{552F}\x{5531}\x{5533}' .
+'\x{5538}\x{5539}\x{553E}\x{5540}\x{5544}\x{5545}\x{5546}\x{554C}\x{554F}' .
+'\x{5553}\x{5556}\x{5557}\x{555C}\x{555D}\x{5563}\x{557B}\x{557C}\x{557E}' .
+'\x{5580}\x{5583}\x{5584}\x{5587}\x{5589}\x{558A}\x{558B}\x{5598}\x{5599}' .
+'\x{559A}\x{559C}\x{559D}\x{559E}\x{559F}\x{55A7}\x{55A8}\x{55A9}\x{55AA}' .
+'\x{55AB}\x{55AC}\x{55AE}\x{55B0}\x{55B6}\x{55C4}\x{55C5}\x{55C7}\x{55D4}' .
+'\x{55DA}\x{55DC}\x{55DF}\x{55E3}\x{55E4}\x{55F7}\x{55F9}\x{55FD}\x{55FE}' .
+'\x{5606}\x{5609}\x{5614}\x{5616}\x{5617}\x{5618}\x{561B}\x{5629}\x{562F}' .
+'\x{5631}\x{5632}\x{5634}\x{5636}\x{5638}\x{5642}\x{564C}\x{564E}\x{5650}' .
+'\x{565B}\x{5664}\x{5668}\x{566A}\x{566B}\x{566C}\x{5674}\x{5678}\x{567A}' .
+'\x{5680}\x{5686}\x{5687}\x{568A}\x{568F}\x{5694}\x{56A0}\x{56A2}\x{56A5}' .
+'\x{56AE}\x{56B4}\x{56B6}\x{56BC}\x{56C0}\x{56C1}\x{56C2}\x{56C3}\x{56C8}' .
+'\x{56CE}\x{56D1}\x{56D3}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DE}\x{56E0}' .
+'\x{56E3}\x{56EE}\x{56F0}\x{56F2}\x{56F3}\x{56F9}\x{56FA}\x{56FD}\x{56FF}' .
+'\x{5700}\x{5703}\x{5704}\x{5708}\x{5709}\x{570B}\x{570D}\x{570F}\x{5712}' .
+'\x{5713}\x{5716}\x{5718}\x{571C}\x{571F}\x{5726}\x{5727}\x{5728}\x{572D}' .
+'\x{5730}\x{5737}\x{5738}\x{573B}\x{5740}\x{5742}\x{5747}\x{574A}\x{574E}' .
+'\x{574F}\x{5750}\x{5751}\x{5761}\x{5764}\x{5766}\x{5769}\x{576A}\x{577F}' .
+'\x{5782}\x{5788}\x{5789}\x{578B}\x{5793}\x{57A0}\x{57A2}\x{57A3}\x{57A4}' .
+'\x{57AA}\x{57B0}\x{57B3}\x{57C0}\x{57C3}\x{57C6}\x{57CB}\x{57CE}\x{57D2}' .
+'\x{57D3}\x{57D4}\x{57D6}\x{57DC}\x{57DF}\x{57E0}\x{57E3}\x{57F4}\x{57F7}' .
+'\x{57F9}\x{57FA}\x{57FC}\x{5800}\x{5802}\x{5805}\x{5806}\x{580A}\x{580B}' .
+'\x{5815}\x{5819}\x{581D}\x{5821}\x{5824}\x{582A}\x{582F}\x{5830}\x{5831}' .
+'\x{5834}\x{5835}\x{583A}\x{583D}\x{5840}\x{5841}\x{584A}\x{584B}\x{5851}' .
+'\x{5852}\x{5854}\x{5857}\x{5858}\x{5859}\x{585A}\x{585E}\x{5862}\x{5869}' .
+'\x{586B}\x{5870}\x{5872}\x{5875}\x{5879}\x{587E}\x{5883}\x{5885}\x{5893}' .
+'\x{5897}\x{589C}\x{589F}\x{58A8}\x{58AB}\x{58AE}\x{58B3}\x{58B8}\x{58B9}' .
+'\x{58BA}\x{58BB}\x{58BE}\x{58C1}\x{58C5}\x{58C7}\x{58CA}\x{58CC}\x{58D1}' .
+'\x{58D3}\x{58D5}\x{58D7}\x{58D8}\x{58D9}\x{58DC}\x{58DE}\x{58DF}\x{58E4}' .
+'\x{58E5}\x{58EB}\x{58EC}\x{58EE}\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F7}' .
+'\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{5902}\x{5909}\x{590A}\x{590F}' .
+'\x{5910}\x{5915}\x{5916}\x{5918}\x{5919}\x{591A}\x{591B}\x{591C}\x{5922}' .
+'\x{5925}\x{5927}\x{5929}\x{592A}\x{592B}\x{592C}\x{592D}\x{592E}\x{5931}' .
+'\x{5932}\x{5937}\x{5938}\x{593E}\x{5944}\x{5947}\x{5948}\x{5949}\x{594E}' .
+'\x{594F}\x{5950}\x{5951}\x{5954}\x{5955}\x{5957}\x{5958}\x{595A}\x{5960}' .
+'\x{5962}\x{5965}\x{5967}\x{5968}\x{5969}\x{596A}\x{596C}\x{596E}\x{5973}' .
+'\x{5974}\x{5978}\x{597D}\x{5981}\x{5982}\x{5983}\x{5984}\x{598A}\x{598D}' .
+'\x{5993}\x{5996}\x{5999}\x{599B}\x{599D}\x{59A3}\x{59A5}\x{59A8}\x{59AC}' .
+'\x{59B2}\x{59B9}\x{59BB}\x{59BE}\x{59C6}\x{59C9}\x{59CB}\x{59D0}\x{59D1}' .
+'\x{59D3}\x{59D4}\x{59D9}\x{59DA}\x{59DC}\x{59E5}\x{59E6}\x{59E8}\x{59EA}' .
+'\x{59EB}\x{59F6}\x{59FB}\x{59FF}\x{5A01}\x{5A03}\x{5A09}\x{5A11}\x{5A18}' .
+'\x{5A1A}\x{5A1C}\x{5A1F}\x{5A20}\x{5A25}\x{5A29}\x{5A2F}\x{5A35}\x{5A36}' .
+'\x{5A3C}\x{5A40}\x{5A41}\x{5A46}\x{5A49}\x{5A5A}\x{5A62}\x{5A66}\x{5A6A}' .
+'\x{5A6C}\x{5A7F}\x{5A92}\x{5A9A}\x{5A9B}\x{5ABC}\x{5ABD}\x{5ABE}\x{5AC1}' .
+'\x{5AC2}\x{5AC9}\x{5ACB}\x{5ACC}\x{5AD0}\x{5AD6}\x{5AD7}\x{5AE1}\x{5AE3}' .
+'\x{5AE6}\x{5AE9}\x{5AFA}\x{5AFB}\x{5B09}\x{5B0B}\x{5B0C}\x{5B16}\x{5B22}' .
+'\x{5B2A}\x{5B2C}\x{5B30}\x{5B32}\x{5B36}\x{5B3E}\x{5B40}\x{5B43}\x{5B45}' .
+'\x{5B50}\x{5B51}\x{5B54}\x{5B55}\x{5B57}\x{5B58}\x{5B5A}\x{5B5B}\x{5B5C}' .
+'\x{5B5D}\x{5B5F}\x{5B63}\x{5B64}\x{5B65}\x{5B66}\x{5B69}\x{5B6B}\x{5B70}' .
+'\x{5B71}\x{5B73}\x{5B75}\x{5B78}\x{5B7A}\x{5B80}\x{5B83}\x{5B85}\x{5B87}' .
+'\x{5B88}\x{5B89}\x{5B8B}\x{5B8C}\x{5B8D}\x{5B8F}\x{5B95}\x{5B97}\x{5B98}' .
+'\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9F}\x{5BA2}\x{5BA3}\x{5BA4}' .
+'\x{5BA5}\x{5BA6}\x{5BAE}\x{5BB0}\x{5BB3}\x{5BB4}\x{5BB5}\x{5BB6}\x{5BB8}' .
+'\x{5BB9}\x{5BBF}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BC9}' .
+'\x{5BCC}\x{5BD0}\x{5BD2}\x{5BD3}\x{5BD4}\x{5BDB}\x{5BDD}\x{5BDE}\x{5BDF}' .
+'\x{5BE1}\x{5BE2}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}\x{5BE8}\x{5BE9}\x{5BEB}' .
+'\x{5BEE}\x{5BF0}\x{5BF3}\x{5BF5}\x{5BF6}\x{5BF8}\x{5BFA}\x{5BFE}\x{5BFF}' .
+'\x{5C01}\x{5C02}\x{5C04}\x{5C05}\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}' .
+'\x{5C0B}\x{5C0D}\x{5C0E}\x{5C0F}\x{5C11}\x{5C13}\x{5C16}\x{5C1A}\x{5C20}' .
+'\x{5C22}\x{5C24}\x{5C28}\x{5C2D}\x{5C31}\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}' .
+'\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}\x{5C41}\x{5C45}\x{5C46}\x{5C48}' .
+'\x{5C4A}\x{5C4B}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C53}\x{5C55}' .
+'\x{5C5E}\x{5C60}\x{5C61}\x{5C64}\x{5C65}\x{5C6C}\x{5C6E}\x{5C6F}\x{5C71}' .
+'\x{5C76}\x{5C79}\x{5C8C}\x{5C90}\x{5C91}\x{5C94}\x{5CA1}\x{5CA8}\x{5CA9}' .
+'\x{5CAB}\x{5CAC}\x{5CB1}\x{5CB3}\x{5CB6}\x{5CB7}\x{5CB8}\x{5CBB}\x{5CBC}' .
+'\x{5CBE}\x{5CC5}\x{5CC7}\x{5CD9}\x{5CE0}\x{5CE1}\x{5CE8}\x{5CE9}\x{5CEA}' .
+'\x{5CED}\x{5CEF}\x{5CF0}\x{5CF6}\x{5CFA}\x{5CFB}\x{5CFD}\x{5D07}\x{5D0B}' .
+'\x{5D0E}\x{5D11}\x{5D14}\x{5D15}\x{5D16}\x{5D17}\x{5D18}\x{5D19}\x{5D1A}' .
+'\x{5D1B}\x{5D1F}\x{5D22}\x{5D29}\x{5D4B}\x{5D4C}\x{5D4E}\x{5D50}\x{5D52}' .
+'\x{5D5C}\x{5D69}\x{5D6C}\x{5D6F}\x{5D73}\x{5D76}\x{5D82}\x{5D84}\x{5D87}' .
+'\x{5D8B}\x{5D8C}\x{5D90}\x{5D9D}\x{5DA2}\x{5DAC}\x{5DAE}\x{5DB7}\x{5DBA}' .
+'\x{5DBC}\x{5DBD}\x{5DC9}\x{5DCC}\x{5DCD}\x{5DD2}\x{5DD3}\x{5DD6}\x{5DDB}' .
+'\x{5DDD}\x{5DDE}\x{5DE1}\x{5DE3}\x{5DE5}\x{5DE6}\x{5DE7}\x{5DE8}\x{5DEB}' .
+'\x{5DEE}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DFB}\x{5DFD}' .
+'\x{5DFE}\x{5E02}\x{5E03}\x{5E06}\x{5E0B}\x{5E0C}\x{5E11}\x{5E16}\x{5E19}' .
+'\x{5E1A}\x{5E1B}\x{5E1D}\x{5E25}\x{5E2B}\x{5E2D}\x{5E2F}\x{5E30}\x{5E33}' .
+'\x{5E36}\x{5E37}\x{5E38}\x{5E3D}\x{5E40}\x{5E43}\x{5E44}\x{5E45}\x{5E47}' .
+'\x{5E4C}\x{5E4E}\x{5E54}\x{5E55}\x{5E57}\x{5E5F}\x{5E61}\x{5E62}\x{5E63}' .
+'\x{5E64}\x{5E72}\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E78}\x{5E79}\x{5E7A}' .
+'\x{5E7B}\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E81}\x{5E83}\x{5E84}\x{5E87}' .
+'\x{5E8A}\x{5E8F}\x{5E95}\x{5E96}\x{5E97}\x{5E9A}\x{5E9C}\x{5EA0}\x{5EA6}' .
+'\x{5EA7}\x{5EAB}\x{5EAD}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EC1}\x{5EC2}' .
+'\x{5EC3}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECF}\x{5ED0}\x{5ED3}\x{5ED6}\x{5EDA}' .
+'\x{5EDB}\x{5EDD}\x{5EDF}\x{5EE0}\x{5EE1}\x{5EE2}\x{5EE3}\x{5EE8}\x{5EE9}' .
+'\x{5EEC}\x{5EF0}\x{5EF1}\x{5EF3}\x{5EF4}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}' .
+'\x{5EFB}\x{5EFC}\x{5EFE}\x{5EFF}\x{5F01}\x{5F03}\x{5F04}\x{5F09}\x{5F0A}' .
+'\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F10}\x{5F11}\x{5F13}\x{5F14}\x{5F15}' .
+'\x{5F16}\x{5F17}\x{5F18}\x{5F1B}\x{5F1F}\x{5F25}\x{5F26}\x{5F27}\x{5F29}' .
+'\x{5F2D}\x{5F2F}\x{5F31}\x{5F35}\x{5F37}\x{5F38}\x{5F3C}\x{5F3E}\x{5F41}' .
+'\x{5F48}\x{5F4A}\x{5F4C}\x{5F4E}\x{5F51}\x{5F53}\x{5F56}\x{5F57}\x{5F59}' .
+'\x{5F5C}\x{5F5D}\x{5F61}\x{5F62}\x{5F66}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}' .
+'\x{5F6D}\x{5F70}\x{5F71}\x{5F73}\x{5F77}\x{5F79}\x{5F7C}\x{5F7F}\x{5F80}' .
+'\x{5F81}\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F87}\x{5F88}\x{5F8A}\x{5F8B}' .
+'\x{5F8C}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F97}\x{5F98}\x{5F99}\x{5F9E}' .
+'\x{5FA0}\x{5FA1}\x{5FA8}\x{5FA9}\x{5FAA}\x{5FAD}\x{5FAE}\x{5FB3}\x{5FB4}' .
+'\x{5FB9}\x{5FBC}\x{5FBD}\x{5FC3}\x{5FC5}\x{5FCC}\x{5FCD}\x{5FD6}\x{5FD7}' .
+'\x{5FD8}\x{5FD9}\x{5FDC}\x{5FDD}\x{5FE0}\x{5FE4}\x{5FEB}\x{5FF0}\x{5FF1}' .
+'\x{5FF5}\x{5FF8}\x{5FFB}\x{5FFD}\x{5FFF}\x{600E}\x{600F}\x{6010}\x{6012}' .
+'\x{6015}\x{6016}\x{6019}\x{601B}\x{601C}\x{601D}\x{6020}\x{6021}\x{6025}' .
+'\x{6026}\x{6027}\x{6028}\x{6029}\x{602A}\x{602B}\x{602F}\x{6031}\x{603A}' .
+'\x{6041}\x{6042}\x{6043}\x{6046}\x{604A}\x{604B}\x{604D}\x{6050}\x{6052}' .
+'\x{6055}\x{6059}\x{605A}\x{605F}\x{6060}\x{6062}\x{6063}\x{6064}\x{6065}' .
+'\x{6068}\x{6069}\x{606A}\x{606B}\x{606C}\x{606D}\x{606F}\x{6070}\x{6075}' .
+'\x{6077}\x{6081}\x{6083}\x{6084}\x{6089}\x{608B}\x{608C}\x{608D}\x{6092}' .
+'\x{6094}\x{6096}\x{6097}\x{609A}\x{609B}\x{609F}\x{60A0}\x{60A3}\x{60A6}' .
+'\x{60A7}\x{60A9}\x{60AA}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B8}' .
+'\x{60BC}\x{60BD}\x{60C5}\x{60C6}\x{60C7}\x{60D1}\x{60D3}\x{60D8}\x{60DA}' .
+'\x{60DC}\x{60DF}\x{60E0}\x{60E1}\x{60E3}\x{60E7}\x{60E8}\x{60F0}\x{60F1}' .
+'\x{60F3}\x{60F4}\x{60F6}\x{60F7}\x{60F9}\x{60FA}\x{60FB}\x{6100}\x{6101}' .
+'\x{6103}\x{6106}\x{6108}\x{6109}\x{610D}\x{610E}\x{610F}\x{6115}\x{611A}' .
+'\x{611B}\x{611F}\x{6121}\x{6127}\x{6128}\x{612C}\x{6134}\x{613C}\x{613D}' .
+'\x{613E}\x{613F}\x{6142}\x{6144}\x{6147}\x{6148}\x{614A}\x{614B}\x{614C}' .
+'\x{614D}\x{614E}\x{6153}\x{6155}\x{6158}\x{6159}\x{615A}\x{615D}\x{615F}' .
+'\x{6162}\x{6163}\x{6165}\x{6167}\x{6168}\x{616B}\x{616E}\x{616F}\x{6170}' .
+'\x{6171}\x{6173}\x{6174}\x{6175}\x{6176}\x{6177}\x{617E}\x{6182}\x{6187}' .
+'\x{618A}\x{618E}\x{6190}\x{6191}\x{6194}\x{6196}\x{6199}\x{619A}\x{61A4}' .
+'\x{61A7}\x{61A9}\x{61AB}\x{61AC}\x{61AE}\x{61B2}\x{61B6}\x{61BA}\x{61BE}' .
+'\x{61C3}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' .
+'\x{61D0}\x{61E3}\x{61E6}\x{61F2}\x{61F4}\x{61F6}\x{61F7}\x{61F8}\x{61FA}' .
+'\x{61FC}\x{61FD}\x{61FE}\x{61FF}\x{6200}\x{6208}\x{6209}\x{620A}\x{620C}' .
+'\x{620D}\x{620E}\x{6210}\x{6211}\x{6212}\x{6214}\x{6216}\x{621A}\x{621B}' .
+'\x{621D}\x{621E}\x{621F}\x{6221}\x{6226}\x{622A}\x{622E}\x{622F}\x{6230}' .
+'\x{6232}\x{6233}\x{6234}\x{6238}\x{623B}\x{623F}\x{6240}\x{6241}\x{6247}' .
+'\x{6248}\x{6249}\x{624B}\x{624D}\x{624E}\x{6253}\x{6255}\x{6258}\x{625B}' .
+'\x{625E}\x{6260}\x{6263}\x{6268}\x{626E}\x{6271}\x{6276}\x{6279}\x{627C}' .
+'\x{627E}\x{627F}\x{6280}\x{6282}\x{6283}\x{6284}\x{6289}\x{628A}\x{6291}' .
+'\x{6292}\x{6293}\x{6294}\x{6295}\x{6296}\x{6297}\x{6298}\x{629B}\x{629C}' .
+'\x{629E}\x{62AB}\x{62AC}\x{62B1}\x{62B5}\x{62B9}\x{62BB}\x{62BC}\x{62BD}' .
+'\x{62C2}\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CC}\x{62CD}' .
+'\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D7}\x{62D8}\x{62D9}' .
+'\x{62DB}\x{62DC}\x{62DD}\x{62E0}\x{62E1}\x{62EC}\x{62ED}\x{62EE}\x{62EF}' .
+'\x{62F1}\x{62F3}\x{62F5}\x{62F6}\x{62F7}\x{62FE}\x{62FF}\x{6301}\x{6302}' .
+'\x{6307}\x{6308}\x{6309}\x{630C}\x{6311}\x{6319}\x{631F}\x{6327}\x{6328}' .
+'\x{632B}\x{632F}\x{633A}\x{633D}\x{633E}\x{633F}\x{6349}\x{634C}\x{634D}' .
+'\x{634F}\x{6350}\x{6355}\x{6357}\x{635C}\x{6367}\x{6368}\x{6369}\x{636B}' .
+'\x{636E}\x{6372}\x{6376}\x{6377}\x{637A}\x{637B}\x{6380}\x{6383}\x{6388}' .
+'\x{6389}\x{638C}\x{638E}\x{638F}\x{6392}\x{6396}\x{6398}\x{639B}\x{639F}' .
+'\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A5}\x{63A7}\x{63A8}\x{63A9}\x{63AA}' .
+'\x{63AB}\x{63AC}\x{63B2}\x{63B4}\x{63B5}\x{63BB}\x{63BE}\x{63C0}\x{63C3}' .
+'\x{63C4}\x{63C6}\x{63C9}\x{63CF}\x{63D0}\x{63D2}\x{63D6}\x{63DA}\x{63DB}' .
+'\x{63E1}\x{63E3}\x{63E9}\x{63EE}\x{63F4}\x{63F6}\x{63FA}\x{6406}\x{640D}' .
+'\x{640F}\x{6413}\x{6416}\x{6417}\x{641C}\x{6426}\x{6428}\x{642C}\x{642D}' .
+'\x{6434}\x{6436}\x{643A}\x{643E}\x{6442}\x{644E}\x{6458}\x{6467}\x{6469}' .
+'\x{646F}\x{6476}\x{6478}\x{647A}\x{6483}\x{6488}\x{6492}\x{6493}\x{6495}' .
+'\x{649A}\x{649E}\x{64A4}\x{64A5}\x{64A9}\x{64AB}\x{64AD}\x{64AE}\x{64B0}' .
+'\x{64B2}\x{64B9}\x{64BB}\x{64BC}\x{64C1}\x{64C2}\x{64C5}\x{64C7}\x{64CD}' .
+'\x{64D2}\x{64D4}\x{64D8}\x{64DA}\x{64E0}\x{64E1}\x{64E2}\x{64E3}\x{64E6}' .
+'\x{64E7}\x{64EC}\x{64EF}\x{64F1}\x{64F2}\x{64F4}\x{64F6}\x{64FA}\x{64FD}' .
+'\x{64FE}\x{6500}\x{6505}\x{6518}\x{651C}\x{651D}\x{6523}\x{6524}\x{652A}' .
+'\x{652B}\x{652C}\x{652F}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}' .
+'\x{653B}\x{653E}\x{653F}\x{6545}\x{6548}\x{654D}\x{654F}\x{6551}\x{6555}' .
+'\x{6556}\x{6557}\x{6558}\x{6559}\x{655D}\x{655E}\x{6562}\x{6563}\x{6566}' .
+'\x{656C}\x{6570}\x{6572}\x{6574}\x{6575}\x{6577}\x{6578}\x{6582}\x{6583}' .
+'\x{6587}\x{6588}\x{6589}\x{658C}\x{658E}\x{6590}\x{6591}\x{6597}\x{6599}' .
+'\x{659B}\x{659C}\x{659F}\x{65A1}\x{65A4}\x{65A5}\x{65A7}\x{65AB}\x{65AC}' .
+'\x{65AD}\x{65AF}\x{65B0}\x{65B7}\x{65B9}\x{65BC}\x{65BD}\x{65C1}\x{65C3}' .
+'\x{65C4}\x{65C5}\x{65C6}\x{65CB}\x{65CC}\x{65CF}\x{65D2}\x{65D7}\x{65D9}' .
+'\x{65DB}\x{65E0}\x{65E1}\x{65E2}\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}' .
+'\x{65EC}\x{65ED}\x{65F1}\x{65FA}\x{65FB}\x{6602}\x{6603}\x{6606}\x{6607}' .
+'\x{660A}\x{660C}\x{660E}\x{660F}\x{6613}\x{6614}\x{661C}\x{661F}\x{6620}' .
+'\x{6625}\x{6627}\x{6628}\x{662D}\x{662F}\x{6634}\x{6635}\x{6636}\x{663C}' .
+'\x{663F}\x{6641}\x{6642}\x{6643}\x{6644}\x{6649}\x{664B}\x{664F}\x{6652}' .
+'\x{665D}\x{665E}\x{665F}\x{6662}\x{6664}\x{6666}\x{6667}\x{6668}\x{6669}' .
+'\x{666E}\x{666F}\x{6670}\x{6674}\x{6676}\x{667A}\x{6681}\x{6683}\x{6684}' .
+'\x{6687}\x{6688}\x{6689}\x{668E}\x{6691}\x{6696}\x{6697}\x{6698}\x{669D}' .
+'\x{66A2}\x{66A6}\x{66AB}\x{66AE}\x{66B4}\x{66B8}\x{66B9}\x{66BC}\x{66BE}' .
+'\x{66C1}\x{66C4}\x{66C7}\x{66C9}\x{66D6}\x{66D9}\x{66DA}\x{66DC}\x{66DD}' .
+'\x{66E0}\x{66E6}\x{66E9}\x{66F0}\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F7}' .
+'\x{66F8}\x{66F9}\x{66FC}\x{66FD}\x{66FE}\x{66FF}\x{6700}\x{6703}\x{6708}' .
+'\x{6709}\x{670B}\x{670D}\x{670F}\x{6714}\x{6715}\x{6716}\x{6717}\x{671B}' .
+'\x{671D}\x{671E}\x{671F}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}' .
+'\x{672D}\x{672E}\x{6731}\x{6734}\x{6736}\x{6737}\x{6738}\x{673A}\x{673D}' .
+'\x{673F}\x{6741}\x{6746}\x{6749}\x{674E}\x{674F}\x{6750}\x{6751}\x{6753}' .
+'\x{6756}\x{6759}\x{675C}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' .
+'\x{6764}\x{6765}\x{676A}\x{676D}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}' .
+'\x{6775}\x{6777}\x{677C}\x{677E}\x{677F}\x{6785}\x{6787}\x{6789}\x{678B}' .
+'\x{678C}\x{6790}\x{6795}\x{6797}\x{679A}\x{679C}\x{679D}\x{67A0}\x{67A1}' .
+'\x{67A2}\x{67A6}\x{67A9}\x{67AF}\x{67B3}\x{67B4}\x{67B6}\x{67B7}\x{67B8}' .
+'\x{67B9}\x{67C1}\x{67C4}\x{67C6}\x{67CA}\x{67CE}\x{67CF}\x{67D0}\x{67D1}' .
+'\x{67D3}\x{67D4}\x{67D8}\x{67DA}\x{67DD}\x{67DE}\x{67E2}\x{67E4}\x{67E7}' .
+'\x{67E9}\x{67EC}\x{67EE}\x{67EF}\x{67F1}\x{67F3}\x{67F4}\x{67F5}\x{67FB}' .
+'\x{67FE}\x{67FF}\x{6802}\x{6803}\x{6804}\x{6813}\x{6816}\x{6817}\x{681E}' .
+'\x{6821}\x{6822}\x{6829}\x{682A}\x{682B}\x{6832}\x{6834}\x{6838}\x{6839}' .
+'\x{683C}\x{683D}\x{6840}\x{6841}\x{6842}\x{6843}\x{6846}\x{6848}\x{684D}' .
+'\x{684E}\x{6850}\x{6851}\x{6853}\x{6854}\x{6859}\x{685C}\x{685D}\x{685F}' .
+'\x{6863}\x{6867}\x{6874}\x{6876}\x{6877}\x{687E}\x{687F}\x{6881}\x{6883}' .
+'\x{6885}\x{688D}\x{688F}\x{6893}\x{6894}\x{6897}\x{689B}\x{689D}\x{689F}' .
+'\x{68A0}\x{68A2}\x{68A6}\x{68A7}\x{68A8}\x{68AD}\x{68AF}\x{68B0}\x{68B1}' .
+'\x{68B3}\x{68B5}\x{68B6}\x{68B9}\x{68BA}\x{68BC}\x{68C4}\x{68C6}\x{68C9}' .
+'\x{68CA}\x{68CB}\x{68CD}\x{68D2}\x{68D4}\x{68D5}\x{68D7}\x{68D8}\x{68DA}' .
+'\x{68DF}\x{68E0}\x{68E1}\x{68E3}\x{68E7}\x{68EE}\x{68EF}\x{68F2}\x{68F9}' .
+'\x{68FA}\x{6900}\x{6901}\x{6904}\x{6905}\x{6908}\x{690B}\x{690C}\x{690D}' .
+'\x{690E}\x{690F}\x{6912}\x{6919}\x{691A}\x{691B}\x{691C}\x{6921}\x{6922}' .
+'\x{6923}\x{6925}\x{6926}\x{6928}\x{692A}\x{6930}\x{6934}\x{6936}\x{6939}' .
+'\x{693D}\x{693F}\x{694A}\x{6953}\x{6954}\x{6955}\x{6959}\x{695A}\x{695C}' .
+'\x{695D}\x{695E}\x{6960}\x{6961}\x{6962}\x{696A}\x{696B}\x{696D}\x{696E}' .
+'\x{696F}\x{6973}\x{6974}\x{6975}\x{6977}\x{6978}\x{6979}\x{697C}\x{697D}' .
+'\x{697E}\x{6981}\x{6982}\x{698A}\x{698E}\x{6991}\x{6994}\x{6995}\x{699B}' .
+'\x{699C}\x{69A0}\x{69A7}\x{69AE}\x{69B1}\x{69B2}\x{69B4}\x{69BB}\x{69BE}' .
+'\x{69BF}\x{69C1}\x{69C3}\x{69C7}\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}' .
+'\x{69D0}\x{69D3}\x{69D8}\x{69D9}\x{69DD}\x{69DE}\x{69E7}\x{69E8}\x{69EB}' .
+'\x{69ED}\x{69F2}\x{69F9}\x{69FB}\x{69FD}\x{69FF}\x{6A02}\x{6A05}\x{6A0A}' .
+'\x{6A0B}\x{6A0C}\x{6A12}\x{6A13}\x{6A14}\x{6A17}\x{6A19}\x{6A1B}\x{6A1E}' .
+'\x{6A1F}\x{6A21}\x{6A22}\x{6A23}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2E}\x{6A35}' .
+'\x{6A36}\x{6A38}\x{6A39}\x{6A3A}\x{6A3D}\x{6A44}\x{6A47}\x{6A48}\x{6A4B}' .
+'\x{6A58}\x{6A59}\x{6A5F}\x{6A61}\x{6A62}\x{6A66}\x{6A72}\x{6A78}\x{6A7F}' .
+'\x{6A80}\x{6A84}\x{6A8D}\x{6A8E}\x{6A90}\x{6A97}\x{6A9C}\x{6AA0}\x{6AA2}' .
+'\x{6AA3}\x{6AAA}\x{6AAC}\x{6AAE}\x{6AB3}\x{6AB8}\x{6ABB}\x{6AC1}\x{6AC2}' .
+'\x{6AC3}\x{6AD1}\x{6AD3}\x{6ADA}\x{6ADB}\x{6ADE}\x{6ADF}\x{6AE8}\x{6AEA}' .
+'\x{6AFA}\x{6AFB}\x{6B04}\x{6B05}\x{6B0A}\x{6B12}\x{6B16}\x{6B1D}\x{6B1F}' .
+'\x{6B20}\x{6B21}\x{6B23}\x{6B27}\x{6B32}\x{6B37}\x{6B38}\x{6B39}\x{6B3A}' .
+'\x{6B3D}\x{6B3E}\x{6B43}\x{6B47}\x{6B49}\x{6B4C}\x{6B4E}\x{6B50}\x{6B53}' .
+'\x{6B54}\x{6B59}\x{6B5B}\x{6B5F}\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B66}' .
+'\x{6B69}\x{6B6A}\x{6B6F}\x{6B73}\x{6B74}\x{6B78}\x{6B79}\x{6B7B}\x{6B7F}' .
+'\x{6B80}\x{6B83}\x{6B84}\x{6B86}\x{6B89}\x{6B8A}\x{6B8B}\x{6B8D}\x{6B95}' .
+'\x{6B96}\x{6B98}\x{6B9E}\x{6BA4}\x{6BAA}\x{6BAB}\x{6BAF}\x{6BB1}\x{6BB2}' .
+'\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB7}\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBF}\x{6BC0}' .
+'\x{6BC5}\x{6BC6}\x{6BCB}\x{6BCD}\x{6BCE}\x{6BD2}\x{6BD3}\x{6BD4}\x{6BD8}' .
+'\x{6BDB}\x{6BDF}\x{6BEB}\x{6BEC}\x{6BEF}\x{6BF3}\x{6C08}\x{6C0F}\x{6C11}' .
+'\x{6C13}\x{6C14}\x{6C17}\x{6C1B}\x{6C23}\x{6C24}\x{6C34}\x{6C37}\x{6C38}' .
+'\x{6C3E}\x{6C40}\x{6C41}\x{6C42}\x{6C4E}\x{6C50}\x{6C55}\x{6C57}\x{6C5A}' .
+'\x{6C5D}\x{6C5E}\x{6C5F}\x{6C60}\x{6C62}\x{6C68}\x{6C6A}\x{6C70}\x{6C72}' .
+'\x{6C73}\x{6C7A}\x{6C7D}\x{6C7E}\x{6C81}\x{6C82}\x{6C83}\x{6C88}\x{6C8C}' .
+'\x{6C8D}\x{6C90}\x{6C92}\x{6C93}\x{6C96}\x{6C99}\x{6C9A}\x{6C9B}\x{6CA1}' .
+'\x{6CA2}\x{6CAB}\x{6CAE}\x{6CB1}\x{6CB3}\x{6CB8}\x{6CB9}\x{6CBA}\x{6CBB}' .
+'\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC1}\x{6CC4}\x{6CC5}\x{6CC9}\x{6CCA}' .
+'\x{6CCC}\x{6CD3}\x{6CD5}\x{6CD7}\x{6CD9}\x{6CDB}\x{6CDD}\x{6CE1}\x{6CE2}' .
+'\x{6CE3}\x{6CE5}\x{6CE8}\x{6CEA}\x{6CEF}\x{6CF0}\x{6CF1}\x{6CF3}\x{6D0B}' .
+'\x{6D0C}\x{6D12}\x{6D17}\x{6D19}\x{6D1B}\x{6D1E}\x{6D1F}\x{6D25}\x{6D29}' .
+'\x{6D2A}\x{6D2B}\x{6D32}\x{6D33}\x{6D35}\x{6D36}\x{6D38}\x{6D3B}\x{6D3D}' .
+'\x{6D3E}\x{6D41}\x{6D44}\x{6D45}\x{6D59}\x{6D5A}\x{6D5C}\x{6D63}\x{6D64}' .
+'\x{6D66}\x{6D69}\x{6D6A}\x{6D6C}\x{6D6E}\x{6D74}\x{6D77}\x{6D78}\x{6D79}' .
+'\x{6D85}\x{6D88}\x{6D8C}\x{6D8E}\x{6D93}\x{6D95}\x{6D99}\x{6D9B}\x{6D9C}' .
+'\x{6DAF}\x{6DB2}\x{6DB5}\x{6DB8}\x{6DBC}\x{6DC0}\x{6DC5}\x{6DC6}\x{6DC7}' .
+'\x{6DCB}\x{6DCC}\x{6DD1}\x{6DD2}\x{6DD5}\x{6DD8}\x{6DD9}\x{6DDE}\x{6DE1}' .
+'\x{6DE4}\x{6DE6}\x{6DE8}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DEE}\x{6DF1}\x{6DF3}' .
+'\x{6DF5}\x{6DF7}\x{6DF9}\x{6DFA}\x{6DFB}\x{6E05}\x{6E07}\x{6E08}\x{6E09}' .
+'\x{6E0A}\x{6E0B}\x{6E13}\x{6E15}\x{6E19}\x{6E1A}\x{6E1B}\x{6E1D}\x{6E1F}' .
+'\x{6E20}\x{6E21}\x{6E23}\x{6E24}\x{6E25}\x{6E26}\x{6E29}\x{6E2B}\x{6E2C}' .
+'\x{6E2D}\x{6E2E}\x{6E2F}\x{6E38}\x{6E3A}\x{6E3E}\x{6E43}\x{6E4A}\x{6E4D}' .
+'\x{6E4E}\x{6E56}\x{6E58}\x{6E5B}\x{6E5F}\x{6E67}\x{6E6B}\x{6E6E}\x{6E6F}' .
+'\x{6E72}\x{6E76}\x{6E7E}\x{6E7F}\x{6E80}\x{6E82}\x{6E8C}\x{6E8F}\x{6E90}' .
+'\x{6E96}\x{6E98}\x{6E9C}\x{6E9D}\x{6E9F}\x{6EA2}\x{6EA5}\x{6EAA}\x{6EAF}' .
+'\x{6EB2}\x{6EB6}\x{6EB7}\x{6EBA}\x{6EBD}\x{6EC2}\x{6EC4}\x{6EC5}\x{6EC9}' .
+'\x{6ECB}\x{6ECC}\x{6ED1}\x{6ED3}\x{6ED4}\x{6ED5}\x{6EDD}\x{6EDE}\x{6EEC}' .
+'\x{6EEF}\x{6EF2}\x{6EF4}\x{6EF7}\x{6EF8}\x{6EFE}\x{6EFF}\x{6F01}\x{6F02}' .
+'\x{6F06}\x{6F09}\x{6F0F}\x{6F11}\x{6F13}\x{6F14}\x{6F15}\x{6F20}\x{6F22}' .
+'\x{6F23}\x{6F2B}\x{6F2C}\x{6F31}\x{6F32}\x{6F38}\x{6F3E}\x{6F3F}\x{6F41}' .
+'\x{6F45}\x{6F54}\x{6F58}\x{6F5B}\x{6F5C}\x{6F5F}\x{6F64}\x{6F66}\x{6F6D}' .
+'\x{6F6E}\x{6F6F}\x{6F70}\x{6F74}\x{6F78}\x{6F7A}\x{6F7C}\x{6F80}\x{6F81}' .
+'\x{6F82}\x{6F84}\x{6F86}\x{6F8E}\x{6F91}\x{6F97}\x{6FA1}\x{6FA3}\x{6FA4}' .
+'\x{6FAA}\x{6FB1}\x{6FB3}\x{6FB9}\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC6}' .
+'\x{6FD4}\x{6FD5}\x{6FD8}\x{6FDB}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE4}\x{6FEB}' .
+'\x{6FEC}\x{6FEE}\x{6FEF}\x{6FF1}\x{6FF3}\x{6FF6}\x{6FFA}\x{6FFE}\x{7001}' .
+'\x{7009}\x{700B}\x{700F}\x{7011}\x{7015}\x{7018}\x{701A}\x{701B}\x{701D}' .
+'\x{701E}\x{701F}\x{7026}\x{7027}\x{702C}\x{7030}\x{7032}\x{703E}\x{704C}' .
+'\x{7051}\x{7058}\x{7063}\x{706B}\x{706F}\x{7070}\x{7078}\x{707C}\x{707D}' .
+'\x{7089}\x{708A}\x{708E}\x{7092}\x{7099}\x{70AC}\x{70AD}\x{70AE}\x{70AF}' .
+'\x{70B3}\x{70B8}\x{70B9}\x{70BA}\x{70C8}\x{70CB}\x{70CF}\x{70D9}\x{70DD}' .
+'\x{70DF}\x{70F1}\x{70F9}\x{70FD}\x{7109}\x{7114}\x{7119}\x{711A}\x{711C}' .
+'\x{7121}\x{7126}\x{7136}\x{713C}\x{7149}\x{714C}\x{714E}\x{7155}\x{7156}' .
+'\x{7159}\x{7162}\x{7164}\x{7165}\x{7166}\x{7167}\x{7169}\x{716C}\x{716E}' .
+'\x{717D}\x{7184}\x{7188}\x{718A}\x{718F}\x{7194}\x{7195}\x{7199}\x{719F}' .
+'\x{71A8}\x{71AC}\x{71B1}\x{71B9}\x{71BE}\x{71C3}\x{71C8}\x{71C9}\x{71CE}' .
+'\x{71D0}\x{71D2}\x{71D4}\x{71D5}\x{71D7}\x{71DF}\x{71E0}\x{71E5}\x{71E6}' .
+'\x{71E7}\x{71EC}\x{71ED}\x{71EE}\x{71F5}\x{71F9}\x{71FB}\x{71FC}\x{71FF}' .
+'\x{7206}\x{720D}\x{7210}\x{721B}\x{7228}\x{722A}\x{722C}\x{722D}\x{7230}' .
+'\x{7232}\x{7235}\x{7236}\x{723A}\x{723B}\x{723C}\x{723D}\x{723E}\x{723F}' .
+'\x{7240}\x{7246}\x{7247}\x{7248}\x{724B}\x{724C}\x{7252}\x{7258}\x{7259}' .
+'\x{725B}\x{725D}\x{725F}\x{7261}\x{7262}\x{7267}\x{7269}\x{7272}\x{7274}' .
+'\x{7279}\x{727D}\x{727E}\x{7280}\x{7281}\x{7282}\x{7287}\x{7292}\x{7296}' .
+'\x{72A0}\x{72A2}\x{72A7}\x{72AC}\x{72AF}\x{72B2}\x{72B6}\x{72B9}\x{72C2}' .
+'\x{72C3}\x{72C4}\x{72C6}\x{72CE}\x{72D0}\x{72D2}\x{72D7}\x{72D9}\x{72DB}' .
+'\x{72E0}\x{72E1}\x{72E2}\x{72E9}\x{72EC}\x{72ED}\x{72F7}\x{72F8}\x{72F9}' .
+'\x{72FC}\x{72FD}\x{730A}\x{7316}\x{7317}\x{731B}\x{731C}\x{731D}\x{731F}' .
+'\x{7325}\x{7329}\x{732A}\x{732B}\x{732E}\x{732F}\x{7334}\x{7336}\x{7337}' .
+'\x{733E}\x{733F}\x{7344}\x{7345}\x{734E}\x{734F}\x{7357}\x{7363}\x{7368}' .
+'\x{736A}\x{7370}\x{7372}\x{7375}\x{7378}\x{737A}\x{737B}\x{7384}\x{7387}' .
+'\x{7389}\x{738B}\x{7396}\x{73A9}\x{73B2}\x{73B3}\x{73BB}\x{73C0}\x{73C2}' .
+'\x{73C8}\x{73CA}\x{73CD}\x{73CE}\x{73DE}\x{73E0}\x{73E5}\x{73EA}\x{73ED}' .
+'\x{73EE}\x{73F1}\x{73F8}\x{73FE}\x{7403}\x{7405}\x{7406}\x{7409}\x{7422}' .
+'\x{7425}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{743A}\x{743F}\x{7441}' .
+'\x{7455}\x{7459}\x{745A}\x{745B}\x{745C}\x{745E}\x{745F}\x{7460}\x{7463}' .
+'\x{7464}\x{7469}\x{746A}\x{746F}\x{7470}\x{7473}\x{7476}\x{747E}\x{7483}' .
+'\x{748B}\x{749E}\x{74A2}\x{74A7}\x{74B0}\x{74BD}\x{74CA}\x{74CF}\x{74D4}' .
+'\x{74DC}\x{74E0}\x{74E2}\x{74E3}\x{74E6}\x{74E7}\x{74E9}\x{74EE}\x{74F0}' .
+'\x{74F1}\x{74F2}\x{74F6}\x{74F7}\x{74F8}\x{7503}\x{7504}\x{7505}\x{750C}' .
+'\x{750D}\x{750E}\x{7511}\x{7513}\x{7515}\x{7518}\x{751A}\x{751C}\x{751E}' .
+'\x{751F}\x{7523}\x{7525}\x{7526}\x{7528}\x{752B}\x{752C}\x{7530}\x{7531}' .
+'\x{7532}\x{7533}\x{7537}\x{7538}\x{753A}\x{753B}\x{753C}\x{7544}\x{7546}' .
+'\x{7549}\x{754A}\x{754B}\x{754C}\x{754D}\x{754F}\x{7551}\x{7554}\x{7559}' .
+'\x{755A}\x{755B}\x{755C}\x{755D}\x{7560}\x{7562}\x{7564}\x{7565}\x{7566}' .
+'\x{7567}\x{7569}\x{756A}\x{756B}\x{756D}\x{7570}\x{7573}\x{7574}\x{7576}' .
+'\x{7577}\x{7578}\x{757F}\x{7582}\x{7586}\x{7587}\x{7589}\x{758A}\x{758B}' .
+'\x{758E}\x{758F}\x{7591}\x{7594}\x{759A}\x{759D}\x{75A3}\x{75A5}\x{75AB}' .
+'\x{75B1}\x{75B2}\x{75B3}\x{75B5}\x{75B8}\x{75B9}\x{75BC}\x{75BD}\x{75BE}' .
+'\x{75C2}\x{75C3}\x{75C5}\x{75C7}\x{75CA}\x{75CD}\x{75D2}\x{75D4}\x{75D5}' .
+'\x{75D8}\x{75D9}\x{75DB}\x{75DE}\x{75E2}\x{75E3}\x{75E9}\x{75F0}\x{75F2}' .
+'\x{75F3}\x{75F4}\x{75FA}\x{75FC}\x{75FE}\x{75FF}\x{7601}\x{7609}\x{760B}' .
+'\x{760D}\x{761F}\x{7620}\x{7621}\x{7622}\x{7624}\x{7627}\x{7630}\x{7634}' .
+'\x{763B}\x{7642}\x{7646}\x{7647}\x{7648}\x{764C}\x{7652}\x{7656}\x{7658}' .
+'\x{765C}\x{7661}\x{7662}\x{7667}\x{7668}\x{7669}\x{766A}\x{766C}\x{7670}' .
+'\x{7672}\x{7676}\x{7678}\x{767A}\x{767B}\x{767C}\x{767D}\x{767E}\x{7680}' .
+'\x{7683}\x{7684}\x{7686}\x{7687}\x{7688}\x{768B}\x{768E}\x{7690}\x{7693}' .
+'\x{7696}\x{7699}\x{769A}\x{76AE}\x{76B0}\x{76B4}\x{76B7}\x{76B8}\x{76B9}' .
+'\x{76BA}\x{76BF}\x{76C2}\x{76C3}\x{76C6}\x{76C8}\x{76CA}\x{76CD}\x{76D2}' .
+'\x{76D6}\x{76D7}\x{76DB}\x{76DC}\x{76DE}\x{76DF}\x{76E1}\x{76E3}\x{76E4}' .
+'\x{76E5}\x{76E7}\x{76EA}\x{76EE}\x{76F2}\x{76F4}\x{76F8}\x{76FB}\x{76FE}' .
+'\x{7701}\x{7704}\x{7707}\x{7708}\x{7709}\x{770B}\x{770C}\x{771B}\x{771E}' .
+'\x{771F}\x{7720}\x{7724}\x{7725}\x{7726}\x{7729}\x{7737}\x{7738}\x{773A}' .
+'\x{773C}\x{7740}\x{7747}\x{775A}\x{775B}\x{7761}\x{7763}\x{7765}\x{7766}' .
+'\x{7768}\x{776B}\x{7779}\x{777E}\x{777F}\x{778B}\x{778E}\x{7791}\x{779E}' .
+'\x{77A0}\x{77A5}\x{77AC}\x{77AD}\x{77B0}\x{77B3}\x{77B6}\x{77B9}\x{77BB}' .
+'\x{77BC}\x{77BD}\x{77BF}\x{77C7}\x{77CD}\x{77D7}\x{77DA}\x{77DB}\x{77DC}' .
+'\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E9}\x{77ED}\x{77EE}\x{77EF}\x{77F3}' .
+'\x{77FC}\x{7802}\x{780C}\x{7812}\x{7814}\x{7815}\x{7820}\x{7825}\x{7826}' .
+'\x{7827}\x{7832}\x{7834}\x{783A}\x{783F}\x{7845}\x{785D}\x{786B}\x{786C}' .
+'\x{786F}\x{7872}\x{7874}\x{787C}\x{7881}\x{7886}\x{7887}\x{788C}\x{788D}' .
+'\x{788E}\x{7891}\x{7893}\x{7895}\x{7897}\x{789A}\x{78A3}\x{78A7}\x{78A9}' .
+'\x{78AA}\x{78AF}\x{78B5}\x{78BA}\x{78BC}\x{78BE}\x{78C1}\x{78C5}\x{78C6}' .
+'\x{78CA}\x{78CB}\x{78D0}\x{78D1}\x{78D4}\x{78DA}\x{78E7}\x{78E8}\x{78EC}' .
+'\x{78EF}\x{78F4}\x{78FD}\x{7901}\x{7907}\x{790E}\x{7911}\x{7912}\x{7919}' .
+'\x{7926}\x{792A}\x{792B}\x{792C}\x{793A}\x{793C}\x{793E}\x{7940}\x{7941}' .
+'\x{7947}\x{7948}\x{7949}\x{7950}\x{7953}\x{7955}\x{7956}\x{7957}\x{795A}' .
+'\x{795D}\x{795E}\x{795F}\x{7960}\x{7962}\x{7965}\x{7968}\x{796D}\x{7977}' .
+'\x{797A}\x{797F}\x{7980}\x{7981}\x{7984}\x{7985}\x{798A}\x{798D}\x{798E}' .
+'\x{798F}\x{799D}\x{79A6}\x{79A7}\x{79AA}\x{79AE}\x{79B0}\x{79B3}\x{79B9}' .
+'\x{79BA}\x{79BD}\x{79BE}\x{79BF}\x{79C0}\x{79C1}\x{79C9}\x{79CB}\x{79D1}' .
+'\x{79D2}\x{79D5}\x{79D8}\x{79DF}\x{79E1}\x{79E3}\x{79E4}\x{79E6}\x{79E7}' .
+'\x{79E9}\x{79EC}\x{79F0}\x{79FB}\x{7A00}\x{7A08}\x{7A0B}\x{7A0D}\x{7A0E}' .
+'\x{7A14}\x{7A17}\x{7A18}\x{7A19}\x{7A1A}\x{7A1C}\x{7A1F}\x{7A20}\x{7A2E}' .
+'\x{7A31}\x{7A32}\x{7A37}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' .
+'\x{7A42}\x{7A43}\x{7A46}\x{7A49}\x{7A4D}\x{7A4E}\x{7A4F}\x{7A50}\x{7A57}' .
+'\x{7A61}\x{7A62}\x{7A63}\x{7A69}\x{7A6B}\x{7A70}\x{7A74}\x{7A76}\x{7A79}' .
+'\x{7A7A}\x{7A7D}\x{7A7F}\x{7A81}\x{7A83}\x{7A84}\x{7A88}\x{7A92}\x{7A93}' .
+'\x{7A95}\x{7A96}\x{7A97}\x{7A98}\x{7A9F}\x{7AA9}\x{7AAA}\x{7AAE}\x{7AAF}' .
+'\x{7AB0}\x{7AB6}\x{7ABA}\x{7ABF}\x{7AC3}\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}' .
+'\x{7ACA}\x{7ACB}\x{7ACD}\x{7ACF}\x{7AD2}\x{7AD3}\x{7AD5}\x{7AD9}\x{7ADA}' .
+'\x{7ADC}\x{7ADD}\x{7ADF}\x{7AE0}\x{7AE1}\x{7AE2}\x{7AE3}\x{7AE5}\x{7AE6}' .
+'\x{7AEA}\x{7AED}\x{7AEF}\x{7AF0}\x{7AF6}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFF}' .
+'\x{7B02}\x{7B04}\x{7B06}\x{7B08}\x{7B0A}\x{7B0B}\x{7B0F}\x{7B11}\x{7B18}' .
+'\x{7B19}\x{7B1B}\x{7B1E}\x{7B20}\x{7B25}\x{7B26}\x{7B28}\x{7B2C}\x{7B33}' .
+'\x{7B35}\x{7B36}\x{7B39}\x{7B45}\x{7B46}\x{7B48}\x{7B49}\x{7B4B}\x{7B4C}' .
+'\x{7B4D}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B56}\x{7B5D}\x{7B65}' .
+'\x{7B67}\x{7B6C}\x{7B6E}\x{7B70}\x{7B71}\x{7B74}\x{7B75}\x{7B7A}\x{7B86}' .
+'\x{7B87}\x{7B8B}\x{7B8D}\x{7B8F}\x{7B92}\x{7B94}\x{7B95}\x{7B97}\x{7B98}' .
+'\x{7B99}\x{7B9A}\x{7B9C}\x{7B9D}\x{7B9F}\x{7BA1}\x{7BAA}\x{7BAD}\x{7BB1}' .
+'\x{7BB4}\x{7BB8}\x{7BC0}\x{7BC1}\x{7BC4}\x{7BC6}\x{7BC7}\x{7BC9}\x{7BCB}' .
+'\x{7BCC}\x{7BCF}\x{7BDD}\x{7BE0}\x{7BE4}\x{7BE5}\x{7BE6}\x{7BE9}\x{7BED}' .
+'\x{7BF3}\x{7BF6}\x{7BF7}\x{7C00}\x{7C07}\x{7C0D}\x{7C11}\x{7C12}\x{7C13}' .
+'\x{7C14}\x{7C17}\x{7C1F}\x{7C21}\x{7C23}\x{7C27}\x{7C2A}\x{7C2B}\x{7C37}' .
+'\x{7C38}\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C43}\x{7C4C}\x{7C4D}\x{7C4F}' .
+'\x{7C50}\x{7C54}\x{7C56}\x{7C58}\x{7C5F}\x{7C60}\x{7C64}\x{7C65}\x{7C6C}' .
+'\x{7C73}\x{7C75}\x{7C7E}\x{7C81}\x{7C82}\x{7C83}\x{7C89}\x{7C8B}\x{7C8D}' .
+'\x{7C90}\x{7C92}\x{7C95}\x{7C97}\x{7C98}\x{7C9B}\x{7C9F}\x{7CA1}\x{7CA2}' .
+'\x{7CA4}\x{7CA5}\x{7CA7}\x{7CA8}\x{7CAB}\x{7CAD}\x{7CAE}\x{7CB1}\x{7CB2}' .
+'\x{7CB3}\x{7CB9}\x{7CBD}\x{7CBE}\x{7CC0}\x{7CC2}\x{7CC5}\x{7CCA}\x{7CCE}' .
+'\x{7CD2}\x{7CD6}\x{7CD8}\x{7CDC}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE7}' .
+'\x{7CEF}\x{7CF2}\x{7CF4}\x{7CF6}\x{7CF8}\x{7CFA}\x{7CFB}\x{7CFE}\x{7D00}' .
+'\x{7D02}\x{7D04}\x{7D05}\x{7D06}\x{7D0A}\x{7D0B}\x{7D0D}\x{7D10}\x{7D14}' .
+'\x{7D15}\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D20}\x{7D21}' .
+'\x{7D22}\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D32}\x{7D33}\x{7D35}' .
+'\x{7D39}\x{7D3A}\x{7D3F}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}\x{7D4B}' .
+'\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D56}\x{7D5B}\x{7D5E}\x{7D61}\x{7D62}' .
+'\x{7D63}\x{7D66}\x{7D68}\x{7D6E}\x{7D71}\x{7D72}\x{7D73}\x{7D75}\x{7D76}' .
+'\x{7D79}\x{7D7D}\x{7D89}\x{7D8F}\x{7D93}\x{7D99}\x{7D9A}\x{7D9B}\x{7D9C}' .
+'\x{7D9F}\x{7DA2}\x{7DA3}\x{7DAB}\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}' .
+'\x{7DB1}\x{7DB2}\x{7DB4}\x{7DB5}\x{7DB8}\x{7DBA}\x{7DBB}\x{7DBD}\x{7DBE}' .
+'\x{7DBF}\x{7DC7}\x{7DCA}\x{7DCB}\x{7DCF}\x{7DD1}\x{7DD2}\x{7DD5}\x{7DD8}' .
+'\x{7DDA}\x{7DDC}\x{7DDD}\x{7DDE}\x{7DE0}\x{7DE1}\x{7DE4}\x{7DE8}\x{7DE9}' .
+'\x{7DEC}\x{7DEF}\x{7DF2}\x{7DF4}\x{7DFB}\x{7E01}\x{7E04}\x{7E05}\x{7E09}' .
+'\x{7E0A}\x{7E0B}\x{7E12}\x{7E1B}\x{7E1E}\x{7E1F}\x{7E21}\x{7E22}\x{7E23}' .
+'\x{7E26}\x{7E2B}\x{7E2E}\x{7E31}\x{7E32}\x{7E35}\x{7E37}\x{7E39}\x{7E3A}' .
+'\x{7E3B}\x{7E3D}\x{7E3E}\x{7E41}\x{7E43}\x{7E46}\x{7E4A}\x{7E4B}\x{7E4D}' .
+'\x{7E54}\x{7E55}\x{7E56}\x{7E59}\x{7E5A}\x{7E5D}\x{7E5E}\x{7E66}\x{7E67}' .
+'\x{7E69}\x{7E6A}\x{7E6D}\x{7E70}\x{7E79}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7F}' .
+'\x{7E82}\x{7E83}\x{7E88}\x{7E89}\x{7E8C}\x{7E8E}\x{7E8F}\x{7E90}\x{7E92}' .
+'\x{7E93}\x{7E94}\x{7E96}\x{7E9B}\x{7E9C}\x{7F36}\x{7F38}\x{7F3A}\x{7F45}' .
+'\x{7F4C}\x{7F4D}\x{7F4E}\x{7F50}\x{7F51}\x{7F54}\x{7F55}\x{7F58}\x{7F5F}' .
+'\x{7F60}\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6E}\x{7F70}\x{7F72}' .
+'\x{7F75}\x{7F77}\x{7F78}\x{7F79}\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}' .
+'\x{7F88}\x{7F8A}\x{7F8C}\x{7F8E}\x{7F94}\x{7F9A}\x{7F9D}\x{7F9E}\x{7FA3}' .
+'\x{7FA4}\x{7FA8}\x{7FA9}\x{7FAE}\x{7FAF}\x{7FB2}\x{7FB6}\x{7FB8}\x{7FB9}' .
+'\x{7FBD}\x{7FC1}\x{7FC5}\x{7FC6}\x{7FCA}\x{7FCC}\x{7FD2}\x{7FD4}\x{7FD5}' .
+'\x{7FE0}\x{7FE1}\x{7FE6}\x{7FE9}\x{7FEB}\x{7FF0}\x{7FF3}\x{7FF9}\x{7FFB}' .
+'\x{7FFC}\x{8000}\x{8001}\x{8003}\x{8004}\x{8005}\x{8006}\x{800B}\x{800C}' .
+'\x{8010}\x{8012}\x{8015}\x{8017}\x{8018}\x{8019}\x{801C}\x{8021}\x{8028}' .
+'\x{8033}\x{8036}\x{803B}\x{803D}\x{803F}\x{8046}\x{804A}\x{8052}\x{8056}' .
+'\x{8058}\x{805A}\x{805E}\x{805F}\x{8061}\x{8062}\x{8068}\x{806F}\x{8070}' .
+'\x{8072}\x{8073}\x{8074}\x{8076}\x{8077}\x{8079}\x{807D}\x{807E}\x{807F}' .
+'\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808B}\x{808C}\x{8093}\x{8096}' .
+'\x{8098}\x{809A}\x{809B}\x{809D}\x{80A1}\x{80A2}\x{80A5}\x{80A9}\x{80AA}' .
+'\x{80AC}\x{80AD}\x{80AF}\x{80B1}\x{80B2}\x{80B4}\x{80BA}\x{80C3}\x{80C4}' .
+'\x{80C6}\x{80CC}\x{80CE}\x{80D6}\x{80D9}\x{80DA}\x{80DB}\x{80DD}\x{80DE}' .
+'\x{80E1}\x{80E4}\x{80E5}\x{80EF}\x{80F1}\x{80F4}\x{80F8}\x{80FC}\x{80FD}' .
+'\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{811A}\x{811B}' .
+'\x{8123}\x{8129}\x{812F}\x{8131}\x{8133}\x{8139}\x{813E}\x{8146}\x{814B}' .
+'\x{814E}\x{8150}\x{8151}\x{8153}\x{8154}\x{8155}\x{815F}\x{8165}\x{8166}' .
+'\x{816B}\x{816E}\x{8170}\x{8171}\x{8174}\x{8178}\x{8179}\x{817A}\x{817F}' .
+'\x{8180}\x{8182}\x{8183}\x{8188}\x{818A}\x{818F}\x{8193}\x{8195}\x{819A}' .
+'\x{819C}\x{819D}\x{81A0}\x{81A3}\x{81A4}\x{81A8}\x{81A9}\x{81B0}\x{81B3}' .
+'\x{81B5}\x{81B8}\x{81BA}\x{81BD}\x{81BE}\x{81BF}\x{81C0}\x{81C2}\x{81C6}' .
+'\x{81C8}\x{81C9}\x{81CD}\x{81D1}\x{81D3}\x{81D8}\x{81D9}\x{81DA}\x{81DF}' .
+'\x{81E0}\x{81E3}\x{81E5}\x{81E7}\x{81E8}\x{81EA}\x{81ED}\x{81F3}\x{81F4}' .
+'\x{81FA}\x{81FB}\x{81FC}\x{81FE}\x{8201}\x{8202}\x{8205}\x{8207}\x{8208}' .
+'\x{8209}\x{820A}\x{820C}\x{820D}\x{820E}\x{8210}\x{8212}\x{8216}\x{8217}' .
+'\x{8218}\x{821B}\x{821C}\x{821E}\x{821F}\x{8229}\x{822A}\x{822B}\x{822C}' .
+'\x{822E}\x{8233}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{8240}\x{8247}' .
+'\x{8258}\x{8259}\x{825A}\x{825D}\x{825F}\x{8262}\x{8264}\x{8266}\x{8268}' .
+'\x{826A}\x{826B}\x{826E}\x{826F}\x{8271}\x{8272}\x{8276}\x{8277}\x{8278}' .
+'\x{827E}\x{828B}\x{828D}\x{8292}\x{8299}\x{829D}\x{829F}\x{82A5}\x{82A6}' .
+'\x{82AB}\x{82AC}\x{82AD}\x{82AF}\x{82B1}\x{82B3}\x{82B8}\x{82B9}\x{82BB}' .
+'\x{82BD}\x{82C5}\x{82D1}\x{82D2}\x{82D3}\x{82D4}\x{82D7}\x{82D9}\x{82DB}' .
+'\x{82DC}\x{82DE}\x{82DF}\x{82E1}\x{82E3}\x{82E5}\x{82E6}\x{82E7}\x{82EB}' .
+'\x{82F1}\x{82F3}\x{82F4}\x{82F9}\x{82FA}\x{82FB}\x{8302}\x{8303}\x{8304}' .
+'\x{8305}\x{8306}\x{8309}\x{830E}\x{8316}\x{8317}\x{8318}\x{831C}\x{8323}' .
+'\x{8328}\x{832B}\x{832F}\x{8331}\x{8332}\x{8334}\x{8335}\x{8336}\x{8338}' .
+'\x{8339}\x{8340}\x{8345}\x{8349}\x{834A}\x{834F}\x{8350}\x{8352}\x{8358}' .
+'\x{8373}\x{8375}\x{8377}\x{837B}\x{837C}\x{8385}\x{8387}\x{8389}\x{838A}' .
+'\x{838E}\x{8393}\x{8396}\x{839A}\x{839E}\x{839F}\x{83A0}\x{83A2}\x{83A8}' .
+'\x{83AA}\x{83AB}\x{83B1}\x{83B5}\x{83BD}\x{83C1}\x{83C5}\x{83CA}\x{83CC}' .
+'\x{83CE}\x{83D3}\x{83D6}\x{83D8}\x{83DC}\x{83DF}\x{83E0}\x{83E9}\x{83EB}' .
+'\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F4}\x{83F7}\x{83FB}\x{83FD}\x{8403}' .
+'\x{8404}\x{8407}\x{840B}\x{840C}\x{840D}\x{840E}\x{8413}\x{8420}\x{8422}' .
+'\x{8429}\x{842A}\x{842C}\x{8431}\x{8435}\x{8438}\x{843C}\x{843D}\x{8446}' .
+'\x{8449}\x{844E}\x{8457}\x{845B}\x{8461}\x{8462}\x{8463}\x{8466}\x{8469}' .
+'\x{846B}\x{846C}\x{846D}\x{846E}\x{846F}\x{8471}\x{8475}\x{8477}\x{8479}' .
+'\x{847A}\x{8482}\x{8484}\x{848B}\x{8490}\x{8494}\x{8499}\x{849C}\x{849F}' .
+'\x{84A1}\x{84AD}\x{84B2}\x{84B8}\x{84B9}\x{84BB}\x{84BC}\x{84BF}\x{84C1}' .
+'\x{84C4}\x{84C6}\x{84C9}\x{84CA}\x{84CB}\x{84CD}\x{84D0}\x{84D1}\x{84D6}' .
+'\x{84D9}\x{84DA}\x{84EC}\x{84EE}\x{84F4}\x{84FC}\x{84FF}\x{8500}\x{8506}' .
+'\x{8511}\x{8513}\x{8514}\x{8515}\x{8517}\x{8518}\x{851A}\x{851F}\x{8521}' .
+'\x{8526}\x{852C}\x{852D}\x{8535}\x{853D}\x{8540}\x{8541}\x{8543}\x{8548}' .
+'\x{8549}\x{854A}\x{854B}\x{854E}\x{8555}\x{8557}\x{8558}\x{855A}\x{8563}' .
+'\x{8568}\x{8569}\x{856A}\x{856D}\x{8577}\x{857E}\x{8580}\x{8584}\x{8587}' .
+'\x{8588}\x{858A}\x{8590}\x{8591}\x{8594}\x{8597}\x{8599}\x{859B}\x{859C}' .
+'\x{85A4}\x{85A6}\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AE}\x{85AF}' .
+'\x{85B9}\x{85BA}\x{85C1}\x{85C9}\x{85CD}\x{85CF}\x{85D0}\x{85D5}\x{85DC}' .
+'\x{85DD}\x{85E4}\x{85E5}\x{85E9}\x{85EA}\x{85F7}\x{85F9}\x{85FA}\x{85FB}' .
+'\x{85FE}\x{8602}\x{8606}\x{8607}\x{860A}\x{860B}\x{8613}\x{8616}\x{8617}' .
+'\x{861A}\x{8622}\x{862D}\x{862F}\x{8630}\x{863F}\x{864D}\x{864E}\x{8650}' .
+'\x{8654}\x{8655}\x{865A}\x{865C}\x{865E}\x{865F}\x{8667}\x{866B}\x{8671}' .
+'\x{8679}\x{867B}\x{868A}\x{868B}\x{868C}\x{8693}\x{8695}\x{86A3}\x{86A4}' .
+'\x{86A9}\x{86AA}\x{86AB}\x{86AF}\x{86B0}\x{86B6}\x{86C4}\x{86C6}\x{86C7}' .
+'\x{86C9}\x{86CB}\x{86CD}\x{86CE}\x{86D4}\x{86D9}\x{86DB}\x{86DE}\x{86DF}' .
+'\x{86E4}\x{86E9}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F8}\x{86F9}\x{86FB}' .
+'\x{86FE}\x{8700}\x{8702}\x{8703}\x{8706}\x{8708}\x{8709}\x{870A}\x{870D}' .
+'\x{8711}\x{8712}\x{8718}\x{871A}\x{871C}\x{8725}\x{8729}\x{8734}\x{8737}' .
+'\x{873B}\x{873F}\x{8749}\x{874B}\x{874C}\x{874E}\x{8753}\x{8755}\x{8757}' .
+'\x{8759}\x{875F}\x{8760}\x{8763}\x{8766}\x{8768}\x{876A}\x{876E}\x{8774}' .
+'\x{8776}\x{8778}\x{877F}\x{8782}\x{878D}\x{879F}\x{87A2}\x{87AB}\x{87AF}' .
+'\x{87B3}\x{87BA}\x{87BB}\x{87BD}\x{87C0}\x{87C4}\x{87C6}\x{87C7}\x{87CB}' .
+'\x{87D0}\x{87D2}\x{87E0}\x{87EF}\x{87F2}\x{87F6}\x{87F7}\x{87F9}\x{87FB}' .
+'\x{87FE}\x{8805}\x{880D}\x{880E}\x{880F}\x{8811}\x{8815}\x{8816}\x{8821}' .
+'\x{8822}\x{8823}\x{8827}\x{8831}\x{8836}\x{8839}\x{883B}\x{8840}\x{8842}' .
+'\x{8844}\x{8846}\x{884C}\x{884D}\x{8852}\x{8853}\x{8857}\x{8859}\x{885B}' .
+'\x{885D}\x{885E}\x{8861}\x{8862}\x{8863}\x{8868}\x{886B}\x{8870}\x{8872}' .
+'\x{8875}\x{8877}\x{887D}\x{887E}\x{887F}\x{8881}\x{8882}\x{8888}\x{888B}' .
+'\x{888D}\x{8892}\x{8896}\x{8897}\x{8899}\x{889E}\x{88A2}\x{88A4}\x{88AB}' .
+'\x{88AE}\x{88B0}\x{88B1}\x{88B4}\x{88B5}\x{88B7}\x{88BF}\x{88C1}\x{88C2}' .
+'\x{88C3}\x{88C4}\x{88C5}\x{88CF}\x{88D4}\x{88D5}\x{88D8}\x{88D9}\x{88DC}' .
+'\x{88DD}\x{88DF}\x{88E1}\x{88E8}\x{88F2}\x{88F3}\x{88F4}\x{88F8}\x{88F9}' .
+'\x{88FC}\x{88FD}\x{88FE}\x{8902}\x{8904}\x{8907}\x{890A}\x{890C}\x{8910}' .
+'\x{8912}\x{8913}\x{891D}\x{891E}\x{8925}\x{892A}\x{892B}\x{8936}\x{8938}' .
+'\x{893B}\x{8941}\x{8943}\x{8944}\x{894C}\x{894D}\x{8956}\x{895E}\x{895F}' .
+'\x{8960}\x{8964}\x{8966}\x{896A}\x{896D}\x{896F}\x{8972}\x{8974}\x{8977}' .
+'\x{897E}\x{897F}\x{8981}\x{8983}\x{8986}\x{8987}\x{8988}\x{898A}\x{898B}' .
+'\x{898F}\x{8993}\x{8996}\x{8997}\x{8998}\x{899A}\x{89A1}\x{89A6}\x{89A7}' .
+'\x{89A9}\x{89AA}\x{89AC}\x{89AF}\x{89B2}\x{89B3}\x{89BA}\x{89BD}\x{89BF}' .
+'\x{89C0}\x{89D2}\x{89DA}\x{89DC}\x{89DD}\x{89E3}\x{89E6}\x{89E7}\x{89F4}' .
+'\x{89F8}\x{8A00}\x{8A02}\x{8A03}\x{8A08}\x{8A0A}\x{8A0C}\x{8A0E}\x{8A10}' .
+'\x{8A13}\x{8A16}\x{8A17}\x{8A18}\x{8A1B}\x{8A1D}\x{8A1F}\x{8A23}\x{8A25}' .
+'\x{8A2A}\x{8A2D}\x{8A31}\x{8A33}\x{8A34}\x{8A36}\x{8A3A}\x{8A3B}\x{8A3C}' .
+'\x{8A41}\x{8A46}\x{8A48}\x{8A50}\x{8A51}\x{8A52}\x{8A54}\x{8A55}\x{8A5B}' .
+'\x{8A5E}\x{8A60}\x{8A62}\x{8A63}\x{8A66}\x{8A69}\x{8A6B}\x{8A6C}\x{8A6D}' .
+'\x{8A6E}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A7C}\x{8A82}\x{8A84}\x{8A85}' .
+'\x{8A87}\x{8A89}\x{8A8C}\x{8A8D}\x{8A91}\x{8A93}\x{8A95}\x{8A98}\x{8A9A}' .
+'\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA8}\x{8AAC}' .
+'\x{8AAD}\x{8AB0}\x{8AB2}\x{8AB9}\x{8ABC}\x{8ABF}\x{8AC2}\x{8AC4}\x{8AC7}' .
+'\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACF}\x{8AD2}\x{8AD6}\x{8ADA}\x{8ADB}\x{8ADC}' .
+'\x{8ADE}\x{8AE0}\x{8AE1}\x{8AE2}\x{8AE4}\x{8AE6}\x{8AE7}\x{8AEB}\x{8AED}' .
+'\x{8AEE}\x{8AF1}\x{8AF3}\x{8AF7}\x{8AF8}\x{8AFA}\x{8AFE}\x{8B00}\x{8B01}' .
+'\x{8B02}\x{8B04}\x{8B07}\x{8B0C}\x{8B0E}\x{8B10}\x{8B14}\x{8B16}\x{8B17}' .
+'\x{8B19}\x{8B1A}\x{8B1B}\x{8B1D}\x{8B20}\x{8B21}\x{8B26}\x{8B28}\x{8B2B}' .
+'\x{8B2C}\x{8B33}\x{8B39}\x{8B3E}\x{8B41}\x{8B49}\x{8B4C}\x{8B4E}\x{8B4F}' .
+'\x{8B56}\x{8B58}\x{8B5A}\x{8B5B}\x{8B5C}\x{8B5F}\x{8B66}\x{8B6B}\x{8B6C}' .
+'\x{8B6F}\x{8B70}\x{8B71}\x{8B72}\x{8B74}\x{8B77}\x{8B7D}\x{8B80}\x{8B83}' .
+'\x{8B8A}\x{8B8C}\x{8B8E}\x{8B90}\x{8B92}\x{8B93}\x{8B96}\x{8B99}\x{8B9A}' .
+'\x{8C37}\x{8C3A}\x{8C3F}\x{8C41}\x{8C46}\x{8C48}\x{8C4A}\x{8C4C}\x{8C4E}' .
+'\x{8C50}\x{8C55}\x{8C5A}\x{8C61}\x{8C62}\x{8C6A}\x{8C6B}\x{8C6C}\x{8C78}' .
+'\x{8C79}\x{8C7A}\x{8C7C}\x{8C82}\x{8C85}\x{8C89}\x{8C8A}\x{8C8C}\x{8C8D}' .
+'\x{8C8E}\x{8C94}\x{8C98}\x{8C9D}\x{8C9E}\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA7}' .
+'\x{8CA8}\x{8CA9}\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}' .
+'\x{8CB2}\x{8CB3}\x{8CB4}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CBB}\x{8CBC}\x{8CBD}' .
+'\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}\x{8CC7}\x{8CC8}\x{8CCA}' .
+'\x{8CCD}\x{8CCE}\x{8CD1}\x{8CD3}\x{8CDA}\x{8CDB}\x{8CDC}\x{8CDE}\x{8CE0}' .
+'\x{8CE2}\x{8CE3}\x{8CE4}\x{8CE6}\x{8CEA}\x{8CED}\x{8CFA}\x{8CFB}\x{8CFC}' .
+'\x{8CFD}\x{8D04}\x{8D05}\x{8D07}\x{8D08}\x{8D0A}\x{8D0B}\x{8D0D}\x{8D0F}' .
+'\x{8D10}\x{8D13}\x{8D14}\x{8D16}\x{8D64}\x{8D66}\x{8D67}\x{8D6B}\x{8D6D}' .
+'\x{8D70}\x{8D71}\x{8D73}\x{8D74}\x{8D77}\x{8D81}\x{8D85}\x{8D8A}\x{8D99}' .
+'\x{8DA3}\x{8DA8}\x{8DB3}\x{8DBA}\x{8DBE}\x{8DC2}\x{8DCB}\x{8DCC}\x{8DCF}' .
+'\x{8DD6}\x{8DDA}\x{8DDB}\x{8DDD}\x{8DDF}\x{8DE1}\x{8DE3}\x{8DE8}\x{8DEA}' .
+'\x{8DEB}\x{8DEF}\x{8DF3}\x{8DF5}\x{8DFC}\x{8DFF}\x{8E08}\x{8E09}\x{8E0A}' .
+'\x{8E0F}\x{8E10}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E2A}\x{8E30}\x{8E34}\x{8E35}' .
+'\x{8E42}\x{8E44}\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4C}\x{8E50}\x{8E55}' .
+'\x{8E59}\x{8E5F}\x{8E60}\x{8E63}\x{8E64}\x{8E72}\x{8E74}\x{8E76}\x{8E7C}' .
+'\x{8E81}\x{8E84}\x{8E85}\x{8E87}\x{8E8A}\x{8E8B}\x{8E8D}\x{8E91}\x{8E93}' .
+'\x{8E94}\x{8E99}\x{8EA1}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAF}\x{8EB0}\x{8EB1}' .
+'\x{8EBE}\x{8EC5}\x{8EC6}\x{8EC8}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ED2}' .
+'\x{8EDB}\x{8EDF}\x{8EE2}\x{8EE3}\x{8EEB}\x{8EF8}\x{8EFB}\x{8EFC}\x{8EFD}' .
+'\x{8EFE}\x{8F03}\x{8F05}\x{8F09}\x{8F0A}\x{8F0C}\x{8F12}\x{8F13}\x{8F14}' .
+'\x{8F15}\x{8F19}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1F}\x{8F26}\x{8F29}\x{8F2A}' .
+'\x{8F2F}\x{8F33}\x{8F38}\x{8F39}\x{8F3B}\x{8F3E}\x{8F3F}\x{8F42}\x{8F44}' .
+'\x{8F45}\x{8F46}\x{8F49}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F57}\x{8F5C}\x{8F5F}' .
+'\x{8F61}\x{8F62}\x{8F63}\x{8F64}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA3}' .
+'\x{8FA7}\x{8FA8}\x{8FAD}\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB7}' .
+'\x{8FBA}\x{8FBB}\x{8FBC}\x{8FBF}\x{8FC2}\x{8FC4}\x{8FC5}\x{8FCE}\x{8FD1}' .
+'\x{8FD4}\x{8FDA}\x{8FE2}\x{8FE5}\x{8FE6}\x{8FE9}\x{8FEA}\x{8FEB}\x{8FED}' .
+'\x{8FEF}\x{8FF0}\x{8FF4}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}\x{8FFD}\x{9000}' .
+'\x{9001}\x{9003}\x{9005}\x{9006}\x{900B}\x{900D}\x{900E}\x{900F}\x{9010}' .
+'\x{9011}\x{9013}\x{9014}\x{9015}\x{9016}\x{9017}\x{9019}\x{901A}\x{901D}' .
+'\x{901E}\x{901F}\x{9020}\x{9021}\x{9022}\x{9023}\x{9027}\x{902E}\x{9031}' .
+'\x{9032}\x{9035}\x{9036}\x{9038}\x{9039}\x{903C}\x{903E}\x{9041}\x{9042}' .
+'\x{9045}\x{9047}\x{9049}\x{904A}\x{904B}\x{904D}\x{904E}\x{904F}\x{9050}' .
+'\x{9051}\x{9052}\x{9053}\x{9054}\x{9055}\x{9056}\x{9058}\x{9059}\x{905C}' .
+'\x{905E}\x{9060}\x{9061}\x{9063}\x{9065}\x{9068}\x{9069}\x{906D}\x{906E}' .
+'\x{906F}\x{9072}\x{9075}\x{9076}\x{9077}\x{9078}\x{907A}\x{907C}\x{907D}' .
+'\x{907F}\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9087}\x{9089}\x{908A}' .
+'\x{908F}\x{9091}\x{90A3}\x{90A6}\x{90A8}\x{90AA}\x{90AF}\x{90B1}\x{90B5}' .
+'\x{90B8}\x{90C1}\x{90CA}\x{90CE}\x{90DB}\x{90E1}\x{90E2}\x{90E4}\x{90E8}' .
+'\x{90ED}\x{90F5}\x{90F7}\x{90FD}\x{9102}\x{9112}\x{9119}\x{912D}\x{9130}' .
+'\x{9132}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}\x{914E}\x{9152}\x{9154}' .
+'\x{9156}\x{9158}\x{9162}\x{9163}\x{9165}\x{9169}\x{916A}\x{916C}\x{9172}' .
+'\x{9173}\x{9175}\x{9177}\x{9178}\x{9182}\x{9187}\x{9189}\x{918B}\x{918D}' .
+'\x{9190}\x{9192}\x{9197}\x{919C}\x{91A2}\x{91A4}\x{91AA}\x{91AB}\x{91AF}' .
+'\x{91B4}\x{91B5}\x{91B8}\x{91BA}\x{91C0}\x{91C1}\x{91C6}\x{91C7}\x{91C8}' .
+'\x{91C9}\x{91CB}\x{91CC}\x{91CD}\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D6}' .
+'\x{91D8}\x{91DB}\x{91DC}\x{91DD}\x{91DF}\x{91E1}\x{91E3}\x{91E6}\x{91E7}' .
+'\x{91F5}\x{91F6}\x{91FC}\x{91FF}\x{920D}\x{920E}\x{9211}\x{9214}\x{9215}' .
+'\x{921E}\x{9229}\x{922C}\x{9234}\x{9237}\x{923F}\x{9244}\x{9245}\x{9248}' .
+'\x{9249}\x{924B}\x{9250}\x{9257}\x{925A}\x{925B}\x{925E}\x{9262}\x{9264}' .
+'\x{9266}\x{9271}\x{927E}\x{9280}\x{9283}\x{9285}\x{9291}\x{9293}\x{9295}' .
+'\x{9296}\x{9298}\x{929A}\x{929B}\x{929C}\x{92AD}\x{92B7}\x{92B9}\x{92CF}' .
+'\x{92D2}\x{92E4}\x{92E9}\x{92EA}\x{92ED}\x{92F2}\x{92F3}\x{92F8}\x{92FA}' .
+'\x{92FC}\x{9306}\x{930F}\x{9310}\x{9318}\x{9319}\x{931A}\x{9320}\x{9322}' .
+'\x{9323}\x{9326}\x{9328}\x{932B}\x{932C}\x{932E}\x{932F}\x{9332}\x{9335}' .
+'\x{933A}\x{933B}\x{9344}\x{934B}\x{934D}\x{9354}\x{9356}\x{935B}\x{935C}' .
+'\x{9360}\x{936C}\x{936E}\x{9375}\x{937C}\x{937E}\x{938C}\x{9394}\x{9396}' .
+'\x{9397}\x{939A}\x{93A7}\x{93AC}\x{93AD}\x{93AE}\x{93B0}\x{93B9}\x{93C3}' .
+'\x{93C8}\x{93D0}\x{93D1}\x{93D6}\x{93D7}\x{93D8}\x{93DD}\x{93E1}\x{93E4}' .
+'\x{93E5}\x{93E8}\x{9403}\x{9407}\x{9410}\x{9413}\x{9414}\x{9418}\x{9419}' .
+'\x{941A}\x{9421}\x{942B}\x{9435}\x{9436}\x{9438}\x{943A}\x{9441}\x{9444}' .
+'\x{9451}\x{9452}\x{9453}\x{945A}\x{945B}\x{945E}\x{9460}\x{9462}\x{946A}' .
+'\x{9470}\x{9475}\x{9477}\x{947C}\x{947D}\x{947E}\x{947F}\x{9481}\x{9577}' .
+'\x{9580}\x{9582}\x{9583}\x{9587}\x{9589}\x{958A}\x{958B}\x{958F}\x{9591}' .
+'\x{9593}\x{9594}\x{9596}\x{9598}\x{9599}\x{95A0}\x{95A2}\x{95A3}\x{95A4}' .
+'\x{95A5}\x{95A7}\x{95A8}\x{95AD}\x{95B2}\x{95B9}\x{95BB}\x{95BC}\x{95BE}' .
+'\x{95C3}\x{95C7}\x{95CA}\x{95CC}\x{95CD}\x{95D4}\x{95D5}\x{95D6}\x{95D8}' .
+'\x{95DC}\x{95E1}\x{95E2}\x{95E5}\x{961C}\x{9621}\x{9628}\x{962A}\x{962E}' .
+'\x{962F}\x{9632}\x{963B}\x{963F}\x{9640}\x{9642}\x{9644}\x{964B}\x{964C}' .
+'\x{964D}\x{964F}\x{9650}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9662}' .
+'\x{9663}\x{9664}\x{9665}\x{9666}\x{966A}\x{966C}\x{9670}\x{9672}\x{9673}' .
+'\x{9675}\x{9676}\x{9677}\x{9678}\x{967A}\x{967D}\x{9685}\x{9686}\x{9688}' .
+'\x{968A}\x{968B}\x{968D}\x{968E}\x{968F}\x{9694}\x{9695}\x{9697}\x{9698}' .
+'\x{9699}\x{969B}\x{969C}\x{96A0}\x{96A3}\x{96A7}\x{96A8}\x{96AA}\x{96B0}' .
+'\x{96B1}\x{96B2}\x{96B4}\x{96B6}\x{96B7}\x{96B8}\x{96B9}\x{96BB}\x{96BC}' .
+'\x{96C0}\x{96C1}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C9}\x{96CB}\x{96CC}' .
+'\x{96CD}\x{96CE}\x{96D1}\x{96D5}\x{96D6}\x{96D9}\x{96DB}\x{96DC}\x{96E2}' .
+'\x{96E3}\x{96E8}\x{96EA}\x{96EB}\x{96F0}\x{96F2}\x{96F6}\x{96F7}\x{96F9}' .
+'\x{96FB}\x{9700}\x{9704}\x{9706}\x{9707}\x{9708}\x{970A}\x{970D}\x{970E}' .
+'\x{970F}\x{9711}\x{9713}\x{9716}\x{9719}\x{971C}\x{971E}\x{9724}\x{9727}' .
+'\x{972A}\x{9730}\x{9732}\x{9738}\x{9739}\x{973D}\x{973E}\x{9742}\x{9744}' .
+'\x{9746}\x{9748}\x{9749}\x{9752}\x{9756}\x{9759}\x{975C}\x{975E}\x{9760}' .
+'\x{9761}\x{9762}\x{9764}\x{9766}\x{9768}\x{9769}\x{976B}\x{976D}\x{9771}' .
+'\x{9774}\x{9779}\x{977A}\x{977C}\x{9781}\x{9784}\x{9785}\x{9786}\x{978B}' .
+'\x{978D}\x{978F}\x{9790}\x{9798}\x{979C}\x{97A0}\x{97A3}\x{97A6}\x{97A8}' .
+'\x{97AB}\x{97AD}\x{97B3}\x{97B4}\x{97C3}\x{97C6}\x{97C8}\x{97CB}\x{97D3}' .
+'\x{97DC}\x{97ED}\x{97EE}\x{97F2}\x{97F3}\x{97F5}\x{97F6}\x{97FB}\x{97FF}' .
+'\x{9801}\x{9802}\x{9803}\x{9805}\x{9806}\x{9808}\x{980C}\x{980F}\x{9810}' .
+'\x{9811}\x{9812}\x{9813}\x{9817}\x{9818}\x{981A}\x{9821}\x{9824}\x{982C}' .
+'\x{982D}\x{9834}\x{9837}\x{9838}\x{983B}\x{983C}\x{983D}\x{9846}\x{984B}' .
+'\x{984C}\x{984D}\x{984E}\x{984F}\x{9854}\x{9855}\x{9858}\x{985B}\x{985E}' .
+'\x{9867}\x{986B}\x{986F}\x{9870}\x{9871}\x{9873}\x{9874}\x{98A8}\x{98AA}' .
+'\x{98AF}\x{98B1}\x{98B6}\x{98C3}\x{98C4}\x{98C6}\x{98DB}\x{98DC}\x{98DF}' .
+'\x{98E2}\x{98E9}\x{98EB}\x{98ED}\x{98EE}\x{98EF}\x{98F2}\x{98F4}\x{98FC}' .
+'\x{98FD}\x{98FE}\x{9903}\x{9905}\x{9909}\x{990A}\x{990C}\x{9910}\x{9912}' .
+'\x{9913}\x{9914}\x{9918}\x{991D}\x{991E}\x{9920}\x{9921}\x{9924}\x{9928}' .
+'\x{992C}\x{992E}\x{993D}\x{993E}\x{9942}\x{9945}\x{9949}\x{994B}\x{994C}' .
+'\x{9950}\x{9951}\x{9952}\x{9955}\x{9957}\x{9996}\x{9997}\x{9998}\x{9999}' .
+'\x{99A5}\x{99A8}\x{99AC}\x{99AD}\x{99AE}\x{99B3}\x{99B4}\x{99BC}\x{99C1}' .
+'\x{99C4}\x{99C5}\x{99C6}\x{99C8}\x{99D0}\x{99D1}\x{99D2}\x{99D5}\x{99D8}' .
+'\x{99DB}\x{99DD}\x{99DF}\x{99E2}\x{99ED}\x{99EE}\x{99F1}\x{99F2}\x{99F8}' .
+'\x{99FB}\x{99FF}\x{9A01}\x{9A05}\x{9A0E}\x{9A0F}\x{9A12}\x{9A13}\x{9A19}' .
+'\x{9A28}\x{9A2B}\x{9A30}\x{9A37}\x{9A3E}\x{9A40}\x{9A42}\x{9A43}\x{9A45}' .
+'\x{9A4D}\x{9A55}\x{9A57}\x{9A5A}\x{9A5B}\x{9A5F}\x{9A62}\x{9A64}\x{9A65}' .
+'\x{9A69}\x{9A6A}\x{9A6B}\x{9AA8}\x{9AAD}\x{9AB0}\x{9AB8}\x{9ABC}\x{9AC0}' .
+'\x{9AC4}\x{9ACF}\x{9AD1}\x{9AD3}\x{9AD4}\x{9AD8}\x{9ADE}\x{9ADF}\x{9AE2}' .
+'\x{9AE3}\x{9AE6}\x{9AEA}\x{9AEB}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF4}' .
+'\x{9AF7}\x{9AFB}\x{9B06}\x{9B18}\x{9B1A}\x{9B1F}\x{9B22}\x{9B23}\x{9B25}' .
+'\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2E}\x{9B2F}\x{9B31}\x{9B32}\x{9B3B}' .
+'\x{9B3C}\x{9B41}\x{9B42}\x{9B43}\x{9B44}\x{9B45}\x{9B4D}\x{9B4E}\x{9B4F}' .
+'\x{9B51}\x{9B54}\x{9B58}\x{9B5A}\x{9B6F}\x{9B74}\x{9B83}\x{9B8E}\x{9B91}' .
+'\x{9B92}\x{9B93}\x{9B96}\x{9B97}\x{9B9F}\x{9BA0}\x{9BA8}\x{9BAA}\x{9BAB}' .
+'\x{9BAD}\x{9BAE}\x{9BB4}\x{9BB9}\x{9BC0}\x{9BC6}\x{9BC9}\x{9BCA}\x{9BCF}' .
+'\x{9BD1}\x{9BD2}\x{9BD4}\x{9BD6}\x{9BDB}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}' .
+'\x{9BE8}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF5}\x{9C04}\x{9C06}\x{9C08}\x{9C09}' .
+'\x{9C0A}\x{9C0C}\x{9C0D}\x{9C10}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C1B}' .
+'\x{9C21}\x{9C24}\x{9C25}\x{9C2D}\x{9C2E}\x{9C2F}\x{9C30}\x{9C32}\x{9C39}' .
+'\x{9C3A}\x{9C3B}\x{9C3E}\x{9C46}\x{9C47}\x{9C48}\x{9C52}\x{9C57}\x{9C5A}' .
+'\x{9C60}\x{9C67}\x{9C76}\x{9C78}\x{9CE5}\x{9CE7}\x{9CE9}\x{9CEB}\x{9CEC}' .
+'\x{9CF0}\x{9CF3}\x{9CF4}\x{9CF6}\x{9D03}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' .
+'\x{9D0E}\x{9D12}\x{9D15}\x{9D1B}\x{9D1F}\x{9D23}\x{9D26}\x{9D28}\x{9D2A}' .
+'\x{9D2B}\x{9D2C}\x{9D3B}\x{9D3E}\x{9D3F}\x{9D41}\x{9D44}\x{9D46}\x{9D48}' .
+'\x{9D50}\x{9D51}\x{9D59}\x{9D5C}\x{9D5D}\x{9D5E}\x{9D60}\x{9D61}\x{9D64}' .
+'\x{9D6C}\x{9D6F}\x{9D72}\x{9D7A}\x{9D87}\x{9D89}\x{9D8F}\x{9D9A}\x{9DA4}' .
+'\x{9DA9}\x{9DAB}\x{9DAF}\x{9DB2}\x{9DB4}\x{9DB8}\x{9DBA}\x{9DBB}\x{9DC1}' .
+'\x{9DC2}\x{9DC4}\x{9DC6}\x{9DCF}\x{9DD3}\x{9DD9}\x{9DE6}\x{9DED}\x{9DEF}' .
+'\x{9DF2}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFD}\x{9E1A}\x{9E1B}\x{9E1E}\x{9E75}' .
+'\x{9E78}\x{9E79}\x{9E7D}\x{9E7F}\x{9E81}\x{9E88}\x{9E8B}\x{9E8C}\x{9E91}' .
+'\x{9E92}\x{9E93}\x{9E95}\x{9E97}\x{9E9D}\x{9E9F}\x{9EA5}\x{9EA6}\x{9EA9}' .
+'\x{9EAA}\x{9EAD}\x{9EB8}\x{9EB9}\x{9EBA}\x{9EBB}\x{9EBC}\x{9EBE}\x{9EBF}' .
+'\x{9EC4}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED2}\x{9ED4}\x{9ED8}' .
+'\x{9ED9}\x{9EDB}\x{9EDC}\x{9EDD}\x{9EDE}\x{9EE0}\x{9EE5}\x{9EE8}\x{9EEF}' .
+'\x{9EF4}\x{9EF6}\x{9EF7}\x{9EF9}\x{9EFB}\x{9EFC}\x{9EFD}\x{9F07}\x{9F08}' .
+'\x{9F0E}\x{9F13}\x{9F15}\x{9F20}\x{9F21}\x{9F2C}\x{9F3B}\x{9F3E}\x{9F4A}' .
+'\x{9F4B}\x{9F4E}\x{9F4F}\x{9F52}\x{9F54}\x{9F5F}\x{9F60}\x{9F61}\x{9F62}' .
+'\x{9F63}\x{9F66}\x{9F67}\x{9F6A}\x{9F6C}\x{9F72}\x{9F76}\x{9F77}\x{9F8D}' .
+'\x{9F95}\x{9F9C}\x{9F9D}\x{9FA0}]{1,15}$/iu',
+ 12 => '/^[\x{002d}0-9a-z\x{3447}\x{3473}\x{359E}\x{360E}\x{361A}\x{3918}\x{396E}\x{39CF}\x{39D0}' .
+'\x{39DF}\x{3A73}\x{3B4E}\x{3C6E}\x{3CE0}\x{4056}\x{415F}\x{4337}\x{43AC}' .
+'\x{43B1}\x{43DD}\x{44D6}\x{464C}\x{4661}\x{4723}\x{4729}\x{477C}\x{478D}' .
+'\x{4947}\x{497A}\x{497D}\x{4982}\x{4983}\x{4985}\x{4986}\x{499B}\x{499F}' .
+'\x{49B6}\x{49B7}\x{4C77}\x{4C9F}\x{4CA0}\x{4CA1}\x{4CA2}\x{4CA3}\x{4D13}' .
+'\x{4D14}\x{4D15}\x{4D16}\x{4D17}\x{4D18}\x{4D19}\x{4DAE}\x{4E00}\x{4E01}' .
+'\x{4E02}\x{4E03}\x{4E04}\x{4E05}\x{4E06}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' .
+'\x{4E0B}\x{4E0C}\x{4E0D}\x{4E0E}\x{4E0F}\x{4E10}\x{4E11}\x{4E13}\x{4E14}' .
+'\x{4E15}\x{4E16}\x{4E17}\x{4E18}\x{4E19}\x{4E1A}\x{4E1B}\x{4E1C}\x{4E1D}' .
+'\x{4E1E}\x{4E1F}\x{4E20}\x{4E21}\x{4E22}\x{4E23}\x{4E24}\x{4E25}\x{4E26}' .
+'\x{4E27}\x{4E28}\x{4E2A}\x{4E2B}\x{4E2C}\x{4E2D}\x{4E2E}\x{4E2F}\x{4E30}' .
+'\x{4E31}\x{4E32}\x{4E33}\x{4E34}\x{4E35}\x{4E36}\x{4E37}\x{4E38}\x{4E39}' .
+'\x{4E3A}\x{4E3B}\x{4E3C}\x{4E3D}\x{4E3E}\x{4E3F}\x{4E40}\x{4E41}\x{4E42}' .
+'\x{4E43}\x{4E44}\x{4E45}\x{4E46}\x{4E47}\x{4E48}\x{4E49}\x{4E4A}\x{4E4B}' .
+'\x{4E4C}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E50}\x{4E51}\x{4E52}\x{4E53}\x{4E54}' .
+'\x{4E56}\x{4E57}\x{4E58}\x{4E59}\x{4E5A}\x{4E5B}\x{4E5C}\x{4E5D}\x{4E5E}' .
+'\x{4E5F}\x{4E60}\x{4E61}\x{4E62}\x{4E63}\x{4E64}\x{4E65}\x{4E66}\x{4E67}' .
+'\x{4E69}\x{4E6A}\x{4E6B}\x{4E6C}\x{4E6D}\x{4E6E}\x{4E6F}\x{4E70}\x{4E71}' .
+'\x{4E72}\x{4E73}\x{4E74}\x{4E75}\x{4E76}\x{4E77}\x{4E78}\x{4E7A}\x{4E7B}' .
+'\x{4E7C}\x{4E7D}\x{4E7E}\x{4E7F}\x{4E80}\x{4E81}\x{4E82}\x{4E83}\x{4E84}' .
+'\x{4E85}\x{4E86}\x{4E87}\x{4E88}\x{4E89}\x{4E8B}\x{4E8C}\x{4E8D}\x{4E8E}' .
+'\x{4E8F}\x{4E90}\x{4E91}\x{4E92}\x{4E93}\x{4E94}\x{4E95}\x{4E97}\x{4E98}' .
+'\x{4E99}\x{4E9A}\x{4E9B}\x{4E9C}\x{4E9D}\x{4E9E}\x{4E9F}\x{4EA0}\x{4EA1}' .
+'\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA7}\x{4EA8}\x{4EA9}\x{4EAA}\x{4EAB}' .
+'\x{4EAC}\x{4EAD}\x{4EAE}\x{4EAF}\x{4EB0}\x{4EB1}\x{4EB2}\x{4EB3}\x{4EB4}' .
+'\x{4EB5}\x{4EB6}\x{4EB7}\x{4EB8}\x{4EB9}\x{4EBA}\x{4EBB}\x{4EBD}\x{4EBE}' .
+'\x{4EBF}\x{4EC0}\x{4EC1}\x{4EC2}\x{4EC3}\x{4EC4}\x{4EC5}\x{4EC6}\x{4EC7}' .
+'\x{4EC8}\x{4EC9}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED0}\x{4ED1}' .
+'\x{4ED2}\x{4ED3}\x{4ED4}\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDA}' .
+'\x{4EDB}\x{4EDC}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE0}\x{4EE1}\x{4EE2}\x{4EE3}' .
+'\x{4EE4}\x{4EE5}\x{4EE6}\x{4EE8}\x{4EE9}\x{4EEA}\x{4EEB}\x{4EEC}\x{4EEF}' .
+'\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EFB}' .
+'\x{4EFD}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}' .
+'\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}' .
+'\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}' .
+'\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}' .
+'\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}' .
+'\x{4F2E}\x{4F2F}\x{4F30}\x{4F32}\x{4F33}\x{4F34}\x{4F36}\x{4F38}\x{4F39}' .
+'\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F41}\x{4F42}\x{4F43}' .
+'\x{4F45}\x{4F46}\x{4F47}\x{4F48}\x{4F49}\x{4F4A}\x{4F4B}\x{4F4C}\x{4F4D}' .
+'\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}\x{4F52}\x{4F53}\x{4F54}\x{4F55}\x{4F56}' .
+'\x{4F57}\x{4F58}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}\x{4F5F}' .
+'\x{4F60}\x{4F61}\x{4F62}\x{4F63}\x{4F64}\x{4F65}\x{4F66}\x{4F67}\x{4F68}' .
+'\x{4F69}\x{4F6A}\x{4F6B}\x{4F6C}\x{4F6D}\x{4F6E}\x{4F6F}\x{4F70}\x{4F72}' .
+'\x{4F73}\x{4F74}\x{4F75}\x{4F76}\x{4F77}\x{4F78}\x{4F79}\x{4F7A}\x{4F7B}' .
+'\x{4F7C}\x{4F7D}\x{4F7E}\x{4F7F}\x{4F80}\x{4F81}\x{4F82}\x{4F83}\x{4F84}' .
+'\x{4F85}\x{4F86}\x{4F87}\x{4F88}\x{4F89}\x{4F8A}\x{4F8B}\x{4F8D}\x{4F8F}' .
+'\x{4F90}\x{4F91}\x{4F92}\x{4F93}\x{4F94}\x{4F95}\x{4F96}\x{4F97}\x{4F98}' .
+'\x{4F99}\x{4F9A}\x{4F9B}\x{4F9C}\x{4F9D}\x{4F9E}\x{4F9F}\x{4FA0}\x{4FA1}' .
+'\x{4FA3}\x{4FA4}\x{4FA5}\x{4FA6}\x{4FA7}\x{4FA8}\x{4FA9}\x{4FAA}\x{4FAB}' .
+'\x{4FAC}\x{4FAE}\x{4FAF}\x{4FB0}\x{4FB1}\x{4FB2}\x{4FB3}\x{4FB4}\x{4FB5}' .
+'\x{4FB6}\x{4FB7}\x{4FB8}\x{4FB9}\x{4FBA}\x{4FBB}\x{4FBC}\x{4FBE}\x{4FBF}' .
+'\x{4FC0}\x{4FC1}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FC5}\x{4FC7}\x{4FC9}\x{4FCA}' .
+'\x{4FCB}\x{4FCD}\x{4FCE}\x{4FCF}\x{4FD0}\x{4FD1}\x{4FD2}\x{4FD3}\x{4FD4}' .
+'\x{4FD5}\x{4FD6}\x{4FD7}\x{4FD8}\x{4FD9}\x{4FDA}\x{4FDB}\x{4FDC}\x{4FDD}' .
+'\x{4FDE}\x{4FDF}\x{4FE0}\x{4FE1}\x{4FE3}\x{4FE4}\x{4FE5}\x{4FE6}\x{4FE7}' .
+'\x{4FE8}\x{4FE9}\x{4FEA}\x{4FEB}\x{4FEC}\x{4FED}\x{4FEE}\x{4FEF}\x{4FF0}' .
+'\x{4FF1}\x{4FF2}\x{4FF3}\x{4FF4}\x{4FF5}\x{4FF6}\x{4FF7}\x{4FF8}\x{4FF9}' .
+'\x{4FFA}\x{4FFB}\x{4FFE}\x{4FFF}\x{5000}\x{5001}\x{5002}\x{5003}\x{5004}' .
+'\x{5005}\x{5006}\x{5007}\x{5008}\x{5009}\x{500A}\x{500B}\x{500C}\x{500D}' .
+'\x{500E}\x{500F}\x{5011}\x{5012}\x{5013}\x{5014}\x{5015}\x{5016}\x{5017}' .
+'\x{5018}\x{5019}\x{501A}\x{501B}\x{501C}\x{501D}\x{501E}\x{501F}\x{5020}' .
+'\x{5021}\x{5022}\x{5023}\x{5024}\x{5025}\x{5026}\x{5027}\x{5028}\x{5029}' .
+'\x{502A}\x{502B}\x{502C}\x{502D}\x{502E}\x{502F}\x{5030}\x{5031}\x{5032}' .
+'\x{5033}\x{5035}\x{5036}\x{5037}\x{5039}\x{503A}\x{503B}\x{503C}\x{503E}' .
+'\x{503F}\x{5040}\x{5041}\x{5043}\x{5044}\x{5045}\x{5046}\x{5047}\x{5048}' .
+'\x{5049}\x{504A}\x{504B}\x{504C}\x{504D}\x{504E}\x{504F}\x{5051}\x{5053}' .
+'\x{5054}\x{5055}\x{5056}\x{5057}\x{5059}\x{505A}\x{505B}\x{505C}\x{505D}' .
+'\x{505E}\x{505F}\x{5060}\x{5061}\x{5062}\x{5063}\x{5064}\x{5065}\x{5066}' .
+'\x{5067}\x{5068}\x{5069}\x{506A}\x{506B}\x{506C}\x{506D}\x{506E}\x{506F}' .
+'\x{5070}\x{5071}\x{5072}\x{5073}\x{5074}\x{5075}\x{5076}\x{5077}\x{5078}' .
+'\x{5079}\x{507A}\x{507B}\x{507D}\x{507E}\x{507F}\x{5080}\x{5082}\x{5083}' .
+'\x{5084}\x{5085}\x{5086}\x{5087}\x{5088}\x{5089}\x{508A}\x{508B}\x{508C}' .
+'\x{508D}\x{508E}\x{508F}\x{5090}\x{5091}\x{5092}\x{5094}\x{5095}\x{5096}' .
+'\x{5098}\x{5099}\x{509A}\x{509B}\x{509C}\x{509D}\x{509E}\x{50A2}\x{50A3}' .
+'\x{50A4}\x{50A5}\x{50A6}\x{50A7}\x{50A8}\x{50A9}\x{50AA}\x{50AB}\x{50AC}' .
+'\x{50AD}\x{50AE}\x{50AF}\x{50B0}\x{50B1}\x{50B2}\x{50B3}\x{50B4}\x{50B5}' .
+'\x{50B6}\x{50B7}\x{50B8}\x{50BA}\x{50BB}\x{50BC}\x{50BD}\x{50BE}\x{50BF}' .
+'\x{50C0}\x{50C1}\x{50C2}\x{50C4}\x{50C5}\x{50C6}\x{50C7}\x{50C8}\x{50C9}' .
+'\x{50CA}\x{50CB}\x{50CC}\x{50CD}\x{50CE}\x{50CF}\x{50D0}\x{50D1}\x{50D2}' .
+'\x{50D3}\x{50D4}\x{50D5}\x{50D6}\x{50D7}\x{50D9}\x{50DA}\x{50DB}\x{50DC}' .
+'\x{50DD}\x{50DE}\x{50E0}\x{50E3}\x{50E4}\x{50E5}\x{50E6}\x{50E7}\x{50E8}' .
+'\x{50E9}\x{50EA}\x{50EC}\x{50ED}\x{50EE}\x{50EF}\x{50F0}\x{50F1}\x{50F2}' .
+'\x{50F3}\x{50F5}\x{50F6}\x{50F8}\x{50F9}\x{50FA}\x{50FB}\x{50FC}\x{50FD}' .
+'\x{50FE}\x{50FF}\x{5100}\x{5101}\x{5102}\x{5103}\x{5104}\x{5105}\x{5106}' .
+'\x{5107}\x{5108}\x{5109}\x{510A}\x{510B}\x{510C}\x{510D}\x{510E}\x{510F}' .
+'\x{5110}\x{5111}\x{5112}\x{5113}\x{5114}\x{5115}\x{5116}\x{5117}\x{5118}' .
+'\x{5119}\x{511A}\x{511C}\x{511D}\x{511E}\x{511F}\x{5120}\x{5121}\x{5122}' .
+'\x{5123}\x{5124}\x{5125}\x{5126}\x{5127}\x{5129}\x{512A}\x{512C}\x{512D}' .
+'\x{512E}\x{512F}\x{5130}\x{5131}\x{5132}\x{5133}\x{5134}\x{5135}\x{5136}' .
+'\x{5137}\x{5138}\x{5139}\x{513A}\x{513B}\x{513C}\x{513D}\x{513E}\x{513F}' .
+'\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' .
+'\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5151}\x{5152}\x{5154}\x{5155}' .
+'\x{5156}\x{5157}\x{5159}\x{515A}\x{515B}\x{515C}\x{515D}\x{515E}\x{515F}' .
+'\x{5161}\x{5162}\x{5163}\x{5165}\x{5166}\x{5167}\x{5168}\x{5169}\x{516A}' .
+'\x{516B}\x{516C}\x{516D}\x{516E}\x{516F}\x{5170}\x{5171}\x{5173}\x{5174}' .
+'\x{5175}\x{5176}\x{5177}\x{5178}\x{5179}\x{517A}\x{517B}\x{517C}\x{517D}' .
+'\x{517F}\x{5180}\x{5181}\x{5182}\x{5185}\x{5186}\x{5187}\x{5188}\x{5189}' .
+'\x{518A}\x{518B}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}\x{5193}' .
+'\x{5194}\x{5195}\x{5196}\x{5197}\x{5198}\x{5199}\x{519A}\x{519B}\x{519C}' .
+'\x{519D}\x{519E}\x{519F}\x{51A0}\x{51A2}\x{51A4}\x{51A5}\x{51A6}\x{51A7}' .
+'\x{51A8}\x{51AA}\x{51AB}\x{51AC}\x{51AE}\x{51AF}\x{51B0}\x{51B1}\x{51B2}' .
+'\x{51B3}\x{51B5}\x{51B6}\x{51B7}\x{51B9}\x{51BB}\x{51BC}\x{51BD}\x{51BE}' .
+'\x{51BF}\x{51C0}\x{51C1}\x{51C3}\x{51C4}\x{51C5}\x{51C6}\x{51C7}\x{51C8}' .
+'\x{51C9}\x{51CA}\x{51CB}\x{51CC}\x{51CD}\x{51CE}\x{51CF}\x{51D0}\x{51D1}' .
+'\x{51D4}\x{51D5}\x{51D6}\x{51D7}\x{51D8}\x{51D9}\x{51DA}\x{51DB}\x{51DC}' .
+'\x{51DD}\x{51DE}\x{51E0}\x{51E1}\x{51E2}\x{51E3}\x{51E4}\x{51E5}\x{51E7}' .
+'\x{51E8}\x{51E9}\x{51EA}\x{51EB}\x{51ED}\x{51EF}\x{51F0}\x{51F1}\x{51F3}' .
+'\x{51F4}\x{51F5}\x{51F6}\x{51F7}\x{51F8}\x{51F9}\x{51FA}\x{51FB}\x{51FC}' .
+'\x{51FD}\x{51FE}\x{51FF}\x{5200}\x{5201}\x{5202}\x{5203}\x{5204}\x{5205}' .
+'\x{5206}\x{5207}\x{5208}\x{5209}\x{520A}\x{520B}\x{520C}\x{520D}\x{520E}' .
+'\x{520F}\x{5210}\x{5211}\x{5212}\x{5213}\x{5214}\x{5215}\x{5216}\x{5217}' .
+'\x{5218}\x{5219}\x{521A}\x{521B}\x{521C}\x{521D}\x{521E}\x{521F}\x{5220}' .
+'\x{5221}\x{5222}\x{5223}\x{5224}\x{5225}\x{5226}\x{5228}\x{5229}\x{522A}' .
+'\x{522B}\x{522C}\x{522D}\x{522E}\x{522F}\x{5230}\x{5231}\x{5232}\x{5233}' .
+'\x{5234}\x{5235}\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{523C}' .
+'\x{523D}\x{523E}\x{523F}\x{5240}\x{5241}\x{5242}\x{5243}\x{5244}\x{5245}' .
+'\x{5246}\x{5247}\x{5248}\x{5249}\x{524A}\x{524B}\x{524C}\x{524D}\x{524E}' .
+'\x{5250}\x{5251}\x{5252}\x{5254}\x{5255}\x{5256}\x{5257}\x{5258}\x{5259}' .
+'\x{525A}\x{525B}\x{525C}\x{525D}\x{525E}\x{525F}\x{5260}\x{5261}\x{5262}' .
+'\x{5263}\x{5264}\x{5265}\x{5267}\x{5268}\x{5269}\x{526A}\x{526B}\x{526C}' .
+'\x{526D}\x{526E}\x{526F}\x{5270}\x{5272}\x{5273}\x{5274}\x{5275}\x{5276}' .
+'\x{5277}\x{5278}\x{527A}\x{527B}\x{527C}\x{527D}\x{527E}\x{527F}\x{5280}' .
+'\x{5281}\x{5282}\x{5283}\x{5284}\x{5286}\x{5287}\x{5288}\x{5289}\x{528A}' .
+'\x{528B}\x{528C}\x{528D}\x{528F}\x{5290}\x{5291}\x{5292}\x{5293}\x{5294}' .
+'\x{5295}\x{5296}\x{5297}\x{5298}\x{5299}\x{529A}\x{529B}\x{529C}\x{529D}' .
+'\x{529E}\x{529F}\x{52A0}\x{52A1}\x{52A2}\x{52A3}\x{52A5}\x{52A6}\x{52A7}' .
+'\x{52A8}\x{52A9}\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52AE}\x{52AF}\x{52B0}' .
+'\x{52B1}\x{52B2}\x{52B3}\x{52B4}\x{52B5}\x{52B6}\x{52B7}\x{52B8}\x{52B9}' .
+'\x{52BA}\x{52BB}\x{52BC}\x{52BD}\x{52BE}\x{52BF}\x{52C0}\x{52C1}\x{52C2}' .
+'\x{52C3}\x{52C6}\x{52C7}\x{52C9}\x{52CA}\x{52CB}\x{52CD}\x{52CF}\x{52D0}' .
+'\x{52D2}\x{52D3}\x{52D5}\x{52D6}\x{52D7}\x{52D8}\x{52D9}\x{52DA}\x{52DB}' .
+'\x{52DC}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}\x{52E4}\x{52E6}' .
+'\x{52E7}\x{52E8}\x{52E9}\x{52EA}\x{52EB}\x{52EC}\x{52ED}\x{52EF}\x{52F0}' .
+'\x{52F1}\x{52F2}\x{52F3}\x{52F4}\x{52F5}\x{52F6}\x{52F7}\x{52F8}\x{52F9}' .
+'\x{52FA}\x{52FB}\x{52FC}\x{52FD}\x{52FE}\x{52FF}\x{5300}\x{5301}\x{5302}' .
+'\x{5305}\x{5306}\x{5307}\x{5308}\x{5309}\x{530A}\x{530B}\x{530C}\x{530D}' .
+'\x{530E}\x{530F}\x{5310}\x{5311}\x{5312}\x{5313}\x{5314}\x{5315}\x{5316}' .
+'\x{5317}\x{5319}\x{531A}\x{531C}\x{531D}\x{531F}\x{5320}\x{5321}\x{5322}' .
+'\x{5323}\x{5324}\x{5325}\x{5326}\x{5328}\x{532A}\x{532B}\x{532C}\x{532D}' .
+'\x{532E}\x{532F}\x{5330}\x{5331}\x{5333}\x{5334}\x{5337}\x{5339}\x{533A}' .
+'\x{533B}\x{533C}\x{533D}\x{533E}\x{533F}\x{5340}\x{5341}\x{5343}\x{5344}' .
+'\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}\x{534A}\x{534B}\x{534C}\x{534D}' .
+'\x{534E}\x{534F}\x{5350}\x{5351}\x{5352}\x{5353}\x{5354}\x{5355}\x{5356}' .
+'\x{5357}\x{5358}\x{5359}\x{535A}\x{535C}\x{535E}\x{535F}\x{5360}\x{5361}' .
+'\x{5362}\x{5363}\x{5364}\x{5365}\x{5366}\x{5367}\x{5369}\x{536B}\x{536C}' .
+'\x{536E}\x{536F}\x{5370}\x{5371}\x{5372}\x{5373}\x{5374}\x{5375}\x{5376}' .
+'\x{5377}\x{5378}\x{5379}\x{537A}\x{537B}\x{537C}\x{537D}\x{537E}\x{537F}' .
+'\x{5381}\x{5382}\x{5383}\x{5384}\x{5385}\x{5386}\x{5387}\x{5388}\x{5389}' .
+'\x{538A}\x{538B}\x{538C}\x{538D}\x{538E}\x{538F}\x{5390}\x{5391}\x{5392}' .
+'\x{5393}\x{5394}\x{5395}\x{5396}\x{5397}\x{5398}\x{5399}\x{539A}\x{539B}' .
+'\x{539C}\x{539D}\x{539E}\x{539F}\x{53A0}\x{53A2}\x{53A3}\x{53A4}\x{53A5}' .
+'\x{53A6}\x{53A7}\x{53A8}\x{53A9}\x{53AC}\x{53AD}\x{53AE}\x{53B0}\x{53B1}' .
+'\x{53B2}\x{53B3}\x{53B4}\x{53B5}\x{53B6}\x{53B7}\x{53B8}\x{53B9}\x{53BB}' .
+'\x{53BC}\x{53BD}\x{53BE}\x{53BF}\x{53C0}\x{53C1}\x{53C2}\x{53C3}\x{53C4}' .
+'\x{53C6}\x{53C7}\x{53C8}\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}' .
+'\x{53D0}\x{53D1}\x{53D2}\x{53D3}\x{53D4}\x{53D5}\x{53D6}\x{53D7}\x{53D8}' .
+'\x{53D9}\x{53DB}\x{53DC}\x{53DF}\x{53E0}\x{53E1}\x{53E2}\x{53E3}\x{53E4}' .
+'\x{53E5}\x{53E6}\x{53E8}\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}' .
+'\x{53EF}\x{53F0}\x{53F1}\x{53F2}\x{53F3}\x{53F4}\x{53F5}\x{53F6}\x{53F7}' .
+'\x{53F8}\x{53F9}\x{53FA}\x{53FB}\x{53FC}\x{53FD}\x{53FE}\x{5401}\x{5402}' .
+'\x{5403}\x{5404}\x{5405}\x{5406}\x{5407}\x{5408}\x{5409}\x{540A}\x{540B}' .
+'\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}\x{5411}\x{5412}\x{5413}\x{5414}' .
+'\x{5415}\x{5416}\x{5417}\x{5418}\x{5419}\x{541B}\x{541C}\x{541D}\x{541E}' .
+'\x{541F}\x{5420}\x{5421}\x{5423}\x{5424}\x{5425}\x{5426}\x{5427}\x{5428}' .
+'\x{5429}\x{542A}\x{542B}\x{542C}\x{542D}\x{542E}\x{542F}\x{5430}\x{5431}' .
+'\x{5432}\x{5433}\x{5434}\x{5435}\x{5436}\x{5437}\x{5438}\x{5439}\x{543A}' .
+'\x{543B}\x{543C}\x{543D}\x{543E}\x{543F}\x{5440}\x{5441}\x{5442}\x{5443}' .
+'\x{5444}\x{5445}\x{5446}\x{5447}\x{5448}\x{5449}\x{544A}\x{544B}\x{544D}' .
+'\x{544E}\x{544F}\x{5450}\x{5451}\x{5452}\x{5453}\x{5454}\x{5455}\x{5456}' .
+'\x{5457}\x{5458}\x{5459}\x{545A}\x{545B}\x{545C}\x{545E}\x{545F}\x{5460}' .
+'\x{5461}\x{5462}\x{5463}\x{5464}\x{5465}\x{5466}\x{5467}\x{5468}\x{546A}' .
+'\x{546B}\x{546C}\x{546D}\x{546E}\x{546F}\x{5470}\x{5471}\x{5472}\x{5473}' .
+'\x{5474}\x{5475}\x{5476}\x{5477}\x{5478}\x{5479}\x{547A}\x{547B}\x{547C}' .
+'\x{547D}\x{547E}\x{547F}\x{5480}\x{5481}\x{5482}\x{5483}\x{5484}\x{5485}' .
+'\x{5486}\x{5487}\x{5488}\x{5489}\x{548B}\x{548C}\x{548D}\x{548E}\x{548F}' .
+'\x{5490}\x{5491}\x{5492}\x{5493}\x{5494}\x{5495}\x{5496}\x{5497}\x{5498}' .
+'\x{5499}\x{549A}\x{549B}\x{549C}\x{549D}\x{549E}\x{549F}\x{54A0}\x{54A1}' .
+'\x{54A2}\x{54A3}\x{54A4}\x{54A5}\x{54A6}\x{54A7}\x{54A8}\x{54A9}\x{54AA}' .
+'\x{54AB}\x{54AC}\x{54AD}\x{54AE}\x{54AF}\x{54B0}\x{54B1}\x{54B2}\x{54B3}' .
+'\x{54B4}\x{54B6}\x{54B7}\x{54B8}\x{54B9}\x{54BA}\x{54BB}\x{54BC}\x{54BD}' .
+'\x{54BE}\x{54BF}\x{54C0}\x{54C1}\x{54C2}\x{54C3}\x{54C4}\x{54C5}\x{54C6}' .
+'\x{54C7}\x{54C8}\x{54C9}\x{54CA}\x{54CB}\x{54CC}\x{54CD}\x{54CE}\x{54CF}' .
+'\x{54D0}\x{54D1}\x{54D2}\x{54D3}\x{54D4}\x{54D5}\x{54D6}\x{54D7}\x{54D8}' .
+'\x{54D9}\x{54DA}\x{54DB}\x{54DC}\x{54DD}\x{54DE}\x{54DF}\x{54E0}\x{54E1}' .
+'\x{54E2}\x{54E3}\x{54E4}\x{54E5}\x{54E6}\x{54E7}\x{54E8}\x{54E9}\x{54EA}' .
+'\x{54EB}\x{54EC}\x{54ED}\x{54EE}\x{54EF}\x{54F0}\x{54F1}\x{54F2}\x{54F3}' .
+'\x{54F4}\x{54F5}\x{54F7}\x{54F8}\x{54F9}\x{54FA}\x{54FB}\x{54FC}\x{54FD}' .
+'\x{54FE}\x{54FF}\x{5500}\x{5501}\x{5502}\x{5503}\x{5504}\x{5505}\x{5506}' .
+'\x{5507}\x{5508}\x{5509}\x{550A}\x{550B}\x{550C}\x{550D}\x{550E}\x{550F}' .
+'\x{5510}\x{5511}\x{5512}\x{5513}\x{5514}\x{5516}\x{5517}\x{551A}\x{551B}' .
+'\x{551C}\x{551D}\x{551E}\x{551F}\x{5520}\x{5521}\x{5522}\x{5523}\x{5524}' .
+'\x{5525}\x{5526}\x{5527}\x{5528}\x{5529}\x{552A}\x{552B}\x{552C}\x{552D}' .
+'\x{552E}\x{552F}\x{5530}\x{5531}\x{5532}\x{5533}\x{5534}\x{5535}\x{5536}' .
+'\x{5537}\x{5538}\x{5539}\x{553A}\x{553B}\x{553C}\x{553D}\x{553E}\x{553F}' .
+'\x{5540}\x{5541}\x{5542}\x{5543}\x{5544}\x{5545}\x{5546}\x{5548}\x{5549}' .
+'\x{554A}\x{554B}\x{554C}\x{554D}\x{554E}\x{554F}\x{5550}\x{5551}\x{5552}' .
+'\x{5553}\x{5554}\x{5555}\x{5556}\x{5557}\x{5558}\x{5559}\x{555A}\x{555B}' .
+'\x{555C}\x{555D}\x{555E}\x{555F}\x{5561}\x{5562}\x{5563}\x{5564}\x{5565}' .
+'\x{5566}\x{5567}\x{5568}\x{5569}\x{556A}\x{556B}\x{556C}\x{556D}\x{556E}' .
+'\x{556F}\x{5570}\x{5571}\x{5572}\x{5573}\x{5574}\x{5575}\x{5576}\x{5577}' .
+'\x{5578}\x{5579}\x{557B}\x{557C}\x{557D}\x{557E}\x{557F}\x{5580}\x{5581}' .
+'\x{5582}\x{5583}\x{5584}\x{5585}\x{5586}\x{5587}\x{5588}\x{5589}\x{558A}' .
+'\x{558B}\x{558C}\x{558D}\x{558E}\x{558F}\x{5590}\x{5591}\x{5592}\x{5593}' .
+'\x{5594}\x{5595}\x{5596}\x{5597}\x{5598}\x{5599}\x{559A}\x{559B}\x{559C}' .
+'\x{559D}\x{559E}\x{559F}\x{55A0}\x{55A1}\x{55A2}\x{55A3}\x{55A4}\x{55A5}' .
+'\x{55A6}\x{55A7}\x{55A8}\x{55A9}\x{55AA}\x{55AB}\x{55AC}\x{55AD}\x{55AE}' .
+'\x{55AF}\x{55B0}\x{55B1}\x{55B2}\x{55B3}\x{55B4}\x{55B5}\x{55B6}\x{55B7}' .
+'\x{55B8}\x{55B9}\x{55BA}\x{55BB}\x{55BC}\x{55BD}\x{55BE}\x{55BF}\x{55C0}' .
+'\x{55C1}\x{55C2}\x{55C3}\x{55C4}\x{55C5}\x{55C6}\x{55C7}\x{55C8}\x{55C9}' .
+'\x{55CA}\x{55CB}\x{55CC}\x{55CD}\x{55CE}\x{55CF}\x{55D0}\x{55D1}\x{55D2}' .
+'\x{55D3}\x{55D4}\x{55D5}\x{55D6}\x{55D7}\x{55D8}\x{55D9}\x{55DA}\x{55DB}' .
+'\x{55DC}\x{55DD}\x{55DE}\x{55DF}\x{55E1}\x{55E2}\x{55E3}\x{55E4}\x{55E5}' .
+'\x{55E6}\x{55E7}\x{55E8}\x{55E9}\x{55EA}\x{55EB}\x{55EC}\x{55ED}\x{55EE}' .
+'\x{55EF}\x{55F0}\x{55F1}\x{55F2}\x{55F3}\x{55F4}\x{55F5}\x{55F6}\x{55F7}' .
+'\x{55F9}\x{55FA}\x{55FB}\x{55FC}\x{55FD}\x{55FE}\x{55FF}\x{5600}\x{5601}' .
+'\x{5602}\x{5603}\x{5604}\x{5606}\x{5607}\x{5608}\x{5609}\x{560C}\x{560D}' .
+'\x{560E}\x{560F}\x{5610}\x{5611}\x{5612}\x{5613}\x{5614}\x{5615}\x{5616}' .
+'\x{5617}\x{5618}\x{5619}\x{561A}\x{561B}\x{561C}\x{561D}\x{561E}\x{561F}' .
+'\x{5621}\x{5622}\x{5623}\x{5624}\x{5625}\x{5626}\x{5627}\x{5628}\x{5629}' .
+'\x{562A}\x{562C}\x{562D}\x{562E}\x{562F}\x{5630}\x{5631}\x{5632}\x{5633}' .
+'\x{5634}\x{5635}\x{5636}\x{5638}\x{5639}\x{563A}\x{563B}\x{563D}\x{563E}' .
+'\x{563F}\x{5640}\x{5641}\x{5642}\x{5643}\x{5645}\x{5646}\x{5647}\x{5648}' .
+'\x{5649}\x{564A}\x{564C}\x{564D}\x{564E}\x{564F}\x{5650}\x{5652}\x{5653}' .
+'\x{5654}\x{5655}\x{5657}\x{5658}\x{5659}\x{565A}\x{565B}\x{565C}\x{565D}' .
+'\x{565E}\x{5660}\x{5662}\x{5663}\x{5664}\x{5665}\x{5666}\x{5667}\x{5668}' .
+'\x{5669}\x{566A}\x{566B}\x{566C}\x{566D}\x{566E}\x{566F}\x{5670}\x{5671}' .
+'\x{5672}\x{5673}\x{5674}\x{5676}\x{5677}\x{5678}\x{5679}\x{567A}\x{567B}' .
+'\x{567C}\x{567E}\x{567F}\x{5680}\x{5681}\x{5682}\x{5683}\x{5684}\x{5685}' .
+'\x{5686}\x{5687}\x{568A}\x{568C}\x{568D}\x{568E}\x{568F}\x{5690}\x{5691}' .
+'\x{5692}\x{5693}\x{5694}\x{5695}\x{5697}\x{5698}\x{5699}\x{569A}\x{569B}' .
+'\x{569C}\x{569D}\x{569F}\x{56A0}\x{56A1}\x{56A3}\x{56A4}\x{56A5}\x{56A6}' .
+'\x{56A7}\x{56A8}\x{56A9}\x{56AA}\x{56AB}\x{56AC}\x{56AD}\x{56AE}\x{56AF}' .
+'\x{56B0}\x{56B1}\x{56B2}\x{56B3}\x{56B4}\x{56B5}\x{56B6}\x{56B7}\x{56B8}' .
+'\x{56B9}\x{56BB}\x{56BC}\x{56BD}\x{56BE}\x{56BF}\x{56C0}\x{56C1}\x{56C2}' .
+'\x{56C3}\x{56C4}\x{56C5}\x{56C6}\x{56C7}\x{56C8}\x{56C9}\x{56CA}\x{56CB}' .
+'\x{56CC}\x{56CD}\x{56CE}\x{56D0}\x{56D1}\x{56D2}\x{56D3}\x{56D4}\x{56D5}' .
+'\x{56D6}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DC}\x{56DD}\x{56DE}\x{56DF}' .
+'\x{56E0}\x{56E1}\x{56E2}\x{56E3}\x{56E4}\x{56E5}\x{56E7}\x{56E8}\x{56E9}' .
+'\x{56EA}\x{56EB}\x{56EC}\x{56ED}\x{56EE}\x{56EF}\x{56F0}\x{56F1}\x{56F2}' .
+'\x{56F3}\x{56F4}\x{56F5}\x{56F7}\x{56F9}\x{56FA}\x{56FD}\x{56FE}\x{56FF}' .
+'\x{5700}\x{5701}\x{5702}\x{5703}\x{5704}\x{5706}\x{5707}\x{5708}\x{5709}' .
+'\x{570A}\x{570B}\x{570C}\x{570D}\x{570E}\x{570F}\x{5710}\x{5712}\x{5713}' .
+'\x{5714}\x{5715}\x{5716}\x{5718}\x{5719}\x{571A}\x{571B}\x{571C}\x{571D}' .
+'\x{571E}\x{571F}\x{5720}\x{5722}\x{5723}\x{5725}\x{5726}\x{5727}\x{5728}' .
+'\x{5729}\x{572A}\x{572B}\x{572C}\x{572D}\x{572E}\x{572F}\x{5730}\x{5731}' .
+'\x{5732}\x{5733}\x{5734}\x{5735}\x{5736}\x{5737}\x{5738}\x{5739}\x{573A}' .
+'\x{573B}\x{573C}\x{573E}\x{573F}\x{5740}\x{5741}\x{5742}\x{5744}\x{5745}' .
+'\x{5746}\x{5747}\x{5749}\x{574A}\x{574B}\x{574C}\x{574D}\x{574E}\x{574F}' .
+'\x{5750}\x{5751}\x{5752}\x{5753}\x{5754}\x{5757}\x{5759}\x{575A}\x{575B}' .
+'\x{575C}\x{575D}\x{575E}\x{575F}\x{5760}\x{5761}\x{5762}\x{5764}\x{5765}' .
+'\x{5766}\x{5767}\x{5768}\x{5769}\x{576A}\x{576B}\x{576C}\x{576D}\x{576F}' .
+'\x{5770}\x{5771}\x{5772}\x{5773}\x{5774}\x{5775}\x{5776}\x{5777}\x{5779}' .
+'\x{577A}\x{577B}\x{577C}\x{577D}\x{577E}\x{577F}\x{5780}\x{5782}\x{5783}' .
+'\x{5784}\x{5785}\x{5786}\x{5788}\x{5789}\x{578A}\x{578B}\x{578C}\x{578D}' .
+'\x{578E}\x{578F}\x{5790}\x{5791}\x{5792}\x{5793}\x{5794}\x{5795}\x{5797}' .
+'\x{5798}\x{5799}\x{579A}\x{579B}\x{579C}\x{579D}\x{579E}\x{579F}\x{57A0}' .
+'\x{57A1}\x{57A2}\x{57A3}\x{57A4}\x{57A5}\x{57A6}\x{57A7}\x{57A9}\x{57AA}' .
+'\x{57AB}\x{57AC}\x{57AD}\x{57AE}\x{57AF}\x{57B0}\x{57B1}\x{57B2}\x{57B3}' .
+'\x{57B4}\x{57B5}\x{57B6}\x{57B7}\x{57B8}\x{57B9}\x{57BA}\x{57BB}\x{57BC}' .
+'\x{57BD}\x{57BE}\x{57BF}\x{57C0}\x{57C1}\x{57C2}\x{57C3}\x{57C4}\x{57C5}' .
+'\x{57C6}\x{57C7}\x{57C8}\x{57C9}\x{57CB}\x{57CC}\x{57CD}\x{57CE}\x{57CF}' .
+'\x{57D0}\x{57D2}\x{57D3}\x{57D4}\x{57D5}\x{57D6}\x{57D8}\x{57D9}\x{57DA}' .
+'\x{57DC}\x{57DD}\x{57DF}\x{57E0}\x{57E1}\x{57E2}\x{57E3}\x{57E4}\x{57E5}' .
+'\x{57E6}\x{57E7}\x{57E8}\x{57E9}\x{57EA}\x{57EB}\x{57EC}\x{57ED}\x{57EE}' .
+'\x{57EF}\x{57F0}\x{57F1}\x{57F2}\x{57F3}\x{57F4}\x{57F5}\x{57F6}\x{57F7}' .
+'\x{57F8}\x{57F9}\x{57FA}\x{57FB}\x{57FC}\x{57FD}\x{57FE}\x{57FF}\x{5800}' .
+'\x{5801}\x{5802}\x{5803}\x{5804}\x{5805}\x{5806}\x{5807}\x{5808}\x{5809}' .
+'\x{580A}\x{580B}\x{580C}\x{580D}\x{580E}\x{580F}\x{5810}\x{5811}\x{5812}' .
+'\x{5813}\x{5814}\x{5815}\x{5816}\x{5819}\x{581A}\x{581B}\x{581C}\x{581D}' .
+'\x{581E}\x{581F}\x{5820}\x{5821}\x{5822}\x{5823}\x{5824}\x{5825}\x{5826}' .
+'\x{5827}\x{5828}\x{5829}\x{582A}\x{582B}\x{582C}\x{582D}\x{582E}\x{582F}' .
+'\x{5830}\x{5831}\x{5832}\x{5833}\x{5834}\x{5835}\x{5836}\x{5837}\x{5838}' .
+'\x{5839}\x{583A}\x{583B}\x{583C}\x{583D}\x{583E}\x{583F}\x{5840}\x{5842}' .
+'\x{5843}\x{5844}\x{5845}\x{5846}\x{5847}\x{5848}\x{5849}\x{584A}\x{584B}' .
+'\x{584C}\x{584D}\x{584E}\x{584F}\x{5851}\x{5852}\x{5853}\x{5854}\x{5855}' .
+'\x{5857}\x{5858}\x{5859}\x{585A}\x{585B}\x{585C}\x{585D}\x{585E}\x{585F}' .
+'\x{5861}\x{5862}\x{5863}\x{5864}\x{5865}\x{5868}\x{5869}\x{586A}\x{586B}' .
+'\x{586C}\x{586D}\x{586E}\x{586F}\x{5870}\x{5871}\x{5872}\x{5873}\x{5874}' .
+'\x{5875}\x{5876}\x{5878}\x{5879}\x{587A}\x{587B}\x{587C}\x{587D}\x{587E}' .
+'\x{587F}\x{5880}\x{5881}\x{5882}\x{5883}\x{5884}\x{5885}\x{5886}\x{5887}' .
+'\x{5888}\x{5889}\x{588A}\x{588B}\x{588C}\x{588D}\x{588E}\x{588F}\x{5890}' .
+'\x{5891}\x{5892}\x{5893}\x{5894}\x{5896}\x{5897}\x{5898}\x{5899}\x{589A}' .
+'\x{589B}\x{589C}\x{589D}\x{589E}\x{589F}\x{58A0}\x{58A1}\x{58A2}\x{58A3}' .
+'\x{58A4}\x{58A5}\x{58A6}\x{58A7}\x{58A8}\x{58A9}\x{58AB}\x{58AC}\x{58AD}' .
+'\x{58AE}\x{58AF}\x{58B0}\x{58B1}\x{58B2}\x{58B3}\x{58B4}\x{58B7}\x{58B8}' .
+'\x{58B9}\x{58BA}\x{58BB}\x{58BC}\x{58BD}\x{58BE}\x{58BF}\x{58C1}\x{58C2}' .
+'\x{58C5}\x{58C6}\x{58C7}\x{58C8}\x{58C9}\x{58CA}\x{58CB}\x{58CE}\x{58CF}' .
+'\x{58D1}\x{58D2}\x{58D3}\x{58D4}\x{58D5}\x{58D6}\x{58D7}\x{58D8}\x{58D9}' .
+'\x{58DA}\x{58DB}\x{58DD}\x{58DE}\x{58DF}\x{58E0}\x{58E2}\x{58E3}\x{58E4}' .
+'\x{58E5}\x{58E7}\x{58E8}\x{58E9}\x{58EA}\x{58EB}\x{58EC}\x{58ED}\x{58EE}' .
+'\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F3}\x{58F4}\x{58F6}\x{58F7}\x{58F8}' .
+'\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{58FE}\x{58FF}\x{5900}\x{5902}' .
+'\x{5903}\x{5904}\x{5906}\x{5907}\x{5909}\x{590A}\x{590B}\x{590C}\x{590D}' .
+'\x{590E}\x{590F}\x{5910}\x{5912}\x{5914}\x{5915}\x{5916}\x{5917}\x{5918}' .
+'\x{5919}\x{591A}\x{591B}\x{591C}\x{591D}\x{591E}\x{591F}\x{5920}\x{5921}' .
+'\x{5922}\x{5924}\x{5925}\x{5926}\x{5927}\x{5928}\x{5929}\x{592A}\x{592B}' .
+'\x{592C}\x{592D}\x{592E}\x{592F}\x{5930}\x{5931}\x{5932}\x{5934}\x{5935}' .
+'\x{5937}\x{5938}\x{5939}\x{593A}\x{593B}\x{593C}\x{593D}\x{593E}\x{593F}' .
+'\x{5940}\x{5941}\x{5942}\x{5943}\x{5944}\x{5945}\x{5946}\x{5947}\x{5948}' .
+'\x{5949}\x{594A}\x{594B}\x{594C}\x{594D}\x{594E}\x{594F}\x{5950}\x{5951}' .
+'\x{5952}\x{5953}\x{5954}\x{5955}\x{5956}\x{5957}\x{5958}\x{595A}\x{595C}' .
+'\x{595D}\x{595E}\x{595F}\x{5960}\x{5961}\x{5962}\x{5963}\x{5964}\x{5965}' .
+'\x{5966}\x{5967}\x{5968}\x{5969}\x{596A}\x{596B}\x{596C}\x{596D}\x{596E}' .
+'\x{596F}\x{5970}\x{5971}\x{5972}\x{5973}\x{5974}\x{5975}\x{5976}\x{5977}' .
+'\x{5978}\x{5979}\x{597A}\x{597B}\x{597C}\x{597D}\x{597E}\x{597F}\x{5980}' .
+'\x{5981}\x{5982}\x{5983}\x{5984}\x{5985}\x{5986}\x{5987}\x{5988}\x{5989}' .
+'\x{598A}\x{598B}\x{598C}\x{598D}\x{598E}\x{598F}\x{5990}\x{5991}\x{5992}' .
+'\x{5993}\x{5994}\x{5995}\x{5996}\x{5997}\x{5998}\x{5999}\x{599A}\x{599C}' .
+'\x{599D}\x{599E}\x{599F}\x{59A0}\x{59A1}\x{59A2}\x{59A3}\x{59A4}\x{59A5}' .
+'\x{59A6}\x{59A7}\x{59A8}\x{59A9}\x{59AA}\x{59AB}\x{59AC}\x{59AD}\x{59AE}' .
+'\x{59AF}\x{59B0}\x{59B1}\x{59B2}\x{59B3}\x{59B4}\x{59B5}\x{59B6}\x{59B8}' .
+'\x{59B9}\x{59BA}\x{59BB}\x{59BC}\x{59BD}\x{59BE}\x{59BF}\x{59C0}\x{59C1}' .
+'\x{59C2}\x{59C3}\x{59C4}\x{59C5}\x{59C6}\x{59C7}\x{59C8}\x{59C9}\x{59CA}' .
+'\x{59CB}\x{59CC}\x{59CD}\x{59CE}\x{59CF}\x{59D0}\x{59D1}\x{59D2}\x{59D3}' .
+'\x{59D4}\x{59D5}\x{59D6}\x{59D7}\x{59D8}\x{59D9}\x{59DA}\x{59DB}\x{59DC}' .
+'\x{59DD}\x{59DE}\x{59DF}\x{59E0}\x{59E1}\x{59E2}\x{59E3}\x{59E4}\x{59E5}' .
+'\x{59E6}\x{59E8}\x{59E9}\x{59EA}\x{59EB}\x{59EC}\x{59ED}\x{59EE}\x{59EF}' .
+'\x{59F0}\x{59F1}\x{59F2}\x{59F3}\x{59F4}\x{59F5}\x{59F6}\x{59F7}\x{59F8}' .
+'\x{59F9}\x{59FA}\x{59FB}\x{59FC}\x{59FD}\x{59FE}\x{59FF}\x{5A00}\x{5A01}' .
+'\x{5A02}\x{5A03}\x{5A04}\x{5A05}\x{5A06}\x{5A07}\x{5A08}\x{5A09}\x{5A0A}' .
+'\x{5A0B}\x{5A0C}\x{5A0D}\x{5A0E}\x{5A0F}\x{5A10}\x{5A11}\x{5A12}\x{5A13}' .
+'\x{5A14}\x{5A15}\x{5A16}\x{5A17}\x{5A18}\x{5A19}\x{5A1A}\x{5A1B}\x{5A1C}' .
+'\x{5A1D}\x{5A1E}\x{5A1F}\x{5A20}\x{5A21}\x{5A22}\x{5A23}\x{5A25}\x{5A27}' .
+'\x{5A28}\x{5A29}\x{5A2A}\x{5A2B}\x{5A2D}\x{5A2E}\x{5A2F}\x{5A31}\x{5A32}' .
+'\x{5A33}\x{5A34}\x{5A35}\x{5A36}\x{5A37}\x{5A38}\x{5A39}\x{5A3A}\x{5A3B}' .
+'\x{5A3C}\x{5A3D}\x{5A3E}\x{5A3F}\x{5A40}\x{5A41}\x{5A42}\x{5A43}\x{5A44}' .
+'\x{5A45}\x{5A46}\x{5A47}\x{5A48}\x{5A49}\x{5A4A}\x{5A4B}\x{5A4C}\x{5A4D}' .
+'\x{5A4E}\x{5A4F}\x{5A50}\x{5A51}\x{5A52}\x{5A53}\x{5A55}\x{5A56}\x{5A57}' .
+'\x{5A58}\x{5A5A}\x{5A5B}\x{5A5C}\x{5A5D}\x{5A5E}\x{5A5F}\x{5A60}\x{5A61}' .
+'\x{5A62}\x{5A63}\x{5A64}\x{5A65}\x{5A66}\x{5A67}\x{5A68}\x{5A69}\x{5A6A}' .
+'\x{5A6B}\x{5A6C}\x{5A6D}\x{5A6E}\x{5A70}\x{5A72}\x{5A73}\x{5A74}\x{5A75}' .
+'\x{5A76}\x{5A77}\x{5A78}\x{5A79}\x{5A7A}\x{5A7B}\x{5A7C}\x{5A7D}\x{5A7E}' .
+'\x{5A7F}\x{5A80}\x{5A81}\x{5A82}\x{5A83}\x{5A84}\x{5A85}\x{5A86}\x{5A88}' .
+'\x{5A89}\x{5A8A}\x{5A8B}\x{5A8C}\x{5A8E}\x{5A8F}\x{5A90}\x{5A91}\x{5A92}' .
+'\x{5A93}\x{5A94}\x{5A95}\x{5A96}\x{5A97}\x{5A98}\x{5A99}\x{5A9A}\x{5A9B}' .
+'\x{5A9C}\x{5A9D}\x{5A9E}\x{5A9F}\x{5AA0}\x{5AA1}\x{5AA2}\x{5AA3}\x{5AA4}' .
+'\x{5AA5}\x{5AA6}\x{5AA7}\x{5AA8}\x{5AA9}\x{5AAA}\x{5AAC}\x{5AAD}\x{5AAE}' .
+'\x{5AAF}\x{5AB0}\x{5AB1}\x{5AB2}\x{5AB3}\x{5AB4}\x{5AB5}\x{5AB6}\x{5AB7}' .
+'\x{5AB8}\x{5AB9}\x{5ABA}\x{5ABB}\x{5ABC}\x{5ABD}\x{5ABE}\x{5ABF}\x{5AC0}' .
+'\x{5AC1}\x{5AC2}\x{5AC3}\x{5AC4}\x{5AC5}\x{5AC6}\x{5AC7}\x{5AC8}\x{5AC9}' .
+'\x{5ACA}\x{5ACB}\x{5ACC}\x{5ACD}\x{5ACE}\x{5ACF}\x{5AD1}\x{5AD2}\x{5AD4}' .
+'\x{5AD5}\x{5AD6}\x{5AD7}\x{5AD8}\x{5AD9}\x{5ADA}\x{5ADB}\x{5ADC}\x{5ADD}' .
+'\x{5ADE}\x{5ADF}\x{5AE0}\x{5AE1}\x{5AE2}\x{5AE3}\x{5AE4}\x{5AE5}\x{5AE6}' .
+'\x{5AE7}\x{5AE8}\x{5AE9}\x{5AEA}\x{5AEB}\x{5AEC}\x{5AED}\x{5AEE}\x{5AF1}' .
+'\x{5AF2}\x{5AF3}\x{5AF4}\x{5AF5}\x{5AF6}\x{5AF7}\x{5AF8}\x{5AF9}\x{5AFA}' .
+'\x{5AFB}\x{5AFC}\x{5AFD}\x{5AFE}\x{5AFF}\x{5B00}\x{5B01}\x{5B02}\x{5B03}' .
+'\x{5B04}\x{5B05}\x{5B06}\x{5B07}\x{5B08}\x{5B09}\x{5B0B}\x{5B0C}\x{5B0E}' .
+'\x{5B0F}\x{5B10}\x{5B11}\x{5B12}\x{5B13}\x{5B14}\x{5B15}\x{5B16}\x{5B17}' .
+'\x{5B18}\x{5B19}\x{5B1A}\x{5B1B}\x{5B1C}\x{5B1D}\x{5B1E}\x{5B1F}\x{5B20}' .
+'\x{5B21}\x{5B22}\x{5B23}\x{5B24}\x{5B25}\x{5B26}\x{5B27}\x{5B28}\x{5B29}' .
+'\x{5B2A}\x{5B2B}\x{5B2C}\x{5B2D}\x{5B2E}\x{5B2F}\x{5B30}\x{5B31}\x{5B32}' .
+'\x{5B33}\x{5B34}\x{5B35}\x{5B36}\x{5B37}\x{5B38}\x{5B3A}\x{5B3B}\x{5B3C}' .
+'\x{5B3D}\x{5B3E}\x{5B3F}\x{5B40}\x{5B41}\x{5B42}\x{5B43}\x{5B44}\x{5B45}' .
+'\x{5B47}\x{5B48}\x{5B49}\x{5B4A}\x{5B4B}\x{5B4C}\x{5B4D}\x{5B4E}\x{5B50}' .
+'\x{5B51}\x{5B53}\x{5B54}\x{5B55}\x{5B56}\x{5B57}\x{5B58}\x{5B59}\x{5B5A}' .
+'\x{5B5B}\x{5B5C}\x{5B5D}\x{5B5E}\x{5B5F}\x{5B62}\x{5B63}\x{5B64}\x{5B65}' .
+'\x{5B66}\x{5B67}\x{5B68}\x{5B69}\x{5B6A}\x{5B6B}\x{5B6C}\x{5B6D}\x{5B6E}' .
+'\x{5B70}\x{5B71}\x{5B72}\x{5B73}\x{5B74}\x{5B75}\x{5B76}\x{5B77}\x{5B78}' .
+'\x{5B7A}\x{5B7B}\x{5B7C}\x{5B7D}\x{5B7F}\x{5B80}\x{5B81}\x{5B82}\x{5B83}' .
+'\x{5B84}\x{5B85}\x{5B87}\x{5B88}\x{5B89}\x{5B8A}\x{5B8B}\x{5B8C}\x{5B8D}' .
+'\x{5B8E}\x{5B8F}\x{5B91}\x{5B92}\x{5B93}\x{5B94}\x{5B95}\x{5B96}\x{5B97}' .
+'\x{5B98}\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9E}\x{5B9F}\x{5BA0}' .
+'\x{5BA1}\x{5BA2}\x{5BA3}\x{5BA4}\x{5BA5}\x{5BA6}\x{5BA7}\x{5BA8}\x{5BAA}' .
+'\x{5BAB}\x{5BAC}\x{5BAD}\x{5BAE}\x{5BAF}\x{5BB0}\x{5BB1}\x{5BB3}\x{5BB4}' .
+'\x{5BB5}\x{5BB6}\x{5BB8}\x{5BB9}\x{5BBA}\x{5BBB}\x{5BBD}\x{5BBE}\x{5BBF}' .
+'\x{5BC0}\x{5BC1}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BCA}' .
+'\x{5BCB}\x{5BCC}\x{5BCD}\x{5BCE}\x{5BCF}\x{5BD0}\x{5BD1}\x{5BD2}\x{5BD3}' .
+'\x{5BD4}\x{5BD5}\x{5BD6}\x{5BD8}\x{5BD9}\x{5BDB}\x{5BDC}\x{5BDD}\x{5BDE}' .
+'\x{5BDF}\x{5BE0}\x{5BE1}\x{5BE2}\x{5BE3}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}' .
+'\x{5BE8}\x{5BE9}\x{5BEA}\x{5BEB}\x{5BEC}\x{5BED}\x{5BEE}\x{5BEF}\x{5BF0}' .
+'\x{5BF1}\x{5BF2}\x{5BF3}\x{5BF4}\x{5BF5}\x{5BF6}\x{5BF7}\x{5BF8}\x{5BF9}' .
+'\x{5BFA}\x{5BFB}\x{5BFC}\x{5BFD}\x{5BFF}\x{5C01}\x{5C03}\x{5C04}\x{5C05}' .
+'\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}\x{5C0B}\x{5C0C}\x{5C0D}\x{5C0E}' .
+'\x{5C0F}\x{5C10}\x{5C11}\x{5C12}\x{5C13}\x{5C14}\x{5C15}\x{5C16}\x{5C17}' .
+'\x{5C18}\x{5C19}\x{5C1A}\x{5C1C}\x{5C1D}\x{5C1E}\x{5C1F}\x{5C20}\x{5C21}' .
+'\x{5C22}\x{5C24}\x{5C25}\x{5C27}\x{5C28}\x{5C2A}\x{5C2B}\x{5C2C}\x{5C2D}' .
+'\x{5C2E}\x{5C2F}\x{5C30}\x{5C31}\x{5C32}\x{5C33}\x{5C34}\x{5C35}\x{5C37}' .
+'\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}' .
+'\x{5C41}\x{5C42}\x{5C43}\x{5C44}\x{5C45}\x{5C46}\x{5C47}\x{5C48}\x{5C49}' .
+'\x{5C4A}\x{5C4B}\x{5C4C}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C52}' .
+'\x{5C53}\x{5C54}\x{5C55}\x{5C56}\x{5C57}\x{5C58}\x{5C59}\x{5C5B}\x{5C5C}' .
+'\x{5C5D}\x{5C5E}\x{5C5F}\x{5C60}\x{5C61}\x{5C62}\x{5C63}\x{5C64}\x{5C65}' .
+'\x{5C66}\x{5C67}\x{5C68}\x{5C69}\x{5C6A}\x{5C6B}\x{5C6C}\x{5C6D}\x{5C6E}' .
+'\x{5C6F}\x{5C70}\x{5C71}\x{5C72}\x{5C73}\x{5C74}\x{5C75}\x{5C76}\x{5C77}' .
+'\x{5C78}\x{5C79}\x{5C7A}\x{5C7B}\x{5C7C}\x{5C7D}\x{5C7E}\x{5C7F}\x{5C80}' .
+'\x{5C81}\x{5C82}\x{5C83}\x{5C84}\x{5C86}\x{5C87}\x{5C88}\x{5C89}\x{5C8A}' .
+'\x{5C8B}\x{5C8C}\x{5C8D}\x{5C8E}\x{5C8F}\x{5C90}\x{5C91}\x{5C92}\x{5C93}' .
+'\x{5C94}\x{5C95}\x{5C96}\x{5C97}\x{5C98}\x{5C99}\x{5C9A}\x{5C9B}\x{5C9C}' .
+'\x{5C9D}\x{5C9E}\x{5C9F}\x{5CA0}\x{5CA1}\x{5CA2}\x{5CA3}\x{5CA4}\x{5CA5}' .
+'\x{5CA6}\x{5CA7}\x{5CA8}\x{5CA9}\x{5CAA}\x{5CAB}\x{5CAC}\x{5CAD}\x{5CAE}' .
+'\x{5CAF}\x{5CB0}\x{5CB1}\x{5CB2}\x{5CB3}\x{5CB5}\x{5CB6}\x{5CB7}\x{5CB8}' .
+'\x{5CBA}\x{5CBB}\x{5CBC}\x{5CBD}\x{5CBE}\x{5CBF}\x{5CC1}\x{5CC2}\x{5CC3}' .
+'\x{5CC4}\x{5CC5}\x{5CC6}\x{5CC7}\x{5CC8}\x{5CC9}\x{5CCA}\x{5CCB}\x{5CCC}' .
+'\x{5CCD}\x{5CCE}\x{5CCF}\x{5CD0}\x{5CD1}\x{5CD2}\x{5CD3}\x{5CD4}\x{5CD6}' .
+'\x{5CD7}\x{5CD8}\x{5CD9}\x{5CDA}\x{5CDB}\x{5CDC}\x{5CDE}\x{5CDF}\x{5CE0}' .
+'\x{5CE1}\x{5CE2}\x{5CE3}\x{5CE4}\x{5CE5}\x{5CE6}\x{5CE7}\x{5CE8}\x{5CE9}' .
+'\x{5CEA}\x{5CEB}\x{5CEC}\x{5CED}\x{5CEE}\x{5CEF}\x{5CF0}\x{5CF1}\x{5CF2}' .
+'\x{5CF3}\x{5CF4}\x{5CF6}\x{5CF7}\x{5CF8}\x{5CF9}\x{5CFA}\x{5CFB}\x{5CFC}' .
+'\x{5CFD}\x{5CFE}\x{5CFF}\x{5D00}\x{5D01}\x{5D02}\x{5D03}\x{5D04}\x{5D05}' .
+'\x{5D06}\x{5D07}\x{5D08}\x{5D09}\x{5D0A}\x{5D0B}\x{5D0C}\x{5D0D}\x{5D0E}' .
+'\x{5D0F}\x{5D10}\x{5D11}\x{5D12}\x{5D13}\x{5D14}\x{5D15}\x{5D16}\x{5D17}' .
+'\x{5D18}\x{5D19}\x{5D1A}\x{5D1B}\x{5D1C}\x{5D1D}\x{5D1E}\x{5D1F}\x{5D20}' .
+'\x{5D21}\x{5D22}\x{5D23}\x{5D24}\x{5D25}\x{5D26}\x{5D27}\x{5D28}\x{5D29}' .
+'\x{5D2A}\x{5D2C}\x{5D2D}\x{5D2E}\x{5D30}\x{5D31}\x{5D32}\x{5D33}\x{5D34}' .
+'\x{5D35}\x{5D36}\x{5D37}\x{5D38}\x{5D39}\x{5D3A}\x{5D3C}\x{5D3D}\x{5D3E}' .
+'\x{5D3F}\x{5D40}\x{5D41}\x{5D42}\x{5D43}\x{5D44}\x{5D45}\x{5D46}\x{5D47}' .
+'\x{5D48}\x{5D49}\x{5D4A}\x{5D4B}\x{5D4C}\x{5D4D}\x{5D4E}\x{5D4F}\x{5D50}' .
+'\x{5D51}\x{5D52}\x{5D54}\x{5D55}\x{5D56}\x{5D58}\x{5D59}\x{5D5A}\x{5D5B}' .
+'\x{5D5D}\x{5D5E}\x{5D5F}\x{5D61}\x{5D62}\x{5D63}\x{5D64}\x{5D65}\x{5D66}' .
+'\x{5D67}\x{5D68}\x{5D69}\x{5D6A}\x{5D6B}\x{5D6C}\x{5D6D}\x{5D6E}\x{5D6F}' .
+'\x{5D70}\x{5D71}\x{5D72}\x{5D73}\x{5D74}\x{5D75}\x{5D76}\x{5D77}\x{5D78}' .
+'\x{5D79}\x{5D7A}\x{5D7B}\x{5D7C}\x{5D7D}\x{5D7E}\x{5D7F}\x{5D80}\x{5D81}' .
+'\x{5D82}\x{5D84}\x{5D85}\x{5D86}\x{5D87}\x{5D88}\x{5D89}\x{5D8A}\x{5D8B}' .
+'\x{5D8C}\x{5D8D}\x{5D8E}\x{5D8F}\x{5D90}\x{5D91}\x{5D92}\x{5D93}\x{5D94}' .
+'\x{5D95}\x{5D97}\x{5D98}\x{5D99}\x{5D9A}\x{5D9B}\x{5D9C}\x{5D9D}\x{5D9E}' .
+'\x{5D9F}\x{5DA0}\x{5DA1}\x{5DA2}\x{5DA5}\x{5DA6}\x{5DA7}\x{5DA8}\x{5DA9}' .
+'\x{5DAA}\x{5DAC}\x{5DAD}\x{5DAE}\x{5DAF}\x{5DB0}\x{5DB1}\x{5DB2}\x{5DB4}' .
+'\x{5DB5}\x{5DB6}\x{5DB7}\x{5DB8}\x{5DBA}\x{5DBB}\x{5DBC}\x{5DBD}\x{5DBE}' .
+'\x{5DBF}\x{5DC0}\x{5DC1}\x{5DC2}\x{5DC3}\x{5DC5}\x{5DC6}\x{5DC7}\x{5DC8}' .
+'\x{5DC9}\x{5DCA}\x{5DCB}\x{5DCC}\x{5DCD}\x{5DCE}\x{5DCF}\x{5DD0}\x{5DD1}' .
+'\x{5DD2}\x{5DD3}\x{5DD4}\x{5DD5}\x{5DD6}\x{5DD8}\x{5DD9}\x{5DDB}\x{5DDD}' .
+'\x{5DDE}\x{5DDF}\x{5DE0}\x{5DE1}\x{5DE2}\x{5DE3}\x{5DE4}\x{5DE5}\x{5DE6}' .
+'\x{5DE7}\x{5DE8}\x{5DE9}\x{5DEA}\x{5DEB}\x{5DEC}\x{5DED}\x{5DEE}\x{5DEF}' .
+'\x{5DF0}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DF8}\x{5DF9}' .
+'\x{5DFA}\x{5DFB}\x{5DFC}\x{5DFD}\x{5DFE}\x{5DFF}\x{5E00}\x{5E01}\x{5E02}' .
+'\x{5E03}\x{5E04}\x{5E05}\x{5E06}\x{5E07}\x{5E08}\x{5E09}\x{5E0A}\x{5E0B}' .
+'\x{5E0C}\x{5E0D}\x{5E0E}\x{5E0F}\x{5E10}\x{5E11}\x{5E13}\x{5E14}\x{5E15}' .
+'\x{5E16}\x{5E17}\x{5E18}\x{5E19}\x{5E1A}\x{5E1B}\x{5E1C}\x{5E1D}\x{5E1E}' .
+'\x{5E1F}\x{5E20}\x{5E21}\x{5E22}\x{5E23}\x{5E24}\x{5E25}\x{5E26}\x{5E27}' .
+'\x{5E28}\x{5E29}\x{5E2A}\x{5E2B}\x{5E2C}\x{5E2D}\x{5E2E}\x{5E2F}\x{5E30}' .
+'\x{5E31}\x{5E32}\x{5E33}\x{5E34}\x{5E35}\x{5E36}\x{5E37}\x{5E38}\x{5E39}' .
+'\x{5E3A}\x{5E3B}\x{5E3C}\x{5E3D}\x{5E3E}\x{5E40}\x{5E41}\x{5E42}\x{5E43}' .
+'\x{5E44}\x{5E45}\x{5E46}\x{5E47}\x{5E49}\x{5E4A}\x{5E4B}\x{5E4C}\x{5E4D}' .
+'\x{5E4E}\x{5E4F}\x{5E50}\x{5E52}\x{5E53}\x{5E54}\x{5E55}\x{5E56}\x{5E57}' .
+'\x{5E58}\x{5E59}\x{5E5A}\x{5E5B}\x{5E5C}\x{5E5D}\x{5E5E}\x{5E5F}\x{5E60}' .
+'\x{5E61}\x{5E62}\x{5E63}\x{5E64}\x{5E65}\x{5E66}\x{5E67}\x{5E68}\x{5E69}' .
+'\x{5E6A}\x{5E6B}\x{5E6C}\x{5E6D}\x{5E6E}\x{5E6F}\x{5E70}\x{5E71}\x{5E72}' .
+'\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E77}\x{5E78}\x{5E79}\x{5E7A}\x{5E7B}' .
+'\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E80}\x{5E81}\x{5E82}\x{5E83}\x{5E84}' .
+'\x{5E85}\x{5E86}\x{5E87}\x{5E88}\x{5E89}\x{5E8A}\x{5E8B}\x{5E8C}\x{5E8D}' .
+'\x{5E8E}\x{5E8F}\x{5E90}\x{5E91}\x{5E93}\x{5E94}\x{5E95}\x{5E96}\x{5E97}' .
+'\x{5E98}\x{5E99}\x{5E9A}\x{5E9B}\x{5E9C}\x{5E9D}\x{5E9E}\x{5E9F}\x{5EA0}' .
+'\x{5EA1}\x{5EA2}\x{5EA3}\x{5EA4}\x{5EA5}\x{5EA6}\x{5EA7}\x{5EA8}\x{5EA9}' .
+'\x{5EAA}\x{5EAB}\x{5EAC}\x{5EAD}\x{5EAE}\x{5EAF}\x{5EB0}\x{5EB1}\x{5EB2}' .
+'\x{5EB3}\x{5EB4}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EB9}\x{5EBB}\x{5EBC}' .
+'\x{5EBD}\x{5EBE}\x{5EBF}\x{5EC1}\x{5EC2}\x{5EC3}\x{5EC4}\x{5EC5}\x{5EC6}' .
+'\x{5EC7}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECB}\x{5ECC}\x{5ECD}\x{5ECE}\x{5ECF}' .
+'\x{5ED0}\x{5ED1}\x{5ED2}\x{5ED3}\x{5ED4}\x{5ED5}\x{5ED6}\x{5ED7}\x{5ED8}' .
+'\x{5ED9}\x{5EDA}\x{5EDB}\x{5EDC}\x{5EDD}\x{5EDE}\x{5EDF}\x{5EE0}\x{5EE1}' .
+'\x{5EE2}\x{5EE3}\x{5EE4}\x{5EE5}\x{5EE6}\x{5EE7}\x{5EE8}\x{5EE9}\x{5EEA}' .
+'\x{5EEC}\x{5EED}\x{5EEE}\x{5EEF}\x{5EF0}\x{5EF1}\x{5EF2}\x{5EF3}\x{5EF4}' .
+'\x{5EF5}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}\x{5EFB}\x{5EFC}\x{5EFD}\x{5EFE}' .
+'\x{5EFF}\x{5F00}\x{5F01}\x{5F02}\x{5F03}\x{5F04}\x{5F05}\x{5F06}\x{5F07}' .
+'\x{5F08}\x{5F0A}\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F11}\x{5F12}\x{5F13}' .
+'\x{5F14}\x{5F15}\x{5F16}\x{5F17}\x{5F18}\x{5F19}\x{5F1A}\x{5F1B}\x{5F1C}' .
+'\x{5F1D}\x{5F1E}\x{5F1F}\x{5F20}\x{5F21}\x{5F22}\x{5F23}\x{5F24}\x{5F25}' .
+'\x{5F26}\x{5F27}\x{5F28}\x{5F29}\x{5F2A}\x{5F2B}\x{5F2C}\x{5F2D}\x{5F2E}' .
+'\x{5F2F}\x{5F30}\x{5F31}\x{5F32}\x{5F33}\x{5F34}\x{5F35}\x{5F36}\x{5F37}' .
+'\x{5F38}\x{5F39}\x{5F3A}\x{5F3C}\x{5F3E}\x{5F3F}\x{5F40}\x{5F41}\x{5F42}' .
+'\x{5F43}\x{5F44}\x{5F45}\x{5F46}\x{5F47}\x{5F48}\x{5F49}\x{5F4A}\x{5F4B}' .
+'\x{5F4C}\x{5F4D}\x{5F4E}\x{5F4F}\x{5F50}\x{5F51}\x{5F52}\x{5F53}\x{5F54}' .
+'\x{5F55}\x{5F56}\x{5F57}\x{5F58}\x{5F59}\x{5F5A}\x{5F5B}\x{5F5C}\x{5F5D}' .
+'\x{5F5E}\x{5F5F}\x{5F60}\x{5F61}\x{5F62}\x{5F63}\x{5F64}\x{5F65}\x{5F66}' .
+'\x{5F67}\x{5F68}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}\x{5F6D}\x{5F6E}\x{5F6F}' .
+'\x{5F70}\x{5F71}\x{5F72}\x{5F73}\x{5F74}\x{5F75}\x{5F76}\x{5F77}\x{5F78}' .
+'\x{5F79}\x{5F7A}\x{5F7B}\x{5F7C}\x{5F7D}\x{5F7E}\x{5F7F}\x{5F80}\x{5F81}' .
+'\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F86}\x{5F87}\x{5F88}\x{5F89}\x{5F8A}' .
+'\x{5F8B}\x{5F8C}\x{5F8D}\x{5F8E}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F94}' .
+'\x{5F95}\x{5F96}\x{5F97}\x{5F98}\x{5F99}\x{5F9B}\x{5F9C}\x{5F9D}\x{5F9E}' .
+'\x{5F9F}\x{5FA0}\x{5FA1}\x{5FA2}\x{5FA5}\x{5FA6}\x{5FA7}\x{5FA8}\x{5FA9}' .
+'\x{5FAA}\x{5FAB}\x{5FAC}\x{5FAD}\x{5FAE}\x{5FAF}\x{5FB1}\x{5FB2}\x{5FB3}' .
+'\x{5FB4}\x{5FB5}\x{5FB6}\x{5FB7}\x{5FB8}\x{5FB9}\x{5FBA}\x{5FBB}\x{5FBC}' .
+'\x{5FBD}\x{5FBE}\x{5FBF}\x{5FC0}\x{5FC1}\x{5FC3}\x{5FC4}\x{5FC5}\x{5FC6}' .
+'\x{5FC7}\x{5FC8}\x{5FC9}\x{5FCA}\x{5FCB}\x{5FCC}\x{5FCD}\x{5FCF}\x{5FD0}' .
+'\x{5FD1}\x{5FD2}\x{5FD3}\x{5FD4}\x{5FD5}\x{5FD6}\x{5FD7}\x{5FD8}\x{5FD9}' .
+'\x{5FDA}\x{5FDC}\x{5FDD}\x{5FDE}\x{5FE0}\x{5FE1}\x{5FE3}\x{5FE4}\x{5FE5}' .
+'\x{5FE6}\x{5FE7}\x{5FE8}\x{5FE9}\x{5FEA}\x{5FEB}\x{5FED}\x{5FEE}\x{5FEF}' .
+'\x{5FF0}\x{5FF1}\x{5FF2}\x{5FF3}\x{5FF4}\x{5FF5}\x{5FF6}\x{5FF7}\x{5FF8}' .
+'\x{5FF9}\x{5FFA}\x{5FFB}\x{5FFD}\x{5FFE}\x{5FFF}\x{6000}\x{6001}\x{6002}' .
+'\x{6003}\x{6004}\x{6005}\x{6006}\x{6007}\x{6008}\x{6009}\x{600A}\x{600B}' .
+'\x{600C}\x{600D}\x{600E}\x{600F}\x{6010}\x{6011}\x{6012}\x{6013}\x{6014}' .
+'\x{6015}\x{6016}\x{6017}\x{6018}\x{6019}\x{601A}\x{601B}\x{601C}\x{601D}' .
+'\x{601E}\x{601F}\x{6020}\x{6021}\x{6022}\x{6024}\x{6025}\x{6026}\x{6027}' .
+'\x{6028}\x{6029}\x{602A}\x{602B}\x{602C}\x{602D}\x{602E}\x{602F}\x{6030}' .
+'\x{6031}\x{6032}\x{6033}\x{6034}\x{6035}\x{6036}\x{6037}\x{6038}\x{6039}' .
+'\x{603A}\x{603B}\x{603C}\x{603D}\x{603E}\x{603F}\x{6040}\x{6041}\x{6042}' .
+'\x{6043}\x{6044}\x{6045}\x{6046}\x{6047}\x{6048}\x{6049}\x{604A}\x{604B}' .
+'\x{604C}\x{604D}\x{604E}\x{604F}\x{6050}\x{6051}\x{6052}\x{6053}\x{6054}' .
+'\x{6055}\x{6057}\x{6058}\x{6059}\x{605A}\x{605B}\x{605C}\x{605D}\x{605E}' .
+'\x{605F}\x{6062}\x{6063}\x{6064}\x{6065}\x{6066}\x{6067}\x{6068}\x{6069}' .
+'\x{606A}\x{606B}\x{606C}\x{606D}\x{606E}\x{606F}\x{6070}\x{6072}\x{6073}' .
+'\x{6075}\x{6076}\x{6077}\x{6078}\x{6079}\x{607A}\x{607B}\x{607C}\x{607D}' .
+'\x{607E}\x{607F}\x{6080}\x{6081}\x{6082}\x{6083}\x{6084}\x{6085}\x{6086}' .
+'\x{6087}\x{6088}\x{6089}\x{608A}\x{608B}\x{608C}\x{608D}\x{608E}\x{608F}' .
+'\x{6090}\x{6092}\x{6094}\x{6095}\x{6096}\x{6097}\x{6098}\x{6099}\x{609A}' .
+'\x{609B}\x{609C}\x{609D}\x{609E}\x{609F}\x{60A0}\x{60A1}\x{60A2}\x{60A3}' .
+'\x{60A4}\x{60A6}\x{60A7}\x{60A8}\x{60AA}\x{60AB}\x{60AC}\x{60AD}\x{60AE}' .
+'\x{60AF}\x{60B0}\x{60B1}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B7}' .
+'\x{60B8}\x{60B9}\x{60BA}\x{60BB}\x{60BC}\x{60BD}\x{60BE}\x{60BF}\x{60C0}' .
+'\x{60C1}\x{60C2}\x{60C3}\x{60C4}\x{60C5}\x{60C6}\x{60C7}\x{60C8}\x{60C9}' .
+'\x{60CA}\x{60CB}\x{60CC}\x{60CD}\x{60CE}\x{60CF}\x{60D0}\x{60D1}\x{60D3}' .
+'\x{60D4}\x{60D5}\x{60D7}\x{60D8}\x{60D9}\x{60DA}\x{60DB}\x{60DC}\x{60DD}' .
+'\x{60DF}\x{60E0}\x{60E1}\x{60E2}\x{60E4}\x{60E6}\x{60E7}\x{60E8}\x{60E9}' .
+'\x{60EA}\x{60EB}\x{60EC}\x{60ED}\x{60EE}\x{60EF}\x{60F0}\x{60F1}\x{60F2}' .
+'\x{60F3}\x{60F4}\x{60F5}\x{60F6}\x{60F7}\x{60F8}\x{60F9}\x{60FA}\x{60FB}' .
+'\x{60FC}\x{60FE}\x{60FF}\x{6100}\x{6101}\x{6103}\x{6104}\x{6105}\x{6106}' .
+'\x{6108}\x{6109}\x{610A}\x{610B}\x{610C}\x{610D}\x{610E}\x{610F}\x{6110}' .
+'\x{6112}\x{6113}\x{6114}\x{6115}\x{6116}\x{6117}\x{6118}\x{6119}\x{611A}' .
+'\x{611B}\x{611C}\x{611D}\x{611F}\x{6120}\x{6122}\x{6123}\x{6124}\x{6125}' .
+'\x{6126}\x{6127}\x{6128}\x{6129}\x{612A}\x{612B}\x{612C}\x{612D}\x{612E}' .
+'\x{612F}\x{6130}\x{6132}\x{6134}\x{6136}\x{6137}\x{613A}\x{613B}\x{613C}' .
+'\x{613D}\x{613E}\x{613F}\x{6140}\x{6141}\x{6142}\x{6143}\x{6144}\x{6145}' .
+'\x{6146}\x{6147}\x{6148}\x{6149}\x{614A}\x{614B}\x{614C}\x{614D}\x{614E}' .
+'\x{614F}\x{6150}\x{6151}\x{6152}\x{6153}\x{6154}\x{6155}\x{6156}\x{6157}' .
+'\x{6158}\x{6159}\x{615A}\x{615B}\x{615C}\x{615D}\x{615E}\x{615F}\x{6161}' .
+'\x{6162}\x{6163}\x{6164}\x{6165}\x{6166}\x{6167}\x{6168}\x{6169}\x{616A}' .
+'\x{616B}\x{616C}\x{616D}\x{616E}\x{6170}\x{6171}\x{6172}\x{6173}\x{6174}' .
+'\x{6175}\x{6176}\x{6177}\x{6178}\x{6179}\x{617A}\x{617C}\x{617E}\x{6180}' .
+'\x{6181}\x{6182}\x{6183}\x{6184}\x{6185}\x{6187}\x{6188}\x{6189}\x{618A}' .
+'\x{618B}\x{618C}\x{618D}\x{618E}\x{618F}\x{6190}\x{6191}\x{6192}\x{6193}' .
+'\x{6194}\x{6195}\x{6196}\x{6198}\x{6199}\x{619A}\x{619B}\x{619D}\x{619E}' .
+'\x{619F}\x{61A0}\x{61A1}\x{61A2}\x{61A3}\x{61A4}\x{61A5}\x{61A6}\x{61A7}' .
+'\x{61A8}\x{61A9}\x{61AA}\x{61AB}\x{61AC}\x{61AD}\x{61AE}\x{61AF}\x{61B0}' .
+'\x{61B1}\x{61B2}\x{61B3}\x{61B4}\x{61B5}\x{61B6}\x{61B7}\x{61B8}\x{61BA}' .
+'\x{61BC}\x{61BD}\x{61BE}\x{61BF}\x{61C0}\x{61C1}\x{61C2}\x{61C3}\x{61C4}' .
+'\x{61C5}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' .
+'\x{61CE}\x{61CF}\x{61D0}\x{61D1}\x{61D2}\x{61D4}\x{61D6}\x{61D7}\x{61D8}' .
+'\x{61D9}\x{61DA}\x{61DB}\x{61DC}\x{61DD}\x{61DE}\x{61DF}\x{61E0}\x{61E1}' .
+'\x{61E2}\x{61E3}\x{61E4}\x{61E5}\x{61E6}\x{61E7}\x{61E8}\x{61E9}\x{61EA}' .
+'\x{61EB}\x{61ED}\x{61EE}\x{61F0}\x{61F1}\x{61F2}\x{61F3}\x{61F5}\x{61F6}' .
+'\x{61F7}\x{61F8}\x{61F9}\x{61FA}\x{61FB}\x{61FC}\x{61FD}\x{61FE}\x{61FF}' .
+'\x{6200}\x{6201}\x{6202}\x{6203}\x{6204}\x{6206}\x{6207}\x{6208}\x{6209}' .
+'\x{620A}\x{620B}\x{620C}\x{620D}\x{620E}\x{620F}\x{6210}\x{6211}\x{6212}' .
+'\x{6213}\x{6214}\x{6215}\x{6216}\x{6217}\x{6218}\x{6219}\x{621A}\x{621B}' .
+'\x{621C}\x{621D}\x{621E}\x{621F}\x{6220}\x{6221}\x{6222}\x{6223}\x{6224}' .
+'\x{6225}\x{6226}\x{6227}\x{6228}\x{6229}\x{622A}\x{622B}\x{622C}\x{622D}' .
+'\x{622E}\x{622F}\x{6230}\x{6231}\x{6232}\x{6233}\x{6234}\x{6236}\x{6237}' .
+'\x{6238}\x{623A}\x{623B}\x{623C}\x{623D}\x{623E}\x{623F}\x{6240}\x{6241}' .
+'\x{6242}\x{6243}\x{6244}\x{6245}\x{6246}\x{6247}\x{6248}\x{6249}\x{624A}' .
+'\x{624B}\x{624C}\x{624D}\x{624E}\x{624F}\x{6250}\x{6251}\x{6252}\x{6253}' .
+'\x{6254}\x{6255}\x{6256}\x{6258}\x{6259}\x{625A}\x{625B}\x{625C}\x{625D}' .
+'\x{625E}\x{625F}\x{6260}\x{6261}\x{6262}\x{6263}\x{6264}\x{6265}\x{6266}' .
+'\x{6267}\x{6268}\x{6269}\x{626A}\x{626B}\x{626C}\x{626D}\x{626E}\x{626F}' .
+'\x{6270}\x{6271}\x{6272}\x{6273}\x{6274}\x{6275}\x{6276}\x{6277}\x{6278}' .
+'\x{6279}\x{627A}\x{627B}\x{627C}\x{627D}\x{627E}\x{627F}\x{6280}\x{6281}' .
+'\x{6283}\x{6284}\x{6285}\x{6286}\x{6287}\x{6288}\x{6289}\x{628A}\x{628B}' .
+'\x{628C}\x{628E}\x{628F}\x{6290}\x{6291}\x{6292}\x{6293}\x{6294}\x{6295}' .
+'\x{6296}\x{6297}\x{6298}\x{6299}\x{629A}\x{629B}\x{629C}\x{629E}\x{629F}' .
+'\x{62A0}\x{62A1}\x{62A2}\x{62A3}\x{62A4}\x{62A5}\x{62A7}\x{62A8}\x{62A9}' .
+'\x{62AA}\x{62AB}\x{62AC}\x{62AD}\x{62AE}\x{62AF}\x{62B0}\x{62B1}\x{62B2}' .
+'\x{62B3}\x{62B4}\x{62B5}\x{62B6}\x{62B7}\x{62B8}\x{62B9}\x{62BA}\x{62BB}' .
+'\x{62BC}\x{62BD}\x{62BE}\x{62BF}\x{62C0}\x{62C1}\x{62C2}\x{62C3}\x{62C4}' .
+'\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CB}\x{62CC}\x{62CD}' .
+'\x{62CE}\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D5}\x{62D6}' .
+'\x{62D7}\x{62D8}\x{62D9}\x{62DA}\x{62DB}\x{62DC}\x{62DD}\x{62DF}\x{62E0}' .
+'\x{62E1}\x{62E2}\x{62E3}\x{62E4}\x{62E5}\x{62E6}\x{62E7}\x{62E8}\x{62E9}' .
+'\x{62EB}\x{62EC}\x{62ED}\x{62EE}\x{62EF}\x{62F0}\x{62F1}\x{62F2}\x{62F3}' .
+'\x{62F4}\x{62F5}\x{62F6}\x{62F7}\x{62F8}\x{62F9}\x{62FA}\x{62FB}\x{62FC}' .
+'\x{62FD}\x{62FE}\x{62FF}\x{6300}\x{6301}\x{6302}\x{6303}\x{6304}\x{6305}' .
+'\x{6306}\x{6307}\x{6308}\x{6309}\x{630B}\x{630C}\x{630D}\x{630E}\x{630F}' .
+'\x{6310}\x{6311}\x{6312}\x{6313}\x{6314}\x{6315}\x{6316}\x{6318}\x{6319}' .
+'\x{631A}\x{631B}\x{631C}\x{631D}\x{631E}\x{631F}\x{6320}\x{6321}\x{6322}' .
+'\x{6323}\x{6324}\x{6325}\x{6326}\x{6327}\x{6328}\x{6329}\x{632A}\x{632B}' .
+'\x{632C}\x{632D}\x{632E}\x{632F}\x{6330}\x{6332}\x{6333}\x{6334}\x{6336}' .
+'\x{6338}\x{6339}\x{633A}\x{633B}\x{633C}\x{633D}\x{633E}\x{6340}\x{6341}' .
+'\x{6342}\x{6343}\x{6344}\x{6345}\x{6346}\x{6347}\x{6348}\x{6349}\x{634A}' .
+'\x{634B}\x{634C}\x{634D}\x{634E}\x{634F}\x{6350}\x{6351}\x{6352}\x{6353}' .
+'\x{6354}\x{6355}\x{6356}\x{6357}\x{6358}\x{6359}\x{635A}\x{635C}\x{635D}' .
+'\x{635E}\x{635F}\x{6360}\x{6361}\x{6362}\x{6363}\x{6364}\x{6365}\x{6366}' .
+'\x{6367}\x{6368}\x{6369}\x{636A}\x{636B}\x{636C}\x{636D}\x{636E}\x{636F}' .
+'\x{6370}\x{6371}\x{6372}\x{6373}\x{6374}\x{6375}\x{6376}\x{6377}\x{6378}' .
+'\x{6379}\x{637A}\x{637B}\x{637C}\x{637D}\x{637E}\x{6380}\x{6381}\x{6382}' .
+'\x{6383}\x{6384}\x{6385}\x{6386}\x{6387}\x{6388}\x{6389}\x{638A}\x{638C}' .
+'\x{638D}\x{638E}\x{638F}\x{6390}\x{6391}\x{6392}\x{6394}\x{6395}\x{6396}' .
+'\x{6397}\x{6398}\x{6399}\x{639A}\x{639B}\x{639C}\x{639D}\x{639E}\x{639F}' .
+'\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A4}\x{63A5}\x{63A6}\x{63A7}\x{63A8}' .
+'\x{63A9}\x{63AA}\x{63AB}\x{63AC}\x{63AD}\x{63AE}\x{63AF}\x{63B0}\x{63B1}' .
+'\x{63B2}\x{63B3}\x{63B4}\x{63B5}\x{63B6}\x{63B7}\x{63B8}\x{63B9}\x{63BA}' .
+'\x{63BC}\x{63BD}\x{63BE}\x{63BF}\x{63C0}\x{63C1}\x{63C2}\x{63C3}\x{63C4}' .
+'\x{63C5}\x{63C6}\x{63C7}\x{63C8}\x{63C9}\x{63CA}\x{63CB}\x{63CC}\x{63CD}' .
+'\x{63CE}\x{63CF}\x{63D0}\x{63D2}\x{63D3}\x{63D4}\x{63D5}\x{63D6}\x{63D7}' .
+'\x{63D8}\x{63D9}\x{63DA}\x{63DB}\x{63DC}\x{63DD}\x{63DE}\x{63DF}\x{63E0}' .
+'\x{63E1}\x{63E2}\x{63E3}\x{63E4}\x{63E5}\x{63E6}\x{63E7}\x{63E8}\x{63E9}' .
+'\x{63EA}\x{63EB}\x{63EC}\x{63ED}\x{63EE}\x{63EF}\x{63F0}\x{63F1}\x{63F2}' .
+'\x{63F3}\x{63F4}\x{63F5}\x{63F6}\x{63F7}\x{63F8}\x{63F9}\x{63FA}\x{63FB}' .
+'\x{63FC}\x{63FD}\x{63FE}\x{63FF}\x{6400}\x{6401}\x{6402}\x{6403}\x{6404}' .
+'\x{6405}\x{6406}\x{6408}\x{6409}\x{640A}\x{640B}\x{640C}\x{640D}\x{640E}' .
+'\x{640F}\x{6410}\x{6411}\x{6412}\x{6413}\x{6414}\x{6415}\x{6416}\x{6417}' .
+'\x{6418}\x{6419}\x{641A}\x{641B}\x{641C}\x{641D}\x{641E}\x{641F}\x{6420}' .
+'\x{6421}\x{6422}\x{6423}\x{6424}\x{6425}\x{6426}\x{6427}\x{6428}\x{6429}' .
+'\x{642A}\x{642B}\x{642C}\x{642D}\x{642E}\x{642F}\x{6430}\x{6431}\x{6432}' .
+'\x{6433}\x{6434}\x{6435}\x{6436}\x{6437}\x{6438}\x{6439}\x{643A}\x{643D}' .
+'\x{643E}\x{643F}\x{6440}\x{6441}\x{6443}\x{6444}\x{6445}\x{6446}\x{6447}' .
+'\x{6448}\x{644A}\x{644B}\x{644C}\x{644D}\x{644E}\x{644F}\x{6450}\x{6451}' .
+'\x{6452}\x{6453}\x{6454}\x{6455}\x{6456}\x{6457}\x{6458}\x{6459}\x{645B}' .
+'\x{645C}\x{645D}\x{645E}\x{645F}\x{6460}\x{6461}\x{6462}\x{6463}\x{6464}' .
+'\x{6465}\x{6466}\x{6467}\x{6468}\x{6469}\x{646A}\x{646B}\x{646C}\x{646D}' .
+'\x{646E}\x{646F}\x{6470}\x{6471}\x{6472}\x{6473}\x{6474}\x{6475}\x{6476}' .
+'\x{6477}\x{6478}\x{6479}\x{647A}\x{647B}\x{647C}\x{647D}\x{647F}\x{6480}' .
+'\x{6481}\x{6482}\x{6483}\x{6484}\x{6485}\x{6487}\x{6488}\x{6489}\x{648A}' .
+'\x{648B}\x{648C}\x{648D}\x{648E}\x{648F}\x{6490}\x{6491}\x{6492}\x{6493}' .
+'\x{6494}\x{6495}\x{6496}\x{6497}\x{6498}\x{6499}\x{649A}\x{649B}\x{649C}' .
+'\x{649D}\x{649E}\x{649F}\x{64A0}\x{64A2}\x{64A3}\x{64A4}\x{64A5}\x{64A6}' .
+'\x{64A7}\x{64A8}\x{64A9}\x{64AA}\x{64AB}\x{64AC}\x{64AD}\x{64AE}\x{64B0}' .
+'\x{64B1}\x{64B2}\x{64B3}\x{64B4}\x{64B5}\x{64B7}\x{64B8}\x{64B9}\x{64BA}' .
+'\x{64BB}\x{64BC}\x{64BD}\x{64BE}\x{64BF}\x{64C0}\x{64C1}\x{64C2}\x{64C3}' .
+'\x{64C4}\x{64C5}\x{64C6}\x{64C7}\x{64C9}\x{64CA}\x{64CB}\x{64CC}\x{64CD}' .
+'\x{64CE}\x{64CF}\x{64D0}\x{64D1}\x{64D2}\x{64D3}\x{64D4}\x{64D6}\x{64D7}' .
+'\x{64D8}\x{64D9}\x{64DA}\x{64DB}\x{64DC}\x{64DD}\x{64DE}\x{64DF}\x{64E0}' .
+'\x{64E2}\x{64E3}\x{64E4}\x{64E6}\x{64E7}\x{64E8}\x{64E9}\x{64EA}\x{64EB}' .
+'\x{64EC}\x{64ED}\x{64EF}\x{64F0}\x{64F1}\x{64F2}\x{64F3}\x{64F4}\x{64F6}' .
+'\x{64F7}\x{64F8}\x{64FA}\x{64FB}\x{64FC}\x{64FD}\x{64FE}\x{64FF}\x{6500}' .
+'\x{6501}\x{6503}\x{6504}\x{6505}\x{6506}\x{6507}\x{6508}\x{6509}\x{650B}' .
+'\x{650C}\x{650D}\x{650E}\x{650F}\x{6510}\x{6511}\x{6512}\x{6513}\x{6514}' .
+'\x{6515}\x{6516}\x{6517}\x{6518}\x{6519}\x{651A}\x{651B}\x{651C}\x{651D}' .
+'\x{651E}\x{6520}\x{6521}\x{6522}\x{6523}\x{6524}\x{6525}\x{6526}\x{6527}' .
+'\x{6529}\x{652A}\x{652B}\x{652C}\x{652D}\x{652E}\x{652F}\x{6530}\x{6531}' .
+'\x{6532}\x{6533}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}\x{653A}' .
+'\x{653B}\x{653C}\x{653D}\x{653E}\x{653F}\x{6541}\x{6543}\x{6544}\x{6545}' .
+'\x{6546}\x{6547}\x{6548}\x{6549}\x{654A}\x{654B}\x{654C}\x{654D}\x{654E}' .
+'\x{654F}\x{6550}\x{6551}\x{6552}\x{6553}\x{6554}\x{6555}\x{6556}\x{6557}' .
+'\x{6558}\x{6559}\x{655B}\x{655C}\x{655D}\x{655E}\x{6560}\x{6561}\x{6562}' .
+'\x{6563}\x{6564}\x{6565}\x{6566}\x{6567}\x{6568}\x{6569}\x{656A}\x{656B}' .
+'\x{656C}\x{656E}\x{656F}\x{6570}\x{6571}\x{6572}\x{6573}\x{6574}\x{6575}' .
+'\x{6576}\x{6577}\x{6578}\x{6579}\x{657A}\x{657B}\x{657C}\x{657E}\x{657F}' .
+'\x{6580}\x{6581}\x{6582}\x{6583}\x{6584}\x{6585}\x{6586}\x{6587}\x{6588}' .
+'\x{6589}\x{658B}\x{658C}\x{658D}\x{658E}\x{658F}\x{6590}\x{6591}\x{6592}' .
+'\x{6593}\x{6594}\x{6595}\x{6596}\x{6597}\x{6598}\x{6599}\x{659B}\x{659C}' .
+'\x{659D}\x{659E}\x{659F}\x{65A0}\x{65A1}\x{65A2}\x{65A3}\x{65A4}\x{65A5}' .
+'\x{65A6}\x{65A7}\x{65A8}\x{65A9}\x{65AA}\x{65AB}\x{65AC}\x{65AD}\x{65AE}' .
+'\x{65AF}\x{65B0}\x{65B1}\x{65B2}\x{65B3}\x{65B4}\x{65B6}\x{65B7}\x{65B8}' .
+'\x{65B9}\x{65BA}\x{65BB}\x{65BC}\x{65BD}\x{65BF}\x{65C0}\x{65C1}\x{65C2}' .
+'\x{65C3}\x{65C4}\x{65C5}\x{65C6}\x{65C7}\x{65CA}\x{65CB}\x{65CC}\x{65CD}' .
+'\x{65CE}\x{65CF}\x{65D0}\x{65D2}\x{65D3}\x{65D4}\x{65D5}\x{65D6}\x{65D7}' .
+'\x{65DA}\x{65DB}\x{65DD}\x{65DE}\x{65DF}\x{65E0}\x{65E1}\x{65E2}\x{65E3}' .
+'\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}\x{65EB}\x{65EC}\x{65ED}\x{65EE}' .
+'\x{65EF}\x{65F0}\x{65F1}\x{65F2}\x{65F3}\x{65F4}\x{65F5}\x{65F6}\x{65F7}' .
+'\x{65F8}\x{65FA}\x{65FB}\x{65FC}\x{65FD}\x{6600}\x{6601}\x{6602}\x{6603}' .
+'\x{6604}\x{6605}\x{6606}\x{6607}\x{6608}\x{6609}\x{660A}\x{660B}\x{660C}' .
+'\x{660D}\x{660E}\x{660F}\x{6610}\x{6611}\x{6612}\x{6613}\x{6614}\x{6615}' .
+'\x{6616}\x{6618}\x{6619}\x{661A}\x{661B}\x{661C}\x{661D}\x{661F}\x{6620}' .
+'\x{6621}\x{6622}\x{6623}\x{6624}\x{6625}\x{6626}\x{6627}\x{6628}\x{6629}' .
+'\x{662A}\x{662B}\x{662D}\x{662E}\x{662F}\x{6630}\x{6631}\x{6632}\x{6633}' .
+'\x{6634}\x{6635}\x{6636}\x{6639}\x{663A}\x{663C}\x{663D}\x{663E}\x{6640}' .
+'\x{6641}\x{6642}\x{6643}\x{6644}\x{6645}\x{6646}\x{6647}\x{6649}\x{664A}' .
+'\x{664B}\x{664C}\x{664E}\x{664F}\x{6650}\x{6651}\x{6652}\x{6653}\x{6654}' .
+'\x{6655}\x{6656}\x{6657}\x{6658}\x{6659}\x{665A}\x{665B}\x{665C}\x{665D}' .
+'\x{665E}\x{665F}\x{6661}\x{6662}\x{6664}\x{6665}\x{6666}\x{6668}\x{6669}' .
+'\x{666A}\x{666B}\x{666C}\x{666D}\x{666E}\x{666F}\x{6670}\x{6671}\x{6672}' .
+'\x{6673}\x{6674}\x{6675}\x{6676}\x{6677}\x{6678}\x{6679}\x{667A}\x{667B}' .
+'\x{667C}\x{667D}\x{667E}\x{667F}\x{6680}\x{6681}\x{6682}\x{6683}\x{6684}' .
+'\x{6685}\x{6686}\x{6687}\x{6688}\x{6689}\x{668A}\x{668B}\x{668C}\x{668D}' .
+'\x{668E}\x{668F}\x{6690}\x{6691}\x{6693}\x{6694}\x{6695}\x{6696}\x{6697}' .
+'\x{6698}\x{6699}\x{669A}\x{669B}\x{669D}\x{669F}\x{66A0}\x{66A1}\x{66A2}' .
+'\x{66A3}\x{66A4}\x{66A5}\x{66A6}\x{66A7}\x{66A8}\x{66A9}\x{66AA}\x{66AB}' .
+'\x{66AE}\x{66AF}\x{66B0}\x{66B1}\x{66B2}\x{66B3}\x{66B4}\x{66B5}\x{66B6}' .
+'\x{66B7}\x{66B8}\x{66B9}\x{66BA}\x{66BB}\x{66BC}\x{66BD}\x{66BE}\x{66BF}' .
+'\x{66C0}\x{66C1}\x{66C2}\x{66C3}\x{66C4}\x{66C5}\x{66C6}\x{66C7}\x{66C8}' .
+'\x{66C9}\x{66CA}\x{66CB}\x{66CC}\x{66CD}\x{66CE}\x{66CF}\x{66D1}\x{66D2}' .
+'\x{66D4}\x{66D5}\x{66D6}\x{66D8}\x{66D9}\x{66DA}\x{66DB}\x{66DC}\x{66DD}' .
+'\x{66DE}\x{66E0}\x{66E1}\x{66E2}\x{66E3}\x{66E4}\x{66E5}\x{66E6}\x{66E7}' .
+'\x{66E8}\x{66E9}\x{66EA}\x{66EB}\x{66EC}\x{66ED}\x{66EE}\x{66F0}\x{66F1}' .
+'\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F6}\x{66F7}\x{66F8}\x{66F9}\x{66FA}' .
+'\x{66FB}\x{66FC}\x{66FE}\x{66FF}\x{6700}\x{6701}\x{6703}\x{6704}\x{6705}' .
+'\x{6706}\x{6708}\x{6709}\x{670A}\x{670B}\x{670C}\x{670D}\x{670E}\x{670F}' .
+'\x{6710}\x{6711}\x{6712}\x{6713}\x{6714}\x{6715}\x{6716}\x{6717}\x{6718}' .
+'\x{671A}\x{671B}\x{671C}\x{671D}\x{671E}\x{671F}\x{6720}\x{6721}\x{6722}' .
+'\x{6723}\x{6725}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}\x{672D}' .
+'\x{672E}\x{672F}\x{6730}\x{6731}\x{6732}\x{6733}\x{6734}\x{6735}\x{6736}' .
+'\x{6737}\x{6738}\x{6739}\x{673A}\x{673B}\x{673C}\x{673D}\x{673E}\x{673F}' .
+'\x{6740}\x{6741}\x{6742}\x{6743}\x{6744}\x{6745}\x{6746}\x{6747}\x{6748}' .
+'\x{6749}\x{674A}\x{674B}\x{674C}\x{674D}\x{674E}\x{674F}\x{6750}\x{6751}' .
+'\x{6752}\x{6753}\x{6754}\x{6755}\x{6756}\x{6757}\x{6758}\x{6759}\x{675A}' .
+'\x{675B}\x{675C}\x{675D}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' .
+'\x{6764}\x{6765}\x{6766}\x{6768}\x{6769}\x{676A}\x{676B}\x{676C}\x{676D}' .
+'\x{676E}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}\x{6774}\x{6775}\x{6776}' .
+'\x{6777}\x{6778}\x{6779}\x{677A}\x{677B}\x{677C}\x{677D}\x{677E}\x{677F}' .
+'\x{6780}\x{6781}\x{6782}\x{6783}\x{6784}\x{6785}\x{6786}\x{6787}\x{6789}' .
+'\x{678A}\x{678B}\x{678C}\x{678D}\x{678E}\x{678F}\x{6790}\x{6791}\x{6792}' .
+'\x{6793}\x{6794}\x{6795}\x{6797}\x{6798}\x{6799}\x{679A}\x{679B}\x{679C}' .
+'\x{679D}\x{679E}\x{679F}\x{67A0}\x{67A1}\x{67A2}\x{67A3}\x{67A4}\x{67A5}' .
+'\x{67A6}\x{67A7}\x{67A8}\x{67AA}\x{67AB}\x{67AC}\x{67AD}\x{67AE}\x{67AF}' .
+'\x{67B0}\x{67B1}\x{67B2}\x{67B3}\x{67B4}\x{67B5}\x{67B6}\x{67B7}\x{67B8}' .
+'\x{67B9}\x{67BA}\x{67BB}\x{67BC}\x{67BE}\x{67C0}\x{67C1}\x{67C2}\x{67C3}' .
+'\x{67C4}\x{67C5}\x{67C6}\x{67C7}\x{67C8}\x{67C9}\x{67CA}\x{67CB}\x{67CC}' .
+'\x{67CD}\x{67CE}\x{67CF}\x{67D0}\x{67D1}\x{67D2}\x{67D3}\x{67D4}\x{67D6}' .
+'\x{67D8}\x{67D9}\x{67DA}\x{67DB}\x{67DC}\x{67DD}\x{67DE}\x{67DF}\x{67E0}' .
+'\x{67E1}\x{67E2}\x{67E3}\x{67E4}\x{67E5}\x{67E6}\x{67E7}\x{67E8}\x{67E9}' .
+'\x{67EA}\x{67EB}\x{67EC}\x{67ED}\x{67EE}\x{67EF}\x{67F0}\x{67F1}\x{67F2}' .
+'\x{67F3}\x{67F4}\x{67F5}\x{67F6}\x{67F7}\x{67F8}\x{67FA}\x{67FB}\x{67FC}' .
+'\x{67FD}\x{67FE}\x{67FF}\x{6800}\x{6802}\x{6803}\x{6804}\x{6805}\x{6806}' .
+'\x{6807}\x{6808}\x{6809}\x{680A}\x{680B}\x{680C}\x{680D}\x{680E}\x{680F}' .
+'\x{6810}\x{6811}\x{6812}\x{6813}\x{6814}\x{6816}\x{6817}\x{6818}\x{6819}' .
+'\x{681A}\x{681B}\x{681C}\x{681D}\x{681F}\x{6820}\x{6821}\x{6822}\x{6823}' .
+'\x{6824}\x{6825}\x{6826}\x{6828}\x{6829}\x{682A}\x{682B}\x{682C}\x{682D}' .
+'\x{682E}\x{682F}\x{6831}\x{6832}\x{6833}\x{6834}\x{6835}\x{6836}\x{6837}' .
+'\x{6838}\x{6839}\x{683A}\x{683B}\x{683C}\x{683D}\x{683E}\x{683F}\x{6840}' .
+'\x{6841}\x{6842}\x{6843}\x{6844}\x{6845}\x{6846}\x{6847}\x{6848}\x{6849}' .
+'\x{684A}\x{684B}\x{684C}\x{684D}\x{684E}\x{684F}\x{6850}\x{6851}\x{6852}' .
+'\x{6853}\x{6854}\x{6855}\x{6856}\x{6857}\x{685B}\x{685D}\x{6860}\x{6861}' .
+'\x{6862}\x{6863}\x{6864}\x{6865}\x{6866}\x{6867}\x{6868}\x{6869}\x{686A}' .
+'\x{686B}\x{686C}\x{686D}\x{686E}\x{686F}\x{6870}\x{6871}\x{6872}\x{6873}' .
+'\x{6874}\x{6875}\x{6876}\x{6877}\x{6878}\x{6879}\x{687B}\x{687C}\x{687D}' .
+'\x{687E}\x{687F}\x{6880}\x{6881}\x{6882}\x{6883}\x{6884}\x{6885}\x{6886}' .
+'\x{6887}\x{6888}\x{6889}\x{688A}\x{688B}\x{688C}\x{688D}\x{688E}\x{688F}' .
+'\x{6890}\x{6891}\x{6892}\x{6893}\x{6894}\x{6896}\x{6897}\x{6898}\x{689A}' .
+'\x{689B}\x{689C}\x{689D}\x{689E}\x{689F}\x{68A0}\x{68A1}\x{68A2}\x{68A3}' .
+'\x{68A4}\x{68A6}\x{68A7}\x{68A8}\x{68A9}\x{68AA}\x{68AB}\x{68AC}\x{68AD}' .
+'\x{68AE}\x{68AF}\x{68B0}\x{68B1}\x{68B2}\x{68B3}\x{68B4}\x{68B5}\x{68B6}' .
+'\x{68B7}\x{68B9}\x{68BB}\x{68BC}\x{68BD}\x{68BE}\x{68BF}\x{68C0}\x{68C1}' .
+'\x{68C2}\x{68C4}\x{68C6}\x{68C7}\x{68C8}\x{68C9}\x{68CA}\x{68CB}\x{68CC}' .
+'\x{68CD}\x{68CE}\x{68CF}\x{68D0}\x{68D1}\x{68D2}\x{68D3}\x{68D4}\x{68D5}' .
+'\x{68D6}\x{68D7}\x{68D8}\x{68DA}\x{68DB}\x{68DC}\x{68DD}\x{68DE}\x{68DF}' .
+'\x{68E0}\x{68E1}\x{68E3}\x{68E4}\x{68E6}\x{68E7}\x{68E8}\x{68E9}\x{68EA}' .
+'\x{68EB}\x{68EC}\x{68ED}\x{68EE}\x{68EF}\x{68F0}\x{68F1}\x{68F2}\x{68F3}' .
+'\x{68F4}\x{68F5}\x{68F6}\x{68F7}\x{68F8}\x{68F9}\x{68FA}\x{68FB}\x{68FC}' .
+'\x{68FD}\x{68FE}\x{68FF}\x{6901}\x{6902}\x{6903}\x{6904}\x{6905}\x{6906}' .
+'\x{6907}\x{6908}\x{690A}\x{690B}\x{690C}\x{690D}\x{690E}\x{690F}\x{6910}' .
+'\x{6911}\x{6912}\x{6913}\x{6914}\x{6915}\x{6916}\x{6917}\x{6918}\x{6919}' .
+'\x{691A}\x{691B}\x{691C}\x{691D}\x{691E}\x{691F}\x{6920}\x{6921}\x{6922}' .
+'\x{6923}\x{6924}\x{6925}\x{6926}\x{6927}\x{6928}\x{6929}\x{692A}\x{692B}' .
+'\x{692C}\x{692D}\x{692E}\x{692F}\x{6930}\x{6931}\x{6932}\x{6933}\x{6934}' .
+'\x{6935}\x{6936}\x{6937}\x{6938}\x{6939}\x{693A}\x{693B}\x{693C}\x{693D}' .
+'\x{693F}\x{6940}\x{6941}\x{6942}\x{6943}\x{6944}\x{6945}\x{6946}\x{6947}' .
+'\x{6948}\x{6949}\x{694A}\x{694B}\x{694C}\x{694E}\x{694F}\x{6950}\x{6951}' .
+'\x{6952}\x{6953}\x{6954}\x{6955}\x{6956}\x{6957}\x{6958}\x{6959}\x{695A}' .
+'\x{695B}\x{695C}\x{695D}\x{695E}\x{695F}\x{6960}\x{6961}\x{6962}\x{6963}' .
+'\x{6964}\x{6965}\x{6966}\x{6967}\x{6968}\x{6969}\x{696A}\x{696B}\x{696C}' .
+'\x{696D}\x{696E}\x{696F}\x{6970}\x{6971}\x{6972}\x{6973}\x{6974}\x{6975}' .
+'\x{6976}\x{6977}\x{6978}\x{6979}\x{697A}\x{697B}\x{697C}\x{697D}\x{697E}' .
+'\x{697F}\x{6980}\x{6981}\x{6982}\x{6983}\x{6984}\x{6985}\x{6986}\x{6987}' .
+'\x{6988}\x{6989}\x{698A}\x{698B}\x{698C}\x{698D}\x{698E}\x{698F}\x{6990}' .
+'\x{6991}\x{6992}\x{6993}\x{6994}\x{6995}\x{6996}\x{6997}\x{6998}\x{6999}' .
+'\x{699A}\x{699B}\x{699C}\x{699D}\x{699E}\x{69A0}\x{69A1}\x{69A3}\x{69A4}' .
+'\x{69A5}\x{69A6}\x{69A7}\x{69A8}\x{69A9}\x{69AA}\x{69AB}\x{69AC}\x{69AD}' .
+'\x{69AE}\x{69AF}\x{69B0}\x{69B1}\x{69B2}\x{69B3}\x{69B4}\x{69B5}\x{69B6}' .
+'\x{69B7}\x{69B8}\x{69B9}\x{69BA}\x{69BB}\x{69BC}\x{69BD}\x{69BE}\x{69BF}' .
+'\x{69C1}\x{69C2}\x{69C3}\x{69C4}\x{69C5}\x{69C6}\x{69C7}\x{69C8}\x{69C9}' .
+'\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}\x{69CF}\x{69D0}\x{69D3}\x{69D4}' .
+'\x{69D8}\x{69D9}\x{69DA}\x{69DB}\x{69DC}\x{69DD}\x{69DE}\x{69DF}\x{69E0}' .
+'\x{69E1}\x{69E2}\x{69E3}\x{69E4}\x{69E5}\x{69E6}\x{69E7}\x{69E8}\x{69E9}' .
+'\x{69EA}\x{69EB}\x{69EC}\x{69ED}\x{69EE}\x{69EF}\x{69F0}\x{69F1}\x{69F2}' .
+'\x{69F3}\x{69F4}\x{69F5}\x{69F6}\x{69F7}\x{69F8}\x{69FA}\x{69FB}\x{69FC}' .
+'\x{69FD}\x{69FE}\x{69FF}\x{6A00}\x{6A01}\x{6A02}\x{6A04}\x{6A05}\x{6A06}' .
+'\x{6A07}\x{6A08}\x{6A09}\x{6A0A}\x{6A0B}\x{6A0D}\x{6A0E}\x{6A0F}\x{6A10}' .
+'\x{6A11}\x{6A12}\x{6A13}\x{6A14}\x{6A15}\x{6A16}\x{6A17}\x{6A18}\x{6A19}' .
+'\x{6A1A}\x{6A1B}\x{6A1D}\x{6A1E}\x{6A1F}\x{6A20}\x{6A21}\x{6A22}\x{6A23}' .
+'\x{6A25}\x{6A26}\x{6A27}\x{6A28}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2C}\x{6A2D}' .
+'\x{6A2E}\x{6A2F}\x{6A30}\x{6A31}\x{6A32}\x{6A33}\x{6A34}\x{6A35}\x{6A36}' .
+'\x{6A38}\x{6A39}\x{6A3A}\x{6A3B}\x{6A3C}\x{6A3D}\x{6A3E}\x{6A3F}\x{6A40}' .
+'\x{6A41}\x{6A42}\x{6A43}\x{6A44}\x{6A45}\x{6A46}\x{6A47}\x{6A48}\x{6A49}' .
+'\x{6A4B}\x{6A4C}\x{6A4D}\x{6A4E}\x{6A4F}\x{6A50}\x{6A51}\x{6A52}\x{6A54}' .
+'\x{6A55}\x{6A56}\x{6A57}\x{6A58}\x{6A59}\x{6A5A}\x{6A5B}\x{6A5D}\x{6A5E}' .
+'\x{6A5F}\x{6A60}\x{6A61}\x{6A62}\x{6A63}\x{6A64}\x{6A65}\x{6A66}\x{6A67}' .
+'\x{6A68}\x{6A69}\x{6A6A}\x{6A6B}\x{6A6C}\x{6A6D}\x{6A6F}\x{6A71}\x{6A72}' .
+'\x{6A73}\x{6A74}\x{6A75}\x{6A76}\x{6A77}\x{6A78}\x{6A79}\x{6A7A}\x{6A7B}' .
+'\x{6A7C}\x{6A7D}\x{6A7E}\x{6A7F}\x{6A80}\x{6A81}\x{6A82}\x{6A83}\x{6A84}' .
+'\x{6A85}\x{6A87}\x{6A88}\x{6A89}\x{6A8B}\x{6A8C}\x{6A8D}\x{6A8E}\x{6A90}' .
+'\x{6A91}\x{6A92}\x{6A93}\x{6A94}\x{6A95}\x{6A96}\x{6A97}\x{6A98}\x{6A9A}' .
+'\x{6A9B}\x{6A9C}\x{6A9E}\x{6A9F}\x{6AA0}\x{6AA1}\x{6AA2}\x{6AA3}\x{6AA4}' .
+'\x{6AA5}\x{6AA6}\x{6AA7}\x{6AA8}\x{6AA9}\x{6AAB}\x{6AAC}\x{6AAD}\x{6AAE}' .
+'\x{6AAF}\x{6AB0}\x{6AB2}\x{6AB3}\x{6AB4}\x{6AB5}\x{6AB6}\x{6AB7}\x{6AB8}' .
+'\x{6AB9}\x{6ABA}\x{6ABB}\x{6ABC}\x{6ABD}\x{6ABF}\x{6AC1}\x{6AC2}\x{6AC3}' .
+'\x{6AC5}\x{6AC6}\x{6AC7}\x{6ACA}\x{6ACB}\x{6ACC}\x{6ACD}\x{6ACE}\x{6ACF}' .
+'\x{6AD0}\x{6AD1}\x{6AD2}\x{6AD3}\x{6AD4}\x{6AD5}\x{6AD6}\x{6AD7}\x{6AD9}' .
+'\x{6ADA}\x{6ADB}\x{6ADC}\x{6ADD}\x{6ADE}\x{6ADF}\x{6AE0}\x{6AE1}\x{6AE2}' .
+'\x{6AE3}\x{6AE4}\x{6AE5}\x{6AE6}\x{6AE7}\x{6AE8}\x{6AEA}\x{6AEB}\x{6AEC}' .
+'\x{6AED}\x{6AEE}\x{6AEF}\x{6AF0}\x{6AF1}\x{6AF2}\x{6AF3}\x{6AF4}\x{6AF5}' .
+'\x{6AF6}\x{6AF7}\x{6AF8}\x{6AF9}\x{6AFA}\x{6AFB}\x{6AFC}\x{6AFD}\x{6AFE}' .
+'\x{6AFF}\x{6B00}\x{6B01}\x{6B02}\x{6B03}\x{6B04}\x{6B05}\x{6B06}\x{6B07}' .
+'\x{6B08}\x{6B09}\x{6B0A}\x{6B0B}\x{6B0C}\x{6B0D}\x{6B0F}\x{6B10}\x{6B11}' .
+'\x{6B12}\x{6B13}\x{6B14}\x{6B15}\x{6B16}\x{6B17}\x{6B18}\x{6B19}\x{6B1A}' .
+'\x{6B1C}\x{6B1D}\x{6B1E}\x{6B1F}\x{6B20}\x{6B21}\x{6B22}\x{6B23}\x{6B24}' .
+'\x{6B25}\x{6B26}\x{6B27}\x{6B28}\x{6B29}\x{6B2A}\x{6B2B}\x{6B2C}\x{6B2D}' .
+'\x{6B2F}\x{6B30}\x{6B31}\x{6B32}\x{6B33}\x{6B34}\x{6B36}\x{6B37}\x{6B38}' .
+'\x{6B39}\x{6B3A}\x{6B3B}\x{6B3C}\x{6B3D}\x{6B3E}\x{6B3F}\x{6B41}\x{6B42}' .
+'\x{6B43}\x{6B44}\x{6B45}\x{6B46}\x{6B47}\x{6B48}\x{6B49}\x{6B4A}\x{6B4B}' .
+'\x{6B4C}\x{6B4D}\x{6B4E}\x{6B4F}\x{6B50}\x{6B51}\x{6B52}\x{6B53}\x{6B54}' .
+'\x{6B55}\x{6B56}\x{6B59}\x{6B5A}\x{6B5B}\x{6B5C}\x{6B5E}\x{6B5F}\x{6B60}' .
+'\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B65}\x{6B66}\x{6B67}\x{6B69}\x{6B6A}' .
+'\x{6B6B}\x{6B6D}\x{6B6F}\x{6B70}\x{6B72}\x{6B73}\x{6B74}\x{6B76}\x{6B77}' .
+'\x{6B78}\x{6B79}\x{6B7A}\x{6B7B}\x{6B7C}\x{6B7E}\x{6B7F}\x{6B80}\x{6B81}' .
+'\x{6B82}\x{6B83}\x{6B84}\x{6B85}\x{6B86}\x{6B87}\x{6B88}\x{6B89}\x{6B8A}' .
+'\x{6B8B}\x{6B8C}\x{6B8D}\x{6B8E}\x{6B8F}\x{6B90}\x{6B91}\x{6B92}\x{6B93}' .
+'\x{6B94}\x{6B95}\x{6B96}\x{6B97}\x{6B98}\x{6B99}\x{6B9A}\x{6B9B}\x{6B9C}' .
+'\x{6B9D}\x{6B9E}\x{6B9F}\x{6BA0}\x{6BA1}\x{6BA2}\x{6BA3}\x{6BA4}\x{6BA5}' .
+'\x{6BA6}\x{6BA7}\x{6BA8}\x{6BA9}\x{6BAA}\x{6BAB}\x{6BAC}\x{6BAD}\x{6BAE}' .
+'\x{6BAF}\x{6BB0}\x{6BB2}\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB6}\x{6BB7}\x{6BB9}' .
+'\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBD}\x{6BBE}\x{6BBF}\x{6BC0}\x{6BC1}\x{6BC2}' .
+'\x{6BC3}\x{6BC4}\x{6BC5}\x{6BC6}\x{6BC7}\x{6BC8}\x{6BC9}\x{6BCA}\x{6BCB}' .
+'\x{6BCC}\x{6BCD}\x{6BCE}\x{6BCF}\x{6BD0}\x{6BD1}\x{6BD2}\x{6BD3}\x{6BD4}' .
+'\x{6BD5}\x{6BD6}\x{6BD7}\x{6BD8}\x{6BD9}\x{6BDA}\x{6BDB}\x{6BDC}\x{6BDD}' .
+'\x{6BDE}\x{6BDF}\x{6BE0}\x{6BE1}\x{6BE2}\x{6BE3}\x{6BE4}\x{6BE5}\x{6BE6}' .
+'\x{6BE7}\x{6BE8}\x{6BEA}\x{6BEB}\x{6BEC}\x{6BED}\x{6BEE}\x{6BEF}\x{6BF0}' .
+'\x{6BF2}\x{6BF3}\x{6BF5}\x{6BF6}\x{6BF7}\x{6BF8}\x{6BF9}\x{6BFB}\x{6BFC}' .
+'\x{6BFD}\x{6BFE}\x{6BFF}\x{6C00}\x{6C01}\x{6C02}\x{6C03}\x{6C04}\x{6C05}' .
+'\x{6C06}\x{6C07}\x{6C08}\x{6C09}\x{6C0B}\x{6C0C}\x{6C0D}\x{6C0E}\x{6C0F}' .
+'\x{6C10}\x{6C11}\x{6C12}\x{6C13}\x{6C14}\x{6C15}\x{6C16}\x{6C18}\x{6C19}' .
+'\x{6C1A}\x{6C1B}\x{6C1D}\x{6C1E}\x{6C1F}\x{6C20}\x{6C21}\x{6C22}\x{6C23}' .
+'\x{6C24}\x{6C25}\x{6C26}\x{6C27}\x{6C28}\x{6C29}\x{6C2A}\x{6C2B}\x{6C2C}' .
+'\x{6C2E}\x{6C2F}\x{6C30}\x{6C31}\x{6C32}\x{6C33}\x{6C34}\x{6C35}\x{6C36}' .
+'\x{6C37}\x{6C38}\x{6C3A}\x{6C3B}\x{6C3D}\x{6C3E}\x{6C3F}\x{6C40}\x{6C41}' .
+'\x{6C42}\x{6C43}\x{6C44}\x{6C46}\x{6C47}\x{6C48}\x{6C49}\x{6C4A}\x{6C4B}' .
+'\x{6C4C}\x{6C4D}\x{6C4E}\x{6C4F}\x{6C50}\x{6C51}\x{6C52}\x{6C53}\x{6C54}' .
+'\x{6C55}\x{6C56}\x{6C57}\x{6C58}\x{6C59}\x{6C5A}\x{6C5B}\x{6C5C}\x{6C5D}' .
+'\x{6C5E}\x{6C5F}\x{6C60}\x{6C61}\x{6C62}\x{6C63}\x{6C64}\x{6C65}\x{6C66}' .
+'\x{6C67}\x{6C68}\x{6C69}\x{6C6A}\x{6C6B}\x{6C6D}\x{6C6F}\x{6C70}\x{6C71}' .
+'\x{6C72}\x{6C73}\x{6C74}\x{6C75}\x{6C76}\x{6C77}\x{6C78}\x{6C79}\x{6C7A}' .
+'\x{6C7B}\x{6C7C}\x{6C7D}\x{6C7E}\x{6C7F}\x{6C80}\x{6C81}\x{6C82}\x{6C83}' .
+'\x{6C84}\x{6C85}\x{6C86}\x{6C87}\x{6C88}\x{6C89}\x{6C8A}\x{6C8B}\x{6C8C}' .
+'\x{6C8D}\x{6C8E}\x{6C8F}\x{6C90}\x{6C91}\x{6C92}\x{6C93}\x{6C94}\x{6C95}' .
+'\x{6C96}\x{6C97}\x{6C98}\x{6C99}\x{6C9A}\x{6C9B}\x{6C9C}\x{6C9D}\x{6C9E}' .
+'\x{6C9F}\x{6CA1}\x{6CA2}\x{6CA3}\x{6CA4}\x{6CA5}\x{6CA6}\x{6CA7}\x{6CA8}' .
+'\x{6CA9}\x{6CAA}\x{6CAB}\x{6CAC}\x{6CAD}\x{6CAE}\x{6CAF}\x{6CB0}\x{6CB1}' .
+'\x{6CB2}\x{6CB3}\x{6CB4}\x{6CB5}\x{6CB6}\x{6CB7}\x{6CB8}\x{6CB9}\x{6CBA}' .
+'\x{6CBB}\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC0}\x{6CC1}\x{6CC2}\x{6CC3}' .
+'\x{6CC4}\x{6CC5}\x{6CC6}\x{6CC7}\x{6CC8}\x{6CC9}\x{6CCA}\x{6CCB}\x{6CCC}' .
+'\x{6CCD}\x{6CCE}\x{6CCF}\x{6CD0}\x{6CD1}\x{6CD2}\x{6CD3}\x{6CD4}\x{6CD5}' .
+'\x{6CD6}\x{6CD7}\x{6CD9}\x{6CDA}\x{6CDB}\x{6CDC}\x{6CDD}\x{6CDE}\x{6CDF}' .
+'\x{6CE0}\x{6CE1}\x{6CE2}\x{6CE3}\x{6CE4}\x{6CE5}\x{6CE6}\x{6CE7}\x{6CE8}' .
+'\x{6CE9}\x{6CEA}\x{6CEB}\x{6CEC}\x{6CED}\x{6CEE}\x{6CEF}\x{6CF0}\x{6CF1}' .
+'\x{6CF2}\x{6CF3}\x{6CF5}\x{6CF6}\x{6CF7}\x{6CF8}\x{6CF9}\x{6CFA}\x{6CFB}' .
+'\x{6CFC}\x{6CFD}\x{6CFE}\x{6CFF}\x{6D00}\x{6D01}\x{6D03}\x{6D04}\x{6D05}' .
+'\x{6D06}\x{6D07}\x{6D08}\x{6D09}\x{6D0A}\x{6D0B}\x{6D0C}\x{6D0D}\x{6D0E}' .
+'\x{6D0F}\x{6D10}\x{6D11}\x{6D12}\x{6D13}\x{6D14}\x{6D15}\x{6D16}\x{6D17}' .
+'\x{6D18}\x{6D19}\x{6D1A}\x{6D1B}\x{6D1D}\x{6D1E}\x{6D1F}\x{6D20}\x{6D21}' .
+'\x{6D22}\x{6D23}\x{6D25}\x{6D26}\x{6D27}\x{6D28}\x{6D29}\x{6D2A}\x{6D2B}' .
+'\x{6D2C}\x{6D2D}\x{6D2E}\x{6D2F}\x{6D30}\x{6D31}\x{6D32}\x{6D33}\x{6D34}' .
+'\x{6D35}\x{6D36}\x{6D37}\x{6D38}\x{6D39}\x{6D3A}\x{6D3B}\x{6D3C}\x{6D3D}' .
+'\x{6D3E}\x{6D3F}\x{6D40}\x{6D41}\x{6D42}\x{6D43}\x{6D44}\x{6D45}\x{6D46}' .
+'\x{6D47}\x{6D48}\x{6D49}\x{6D4A}\x{6D4B}\x{6D4C}\x{6D4D}\x{6D4E}\x{6D4F}' .
+'\x{6D50}\x{6D51}\x{6D52}\x{6D53}\x{6D54}\x{6D55}\x{6D56}\x{6D57}\x{6D58}' .
+'\x{6D59}\x{6D5A}\x{6D5B}\x{6D5C}\x{6D5D}\x{6D5E}\x{6D5F}\x{6D60}\x{6D61}' .
+'\x{6D62}\x{6D63}\x{6D64}\x{6D65}\x{6D66}\x{6D67}\x{6D68}\x{6D69}\x{6D6A}' .
+'\x{6D6B}\x{6D6C}\x{6D6D}\x{6D6E}\x{6D6F}\x{6D70}\x{6D72}\x{6D73}\x{6D74}' .
+'\x{6D75}\x{6D76}\x{6D77}\x{6D78}\x{6D79}\x{6D7A}\x{6D7B}\x{6D7C}\x{6D7D}' .
+'\x{6D7E}\x{6D7F}\x{6D80}\x{6D82}\x{6D83}\x{6D84}\x{6D85}\x{6D86}\x{6D87}' .
+'\x{6D88}\x{6D89}\x{6D8A}\x{6D8B}\x{6D8C}\x{6D8D}\x{6D8E}\x{6D8F}\x{6D90}' .
+'\x{6D91}\x{6D92}\x{6D93}\x{6D94}\x{6D95}\x{6D97}\x{6D98}\x{6D99}\x{6D9A}' .
+'\x{6D9B}\x{6D9D}\x{6D9E}\x{6D9F}\x{6DA0}\x{6DA1}\x{6DA2}\x{6DA3}\x{6DA4}' .
+'\x{6DA5}\x{6DA6}\x{6DA7}\x{6DA8}\x{6DA9}\x{6DAA}\x{6DAB}\x{6DAC}\x{6DAD}' .
+'\x{6DAE}\x{6DAF}\x{6DB2}\x{6DB3}\x{6DB4}\x{6DB5}\x{6DB7}\x{6DB8}\x{6DB9}' .
+'\x{6DBA}\x{6DBB}\x{6DBC}\x{6DBD}\x{6DBE}\x{6DBF}\x{6DC0}\x{6DC1}\x{6DC2}' .
+'\x{6DC3}\x{6DC4}\x{6DC5}\x{6DC6}\x{6DC7}\x{6DC8}\x{6DC9}\x{6DCA}\x{6DCB}' .
+'\x{6DCC}\x{6DCD}\x{6DCE}\x{6DCF}\x{6DD0}\x{6DD1}\x{6DD2}\x{6DD3}\x{6DD4}' .
+'\x{6DD5}\x{6DD6}\x{6DD7}\x{6DD8}\x{6DD9}\x{6DDA}\x{6DDB}\x{6DDC}\x{6DDD}' .
+'\x{6DDE}\x{6DDF}\x{6DE0}\x{6DE1}\x{6DE2}\x{6DE3}\x{6DE4}\x{6DE5}\x{6DE6}' .
+'\x{6DE7}\x{6DE8}\x{6DE9}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DED}\x{6DEE}\x{6DEF}' .
+'\x{6DF0}\x{6DF1}\x{6DF2}\x{6DF3}\x{6DF4}\x{6DF5}\x{6DF6}\x{6DF7}\x{6DF8}' .
+'\x{6DF9}\x{6DFA}\x{6DFB}\x{6DFC}\x{6DFD}\x{6E00}\x{6E03}\x{6E04}\x{6E05}' .
+'\x{6E07}\x{6E08}\x{6E09}\x{6E0A}\x{6E0B}\x{6E0C}\x{6E0D}\x{6E0E}\x{6E0F}' .
+'\x{6E10}\x{6E11}\x{6E14}\x{6E15}\x{6E16}\x{6E17}\x{6E19}\x{6E1A}\x{6E1B}' .
+'\x{6E1C}\x{6E1D}\x{6E1E}\x{6E1F}\x{6E20}\x{6E21}\x{6E22}\x{6E23}\x{6E24}' .
+'\x{6E25}\x{6E26}\x{6E27}\x{6E28}\x{6E29}\x{6E2B}\x{6E2C}\x{6E2D}\x{6E2E}' .
+'\x{6E2F}\x{6E30}\x{6E31}\x{6E32}\x{6E33}\x{6E34}\x{6E35}\x{6E36}\x{6E37}' .
+'\x{6E38}\x{6E39}\x{6E3A}\x{6E3B}\x{6E3C}\x{6E3D}\x{6E3E}\x{6E3F}\x{6E40}' .
+'\x{6E41}\x{6E42}\x{6E43}\x{6E44}\x{6E45}\x{6E46}\x{6E47}\x{6E48}\x{6E49}' .
+'\x{6E4A}\x{6E4B}\x{6E4D}\x{6E4E}\x{6E4F}\x{6E50}\x{6E51}\x{6E52}\x{6E53}' .
+'\x{6E54}\x{6E55}\x{6E56}\x{6E57}\x{6E58}\x{6E59}\x{6E5A}\x{6E5B}\x{6E5C}' .
+'\x{6E5D}\x{6E5E}\x{6E5F}\x{6E60}\x{6E61}\x{6E62}\x{6E63}\x{6E64}\x{6E65}' .
+'\x{6E66}\x{6E67}\x{6E68}\x{6E69}\x{6E6A}\x{6E6B}\x{6E6D}\x{6E6E}\x{6E6F}' .
+'\x{6E70}\x{6E71}\x{6E72}\x{6E73}\x{6E74}\x{6E75}\x{6E77}\x{6E78}\x{6E79}' .
+'\x{6E7E}\x{6E7F}\x{6E80}\x{6E81}\x{6E82}\x{6E83}\x{6E84}\x{6E85}\x{6E86}' .
+'\x{6E87}\x{6E88}\x{6E89}\x{6E8A}\x{6E8D}\x{6E8E}\x{6E8F}\x{6E90}\x{6E91}' .
+'\x{6E92}\x{6E93}\x{6E94}\x{6E96}\x{6E97}\x{6E98}\x{6E99}\x{6E9A}\x{6E9B}' .
+'\x{6E9C}\x{6E9D}\x{6E9E}\x{6E9F}\x{6EA0}\x{6EA1}\x{6EA2}\x{6EA3}\x{6EA4}' .
+'\x{6EA5}\x{6EA6}\x{6EA7}\x{6EA8}\x{6EA9}\x{6EAA}\x{6EAB}\x{6EAC}\x{6EAD}' .
+'\x{6EAE}\x{6EAF}\x{6EB0}\x{6EB1}\x{6EB2}\x{6EB3}\x{6EB4}\x{6EB5}\x{6EB6}' .
+'\x{6EB7}\x{6EB8}\x{6EB9}\x{6EBA}\x{6EBB}\x{6EBC}\x{6EBD}\x{6EBE}\x{6EBF}' .
+'\x{6EC0}\x{6EC1}\x{6EC2}\x{6EC3}\x{6EC4}\x{6EC5}\x{6EC6}\x{6EC7}\x{6EC8}' .
+'\x{6EC9}\x{6ECA}\x{6ECB}\x{6ECC}\x{6ECD}\x{6ECE}\x{6ECF}\x{6ED0}\x{6ED1}' .
+'\x{6ED2}\x{6ED3}\x{6ED4}\x{6ED5}\x{6ED6}\x{6ED7}\x{6ED8}\x{6ED9}\x{6EDA}' .
+'\x{6EDC}\x{6EDE}\x{6EDF}\x{6EE0}\x{6EE1}\x{6EE2}\x{6EE4}\x{6EE5}\x{6EE6}' .
+'\x{6EE7}\x{6EE8}\x{6EE9}\x{6EEA}\x{6EEB}\x{6EEC}\x{6EED}\x{6EEE}\x{6EEF}' .
+'\x{6EF0}\x{6EF1}\x{6EF2}\x{6EF3}\x{6EF4}\x{6EF5}\x{6EF6}\x{6EF7}\x{6EF8}' .
+'\x{6EF9}\x{6EFA}\x{6EFB}\x{6EFC}\x{6EFD}\x{6EFE}\x{6EFF}\x{6F00}\x{6F01}' .
+'\x{6F02}\x{6F03}\x{6F05}\x{6F06}\x{6F07}\x{6F08}\x{6F09}\x{6F0A}\x{6F0C}' .
+'\x{6F0D}\x{6F0E}\x{6F0F}\x{6F10}\x{6F11}\x{6F12}\x{6F13}\x{6F14}\x{6F15}' .
+'\x{6F16}\x{6F17}\x{6F18}\x{6F19}\x{6F1A}\x{6F1B}\x{6F1C}\x{6F1D}\x{6F1E}' .
+'\x{6F1F}\x{6F20}\x{6F21}\x{6F22}\x{6F23}\x{6F24}\x{6F25}\x{6F26}\x{6F27}' .
+'\x{6F28}\x{6F29}\x{6F2A}\x{6F2B}\x{6F2C}\x{6F2D}\x{6F2E}\x{6F2F}\x{6F30}' .
+'\x{6F31}\x{6F32}\x{6F33}\x{6F34}\x{6F35}\x{6F36}\x{6F37}\x{6F38}\x{6F39}' .
+'\x{6F3A}\x{6F3B}\x{6F3C}\x{6F3D}\x{6F3E}\x{6F3F}\x{6F40}\x{6F41}\x{6F43}' .
+'\x{6F44}\x{6F45}\x{6F46}\x{6F47}\x{6F49}\x{6F4B}\x{6F4C}\x{6F4D}\x{6F4E}' .
+'\x{6F4F}\x{6F50}\x{6F51}\x{6F52}\x{6F53}\x{6F54}\x{6F55}\x{6F56}\x{6F57}' .
+'\x{6F58}\x{6F59}\x{6F5A}\x{6F5B}\x{6F5C}\x{6F5D}\x{6F5E}\x{6F5F}\x{6F60}' .
+'\x{6F61}\x{6F62}\x{6F63}\x{6F64}\x{6F65}\x{6F66}\x{6F67}\x{6F68}\x{6F69}' .
+'\x{6F6A}\x{6F6B}\x{6F6C}\x{6F6D}\x{6F6E}\x{6F6F}\x{6F70}\x{6F71}\x{6F72}' .
+'\x{6F73}\x{6F74}\x{6F75}\x{6F76}\x{6F77}\x{6F78}\x{6F7A}\x{6F7B}\x{6F7C}' .
+'\x{6F7D}\x{6F7E}\x{6F7F}\x{6F80}\x{6F81}\x{6F82}\x{6F83}\x{6F84}\x{6F85}' .
+'\x{6F86}\x{6F87}\x{6F88}\x{6F89}\x{6F8A}\x{6F8B}\x{6F8C}\x{6F8D}\x{6F8E}' .
+'\x{6F8F}\x{6F90}\x{6F91}\x{6F92}\x{6F93}\x{6F94}\x{6F95}\x{6F96}\x{6F97}' .
+'\x{6F99}\x{6F9B}\x{6F9C}\x{6F9D}\x{6F9E}\x{6FA0}\x{6FA1}\x{6FA2}\x{6FA3}' .
+'\x{6FA4}\x{6FA5}\x{6FA6}\x{6FA7}\x{6FA8}\x{6FA9}\x{6FAA}\x{6FAB}\x{6FAC}' .
+'\x{6FAD}\x{6FAE}\x{6FAF}\x{6FB0}\x{6FB1}\x{6FB2}\x{6FB3}\x{6FB4}\x{6FB5}' .
+'\x{6FB6}\x{6FB8}\x{6FB9}\x{6FBA}\x{6FBB}\x{6FBC}\x{6FBD}\x{6FBE}\x{6FBF}' .
+'\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC4}\x{6FC6}\x{6FC7}\x{6FC8}\x{6FC9}' .
+'\x{6FCA}\x{6FCB}\x{6FCC}\x{6FCD}\x{6FCE}\x{6FCF}\x{6FD1}\x{6FD2}\x{6FD4}' .
+'\x{6FD5}\x{6FD6}\x{6FD7}\x{6FD8}\x{6FD9}\x{6FDA}\x{6FDB}\x{6FDC}\x{6FDD}' .
+'\x{6FDE}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE2}\x{6FE3}\x{6FE4}\x{6FE5}\x{6FE6}' .
+'\x{6FE7}\x{6FE8}\x{6FE9}\x{6FEA}\x{6FEB}\x{6FEC}\x{6FED}\x{6FEE}\x{6FEF}' .
+'\x{6FF0}\x{6FF1}\x{6FF2}\x{6FF3}\x{6FF4}\x{6FF6}\x{6FF7}\x{6FF8}\x{6FF9}' .
+'\x{6FFA}\x{6FFB}\x{6FFC}\x{6FFE}\x{6FFF}\x{7000}\x{7001}\x{7002}\x{7003}' .
+'\x{7004}\x{7005}\x{7006}\x{7007}\x{7008}\x{7009}\x{700A}\x{700B}\x{700C}' .
+'\x{700D}\x{700E}\x{700F}\x{7011}\x{7012}\x{7014}\x{7015}\x{7016}\x{7017}' .
+'\x{7018}\x{7019}\x{701A}\x{701B}\x{701C}\x{701D}\x{701F}\x{7020}\x{7021}' .
+'\x{7022}\x{7023}\x{7024}\x{7025}\x{7026}\x{7027}\x{7028}\x{7029}\x{702A}' .
+'\x{702B}\x{702C}\x{702D}\x{702E}\x{702F}\x{7030}\x{7031}\x{7032}\x{7033}' .
+'\x{7034}\x{7035}\x{7036}\x{7037}\x{7038}\x{7039}\x{703A}\x{703B}\x{703C}' .
+'\x{703D}\x{703E}\x{703F}\x{7040}\x{7041}\x{7042}\x{7043}\x{7044}\x{7045}' .
+'\x{7046}\x{7048}\x{7049}\x{704A}\x{704C}\x{704D}\x{704F}\x{7050}\x{7051}' .
+'\x{7052}\x{7053}\x{7054}\x{7055}\x{7056}\x{7057}\x{7058}\x{7059}\x{705A}' .
+'\x{705B}\x{705C}\x{705D}\x{705E}\x{705F}\x{7060}\x{7061}\x{7062}\x{7063}' .
+'\x{7064}\x{7065}\x{7066}\x{7067}\x{7068}\x{7069}\x{706A}\x{706B}\x{706C}' .
+'\x{706D}\x{706E}\x{706F}\x{7070}\x{7071}\x{7074}\x{7075}\x{7076}\x{7077}' .
+'\x{7078}\x{7079}\x{707A}\x{707C}\x{707D}\x{707E}\x{707F}\x{7080}\x{7082}' .
+'\x{7083}\x{7084}\x{7085}\x{7086}\x{7087}\x{7088}\x{7089}\x{708A}\x{708B}' .
+'\x{708C}\x{708E}\x{708F}\x{7090}\x{7091}\x{7092}\x{7093}\x{7094}\x{7095}' .
+'\x{7096}\x{7098}\x{7099}\x{709A}\x{709C}\x{709D}\x{709E}\x{709F}\x{70A0}' .
+'\x{70A1}\x{70A2}\x{70A3}\x{70A4}\x{70A5}\x{70A6}\x{70A7}\x{70A8}\x{70A9}' .
+'\x{70AB}\x{70AC}\x{70AD}\x{70AE}\x{70AF}\x{70B0}\x{70B1}\x{70B3}\x{70B4}' .
+'\x{70B5}\x{70B7}\x{70B8}\x{70B9}\x{70BA}\x{70BB}\x{70BC}\x{70BD}\x{70BE}' .
+'\x{70BF}\x{70C0}\x{70C1}\x{70C2}\x{70C3}\x{70C4}\x{70C5}\x{70C6}\x{70C7}' .
+'\x{70C8}\x{70C9}\x{70CA}\x{70CB}\x{70CC}\x{70CD}\x{70CE}\x{70CF}\x{70D0}' .
+'\x{70D1}\x{70D2}\x{70D3}\x{70D4}\x{70D6}\x{70D7}\x{70D8}\x{70D9}\x{70DA}' .
+'\x{70DB}\x{70DC}\x{70DD}\x{70DE}\x{70DF}\x{70E0}\x{70E1}\x{70E2}\x{70E3}' .
+'\x{70E4}\x{70E5}\x{70E6}\x{70E7}\x{70E8}\x{70E9}\x{70EA}\x{70EB}\x{70EC}' .
+'\x{70ED}\x{70EE}\x{70EF}\x{70F0}\x{70F1}\x{70F2}\x{70F3}\x{70F4}\x{70F5}' .
+'\x{70F6}\x{70F7}\x{70F8}\x{70F9}\x{70FA}\x{70FB}\x{70FC}\x{70FD}\x{70FF}' .
+'\x{7100}\x{7101}\x{7102}\x{7103}\x{7104}\x{7105}\x{7106}\x{7107}\x{7109}' .
+'\x{710A}\x{710B}\x{710C}\x{710D}\x{710E}\x{710F}\x{7110}\x{7111}\x{7112}' .
+'\x{7113}\x{7115}\x{7116}\x{7117}\x{7118}\x{7119}\x{711A}\x{711B}\x{711C}' .
+'\x{711D}\x{711E}\x{711F}\x{7120}\x{7121}\x{7122}\x{7123}\x{7125}\x{7126}' .
+'\x{7127}\x{7128}\x{7129}\x{712A}\x{712B}\x{712C}\x{712D}\x{712E}\x{712F}' .
+'\x{7130}\x{7131}\x{7132}\x{7135}\x{7136}\x{7137}\x{7138}\x{7139}\x{713A}' .
+'\x{713B}\x{713D}\x{713E}\x{713F}\x{7140}\x{7141}\x{7142}\x{7143}\x{7144}' .
+'\x{7145}\x{7146}\x{7147}\x{7148}\x{7149}\x{714A}\x{714B}\x{714C}\x{714D}' .
+'\x{714E}\x{714F}\x{7150}\x{7151}\x{7152}\x{7153}\x{7154}\x{7156}\x{7158}' .
+'\x{7159}\x{715A}\x{715B}\x{715C}\x{715D}\x{715E}\x{715F}\x{7160}\x{7161}' .
+'\x{7162}\x{7163}\x{7164}\x{7165}\x{7166}\x{7167}\x{7168}\x{7169}\x{716A}' .
+'\x{716C}\x{716E}\x{716F}\x{7170}\x{7171}\x{7172}\x{7173}\x{7174}\x{7175}' .
+'\x{7176}\x{7177}\x{7178}\x{7179}\x{717A}\x{717B}\x{717C}\x{717D}\x{717E}' .
+'\x{717F}\x{7180}\x{7181}\x{7182}\x{7183}\x{7184}\x{7185}\x{7186}\x{7187}' .
+'\x{7188}\x{7189}\x{718A}\x{718B}\x{718C}\x{718E}\x{718F}\x{7190}\x{7191}' .
+'\x{7192}\x{7193}\x{7194}\x{7195}\x{7197}\x{7198}\x{7199}\x{719A}\x{719B}' .
+'\x{719C}\x{719D}\x{719E}\x{719F}\x{71A0}\x{71A1}\x{71A2}\x{71A3}\x{71A4}' .
+'\x{71A5}\x{71A7}\x{71A8}\x{71A9}\x{71AA}\x{71AC}\x{71AD}\x{71AE}\x{71AF}' .
+'\x{71B0}\x{71B1}\x{71B2}\x{71B3}\x{71B4}\x{71B5}\x{71B7}\x{71B8}\x{71B9}' .
+'\x{71BA}\x{71BB}\x{71BC}\x{71BD}\x{71BE}\x{71BF}\x{71C0}\x{71C1}\x{71C2}' .
+'\x{71C3}\x{71C4}\x{71C5}\x{71C6}\x{71C7}\x{71C8}\x{71C9}\x{71CA}\x{71CB}' .
+'\x{71CD}\x{71CE}\x{71CF}\x{71D0}\x{71D1}\x{71D2}\x{71D4}\x{71D5}\x{71D6}' .
+'\x{71D7}\x{71D8}\x{71D9}\x{71DA}\x{71DB}\x{71DC}\x{71DD}\x{71DE}\x{71DF}' .
+'\x{71E0}\x{71E1}\x{71E2}\x{71E3}\x{71E4}\x{71E5}\x{71E6}\x{71E7}\x{71E8}' .
+'\x{71E9}\x{71EA}\x{71EB}\x{71EC}\x{71ED}\x{71EE}\x{71EF}\x{71F0}\x{71F1}' .
+'\x{71F2}\x{71F4}\x{71F5}\x{71F6}\x{71F7}\x{71F8}\x{71F9}\x{71FB}\x{71FC}' .
+'\x{71FD}\x{71FE}\x{71FF}\x{7201}\x{7202}\x{7203}\x{7204}\x{7205}\x{7206}' .
+'\x{7207}\x{7208}\x{7209}\x{720A}\x{720C}\x{720D}\x{720E}\x{720F}\x{7210}' .
+'\x{7212}\x{7213}\x{7214}\x{7216}\x{7218}\x{7219}\x{721A}\x{721B}\x{721C}' .
+'\x{721D}\x{721E}\x{721F}\x{7221}\x{7222}\x{7223}\x{7226}\x{7227}\x{7228}' .
+'\x{7229}\x{722A}\x{722B}\x{722C}\x{722D}\x{722E}\x{7230}\x{7231}\x{7232}' .
+'\x{7233}\x{7235}\x{7236}\x{7237}\x{7238}\x{7239}\x{723A}\x{723B}\x{723C}' .
+'\x{723D}\x{723E}\x{723F}\x{7240}\x{7241}\x{7242}\x{7243}\x{7244}\x{7246}' .
+'\x{7247}\x{7248}\x{7249}\x{724A}\x{724B}\x{724C}\x{724D}\x{724F}\x{7251}' .
+'\x{7252}\x{7253}\x{7254}\x{7256}\x{7257}\x{7258}\x{7259}\x{725A}\x{725B}' .
+'\x{725C}\x{725D}\x{725E}\x{725F}\x{7260}\x{7261}\x{7262}\x{7263}\x{7264}' .
+'\x{7265}\x{7266}\x{7267}\x{7268}\x{7269}\x{726A}\x{726B}\x{726C}\x{726D}' .
+'\x{726E}\x{726F}\x{7270}\x{7271}\x{7272}\x{7273}\x{7274}\x{7275}\x{7276}' .
+'\x{7277}\x{7278}\x{7279}\x{727A}\x{727B}\x{727C}\x{727D}\x{727E}\x{727F}' .
+'\x{7280}\x{7281}\x{7282}\x{7283}\x{7284}\x{7285}\x{7286}\x{7287}\x{7288}' .
+'\x{7289}\x{728A}\x{728B}\x{728C}\x{728D}\x{728E}\x{728F}\x{7290}\x{7291}' .
+'\x{7292}\x{7293}\x{7294}\x{7295}\x{7296}\x{7297}\x{7298}\x{7299}\x{729A}' .
+'\x{729B}\x{729C}\x{729D}\x{729E}\x{729F}\x{72A1}\x{72A2}\x{72A3}\x{72A4}' .
+'\x{72A5}\x{72A6}\x{72A7}\x{72A8}\x{72A9}\x{72AA}\x{72AC}\x{72AD}\x{72AE}' .
+'\x{72AF}\x{72B0}\x{72B1}\x{72B2}\x{72B3}\x{72B4}\x{72B5}\x{72B6}\x{72B7}' .
+'\x{72B8}\x{72B9}\x{72BA}\x{72BB}\x{72BC}\x{72BD}\x{72BF}\x{72C0}\x{72C1}' .
+'\x{72C2}\x{72C3}\x{72C4}\x{72C5}\x{72C6}\x{72C7}\x{72C8}\x{72C9}\x{72CA}' .
+'\x{72CB}\x{72CC}\x{72CD}\x{72CE}\x{72CF}\x{72D0}\x{72D1}\x{72D2}\x{72D3}' .
+'\x{72D4}\x{72D5}\x{72D6}\x{72D7}\x{72D8}\x{72D9}\x{72DA}\x{72DB}\x{72DC}' .
+'\x{72DD}\x{72DE}\x{72DF}\x{72E0}\x{72E1}\x{72E2}\x{72E3}\x{72E4}\x{72E5}' .
+'\x{72E6}\x{72E7}\x{72E8}\x{72E9}\x{72EA}\x{72EB}\x{72EC}\x{72ED}\x{72EE}' .
+'\x{72EF}\x{72F0}\x{72F1}\x{72F2}\x{72F3}\x{72F4}\x{72F5}\x{72F6}\x{72F7}' .
+'\x{72F8}\x{72F9}\x{72FA}\x{72FB}\x{72FC}\x{72FD}\x{72FE}\x{72FF}\x{7300}' .
+'\x{7301}\x{7303}\x{7304}\x{7305}\x{7306}\x{7307}\x{7308}\x{7309}\x{730A}' .
+'\x{730B}\x{730C}\x{730D}\x{730E}\x{730F}\x{7311}\x{7312}\x{7313}\x{7314}' .
+'\x{7315}\x{7316}\x{7317}\x{7318}\x{7319}\x{731A}\x{731B}\x{731C}\x{731D}' .
+'\x{731E}\x{7320}\x{7321}\x{7322}\x{7323}\x{7324}\x{7325}\x{7326}\x{7327}' .
+'\x{7329}\x{732A}\x{732B}\x{732C}\x{732D}\x{732E}\x{7330}\x{7331}\x{7332}' .
+'\x{7333}\x{7334}\x{7335}\x{7336}\x{7337}\x{7338}\x{7339}\x{733A}\x{733B}' .
+'\x{733C}\x{733D}\x{733E}\x{733F}\x{7340}\x{7341}\x{7342}\x{7343}\x{7344}' .
+'\x{7345}\x{7346}\x{7347}\x{7348}\x{7349}\x{734A}\x{734B}\x{734C}\x{734D}' .
+'\x{734E}\x{7350}\x{7351}\x{7352}\x{7354}\x{7355}\x{7356}\x{7357}\x{7358}' .
+'\x{7359}\x{735A}\x{735B}\x{735C}\x{735D}\x{735E}\x{735F}\x{7360}\x{7361}' .
+'\x{7362}\x{7364}\x{7365}\x{7366}\x{7367}\x{7368}\x{7369}\x{736A}\x{736B}' .
+'\x{736C}\x{736D}\x{736E}\x{736F}\x{7370}\x{7371}\x{7372}\x{7373}\x{7374}' .
+'\x{7375}\x{7376}\x{7377}\x{7378}\x{7379}\x{737A}\x{737B}\x{737C}\x{737D}' .
+'\x{737E}\x{737F}\x{7380}\x{7381}\x{7382}\x{7383}\x{7384}\x{7385}\x{7386}' .
+'\x{7387}\x{7388}\x{7389}\x{738A}\x{738B}\x{738C}\x{738D}\x{738E}\x{738F}' .
+'\x{7390}\x{7391}\x{7392}\x{7393}\x{7394}\x{7395}\x{7396}\x{7397}\x{7398}' .
+'\x{7399}\x{739A}\x{739B}\x{739D}\x{739E}\x{739F}\x{73A0}\x{73A1}\x{73A2}' .
+'\x{73A3}\x{73A4}\x{73A5}\x{73A6}\x{73A7}\x{73A8}\x{73A9}\x{73AA}\x{73AB}' .
+'\x{73AC}\x{73AD}\x{73AE}\x{73AF}\x{73B0}\x{73B1}\x{73B2}\x{73B3}\x{73B4}' .
+'\x{73B5}\x{73B6}\x{73B7}\x{73B8}\x{73B9}\x{73BA}\x{73BB}\x{73BC}\x{73BD}' .
+'\x{73BE}\x{73BF}\x{73C0}\x{73C2}\x{73C3}\x{73C4}\x{73C5}\x{73C6}\x{73C7}' .
+'\x{73C8}\x{73C9}\x{73CA}\x{73CB}\x{73CC}\x{73CD}\x{73CE}\x{73CF}\x{73D0}' .
+'\x{73D1}\x{73D2}\x{73D3}\x{73D4}\x{73D5}\x{73D6}\x{73D7}\x{73D8}\x{73D9}' .
+'\x{73DA}\x{73DB}\x{73DC}\x{73DD}\x{73DE}\x{73DF}\x{73E0}\x{73E2}\x{73E3}' .
+'\x{73E5}\x{73E6}\x{73E7}\x{73E8}\x{73E9}\x{73EA}\x{73EB}\x{73EC}\x{73ED}' .
+'\x{73EE}\x{73EF}\x{73F0}\x{73F1}\x{73F2}\x{73F4}\x{73F5}\x{73F6}\x{73F7}' .
+'\x{73F8}\x{73F9}\x{73FA}\x{73FC}\x{73FD}\x{73FE}\x{73FF}\x{7400}\x{7401}' .
+'\x{7402}\x{7403}\x{7404}\x{7405}\x{7406}\x{7407}\x{7408}\x{7409}\x{740A}' .
+'\x{740B}\x{740C}\x{740D}\x{740E}\x{740F}\x{7410}\x{7411}\x{7412}\x{7413}' .
+'\x{7414}\x{7415}\x{7416}\x{7417}\x{7419}\x{741A}\x{741B}\x{741C}\x{741D}' .
+'\x{741E}\x{741F}\x{7420}\x{7421}\x{7422}\x{7423}\x{7424}\x{7425}\x{7426}' .
+'\x{7427}\x{7428}\x{7429}\x{742A}\x{742B}\x{742C}\x{742D}\x{742E}\x{742F}' .
+'\x{7430}\x{7431}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{7437}\x{7438}' .
+'\x{743A}\x{743B}\x{743C}\x{743D}\x{743F}\x{7440}\x{7441}\x{7442}\x{7443}' .
+'\x{7444}\x{7445}\x{7446}\x{7448}\x{744A}\x{744B}\x{744C}\x{744D}\x{744E}' .
+'\x{744F}\x{7450}\x{7451}\x{7452}\x{7453}\x{7454}\x{7455}\x{7456}\x{7457}' .
+'\x{7459}\x{745A}\x{745B}\x{745C}\x{745D}\x{745E}\x{745F}\x{7461}\x{7462}' .
+'\x{7463}\x{7464}\x{7465}\x{7466}\x{7467}\x{7468}\x{7469}\x{746A}\x{746B}' .
+'\x{746C}\x{746D}\x{746E}\x{746F}\x{7470}\x{7471}\x{7472}\x{7473}\x{7474}' .
+'\x{7475}\x{7476}\x{7477}\x{7478}\x{7479}\x{747A}\x{747C}\x{747D}\x{747E}' .
+'\x{747F}\x{7480}\x{7481}\x{7482}\x{7483}\x{7485}\x{7486}\x{7487}\x{7488}' .
+'\x{7489}\x{748A}\x{748B}\x{748C}\x{748D}\x{748E}\x{748F}\x{7490}\x{7491}' .
+'\x{7492}\x{7493}\x{7494}\x{7495}\x{7497}\x{7498}\x{7499}\x{749A}\x{749B}' .
+'\x{749C}\x{749E}\x{749F}\x{74A0}\x{74A1}\x{74A3}\x{74A4}\x{74A5}\x{74A6}' .
+'\x{74A7}\x{74A8}\x{74A9}\x{74AA}\x{74AB}\x{74AC}\x{74AD}\x{74AE}\x{74AF}' .
+'\x{74B0}\x{74B1}\x{74B2}\x{74B3}\x{74B4}\x{74B5}\x{74B6}\x{74B7}\x{74B8}' .
+'\x{74B9}\x{74BA}\x{74BB}\x{74BC}\x{74BD}\x{74BE}\x{74BF}\x{74C0}\x{74C1}' .
+'\x{74C2}\x{74C3}\x{74C4}\x{74C5}\x{74C6}\x{74CA}\x{74CB}\x{74CD}\x{74CE}' .
+'\x{74CF}\x{74D0}\x{74D1}\x{74D2}\x{74D3}\x{74D4}\x{74D5}\x{74D6}\x{74D7}' .
+'\x{74D8}\x{74D9}\x{74DA}\x{74DB}\x{74DC}\x{74DD}\x{74DE}\x{74DF}\x{74E0}' .
+'\x{74E1}\x{74E2}\x{74E3}\x{74E4}\x{74E5}\x{74E6}\x{74E7}\x{74E8}\x{74E9}' .
+'\x{74EA}\x{74EC}\x{74ED}\x{74EE}\x{74EF}\x{74F0}\x{74F1}\x{74F2}\x{74F3}' .
+'\x{74F4}\x{74F5}\x{74F6}\x{74F7}\x{74F8}\x{74F9}\x{74FA}\x{74FB}\x{74FC}' .
+'\x{74FD}\x{74FE}\x{74FF}\x{7500}\x{7501}\x{7502}\x{7503}\x{7504}\x{7505}' .
+'\x{7506}\x{7507}\x{7508}\x{7509}\x{750A}\x{750B}\x{750C}\x{750D}\x{750F}' .
+'\x{7510}\x{7511}\x{7512}\x{7513}\x{7514}\x{7515}\x{7516}\x{7517}\x{7518}' .
+'\x{7519}\x{751A}\x{751B}\x{751C}\x{751D}\x{751E}\x{751F}\x{7521}\x{7522}' .
+'\x{7523}\x{7524}\x{7525}\x{7526}\x{7527}\x{7528}\x{7529}\x{752A}\x{752B}' .
+'\x{752C}\x{752D}\x{752E}\x{752F}\x{7530}\x{7531}\x{7532}\x{7533}\x{7535}' .
+'\x{7536}\x{7537}\x{7538}\x{7539}\x{753A}\x{753B}\x{753C}\x{753D}\x{753E}' .
+'\x{753F}\x{7540}\x{7542}\x{7543}\x{7544}\x{7545}\x{7546}\x{7547}\x{7548}' .
+'\x{7549}\x{754B}\x{754C}\x{754D}\x{754E}\x{754F}\x{7550}\x{7551}\x{7553}' .
+'\x{7554}\x{7556}\x{7557}\x{7558}\x{7559}\x{755A}\x{755B}\x{755C}\x{755D}' .
+'\x{755F}\x{7560}\x{7562}\x{7563}\x{7564}\x{7565}\x{7566}\x{7567}\x{7568}' .
+'\x{7569}\x{756A}\x{756B}\x{756C}\x{756D}\x{756E}\x{756F}\x{7570}\x{7572}' .
+'\x{7574}\x{7575}\x{7576}\x{7577}\x{7578}\x{7579}\x{757C}\x{757D}\x{757E}' .
+'\x{757F}\x{7580}\x{7581}\x{7582}\x{7583}\x{7584}\x{7586}\x{7587}\x{7588}' .
+'\x{7589}\x{758A}\x{758B}\x{758C}\x{758D}\x{758F}\x{7590}\x{7591}\x{7592}' .
+'\x{7593}\x{7594}\x{7595}\x{7596}\x{7597}\x{7598}\x{7599}\x{759A}\x{759B}' .
+'\x{759C}\x{759D}\x{759E}\x{759F}\x{75A0}\x{75A1}\x{75A2}\x{75A3}\x{75A4}' .
+'\x{75A5}\x{75A6}\x{75A7}\x{75A8}\x{75AA}\x{75AB}\x{75AC}\x{75AD}\x{75AE}' .
+'\x{75AF}\x{75B0}\x{75B1}\x{75B2}\x{75B3}\x{75B4}\x{75B5}\x{75B6}\x{75B8}' .
+'\x{75B9}\x{75BA}\x{75BB}\x{75BC}\x{75BD}\x{75BE}\x{75BF}\x{75C0}\x{75C1}' .
+'\x{75C2}\x{75C3}\x{75C4}\x{75C5}\x{75C6}\x{75C7}\x{75C8}\x{75C9}\x{75CA}' .
+'\x{75CB}\x{75CC}\x{75CD}\x{75CE}\x{75CF}\x{75D0}\x{75D1}\x{75D2}\x{75D3}' .
+'\x{75D4}\x{75D5}\x{75D6}\x{75D7}\x{75D8}\x{75D9}\x{75DA}\x{75DB}\x{75DD}' .
+'\x{75DE}\x{75DF}\x{75E0}\x{75E1}\x{75E2}\x{75E3}\x{75E4}\x{75E5}\x{75E6}' .
+'\x{75E7}\x{75E8}\x{75EA}\x{75EB}\x{75EC}\x{75ED}\x{75EF}\x{75F0}\x{75F1}' .
+'\x{75F2}\x{75F3}\x{75F4}\x{75F5}\x{75F6}\x{75F7}\x{75F8}\x{75F9}\x{75FA}' .
+'\x{75FB}\x{75FC}\x{75FD}\x{75FE}\x{75FF}\x{7600}\x{7601}\x{7602}\x{7603}' .
+'\x{7604}\x{7605}\x{7606}\x{7607}\x{7608}\x{7609}\x{760A}\x{760B}\x{760C}' .
+'\x{760D}\x{760E}\x{760F}\x{7610}\x{7611}\x{7612}\x{7613}\x{7614}\x{7615}' .
+'\x{7616}\x{7617}\x{7618}\x{7619}\x{761A}\x{761B}\x{761C}\x{761D}\x{761E}' .
+'\x{761F}\x{7620}\x{7621}\x{7622}\x{7623}\x{7624}\x{7625}\x{7626}\x{7627}' .
+'\x{7628}\x{7629}\x{762A}\x{762B}\x{762D}\x{762E}\x{762F}\x{7630}\x{7631}' .
+'\x{7632}\x{7633}\x{7634}\x{7635}\x{7636}\x{7637}\x{7638}\x{7639}\x{763A}' .
+'\x{763B}\x{763C}\x{763D}\x{763E}\x{763F}\x{7640}\x{7641}\x{7642}\x{7643}' .
+'\x{7646}\x{7647}\x{7648}\x{7649}\x{764A}\x{764B}\x{764C}\x{764D}\x{764F}' .
+'\x{7650}\x{7652}\x{7653}\x{7654}\x{7656}\x{7657}\x{7658}\x{7659}\x{765A}' .
+'\x{765B}\x{765C}\x{765D}\x{765E}\x{765F}\x{7660}\x{7661}\x{7662}\x{7663}' .
+'\x{7664}\x{7665}\x{7666}\x{7667}\x{7668}\x{7669}\x{766A}\x{766B}\x{766C}' .
+'\x{766D}\x{766E}\x{766F}\x{7670}\x{7671}\x{7672}\x{7674}\x{7675}\x{7676}' .
+'\x{7677}\x{7678}\x{7679}\x{767B}\x{767C}\x{767D}\x{767E}\x{767F}\x{7680}' .
+'\x{7681}\x{7682}\x{7683}\x{7684}\x{7685}\x{7686}\x{7687}\x{7688}\x{7689}' .
+'\x{768A}\x{768B}\x{768C}\x{768E}\x{768F}\x{7690}\x{7691}\x{7692}\x{7693}' .
+'\x{7694}\x{7695}\x{7696}\x{7697}\x{7698}\x{7699}\x{769A}\x{769B}\x{769C}' .
+'\x{769D}\x{769E}\x{769F}\x{76A0}\x{76A3}\x{76A4}\x{76A6}\x{76A7}\x{76A9}' .
+'\x{76AA}\x{76AB}\x{76AC}\x{76AD}\x{76AE}\x{76AF}\x{76B0}\x{76B1}\x{76B2}' .
+'\x{76B4}\x{76B5}\x{76B7}\x{76B8}\x{76BA}\x{76BB}\x{76BC}\x{76BD}\x{76BE}' .
+'\x{76BF}\x{76C0}\x{76C2}\x{76C3}\x{76C4}\x{76C5}\x{76C6}\x{76C7}\x{76C8}' .
+'\x{76C9}\x{76CA}\x{76CD}\x{76CE}\x{76CF}\x{76D0}\x{76D1}\x{76D2}\x{76D3}' .
+'\x{76D4}\x{76D5}\x{76D6}\x{76D7}\x{76D8}\x{76DA}\x{76DB}\x{76DC}\x{76DD}' .
+'\x{76DE}\x{76DF}\x{76E0}\x{76E1}\x{76E2}\x{76E3}\x{76E4}\x{76E5}\x{76E6}' .
+'\x{76E7}\x{76E8}\x{76E9}\x{76EA}\x{76EC}\x{76ED}\x{76EE}\x{76EF}\x{76F0}' .
+'\x{76F1}\x{76F2}\x{76F3}\x{76F4}\x{76F5}\x{76F6}\x{76F7}\x{76F8}\x{76F9}' .
+'\x{76FA}\x{76FB}\x{76FC}\x{76FD}\x{76FE}\x{76FF}\x{7701}\x{7703}\x{7704}' .
+'\x{7705}\x{7706}\x{7707}\x{7708}\x{7709}\x{770A}\x{770B}\x{770C}\x{770D}' .
+'\x{770F}\x{7710}\x{7711}\x{7712}\x{7713}\x{7714}\x{7715}\x{7716}\x{7717}' .
+'\x{7718}\x{7719}\x{771A}\x{771B}\x{771C}\x{771D}\x{771E}\x{771F}\x{7720}' .
+'\x{7722}\x{7723}\x{7725}\x{7726}\x{7727}\x{7728}\x{7729}\x{772A}\x{772C}' .
+'\x{772D}\x{772E}\x{772F}\x{7730}\x{7731}\x{7732}\x{7733}\x{7734}\x{7735}' .
+'\x{7736}\x{7737}\x{7738}\x{7739}\x{773A}\x{773B}\x{773C}\x{773D}\x{773E}' .
+'\x{7740}\x{7741}\x{7743}\x{7744}\x{7745}\x{7746}\x{7747}\x{7748}\x{7749}' .
+'\x{774A}\x{774B}\x{774C}\x{774D}\x{774E}\x{774F}\x{7750}\x{7751}\x{7752}' .
+'\x{7753}\x{7754}\x{7755}\x{7756}\x{7757}\x{7758}\x{7759}\x{775A}\x{775B}' .
+'\x{775C}\x{775D}\x{775E}\x{775F}\x{7760}\x{7761}\x{7762}\x{7763}\x{7765}' .
+'\x{7766}\x{7767}\x{7768}\x{7769}\x{776A}\x{776B}\x{776C}\x{776D}\x{776E}' .
+'\x{776F}\x{7770}\x{7771}\x{7772}\x{7773}\x{7774}\x{7775}\x{7776}\x{7777}' .
+'\x{7778}\x{7779}\x{777A}\x{777B}\x{777C}\x{777D}\x{777E}\x{777F}\x{7780}' .
+'\x{7781}\x{7782}\x{7783}\x{7784}\x{7785}\x{7786}\x{7787}\x{7788}\x{7789}' .
+'\x{778A}\x{778B}\x{778C}\x{778D}\x{778E}\x{778F}\x{7790}\x{7791}\x{7792}' .
+'\x{7793}\x{7794}\x{7795}\x{7797}\x{7798}\x{7799}\x{779A}\x{779B}\x{779C}' .
+'\x{779D}\x{779E}\x{779F}\x{77A0}\x{77A1}\x{77A2}\x{77A3}\x{77A5}\x{77A6}' .
+'\x{77A7}\x{77A8}\x{77A9}\x{77AA}\x{77AB}\x{77AC}\x{77AD}\x{77AE}\x{77AF}' .
+'\x{77B0}\x{77B1}\x{77B2}\x{77B3}\x{77B4}\x{77B5}\x{77B6}\x{77B7}\x{77B8}' .
+'\x{77B9}\x{77BA}\x{77BB}\x{77BC}\x{77BD}\x{77BF}\x{77C0}\x{77C2}\x{77C3}' .
+'\x{77C4}\x{77C5}\x{77C6}\x{77C7}\x{77C8}\x{77C9}\x{77CA}\x{77CB}\x{77CC}' .
+'\x{77CD}\x{77CE}\x{77CF}\x{77D0}\x{77D1}\x{77D3}\x{77D4}\x{77D5}\x{77D6}' .
+'\x{77D7}\x{77D8}\x{77D9}\x{77DA}\x{77DB}\x{77DC}\x{77DE}\x{77DF}\x{77E0}' .
+'\x{77E1}\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E8}\x{77E9}\x{77EA}\x{77EB}' .
+'\x{77EC}\x{77ED}\x{77EE}\x{77EF}\x{77F0}\x{77F1}\x{77F2}\x{77F3}\x{77F6}' .
+'\x{77F7}\x{77F8}\x{77F9}\x{77FA}\x{77FB}\x{77FC}\x{77FD}\x{77FE}\x{77FF}' .
+'\x{7800}\x{7801}\x{7802}\x{7803}\x{7804}\x{7805}\x{7806}\x{7808}\x{7809}' .
+'\x{780A}\x{780B}\x{780C}\x{780D}\x{780E}\x{780F}\x{7810}\x{7811}\x{7812}' .
+'\x{7813}\x{7814}\x{7815}\x{7816}\x{7817}\x{7818}\x{7819}\x{781A}\x{781B}' .
+'\x{781C}\x{781D}\x{781E}\x{781F}\x{7820}\x{7821}\x{7822}\x{7823}\x{7825}' .
+'\x{7826}\x{7827}\x{7828}\x{7829}\x{782A}\x{782B}\x{782C}\x{782D}\x{782E}' .
+'\x{782F}\x{7830}\x{7831}\x{7832}\x{7833}\x{7834}\x{7835}\x{7837}\x{7838}' .
+'\x{7839}\x{783A}\x{783B}\x{783C}\x{783D}\x{783E}\x{7840}\x{7841}\x{7843}' .
+'\x{7844}\x{7845}\x{7847}\x{7848}\x{7849}\x{784A}\x{784C}\x{784D}\x{784E}' .
+'\x{7850}\x{7851}\x{7852}\x{7853}\x{7854}\x{7855}\x{7856}\x{7857}\x{7858}' .
+'\x{7859}\x{785A}\x{785B}\x{785C}\x{785D}\x{785E}\x{785F}\x{7860}\x{7861}' .
+'\x{7862}\x{7863}\x{7864}\x{7865}\x{7866}\x{7867}\x{7868}\x{7869}\x{786A}' .
+'\x{786B}\x{786C}\x{786D}\x{786E}\x{786F}\x{7870}\x{7871}\x{7872}\x{7873}' .
+'\x{7874}\x{7875}\x{7877}\x{7878}\x{7879}\x{787A}\x{787B}\x{787C}\x{787D}' .
+'\x{787E}\x{787F}\x{7880}\x{7881}\x{7882}\x{7883}\x{7884}\x{7885}\x{7886}' .
+'\x{7887}\x{7889}\x{788A}\x{788B}\x{788C}\x{788D}\x{788E}\x{788F}\x{7890}' .
+'\x{7891}\x{7892}\x{7893}\x{7894}\x{7895}\x{7896}\x{7897}\x{7898}\x{7899}' .
+'\x{789A}\x{789B}\x{789C}\x{789D}\x{789E}\x{789F}\x{78A0}\x{78A1}\x{78A2}' .
+'\x{78A3}\x{78A4}\x{78A5}\x{78A6}\x{78A7}\x{78A8}\x{78A9}\x{78AA}\x{78AB}' .
+'\x{78AC}\x{78AD}\x{78AE}\x{78AF}\x{78B0}\x{78B1}\x{78B2}\x{78B3}\x{78B4}' .
+'\x{78B5}\x{78B6}\x{78B7}\x{78B8}\x{78B9}\x{78BA}\x{78BB}\x{78BC}\x{78BD}' .
+'\x{78BE}\x{78BF}\x{78C0}\x{78C1}\x{78C3}\x{78C4}\x{78C5}\x{78C6}\x{78C8}' .
+'\x{78C9}\x{78CA}\x{78CB}\x{78CC}\x{78CD}\x{78CE}\x{78CF}\x{78D0}\x{78D1}' .
+'\x{78D3}\x{78D4}\x{78D5}\x{78D6}\x{78D7}\x{78D8}\x{78D9}\x{78DA}\x{78DB}' .
+'\x{78DC}\x{78DD}\x{78DE}\x{78DF}\x{78E0}\x{78E1}\x{78E2}\x{78E3}\x{78E4}' .
+'\x{78E5}\x{78E6}\x{78E7}\x{78E8}\x{78E9}\x{78EA}\x{78EB}\x{78EC}\x{78ED}' .
+'\x{78EE}\x{78EF}\x{78F1}\x{78F2}\x{78F3}\x{78F4}\x{78F5}\x{78F6}\x{78F7}' .
+'\x{78F9}\x{78FA}\x{78FB}\x{78FC}\x{78FD}\x{78FE}\x{78FF}\x{7901}\x{7902}' .
+'\x{7903}\x{7904}\x{7905}\x{7906}\x{7907}\x{7909}\x{790A}\x{790B}\x{790C}' .
+'\x{790E}\x{790F}\x{7910}\x{7911}\x{7912}\x{7913}\x{7914}\x{7916}\x{7917}' .
+'\x{7918}\x{7919}\x{791A}\x{791B}\x{791C}\x{791D}\x{791E}\x{7921}\x{7922}' .
+'\x{7923}\x{7924}\x{7925}\x{7926}\x{7927}\x{7928}\x{7929}\x{792A}\x{792B}' .
+'\x{792C}\x{792D}\x{792E}\x{792F}\x{7930}\x{7931}\x{7933}\x{7934}\x{7935}' .
+'\x{7937}\x{7938}\x{7939}\x{793A}\x{793B}\x{793C}\x{793D}\x{793E}\x{793F}' .
+'\x{7940}\x{7941}\x{7942}\x{7943}\x{7944}\x{7945}\x{7946}\x{7947}\x{7948}' .
+'\x{7949}\x{794A}\x{794B}\x{794C}\x{794D}\x{794E}\x{794F}\x{7950}\x{7951}' .
+'\x{7952}\x{7953}\x{7954}\x{7955}\x{7956}\x{7957}\x{7958}\x{795A}\x{795B}' .
+'\x{795C}\x{795D}\x{795E}\x{795F}\x{7960}\x{7961}\x{7962}\x{7963}\x{7964}' .
+'\x{7965}\x{7966}\x{7967}\x{7968}\x{7969}\x{796A}\x{796B}\x{796D}\x{796F}' .
+'\x{7970}\x{7971}\x{7972}\x{7973}\x{7974}\x{7977}\x{7978}\x{7979}\x{797A}' .
+'\x{797B}\x{797C}\x{797D}\x{797E}\x{797F}\x{7980}\x{7981}\x{7982}\x{7983}' .
+'\x{7984}\x{7985}\x{7988}\x{7989}\x{798A}\x{798B}\x{798C}\x{798D}\x{798E}' .
+'\x{798F}\x{7990}\x{7991}\x{7992}\x{7993}\x{7994}\x{7995}\x{7996}\x{7997}' .
+'\x{7998}\x{7999}\x{799A}\x{799B}\x{799C}\x{799F}\x{79A0}\x{79A1}\x{79A2}' .
+'\x{79A3}\x{79A4}\x{79A5}\x{79A6}\x{79A7}\x{79A8}\x{79AA}\x{79AB}\x{79AC}' .
+'\x{79AD}\x{79AE}\x{79AF}\x{79B0}\x{79B1}\x{79B2}\x{79B3}\x{79B4}\x{79B5}' .
+'\x{79B6}\x{79B7}\x{79B8}\x{79B9}\x{79BA}\x{79BB}\x{79BD}\x{79BE}\x{79BF}' .
+'\x{79C0}\x{79C1}\x{79C2}\x{79C3}\x{79C5}\x{79C6}\x{79C8}\x{79C9}\x{79CA}' .
+'\x{79CB}\x{79CD}\x{79CE}\x{79CF}\x{79D0}\x{79D1}\x{79D2}\x{79D3}\x{79D5}' .
+'\x{79D6}\x{79D8}\x{79D9}\x{79DA}\x{79DB}\x{79DC}\x{79DD}\x{79DE}\x{79DF}' .
+'\x{79E0}\x{79E1}\x{79E2}\x{79E3}\x{79E4}\x{79E5}\x{79E6}\x{79E7}\x{79E8}' .
+'\x{79E9}\x{79EA}\x{79EB}\x{79EC}\x{79ED}\x{79EE}\x{79EF}\x{79F0}\x{79F1}' .
+'\x{79F2}\x{79F3}\x{79F4}\x{79F5}\x{79F6}\x{79F7}\x{79F8}\x{79F9}\x{79FA}' .
+'\x{79FB}\x{79FC}\x{79FD}\x{79FE}\x{79FF}\x{7A00}\x{7A02}\x{7A03}\x{7A04}' .
+'\x{7A05}\x{7A06}\x{7A08}\x{7A0A}\x{7A0B}\x{7A0C}\x{7A0D}\x{7A0E}\x{7A0F}' .
+'\x{7A10}\x{7A11}\x{7A12}\x{7A13}\x{7A14}\x{7A15}\x{7A16}\x{7A17}\x{7A18}' .
+'\x{7A19}\x{7A1A}\x{7A1B}\x{7A1C}\x{7A1D}\x{7A1E}\x{7A1F}\x{7A20}\x{7A21}' .
+'\x{7A22}\x{7A23}\x{7A24}\x{7A25}\x{7A26}\x{7A27}\x{7A28}\x{7A29}\x{7A2A}' .
+'\x{7A2B}\x{7A2D}\x{7A2E}\x{7A2F}\x{7A30}\x{7A31}\x{7A32}\x{7A33}\x{7A34}' .
+'\x{7A35}\x{7A37}\x{7A39}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' .
+'\x{7A41}\x{7A42}\x{7A43}\x{7A44}\x{7A45}\x{7A46}\x{7A47}\x{7A48}\x{7A49}' .
+'\x{7A4A}\x{7A4B}\x{7A4C}\x{7A4D}\x{7A4E}\x{7A50}\x{7A51}\x{7A52}\x{7A53}' .
+'\x{7A54}\x{7A55}\x{7A56}\x{7A57}\x{7A58}\x{7A59}\x{7A5A}\x{7A5B}\x{7A5C}' .
+'\x{7A5D}\x{7A5E}\x{7A5F}\x{7A60}\x{7A61}\x{7A62}\x{7A65}\x{7A66}\x{7A67}' .
+'\x{7A68}\x{7A69}\x{7A6B}\x{7A6C}\x{7A6D}\x{7A6E}\x{7A70}\x{7A71}\x{7A72}' .
+'\x{7A73}\x{7A74}\x{7A75}\x{7A76}\x{7A77}\x{7A78}\x{7A79}\x{7A7A}\x{7A7B}' .
+'\x{7A7C}\x{7A7D}\x{7A7E}\x{7A7F}\x{7A80}\x{7A81}\x{7A83}\x{7A84}\x{7A85}' .
+'\x{7A86}\x{7A87}\x{7A88}\x{7A89}\x{7A8A}\x{7A8B}\x{7A8C}\x{7A8D}\x{7A8E}' .
+'\x{7A8F}\x{7A90}\x{7A91}\x{7A92}\x{7A93}\x{7A94}\x{7A95}\x{7A96}\x{7A97}' .
+'\x{7A98}\x{7A99}\x{7A9C}\x{7A9D}\x{7A9E}\x{7A9F}\x{7AA0}\x{7AA1}\x{7AA2}' .
+'\x{7AA3}\x{7AA4}\x{7AA5}\x{7AA6}\x{7AA7}\x{7AA8}\x{7AA9}\x{7AAA}\x{7AAB}' .
+'\x{7AAC}\x{7AAD}\x{7AAE}\x{7AAF}\x{7AB0}\x{7AB1}\x{7AB2}\x{7AB3}\x{7AB4}' .
+'\x{7AB5}\x{7AB6}\x{7AB7}\x{7AB8}\x{7ABA}\x{7ABE}\x{7ABF}\x{7AC0}\x{7AC1}' .
+'\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}\x{7AC9}\x{7ACA}\x{7ACB}\x{7ACC}\x{7ACD}' .
+'\x{7ACE}\x{7ACF}\x{7AD0}\x{7AD1}\x{7AD2}\x{7AD3}\x{7AD4}\x{7AD5}\x{7AD6}' .
+'\x{7AD8}\x{7AD9}\x{7ADB}\x{7ADC}\x{7ADD}\x{7ADE}\x{7ADF}\x{7AE0}\x{7AE1}' .
+'\x{7AE2}\x{7AE3}\x{7AE4}\x{7AE5}\x{7AE6}\x{7AE7}\x{7AE8}\x{7AEA}\x{7AEB}' .
+'\x{7AEC}\x{7AED}\x{7AEE}\x{7AEF}\x{7AF0}\x{7AF1}\x{7AF2}\x{7AF3}\x{7AF4}' .
+'\x{7AF6}\x{7AF7}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFB}\x{7AFD}\x{7AFE}\x{7AFF}' .
+'\x{7B00}\x{7B01}\x{7B02}\x{7B03}\x{7B04}\x{7B05}\x{7B06}\x{7B08}\x{7B09}' .
+'\x{7B0A}\x{7B0B}\x{7B0C}\x{7B0D}\x{7B0E}\x{7B0F}\x{7B10}\x{7B11}\x{7B12}' .
+'\x{7B13}\x{7B14}\x{7B15}\x{7B16}\x{7B17}\x{7B18}\x{7B19}\x{7B1A}\x{7B1B}' .
+'\x{7B1C}\x{7B1D}\x{7B1E}\x{7B20}\x{7B21}\x{7B22}\x{7B23}\x{7B24}\x{7B25}' .
+'\x{7B26}\x{7B28}\x{7B2A}\x{7B2B}\x{7B2C}\x{7B2D}\x{7B2E}\x{7B2F}\x{7B30}' .
+'\x{7B31}\x{7B32}\x{7B33}\x{7B34}\x{7B35}\x{7B36}\x{7B37}\x{7B38}\x{7B39}' .
+'\x{7B3A}\x{7B3B}\x{7B3C}\x{7B3D}\x{7B3E}\x{7B3F}\x{7B40}\x{7B41}\x{7B43}' .
+'\x{7B44}\x{7B45}\x{7B46}\x{7B47}\x{7B48}\x{7B49}\x{7B4A}\x{7B4B}\x{7B4C}' .
+'\x{7B4D}\x{7B4E}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B55}\x{7B56}' .
+'\x{7B57}\x{7B58}\x{7B59}\x{7B5A}\x{7B5B}\x{7B5C}\x{7B5D}\x{7B5E}\x{7B5F}' .
+'\x{7B60}\x{7B61}\x{7B62}\x{7B63}\x{7B64}\x{7B65}\x{7B66}\x{7B67}\x{7B68}' .
+'\x{7B69}\x{7B6A}\x{7B6B}\x{7B6C}\x{7B6D}\x{7B6E}\x{7B70}\x{7B71}\x{7B72}' .
+'\x{7B73}\x{7B74}\x{7B75}\x{7B76}\x{7B77}\x{7B78}\x{7B79}\x{7B7B}\x{7B7C}' .
+'\x{7B7D}\x{7B7E}\x{7B7F}\x{7B80}\x{7B81}\x{7B82}\x{7B83}\x{7B84}\x{7B85}' .
+'\x{7B87}\x{7B88}\x{7B89}\x{7B8A}\x{7B8B}\x{7B8C}\x{7B8D}\x{7B8E}\x{7B8F}' .
+'\x{7B90}\x{7B91}\x{7B93}\x{7B94}\x{7B95}\x{7B96}\x{7B97}\x{7B98}\x{7B99}' .
+'\x{7B9A}\x{7B9B}\x{7B9C}\x{7B9D}\x{7B9E}\x{7B9F}\x{7BA0}\x{7BA1}\x{7BA2}' .
+'\x{7BA4}\x{7BA6}\x{7BA7}\x{7BA8}\x{7BA9}\x{7BAA}\x{7BAB}\x{7BAC}\x{7BAD}' .
+'\x{7BAE}\x{7BAF}\x{7BB1}\x{7BB3}\x{7BB4}\x{7BB5}\x{7BB6}\x{7BB7}\x{7BB8}' .
+'\x{7BB9}\x{7BBA}\x{7BBB}\x{7BBC}\x{7BBD}\x{7BBE}\x{7BBF}\x{7BC0}\x{7BC1}' .
+'\x{7BC2}\x{7BC3}\x{7BC4}\x{7BC5}\x{7BC6}\x{7BC7}\x{7BC8}\x{7BC9}\x{7BCA}' .
+'\x{7BCB}\x{7BCC}\x{7BCD}\x{7BCE}\x{7BD0}\x{7BD1}\x{7BD2}\x{7BD3}\x{7BD4}' .
+'\x{7BD5}\x{7BD6}\x{7BD7}\x{7BD8}\x{7BD9}\x{7BDA}\x{7BDB}\x{7BDC}\x{7BDD}' .
+'\x{7BDE}\x{7BDF}\x{7BE0}\x{7BE1}\x{7BE2}\x{7BE3}\x{7BE4}\x{7BE5}\x{7BE6}' .
+'\x{7BE7}\x{7BE8}\x{7BE9}\x{7BEA}\x{7BEB}\x{7BEC}\x{7BED}\x{7BEE}\x{7BEF}' .
+'\x{7BF0}\x{7BF1}\x{7BF2}\x{7BF3}\x{7BF4}\x{7BF5}\x{7BF6}\x{7BF7}\x{7BF8}' .
+'\x{7BF9}\x{7BFB}\x{7BFC}\x{7BFD}\x{7BFE}\x{7BFF}\x{7C00}\x{7C01}\x{7C02}' .
+'\x{7C03}\x{7C04}\x{7C05}\x{7C06}\x{7C07}\x{7C08}\x{7C09}\x{7C0A}\x{7C0B}' .
+'\x{7C0C}\x{7C0D}\x{7C0E}\x{7C0F}\x{7C10}\x{7C11}\x{7C12}\x{7C13}\x{7C15}' .
+'\x{7C16}\x{7C17}\x{7C18}\x{7C19}\x{7C1A}\x{7C1C}\x{7C1D}\x{7C1E}\x{7C1F}' .
+'\x{7C20}\x{7C21}\x{7C22}\x{7C23}\x{7C24}\x{7C25}\x{7C26}\x{7C27}\x{7C28}' .
+'\x{7C29}\x{7C2A}\x{7C2B}\x{7C2C}\x{7C2D}\x{7C30}\x{7C31}\x{7C32}\x{7C33}' .
+'\x{7C34}\x{7C35}\x{7C36}\x{7C37}\x{7C38}\x{7C39}\x{7C3A}\x{7C3B}\x{7C3C}' .
+'\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C41}\x{7C42}\x{7C43}\x{7C44}\x{7C45}' .
+'\x{7C46}\x{7C47}\x{7C48}\x{7C49}\x{7C4A}\x{7C4B}\x{7C4C}\x{7C4D}\x{7C4E}' .
+'\x{7C50}\x{7C51}\x{7C53}\x{7C54}\x{7C56}\x{7C57}\x{7C58}\x{7C59}\x{7C5A}' .
+'\x{7C5B}\x{7C5C}\x{7C5E}\x{7C5F}\x{7C60}\x{7C61}\x{7C62}\x{7C63}\x{7C64}' .
+'\x{7C65}\x{7C66}\x{7C67}\x{7C68}\x{7C69}\x{7C6A}\x{7C6B}\x{7C6C}\x{7C6D}' .
+'\x{7C6E}\x{7C6F}\x{7C70}\x{7C71}\x{7C72}\x{7C73}\x{7C74}\x{7C75}\x{7C77}' .
+'\x{7C78}\x{7C79}\x{7C7A}\x{7C7B}\x{7C7C}\x{7C7D}\x{7C7E}\x{7C7F}\x{7C80}' .
+'\x{7C81}\x{7C82}\x{7C84}\x{7C85}\x{7C86}\x{7C88}\x{7C89}\x{7C8A}\x{7C8B}' .
+'\x{7C8C}\x{7C8D}\x{7C8E}\x{7C8F}\x{7C90}\x{7C91}\x{7C92}\x{7C94}\x{7C95}' .
+'\x{7C96}\x{7C97}\x{7C98}\x{7C99}\x{7C9B}\x{7C9C}\x{7C9D}\x{7C9E}\x{7C9F}' .
+'\x{7CA0}\x{7CA1}\x{7CA2}\x{7CA3}\x{7CA4}\x{7CA5}\x{7CA6}\x{7CA7}\x{7CA8}' .
+'\x{7CA9}\x{7CAA}\x{7CAD}\x{7CAE}\x{7CAF}\x{7CB0}\x{7CB1}\x{7CB2}\x{7CB3}' .
+'\x{7CB4}\x{7CB5}\x{7CB6}\x{7CB7}\x{7CB8}\x{7CB9}\x{7CBA}\x{7CBB}\x{7CBC}' .
+'\x{7CBD}\x{7CBE}\x{7CBF}\x{7CC0}\x{7CC1}\x{7CC2}\x{7CC3}\x{7CC4}\x{7CC5}' .
+'\x{7CC6}\x{7CC7}\x{7CC8}\x{7CC9}\x{7CCA}\x{7CCB}\x{7CCC}\x{7CCD}\x{7CCE}' .
+'\x{7CCF}\x{7CD0}\x{7CD1}\x{7CD2}\x{7CD4}\x{7CD5}\x{7CD6}\x{7CD7}\x{7CD8}' .
+'\x{7CD9}\x{7CDC}\x{7CDD}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE4}\x{7CE7}' .
+'\x{7CE8}\x{7CE9}\x{7CEA}\x{7CEB}\x{7CEC}\x{7CED}\x{7CEE}\x{7CEF}\x{7CF0}' .
+'\x{7CF1}\x{7CF2}\x{7CF3}\x{7CF4}\x{7CF5}\x{7CF6}\x{7CF7}\x{7CF8}\x{7CF9}' .
+'\x{7CFA}\x{7CFB}\x{7CFD}\x{7CFE}\x{7D00}\x{7D01}\x{7D02}\x{7D03}\x{7D04}' .
+'\x{7D05}\x{7D06}\x{7D07}\x{7D08}\x{7D09}\x{7D0A}\x{7D0B}\x{7D0C}\x{7D0D}' .
+'\x{7D0E}\x{7D0F}\x{7D10}\x{7D11}\x{7D12}\x{7D13}\x{7D14}\x{7D15}\x{7D16}' .
+'\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D1D}\x{7D1E}\x{7D1F}' .
+'\x{7D20}\x{7D21}\x{7D22}\x{7D24}\x{7D25}\x{7D26}\x{7D27}\x{7D28}\x{7D29}' .
+'\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D31}\x{7D32}\x{7D33}\x{7D34}' .
+'\x{7D35}\x{7D36}\x{7D37}\x{7D38}\x{7D39}\x{7D3A}\x{7D3B}\x{7D3C}\x{7D3D}' .
+'\x{7D3E}\x{7D3F}\x{7D40}\x{7D41}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}' .
+'\x{7D47}\x{7D49}\x{7D4A}\x{7D4B}\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D51}' .
+'\x{7D52}\x{7D53}\x{7D54}\x{7D55}\x{7D56}\x{7D57}\x{7D58}\x{7D59}\x{7D5B}' .
+'\x{7D5C}\x{7D5D}\x{7D5E}\x{7D5F}\x{7D60}\x{7D61}\x{7D62}\x{7D63}\x{7D65}' .
+'\x{7D66}\x{7D67}\x{7D68}\x{7D69}\x{7D6A}\x{7D6B}\x{7D6C}\x{7D6D}\x{7D6E}' .
+'\x{7D6F}\x{7D70}\x{7D71}\x{7D72}\x{7D73}\x{7D74}\x{7D75}\x{7D76}\x{7D77}' .
+'\x{7D79}\x{7D7A}\x{7D7B}\x{7D7C}\x{7D7D}\x{7D7E}\x{7D7F}\x{7D80}\x{7D81}' .
+'\x{7D83}\x{7D84}\x{7D85}\x{7D86}\x{7D87}\x{7D88}\x{7D89}\x{7D8A}\x{7D8B}' .
+'\x{7D8C}\x{7D8D}\x{7D8E}\x{7D8F}\x{7D90}\x{7D91}\x{7D92}\x{7D93}\x{7D94}' .
+'\x{7D96}\x{7D97}\x{7D99}\x{7D9B}\x{7D9C}\x{7D9D}\x{7D9E}\x{7D9F}\x{7DA0}' .
+'\x{7DA1}\x{7DA2}\x{7DA3}\x{7DA5}\x{7DA6}\x{7DA7}\x{7DA9}\x{7DAA}\x{7DAB}' .
+'\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}\x{7DB1}\x{7DB2}\x{7DB3}\x{7DB4}' .
+'\x{7DB5}\x{7DB6}\x{7DB7}\x{7DB8}\x{7DB9}\x{7DBA}\x{7DBB}\x{7DBC}\x{7DBD}' .
+'\x{7DBE}\x{7DBF}\x{7DC0}\x{7DC1}\x{7DC2}\x{7DC3}\x{7DC4}\x{7DC5}\x{7DC6}' .
+'\x{7DC7}\x{7DC8}\x{7DC9}\x{7DCA}\x{7DCB}\x{7DCC}\x{7DCE}\x{7DCF}\x{7DD0}' .
+'\x{7DD1}\x{7DD2}\x{7DD4}\x{7DD5}\x{7DD6}\x{7DD7}\x{7DD8}\x{7DD9}\x{7DDA}' .
+'\x{7DDB}\x{7DDD}\x{7DDE}\x{7DDF}\x{7DE0}\x{7DE1}\x{7DE2}\x{7DE3}\x{7DE6}' .
+'\x{7DE7}\x{7DE8}\x{7DE9}\x{7DEA}\x{7DEC}\x{7DED}\x{7DEE}\x{7DEF}\x{7DF0}' .
+'\x{7DF1}\x{7DF2}\x{7DF3}\x{7DF4}\x{7DF5}\x{7DF6}\x{7DF7}\x{7DF8}\x{7DF9}' .
+'\x{7DFA}\x{7DFB}\x{7DFC}\x{7E00}\x{7E01}\x{7E02}\x{7E03}\x{7E04}\x{7E05}' .
+'\x{7E06}\x{7E07}\x{7E08}\x{7E09}\x{7E0A}\x{7E0B}\x{7E0C}\x{7E0D}\x{7E0E}' .
+'\x{7E0F}\x{7E10}\x{7E11}\x{7E12}\x{7E13}\x{7E14}\x{7E15}\x{7E16}\x{7E17}' .
+'\x{7E19}\x{7E1A}\x{7E1B}\x{7E1C}\x{7E1D}\x{7E1E}\x{7E1F}\x{7E20}\x{7E21}' .
+'\x{7E22}\x{7E23}\x{7E24}\x{7E25}\x{7E26}\x{7E27}\x{7E28}\x{7E29}\x{7E2A}' .
+'\x{7E2B}\x{7E2C}\x{7E2D}\x{7E2E}\x{7E2F}\x{7E30}\x{7E31}\x{7E32}\x{7E33}' .
+'\x{7E34}\x{7E35}\x{7E36}\x{7E37}\x{7E38}\x{7E39}\x{7E3A}\x{7E3B}\x{7E3C}' .
+'\x{7E3D}\x{7E3E}\x{7E3F}\x{7E40}\x{7E41}\x{7E42}\x{7E43}\x{7E44}\x{7E45}' .
+'\x{7E46}\x{7E47}\x{7E48}\x{7E49}\x{7E4C}\x{7E4D}\x{7E4E}\x{7E4F}\x{7E50}' .
+'\x{7E51}\x{7E52}\x{7E53}\x{7E54}\x{7E55}\x{7E56}\x{7E57}\x{7E58}\x{7E59}' .
+'\x{7E5A}\x{7E5C}\x{7E5D}\x{7E5E}\x{7E5F}\x{7E60}\x{7E61}\x{7E62}\x{7E63}' .
+'\x{7E65}\x{7E66}\x{7E67}\x{7E68}\x{7E69}\x{7E6A}\x{7E6B}\x{7E6C}\x{7E6D}' .
+'\x{7E6E}\x{7E6F}\x{7E70}\x{7E71}\x{7E72}\x{7E73}\x{7E74}\x{7E75}\x{7E76}' .
+'\x{7E77}\x{7E78}\x{7E79}\x{7E7A}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7E}\x{7E7F}' .
+'\x{7E80}\x{7E81}\x{7E82}\x{7E83}\x{7E84}\x{7E85}\x{7E86}\x{7E87}\x{7E88}' .
+'\x{7E89}\x{7E8A}\x{7E8B}\x{7E8C}\x{7E8D}\x{7E8E}\x{7E8F}\x{7E90}\x{7E91}' .
+'\x{7E92}\x{7E93}\x{7E94}\x{7E95}\x{7E96}\x{7E97}\x{7E98}\x{7E99}\x{7E9A}' .
+'\x{7E9B}\x{7E9C}\x{7E9E}\x{7E9F}\x{7EA0}\x{7EA1}\x{7EA2}\x{7EA3}\x{7EA4}' .
+'\x{7EA5}\x{7EA6}\x{7EA7}\x{7EA8}\x{7EA9}\x{7EAA}\x{7EAB}\x{7EAC}\x{7EAD}' .
+'\x{7EAE}\x{7EAF}\x{7EB0}\x{7EB1}\x{7EB2}\x{7EB3}\x{7EB4}\x{7EB5}\x{7EB6}' .
+'\x{7EB7}\x{7EB8}\x{7EB9}\x{7EBA}\x{7EBB}\x{7EBC}\x{7EBD}\x{7EBE}\x{7EBF}' .
+'\x{7EC0}\x{7EC1}\x{7EC2}\x{7EC3}\x{7EC4}\x{7EC5}\x{7EC6}\x{7EC7}\x{7EC8}' .
+'\x{7EC9}\x{7ECA}\x{7ECB}\x{7ECC}\x{7ECD}\x{7ECE}\x{7ECF}\x{7ED0}\x{7ED1}' .
+'\x{7ED2}\x{7ED3}\x{7ED4}\x{7ED5}\x{7ED6}\x{7ED7}\x{7ED8}\x{7ED9}\x{7EDA}' .
+'\x{7EDB}\x{7EDC}\x{7EDD}\x{7EDE}\x{7EDF}\x{7EE0}\x{7EE1}\x{7EE2}\x{7EE3}' .
+'\x{7EE4}\x{7EE5}\x{7EE6}\x{7EE7}\x{7EE8}\x{7EE9}\x{7EEA}\x{7EEB}\x{7EEC}' .
+'\x{7EED}\x{7EEE}\x{7EEF}\x{7EF0}\x{7EF1}\x{7EF2}\x{7EF3}\x{7EF4}\x{7EF5}' .
+'\x{7EF6}\x{7EF7}\x{7EF8}\x{7EF9}\x{7EFA}\x{7EFB}\x{7EFC}\x{7EFD}\x{7EFE}' .
+'\x{7EFF}\x{7F00}\x{7F01}\x{7F02}\x{7F03}\x{7F04}\x{7F05}\x{7F06}\x{7F07}' .
+'\x{7F08}\x{7F09}\x{7F0A}\x{7F0B}\x{7F0C}\x{7F0D}\x{7F0E}\x{7F0F}\x{7F10}' .
+'\x{7F11}\x{7F12}\x{7F13}\x{7F14}\x{7F15}\x{7F16}\x{7F17}\x{7F18}\x{7F19}' .
+'\x{7F1A}\x{7F1B}\x{7F1C}\x{7F1D}\x{7F1E}\x{7F1F}\x{7F20}\x{7F21}\x{7F22}' .
+'\x{7F23}\x{7F24}\x{7F25}\x{7F26}\x{7F27}\x{7F28}\x{7F29}\x{7F2A}\x{7F2B}' .
+'\x{7F2C}\x{7F2D}\x{7F2E}\x{7F2F}\x{7F30}\x{7F31}\x{7F32}\x{7F33}\x{7F34}' .
+'\x{7F35}\x{7F36}\x{7F37}\x{7F38}\x{7F39}\x{7F3A}\x{7F3D}\x{7F3E}\x{7F3F}' .
+'\x{7F40}\x{7F42}\x{7F43}\x{7F44}\x{7F45}\x{7F47}\x{7F48}\x{7F49}\x{7F4A}' .
+'\x{7F4B}\x{7F4C}\x{7F4D}\x{7F4E}\x{7F4F}\x{7F50}\x{7F51}\x{7F52}\x{7F53}' .
+'\x{7F54}\x{7F55}\x{7F56}\x{7F57}\x{7F58}\x{7F5A}\x{7F5B}\x{7F5C}\x{7F5D}' .
+'\x{7F5E}\x{7F5F}\x{7F60}\x{7F61}\x{7F62}\x{7F63}\x{7F64}\x{7F65}\x{7F66}' .
+'\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6C}\x{7F6D}\x{7F6E}\x{7F6F}' .
+'\x{7F70}\x{7F71}\x{7F72}\x{7F73}\x{7F74}\x{7F75}\x{7F76}\x{7F77}\x{7F78}' .
+'\x{7F79}\x{7F7A}\x{7F7B}\x{7F7C}\x{7F7D}\x{7F7E}\x{7F7F}\x{7F80}\x{7F81}' .
+'\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}\x{7F88}\x{7F89}\x{7F8A}\x{7F8B}' .
+'\x{7F8C}\x{7F8D}\x{7F8E}\x{7F8F}\x{7F91}\x{7F92}\x{7F93}\x{7F94}\x{7F95}' .
+'\x{7F96}\x{7F98}\x{7F9A}\x{7F9B}\x{7F9C}\x{7F9D}\x{7F9E}\x{7F9F}\x{7FA0}' .
+'\x{7FA1}\x{7FA2}\x{7FA3}\x{7FA4}\x{7FA5}\x{7FA6}\x{7FA7}\x{7FA8}\x{7FA9}' .
+'\x{7FAA}\x{7FAB}\x{7FAC}\x{7FAD}\x{7FAE}\x{7FAF}\x{7FB0}\x{7FB1}\x{7FB2}' .
+'\x{7FB3}\x{7FB5}\x{7FB6}\x{7FB7}\x{7FB8}\x{7FB9}\x{7FBA}\x{7FBB}\x{7FBC}' .
+'\x{7FBD}\x{7FBE}\x{7FBF}\x{7FC0}\x{7FC1}\x{7FC2}\x{7FC3}\x{7FC4}\x{7FC5}' .
+'\x{7FC6}\x{7FC7}\x{7FC8}\x{7FC9}\x{7FCA}\x{7FCB}\x{7FCC}\x{7FCD}\x{7FCE}' .
+'\x{7FCF}\x{7FD0}\x{7FD1}\x{7FD2}\x{7FD3}\x{7FD4}\x{7FD5}\x{7FD7}\x{7FD8}' .
+'\x{7FD9}\x{7FDA}\x{7FDB}\x{7FDC}\x{7FDE}\x{7FDF}\x{7FE0}\x{7FE1}\x{7FE2}' .
+'\x{7FE3}\x{7FE5}\x{7FE6}\x{7FE7}\x{7FE8}\x{7FE9}\x{7FEA}\x{7FEB}\x{7FEC}' .
+'\x{7FED}\x{7FEE}\x{7FEF}\x{7FF0}\x{7FF1}\x{7FF2}\x{7FF3}\x{7FF4}\x{7FF5}' .
+'\x{7FF6}\x{7FF7}\x{7FF8}\x{7FF9}\x{7FFA}\x{7FFB}\x{7FFC}\x{7FFD}\x{7FFE}' .
+'\x{7FFF}\x{8000}\x{8001}\x{8002}\x{8003}\x{8004}\x{8005}\x{8006}\x{8007}' .
+'\x{8008}\x{8009}\x{800B}\x{800C}\x{800D}\x{800E}\x{800F}\x{8010}\x{8011}' .
+'\x{8012}\x{8013}\x{8014}\x{8015}\x{8016}\x{8017}\x{8018}\x{8019}\x{801A}' .
+'\x{801B}\x{801C}\x{801D}\x{801E}\x{801F}\x{8020}\x{8021}\x{8022}\x{8023}' .
+'\x{8024}\x{8025}\x{8026}\x{8027}\x{8028}\x{8029}\x{802A}\x{802B}\x{802C}' .
+'\x{802D}\x{802E}\x{8030}\x{8031}\x{8032}\x{8033}\x{8034}\x{8035}\x{8036}' .
+'\x{8037}\x{8038}\x{8039}\x{803A}\x{803B}\x{803D}\x{803E}\x{803F}\x{8041}' .
+'\x{8042}\x{8043}\x{8044}\x{8045}\x{8046}\x{8047}\x{8048}\x{8049}\x{804A}' .
+'\x{804B}\x{804C}\x{804D}\x{804E}\x{804F}\x{8050}\x{8051}\x{8052}\x{8053}' .
+'\x{8054}\x{8055}\x{8056}\x{8057}\x{8058}\x{8059}\x{805A}\x{805B}\x{805C}' .
+'\x{805D}\x{805E}\x{805F}\x{8060}\x{8061}\x{8062}\x{8063}\x{8064}\x{8065}' .
+'\x{8067}\x{8068}\x{8069}\x{806A}\x{806B}\x{806C}\x{806D}\x{806E}\x{806F}' .
+'\x{8070}\x{8071}\x{8072}\x{8073}\x{8074}\x{8075}\x{8076}\x{8077}\x{8078}' .
+'\x{8079}\x{807A}\x{807B}\x{807C}\x{807D}\x{807E}\x{807F}\x{8080}\x{8081}' .
+'\x{8082}\x{8083}\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808A}\x{808B}' .
+'\x{808C}\x{808D}\x{808F}\x{8090}\x{8091}\x{8092}\x{8093}\x{8095}\x{8096}' .
+'\x{8097}\x{8098}\x{8099}\x{809A}\x{809B}\x{809C}\x{809D}\x{809E}\x{809F}' .
+'\x{80A0}\x{80A1}\x{80A2}\x{80A3}\x{80A4}\x{80A5}\x{80A9}\x{80AA}\x{80AB}' .
+'\x{80AD}\x{80AE}\x{80AF}\x{80B0}\x{80B1}\x{80B2}\x{80B4}\x{80B5}\x{80B6}' .
+'\x{80B7}\x{80B8}\x{80BA}\x{80BB}\x{80BC}\x{80BD}\x{80BE}\x{80BF}\x{80C0}' .
+'\x{80C1}\x{80C2}\x{80C3}\x{80C4}\x{80C5}\x{80C6}\x{80C7}\x{80C8}\x{80C9}' .
+'\x{80CA}\x{80CB}\x{80CC}\x{80CD}\x{80CE}\x{80CF}\x{80D0}\x{80D1}\x{80D2}' .
+'\x{80D3}\x{80D4}\x{80D5}\x{80D6}\x{80D7}\x{80D8}\x{80D9}\x{80DA}\x{80DB}' .
+'\x{80DC}\x{80DD}\x{80DE}\x{80E0}\x{80E1}\x{80E2}\x{80E3}\x{80E4}\x{80E5}' .
+'\x{80E6}\x{80E7}\x{80E8}\x{80E9}\x{80EA}\x{80EB}\x{80EC}\x{80ED}\x{80EE}' .
+'\x{80EF}\x{80F0}\x{80F1}\x{80F2}\x{80F3}\x{80F4}\x{80F5}\x{80F6}\x{80F7}' .
+'\x{80F8}\x{80F9}\x{80FA}\x{80FB}\x{80FC}\x{80FD}\x{80FE}\x{80FF}\x{8100}' .
+'\x{8101}\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{810B}' .
+'\x{810C}\x{810D}\x{810E}\x{810F}\x{8110}\x{8111}\x{8112}\x{8113}\x{8114}' .
+'\x{8115}\x{8116}\x{8118}\x{8119}\x{811A}\x{811B}\x{811C}\x{811D}\x{811E}' .
+'\x{811F}\x{8120}\x{8121}\x{8122}\x{8123}\x{8124}\x{8125}\x{8126}\x{8127}' .
+'\x{8128}\x{8129}\x{812A}\x{812B}\x{812C}\x{812D}\x{812E}\x{812F}\x{8130}' .
+'\x{8131}\x{8132}\x{8136}\x{8137}\x{8138}\x{8139}\x{813A}\x{813B}\x{813C}' .
+'\x{813D}\x{813E}\x{813F}\x{8140}\x{8141}\x{8142}\x{8143}\x{8144}\x{8145}' .
+'\x{8146}\x{8147}\x{8148}\x{8149}\x{814A}\x{814B}\x{814C}\x{814D}\x{814E}' .
+'\x{814F}\x{8150}\x{8151}\x{8152}\x{8153}\x{8154}\x{8155}\x{8156}\x{8157}' .
+'\x{8158}\x{8159}\x{815A}\x{815B}\x{815C}\x{815D}\x{815E}\x{8160}\x{8161}' .
+'\x{8162}\x{8163}\x{8164}\x{8165}\x{8166}\x{8167}\x{8168}\x{8169}\x{816A}' .
+'\x{816B}\x{816C}\x{816D}\x{816E}\x{816F}\x{8170}\x{8171}\x{8172}\x{8173}' .
+'\x{8174}\x{8175}\x{8176}\x{8177}\x{8178}\x{8179}\x{817A}\x{817B}\x{817C}' .
+'\x{817D}\x{817E}\x{817F}\x{8180}\x{8181}\x{8182}\x{8183}\x{8185}\x{8186}' .
+'\x{8187}\x{8188}\x{8189}\x{818A}\x{818B}\x{818C}\x{818D}\x{818E}\x{818F}' .
+'\x{8191}\x{8192}\x{8193}\x{8194}\x{8195}\x{8197}\x{8198}\x{8199}\x{819A}' .
+'\x{819B}\x{819C}\x{819D}\x{819E}\x{819F}\x{81A0}\x{81A1}\x{81A2}\x{81A3}' .
+'\x{81A4}\x{81A5}\x{81A6}\x{81A7}\x{81A8}\x{81A9}\x{81AA}\x{81AB}\x{81AC}' .
+'\x{81AD}\x{81AE}\x{81AF}\x{81B0}\x{81B1}\x{81B2}\x{81B3}\x{81B4}\x{81B5}' .
+'\x{81B6}\x{81B7}\x{81B8}\x{81B9}\x{81BA}\x{81BB}\x{81BC}\x{81BD}\x{81BE}' .
+'\x{81BF}\x{81C0}\x{81C1}\x{81C2}\x{81C3}\x{81C4}\x{81C5}\x{81C6}\x{81C7}' .
+'\x{81C8}\x{81C9}\x{81CA}\x{81CC}\x{81CD}\x{81CE}\x{81CF}\x{81D0}\x{81D1}' .
+'\x{81D2}\x{81D4}\x{81D5}\x{81D6}\x{81D7}\x{81D8}\x{81D9}\x{81DA}\x{81DB}' .
+'\x{81DC}\x{81DD}\x{81DE}\x{81DF}\x{81E0}\x{81E1}\x{81E2}\x{81E3}\x{81E5}' .
+'\x{81E6}\x{81E7}\x{81E8}\x{81E9}\x{81EA}\x{81EB}\x{81EC}\x{81ED}\x{81EE}' .
+'\x{81F1}\x{81F2}\x{81F3}\x{81F4}\x{81F5}\x{81F6}\x{81F7}\x{81F8}\x{81F9}' .
+'\x{81FA}\x{81FB}\x{81FC}\x{81FD}\x{81FE}\x{81FF}\x{8200}\x{8201}\x{8202}' .
+'\x{8203}\x{8204}\x{8205}\x{8206}\x{8207}\x{8208}\x{8209}\x{820A}\x{820B}' .
+'\x{820C}\x{820D}\x{820E}\x{820F}\x{8210}\x{8211}\x{8212}\x{8214}\x{8215}' .
+'\x{8216}\x{8218}\x{8219}\x{821A}\x{821B}\x{821C}\x{821D}\x{821E}\x{821F}' .
+'\x{8220}\x{8221}\x{8222}\x{8223}\x{8225}\x{8226}\x{8227}\x{8228}\x{8229}' .
+'\x{822A}\x{822B}\x{822C}\x{822D}\x{822F}\x{8230}\x{8231}\x{8232}\x{8233}' .
+'\x{8234}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{823A}\x{823B}\x{823C}' .
+'\x{823D}\x{823E}\x{823F}\x{8240}\x{8242}\x{8243}\x{8244}\x{8245}\x{8246}' .
+'\x{8247}\x{8248}\x{8249}\x{824A}\x{824B}\x{824C}\x{824D}\x{824E}\x{824F}' .
+'\x{8250}\x{8251}\x{8252}\x{8253}\x{8254}\x{8255}\x{8256}\x{8257}\x{8258}' .
+'\x{8259}\x{825A}\x{825B}\x{825C}\x{825D}\x{825E}\x{825F}\x{8260}\x{8261}' .
+'\x{8263}\x{8264}\x{8266}\x{8267}\x{8268}\x{8269}\x{826A}\x{826B}\x{826C}' .
+'\x{826D}\x{826E}\x{826F}\x{8270}\x{8271}\x{8272}\x{8273}\x{8274}\x{8275}' .
+'\x{8276}\x{8277}\x{8278}\x{8279}\x{827A}\x{827B}\x{827C}\x{827D}\x{827E}' .
+'\x{827F}\x{8280}\x{8281}\x{8282}\x{8283}\x{8284}\x{8285}\x{8286}\x{8287}' .
+'\x{8288}\x{8289}\x{828A}\x{828B}\x{828D}\x{828E}\x{828F}\x{8290}\x{8291}' .
+'\x{8292}\x{8293}\x{8294}\x{8295}\x{8296}\x{8297}\x{8298}\x{8299}\x{829A}' .
+'\x{829B}\x{829C}\x{829D}\x{829E}\x{829F}\x{82A0}\x{82A1}\x{82A2}\x{82A3}' .
+'\x{82A4}\x{82A5}\x{82A6}\x{82A7}\x{82A8}\x{82A9}\x{82AA}\x{82AB}\x{82AC}' .
+'\x{82AD}\x{82AE}\x{82AF}\x{82B0}\x{82B1}\x{82B3}\x{82B4}\x{82B5}\x{82B6}' .
+'\x{82B7}\x{82B8}\x{82B9}\x{82BA}\x{82BB}\x{82BC}\x{82BD}\x{82BE}\x{82BF}' .
+'\x{82C0}\x{82C1}\x{82C2}\x{82C3}\x{82C4}\x{82C5}\x{82C6}\x{82C7}\x{82C8}' .
+'\x{82C9}\x{82CA}\x{82CB}\x{82CC}\x{82CD}\x{82CE}\x{82CF}\x{82D0}\x{82D1}' .
+'\x{82D2}\x{82D3}\x{82D4}\x{82D5}\x{82D6}\x{82D7}\x{82D8}\x{82D9}\x{82DA}' .
+'\x{82DB}\x{82DC}\x{82DD}\x{82DE}\x{82DF}\x{82E0}\x{82E1}\x{82E3}\x{82E4}' .
+'\x{82E5}\x{82E6}\x{82E7}\x{82E8}\x{82E9}\x{82EA}\x{82EB}\x{82EC}\x{82ED}' .
+'\x{82EE}\x{82EF}\x{82F0}\x{82F1}\x{82F2}\x{82F3}\x{82F4}\x{82F5}\x{82F6}' .
+'\x{82F7}\x{82F8}\x{82F9}\x{82FA}\x{82FB}\x{82FD}\x{82FE}\x{82FF}\x{8300}' .
+'\x{8301}\x{8302}\x{8303}\x{8304}\x{8305}\x{8306}\x{8307}\x{8308}\x{8309}' .
+'\x{830B}\x{830C}\x{830D}\x{830E}\x{830F}\x{8311}\x{8312}\x{8313}\x{8314}' .
+'\x{8315}\x{8316}\x{8317}\x{8318}\x{8319}\x{831A}\x{831B}\x{831C}\x{831D}' .
+'\x{831E}\x{831F}\x{8320}\x{8321}\x{8322}\x{8323}\x{8324}\x{8325}\x{8326}' .
+'\x{8327}\x{8328}\x{8329}\x{832A}\x{832B}\x{832C}\x{832D}\x{832E}\x{832F}' .
+'\x{8331}\x{8332}\x{8333}\x{8334}\x{8335}\x{8336}\x{8337}\x{8338}\x{8339}' .
+'\x{833A}\x{833B}\x{833C}\x{833D}\x{833E}\x{833F}\x{8340}\x{8341}\x{8342}' .
+'\x{8343}\x{8344}\x{8345}\x{8346}\x{8347}\x{8348}\x{8349}\x{834A}\x{834B}' .
+'\x{834C}\x{834D}\x{834E}\x{834F}\x{8350}\x{8351}\x{8352}\x{8353}\x{8354}' .
+'\x{8356}\x{8357}\x{8358}\x{8359}\x{835A}\x{835B}\x{835C}\x{835D}\x{835E}' .
+'\x{835F}\x{8360}\x{8361}\x{8362}\x{8363}\x{8364}\x{8365}\x{8366}\x{8367}' .
+'\x{8368}\x{8369}\x{836A}\x{836B}\x{836C}\x{836D}\x{836E}\x{836F}\x{8370}' .
+'\x{8371}\x{8372}\x{8373}\x{8374}\x{8375}\x{8376}\x{8377}\x{8378}\x{8379}' .
+'\x{837A}\x{837B}\x{837C}\x{837D}\x{837E}\x{837F}\x{8380}\x{8381}\x{8382}' .
+'\x{8383}\x{8384}\x{8385}\x{8386}\x{8387}\x{8388}\x{8389}\x{838A}\x{838B}' .
+'\x{838C}\x{838D}\x{838E}\x{838F}\x{8390}\x{8391}\x{8392}\x{8393}\x{8394}' .
+'\x{8395}\x{8396}\x{8397}\x{8398}\x{8399}\x{839A}\x{839B}\x{839C}\x{839D}' .
+'\x{839E}\x{83A0}\x{83A1}\x{83A2}\x{83A3}\x{83A4}\x{83A5}\x{83A6}\x{83A7}' .
+'\x{83A8}\x{83A9}\x{83AA}\x{83AB}\x{83AC}\x{83AD}\x{83AE}\x{83AF}\x{83B0}' .
+'\x{83B1}\x{83B2}\x{83B3}\x{83B4}\x{83B6}\x{83B7}\x{83B8}\x{83B9}\x{83BA}' .
+'\x{83BB}\x{83BC}\x{83BD}\x{83BF}\x{83C0}\x{83C1}\x{83C2}\x{83C3}\x{83C4}' .
+'\x{83C5}\x{83C6}\x{83C7}\x{83C8}\x{83C9}\x{83CA}\x{83CB}\x{83CC}\x{83CD}' .
+'\x{83CE}\x{83CF}\x{83D0}\x{83D1}\x{83D2}\x{83D3}\x{83D4}\x{83D5}\x{83D6}' .
+'\x{83D7}\x{83D8}\x{83D9}\x{83DA}\x{83DB}\x{83DC}\x{83DD}\x{83DE}\x{83DF}' .
+'\x{83E0}\x{83E1}\x{83E2}\x{83E3}\x{83E4}\x{83E5}\x{83E7}\x{83E8}\x{83E9}' .
+'\x{83EA}\x{83EB}\x{83EC}\x{83EE}\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F3}' .
+'\x{83F4}\x{83F5}\x{83F6}\x{83F7}\x{83F8}\x{83F9}\x{83FA}\x{83FB}\x{83FC}' .
+'\x{83FD}\x{83FE}\x{83FF}\x{8400}\x{8401}\x{8402}\x{8403}\x{8404}\x{8405}' .
+'\x{8406}\x{8407}\x{8408}\x{8409}\x{840A}\x{840B}\x{840C}\x{840D}\x{840E}' .
+'\x{840F}\x{8410}\x{8411}\x{8412}\x{8413}\x{8415}\x{8418}\x{8419}\x{841A}' .
+'\x{841B}\x{841C}\x{841D}\x{841E}\x{8421}\x{8422}\x{8423}\x{8424}\x{8425}' .
+'\x{8426}\x{8427}\x{8428}\x{8429}\x{842A}\x{842B}\x{842C}\x{842D}\x{842E}' .
+'\x{842F}\x{8430}\x{8431}\x{8432}\x{8433}\x{8434}\x{8435}\x{8436}\x{8437}' .
+'\x{8438}\x{8439}\x{843A}\x{843B}\x{843C}\x{843D}\x{843E}\x{843F}\x{8440}' .
+'\x{8441}\x{8442}\x{8443}\x{8444}\x{8445}\x{8446}\x{8447}\x{8448}\x{8449}' .
+'\x{844A}\x{844B}\x{844C}\x{844D}\x{844E}\x{844F}\x{8450}\x{8451}\x{8452}' .
+'\x{8453}\x{8454}\x{8455}\x{8456}\x{8457}\x{8459}\x{845A}\x{845B}\x{845C}' .
+'\x{845D}\x{845E}\x{845F}\x{8460}\x{8461}\x{8462}\x{8463}\x{8464}\x{8465}' .
+'\x{8466}\x{8467}\x{8468}\x{8469}\x{846A}\x{846B}\x{846C}\x{846D}\x{846E}' .
+'\x{846F}\x{8470}\x{8471}\x{8472}\x{8473}\x{8474}\x{8475}\x{8476}\x{8477}' .
+'\x{8478}\x{8479}\x{847A}\x{847B}\x{847C}\x{847D}\x{847E}\x{847F}\x{8480}' .
+'\x{8481}\x{8482}\x{8484}\x{8485}\x{8486}\x{8487}\x{8488}\x{8489}\x{848A}' .
+'\x{848B}\x{848C}\x{848D}\x{848E}\x{848F}\x{8490}\x{8491}\x{8492}\x{8493}' .
+'\x{8494}\x{8496}\x{8497}\x{8498}\x{8499}\x{849A}\x{849B}\x{849C}\x{849D}' .
+'\x{849E}\x{849F}\x{84A0}\x{84A1}\x{84A2}\x{84A3}\x{84A4}\x{84A5}\x{84A6}' .
+'\x{84A7}\x{84A8}\x{84A9}\x{84AA}\x{84AB}\x{84AC}\x{84AE}\x{84AF}\x{84B0}' .
+'\x{84B1}\x{84B2}\x{84B3}\x{84B4}\x{84B5}\x{84B6}\x{84B8}\x{84B9}\x{84BA}' .
+'\x{84BB}\x{84BC}\x{84BD}\x{84BE}\x{84BF}\x{84C0}\x{84C1}\x{84C2}\x{84C4}' .
+'\x{84C5}\x{84C6}\x{84C7}\x{84C8}\x{84C9}\x{84CA}\x{84CB}\x{84CC}\x{84CD}' .
+'\x{84CE}\x{84CF}\x{84D0}\x{84D1}\x{84D2}\x{84D3}\x{84D4}\x{84D5}\x{84D6}' .
+'\x{84D7}\x{84D8}\x{84D9}\x{84DB}\x{84DC}\x{84DD}\x{84DE}\x{84DF}\x{84E0}' .
+'\x{84E1}\x{84E2}\x{84E3}\x{84E4}\x{84E5}\x{84E6}\x{84E7}\x{84E8}\x{84E9}' .
+'\x{84EA}\x{84EB}\x{84EC}\x{84EE}\x{84EF}\x{84F0}\x{84F1}\x{84F2}\x{84F3}' .
+'\x{84F4}\x{84F5}\x{84F6}\x{84F7}\x{84F8}\x{84F9}\x{84FA}\x{84FB}\x{84FC}' .
+'\x{84FD}\x{84FE}\x{84FF}\x{8500}\x{8501}\x{8502}\x{8503}\x{8504}\x{8506}' .
+'\x{8507}\x{8508}\x{8509}\x{850A}\x{850B}\x{850C}\x{850D}\x{850E}\x{850F}' .
+'\x{8511}\x{8512}\x{8513}\x{8514}\x{8515}\x{8516}\x{8517}\x{8518}\x{8519}' .
+'\x{851A}\x{851B}\x{851C}\x{851D}\x{851E}\x{851F}\x{8520}\x{8521}\x{8522}' .
+'\x{8523}\x{8524}\x{8525}\x{8526}\x{8527}\x{8528}\x{8529}\x{852A}\x{852B}' .
+'\x{852C}\x{852D}\x{852E}\x{852F}\x{8530}\x{8531}\x{8534}\x{8535}\x{8536}' .
+'\x{8537}\x{8538}\x{8539}\x{853A}\x{853B}\x{853C}\x{853D}\x{853E}\x{853F}' .
+'\x{8540}\x{8541}\x{8542}\x{8543}\x{8544}\x{8545}\x{8546}\x{8547}\x{8548}' .
+'\x{8549}\x{854A}\x{854B}\x{854D}\x{854E}\x{854F}\x{8551}\x{8552}\x{8553}' .
+'\x{8554}\x{8555}\x{8556}\x{8557}\x{8558}\x{8559}\x{855A}\x{855B}\x{855C}' .
+'\x{855D}\x{855E}\x{855F}\x{8560}\x{8561}\x{8562}\x{8563}\x{8564}\x{8565}' .
+'\x{8566}\x{8567}\x{8568}\x{8569}\x{856A}\x{856B}\x{856C}\x{856D}\x{856E}' .
+'\x{856F}\x{8570}\x{8571}\x{8572}\x{8573}\x{8574}\x{8575}\x{8576}\x{8577}' .
+'\x{8578}\x{8579}\x{857A}\x{857B}\x{857C}\x{857D}\x{857E}\x{8580}\x{8581}' .
+'\x{8582}\x{8583}\x{8584}\x{8585}\x{8586}\x{8587}\x{8588}\x{8589}\x{858A}' .
+'\x{858B}\x{858C}\x{858D}\x{858E}\x{858F}\x{8590}\x{8591}\x{8592}\x{8594}' .
+'\x{8595}\x{8596}\x{8598}\x{8599}\x{859A}\x{859B}\x{859C}\x{859D}\x{859E}' .
+'\x{859F}\x{85A0}\x{85A1}\x{85A2}\x{85A3}\x{85A4}\x{85A5}\x{85A6}\x{85A7}' .
+'\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AD}\x{85AE}\x{85AF}\x{85B0}' .
+'\x{85B1}\x{85B3}\x{85B4}\x{85B5}\x{85B6}\x{85B7}\x{85B8}\x{85B9}\x{85BA}' .
+'\x{85BC}\x{85BD}\x{85BE}\x{85BF}\x{85C0}\x{85C1}\x{85C2}\x{85C3}\x{85C4}' .
+'\x{85C5}\x{85C6}\x{85C7}\x{85C8}\x{85C9}\x{85CA}\x{85CB}\x{85CD}\x{85CE}' .
+'\x{85CF}\x{85D0}\x{85D1}\x{85D2}\x{85D3}\x{85D4}\x{85D5}\x{85D6}\x{85D7}' .
+'\x{85D8}\x{85D9}\x{85DA}\x{85DB}\x{85DC}\x{85DD}\x{85DE}\x{85DF}\x{85E0}' .
+'\x{85E1}\x{85E2}\x{85E3}\x{85E4}\x{85E5}\x{85E6}\x{85E7}\x{85E8}\x{85E9}' .
+'\x{85EA}\x{85EB}\x{85EC}\x{85ED}\x{85EF}\x{85F0}\x{85F1}\x{85F2}\x{85F4}' .
+'\x{85F5}\x{85F6}\x{85F7}\x{85F8}\x{85F9}\x{85FA}\x{85FB}\x{85FD}\x{85FE}' .
+'\x{85FF}\x{8600}\x{8601}\x{8602}\x{8604}\x{8605}\x{8606}\x{8607}\x{8608}' .
+'\x{8609}\x{860A}\x{860B}\x{860C}\x{860F}\x{8611}\x{8612}\x{8613}\x{8614}' .
+'\x{8616}\x{8617}\x{8618}\x{8619}\x{861A}\x{861B}\x{861C}\x{861E}\x{861F}' .
+'\x{8620}\x{8621}\x{8622}\x{8623}\x{8624}\x{8625}\x{8626}\x{8627}\x{8628}' .
+'\x{8629}\x{862A}\x{862B}\x{862C}\x{862D}\x{862E}\x{862F}\x{8630}\x{8631}' .
+'\x{8632}\x{8633}\x{8634}\x{8635}\x{8636}\x{8638}\x{8639}\x{863A}\x{863B}' .
+'\x{863C}\x{863D}\x{863E}\x{863F}\x{8640}\x{8641}\x{8642}\x{8643}\x{8644}' .
+'\x{8645}\x{8646}\x{8647}\x{8648}\x{8649}\x{864A}\x{864B}\x{864C}\x{864D}' .
+'\x{864E}\x{864F}\x{8650}\x{8651}\x{8652}\x{8653}\x{8654}\x{8655}\x{8656}' .
+'\x{8658}\x{8659}\x{865A}\x{865B}\x{865C}\x{865D}\x{865E}\x{865F}\x{8660}' .
+'\x{8661}\x{8662}\x{8663}\x{8664}\x{8665}\x{8666}\x{8667}\x{8668}\x{8669}' .
+'\x{866A}\x{866B}\x{866C}\x{866D}\x{866E}\x{866F}\x{8670}\x{8671}\x{8672}' .
+'\x{8673}\x{8674}\x{8676}\x{8677}\x{8678}\x{8679}\x{867A}\x{867B}\x{867C}' .
+'\x{867D}\x{867E}\x{867F}\x{8680}\x{8681}\x{8682}\x{8683}\x{8684}\x{8685}' .
+'\x{8686}\x{8687}\x{8688}\x{868A}\x{868B}\x{868C}\x{868D}\x{868E}\x{868F}' .
+'\x{8690}\x{8691}\x{8693}\x{8694}\x{8695}\x{8696}\x{8697}\x{8698}\x{8699}' .
+'\x{869A}\x{869B}\x{869C}\x{869D}\x{869E}\x{869F}\x{86A1}\x{86A2}\x{86A3}' .
+'\x{86A4}\x{86A5}\x{86A7}\x{86A8}\x{86A9}\x{86AA}\x{86AB}\x{86AC}\x{86AD}' .
+'\x{86AE}\x{86AF}\x{86B0}\x{86B1}\x{86B2}\x{86B3}\x{86B4}\x{86B5}\x{86B6}' .
+'\x{86B7}\x{86B8}\x{86B9}\x{86BA}\x{86BB}\x{86BC}\x{86BD}\x{86BE}\x{86BF}' .
+'\x{86C0}\x{86C1}\x{86C2}\x{86C3}\x{86C4}\x{86C5}\x{86C6}\x{86C7}\x{86C8}' .
+'\x{86C9}\x{86CA}\x{86CB}\x{86CC}\x{86CE}\x{86CF}\x{86D0}\x{86D1}\x{86D2}' .
+'\x{86D3}\x{86D4}\x{86D6}\x{86D7}\x{86D8}\x{86D9}\x{86DA}\x{86DB}\x{86DC}' .
+'\x{86DD}\x{86DE}\x{86DF}\x{86E1}\x{86E2}\x{86E3}\x{86E4}\x{86E5}\x{86E6}' .
+'\x{86E8}\x{86E9}\x{86EA}\x{86EB}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F0}' .
+'\x{86F1}\x{86F2}\x{86F3}\x{86F4}\x{86F5}\x{86F6}\x{86F7}\x{86F8}\x{86F9}' .
+'\x{86FA}\x{86FB}\x{86FC}\x{86FE}\x{86FF}\x{8700}\x{8701}\x{8702}\x{8703}' .
+'\x{8704}\x{8705}\x{8706}\x{8707}\x{8708}\x{8709}\x{870A}\x{870B}\x{870C}' .
+'\x{870D}\x{870E}\x{870F}\x{8710}\x{8711}\x{8712}\x{8713}\x{8714}\x{8715}' .
+'\x{8716}\x{8717}\x{8718}\x{8719}\x{871A}\x{871B}\x{871C}\x{871E}\x{871F}' .
+'\x{8720}\x{8721}\x{8722}\x{8723}\x{8724}\x{8725}\x{8726}\x{8727}\x{8728}' .
+'\x{8729}\x{872A}\x{872B}\x{872C}\x{872D}\x{872E}\x{8730}\x{8731}\x{8732}' .
+'\x{8733}\x{8734}\x{8735}\x{8736}\x{8737}\x{8738}\x{8739}\x{873A}\x{873B}' .
+'\x{873C}\x{873E}\x{873F}\x{8740}\x{8741}\x{8742}\x{8743}\x{8744}\x{8746}' .
+'\x{8747}\x{8748}\x{8749}\x{874A}\x{874C}\x{874D}\x{874E}\x{874F}\x{8750}' .
+'\x{8751}\x{8752}\x{8753}\x{8754}\x{8755}\x{8756}\x{8757}\x{8758}\x{8759}' .
+'\x{875A}\x{875B}\x{875C}\x{875D}\x{875E}\x{875F}\x{8760}\x{8761}\x{8762}' .
+'\x{8763}\x{8764}\x{8765}\x{8766}\x{8767}\x{8768}\x{8769}\x{876A}\x{876B}' .
+'\x{876C}\x{876D}\x{876E}\x{876F}\x{8770}\x{8772}\x{8773}\x{8774}\x{8775}' .
+'\x{8776}\x{8777}\x{8778}\x{8779}\x{877A}\x{877B}\x{877C}\x{877D}\x{877E}' .
+'\x{8780}\x{8781}\x{8782}\x{8783}\x{8784}\x{8785}\x{8786}\x{8787}\x{8788}' .
+'\x{8789}\x{878A}\x{878B}\x{878C}\x{878D}\x{878F}\x{8790}\x{8791}\x{8792}' .
+'\x{8793}\x{8794}\x{8795}\x{8796}\x{8797}\x{8798}\x{879A}\x{879B}\x{879C}' .
+'\x{879D}\x{879E}\x{879F}\x{87A0}\x{87A1}\x{87A2}\x{87A3}\x{87A4}\x{87A5}' .
+'\x{87A6}\x{87A7}\x{87A8}\x{87A9}\x{87AA}\x{87AB}\x{87AC}\x{87AD}\x{87AE}' .
+'\x{87AF}\x{87B0}\x{87B1}\x{87B2}\x{87B3}\x{87B4}\x{87B5}\x{87B6}\x{87B7}' .
+'\x{87B8}\x{87B9}\x{87BA}\x{87BB}\x{87BC}\x{87BD}\x{87BE}\x{87BF}\x{87C0}' .
+'\x{87C1}\x{87C2}\x{87C3}\x{87C4}\x{87C5}\x{87C6}\x{87C7}\x{87C8}\x{87C9}' .
+'\x{87CA}\x{87CB}\x{87CC}\x{87CD}\x{87CE}\x{87CF}\x{87D0}\x{87D1}\x{87D2}' .
+'\x{87D3}\x{87D4}\x{87D5}\x{87D6}\x{87D7}\x{87D8}\x{87D9}\x{87DB}\x{87DC}' .
+'\x{87DD}\x{87DE}\x{87DF}\x{87E0}\x{87E1}\x{87E2}\x{87E3}\x{87E4}\x{87E5}' .
+'\x{87E6}\x{87E7}\x{87E8}\x{87E9}\x{87EA}\x{87EB}\x{87EC}\x{87ED}\x{87EE}' .
+'\x{87EF}\x{87F1}\x{87F2}\x{87F3}\x{87F4}\x{87F5}\x{87F6}\x{87F7}\x{87F8}' .
+'\x{87F9}\x{87FA}\x{87FB}\x{87FC}\x{87FD}\x{87FE}\x{87FF}\x{8800}\x{8801}' .
+'\x{8802}\x{8803}\x{8804}\x{8805}\x{8806}\x{8808}\x{8809}\x{880A}\x{880B}' .
+'\x{880C}\x{880D}\x{880E}\x{880F}\x{8810}\x{8811}\x{8813}\x{8814}\x{8815}' .
+'\x{8816}\x{8817}\x{8818}\x{8819}\x{881A}\x{881B}\x{881C}\x{881D}\x{881E}' .
+'\x{881F}\x{8820}\x{8821}\x{8822}\x{8823}\x{8824}\x{8825}\x{8826}\x{8827}' .
+'\x{8828}\x{8829}\x{882A}\x{882B}\x{882C}\x{882E}\x{882F}\x{8830}\x{8831}' .
+'\x{8832}\x{8833}\x{8834}\x{8835}\x{8836}\x{8837}\x{8838}\x{8839}\x{883B}' .
+'\x{883C}\x{883D}\x{883E}\x{883F}\x{8840}\x{8841}\x{8842}\x{8843}\x{8844}' .
+'\x{8845}\x{8846}\x{8848}\x{8849}\x{884A}\x{884B}\x{884C}\x{884D}\x{884E}' .
+'\x{884F}\x{8850}\x{8851}\x{8852}\x{8853}\x{8854}\x{8855}\x{8856}\x{8857}' .
+'\x{8859}\x{885A}\x{885B}\x{885D}\x{885E}\x{8860}\x{8861}\x{8862}\x{8863}' .
+'\x{8864}\x{8865}\x{8866}\x{8867}\x{8868}\x{8869}\x{886A}\x{886B}\x{886C}' .
+'\x{886D}\x{886E}\x{886F}\x{8870}\x{8871}\x{8872}\x{8873}\x{8874}\x{8875}' .
+'\x{8876}\x{8877}\x{8878}\x{8879}\x{887B}\x{887C}\x{887D}\x{887E}\x{887F}' .
+'\x{8880}\x{8881}\x{8882}\x{8883}\x{8884}\x{8885}\x{8886}\x{8887}\x{8888}' .
+'\x{8889}\x{888A}\x{888B}\x{888C}\x{888D}\x{888E}\x{888F}\x{8890}\x{8891}' .
+'\x{8892}\x{8893}\x{8894}\x{8895}\x{8896}\x{8897}\x{8898}\x{8899}\x{889A}' .
+'\x{889B}\x{889C}\x{889D}\x{889E}\x{889F}\x{88A0}\x{88A1}\x{88A2}\x{88A3}' .
+'\x{88A4}\x{88A5}\x{88A6}\x{88A7}\x{88A8}\x{88A9}\x{88AA}\x{88AB}\x{88AC}' .
+'\x{88AD}\x{88AE}\x{88AF}\x{88B0}\x{88B1}\x{88B2}\x{88B3}\x{88B4}\x{88B6}' .
+'\x{88B7}\x{88B8}\x{88B9}\x{88BA}\x{88BB}\x{88BC}\x{88BD}\x{88BE}\x{88BF}' .
+'\x{88C0}\x{88C1}\x{88C2}\x{88C3}\x{88C4}\x{88C5}\x{88C6}\x{88C7}\x{88C8}' .
+'\x{88C9}\x{88CA}\x{88CB}\x{88CC}\x{88CD}\x{88CE}\x{88CF}\x{88D0}\x{88D1}' .
+'\x{88D2}\x{88D3}\x{88D4}\x{88D5}\x{88D6}\x{88D7}\x{88D8}\x{88D9}\x{88DA}' .
+'\x{88DB}\x{88DC}\x{88DD}\x{88DE}\x{88DF}\x{88E0}\x{88E1}\x{88E2}\x{88E3}' .
+'\x{88E4}\x{88E5}\x{88E7}\x{88E8}\x{88EA}\x{88EB}\x{88EC}\x{88EE}\x{88EF}' .
+'\x{88F0}\x{88F1}\x{88F2}\x{88F3}\x{88F4}\x{88F5}\x{88F6}\x{88F7}\x{88F8}' .
+'\x{88F9}\x{88FA}\x{88FB}\x{88FC}\x{88FD}\x{88FE}\x{88FF}\x{8900}\x{8901}' .
+'\x{8902}\x{8904}\x{8905}\x{8906}\x{8907}\x{8908}\x{8909}\x{890A}\x{890B}' .
+'\x{890C}\x{890D}\x{890E}\x{8910}\x{8911}\x{8912}\x{8913}\x{8914}\x{8915}' .
+'\x{8916}\x{8917}\x{8918}\x{8919}\x{891A}\x{891B}\x{891C}\x{891D}\x{891E}' .
+'\x{891F}\x{8920}\x{8921}\x{8922}\x{8923}\x{8925}\x{8926}\x{8927}\x{8928}' .
+'\x{8929}\x{892A}\x{892B}\x{892C}\x{892D}\x{892E}\x{892F}\x{8930}\x{8931}' .
+'\x{8932}\x{8933}\x{8934}\x{8935}\x{8936}\x{8937}\x{8938}\x{8939}\x{893A}' .
+'\x{893B}\x{893C}\x{893D}\x{893E}\x{893F}\x{8940}\x{8941}\x{8942}\x{8943}' .
+'\x{8944}\x{8945}\x{8946}\x{8947}\x{8948}\x{8949}\x{894A}\x{894B}\x{894C}' .
+'\x{894E}\x{894F}\x{8950}\x{8951}\x{8952}\x{8953}\x{8954}\x{8955}\x{8956}' .
+'\x{8957}\x{8958}\x{8959}\x{895A}\x{895B}\x{895C}\x{895D}\x{895E}\x{895F}' .
+'\x{8960}\x{8961}\x{8962}\x{8963}\x{8964}\x{8966}\x{8967}\x{8968}\x{8969}' .
+'\x{896A}\x{896B}\x{896C}\x{896D}\x{896E}\x{896F}\x{8970}\x{8971}\x{8972}' .
+'\x{8973}\x{8974}\x{8976}\x{8977}\x{8978}\x{8979}\x{897A}\x{897B}\x{897C}' .
+'\x{897E}\x{897F}\x{8980}\x{8981}\x{8982}\x{8983}\x{8984}\x{8985}\x{8986}' .
+'\x{8987}\x{8988}\x{8989}\x{898A}\x{898B}\x{898C}\x{898E}\x{898F}\x{8991}' .
+'\x{8992}\x{8993}\x{8995}\x{8996}\x{8997}\x{8998}\x{899A}\x{899B}\x{899C}' .
+'\x{899D}\x{899E}\x{899F}\x{89A0}\x{89A1}\x{89A2}\x{89A3}\x{89A4}\x{89A5}' .
+'\x{89A6}\x{89A7}\x{89A8}\x{89AA}\x{89AB}\x{89AC}\x{89AD}\x{89AE}\x{89AF}' .
+'\x{89B1}\x{89B2}\x{89B3}\x{89B5}\x{89B6}\x{89B7}\x{89B8}\x{89B9}\x{89BA}' .
+'\x{89BD}\x{89BE}\x{89BF}\x{89C0}\x{89C1}\x{89C2}\x{89C3}\x{89C4}\x{89C5}' .
+'\x{89C6}\x{89C7}\x{89C8}\x{89C9}\x{89CA}\x{89CB}\x{89CC}\x{89CD}\x{89CE}' .
+'\x{89CF}\x{89D0}\x{89D1}\x{89D2}\x{89D3}\x{89D4}\x{89D5}\x{89D6}\x{89D7}' .
+'\x{89D8}\x{89D9}\x{89DA}\x{89DB}\x{89DC}\x{89DD}\x{89DE}\x{89DF}\x{89E0}' .
+'\x{89E1}\x{89E2}\x{89E3}\x{89E4}\x{89E5}\x{89E6}\x{89E7}\x{89E8}\x{89E9}' .
+'\x{89EA}\x{89EB}\x{89EC}\x{89ED}\x{89EF}\x{89F0}\x{89F1}\x{89F2}\x{89F3}' .
+'\x{89F4}\x{89F6}\x{89F7}\x{89F8}\x{89FA}\x{89FB}\x{89FC}\x{89FE}\x{89FF}' .
+'\x{8A00}\x{8A01}\x{8A02}\x{8A03}\x{8A04}\x{8A07}\x{8A08}\x{8A09}\x{8A0A}' .
+'\x{8A0B}\x{8A0C}\x{8A0D}\x{8A0E}\x{8A0F}\x{8A10}\x{8A11}\x{8A12}\x{8A13}' .
+'\x{8A15}\x{8A16}\x{8A17}\x{8A18}\x{8A1A}\x{8A1B}\x{8A1C}\x{8A1D}\x{8A1E}' .
+'\x{8A1F}\x{8A22}\x{8A23}\x{8A24}\x{8A25}\x{8A26}\x{8A27}\x{8A28}\x{8A29}' .
+'\x{8A2A}\x{8A2C}\x{8A2D}\x{8A2E}\x{8A2F}\x{8A30}\x{8A31}\x{8A32}\x{8A34}' .
+'\x{8A35}\x{8A36}\x{8A37}\x{8A38}\x{8A39}\x{8A3A}\x{8A3B}\x{8A3C}\x{8A3E}' .
+'\x{8A3F}\x{8A40}\x{8A41}\x{8A42}\x{8A43}\x{8A44}\x{8A45}\x{8A46}\x{8A47}' .
+'\x{8A48}\x{8A49}\x{8A4A}\x{8A4C}\x{8A4D}\x{8A4E}\x{8A4F}\x{8A50}\x{8A51}' .
+'\x{8A52}\x{8A53}\x{8A54}\x{8A55}\x{8A56}\x{8A57}\x{8A58}\x{8A59}\x{8A5A}' .
+'\x{8A5B}\x{8A5C}\x{8A5D}\x{8A5E}\x{8A5F}\x{8A60}\x{8A61}\x{8A62}\x{8A63}' .
+'\x{8A65}\x{8A66}\x{8A67}\x{8A68}\x{8A69}\x{8A6A}\x{8A6B}\x{8A6C}\x{8A6D}' .
+'\x{8A6E}\x{8A6F}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A74}\x{8A75}\x{8A76}' .
+'\x{8A77}\x{8A79}\x{8A7A}\x{8A7B}\x{8A7C}\x{8A7E}\x{8A7F}\x{8A80}\x{8A81}' .
+'\x{8A82}\x{8A83}\x{8A84}\x{8A85}\x{8A86}\x{8A87}\x{8A89}\x{8A8A}\x{8A8B}' .
+'\x{8A8C}\x{8A8D}\x{8A8E}\x{8A8F}\x{8A90}\x{8A91}\x{8A92}\x{8A93}\x{8A94}' .
+'\x{8A95}\x{8A96}\x{8A97}\x{8A98}\x{8A99}\x{8A9A}\x{8A9B}\x{8A9C}\x{8A9D}' .
+'\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA2}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA7}' .
+'\x{8AA8}\x{8AA9}\x{8AAA}\x{8AAB}\x{8AAC}\x{8AAE}\x{8AB0}\x{8AB1}\x{8AB2}' .
+'\x{8AB3}\x{8AB4}\x{8AB5}\x{8AB6}\x{8AB8}\x{8AB9}\x{8ABA}\x{8ABB}\x{8ABC}' .
+'\x{8ABD}\x{8ABE}\x{8ABF}\x{8AC0}\x{8AC1}\x{8AC2}\x{8AC3}\x{8AC4}\x{8AC5}' .
+'\x{8AC6}\x{8AC7}\x{8AC8}\x{8AC9}\x{8ACA}\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACE}' .
+'\x{8ACF}\x{8AD1}\x{8AD2}\x{8AD3}\x{8AD4}\x{8AD5}\x{8AD6}\x{8AD7}\x{8AD8}' .
+'\x{8AD9}\x{8ADA}\x{8ADB}\x{8ADC}\x{8ADD}\x{8ADE}\x{8ADF}\x{8AE0}\x{8AE1}' .
+'\x{8AE2}\x{8AE3}\x{8AE4}\x{8AE5}\x{8AE6}\x{8AE7}\x{8AE8}\x{8AE9}\x{8AEA}' .
+'\x{8AEB}\x{8AED}\x{8AEE}\x{8AEF}\x{8AF0}\x{8AF1}\x{8AF2}\x{8AF3}\x{8AF4}' .
+'\x{8AF5}\x{8AF6}\x{8AF7}\x{8AF8}\x{8AF9}\x{8AFA}\x{8AFB}\x{8AFC}\x{8AFD}' .
+'\x{8AFE}\x{8AFF}\x{8B00}\x{8B01}\x{8B02}\x{8B03}\x{8B04}\x{8B05}\x{8B06}' .
+'\x{8B07}\x{8B08}\x{8B09}\x{8B0A}\x{8B0B}\x{8B0D}\x{8B0E}\x{8B0F}\x{8B10}' .
+'\x{8B11}\x{8B12}\x{8B13}\x{8B14}\x{8B15}\x{8B16}\x{8B17}\x{8B18}\x{8B19}' .
+'\x{8B1A}\x{8B1B}\x{8B1C}\x{8B1D}\x{8B1E}\x{8B1F}\x{8B20}\x{8B21}\x{8B22}' .
+'\x{8B23}\x{8B24}\x{8B25}\x{8B26}\x{8B27}\x{8B28}\x{8B2A}\x{8B2B}\x{8B2C}' .
+'\x{8B2D}\x{8B2E}\x{8B2F}\x{8B30}\x{8B31}\x{8B33}\x{8B34}\x{8B35}\x{8B36}' .
+'\x{8B37}\x{8B39}\x{8B3A}\x{8B3B}\x{8B3C}\x{8B3D}\x{8B3E}\x{8B40}\x{8B41}' .
+'\x{8B42}\x{8B43}\x{8B44}\x{8B45}\x{8B46}\x{8B47}\x{8B48}\x{8B49}\x{8B4A}' .
+'\x{8B4B}\x{8B4C}\x{8B4D}\x{8B4E}\x{8B4F}\x{8B50}\x{8B51}\x{8B52}\x{8B53}' .
+'\x{8B54}\x{8B55}\x{8B56}\x{8B57}\x{8B58}\x{8B59}\x{8B5A}\x{8B5B}\x{8B5C}' .
+'\x{8B5D}\x{8B5E}\x{8B5F}\x{8B60}\x{8B63}\x{8B64}\x{8B65}\x{8B66}\x{8B67}' .
+'\x{8B68}\x{8B6A}\x{8B6B}\x{8B6C}\x{8B6D}\x{8B6E}\x{8B6F}\x{8B70}\x{8B71}' .
+'\x{8B73}\x{8B74}\x{8B76}\x{8B77}\x{8B78}\x{8B79}\x{8B7A}\x{8B7B}\x{8B7D}' .
+'\x{8B7E}\x{8B7F}\x{8B80}\x{8B82}\x{8B83}\x{8B84}\x{8B85}\x{8B86}\x{8B88}' .
+'\x{8B89}\x{8B8A}\x{8B8B}\x{8B8C}\x{8B8E}\x{8B90}\x{8B91}\x{8B92}\x{8B93}' .
+'\x{8B94}\x{8B95}\x{8B96}\x{8B97}\x{8B98}\x{8B99}\x{8B9A}\x{8B9C}\x{8B9D}' .
+'\x{8B9E}\x{8B9F}\x{8BA0}\x{8BA1}\x{8BA2}\x{8BA3}\x{8BA4}\x{8BA5}\x{8BA6}' .
+'\x{8BA7}\x{8BA8}\x{8BA9}\x{8BAA}\x{8BAB}\x{8BAC}\x{8BAD}\x{8BAE}\x{8BAF}' .
+'\x{8BB0}\x{8BB1}\x{8BB2}\x{8BB3}\x{8BB4}\x{8BB5}\x{8BB6}\x{8BB7}\x{8BB8}' .
+'\x{8BB9}\x{8BBA}\x{8BBB}\x{8BBC}\x{8BBD}\x{8BBE}\x{8BBF}\x{8BC0}\x{8BC1}' .
+'\x{8BC2}\x{8BC3}\x{8BC4}\x{8BC5}\x{8BC6}\x{8BC7}\x{8BC8}\x{8BC9}\x{8BCA}' .
+'\x{8BCB}\x{8BCC}\x{8BCD}\x{8BCE}\x{8BCF}\x{8BD0}\x{8BD1}\x{8BD2}\x{8BD3}' .
+'\x{8BD4}\x{8BD5}\x{8BD6}\x{8BD7}\x{8BD8}\x{8BD9}\x{8BDA}\x{8BDB}\x{8BDC}' .
+'\x{8BDD}\x{8BDE}\x{8BDF}\x{8BE0}\x{8BE1}\x{8BE2}\x{8BE3}\x{8BE4}\x{8BE5}' .
+'\x{8BE6}\x{8BE7}\x{8BE8}\x{8BE9}\x{8BEA}\x{8BEB}\x{8BEC}\x{8BED}\x{8BEE}' .
+'\x{8BEF}\x{8BF0}\x{8BF1}\x{8BF2}\x{8BF3}\x{8BF4}\x{8BF5}\x{8BF6}\x{8BF7}' .
+'\x{8BF8}\x{8BF9}\x{8BFA}\x{8BFB}\x{8BFC}\x{8BFD}\x{8BFE}\x{8BFF}\x{8C00}' .
+'\x{8C01}\x{8C02}\x{8C03}\x{8C04}\x{8C05}\x{8C06}\x{8C07}\x{8C08}\x{8C09}' .
+'\x{8C0A}\x{8C0B}\x{8C0C}\x{8C0D}\x{8C0E}\x{8C0F}\x{8C10}\x{8C11}\x{8C12}' .
+'\x{8C13}\x{8C14}\x{8C15}\x{8C16}\x{8C17}\x{8C18}\x{8C19}\x{8C1A}\x{8C1B}' .
+'\x{8C1C}\x{8C1D}\x{8C1E}\x{8C1F}\x{8C20}\x{8C21}\x{8C22}\x{8C23}\x{8C24}' .
+'\x{8C25}\x{8C26}\x{8C27}\x{8C28}\x{8C29}\x{8C2A}\x{8C2B}\x{8C2C}\x{8C2D}' .
+'\x{8C2E}\x{8C2F}\x{8C30}\x{8C31}\x{8C32}\x{8C33}\x{8C34}\x{8C35}\x{8C36}' .
+'\x{8C37}\x{8C39}\x{8C3A}\x{8C3B}\x{8C3C}\x{8C3D}\x{8C3E}\x{8C3F}\x{8C41}' .
+'\x{8C42}\x{8C43}\x{8C45}\x{8C46}\x{8C47}\x{8C48}\x{8C49}\x{8C4A}\x{8C4B}' .
+'\x{8C4C}\x{8C4D}\x{8C4E}\x{8C4F}\x{8C50}\x{8C54}\x{8C55}\x{8C56}\x{8C57}' .
+'\x{8C59}\x{8C5A}\x{8C5B}\x{8C5C}\x{8C5D}\x{8C5E}\x{8C5F}\x{8C60}\x{8C61}' .
+'\x{8C62}\x{8C63}\x{8C64}\x{8C65}\x{8C66}\x{8C67}\x{8C68}\x{8C69}\x{8C6A}' .
+'\x{8C6B}\x{8C6C}\x{8C6D}\x{8C6E}\x{8C6F}\x{8C70}\x{8C71}\x{8C72}\x{8C73}' .
+'\x{8C75}\x{8C76}\x{8C77}\x{8C78}\x{8C79}\x{8C7A}\x{8C7B}\x{8C7D}\x{8C7E}' .
+'\x{8C80}\x{8C81}\x{8C82}\x{8C84}\x{8C85}\x{8C86}\x{8C88}\x{8C89}\x{8C8A}' .
+'\x{8C8C}\x{8C8D}\x{8C8F}\x{8C90}\x{8C91}\x{8C92}\x{8C93}\x{8C94}\x{8C95}' .
+'\x{8C96}\x{8C97}\x{8C98}\x{8C99}\x{8C9A}\x{8C9C}\x{8C9D}\x{8C9E}\x{8C9F}' .
+'\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA3}\x{8CA4}\x{8CA5}\x{8CA7}\x{8CA8}\x{8CA9}' .
+'\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}\x{8CB1}\x{8CB2}' .
+'\x{8CB3}\x{8CB4}\x{8CB5}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CB9}\x{8CBA}\x{8CBB}' .
+'\x{8CBC}\x{8CBD}\x{8CBE}\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}' .
+'\x{8CC5}\x{8CC6}\x{8CC7}\x{8CC8}\x{8CC9}\x{8CCA}\x{8CCC}\x{8CCE}\x{8CCF}' .
+'\x{8CD0}\x{8CD1}\x{8CD2}\x{8CD3}\x{8CD4}\x{8CD5}\x{8CD7}\x{8CD9}\x{8CDA}' .
+'\x{8CDB}\x{8CDC}\x{8CDD}\x{8CDE}\x{8CDF}\x{8CE0}\x{8CE1}\x{8CE2}\x{8CE3}' .
+'\x{8CE4}\x{8CE5}\x{8CE6}\x{8CE7}\x{8CE8}\x{8CEA}\x{8CEB}\x{8CEC}\x{8CED}' .
+'\x{8CEE}\x{8CEF}\x{8CF0}\x{8CF1}\x{8CF2}\x{8CF3}\x{8CF4}\x{8CF5}\x{8CF6}' .
+'\x{8CF8}\x{8CF9}\x{8CFA}\x{8CFB}\x{8CFC}\x{8CFD}\x{8CFE}\x{8CFF}\x{8D00}' .
+'\x{8D02}\x{8D03}\x{8D04}\x{8D05}\x{8D06}\x{8D07}\x{8D08}\x{8D09}\x{8D0A}' .
+'\x{8D0B}\x{8D0C}\x{8D0D}\x{8D0E}\x{8D0F}\x{8D10}\x{8D13}\x{8D14}\x{8D15}' .
+'\x{8D16}\x{8D17}\x{8D18}\x{8D19}\x{8D1A}\x{8D1B}\x{8D1C}\x{8D1D}\x{8D1E}' .
+'\x{8D1F}\x{8D20}\x{8D21}\x{8D22}\x{8D23}\x{8D24}\x{8D25}\x{8D26}\x{8D27}' .
+'\x{8D28}\x{8D29}\x{8D2A}\x{8D2B}\x{8D2C}\x{8D2D}\x{8D2E}\x{8D2F}\x{8D30}' .
+'\x{8D31}\x{8D32}\x{8D33}\x{8D34}\x{8D35}\x{8D36}\x{8D37}\x{8D38}\x{8D39}' .
+'\x{8D3A}\x{8D3B}\x{8D3C}\x{8D3D}\x{8D3E}\x{8D3F}\x{8D40}\x{8D41}\x{8D42}' .
+'\x{8D43}\x{8D44}\x{8D45}\x{8D46}\x{8D47}\x{8D48}\x{8D49}\x{8D4A}\x{8D4B}' .
+'\x{8D4C}\x{8D4D}\x{8D4E}\x{8D4F}\x{8D50}\x{8D51}\x{8D52}\x{8D53}\x{8D54}' .
+'\x{8D55}\x{8D56}\x{8D57}\x{8D58}\x{8D59}\x{8D5A}\x{8D5B}\x{8D5C}\x{8D5D}' .
+'\x{8D5E}\x{8D5F}\x{8D60}\x{8D61}\x{8D62}\x{8D63}\x{8D64}\x{8D65}\x{8D66}' .
+'\x{8D67}\x{8D68}\x{8D69}\x{8D6A}\x{8D6B}\x{8D6C}\x{8D6D}\x{8D6E}\x{8D6F}' .
+'\x{8D70}\x{8D71}\x{8D72}\x{8D73}\x{8D74}\x{8D75}\x{8D76}\x{8D77}\x{8D78}' .
+'\x{8D79}\x{8D7A}\x{8D7B}\x{8D7D}\x{8D7E}\x{8D7F}\x{8D80}\x{8D81}\x{8D82}' .
+'\x{8D83}\x{8D84}\x{8D85}\x{8D86}\x{8D87}\x{8D88}\x{8D89}\x{8D8A}\x{8D8B}' .
+'\x{8D8C}\x{8D8D}\x{8D8E}\x{8D8F}\x{8D90}\x{8D91}\x{8D92}\x{8D93}\x{8D94}' .
+'\x{8D95}\x{8D96}\x{8D97}\x{8D98}\x{8D99}\x{8D9A}\x{8D9B}\x{8D9C}\x{8D9D}' .
+'\x{8D9E}\x{8D9F}\x{8DA0}\x{8DA1}\x{8DA2}\x{8DA3}\x{8DA4}\x{8DA5}\x{8DA7}' .
+'\x{8DA8}\x{8DA9}\x{8DAA}\x{8DAB}\x{8DAC}\x{8DAD}\x{8DAE}\x{8DAF}\x{8DB0}' .
+'\x{8DB1}\x{8DB2}\x{8DB3}\x{8DB4}\x{8DB5}\x{8DB6}\x{8DB7}\x{8DB8}\x{8DB9}' .
+'\x{8DBA}\x{8DBB}\x{8DBC}\x{8DBD}\x{8DBE}\x{8DBF}\x{8DC1}\x{8DC2}\x{8DC3}' .
+'\x{8DC4}\x{8DC5}\x{8DC6}\x{8DC7}\x{8DC8}\x{8DC9}\x{8DCA}\x{8DCB}\x{8DCC}' .
+'\x{8DCD}\x{8DCE}\x{8DCF}\x{8DD0}\x{8DD1}\x{8DD2}\x{8DD3}\x{8DD4}\x{8DD5}' .
+'\x{8DD6}\x{8DD7}\x{8DD8}\x{8DD9}\x{8DDA}\x{8DDB}\x{8DDC}\x{8DDD}\x{8DDE}' .
+'\x{8DDF}\x{8DE0}\x{8DE1}\x{8DE2}\x{8DE3}\x{8DE4}\x{8DE6}\x{8DE7}\x{8DE8}' .
+'\x{8DE9}\x{8DEA}\x{8DEB}\x{8DEC}\x{8DED}\x{8DEE}\x{8DEF}\x{8DF0}\x{8DF1}' .
+'\x{8DF2}\x{8DF3}\x{8DF4}\x{8DF5}\x{8DF6}\x{8DF7}\x{8DF8}\x{8DF9}\x{8DFA}' .
+'\x{8DFB}\x{8DFC}\x{8DFD}\x{8DFE}\x{8DFF}\x{8E00}\x{8E02}\x{8E03}\x{8E04}' .
+'\x{8E05}\x{8E06}\x{8E07}\x{8E08}\x{8E09}\x{8E0A}\x{8E0C}\x{8E0D}\x{8E0E}' .
+'\x{8E0F}\x{8E10}\x{8E11}\x{8E12}\x{8E13}\x{8E14}\x{8E15}\x{8E16}\x{8E17}' .
+'\x{8E18}\x{8E19}\x{8E1A}\x{8E1B}\x{8E1C}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E20}' .
+'\x{8E21}\x{8E22}\x{8E23}\x{8E24}\x{8E25}\x{8E26}\x{8E27}\x{8E28}\x{8E29}' .
+'\x{8E2A}\x{8E2B}\x{8E2C}\x{8E2D}\x{8E2E}\x{8E2F}\x{8E30}\x{8E31}\x{8E33}' .
+'\x{8E34}\x{8E35}\x{8E36}\x{8E37}\x{8E38}\x{8E39}\x{8E3A}\x{8E3B}\x{8E3C}' .
+'\x{8E3D}\x{8E3E}\x{8E3F}\x{8E40}\x{8E41}\x{8E42}\x{8E43}\x{8E44}\x{8E45}' .
+'\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4B}\x{8E4C}\x{8E4D}\x{8E4E}\x{8E50}' .
+'\x{8E51}\x{8E52}\x{8E53}\x{8E54}\x{8E55}\x{8E56}\x{8E57}\x{8E58}\x{8E59}' .
+'\x{8E5A}\x{8E5B}\x{8E5C}\x{8E5D}\x{8E5E}\x{8E5F}\x{8E60}\x{8E61}\x{8E62}' .
+'\x{8E63}\x{8E64}\x{8E65}\x{8E66}\x{8E67}\x{8E68}\x{8E69}\x{8E6A}\x{8E6B}' .
+'\x{8E6C}\x{8E6D}\x{8E6F}\x{8E70}\x{8E71}\x{8E72}\x{8E73}\x{8E74}\x{8E76}' .
+'\x{8E78}\x{8E7A}\x{8E7B}\x{8E7C}\x{8E7D}\x{8E7E}\x{8E7F}\x{8E80}\x{8E81}' .
+'\x{8E82}\x{8E83}\x{8E84}\x{8E85}\x{8E86}\x{8E87}\x{8E88}\x{8E89}\x{8E8A}' .
+'\x{8E8B}\x{8E8C}\x{8E8D}\x{8E8E}\x{8E8F}\x{8E90}\x{8E91}\x{8E92}\x{8E93}' .
+'\x{8E94}\x{8E95}\x{8E96}\x{8E97}\x{8E98}\x{8E9A}\x{8E9C}\x{8E9D}\x{8E9E}' .
+'\x{8E9F}\x{8EA0}\x{8EA1}\x{8EA3}\x{8EA4}\x{8EA5}\x{8EA6}\x{8EA7}\x{8EA8}' .
+'\x{8EA9}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAD}\x{8EAE}\x{8EAF}\x{8EB0}\x{8EB1}' .
+'\x{8EB2}\x{8EB4}\x{8EB5}\x{8EB8}\x{8EB9}\x{8EBA}\x{8EBB}\x{8EBC}\x{8EBD}' .
+'\x{8EBE}\x{8EBF}\x{8EC0}\x{8EC2}\x{8EC3}\x{8EC5}\x{8EC6}\x{8EC7}\x{8EC8}' .
+'\x{8EC9}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ECE}\x{8ECF}\x{8ED0}\x{8ED1}' .
+'\x{8ED2}\x{8ED3}\x{8ED4}\x{8ED5}\x{8ED6}\x{8ED7}\x{8ED8}\x{8EDA}\x{8EDB}' .
+'\x{8EDC}\x{8EDD}\x{8EDE}\x{8EDF}\x{8EE0}\x{8EE1}\x{8EE4}\x{8EE5}\x{8EE6}' .
+'\x{8EE7}\x{8EE8}\x{8EE9}\x{8EEA}\x{8EEB}\x{8EEC}\x{8EED}\x{8EEE}\x{8EEF}' .
+'\x{8EF1}\x{8EF2}\x{8EF3}\x{8EF4}\x{8EF5}\x{8EF6}\x{8EF7}\x{8EF8}\x{8EF9}' .
+'\x{8EFA}\x{8EFB}\x{8EFC}\x{8EFD}\x{8EFE}\x{8EFF}\x{8F00}\x{8F01}\x{8F02}' .
+'\x{8F03}\x{8F04}\x{8F05}\x{8F06}\x{8F07}\x{8F08}\x{8F09}\x{8F0A}\x{8F0B}' .
+'\x{8F0D}\x{8F0E}\x{8F10}\x{8F11}\x{8F12}\x{8F13}\x{8F14}\x{8F15}\x{8F16}' .
+'\x{8F17}\x{8F18}\x{8F1A}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1E}\x{8F1F}\x{8F20}' .
+'\x{8F21}\x{8F22}\x{8F23}\x{8F24}\x{8F25}\x{8F26}\x{8F27}\x{8F28}\x{8F29}' .
+'\x{8F2A}\x{8F2B}\x{8F2C}\x{8F2E}\x{8F2F}\x{8F30}\x{8F31}\x{8F32}\x{8F33}' .
+'\x{8F34}\x{8F35}\x{8F36}\x{8F37}\x{8F38}\x{8F39}\x{8F3B}\x{8F3C}\x{8F3D}' .
+'\x{8F3E}\x{8F3F}\x{8F40}\x{8F42}\x{8F43}\x{8F44}\x{8F45}\x{8F46}\x{8F47}' .
+'\x{8F48}\x{8F49}\x{8F4A}\x{8F4B}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F4F}\x{8F50}' .
+'\x{8F51}\x{8F52}\x{8F53}\x{8F54}\x{8F55}\x{8F56}\x{8F57}\x{8F58}\x{8F59}' .
+'\x{8F5A}\x{8F5B}\x{8F5D}\x{8F5E}\x{8F5F}\x{8F60}\x{8F61}\x{8F62}\x{8F63}' .
+'\x{8F64}\x{8F65}\x{8F66}\x{8F67}\x{8F68}\x{8F69}\x{8F6A}\x{8F6B}\x{8F6C}' .
+'\x{8F6D}\x{8F6E}\x{8F6F}\x{8F70}\x{8F71}\x{8F72}\x{8F73}\x{8F74}\x{8F75}' .
+'\x{8F76}\x{8F77}\x{8F78}\x{8F79}\x{8F7A}\x{8F7B}\x{8F7C}\x{8F7D}\x{8F7E}' .
+'\x{8F7F}\x{8F80}\x{8F81}\x{8F82}\x{8F83}\x{8F84}\x{8F85}\x{8F86}\x{8F87}' .
+'\x{8F88}\x{8F89}\x{8F8A}\x{8F8B}\x{8F8C}\x{8F8D}\x{8F8E}\x{8F8F}\x{8F90}' .
+'\x{8F91}\x{8F92}\x{8F93}\x{8F94}\x{8F95}\x{8F96}\x{8F97}\x{8F98}\x{8F99}' .
+'\x{8F9A}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA0}\x{8FA1}\x{8FA2}\x{8FA3}' .
+'\x{8FA5}\x{8FA6}\x{8FA7}\x{8FA8}\x{8FA9}\x{8FAA}\x{8FAB}\x{8FAC}\x{8FAD}' .
+'\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB4}\x{8FB5}\x{8FB6}\x{8FB7}' .
+'\x{8FB8}\x{8FB9}\x{8FBB}\x{8FBC}\x{8FBD}\x{8FBE}\x{8FBF}\x{8FC0}\x{8FC1}' .
+'\x{8FC2}\x{8FC4}\x{8FC5}\x{8FC6}\x{8FC7}\x{8FC8}\x{8FC9}\x{8FCB}\x{8FCC}' .
+'\x{8FCD}\x{8FCE}\x{8FCF}\x{8FD0}\x{8FD1}\x{8FD2}\x{8FD3}\x{8FD4}\x{8FD5}' .
+'\x{8FD6}\x{8FD7}\x{8FD8}\x{8FD9}\x{8FDA}\x{8FDB}\x{8FDC}\x{8FDD}\x{8FDE}' .
+'\x{8FDF}\x{8FE0}\x{8FE1}\x{8FE2}\x{8FE3}\x{8FE4}\x{8FE5}\x{8FE6}\x{8FE8}' .
+'\x{8FE9}\x{8FEA}\x{8FEB}\x{8FEC}\x{8FED}\x{8FEE}\x{8FEF}\x{8FF0}\x{8FF1}' .
+'\x{8FF2}\x{8FF3}\x{8FF4}\x{8FF5}\x{8FF6}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}' .
+'\x{8FFB}\x{8FFC}\x{8FFD}\x{8FFE}\x{8FFF}\x{9000}\x{9001}\x{9002}\x{9003}' .
+'\x{9004}\x{9005}\x{9006}\x{9007}\x{9008}\x{9009}\x{900A}\x{900B}\x{900C}' .
+'\x{900D}\x{900F}\x{9010}\x{9011}\x{9012}\x{9013}\x{9014}\x{9015}\x{9016}' .
+'\x{9017}\x{9018}\x{9019}\x{901A}\x{901B}\x{901C}\x{901D}\x{901E}\x{901F}' .
+'\x{9020}\x{9021}\x{9022}\x{9023}\x{9024}\x{9025}\x{9026}\x{9027}\x{9028}' .
+'\x{9029}\x{902B}\x{902D}\x{902E}\x{902F}\x{9030}\x{9031}\x{9032}\x{9033}' .
+'\x{9034}\x{9035}\x{9036}\x{9038}\x{903A}\x{903B}\x{903C}\x{903D}\x{903E}' .
+'\x{903F}\x{9041}\x{9042}\x{9043}\x{9044}\x{9045}\x{9047}\x{9048}\x{9049}' .
+'\x{904A}\x{904B}\x{904C}\x{904D}\x{904E}\x{904F}\x{9050}\x{9051}\x{9052}' .
+'\x{9053}\x{9054}\x{9055}\x{9056}\x{9057}\x{9058}\x{9059}\x{905A}\x{905B}' .
+'\x{905C}\x{905D}\x{905E}\x{905F}\x{9060}\x{9061}\x{9062}\x{9063}\x{9064}' .
+'\x{9065}\x{9066}\x{9067}\x{9068}\x{9069}\x{906A}\x{906B}\x{906C}\x{906D}' .
+'\x{906E}\x{906F}\x{9070}\x{9071}\x{9072}\x{9073}\x{9074}\x{9075}\x{9076}' .
+'\x{9077}\x{9078}\x{9079}\x{907A}\x{907B}\x{907C}\x{907D}\x{907E}\x{907F}' .
+'\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9085}\x{9086}\x{9087}\x{9088}' .
+'\x{9089}\x{908A}\x{908B}\x{908C}\x{908D}\x{908E}\x{908F}\x{9090}\x{9091}' .
+'\x{9092}\x{9093}\x{9094}\x{9095}\x{9096}\x{9097}\x{9098}\x{9099}\x{909A}' .
+'\x{909B}\x{909C}\x{909D}\x{909E}\x{909F}\x{90A0}\x{90A1}\x{90A2}\x{90A3}' .
+'\x{90A4}\x{90A5}\x{90A6}\x{90A7}\x{90A8}\x{90A9}\x{90AA}\x{90AC}\x{90AD}' .
+'\x{90AE}\x{90AF}\x{90B0}\x{90B1}\x{90B2}\x{90B3}\x{90B4}\x{90B5}\x{90B6}' .
+'\x{90B7}\x{90B8}\x{90B9}\x{90BA}\x{90BB}\x{90BC}\x{90BD}\x{90BE}\x{90BF}' .
+'\x{90C0}\x{90C1}\x{90C2}\x{90C3}\x{90C4}\x{90C5}\x{90C6}\x{90C7}\x{90C8}' .
+'\x{90C9}\x{90CA}\x{90CB}\x{90CE}\x{90CF}\x{90D0}\x{90D1}\x{90D3}\x{90D4}' .
+'\x{90D5}\x{90D6}\x{90D7}\x{90D8}\x{90D9}\x{90DA}\x{90DB}\x{90DC}\x{90DD}' .
+'\x{90DE}\x{90DF}\x{90E0}\x{90E1}\x{90E2}\x{90E3}\x{90E4}\x{90E5}\x{90E6}' .
+'\x{90E7}\x{90E8}\x{90E9}\x{90EA}\x{90EB}\x{90EC}\x{90ED}\x{90EE}\x{90EF}' .
+'\x{90F0}\x{90F1}\x{90F2}\x{90F3}\x{90F4}\x{90F5}\x{90F7}\x{90F8}\x{90F9}' .
+'\x{90FA}\x{90FB}\x{90FC}\x{90FD}\x{90FE}\x{90FF}\x{9100}\x{9101}\x{9102}' .
+'\x{9103}\x{9104}\x{9105}\x{9106}\x{9107}\x{9108}\x{9109}\x{910B}\x{910C}' .
+'\x{910D}\x{910E}\x{910F}\x{9110}\x{9111}\x{9112}\x{9113}\x{9114}\x{9115}' .
+'\x{9116}\x{9117}\x{9118}\x{9119}\x{911A}\x{911B}\x{911C}\x{911D}\x{911E}' .
+'\x{911F}\x{9120}\x{9121}\x{9122}\x{9123}\x{9124}\x{9125}\x{9126}\x{9127}' .
+'\x{9128}\x{9129}\x{912A}\x{912B}\x{912C}\x{912D}\x{912E}\x{912F}\x{9130}' .
+'\x{9131}\x{9132}\x{9133}\x{9134}\x{9135}\x{9136}\x{9137}\x{9138}\x{9139}' .
+'\x{913A}\x{913B}\x{913E}\x{913F}\x{9140}\x{9141}\x{9142}\x{9143}\x{9144}' .
+'\x{9145}\x{9146}\x{9147}\x{9148}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}' .
+'\x{914E}\x{914F}\x{9150}\x{9151}\x{9152}\x{9153}\x{9154}\x{9155}\x{9156}' .
+'\x{9157}\x{9158}\x{915A}\x{915B}\x{915C}\x{915D}\x{915E}\x{915F}\x{9160}' .
+'\x{9161}\x{9162}\x{9163}\x{9164}\x{9165}\x{9166}\x{9167}\x{9168}\x{9169}' .
+'\x{916A}\x{916B}\x{916C}\x{916D}\x{916E}\x{916F}\x{9170}\x{9171}\x{9172}' .
+'\x{9173}\x{9174}\x{9175}\x{9176}\x{9177}\x{9178}\x{9179}\x{917A}\x{917C}' .
+'\x{917D}\x{917E}\x{917F}\x{9180}\x{9181}\x{9182}\x{9183}\x{9184}\x{9185}' .
+'\x{9186}\x{9187}\x{9188}\x{9189}\x{918A}\x{918B}\x{918C}\x{918D}\x{918E}' .
+'\x{918F}\x{9190}\x{9191}\x{9192}\x{9193}\x{9194}\x{9196}\x{9199}\x{919A}' .
+'\x{919B}\x{919C}\x{919D}\x{919E}\x{919F}\x{91A0}\x{91A1}\x{91A2}\x{91A3}' .
+'\x{91A5}\x{91A6}\x{91A7}\x{91A8}\x{91AA}\x{91AB}\x{91AC}\x{91AD}\x{91AE}' .
+'\x{91AF}\x{91B0}\x{91B1}\x{91B2}\x{91B3}\x{91B4}\x{91B5}\x{91B6}\x{91B7}' .
+'\x{91B9}\x{91BA}\x{91BB}\x{91BC}\x{91BD}\x{91BE}\x{91C0}\x{91C1}\x{91C2}' .
+'\x{91C3}\x{91C5}\x{91C6}\x{91C7}\x{91C9}\x{91CA}\x{91CB}\x{91CC}\x{91CD}' .
+'\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D2}\x{91D3}\x{91D4}\x{91D5}\x{91D7}' .
+'\x{91D8}\x{91D9}\x{91DA}\x{91DB}\x{91DC}\x{91DD}\x{91DE}\x{91DF}\x{91E2}' .
+'\x{91E3}\x{91E4}\x{91E5}\x{91E6}\x{91E7}\x{91E8}\x{91E9}\x{91EA}\x{91EB}' .
+'\x{91EC}\x{91ED}\x{91EE}\x{91F0}\x{91F1}\x{91F2}\x{91F3}\x{91F4}\x{91F5}' .
+'\x{91F7}\x{91F8}\x{91F9}\x{91FA}\x{91FB}\x{91FD}\x{91FE}\x{91FF}\x{9200}' .
+'\x{9201}\x{9202}\x{9203}\x{9204}\x{9205}\x{9206}\x{9207}\x{9208}\x{9209}' .
+'\x{920A}\x{920B}\x{920C}\x{920D}\x{920E}\x{920F}\x{9210}\x{9211}\x{9212}' .
+'\x{9214}\x{9215}\x{9216}\x{9217}\x{9218}\x{9219}\x{921A}\x{921B}\x{921C}' .
+'\x{921D}\x{921E}\x{9220}\x{9221}\x{9223}\x{9224}\x{9225}\x{9226}\x{9227}' .
+'\x{9228}\x{9229}\x{922A}\x{922B}\x{922D}\x{922E}\x{922F}\x{9230}\x{9231}' .
+'\x{9232}\x{9233}\x{9234}\x{9235}\x{9236}\x{9237}\x{9238}\x{9239}\x{923A}' .
+'\x{923B}\x{923C}\x{923D}\x{923E}\x{923F}\x{9240}\x{9241}\x{9242}\x{9245}' .
+'\x{9246}\x{9247}\x{9248}\x{9249}\x{924A}\x{924B}\x{924C}\x{924D}\x{924E}' .
+'\x{924F}\x{9250}\x{9251}\x{9252}\x{9253}\x{9254}\x{9255}\x{9256}\x{9257}' .
+'\x{9258}\x{9259}\x{925A}\x{925B}\x{925C}\x{925D}\x{925E}\x{925F}\x{9260}' .
+'\x{9261}\x{9262}\x{9263}\x{9264}\x{9265}\x{9266}\x{9267}\x{9268}\x{926B}' .
+'\x{926C}\x{926D}\x{926E}\x{926F}\x{9270}\x{9272}\x{9273}\x{9274}\x{9275}' .
+'\x{9276}\x{9277}\x{9278}\x{9279}\x{927A}\x{927B}\x{927C}\x{927D}\x{927E}' .
+'\x{927F}\x{9280}\x{9282}\x{9283}\x{9285}\x{9286}\x{9287}\x{9288}\x{9289}' .
+'\x{928A}\x{928B}\x{928C}\x{928D}\x{928E}\x{928F}\x{9290}\x{9291}\x{9292}' .
+'\x{9293}\x{9294}\x{9295}\x{9296}\x{9297}\x{9298}\x{9299}\x{929A}\x{929B}' .
+'\x{929C}\x{929D}\x{929F}\x{92A0}\x{92A1}\x{92A2}\x{92A3}\x{92A4}\x{92A5}' .
+'\x{92A6}\x{92A7}\x{92A8}\x{92A9}\x{92AA}\x{92AB}\x{92AC}\x{92AD}\x{92AE}' .
+'\x{92AF}\x{92B0}\x{92B1}\x{92B2}\x{92B3}\x{92B4}\x{92B5}\x{92B6}\x{92B7}' .
+'\x{92B8}\x{92B9}\x{92BA}\x{92BB}\x{92BC}\x{92BE}\x{92BF}\x{92C0}\x{92C1}' .
+'\x{92C2}\x{92C3}\x{92C4}\x{92C5}\x{92C6}\x{92C7}\x{92C8}\x{92C9}\x{92CA}' .
+'\x{92CB}\x{92CC}\x{92CD}\x{92CE}\x{92CF}\x{92D0}\x{92D1}\x{92D2}\x{92D3}' .
+'\x{92D5}\x{92D6}\x{92D7}\x{92D8}\x{92D9}\x{92DA}\x{92DC}\x{92DD}\x{92DE}' .
+'\x{92DF}\x{92E0}\x{92E1}\x{92E3}\x{92E4}\x{92E5}\x{92E6}\x{92E7}\x{92E8}' .
+'\x{92E9}\x{92EA}\x{92EB}\x{92EC}\x{92ED}\x{92EE}\x{92EF}\x{92F0}\x{92F1}' .
+'\x{92F2}\x{92F3}\x{92F4}\x{92F5}\x{92F6}\x{92F7}\x{92F8}\x{92F9}\x{92FA}' .
+'\x{92FB}\x{92FC}\x{92FD}\x{92FE}\x{92FF}\x{9300}\x{9301}\x{9302}\x{9303}' .
+'\x{9304}\x{9305}\x{9306}\x{9307}\x{9308}\x{9309}\x{930A}\x{930B}\x{930C}' .
+'\x{930D}\x{930E}\x{930F}\x{9310}\x{9311}\x{9312}\x{9313}\x{9314}\x{9315}' .
+'\x{9316}\x{9317}\x{9318}\x{9319}\x{931A}\x{931B}\x{931D}\x{931E}\x{931F}' .
+'\x{9320}\x{9321}\x{9322}\x{9323}\x{9324}\x{9325}\x{9326}\x{9327}\x{9328}' .
+'\x{9329}\x{932A}\x{932B}\x{932D}\x{932E}\x{932F}\x{9332}\x{9333}\x{9334}' .
+'\x{9335}\x{9336}\x{9337}\x{9338}\x{9339}\x{933A}\x{933B}\x{933C}\x{933D}' .
+'\x{933E}\x{933F}\x{9340}\x{9341}\x{9342}\x{9343}\x{9344}\x{9345}\x{9346}' .
+'\x{9347}\x{9348}\x{9349}\x{934A}\x{934B}\x{934C}\x{934D}\x{934E}\x{934F}' .
+'\x{9350}\x{9351}\x{9352}\x{9353}\x{9354}\x{9355}\x{9356}\x{9357}\x{9358}' .
+'\x{9359}\x{935A}\x{935B}\x{935C}\x{935D}\x{935E}\x{935F}\x{9360}\x{9361}' .
+'\x{9363}\x{9364}\x{9365}\x{9366}\x{9367}\x{9369}\x{936A}\x{936C}\x{936D}' .
+'\x{936E}\x{9370}\x{9371}\x{9372}\x{9374}\x{9375}\x{9376}\x{9377}\x{9379}' .
+'\x{937A}\x{937B}\x{937C}\x{937D}\x{937E}\x{9380}\x{9382}\x{9383}\x{9384}' .
+'\x{9385}\x{9386}\x{9387}\x{9388}\x{9389}\x{938A}\x{938C}\x{938D}\x{938E}' .
+'\x{938F}\x{9390}\x{9391}\x{9392}\x{9393}\x{9394}\x{9395}\x{9396}\x{9397}' .
+'\x{9398}\x{9399}\x{939A}\x{939B}\x{939D}\x{939E}\x{939F}\x{93A1}\x{93A2}' .
+'\x{93A3}\x{93A4}\x{93A5}\x{93A6}\x{93A7}\x{93A8}\x{93A9}\x{93AA}\x{93AC}' .
+'\x{93AD}\x{93AE}\x{93AF}\x{93B0}\x{93B1}\x{93B2}\x{93B3}\x{93B4}\x{93B5}' .
+'\x{93B6}\x{93B7}\x{93B8}\x{93B9}\x{93BA}\x{93BC}\x{93BD}\x{93BE}\x{93BF}' .
+'\x{93C0}\x{93C1}\x{93C2}\x{93C3}\x{93C4}\x{93C5}\x{93C6}\x{93C7}\x{93C8}' .
+'\x{93C9}\x{93CA}\x{93CB}\x{93CC}\x{93CD}\x{93CE}\x{93CF}\x{93D0}\x{93D1}' .
+'\x{93D2}\x{93D3}\x{93D4}\x{93D5}\x{93D6}\x{93D7}\x{93D8}\x{93D9}\x{93DA}' .
+'\x{93DB}\x{93DC}\x{93DD}\x{93DE}\x{93DF}\x{93E1}\x{93E2}\x{93E3}\x{93E4}' .
+'\x{93E6}\x{93E7}\x{93E8}\x{93E9}\x{93EA}\x{93EB}\x{93EC}\x{93ED}\x{93EE}' .
+'\x{93EF}\x{93F0}\x{93F1}\x{93F2}\x{93F4}\x{93F5}\x{93F6}\x{93F7}\x{93F8}' .
+'\x{93F9}\x{93FA}\x{93FB}\x{93FC}\x{93FD}\x{93FE}\x{93FF}\x{9400}\x{9401}' .
+'\x{9403}\x{9404}\x{9405}\x{9406}\x{9407}\x{9408}\x{9409}\x{940A}\x{940B}' .
+'\x{940C}\x{940D}\x{940E}\x{940F}\x{9410}\x{9411}\x{9412}\x{9413}\x{9414}' .
+'\x{9415}\x{9416}\x{9418}\x{9419}\x{941B}\x{941D}\x{9420}\x{9422}\x{9423}' .
+'\x{9425}\x{9426}\x{9427}\x{9428}\x{9429}\x{942A}\x{942B}\x{942C}\x{942D}' .
+'\x{942E}\x{942F}\x{9430}\x{9431}\x{9432}\x{9433}\x{9434}\x{9435}\x{9436}' .
+'\x{9437}\x{9438}\x{9439}\x{943A}\x{943B}\x{943C}\x{943D}\x{943E}\x{943F}' .
+'\x{9440}\x{9441}\x{9442}\x{9444}\x{9445}\x{9446}\x{9447}\x{9448}\x{9449}' .
+'\x{944A}\x{944B}\x{944C}\x{944D}\x{944F}\x{9450}\x{9451}\x{9452}\x{9453}' .
+'\x{9454}\x{9455}\x{9456}\x{9457}\x{9458}\x{9459}\x{945B}\x{945C}\x{945D}' .
+'\x{945E}\x{945F}\x{9460}\x{9461}\x{9462}\x{9463}\x{9464}\x{9465}\x{9466}' .
+'\x{9467}\x{9468}\x{9469}\x{946A}\x{946B}\x{946D}\x{946E}\x{946F}\x{9470}' .
+'\x{9471}\x{9472}\x{9473}\x{9474}\x{9475}\x{9476}\x{9477}\x{9478}\x{9479}' .
+'\x{947A}\x{947C}\x{947D}\x{947E}\x{947F}\x{9480}\x{9481}\x{9482}\x{9483}' .
+'\x{9484}\x{9485}\x{9486}\x{9487}\x{9488}\x{9489}\x{948A}\x{948B}\x{948C}' .
+'\x{948D}\x{948E}\x{948F}\x{9490}\x{9491}\x{9492}\x{9493}\x{9494}\x{9495}' .
+'\x{9496}\x{9497}\x{9498}\x{9499}\x{949A}\x{949B}\x{949C}\x{949D}\x{949E}' .
+'\x{949F}\x{94A0}\x{94A1}\x{94A2}\x{94A3}\x{94A4}\x{94A5}\x{94A6}\x{94A7}' .
+'\x{94A8}\x{94A9}\x{94AA}\x{94AB}\x{94AC}\x{94AD}\x{94AE}\x{94AF}\x{94B0}' .
+'\x{94B1}\x{94B2}\x{94B3}\x{94B4}\x{94B5}\x{94B6}\x{94B7}\x{94B8}\x{94B9}' .
+'\x{94BA}\x{94BB}\x{94BC}\x{94BD}\x{94BE}\x{94BF}\x{94C0}\x{94C1}\x{94C2}' .
+'\x{94C3}\x{94C4}\x{94C5}\x{94C6}\x{94C7}\x{94C8}\x{94C9}\x{94CA}\x{94CB}' .
+'\x{94CC}\x{94CD}\x{94CE}\x{94CF}\x{94D0}\x{94D1}\x{94D2}\x{94D3}\x{94D4}' .
+'\x{94D5}\x{94D6}\x{94D7}\x{94D8}\x{94D9}\x{94DA}\x{94DB}\x{94DC}\x{94DD}' .
+'\x{94DE}\x{94DF}\x{94E0}\x{94E1}\x{94E2}\x{94E3}\x{94E4}\x{94E5}\x{94E6}' .
+'\x{94E7}\x{94E8}\x{94E9}\x{94EA}\x{94EB}\x{94EC}\x{94ED}\x{94EE}\x{94EF}' .
+'\x{94F0}\x{94F1}\x{94F2}\x{94F3}\x{94F4}\x{94F5}\x{94F6}\x{94F7}\x{94F8}' .
+'\x{94F9}\x{94FA}\x{94FB}\x{94FC}\x{94FD}\x{94FE}\x{94FF}\x{9500}\x{9501}' .
+'\x{9502}\x{9503}\x{9504}\x{9505}\x{9506}\x{9507}\x{9508}\x{9509}\x{950A}' .
+'\x{950B}\x{950C}\x{950D}\x{950E}\x{950F}\x{9510}\x{9511}\x{9512}\x{9513}' .
+'\x{9514}\x{9515}\x{9516}\x{9517}\x{9518}\x{9519}\x{951A}\x{951B}\x{951C}' .
+'\x{951D}\x{951E}\x{951F}\x{9520}\x{9521}\x{9522}\x{9523}\x{9524}\x{9525}' .
+'\x{9526}\x{9527}\x{9528}\x{9529}\x{952A}\x{952B}\x{952C}\x{952D}\x{952E}' .
+'\x{952F}\x{9530}\x{9531}\x{9532}\x{9533}\x{9534}\x{9535}\x{9536}\x{9537}' .
+'\x{9538}\x{9539}\x{953A}\x{953B}\x{953C}\x{953D}\x{953E}\x{953F}\x{9540}' .
+'\x{9541}\x{9542}\x{9543}\x{9544}\x{9545}\x{9546}\x{9547}\x{9548}\x{9549}' .
+'\x{954A}\x{954B}\x{954C}\x{954D}\x{954E}\x{954F}\x{9550}\x{9551}\x{9552}' .
+'\x{9553}\x{9554}\x{9555}\x{9556}\x{9557}\x{9558}\x{9559}\x{955A}\x{955B}' .
+'\x{955C}\x{955D}\x{955E}\x{955F}\x{9560}\x{9561}\x{9562}\x{9563}\x{9564}' .
+'\x{9565}\x{9566}\x{9567}\x{9568}\x{9569}\x{956A}\x{956B}\x{956C}\x{956D}' .
+'\x{956E}\x{956F}\x{9570}\x{9571}\x{9572}\x{9573}\x{9574}\x{9575}\x{9576}' .
+'\x{9577}\x{957A}\x{957B}\x{957C}\x{957D}\x{957F}\x{9580}\x{9581}\x{9582}' .
+'\x{9583}\x{9584}\x{9586}\x{9587}\x{9588}\x{9589}\x{958A}\x{958B}\x{958C}' .
+'\x{958D}\x{958E}\x{958F}\x{9590}\x{9591}\x{9592}\x{9593}\x{9594}\x{9595}' .
+'\x{9596}\x{9598}\x{9599}\x{959A}\x{959B}\x{959C}\x{959D}\x{959E}\x{959F}' .
+'\x{95A1}\x{95A2}\x{95A3}\x{95A4}\x{95A5}\x{95A6}\x{95A7}\x{95A8}\x{95A9}' .
+'\x{95AA}\x{95AB}\x{95AC}\x{95AD}\x{95AE}\x{95AF}\x{95B0}\x{95B1}\x{95B2}' .
+'\x{95B5}\x{95B6}\x{95B7}\x{95B9}\x{95BA}\x{95BB}\x{95BC}\x{95BD}\x{95BE}' .
+'\x{95BF}\x{95C0}\x{95C2}\x{95C3}\x{95C4}\x{95C5}\x{95C6}\x{95C7}\x{95C8}' .
+'\x{95C9}\x{95CA}\x{95CB}\x{95CC}\x{95CD}\x{95CE}\x{95CF}\x{95D0}\x{95D1}' .
+'\x{95D2}\x{95D3}\x{95D4}\x{95D5}\x{95D6}\x{95D7}\x{95D8}\x{95DA}\x{95DB}' .
+'\x{95DC}\x{95DE}\x{95DF}\x{95E0}\x{95E1}\x{95E2}\x{95E3}\x{95E4}\x{95E5}' .
+'\x{95E6}\x{95E7}\x{95E8}\x{95E9}\x{95EA}\x{95EB}\x{95EC}\x{95ED}\x{95EE}' .
+'\x{95EF}\x{95F0}\x{95F1}\x{95F2}\x{95F3}\x{95F4}\x{95F5}\x{95F6}\x{95F7}' .
+'\x{95F8}\x{95F9}\x{95FA}\x{95FB}\x{95FC}\x{95FD}\x{95FE}\x{95FF}\x{9600}' .
+'\x{9601}\x{9602}\x{9603}\x{9604}\x{9605}\x{9606}\x{9607}\x{9608}\x{9609}' .
+'\x{960A}\x{960B}\x{960C}\x{960D}\x{960E}\x{960F}\x{9610}\x{9611}\x{9612}' .
+'\x{9613}\x{9614}\x{9615}\x{9616}\x{9617}\x{9618}\x{9619}\x{961A}\x{961B}' .
+'\x{961C}\x{961D}\x{961E}\x{961F}\x{9620}\x{9621}\x{9622}\x{9623}\x{9624}' .
+'\x{9627}\x{9628}\x{962A}\x{962B}\x{962C}\x{962D}\x{962E}\x{962F}\x{9630}' .
+'\x{9631}\x{9632}\x{9633}\x{9634}\x{9635}\x{9636}\x{9637}\x{9638}\x{9639}' .
+'\x{963A}\x{963B}\x{963C}\x{963D}\x{963F}\x{9640}\x{9641}\x{9642}\x{9643}' .
+'\x{9644}\x{9645}\x{9646}\x{9647}\x{9648}\x{9649}\x{964A}\x{964B}\x{964C}' .
+'\x{964D}\x{964E}\x{964F}\x{9650}\x{9651}\x{9652}\x{9653}\x{9654}\x{9655}' .
+'\x{9658}\x{9659}\x{965A}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9660}' .
+'\x{9661}\x{9662}\x{9663}\x{9664}\x{9666}\x{9667}\x{9668}\x{9669}\x{966A}' .
+'\x{966B}\x{966C}\x{966D}\x{966E}\x{966F}\x{9670}\x{9671}\x{9672}\x{9673}' .
+'\x{9674}\x{9675}\x{9676}\x{9677}\x{9678}\x{967C}\x{967D}\x{967E}\x{9680}' .
+'\x{9683}\x{9684}\x{9685}\x{9686}\x{9687}\x{9688}\x{9689}\x{968A}\x{968B}' .
+'\x{968D}\x{968E}\x{968F}\x{9690}\x{9691}\x{9692}\x{9693}\x{9694}\x{9695}' .
+'\x{9697}\x{9698}\x{9699}\x{969B}\x{969C}\x{969E}\x{96A0}\x{96A1}\x{96A2}' .
+'\x{96A3}\x{96A4}\x{96A5}\x{96A6}\x{96A7}\x{96A8}\x{96A9}\x{96AA}\x{96AC}' .
+'\x{96AD}\x{96AE}\x{96B0}\x{96B1}\x{96B3}\x{96B4}\x{96B6}\x{96B7}\x{96B8}' .
+'\x{96B9}\x{96BA}\x{96BB}\x{96BC}\x{96BD}\x{96BE}\x{96BF}\x{96C0}\x{96C1}' .
+'\x{96C2}\x{96C3}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C8}\x{96C9}\x{96CA}' .
+'\x{96CB}\x{96CC}\x{96CD}\x{96CE}\x{96CF}\x{96D0}\x{96D1}\x{96D2}\x{96D3}' .
+'\x{96D4}\x{96D5}\x{96D6}\x{96D7}\x{96D8}\x{96D9}\x{96DA}\x{96DB}\x{96DC}' .
+'\x{96DD}\x{96DE}\x{96DF}\x{96E0}\x{96E1}\x{96E2}\x{96E3}\x{96E5}\x{96E8}' .
+'\x{96E9}\x{96EA}\x{96EB}\x{96EC}\x{96ED}\x{96EE}\x{96EF}\x{96F0}\x{96F1}' .
+'\x{96F2}\x{96F3}\x{96F4}\x{96F5}\x{96F6}\x{96F7}\x{96F8}\x{96F9}\x{96FA}' .
+'\x{96FB}\x{96FD}\x{96FE}\x{96FF}\x{9700}\x{9701}\x{9702}\x{9703}\x{9704}' .
+'\x{9705}\x{9706}\x{9707}\x{9708}\x{9709}\x{970A}\x{970B}\x{970C}\x{970D}' .
+'\x{970E}\x{970F}\x{9710}\x{9711}\x{9712}\x{9713}\x{9715}\x{9716}\x{9718}' .
+'\x{9719}\x{971C}\x{971D}\x{971E}\x{971F}\x{9720}\x{9721}\x{9722}\x{9723}' .
+'\x{9724}\x{9725}\x{9726}\x{9727}\x{9728}\x{9729}\x{972A}\x{972B}\x{972C}' .
+'\x{972D}\x{972E}\x{972F}\x{9730}\x{9731}\x{9732}\x{9735}\x{9736}\x{9738}' .
+'\x{9739}\x{973A}\x{973B}\x{973C}\x{973D}\x{973E}\x{973F}\x{9742}\x{9743}' .
+'\x{9744}\x{9745}\x{9746}\x{9747}\x{9748}\x{9749}\x{974A}\x{974B}\x{974C}' .
+'\x{974E}\x{974F}\x{9750}\x{9751}\x{9752}\x{9753}\x{9754}\x{9755}\x{9756}' .
+'\x{9758}\x{9759}\x{975A}\x{975B}\x{975C}\x{975D}\x{975E}\x{975F}\x{9760}' .
+'\x{9761}\x{9762}\x{9765}\x{9766}\x{9767}\x{9768}\x{9769}\x{976A}\x{976B}' .
+'\x{976C}\x{976D}\x{976E}\x{976F}\x{9770}\x{9772}\x{9773}\x{9774}\x{9776}' .
+'\x{9777}\x{9778}\x{9779}\x{977A}\x{977B}\x{977C}\x{977D}\x{977E}\x{977F}' .
+'\x{9780}\x{9781}\x{9782}\x{9783}\x{9784}\x{9785}\x{9786}\x{9788}\x{978A}' .
+'\x{978B}\x{978C}\x{978D}\x{978E}\x{978F}\x{9790}\x{9791}\x{9792}\x{9793}' .
+'\x{9794}\x{9795}\x{9796}\x{9797}\x{9798}\x{9799}\x{979A}\x{979C}\x{979D}' .
+'\x{979E}\x{979F}\x{97A0}\x{97A1}\x{97A2}\x{97A3}\x{97A4}\x{97A5}\x{97A6}' .
+'\x{97A7}\x{97A8}\x{97AA}\x{97AB}\x{97AC}\x{97AD}\x{97AE}\x{97AF}\x{97B2}' .
+'\x{97B3}\x{97B4}\x{97B6}\x{97B7}\x{97B8}\x{97B9}\x{97BA}\x{97BB}\x{97BC}' .
+'\x{97BD}\x{97BF}\x{97C1}\x{97C2}\x{97C3}\x{97C4}\x{97C5}\x{97C6}\x{97C7}' .
+'\x{97C8}\x{97C9}\x{97CA}\x{97CB}\x{97CC}\x{97CD}\x{97CE}\x{97CF}\x{97D0}' .
+'\x{97D1}\x{97D3}\x{97D4}\x{97D5}\x{97D6}\x{97D7}\x{97D8}\x{97D9}\x{97DA}' .
+'\x{97DB}\x{97DC}\x{97DD}\x{97DE}\x{97DF}\x{97E0}\x{97E1}\x{97E2}\x{97E3}' .
+'\x{97E4}\x{97E5}\x{97E6}\x{97E7}\x{97E8}\x{97E9}\x{97EA}\x{97EB}\x{97EC}' .
+'\x{97ED}\x{97EE}\x{97EF}\x{97F0}\x{97F1}\x{97F2}\x{97F3}\x{97F4}\x{97F5}' .
+'\x{97F6}\x{97F7}\x{97F8}\x{97F9}\x{97FA}\x{97FB}\x{97FD}\x{97FE}\x{97FF}' .
+'\x{9800}\x{9801}\x{9802}\x{9803}\x{9804}\x{9805}\x{9806}\x{9807}\x{9808}' .
+'\x{9809}\x{980A}\x{980B}\x{980C}\x{980D}\x{980E}\x{980F}\x{9810}\x{9811}' .
+'\x{9812}\x{9813}\x{9814}\x{9815}\x{9816}\x{9817}\x{9818}\x{9819}\x{981A}' .
+'\x{981B}\x{981C}\x{981D}\x{981E}\x{9820}\x{9821}\x{9822}\x{9823}\x{9824}' .
+'\x{9826}\x{9827}\x{9828}\x{9829}\x{982B}\x{982D}\x{982E}\x{982F}\x{9830}' .
+'\x{9831}\x{9832}\x{9834}\x{9835}\x{9836}\x{9837}\x{9838}\x{9839}\x{983B}' .
+'\x{983C}\x{983D}\x{983F}\x{9840}\x{9841}\x{9843}\x{9844}\x{9845}\x{9846}' .
+'\x{9848}\x{9849}\x{984A}\x{984C}\x{984D}\x{984E}\x{984F}\x{9850}\x{9851}' .
+'\x{9852}\x{9853}\x{9854}\x{9855}\x{9857}\x{9858}\x{9859}\x{985A}\x{985B}' .
+'\x{985C}\x{985D}\x{985E}\x{985F}\x{9860}\x{9861}\x{9862}\x{9863}\x{9864}' .
+'\x{9865}\x{9867}\x{9869}\x{986A}\x{986B}\x{986C}\x{986D}\x{986E}\x{986F}' .
+'\x{9870}\x{9871}\x{9872}\x{9873}\x{9874}\x{9875}\x{9876}\x{9877}\x{9878}' .
+'\x{9879}\x{987A}\x{987B}\x{987C}\x{987D}\x{987E}\x{987F}\x{9880}\x{9881}' .
+'\x{9882}\x{9883}\x{9884}\x{9885}\x{9886}\x{9887}\x{9888}\x{9889}\x{988A}' .
+'\x{988B}\x{988C}\x{988D}\x{988E}\x{988F}\x{9890}\x{9891}\x{9892}\x{9893}' .
+'\x{9894}\x{9895}\x{9896}\x{9897}\x{9898}\x{9899}\x{989A}\x{989B}\x{989C}' .
+'\x{989D}\x{989E}\x{989F}\x{98A0}\x{98A1}\x{98A2}\x{98A3}\x{98A4}\x{98A5}' .
+'\x{98A6}\x{98A7}\x{98A8}\x{98A9}\x{98AA}\x{98AB}\x{98AC}\x{98AD}\x{98AE}' .
+'\x{98AF}\x{98B0}\x{98B1}\x{98B2}\x{98B3}\x{98B4}\x{98B5}\x{98B6}\x{98B8}' .
+'\x{98B9}\x{98BA}\x{98BB}\x{98BC}\x{98BD}\x{98BE}\x{98BF}\x{98C0}\x{98C1}' .
+'\x{98C2}\x{98C3}\x{98C4}\x{98C5}\x{98C6}\x{98C8}\x{98C9}\x{98CB}\x{98CC}' .
+'\x{98CD}\x{98CE}\x{98CF}\x{98D0}\x{98D1}\x{98D2}\x{98D3}\x{98D4}\x{98D5}' .
+'\x{98D6}\x{98D7}\x{98D8}\x{98D9}\x{98DA}\x{98DB}\x{98DC}\x{98DD}\x{98DE}' .
+'\x{98DF}\x{98E0}\x{98E2}\x{98E3}\x{98E5}\x{98E6}\x{98E7}\x{98E8}\x{98E9}' .
+'\x{98EA}\x{98EB}\x{98ED}\x{98EF}\x{98F0}\x{98F2}\x{98F3}\x{98F4}\x{98F5}' .
+'\x{98F6}\x{98F7}\x{98F9}\x{98FA}\x{98FC}\x{98FD}\x{98FE}\x{98FF}\x{9900}' .
+'\x{9901}\x{9902}\x{9903}\x{9904}\x{9905}\x{9906}\x{9907}\x{9908}\x{9909}' .
+'\x{990A}\x{990B}\x{990C}\x{990D}\x{990E}\x{990F}\x{9910}\x{9911}\x{9912}' .
+'\x{9913}\x{9914}\x{9915}\x{9916}\x{9917}\x{9918}\x{991A}\x{991B}\x{991C}' .
+'\x{991D}\x{991E}\x{991F}\x{9920}\x{9921}\x{9922}\x{9923}\x{9924}\x{9925}' .
+'\x{9926}\x{9927}\x{9928}\x{9929}\x{992A}\x{992B}\x{992C}\x{992D}\x{992E}' .
+'\x{992F}\x{9930}\x{9931}\x{9932}\x{9933}\x{9934}\x{9935}\x{9936}\x{9937}' .
+'\x{9938}\x{9939}\x{993A}\x{993C}\x{993D}\x{993E}\x{993F}\x{9940}\x{9941}' .
+'\x{9942}\x{9943}\x{9945}\x{9946}\x{9947}\x{9948}\x{9949}\x{994A}\x{994B}' .
+'\x{994C}\x{994E}\x{994F}\x{9950}\x{9951}\x{9952}\x{9953}\x{9954}\x{9955}' .
+'\x{9956}\x{9957}\x{9958}\x{9959}\x{995B}\x{995C}\x{995E}\x{995F}\x{9960}' .
+'\x{9961}\x{9962}\x{9963}\x{9964}\x{9965}\x{9966}\x{9967}\x{9968}\x{9969}' .
+'\x{996A}\x{996B}\x{996C}\x{996D}\x{996E}\x{996F}\x{9970}\x{9971}\x{9972}' .
+'\x{9973}\x{9974}\x{9975}\x{9976}\x{9977}\x{9978}\x{9979}\x{997A}\x{997B}' .
+'\x{997C}\x{997D}\x{997E}\x{997F}\x{9980}\x{9981}\x{9982}\x{9983}\x{9984}' .
+'\x{9985}\x{9986}\x{9987}\x{9988}\x{9989}\x{998A}\x{998B}\x{998C}\x{998D}' .
+'\x{998E}\x{998F}\x{9990}\x{9991}\x{9992}\x{9993}\x{9994}\x{9995}\x{9996}' .
+'\x{9997}\x{9998}\x{9999}\x{999A}\x{999B}\x{999C}\x{999D}\x{999E}\x{999F}' .
+'\x{99A0}\x{99A1}\x{99A2}\x{99A3}\x{99A4}\x{99A5}\x{99A6}\x{99A7}\x{99A8}' .
+'\x{99A9}\x{99AA}\x{99AB}\x{99AC}\x{99AD}\x{99AE}\x{99AF}\x{99B0}\x{99B1}' .
+'\x{99B2}\x{99B3}\x{99B4}\x{99B5}\x{99B6}\x{99B7}\x{99B8}\x{99B9}\x{99BA}' .
+'\x{99BB}\x{99BC}\x{99BD}\x{99BE}\x{99C0}\x{99C1}\x{99C2}\x{99C3}\x{99C4}' .
+'\x{99C6}\x{99C7}\x{99C8}\x{99C9}\x{99CA}\x{99CB}\x{99CC}\x{99CD}\x{99CE}' .
+'\x{99CF}\x{99D0}\x{99D1}\x{99D2}\x{99D3}\x{99D4}\x{99D5}\x{99D6}\x{99D7}' .
+'\x{99D8}\x{99D9}\x{99DA}\x{99DB}\x{99DC}\x{99DD}\x{99DE}\x{99DF}\x{99E1}' .
+'\x{99E2}\x{99E3}\x{99E4}\x{99E5}\x{99E7}\x{99E8}\x{99E9}\x{99EA}\x{99EC}' .
+'\x{99ED}\x{99EE}\x{99EF}\x{99F0}\x{99F1}\x{99F2}\x{99F3}\x{99F4}\x{99F6}' .
+'\x{99F7}\x{99F8}\x{99F9}\x{99FA}\x{99FB}\x{99FC}\x{99FD}\x{99FE}\x{99FF}' .
+'\x{9A00}\x{9A01}\x{9A02}\x{9A03}\x{9A04}\x{9A05}\x{9A06}\x{9A07}\x{9A08}' .
+'\x{9A09}\x{9A0A}\x{9A0B}\x{9A0C}\x{9A0D}\x{9A0E}\x{9A0F}\x{9A11}\x{9A14}' .
+'\x{9A15}\x{9A16}\x{9A19}\x{9A1A}\x{9A1B}\x{9A1C}\x{9A1D}\x{9A1E}\x{9A1F}' .
+'\x{9A20}\x{9A21}\x{9A22}\x{9A23}\x{9A24}\x{9A25}\x{9A26}\x{9A27}\x{9A29}' .
+'\x{9A2A}\x{9A2B}\x{9A2C}\x{9A2D}\x{9A2E}\x{9A2F}\x{9A30}\x{9A31}\x{9A32}' .
+'\x{9A33}\x{9A34}\x{9A35}\x{9A36}\x{9A37}\x{9A38}\x{9A39}\x{9A3A}\x{9A3C}' .
+'\x{9A3D}\x{9A3E}\x{9A3F}\x{9A40}\x{9A41}\x{9A42}\x{9A43}\x{9A44}\x{9A45}' .
+'\x{9A46}\x{9A47}\x{9A48}\x{9A49}\x{9A4A}\x{9A4B}\x{9A4C}\x{9A4D}\x{9A4E}' .
+'\x{9A4F}\x{9A50}\x{9A52}\x{9A53}\x{9A54}\x{9A55}\x{9A56}\x{9A57}\x{9A59}' .
+'\x{9A5A}\x{9A5B}\x{9A5C}\x{9A5E}\x{9A5F}\x{9A60}\x{9A61}\x{9A62}\x{9A64}' .
+'\x{9A65}\x{9A66}\x{9A67}\x{9A68}\x{9A69}\x{9A6A}\x{9A6B}\x{9A6C}\x{9A6D}' .
+'\x{9A6E}\x{9A6F}\x{9A70}\x{9A71}\x{9A72}\x{9A73}\x{9A74}\x{9A75}\x{9A76}' .
+'\x{9A77}\x{9A78}\x{9A79}\x{9A7A}\x{9A7B}\x{9A7C}\x{9A7D}\x{9A7E}\x{9A7F}' .
+'\x{9A80}\x{9A81}\x{9A82}\x{9A83}\x{9A84}\x{9A85}\x{9A86}\x{9A87}\x{9A88}' .
+'\x{9A89}\x{9A8A}\x{9A8B}\x{9A8C}\x{9A8D}\x{9A8E}\x{9A8F}\x{9A90}\x{9A91}' .
+'\x{9A92}\x{9A93}\x{9A94}\x{9A95}\x{9A96}\x{9A97}\x{9A98}\x{9A99}\x{9A9A}' .
+'\x{9A9B}\x{9A9C}\x{9A9D}\x{9A9E}\x{9A9F}\x{9AA0}\x{9AA1}\x{9AA2}\x{9AA3}' .
+'\x{9AA4}\x{9AA5}\x{9AA6}\x{9AA7}\x{9AA8}\x{9AAA}\x{9AAB}\x{9AAC}\x{9AAD}' .
+'\x{9AAE}\x{9AAF}\x{9AB0}\x{9AB1}\x{9AB2}\x{9AB3}\x{9AB4}\x{9AB5}\x{9AB6}' .
+'\x{9AB7}\x{9AB8}\x{9AB9}\x{9ABA}\x{9ABB}\x{9ABC}\x{9ABE}\x{9ABF}\x{9AC0}' .
+'\x{9AC1}\x{9AC2}\x{9AC3}\x{9AC4}\x{9AC5}\x{9AC6}\x{9AC7}\x{9AC9}\x{9ACA}' .
+'\x{9ACB}\x{9ACC}\x{9ACD}\x{9ACE}\x{9ACF}\x{9AD0}\x{9AD1}\x{9AD2}\x{9AD3}' .
+'\x{9AD4}\x{9AD5}\x{9AD6}\x{9AD8}\x{9AD9}\x{9ADA}\x{9ADB}\x{9ADC}\x{9ADD}' .
+'\x{9ADE}\x{9ADF}\x{9AE1}\x{9AE2}\x{9AE3}\x{9AE5}\x{9AE6}\x{9AE7}\x{9AEA}' .
+'\x{9AEB}\x{9AEC}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF2}\x{9AF3}\x{9AF4}' .
+'\x{9AF5}\x{9AF6}\x{9AF7}\x{9AF8}\x{9AF9}\x{9AFA}\x{9AFB}\x{9AFC}\x{9AFD}' .
+'\x{9AFE}\x{9AFF}\x{9B01}\x{9B03}\x{9B04}\x{9B05}\x{9B06}\x{9B07}\x{9B08}' .
+'\x{9B0A}\x{9B0B}\x{9B0C}\x{9B0D}\x{9B0E}\x{9B0F}\x{9B10}\x{9B11}\x{9B12}' .
+'\x{9B13}\x{9B15}\x{9B16}\x{9B17}\x{9B18}\x{9B19}\x{9B1A}\x{9B1C}\x{9B1D}' .
+'\x{9B1E}\x{9B1F}\x{9B20}\x{9B21}\x{9B22}\x{9B23}\x{9B24}\x{9B25}\x{9B26}' .
+'\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2B}\x{9B2C}\x{9B2D}\x{9B2E}\x{9B2F}' .
+'\x{9B30}\x{9B31}\x{9B32}\x{9B33}\x{9B35}\x{9B36}\x{9B37}\x{9B38}\x{9B39}' .
+'\x{9B3A}\x{9B3B}\x{9B3C}\x{9B3E}\x{9B3F}\x{9B41}\x{9B42}\x{9B43}\x{9B44}' .
+'\x{9B45}\x{9B46}\x{9B47}\x{9B48}\x{9B49}\x{9B4A}\x{9B4B}\x{9B4C}\x{9B4D}' .
+'\x{9B4E}\x{9B4F}\x{9B51}\x{9B52}\x{9B53}\x{9B54}\x{9B55}\x{9B56}\x{9B58}' .
+'\x{9B59}\x{9B5A}\x{9B5B}\x{9B5C}\x{9B5D}\x{9B5E}\x{9B5F}\x{9B60}\x{9B61}' .
+'\x{9B63}\x{9B64}\x{9B65}\x{9B66}\x{9B67}\x{9B68}\x{9B69}\x{9B6A}\x{9B6B}' .
+'\x{9B6C}\x{9B6D}\x{9B6E}\x{9B6F}\x{9B70}\x{9B71}\x{9B73}\x{9B74}\x{9B75}' .
+'\x{9B76}\x{9B77}\x{9B78}\x{9B79}\x{9B7A}\x{9B7B}\x{9B7C}\x{9B7D}\x{9B7E}' .
+'\x{9B7F}\x{9B80}\x{9B81}\x{9B82}\x{9B83}\x{9B84}\x{9B85}\x{9B86}\x{9B87}' .
+'\x{9B88}\x{9B8A}\x{9B8B}\x{9B8D}\x{9B8E}\x{9B8F}\x{9B90}\x{9B91}\x{9B92}' .
+'\x{9B93}\x{9B94}\x{9B95}\x{9B96}\x{9B97}\x{9B98}\x{9B9A}\x{9B9B}\x{9B9C}' .
+'\x{9B9D}\x{9B9E}\x{9B9F}\x{9BA0}\x{9BA1}\x{9BA2}\x{9BA3}\x{9BA4}\x{9BA5}' .
+'\x{9BA6}\x{9BA7}\x{9BA8}\x{9BA9}\x{9BAA}\x{9BAB}\x{9BAC}\x{9BAD}\x{9BAE}' .
+'\x{9BAF}\x{9BB0}\x{9BB1}\x{9BB2}\x{9BB3}\x{9BB4}\x{9BB5}\x{9BB6}\x{9BB7}' .
+'\x{9BB8}\x{9BB9}\x{9BBA}\x{9BBB}\x{9BBC}\x{9BBD}\x{9BBE}\x{9BBF}\x{9BC0}' .
+'\x{9BC1}\x{9BC3}\x{9BC4}\x{9BC5}\x{9BC6}\x{9BC7}\x{9BC8}\x{9BC9}\x{9BCA}' .
+'\x{9BCB}\x{9BCC}\x{9BCD}\x{9BCE}\x{9BCF}\x{9BD0}\x{9BD1}\x{9BD2}\x{9BD3}' .
+'\x{9BD4}\x{9BD5}\x{9BD6}\x{9BD7}\x{9BD8}\x{9BD9}\x{9BDA}\x{9BDB}\x{9BDC}' .
+'\x{9BDD}\x{9BDE}\x{9BDF}\x{9BE0}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}\x{9BE5}' .
+'\x{9BE6}\x{9BE7}\x{9BE8}\x{9BE9}\x{9BEA}\x{9BEB}\x{9BEC}\x{9BED}\x{9BEE}' .
+'\x{9BEF}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF3}\x{9BF4}\x{9BF5}\x{9BF7}\x{9BF8}' .
+'\x{9BF9}\x{9BFA}\x{9BFB}\x{9BFC}\x{9BFD}\x{9BFE}\x{9BFF}\x{9C02}\x{9C05}' .
+'\x{9C06}\x{9C07}\x{9C08}\x{9C09}\x{9C0A}\x{9C0B}\x{9C0C}\x{9C0D}\x{9C0E}' .
+'\x{9C0F}\x{9C10}\x{9C11}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C16}\x{9C17}' .
+'\x{9C18}\x{9C19}\x{9C1A}\x{9C1B}\x{9C1C}\x{9C1D}\x{9C1E}\x{9C1F}\x{9C20}' .
+'\x{9C21}\x{9C22}\x{9C23}\x{9C24}\x{9C25}\x{9C26}\x{9C27}\x{9C28}\x{9C29}' .
+'\x{9C2A}\x{9C2B}\x{9C2C}\x{9C2D}\x{9C2F}\x{9C30}\x{9C31}\x{9C32}\x{9C33}' .
+'\x{9C34}\x{9C35}\x{9C36}\x{9C37}\x{9C38}\x{9C39}\x{9C3A}\x{9C3B}\x{9C3C}' .
+'\x{9C3D}\x{9C3E}\x{9C3F}\x{9C40}\x{9C41}\x{9C43}\x{9C44}\x{9C45}\x{9C46}' .
+'\x{9C47}\x{9C48}\x{9C49}\x{9C4A}\x{9C4B}\x{9C4C}\x{9C4D}\x{9C4E}\x{9C50}' .
+'\x{9C52}\x{9C53}\x{9C54}\x{9C55}\x{9C56}\x{9C57}\x{9C58}\x{9C59}\x{9C5A}' .
+'\x{9C5B}\x{9C5C}\x{9C5D}\x{9C5E}\x{9C5F}\x{9C60}\x{9C62}\x{9C63}\x{9C65}' .
+'\x{9C66}\x{9C67}\x{9C68}\x{9C69}\x{9C6A}\x{9C6B}\x{9C6C}\x{9C6D}\x{9C6E}' .
+'\x{9C6F}\x{9C70}\x{9C71}\x{9C72}\x{9C73}\x{9C74}\x{9C75}\x{9C77}\x{9C78}' .
+'\x{9C79}\x{9C7A}\x{9C7C}\x{9C7D}\x{9C7E}\x{9C7F}\x{9C80}\x{9C81}\x{9C82}' .
+'\x{9C83}\x{9C84}\x{9C85}\x{9C86}\x{9C87}\x{9C88}\x{9C89}\x{9C8A}\x{9C8B}' .
+'\x{9C8C}\x{9C8D}\x{9C8E}\x{9C8F}\x{9C90}\x{9C91}\x{9C92}\x{9C93}\x{9C94}' .
+'\x{9C95}\x{9C96}\x{9C97}\x{9C98}\x{9C99}\x{9C9A}\x{9C9B}\x{9C9C}\x{9C9D}' .
+'\x{9C9E}\x{9C9F}\x{9CA0}\x{9CA1}\x{9CA2}\x{9CA3}\x{9CA4}\x{9CA5}\x{9CA6}' .
+'\x{9CA7}\x{9CA8}\x{9CA9}\x{9CAA}\x{9CAB}\x{9CAC}\x{9CAD}\x{9CAE}\x{9CAF}' .
+'\x{9CB0}\x{9CB1}\x{9CB2}\x{9CB3}\x{9CB4}\x{9CB5}\x{9CB6}\x{9CB7}\x{9CB8}' .
+'\x{9CB9}\x{9CBA}\x{9CBB}\x{9CBC}\x{9CBD}\x{9CBE}\x{9CBF}\x{9CC0}\x{9CC1}' .
+'\x{9CC2}\x{9CC3}\x{9CC4}\x{9CC5}\x{9CC6}\x{9CC7}\x{9CC8}\x{9CC9}\x{9CCA}' .
+'\x{9CCB}\x{9CCC}\x{9CCD}\x{9CCE}\x{9CCF}\x{9CD0}\x{9CD1}\x{9CD2}\x{9CD3}' .
+'\x{9CD4}\x{9CD5}\x{9CD6}\x{9CD7}\x{9CD8}\x{9CD9}\x{9CDA}\x{9CDB}\x{9CDC}' .
+'\x{9CDD}\x{9CDE}\x{9CDF}\x{9CE0}\x{9CE1}\x{9CE2}\x{9CE3}\x{9CE4}\x{9CE5}' .
+'\x{9CE6}\x{9CE7}\x{9CE8}\x{9CE9}\x{9CEA}\x{9CEB}\x{9CEC}\x{9CED}\x{9CEE}' .
+'\x{9CEF}\x{9CF0}\x{9CF1}\x{9CF2}\x{9CF3}\x{9CF4}\x{9CF5}\x{9CF6}\x{9CF7}' .
+'\x{9CF8}\x{9CF9}\x{9CFA}\x{9CFB}\x{9CFC}\x{9CFD}\x{9CFE}\x{9CFF}\x{9D00}' .
+'\x{9D01}\x{9D02}\x{9D03}\x{9D04}\x{9D05}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' .
+'\x{9D0A}\x{9D0B}\x{9D0F}\x{9D10}\x{9D12}\x{9D13}\x{9D14}\x{9D15}\x{9D16}' .
+'\x{9D17}\x{9D18}\x{9D19}\x{9D1A}\x{9D1B}\x{9D1C}\x{9D1D}\x{9D1E}\x{9D1F}' .
+'\x{9D20}\x{9D21}\x{9D22}\x{9D23}\x{9D24}\x{9D25}\x{9D26}\x{9D28}\x{9D29}' .
+'\x{9D2B}\x{9D2D}\x{9D2E}\x{9D2F}\x{9D30}\x{9D31}\x{9D32}\x{9D33}\x{9D34}' .
+'\x{9D36}\x{9D37}\x{9D38}\x{9D39}\x{9D3A}\x{9D3B}\x{9D3D}\x{9D3E}\x{9D3F}' .
+'\x{9D40}\x{9D41}\x{9D42}\x{9D43}\x{9D45}\x{9D46}\x{9D47}\x{9D48}\x{9D49}' .
+'\x{9D4A}\x{9D4B}\x{9D4C}\x{9D4D}\x{9D4E}\x{9D4F}\x{9D50}\x{9D51}\x{9D52}' .
+'\x{9D53}\x{9D54}\x{9D55}\x{9D56}\x{9D57}\x{9D58}\x{9D59}\x{9D5A}\x{9D5B}' .
+'\x{9D5C}\x{9D5D}\x{9D5E}\x{9D5F}\x{9D60}\x{9D61}\x{9D62}\x{9D63}\x{9D64}' .
+'\x{9D65}\x{9D66}\x{9D67}\x{9D68}\x{9D69}\x{9D6A}\x{9D6B}\x{9D6C}\x{9D6E}' .
+'\x{9D6F}\x{9D70}\x{9D71}\x{9D72}\x{9D73}\x{9D74}\x{9D75}\x{9D76}\x{9D77}' .
+'\x{9D78}\x{9D79}\x{9D7A}\x{9D7B}\x{9D7C}\x{9D7D}\x{9D7E}\x{9D7F}\x{9D80}' .
+'\x{9D81}\x{9D82}\x{9D83}\x{9D84}\x{9D85}\x{9D86}\x{9D87}\x{9D88}\x{9D89}' .
+'\x{9D8A}\x{9D8B}\x{9D8C}\x{9D8D}\x{9D8E}\x{9D90}\x{9D91}\x{9D92}\x{9D93}' .
+'\x{9D94}\x{9D96}\x{9D97}\x{9D98}\x{9D99}\x{9D9A}\x{9D9B}\x{9D9C}\x{9D9D}' .
+'\x{9D9E}\x{9D9F}\x{9DA0}\x{9DA1}\x{9DA2}\x{9DA3}\x{9DA4}\x{9DA5}\x{9DA6}' .
+'\x{9DA7}\x{9DA8}\x{9DA9}\x{9DAA}\x{9DAB}\x{9DAC}\x{9DAD}\x{9DAF}\x{9DB0}' .
+'\x{9DB1}\x{9DB2}\x{9DB3}\x{9DB4}\x{9DB5}\x{9DB6}\x{9DB7}\x{9DB8}\x{9DB9}' .
+'\x{9DBA}\x{9DBB}\x{9DBC}\x{9DBE}\x{9DBF}\x{9DC1}\x{9DC2}\x{9DC3}\x{9DC4}' .
+'\x{9DC5}\x{9DC7}\x{9DC8}\x{9DC9}\x{9DCA}\x{9DCB}\x{9DCC}\x{9DCD}\x{9DCE}' .
+'\x{9DCF}\x{9DD0}\x{9DD1}\x{9DD2}\x{9DD3}\x{9DD4}\x{9DD5}\x{9DD6}\x{9DD7}' .
+'\x{9DD8}\x{9DD9}\x{9DDA}\x{9DDB}\x{9DDC}\x{9DDD}\x{9DDE}\x{9DDF}\x{9DE0}' .
+'\x{9DE1}\x{9DE2}\x{9DE3}\x{9DE4}\x{9DE5}\x{9DE6}\x{9DE7}\x{9DE8}\x{9DE9}' .
+'\x{9DEB}\x{9DEC}\x{9DED}\x{9DEE}\x{9DEF}\x{9DF0}\x{9DF1}\x{9DF2}\x{9DF3}' .
+'\x{9DF4}\x{9DF5}\x{9DF6}\x{9DF7}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFB}\x{9DFD}' .
+'\x{9DFE}\x{9DFF}\x{9E00}\x{9E01}\x{9E02}\x{9E03}\x{9E04}\x{9E05}\x{9E06}' .
+'\x{9E07}\x{9E08}\x{9E09}\x{9E0A}\x{9E0B}\x{9E0C}\x{9E0D}\x{9E0F}\x{9E10}' .
+'\x{9E11}\x{9E12}\x{9E13}\x{9E14}\x{9E15}\x{9E17}\x{9E18}\x{9E19}\x{9E1A}' .
+'\x{9E1B}\x{9E1D}\x{9E1E}\x{9E1F}\x{9E20}\x{9E21}\x{9E22}\x{9E23}\x{9E24}' .
+'\x{9E25}\x{9E26}\x{9E27}\x{9E28}\x{9E29}\x{9E2A}\x{9E2B}\x{9E2C}\x{9E2D}' .
+'\x{9E2E}\x{9E2F}\x{9E30}\x{9E31}\x{9E32}\x{9E33}\x{9E34}\x{9E35}\x{9E36}' .
+'\x{9E37}\x{9E38}\x{9E39}\x{9E3A}\x{9E3B}\x{9E3C}\x{9E3D}\x{9E3E}\x{9E3F}' .
+'\x{9E40}\x{9E41}\x{9E42}\x{9E43}\x{9E44}\x{9E45}\x{9E46}\x{9E47}\x{9E48}' .
+'\x{9E49}\x{9E4A}\x{9E4B}\x{9E4C}\x{9E4D}\x{9E4E}\x{9E4F}\x{9E50}\x{9E51}' .
+'\x{9E52}\x{9E53}\x{9E54}\x{9E55}\x{9E56}\x{9E57}\x{9E58}\x{9E59}\x{9E5A}' .
+'\x{9E5B}\x{9E5C}\x{9E5D}\x{9E5E}\x{9E5F}\x{9E60}\x{9E61}\x{9E62}\x{9E63}' .
+'\x{9E64}\x{9E65}\x{9E66}\x{9E67}\x{9E68}\x{9E69}\x{9E6A}\x{9E6B}\x{9E6C}' .
+'\x{9E6D}\x{9E6E}\x{9E6F}\x{9E70}\x{9E71}\x{9E72}\x{9E73}\x{9E74}\x{9E75}' .
+'\x{9E76}\x{9E77}\x{9E79}\x{9E7A}\x{9E7C}\x{9E7D}\x{9E7E}\x{9E7F}\x{9E80}' .
+'\x{9E81}\x{9E82}\x{9E83}\x{9E84}\x{9E85}\x{9E86}\x{9E87}\x{9E88}\x{9E89}' .
+'\x{9E8A}\x{9E8B}\x{9E8C}\x{9E8D}\x{9E8E}\x{9E91}\x{9E92}\x{9E93}\x{9E94}' .
+'\x{9E96}\x{9E97}\x{9E99}\x{9E9A}\x{9E9B}\x{9E9C}\x{9E9D}\x{9E9F}\x{9EA0}' .
+'\x{9EA1}\x{9EA3}\x{9EA4}\x{9EA5}\x{9EA6}\x{9EA7}\x{9EA8}\x{9EA9}\x{9EAA}' .
+'\x{9EAD}\x{9EAE}\x{9EAF}\x{9EB0}\x{9EB2}\x{9EB3}\x{9EB4}\x{9EB5}\x{9EB6}' .
+'\x{9EB7}\x{9EB8}\x{9EBB}\x{9EBC}\x{9EBD}\x{9EBE}\x{9EBF}\x{9EC0}\x{9EC1}' .
+'\x{9EC2}\x{9EC3}\x{9EC4}\x{9EC5}\x{9EC6}\x{9EC7}\x{9EC8}\x{9EC9}\x{9ECA}' .
+'\x{9ECB}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED1}\x{9ED2}\x{9ED3}' .
+'\x{9ED4}\x{9ED5}\x{9ED6}\x{9ED7}\x{9ED8}\x{9ED9}\x{9EDA}\x{9EDB}\x{9EDC}' .
+'\x{9EDD}\x{9EDE}\x{9EDF}\x{9EE0}\x{9EE1}\x{9EE2}\x{9EE3}\x{9EE4}\x{9EE5}' .
+'\x{9EE6}\x{9EE7}\x{9EE8}\x{9EE9}\x{9EEA}\x{9EEB}\x{9EED}\x{9EEE}\x{9EEF}' .
+'\x{9EF0}\x{9EF2}\x{9EF3}\x{9EF4}\x{9EF5}\x{9EF6}\x{9EF7}\x{9EF8}\x{9EF9}' .
+'\x{9EFA}\x{9EFB}\x{9EFC}\x{9EFD}\x{9EFE}\x{9EFF}\x{9F00}\x{9F01}\x{9F02}' .
+'\x{9F04}\x{9F05}\x{9F06}\x{9F07}\x{9F08}\x{9F09}\x{9F0A}\x{9F0B}\x{9F0C}' .
+'\x{9F0D}\x{9F0E}\x{9F0F}\x{9F10}\x{9F12}\x{9F13}\x{9F15}\x{9F16}\x{9F17}' .
+'\x{9F18}\x{9F19}\x{9F1A}\x{9F1B}\x{9F1C}\x{9F1D}\x{9F1E}\x{9F1F}\x{9F20}' .
+'\x{9F22}\x{9F23}\x{9F24}\x{9F25}\x{9F27}\x{9F28}\x{9F29}\x{9F2A}\x{9F2B}' .
+'\x{9F2C}\x{9F2D}\x{9F2E}\x{9F2F}\x{9F30}\x{9F31}\x{9F32}\x{9F33}\x{9F34}' .
+'\x{9F35}\x{9F36}\x{9F37}\x{9F38}\x{9F39}\x{9F3A}\x{9F3B}\x{9F3C}\x{9F3D}' .
+'\x{9F3E}\x{9F3F}\x{9F40}\x{9F41}\x{9F42}\x{9F43}\x{9F44}\x{9F46}\x{9F47}' .
+'\x{9F48}\x{9F49}\x{9F4A}\x{9F4B}\x{9F4C}\x{9F4D}\x{9F4E}\x{9F4F}\x{9F50}' .
+'\x{9F51}\x{9F52}\x{9F54}\x{9F55}\x{9F56}\x{9F57}\x{9F58}\x{9F59}\x{9F5A}' .
+'\x{9F5B}\x{9F5C}\x{9F5D}\x{9F5E}\x{9F5F}\x{9F60}\x{9F61}\x{9F63}\x{9F64}' .
+'\x{9F65}\x{9F66}\x{9F67}\x{9F68}\x{9F69}\x{9F6A}\x{9F6B}\x{9F6C}\x{9F6E}' .
+'\x{9F6F}\x{9F70}\x{9F71}\x{9F72}\x{9F73}\x{9F74}\x{9F75}\x{9F76}\x{9F77}' .
+'\x{9F78}\x{9F79}\x{9F7A}\x{9F7B}\x{9F7C}\x{9F7D}\x{9F7E}\x{9F7F}\x{9F80}' .
+'\x{9F81}\x{9F82}\x{9F83}\x{9F84}\x{9F85}\x{9F86}\x{9F87}\x{9F88}\x{9F89}' .
+'\x{9F8A}\x{9F8B}\x{9F8C}\x{9F8D}\x{9F8E}\x{9F8F}\x{9F90}\x{9F91}\x{9F92}' .
+'\x{9F93}\x{9F94}\x{9F95}\x{9F96}\x{9F97}\x{9F98}\x{9F99}\x{9F9A}\x{9F9B}' .
+'\x{9F9C}\x{9F9D}\x{9F9E}\x{9F9F}\x{9FA0}\x{9FA2}\x{9FA4}\x{9FA5}]{1,20}$/iu');
diff --git a/library/vendor/Zend/Validate/Hostname/Cn.php b/library/vendor/Zend/Validate/Hostname/Cn.php
new file mode 100644
index 0000000..816e499
--- /dev/null
+++ b/library/vendor/Zend/Validate/Hostname/Cn.php
@@ -0,0 +1,2199 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Ressource file for chinese idn validation
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+return array(
+ 1 => '/^[\x{002d}0-9a-z\x{3447}\x{3473}\x{359E}\x{360E}\x{361A}\x{3918}\x{396E}\x{39CF}\x{39D0}' .
+'\x{39DF}\x{3A73}\x{3B4E}\x{3C6E}\x{3CE0}\x{4056}\x{415F}\x{4337}\x{43AC}' .
+'\x{43B1}\x{43DD}\x{44D6}\x{464C}\x{4661}\x{4723}\x{4729}\x{477C}\x{478D}' .
+'\x{4947}\x{497A}\x{497D}\x{4982}\x{4983}\x{4985}\x{4986}\x{499B}\x{499F}' .
+'\x{49B6}\x{49B7}\x{4C77}\x{4C9F}\x{4CA0}\x{4CA1}\x{4CA2}\x{4CA3}\x{4D13}' .
+'\x{4D14}\x{4D15}\x{4D16}\x{4D17}\x{4D18}\x{4D19}\x{4DAE}\x{4E00}\x{4E01}' .
+'\x{4E02}\x{4E03}\x{4E04}\x{4E05}\x{4E06}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' .
+'\x{4E0B}\x{4E0C}\x{4E0D}\x{4E0E}\x{4E0F}\x{4E10}\x{4E11}\x{4E13}\x{4E14}' .
+'\x{4E15}\x{4E16}\x{4E17}\x{4E18}\x{4E19}\x{4E1A}\x{4E1B}\x{4E1C}\x{4E1D}' .
+'\x{4E1E}\x{4E1F}\x{4E20}\x{4E21}\x{4E22}\x{4E23}\x{4E24}\x{4E25}\x{4E26}' .
+'\x{4E27}\x{4E28}\x{4E2A}\x{4E2B}\x{4E2C}\x{4E2D}\x{4E2E}\x{4E2F}\x{4E30}' .
+'\x{4E31}\x{4E32}\x{4E33}\x{4E34}\x{4E35}\x{4E36}\x{4E37}\x{4E38}\x{4E39}' .
+'\x{4E3A}\x{4E3B}\x{4E3C}\x{4E3D}\x{4E3E}\x{4E3F}\x{4E40}\x{4E41}\x{4E42}' .
+'\x{4E43}\x{4E44}\x{4E45}\x{4E46}\x{4E47}\x{4E48}\x{4E49}\x{4E4A}\x{4E4B}' .
+'\x{4E4C}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E50}\x{4E51}\x{4E52}\x{4E53}\x{4E54}' .
+'\x{4E56}\x{4E57}\x{4E58}\x{4E59}\x{4E5A}\x{4E5B}\x{4E5C}\x{4E5D}\x{4E5E}' .
+'\x{4E5F}\x{4E60}\x{4E61}\x{4E62}\x{4E63}\x{4E64}\x{4E65}\x{4E66}\x{4E67}' .
+'\x{4E69}\x{4E6A}\x{4E6B}\x{4E6C}\x{4E6D}\x{4E6E}\x{4E6F}\x{4E70}\x{4E71}' .
+'\x{4E72}\x{4E73}\x{4E74}\x{4E75}\x{4E76}\x{4E77}\x{4E78}\x{4E7A}\x{4E7B}' .
+'\x{4E7C}\x{4E7D}\x{4E7E}\x{4E7F}\x{4E80}\x{4E81}\x{4E82}\x{4E83}\x{4E84}' .
+'\x{4E85}\x{4E86}\x{4E87}\x{4E88}\x{4E89}\x{4E8B}\x{4E8C}\x{4E8D}\x{4E8E}' .
+'\x{4E8F}\x{4E90}\x{4E91}\x{4E92}\x{4E93}\x{4E94}\x{4E95}\x{4E97}\x{4E98}' .
+'\x{4E99}\x{4E9A}\x{4E9B}\x{4E9C}\x{4E9D}\x{4E9E}\x{4E9F}\x{4EA0}\x{4EA1}' .
+'\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA7}\x{4EA8}\x{4EA9}\x{4EAA}\x{4EAB}' .
+'\x{4EAC}\x{4EAD}\x{4EAE}\x{4EAF}\x{4EB0}\x{4EB1}\x{4EB2}\x{4EB3}\x{4EB4}' .
+'\x{4EB5}\x{4EB6}\x{4EB7}\x{4EB8}\x{4EB9}\x{4EBA}\x{4EBB}\x{4EBD}\x{4EBE}' .
+'\x{4EBF}\x{4EC0}\x{4EC1}\x{4EC2}\x{4EC3}\x{4EC4}\x{4EC5}\x{4EC6}\x{4EC7}' .
+'\x{4EC8}\x{4EC9}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED0}\x{4ED1}' .
+'\x{4ED2}\x{4ED3}\x{4ED4}\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDA}' .
+'\x{4EDB}\x{4EDC}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE0}\x{4EE1}\x{4EE2}\x{4EE3}' .
+'\x{4EE4}\x{4EE5}\x{4EE6}\x{4EE8}\x{4EE9}\x{4EEA}\x{4EEB}\x{4EEC}\x{4EEF}' .
+'\x{4EF0}\x{4EF1}\x{4EF2}\x{4EF3}\x{4EF4}\x{4EF5}\x{4EF6}\x{4EF7}\x{4EFB}' .
+'\x{4EFD}\x{4EFF}\x{4F00}\x{4F01}\x{4F02}\x{4F03}\x{4F04}\x{4F05}\x{4F06}' .
+'\x{4F08}\x{4F09}\x{4F0A}\x{4F0B}\x{4F0C}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}' .
+'\x{4F11}\x{4F12}\x{4F13}\x{4F14}\x{4F15}\x{4F17}\x{4F18}\x{4F19}\x{4F1A}' .
+'\x{4F1B}\x{4F1C}\x{4F1D}\x{4F1E}\x{4F1F}\x{4F20}\x{4F21}\x{4F22}\x{4F23}' .
+'\x{4F24}\x{4F25}\x{4F26}\x{4F27}\x{4F29}\x{4F2A}\x{4F2B}\x{4F2C}\x{4F2D}' .
+'\x{4F2E}\x{4F2F}\x{4F30}\x{4F32}\x{4F33}\x{4F34}\x{4F36}\x{4F38}\x{4F39}' .
+'\x{4F3A}\x{4F3B}\x{4F3C}\x{4F3D}\x{4F3E}\x{4F3F}\x{4F41}\x{4F42}\x{4F43}' .
+'\x{4F45}\x{4F46}\x{4F47}\x{4F48}\x{4F49}\x{4F4A}\x{4F4B}\x{4F4C}\x{4F4D}' .
+'\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}\x{4F52}\x{4F53}\x{4F54}\x{4F55}\x{4F56}' .
+'\x{4F57}\x{4F58}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}\x{4F5F}' .
+'\x{4F60}\x{4F61}\x{4F62}\x{4F63}\x{4F64}\x{4F65}\x{4F66}\x{4F67}\x{4F68}' .
+'\x{4F69}\x{4F6A}\x{4F6B}\x{4F6C}\x{4F6D}\x{4F6E}\x{4F6F}\x{4F70}\x{4F72}' .
+'\x{4F73}\x{4F74}\x{4F75}\x{4F76}\x{4F77}\x{4F78}\x{4F79}\x{4F7A}\x{4F7B}' .
+'\x{4F7C}\x{4F7D}\x{4F7E}\x{4F7F}\x{4F80}\x{4F81}\x{4F82}\x{4F83}\x{4F84}' .
+'\x{4F85}\x{4F86}\x{4F87}\x{4F88}\x{4F89}\x{4F8A}\x{4F8B}\x{4F8D}\x{4F8F}' .
+'\x{4F90}\x{4F91}\x{4F92}\x{4F93}\x{4F94}\x{4F95}\x{4F96}\x{4F97}\x{4F98}' .
+'\x{4F99}\x{4F9A}\x{4F9B}\x{4F9C}\x{4F9D}\x{4F9E}\x{4F9F}\x{4FA0}\x{4FA1}' .
+'\x{4FA3}\x{4FA4}\x{4FA5}\x{4FA6}\x{4FA7}\x{4FA8}\x{4FA9}\x{4FAA}\x{4FAB}' .
+'\x{4FAC}\x{4FAE}\x{4FAF}\x{4FB0}\x{4FB1}\x{4FB2}\x{4FB3}\x{4FB4}\x{4FB5}' .
+'\x{4FB6}\x{4FB7}\x{4FB8}\x{4FB9}\x{4FBA}\x{4FBB}\x{4FBC}\x{4FBE}\x{4FBF}' .
+'\x{4FC0}\x{4FC1}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FC5}\x{4FC7}\x{4FC9}\x{4FCA}' .
+'\x{4FCB}\x{4FCD}\x{4FCE}\x{4FCF}\x{4FD0}\x{4FD1}\x{4FD2}\x{4FD3}\x{4FD4}' .
+'\x{4FD5}\x{4FD6}\x{4FD7}\x{4FD8}\x{4FD9}\x{4FDA}\x{4FDB}\x{4FDC}\x{4FDD}' .
+'\x{4FDE}\x{4FDF}\x{4FE0}\x{4FE1}\x{4FE3}\x{4FE4}\x{4FE5}\x{4FE6}\x{4FE7}' .
+'\x{4FE8}\x{4FE9}\x{4FEA}\x{4FEB}\x{4FEC}\x{4FED}\x{4FEE}\x{4FEF}\x{4FF0}' .
+'\x{4FF1}\x{4FF2}\x{4FF3}\x{4FF4}\x{4FF5}\x{4FF6}\x{4FF7}\x{4FF8}\x{4FF9}' .
+'\x{4FFA}\x{4FFB}\x{4FFE}\x{4FFF}\x{5000}\x{5001}\x{5002}\x{5003}\x{5004}' .
+'\x{5005}\x{5006}\x{5007}\x{5008}\x{5009}\x{500A}\x{500B}\x{500C}\x{500D}' .
+'\x{500E}\x{500F}\x{5011}\x{5012}\x{5013}\x{5014}\x{5015}\x{5016}\x{5017}' .
+'\x{5018}\x{5019}\x{501A}\x{501B}\x{501C}\x{501D}\x{501E}\x{501F}\x{5020}' .
+'\x{5021}\x{5022}\x{5023}\x{5024}\x{5025}\x{5026}\x{5027}\x{5028}\x{5029}' .
+'\x{502A}\x{502B}\x{502C}\x{502D}\x{502E}\x{502F}\x{5030}\x{5031}\x{5032}' .
+'\x{5033}\x{5035}\x{5036}\x{5037}\x{5039}\x{503A}\x{503B}\x{503C}\x{503E}' .
+'\x{503F}\x{5040}\x{5041}\x{5043}\x{5044}\x{5045}\x{5046}\x{5047}\x{5048}' .
+'\x{5049}\x{504A}\x{504B}\x{504C}\x{504D}\x{504E}\x{504F}\x{5051}\x{5053}' .
+'\x{5054}\x{5055}\x{5056}\x{5057}\x{5059}\x{505A}\x{505B}\x{505C}\x{505D}' .
+'\x{505E}\x{505F}\x{5060}\x{5061}\x{5062}\x{5063}\x{5064}\x{5065}\x{5066}' .
+'\x{5067}\x{5068}\x{5069}\x{506A}\x{506B}\x{506C}\x{506D}\x{506E}\x{506F}' .
+'\x{5070}\x{5071}\x{5072}\x{5073}\x{5074}\x{5075}\x{5076}\x{5077}\x{5078}' .
+'\x{5079}\x{507A}\x{507B}\x{507D}\x{507E}\x{507F}\x{5080}\x{5082}\x{5083}' .
+'\x{5084}\x{5085}\x{5086}\x{5087}\x{5088}\x{5089}\x{508A}\x{508B}\x{508C}' .
+'\x{508D}\x{508E}\x{508F}\x{5090}\x{5091}\x{5092}\x{5094}\x{5095}\x{5096}' .
+'\x{5098}\x{5099}\x{509A}\x{509B}\x{509C}\x{509D}\x{509E}\x{50A2}\x{50A3}' .
+'\x{50A4}\x{50A5}\x{50A6}\x{50A7}\x{50A8}\x{50A9}\x{50AA}\x{50AB}\x{50AC}' .
+'\x{50AD}\x{50AE}\x{50AF}\x{50B0}\x{50B1}\x{50B2}\x{50B3}\x{50B4}\x{50B5}' .
+'\x{50B6}\x{50B7}\x{50B8}\x{50BA}\x{50BB}\x{50BC}\x{50BD}\x{50BE}\x{50BF}' .
+'\x{50C0}\x{50C1}\x{50C2}\x{50C4}\x{50C5}\x{50C6}\x{50C7}\x{50C8}\x{50C9}' .
+'\x{50CA}\x{50CB}\x{50CC}\x{50CD}\x{50CE}\x{50CF}\x{50D0}\x{50D1}\x{50D2}' .
+'\x{50D3}\x{50D4}\x{50D5}\x{50D6}\x{50D7}\x{50D9}\x{50DA}\x{50DB}\x{50DC}' .
+'\x{50DD}\x{50DE}\x{50E0}\x{50E3}\x{50E4}\x{50E5}\x{50E6}\x{50E7}\x{50E8}' .
+'\x{50E9}\x{50EA}\x{50EC}\x{50ED}\x{50EE}\x{50EF}\x{50F0}\x{50F1}\x{50F2}' .
+'\x{50F3}\x{50F5}\x{50F6}\x{50F8}\x{50F9}\x{50FA}\x{50FB}\x{50FC}\x{50FD}' .
+'\x{50FE}\x{50FF}\x{5100}\x{5101}\x{5102}\x{5103}\x{5104}\x{5105}\x{5106}' .
+'\x{5107}\x{5108}\x{5109}\x{510A}\x{510B}\x{510C}\x{510D}\x{510E}\x{510F}' .
+'\x{5110}\x{5111}\x{5112}\x{5113}\x{5114}\x{5115}\x{5116}\x{5117}\x{5118}' .
+'\x{5119}\x{511A}\x{511C}\x{511D}\x{511E}\x{511F}\x{5120}\x{5121}\x{5122}' .
+'\x{5123}\x{5124}\x{5125}\x{5126}\x{5127}\x{5129}\x{512A}\x{512C}\x{512D}' .
+'\x{512E}\x{512F}\x{5130}\x{5131}\x{5132}\x{5133}\x{5134}\x{5135}\x{5136}' .
+'\x{5137}\x{5138}\x{5139}\x{513A}\x{513B}\x{513C}\x{513D}\x{513E}\x{513F}' .
+'\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' .
+'\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5151}\x{5152}\x{5154}\x{5155}' .
+'\x{5156}\x{5157}\x{5159}\x{515A}\x{515B}\x{515C}\x{515D}\x{515E}\x{515F}' .
+'\x{5161}\x{5162}\x{5163}\x{5165}\x{5166}\x{5167}\x{5168}\x{5169}\x{516A}' .
+'\x{516B}\x{516C}\x{516D}\x{516E}\x{516F}\x{5170}\x{5171}\x{5173}\x{5174}' .
+'\x{5175}\x{5176}\x{5177}\x{5178}\x{5179}\x{517A}\x{517B}\x{517C}\x{517D}' .
+'\x{517F}\x{5180}\x{5181}\x{5182}\x{5185}\x{5186}\x{5187}\x{5188}\x{5189}' .
+'\x{518A}\x{518B}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}\x{5193}' .
+'\x{5194}\x{5195}\x{5196}\x{5197}\x{5198}\x{5199}\x{519A}\x{519B}\x{519C}' .
+'\x{519D}\x{519E}\x{519F}\x{51A0}\x{51A2}\x{51A4}\x{51A5}\x{51A6}\x{51A7}' .
+'\x{51A8}\x{51AA}\x{51AB}\x{51AC}\x{51AE}\x{51AF}\x{51B0}\x{51B1}\x{51B2}' .
+'\x{51B3}\x{51B5}\x{51B6}\x{51B7}\x{51B9}\x{51BB}\x{51BC}\x{51BD}\x{51BE}' .
+'\x{51BF}\x{51C0}\x{51C1}\x{51C3}\x{51C4}\x{51C5}\x{51C6}\x{51C7}\x{51C8}' .
+'\x{51C9}\x{51CA}\x{51CB}\x{51CC}\x{51CD}\x{51CE}\x{51CF}\x{51D0}\x{51D1}' .
+'\x{51D4}\x{51D5}\x{51D6}\x{51D7}\x{51D8}\x{51D9}\x{51DA}\x{51DB}\x{51DC}' .
+'\x{51DD}\x{51DE}\x{51E0}\x{51E1}\x{51E2}\x{51E3}\x{51E4}\x{51E5}\x{51E7}' .
+'\x{51E8}\x{51E9}\x{51EA}\x{51EB}\x{51ED}\x{51EF}\x{51F0}\x{51F1}\x{51F3}' .
+'\x{51F4}\x{51F5}\x{51F6}\x{51F7}\x{51F8}\x{51F9}\x{51FA}\x{51FB}\x{51FC}' .
+'\x{51FD}\x{51FE}\x{51FF}\x{5200}\x{5201}\x{5202}\x{5203}\x{5204}\x{5205}' .
+'\x{5206}\x{5207}\x{5208}\x{5209}\x{520A}\x{520B}\x{520C}\x{520D}\x{520E}' .
+'\x{520F}\x{5210}\x{5211}\x{5212}\x{5213}\x{5214}\x{5215}\x{5216}\x{5217}' .
+'\x{5218}\x{5219}\x{521A}\x{521B}\x{521C}\x{521D}\x{521E}\x{521F}\x{5220}' .
+'\x{5221}\x{5222}\x{5223}\x{5224}\x{5225}\x{5226}\x{5228}\x{5229}\x{522A}' .
+'\x{522B}\x{522C}\x{522D}\x{522E}\x{522F}\x{5230}\x{5231}\x{5232}\x{5233}' .
+'\x{5234}\x{5235}\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{523C}' .
+'\x{523D}\x{523E}\x{523F}\x{5240}\x{5241}\x{5242}\x{5243}\x{5244}\x{5245}' .
+'\x{5246}\x{5247}\x{5248}\x{5249}\x{524A}\x{524B}\x{524C}\x{524D}\x{524E}' .
+'\x{5250}\x{5251}\x{5252}\x{5254}\x{5255}\x{5256}\x{5257}\x{5258}\x{5259}' .
+'\x{525A}\x{525B}\x{525C}\x{525D}\x{525E}\x{525F}\x{5260}\x{5261}\x{5262}' .
+'\x{5263}\x{5264}\x{5265}\x{5267}\x{5268}\x{5269}\x{526A}\x{526B}\x{526C}' .
+'\x{526D}\x{526E}\x{526F}\x{5270}\x{5272}\x{5273}\x{5274}\x{5275}\x{5276}' .
+'\x{5277}\x{5278}\x{527A}\x{527B}\x{527C}\x{527D}\x{527E}\x{527F}\x{5280}' .
+'\x{5281}\x{5282}\x{5283}\x{5284}\x{5286}\x{5287}\x{5288}\x{5289}\x{528A}' .
+'\x{528B}\x{528C}\x{528D}\x{528F}\x{5290}\x{5291}\x{5292}\x{5293}\x{5294}' .
+'\x{5295}\x{5296}\x{5297}\x{5298}\x{5299}\x{529A}\x{529B}\x{529C}\x{529D}' .
+'\x{529E}\x{529F}\x{52A0}\x{52A1}\x{52A2}\x{52A3}\x{52A5}\x{52A6}\x{52A7}' .
+'\x{52A8}\x{52A9}\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52AE}\x{52AF}\x{52B0}' .
+'\x{52B1}\x{52B2}\x{52B3}\x{52B4}\x{52B5}\x{52B6}\x{52B7}\x{52B8}\x{52B9}' .
+'\x{52BA}\x{52BB}\x{52BC}\x{52BD}\x{52BE}\x{52BF}\x{52C0}\x{52C1}\x{52C2}' .
+'\x{52C3}\x{52C6}\x{52C7}\x{52C9}\x{52CA}\x{52CB}\x{52CD}\x{52CF}\x{52D0}' .
+'\x{52D2}\x{52D3}\x{52D5}\x{52D6}\x{52D7}\x{52D8}\x{52D9}\x{52DA}\x{52DB}' .
+'\x{52DC}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}\x{52E4}\x{52E6}' .
+'\x{52E7}\x{52E8}\x{52E9}\x{52EA}\x{52EB}\x{52EC}\x{52ED}\x{52EF}\x{52F0}' .
+'\x{52F1}\x{52F2}\x{52F3}\x{52F4}\x{52F5}\x{52F6}\x{52F7}\x{52F8}\x{52F9}' .
+'\x{52FA}\x{52FB}\x{52FC}\x{52FD}\x{52FE}\x{52FF}\x{5300}\x{5301}\x{5302}' .
+'\x{5305}\x{5306}\x{5307}\x{5308}\x{5309}\x{530A}\x{530B}\x{530C}\x{530D}' .
+'\x{530E}\x{530F}\x{5310}\x{5311}\x{5312}\x{5313}\x{5314}\x{5315}\x{5316}' .
+'\x{5317}\x{5319}\x{531A}\x{531C}\x{531D}\x{531F}\x{5320}\x{5321}\x{5322}' .
+'\x{5323}\x{5324}\x{5325}\x{5326}\x{5328}\x{532A}\x{532B}\x{532C}\x{532D}' .
+'\x{532E}\x{532F}\x{5330}\x{5331}\x{5333}\x{5334}\x{5337}\x{5339}\x{533A}' .
+'\x{533B}\x{533C}\x{533D}\x{533E}\x{533F}\x{5340}\x{5341}\x{5343}\x{5344}' .
+'\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}\x{534A}\x{534B}\x{534C}\x{534D}' .
+'\x{534E}\x{534F}\x{5350}\x{5351}\x{5352}\x{5353}\x{5354}\x{5355}\x{5356}' .
+'\x{5357}\x{5358}\x{5359}\x{535A}\x{535C}\x{535E}\x{535F}\x{5360}\x{5361}' .
+'\x{5362}\x{5363}\x{5364}\x{5365}\x{5366}\x{5367}\x{5369}\x{536B}\x{536C}' .
+'\x{536E}\x{536F}\x{5370}\x{5371}\x{5372}\x{5373}\x{5374}\x{5375}\x{5376}' .
+'\x{5377}\x{5378}\x{5379}\x{537A}\x{537B}\x{537C}\x{537D}\x{537E}\x{537F}' .
+'\x{5381}\x{5382}\x{5383}\x{5384}\x{5385}\x{5386}\x{5387}\x{5388}\x{5389}' .
+'\x{538A}\x{538B}\x{538C}\x{538D}\x{538E}\x{538F}\x{5390}\x{5391}\x{5392}' .
+'\x{5393}\x{5394}\x{5395}\x{5396}\x{5397}\x{5398}\x{5399}\x{539A}\x{539B}' .
+'\x{539C}\x{539D}\x{539E}\x{539F}\x{53A0}\x{53A2}\x{53A3}\x{53A4}\x{53A5}' .
+'\x{53A6}\x{53A7}\x{53A8}\x{53A9}\x{53AC}\x{53AD}\x{53AE}\x{53B0}\x{53B1}' .
+'\x{53B2}\x{53B3}\x{53B4}\x{53B5}\x{53B6}\x{53B7}\x{53B8}\x{53B9}\x{53BB}' .
+'\x{53BC}\x{53BD}\x{53BE}\x{53BF}\x{53C0}\x{53C1}\x{53C2}\x{53C3}\x{53C4}' .
+'\x{53C6}\x{53C7}\x{53C8}\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}' .
+'\x{53D0}\x{53D1}\x{53D2}\x{53D3}\x{53D4}\x{53D5}\x{53D6}\x{53D7}\x{53D8}' .
+'\x{53D9}\x{53DB}\x{53DC}\x{53DF}\x{53E0}\x{53E1}\x{53E2}\x{53E3}\x{53E4}' .
+'\x{53E5}\x{53E6}\x{53E8}\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}' .
+'\x{53EF}\x{53F0}\x{53F1}\x{53F2}\x{53F3}\x{53F4}\x{53F5}\x{53F6}\x{53F7}' .
+'\x{53F8}\x{53F9}\x{53FA}\x{53FB}\x{53FC}\x{53FD}\x{53FE}\x{5401}\x{5402}' .
+'\x{5403}\x{5404}\x{5405}\x{5406}\x{5407}\x{5408}\x{5409}\x{540A}\x{540B}' .
+'\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}\x{5411}\x{5412}\x{5413}\x{5414}' .
+'\x{5415}\x{5416}\x{5417}\x{5418}\x{5419}\x{541B}\x{541C}\x{541D}\x{541E}' .
+'\x{541F}\x{5420}\x{5421}\x{5423}\x{5424}\x{5425}\x{5426}\x{5427}\x{5428}' .
+'\x{5429}\x{542A}\x{542B}\x{542C}\x{542D}\x{542E}\x{542F}\x{5430}\x{5431}' .
+'\x{5432}\x{5433}\x{5434}\x{5435}\x{5436}\x{5437}\x{5438}\x{5439}\x{543A}' .
+'\x{543B}\x{543C}\x{543D}\x{543E}\x{543F}\x{5440}\x{5441}\x{5442}\x{5443}' .
+'\x{5444}\x{5445}\x{5446}\x{5447}\x{5448}\x{5449}\x{544A}\x{544B}\x{544D}' .
+'\x{544E}\x{544F}\x{5450}\x{5451}\x{5452}\x{5453}\x{5454}\x{5455}\x{5456}' .
+'\x{5457}\x{5458}\x{5459}\x{545A}\x{545B}\x{545C}\x{545E}\x{545F}\x{5460}' .
+'\x{5461}\x{5462}\x{5463}\x{5464}\x{5465}\x{5466}\x{5467}\x{5468}\x{546A}' .
+'\x{546B}\x{546C}\x{546D}\x{546E}\x{546F}\x{5470}\x{5471}\x{5472}\x{5473}' .
+'\x{5474}\x{5475}\x{5476}\x{5477}\x{5478}\x{5479}\x{547A}\x{547B}\x{547C}' .
+'\x{547D}\x{547E}\x{547F}\x{5480}\x{5481}\x{5482}\x{5483}\x{5484}\x{5485}' .
+'\x{5486}\x{5487}\x{5488}\x{5489}\x{548B}\x{548C}\x{548D}\x{548E}\x{548F}' .
+'\x{5490}\x{5491}\x{5492}\x{5493}\x{5494}\x{5495}\x{5496}\x{5497}\x{5498}' .
+'\x{5499}\x{549A}\x{549B}\x{549C}\x{549D}\x{549E}\x{549F}\x{54A0}\x{54A1}' .
+'\x{54A2}\x{54A3}\x{54A4}\x{54A5}\x{54A6}\x{54A7}\x{54A8}\x{54A9}\x{54AA}' .
+'\x{54AB}\x{54AC}\x{54AD}\x{54AE}\x{54AF}\x{54B0}\x{54B1}\x{54B2}\x{54B3}' .
+'\x{54B4}\x{54B6}\x{54B7}\x{54B8}\x{54B9}\x{54BA}\x{54BB}\x{54BC}\x{54BD}' .
+'\x{54BE}\x{54BF}\x{54C0}\x{54C1}\x{54C2}\x{54C3}\x{54C4}\x{54C5}\x{54C6}' .
+'\x{54C7}\x{54C8}\x{54C9}\x{54CA}\x{54CB}\x{54CC}\x{54CD}\x{54CE}\x{54CF}' .
+'\x{54D0}\x{54D1}\x{54D2}\x{54D3}\x{54D4}\x{54D5}\x{54D6}\x{54D7}\x{54D8}' .
+'\x{54D9}\x{54DA}\x{54DB}\x{54DC}\x{54DD}\x{54DE}\x{54DF}\x{54E0}\x{54E1}' .
+'\x{54E2}\x{54E3}\x{54E4}\x{54E5}\x{54E6}\x{54E7}\x{54E8}\x{54E9}\x{54EA}' .
+'\x{54EB}\x{54EC}\x{54ED}\x{54EE}\x{54EF}\x{54F0}\x{54F1}\x{54F2}\x{54F3}' .
+'\x{54F4}\x{54F5}\x{54F7}\x{54F8}\x{54F9}\x{54FA}\x{54FB}\x{54FC}\x{54FD}' .
+'\x{54FE}\x{54FF}\x{5500}\x{5501}\x{5502}\x{5503}\x{5504}\x{5505}\x{5506}' .
+'\x{5507}\x{5508}\x{5509}\x{550A}\x{550B}\x{550C}\x{550D}\x{550E}\x{550F}' .
+'\x{5510}\x{5511}\x{5512}\x{5513}\x{5514}\x{5516}\x{5517}\x{551A}\x{551B}' .
+'\x{551C}\x{551D}\x{551E}\x{551F}\x{5520}\x{5521}\x{5522}\x{5523}\x{5524}' .
+'\x{5525}\x{5526}\x{5527}\x{5528}\x{5529}\x{552A}\x{552B}\x{552C}\x{552D}' .
+'\x{552E}\x{552F}\x{5530}\x{5531}\x{5532}\x{5533}\x{5534}\x{5535}\x{5536}' .
+'\x{5537}\x{5538}\x{5539}\x{553A}\x{553B}\x{553C}\x{553D}\x{553E}\x{553F}' .
+'\x{5540}\x{5541}\x{5542}\x{5543}\x{5544}\x{5545}\x{5546}\x{5548}\x{5549}' .
+'\x{554A}\x{554B}\x{554C}\x{554D}\x{554E}\x{554F}\x{5550}\x{5551}\x{5552}' .
+'\x{5553}\x{5554}\x{5555}\x{5556}\x{5557}\x{5558}\x{5559}\x{555A}\x{555B}' .
+'\x{555C}\x{555D}\x{555E}\x{555F}\x{5561}\x{5562}\x{5563}\x{5564}\x{5565}' .
+'\x{5566}\x{5567}\x{5568}\x{5569}\x{556A}\x{556B}\x{556C}\x{556D}\x{556E}' .
+'\x{556F}\x{5570}\x{5571}\x{5572}\x{5573}\x{5574}\x{5575}\x{5576}\x{5577}' .
+'\x{5578}\x{5579}\x{557B}\x{557C}\x{557D}\x{557E}\x{557F}\x{5580}\x{5581}' .
+'\x{5582}\x{5583}\x{5584}\x{5585}\x{5586}\x{5587}\x{5588}\x{5589}\x{558A}' .
+'\x{558B}\x{558C}\x{558D}\x{558E}\x{558F}\x{5590}\x{5591}\x{5592}\x{5593}' .
+'\x{5594}\x{5595}\x{5596}\x{5597}\x{5598}\x{5599}\x{559A}\x{559B}\x{559C}' .
+'\x{559D}\x{559E}\x{559F}\x{55A0}\x{55A1}\x{55A2}\x{55A3}\x{55A4}\x{55A5}' .
+'\x{55A6}\x{55A7}\x{55A8}\x{55A9}\x{55AA}\x{55AB}\x{55AC}\x{55AD}\x{55AE}' .
+'\x{55AF}\x{55B0}\x{55B1}\x{55B2}\x{55B3}\x{55B4}\x{55B5}\x{55B6}\x{55B7}' .
+'\x{55B8}\x{55B9}\x{55BA}\x{55BB}\x{55BC}\x{55BD}\x{55BE}\x{55BF}\x{55C0}' .
+'\x{55C1}\x{55C2}\x{55C3}\x{55C4}\x{55C5}\x{55C6}\x{55C7}\x{55C8}\x{55C9}' .
+'\x{55CA}\x{55CB}\x{55CC}\x{55CD}\x{55CE}\x{55CF}\x{55D0}\x{55D1}\x{55D2}' .
+'\x{55D3}\x{55D4}\x{55D5}\x{55D6}\x{55D7}\x{55D8}\x{55D9}\x{55DA}\x{55DB}' .
+'\x{55DC}\x{55DD}\x{55DE}\x{55DF}\x{55E1}\x{55E2}\x{55E3}\x{55E4}\x{55E5}' .
+'\x{55E6}\x{55E7}\x{55E8}\x{55E9}\x{55EA}\x{55EB}\x{55EC}\x{55ED}\x{55EE}' .
+'\x{55EF}\x{55F0}\x{55F1}\x{55F2}\x{55F3}\x{55F4}\x{55F5}\x{55F6}\x{55F7}' .
+'\x{55F9}\x{55FA}\x{55FB}\x{55FC}\x{55FD}\x{55FE}\x{55FF}\x{5600}\x{5601}' .
+'\x{5602}\x{5603}\x{5604}\x{5606}\x{5607}\x{5608}\x{5609}\x{560C}\x{560D}' .
+'\x{560E}\x{560F}\x{5610}\x{5611}\x{5612}\x{5613}\x{5614}\x{5615}\x{5616}' .
+'\x{5617}\x{5618}\x{5619}\x{561A}\x{561B}\x{561C}\x{561D}\x{561E}\x{561F}' .
+'\x{5621}\x{5622}\x{5623}\x{5624}\x{5625}\x{5626}\x{5627}\x{5628}\x{5629}' .
+'\x{562A}\x{562C}\x{562D}\x{562E}\x{562F}\x{5630}\x{5631}\x{5632}\x{5633}' .
+'\x{5634}\x{5635}\x{5636}\x{5638}\x{5639}\x{563A}\x{563B}\x{563D}\x{563E}' .
+'\x{563F}\x{5640}\x{5641}\x{5642}\x{5643}\x{5645}\x{5646}\x{5647}\x{5648}' .
+'\x{5649}\x{564A}\x{564C}\x{564D}\x{564E}\x{564F}\x{5650}\x{5652}\x{5653}' .
+'\x{5654}\x{5655}\x{5657}\x{5658}\x{5659}\x{565A}\x{565B}\x{565C}\x{565D}' .
+'\x{565E}\x{5660}\x{5662}\x{5663}\x{5664}\x{5665}\x{5666}\x{5667}\x{5668}' .
+'\x{5669}\x{566A}\x{566B}\x{566C}\x{566D}\x{566E}\x{566F}\x{5670}\x{5671}' .
+'\x{5672}\x{5673}\x{5674}\x{5676}\x{5677}\x{5678}\x{5679}\x{567A}\x{567B}' .
+'\x{567C}\x{567E}\x{567F}\x{5680}\x{5681}\x{5682}\x{5683}\x{5684}\x{5685}' .
+'\x{5686}\x{5687}\x{568A}\x{568C}\x{568D}\x{568E}\x{568F}\x{5690}\x{5691}' .
+'\x{5692}\x{5693}\x{5694}\x{5695}\x{5697}\x{5698}\x{5699}\x{569A}\x{569B}' .
+'\x{569C}\x{569D}\x{569F}\x{56A0}\x{56A1}\x{56A3}\x{56A4}\x{56A5}\x{56A6}' .
+'\x{56A7}\x{56A8}\x{56A9}\x{56AA}\x{56AB}\x{56AC}\x{56AD}\x{56AE}\x{56AF}' .
+'\x{56B0}\x{56B1}\x{56B2}\x{56B3}\x{56B4}\x{56B5}\x{56B6}\x{56B7}\x{56B8}' .
+'\x{56B9}\x{56BB}\x{56BC}\x{56BD}\x{56BE}\x{56BF}\x{56C0}\x{56C1}\x{56C2}' .
+'\x{56C3}\x{56C4}\x{56C5}\x{56C6}\x{56C7}\x{56C8}\x{56C9}\x{56CA}\x{56CB}' .
+'\x{56CC}\x{56CD}\x{56CE}\x{56D0}\x{56D1}\x{56D2}\x{56D3}\x{56D4}\x{56D5}' .
+'\x{56D6}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DC}\x{56DD}\x{56DE}\x{56DF}' .
+'\x{56E0}\x{56E1}\x{56E2}\x{56E3}\x{56E4}\x{56E5}\x{56E7}\x{56E8}\x{56E9}' .
+'\x{56EA}\x{56EB}\x{56EC}\x{56ED}\x{56EE}\x{56EF}\x{56F0}\x{56F1}\x{56F2}' .
+'\x{56F3}\x{56F4}\x{56F5}\x{56F7}\x{56F9}\x{56FA}\x{56FD}\x{56FE}\x{56FF}' .
+'\x{5700}\x{5701}\x{5702}\x{5703}\x{5704}\x{5706}\x{5707}\x{5708}\x{5709}' .
+'\x{570A}\x{570B}\x{570C}\x{570D}\x{570E}\x{570F}\x{5710}\x{5712}\x{5713}' .
+'\x{5714}\x{5715}\x{5716}\x{5718}\x{5719}\x{571A}\x{571B}\x{571C}\x{571D}' .
+'\x{571E}\x{571F}\x{5720}\x{5722}\x{5723}\x{5725}\x{5726}\x{5727}\x{5728}' .
+'\x{5729}\x{572A}\x{572B}\x{572C}\x{572D}\x{572E}\x{572F}\x{5730}\x{5731}' .
+'\x{5732}\x{5733}\x{5734}\x{5735}\x{5736}\x{5737}\x{5738}\x{5739}\x{573A}' .
+'\x{573B}\x{573C}\x{573E}\x{573F}\x{5740}\x{5741}\x{5742}\x{5744}\x{5745}' .
+'\x{5746}\x{5747}\x{5749}\x{574A}\x{574B}\x{574C}\x{574D}\x{574E}\x{574F}' .
+'\x{5750}\x{5751}\x{5752}\x{5753}\x{5754}\x{5757}\x{5759}\x{575A}\x{575B}' .
+'\x{575C}\x{575D}\x{575E}\x{575F}\x{5760}\x{5761}\x{5762}\x{5764}\x{5765}' .
+'\x{5766}\x{5767}\x{5768}\x{5769}\x{576A}\x{576B}\x{576C}\x{576D}\x{576F}' .
+'\x{5770}\x{5771}\x{5772}\x{5773}\x{5774}\x{5775}\x{5776}\x{5777}\x{5779}' .
+'\x{577A}\x{577B}\x{577C}\x{577D}\x{577E}\x{577F}\x{5780}\x{5782}\x{5783}' .
+'\x{5784}\x{5785}\x{5786}\x{5788}\x{5789}\x{578A}\x{578B}\x{578C}\x{578D}' .
+'\x{578E}\x{578F}\x{5790}\x{5791}\x{5792}\x{5793}\x{5794}\x{5795}\x{5797}' .
+'\x{5798}\x{5799}\x{579A}\x{579B}\x{579C}\x{579D}\x{579E}\x{579F}\x{57A0}' .
+'\x{57A1}\x{57A2}\x{57A3}\x{57A4}\x{57A5}\x{57A6}\x{57A7}\x{57A9}\x{57AA}' .
+'\x{57AB}\x{57AC}\x{57AD}\x{57AE}\x{57AF}\x{57B0}\x{57B1}\x{57B2}\x{57B3}' .
+'\x{57B4}\x{57B5}\x{57B6}\x{57B7}\x{57B8}\x{57B9}\x{57BA}\x{57BB}\x{57BC}' .
+'\x{57BD}\x{57BE}\x{57BF}\x{57C0}\x{57C1}\x{57C2}\x{57C3}\x{57C4}\x{57C5}' .
+'\x{57C6}\x{57C7}\x{57C8}\x{57C9}\x{57CB}\x{57CC}\x{57CD}\x{57CE}\x{57CF}' .
+'\x{57D0}\x{57D2}\x{57D3}\x{57D4}\x{57D5}\x{57D6}\x{57D8}\x{57D9}\x{57DA}' .
+'\x{57DC}\x{57DD}\x{57DF}\x{57E0}\x{57E1}\x{57E2}\x{57E3}\x{57E4}\x{57E5}' .
+'\x{57E6}\x{57E7}\x{57E8}\x{57E9}\x{57EA}\x{57EB}\x{57EC}\x{57ED}\x{57EE}' .
+'\x{57EF}\x{57F0}\x{57F1}\x{57F2}\x{57F3}\x{57F4}\x{57F5}\x{57F6}\x{57F7}' .
+'\x{57F8}\x{57F9}\x{57FA}\x{57FB}\x{57FC}\x{57FD}\x{57FE}\x{57FF}\x{5800}' .
+'\x{5801}\x{5802}\x{5803}\x{5804}\x{5805}\x{5806}\x{5807}\x{5808}\x{5809}' .
+'\x{580A}\x{580B}\x{580C}\x{580D}\x{580E}\x{580F}\x{5810}\x{5811}\x{5812}' .
+'\x{5813}\x{5814}\x{5815}\x{5816}\x{5819}\x{581A}\x{581B}\x{581C}\x{581D}' .
+'\x{581E}\x{581F}\x{5820}\x{5821}\x{5822}\x{5823}\x{5824}\x{5825}\x{5826}' .
+'\x{5827}\x{5828}\x{5829}\x{582A}\x{582B}\x{582C}\x{582D}\x{582E}\x{582F}' .
+'\x{5830}\x{5831}\x{5832}\x{5833}\x{5834}\x{5835}\x{5836}\x{5837}\x{5838}' .
+'\x{5839}\x{583A}\x{583B}\x{583C}\x{583D}\x{583E}\x{583F}\x{5840}\x{5842}' .
+'\x{5843}\x{5844}\x{5845}\x{5846}\x{5847}\x{5848}\x{5849}\x{584A}\x{584B}' .
+'\x{584C}\x{584D}\x{584E}\x{584F}\x{5851}\x{5852}\x{5853}\x{5854}\x{5855}' .
+'\x{5857}\x{5858}\x{5859}\x{585A}\x{585B}\x{585C}\x{585D}\x{585E}\x{585F}' .
+'\x{5861}\x{5862}\x{5863}\x{5864}\x{5865}\x{5868}\x{5869}\x{586A}\x{586B}' .
+'\x{586C}\x{586D}\x{586E}\x{586F}\x{5870}\x{5871}\x{5872}\x{5873}\x{5874}' .
+'\x{5875}\x{5876}\x{5878}\x{5879}\x{587A}\x{587B}\x{587C}\x{587D}\x{587E}' .
+'\x{587F}\x{5880}\x{5881}\x{5882}\x{5883}\x{5884}\x{5885}\x{5886}\x{5887}' .
+'\x{5888}\x{5889}\x{588A}\x{588B}\x{588C}\x{588D}\x{588E}\x{588F}\x{5890}' .
+'\x{5891}\x{5892}\x{5893}\x{5894}\x{5896}\x{5897}\x{5898}\x{5899}\x{589A}' .
+'\x{589B}\x{589C}\x{589D}\x{589E}\x{589F}\x{58A0}\x{58A1}\x{58A2}\x{58A3}' .
+'\x{58A4}\x{58A5}\x{58A6}\x{58A7}\x{58A8}\x{58A9}\x{58AB}\x{58AC}\x{58AD}' .
+'\x{58AE}\x{58AF}\x{58B0}\x{58B1}\x{58B2}\x{58B3}\x{58B4}\x{58B7}\x{58B8}' .
+'\x{58B9}\x{58BA}\x{58BB}\x{58BC}\x{58BD}\x{58BE}\x{58BF}\x{58C1}\x{58C2}' .
+'\x{58C5}\x{58C6}\x{58C7}\x{58C8}\x{58C9}\x{58CA}\x{58CB}\x{58CE}\x{58CF}' .
+'\x{58D1}\x{58D2}\x{58D3}\x{58D4}\x{58D5}\x{58D6}\x{58D7}\x{58D8}\x{58D9}' .
+'\x{58DA}\x{58DB}\x{58DD}\x{58DE}\x{58DF}\x{58E0}\x{58E2}\x{58E3}\x{58E4}' .
+'\x{58E5}\x{58E7}\x{58E8}\x{58E9}\x{58EA}\x{58EB}\x{58EC}\x{58ED}\x{58EE}' .
+'\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F3}\x{58F4}\x{58F6}\x{58F7}\x{58F8}' .
+'\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{58FE}\x{58FF}\x{5900}\x{5902}' .
+'\x{5903}\x{5904}\x{5906}\x{5907}\x{5909}\x{590A}\x{590B}\x{590C}\x{590D}' .
+'\x{590E}\x{590F}\x{5910}\x{5912}\x{5914}\x{5915}\x{5916}\x{5917}\x{5918}' .
+'\x{5919}\x{591A}\x{591B}\x{591C}\x{591D}\x{591E}\x{591F}\x{5920}\x{5921}' .
+'\x{5922}\x{5924}\x{5925}\x{5926}\x{5927}\x{5928}\x{5929}\x{592A}\x{592B}' .
+'\x{592C}\x{592D}\x{592E}\x{592F}\x{5930}\x{5931}\x{5932}\x{5934}\x{5935}' .
+'\x{5937}\x{5938}\x{5939}\x{593A}\x{593B}\x{593C}\x{593D}\x{593E}\x{593F}' .
+'\x{5940}\x{5941}\x{5942}\x{5943}\x{5944}\x{5945}\x{5946}\x{5947}\x{5948}' .
+'\x{5949}\x{594A}\x{594B}\x{594C}\x{594D}\x{594E}\x{594F}\x{5950}\x{5951}' .
+'\x{5952}\x{5953}\x{5954}\x{5955}\x{5956}\x{5957}\x{5958}\x{595A}\x{595C}' .
+'\x{595D}\x{595E}\x{595F}\x{5960}\x{5961}\x{5962}\x{5963}\x{5964}\x{5965}' .
+'\x{5966}\x{5967}\x{5968}\x{5969}\x{596A}\x{596B}\x{596C}\x{596D}\x{596E}' .
+'\x{596F}\x{5970}\x{5971}\x{5972}\x{5973}\x{5974}\x{5975}\x{5976}\x{5977}' .
+'\x{5978}\x{5979}\x{597A}\x{597B}\x{597C}\x{597D}\x{597E}\x{597F}\x{5980}' .
+'\x{5981}\x{5982}\x{5983}\x{5984}\x{5985}\x{5986}\x{5987}\x{5988}\x{5989}' .
+'\x{598A}\x{598B}\x{598C}\x{598D}\x{598E}\x{598F}\x{5990}\x{5991}\x{5992}' .
+'\x{5993}\x{5994}\x{5995}\x{5996}\x{5997}\x{5998}\x{5999}\x{599A}\x{599C}' .
+'\x{599D}\x{599E}\x{599F}\x{59A0}\x{59A1}\x{59A2}\x{59A3}\x{59A4}\x{59A5}' .
+'\x{59A6}\x{59A7}\x{59A8}\x{59A9}\x{59AA}\x{59AB}\x{59AC}\x{59AD}\x{59AE}' .
+'\x{59AF}\x{59B0}\x{59B1}\x{59B2}\x{59B3}\x{59B4}\x{59B5}\x{59B6}\x{59B8}' .
+'\x{59B9}\x{59BA}\x{59BB}\x{59BC}\x{59BD}\x{59BE}\x{59BF}\x{59C0}\x{59C1}' .
+'\x{59C2}\x{59C3}\x{59C4}\x{59C5}\x{59C6}\x{59C7}\x{59C8}\x{59C9}\x{59CA}' .
+'\x{59CB}\x{59CC}\x{59CD}\x{59CE}\x{59CF}\x{59D0}\x{59D1}\x{59D2}\x{59D3}' .
+'\x{59D4}\x{59D5}\x{59D6}\x{59D7}\x{59D8}\x{59D9}\x{59DA}\x{59DB}\x{59DC}' .
+'\x{59DD}\x{59DE}\x{59DF}\x{59E0}\x{59E1}\x{59E2}\x{59E3}\x{59E4}\x{59E5}' .
+'\x{59E6}\x{59E8}\x{59E9}\x{59EA}\x{59EB}\x{59EC}\x{59ED}\x{59EE}\x{59EF}' .
+'\x{59F0}\x{59F1}\x{59F2}\x{59F3}\x{59F4}\x{59F5}\x{59F6}\x{59F7}\x{59F8}' .
+'\x{59F9}\x{59FA}\x{59FB}\x{59FC}\x{59FD}\x{59FE}\x{59FF}\x{5A00}\x{5A01}' .
+'\x{5A02}\x{5A03}\x{5A04}\x{5A05}\x{5A06}\x{5A07}\x{5A08}\x{5A09}\x{5A0A}' .
+'\x{5A0B}\x{5A0C}\x{5A0D}\x{5A0E}\x{5A0F}\x{5A10}\x{5A11}\x{5A12}\x{5A13}' .
+'\x{5A14}\x{5A15}\x{5A16}\x{5A17}\x{5A18}\x{5A19}\x{5A1A}\x{5A1B}\x{5A1C}' .
+'\x{5A1D}\x{5A1E}\x{5A1F}\x{5A20}\x{5A21}\x{5A22}\x{5A23}\x{5A25}\x{5A27}' .
+'\x{5A28}\x{5A29}\x{5A2A}\x{5A2B}\x{5A2D}\x{5A2E}\x{5A2F}\x{5A31}\x{5A32}' .
+'\x{5A33}\x{5A34}\x{5A35}\x{5A36}\x{5A37}\x{5A38}\x{5A39}\x{5A3A}\x{5A3B}' .
+'\x{5A3C}\x{5A3D}\x{5A3E}\x{5A3F}\x{5A40}\x{5A41}\x{5A42}\x{5A43}\x{5A44}' .
+'\x{5A45}\x{5A46}\x{5A47}\x{5A48}\x{5A49}\x{5A4A}\x{5A4B}\x{5A4C}\x{5A4D}' .
+'\x{5A4E}\x{5A4F}\x{5A50}\x{5A51}\x{5A52}\x{5A53}\x{5A55}\x{5A56}\x{5A57}' .
+'\x{5A58}\x{5A5A}\x{5A5B}\x{5A5C}\x{5A5D}\x{5A5E}\x{5A5F}\x{5A60}\x{5A61}' .
+'\x{5A62}\x{5A63}\x{5A64}\x{5A65}\x{5A66}\x{5A67}\x{5A68}\x{5A69}\x{5A6A}' .
+'\x{5A6B}\x{5A6C}\x{5A6D}\x{5A6E}\x{5A70}\x{5A72}\x{5A73}\x{5A74}\x{5A75}' .
+'\x{5A76}\x{5A77}\x{5A78}\x{5A79}\x{5A7A}\x{5A7B}\x{5A7C}\x{5A7D}\x{5A7E}' .
+'\x{5A7F}\x{5A80}\x{5A81}\x{5A82}\x{5A83}\x{5A84}\x{5A85}\x{5A86}\x{5A88}' .
+'\x{5A89}\x{5A8A}\x{5A8B}\x{5A8C}\x{5A8E}\x{5A8F}\x{5A90}\x{5A91}\x{5A92}' .
+'\x{5A93}\x{5A94}\x{5A95}\x{5A96}\x{5A97}\x{5A98}\x{5A99}\x{5A9A}\x{5A9B}' .
+'\x{5A9C}\x{5A9D}\x{5A9E}\x{5A9F}\x{5AA0}\x{5AA1}\x{5AA2}\x{5AA3}\x{5AA4}' .
+'\x{5AA5}\x{5AA6}\x{5AA7}\x{5AA8}\x{5AA9}\x{5AAA}\x{5AAC}\x{5AAD}\x{5AAE}' .
+'\x{5AAF}\x{5AB0}\x{5AB1}\x{5AB2}\x{5AB3}\x{5AB4}\x{5AB5}\x{5AB6}\x{5AB7}' .
+'\x{5AB8}\x{5AB9}\x{5ABA}\x{5ABB}\x{5ABC}\x{5ABD}\x{5ABE}\x{5ABF}\x{5AC0}' .
+'\x{5AC1}\x{5AC2}\x{5AC3}\x{5AC4}\x{5AC5}\x{5AC6}\x{5AC7}\x{5AC8}\x{5AC9}' .
+'\x{5ACA}\x{5ACB}\x{5ACC}\x{5ACD}\x{5ACE}\x{5ACF}\x{5AD1}\x{5AD2}\x{5AD4}' .
+'\x{5AD5}\x{5AD6}\x{5AD7}\x{5AD8}\x{5AD9}\x{5ADA}\x{5ADB}\x{5ADC}\x{5ADD}' .
+'\x{5ADE}\x{5ADF}\x{5AE0}\x{5AE1}\x{5AE2}\x{5AE3}\x{5AE4}\x{5AE5}\x{5AE6}' .
+'\x{5AE7}\x{5AE8}\x{5AE9}\x{5AEA}\x{5AEB}\x{5AEC}\x{5AED}\x{5AEE}\x{5AF1}' .
+'\x{5AF2}\x{5AF3}\x{5AF4}\x{5AF5}\x{5AF6}\x{5AF7}\x{5AF8}\x{5AF9}\x{5AFA}' .
+'\x{5AFB}\x{5AFC}\x{5AFD}\x{5AFE}\x{5AFF}\x{5B00}\x{5B01}\x{5B02}\x{5B03}' .
+'\x{5B04}\x{5B05}\x{5B06}\x{5B07}\x{5B08}\x{5B09}\x{5B0B}\x{5B0C}\x{5B0E}' .
+'\x{5B0F}\x{5B10}\x{5B11}\x{5B12}\x{5B13}\x{5B14}\x{5B15}\x{5B16}\x{5B17}' .
+'\x{5B18}\x{5B19}\x{5B1A}\x{5B1B}\x{5B1C}\x{5B1D}\x{5B1E}\x{5B1F}\x{5B20}' .
+'\x{5B21}\x{5B22}\x{5B23}\x{5B24}\x{5B25}\x{5B26}\x{5B27}\x{5B28}\x{5B29}' .
+'\x{5B2A}\x{5B2B}\x{5B2C}\x{5B2D}\x{5B2E}\x{5B2F}\x{5B30}\x{5B31}\x{5B32}' .
+'\x{5B33}\x{5B34}\x{5B35}\x{5B36}\x{5B37}\x{5B38}\x{5B3A}\x{5B3B}\x{5B3C}' .
+'\x{5B3D}\x{5B3E}\x{5B3F}\x{5B40}\x{5B41}\x{5B42}\x{5B43}\x{5B44}\x{5B45}' .
+'\x{5B47}\x{5B48}\x{5B49}\x{5B4A}\x{5B4B}\x{5B4C}\x{5B4D}\x{5B4E}\x{5B50}' .
+'\x{5B51}\x{5B53}\x{5B54}\x{5B55}\x{5B56}\x{5B57}\x{5B58}\x{5B59}\x{5B5A}' .
+'\x{5B5B}\x{5B5C}\x{5B5D}\x{5B5E}\x{5B5F}\x{5B62}\x{5B63}\x{5B64}\x{5B65}' .
+'\x{5B66}\x{5B67}\x{5B68}\x{5B69}\x{5B6A}\x{5B6B}\x{5B6C}\x{5B6D}\x{5B6E}' .
+'\x{5B70}\x{5B71}\x{5B72}\x{5B73}\x{5B74}\x{5B75}\x{5B76}\x{5B77}\x{5B78}' .
+'\x{5B7A}\x{5B7B}\x{5B7C}\x{5B7D}\x{5B7F}\x{5B80}\x{5B81}\x{5B82}\x{5B83}' .
+'\x{5B84}\x{5B85}\x{5B87}\x{5B88}\x{5B89}\x{5B8A}\x{5B8B}\x{5B8C}\x{5B8D}' .
+'\x{5B8E}\x{5B8F}\x{5B91}\x{5B92}\x{5B93}\x{5B94}\x{5B95}\x{5B96}\x{5B97}' .
+'\x{5B98}\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9E}\x{5B9F}\x{5BA0}' .
+'\x{5BA1}\x{5BA2}\x{5BA3}\x{5BA4}\x{5BA5}\x{5BA6}\x{5BA7}\x{5BA8}\x{5BAA}' .
+'\x{5BAB}\x{5BAC}\x{5BAD}\x{5BAE}\x{5BAF}\x{5BB0}\x{5BB1}\x{5BB3}\x{5BB4}' .
+'\x{5BB5}\x{5BB6}\x{5BB8}\x{5BB9}\x{5BBA}\x{5BBB}\x{5BBD}\x{5BBE}\x{5BBF}' .
+'\x{5BC0}\x{5BC1}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BCA}' .
+'\x{5BCB}\x{5BCC}\x{5BCD}\x{5BCE}\x{5BCF}\x{5BD0}\x{5BD1}\x{5BD2}\x{5BD3}' .
+'\x{5BD4}\x{5BD5}\x{5BD6}\x{5BD8}\x{5BD9}\x{5BDB}\x{5BDC}\x{5BDD}\x{5BDE}' .
+'\x{5BDF}\x{5BE0}\x{5BE1}\x{5BE2}\x{5BE3}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}' .
+'\x{5BE8}\x{5BE9}\x{5BEA}\x{5BEB}\x{5BEC}\x{5BED}\x{5BEE}\x{5BEF}\x{5BF0}' .
+'\x{5BF1}\x{5BF2}\x{5BF3}\x{5BF4}\x{5BF5}\x{5BF6}\x{5BF7}\x{5BF8}\x{5BF9}' .
+'\x{5BFA}\x{5BFB}\x{5BFC}\x{5BFD}\x{5BFF}\x{5C01}\x{5C03}\x{5C04}\x{5C05}' .
+'\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}\x{5C0B}\x{5C0C}\x{5C0D}\x{5C0E}' .
+'\x{5C0F}\x{5C10}\x{5C11}\x{5C12}\x{5C13}\x{5C14}\x{5C15}\x{5C16}\x{5C17}' .
+'\x{5C18}\x{5C19}\x{5C1A}\x{5C1C}\x{5C1D}\x{5C1E}\x{5C1F}\x{5C20}\x{5C21}' .
+'\x{5C22}\x{5C24}\x{5C25}\x{5C27}\x{5C28}\x{5C2A}\x{5C2B}\x{5C2C}\x{5C2D}' .
+'\x{5C2E}\x{5C2F}\x{5C30}\x{5C31}\x{5C32}\x{5C33}\x{5C34}\x{5C35}\x{5C37}' .
+'\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}' .
+'\x{5C41}\x{5C42}\x{5C43}\x{5C44}\x{5C45}\x{5C46}\x{5C47}\x{5C48}\x{5C49}' .
+'\x{5C4A}\x{5C4B}\x{5C4C}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C52}' .
+'\x{5C53}\x{5C54}\x{5C55}\x{5C56}\x{5C57}\x{5C58}\x{5C59}\x{5C5B}\x{5C5C}' .
+'\x{5C5D}\x{5C5E}\x{5C5F}\x{5C60}\x{5C61}\x{5C62}\x{5C63}\x{5C64}\x{5C65}' .
+'\x{5C66}\x{5C67}\x{5C68}\x{5C69}\x{5C6A}\x{5C6B}\x{5C6C}\x{5C6D}\x{5C6E}' .
+'\x{5C6F}\x{5C70}\x{5C71}\x{5C72}\x{5C73}\x{5C74}\x{5C75}\x{5C76}\x{5C77}' .
+'\x{5C78}\x{5C79}\x{5C7A}\x{5C7B}\x{5C7C}\x{5C7D}\x{5C7E}\x{5C7F}\x{5C80}' .
+'\x{5C81}\x{5C82}\x{5C83}\x{5C84}\x{5C86}\x{5C87}\x{5C88}\x{5C89}\x{5C8A}' .
+'\x{5C8B}\x{5C8C}\x{5C8D}\x{5C8E}\x{5C8F}\x{5C90}\x{5C91}\x{5C92}\x{5C93}' .
+'\x{5C94}\x{5C95}\x{5C96}\x{5C97}\x{5C98}\x{5C99}\x{5C9A}\x{5C9B}\x{5C9C}' .
+'\x{5C9D}\x{5C9E}\x{5C9F}\x{5CA0}\x{5CA1}\x{5CA2}\x{5CA3}\x{5CA4}\x{5CA5}' .
+'\x{5CA6}\x{5CA7}\x{5CA8}\x{5CA9}\x{5CAA}\x{5CAB}\x{5CAC}\x{5CAD}\x{5CAE}' .
+'\x{5CAF}\x{5CB0}\x{5CB1}\x{5CB2}\x{5CB3}\x{5CB5}\x{5CB6}\x{5CB7}\x{5CB8}' .
+'\x{5CBA}\x{5CBB}\x{5CBC}\x{5CBD}\x{5CBE}\x{5CBF}\x{5CC1}\x{5CC2}\x{5CC3}' .
+'\x{5CC4}\x{5CC5}\x{5CC6}\x{5CC7}\x{5CC8}\x{5CC9}\x{5CCA}\x{5CCB}\x{5CCC}' .
+'\x{5CCD}\x{5CCE}\x{5CCF}\x{5CD0}\x{5CD1}\x{5CD2}\x{5CD3}\x{5CD4}\x{5CD6}' .
+'\x{5CD7}\x{5CD8}\x{5CD9}\x{5CDA}\x{5CDB}\x{5CDC}\x{5CDE}\x{5CDF}\x{5CE0}' .
+'\x{5CE1}\x{5CE2}\x{5CE3}\x{5CE4}\x{5CE5}\x{5CE6}\x{5CE7}\x{5CE8}\x{5CE9}' .
+'\x{5CEA}\x{5CEB}\x{5CEC}\x{5CED}\x{5CEE}\x{5CEF}\x{5CF0}\x{5CF1}\x{5CF2}' .
+'\x{5CF3}\x{5CF4}\x{5CF6}\x{5CF7}\x{5CF8}\x{5CF9}\x{5CFA}\x{5CFB}\x{5CFC}' .
+'\x{5CFD}\x{5CFE}\x{5CFF}\x{5D00}\x{5D01}\x{5D02}\x{5D03}\x{5D04}\x{5D05}' .
+'\x{5D06}\x{5D07}\x{5D08}\x{5D09}\x{5D0A}\x{5D0B}\x{5D0C}\x{5D0D}\x{5D0E}' .
+'\x{5D0F}\x{5D10}\x{5D11}\x{5D12}\x{5D13}\x{5D14}\x{5D15}\x{5D16}\x{5D17}' .
+'\x{5D18}\x{5D19}\x{5D1A}\x{5D1B}\x{5D1C}\x{5D1D}\x{5D1E}\x{5D1F}\x{5D20}' .
+'\x{5D21}\x{5D22}\x{5D23}\x{5D24}\x{5D25}\x{5D26}\x{5D27}\x{5D28}\x{5D29}' .
+'\x{5D2A}\x{5D2C}\x{5D2D}\x{5D2E}\x{5D30}\x{5D31}\x{5D32}\x{5D33}\x{5D34}' .
+'\x{5D35}\x{5D36}\x{5D37}\x{5D38}\x{5D39}\x{5D3A}\x{5D3C}\x{5D3D}\x{5D3E}' .
+'\x{5D3F}\x{5D40}\x{5D41}\x{5D42}\x{5D43}\x{5D44}\x{5D45}\x{5D46}\x{5D47}' .
+'\x{5D48}\x{5D49}\x{5D4A}\x{5D4B}\x{5D4C}\x{5D4D}\x{5D4E}\x{5D4F}\x{5D50}' .
+'\x{5D51}\x{5D52}\x{5D54}\x{5D55}\x{5D56}\x{5D58}\x{5D59}\x{5D5A}\x{5D5B}' .
+'\x{5D5D}\x{5D5E}\x{5D5F}\x{5D61}\x{5D62}\x{5D63}\x{5D64}\x{5D65}\x{5D66}' .
+'\x{5D67}\x{5D68}\x{5D69}\x{5D6A}\x{5D6B}\x{5D6C}\x{5D6D}\x{5D6E}\x{5D6F}' .
+'\x{5D70}\x{5D71}\x{5D72}\x{5D73}\x{5D74}\x{5D75}\x{5D76}\x{5D77}\x{5D78}' .
+'\x{5D79}\x{5D7A}\x{5D7B}\x{5D7C}\x{5D7D}\x{5D7E}\x{5D7F}\x{5D80}\x{5D81}' .
+'\x{5D82}\x{5D84}\x{5D85}\x{5D86}\x{5D87}\x{5D88}\x{5D89}\x{5D8A}\x{5D8B}' .
+'\x{5D8C}\x{5D8D}\x{5D8E}\x{5D8F}\x{5D90}\x{5D91}\x{5D92}\x{5D93}\x{5D94}' .
+'\x{5D95}\x{5D97}\x{5D98}\x{5D99}\x{5D9A}\x{5D9B}\x{5D9C}\x{5D9D}\x{5D9E}' .
+'\x{5D9F}\x{5DA0}\x{5DA1}\x{5DA2}\x{5DA5}\x{5DA6}\x{5DA7}\x{5DA8}\x{5DA9}' .
+'\x{5DAA}\x{5DAC}\x{5DAD}\x{5DAE}\x{5DAF}\x{5DB0}\x{5DB1}\x{5DB2}\x{5DB4}' .
+'\x{5DB5}\x{5DB6}\x{5DB7}\x{5DB8}\x{5DBA}\x{5DBB}\x{5DBC}\x{5DBD}\x{5DBE}' .
+'\x{5DBF}\x{5DC0}\x{5DC1}\x{5DC2}\x{5DC3}\x{5DC5}\x{5DC6}\x{5DC7}\x{5DC8}' .
+'\x{5DC9}\x{5DCA}\x{5DCB}\x{5DCC}\x{5DCD}\x{5DCE}\x{5DCF}\x{5DD0}\x{5DD1}' .
+'\x{5DD2}\x{5DD3}\x{5DD4}\x{5DD5}\x{5DD6}\x{5DD8}\x{5DD9}\x{5DDB}\x{5DDD}' .
+'\x{5DDE}\x{5DDF}\x{5DE0}\x{5DE1}\x{5DE2}\x{5DE3}\x{5DE4}\x{5DE5}\x{5DE6}' .
+'\x{5DE7}\x{5DE8}\x{5DE9}\x{5DEA}\x{5DEB}\x{5DEC}\x{5DED}\x{5DEE}\x{5DEF}' .
+'\x{5DF0}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DF8}\x{5DF9}' .
+'\x{5DFA}\x{5DFB}\x{5DFC}\x{5DFD}\x{5DFE}\x{5DFF}\x{5E00}\x{5E01}\x{5E02}' .
+'\x{5E03}\x{5E04}\x{5E05}\x{5E06}\x{5E07}\x{5E08}\x{5E09}\x{5E0A}\x{5E0B}' .
+'\x{5E0C}\x{5E0D}\x{5E0E}\x{5E0F}\x{5E10}\x{5E11}\x{5E13}\x{5E14}\x{5E15}' .
+'\x{5E16}\x{5E17}\x{5E18}\x{5E19}\x{5E1A}\x{5E1B}\x{5E1C}\x{5E1D}\x{5E1E}' .
+'\x{5E1F}\x{5E20}\x{5E21}\x{5E22}\x{5E23}\x{5E24}\x{5E25}\x{5E26}\x{5E27}' .
+'\x{5E28}\x{5E29}\x{5E2A}\x{5E2B}\x{5E2C}\x{5E2D}\x{5E2E}\x{5E2F}\x{5E30}' .
+'\x{5E31}\x{5E32}\x{5E33}\x{5E34}\x{5E35}\x{5E36}\x{5E37}\x{5E38}\x{5E39}' .
+'\x{5E3A}\x{5E3B}\x{5E3C}\x{5E3D}\x{5E3E}\x{5E40}\x{5E41}\x{5E42}\x{5E43}' .
+'\x{5E44}\x{5E45}\x{5E46}\x{5E47}\x{5E49}\x{5E4A}\x{5E4B}\x{5E4C}\x{5E4D}' .
+'\x{5E4E}\x{5E4F}\x{5E50}\x{5E52}\x{5E53}\x{5E54}\x{5E55}\x{5E56}\x{5E57}' .
+'\x{5E58}\x{5E59}\x{5E5A}\x{5E5B}\x{5E5C}\x{5E5D}\x{5E5E}\x{5E5F}\x{5E60}' .
+'\x{5E61}\x{5E62}\x{5E63}\x{5E64}\x{5E65}\x{5E66}\x{5E67}\x{5E68}\x{5E69}' .
+'\x{5E6A}\x{5E6B}\x{5E6C}\x{5E6D}\x{5E6E}\x{5E6F}\x{5E70}\x{5E71}\x{5E72}' .
+'\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E77}\x{5E78}\x{5E79}\x{5E7A}\x{5E7B}' .
+'\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E80}\x{5E81}\x{5E82}\x{5E83}\x{5E84}' .
+'\x{5E85}\x{5E86}\x{5E87}\x{5E88}\x{5E89}\x{5E8A}\x{5E8B}\x{5E8C}\x{5E8D}' .
+'\x{5E8E}\x{5E8F}\x{5E90}\x{5E91}\x{5E93}\x{5E94}\x{5E95}\x{5E96}\x{5E97}' .
+'\x{5E98}\x{5E99}\x{5E9A}\x{5E9B}\x{5E9C}\x{5E9D}\x{5E9E}\x{5E9F}\x{5EA0}' .
+'\x{5EA1}\x{5EA2}\x{5EA3}\x{5EA4}\x{5EA5}\x{5EA6}\x{5EA7}\x{5EA8}\x{5EA9}' .
+'\x{5EAA}\x{5EAB}\x{5EAC}\x{5EAD}\x{5EAE}\x{5EAF}\x{5EB0}\x{5EB1}\x{5EB2}' .
+'\x{5EB3}\x{5EB4}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EB9}\x{5EBB}\x{5EBC}' .
+'\x{5EBD}\x{5EBE}\x{5EBF}\x{5EC1}\x{5EC2}\x{5EC3}\x{5EC4}\x{5EC5}\x{5EC6}' .
+'\x{5EC7}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECB}\x{5ECC}\x{5ECD}\x{5ECE}\x{5ECF}' .
+'\x{5ED0}\x{5ED1}\x{5ED2}\x{5ED3}\x{5ED4}\x{5ED5}\x{5ED6}\x{5ED7}\x{5ED8}' .
+'\x{5ED9}\x{5EDA}\x{5EDB}\x{5EDC}\x{5EDD}\x{5EDE}\x{5EDF}\x{5EE0}\x{5EE1}' .
+'\x{5EE2}\x{5EE3}\x{5EE4}\x{5EE5}\x{5EE6}\x{5EE7}\x{5EE8}\x{5EE9}\x{5EEA}' .
+'\x{5EEC}\x{5EED}\x{5EEE}\x{5EEF}\x{5EF0}\x{5EF1}\x{5EF2}\x{5EF3}\x{5EF4}' .
+'\x{5EF5}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}\x{5EFB}\x{5EFC}\x{5EFD}\x{5EFE}' .
+'\x{5EFF}\x{5F00}\x{5F01}\x{5F02}\x{5F03}\x{5F04}\x{5F05}\x{5F06}\x{5F07}' .
+'\x{5F08}\x{5F0A}\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F11}\x{5F12}\x{5F13}' .
+'\x{5F14}\x{5F15}\x{5F16}\x{5F17}\x{5F18}\x{5F19}\x{5F1A}\x{5F1B}\x{5F1C}' .
+'\x{5F1D}\x{5F1E}\x{5F1F}\x{5F20}\x{5F21}\x{5F22}\x{5F23}\x{5F24}\x{5F25}' .
+'\x{5F26}\x{5F27}\x{5F28}\x{5F29}\x{5F2A}\x{5F2B}\x{5F2C}\x{5F2D}\x{5F2E}' .
+'\x{5F2F}\x{5F30}\x{5F31}\x{5F32}\x{5F33}\x{5F34}\x{5F35}\x{5F36}\x{5F37}' .
+'\x{5F38}\x{5F39}\x{5F3A}\x{5F3C}\x{5F3E}\x{5F3F}\x{5F40}\x{5F41}\x{5F42}' .
+'\x{5F43}\x{5F44}\x{5F45}\x{5F46}\x{5F47}\x{5F48}\x{5F49}\x{5F4A}\x{5F4B}' .
+'\x{5F4C}\x{5F4D}\x{5F4E}\x{5F4F}\x{5F50}\x{5F51}\x{5F52}\x{5F53}\x{5F54}' .
+'\x{5F55}\x{5F56}\x{5F57}\x{5F58}\x{5F59}\x{5F5A}\x{5F5B}\x{5F5C}\x{5F5D}' .
+'\x{5F5E}\x{5F5F}\x{5F60}\x{5F61}\x{5F62}\x{5F63}\x{5F64}\x{5F65}\x{5F66}' .
+'\x{5F67}\x{5F68}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}\x{5F6D}\x{5F6E}\x{5F6F}' .
+'\x{5F70}\x{5F71}\x{5F72}\x{5F73}\x{5F74}\x{5F75}\x{5F76}\x{5F77}\x{5F78}' .
+'\x{5F79}\x{5F7A}\x{5F7B}\x{5F7C}\x{5F7D}\x{5F7E}\x{5F7F}\x{5F80}\x{5F81}' .
+'\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F86}\x{5F87}\x{5F88}\x{5F89}\x{5F8A}' .
+'\x{5F8B}\x{5F8C}\x{5F8D}\x{5F8E}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F94}' .
+'\x{5F95}\x{5F96}\x{5F97}\x{5F98}\x{5F99}\x{5F9B}\x{5F9C}\x{5F9D}\x{5F9E}' .
+'\x{5F9F}\x{5FA0}\x{5FA1}\x{5FA2}\x{5FA5}\x{5FA6}\x{5FA7}\x{5FA8}\x{5FA9}' .
+'\x{5FAA}\x{5FAB}\x{5FAC}\x{5FAD}\x{5FAE}\x{5FAF}\x{5FB1}\x{5FB2}\x{5FB3}' .
+'\x{5FB4}\x{5FB5}\x{5FB6}\x{5FB7}\x{5FB8}\x{5FB9}\x{5FBA}\x{5FBB}\x{5FBC}' .
+'\x{5FBD}\x{5FBE}\x{5FBF}\x{5FC0}\x{5FC1}\x{5FC3}\x{5FC4}\x{5FC5}\x{5FC6}' .
+'\x{5FC7}\x{5FC8}\x{5FC9}\x{5FCA}\x{5FCB}\x{5FCC}\x{5FCD}\x{5FCF}\x{5FD0}' .
+'\x{5FD1}\x{5FD2}\x{5FD3}\x{5FD4}\x{5FD5}\x{5FD6}\x{5FD7}\x{5FD8}\x{5FD9}' .
+'\x{5FDA}\x{5FDC}\x{5FDD}\x{5FDE}\x{5FE0}\x{5FE1}\x{5FE3}\x{5FE4}\x{5FE5}' .
+'\x{5FE6}\x{5FE7}\x{5FE8}\x{5FE9}\x{5FEA}\x{5FEB}\x{5FED}\x{5FEE}\x{5FEF}' .
+'\x{5FF0}\x{5FF1}\x{5FF2}\x{5FF3}\x{5FF4}\x{5FF5}\x{5FF6}\x{5FF7}\x{5FF8}' .
+'\x{5FF9}\x{5FFA}\x{5FFB}\x{5FFD}\x{5FFE}\x{5FFF}\x{6000}\x{6001}\x{6002}' .
+'\x{6003}\x{6004}\x{6005}\x{6006}\x{6007}\x{6008}\x{6009}\x{600A}\x{600B}' .
+'\x{600C}\x{600D}\x{600E}\x{600F}\x{6010}\x{6011}\x{6012}\x{6013}\x{6014}' .
+'\x{6015}\x{6016}\x{6017}\x{6018}\x{6019}\x{601A}\x{601B}\x{601C}\x{601D}' .
+'\x{601E}\x{601F}\x{6020}\x{6021}\x{6022}\x{6024}\x{6025}\x{6026}\x{6027}' .
+'\x{6028}\x{6029}\x{602A}\x{602B}\x{602C}\x{602D}\x{602E}\x{602F}\x{6030}' .
+'\x{6031}\x{6032}\x{6033}\x{6034}\x{6035}\x{6036}\x{6037}\x{6038}\x{6039}' .
+'\x{603A}\x{603B}\x{603C}\x{603D}\x{603E}\x{603F}\x{6040}\x{6041}\x{6042}' .
+'\x{6043}\x{6044}\x{6045}\x{6046}\x{6047}\x{6048}\x{6049}\x{604A}\x{604B}' .
+'\x{604C}\x{604D}\x{604E}\x{604F}\x{6050}\x{6051}\x{6052}\x{6053}\x{6054}' .
+'\x{6055}\x{6057}\x{6058}\x{6059}\x{605A}\x{605B}\x{605C}\x{605D}\x{605E}' .
+'\x{605F}\x{6062}\x{6063}\x{6064}\x{6065}\x{6066}\x{6067}\x{6068}\x{6069}' .
+'\x{606A}\x{606B}\x{606C}\x{606D}\x{606E}\x{606F}\x{6070}\x{6072}\x{6073}' .
+'\x{6075}\x{6076}\x{6077}\x{6078}\x{6079}\x{607A}\x{607B}\x{607C}\x{607D}' .
+'\x{607E}\x{607F}\x{6080}\x{6081}\x{6082}\x{6083}\x{6084}\x{6085}\x{6086}' .
+'\x{6087}\x{6088}\x{6089}\x{608A}\x{608B}\x{608C}\x{608D}\x{608E}\x{608F}' .
+'\x{6090}\x{6092}\x{6094}\x{6095}\x{6096}\x{6097}\x{6098}\x{6099}\x{609A}' .
+'\x{609B}\x{609C}\x{609D}\x{609E}\x{609F}\x{60A0}\x{60A1}\x{60A2}\x{60A3}' .
+'\x{60A4}\x{60A6}\x{60A7}\x{60A8}\x{60AA}\x{60AB}\x{60AC}\x{60AD}\x{60AE}' .
+'\x{60AF}\x{60B0}\x{60B1}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B7}' .
+'\x{60B8}\x{60B9}\x{60BA}\x{60BB}\x{60BC}\x{60BD}\x{60BE}\x{60BF}\x{60C0}' .
+'\x{60C1}\x{60C2}\x{60C3}\x{60C4}\x{60C5}\x{60C6}\x{60C7}\x{60C8}\x{60C9}' .
+'\x{60CA}\x{60CB}\x{60CC}\x{60CD}\x{60CE}\x{60CF}\x{60D0}\x{60D1}\x{60D3}' .
+'\x{60D4}\x{60D5}\x{60D7}\x{60D8}\x{60D9}\x{60DA}\x{60DB}\x{60DC}\x{60DD}' .
+'\x{60DF}\x{60E0}\x{60E1}\x{60E2}\x{60E4}\x{60E6}\x{60E7}\x{60E8}\x{60E9}' .
+'\x{60EA}\x{60EB}\x{60EC}\x{60ED}\x{60EE}\x{60EF}\x{60F0}\x{60F1}\x{60F2}' .
+'\x{60F3}\x{60F4}\x{60F5}\x{60F6}\x{60F7}\x{60F8}\x{60F9}\x{60FA}\x{60FB}' .
+'\x{60FC}\x{60FE}\x{60FF}\x{6100}\x{6101}\x{6103}\x{6104}\x{6105}\x{6106}' .
+'\x{6108}\x{6109}\x{610A}\x{610B}\x{610C}\x{610D}\x{610E}\x{610F}\x{6110}' .
+'\x{6112}\x{6113}\x{6114}\x{6115}\x{6116}\x{6117}\x{6118}\x{6119}\x{611A}' .
+'\x{611B}\x{611C}\x{611D}\x{611F}\x{6120}\x{6122}\x{6123}\x{6124}\x{6125}' .
+'\x{6126}\x{6127}\x{6128}\x{6129}\x{612A}\x{612B}\x{612C}\x{612D}\x{612E}' .
+'\x{612F}\x{6130}\x{6132}\x{6134}\x{6136}\x{6137}\x{613A}\x{613B}\x{613C}' .
+'\x{613D}\x{613E}\x{613F}\x{6140}\x{6141}\x{6142}\x{6143}\x{6144}\x{6145}' .
+'\x{6146}\x{6147}\x{6148}\x{6149}\x{614A}\x{614B}\x{614C}\x{614D}\x{614E}' .
+'\x{614F}\x{6150}\x{6151}\x{6152}\x{6153}\x{6154}\x{6155}\x{6156}\x{6157}' .
+'\x{6158}\x{6159}\x{615A}\x{615B}\x{615C}\x{615D}\x{615E}\x{615F}\x{6161}' .
+'\x{6162}\x{6163}\x{6164}\x{6165}\x{6166}\x{6167}\x{6168}\x{6169}\x{616A}' .
+'\x{616B}\x{616C}\x{616D}\x{616E}\x{6170}\x{6171}\x{6172}\x{6173}\x{6174}' .
+'\x{6175}\x{6176}\x{6177}\x{6178}\x{6179}\x{617A}\x{617C}\x{617E}\x{6180}' .
+'\x{6181}\x{6182}\x{6183}\x{6184}\x{6185}\x{6187}\x{6188}\x{6189}\x{618A}' .
+'\x{618B}\x{618C}\x{618D}\x{618E}\x{618F}\x{6190}\x{6191}\x{6192}\x{6193}' .
+'\x{6194}\x{6195}\x{6196}\x{6198}\x{6199}\x{619A}\x{619B}\x{619D}\x{619E}' .
+'\x{619F}\x{61A0}\x{61A1}\x{61A2}\x{61A3}\x{61A4}\x{61A5}\x{61A6}\x{61A7}' .
+'\x{61A8}\x{61A9}\x{61AA}\x{61AB}\x{61AC}\x{61AD}\x{61AE}\x{61AF}\x{61B0}' .
+'\x{61B1}\x{61B2}\x{61B3}\x{61B4}\x{61B5}\x{61B6}\x{61B7}\x{61B8}\x{61BA}' .
+'\x{61BC}\x{61BD}\x{61BE}\x{61BF}\x{61C0}\x{61C1}\x{61C2}\x{61C3}\x{61C4}' .
+'\x{61C5}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' .
+'\x{61CE}\x{61CF}\x{61D0}\x{61D1}\x{61D2}\x{61D4}\x{61D6}\x{61D7}\x{61D8}' .
+'\x{61D9}\x{61DA}\x{61DB}\x{61DC}\x{61DD}\x{61DE}\x{61DF}\x{61E0}\x{61E1}' .
+'\x{61E2}\x{61E3}\x{61E4}\x{61E5}\x{61E6}\x{61E7}\x{61E8}\x{61E9}\x{61EA}' .
+'\x{61EB}\x{61ED}\x{61EE}\x{61F0}\x{61F1}\x{61F2}\x{61F3}\x{61F5}\x{61F6}' .
+'\x{61F7}\x{61F8}\x{61F9}\x{61FA}\x{61FB}\x{61FC}\x{61FD}\x{61FE}\x{61FF}' .
+'\x{6200}\x{6201}\x{6202}\x{6203}\x{6204}\x{6206}\x{6207}\x{6208}\x{6209}' .
+'\x{620A}\x{620B}\x{620C}\x{620D}\x{620E}\x{620F}\x{6210}\x{6211}\x{6212}' .
+'\x{6213}\x{6214}\x{6215}\x{6216}\x{6217}\x{6218}\x{6219}\x{621A}\x{621B}' .
+'\x{621C}\x{621D}\x{621E}\x{621F}\x{6220}\x{6221}\x{6222}\x{6223}\x{6224}' .
+'\x{6225}\x{6226}\x{6227}\x{6228}\x{6229}\x{622A}\x{622B}\x{622C}\x{622D}' .
+'\x{622E}\x{622F}\x{6230}\x{6231}\x{6232}\x{6233}\x{6234}\x{6236}\x{6237}' .
+'\x{6238}\x{623A}\x{623B}\x{623C}\x{623D}\x{623E}\x{623F}\x{6240}\x{6241}' .
+'\x{6242}\x{6243}\x{6244}\x{6245}\x{6246}\x{6247}\x{6248}\x{6249}\x{624A}' .
+'\x{624B}\x{624C}\x{624D}\x{624E}\x{624F}\x{6250}\x{6251}\x{6252}\x{6253}' .
+'\x{6254}\x{6255}\x{6256}\x{6258}\x{6259}\x{625A}\x{625B}\x{625C}\x{625D}' .
+'\x{625E}\x{625F}\x{6260}\x{6261}\x{6262}\x{6263}\x{6264}\x{6265}\x{6266}' .
+'\x{6267}\x{6268}\x{6269}\x{626A}\x{626B}\x{626C}\x{626D}\x{626E}\x{626F}' .
+'\x{6270}\x{6271}\x{6272}\x{6273}\x{6274}\x{6275}\x{6276}\x{6277}\x{6278}' .
+'\x{6279}\x{627A}\x{627B}\x{627C}\x{627D}\x{627E}\x{627F}\x{6280}\x{6281}' .
+'\x{6283}\x{6284}\x{6285}\x{6286}\x{6287}\x{6288}\x{6289}\x{628A}\x{628B}' .
+'\x{628C}\x{628E}\x{628F}\x{6290}\x{6291}\x{6292}\x{6293}\x{6294}\x{6295}' .
+'\x{6296}\x{6297}\x{6298}\x{6299}\x{629A}\x{629B}\x{629C}\x{629E}\x{629F}' .
+'\x{62A0}\x{62A1}\x{62A2}\x{62A3}\x{62A4}\x{62A5}\x{62A7}\x{62A8}\x{62A9}' .
+'\x{62AA}\x{62AB}\x{62AC}\x{62AD}\x{62AE}\x{62AF}\x{62B0}\x{62B1}\x{62B2}' .
+'\x{62B3}\x{62B4}\x{62B5}\x{62B6}\x{62B7}\x{62B8}\x{62B9}\x{62BA}\x{62BB}' .
+'\x{62BC}\x{62BD}\x{62BE}\x{62BF}\x{62C0}\x{62C1}\x{62C2}\x{62C3}\x{62C4}' .
+'\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CB}\x{62CC}\x{62CD}' .
+'\x{62CE}\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D5}\x{62D6}' .
+'\x{62D7}\x{62D8}\x{62D9}\x{62DA}\x{62DB}\x{62DC}\x{62DD}\x{62DF}\x{62E0}' .
+'\x{62E1}\x{62E2}\x{62E3}\x{62E4}\x{62E5}\x{62E6}\x{62E7}\x{62E8}\x{62E9}' .
+'\x{62EB}\x{62EC}\x{62ED}\x{62EE}\x{62EF}\x{62F0}\x{62F1}\x{62F2}\x{62F3}' .
+'\x{62F4}\x{62F5}\x{62F6}\x{62F7}\x{62F8}\x{62F9}\x{62FA}\x{62FB}\x{62FC}' .
+'\x{62FD}\x{62FE}\x{62FF}\x{6300}\x{6301}\x{6302}\x{6303}\x{6304}\x{6305}' .
+'\x{6306}\x{6307}\x{6308}\x{6309}\x{630B}\x{630C}\x{630D}\x{630E}\x{630F}' .
+'\x{6310}\x{6311}\x{6312}\x{6313}\x{6314}\x{6315}\x{6316}\x{6318}\x{6319}' .
+'\x{631A}\x{631B}\x{631C}\x{631D}\x{631E}\x{631F}\x{6320}\x{6321}\x{6322}' .
+'\x{6323}\x{6324}\x{6325}\x{6326}\x{6327}\x{6328}\x{6329}\x{632A}\x{632B}' .
+'\x{632C}\x{632D}\x{632E}\x{632F}\x{6330}\x{6332}\x{6333}\x{6334}\x{6336}' .
+'\x{6338}\x{6339}\x{633A}\x{633B}\x{633C}\x{633D}\x{633E}\x{6340}\x{6341}' .
+'\x{6342}\x{6343}\x{6344}\x{6345}\x{6346}\x{6347}\x{6348}\x{6349}\x{634A}' .
+'\x{634B}\x{634C}\x{634D}\x{634E}\x{634F}\x{6350}\x{6351}\x{6352}\x{6353}' .
+'\x{6354}\x{6355}\x{6356}\x{6357}\x{6358}\x{6359}\x{635A}\x{635C}\x{635D}' .
+'\x{635E}\x{635F}\x{6360}\x{6361}\x{6362}\x{6363}\x{6364}\x{6365}\x{6366}' .
+'\x{6367}\x{6368}\x{6369}\x{636A}\x{636B}\x{636C}\x{636D}\x{636E}\x{636F}' .
+'\x{6370}\x{6371}\x{6372}\x{6373}\x{6374}\x{6375}\x{6376}\x{6377}\x{6378}' .
+'\x{6379}\x{637A}\x{637B}\x{637C}\x{637D}\x{637E}\x{6380}\x{6381}\x{6382}' .
+'\x{6383}\x{6384}\x{6385}\x{6386}\x{6387}\x{6388}\x{6389}\x{638A}\x{638C}' .
+'\x{638D}\x{638E}\x{638F}\x{6390}\x{6391}\x{6392}\x{6394}\x{6395}\x{6396}' .
+'\x{6397}\x{6398}\x{6399}\x{639A}\x{639B}\x{639C}\x{639D}\x{639E}\x{639F}' .
+'\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A4}\x{63A5}\x{63A6}\x{63A7}\x{63A8}' .
+'\x{63A9}\x{63AA}\x{63AB}\x{63AC}\x{63AD}\x{63AE}\x{63AF}\x{63B0}\x{63B1}' .
+'\x{63B2}\x{63B3}\x{63B4}\x{63B5}\x{63B6}\x{63B7}\x{63B8}\x{63B9}\x{63BA}' .
+'\x{63BC}\x{63BD}\x{63BE}\x{63BF}\x{63C0}\x{63C1}\x{63C2}\x{63C3}\x{63C4}' .
+'\x{63C5}\x{63C6}\x{63C7}\x{63C8}\x{63C9}\x{63CA}\x{63CB}\x{63CC}\x{63CD}' .
+'\x{63CE}\x{63CF}\x{63D0}\x{63D2}\x{63D3}\x{63D4}\x{63D5}\x{63D6}\x{63D7}' .
+'\x{63D8}\x{63D9}\x{63DA}\x{63DB}\x{63DC}\x{63DD}\x{63DE}\x{63DF}\x{63E0}' .
+'\x{63E1}\x{63E2}\x{63E3}\x{63E4}\x{63E5}\x{63E6}\x{63E7}\x{63E8}\x{63E9}' .
+'\x{63EA}\x{63EB}\x{63EC}\x{63ED}\x{63EE}\x{63EF}\x{63F0}\x{63F1}\x{63F2}' .
+'\x{63F3}\x{63F4}\x{63F5}\x{63F6}\x{63F7}\x{63F8}\x{63F9}\x{63FA}\x{63FB}' .
+'\x{63FC}\x{63FD}\x{63FE}\x{63FF}\x{6400}\x{6401}\x{6402}\x{6403}\x{6404}' .
+'\x{6405}\x{6406}\x{6408}\x{6409}\x{640A}\x{640B}\x{640C}\x{640D}\x{640E}' .
+'\x{640F}\x{6410}\x{6411}\x{6412}\x{6413}\x{6414}\x{6415}\x{6416}\x{6417}' .
+'\x{6418}\x{6419}\x{641A}\x{641B}\x{641C}\x{641D}\x{641E}\x{641F}\x{6420}' .
+'\x{6421}\x{6422}\x{6423}\x{6424}\x{6425}\x{6426}\x{6427}\x{6428}\x{6429}' .
+'\x{642A}\x{642B}\x{642C}\x{642D}\x{642E}\x{642F}\x{6430}\x{6431}\x{6432}' .
+'\x{6433}\x{6434}\x{6435}\x{6436}\x{6437}\x{6438}\x{6439}\x{643A}\x{643D}' .
+'\x{643E}\x{643F}\x{6440}\x{6441}\x{6443}\x{6444}\x{6445}\x{6446}\x{6447}' .
+'\x{6448}\x{644A}\x{644B}\x{644C}\x{644D}\x{644E}\x{644F}\x{6450}\x{6451}' .
+'\x{6452}\x{6453}\x{6454}\x{6455}\x{6456}\x{6457}\x{6458}\x{6459}\x{645B}' .
+'\x{645C}\x{645D}\x{645E}\x{645F}\x{6460}\x{6461}\x{6462}\x{6463}\x{6464}' .
+'\x{6465}\x{6466}\x{6467}\x{6468}\x{6469}\x{646A}\x{646B}\x{646C}\x{646D}' .
+'\x{646E}\x{646F}\x{6470}\x{6471}\x{6472}\x{6473}\x{6474}\x{6475}\x{6476}' .
+'\x{6477}\x{6478}\x{6479}\x{647A}\x{647B}\x{647C}\x{647D}\x{647F}\x{6480}' .
+'\x{6481}\x{6482}\x{6483}\x{6484}\x{6485}\x{6487}\x{6488}\x{6489}\x{648A}' .
+'\x{648B}\x{648C}\x{648D}\x{648E}\x{648F}\x{6490}\x{6491}\x{6492}\x{6493}' .
+'\x{6494}\x{6495}\x{6496}\x{6497}\x{6498}\x{6499}\x{649A}\x{649B}\x{649C}' .
+'\x{649D}\x{649E}\x{649F}\x{64A0}\x{64A2}\x{64A3}\x{64A4}\x{64A5}\x{64A6}' .
+'\x{64A7}\x{64A8}\x{64A9}\x{64AA}\x{64AB}\x{64AC}\x{64AD}\x{64AE}\x{64B0}' .
+'\x{64B1}\x{64B2}\x{64B3}\x{64B4}\x{64B5}\x{64B7}\x{64B8}\x{64B9}\x{64BA}' .
+'\x{64BB}\x{64BC}\x{64BD}\x{64BE}\x{64BF}\x{64C0}\x{64C1}\x{64C2}\x{64C3}' .
+'\x{64C4}\x{64C5}\x{64C6}\x{64C7}\x{64C9}\x{64CA}\x{64CB}\x{64CC}\x{64CD}' .
+'\x{64CE}\x{64CF}\x{64D0}\x{64D1}\x{64D2}\x{64D3}\x{64D4}\x{64D6}\x{64D7}' .
+'\x{64D8}\x{64D9}\x{64DA}\x{64DB}\x{64DC}\x{64DD}\x{64DE}\x{64DF}\x{64E0}' .
+'\x{64E2}\x{64E3}\x{64E4}\x{64E6}\x{64E7}\x{64E8}\x{64E9}\x{64EA}\x{64EB}' .
+'\x{64EC}\x{64ED}\x{64EF}\x{64F0}\x{64F1}\x{64F2}\x{64F3}\x{64F4}\x{64F6}' .
+'\x{64F7}\x{64F8}\x{64FA}\x{64FB}\x{64FC}\x{64FD}\x{64FE}\x{64FF}\x{6500}' .
+'\x{6501}\x{6503}\x{6504}\x{6505}\x{6506}\x{6507}\x{6508}\x{6509}\x{650B}' .
+'\x{650C}\x{650D}\x{650E}\x{650F}\x{6510}\x{6511}\x{6512}\x{6513}\x{6514}' .
+'\x{6515}\x{6516}\x{6517}\x{6518}\x{6519}\x{651A}\x{651B}\x{651C}\x{651D}' .
+'\x{651E}\x{6520}\x{6521}\x{6522}\x{6523}\x{6524}\x{6525}\x{6526}\x{6527}' .
+'\x{6529}\x{652A}\x{652B}\x{652C}\x{652D}\x{652E}\x{652F}\x{6530}\x{6531}' .
+'\x{6532}\x{6533}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}\x{653A}' .
+'\x{653B}\x{653C}\x{653D}\x{653E}\x{653F}\x{6541}\x{6543}\x{6544}\x{6545}' .
+'\x{6546}\x{6547}\x{6548}\x{6549}\x{654A}\x{654B}\x{654C}\x{654D}\x{654E}' .
+'\x{654F}\x{6550}\x{6551}\x{6552}\x{6553}\x{6554}\x{6555}\x{6556}\x{6557}' .
+'\x{6558}\x{6559}\x{655B}\x{655C}\x{655D}\x{655E}\x{6560}\x{6561}\x{6562}' .
+'\x{6563}\x{6564}\x{6565}\x{6566}\x{6567}\x{6568}\x{6569}\x{656A}\x{656B}' .
+'\x{656C}\x{656E}\x{656F}\x{6570}\x{6571}\x{6572}\x{6573}\x{6574}\x{6575}' .
+'\x{6576}\x{6577}\x{6578}\x{6579}\x{657A}\x{657B}\x{657C}\x{657E}\x{657F}' .
+'\x{6580}\x{6581}\x{6582}\x{6583}\x{6584}\x{6585}\x{6586}\x{6587}\x{6588}' .
+'\x{6589}\x{658B}\x{658C}\x{658D}\x{658E}\x{658F}\x{6590}\x{6591}\x{6592}' .
+'\x{6593}\x{6594}\x{6595}\x{6596}\x{6597}\x{6598}\x{6599}\x{659B}\x{659C}' .
+'\x{659D}\x{659E}\x{659F}\x{65A0}\x{65A1}\x{65A2}\x{65A3}\x{65A4}\x{65A5}' .
+'\x{65A6}\x{65A7}\x{65A8}\x{65A9}\x{65AA}\x{65AB}\x{65AC}\x{65AD}\x{65AE}' .
+'\x{65AF}\x{65B0}\x{65B1}\x{65B2}\x{65B3}\x{65B4}\x{65B6}\x{65B7}\x{65B8}' .
+'\x{65B9}\x{65BA}\x{65BB}\x{65BC}\x{65BD}\x{65BF}\x{65C0}\x{65C1}\x{65C2}' .
+'\x{65C3}\x{65C4}\x{65C5}\x{65C6}\x{65C7}\x{65CA}\x{65CB}\x{65CC}\x{65CD}' .
+'\x{65CE}\x{65CF}\x{65D0}\x{65D2}\x{65D3}\x{65D4}\x{65D5}\x{65D6}\x{65D7}' .
+'\x{65DA}\x{65DB}\x{65DD}\x{65DE}\x{65DF}\x{65E0}\x{65E1}\x{65E2}\x{65E3}' .
+'\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}\x{65EB}\x{65EC}\x{65ED}\x{65EE}' .
+'\x{65EF}\x{65F0}\x{65F1}\x{65F2}\x{65F3}\x{65F4}\x{65F5}\x{65F6}\x{65F7}' .
+'\x{65F8}\x{65FA}\x{65FB}\x{65FC}\x{65FD}\x{6600}\x{6601}\x{6602}\x{6603}' .
+'\x{6604}\x{6605}\x{6606}\x{6607}\x{6608}\x{6609}\x{660A}\x{660B}\x{660C}' .
+'\x{660D}\x{660E}\x{660F}\x{6610}\x{6611}\x{6612}\x{6613}\x{6614}\x{6615}' .
+'\x{6616}\x{6618}\x{6619}\x{661A}\x{661B}\x{661C}\x{661D}\x{661F}\x{6620}' .
+'\x{6621}\x{6622}\x{6623}\x{6624}\x{6625}\x{6626}\x{6627}\x{6628}\x{6629}' .
+'\x{662A}\x{662B}\x{662D}\x{662E}\x{662F}\x{6630}\x{6631}\x{6632}\x{6633}' .
+'\x{6634}\x{6635}\x{6636}\x{6639}\x{663A}\x{663C}\x{663D}\x{663E}\x{6640}' .
+'\x{6641}\x{6642}\x{6643}\x{6644}\x{6645}\x{6646}\x{6647}\x{6649}\x{664A}' .
+'\x{664B}\x{664C}\x{664E}\x{664F}\x{6650}\x{6651}\x{6652}\x{6653}\x{6654}' .
+'\x{6655}\x{6656}\x{6657}\x{6658}\x{6659}\x{665A}\x{665B}\x{665C}\x{665D}' .
+'\x{665E}\x{665F}\x{6661}\x{6662}\x{6664}\x{6665}\x{6666}\x{6668}\x{6669}' .
+'\x{666A}\x{666B}\x{666C}\x{666D}\x{666E}\x{666F}\x{6670}\x{6671}\x{6672}' .
+'\x{6673}\x{6674}\x{6675}\x{6676}\x{6677}\x{6678}\x{6679}\x{667A}\x{667B}' .
+'\x{667C}\x{667D}\x{667E}\x{667F}\x{6680}\x{6681}\x{6682}\x{6683}\x{6684}' .
+'\x{6685}\x{6686}\x{6687}\x{6688}\x{6689}\x{668A}\x{668B}\x{668C}\x{668D}' .
+'\x{668E}\x{668F}\x{6690}\x{6691}\x{6693}\x{6694}\x{6695}\x{6696}\x{6697}' .
+'\x{6698}\x{6699}\x{669A}\x{669B}\x{669D}\x{669F}\x{66A0}\x{66A1}\x{66A2}' .
+'\x{66A3}\x{66A4}\x{66A5}\x{66A6}\x{66A7}\x{66A8}\x{66A9}\x{66AA}\x{66AB}' .
+'\x{66AE}\x{66AF}\x{66B0}\x{66B1}\x{66B2}\x{66B3}\x{66B4}\x{66B5}\x{66B6}' .
+'\x{66B7}\x{66B8}\x{66B9}\x{66BA}\x{66BB}\x{66BC}\x{66BD}\x{66BE}\x{66BF}' .
+'\x{66C0}\x{66C1}\x{66C2}\x{66C3}\x{66C4}\x{66C5}\x{66C6}\x{66C7}\x{66C8}' .
+'\x{66C9}\x{66CA}\x{66CB}\x{66CC}\x{66CD}\x{66CE}\x{66CF}\x{66D1}\x{66D2}' .
+'\x{66D4}\x{66D5}\x{66D6}\x{66D8}\x{66D9}\x{66DA}\x{66DB}\x{66DC}\x{66DD}' .
+'\x{66DE}\x{66E0}\x{66E1}\x{66E2}\x{66E3}\x{66E4}\x{66E5}\x{66E6}\x{66E7}' .
+'\x{66E8}\x{66E9}\x{66EA}\x{66EB}\x{66EC}\x{66ED}\x{66EE}\x{66F0}\x{66F1}' .
+'\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F6}\x{66F7}\x{66F8}\x{66F9}\x{66FA}' .
+'\x{66FB}\x{66FC}\x{66FE}\x{66FF}\x{6700}\x{6701}\x{6703}\x{6704}\x{6705}' .
+'\x{6706}\x{6708}\x{6709}\x{670A}\x{670B}\x{670C}\x{670D}\x{670E}\x{670F}' .
+'\x{6710}\x{6711}\x{6712}\x{6713}\x{6714}\x{6715}\x{6716}\x{6717}\x{6718}' .
+'\x{671A}\x{671B}\x{671C}\x{671D}\x{671E}\x{671F}\x{6720}\x{6721}\x{6722}' .
+'\x{6723}\x{6725}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}\x{672D}' .
+'\x{672E}\x{672F}\x{6730}\x{6731}\x{6732}\x{6733}\x{6734}\x{6735}\x{6736}' .
+'\x{6737}\x{6738}\x{6739}\x{673A}\x{673B}\x{673C}\x{673D}\x{673E}\x{673F}' .
+'\x{6740}\x{6741}\x{6742}\x{6743}\x{6744}\x{6745}\x{6746}\x{6747}\x{6748}' .
+'\x{6749}\x{674A}\x{674B}\x{674C}\x{674D}\x{674E}\x{674F}\x{6750}\x{6751}' .
+'\x{6752}\x{6753}\x{6754}\x{6755}\x{6756}\x{6757}\x{6758}\x{6759}\x{675A}' .
+'\x{675B}\x{675C}\x{675D}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' .
+'\x{6764}\x{6765}\x{6766}\x{6768}\x{6769}\x{676A}\x{676B}\x{676C}\x{676D}' .
+'\x{676E}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}\x{6774}\x{6775}\x{6776}' .
+'\x{6777}\x{6778}\x{6779}\x{677A}\x{677B}\x{677C}\x{677D}\x{677E}\x{677F}' .
+'\x{6780}\x{6781}\x{6782}\x{6783}\x{6784}\x{6785}\x{6786}\x{6787}\x{6789}' .
+'\x{678A}\x{678B}\x{678C}\x{678D}\x{678E}\x{678F}\x{6790}\x{6791}\x{6792}' .
+'\x{6793}\x{6794}\x{6795}\x{6797}\x{6798}\x{6799}\x{679A}\x{679B}\x{679C}' .
+'\x{679D}\x{679E}\x{679F}\x{67A0}\x{67A1}\x{67A2}\x{67A3}\x{67A4}\x{67A5}' .
+'\x{67A6}\x{67A7}\x{67A8}\x{67AA}\x{67AB}\x{67AC}\x{67AD}\x{67AE}\x{67AF}' .
+'\x{67B0}\x{67B1}\x{67B2}\x{67B3}\x{67B4}\x{67B5}\x{67B6}\x{67B7}\x{67B8}' .
+'\x{67B9}\x{67BA}\x{67BB}\x{67BC}\x{67BE}\x{67C0}\x{67C1}\x{67C2}\x{67C3}' .
+'\x{67C4}\x{67C5}\x{67C6}\x{67C7}\x{67C8}\x{67C9}\x{67CA}\x{67CB}\x{67CC}' .
+'\x{67CD}\x{67CE}\x{67CF}\x{67D0}\x{67D1}\x{67D2}\x{67D3}\x{67D4}\x{67D6}' .
+'\x{67D8}\x{67D9}\x{67DA}\x{67DB}\x{67DC}\x{67DD}\x{67DE}\x{67DF}\x{67E0}' .
+'\x{67E1}\x{67E2}\x{67E3}\x{67E4}\x{67E5}\x{67E6}\x{67E7}\x{67E8}\x{67E9}' .
+'\x{67EA}\x{67EB}\x{67EC}\x{67ED}\x{67EE}\x{67EF}\x{67F0}\x{67F1}\x{67F2}' .
+'\x{67F3}\x{67F4}\x{67F5}\x{67F6}\x{67F7}\x{67F8}\x{67FA}\x{67FB}\x{67FC}' .
+'\x{67FD}\x{67FE}\x{67FF}\x{6800}\x{6802}\x{6803}\x{6804}\x{6805}\x{6806}' .
+'\x{6807}\x{6808}\x{6809}\x{680A}\x{680B}\x{680C}\x{680D}\x{680E}\x{680F}' .
+'\x{6810}\x{6811}\x{6812}\x{6813}\x{6814}\x{6816}\x{6817}\x{6818}\x{6819}' .
+'\x{681A}\x{681B}\x{681C}\x{681D}\x{681F}\x{6820}\x{6821}\x{6822}\x{6823}' .
+'\x{6824}\x{6825}\x{6826}\x{6828}\x{6829}\x{682A}\x{682B}\x{682C}\x{682D}' .
+'\x{682E}\x{682F}\x{6831}\x{6832}\x{6833}\x{6834}\x{6835}\x{6836}\x{6837}' .
+'\x{6838}\x{6839}\x{683A}\x{683B}\x{683C}\x{683D}\x{683E}\x{683F}\x{6840}' .
+'\x{6841}\x{6842}\x{6843}\x{6844}\x{6845}\x{6846}\x{6847}\x{6848}\x{6849}' .
+'\x{684A}\x{684B}\x{684C}\x{684D}\x{684E}\x{684F}\x{6850}\x{6851}\x{6852}' .
+'\x{6853}\x{6854}\x{6855}\x{6856}\x{6857}\x{685B}\x{685D}\x{6860}\x{6861}' .
+'\x{6862}\x{6863}\x{6864}\x{6865}\x{6866}\x{6867}\x{6868}\x{6869}\x{686A}' .
+'\x{686B}\x{686C}\x{686D}\x{686E}\x{686F}\x{6870}\x{6871}\x{6872}\x{6873}' .
+'\x{6874}\x{6875}\x{6876}\x{6877}\x{6878}\x{6879}\x{687B}\x{687C}\x{687D}' .
+'\x{687E}\x{687F}\x{6880}\x{6881}\x{6882}\x{6883}\x{6884}\x{6885}\x{6886}' .
+'\x{6887}\x{6888}\x{6889}\x{688A}\x{688B}\x{688C}\x{688D}\x{688E}\x{688F}' .
+'\x{6890}\x{6891}\x{6892}\x{6893}\x{6894}\x{6896}\x{6897}\x{6898}\x{689A}' .
+'\x{689B}\x{689C}\x{689D}\x{689E}\x{689F}\x{68A0}\x{68A1}\x{68A2}\x{68A3}' .
+'\x{68A4}\x{68A6}\x{68A7}\x{68A8}\x{68A9}\x{68AA}\x{68AB}\x{68AC}\x{68AD}' .
+'\x{68AE}\x{68AF}\x{68B0}\x{68B1}\x{68B2}\x{68B3}\x{68B4}\x{68B5}\x{68B6}' .
+'\x{68B7}\x{68B9}\x{68BB}\x{68BC}\x{68BD}\x{68BE}\x{68BF}\x{68C0}\x{68C1}' .
+'\x{68C2}\x{68C4}\x{68C6}\x{68C7}\x{68C8}\x{68C9}\x{68CA}\x{68CB}\x{68CC}' .
+'\x{68CD}\x{68CE}\x{68CF}\x{68D0}\x{68D1}\x{68D2}\x{68D3}\x{68D4}\x{68D5}' .
+'\x{68D6}\x{68D7}\x{68D8}\x{68DA}\x{68DB}\x{68DC}\x{68DD}\x{68DE}\x{68DF}' .
+'\x{68E0}\x{68E1}\x{68E3}\x{68E4}\x{68E6}\x{68E7}\x{68E8}\x{68E9}\x{68EA}' .
+'\x{68EB}\x{68EC}\x{68ED}\x{68EE}\x{68EF}\x{68F0}\x{68F1}\x{68F2}\x{68F3}' .
+'\x{68F4}\x{68F5}\x{68F6}\x{68F7}\x{68F8}\x{68F9}\x{68FA}\x{68FB}\x{68FC}' .
+'\x{68FD}\x{68FE}\x{68FF}\x{6901}\x{6902}\x{6903}\x{6904}\x{6905}\x{6906}' .
+'\x{6907}\x{6908}\x{690A}\x{690B}\x{690C}\x{690D}\x{690E}\x{690F}\x{6910}' .
+'\x{6911}\x{6912}\x{6913}\x{6914}\x{6915}\x{6916}\x{6917}\x{6918}\x{6919}' .
+'\x{691A}\x{691B}\x{691C}\x{691D}\x{691E}\x{691F}\x{6920}\x{6921}\x{6922}' .
+'\x{6923}\x{6924}\x{6925}\x{6926}\x{6927}\x{6928}\x{6929}\x{692A}\x{692B}' .
+'\x{692C}\x{692D}\x{692E}\x{692F}\x{6930}\x{6931}\x{6932}\x{6933}\x{6934}' .
+'\x{6935}\x{6936}\x{6937}\x{6938}\x{6939}\x{693A}\x{693B}\x{693C}\x{693D}' .
+'\x{693F}\x{6940}\x{6941}\x{6942}\x{6943}\x{6944}\x{6945}\x{6946}\x{6947}' .
+'\x{6948}\x{6949}\x{694A}\x{694B}\x{694C}\x{694E}\x{694F}\x{6950}\x{6951}' .
+'\x{6952}\x{6953}\x{6954}\x{6955}\x{6956}\x{6957}\x{6958}\x{6959}\x{695A}' .
+'\x{695B}\x{695C}\x{695D}\x{695E}\x{695F}\x{6960}\x{6961}\x{6962}\x{6963}' .
+'\x{6964}\x{6965}\x{6966}\x{6967}\x{6968}\x{6969}\x{696A}\x{696B}\x{696C}' .
+'\x{696D}\x{696E}\x{696F}\x{6970}\x{6971}\x{6972}\x{6973}\x{6974}\x{6975}' .
+'\x{6976}\x{6977}\x{6978}\x{6979}\x{697A}\x{697B}\x{697C}\x{697D}\x{697E}' .
+'\x{697F}\x{6980}\x{6981}\x{6982}\x{6983}\x{6984}\x{6985}\x{6986}\x{6987}' .
+'\x{6988}\x{6989}\x{698A}\x{698B}\x{698C}\x{698D}\x{698E}\x{698F}\x{6990}' .
+'\x{6991}\x{6992}\x{6993}\x{6994}\x{6995}\x{6996}\x{6997}\x{6998}\x{6999}' .
+'\x{699A}\x{699B}\x{699C}\x{699D}\x{699E}\x{69A0}\x{69A1}\x{69A3}\x{69A4}' .
+'\x{69A5}\x{69A6}\x{69A7}\x{69A8}\x{69A9}\x{69AA}\x{69AB}\x{69AC}\x{69AD}' .
+'\x{69AE}\x{69AF}\x{69B0}\x{69B1}\x{69B2}\x{69B3}\x{69B4}\x{69B5}\x{69B6}' .
+'\x{69B7}\x{69B8}\x{69B9}\x{69BA}\x{69BB}\x{69BC}\x{69BD}\x{69BE}\x{69BF}' .
+'\x{69C1}\x{69C2}\x{69C3}\x{69C4}\x{69C5}\x{69C6}\x{69C7}\x{69C8}\x{69C9}' .
+'\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}\x{69CF}\x{69D0}\x{69D3}\x{69D4}' .
+'\x{69D8}\x{69D9}\x{69DA}\x{69DB}\x{69DC}\x{69DD}\x{69DE}\x{69DF}\x{69E0}' .
+'\x{69E1}\x{69E2}\x{69E3}\x{69E4}\x{69E5}\x{69E6}\x{69E7}\x{69E8}\x{69E9}' .
+'\x{69EA}\x{69EB}\x{69EC}\x{69ED}\x{69EE}\x{69EF}\x{69F0}\x{69F1}\x{69F2}' .
+'\x{69F3}\x{69F4}\x{69F5}\x{69F6}\x{69F7}\x{69F8}\x{69FA}\x{69FB}\x{69FC}' .
+'\x{69FD}\x{69FE}\x{69FF}\x{6A00}\x{6A01}\x{6A02}\x{6A04}\x{6A05}\x{6A06}' .
+'\x{6A07}\x{6A08}\x{6A09}\x{6A0A}\x{6A0B}\x{6A0D}\x{6A0E}\x{6A0F}\x{6A10}' .
+'\x{6A11}\x{6A12}\x{6A13}\x{6A14}\x{6A15}\x{6A16}\x{6A17}\x{6A18}\x{6A19}' .
+'\x{6A1A}\x{6A1B}\x{6A1D}\x{6A1E}\x{6A1F}\x{6A20}\x{6A21}\x{6A22}\x{6A23}' .
+'\x{6A25}\x{6A26}\x{6A27}\x{6A28}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2C}\x{6A2D}' .
+'\x{6A2E}\x{6A2F}\x{6A30}\x{6A31}\x{6A32}\x{6A33}\x{6A34}\x{6A35}\x{6A36}' .
+'\x{6A38}\x{6A39}\x{6A3A}\x{6A3B}\x{6A3C}\x{6A3D}\x{6A3E}\x{6A3F}\x{6A40}' .
+'\x{6A41}\x{6A42}\x{6A43}\x{6A44}\x{6A45}\x{6A46}\x{6A47}\x{6A48}\x{6A49}' .
+'\x{6A4B}\x{6A4C}\x{6A4D}\x{6A4E}\x{6A4F}\x{6A50}\x{6A51}\x{6A52}\x{6A54}' .
+'\x{6A55}\x{6A56}\x{6A57}\x{6A58}\x{6A59}\x{6A5A}\x{6A5B}\x{6A5D}\x{6A5E}' .
+'\x{6A5F}\x{6A60}\x{6A61}\x{6A62}\x{6A63}\x{6A64}\x{6A65}\x{6A66}\x{6A67}' .
+'\x{6A68}\x{6A69}\x{6A6A}\x{6A6B}\x{6A6C}\x{6A6D}\x{6A6F}\x{6A71}\x{6A72}' .
+'\x{6A73}\x{6A74}\x{6A75}\x{6A76}\x{6A77}\x{6A78}\x{6A79}\x{6A7A}\x{6A7B}' .
+'\x{6A7C}\x{6A7D}\x{6A7E}\x{6A7F}\x{6A80}\x{6A81}\x{6A82}\x{6A83}\x{6A84}' .
+'\x{6A85}\x{6A87}\x{6A88}\x{6A89}\x{6A8B}\x{6A8C}\x{6A8D}\x{6A8E}\x{6A90}' .
+'\x{6A91}\x{6A92}\x{6A93}\x{6A94}\x{6A95}\x{6A96}\x{6A97}\x{6A98}\x{6A9A}' .
+'\x{6A9B}\x{6A9C}\x{6A9E}\x{6A9F}\x{6AA0}\x{6AA1}\x{6AA2}\x{6AA3}\x{6AA4}' .
+'\x{6AA5}\x{6AA6}\x{6AA7}\x{6AA8}\x{6AA9}\x{6AAB}\x{6AAC}\x{6AAD}\x{6AAE}' .
+'\x{6AAF}\x{6AB0}\x{6AB2}\x{6AB3}\x{6AB4}\x{6AB5}\x{6AB6}\x{6AB7}\x{6AB8}' .
+'\x{6AB9}\x{6ABA}\x{6ABB}\x{6ABC}\x{6ABD}\x{6ABF}\x{6AC1}\x{6AC2}\x{6AC3}' .
+'\x{6AC5}\x{6AC6}\x{6AC7}\x{6ACA}\x{6ACB}\x{6ACC}\x{6ACD}\x{6ACE}\x{6ACF}' .
+'\x{6AD0}\x{6AD1}\x{6AD2}\x{6AD3}\x{6AD4}\x{6AD5}\x{6AD6}\x{6AD7}\x{6AD9}' .
+'\x{6ADA}\x{6ADB}\x{6ADC}\x{6ADD}\x{6ADE}\x{6ADF}\x{6AE0}\x{6AE1}\x{6AE2}' .
+'\x{6AE3}\x{6AE4}\x{6AE5}\x{6AE6}\x{6AE7}\x{6AE8}\x{6AEA}\x{6AEB}\x{6AEC}' .
+'\x{6AED}\x{6AEE}\x{6AEF}\x{6AF0}\x{6AF1}\x{6AF2}\x{6AF3}\x{6AF4}\x{6AF5}' .
+'\x{6AF6}\x{6AF7}\x{6AF8}\x{6AF9}\x{6AFA}\x{6AFB}\x{6AFC}\x{6AFD}\x{6AFE}' .
+'\x{6AFF}\x{6B00}\x{6B01}\x{6B02}\x{6B03}\x{6B04}\x{6B05}\x{6B06}\x{6B07}' .
+'\x{6B08}\x{6B09}\x{6B0A}\x{6B0B}\x{6B0C}\x{6B0D}\x{6B0F}\x{6B10}\x{6B11}' .
+'\x{6B12}\x{6B13}\x{6B14}\x{6B15}\x{6B16}\x{6B17}\x{6B18}\x{6B19}\x{6B1A}' .
+'\x{6B1C}\x{6B1D}\x{6B1E}\x{6B1F}\x{6B20}\x{6B21}\x{6B22}\x{6B23}\x{6B24}' .
+'\x{6B25}\x{6B26}\x{6B27}\x{6B28}\x{6B29}\x{6B2A}\x{6B2B}\x{6B2C}\x{6B2D}' .
+'\x{6B2F}\x{6B30}\x{6B31}\x{6B32}\x{6B33}\x{6B34}\x{6B36}\x{6B37}\x{6B38}' .
+'\x{6B39}\x{6B3A}\x{6B3B}\x{6B3C}\x{6B3D}\x{6B3E}\x{6B3F}\x{6B41}\x{6B42}' .
+'\x{6B43}\x{6B44}\x{6B45}\x{6B46}\x{6B47}\x{6B48}\x{6B49}\x{6B4A}\x{6B4B}' .
+'\x{6B4C}\x{6B4D}\x{6B4E}\x{6B4F}\x{6B50}\x{6B51}\x{6B52}\x{6B53}\x{6B54}' .
+'\x{6B55}\x{6B56}\x{6B59}\x{6B5A}\x{6B5B}\x{6B5C}\x{6B5E}\x{6B5F}\x{6B60}' .
+'\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B65}\x{6B66}\x{6B67}\x{6B69}\x{6B6A}' .
+'\x{6B6B}\x{6B6D}\x{6B6F}\x{6B70}\x{6B72}\x{6B73}\x{6B74}\x{6B76}\x{6B77}' .
+'\x{6B78}\x{6B79}\x{6B7A}\x{6B7B}\x{6B7C}\x{6B7E}\x{6B7F}\x{6B80}\x{6B81}' .
+'\x{6B82}\x{6B83}\x{6B84}\x{6B85}\x{6B86}\x{6B87}\x{6B88}\x{6B89}\x{6B8A}' .
+'\x{6B8B}\x{6B8C}\x{6B8D}\x{6B8E}\x{6B8F}\x{6B90}\x{6B91}\x{6B92}\x{6B93}' .
+'\x{6B94}\x{6B95}\x{6B96}\x{6B97}\x{6B98}\x{6B99}\x{6B9A}\x{6B9B}\x{6B9C}' .
+'\x{6B9D}\x{6B9E}\x{6B9F}\x{6BA0}\x{6BA1}\x{6BA2}\x{6BA3}\x{6BA4}\x{6BA5}' .
+'\x{6BA6}\x{6BA7}\x{6BA8}\x{6BA9}\x{6BAA}\x{6BAB}\x{6BAC}\x{6BAD}\x{6BAE}' .
+'\x{6BAF}\x{6BB0}\x{6BB2}\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB6}\x{6BB7}\x{6BB9}' .
+'\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBD}\x{6BBE}\x{6BBF}\x{6BC0}\x{6BC1}\x{6BC2}' .
+'\x{6BC3}\x{6BC4}\x{6BC5}\x{6BC6}\x{6BC7}\x{6BC8}\x{6BC9}\x{6BCA}\x{6BCB}' .
+'\x{6BCC}\x{6BCD}\x{6BCE}\x{6BCF}\x{6BD0}\x{6BD1}\x{6BD2}\x{6BD3}\x{6BD4}' .
+'\x{6BD5}\x{6BD6}\x{6BD7}\x{6BD8}\x{6BD9}\x{6BDA}\x{6BDB}\x{6BDC}\x{6BDD}' .
+'\x{6BDE}\x{6BDF}\x{6BE0}\x{6BE1}\x{6BE2}\x{6BE3}\x{6BE4}\x{6BE5}\x{6BE6}' .
+'\x{6BE7}\x{6BE8}\x{6BEA}\x{6BEB}\x{6BEC}\x{6BED}\x{6BEE}\x{6BEF}\x{6BF0}' .
+'\x{6BF2}\x{6BF3}\x{6BF5}\x{6BF6}\x{6BF7}\x{6BF8}\x{6BF9}\x{6BFB}\x{6BFC}' .
+'\x{6BFD}\x{6BFE}\x{6BFF}\x{6C00}\x{6C01}\x{6C02}\x{6C03}\x{6C04}\x{6C05}' .
+'\x{6C06}\x{6C07}\x{6C08}\x{6C09}\x{6C0B}\x{6C0C}\x{6C0D}\x{6C0E}\x{6C0F}' .
+'\x{6C10}\x{6C11}\x{6C12}\x{6C13}\x{6C14}\x{6C15}\x{6C16}\x{6C18}\x{6C19}' .
+'\x{6C1A}\x{6C1B}\x{6C1D}\x{6C1E}\x{6C1F}\x{6C20}\x{6C21}\x{6C22}\x{6C23}' .
+'\x{6C24}\x{6C25}\x{6C26}\x{6C27}\x{6C28}\x{6C29}\x{6C2A}\x{6C2B}\x{6C2C}' .
+'\x{6C2E}\x{6C2F}\x{6C30}\x{6C31}\x{6C32}\x{6C33}\x{6C34}\x{6C35}\x{6C36}' .
+'\x{6C37}\x{6C38}\x{6C3A}\x{6C3B}\x{6C3D}\x{6C3E}\x{6C3F}\x{6C40}\x{6C41}' .
+'\x{6C42}\x{6C43}\x{6C44}\x{6C46}\x{6C47}\x{6C48}\x{6C49}\x{6C4A}\x{6C4B}' .
+'\x{6C4C}\x{6C4D}\x{6C4E}\x{6C4F}\x{6C50}\x{6C51}\x{6C52}\x{6C53}\x{6C54}' .
+'\x{6C55}\x{6C56}\x{6C57}\x{6C58}\x{6C59}\x{6C5A}\x{6C5B}\x{6C5C}\x{6C5D}' .
+'\x{6C5E}\x{6C5F}\x{6C60}\x{6C61}\x{6C62}\x{6C63}\x{6C64}\x{6C65}\x{6C66}' .
+'\x{6C67}\x{6C68}\x{6C69}\x{6C6A}\x{6C6B}\x{6C6D}\x{6C6F}\x{6C70}\x{6C71}' .
+'\x{6C72}\x{6C73}\x{6C74}\x{6C75}\x{6C76}\x{6C77}\x{6C78}\x{6C79}\x{6C7A}' .
+'\x{6C7B}\x{6C7C}\x{6C7D}\x{6C7E}\x{6C7F}\x{6C80}\x{6C81}\x{6C82}\x{6C83}' .
+'\x{6C84}\x{6C85}\x{6C86}\x{6C87}\x{6C88}\x{6C89}\x{6C8A}\x{6C8B}\x{6C8C}' .
+'\x{6C8D}\x{6C8E}\x{6C8F}\x{6C90}\x{6C91}\x{6C92}\x{6C93}\x{6C94}\x{6C95}' .
+'\x{6C96}\x{6C97}\x{6C98}\x{6C99}\x{6C9A}\x{6C9B}\x{6C9C}\x{6C9D}\x{6C9E}' .
+'\x{6C9F}\x{6CA1}\x{6CA2}\x{6CA3}\x{6CA4}\x{6CA5}\x{6CA6}\x{6CA7}\x{6CA8}' .
+'\x{6CA9}\x{6CAA}\x{6CAB}\x{6CAC}\x{6CAD}\x{6CAE}\x{6CAF}\x{6CB0}\x{6CB1}' .
+'\x{6CB2}\x{6CB3}\x{6CB4}\x{6CB5}\x{6CB6}\x{6CB7}\x{6CB8}\x{6CB9}\x{6CBA}' .
+'\x{6CBB}\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC0}\x{6CC1}\x{6CC2}\x{6CC3}' .
+'\x{6CC4}\x{6CC5}\x{6CC6}\x{6CC7}\x{6CC8}\x{6CC9}\x{6CCA}\x{6CCB}\x{6CCC}' .
+'\x{6CCD}\x{6CCE}\x{6CCF}\x{6CD0}\x{6CD1}\x{6CD2}\x{6CD3}\x{6CD4}\x{6CD5}' .
+'\x{6CD6}\x{6CD7}\x{6CD9}\x{6CDA}\x{6CDB}\x{6CDC}\x{6CDD}\x{6CDE}\x{6CDF}' .
+'\x{6CE0}\x{6CE1}\x{6CE2}\x{6CE3}\x{6CE4}\x{6CE5}\x{6CE6}\x{6CE7}\x{6CE8}' .
+'\x{6CE9}\x{6CEA}\x{6CEB}\x{6CEC}\x{6CED}\x{6CEE}\x{6CEF}\x{6CF0}\x{6CF1}' .
+'\x{6CF2}\x{6CF3}\x{6CF5}\x{6CF6}\x{6CF7}\x{6CF8}\x{6CF9}\x{6CFA}\x{6CFB}' .
+'\x{6CFC}\x{6CFD}\x{6CFE}\x{6CFF}\x{6D00}\x{6D01}\x{6D03}\x{6D04}\x{6D05}' .
+'\x{6D06}\x{6D07}\x{6D08}\x{6D09}\x{6D0A}\x{6D0B}\x{6D0C}\x{6D0D}\x{6D0E}' .
+'\x{6D0F}\x{6D10}\x{6D11}\x{6D12}\x{6D13}\x{6D14}\x{6D15}\x{6D16}\x{6D17}' .
+'\x{6D18}\x{6D19}\x{6D1A}\x{6D1B}\x{6D1D}\x{6D1E}\x{6D1F}\x{6D20}\x{6D21}' .
+'\x{6D22}\x{6D23}\x{6D25}\x{6D26}\x{6D27}\x{6D28}\x{6D29}\x{6D2A}\x{6D2B}' .
+'\x{6D2C}\x{6D2D}\x{6D2E}\x{6D2F}\x{6D30}\x{6D31}\x{6D32}\x{6D33}\x{6D34}' .
+'\x{6D35}\x{6D36}\x{6D37}\x{6D38}\x{6D39}\x{6D3A}\x{6D3B}\x{6D3C}\x{6D3D}' .
+'\x{6D3E}\x{6D3F}\x{6D40}\x{6D41}\x{6D42}\x{6D43}\x{6D44}\x{6D45}\x{6D46}' .
+'\x{6D47}\x{6D48}\x{6D49}\x{6D4A}\x{6D4B}\x{6D4C}\x{6D4D}\x{6D4E}\x{6D4F}' .
+'\x{6D50}\x{6D51}\x{6D52}\x{6D53}\x{6D54}\x{6D55}\x{6D56}\x{6D57}\x{6D58}' .
+'\x{6D59}\x{6D5A}\x{6D5B}\x{6D5C}\x{6D5D}\x{6D5E}\x{6D5F}\x{6D60}\x{6D61}' .
+'\x{6D62}\x{6D63}\x{6D64}\x{6D65}\x{6D66}\x{6D67}\x{6D68}\x{6D69}\x{6D6A}' .
+'\x{6D6B}\x{6D6C}\x{6D6D}\x{6D6E}\x{6D6F}\x{6D70}\x{6D72}\x{6D73}\x{6D74}' .
+'\x{6D75}\x{6D76}\x{6D77}\x{6D78}\x{6D79}\x{6D7A}\x{6D7B}\x{6D7C}\x{6D7D}' .
+'\x{6D7E}\x{6D7F}\x{6D80}\x{6D82}\x{6D83}\x{6D84}\x{6D85}\x{6D86}\x{6D87}' .
+'\x{6D88}\x{6D89}\x{6D8A}\x{6D8B}\x{6D8C}\x{6D8D}\x{6D8E}\x{6D8F}\x{6D90}' .
+'\x{6D91}\x{6D92}\x{6D93}\x{6D94}\x{6D95}\x{6D97}\x{6D98}\x{6D99}\x{6D9A}' .
+'\x{6D9B}\x{6D9D}\x{6D9E}\x{6D9F}\x{6DA0}\x{6DA1}\x{6DA2}\x{6DA3}\x{6DA4}' .
+'\x{6DA5}\x{6DA6}\x{6DA7}\x{6DA8}\x{6DA9}\x{6DAA}\x{6DAB}\x{6DAC}\x{6DAD}' .
+'\x{6DAE}\x{6DAF}\x{6DB2}\x{6DB3}\x{6DB4}\x{6DB5}\x{6DB7}\x{6DB8}\x{6DB9}' .
+'\x{6DBA}\x{6DBB}\x{6DBC}\x{6DBD}\x{6DBE}\x{6DBF}\x{6DC0}\x{6DC1}\x{6DC2}' .
+'\x{6DC3}\x{6DC4}\x{6DC5}\x{6DC6}\x{6DC7}\x{6DC8}\x{6DC9}\x{6DCA}\x{6DCB}' .
+'\x{6DCC}\x{6DCD}\x{6DCE}\x{6DCF}\x{6DD0}\x{6DD1}\x{6DD2}\x{6DD3}\x{6DD4}' .
+'\x{6DD5}\x{6DD6}\x{6DD7}\x{6DD8}\x{6DD9}\x{6DDA}\x{6DDB}\x{6DDC}\x{6DDD}' .
+'\x{6DDE}\x{6DDF}\x{6DE0}\x{6DE1}\x{6DE2}\x{6DE3}\x{6DE4}\x{6DE5}\x{6DE6}' .
+'\x{6DE7}\x{6DE8}\x{6DE9}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DED}\x{6DEE}\x{6DEF}' .
+'\x{6DF0}\x{6DF1}\x{6DF2}\x{6DF3}\x{6DF4}\x{6DF5}\x{6DF6}\x{6DF7}\x{6DF8}' .
+'\x{6DF9}\x{6DFA}\x{6DFB}\x{6DFC}\x{6DFD}\x{6E00}\x{6E03}\x{6E04}\x{6E05}' .
+'\x{6E07}\x{6E08}\x{6E09}\x{6E0A}\x{6E0B}\x{6E0C}\x{6E0D}\x{6E0E}\x{6E0F}' .
+'\x{6E10}\x{6E11}\x{6E14}\x{6E15}\x{6E16}\x{6E17}\x{6E19}\x{6E1A}\x{6E1B}' .
+'\x{6E1C}\x{6E1D}\x{6E1E}\x{6E1F}\x{6E20}\x{6E21}\x{6E22}\x{6E23}\x{6E24}' .
+'\x{6E25}\x{6E26}\x{6E27}\x{6E28}\x{6E29}\x{6E2B}\x{6E2C}\x{6E2D}\x{6E2E}' .
+'\x{6E2F}\x{6E30}\x{6E31}\x{6E32}\x{6E33}\x{6E34}\x{6E35}\x{6E36}\x{6E37}' .
+'\x{6E38}\x{6E39}\x{6E3A}\x{6E3B}\x{6E3C}\x{6E3D}\x{6E3E}\x{6E3F}\x{6E40}' .
+'\x{6E41}\x{6E42}\x{6E43}\x{6E44}\x{6E45}\x{6E46}\x{6E47}\x{6E48}\x{6E49}' .
+'\x{6E4A}\x{6E4B}\x{6E4D}\x{6E4E}\x{6E4F}\x{6E50}\x{6E51}\x{6E52}\x{6E53}' .
+'\x{6E54}\x{6E55}\x{6E56}\x{6E57}\x{6E58}\x{6E59}\x{6E5A}\x{6E5B}\x{6E5C}' .
+'\x{6E5D}\x{6E5E}\x{6E5F}\x{6E60}\x{6E61}\x{6E62}\x{6E63}\x{6E64}\x{6E65}' .
+'\x{6E66}\x{6E67}\x{6E68}\x{6E69}\x{6E6A}\x{6E6B}\x{6E6D}\x{6E6E}\x{6E6F}' .
+'\x{6E70}\x{6E71}\x{6E72}\x{6E73}\x{6E74}\x{6E75}\x{6E77}\x{6E78}\x{6E79}' .
+'\x{6E7E}\x{6E7F}\x{6E80}\x{6E81}\x{6E82}\x{6E83}\x{6E84}\x{6E85}\x{6E86}' .
+'\x{6E87}\x{6E88}\x{6E89}\x{6E8A}\x{6E8D}\x{6E8E}\x{6E8F}\x{6E90}\x{6E91}' .
+'\x{6E92}\x{6E93}\x{6E94}\x{6E96}\x{6E97}\x{6E98}\x{6E99}\x{6E9A}\x{6E9B}' .
+'\x{6E9C}\x{6E9D}\x{6E9E}\x{6E9F}\x{6EA0}\x{6EA1}\x{6EA2}\x{6EA3}\x{6EA4}' .
+'\x{6EA5}\x{6EA6}\x{6EA7}\x{6EA8}\x{6EA9}\x{6EAA}\x{6EAB}\x{6EAC}\x{6EAD}' .
+'\x{6EAE}\x{6EAF}\x{6EB0}\x{6EB1}\x{6EB2}\x{6EB3}\x{6EB4}\x{6EB5}\x{6EB6}' .
+'\x{6EB7}\x{6EB8}\x{6EB9}\x{6EBA}\x{6EBB}\x{6EBC}\x{6EBD}\x{6EBE}\x{6EBF}' .
+'\x{6EC0}\x{6EC1}\x{6EC2}\x{6EC3}\x{6EC4}\x{6EC5}\x{6EC6}\x{6EC7}\x{6EC8}' .
+'\x{6EC9}\x{6ECA}\x{6ECB}\x{6ECC}\x{6ECD}\x{6ECE}\x{6ECF}\x{6ED0}\x{6ED1}' .
+'\x{6ED2}\x{6ED3}\x{6ED4}\x{6ED5}\x{6ED6}\x{6ED7}\x{6ED8}\x{6ED9}\x{6EDA}' .
+'\x{6EDC}\x{6EDE}\x{6EDF}\x{6EE0}\x{6EE1}\x{6EE2}\x{6EE4}\x{6EE5}\x{6EE6}' .
+'\x{6EE7}\x{6EE8}\x{6EE9}\x{6EEA}\x{6EEB}\x{6EEC}\x{6EED}\x{6EEE}\x{6EEF}' .
+'\x{6EF0}\x{6EF1}\x{6EF2}\x{6EF3}\x{6EF4}\x{6EF5}\x{6EF6}\x{6EF7}\x{6EF8}' .
+'\x{6EF9}\x{6EFA}\x{6EFB}\x{6EFC}\x{6EFD}\x{6EFE}\x{6EFF}\x{6F00}\x{6F01}' .
+'\x{6F02}\x{6F03}\x{6F05}\x{6F06}\x{6F07}\x{6F08}\x{6F09}\x{6F0A}\x{6F0C}' .
+'\x{6F0D}\x{6F0E}\x{6F0F}\x{6F10}\x{6F11}\x{6F12}\x{6F13}\x{6F14}\x{6F15}' .
+'\x{6F16}\x{6F17}\x{6F18}\x{6F19}\x{6F1A}\x{6F1B}\x{6F1C}\x{6F1D}\x{6F1E}' .
+'\x{6F1F}\x{6F20}\x{6F21}\x{6F22}\x{6F23}\x{6F24}\x{6F25}\x{6F26}\x{6F27}' .
+'\x{6F28}\x{6F29}\x{6F2A}\x{6F2B}\x{6F2C}\x{6F2D}\x{6F2E}\x{6F2F}\x{6F30}' .
+'\x{6F31}\x{6F32}\x{6F33}\x{6F34}\x{6F35}\x{6F36}\x{6F37}\x{6F38}\x{6F39}' .
+'\x{6F3A}\x{6F3B}\x{6F3C}\x{6F3D}\x{6F3E}\x{6F3F}\x{6F40}\x{6F41}\x{6F43}' .
+'\x{6F44}\x{6F45}\x{6F46}\x{6F47}\x{6F49}\x{6F4B}\x{6F4C}\x{6F4D}\x{6F4E}' .
+'\x{6F4F}\x{6F50}\x{6F51}\x{6F52}\x{6F53}\x{6F54}\x{6F55}\x{6F56}\x{6F57}' .
+'\x{6F58}\x{6F59}\x{6F5A}\x{6F5B}\x{6F5C}\x{6F5D}\x{6F5E}\x{6F5F}\x{6F60}' .
+'\x{6F61}\x{6F62}\x{6F63}\x{6F64}\x{6F65}\x{6F66}\x{6F67}\x{6F68}\x{6F69}' .
+'\x{6F6A}\x{6F6B}\x{6F6C}\x{6F6D}\x{6F6E}\x{6F6F}\x{6F70}\x{6F71}\x{6F72}' .
+'\x{6F73}\x{6F74}\x{6F75}\x{6F76}\x{6F77}\x{6F78}\x{6F7A}\x{6F7B}\x{6F7C}' .
+'\x{6F7D}\x{6F7E}\x{6F7F}\x{6F80}\x{6F81}\x{6F82}\x{6F83}\x{6F84}\x{6F85}' .
+'\x{6F86}\x{6F87}\x{6F88}\x{6F89}\x{6F8A}\x{6F8B}\x{6F8C}\x{6F8D}\x{6F8E}' .
+'\x{6F8F}\x{6F90}\x{6F91}\x{6F92}\x{6F93}\x{6F94}\x{6F95}\x{6F96}\x{6F97}' .
+'\x{6F99}\x{6F9B}\x{6F9C}\x{6F9D}\x{6F9E}\x{6FA0}\x{6FA1}\x{6FA2}\x{6FA3}' .
+'\x{6FA4}\x{6FA5}\x{6FA6}\x{6FA7}\x{6FA8}\x{6FA9}\x{6FAA}\x{6FAB}\x{6FAC}' .
+'\x{6FAD}\x{6FAE}\x{6FAF}\x{6FB0}\x{6FB1}\x{6FB2}\x{6FB3}\x{6FB4}\x{6FB5}' .
+'\x{6FB6}\x{6FB8}\x{6FB9}\x{6FBA}\x{6FBB}\x{6FBC}\x{6FBD}\x{6FBE}\x{6FBF}' .
+'\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC4}\x{6FC6}\x{6FC7}\x{6FC8}\x{6FC9}' .
+'\x{6FCA}\x{6FCB}\x{6FCC}\x{6FCD}\x{6FCE}\x{6FCF}\x{6FD1}\x{6FD2}\x{6FD4}' .
+'\x{6FD5}\x{6FD6}\x{6FD7}\x{6FD8}\x{6FD9}\x{6FDA}\x{6FDB}\x{6FDC}\x{6FDD}' .
+'\x{6FDE}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE2}\x{6FE3}\x{6FE4}\x{6FE5}\x{6FE6}' .
+'\x{6FE7}\x{6FE8}\x{6FE9}\x{6FEA}\x{6FEB}\x{6FEC}\x{6FED}\x{6FEE}\x{6FEF}' .
+'\x{6FF0}\x{6FF1}\x{6FF2}\x{6FF3}\x{6FF4}\x{6FF6}\x{6FF7}\x{6FF8}\x{6FF9}' .
+'\x{6FFA}\x{6FFB}\x{6FFC}\x{6FFE}\x{6FFF}\x{7000}\x{7001}\x{7002}\x{7003}' .
+'\x{7004}\x{7005}\x{7006}\x{7007}\x{7008}\x{7009}\x{700A}\x{700B}\x{700C}' .
+'\x{700D}\x{700E}\x{700F}\x{7011}\x{7012}\x{7014}\x{7015}\x{7016}\x{7017}' .
+'\x{7018}\x{7019}\x{701A}\x{701B}\x{701C}\x{701D}\x{701F}\x{7020}\x{7021}' .
+'\x{7022}\x{7023}\x{7024}\x{7025}\x{7026}\x{7027}\x{7028}\x{7029}\x{702A}' .
+'\x{702B}\x{702C}\x{702D}\x{702E}\x{702F}\x{7030}\x{7031}\x{7032}\x{7033}' .
+'\x{7034}\x{7035}\x{7036}\x{7037}\x{7038}\x{7039}\x{703A}\x{703B}\x{703C}' .
+'\x{703D}\x{703E}\x{703F}\x{7040}\x{7041}\x{7042}\x{7043}\x{7044}\x{7045}' .
+'\x{7046}\x{7048}\x{7049}\x{704A}\x{704C}\x{704D}\x{704F}\x{7050}\x{7051}' .
+'\x{7052}\x{7053}\x{7054}\x{7055}\x{7056}\x{7057}\x{7058}\x{7059}\x{705A}' .
+'\x{705B}\x{705C}\x{705D}\x{705E}\x{705F}\x{7060}\x{7061}\x{7062}\x{7063}' .
+'\x{7064}\x{7065}\x{7066}\x{7067}\x{7068}\x{7069}\x{706A}\x{706B}\x{706C}' .
+'\x{706D}\x{706E}\x{706F}\x{7070}\x{7071}\x{7074}\x{7075}\x{7076}\x{7077}' .
+'\x{7078}\x{7079}\x{707A}\x{707C}\x{707D}\x{707E}\x{707F}\x{7080}\x{7082}' .
+'\x{7083}\x{7084}\x{7085}\x{7086}\x{7087}\x{7088}\x{7089}\x{708A}\x{708B}' .
+'\x{708C}\x{708E}\x{708F}\x{7090}\x{7091}\x{7092}\x{7093}\x{7094}\x{7095}' .
+'\x{7096}\x{7098}\x{7099}\x{709A}\x{709C}\x{709D}\x{709E}\x{709F}\x{70A0}' .
+'\x{70A1}\x{70A2}\x{70A3}\x{70A4}\x{70A5}\x{70A6}\x{70A7}\x{70A8}\x{70A9}' .
+'\x{70AB}\x{70AC}\x{70AD}\x{70AE}\x{70AF}\x{70B0}\x{70B1}\x{70B3}\x{70B4}' .
+'\x{70B5}\x{70B7}\x{70B8}\x{70B9}\x{70BA}\x{70BB}\x{70BC}\x{70BD}\x{70BE}' .
+'\x{70BF}\x{70C0}\x{70C1}\x{70C2}\x{70C3}\x{70C4}\x{70C5}\x{70C6}\x{70C7}' .
+'\x{70C8}\x{70C9}\x{70CA}\x{70CB}\x{70CC}\x{70CD}\x{70CE}\x{70CF}\x{70D0}' .
+'\x{70D1}\x{70D2}\x{70D3}\x{70D4}\x{70D6}\x{70D7}\x{70D8}\x{70D9}\x{70DA}' .
+'\x{70DB}\x{70DC}\x{70DD}\x{70DE}\x{70DF}\x{70E0}\x{70E1}\x{70E2}\x{70E3}' .
+'\x{70E4}\x{70E5}\x{70E6}\x{70E7}\x{70E8}\x{70E9}\x{70EA}\x{70EB}\x{70EC}' .
+'\x{70ED}\x{70EE}\x{70EF}\x{70F0}\x{70F1}\x{70F2}\x{70F3}\x{70F4}\x{70F5}' .
+'\x{70F6}\x{70F7}\x{70F8}\x{70F9}\x{70FA}\x{70FB}\x{70FC}\x{70FD}\x{70FF}' .
+'\x{7100}\x{7101}\x{7102}\x{7103}\x{7104}\x{7105}\x{7106}\x{7107}\x{7109}' .
+'\x{710A}\x{710B}\x{710C}\x{710D}\x{710E}\x{710F}\x{7110}\x{7111}\x{7112}' .
+'\x{7113}\x{7115}\x{7116}\x{7117}\x{7118}\x{7119}\x{711A}\x{711B}\x{711C}' .
+'\x{711D}\x{711E}\x{711F}\x{7120}\x{7121}\x{7122}\x{7123}\x{7125}\x{7126}' .
+'\x{7127}\x{7128}\x{7129}\x{712A}\x{712B}\x{712C}\x{712D}\x{712E}\x{712F}' .
+'\x{7130}\x{7131}\x{7132}\x{7135}\x{7136}\x{7137}\x{7138}\x{7139}\x{713A}' .
+'\x{713B}\x{713D}\x{713E}\x{713F}\x{7140}\x{7141}\x{7142}\x{7143}\x{7144}' .
+'\x{7145}\x{7146}\x{7147}\x{7148}\x{7149}\x{714A}\x{714B}\x{714C}\x{714D}' .
+'\x{714E}\x{714F}\x{7150}\x{7151}\x{7152}\x{7153}\x{7154}\x{7156}\x{7158}' .
+'\x{7159}\x{715A}\x{715B}\x{715C}\x{715D}\x{715E}\x{715F}\x{7160}\x{7161}' .
+'\x{7162}\x{7163}\x{7164}\x{7165}\x{7166}\x{7167}\x{7168}\x{7169}\x{716A}' .
+'\x{716C}\x{716E}\x{716F}\x{7170}\x{7171}\x{7172}\x{7173}\x{7174}\x{7175}' .
+'\x{7176}\x{7177}\x{7178}\x{7179}\x{717A}\x{717B}\x{717C}\x{717D}\x{717E}' .
+'\x{717F}\x{7180}\x{7181}\x{7182}\x{7183}\x{7184}\x{7185}\x{7186}\x{7187}' .
+'\x{7188}\x{7189}\x{718A}\x{718B}\x{718C}\x{718E}\x{718F}\x{7190}\x{7191}' .
+'\x{7192}\x{7193}\x{7194}\x{7195}\x{7197}\x{7198}\x{7199}\x{719A}\x{719B}' .
+'\x{719C}\x{719D}\x{719E}\x{719F}\x{71A0}\x{71A1}\x{71A2}\x{71A3}\x{71A4}' .
+'\x{71A5}\x{71A7}\x{71A8}\x{71A9}\x{71AA}\x{71AC}\x{71AD}\x{71AE}\x{71AF}' .
+'\x{71B0}\x{71B1}\x{71B2}\x{71B3}\x{71B4}\x{71B5}\x{71B7}\x{71B8}\x{71B9}' .
+'\x{71BA}\x{71BB}\x{71BC}\x{71BD}\x{71BE}\x{71BF}\x{71C0}\x{71C1}\x{71C2}' .
+'\x{71C3}\x{71C4}\x{71C5}\x{71C6}\x{71C7}\x{71C8}\x{71C9}\x{71CA}\x{71CB}' .
+'\x{71CD}\x{71CE}\x{71CF}\x{71D0}\x{71D1}\x{71D2}\x{71D4}\x{71D5}\x{71D6}' .
+'\x{71D7}\x{71D8}\x{71D9}\x{71DA}\x{71DB}\x{71DC}\x{71DD}\x{71DE}\x{71DF}' .
+'\x{71E0}\x{71E1}\x{71E2}\x{71E3}\x{71E4}\x{71E5}\x{71E6}\x{71E7}\x{71E8}' .
+'\x{71E9}\x{71EA}\x{71EB}\x{71EC}\x{71ED}\x{71EE}\x{71EF}\x{71F0}\x{71F1}' .
+'\x{71F2}\x{71F4}\x{71F5}\x{71F6}\x{71F7}\x{71F8}\x{71F9}\x{71FB}\x{71FC}' .
+'\x{71FD}\x{71FE}\x{71FF}\x{7201}\x{7202}\x{7203}\x{7204}\x{7205}\x{7206}' .
+'\x{7207}\x{7208}\x{7209}\x{720A}\x{720C}\x{720D}\x{720E}\x{720F}\x{7210}' .
+'\x{7212}\x{7213}\x{7214}\x{7216}\x{7218}\x{7219}\x{721A}\x{721B}\x{721C}' .
+'\x{721D}\x{721E}\x{721F}\x{7221}\x{7222}\x{7223}\x{7226}\x{7227}\x{7228}' .
+'\x{7229}\x{722A}\x{722B}\x{722C}\x{722D}\x{722E}\x{7230}\x{7231}\x{7232}' .
+'\x{7233}\x{7235}\x{7236}\x{7237}\x{7238}\x{7239}\x{723A}\x{723B}\x{723C}' .
+'\x{723D}\x{723E}\x{723F}\x{7240}\x{7241}\x{7242}\x{7243}\x{7244}\x{7246}' .
+'\x{7247}\x{7248}\x{7249}\x{724A}\x{724B}\x{724C}\x{724D}\x{724F}\x{7251}' .
+'\x{7252}\x{7253}\x{7254}\x{7256}\x{7257}\x{7258}\x{7259}\x{725A}\x{725B}' .
+'\x{725C}\x{725D}\x{725E}\x{725F}\x{7260}\x{7261}\x{7262}\x{7263}\x{7264}' .
+'\x{7265}\x{7266}\x{7267}\x{7268}\x{7269}\x{726A}\x{726B}\x{726C}\x{726D}' .
+'\x{726E}\x{726F}\x{7270}\x{7271}\x{7272}\x{7273}\x{7274}\x{7275}\x{7276}' .
+'\x{7277}\x{7278}\x{7279}\x{727A}\x{727B}\x{727C}\x{727D}\x{727E}\x{727F}' .
+'\x{7280}\x{7281}\x{7282}\x{7283}\x{7284}\x{7285}\x{7286}\x{7287}\x{7288}' .
+'\x{7289}\x{728A}\x{728B}\x{728C}\x{728D}\x{728E}\x{728F}\x{7290}\x{7291}' .
+'\x{7292}\x{7293}\x{7294}\x{7295}\x{7296}\x{7297}\x{7298}\x{7299}\x{729A}' .
+'\x{729B}\x{729C}\x{729D}\x{729E}\x{729F}\x{72A1}\x{72A2}\x{72A3}\x{72A4}' .
+'\x{72A5}\x{72A6}\x{72A7}\x{72A8}\x{72A9}\x{72AA}\x{72AC}\x{72AD}\x{72AE}' .
+'\x{72AF}\x{72B0}\x{72B1}\x{72B2}\x{72B3}\x{72B4}\x{72B5}\x{72B6}\x{72B7}' .
+'\x{72B8}\x{72B9}\x{72BA}\x{72BB}\x{72BC}\x{72BD}\x{72BF}\x{72C0}\x{72C1}' .
+'\x{72C2}\x{72C3}\x{72C4}\x{72C5}\x{72C6}\x{72C7}\x{72C8}\x{72C9}\x{72CA}' .
+'\x{72CB}\x{72CC}\x{72CD}\x{72CE}\x{72CF}\x{72D0}\x{72D1}\x{72D2}\x{72D3}' .
+'\x{72D4}\x{72D5}\x{72D6}\x{72D7}\x{72D8}\x{72D9}\x{72DA}\x{72DB}\x{72DC}' .
+'\x{72DD}\x{72DE}\x{72DF}\x{72E0}\x{72E1}\x{72E2}\x{72E3}\x{72E4}\x{72E5}' .
+'\x{72E6}\x{72E7}\x{72E8}\x{72E9}\x{72EA}\x{72EB}\x{72EC}\x{72ED}\x{72EE}' .
+'\x{72EF}\x{72F0}\x{72F1}\x{72F2}\x{72F3}\x{72F4}\x{72F5}\x{72F6}\x{72F7}' .
+'\x{72F8}\x{72F9}\x{72FA}\x{72FB}\x{72FC}\x{72FD}\x{72FE}\x{72FF}\x{7300}' .
+'\x{7301}\x{7303}\x{7304}\x{7305}\x{7306}\x{7307}\x{7308}\x{7309}\x{730A}' .
+'\x{730B}\x{730C}\x{730D}\x{730E}\x{730F}\x{7311}\x{7312}\x{7313}\x{7314}' .
+'\x{7315}\x{7316}\x{7317}\x{7318}\x{7319}\x{731A}\x{731B}\x{731C}\x{731D}' .
+'\x{731E}\x{7320}\x{7321}\x{7322}\x{7323}\x{7324}\x{7325}\x{7326}\x{7327}' .
+'\x{7329}\x{732A}\x{732B}\x{732C}\x{732D}\x{732E}\x{7330}\x{7331}\x{7332}' .
+'\x{7333}\x{7334}\x{7335}\x{7336}\x{7337}\x{7338}\x{7339}\x{733A}\x{733B}' .
+'\x{733C}\x{733D}\x{733E}\x{733F}\x{7340}\x{7341}\x{7342}\x{7343}\x{7344}' .
+'\x{7345}\x{7346}\x{7347}\x{7348}\x{7349}\x{734A}\x{734B}\x{734C}\x{734D}' .
+'\x{734E}\x{7350}\x{7351}\x{7352}\x{7354}\x{7355}\x{7356}\x{7357}\x{7358}' .
+'\x{7359}\x{735A}\x{735B}\x{735C}\x{735D}\x{735E}\x{735F}\x{7360}\x{7361}' .
+'\x{7362}\x{7364}\x{7365}\x{7366}\x{7367}\x{7368}\x{7369}\x{736A}\x{736B}' .
+'\x{736C}\x{736D}\x{736E}\x{736F}\x{7370}\x{7371}\x{7372}\x{7373}\x{7374}' .
+'\x{7375}\x{7376}\x{7377}\x{7378}\x{7379}\x{737A}\x{737B}\x{737C}\x{737D}' .
+'\x{737E}\x{737F}\x{7380}\x{7381}\x{7382}\x{7383}\x{7384}\x{7385}\x{7386}' .
+'\x{7387}\x{7388}\x{7389}\x{738A}\x{738B}\x{738C}\x{738D}\x{738E}\x{738F}' .
+'\x{7390}\x{7391}\x{7392}\x{7393}\x{7394}\x{7395}\x{7396}\x{7397}\x{7398}' .
+'\x{7399}\x{739A}\x{739B}\x{739D}\x{739E}\x{739F}\x{73A0}\x{73A1}\x{73A2}' .
+'\x{73A3}\x{73A4}\x{73A5}\x{73A6}\x{73A7}\x{73A8}\x{73A9}\x{73AA}\x{73AB}' .
+'\x{73AC}\x{73AD}\x{73AE}\x{73AF}\x{73B0}\x{73B1}\x{73B2}\x{73B3}\x{73B4}' .
+'\x{73B5}\x{73B6}\x{73B7}\x{73B8}\x{73B9}\x{73BA}\x{73BB}\x{73BC}\x{73BD}' .
+'\x{73BE}\x{73BF}\x{73C0}\x{73C2}\x{73C3}\x{73C4}\x{73C5}\x{73C6}\x{73C7}' .
+'\x{73C8}\x{73C9}\x{73CA}\x{73CB}\x{73CC}\x{73CD}\x{73CE}\x{73CF}\x{73D0}' .
+'\x{73D1}\x{73D2}\x{73D3}\x{73D4}\x{73D5}\x{73D6}\x{73D7}\x{73D8}\x{73D9}' .
+'\x{73DA}\x{73DB}\x{73DC}\x{73DD}\x{73DE}\x{73DF}\x{73E0}\x{73E2}\x{73E3}' .
+'\x{73E5}\x{73E6}\x{73E7}\x{73E8}\x{73E9}\x{73EA}\x{73EB}\x{73EC}\x{73ED}' .
+'\x{73EE}\x{73EF}\x{73F0}\x{73F1}\x{73F2}\x{73F4}\x{73F5}\x{73F6}\x{73F7}' .
+'\x{73F8}\x{73F9}\x{73FA}\x{73FC}\x{73FD}\x{73FE}\x{73FF}\x{7400}\x{7401}' .
+'\x{7402}\x{7403}\x{7404}\x{7405}\x{7406}\x{7407}\x{7408}\x{7409}\x{740A}' .
+'\x{740B}\x{740C}\x{740D}\x{740E}\x{740F}\x{7410}\x{7411}\x{7412}\x{7413}' .
+'\x{7414}\x{7415}\x{7416}\x{7417}\x{7419}\x{741A}\x{741B}\x{741C}\x{741D}' .
+'\x{741E}\x{741F}\x{7420}\x{7421}\x{7422}\x{7423}\x{7424}\x{7425}\x{7426}' .
+'\x{7427}\x{7428}\x{7429}\x{742A}\x{742B}\x{742C}\x{742D}\x{742E}\x{742F}' .
+'\x{7430}\x{7431}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{7437}\x{7438}' .
+'\x{743A}\x{743B}\x{743C}\x{743D}\x{743F}\x{7440}\x{7441}\x{7442}\x{7443}' .
+'\x{7444}\x{7445}\x{7446}\x{7448}\x{744A}\x{744B}\x{744C}\x{744D}\x{744E}' .
+'\x{744F}\x{7450}\x{7451}\x{7452}\x{7453}\x{7454}\x{7455}\x{7456}\x{7457}' .
+'\x{7459}\x{745A}\x{745B}\x{745C}\x{745D}\x{745E}\x{745F}\x{7461}\x{7462}' .
+'\x{7463}\x{7464}\x{7465}\x{7466}\x{7467}\x{7468}\x{7469}\x{746A}\x{746B}' .
+'\x{746C}\x{746D}\x{746E}\x{746F}\x{7470}\x{7471}\x{7472}\x{7473}\x{7474}' .
+'\x{7475}\x{7476}\x{7477}\x{7478}\x{7479}\x{747A}\x{747C}\x{747D}\x{747E}' .
+'\x{747F}\x{7480}\x{7481}\x{7482}\x{7483}\x{7485}\x{7486}\x{7487}\x{7488}' .
+'\x{7489}\x{748A}\x{748B}\x{748C}\x{748D}\x{748E}\x{748F}\x{7490}\x{7491}' .
+'\x{7492}\x{7493}\x{7494}\x{7495}\x{7497}\x{7498}\x{7499}\x{749A}\x{749B}' .
+'\x{749C}\x{749E}\x{749F}\x{74A0}\x{74A1}\x{74A3}\x{74A4}\x{74A5}\x{74A6}' .
+'\x{74A7}\x{74A8}\x{74A9}\x{74AA}\x{74AB}\x{74AC}\x{74AD}\x{74AE}\x{74AF}' .
+'\x{74B0}\x{74B1}\x{74B2}\x{74B3}\x{74B4}\x{74B5}\x{74B6}\x{74B7}\x{74B8}' .
+'\x{74B9}\x{74BA}\x{74BB}\x{74BC}\x{74BD}\x{74BE}\x{74BF}\x{74C0}\x{74C1}' .
+'\x{74C2}\x{74C3}\x{74C4}\x{74C5}\x{74C6}\x{74CA}\x{74CB}\x{74CD}\x{74CE}' .
+'\x{74CF}\x{74D0}\x{74D1}\x{74D2}\x{74D3}\x{74D4}\x{74D5}\x{74D6}\x{74D7}' .
+'\x{74D8}\x{74D9}\x{74DA}\x{74DB}\x{74DC}\x{74DD}\x{74DE}\x{74DF}\x{74E0}' .
+'\x{74E1}\x{74E2}\x{74E3}\x{74E4}\x{74E5}\x{74E6}\x{74E7}\x{74E8}\x{74E9}' .
+'\x{74EA}\x{74EC}\x{74ED}\x{74EE}\x{74EF}\x{74F0}\x{74F1}\x{74F2}\x{74F3}' .
+'\x{74F4}\x{74F5}\x{74F6}\x{74F7}\x{74F8}\x{74F9}\x{74FA}\x{74FB}\x{74FC}' .
+'\x{74FD}\x{74FE}\x{74FF}\x{7500}\x{7501}\x{7502}\x{7503}\x{7504}\x{7505}' .
+'\x{7506}\x{7507}\x{7508}\x{7509}\x{750A}\x{750B}\x{750C}\x{750D}\x{750F}' .
+'\x{7510}\x{7511}\x{7512}\x{7513}\x{7514}\x{7515}\x{7516}\x{7517}\x{7518}' .
+'\x{7519}\x{751A}\x{751B}\x{751C}\x{751D}\x{751E}\x{751F}\x{7521}\x{7522}' .
+'\x{7523}\x{7524}\x{7525}\x{7526}\x{7527}\x{7528}\x{7529}\x{752A}\x{752B}' .
+'\x{752C}\x{752D}\x{752E}\x{752F}\x{7530}\x{7531}\x{7532}\x{7533}\x{7535}' .
+'\x{7536}\x{7537}\x{7538}\x{7539}\x{753A}\x{753B}\x{753C}\x{753D}\x{753E}' .
+'\x{753F}\x{7540}\x{7542}\x{7543}\x{7544}\x{7545}\x{7546}\x{7547}\x{7548}' .
+'\x{7549}\x{754B}\x{754C}\x{754D}\x{754E}\x{754F}\x{7550}\x{7551}\x{7553}' .
+'\x{7554}\x{7556}\x{7557}\x{7558}\x{7559}\x{755A}\x{755B}\x{755C}\x{755D}' .
+'\x{755F}\x{7560}\x{7562}\x{7563}\x{7564}\x{7565}\x{7566}\x{7567}\x{7568}' .
+'\x{7569}\x{756A}\x{756B}\x{756C}\x{756D}\x{756E}\x{756F}\x{7570}\x{7572}' .
+'\x{7574}\x{7575}\x{7576}\x{7577}\x{7578}\x{7579}\x{757C}\x{757D}\x{757E}' .
+'\x{757F}\x{7580}\x{7581}\x{7582}\x{7583}\x{7584}\x{7586}\x{7587}\x{7588}' .
+'\x{7589}\x{758A}\x{758B}\x{758C}\x{758D}\x{758F}\x{7590}\x{7591}\x{7592}' .
+'\x{7593}\x{7594}\x{7595}\x{7596}\x{7597}\x{7598}\x{7599}\x{759A}\x{759B}' .
+'\x{759C}\x{759D}\x{759E}\x{759F}\x{75A0}\x{75A1}\x{75A2}\x{75A3}\x{75A4}' .
+'\x{75A5}\x{75A6}\x{75A7}\x{75A8}\x{75AA}\x{75AB}\x{75AC}\x{75AD}\x{75AE}' .
+'\x{75AF}\x{75B0}\x{75B1}\x{75B2}\x{75B3}\x{75B4}\x{75B5}\x{75B6}\x{75B8}' .
+'\x{75B9}\x{75BA}\x{75BB}\x{75BC}\x{75BD}\x{75BE}\x{75BF}\x{75C0}\x{75C1}' .
+'\x{75C2}\x{75C3}\x{75C4}\x{75C5}\x{75C6}\x{75C7}\x{75C8}\x{75C9}\x{75CA}' .
+'\x{75CB}\x{75CC}\x{75CD}\x{75CE}\x{75CF}\x{75D0}\x{75D1}\x{75D2}\x{75D3}' .
+'\x{75D4}\x{75D5}\x{75D6}\x{75D7}\x{75D8}\x{75D9}\x{75DA}\x{75DB}\x{75DD}' .
+'\x{75DE}\x{75DF}\x{75E0}\x{75E1}\x{75E2}\x{75E3}\x{75E4}\x{75E5}\x{75E6}' .
+'\x{75E7}\x{75E8}\x{75EA}\x{75EB}\x{75EC}\x{75ED}\x{75EF}\x{75F0}\x{75F1}' .
+'\x{75F2}\x{75F3}\x{75F4}\x{75F5}\x{75F6}\x{75F7}\x{75F8}\x{75F9}\x{75FA}' .
+'\x{75FB}\x{75FC}\x{75FD}\x{75FE}\x{75FF}\x{7600}\x{7601}\x{7602}\x{7603}' .
+'\x{7604}\x{7605}\x{7606}\x{7607}\x{7608}\x{7609}\x{760A}\x{760B}\x{760C}' .
+'\x{760D}\x{760E}\x{760F}\x{7610}\x{7611}\x{7612}\x{7613}\x{7614}\x{7615}' .
+'\x{7616}\x{7617}\x{7618}\x{7619}\x{761A}\x{761B}\x{761C}\x{761D}\x{761E}' .
+'\x{761F}\x{7620}\x{7621}\x{7622}\x{7623}\x{7624}\x{7625}\x{7626}\x{7627}' .
+'\x{7628}\x{7629}\x{762A}\x{762B}\x{762D}\x{762E}\x{762F}\x{7630}\x{7631}' .
+'\x{7632}\x{7633}\x{7634}\x{7635}\x{7636}\x{7637}\x{7638}\x{7639}\x{763A}' .
+'\x{763B}\x{763C}\x{763D}\x{763E}\x{763F}\x{7640}\x{7641}\x{7642}\x{7643}' .
+'\x{7646}\x{7647}\x{7648}\x{7649}\x{764A}\x{764B}\x{764C}\x{764D}\x{764F}' .
+'\x{7650}\x{7652}\x{7653}\x{7654}\x{7656}\x{7657}\x{7658}\x{7659}\x{765A}' .
+'\x{765B}\x{765C}\x{765D}\x{765E}\x{765F}\x{7660}\x{7661}\x{7662}\x{7663}' .
+'\x{7664}\x{7665}\x{7666}\x{7667}\x{7668}\x{7669}\x{766A}\x{766B}\x{766C}' .
+'\x{766D}\x{766E}\x{766F}\x{7670}\x{7671}\x{7672}\x{7674}\x{7675}\x{7676}' .
+'\x{7677}\x{7678}\x{7679}\x{767B}\x{767C}\x{767D}\x{767E}\x{767F}\x{7680}' .
+'\x{7681}\x{7682}\x{7683}\x{7684}\x{7685}\x{7686}\x{7687}\x{7688}\x{7689}' .
+'\x{768A}\x{768B}\x{768C}\x{768E}\x{768F}\x{7690}\x{7691}\x{7692}\x{7693}' .
+'\x{7694}\x{7695}\x{7696}\x{7697}\x{7698}\x{7699}\x{769A}\x{769B}\x{769C}' .
+'\x{769D}\x{769E}\x{769F}\x{76A0}\x{76A3}\x{76A4}\x{76A6}\x{76A7}\x{76A9}' .
+'\x{76AA}\x{76AB}\x{76AC}\x{76AD}\x{76AE}\x{76AF}\x{76B0}\x{76B1}\x{76B2}' .
+'\x{76B4}\x{76B5}\x{76B7}\x{76B8}\x{76BA}\x{76BB}\x{76BC}\x{76BD}\x{76BE}' .
+'\x{76BF}\x{76C0}\x{76C2}\x{76C3}\x{76C4}\x{76C5}\x{76C6}\x{76C7}\x{76C8}' .
+'\x{76C9}\x{76CA}\x{76CD}\x{76CE}\x{76CF}\x{76D0}\x{76D1}\x{76D2}\x{76D3}' .
+'\x{76D4}\x{76D5}\x{76D6}\x{76D7}\x{76D8}\x{76DA}\x{76DB}\x{76DC}\x{76DD}' .
+'\x{76DE}\x{76DF}\x{76E0}\x{76E1}\x{76E2}\x{76E3}\x{76E4}\x{76E5}\x{76E6}' .
+'\x{76E7}\x{76E8}\x{76E9}\x{76EA}\x{76EC}\x{76ED}\x{76EE}\x{76EF}\x{76F0}' .
+'\x{76F1}\x{76F2}\x{76F3}\x{76F4}\x{76F5}\x{76F6}\x{76F7}\x{76F8}\x{76F9}' .
+'\x{76FA}\x{76FB}\x{76FC}\x{76FD}\x{76FE}\x{76FF}\x{7701}\x{7703}\x{7704}' .
+'\x{7705}\x{7706}\x{7707}\x{7708}\x{7709}\x{770A}\x{770B}\x{770C}\x{770D}' .
+'\x{770F}\x{7710}\x{7711}\x{7712}\x{7713}\x{7714}\x{7715}\x{7716}\x{7717}' .
+'\x{7718}\x{7719}\x{771A}\x{771B}\x{771C}\x{771D}\x{771E}\x{771F}\x{7720}' .
+'\x{7722}\x{7723}\x{7725}\x{7726}\x{7727}\x{7728}\x{7729}\x{772A}\x{772C}' .
+'\x{772D}\x{772E}\x{772F}\x{7730}\x{7731}\x{7732}\x{7733}\x{7734}\x{7735}' .
+'\x{7736}\x{7737}\x{7738}\x{7739}\x{773A}\x{773B}\x{773C}\x{773D}\x{773E}' .
+'\x{7740}\x{7741}\x{7743}\x{7744}\x{7745}\x{7746}\x{7747}\x{7748}\x{7749}' .
+'\x{774A}\x{774B}\x{774C}\x{774D}\x{774E}\x{774F}\x{7750}\x{7751}\x{7752}' .
+'\x{7753}\x{7754}\x{7755}\x{7756}\x{7757}\x{7758}\x{7759}\x{775A}\x{775B}' .
+'\x{775C}\x{775D}\x{775E}\x{775F}\x{7760}\x{7761}\x{7762}\x{7763}\x{7765}' .
+'\x{7766}\x{7767}\x{7768}\x{7769}\x{776A}\x{776B}\x{776C}\x{776D}\x{776E}' .
+'\x{776F}\x{7770}\x{7771}\x{7772}\x{7773}\x{7774}\x{7775}\x{7776}\x{7777}' .
+'\x{7778}\x{7779}\x{777A}\x{777B}\x{777C}\x{777D}\x{777E}\x{777F}\x{7780}' .
+'\x{7781}\x{7782}\x{7783}\x{7784}\x{7785}\x{7786}\x{7787}\x{7788}\x{7789}' .
+'\x{778A}\x{778B}\x{778C}\x{778D}\x{778E}\x{778F}\x{7790}\x{7791}\x{7792}' .
+'\x{7793}\x{7794}\x{7795}\x{7797}\x{7798}\x{7799}\x{779A}\x{779B}\x{779C}' .
+'\x{779D}\x{779E}\x{779F}\x{77A0}\x{77A1}\x{77A2}\x{77A3}\x{77A5}\x{77A6}' .
+'\x{77A7}\x{77A8}\x{77A9}\x{77AA}\x{77AB}\x{77AC}\x{77AD}\x{77AE}\x{77AF}' .
+'\x{77B0}\x{77B1}\x{77B2}\x{77B3}\x{77B4}\x{77B5}\x{77B6}\x{77B7}\x{77B8}' .
+'\x{77B9}\x{77BA}\x{77BB}\x{77BC}\x{77BD}\x{77BF}\x{77C0}\x{77C2}\x{77C3}' .
+'\x{77C4}\x{77C5}\x{77C6}\x{77C7}\x{77C8}\x{77C9}\x{77CA}\x{77CB}\x{77CC}' .
+'\x{77CD}\x{77CE}\x{77CF}\x{77D0}\x{77D1}\x{77D3}\x{77D4}\x{77D5}\x{77D6}' .
+'\x{77D7}\x{77D8}\x{77D9}\x{77DA}\x{77DB}\x{77DC}\x{77DE}\x{77DF}\x{77E0}' .
+'\x{77E1}\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E8}\x{77E9}\x{77EA}\x{77EB}' .
+'\x{77EC}\x{77ED}\x{77EE}\x{77EF}\x{77F0}\x{77F1}\x{77F2}\x{77F3}\x{77F6}' .
+'\x{77F7}\x{77F8}\x{77F9}\x{77FA}\x{77FB}\x{77FC}\x{77FD}\x{77FE}\x{77FF}' .
+'\x{7800}\x{7801}\x{7802}\x{7803}\x{7804}\x{7805}\x{7806}\x{7808}\x{7809}' .
+'\x{780A}\x{780B}\x{780C}\x{780D}\x{780E}\x{780F}\x{7810}\x{7811}\x{7812}' .
+'\x{7813}\x{7814}\x{7815}\x{7816}\x{7817}\x{7818}\x{7819}\x{781A}\x{781B}' .
+'\x{781C}\x{781D}\x{781E}\x{781F}\x{7820}\x{7821}\x{7822}\x{7823}\x{7825}' .
+'\x{7826}\x{7827}\x{7828}\x{7829}\x{782A}\x{782B}\x{782C}\x{782D}\x{782E}' .
+'\x{782F}\x{7830}\x{7831}\x{7832}\x{7833}\x{7834}\x{7835}\x{7837}\x{7838}' .
+'\x{7839}\x{783A}\x{783B}\x{783C}\x{783D}\x{783E}\x{7840}\x{7841}\x{7843}' .
+'\x{7844}\x{7845}\x{7847}\x{7848}\x{7849}\x{784A}\x{784C}\x{784D}\x{784E}' .
+'\x{7850}\x{7851}\x{7852}\x{7853}\x{7854}\x{7855}\x{7856}\x{7857}\x{7858}' .
+'\x{7859}\x{785A}\x{785B}\x{785C}\x{785D}\x{785E}\x{785F}\x{7860}\x{7861}' .
+'\x{7862}\x{7863}\x{7864}\x{7865}\x{7866}\x{7867}\x{7868}\x{7869}\x{786A}' .
+'\x{786B}\x{786C}\x{786D}\x{786E}\x{786F}\x{7870}\x{7871}\x{7872}\x{7873}' .
+'\x{7874}\x{7875}\x{7877}\x{7878}\x{7879}\x{787A}\x{787B}\x{787C}\x{787D}' .
+'\x{787E}\x{787F}\x{7880}\x{7881}\x{7882}\x{7883}\x{7884}\x{7885}\x{7886}' .
+'\x{7887}\x{7889}\x{788A}\x{788B}\x{788C}\x{788D}\x{788E}\x{788F}\x{7890}' .
+'\x{7891}\x{7892}\x{7893}\x{7894}\x{7895}\x{7896}\x{7897}\x{7898}\x{7899}' .
+'\x{789A}\x{789B}\x{789C}\x{789D}\x{789E}\x{789F}\x{78A0}\x{78A1}\x{78A2}' .
+'\x{78A3}\x{78A4}\x{78A5}\x{78A6}\x{78A7}\x{78A8}\x{78A9}\x{78AA}\x{78AB}' .
+'\x{78AC}\x{78AD}\x{78AE}\x{78AF}\x{78B0}\x{78B1}\x{78B2}\x{78B3}\x{78B4}' .
+'\x{78B5}\x{78B6}\x{78B7}\x{78B8}\x{78B9}\x{78BA}\x{78BB}\x{78BC}\x{78BD}' .
+'\x{78BE}\x{78BF}\x{78C0}\x{78C1}\x{78C3}\x{78C4}\x{78C5}\x{78C6}\x{78C8}' .
+'\x{78C9}\x{78CA}\x{78CB}\x{78CC}\x{78CD}\x{78CE}\x{78CF}\x{78D0}\x{78D1}' .
+'\x{78D3}\x{78D4}\x{78D5}\x{78D6}\x{78D7}\x{78D8}\x{78D9}\x{78DA}\x{78DB}' .
+'\x{78DC}\x{78DD}\x{78DE}\x{78DF}\x{78E0}\x{78E1}\x{78E2}\x{78E3}\x{78E4}' .
+'\x{78E5}\x{78E6}\x{78E7}\x{78E8}\x{78E9}\x{78EA}\x{78EB}\x{78EC}\x{78ED}' .
+'\x{78EE}\x{78EF}\x{78F1}\x{78F2}\x{78F3}\x{78F4}\x{78F5}\x{78F6}\x{78F7}' .
+'\x{78F9}\x{78FA}\x{78FB}\x{78FC}\x{78FD}\x{78FE}\x{78FF}\x{7901}\x{7902}' .
+'\x{7903}\x{7904}\x{7905}\x{7906}\x{7907}\x{7909}\x{790A}\x{790B}\x{790C}' .
+'\x{790E}\x{790F}\x{7910}\x{7911}\x{7912}\x{7913}\x{7914}\x{7916}\x{7917}' .
+'\x{7918}\x{7919}\x{791A}\x{791B}\x{791C}\x{791D}\x{791E}\x{7921}\x{7922}' .
+'\x{7923}\x{7924}\x{7925}\x{7926}\x{7927}\x{7928}\x{7929}\x{792A}\x{792B}' .
+'\x{792C}\x{792D}\x{792E}\x{792F}\x{7930}\x{7931}\x{7933}\x{7934}\x{7935}' .
+'\x{7937}\x{7938}\x{7939}\x{793A}\x{793B}\x{793C}\x{793D}\x{793E}\x{793F}' .
+'\x{7940}\x{7941}\x{7942}\x{7943}\x{7944}\x{7945}\x{7946}\x{7947}\x{7948}' .
+'\x{7949}\x{794A}\x{794B}\x{794C}\x{794D}\x{794E}\x{794F}\x{7950}\x{7951}' .
+'\x{7952}\x{7953}\x{7954}\x{7955}\x{7956}\x{7957}\x{7958}\x{795A}\x{795B}' .
+'\x{795C}\x{795D}\x{795E}\x{795F}\x{7960}\x{7961}\x{7962}\x{7963}\x{7964}' .
+'\x{7965}\x{7966}\x{7967}\x{7968}\x{7969}\x{796A}\x{796B}\x{796D}\x{796F}' .
+'\x{7970}\x{7971}\x{7972}\x{7973}\x{7974}\x{7977}\x{7978}\x{7979}\x{797A}' .
+'\x{797B}\x{797C}\x{797D}\x{797E}\x{797F}\x{7980}\x{7981}\x{7982}\x{7983}' .
+'\x{7984}\x{7985}\x{7988}\x{7989}\x{798A}\x{798B}\x{798C}\x{798D}\x{798E}' .
+'\x{798F}\x{7990}\x{7991}\x{7992}\x{7993}\x{7994}\x{7995}\x{7996}\x{7997}' .
+'\x{7998}\x{7999}\x{799A}\x{799B}\x{799C}\x{799F}\x{79A0}\x{79A1}\x{79A2}' .
+'\x{79A3}\x{79A4}\x{79A5}\x{79A6}\x{79A7}\x{79A8}\x{79AA}\x{79AB}\x{79AC}' .
+'\x{79AD}\x{79AE}\x{79AF}\x{79B0}\x{79B1}\x{79B2}\x{79B3}\x{79B4}\x{79B5}' .
+'\x{79B6}\x{79B7}\x{79B8}\x{79B9}\x{79BA}\x{79BB}\x{79BD}\x{79BE}\x{79BF}' .
+'\x{79C0}\x{79C1}\x{79C2}\x{79C3}\x{79C5}\x{79C6}\x{79C8}\x{79C9}\x{79CA}' .
+'\x{79CB}\x{79CD}\x{79CE}\x{79CF}\x{79D0}\x{79D1}\x{79D2}\x{79D3}\x{79D5}' .
+'\x{79D6}\x{79D8}\x{79D9}\x{79DA}\x{79DB}\x{79DC}\x{79DD}\x{79DE}\x{79DF}' .
+'\x{79E0}\x{79E1}\x{79E2}\x{79E3}\x{79E4}\x{79E5}\x{79E6}\x{79E7}\x{79E8}' .
+'\x{79E9}\x{79EA}\x{79EB}\x{79EC}\x{79ED}\x{79EE}\x{79EF}\x{79F0}\x{79F1}' .
+'\x{79F2}\x{79F3}\x{79F4}\x{79F5}\x{79F6}\x{79F7}\x{79F8}\x{79F9}\x{79FA}' .
+'\x{79FB}\x{79FC}\x{79FD}\x{79FE}\x{79FF}\x{7A00}\x{7A02}\x{7A03}\x{7A04}' .
+'\x{7A05}\x{7A06}\x{7A08}\x{7A0A}\x{7A0B}\x{7A0C}\x{7A0D}\x{7A0E}\x{7A0F}' .
+'\x{7A10}\x{7A11}\x{7A12}\x{7A13}\x{7A14}\x{7A15}\x{7A16}\x{7A17}\x{7A18}' .
+'\x{7A19}\x{7A1A}\x{7A1B}\x{7A1C}\x{7A1D}\x{7A1E}\x{7A1F}\x{7A20}\x{7A21}' .
+'\x{7A22}\x{7A23}\x{7A24}\x{7A25}\x{7A26}\x{7A27}\x{7A28}\x{7A29}\x{7A2A}' .
+'\x{7A2B}\x{7A2D}\x{7A2E}\x{7A2F}\x{7A30}\x{7A31}\x{7A32}\x{7A33}\x{7A34}' .
+'\x{7A35}\x{7A37}\x{7A39}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' .
+'\x{7A41}\x{7A42}\x{7A43}\x{7A44}\x{7A45}\x{7A46}\x{7A47}\x{7A48}\x{7A49}' .
+'\x{7A4A}\x{7A4B}\x{7A4C}\x{7A4D}\x{7A4E}\x{7A50}\x{7A51}\x{7A52}\x{7A53}' .
+'\x{7A54}\x{7A55}\x{7A56}\x{7A57}\x{7A58}\x{7A59}\x{7A5A}\x{7A5B}\x{7A5C}' .
+'\x{7A5D}\x{7A5E}\x{7A5F}\x{7A60}\x{7A61}\x{7A62}\x{7A65}\x{7A66}\x{7A67}' .
+'\x{7A68}\x{7A69}\x{7A6B}\x{7A6C}\x{7A6D}\x{7A6E}\x{7A70}\x{7A71}\x{7A72}' .
+'\x{7A73}\x{7A74}\x{7A75}\x{7A76}\x{7A77}\x{7A78}\x{7A79}\x{7A7A}\x{7A7B}' .
+'\x{7A7C}\x{7A7D}\x{7A7E}\x{7A7F}\x{7A80}\x{7A81}\x{7A83}\x{7A84}\x{7A85}' .
+'\x{7A86}\x{7A87}\x{7A88}\x{7A89}\x{7A8A}\x{7A8B}\x{7A8C}\x{7A8D}\x{7A8E}' .
+'\x{7A8F}\x{7A90}\x{7A91}\x{7A92}\x{7A93}\x{7A94}\x{7A95}\x{7A96}\x{7A97}' .
+'\x{7A98}\x{7A99}\x{7A9C}\x{7A9D}\x{7A9E}\x{7A9F}\x{7AA0}\x{7AA1}\x{7AA2}' .
+'\x{7AA3}\x{7AA4}\x{7AA5}\x{7AA6}\x{7AA7}\x{7AA8}\x{7AA9}\x{7AAA}\x{7AAB}' .
+'\x{7AAC}\x{7AAD}\x{7AAE}\x{7AAF}\x{7AB0}\x{7AB1}\x{7AB2}\x{7AB3}\x{7AB4}' .
+'\x{7AB5}\x{7AB6}\x{7AB7}\x{7AB8}\x{7ABA}\x{7ABE}\x{7ABF}\x{7AC0}\x{7AC1}' .
+'\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}\x{7AC9}\x{7ACA}\x{7ACB}\x{7ACC}\x{7ACD}' .
+'\x{7ACE}\x{7ACF}\x{7AD0}\x{7AD1}\x{7AD2}\x{7AD3}\x{7AD4}\x{7AD5}\x{7AD6}' .
+'\x{7AD8}\x{7AD9}\x{7ADB}\x{7ADC}\x{7ADD}\x{7ADE}\x{7ADF}\x{7AE0}\x{7AE1}' .
+'\x{7AE2}\x{7AE3}\x{7AE4}\x{7AE5}\x{7AE6}\x{7AE7}\x{7AE8}\x{7AEA}\x{7AEB}' .
+'\x{7AEC}\x{7AED}\x{7AEE}\x{7AEF}\x{7AF0}\x{7AF1}\x{7AF2}\x{7AF3}\x{7AF4}' .
+'\x{7AF6}\x{7AF7}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFB}\x{7AFD}\x{7AFE}\x{7AFF}' .
+'\x{7B00}\x{7B01}\x{7B02}\x{7B03}\x{7B04}\x{7B05}\x{7B06}\x{7B08}\x{7B09}' .
+'\x{7B0A}\x{7B0B}\x{7B0C}\x{7B0D}\x{7B0E}\x{7B0F}\x{7B10}\x{7B11}\x{7B12}' .
+'\x{7B13}\x{7B14}\x{7B15}\x{7B16}\x{7B17}\x{7B18}\x{7B19}\x{7B1A}\x{7B1B}' .
+'\x{7B1C}\x{7B1D}\x{7B1E}\x{7B20}\x{7B21}\x{7B22}\x{7B23}\x{7B24}\x{7B25}' .
+'\x{7B26}\x{7B28}\x{7B2A}\x{7B2B}\x{7B2C}\x{7B2D}\x{7B2E}\x{7B2F}\x{7B30}' .
+'\x{7B31}\x{7B32}\x{7B33}\x{7B34}\x{7B35}\x{7B36}\x{7B37}\x{7B38}\x{7B39}' .
+'\x{7B3A}\x{7B3B}\x{7B3C}\x{7B3D}\x{7B3E}\x{7B3F}\x{7B40}\x{7B41}\x{7B43}' .
+'\x{7B44}\x{7B45}\x{7B46}\x{7B47}\x{7B48}\x{7B49}\x{7B4A}\x{7B4B}\x{7B4C}' .
+'\x{7B4D}\x{7B4E}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B55}\x{7B56}' .
+'\x{7B57}\x{7B58}\x{7B59}\x{7B5A}\x{7B5B}\x{7B5C}\x{7B5D}\x{7B5E}\x{7B5F}' .
+'\x{7B60}\x{7B61}\x{7B62}\x{7B63}\x{7B64}\x{7B65}\x{7B66}\x{7B67}\x{7B68}' .
+'\x{7B69}\x{7B6A}\x{7B6B}\x{7B6C}\x{7B6D}\x{7B6E}\x{7B70}\x{7B71}\x{7B72}' .
+'\x{7B73}\x{7B74}\x{7B75}\x{7B76}\x{7B77}\x{7B78}\x{7B79}\x{7B7B}\x{7B7C}' .
+'\x{7B7D}\x{7B7E}\x{7B7F}\x{7B80}\x{7B81}\x{7B82}\x{7B83}\x{7B84}\x{7B85}' .
+'\x{7B87}\x{7B88}\x{7B89}\x{7B8A}\x{7B8B}\x{7B8C}\x{7B8D}\x{7B8E}\x{7B8F}' .
+'\x{7B90}\x{7B91}\x{7B93}\x{7B94}\x{7B95}\x{7B96}\x{7B97}\x{7B98}\x{7B99}' .
+'\x{7B9A}\x{7B9B}\x{7B9C}\x{7B9D}\x{7B9E}\x{7B9F}\x{7BA0}\x{7BA1}\x{7BA2}' .
+'\x{7BA4}\x{7BA6}\x{7BA7}\x{7BA8}\x{7BA9}\x{7BAA}\x{7BAB}\x{7BAC}\x{7BAD}' .
+'\x{7BAE}\x{7BAF}\x{7BB1}\x{7BB3}\x{7BB4}\x{7BB5}\x{7BB6}\x{7BB7}\x{7BB8}' .
+'\x{7BB9}\x{7BBA}\x{7BBB}\x{7BBC}\x{7BBD}\x{7BBE}\x{7BBF}\x{7BC0}\x{7BC1}' .
+'\x{7BC2}\x{7BC3}\x{7BC4}\x{7BC5}\x{7BC6}\x{7BC7}\x{7BC8}\x{7BC9}\x{7BCA}' .
+'\x{7BCB}\x{7BCC}\x{7BCD}\x{7BCE}\x{7BD0}\x{7BD1}\x{7BD2}\x{7BD3}\x{7BD4}' .
+'\x{7BD5}\x{7BD6}\x{7BD7}\x{7BD8}\x{7BD9}\x{7BDA}\x{7BDB}\x{7BDC}\x{7BDD}' .
+'\x{7BDE}\x{7BDF}\x{7BE0}\x{7BE1}\x{7BE2}\x{7BE3}\x{7BE4}\x{7BE5}\x{7BE6}' .
+'\x{7BE7}\x{7BE8}\x{7BE9}\x{7BEA}\x{7BEB}\x{7BEC}\x{7BED}\x{7BEE}\x{7BEF}' .
+'\x{7BF0}\x{7BF1}\x{7BF2}\x{7BF3}\x{7BF4}\x{7BF5}\x{7BF6}\x{7BF7}\x{7BF8}' .
+'\x{7BF9}\x{7BFB}\x{7BFC}\x{7BFD}\x{7BFE}\x{7BFF}\x{7C00}\x{7C01}\x{7C02}' .
+'\x{7C03}\x{7C04}\x{7C05}\x{7C06}\x{7C07}\x{7C08}\x{7C09}\x{7C0A}\x{7C0B}' .
+'\x{7C0C}\x{7C0D}\x{7C0E}\x{7C0F}\x{7C10}\x{7C11}\x{7C12}\x{7C13}\x{7C15}' .
+'\x{7C16}\x{7C17}\x{7C18}\x{7C19}\x{7C1A}\x{7C1C}\x{7C1D}\x{7C1E}\x{7C1F}' .
+'\x{7C20}\x{7C21}\x{7C22}\x{7C23}\x{7C24}\x{7C25}\x{7C26}\x{7C27}\x{7C28}' .
+'\x{7C29}\x{7C2A}\x{7C2B}\x{7C2C}\x{7C2D}\x{7C30}\x{7C31}\x{7C32}\x{7C33}' .
+'\x{7C34}\x{7C35}\x{7C36}\x{7C37}\x{7C38}\x{7C39}\x{7C3A}\x{7C3B}\x{7C3C}' .
+'\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C41}\x{7C42}\x{7C43}\x{7C44}\x{7C45}' .
+'\x{7C46}\x{7C47}\x{7C48}\x{7C49}\x{7C4A}\x{7C4B}\x{7C4C}\x{7C4D}\x{7C4E}' .
+'\x{7C50}\x{7C51}\x{7C53}\x{7C54}\x{7C56}\x{7C57}\x{7C58}\x{7C59}\x{7C5A}' .
+'\x{7C5B}\x{7C5C}\x{7C5E}\x{7C5F}\x{7C60}\x{7C61}\x{7C62}\x{7C63}\x{7C64}' .
+'\x{7C65}\x{7C66}\x{7C67}\x{7C68}\x{7C69}\x{7C6A}\x{7C6B}\x{7C6C}\x{7C6D}' .
+'\x{7C6E}\x{7C6F}\x{7C70}\x{7C71}\x{7C72}\x{7C73}\x{7C74}\x{7C75}\x{7C77}' .
+'\x{7C78}\x{7C79}\x{7C7A}\x{7C7B}\x{7C7C}\x{7C7D}\x{7C7E}\x{7C7F}\x{7C80}' .
+'\x{7C81}\x{7C82}\x{7C84}\x{7C85}\x{7C86}\x{7C88}\x{7C89}\x{7C8A}\x{7C8B}' .
+'\x{7C8C}\x{7C8D}\x{7C8E}\x{7C8F}\x{7C90}\x{7C91}\x{7C92}\x{7C94}\x{7C95}' .
+'\x{7C96}\x{7C97}\x{7C98}\x{7C99}\x{7C9B}\x{7C9C}\x{7C9D}\x{7C9E}\x{7C9F}' .
+'\x{7CA0}\x{7CA1}\x{7CA2}\x{7CA3}\x{7CA4}\x{7CA5}\x{7CA6}\x{7CA7}\x{7CA8}' .
+'\x{7CA9}\x{7CAA}\x{7CAD}\x{7CAE}\x{7CAF}\x{7CB0}\x{7CB1}\x{7CB2}\x{7CB3}' .
+'\x{7CB4}\x{7CB5}\x{7CB6}\x{7CB7}\x{7CB8}\x{7CB9}\x{7CBA}\x{7CBB}\x{7CBC}' .
+'\x{7CBD}\x{7CBE}\x{7CBF}\x{7CC0}\x{7CC1}\x{7CC2}\x{7CC3}\x{7CC4}\x{7CC5}' .
+'\x{7CC6}\x{7CC7}\x{7CC8}\x{7CC9}\x{7CCA}\x{7CCB}\x{7CCC}\x{7CCD}\x{7CCE}' .
+'\x{7CCF}\x{7CD0}\x{7CD1}\x{7CD2}\x{7CD4}\x{7CD5}\x{7CD6}\x{7CD7}\x{7CD8}' .
+'\x{7CD9}\x{7CDC}\x{7CDD}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE4}\x{7CE7}' .
+'\x{7CE8}\x{7CE9}\x{7CEA}\x{7CEB}\x{7CEC}\x{7CED}\x{7CEE}\x{7CEF}\x{7CF0}' .
+'\x{7CF1}\x{7CF2}\x{7CF3}\x{7CF4}\x{7CF5}\x{7CF6}\x{7CF7}\x{7CF8}\x{7CF9}' .
+'\x{7CFA}\x{7CFB}\x{7CFD}\x{7CFE}\x{7D00}\x{7D01}\x{7D02}\x{7D03}\x{7D04}' .
+'\x{7D05}\x{7D06}\x{7D07}\x{7D08}\x{7D09}\x{7D0A}\x{7D0B}\x{7D0C}\x{7D0D}' .
+'\x{7D0E}\x{7D0F}\x{7D10}\x{7D11}\x{7D12}\x{7D13}\x{7D14}\x{7D15}\x{7D16}' .
+'\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D1D}\x{7D1E}\x{7D1F}' .
+'\x{7D20}\x{7D21}\x{7D22}\x{7D24}\x{7D25}\x{7D26}\x{7D27}\x{7D28}\x{7D29}' .
+'\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D31}\x{7D32}\x{7D33}\x{7D34}' .
+'\x{7D35}\x{7D36}\x{7D37}\x{7D38}\x{7D39}\x{7D3A}\x{7D3B}\x{7D3C}\x{7D3D}' .
+'\x{7D3E}\x{7D3F}\x{7D40}\x{7D41}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}' .
+'\x{7D47}\x{7D49}\x{7D4A}\x{7D4B}\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D51}' .
+'\x{7D52}\x{7D53}\x{7D54}\x{7D55}\x{7D56}\x{7D57}\x{7D58}\x{7D59}\x{7D5B}' .
+'\x{7D5C}\x{7D5D}\x{7D5E}\x{7D5F}\x{7D60}\x{7D61}\x{7D62}\x{7D63}\x{7D65}' .
+'\x{7D66}\x{7D67}\x{7D68}\x{7D69}\x{7D6A}\x{7D6B}\x{7D6C}\x{7D6D}\x{7D6E}' .
+'\x{7D6F}\x{7D70}\x{7D71}\x{7D72}\x{7D73}\x{7D74}\x{7D75}\x{7D76}\x{7D77}' .
+'\x{7D79}\x{7D7A}\x{7D7B}\x{7D7C}\x{7D7D}\x{7D7E}\x{7D7F}\x{7D80}\x{7D81}' .
+'\x{7D83}\x{7D84}\x{7D85}\x{7D86}\x{7D87}\x{7D88}\x{7D89}\x{7D8A}\x{7D8B}' .
+'\x{7D8C}\x{7D8D}\x{7D8E}\x{7D8F}\x{7D90}\x{7D91}\x{7D92}\x{7D93}\x{7D94}' .
+'\x{7D96}\x{7D97}\x{7D99}\x{7D9B}\x{7D9C}\x{7D9D}\x{7D9E}\x{7D9F}\x{7DA0}' .
+'\x{7DA1}\x{7DA2}\x{7DA3}\x{7DA5}\x{7DA6}\x{7DA7}\x{7DA9}\x{7DAA}\x{7DAB}' .
+'\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}\x{7DB1}\x{7DB2}\x{7DB3}\x{7DB4}' .
+'\x{7DB5}\x{7DB6}\x{7DB7}\x{7DB8}\x{7DB9}\x{7DBA}\x{7DBB}\x{7DBC}\x{7DBD}' .
+'\x{7DBE}\x{7DBF}\x{7DC0}\x{7DC1}\x{7DC2}\x{7DC3}\x{7DC4}\x{7DC5}\x{7DC6}' .
+'\x{7DC7}\x{7DC8}\x{7DC9}\x{7DCA}\x{7DCB}\x{7DCC}\x{7DCE}\x{7DCF}\x{7DD0}' .
+'\x{7DD1}\x{7DD2}\x{7DD4}\x{7DD5}\x{7DD6}\x{7DD7}\x{7DD8}\x{7DD9}\x{7DDA}' .
+'\x{7DDB}\x{7DDD}\x{7DDE}\x{7DDF}\x{7DE0}\x{7DE1}\x{7DE2}\x{7DE3}\x{7DE6}' .
+'\x{7DE7}\x{7DE8}\x{7DE9}\x{7DEA}\x{7DEC}\x{7DED}\x{7DEE}\x{7DEF}\x{7DF0}' .
+'\x{7DF1}\x{7DF2}\x{7DF3}\x{7DF4}\x{7DF5}\x{7DF6}\x{7DF7}\x{7DF8}\x{7DF9}' .
+'\x{7DFA}\x{7DFB}\x{7DFC}\x{7E00}\x{7E01}\x{7E02}\x{7E03}\x{7E04}\x{7E05}' .
+'\x{7E06}\x{7E07}\x{7E08}\x{7E09}\x{7E0A}\x{7E0B}\x{7E0C}\x{7E0D}\x{7E0E}' .
+'\x{7E0F}\x{7E10}\x{7E11}\x{7E12}\x{7E13}\x{7E14}\x{7E15}\x{7E16}\x{7E17}' .
+'\x{7E19}\x{7E1A}\x{7E1B}\x{7E1C}\x{7E1D}\x{7E1E}\x{7E1F}\x{7E20}\x{7E21}' .
+'\x{7E22}\x{7E23}\x{7E24}\x{7E25}\x{7E26}\x{7E27}\x{7E28}\x{7E29}\x{7E2A}' .
+'\x{7E2B}\x{7E2C}\x{7E2D}\x{7E2E}\x{7E2F}\x{7E30}\x{7E31}\x{7E32}\x{7E33}' .
+'\x{7E34}\x{7E35}\x{7E36}\x{7E37}\x{7E38}\x{7E39}\x{7E3A}\x{7E3B}\x{7E3C}' .
+'\x{7E3D}\x{7E3E}\x{7E3F}\x{7E40}\x{7E41}\x{7E42}\x{7E43}\x{7E44}\x{7E45}' .
+'\x{7E46}\x{7E47}\x{7E48}\x{7E49}\x{7E4C}\x{7E4D}\x{7E4E}\x{7E4F}\x{7E50}' .
+'\x{7E51}\x{7E52}\x{7E53}\x{7E54}\x{7E55}\x{7E56}\x{7E57}\x{7E58}\x{7E59}' .
+'\x{7E5A}\x{7E5C}\x{7E5D}\x{7E5E}\x{7E5F}\x{7E60}\x{7E61}\x{7E62}\x{7E63}' .
+'\x{7E65}\x{7E66}\x{7E67}\x{7E68}\x{7E69}\x{7E6A}\x{7E6B}\x{7E6C}\x{7E6D}' .
+'\x{7E6E}\x{7E6F}\x{7E70}\x{7E71}\x{7E72}\x{7E73}\x{7E74}\x{7E75}\x{7E76}' .
+'\x{7E77}\x{7E78}\x{7E79}\x{7E7A}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7E}\x{7E7F}' .
+'\x{7E80}\x{7E81}\x{7E82}\x{7E83}\x{7E84}\x{7E85}\x{7E86}\x{7E87}\x{7E88}' .
+'\x{7E89}\x{7E8A}\x{7E8B}\x{7E8C}\x{7E8D}\x{7E8E}\x{7E8F}\x{7E90}\x{7E91}' .
+'\x{7E92}\x{7E93}\x{7E94}\x{7E95}\x{7E96}\x{7E97}\x{7E98}\x{7E99}\x{7E9A}' .
+'\x{7E9B}\x{7E9C}\x{7E9E}\x{7E9F}\x{7EA0}\x{7EA1}\x{7EA2}\x{7EA3}\x{7EA4}' .
+'\x{7EA5}\x{7EA6}\x{7EA7}\x{7EA8}\x{7EA9}\x{7EAA}\x{7EAB}\x{7EAC}\x{7EAD}' .
+'\x{7EAE}\x{7EAF}\x{7EB0}\x{7EB1}\x{7EB2}\x{7EB3}\x{7EB4}\x{7EB5}\x{7EB6}' .
+'\x{7EB7}\x{7EB8}\x{7EB9}\x{7EBA}\x{7EBB}\x{7EBC}\x{7EBD}\x{7EBE}\x{7EBF}' .
+'\x{7EC0}\x{7EC1}\x{7EC2}\x{7EC3}\x{7EC4}\x{7EC5}\x{7EC6}\x{7EC7}\x{7EC8}' .
+'\x{7EC9}\x{7ECA}\x{7ECB}\x{7ECC}\x{7ECD}\x{7ECE}\x{7ECF}\x{7ED0}\x{7ED1}' .
+'\x{7ED2}\x{7ED3}\x{7ED4}\x{7ED5}\x{7ED6}\x{7ED7}\x{7ED8}\x{7ED9}\x{7EDA}' .
+'\x{7EDB}\x{7EDC}\x{7EDD}\x{7EDE}\x{7EDF}\x{7EE0}\x{7EE1}\x{7EE2}\x{7EE3}' .
+'\x{7EE4}\x{7EE5}\x{7EE6}\x{7EE7}\x{7EE8}\x{7EE9}\x{7EEA}\x{7EEB}\x{7EEC}' .
+'\x{7EED}\x{7EEE}\x{7EEF}\x{7EF0}\x{7EF1}\x{7EF2}\x{7EF3}\x{7EF4}\x{7EF5}' .
+'\x{7EF6}\x{7EF7}\x{7EF8}\x{7EF9}\x{7EFA}\x{7EFB}\x{7EFC}\x{7EFD}\x{7EFE}' .
+'\x{7EFF}\x{7F00}\x{7F01}\x{7F02}\x{7F03}\x{7F04}\x{7F05}\x{7F06}\x{7F07}' .
+'\x{7F08}\x{7F09}\x{7F0A}\x{7F0B}\x{7F0C}\x{7F0D}\x{7F0E}\x{7F0F}\x{7F10}' .
+'\x{7F11}\x{7F12}\x{7F13}\x{7F14}\x{7F15}\x{7F16}\x{7F17}\x{7F18}\x{7F19}' .
+'\x{7F1A}\x{7F1B}\x{7F1C}\x{7F1D}\x{7F1E}\x{7F1F}\x{7F20}\x{7F21}\x{7F22}' .
+'\x{7F23}\x{7F24}\x{7F25}\x{7F26}\x{7F27}\x{7F28}\x{7F29}\x{7F2A}\x{7F2B}' .
+'\x{7F2C}\x{7F2D}\x{7F2E}\x{7F2F}\x{7F30}\x{7F31}\x{7F32}\x{7F33}\x{7F34}' .
+'\x{7F35}\x{7F36}\x{7F37}\x{7F38}\x{7F39}\x{7F3A}\x{7F3D}\x{7F3E}\x{7F3F}' .
+'\x{7F40}\x{7F42}\x{7F43}\x{7F44}\x{7F45}\x{7F47}\x{7F48}\x{7F49}\x{7F4A}' .
+'\x{7F4B}\x{7F4C}\x{7F4D}\x{7F4E}\x{7F4F}\x{7F50}\x{7F51}\x{7F52}\x{7F53}' .
+'\x{7F54}\x{7F55}\x{7F56}\x{7F57}\x{7F58}\x{7F5A}\x{7F5B}\x{7F5C}\x{7F5D}' .
+'\x{7F5E}\x{7F5F}\x{7F60}\x{7F61}\x{7F62}\x{7F63}\x{7F64}\x{7F65}\x{7F66}' .
+'\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6C}\x{7F6D}\x{7F6E}\x{7F6F}' .
+'\x{7F70}\x{7F71}\x{7F72}\x{7F73}\x{7F74}\x{7F75}\x{7F76}\x{7F77}\x{7F78}' .
+'\x{7F79}\x{7F7A}\x{7F7B}\x{7F7C}\x{7F7D}\x{7F7E}\x{7F7F}\x{7F80}\x{7F81}' .
+'\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}\x{7F88}\x{7F89}\x{7F8A}\x{7F8B}' .
+'\x{7F8C}\x{7F8D}\x{7F8E}\x{7F8F}\x{7F91}\x{7F92}\x{7F93}\x{7F94}\x{7F95}' .
+'\x{7F96}\x{7F98}\x{7F9A}\x{7F9B}\x{7F9C}\x{7F9D}\x{7F9E}\x{7F9F}\x{7FA0}' .
+'\x{7FA1}\x{7FA2}\x{7FA3}\x{7FA4}\x{7FA5}\x{7FA6}\x{7FA7}\x{7FA8}\x{7FA9}' .
+'\x{7FAA}\x{7FAB}\x{7FAC}\x{7FAD}\x{7FAE}\x{7FAF}\x{7FB0}\x{7FB1}\x{7FB2}' .
+'\x{7FB3}\x{7FB5}\x{7FB6}\x{7FB7}\x{7FB8}\x{7FB9}\x{7FBA}\x{7FBB}\x{7FBC}' .
+'\x{7FBD}\x{7FBE}\x{7FBF}\x{7FC0}\x{7FC1}\x{7FC2}\x{7FC3}\x{7FC4}\x{7FC5}' .
+'\x{7FC6}\x{7FC7}\x{7FC8}\x{7FC9}\x{7FCA}\x{7FCB}\x{7FCC}\x{7FCD}\x{7FCE}' .
+'\x{7FCF}\x{7FD0}\x{7FD1}\x{7FD2}\x{7FD3}\x{7FD4}\x{7FD5}\x{7FD7}\x{7FD8}' .
+'\x{7FD9}\x{7FDA}\x{7FDB}\x{7FDC}\x{7FDE}\x{7FDF}\x{7FE0}\x{7FE1}\x{7FE2}' .
+'\x{7FE3}\x{7FE5}\x{7FE6}\x{7FE7}\x{7FE8}\x{7FE9}\x{7FEA}\x{7FEB}\x{7FEC}' .
+'\x{7FED}\x{7FEE}\x{7FEF}\x{7FF0}\x{7FF1}\x{7FF2}\x{7FF3}\x{7FF4}\x{7FF5}' .
+'\x{7FF6}\x{7FF7}\x{7FF8}\x{7FF9}\x{7FFA}\x{7FFB}\x{7FFC}\x{7FFD}\x{7FFE}' .
+'\x{7FFF}\x{8000}\x{8001}\x{8002}\x{8003}\x{8004}\x{8005}\x{8006}\x{8007}' .
+'\x{8008}\x{8009}\x{800B}\x{800C}\x{800D}\x{800E}\x{800F}\x{8010}\x{8011}' .
+'\x{8012}\x{8013}\x{8014}\x{8015}\x{8016}\x{8017}\x{8018}\x{8019}\x{801A}' .
+'\x{801B}\x{801C}\x{801D}\x{801E}\x{801F}\x{8020}\x{8021}\x{8022}\x{8023}' .
+'\x{8024}\x{8025}\x{8026}\x{8027}\x{8028}\x{8029}\x{802A}\x{802B}\x{802C}' .
+'\x{802D}\x{802E}\x{8030}\x{8031}\x{8032}\x{8033}\x{8034}\x{8035}\x{8036}' .
+'\x{8037}\x{8038}\x{8039}\x{803A}\x{803B}\x{803D}\x{803E}\x{803F}\x{8041}' .
+'\x{8042}\x{8043}\x{8044}\x{8045}\x{8046}\x{8047}\x{8048}\x{8049}\x{804A}' .
+'\x{804B}\x{804C}\x{804D}\x{804E}\x{804F}\x{8050}\x{8051}\x{8052}\x{8053}' .
+'\x{8054}\x{8055}\x{8056}\x{8057}\x{8058}\x{8059}\x{805A}\x{805B}\x{805C}' .
+'\x{805D}\x{805E}\x{805F}\x{8060}\x{8061}\x{8062}\x{8063}\x{8064}\x{8065}' .
+'\x{8067}\x{8068}\x{8069}\x{806A}\x{806B}\x{806C}\x{806D}\x{806E}\x{806F}' .
+'\x{8070}\x{8071}\x{8072}\x{8073}\x{8074}\x{8075}\x{8076}\x{8077}\x{8078}' .
+'\x{8079}\x{807A}\x{807B}\x{807C}\x{807D}\x{807E}\x{807F}\x{8080}\x{8081}' .
+'\x{8082}\x{8083}\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808A}\x{808B}' .
+'\x{808C}\x{808D}\x{808F}\x{8090}\x{8091}\x{8092}\x{8093}\x{8095}\x{8096}' .
+'\x{8097}\x{8098}\x{8099}\x{809A}\x{809B}\x{809C}\x{809D}\x{809E}\x{809F}' .
+'\x{80A0}\x{80A1}\x{80A2}\x{80A3}\x{80A4}\x{80A5}\x{80A9}\x{80AA}\x{80AB}' .
+'\x{80AD}\x{80AE}\x{80AF}\x{80B0}\x{80B1}\x{80B2}\x{80B4}\x{80B5}\x{80B6}' .
+'\x{80B7}\x{80B8}\x{80BA}\x{80BB}\x{80BC}\x{80BD}\x{80BE}\x{80BF}\x{80C0}' .
+'\x{80C1}\x{80C2}\x{80C3}\x{80C4}\x{80C5}\x{80C6}\x{80C7}\x{80C8}\x{80C9}' .
+'\x{80CA}\x{80CB}\x{80CC}\x{80CD}\x{80CE}\x{80CF}\x{80D0}\x{80D1}\x{80D2}' .
+'\x{80D3}\x{80D4}\x{80D5}\x{80D6}\x{80D7}\x{80D8}\x{80D9}\x{80DA}\x{80DB}' .
+'\x{80DC}\x{80DD}\x{80DE}\x{80E0}\x{80E1}\x{80E2}\x{80E3}\x{80E4}\x{80E5}' .
+'\x{80E6}\x{80E7}\x{80E8}\x{80E9}\x{80EA}\x{80EB}\x{80EC}\x{80ED}\x{80EE}' .
+'\x{80EF}\x{80F0}\x{80F1}\x{80F2}\x{80F3}\x{80F4}\x{80F5}\x{80F6}\x{80F7}' .
+'\x{80F8}\x{80F9}\x{80FA}\x{80FB}\x{80FC}\x{80FD}\x{80FE}\x{80FF}\x{8100}' .
+'\x{8101}\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{810B}' .
+'\x{810C}\x{810D}\x{810E}\x{810F}\x{8110}\x{8111}\x{8112}\x{8113}\x{8114}' .
+'\x{8115}\x{8116}\x{8118}\x{8119}\x{811A}\x{811B}\x{811C}\x{811D}\x{811E}' .
+'\x{811F}\x{8120}\x{8121}\x{8122}\x{8123}\x{8124}\x{8125}\x{8126}\x{8127}' .
+'\x{8128}\x{8129}\x{812A}\x{812B}\x{812C}\x{812D}\x{812E}\x{812F}\x{8130}' .
+'\x{8131}\x{8132}\x{8136}\x{8137}\x{8138}\x{8139}\x{813A}\x{813B}\x{813C}' .
+'\x{813D}\x{813E}\x{813F}\x{8140}\x{8141}\x{8142}\x{8143}\x{8144}\x{8145}' .
+'\x{8146}\x{8147}\x{8148}\x{8149}\x{814A}\x{814B}\x{814C}\x{814D}\x{814E}' .
+'\x{814F}\x{8150}\x{8151}\x{8152}\x{8153}\x{8154}\x{8155}\x{8156}\x{8157}' .
+'\x{8158}\x{8159}\x{815A}\x{815B}\x{815C}\x{815D}\x{815E}\x{8160}\x{8161}' .
+'\x{8162}\x{8163}\x{8164}\x{8165}\x{8166}\x{8167}\x{8168}\x{8169}\x{816A}' .
+'\x{816B}\x{816C}\x{816D}\x{816E}\x{816F}\x{8170}\x{8171}\x{8172}\x{8173}' .
+'\x{8174}\x{8175}\x{8176}\x{8177}\x{8178}\x{8179}\x{817A}\x{817B}\x{817C}' .
+'\x{817D}\x{817E}\x{817F}\x{8180}\x{8181}\x{8182}\x{8183}\x{8185}\x{8186}' .
+'\x{8187}\x{8188}\x{8189}\x{818A}\x{818B}\x{818C}\x{818D}\x{818E}\x{818F}' .
+'\x{8191}\x{8192}\x{8193}\x{8194}\x{8195}\x{8197}\x{8198}\x{8199}\x{819A}' .
+'\x{819B}\x{819C}\x{819D}\x{819E}\x{819F}\x{81A0}\x{81A1}\x{81A2}\x{81A3}' .
+'\x{81A4}\x{81A5}\x{81A6}\x{81A7}\x{81A8}\x{81A9}\x{81AA}\x{81AB}\x{81AC}' .
+'\x{81AD}\x{81AE}\x{81AF}\x{81B0}\x{81B1}\x{81B2}\x{81B3}\x{81B4}\x{81B5}' .
+'\x{81B6}\x{81B7}\x{81B8}\x{81B9}\x{81BA}\x{81BB}\x{81BC}\x{81BD}\x{81BE}' .
+'\x{81BF}\x{81C0}\x{81C1}\x{81C2}\x{81C3}\x{81C4}\x{81C5}\x{81C6}\x{81C7}' .
+'\x{81C8}\x{81C9}\x{81CA}\x{81CC}\x{81CD}\x{81CE}\x{81CF}\x{81D0}\x{81D1}' .
+'\x{81D2}\x{81D4}\x{81D5}\x{81D6}\x{81D7}\x{81D8}\x{81D9}\x{81DA}\x{81DB}' .
+'\x{81DC}\x{81DD}\x{81DE}\x{81DF}\x{81E0}\x{81E1}\x{81E2}\x{81E3}\x{81E5}' .
+'\x{81E6}\x{81E7}\x{81E8}\x{81E9}\x{81EA}\x{81EB}\x{81EC}\x{81ED}\x{81EE}' .
+'\x{81F1}\x{81F2}\x{81F3}\x{81F4}\x{81F5}\x{81F6}\x{81F7}\x{81F8}\x{81F9}' .
+'\x{81FA}\x{81FB}\x{81FC}\x{81FD}\x{81FE}\x{81FF}\x{8200}\x{8201}\x{8202}' .
+'\x{8203}\x{8204}\x{8205}\x{8206}\x{8207}\x{8208}\x{8209}\x{820A}\x{820B}' .
+'\x{820C}\x{820D}\x{820E}\x{820F}\x{8210}\x{8211}\x{8212}\x{8214}\x{8215}' .
+'\x{8216}\x{8218}\x{8219}\x{821A}\x{821B}\x{821C}\x{821D}\x{821E}\x{821F}' .
+'\x{8220}\x{8221}\x{8222}\x{8223}\x{8225}\x{8226}\x{8227}\x{8228}\x{8229}' .
+'\x{822A}\x{822B}\x{822C}\x{822D}\x{822F}\x{8230}\x{8231}\x{8232}\x{8233}' .
+'\x{8234}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{823A}\x{823B}\x{823C}' .
+'\x{823D}\x{823E}\x{823F}\x{8240}\x{8242}\x{8243}\x{8244}\x{8245}\x{8246}' .
+'\x{8247}\x{8248}\x{8249}\x{824A}\x{824B}\x{824C}\x{824D}\x{824E}\x{824F}' .
+'\x{8250}\x{8251}\x{8252}\x{8253}\x{8254}\x{8255}\x{8256}\x{8257}\x{8258}' .
+'\x{8259}\x{825A}\x{825B}\x{825C}\x{825D}\x{825E}\x{825F}\x{8260}\x{8261}' .
+'\x{8263}\x{8264}\x{8266}\x{8267}\x{8268}\x{8269}\x{826A}\x{826B}\x{826C}' .
+'\x{826D}\x{826E}\x{826F}\x{8270}\x{8271}\x{8272}\x{8273}\x{8274}\x{8275}' .
+'\x{8276}\x{8277}\x{8278}\x{8279}\x{827A}\x{827B}\x{827C}\x{827D}\x{827E}' .
+'\x{827F}\x{8280}\x{8281}\x{8282}\x{8283}\x{8284}\x{8285}\x{8286}\x{8287}' .
+'\x{8288}\x{8289}\x{828A}\x{828B}\x{828D}\x{828E}\x{828F}\x{8290}\x{8291}' .
+'\x{8292}\x{8293}\x{8294}\x{8295}\x{8296}\x{8297}\x{8298}\x{8299}\x{829A}' .
+'\x{829B}\x{829C}\x{829D}\x{829E}\x{829F}\x{82A0}\x{82A1}\x{82A2}\x{82A3}' .
+'\x{82A4}\x{82A5}\x{82A6}\x{82A7}\x{82A8}\x{82A9}\x{82AA}\x{82AB}\x{82AC}' .
+'\x{82AD}\x{82AE}\x{82AF}\x{82B0}\x{82B1}\x{82B3}\x{82B4}\x{82B5}\x{82B6}' .
+'\x{82B7}\x{82B8}\x{82B9}\x{82BA}\x{82BB}\x{82BC}\x{82BD}\x{82BE}\x{82BF}' .
+'\x{82C0}\x{82C1}\x{82C2}\x{82C3}\x{82C4}\x{82C5}\x{82C6}\x{82C7}\x{82C8}' .
+'\x{82C9}\x{82CA}\x{82CB}\x{82CC}\x{82CD}\x{82CE}\x{82CF}\x{82D0}\x{82D1}' .
+'\x{82D2}\x{82D3}\x{82D4}\x{82D5}\x{82D6}\x{82D7}\x{82D8}\x{82D9}\x{82DA}' .
+'\x{82DB}\x{82DC}\x{82DD}\x{82DE}\x{82DF}\x{82E0}\x{82E1}\x{82E3}\x{82E4}' .
+'\x{82E5}\x{82E6}\x{82E7}\x{82E8}\x{82E9}\x{82EA}\x{82EB}\x{82EC}\x{82ED}' .
+'\x{82EE}\x{82EF}\x{82F0}\x{82F1}\x{82F2}\x{82F3}\x{82F4}\x{82F5}\x{82F6}' .
+'\x{82F7}\x{82F8}\x{82F9}\x{82FA}\x{82FB}\x{82FD}\x{82FE}\x{82FF}\x{8300}' .
+'\x{8301}\x{8302}\x{8303}\x{8304}\x{8305}\x{8306}\x{8307}\x{8308}\x{8309}' .
+'\x{830B}\x{830C}\x{830D}\x{830E}\x{830F}\x{8311}\x{8312}\x{8313}\x{8314}' .
+'\x{8315}\x{8316}\x{8317}\x{8318}\x{8319}\x{831A}\x{831B}\x{831C}\x{831D}' .
+'\x{831E}\x{831F}\x{8320}\x{8321}\x{8322}\x{8323}\x{8324}\x{8325}\x{8326}' .
+'\x{8327}\x{8328}\x{8329}\x{832A}\x{832B}\x{832C}\x{832D}\x{832E}\x{832F}' .
+'\x{8331}\x{8332}\x{8333}\x{8334}\x{8335}\x{8336}\x{8337}\x{8338}\x{8339}' .
+'\x{833A}\x{833B}\x{833C}\x{833D}\x{833E}\x{833F}\x{8340}\x{8341}\x{8342}' .
+'\x{8343}\x{8344}\x{8345}\x{8346}\x{8347}\x{8348}\x{8349}\x{834A}\x{834B}' .
+'\x{834C}\x{834D}\x{834E}\x{834F}\x{8350}\x{8351}\x{8352}\x{8353}\x{8354}' .
+'\x{8356}\x{8357}\x{8358}\x{8359}\x{835A}\x{835B}\x{835C}\x{835D}\x{835E}' .
+'\x{835F}\x{8360}\x{8361}\x{8362}\x{8363}\x{8364}\x{8365}\x{8366}\x{8367}' .
+'\x{8368}\x{8369}\x{836A}\x{836B}\x{836C}\x{836D}\x{836E}\x{836F}\x{8370}' .
+'\x{8371}\x{8372}\x{8373}\x{8374}\x{8375}\x{8376}\x{8377}\x{8378}\x{8379}' .
+'\x{837A}\x{837B}\x{837C}\x{837D}\x{837E}\x{837F}\x{8380}\x{8381}\x{8382}' .
+'\x{8383}\x{8384}\x{8385}\x{8386}\x{8387}\x{8388}\x{8389}\x{838A}\x{838B}' .
+'\x{838C}\x{838D}\x{838E}\x{838F}\x{8390}\x{8391}\x{8392}\x{8393}\x{8394}' .
+'\x{8395}\x{8396}\x{8397}\x{8398}\x{8399}\x{839A}\x{839B}\x{839C}\x{839D}' .
+'\x{839E}\x{83A0}\x{83A1}\x{83A2}\x{83A3}\x{83A4}\x{83A5}\x{83A6}\x{83A7}' .
+'\x{83A8}\x{83A9}\x{83AA}\x{83AB}\x{83AC}\x{83AD}\x{83AE}\x{83AF}\x{83B0}' .
+'\x{83B1}\x{83B2}\x{83B3}\x{83B4}\x{83B6}\x{83B7}\x{83B8}\x{83B9}\x{83BA}' .
+'\x{83BB}\x{83BC}\x{83BD}\x{83BF}\x{83C0}\x{83C1}\x{83C2}\x{83C3}\x{83C4}' .
+'\x{83C5}\x{83C6}\x{83C7}\x{83C8}\x{83C9}\x{83CA}\x{83CB}\x{83CC}\x{83CD}' .
+'\x{83CE}\x{83CF}\x{83D0}\x{83D1}\x{83D2}\x{83D3}\x{83D4}\x{83D5}\x{83D6}' .
+'\x{83D7}\x{83D8}\x{83D9}\x{83DA}\x{83DB}\x{83DC}\x{83DD}\x{83DE}\x{83DF}' .
+'\x{83E0}\x{83E1}\x{83E2}\x{83E3}\x{83E4}\x{83E5}\x{83E7}\x{83E8}\x{83E9}' .
+'\x{83EA}\x{83EB}\x{83EC}\x{83EE}\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F3}' .
+'\x{83F4}\x{83F5}\x{83F6}\x{83F7}\x{83F8}\x{83F9}\x{83FA}\x{83FB}\x{83FC}' .
+'\x{83FD}\x{83FE}\x{83FF}\x{8400}\x{8401}\x{8402}\x{8403}\x{8404}\x{8405}' .
+'\x{8406}\x{8407}\x{8408}\x{8409}\x{840A}\x{840B}\x{840C}\x{840D}\x{840E}' .
+'\x{840F}\x{8410}\x{8411}\x{8412}\x{8413}\x{8415}\x{8418}\x{8419}\x{841A}' .
+'\x{841B}\x{841C}\x{841D}\x{841E}\x{8421}\x{8422}\x{8423}\x{8424}\x{8425}' .
+'\x{8426}\x{8427}\x{8428}\x{8429}\x{842A}\x{842B}\x{842C}\x{842D}\x{842E}' .
+'\x{842F}\x{8430}\x{8431}\x{8432}\x{8433}\x{8434}\x{8435}\x{8436}\x{8437}' .
+'\x{8438}\x{8439}\x{843A}\x{843B}\x{843C}\x{843D}\x{843E}\x{843F}\x{8440}' .
+'\x{8441}\x{8442}\x{8443}\x{8444}\x{8445}\x{8446}\x{8447}\x{8448}\x{8449}' .
+'\x{844A}\x{844B}\x{844C}\x{844D}\x{844E}\x{844F}\x{8450}\x{8451}\x{8452}' .
+'\x{8453}\x{8454}\x{8455}\x{8456}\x{8457}\x{8459}\x{845A}\x{845B}\x{845C}' .
+'\x{845D}\x{845E}\x{845F}\x{8460}\x{8461}\x{8462}\x{8463}\x{8464}\x{8465}' .
+'\x{8466}\x{8467}\x{8468}\x{8469}\x{846A}\x{846B}\x{846C}\x{846D}\x{846E}' .
+'\x{846F}\x{8470}\x{8471}\x{8472}\x{8473}\x{8474}\x{8475}\x{8476}\x{8477}' .
+'\x{8478}\x{8479}\x{847A}\x{847B}\x{847C}\x{847D}\x{847E}\x{847F}\x{8480}' .
+'\x{8481}\x{8482}\x{8484}\x{8485}\x{8486}\x{8487}\x{8488}\x{8489}\x{848A}' .
+'\x{848B}\x{848C}\x{848D}\x{848E}\x{848F}\x{8490}\x{8491}\x{8492}\x{8493}' .
+'\x{8494}\x{8496}\x{8497}\x{8498}\x{8499}\x{849A}\x{849B}\x{849C}\x{849D}' .
+'\x{849E}\x{849F}\x{84A0}\x{84A1}\x{84A2}\x{84A3}\x{84A4}\x{84A5}\x{84A6}' .
+'\x{84A7}\x{84A8}\x{84A9}\x{84AA}\x{84AB}\x{84AC}\x{84AE}\x{84AF}\x{84B0}' .
+'\x{84B1}\x{84B2}\x{84B3}\x{84B4}\x{84B5}\x{84B6}\x{84B8}\x{84B9}\x{84BA}' .
+'\x{84BB}\x{84BC}\x{84BD}\x{84BE}\x{84BF}\x{84C0}\x{84C1}\x{84C2}\x{84C4}' .
+'\x{84C5}\x{84C6}\x{84C7}\x{84C8}\x{84C9}\x{84CA}\x{84CB}\x{84CC}\x{84CD}' .
+'\x{84CE}\x{84CF}\x{84D0}\x{84D1}\x{84D2}\x{84D3}\x{84D4}\x{84D5}\x{84D6}' .
+'\x{84D7}\x{84D8}\x{84D9}\x{84DB}\x{84DC}\x{84DD}\x{84DE}\x{84DF}\x{84E0}' .
+'\x{84E1}\x{84E2}\x{84E3}\x{84E4}\x{84E5}\x{84E6}\x{84E7}\x{84E8}\x{84E9}' .
+'\x{84EA}\x{84EB}\x{84EC}\x{84EE}\x{84EF}\x{84F0}\x{84F1}\x{84F2}\x{84F3}' .
+'\x{84F4}\x{84F5}\x{84F6}\x{84F7}\x{84F8}\x{84F9}\x{84FA}\x{84FB}\x{84FC}' .
+'\x{84FD}\x{84FE}\x{84FF}\x{8500}\x{8501}\x{8502}\x{8503}\x{8504}\x{8506}' .
+'\x{8507}\x{8508}\x{8509}\x{850A}\x{850B}\x{850C}\x{850D}\x{850E}\x{850F}' .
+'\x{8511}\x{8512}\x{8513}\x{8514}\x{8515}\x{8516}\x{8517}\x{8518}\x{8519}' .
+'\x{851A}\x{851B}\x{851C}\x{851D}\x{851E}\x{851F}\x{8520}\x{8521}\x{8522}' .
+'\x{8523}\x{8524}\x{8525}\x{8526}\x{8527}\x{8528}\x{8529}\x{852A}\x{852B}' .
+'\x{852C}\x{852D}\x{852E}\x{852F}\x{8530}\x{8531}\x{8534}\x{8535}\x{8536}' .
+'\x{8537}\x{8538}\x{8539}\x{853A}\x{853B}\x{853C}\x{853D}\x{853E}\x{853F}' .
+'\x{8540}\x{8541}\x{8542}\x{8543}\x{8544}\x{8545}\x{8546}\x{8547}\x{8548}' .
+'\x{8549}\x{854A}\x{854B}\x{854D}\x{854E}\x{854F}\x{8551}\x{8552}\x{8553}' .
+'\x{8554}\x{8555}\x{8556}\x{8557}\x{8558}\x{8559}\x{855A}\x{855B}\x{855C}' .
+'\x{855D}\x{855E}\x{855F}\x{8560}\x{8561}\x{8562}\x{8563}\x{8564}\x{8565}' .
+'\x{8566}\x{8567}\x{8568}\x{8569}\x{856A}\x{856B}\x{856C}\x{856D}\x{856E}' .
+'\x{856F}\x{8570}\x{8571}\x{8572}\x{8573}\x{8574}\x{8575}\x{8576}\x{8577}' .
+'\x{8578}\x{8579}\x{857A}\x{857B}\x{857C}\x{857D}\x{857E}\x{8580}\x{8581}' .
+'\x{8582}\x{8583}\x{8584}\x{8585}\x{8586}\x{8587}\x{8588}\x{8589}\x{858A}' .
+'\x{858B}\x{858C}\x{858D}\x{858E}\x{858F}\x{8590}\x{8591}\x{8592}\x{8594}' .
+'\x{8595}\x{8596}\x{8598}\x{8599}\x{859A}\x{859B}\x{859C}\x{859D}\x{859E}' .
+'\x{859F}\x{85A0}\x{85A1}\x{85A2}\x{85A3}\x{85A4}\x{85A5}\x{85A6}\x{85A7}' .
+'\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AD}\x{85AE}\x{85AF}\x{85B0}' .
+'\x{85B1}\x{85B3}\x{85B4}\x{85B5}\x{85B6}\x{85B7}\x{85B8}\x{85B9}\x{85BA}' .
+'\x{85BC}\x{85BD}\x{85BE}\x{85BF}\x{85C0}\x{85C1}\x{85C2}\x{85C3}\x{85C4}' .
+'\x{85C5}\x{85C6}\x{85C7}\x{85C8}\x{85C9}\x{85CA}\x{85CB}\x{85CD}\x{85CE}' .
+'\x{85CF}\x{85D0}\x{85D1}\x{85D2}\x{85D3}\x{85D4}\x{85D5}\x{85D6}\x{85D7}' .
+'\x{85D8}\x{85D9}\x{85DA}\x{85DB}\x{85DC}\x{85DD}\x{85DE}\x{85DF}\x{85E0}' .
+'\x{85E1}\x{85E2}\x{85E3}\x{85E4}\x{85E5}\x{85E6}\x{85E7}\x{85E8}\x{85E9}' .
+'\x{85EA}\x{85EB}\x{85EC}\x{85ED}\x{85EF}\x{85F0}\x{85F1}\x{85F2}\x{85F4}' .
+'\x{85F5}\x{85F6}\x{85F7}\x{85F8}\x{85F9}\x{85FA}\x{85FB}\x{85FD}\x{85FE}' .
+'\x{85FF}\x{8600}\x{8601}\x{8602}\x{8604}\x{8605}\x{8606}\x{8607}\x{8608}' .
+'\x{8609}\x{860A}\x{860B}\x{860C}\x{860F}\x{8611}\x{8612}\x{8613}\x{8614}' .
+'\x{8616}\x{8617}\x{8618}\x{8619}\x{861A}\x{861B}\x{861C}\x{861E}\x{861F}' .
+'\x{8620}\x{8621}\x{8622}\x{8623}\x{8624}\x{8625}\x{8626}\x{8627}\x{8628}' .
+'\x{8629}\x{862A}\x{862B}\x{862C}\x{862D}\x{862E}\x{862F}\x{8630}\x{8631}' .
+'\x{8632}\x{8633}\x{8634}\x{8635}\x{8636}\x{8638}\x{8639}\x{863A}\x{863B}' .
+'\x{863C}\x{863D}\x{863E}\x{863F}\x{8640}\x{8641}\x{8642}\x{8643}\x{8644}' .
+'\x{8645}\x{8646}\x{8647}\x{8648}\x{8649}\x{864A}\x{864B}\x{864C}\x{864D}' .
+'\x{864E}\x{864F}\x{8650}\x{8651}\x{8652}\x{8653}\x{8654}\x{8655}\x{8656}' .
+'\x{8658}\x{8659}\x{865A}\x{865B}\x{865C}\x{865D}\x{865E}\x{865F}\x{8660}' .
+'\x{8661}\x{8662}\x{8663}\x{8664}\x{8665}\x{8666}\x{8667}\x{8668}\x{8669}' .
+'\x{866A}\x{866B}\x{866C}\x{866D}\x{866E}\x{866F}\x{8670}\x{8671}\x{8672}' .
+'\x{8673}\x{8674}\x{8676}\x{8677}\x{8678}\x{8679}\x{867A}\x{867B}\x{867C}' .
+'\x{867D}\x{867E}\x{867F}\x{8680}\x{8681}\x{8682}\x{8683}\x{8684}\x{8685}' .
+'\x{8686}\x{8687}\x{8688}\x{868A}\x{868B}\x{868C}\x{868D}\x{868E}\x{868F}' .
+'\x{8690}\x{8691}\x{8693}\x{8694}\x{8695}\x{8696}\x{8697}\x{8698}\x{8699}' .
+'\x{869A}\x{869B}\x{869C}\x{869D}\x{869E}\x{869F}\x{86A1}\x{86A2}\x{86A3}' .
+'\x{86A4}\x{86A5}\x{86A7}\x{86A8}\x{86A9}\x{86AA}\x{86AB}\x{86AC}\x{86AD}' .
+'\x{86AE}\x{86AF}\x{86B0}\x{86B1}\x{86B2}\x{86B3}\x{86B4}\x{86B5}\x{86B6}' .
+'\x{86B7}\x{86B8}\x{86B9}\x{86BA}\x{86BB}\x{86BC}\x{86BD}\x{86BE}\x{86BF}' .
+'\x{86C0}\x{86C1}\x{86C2}\x{86C3}\x{86C4}\x{86C5}\x{86C6}\x{86C7}\x{86C8}' .
+'\x{86C9}\x{86CA}\x{86CB}\x{86CC}\x{86CE}\x{86CF}\x{86D0}\x{86D1}\x{86D2}' .
+'\x{86D3}\x{86D4}\x{86D6}\x{86D7}\x{86D8}\x{86D9}\x{86DA}\x{86DB}\x{86DC}' .
+'\x{86DD}\x{86DE}\x{86DF}\x{86E1}\x{86E2}\x{86E3}\x{86E4}\x{86E5}\x{86E6}' .
+'\x{86E8}\x{86E9}\x{86EA}\x{86EB}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F0}' .
+'\x{86F1}\x{86F2}\x{86F3}\x{86F4}\x{86F5}\x{86F6}\x{86F7}\x{86F8}\x{86F9}' .
+'\x{86FA}\x{86FB}\x{86FC}\x{86FE}\x{86FF}\x{8700}\x{8701}\x{8702}\x{8703}' .
+'\x{8704}\x{8705}\x{8706}\x{8707}\x{8708}\x{8709}\x{870A}\x{870B}\x{870C}' .
+'\x{870D}\x{870E}\x{870F}\x{8710}\x{8711}\x{8712}\x{8713}\x{8714}\x{8715}' .
+'\x{8716}\x{8717}\x{8718}\x{8719}\x{871A}\x{871B}\x{871C}\x{871E}\x{871F}' .
+'\x{8720}\x{8721}\x{8722}\x{8723}\x{8724}\x{8725}\x{8726}\x{8727}\x{8728}' .
+'\x{8729}\x{872A}\x{872B}\x{872C}\x{872D}\x{872E}\x{8730}\x{8731}\x{8732}' .
+'\x{8733}\x{8734}\x{8735}\x{8736}\x{8737}\x{8738}\x{8739}\x{873A}\x{873B}' .
+'\x{873C}\x{873E}\x{873F}\x{8740}\x{8741}\x{8742}\x{8743}\x{8744}\x{8746}' .
+'\x{8747}\x{8748}\x{8749}\x{874A}\x{874C}\x{874D}\x{874E}\x{874F}\x{8750}' .
+'\x{8751}\x{8752}\x{8753}\x{8754}\x{8755}\x{8756}\x{8757}\x{8758}\x{8759}' .
+'\x{875A}\x{875B}\x{875C}\x{875D}\x{875E}\x{875F}\x{8760}\x{8761}\x{8762}' .
+'\x{8763}\x{8764}\x{8765}\x{8766}\x{8767}\x{8768}\x{8769}\x{876A}\x{876B}' .
+'\x{876C}\x{876D}\x{876E}\x{876F}\x{8770}\x{8772}\x{8773}\x{8774}\x{8775}' .
+'\x{8776}\x{8777}\x{8778}\x{8779}\x{877A}\x{877B}\x{877C}\x{877D}\x{877E}' .
+'\x{8780}\x{8781}\x{8782}\x{8783}\x{8784}\x{8785}\x{8786}\x{8787}\x{8788}' .
+'\x{8789}\x{878A}\x{878B}\x{878C}\x{878D}\x{878F}\x{8790}\x{8791}\x{8792}' .
+'\x{8793}\x{8794}\x{8795}\x{8796}\x{8797}\x{8798}\x{879A}\x{879B}\x{879C}' .
+'\x{879D}\x{879E}\x{879F}\x{87A0}\x{87A1}\x{87A2}\x{87A3}\x{87A4}\x{87A5}' .
+'\x{87A6}\x{87A7}\x{87A8}\x{87A9}\x{87AA}\x{87AB}\x{87AC}\x{87AD}\x{87AE}' .
+'\x{87AF}\x{87B0}\x{87B1}\x{87B2}\x{87B3}\x{87B4}\x{87B5}\x{87B6}\x{87B7}' .
+'\x{87B8}\x{87B9}\x{87BA}\x{87BB}\x{87BC}\x{87BD}\x{87BE}\x{87BF}\x{87C0}' .
+'\x{87C1}\x{87C2}\x{87C3}\x{87C4}\x{87C5}\x{87C6}\x{87C7}\x{87C8}\x{87C9}' .
+'\x{87CA}\x{87CB}\x{87CC}\x{87CD}\x{87CE}\x{87CF}\x{87D0}\x{87D1}\x{87D2}' .
+'\x{87D3}\x{87D4}\x{87D5}\x{87D6}\x{87D7}\x{87D8}\x{87D9}\x{87DB}\x{87DC}' .
+'\x{87DD}\x{87DE}\x{87DF}\x{87E0}\x{87E1}\x{87E2}\x{87E3}\x{87E4}\x{87E5}' .
+'\x{87E6}\x{87E7}\x{87E8}\x{87E9}\x{87EA}\x{87EB}\x{87EC}\x{87ED}\x{87EE}' .
+'\x{87EF}\x{87F1}\x{87F2}\x{87F3}\x{87F4}\x{87F5}\x{87F6}\x{87F7}\x{87F8}' .
+'\x{87F9}\x{87FA}\x{87FB}\x{87FC}\x{87FD}\x{87FE}\x{87FF}\x{8800}\x{8801}' .
+'\x{8802}\x{8803}\x{8804}\x{8805}\x{8806}\x{8808}\x{8809}\x{880A}\x{880B}' .
+'\x{880C}\x{880D}\x{880E}\x{880F}\x{8810}\x{8811}\x{8813}\x{8814}\x{8815}' .
+'\x{8816}\x{8817}\x{8818}\x{8819}\x{881A}\x{881B}\x{881C}\x{881D}\x{881E}' .
+'\x{881F}\x{8820}\x{8821}\x{8822}\x{8823}\x{8824}\x{8825}\x{8826}\x{8827}' .
+'\x{8828}\x{8829}\x{882A}\x{882B}\x{882C}\x{882E}\x{882F}\x{8830}\x{8831}' .
+'\x{8832}\x{8833}\x{8834}\x{8835}\x{8836}\x{8837}\x{8838}\x{8839}\x{883B}' .
+'\x{883C}\x{883D}\x{883E}\x{883F}\x{8840}\x{8841}\x{8842}\x{8843}\x{8844}' .
+'\x{8845}\x{8846}\x{8848}\x{8849}\x{884A}\x{884B}\x{884C}\x{884D}\x{884E}' .
+'\x{884F}\x{8850}\x{8851}\x{8852}\x{8853}\x{8854}\x{8855}\x{8856}\x{8857}' .
+'\x{8859}\x{885A}\x{885B}\x{885D}\x{885E}\x{8860}\x{8861}\x{8862}\x{8863}' .
+'\x{8864}\x{8865}\x{8866}\x{8867}\x{8868}\x{8869}\x{886A}\x{886B}\x{886C}' .
+'\x{886D}\x{886E}\x{886F}\x{8870}\x{8871}\x{8872}\x{8873}\x{8874}\x{8875}' .
+'\x{8876}\x{8877}\x{8878}\x{8879}\x{887B}\x{887C}\x{887D}\x{887E}\x{887F}' .
+'\x{8880}\x{8881}\x{8882}\x{8883}\x{8884}\x{8885}\x{8886}\x{8887}\x{8888}' .
+'\x{8889}\x{888A}\x{888B}\x{888C}\x{888D}\x{888E}\x{888F}\x{8890}\x{8891}' .
+'\x{8892}\x{8893}\x{8894}\x{8895}\x{8896}\x{8897}\x{8898}\x{8899}\x{889A}' .
+'\x{889B}\x{889C}\x{889D}\x{889E}\x{889F}\x{88A0}\x{88A1}\x{88A2}\x{88A3}' .
+'\x{88A4}\x{88A5}\x{88A6}\x{88A7}\x{88A8}\x{88A9}\x{88AA}\x{88AB}\x{88AC}' .
+'\x{88AD}\x{88AE}\x{88AF}\x{88B0}\x{88B1}\x{88B2}\x{88B3}\x{88B4}\x{88B6}' .
+'\x{88B7}\x{88B8}\x{88B9}\x{88BA}\x{88BB}\x{88BC}\x{88BD}\x{88BE}\x{88BF}' .
+'\x{88C0}\x{88C1}\x{88C2}\x{88C3}\x{88C4}\x{88C5}\x{88C6}\x{88C7}\x{88C8}' .
+'\x{88C9}\x{88CA}\x{88CB}\x{88CC}\x{88CD}\x{88CE}\x{88CF}\x{88D0}\x{88D1}' .
+'\x{88D2}\x{88D3}\x{88D4}\x{88D5}\x{88D6}\x{88D7}\x{88D8}\x{88D9}\x{88DA}' .
+'\x{88DB}\x{88DC}\x{88DD}\x{88DE}\x{88DF}\x{88E0}\x{88E1}\x{88E2}\x{88E3}' .
+'\x{88E4}\x{88E5}\x{88E7}\x{88E8}\x{88EA}\x{88EB}\x{88EC}\x{88EE}\x{88EF}' .
+'\x{88F0}\x{88F1}\x{88F2}\x{88F3}\x{88F4}\x{88F5}\x{88F6}\x{88F7}\x{88F8}' .
+'\x{88F9}\x{88FA}\x{88FB}\x{88FC}\x{88FD}\x{88FE}\x{88FF}\x{8900}\x{8901}' .
+'\x{8902}\x{8904}\x{8905}\x{8906}\x{8907}\x{8908}\x{8909}\x{890A}\x{890B}' .
+'\x{890C}\x{890D}\x{890E}\x{8910}\x{8911}\x{8912}\x{8913}\x{8914}\x{8915}' .
+'\x{8916}\x{8917}\x{8918}\x{8919}\x{891A}\x{891B}\x{891C}\x{891D}\x{891E}' .
+'\x{891F}\x{8920}\x{8921}\x{8922}\x{8923}\x{8925}\x{8926}\x{8927}\x{8928}' .
+'\x{8929}\x{892A}\x{892B}\x{892C}\x{892D}\x{892E}\x{892F}\x{8930}\x{8931}' .
+'\x{8932}\x{8933}\x{8934}\x{8935}\x{8936}\x{8937}\x{8938}\x{8939}\x{893A}' .
+'\x{893B}\x{893C}\x{893D}\x{893E}\x{893F}\x{8940}\x{8941}\x{8942}\x{8943}' .
+'\x{8944}\x{8945}\x{8946}\x{8947}\x{8948}\x{8949}\x{894A}\x{894B}\x{894C}' .
+'\x{894E}\x{894F}\x{8950}\x{8951}\x{8952}\x{8953}\x{8954}\x{8955}\x{8956}' .
+'\x{8957}\x{8958}\x{8959}\x{895A}\x{895B}\x{895C}\x{895D}\x{895E}\x{895F}' .
+'\x{8960}\x{8961}\x{8962}\x{8963}\x{8964}\x{8966}\x{8967}\x{8968}\x{8969}' .
+'\x{896A}\x{896B}\x{896C}\x{896D}\x{896E}\x{896F}\x{8970}\x{8971}\x{8972}' .
+'\x{8973}\x{8974}\x{8976}\x{8977}\x{8978}\x{8979}\x{897A}\x{897B}\x{897C}' .
+'\x{897E}\x{897F}\x{8980}\x{8981}\x{8982}\x{8983}\x{8984}\x{8985}\x{8986}' .
+'\x{8987}\x{8988}\x{8989}\x{898A}\x{898B}\x{898C}\x{898E}\x{898F}\x{8991}' .
+'\x{8992}\x{8993}\x{8995}\x{8996}\x{8997}\x{8998}\x{899A}\x{899B}\x{899C}' .
+'\x{899D}\x{899E}\x{899F}\x{89A0}\x{89A1}\x{89A2}\x{89A3}\x{89A4}\x{89A5}' .
+'\x{89A6}\x{89A7}\x{89A8}\x{89AA}\x{89AB}\x{89AC}\x{89AD}\x{89AE}\x{89AF}' .
+'\x{89B1}\x{89B2}\x{89B3}\x{89B5}\x{89B6}\x{89B7}\x{89B8}\x{89B9}\x{89BA}' .
+'\x{89BD}\x{89BE}\x{89BF}\x{89C0}\x{89C1}\x{89C2}\x{89C3}\x{89C4}\x{89C5}' .
+'\x{89C6}\x{89C7}\x{89C8}\x{89C9}\x{89CA}\x{89CB}\x{89CC}\x{89CD}\x{89CE}' .
+'\x{89CF}\x{89D0}\x{89D1}\x{89D2}\x{89D3}\x{89D4}\x{89D5}\x{89D6}\x{89D7}' .
+'\x{89D8}\x{89D9}\x{89DA}\x{89DB}\x{89DC}\x{89DD}\x{89DE}\x{89DF}\x{89E0}' .
+'\x{89E1}\x{89E2}\x{89E3}\x{89E4}\x{89E5}\x{89E6}\x{89E7}\x{89E8}\x{89E9}' .
+'\x{89EA}\x{89EB}\x{89EC}\x{89ED}\x{89EF}\x{89F0}\x{89F1}\x{89F2}\x{89F3}' .
+'\x{89F4}\x{89F6}\x{89F7}\x{89F8}\x{89FA}\x{89FB}\x{89FC}\x{89FE}\x{89FF}' .
+'\x{8A00}\x{8A01}\x{8A02}\x{8A03}\x{8A04}\x{8A07}\x{8A08}\x{8A09}\x{8A0A}' .
+'\x{8A0B}\x{8A0C}\x{8A0D}\x{8A0E}\x{8A0F}\x{8A10}\x{8A11}\x{8A12}\x{8A13}' .
+'\x{8A15}\x{8A16}\x{8A17}\x{8A18}\x{8A1A}\x{8A1B}\x{8A1C}\x{8A1D}\x{8A1E}' .
+'\x{8A1F}\x{8A22}\x{8A23}\x{8A24}\x{8A25}\x{8A26}\x{8A27}\x{8A28}\x{8A29}' .
+'\x{8A2A}\x{8A2C}\x{8A2D}\x{8A2E}\x{8A2F}\x{8A30}\x{8A31}\x{8A32}\x{8A34}' .
+'\x{8A35}\x{8A36}\x{8A37}\x{8A38}\x{8A39}\x{8A3A}\x{8A3B}\x{8A3C}\x{8A3E}' .
+'\x{8A3F}\x{8A40}\x{8A41}\x{8A42}\x{8A43}\x{8A44}\x{8A45}\x{8A46}\x{8A47}' .
+'\x{8A48}\x{8A49}\x{8A4A}\x{8A4C}\x{8A4D}\x{8A4E}\x{8A4F}\x{8A50}\x{8A51}' .
+'\x{8A52}\x{8A53}\x{8A54}\x{8A55}\x{8A56}\x{8A57}\x{8A58}\x{8A59}\x{8A5A}' .
+'\x{8A5B}\x{8A5C}\x{8A5D}\x{8A5E}\x{8A5F}\x{8A60}\x{8A61}\x{8A62}\x{8A63}' .
+'\x{8A65}\x{8A66}\x{8A67}\x{8A68}\x{8A69}\x{8A6A}\x{8A6B}\x{8A6C}\x{8A6D}' .
+'\x{8A6E}\x{8A6F}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A74}\x{8A75}\x{8A76}' .
+'\x{8A77}\x{8A79}\x{8A7A}\x{8A7B}\x{8A7C}\x{8A7E}\x{8A7F}\x{8A80}\x{8A81}' .
+'\x{8A82}\x{8A83}\x{8A84}\x{8A85}\x{8A86}\x{8A87}\x{8A89}\x{8A8A}\x{8A8B}' .
+'\x{8A8C}\x{8A8D}\x{8A8E}\x{8A8F}\x{8A90}\x{8A91}\x{8A92}\x{8A93}\x{8A94}' .
+'\x{8A95}\x{8A96}\x{8A97}\x{8A98}\x{8A99}\x{8A9A}\x{8A9B}\x{8A9C}\x{8A9D}' .
+'\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA2}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA7}' .
+'\x{8AA8}\x{8AA9}\x{8AAA}\x{8AAB}\x{8AAC}\x{8AAE}\x{8AB0}\x{8AB1}\x{8AB2}' .
+'\x{8AB3}\x{8AB4}\x{8AB5}\x{8AB6}\x{8AB8}\x{8AB9}\x{8ABA}\x{8ABB}\x{8ABC}' .
+'\x{8ABD}\x{8ABE}\x{8ABF}\x{8AC0}\x{8AC1}\x{8AC2}\x{8AC3}\x{8AC4}\x{8AC5}' .
+'\x{8AC6}\x{8AC7}\x{8AC8}\x{8AC9}\x{8ACA}\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACE}' .
+'\x{8ACF}\x{8AD1}\x{8AD2}\x{8AD3}\x{8AD4}\x{8AD5}\x{8AD6}\x{8AD7}\x{8AD8}' .
+'\x{8AD9}\x{8ADA}\x{8ADB}\x{8ADC}\x{8ADD}\x{8ADE}\x{8ADF}\x{8AE0}\x{8AE1}' .
+'\x{8AE2}\x{8AE3}\x{8AE4}\x{8AE5}\x{8AE6}\x{8AE7}\x{8AE8}\x{8AE9}\x{8AEA}' .
+'\x{8AEB}\x{8AED}\x{8AEE}\x{8AEF}\x{8AF0}\x{8AF1}\x{8AF2}\x{8AF3}\x{8AF4}' .
+'\x{8AF5}\x{8AF6}\x{8AF7}\x{8AF8}\x{8AF9}\x{8AFA}\x{8AFB}\x{8AFC}\x{8AFD}' .
+'\x{8AFE}\x{8AFF}\x{8B00}\x{8B01}\x{8B02}\x{8B03}\x{8B04}\x{8B05}\x{8B06}' .
+'\x{8B07}\x{8B08}\x{8B09}\x{8B0A}\x{8B0B}\x{8B0D}\x{8B0E}\x{8B0F}\x{8B10}' .
+'\x{8B11}\x{8B12}\x{8B13}\x{8B14}\x{8B15}\x{8B16}\x{8B17}\x{8B18}\x{8B19}' .
+'\x{8B1A}\x{8B1B}\x{8B1C}\x{8B1D}\x{8B1E}\x{8B1F}\x{8B20}\x{8B21}\x{8B22}' .
+'\x{8B23}\x{8B24}\x{8B25}\x{8B26}\x{8B27}\x{8B28}\x{8B2A}\x{8B2B}\x{8B2C}' .
+'\x{8B2D}\x{8B2E}\x{8B2F}\x{8B30}\x{8B31}\x{8B33}\x{8B34}\x{8B35}\x{8B36}' .
+'\x{8B37}\x{8B39}\x{8B3A}\x{8B3B}\x{8B3C}\x{8B3D}\x{8B3E}\x{8B40}\x{8B41}' .
+'\x{8B42}\x{8B43}\x{8B44}\x{8B45}\x{8B46}\x{8B47}\x{8B48}\x{8B49}\x{8B4A}' .
+'\x{8B4B}\x{8B4C}\x{8B4D}\x{8B4E}\x{8B4F}\x{8B50}\x{8B51}\x{8B52}\x{8B53}' .
+'\x{8B54}\x{8B55}\x{8B56}\x{8B57}\x{8B58}\x{8B59}\x{8B5A}\x{8B5B}\x{8B5C}' .
+'\x{8B5D}\x{8B5E}\x{8B5F}\x{8B60}\x{8B63}\x{8B64}\x{8B65}\x{8B66}\x{8B67}' .
+'\x{8B68}\x{8B6A}\x{8B6B}\x{8B6C}\x{8B6D}\x{8B6E}\x{8B6F}\x{8B70}\x{8B71}' .
+'\x{8B73}\x{8B74}\x{8B76}\x{8B77}\x{8B78}\x{8B79}\x{8B7A}\x{8B7B}\x{8B7D}' .
+'\x{8B7E}\x{8B7F}\x{8B80}\x{8B82}\x{8B83}\x{8B84}\x{8B85}\x{8B86}\x{8B88}' .
+'\x{8B89}\x{8B8A}\x{8B8B}\x{8B8C}\x{8B8E}\x{8B90}\x{8B91}\x{8B92}\x{8B93}' .
+'\x{8B94}\x{8B95}\x{8B96}\x{8B97}\x{8B98}\x{8B99}\x{8B9A}\x{8B9C}\x{8B9D}' .
+'\x{8B9E}\x{8B9F}\x{8BA0}\x{8BA1}\x{8BA2}\x{8BA3}\x{8BA4}\x{8BA5}\x{8BA6}' .
+'\x{8BA7}\x{8BA8}\x{8BA9}\x{8BAA}\x{8BAB}\x{8BAC}\x{8BAD}\x{8BAE}\x{8BAF}' .
+'\x{8BB0}\x{8BB1}\x{8BB2}\x{8BB3}\x{8BB4}\x{8BB5}\x{8BB6}\x{8BB7}\x{8BB8}' .
+'\x{8BB9}\x{8BBA}\x{8BBB}\x{8BBC}\x{8BBD}\x{8BBE}\x{8BBF}\x{8BC0}\x{8BC1}' .
+'\x{8BC2}\x{8BC3}\x{8BC4}\x{8BC5}\x{8BC6}\x{8BC7}\x{8BC8}\x{8BC9}\x{8BCA}' .
+'\x{8BCB}\x{8BCC}\x{8BCD}\x{8BCE}\x{8BCF}\x{8BD0}\x{8BD1}\x{8BD2}\x{8BD3}' .
+'\x{8BD4}\x{8BD5}\x{8BD6}\x{8BD7}\x{8BD8}\x{8BD9}\x{8BDA}\x{8BDB}\x{8BDC}' .
+'\x{8BDD}\x{8BDE}\x{8BDF}\x{8BE0}\x{8BE1}\x{8BE2}\x{8BE3}\x{8BE4}\x{8BE5}' .
+'\x{8BE6}\x{8BE7}\x{8BE8}\x{8BE9}\x{8BEA}\x{8BEB}\x{8BEC}\x{8BED}\x{8BEE}' .
+'\x{8BEF}\x{8BF0}\x{8BF1}\x{8BF2}\x{8BF3}\x{8BF4}\x{8BF5}\x{8BF6}\x{8BF7}' .
+'\x{8BF8}\x{8BF9}\x{8BFA}\x{8BFB}\x{8BFC}\x{8BFD}\x{8BFE}\x{8BFF}\x{8C00}' .
+'\x{8C01}\x{8C02}\x{8C03}\x{8C04}\x{8C05}\x{8C06}\x{8C07}\x{8C08}\x{8C09}' .
+'\x{8C0A}\x{8C0B}\x{8C0C}\x{8C0D}\x{8C0E}\x{8C0F}\x{8C10}\x{8C11}\x{8C12}' .
+'\x{8C13}\x{8C14}\x{8C15}\x{8C16}\x{8C17}\x{8C18}\x{8C19}\x{8C1A}\x{8C1B}' .
+'\x{8C1C}\x{8C1D}\x{8C1E}\x{8C1F}\x{8C20}\x{8C21}\x{8C22}\x{8C23}\x{8C24}' .
+'\x{8C25}\x{8C26}\x{8C27}\x{8C28}\x{8C29}\x{8C2A}\x{8C2B}\x{8C2C}\x{8C2D}' .
+'\x{8C2E}\x{8C2F}\x{8C30}\x{8C31}\x{8C32}\x{8C33}\x{8C34}\x{8C35}\x{8C36}' .
+'\x{8C37}\x{8C39}\x{8C3A}\x{8C3B}\x{8C3C}\x{8C3D}\x{8C3E}\x{8C3F}\x{8C41}' .
+'\x{8C42}\x{8C43}\x{8C45}\x{8C46}\x{8C47}\x{8C48}\x{8C49}\x{8C4A}\x{8C4B}' .
+'\x{8C4C}\x{8C4D}\x{8C4E}\x{8C4F}\x{8C50}\x{8C54}\x{8C55}\x{8C56}\x{8C57}' .
+'\x{8C59}\x{8C5A}\x{8C5B}\x{8C5C}\x{8C5D}\x{8C5E}\x{8C5F}\x{8C60}\x{8C61}' .
+'\x{8C62}\x{8C63}\x{8C64}\x{8C65}\x{8C66}\x{8C67}\x{8C68}\x{8C69}\x{8C6A}' .
+'\x{8C6B}\x{8C6C}\x{8C6D}\x{8C6E}\x{8C6F}\x{8C70}\x{8C71}\x{8C72}\x{8C73}' .
+'\x{8C75}\x{8C76}\x{8C77}\x{8C78}\x{8C79}\x{8C7A}\x{8C7B}\x{8C7D}\x{8C7E}' .
+'\x{8C80}\x{8C81}\x{8C82}\x{8C84}\x{8C85}\x{8C86}\x{8C88}\x{8C89}\x{8C8A}' .
+'\x{8C8C}\x{8C8D}\x{8C8F}\x{8C90}\x{8C91}\x{8C92}\x{8C93}\x{8C94}\x{8C95}' .
+'\x{8C96}\x{8C97}\x{8C98}\x{8C99}\x{8C9A}\x{8C9C}\x{8C9D}\x{8C9E}\x{8C9F}' .
+'\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA3}\x{8CA4}\x{8CA5}\x{8CA7}\x{8CA8}\x{8CA9}' .
+'\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}\x{8CB1}\x{8CB2}' .
+'\x{8CB3}\x{8CB4}\x{8CB5}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CB9}\x{8CBA}\x{8CBB}' .
+'\x{8CBC}\x{8CBD}\x{8CBE}\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}' .
+'\x{8CC5}\x{8CC6}\x{8CC7}\x{8CC8}\x{8CC9}\x{8CCA}\x{8CCC}\x{8CCE}\x{8CCF}' .
+'\x{8CD0}\x{8CD1}\x{8CD2}\x{8CD3}\x{8CD4}\x{8CD5}\x{8CD7}\x{8CD9}\x{8CDA}' .
+'\x{8CDB}\x{8CDC}\x{8CDD}\x{8CDE}\x{8CDF}\x{8CE0}\x{8CE1}\x{8CE2}\x{8CE3}' .
+'\x{8CE4}\x{8CE5}\x{8CE6}\x{8CE7}\x{8CE8}\x{8CEA}\x{8CEB}\x{8CEC}\x{8CED}' .
+'\x{8CEE}\x{8CEF}\x{8CF0}\x{8CF1}\x{8CF2}\x{8CF3}\x{8CF4}\x{8CF5}\x{8CF6}' .
+'\x{8CF8}\x{8CF9}\x{8CFA}\x{8CFB}\x{8CFC}\x{8CFD}\x{8CFE}\x{8CFF}\x{8D00}' .
+'\x{8D02}\x{8D03}\x{8D04}\x{8D05}\x{8D06}\x{8D07}\x{8D08}\x{8D09}\x{8D0A}' .
+'\x{8D0B}\x{8D0C}\x{8D0D}\x{8D0E}\x{8D0F}\x{8D10}\x{8D13}\x{8D14}\x{8D15}' .
+'\x{8D16}\x{8D17}\x{8D18}\x{8D19}\x{8D1A}\x{8D1B}\x{8D1C}\x{8D1D}\x{8D1E}' .
+'\x{8D1F}\x{8D20}\x{8D21}\x{8D22}\x{8D23}\x{8D24}\x{8D25}\x{8D26}\x{8D27}' .
+'\x{8D28}\x{8D29}\x{8D2A}\x{8D2B}\x{8D2C}\x{8D2D}\x{8D2E}\x{8D2F}\x{8D30}' .
+'\x{8D31}\x{8D32}\x{8D33}\x{8D34}\x{8D35}\x{8D36}\x{8D37}\x{8D38}\x{8D39}' .
+'\x{8D3A}\x{8D3B}\x{8D3C}\x{8D3D}\x{8D3E}\x{8D3F}\x{8D40}\x{8D41}\x{8D42}' .
+'\x{8D43}\x{8D44}\x{8D45}\x{8D46}\x{8D47}\x{8D48}\x{8D49}\x{8D4A}\x{8D4B}' .
+'\x{8D4C}\x{8D4D}\x{8D4E}\x{8D4F}\x{8D50}\x{8D51}\x{8D52}\x{8D53}\x{8D54}' .
+'\x{8D55}\x{8D56}\x{8D57}\x{8D58}\x{8D59}\x{8D5A}\x{8D5B}\x{8D5C}\x{8D5D}' .
+'\x{8D5E}\x{8D5F}\x{8D60}\x{8D61}\x{8D62}\x{8D63}\x{8D64}\x{8D65}\x{8D66}' .
+'\x{8D67}\x{8D68}\x{8D69}\x{8D6A}\x{8D6B}\x{8D6C}\x{8D6D}\x{8D6E}\x{8D6F}' .
+'\x{8D70}\x{8D71}\x{8D72}\x{8D73}\x{8D74}\x{8D75}\x{8D76}\x{8D77}\x{8D78}' .
+'\x{8D79}\x{8D7A}\x{8D7B}\x{8D7D}\x{8D7E}\x{8D7F}\x{8D80}\x{8D81}\x{8D82}' .
+'\x{8D83}\x{8D84}\x{8D85}\x{8D86}\x{8D87}\x{8D88}\x{8D89}\x{8D8A}\x{8D8B}' .
+'\x{8D8C}\x{8D8D}\x{8D8E}\x{8D8F}\x{8D90}\x{8D91}\x{8D92}\x{8D93}\x{8D94}' .
+'\x{8D95}\x{8D96}\x{8D97}\x{8D98}\x{8D99}\x{8D9A}\x{8D9B}\x{8D9C}\x{8D9D}' .
+'\x{8D9E}\x{8D9F}\x{8DA0}\x{8DA1}\x{8DA2}\x{8DA3}\x{8DA4}\x{8DA5}\x{8DA7}' .
+'\x{8DA8}\x{8DA9}\x{8DAA}\x{8DAB}\x{8DAC}\x{8DAD}\x{8DAE}\x{8DAF}\x{8DB0}' .
+'\x{8DB1}\x{8DB2}\x{8DB3}\x{8DB4}\x{8DB5}\x{8DB6}\x{8DB7}\x{8DB8}\x{8DB9}' .
+'\x{8DBA}\x{8DBB}\x{8DBC}\x{8DBD}\x{8DBE}\x{8DBF}\x{8DC1}\x{8DC2}\x{8DC3}' .
+'\x{8DC4}\x{8DC5}\x{8DC6}\x{8DC7}\x{8DC8}\x{8DC9}\x{8DCA}\x{8DCB}\x{8DCC}' .
+'\x{8DCD}\x{8DCE}\x{8DCF}\x{8DD0}\x{8DD1}\x{8DD2}\x{8DD3}\x{8DD4}\x{8DD5}' .
+'\x{8DD6}\x{8DD7}\x{8DD8}\x{8DD9}\x{8DDA}\x{8DDB}\x{8DDC}\x{8DDD}\x{8DDE}' .
+'\x{8DDF}\x{8DE0}\x{8DE1}\x{8DE2}\x{8DE3}\x{8DE4}\x{8DE6}\x{8DE7}\x{8DE8}' .
+'\x{8DE9}\x{8DEA}\x{8DEB}\x{8DEC}\x{8DED}\x{8DEE}\x{8DEF}\x{8DF0}\x{8DF1}' .
+'\x{8DF2}\x{8DF3}\x{8DF4}\x{8DF5}\x{8DF6}\x{8DF7}\x{8DF8}\x{8DF9}\x{8DFA}' .
+'\x{8DFB}\x{8DFC}\x{8DFD}\x{8DFE}\x{8DFF}\x{8E00}\x{8E02}\x{8E03}\x{8E04}' .
+'\x{8E05}\x{8E06}\x{8E07}\x{8E08}\x{8E09}\x{8E0A}\x{8E0C}\x{8E0D}\x{8E0E}' .
+'\x{8E0F}\x{8E10}\x{8E11}\x{8E12}\x{8E13}\x{8E14}\x{8E15}\x{8E16}\x{8E17}' .
+'\x{8E18}\x{8E19}\x{8E1A}\x{8E1B}\x{8E1C}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E20}' .
+'\x{8E21}\x{8E22}\x{8E23}\x{8E24}\x{8E25}\x{8E26}\x{8E27}\x{8E28}\x{8E29}' .
+'\x{8E2A}\x{8E2B}\x{8E2C}\x{8E2D}\x{8E2E}\x{8E2F}\x{8E30}\x{8E31}\x{8E33}' .
+'\x{8E34}\x{8E35}\x{8E36}\x{8E37}\x{8E38}\x{8E39}\x{8E3A}\x{8E3B}\x{8E3C}' .
+'\x{8E3D}\x{8E3E}\x{8E3F}\x{8E40}\x{8E41}\x{8E42}\x{8E43}\x{8E44}\x{8E45}' .
+'\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4B}\x{8E4C}\x{8E4D}\x{8E4E}\x{8E50}' .
+'\x{8E51}\x{8E52}\x{8E53}\x{8E54}\x{8E55}\x{8E56}\x{8E57}\x{8E58}\x{8E59}' .
+'\x{8E5A}\x{8E5B}\x{8E5C}\x{8E5D}\x{8E5E}\x{8E5F}\x{8E60}\x{8E61}\x{8E62}' .
+'\x{8E63}\x{8E64}\x{8E65}\x{8E66}\x{8E67}\x{8E68}\x{8E69}\x{8E6A}\x{8E6B}' .
+'\x{8E6C}\x{8E6D}\x{8E6F}\x{8E70}\x{8E71}\x{8E72}\x{8E73}\x{8E74}\x{8E76}' .
+'\x{8E78}\x{8E7A}\x{8E7B}\x{8E7C}\x{8E7D}\x{8E7E}\x{8E7F}\x{8E80}\x{8E81}' .
+'\x{8E82}\x{8E83}\x{8E84}\x{8E85}\x{8E86}\x{8E87}\x{8E88}\x{8E89}\x{8E8A}' .
+'\x{8E8B}\x{8E8C}\x{8E8D}\x{8E8E}\x{8E8F}\x{8E90}\x{8E91}\x{8E92}\x{8E93}' .
+'\x{8E94}\x{8E95}\x{8E96}\x{8E97}\x{8E98}\x{8E9A}\x{8E9C}\x{8E9D}\x{8E9E}' .
+'\x{8E9F}\x{8EA0}\x{8EA1}\x{8EA3}\x{8EA4}\x{8EA5}\x{8EA6}\x{8EA7}\x{8EA8}' .
+'\x{8EA9}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAD}\x{8EAE}\x{8EAF}\x{8EB0}\x{8EB1}' .
+'\x{8EB2}\x{8EB4}\x{8EB5}\x{8EB8}\x{8EB9}\x{8EBA}\x{8EBB}\x{8EBC}\x{8EBD}' .
+'\x{8EBE}\x{8EBF}\x{8EC0}\x{8EC2}\x{8EC3}\x{8EC5}\x{8EC6}\x{8EC7}\x{8EC8}' .
+'\x{8EC9}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ECE}\x{8ECF}\x{8ED0}\x{8ED1}' .
+'\x{8ED2}\x{8ED3}\x{8ED4}\x{8ED5}\x{8ED6}\x{8ED7}\x{8ED8}\x{8EDA}\x{8EDB}' .
+'\x{8EDC}\x{8EDD}\x{8EDE}\x{8EDF}\x{8EE0}\x{8EE1}\x{8EE4}\x{8EE5}\x{8EE6}' .
+'\x{8EE7}\x{8EE8}\x{8EE9}\x{8EEA}\x{8EEB}\x{8EEC}\x{8EED}\x{8EEE}\x{8EEF}' .
+'\x{8EF1}\x{8EF2}\x{8EF3}\x{8EF4}\x{8EF5}\x{8EF6}\x{8EF7}\x{8EF8}\x{8EF9}' .
+'\x{8EFA}\x{8EFB}\x{8EFC}\x{8EFD}\x{8EFE}\x{8EFF}\x{8F00}\x{8F01}\x{8F02}' .
+'\x{8F03}\x{8F04}\x{8F05}\x{8F06}\x{8F07}\x{8F08}\x{8F09}\x{8F0A}\x{8F0B}' .
+'\x{8F0D}\x{8F0E}\x{8F10}\x{8F11}\x{8F12}\x{8F13}\x{8F14}\x{8F15}\x{8F16}' .
+'\x{8F17}\x{8F18}\x{8F1A}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1E}\x{8F1F}\x{8F20}' .
+'\x{8F21}\x{8F22}\x{8F23}\x{8F24}\x{8F25}\x{8F26}\x{8F27}\x{8F28}\x{8F29}' .
+'\x{8F2A}\x{8F2B}\x{8F2C}\x{8F2E}\x{8F2F}\x{8F30}\x{8F31}\x{8F32}\x{8F33}' .
+'\x{8F34}\x{8F35}\x{8F36}\x{8F37}\x{8F38}\x{8F39}\x{8F3B}\x{8F3C}\x{8F3D}' .
+'\x{8F3E}\x{8F3F}\x{8F40}\x{8F42}\x{8F43}\x{8F44}\x{8F45}\x{8F46}\x{8F47}' .
+'\x{8F48}\x{8F49}\x{8F4A}\x{8F4B}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F4F}\x{8F50}' .
+'\x{8F51}\x{8F52}\x{8F53}\x{8F54}\x{8F55}\x{8F56}\x{8F57}\x{8F58}\x{8F59}' .
+'\x{8F5A}\x{8F5B}\x{8F5D}\x{8F5E}\x{8F5F}\x{8F60}\x{8F61}\x{8F62}\x{8F63}' .
+'\x{8F64}\x{8F65}\x{8F66}\x{8F67}\x{8F68}\x{8F69}\x{8F6A}\x{8F6B}\x{8F6C}' .
+'\x{8F6D}\x{8F6E}\x{8F6F}\x{8F70}\x{8F71}\x{8F72}\x{8F73}\x{8F74}\x{8F75}' .
+'\x{8F76}\x{8F77}\x{8F78}\x{8F79}\x{8F7A}\x{8F7B}\x{8F7C}\x{8F7D}\x{8F7E}' .
+'\x{8F7F}\x{8F80}\x{8F81}\x{8F82}\x{8F83}\x{8F84}\x{8F85}\x{8F86}\x{8F87}' .
+'\x{8F88}\x{8F89}\x{8F8A}\x{8F8B}\x{8F8C}\x{8F8D}\x{8F8E}\x{8F8F}\x{8F90}' .
+'\x{8F91}\x{8F92}\x{8F93}\x{8F94}\x{8F95}\x{8F96}\x{8F97}\x{8F98}\x{8F99}' .
+'\x{8F9A}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA0}\x{8FA1}\x{8FA2}\x{8FA3}' .
+'\x{8FA5}\x{8FA6}\x{8FA7}\x{8FA8}\x{8FA9}\x{8FAA}\x{8FAB}\x{8FAC}\x{8FAD}' .
+'\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB4}\x{8FB5}\x{8FB6}\x{8FB7}' .
+'\x{8FB8}\x{8FB9}\x{8FBB}\x{8FBC}\x{8FBD}\x{8FBE}\x{8FBF}\x{8FC0}\x{8FC1}' .
+'\x{8FC2}\x{8FC4}\x{8FC5}\x{8FC6}\x{8FC7}\x{8FC8}\x{8FC9}\x{8FCB}\x{8FCC}' .
+'\x{8FCD}\x{8FCE}\x{8FCF}\x{8FD0}\x{8FD1}\x{8FD2}\x{8FD3}\x{8FD4}\x{8FD5}' .
+'\x{8FD6}\x{8FD7}\x{8FD8}\x{8FD9}\x{8FDA}\x{8FDB}\x{8FDC}\x{8FDD}\x{8FDE}' .
+'\x{8FDF}\x{8FE0}\x{8FE1}\x{8FE2}\x{8FE3}\x{8FE4}\x{8FE5}\x{8FE6}\x{8FE8}' .
+'\x{8FE9}\x{8FEA}\x{8FEB}\x{8FEC}\x{8FED}\x{8FEE}\x{8FEF}\x{8FF0}\x{8FF1}' .
+'\x{8FF2}\x{8FF3}\x{8FF4}\x{8FF5}\x{8FF6}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}' .
+'\x{8FFB}\x{8FFC}\x{8FFD}\x{8FFE}\x{8FFF}\x{9000}\x{9001}\x{9002}\x{9003}' .
+'\x{9004}\x{9005}\x{9006}\x{9007}\x{9008}\x{9009}\x{900A}\x{900B}\x{900C}' .
+'\x{900D}\x{900F}\x{9010}\x{9011}\x{9012}\x{9013}\x{9014}\x{9015}\x{9016}' .
+'\x{9017}\x{9018}\x{9019}\x{901A}\x{901B}\x{901C}\x{901D}\x{901E}\x{901F}' .
+'\x{9020}\x{9021}\x{9022}\x{9023}\x{9024}\x{9025}\x{9026}\x{9027}\x{9028}' .
+'\x{9029}\x{902B}\x{902D}\x{902E}\x{902F}\x{9030}\x{9031}\x{9032}\x{9033}' .
+'\x{9034}\x{9035}\x{9036}\x{9038}\x{903A}\x{903B}\x{903C}\x{903D}\x{903E}' .
+'\x{903F}\x{9041}\x{9042}\x{9043}\x{9044}\x{9045}\x{9047}\x{9048}\x{9049}' .
+'\x{904A}\x{904B}\x{904C}\x{904D}\x{904E}\x{904F}\x{9050}\x{9051}\x{9052}' .
+'\x{9053}\x{9054}\x{9055}\x{9056}\x{9057}\x{9058}\x{9059}\x{905A}\x{905B}' .
+'\x{905C}\x{905D}\x{905E}\x{905F}\x{9060}\x{9061}\x{9062}\x{9063}\x{9064}' .
+'\x{9065}\x{9066}\x{9067}\x{9068}\x{9069}\x{906A}\x{906B}\x{906C}\x{906D}' .
+'\x{906E}\x{906F}\x{9070}\x{9071}\x{9072}\x{9073}\x{9074}\x{9075}\x{9076}' .
+'\x{9077}\x{9078}\x{9079}\x{907A}\x{907B}\x{907C}\x{907D}\x{907E}\x{907F}' .
+'\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9085}\x{9086}\x{9087}\x{9088}' .
+'\x{9089}\x{908A}\x{908B}\x{908C}\x{908D}\x{908E}\x{908F}\x{9090}\x{9091}' .
+'\x{9092}\x{9093}\x{9094}\x{9095}\x{9096}\x{9097}\x{9098}\x{9099}\x{909A}' .
+'\x{909B}\x{909C}\x{909D}\x{909E}\x{909F}\x{90A0}\x{90A1}\x{90A2}\x{90A3}' .
+'\x{90A4}\x{90A5}\x{90A6}\x{90A7}\x{90A8}\x{90A9}\x{90AA}\x{90AC}\x{90AD}' .
+'\x{90AE}\x{90AF}\x{90B0}\x{90B1}\x{90B2}\x{90B3}\x{90B4}\x{90B5}\x{90B6}' .
+'\x{90B7}\x{90B8}\x{90B9}\x{90BA}\x{90BB}\x{90BC}\x{90BD}\x{90BE}\x{90BF}' .
+'\x{90C0}\x{90C1}\x{90C2}\x{90C3}\x{90C4}\x{90C5}\x{90C6}\x{90C7}\x{90C8}' .
+'\x{90C9}\x{90CA}\x{90CB}\x{90CE}\x{90CF}\x{90D0}\x{90D1}\x{90D3}\x{90D4}' .
+'\x{90D5}\x{90D6}\x{90D7}\x{90D8}\x{90D9}\x{90DA}\x{90DB}\x{90DC}\x{90DD}' .
+'\x{90DE}\x{90DF}\x{90E0}\x{90E1}\x{90E2}\x{90E3}\x{90E4}\x{90E5}\x{90E6}' .
+'\x{90E7}\x{90E8}\x{90E9}\x{90EA}\x{90EB}\x{90EC}\x{90ED}\x{90EE}\x{90EF}' .
+'\x{90F0}\x{90F1}\x{90F2}\x{90F3}\x{90F4}\x{90F5}\x{90F7}\x{90F8}\x{90F9}' .
+'\x{90FA}\x{90FB}\x{90FC}\x{90FD}\x{90FE}\x{90FF}\x{9100}\x{9101}\x{9102}' .
+'\x{9103}\x{9104}\x{9105}\x{9106}\x{9107}\x{9108}\x{9109}\x{910B}\x{910C}' .
+'\x{910D}\x{910E}\x{910F}\x{9110}\x{9111}\x{9112}\x{9113}\x{9114}\x{9115}' .
+'\x{9116}\x{9117}\x{9118}\x{9119}\x{911A}\x{911B}\x{911C}\x{911D}\x{911E}' .
+'\x{911F}\x{9120}\x{9121}\x{9122}\x{9123}\x{9124}\x{9125}\x{9126}\x{9127}' .
+'\x{9128}\x{9129}\x{912A}\x{912B}\x{912C}\x{912D}\x{912E}\x{912F}\x{9130}' .
+'\x{9131}\x{9132}\x{9133}\x{9134}\x{9135}\x{9136}\x{9137}\x{9138}\x{9139}' .
+'\x{913A}\x{913B}\x{913E}\x{913F}\x{9140}\x{9141}\x{9142}\x{9143}\x{9144}' .
+'\x{9145}\x{9146}\x{9147}\x{9148}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}' .
+'\x{914E}\x{914F}\x{9150}\x{9151}\x{9152}\x{9153}\x{9154}\x{9155}\x{9156}' .
+'\x{9157}\x{9158}\x{915A}\x{915B}\x{915C}\x{915D}\x{915E}\x{915F}\x{9160}' .
+'\x{9161}\x{9162}\x{9163}\x{9164}\x{9165}\x{9166}\x{9167}\x{9168}\x{9169}' .
+'\x{916A}\x{916B}\x{916C}\x{916D}\x{916E}\x{916F}\x{9170}\x{9171}\x{9172}' .
+'\x{9173}\x{9174}\x{9175}\x{9176}\x{9177}\x{9178}\x{9179}\x{917A}\x{917C}' .
+'\x{917D}\x{917E}\x{917F}\x{9180}\x{9181}\x{9182}\x{9183}\x{9184}\x{9185}' .
+'\x{9186}\x{9187}\x{9188}\x{9189}\x{918A}\x{918B}\x{918C}\x{918D}\x{918E}' .
+'\x{918F}\x{9190}\x{9191}\x{9192}\x{9193}\x{9194}\x{9196}\x{9199}\x{919A}' .
+'\x{919B}\x{919C}\x{919D}\x{919E}\x{919F}\x{91A0}\x{91A1}\x{91A2}\x{91A3}' .
+'\x{91A5}\x{91A6}\x{91A7}\x{91A8}\x{91AA}\x{91AB}\x{91AC}\x{91AD}\x{91AE}' .
+'\x{91AF}\x{91B0}\x{91B1}\x{91B2}\x{91B3}\x{91B4}\x{91B5}\x{91B6}\x{91B7}' .
+'\x{91B9}\x{91BA}\x{91BB}\x{91BC}\x{91BD}\x{91BE}\x{91C0}\x{91C1}\x{91C2}' .
+'\x{91C3}\x{91C5}\x{91C6}\x{91C7}\x{91C9}\x{91CA}\x{91CB}\x{91CC}\x{91CD}' .
+'\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D2}\x{91D3}\x{91D4}\x{91D5}\x{91D7}' .
+'\x{91D8}\x{91D9}\x{91DA}\x{91DB}\x{91DC}\x{91DD}\x{91DE}\x{91DF}\x{91E2}' .
+'\x{91E3}\x{91E4}\x{91E5}\x{91E6}\x{91E7}\x{91E8}\x{91E9}\x{91EA}\x{91EB}' .
+'\x{91EC}\x{91ED}\x{91EE}\x{91F0}\x{91F1}\x{91F2}\x{91F3}\x{91F4}\x{91F5}' .
+'\x{91F7}\x{91F8}\x{91F9}\x{91FA}\x{91FB}\x{91FD}\x{91FE}\x{91FF}\x{9200}' .
+'\x{9201}\x{9202}\x{9203}\x{9204}\x{9205}\x{9206}\x{9207}\x{9208}\x{9209}' .
+'\x{920A}\x{920B}\x{920C}\x{920D}\x{920E}\x{920F}\x{9210}\x{9211}\x{9212}' .
+'\x{9214}\x{9215}\x{9216}\x{9217}\x{9218}\x{9219}\x{921A}\x{921B}\x{921C}' .
+'\x{921D}\x{921E}\x{9220}\x{9221}\x{9223}\x{9224}\x{9225}\x{9226}\x{9227}' .
+'\x{9228}\x{9229}\x{922A}\x{922B}\x{922D}\x{922E}\x{922F}\x{9230}\x{9231}' .
+'\x{9232}\x{9233}\x{9234}\x{9235}\x{9236}\x{9237}\x{9238}\x{9239}\x{923A}' .
+'\x{923B}\x{923C}\x{923D}\x{923E}\x{923F}\x{9240}\x{9241}\x{9242}\x{9245}' .
+'\x{9246}\x{9247}\x{9248}\x{9249}\x{924A}\x{924B}\x{924C}\x{924D}\x{924E}' .
+'\x{924F}\x{9250}\x{9251}\x{9252}\x{9253}\x{9254}\x{9255}\x{9256}\x{9257}' .
+'\x{9258}\x{9259}\x{925A}\x{925B}\x{925C}\x{925D}\x{925E}\x{925F}\x{9260}' .
+'\x{9261}\x{9262}\x{9263}\x{9264}\x{9265}\x{9266}\x{9267}\x{9268}\x{926B}' .
+'\x{926C}\x{926D}\x{926E}\x{926F}\x{9270}\x{9272}\x{9273}\x{9274}\x{9275}' .
+'\x{9276}\x{9277}\x{9278}\x{9279}\x{927A}\x{927B}\x{927C}\x{927D}\x{927E}' .
+'\x{927F}\x{9280}\x{9282}\x{9283}\x{9285}\x{9286}\x{9287}\x{9288}\x{9289}' .
+'\x{928A}\x{928B}\x{928C}\x{928D}\x{928E}\x{928F}\x{9290}\x{9291}\x{9292}' .
+'\x{9293}\x{9294}\x{9295}\x{9296}\x{9297}\x{9298}\x{9299}\x{929A}\x{929B}' .
+'\x{929C}\x{929D}\x{929F}\x{92A0}\x{92A1}\x{92A2}\x{92A3}\x{92A4}\x{92A5}' .
+'\x{92A6}\x{92A7}\x{92A8}\x{92A9}\x{92AA}\x{92AB}\x{92AC}\x{92AD}\x{92AE}' .
+'\x{92AF}\x{92B0}\x{92B1}\x{92B2}\x{92B3}\x{92B4}\x{92B5}\x{92B6}\x{92B7}' .
+'\x{92B8}\x{92B9}\x{92BA}\x{92BB}\x{92BC}\x{92BE}\x{92BF}\x{92C0}\x{92C1}' .
+'\x{92C2}\x{92C3}\x{92C4}\x{92C5}\x{92C6}\x{92C7}\x{92C8}\x{92C9}\x{92CA}' .
+'\x{92CB}\x{92CC}\x{92CD}\x{92CE}\x{92CF}\x{92D0}\x{92D1}\x{92D2}\x{92D3}' .
+'\x{92D5}\x{92D6}\x{92D7}\x{92D8}\x{92D9}\x{92DA}\x{92DC}\x{92DD}\x{92DE}' .
+'\x{92DF}\x{92E0}\x{92E1}\x{92E3}\x{92E4}\x{92E5}\x{92E6}\x{92E7}\x{92E8}' .
+'\x{92E9}\x{92EA}\x{92EB}\x{92EC}\x{92ED}\x{92EE}\x{92EF}\x{92F0}\x{92F1}' .
+'\x{92F2}\x{92F3}\x{92F4}\x{92F5}\x{92F6}\x{92F7}\x{92F8}\x{92F9}\x{92FA}' .
+'\x{92FB}\x{92FC}\x{92FD}\x{92FE}\x{92FF}\x{9300}\x{9301}\x{9302}\x{9303}' .
+'\x{9304}\x{9305}\x{9306}\x{9307}\x{9308}\x{9309}\x{930A}\x{930B}\x{930C}' .
+'\x{930D}\x{930E}\x{930F}\x{9310}\x{9311}\x{9312}\x{9313}\x{9314}\x{9315}' .
+'\x{9316}\x{9317}\x{9318}\x{9319}\x{931A}\x{931B}\x{931D}\x{931E}\x{931F}' .
+'\x{9320}\x{9321}\x{9322}\x{9323}\x{9324}\x{9325}\x{9326}\x{9327}\x{9328}' .
+'\x{9329}\x{932A}\x{932B}\x{932D}\x{932E}\x{932F}\x{9332}\x{9333}\x{9334}' .
+'\x{9335}\x{9336}\x{9337}\x{9338}\x{9339}\x{933A}\x{933B}\x{933C}\x{933D}' .
+'\x{933E}\x{933F}\x{9340}\x{9341}\x{9342}\x{9343}\x{9344}\x{9345}\x{9346}' .
+'\x{9347}\x{9348}\x{9349}\x{934A}\x{934B}\x{934C}\x{934D}\x{934E}\x{934F}' .
+'\x{9350}\x{9351}\x{9352}\x{9353}\x{9354}\x{9355}\x{9356}\x{9357}\x{9358}' .
+'\x{9359}\x{935A}\x{935B}\x{935C}\x{935D}\x{935E}\x{935F}\x{9360}\x{9361}' .
+'\x{9363}\x{9364}\x{9365}\x{9366}\x{9367}\x{9369}\x{936A}\x{936C}\x{936D}' .
+'\x{936E}\x{9370}\x{9371}\x{9372}\x{9374}\x{9375}\x{9376}\x{9377}\x{9379}' .
+'\x{937A}\x{937B}\x{937C}\x{937D}\x{937E}\x{9380}\x{9382}\x{9383}\x{9384}' .
+'\x{9385}\x{9386}\x{9387}\x{9388}\x{9389}\x{938A}\x{938C}\x{938D}\x{938E}' .
+'\x{938F}\x{9390}\x{9391}\x{9392}\x{9393}\x{9394}\x{9395}\x{9396}\x{9397}' .
+'\x{9398}\x{9399}\x{939A}\x{939B}\x{939D}\x{939E}\x{939F}\x{93A1}\x{93A2}' .
+'\x{93A3}\x{93A4}\x{93A5}\x{93A6}\x{93A7}\x{93A8}\x{93A9}\x{93AA}\x{93AC}' .
+'\x{93AD}\x{93AE}\x{93AF}\x{93B0}\x{93B1}\x{93B2}\x{93B3}\x{93B4}\x{93B5}' .
+'\x{93B6}\x{93B7}\x{93B8}\x{93B9}\x{93BA}\x{93BC}\x{93BD}\x{93BE}\x{93BF}' .
+'\x{93C0}\x{93C1}\x{93C2}\x{93C3}\x{93C4}\x{93C5}\x{93C6}\x{93C7}\x{93C8}' .
+'\x{93C9}\x{93CA}\x{93CB}\x{93CC}\x{93CD}\x{93CE}\x{93CF}\x{93D0}\x{93D1}' .
+'\x{93D2}\x{93D3}\x{93D4}\x{93D5}\x{93D6}\x{93D7}\x{93D8}\x{93D9}\x{93DA}' .
+'\x{93DB}\x{93DC}\x{93DD}\x{93DE}\x{93DF}\x{93E1}\x{93E2}\x{93E3}\x{93E4}' .
+'\x{93E6}\x{93E7}\x{93E8}\x{93E9}\x{93EA}\x{93EB}\x{93EC}\x{93ED}\x{93EE}' .
+'\x{93EF}\x{93F0}\x{93F1}\x{93F2}\x{93F4}\x{93F5}\x{93F6}\x{93F7}\x{93F8}' .
+'\x{93F9}\x{93FA}\x{93FB}\x{93FC}\x{93FD}\x{93FE}\x{93FF}\x{9400}\x{9401}' .
+'\x{9403}\x{9404}\x{9405}\x{9406}\x{9407}\x{9408}\x{9409}\x{940A}\x{940B}' .
+'\x{940C}\x{940D}\x{940E}\x{940F}\x{9410}\x{9411}\x{9412}\x{9413}\x{9414}' .
+'\x{9415}\x{9416}\x{9418}\x{9419}\x{941B}\x{941D}\x{9420}\x{9422}\x{9423}' .
+'\x{9425}\x{9426}\x{9427}\x{9428}\x{9429}\x{942A}\x{942B}\x{942C}\x{942D}' .
+'\x{942E}\x{942F}\x{9430}\x{9431}\x{9432}\x{9433}\x{9434}\x{9435}\x{9436}' .
+'\x{9437}\x{9438}\x{9439}\x{943A}\x{943B}\x{943C}\x{943D}\x{943E}\x{943F}' .
+'\x{9440}\x{9441}\x{9442}\x{9444}\x{9445}\x{9446}\x{9447}\x{9448}\x{9449}' .
+'\x{944A}\x{944B}\x{944C}\x{944D}\x{944F}\x{9450}\x{9451}\x{9452}\x{9453}' .
+'\x{9454}\x{9455}\x{9456}\x{9457}\x{9458}\x{9459}\x{945B}\x{945C}\x{945D}' .
+'\x{945E}\x{945F}\x{9460}\x{9461}\x{9462}\x{9463}\x{9464}\x{9465}\x{9466}' .
+'\x{9467}\x{9468}\x{9469}\x{946A}\x{946B}\x{946D}\x{946E}\x{946F}\x{9470}' .
+'\x{9471}\x{9472}\x{9473}\x{9474}\x{9475}\x{9476}\x{9477}\x{9478}\x{9479}' .
+'\x{947A}\x{947C}\x{947D}\x{947E}\x{947F}\x{9480}\x{9481}\x{9482}\x{9483}' .
+'\x{9484}\x{9485}\x{9486}\x{9487}\x{9488}\x{9489}\x{948A}\x{948B}\x{948C}' .
+'\x{948D}\x{948E}\x{948F}\x{9490}\x{9491}\x{9492}\x{9493}\x{9494}\x{9495}' .
+'\x{9496}\x{9497}\x{9498}\x{9499}\x{949A}\x{949B}\x{949C}\x{949D}\x{949E}' .
+'\x{949F}\x{94A0}\x{94A1}\x{94A2}\x{94A3}\x{94A4}\x{94A5}\x{94A6}\x{94A7}' .
+'\x{94A8}\x{94A9}\x{94AA}\x{94AB}\x{94AC}\x{94AD}\x{94AE}\x{94AF}\x{94B0}' .
+'\x{94B1}\x{94B2}\x{94B3}\x{94B4}\x{94B5}\x{94B6}\x{94B7}\x{94B8}\x{94B9}' .
+'\x{94BA}\x{94BB}\x{94BC}\x{94BD}\x{94BE}\x{94BF}\x{94C0}\x{94C1}\x{94C2}' .
+'\x{94C3}\x{94C4}\x{94C5}\x{94C6}\x{94C7}\x{94C8}\x{94C9}\x{94CA}\x{94CB}' .
+'\x{94CC}\x{94CD}\x{94CE}\x{94CF}\x{94D0}\x{94D1}\x{94D2}\x{94D3}\x{94D4}' .
+'\x{94D5}\x{94D6}\x{94D7}\x{94D8}\x{94D9}\x{94DA}\x{94DB}\x{94DC}\x{94DD}' .
+'\x{94DE}\x{94DF}\x{94E0}\x{94E1}\x{94E2}\x{94E3}\x{94E4}\x{94E5}\x{94E6}' .
+'\x{94E7}\x{94E8}\x{94E9}\x{94EA}\x{94EB}\x{94EC}\x{94ED}\x{94EE}\x{94EF}' .
+'\x{94F0}\x{94F1}\x{94F2}\x{94F3}\x{94F4}\x{94F5}\x{94F6}\x{94F7}\x{94F8}' .
+'\x{94F9}\x{94FA}\x{94FB}\x{94FC}\x{94FD}\x{94FE}\x{94FF}\x{9500}\x{9501}' .
+'\x{9502}\x{9503}\x{9504}\x{9505}\x{9506}\x{9507}\x{9508}\x{9509}\x{950A}' .
+'\x{950B}\x{950C}\x{950D}\x{950E}\x{950F}\x{9510}\x{9511}\x{9512}\x{9513}' .
+'\x{9514}\x{9515}\x{9516}\x{9517}\x{9518}\x{9519}\x{951A}\x{951B}\x{951C}' .
+'\x{951D}\x{951E}\x{951F}\x{9520}\x{9521}\x{9522}\x{9523}\x{9524}\x{9525}' .
+'\x{9526}\x{9527}\x{9528}\x{9529}\x{952A}\x{952B}\x{952C}\x{952D}\x{952E}' .
+'\x{952F}\x{9530}\x{9531}\x{9532}\x{9533}\x{9534}\x{9535}\x{9536}\x{9537}' .
+'\x{9538}\x{9539}\x{953A}\x{953B}\x{953C}\x{953D}\x{953E}\x{953F}\x{9540}' .
+'\x{9541}\x{9542}\x{9543}\x{9544}\x{9545}\x{9546}\x{9547}\x{9548}\x{9549}' .
+'\x{954A}\x{954B}\x{954C}\x{954D}\x{954E}\x{954F}\x{9550}\x{9551}\x{9552}' .
+'\x{9553}\x{9554}\x{9555}\x{9556}\x{9557}\x{9558}\x{9559}\x{955A}\x{955B}' .
+'\x{955C}\x{955D}\x{955E}\x{955F}\x{9560}\x{9561}\x{9562}\x{9563}\x{9564}' .
+'\x{9565}\x{9566}\x{9567}\x{9568}\x{9569}\x{956A}\x{956B}\x{956C}\x{956D}' .
+'\x{956E}\x{956F}\x{9570}\x{9571}\x{9572}\x{9573}\x{9574}\x{9575}\x{9576}' .
+'\x{9577}\x{957A}\x{957B}\x{957C}\x{957D}\x{957F}\x{9580}\x{9581}\x{9582}' .
+'\x{9583}\x{9584}\x{9586}\x{9587}\x{9588}\x{9589}\x{958A}\x{958B}\x{958C}' .
+'\x{958D}\x{958E}\x{958F}\x{9590}\x{9591}\x{9592}\x{9593}\x{9594}\x{9595}' .
+'\x{9596}\x{9598}\x{9599}\x{959A}\x{959B}\x{959C}\x{959D}\x{959E}\x{959F}' .
+'\x{95A1}\x{95A2}\x{95A3}\x{95A4}\x{95A5}\x{95A6}\x{95A7}\x{95A8}\x{95A9}' .
+'\x{95AA}\x{95AB}\x{95AC}\x{95AD}\x{95AE}\x{95AF}\x{95B0}\x{95B1}\x{95B2}' .
+'\x{95B5}\x{95B6}\x{95B7}\x{95B9}\x{95BA}\x{95BB}\x{95BC}\x{95BD}\x{95BE}' .
+'\x{95BF}\x{95C0}\x{95C2}\x{95C3}\x{95C4}\x{95C5}\x{95C6}\x{95C7}\x{95C8}' .
+'\x{95C9}\x{95CA}\x{95CB}\x{95CC}\x{95CD}\x{95CE}\x{95CF}\x{95D0}\x{95D1}' .
+'\x{95D2}\x{95D3}\x{95D4}\x{95D5}\x{95D6}\x{95D7}\x{95D8}\x{95DA}\x{95DB}' .
+'\x{95DC}\x{95DE}\x{95DF}\x{95E0}\x{95E1}\x{95E2}\x{95E3}\x{95E4}\x{95E5}' .
+'\x{95E6}\x{95E7}\x{95E8}\x{95E9}\x{95EA}\x{95EB}\x{95EC}\x{95ED}\x{95EE}' .
+'\x{95EF}\x{95F0}\x{95F1}\x{95F2}\x{95F3}\x{95F4}\x{95F5}\x{95F6}\x{95F7}' .
+'\x{95F8}\x{95F9}\x{95FA}\x{95FB}\x{95FC}\x{95FD}\x{95FE}\x{95FF}\x{9600}' .
+'\x{9601}\x{9602}\x{9603}\x{9604}\x{9605}\x{9606}\x{9607}\x{9608}\x{9609}' .
+'\x{960A}\x{960B}\x{960C}\x{960D}\x{960E}\x{960F}\x{9610}\x{9611}\x{9612}' .
+'\x{9613}\x{9614}\x{9615}\x{9616}\x{9617}\x{9618}\x{9619}\x{961A}\x{961B}' .
+'\x{961C}\x{961D}\x{961E}\x{961F}\x{9620}\x{9621}\x{9622}\x{9623}\x{9624}' .
+'\x{9627}\x{9628}\x{962A}\x{962B}\x{962C}\x{962D}\x{962E}\x{962F}\x{9630}' .
+'\x{9631}\x{9632}\x{9633}\x{9634}\x{9635}\x{9636}\x{9637}\x{9638}\x{9639}' .
+'\x{963A}\x{963B}\x{963C}\x{963D}\x{963F}\x{9640}\x{9641}\x{9642}\x{9643}' .
+'\x{9644}\x{9645}\x{9646}\x{9647}\x{9648}\x{9649}\x{964A}\x{964B}\x{964C}' .
+'\x{964D}\x{964E}\x{964F}\x{9650}\x{9651}\x{9652}\x{9653}\x{9654}\x{9655}' .
+'\x{9658}\x{9659}\x{965A}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9660}' .
+'\x{9661}\x{9662}\x{9663}\x{9664}\x{9666}\x{9667}\x{9668}\x{9669}\x{966A}' .
+'\x{966B}\x{966C}\x{966D}\x{966E}\x{966F}\x{9670}\x{9671}\x{9672}\x{9673}' .
+'\x{9674}\x{9675}\x{9676}\x{9677}\x{9678}\x{967C}\x{967D}\x{967E}\x{9680}' .
+'\x{9683}\x{9684}\x{9685}\x{9686}\x{9687}\x{9688}\x{9689}\x{968A}\x{968B}' .
+'\x{968D}\x{968E}\x{968F}\x{9690}\x{9691}\x{9692}\x{9693}\x{9694}\x{9695}' .
+'\x{9697}\x{9698}\x{9699}\x{969B}\x{969C}\x{969E}\x{96A0}\x{96A1}\x{96A2}' .
+'\x{96A3}\x{96A4}\x{96A5}\x{96A6}\x{96A7}\x{96A8}\x{96A9}\x{96AA}\x{96AC}' .
+'\x{96AD}\x{96AE}\x{96B0}\x{96B1}\x{96B3}\x{96B4}\x{96B6}\x{96B7}\x{96B8}' .
+'\x{96B9}\x{96BA}\x{96BB}\x{96BC}\x{96BD}\x{96BE}\x{96BF}\x{96C0}\x{96C1}' .
+'\x{96C2}\x{96C3}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C8}\x{96C9}\x{96CA}' .
+'\x{96CB}\x{96CC}\x{96CD}\x{96CE}\x{96CF}\x{96D0}\x{96D1}\x{96D2}\x{96D3}' .
+'\x{96D4}\x{96D5}\x{96D6}\x{96D7}\x{96D8}\x{96D9}\x{96DA}\x{96DB}\x{96DC}' .
+'\x{96DD}\x{96DE}\x{96DF}\x{96E0}\x{96E1}\x{96E2}\x{96E3}\x{96E5}\x{96E8}' .
+'\x{96E9}\x{96EA}\x{96EB}\x{96EC}\x{96ED}\x{96EE}\x{96EF}\x{96F0}\x{96F1}' .
+'\x{96F2}\x{96F3}\x{96F4}\x{96F5}\x{96F6}\x{96F7}\x{96F8}\x{96F9}\x{96FA}' .
+'\x{96FB}\x{96FD}\x{96FE}\x{96FF}\x{9700}\x{9701}\x{9702}\x{9703}\x{9704}' .
+'\x{9705}\x{9706}\x{9707}\x{9708}\x{9709}\x{970A}\x{970B}\x{970C}\x{970D}' .
+'\x{970E}\x{970F}\x{9710}\x{9711}\x{9712}\x{9713}\x{9715}\x{9716}\x{9718}' .
+'\x{9719}\x{971C}\x{971D}\x{971E}\x{971F}\x{9720}\x{9721}\x{9722}\x{9723}' .
+'\x{9724}\x{9725}\x{9726}\x{9727}\x{9728}\x{9729}\x{972A}\x{972B}\x{972C}' .
+'\x{972D}\x{972E}\x{972F}\x{9730}\x{9731}\x{9732}\x{9735}\x{9736}\x{9738}' .
+'\x{9739}\x{973A}\x{973B}\x{973C}\x{973D}\x{973E}\x{973F}\x{9742}\x{9743}' .
+'\x{9744}\x{9745}\x{9746}\x{9747}\x{9748}\x{9749}\x{974A}\x{974B}\x{974C}' .
+'\x{974E}\x{974F}\x{9750}\x{9751}\x{9752}\x{9753}\x{9754}\x{9755}\x{9756}' .
+'\x{9758}\x{9759}\x{975A}\x{975B}\x{975C}\x{975D}\x{975E}\x{975F}\x{9760}' .
+'\x{9761}\x{9762}\x{9765}\x{9766}\x{9767}\x{9768}\x{9769}\x{976A}\x{976B}' .
+'\x{976C}\x{976D}\x{976E}\x{976F}\x{9770}\x{9772}\x{9773}\x{9774}\x{9776}' .
+'\x{9777}\x{9778}\x{9779}\x{977A}\x{977B}\x{977C}\x{977D}\x{977E}\x{977F}' .
+'\x{9780}\x{9781}\x{9782}\x{9783}\x{9784}\x{9785}\x{9786}\x{9788}\x{978A}' .
+'\x{978B}\x{978C}\x{978D}\x{978E}\x{978F}\x{9790}\x{9791}\x{9792}\x{9793}' .
+'\x{9794}\x{9795}\x{9796}\x{9797}\x{9798}\x{9799}\x{979A}\x{979C}\x{979D}' .
+'\x{979E}\x{979F}\x{97A0}\x{97A1}\x{97A2}\x{97A3}\x{97A4}\x{97A5}\x{97A6}' .
+'\x{97A7}\x{97A8}\x{97AA}\x{97AB}\x{97AC}\x{97AD}\x{97AE}\x{97AF}\x{97B2}' .
+'\x{97B3}\x{97B4}\x{97B6}\x{97B7}\x{97B8}\x{97B9}\x{97BA}\x{97BB}\x{97BC}' .
+'\x{97BD}\x{97BF}\x{97C1}\x{97C2}\x{97C3}\x{97C4}\x{97C5}\x{97C6}\x{97C7}' .
+'\x{97C8}\x{97C9}\x{97CA}\x{97CB}\x{97CC}\x{97CD}\x{97CE}\x{97CF}\x{97D0}' .
+'\x{97D1}\x{97D3}\x{97D4}\x{97D5}\x{97D6}\x{97D7}\x{97D8}\x{97D9}\x{97DA}' .
+'\x{97DB}\x{97DC}\x{97DD}\x{97DE}\x{97DF}\x{97E0}\x{97E1}\x{97E2}\x{97E3}' .
+'\x{97E4}\x{97E5}\x{97E6}\x{97E7}\x{97E8}\x{97E9}\x{97EA}\x{97EB}\x{97EC}' .
+'\x{97ED}\x{97EE}\x{97EF}\x{97F0}\x{97F1}\x{97F2}\x{97F3}\x{97F4}\x{97F5}' .
+'\x{97F6}\x{97F7}\x{97F8}\x{97F9}\x{97FA}\x{97FB}\x{97FD}\x{97FE}\x{97FF}' .
+'\x{9800}\x{9801}\x{9802}\x{9803}\x{9804}\x{9805}\x{9806}\x{9807}\x{9808}' .
+'\x{9809}\x{980A}\x{980B}\x{980C}\x{980D}\x{980E}\x{980F}\x{9810}\x{9811}' .
+'\x{9812}\x{9813}\x{9814}\x{9815}\x{9816}\x{9817}\x{9818}\x{9819}\x{981A}' .
+'\x{981B}\x{981C}\x{981D}\x{981E}\x{9820}\x{9821}\x{9822}\x{9823}\x{9824}' .
+'\x{9826}\x{9827}\x{9828}\x{9829}\x{982B}\x{982D}\x{982E}\x{982F}\x{9830}' .
+'\x{9831}\x{9832}\x{9834}\x{9835}\x{9836}\x{9837}\x{9838}\x{9839}\x{983B}' .
+'\x{983C}\x{983D}\x{983F}\x{9840}\x{9841}\x{9843}\x{9844}\x{9845}\x{9846}' .
+'\x{9848}\x{9849}\x{984A}\x{984C}\x{984D}\x{984E}\x{984F}\x{9850}\x{9851}' .
+'\x{9852}\x{9853}\x{9854}\x{9855}\x{9857}\x{9858}\x{9859}\x{985A}\x{985B}' .
+'\x{985C}\x{985D}\x{985E}\x{985F}\x{9860}\x{9861}\x{9862}\x{9863}\x{9864}' .
+'\x{9865}\x{9867}\x{9869}\x{986A}\x{986B}\x{986C}\x{986D}\x{986E}\x{986F}' .
+'\x{9870}\x{9871}\x{9872}\x{9873}\x{9874}\x{9875}\x{9876}\x{9877}\x{9878}' .
+'\x{9879}\x{987A}\x{987B}\x{987C}\x{987D}\x{987E}\x{987F}\x{9880}\x{9881}' .
+'\x{9882}\x{9883}\x{9884}\x{9885}\x{9886}\x{9887}\x{9888}\x{9889}\x{988A}' .
+'\x{988B}\x{988C}\x{988D}\x{988E}\x{988F}\x{9890}\x{9891}\x{9892}\x{9893}' .
+'\x{9894}\x{9895}\x{9896}\x{9897}\x{9898}\x{9899}\x{989A}\x{989B}\x{989C}' .
+'\x{989D}\x{989E}\x{989F}\x{98A0}\x{98A1}\x{98A2}\x{98A3}\x{98A4}\x{98A5}' .
+'\x{98A6}\x{98A7}\x{98A8}\x{98A9}\x{98AA}\x{98AB}\x{98AC}\x{98AD}\x{98AE}' .
+'\x{98AF}\x{98B0}\x{98B1}\x{98B2}\x{98B3}\x{98B4}\x{98B5}\x{98B6}\x{98B8}' .
+'\x{98B9}\x{98BA}\x{98BB}\x{98BC}\x{98BD}\x{98BE}\x{98BF}\x{98C0}\x{98C1}' .
+'\x{98C2}\x{98C3}\x{98C4}\x{98C5}\x{98C6}\x{98C8}\x{98C9}\x{98CB}\x{98CC}' .
+'\x{98CD}\x{98CE}\x{98CF}\x{98D0}\x{98D1}\x{98D2}\x{98D3}\x{98D4}\x{98D5}' .
+'\x{98D6}\x{98D7}\x{98D8}\x{98D9}\x{98DA}\x{98DB}\x{98DC}\x{98DD}\x{98DE}' .
+'\x{98DF}\x{98E0}\x{98E2}\x{98E3}\x{98E5}\x{98E6}\x{98E7}\x{98E8}\x{98E9}' .
+'\x{98EA}\x{98EB}\x{98ED}\x{98EF}\x{98F0}\x{98F2}\x{98F3}\x{98F4}\x{98F5}' .
+'\x{98F6}\x{98F7}\x{98F9}\x{98FA}\x{98FC}\x{98FD}\x{98FE}\x{98FF}\x{9900}' .
+'\x{9901}\x{9902}\x{9903}\x{9904}\x{9905}\x{9906}\x{9907}\x{9908}\x{9909}' .
+'\x{990A}\x{990B}\x{990C}\x{990D}\x{990E}\x{990F}\x{9910}\x{9911}\x{9912}' .
+'\x{9913}\x{9914}\x{9915}\x{9916}\x{9917}\x{9918}\x{991A}\x{991B}\x{991C}' .
+'\x{991D}\x{991E}\x{991F}\x{9920}\x{9921}\x{9922}\x{9923}\x{9924}\x{9925}' .
+'\x{9926}\x{9927}\x{9928}\x{9929}\x{992A}\x{992B}\x{992C}\x{992D}\x{992E}' .
+'\x{992F}\x{9930}\x{9931}\x{9932}\x{9933}\x{9934}\x{9935}\x{9936}\x{9937}' .
+'\x{9938}\x{9939}\x{993A}\x{993C}\x{993D}\x{993E}\x{993F}\x{9940}\x{9941}' .
+'\x{9942}\x{9943}\x{9945}\x{9946}\x{9947}\x{9948}\x{9949}\x{994A}\x{994B}' .
+'\x{994C}\x{994E}\x{994F}\x{9950}\x{9951}\x{9952}\x{9953}\x{9954}\x{9955}' .
+'\x{9956}\x{9957}\x{9958}\x{9959}\x{995B}\x{995C}\x{995E}\x{995F}\x{9960}' .
+'\x{9961}\x{9962}\x{9963}\x{9964}\x{9965}\x{9966}\x{9967}\x{9968}\x{9969}' .
+'\x{996A}\x{996B}\x{996C}\x{996D}\x{996E}\x{996F}\x{9970}\x{9971}\x{9972}' .
+'\x{9973}\x{9974}\x{9975}\x{9976}\x{9977}\x{9978}\x{9979}\x{997A}\x{997B}' .
+'\x{997C}\x{997D}\x{997E}\x{997F}\x{9980}\x{9981}\x{9982}\x{9983}\x{9984}' .
+'\x{9985}\x{9986}\x{9987}\x{9988}\x{9989}\x{998A}\x{998B}\x{998C}\x{998D}' .
+'\x{998E}\x{998F}\x{9990}\x{9991}\x{9992}\x{9993}\x{9994}\x{9995}\x{9996}' .
+'\x{9997}\x{9998}\x{9999}\x{999A}\x{999B}\x{999C}\x{999D}\x{999E}\x{999F}' .
+'\x{99A0}\x{99A1}\x{99A2}\x{99A3}\x{99A4}\x{99A5}\x{99A6}\x{99A7}\x{99A8}' .
+'\x{99A9}\x{99AA}\x{99AB}\x{99AC}\x{99AD}\x{99AE}\x{99AF}\x{99B0}\x{99B1}' .
+'\x{99B2}\x{99B3}\x{99B4}\x{99B5}\x{99B6}\x{99B7}\x{99B8}\x{99B9}\x{99BA}' .
+'\x{99BB}\x{99BC}\x{99BD}\x{99BE}\x{99C0}\x{99C1}\x{99C2}\x{99C3}\x{99C4}' .
+'\x{99C6}\x{99C7}\x{99C8}\x{99C9}\x{99CA}\x{99CB}\x{99CC}\x{99CD}\x{99CE}' .
+'\x{99CF}\x{99D0}\x{99D1}\x{99D2}\x{99D3}\x{99D4}\x{99D5}\x{99D6}\x{99D7}' .
+'\x{99D8}\x{99D9}\x{99DA}\x{99DB}\x{99DC}\x{99DD}\x{99DE}\x{99DF}\x{99E1}' .
+'\x{99E2}\x{99E3}\x{99E4}\x{99E5}\x{99E7}\x{99E8}\x{99E9}\x{99EA}\x{99EC}' .
+'\x{99ED}\x{99EE}\x{99EF}\x{99F0}\x{99F1}\x{99F2}\x{99F3}\x{99F4}\x{99F6}' .
+'\x{99F7}\x{99F8}\x{99F9}\x{99FA}\x{99FB}\x{99FC}\x{99FD}\x{99FE}\x{99FF}' .
+'\x{9A00}\x{9A01}\x{9A02}\x{9A03}\x{9A04}\x{9A05}\x{9A06}\x{9A07}\x{9A08}' .
+'\x{9A09}\x{9A0A}\x{9A0B}\x{9A0C}\x{9A0D}\x{9A0E}\x{9A0F}\x{9A11}\x{9A14}' .
+'\x{9A15}\x{9A16}\x{9A19}\x{9A1A}\x{9A1B}\x{9A1C}\x{9A1D}\x{9A1E}\x{9A1F}' .
+'\x{9A20}\x{9A21}\x{9A22}\x{9A23}\x{9A24}\x{9A25}\x{9A26}\x{9A27}\x{9A29}' .
+'\x{9A2A}\x{9A2B}\x{9A2C}\x{9A2D}\x{9A2E}\x{9A2F}\x{9A30}\x{9A31}\x{9A32}' .
+'\x{9A33}\x{9A34}\x{9A35}\x{9A36}\x{9A37}\x{9A38}\x{9A39}\x{9A3A}\x{9A3C}' .
+'\x{9A3D}\x{9A3E}\x{9A3F}\x{9A40}\x{9A41}\x{9A42}\x{9A43}\x{9A44}\x{9A45}' .
+'\x{9A46}\x{9A47}\x{9A48}\x{9A49}\x{9A4A}\x{9A4B}\x{9A4C}\x{9A4D}\x{9A4E}' .
+'\x{9A4F}\x{9A50}\x{9A52}\x{9A53}\x{9A54}\x{9A55}\x{9A56}\x{9A57}\x{9A59}' .
+'\x{9A5A}\x{9A5B}\x{9A5C}\x{9A5E}\x{9A5F}\x{9A60}\x{9A61}\x{9A62}\x{9A64}' .
+'\x{9A65}\x{9A66}\x{9A67}\x{9A68}\x{9A69}\x{9A6A}\x{9A6B}\x{9A6C}\x{9A6D}' .
+'\x{9A6E}\x{9A6F}\x{9A70}\x{9A71}\x{9A72}\x{9A73}\x{9A74}\x{9A75}\x{9A76}' .
+'\x{9A77}\x{9A78}\x{9A79}\x{9A7A}\x{9A7B}\x{9A7C}\x{9A7D}\x{9A7E}\x{9A7F}' .
+'\x{9A80}\x{9A81}\x{9A82}\x{9A83}\x{9A84}\x{9A85}\x{9A86}\x{9A87}\x{9A88}' .
+'\x{9A89}\x{9A8A}\x{9A8B}\x{9A8C}\x{9A8D}\x{9A8E}\x{9A8F}\x{9A90}\x{9A91}' .
+'\x{9A92}\x{9A93}\x{9A94}\x{9A95}\x{9A96}\x{9A97}\x{9A98}\x{9A99}\x{9A9A}' .
+'\x{9A9B}\x{9A9C}\x{9A9D}\x{9A9E}\x{9A9F}\x{9AA0}\x{9AA1}\x{9AA2}\x{9AA3}' .
+'\x{9AA4}\x{9AA5}\x{9AA6}\x{9AA7}\x{9AA8}\x{9AAA}\x{9AAB}\x{9AAC}\x{9AAD}' .
+'\x{9AAE}\x{9AAF}\x{9AB0}\x{9AB1}\x{9AB2}\x{9AB3}\x{9AB4}\x{9AB5}\x{9AB6}' .
+'\x{9AB7}\x{9AB8}\x{9AB9}\x{9ABA}\x{9ABB}\x{9ABC}\x{9ABE}\x{9ABF}\x{9AC0}' .
+'\x{9AC1}\x{9AC2}\x{9AC3}\x{9AC4}\x{9AC5}\x{9AC6}\x{9AC7}\x{9AC9}\x{9ACA}' .
+'\x{9ACB}\x{9ACC}\x{9ACD}\x{9ACE}\x{9ACF}\x{9AD0}\x{9AD1}\x{9AD2}\x{9AD3}' .
+'\x{9AD4}\x{9AD5}\x{9AD6}\x{9AD8}\x{9AD9}\x{9ADA}\x{9ADB}\x{9ADC}\x{9ADD}' .
+'\x{9ADE}\x{9ADF}\x{9AE1}\x{9AE2}\x{9AE3}\x{9AE5}\x{9AE6}\x{9AE7}\x{9AEA}' .
+'\x{9AEB}\x{9AEC}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF2}\x{9AF3}\x{9AF4}' .
+'\x{9AF5}\x{9AF6}\x{9AF7}\x{9AF8}\x{9AF9}\x{9AFA}\x{9AFB}\x{9AFC}\x{9AFD}' .
+'\x{9AFE}\x{9AFF}\x{9B01}\x{9B03}\x{9B04}\x{9B05}\x{9B06}\x{9B07}\x{9B08}' .
+'\x{9B0A}\x{9B0B}\x{9B0C}\x{9B0D}\x{9B0E}\x{9B0F}\x{9B10}\x{9B11}\x{9B12}' .
+'\x{9B13}\x{9B15}\x{9B16}\x{9B17}\x{9B18}\x{9B19}\x{9B1A}\x{9B1C}\x{9B1D}' .
+'\x{9B1E}\x{9B1F}\x{9B20}\x{9B21}\x{9B22}\x{9B23}\x{9B24}\x{9B25}\x{9B26}' .
+'\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2B}\x{9B2C}\x{9B2D}\x{9B2E}\x{9B2F}' .
+'\x{9B30}\x{9B31}\x{9B32}\x{9B33}\x{9B35}\x{9B36}\x{9B37}\x{9B38}\x{9B39}' .
+'\x{9B3A}\x{9B3B}\x{9B3C}\x{9B3E}\x{9B3F}\x{9B41}\x{9B42}\x{9B43}\x{9B44}' .
+'\x{9B45}\x{9B46}\x{9B47}\x{9B48}\x{9B49}\x{9B4A}\x{9B4B}\x{9B4C}\x{9B4D}' .
+'\x{9B4E}\x{9B4F}\x{9B51}\x{9B52}\x{9B53}\x{9B54}\x{9B55}\x{9B56}\x{9B58}' .
+'\x{9B59}\x{9B5A}\x{9B5B}\x{9B5C}\x{9B5D}\x{9B5E}\x{9B5F}\x{9B60}\x{9B61}' .
+'\x{9B63}\x{9B64}\x{9B65}\x{9B66}\x{9B67}\x{9B68}\x{9B69}\x{9B6A}\x{9B6B}' .
+'\x{9B6C}\x{9B6D}\x{9B6E}\x{9B6F}\x{9B70}\x{9B71}\x{9B73}\x{9B74}\x{9B75}' .
+'\x{9B76}\x{9B77}\x{9B78}\x{9B79}\x{9B7A}\x{9B7B}\x{9B7C}\x{9B7D}\x{9B7E}' .
+'\x{9B7F}\x{9B80}\x{9B81}\x{9B82}\x{9B83}\x{9B84}\x{9B85}\x{9B86}\x{9B87}' .
+'\x{9B88}\x{9B8A}\x{9B8B}\x{9B8D}\x{9B8E}\x{9B8F}\x{9B90}\x{9B91}\x{9B92}' .
+'\x{9B93}\x{9B94}\x{9B95}\x{9B96}\x{9B97}\x{9B98}\x{9B9A}\x{9B9B}\x{9B9C}' .
+'\x{9B9D}\x{9B9E}\x{9B9F}\x{9BA0}\x{9BA1}\x{9BA2}\x{9BA3}\x{9BA4}\x{9BA5}' .
+'\x{9BA6}\x{9BA7}\x{9BA8}\x{9BA9}\x{9BAA}\x{9BAB}\x{9BAC}\x{9BAD}\x{9BAE}' .
+'\x{9BAF}\x{9BB0}\x{9BB1}\x{9BB2}\x{9BB3}\x{9BB4}\x{9BB5}\x{9BB6}\x{9BB7}' .
+'\x{9BB8}\x{9BB9}\x{9BBA}\x{9BBB}\x{9BBC}\x{9BBD}\x{9BBE}\x{9BBF}\x{9BC0}' .
+'\x{9BC1}\x{9BC3}\x{9BC4}\x{9BC5}\x{9BC6}\x{9BC7}\x{9BC8}\x{9BC9}\x{9BCA}' .
+'\x{9BCB}\x{9BCC}\x{9BCD}\x{9BCE}\x{9BCF}\x{9BD0}\x{9BD1}\x{9BD2}\x{9BD3}' .
+'\x{9BD4}\x{9BD5}\x{9BD6}\x{9BD7}\x{9BD8}\x{9BD9}\x{9BDA}\x{9BDB}\x{9BDC}' .
+'\x{9BDD}\x{9BDE}\x{9BDF}\x{9BE0}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}\x{9BE5}' .
+'\x{9BE6}\x{9BE7}\x{9BE8}\x{9BE9}\x{9BEA}\x{9BEB}\x{9BEC}\x{9BED}\x{9BEE}' .
+'\x{9BEF}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF3}\x{9BF4}\x{9BF5}\x{9BF7}\x{9BF8}' .
+'\x{9BF9}\x{9BFA}\x{9BFB}\x{9BFC}\x{9BFD}\x{9BFE}\x{9BFF}\x{9C02}\x{9C05}' .
+'\x{9C06}\x{9C07}\x{9C08}\x{9C09}\x{9C0A}\x{9C0B}\x{9C0C}\x{9C0D}\x{9C0E}' .
+'\x{9C0F}\x{9C10}\x{9C11}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C16}\x{9C17}' .
+'\x{9C18}\x{9C19}\x{9C1A}\x{9C1B}\x{9C1C}\x{9C1D}\x{9C1E}\x{9C1F}\x{9C20}' .
+'\x{9C21}\x{9C22}\x{9C23}\x{9C24}\x{9C25}\x{9C26}\x{9C27}\x{9C28}\x{9C29}' .
+'\x{9C2A}\x{9C2B}\x{9C2C}\x{9C2D}\x{9C2F}\x{9C30}\x{9C31}\x{9C32}\x{9C33}' .
+'\x{9C34}\x{9C35}\x{9C36}\x{9C37}\x{9C38}\x{9C39}\x{9C3A}\x{9C3B}\x{9C3C}' .
+'\x{9C3D}\x{9C3E}\x{9C3F}\x{9C40}\x{9C41}\x{9C43}\x{9C44}\x{9C45}\x{9C46}' .
+'\x{9C47}\x{9C48}\x{9C49}\x{9C4A}\x{9C4B}\x{9C4C}\x{9C4D}\x{9C4E}\x{9C50}' .
+'\x{9C52}\x{9C53}\x{9C54}\x{9C55}\x{9C56}\x{9C57}\x{9C58}\x{9C59}\x{9C5A}' .
+'\x{9C5B}\x{9C5C}\x{9C5D}\x{9C5E}\x{9C5F}\x{9C60}\x{9C62}\x{9C63}\x{9C65}' .
+'\x{9C66}\x{9C67}\x{9C68}\x{9C69}\x{9C6A}\x{9C6B}\x{9C6C}\x{9C6D}\x{9C6E}' .
+'\x{9C6F}\x{9C70}\x{9C71}\x{9C72}\x{9C73}\x{9C74}\x{9C75}\x{9C77}\x{9C78}' .
+'\x{9C79}\x{9C7A}\x{9C7C}\x{9C7D}\x{9C7E}\x{9C7F}\x{9C80}\x{9C81}\x{9C82}' .
+'\x{9C83}\x{9C84}\x{9C85}\x{9C86}\x{9C87}\x{9C88}\x{9C89}\x{9C8A}\x{9C8B}' .
+'\x{9C8C}\x{9C8D}\x{9C8E}\x{9C8F}\x{9C90}\x{9C91}\x{9C92}\x{9C93}\x{9C94}' .
+'\x{9C95}\x{9C96}\x{9C97}\x{9C98}\x{9C99}\x{9C9A}\x{9C9B}\x{9C9C}\x{9C9D}' .
+'\x{9C9E}\x{9C9F}\x{9CA0}\x{9CA1}\x{9CA2}\x{9CA3}\x{9CA4}\x{9CA5}\x{9CA6}' .
+'\x{9CA7}\x{9CA8}\x{9CA9}\x{9CAA}\x{9CAB}\x{9CAC}\x{9CAD}\x{9CAE}\x{9CAF}' .
+'\x{9CB0}\x{9CB1}\x{9CB2}\x{9CB3}\x{9CB4}\x{9CB5}\x{9CB6}\x{9CB7}\x{9CB8}' .
+'\x{9CB9}\x{9CBA}\x{9CBB}\x{9CBC}\x{9CBD}\x{9CBE}\x{9CBF}\x{9CC0}\x{9CC1}' .
+'\x{9CC2}\x{9CC3}\x{9CC4}\x{9CC5}\x{9CC6}\x{9CC7}\x{9CC8}\x{9CC9}\x{9CCA}' .
+'\x{9CCB}\x{9CCC}\x{9CCD}\x{9CCE}\x{9CCF}\x{9CD0}\x{9CD1}\x{9CD2}\x{9CD3}' .
+'\x{9CD4}\x{9CD5}\x{9CD6}\x{9CD7}\x{9CD8}\x{9CD9}\x{9CDA}\x{9CDB}\x{9CDC}' .
+'\x{9CDD}\x{9CDE}\x{9CDF}\x{9CE0}\x{9CE1}\x{9CE2}\x{9CE3}\x{9CE4}\x{9CE5}' .
+'\x{9CE6}\x{9CE7}\x{9CE8}\x{9CE9}\x{9CEA}\x{9CEB}\x{9CEC}\x{9CED}\x{9CEE}' .
+'\x{9CEF}\x{9CF0}\x{9CF1}\x{9CF2}\x{9CF3}\x{9CF4}\x{9CF5}\x{9CF6}\x{9CF7}' .
+'\x{9CF8}\x{9CF9}\x{9CFA}\x{9CFB}\x{9CFC}\x{9CFD}\x{9CFE}\x{9CFF}\x{9D00}' .
+'\x{9D01}\x{9D02}\x{9D03}\x{9D04}\x{9D05}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' .
+'\x{9D0A}\x{9D0B}\x{9D0F}\x{9D10}\x{9D12}\x{9D13}\x{9D14}\x{9D15}\x{9D16}' .
+'\x{9D17}\x{9D18}\x{9D19}\x{9D1A}\x{9D1B}\x{9D1C}\x{9D1D}\x{9D1E}\x{9D1F}' .
+'\x{9D20}\x{9D21}\x{9D22}\x{9D23}\x{9D24}\x{9D25}\x{9D26}\x{9D28}\x{9D29}' .
+'\x{9D2B}\x{9D2D}\x{9D2E}\x{9D2F}\x{9D30}\x{9D31}\x{9D32}\x{9D33}\x{9D34}' .
+'\x{9D36}\x{9D37}\x{9D38}\x{9D39}\x{9D3A}\x{9D3B}\x{9D3D}\x{9D3E}\x{9D3F}' .
+'\x{9D40}\x{9D41}\x{9D42}\x{9D43}\x{9D45}\x{9D46}\x{9D47}\x{9D48}\x{9D49}' .
+'\x{9D4A}\x{9D4B}\x{9D4C}\x{9D4D}\x{9D4E}\x{9D4F}\x{9D50}\x{9D51}\x{9D52}' .
+'\x{9D53}\x{9D54}\x{9D55}\x{9D56}\x{9D57}\x{9D58}\x{9D59}\x{9D5A}\x{9D5B}' .
+'\x{9D5C}\x{9D5D}\x{9D5E}\x{9D5F}\x{9D60}\x{9D61}\x{9D62}\x{9D63}\x{9D64}' .
+'\x{9D65}\x{9D66}\x{9D67}\x{9D68}\x{9D69}\x{9D6A}\x{9D6B}\x{9D6C}\x{9D6E}' .
+'\x{9D6F}\x{9D70}\x{9D71}\x{9D72}\x{9D73}\x{9D74}\x{9D75}\x{9D76}\x{9D77}' .
+'\x{9D78}\x{9D79}\x{9D7A}\x{9D7B}\x{9D7C}\x{9D7D}\x{9D7E}\x{9D7F}\x{9D80}' .
+'\x{9D81}\x{9D82}\x{9D83}\x{9D84}\x{9D85}\x{9D86}\x{9D87}\x{9D88}\x{9D89}' .
+'\x{9D8A}\x{9D8B}\x{9D8C}\x{9D8D}\x{9D8E}\x{9D90}\x{9D91}\x{9D92}\x{9D93}' .
+'\x{9D94}\x{9D96}\x{9D97}\x{9D98}\x{9D99}\x{9D9A}\x{9D9B}\x{9D9C}\x{9D9D}' .
+'\x{9D9E}\x{9D9F}\x{9DA0}\x{9DA1}\x{9DA2}\x{9DA3}\x{9DA4}\x{9DA5}\x{9DA6}' .
+'\x{9DA7}\x{9DA8}\x{9DA9}\x{9DAA}\x{9DAB}\x{9DAC}\x{9DAD}\x{9DAF}\x{9DB0}' .
+'\x{9DB1}\x{9DB2}\x{9DB3}\x{9DB4}\x{9DB5}\x{9DB6}\x{9DB7}\x{9DB8}\x{9DB9}' .
+'\x{9DBA}\x{9DBB}\x{9DBC}\x{9DBE}\x{9DBF}\x{9DC1}\x{9DC2}\x{9DC3}\x{9DC4}' .
+'\x{9DC5}\x{9DC7}\x{9DC8}\x{9DC9}\x{9DCA}\x{9DCB}\x{9DCC}\x{9DCD}\x{9DCE}' .
+'\x{9DCF}\x{9DD0}\x{9DD1}\x{9DD2}\x{9DD3}\x{9DD4}\x{9DD5}\x{9DD6}\x{9DD7}' .
+'\x{9DD8}\x{9DD9}\x{9DDA}\x{9DDB}\x{9DDC}\x{9DDD}\x{9DDE}\x{9DDF}\x{9DE0}' .
+'\x{9DE1}\x{9DE2}\x{9DE3}\x{9DE4}\x{9DE5}\x{9DE6}\x{9DE7}\x{9DE8}\x{9DE9}' .
+'\x{9DEB}\x{9DEC}\x{9DED}\x{9DEE}\x{9DEF}\x{9DF0}\x{9DF1}\x{9DF2}\x{9DF3}' .
+'\x{9DF4}\x{9DF5}\x{9DF6}\x{9DF7}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFB}\x{9DFD}' .
+'\x{9DFE}\x{9DFF}\x{9E00}\x{9E01}\x{9E02}\x{9E03}\x{9E04}\x{9E05}\x{9E06}' .
+'\x{9E07}\x{9E08}\x{9E09}\x{9E0A}\x{9E0B}\x{9E0C}\x{9E0D}\x{9E0F}\x{9E10}' .
+'\x{9E11}\x{9E12}\x{9E13}\x{9E14}\x{9E15}\x{9E17}\x{9E18}\x{9E19}\x{9E1A}' .
+'\x{9E1B}\x{9E1D}\x{9E1E}\x{9E1F}\x{9E20}\x{9E21}\x{9E22}\x{9E23}\x{9E24}' .
+'\x{9E25}\x{9E26}\x{9E27}\x{9E28}\x{9E29}\x{9E2A}\x{9E2B}\x{9E2C}\x{9E2D}' .
+'\x{9E2E}\x{9E2F}\x{9E30}\x{9E31}\x{9E32}\x{9E33}\x{9E34}\x{9E35}\x{9E36}' .
+'\x{9E37}\x{9E38}\x{9E39}\x{9E3A}\x{9E3B}\x{9E3C}\x{9E3D}\x{9E3E}\x{9E3F}' .
+'\x{9E40}\x{9E41}\x{9E42}\x{9E43}\x{9E44}\x{9E45}\x{9E46}\x{9E47}\x{9E48}' .
+'\x{9E49}\x{9E4A}\x{9E4B}\x{9E4C}\x{9E4D}\x{9E4E}\x{9E4F}\x{9E50}\x{9E51}' .
+'\x{9E52}\x{9E53}\x{9E54}\x{9E55}\x{9E56}\x{9E57}\x{9E58}\x{9E59}\x{9E5A}' .
+'\x{9E5B}\x{9E5C}\x{9E5D}\x{9E5E}\x{9E5F}\x{9E60}\x{9E61}\x{9E62}\x{9E63}' .
+'\x{9E64}\x{9E65}\x{9E66}\x{9E67}\x{9E68}\x{9E69}\x{9E6A}\x{9E6B}\x{9E6C}' .
+'\x{9E6D}\x{9E6E}\x{9E6F}\x{9E70}\x{9E71}\x{9E72}\x{9E73}\x{9E74}\x{9E75}' .
+'\x{9E76}\x{9E77}\x{9E79}\x{9E7A}\x{9E7C}\x{9E7D}\x{9E7E}\x{9E7F}\x{9E80}' .
+'\x{9E81}\x{9E82}\x{9E83}\x{9E84}\x{9E85}\x{9E86}\x{9E87}\x{9E88}\x{9E89}' .
+'\x{9E8A}\x{9E8B}\x{9E8C}\x{9E8D}\x{9E8E}\x{9E91}\x{9E92}\x{9E93}\x{9E94}' .
+'\x{9E96}\x{9E97}\x{9E99}\x{9E9A}\x{9E9B}\x{9E9C}\x{9E9D}\x{9E9F}\x{9EA0}' .
+'\x{9EA1}\x{9EA3}\x{9EA4}\x{9EA5}\x{9EA6}\x{9EA7}\x{9EA8}\x{9EA9}\x{9EAA}' .
+'\x{9EAD}\x{9EAE}\x{9EAF}\x{9EB0}\x{9EB2}\x{9EB3}\x{9EB4}\x{9EB5}\x{9EB6}' .
+'\x{9EB7}\x{9EB8}\x{9EBB}\x{9EBC}\x{9EBD}\x{9EBE}\x{9EBF}\x{9EC0}\x{9EC1}' .
+'\x{9EC2}\x{9EC3}\x{9EC4}\x{9EC5}\x{9EC6}\x{9EC7}\x{9EC8}\x{9EC9}\x{9ECA}' .
+'\x{9ECB}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED1}\x{9ED2}\x{9ED3}' .
+'\x{9ED4}\x{9ED5}\x{9ED6}\x{9ED7}\x{9ED8}\x{9ED9}\x{9EDA}\x{9EDB}\x{9EDC}' .
+'\x{9EDD}\x{9EDE}\x{9EDF}\x{9EE0}\x{9EE1}\x{9EE2}\x{9EE3}\x{9EE4}\x{9EE5}' .
+'\x{9EE6}\x{9EE7}\x{9EE8}\x{9EE9}\x{9EEA}\x{9EEB}\x{9EED}\x{9EEE}\x{9EEF}' .
+'\x{9EF0}\x{9EF2}\x{9EF3}\x{9EF4}\x{9EF5}\x{9EF6}\x{9EF7}\x{9EF8}\x{9EF9}' .
+'\x{9EFA}\x{9EFB}\x{9EFC}\x{9EFD}\x{9EFE}\x{9EFF}\x{9F00}\x{9F01}\x{9F02}' .
+'\x{9F04}\x{9F05}\x{9F06}\x{9F07}\x{9F08}\x{9F09}\x{9F0A}\x{9F0B}\x{9F0C}' .
+'\x{9F0D}\x{9F0E}\x{9F0F}\x{9F10}\x{9F12}\x{9F13}\x{9F15}\x{9F16}\x{9F17}' .
+'\x{9F18}\x{9F19}\x{9F1A}\x{9F1B}\x{9F1C}\x{9F1D}\x{9F1E}\x{9F1F}\x{9F20}' .
+'\x{9F22}\x{9F23}\x{9F24}\x{9F25}\x{9F27}\x{9F28}\x{9F29}\x{9F2A}\x{9F2B}' .
+'\x{9F2C}\x{9F2D}\x{9F2E}\x{9F2F}\x{9F30}\x{9F31}\x{9F32}\x{9F33}\x{9F34}' .
+'\x{9F35}\x{9F36}\x{9F37}\x{9F38}\x{9F39}\x{9F3A}\x{9F3B}\x{9F3C}\x{9F3D}' .
+'\x{9F3E}\x{9F3F}\x{9F40}\x{9F41}\x{9F42}\x{9F43}\x{9F44}\x{9F46}\x{9F47}' .
+'\x{9F48}\x{9F49}\x{9F4A}\x{9F4B}\x{9F4C}\x{9F4D}\x{9F4E}\x{9F4F}\x{9F50}' .
+'\x{9F51}\x{9F52}\x{9F54}\x{9F55}\x{9F56}\x{9F57}\x{9F58}\x{9F59}\x{9F5A}' .
+'\x{9F5B}\x{9F5C}\x{9F5D}\x{9F5E}\x{9F5F}\x{9F60}\x{9F61}\x{9F63}\x{9F64}' .
+'\x{9F65}\x{9F66}\x{9F67}\x{9F68}\x{9F69}\x{9F6A}\x{9F6B}\x{9F6C}\x{9F6E}' .
+'\x{9F6F}\x{9F70}\x{9F71}\x{9F72}\x{9F73}\x{9F74}\x{9F75}\x{9F76}\x{9F77}' .
+'\x{9F78}\x{9F79}\x{9F7A}\x{9F7B}\x{9F7C}\x{9F7D}\x{9F7E}\x{9F7F}\x{9F80}' .
+'\x{9F81}\x{9F82}\x{9F83}\x{9F84}\x{9F85}\x{9F86}\x{9F87}\x{9F88}\x{9F89}' .
+'\x{9F8A}\x{9F8B}\x{9F8C}\x{9F8D}\x{9F8E}\x{9F8F}\x{9F90}\x{9F91}\x{9F92}' .
+'\x{9F93}\x{9F94}\x{9F95}\x{9F96}\x{9F97}\x{9F98}\x{9F99}\x{9F9A}\x{9F9B}' .
+'\x{9F9C}\x{9F9D}\x{9F9E}\x{9F9F}\x{9FA0}\x{9FA2}\x{9FA4}\x{9FA5}]{1,20}$/iu');
diff --git a/library/vendor/Zend/Validate/Hostname/Com.php b/library/vendor/Zend/Validate/Hostname/Com.php
new file mode 100644
index 0000000..873dd52
--- /dev/null
+++ b/library/vendor/Zend/Validate/Hostname/Com.php
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Ressource file for com and net idn validation
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+return array(
+ 1 => '/^[\x{002d}0-9\x{0400}-\x{052f}]{1,63}$/iu',
+ 2 => '/^[\x{002d}0-9\x{0370}-\x{03ff}]{1,63}$/iu',
+ 3 => '/^[\x{002d}0-9a-z\x{ac00}-\x{d7a3}]{1,17}$/iu',
+ 4 => '/^[\x{002d}0-9a-z·à-öø-ÿÄăąćĉċÄÄđēĕėęěÄğġģĥħĩīĭįıĵķĸĺļľłńņňŋÅÅőœŕŗřśÅşšţťŧũūŭůűųŵŷźżž]{1,63}$/iu',
+ 5 => '/^[\x{002d}0-9A-Za-z\x{3400}-\x{3401}\x{3404}-\x{3406}\x{340C}\x{3416}\x{341C}' .
+'\x{3421}\x{3424}\x{3428}-\x{3429}\x{342B}-\x{342E}\x{3430}-\x{3434}\x{3436}' .
+'\x{3438}-\x{343C}\x{343E}\x{3441}-\x{3445}\x{3447}\x{3449}-\x{3451}\x{3453}' .
+'\x{3457}-\x{345F}\x{3463}-\x{3467}\x{346E}-\x{3471}\x{3473}-\x{3477}\x{3479}-\x{348E}\x{3491}-\x{3497}' .
+'\x{3499}-\x{34A1}\x{34A4}-\x{34AD}\x{34AF}-\x{34B0}\x{34B2}-\x{34BF}\x{34C2}-\x{34C5}\x{34C7}-\x{34CC}' .
+'\x{34CE}-\x{34D1}\x{34D3}-\x{34D8}\x{34DA}-\x{34E4}\x{34E7}-\x{34E9}\x{34EC}-\x{34EF}\x{34F1}-\x{34FE}' .
+'\x{3500}-\x{3507}\x{350A}-\x{3513}\x{3515}\x{3517}-\x{351A}\x{351C}-\x{351E}\x{3520}-\x{352A}' .
+'\x{352C}-\x{3552}\x{3554}-\x{355C}\x{355E}-\x{3567}\x{3569}-\x{3573}\x{3575}-\x{357C}\x{3580}-\x{3588}' .
+'\x{358F}-\x{3598}\x{359E}-\x{35AB}\x{35B4}-\x{35CD}\x{35D0}\x{35D3}-\x{35DC}\x{35E2}-\x{35ED}' .
+'\x{35F0}-\x{35F6}\x{35FB}-\x{3602}\x{3605}-\x{360E}\x{3610}-\x{3611}\x{3613}-\x{3616}\x{3619}-\x{362D}' .
+'\x{362F}-\x{3634}\x{3636}-\x{363B}\x{363F}-\x{3645}\x{3647}-\x{364B}\x{364D}-\x{3653}\x{3655}' .
+'\x{3659}-\x{365E}\x{3660}-\x{3665}\x{3667}-\x{367C}\x{367E}\x{3680}-\x{3685}\x{3687}' .
+'\x{3689}-\x{3690}\x{3692}-\x{3698}\x{369A}\x{369C}-\x{36AE}\x{36B0}-\x{36BF}\x{36C1}-\x{36C5}' .
+'\x{36C9}-\x{36CA}\x{36CD}-\x{36DE}\x{36E1}-\x{36E2}\x{36E5}-\x{36FE}\x{3701}-\x{3713}\x{3715}-\x{371E}' .
+'\x{3720}-\x{372C}\x{372E}-\x{3745}\x{3747}-\x{3748}\x{374A}\x{374C}-\x{3759}\x{375B}-\x{3760}' .
+'\x{3762}-\x{3767}\x{3769}-\x{3772}\x{3774}-\x{378C}\x{378F}-\x{379C}\x{379F}\x{37A1}-\x{37AD}' .
+'\x{37AF}-\x{37B7}\x{37B9}-\x{37C1}\x{37C3}-\x{37C5}\x{37C7}-\x{37D4}\x{37D6}-\x{37E0}\x{37E2}' .
+'\x{37E5}-\x{37ED}\x{37EF}-\x{37F6}\x{37F8}-\x{3802}\x{3804}-\x{381D}\x{3820}-\x{3822}\x{3825}-\x{382A}' .
+'\x{382D}-\x{382F}\x{3831}-\x{3832}\x{3834}-\x{384C}\x{384E}-\x{3860}\x{3862}-\x{3863}\x{3865}-\x{386B}' .
+'\x{386D}-\x{3886}\x{3888}-\x{38A1}\x{38A3}\x{38A5}-\x{38AA}\x{38AC}\x{38AE}-\x{38B0}' .
+'\x{38B2}-\x{38B6}\x{38B8}\x{38BA}-\x{38BE}\x{38C0}-\x{38C9}\x{38CB}-\x{38D4}\x{38D8}-\x{38E0}' .
+'\x{38E2}-\x{38E6}\x{38EB}-\x{38ED}\x{38EF}-\x{38F2}\x{38F5}-\x{38F7}\x{38FA}-\x{38FF}\x{3901}-\x{392A}' .
+'\x{392C}\x{392E}-\x{393B}\x{393E}-\x{3956}\x{395A}-\x{3969}\x{396B}-\x{397A}\x{397C}-\x{3987}' .
+'\x{3989}-\x{3998}\x{399A}-\x{39B0}\x{39B2}\x{39B4}-\x{39D0}\x{39D2}-\x{39DA}\x{39DE}-\x{39DF}' .
+'\x{39E1}-\x{39EF}\x{39F1}-\x{3A17}\x{3A19}-\x{3A2A}\x{3A2D}-\x{3A40}\x{3A43}-\x{3A4E}\x{3A50}' .
+'\x{3A52}-\x{3A5E}\x{3A60}-\x{3A6D}\x{3A6F}-\x{3A77}\x{3A79}-\x{3A82}\x{3A84}-\x{3A85}\x{3A87}-\x{3A89}' .
+'\x{3A8B}-\x{3A8F}\x{3A91}-\x{3A93}\x{3A95}-\x{3A96}\x{3A9A}\x{3A9C}-\x{3AA6}\x{3AA8}-\x{3AA9}' .
+'\x{3AAB}-\x{3AB1}\x{3AB4}-\x{3ABC}\x{3ABE}-\x{3AC5}\x{3ACA}-\x{3ACB}\x{3ACD}-\x{3AD5}\x{3AD7}-\x{3AE1}' .
+'\x{3AE4}-\x{3AE7}\x{3AE9}-\x{3AEC}\x{3AEE}-\x{3AFD}\x{3B01}-\x{3B10}\x{3B12}-\x{3B15}\x{3B17}-\x{3B1E}' .
+'\x{3B20}-\x{3B23}\x{3B25}-\x{3B27}\x{3B29}-\x{3B36}\x{3B38}-\x{3B39}\x{3B3B}-\x{3B3C}\x{3B3F}' .
+'\x{3B41}-\x{3B44}\x{3B47}-\x{3B4C}\x{3B4E}\x{3B51}-\x{3B55}\x{3B58}-\x{3B62}\x{3B68}-\x{3B72}' .
+'\x{3B78}-\x{3B88}\x{3B8B}-\x{3B9F}\x{3BA1}\x{3BA3}-\x{3BBA}\x{3BBC}\x{3BBF}-\x{3BD0}' .
+'\x{3BD3}-\x{3BE6}\x{3BEA}-\x{3BFB}\x{3BFE}-\x{3C12}\x{3C14}-\x{3C1B}\x{3C1D}-\x{3C37}\x{3C39}-\x{3C4F}' .
+'\x{3C52}\x{3C54}-\x{3C5C}\x{3C5E}-\x{3C68}\x{3C6A}-\x{3C76}\x{3C78}-\x{3C8F}\x{3C91}-\x{3CA8}' .
+'\x{3CAA}-\x{3CAD}\x{3CAF}-\x{3CBE}\x{3CC0}-\x{3CC8}\x{3CCA}-\x{3CD3}\x{3CD6}-\x{3CE0}\x{3CE4}-\x{3CEE}' .
+'\x{3CF3}-\x{3D0A}\x{3D0E}-\x{3D1E}\x{3D20}-\x{3D21}\x{3D25}-\x{3D38}\x{3D3B}-\x{3D46}\x{3D4A}-\x{3D59}' .
+'\x{3D5D}-\x{3D7B}\x{3D7D}-\x{3D81}\x{3D84}-\x{3D88}\x{3D8C}-\x{3D8F}\x{3D91}-\x{3D98}\x{3D9A}-\x{3D9C}' .
+'\x{3D9E}-\x{3DA1}\x{3DA3}-\x{3DB0}\x{3DB2}-\x{3DB5}\x{3DB9}-\x{3DBC}\x{3DBE}-\x{3DCB}\x{3DCD}-\x{3DDB}' .
+'\x{3DDF}-\x{3DE8}\x{3DEB}-\x{3DF0}\x{3DF3}-\x{3DF9}\x{3DFB}-\x{3DFC}\x{3DFE}-\x{3E05}\x{3E08}-\x{3E33}' .
+'\x{3E35}-\x{3E3E}\x{3E40}-\x{3E47}\x{3E49}-\x{3E67}\x{3E6B}-\x{3E6F}\x{3E71}-\x{3E85}\x{3E87}-\x{3E8C}' .
+'\x{3E8E}-\x{3E98}\x{3E9A}-\x{3EA1}\x{3EA3}-\x{3EAE}\x{3EB0}-\x{3EB5}\x{3EB7}-\x{3EBA}\x{3EBD}' .
+'\x{3EBF}-\x{3EC4}\x{3EC7}-\x{3ECE}\x{3ED1}-\x{3ED7}\x{3ED9}-\x{3EDA}\x{3EDD}-\x{3EE3}\x{3EE7}-\x{3EE8}' .
+'\x{3EEB}-\x{3EF2}\x{3EF5}-\x{3EFF}\x{3F01}-\x{3F02}\x{3F04}-\x{3F07}\x{3F09}-\x{3F44}\x{3F46}-\x{3F4E}' .
+'\x{3F50}-\x{3F53}\x{3F55}-\x{3F72}\x{3F74}-\x{3F75}\x{3F77}-\x{3F7B}\x{3F7D}-\x{3FB0}\x{3FB6}-\x{3FBF}' .
+'\x{3FC1}-\x{3FCF}\x{3FD1}-\x{3FD3}\x{3FD5}-\x{3FDF}\x{3FE1}-\x{400B}\x{400D}-\x{401C}\x{401E}-\x{4024}' .
+'\x{4027}-\x{403F}\x{4041}-\x{4060}\x{4062}-\x{4069}\x{406B}-\x{408A}\x{408C}-\x{40A7}\x{40A9}-\x{40B4}' .
+'\x{40B6}-\x{40C2}\x{40C7}-\x{40CF}\x{40D1}-\x{40DE}\x{40E0}-\x{40E7}\x{40E9}-\x{40EE}\x{40F0}-\x{40FB}' .
+'\x{40FD}-\x{4109}\x{410B}-\x{4115}\x{4118}-\x{411D}\x{411F}-\x{4122}\x{4124}-\x{4133}\x{4136}-\x{4138}' .
+'\x{413A}-\x{4148}\x{414A}-\x{4169}\x{416C}-\x{4185}\x{4188}-\x{418B}\x{418D}-\x{41AD}\x{41AF}-\x{41B3}' .
+'\x{41B5}-\x{41C3}\x{41C5}-\x{41C9}\x{41CB}-\x{41F2}\x{41F5}-\x{41FE}\x{4200}-\x{4227}\x{422A}-\x{4246}' .
+'\x{4248}-\x{4263}\x{4265}-\x{428B}\x{428D}-\x{42A1}\x{42A3}-\x{42C4}\x{42C8}-\x{42DC}\x{42DE}-\x{430A}' .
+'\x{430C}-\x{4335}\x{4337}\x{4342}-\x{435F}\x{4361}-\x{439A}\x{439C}-\x{439D}\x{439F}-\x{43A4}' .
+'\x{43A6}-\x{43EC}\x{43EF}-\x{4405}\x{4407}-\x{4429}\x{442B}-\x{4455}\x{4457}-\x{4468}\x{446A}-\x{446D}' .
+'\x{446F}-\x{4476}\x{4479}-\x{447D}\x{447F}-\x{4486}\x{4488}-\x{4490}\x{4492}-\x{4498}\x{449A}-\x{44AD}' .
+'\x{44B0}-\x{44BD}\x{44C1}-\x{44D3}\x{44D6}-\x{44E7}\x{44EA}\x{44EC}-\x{44FA}\x{44FC}-\x{4541}' .
+'\x{4543}-\x{454F}\x{4551}-\x{4562}\x{4564}-\x{4575}\x{4577}-\x{45AB}\x{45AD}-\x{45BD}\x{45BF}-\x{45D5}' .
+'\x{45D7}-\x{45EC}\x{45EE}-\x{45F2}\x{45F4}-\x{45FA}\x{45FC}-\x{461A}\x{461C}-\x{461D}\x{461F}-\x{4631}' .
+'\x{4633}-\x{4649}\x{464C}\x{464E}-\x{4652}\x{4654}-\x{466A}\x{466C}-\x{4675}\x{4677}-\x{467A}' .
+'\x{467C}-\x{4694}\x{4696}-\x{46A3}\x{46A5}-\x{46AB}\x{46AD}-\x{46D2}\x{46D4}-\x{4723}\x{4729}-\x{4732}' .
+'\x{4734}-\x{4758}\x{475A}\x{475C}-\x{478B}\x{478D}\x{4791}-\x{47B1}\x{47B3}-\x{47F1}' .
+'\x{47F3}-\x{480B}\x{480D}-\x{4815}\x{4817}-\x{4839}\x{483B}-\x{4870}\x{4872}-\x{487A}\x{487C}-\x{487F}' .
+'\x{4883}-\x{488E}\x{4890}-\x{4896}\x{4899}-\x{48A2}\x{48A4}-\x{48B9}\x{48BB}-\x{48C8}\x{48CA}-\x{48D1}' .
+'\x{48D3}-\x{48E5}\x{48E7}-\x{48F2}\x{48F4}-\x{48FF}\x{4901}-\x{4922}\x{4924}-\x{4928}\x{492A}-\x{4931}' .
+'\x{4933}-\x{495B}\x{495D}-\x{4978}\x{497A}\x{497D}\x{4982}-\x{4983}\x{4985}-\x{49A8}' .
+'\x{49AA}-\x{49AF}\x{49B1}-\x{49B7}\x{49B9}-\x{49BD}\x{49C1}-\x{49C7}\x{49C9}-\x{49CE}\x{49D0}-\x{49E8}' .
+'\x{49EA}\x{49EC}\x{49EE}-\x{4A19}\x{4A1B}-\x{4A43}\x{4A45}-\x{4A4D}\x{4A4F}-\x{4A9E}' .
+'\x{4AA0}-\x{4AA9}\x{4AAB}-\x{4B4E}\x{4B50}-\x{4B5B}\x{4B5D}-\x{4B69}\x{4B6B}-\x{4BC2}\x{4BC6}-\x{4BE8}' .
+'\x{4BEA}-\x{4BFA}\x{4BFC}-\x{4C06}\x{4C08}-\x{4C2D}\x{4C2F}-\x{4C32}\x{4C34}-\x{4C35}\x{4C37}-\x{4C69}' .
+'\x{4C6B}-\x{4C73}\x{4C75}-\x{4C86}\x{4C88}-\x{4C97}\x{4C99}-\x{4C9C}\x{4C9F}-\x{4CA3}\x{4CA5}-\x{4CB5}' .
+'\x{4CB7}-\x{4CF8}\x{4CFA}-\x{4D27}\x{4D29}-\x{4DAC}\x{4DAE}-\x{4DB1}\x{4DB3}-\x{4DB5}\x{4E00}-\x{4E54}' .
+'\x{4E56}-\x{4E89}\x{4E8B}-\x{4EEC}\x{4EEE}-\x{4FAC}\x{4FAE}-\x{503C}\x{503E}-\x{51E5}\x{51E7}-\x{5270}' .
+'\x{5272}-\x{56A1}\x{56A3}-\x{5840}\x{5842}-\x{58B5}\x{58B7}-\x{58CB}\x{58CD}-\x{5BC8}\x{5BCA}-\x{5C01}' .
+'\x{5C03}-\x{5C25}\x{5C27}-\x{5D5B}\x{5D5D}-\x{5F08}\x{5F0A}-\x{61F3}\x{61F5}-\x{63BA}\x{63BC}-\x{6441}' .
+'\x{6443}-\x{657C}\x{657E}-\x{663E}\x{6640}-\x{66FC}\x{66FE}-\x{6728}\x{672A}-\x{6766}\x{6768}-\x{67A8}' .
+'\x{67AA}-\x{685B}\x{685D}-\x{685E}\x{6860}-\x{68B9}\x{68BB}-\x{6AC8}\x{6ACA}-\x{6BB0}\x{6BB2}-\x{6C16}' .
+'\x{6C18}-\x{6D9B}\x{6D9D}-\x{6E12}\x{6E14}-\x{6E8B}\x{6E8D}-\x{704D}\x{704F}-\x{7113}\x{7115}-\x{713B}' .
+'\x{713D}-\x{7154}\x{7156}-\x{729F}\x{72A1}-\x{731E}\x{7320}-\x{7362}\x{7364}-\x{7533}\x{7535}-\x{7551}' .
+'\x{7553}-\x{7572}\x{7574}-\x{75E8}\x{75EA}-\x{7679}\x{767B}-\x{783E}\x{7840}-\x{7A62}\x{7A64}-\x{7AC2}' .
+'\x{7AC4}-\x{7B06}\x{7B08}-\x{7B79}\x{7B7B}-\x{7BCE}\x{7BD0}-\x{7D99}\x{7D9B}-\x{7E49}\x{7E4C}-\x{8132}' .
+'\x{8134}\x{8136}-\x{81D2}\x{81D4}-\x{8216}\x{8218}-\x{822D}\x{822F}-\x{83B4}\x{83B6}-\x{841F}' .
+'\x{8421}-\x{86CC}\x{86CE}-\x{874A}\x{874C}-\x{877E}\x{8780}-\x{8A32}\x{8A34}-\x{8B71}\x{8B73}-\x{8B8E}' .
+'\x{8B90}-\x{8DE4}\x{8DE6}-\x{8E9A}\x{8E9C}-\x{8EE1}\x{8EE4}-\x{8F0B}\x{8F0D}-\x{8FB9}\x{8FBB}-\x{9038}' .
+'\x{903A}-\x{9196}\x{9198}-\x{91A3}\x{91A5}-\x{91B7}\x{91B9}-\x{91C7}\x{91C9}-\x{91E0}\x{91E2}-\x{91FB}' .
+'\x{91FD}-\x{922B}\x{922D}-\x{9270}\x{9272}-\x{9420}\x{9422}-\x{9664}\x{9666}-\x{9679}\x{967B}-\x{9770}' .
+'\x{9772}-\x{982B}\x{982D}-\x{98ED}\x{98EF}-\x{99C4}\x{99C6}-\x{9A11}\x{9A14}-\x{9A27}\x{9A29}-\x{9D0D}' .
+'\x{9D0F}-\x{9D2B}\x{9D2D}-\x{9D8E}\x{9D90}-\x{9DC5}\x{9DC7}-\x{9E77}\x{9E79}-\x{9EB8}\x{9EBB}-\x{9F20}' .
+'\x{9F22}-\x{9F61}\x{9F63}-\x{9FA5}\x{FA28}]{1,20}$/iu',
+ 6 => '/^[\x{002d}0-9A-Za-z]{1,63}$/iu',
+ 7 => '/^[\x{00A1}-\x{00FF}]{1,63}$/iu',
+ 8 => '/^[\x{0100}-\x{017f}]{1,63}$/iu',
+ 9 => '/^[\x{0180}-\x{024f}]{1,63}$/iu',
+ 10 => '/^[\x{0250}-\x{02af}]{1,63}$/iu',
+ 11 => '/^[\x{02b0}-\x{02ff}]{1,63}$/iu',
+ 12 => '/^[\x{0300}-\x{036f}]{1,63}$/iu',
+ 13 => '/^[\x{0370}-\x{03ff}]{1,63}$/iu',
+ 14 => '/^[\x{0400}-\x{04ff}]{1,63}$/iu',
+ 15 => '/^[\x{0500}-\x{052f}]{1,63}$/iu',
+ 16 => '/^[\x{0530}-\x{058F}]{1,63}$/iu',
+ 17 => '/^[\x{0590}-\x{05FF}]{1,63}$/iu',
+ 18 => '/^[\x{0600}-\x{06FF}]{1,63}$/iu',
+ 19 => '/^[\x{0700}-\x{074F}]{1,63}$/iu',
+ 20 => '/^[\x{0780}-\x{07BF}]{1,63}$/iu',
+ 21 => '/^[\x{0900}-\x{097F}]{1,63}$/iu',
+ 22 => '/^[\x{0980}-\x{09FF}]{1,63}$/iu',
+ 23 => '/^[\x{0A00}-\x{0A7F}]{1,63}$/iu',
+ 24 => '/^[\x{0A80}-\x{0AFF}]{1,63}$/iu',
+ 25 => '/^[\x{0B00}-\x{0B7F}]{1,63}$/iu',
+ 26 => '/^[\x{0B80}-\x{0BFF}]{1,63}$/iu',
+ 27 => '/^[\x{0C00}-\x{0C7F}]{1,63}$/iu',
+ 28 => '/^[\x{0C80}-\x{0CFF}]{1,63}$/iu',
+ 29 => '/^[\x{0D00}-\x{0D7F}]{1,63}$/iu',
+ 30 => '/^[\x{0D80}-\x{0DFF}]{1,63}$/iu',
+ 31 => '/^[\x{0E00}-\x{0E7F}]{1,63}$/iu',
+ 32 => '/^[\x{0E80}-\x{0EFF}]{1,63}$/iu',
+ 33 => '/^[\x{0F00}-\x{0FFF}]{1,63}$/iu',
+ 34 => '/^[\x{1000}-\x{109F}]{1,63}$/iu',
+ 35 => '/^[\x{10A0}-\x{10FF}]{1,63}$/iu',
+ 36 => '/^[\x{1100}-\x{11FF}]{1,63}$/iu',
+ 37 => '/^[\x{1200}-\x{137F}]{1,63}$/iu',
+ 38 => '/^[\x{13A0}-\x{13FF}]{1,63}$/iu',
+ 39 => '/^[\x{1400}-\x{167F}]{1,63}$/iu',
+ 40 => '/^[\x{1680}-\x{169F}]{1,63}$/iu',
+ 41 => '/^[\x{16A0}-\x{16FF}]{1,63}$/iu',
+ 42 => '/^[\x{1700}-\x{171F}]{1,63}$/iu',
+ 43 => '/^[\x{1720}-\x{173F}]{1,63}$/iu',
+ 44 => '/^[\x{1740}-\x{175F}]{1,63}$/iu',
+ 45 => '/^[\x{1760}-\x{177F}]{1,63}$/iu',
+ 46 => '/^[\x{1780}-\x{17FF}]{1,63}$/iu',
+ 47 => '/^[\x{1800}-\x{18AF}]{1,63}$/iu',
+ 48 => '/^[\x{1E00}-\x{1EFF}]{1,63}$/iu',
+ 49 => '/^[\x{1F00}-\x{1FFF}]{1,63}$/iu',
+ 50 => '/^[\x{2070}-\x{209F}]{1,63}$/iu',
+ 51 => '/^[\x{2100}-\x{214F}]{1,63}$/iu',
+ 52 => '/^[\x{2150}-\x{218F}]{1,63}$/iu',
+ 53 => '/^[\x{2460}-\x{24FF}]{1,63}$/iu',
+ 54 => '/^[\x{2E80}-\x{2EFF}]{1,63}$/iu',
+ 55 => '/^[\x{2F00}-\x{2FDF}]{1,63}$/iu',
+ 56 => '/^[\x{2FF0}-\x{2FFF}]{1,63}$/iu',
+ 57 => '/^[\x{3040}-\x{309F}]{1,63}$/iu',
+ 58 => '/^[\x{30A0}-\x{30FF}]{1,63}$/iu',
+ 59 => '/^[\x{3100}-\x{312F}]{1,63}$/iu',
+ 60 => '/^[\x{3130}-\x{318F}]{1,63}$/iu',
+ 61 => '/^[\x{3190}-\x{319F}]{1,63}$/iu',
+ 62 => '/^[\x{31A0}-\x{31BF}]{1,63}$/iu',
+ 63 => '/^[\x{31F0}-\x{31FF}]{1,63}$/iu',
+ 64 => '/^[\x{3200}-\x{32FF}]{1,63}$/iu',
+ 65 => '/^[\x{3300}-\x{33FF}]{1,63}$/iu',
+ 66 => '/^[\x{3400}-\x{4DBF}]{1,63}$/iu',
+ 67 => '/^[\x{4E00}-\x{9FFF}]{1,63}$/iu',
+ 68 => '/^[\x{A000}-\x{A48F}]{1,63}$/iu',
+ 69 => '/^[\x{A490}-\x{A4CF}]{1,63}$/iu',
+ 70 => '/^[\x{AC00}-\x{D7AF}]{1,63}$/iu',
+ 73 => '/^[\x{F900}-\x{FAFF}]{1,63}$/iu',
+ 74 => '/^[\x{FB00}-\x{FB4F}]{1,63}$/iu',
+ 75 => '/^[\x{FB50}-\x{FDFF}]{1,63}$/iu',
+ 76 => '/^[\x{FE20}-\x{FE2F}]{1,63}$/iu',
+ 77 => '/^[\x{FE70}-\x{FEFF}]{1,63}$/iu',
+ 78 => '/^[\x{FF00}-\x{FFEF}]{1,63}$/iu',
+ 79 => '/^[\x{20000}-\x{2A6DF}]{1,63}$/iu',
+ 80 => '/^[\x{2F800}-\x{2FA1F}]{1,63}$/iu'
+
+);
diff --git a/library/vendor/Zend/Validate/Hostname/Jp.php b/library/vendor/Zend/Validate/Hostname/Jp.php
new file mode 100644
index 0000000..caca994
--- /dev/null
+++ b/library/vendor/Zend/Validate/Hostname/Jp.php
@@ -0,0 +1,739 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Ressource file for japanese idn validation
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+return array(
+ 1 => '/^[\x{002d}0-9a-z\x{3005}-\x{3007}\x{3041}-\x{3093}\x{309D}\x{309E}' .
+'\x{30A1}-\x{30F6}\x{30FC}' .
+'\x{30FD}\x{30FE}\x{4E00}\x{4E01}\x{4E03}\x{4E07}\x{4E08}\x{4E09}\x{4E0A}' .
+'\x{4E0B}\x{4E0D}\x{4E0E}\x{4E10}\x{4E11}\x{4E14}\x{4E15}\x{4E16}\x{4E17}' .
+'\x{4E18}\x{4E19}\x{4E1E}\x{4E21}\x{4E26}\x{4E2A}\x{4E2D}\x{4E31}\x{4E32}' .
+'\x{4E36}\x{4E38}\x{4E39}\x{4E3B}\x{4E3C}\x{4E3F}\x{4E42}\x{4E43}\x{4E45}' .
+'\x{4E4B}\x{4E4D}\x{4E4E}\x{4E4F}\x{4E55}\x{4E56}\x{4E57}\x{4E58}\x{4E59}' .
+'\x{4E5D}\x{4E5E}\x{4E5F}\x{4E62}\x{4E71}\x{4E73}\x{4E7E}\x{4E80}\x{4E82}' .
+'\x{4E85}\x{4E86}\x{4E88}\x{4E89}\x{4E8A}\x{4E8B}\x{4E8C}\x{4E8E}\x{4E91}' .
+'\x{4E92}\x{4E94}\x{4E95}\x{4E98}\x{4E99}\x{4E9B}\x{4E9C}\x{4E9E}\x{4E9F}' .
+'\x{4EA0}\x{4EA1}\x{4EA2}\x{4EA4}\x{4EA5}\x{4EA6}\x{4EA8}\x{4EAB}\x{4EAC}' .
+'\x{4EAD}\x{4EAE}\x{4EB0}\x{4EB3}\x{4EB6}\x{4EBA}\x{4EC0}\x{4EC1}\x{4EC2}' .
+'\x{4EC4}\x{4EC6}\x{4EC7}\x{4ECA}\x{4ECB}\x{4ECD}\x{4ECE}\x{4ECF}\x{4ED4}' .
+'\x{4ED5}\x{4ED6}\x{4ED7}\x{4ED8}\x{4ED9}\x{4EDD}\x{4EDE}\x{4EDF}\x{4EE3}' .
+'\x{4EE4}\x{4EE5}\x{4EED}\x{4EEE}\x{4EF0}\x{4EF2}\x{4EF6}\x{4EF7}\x{4EFB}' .
+'\x{4F01}\x{4F09}\x{4F0A}\x{4F0D}\x{4F0E}\x{4F0F}\x{4F10}\x{4F11}\x{4F1A}' .
+'\x{4F1C}\x{4F1D}\x{4F2F}\x{4F30}\x{4F34}\x{4F36}\x{4F38}\x{4F3A}\x{4F3C}' .
+'\x{4F3D}\x{4F43}\x{4F46}\x{4F47}\x{4F4D}\x{4F4E}\x{4F4F}\x{4F50}\x{4F51}' .
+'\x{4F53}\x{4F55}\x{4F57}\x{4F59}\x{4F5A}\x{4F5B}\x{4F5C}\x{4F5D}\x{4F5E}' .
+'\x{4F69}\x{4F6F}\x{4F70}\x{4F73}\x{4F75}\x{4F76}\x{4F7B}\x{4F7C}\x{4F7F}' .
+'\x{4F83}\x{4F86}\x{4F88}\x{4F8B}\x{4F8D}\x{4F8F}\x{4F91}\x{4F96}\x{4F98}' .
+'\x{4F9B}\x{4F9D}\x{4FA0}\x{4FA1}\x{4FAB}\x{4FAD}\x{4FAE}\x{4FAF}\x{4FB5}' .
+'\x{4FB6}\x{4FBF}\x{4FC2}\x{4FC3}\x{4FC4}\x{4FCA}\x{4FCE}\x{4FD0}\x{4FD1}' .
+'\x{4FD4}\x{4FD7}\x{4FD8}\x{4FDA}\x{4FDB}\x{4FDD}\x{4FDF}\x{4FE1}\x{4FE3}' .
+'\x{4FE4}\x{4FE5}\x{4FEE}\x{4FEF}\x{4FF3}\x{4FF5}\x{4FF6}\x{4FF8}\x{4FFA}' .
+'\x{4FFE}\x{5005}\x{5006}\x{5009}\x{500B}\x{500D}\x{500F}\x{5011}\x{5012}' .
+'\x{5014}\x{5016}\x{5019}\x{501A}\x{501F}\x{5021}\x{5023}\x{5024}\x{5025}' .
+'\x{5026}\x{5028}\x{5029}\x{502A}\x{502B}\x{502C}\x{502D}\x{5036}\x{5039}' .
+'\x{5043}\x{5047}\x{5048}\x{5049}\x{504F}\x{5050}\x{5055}\x{5056}\x{505A}' .
+'\x{505C}\x{5065}\x{506C}\x{5072}\x{5074}\x{5075}\x{5076}\x{5078}\x{507D}' .
+'\x{5080}\x{5085}\x{508D}\x{5091}\x{5098}\x{5099}\x{509A}\x{50AC}\x{50AD}' .
+'\x{50B2}\x{50B3}\x{50B4}\x{50B5}\x{50B7}\x{50BE}\x{50C2}\x{50C5}\x{50C9}' .
+'\x{50CA}\x{50CD}\x{50CF}\x{50D1}\x{50D5}\x{50D6}\x{50DA}\x{50DE}\x{50E3}' .
+'\x{50E5}\x{50E7}\x{50ED}\x{50EE}\x{50F5}\x{50F9}\x{50FB}\x{5100}\x{5101}' .
+'\x{5102}\x{5104}\x{5109}\x{5112}\x{5114}\x{5115}\x{5116}\x{5118}\x{511A}' .
+'\x{511F}\x{5121}\x{512A}\x{5132}\x{5137}\x{513A}\x{513B}\x{513C}\x{513F}' .
+'\x{5140}\x{5141}\x{5143}\x{5144}\x{5145}\x{5146}\x{5147}\x{5148}\x{5149}' .
+'\x{514B}\x{514C}\x{514D}\x{514E}\x{5150}\x{5152}\x{5154}\x{515A}\x{515C}' .
+'\x{5162}\x{5165}\x{5168}\x{5169}\x{516A}\x{516B}\x{516C}\x{516D}\x{516E}' .
+'\x{5171}\x{5175}\x{5176}\x{5177}\x{5178}\x{517C}\x{5180}\x{5182}\x{5185}' .
+'\x{5186}\x{5189}\x{518A}\x{518C}\x{518D}\x{518F}\x{5190}\x{5191}\x{5192}' .
+'\x{5193}\x{5195}\x{5196}\x{5197}\x{5199}\x{51A0}\x{51A2}\x{51A4}\x{51A5}' .
+'\x{51A6}\x{51A8}\x{51A9}\x{51AA}\x{51AB}\x{51AC}\x{51B0}\x{51B1}\x{51B2}' .
+'\x{51B3}\x{51B4}\x{51B5}\x{51B6}\x{51B7}\x{51BD}\x{51C4}\x{51C5}\x{51C6}' .
+'\x{51C9}\x{51CB}\x{51CC}\x{51CD}\x{51D6}\x{51DB}\x{51DC}\x{51DD}\x{51E0}' .
+'\x{51E1}\x{51E6}\x{51E7}\x{51E9}\x{51EA}\x{51ED}\x{51F0}\x{51F1}\x{51F5}' .
+'\x{51F6}\x{51F8}\x{51F9}\x{51FA}\x{51FD}\x{51FE}\x{5200}\x{5203}\x{5204}' .
+'\x{5206}\x{5207}\x{5208}\x{520A}\x{520B}\x{520E}\x{5211}\x{5214}\x{5217}' .
+'\x{521D}\x{5224}\x{5225}\x{5227}\x{5229}\x{522A}\x{522E}\x{5230}\x{5233}' .
+'\x{5236}\x{5237}\x{5238}\x{5239}\x{523A}\x{523B}\x{5243}\x{5244}\x{5247}' .
+'\x{524A}\x{524B}\x{524C}\x{524D}\x{524F}\x{5254}\x{5256}\x{525B}\x{525E}' .
+'\x{5263}\x{5264}\x{5265}\x{5269}\x{526A}\x{526F}\x{5270}\x{5271}\x{5272}' .
+'\x{5273}\x{5274}\x{5275}\x{527D}\x{527F}\x{5283}\x{5287}\x{5288}\x{5289}' .
+'\x{528D}\x{5291}\x{5292}\x{5294}\x{529B}\x{529F}\x{52A0}\x{52A3}\x{52A9}' .
+'\x{52AA}\x{52AB}\x{52AC}\x{52AD}\x{52B1}\x{52B4}\x{52B5}\x{52B9}\x{52BC}' .
+'\x{52BE}\x{52C1}\x{52C3}\x{52C5}\x{52C7}\x{52C9}\x{52CD}\x{52D2}\x{52D5}' .
+'\x{52D7}\x{52D8}\x{52D9}\x{52DD}\x{52DE}\x{52DF}\x{52E0}\x{52E2}\x{52E3}' .
+'\x{52E4}\x{52E6}\x{52E7}\x{52F2}\x{52F3}\x{52F5}\x{52F8}\x{52F9}\x{52FA}' .
+'\x{52FE}\x{52FF}\x{5301}\x{5302}\x{5305}\x{5306}\x{5308}\x{530D}\x{530F}' .
+'\x{5310}\x{5315}\x{5316}\x{5317}\x{5319}\x{531A}\x{531D}\x{5320}\x{5321}' .
+'\x{5323}\x{532A}\x{532F}\x{5331}\x{5333}\x{5338}\x{5339}\x{533A}\x{533B}' .
+'\x{533F}\x{5340}\x{5341}\x{5343}\x{5345}\x{5346}\x{5347}\x{5348}\x{5349}' .
+'\x{534A}\x{534D}\x{5351}\x{5352}\x{5353}\x{5354}\x{5357}\x{5358}\x{535A}' .
+'\x{535C}\x{535E}\x{5360}\x{5366}\x{5369}\x{536E}\x{536F}\x{5370}\x{5371}' .
+'\x{5373}\x{5374}\x{5375}\x{5377}\x{5378}\x{537B}\x{537F}\x{5382}\x{5384}' .
+'\x{5396}\x{5398}\x{539A}\x{539F}\x{53A0}\x{53A5}\x{53A6}\x{53A8}\x{53A9}' .
+'\x{53AD}\x{53AE}\x{53B0}\x{53B3}\x{53B6}\x{53BB}\x{53C2}\x{53C3}\x{53C8}' .
+'\x{53C9}\x{53CA}\x{53CB}\x{53CC}\x{53CD}\x{53CE}\x{53D4}\x{53D6}\x{53D7}' .
+'\x{53D9}\x{53DB}\x{53DF}\x{53E1}\x{53E2}\x{53E3}\x{53E4}\x{53E5}\x{53E8}' .
+'\x{53E9}\x{53EA}\x{53EB}\x{53EC}\x{53ED}\x{53EE}\x{53EF}\x{53F0}\x{53F1}' .
+'\x{53F2}\x{53F3}\x{53F6}\x{53F7}\x{53F8}\x{53FA}\x{5401}\x{5403}\x{5404}' .
+'\x{5408}\x{5409}\x{540A}\x{540B}\x{540C}\x{540D}\x{540E}\x{540F}\x{5410}' .
+'\x{5411}\x{541B}\x{541D}\x{541F}\x{5420}\x{5426}\x{5429}\x{542B}\x{542C}' .
+'\x{542D}\x{542E}\x{5436}\x{5438}\x{5439}\x{543B}\x{543C}\x{543D}\x{543E}' .
+'\x{5440}\x{5442}\x{5446}\x{5448}\x{5449}\x{544A}\x{544E}\x{5451}\x{545F}' .
+'\x{5468}\x{546A}\x{5470}\x{5471}\x{5473}\x{5475}\x{5476}\x{5477}\x{547B}' .
+'\x{547C}\x{547D}\x{5480}\x{5484}\x{5486}\x{548B}\x{548C}\x{548E}\x{548F}' .
+'\x{5490}\x{5492}\x{54A2}\x{54A4}\x{54A5}\x{54A8}\x{54AB}\x{54AC}\x{54AF}' .
+'\x{54B2}\x{54B3}\x{54B8}\x{54BC}\x{54BD}\x{54BE}\x{54C0}\x{54C1}\x{54C2}' .
+'\x{54C4}\x{54C7}\x{54C8}\x{54C9}\x{54D8}\x{54E1}\x{54E2}\x{54E5}\x{54E6}' .
+'\x{54E8}\x{54E9}\x{54ED}\x{54EE}\x{54F2}\x{54FA}\x{54FD}\x{5504}\x{5506}' .
+'\x{5507}\x{550F}\x{5510}\x{5514}\x{5516}\x{552E}\x{552F}\x{5531}\x{5533}' .
+'\x{5538}\x{5539}\x{553E}\x{5540}\x{5544}\x{5545}\x{5546}\x{554C}\x{554F}' .
+'\x{5553}\x{5556}\x{5557}\x{555C}\x{555D}\x{5563}\x{557B}\x{557C}\x{557E}' .
+'\x{5580}\x{5583}\x{5584}\x{5587}\x{5589}\x{558A}\x{558B}\x{5598}\x{5599}' .
+'\x{559A}\x{559C}\x{559D}\x{559E}\x{559F}\x{55A7}\x{55A8}\x{55A9}\x{55AA}' .
+'\x{55AB}\x{55AC}\x{55AE}\x{55B0}\x{55B6}\x{55C4}\x{55C5}\x{55C7}\x{55D4}' .
+'\x{55DA}\x{55DC}\x{55DF}\x{55E3}\x{55E4}\x{55F7}\x{55F9}\x{55FD}\x{55FE}' .
+'\x{5606}\x{5609}\x{5614}\x{5616}\x{5617}\x{5618}\x{561B}\x{5629}\x{562F}' .
+'\x{5631}\x{5632}\x{5634}\x{5636}\x{5638}\x{5642}\x{564C}\x{564E}\x{5650}' .
+'\x{565B}\x{5664}\x{5668}\x{566A}\x{566B}\x{566C}\x{5674}\x{5678}\x{567A}' .
+'\x{5680}\x{5686}\x{5687}\x{568A}\x{568F}\x{5694}\x{56A0}\x{56A2}\x{56A5}' .
+'\x{56AE}\x{56B4}\x{56B6}\x{56BC}\x{56C0}\x{56C1}\x{56C2}\x{56C3}\x{56C8}' .
+'\x{56CE}\x{56D1}\x{56D3}\x{56D7}\x{56D8}\x{56DA}\x{56DB}\x{56DE}\x{56E0}' .
+'\x{56E3}\x{56EE}\x{56F0}\x{56F2}\x{56F3}\x{56F9}\x{56FA}\x{56FD}\x{56FF}' .
+'\x{5700}\x{5703}\x{5704}\x{5708}\x{5709}\x{570B}\x{570D}\x{570F}\x{5712}' .
+'\x{5713}\x{5716}\x{5718}\x{571C}\x{571F}\x{5726}\x{5727}\x{5728}\x{572D}' .
+'\x{5730}\x{5737}\x{5738}\x{573B}\x{5740}\x{5742}\x{5747}\x{574A}\x{574E}' .
+'\x{574F}\x{5750}\x{5751}\x{5761}\x{5764}\x{5766}\x{5769}\x{576A}\x{577F}' .
+'\x{5782}\x{5788}\x{5789}\x{578B}\x{5793}\x{57A0}\x{57A2}\x{57A3}\x{57A4}' .
+'\x{57AA}\x{57B0}\x{57B3}\x{57C0}\x{57C3}\x{57C6}\x{57CB}\x{57CE}\x{57D2}' .
+'\x{57D3}\x{57D4}\x{57D6}\x{57DC}\x{57DF}\x{57E0}\x{57E3}\x{57F4}\x{57F7}' .
+'\x{57F9}\x{57FA}\x{57FC}\x{5800}\x{5802}\x{5805}\x{5806}\x{580A}\x{580B}' .
+'\x{5815}\x{5819}\x{581D}\x{5821}\x{5824}\x{582A}\x{582F}\x{5830}\x{5831}' .
+'\x{5834}\x{5835}\x{583A}\x{583D}\x{5840}\x{5841}\x{584A}\x{584B}\x{5851}' .
+'\x{5852}\x{5854}\x{5857}\x{5858}\x{5859}\x{585A}\x{585E}\x{5862}\x{5869}' .
+'\x{586B}\x{5870}\x{5872}\x{5875}\x{5879}\x{587E}\x{5883}\x{5885}\x{5893}' .
+'\x{5897}\x{589C}\x{589F}\x{58A8}\x{58AB}\x{58AE}\x{58B3}\x{58B8}\x{58B9}' .
+'\x{58BA}\x{58BB}\x{58BE}\x{58C1}\x{58C5}\x{58C7}\x{58CA}\x{58CC}\x{58D1}' .
+'\x{58D3}\x{58D5}\x{58D7}\x{58D8}\x{58D9}\x{58DC}\x{58DE}\x{58DF}\x{58E4}' .
+'\x{58E5}\x{58EB}\x{58EC}\x{58EE}\x{58EF}\x{58F0}\x{58F1}\x{58F2}\x{58F7}' .
+'\x{58F9}\x{58FA}\x{58FB}\x{58FC}\x{58FD}\x{5902}\x{5909}\x{590A}\x{590F}' .
+'\x{5910}\x{5915}\x{5916}\x{5918}\x{5919}\x{591A}\x{591B}\x{591C}\x{5922}' .
+'\x{5925}\x{5927}\x{5929}\x{592A}\x{592B}\x{592C}\x{592D}\x{592E}\x{5931}' .
+'\x{5932}\x{5937}\x{5938}\x{593E}\x{5944}\x{5947}\x{5948}\x{5949}\x{594E}' .
+'\x{594F}\x{5950}\x{5951}\x{5954}\x{5955}\x{5957}\x{5958}\x{595A}\x{5960}' .
+'\x{5962}\x{5965}\x{5967}\x{5968}\x{5969}\x{596A}\x{596C}\x{596E}\x{5973}' .
+'\x{5974}\x{5978}\x{597D}\x{5981}\x{5982}\x{5983}\x{5984}\x{598A}\x{598D}' .
+'\x{5993}\x{5996}\x{5999}\x{599B}\x{599D}\x{59A3}\x{59A5}\x{59A8}\x{59AC}' .
+'\x{59B2}\x{59B9}\x{59BB}\x{59BE}\x{59C6}\x{59C9}\x{59CB}\x{59D0}\x{59D1}' .
+'\x{59D3}\x{59D4}\x{59D9}\x{59DA}\x{59DC}\x{59E5}\x{59E6}\x{59E8}\x{59EA}' .
+'\x{59EB}\x{59F6}\x{59FB}\x{59FF}\x{5A01}\x{5A03}\x{5A09}\x{5A11}\x{5A18}' .
+'\x{5A1A}\x{5A1C}\x{5A1F}\x{5A20}\x{5A25}\x{5A29}\x{5A2F}\x{5A35}\x{5A36}' .
+'\x{5A3C}\x{5A40}\x{5A41}\x{5A46}\x{5A49}\x{5A5A}\x{5A62}\x{5A66}\x{5A6A}' .
+'\x{5A6C}\x{5A7F}\x{5A92}\x{5A9A}\x{5A9B}\x{5ABC}\x{5ABD}\x{5ABE}\x{5AC1}' .
+'\x{5AC2}\x{5AC9}\x{5ACB}\x{5ACC}\x{5AD0}\x{5AD6}\x{5AD7}\x{5AE1}\x{5AE3}' .
+'\x{5AE6}\x{5AE9}\x{5AFA}\x{5AFB}\x{5B09}\x{5B0B}\x{5B0C}\x{5B16}\x{5B22}' .
+'\x{5B2A}\x{5B2C}\x{5B30}\x{5B32}\x{5B36}\x{5B3E}\x{5B40}\x{5B43}\x{5B45}' .
+'\x{5B50}\x{5B51}\x{5B54}\x{5B55}\x{5B57}\x{5B58}\x{5B5A}\x{5B5B}\x{5B5C}' .
+'\x{5B5D}\x{5B5F}\x{5B63}\x{5B64}\x{5B65}\x{5B66}\x{5B69}\x{5B6B}\x{5B70}' .
+'\x{5B71}\x{5B73}\x{5B75}\x{5B78}\x{5B7A}\x{5B80}\x{5B83}\x{5B85}\x{5B87}' .
+'\x{5B88}\x{5B89}\x{5B8B}\x{5B8C}\x{5B8D}\x{5B8F}\x{5B95}\x{5B97}\x{5B98}' .
+'\x{5B99}\x{5B9A}\x{5B9B}\x{5B9C}\x{5B9D}\x{5B9F}\x{5BA2}\x{5BA3}\x{5BA4}' .
+'\x{5BA5}\x{5BA6}\x{5BAE}\x{5BB0}\x{5BB3}\x{5BB4}\x{5BB5}\x{5BB6}\x{5BB8}' .
+'\x{5BB9}\x{5BBF}\x{5BC2}\x{5BC3}\x{5BC4}\x{5BC5}\x{5BC6}\x{5BC7}\x{5BC9}' .
+'\x{5BCC}\x{5BD0}\x{5BD2}\x{5BD3}\x{5BD4}\x{5BDB}\x{5BDD}\x{5BDE}\x{5BDF}' .
+'\x{5BE1}\x{5BE2}\x{5BE4}\x{5BE5}\x{5BE6}\x{5BE7}\x{5BE8}\x{5BE9}\x{5BEB}' .
+'\x{5BEE}\x{5BF0}\x{5BF3}\x{5BF5}\x{5BF6}\x{5BF8}\x{5BFA}\x{5BFE}\x{5BFF}' .
+'\x{5C01}\x{5C02}\x{5C04}\x{5C05}\x{5C06}\x{5C07}\x{5C08}\x{5C09}\x{5C0A}' .
+'\x{5C0B}\x{5C0D}\x{5C0E}\x{5C0F}\x{5C11}\x{5C13}\x{5C16}\x{5C1A}\x{5C20}' .
+'\x{5C22}\x{5C24}\x{5C28}\x{5C2D}\x{5C31}\x{5C38}\x{5C39}\x{5C3A}\x{5C3B}' .
+'\x{5C3C}\x{5C3D}\x{5C3E}\x{5C3F}\x{5C40}\x{5C41}\x{5C45}\x{5C46}\x{5C48}' .
+'\x{5C4A}\x{5C4B}\x{5C4D}\x{5C4E}\x{5C4F}\x{5C50}\x{5C51}\x{5C53}\x{5C55}' .
+'\x{5C5E}\x{5C60}\x{5C61}\x{5C64}\x{5C65}\x{5C6C}\x{5C6E}\x{5C6F}\x{5C71}' .
+'\x{5C76}\x{5C79}\x{5C8C}\x{5C90}\x{5C91}\x{5C94}\x{5CA1}\x{5CA8}\x{5CA9}' .
+'\x{5CAB}\x{5CAC}\x{5CB1}\x{5CB3}\x{5CB6}\x{5CB7}\x{5CB8}\x{5CBB}\x{5CBC}' .
+'\x{5CBE}\x{5CC5}\x{5CC7}\x{5CD9}\x{5CE0}\x{5CE1}\x{5CE8}\x{5CE9}\x{5CEA}' .
+'\x{5CED}\x{5CEF}\x{5CF0}\x{5CF6}\x{5CFA}\x{5CFB}\x{5CFD}\x{5D07}\x{5D0B}' .
+'\x{5D0E}\x{5D11}\x{5D14}\x{5D15}\x{5D16}\x{5D17}\x{5D18}\x{5D19}\x{5D1A}' .
+'\x{5D1B}\x{5D1F}\x{5D22}\x{5D29}\x{5D4B}\x{5D4C}\x{5D4E}\x{5D50}\x{5D52}' .
+'\x{5D5C}\x{5D69}\x{5D6C}\x{5D6F}\x{5D73}\x{5D76}\x{5D82}\x{5D84}\x{5D87}' .
+'\x{5D8B}\x{5D8C}\x{5D90}\x{5D9D}\x{5DA2}\x{5DAC}\x{5DAE}\x{5DB7}\x{5DBA}' .
+'\x{5DBC}\x{5DBD}\x{5DC9}\x{5DCC}\x{5DCD}\x{5DD2}\x{5DD3}\x{5DD6}\x{5DDB}' .
+'\x{5DDD}\x{5DDE}\x{5DE1}\x{5DE3}\x{5DE5}\x{5DE6}\x{5DE7}\x{5DE8}\x{5DEB}' .
+'\x{5DEE}\x{5DF1}\x{5DF2}\x{5DF3}\x{5DF4}\x{5DF5}\x{5DF7}\x{5DFB}\x{5DFD}' .
+'\x{5DFE}\x{5E02}\x{5E03}\x{5E06}\x{5E0B}\x{5E0C}\x{5E11}\x{5E16}\x{5E19}' .
+'\x{5E1A}\x{5E1B}\x{5E1D}\x{5E25}\x{5E2B}\x{5E2D}\x{5E2F}\x{5E30}\x{5E33}' .
+'\x{5E36}\x{5E37}\x{5E38}\x{5E3D}\x{5E40}\x{5E43}\x{5E44}\x{5E45}\x{5E47}' .
+'\x{5E4C}\x{5E4E}\x{5E54}\x{5E55}\x{5E57}\x{5E5F}\x{5E61}\x{5E62}\x{5E63}' .
+'\x{5E64}\x{5E72}\x{5E73}\x{5E74}\x{5E75}\x{5E76}\x{5E78}\x{5E79}\x{5E7A}' .
+'\x{5E7B}\x{5E7C}\x{5E7D}\x{5E7E}\x{5E7F}\x{5E81}\x{5E83}\x{5E84}\x{5E87}' .
+'\x{5E8A}\x{5E8F}\x{5E95}\x{5E96}\x{5E97}\x{5E9A}\x{5E9C}\x{5EA0}\x{5EA6}' .
+'\x{5EA7}\x{5EAB}\x{5EAD}\x{5EB5}\x{5EB6}\x{5EB7}\x{5EB8}\x{5EC1}\x{5EC2}' .
+'\x{5EC3}\x{5EC8}\x{5EC9}\x{5ECA}\x{5ECF}\x{5ED0}\x{5ED3}\x{5ED6}\x{5EDA}' .
+'\x{5EDB}\x{5EDD}\x{5EDF}\x{5EE0}\x{5EE1}\x{5EE2}\x{5EE3}\x{5EE8}\x{5EE9}' .
+'\x{5EEC}\x{5EF0}\x{5EF1}\x{5EF3}\x{5EF4}\x{5EF6}\x{5EF7}\x{5EF8}\x{5EFA}' .
+'\x{5EFB}\x{5EFC}\x{5EFE}\x{5EFF}\x{5F01}\x{5F03}\x{5F04}\x{5F09}\x{5F0A}' .
+'\x{5F0B}\x{5F0C}\x{5F0D}\x{5F0F}\x{5F10}\x{5F11}\x{5F13}\x{5F14}\x{5F15}' .
+'\x{5F16}\x{5F17}\x{5F18}\x{5F1B}\x{5F1F}\x{5F25}\x{5F26}\x{5F27}\x{5F29}' .
+'\x{5F2D}\x{5F2F}\x{5F31}\x{5F35}\x{5F37}\x{5F38}\x{5F3C}\x{5F3E}\x{5F41}' .
+'\x{5F48}\x{5F4A}\x{5F4C}\x{5F4E}\x{5F51}\x{5F53}\x{5F56}\x{5F57}\x{5F59}' .
+'\x{5F5C}\x{5F5D}\x{5F61}\x{5F62}\x{5F66}\x{5F69}\x{5F6A}\x{5F6B}\x{5F6C}' .
+'\x{5F6D}\x{5F70}\x{5F71}\x{5F73}\x{5F77}\x{5F79}\x{5F7C}\x{5F7F}\x{5F80}' .
+'\x{5F81}\x{5F82}\x{5F83}\x{5F84}\x{5F85}\x{5F87}\x{5F88}\x{5F8A}\x{5F8B}' .
+'\x{5F8C}\x{5F90}\x{5F91}\x{5F92}\x{5F93}\x{5F97}\x{5F98}\x{5F99}\x{5F9E}' .
+'\x{5FA0}\x{5FA1}\x{5FA8}\x{5FA9}\x{5FAA}\x{5FAD}\x{5FAE}\x{5FB3}\x{5FB4}' .
+'\x{5FB9}\x{5FBC}\x{5FBD}\x{5FC3}\x{5FC5}\x{5FCC}\x{5FCD}\x{5FD6}\x{5FD7}' .
+'\x{5FD8}\x{5FD9}\x{5FDC}\x{5FDD}\x{5FE0}\x{5FE4}\x{5FEB}\x{5FF0}\x{5FF1}' .
+'\x{5FF5}\x{5FF8}\x{5FFB}\x{5FFD}\x{5FFF}\x{600E}\x{600F}\x{6010}\x{6012}' .
+'\x{6015}\x{6016}\x{6019}\x{601B}\x{601C}\x{601D}\x{6020}\x{6021}\x{6025}' .
+'\x{6026}\x{6027}\x{6028}\x{6029}\x{602A}\x{602B}\x{602F}\x{6031}\x{603A}' .
+'\x{6041}\x{6042}\x{6043}\x{6046}\x{604A}\x{604B}\x{604D}\x{6050}\x{6052}' .
+'\x{6055}\x{6059}\x{605A}\x{605F}\x{6060}\x{6062}\x{6063}\x{6064}\x{6065}' .
+'\x{6068}\x{6069}\x{606A}\x{606B}\x{606C}\x{606D}\x{606F}\x{6070}\x{6075}' .
+'\x{6077}\x{6081}\x{6083}\x{6084}\x{6089}\x{608B}\x{608C}\x{608D}\x{6092}' .
+'\x{6094}\x{6096}\x{6097}\x{609A}\x{609B}\x{609F}\x{60A0}\x{60A3}\x{60A6}' .
+'\x{60A7}\x{60A9}\x{60AA}\x{60B2}\x{60B3}\x{60B4}\x{60B5}\x{60B6}\x{60B8}' .
+'\x{60BC}\x{60BD}\x{60C5}\x{60C6}\x{60C7}\x{60D1}\x{60D3}\x{60D8}\x{60DA}' .
+'\x{60DC}\x{60DF}\x{60E0}\x{60E1}\x{60E3}\x{60E7}\x{60E8}\x{60F0}\x{60F1}' .
+'\x{60F3}\x{60F4}\x{60F6}\x{60F7}\x{60F9}\x{60FA}\x{60FB}\x{6100}\x{6101}' .
+'\x{6103}\x{6106}\x{6108}\x{6109}\x{610D}\x{610E}\x{610F}\x{6115}\x{611A}' .
+'\x{611B}\x{611F}\x{6121}\x{6127}\x{6128}\x{612C}\x{6134}\x{613C}\x{613D}' .
+'\x{613E}\x{613F}\x{6142}\x{6144}\x{6147}\x{6148}\x{614A}\x{614B}\x{614C}' .
+'\x{614D}\x{614E}\x{6153}\x{6155}\x{6158}\x{6159}\x{615A}\x{615D}\x{615F}' .
+'\x{6162}\x{6163}\x{6165}\x{6167}\x{6168}\x{616B}\x{616E}\x{616F}\x{6170}' .
+'\x{6171}\x{6173}\x{6174}\x{6175}\x{6176}\x{6177}\x{617E}\x{6182}\x{6187}' .
+'\x{618A}\x{618E}\x{6190}\x{6191}\x{6194}\x{6196}\x{6199}\x{619A}\x{61A4}' .
+'\x{61A7}\x{61A9}\x{61AB}\x{61AC}\x{61AE}\x{61B2}\x{61B6}\x{61BA}\x{61BE}' .
+'\x{61C3}\x{61C6}\x{61C7}\x{61C8}\x{61C9}\x{61CA}\x{61CB}\x{61CC}\x{61CD}' .
+'\x{61D0}\x{61E3}\x{61E6}\x{61F2}\x{61F4}\x{61F6}\x{61F7}\x{61F8}\x{61FA}' .
+'\x{61FC}\x{61FD}\x{61FE}\x{61FF}\x{6200}\x{6208}\x{6209}\x{620A}\x{620C}' .
+'\x{620D}\x{620E}\x{6210}\x{6211}\x{6212}\x{6214}\x{6216}\x{621A}\x{621B}' .
+'\x{621D}\x{621E}\x{621F}\x{6221}\x{6226}\x{622A}\x{622E}\x{622F}\x{6230}' .
+'\x{6232}\x{6233}\x{6234}\x{6238}\x{623B}\x{623F}\x{6240}\x{6241}\x{6247}' .
+'\x{6248}\x{6249}\x{624B}\x{624D}\x{624E}\x{6253}\x{6255}\x{6258}\x{625B}' .
+'\x{625E}\x{6260}\x{6263}\x{6268}\x{626E}\x{6271}\x{6276}\x{6279}\x{627C}' .
+'\x{627E}\x{627F}\x{6280}\x{6282}\x{6283}\x{6284}\x{6289}\x{628A}\x{6291}' .
+'\x{6292}\x{6293}\x{6294}\x{6295}\x{6296}\x{6297}\x{6298}\x{629B}\x{629C}' .
+'\x{629E}\x{62AB}\x{62AC}\x{62B1}\x{62B5}\x{62B9}\x{62BB}\x{62BC}\x{62BD}' .
+'\x{62C2}\x{62C5}\x{62C6}\x{62C7}\x{62C8}\x{62C9}\x{62CA}\x{62CC}\x{62CD}' .
+'\x{62CF}\x{62D0}\x{62D1}\x{62D2}\x{62D3}\x{62D4}\x{62D7}\x{62D8}\x{62D9}' .
+'\x{62DB}\x{62DC}\x{62DD}\x{62E0}\x{62E1}\x{62EC}\x{62ED}\x{62EE}\x{62EF}' .
+'\x{62F1}\x{62F3}\x{62F5}\x{62F6}\x{62F7}\x{62FE}\x{62FF}\x{6301}\x{6302}' .
+'\x{6307}\x{6308}\x{6309}\x{630C}\x{6311}\x{6319}\x{631F}\x{6327}\x{6328}' .
+'\x{632B}\x{632F}\x{633A}\x{633D}\x{633E}\x{633F}\x{6349}\x{634C}\x{634D}' .
+'\x{634F}\x{6350}\x{6355}\x{6357}\x{635C}\x{6367}\x{6368}\x{6369}\x{636B}' .
+'\x{636E}\x{6372}\x{6376}\x{6377}\x{637A}\x{637B}\x{6380}\x{6383}\x{6388}' .
+'\x{6389}\x{638C}\x{638E}\x{638F}\x{6392}\x{6396}\x{6398}\x{639B}\x{639F}' .
+'\x{63A0}\x{63A1}\x{63A2}\x{63A3}\x{63A5}\x{63A7}\x{63A8}\x{63A9}\x{63AA}' .
+'\x{63AB}\x{63AC}\x{63B2}\x{63B4}\x{63B5}\x{63BB}\x{63BE}\x{63C0}\x{63C3}' .
+'\x{63C4}\x{63C6}\x{63C9}\x{63CF}\x{63D0}\x{63D2}\x{63D6}\x{63DA}\x{63DB}' .
+'\x{63E1}\x{63E3}\x{63E9}\x{63EE}\x{63F4}\x{63F6}\x{63FA}\x{6406}\x{640D}' .
+'\x{640F}\x{6413}\x{6416}\x{6417}\x{641C}\x{6426}\x{6428}\x{642C}\x{642D}' .
+'\x{6434}\x{6436}\x{643A}\x{643E}\x{6442}\x{644E}\x{6458}\x{6467}\x{6469}' .
+'\x{646F}\x{6476}\x{6478}\x{647A}\x{6483}\x{6488}\x{6492}\x{6493}\x{6495}' .
+'\x{649A}\x{649E}\x{64A4}\x{64A5}\x{64A9}\x{64AB}\x{64AD}\x{64AE}\x{64B0}' .
+'\x{64B2}\x{64B9}\x{64BB}\x{64BC}\x{64C1}\x{64C2}\x{64C5}\x{64C7}\x{64CD}' .
+'\x{64D2}\x{64D4}\x{64D8}\x{64DA}\x{64E0}\x{64E1}\x{64E2}\x{64E3}\x{64E6}' .
+'\x{64E7}\x{64EC}\x{64EF}\x{64F1}\x{64F2}\x{64F4}\x{64F6}\x{64FA}\x{64FD}' .
+'\x{64FE}\x{6500}\x{6505}\x{6518}\x{651C}\x{651D}\x{6523}\x{6524}\x{652A}' .
+'\x{652B}\x{652C}\x{652F}\x{6534}\x{6535}\x{6536}\x{6537}\x{6538}\x{6539}' .
+'\x{653B}\x{653E}\x{653F}\x{6545}\x{6548}\x{654D}\x{654F}\x{6551}\x{6555}' .
+'\x{6556}\x{6557}\x{6558}\x{6559}\x{655D}\x{655E}\x{6562}\x{6563}\x{6566}' .
+'\x{656C}\x{6570}\x{6572}\x{6574}\x{6575}\x{6577}\x{6578}\x{6582}\x{6583}' .
+'\x{6587}\x{6588}\x{6589}\x{658C}\x{658E}\x{6590}\x{6591}\x{6597}\x{6599}' .
+'\x{659B}\x{659C}\x{659F}\x{65A1}\x{65A4}\x{65A5}\x{65A7}\x{65AB}\x{65AC}' .
+'\x{65AD}\x{65AF}\x{65B0}\x{65B7}\x{65B9}\x{65BC}\x{65BD}\x{65C1}\x{65C3}' .
+'\x{65C4}\x{65C5}\x{65C6}\x{65CB}\x{65CC}\x{65CF}\x{65D2}\x{65D7}\x{65D9}' .
+'\x{65DB}\x{65E0}\x{65E1}\x{65E2}\x{65E5}\x{65E6}\x{65E7}\x{65E8}\x{65E9}' .
+'\x{65EC}\x{65ED}\x{65F1}\x{65FA}\x{65FB}\x{6602}\x{6603}\x{6606}\x{6607}' .
+'\x{660A}\x{660C}\x{660E}\x{660F}\x{6613}\x{6614}\x{661C}\x{661F}\x{6620}' .
+'\x{6625}\x{6627}\x{6628}\x{662D}\x{662F}\x{6634}\x{6635}\x{6636}\x{663C}' .
+'\x{663F}\x{6641}\x{6642}\x{6643}\x{6644}\x{6649}\x{664B}\x{664F}\x{6652}' .
+'\x{665D}\x{665E}\x{665F}\x{6662}\x{6664}\x{6666}\x{6667}\x{6668}\x{6669}' .
+'\x{666E}\x{666F}\x{6670}\x{6674}\x{6676}\x{667A}\x{6681}\x{6683}\x{6684}' .
+'\x{6687}\x{6688}\x{6689}\x{668E}\x{6691}\x{6696}\x{6697}\x{6698}\x{669D}' .
+'\x{66A2}\x{66A6}\x{66AB}\x{66AE}\x{66B4}\x{66B8}\x{66B9}\x{66BC}\x{66BE}' .
+'\x{66C1}\x{66C4}\x{66C7}\x{66C9}\x{66D6}\x{66D9}\x{66DA}\x{66DC}\x{66DD}' .
+'\x{66E0}\x{66E6}\x{66E9}\x{66F0}\x{66F2}\x{66F3}\x{66F4}\x{66F5}\x{66F7}' .
+'\x{66F8}\x{66F9}\x{66FC}\x{66FD}\x{66FE}\x{66FF}\x{6700}\x{6703}\x{6708}' .
+'\x{6709}\x{670B}\x{670D}\x{670F}\x{6714}\x{6715}\x{6716}\x{6717}\x{671B}' .
+'\x{671D}\x{671E}\x{671F}\x{6726}\x{6727}\x{6728}\x{672A}\x{672B}\x{672C}' .
+'\x{672D}\x{672E}\x{6731}\x{6734}\x{6736}\x{6737}\x{6738}\x{673A}\x{673D}' .
+'\x{673F}\x{6741}\x{6746}\x{6749}\x{674E}\x{674F}\x{6750}\x{6751}\x{6753}' .
+'\x{6756}\x{6759}\x{675C}\x{675E}\x{675F}\x{6760}\x{6761}\x{6762}\x{6763}' .
+'\x{6764}\x{6765}\x{676A}\x{676D}\x{676F}\x{6770}\x{6771}\x{6772}\x{6773}' .
+'\x{6775}\x{6777}\x{677C}\x{677E}\x{677F}\x{6785}\x{6787}\x{6789}\x{678B}' .
+'\x{678C}\x{6790}\x{6795}\x{6797}\x{679A}\x{679C}\x{679D}\x{67A0}\x{67A1}' .
+'\x{67A2}\x{67A6}\x{67A9}\x{67AF}\x{67B3}\x{67B4}\x{67B6}\x{67B7}\x{67B8}' .
+'\x{67B9}\x{67C1}\x{67C4}\x{67C6}\x{67CA}\x{67CE}\x{67CF}\x{67D0}\x{67D1}' .
+'\x{67D3}\x{67D4}\x{67D8}\x{67DA}\x{67DD}\x{67DE}\x{67E2}\x{67E4}\x{67E7}' .
+'\x{67E9}\x{67EC}\x{67EE}\x{67EF}\x{67F1}\x{67F3}\x{67F4}\x{67F5}\x{67FB}' .
+'\x{67FE}\x{67FF}\x{6802}\x{6803}\x{6804}\x{6813}\x{6816}\x{6817}\x{681E}' .
+'\x{6821}\x{6822}\x{6829}\x{682A}\x{682B}\x{6832}\x{6834}\x{6838}\x{6839}' .
+'\x{683C}\x{683D}\x{6840}\x{6841}\x{6842}\x{6843}\x{6846}\x{6848}\x{684D}' .
+'\x{684E}\x{6850}\x{6851}\x{6853}\x{6854}\x{6859}\x{685C}\x{685D}\x{685F}' .
+'\x{6863}\x{6867}\x{6874}\x{6876}\x{6877}\x{687E}\x{687F}\x{6881}\x{6883}' .
+'\x{6885}\x{688D}\x{688F}\x{6893}\x{6894}\x{6897}\x{689B}\x{689D}\x{689F}' .
+'\x{68A0}\x{68A2}\x{68A6}\x{68A7}\x{68A8}\x{68AD}\x{68AF}\x{68B0}\x{68B1}' .
+'\x{68B3}\x{68B5}\x{68B6}\x{68B9}\x{68BA}\x{68BC}\x{68C4}\x{68C6}\x{68C9}' .
+'\x{68CA}\x{68CB}\x{68CD}\x{68D2}\x{68D4}\x{68D5}\x{68D7}\x{68D8}\x{68DA}' .
+'\x{68DF}\x{68E0}\x{68E1}\x{68E3}\x{68E7}\x{68EE}\x{68EF}\x{68F2}\x{68F9}' .
+'\x{68FA}\x{6900}\x{6901}\x{6904}\x{6905}\x{6908}\x{690B}\x{690C}\x{690D}' .
+'\x{690E}\x{690F}\x{6912}\x{6919}\x{691A}\x{691B}\x{691C}\x{6921}\x{6922}' .
+'\x{6923}\x{6925}\x{6926}\x{6928}\x{692A}\x{6930}\x{6934}\x{6936}\x{6939}' .
+'\x{693D}\x{693F}\x{694A}\x{6953}\x{6954}\x{6955}\x{6959}\x{695A}\x{695C}' .
+'\x{695D}\x{695E}\x{6960}\x{6961}\x{6962}\x{696A}\x{696B}\x{696D}\x{696E}' .
+'\x{696F}\x{6973}\x{6974}\x{6975}\x{6977}\x{6978}\x{6979}\x{697C}\x{697D}' .
+'\x{697E}\x{6981}\x{6982}\x{698A}\x{698E}\x{6991}\x{6994}\x{6995}\x{699B}' .
+'\x{699C}\x{69A0}\x{69A7}\x{69AE}\x{69B1}\x{69B2}\x{69B4}\x{69BB}\x{69BE}' .
+'\x{69BF}\x{69C1}\x{69C3}\x{69C7}\x{69CA}\x{69CB}\x{69CC}\x{69CD}\x{69CE}' .
+'\x{69D0}\x{69D3}\x{69D8}\x{69D9}\x{69DD}\x{69DE}\x{69E7}\x{69E8}\x{69EB}' .
+'\x{69ED}\x{69F2}\x{69F9}\x{69FB}\x{69FD}\x{69FF}\x{6A02}\x{6A05}\x{6A0A}' .
+'\x{6A0B}\x{6A0C}\x{6A12}\x{6A13}\x{6A14}\x{6A17}\x{6A19}\x{6A1B}\x{6A1E}' .
+'\x{6A1F}\x{6A21}\x{6A22}\x{6A23}\x{6A29}\x{6A2A}\x{6A2B}\x{6A2E}\x{6A35}' .
+'\x{6A36}\x{6A38}\x{6A39}\x{6A3A}\x{6A3D}\x{6A44}\x{6A47}\x{6A48}\x{6A4B}' .
+'\x{6A58}\x{6A59}\x{6A5F}\x{6A61}\x{6A62}\x{6A66}\x{6A72}\x{6A78}\x{6A7F}' .
+'\x{6A80}\x{6A84}\x{6A8D}\x{6A8E}\x{6A90}\x{6A97}\x{6A9C}\x{6AA0}\x{6AA2}' .
+'\x{6AA3}\x{6AAA}\x{6AAC}\x{6AAE}\x{6AB3}\x{6AB8}\x{6ABB}\x{6AC1}\x{6AC2}' .
+'\x{6AC3}\x{6AD1}\x{6AD3}\x{6ADA}\x{6ADB}\x{6ADE}\x{6ADF}\x{6AE8}\x{6AEA}' .
+'\x{6AFA}\x{6AFB}\x{6B04}\x{6B05}\x{6B0A}\x{6B12}\x{6B16}\x{6B1D}\x{6B1F}' .
+'\x{6B20}\x{6B21}\x{6B23}\x{6B27}\x{6B32}\x{6B37}\x{6B38}\x{6B39}\x{6B3A}' .
+'\x{6B3D}\x{6B3E}\x{6B43}\x{6B47}\x{6B49}\x{6B4C}\x{6B4E}\x{6B50}\x{6B53}' .
+'\x{6B54}\x{6B59}\x{6B5B}\x{6B5F}\x{6B61}\x{6B62}\x{6B63}\x{6B64}\x{6B66}' .
+'\x{6B69}\x{6B6A}\x{6B6F}\x{6B73}\x{6B74}\x{6B78}\x{6B79}\x{6B7B}\x{6B7F}' .
+'\x{6B80}\x{6B83}\x{6B84}\x{6B86}\x{6B89}\x{6B8A}\x{6B8B}\x{6B8D}\x{6B95}' .
+'\x{6B96}\x{6B98}\x{6B9E}\x{6BA4}\x{6BAA}\x{6BAB}\x{6BAF}\x{6BB1}\x{6BB2}' .
+'\x{6BB3}\x{6BB4}\x{6BB5}\x{6BB7}\x{6BBA}\x{6BBB}\x{6BBC}\x{6BBF}\x{6BC0}' .
+'\x{6BC5}\x{6BC6}\x{6BCB}\x{6BCD}\x{6BCE}\x{6BD2}\x{6BD3}\x{6BD4}\x{6BD8}' .
+'\x{6BDB}\x{6BDF}\x{6BEB}\x{6BEC}\x{6BEF}\x{6BF3}\x{6C08}\x{6C0F}\x{6C11}' .
+'\x{6C13}\x{6C14}\x{6C17}\x{6C1B}\x{6C23}\x{6C24}\x{6C34}\x{6C37}\x{6C38}' .
+'\x{6C3E}\x{6C40}\x{6C41}\x{6C42}\x{6C4E}\x{6C50}\x{6C55}\x{6C57}\x{6C5A}' .
+'\x{6C5D}\x{6C5E}\x{6C5F}\x{6C60}\x{6C62}\x{6C68}\x{6C6A}\x{6C70}\x{6C72}' .
+'\x{6C73}\x{6C7A}\x{6C7D}\x{6C7E}\x{6C81}\x{6C82}\x{6C83}\x{6C88}\x{6C8C}' .
+'\x{6C8D}\x{6C90}\x{6C92}\x{6C93}\x{6C96}\x{6C99}\x{6C9A}\x{6C9B}\x{6CA1}' .
+'\x{6CA2}\x{6CAB}\x{6CAE}\x{6CB1}\x{6CB3}\x{6CB8}\x{6CB9}\x{6CBA}\x{6CBB}' .
+'\x{6CBC}\x{6CBD}\x{6CBE}\x{6CBF}\x{6CC1}\x{6CC4}\x{6CC5}\x{6CC9}\x{6CCA}' .
+'\x{6CCC}\x{6CD3}\x{6CD5}\x{6CD7}\x{6CD9}\x{6CDB}\x{6CDD}\x{6CE1}\x{6CE2}' .
+'\x{6CE3}\x{6CE5}\x{6CE8}\x{6CEA}\x{6CEF}\x{6CF0}\x{6CF1}\x{6CF3}\x{6D0B}' .
+'\x{6D0C}\x{6D12}\x{6D17}\x{6D19}\x{6D1B}\x{6D1E}\x{6D1F}\x{6D25}\x{6D29}' .
+'\x{6D2A}\x{6D2B}\x{6D32}\x{6D33}\x{6D35}\x{6D36}\x{6D38}\x{6D3B}\x{6D3D}' .
+'\x{6D3E}\x{6D41}\x{6D44}\x{6D45}\x{6D59}\x{6D5A}\x{6D5C}\x{6D63}\x{6D64}' .
+'\x{6D66}\x{6D69}\x{6D6A}\x{6D6C}\x{6D6E}\x{6D74}\x{6D77}\x{6D78}\x{6D79}' .
+'\x{6D85}\x{6D88}\x{6D8C}\x{6D8E}\x{6D93}\x{6D95}\x{6D99}\x{6D9B}\x{6D9C}' .
+'\x{6DAF}\x{6DB2}\x{6DB5}\x{6DB8}\x{6DBC}\x{6DC0}\x{6DC5}\x{6DC6}\x{6DC7}' .
+'\x{6DCB}\x{6DCC}\x{6DD1}\x{6DD2}\x{6DD5}\x{6DD8}\x{6DD9}\x{6DDE}\x{6DE1}' .
+'\x{6DE4}\x{6DE6}\x{6DE8}\x{6DEA}\x{6DEB}\x{6DEC}\x{6DEE}\x{6DF1}\x{6DF3}' .
+'\x{6DF5}\x{6DF7}\x{6DF9}\x{6DFA}\x{6DFB}\x{6E05}\x{6E07}\x{6E08}\x{6E09}' .
+'\x{6E0A}\x{6E0B}\x{6E13}\x{6E15}\x{6E19}\x{6E1A}\x{6E1B}\x{6E1D}\x{6E1F}' .
+'\x{6E20}\x{6E21}\x{6E23}\x{6E24}\x{6E25}\x{6E26}\x{6E29}\x{6E2B}\x{6E2C}' .
+'\x{6E2D}\x{6E2E}\x{6E2F}\x{6E38}\x{6E3A}\x{6E3E}\x{6E43}\x{6E4A}\x{6E4D}' .
+'\x{6E4E}\x{6E56}\x{6E58}\x{6E5B}\x{6E5F}\x{6E67}\x{6E6B}\x{6E6E}\x{6E6F}' .
+'\x{6E72}\x{6E76}\x{6E7E}\x{6E7F}\x{6E80}\x{6E82}\x{6E8C}\x{6E8F}\x{6E90}' .
+'\x{6E96}\x{6E98}\x{6E9C}\x{6E9D}\x{6E9F}\x{6EA2}\x{6EA5}\x{6EAA}\x{6EAF}' .
+'\x{6EB2}\x{6EB6}\x{6EB7}\x{6EBA}\x{6EBD}\x{6EC2}\x{6EC4}\x{6EC5}\x{6EC9}' .
+'\x{6ECB}\x{6ECC}\x{6ED1}\x{6ED3}\x{6ED4}\x{6ED5}\x{6EDD}\x{6EDE}\x{6EEC}' .
+'\x{6EEF}\x{6EF2}\x{6EF4}\x{6EF7}\x{6EF8}\x{6EFE}\x{6EFF}\x{6F01}\x{6F02}' .
+'\x{6F06}\x{6F09}\x{6F0F}\x{6F11}\x{6F13}\x{6F14}\x{6F15}\x{6F20}\x{6F22}' .
+'\x{6F23}\x{6F2B}\x{6F2C}\x{6F31}\x{6F32}\x{6F38}\x{6F3E}\x{6F3F}\x{6F41}' .
+'\x{6F45}\x{6F54}\x{6F58}\x{6F5B}\x{6F5C}\x{6F5F}\x{6F64}\x{6F66}\x{6F6D}' .
+'\x{6F6E}\x{6F6F}\x{6F70}\x{6F74}\x{6F78}\x{6F7A}\x{6F7C}\x{6F80}\x{6F81}' .
+'\x{6F82}\x{6F84}\x{6F86}\x{6F8E}\x{6F91}\x{6F97}\x{6FA1}\x{6FA3}\x{6FA4}' .
+'\x{6FAA}\x{6FB1}\x{6FB3}\x{6FB9}\x{6FC0}\x{6FC1}\x{6FC2}\x{6FC3}\x{6FC6}' .
+'\x{6FD4}\x{6FD5}\x{6FD8}\x{6FDB}\x{6FDF}\x{6FE0}\x{6FE1}\x{6FE4}\x{6FEB}' .
+'\x{6FEC}\x{6FEE}\x{6FEF}\x{6FF1}\x{6FF3}\x{6FF6}\x{6FFA}\x{6FFE}\x{7001}' .
+'\x{7009}\x{700B}\x{700F}\x{7011}\x{7015}\x{7018}\x{701A}\x{701B}\x{701D}' .
+'\x{701E}\x{701F}\x{7026}\x{7027}\x{702C}\x{7030}\x{7032}\x{703E}\x{704C}' .
+'\x{7051}\x{7058}\x{7063}\x{706B}\x{706F}\x{7070}\x{7078}\x{707C}\x{707D}' .
+'\x{7089}\x{708A}\x{708E}\x{7092}\x{7099}\x{70AC}\x{70AD}\x{70AE}\x{70AF}' .
+'\x{70B3}\x{70B8}\x{70B9}\x{70BA}\x{70C8}\x{70CB}\x{70CF}\x{70D9}\x{70DD}' .
+'\x{70DF}\x{70F1}\x{70F9}\x{70FD}\x{7109}\x{7114}\x{7119}\x{711A}\x{711C}' .
+'\x{7121}\x{7126}\x{7136}\x{713C}\x{7149}\x{714C}\x{714E}\x{7155}\x{7156}' .
+'\x{7159}\x{7162}\x{7164}\x{7165}\x{7166}\x{7167}\x{7169}\x{716C}\x{716E}' .
+'\x{717D}\x{7184}\x{7188}\x{718A}\x{718F}\x{7194}\x{7195}\x{7199}\x{719F}' .
+'\x{71A8}\x{71AC}\x{71B1}\x{71B9}\x{71BE}\x{71C3}\x{71C8}\x{71C9}\x{71CE}' .
+'\x{71D0}\x{71D2}\x{71D4}\x{71D5}\x{71D7}\x{71DF}\x{71E0}\x{71E5}\x{71E6}' .
+'\x{71E7}\x{71EC}\x{71ED}\x{71EE}\x{71F5}\x{71F9}\x{71FB}\x{71FC}\x{71FF}' .
+'\x{7206}\x{720D}\x{7210}\x{721B}\x{7228}\x{722A}\x{722C}\x{722D}\x{7230}' .
+'\x{7232}\x{7235}\x{7236}\x{723A}\x{723B}\x{723C}\x{723D}\x{723E}\x{723F}' .
+'\x{7240}\x{7246}\x{7247}\x{7248}\x{724B}\x{724C}\x{7252}\x{7258}\x{7259}' .
+'\x{725B}\x{725D}\x{725F}\x{7261}\x{7262}\x{7267}\x{7269}\x{7272}\x{7274}' .
+'\x{7279}\x{727D}\x{727E}\x{7280}\x{7281}\x{7282}\x{7287}\x{7292}\x{7296}' .
+'\x{72A0}\x{72A2}\x{72A7}\x{72AC}\x{72AF}\x{72B2}\x{72B6}\x{72B9}\x{72C2}' .
+'\x{72C3}\x{72C4}\x{72C6}\x{72CE}\x{72D0}\x{72D2}\x{72D7}\x{72D9}\x{72DB}' .
+'\x{72E0}\x{72E1}\x{72E2}\x{72E9}\x{72EC}\x{72ED}\x{72F7}\x{72F8}\x{72F9}' .
+'\x{72FC}\x{72FD}\x{730A}\x{7316}\x{7317}\x{731B}\x{731C}\x{731D}\x{731F}' .
+'\x{7325}\x{7329}\x{732A}\x{732B}\x{732E}\x{732F}\x{7334}\x{7336}\x{7337}' .
+'\x{733E}\x{733F}\x{7344}\x{7345}\x{734E}\x{734F}\x{7357}\x{7363}\x{7368}' .
+'\x{736A}\x{7370}\x{7372}\x{7375}\x{7378}\x{737A}\x{737B}\x{7384}\x{7387}' .
+'\x{7389}\x{738B}\x{7396}\x{73A9}\x{73B2}\x{73B3}\x{73BB}\x{73C0}\x{73C2}' .
+'\x{73C8}\x{73CA}\x{73CD}\x{73CE}\x{73DE}\x{73E0}\x{73E5}\x{73EA}\x{73ED}' .
+'\x{73EE}\x{73F1}\x{73F8}\x{73FE}\x{7403}\x{7405}\x{7406}\x{7409}\x{7422}' .
+'\x{7425}\x{7432}\x{7433}\x{7434}\x{7435}\x{7436}\x{743A}\x{743F}\x{7441}' .
+'\x{7455}\x{7459}\x{745A}\x{745B}\x{745C}\x{745E}\x{745F}\x{7460}\x{7463}' .
+'\x{7464}\x{7469}\x{746A}\x{746F}\x{7470}\x{7473}\x{7476}\x{747E}\x{7483}' .
+'\x{748B}\x{749E}\x{74A2}\x{74A7}\x{74B0}\x{74BD}\x{74CA}\x{74CF}\x{74D4}' .
+'\x{74DC}\x{74E0}\x{74E2}\x{74E3}\x{74E6}\x{74E7}\x{74E9}\x{74EE}\x{74F0}' .
+'\x{74F1}\x{74F2}\x{74F6}\x{74F7}\x{74F8}\x{7503}\x{7504}\x{7505}\x{750C}' .
+'\x{750D}\x{750E}\x{7511}\x{7513}\x{7515}\x{7518}\x{751A}\x{751C}\x{751E}' .
+'\x{751F}\x{7523}\x{7525}\x{7526}\x{7528}\x{752B}\x{752C}\x{7530}\x{7531}' .
+'\x{7532}\x{7533}\x{7537}\x{7538}\x{753A}\x{753B}\x{753C}\x{7544}\x{7546}' .
+'\x{7549}\x{754A}\x{754B}\x{754C}\x{754D}\x{754F}\x{7551}\x{7554}\x{7559}' .
+'\x{755A}\x{755B}\x{755C}\x{755D}\x{7560}\x{7562}\x{7564}\x{7565}\x{7566}' .
+'\x{7567}\x{7569}\x{756A}\x{756B}\x{756D}\x{7570}\x{7573}\x{7574}\x{7576}' .
+'\x{7577}\x{7578}\x{757F}\x{7582}\x{7586}\x{7587}\x{7589}\x{758A}\x{758B}' .
+'\x{758E}\x{758F}\x{7591}\x{7594}\x{759A}\x{759D}\x{75A3}\x{75A5}\x{75AB}' .
+'\x{75B1}\x{75B2}\x{75B3}\x{75B5}\x{75B8}\x{75B9}\x{75BC}\x{75BD}\x{75BE}' .
+'\x{75C2}\x{75C3}\x{75C5}\x{75C7}\x{75CA}\x{75CD}\x{75D2}\x{75D4}\x{75D5}' .
+'\x{75D8}\x{75D9}\x{75DB}\x{75DE}\x{75E2}\x{75E3}\x{75E9}\x{75F0}\x{75F2}' .
+'\x{75F3}\x{75F4}\x{75FA}\x{75FC}\x{75FE}\x{75FF}\x{7601}\x{7609}\x{760B}' .
+'\x{760D}\x{761F}\x{7620}\x{7621}\x{7622}\x{7624}\x{7627}\x{7630}\x{7634}' .
+'\x{763B}\x{7642}\x{7646}\x{7647}\x{7648}\x{764C}\x{7652}\x{7656}\x{7658}' .
+'\x{765C}\x{7661}\x{7662}\x{7667}\x{7668}\x{7669}\x{766A}\x{766C}\x{7670}' .
+'\x{7672}\x{7676}\x{7678}\x{767A}\x{767B}\x{767C}\x{767D}\x{767E}\x{7680}' .
+'\x{7683}\x{7684}\x{7686}\x{7687}\x{7688}\x{768B}\x{768E}\x{7690}\x{7693}' .
+'\x{7696}\x{7699}\x{769A}\x{76AE}\x{76B0}\x{76B4}\x{76B7}\x{76B8}\x{76B9}' .
+'\x{76BA}\x{76BF}\x{76C2}\x{76C3}\x{76C6}\x{76C8}\x{76CA}\x{76CD}\x{76D2}' .
+'\x{76D6}\x{76D7}\x{76DB}\x{76DC}\x{76DE}\x{76DF}\x{76E1}\x{76E3}\x{76E4}' .
+'\x{76E5}\x{76E7}\x{76EA}\x{76EE}\x{76F2}\x{76F4}\x{76F8}\x{76FB}\x{76FE}' .
+'\x{7701}\x{7704}\x{7707}\x{7708}\x{7709}\x{770B}\x{770C}\x{771B}\x{771E}' .
+'\x{771F}\x{7720}\x{7724}\x{7725}\x{7726}\x{7729}\x{7737}\x{7738}\x{773A}' .
+'\x{773C}\x{7740}\x{7747}\x{775A}\x{775B}\x{7761}\x{7763}\x{7765}\x{7766}' .
+'\x{7768}\x{776B}\x{7779}\x{777E}\x{777F}\x{778B}\x{778E}\x{7791}\x{779E}' .
+'\x{77A0}\x{77A5}\x{77AC}\x{77AD}\x{77B0}\x{77B3}\x{77B6}\x{77B9}\x{77BB}' .
+'\x{77BC}\x{77BD}\x{77BF}\x{77C7}\x{77CD}\x{77D7}\x{77DA}\x{77DB}\x{77DC}' .
+'\x{77E2}\x{77E3}\x{77E5}\x{77E7}\x{77E9}\x{77ED}\x{77EE}\x{77EF}\x{77F3}' .
+'\x{77FC}\x{7802}\x{780C}\x{7812}\x{7814}\x{7815}\x{7820}\x{7825}\x{7826}' .
+'\x{7827}\x{7832}\x{7834}\x{783A}\x{783F}\x{7845}\x{785D}\x{786B}\x{786C}' .
+'\x{786F}\x{7872}\x{7874}\x{787C}\x{7881}\x{7886}\x{7887}\x{788C}\x{788D}' .
+'\x{788E}\x{7891}\x{7893}\x{7895}\x{7897}\x{789A}\x{78A3}\x{78A7}\x{78A9}' .
+'\x{78AA}\x{78AF}\x{78B5}\x{78BA}\x{78BC}\x{78BE}\x{78C1}\x{78C5}\x{78C6}' .
+'\x{78CA}\x{78CB}\x{78D0}\x{78D1}\x{78D4}\x{78DA}\x{78E7}\x{78E8}\x{78EC}' .
+'\x{78EF}\x{78F4}\x{78FD}\x{7901}\x{7907}\x{790E}\x{7911}\x{7912}\x{7919}' .
+'\x{7926}\x{792A}\x{792B}\x{792C}\x{793A}\x{793C}\x{793E}\x{7940}\x{7941}' .
+'\x{7947}\x{7948}\x{7949}\x{7950}\x{7953}\x{7955}\x{7956}\x{7957}\x{795A}' .
+'\x{795D}\x{795E}\x{795F}\x{7960}\x{7962}\x{7965}\x{7968}\x{796D}\x{7977}' .
+'\x{797A}\x{797F}\x{7980}\x{7981}\x{7984}\x{7985}\x{798A}\x{798D}\x{798E}' .
+'\x{798F}\x{799D}\x{79A6}\x{79A7}\x{79AA}\x{79AE}\x{79B0}\x{79B3}\x{79B9}' .
+'\x{79BA}\x{79BD}\x{79BE}\x{79BF}\x{79C0}\x{79C1}\x{79C9}\x{79CB}\x{79D1}' .
+'\x{79D2}\x{79D5}\x{79D8}\x{79DF}\x{79E1}\x{79E3}\x{79E4}\x{79E6}\x{79E7}' .
+'\x{79E9}\x{79EC}\x{79F0}\x{79FB}\x{7A00}\x{7A08}\x{7A0B}\x{7A0D}\x{7A0E}' .
+'\x{7A14}\x{7A17}\x{7A18}\x{7A19}\x{7A1A}\x{7A1C}\x{7A1F}\x{7A20}\x{7A2E}' .
+'\x{7A31}\x{7A32}\x{7A37}\x{7A3B}\x{7A3C}\x{7A3D}\x{7A3E}\x{7A3F}\x{7A40}' .
+'\x{7A42}\x{7A43}\x{7A46}\x{7A49}\x{7A4D}\x{7A4E}\x{7A4F}\x{7A50}\x{7A57}' .
+'\x{7A61}\x{7A62}\x{7A63}\x{7A69}\x{7A6B}\x{7A70}\x{7A74}\x{7A76}\x{7A79}' .
+'\x{7A7A}\x{7A7D}\x{7A7F}\x{7A81}\x{7A83}\x{7A84}\x{7A88}\x{7A92}\x{7A93}' .
+'\x{7A95}\x{7A96}\x{7A97}\x{7A98}\x{7A9F}\x{7AA9}\x{7AAA}\x{7AAE}\x{7AAF}' .
+'\x{7AB0}\x{7AB6}\x{7ABA}\x{7ABF}\x{7AC3}\x{7AC4}\x{7AC5}\x{7AC7}\x{7AC8}' .
+'\x{7ACA}\x{7ACB}\x{7ACD}\x{7ACF}\x{7AD2}\x{7AD3}\x{7AD5}\x{7AD9}\x{7ADA}' .
+'\x{7ADC}\x{7ADD}\x{7ADF}\x{7AE0}\x{7AE1}\x{7AE2}\x{7AE3}\x{7AE5}\x{7AE6}' .
+'\x{7AEA}\x{7AED}\x{7AEF}\x{7AF0}\x{7AF6}\x{7AF8}\x{7AF9}\x{7AFA}\x{7AFF}' .
+'\x{7B02}\x{7B04}\x{7B06}\x{7B08}\x{7B0A}\x{7B0B}\x{7B0F}\x{7B11}\x{7B18}' .
+'\x{7B19}\x{7B1B}\x{7B1E}\x{7B20}\x{7B25}\x{7B26}\x{7B28}\x{7B2C}\x{7B33}' .
+'\x{7B35}\x{7B36}\x{7B39}\x{7B45}\x{7B46}\x{7B48}\x{7B49}\x{7B4B}\x{7B4C}' .
+'\x{7B4D}\x{7B4F}\x{7B50}\x{7B51}\x{7B52}\x{7B54}\x{7B56}\x{7B5D}\x{7B65}' .
+'\x{7B67}\x{7B6C}\x{7B6E}\x{7B70}\x{7B71}\x{7B74}\x{7B75}\x{7B7A}\x{7B86}' .
+'\x{7B87}\x{7B8B}\x{7B8D}\x{7B8F}\x{7B92}\x{7B94}\x{7B95}\x{7B97}\x{7B98}' .
+'\x{7B99}\x{7B9A}\x{7B9C}\x{7B9D}\x{7B9F}\x{7BA1}\x{7BAA}\x{7BAD}\x{7BB1}' .
+'\x{7BB4}\x{7BB8}\x{7BC0}\x{7BC1}\x{7BC4}\x{7BC6}\x{7BC7}\x{7BC9}\x{7BCB}' .
+'\x{7BCC}\x{7BCF}\x{7BDD}\x{7BE0}\x{7BE4}\x{7BE5}\x{7BE6}\x{7BE9}\x{7BED}' .
+'\x{7BF3}\x{7BF6}\x{7BF7}\x{7C00}\x{7C07}\x{7C0D}\x{7C11}\x{7C12}\x{7C13}' .
+'\x{7C14}\x{7C17}\x{7C1F}\x{7C21}\x{7C23}\x{7C27}\x{7C2A}\x{7C2B}\x{7C37}' .
+'\x{7C38}\x{7C3D}\x{7C3E}\x{7C3F}\x{7C40}\x{7C43}\x{7C4C}\x{7C4D}\x{7C4F}' .
+'\x{7C50}\x{7C54}\x{7C56}\x{7C58}\x{7C5F}\x{7C60}\x{7C64}\x{7C65}\x{7C6C}' .
+'\x{7C73}\x{7C75}\x{7C7E}\x{7C81}\x{7C82}\x{7C83}\x{7C89}\x{7C8B}\x{7C8D}' .
+'\x{7C90}\x{7C92}\x{7C95}\x{7C97}\x{7C98}\x{7C9B}\x{7C9F}\x{7CA1}\x{7CA2}' .
+'\x{7CA4}\x{7CA5}\x{7CA7}\x{7CA8}\x{7CAB}\x{7CAD}\x{7CAE}\x{7CB1}\x{7CB2}' .
+'\x{7CB3}\x{7CB9}\x{7CBD}\x{7CBE}\x{7CC0}\x{7CC2}\x{7CC5}\x{7CCA}\x{7CCE}' .
+'\x{7CD2}\x{7CD6}\x{7CD8}\x{7CDC}\x{7CDE}\x{7CDF}\x{7CE0}\x{7CE2}\x{7CE7}' .
+'\x{7CEF}\x{7CF2}\x{7CF4}\x{7CF6}\x{7CF8}\x{7CFA}\x{7CFB}\x{7CFE}\x{7D00}' .
+'\x{7D02}\x{7D04}\x{7D05}\x{7D06}\x{7D0A}\x{7D0B}\x{7D0D}\x{7D10}\x{7D14}' .
+'\x{7D15}\x{7D17}\x{7D18}\x{7D19}\x{7D1A}\x{7D1B}\x{7D1C}\x{7D20}\x{7D21}' .
+'\x{7D22}\x{7D2B}\x{7D2C}\x{7D2E}\x{7D2F}\x{7D30}\x{7D32}\x{7D33}\x{7D35}' .
+'\x{7D39}\x{7D3A}\x{7D3F}\x{7D42}\x{7D43}\x{7D44}\x{7D45}\x{7D46}\x{7D4B}' .
+'\x{7D4C}\x{7D4E}\x{7D4F}\x{7D50}\x{7D56}\x{7D5B}\x{7D5E}\x{7D61}\x{7D62}' .
+'\x{7D63}\x{7D66}\x{7D68}\x{7D6E}\x{7D71}\x{7D72}\x{7D73}\x{7D75}\x{7D76}' .
+'\x{7D79}\x{7D7D}\x{7D89}\x{7D8F}\x{7D93}\x{7D99}\x{7D9A}\x{7D9B}\x{7D9C}' .
+'\x{7D9F}\x{7DA2}\x{7DA3}\x{7DAB}\x{7DAC}\x{7DAD}\x{7DAE}\x{7DAF}\x{7DB0}' .
+'\x{7DB1}\x{7DB2}\x{7DB4}\x{7DB5}\x{7DB8}\x{7DBA}\x{7DBB}\x{7DBD}\x{7DBE}' .
+'\x{7DBF}\x{7DC7}\x{7DCA}\x{7DCB}\x{7DCF}\x{7DD1}\x{7DD2}\x{7DD5}\x{7DD8}' .
+'\x{7DDA}\x{7DDC}\x{7DDD}\x{7DDE}\x{7DE0}\x{7DE1}\x{7DE4}\x{7DE8}\x{7DE9}' .
+'\x{7DEC}\x{7DEF}\x{7DF2}\x{7DF4}\x{7DFB}\x{7E01}\x{7E04}\x{7E05}\x{7E09}' .
+'\x{7E0A}\x{7E0B}\x{7E12}\x{7E1B}\x{7E1E}\x{7E1F}\x{7E21}\x{7E22}\x{7E23}' .
+'\x{7E26}\x{7E2B}\x{7E2E}\x{7E31}\x{7E32}\x{7E35}\x{7E37}\x{7E39}\x{7E3A}' .
+'\x{7E3B}\x{7E3D}\x{7E3E}\x{7E41}\x{7E43}\x{7E46}\x{7E4A}\x{7E4B}\x{7E4D}' .
+'\x{7E54}\x{7E55}\x{7E56}\x{7E59}\x{7E5A}\x{7E5D}\x{7E5E}\x{7E66}\x{7E67}' .
+'\x{7E69}\x{7E6A}\x{7E6D}\x{7E70}\x{7E79}\x{7E7B}\x{7E7C}\x{7E7D}\x{7E7F}' .
+'\x{7E82}\x{7E83}\x{7E88}\x{7E89}\x{7E8C}\x{7E8E}\x{7E8F}\x{7E90}\x{7E92}' .
+'\x{7E93}\x{7E94}\x{7E96}\x{7E9B}\x{7E9C}\x{7F36}\x{7F38}\x{7F3A}\x{7F45}' .
+'\x{7F4C}\x{7F4D}\x{7F4E}\x{7F50}\x{7F51}\x{7F54}\x{7F55}\x{7F58}\x{7F5F}' .
+'\x{7F60}\x{7F67}\x{7F68}\x{7F69}\x{7F6A}\x{7F6B}\x{7F6E}\x{7F70}\x{7F72}' .
+'\x{7F75}\x{7F77}\x{7F78}\x{7F79}\x{7F82}\x{7F83}\x{7F85}\x{7F86}\x{7F87}' .
+'\x{7F88}\x{7F8A}\x{7F8C}\x{7F8E}\x{7F94}\x{7F9A}\x{7F9D}\x{7F9E}\x{7FA3}' .
+'\x{7FA4}\x{7FA8}\x{7FA9}\x{7FAE}\x{7FAF}\x{7FB2}\x{7FB6}\x{7FB8}\x{7FB9}' .
+'\x{7FBD}\x{7FC1}\x{7FC5}\x{7FC6}\x{7FCA}\x{7FCC}\x{7FD2}\x{7FD4}\x{7FD5}' .
+'\x{7FE0}\x{7FE1}\x{7FE6}\x{7FE9}\x{7FEB}\x{7FF0}\x{7FF3}\x{7FF9}\x{7FFB}' .
+'\x{7FFC}\x{8000}\x{8001}\x{8003}\x{8004}\x{8005}\x{8006}\x{800B}\x{800C}' .
+'\x{8010}\x{8012}\x{8015}\x{8017}\x{8018}\x{8019}\x{801C}\x{8021}\x{8028}' .
+'\x{8033}\x{8036}\x{803B}\x{803D}\x{803F}\x{8046}\x{804A}\x{8052}\x{8056}' .
+'\x{8058}\x{805A}\x{805E}\x{805F}\x{8061}\x{8062}\x{8068}\x{806F}\x{8070}' .
+'\x{8072}\x{8073}\x{8074}\x{8076}\x{8077}\x{8079}\x{807D}\x{807E}\x{807F}' .
+'\x{8084}\x{8085}\x{8086}\x{8087}\x{8089}\x{808B}\x{808C}\x{8093}\x{8096}' .
+'\x{8098}\x{809A}\x{809B}\x{809D}\x{80A1}\x{80A2}\x{80A5}\x{80A9}\x{80AA}' .
+'\x{80AC}\x{80AD}\x{80AF}\x{80B1}\x{80B2}\x{80B4}\x{80BA}\x{80C3}\x{80C4}' .
+'\x{80C6}\x{80CC}\x{80CE}\x{80D6}\x{80D9}\x{80DA}\x{80DB}\x{80DD}\x{80DE}' .
+'\x{80E1}\x{80E4}\x{80E5}\x{80EF}\x{80F1}\x{80F4}\x{80F8}\x{80FC}\x{80FD}' .
+'\x{8102}\x{8105}\x{8106}\x{8107}\x{8108}\x{8109}\x{810A}\x{811A}\x{811B}' .
+'\x{8123}\x{8129}\x{812F}\x{8131}\x{8133}\x{8139}\x{813E}\x{8146}\x{814B}' .
+'\x{814E}\x{8150}\x{8151}\x{8153}\x{8154}\x{8155}\x{815F}\x{8165}\x{8166}' .
+'\x{816B}\x{816E}\x{8170}\x{8171}\x{8174}\x{8178}\x{8179}\x{817A}\x{817F}' .
+'\x{8180}\x{8182}\x{8183}\x{8188}\x{818A}\x{818F}\x{8193}\x{8195}\x{819A}' .
+'\x{819C}\x{819D}\x{81A0}\x{81A3}\x{81A4}\x{81A8}\x{81A9}\x{81B0}\x{81B3}' .
+'\x{81B5}\x{81B8}\x{81BA}\x{81BD}\x{81BE}\x{81BF}\x{81C0}\x{81C2}\x{81C6}' .
+'\x{81C8}\x{81C9}\x{81CD}\x{81D1}\x{81D3}\x{81D8}\x{81D9}\x{81DA}\x{81DF}' .
+'\x{81E0}\x{81E3}\x{81E5}\x{81E7}\x{81E8}\x{81EA}\x{81ED}\x{81F3}\x{81F4}' .
+'\x{81FA}\x{81FB}\x{81FC}\x{81FE}\x{8201}\x{8202}\x{8205}\x{8207}\x{8208}' .
+'\x{8209}\x{820A}\x{820C}\x{820D}\x{820E}\x{8210}\x{8212}\x{8216}\x{8217}' .
+'\x{8218}\x{821B}\x{821C}\x{821E}\x{821F}\x{8229}\x{822A}\x{822B}\x{822C}' .
+'\x{822E}\x{8233}\x{8235}\x{8236}\x{8237}\x{8238}\x{8239}\x{8240}\x{8247}' .
+'\x{8258}\x{8259}\x{825A}\x{825D}\x{825F}\x{8262}\x{8264}\x{8266}\x{8268}' .
+'\x{826A}\x{826B}\x{826E}\x{826F}\x{8271}\x{8272}\x{8276}\x{8277}\x{8278}' .
+'\x{827E}\x{828B}\x{828D}\x{8292}\x{8299}\x{829D}\x{829F}\x{82A5}\x{82A6}' .
+'\x{82AB}\x{82AC}\x{82AD}\x{82AF}\x{82B1}\x{82B3}\x{82B8}\x{82B9}\x{82BB}' .
+'\x{82BD}\x{82C5}\x{82D1}\x{82D2}\x{82D3}\x{82D4}\x{82D7}\x{82D9}\x{82DB}' .
+'\x{82DC}\x{82DE}\x{82DF}\x{82E1}\x{82E3}\x{82E5}\x{82E6}\x{82E7}\x{82EB}' .
+'\x{82F1}\x{82F3}\x{82F4}\x{82F9}\x{82FA}\x{82FB}\x{8302}\x{8303}\x{8304}' .
+'\x{8305}\x{8306}\x{8309}\x{830E}\x{8316}\x{8317}\x{8318}\x{831C}\x{8323}' .
+'\x{8328}\x{832B}\x{832F}\x{8331}\x{8332}\x{8334}\x{8335}\x{8336}\x{8338}' .
+'\x{8339}\x{8340}\x{8345}\x{8349}\x{834A}\x{834F}\x{8350}\x{8352}\x{8358}' .
+'\x{8373}\x{8375}\x{8377}\x{837B}\x{837C}\x{8385}\x{8387}\x{8389}\x{838A}' .
+'\x{838E}\x{8393}\x{8396}\x{839A}\x{839E}\x{839F}\x{83A0}\x{83A2}\x{83A8}' .
+'\x{83AA}\x{83AB}\x{83B1}\x{83B5}\x{83BD}\x{83C1}\x{83C5}\x{83CA}\x{83CC}' .
+'\x{83CE}\x{83D3}\x{83D6}\x{83D8}\x{83DC}\x{83DF}\x{83E0}\x{83E9}\x{83EB}' .
+'\x{83EF}\x{83F0}\x{83F1}\x{83F2}\x{83F4}\x{83F7}\x{83FB}\x{83FD}\x{8403}' .
+'\x{8404}\x{8407}\x{840B}\x{840C}\x{840D}\x{840E}\x{8413}\x{8420}\x{8422}' .
+'\x{8429}\x{842A}\x{842C}\x{8431}\x{8435}\x{8438}\x{843C}\x{843D}\x{8446}' .
+'\x{8449}\x{844E}\x{8457}\x{845B}\x{8461}\x{8462}\x{8463}\x{8466}\x{8469}' .
+'\x{846B}\x{846C}\x{846D}\x{846E}\x{846F}\x{8471}\x{8475}\x{8477}\x{8479}' .
+'\x{847A}\x{8482}\x{8484}\x{848B}\x{8490}\x{8494}\x{8499}\x{849C}\x{849F}' .
+'\x{84A1}\x{84AD}\x{84B2}\x{84B8}\x{84B9}\x{84BB}\x{84BC}\x{84BF}\x{84C1}' .
+'\x{84C4}\x{84C6}\x{84C9}\x{84CA}\x{84CB}\x{84CD}\x{84D0}\x{84D1}\x{84D6}' .
+'\x{84D9}\x{84DA}\x{84EC}\x{84EE}\x{84F4}\x{84FC}\x{84FF}\x{8500}\x{8506}' .
+'\x{8511}\x{8513}\x{8514}\x{8515}\x{8517}\x{8518}\x{851A}\x{851F}\x{8521}' .
+'\x{8526}\x{852C}\x{852D}\x{8535}\x{853D}\x{8540}\x{8541}\x{8543}\x{8548}' .
+'\x{8549}\x{854A}\x{854B}\x{854E}\x{8555}\x{8557}\x{8558}\x{855A}\x{8563}' .
+'\x{8568}\x{8569}\x{856A}\x{856D}\x{8577}\x{857E}\x{8580}\x{8584}\x{8587}' .
+'\x{8588}\x{858A}\x{8590}\x{8591}\x{8594}\x{8597}\x{8599}\x{859B}\x{859C}' .
+'\x{85A4}\x{85A6}\x{85A8}\x{85A9}\x{85AA}\x{85AB}\x{85AC}\x{85AE}\x{85AF}' .
+'\x{85B9}\x{85BA}\x{85C1}\x{85C9}\x{85CD}\x{85CF}\x{85D0}\x{85D5}\x{85DC}' .
+'\x{85DD}\x{85E4}\x{85E5}\x{85E9}\x{85EA}\x{85F7}\x{85F9}\x{85FA}\x{85FB}' .
+'\x{85FE}\x{8602}\x{8606}\x{8607}\x{860A}\x{860B}\x{8613}\x{8616}\x{8617}' .
+'\x{861A}\x{8622}\x{862D}\x{862F}\x{8630}\x{863F}\x{864D}\x{864E}\x{8650}' .
+'\x{8654}\x{8655}\x{865A}\x{865C}\x{865E}\x{865F}\x{8667}\x{866B}\x{8671}' .
+'\x{8679}\x{867B}\x{868A}\x{868B}\x{868C}\x{8693}\x{8695}\x{86A3}\x{86A4}' .
+'\x{86A9}\x{86AA}\x{86AB}\x{86AF}\x{86B0}\x{86B6}\x{86C4}\x{86C6}\x{86C7}' .
+'\x{86C9}\x{86CB}\x{86CD}\x{86CE}\x{86D4}\x{86D9}\x{86DB}\x{86DE}\x{86DF}' .
+'\x{86E4}\x{86E9}\x{86EC}\x{86ED}\x{86EE}\x{86EF}\x{86F8}\x{86F9}\x{86FB}' .
+'\x{86FE}\x{8700}\x{8702}\x{8703}\x{8706}\x{8708}\x{8709}\x{870A}\x{870D}' .
+'\x{8711}\x{8712}\x{8718}\x{871A}\x{871C}\x{8725}\x{8729}\x{8734}\x{8737}' .
+'\x{873B}\x{873F}\x{8749}\x{874B}\x{874C}\x{874E}\x{8753}\x{8755}\x{8757}' .
+'\x{8759}\x{875F}\x{8760}\x{8763}\x{8766}\x{8768}\x{876A}\x{876E}\x{8774}' .
+'\x{8776}\x{8778}\x{877F}\x{8782}\x{878D}\x{879F}\x{87A2}\x{87AB}\x{87AF}' .
+'\x{87B3}\x{87BA}\x{87BB}\x{87BD}\x{87C0}\x{87C4}\x{87C6}\x{87C7}\x{87CB}' .
+'\x{87D0}\x{87D2}\x{87E0}\x{87EF}\x{87F2}\x{87F6}\x{87F7}\x{87F9}\x{87FB}' .
+'\x{87FE}\x{8805}\x{880D}\x{880E}\x{880F}\x{8811}\x{8815}\x{8816}\x{8821}' .
+'\x{8822}\x{8823}\x{8827}\x{8831}\x{8836}\x{8839}\x{883B}\x{8840}\x{8842}' .
+'\x{8844}\x{8846}\x{884C}\x{884D}\x{8852}\x{8853}\x{8857}\x{8859}\x{885B}' .
+'\x{885D}\x{885E}\x{8861}\x{8862}\x{8863}\x{8868}\x{886B}\x{8870}\x{8872}' .
+'\x{8875}\x{8877}\x{887D}\x{887E}\x{887F}\x{8881}\x{8882}\x{8888}\x{888B}' .
+'\x{888D}\x{8892}\x{8896}\x{8897}\x{8899}\x{889E}\x{88A2}\x{88A4}\x{88AB}' .
+'\x{88AE}\x{88B0}\x{88B1}\x{88B4}\x{88B5}\x{88B7}\x{88BF}\x{88C1}\x{88C2}' .
+'\x{88C3}\x{88C4}\x{88C5}\x{88CF}\x{88D4}\x{88D5}\x{88D8}\x{88D9}\x{88DC}' .
+'\x{88DD}\x{88DF}\x{88E1}\x{88E8}\x{88F2}\x{88F3}\x{88F4}\x{88F8}\x{88F9}' .
+'\x{88FC}\x{88FD}\x{88FE}\x{8902}\x{8904}\x{8907}\x{890A}\x{890C}\x{8910}' .
+'\x{8912}\x{8913}\x{891D}\x{891E}\x{8925}\x{892A}\x{892B}\x{8936}\x{8938}' .
+'\x{893B}\x{8941}\x{8943}\x{8944}\x{894C}\x{894D}\x{8956}\x{895E}\x{895F}' .
+'\x{8960}\x{8964}\x{8966}\x{896A}\x{896D}\x{896F}\x{8972}\x{8974}\x{8977}' .
+'\x{897E}\x{897F}\x{8981}\x{8983}\x{8986}\x{8987}\x{8988}\x{898A}\x{898B}' .
+'\x{898F}\x{8993}\x{8996}\x{8997}\x{8998}\x{899A}\x{89A1}\x{89A6}\x{89A7}' .
+'\x{89A9}\x{89AA}\x{89AC}\x{89AF}\x{89B2}\x{89B3}\x{89BA}\x{89BD}\x{89BF}' .
+'\x{89C0}\x{89D2}\x{89DA}\x{89DC}\x{89DD}\x{89E3}\x{89E6}\x{89E7}\x{89F4}' .
+'\x{89F8}\x{8A00}\x{8A02}\x{8A03}\x{8A08}\x{8A0A}\x{8A0C}\x{8A0E}\x{8A10}' .
+'\x{8A13}\x{8A16}\x{8A17}\x{8A18}\x{8A1B}\x{8A1D}\x{8A1F}\x{8A23}\x{8A25}' .
+'\x{8A2A}\x{8A2D}\x{8A31}\x{8A33}\x{8A34}\x{8A36}\x{8A3A}\x{8A3B}\x{8A3C}' .
+'\x{8A41}\x{8A46}\x{8A48}\x{8A50}\x{8A51}\x{8A52}\x{8A54}\x{8A55}\x{8A5B}' .
+'\x{8A5E}\x{8A60}\x{8A62}\x{8A63}\x{8A66}\x{8A69}\x{8A6B}\x{8A6C}\x{8A6D}' .
+'\x{8A6E}\x{8A70}\x{8A71}\x{8A72}\x{8A73}\x{8A7C}\x{8A82}\x{8A84}\x{8A85}' .
+'\x{8A87}\x{8A89}\x{8A8C}\x{8A8D}\x{8A91}\x{8A93}\x{8A95}\x{8A98}\x{8A9A}' .
+'\x{8A9E}\x{8AA0}\x{8AA1}\x{8AA3}\x{8AA4}\x{8AA5}\x{8AA6}\x{8AA8}\x{8AAC}' .
+'\x{8AAD}\x{8AB0}\x{8AB2}\x{8AB9}\x{8ABC}\x{8ABF}\x{8AC2}\x{8AC4}\x{8AC7}' .
+'\x{8ACB}\x{8ACC}\x{8ACD}\x{8ACF}\x{8AD2}\x{8AD6}\x{8ADA}\x{8ADB}\x{8ADC}' .
+'\x{8ADE}\x{8AE0}\x{8AE1}\x{8AE2}\x{8AE4}\x{8AE6}\x{8AE7}\x{8AEB}\x{8AED}' .
+'\x{8AEE}\x{8AF1}\x{8AF3}\x{8AF7}\x{8AF8}\x{8AFA}\x{8AFE}\x{8B00}\x{8B01}' .
+'\x{8B02}\x{8B04}\x{8B07}\x{8B0C}\x{8B0E}\x{8B10}\x{8B14}\x{8B16}\x{8B17}' .
+'\x{8B19}\x{8B1A}\x{8B1B}\x{8B1D}\x{8B20}\x{8B21}\x{8B26}\x{8B28}\x{8B2B}' .
+'\x{8B2C}\x{8B33}\x{8B39}\x{8B3E}\x{8B41}\x{8B49}\x{8B4C}\x{8B4E}\x{8B4F}' .
+'\x{8B56}\x{8B58}\x{8B5A}\x{8B5B}\x{8B5C}\x{8B5F}\x{8B66}\x{8B6B}\x{8B6C}' .
+'\x{8B6F}\x{8B70}\x{8B71}\x{8B72}\x{8B74}\x{8B77}\x{8B7D}\x{8B80}\x{8B83}' .
+'\x{8B8A}\x{8B8C}\x{8B8E}\x{8B90}\x{8B92}\x{8B93}\x{8B96}\x{8B99}\x{8B9A}' .
+'\x{8C37}\x{8C3A}\x{8C3F}\x{8C41}\x{8C46}\x{8C48}\x{8C4A}\x{8C4C}\x{8C4E}' .
+'\x{8C50}\x{8C55}\x{8C5A}\x{8C61}\x{8C62}\x{8C6A}\x{8C6B}\x{8C6C}\x{8C78}' .
+'\x{8C79}\x{8C7A}\x{8C7C}\x{8C82}\x{8C85}\x{8C89}\x{8C8A}\x{8C8C}\x{8C8D}' .
+'\x{8C8E}\x{8C94}\x{8C98}\x{8C9D}\x{8C9E}\x{8CA0}\x{8CA1}\x{8CA2}\x{8CA7}' .
+'\x{8CA8}\x{8CA9}\x{8CAA}\x{8CAB}\x{8CAC}\x{8CAD}\x{8CAE}\x{8CAF}\x{8CB0}' .
+'\x{8CB2}\x{8CB3}\x{8CB4}\x{8CB6}\x{8CB7}\x{8CB8}\x{8CBB}\x{8CBC}\x{8CBD}' .
+'\x{8CBF}\x{8CC0}\x{8CC1}\x{8CC2}\x{8CC3}\x{8CC4}\x{8CC7}\x{8CC8}\x{8CCA}' .
+'\x{8CCD}\x{8CCE}\x{8CD1}\x{8CD3}\x{8CDA}\x{8CDB}\x{8CDC}\x{8CDE}\x{8CE0}' .
+'\x{8CE2}\x{8CE3}\x{8CE4}\x{8CE6}\x{8CEA}\x{8CED}\x{8CFA}\x{8CFB}\x{8CFC}' .
+'\x{8CFD}\x{8D04}\x{8D05}\x{8D07}\x{8D08}\x{8D0A}\x{8D0B}\x{8D0D}\x{8D0F}' .
+'\x{8D10}\x{8D13}\x{8D14}\x{8D16}\x{8D64}\x{8D66}\x{8D67}\x{8D6B}\x{8D6D}' .
+'\x{8D70}\x{8D71}\x{8D73}\x{8D74}\x{8D77}\x{8D81}\x{8D85}\x{8D8A}\x{8D99}' .
+'\x{8DA3}\x{8DA8}\x{8DB3}\x{8DBA}\x{8DBE}\x{8DC2}\x{8DCB}\x{8DCC}\x{8DCF}' .
+'\x{8DD6}\x{8DDA}\x{8DDB}\x{8DDD}\x{8DDF}\x{8DE1}\x{8DE3}\x{8DE8}\x{8DEA}' .
+'\x{8DEB}\x{8DEF}\x{8DF3}\x{8DF5}\x{8DFC}\x{8DFF}\x{8E08}\x{8E09}\x{8E0A}' .
+'\x{8E0F}\x{8E10}\x{8E1D}\x{8E1E}\x{8E1F}\x{8E2A}\x{8E30}\x{8E34}\x{8E35}' .
+'\x{8E42}\x{8E44}\x{8E47}\x{8E48}\x{8E49}\x{8E4A}\x{8E4C}\x{8E50}\x{8E55}' .
+'\x{8E59}\x{8E5F}\x{8E60}\x{8E63}\x{8E64}\x{8E72}\x{8E74}\x{8E76}\x{8E7C}' .
+'\x{8E81}\x{8E84}\x{8E85}\x{8E87}\x{8E8A}\x{8E8B}\x{8E8D}\x{8E91}\x{8E93}' .
+'\x{8E94}\x{8E99}\x{8EA1}\x{8EAA}\x{8EAB}\x{8EAC}\x{8EAF}\x{8EB0}\x{8EB1}' .
+'\x{8EBE}\x{8EC5}\x{8EC6}\x{8EC8}\x{8ECA}\x{8ECB}\x{8ECC}\x{8ECD}\x{8ED2}' .
+'\x{8EDB}\x{8EDF}\x{8EE2}\x{8EE3}\x{8EEB}\x{8EF8}\x{8EFB}\x{8EFC}\x{8EFD}' .
+'\x{8EFE}\x{8F03}\x{8F05}\x{8F09}\x{8F0A}\x{8F0C}\x{8F12}\x{8F13}\x{8F14}' .
+'\x{8F15}\x{8F19}\x{8F1B}\x{8F1C}\x{8F1D}\x{8F1F}\x{8F26}\x{8F29}\x{8F2A}' .
+'\x{8F2F}\x{8F33}\x{8F38}\x{8F39}\x{8F3B}\x{8F3E}\x{8F3F}\x{8F42}\x{8F44}' .
+'\x{8F45}\x{8F46}\x{8F49}\x{8F4C}\x{8F4D}\x{8F4E}\x{8F57}\x{8F5C}\x{8F5F}' .
+'\x{8F61}\x{8F62}\x{8F63}\x{8F64}\x{8F9B}\x{8F9C}\x{8F9E}\x{8F9F}\x{8FA3}' .
+'\x{8FA7}\x{8FA8}\x{8FAD}\x{8FAE}\x{8FAF}\x{8FB0}\x{8FB1}\x{8FB2}\x{8FB7}' .
+'\x{8FBA}\x{8FBB}\x{8FBC}\x{8FBF}\x{8FC2}\x{8FC4}\x{8FC5}\x{8FCE}\x{8FD1}' .
+'\x{8FD4}\x{8FDA}\x{8FE2}\x{8FE5}\x{8FE6}\x{8FE9}\x{8FEA}\x{8FEB}\x{8FED}' .
+'\x{8FEF}\x{8FF0}\x{8FF4}\x{8FF7}\x{8FF8}\x{8FF9}\x{8FFA}\x{8FFD}\x{9000}' .
+'\x{9001}\x{9003}\x{9005}\x{9006}\x{900B}\x{900D}\x{900E}\x{900F}\x{9010}' .
+'\x{9011}\x{9013}\x{9014}\x{9015}\x{9016}\x{9017}\x{9019}\x{901A}\x{901D}' .
+'\x{901E}\x{901F}\x{9020}\x{9021}\x{9022}\x{9023}\x{9027}\x{902E}\x{9031}' .
+'\x{9032}\x{9035}\x{9036}\x{9038}\x{9039}\x{903C}\x{903E}\x{9041}\x{9042}' .
+'\x{9045}\x{9047}\x{9049}\x{904A}\x{904B}\x{904D}\x{904E}\x{904F}\x{9050}' .
+'\x{9051}\x{9052}\x{9053}\x{9054}\x{9055}\x{9056}\x{9058}\x{9059}\x{905C}' .
+'\x{905E}\x{9060}\x{9061}\x{9063}\x{9065}\x{9068}\x{9069}\x{906D}\x{906E}' .
+'\x{906F}\x{9072}\x{9075}\x{9076}\x{9077}\x{9078}\x{907A}\x{907C}\x{907D}' .
+'\x{907F}\x{9080}\x{9081}\x{9082}\x{9083}\x{9084}\x{9087}\x{9089}\x{908A}' .
+'\x{908F}\x{9091}\x{90A3}\x{90A6}\x{90A8}\x{90AA}\x{90AF}\x{90B1}\x{90B5}' .
+'\x{90B8}\x{90C1}\x{90CA}\x{90CE}\x{90DB}\x{90E1}\x{90E2}\x{90E4}\x{90E8}' .
+'\x{90ED}\x{90F5}\x{90F7}\x{90FD}\x{9102}\x{9112}\x{9119}\x{912D}\x{9130}' .
+'\x{9132}\x{9149}\x{914A}\x{914B}\x{914C}\x{914D}\x{914E}\x{9152}\x{9154}' .
+'\x{9156}\x{9158}\x{9162}\x{9163}\x{9165}\x{9169}\x{916A}\x{916C}\x{9172}' .
+'\x{9173}\x{9175}\x{9177}\x{9178}\x{9182}\x{9187}\x{9189}\x{918B}\x{918D}' .
+'\x{9190}\x{9192}\x{9197}\x{919C}\x{91A2}\x{91A4}\x{91AA}\x{91AB}\x{91AF}' .
+'\x{91B4}\x{91B5}\x{91B8}\x{91BA}\x{91C0}\x{91C1}\x{91C6}\x{91C7}\x{91C8}' .
+'\x{91C9}\x{91CB}\x{91CC}\x{91CD}\x{91CE}\x{91CF}\x{91D0}\x{91D1}\x{91D6}' .
+'\x{91D8}\x{91DB}\x{91DC}\x{91DD}\x{91DF}\x{91E1}\x{91E3}\x{91E6}\x{91E7}' .
+'\x{91F5}\x{91F6}\x{91FC}\x{91FF}\x{920D}\x{920E}\x{9211}\x{9214}\x{9215}' .
+'\x{921E}\x{9229}\x{922C}\x{9234}\x{9237}\x{923F}\x{9244}\x{9245}\x{9248}' .
+'\x{9249}\x{924B}\x{9250}\x{9257}\x{925A}\x{925B}\x{925E}\x{9262}\x{9264}' .
+'\x{9266}\x{9271}\x{927E}\x{9280}\x{9283}\x{9285}\x{9291}\x{9293}\x{9295}' .
+'\x{9296}\x{9298}\x{929A}\x{929B}\x{929C}\x{92AD}\x{92B7}\x{92B9}\x{92CF}' .
+'\x{92D2}\x{92E4}\x{92E9}\x{92EA}\x{92ED}\x{92F2}\x{92F3}\x{92F8}\x{92FA}' .
+'\x{92FC}\x{9306}\x{930F}\x{9310}\x{9318}\x{9319}\x{931A}\x{9320}\x{9322}' .
+'\x{9323}\x{9326}\x{9328}\x{932B}\x{932C}\x{932E}\x{932F}\x{9332}\x{9335}' .
+'\x{933A}\x{933B}\x{9344}\x{934B}\x{934D}\x{9354}\x{9356}\x{935B}\x{935C}' .
+'\x{9360}\x{936C}\x{936E}\x{9375}\x{937C}\x{937E}\x{938C}\x{9394}\x{9396}' .
+'\x{9397}\x{939A}\x{93A7}\x{93AC}\x{93AD}\x{93AE}\x{93B0}\x{93B9}\x{93C3}' .
+'\x{93C8}\x{93D0}\x{93D1}\x{93D6}\x{93D7}\x{93D8}\x{93DD}\x{93E1}\x{93E4}' .
+'\x{93E5}\x{93E8}\x{9403}\x{9407}\x{9410}\x{9413}\x{9414}\x{9418}\x{9419}' .
+'\x{941A}\x{9421}\x{942B}\x{9435}\x{9436}\x{9438}\x{943A}\x{9441}\x{9444}' .
+'\x{9451}\x{9452}\x{9453}\x{945A}\x{945B}\x{945E}\x{9460}\x{9462}\x{946A}' .
+'\x{9470}\x{9475}\x{9477}\x{947C}\x{947D}\x{947E}\x{947F}\x{9481}\x{9577}' .
+'\x{9580}\x{9582}\x{9583}\x{9587}\x{9589}\x{958A}\x{958B}\x{958F}\x{9591}' .
+'\x{9593}\x{9594}\x{9596}\x{9598}\x{9599}\x{95A0}\x{95A2}\x{95A3}\x{95A4}' .
+'\x{95A5}\x{95A7}\x{95A8}\x{95AD}\x{95B2}\x{95B9}\x{95BB}\x{95BC}\x{95BE}' .
+'\x{95C3}\x{95C7}\x{95CA}\x{95CC}\x{95CD}\x{95D4}\x{95D5}\x{95D6}\x{95D8}' .
+'\x{95DC}\x{95E1}\x{95E2}\x{95E5}\x{961C}\x{9621}\x{9628}\x{962A}\x{962E}' .
+'\x{962F}\x{9632}\x{963B}\x{963F}\x{9640}\x{9642}\x{9644}\x{964B}\x{964C}' .
+'\x{964D}\x{964F}\x{9650}\x{965B}\x{965C}\x{965D}\x{965E}\x{965F}\x{9662}' .
+'\x{9663}\x{9664}\x{9665}\x{9666}\x{966A}\x{966C}\x{9670}\x{9672}\x{9673}' .
+'\x{9675}\x{9676}\x{9677}\x{9678}\x{967A}\x{967D}\x{9685}\x{9686}\x{9688}' .
+'\x{968A}\x{968B}\x{968D}\x{968E}\x{968F}\x{9694}\x{9695}\x{9697}\x{9698}' .
+'\x{9699}\x{969B}\x{969C}\x{96A0}\x{96A3}\x{96A7}\x{96A8}\x{96AA}\x{96B0}' .
+'\x{96B1}\x{96B2}\x{96B4}\x{96B6}\x{96B7}\x{96B8}\x{96B9}\x{96BB}\x{96BC}' .
+'\x{96C0}\x{96C1}\x{96C4}\x{96C5}\x{96C6}\x{96C7}\x{96C9}\x{96CB}\x{96CC}' .
+'\x{96CD}\x{96CE}\x{96D1}\x{96D5}\x{96D6}\x{96D9}\x{96DB}\x{96DC}\x{96E2}' .
+'\x{96E3}\x{96E8}\x{96EA}\x{96EB}\x{96F0}\x{96F2}\x{96F6}\x{96F7}\x{96F9}' .
+'\x{96FB}\x{9700}\x{9704}\x{9706}\x{9707}\x{9708}\x{970A}\x{970D}\x{970E}' .
+'\x{970F}\x{9711}\x{9713}\x{9716}\x{9719}\x{971C}\x{971E}\x{9724}\x{9727}' .
+'\x{972A}\x{9730}\x{9732}\x{9738}\x{9739}\x{973D}\x{973E}\x{9742}\x{9744}' .
+'\x{9746}\x{9748}\x{9749}\x{9752}\x{9756}\x{9759}\x{975C}\x{975E}\x{9760}' .
+'\x{9761}\x{9762}\x{9764}\x{9766}\x{9768}\x{9769}\x{976B}\x{976D}\x{9771}' .
+'\x{9774}\x{9779}\x{977A}\x{977C}\x{9781}\x{9784}\x{9785}\x{9786}\x{978B}' .
+'\x{978D}\x{978F}\x{9790}\x{9798}\x{979C}\x{97A0}\x{97A3}\x{97A6}\x{97A8}' .
+'\x{97AB}\x{97AD}\x{97B3}\x{97B4}\x{97C3}\x{97C6}\x{97C8}\x{97CB}\x{97D3}' .
+'\x{97DC}\x{97ED}\x{97EE}\x{97F2}\x{97F3}\x{97F5}\x{97F6}\x{97FB}\x{97FF}' .
+'\x{9801}\x{9802}\x{9803}\x{9805}\x{9806}\x{9808}\x{980C}\x{980F}\x{9810}' .
+'\x{9811}\x{9812}\x{9813}\x{9817}\x{9818}\x{981A}\x{9821}\x{9824}\x{982C}' .
+'\x{982D}\x{9834}\x{9837}\x{9838}\x{983B}\x{983C}\x{983D}\x{9846}\x{984B}' .
+'\x{984C}\x{984D}\x{984E}\x{984F}\x{9854}\x{9855}\x{9858}\x{985B}\x{985E}' .
+'\x{9867}\x{986B}\x{986F}\x{9870}\x{9871}\x{9873}\x{9874}\x{98A8}\x{98AA}' .
+'\x{98AF}\x{98B1}\x{98B6}\x{98C3}\x{98C4}\x{98C6}\x{98DB}\x{98DC}\x{98DF}' .
+'\x{98E2}\x{98E9}\x{98EB}\x{98ED}\x{98EE}\x{98EF}\x{98F2}\x{98F4}\x{98FC}' .
+'\x{98FD}\x{98FE}\x{9903}\x{9905}\x{9909}\x{990A}\x{990C}\x{9910}\x{9912}' .
+'\x{9913}\x{9914}\x{9918}\x{991D}\x{991E}\x{9920}\x{9921}\x{9924}\x{9928}' .
+'\x{992C}\x{992E}\x{993D}\x{993E}\x{9942}\x{9945}\x{9949}\x{994B}\x{994C}' .
+'\x{9950}\x{9951}\x{9952}\x{9955}\x{9957}\x{9996}\x{9997}\x{9998}\x{9999}' .
+'\x{99A5}\x{99A8}\x{99AC}\x{99AD}\x{99AE}\x{99B3}\x{99B4}\x{99BC}\x{99C1}' .
+'\x{99C4}\x{99C5}\x{99C6}\x{99C8}\x{99D0}\x{99D1}\x{99D2}\x{99D5}\x{99D8}' .
+'\x{99DB}\x{99DD}\x{99DF}\x{99E2}\x{99ED}\x{99EE}\x{99F1}\x{99F2}\x{99F8}' .
+'\x{99FB}\x{99FF}\x{9A01}\x{9A05}\x{9A0E}\x{9A0F}\x{9A12}\x{9A13}\x{9A19}' .
+'\x{9A28}\x{9A2B}\x{9A30}\x{9A37}\x{9A3E}\x{9A40}\x{9A42}\x{9A43}\x{9A45}' .
+'\x{9A4D}\x{9A55}\x{9A57}\x{9A5A}\x{9A5B}\x{9A5F}\x{9A62}\x{9A64}\x{9A65}' .
+'\x{9A69}\x{9A6A}\x{9A6B}\x{9AA8}\x{9AAD}\x{9AB0}\x{9AB8}\x{9ABC}\x{9AC0}' .
+'\x{9AC4}\x{9ACF}\x{9AD1}\x{9AD3}\x{9AD4}\x{9AD8}\x{9ADE}\x{9ADF}\x{9AE2}' .
+'\x{9AE3}\x{9AE6}\x{9AEA}\x{9AEB}\x{9AED}\x{9AEE}\x{9AEF}\x{9AF1}\x{9AF4}' .
+'\x{9AF7}\x{9AFB}\x{9B06}\x{9B18}\x{9B1A}\x{9B1F}\x{9B22}\x{9B23}\x{9B25}' .
+'\x{9B27}\x{9B28}\x{9B29}\x{9B2A}\x{9B2E}\x{9B2F}\x{9B31}\x{9B32}\x{9B3B}' .
+'\x{9B3C}\x{9B41}\x{9B42}\x{9B43}\x{9B44}\x{9B45}\x{9B4D}\x{9B4E}\x{9B4F}' .
+'\x{9B51}\x{9B54}\x{9B58}\x{9B5A}\x{9B6F}\x{9B74}\x{9B83}\x{9B8E}\x{9B91}' .
+'\x{9B92}\x{9B93}\x{9B96}\x{9B97}\x{9B9F}\x{9BA0}\x{9BA8}\x{9BAA}\x{9BAB}' .
+'\x{9BAD}\x{9BAE}\x{9BB4}\x{9BB9}\x{9BC0}\x{9BC6}\x{9BC9}\x{9BCA}\x{9BCF}' .
+'\x{9BD1}\x{9BD2}\x{9BD4}\x{9BD6}\x{9BDB}\x{9BE1}\x{9BE2}\x{9BE3}\x{9BE4}' .
+'\x{9BE8}\x{9BF0}\x{9BF1}\x{9BF2}\x{9BF5}\x{9C04}\x{9C06}\x{9C08}\x{9C09}' .
+'\x{9C0A}\x{9C0C}\x{9C0D}\x{9C10}\x{9C12}\x{9C13}\x{9C14}\x{9C15}\x{9C1B}' .
+'\x{9C21}\x{9C24}\x{9C25}\x{9C2D}\x{9C2E}\x{9C2F}\x{9C30}\x{9C32}\x{9C39}' .
+'\x{9C3A}\x{9C3B}\x{9C3E}\x{9C46}\x{9C47}\x{9C48}\x{9C52}\x{9C57}\x{9C5A}' .
+'\x{9C60}\x{9C67}\x{9C76}\x{9C78}\x{9CE5}\x{9CE7}\x{9CE9}\x{9CEB}\x{9CEC}' .
+'\x{9CF0}\x{9CF3}\x{9CF4}\x{9CF6}\x{9D03}\x{9D06}\x{9D07}\x{9D08}\x{9D09}' .
+'\x{9D0E}\x{9D12}\x{9D15}\x{9D1B}\x{9D1F}\x{9D23}\x{9D26}\x{9D28}\x{9D2A}' .
+'\x{9D2B}\x{9D2C}\x{9D3B}\x{9D3E}\x{9D3F}\x{9D41}\x{9D44}\x{9D46}\x{9D48}' .
+'\x{9D50}\x{9D51}\x{9D59}\x{9D5C}\x{9D5D}\x{9D5E}\x{9D60}\x{9D61}\x{9D64}' .
+'\x{9D6C}\x{9D6F}\x{9D72}\x{9D7A}\x{9D87}\x{9D89}\x{9D8F}\x{9D9A}\x{9DA4}' .
+'\x{9DA9}\x{9DAB}\x{9DAF}\x{9DB2}\x{9DB4}\x{9DB8}\x{9DBA}\x{9DBB}\x{9DC1}' .
+'\x{9DC2}\x{9DC4}\x{9DC6}\x{9DCF}\x{9DD3}\x{9DD9}\x{9DE6}\x{9DED}\x{9DEF}' .
+'\x{9DF2}\x{9DF8}\x{9DF9}\x{9DFA}\x{9DFD}\x{9E1A}\x{9E1B}\x{9E1E}\x{9E75}' .
+'\x{9E78}\x{9E79}\x{9E7D}\x{9E7F}\x{9E81}\x{9E88}\x{9E8B}\x{9E8C}\x{9E91}' .
+'\x{9E92}\x{9E93}\x{9E95}\x{9E97}\x{9E9D}\x{9E9F}\x{9EA5}\x{9EA6}\x{9EA9}' .
+'\x{9EAA}\x{9EAD}\x{9EB8}\x{9EB9}\x{9EBA}\x{9EBB}\x{9EBC}\x{9EBE}\x{9EBF}' .
+'\x{9EC4}\x{9ECC}\x{9ECD}\x{9ECE}\x{9ECF}\x{9ED0}\x{9ED2}\x{9ED4}\x{9ED8}' .
+'\x{9ED9}\x{9EDB}\x{9EDC}\x{9EDD}\x{9EDE}\x{9EE0}\x{9EE5}\x{9EE8}\x{9EEF}' .
+'\x{9EF4}\x{9EF6}\x{9EF7}\x{9EF9}\x{9EFB}\x{9EFC}\x{9EFD}\x{9F07}\x{9F08}' .
+'\x{9F0E}\x{9F13}\x{9F15}\x{9F20}\x{9F21}\x{9F2C}\x{9F3B}\x{9F3E}\x{9F4A}' .
+'\x{9F4B}\x{9F4E}\x{9F4F}\x{9F52}\x{9F54}\x{9F5F}\x{9F60}\x{9F61}\x{9F62}' .
+'\x{9F63}\x{9F66}\x{9F67}\x{9F6A}\x{9F6C}\x{9F72}\x{9F76}\x{9F77}\x{9F8D}' .
+'\x{9F95}\x{9F9C}\x{9F9D}\x{9FA0}]{1,15}$/iu');
diff --git a/library/vendor/Zend/Validate/Iban.php b/library/vendor/Zend/Validate/Iban.php
new file mode 100644
index 0000000..f01005b
--- /dev/null
+++ b/library/vendor/Zend/Validate/Iban.php
@@ -0,0 +1,246 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validates IBAN Numbers (International Bank Account Numbers)
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Iban extends Zend_Validate_Abstract
+{
+ const NOTSUPPORTED = 'ibanNotSupported';
+ const FALSEFORMAT = 'ibanFalseFormat';
+ const CHECKFAILED = 'ibanCheckFailed';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOTSUPPORTED => "Unknown country within the IBAN '%value%'",
+ self::FALSEFORMAT => "'%value%' has a false IBAN format",
+ self::CHECKFAILED => "'%value%' has failed the IBAN check",
+ );
+
+ /**
+ * Optional locale
+ *
+ * @var string|Zend_Locale|null
+ */
+ protected $_locale;
+
+ /**
+ * IBAN regexes by region
+ *
+ * @var array
+ */
+ protected $_ibanregex = array(
+ 'AD' => '/^AD[0-9]{2}[0-9]{8}[A-Z0-9]{12}$/',
+ 'AE' => '/^AE[0-9]{2}[0-9]{3}[0-9]{16}$/',
+ 'AL' => '/^AL[0-9]{2}[0-9]{8}[A-Z0-9]{16}$/',
+ 'AT' => '/^AT[0-9]{2}[0-9]{5}[0-9]{11}$/',
+ 'AZ' => '/^AZ[0-9]{2}[0-9]{4}[A-Z0-9]{20}$/',
+ 'BA' => '/^BA[0-9]{2}[0-9]{6}[0-9]{10}$/',
+ 'BE' => '/^BE[0-9]{2}[0-9]{3}[0-9]{9}$/',
+ 'BG' => '/^BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}$/',
+ 'BH' => '/^BH[0-9]{2}[A-Z]{4}[A-Z0-9]{14}$/',
+ 'BR' => '/^BR[0-9]{2}[0-9]{8}[0-9]{5}[0-9]{10}[A-Z]{1}[A-Z0-9]{1}$/',
+ 'CH' => '/^CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}$/',
+ 'CR' => '/^CR[0-9]{2}[0-9]{3}[0-9]{14}$/',
+ 'CS' => '/^CS[0-9]{2}[0-9]{3}[0-9]{15}$/',
+ 'CY' => '/^CY[0-9]{2}[0-9]{8}[A-Z0-9]{16}$/',
+ 'CZ' => '/^CZ[0-9]{2}[0-9]{4}[0-9]{16}$/',
+ 'DE' => '/^DE[0-9]{2}[0-9]{8}[0-9]{10}$/',
+ 'DK' => '/^DK[0-9]{2}[0-9]{4}[0-9]{10}$/',
+ 'DO' => '/^DO[0-9]{2}[A-Z0-9]{4}[0-9]{20}$/',
+ 'EE' => '/^EE[0-9]{2}[0-9]{4}[0-9]{12}$/',
+ 'ES' => '/^ES[0-9]{2}[0-9]{8}[0-9]{12}$/',
+ 'FR' => '/^FR[0-9]{2}[0-9]{10}[A-Z0-9]{11}[0-9]{2}$/',
+ 'FI' => '/^FI[0-9]{2}[0-9]{6}[0-9]{8}$/',
+ 'FO' => '/^FO[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}$/',
+ 'GB' => '/^GB[0-9]{2}[A-Z]{4}[0-9]{14}$/',
+ 'GE' => '/^GE[0-9]{2}[A-Z]{2}[0-9]{16}$/',
+ 'GI' => '/^GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}$/',
+ 'GL' => '/^GL[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}$/',
+ 'GR' => '/^GR[0-9]{2}[0-9]{7}[A-Z0-9]{16}$/',
+ 'GT' => '/^GT[0-9]{2}[A-Z0-9]{4}[A-Z0-9]{20}$/',
+ 'HR' => '/^HR[0-9]{2}[0-9]{7}[0-9]{10}$/',
+ 'HU' => '/^HU[0-9]{2}[0-9]{7}[0-9]{1}[0-9]{15}[0-9]{1}$/',
+ 'IE' => '/^IE[0-9]{2}[A-Z0-9]{4}[0-9]{6}[0-9]{8}$/',
+ 'IL' => '/^IL[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{13}$/',
+ 'IS' => '/^IS[0-9]{2}[0-9]{4}[0-9]{18}$/',
+ 'IT' => '/^IT[0-9]{2}[A-Z]{1}[0-9]{10}[A-Z0-9]{12}$/',
+ 'KW' => '/^KW[0-9]{2}[A-Z]{4}[0-9]{3}[0-9]{22}$/',
+ 'KZ' => '/^KZ[A-Z]{2}[0-9]{2}[0-9]{3}[A-Z0-9]{13}$/',
+ 'LB' => '/^LB[0-9]{2}[0-9]{4}[A-Z0-9]{20}$/',
+ 'LI' => '/^LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}$/',
+ 'LU' => '/^LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}$/',
+ 'LT' => '/^LT[0-9]{2}[0-9]{5}[0-9]{11}$/',
+ 'LV' => '/^LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}$/',
+ 'MC' => '/^MC[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}$/',
+ 'MD' => '/^MD[0-9]{2}[A-Z0-9]{20}$/',
+ 'ME' => '/^ME[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}$/',
+ 'MK' => '/^MK[0-9]{2}[0-9]{3}[A-Z0-9]{10}[0-9]{2}$/',
+ 'MR' => '/^MR13[0-9]{5}[0-9]{5}[0-9]{11}[0-9]{2}$/',
+ 'MU' => '/^MU[0-9]{2}[A-Z]{4}[0-9]{2}[0-9]{2}[0-9]{12}[0-9]{3}[A-Z]{2}$/',
+ 'MT' => '/^MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}$/',
+ 'NL' => '/^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/',
+ 'NO' => '/^NO[0-9]{2}[0-9]{4}[0-9]{7}$/',
+ 'PK' => '/^PK[0-9]{2}[A-Z]{4}[0-9]{16}$/',
+ 'PL' => '/^PL[0-9]{2}[0-9]{8}[0-9]{16}$/',
+ 'PS' => '/^PS[0-9]{2}[A-Z]{4}[0-9]{21}$/',
+ 'PT' => '/^PT[0-9]{2}[0-9]{8}[0-9]{13}$/',
+ 'RO' => '/^RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}$/',
+ 'RS' => '/^RS[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}$/',
+ 'SA' => '/^SA[0-9]{2}[0-9]{2}[A-Z0-9]{18}$/',
+ 'SE' => '/^SE[0-9]{2}[0-9]{3}[0-9]{17}$/',
+ 'SI' => '/^SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}$/',
+ 'SK' => '/^SK[0-9]{2}[0-9]{4}[0-9]{16}$/',
+ 'SM' => '/^SM[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}$/',
+ 'TN' => '/^TN[0-9]{2}[0-9]{5}[0-9]{15}$/',
+ 'TR' => '/^TR[0-9]{2}[0-9]{5}[A-Z0-9]{17}$/',
+ 'VG' => '/^VG[0-9]{2}[A-Z]{4}[0-9]{16}$/'
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @param string|Zend_Config|Zend_Locale $locale OPTIONAL
+ */
+ public function __construct($locale = null)
+ {
+ if ($locale instanceof Zend_Config) {
+ $locale = $locale->toArray();
+ }
+
+ if (is_array($locale)) {
+ if (array_key_exists('locale', $locale)) {
+ $locale = $locale['locale'];
+ } else {
+ $locale = null;
+ }
+ }
+
+ if (empty($locale)) {
+ if (Zend_Registry::isRegistered('Zend_Locale')) {
+ $locale = Zend_Registry::get('Zend_Locale');
+ }
+ }
+
+ if ($locale !== null) {
+ $this->setLocale($locale);
+ }
+ }
+
+ /**
+ * Returns the locale option
+ *
+ * @return string|Zend_Locale|null
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Sets the locale option
+ *
+ * @param string|Zend_Locale $locale
+ * @throws Zend_Locale_Exception
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_Date provides a fluent interface
+ */
+ public function setLocale($locale = null)
+ {
+ if ($locale !== false) {
+ $locale = Zend_Locale::findLocale($locale);
+ if (strlen($locale) < 4) {
+ throw new Zend_Validate_Exception('Region must be given for IBAN validation');
+ }
+ }
+
+ $this->_locale = $locale;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if $value is a valid IBAN
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $value = strtoupper($value);
+ $this->_setValue($value);
+
+ if (empty($this->_locale)) {
+ $region = substr($value, 0, 2);
+ } else {
+ $region = new Zend_Locale($this->_locale);
+ $region = $region->getRegion();
+ }
+
+ if (!array_key_exists($region, $this->_ibanregex)) {
+ $this->_setValue($region);
+ $this->_error(self::NOTSUPPORTED);
+ return false;
+ }
+
+ if (!preg_match($this->_ibanregex[$region], $value)) {
+ $this->_error(self::FALSEFORMAT);
+ return false;
+ }
+
+ $format = substr($value, 4) . substr($value, 0, 4);
+ $format = str_replace(
+ array('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'),
+ array('10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22',
+ '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35'),
+ $format);
+
+ $temp = intval(substr($format, 0, 1));
+ $len = strlen($format);
+ for ($x = 1; $x < $len; ++$x) {
+ $temp *= 10;
+ $temp += intval(substr($format, $x, 1));
+ $temp %= 97;
+ }
+
+ if ($temp != 1) {
+ $this->_error(self::CHECKFAILED);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Identical.php b/library/vendor/Zend/Validate/Identical.php
new file mode 100644
index 0000000..6afa0eb
--- /dev/null
+++ b/library/vendor/Zend/Validate/Identical.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** @see Zend_Validate_Abstract */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Identical extends Zend_Validate_Abstract
+{
+ /**
+ * Error codes
+ * @const string
+ */
+ const NOT_SAME = 'notSame';
+ const MISSING_TOKEN = 'missingToken';
+
+ /**
+ * Error messages
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_SAME => "The two given tokens do not match",
+ self::MISSING_TOKEN => 'No token was provided to match against',
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'token' => '_tokenString'
+ );
+
+ /**
+ * Original token against which to validate
+ * @var string
+ */
+ protected $_tokenString;
+ protected $_token;
+ protected $_strict = true;
+
+ /**
+ * Sets validator options
+ *
+ * @param mixed $token
+ */
+ public function __construct($token = null)
+ {
+ if ($token instanceof Zend_Config) {
+ $token = $token->toArray();
+ }
+
+ if (is_array($token) && array_key_exists('token', $token)) {
+ if (array_key_exists('strict', $token)) {
+ $this->setStrict($token['strict']);
+ }
+
+ $this->setToken($token['token']);
+ } else if (null !== $token) {
+ $this->setToken($token);
+ }
+ }
+
+ /**
+ * Retrieve token
+ *
+ * @return string
+ */
+ public function getToken()
+ {
+ return $this->_token;
+ }
+
+ /**
+ * Set token against which to compare
+ *
+ * @param mixed $token
+ * @return Zend_Validate_Identical
+ */
+ public function setToken($token)
+ {
+ $this->_tokenString = $token;
+ $this->_token = $token;
+ return $this;
+ }
+
+ /**
+ * Returns the strict parameter
+ *
+ * @return boolean
+ */
+ public function getStrict()
+ {
+ return $this->_strict;
+ }
+
+ /**
+ * Sets the strict parameter
+ *
+ * @param Zend_Validate_Identical
+ * @return $this
+ */
+ public function setStrict($strict)
+ {
+ $this->_strict = (boolean) $strict;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if a token has been set and the provided value
+ * matches that token.
+ *
+ * @param mixed $value
+ * @param array $context
+ * @return boolean
+ */
+ public function isValid($value, $context = null)
+ {
+ $this->_setValue($value);
+
+ if (($context !== null) && isset($context) && array_key_exists($this->getToken(), $context)) {
+ $token = $context[$this->getToken()];
+ } else {
+ $token = $this->getToken();
+ }
+
+ if ($token === null) {
+ $this->_error(self::MISSING_TOKEN);
+ return false;
+ }
+
+ $strict = $this->getStrict();
+ if (($strict && ($value !== $token)) || (!$strict && ($value != $token))) {
+ $this->_error(self::NOT_SAME);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/InArray.php b/library/vendor/Zend/Validate/InArray.php
new file mode 100644
index 0000000..9a9a353
--- /dev/null
+++ b/library/vendor/Zend/Validate/InArray.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_InArray extends Zend_Validate_Abstract
+{
+ const NOT_IN_ARRAY = 'notInArray';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_IN_ARRAY => "'%value%' was not found in the haystack",
+ );
+
+ /**
+ * Haystack of possible values
+ *
+ * @var array
+ */
+ protected $_haystack;
+
+ /**
+ * Whether a strict in_array() invocation is used
+ *
+ * @var boolean
+ */
+ protected $_strict = false;
+
+ /**
+ * Whether a recursive search should be done
+ *
+ * @var boolean
+ */
+ protected $_recursive = false;
+
+ /**
+ * Sets validator options
+ *
+ * @param array|Zend_Config $options Validator options
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($options)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ throw new Zend_Validate_Exception('Array expected as parameter');
+ } else {
+ $count = func_num_args();
+ $temp = array();
+ if ($count > 1) {
+ $temp['haystack'] = func_get_arg(0);
+ $temp['strict'] = func_get_arg(1);
+ $options = $temp;
+ } else {
+ $temp = func_get_arg(0);
+ if (!array_key_exists('haystack', $options)) {
+ $options = array();
+ $options['haystack'] = $temp;
+ } else {
+ $options = $temp;
+ }
+ }
+ }
+
+ $this->setHaystack($options['haystack']);
+ if (array_key_exists('strict', $options)) {
+ $this->setStrict($options['strict']);
+ }
+
+ if (array_key_exists('recursive', $options)) {
+ $this->setRecursive($options['recursive']);
+ }
+ }
+
+ /**
+ * Returns the haystack option
+ *
+ * @return mixed
+ */
+ public function getHaystack()
+ {
+ return $this->_haystack;
+ }
+
+ /**
+ * Sets the haystack option
+ *
+ * @param mixed $haystack
+ * @return Zend_Validate_InArray Provides a fluent interface
+ */
+ public function setHaystack(array $haystack)
+ {
+ $this->_haystack = $haystack;
+ return $this;
+ }
+
+ /**
+ * Returns the strict option
+ *
+ * @return boolean
+ */
+ public function getStrict()
+ {
+ return $this->_strict;
+ }
+
+ /**
+ * Sets the strict option
+ *
+ * @param boolean $strict
+ * @return Zend_Validate_InArray Provides a fluent interface
+ */
+ public function setStrict($strict)
+ {
+ $this->_strict = (boolean) $strict;
+ return $this;
+ }
+
+ /**
+ * Returns the recursive option
+ *
+ * @return boolean
+ */
+ public function getRecursive()
+ {
+ return $this->_recursive;
+ }
+
+ /**
+ * Sets the recursive option
+ *
+ * @param boolean $recursive
+ * @return Zend_Validate_InArray Provides a fluent interface
+ */
+ public function setRecursive($recursive)
+ {
+ $this->_recursive = (boolean) $recursive;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is contained in the haystack option. If the strict
+ * option is true, then the type of $value is also checked.
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+ if ($this->getRecursive()) {
+ $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($this->_haystack));
+ foreach($iterator as $element) {
+ if ($this->_strict) {
+ if ($element === $value) {
+ return true;
+ }
+ } else if ($element == $value) {
+ return true;
+ }
+ }
+ } else {
+ if (in_array($value, $this->_haystack, $this->_strict)) {
+ return true;
+ }
+ }
+
+ $this->_error(self::NOT_IN_ARRAY);
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Int.php b/library/vendor/Zend/Validate/Int.php
new file mode 100644
index 0000000..75ec6cd
--- /dev/null
+++ b/library/vendor/Zend/Validate/Int.php
@@ -0,0 +1,145 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Locale_Format
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Int extends Zend_Validate_Abstract
+{
+ const INVALID = 'intInvalid';
+ const NOT_INT = 'notInt';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String or integer expected",
+ self::NOT_INT => "'%value%' does not appear to be an integer",
+ );
+
+ protected $_locale;
+
+ /**
+ * Constructor for the integer validator
+ *
+ * @param string|Zend_Config|Zend_Locale $locale
+ */
+ public function __construct($locale = null)
+ {
+ if ($locale instanceof Zend_Config) {
+ $locale = $locale->toArray();
+ }
+
+ if (is_array($locale)) {
+ if (array_key_exists('locale', $locale)) {
+ $locale = $locale['locale'];
+ } else {
+ $locale = null;
+ }
+ }
+
+ if (empty($locale)) {
+ if (Zend_Registry::isRegistered('Zend_Locale')) {
+ $locale = Zend_Registry::get('Zend_Locale');
+ }
+ }
+
+ if ($locale !== null) {
+ $this->setLocale($locale);
+ }
+ }
+
+ /**
+ * Returns the set locale
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Sets the locale to use
+ *
+ * @param string|Zend_Locale $locale
+ * @return $this
+ */
+ public function setLocale($locale = null)
+ {
+ $this->_locale = Zend_Locale::findLocale($locale);
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is a valid integer
+ *
+ * @param string|integer $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value) && !is_float($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ if (is_int($value)) {
+ return true;
+ }
+
+ $this->_setValue($value);
+ if ($this->_locale === null) {
+ $locale = localeconv();
+ $valueFiltered = str_replace($locale['decimal_point'], '.', $value);
+ $valueFiltered = str_replace($locale['thousands_sep'], '', $valueFiltered);
+
+ if (strval(intval($valueFiltered)) != $valueFiltered) {
+ $this->_error(self::NOT_INT);
+ return false;
+ }
+
+ } else {
+ try {
+ if (!Zend_Locale_Format::isInteger($value, array('locale' => $this->_locale))) {
+ $this->_error(self::NOT_INT);
+ return false;
+ }
+ } catch (Zend_Locale_Exception $e) {
+ $this->_error(self::NOT_INT);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Interface.php b/library/vendor/Zend/Validate/Interface.php
new file mode 100644
index 0000000..f1d0883
--- /dev/null
+++ b/library/vendor/Zend/Validate/Interface.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_Validate_Interface
+{
+ /**
+ * Returns true if and only if $value meets the validation requirements
+ *
+ * If $value fails validation, then this method returns false, and
+ * getMessages() will return an array of messages that explain why the
+ * validation failed.
+ *
+ * @param mixed $value
+ * @return boolean
+ * @throws Zend_Validate_Exception If validation of $value is impossible
+ */
+ public function isValid($value);
+
+ /**
+ * Returns an array of messages that explain why the most recent isValid()
+ * call returned false. The array keys are validation failure message identifiers,
+ * and the array values are the corresponding human-readable message strings.
+ *
+ * If isValid() was never called or if the most recent isValid() call
+ * returned true, then this method returns an empty array.
+ *
+ * @return array
+ */
+ public function getMessages();
+}
diff --git a/library/vendor/Zend/Validate/Ip.php b/library/vendor/Zend/Validate/Ip.php
new file mode 100644
index 0000000..1195ec7
--- /dev/null
+++ b/library/vendor/Zend/Validate/Ip.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Ip extends Zend_Validate_Abstract
+{
+ const INVALID = 'ipInvalid';
+ const NOT_IP_ADDRESS = 'notIpAddress';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String expected",
+ self::NOT_IP_ADDRESS => "'%value%' does not appear to be a valid IP address",
+ );
+
+ /**
+ * internal options
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'allowipv6' => true,
+ 'allowipv4' => true
+ );
+
+ /**
+ * Sets validator options
+ *
+ * @param array $options OPTIONAL Options to set, see the manual for all available options
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['allowipv6'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['allowipv4'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ $options += $this->_options;
+ $this->setOptions($options);
+ }
+
+ /**
+ * Returns all set options
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->_options;
+ }
+
+ /**
+ * Sets the options for this validator
+ *
+ * @param array $options
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_Ip
+ */
+ public function setOptions($options)
+ {
+ if (array_key_exists('allowipv6', $options)) {
+ $this->_options['allowipv6'] = (boolean) $options['allowipv6'];
+ }
+
+ if (array_key_exists('allowipv4', $options)) {
+ $this->_options['allowipv4'] = (boolean) $options['allowipv4'];
+ }
+
+ if (!$this->_options['allowipv4'] && !$this->_options['allowipv6']) {
+ throw new Zend_Validate_Exception('Nothing to validate. Check your options');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is a valid IP address
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ if (($this->_options['allowipv4'] && !$this->_options['allowipv6'] && !$this->_validateIPv4($value)) ||
+ (!$this->_options['allowipv4'] && $this->_options['allowipv6'] && !$this->_validateIPv6($value)) ||
+ ($this->_options['allowipv4'] && $this->_options['allowipv6'] && !$this->_validateIPv4($value) && !$this->_validateIPv6($value))) {
+ $this->_error(self::NOT_IP_ADDRESS);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Validates an IPv4 address
+ *
+ * @param string $value
+ * @return bool
+ */
+ protected function _validateIPv4($value) {
+ $ip2long = ip2long($value);
+ if($ip2long === false) {
+ return false;
+ }
+
+ return $value == long2ip($ip2long);
+ }
+
+ /**
+ * Validates an IPv6 address
+ *
+ * @param string $value Value to check against
+ * @return boolean True when $value is a valid ipv6 address
+ * False otherwise
+ */
+ protected function _validateIPv6($value) {
+ if (strlen($value) < 3) {
+ return $value == '::';
+ }
+
+ if (strpos($value, '.')) {
+ $lastcolon = strrpos($value, ':');
+ if (!($lastcolon && $this->_validateIPv4(substr($value, $lastcolon + 1)))) {
+ return false;
+ }
+
+ $value = substr($value, 0, $lastcolon) . ':0:0';
+ }
+
+ if (strpos($value, '::') === false) {
+ return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value);
+ }
+
+ $colonCount = substr_count($value, ':');
+ if ($colonCount < 8) {
+ return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value);
+ }
+
+ // special case with ending or starting double colon
+ if ($colonCount == 8) {
+ return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value);
+ }
+
+ return false;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Isbn.php b/library/vendor/Zend/Validate/Isbn.php
new file mode 100644
index 0000000..e917afc
--- /dev/null
+++ b/library/vendor/Zend/Validate/Isbn.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Isbn extends Zend_Validate_Abstract
+{
+ const AUTO = 'auto';
+ const ISBN10 = '10';
+ const ISBN13 = '13';
+ const INVALID = 'isbnInvalid';
+ const NO_ISBN = 'isbnNoIsbn';
+
+ /**
+ * Validation failure message template definitions.
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String or integer expected",
+ self::NO_ISBN => "'%value%' is not a valid ISBN number",
+ );
+
+ /**
+ * Allowed type.
+ *
+ * @var string
+ */
+ protected $_type = self::AUTO;
+
+ /**
+ * Separator character.
+ *
+ * @var string
+ */
+ protected $_separator = '';
+
+ /**
+ * Set up options.
+ *
+ * @param Zend_Config|array $options
+ * @throws Zend_Validate_Exception When $options is not valid
+ */
+ public function __construct($options = array())
+ {
+ // prepare options
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+ if (!is_array($options)) {
+ /**
+ * @see Zend_Validate_Exception
+ */
+ throw new Zend_Validate_Exception('Invalid options provided.');
+ }
+
+ // set type
+ if (array_key_exists('type', $options)) {
+ $this->setType($options['type']);
+ }
+
+ // set separator
+ if (array_key_exists('separator', $options)) {
+ $this->setSeparator($options['separator']);
+ }
+ }
+
+ /**
+ * Detect input format.
+ *
+ * @return string
+ */
+ protected function _detectFormat()
+ {
+ // prepare separator and pattern list
+ $sep = quotemeta($this->_separator);
+ $patterns = array();
+ $lengths = array();
+
+ // check for ISBN-10
+ if ($this->_type == self::ISBN10 || $this->_type == self::AUTO) {
+ if (empty($sep)) {
+ $pattern = '/^[0-9]{9}[0-9X]{1}$/';
+ $length = 10;
+ } else {
+ $pattern = "/^[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9]{1,7}[{$sep}]{1}[0-9X]{1}$/";
+ $length = 13;
+ }
+
+ $patterns[$pattern] = self::ISBN10;
+ $lengths[$pattern] = $length;
+ }
+
+ // check for ISBN-13
+ if ($this->_type == self::ISBN13 || $this->_type == self::AUTO) {
+ if (empty($sep)) {
+ $pattern = '/^[0-9]{13}$/';
+ $length = 13;
+ } else {
+ $pattern = "/^[0-9]{1,9}[{$sep}]{1}[0-9]{1,5}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1,9}[{$sep}]{1}[0-9]{1}$/";
+ $length = 17;
+ }
+
+ $patterns[$pattern] = self::ISBN13;
+ $lengths[$pattern] = $length;
+ }
+
+ // check pattern list
+ foreach ($patterns as $pattern => $type) {
+ if ((strlen($this->_value) == $lengths[$pattern]) && preg_match($pattern, $this->_value)) {
+ return $type;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface.
+ *
+ * Returns true if and only if $value is a valid ISBN.
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $value = (string) $value;
+ $this->_setValue($value);
+
+ switch ($this->_detectFormat()) {
+ case self::ISBN10:
+ // sum
+ $isbn10 = str_replace($this->_separator, '', $value);
+ $sum = 0;
+ for ($i = 0; $i < 9; $i++) {
+ $sum += (10 - $i) * $isbn10[$i];
+ }
+
+ // checksum
+ $checksum = 11 - ($sum % 11);
+ if ($checksum == 11) {
+ $checksum = '0';
+ } elseif ($checksum == 10) {
+ $checksum = 'X';
+ }
+ break;
+
+ case self::ISBN13:
+ // sum
+ $isbn13 = str_replace($this->_separator, '', $value);
+ $sum = 0;
+ for ($i = 0; $i < 12; $i++) {
+ if ($i % 2 == 0) {
+ $sum += $isbn13[$i];
+ } else {
+ $sum += 3 * $isbn13[$i];
+ }
+ }
+ // checksum
+ $checksum = 10 - ($sum % 10);
+ if ($checksum == 10) {
+ $checksum = '0';
+ }
+ break;
+
+ default:
+ $this->_error(self::NO_ISBN);
+ return false;
+ }
+
+ // validate
+ if (substr($this->_value, -1) != $checksum) {
+ $this->_error(self::NO_ISBN);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set separator characters.
+ *
+ * It is allowed only empty string, hyphen and space.
+ *
+ * @param string $separator
+ * @throws Zend_Validate_Exception When $separator is not valid
+ * @return Zend_Validate_Isbn Provides a fluent interface
+ */
+ public function setSeparator($separator)
+ {
+ // check separator
+ if (!in_array($separator, array('-', ' ', ''))) {
+ /**
+ * @see Zend_Validate_Exception
+ */
+ throw new Zend_Validate_Exception('Invalid ISBN separator.');
+ }
+
+ $this->_separator = $separator;
+ return $this;
+ }
+
+ /**
+ * Get separator characters.
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ return $this->_separator;
+ }
+
+ /**
+ * Set allowed ISBN type.
+ *
+ * @param string $type
+ * @throws Zend_Validate_Exception When $type is not valid
+ * @return Zend_Validate_Isbn Provides a fluent interface
+ */
+ public function setType($type)
+ {
+ // check type
+ if (!in_array($type, array(self::AUTO, self::ISBN10, self::ISBN13))) {
+ /**
+ * @see Zend_Validate_Exception
+ */
+ throw new Zend_Validate_Exception('Invalid ISBN type');
+ }
+
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * Get allowed ISBN type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+}
diff --git a/library/vendor/Zend/Validate/LessThan.php b/library/vendor/Zend/Validate/LessThan.php
new file mode 100644
index 0000000..6c1244b
--- /dev/null
+++ b/library/vendor/Zend/Validate/LessThan.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_LessThan extends Zend_Validate_Abstract
+{
+ const NOT_LESS = 'notLessThan';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_LESS => "'%value%' is not less than '%max%'"
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'max' => '_max'
+ );
+
+ /**
+ * Maximum value
+ *
+ * @var mixed
+ */
+ protected $_max;
+
+ /**
+ * Sets validator options
+ *
+ * @param mixed|Zend_Config $max
+ * @throws Zend_Validate_Exception
+ */
+ public function __construct($max)
+ {
+ if ($max instanceof Zend_Config) {
+ $max = $max->toArray();
+ }
+
+ if (is_array($max)) {
+ if (array_key_exists('max', $max)) {
+ $max = $max['max'];
+ } else {
+ throw new Zend_Validate_Exception("Missing option 'max'");
+ }
+ }
+
+ $this->setMax($max);
+ }
+
+ /**
+ * Returns the max option
+ *
+ * @return mixed
+ */
+ public function getMax()
+ {
+ return $this->_max;
+ }
+
+ /**
+ * Sets the max option
+ *
+ * @param mixed $max
+ * @return Zend_Validate_LessThan Provides a fluent interface
+ */
+ public function setMax($max)
+ {
+ $this->_max = $max;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is less than max option
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+ if ($this->_max <= $value) {
+ $this->_error(self::NOT_LESS);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/library/vendor/Zend/Validate/NotEmpty.php b/library/vendor/Zend/Validate/NotEmpty.php
new file mode 100644
index 0000000..7b70e68
--- /dev/null
+++ b/library/vendor/Zend/Validate/NotEmpty.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_NotEmpty extends Zend_Validate_Abstract
+{
+ const BOOLEAN = 1;
+ const INTEGER = 2;
+ const FLOAT = 4;
+ const STRING = 8;
+ const ZERO = 16;
+ const EMPTY_ARRAY = 32;
+ const NULL = 64;
+ const PHP = 127;
+ const SPACE = 128;
+ const OBJECT = 256;
+ const OBJECT_STRING = 512;
+ const OBJECT_COUNT = 1024;
+ const ALL = 2047;
+
+ const INVALID = 'notEmptyInvalid';
+ const IS_EMPTY = 'isEmpty';
+
+ protected $_constants = array(
+ self::BOOLEAN => 'boolean',
+ self::INTEGER => 'integer',
+ self::FLOAT => 'float',
+ self::STRING => 'string',
+ self::ZERO => 'zero',
+ self::EMPTY_ARRAY => 'array',
+ self::NULL => 'null',
+ self::PHP => 'php',
+ self::SPACE => 'space',
+ self::OBJECT => 'object',
+ self::OBJECT_STRING => 'objectstring',
+ self::OBJECT_COUNT => 'objectcount',
+ self::ALL => 'all',
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::IS_EMPTY => "Value is required and can't be empty",
+ self::INVALID => "Invalid type given. String, integer, float, boolean or array expected",
+ );
+
+ /**
+ * Internal type to detect
+ *
+ * @var integer
+ */
+ protected $_type = 493;
+
+ /**
+ * Constructor
+ *
+ * @param string|array|Zend_Config $options OPTIONAL
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp = array();
+ if (!empty($options)) {
+ $temp['type'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (is_array($options) && array_key_exists('type', $options)) {
+ $this->setType($options['type']);
+ }
+ }
+
+ /**
+ * Returns the set types
+ *
+ * @return array
+ */
+ public function getType()
+ {
+ return $this->_type;
+ }
+
+ /**
+ * Set the types
+ *
+ * @param integer|array $type
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_NotEmpty
+ */
+ public function setType($type = null)
+ {
+ if (is_array($type)) {
+ $detected = 0;
+ foreach($type as $value) {
+ if (is_int($value)) {
+ $detected += $value;
+ } else if (in_array($value, $this->_constants)) {
+ $detected += array_search($value, $this->_constants);
+ }
+ }
+
+ $type = $detected;
+ } else if (is_string($type) && in_array($type, $this->_constants)) {
+ $type = array_search($type, $this->_constants);
+ }
+
+ if (!is_int($type) || ($type < 0) || ($type > self::ALL)) {
+ throw new Zend_Validate_Exception('Unknown type');
+ }
+
+ $this->_type = $type;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is not an empty value.
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if ($value !== null && !is_string($value) && !is_int($value) && !is_float($value) &&
+ !is_bool($value) && !is_array($value) && !is_object($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $type = $this->getType();
+ $this->_setValue($value);
+ $object = false;
+
+ // OBJECT_COUNT (countable object)
+ if ($type >= self::OBJECT_COUNT) {
+ $type -= self::OBJECT_COUNT;
+ $object = true;
+
+ if (is_object($value) && ($value instanceof Countable) && (count($value) == 0)) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // OBJECT_STRING (object's toString)
+ if ($type >= self::OBJECT_STRING) {
+ $type -= self::OBJECT_STRING;
+ $object = true;
+
+ if ((is_object($value) && (!method_exists($value, '__toString'))) ||
+ (is_object($value) && (method_exists($value, '__toString')) && (((string) $value) == ""))) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // OBJECT (object)
+ if ($type >= self::OBJECT) {
+ $type -= self::OBJECT;
+ // fall trough, objects are always not empty
+ } else if ($object === false) {
+ // object not allowed but object given -> return false
+ if (is_object($value)) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // SPACE (' ')
+ if ($type >= self::SPACE) {
+ $type -= self::SPACE;
+ if (is_string($value) && (preg_match('/^\s+$/s', $value))) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // NULL (null)
+ if ($type >= self::NULL) {
+ $type -= self::NULL;
+ if ($value === null) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // EMPTY_ARRAY (array())
+ if ($type >= self::EMPTY_ARRAY) {
+ $type -= self::EMPTY_ARRAY;
+ if (is_array($value) && ($value == array())) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // ZERO ('0')
+ if ($type >= self::ZERO) {
+ $type -= self::ZERO;
+ if (is_string($value) && ($value == '0')) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // STRING ('')
+ if ($type >= self::STRING) {
+ $type -= self::STRING;
+ if (is_string($value) && ($value == '')) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // FLOAT (0.0)
+ if ($type >= self::FLOAT) {
+ $type -= self::FLOAT;
+ if (is_float($value) && ($value == 0.0)) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // INTEGER (0)
+ if ($type >= self::INTEGER) {
+ $type -= self::INTEGER;
+ if (is_int($value) && ($value == 0)) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ // BOOLEAN (false)
+ if ($type >= self::BOOLEAN) {
+ $type -= self::BOOLEAN;
+ if (is_bool($value) && ($value == false)) {
+ $this->_error(self::IS_EMPTY);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/PostCode.php b/library/vendor/Zend/Validate/PostCode.php
new file mode 100644
index 0000000..0ae7b03
--- /dev/null
+++ b/library/vendor/Zend/Validate/PostCode.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Locale_Format
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_PostCode extends Zend_Validate_Abstract
+{
+ const INVALID = 'postcodeInvalid';
+ const NO_MATCH = 'postcodeNoMatch';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String or integer expected",
+ self::NO_MATCH => "'%value%' does not appear to be a postal code",
+ );
+
+ /**
+ * Locale to use
+ *
+ * @var string
+ */
+ protected $_locale;
+
+ /**
+ * Manual postal code format
+ *
+ * @var unknown_type
+ */
+ protected $_format;
+
+ /**
+ * Constructor for the integer validator
+ *
+ * Accepts either a string locale, a Zend_Locale object, or an array or
+ * Zend_Config object containing the keys "locale" and/or "format".
+ *
+ * @param string|Zend_Locale|array|Zend_Config $options
+ * @throws Zend_Validate_Exception On empty format
+ */
+ public function __construct($options = null)
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ }
+
+ if (empty($options)) {
+ if (Zend_Registry::isRegistered('Zend_Locale')) {
+ $this->setLocale(Zend_Registry::get('Zend_Locale'));
+ }
+ } elseif (is_array($options)) {
+ // Received
+ if (array_key_exists('locale', $options)) {
+ $this->setLocale($options['locale']);
+ }
+
+ if (array_key_exists('format', $options)) {
+ $this->setFormat($options['format']);
+ }
+ } elseif ($options instanceof Zend_Locale || is_string($options)) {
+ // Received Locale object or string locale
+ $this->setLocale($options);
+ }
+
+ $format = $this->getFormat();
+ if (empty($format)) {
+ throw new Zend_Validate_Exception("A postcode-format string has to be given for validation");
+ }
+ }
+
+ /**
+ * Returns the set locale
+ *
+ * @return string|Zend_Locale The set locale
+ */
+ public function getLocale()
+ {
+ return $this->_locale;
+ }
+
+ /**
+ * Sets the locale to use
+ *
+ * @param string|Zend_Locale $locale
+ * @throws Zend_Validate_Exception On unrecognised region
+ * @throws Zend_Validate_Exception On not detected format
+ * @return Zend_Validate_PostCode Provides a fluent interface
+ */
+ public function setLocale($locale = null)
+ {
+ $this->_locale = Zend_Locale::findLocale($locale);
+ $locale = new Zend_Locale($this->_locale);
+ $region = $locale->getRegion();
+ if (empty($region)) {
+ throw new Zend_Validate_Exception("Unable to detect a region for the locale '$locale'");
+ }
+
+ $format = Zend_Locale::getTranslation(
+ $locale->getRegion(),
+ 'postaltoterritory',
+ $this->_locale
+ );
+
+ if (empty($format)) {
+ throw new Zend_Validate_Exception("Unable to detect a postcode format for the region '{$locale->getRegion()}'");
+ }
+
+ $this->setFormat($format);
+ return $this;
+ }
+
+ /**
+ * Returns the set postal code format
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->_format;
+ }
+
+ /**
+ * Sets a self defined postal format as regex
+ *
+ * @param string $format
+ * @throws Zend_Validate_Exception On empty format
+ * @return Zend_Validate_PostCode Provides a fluent interface
+ */
+ public function setFormat($format)
+ {
+ if (empty($format) || !is_string($format)) {
+ throw new Zend_Validate_Exception("A postcode-format string has to be given for validation");
+ }
+
+ if ($format[0] !== '/') {
+ $format = '/^' . $format;
+ }
+
+ if ($format[strlen($format) - 1] !== '/') {
+ $format .= '$/';
+ }
+
+ $this->_format = $format;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value is a valid postalcode
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ $this->_setValue($value);
+ if (!is_string($value) && !is_int($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $format = $this->getFormat();
+ if (!preg_match($format, $value)) {
+ $this->_error(self::NO_MATCH);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Regex.php b/library/vendor/Zend/Validate/Regex.php
new file mode 100644
index 0000000..533efc0
--- /dev/null
+++ b/library/vendor/Zend/Validate/Regex.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Regex extends Zend_Validate_Abstract
+{
+ const INVALID = 'regexInvalid';
+ const NOT_MATCH = 'regexNotMatch';
+ const ERROROUS = 'regexErrorous';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String, integer or float expected",
+ self::NOT_MATCH => "'%value%' does not match against pattern '%pattern%'",
+ self::ERROROUS => "There was an internal error while using the pattern '%pattern%'",
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'pattern' => '_pattern'
+ );
+
+ /**
+ * Regular expression pattern
+ *
+ * @var string
+ */
+ protected $_pattern;
+
+ /**
+ * Sets validator options
+ *
+ * @param string|Zend_Config $pattern
+ * @throws Zend_Validate_Exception On missing 'pattern' parameter
+ */
+ public function __construct($pattern)
+ {
+ if ($pattern instanceof Zend_Config) {
+ $pattern = $pattern->toArray();
+ }
+
+ if (is_array($pattern)) {
+ if (array_key_exists('pattern', $pattern)) {
+ $pattern = $pattern['pattern'];
+ } else {
+ throw new Zend_Validate_Exception("Missing option 'pattern'");
+ }
+ }
+
+ $this->setPattern($pattern);
+ }
+
+ /**
+ * Returns the pattern option
+ *
+ * @return string
+ */
+ public function getPattern()
+ {
+ return $this->_pattern;
+ }
+
+ /**
+ * Sets the pattern option
+ *
+ * @param string $pattern
+ * @throws Zend_Validate_Exception if there is a fatal error in pattern matching
+ * @return Zend_Validate_Regex Provides a fluent interface
+ */
+ public function setPattern($pattern)
+ {
+ $this->_pattern = (string) $pattern;
+ $status = @preg_match($this->_pattern, "Test");
+
+ if (false === $status) {
+ throw new Zend_Validate_Exception("Internal error while using the pattern '$this->_pattern'");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if $value matches against the pattern option
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value) && !is_int($value) && !is_float($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+
+ $status = @preg_match($this->_pattern, $value);
+ if (false === $status) {
+ $this->_error(self::ERROROUS);
+ return false;
+ }
+
+ if (!$status) {
+ $this->_error(self::NOT_MATCH);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Sitemap/Changefreq.php b/library/vendor/Zend/Validate/Sitemap/Changefreq.php
new file mode 100644
index 0000000..972f355
--- /dev/null
+++ b/library/vendor/Zend/Validate/Sitemap/Changefreq.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validates whether a given value is valid as a sitemap <changefreq> value
+ *
+ * @link http://www.sitemaps.org/protocol.php Sitemaps XML format
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Sitemap_Changefreq extends Zend_Validate_Abstract
+{
+ /**
+ * Validation key for not valid
+ *
+ */
+ const NOT_VALID = 'sitemapChangefreqNotValid';
+ const INVALID = 'sitemapChangefreqInvalid';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_VALID => "'%value%' is not a valid sitemap changefreq",
+ self::INVALID => "Invalid type given. String expected",
+ );
+
+ /**
+ * Valid change frequencies
+ *
+ * @var array
+ */
+ protected $_changeFreqs = array(
+ 'always', 'hourly', 'daily', 'weekly',
+ 'monthly', 'yearly', 'never'
+ );
+
+ /**
+ * Validates if a string is valid as a sitemap changefreq
+ *
+ * @link http://www.sitemaps.org/protocol.php#changefreqdef <changefreq>
+ *
+ * @param string $value value to validate
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ if (!is_string($value)) {
+ return false;
+ }
+
+ if (!in_array($value, $this->_changeFreqs, true)) {
+ $this->_error(self::NOT_VALID);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Sitemap/Lastmod.php b/library/vendor/Zend/Validate/Sitemap/Lastmod.php
new file mode 100644
index 0000000..e813e5a
--- /dev/null
+++ b/library/vendor/Zend/Validate/Sitemap/Lastmod.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validates whether a given value is valid as a sitemap <lastmod> value
+ *
+ * @link http://www.sitemaps.org/protocol.php Sitemaps XML format
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Sitemap_Lastmod extends Zend_Validate_Abstract
+{
+ /**
+ * Regular expression to use when validating
+ *
+ */
+ const LASTMOD_REGEX = '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])(T([0-1][0-9]|2[0-3])(:[0-5][0-9])(:[0-5][0-9])?(\\+|-)([0-1][0-9]|2[0-3]):[0-5][0-9])?$/';
+
+ /**
+ * Validation key for not valid
+ *
+ */
+ const NOT_VALID = 'sitemapLastmodNotValid';
+ const INVALID = 'sitemapLastmodInvalid';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_VALID => "'%value%' is not a valid sitemap lastmod",
+ self::INVALID => "Invalid type given. String expected",
+ );
+
+ /**
+ * Validates if a string is valid as a sitemap lastmod
+ *
+ * @link http://www.sitemaps.org/protocol.php#lastmoddef <lastmod>
+ *
+ * @param string $value value to validate
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ $result = @preg_match(self::LASTMOD_REGEX, $value);
+ if ($result != 1) {
+ $this->_error(self::NOT_VALID);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Sitemap/Loc.php b/library/vendor/Zend/Validate/Sitemap/Loc.php
new file mode 100644
index 0000000..84a16bc
--- /dev/null
+++ b/library/vendor/Zend/Validate/Sitemap/Loc.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @see Zend_Uri
+ */
+
+/**
+ * Validates whether a given value is valid as a sitemap <loc> value
+ *
+ * @link http://www.sitemaps.org/protocol.php Sitemaps XML format
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Sitemap_Loc extends Zend_Validate_Abstract
+{
+ /**
+ * Validation key for not valid
+ *
+ */
+ const NOT_VALID = 'sitemapLocNotValid';
+ const INVALID = 'sitemapLocInvalid';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_VALID => "'%value%' is not a valid sitemap location",
+ self::INVALID => "Invalid type given. String expected",
+ );
+
+ /**
+ * Validates if a string is valid as a sitemap location
+ *
+ * @link http://www.sitemaps.org/protocol.php#locdef <loc>
+ *
+ * @param string $value value to validate
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ $result = Zend_Uri::check($value);
+ if ($result !== true) {
+ $this->_error(self::NOT_VALID);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/Sitemap/Priority.php b/library/vendor/Zend/Validate/Sitemap/Priority.php
new file mode 100644
index 0000000..abb0ced
--- /dev/null
+++ b/library/vendor/Zend/Validate/Sitemap/Priority.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * Validates whether a given value is valid as a sitemap <priority> value
+ *
+ * @link http://www.sitemaps.org/protocol.php Sitemaps XML format
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @subpackage Sitemap
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_Sitemap_Priority extends Zend_Validate_Abstract
+{
+ /**
+ * Validation key for not valid
+ *
+ */
+ const NOT_VALID = 'sitemapPriorityNotValid';
+ const INVALID = 'sitemapPriorityInvalid';
+
+ /**
+ * Validation failure message template definitions
+ *
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::NOT_VALID => "'%value%' is not a valid sitemap priority",
+ self::INVALID => "Invalid type given. Numeric string, integer or float expected",
+ );
+
+ /**
+ * Validates if a string is valid as a sitemap priority
+ *
+ * @link http://www.sitemaps.org/protocol.php#prioritydef <priority>
+ *
+ * @param string $value value to validate
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_numeric($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ $value = (float) $value;
+ if ($value < 0 || $value > 1) {
+ $this->_error(self::NOT_VALID);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/vendor/Zend/Validate/StringLength.php b/library/vendor/Zend/Validate/StringLength.php
new file mode 100644
index 0000000..22f9887
--- /dev/null
+++ b/library/vendor/Zend/Validate/StringLength.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_Validate_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_Validate
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Validate_StringLength extends Zend_Validate_Abstract
+{
+ const INVALID = 'stringLengthInvalid';
+ const TOO_SHORT = 'stringLengthTooShort';
+ const TOO_LONG = 'stringLengthTooLong';
+
+ /**
+ * @var array
+ */
+ protected $_messageTemplates = array(
+ self::INVALID => "Invalid type given. String expected",
+ self::TOO_SHORT => "'%value%' is less than %min% characters long",
+ self::TOO_LONG => "'%value%' is more than %max% characters long",
+ );
+
+ /**
+ * @var array
+ */
+ protected $_messageVariables = array(
+ 'min' => '_min',
+ 'max' => '_max'
+ );
+
+ /**
+ * Minimum length
+ *
+ * @var integer
+ */
+ protected $_min;
+
+ /**
+ * Maximum length
+ *
+ * If null, there is no maximum length
+ *
+ * @var integer|null
+ */
+ protected $_max;
+
+ /**
+ * Encoding to use
+ *
+ * @var string|null
+ */
+ protected $_encoding;
+
+ /**
+ * Sets validator options
+ *
+ * @param integer|array|Zend_Config $options
+ */
+ public function __construct($options = array())
+ {
+ if ($options instanceof Zend_Config) {
+ $options = $options->toArray();
+ } else if (!is_array($options)) {
+ $options = func_get_args();
+ $temp['min'] = array_shift($options);
+ if (!empty($options)) {
+ $temp['max'] = array_shift($options);
+ }
+
+ if (!empty($options)) {
+ $temp['encoding'] = array_shift($options);
+ }
+
+ $options = $temp;
+ }
+
+ if (!array_key_exists('min', $options)) {
+ $options['min'] = 0;
+ }
+
+ $this->setMin($options['min']);
+ if (array_key_exists('max', $options)) {
+ $this->setMax($options['max']);
+ }
+
+ if (array_key_exists('encoding', $options)) {
+ $this->setEncoding($options['encoding']);
+ }
+ }
+
+ /**
+ * Returns the min option
+ *
+ * @return integer
+ */
+ public function getMin()
+ {
+ return $this->_min;
+ }
+
+ /**
+ * Sets the min option
+ *
+ * @param integer $min
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_StringLength Provides a fluent interface
+ */
+ public function setMin($min)
+ {
+ if (null !== $this->_max && $min > $this->_max) {
+ /**
+ * @see Zend_Validate_Exception
+ */
+ throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum length, but $min >"
+ . " $this->_max");
+ }
+ $this->_min = max(0, (integer) $min);
+ return $this;
+ }
+
+ /**
+ * Returns the max option
+ *
+ * @return integer|null
+ */
+ public function getMax()
+ {
+ return $this->_max;
+ }
+
+ /**
+ * Sets the max option
+ *
+ * @param integer|null $max
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_StringLength Provides a fluent interface
+ */
+ public function setMax($max)
+ {
+ if (null === $max) {
+ $this->_max = null;
+ } else if ($max < $this->_min) {
+ /**
+ * @see Zend_Validate_Exception
+ */
+ throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum length, but "
+ . "$max < $this->_min");
+ } else {
+ $this->_max = (integer) $max;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the actual encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Sets a new encoding to use
+ *
+ * @param string $encoding
+ * @throws Zend_Validate_Exception
+ * @return Zend_Validate_StringLength
+ */
+ public function setEncoding($encoding = null)
+ {
+ if ($encoding !== null) {
+ $orig = PHP_VERSION_ID < 50600
+ ? iconv_get_encoding('internal_encoding')
+ : ini_get('default_charset');
+ if (PHP_VERSION_ID < 50600) {
+ if ($encoding) {
+ $result = iconv_set_encoding('internal_encoding', $encoding);
+ } else {
+ $result = false;
+ }
+ } else {
+ ini_set('default_charset', $encoding);
+ $result = ini_get('default_charset');
+ }
+ if (!$result) {
+ throw new Zend_Validate_Exception('Given encoding not supported on this OS!');
+ }
+
+ if (PHP_VERSION_ID < 50600) {
+ iconv_set_encoding('internal_encoding', $orig);
+ } else {
+ ini_set('default_charset', $orig);
+ }
+ }
+ $this->_encoding = $encoding;
+ return $this;
+ }
+
+ /**
+ * Defined by Zend_Validate_Interface
+ *
+ * Returns true if and only if the string length of $value is at least the min option and
+ * no greater than the max option (when the max option is not null).
+ *
+ * @param string $value
+ * @return boolean
+ */
+ public function isValid($value)
+ {
+ if (!is_string($value)) {
+ $this->_error(self::INVALID);
+ return false;
+ }
+
+ $this->_setValue($value);
+ if ($this->_encoding !== null) {
+ $length = iconv_strlen($value, $this->_encoding);
+ } else {
+ $length = iconv_strlen($value);
+ }
+
+ if ($length < $this->_min) {
+ $this->_error(self::TOO_SHORT);
+ }
+
+ if (null !== $this->_max && $this->_max < $length) {
+ $this->_error(self::TOO_LONG);
+ }
+
+ if (count($this->_messages)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/library/vendor/Zend/View.php b/library/vendor/Zend/View.php
new file mode 100644
index 0000000..b266b7e
--- /dev/null
+++ b/library/vendor/Zend/View.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract master class for extension.
+ */
+
+
+/**
+ * Concrete class for handling view scripts.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ *
+ * Convenience methods for build in helpers (@see __call):
+ *
+ * @method string baseUrl($file = null)
+ * @method string currency($value = null, $currency = null)
+ * @method Zend_View_Helper_Cycle cycle(array $data = array(), $name = Zend_View_Helper_Cycle::DEFAULT_NAME)
+ * @method Zend_View_Helper_Doctype doctype($doctype = null)
+ * @method string fieldset($name, $content, $attribs = null)
+ * @method string form($name, $attribs = null, $content = false)
+ * @method string formButton($name, $value = null, $attribs = null)
+ * @method string formCheckbox($name, $value = null, $attribs = null, array $checkedOptions = null)
+ * @method string formErrors($errors, array $options = null)
+ * @method string formFile($name, $attribs = null)
+ * @method string formHidden($name, $value = null, array $attribs = null)
+ * @method string formImage($name, $value = null, $attribs = null)
+ * @method string formLabel($name, $value = null, array $attribs = null)
+ * @method string formMultiCheckbox($name, $value = null, $attribs = null, $options = null, $listsep = "<br />\n")
+ * @method string formNote($name, $value = null)
+ * @method string formPassword($name, $value = null, $attribs = null)
+ * @method string formRadio($name, $value = null, $attribs = null, $options = null, $listsep = "<br />\n")
+ * @method string formReset($name = '', $value = 'Reset', $attribs = null)
+ * @method string formSelect($name, $value = null, $attribs = null, $options = null, $listsep = "<br />\n")
+ * @method string formSubmit($name, $value = null, $attribs = null)
+ * @method string formText($name, $value = null, $attribs = null)
+ * @method string formTextarea($name, $value = null, $attribs = null)
+ * @method Zend_View_Helper_Gravatar gravatar($email = "", $options = array(), $attribs = array())
+ * @method Zend_View_Helper_HeadLink headLink(array $attributes = null, $placement = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
+ * @method Zend_View_Helper_HeadMeta headMeta($content = null, $keyValue = null, $keyType = 'name', $modifiers = array(), $placement = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
+ * @method Zend_View_Helper_HeadScript headScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
+ * @method Zend_View_Helper_HeadStyle headStyle($content = null, $placement = 'APPEND', $attributes = array())
+ * @method Zend_View_Helper_HeadTitle headTitle($title = null, $setType = null)
+ * @method string htmlFlash($data, array $attribs = array(), array $params = array(), $content = null)
+ * @method string htmlList(array $items, $ordered = false, $attribs = false, $escape = true)
+ * @method string htmlObject($data, $type, array $attribs = array(), array $params = array(), $content = null)
+ * @method string htmlPage($data, array $attribs = array(), array $params = array(), $content = null)
+ * @method string htmlQuicktime($data, array $attribs = array(), array $params = array(), $content = null)
+ * @method Zend_View_Helper_InlineScript inlineScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
+ * @method string|void json($data, $keepLayouts = false, $encodeData = true)
+ * @method Zend_View_Helper_Layout layout()
+ * @method Zend_View_Helper_Navigation navigation(Zend_Navigation_Container $container = null)
+ * @method string paginationControl(Zend_Paginator $paginator = null, $scrollingStyle = null, $partial = null, $params = null)
+ * @method string partial($name = null, $module = null, $model = null)
+ * @method string partialLoop($name = null, $module = null, $model = null)
+ * @method Zend_View_Helper_Placeholder_Container_Abstract placeholder($name)
+ * @method void renderToPlaceholder($script, $placeholder)
+ * @method string serverUrl($requestUri = null)
+ * @method string translate($messageid = null)
+ * @method string url(array $urlOptions = array(), $name = null, $reset = false, $encode = true)
+ * @method Zend_Http_UserAgent userAgent(Zend_Http_UserAgent $userAgent = null)
+ */
+class Zend_View extends Zend_View_Abstract
+{
+ /**
+ * Whether or not to use streams to mimic short tags
+ * @var bool
+ */
+ private $_useViewStream = false;
+
+ /**
+ * Whether or not to use stream wrapper if short_open_tag is false
+ * @var bool
+ */
+ private $_useStreamWrapper = false;
+
+ /**
+ * Constructor
+ *
+ * Register Zend_View_Stream stream wrapper if short tags are disabled.
+ *
+ * @param array $config
+ * @return void
+ */
+ public function __construct($config = array())
+ {
+ $this->_useViewStream = (bool) ini_get('short_open_tag') ? false : true;
+ if ($this->_useViewStream) {
+ if (!in_array('zend.view', stream_get_wrappers())) {
+ stream_wrapper_register('zend.view', 'Zend_View_Stream');
+ }
+ }
+
+ if (array_key_exists('useStreamWrapper', $config)) {
+ $this->setUseStreamWrapper($config['useStreamWrapper']);
+ }
+
+ parent::__construct($config);
+ }
+
+ /**
+ * Set flag indicating if stream wrapper should be used if short_open_tag is off
+ *
+ * @param bool $flag
+ * @return Zend_View
+ */
+ public function setUseStreamWrapper($flag)
+ {
+ $this->_useStreamWrapper = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Should the stream wrapper be used if short_open_tag is off?
+ *
+ * @return bool
+ */
+ public function useStreamWrapper()
+ {
+ return $this->_useStreamWrapper;
+ }
+
+ /**
+ * Includes the view script in a scope with only public $this variables.
+ *
+ * @param string The view script to execute.
+ */
+ protected function _run()
+ {
+ if ($this->_useViewStream && $this->useStreamWrapper()) {
+ include 'zend.view://' . func_get_arg(0);
+ } else {
+ include func_get_arg(0);
+ }
+ }
+}
diff --git a/library/vendor/Zend/View/Abstract.php b/library/vendor/Zend/View/Abstract.php
new file mode 100644
index 0000000..edb577d
--- /dev/null
+++ b/library/vendor/Zend/View/Abstract.php
@@ -0,0 +1,1186 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** @see Zend_Loader */
+
+/** @see Zend_Loader_PluginLoader */
+
+/** @see Zend_View_Interface */
+
+/**
+ * Abstract class for Zend_View to help enforce private constructs.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_View_Abstract implements Zend_View_Interface
+{
+ /**
+ * Path stack for script, helper, and filter directories.
+ *
+ * @var array
+ */
+ private $_path = array(
+ 'script' => array(),
+ 'helper' => array(),
+ 'filter' => array(),
+ );
+
+ /**
+ * Script file name to execute
+ *
+ * @var string
+ */
+ private $_file = null;
+
+ /**
+ * Instances of helper objects.
+ *
+ * @var array
+ */
+ private $_helper = array();
+
+ /**
+ * Map of helper => class pairs to help in determining helper class from
+ * name
+ * @var array
+ */
+ private $_helperLoaded = array();
+
+ /**
+ * Map of helper => classfile pairs to aid in determining helper classfile
+ * @var array
+ */
+ private $_helperLoadedDir = array();
+
+ /**
+ * Stack of Zend_View_Filter names to apply as filters.
+ * @var array
+ */
+ private $_filter = array();
+
+ /**
+ * Stack of Zend_View_Filter objects that have been loaded
+ * @var array
+ */
+ private $_filterClass = array();
+
+ /**
+ * Map of filter => class pairs to help in determining filter class from
+ * name
+ * @var array
+ */
+ private $_filterLoaded = array();
+
+ /**
+ * Map of filter => classfile pairs to aid in determining filter classfile
+ * @var array
+ */
+ private $_filterLoadedDir = array();
+
+ /**
+ * Callback for escaping.
+ *
+ * @var string
+ */
+ private $_escape = 'htmlspecialchars';
+
+ /**
+ * Encoding to use in escaping mechanisms; defaults to utf-8
+ * @var string
+ */
+ private $_encoding = 'UTF-8';
+
+ /**
+ * Flag indicating whether or not LFI protection for rendering view scripts is enabled
+ * @var bool
+ */
+ private $_lfiProtectionOn = true;
+
+ /**
+ * Plugin loaders
+ * @var array
+ */
+ private $_loaders = array();
+
+ /**
+ * Plugin types
+ * @var array
+ */
+ private $_loaderTypes = array('filter', 'helper');
+
+ /**
+ * Strict variables flag; when on, undefined variables accessed in the view
+ * scripts will trigger notices
+ * @var boolean
+ */
+ private $_strictVars = false;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config Configuration key-value pairs.
+ */
+ public function __construct($config = array())
+ {
+ // set inital paths and properties
+ $this->setScriptPath(null);
+
+ // $this->setHelperPath(null);
+ $this->setFilterPath(null);
+
+ // user-defined escaping callback
+ if (array_key_exists('escape', $config)) {
+ $this->setEscape($config['escape']);
+ }
+
+ // encoding
+ if (array_key_exists('encoding', $config)) {
+ $this->setEncoding($config['encoding']);
+ }
+
+ // base path
+ if (array_key_exists('basePath', $config)) {
+ $prefix = 'Zend_View';
+ if (array_key_exists('basePathPrefix', $config)) {
+ $prefix = $config['basePathPrefix'];
+ }
+ $this->setBasePath($config['basePath'], $prefix);
+ }
+
+ // user-defined view script path
+ if (array_key_exists('scriptPath', $config)) {
+ $this->addScriptPath($config['scriptPath']);
+ }
+
+ // user-defined helper path
+ if (array_key_exists('helperPath', $config)) {
+ if (is_array($config['helperPath'])) {
+ foreach ($config['helperPath'] as $prefix => $path) {
+ $this->addHelperPath($path, $prefix);
+ }
+ } else {
+ $prefix = 'Zend_View_Helper';
+ if (array_key_exists('helperPathPrefix', $config)) {
+ $prefix = $config['helperPathPrefix'];
+ }
+ $this->addHelperPath($config['helperPath'], $prefix);
+ }
+ }
+
+ // user-defined filter path
+ if (array_key_exists('filterPath', $config)) {
+ if (is_array($config['filterPath'])) {
+ foreach ($config['filterPath'] as $prefix => $path) {
+ $this->addFilterPath($path, $prefix);
+ }
+ } else {
+ $prefix = 'Zend_View_Filter';
+ if (array_key_exists('filterPathPrefix', $config)) {
+ $prefix = $config['filterPathPrefix'];
+ }
+ $this->addFilterPath($config['filterPath'], $prefix);
+ }
+ }
+
+ // user-defined filters
+ if (array_key_exists('filter', $config)) {
+ $this->addFilter($config['filter']);
+ }
+
+ // strict vars
+ if (array_key_exists('strictVars', $config)) {
+ $this->strictVars($config['strictVars']);
+ }
+
+ // LFI protection flag
+ if (array_key_exists('lfiProtectionOn', $config)) {
+ $this->setLfiProtection($config['lfiProtectionOn']);
+ }
+
+ if (array_key_exists('assign', $config)
+ && is_array($config['assign'])
+ ) {
+ foreach ($config['assign'] as $key => $value) {
+ $this->assign($key, $value);
+ }
+ }
+
+ $this->init();
+ }
+
+ /**
+ * Return the template engine object
+ *
+ * Returns the object instance, as it is its own template engine
+ *
+ * @return Zend_View_Abstract
+ */
+ public function getEngine()
+ {
+ return $this;
+ }
+
+ /**
+ * Allow custom object initialization when extending Zend_View_Abstract or
+ * Zend_View
+ *
+ * Triggered by {@link __construct() the constructor} as its final action.
+ *
+ * @return void
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Prevent E_NOTICE for nonexistent values
+ *
+ * If {@link strictVars()} is on, raises a notice.
+ *
+ * @param string $key
+ * @return null
+ */
+ public function __get($key)
+ {
+ if ($this->_strictVars) {
+ trigger_error('Key "' . $key . '" does not exist', E_USER_NOTICE);
+ }
+
+ return null;
+ }
+
+ /**
+ * Allows testing with empty() and isset() to work inside
+ * templates.
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function __isset($key)
+ {
+ if ('_' != substr($key, 0, 1)) {
+ return isset($this->$key);
+ }
+
+ return false;
+ }
+
+ /**
+ * Directly assigns a variable to the view script.
+ *
+ * Checks first to ensure that the caller is not attempting to set a
+ * protected or private member (by checking for a prefixed underscore); if
+ * not, the public member is set; otherwise, an exception is raised.
+ *
+ * @param string $key The variable name.
+ * @param mixed $val The variable value.
+ * @return void
+ * @throws Zend_View_Exception if an attempt to set a private or protected
+ * member is detected
+ */
+ public function __set($key, $val)
+ {
+ if ('_' != substr($key, 0, 1)) {
+ $this->$key = $val;
+ return;
+ }
+
+ $e = new Zend_View_Exception('Setting private or protected class members is not allowed');
+ $e->setView($this);
+ throw $e;
+ }
+
+ /**
+ * Allows unset() on object properties to work
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key)
+ {
+ if ('_' != substr($key, 0, 1) && isset($this->$key)) {
+ unset($this->$key);
+ }
+ }
+
+ /**
+ * Accesses a helper object from within a script.
+ *
+ * If the helper class has a 'view' property, sets it with the current view
+ * object.
+ *
+ * @param string $name The helper name.
+ * @param array $args The parameters for the helper.
+ * @return string The result of the helper output.
+ */
+ public function __call($name, $args)
+ {
+ // is the helper already loaded?
+ $helper = $this->getHelper($name);
+
+ // call the helper method
+ return call_user_func_array(
+ array($helper, $name),
+ $args
+ );
+ }
+
+ /**
+ * Given a base path, sets the script, helper, and filter paths relative to it
+ *
+ * Assumes a directory structure of:
+ * <code>
+ * basePath/
+ * scripts/
+ * helpers/
+ * filters/
+ * </code>
+ *
+ * @param string $path
+ * @param string $prefix Prefix to use for helper and filter paths
+ * @return Zend_View_Abstract
+ */
+ public function setBasePath($path, $classPrefix = 'Zend_View')
+ {
+ $path = rtrim($path, '/');
+ $path = rtrim($path, '\\');
+ $path .= DIRECTORY_SEPARATOR;
+ $classPrefix = rtrim($classPrefix, '_') . '_';
+ $this->setScriptPath($path . 'scripts');
+ $this->setHelperPath($path . 'helpers', $classPrefix . 'Helper');
+ $this->setFilterPath($path . 'filters', $classPrefix . 'Filter');
+ return $this;
+ }
+
+ /**
+ * Given a base path, add script, helper, and filter paths relative to it
+ *
+ * Assumes a directory structure of:
+ * <code>
+ * basePath/
+ * scripts/
+ * helpers/
+ * filters/
+ * </code>
+ *
+ * @param string $path
+ * @param string $prefix Prefix to use for helper and filter paths
+ * @return Zend_View_Abstract
+ */
+ public function addBasePath($path, $classPrefix = 'Zend_View')
+ {
+ $path = rtrim($path, '/');
+ $path = rtrim($path, '\\');
+ $path .= DIRECTORY_SEPARATOR;
+ $classPrefix = rtrim($classPrefix, '_') . '_';
+ $this->addScriptPath($path . 'scripts');
+ $this->addHelperPath($path . 'helpers', $classPrefix . 'Helper');
+ $this->addFilterPath($path . 'filters', $classPrefix . 'Filter');
+ return $this;
+ }
+
+ /**
+ * Adds to the stack of view script paths in LIFO order.
+ *
+ * @param string|array The directory (-ies) to add.
+ * @return Zend_View_Abstract
+ */
+ public function addScriptPath($path)
+ {
+ $this->_addPath('script', $path);
+ return $this;
+ }
+
+ /**
+ * Resets the stack of view script paths.
+ *
+ * To clear all paths, use Zend_View::setScriptPath(null).
+ *
+ * @param string|array The directory (-ies) to set as the path.
+ * @return Zend_View_Abstract
+ */
+ public function setScriptPath($path)
+ {
+ $this->_path['script'] = array();
+ $this->_addPath('script', $path);
+ return $this;
+ }
+
+ /**
+ * Return full path to a view script specified by $name
+ *
+ * @param string $name
+ * @return false|string False if script not found
+ * @throws Zend_View_Exception if no script directory set
+ */
+ public function getScriptPath($name)
+ {
+ try {
+ $path = $this->_script($name);
+ return $path;
+ } catch (Zend_View_Exception $e) {
+ if (strstr($e->getMessage(), 'no view script directory set')) {
+ throw $e;
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Returns an array of all currently set script paths
+ *
+ * @return array
+ */
+ public function getScriptPaths()
+ {
+ return $this->_getPaths('script');
+ }
+
+ /**
+ * Set plugin loader for a particular plugin type
+ *
+ * @param Zend_Loader_PluginLoader $loader
+ * @param string $type
+ * @return Zend_View_Abstract
+ */
+ public function setPluginLoader(Zend_Loader_PluginLoader $loader, $type)
+ {
+ $type = strtolower($type);
+ if (!in_array($type, $this->_loaderTypes)) {
+ $e = new Zend_View_Exception(sprintf('Invalid plugin loader type "%s"', $type));
+ $e->setView($this);
+ throw $e;
+ }
+
+ $this->_loaders[$type] = $loader;
+ return $this;
+ }
+
+ /**
+ * Retrieve plugin loader for a specific plugin type
+ *
+ * @param string $type
+ * @return Zend_Loader_PluginLoader
+ */
+ public function getPluginLoader($type)
+ {
+ $type = strtolower($type);
+ if (!in_array($type, $this->_loaderTypes)) {
+ $e = new Zend_View_Exception(sprintf('Invalid plugin loader type "%s"; cannot retrieve', $type));
+ $e->setView($this);
+ throw $e;
+ }
+
+ if (!array_key_exists($type, $this->_loaders)) {
+ $prefix = 'Zend_View_';
+ $pathPrefix = 'Zend/View/';
+
+ $pType = ucfirst($type);
+ switch ($type) {
+ case 'filter':
+ case 'helper':
+ default:
+ $prefix .= $pType;
+ $pathPrefix .= $pType;
+ $loader = new Zend_Loader_PluginLoader(array(
+ $prefix => $pathPrefix
+ ));
+ $this->_loaders[$type] = $loader;
+ break;
+ }
+ }
+ return $this->_loaders[$type];
+ }
+
+ /**
+ * Adds to the stack of helper paths in LIFO order.
+ *
+ * @param string|array The directory (-ies) to add.
+ * @param string $classPrefix Class prefix to use with classes in this
+ * directory; defaults to Zend_View_Helper
+ * @return Zend_View_Abstract
+ */
+ public function addHelperPath($path, $classPrefix = 'Zend_View_Helper_')
+ {
+ return $this->_addPluginPath('helper', $classPrefix, (array) $path);
+ }
+
+ /**
+ * Resets the stack of helper paths.
+ *
+ * To clear all paths, use Zend_View::setHelperPath(null).
+ *
+ * @param string|array $path The directory (-ies) to set as the path.
+ * @param string $classPrefix The class prefix to apply to all elements in
+ * $path; defaults to Zend_View_Helper
+ * @return Zend_View_Abstract
+ */
+ public function setHelperPath($path, $classPrefix = 'Zend_View_Helper_')
+ {
+ unset($this->_loaders['helper']);
+ return $this->addHelperPath($path, $classPrefix);
+ }
+
+ /**
+ * Get full path to a helper class file specified by $name
+ *
+ * @param string $name
+ * @return string|false False on failure, path on success
+ */
+ public function getHelperPath($name)
+ {
+ return $this->_getPluginPath('helper', $name);
+ }
+
+ /**
+ * Returns an array of all currently set helper paths
+ *
+ * @return array
+ */
+ public function getHelperPaths()
+ {
+ return $this->getPluginLoader('helper')->getPaths();
+ }
+
+ /**
+ * Registers a helper object, bypassing plugin loader
+ *
+ * @param Zend_View_Helper_Abstract|object $helper
+ * @param string $name
+ * @return Zend_View_Abstract
+ * @throws Zend_View_Exception
+ */
+ public function registerHelper($helper, $name)
+ {
+ if (!is_object($helper)) {
+ $e = new Zend_View_Exception('View helper must be an object');
+ $e->setView($this);
+ throw $e;
+ }
+
+ if (!$helper instanceof Zend_View_Interface) {
+ if (!method_exists($helper, $name)) {
+ $e = new Zend_View_Exception(
+ 'View helper must implement Zend_View_Interface or have a method matching the name provided'
+ );
+ $e->setView($this);
+ throw $e;
+ }
+ }
+
+ if (method_exists($helper, 'setView')) {
+ $helper->setView($this);
+ }
+
+ $name = ucfirst($name);
+ $this->_helper[$name] = $helper;
+ return $this;
+ }
+
+ /**
+ * Get a helper by name
+ *
+ * @param string $name
+ * @return object
+ */
+ public function getHelper($name)
+ {
+ return $this->_getPlugin('helper', $name);
+ }
+
+ /**
+ * Get array of all active helpers
+ *
+ * Only returns those that have already been instantiated.
+ *
+ * @return array
+ */
+ public function getHelpers()
+ {
+ return $this->_helper;
+ }
+
+ /**
+ * Adds to the stack of filter paths in LIFO order.
+ *
+ * @param string|array The directory (-ies) to add.
+ * @param string $classPrefix Class prefix to use with classes in this
+ * directory; defaults to Zend_View_Filter
+ * @return Zend_View_Abstract
+ */
+ public function addFilterPath($path, $classPrefix = 'Zend_View_Filter_')
+ {
+ return $this->_addPluginPath('filter', $classPrefix, (array) $path);
+ }
+
+ /**
+ * Resets the stack of filter paths.
+ *
+ * To clear all paths, use Zend_View::setFilterPath(null).
+ *
+ * @param string|array The directory (-ies) to set as the path.
+ * @param string $classPrefix The class prefix to apply to all elements in
+ * $path; defaults to Zend_View_Filter
+ * @return Zend_View_Abstract
+ */
+ public function setFilterPath($path, $classPrefix = 'Zend_View_Filter_')
+ {
+ unset($this->_loaders['filter']);
+ return $this->addFilterPath($path, $classPrefix);
+ }
+
+ /**
+ * Get full path to a filter class file specified by $name
+ *
+ * @param string $name
+ * @return string|false False on failure, path on success
+ */
+ public function getFilterPath($name)
+ {
+ return $this->_getPluginPath('filter', $name);
+ }
+
+ /**
+ * Get a filter object by name
+ *
+ * @param string $name
+ * @return object
+ */
+ public function getFilter($name)
+ {
+ return $this->_getPlugin('filter', $name);
+ }
+
+ /**
+ * Return array of all currently active filters
+ *
+ * Only returns those that have already been instantiated.
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->_filter;
+ }
+
+ /**
+ * Returns an array of all currently set filter paths
+ *
+ * @return array
+ */
+ public function getFilterPaths()
+ {
+ return $this->getPluginLoader('filter')->getPaths();
+ }
+
+ /**
+ * Return associative array of path types => paths
+ *
+ * @return array
+ */
+ public function getAllPaths()
+ {
+ $paths = $this->_path;
+ $paths['helper'] = $this->getHelperPaths();
+ $paths['filter'] = $this->getFilterPaths();
+ return $paths;
+ }
+
+ /**
+ * Add one or more filters to the stack in FIFO order.
+ *
+ * @param string|array One or more filters to add.
+ * @return Zend_View_Abstract
+ */
+ public function addFilter($name)
+ {
+ foreach ((array) $name as $val) {
+ $this->_filter[] = $val;
+ }
+ return $this;
+ }
+
+ /**
+ * Resets the filter stack.
+ *
+ * To clear all filters, use Zend_View::setFilter(null).
+ *
+ * @param string|array One or more filters to set.
+ * @return Zend_View_Abstract
+ */
+ public function setFilter($name)
+ {
+ $this->_filter = array();
+ $this->addFilter($name);
+ return $this;
+ }
+
+ /**
+ * Sets the _escape() callback.
+ *
+ * @param mixed $spec The callback for _escape() to use.
+ * @return Zend_View_Abstract
+ */
+ public function setEscape($spec)
+ {
+ $this->_escape = $spec;
+ return $this;
+ }
+
+ /**
+ * Set LFI protection flag
+ *
+ * @param bool $flag
+ * @return Zend_View_Abstract
+ */
+ public function setLfiProtection($flag)
+ {
+ $this->_lfiProtectionOn = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Return status of LFI protection flag
+ *
+ * @return bool
+ */
+ public function isLfiProtectionOn()
+ {
+ return $this->_lfiProtectionOn;
+ }
+
+ /**
+ * Assigns variables to the view script via differing strategies.
+ *
+ * Zend_View::assign('name', $value) assigns a variable called 'name'
+ * with the corresponding $value.
+ *
+ * Zend_View::assign($array) assigns the array keys as variable
+ * names (with the corresponding array values).
+ *
+ * @see __set()
+ * @param string|array The assignment strategy to use.
+ * @param mixed (Optional) If assigning a named variable, use this
+ * as the value.
+ * @return Zend_View_Abstract Fluent interface
+ * @throws Zend_View_Exception if $spec is neither a string nor an array,
+ * or if an attempt to set a private or protected member is detected
+ */
+ public function assign($spec, $value = null)
+ {
+ // which strategy to use?
+ if (is_string($spec)) {
+ // assign by name and value
+ if ('_' == substr($spec, 0, 1)) {
+ $e = new Zend_View_Exception('Setting private or protected class members is not allowed');
+ $e->setView($this);
+ throw $e;
+ }
+ $this->$spec = $value;
+ } elseif (is_array($spec)) {
+ // assign from associative array
+ $error = false;
+ foreach ($spec as $key => $val) {
+ if ('_' == substr($key, 0, 1)) {
+ $error = true;
+ break;
+ }
+ $this->$key = $val;
+ }
+ if ($error) {
+ $e = new Zend_View_Exception('Setting private or protected class members is not allowed');
+ $e->setView($this);
+ throw $e;
+ }
+ } else {
+ $e = new Zend_View_Exception('assign() expects a string or array, received ' . gettype($spec));
+ $e->setView($this);
+ throw $e;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return list of all assigned variables
+ *
+ * Returns all public properties of the object. Reflection is not used
+ * here as testing reflection properties for visibility is buggy.
+ *
+ * @return array
+ */
+ public function getVars()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if ('_' == substr($key, 0, 1)) {
+ unset($vars[$key]);
+ }
+ }
+
+ return $vars;
+ }
+
+ /**
+ * Clear all assigned variables
+ *
+ * Clears all variables assigned to Zend_View either via {@link assign()} or
+ * property overloading ({@link __set()}).
+ *
+ * @return void
+ */
+ public function clearVars()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if ('_' != substr($key, 0, 1)) {
+ unset($this->$key);
+ }
+ }
+ }
+
+ /**
+ * Processes a view script and returns the output.
+ *
+ * @param string $name The script name to process.
+ * @return string The script output.
+ */
+ public function render($name)
+ {
+ // find the script file name using the parent private method
+ $this->_file = $this->_script($name);
+ unset($name); // remove $name from local scope
+
+ ob_start();
+ $this->_run($this->_file);
+
+ return $this->_filter(ob_get_clean()); // filter output
+ }
+
+ /**
+ * Escapes a value for output in a view script.
+ *
+ * If escaping mechanism is one of htmlspecialchars or htmlentities, uses
+ * {@link $_encoding} setting.
+ *
+ * @param mixed $var The output to escape.
+ * @return mixed The escaped value.
+ */
+ public function escape($var)
+ {
+ if (in_array($this->_escape, array('htmlspecialchars', 'htmlentities'))) {
+ return call_user_func($this->_escape, $var, ENT_COMPAT, $this->_encoding);
+ }
+
+ if (1 == func_num_args()) {
+ return call_user_func($this->_escape, $var);
+ }
+ $args = func_get_args();
+ return call_user_func_array($this->_escape, $args);
+ }
+
+ /**
+ * Set encoding to use with htmlentities() and htmlspecialchars()
+ *
+ * @param string $encoding
+ * @return Zend_View_Abstract
+ */
+ public function setEncoding($encoding)
+ {
+ $this->_encoding = $encoding;
+ return $this;
+ }
+
+ /**
+ * Return current escape encoding
+ *
+ * @return string
+ */
+ public function getEncoding()
+ {
+ return $this->_encoding;
+ }
+
+ /**
+ * Enable or disable strict vars
+ *
+ * If strict variables are enabled, {@link __get()} will raise a notice
+ * when a variable is not defined.
+ *
+ * Use in conjunction with {@link Zend_View_Helper_DeclareVars the declareVars() helper}
+ * to enforce strict variable handling in your view scripts.
+ *
+ * @param boolean $flag
+ * @return Zend_View_Abstract
+ */
+ public function strictVars($flag = true)
+ {
+ $this->_strictVars = ($flag) ? true : false;
+
+ return $this;
+ }
+
+ /**
+ * Finds a view script from the available directories.
+ *
+ * @param string $name The base name of the script.
+ * @return void
+ */
+ protected function _script($name)
+ {
+ if ($this->isLfiProtectionOn() && preg_match('#\.\.[\\\/]#', $name)) {
+ $e = new Zend_View_Exception('Requested scripts may not include parent directory traversal ("../", "..\\" notation)');
+ $e->setView($this);
+ throw $e;
+ }
+
+ if (0 == count($this->_path['script'])) {
+ $e = new Zend_View_Exception('no view script directory set; unable to determine location for view script');
+ $e->setView($this);
+ throw $e;
+ }
+
+ foreach ($this->_path['script'] as $dir) {
+ if (is_readable($dir . $name)) {
+ return $dir . $name;
+ }
+ }
+
+ $message = "script '$name' not found in path ("
+ . implode(PATH_SEPARATOR, $this->_path['script'])
+ . ")";
+ $e = new Zend_View_Exception($message);
+ $e->setView($this);
+ throw $e;
+ }
+
+ /**
+ * Applies the filter callback to a buffer.
+ *
+ * @param string $buffer The buffer contents.
+ * @return string The filtered buffer.
+ */
+ private function _filter($buffer)
+ {
+ // loop through each filter class
+ foreach ($this->_filter as $name) {
+ // load and apply the filter class
+ $filter = $this->getFilter($name);
+ $buffer = call_user_func(array($filter, 'filter'), $buffer);
+ }
+
+ // done!
+ return $buffer;
+ }
+
+ /**
+ * Adds paths to the path stack in LIFO order.
+ *
+ * Zend_View::_addPath($type, 'dirname') adds one directory
+ * to the path stack.
+ *
+ * Zend_View::_addPath($type, $array) adds one directory for
+ * each array element value.
+ *
+ * In the case of filter and helper paths, $prefix should be used to
+ * specify what class prefix to use with the given path.
+ *
+ * @param string $type The path type ('script', 'helper', or 'filter').
+ * @param string|array $path The path specification.
+ * @param string $prefix Class prefix to use with path (helpers and filters
+ * only)
+ * @return void
+ */
+ private function _addPath($type, $path, $prefix = null)
+ {
+ foreach ((array) $path as $dir) {
+ // attempt to strip any possible separator and
+ // append the system directory separator
+ $dir = rtrim($dir, '/');
+ $dir = rtrim($dir, '\\');
+ $dir .= '/';
+
+ switch ($type) {
+ case 'script':
+ // add to the top of the stack.
+ array_unshift($this->_path[$type], $dir);
+ break;
+ case 'filter':
+ case 'helper':
+ default:
+ // add as array with prefix and dir keys
+ array_unshift($this->_path[$type], array('prefix' => $prefix, 'dir' => $dir));
+ break;
+ }
+ }
+ }
+
+ /**
+ * Resets the path stack for helpers and filters.
+ *
+ * @param string $type The path type ('helper' or 'filter').
+ * @param string|array $path The directory (-ies) to set as the path.
+ * @param string $classPrefix Class prefix to apply to elements of $path
+ */
+ private function _setPath($type, $path, $classPrefix = null)
+ {
+ $dir = DIRECTORY_SEPARATOR . ucfirst($type) . DIRECTORY_SEPARATOR;
+
+ switch ($type) {
+ case 'script':
+ $this->_path[$type] = array(dirname(__FILE__) . $dir);
+ $this->_addPath($type, $path);
+ break;
+ case 'filter':
+ case 'helper':
+ default:
+ $this->_path[$type] = array(array(
+ 'prefix' => 'Zend_View_' . ucfirst($type) . '_',
+ 'dir' => dirname(__FILE__) . $dir
+ ));
+ $this->_addPath($type, $path, $classPrefix);
+ break;
+ }
+ }
+
+ /**
+ * Return all paths for a given path type
+ *
+ * @param string $type The path type ('helper', 'filter', 'script')
+ * @return array
+ */
+ private function _getPaths($type)
+ {
+ return $this->_path[$type];
+ }
+
+ /**
+ * Register helper class as loaded
+ *
+ * @param string $name
+ * @param string $class
+ * @param string $file path to class file
+ * @return void
+ */
+ private function _setHelperClass($name, $class, $file)
+ {
+ $this->_helperLoadedDir[$name] = $file;
+ $this->_helperLoaded[$name] = $class;
+ }
+
+ /**
+ * Register filter class as loaded
+ *
+ * @param string $name
+ * @param string $class
+ * @param string $file path to class file
+ * @return void
+ */
+ private function _setFilterClass($name, $class, $file)
+ {
+ $this->_filterLoadedDir[$name] = $file;
+ $this->_filterLoaded[$name] = $class;
+ }
+
+ /**
+ * Add a prefixPath for a plugin type
+ *
+ * @param string $type
+ * @param string $classPrefix
+ * @param array $paths
+ * @return Zend_View_Abstract
+ */
+ private function _addPluginPath($type, $classPrefix, array $paths)
+ {
+ $loader = $this->getPluginLoader($type);
+ foreach ($paths as $path) {
+ $loader->addPrefixPath($classPrefix, $path);
+ }
+ return $this;
+ }
+
+ /**
+ * Get a path to a given plugin class of a given type
+ *
+ * @param string $type
+ * @param string $name
+ * @return string|false
+ */
+ private function _getPluginPath($type, $name)
+ {
+ $loader = $this->getPluginLoader($type);
+ if ($loader->isLoaded($name)) {
+ return $loader->getClassPath($name);
+ }
+
+ try {
+ $loader->load($name);
+ return $loader->getClassPath($name);
+ } catch (Zend_Loader_Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Retrieve a plugin object
+ *
+ * @param string $type
+ * @param string $name
+ * @return object
+ */
+ private function _getPlugin($type, $name)
+ {
+ $name = ucfirst($name);
+ switch ($type) {
+ case 'filter':
+ $storeVar = '_filterClass';
+ $store = $this->_filterClass;
+ break;
+ case 'helper':
+ $storeVar = '_helper';
+ $store = $this->_helper;
+ break;
+ }
+
+ if (!isset($store[$name])) {
+ $class = $this->getPluginLoader($type)->load($name);
+ $store[$name] = new $class();
+ if (method_exists($store[$name], 'setView')) {
+ $store[$name]->setView($this);
+ }
+ }
+
+ $this->$storeVar = $store;
+ return $store[$name];
+ }
+
+ /**
+ * Use to include the view script in a scope that only allows public
+ * members.
+ *
+ * @return mixed
+ */
+ abstract protected function _run();
+}
diff --git a/library/vendor/Zend/View/Exception.php b/library/vendor/Zend/View/Exception.php
new file mode 100644
index 0000000..87685b8
--- /dev/null
+++ b/library/vendor/Zend/View/Exception.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/**
+ * Zend_Exception
+ */
+
+
+/**
+ * Exception for Zend_View class.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Exception extends Zend_Exception
+{
+ protected $view = null;
+
+ public function setView(Zend_View_Interface $view = null)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ public function getView()
+ {
+ return $this->view;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Abstract.php b/library/vendor/Zend/View/Helper/Abstract.php
new file mode 100644
index 0000000..0946bc3
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Abstract.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_Interface
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_View_Helper_Abstract implements Zend_View_Helper_Interface
+{
+ /**
+ * View object
+ *
+ * @var Zend_View_Interface
+ */
+ public $view = null;
+
+ /**
+ * Set the View object
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_View_Helper_Abstract
+ */
+ public function setView(Zend_View_Interface $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Strategy pattern: currently unutilized
+ *
+ * @return void
+ */
+ public function direct()
+ {
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Action.php b/library/vendor/Zend/View/Helper/Action.php
new file mode 100644
index 0000000..5020a47
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Action.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for rendering output of a controller action
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Action extends Zend_View_Helper_Abstract
+{
+ /**
+ * @var string
+ */
+ public $defaultModule;
+
+ /**
+ * @var Zend_Controller_Dispatcher_Interface
+ */
+ public $dispatcher;
+
+ /**
+ * @var Zend_Controller_Request_Abstract
+ */
+ public $request;
+
+ /**
+ * @var Zend_Controller_Response_Abstract
+ */
+ public $response;
+
+ /**
+ * Constructor
+ *
+ * Grab local copies of various MVC objects
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $front = Zend_Controller_Front::getInstance();
+ $modules = $front->getControllerDirectory();
+ if (empty($modules)) {
+ $e = new Zend_View_Exception('Action helper depends on valid front controller instance');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $request = $front->getRequest();
+ $response = $front->getResponse();
+
+ if (empty($request) || empty($response)) {
+ $e = new Zend_View_Exception('Action view helper requires both a registered request and response object in the front controller instance');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->request = clone $request;
+ $this->response = clone $response;
+ $this->dispatcher = clone $front->getDispatcher();
+ $this->defaultModule = $front->getDefaultModule();
+ }
+
+ /**
+ * Reset object states
+ *
+ * @return void
+ */
+ public function resetObjects()
+ {
+ $params = $this->request->getUserParams();
+ foreach (array_keys($params) as $key) {
+ $this->request->setParam($key, null);
+ }
+
+ $this->response->clearBody();
+ $this->response->clearHeaders()
+ ->clearRawHeaders();
+ }
+
+ /**
+ * Retrieve rendered contents of a controller action
+ *
+ * If the action results in a forward or redirect, returns empty string.
+ *
+ * @param string $action
+ * @param string $controller
+ * @param string $module Defaults to default module
+ * @param array $params
+ * @return string
+ */
+ public function action($action, $controller, $module = null, array $params = array())
+ {
+ $this->resetObjects();
+ if (null === $module) {
+ $module = $this->defaultModule;
+ }
+
+ // clone the view object to prevent over-writing of view variables
+ $viewRendererObj = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
+ Zend_Controller_Action_HelperBroker::addHelper(clone $viewRendererObj);
+
+ $this->request->setParams($params)
+ ->setModuleName($module)
+ ->setControllerName($controller)
+ ->setActionName($action)
+ ->setDispatched(true);
+
+ $this->dispatcher->dispatch($this->request, $this->response);
+
+ // reset the viewRenderer object to it's original state
+ Zend_Controller_Action_HelperBroker::addHelper($viewRendererObj);
+
+
+ if (!$this->request->isDispatched()
+ || $this->response->isRedirect())
+ {
+ // forwards and redirects render nothing
+ return '';
+ }
+
+ $return = $this->response->getBody();
+ $this->resetObjects();
+ return $return;
+ }
+
+ /**
+ * Clone the current View
+ *
+ * @return Zend_View_Interface
+ */
+ public function cloneView()
+ {
+ $view = clone $this->view;
+ $view->clearVars();
+ return $view;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/BaseUrl.php b/library/vendor/Zend/View/Helper/BaseUrl.php
new file mode 100644
index 0000000..48f8049
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/BaseUrl.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** @see Zend_View_Helper_Abstract */
+
+/**
+ * Helper for retrieving the BaseUrl
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_BaseUrl extends Zend_View_Helper_Abstract
+{
+ /**
+ * BaseUrl
+ *
+ * @var string
+ */
+ protected $_baseUrl;
+
+ /**
+ * Returns site's base url, or file with base url prepended
+ *
+ * $file is appended to the base url for simplicity
+ *
+ * @param string|null $file
+ * @return string
+ */
+ public function baseUrl($file = null)
+ {
+ // Get baseUrl
+ $baseUrl = $this->getBaseUrl();
+
+ // Remove trailing slashes
+ if (null !== $file) {
+ $file = '/' . ltrim($file, '/\\');
+ }
+
+ return $baseUrl . $file;
+ }
+
+ /**
+ * Set BaseUrl
+ *
+ * @param string $base
+ * @return Zend_View_Helper_BaseUrl
+ */
+ public function setBaseUrl($base)
+ {
+ $this->_baseUrl = rtrim($base, '/\\');
+ return $this;
+ }
+
+ /**
+ * Get BaseUrl
+ *
+ * @return string
+ */
+ public function getBaseUrl()
+ {
+ if ($this->_baseUrl === null) {
+ /** @see Zend_Controller_Front */
+ $baseUrl = Zend_Controller_Front::getInstance()->getBaseUrl();
+
+ // Remove scriptname, eg. index.php from baseUrl
+ $baseUrl = $this->_removeScriptName($baseUrl);
+
+ $this->setBaseUrl($baseUrl);
+ }
+
+ return $this->_baseUrl;
+ }
+
+ /**
+ * Remove Script filename from baseurl
+ *
+ * @param string $url
+ * @return string
+ */
+ protected function _removeScriptName($url)
+ {
+ if (!isset($_SERVER['SCRIPT_NAME'])) {
+ // We can't do much now can we? (Well, we could parse out by ".")
+ return $url;
+ }
+
+ if (($pos = strripos($url, basename($_SERVER['SCRIPT_NAME']))) !== false) {
+ $url = substr($url, 0, $pos);
+ }
+
+ return $url;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Cycle.php b/library/vendor/Zend/View/Helper/Cycle.php
new file mode 100644
index 0000000..eb5fd51
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Cycle.php
@@ -0,0 +1,225 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Helper for alternating between set of values
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Cycle implements Iterator
+{
+
+ /**
+ * Default name
+ * @var string
+ */
+ const DEFAULT_NAME = 'default';
+
+ /**
+ * Pointers
+ *
+ * @var array
+ */
+ protected $_pointers = array(self::DEFAULT_NAME =>-1) ;
+
+ /**
+ * Array of values
+ *
+ * @var array
+ */
+ protected $_data = array(self::DEFAULT_NAME=>array());
+
+ /**
+ * Actual name of cycle
+ *
+ * @var string
+ */
+ protected $_name = self::DEFAULT_NAME;
+
+ /**
+ * Add elements to alternate
+ *
+ * @param array $data
+ * @param string $name
+ * @return Zend_View_Helper_Cycle
+ */
+ public function cycle(array $data = array(), $name = self::DEFAULT_NAME)
+ {
+ if(!empty($data))
+ $this->_data[$name] = $data;
+
+ $this->setName($name);
+ return $this;
+ }
+
+ /**
+ * Add elements to alternate
+ *
+ * @param array $data
+ * @param string $name
+ * @return Zend_View_Helper_Cycle
+ */
+ public function assign(Array $data , $name = self::DEFAULT_NAME)
+ {
+ $this->setName($name);
+ $this->_data[$name] = $data;
+ $this->rewind();
+ return $this;
+ }
+
+ /**
+ * Sets actual name of cycle
+ *
+ * @param string $name
+ * @return Zend_View_Helper_Cycle
+ */
+ public function setName($name = self::DEFAULT_NAME)
+ {
+ $this->_name = $name;
+
+ if(!isset($this->_data[$this->_name]))
+ $this->_data[$this->_name] = array();
+
+ if(!isset($this->_pointers[$this->_name]))
+ $this->rewind();
+
+ return $this;
+ }
+
+ /**
+ * Gets actual name of cycle
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->_name;
+ }
+
+
+ /**
+ * Return all elements
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->_data[$this->_name];
+ }
+
+ /**
+ * Turn helper into string
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return (string) $this->_data[$this->_name][$this->key()];
+ }
+
+ /**
+ * Cast to string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Move to next value
+ *
+ * @return Zend_View_Helper_Cycle
+ */
+ public function next()
+ {
+ $count = count($this->_data[$this->_name]);
+ if ($this->_pointers[$this->_name] == ($count - 1))
+ $this->_pointers[$this->_name] = 0;
+ else
+ $this->_pointers[$this->_name] = ++$this->_pointers[$this->_name];
+ return $this;
+ }
+
+ /**
+ * Move to previous value
+ *
+ * @return Zend_View_Helper_Cycle
+ */
+ public function prev()
+ {
+ $count = count($this->_data[$this->_name]);
+ if ($this->_pointers[$this->_name] <= 0)
+ $this->_pointers[$this->_name] = $count - 1;
+ else
+ $this->_pointers[$this->_name] = --$this->_pointers[$this->_name];
+ return $this;
+ }
+
+ /**
+ * Return iteration number
+ *
+ * @return int
+ */
+ public function key()
+ {
+ if ($this->_pointers[$this->_name] < 0)
+ return 0;
+ else
+ return $this->_pointers[$this->_name];
+ }
+
+ /**
+ * Rewind pointer
+ *
+ * @return Zend_View_Helper_Cycle
+ */
+ public function rewind()
+ {
+ $this->_pointers[$this->_name] = -1;
+ return $this;
+ }
+
+ /**
+ * Check if element is valid
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return isset($this->_data[$this->_name][$this->key()]);
+ }
+
+ /**
+ * Return current element
+ *
+ * @return mixed
+ */
+ public function current()
+ {
+ return $this->_data[$this->_name][$this->key()];
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/DeclareVars.php b/library/vendor/Zend/View/Helper/DeclareVars.php
new file mode 100644
index 0000000..a6c0fcf
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/DeclareVars.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for declaring default values of template variables
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_DeclareVars extends Zend_View_Helper_Abstract
+{
+ /**
+ * The view object that created this helper object.
+ * @var Zend_View
+ */
+ public $view;
+
+ /**
+ * Declare template vars to set default values and avoid notices when using strictVars
+ *
+ * Primarily for use when using {@link Zend_View_Abstract::strictVars() Zend_View strictVars()},
+ * this helper can be used to declare template variables that may or may
+ * not already be set in the view object, as well as to set default values.
+ * Arrays passed as arguments to the method will be used to set default
+ * values; otherwise, if the variable does not exist, it is set to an empty
+ * string.
+ *
+ * Usage:
+ * <code>
+ * $this->declareVars(
+ * 'varName1',
+ * 'varName2',
+ * array('varName3' => 'defaultValue',
+ * 'varName4' => array()
+ * )
+ * );
+ * </code>
+ *
+ * @param string|array variable number of arguments, all string names of variables to test
+ * @return void
+ */
+ public function declareVars()
+ {
+ $args = func_get_args();
+ foreach($args as $key) {
+ if (is_array($key)) {
+ foreach ($key as $name => $value) {
+ $this->_declareVar($name, $value);
+ }
+ } else if (!isset($view->$key)) {
+ $this->_declareVar($key);
+ }
+ }
+ }
+
+ /**
+ * Set a view variable
+ *
+ * Checks to see if a $key is set in the view object; if not, sets it to $value.
+ *
+ * @param string $key
+ * @param string $value Defaults to an empty string
+ * @return void
+ */
+ protected function _declareVar($key, $value = '')
+ {
+ if (!isset($this->view->$key)) {
+ $this->view->$key = $value;
+ }
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Doctype.php b/library/vendor/Zend/View/Helper/Doctype.php
new file mode 100644
index 0000000..866b193
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Doctype.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Registry */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for setting and retrieving the doctype
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Doctype extends Zend_View_Helper_Abstract
+{
+ /**#@+
+ * DocType constants
+ */
+ const XHTML11 = 'XHTML11';
+ const XHTML1_STRICT = 'XHTML1_STRICT';
+ const XHTML1_TRANSITIONAL = 'XHTML1_TRANSITIONAL';
+ const XHTML1_FRAMESET = 'XHTML1_FRAMESET';
+ const XHTML1_RDFA = 'XHTML1_RDFA';
+ const XHTML1_RDFA11 = 'XHTML1_RDFA11';
+ const XHTML_BASIC1 = 'XHTML_BASIC1';
+ const XHTML5 = 'XHTML5';
+ const HTML4_STRICT = 'HTML4_STRICT';
+ const HTML4_LOOSE = 'HTML4_LOOSE';
+ const HTML4_FRAMESET = 'HTML4_FRAMESET';
+ const HTML5 = 'HTML5';
+ const CUSTOM_XHTML = 'CUSTOM_XHTML';
+ const CUSTOM = 'CUSTOM';
+ /**#@-*/
+
+ /**
+ * Default DocType
+ * @var string
+ */
+ protected $_defaultDoctype = self::HTML4_LOOSE;
+
+ /**
+ * Registry containing current doctype and mappings
+ * @var ArrayObject
+ */
+ protected $_registry;
+
+ /**
+ * Registry key in which helper is stored
+ * @var string
+ */
+ protected $_regKey = 'Zend_View_Helper_Doctype';
+
+ /**
+ * Constructor
+ *
+ * Map constants to doctype strings, and set default doctype
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ if (!Zend_Registry::isRegistered($this->_regKey)) {
+ $this->_registry = new ArrayObject(array(
+ 'doctypes' => array(
+ self::XHTML11 => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
+ self::XHTML1_STRICT => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
+ self::XHTML1_TRANSITIONAL => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
+ self::XHTML1_FRAMESET => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
+ self::XHTML1_RDFA => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">',
+ self::XHTML1_RDFA11 => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.1//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-2.dtd">',
+ self::XHTML_BASIC1 => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">',
+ self::XHTML5 => '<!DOCTYPE html>',
+ self::HTML4_STRICT => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
+ self::HTML4_LOOSE => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
+ self::HTML4_FRAMESET => '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
+ self::HTML5 => '<!DOCTYPE html>',
+ )
+ ));
+ Zend_Registry::set($this->_regKey, $this->_registry);
+ $this->setDoctype($this->_defaultDoctype);
+ } else {
+ $this->_registry = Zend_Registry::get($this->_regKey);
+ }
+ }
+
+ /**
+ * Set or retrieve doctype
+ *
+ * @param string $doctype
+ * @return Zend_View_Helper_Doctype
+ */
+ public function doctype($doctype = null)
+ {
+ if (null !== $doctype) {
+ switch ($doctype) {
+ case self::XHTML11:
+ case self::XHTML1_STRICT:
+ case self::XHTML1_TRANSITIONAL:
+ case self::XHTML1_FRAMESET:
+ case self::XHTML_BASIC1:
+ case self::XHTML1_RDFA:
+ case self::XHTML1_RDFA11:
+ case self::XHTML5:
+ case self::HTML4_STRICT:
+ case self::HTML4_LOOSE:
+ case self::HTML4_FRAMESET:
+ case self::HTML5:
+ $this->setDoctype($doctype);
+ break;
+ default:
+ if (substr($doctype, 0, 9) != '<!DOCTYPE') {
+ $e = new Zend_View_Exception('The specified doctype is malformed');
+ $e->setView($this->view);
+ throw $e;
+ }
+ if (stristr($doctype, 'xhtml')) {
+ $type = self::CUSTOM_XHTML;
+ } else {
+ $type = self::CUSTOM;
+ }
+ $this->setDoctype($type);
+ $this->_registry['doctypes'][$type] = $doctype;
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set doctype
+ *
+ * @param string $doctype
+ * @return Zend_View_Helper_Doctype
+ */
+ public function setDoctype($doctype)
+ {
+ $this->_registry['doctype'] = $doctype;
+ return $this;
+ }
+
+ /**
+ * Retrieve doctype
+ *
+ * @return string
+ */
+ public function getDoctype()
+ {
+ return $this->_registry['doctype'];
+ }
+
+ /**
+ * Get doctype => string mappings
+ *
+ * @return array
+ */
+ public function getDoctypes()
+ {
+ return $this->_registry['doctypes'];
+ }
+
+ /**
+ * Is doctype XHTML?
+ *
+ * @return boolean
+ */
+ public function isXhtml()
+ {
+ return (stristr($this->getDoctype(), 'xhtml') ? true : false);
+ }
+
+ /**
+ * Is doctype strict?
+ *
+ * @return boolean
+ */
+ public function isStrict()
+ {
+ switch ( $this->getDoctype() )
+ {
+ case self::XHTML1_STRICT:
+ case self::XHTML11:
+ case self::HTML4_STRICT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Is doctype HTML5? (HeadMeta uses this for validation)
+ *
+ * @return booleean
+ */
+ public function isHtml5() {
+ return (stristr($this->doctype(), '<!DOCTYPE html>') ? true : false);
+ }
+
+ /**
+ * Is doctype RDFa?
+ *
+ * @return booleean
+ */
+ public function isRdfa() {
+ return (stristr($this->getDoctype(), 'rdfa') ? true : false);
+ }
+
+ /**
+ * String representation of doctype
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $doctypes = $this->getDoctypes();
+ return $doctypes[$this->getDoctype()];
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Fieldset.php b/library/vendor/Zend/View/Helper/Fieldset.php
new file mode 100644
index 0000000..5456782
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Fieldset.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_FormElement */
+
+/**
+ * Helper for rendering fieldsets
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Fieldset extends Zend_View_Helper_FormElement
+{
+ /**
+ * Render HTML form
+ *
+ * @param string $name Form name
+ * @param string $content Form content
+ * @param array $attribs HTML form attributes
+ * @return string
+ */
+ public function fieldset($name, $content, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $content, $attribs);
+ extract($info);
+
+ // get legend
+ $legend = '';
+ if (isset($attribs['legend'])) {
+ $legendString = trim($attribs['legend']);
+ if (!empty($legendString)) {
+ $legend = '<legend>'
+ . (($escape) ? $this->view->escape($legendString) : $legendString)
+ . '</legend>' . PHP_EOL;
+ }
+ unset($attribs['legend']);
+ }
+
+ // get id
+ if (!empty($id)) {
+ $id = ' id="' . $this->view->escape($id) . '"';
+ } else {
+ $id = '';
+ }
+
+ // render fieldset
+ $xhtml = '<fieldset'
+ . $id
+ . $this->_htmlAttribs($attribs)
+ . '>'
+ . $legend
+ . $content
+ . '</fieldset>';
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Form.php b/library/vendor/Zend/View/Helper/Form.php
new file mode 100644
index 0000000..87811d4
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Form.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_FormElement */
+
+/**
+ * Helper for rendering HTML forms
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Form extends Zend_View_Helper_FormElement
+{
+ /**
+ * Render HTML form
+ *
+ * @param string $name Form name
+ * @param null|array $attribs HTML form attributes
+ * @param false|string $content Form content
+ * @return string
+ */
+ public function form($name, $attribs = null, $content = false)
+ {
+ $info = $this->_getInfo($name, $content, $attribs);
+ extract($info);
+
+ if (!empty($id)) {
+ $id = ' id="' . $this->view->escape($id) . '"';
+ } else {
+ $id = '';
+ }
+
+ if (array_key_exists('id', $attribs) && empty($attribs['id'])) {
+ unset($attribs['id']);
+ }
+
+ if (!empty($name) && !($this->_isXhtml() && $this->_isStrictDoctype())) {
+ $name = ' name="' . $this->view->escape($name) . '"';
+ } else {
+ $name = '';
+ }
+
+ if ($this->_isHtml5() && array_key_exists('action', $attribs) && !$attribs['action']) {
+ unset($attribs['action']);
+ }
+
+ if ( array_key_exists('name', $attribs) && empty($attribs['id'])) {
+ unset($attribs['id']);
+ }
+
+ $xhtml = '<form'
+ . $id
+ . $name
+ . $this->_htmlAttribs($attribs)
+ . '>';
+
+ if (false !== $content) {
+ $xhtml .= $content
+ . '</form>';
+ }
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormButton.php b/library/vendor/Zend/View/Helper/FormButton.php
new file mode 100644
index 0000000..312952e
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormButton.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "button" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormButton extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'button' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The element value.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formButton($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, id, value, attribs, options, listsep, disable, escape
+
+ // Get content
+ $content = '';
+ if (isset($attribs['content'])) {
+ $content = $attribs['content'];
+ unset($attribs['content']);
+ } else {
+ $content = $value;
+ }
+
+ // Ensure type is sane
+ $type = 'button';
+ if (isset($attribs['type'])) {
+ $attribs['type'] = strtolower($attribs['type']);
+ if (in_array($attribs['type'], array('submit', 'reset', 'button'))) {
+ $type = $attribs['type'];
+ }
+ unset($attribs['type']);
+ }
+
+ // build the element
+ if ($disable) {
+ $attribs['disabled'] = 'disabled';
+ }
+
+ $content = ($escape) ? $this->view->escape($content) : $content;
+
+ $xhtml = '<button'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . ' type="' . $type . '"';
+
+ // add a value if one is given
+ if (!empty($value)) {
+ $xhtml .= ' value="' . $this->view->escape($value) . '"';
+ }
+
+ // add attributes and close start tag
+ $xhtml .= $this->_htmlAttribs($attribs) . '>';
+
+ // add content and end tag
+ $xhtml .= $content . '</button>';
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormCheckbox.php b/library/vendor/Zend/View/Helper/FormCheckbox.php
new file mode 100644
index 0000000..4acf3cb
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormCheckbox.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "checkbox" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormCheckbox extends Zend_View_Helper_FormElement
+{
+ /**
+ * Default checked/unchecked options
+ * @var array
+ */
+ protected static $_defaultCheckedOptions = array(
+ 'checkedValue' => '1',
+ 'uncheckedValue' => '0'
+ );
+
+ /**
+ * Generates a 'checkbox' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ * @param mixed $value The element value.
+ * @param array $attribs Attributes for the element tag.
+ * @return string The element XHTML.
+ */
+ public function formCheckbox($name, $value = null, $attribs = null, array $checkedOptions = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, id, value, attribs, options, listsep, disable
+
+ $checked = false;
+ if (isset($attribs['checked']) && $attribs['checked']) {
+ $checked = true;
+ unset($attribs['checked']);
+ } elseif (isset($attribs['checked'])) {
+ $checked = false;
+ unset($attribs['checked']);
+ }
+
+ $checkedOptions = self::determineCheckboxInfo($value, $checked, $checkedOptions);
+
+ // is the element disabled?
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ // build the element
+ $xhtml = '';
+ if ((!$disable && !strstr($name, '[]'))
+ && (empty($attribs['disableHidden']) || !$attribs['disableHidden'])
+ ) {
+ $xhtml = $this->_hidden($name, $checkedOptions['uncheckedValue']);
+ }
+
+ if (array_key_exists('disableHidden', $attribs)) {
+ unset($attribs['disableHidden']);
+ }
+
+ $xhtml .= '<input type="checkbox"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . ' value="' . $this->view->escape($checkedOptions['checkedValue']) . '"'
+ . $checkedOptions['checkedString']
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket();
+
+ return $xhtml;
+ }
+
+ /**
+ * Determine checkbox information
+ *
+ * @param string $value
+ * @param bool $checked
+ * @param array|null $checkedOptions
+ * @return array
+ */
+ public static function determineCheckboxInfo($value, $checked, array $checkedOptions = null)
+ {
+ // Checked/unchecked values
+ $checkedValue = null;
+ $uncheckedValue = null;
+ if (is_array($checkedOptions)) {
+ if (array_key_exists('checkedValue', $checkedOptions)) {
+ $checkedValue = (string) $checkedOptions['checkedValue'];
+ unset($checkedOptions['checkedValue']);
+ }
+ if (array_key_exists('uncheckedValue', $checkedOptions)) {
+ $uncheckedValue = (string) $checkedOptions['uncheckedValue'];
+ unset($checkedOptions['uncheckedValue']);
+ }
+ if (null === $checkedValue) {
+ $checkedValue = (string) array_shift($checkedOptions);
+ }
+ if (null === $uncheckedValue) {
+ $uncheckedValue = (string) array_shift($checkedOptions);
+ }
+ } elseif ($value !== null) {
+ $uncheckedValue = self::$_defaultCheckedOptions['uncheckedValue'];
+ } else {
+ $checkedValue = self::$_defaultCheckedOptions['checkedValue'];
+ $uncheckedValue = self::$_defaultCheckedOptions['uncheckedValue'];
+ }
+
+ // is the element checked?
+ $checkedString = '';
+ if ($checked || ((string) $value === $checkedValue)) {
+ $checkedString = ' checked="checked"';
+ $checked = true;
+ } else {
+ $checked = false;
+ }
+
+ // Checked value should be value if no checked options provided
+ if ($checkedValue == null) {
+ $checkedValue = $value;
+ }
+
+ return array(
+ 'checked' => $checked,
+ 'checkedString' => $checkedString,
+ 'checkedValue' => $checkedValue,
+ 'uncheckedValue' => $uncheckedValue,
+ );
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormElement.php b/library/vendor/Zend/View/Helper/FormElement.php
new file mode 100644
index 0000000..25f77b5
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormElement.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_HtmlElement
+ */
+
+/**
+ * Base helper for form elements. Extend this, don't use it on its own.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_View_Helper_FormElement extends Zend_View_Helper_HtmlElement
+{
+ /**
+ * @var Zend_Translate_Adapter|null
+ */
+ protected $_translator;
+
+ /**
+ * Get translator
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ return $this->_translator;
+ }
+
+ /**
+ * Set translator
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter|null $translator
+ * @return Zend_View_Helper_FormElement
+ */
+ public function setTranslator($translator = null)
+ {
+ if (null === $translator) {
+ $this->_translator = null;
+ } elseif ($translator instanceof Zend_Translate_Adapter) {
+ $this->_translator = $translator;
+ } elseif ($translator instanceof Zend_Translate) {
+ $this->_translator = $translator->getAdapter();
+ } else {
+ $e = new Zend_View_Exception('Invalid translator specified');
+ $e->setView($this->view);
+ throw $e;
+ }
+ return $this;
+ }
+
+ /**
+ * Converts parameter arguments to an element info array.
+ *
+ * E.g, formExample($name, $value, $attribs, $options, $listsep) is
+ * the same thing as formExample(array('name' => ...)).
+ *
+ * Note that you cannot pass a 'disable' param; you need to pass
+ * it as an 'attribs' key.
+ *
+ * @access protected
+ *
+ * @return array An element info array with keys for name, value,
+ * attribs, options, listsep, disable, and escape.
+ */
+ protected function _getInfo($name, $value = null, $attribs = null,
+ $options = null, $listsep = null
+ ) {
+ // the baseline info. note that $name serves a dual purpose;
+ // if an array, it's an element info array that will override
+ // these baseline values. as such, ignore it for the 'name'
+ // if it's an array.
+ $info = array(
+ 'name' => is_array($name) ? '' : $name,
+ 'id' => is_array($name) ? '' : $name,
+ 'value' => $value,
+ 'attribs' => $attribs,
+ 'options' => $options,
+ 'listsep' => $listsep,
+ 'disable' => false,
+ 'escape' => true,
+ );
+
+ // override with named args
+ if (is_array($name)) {
+ // only set keys that are already in info
+ foreach ($info as $key => $val) {
+ if (isset($name[$key])) {
+ $info[$key] = $name[$key];
+ }
+ }
+
+ // If all helper options are passed as an array, attribs may have
+ // been as well
+ if (null === $attribs) {
+ $attribs = $info['attribs'];
+ }
+ }
+
+ $attribs = (array)$attribs;
+
+ // Normalize readonly tag
+ if (array_key_exists('readonly', $attribs)) {
+ $attribs['readonly'] = 'readonly';
+ }
+
+ // Disable attribute
+ if (array_key_exists('disable', $attribs)) {
+ if (is_scalar($attribs['disable'])) {
+ // disable the element
+ $info['disable'] = (bool)$attribs['disable'];
+ } else if (is_array($attribs['disable'])) {
+ $info['disable'] = $attribs['disable'];
+ }
+ }
+
+ // Set ID for element
+ if (array_key_exists('id', $attribs)) {
+ $info['id'] = (string)$attribs['id'];
+ } else if ('' !== $info['name']) {
+ $info['id'] = trim(strtr($info['name'],
+ array('[' => '-', ']' => '')), '-');
+ }
+
+ // Remove NULL name attribute override
+ if (array_key_exists('name', $attribs) && is_null($attribs['name'])) {
+ unset($attribs['name']);
+ }
+
+ // Override name in info if specified in attribs
+ if (array_key_exists('name', $attribs) && $attribs['name'] != $info['name']) {
+ $info['name'] = $attribs['name'];
+ }
+
+ // Determine escaping from attributes
+ if (array_key_exists('escape', $attribs)) {
+ $info['escape'] = (bool)$attribs['escape'];
+ }
+
+ // Determine listsetp from attributes
+ if (array_key_exists('listsep', $attribs)) {
+ $info['listsep'] = (string)$attribs['listsep'];
+ }
+
+ // Remove attribs that might overwrite the other keys. We do this LAST
+ // because we needed the other attribs values earlier.
+ foreach ($info as $key => $val) {
+ if (array_key_exists($key, $attribs)) {
+ unset($attribs[$key]);
+ }
+ }
+ $info['attribs'] = $attribs;
+
+ // done!
+ return $info;
+ }
+
+ /**
+ * Creates a hidden element.
+ *
+ * We have this as a common method because other elements often
+ * need hidden elements for their operation.
+ *
+ * @access protected
+ *
+ * @param string $name The element name.
+ * @param string $value The element value.
+ * @param array $attribs Attributes for the element.
+ *
+ * @return string A hidden element.
+ */
+ protected function _hidden($name, $value = null, $attribs = null)
+ {
+ return '<input type="hidden"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' value="' . $this->view->escape($value) . '"'
+ . $this->_htmlAttribs($attribs) . $this->getClosingBracket();
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormErrors.php b/library/vendor/Zend/View/Helper/FormErrors.php
new file mode 100644
index 0000000..b57d851
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormErrors.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to render errors for a form element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormErrors extends Zend_View_Helper_FormElement
+{
+ /**
+ * @var Zend_Form_Element
+ */
+ protected $_element;
+
+ /**#@+
+ * @var string Element block start/end tags and separator
+ */
+ protected $_htmlElementEnd = '</li></ul>';
+ protected $_htmlElementStart = '<ul%s><li>';
+ protected $_htmlElementSeparator = '</li><li>';
+ /**#@-*/
+
+ /**
+ * Render form errors
+ *
+ * @param string|array $errors Error(s) to render
+ * @param array $options
+ * @return string
+ */
+ public function formErrors($errors, array $options = null)
+ {
+ $escape = true;
+ if (isset($options['escape'])) {
+ $escape = (bool) $options['escape'];
+ unset($options['escape']);
+ }
+
+ if (empty($options['class'])) {
+ $options['class'] = 'errors';
+ }
+
+ if (isset($options['elementStart'])) {
+ $this->setElementStart($options['elementStart']);
+ }
+ if (isset($options['elementEnd'])) {
+ $this->setElementEnd($options['elementEnd']);
+ }
+ if (isset($options['elementSeparator'])) {
+ $this->setElementSeparator($options['elementSeparator']);
+ }
+
+ $start = $this->getElementStart();
+ if (strstr($start, '%s')) {
+ $attribs = $this->_htmlAttribs($options);
+ $start = sprintf($start, $attribs);
+ }
+
+ if ($escape) {
+ foreach ($errors as $key => $error) {
+ $errors[$key] = $this->view->escape($error);
+ }
+ }
+
+ $html = $start
+ . implode($this->getElementSeparator(), (array) $errors)
+ . $this->getElementEnd();
+
+ return $html;
+ }
+
+ /**
+ * Set end string for displaying errors
+ *
+ * @param string $string
+ * @return Zend_View_Helper_FormErrors
+ */
+ public function setElementEnd($string)
+ {
+ $this->_htmlElementEnd = (string) $string;
+ return $this;
+ }
+
+ /**
+ * Retrieve end string for displaying errors
+ *
+ * @return string
+ */
+ public function getElementEnd()
+ {
+ return $this->_htmlElementEnd;
+ }
+
+ /**
+ * Set separator string for displaying errors
+ *
+ * @param string $string
+ * @return Zend_View_Helper_FormErrors
+ */
+ public function setElementSeparator($string)
+ {
+ $this->_htmlElementSeparator = (string) $string;
+ return $this;
+ }
+
+ /**
+ * Retrieve separator string for displaying errors
+ *
+ * @return string
+ */
+ public function getElementSeparator()
+ {
+ return $this->_htmlElementSeparator;
+ }
+
+ /**
+ * Set start string for displaying errors
+ *
+ * @param string $string
+ * @return Zend_View_Helper_FormErrors
+ */
+ public function setElementStart($string)
+ {
+ $this->_htmlElementStart = (string) $string;
+ return $this;
+ }
+
+ /**
+ * Retrieve start string for displaying errors
+ *
+ * @return string
+ */
+ public function getElementStart()
+ {
+ return $this->_htmlElementStart;
+ }
+
+}
diff --git a/library/vendor/Zend/View/Helper/FormFile.php b/library/vendor/Zend/View/Helper/FormFile.php
new file mode 100644
index 0000000..24a37db
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormFile.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "file" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormFile extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'file' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formFile($name, $attribs = null)
+ {
+ $info = $this->_getInfo($name, null, $attribs);
+ extract($info); // name, id, value, attribs, options, listsep, disable
+
+ // is it disabled?
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ // build the element
+ $xhtml = '<input type="file"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket();
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormHidden.php b/library/vendor/Zend/View/Helper/FormHidden.php
new file mode 100644
index 0000000..36c9c9b
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormHidden.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "hidden" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormHidden extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'hidden' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ * @param mixed $value The element value.
+ * @param array $attribs Attributes for the element tag.
+ * @return string The element XHTML.
+ */
+ public function formHidden($name, $value = null, array $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+ if (isset($id)) {
+ if (isset($attribs) && is_array($attribs)) {
+ $attribs['id'] = $id;
+ } else {
+ $attribs = array('id' => $id);
+ }
+ }
+ return $this->_hidden($name, $value, $attribs);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormImage.php b/library/vendor/Zend/View/Helper/FormImage.php
new file mode 100644
index 0000000..31d6545
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormImage.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate an "image" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormImage extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates an 'image' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The source ('src="..."') for the image.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formImage($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ // Determine if we should use the value or the src attribute
+ if (isset($attribs['src'])) {
+ $src = ' src="' . $this->view->escape($attribs['src']) . '"';
+ unset($attribs['src']);
+ } else {
+ $src = ' src="' . $this->view->escape($value) . '"';
+ unset($value);
+ }
+
+ // Do we have a value?
+ if (isset($value) && !empty($value)) {
+ $value = ' value="' . $this->view->escape($value) . '"';
+ } else {
+ $value = '';
+ }
+
+ // Disabled?
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ // build the element
+ $xhtml = '<input type="image"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . $src
+ . $value
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket();
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormLabel.php b/library/vendor/Zend/View/Helper/FormLabel.php
new file mode 100644
index 0000000..8b5fa71
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormLabel.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_View_Helper_FormElement **/
+
+/**
+ * Form label helper
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormLabel extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'label' element.
+ *
+ * @param string $name The form element name for which the label is being generated
+ * @param string $value The label text
+ * @param array $attribs Form element attributes (used to determine if disabled)
+ * @return string The element XHTML.
+ */
+ public function formLabel($name, $value = null, array $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable, escape
+
+ // build the element
+ if ($disable) {
+ // disabled; display nothing
+ return '';
+ }
+
+ $value = ($escape) ? $this->view->escape($value) : $value;
+ $for = (empty($attribs['disableFor']) || !$attribs['disableFor'])
+ ? ' for="' . $this->view->escape($id) . '"'
+ : '';
+ if (array_key_exists('disableFor', $attribs)) {
+ unset($attribs['disableFor']);
+ }
+
+ // enabled; display label
+ $xhtml = '<label'
+ . $for
+ . $this->_htmlAttribs($attribs)
+ . '>' . $value . '</label>';
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormMultiCheckbox.php b/library/vendor/Zend/View/Helper/FormMultiCheckbox.php
new file mode 100644
index 0000000..3147369
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormMultiCheckbox.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/** Zend_View_Helper_FormRadio */
+
+
+/**
+ * Helper to generate a set of checkbox button elements
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormMultiCheckbox extends Zend_View_Helper_FormRadio
+{
+ /**
+ * Input type to use
+ * @var string
+ */
+ protected $_inputType = 'checkbox';
+
+ /**
+ * Whether or not this element represents an array collection by default
+ * @var bool
+ */
+ protected $_isArray = true;
+
+ /**
+ * Generates a set of checkbox button elements.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The checkbox value to mark as 'checked'.
+ *
+ * @param array $options An array of key-value pairs where the array
+ * key is the checkbox value, and the array value is the radio text.
+ *
+ * @param array|string $attribs Attributes added to each radio.
+ *
+ * @return string The radio buttons XHTML.
+ */
+ public function formMultiCheckbox($name, $value = null, $attribs = null,
+ $options = null, $listsep = "<br />\n")
+ {
+ return $this->formRadio($name, $value, $attribs, $options, $listsep);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormNote.php b/library/vendor/Zend/View/Helper/FormNote.php
new file mode 100644
index 0000000..adeb411
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormNote.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to show an HTML note
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormNote extends Zend_View_Helper_FormElement
+{
+ /**
+ * Helper to show a "note" based on a hidden value.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param array $value The note to display. HTML is *not* escaped; the
+ * note is displayed as-is.
+ *
+ * @return string The element XHTML.
+ */
+ public function formNote($name, $value = null)
+ {
+ $info = $this->_getInfo($name, $value);
+ extract($info); // name, value, attribs, options, listsep, disable
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormPassword.php b/library/vendor/Zend/View/Helper/FormPassword.php
new file mode 100644
index 0000000..3cd880d
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormPassword.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "password" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormPassword extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'password' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The element value.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formPassword($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ // is it disabled?
+ $disabled = '';
+ if ($disable) {
+ // disabled
+ $disabled = ' disabled="disabled"';
+ }
+
+ // determine the XHTML value
+ $valueString = ' value=""';
+ if (array_key_exists('renderPassword', $attribs)) {
+ if ($attribs['renderPassword']) {
+ $valueString = ' value="' . $this->view->escape($value) . '"';
+ }
+ unset($attribs['renderPassword']);
+ }
+
+ // render the element
+ $xhtml = '<input type="password"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . $valueString
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket();
+
+ return $xhtml;
+ }
+
+}
diff --git a/library/vendor/Zend/View/Helper/FormRadio.php b/library/vendor/Zend/View/Helper/FormRadio.php
new file mode 100644
index 0000000..b67c0d6
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormRadio.php
@@ -0,0 +1,185 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a set of radio button elements
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormRadio extends Zend_View_Helper_FormElement
+{
+ /**
+ * Input type to use
+ * @var string
+ */
+ protected $_inputType = 'radio';
+
+ /**
+ * Whether or not this element represents an array collection by default
+ * @var bool
+ */
+ protected $_isArray = false;
+
+ /**
+ * Generates a set of radio button elements.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The radio value to mark as 'checked'.
+ *
+ * @param array $options An array of key-value pairs where the array
+ * key is the radio value, and the array value is the radio text.
+ *
+ * @param array|string $attribs Attributes added to each radio.
+ *
+ * @return string The radio buttons XHTML.
+ */
+ public function formRadio($name, $value = null, $attribs = null,
+ $options = null, $listsep = "<br />\n")
+ {
+
+ $info = $this->_getInfo($name, $value, $attribs, $options, $listsep);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ // retrieve attributes for labels (prefixed with 'label_' or 'label')
+ $label_attribs = array();
+ foreach ($attribs as $key => $val) {
+ $tmp = false;
+ $keyLen = strlen($key);
+ if ((6 < $keyLen) && (substr($key, 0, 6) == 'label_')) {
+ $tmp = substr($key, 6);
+ } elseif ((5 < $keyLen) && (substr($key, 0, 5) == 'label')) {
+ $tmp = substr($key, 5);
+ }
+
+ if ($tmp) {
+ // make sure first char is lowercase
+ $tmp[0] = strtolower($tmp[0]);
+ $label_attribs[$tmp] = $val;
+ unset($attribs[$key]);
+ }
+ }
+
+ $labelPlacement = 'append';
+ foreach ($label_attribs as $key => $val) {
+ switch (strtolower($key)) {
+ case 'placement':
+ unset($label_attribs[$key]);
+ $val = strtolower($val);
+ if (in_array($val, array('prepend', 'append'))) {
+ $labelPlacement = $val;
+ }
+ break;
+ }
+ }
+
+ // the radio button values and labels
+ $options = (array) $options;
+
+ // build the element
+ $xhtml = '';
+ $list = array();
+
+ // should the name affect an array collection?
+ $name = $this->view->escape($name);
+ if ($this->_isArray && ('[]' != substr($name, -2))) {
+ $name .= '[]';
+ }
+
+ // ensure value is an array to allow matching multiple times
+ $value = (array) $value;
+
+ // Set up the filter - Alnum + hyphen + underscore
+ $pattern = @preg_match('/\pL/u', 'a')
+ ? '/[^\p{L}\p{N}\-\_]/u' // Unicode
+ : '/[^a-zA-Z0-9\-\_]/'; // No Unicode
+ $filter = new Zend_Filter_PregReplace($pattern, "");
+
+ // add radio buttons to the list.
+ foreach ($options as $opt_value => $opt_label) {
+
+ // Should the label be escaped?
+ if ($escape) {
+ $opt_label = $this->view->escape($opt_label);
+ }
+
+ // is it disabled?
+ $disabled = '';
+ if (true === $disable) {
+ $disabled = ' disabled="disabled"';
+ } elseif (is_array($disable) && in_array($opt_value, $disable)) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ // is it checked?
+ $checked = '';
+ if (in_array($opt_value, $value)) {
+ $checked = ' checked="checked"';
+ }
+
+ // generate ID
+ $optId = $id . '-' . $filter->filter($opt_value);
+
+ // Wrap the radios in labels
+ $radio = '<label'
+ . $this->_htmlAttribs($label_attribs) . '>'
+ . (('prepend' == $labelPlacement) ? $opt_label : '')
+ . '<input type="' . $this->_inputType . '"'
+ . ' name="' . $name . '"'
+ . ' id="' . $optId . '"'
+ . ' value="' . $this->view->escape($opt_value) . '"'
+ . $checked
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket()
+ . (('append' == $labelPlacement) ? $opt_label : '')
+ . '</label>';
+
+ // add to the array of radio buttons
+ $list[] = $radio;
+ }
+
+ // XHTML or HTML for standard list separator?
+ if (!$this->_isXhtml() && false !== strpos($listsep, '<br />')) {
+ $listsep = str_replace('<br />', '<br>', $listsep);
+ }
+
+ // done!
+ $xhtml .= implode($listsep, $list);
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormReset.php b/library/vendor/Zend/View/Helper/FormReset.php
new file mode 100644
index 0000000..d6276f6
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormReset.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "reset" button
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormReset extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'reset' button.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The element value.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formReset($name = '', $value = 'Reset', $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ // check if disabled
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ // Render button
+ $xhtml = '<input type="reset"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . $disabled;
+
+ // add a value if one is given
+ if (! empty($value)) {
+ $xhtml .= ' value="' . $this->view->escape($value) . '"';
+ }
+
+ // add attributes, close, and return
+ $xhtml .= $this->_htmlAttribs($attribs) . $this->getClosingBracket();
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormSelect.php b/library/vendor/Zend/View/Helper/FormSelect.php
new file mode 100644
index 0000000..82709b1
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormSelect.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate "select" list of options
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormSelect extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates 'select' list of options.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The option value to mark as 'selected'; if an
+ * array, will mark all values in the array as 'selected' (used for
+ * multiple-select elements).
+ *
+ * @param array|string $attribs Attributes added to the 'select' tag.
+ * the optional 'optionClasses' attribute is used to add a class to
+ * the options within the select (associative array linking the option
+ * value to the desired class)
+ *
+ * @param array $options An array of key-value pairs where the array
+ * key is the radio value, and the array value is the radio text.
+ *
+ * @param string $listsep When disabled, use this list separator string
+ * between list values.
+ *
+ * @return string The select tag and options XHTML.
+ */
+ public function formSelect($name, $value = null, $attribs = null,
+ $options = null, $listsep = "<br />\n")
+ {
+ $info = $this->_getInfo($name, $value, $attribs, $options, $listsep);
+ extract($info); // name, id, value, attribs, options, listsep, disable
+
+ // force $value to array so we can compare multiple values to multiple
+ // options; also ensure it's a string for comparison purposes.
+ $value = array_map('strval', (array) $value);
+
+ // check if element may have multiple values
+ $multiple = '';
+
+ if (substr($name, -2) == '[]') {
+ // multiple implied by the name
+ $multiple = ' multiple="multiple"';
+ }
+
+ if (isset($attribs['multiple'])) {
+ // Attribute set
+ if ($attribs['multiple']) {
+ // True attribute; set multiple attribute
+ $multiple = ' multiple="multiple"';
+
+ // Make sure name indicates multiple values are allowed
+ if (!empty($multiple) && (substr($name, -2) != '[]')) {
+ $name .= '[]';
+ }
+ } else {
+ // False attribute; ensure attribute not set
+ $multiple = '';
+ }
+ unset($attribs['multiple']);
+ }
+
+ // handle the options classes
+ $optionClasses = array();
+ if (isset($attribs['optionClasses'])) {
+ $optionClasses = $attribs['optionClasses'];
+ unset($attribs['optionClasses']);
+ }
+
+ // now start building the XHTML.
+ $disabled = '';
+ if (true === $disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ // Build the surrounding select element first.
+ $xhtml = '<select'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . $multiple
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . ">\n ";
+
+ // build the list of options
+ $list = array();
+ $translator = $this->getTranslator();
+ foreach ((array) $options as $opt_value => $opt_label) {
+ if (is_array($opt_label)) {
+ $opt_disable = '';
+ if (is_array($disable) && in_array($opt_value, $disable)) {
+ $opt_disable = ' disabled="disabled"';
+ }
+ if (null !== $translator) {
+ $opt_value = $translator->translate($opt_value);
+ }
+ $opt_id = ' id="' . $this->view->escape($id) . '-optgroup-'
+ . $this->view->escape($opt_value) . '"';
+ $list[] = '<optgroup'
+ . $opt_disable
+ . $opt_id
+ . ' label="' . $this->view->escape($opt_value) .'">';
+ foreach ($opt_label as $val => $lab) {
+ $list[] = $this->_build($val, $lab, $value, $disable, $optionClasses);
+ }
+ $list[] = '</optgroup>';
+ } else {
+ $list[] = $this->_build($opt_value, $opt_label, $value, $disable, $optionClasses);
+ }
+ }
+
+ // add the options to the xhtml and close the select
+ $xhtml .= implode("\n ", $list) . "\n</select>";
+
+ return $xhtml;
+ }
+
+ /**
+ * Builds the actual <option> tag
+ *
+ * @param string $value Options Value
+ * @param string $label Options Label
+ * @param array $selected The option value(s) to mark as 'selected'
+ * @param array|bool $disable Whether the select is disabled, or individual options are
+ * @param array $optionClasses The classes to associate with each option value
+ * @return string Option Tag XHTML
+ */
+ protected function _build($value, $label, $selected, $disable, $optionClasses = array())
+ {
+ if (is_bool($disable)) {
+ $disable = array();
+ }
+
+ $class = null;
+ if (array_key_exists($value, $optionClasses)) {
+ $class = $optionClasses[$value];
+ }
+
+
+ $opt = '<option'
+ . ' value="' . $this->view->escape($value) . '"';
+
+ if ($class) {
+ $opt .= ' class="' . $class . '"';
+ }
+ // selected?
+ if (in_array((string) $value, $selected)) {
+ $opt .= ' selected="selected"';
+ }
+
+ // disabled?
+ if (in_array($value, $disable)) {
+ $opt .= ' disabled="disabled"';
+ }
+
+ $opt .= '>' . $this->view->escape($label) . "</option>";
+
+ return $opt;
+ }
+
+}
diff --git a/library/vendor/Zend/View/Helper/FormSubmit.php b/library/vendor/Zend/View/Helper/FormSubmit.php
new file mode 100644
index 0000000..959feb1
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormSubmit.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "submit" button
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormSubmit extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'submit' button.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The element value.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formSubmit($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable, id
+ // check if disabled
+ $disabled = '';
+ if ($disable) {
+ $disabled = ' disabled="disabled"';
+ }
+
+ if ($id) {
+ $id = ' id="' . $this->view->escape($id) . '"';
+ }
+
+ // Render the button.
+ $xhtml = '<input type="submit"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . $id
+ . ' value="' . $this->view->escape($value) . '"'
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket();
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormText.php b/library/vendor/Zend/View/Helper/FormText.php
new file mode 100644
index 0000000..8b2dbd9
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormText.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "text" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormText extends Zend_View_Helper_FormElement
+{
+ /**
+ * Generates a 'text' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are used in place of added parameters.
+ *
+ * @param mixed $value The element value.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formText($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ // build the element
+ $disabled = '';
+ if ($disable) {
+ // disabled
+ $disabled = ' disabled="disabled"';
+ }
+
+ $xhtml = '<input type="text"'
+ . ' name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . ' value="' . $this->view->escape($value) . '"'
+ . $disabled
+ . $this->_htmlAttribs($attribs)
+ . $this->getClosingBracket();
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/FormTextarea.php b/library/vendor/Zend/View/Helper/FormTextarea.php
new file mode 100644
index 0000000..7fa8442
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/FormTextarea.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Abstract class for extension
+ */
+
+
+/**
+ * Helper to generate a "textarea" element
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_FormTextarea extends Zend_View_Helper_FormElement
+{
+ /**
+ * The default number of rows for a textarea.
+ *
+ * @access public
+ *
+ * @var int
+ */
+ public $rows = 24;
+
+ /**
+ * The default number of columns for a textarea.
+ *
+ * @access public
+ *
+ * @var int
+ */
+ public $cols = 80;
+
+ /**
+ * Generates a 'textarea' element.
+ *
+ * @access public
+ *
+ * @param string|array $name If a string, the element name. If an
+ * array, all other parameters are ignored, and the array elements
+ * are extracted in place of added parameters.
+ *
+ * @param mixed $value The element value.
+ *
+ * @param array $attribs Attributes for the element tag.
+ *
+ * @return string The element XHTML.
+ */
+ public function formTextarea($name, $value = null, $attribs = null)
+ {
+ $info = $this->_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ // is it disabled?
+ $disabled = '';
+ if ($disable) {
+ // disabled.
+ $disabled = ' disabled="disabled"';
+ }
+
+ // Make sure that there are 'rows' and 'cols' values
+ // as required by the spec. noted by Orjan Persson.
+ if (empty($attribs['rows'])) {
+ $attribs['rows'] = (int) $this->rows;
+ }
+ if (empty($attribs['cols'])) {
+ $attribs['cols'] = (int) $this->cols;
+ }
+
+ // build the element
+ $xhtml = '<textarea name="' . $this->view->escape($name) . '"'
+ . ' id="' . $this->view->escape($id) . '"'
+ . $disabled
+ . $this->_htmlAttribs($attribs) . '>'
+ . $this->view->escape($value) . '</textarea>';
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Gravatar.php b/library/vendor/Zend/View/Helper/Gravatar.php
new file mode 100644
index 0000000..1afc4cb
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Gravatar.php
@@ -0,0 +1,361 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id: Doctype.php 16971 2009-07-22 18:05:45Z mikaelkael $
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_HtmlElement */
+
+/**
+ * Helper for retrieving avatars from gravatar.com
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @link http://pl.gravatar.com/site/implement/url
+ */
+class Zend_View_Helper_Gravatar extends Zend_View_Helper_HtmlElement
+{
+
+ /**
+ * URL to gravatar service
+ */
+ const GRAVATAR_URL = 'http://www.gravatar.com/avatar';
+ /**
+ * Secure URL to gravatar service
+ */
+ const GRAVATAR_URL_SECURE = 'https://secure.gravatar.com/avatar';
+
+ /**
+ * Gravatar rating
+ */
+ const RATING_G = 'g';
+ const RATING_PG = 'pg';
+ const RATING_R = 'r';
+ const RATING_X = 'x';
+
+ /**
+ * Default gravatar image value constants
+ */
+ const DEFAULT_404 = '404';
+ const DEFAULT_MM = 'mm';
+ const DEFAULT_IDENTICON = 'identicon';
+ const DEFAULT_MONSTERID = 'monsterid';
+ const DEFAULT_WAVATAR = 'wavatar';
+
+ /**
+ * Options
+ *
+ * @var array
+ */
+ protected $_options = array(
+ 'img_size' => 80,
+ 'default_img' => self::DEFAULT_MM,
+ 'rating' => self::RATING_G,
+ 'secure' => null,
+ );
+
+ /**
+ * Email Adress
+ *
+ * @var string
+ */
+ protected $_email;
+
+ /**
+ * Attributes for HTML image tag
+ *
+ * @var array
+ */
+ protected $_attribs;
+
+ /**
+ * Returns an avatar from gravatar's service.
+ *
+ * $options may include the following:
+ * - 'img_size' int height of img to return
+ * - 'default_img' string img to return if email adress has not found
+ * - 'rating' string rating parameter for avatar
+ * - 'secure' bool load from the SSL or Non-SSL location
+ *
+ * @see http://pl.gravatar.com/site/implement/url
+ * @see http://pl.gravatar.com/site/implement/url More information about gravatar's service.
+ * @param string|null $email Email adress.
+ * @param null|array $options Options
+ * @param array $attribs Attributes for image tag (title, alt etc.)
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function gravatar($email = "", $options = array(), $attribs = array())
+ {
+ $this->setEmail($email);
+ $this->setOptions($options);
+ $this->setAttribs($attribs);
+ return $this;
+ }
+
+ /**
+ * Configure state
+ *
+ * @param array $options
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function setOptions(array $options)
+ {
+ foreach ($options as $key => $value) {
+ $method = 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
+ if (method_exists($this, $method)) {
+ $this->{$method}($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Get img size
+ *
+ * @return int The img size
+ */
+ public function getImgSize()
+ {
+ return $this->_options['img_size'];
+ }
+
+ /**
+ * Set img size in pixels
+ *
+ * @param int $imgSize Size of img must be between 1 and 512
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function setImgSize($imgSize)
+ {
+ $this->_options['img_size'] = (int) $imgSize;
+ return $this;
+ }
+
+ /**
+ * Get default img
+ *
+ * @return string
+ */
+ public function getDefaultImg()
+ {
+ return $this->_options['default_img'];
+ }
+
+ /**
+ * Set default img
+ *
+ * Can be either an absolute URL to an image, or one of the DEFAULT_* constants
+ *
+ * @param string $defaultImg
+ * @link http://pl.gravatar.com/site/implement/url More information about default image.
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function setDefaultImg($defaultImg)
+ {
+ $this->_options['default_img'] = urlencode($defaultImg);
+ return $this;
+ }
+
+ /**
+ * Set rating value
+ *
+ * Must be one of the RATING_* constants
+ *
+ * @param string $rating Value for rating. Allowed values are: g, px, r,x
+ * @link http://pl.gravatar.com/site/implement/url More information about rating.
+ * @throws Zend_View_Exception
+ */
+ public function setRating($rating)
+ {
+ switch ($rating) {
+ case self::RATING_G:
+ case self::RATING_PG:
+ case self::RATING_R:
+ case self::RATING_X:
+ $this->_options['rating'] = $rating;
+ break;
+ default:
+ throw new Zend_View_Exception(sprintf(
+ 'The rating value "%s" is not allowed',
+ $rating
+ ));
+ }
+ return $this;
+ }
+
+ /**
+ * Get rating value
+ *
+ * @return string
+ */
+ public function getRating()
+ {
+ return $this->_options['rating'];
+ }
+
+ /**
+ * Set email adress
+ *
+ * @param string $email
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function setEmail( $email )
+ {
+ $this->_email = $email;
+ return $this;
+ }
+
+ /**
+ * Get email adress
+ *
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->_email;
+ }
+
+ /**
+ * Load from an SSL or No-SSL location?
+ *
+ * @param bool $flag
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function setSecure($flag)
+ {
+ $this->_options['secure'] = ($flag === null) ? null : (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Get an SSL or a No-SSL location
+ *
+ * @return bool
+ */
+ public function getSecure()
+ {
+ if ($this->_options['secure'] === null) {
+ return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
+ }
+ return $this->_options['secure'];
+ }
+
+ /**
+ * Get attribs of image
+ *
+ * Warning!
+ * If you set src attrib, you get it, but this value will be overwritten in
+ * protected method _setSrcAttribForImg(). And finally your get other src
+ * value!
+ *
+ * @return array
+ */
+ public function getAttribs()
+ {
+ return $this->_attribs;
+ }
+
+ /**
+ * Set attribs for image tag
+ *
+ * Warning! You shouldn't set src attrib for image tag.
+ * This attrib is overwritten in protected method _setSrcAttribForImg().
+ * This method(_setSrcAttribForImg) is called in public method getImgTag().
+
+ * @param array $attribs
+ * @return Zend_View_Helper_Gravatar
+ */
+ public function setAttribs(array $attribs)
+ {
+ $this->_attribs = $attribs;
+ return $this;
+ }
+
+ /**
+ * Get URL to gravatar's service.
+ *
+ * @return string URL
+ */
+ protected function _getGravatarUrl()
+ {
+ return ($this->getSecure() === false) ? self::GRAVATAR_URL : self::GRAVATAR_URL_SECURE;
+ }
+
+ /**
+ * Get avatar url (including size, rating and default image oprions)
+ *
+ * @return string
+ */
+ protected function _getAvatarUrl()
+ {
+ $src = $this->_getGravatarUrl()
+ . '/'
+ . md5(strtolower(trim($this->getEmail())))
+ . '?s='
+ . $this->getImgSize()
+ . '&d='
+ . $this->getDefaultImg()
+ . '&r='
+ . $this->getRating();
+ return $src;
+ }
+
+ /**
+ * Set src attrib for image.
+ *
+ * You shouldn't set a own url value!
+ * It sets value, uses protected method _getAvatarUrl.
+ *
+ * If already exsist overwritten.
+ */
+ protected function _setSrcAttribForImg()
+ {
+ $attribs = $this->getAttribs();
+ $attribs['src'] = $this->_getAvatarUrl();
+ $this->setAttribs($attribs);
+ }
+
+ /**
+ * Return valid image tag
+ *
+ * @return string
+ */
+ public function getImgTag()
+ {
+ $this->_setSrcAttribForImg();
+ $html = '<img'
+ . $this->_htmlAttribs($this->getAttribs())
+ . $this->getClosingBracket();
+
+ return $html;
+ }
+
+ /**
+ * Return valid image tag
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getImgTag();
+
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HeadLink.php b/library/vendor/Zend/View/Helper/HeadLink.php
new file mode 100644
index 0000000..3b1bcb7
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HeadLink.php
@@ -0,0 +1,471 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Container_Standalone */
+
+/**
+ * Zend_Layout_View_Helper_HeadLink
+ *
+ * @see http://www.w3.org/TR/xhtml1/dtds.html
+ * @uses Zend_View_Helper_Placeholder_Container_Standalone
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @method $this appendAlternate($href, $type, $title, $extras)
+ * @method $this appendStylesheet($href, $media = 'screen', $conditionalStylesheet = false, array $extras = array())
+ * @method $this offsetSetAlternate($index, $href, $type, $title, $extras)
+ * @method $this offsetSetStylesheet($index, $href, $media = 'screen', $conditionalStylesheet = false, array $extras = array())
+ * @method $this prependAlternate($href, $type, $title, $extras)
+ * @method $this prependStylesheet($href, $media = 'screen', $conditionalStylesheet = false, array $extras = array())
+ * @method $this setAlternate($href, $type, $title, $extras)
+ * @method $this setStylesheet($href, $media = 'screen', $conditionalStylesheet = false, array $extras = array())
+ */
+class Zend_View_Helper_HeadLink extends Zend_View_Helper_Placeholder_Container_Standalone
+{
+ /**
+ * $_validAttributes
+ *
+ * @var array
+ */
+ protected $_itemKeys = array(
+ 'charset',
+ 'href',
+ 'hreflang',
+ 'id',
+ 'media',
+ 'rel',
+ 'rev',
+ 'type',
+ 'title',
+ 'extras',
+ 'sizes',
+ );
+
+ /**
+ * @var string registry key
+ */
+ protected $_regKey = 'Zend_View_Helper_HeadLink';
+
+ /**
+ * Constructor
+ *
+ * Use PHP_EOL as separator
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->setSeparator(PHP_EOL);
+ }
+
+ /**
+ * headLink() - View Helper Method
+ *
+ * Returns current object instance. Optionally, allows passing array of
+ * values to build link.
+ *
+ * @return Zend_View_Helper_HeadLink
+ */
+ public function headLink(array $attributes = null, $placement = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
+ {
+ if (null !== $attributes) {
+ $item = $this->createData($attributes);
+ switch ($placement) {
+ case Zend_View_Helper_Placeholder_Container_Abstract::SET:
+ $this->set($item);
+ break;
+ case Zend_View_Helper_Placeholder_Container_Abstract::PREPEND:
+ $this->prepend($item);
+ break;
+ case Zend_View_Helper_Placeholder_Container_Abstract::APPEND:
+ default:
+ $this->append($item);
+ break;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Overload method access
+ *
+ * Creates the following virtual methods:
+ * - appendStylesheet($href, $media, $conditionalStylesheet, $extras)
+ * - offsetSetStylesheet($index, $href, $media, $conditionalStylesheet, $extras)
+ * - prependStylesheet($href, $media, $conditionalStylesheet, $extras)
+ * - setStylesheet($href, $media, $conditionalStylesheet, $extras)
+ * - appendAlternate($href, $type, $title, $extras)
+ * - offsetSetAlternate($index, $href, $type, $title, $extras)
+ * - prependAlternate($href, $type, $title, $extras)
+ * - setAlternate($href, $type, $title, $extras)
+ *
+ * Items that may be added in the future:
+ * - Navigation? need to find docs on this
+ * - public function appendStart()
+ * - public function appendContents()
+ * - public function appendPrev()
+ * - public function appendNext()
+ * - public function appendIndex()
+ * - public function appendEnd()
+ * - public function appendGlossary()
+ * - public function appendAppendix()
+ * - public function appendHelp()
+ * - public function appendBookmark()
+ * - Other?
+ * - public function appendCopyright()
+ * - public function appendChapter()
+ * - public function appendSection()
+ * - public function appendSubsection()
+ *
+ * @param mixed $method
+ * @param mixed $args
+ * @return void
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?P<action>set|(ap|pre)pend|offsetSet)(?P<type>Stylesheet|Alternate)$/', $method, $matches)) {
+ $argc = count($args);
+ $action = $matches['action'];
+ $type = $matches['type'];
+ $index = null;
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (1 > $argc) {
+ $e = new Zend_View_Exception(sprintf('%s requires at least one argument', $method));
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ if (is_array($args[0])) {
+ $item = $this->createData($args[0]);
+ } else {
+ $dataMethod = 'createData' . $type;
+ $item = $this->$dataMethod($args);
+ }
+
+ if ($item) {
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Check if value is valid
+ *
+ * @param mixed $value
+ * @return boolean
+ */
+ protected function _isValid($value)
+ {
+ if (!$value instanceof stdClass) {
+ return false;
+ }
+
+ $vars = get_object_vars($value);
+ $keys = array_keys($vars);
+ $intersection = array_intersect($this->_itemKeys, $keys);
+ if (empty($intersection)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * append()
+ *
+ * @param array $value
+ * @return void
+ */
+ public function append($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('append() expects a data token; please use one of the custom append*() methods');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * offsetSet()
+ *
+ * @param string|int $index
+ * @param array $value
+ * @return void
+ */
+ public function offsetSet($index, $value): void
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('offsetSet() expects a data token; please use one of the custom offsetSet*() methods');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * prepend()
+ *
+ * @param array $value
+ * @return Zend_Layout_ViewHelper_HeadLink
+ */
+ public function prepend($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('prepend() expects a data token; please use one of the custom prepend*() methods');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * set()
+ *
+ * @param array $value
+ * @return Zend_Layout_ViewHelper_HeadLink
+ */
+ public function set($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('set() expects a data token; please use one of the custom set*() methods');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->set($value);
+ }
+
+
+ /**
+ * Create HTML link element from data item
+ *
+ * @param stdClass $item
+ * @return string
+ */
+ public function itemToString(stdClass $item)
+ {
+ $attributes = (array) $item;
+ $link = '<link ';
+
+ foreach ($this->_itemKeys as $itemKey) {
+ if (isset($attributes[$itemKey])) {
+ if(is_array($attributes[$itemKey])) {
+ foreach($attributes[$itemKey] as $key => $value) {
+ $link .= sprintf('%s="%s" ', $key, ($this->_autoEscape) ? $this->_escape($value) : $value);
+ }
+ } else {
+ $link .= sprintf('%s="%s" ', $itemKey, ($this->_autoEscape) ? $this->_escape($attributes[$itemKey]) : $attributes[$itemKey]);
+ }
+ }
+ }
+
+ if ($this->view instanceof Zend_View_Abstract) {
+ $link .= ($this->view->doctype()->isXhtml()) ? '/>' : '>';
+ } else {
+ $link .= '/>';
+ }
+
+ if (($link == '<link />') || ($link == '<link >')) {
+ return '';
+ }
+
+ if (isset($attributes['conditionalStylesheet'])
+ && !empty($attributes['conditionalStylesheet'])
+ && is_string($attributes['conditionalStylesheet']))
+ {
+ if (str_replace(' ', '', $attributes['conditionalStylesheet']) === '!IE') {
+ $link = '<!-->' . $link . '<!--';
+ }
+ $link = '<!--[if ' . $attributes['conditionalStylesheet'] . ']>' . $link . '<![endif]-->';
+ }
+
+ return $link;
+ }
+
+ /**
+ * Render link elements as string
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ $items[] = $this->itemToString($item);
+ }
+
+ return $indent . implode($this->_escape($this->getSeparator()) . $indent, $items);
+ }
+
+ /**
+ * Create data item for stack
+ *
+ * @param array $attributes
+ * @return stdClass
+ */
+ public function createData(array $attributes)
+ {
+ $data = (object) $attributes;
+ return $data;
+ }
+
+ /**
+ * Create item for stylesheet link item
+ *
+ * @param array $args
+ * @return stdClass|false Returns fals if stylesheet is a duplicate
+ */
+ public function createDataStylesheet(array $args)
+ {
+ $rel = 'stylesheet';
+ $type = 'text/css';
+ $media = 'screen';
+ $conditionalStylesheet = false;
+ $href = array_shift($args);
+
+ if ($this->_isDuplicateStylesheet($href)) {
+ return false;
+ }
+
+ if (0 < count($args)) {
+ $media = array_shift($args);
+ if(is_array($media)) {
+ $media = implode(',', $media);
+ } else {
+ $media = (string) $media;
+ }
+ }
+ if (0 < count($args)) {
+ $conditionalStylesheet = array_shift($args);
+ if(!empty($conditionalStylesheet) && is_string($conditionalStylesheet)) {
+ $conditionalStylesheet = (string) $conditionalStylesheet;
+ } else {
+ $conditionalStylesheet = null;
+ }
+ }
+
+ if(0 < count($args) && is_array($args[0])) {
+ $extras = array_shift($args);
+ $extras = (array) $extras;
+ }
+
+ $attributes = compact('rel', 'type', 'href', 'media', 'conditionalStylesheet', 'extras');
+ return $this->createData($this->_applyExtras($attributes));
+ }
+
+ /**
+ * Is the linked stylesheet a duplicate?
+ *
+ * @param string $uri
+ * @return bool
+ */
+ protected function _isDuplicateStylesheet($uri)
+ {
+ foreach ($this->getContainer() as $item) {
+ if (($item->rel == 'stylesheet') && ($item->href == $uri)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Create item for alternate link item
+ *
+ * @param array $args
+ * @return stdClass
+ */
+ public function createDataAlternate(array $args)
+ {
+ if (3 > count($args)) {
+ $e = new Zend_View_Exception(sprintf('Alternate tags require 3 arguments; %s provided', count($args)));
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $rel = 'alternate';
+ $href = array_shift($args);
+ $type = array_shift($args);
+ $title = array_shift($args);
+
+ if(0 < count($args) && is_array($args[0])) {
+ $extras = array_shift($args);
+ $extras = (array) $extras;
+
+ if(isset($extras['media']) && is_array($extras['media'])) {
+ $extras['media'] = implode(',', $extras['media']);
+ }
+ }
+
+ $href = (string) $href;
+ $type = (string) $type;
+ $title = (string) $title;
+
+ $attributes = compact('rel', 'href', 'type', 'title', 'extras');
+ return $this->createData($this->_applyExtras($attributes));
+ }
+
+ /**
+ * Apply any overrides specified in the 'extras' array
+ * @param array $attributes
+ * @return array
+ */
+ protected function _applyExtras($attributes)
+ {
+ if (isset($attributes['extras'])) {
+ foreach ($attributes['extras'] as $eKey=>$eVal) {
+ if (isset($attributes[$eKey])) {
+ $attributes[$eKey] = $eVal;
+ unset($attributes['extras'][$eKey]);
+ }
+ }
+ }
+ return $attributes;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HeadMeta.php b/library/vendor/Zend/View/Helper/HeadMeta.php
new file mode 100644
index 0000000..88cd3a7
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HeadMeta.php
@@ -0,0 +1,440 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Container_Standalone */
+
+/**
+ * Zend_Layout_View_Helper_HeadMeta
+ *
+ * @see http://www.w3.org/TR/xhtml1/dtds.html
+ * @uses Zend_View_Helper_Placeholder_Container_Standalone
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @method $this appendHttpEquiv($keyValue, $content, $conditionalHttpEquiv)
+ * @method $this appendName($keyValue, $content, $conditionalName)
+ * @method $this appendProperty($property, $content, $modifiers)
+ * @method $this offsetSetHttpEquiv($index, $keyValue, $content, $conditionalHttpEquiv)
+ * @method $this offsetSetName($index, $keyValue, $content, $conditionalName)
+ * @method $this offsetSetProperty($index, $property, $content, $modifiers)
+ * @method $this prependHttpEquiv($keyValue, $content, $conditionalHttpEquiv)
+ * @method $this prependName($keyValue, $content, $conditionalName)
+ * @method $this prependProperty($property, $content, $modifiers)
+ * @method $this setCharset($charset)
+ * @method $this setHttpEquiv($keyValue, $content, $modifiers)
+ * @method $this setName($keyValue, $content, $modifiers)
+ * @method $this setProperty($property, $content, $modifiers)
+ */
+class Zend_View_Helper_HeadMeta extends Zend_View_Helper_Placeholder_Container_Standalone
+{
+ /**
+ * Types of attributes
+ * @var array
+ */
+ protected $_typeKeys = array('name', 'http-equiv', 'charset', 'property');
+ protected $_requiredKeys = array('content');
+ protected $_modifierKeys = array('lang', 'scheme');
+
+ /**
+ * @var string registry key
+ */
+ protected $_regKey = 'Zend_View_Helper_HeadMeta';
+
+ /**
+ * Constructor
+ *
+ * Set separator to PHP_EOL
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Retrieve object instance; optionally add meta tag
+ *
+ * @param string $content
+ * @param string $keyValue
+ * @param string $keyType
+ * @param array $modifiers
+ * @param string $placement
+ * @return Zend_View_Helper_HeadMeta
+ */
+ public function headMeta($content = null, $keyValue = null, $keyType = 'name', $modifiers = array(), $placement = Zend_View_Helper_Placeholder_Container_Abstract::APPEND)
+ {
+ if ((null !== $content) && (null !== $keyValue)) {
+ $item = $this->createData($keyType, $keyValue, $content, $modifiers);
+ $action = strtolower($placement);
+ switch ($action) {
+ case 'append':
+ case 'prepend':
+ case 'set':
+ $this->$action($item);
+ break;
+ default:
+ $this->append($item);
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ protected function _normalizeType($type)
+ {
+ switch ($type) {
+ case 'Name':
+ return 'name';
+ case 'HttpEquiv':
+ return 'http-equiv';
+ case 'Property':
+ return 'property';
+ default:
+ $e = new Zend_View_Exception(sprintf('Invalid type "%s" passed to _normalizeType', $type));
+ $e->setView($this->view);
+ throw $e;
+ }
+ }
+
+ /**
+ * Overload method access
+ *
+ * Allows the following 'virtual' methods:
+ * - appendName($keyValue, $content, $modifiers = array())
+ * - offsetGetName($index, $keyValue, $content, $modifers = array())
+ * - prependName($keyValue, $content, $modifiers = array())
+ * - setName($keyValue, $content, $modifiers = array())
+ * - appendHttpEquiv($keyValue, $content, $modifiers = array())
+ * - offsetGetHttpEquiv($index, $keyValue, $content, $modifers = array())
+ * - prependHttpEquiv($keyValue, $content, $modifiers = array())
+ * - setHttpEquiv($keyValue, $content, $modifiers = array())
+ * - appendProperty($keyValue, $content, $modifiers = array())
+ * - offsetGetProperty($index, $keyValue, $content, $modifiers = array())
+ * - prependProperty($keyValue, $content, $modifiers = array())
+ * - setProperty($keyValue, $content, $modifiers = array())
+ *
+ * @param string $method
+ * @param array $args
+ * @return Zend_View_Helper_HeadMeta
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?P<action>set|(pre|ap)pend|offsetSet)(?P<type>Name|HttpEquiv|Property)$/', $method, $matches)) {
+ $action = $matches['action'];
+ $type = $this->_normalizeType($matches['type']);
+ $argc = count($args);
+ $index = null;
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (2 > $argc) {
+ $e = new Zend_View_Exception('Too few arguments provided; requires key value, and content');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ if (3 > $argc) {
+ $args[] = array();
+ }
+
+ $item = $this->createData($type, $args[0], $args[1], $args[2]);
+
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ return $this;
+ }
+
+ $this->$action($item);
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Create an HTML5-style meta charset tag. Something like <meta charset="utf-8">
+ *
+ * Not valid in a non-HTML5 doctype
+ *
+ * @param string $charset
+ * @return Zend_View_Helper_HeadMeta Provides a fluent interface
+ */
+ public function setCharset($charset)
+ {
+ $item = new stdClass;
+ $item->type = 'charset';
+ $item->charset = $charset;
+ $item->content = null;
+ $item->modifiers = array();
+ $this->set($item);
+ return $this;
+ }
+
+ /**
+ * Determine if item is valid
+ *
+ * @param mixed $item
+ * @return boolean
+ */
+ protected function _isValid($item)
+ {
+ if ((!$item instanceof stdClass)
+ || !isset($item->type)
+ || !isset($item->modifiers))
+ {
+ return false;
+ }
+
+ $isHtml5 = is_null($this->view) ? false : $this->view->doctype()->isHtml5();
+
+ if (!isset($item->content)
+ && (! $isHtml5 || (! $isHtml5 && $item->type !== 'charset'))) {
+ return false;
+ }
+
+ // <meta property= ... /> is only supported with doctype RDFa
+ if ( !is_null($this->view) && !$this->view->doctype()->isRdfa()
+ && $item->type === 'property') {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Append
+ *
+ * @param string $value
+ * @return void
+ * @throws Zend_View_Exception
+ */
+ public function append($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to append; please use appendMeta()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * OffsetSet
+ *
+ * @param string|int $index
+ * @param string $value
+ * @return void
+ * @throws Zend_View_Exception
+ */
+ public function offsetSet($index, $value): void
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to offsetSet; please use offsetSetName() or offsetSetHttpEquiv()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * OffsetUnset
+ *
+ * @param string|int $index
+ * @return void
+ * @throws Zend_View_Exception
+ */
+ public function offsetUnset($index): void
+ {
+ if (!in_array($index, $this->getContainer()->getKeys())) {
+ $e = new Zend_View_Exception('Invalid index passed to offsetUnset()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->getContainer()->offsetUnset($index);
+ }
+
+ /**
+ * Prepend
+ *
+ * @param string $value
+ * @return void
+ * @throws Zend_View_Exception
+ */
+ public function prepend($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to prepend; please use prependMeta()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Set
+ *
+ * @param string $value
+ * @return void
+ * @throws Zend_View_Exception
+ */
+ public function set($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to set; please use setMeta()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $container = $this->getContainer();
+ foreach ($container->getArrayCopy() as $index => $item) {
+ if ($item->type == $value->type && $item->{$item->type} == $value->{$value->type}) {
+ $this->offsetUnset($index);
+ }
+ }
+
+ return $this->append($value);
+ }
+
+ /**
+ * Build meta HTML string
+ *
+ * @param string $type
+ * @param string $typeValue
+ * @param string $content
+ * @param array $modifiers
+ * @return string
+ */
+ public function itemToString(stdClass $item)
+ {
+ if (!in_array($item->type, $this->_typeKeys)) {
+ $e = new Zend_View_Exception(sprintf('Invalid type "%s" provided for meta', $item->type));
+ $e->setView($this->view);
+ throw $e;
+ }
+ $type = $item->type;
+
+ $modifiersString = '';
+ foreach ($item->modifiers as $key => $value) {
+ if (!is_null($this->view) && $this->view->doctype()->isHtml5()
+ && $key == 'scheme') {
+ throw new Zend_View_Exception('Invalid modifier '
+ . '"scheme" provided; not supported by HTML5');
+ }
+ if (!in_array($key, $this->_modifierKeys)) {
+ continue;
+ }
+ $modifiersString .= $key . '="' . $this->_escape($value) . '" ';
+ }
+
+ if ($this->view instanceof Zend_View_Abstract) {
+ if ($this->view->doctype()->isHtml5()
+ && $type == 'charset') {
+ $tpl = ($this->view->doctype()->isXhtml())
+ ? '<meta %s="%s"/>'
+ : '<meta %s="%s">';
+ } elseif ($this->view->doctype()->isXhtml()) {
+ $tpl = '<meta %s="%s" content="%s" %s/>';
+ } else {
+ $tpl = '<meta %s="%s" content="%s" %s>';
+ }
+ } else {
+ $tpl = '<meta %s="%s" content="%s" %s/>';
+ }
+
+ $meta = sprintf(
+ $tpl,
+ $type,
+ $this->_escape($item->$type),
+ $this->_escape($item->content),
+ $modifiersString
+ );
+
+ if (isset($item->modifiers['conditional'])
+ && !empty($item->modifiers['conditional'])
+ && is_string($item->modifiers['conditional']))
+ {
+ if (str_replace(' ', '', $item->modifiers['conditional']) === '!IE') {
+ $meta = '<!-->' . $meta . '<!--';
+ }
+ $meta = '<!--[if ' . $this->_escape($item->modifiers['conditional']) . ']>' . $meta . '<![endif]-->';
+ }
+
+ return $meta;
+ }
+
+ /**
+ * Render placeholder as string
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+ $this->getContainer()->ksort();
+ try {
+ foreach ($this as $item) {
+ $items[] = $this->itemToString($item);
+ }
+ } catch (Zend_View_Exception $e) {
+ trigger_error($e->getMessage(), E_USER_WARNING);
+ return '';
+ }
+ return $indent . implode($this->_escape($this->getSeparator()) . $indent, $items);
+ }
+
+ /**
+ * Create data item for inserting into stack
+ *
+ * @param string $type
+ * @param string $typeValue
+ * @param string $content
+ * @param array $modifiers
+ * @return stdClass
+ */
+ public function createData($type, $typeValue, $content, array $modifiers)
+ {
+ $data = new stdClass;
+ $data->type = $type;
+ $data->$type = $typeValue;
+ $data->content = $content;
+ $data->modifiers = $modifiers;
+ return $data;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HeadScript.php b/library/vendor/Zend/View/Helper/HeadScript.php
new file mode 100644
index 0000000..5b8461d
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HeadScript.php
@@ -0,0 +1,512 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Container_Standalone */
+
+/**
+ * Helper for setting and retrieving script elements for HTML head section
+ *
+ * @uses Zend_View_Helper_Placeholder_Container_Standalone
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @method $this appendFile($src, $type = 'text/javascript', array $attrs = array())
+ * @method $this appendScript($script, $type = 'text/javascript', array $attrs = array())
+ * @method $this offsetSetFile($index, $src, $type = 'text/javascript', array $attrs = array())
+ * @method $this offsetSetScript($index, $script, $type = 'text/javascript', array $attrs = array())
+ * @method $this prependFile($src, $type = 'text/javascript', array $attrs = array())
+ * @method $this prependScript($script, $type = 'text/javascript', array $attrs = array())
+ * @method $this setFile($src, $type = 'text/javascript', array $attrs = array())
+ * @method $this setScript($script, $type = 'text/javascript', array $attrs = array())
+ */
+class Zend_View_Helper_HeadScript extends Zend_View_Helper_Placeholder_Container_Standalone
+{
+ /**#@+
+ * Script type contants
+ * @const string
+ */
+ const FILE = 'FILE';
+ const SCRIPT = 'SCRIPT';
+ /**#@-*/
+
+ /**
+ * Registry key for placeholder
+ * @var string
+ */
+ protected $_regKey = 'Zend_View_Helper_HeadScript';
+
+ /**
+ * Are arbitrary attributes allowed?
+ * @var bool
+ */
+ protected $_arbitraryAttributes = false;
+
+ /**#@+
+ * Capture type and/or attributes (used for hinting during capture)
+ * @var string
+ */
+ protected $_captureLock;
+ protected $_captureScriptType = null;
+ protected $_captureScriptAttrs = null;
+ protected $_captureType;
+ /**#@-*/
+
+ /**
+ * Optional allowed attributes for script tag
+ * @var array
+ */
+ protected $_optionalAttributes = array(
+ 'charset', 'defer', 'language', 'src'
+ );
+
+ /**
+ * Required attributes for script tag
+ * @var string
+ */
+ protected $_requiredAttributes = array('type');
+
+ /**
+ * Whether or not to format scripts using CDATA; used only if doctype
+ * helper is not accessible
+ * @var bool
+ */
+ public $useCdata = false;
+
+ /**
+ * Constructor
+ *
+ * Set separator to PHP_EOL.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Return headScript object
+ *
+ * Returns headScript helper object; optionally, allows specifying a script
+ * or script file to include.
+ *
+ * @param string $mode Script or file
+ * @param string $spec Script/url
+ * @param string $placement Append, prepend, or set
+ * @param array $attrs Array of script attributes
+ * @param string $type Script type and/or array of script attributes
+ * @return Zend_View_Helper_HeadScript
+ */
+ public function headScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
+ {
+ if ((null !== $spec) && is_string($spec)) {
+ $action = ucfirst(strtolower($mode));
+ $placement = strtolower($placement);
+ switch ($placement) {
+ case 'set':
+ case 'prepend':
+ case 'append':
+ $action = $placement . $action;
+ break;
+ default:
+ $action = 'append' . $action;
+ break;
+ }
+ $this->$action($spec, $type, $attrs);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Start capture action
+ *
+ * @param mixed $captureType
+ * @param string $typeOrAttrs
+ * @return void
+ */
+ public function captureStart($captureType = Zend_View_Helper_Placeholder_Container_Abstract::APPEND, $type = 'text/javascript', $attrs = array())
+ {
+ if ($this->_captureLock) {
+ $e = new Zend_View_Helper_Placeholder_Container_Exception('Cannot nest headScript captures');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->_captureLock = true;
+ $this->_captureType = $captureType;
+ $this->_captureScriptType = $type;
+ $this->_captureScriptAttrs = $attrs;
+ ob_start();
+ }
+
+ /**
+ * End capture action and store
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $content = ob_get_clean();
+ $type = $this->_captureScriptType;
+ $attrs = $this->_captureScriptAttrs;
+ $this->_captureScriptType = null;
+ $this->_captureScriptAttrs = null;
+ $this->_captureLock = false;
+
+ switch ($this->_captureType) {
+ case Zend_View_Helper_Placeholder_Container_Abstract::SET:
+ case Zend_View_Helper_Placeholder_Container_Abstract::PREPEND:
+ case Zend_View_Helper_Placeholder_Container_Abstract::APPEND:
+ $action = strtolower($this->_captureType) . 'Script';
+ break;
+ default:
+ $action = 'appendScript';
+ break;
+ }
+ $this->$action($content, $type, $attrs);
+ }
+
+ /**
+ * Overload method access
+ *
+ * Allows the following method calls:
+ * - appendFile($src, $type = 'text/javascript', $attrs = array())
+ * - offsetSetFile($index, $src, $type = 'text/javascript', $attrs = array())
+ * - prependFile($src, $type = 'text/javascript', $attrs = array())
+ * - setFile($src, $type = 'text/javascript', $attrs = array())
+ * - appendScript($script, $type = 'text/javascript', $attrs = array())
+ * - offsetSetScript($index, $src, $type = 'text/javascript', $attrs = array())
+ * - prependScript($script, $type = 'text/javascript', $attrs = array())
+ * - setScript($script, $type = 'text/javascript', $attrs = array())
+ *
+ * @param string $method
+ * @param array $args
+ * @return Zend_View_Helper_HeadScript
+ * @throws Zend_View_Exception if too few arguments or invalid method
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?P<action>set|(ap|pre)pend|offsetSet)(?P<mode>File|Script)$/', $method, $matches)) {
+ if (1 > count($args)) {
+ $e = new Zend_View_Exception(sprintf('Method "%s" requires at least one argument', $method));
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $action = $matches['action'];
+ $mode = strtolower($matches['mode']);
+ $type = 'text/javascript';
+ $attrs = array();
+
+ if ('offsetSet' == $action) {
+ $index = array_shift($args);
+ if (1 > count($args)) {
+ $e = new Zend_View_Exception(sprintf('Method "%s" requires at least two arguments, an index and source', $method));
+ $e->setView($this->view);
+ throw $e;
+ }
+ }
+
+ $content = $args[0];
+
+ if (isset($args[1])) {
+ $type = (string) $args[1];
+ }
+ if (isset($args[2])) {
+ $attrs = (array) $args[2];
+ }
+
+ switch ($mode) {
+ case 'script':
+ $item = $this->createData($type, $attrs, $content);
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ break;
+ case 'file':
+ default:
+ if (!$this->_isDuplicate($content) || $action=='set') {
+ $attrs['src'] = $content;
+ $item = $this->createData($type, $attrs);
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+ }
+ break;
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Is the file specified a duplicate?
+ *
+ * @param string $file
+ * @return bool
+ */
+ protected function _isDuplicate($file)
+ {
+ foreach ($this->getContainer() as $item) {
+ if (($item->source === null)
+ && array_key_exists('src', $item->attributes)
+ && ($file == $item->attributes['src']))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Is the script provided valid?
+ *
+ * @param mixed $value
+ * @param string $method
+ * @return bool
+ */
+ protected function _isValid($value)
+ {
+ if ((!$value instanceof stdClass)
+ || !isset($value->type)
+ || (!isset($value->source) && !isset($value->attributes)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Override append
+ *
+ * @param string $value
+ * @return void
+ */
+ public function append($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid argument passed to append(); please use one of the helper methods, appendScript() or appendFile()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * Override prepend
+ *
+ * @param string $value
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid argument passed to prepend(); please use one of the helper methods, prependScript() or prependFile()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Override set
+ *
+ * @param string $value
+ * @return void
+ */
+ public function set($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid argument passed to set(); please use one of the helper methods, setScript() or setFile()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->set($value);
+ }
+
+ /**
+ * Override offsetSet
+ *
+ * @param string|int $index
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($index, $value): void
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid argument passed to offsetSet(); please use one of the helper methods, offsetSetScript() or offsetSetFile()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * Set flag indicating if arbitrary attributes are allowed
+ *
+ * @param bool $flag
+ * @return Zend_View_Helper_HeadScript
+ */
+ public function setAllowArbitraryAttributes($flag)
+ {
+ $this->_arbitraryAttributes = (bool) $flag;
+ return $this;
+ }
+
+ /**
+ * Are arbitrary attributes allowed?
+ *
+ * @return bool
+ */
+ public function arbitraryAttributesAllowed()
+ {
+ return $this->_arbitraryAttributes;
+ }
+
+ /**
+ * Create script HTML
+ *
+ * @param string $type
+ * @param array $attributes
+ * @param string $content
+ * @param string|int $indent
+ * @return string
+ */
+ public function itemToString($item, $indent, $escapeStart, $escapeEnd)
+ {
+ $attrString = '';
+ if (!empty($item->attributes)) {
+ foreach ($item->attributes as $key => $value) {
+ if ((!$this->arbitraryAttributesAllowed() && !in_array($key, $this->_optionalAttributes))
+ || in_array($key, array('conditional', 'noescape')))
+ {
+ continue;
+ }
+ if ('defer' == $key) {
+ $value = 'defer';
+ }
+ $attrString .= sprintf(' %s="%s"', $key, ($this->_autoEscape) ? $this->_escape($value) : $value);
+ }
+ }
+
+ $addScriptEscape = !(isset($item->attributes['noescape']) && filter_var($item->attributes['noescape'], FILTER_VALIDATE_BOOLEAN));
+
+ $type = ($this->_autoEscape) ? $this->_escape($item->type) : $item->type;
+ $html = '<script type="' . $type . '"' . $attrString . '>';
+ if (!empty($item->source)) {
+ $html .= PHP_EOL ;
+
+ if ($addScriptEscape) {
+ $html .= $indent . ' ' . $escapeStart . PHP_EOL;
+ }
+
+ $html .= $indent . ' ' . $item->source;
+
+ if ($addScriptEscape) {
+ $html .= $indent . ' ' . $escapeEnd . PHP_EOL;
+ }
+
+ $html .= $indent;
+ }
+ $html .= '</script>';
+
+ if (isset($item->attributes['conditional'])
+ && !empty($item->attributes['conditional'])
+ && is_string($item->attributes['conditional']))
+ {
+ // inner wrap with comment end and start if !IE
+ if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
+ $html = '<!-->' . $html . '<!--';
+ }
+ $html = $indent . '<!--[if ' . $item->attributes['conditional'] . ']>' . $html . '<![endif]-->';
+ } else {
+ $html = $indent . $html;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Retrieve string representation
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ if ($this->view) {
+ $useCdata = $this->view->doctype()->isXhtml() ? true : false;
+ } else {
+ $useCdata = $this->useCdata ? true : false;
+ }
+ $escapeStart = ($useCdata) ? '//<![CDATA[' : '//<!--';
+ $escapeEnd = ($useCdata) ? '//]]>' : '//-->';
+
+ $items = array();
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ if (!$this->_isValid($item)) {
+ continue;
+ }
+
+ $items[] = $this->itemToString($item, $indent, $escapeStart, $escapeEnd);
+ }
+
+ $return = implode($this->getSeparator(), $items);
+ return $return;
+ }
+
+ /**
+ * Create data item containing all necessary components of script
+ *
+ * @param string $type
+ * @param array $attributes
+ * @param string $content
+ * @return stdClass
+ */
+ public function createData($type, array $attributes, $content = null)
+ {
+ $data = new stdClass();
+ $data->type = $type;
+ $data->attributes = $attributes;
+ $data->source = $content;
+ return $data;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HeadStyle.php b/library/vendor/Zend/View/Helper/HeadStyle.php
new file mode 100644
index 0000000..c28f997
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HeadStyle.php
@@ -0,0 +1,426 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Container_Standalone */
+
+/**
+ * Helper for setting and retrieving stylesheets
+ *
+ * @uses Zend_View_Helper_Placeholder_Container_Standalone
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @method $this appendStyle($content, array $attributes = array())
+ * @method $this offsetSetStyle($index, $content, array $attributes = array())
+ * @method $this prependStyle($content, array $attributes = array())
+ * @method $this setStyle($content, array $attributes = array())
+ */
+class Zend_View_Helper_HeadStyle extends Zend_View_Helper_Placeholder_Container_Standalone
+{
+ /**
+ * Registry key for placeholder
+ * @var string
+ */
+ protected $_regKey = 'Zend_View_Helper_HeadStyle';
+
+ /**
+ * Allowed optional attributes
+ * @var array
+ */
+ protected $_optionalAttributes = array('lang', 'title', 'media', 'dir');
+
+ /**
+ * Allowed media types
+ * @var array
+ */
+ protected $_mediaTypes = array(
+ 'all', 'aural', 'braille', 'handheld', 'print',
+ 'projection', 'screen', 'tty', 'tv'
+ );
+
+ /**
+ * Capture type and/or attributes (used for hinting during capture)
+ * @var string
+ */
+ protected $_captureAttrs = null;
+
+ /**
+ * Capture lock
+ * @var bool
+ */
+ protected $_captureLock;
+
+ /**
+ * Capture type (append, prepend, set)
+ * @var string
+ */
+ protected $_captureType;
+
+ /**
+ * Constructor
+ *
+ * Set separator to PHP_EOL.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->setSeparator(PHP_EOL);
+ }
+
+ /**
+ * Return headStyle object
+ *
+ * Returns headStyle helper object; optionally, allows specifying
+ *
+ * @param string $content Stylesheet contents
+ * @param string $placement Append, prepend, or set
+ * @param string|array $attributes Optional attributes to utilize
+ * @return Zend_View_Helper_HeadStyle
+ */
+ public function headStyle($content = null, $placement = 'APPEND', $attributes = array())
+ {
+ if ((null !== $content) && is_string($content)) {
+ switch (strtoupper($placement)) {
+ case 'SET':
+ $action = 'setStyle';
+ break;
+ case 'PREPEND':
+ $action = 'prependStyle';
+ break;
+ case 'APPEND':
+ default:
+ $action = 'appendStyle';
+ break;
+ }
+ $this->$action($content, $attributes);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Overload method calls
+ *
+ * Allows the following method calls:
+ * - appendStyle($content, $attributes = array())
+ * - offsetSetStyle($index, $content, $attributes = array())
+ * - prependStyle($content, $attributes = array())
+ * - setStyle($content, $attributes = array())
+ *
+ * @param string $method
+ * @param array $args
+ * @return void
+ * @throws Zend_View_Exception When no $content provided or invalid method
+ */
+ public function __call($method, $args)
+ {
+ if (preg_match('/^(?P<action>set|(ap|pre)pend|offsetSet)(Style)$/', $method, $matches)) {
+ $index = null;
+ $argc = count($args);
+ $action = $matches['action'];
+
+ if ('offsetSet' == $action) {
+ if (0 < $argc) {
+ $index = array_shift($args);
+ --$argc;
+ }
+ }
+
+ if (1 > $argc) {
+ $e = new Zend_View_Exception(sprintf('Method "%s" requires minimally content for the stylesheet', $method));
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $content = $args[0];
+ $attrs = array();
+ if (isset($args[1])) {
+ $attrs = (array) $args[1];
+ }
+
+ $item = $this->createData($content, $attrs);
+
+ if ('offsetSet' == $action) {
+ $this->offsetSet($index, $item);
+ } else {
+ $this->$action($item);
+ }
+
+ return $this;
+ }
+
+ return parent::__call($method, $args);
+ }
+
+ /**
+ * Determine if a value is a valid style tag
+ *
+ * @param mixed $value
+ * @param string $method
+ * @return boolean
+ */
+ protected function _isValid($value)
+ {
+ if ((!$value instanceof stdClass)
+ || !isset($value->content)
+ || !isset($value->attributes))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Override append to enforce style creation
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function append($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to append; please use appendStyle()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->append($value);
+ }
+
+ /**
+ * Override offsetSet to enforce style creation
+ *
+ * @param string|int $index
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($index, $value): void
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to offsetSet; please use offsetSetStyle()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->getContainer()->offsetSet($index, $value);
+ }
+
+ /**
+ * Override prepend to enforce style creation
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function prepend($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to prepend; please use prependStyle()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->prepend($value);
+ }
+
+ /**
+ * Override set to enforce style creation
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function set($value)
+ {
+ if (!$this->_isValid($value)) {
+ $e = new Zend_View_Exception('Invalid value passed to set; please use setStyle()');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this->getContainer()->set($value);
+ }
+
+ /**
+ * Start capture action
+ *
+ * @param mixed $captureType
+ * @param string $typeOrAttrs
+ * @return void
+ */
+ public function captureStart($type = Zend_View_Helper_Placeholder_Container_Abstract::APPEND, $attrs = null)
+ {
+ if ($this->_captureLock) {
+ $e = new Zend_View_Helper_Placeholder_Container_Exception('Cannot nest headStyle captures');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->_captureLock = true;
+ $this->_captureAttrs = $attrs;
+ $this->_captureType = $type;
+ ob_start();
+ }
+
+ /**
+ * End capture action and store
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $content = ob_get_clean();
+ $attrs = $this->_captureAttrs;
+ $this->_captureAttrs = null;
+ $this->_captureLock = false;
+
+ switch ($this->_captureType) {
+ case Zend_View_Helper_Placeholder_Container_Abstract::SET:
+ $this->setStyle($content, $attrs);
+ break;
+ case Zend_View_Helper_Placeholder_Container_Abstract::PREPEND:
+ $this->prependStyle($content, $attrs);
+ break;
+ case Zend_View_Helper_Placeholder_Container_Abstract::APPEND:
+ default:
+ $this->appendStyle($content, $attrs);
+ break;
+ }
+ }
+
+ /**
+ * Convert content and attributes into valid style tag
+ *
+ * @param stdClass $item Item to render
+ * @param string $indent Indentation to use
+ * @return string
+ */
+ public function itemToString(stdClass $item, $indent)
+ {
+ $attrString = '';
+ if (!empty($item->attributes)) {
+ $enc = 'UTF-8';
+ if ($this->view instanceof Zend_View_Interface
+ && method_exists($this->view, 'getEncoding')
+ ) {
+ $enc = $this->view->getEncoding();
+ }
+ foreach ($item->attributes as $key => $value) {
+ if (!in_array($key, $this->_optionalAttributes)) {
+ continue;
+ }
+ if ('media' == $key) {
+ if(false === strpos($value, ',')) {
+ if (!in_array($value, $this->_mediaTypes)) {
+ continue;
+ }
+ } else {
+ $media_types = explode(',', $value);
+ $value = '';
+ foreach($media_types as $type) {
+ $type = trim($type);
+ if (!in_array($type, $this->_mediaTypes)) {
+ continue;
+ }
+ $value .= $type .',';
+ }
+ $value = substr($value, 0, -1);
+ }
+ }
+ $attrString .= sprintf(' %s="%s"', $key, htmlspecialchars($value, ENT_COMPAT, $enc));
+ }
+ }
+
+ $escapeStart = $indent . '<!--'. PHP_EOL;
+ $escapeEnd = $indent . '-->'. PHP_EOL;
+ if (isset($item->attributes['conditional'])
+ && !empty($item->attributes['conditional'])
+ && is_string($item->attributes['conditional'])
+ ) {
+ $escapeStart = null;
+ $escapeEnd = null;
+ }
+
+ $html = '<style type="text/css"' . $attrString . '>' . PHP_EOL
+ . $escapeStart . $indent . $item->content . PHP_EOL . $escapeEnd
+ . '</style>';
+
+ if (null == $escapeStart && null == $escapeEnd) {
+ if (str_replace(' ', '', $item->attributes['conditional']) === '!IE') {
+ $html = '<!-->' . $html . '<!--';
+ }
+ $html = '<!--[if ' . $item->attributes['conditional'] . ']>' . $html . '<![endif]-->';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Create string representation of placeholder
+ *
+ * @param string|int $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+ $this->getContainer()->ksort();
+ foreach ($this as $item) {
+ if (!$this->_isValid($item)) {
+ continue;
+ }
+ $items[] = $this->itemToString($item, $indent);
+ }
+
+ $return = $indent . implode($this->getSeparator() . $indent, $items);
+ $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
+ return $return;
+ }
+
+ /**
+ * Create data item for use in stack
+ *
+ * @param string $content
+ * @param array $attributes
+ * @return stdClass
+ */
+ public function createData($content, array $attributes)
+ {
+ if (!isset($attributes['media'])) {
+ $attributes['media'] = 'screen';
+ } else if(is_array($attributes['media'])) {
+ $attributes['media'] = implode(',', $attributes['media']);
+ }
+
+ $data = new stdClass();
+ $data->content = $content;
+ $data->attributes = $attributes;
+
+ return $data;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HeadTitle.php b/library/vendor/Zend/View/Helper/HeadTitle.php
new file mode 100644
index 0000000..77aef26
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HeadTitle.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Container_Standalone */
+
+/**
+ * Helper for setting and retrieving title element for HTML head
+ *
+ * @uses Zend_View_Helper_Placeholder_Container_Standalone
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_HeadTitle extends Zend_View_Helper_Placeholder_Container_Standalone
+{
+ /**
+ * Registry key for placeholder
+ * @var string
+ */
+ protected $_regKey = 'Zend_View_Helper_HeadTitle';
+
+ /**
+ * Whether or not auto-translation is enabled
+ * @var boolean
+ */
+ protected $_translate = false;
+
+ /**
+ * Translation object
+ *
+ * @var Zend_Translate_Adapter
+ */
+ protected $_translator;
+
+ /**
+ * Default title rendering order (i.e. order in which each title attached)
+ *
+ * @var string
+ */
+ protected $_defaultAttachOrder = null;
+
+ /**
+ * Retrieve placeholder for title element and optionally set state
+ *
+ * @param string $title
+ * @param string $setType
+ * @return Zend_View_Helper_HeadTitle
+ */
+ public function headTitle($title = null, $setType = null)
+ {
+ if (null === $setType) {
+ $setType = (null === $this->getDefaultAttachOrder())
+ ? Zend_View_Helper_Placeholder_Container_Abstract::APPEND
+ : $this->getDefaultAttachOrder();
+ }
+ $title = (string) $title;
+ if ($title !== '') {
+ if ($setType == Zend_View_Helper_Placeholder_Container_Abstract::SET) {
+ $this->set($title);
+ } elseif ($setType == Zend_View_Helper_Placeholder_Container_Abstract::PREPEND) {
+ $this->prepend($title);
+ } else {
+ $this->append($title);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set a default order to add titles
+ *
+ * @param string $setType
+ */
+ public function setDefaultAttachOrder($setType)
+ {
+ if (!in_array($setType, array(
+ Zend_View_Helper_Placeholder_Container_Abstract::APPEND,
+ Zend_View_Helper_Placeholder_Container_Abstract::SET,
+ Zend_View_Helper_Placeholder_Container_Abstract::PREPEND
+ ))) {
+ throw new Zend_View_Exception("You must use a valid attach order: 'PREPEND', 'APPEND' or 'SET'");
+ }
+
+ $this->_defaultAttachOrder = $setType;
+ return $this;
+ }
+
+ /**
+ * Get the default attach order, if any.
+ *
+ * @return mixed
+ */
+ public function getDefaultAttachOrder()
+ {
+ return $this->_defaultAttachOrder;
+ }
+
+ /**
+ * Sets a translation Adapter for translation
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter $translate
+ * @return Zend_View_Helper_HeadTitle
+ */
+ public function setTranslator($translate)
+ {
+ if ($translate instanceof Zend_Translate_Adapter) {
+ $this->_translator = $translate;
+ } elseif ($translate instanceof Zend_Translate) {
+ $this->_translator = $translate->getAdapter();
+ } else {
+ $e = new Zend_View_Exception("You must set an instance of Zend_Translate or Zend_Translate_Adapter");
+ $e->setView($this->view);
+ throw $e;
+ }
+ return $this;
+ }
+
+ /**
+ * Retrieve translation object
+ *
+ * If none is currently registered, attempts to pull it from the registry
+ * using the key 'Zend_Translate'.
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if (null === $this->_translator) {
+ if (Zend_Registry::isRegistered('Zend_Translate')) {
+ $this->setTranslator(Zend_Registry::get('Zend_Translate'));
+ }
+ }
+ return $this->_translator;
+ }
+
+ /**
+ * Enables translation
+ *
+ * @return Zend_View_Helper_HeadTitle
+ */
+ public function enableTranslation()
+ {
+ $this->_translate = true;
+ return $this;
+ }
+
+ /**
+ * Disables translation
+ *
+ * @return Zend_View_Helper_HeadTitle
+ */
+ public function disableTranslation()
+ {
+ $this->_translate = false;
+ return $this;
+ }
+
+ /**
+ * Turn helper into string
+ *
+ * @param string|null $indent
+ * @param string|null $locale
+ * @return string
+ */
+ public function toString($indent = null, $locale = null)
+ {
+ $indent = (null !== $indent)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = array();
+
+ if($this->_translate && $translator = $this->getTranslator()) {
+ foreach ($this as $item) {
+ $items[] = $translator->translate($item, $locale);
+ }
+ } else {
+ foreach ($this as $item) {
+ $items[] = $item;
+ }
+ }
+
+ $separator = $this->getSeparator();
+ $output = '';
+ if(($prefix = $this->getPrefix())) {
+ $output .= $prefix;
+ }
+ $output .= implode($separator, $items);
+ if(($postfix = $this->getPostfix())) {
+ $output .= $postfix;
+ }
+
+ $output = ($this->_autoEscape) ? $this->_escape($output) : $output;
+
+ return $indent . '<title>' . $output . '</title>';
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HtmlElement.php b/library/vendor/Zend/View/Helper/HtmlElement.php
new file mode 100644
index 0000000..212afbd
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HtmlElement.php
@@ -0,0 +1,165 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_Abstract
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_View_Helper_HtmlElement extends Zend_View_Helper_Abstract
+{
+ /**
+ * EOL character
+ */
+ const EOL = "\n";
+
+ /**
+ * The tag closing bracket
+ *
+ * @var string
+ */
+ protected $_closingBracket = null;
+
+ /**
+ * Get the tag closing bracket
+ *
+ * @return string
+ */
+ public function getClosingBracket()
+ {
+ if (!$this->_closingBracket) {
+ if ($this->_isXhtml()) {
+ $this->_closingBracket = ' />';
+ } else {
+ $this->_closingBracket = '>';
+ }
+ }
+
+ return $this->_closingBracket;
+ }
+
+ /**
+ * Is doctype XHTML?
+ *
+ * @return boolean
+ */
+ protected function _isXhtml()
+ {
+ $doctype = $this->view->doctype();
+ return $doctype->isXhtml();
+ }
+
+ /**
+ * Is doctype HTML5?
+ *
+ * @return boolean
+ */
+ protected function _isHtml5()
+ {
+ $doctype = $this->view->doctype();
+ return $doctype->isHtml5();
+ }
+
+ /**
+ * Is doctype strict?
+ *
+ * @return boolean
+ */
+ protected function _isStrictDoctype()
+ {
+ $doctype = $this->view->doctype();
+ return $doctype->isStrict();
+ }
+
+ /**
+ * Converts an associative array to a string of tag attributes.
+ *
+ * @access public
+ *
+ * @param array $attribs From this array, each key-value pair is
+ * converted to an attribute name and value.
+ *
+ * @return string The XHTML for the attributes.
+ */
+ protected function _htmlAttribs($attribs)
+ {
+ $xhtml = '';
+ foreach ((array) $attribs as $key => $val) {
+ $key = $this->view->escape($key);
+
+ if (('on' == substr($key, 0, 2)) || ('constraints' == $key)) {
+ // Don't escape event attributes; _do_ substitute double quotes with singles
+ if (!is_scalar($val)) {
+ // non-scalar data should be cast to JSON first
+ $val = Zend_Json::encode($val);
+ }
+ // Escape single quotes inside event attribute values.
+ // This will create html, where the attribute value has
+ // single quotes around it, and escaped single quotes or
+ // non-escaped double quotes inside of it
+ $val = str_replace('\'', '&#39;', $val);
+ } else {
+ if (is_array($val)) {
+ $val = implode(' ', $val);
+ }
+ $val = $this->view->escape($val);
+ }
+
+ if ('id' == $key) {
+ $val = $this->_normalizeId($val);
+ }
+
+ if (strpos($val, '"') !== false) {
+ $xhtml .= " $key='$val'";
+ } else {
+ $xhtml .= " $key=\"$val\"";
+ }
+
+ }
+ return $xhtml;
+ }
+
+ /**
+ * Normalize an ID
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function _normalizeId($value)
+ {
+ if (strstr($value, '[')) {
+ if ('[]' == substr($value, -2)) {
+ $value = substr($value, 0, strlen($value) - 2);
+ }
+ $value = trim($value, ']');
+ $value = str_replace('][', '-', $value);
+ $value = str_replace('[', '-', $value);
+ }
+ return $value;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HtmlFlash.php b/library/vendor/Zend/View/Helper/HtmlFlash.php
new file mode 100644
index 0000000..b33ad51
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HtmlFlash.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_HtmlObject
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_HtmlFlash extends Zend_View_Helper_HtmlObject
+{
+ /**
+ * Default file type for a flash applet
+ *
+ */
+ const TYPE = 'application/x-shockwave-flash';
+
+ /**
+ * Output a flash movie object tag
+ *
+ * @param string $data The flash file
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function htmlFlash($data, array $attribs = array(), array $params = array(), $content = null)
+ {
+ // Params
+ $params = array_merge(array('movie' => $data,
+ 'quality' => 'high'), $params);
+
+ return $this->htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HtmlList.php b/library/vendor/Zend/View/Helper/HtmlList.php
new file mode 100644
index 0000000..e080eb1
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HtmlList.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Zend_View_Helper_FormELement
+ */
+
+/**
+ * Helper for ordered and unordered lists
+ *
+ * @uses Zend_View_Helper_FormElement
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_HtmlList extends Zend_View_Helper_FormElement
+{
+
+ /**
+ * Generates a 'List' element.
+ *
+ * @param array $items Array with the elements of the list
+ * @param boolean $ordered Specifies ordered/unordered list; default unordered
+ * @param array $attribs Attributes for the ol/ul tag.
+ * @return string The list XHTML.
+ */
+ public function htmlList(array $items, $ordered = false, $attribs = false, $escape = true)
+ {
+ if (!is_array($items)) {
+ $e = new Zend_View_Exception('First param must be an array');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $list = '';
+
+ foreach ($items as $item) {
+ if (!is_array($item)) {
+ if ($escape) {
+ $item = $this->view->escape($item);
+ }
+ $list .= '<li>' . $item . '</li>' . self::EOL;
+ } else {
+ if (6 < strlen($list)) {
+ $list = substr($list, 0, strlen($list) - 6)
+ . $this->htmlList($item, $ordered, $attribs, $escape) . '</li>' . self::EOL;
+ } else {
+ $list .= '<li>' . $this->htmlList($item, $ordered, $attribs, $escape) . '</li>' . self::EOL;
+ }
+ }
+ }
+
+ if ($attribs) {
+ $attribs = $this->_htmlAttribs($attribs);
+ } else {
+ $attribs = '';
+ }
+
+ $tag = 'ul';
+ if ($ordered) {
+ $tag = 'ol';
+ }
+
+ return '<' . $tag . $attribs . '>' . self::EOL . $list . '</' . $tag . '>' . self::EOL;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HtmlObject.php b/library/vendor/Zend/View/Helper/HtmlObject.php
new file mode 100644
index 0000000..a5a4e22
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HtmlObject.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_HtmlElement
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_HtmlObject extends Zend_View_Helper_HtmlElement
+{
+ /**
+ * Output an object set
+ *
+ * @param string $data The data file
+ * @param string $type Data file type
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content for object
+ * @return string
+ */
+ public function htmlObject($data, $type, array $attribs = array(), array $params = array(), $content = null)
+ {
+ // Merge data and type
+ $attribs = array_merge(array('data' => $data,
+ 'type' => $type), $attribs);
+
+ // Params
+ $paramHtml = array();
+ $closingBracket = $this->getClosingBracket();
+
+ foreach ($params as $param => $options) {
+ if (is_string($options)) {
+ $options = array('value' => $options);
+ }
+
+ $options = array_merge(array('name' => $param), $options);
+
+ $paramHtml[] = '<param' . $this->_htmlAttribs($options) . $closingBracket;
+ }
+
+ // Content
+ if (is_array($content)) {
+ $content = implode(self::EOL, $content);
+ }
+
+ // Object header
+ $xhtml = '<object' . $this->_htmlAttribs($attribs) . '>' . self::EOL
+ . implode(self::EOL, $paramHtml) . self::EOL
+ . ($content ? $content . self::EOL : '')
+ . '</object>';
+
+ return $xhtml;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HtmlPage.php b/library/vendor/Zend/View/Helper/HtmlPage.php
new file mode 100644
index 0000000..2a2265c
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HtmlPage.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_HtmlObject
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_HtmlPage extends Zend_View_Helper_HtmlObject
+{
+ /**
+ * Default file type for html
+ *
+ */
+ const TYPE = 'text/html';
+
+ /**
+ * Object classid
+ *
+ */
+ const ATTRIB_CLASSID = 'clsid:25336920-03F9-11CF-8FD0-00AA00686F13';
+
+ /**
+ * Default attributes
+ *
+ * @var array
+ */
+ protected $_attribs = array('classid' => self::ATTRIB_CLASSID);
+
+ /**
+ * Output a html object tag
+ *
+ * @param string $data The html url
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function htmlPage($data, array $attribs = array(), array $params = array(), $content = null)
+ {
+ // Attrs
+ $attribs = array_merge($this->_attribs, $attribs);
+
+ // Params
+ $params = array_merge(array('data' => $data), $params);
+
+ return $this->htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/HtmlQuicktime.php b/library/vendor/Zend/View/Helper/HtmlQuicktime.php
new file mode 100644
index 0000000..f09452d
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/HtmlQuicktime.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @see Zend_View_Helper_HtmlObject
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_HtmlQuicktime extends Zend_View_Helper_HtmlObject
+{
+ /**
+ * Default file type for a movie applet
+ *
+ */
+ const TYPE = 'video/quicktime';
+
+ /**
+ * Object classid
+ *
+ */
+ const ATTRIB_CLASSID = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B';
+
+ /**
+ * Object Codebase
+ *
+ */
+ const ATTRIB_CODEBASE = 'http://www.apple.com/qtactivex/qtplugin.cab';
+
+ /**
+ * Default attributes
+ *
+ * @var array
+ */
+ protected $_attribs = array('classid' => self::ATTRIB_CLASSID,
+ 'codebase' => self::ATTRIB_CODEBASE);
+
+ /**
+ * Output a quicktime movie object tag
+ *
+ * @param string $data The quicktime file
+ * @param array $attribs Attribs for the object tag
+ * @param array $params Params for in the object tag
+ * @param string $content Alternative content
+ * @return string
+ */
+ public function htmlQuicktime($data, array $attribs = array(), array $params = array(), $content = null)
+ {
+ // Attrs
+ $attribs = array_merge($this->_attribs, $attribs);
+
+ // Params
+ $params = array_merge(array('src' => $data), $params);
+
+ return $this->htmlObject($data, self::TYPE, $attribs, $params, $content);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/InlineScript.php b/library/vendor/Zend/View/Helper/InlineScript.php
new file mode 100644
index 0000000..555a2f4
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/InlineScript.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_HeadScript */
+
+/**
+ * Helper for setting and retrieving script elements for inclusion in HTML body
+ * section
+ *
+ * @uses Zend_View_Helper_Head_Script
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_InlineScript extends Zend_View_Helper_HeadScript
+{
+ /**
+ * Registry key for placeholder
+ * @var string
+ */
+ protected $_regKey = 'Zend_View_Helper_InlineScript';
+
+ /**
+ * Return InlineScript object
+ *
+ * Returns InlineScript helper object; optionally, allows specifying a
+ * script or script file to include.
+ *
+ * @param string $mode Script or file
+ * @param string $spec Script/url
+ * @param string $placement Append, prepend, or set
+ * @param array $attrs Array of script attributes
+ * @param string $type Script type and/or array of script attributes
+ * @return Zend_View_Helper_InlineScript
+ */
+ public function inlineScript($mode = Zend_View_Helper_HeadScript::FILE, $spec = null, $placement = 'APPEND', array $attrs = array(), $type = 'text/javascript')
+ {
+ return $this->headScript($mode, $spec, $placement, $attrs, $type);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Interface.php b/library/vendor/Zend/View/Helper/Interface.php
new file mode 100644
index 0000000..c7e761b
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Interface.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_View_Helper_Interface
+{
+ /**
+ * Set the View object
+ *
+ * @param Zend_View_Interface $view
+ * @return Zend_View_Helper_Interface
+ */
+ public function setView(Zend_View_Interface $view);
+
+ /**
+ * Strategy pattern: helper method to invoke
+ *
+ * @return mixed
+ */
+ public function direct();
+}
diff --git a/library/vendor/Zend/View/Helper/Json.php b/library/vendor/Zend/View/Helper/Json.php
new file mode 100644
index 0000000..62bf079
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Json.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Json */
+
+/** Zend_Controller_Front */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for simplifying JSON responses
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Json extends Zend_View_Helper_Abstract
+{
+ /**
+ * Encode data as JSON, disable layouts, and set response header
+ *
+ * If $keepLayouts is true, does not disable layouts.
+ * If $encodeJson is false, does not JSON-encode $data
+ *
+ * @param mixed $data
+ * @param bool $keepLayouts
+ * NOTE: if boolean, establish $keepLayouts to true|false
+ * if array, admit params for Zend_Json::encode as enableJsonExprFinder=>true|false
+ * this array can contains a 'keepLayout'=>true|false and/or 'encodeData'=>true|false
+ * that will not be passed to Zend_Json::encode method but will be used here
+ * @param bool $encodeData
+ * @return string|void
+ */
+ public function json($data, $keepLayouts = false, $encodeData = true)
+ {
+ $options = array();
+ if (is_array($keepLayouts)) {
+ $options = $keepLayouts;
+
+ $keepLayouts = false;
+ if (array_key_exists('keepLayouts', $options)) {
+ $keepLayouts = $options['keepLayouts'];
+ unset($options['keepLayouts']);
+ }
+
+ if (array_key_exists('encodeData', $options)) {
+ $encodeData = $options['encodeData'];
+ unset($options['encodeData']);
+ }
+ }
+
+ if ($encodeData) {
+ $data = Zend_Json::encode($data, null, $options);
+ }
+ if (!$keepLayouts) {
+ $layout = Zend_Layout::getMvcInstance();
+ if ($layout instanceof Zend_Layout) {
+ $layout->disableLayout();
+ }
+ }
+
+ $response = Zend_Controller_Front::getInstance()->getResponse();
+ $response->setHeader('Content-Type', 'application/json', true);
+ return $data;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Layout.php b/library/vendor/Zend/View/Helper/Layout.php
new file mode 100644
index 0000000..a83bf06
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Layout.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * View helper for retrieving layout object
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Layout extends Zend_View_Helper_Abstract
+{
+ /** @var Zend_Layout */
+ protected $_layout;
+
+ /**
+ * Get layout object
+ *
+ * @return Zend_Layout
+ */
+ public function getLayout()
+ {
+ if (null === $this->_layout) {
+ $this->_layout = Zend_Layout::getMvcInstance();
+ if (null === $this->_layout) {
+ // Implicitly creates layout object
+ $this->_layout = new Zend_Layout();
+ }
+ }
+
+ return $this->_layout;
+ }
+
+ /**
+ * Set layout object
+ *
+ * @param Zend_Layout $layout
+ * @return Zend_Layout_Controller_Action_Helper_Layout
+ */
+ public function setLayout(Zend_Layout $layout)
+ {
+ $this->_layout = $layout;
+ return $this;
+ }
+
+ /**
+ * Return layout object
+ *
+ * Usage: $this->layout()->setLayout('alternate');
+ *
+ * @return Zend_Layout
+ */
+ public function layout()
+ {
+ return $this->getLayout();
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/PaginationControl.php b/library/vendor/Zend/View/Helper/PaginationControl.php
new file mode 100644
index 0000000..b474963
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/PaginationControl.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_PaginationControl
+{
+ /**
+ * View instance
+ *
+ * @var Zend_View_Instance
+ */
+ public $view = null;
+
+ /**
+ * Default view partial
+ *
+ * @var string|array
+ */
+ protected static $_defaultViewPartial = null;
+
+ /**
+ * Sets the view instance.
+ *
+ * @param Zend_View_Interface $view View instance
+ * @return Zend_View_Helper_PaginationControl
+ */
+ public function setView(Zend_View_Interface $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Sets the default view partial.
+ *
+ * @param string|array $partial View partial
+ */
+ public static function setDefaultViewPartial($partial)
+ {
+ self::$_defaultViewPartial = $partial;
+ }
+
+ /**
+ * Gets the default view partial
+ *
+ * @return string|array
+ */
+ public static function getDefaultViewPartial()
+ {
+ return self::$_defaultViewPartial;
+ }
+
+ /**
+ * Render the provided pages. This checks if $view->paginator is set and,
+ * if so, uses that. Also, if no scrolling style or partial are specified,
+ * the defaults will be used (if set).
+ *
+ * @param Zend_Paginator (Optional) $paginator
+ * @param string $scrollingStyle (Optional) Scrolling style
+ * @param string $partial (Optional) View partial
+ * @param array|string $params (Optional) params to pass to the partial
+ * @return string
+ * @throws Zend_View_Exception
+ */
+ public function paginationControl(Zend_Paginator $paginator = null, $scrollingStyle = null, $partial = null, $params = null)
+ {
+ if ($paginator === null) {
+ if (isset($this->view->paginator) and $this->view->paginator !== null and $this->view->paginator instanceof Zend_Paginator) {
+ $paginator = $this->view->paginator;
+ } else {
+ /**
+ * @see Zend_View_Exception
+ */
+
+ $e = new Zend_View_Exception('No paginator instance provided or incorrect type');
+ $e->setView($this->view);
+ throw $e;
+ }
+ }
+
+ if ($partial === null) {
+ if (self::$_defaultViewPartial === null) {
+ /**
+ * @see Zend_View_Exception
+ */
+ $e = new Zend_View_Exception('No view partial provided and no default set');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $partial = self::$_defaultViewPartial;
+ }
+
+ $pages = get_object_vars($paginator->getPages($scrollingStyle));
+
+ if ($params !== null) {
+ $pages = array_merge($pages, (array) $params);
+ }
+
+ if (is_array($partial)) {
+ if (count($partial) != 2) {
+ /**
+ * @see Zend_View_Exception
+ */
+ $e = new Zend_View_Exception('A view partial supplied as an array must contain two values: the filename and its module');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ if ($partial[1] !== null) {
+ return $this->view->partial($partial[0], $partial[1], $pages);
+ }
+
+ $partial = $partial[0];
+ }
+
+ return $this->view->partial($partial, $pages);
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Partial.php b/library/vendor/Zend/View/Helper/Partial.php
new file mode 100644
index 0000000..787db9c
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Partial.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for rendering a template fragment in its own variable scope.
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Partial extends Zend_View_Helper_Abstract
+{
+ /**
+ * Variable to which object will be assigned
+ * @var string
+ */
+ protected $_objectKey;
+
+ /**
+ * Renders a template fragment within a variable scope distinct from the
+ * calling View object.
+ *
+ * If no arguments are passed, returns the helper instance.
+ *
+ * If the $model is an array, it is passed to the view object's assign()
+ * method.
+ *
+ * If the $model is an object, it first checks to see if the object
+ * implements a 'toArray' method; if so, it passes the result of that
+ * method to to the view object's assign() method. Otherwise, the result of
+ * get_object_vars() is passed.
+ *
+ * @param string $name Name of view script
+ * @param string|array $module If $model is empty, and $module is an array,
+ * these are the variables to populate in the
+ * view. Otherwise, the module in which the
+ * partial resides
+ * @param array $model Variables to populate in the view
+ * @return string|Zend_View_Helper_Partial
+ */
+ public function partial($name = null, $module = null, $model = null)
+ {
+ if (0 == func_num_args()) {
+ return $this;
+ }
+
+ $view = $this->cloneView();
+ if (isset($this->partialCounter)) {
+ $view->partialCounter = $this->partialCounter;
+ }
+ if (isset($this->partialTotalCount)) {
+ $view->partialTotalCount = $this->partialTotalCount;
+ }
+
+ if ((null !== $module) && is_string($module)) {
+ $moduleDir = Zend_Controller_Front::getInstance()->getControllerDirectory($module);
+ if (null === $moduleDir) {
+ $e = new Zend_View_Helper_Partial_Exception('Cannot render partial; module does not exist');
+ $e->setView($this->view);
+ throw $e;
+ }
+ $viewsDir = dirname($moduleDir) . '/views';
+ $view->addBasePath($viewsDir);
+ } elseif ((null == $model) && (null !== $module)
+ && (is_array($module) || is_object($module)))
+ {
+ $model = $module;
+ }
+
+ if (!empty($model)) {
+ if (is_array($model)) {
+ $view->assign($model);
+ } elseif (is_object($model)) {
+ if (null !== ($objectKey = $this->getObjectKey())) {
+ $view->assign($objectKey, $model);
+ } elseif (method_exists($model, 'toArray')) {
+ $view->assign($model->toArray());
+ } else {
+ $view->assign(get_object_vars($model));
+ }
+ }
+ }
+
+ return $view->render($name);
+ }
+
+ /**
+ * Clone the current View
+ *
+ * @return Zend_View_Interface
+ */
+ public function cloneView()
+ {
+ $view = clone $this->view;
+ $view->clearVars();
+ return $view;
+ }
+
+ /**
+ * Set object key
+ *
+ * @param string $key
+ * @return Zend_View_Helper_Partial
+ */
+ public function setObjectKey($key)
+ {
+ if (null === $key) {
+ $this->_objectKey = null;
+ } else {
+ $this->_objectKey = (string) $key;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve object key
+ *
+ * The objectKey is the variable to which an object in the iterator will be
+ * assigned.
+ *
+ * @return null|string
+ */
+ public function getObjectKey()
+ {
+ return $this->_objectKey;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Partial/Exception.php b/library/vendor/Zend/View/Helper/Partial/Exception.php
new file mode 100644
index 0000000..5cd850a
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Partial/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/** Zend_View_Exception */
+
+
+/**
+ * Exception for Zend_View_Helper_Partial class.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Partial_Exception extends Zend_View_Exception
+{
+}
diff --git a/library/vendor/Zend/View/Helper/PartialLoop.php b/library/vendor/Zend/View/Helper/PartialLoop.php
new file mode 100644
index 0000000..12125b4
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/PartialLoop.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Partial */
+
+/**
+ * Helper for rendering a template fragment in its own variable scope; iterates
+ * over data provided and renders for each iteration.
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_PartialLoop extends Zend_View_Helper_Partial
+{
+
+ /**
+ * Marker to where the pointer is at in the loop
+ * @var integer
+ */
+ protected $partialCounter = 0;
+
+ /**
+ * Renders a template fragment within a variable scope distinct from the
+ * calling View object.
+ *
+ * If no arguments are provided, returns object instance.
+ *
+ * @param string $name Name of view script
+ * @param string|array $module If $model is empty, and $module is an array,
+ * these are the variables to populate in the
+ * view. Otherwise, the module in which the
+ * partial resides
+ * @param array $model Variables to populate in the view
+ * @return string
+ */
+ public function partialLoop($name = null, $module = null, $model = null)
+ {
+ if (0 == func_num_args()) {
+ return $this;
+ }
+
+ if ((null === $model) && (null !== $module)) {
+ $model = $module;
+ $module = null;
+ }
+
+ if (!is_array($model)
+ && (!$model instanceof Traversable)
+ && (is_object($model) && !method_exists($model, 'toArray'))
+ ) {
+ $e = new Zend_View_Helper_Partial_Exception('PartialLoop helper requires iterable data');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ if (is_object($model)
+ && (!$model instanceof Traversable)
+ && method_exists($model, 'toArray')
+ ) {
+ $model = $model->toArray();
+ }
+
+ $content = '';
+ // reset the counter if it's call again
+ $this->partialCounter = 0;
+ $this->partialTotalCount = count($model);
+
+ foreach ($model as $item) {
+ // increment the counter variable
+ $this->partialCounter++;
+
+ $content .= $this->partial($name, $module, $item);
+ }
+
+ return $content;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder.php b/library/vendor/Zend/View/Helper/Placeholder.php
new file mode 100644
index 0000000..3ca47ad
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Registry */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for passing data between otherwise segregated Views. It's called
+ * Placeholder to make its typical usage obvious, but can be used just as easily
+ * for non-Placeholder things. That said, the support for this is only
+ * guaranteed to effect subsequently rendered templates, and of course Layouts.
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Placeholder extends Zend_View_Helper_Abstract
+{
+ /**
+ * Placeholder items
+ * @var array
+ */
+ protected $_items = array();
+
+ /**
+ * @var Zend_View_Helper_Placeholder_Registry
+ */
+ protected $_registry;
+
+ /**
+ * Constructor
+ *
+ * Retrieve container registry from Zend_Registry, or create new one and register it.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->_registry = Zend_View_Helper_Placeholder_Registry::getRegistry();
+ }
+
+
+ /**
+ * Placeholder helper
+ *
+ * @param string $name
+ * @return Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ public function placeholder($name)
+ {
+ $name = (string) $name;
+ return $this->_registry->getContainer($name);
+ }
+
+ /**
+ * Retrieve the registry
+ *
+ * @return Zend_View_Helper_Placeholder_Registry
+ */
+ public function getRegistry()
+ {
+ return $this->_registry;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder/Container.php b/library/vendor/Zend/View/Helper/Placeholder/Container.php
new file mode 100644
index 0000000..c30025a
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder/Container.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Container_Abstract */
+
+/**
+ * Container for placeholder values
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Placeholder_Container extends Zend_View_Helper_Placeholder_Container_Abstract
+{
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder/Container/Abstract.php b/library/vendor/Zend/View/Helper/Placeholder/Container/Abstract.php
new file mode 100644
index 0000000..d015c1d
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder/Container/Abstract.php
@@ -0,0 +1,384 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/**
+ * Abstract class representing container for placeholder values
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_View_Helper_Placeholder_Container_Abstract extends ArrayObject
+{
+ /**
+ * Whether or not to override all contents of placeholder
+ * @const string
+ */
+ const SET = 'SET';
+
+ /**
+ * Whether or not to append contents to placeholder
+ * @const string
+ */
+ const APPEND = 'APPEND';
+
+ /**
+ * Whether or not to prepend contents to placeholder
+ * @const string
+ */
+ const PREPEND = 'PREPEND';
+
+ /**
+ * What text to prefix the placeholder with when rendering
+ * @var string
+ */
+ protected $_prefix = '';
+
+ /**
+ * What text to append the placeholder with when rendering
+ * @var string
+ */
+ protected $_postfix = '';
+
+ /**
+ * What string to use between individual items in the placeholder when rendering
+ * @var string
+ */
+ protected $_separator = '';
+
+ /**
+ * What string to use as the indentation of output, this will typically be spaces. Eg: ' '
+ * @var string
+ */
+ protected $_indent = '';
+
+ /**
+ * Whether or not we're already capturing for this given container
+ * @var bool
+ */
+ protected $_captureLock = false;
+
+ /**
+ * What type of capture (overwrite (set), append, prepend) to use
+ * @var string
+ */
+ protected $_captureType;
+
+ /**
+ * Key to which to capture content
+ * @var string
+ */
+ protected $_captureKey;
+
+ /**
+ * Constructor - This is needed so that we can attach a class member as the ArrayObject container
+ *
+ * @return \Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ public function __construct()
+ {
+ parent::__construct(array(), parent::ARRAY_AS_PROPS);
+ }
+
+ /**
+ * Set a single value
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function set($value)
+ {
+ $this->exchangeArray(array($value));
+ }
+
+ /**
+ * Prepend a value to the top of the container
+ *
+ * @param mixed $value
+ * @return void
+ */
+ public function prepend($value)
+ {
+ $values = $this->getArrayCopy();
+ array_unshift($values, $value);
+ $this->exchangeArray($values);
+ }
+
+ /**
+ * Retrieve container value
+ *
+ * If single element registered, returns that element; otherwise,
+ * serializes to array.
+ *
+ * @return mixed
+ */
+ public function getValue()
+ {
+ if (1 == count($this)) {
+ $keys = $this->getKeys();
+ $key = array_shift($keys);
+ return $this[$key];
+ }
+
+ return $this->getArrayCopy();
+ }
+
+ /**
+ * Set prefix for __toString() serialization
+ *
+ * @param string $prefix
+ * @return Zend_View_Helper_Placeholder_Container
+ */
+ public function setPrefix($prefix)
+ {
+ $this->_prefix = (string) $prefix;
+ return $this;
+ }
+
+ /**
+ * Retrieve prefix
+ *
+ * @return string
+ */
+ public function getPrefix()
+ {
+ return $this->_prefix;
+ }
+
+ /**
+ * Set postfix for __toString() serialization
+ *
+ * @param string $postfix
+ * @return Zend_View_Helper_Placeholder_Container
+ */
+ public function setPostfix($postfix)
+ {
+ $this->_postfix = (string) $postfix;
+ return $this;
+ }
+
+ /**
+ * Retrieve postfix
+ *
+ * @return string
+ */
+ public function getPostfix()
+ {
+ return $this->_postfix;
+ }
+
+ /**
+ * Set separator for __toString() serialization
+ *
+ * Used to implode elements in container
+ *
+ * @param string $separator
+ * @return Zend_View_Helper_Placeholder_Container
+ */
+ public function setSeparator($separator)
+ {
+ $this->_separator = (string) $separator;
+ return $this;
+ }
+
+ /**
+ * Retrieve separator
+ *
+ * @return string
+ */
+ public function getSeparator()
+ {
+ return $this->_separator;
+ }
+
+ /**
+ * Set the indentation string for __toString() serialization,
+ * optionally, if a number is passed, it will be the number of spaces
+ *
+ * @param string|int $indent
+ * @return Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ public function setIndent($indent)
+ {
+ $this->_indent = $this->getWhitespace($indent);
+ return $this;
+ }
+
+ /**
+ * Retrieve indentation
+ *
+ * @return string
+ */
+ public function getIndent()
+ {
+ return $this->_indent;
+ }
+
+ /**
+ * Retrieve whitespace representation of $indent
+ *
+ * @param int|string $indent
+ * @return string
+ */
+ public function getWhitespace($indent)
+ {
+ if (is_int($indent)) {
+ $indent = str_repeat(' ', $indent);
+ }
+
+ return (string) $indent;
+ }
+
+ /**
+ * Start capturing content to push into placeholder
+ *
+ * @param int|string $type How to capture content into placeholder; append, prepend, or set
+ * @param null $key
+ * @throws Zend_View_Helper_Placeholder_Container_Exception
+ * @return void
+ */
+ public function captureStart($type = Zend_View_Helper_Placeholder_Container_Abstract::APPEND, $key = null)
+ {
+ if ($this->_captureLock) {
+ $e = new Zend_View_Helper_Placeholder_Container_Exception('Cannot nest placeholder captures for the same placeholder');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->_captureLock = true;
+ $this->_captureType = $type;
+ if ((null !== $key) && is_scalar($key)) {
+ $this->_captureKey = (string) $key;
+ }
+ ob_start();
+ }
+
+ /**
+ * End content capture
+ *
+ * @return void
+ */
+ public function captureEnd()
+ {
+ $data = ob_get_clean();
+ $key = null;
+ $this->_captureLock = false;
+ if (null !== $this->_captureKey) {
+ $key = $this->_captureKey;
+ }
+ switch ($this->_captureType) {
+ case self::SET:
+ if (null !== $key) {
+ $this[$key] = $data;
+ } else {
+ $this->exchangeArray(array($data));
+ }
+ break;
+ case self::PREPEND:
+ if (null !== $key) {
+ $array = array($key => $data);
+ $values = $this->getArrayCopy();
+ $final = $array + $values;
+ $this->exchangeArray($final);
+ } else {
+ $this->prepend($data);
+ }
+ break;
+ case self::APPEND:
+ default:
+ if (null !== $key) {
+ if (empty($this[$key])) {
+ $this[$key] = $data;
+ } else {
+ $this[$key] .= $data;
+ }
+ } else {
+ $this[$this->nextIndex()] = $data;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Get keys
+ *
+ * @return array
+ */
+ public function getKeys()
+ {
+ $array = $this->getArrayCopy();
+ return array_keys($array);
+ }
+
+ /**
+ * Next Index
+ *
+ * as defined by the PHP manual
+ * @return int
+ */
+ public function nextIndex()
+ {
+ $keys = $this->getKeys();
+ if (0 == count($keys)) {
+ return 0;
+ }
+
+ return $nextIndex = max($keys) + 1;
+ }
+
+ /**
+ * Render the placeholder
+ *
+ * @param null $indent
+ * @return string
+ */
+ public function toString($indent = null)
+ {
+ // Check items
+ if (0 === $this->count()) {
+ return '';
+ }
+
+ $indent = ($indent !== null)
+ ? $this->getWhitespace($indent)
+ : $this->getIndent();
+
+ $items = $this->getArrayCopy();
+ $return = $indent
+ . $this->getPrefix()
+ . implode($this->getSeparator(), $items)
+ . $this->getPostfix();
+ $return = preg_replace("/(\r\n?|\n)/", '$1' . $indent, $return);
+ return $return;
+ }
+
+ /**
+ * Serialize object to string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder/Container/Exception.php b/library/vendor/Zend/View/Helper/Placeholder/Container/Exception.php
new file mode 100644
index 0000000..aceb769
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder/Container/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/** Zend_View_Exception */
+
+
+/**
+ * Exception for Zend_View_Helper_Placeholder_Container class.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Placeholder_Container_Exception extends Zend_View_Exception
+{
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder/Container/Standalone.php b/library/vendor/Zend/View/Helper/Placeholder/Container/Standalone.php
new file mode 100644
index 0000000..c875ca7
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder/Container/Standalone.php
@@ -0,0 +1,322 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Placeholder_Registry */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Base class for targetted placeholder helpers
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+abstract class Zend_View_Helper_Placeholder_Container_Standalone extends Zend_View_Helper_Abstract implements IteratorAggregate, Countable, ArrayAccess
+{
+ /**
+ * @var Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ protected $_container;
+
+ /**
+ * @var Zend_View_Helper_Placeholder_Registry
+ */
+ protected $_registry;
+
+ /**
+ * Registry key under which container registers itself
+ * @var string
+ */
+ protected $_regKey;
+
+ /**
+ * Flag wheter to automatically escape output, must also be
+ * enforced in the child class if __toString/toString is overriden
+ * @var book
+ */
+ protected $_autoEscape = true;
+
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ $this->setRegistry(Zend_View_Helper_Placeholder_Registry::getRegistry());
+ $this->setContainer($this->getRegistry()->getContainer($this->_regKey));
+ }
+
+ /**
+ * Retrieve registry
+ *
+ * @return Zend_View_Helper_Placeholder_Registry
+ */
+ public function getRegistry()
+ {
+ return $this->_registry;
+ }
+
+ /**
+ * Set registry object
+ *
+ * @param Zend_View_Helper_Placeholder_Registry $registry
+ * @return Zend_View_Helper_Placeholder_Container_Standalone
+ */
+ public function setRegistry(Zend_View_Helper_Placeholder_Registry $registry)
+ {
+ $this->_registry = $registry;
+ return $this;
+ }
+
+ /**
+ * Set whether or not auto escaping should be used
+ *
+ * @param bool $autoEscape whether or not to auto escape output
+ * @return Zend_View_Helper_Placeholder_Container_Standalone
+ */
+ public function setAutoEscape($autoEscape = true)
+ {
+ $this->_autoEscape = ($autoEscape) ? true : false;
+ return $this;
+ }
+
+ /**
+ * Return whether autoEscaping is enabled or disabled
+ *
+ * return bool
+ */
+ public function getAutoEscape()
+ {
+ return $this->_autoEscape;
+ }
+
+ /**
+ * Escape a string
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function _escape($string)
+ {
+ $enc = 'UTF-8';
+ if ($this->view instanceof Zend_View_Interface
+ && method_exists($this->view, 'getEncoding')
+ ) {
+ $enc = $this->view->getEncoding();
+ }
+
+ return htmlspecialchars((string) $string, ENT_COMPAT, $enc);
+ }
+
+ /**
+ * Set container on which to operate
+ *
+ * @param Zend_View_Helper_Placeholder_Container_Abstract $container
+ * @return Zend_View_Helper_Placeholder_Container_Standalone
+ */
+ public function setContainer(Zend_View_Helper_Placeholder_Container_Abstract $container)
+ {
+ $this->_container = $container;
+ return $this;
+ }
+
+ /**
+ * Retrieve placeholder container
+ *
+ * @return Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ public function getContainer()
+ {
+ return $this->_container;
+ }
+
+ /**
+ * Overloading: set property value
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return void
+ */
+ public function __set($key, $value)
+ {
+ $container = $this->getContainer();
+ $container[$key] = $value;
+ }
+
+ /**
+ * Overloading: retrieve property
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ $container = $this->getContainer();
+ if (isset($container[$key])) {
+ return $container[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * Overloading: check if property is set
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ $container = $this->getContainer();
+ return isset($container[$key]);
+ }
+
+ /**
+ * Overloading: unset property
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key)
+ {
+ $container = $this->getContainer();
+ if (isset($container[$key])) {
+ unset($container[$key]);
+ }
+ }
+
+ /**
+ * Overload
+ *
+ * Proxy to container methods
+ *
+ * @param string $method
+ * @param array $args
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ $container = $this->getContainer();
+ if (method_exists($container, $method)) {
+ $return = call_user_func_array(array($container, $method), $args);
+ if ($return === $container) {
+ // If the container is returned, we really want the current object
+ return $this;
+ }
+ return $return;
+ }
+
+ $e = new Zend_View_Exception('Method "' . $method . '" does not exist');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ /**
+ * String representation
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return $this->getContainer()->toString();
+ }
+
+ /**
+ * Cast to string representation
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * Countable
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ $container = $this->getContainer();
+ return count($container);
+ }
+
+ /**
+ * ArrayAccess: offsetExists
+ *
+ * @param string|int $offset
+ * @return bool
+ */
+ public function offsetExists($offset): bool
+ {
+ return $this->getContainer()->offsetExists($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetGet
+ *
+ * @param string|int $offset
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->getContainer()->offsetGet($offset);
+ }
+
+ /**
+ * ArrayAccess: offsetSet
+ *
+ * @param string|int $offset
+ * @param mixed $value
+ * @return void
+ */
+ public function offsetSet($offset, $value): void
+ {
+ $this->getContainer()->offsetSet($offset, $value);
+ }
+
+ /**
+ * ArrayAccess: offsetUnset
+ *
+ * @param string|int $offset
+ * @return void
+ */
+ public function offsetUnset($offset): void
+ {
+ $this->getContainer()->offsetUnset($offset);
+ }
+
+ /**
+ * IteratorAggregate: get Iterator
+ *
+ * @return Iterator
+ */
+ public function getIterator(): Traversable
+ {
+ return $this->getContainer()->getIterator();
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder/Registry.php b/library/vendor/Zend/View/Helper/Placeholder/Registry.php
new file mode 100644
index 0000000..111604c
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder/Registry.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_Registry */
+
+/** Zend_View_Helper_Placeholder_Container_Abstract */
+
+/** Zend_View_Helper_Placeholder_Container */
+
+/**
+ * Registry for placeholder containers
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Placeholder_Registry
+{
+ /**
+ * Zend_Registry key under which placeholder registry exists
+ * @const string
+ */
+ const REGISTRY_KEY = 'Zend_View_Helper_Placeholder_Registry';
+
+ /**
+ * Default container class
+ * @var string
+ */
+ protected $_containerClass = 'Zend_View_Helper_Placeholder_Container';
+
+ /**
+ * Placeholder containers
+ * @var array
+ */
+ protected $_items = array();
+
+ /**
+ * Retrieve or create registry instnace
+ *
+ * @return void
+ */
+ public static function getRegistry()
+ {
+ if (Zend_Registry::isRegistered(self::REGISTRY_KEY)) {
+ $registry = Zend_Registry::get(self::REGISTRY_KEY);
+ } else {
+ $registry = new self();
+ Zend_Registry::set(self::REGISTRY_KEY, $registry);
+ }
+
+ return $registry;
+ }
+
+ /**
+ * createContainer
+ *
+ * @param string $key
+ * @param array $value
+ * @return Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ public function createContainer($key, array $value = array())
+ {
+ $key = (string) $key;
+
+ $this->_items[$key] = new $this->_containerClass($value);
+ return $this->_items[$key];
+ }
+
+ /**
+ * Retrieve a placeholder container
+ *
+ * @param string $key
+ * @return Zend_View_Helper_Placeholder_Container_Abstract
+ */
+ public function getContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->_items[$key])) {
+ return $this->_items[$key];
+ }
+
+ $container = $this->createContainer($key);
+
+ return $container;
+ }
+
+ /**
+ * Does a particular container exist?
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function containerExists($key)
+ {
+ $key = (string) $key;
+ $return = array_key_exists($key, $this->_items);
+ return $return;
+ }
+
+ /**
+ * Set the container for an item in the registry
+ *
+ * @param string $key
+ * @param Zend_View_Placeholder_Container_Abstract $container
+ * @return Zend_View_Placeholder_Registry
+ */
+ public function setContainer($key, Zend_View_Helper_Placeholder_Container_Abstract $container)
+ {
+ $key = (string) $key;
+ $this->_items[$key] = $container;
+ return $this;
+ }
+
+ /**
+ * Delete a container
+ *
+ * @param string $key
+ * @return bool
+ */
+ public function deleteContainer($key)
+ {
+ $key = (string) $key;
+ if (isset($this->_items[$key])) {
+ unset($this->_items[$key]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the container class to use
+ *
+ * @param string $name
+ * @return Zend_View_Helper_Placeholder_Registry
+ */
+ public function setContainerClass($name)
+ {
+ if (!class_exists($name)) {
+ Zend_Loader::loadClass($name);
+ }
+
+ $reflection = new ReflectionClass($name);
+ if (!$reflection->isSubclassOf(new ReflectionClass('Zend_View_Helper_Placeholder_Container_Abstract'))) {
+ $e = new Zend_View_Helper_Placeholder_Registry_Exception('Invalid Container class specified');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $this->_containerClass = $name;
+ return $this;
+ }
+
+ /**
+ * Retrieve the container class
+ *
+ * @return string
+ */
+ public function getContainerClass()
+ {
+ return $this->_containerClass;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Placeholder/Registry/Exception.php b/library/vendor/Zend/View/Helper/Placeholder/Registry/Exception.php
new file mode 100644
index 0000000..42d7a28
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Placeholder/Registry/Exception.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+
+/** Zend_View_Exception */
+
+
+/**
+ * Exception for Zend_View_Helper_Placeholder_Registry class.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Placeholder_Registry_Exception extends Zend_View_Exception
+{
+}
diff --git a/library/vendor/Zend/View/Helper/RenderToPlaceholder.php b/library/vendor/Zend/View/Helper/RenderToPlaceholder.php
new file mode 100644
index 0000000..1d11186
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/RenderToPlaceholder.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Renders a template and stores the rendered output as a placeholder
+ * variable for later use.
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+class Zend_View_Helper_RenderToPlaceholder extends Zend_View_Helper_Abstract
+{
+
+ /**
+ * Renders a template and stores the rendered output as a placeholder
+ * variable for later use.
+ *
+ * @param string $script The template script to render
+ * @param string $placeholder The placeholder variable name in which to store the rendered output
+ * @return void
+ */
+ public function renderToPlaceholder($script, $placeholder)
+ {
+ $this->view->placeholder($placeholder)->captureStart();
+ echo $this->view->render($script);
+ $this->view->placeholder($placeholder)->captureEnd();
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/ServerUrl.php b/library/vendor/Zend/View/Helper/ServerUrl.php
new file mode 100644
index 0000000..c38ec8b
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/ServerUrl.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Helper for returning the current server URL (optionally with request URI)
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_ServerUrl
+{
+ /**
+ * Scheme
+ *
+ * @var string
+ */
+ protected $_scheme;
+
+ /**
+ * Host (including port)
+ *
+ * @var string
+ */
+ protected $_host;
+
+ /**
+ * Constructor
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ switch (true) {
+ case (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true)):
+ case (isset($_SERVER['HTTP_SCHEME']) && ($_SERVER['HTTP_SCHEME'] == 'https')):
+ case (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == 443)):
+ $scheme = 'https';
+ break;
+ default:
+ $scheme = 'http';
+ }
+ $this->setScheme($scheme);
+
+ if (isset($_SERVER['HTTP_HOST']) && !empty($_SERVER['HTTP_HOST'])) {
+ $this->setHost($_SERVER['HTTP_HOST']);
+ } else if (isset($_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT'])) {
+ $name = $_SERVER['SERVER_NAME'];
+ $port = $_SERVER['SERVER_PORT'];
+
+ if (($scheme == 'http' && $port == 80) ||
+ ($scheme == 'https' && $port == 443)) {
+ $this->setHost($name);
+ } else {
+ $this->setHost($name . ':' . $port);
+ }
+ }
+ }
+
+ /**
+ * View helper entry point:
+ * Returns the current host's URL like http://site.com
+ *
+ * @param string|boolean $requestUri [optional] if true, the request URI
+ * found in $_SERVER will be appended
+ * as a path. If a string is given, it
+ * will be appended as a path. Default
+ * is to not append any path.
+ * @return string server url
+ */
+ public function serverUrl($requestUri = null)
+ {
+ if ($requestUri === true) {
+ $path = $_SERVER['REQUEST_URI'];
+ } else if (is_string($requestUri)) {
+ $path = $requestUri;
+ } else {
+ $path = '';
+ }
+
+ return $this->getScheme() . '://' . $this->getHost() . $path;
+ }
+
+ /**
+ * Returns host
+ *
+ * @return string host
+ */
+ public function getHost()
+ {
+ return $this->_host;
+ }
+
+ /**
+ * Sets host
+ *
+ * @param string $host new host
+ * @return Zend_View_Helper_ServerUrl fluent interface, returns self
+ */
+ public function setHost($host)
+ {
+ $this->_host = $host;
+ return $this;
+ }
+
+ /**
+ * Returns scheme (typically http or https)
+ *
+ * @return string scheme (typically http or https)
+ */
+ public function getScheme()
+ {
+ return $this->_scheme;
+ }
+
+ /**
+ * Sets scheme (typically http or https)
+ *
+ * @param string $scheme new scheme (typically http or https)
+ * @return Zend_View_Helper_ServerUrl fluent interface, returns self
+ */
+ public function setScheme($scheme)
+ {
+ $this->_scheme = $scheme;
+ return $this;
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Translate.php b/library/vendor/Zend/View/Helper/Translate.php
new file mode 100644
index 0000000..8a7d916
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Translate.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/** Zend_Locale */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Translation view helper
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Translate extends Zend_View_Helper_Abstract
+{
+ /**
+ * Translation object
+ *
+ * @var Zend_Translate_Adapter
+ */
+ protected $_translator;
+
+ /**
+ * Constructor for manually handling
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter $translate Instance of Zend_Translate
+ */
+ public function __construct($translate = null)
+ {
+ if ($translate !== null) {
+ $this->setTranslator($translate);
+ }
+ }
+
+ /**
+ * Translate a message
+ * You can give multiple params or an array of params.
+ * If you want to output another locale just set it as last single parameter
+ * Example 1: translate('%1\$s + %2\$s', $value1, $value2, $locale);
+ * Example 2: translate('%1\$s + %2\$s', array($value1, $value2), $locale);
+ *
+ * @param string $messageid Id of the message to be translated
+ * @return string|Zend_View_Helper_Translate Translated message
+ */
+ public function translate($messageid = null)
+ {
+ if ($messageid === null) {
+ return $this;
+ }
+
+ $translate = $this->getTranslator();
+ $options = func_get_args();
+
+ array_shift($options);
+ $count = count($options);
+ $locale = null;
+ if ($count > 0) {
+ if (Zend_Locale::isLocale($options[($count - 1)], null, false) !== false) {
+ $locale = array_pop($options);
+ }
+ }
+
+ if ((count($options) === 1) and (is_array($options[0]) === true)) {
+ $options = $options[0];
+ }
+
+ if ($translate !== null) {
+ $messageid = $translate->translate($messageid, $locale);
+ }
+
+ if (count($options) === 0) {
+ return $messageid;
+ }
+
+ return vsprintf($messageid, $options);
+ }
+
+ /**
+ * Sets a translation Adapter for translation
+ *
+ * @param Zend_Translate|Zend_Translate_Adapter $translate Instance of Zend_Translate
+ * @throws Zend_View_Exception When no or a false instance was set
+ * @return Zend_View_Helper_Translate
+ */
+ public function setTranslator($translate)
+ {
+ if ($translate instanceof Zend_Translate_Adapter) {
+ $this->_translator = $translate;
+ } else if ($translate instanceof Zend_Translate) {
+ $this->_translator = $translate->getAdapter();
+ } else {
+ $e = new Zend_View_Exception('You must set an instance of Zend_Translate or Zend_Translate_Adapter');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Retrieve translation object
+ *
+ * @return Zend_Translate_Adapter|null
+ */
+ public function getTranslator()
+ {
+ if ($this->_translator === null) {
+ if (Zend_Registry::isRegistered('Zend_Translate')) {
+ $this->setTranslator(Zend_Registry::get('Zend_Translate'));
+ }
+ }
+
+ return $this->_translator;
+ }
+
+ /**
+ * Set's an new locale for all further translations
+ *
+ * @param string|Zend_Locale $locale New locale to set
+ * @throws Zend_View_Exception When no Zend_Translate instance was set
+ * @return Zend_View_Helper_Translate
+ */
+ public function setLocale($locale = null)
+ {
+ $translate = $this->getTranslator();
+ if ($translate === null) {
+ $e = new Zend_View_Exception('You must set an instance of Zend_Translate or Zend_Translate_Adapter');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ $translate->setLocale($locale);
+ return $this;
+ }
+
+ /**
+ * Returns the set locale for translations
+ *
+ * @throws Zend_View_Exception When no Zend_Translate instance was set
+ * @return string|Zend_Locale
+ */
+ public function getLocale()
+ {
+ $translate = $this->getTranslator();
+ if ($translate === null) {
+ $e = new Zend_View_Exception('You must set an instance of Zend_Translate or Zend_Translate_Adapter');
+ $e->setView($this->view);
+ throw $e;
+ }
+
+ return $translate->getLocale();
+ }
+}
diff --git a/library/vendor/Zend/View/Helper/Url.php b/library/vendor/Zend/View/Helper/Url.php
new file mode 100644
index 0000000..3ed84ca
--- /dev/null
+++ b/library/vendor/Zend/View/Helper/Url.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @version $Id$
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+
+/** Zend_View_Helper_Abstract.php */
+
+/**
+ * Helper for making easy links and getting urls that depend on the routes and router
+ *
+ * @package Zend_View
+ * @subpackage Helper
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Helper_Url extends Zend_View_Helper_Abstract
+{
+ /**
+ * Generates an url given the name of a route.
+ *
+ * @access public
+ *
+ * @param array $urlOptions Options passed to the assemble method of the Route object.
+ * @param mixed $name The name of a Route to use. If null it will use the current Route
+ * @param bool $reset Whether or not to reset the route defaults with those provided
+ * @return string Url for the link href attribute.
+ */
+ public function url(array $urlOptions = array(), $name = null, $reset = false, $encode = true)
+ {
+ $router = Zend_Controller_Front::getInstance()->getRouter();
+ return $router->assemble($urlOptions, $name, $reset, $encode);
+ }
+}
diff --git a/library/vendor/Zend/View/Interface.php b/library/vendor/Zend/View/Interface.php
new file mode 100644
index 0000000..496ec71
--- /dev/null
+++ b/library/vendor/Zend/View/Interface.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * Interface class for Zend_View compatible template engine implementations
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+interface Zend_View_Interface
+{
+ /**
+ * Return the template engine object, if any
+ *
+ * If using a third-party template engine, such as Smarty, patTemplate,
+ * phplib, etc, return the template engine object. Useful for calling
+ * methods on these objects, such as for setting filters, modifiers, etc.
+ *
+ * @return mixed
+ */
+ public function getEngine();
+
+ /**
+ * Set the path to find the view script used by render()
+ *
+ * @param string|array The directory (-ies) to set as the path. Note that
+ * the concrete view implentation may not necessarily support multiple
+ * directories.
+ * @return void
+ */
+ public function setScriptPath($path);
+
+ /**
+ * Retrieve all view script paths
+ *
+ * @return array
+ */
+ public function getScriptPaths();
+
+ /**
+ * Set a base path to all view resources
+ *
+ * @param string $path
+ * @param string $classPrefix
+ * @return void
+ */
+ public function setBasePath($path, $classPrefix = 'Zend_View');
+
+ /**
+ * Add an additional path to view resources
+ *
+ * @param string $path
+ * @param string $classPrefix
+ * @return void
+ */
+ public function addBasePath($path, $classPrefix = 'Zend_View');
+
+ /**
+ * Assign a variable to the view
+ *
+ * @param string $key The variable name.
+ * @param mixed $val The variable value.
+ * @return void
+ */
+ public function __set($key, $val);
+
+ /**
+ * Allows testing with empty() and isset() to work
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function __isset($key);
+
+ /**
+ * Allows unset() on object properties to work
+ *
+ * @param string $key
+ * @return void
+ */
+ public function __unset($key);
+
+ /**
+ * Assign variables to the view script via differing strategies.
+ *
+ * Suggested implementation is to allow setting a specific key to the
+ * specified value, OR passing an array of key => value pairs to set en
+ * masse.
+ *
+ * @see __set()
+ * @param string|array $spec The assignment strategy to use (key or array of key
+ * => value pairs)
+ * @param mixed $value (Optional) If assigning a named variable, use this
+ * as the value.
+ * @return void
+ */
+ public function assign($spec, $value = null);
+
+ /**
+ * Clear all assigned variables
+ *
+ * Clears all variables assigned to Zend_View either via {@link assign()} or
+ * property overloading ({@link __get()}/{@link __set()}).
+ *
+ * @return void
+ */
+ public function clearVars();
+
+ /**
+ * Processes a view script and returns the output.
+ *
+ * @param string $name The script name to process.
+ * @return string The script output.
+ */
+ public function render($name);
+}
diff --git a/library/vendor/Zend/View/Stream.php b/library/vendor/Zend/View/Stream.php
new file mode 100644
index 0000000..ae8d524
--- /dev/null
+++ b/library/vendor/Zend/View/Stream.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+/**
+ * Stream wrapper to convert markup of mostly-PHP templates into PHP prior to
+ * include().
+ *
+ * Based in large part on the example at
+ * http://www.php.net/manual/en/function.stream-wrapper-register.php
+ *
+ * As well as the example provided at:
+ * http://mikenaberezny.com/2006/02/19/symphony-templates-ruby-erb/
+ * written by
+ * Mike Naberezny (@link http://mikenaberezny.com)
+ * Paul M. Jones (@link http://paul-m-jones.com)
+ *
+ * @category Zend
+ * @package Zend_View
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_View_Stream
+{
+ /**
+ * Current stream position.
+ *
+ * @var int
+ */
+ protected $_pos = 0;
+
+ /**
+ * Data for streaming.
+ *
+ * @var string
+ */
+ protected $_data;
+
+ /**
+ * Stream stats.
+ *
+ * @var array
+ */
+ protected $_stat;
+
+ /**
+ * Opens the script file and converts markup.
+ */
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ // get the view script source
+ $path = str_replace('zend.view://', '', $path);
+ $this->_data = file_get_contents($path);
+
+ /**
+ * If reading the file failed, update our local stat store
+ * to reflect the real stat of the file, then return on failure
+ */
+ if ($this->_data === false) {
+ $this->_stat = stat($path);
+ return false;
+ }
+
+ /**
+ * Convert <?= ?> to long-form <?php echo ?> and <? ?> to <?php ?>
+ *
+ */
+ $this->_data = preg_replace('/\<\?\=/', "<?php echo ", $this->_data);
+ $this->_data = preg_replace('/<\?(?!xml|php)/s', '<?php ', $this->_data);
+
+ /**
+ * file_get_contents() won't update PHP's stat cache, so we grab a stat
+ * of the file to prevent additional reads should the script be
+ * requested again, which will make include() happy.
+ */
+ $this->_stat = stat($path);
+
+ return true;
+ }
+
+ /**
+ * Included so that __FILE__ returns the appropriate info
+ *
+ * @return array
+ */
+ public function url_stat()
+ {
+ return $this->_stat;
+ }
+
+ /**
+ * Reads from the stream.
+ */
+ public function stream_read($count)
+ {
+ $ret = substr($this->_data, $this->_pos, $count);
+ $this->_pos += strlen($ret);
+ return $ret;
+ }
+
+
+ /**
+ * Tells the current position in the stream.
+ */
+ public function stream_tell()
+ {
+ return $this->_pos;
+ }
+
+
+ /**
+ * Tells if we are at the end of the stream.
+ */
+ public function stream_eof()
+ {
+ return $this->_pos >= strlen($this->_data);
+ }
+
+
+ /**
+ * Stream statistics.
+ */
+ public function stream_stat()
+ {
+ return $this->_stat;
+ }
+
+
+ /**
+ * Seek to a specific point in the stream.
+ */
+ public function stream_seek($offset, $whence)
+ {
+ switch ($whence) {
+ case SEEK_SET:
+ if ($offset < strlen($this->_data) && $offset >= 0) {
+ $this->_pos = $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ case SEEK_CUR:
+ if ($offset >= 0) {
+ $this->_pos += $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ case SEEK_END:
+ if (strlen($this->_data) + $offset >= 0) {
+ $this->_pos = strlen($this->_data) + $offset;
+ return true;
+ } else {
+ return false;
+ }
+ break;
+
+ default:
+ return false;
+ }
+ }
+}
diff --git a/library/vendor/Zend/Xml/Exception.php b/library/vendor/Zend/Xml/Exception.php
new file mode 100644
index 0000000..3d12d82
--- /dev/null
+++ b/library/vendor/Zend/Xml/Exception.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Xml
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @see Zend_Exception
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Xml
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Xml_Exception extends Zend_Exception
+{}
diff --git a/library/vendor/Zend/Xml/Security.php b/library/vendor/Zend/Xml/Security.php
new file mode 100644
index 0000000..321e1e6
--- /dev/null
+++ b/library/vendor/Zend/Xml/Security.php
@@ -0,0 +1,486 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category Zend
+ * @package Zend_Xml
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ * @version $Id$
+ */
+
+
+/**
+ * @category Zend
+ * @package Zend_Xml_SecurityScan
+ * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license http://framework.zend.com/license/new-bsd New BSD License
+ */
+class Zend_Xml_Security
+{
+ const ENTITY_DETECT = 'Detected use of ENTITY in XML, disabled to prevent XXE/XEE attacks';
+
+ /**
+ * Heuristic scan to detect entity in XML
+ *
+ * @param string $xml
+ * @throws Zend_Xml_Exception If entity expansion or external entity declaration was discovered.
+ */
+ protected static function heuristicScan($xml)
+ {
+ foreach (self::getEntityComparison($xml) as $compare) {
+ if (strpos($xml, $compare) !== false) {
+ throw new Zend_Xml_Exception(self::ENTITY_DETECT);
+ }
+ }
+ }
+
+ /**
+ * @param integer $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param integer $errline
+ * @return bool
+ */
+ public static function loadXmlErrorHandler($errno, $errstr, $errfile, $errline)
+ {
+ if (substr_count($errstr, 'DOMDocument::loadXML()') > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Scan XML string for potential XXE and XEE attacks
+ *
+ * @param string $xml
+ * @param DomDocument $dom
+ * @throws Zend_Xml_Exception
+ * @return SimpleXMLElement|DomDocument|boolean
+ */
+ public static function scan($xml, DOMDocument $dom = null)
+ {
+ // If running with PHP-FPM we perform an heuristic scan
+ // We cannot use libxml_disable_entity_loader because of this bug
+ // @see https://bugs.php.net/bug.php?id=64938
+ if (self::isPhpFpm()) {
+ self::heuristicScan($xml);
+ }
+
+ if (null === $dom) {
+ $simpleXml = true;
+ $dom = new DOMDocument();
+ }
+
+ if (!self::isPhpFpm()) {
+ $loadEntities = libxml_disable_entity_loader(true);
+ $useInternalXmlErrors = libxml_use_internal_errors(true);
+ }
+
+ // Load XML with network access disabled (LIBXML_NONET)
+ // error disabled with @ for PHP-FPM scenario
+ set_error_handler(array('Zend_Xml_Security', 'loadXmlErrorHandler'), E_WARNING);
+
+ $result = $dom->loadXml($xml, LIBXML_NONET);
+ restore_error_handler();
+
+ if (!$result) {
+ // Entity load to previous setting
+ if (!self::isPhpFpm()) {
+ libxml_disable_entity_loader($loadEntities);
+ libxml_use_internal_errors($useInternalXmlErrors);
+ }
+ return false;
+ }
+
+ // Scan for potential XEE attacks using ENTITY, if not PHP-FPM
+ if (!self::isPhpFpm()) {
+ foreach ($dom->childNodes as $child) {
+ if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
+ if ($child->entities->length > 0) {
+ throw new Zend_Xml_Exception(self::ENTITY_DETECT);
+ }
+ }
+ }
+ }
+
+ // Entity load to previous setting
+ if (!self::isPhpFpm()) {
+ libxml_disable_entity_loader($loadEntities);
+ libxml_use_internal_errors($useInternalXmlErrors);
+ }
+
+ if (isset($simpleXml)) {
+ $result = simplexml_import_dom($dom);
+ if (!$result instanceof SimpleXMLElement) {
+ return false;
+ }
+ return $result;
+ }
+ return $dom;
+ }
+
+ /**
+ * Scan XML file for potential XXE/XEE attacks
+ *
+ * @param string $file
+ * @param DOMDocument $dom
+ * @throws Zend_Xml_Exception
+ * @return SimpleXMLElement|DomDocument
+ */
+ public static function scanFile($file, DOMDocument $dom = null)
+ {
+ if (!file_exists($file)) {
+ throw new Zend_Xml_Exception(
+ "The file $file specified doesn't exist"
+ );
+ }
+ return self::scan(file_get_contents($file), $dom);
+ }
+
+ /**
+ * Return true if PHP is running with PHP-FPM
+ *
+ * This method is mainly used to determine whether or not heuristic checks
+ * (vs libxml checks) should be made, due to threading issues in libxml;
+ * under php-fpm, threading becomes a concern.
+ *
+ * However, PHP versions 5.5.22+ and 5.6.6+ contain a patch to the
+ * libxml support in PHP that makes the libxml checks viable; in such
+ * versions, this method will return false to enforce those checks, which
+ * are more strict and accurate than the heuristic checks.
+ *
+ * @return boolean
+ */
+ public static function isPhpFpm()
+ {
+ $isVulnerableVersion = (
+ version_compare(PHP_VERSION, '5.5.22', 'lt')
+ || (
+ version_compare(PHP_VERSION, '5.6', 'gte')
+ && version_compare(PHP_VERSION, '5.6.6', 'lt')
+ )
+ );
+
+ if (substr(php_sapi_name(), 0, 3) === 'fpm' && $isVulnerableVersion) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determine and return the string(s) to use for the <!ENTITY comparison.
+ *
+ * @param string $xml
+ * @return string[]
+ */
+ protected static function getEntityComparison($xml)
+ {
+ $encodingMap = self::getAsciiEncodingMap();
+ return array_map(
+ array(__CLASS__, 'generateEntityComparison'),
+ self::detectXmlEncoding($xml, self::detectStringEncoding($xml))
+ );
+ }
+
+ /**
+ * Determine the string encoding.
+ *
+ * Determines string encoding from either a detected BOM or a
+ * heuristic.
+ *
+ * @param string $xml
+ * @return string File encoding
+ */
+ protected static function detectStringEncoding($xml)
+ {
+ $encoding = self::detectBom($xml);
+ return ($encoding) ? $encoding : self::detectXmlStringEncoding($xml);
+ }
+
+ /**
+ * Attempt to match a known BOM.
+ *
+ * Iterates through the return of getBomMap(), comparing the initial bytes
+ * of the provided string to the BOM of each; if a match is determined,
+ * it returns the encoding.
+ *
+ * @param string $string
+ * @return false|string Returns encoding on success.
+ */
+ protected static function detectBom($string)
+ {
+ foreach (self::getBomMap() as $criteria) {
+ if (0 === strncmp($string, $criteria['bom'], $criteria['length'])) {
+ return $criteria['encoding'];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Attempt to detect the string encoding of an XML string.
+ *
+ * @param string $xml
+ * @return string Encoding
+ */
+ protected static function detectXmlStringEncoding($xml)
+ {
+ foreach (self::getAsciiEncodingMap() as $encoding => $generator) {
+ $prefix = call_user_func($generator, '<' . '?xml');
+ if (0 === strncmp($xml, $prefix, strlen($prefix))) {
+ return $encoding;
+ }
+ }
+
+ // Fallback
+ return 'UTF-8';
+ }
+
+ /**
+ * Attempt to detect the specified XML encoding.
+ *
+ * Using the file's encoding, determines if an "encoding" attribute is
+ * present and well-formed in the XML declaration; if so, it returns a
+ * list with both the ASCII representation of that declaration and the
+ * original file encoding.
+ *
+ * If not, a list containing only the provided file encoding is returned.
+ *
+ * @param string $xml
+ * @param string $fileEncoding
+ * @return string[] Potential XML encodings
+ */
+ protected static function detectXmlEncoding($xml, $fileEncoding)
+ {
+ $encodingMap = self::getAsciiEncodingMap();
+ $generator = $encodingMap[$fileEncoding];
+ $encAttr = call_user_func($generator, 'encoding="');
+ $quote = call_user_func($generator, '"');
+ $close = call_user_func($generator, '>');
+
+ $closePos = strpos($xml, $close);
+ if (false === $closePos) {
+ return array($fileEncoding);
+ }
+
+ $encPos = strpos($xml, $encAttr);
+ if (false === $encPos
+ || $encPos > $closePos
+ ) {
+ return array($fileEncoding);
+ }
+
+ $encPos += strlen($encAttr);
+ $quotePos = strpos($xml, $quote, $encPos);
+ if (false === $quotePos) {
+ return array($fileEncoding);
+ }
+
+ $encoding = self::substr($xml, $encPos, $quotePos);
+ return array(
+ // Following line works because we're only supporting 8-bit safe encodings at this time.
+ str_replace('\0', '', $encoding), // detected encoding
+ $fileEncoding, // file encoding
+ );
+ }
+
+ /**
+ * Return a list of BOM maps.
+ *
+ * Returns a list of common encoding -> BOM maps, along with the character
+ * length to compare against.
+ *
+ * @link https://en.wikipedia.org/wiki/Byte_order_mark
+ * @return array
+ */
+ protected static function getBomMap()
+ {
+ return array(
+ array(
+ 'encoding' => 'UTF-32BE',
+ 'bom' => pack('CCCC', 0x00, 0x00, 0xfe, 0xff),
+ 'length' => 4,
+ ),
+ array(
+ 'encoding' => 'UTF-32LE',
+ 'bom' => pack('CCCC', 0xff, 0xfe, 0x00, 0x00),
+ 'length' => 4,
+ ),
+ array(
+ 'encoding' => 'GB-18030',
+ 'bom' => pack('CCCC', 0x84, 0x31, 0x95, 0x33),
+ 'length' => 4,
+ ),
+ array(
+ 'encoding' => 'UTF-16BE',
+ 'bom' => pack('CC', 0xfe, 0xff),
+ 'length' => 2,
+ ),
+ array(
+ 'encoding' => 'UTF-16LE',
+ 'bom' => pack('CC', 0xff, 0xfe),
+ 'length' => 2,
+ ),
+ array(
+ 'encoding' => 'UTF-8',
+ 'bom' => pack('CCC', 0xef, 0xbb, 0xbf),
+ 'length' => 3,
+ ),
+ );
+ }
+
+ /**
+ * Return a map of encoding => generator pairs.
+ *
+ * Returns a map of encoding => generator pairs, where the generator is a
+ * callable that accepts a string and returns the appropriate byte order
+ * sequence of that string for the encoding.
+ *
+ * @return array
+ */
+ protected static function getAsciiEncodingMap()
+ {
+ return array(
+ 'UTF-32BE' => array(__CLASS__, 'encodeToUTF32BE'),
+ 'UTF-32LE' => array(__CLASS__, 'encodeToUTF32LE'),
+ 'UTF-32odd1' => array(__CLASS__, 'encodeToUTF32odd1'),
+ 'UTF-32odd2' => array(__CLASS__, 'encodeToUTF32odd2'),
+ 'UTF-16BE' => array(__CLASS__, 'encodeToUTF16BE'),
+ 'UTF-16LE' => array(__CLASS__, 'encodeToUTF16LE'),
+ 'UTF-8' => array(__CLASS__, 'encodeToUTF8'),
+ 'GB-18030' => array(__CLASS__, 'encodeToUTF8'),
+ );
+ }
+
+ /**
+ * Binary-safe substr.
+ *
+ * substr() is not binary-safe; this method loops by character to ensure
+ * multi-byte characters are aggregated correctly.
+ *
+ * @param string $string
+ * @param int $start
+ * @param int $end
+ * @return string
+ */
+ protected static function substr($string, $start, $end)
+ {
+ $substr = '';
+ for ($i = $start; $i < $end; $i += 1) {
+ $substr .= $string[$i];
+ }
+ return $substr;
+ }
+
+ /**
+ * Generate an entity comparison based on the given encoding.
+ *
+ * This patch is internal only, and public only so it can be used as a
+ * callable to pass to array_map.
+ *
+ * @internal
+ * @param string $encoding
+ * @return string
+ */
+ public static function generateEntityComparison($encoding)
+ {
+ $encodingMap = self::getAsciiEncodingMap();
+ $generator = isset($encodingMap[$encoding]) ? $encodingMap[$encoding] : $encodingMap['UTF-8'];
+ return call_user_func($generator, '<!ENTITY');
+ }
+
+ /**
+ * Encode an ASCII string to UTF-32BE
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF32BE($ascii)
+ {
+ return preg_replace('/(.)/', "\0\0\0\\1", $ascii);
+ }
+
+ /**
+ * Encode an ASCII string to UTF-32LE
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF32LE($ascii)
+ {
+ return preg_replace('/(.)/', "\\1\0\0\0", $ascii);
+ }
+
+ /**
+ * Encode an ASCII string to UTF-32odd1
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF32odd1($ascii)
+ {
+ return preg_replace('/(.)/', "\0\\1\0\0", $ascii);
+ }
+
+ /**
+ * Encode an ASCII string to UTF-32odd2
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF32odd2($ascii)
+ {
+ return preg_replace('/(.)/', "\0\0\\1\0", $ascii);
+ }
+
+ /**
+ * Encode an ASCII string to UTF-16BE
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF16BE($ascii)
+ {
+ return preg_replace('/(.)/', "\0\\1", $ascii);
+ }
+
+ /**
+ * Encode an ASCII string to UTF-16LE
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF16LE($ascii)
+ {
+ return preg_replace('/(.)/', "\\1\0", $ascii);
+ }
+
+ /**
+ * Encode an ASCII string to UTF-8
+ *
+ * @internal
+ * @param string $ascii
+ * @return string
+ */
+ public static function encodeToUTF8($ascii)
+ {
+ return $ascii;
+ }
+}
diff --git a/library/vendor/dompdf/AUTHORS.md b/library/vendor/dompdf/AUTHORS.md
new file mode 100644
index 0000000..6861479
--- /dev/null
+++ b/library/vendor/dompdf/AUTHORS.md
@@ -0,0 +1,24 @@
+Dompdf was designed and developed by Benj Carson.
+
+### Current Team
+
+* **Brian Sweeney** (maintainer)
+* **Till Berger**
+
+### Alumni
+
+* **Benj Carson** (creator)
+* **Fabien Ménager**
+* **Simon Berger**
+* **Orion Richardson**
+
+### Contributors
+* **Gabriel Bull**
+* **Barry vd. Heuvel**
+* **Ryan H. Masten**
+* **Helmut Tischer**
+* [and many more...](https://github.com/dompdf/dompdf/graphs/contributors)
+
+### Thanks
+
+Dompdf would not have been possible without strong community support.
diff --git a/library/vendor/dompdf/LICENSE.LGPL b/library/vendor/dompdf/LICENSE.LGPL
new file mode 100644
index 0000000..6ef5de8
--- /dev/null
+++ b/library/vendor/dompdf/LICENSE.LGPL
@@ -0,0 +1,456 @@
+ 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. \ No newline at end of file
diff --git a/library/vendor/dompdf/README.md b/library/vendor/dompdf/README.md
new file mode 100644
index 0000000..7546e80
--- /dev/null
+++ b/library/vendor/dompdf/README.md
@@ -0,0 +1,232 @@
+Dompdf
+======
+
+[![Build Status](https://github.com/dompdf/dompdf/actions/workflows/test.yml/badge.svg)](https://github.com/dompdf/dompdf/actions/workflows/test.yml)
+[![Latest Release](https://poser.pugx.org/dompdf/dompdf/v/stable.png)](https://packagist.org/packages/dompdf/dompdf)
+[![Total Downloads](https://poser.pugx.org/dompdf/dompdf/downloads.png)](https://packagist.org/packages/dompdf/dompdf)
+[![License](https://poser.pugx.org/dompdf/dompdf/license.png)](https://packagist.org/packages/dompdf/dompdf)
+
+**Dompdf is an HTML to PDF converter**
+
+At its heart, dompdf is (mostly) a [CSS 2.1](http://www.w3.org/TR/CSS2/) compliant
+HTML layout and rendering engine written in PHP. It is a style-driven renderer:
+it will download and read external stylesheets, inline style tags, and the style
+attributes of individual HTML elements. It also supports most presentational
+HTML attributes.
+
+*This document applies to the latest stable code which may not reflect the current
+release. For released code please
+[navigate to the appropriate tag](https://github.com/dompdf/dompdf/tags).*
+
+----
+
+**Check out the [demo](http://eclecticgeek.com/dompdf/debug.php) and ask any
+question on [StackOverflow](https://stackoverflow.com/questions/tagged/dompdf) or
+in [Discussions](https://github.com/dompdf/dompdf/discussions).**
+
+Follow us on [![Twitter](http://twitter-badges.s3.amazonaws.com/twitter-a.png)](http://www.twitter.com/dompdf).
+
+---
+
+
+
+## Features
+
+ * Handles most CSS 2.1 and a few CSS3 properties, including @import, @media &
+ @page rules
+ * Supports most presentational HTML 4.0 attributes
+ * Supports external stylesheets, either local or through http/ftp (via
+ fopen-wrappers)
+ * Supports complex tables, including row & column spans, separate & collapsed
+ border models, individual cell styling
+ * Image support (gif, png (8, 24 and 32 bit with alpha channel), bmp & jpeg)
+ * No dependencies on external PDF libraries, thanks to the R&OS PDF class
+ * Inline PHP support
+ * Basic SVG support (see "Limitations" below)
+
+## Requirements
+
+ * PHP version 7.1 or higher
+ * DOM extension
+ * MBString extension
+ * php-font-lib
+ * php-svg-lib
+
+Note that some required dependencies may have further dependencies
+(notably php-svg-lib requires sabberworm/php-css-parser).
+
+### Recommendations
+
+ * OPcache (OPcache, XCache, APC, etc.): improves performance
+ * GD (for image processing)
+ * IMagick or GMagick extension: improves image processing performance
+
+Visit the wiki for more information:
+https://github.com/dompdf/dompdf/wiki/Requirements
+
+## About Fonts & Character Encoding
+
+PDF documents internally support the following fonts: Helvetica, Times-Roman,
+Courier, Zapf-Dingbats, & Symbol. These fonts only support Windows ANSI
+encoding. In order for a PDF to display characters that are not available in
+Windows ANSI, you must supply an external font. Dompdf will embed any referenced
+font in the PDF so long as it has been pre-loaded or is accessible to dompdf and
+reference in CSS @font-face rules. See the
+[font overview](https://github.com/dompdf/dompdf/wiki/About-Fonts-and-Character-Encoding)
+for more information on how to use fonts.
+
+The [DejaVu TrueType fonts](https://dejavu-fonts.github.io/) have been pre-installed
+to give dompdf decent Unicode character coverage by default. To use the DejaVu
+fonts reference the font in your stylesheet, e.g. `body { font-family: DejaVu
+Sans; }` (for DejaVu Sans). The following DejaVu 2.34 fonts are available:
+DejaVu Sans, DejaVu Serif, and DejaVu Sans Mono.
+
+## Easy Installation
+
+### Install with composer
+
+To install with [Composer](https://getcomposer.org/), simply require the
+latest version of this package.
+
+```bash
+composer require dompdf/dompdf
+```
+
+Make sure that the autoload file from Composer is loaded.
+
+```php
+// somewhere early in your project's loading, require the Composer autoloader
+// see: http://getcomposer.org/doc/00-intro.md
+require 'vendor/autoload.php';
+
+```
+
+### Download and install
+
+Download a packaged archive of dompdf and extract it into the
+directory where dompdf will reside
+
+ * You can download stable copies of dompdf from
+ https://github.com/dompdf/dompdf/releases
+ * Or download a nightly (the latest, unreleased code) from
+ http://eclecticgeek.com/dompdf
+
+Use the packaged release autoloader to load dompdf, libraries,
+and helper functions in your PHP:
+
+```php
+// include autoloader
+require_once 'dompdf/autoload.inc.php';
+```
+
+Note: packaged releases are named according using semantic
+versioning (_dompdf_MAJOR-MINOR-PATCH.zip_). So the 1.0.0
+release would be dompdf_1-0-0.zip. This is the only download
+that includes the autoloader for Dompdf and all its dependencies.
+
+### Install with git
+
+From the command line, switch to the directory where dompdf will
+reside and run the following commands:
+
+```sh
+git clone https://github.com/dompdf/dompdf.git
+cd dompdf/lib
+
+git clone https://github.com/PhenX/php-font-lib.git php-font-lib
+cd php-font-lib
+git checkout 0.5.1
+cd ..
+
+git clone https://github.com/PhenX/php-svg-lib.git php-svg-lib
+cd php-svg-lib
+git checkout v0.3.2
+cd ..
+
+git clone https://github.com/sabberworm/PHP-CSS-Parser.git php-css-parser
+cd php-css-parser
+git checkout 8.1.0
+```
+
+Require dompdf and it's dependencies in your PHP.
+For details see the [autoloader in the utils project](https://github.com/dompdf/utils/blob/master/autoload.inc.php).
+
+## Quick Start
+
+Just pass your HTML in to dompdf and stream the output:
+
+```php
+// reference the Dompdf namespace
+use Dompdf\Dompdf;
+
+// instantiate and use the dompdf class
+$dompdf = new Dompdf();
+$dompdf->loadHtml('hello world');
+
+// (Optional) Setup the paper size and orientation
+$dompdf->setPaper('A4', 'landscape');
+
+// Render the HTML as PDF
+$dompdf->render();
+
+// Output the generated PDF to Browser
+$dompdf->stream();
+```
+
+### Setting Options
+
+Set options during dompdf instantiation:
+
+```php
+use Dompdf\Dompdf;
+use Dompdf\Options;
+
+$options = new Options();
+$options->set('defaultFont', 'Courier');
+$dompdf = new Dompdf($options);
+```
+
+or at run time
+
+```php
+use Dompdf\Dompdf;
+
+$dompdf = new Dompdf();
+$options = $dompdf->getOptions();
+$options->setDefaultFont('Courier');
+$dompdf->setOptions($options);
+```
+
+See [Dompdf\Options](src/Options.php) for a list of available options.
+
+### Resource Reference Requirements
+
+In order to protect potentially sensitive information Dompdf imposes
+restrictions on files referenced from the local file system or the web.
+
+Files accessed through web-based protocols have the following requirements:
+ * The Dompdf option "isRemoteEnabled" must be set to "true"
+ * PHP must either have the curl extension enabled or the
+ allow_url_fopen setting set to true
+
+Files accessed through the local file system have the following requirement:
+ * The file must fall within the path(s) specified for the Dompdf "chroot" option
+
+## Limitations (Known Issues)
+
+ * Table cells are not pageable, meaning a table row must fit on a single page.
+ * Elements are rendered on the active page when they are parsed.
+ * Embedding "raw" SVG's (`<svg><path...></svg>`) isn't working yet, you need to
+ either link to an external SVG file, or use a DataURI like this:
+ ```php
+ $html = '<img src="data:image/svg+xml;base64,' . base64_encode($svg) . '" ...>';
+ ```
+ Watch https://github.com/dompdf/dompdf/issues/320 for progress
+ * Does not support CSS flexbox.
+ * Does not support CSS Grid.
+---
+
+[![Donate button](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](http://goo.gl/DSvWf)
+
+*If you find this project useful, please consider making a donation.
+Any funds donated will be used to help further development on this project.)*
diff --git a/library/vendor/dompdf/VERSION b/library/vendor/dompdf/VERSION
new file mode 100644
index 0000000..38f77a6
--- /dev/null
+++ b/library/vendor/dompdf/VERSION
@@ -0,0 +1 @@
+2.0.1
diff --git a/library/vendor/dompdf/autoload.inc.php b/library/vendor/dompdf/autoload.inc.php
new file mode 100644
index 0000000..dc542ab
--- /dev/null
+++ b/library/vendor/dompdf/autoload.inc.php
@@ -0,0 +1 @@
+<?php require (__DIR__ . '/vendor/autoload.php'); \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/autoload.php b/library/vendor/dompdf/vendor/autoload.php
new file mode 100644
index 0000000..77629e9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit47dfdbccdbe2c66869f4658d0723ed1a::getLoader();
diff --git a/library/vendor/dompdf/vendor/composer/ClassLoader.php b/library/vendor/dompdf/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000..afef3fa
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/ClassLoader.php
@@ -0,0 +1,572 @@
+<?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 https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var ?string */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array[]
+ * @psalm-var array<string, array<string, int>>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array<string, array<int, string>>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array<string, string>
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * @var array[]
+ * @psalm-var array<string, array<string, string[]>>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var array[]
+ * @psalm-var array<string, string>
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var string[]
+ * @psalm-var array<string, string>
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var bool[]
+ * @psalm-var array<string, bool>
+ */
+ private $missingClasses = array();
+
+ /** @var ?string */
+ private $apcuPrefix;
+
+ /**
+ * @var self[]
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param ?string $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array<string, array<int, string>>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array<string, string>
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array<string, string>
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return string[] Array of classname => path
+ * @psalm-return array<string, string>
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param string[] $classMap Class to filename map
+ * @psalm-param array<string, string> $classMap
+ *
+ * @return void
+ */
+ 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 string[]|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ 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 string[]|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ 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 string[]|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ 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 string[]|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ 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
+ *
+ * @return void
+ */
+ 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
+ *
+ * @return void
+ */
+ 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
+ *
+ * @return void
+ */
+ 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
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
+ *
+ * @return self[]
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ 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.
+ *
+ * @param string $file
+ * @return void
+ * @private
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/library/vendor/dompdf/vendor/composer/InstalledVersions.php b/library/vendor/dompdf/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..d50e0c9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/InstalledVersions.php
@@ -0,0 +1,350 @@
+<?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;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list<string>
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list<string>
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints($constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = require __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+ $installed[] = self::$installed;
+
+ return $installed;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/composer/LICENSE b/library/vendor/dompdf/vendor/composer/LICENSE
new file mode 100644
index 0000000..f27399a
--- /dev/null
+++ b/library/vendor/dompdf/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/library/vendor/dompdf/vendor/composer/autoload_classmap.php b/library/vendor/dompdf/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..b708d77
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/autoload_classmap.php
@@ -0,0 +1,11 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+ 'Dompdf\\Cpdf' => $vendorDir . '/dompdf/dompdf/lib/Cpdf.php',
+);
diff --git a/library/vendor/dompdf/vendor/composer/autoload_namespaces.php b/library/vendor/dompdf/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..b7fc012
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);
diff --git a/library/vendor/dompdf/vendor/composer/autoload_psr4.php b/library/vendor/dompdf/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000..bd7244b
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/autoload_psr4.php
@@ -0,0 +1,14 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Svg\\' => array($vendorDir . '/phenx/php-svg-lib/src/Svg'),
+ 'Sabberworm\\CSS\\' => array($vendorDir . '/sabberworm/php-css-parser/src'),
+ 'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
+ 'FontLib\\' => array($vendorDir . '/phenx/php-font-lib/src/FontLib'),
+ 'Dompdf\\' => array($vendorDir . '/dompdf/dompdf/src'),
+);
diff --git a/library/vendor/dompdf/vendor/composer/autoload_real.php b/library/vendor/dompdf/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..49d5ac3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/autoload_real.php
@@ -0,0 +1,57 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit47dfdbccdbe2c66869f4658d0723ed1a
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ /**
+ * @return \Composer\Autoload\ClassLoader
+ */
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ require __DIR__ . '/platform_check.php';
+
+ spl_autoload_register(array('ComposerAutoloaderInit47dfdbccdbe2c66869f4658d0723ed1a', 'loadClassLoader'), true, true);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
+ spl_autoload_unregister(array('ComposerAutoloaderInit47dfdbccdbe2c66869f4658d0723ed1a', 'loadClassLoader'));
+
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInit47dfdbccdbe2c66869f4658d0723ed1a::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);
+
+ return $loader;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/composer/autoload_static.php b/library/vendor/dompdf/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..2c05e6c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/autoload_static.php
@@ -0,0 +1,66 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit47dfdbccdbe2c66869f4658d0723ed1a
+{
+ public static $prefixLengthsPsr4 = array (
+ 'S' =>
+ array (
+ 'Svg\\' => 4,
+ 'Sabberworm\\CSS\\' => 15,
+ ),
+ 'M' =>
+ array (
+ 'Masterminds\\' => 12,
+ ),
+ 'F' =>
+ array (
+ 'FontLib\\' => 8,
+ ),
+ 'D' =>
+ array (
+ 'Dompdf\\' => 7,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Svg\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/phenx/php-svg-lib/src/Svg',
+ ),
+ 'Sabberworm\\CSS\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/sabberworm/php-css-parser/src',
+ ),
+ 'Masterminds\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/masterminds/html5/src',
+ ),
+ 'FontLib\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/phenx/php-font-lib/src/FontLib',
+ ),
+ 'Dompdf\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/dompdf/dompdf/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'Dompdf\\Cpdf' => __DIR__ . '/..' . '/dompdf/dompdf/lib/Cpdf.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit47dfdbccdbe2c66869f4658d0723ed1a::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit47dfdbccdbe2c66869f4658d0723ed1a::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInit47dfdbccdbe2c66869f4658d0723ed1a::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/composer/installed.json b/library/vendor/dompdf/vendor/composer/installed.json
new file mode 100644
index 0000000..0acb559
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/installed.json
@@ -0,0 +1,295 @@
+{
+ "packages": [
+ {
+ "name": "dompdf/dompdf",
+ "version": "v2.0.1",
+ "version_normalized": "2.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dompdf/dompdf.git",
+ "reference": "c5310df0e22c758c85ea5288175fc6cd777bc085"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/c5310df0e22c758c85ea5288175fc6cd777bc085",
+ "reference": "c5310df0e22c758c85ea5288175fc6cd777bc085",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "masterminds/html5": "^2.0",
+ "phenx/php-font-lib": ">=0.5.4 <1.0.0",
+ "phenx/php-svg-lib": ">=0.3.3 <1.0.0",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "ext-zip": "*",
+ "mockery/mockery": "^1.3",
+ "phpunit/phpunit": "^7.5 || ^8 || ^9",
+ "squizlabs/php_codesniffer": "^3.5"
+ },
+ "suggest": {
+ "ext-gd": "Needed to process images",
+ "ext-gmagick": "Improves image processing performance",
+ "ext-imagick": "Improves image processing performance",
+ "ext-zlib": "Needed for pdf stream compression"
+ },
+ "time": "2022-09-22T13:43:41+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Dompdf\\": "src/"
+ },
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-2.1"
+ ],
+ "authors": [
+ {
+ "name": "The Dompdf Community",
+ "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
+ }
+ ],
+ "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
+ "homepage": "https://github.com/dompdf/dompdf",
+ "support": {
+ "issues": "https://github.com/dompdf/dompdf/issues",
+ "source": "https://github.com/dompdf/dompdf/tree/v2.0.1"
+ },
+ "install-path": "../dompdf/dompdf"
+ },
+ {
+ "name": "masterminds/html5",
+ "version": "2.7.6",
+ "version_normalized": "2.7.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Masterminds/html5-php.git",
+ "reference": "897eb517a343a2281f11bc5556d6548db7d93947"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947",
+ "reference": "897eb517a343a2281f11bc5556d6548db7d93947",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
+ },
+ "time": "2022-08-18T16:18:26+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Masterminds\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ }
+ ],
+ "description": "An HTML5 parser and serializer.",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "keywords": [
+ "HTML5",
+ "dom",
+ "html",
+ "parser",
+ "querypath",
+ "serializer",
+ "xml"
+ ],
+ "support": {
+ "issues": "https://github.com/Masterminds/html5-php/issues",
+ "source": "https://github.com/Masterminds/html5-php/tree/2.7.6"
+ },
+ "install-path": "../masterminds/html5"
+ },
+ {
+ "name": "phenx/php-font-lib",
+ "version": "0.5.4",
+ "version_normalized": "0.5.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dompdf/php-font-lib.git",
+ "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/dd448ad1ce34c63d09baccd05415e361300c35b4",
+ "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^3 || ^4 || ^5"
+ },
+ "time": "2021-12-17T19:44:54+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "FontLib\\": "src/FontLib"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Ménager",
+ "email": "fabien.menager@gmail.com"
+ }
+ ],
+ "description": "A library to read, parse, export and make subsets of different types of font files.",
+ "homepage": "https://github.com/PhenX/php-font-lib",
+ "support": {
+ "issues": "https://github.com/dompdf/php-font-lib/issues",
+ "source": "https://github.com/dompdf/php-font-lib/tree/0.5.4"
+ },
+ "install-path": "../phenx/php-font-lib"
+ },
+ {
+ "name": "phenx/php-svg-lib",
+ "version": "0.5.0",
+ "version_normalized": "0.5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dompdf/php-svg-lib.git",
+ "reference": "76876c6cf3080bcb6f249d7d59705108166a6685"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685",
+ "reference": "76876c6cf3080bcb6f249d7d59705108166a6685",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": "^7.1 || ^8.0",
+ "sabberworm/php-css-parser": "^8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
+ },
+ "time": "2022-09-06T12:16:56+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Svg\\": "src/Svg"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Ménager",
+ "email": "fabien.menager@gmail.com"
+ }
+ ],
+ "description": "A library to read, parse and export to PDF SVG files.",
+ "homepage": "https://github.com/PhenX/php-svg-lib",
+ "support": {
+ "issues": "https://github.com/dompdf/php-svg-lib/issues",
+ "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0"
+ },
+ "install-path": "../phenx/php-svg-lib"
+ },
+ {
+ "name": "sabberworm/php-css-parser",
+ "version": "8.4.0",
+ "version_normalized": "8.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sabberworm/PHP-CSS-Parser.git",
+ "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30",
+ "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "php": ">=5.6.20"
+ },
+ "require-dev": {
+ "codacy/coverage": "^1.4",
+ "phpunit/phpunit": "^4.8.36"
+ },
+ "suggest": {
+ "ext-mbstring": "for parsing UTF-8 CSS"
+ },
+ "time": "2021-12-11T13:40:54+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Sabberworm\\CSS\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Raphael Schweikert"
+ }
+ ],
+ "description": "Parser for CSS Files written in PHP",
+ "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
+ "keywords": [
+ "css",
+ "parser",
+ "stylesheet"
+ ],
+ "support": {
+ "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues",
+ "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0"
+ },
+ "install-path": "../sabberworm/php-css-parser"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/library/vendor/dompdf/vendor/composer/installed.php b/library/vendor/dompdf/vendor/composer/installed.php
new file mode 100644
index 0000000..cdf63de
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/installed.php
@@ -0,0 +1,68 @@
+<?php return array(
+ 'root' => array(
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'type' => 'project',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'reference' => NULL,
+ 'name' => '__root__',
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'type' => 'project',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'reference' => NULL,
+ 'dev_requirement' => false,
+ ),
+ 'dompdf/dompdf' => array(
+ 'pretty_version' => 'v2.0.1',
+ 'version' => '2.0.1.0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../dompdf/dompdf',
+ 'aliases' => array(),
+ 'reference' => 'c5310df0e22c758c85ea5288175fc6cd777bc085',
+ 'dev_requirement' => false,
+ ),
+ 'masterminds/html5' => array(
+ 'pretty_version' => '2.7.6',
+ 'version' => '2.7.6.0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../masterminds/html5',
+ 'aliases' => array(),
+ 'reference' => '897eb517a343a2281f11bc5556d6548db7d93947',
+ 'dev_requirement' => false,
+ ),
+ 'phenx/php-font-lib' => array(
+ 'pretty_version' => '0.5.4',
+ 'version' => '0.5.4.0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../phenx/php-font-lib',
+ 'aliases' => array(),
+ 'reference' => 'dd448ad1ce34c63d09baccd05415e361300c35b4',
+ 'dev_requirement' => false,
+ ),
+ 'phenx/php-svg-lib' => array(
+ 'pretty_version' => '0.5.0',
+ 'version' => '0.5.0.0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../phenx/php-svg-lib',
+ 'aliases' => array(),
+ 'reference' => '76876c6cf3080bcb6f249d7d59705108166a6685',
+ 'dev_requirement' => false,
+ ),
+ 'sabberworm/php-css-parser' => array(
+ 'pretty_version' => '8.4.0',
+ 'version' => '8.4.0.0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../sabberworm/php-css-parser',
+ 'aliases' => array(),
+ 'reference' => 'e41d2140031d533348b2192a83f02d8dd8a71d30',
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/library/vendor/dompdf/vendor/composer/platform_check.php b/library/vendor/dompdf/vendor/composer/platform_check.php
new file mode 100644
index 0000000..6d3407d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/composer/platform_check.php
@@ -0,0 +1,26 @@
+<?php
+
+// platform_check.php @generated by Composer
+
+$issues = array();
+
+if (!(PHP_VERSION_ID >= 70100)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ trigger_error(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
+ E_USER_ERROR
+ );
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/AUTHORS.md b/library/vendor/dompdf/vendor/dompdf/dompdf/AUTHORS.md
new file mode 100644
index 0000000..6861479
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/AUTHORS.md
@@ -0,0 +1,24 @@
+Dompdf was designed and developed by Benj Carson.
+
+### Current Team
+
+* **Brian Sweeney** (maintainer)
+* **Till Berger**
+
+### Alumni
+
+* **Benj Carson** (creator)
+* **Fabien Ménager**
+* **Simon Berger**
+* **Orion Richardson**
+
+### Contributors
+* **Gabriel Bull**
+* **Barry vd. Heuvel**
+* **Ryan H. Masten**
+* **Helmut Tischer**
+* [and many more...](https://github.com/dompdf/dompdf/graphs/contributors)
+
+### Thanks
+
+Dompdf would not have been possible without strong community support.
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/LICENSE.LGPL b/library/vendor/dompdf/vendor/dompdf/dompdf/LICENSE.LGPL
new file mode 100644
index 0000000..6ef5de8
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/LICENSE.LGPL
@@ -0,0 +1,456 @@
+ 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. \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/README.md b/library/vendor/dompdf/vendor/dompdf/dompdf/README.md
new file mode 100644
index 0000000..7546e80
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/README.md
@@ -0,0 +1,232 @@
+Dompdf
+======
+
+[![Build Status](https://github.com/dompdf/dompdf/actions/workflows/test.yml/badge.svg)](https://github.com/dompdf/dompdf/actions/workflows/test.yml)
+[![Latest Release](https://poser.pugx.org/dompdf/dompdf/v/stable.png)](https://packagist.org/packages/dompdf/dompdf)
+[![Total Downloads](https://poser.pugx.org/dompdf/dompdf/downloads.png)](https://packagist.org/packages/dompdf/dompdf)
+[![License](https://poser.pugx.org/dompdf/dompdf/license.png)](https://packagist.org/packages/dompdf/dompdf)
+
+**Dompdf is an HTML to PDF converter**
+
+At its heart, dompdf is (mostly) a [CSS 2.1](http://www.w3.org/TR/CSS2/) compliant
+HTML layout and rendering engine written in PHP. It is a style-driven renderer:
+it will download and read external stylesheets, inline style tags, and the style
+attributes of individual HTML elements. It also supports most presentational
+HTML attributes.
+
+*This document applies to the latest stable code which may not reflect the current
+release. For released code please
+[navigate to the appropriate tag](https://github.com/dompdf/dompdf/tags).*
+
+----
+
+**Check out the [demo](http://eclecticgeek.com/dompdf/debug.php) and ask any
+question on [StackOverflow](https://stackoverflow.com/questions/tagged/dompdf) or
+in [Discussions](https://github.com/dompdf/dompdf/discussions).**
+
+Follow us on [![Twitter](http://twitter-badges.s3.amazonaws.com/twitter-a.png)](http://www.twitter.com/dompdf).
+
+---
+
+
+
+## Features
+
+ * Handles most CSS 2.1 and a few CSS3 properties, including @import, @media &
+ @page rules
+ * Supports most presentational HTML 4.0 attributes
+ * Supports external stylesheets, either local or through http/ftp (via
+ fopen-wrappers)
+ * Supports complex tables, including row & column spans, separate & collapsed
+ border models, individual cell styling
+ * Image support (gif, png (8, 24 and 32 bit with alpha channel), bmp & jpeg)
+ * No dependencies on external PDF libraries, thanks to the R&OS PDF class
+ * Inline PHP support
+ * Basic SVG support (see "Limitations" below)
+
+## Requirements
+
+ * PHP version 7.1 or higher
+ * DOM extension
+ * MBString extension
+ * php-font-lib
+ * php-svg-lib
+
+Note that some required dependencies may have further dependencies
+(notably php-svg-lib requires sabberworm/php-css-parser).
+
+### Recommendations
+
+ * OPcache (OPcache, XCache, APC, etc.): improves performance
+ * GD (for image processing)
+ * IMagick or GMagick extension: improves image processing performance
+
+Visit the wiki for more information:
+https://github.com/dompdf/dompdf/wiki/Requirements
+
+## About Fonts & Character Encoding
+
+PDF documents internally support the following fonts: Helvetica, Times-Roman,
+Courier, Zapf-Dingbats, & Symbol. These fonts only support Windows ANSI
+encoding. In order for a PDF to display characters that are not available in
+Windows ANSI, you must supply an external font. Dompdf will embed any referenced
+font in the PDF so long as it has been pre-loaded or is accessible to dompdf and
+reference in CSS @font-face rules. See the
+[font overview](https://github.com/dompdf/dompdf/wiki/About-Fonts-and-Character-Encoding)
+for more information on how to use fonts.
+
+The [DejaVu TrueType fonts](https://dejavu-fonts.github.io/) have been pre-installed
+to give dompdf decent Unicode character coverage by default. To use the DejaVu
+fonts reference the font in your stylesheet, e.g. `body { font-family: DejaVu
+Sans; }` (for DejaVu Sans). The following DejaVu 2.34 fonts are available:
+DejaVu Sans, DejaVu Serif, and DejaVu Sans Mono.
+
+## Easy Installation
+
+### Install with composer
+
+To install with [Composer](https://getcomposer.org/), simply require the
+latest version of this package.
+
+```bash
+composer require dompdf/dompdf
+```
+
+Make sure that the autoload file from Composer is loaded.
+
+```php
+// somewhere early in your project's loading, require the Composer autoloader
+// see: http://getcomposer.org/doc/00-intro.md
+require 'vendor/autoload.php';
+
+```
+
+### Download and install
+
+Download a packaged archive of dompdf and extract it into the
+directory where dompdf will reside
+
+ * You can download stable copies of dompdf from
+ https://github.com/dompdf/dompdf/releases
+ * Or download a nightly (the latest, unreleased code) from
+ http://eclecticgeek.com/dompdf
+
+Use the packaged release autoloader to load dompdf, libraries,
+and helper functions in your PHP:
+
+```php
+// include autoloader
+require_once 'dompdf/autoload.inc.php';
+```
+
+Note: packaged releases are named according using semantic
+versioning (_dompdf_MAJOR-MINOR-PATCH.zip_). So the 1.0.0
+release would be dompdf_1-0-0.zip. This is the only download
+that includes the autoloader for Dompdf and all its dependencies.
+
+### Install with git
+
+From the command line, switch to the directory where dompdf will
+reside and run the following commands:
+
+```sh
+git clone https://github.com/dompdf/dompdf.git
+cd dompdf/lib
+
+git clone https://github.com/PhenX/php-font-lib.git php-font-lib
+cd php-font-lib
+git checkout 0.5.1
+cd ..
+
+git clone https://github.com/PhenX/php-svg-lib.git php-svg-lib
+cd php-svg-lib
+git checkout v0.3.2
+cd ..
+
+git clone https://github.com/sabberworm/PHP-CSS-Parser.git php-css-parser
+cd php-css-parser
+git checkout 8.1.0
+```
+
+Require dompdf and it's dependencies in your PHP.
+For details see the [autoloader in the utils project](https://github.com/dompdf/utils/blob/master/autoload.inc.php).
+
+## Quick Start
+
+Just pass your HTML in to dompdf and stream the output:
+
+```php
+// reference the Dompdf namespace
+use Dompdf\Dompdf;
+
+// instantiate and use the dompdf class
+$dompdf = new Dompdf();
+$dompdf->loadHtml('hello world');
+
+// (Optional) Setup the paper size and orientation
+$dompdf->setPaper('A4', 'landscape');
+
+// Render the HTML as PDF
+$dompdf->render();
+
+// Output the generated PDF to Browser
+$dompdf->stream();
+```
+
+### Setting Options
+
+Set options during dompdf instantiation:
+
+```php
+use Dompdf\Dompdf;
+use Dompdf\Options;
+
+$options = new Options();
+$options->set('defaultFont', 'Courier');
+$dompdf = new Dompdf($options);
+```
+
+or at run time
+
+```php
+use Dompdf\Dompdf;
+
+$dompdf = new Dompdf();
+$options = $dompdf->getOptions();
+$options->setDefaultFont('Courier');
+$dompdf->setOptions($options);
+```
+
+See [Dompdf\Options](src/Options.php) for a list of available options.
+
+### Resource Reference Requirements
+
+In order to protect potentially sensitive information Dompdf imposes
+restrictions on files referenced from the local file system or the web.
+
+Files accessed through web-based protocols have the following requirements:
+ * The Dompdf option "isRemoteEnabled" must be set to "true"
+ * PHP must either have the curl extension enabled or the
+ allow_url_fopen setting set to true
+
+Files accessed through the local file system have the following requirement:
+ * The file must fall within the path(s) specified for the Dompdf "chroot" option
+
+## Limitations (Known Issues)
+
+ * Table cells are not pageable, meaning a table row must fit on a single page.
+ * Elements are rendered on the active page when they are parsed.
+ * Embedding "raw" SVG's (`<svg><path...></svg>`) isn't working yet, you need to
+ either link to an external SVG file, or use a DataURI like this:
+ ```php
+ $html = '<img src="data:image/svg+xml;base64,' . base64_encode($svg) . '" ...>';
+ ```
+ Watch https://github.com/dompdf/dompdf/issues/320 for progress
+ * Does not support CSS flexbox.
+ * Does not support CSS Grid.
+---
+
+[![Donate button](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](http://goo.gl/DSvWf)
+
+*If you find this project useful, please consider making a donation.
+Any funds donated will be used to help further development on this project.)*
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/VERSION b/library/vendor/dompdf/vendor/dompdf/dompdf/VERSION
new file mode 100644
index 0000000..38f77a6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/VERSION
@@ -0,0 +1 @@
+2.0.1
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/composer.json b/library/vendor/dompdf/vendor/dompdf/dompdf/composer.json
new file mode 100644
index 0000000..268a81e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/composer.json
@@ -0,0 +1,47 @@
+{
+ "name": "dompdf/dompdf",
+ "type": "library",
+ "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
+ "homepage": "https://github.com/dompdf/dompdf",
+ "license": "LGPL-2.1",
+ "authors": [
+ {
+ "name": "The Dompdf Community",
+ "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Dompdf\\": "src/"
+ },
+ "classmap": [
+ "lib/"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Dompdf\\Tests\\": "tests/"
+ }
+ },
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "masterminds/html5": "^2.0",
+ "phenx/php-font-lib": ">=0.5.4 <1.0.0",
+ "phenx/php-svg-lib": ">=0.3.3 <1.0.0"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "ext-zip": "*",
+ "phpunit/phpunit": "^7.5 || ^8 || ^9",
+ "squizlabs/php_codesniffer": "^3.5",
+ "mockery/mockery": "^1.3"
+ },
+ "suggest": {
+ "ext-gd": "Needed to process images",
+ "ext-imagick": "Improves image processing performance",
+ "ext-gmagick": "Improves image processing performance",
+ "ext-zlib": "Needed for pdf stream compression"
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/Cpdf.php b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/Cpdf.php
new file mode 100644
index 0000000..27bfa29
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/Cpdf.php
@@ -0,0 +1,6501 @@
+<?php
+/**
+ * A PHP class to provide the basic functionality to create a pdf document without
+ * any requirement for additional modules.
+ *
+ * @author Wayne Munro
+ * @license http://creativecommons.org/licenses/publicdomain/ Public Domain
+ * @package Cpdf
+ */
+namespace Dompdf;
+
+use FontLib\Exception\FontNotFoundException;
+use FontLib\Font;
+use FontLib\BinaryStream;
+
+class Cpdf
+{
+ const PDF_VERSION = '1.7';
+
+ const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
+ const ACROFORM_SIG_APPENDONLY = 0x0002;
+
+ const ACROFORM_FIELD_BUTTON = 'Btn';
+ const ACROFORM_FIELD_TEXT = 'Tx';
+ const ACROFORM_FIELD_CHOICE = 'Ch';
+ const ACROFORM_FIELD_SIG = 'Sig';
+
+ const ACROFORM_FIELD_READONLY = 0x0001;
+ const ACROFORM_FIELD_REQUIRED = 0x0002;
+
+ const ACROFORM_FIELD_TEXT_MULTILINE = 0x1000;
+ const ACROFORM_FIELD_TEXT_PASSWORD = 0x2000;
+ const ACROFORM_FIELD_TEXT_RICHTEXT = 0x10000;
+
+ const ACROFORM_FIELD_CHOICE_COMBO = 0x20000;
+ const ACROFORM_FIELD_CHOICE_EDIT = 0x40000;
+ const ACROFORM_FIELD_CHOICE_SORT = 0x80000;
+ const ACROFORM_FIELD_CHOICE_MULTISELECT = 0x200000;
+
+ const XOBJECT_SUBTYPE_FORM = 'Form';
+
+ /**
+ * @var integer The current number of pdf objects in the document
+ */
+ public $numObj = 0;
+
+ /**
+ * @var array This array contains all of the pdf objects, ready for final assembly
+ */
+ public $objects = [];
+
+ /**
+ * @var integer The objectId (number within the objects array) of the document catalog
+ */
+ public $catalogId;
+
+ /**
+ * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
+ */
+ protected $indirectReferenceId = 0;
+
+ /**
+ * @var integer The objectId (number within the objects array)
+ */
+ protected $embeddedFilesId = 0;
+
+ /**
+ * AcroForm objectId
+ *
+ * @var integer
+ */
+ public $acroFormId;
+
+ /**
+ * @var int
+ */
+ public $signatureMaxLen = 5000;
+
+ /**
+ * @var array Array carrying information about the fonts that the system currently knows about
+ * Used to ensure that a font is not loaded twice, among other things
+ */
+ public $fonts = [];
+
+ /**
+ * @var string The default font metrics file to use if no other font has been loaded.
+ * The path to the directory containing the font metrics should be included
+ */
+ public $defaultFont = './fonts/Helvetica.afm';
+
+ /**
+ * @string A record of the current font
+ */
+ public $currentFont = '';
+
+ /**
+ * @var string The current base font
+ */
+ public $currentBaseFont = '';
+
+ /**
+ * @var integer The number of the current font within the font array
+ */
+ public $currentFontNum = 0;
+
+ /**
+ * @var integer
+ */
+ public $currentNode;
+
+ /**
+ * @var integer Object number of the current page
+ */
+ public $currentPage;
+
+ /**
+ * @var integer Object number of the currently active contents block
+ */
+ public $currentContents;
+
+ /**
+ * @var integer Number of fonts within the system
+ */
+ public $numFonts = 0;
+
+ /**
+ * @var integer Number of graphic state resources used
+ */
+ private $numStates = 0;
+
+ /**
+ * @var array Number of graphic state resources used
+ */
+ private $gstates = [];
+
+ /**
+ * @var array Current color for fill operations, defaults to inactive value,
+ * all three components should be between 0 and 1 inclusive when active
+ */
+ public $currentColor = null;
+
+ /**
+ * @var array Current color for stroke operations (lines etc.)
+ */
+ public $currentStrokeColor = null;
+
+ /**
+ * @var string Fill rule (nonzero or evenodd)
+ */
+ public $fillRule = "nonzero";
+
+ /**
+ * @var string Current style that lines are drawn in
+ */
+ public $currentLineStyle = '';
+
+ /**
+ * @var array Current line transparency (partial graphics state)
+ */
+ public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
+
+ /**
+ * array Current fill transparency (partial graphics state)
+ */
+ public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
+
+ /**
+ * @var array An array which is used to save the state of the document, mainly the colors and styles
+ * it is used to temporarily change to another state, then change back to what it was before
+ */
+ public $stateStack = [];
+
+ /**
+ * @var integer Number of elements within the state stack
+ */
+ public $nStateStack = 0;
+
+ /**
+ * @var integer Number of page objects within the document
+ */
+ public $numPages = 0;
+
+ /**
+ * @var array Object Id storage stack
+ */
+ public $stack = [];
+
+ /**
+ * @var integer Number of elements within the object Id storage stack
+ */
+ public $nStack = 0;
+
+ /**
+ * an array which contains information about the objects which are not firmly attached to pages
+ * these have been added with the addObject function
+ */
+ public $looseObjects = [];
+
+ /**
+ * array contains information about how the loose objects are to be added to the document
+ */
+ public $addLooseObjects = [];
+
+ /**
+ * @var integer The objectId of the information object for the document
+ * this contains authorship, title etc.
+ */
+ public $infoObject = 0;
+
+ /**
+ * @var integer Number of images being tracked within the document
+ */
+ public $numImages = 0;
+
+ /**
+ * @var array An array containing options about the document
+ * it defaults to turning on the compression of the objects
+ */
+ public $options = ['compression' => true];
+
+ /**
+ * @var integer The objectId of the first page of the document
+ */
+ public $firstPageId;
+
+ /**
+ * @var integer The object Id of the procset object
+ */
+ public $procsetObjectId;
+
+ /**
+ * @var array Store the information about the relationship between font families
+ * this used so that the code knows which font is the bold version of another font, etc.
+ * the value of this array is initialised in the constructor function.
+ */
+ public $fontFamilies = [];
+
+ /**
+ * @var string Folder for php serialized formats of font metrics files.
+ * If empty string, use same folder as original metrics files.
+ * This can be passed in from class creator.
+ * If this folder does not exist or is not writable, Cpdf will be **much** slower.
+ * Because of potential trouble with php safe mode, folder cannot be created at runtime.
+ */
+ public $fontcache = '';
+
+ /**
+ * @var integer The version of the font metrics cache file.
+ * This value must be manually incremented whenever the internal font data structure is modified.
+ */
+ public $fontcacheVersion = 6;
+
+ /**
+ * @var string Temporary folder.
+ * If empty string, will attempt system tmp folder.
+ * This can be passed in from class creator.
+ */
+ public $tmp = '';
+
+ /**
+ * @var string Track if the current font is bolded or italicised
+ */
+ public $currentTextState = '';
+
+ /**
+ * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
+ */
+ public $messages = '';
+
+ /**
+ * @var string The encryption array for the document encryption is stored here
+ */
+ public $arc4 = '';
+
+ /**
+ * @var integer The object Id of the encryption information
+ */
+ public $arc4_objnum = 0;
+
+ /**
+ * @var string The file identifier, used to uniquely identify a pdf document
+ */
+ public $fileIdentifier = '';
+
+ /**
+ * @var boolean A flag to say if a document is to be encrypted or not
+ */
+ public $encrypted = false;
+
+ /**
+ * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
+ */
+ public $encryptionKey = '';
+
+ /**
+ * @var array Array which forms a stack to keep track of nested callback functions
+ */
+ public $callback = [];
+
+ /**
+ * @var integer The number of callback functions in the callback array
+ */
+ public $nCallback = 0;
+
+ /**
+ * @var array Store label->id pairs for named destinations, these will be used to replace internal links
+ * done this way so that destinations can be defined after the location that links to them
+ */
+ public $destinations = [];
+
+ /**
+ * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
+ * publiciables within the class, so that the user can rollback at will (from each 'start' command)
+ * note that this includes the objects array, so these can be large.
+ */
+ public $checkpoint = '';
+
+ /**
+ * @var array Table of Image origin filenames and image labels which were already added with o_image().
+ * Allows to merge identical images
+ */
+ public $imagelist = [];
+
+ /**
+ * @var array Table of already added alpha and plain image files for transparent PNG images.
+ */
+ protected $imageAlphaList = [];
+
+ /**
+ * @var array List of temporary image files to be deleted after processing.
+ */
+ protected $imageCache = [];
+
+ /**
+ * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
+ */
+ public $isUnicode = false;
+
+ /**
+ * @var string the JavaScript code of the document
+ */
+ public $javascript = '';
+
+ /**
+ * @var boolean whether the compression is possible
+ */
+ protected $compressionReady = false;
+
+ /**
+ * @var array Current page size
+ */
+ protected $currentPageSize = ["width" => 0, "height" => 0];
+
+ /**
+ * @var array All the chars that will be required in the font subsets
+ */
+ protected $stringSubsets = [];
+
+ /**
+ * @var string The target internal encoding
+ */
+ protected static $targetEncoding = 'Windows-1252';
+
+ /**
+ * @var array
+ */
+ protected $byteRange = array();
+
+ /**
+ * @var array The list of the core fonts
+ */
+ protected static $coreFonts = [
+ 'courier',
+ 'courier-bold',
+ 'courier-oblique',
+ 'courier-boldoblique',
+ 'helvetica',
+ 'helvetica-bold',
+ 'helvetica-oblique',
+ 'helvetica-boldoblique',
+ 'times-roman',
+ 'times-bold',
+ 'times-italic',
+ 'times-bolditalic',
+ 'symbol',
+ 'zapfdingbats'
+ ];
+
+ /**
+ * Class constructor
+ * This will start a new document
+ *
+ * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
+ * @param boolean $isUnicode Whether text will be treated as Unicode or not.
+ * @param string $fontcache The font cache folder
+ * @param string $tmp The temporary folder
+ */
+ function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
+ {
+ $this->isUnicode = $isUnicode;
+ $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
+ $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
+ $this->newDocument($pageSize);
+
+ $this->compressionReady = function_exists('gzcompress');
+
+ if (in_array('Windows-1252', mb_list_encodings())) {
+ self::$targetEncoding = 'Windows-1252';
+ }
+
+ // also initialize the font families that are known about already
+ $this->setFontFamily('init');
+ }
+
+ public function __destruct()
+ {
+ foreach ($this->imageCache as $file) {
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ }
+
+ /**
+ * Document object methods (internal use only)
+ *
+ * There is about one object method for each type of object in the pdf document
+ * Each function has the same call list ($id,$action,$options).
+ * $id = the object ID of the object, or what it is to be if it is being created
+ * $action = a string specifying the action to be performed, though ALL must support:
+ * 'new' - create the object with the id $id
+ * 'out' - produce the output for the pdf object
+ * $options = optional, a string or array containing the various parameters for the object
+ *
+ * These, in conjunction with the output function are the ONLY way for output to be produced
+ * within the pdf 'file'.
+ */
+
+ /**
+ * Destination object, used to specify the location for the user to jump to, presently on opening
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_destination($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'destination', 'info' => []];
+ $tmp = '';
+ switch ($options['type']) {
+ case 'XYZ':
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 'FitR':
+ $tmp = ' ' . $options['p3'] . $tmp;
+ case 'FitH':
+ case 'FitV':
+ case 'FitBH':
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 'FitBV':
+ $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
+ case 'Fit':
+ case 'FitB':
+ $tmp = $options['type'] . $tmp;
+ $this->objects[$id]['info']['string'] = $tmp;
+ $this->objects[$id]['info']['page'] = $options['page'];
+ }
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+
+ $tmp = $o['info'];
+ $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * set the viewer preferences
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return string|null
+ */
+ protected function o_viewerPreferences($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
+ break;
+
+ case 'add':
+ $o = &$this->objects[$id];
+
+ foreach ($options as $k => $v) {
+ switch ($k) {
+ // Boolean keys
+ case 'HideToolbar':
+ case 'HideMenubar':
+ case 'HideWindowUI':
+ case 'FitWindow':
+ case 'CenterWindow':
+ case 'DisplayDocTitle':
+ case 'PickTrayByPDFSize':
+ $o['info'][$k] = (bool)$v;
+ break;
+
+ // Integer keys
+ case 'NumCopies':
+ $o['info'][$k] = (int)$v;
+ break;
+
+ // Name keys
+ case 'ViewArea':
+ case 'ViewClip':
+ case 'PrintClip':
+ case 'PrintArea':
+ $o['info'][$k] = (string)$v;
+ break;
+
+ // Named with limited valid values
+ case 'NonFullScreenPageMode':
+ if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ case 'Direction':
+ if (!in_array($v, ['L2R', 'R2L'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ case 'PrintScaling':
+ if (!in_array($v, ['None', 'AppDefault'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ case 'Duplex':
+ if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ // Integer array
+ case 'PrintPageRange':
+ // Cast to integer array
+ foreach ($v as $vK => $vV) {
+ $v[$vK] = (int)$vV;
+ }
+ $o['info'][$k] = array_values($v);
+ break;
+ }
+ }
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< ";
+
+ foreach ($o['info'] as $k => $v) {
+ if (is_string($v)) {
+ $v = '/' . $v;
+ } elseif (is_int($v)) {
+ $v = (string) $v;
+ } elseif (is_bool($v)) {
+ $v = ($v ? 'true' : 'false');
+ } elseif (is_array($v)) {
+ $v = '[' . implode(' ', $v) . ']';
+ }
+ $res .= "\n/$k $v";
+ }
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * define the document catalog, the overall controller for the document
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return string|null
+ */
+ protected function o_catalog($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'catalog', 'info' => []];
+ $this->catalogId = $id;
+ break;
+
+ case 'acroform':
+ case 'outlines':
+ case 'pages':
+ case 'openHere':
+ case 'names':
+ $o['info'][$action] = $options;
+ break;
+
+ case 'viewerPreferences':
+ if (!isset($o['info']['viewerPreferences'])) {
+ $this->numObj++;
+ $this->o_viewerPreferences($this->numObj, 'new');
+ $o['info']['viewerPreferences'] = $this->numObj;
+ }
+
+ $vp = $o['info']['viewerPreferences'];
+ $this->o_viewerPreferences($vp, 'add', $options);
+
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Catalog";
+
+ foreach ($o['info'] as $k => $v) {
+ switch ($k) {
+ case 'outlines':
+ $res .= "\n/Outlines $v 0 R";
+ break;
+
+ case 'pages':
+ $res .= "\n/Pages $v 0 R";
+ break;
+
+ case 'viewerPreferences':
+ $res .= "\n/ViewerPreferences $v 0 R";
+ break;
+
+ case 'openHere':
+ $res .= "\n/OpenAction $v 0 R";
+ break;
+
+ case 'names':
+ $res .= "\n/Names $v 0 R";
+ break;
+
+ case 'acroform':
+ $res .= "\n/AcroForm $v 0 R";
+ break;
+ }
+ }
+
+ $res .= " >>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * object which is a parent to the pages in the document
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_pages($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'pages', 'info' => []];
+ $this->o_catalog($this->catalogId, 'pages', $id);
+ break;
+
+ case 'page':
+ if (!is_array($options)) {
+ // then it will just be the id of the new page
+ $o['info']['pages'][] = $options;
+ } else {
+ // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
+ // and pos is either 'before' or 'after', saying where this page will fit.
+ if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
+ $i = array_search($options['rid'], $o['info']['pages']);
+ if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
+
+ // then there is a match
+ // make a space
+ switch ($options['pos']) {
+ case 'before':
+ $k = $i;
+ break;
+
+ case 'after':
+ $k = $i + 1;
+ break;
+
+ default:
+ $k = -1;
+ break;
+ }
+
+ if ($k >= 0) {
+ for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
+ $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
+ }
+
+ $o['info']['pages'][$k] = $options['id'];
+ }
+ }
+ }
+ }
+ break;
+
+ case 'procset':
+ $o['info']['procset'] = $options;
+ break;
+
+ case 'mediaBox':
+ $o['info']['mediaBox'] = $options;
+ // which should be an array of 4 numbers
+ $this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
+ break;
+
+ case 'font':
+ $o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
+ break;
+
+ case 'extGState':
+ $o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
+ break;
+
+ case 'xObject':
+ $o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
+ break;
+
+ case 'out':
+ if (count($o['info']['pages'])) {
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
+ foreach ($o['info']['pages'] as $v) {
+ $res .= "$v 0 R\n";
+ }
+
+ $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
+
+ if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
+ isset($o['info']['procset']) ||
+ (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
+ ) {
+ $res .= "\n/Resources <<";
+
+ if (isset($o['info']['procset'])) {
+ $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
+ }
+
+ if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
+ $res .= "\n/Font << ";
+ foreach ($o['info']['fonts'] as $finfo) {
+ $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+
+ if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
+ $res .= "\n/XObject << ";
+ foreach ($o['info']['xObjects'] as $finfo) {
+ $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+
+ if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
+ $res .= "\n/ExtGState << ";
+ foreach ($o['info']['extGStates'] as $gstate) {
+ $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+
+ $res .= "\n>>";
+ if (isset($o['info']['mediaBox'])) {
+ $tmp = $o['info']['mediaBox'];
+ $res .= "\n/MediaBox [" . sprintf(
+ '%.3F %.3F %.3F %.3F',
+ $tmp[0],
+ $tmp[1],
+ $tmp[2],
+ $tmp[3]
+ ) . ']';
+ }
+ }
+
+ $res .= "\n >>\nendobj";
+ } else {
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
+ }
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * define the outlines in the doc, empty for now
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_outlines($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
+ $this->o_catalog($this->catalogId, 'outlines', $id);
+ break;
+
+ case 'outline':
+ $o['info']['outlines'][] = $options;
+ break;
+
+ case 'out':
+ if (count($o['info']['outlines'])) {
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
+ foreach ($o['info']['outlines'] as $v) {
+ $res .= "$v 0 R ";
+ }
+
+ $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
+ } else {
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
+ }
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an object to hold the font description
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return string|null
+ * @throws FontNotFoundException
+ */
+ protected function o_font($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'font',
+ 'info' => [
+ 'name' => $options['name'],
+ 'fontFileName' => $options['fontFileName'],
+ 'SubType' => 'Type1',
+ 'isSubsetting' => $options['isSubsetting']
+ ]
+ ];
+ $fontNum = $this->numFonts;
+ $this->objects[$id]['info']['fontNum'] = $fontNum;
+
+ // deal with the encoding and the differences
+ if (isset($options['differences'])) {
+ // then we'll need an encoding dictionary
+ $this->numObj++;
+ $this->o_fontEncoding($this->numObj, 'new', $options);
+ $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
+ } else {
+ if (isset($options['encoding'])) {
+ // we can specify encoding here
+ switch ($options['encoding']) {
+ case 'WinAnsiEncoding':
+ case 'MacRomanEncoding':
+ case 'MacExpertEncoding':
+ $this->objects[$id]['info']['encoding'] = $options['encoding'];
+ break;
+
+ case 'none':
+ break;
+
+ default:
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
+ break;
+ }
+ } else {
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
+ }
+ }
+
+ if ($this->fonts[$options['fontFileName']]['isUnicode']) {
+ // For Unicode fonts, we need to incorporate font data into
+ // sub-sections that are linked from the primary font section.
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
+ // for more information.
+ //
+ // All of this code is adapted from the excellent changes made to
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
+
+ $toUnicodeId = ++$this->numObj;
+ $this->o_toUnicode($toUnicodeId, 'new');
+ $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
+
+ $cidFontId = ++$this->numObj;
+ $this->o_fontDescendentCID($cidFontId, 'new', $options);
+ $this->objects[$id]['info']['cidFont'] = $cidFontId;
+ }
+
+ // also tell the pages node about the new font
+ $this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
+ break;
+
+ case 'add':
+ $font_options = $this->processFont($id, $o['info']);
+
+ if ($font_options !== false) {
+ foreach ($font_options as $k => $v) {
+ switch ($k) {
+ case 'BaseFont':
+ $o['info']['name'] = $v;
+ break;
+ case 'FirstChar':
+ case 'LastChar':
+ case 'Widths':
+ case 'FontDescriptor':
+ case 'SubType':
+ $this->addMessage('o_font ' . $k . " : " . $v);
+ $o['info'][$k] = $v;
+ break;
+ }
+ }
+
+ // pass values down to descendent font
+ if (isset($o['info']['cidFont'])) {
+ $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
+ }
+ }
+ break;
+
+ case 'out':
+ if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
+ // For Unicode fonts, we need to incorporate font data into
+ // sub-sections that are linked from the primary font section.
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
+ // for more information.
+ //
+ // All of this code is adapted from the excellent changes made to
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
+
+ $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
+
+ // The horizontal identity mapping for 2-byte CIDs; may be used
+ // with CIDFonts using any Registry, Ordering, and Supplement values.
+ $res .= "/Encoding /Identity-H\n";
+ $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
+ $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
+ $res .= ">>\n";
+ $res .= "endobj";
+ } else {
+ $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
+ $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
+
+ if (isset($o['info']['encodingDictionary'])) {
+ // then place a reference to the dictionary
+ $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
+ } else {
+ if (isset($o['info']['encoding'])) {
+ // use the specified encoding
+ $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
+ }
+ }
+
+ if (isset($o['info']['FirstChar'])) {
+ $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
+ }
+
+ if (isset($o['info']['LastChar'])) {
+ $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
+ }
+
+ if (isset($o['info']['Widths'])) {
+ $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
+ }
+
+ if (isset($o['info']['FontDescriptor'])) {
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
+ }
+
+ $res .= ">>\n";
+ $res .= "endobj";
+ }
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function getFontSubsettingTag(array $font): string
+ {
+ // convert font num to hexavigesimal numeral system letters A - Z only
+ $base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
+ for ($i = 0; $i < strlen($base_26); $i++) {
+ $char = $base_26[$i];
+ if ($char <= "9") {
+ $base_26[$i] = chr(65 + intval($char));
+ } else {
+ $base_26[$i] = chr(ord($char) + 10);
+ }
+ }
+
+ return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
+ }
+
+ /**
+ * @param int $fontObjId
+ * @param array $object_info
+ * @return array|false
+ * @throws FontNotFoundException
+ */
+ private function processFont(int $fontObjId, array $object_info)
+ {
+ $fontFileName = $object_info['fontFileName'];
+ if (!isset($this->fonts[$fontFileName])) {
+ return false;
+ }
+
+ $font = &$this->fonts[$fontFileName];
+
+ $fileSuffix = $font['fileSuffix'];
+ $fileSuffixLower = strtolower($font['fileSuffix']);
+ $fbfile = "$fontFileName.$fileSuffix";
+ $isTtfFont = $fileSuffixLower === 'ttf';
+ $isPfbFont = $fileSuffixLower === 'pfb';
+
+ $this->addMessage('selectFont: checking for - ' . $fbfile);
+
+ if (!$fileSuffix) {
+ $this->addMessage(
+ 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
+ );
+
+ return false;
+ } else {
+ $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
+ // $fontObj = $this->numObj;
+ $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
+
+ // find the array of font widths, and put that into an object.
+ $firstChar = -1;
+ $lastChar = 0;
+ $widths = [];
+ $cid_widths = [];
+
+ foreach ($font['C'] as $num => $d) {
+ if (intval($num) > 0 || $num == '0') {
+ if (!$font['isUnicode']) {
+ // With Unicode, widths array isn't used
+ if ($lastChar > 0 && $num > $lastChar + 1) {
+ for ($i = $lastChar + 1; $i < $num; $i++) {
+ $widths[] = 0;
+ }
+ }
+ }
+
+ $widths[] = $d;
+
+ if ($font['isUnicode']) {
+ $cid_widths[$num] = $d;
+ }
+
+ if ($firstChar == -1) {
+ $firstChar = $num;
+ }
+
+ $lastChar = $num;
+ }
+ }
+
+ // also need to adjust the widths for the differences array
+ if (isset($object['differences'])) {
+ foreach ($object['differences'] as $charNum => $charName) {
+ if ($charNum > $lastChar) {
+ if (!$object['isUnicode']) {
+ // With Unicode, widths array isn't used
+ for ($i = $lastChar + 1; $i <= $charNum; $i++) {
+ $widths[] = 0;
+ }
+ }
+
+ $lastChar = $charNum;
+ }
+
+ if (isset($font['C'][$charName])) {
+ $widths[$charNum - $firstChar] = $font['C'][$charName];
+ if ($font['isUnicode']) {
+ $cid_widths[$charName] = $font['C'][$charName];
+ }
+ }
+ }
+ }
+
+ if ($font['isUnicode']) {
+ $font['CIDWidths'] = $cid_widths;
+ }
+
+ $this->addMessage('selectFont: FirstChar = ' . $firstChar);
+ $this->addMessage('selectFont: LastChar = ' . $lastChar);
+
+ $widthid = -1;
+
+ if (!$font['isUnicode']) {
+ // With Unicode, widths array isn't used
+
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new', 'raw');
+ $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
+ $widthid = $this->numObj;
+ }
+
+ $missing_width = 500;
+ $stemV = 70;
+
+ if (isset($font['MissingWidth'])) {
+ $missing_width = $font['MissingWidth'];
+ }
+ if (isset($font['StdVW'])) {
+ $stemV = $font['StdVW'];
+ } else {
+ if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
+ $stemV = 120;
+ }
+ }
+
+ // load the pfb file, and put that into an object too.
+ // note that pdf supports only binary format type 1 font files, though there is a
+ // simple utility to convert them from pfa to pfb.
+ if (!$font['isSubsetting']) {
+ $data = file_get_contents($fbfile);
+ } else {
+ $adobeFontName = $this->getFontSubsettingTag($font) . '+' . $adobeFontName;
+ $this->stringSubsets[$fontFileName][] = 32; // Force space if not in yet
+
+ $subset = $this->stringSubsets[$fontFileName];
+ sort($subset);
+
+ // Load font
+ $font_obj = Font::load($fbfile);
+ $font_obj->parse();
+
+ // Define subset
+ $font_obj->setSubset($subset);
+ $font_obj->reduce();
+
+ // Write new font
+ $tmp_name = @tempnam($this->tmp, "cpdf_subset_");
+ $font_obj->open($tmp_name, BinaryStream::modeReadWrite);
+ $font_obj->encode(["OS/2"]);
+ $font_obj->close();
+
+ // Parse the new font to get cid2gid and widths
+ $font_obj = Font::load($tmp_name);
+
+ // Find Unicode char map table
+ $subtable = null;
+ foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
+ if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
+ $subtable = $_subtable;
+ break;
+ }
+ }
+
+ if ($subtable) {
+ $glyphIndexArray = $subtable["glyphIndexArray"];
+ $hmtx = $font_obj->getData("hmtx");
+
+ unset($glyphIndexArray[0xFFFF]);
+
+ $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
+ $font['CIDWidths'] = [];
+ foreach ($glyphIndexArray as $cid => $gid) {
+ if ($cid >= 0 && $cid < 0xFFFF && $gid) {
+ $cidtogid[$cid * 2] = chr($gid >> 8);
+ $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
+ }
+
+ $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
+ $font['CIDWidths'][$cid] = $width;
+ }
+
+ $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
+ $font['CIDtoGID_Compressed'] = true;
+
+ $data = file_get_contents($tmp_name);
+ } else {
+ $data = file_get_contents($fbfile);
+ }
+
+ $font_obj->close();
+ unlink($tmp_name);
+ }
+
+ // create the font descriptor
+ $this->numObj++;
+ $fontDescriptorId = $this->numObj;
+
+ $this->numObj++;
+ $pfbid = $this->numObj;
+
+ // determine flags (more than a little flakey, hopefully will not matter much)
+ $flags = 0;
+
+ if ($font['ItalicAngle'] != 0) {
+ $flags += pow(2, 6);
+ }
+
+ if ($font['IsFixedPitch'] === 'true') {
+ $flags += 1;
+ }
+
+ $flags += pow(2, 5); // assume non-sybolic
+ $list = [
+ 'Ascent' => 'Ascender',
+ 'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
+ 'MissingWidth' => 'MissingWidth',
+ 'Descent' => 'Descender',
+ 'FontBBox' => 'FontBBox',
+ 'ItalicAngle' => 'ItalicAngle'
+ ];
+ $fdopt = [
+ 'Flags' => $flags,
+ 'FontName' => $adobeFontName,
+ 'StemV' => $stemV
+ ];
+
+ foreach ($list as $k => $v) {
+ if (isset($font[$v])) {
+ $fdopt[$k] = $font[$v];
+ }
+ }
+
+ if ($isPfbFont) {
+ $fdopt['FontFile'] = $pfbid;
+ } elseif ($isTtfFont) {
+ $fdopt['FontFile2'] = $pfbid;
+ }
+
+ $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
+
+ // embed the font program
+ $this->o_contents($this->numObj, 'new');
+ $this->objects[$pfbid]['c'] .= $data;
+
+ // determine the cruicial lengths within this file
+ if ($isPfbFont) {
+ $l1 = strpos($data, 'eexec') + 6;
+ $l2 = strpos($data, '00000000') - $l1;
+ $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
+ $this->o_contents(
+ $this->numObj,
+ 'add',
+ ['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
+ );
+ } elseif ($isTtfFont) {
+ $l1 = mb_strlen($data, '8bit');
+ $this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
+ }
+
+ // tell the font object about all this new stuff
+ $options = [
+ 'BaseFont' => $adobeFontName,
+ 'MissingWidth' => $missing_width,
+ 'Widths' => $widthid,
+ 'FirstChar' => $firstChar,
+ 'LastChar' => $lastChar,
+ 'FontDescriptor' => $fontDescriptorId
+ ];
+
+ if ($isTtfFont) {
+ $options['SubType'] = 'TrueType';
+ }
+
+ $this->addMessage("adding extra info to font.($fontObjId)");
+
+ foreach ($options as $fk => $fv) {
+ $this->addMessage("$fk : $fv");
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * A toUnicode section, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @return null|string
+ */
+ protected function o_toUnicode($id, $action)
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'toUnicode'
+ ];
+ break;
+ case 'add':
+ break;
+ case 'out':
+ $ordering = 'UCS';
+ $registry = 'Adobe';
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $ordering = $this->ARC4($ordering);
+ $registry = $this->filterText($this->ARC4($registry), false, false);
+ }
+
+ $stream = <<<EOT
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<</Registry ($registry)
+/Ordering ($ordering)
+/Supplement 0
+>> def
+/CMapName /Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<0000> <FFFF>
+endcodespacerange
+1 beginbfrange
+<0000> <FFFF> <0000>
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+EOT;
+
+ $res = "\n$id 0 obj\n";
+ $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
+ $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a font descriptor, needed for including additional fonts
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_fontDescriptor($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
+ foreach ($o['info'] as $label => $value) {
+ switch ($label) {
+ case 'Ascent':
+ case 'CapHeight':
+ case 'Descent':
+ case 'Flags':
+ case 'ItalicAngle':
+ case 'StemV':
+ case 'AvgWidth':
+ case 'Leading':
+ case 'MaxWidth':
+ case 'MissingWidth':
+ case 'StemH':
+ case 'XHeight':
+ case 'CharSet':
+ if (mb_strlen($value, '8bit')) {
+ $res .= "/$label $value\n";
+ }
+
+ break;
+ case 'FontFile':
+ case 'FontFile2':
+ case 'FontFile3':
+ $res .= "/$label $value 0 R\n";
+ break;
+
+ case 'FontBBox':
+ $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
+ break;
+
+ case 'FontName':
+ $res .= "/$label /$value\n";
+ break;
+ }
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * the font encoding
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_fontEncoding($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ // the options array should contain 'differences' and maybe 'encoding'
+ $this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Encoding\n";
+ if (!isset($o['info']['encoding'])) {
+ $o['info']['encoding'] = 'WinAnsiEncoding';
+ }
+
+ if ($o['info']['encoding'] !== 'none') {
+ $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
+ }
+
+ $res .= "/Differences \n[";
+
+ $onum = -100;
+
+ foreach ($o['info']['differences'] as $num => $label) {
+ if ($num != $onum + 1) {
+ // we cannot make use of consecutive numbering
+ $res .= "\n$num /$label";
+ } else {
+ $res .= " /$label";
+ }
+
+ $onum = $num;
+ }
+
+ $res .= "\n]\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a descendent cid font, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return null|string
+ */
+ protected function o_fontDescendentCID($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
+
+ // we need a CID system info section
+ $cidSystemInfoId = ++$this->numObj;
+ $this->o_cidSystemInfo($cidSystemInfoId, 'new');
+ $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
+
+ // and a CID to GID map
+ $cidToGidMapId = ++$this->numObj;
+ $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
+ $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
+ break;
+
+ case 'add':
+ foreach ($options as $k => $v) {
+ switch ($k) {
+ case 'BaseFont':
+ $o['info']['name'] = $v;
+ break;
+
+ case 'FirstChar':
+ case 'LastChar':
+ case 'MissingWidth':
+ case 'FontDescriptor':
+ case 'SubType':
+ $this->addMessage("o_fontDescendentCID $k : $v");
+ $o['info'][$k] = $v;
+ break;
+ }
+ }
+
+ // pass values down to cid to gid map
+ $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n";
+ $res .= "<</Type /Font\n";
+ $res .= "/Subtype /CIDFontType2\n";
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
+ $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
+ // if (isset($o['info']['FirstChar'])) {
+ // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
+ // }
+
+ // if (isset($o['info']['LastChar'])) {
+ // $res.= "/LastChar ".$o['info']['LastChar']."\n";
+ // }
+ if (isset($o['info']['FontDescriptor'])) {
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
+ }
+
+ if (isset($o['info']['MissingWidth'])) {
+ $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
+ }
+
+ if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
+ $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
+ $w = '';
+ foreach ($cid_widths as $cid => $width) {
+ $w .= "$cid [$width] ";
+ }
+ $res .= "/W [$w]\n";
+ }
+
+ $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
+ $res .= ">>\n";
+ $res .= "endobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * CID system info section, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @return null|string
+ */
+ protected function o_cidSystemInfo($id, $action)
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'cidSystemInfo'
+ ];
+ break;
+ case 'add':
+ break;
+ case 'out':
+ $ordering = 'UCS';
+ $registry = 'Adobe';
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $ordering = $this->ARC4($ordering);
+ $registry = $this->ARC4($registry);
+ }
+
+
+ $res = "\n$id 0 obj\n";
+
+ $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
+ $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
+ $res .= "/Supplement 0\n"; // The supplement number of the character collection.
+ $res .= ">>";
+
+ $res .= "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a font glyph to character map, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_fontGIDtoCIDMap($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n";
+ $fontFileName = $o['info']['fontFileName'];
+ $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
+
+ $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
+ $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
+
+ if (!$compressed && isset($o['raw'])) {
+ $res .= $tmp;
+ } else {
+ $res .= "<<";
+
+ if (!$compressed && $this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on this content stream
+ $compressed = true;
+ $tmp = gzcompress($tmp, 6);
+ }
+ if ($compressed) {
+ $res .= "\n/Filter /FlateDecode";
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
+ }
+
+ $res .= "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * the document procset, solves some problems with printing to old PS printers
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_procset($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
+ $this->o_pages($this->currentNode, 'procset', $id);
+ $this->procsetObjectId = $id;
+ break;
+
+ case 'add':
+ // this is to add new items to the procset list, despite the fact that this is considered
+ // obsolete, the items are required for printing to some postscript printers
+ switch ($options) {
+ case 'ImageB':
+ case 'ImageC':
+ case 'ImageI':
+ $o['info'][$options] = 1;
+ break;
+ }
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n[";
+ foreach ($o['info'] as $label => $val) {
+ $res .= "/$label ";
+ }
+ $res .= "]\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * define the document information
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_info($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->infoObject = $id;
+ $date = 'D:' . @date('Ymd');
+ $this->objects[$id] = [
+ 't' => 'info',
+ 'info' => [
+ 'Producer' => 'CPDF (dompdf)',
+ 'CreationDate' => $date
+ ]
+ ];
+ break;
+ case 'Title':
+ case 'Author':
+ case 'Subject':
+ case 'Keywords':
+ case 'Creator':
+ case 'Producer':
+ case 'CreationDate':
+ case 'ModDate':
+ case 'Trapped':
+ $this->objects[$id]['info'][$action] = $options;
+ break;
+
+ case 'out':
+ $encrypted = $this->encrypted;
+ if ($encrypted) {
+ $this->encryptInit($id);
+ }
+
+ $res = "\n$id 0 obj\n<<\n";
+ $o = &$this->objects[$id];
+ foreach ($o['info'] as $k => $v) {
+ $res .= "/$k (";
+
+ // dates must be outputted as-is, without Unicode transformations
+ if ($k !== 'CreationDate' && $k !== 'ModDate') {
+ $v = $this->utf8toUtf16BE($v);
+ }
+
+ if ($encrypted) {
+ $v = $this->ARC4($v);
+ }
+
+ $res .= $this->filterText($v, false, false);
+ $res .= ")\n";
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an action object, used to link to URLS initially
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_action($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ if (is_array($options)) {
+ $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
+ } else {
+ // then assume a URI action
+ $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
+ }
+ break;
+
+ case 'out':
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ }
+
+ $res = "\n$id 0 obj\n<< /Type /Action";
+ switch ($o['type']) {
+ case 'ilink':
+ if (!isset($this->destinations[(string)$o['info']['label']])) {
+ break;
+ }
+
+ // there will be an 'label' setting, this is the name of the destination
+ $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
+ break;
+
+ case 'URI':
+ $res .= "\n/S /URI\n/URI (";
+ if ($this->encrypted) {
+ $res .= $this->filterText($this->ARC4($o['info']), false, false);
+ } else {
+ $res .= $this->filterText($o['info'], false, false);
+ }
+
+ $res .= ")";
+ break;
+ }
+
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an annotation object, this will add an annotation to the current page.
+ * initially will support just link annotations
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_annotation($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ // add the annotation to the current page
+ $pageId = $this->currentPage;
+ $this->o_page($pageId, 'annot', $id);
+
+ // and add the action object which is going to be required
+ switch ($options['type']) {
+ case 'link':
+ $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
+ $this->numObj++;
+ $this->o_action($this->numObj, 'new', $options['url']);
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
+ break;
+
+ case 'ilink':
+ // this is to a named internal link
+ $label = $options['label'];
+ $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
+ $this->numObj++;
+ $this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
+ break;
+ }
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Annot";
+ switch ($o['info']['type']) {
+ case 'link':
+ case 'ilink':
+ $res .= "\n/Subtype /Link";
+ break;
+ }
+ $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
+ $res .= "\n/Border [0 0 0]";
+ $res .= "\n/H /I";
+ $res .= "\n/Rect [ ";
+
+ foreach ($o['info']['rect'] as $v) {
+ $res .= sprintf("%.4F ", $v);
+ }
+
+ $res .= "]";
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a page object, it also creates a contents object to hold its contents
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_page($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->numPages++;
+ $this->objects[$id] = [
+ 't' => 'page',
+ 'info' => [
+ 'parent' => $this->currentNode,
+ 'pageNum' => $this->numPages,
+ 'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
+ ]
+ ];
+
+ if (is_array($options)) {
+ // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
+ $options['id'] = $id;
+ $this->o_pages($this->currentNode, 'page', $options);
+ } else {
+ $this->o_pages($this->currentNode, 'page', $id);
+ }
+
+ $this->currentPage = $id;
+ //make a contents object to go with this page
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new', $id);
+ $this->currentContents = $this->numObj;
+ $this->objects[$id]['info']['contents'] = [];
+ $this->objects[$id]['info']['contents'][] = $this->numObj;
+
+ $match = ($this->numPages % 2 ? 'odd' : 'even');
+ foreach ($this->addLooseObjects as $oId => $target) {
+ if ($target === 'all' || $match === $target) {
+ $this->objects[$id]['info']['contents'][] = $oId;
+ }
+ }
+ break;
+
+ case 'content':
+ $o['info']['contents'][] = $options;
+ break;
+
+ case 'annot':
+ // add an annotation to this page
+ if (!isset($o['info']['annot'])) {
+ $o['info']['annot'] = [];
+ }
+
+ // $options should contain the id of the annotation dictionary
+ $o['info']['annot'][] = $options;
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Page";
+ if (isset($o['info']['mediaBox'])) {
+ $tmp = $o['info']['mediaBox'];
+ $res .= "\n/MediaBox [" . sprintf(
+ '%.3F %.3F %.3F %.3F',
+ $tmp[0],
+ $tmp[1],
+ $tmp[2],
+ $tmp[3]
+ ) . ']';
+ }
+ $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
+
+ if (isset($o['info']['annot'])) {
+ $res .= "\n/Annots [";
+ foreach ($o['info']['annot'] as $aId) {
+ $res .= " $aId 0 R";
+ }
+ $res .= " ]";
+ }
+
+ $count = count($o['info']['contents']);
+ if ($count == 1) {
+ $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
+ } else {
+ if ($count > 1) {
+ $res .= "\n/Contents [\n";
+
+ // reverse the page contents so added objects are below normal content
+ //foreach (array_reverse($o['info']['contents']) as $cId) {
+ // Back to normal now that I've got transparency working --Benj
+ foreach ($o['info']['contents'] as $cId) {
+ $res .= "$cId 0 R\n";
+ }
+ $res .= "]";
+ }
+ }
+
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * the contents objects hold all of the content which appears on pages
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return null|string
+ */
+ protected function o_contents($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
+ if (mb_strlen($options, '8bit') && intval($options)) {
+ // then this contents is the primary for a page
+ $this->objects[$id]['onPage'] = $options;
+ } else {
+ if ($options === 'raw') {
+ // then this page contains some other type of system object
+ $this->objects[$id]['raw'] = 1;
+ }
+ }
+ break;
+
+ case 'add':
+ // add more options to the declaration
+ foreach ($options as $k => $v) {
+ $o['info'][$k] = $v;
+ }
+
+ case 'out':
+ $tmp = $o['c'];
+ $res = "\n$id 0 obj\n";
+
+ if (isset($this->objects[$id]['raw'])) {
+ $res .= $tmp;
+ } else {
+ $res .= "<<";
+ if ($this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on this content stream
+ $res .= " /Filter /FlateDecode";
+ $tmp = gzcompress($tmp, 6);
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
+ }
+
+ $res .= "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @return string|null
+ */
+ protected function o_embedjs($id, $action)
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'embedjs',
+ 'info' => [
+ 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
+ ]
+ ];
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< ";
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @param string $code
+ * @return null|string
+ */
+ protected function o_javascript($id, $action, $code = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'javascript',
+ 'info' => [
+ 'S' => '/JavaScript',
+ 'JS' => '(' . $this->filterText($code, true, false) . ')',
+ ]
+ ];
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< ";
+
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an image object, will be an XObject in the document, includes description and data
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_image($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ // make the new object
+ $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
+
+ $info =& $this->objects[$id]['info'];
+
+ $info['Type'] = '/XObject';
+ $info['Subtype'] = '/Image';
+ $info['Width'] = $options['iw'];
+ $info['Height'] = $options['ih'];
+
+ if (isset($options['masked']) && $options['masked']) {
+ $info['SMask'] = ($this->numObj - 1) . ' 0 R';
+ }
+
+ if (!isset($options['type']) || $options['type'] === 'jpg') {
+ if (!isset($options['channels'])) {
+ $options['channels'] = 3;
+ }
+
+ switch ($options['channels']) {
+ case 1:
+ $info['ColorSpace'] = '/DeviceGray';
+ break;
+ case 4:
+ $info['ColorSpace'] = '/DeviceCMYK';
+ break;
+ default:
+ $info['ColorSpace'] = '/DeviceRGB';
+ break;
+ }
+
+ if ($info['ColorSpace'] === '/DeviceCMYK') {
+ $info['Decode'] = '[1 0 1 0 1 0 1 0]';
+ }
+
+ $info['Filter'] = '/DCTDecode';
+ $info['BitsPerComponent'] = 8;
+ } else {
+ if ($options['type'] === 'png') {
+ $info['Filter'] = '/FlateDecode';
+ $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
+
+ if ($options['isMask']) {
+ $info['ColorSpace'] = '/DeviceGray';
+ } else {
+ if (mb_strlen($options['pdata'], '8bit')) {
+ $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new');
+ $this->objects[$this->numObj]['c'] = $options['pdata'];
+ $tmp .= $this->numObj . ' 0 R';
+ $tmp .= ' ]';
+ $info['ColorSpace'] = $tmp;
+
+ if (isset($options['transparency'])) {
+ $transparency = $options['transparency'];
+ switch ($transparency['type']) {
+ case 'indexed':
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
+ $info['Mask'] = $tmp;
+ break;
+
+ case 'color-key':
+ $tmp = ' [ ' .
+ $transparency['r'] . ' ' . $transparency['r'] .
+ $transparency['g'] . ' ' . $transparency['g'] .
+ $transparency['b'] . ' ' . $transparency['b'] .
+ ' ] ';
+ $info['Mask'] = $tmp;
+ break;
+ }
+ }
+ } else {
+ if (isset($options['transparency'])) {
+ $transparency = $options['transparency'];
+
+ switch ($transparency['type']) {
+ case 'indexed':
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
+ $info['Mask'] = $tmp;
+ break;
+
+ case 'color-key':
+ $tmp = ' [ ' .
+ $transparency['r'] . ' ' . $transparency['r'] . ' ' .
+ $transparency['g'] . ' ' . $transparency['g'] . ' ' .
+ $transparency['b'] . ' ' . $transparency['b'] .
+ ' ] ';
+ $info['Mask'] = $tmp;
+ break;
+ }
+ }
+ $info['ColorSpace'] = '/' . $options['color'];
+ }
+ }
+
+ $info['BitsPerComponent'] = $options['bitsPerComponent'];
+ }
+ }
+
+ // assign it a place in the named resource dictionary as an external object, according to
+ // the label passed in with it.
+ $this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
+
+ // also make sure that we have the right procset object for it.
+ $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $tmp = &$o['data'];
+ $res = "\n$id 0 obj\n<<";
+
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * graphics state object
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_extGState($id, $action, $options = "")
+ {
+ static $valid_params = [
+ "LW",
+ "LC",
+ "LC",
+ "LJ",
+ "ML",
+ "D",
+ "RI",
+ "OP",
+ "op",
+ "OPM",
+ "Font",
+ "BG",
+ "BG2",
+ "UCR",
+ "TR",
+ "TR2",
+ "HT",
+ "FL",
+ "SM",
+ "SA",
+ "BM",
+ "SMask",
+ "CA",
+ "ca",
+ "AIS",
+ "TK"
+ ];
+
+ switch ($action) {
+ case "new":
+ $this->objects[$id] = ['t' => 'extGState', 'info' => $options];
+
+ // Tell the pages about the new resource
+ $this->numStates++;
+ $this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
+ break;
+
+ case "out":
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
+
+ foreach ($o["info"] as $k => $v) {
+ if (!in_array($k, $valid_params)) {
+ continue;
+ }
+ $res .= "/$k $v\n";
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param integer $id
+ * @param string $action
+ * @param mixed $options
+ * @return string
+ */
+ protected function o_xobject($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
+ break;
+
+ case 'procset':
+ $this->objects[$id]['procset'] = $options;
+ break;
+
+ case 'font':
+ $this->objects[$id]['fonts'][$options['fontNum']] = [
+ 'objNum' => $options['objNum'],
+ 'fontNum' => $options['fontNum']
+ ];
+ break;
+
+ case 'xObject':
+ $this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< /Type /XObject\n";
+
+ foreach ($o["info"] as $k => $v) {
+ switch ($k) {
+ case 'Subtype':
+ $res .= "/Subtype /$v\n";
+ break;
+ case 'bbox':
+ $res .= "/BBox [";
+ foreach ($v as $value) {
+ $res .= sprintf("%.4F ", $value);
+ }
+ $res .= "]\n";
+ break;
+ default:
+ $res .= "/$k $v\n";
+ break;
+ }
+ }
+ $res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
+
+ $res .= "/Resources <<";
+ if (isset($o['procset'])) {
+ $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
+ } else {
+ $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
+ }
+ if (isset($o['fonts']) && count($o['fonts'])) {
+ $res .= "\n/Font << ";
+ foreach ($o['fonts'] as $finfo) {
+ $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+ if (isset($o['xObjects']) && count($o['xObjects'])) {
+ $res .= "\n/XObject << ";
+ foreach ($o['xObjects'] as $finfo) {
+ $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+ $res .= "\n>>\n";
+
+ $tmp = $o["c"];
+ if ($this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on this content stream
+ $res .= " /Filter /FlateDecode\n";
+ $tmp = gzcompress($tmp, 6);
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
+ $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_acroform($id, $action, $options = '')
+ {
+ switch ($action) {
+ case "new":
+ $this->o_catalog($this->catalogId, 'acroform', $id);
+ $this->objects[$id] = array('t' => 'acroform', 'info' => $options);
+ break;
+
+ case 'addfield':
+ $this->objects[$id]['info']['Fields'][] = $options;
+ break;
+
+ case 'font':
+ $this->objects[$id]['fonts'][$options['fontNum']] = [
+ 'objNum' => $options['objNum'],
+ 'fontNum' => $options['fontNum']
+ ];
+ break;
+
+ case "out":
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<<";
+
+ foreach ($o["info"] as $k => $v) {
+ switch ($k) {
+ case 'Fields':
+ $res .= " /Fields [";
+ foreach ($v as $i) {
+ $res .= "$i 0 R ";
+ }
+ $res .= "]\n";
+ break;
+ default:
+ $res .= "/$k $v\n";
+ }
+ }
+
+ $res .= "/DR <<\n";
+ if (isset($o['fonts']) && count($o['fonts'])) {
+ $res .= "/Font << \n";
+ foreach ($o['fonts'] as $finfo) {
+ $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
+ }
+ $res .= ">>\n";
+ }
+ $res .= ">>\n";
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @param mixed $options
+ * @return null|string
+ */
+ protected function o_field($id, $action, $options = '')
+ {
+ switch ($action) {
+ case "new":
+ $this->o_page($options['pageid'], 'annot', $id);
+ $this->o_acroform($this->acroFormId, 'addfield', $id);
+ $this->objects[$id] = ['t' => 'field', 'info' => $options];
+ break;
+
+ case 'set':
+ $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
+ break;
+
+ case "out":
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
+
+ $encrypted = $this->encrypted;
+ if ($encrypted) {
+ $this->encryptInit($id);
+ }
+
+ foreach ($o["info"] as $k => $v) {
+ switch ($k) {
+ case 'pageid':
+ $res .= "/P $v 0 R\n";
+ break;
+ case 'value':
+ if ($encrypted) {
+ $v = $this->filterText($this->ARC4($v), false, false);
+ }
+ $res .= "/V ($v)\n";
+ break;
+ case 'refvalue':
+ $res .= "/V $v 0 R\n";
+ break;
+ case 'da':
+ if ($encrypted) {
+ $v = $this->filterText($this->ARC4($v), false, false);
+ }
+ $res .= "/DA ($v)\n";
+ break;
+ case 'options':
+ $res .= "/Opt [\n";
+ foreach ($v as $opt) {
+ if ($encrypted) {
+ $opt = $this->filterText($this->ARC4($opt), false, false);
+ }
+ $res .= "($opt)\n";
+ }
+ $res .= "]\n";
+ break;
+ case 'rect':
+ $res .= "/Rect [";
+ foreach ($v as $value) {
+ $res .= sprintf("%.4F ", $value);
+ }
+ $res .= "]\n";
+ break;
+ case 'appearance':
+ $res .= "/AP << ";
+ foreach ($v as $a => $ref) {
+ $res .= "/$a $ref 0 R ";
+ }
+ $res .= ">>\n";
+ break;
+ case 'T':
+ if ($encrypted) {
+ $v = $this->filterText($this->ARC4($v), false, false);
+ }
+ $res .= "/T ($v)\n";
+ break;
+ default:
+ $res .= "/$k $v\n";
+ }
+
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_sig($id, $action, $options = '')
+ {
+ $sign_maxlen = $this->signatureMaxLen;
+
+ switch ($action) {
+ case "new":
+ $this->objects[$id] = array('t' => 'sig', 'info' => $options);
+ $this->byteRange[$id] = ['t' => 'sig'];
+ break;
+
+ case 'byterange':
+ $o = &$this->objects[$id];
+ $content =& $options['content'];
+ $content_len = strlen($content);
+ $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
+ $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
+ $rangeStartPos = $pos + $len + 1 + 10; // before '<'
+ $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
+
+ $fuid = uniqid();
+ $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
+ $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
+
+ if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
+ throw new \Exception("Unable to write temporary file for signing.");
+ }
+ if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
+ FILE_APPEND) === false) {
+ throw new \Exception("Unable to write temporary file for signing.");
+ }
+
+ if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
+ $o['info']['SignCert'],
+ array($o['info']['PrivKey'], $o['info']['Password']),
+ array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
+ throw new \Exception("Failed to prepare signature.");
+ }
+
+ $signature = file_get_contents($tmpOutput);
+
+ unlink($tmpInput);
+ unlink($tmpOutput);
+
+ $sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
+ list($head, $signature) = explode("\n\n", $sign);
+
+ $signature = base64_decode(trim($signature));
+
+ $signature = current(unpack('H*', $signature));
+ $signature = str_pad($signature, $sign_maxlen, '0');
+ $siglen = strlen($signature);
+ if (strlen($signature) > $sign_maxlen) {
+ throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
+ }
+
+ $content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
+ break;
+
+ case "out":
+ $res = "\n$id 0 obj\n<<\n";
+
+ $encrypted = $this->encrypted;
+ if ($encrypted) {
+ $this->encryptInit($id);
+ }
+
+ $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
+ $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
+ $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
+ $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
+
+ $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
+ if ($encrypted) {
+ $date = $this->ARC4($date);
+ }
+
+ $res .= "/M ($date)\n";
+ $res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
+
+ $o = &$this->objects[$id];
+ foreach ($o['info'] as $k => $v) {
+ switch ($k) {
+ case 'Name':
+ case 'Location':
+ case 'Reason':
+ case 'ContactInfo':
+ if ($v !== null && $v !== '') {
+ $res .= "/$k (" .
+ ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
+ }
+ break;
+ }
+ }
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * encryption object.
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_encryption($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ // make the new object
+ $this->objects[$id] = ['t' => 'encryption', 'info' => $options];
+ $this->arc4_objnum = $id;
+ break;
+
+ case 'keys':
+ // figure out the additional parameters required
+ $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
+ . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
+ . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
+ . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
+
+ $info = $this->objects[$id]['info'];
+
+ $len = mb_strlen($info['owner'], '8bit');
+
+ if ($len > 32) {
+ $owner = substr($info['owner'], 0, 32);
+ } else {
+ if ($len < 32) {
+ $owner = $info['owner'] . substr($pad, 0, 32 - $len);
+ } else {
+ $owner = $info['owner'];
+ }
+ }
+
+ $len = mb_strlen($info['user'], '8bit');
+ if ($len > 32) {
+ $user = substr($info['user'], 0, 32);
+ } else {
+ if ($len < 32) {
+ $user = $info['user'] . substr($pad, 0, 32 - $len);
+ } else {
+ $user = $info['user'];
+ }
+ }
+
+ $tmp = $this->md5_16($owner);
+ $okey = substr($tmp, 0, 5);
+ $this->ARC4_init($okey);
+ $ovalue = $this->ARC4($user);
+ $this->objects[$id]['info']['O'] = $ovalue;
+
+ // now make the u value, phew.
+ $tmp = $this->md5_16(
+ $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
+ );
+
+ $ukey = substr($tmp, 0, 5);
+ $this->ARC4_init($ukey);
+ $this->encryptionKey = $ukey;
+ $this->encrypted = true;
+ $uvalue = $this->ARC4($pad);
+ $this->objects[$id]['info']['U'] = $uvalue;
+ // initialize the arc4 array
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+
+ $res = "\n$id 0 obj\n<<";
+ $res .= "\n/Filter /Standard";
+ $res .= "\n/V 1";
+ $res .= "\n/R 2";
+ $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
+ $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
+ // and the p-value needs to be converted to account for the twos-complement approach
+ $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
+ $res .= "\n/P " . ($o['info']['p']);
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_indirect_references($id, $action, $options = null)
+ {
+ switch ($action) {
+ case 'new':
+ case 'add':
+ if ($id === 0) {
+ $id = ++$this->numObj;
+ $this->o_catalog($this->catalogId, 'names', $id);
+ $this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
+ $this->indirectReferenceId = $id;
+ } else {
+ $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
+ }
+ break;
+ case 'out':
+ $res = "\n$id 0 obj << ";
+
+ foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
+ $res .= "/$referenceObjName $referenceObjId 0 R ";
+ }
+
+ $res .= ">> endobj";
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_names($id, $action, $options = null)
+ {
+ switch ($action) {
+ case 'new':
+ case 'add':
+ if ($id === 0) {
+ $id = ++$this->numObj;
+ $this->objects[$id] = ['t' => 'names', 'info' => [$options]];
+ $this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
+ $this->embeddedFilesId = $id;
+ } else {
+ $this->objects[$id]['info'][] = $options;
+ }
+ break;
+ case 'out':
+ $info = &$this->objects[$id]['info'];
+ $res = '';
+ if (count($info) > 0) {
+ $res = "\n$id 0 obj << /Names [ ";
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ }
+
+ foreach ($info as $entry) {
+ if ($this->encrypted) {
+ $filename = $this->ARC4($entry['filename']);
+ } else {
+ $filename = $entry['filename'];
+ }
+
+ $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
+ }
+
+ $res .= "] >> endobj";
+ }
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_embedded_file_dictionary($id, $action, $options = null)
+ {
+ switch ($action) {
+ case 'new':
+ $embeddedFileId = ++$this->numObj;
+ $options['embedded_reference'] = $embeddedFileId;
+ $this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
+ $this->o_embedded_file($embeddedFileId, 'new', $options);
+ $options['dict_reference'] = $id;
+ $this->o_names($this->embeddedFilesId, 'add', $options);
+ break;
+ case 'out':
+ $info = &$this->objects[$id]['info'];
+ $filename = $this->utf8toUtf16BE($info['filename']);
+ $description = $this->utf8toUtf16BE($info['description']);
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $filename = $this->ARC4($filename);
+ $description = $this->ARC4($description);
+ }
+
+ $filename = $this->filterText($filename, false, false);
+ $description = $this->filterText($description, false, false);
+
+ $res = "\n$id 0 obj <</Type /Filespec /EF";
+ $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
+ $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
+ $res .= " >> endobj";
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_embedded_file($id, $action, $options = null): ?string
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
+ break;
+ case 'out':
+ $info = &$this->objects[$id]['info'];
+
+ if ($this->compressionReady) {
+ $filepath = $info['filepath'];
+ $checksum = md5_file($filepath);
+ $f = fopen($filepath, "rb");
+
+ $file_content_compressed = '';
+ $deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
+ while (($block = fread($f, 8192))) {
+ $file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
+ }
+ $file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
+ $file_size_uncompressed = ftell($f);
+ fclose($f);
+ } else {
+ $file_content = file_get_contents($info['filepath']);
+ $file_size_uncompressed = mb_strlen($file_content, '8bit');
+ $checksum = md5($file_content);
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $checksum = $this->ARC4($checksum);
+ $file_content_compressed = $this->ARC4($file_content_compressed);
+ }
+ $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
+
+ $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
+ " /Type/EmbeddedFile /Filter/FlateDecode" .
+ " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * ARC4 functions
+ * A series of function to implement ARC4 encoding in PHP
+ */
+
+ /**
+ * calculate the 16 byte version of the 128 bit md5 digest of the string
+ *
+ * @param $string
+ * @return string
+ */
+ function md5_16($string)
+ {
+ $tmp = md5($string);
+ $out = '';
+ for ($i = 0; $i <= 30; $i = $i + 2) {
+ $out .= chr(hexdec(substr($tmp, $i, 2)));
+ }
+
+ return $out;
+ }
+
+ /**
+ * initialize the encryption for processing a particular object
+ *
+ * @param $id
+ */
+ function encryptInit($id)
+ {
+ $tmp = $this->encryptionKey;
+ $hex = dechex($id);
+ if (mb_strlen($hex, '8bit') < 6) {
+ $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
+ }
+ $tmp .= chr(hexdec(substr($hex, 4, 2)))
+ . chr(hexdec(substr($hex, 2, 2)))
+ . chr(hexdec(substr($hex, 0, 2)))
+ . chr(0)
+ . chr(0)
+ ;
+ $key = $this->md5_16($tmp);
+ $this->ARC4_init(substr($key, 0, 10));
+ }
+
+ /**
+ * initialize the ARC4 encryption
+ *
+ * @param string $key
+ */
+ function ARC4_init($key = '')
+ {
+ $this->arc4 = '';
+
+ // setup the control array
+ if (mb_strlen($key, '8bit') == 0) {
+ return;
+ }
+
+ $k = '';
+ while (mb_strlen($k, '8bit') < 256) {
+ $k .= $key;
+ }
+
+ $k = substr($k, 0, 256);
+ for ($i = 0; $i < 256; $i++) {
+ $this->arc4 .= chr($i);
+ }
+
+ $j = 0;
+
+ for ($i = 0; $i < 256; $i++) {
+ $t = $this->arc4[$i];
+ $j = ($j + ord($t) + ord($k[$i])) % 256;
+ $this->arc4[$i] = $this->arc4[$j];
+ $this->arc4[$j] = $t;
+ }
+ }
+
+ /**
+ * ARC4 encrypt a text string
+ *
+ * @param $text
+ * @return string
+ */
+ function ARC4($text)
+ {
+ $len = mb_strlen($text, '8bit');
+ $a = 0;
+ $b = 0;
+ $c = $this->arc4;
+ $out = '';
+ for ($i = 0; $i < $len; $i++) {
+ $a = ($a + 1) % 256;
+ $t = $c[$a];
+ $b = ($b + ord($t)) % 256;
+ $c[$a] = $c[$b];
+ $c[$b] = $t;
+ $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
+ $out .= chr(ord($text[$i]) ^ $k);
+ }
+
+ return $out;
+ }
+
+ /**
+ * functions which can be called to adjust or add to the document
+ */
+
+ /**
+ * add a link in the document to an external URL
+ *
+ * @param $url
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ */
+ function addLink($url, $x0, $y0, $x1, $y1)
+ {
+ $this->numObj++;
+ $info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
+ $this->o_annotation($this->numObj, 'new', $info);
+ }
+
+ /**
+ * add a link in the document to an internal destination (ie. within the document)
+ *
+ * @param $label
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ */
+ function addInternalLink($label, $x0, $y0, $x1, $y1)
+ {
+ $this->numObj++;
+ $info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
+ $this->o_annotation($this->numObj, 'new', $info);
+ }
+
+ /**
+ * set the encryption of the document
+ * can be used to turn it on and/or set the passwords which it will have.
+ * also the functions that the user will have are set here, such as print, modify, add
+ *
+ * @param string $userPass
+ * @param string $ownerPass
+ * @param array $pc
+ */
+ function setEncryption($userPass = '', $ownerPass = '', $pc = [])
+ {
+ $p = bindec("11000000");
+
+ $options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
+
+ foreach ($pc as $k => $v) {
+ if ($v && isset($options[$k])) {
+ $p += $options[$k];
+ } else {
+ if (isset($options[$v])) {
+ $p += $options[$v];
+ }
+ }
+ }
+
+ // implement encryption on the document
+ if ($this->arc4_objnum == 0) {
+ // then the block does not exist already, add it.
+ $this->numObj++;
+ if (mb_strlen($ownerPass) == 0) {
+ $ownerPass = $userPass;
+ }
+
+ $this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
+ }
+ }
+
+ /**
+ * should be used for internal checks, not implemented as yet
+ */
+ function checkAllHere()
+ {
+ }
+
+ /**
+ * return the pdf stream as a string returned from the function
+ *
+ * @param bool $debug
+ * @return string
+ */
+ function output($debug = false)
+ {
+ if ($debug) {
+ // turn compression off
+ $this->options['compression'] = false;
+ }
+
+ if ($this->javascript) {
+ $this->numObj++;
+
+ $js_id = $this->numObj;
+ $this->o_embedjs($js_id, 'new');
+ $this->o_javascript(++$this->numObj, 'new', $this->javascript);
+
+ $id = $this->catalogId;
+
+ $this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
+ }
+
+ if ($this->fileIdentifier === '') {
+ $tmp = implode('', $this->objects[$this->infoObject]['info']);
+ $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
+ }
+
+ if ($this->arc4_objnum) {
+ $this->o_encryption($this->arc4_objnum, 'keys');
+ $this->ARC4_init($this->encryptionKey);
+ }
+
+ $this->checkAllHere();
+
+ $xref = [];
+ $content = '%PDF-' . self::PDF_VERSION;
+ $pos = mb_strlen($content, '8bit');
+
+ // pre-process o_font objects before output of all objects
+ foreach ($this->objects as $k => $v) {
+ if ($v['t'] === 'font') {
+ $this->o_font($k, 'add');
+ }
+ }
+
+ foreach ($this->objects as $k => $v) {
+ $tmp = 'o_' . $v['t'];
+ $cont = $this->$tmp($k, 'out');
+ $content .= $cont;
+ $xref[] = $pos + 1; //+1 to account for \n at the start of each object
+ $pos += mb_strlen($cont, '8bit');
+ }
+
+ $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
+
+ foreach ($xref as $p) {
+ $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
+ }
+
+ $content .= "trailer\n<<\n" .
+ '/Size ' . (count($xref) + 1) . "\n" .
+ '/Root 1 0 R' . "\n" .
+ '/Info ' . $this->infoObject . " 0 R\n"
+ ;
+
+ // if encryption has been applied to this document then add the marker for this dictionary
+ if ($this->arc4_objnum > 0) {
+ $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
+ }
+
+ $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
+
+ // account for \n added at start of xref table
+ $pos++;
+
+ $content .= ">>\nstartxref\n$pos\n%%EOF\n";
+
+ if (count($this->byteRange) > 0) {
+ foreach ($this->byteRange as $k => $v) {
+ $tmp = 'o_' . $v['t'];
+ $this->$tmp($k, 'byterange', ['content' => &$content]);
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * initialize a new document
+ * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
+ * this function is called automatically by the constructor function
+ *
+ * @param array $pageSize
+ */
+ private function newDocument($pageSize = [0, 0, 612, 792])
+ {
+ $this->numObj = 0;
+ $this->objects = [];
+
+ $this->numObj++;
+ $this->o_catalog($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_outlines($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_pages($this->numObj, 'new');
+
+ $this->o_pages($this->numObj, 'mediaBox', $pageSize);
+ $this->currentNode = 3;
+
+ $this->numObj++;
+ $this->o_procset($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_info($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_page($this->numObj, 'new');
+
+ // need to store the first page id as there is no way to get it to the user during
+ // startup
+ $this->firstPageId = $this->currentContents;
+ }
+
+ /**
+ * open the font file and return a php structure containing it.
+ * first check if this one has been done before and saved in a form more suited to php
+ * note that if a php serialized version does not exist it will try and make one, but will
+ * require write access to the directory to do it... it is MUCH faster to have these serialized
+ * files.
+ *
+ * @param $font
+ */
+ private function openFont($font)
+ {
+ // assume that $font contains the path and file but not the extension
+ $name = basename($font);
+ $dir = dirname($font);
+
+ $fontcache = $this->fontcache;
+ if ($fontcache == '') {
+ $fontcache = $dir;
+ }
+
+ //$name filename without folder and extension of font metrics
+ //$dir folder of font metrics
+ //$fontcache folder of runtime created php serialized version of font metrics.
+ // If this is not given, the same folder as the font metrics will be used.
+ // Storing and reusing serialized versions improves speed much
+
+ $this->addMessage("openFont: $font - $name");
+
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
+ $metrics_name = "$name.afm";
+ } else {
+ $metrics_name = "$name.ufm";
+ }
+
+ $cache_name = "$metrics_name.json";
+ $this->addMessage("metrics: $metrics_name, cache: $cache_name");
+
+ if (file_exists($fontcache . '/' . $cache_name)) {
+ $this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
+ $cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
+ if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
+ $this->addMessage('openFont: font cache is out of date, regenerating');
+ } else {
+ $this->fonts[$font] = $cached_font_info;
+ }
+ }
+
+ if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
+ // then rebuild the php_<font>.afm file from the <font>.afm file
+ $this->addMessage("openFont: build php file from $dir/$metrics_name");
+ $data = [];
+
+ // 20 => 'space'
+ $data['codeToName'] = [];
+
+ // Since we're not going to enable Unicode for the core fonts we need to use a font-based
+ // setting for Unicode support rather than a global setting.
+ $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
+
+ $cidtogid = '';
+ if ($data['isUnicode']) {
+ $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
+ }
+
+ $file = file("$dir/$metrics_name");
+
+ foreach ($file as $rowA) {
+ $row = trim($rowA);
+ $pos = strpos($row, ' ');
+
+ if ($pos) {
+ // then there must be some keyword
+ $key = substr($row, 0, $pos);
+ switch ($key) {
+ case 'FontName':
+ case 'FullName':
+ case 'FamilyName':
+ case 'PostScriptName':
+ case 'Weight':
+ case 'ItalicAngle':
+ case 'IsFixedPitch':
+ case 'CharacterSet':
+ case 'UnderlinePosition':
+ case 'UnderlineThickness':
+ case 'Version':
+ case 'EncodingScheme':
+ case 'CapHeight':
+ case 'XHeight':
+ case 'Ascender':
+ case 'Descender':
+ case 'StdHW':
+ case 'StdVW':
+ case 'StartCharMetrics':
+ case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big.
+ $data[$key] = trim(substr($row, $pos));
+ break;
+
+ case 'FontBBox':
+ $data[$key] = explode(' ', trim(substr($row, $pos)));
+ break;
+
+ //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
+ case 'C': // Found in AFM files
+ $bits = explode(';', trim($row));
+ $dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
+
+ foreach ($bits as $bit) {
+ $bits2 = explode(' ', trim($bit));
+ if (mb_strlen($bits2[0], '8bit') == 0) {
+ continue;
+ }
+
+ if (count($bits2) > 2) {
+ $dtmp[$bits2[0]] = [];
+ for ($i = 1; $i < count($bits2); $i++) {
+ $dtmp[$bits2[0]][] = $bits2[$i];
+ }
+ } else {
+ if (count($bits2) == 2) {
+ $dtmp[$bits2[0]] = $bits2[1];
+ }
+ }
+ }
+
+ $c = (int)$dtmp['C'];
+ $n = $dtmp['N'];
+ $width = floatval($dtmp['WX']);
+
+ if ($c >= 0) {
+ if (!ctype_xdigit($n) || $c != hexdec($n)) {
+ $data['codeToName'][$c] = $n;
+ }
+ $data['C'][$c] = $width;
+ } elseif (isset($n)) {
+ $data['C'][$n] = $width;
+ }
+
+ if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
+ $data['MissingWidth'] = $width;
+ }
+
+ break;
+
+ // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
+ case 'U': // Found in UFM files
+ if (!$data['isUnicode']) {
+ break;
+ }
+
+ $bits = explode(';', trim($row));
+ $dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
+
+ foreach ($bits as $bit) {
+ $bits2 = explode(' ', trim($bit));
+ if (mb_strlen($bits2[0], '8bit') === 0) {
+ continue;
+ }
+
+ if (count($bits2) > 2) {
+ $dtmp[$bits2[0]] = [];
+ for ($i = 1; $i < count($bits2); $i++) {
+ $dtmp[$bits2[0]][] = $bits2[$i];
+ }
+ } else {
+ if (count($bits2) == 2) {
+ $dtmp[$bits2[0]] = $bits2[1];
+ }
+ }
+ }
+
+ $c = (int)$dtmp['U'];
+ $n = $dtmp['N'];
+ $glyph = $dtmp['G'];
+ $width = floatval($dtmp['WX']);
+
+ if ($c >= 0) {
+ // Set values in CID to GID map
+ if ($c >= 0 && $c < 0xFFFF && $glyph) {
+ $cidtogid[$c * 2] = chr($glyph >> 8);
+ $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
+ }
+
+ if (!ctype_xdigit($n) || $c != hexdec($n)) {
+ $data['codeToName'][$c] = $n;
+ }
+ $data['C'][$c] = $width;
+ } elseif (isset($n)) {
+ $data['C'][$n] = $width;
+ }
+
+ if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
+ $data['MissingWidth'] = $width;
+ }
+
+ break;
+
+ case 'KPX':
+ break; // don't include them as they are not used yet
+ //KPX Adieresis yacute -40
+ /*$bits = explode(' ', trim($row));
+ $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
+ break;*/
+ }
+ }
+ }
+
+ if ($this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on CIDtoGID string
+ $data['CIDtoGID_Compressed'] = true;
+ $cidtogid = gzcompress($cidtogid, 6);
+ }
+ $data['CIDtoGID'] = base64_encode($cidtogid);
+ $data['_version_'] = $this->fontcacheVersion;
+ $this->fonts[$font] = $data;
+
+ //Because of potential trouble with php safe mode, expect that the folder already exists.
+ //If not existing, this will hit performance because of missing cached results.
+ if (is_dir($fontcache) && is_writable($fontcache)) {
+ file_put_contents("$fontcache/$cache_name", json_encode($data, JSON_PRETTY_PRINT));
+ }
+ $data = null;
+ }
+
+ if (!isset($this->fonts[$font])) {
+ $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
+ }
+ }
+
+ /**
+ * if the font is not loaded then load it and make the required object
+ * else just make it the current font
+ * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
+ * note that encoding='none' will need to be used for symbolic fonts
+ * and 'differences' => an array of mappings between numbers 0->255 and character names.
+ *
+ * @param string $fontName
+ * @param string $encoding
+ * @param bool $set
+ * @param bool $isSubsetting
+ * @return int
+ * @throws FontNotFoundException
+ */
+ function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
+ {
+ if ($fontName === null || $fontName === '') {
+ return $this->currentFontNum;
+ }
+
+ $ext = substr($fontName, -4);
+ if ($ext === '.afm' || $ext === '.ufm') {
+ $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
+ }
+
+ if (!isset($this->fonts[$fontName])) {
+ $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
+
+ // load the file
+ $this->openFont($fontName);
+
+ if (isset($this->fonts[$fontName])) {
+ $this->numObj++;
+ $this->numFonts++;
+
+ $font = &$this->fonts[$fontName];
+
+ $name = basename($fontName);
+ $options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
+
+ if (is_array($encoding)) {
+ // then encoding and differences might be set
+ if (isset($encoding['encoding'])) {
+ $options['encoding'] = $encoding['encoding'];
+ }
+
+ if (isset($encoding['differences'])) {
+ $options['differences'] = $encoding['differences'];
+ }
+ } else {
+ if (mb_strlen($encoding, '8bit')) {
+ // then perhaps only the encoding has been set
+ $options['encoding'] = $encoding;
+ }
+ }
+
+ $this->o_font($this->numObj, 'new', $options);
+
+ if (file_exists("$fontName.ttf")) {
+ $fileSuffix = 'ttf';
+ } elseif (file_exists("$fontName.TTF")) {
+ $fileSuffix = 'TTF';
+ } elseif (file_exists("$fontName.pfb")) {
+ $fileSuffix = 'pfb';
+ } elseif (file_exists("$fontName.PFB")) {
+ $fileSuffix = 'PFB';
+ } else {
+ $fileSuffix = '';
+ }
+
+ $font['fileSuffix'] = $fileSuffix;
+
+ $font['fontNum'] = $this->numFonts;
+ $font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
+
+ // also set the differences here, note that this means that these will take effect only the
+ //first time that a font is selected, else they are ignored
+ if (isset($options['differences'])) {
+ $font['differences'] = $options['differences'];
+ }
+ }
+ }
+
+ if ($set && isset($this->fonts[$fontName])) {
+ // so if for some reason the font was not set in the last one then it will not be selected
+ $this->currentBaseFont = $fontName;
+
+ // the next lines mean that if a new font is selected, then the current text state will be
+ // applied to it as well.
+ $this->currentFont = $this->currentBaseFont;
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
+ }
+
+ return $this->currentFontNum;
+ }
+
+ /**
+ * sets up the current font, based on the font families, and the current text state
+ * note that this system is quite flexible, a bold-italic font can be completely different to a
+ * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
+ * This function is to be called whenever the currentTextState is changed, it will update
+ * the currentFont setting to whatever the appropriate family one is.
+ * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
+ * This function will change the currentFont to whatever it should be, but will not change the
+ * currentBaseFont.
+ */
+ private function setCurrentFont()
+ {
+ // if (strlen($this->currentBaseFont) == 0){
+ // // then assume an initial font
+ // $this->selectFont($this->defaultFont);
+ // }
+ // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
+ // if (strlen($this->currentTextState)
+ // && isset($this->fontFamilies[$cf])
+ // && isset($this->fontFamilies[$cf][$this->currentTextState])){
+ // // then we are in some state or another
+ // // and this font has a family, and the current setting exists within it
+ // // select the font, then return it
+ // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
+ // $this->selectFont($nf,'',0);
+ // $this->currentFont = $nf;
+ // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
+ // } else {
+ // // the this font must not have the right family member for the current state
+ // // simply assume the base font
+ $this->currentFont = $this->currentBaseFont;
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
+ // }
+ }
+
+ /**
+ * function for the user to find out what the ID is of the first page that was created during
+ * startup - useful if they wish to add something to it later.
+ *
+ * @return int
+ */
+ function getFirstPageId()
+ {
+ return $this->firstPageId;
+ }
+
+ /**
+ * add content to the currently active object
+ *
+ * @param $content
+ */
+ private function addContent($content)
+ {
+ $this->objects[$this->currentContents]['c'] .= $content;
+ }
+
+ /**
+ * sets the color for fill operations
+ *
+ * @param array $color
+ * @param bool $force
+ */
+ function setColor($color, $force = false)
+ {
+ $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
+
+ if (!$force && $this->currentColor == $new_color) {
+ return;
+ }
+
+ if (isset($new_color[3])) {
+ $this->currentColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
+ } else {
+ if (isset($new_color[2])) {
+ $this->currentColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
+ }
+ }
+ }
+
+ /**
+ * @param string $fillRule
+ */
+ function setFillRule($fillRule)
+ {
+ if (!in_array($fillRule, ["nonzero", "evenodd"])) {
+ return;
+ }
+
+ $this->fillRule = $fillRule;
+ }
+
+ /**
+ * sets the color for stroke operations
+ *
+ * @param array $color
+ * @param bool $force
+ */
+ function setStrokeColor($color, $force = false)
+ {
+ $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
+
+ if (!$force && $this->currentStrokeColor == $new_color) {
+ return;
+ }
+
+ if (isset($new_color[3])) {
+ $this->currentStrokeColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
+ } else {
+ if (isset($new_color[2])) {
+ $this->currentStrokeColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
+ }
+ }
+ }
+
+ /**
+ * Set the graphics state for compositions
+ *
+ * @param $parameters
+ */
+ function setGraphicsState($parameters)
+ {
+ // Create a new graphics state object if necessary
+ if (($gstate = array_search($parameters, $this->gstates)) === false) {
+ $this->numObj++;
+ $this->o_extGState($this->numObj, 'new', $parameters);
+ $gstate = $this->numStates;
+ $this->gstates[$gstate] = $parameters;
+ }
+ $this->addContent("\n/GS$gstate gs");
+ }
+
+ /**
+ * Set current blend mode & opacity for lines.
+ *
+ * Valid blend modes are:
+ *
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
+ * Exclusion
+ *
+ * @param string $mode the blend mode to use
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
+ */
+ function setLineTransparency($mode, $opacity)
+ {
+ static $blend_modes = [
+ "Normal",
+ "Multiply",
+ "Screen",
+ "Overlay",
+ "Darken",
+ "Lighten",
+ "ColorDogde",
+ "ColorBurn",
+ "HardLight",
+ "SoftLight",
+ "Difference",
+ "Exclusion"
+ ];
+
+ if (!in_array($mode, $blend_modes)) {
+ $mode = "Normal";
+ }
+
+ if (is_null($this->currentLineTransparency)) {
+ $this->currentLineTransparency = [];
+ }
+
+ if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
+ $this->currentLineTransparency['mode'] : '') &&
+ $opacity === (key_exists('opacity', $this->currentLineTransparency) ?
+ $this->currentLineTransparency["opacity"] : '')) {
+ return;
+ }
+
+ $this->currentLineTransparency["mode"] = $mode;
+ $this->currentLineTransparency["opacity"] = $opacity;
+
+ $options = [
+ "BM" => "/$mode",
+ "CA" => (float)$opacity
+ ];
+
+ $this->setGraphicsState($options);
+ }
+
+ /**
+ * Set current blend mode & opacity for filled objects.
+ *
+ * Valid blend modes are:
+ *
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
+ * Exclusion
+ *
+ * @param string $mode the blend mode to use
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
+ */
+ function setFillTransparency($mode, $opacity)
+ {
+ static $blend_modes = [
+ "Normal",
+ "Multiply",
+ "Screen",
+ "Overlay",
+ "Darken",
+ "Lighten",
+ "ColorDogde",
+ "ColorBurn",
+ "HardLight",
+ "SoftLight",
+ "Difference",
+ "Exclusion"
+ ];
+
+ if (!in_array($mode, $blend_modes)) {
+ $mode = "Normal";
+ }
+
+ if (is_null($this->currentFillTransparency)) {
+ $this->currentFillTransparency = [];
+ }
+
+ if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
+ $this->currentFillTransparency['mode'] : '') &&
+ $opacity === (key_exists('opacity', $this->currentFillTransparency) ?
+ $this->currentFillTransparency["opacity"] : '')) {
+ return;
+ }
+
+ $this->currentFillTransparency["mode"] = $mode;
+ $this->currentFillTransparency["opacity"] = $opacity;
+
+ $options = [
+ "BM" => "/$mode",
+ "ca" => (float)$opacity,
+ ];
+
+ $this->setGraphicsState($options);
+ }
+
+ /**
+ * draw a line from one set of coordinates to another
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $x2
+ * @param float $y2
+ * @param bool $stroke
+ */
+ function line($x1, $y1, $x2, $y2, $stroke = true)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
+
+ if ($stroke) {
+ $this->addContent(' S');
+ }
+ }
+
+ /**
+ * draw a bezier curve based on 4 control points
+ *
+ * @param float $x0
+ * @param float $y0
+ * @param float $x1
+ * @param float $y1
+ * @param float $x2
+ * @param float $y2
+ * @param float $x3
+ * @param float $y3
+ */
+ function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+ {
+ // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
+ // as the control points for the curve.
+ $this->addContent(
+ sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+ );
+ }
+
+ /**
+ * draw a part of an ellipse
+ *
+ * @param float $x0
+ * @param float $y0
+ * @param float $astart
+ * @param float $afinish
+ * @param float $r1
+ * @param float $r2
+ * @param float $angle
+ * @param int $nSeg
+ */
+ function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
+ {
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
+ }
+
+ /**
+ * draw a filled ellipse
+ *
+ * @param float $x0
+ * @param float $y0
+ * @param float $r1
+ * @param float $r2
+ * @param float $angle
+ * @param int $nSeg
+ * @param float $astart
+ * @param float $afinish
+ */
+ function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
+ {
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ */
+ function lineTo($x, $y)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ */
+ function moveTo($x, $y)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
+ }
+
+ /**
+ * draw a bezier curve based on 4 control points
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $x2
+ * @param float $y2
+ * @param float $x3
+ * @param float $y3
+ */
+ function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
+ }
+
+ /**
+ * draw a bezier curve based on 4 control points
+ *
+ * @param float $cpx
+ * @param float $cpy
+ * @param float $x
+ * @param float $y
+ */
+ function quadTo($cpx, $cpy, $x, $y)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
+ }
+
+ function closePath()
+ {
+ $this->addContent(' h');
+ }
+
+ function endPath()
+ {
+ $this->addContent(' n');
+ }
+
+ /**
+ * draw an ellipse
+ * note that the part and filled ellipse are just special cases of this function
+ *
+ * draws an ellipse in the current line style
+ * centered at $x0,$y0, radii $r1,$r2
+ * if $r2 is not set, then a circle is drawn
+ * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
+ * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
+ * pretty crappy shape at 2, as we are approximating with bezier curves.
+ *
+ * @param float $x0
+ * @param float $y0
+ * @param float $r1
+ * @param float $r2
+ * @param float $angle
+ * @param int $nSeg
+ * @param float $astart
+ * @param float $afinish
+ * @param bool $close
+ * @param bool $fill
+ * @param bool $stroke
+ * @param bool $incomplete
+ */
+ function ellipse(
+ $x0,
+ $y0,
+ $r1,
+ $r2 = 0,
+ $angle = 0,
+ $nSeg = 8,
+ $astart = 0,
+ $afinish = 360,
+ $close = true,
+ $fill = false,
+ $stroke = true,
+ $incomplete = false
+ ) {
+ if ($r1 == 0) {
+ return;
+ }
+
+ if ($r2 == 0) {
+ $r2 = $r1;
+ }
+
+ if ($nSeg < 2) {
+ $nSeg = 2;
+ }
+
+ $astart = deg2rad((float)$astart);
+ $afinish = deg2rad((float)$afinish);
+ $totalAngle = $afinish - $astart;
+
+ $dt = $totalAngle / $nSeg;
+ $dtm = $dt / 3;
+
+ if ($angle != 0) {
+ $a = -1 * deg2rad((float)$angle);
+
+ $this->addContent(
+ sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
+ );
+
+ $x0 = 0;
+ $y0 = 0;
+ }
+
+ $t1 = $astart;
+ $a0 = $x0 + $r1 * cos($t1);
+ $b0 = $y0 + $r2 * sin($t1);
+ $c0 = -$r1 * sin($t1);
+ $d0 = $r2 * cos($t1);
+
+ if (!$incomplete) {
+ $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
+ }
+
+ for ($i = 1; $i <= $nSeg; $i++) {
+ // draw this bit of the total curve
+ $t1 = $i * $dt + $astart;
+ $a1 = $x0 + $r1 * cos($t1);
+ $b1 = $y0 + $r2 * sin($t1);
+ $c1 = -$r1 * sin($t1);
+ $d1 = $r2 * cos($t1);
+
+ $this->addContent(
+ sprintf(
+ "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
+ ($a0 + $c0 * $dtm),
+ ($b0 + $d0 * $dtm),
+ ($a1 - $c1 * $dtm),
+ ($b1 - $d1 * $dtm),
+ $a1,
+ $b1
+ )
+ );
+
+ $a0 = $a1;
+ $b0 = $b1;
+ $c0 = $c1;
+ $d0 = $d1;
+ }
+
+ if (!$incomplete) {
+ if ($fill) {
+ $this->addContent(' f');
+ }
+
+ if ($stroke) {
+ if ($close) {
+ $this->addContent(' s'); // small 's' signifies closing the path as well
+ } else {
+ $this->addContent(' S');
+ }
+ }
+ }
+
+ if ($angle != 0) {
+ $this->addContent(' Q');
+ }
+ }
+
+ /**
+ * this sets the line drawing style.
+ * width, is the thickness of the line in user units
+ * cap is the type of cap to put on the line, values can be 'butt','round','square'
+ * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
+ * end of the line.
+ * join can be 'miter', 'round', 'bevel'
+ * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
+ * on and off dashes.
+ * (2) represents 2 on, 2 off, 2 on , 2 off ...
+ * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
+ * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
+ *
+ * @param float $width
+ * @param string $cap
+ * @param string $join
+ * @param array $dash
+ * @param int $phase
+ */
+ function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
+ {
+ // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
+ $string = '';
+
+ if ($width > 0) {
+ $string .= "$width w";
+ }
+
+ $ca = ['butt' => 0, 'round' => 1, 'square' => 2];
+
+ if (isset($ca[$cap])) {
+ $string .= " $ca[$cap] J";
+ }
+
+ $ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
+
+ if (isset($ja[$join])) {
+ $string .= " $ja[$join] j";
+ }
+
+ if (is_array($dash)) {
+ $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
+ }
+
+ $this->currentLineStyle = $string;
+ $this->addContent("\n$string");
+ }
+
+ /**
+ * draw a polygon, the syntax for this is similar to the GD polygon command
+ *
+ * @param float[] $p
+ * @param bool $fill
+ */
+ public function polygon(array $p, bool $fill = false): void
+ {
+ $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
+
+ $n = count($p);
+ for ($i = 2; $i < $n; $i = $i + 2) {
+ $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
+ }
+
+ if ($fill) {
+ $this->addContent(' f');
+ } else {
+ $this->addContent(' S');
+ }
+ }
+
+ /**
+ * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
+ * the coordinates of the upper-right corner
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $width
+ * @param float $height
+ */
+ function filledRectangle($x1, $y1, $width, $height)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
+ }
+
+ /**
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
+ * the coordinates of the upper-right corner
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $width
+ * @param float $height
+ */
+ function rectangle($x1, $y1, $width, $height)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
+ }
+
+ /**
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
+ * the coordinates of the upper-right corner
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $width
+ * @param float $height
+ */
+ function rect($x1, $y1, $width, $height)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
+ }
+
+ function stroke()
+ {
+ $this->addContent("\nS");
+ }
+
+ function fill()
+ {
+ $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
+ }
+
+ function fillStroke()
+ {
+ $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
+ }
+
+ /**
+ * @param string $subtype
+ * @param integer $x
+ * @param integer $y
+ * @param integer $w
+ * @param integer $h
+ * @return int
+ */
+ function addXObject($subtype, $x, $y, $w, $h)
+ {
+ $id = ++$this->numObj;
+ $this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
+ return $id;
+ }
+
+ /**
+ * @param integer $numXObject
+ * @param string $type
+ * @param array $options
+ */
+ function setXObjectResource($numXObject, $type, $options)
+ {
+ if (in_array($type, ['procset', 'font', 'xObject'])) {
+ $this->o_xobject($numXObject, $type, $options);
+ }
+ }
+
+ /**
+ * add signature
+ *
+ * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
+ *
+ * $signatureId = $cpdf->addSignature([
+ * 'signcert' => file_get_contents('dompdf.crt'),
+ * 'privkey' => file_get_contents('dompdf.key'),
+ * 'password' => 'password',
+ * 'name' => 'DomPDF DEMO',
+ * 'location' => 'Home',
+ * 'reason' => 'First Form',
+ * 'contactinfo' => 'info'
+ * ]);
+ * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
+ *
+ * @param string $signcert
+ * @param string $privkey
+ * @param string $password
+ * @param string|null $name
+ * @param string|null $location
+ * @param string|null $reason
+ * @param string|null $contactinfo
+ * @return int
+ */
+ function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
+ $sigId = ++$this->numObj;
+ $this->o_sig($sigId, 'new', [
+ 'SignCert' => $signcert,
+ 'PrivKey' => $privkey,
+ 'Password' => $password,
+ 'Name' => $name,
+ 'Location' => $location,
+ 'Reason' => $reason,
+ 'ContactInfo' => $contactinfo
+ ]);
+
+ return $sigId;
+ }
+
+ /**
+ * add field to form
+ *
+ * @param string $type ACROFORM_FIELD_*
+ * @param string $name
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ * @param integer $ff Field Flag ACROFORM_FIELD_*_*
+ * @param float $size
+ * @param array $color
+ * @return int
+ */
+ public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $color = implode(' ', $color) . ' rg';
+
+ $currentFontNum = $this->currentFontNum;
+ $font = array_filter(
+ $this->objects[$this->currentNode]['info']['fonts'],
+ function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
+ );
+
+ $this->o_acroform($this->acroFormId, 'font',
+ ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
+
+ $fieldId = ++$this->numObj;
+ $this->o_field($fieldId, 'new', [
+ 'rect' => [$x0, $y0, $x1, $y1],
+ 'F' => 4,
+ 'FT' => "/$type",
+ 'T' => $name,
+ 'Ff' => $ff,
+ 'pageid' => $this->currentPage,
+ 'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
+ ]);
+
+ return $fieldId;
+ }
+
+ /**
+ * set Field value
+ *
+ * @param integer $numFieldObj
+ * @param string $value
+ */
+ public function setFormFieldValue($numFieldObj, $value)
+ {
+ $this->o_field($numFieldObj, 'set', ['value' => $value]);
+ }
+
+ /**
+ * set Field value (reference)
+ *
+ * @param integer $numFieldObj
+ * @param integer $numObj Object number
+ */
+ public function setFormFieldRefValue($numFieldObj, $numObj)
+ {
+ $this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
+ }
+
+ /**
+ * set Field Appearanc (reference)
+ *
+ * @param integer $numFieldObj
+ * @param integer $normalNumObj
+ * @param integer|null $rolloverNumObj
+ * @param integer|null $downNumObj
+ */
+ public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
+ {
+ $appearance['N'] = $normalNumObj;
+
+ if ($rolloverNumObj !== null) {
+ $appearance['R'] = $rolloverNumObj;
+ }
+
+ if ($downNumObj !== null) {
+ $appearance['D'] = $downNumObj;
+ }
+
+ $this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
+ }
+
+ /**
+ * set Choice Field option values
+ *
+ * @param integer $numFieldObj
+ * @param array $value
+ */
+ public function setFormFieldOpt($numFieldObj, $value)
+ {
+ $this->o_field($numFieldObj, 'set', ['options' => $value]);
+ }
+
+ /**
+ * add form to document
+ *
+ * @param integer $sigFlags
+ * @param boolean $needAppearances
+ */
+ public function addForm($sigFlags = 0, $needAppearances = false)
+ {
+ $this->acroFormId = ++$this->numObj;
+ $this->o_acroform($this->acroFormId, 'new', [
+ 'NeedAppearances' => $needAppearances ? 'true' : 'false',
+ 'SigFlags' => $sigFlags
+ ]);
+ }
+
+ /**
+ * save the current graphic state
+ */
+ function save()
+ {
+ // we must reset the color cache or it will keep bad colors after clipping
+ $this->currentColor = null;
+ $this->currentStrokeColor = null;
+ $this->addContent("\nq");
+ }
+
+ /**
+ * restore the last graphic state
+ */
+ function restore()
+ {
+ // we must reset the color cache or it will keep bad colors after clipping
+ $this->currentColor = null;
+ $this->currentStrokeColor = null;
+ $this->addContent("\nQ");
+ }
+
+ /**
+ * draw a clipping rectangle, all the elements added after this will be clipped
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $width
+ * @param float $height
+ */
+ function clippingRectangle($x1, $y1, $width, $height)
+ {
+ $this->save();
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
+ }
+
+ /**
+ * draw a clipping rounded rectangle, all the elements added after this will be clipped
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $w
+ * @param float $h
+ * @param float $rTL
+ * @param float $rTR
+ * @param float $rBR
+ * @param float $rBL
+ */
+ function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
+ {
+ $this->save();
+
+ // start: top edge, left end
+ $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
+
+ // line: bottom edge, left end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
+
+ // curve: bottom-left corner
+ $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
+
+ // line: right edge, bottom end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
+
+ // curve: bottom-right corner
+ $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
+
+ // line: right edge, top end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
+
+ // curve: bottom-right corner
+ $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
+
+ // line: bottom edge, right end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
+
+ // curve: top-right corner
+ $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
+
+ // line: top edge, left end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
+
+ // Close & clip
+ $this->addContent(" W n");
+ }
+
+ /**
+ * draw a clipping polygon, the syntax for this is similar to the GD polygon command
+ *
+ * @param float[] $p
+ */
+ public function clippingPolygon(array $p): void
+ {
+ $this->save();
+
+ $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
+
+ $n = count($p);
+ for ($i = 2; $i < $n; $i = $i + 2) {
+ $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
+ }
+
+ $this->addContent("W n");
+ }
+
+ /**
+ * ends the last clipping shape
+ */
+ function clippingEnd()
+ {
+ $this->restore();
+ }
+
+ /**
+ * scale
+ *
+ * @param float $s_x scaling factor for width as percent
+ * @param float $s_y scaling factor for height as percent
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function scale($s_x, $s_y, $x, $y)
+ {
+ $y = $this->currentPageSize["height"] - $y;
+
+ $tm = [
+ $s_x,
+ 0,
+ 0,
+ $s_y,
+ $x * (1 - $s_x),
+ $y * (1 - $s_y)
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * translate
+ *
+ * @param float $t_x movement to the right
+ * @param float $t_y movement to the bottom
+ */
+ function translate($t_x, $t_y)
+ {
+ $tm = [
+ 1,
+ 0,
+ 0,
+ 1,
+ $t_x,
+ -$t_y
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * rotate
+ *
+ * @param float $angle angle in degrees for counter-clockwise rotation
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function rotate($angle, $x, $y)
+ {
+ $y = $this->currentPageSize["height"] - $y;
+
+ $a = deg2rad($angle);
+ $cos_a = cos($a);
+ $sin_a = sin($a);
+
+ $tm = [
+ $cos_a,
+ -$sin_a,
+ $sin_a,
+ $cos_a,
+ $x - $sin_a * $y - $cos_a * $x,
+ $y - $cos_a * $y + $sin_a * $x,
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * skew
+ *
+ * @param float $angle_x
+ * @param float $angle_y
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function skew($angle_x, $angle_y, $x, $y)
+ {
+ $y = $this->currentPageSize["height"] - $y;
+
+ $tan_x = tan(deg2rad($angle_x));
+ $tan_y = tan(deg2rad($angle_y));
+
+ $tm = [
+ 1,
+ -$tan_y,
+ -$tan_x,
+ 1,
+ $tan_x * $y,
+ $tan_y * $x,
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * apply graphic transformations
+ *
+ * @param array $tm transformation matrix
+ */
+ function transform($tm)
+ {
+ $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
+ }
+
+ /**
+ * add a new page to the document
+ * this also makes the new page the current active object
+ *
+ * @param int $insert
+ * @param int $id
+ * @param string $pos
+ * @return int
+ */
+ function newPage($insert = 0, $id = 0, $pos = 'after')
+ {
+ // if there is a state saved, then go up the stack closing them
+ // then on the new page, re-open them with the right setings
+
+ if ($this->nStateStack) {
+ for ($i = $this->nStateStack; $i >= 1; $i--) {
+ $this->restoreState($i);
+ }
+ }
+
+ $this->numObj++;
+
+ if ($insert) {
+ // the id from the ezPdf class is the id of the contents of the page, not the page object itself
+ // query that object to find the parent
+ $rid = $this->objects[$id]['onPage'];
+ $opt = ['rid' => $rid, 'pos' => $pos];
+ $this->o_page($this->numObj, 'new', $opt);
+ } else {
+ $this->o_page($this->numObj, 'new');
+ }
+
+ // if there is a stack saved, then put that onto the page
+ if ($this->nStateStack) {
+ for ($i = 1; $i <= $this->nStateStack; $i++) {
+ $this->saveState($i);
+ }
+ }
+
+ // and if there has been a stroke or fill color set, then transfer them
+ if (isset($this->currentColor)) {
+ $this->setColor($this->currentColor, true);
+ }
+
+ if (isset($this->currentStrokeColor)) {
+ $this->setStrokeColor($this->currentStrokeColor, true);
+ }
+
+ // if there is a line style set, then put this in too
+ if (mb_strlen($this->currentLineStyle, '8bit')) {
+ $this->addContent("\n$this->currentLineStyle");
+ }
+
+ // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
+ return $this->currentContents;
+ }
+
+ /**
+ * Streams the PDF to the client.
+ *
+ * @param string $filename The filename to present to the client.
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
+ */
+ function stream($filename = "document.pdf", $options = [])
+ {
+ if (headers_sent()) {
+ die("Unable to stream pdf: headers already sent");
+ }
+
+ if (!isset($options["compress"])) $options["compress"] = true;
+ if (!isset($options["Attachment"])) $options["Attachment"] = true;
+
+ $debug = !$options['compress'];
+ $tmp = ltrim($this->output($debug));
+
+ header("Cache-Control: private");
+ header("Content-Type: application/pdf");
+ header("Content-Length: " . mb_strlen($tmp, "8bit"));
+
+ $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
+
+ $encoding = mb_detect_encoding($filename);
+ $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
+ $fallbackfilename = str_replace("\"", "", $fallbackfilename);
+ $encodedfilename = rawurlencode($filename);
+
+ $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
+ if ($fallbackfilename !== $filename) {
+ $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
+ }
+ header($contentDisposition);
+
+ echo $tmp;
+ flush();
+ }
+
+ /**
+ * return the height in units of the current font in the given size
+ *
+ * @param float $size
+ *
+ * @return float
+ */
+ public function getFontHeight(float $size): float
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $font = $this->fonts[$this->currentFont];
+
+ // for the current font, and the given size, what is the height of the font in user units
+ if (isset($font['Ascender']) && isset($font['Descender'])) {
+ $h = $font['Ascender'] - $font['Descender'];
+ } else {
+ $h = $font['FontBBox'][3] - $font['FontBBox'][1];
+ }
+
+ // have to adjust by a font offset for Windows fonts. unfortunately it looks like
+ // the bounding box calculations are wrong and I don't know why.
+ if (isset($font['FontHeightOffset'])) {
+ // For CourierNew from Windows this needs to be -646 to match the
+ // Adobe native Courier font.
+ //
+ // For FreeMono from GNU this needs to be -337 to match the
+ // Courier font.
+ //
+ // Both have been added manually to the .afm and .ufm files.
+ $h += (int)$font['FontHeightOffset'];
+ }
+
+ return $size * $h / 1000;
+ }
+
+ /**
+ * @param float $size
+ *
+ * @return float
+ */
+ public function getFontXHeight(float $size): float
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $font = $this->fonts[$this->currentFont];
+
+ // for the current font, and the given size, what is the height of the font in user units
+ if (isset($font['XHeight'])) {
+ $xh = $font['Ascender'] - $font['Descender'];
+ } else {
+ $xh = $this->getFontHeight($size) / 2;
+ }
+
+ return $size * $xh / 1000;
+ }
+
+ /**
+ * return the font descender, this will normally return a negative number
+ * if you add this number to the baseline, you get the level of the bottom of the font
+ * it is in the pdf user units
+ *
+ * @param float $size
+ *
+ * @return float
+ */
+ public function getFontDescender(float $size): float
+ {
+ // note that this will most likely return a negative value
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
+ $h = $this->fonts[$this->currentFont]['Descender'];
+
+ return $size * $h / 1000;
+ }
+
+ /**
+ * filter the text, this is applied to all text just before being inserted into the pdf document
+ * it escapes the various things that need to be escaped, and so on
+ *
+ * @param $text
+ * @param bool $bom
+ * @param bool $convert_encoding
+ * @return string
+ */
+ function filterText($text, $bom = true, $convert_encoding = true)
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ if ($convert_encoding) {
+ $cf = $this->currentFont;
+ if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
+ $text = $this->utf8toUtf16BE($text, $bom);
+ } else {
+ //$text = html_entity_decode($text, ENT_QUOTES);
+ $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
+ }
+ } elseif ($bom) {
+ $text = $this->utf8toUtf16BE($text, $bom);
+ }
+
+ // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
+ return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
+ }
+
+ /**
+ * return array containing codepoints (UTF-8 character values) for the
+ * string passed in.
+ *
+ * based on the excellent TCPDF code by Nicola Asuni and the
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
+ *
+ * @param string $text UTF-8 string to process
+ * @return array UTF-8 codepoints array for the string
+ */
+ function utf8toCodePointsArray(&$text)
+ {
+ $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
+ $unicode = []; // array containing unicode values
+ $bytes = []; // array containing single character byte sequences
+ $numbytes = 1; // number of octets needed to represent the UTF-8 character
+
+ for ($i = 0; $i < $length; $i++) {
+ $c = ord($text[$i]); // get one string character at time
+ if (count($bytes) === 0) { // get starting octect
+ if ($c <= 0x7F) {
+ $unicode[] = $c; // use the character "as is" because is ASCII
+ $numbytes = 1;
+ } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
+ $bytes[] = ($c - 0xC0) << 0x06;
+ $numbytes = 2;
+ } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
+ $bytes[] = ($c - 0xE0) << 0x0C;
+ $numbytes = 3;
+ } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
+ $bytes[] = ($c - 0xF0) << 0x12;
+ $numbytes = 4;
+ } else {
+ // use replacement character for other invalid sequences
+ $unicode[] = 0xFFFD;
+ $bytes = [];
+ $numbytes = 1;
+ }
+ } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
+ $bytes[] = $c - 0x80;
+ if (count($bytes) === $numbytes) {
+ // compose UTF-8 bytes to a single unicode value
+ $c = $bytes[0];
+ for ($j = 1; $j < $numbytes; $j++) {
+ $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
+ }
+ if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
+ // The definition of UTF-8 prohibits encoding character numbers between
+ // U+D800 and U+DFFF, which are reserved for use with the UTF-16
+ // encoding form (as surrogate pairs) and do not directly represent
+ // characters.
+ $unicode[] = 0xFFFD; // use replacement character
+ } else {
+ $unicode[] = $c; // add char to array
+ }
+ // reset data for next char
+ $bytes = [];
+ $numbytes = 1;
+ }
+ } else {
+ // use replacement character for other invalid sequences
+ $unicode[] = 0xFFFD;
+ $bytes = [];
+ $numbytes = 1;
+ }
+ }
+
+ return $unicode;
+ }
+
+ /**
+ * convert UTF-8 to UTF-16 with an additional byte order marker
+ * at the front if required.
+ *
+ * based on the excellent TCPDF code by Nicola Asuni and the
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
+ *
+ * @param string $text UTF-8 string to process
+ * @param boolean $bom whether to add the byte order marker
+ * @return string UTF-16 result string
+ */
+ function utf8toUtf16BE(&$text, $bom = true)
+ {
+ $out = $bom ? "\xFE\xFF" : '';
+
+ $unicode = $this->utf8toCodePointsArray($text);
+ foreach ($unicode as $c) {
+ if ($c === 0xFFFD) {
+ $out .= "\xFF\xFD"; // replacement character
+ } elseif ($c < 0x10000) {
+ $out .= chr($c >> 0x08) . chr($c & 0xFF);
+ } else {
+ $c -= 0x10000;
+ $w1 = 0xD800 | ($c >> 0x10);
+ $w2 = 0xDC00 | ($c & 0x3FF);
+ $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * given a start position and information about how text is to be laid out, calculate where
+ * on the page the text will end
+ *
+ * @param $x
+ * @param $y
+ * @param $angle
+ * @param $size
+ * @param $wa
+ * @param $text
+ * @return array
+ */
+ private function getTextPosition($x, $y, $angle, $size, $wa, $text)
+ {
+ // given this information return an array containing x and y for the end position as elements 0 and 1
+ $w = $this->getTextWidth($size, $text);
+
+ // need to adjust for the number of spaces in this text
+ $words = explode(' ', $text);
+ $nspaces = count($words) - 1;
+ $w += $wa * $nspaces;
+ $a = deg2rad((float)$angle);
+
+ return [cos($a) * $w + $x, -sin($a) * $w + $y];
+ }
+
+ /**
+ * Callback method used by smallCaps
+ *
+ * @param array $matches
+ *
+ * @return string
+ */
+ function toUpper($matches)
+ {
+ return mb_strtoupper($matches[0]);
+ }
+
+ function concatMatches($matches)
+ {
+ $str = "";
+ foreach ($matches as $match) {
+ $str .= $match[0];
+ }
+
+ return $str;
+ }
+
+ /**
+ * register text for font subsetting
+ *
+ * @param string $font
+ * @param string $text
+ */
+ function registerText($font, $text)
+ {
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
+ return;
+ }
+
+ if (!isset($this->stringSubsets[$font])) {
+ $base_subset = "\u{fffd}\u{fffe}\u{ffff}";
+ $this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
+ }
+
+ $this->stringSubsets[$font] = array_unique(
+ array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
+ );
+ }
+
+ /**
+ * add text to the document, at a specified location, size and angle on the page
+ *
+ * @param float $x
+ * @param float $y
+ * @param float $size
+ * @param string $text
+ * @param float $angle
+ * @param float $wordSpaceAdjust
+ * @param float $charSpaceAdjust
+ * @param bool $smallCaps
+ */
+ function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $text = str_replace(["\r", "\n"], "", $text);
+
+ // if ($smallCaps) {
+ // preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
+ // $lower = $this->concatMatches($matches);
+ // d($lower);
+
+ // preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
+ // $other = $this->concatMatches($matches);
+ // d($other);
+
+ // $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
+ // }
+
+ // if there are any open callbacks, then they should be called, to show the start of the line
+ if ($this->nCallback > 0) {
+ for ($i = $this->nCallback; $i > 0; $i--) {
+ // call each function
+ $info = [
+ 'x' => $x,
+ 'y' => $y,
+ 'angle' => $angle,
+ 'status' => 'sol',
+ 'p' => $this->callback[$i]['p'],
+ 'nCallback' => $this->callback[$i]['nCallback'],
+ 'height' => $this->callback[$i]['height'],
+ 'descender' => $this->callback[$i]['descender']
+ ];
+
+ $func = $this->callback[$i]['f'];
+ $this->$func($info);
+ }
+ }
+
+ if ($angle == 0) {
+ $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
+ } else {
+ $a = deg2rad((float)$angle);
+ $this->addContent(
+ sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
+ );
+ }
+
+ if ($wordSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
+ }
+
+ if ($charSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
+ }
+
+ $len = mb_strlen($text);
+ $start = 0;
+
+ if ($start < $len) {
+ $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start);
+ $place_text = $this->filterText($part, false);
+ // modify unicode text so that extra word spacing is manually implemented (bug #)
+ if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
+ $space_scale = 1000 / $size;
+ $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
+ }
+ $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
+ $this->addContent(" [($place_text)] TJ");
+ }
+
+ if ($wordSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tw", 0));
+ }
+
+ if ($charSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tc", 0));
+ }
+
+ $this->addContent(' ET');
+
+ // if there are any open callbacks, then they should be called, to show the end of the line
+ if ($this->nCallback > 0) {
+ for ($i = $this->nCallback; $i > 0; $i--) {
+ // call each function
+ $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
+ $info = [
+ 'x' => $tmp[0],
+ 'y' => $tmp[1],
+ 'angle' => $angle,
+ 'status' => 'eol',
+ 'p' => $this->callback[$i]['p'],
+ 'nCallback' => $this->callback[$i]['nCallback'],
+ 'height' => $this->callback[$i]['height'],
+ 'descender' => $this->callback[$i]['descender']
+ ];
+ $func = $this->callback[$i]['f'];
+ $this->$func($info);
+ }
+ }
+
+ if ($this->fonts[$this->currentFont]['isSubsetting']) {
+ $this->registerText($this->currentFont, $text);
+ }
+ }
+
+ /**
+ * calculate how wide a given text string will be on a page, at a given size.
+ * this can be called externally, but is also used by the other class functions
+ *
+ * @param float $size
+ * @param string $text
+ * @param float $wordSpacing
+ * @param float $charSpacing
+ *
+ * @return float
+ */
+ public function getTextWidth(float $size, string $text, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
+ {
+ static $ord_cache = [];
+
+ // this function should not change any of the settings, though it will need to
+ // track any directives which change during calculation, so copy them at the start
+ // and put them back at the end.
+ $store_currentTextState = $this->currentTextState;
+
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $text = str_replace(["\r", "\n"], "", $text);
+
+ // hmm, this is where it all starts to get tricky - use the font information to
+ // calculate the width of each character, add them up and convert to user units
+ $w = 0;
+ $cf = $this->currentFont;
+ $current_font = $this->fonts[$cf];
+ $space_scale = 1000 / ($size > 0 ? $size : 1);
+
+ if ($current_font['isUnicode']) {
+ // for Unicode, use the code points array to calculate width rather
+ // than just the string itself
+ $unicode = $this->utf8toCodePointsArray($text);
+
+ foreach ($unicode as $char) {
+ // check if we have to replace character
+ if (isset($current_font['differences'][$char])) {
+ $char = $current_font['differences'][$char];
+ }
+
+ if (isset($current_font['C'][$char])) {
+ $char_width = $current_font['C'][$char];
+
+ // add the character width
+ $w += $char_width;
+
+ // add additional padding for space
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
+ $w += $wordSpacing * $space_scale;
+ }
+ }
+ }
+
+ // add additional char spacing
+ if ($charSpacing != 0) {
+ $w += $charSpacing * $space_scale * count($unicode);
+ }
+
+ } else {
+ // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
+ if ($this->isUnicode) {
+ $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
+ }
+
+ $len = mb_strlen($text, 'Windows-1252');
+
+ for ($i = 0; $i < $len; $i++) {
+ $c = $text[$i];
+ $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
+
+ // check if we have to replace character
+ if (isset($current_font['differences'][$char])) {
+ $char = $current_font['differences'][$char];
+ }
+
+ if (isset($current_font['C'][$char])) {
+ $char_width = $current_font['C'][$char];
+
+ // add the character width
+ $w += $char_width;
+
+ // add additional padding for space
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
+ $w += $wordSpacing * $space_scale;
+ }
+ }
+ }
+
+ // add additional char spacing
+ if ($charSpacing != 0) {
+ $w += $charSpacing * $space_scale * $len;
+ }
+ }
+
+ $this->currentTextState = $store_currentTextState;
+ $this->setCurrentFont();
+
+ return $w * $size / 1000;
+ }
+
+ /**
+ * this will be called at a new page to return the state to what it was on the
+ * end of the previous page, before the stack was closed down
+ * This is to get around not being able to have open 'q' across pages
+ *
+ * @param int $pageEnd
+ */
+ function saveState($pageEnd = 0)
+ {
+ if ($pageEnd) {
+ // this will be called at a new page to return the state to what it was on the
+ // end of the previous page, before the stack was closed down
+ // This is to get around not being able to have open 'q' across pages
+ $opt = $this->stateStack[$pageEnd];
+ // ok to use this as stack starts numbering at 1
+ $this->setColor($opt['col'], true);
+ $this->setStrokeColor($opt['str'], true);
+ $this->addContent("\n" . $opt['lin']);
+ // $this->currentLineStyle = $opt['lin'];
+ } else {
+ $this->nStateStack++;
+ $this->stateStack[$this->nStateStack] = [
+ 'col' => $this->currentColor,
+ 'str' => $this->currentStrokeColor,
+ 'lin' => $this->currentLineStyle
+ ];
+ }
+
+ $this->save();
+ }
+
+ /**
+ * restore a previously saved state
+ *
+ * @param int $pageEnd
+ */
+ function restoreState($pageEnd = 0)
+ {
+ if (!$pageEnd) {
+ $n = $this->nStateStack;
+ $this->currentColor = $this->stateStack[$n]['col'];
+ $this->currentStrokeColor = $this->stateStack[$n]['str'];
+ $this->addContent("\n" . $this->stateStack[$n]['lin']);
+ $this->currentLineStyle = $this->stateStack[$n]['lin'];
+ $this->stateStack[$n] = null;
+ unset($this->stateStack[$n]);
+ $this->nStateStack--;
+ }
+
+ $this->restore();
+ }
+
+ /**
+ * make a loose object, the output will go into this object, until it is closed, then will revert to
+ * the current one.
+ * this object will not appear until it is included within a page.
+ * the function will return the object number
+ *
+ * @return int
+ */
+ function openObject()
+ {
+ $this->nStack++;
+ $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
+ // add a new object of the content type, to hold the data flow
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new');
+ $this->currentContents = $this->numObj;
+ $this->looseObjects[$this->numObj] = 1;
+
+ return $this->numObj;
+ }
+
+ /**
+ * open an existing object for editing
+ *
+ * @param $id
+ */
+ function reopenObject($id)
+ {
+ $this->nStack++;
+ $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
+ $this->currentContents = $id;
+
+ // also if this object is the primary contents for a page, then set the current page to its parent
+ if (isset($this->objects[$id]['onPage'])) {
+ $this->currentPage = $this->objects[$id]['onPage'];
+ }
+ }
+
+ /**
+ * close an object
+ */
+ function closeObject()
+ {
+ // close the object, as long as there was one open in the first place, which will be indicated by
+ // an objectId on the stack.
+ if ($this->nStack > 0) {
+ $this->currentContents = $this->stack[$this->nStack]['c'];
+ $this->currentPage = $this->stack[$this->nStack]['p'];
+ $this->nStack--;
+ // easier to probably not worry about removing the old entries, they will be overwritten
+ // if there are new ones.
+ }
+ }
+
+ /**
+ * stop an object from appearing on pages from this point on
+ *
+ * @param $id
+ */
+ function stopObject($id)
+ {
+ // if an object has been appearing on pages up to now, then stop it, this page will
+ // be the last one that could contain it.
+ if (isset($this->addLooseObjects[$id])) {
+ $this->addLooseObjects[$id] = '';
+ }
+ }
+
+ /**
+ * after an object has been created, it wil only show if it has been added, using this function.
+ *
+ * @param $id
+ * @param string $options
+ */
+ function addObject($id, $options = 'add')
+ {
+ // add the specified object to the page
+ if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
+ // then it is a valid object, and it is not being added to itself
+ switch ($options) {
+ case 'all':
+ // then this object is to be added to this page (done in the next block) and
+ // all future new pages.
+ $this->addLooseObjects[$id] = 'all';
+
+ case 'add':
+ if (isset($this->objects[$this->currentContents]['onPage'])) {
+ // then the destination contents is the primary for the page
+ // (though this object is actually added to that page)
+ $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
+ }
+ break;
+
+ case 'even':
+ $this->addLooseObjects[$id] = 'even';
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
+ $this->addObject($id);
+ // hacky huh :)
+ }
+ break;
+
+ case 'odd':
+ $this->addLooseObjects[$id] = 'odd';
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
+ $this->addObject($id);
+ // hacky huh :)
+ }
+ break;
+
+ case 'next':
+ $this->addLooseObjects[$id] = 'all';
+ break;
+
+ case 'nexteven':
+ $this->addLooseObjects[$id] = 'even';
+ break;
+
+ case 'nextodd':
+ $this->addLooseObjects[$id] = 'odd';
+ break;
+ }
+ }
+ }
+
+ /**
+ * return a storable representation of a specific object
+ *
+ * @param $id
+ * @return string|null
+ */
+ function serializeObject($id)
+ {
+ if (array_key_exists($id, $this->objects)) {
+ return serialize($this->objects[$id]);
+ }
+
+ return null;
+ }
+
+ /**
+ * restore an object from its stored representation. Returns its new object id.
+ *
+ * @param $obj
+ * @return int
+ */
+ function restoreSerializedObject($obj)
+ {
+ $obj_id = $this->openObject();
+ $this->objects[$obj_id] = unserialize($obj);
+ $this->closeObject();
+
+ return $obj_id;
+ }
+
+ /**
+ * Embeds a file inside the PDF
+ *
+ * @param string $filepath path to the file to store inside the PDF
+ * @param string $embeddedFilename the filename displayed in the list of embedded files
+ * @param string $description a description in the list of embedded files
+ */
+ public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
+ {
+ $this->numObj++;
+ $this->o_embedded_file_dictionary(
+ $this->numObj,
+ 'new',
+ [
+ 'filepath' => $filepath,
+ 'filename' => $embeddedFilename,
+ 'description' => $description
+ ]
+ );
+ }
+
+ /**
+ * Add content to the documents info object
+ *
+ * @param string|array $label
+ * @param string $value
+ */
+ public function addInfo($label, string $value = ""): void
+ {
+ // this will only work if the label is one of the valid ones.
+ // modify this so that arrays can be passed as well.
+ // if $label is an array then assume that it is key => value pairs
+ // else assume that they are both scalar, anything else will probably error
+ if (is_array($label)) {
+ foreach ($label as $l => $v) {
+ $this->o_info($this->infoObject, $l, (string) $v);
+ }
+ } else {
+ $this->o_info($this->infoObject, $label, $value);
+ }
+ }
+
+ /**
+ * set the viewer preferences of the document, it is up to the browser to obey these.
+ *
+ * @param $label
+ * @param int $value
+ */
+ function setPreferences($label, $value = 0)
+ {
+ // this will only work if the label is one of the valid ones.
+ if (is_array($label)) {
+ foreach ($label as $l => $v) {
+ $this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
+ }
+ } else {
+ $this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
+ }
+ }
+
+ /**
+ * extract an integer from a position in a byte stream
+ *
+ * @param $data
+ * @param $pos
+ * @param $num
+ * @return int
+ */
+ private function getBytes(&$data, $pos, $num)
+ {
+ // return the integer represented by $num bytes from $pos within $data
+ $ret = 0;
+ for ($i = 0; $i < $num; $i++) {
+ $ret *= 256;
+ $ret += ord($data[$pos + $i]);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Check if image already added to pdf image directory.
+ * If yes, need not to create again (pass empty data)
+ *
+ * @param string $imgname
+ * @return bool
+ */
+ function image_iscached($imgname)
+ {
+ return isset($this->imagelist[$imgname]);
+ }
+
+ /**
+ * add a PNG image into the document, from a GD object
+ * this should work with remote files
+ *
+ * @param \GdImage|resource $img A GD resource
+ * @param string $file The PNG file
+ * @param float $x X position
+ * @param float $y Y position
+ * @param float $w Width
+ * @param float $h Height
+ * @param bool $is_mask true if the image is a mask
+ * @param bool $mask true if the image is masked
+ * @throws Exception
+ */
+ function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
+ {
+ if (!function_exists("imagepng")) {
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
+ }
+
+ //if already cached, need not to read again
+ if (isset($this->imagelist[$file])) {
+ $data = null;
+ } else {
+ // Example for transparency handling on new image. Retain for current image
+ // $tIndex = imagecolortransparent($img);
+ // if ($tIndex > 0) {
+ // $tColor = imagecolorsforindex($img, $tIndex);
+ // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
+ // imagefill($new_img, 0, 0, $new_tIndex);
+ // imagecolortransparent($new_img, $new_tIndex);
+ // }
+ // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
+ //imagealphablending($img, true);
+
+ //default, but explicitely set to ensure pdf compatibility
+ imagesavealpha($img, false/*!$is_mask && !$mask*/);
+
+ $error = 0;
+ //DEBUG_IMG_TEMP
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addImagePng ' . $file . ']';
+ }
+
+ ob_start();
+ @imagepng($img);
+ $data = ob_get_clean();
+
+ if ($data == '') {
+ $error = 1;
+ $errormsg = 'trouble writing file from GD';
+ //DEBUG_IMG_TEMP
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print 'trouble writing file from GD';
+ }
+ }
+
+ if ($error) {
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
+
+ return;
+ }
+ } //End isset($this->imagelist[$file]) (png Duplicate removal)
+
+ $this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
+ }
+
+ /**
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param $w
+ * @param $h
+ * @param $byte
+ */
+ protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
+ {
+ // generate images
+ $img = @imagecreatefrompng($file);
+
+ if ($img === false) {
+ return;
+ }
+
+ // FIXME The pixel transformation doesn't work well with 8bit PNGs
+ $eight_bit = ($byte & 4) !== 4;
+
+ $wpx = imagesx($img);
+ $hpx = imagesy($img);
+
+ imagesavealpha($img, false);
+
+ // create temp alpha file
+ $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
+ @unlink($tempfile_alpha);
+ $tempfile_alpha = "$tempfile_alpha.png";
+
+ // create temp plain file
+ $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
+ @unlink($tempfile_plain);
+ $tempfile_plain = "$tempfile_plain.png";
+
+ $imgalpha = imagecreate($wpx, $hpx);
+ imagesavealpha($imgalpha, false);
+
+ // generate gray scale palette (0 -> 255)
+ for ($c = 0; $c < 256; ++$c) {
+ imagecolorallocate($imgalpha, $c, $c, $c);
+ }
+
+ // Use PECL gmagick + Graphics Magic to process transparent PNG images
+ if (extension_loaded("gmagick")) {
+ $gmagick = new \Gmagick($file);
+ $gmagick->setimageformat('png');
+
+ // Get opacity channel (negative of alpha channel)
+ $alpha_channel_neg = clone $gmagick;
+ $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
+
+ // Negate opacity channel
+ $alpha_channel = new \Gmagick();
+ $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
+ $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
+ $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
+ $alpha_channel->writeimage($tempfile_alpha);
+
+ // Cast to 8bit+palette
+ $imgalpha_ = @imagecreatefrompng($tempfile_alpha);
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
+ imagedestroy($imgalpha_);
+ imagepng($imgalpha, $tempfile_alpha);
+
+ // Make opaque image
+ $color_channels = new \Gmagick();
+ $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
+ $color_channels->writeimage($tempfile_plain);
+
+ $imgplain = @imagecreatefrompng($tempfile_plain);
+ }
+ // Use PECL imagick + ImageMagic to process transparent PNG images
+ elseif (extension_loaded("imagick")) {
+ // Native cloning was added to pecl-imagick in svn commit 263814
+ // the first version containing it was 3.0.1RC1
+ static $imagickClonable = null;
+ if ($imagickClonable === null) {
+ $imagickClonable = true;
+ if (defined('Imagick::IMAGICK_EXTVER')) {
+ $imagickVersion = \Imagick::IMAGICK_EXTVER;
+ } else {
+ $imagickVersion = '0';
+ }
+ if (version_compare($imagickVersion, '0.0.1', '>=')) {
+ $imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
+ }
+ }
+
+ $imagick = new \Imagick($file);
+ $imagick->setFormat('png');
+
+ // Get opacity channel (negative of alpha channel)
+ if ($imagick->getImageAlphaChannel()) {
+ $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
+ $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
+ // Since ImageMagick7 negate invert transparency as default
+ if (\Imagick::getVersion()['versionNumber'] < 1800) {
+ $alpha_channel->negateImage(true);
+ }
+ $alpha_channel->writeImage($tempfile_alpha);
+
+ // Cast to 8bit+palette
+ $imgalpha_ = @imagecreatefrompng($tempfile_alpha);
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
+ imagedestroy($imgalpha_);
+ imagepng($imgalpha, $tempfile_alpha);
+ } else {
+ $tempfile_alpha = null;
+ }
+
+ // Make opaque image
+ $color_channels = new \Imagick();
+ $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
+ $color_channels->writeImage($tempfile_plain);
+
+ $imgplain = @imagecreatefrompng($tempfile_plain);
+ } else {
+ // allocated colors cache
+ $allocated_colors = [];
+
+ // extract alpha channel
+ for ($xpx = 0; $xpx < $wpx; ++$xpx) {
+ for ($ypx = 0; $ypx < $hpx; ++$ypx) {
+ $color = imagecolorat($img, $xpx, $ypx);
+ $col = imagecolorsforindex($img, $color);
+ $alpha = $col['alpha'];
+
+ if ($eight_bit) {
+ // with gamma correction
+ $gammacorr = 2.2;
+ $pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
+ } else {
+ // without gamma correction
+ $pixel = (127 - $alpha) * 2;
+
+ $key = $col['red'] . $col['green'] . $col['blue'];
+
+ if (!isset($allocated_colors[$key])) {
+ $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
+ $allocated_colors[$key] = $pixel_img;
+ } else {
+ $pixel_img = $allocated_colors[$key];
+ }
+
+ imagesetpixel($img, $xpx, $ypx, $pixel_img);
+ }
+
+ imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
+ }
+ }
+
+ // extract image without alpha channel
+ $imgplain = imagecreatetruecolor($wpx, $hpx);
+ imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
+ imagedestroy($img);
+
+ imagepng($imgalpha, $tempfile_alpha);
+ imagepng($imgplain, $tempfile_plain);
+ }
+
+ $this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
+
+ // embed mask image
+ if ($tempfile_alpha) {
+ $this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
+ imagedestroy($imgalpha);
+ $this->imageCache[] = $tempfile_alpha;
+ }
+
+ // embed image, masked with previously embedded mask
+ $this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
+ imagedestroy($imgplain);
+ $this->imageCache[] = $tempfile_plain;
+ }
+
+ /**
+ * add a PNG image into the document, from a file
+ * this should work with remote files
+ *
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ * @throws Exception
+ */
+ function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
+ {
+ if (!function_exists("imagecreatefrompng")) {
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
+ }
+
+ if (isset($this->imageAlphaList[$file])) {
+ [$alphaFile, $plainFile] = $this->imageAlphaList[$file];
+
+ if ($alphaFile) {
+ $img = null;
+ $this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
+ }
+
+ $img = null;
+ $this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
+ return;
+ }
+
+ //if already cached, need not to read again
+ if (isset($this->imagelist[$file])) {
+ $img = null;
+ } else {
+ $info = file_get_contents($file, false, null, 24, 5);
+ $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
+ $bit_depth = $meta["bitDepth"];
+ $color_type = $meta["colorType"];
+
+ // http://www.w3.org/TR/PNG/#11IHDR
+ // 3 => indexed
+ // 4 => greyscale with alpha
+ // 6 => fullcolor with alpha
+ $is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
+
+ if ($is_alpha) { // exclude grayscale alpha
+ $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
+ return;
+ }
+
+ //png files typically contain an alpha channel.
+ //pdf file format or class.pdf does not support alpha blending.
+ //on alpha blended images, more transparent areas have a color near black.
+ //This appears in the result on not storing the alpha channel.
+ //Correct would be the box background image or its parent when transparent.
+ //But this would make the image dependent on the background.
+ //Therefore create an image with white background and copy in
+ //A more natural background than black is white.
+ //Therefore create an empty image with white background and merge the
+ //image in with alpha blending.
+ $imgtmp = @imagecreatefrompng($file);
+ if (!$imgtmp) {
+ return;
+ }
+ $sx = imagesx($imgtmp);
+ $sy = imagesy($imgtmp);
+ $img = imagecreatetruecolor($sx, $sy);
+ imagealphablending($img, true);
+
+ // @todo is it still needed ??
+ $ti = imagecolortransparent($imgtmp);
+ if ($ti >= 0) {
+ $tc = imagecolorsforindex($imgtmp, $ti);
+ $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
+ imagefill($img, 0, 0, $ti);
+ imagecolortransparent($img, $ti);
+ } else {
+ imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
+ }
+
+ imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
+ imagedestroy($imgtmp);
+ }
+ $this->addImagePng($img, $file, $x, $y, $w, $h);
+
+ if ($img) {
+ imagedestroy($img);
+ }
+ }
+
+ /**
+ * add a PNG image into the document, from a file
+ * this should work with remote files
+ *
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ */
+ function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
+ {
+ $doc = new \Svg\Document();
+ $doc->loadFile($file);
+ $dimensions = $doc->getDimensions();
+
+ $this->save();
+
+ $this->transform([$w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y]);
+
+ $surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
+ $doc->render($surface);
+
+ $this->restore();
+ }
+
+ /**
+ * add a PNG image into the document, from a memory buffer of the file
+ *
+ * @param $data
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param float $w
+ * @param float $h
+ * @param bool $is_mask
+ * @param null $mask
+ */
+ function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
+ {
+ if (isset($this->imagelist[$file])) {
+ $data = null;
+ $info['width'] = $this->imagelist[$file]['w'];
+ $info['height'] = $this->imagelist[$file]['h'];
+ $label = $this->imagelist[$file]['label'];
+ } else {
+ if ($data == null) {
+ $this->addMessage('addPngFromBuf error - data not present!');
+
+ return;
+ }
+
+ $error = 0;
+
+ if (!$error) {
+ $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
+
+ if (mb_substr($data, 0, 8, '8bit') != $header) {
+ $error = 1;
+
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile this file does not have a valid header ' . $file . ']';
+ }
+
+ $errormsg = 'this file does not have a valid header';
+ }
+ }
+
+ if (!$error) {
+ // set pointer
+ $p = 8;
+ $len = mb_strlen($data, '8bit');
+
+ // cycle through the file, identifying chunks
+ $haveHeader = 0;
+ $info = [];
+ $idata = '';
+ $pdata = '';
+
+ while ($p < $len) {
+ $chunkLen = $this->getBytes($data, $p, 4);
+ $chunkType = mb_substr($data, $p + 4, 4, '8bit');
+
+ switch ($chunkType) {
+ case 'IHDR':
+ // this is where all the file information comes from
+ $info['width'] = $this->getBytes($data, $p + 8, 4);
+ $info['height'] = $this->getBytes($data, $p + 12, 4);
+ $info['bitDepth'] = ord($data[$p + 16]);
+ $info['colorType'] = ord($data[$p + 17]);
+ $info['compressionMethod'] = ord($data[$p + 18]);
+ $info['filterMethod'] = ord($data[$p + 19]);
+ $info['interlaceMethod'] = ord($data[$p + 20]);
+
+ //print_r($info);
+ $haveHeader = 1;
+ if ($info['compressionMethod'] != 0) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile unsupported compression method ' . $file . ']';
+ }
+
+ $errormsg = 'unsupported compression method';
+ }
+
+ if ($info['filterMethod'] != 0) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile unsupported filter method ' . $file . ']';
+ }
+
+ $errormsg = 'unsupported filter method';
+ }
+ break;
+
+ case 'PLTE':
+ $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
+ break;
+
+ case 'IDAT':
+ $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
+ break;
+
+ case 'tRNS':
+ //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
+ //print "tRNS found, color type = ".$info['colorType']."\n";
+ $transparency = [];
+
+ switch ($info['colorType']) {
+ // indexed color, rbg
+ case 3:
+ /* corresponding to entries in the plte chunk
+ Alpha for palette index 0: 1 byte
+ Alpha for palette index 1: 1 byte
+ ...etc...
+ */
+ // there will be one entry for each palette entry. up until the last non-opaque entry.
+ // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
+ $transparency['type'] = 'indexed';
+ $trans = 0;
+
+ for ($i = $chunkLen; $i >= 0; $i--) {
+ if (ord($data[$p + 8 + $i]) == 0) {
+ $trans = $i;
+ }
+ }
+
+ $transparency['data'] = $trans;
+ break;
+
+ // grayscale
+ case 0:
+ /* corresponding to entries in the plte chunk
+ Gray: 2 bytes, range 0 .. (2^bitdepth)-1
+ */
+ // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
+ $transparency['type'] = 'indexed';
+ $transparency['data'] = ord($data[$p + 8 + 1]);
+ break;
+
+ // truecolor
+ case 2:
+ /* corresponding to entries in the plte chunk
+ Red: 2 bytes, range 0 .. (2^bitdepth)-1
+ Green: 2 bytes, range 0 .. (2^bitdepth)-1
+ Blue: 2 bytes, range 0 .. (2^bitdepth)-1
+ */
+ $transparency['r'] = $this->getBytes($data, $p + 8, 2);
+ // r from truecolor
+ $transparency['g'] = $this->getBytes($data, $p + 10, 2);
+ // g from truecolor
+ $transparency['b'] = $this->getBytes($data, $p + 12, 2);
+ // b from truecolor
+
+ $transparency['type'] = 'color-key';
+ break;
+
+ //unsupported transparency type
+ default:
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile unsupported transparency type ' . $file . ']';
+ }
+ break;
+ }
+
+ // KS End new code
+ break;
+
+ default:
+ break;
+ }
+
+ $p += $chunkLen + 12;
+ }
+
+ if (!$haveHeader) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile information header is missing ' . $file . ']';
+ }
+
+ $errormsg = 'information header is missing';
+ }
+
+ if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
+ }
+
+ $errormsg = 'There appears to be no support for interlaced images in pdf.';
+ }
+ }
+
+ if (!$error && $info['bitDepth'] > 8) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
+ }
+
+ $errormsg = 'only bit depth of 8 or less is supported';
+ }
+
+ if (!$error) {
+ switch ($info['colorType']) {
+ case 3:
+ $color = 'DeviceRGB';
+ $ncolor = 1;
+ break;
+
+ case 2:
+ $color = 'DeviceRGB';
+ $ncolor = 3;
+ break;
+
+ case 0:
+ $color = 'DeviceGray';
+ $ncolor = 1;
+ break;
+
+ default:
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
+ }
+
+ $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
+ }
+ }
+
+ if ($error) {
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
+
+ return;
+ }
+
+ //print_r($info);
+ // so this image is ok... add it in.
+ $this->numImages++;
+ $im = $this->numImages;
+ $label = "I$im";
+ $this->numObj++;
+
+ // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
+ $options = [
+ 'label' => $label,
+ 'data' => $idata,
+ 'bitsPerComponent' => $info['bitDepth'],
+ 'pdata' => $pdata,
+ 'iw' => $info['width'],
+ 'ih' => $info['height'],
+ 'type' => 'png',
+ 'color' => $color,
+ 'ncolor' => $ncolor,
+ 'masked' => $mask,
+ 'isMask' => $is_mask
+ ];
+
+ if (isset($transparency)) {
+ $options['transparency'] = $transparency;
+ }
+
+ $this->o_image($this->numObj, 'new', $options);
+ $this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
+ }
+
+ if ($is_mask) {
+ return;
+ }
+
+ if ($w <= 0 && $h <= 0) {
+ $w = $info['width'];
+ $h = $info['height'];
+ }
+
+ if ($w <= 0) {
+ $w = $h / $info['height'] * $info['width'];
+ }
+
+ if ($h <= 0) {
+ $h = $w * $info['height'] / $info['width'];
+ }
+
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
+ }
+
+ /**
+ * add a JPEG image into the document, from a file
+ *
+ * @param $img
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ */
+ function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
+ {
+ // attempt to add a jpeg image straight from a file, using no GD commands
+ // note that this function is unable to operate on a remote file.
+
+ if (!file_exists($img)) {
+ return;
+ }
+
+ if ($this->image_iscached($img)) {
+ $data = null;
+ $imageWidth = $this->imagelist[$img]['w'];
+ $imageHeight = $this->imagelist[$img]['h'];
+ $channels = $this->imagelist[$img]['c'];
+ } else {
+ $tmp = getimagesize($img);
+ $imageWidth = $tmp[0];
+ $imageHeight = $tmp[1];
+
+ if (isset($tmp['channels'])) {
+ $channels = $tmp['channels'];
+ } else {
+ $channels = 3;
+ }
+
+ $data = file_get_contents($img);
+ }
+
+ if ($w <= 0 && $h <= 0) {
+ $w = $imageWidth;
+ }
+
+ if ($w == 0) {
+ $w = $h / $imageHeight * $imageWidth;
+ }
+
+ if ($h == 0) {
+ $h = $w * $imageHeight / $imageWidth;
+ }
+
+ $this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
+ }
+
+ /**
+ * common code used by the two JPEG adding functions
+ * @param $data
+ * @param $imgname
+ * @param $imageWidth
+ * @param $imageHeight
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ * @param int $channels
+ */
+ private function addJpegImage_common(
+ &$data,
+ $imgname,
+ $imageWidth,
+ $imageHeight,
+ $x,
+ $y,
+ $w = 0,
+ $h = 0,
+ $channels = 3
+ ) {
+ if ($this->image_iscached($imgname)) {
+ $label = $this->imagelist[$imgname]['label'];
+ //debugpng
+ //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
+
+ } else {
+ if ($data == null) {
+ $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
+
+ return;
+ }
+
+ // note that this function is not to be called externally
+ // it is just the common code between the GD and the file options
+ $this->numImages++;
+ $im = $this->numImages;
+ $label = "I$im";
+ $this->numObj++;
+
+ $this->o_image(
+ $this->numObj,
+ 'new',
+ [
+ 'label' => $label,
+ 'data' => &$data,
+ 'iw' => $imageWidth,
+ 'ih' => $imageHeight,
+ 'channels' => $channels
+ ]
+ );
+
+ $this->imagelist[$imgname] = [
+ 'label' => $label,
+ 'w' => $imageWidth,
+ 'h' => $imageHeight,
+ 'c' => $channels
+ ];
+ }
+
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
+ }
+
+ /**
+ * specify where the document should open when it first starts
+ *
+ * @param $style
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ */
+ function openHere($style, $a = 0, $b = 0, $c = 0)
+ {
+ // this function will open the document at a specified page, in a specified style
+ // the values for style, and the required parameters are:
+ // 'XYZ' left, top, zoom
+ // 'Fit'
+ // 'FitH' top
+ // 'FitV' left
+ // 'FitR' left,bottom,right
+ // 'FitB'
+ // 'FitBH' top
+ // 'FitBV' left
+ $this->numObj++;
+ $this->o_destination(
+ $this->numObj,
+ 'new',
+ ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
+ );
+ $id = $this->catalogId;
+ $this->o_catalog($id, 'openHere', $this->numObj);
+ }
+
+ /**
+ * Add JavaScript code to the PDF document
+ *
+ * @param string $code
+ */
+ function addJavascript($code)
+ {
+ $this->javascript .= $code;
+ }
+
+ /**
+ * create a labelled destination within the document
+ *
+ * @param $label
+ * @param $style
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ */
+ function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
+ {
+ // associates the given label with the destination, it is done this way so that a destination can be specified after
+ // it has been linked to
+ // styles are the same as the 'openHere' function
+ $this->numObj++;
+ $this->o_destination(
+ $this->numObj,
+ 'new',
+ ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
+ );
+ $id = $this->numObj;
+
+ // store the label->idf relationship, note that this means that labels can be used only once
+ $this->destinations["$label"] = $id;
+ }
+
+ /**
+ * define font families, this is used to initialize the font families for the default fonts
+ * and for the user to add new ones for their fonts. The default bahavious can be overridden should
+ * that be desired.
+ *
+ * @param $family
+ * @param string $options
+ */
+ function setFontFamily($family, $options = '')
+ {
+ if (!is_array($options)) {
+ if ($family === 'init') {
+ // set the known family groups
+ // these font families will be used to enable bold and italic markers to be included
+ // within text streams. html forms will be used... <b></b> <i></i>
+ $this->fontFamilies['Helvetica.afm'] =
+ [
+ 'b' => 'Helvetica-Bold.afm',
+ 'i' => 'Helvetica-Oblique.afm',
+ 'bi' => 'Helvetica-BoldOblique.afm',
+ 'ib' => 'Helvetica-BoldOblique.afm'
+ ];
+
+ $this->fontFamilies['Courier.afm'] =
+ [
+ 'b' => 'Courier-Bold.afm',
+ 'i' => 'Courier-Oblique.afm',
+ 'bi' => 'Courier-BoldOblique.afm',
+ 'ib' => 'Courier-BoldOblique.afm'
+ ];
+
+ $this->fontFamilies['Times-Roman.afm'] =
+ [
+ 'b' => 'Times-Bold.afm',
+ 'i' => 'Times-Italic.afm',
+ 'bi' => 'Times-BoldItalic.afm',
+ 'ib' => 'Times-BoldItalic.afm'
+ ];
+ }
+ } else {
+
+ // the user is trying to set a font family
+ // note that this can also be used to set the base ones to something else
+ if (mb_strlen($family)) {
+ $this->fontFamilies[$family] = $options;
+ }
+ }
+ }
+
+ /**
+ * used to add messages for use in debugging
+ *
+ * @param $message
+ */
+ function addMessage($message)
+ {
+ $this->messages .= $message . "\n";
+ }
+
+ /**
+ * a few functions which should allow the document to be treated transactionally.
+ *
+ * @param $action
+ */
+ function transaction($action)
+ {
+ switch ($action) {
+ case 'start':
+ // store all the data away into the checkpoint variable
+ $data = get_object_vars($this);
+ $this->checkpoint = $data;
+ unset($data);
+ break;
+
+ case 'commit':
+ if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
+ $tmp = $this->checkpoint['checkpoint'];
+ $this->checkpoint = $tmp;
+ unset($tmp);
+ } else {
+ $this->checkpoint = '';
+ }
+ break;
+
+ case 'rewind':
+ // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
+ if (is_array($this->checkpoint)) {
+ // can only abort if were inside a checkpoint
+ $tmp = $this->checkpoint;
+
+ foreach ($tmp as $k => $v) {
+ if ($k !== 'checkpoint') {
+ $this->$k = $v;
+ }
+ }
+ unset($tmp);
+ }
+ break;
+
+ case 'abort':
+ if (is_array($this->checkpoint)) {
+ // can only abort if were inside a checkpoint
+ $tmp = $this->checkpoint;
+ foreach ($tmp as $k => $v) {
+ $this->$k = $v;
+ }
+ unset($tmp);
+ }
+ break;
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Bold.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Bold.afm
new file mode 100644
index 0000000..1f1d8e6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Bold.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jun 23 16:28:00 0:00:00
+Comment UniqueID 43048
+Comment VMusage 41139 52164
+FontName Courier-Bold
+FullName Courier Bold
+FamilyName Courier
+Weight Bold
+ItalicAngle 0
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -113 -250 749 801
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme WinAnsiEncoding
+CapHeight 562
+XHeight 439
+Ascender 629
+Descender -157
+StdHW 84
+StdVW 106
+StartCharMetrics 317
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 202 -15 398 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 135 277 465 562 ;
+C 35 ; WX 600 ; N numbersign ; B 56 -45 544 651 ;
+C 36 ; WX 600 ; N dollar ; B 82 -126 519 666 ;
+C 37 ; WX 600 ; N percent ; B 5 -15 595 616 ;
+C 38 ; WX 600 ; N ampersand ; B 36 -15 546 543 ;
+C 146 ; WX 600 ; N quoteright ; B 171 277 423 562 ;
+C 40 ; WX 600 ; N parenleft ; B 219 -102 461 616 ;
+C 41 ; WX 600 ; N parenright ; B 139 -102 381 616 ;
+C 42 ; WX 600 ; N asterisk ; B 91 219 509 601 ;
+C 43 ; WX 600 ; N plus ; B 71 39 529 478 ;
+C 44 ; WX 600 ; N comma ; B 123 -111 393 174 ;
+C 45 ; WX 600 ; N hyphen ; B 100 203 500 313 ;
+C 173 ; WX 600 ; N hyphen ; B 100 203 500 313 ;
+C 46 ; WX 600 ; N period ; B 192 -15 408 171 ;
+C 47 ; WX 600 ; N slash ; B 98 -77 502 626 ;
+C 48 ; WX 600 ; N zero ; B 87 -15 513 616 ;
+C 49 ; WX 600 ; N one ; B 81 0 539 616 ;
+C 50 ; WX 600 ; N two ; B 61 0 499 616 ;
+C 51 ; WX 600 ; N three ; B 63 -15 501 616 ;
+C 52 ; WX 600 ; N four ; B 53 0 507 616 ;
+C 53 ; WX 600 ; N five ; B 70 -15 521 601 ;
+C 54 ; WX 600 ; N six ; B 90 -15 521 616 ;
+C 55 ; WX 600 ; N seven ; B 55 0 494 601 ;
+C 56 ; WX 600 ; N eight ; B 83 -15 517 616 ;
+C 57 ; WX 600 ; N nine ; B 79 -15 510 616 ;
+C 58 ; WX 600 ; N colon ; B 191 -15 407 425 ;
+C 59 ; WX 600 ; N semicolon ; B 123 -111 408 425 ;
+C 60 ; WX 600 ; N less ; B 66 15 523 501 ;
+C 61 ; WX 600 ; N equal ; B 71 118 529 398 ;
+C 62 ; WX 600 ; N greater ; B 77 15 534 501 ;
+C 63 ; WX 600 ; N question ; B 98 -14 501 580 ;
+C 64 ; WX 600 ; N at ; B 16 -15 584 616 ;
+C 65 ; WX 600 ; N A ; B -9 0 609 562 ;
+C 66 ; WX 600 ; N B ; B 30 0 573 562 ;
+C 67 ; WX 600 ; N C ; B 22 -18 560 580 ;
+C 68 ; WX 600 ; N D ; B 30 0 594 562 ;
+C 69 ; WX 600 ; N E ; B 25 0 560 562 ;
+C 70 ; WX 600 ; N F ; B 39 0 570 562 ;
+C 71 ; WX 600 ; N G ; B 22 -18 594 580 ;
+C 72 ; WX 600 ; N H ; B 20 0 580 562 ;
+C 73 ; WX 600 ; N I ; B 77 0 523 562 ;
+C 74 ; WX 600 ; N J ; B 37 -18 601 562 ;
+C 75 ; WX 600 ; N K ; B 21 0 599 562 ;
+C 76 ; WX 600 ; N L ; B 39 0 578 562 ;
+C 77 ; WX 600 ; N M ; B -2 0 602 562 ;
+C 78 ; WX 600 ; N N ; B 8 -12 610 562 ;
+C 79 ; WX 600 ; N O ; B 22 -18 578 580 ;
+C 80 ; WX 600 ; N P ; B 48 0 559 562 ;
+C 81 ; WX 600 ; N Q ; B 32 -138 578 580 ;
+C 82 ; WX 600 ; N R ; B 24 0 599 562 ;
+C 83 ; WX 600 ; N S ; B 47 -22 553 582 ;
+C 84 ; WX 600 ; N T ; B 21 0 579 562 ;
+C 85 ; WX 600 ; N U ; B 4 -18 596 562 ;
+C 86 ; WX 600 ; N V ; B -13 0 613 562 ;
+C 87 ; WX 600 ; N W ; B -18 0 618 562 ;
+C 88 ; WX 600 ; N X ; B 12 0 588 562 ;
+C 89 ; WX 600 ; N Y ; B 12 0 589 562 ;
+C 90 ; WX 600 ; N Z ; B 62 0 539 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 245 -102 475 616 ;
+C 92 ; WX 600 ; N backslash ; B 99 -77 503 626 ;
+C 93 ; WX 600 ; N bracketright ; B 125 -102 355 616 ;
+C 94 ; WX 600 ; N asciicircum ; B 108 250 492 616 ;
+C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
+C 145 ; WX 600 ; N quoteleft ; B 178 277 428 562 ;
+C 97 ; WX 600 ; N a ; B 35 -15 570 454 ;
+C 98 ; WX 600 ; N b ; B 0 -15 584 626 ;
+C 99 ; WX 600 ; N c ; B 40 -15 545 459 ;
+C 100 ; WX 600 ; N d ; B 20 -15 591 626 ;
+C 101 ; WX 600 ; N e ; B 40 -15 563 454 ;
+C 102 ; WX 600 ; N f ; B 83 0 547 626 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 30 -146 580 454 ;
+C 104 ; WX 600 ; N h ; B 5 0 592 626 ;
+C 105 ; WX 600 ; N i ; B 77 0 523 658 ;
+C 106 ; WX 600 ; N j ; B 63 -146 440 658 ;
+C 107 ; WX 600 ; N k ; B 20 0 585 626 ;
+C 108 ; WX 600 ; N l ; B 77 0 523 626 ;
+C 109 ; WX 600 ; N m ; B -22 0 626 454 ;
+C 110 ; WX 600 ; N n ; B 18 0 592 454 ;
+C 111 ; WX 600 ; N o ; B 30 -15 570 454 ;
+C 112 ; WX 600 ; N p ; B -1 -142 570 454 ;
+C 113 ; WX 600 ; N q ; B 20 -142 591 454 ;
+C 114 ; WX 600 ; N r ; B 47 0 580 454 ;
+C 115 ; WX 600 ; N s ; B 68 -17 535 459 ;
+C 116 ; WX 600 ; N t ; B 47 -15 532 562 ;
+C 117 ; WX 600 ; N u ; B -1 -15 569 439 ;
+C 118 ; WX 600 ; N v ; B -1 0 601 439 ;
+C 119 ; WX 600 ; N w ; B -18 0 618 439 ;
+C 120 ; WX 600 ; N x ; B 6 0 594 439 ;
+C 121 ; WX 600 ; N y ; B -4 -142 601 439 ;
+C 122 ; WX 600 ; N z ; B 81 0 520 439 ;
+C 123 ; WX 600 ; N braceleft ; B 160 -102 464 616 ;
+C 124 ; WX 600 ; N bar ; B 255 -250 345 750 ;
+C 125 ; WX 600 ; N braceright ; B 136 -102 440 616 ;
+C 126 ; WX 600 ; N asciitilde ; B 71 153 530 356 ;
+C 161 ; WX 600 ; N exclamdown ; B 202 -146 398 449 ;
+C 162 ; WX 600 ; N cent ; B 66 -49 518 614 ;
+C 163 ; WX 600 ; N sterling ; B 72 -28 558 611 ;
+C -1 ; WX 600 ; N fraction ; B 25 -60 576 661 ;
+C 165 ; WX 600 ; N yen ; B 10 0 590 562 ;
+C 131 ; WX 600 ; N florin ; B -30 -131 572 616 ;
+C 167 ; WX 600 ; N section ; B 83 -70 517 580 ;
+C 164 ; WX 600 ; N currency ; B 54 49 546 517 ;
+C 39 ; WX 600 ; N quotesingle ; B 227 277 373 562 ;
+C 147 ; WX 600 ; N quotedblleft ; B 71 277 535 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 8 70 553 446 ;
+C 139 ; WX 600 ; N guilsinglleft ; B 141 70 459 446 ;
+C 155 ; WX 600 ; N guilsinglright ; B 141 70 459 446 ;
+C -1 ; WX 600 ; N fi ; B 12 0 593 626 ;
+C -1 ; WX 600 ; N fl ; B 12 0 593 626 ;
+C 150 ; WX 600 ; N endash ; B 65 203 535 313 ;
+C 134 ; WX 600 ; N dagger ; B 106 -70 494 580 ;
+C 135 ; WX 600 ; N daggerdbl ; B 106 -70 494 580 ;
+C 183 ; WX 600 ; N periodcentered ; B 196 165 404 351 ;
+C 182 ; WX 600 ; N paragraph ; B 6 -70 576 580 ;
+C 149 ; WX 600 ; N bullet ; B 140 132 460 430 ;
+C 130 ; WX 600 ; N quotesinglbase ; B 175 -142 427 143 ;
+C 132 ; WX 600 ; N quotedblbase ; B 65 -142 529 143 ;
+C 148 ; WX 600 ; N quotedblright ; B 61 277 525 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 47 70 592 446 ;
+C 133 ; WX 600 ; N ellipsis ; B 26 -15 574 116 ;
+C 137 ; WX 600 ; N perthousand ; B -113 -15 713 616 ;
+C 191 ; WX 600 ; N questiondown ; B 99 -146 502 449 ;
+C 96 ; WX 600 ; N grave ; B 132 508 395 661 ;
+C 180 ; WX 600 ; N acute ; B 205 508 468 661 ;
+C 136 ; WX 600 ; N circumflex ; B 103 483 497 657 ;
+C 152 ; WX 600 ; N tilde ; B 89 493 512 636 ;
+C 175 ; WX 600 ; N macron ; B 88 505 512 585 ;
+C -1 ; WX 600 ; N breve ; B 83 468 517 631 ;
+C -1 ; WX 600 ; N dotaccent ; B 230 498 370 638 ;
+C 168 ; WX 600 ; N dieresis ; B 128 498 472 638 ;
+C -1 ; WX 600 ; N ring ; B 198 481 402 678 ;
+C 184 ; WX 600 ; N cedilla ; B 205 -206 387 0 ;
+C -1 ; WX 600 ; N hungarumlaut ; B 68 488 588 661 ;
+C -1 ; WX 600 ; N ogonek ; B 169 -199 400 0 ;
+C -1 ; WX 600 ; N caron ; B 103 493 497 667 ;
+C 151 ; WX 600 ; N emdash ; B -10 203 610 313 ;
+C 198 ; WX 600 ; N AE ; B -29 0 602 562 ;
+C 170 ; WX 600 ; N ordfeminine ; B 147 196 453 580 ;
+C -1 ; WX 600 ; N Lslash ; B 39 0 578 562 ;
+C 216 ; WX 600 ; N Oslash ; B 22 -22 578 584 ;
+C 140 ; WX 600 ; N OE ; B -25 0 595 562 ;
+C 186 ; WX 600 ; N ordmasculine ; B 147 196 453 580 ;
+C 230 ; WX 600 ; N ae ; B -4 -15 601 454 ;
+C -1 ; WX 600 ; N dotlessi ; B 77 0 523 439 ;
+C -1 ; WX 600 ; N lslash ; B 77 0 523 626 ;
+C 248 ; WX 600 ; N oslash ; B 30 -24 570 463 ;
+C 156 ; WX 600 ; N oe ; B -18 -15 611 454 ;
+C 223 ; WX 600 ; N germandbls ; B 22 -15 596 626 ;
+C 207 ; WX 600 ; N Idieresis ; B 77 0 523 761 ;
+C 233 ; WX 600 ; N eacute ; B 40 -15 563 661 ;
+C -1 ; WX 600 ; N abreve ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B -1 -15 628 661 ;
+C -1 ; WX 600 ; N ecaron ; B 40 -15 563 667 ;
+C 159 ; WX 600 ; N Ydieresis ; B 12 0 589 761 ;
+C 247 ; WX 600 ; N divide ; B 71 16 529 500 ;
+C 221 ; WX 600 ; N Yacute ; B 12 0 589 784 ;
+C 194 ; WX 600 ; N Acircumflex ; B -9 0 609 780 ;
+C 225 ; WX 600 ; N aacute ; B 35 -15 570 661 ;
+C 219 ; WX 600 ; N Ucircumflex ; B 4 -18 596 780 ;
+C 253 ; WX 600 ; N yacute ; B -4 -142 601 661 ;
+C -1 ; WX 600 ; N scommaaccent ; B 68 -250 535 459 ;
+C 234 ; WX 600 ; N ecircumflex ; B 40 -15 563 657 ;
+C -1 ; WX 600 ; N Uring ; B 4 -18 596 801 ;
+C 220 ; WX 600 ; N Udieresis ; B 4 -18 596 761 ;
+C -1 ; WX 600 ; N aogonek ; B 35 -199 586 454 ;
+C 218 ; WX 600 ; N Uacute ; B 4 -18 596 784 ;
+C -1 ; WX 600 ; N uogonek ; B -1 -199 585 439 ;
+C 203 ; WX 600 ; N Edieresis ; B 25 0 560 761 ;
+C -1 ; WX 600 ; N Dcroat ; B 30 0 594 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 205 -250 397 -57 ;
+C 169 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Emacron ; B 25 0 560 708 ;
+C -1 ; WX 600 ; N ccaron ; B 40 -15 545 667 ;
+C 229 ; WX 600 ; N aring ; B 35 -15 570 678 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 8 -250 610 562 ;
+C -1 ; WX 600 ; N lacute ; B 77 0 523 801 ;
+C 224 ; WX 600 ; N agrave ; B 35 -15 570 661 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 21 -250 579 562 ;
+C -1 ; WX 600 ; N Cacute ; B 22 -18 560 784 ;
+C 227 ; WX 600 ; N atilde ; B 35 -15 570 636 ;
+C -1 ; WX 600 ; N Edotaccent ; B 25 0 560 761 ;
+C 154 ; WX 600 ; N scaron ; B 68 -17 535 667 ;
+C -1 ; WX 600 ; N scedilla ; B 68 -206 535 459 ;
+C 237 ; WX 600 ; N iacute ; B 77 0 523 661 ;
+C -1 ; WX 600 ; N lozenge ; B 66 0 534 740 ;
+C -1 ; WX 600 ; N Rcaron ; B 24 0 599 790 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 22 -250 594 580 ;
+C 251 ; WX 600 ; N ucircumflex ; B -1 -15 569 657 ;
+C 226 ; WX 600 ; N acircumflex ; B 35 -15 570 657 ;
+C -1 ; WX 600 ; N Amacron ; B -9 0 609 708 ;
+C -1 ; WX 600 ; N rcaron ; B 47 0 580 667 ;
+C 231 ; WX 600 ; N ccedilla ; B 40 -206 545 459 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 62 0 539 761 ;
+C 222 ; WX 600 ; N Thorn ; B 48 0 557 562 ;
+C -1 ; WX 600 ; N Omacron ; B 22 -18 578 708 ;
+C -1 ; WX 600 ; N Racute ; B 24 0 599 784 ;
+C -1 ; WX 600 ; N Sacute ; B 47 -22 553 784 ;
+C -1 ; WX 600 ; N dcaron ; B 20 -15 727 626 ;
+C -1 ; WX 600 ; N Umacron ; B 4 -18 596 708 ;
+C -1 ; WX 600 ; N uring ; B -1 -15 569 678 ;
+C 179 ; WX 600 ; N threesuperior ; B 138 222 433 616 ;
+C 210 ; WX 600 ; N Ograve ; B 22 -18 578 784 ;
+C 192 ; WX 600 ; N Agrave ; B -9 0 609 784 ;
+C -1 ; WX 600 ; N Abreve ; B -9 0 609 784 ;
+C 215 ; WX 600 ; N multiply ; B 81 39 520 478 ;
+C 250 ; WX 600 ; N uacute ; B -1 -15 569 661 ;
+C -1 ; WX 600 ; N Tcaron ; B 21 0 579 790 ;
+C -1 ; WX 600 ; N partialdiff ; B 63 -38 537 728 ;
+C 255 ; WX 600 ; N ydieresis ; B -4 -142 601 638 ;
+C -1 ; WX 600 ; N Nacute ; B 8 -12 610 784 ;
+C 238 ; WX 600 ; N icircumflex ; B 73 0 523 657 ;
+C 202 ; WX 600 ; N Ecircumflex ; B 25 0 560 780 ;
+C 228 ; WX 600 ; N adieresis ; B 35 -15 570 638 ;
+C 235 ; WX 600 ; N edieresis ; B 40 -15 563 638 ;
+C -1 ; WX 600 ; N cacute ; B 40 -15 545 661 ;
+C -1 ; WX 600 ; N nacute ; B 18 0 592 661 ;
+C -1 ; WX 600 ; N umacron ; B -1 -15 569 585 ;
+C -1 ; WX 600 ; N Ncaron ; B 8 -12 610 790 ;
+C 205 ; WX 600 ; N Iacute ; B 77 0 523 784 ;
+C 177 ; WX 600 ; N plusminus ; B 71 24 529 515 ;
+C 166 ; WX 600 ; N brokenbar ; B 255 -175 345 675 ;
+C 174 ; WX 600 ; N registered ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 22 -18 594 784 ;
+C -1 ; WX 600 ; N Idotaccent ; B 77 0 523 761 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 586 706 ;
+C 200 ; WX 600 ; N Egrave ; B 25 0 560 784 ;
+C -1 ; WX 600 ; N racute ; B 47 0 580 661 ;
+C -1 ; WX 600 ; N omacron ; B 30 -15 570 585 ;
+C -1 ; WX 600 ; N Zacute ; B 62 0 539 784 ;
+C 142 ; WX 600 ; N Zcaron ; B 62 0 539 790 ;
+C -1 ; WX 600 ; N greaterequal ; B 26 0 523 696 ;
+C 208 ; WX 600 ; N Eth ; B 30 0 594 562 ;
+C 199 ; WX 600 ; N Ccedilla ; B 22 -206 560 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 77 -250 523 626 ;
+C -1 ; WX 600 ; N tcaron ; B 47 -15 532 703 ;
+C -1 ; WX 600 ; N eogonek ; B 40 -199 563 454 ;
+C -1 ; WX 600 ; N Uogonek ; B 4 -199 596 562 ;
+C 193 ; WX 600 ; N Aacute ; B -9 0 609 784 ;
+C 196 ; WX 600 ; N Adieresis ; B -9 0 609 761 ;
+C 232 ; WX 600 ; N egrave ; B 40 -15 563 661 ;
+C -1 ; WX 600 ; N zacute ; B 81 0 520 661 ;
+C -1 ; WX 600 ; N iogonek ; B 77 -199 523 658 ;
+C 211 ; WX 600 ; N Oacute ; B 22 -18 578 784 ;
+C 243 ; WX 600 ; N oacute ; B 30 -15 570 661 ;
+C -1 ; WX 600 ; N amacron ; B 35 -15 570 585 ;
+C -1 ; WX 600 ; N sacute ; B 68 -17 535 661 ;
+C 239 ; WX 600 ; N idieresis ; B 77 0 523 618 ;
+C 212 ; WX 600 ; N Ocircumflex ; B 22 -18 578 780 ;
+C 217 ; WX 600 ; N Ugrave ; B 4 -18 596 784 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 594 688 ;
+C 254 ; WX 600 ; N thorn ; B -14 -142 570 626 ;
+C 178 ; WX 600 ; N twosuperior ; B 143 230 436 616 ;
+C 214 ; WX 600 ; N Odieresis ; B 22 -18 578 761 ;
+C 181 ; WX 600 ; N mu ; B -1 -142 569 439 ;
+C 236 ; WX 600 ; N igrave ; B 77 0 523 661 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 30 -15 668 661 ;
+C -1 ; WX 600 ; N Eogonek ; B 25 -199 576 562 ;
+C -1 ; WX 600 ; N dcroat ; B 20 -15 591 626 ;
+C 190 ; WX 600 ; N threequarters ; B -47 -60 648 661 ;
+C -1 ; WX 600 ; N Scedilla ; B 47 -206 553 582 ;
+C -1 ; WX 600 ; N lcaron ; B 77 0 597 626 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 21 -250 599 562 ;
+C -1 ; WX 600 ; N Lacute ; B 39 0 578 784 ;
+C 153 ; WX 600 ; N trademark ; B -9 230 749 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 40 -15 563 638 ;
+C 204 ; WX 600 ; N Igrave ; B 77 0 523 784 ;
+C -1 ; WX 600 ; N Imacron ; B 77 0 523 708 ;
+C -1 ; WX 600 ; N Lcaron ; B 39 0 637 562 ;
+C 189 ; WX 600 ; N onehalf ; B -47 -60 648 661 ;
+C -1 ; WX 600 ; N lessequal ; B 26 0 523 696 ;
+C 244 ; WX 600 ; N ocircumflex ; B 30 -15 570 657 ;
+C 241 ; WX 600 ; N ntilde ; B 18 0 592 636 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 4 -18 638 784 ;
+C 201 ; WX 600 ; N Eacute ; B 25 0 560 784 ;
+C -1 ; WX 600 ; N emacron ; B 40 -15 563 585 ;
+C -1 ; WX 600 ; N gbreve ; B 30 -146 580 661 ;
+C 188 ; WX 600 ; N onequarter ; B -56 -60 656 661 ;
+C 138 ; WX 600 ; N Scaron ; B 47 -22 553 790 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 47 -250 553 582 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 22 -18 628 784 ;
+C 176 ; WX 600 ; N degree ; B 86 243 474 616 ;
+C 242 ; WX 600 ; N ograve ; B 30 -15 570 661 ;
+C -1 ; WX 600 ; N Ccaron ; B 22 -18 560 790 ;
+C 249 ; WX 600 ; N ugrave ; B -1 -15 569 661 ;
+C -1 ; WX 600 ; N radical ; B -19 -104 473 778 ;
+C -1 ; WX 600 ; N Dcaron ; B 30 0 594 790 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 47 -250 580 454 ;
+C 209 ; WX 600 ; N Ntilde ; B 8 -12 610 759 ;
+C 245 ; WX 600 ; N otilde ; B 30 -15 570 636 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 24 -250 599 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 39 -250 578 562 ;
+C 195 ; WX 600 ; N Atilde ; B -9 0 609 759 ;
+C -1 ; WX 600 ; N Aogonek ; B -9 -199 625 562 ;
+C 197 ; WX 600 ; N Aring ; B -9 0 609 801 ;
+C 213 ; WX 600 ; N Otilde ; B 22 -18 578 759 ;
+C -1 ; WX 600 ; N zdotaccent ; B 81 0 520 638 ;
+C -1 ; WX 600 ; N Ecaron ; B 25 0 560 790 ;
+C -1 ; WX 600 ; N Iogonek ; B 77 -199 523 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 20 -250 585 626 ;
+C -1 ; WX 600 ; N minus ; B 71 203 529 313 ;
+C 206 ; WX 600 ; N Icircumflex ; B 77 0 523 780 ;
+C -1 ; WX 600 ; N ncaron ; B 18 0 592 667 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 47 -250 532 562 ;
+C 172 ; WX 600 ; N logicalnot ; B 71 103 529 413 ;
+C 246 ; WX 600 ; N odieresis ; B 30 -15 570 638 ;
+C 252 ; WX 600 ; N udieresis ; B -1 -15 569 638 ;
+C -1 ; WX 600 ; N notequal ; B 12 -47 537 563 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 30 -146 580 714 ;
+C 240 ; WX 600 ; N eth ; B 58 -27 543 626 ;
+C 158 ; WX 600 ; N zcaron ; B 81 0 520 667 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 18 -250 592 454 ;
+C 185 ; WX 600 ; N onesuperior ; B 153 230 447 616 ;
+C -1 ; WX 600 ; N imacron ; B 77 0 523 585 ;
+C 128 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-BoldOblique.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-BoldOblique.afm
new file mode 100644
index 0000000..914680a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-BoldOblique.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Mon Jun 23 16:28:46 0:00:00
+Comment UniqueID 43049
+Comment VMusage 17529 79244
+FontName Courier-BoldOblique
+FullName Courier Bold Oblique
+FamilyName Courier
+Weight Bold
+ItalicAngle -12
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -57 -250 869 801
+UnderlinePosition -100
+UnderlineThickness 50
+Version 3
+Notice Copyright (c) 1989, 1990, 1991, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme WinAnsiEncoding
+CapHeight 562
+XHeight 439
+Ascender 629
+Descender -157
+StdHW 84
+StdVW 106
+StartCharMetrics 317
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 215 -15 495 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 211 277 585 562 ;
+C 35 ; WX 600 ; N numbersign ; B 88 -45 641 651 ;
+C 36 ; WX 600 ; N dollar ; B 87 -126 630 666 ;
+C 37 ; WX 600 ; N percent ; B 101 -15 625 616 ;
+C 38 ; WX 600 ; N ampersand ; B 61 -15 595 543 ;
+C 146 ; WX 600 ; N quoteright ; B 229 277 543 562 ;
+C 40 ; WX 600 ; N parenleft ; B 265 -102 592 616 ;
+C 41 ; WX 600 ; N parenright ; B 117 -102 444 616 ;
+C 42 ; WX 600 ; N asterisk ; B 179 219 598 601 ;
+C 43 ; WX 600 ; N plus ; B 114 39 596 478 ;
+C 44 ; WX 600 ; N comma ; B 99 -111 430 174 ;
+C 45 ; WX 600 ; N hyphen ; B 143 203 567 313 ;
+C 173 ; WX 600 ; N hyphen ; B 143 203 567 313 ;
+C 46 ; WX 600 ; N period ; B 206 -15 427 171 ;
+C 47 ; WX 600 ; N slash ; B 90 -77 626 626 ;
+C 48 ; WX 600 ; N zero ; B 135 -15 593 616 ;
+C 49 ; WX 600 ; N one ; B 93 0 562 616 ;
+C 50 ; WX 600 ; N two ; B 61 0 594 616 ;
+C 51 ; WX 600 ; N three ; B 71 -15 571 616 ;
+C 52 ; WX 600 ; N four ; B 81 0 559 616 ;
+C 53 ; WX 600 ; N five ; B 77 -15 621 601 ;
+C 54 ; WX 600 ; N six ; B 135 -15 652 616 ;
+C 55 ; WX 600 ; N seven ; B 147 0 622 601 ;
+C 56 ; WX 600 ; N eight ; B 115 -15 604 616 ;
+C 57 ; WX 600 ; N nine ; B 75 -15 592 616 ;
+C 58 ; WX 600 ; N colon ; B 205 -15 480 425 ;
+C 59 ; WX 600 ; N semicolon ; B 99 -111 481 425 ;
+C 60 ; WX 600 ; N less ; B 120 15 613 501 ;
+C 61 ; WX 600 ; N equal ; B 96 118 614 398 ;
+C 62 ; WX 600 ; N greater ; B 97 15 589 501 ;
+C 63 ; WX 600 ; N question ; B 183 -14 592 580 ;
+C 64 ; WX 600 ; N at ; B 65 -15 642 616 ;
+C 65 ; WX 600 ; N A ; B -9 0 632 562 ;
+C 66 ; WX 600 ; N B ; B 30 0 630 562 ;
+C 67 ; WX 600 ; N C ; B 74 -18 675 580 ;
+C 68 ; WX 600 ; N D ; B 30 0 664 562 ;
+C 69 ; WX 600 ; N E ; B 25 0 670 562 ;
+C 70 ; WX 600 ; N F ; B 39 0 684 562 ;
+C 71 ; WX 600 ; N G ; B 74 -18 675 580 ;
+C 72 ; WX 600 ; N H ; B 20 0 700 562 ;
+C 73 ; WX 600 ; N I ; B 77 0 643 562 ;
+C 74 ; WX 600 ; N J ; B 58 -18 721 562 ;
+C 75 ; WX 600 ; N K ; B 21 0 692 562 ;
+C 76 ; WX 600 ; N L ; B 39 0 636 562 ;
+C 77 ; WX 600 ; N M ; B -2 0 722 562 ;
+C 78 ; WX 600 ; N N ; B 8 -12 730 562 ;
+C 79 ; WX 600 ; N O ; B 74 -18 645 580 ;
+C 80 ; WX 600 ; N P ; B 48 0 643 562 ;
+C 81 ; WX 600 ; N Q ; B 83 -138 636 580 ;
+C 82 ; WX 600 ; N R ; B 24 0 617 562 ;
+C 83 ; WX 600 ; N S ; B 54 -22 673 582 ;
+C 84 ; WX 600 ; N T ; B 86 0 679 562 ;
+C 85 ; WX 600 ; N U ; B 101 -18 716 562 ;
+C 86 ; WX 600 ; N V ; B 84 0 733 562 ;
+C 87 ; WX 600 ; N W ; B 79 0 738 562 ;
+C 88 ; WX 600 ; N X ; B 12 0 690 562 ;
+C 89 ; WX 600 ; N Y ; B 109 0 709 562 ;
+C 90 ; WX 600 ; N Z ; B 62 0 637 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 223 -102 606 616 ;
+C 92 ; WX 600 ; N backslash ; B 222 -77 496 626 ;
+C 93 ; WX 600 ; N bracketright ; B 103 -102 486 616 ;
+C 94 ; WX 600 ; N asciicircum ; B 171 250 556 616 ;
+C 95 ; WX 600 ; N underscore ; B -27 -125 585 -75 ;
+C 145 ; WX 600 ; N quoteleft ; B 297 277 487 562 ;
+C 97 ; WX 600 ; N a ; B 61 -15 593 454 ;
+C 98 ; WX 600 ; N b ; B 13 -15 636 626 ;
+C 99 ; WX 600 ; N c ; B 81 -15 631 459 ;
+C 100 ; WX 600 ; N d ; B 60 -15 645 626 ;
+C 101 ; WX 600 ; N e ; B 81 -15 605 454 ;
+C 102 ; WX 600 ; N f ; B 83 0 677 626 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 40 -146 674 454 ;
+C 104 ; WX 600 ; N h ; B 18 0 615 626 ;
+C 105 ; WX 600 ; N i ; B 77 0 546 658 ;
+C 106 ; WX 600 ; N j ; B 36 -146 580 658 ;
+C 107 ; WX 600 ; N k ; B 33 0 643 626 ;
+C 108 ; WX 600 ; N l ; B 77 0 546 626 ;
+C 109 ; WX 600 ; N m ; B -22 0 649 454 ;
+C 110 ; WX 600 ; N n ; B 18 0 615 454 ;
+C 111 ; WX 600 ; N o ; B 71 -15 622 454 ;
+C 112 ; WX 600 ; N p ; B -32 -142 622 454 ;
+C 113 ; WX 600 ; N q ; B 60 -142 685 454 ;
+C 114 ; WX 600 ; N r ; B 47 0 655 454 ;
+C 115 ; WX 600 ; N s ; B 66 -17 608 459 ;
+C 116 ; WX 600 ; N t ; B 118 -15 567 562 ;
+C 117 ; WX 600 ; N u ; B 70 -15 592 439 ;
+C 118 ; WX 600 ; N v ; B 70 0 695 439 ;
+C 119 ; WX 600 ; N w ; B 53 0 712 439 ;
+C 120 ; WX 600 ; N x ; B 6 0 671 439 ;
+C 121 ; WX 600 ; N y ; B -21 -142 695 439 ;
+C 122 ; WX 600 ; N z ; B 81 0 614 439 ;
+C 123 ; WX 600 ; N braceleft ; B 203 -102 595 616 ;
+C 124 ; WX 600 ; N bar ; B 201 -250 505 750 ;
+C 125 ; WX 600 ; N braceright ; B 114 -102 506 616 ;
+C 126 ; WX 600 ; N asciitilde ; B 120 153 590 356 ;
+C 161 ; WX 600 ; N exclamdown ; B 196 -146 477 449 ;
+C 162 ; WX 600 ; N cent ; B 121 -49 605 614 ;
+C 163 ; WX 600 ; N sterling ; B 106 -28 650 611 ;
+C -1 ; WX 600 ; N fraction ; B 22 -60 708 661 ;
+C 165 ; WX 600 ; N yen ; B 98 0 710 562 ;
+C 131 ; WX 600 ; N florin ; B -57 -131 702 616 ;
+C 167 ; WX 600 ; N section ; B 74 -70 620 580 ;
+C 164 ; WX 600 ; N currency ; B 77 49 644 517 ;
+C 39 ; WX 600 ; N quotesingle ; B 303 277 493 562 ;
+C 147 ; WX 600 ; N quotedblleft ; B 190 277 594 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 62 70 639 446 ;
+C 139 ; WX 600 ; N guilsinglleft ; B 195 70 545 446 ;
+C 155 ; WX 600 ; N guilsinglright ; B 165 70 514 446 ;
+C -1 ; WX 600 ; N fi ; B 12 0 644 626 ;
+C -1 ; WX 600 ; N fl ; B 12 0 644 626 ;
+C 150 ; WX 600 ; N endash ; B 108 203 602 313 ;
+C 134 ; WX 600 ; N dagger ; B 175 -70 586 580 ;
+C 135 ; WX 600 ; N daggerdbl ; B 121 -70 587 580 ;
+C 183 ; WX 600 ; N periodcentered ; B 248 165 461 351 ;
+C 182 ; WX 600 ; N paragraph ; B 61 -70 700 580 ;
+C 149 ; WX 600 ; N bullet ; B 196 132 523 430 ;
+C 130 ; WX 600 ; N quotesinglbase ; B 144 -142 458 143 ;
+C 132 ; WX 600 ; N quotedblbase ; B 34 -142 560 143 ;
+C 148 ; WX 600 ; N quotedblright ; B 119 277 645 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 71 70 647 446 ;
+C 133 ; WX 600 ; N ellipsis ; B 35 -15 587 116 ;
+C 137 ; WX 600 ; N perthousand ; B -45 -15 743 616 ;
+C 191 ; WX 600 ; N questiondown ; B 100 -146 509 449 ;
+C 96 ; WX 600 ; N grave ; B 272 508 503 661 ;
+C 180 ; WX 600 ; N acute ; B 312 508 609 661 ;
+C 136 ; WX 600 ; N circumflex ; B 212 483 607 657 ;
+C 152 ; WX 600 ; N tilde ; B 199 493 643 636 ;
+C 175 ; WX 600 ; N macron ; B 195 505 637 585 ;
+C -1 ; WX 600 ; N breve ; B 217 468 652 631 ;
+C -1 ; WX 600 ; N dotaccent ; B 348 498 493 638 ;
+C 168 ; WX 600 ; N dieresis ; B 246 498 595 638 ;
+C -1 ; WX 600 ; N ring ; B 319 481 528 678 ;
+C 184 ; WX 600 ; N cedilla ; B 168 -206 368 0 ;
+C -1 ; WX 600 ; N hungarumlaut ; B 171 488 729 661 ;
+C -1 ; WX 600 ; N ogonek ; B 143 -199 367 0 ;
+C -1 ; WX 600 ; N caron ; B 238 493 633 667 ;
+C 151 ; WX 600 ; N emdash ; B 33 203 677 313 ;
+C 198 ; WX 600 ; N AE ; B -29 0 708 562 ;
+C 170 ; WX 600 ; N ordfeminine ; B 188 196 526 580 ;
+C -1 ; WX 600 ; N Lslash ; B 39 0 636 562 ;
+C 216 ; WX 600 ; N Oslash ; B 48 -22 673 584 ;
+C 140 ; WX 600 ; N OE ; B 26 0 701 562 ;
+C 186 ; WX 600 ; N ordmasculine ; B 188 196 543 580 ;
+C 230 ; WX 600 ; N ae ; B 21 -15 652 454 ;
+C -1 ; WX 600 ; N dotlessi ; B 77 0 546 439 ;
+C -1 ; WX 600 ; N lslash ; B 77 0 587 626 ;
+C 248 ; WX 600 ; N oslash ; B 54 -24 638 463 ;
+C 156 ; WX 600 ; N oe ; B 18 -15 662 454 ;
+C 223 ; WX 600 ; N germandbls ; B 22 -15 629 626 ;
+C 207 ; WX 600 ; N Idieresis ; B 77 0 643 761 ;
+C 233 ; WX 600 ; N eacute ; B 81 -15 609 661 ;
+C -1 ; WX 600 ; N abreve ; B 61 -15 658 661 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B 70 -15 769 661 ;
+C -1 ; WX 600 ; N ecaron ; B 81 -15 633 667 ;
+C 159 ; WX 600 ; N Ydieresis ; B 109 0 709 761 ;
+C 247 ; WX 600 ; N divide ; B 114 16 596 500 ;
+C 221 ; WX 600 ; N Yacute ; B 109 0 709 784 ;
+C 194 ; WX 600 ; N Acircumflex ; B -9 0 632 780 ;
+C 225 ; WX 600 ; N aacute ; B 61 -15 609 661 ;
+C 219 ; WX 600 ; N Ucircumflex ; B 101 -18 716 780 ;
+C 253 ; WX 600 ; N yacute ; B -21 -142 695 661 ;
+C -1 ; WX 600 ; N scommaaccent ; B 66 -250 608 459 ;
+C 234 ; WX 600 ; N ecircumflex ; B 81 -15 607 657 ;
+C -1 ; WX 600 ; N Uring ; B 101 -18 716 801 ;
+C 220 ; WX 600 ; N Udieresis ; B 101 -18 716 761 ;
+C -1 ; WX 600 ; N aogonek ; B 61 -199 593 454 ;
+C 218 ; WX 600 ; N Uacute ; B 101 -18 716 784 ;
+C -1 ; WX 600 ; N uogonek ; B 70 -199 592 439 ;
+C 203 ; WX 600 ; N Edieresis ; B 25 0 670 761 ;
+C -1 ; WX 600 ; N Dcroat ; B 30 0 664 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 151 -250 385 -57 ;
+C 169 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Emacron ; B 25 0 670 708 ;
+C -1 ; WX 600 ; N ccaron ; B 81 -15 633 667 ;
+C 229 ; WX 600 ; N aring ; B 61 -15 593 678 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 8 -250 730 562 ;
+C -1 ; WX 600 ; N lacute ; B 77 0 639 801 ;
+C 224 ; WX 600 ; N agrave ; B 61 -15 593 661 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 86 -250 679 562 ;
+C -1 ; WX 600 ; N Cacute ; B 74 -18 675 784 ;
+C 227 ; WX 600 ; N atilde ; B 61 -15 643 636 ;
+C -1 ; WX 600 ; N Edotaccent ; B 25 0 670 761 ;
+C 154 ; WX 600 ; N scaron ; B 66 -17 633 667 ;
+C -1 ; WX 600 ; N scedilla ; B 66 -206 608 459 ;
+C 237 ; WX 600 ; N iacute ; B 77 0 609 661 ;
+C -1 ; WX 600 ; N lozenge ; B 145 0 614 740 ;
+C -1 ; WX 600 ; N Rcaron ; B 24 0 659 790 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 74 -250 675 580 ;
+C 251 ; WX 600 ; N ucircumflex ; B 70 -15 597 657 ;
+C 226 ; WX 600 ; N acircumflex ; B 61 -15 607 657 ;
+C -1 ; WX 600 ; N Amacron ; B -9 0 633 708 ;
+C -1 ; WX 600 ; N rcaron ; B 47 0 655 667 ;
+C 231 ; WX 600 ; N ccedilla ; B 81 -206 631 459 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 62 0 637 761 ;
+C 222 ; WX 600 ; N Thorn ; B 48 0 620 562 ;
+C -1 ; WX 600 ; N Omacron ; B 74 -18 663 708 ;
+C -1 ; WX 600 ; N Racute ; B 24 0 665 784 ;
+C -1 ; WX 600 ; N Sacute ; B 54 -22 673 784 ;
+C -1 ; WX 600 ; N dcaron ; B 60 -15 861 626 ;
+C -1 ; WX 600 ; N Umacron ; B 101 -18 716 708 ;
+C -1 ; WX 600 ; N uring ; B 70 -15 592 678 ;
+C 179 ; WX 600 ; N threesuperior ; B 193 222 526 616 ;
+C 210 ; WX 600 ; N Ograve ; B 74 -18 645 784 ;
+C 192 ; WX 600 ; N Agrave ; B -9 0 632 784 ;
+C -1 ; WX 600 ; N Abreve ; B -9 0 684 784 ;
+C 215 ; WX 600 ; N multiply ; B 104 39 606 478 ;
+C 250 ; WX 600 ; N uacute ; B 70 -15 599 661 ;
+C -1 ; WX 600 ; N Tcaron ; B 86 0 679 790 ;
+C -1 ; WX 600 ; N partialdiff ; B 91 -38 627 728 ;
+C 255 ; WX 600 ; N ydieresis ; B -21 -142 695 638 ;
+C -1 ; WX 600 ; N Nacute ; B 8 -12 730 784 ;
+C 238 ; WX 600 ; N icircumflex ; B 77 0 577 657 ;
+C 202 ; WX 600 ; N Ecircumflex ; B 25 0 670 780 ;
+C 228 ; WX 600 ; N adieresis ; B 61 -15 595 638 ;
+C 235 ; WX 600 ; N edieresis ; B 81 -15 605 638 ;
+C -1 ; WX 600 ; N cacute ; B 81 -15 649 661 ;
+C -1 ; WX 600 ; N nacute ; B 18 0 639 661 ;
+C -1 ; WX 600 ; N umacron ; B 70 -15 637 585 ;
+C -1 ; WX 600 ; N Ncaron ; B 8 -12 730 790 ;
+C 205 ; WX 600 ; N Iacute ; B 77 0 643 784 ;
+C 177 ; WX 600 ; N plusminus ; B 76 24 614 515 ;
+C 166 ; WX 600 ; N brokenbar ; B 217 -175 489 675 ;
+C 174 ; WX 600 ; N registered ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 74 -18 684 784 ;
+C -1 ; WX 600 ; N Idotaccent ; B 77 0 643 761 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 672 706 ;
+C 200 ; WX 600 ; N Egrave ; B 25 0 670 784 ;
+C -1 ; WX 600 ; N racute ; B 47 0 655 661 ;
+C -1 ; WX 600 ; N omacron ; B 71 -15 637 585 ;
+C -1 ; WX 600 ; N Zacute ; B 62 0 665 784 ;
+C 142 ; WX 600 ; N Zcaron ; B 62 0 659 790 ;
+C -1 ; WX 600 ; N greaterequal ; B 26 0 627 696 ;
+C 208 ; WX 600 ; N Eth ; B 30 0 664 562 ;
+C 199 ; WX 600 ; N Ccedilla ; B 74 -206 675 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 77 -250 546 626 ;
+C -1 ; WX 600 ; N tcaron ; B 118 -15 627 703 ;
+C -1 ; WX 600 ; N eogonek ; B 81 -199 605 454 ;
+C -1 ; WX 600 ; N Uogonek ; B 101 -199 716 562 ;
+C 193 ; WX 600 ; N Aacute ; B -9 0 655 784 ;
+C 196 ; WX 600 ; N Adieresis ; B -9 0 632 761 ;
+C 232 ; WX 600 ; N egrave ; B 81 -15 605 661 ;
+C -1 ; WX 600 ; N zacute ; B 81 0 614 661 ;
+C -1 ; WX 600 ; N iogonek ; B 77 -199 546 658 ;
+C 211 ; WX 600 ; N Oacute ; B 74 -18 645 784 ;
+C 243 ; WX 600 ; N oacute ; B 71 -15 649 661 ;
+C -1 ; WX 600 ; N amacron ; B 61 -15 637 585 ;
+C -1 ; WX 600 ; N sacute ; B 66 -17 609 661 ;
+C 239 ; WX 600 ; N idieresis ; B 77 0 561 618 ;
+C 212 ; WX 600 ; N Ocircumflex ; B 74 -18 645 780 ;
+C 217 ; WX 600 ; N Ugrave ; B 101 -18 716 784 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 594 688 ;
+C 254 ; WX 600 ; N thorn ; B -32 -142 622 626 ;
+C 178 ; WX 600 ; N twosuperior ; B 191 230 542 616 ;
+C 214 ; WX 600 ; N Odieresis ; B 74 -18 645 761 ;
+C 181 ; WX 600 ; N mu ; B 49 -142 592 439 ;
+C 236 ; WX 600 ; N igrave ; B 77 0 546 661 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 71 -15 809 661 ;
+C -1 ; WX 600 ; N Eogonek ; B 25 -199 670 562 ;
+C -1 ; WX 600 ; N dcroat ; B 60 -15 712 626 ;
+C 190 ; WX 600 ; N threequarters ; B 8 -60 699 661 ;
+C -1 ; WX 600 ; N Scedilla ; B 54 -206 673 582 ;
+C -1 ; WX 600 ; N lcaron ; B 77 0 731 626 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 21 -250 692 562 ;
+C -1 ; WX 600 ; N Lacute ; B 39 0 636 784 ;
+C 153 ; WX 600 ; N trademark ; B 86 230 869 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 81 -15 605 638 ;
+C 204 ; WX 600 ; N Igrave ; B 77 0 643 784 ;
+C -1 ; WX 600 ; N Imacron ; B 77 0 663 708 ;
+C -1 ; WX 600 ; N Lcaron ; B 39 0 757 562 ;
+C 189 ; WX 600 ; N onehalf ; B 22 -60 716 661 ;
+C -1 ; WX 600 ; N lessequal ; B 26 0 671 696 ;
+C 244 ; WX 600 ; N ocircumflex ; B 71 -15 622 657 ;
+C 241 ; WX 600 ; N ntilde ; B 18 0 643 636 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 101 -18 805 784 ;
+C 201 ; WX 600 ; N Eacute ; B 25 0 670 784 ;
+C -1 ; WX 600 ; N emacron ; B 81 -15 637 585 ;
+C -1 ; WX 600 ; N gbreve ; B 40 -146 674 661 ;
+C 188 ; WX 600 ; N onequarter ; B 13 -60 707 661 ;
+C 138 ; WX 600 ; N Scaron ; B 54 -22 689 790 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 54 -250 673 582 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 74 -18 795 784 ;
+C 176 ; WX 600 ; N degree ; B 173 243 570 616 ;
+C 242 ; WX 600 ; N ograve ; B 71 -15 622 661 ;
+C -1 ; WX 600 ; N Ccaron ; B 74 -18 689 790 ;
+C 249 ; WX 600 ; N ugrave ; B 70 -15 592 661 ;
+C -1 ; WX 600 ; N radical ; B 67 -104 635 778 ;
+C -1 ; WX 600 ; N Dcaron ; B 30 0 664 790 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 47 -250 655 454 ;
+C 209 ; WX 600 ; N Ntilde ; B 8 -12 730 759 ;
+C 245 ; WX 600 ; N otilde ; B 71 -15 643 636 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 24 -250 617 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 39 -250 636 562 ;
+C 195 ; WX 600 ; N Atilde ; B -9 0 669 759 ;
+C -1 ; WX 600 ; N Aogonek ; B -9 -199 632 562 ;
+C 197 ; WX 600 ; N Aring ; B -9 0 632 801 ;
+C 213 ; WX 600 ; N Otilde ; B 74 -18 669 759 ;
+C -1 ; WX 600 ; N zdotaccent ; B 81 0 614 638 ;
+C -1 ; WX 600 ; N Ecaron ; B 25 0 670 790 ;
+C -1 ; WX 600 ; N Iogonek ; B 77 -199 643 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 33 -250 643 626 ;
+C -1 ; WX 600 ; N minus ; B 114 203 596 313 ;
+C 206 ; WX 600 ; N Icircumflex ; B 77 0 643 780 ;
+C -1 ; WX 600 ; N ncaron ; B 18 0 633 667 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 118 -250 567 562 ;
+C 172 ; WX 600 ; N logicalnot ; B 135 103 617 413 ;
+C 246 ; WX 600 ; N odieresis ; B 71 -15 622 638 ;
+C 252 ; WX 600 ; N udieresis ; B 70 -15 595 638 ;
+C -1 ; WX 600 ; N notequal ; B 30 -47 626 563 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 40 -146 674 714 ;
+C 240 ; WX 600 ; N eth ; B 93 -27 661 626 ;
+C 158 ; WX 600 ; N zcaron ; B 81 0 643 667 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 18 -250 615 454 ;
+C 185 ; WX 600 ; N onesuperior ; B 212 230 514 616 ;
+C -1 ; WX 600 ; N imacron ; B 77 0 575 585 ;
+C 128 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Oblique.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Oblique.afm
new file mode 100644
index 0000000..87e2154
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier-Oblique.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 0:00:00 17:37:52 1997
+Comment UniqueID 43051
+Comment VMusage 16248 75829
+FontName Courier-Oblique
+FullName Courier Oblique
+FamilyName Courier
+Weight Medium
+ItalicAngle -12
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -27 -250 849 805
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme WinAnsiEncoding
+CapHeight 562
+XHeight 426
+Ascender 629
+Descender -157
+StdHW 51
+StdVW 51
+StartCharMetrics 317
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 243 -15 464 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 273 328 532 562 ;
+C 35 ; WX 600 ; N numbersign ; B 133 -32 596 639 ;
+C 36 ; WX 600 ; N dollar ; B 108 -126 596 662 ;
+C 37 ; WX 600 ; N percent ; B 134 -15 599 622 ;
+C 38 ; WX 600 ; N ampersand ; B 87 -15 580 543 ;
+C 146 ; WX 600 ; N quoteright ; B 283 328 495 562 ;
+C 40 ; WX 600 ; N parenleft ; B 313 -108 572 622 ;
+C 41 ; WX 600 ; N parenright ; B 137 -108 396 622 ;
+C 42 ; WX 600 ; N asterisk ; B 212 257 580 607 ;
+C 43 ; WX 600 ; N plus ; B 129 44 580 470 ;
+C 44 ; WX 600 ; N comma ; B 157 -112 370 122 ;
+C 45 ; WX 600 ; N hyphen ; B 152 231 558 285 ;
+C 173 ; WX 600 ; N hyphen ; B 152 231 558 285 ;
+C 46 ; WX 600 ; N period ; B 238 -15 382 109 ;
+C 47 ; WX 600 ; N slash ; B 112 -80 604 629 ;
+C 48 ; WX 600 ; N zero ; B 154 -15 575 622 ;
+C 49 ; WX 600 ; N one ; B 98 0 515 622 ;
+C 50 ; WX 600 ; N two ; B 70 0 568 622 ;
+C 51 ; WX 600 ; N three ; B 82 -15 538 622 ;
+C 52 ; WX 600 ; N four ; B 108 0 541 622 ;
+C 53 ; WX 600 ; N five ; B 99 -15 589 607 ;
+C 54 ; WX 600 ; N six ; B 155 -15 629 622 ;
+C 55 ; WX 600 ; N seven ; B 182 0 612 607 ;
+C 56 ; WX 600 ; N eight ; B 132 -15 588 622 ;
+C 57 ; WX 600 ; N nine ; B 93 -15 574 622 ;
+C 58 ; WX 600 ; N colon ; B 238 -15 441 385 ;
+C 59 ; WX 600 ; N semicolon ; B 157 -112 441 385 ;
+C 60 ; WX 600 ; N less ; B 96 42 610 472 ;
+C 61 ; WX 600 ; N equal ; B 109 138 600 376 ;
+C 62 ; WX 600 ; N greater ; B 85 42 599 472 ;
+C 63 ; WX 600 ; N question ; B 222 -15 583 572 ;
+C 64 ; WX 600 ; N at ; B 127 -15 582 622 ;
+C 65 ; WX 600 ; N A ; B 3 0 607 562 ;
+C 66 ; WX 600 ; N B ; B 43 0 616 562 ;
+C 67 ; WX 600 ; N C ; B 93 -18 655 580 ;
+C 68 ; WX 600 ; N D ; B 43 0 645 562 ;
+C 69 ; WX 600 ; N E ; B 53 0 660 562 ;
+C 70 ; WX 600 ; N F ; B 53 0 660 562 ;
+C 71 ; WX 600 ; N G ; B 83 -18 645 580 ;
+C 72 ; WX 600 ; N H ; B 32 0 687 562 ;
+C 73 ; WX 600 ; N I ; B 96 0 623 562 ;
+C 74 ; WX 600 ; N J ; B 52 -18 685 562 ;
+C 75 ; WX 600 ; N K ; B 38 0 671 562 ;
+C 76 ; WX 600 ; N L ; B 47 0 607 562 ;
+C 77 ; WX 600 ; N M ; B 4 0 715 562 ;
+C 78 ; WX 600 ; N N ; B 7 -13 712 562 ;
+C 79 ; WX 600 ; N O ; B 94 -18 625 580 ;
+C 80 ; WX 600 ; N P ; B 79 0 644 562 ;
+C 81 ; WX 600 ; N Q ; B 95 -138 625 580 ;
+C 82 ; WX 600 ; N R ; B 38 0 598 562 ;
+C 83 ; WX 600 ; N S ; B 76 -20 650 580 ;
+C 84 ; WX 600 ; N T ; B 108 0 665 562 ;
+C 85 ; WX 600 ; N U ; B 125 -18 702 562 ;
+C 86 ; WX 600 ; N V ; B 105 -13 723 562 ;
+C 87 ; WX 600 ; N W ; B 106 -13 722 562 ;
+C 88 ; WX 600 ; N X ; B 23 0 675 562 ;
+C 89 ; WX 600 ; N Y ; B 133 0 695 562 ;
+C 90 ; WX 600 ; N Z ; B 86 0 610 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 246 -108 574 622 ;
+C 92 ; WX 600 ; N backslash ; B 249 -80 468 629 ;
+C 93 ; WX 600 ; N bracketright ; B 135 -108 463 622 ;
+C 94 ; WX 600 ; N asciicircum ; B 175 354 587 622 ;
+C 95 ; WX 600 ; N underscore ; B -27 -125 584 -75 ;
+C 145 ; WX 600 ; N quoteleft ; B 343 328 457 562 ;
+C 97 ; WX 600 ; N a ; B 76 -15 569 441 ;
+C 98 ; WX 600 ; N b ; B 29 -15 625 629 ;
+C 99 ; WX 600 ; N c ; B 106 -15 608 441 ;
+C 100 ; WX 600 ; N d ; B 85 -15 640 629 ;
+C 101 ; WX 600 ; N e ; B 106 -15 598 441 ;
+C 102 ; WX 600 ; N f ; B 114 0 662 629 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 61 -157 657 441 ;
+C 104 ; WX 600 ; N h ; B 33 0 592 629 ;
+C 105 ; WX 600 ; N i ; B 95 0 515 657 ;
+C 106 ; WX 600 ; N j ; B 52 -157 550 657 ;
+C 107 ; WX 600 ; N k ; B 58 0 633 629 ;
+C 108 ; WX 600 ; N l ; B 95 0 515 629 ;
+C 109 ; WX 600 ; N m ; B -5 0 615 441 ;
+C 110 ; WX 600 ; N n ; B 26 0 585 441 ;
+C 111 ; WX 600 ; N o ; B 102 -15 588 441 ;
+C 112 ; WX 600 ; N p ; B -24 -157 605 441 ;
+C 113 ; WX 600 ; N q ; B 85 -157 682 441 ;
+C 114 ; WX 600 ; N r ; B 60 0 636 441 ;
+C 115 ; WX 600 ; N s ; B 78 -15 584 441 ;
+C 116 ; WX 600 ; N t ; B 167 -15 561 561 ;
+C 117 ; WX 600 ; N u ; B 101 -15 572 426 ;
+C 118 ; WX 600 ; N v ; B 90 -10 681 426 ;
+C 119 ; WX 600 ; N w ; B 76 -10 695 426 ;
+C 120 ; WX 600 ; N x ; B 20 0 655 426 ;
+C 121 ; WX 600 ; N y ; B -4 -157 683 426 ;
+C 122 ; WX 600 ; N z ; B 99 0 593 426 ;
+C 123 ; WX 600 ; N braceleft ; B 233 -108 569 622 ;
+C 124 ; WX 600 ; N bar ; B 222 -250 485 750 ;
+C 125 ; WX 600 ; N braceright ; B 140 -108 477 622 ;
+C 126 ; WX 600 ; N asciitilde ; B 116 197 600 320 ;
+C 161 ; WX 600 ; N exclamdown ; B 225 -157 445 430 ;
+C 162 ; WX 600 ; N cent ; B 151 -49 588 614 ;
+C 163 ; WX 600 ; N sterling ; B 124 -21 621 611 ;
+C -1 ; WX 600 ; N fraction ; B 84 -57 646 665 ;
+C 165 ; WX 600 ; N yen ; B 120 0 693 562 ;
+C 131 ; WX 600 ; N florin ; B -26 -143 671 622 ;
+C 167 ; WX 600 ; N section ; B 104 -78 590 580 ;
+C 164 ; WX 600 ; N currency ; B 94 58 628 506 ;
+C 39 ; WX 600 ; N quotesingle ; B 345 328 460 562 ;
+C 147 ; WX 600 ; N quotedblleft ; B 262 328 541 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 92 70 652 446 ;
+C 139 ; WX 600 ; N guilsinglleft ; B 204 70 540 446 ;
+C 155 ; WX 600 ; N guilsinglright ; B 170 70 506 446 ;
+C -1 ; WX 600 ; N fi ; B 3 0 619 629 ;
+C -1 ; WX 600 ; N fl ; B 3 0 619 629 ;
+C 150 ; WX 600 ; N endash ; B 124 231 586 285 ;
+C 134 ; WX 600 ; N dagger ; B 217 -78 546 580 ;
+C 135 ; WX 600 ; N daggerdbl ; B 163 -78 546 580 ;
+C 183 ; WX 600 ; N periodcentered ; B 275 189 434 327 ;
+C 182 ; WX 600 ; N paragraph ; B 100 -78 630 562 ;
+C 149 ; WX 600 ; N bullet ; B 224 130 485 383 ;
+C 130 ; WX 600 ; N quotesinglbase ; B 185 -134 397 100 ;
+C 132 ; WX 600 ; N quotedblbase ; B 115 -134 478 100 ;
+C 148 ; WX 600 ; N quotedblright ; B 213 328 576 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 58 70 618 446 ;
+C 133 ; WX 600 ; N ellipsis ; B 46 -15 575 111 ;
+C 137 ; WX 600 ; N perthousand ; B 59 -15 627 622 ;
+C 191 ; WX 600 ; N questiondown ; B 105 -157 466 430 ;
+C 96 ; WX 600 ; N grave ; B 294 497 484 672 ;
+C 180 ; WX 600 ; N acute ; B 348 497 612 672 ;
+C 136 ; WX 600 ; N circumflex ; B 229 477 581 654 ;
+C 152 ; WX 600 ; N tilde ; B 212 489 629 606 ;
+C 175 ; WX 600 ; N macron ; B 232 525 600 565 ;
+C -1 ; WX 600 ; N breve ; B 279 501 576 609 ;
+C -1 ; WX 600 ; N dotaccent ; B 373 537 478 640 ;
+C 168 ; WX 600 ; N dieresis ; B 272 537 579 640 ;
+C -1 ; WX 600 ; N ring ; B 332 463 500 627 ;
+C 184 ; WX 600 ; N cedilla ; B 197 -151 344 10 ;
+C -1 ; WX 600 ; N hungarumlaut ; B 239 497 683 672 ;
+C -1 ; WX 600 ; N ogonek ; B 189 -172 377 4 ;
+C -1 ; WX 600 ; N caron ; B 262 492 614 669 ;
+C 151 ; WX 600 ; N emdash ; B 49 231 661 285 ;
+C 198 ; WX 600 ; N AE ; B 3 0 655 562 ;
+C 170 ; WX 600 ; N ordfeminine ; B 209 249 512 580 ;
+C -1 ; WX 600 ; N Lslash ; B 47 0 607 562 ;
+C 216 ; WX 600 ; N Oslash ; B 94 -80 625 629 ;
+C 140 ; WX 600 ; N OE ; B 59 0 672 562 ;
+C 186 ; WX 600 ; N ordmasculine ; B 210 249 535 580 ;
+C 230 ; WX 600 ; N ae ; B 41 -15 626 441 ;
+C -1 ; WX 600 ; N dotlessi ; B 95 0 515 426 ;
+C -1 ; WX 600 ; N lslash ; B 95 0 587 629 ;
+C 248 ; WX 600 ; N oslash ; B 102 -80 588 506 ;
+C 156 ; WX 600 ; N oe ; B 54 -15 615 441 ;
+C 223 ; WX 600 ; N germandbls ; B 48 -15 617 629 ;
+C 207 ; WX 600 ; N Idieresis ; B 96 0 623 753 ;
+C 233 ; WX 600 ; N eacute ; B 106 -15 612 672 ;
+C -1 ; WX 600 ; N abreve ; B 76 -15 576 609 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B 101 -15 723 672 ;
+C -1 ; WX 600 ; N ecaron ; B 106 -15 614 669 ;
+C 159 ; WX 600 ; N Ydieresis ; B 133 0 695 753 ;
+C 247 ; WX 600 ; N divide ; B 136 48 573 467 ;
+C 221 ; WX 600 ; N Yacute ; B 133 0 695 805 ;
+C 194 ; WX 600 ; N Acircumflex ; B 3 0 607 787 ;
+C 225 ; WX 600 ; N aacute ; B 76 -15 612 672 ;
+C 219 ; WX 600 ; N Ucircumflex ; B 125 -18 702 787 ;
+C 253 ; WX 600 ; N yacute ; B -4 -157 683 672 ;
+C -1 ; WX 600 ; N scommaaccent ; B 78 -250 584 441 ;
+C 234 ; WX 600 ; N ecircumflex ; B 106 -15 598 654 ;
+C -1 ; WX 600 ; N Uring ; B 125 -18 702 760 ;
+C 220 ; WX 600 ; N Udieresis ; B 125 -18 702 753 ;
+C -1 ; WX 600 ; N aogonek ; B 76 -172 569 441 ;
+C 218 ; WX 600 ; N Uacute ; B 125 -18 702 805 ;
+C -1 ; WX 600 ; N uogonek ; B 101 -172 572 426 ;
+C 203 ; WX 600 ; N Edieresis ; B 53 0 660 753 ;
+C -1 ; WX 600 ; N Dcroat ; B 43 0 645 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 145 -250 323 -58 ;
+C 169 ; WX 600 ; N copyright ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Emacron ; B 53 0 660 698 ;
+C -1 ; WX 600 ; N ccaron ; B 106 -15 614 669 ;
+C 229 ; WX 600 ; N aring ; B 76 -15 569 627 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 7 -250 712 562 ;
+C -1 ; WX 600 ; N lacute ; B 95 0 640 805 ;
+C 224 ; WX 600 ; N agrave ; B 76 -15 569 672 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 108 -250 665 562 ;
+C -1 ; WX 600 ; N Cacute ; B 93 -18 655 805 ;
+C 227 ; WX 600 ; N atilde ; B 76 -15 629 606 ;
+C -1 ; WX 600 ; N Edotaccent ; B 53 0 660 753 ;
+C 154 ; WX 600 ; N scaron ; B 78 -15 614 669 ;
+C -1 ; WX 600 ; N scedilla ; B 78 -151 584 441 ;
+C 237 ; WX 600 ; N iacute ; B 95 0 612 672 ;
+C -1 ; WX 600 ; N lozenge ; B 94 0 519 706 ;
+C -1 ; WX 600 ; N Rcaron ; B 38 0 642 802 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 83 -250 645 580 ;
+C 251 ; WX 600 ; N ucircumflex ; B 101 -15 572 654 ;
+C 226 ; WX 600 ; N acircumflex ; B 76 -15 581 654 ;
+C -1 ; WX 600 ; N Amacron ; B 3 0 607 698 ;
+C -1 ; WX 600 ; N rcaron ; B 60 0 636 669 ;
+C 231 ; WX 600 ; N ccedilla ; B 106 -151 614 441 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 86 0 610 753 ;
+C 222 ; WX 600 ; N Thorn ; B 79 0 606 562 ;
+C -1 ; WX 600 ; N Omacron ; B 94 -18 628 698 ;
+C -1 ; WX 600 ; N Racute ; B 38 0 670 805 ;
+C -1 ; WX 600 ; N Sacute ; B 76 -20 650 805 ;
+C -1 ; WX 600 ; N dcaron ; B 85 -15 849 629 ;
+C -1 ; WX 600 ; N Umacron ; B 125 -18 702 698 ;
+C -1 ; WX 600 ; N uring ; B 101 -15 572 627 ;
+C 179 ; WX 600 ; N threesuperior ; B 213 240 501 622 ;
+C 210 ; WX 600 ; N Ograve ; B 94 -18 625 805 ;
+C 192 ; WX 600 ; N Agrave ; B 3 0 607 805 ;
+C -1 ; WX 600 ; N Abreve ; B 3 0 607 732 ;
+C 215 ; WX 600 ; N multiply ; B 103 43 607 470 ;
+C 250 ; WX 600 ; N uacute ; B 101 -15 602 672 ;
+C -1 ; WX 600 ; N Tcaron ; B 108 0 665 802 ;
+C -1 ; WX 600 ; N partialdiff ; B 45 -38 546 710 ;
+C 255 ; WX 600 ; N ydieresis ; B -4 -157 683 620 ;
+C -1 ; WX 600 ; N Nacute ; B 7 -13 712 805 ;
+C 238 ; WX 600 ; N icircumflex ; B 95 0 551 654 ;
+C 202 ; WX 600 ; N Ecircumflex ; B 53 0 660 787 ;
+C 228 ; WX 600 ; N adieresis ; B 76 -15 575 620 ;
+C 235 ; WX 600 ; N edieresis ; B 106 -15 598 620 ;
+C -1 ; WX 600 ; N cacute ; B 106 -15 612 672 ;
+C -1 ; WX 600 ; N nacute ; B 26 0 602 672 ;
+C -1 ; WX 600 ; N umacron ; B 101 -15 600 565 ;
+C -1 ; WX 600 ; N Ncaron ; B 7 -13 712 802 ;
+C 205 ; WX 600 ; N Iacute ; B 96 0 640 805 ;
+C 177 ; WX 600 ; N plusminus ; B 96 44 594 558 ;
+C 166 ; WX 600 ; N brokenbar ; B 238 -175 469 675 ;
+C 174 ; WX 600 ; N registered ; B 53 -18 667 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 83 -18 645 732 ;
+C -1 ; WX 600 ; N Idotaccent ; B 96 0 623 753 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 670 706 ;
+C 200 ; WX 600 ; N Egrave ; B 53 0 660 805 ;
+C -1 ; WX 600 ; N racute ; B 60 0 636 672 ;
+C -1 ; WX 600 ; N omacron ; B 102 -15 600 565 ;
+C -1 ; WX 600 ; N Zacute ; B 86 0 670 805 ;
+C 142 ; WX 600 ; N Zcaron ; B 86 0 642 802 ;
+C -1 ; WX 600 ; N greaterequal ; B 98 0 594 710 ;
+C 208 ; WX 600 ; N Eth ; B 43 0 645 562 ;
+C 199 ; WX 600 ; N Ccedilla ; B 93 -151 658 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 95 -250 515 629 ;
+C -1 ; WX 600 ; N tcaron ; B 167 -15 587 717 ;
+C -1 ; WX 600 ; N eogonek ; B 106 -172 598 441 ;
+C -1 ; WX 600 ; N Uogonek ; B 124 -172 702 562 ;
+C 193 ; WX 600 ; N Aacute ; B 3 0 660 805 ;
+C 196 ; WX 600 ; N Adieresis ; B 3 0 607 753 ;
+C 232 ; WX 600 ; N egrave ; B 106 -15 598 672 ;
+C -1 ; WX 600 ; N zacute ; B 99 0 612 672 ;
+C -1 ; WX 600 ; N iogonek ; B 95 -172 515 657 ;
+C 211 ; WX 600 ; N Oacute ; B 94 -18 640 805 ;
+C 243 ; WX 600 ; N oacute ; B 102 -15 612 672 ;
+C -1 ; WX 600 ; N amacron ; B 76 -15 600 565 ;
+C -1 ; WX 600 ; N sacute ; B 78 -15 612 672 ;
+C 239 ; WX 600 ; N idieresis ; B 95 0 545 620 ;
+C 212 ; WX 600 ; N Ocircumflex ; B 94 -18 625 787 ;
+C 217 ; WX 600 ; N Ugrave ; B 125 -18 702 805 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 598 688 ;
+C 254 ; WX 600 ; N thorn ; B -24 -157 605 629 ;
+C 178 ; WX 600 ; N twosuperior ; B 230 249 535 622 ;
+C 214 ; WX 600 ; N Odieresis ; B 94 -18 625 753 ;
+C 181 ; WX 600 ; N mu ; B 72 -157 572 426 ;
+C 236 ; WX 600 ; N igrave ; B 95 0 515 672 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 102 -15 723 672 ;
+C -1 ; WX 600 ; N Eogonek ; B 53 -172 660 562 ;
+C -1 ; WX 600 ; N dcroat ; B 85 -15 704 629 ;
+C 190 ; WX 600 ; N threequarters ; B 73 -56 659 666 ;
+C -1 ; WX 600 ; N Scedilla ; B 76 -151 650 580 ;
+C -1 ; WX 600 ; N lcaron ; B 95 0 667 629 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 38 -250 671 562 ;
+C -1 ; WX 600 ; N Lacute ; B 47 0 607 805 ;
+C 153 ; WX 600 ; N trademark ; B 75 263 742 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 106 -15 598 620 ;
+C 204 ; WX 600 ; N Igrave ; B 96 0 623 805 ;
+C -1 ; WX 600 ; N Imacron ; B 96 0 628 698 ;
+C -1 ; WX 600 ; N Lcaron ; B 47 0 632 562 ;
+C 189 ; WX 600 ; N onehalf ; B 65 -57 669 665 ;
+C -1 ; WX 600 ; N lessequal ; B 98 0 645 710 ;
+C 244 ; WX 600 ; N ocircumflex ; B 102 -15 588 654 ;
+C 241 ; WX 600 ; N ntilde ; B 26 0 629 606 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 125 -18 761 805 ;
+C 201 ; WX 600 ; N Eacute ; B 53 0 670 805 ;
+C -1 ; WX 600 ; N emacron ; B 106 -15 600 565 ;
+C -1 ; WX 600 ; N gbreve ; B 61 -157 657 609 ;
+C 188 ; WX 600 ; N onequarter ; B 65 -57 674 665 ;
+C 138 ; WX 600 ; N Scaron ; B 76 -20 672 802 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 76 -250 650 580 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 94 -18 751 805 ;
+C 176 ; WX 600 ; N degree ; B 214 269 576 622 ;
+C 242 ; WX 600 ; N ograve ; B 102 -15 588 672 ;
+C -1 ; WX 600 ; N Ccaron ; B 93 -18 672 802 ;
+C 249 ; WX 600 ; N ugrave ; B 101 -15 572 672 ;
+C -1 ; WX 600 ; N radical ; B 85 -15 765 792 ;
+C -1 ; WX 600 ; N Dcaron ; B 43 0 645 802 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 60 -250 636 441 ;
+C 209 ; WX 600 ; N Ntilde ; B 7 -13 712 729 ;
+C 245 ; WX 600 ; N otilde ; B 102 -15 629 606 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 38 -250 598 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 47 -250 607 562 ;
+C 195 ; WX 600 ; N Atilde ; B 3 0 655 729 ;
+C -1 ; WX 600 ; N Aogonek ; B 3 -172 607 562 ;
+C 197 ; WX 600 ; N Aring ; B 3 0 607 750 ;
+C 213 ; WX 600 ; N Otilde ; B 94 -18 655 729 ;
+C -1 ; WX 600 ; N zdotaccent ; B 99 0 593 620 ;
+C -1 ; WX 600 ; N Ecaron ; B 53 0 660 802 ;
+C -1 ; WX 600 ; N Iogonek ; B 96 -172 623 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 58 -250 633 629 ;
+C -1 ; WX 600 ; N minus ; B 129 232 580 283 ;
+C 206 ; WX 600 ; N Icircumflex ; B 96 0 623 787 ;
+C -1 ; WX 600 ; N ncaron ; B 26 0 614 669 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 165 -250 561 561 ;
+C 172 ; WX 600 ; N logicalnot ; B 155 108 591 369 ;
+C 246 ; WX 600 ; N odieresis ; B 102 -15 588 620 ;
+C 252 ; WX 600 ; N udieresis ; B 101 -15 575 620 ;
+C -1 ; WX 600 ; N notequal ; B 43 -16 621 529 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 61 -157 657 708 ;
+C 240 ; WX 600 ; N eth ; B 102 -15 639 629 ;
+C 158 ; WX 600 ; N zcaron ; B 99 0 624 669 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 26 -250 585 441 ;
+C 185 ; WX 600 ; N onesuperior ; B 231 249 491 622 ;
+C -1 ; WX 600 ; N imacron ; B 95 0 543 565 ;
+C 128 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier.afm
new file mode 100644
index 0000000..9e7982e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Courier.afm
@@ -0,0 +1,344 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 17:27:09 1997
+Comment UniqueID 43050
+Comment VMusage 39754 50779
+FontName Courier
+FullName Courier
+FamilyName Courier
+Weight Medium
+ItalicAngle 0
+IsFixedPitch true
+CharacterSet ExtendedRoman
+FontBBox -23 -250 715 805
+UnderlinePosition -100
+UnderlineThickness 50
+Version 003.000
+Notice Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+EncodingScheme WinAnsiEncoding
+CapHeight 562
+XHeight 426
+Ascender 629
+Descender -157
+StdHW 51
+StdVW 51
+StartCharMetrics 317
+C 32 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 600 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 600 ; N exclam ; B 236 -15 364 572 ;
+C 34 ; WX 600 ; N quotedbl ; B 187 328 413 562 ;
+C 35 ; WX 600 ; N numbersign ; B 93 -32 507 639 ;
+C 36 ; WX 600 ; N dollar ; B 105 -126 496 662 ;
+C 37 ; WX 600 ; N percent ; B 81 -15 518 622 ;
+C 38 ; WX 600 ; N ampersand ; B 63 -15 538 543 ;
+C 146 ; WX 600 ; N quoteright ; B 213 328 376 562 ;
+C 40 ; WX 600 ; N parenleft ; B 269 -108 440 622 ;
+C 41 ; WX 600 ; N parenright ; B 160 -108 331 622 ;
+C 42 ; WX 600 ; N asterisk ; B 116 257 484 607 ;
+C 43 ; WX 600 ; N plus ; B 80 44 520 470 ;
+C 44 ; WX 600 ; N comma ; B 181 -112 344 122 ;
+C 45 ; WX 600 ; N hyphen ; B 103 231 497 285 ;
+C 173 ; WX 600 ; N hyphen ; B 103 231 497 285 ;
+C 46 ; WX 600 ; N period ; B 229 -15 371 109 ;
+C 47 ; WX 600 ; N slash ; B 125 -80 475 629 ;
+C 48 ; WX 600 ; N zero ; B 106 -15 494 622 ;
+C 49 ; WX 600 ; N one ; B 96 0 505 622 ;
+C 50 ; WX 600 ; N two ; B 70 0 471 622 ;
+C 51 ; WX 600 ; N three ; B 75 -15 466 622 ;
+C 52 ; WX 600 ; N four ; B 78 0 500 622 ;
+C 53 ; WX 600 ; N five ; B 92 -15 497 607 ;
+C 54 ; WX 600 ; N six ; B 111 -15 497 622 ;
+C 55 ; WX 600 ; N seven ; B 82 0 483 607 ;
+C 56 ; WX 600 ; N eight ; B 102 -15 498 622 ;
+C 57 ; WX 600 ; N nine ; B 96 -15 489 622 ;
+C 58 ; WX 600 ; N colon ; B 229 -15 371 385 ;
+C 59 ; WX 600 ; N semicolon ; B 181 -112 371 385 ;
+C 60 ; WX 600 ; N less ; B 41 42 519 472 ;
+C 61 ; WX 600 ; N equal ; B 80 138 520 376 ;
+C 62 ; WX 600 ; N greater ; B 66 42 544 472 ;
+C 63 ; WX 600 ; N question ; B 129 -15 492 572 ;
+C 64 ; WX 600 ; N at ; B 77 -15 533 622 ;
+C 65 ; WX 600 ; N A ; B 3 0 597 562 ;
+C 66 ; WX 600 ; N B ; B 43 0 559 562 ;
+C 67 ; WX 600 ; N C ; B 41 -18 540 580 ;
+C 68 ; WX 600 ; N D ; B 43 0 574 562 ;
+C 69 ; WX 600 ; N E ; B 53 0 550 562 ;
+C 70 ; WX 600 ; N F ; B 53 0 545 562 ;
+C 71 ; WX 600 ; N G ; B 31 -18 575 580 ;
+C 72 ; WX 600 ; N H ; B 32 0 568 562 ;
+C 73 ; WX 600 ; N I ; B 96 0 504 562 ;
+C 74 ; WX 600 ; N J ; B 34 -18 566 562 ;
+C 75 ; WX 600 ; N K ; B 38 0 582 562 ;
+C 76 ; WX 600 ; N L ; B 47 0 554 562 ;
+C 77 ; WX 600 ; N M ; B 4 0 596 562 ;
+C 78 ; WX 600 ; N N ; B 7 -13 593 562 ;
+C 79 ; WX 600 ; N O ; B 43 -18 557 580 ;
+C 80 ; WX 600 ; N P ; B 79 0 558 562 ;
+C 81 ; WX 600 ; N Q ; B 43 -138 557 580 ;
+C 82 ; WX 600 ; N R ; B 38 0 588 562 ;
+C 83 ; WX 600 ; N S ; B 72 -20 529 580 ;
+C 84 ; WX 600 ; N T ; B 38 0 563 562 ;
+C 85 ; WX 600 ; N U ; B 17 -18 583 562 ;
+C 86 ; WX 600 ; N V ; B -4 -13 604 562 ;
+C 87 ; WX 600 ; N W ; B -3 -13 603 562 ;
+C 88 ; WX 600 ; N X ; B 23 0 577 562 ;
+C 89 ; WX 600 ; N Y ; B 24 0 576 562 ;
+C 90 ; WX 600 ; N Z ; B 86 0 514 562 ;
+C 91 ; WX 600 ; N bracketleft ; B 269 -108 442 622 ;
+C 92 ; WX 600 ; N backslash ; B 118 -80 482 629 ;
+C 93 ; WX 600 ; N bracketright ; B 158 -108 331 622 ;
+C 94 ; WX 600 ; N asciicircum ; B 94 354 506 622 ;
+C 95 ; WX 600 ; N underscore ; B 0 -125 600 -75 ;
+C 145 ; WX 600 ; N quoteleft ; B 224 328 387 562 ;
+C 97 ; WX 600 ; N a ; B 53 -15 559 441 ;
+C 98 ; WX 600 ; N b ; B 14 -15 575 629 ;
+C 99 ; WX 600 ; N c ; B 66 -15 529 441 ;
+C 100 ; WX 600 ; N d ; B 45 -15 591 629 ;
+C 101 ; WX 600 ; N e ; B 66 -15 548 441 ;
+C 102 ; WX 600 ; N f ; B 114 0 531 629 ; L i fi ; L l fl ;
+C 103 ; WX 600 ; N g ; B 45 -157 566 441 ;
+C 104 ; WX 600 ; N h ; B 18 0 582 629 ;
+C 105 ; WX 600 ; N i ; B 95 0 505 657 ;
+C 106 ; WX 600 ; N j ; B 82 -157 410 657 ;
+C 107 ; WX 600 ; N k ; B 43 0 580 629 ;
+C 108 ; WX 600 ; N l ; B 95 0 505 629 ;
+C 109 ; WX 600 ; N m ; B -5 0 605 441 ;
+C 110 ; WX 600 ; N n ; B 26 0 575 441 ;
+C 111 ; WX 600 ; N o ; B 62 -15 538 441 ;
+C 112 ; WX 600 ; N p ; B 9 -157 555 441 ;
+C 113 ; WX 600 ; N q ; B 45 -157 591 441 ;
+C 114 ; WX 600 ; N r ; B 60 0 559 441 ;
+C 115 ; WX 600 ; N s ; B 80 -15 513 441 ;
+C 116 ; WX 600 ; N t ; B 87 -15 530 561 ;
+C 117 ; WX 600 ; N u ; B 21 -15 562 426 ;
+C 118 ; WX 600 ; N v ; B 10 -10 590 426 ;
+C 119 ; WX 600 ; N w ; B -4 -10 604 426 ;
+C 120 ; WX 600 ; N x ; B 20 0 580 426 ;
+C 121 ; WX 600 ; N y ; B 7 -157 592 426 ;
+C 122 ; WX 600 ; N z ; B 99 0 502 426 ;
+C 123 ; WX 600 ; N braceleft ; B 182 -108 437 622 ;
+C 124 ; WX 600 ; N bar ; B 275 -250 326 750 ;
+C 125 ; WX 600 ; N braceright ; B 163 -108 418 622 ;
+C 126 ; WX 600 ; N asciitilde ; B 63 197 540 320 ;
+C 161 ; WX 600 ; N exclamdown ; B 236 -157 364 430 ;
+C 162 ; WX 600 ; N cent ; B 96 -49 500 614 ;
+C 163 ; WX 600 ; N sterling ; B 84 -21 521 611 ;
+C -1 ; WX 600 ; N fraction ; B 92 -57 509 665 ;
+C 165 ; WX 600 ; N yen ; B 26 0 574 562 ;
+C 131 ; WX 600 ; N florin ; B 4 -143 539 622 ;
+C 167 ; WX 600 ; N section ; B 113 -78 488 580 ;
+C 164 ; WX 600 ; N currency ; B 73 58 527 506 ;
+C 39 ; WX 600 ; N quotesingle ; B 259 328 341 562 ;
+C 147 ; WX 600 ; N quotedblleft ; B 143 328 471 562 ;
+C 171 ; WX 600 ; N guillemotleft ; B 37 70 563 446 ;
+C 139 ; WX 600 ; N guilsinglleft ; B 149 70 451 446 ;
+C 155 ; WX 600 ; N guilsinglright ; B 149 70 451 446 ;
+C -1 ; WX 600 ; N fi ; B 3 0 597 629 ;
+C -1 ; WX 600 ; N fl ; B 3 0 597 629 ;
+C 150 ; WX 600 ; N endash ; B 75 231 525 285 ;
+C 134 ; WX 600 ; N dagger ; B 141 -78 459 580 ;
+C 135 ; WX 600 ; N daggerdbl ; B 141 -78 459 580 ;
+C 183 ; WX 600 ; N periodcentered ; B 222 189 378 327 ;
+C 182 ; WX 600 ; N paragraph ; B 50 -78 511 562 ;
+C 149 ; WX 600 ; N bullet ; B 172 130 428 383 ;
+C 130 ; WX 600 ; N quotesinglbase ; B 213 -134 376 100 ;
+C 132 ; WX 600 ; N quotedblbase ; B 143 -134 457 100 ;
+C 148 ; WX 600 ; N quotedblright ; B 143 328 457 562 ;
+C 187 ; WX 600 ; N guillemotright ; B 37 70 563 446 ;
+C 133 ; WX 600 ; N ellipsis ; B 37 -15 563 111 ;
+C 137 ; WX 600 ; N perthousand ; B 3 -15 600 622 ;
+C 191 ; WX 600 ; N questiondown ; B 108 -157 471 430 ;
+C 96 ; WX 600 ; N grave ; B 151 497 378 672 ;
+C 180 ; WX 600 ; N acute ; B 242 497 469 672 ;
+C 136 ; WX 600 ; N circumflex ; B 124 477 476 654 ;
+C 152 ; WX 600 ; N tilde ; B 105 489 503 606 ;
+C 175 ; WX 600 ; N macron ; B 120 525 480 565 ;
+C -1 ; WX 600 ; N breve ; B 153 501 447 609 ;
+C -1 ; WX 600 ; N dotaccent ; B 249 537 352 640 ;
+C 168 ; WX 600 ; N dieresis ; B 148 537 453 640 ;
+C -1 ; WX 600 ; N ring ; B 218 463 382 627 ;
+C 184 ; WX 600 ; N cedilla ; B 224 -151 362 10 ;
+C -1 ; WX 600 ; N hungarumlaut ; B 133 497 540 672 ;
+C -1 ; WX 600 ; N ogonek ; B 211 -172 407 4 ;
+C -1 ; WX 600 ; N caron ; B 124 492 476 669 ;
+C 151 ; WX 600 ; N emdash ; B 0 231 600 285 ;
+C 198 ; WX 600 ; N AE ; B 3 0 550 562 ;
+C 170 ; WX 600 ; N ordfeminine ; B 156 249 442 580 ;
+C -1 ; WX 600 ; N Lslash ; B 47 0 554 562 ;
+C 216 ; WX 600 ; N Oslash ; B 43 -80 557 629 ;
+C 140 ; WX 600 ; N OE ; B 7 0 567 562 ;
+C 186 ; WX 600 ; N ordmasculine ; B 157 249 443 580 ;
+C 230 ; WX 600 ; N ae ; B 19 -15 570 441 ;
+C -1 ; WX 600 ; N dotlessi ; B 95 0 505 426 ;
+C -1 ; WX 600 ; N lslash ; B 95 0 505 629 ;
+C 248 ; WX 600 ; N oslash ; B 62 -80 538 506 ;
+C 156 ; WX 600 ; N oe ; B 19 -15 559 441 ;
+C 223 ; WX 600 ; N germandbls ; B 48 -15 588 629 ;
+C 207 ; WX 600 ; N Idieresis ; B 96 0 504 753 ;
+C 233 ; WX 600 ; N eacute ; B 66 -15 548 672 ;
+C -1 ; WX 600 ; N abreve ; B 53 -15 559 609 ;
+C -1 ; WX 600 ; N uhungarumlaut ; B 21 -15 580 672 ;
+C -1 ; WX 600 ; N ecaron ; B 66 -15 548 669 ;
+C 159 ; WX 600 ; N Ydieresis ; B 24 0 576 753 ;
+C 247 ; WX 600 ; N divide ; B 87 48 513 467 ;
+C 221 ; WX 600 ; N Yacute ; B 24 0 576 805 ;
+C 194 ; WX 600 ; N Acircumflex ; B 3 0 597 787 ;
+C 225 ; WX 600 ; N aacute ; B 53 -15 559 672 ;
+C 219 ; WX 600 ; N Ucircumflex ; B 17 -18 583 787 ;
+C 253 ; WX 600 ; N yacute ; B 7 -157 592 672 ;
+C -1 ; WX 600 ; N scommaaccent ; B 80 -250 513 441 ;
+C 234 ; WX 600 ; N ecircumflex ; B 66 -15 548 654 ;
+C -1 ; WX 600 ; N Uring ; B 17 -18 583 760 ;
+C 220 ; WX 600 ; N Udieresis ; B 17 -18 583 753 ;
+C -1 ; WX 600 ; N aogonek ; B 53 -172 587 441 ;
+C 218 ; WX 600 ; N Uacute ; B 17 -18 583 805 ;
+C -1 ; WX 600 ; N uogonek ; B 21 -172 590 426 ;
+C 203 ; WX 600 ; N Edieresis ; B 53 0 550 753 ;
+C -1 ; WX 600 ; N Dcroat ; B 30 0 574 562 ;
+C -1 ; WX 600 ; N commaaccent ; B 198 -250 335 -58 ;
+C 169 ; WX 600 ; N copyright ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Emacron ; B 53 0 550 698 ;
+C -1 ; WX 600 ; N ccaron ; B 66 -15 529 669 ;
+C 229 ; WX 600 ; N aring ; B 53 -15 559 627 ;
+C -1 ; WX 600 ; N Ncommaaccent ; B 7 -250 593 562 ;
+C -1 ; WX 600 ; N lacute ; B 95 0 505 805 ;
+C 224 ; WX 600 ; N agrave ; B 53 -15 559 672 ;
+C -1 ; WX 600 ; N Tcommaaccent ; B 38 -250 563 562 ;
+C -1 ; WX 600 ; N Cacute ; B 41 -18 540 805 ;
+C 227 ; WX 600 ; N atilde ; B 53 -15 559 606 ;
+C -1 ; WX 600 ; N Edotaccent ; B 53 0 550 753 ;
+C 154 ; WX 600 ; N scaron ; B 80 -15 513 669 ;
+C -1 ; WX 600 ; N scedilla ; B 80 -151 513 441 ;
+C 237 ; WX 600 ; N iacute ; B 95 0 505 672 ;
+C -1 ; WX 600 ; N lozenge ; B 18 0 443 706 ;
+C -1 ; WX 600 ; N Rcaron ; B 38 0 588 802 ;
+C -1 ; WX 600 ; N Gcommaaccent ; B 31 -250 575 580 ;
+C 251 ; WX 600 ; N ucircumflex ; B 21 -15 562 654 ;
+C 226 ; WX 600 ; N acircumflex ; B 53 -15 559 654 ;
+C -1 ; WX 600 ; N Amacron ; B 3 0 597 698 ;
+C -1 ; WX 600 ; N rcaron ; B 60 0 559 669 ;
+C 231 ; WX 600 ; N ccedilla ; B 66 -151 529 441 ;
+C -1 ; WX 600 ; N Zdotaccent ; B 86 0 514 753 ;
+C 222 ; WX 600 ; N Thorn ; B 79 0 538 562 ;
+C -1 ; WX 600 ; N Omacron ; B 43 -18 557 698 ;
+C -1 ; WX 600 ; N Racute ; B 38 0 588 805 ;
+C -1 ; WX 600 ; N Sacute ; B 72 -20 529 805 ;
+C -1 ; WX 600 ; N dcaron ; B 45 -15 715 629 ;
+C -1 ; WX 600 ; N Umacron ; B 17 -18 583 698 ;
+C -1 ; WX 600 ; N uring ; B 21 -15 562 627 ;
+C 179 ; WX 600 ; N threesuperior ; B 155 240 406 622 ;
+C 210 ; WX 600 ; N Ograve ; B 43 -18 557 805 ;
+C 192 ; WX 600 ; N Agrave ; B 3 0 597 805 ;
+C -1 ; WX 600 ; N Abreve ; B 3 0 597 732 ;
+C 215 ; WX 600 ; N multiply ; B 87 43 515 470 ;
+C 250 ; WX 600 ; N uacute ; B 21 -15 562 672 ;
+C -1 ; WX 600 ; N Tcaron ; B 38 0 563 802 ;
+C -1 ; WX 600 ; N partialdiff ; B 17 -38 459 710 ;
+C 255 ; WX 600 ; N ydieresis ; B 7 -157 592 620 ;
+C -1 ; WX 600 ; N Nacute ; B 7 -13 593 805 ;
+C 238 ; WX 600 ; N icircumflex ; B 94 0 505 654 ;
+C 202 ; WX 600 ; N Ecircumflex ; B 53 0 550 787 ;
+C 228 ; WX 600 ; N adieresis ; B 53 -15 559 620 ;
+C 235 ; WX 600 ; N edieresis ; B 66 -15 548 620 ;
+C -1 ; WX 600 ; N cacute ; B 66 -15 529 672 ;
+C -1 ; WX 600 ; N nacute ; B 26 0 575 672 ;
+C -1 ; WX 600 ; N umacron ; B 21 -15 562 565 ;
+C -1 ; WX 600 ; N Ncaron ; B 7 -13 593 802 ;
+C 205 ; WX 600 ; N Iacute ; B 96 0 504 805 ;
+C 177 ; WX 600 ; N plusminus ; B 87 44 513 558 ;
+C 166 ; WX 600 ; N brokenbar ; B 275 -175 326 675 ;
+C 174 ; WX 600 ; N registered ; B 0 -18 600 580 ;
+C -1 ; WX 600 ; N Gbreve ; B 31 -18 575 732 ;
+C -1 ; WX 600 ; N Idotaccent ; B 96 0 504 753 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
+C 200 ; WX 600 ; N Egrave ; B 53 0 550 805 ;
+C -1 ; WX 600 ; N racute ; B 60 0 559 672 ;
+C -1 ; WX 600 ; N omacron ; B 62 -15 538 565 ;
+C -1 ; WX 600 ; N Zacute ; B 86 0 514 805 ;
+C 142 ; WX 600 ; N Zcaron ; B 86 0 514 802 ;
+C -1 ; WX 600 ; N greaterequal ; B 98 0 502 710 ;
+C 208 ; WX 600 ; N Eth ; B 30 0 574 562 ;
+C 199 ; WX 600 ; N Ccedilla ; B 41 -151 540 580 ;
+C -1 ; WX 600 ; N lcommaaccent ; B 95 -250 505 629 ;
+C -1 ; WX 600 ; N tcaron ; B 87 -15 530 717 ;
+C -1 ; WX 600 ; N eogonek ; B 66 -172 548 441 ;
+C -1 ; WX 600 ; N Uogonek ; B 17 -172 583 562 ;
+C 193 ; WX 600 ; N Aacute ; B 3 0 597 805 ;
+C 196 ; WX 600 ; N Adieresis ; B 3 0 597 753 ;
+C 232 ; WX 600 ; N egrave ; B 66 -15 548 672 ;
+C -1 ; WX 600 ; N zacute ; B 99 0 502 672 ;
+C -1 ; WX 600 ; N iogonek ; B 95 -172 505 657 ;
+C 211 ; WX 600 ; N Oacute ; B 43 -18 557 805 ;
+C 243 ; WX 600 ; N oacute ; B 62 -15 538 672 ;
+C -1 ; WX 600 ; N amacron ; B 53 -15 559 565 ;
+C -1 ; WX 600 ; N sacute ; B 80 -15 513 672 ;
+C 239 ; WX 600 ; N idieresis ; B 95 0 505 620 ;
+C 212 ; WX 600 ; N Ocircumflex ; B 43 -18 557 787 ;
+C 217 ; WX 600 ; N Ugrave ; B 17 -18 583 805 ;
+C -1 ; WX 600 ; N Delta ; B 6 0 598 688 ;
+C 254 ; WX 600 ; N thorn ; B -6 -157 555 629 ;
+C 178 ; WX 600 ; N twosuperior ; B 177 249 424 622 ;
+C 214 ; WX 600 ; N Odieresis ; B 43 -18 557 753 ;
+C 181 ; WX 600 ; N mu ; B 21 -157 562 426 ;
+C 236 ; WX 600 ; N igrave ; B 95 0 505 672 ;
+C -1 ; WX 600 ; N ohungarumlaut ; B 62 -15 580 672 ;
+C -1 ; WX 600 ; N Eogonek ; B 53 -172 561 562 ;
+C -1 ; WX 600 ; N dcroat ; B 45 -15 591 629 ;
+C 190 ; WX 600 ; N threequarters ; B 8 -56 593 666 ;
+C -1 ; WX 600 ; N Scedilla ; B 72 -151 529 580 ;
+C -1 ; WX 600 ; N lcaron ; B 95 0 533 629 ;
+C -1 ; WX 600 ; N Kcommaaccent ; B 38 -250 582 562 ;
+C -1 ; WX 600 ; N Lacute ; B 47 0 554 805 ;
+C 153 ; WX 600 ; N trademark ; B -23 263 623 562 ;
+C -1 ; WX 600 ; N edotaccent ; B 66 -15 548 620 ;
+C 204 ; WX 600 ; N Igrave ; B 96 0 504 805 ;
+C -1 ; WX 600 ; N Imacron ; B 96 0 504 698 ;
+C -1 ; WX 600 ; N Lcaron ; B 47 0 554 562 ;
+C 189 ; WX 600 ; N onehalf ; B 0 -57 611 665 ;
+C -1 ; WX 600 ; N lessequal ; B 98 0 502 710 ;
+C 244 ; WX 600 ; N ocircumflex ; B 62 -15 538 654 ;
+C 241 ; WX 600 ; N ntilde ; B 26 0 575 606 ;
+C -1 ; WX 600 ; N Uhungarumlaut ; B 17 -18 590 805 ;
+C 201 ; WX 600 ; N Eacute ; B 53 0 550 805 ;
+C -1 ; WX 600 ; N emacron ; B 66 -15 548 565 ;
+C -1 ; WX 600 ; N gbreve ; B 45 -157 566 609 ;
+C 188 ; WX 600 ; N onequarter ; B 0 -57 600 665 ;
+C 138 ; WX 600 ; N Scaron ; B 72 -20 529 802 ;
+C -1 ; WX 600 ; N Scommaaccent ; B 72 -250 529 580 ;
+C -1 ; WX 600 ; N Ohungarumlaut ; B 43 -18 580 805 ;
+C 176 ; WX 600 ; N degree ; B 123 269 477 622 ;
+C 242 ; WX 600 ; N ograve ; B 62 -15 538 672 ;
+C -1 ; WX 600 ; N Ccaron ; B 41 -18 540 802 ;
+C 249 ; WX 600 ; N ugrave ; B 21 -15 562 672 ;
+C -1 ; WX 600 ; N radical ; B 3 -15 597 792 ;
+C -1 ; WX 600 ; N Dcaron ; B 43 0 574 802 ;
+C -1 ; WX 600 ; N rcommaaccent ; B 60 -250 559 441 ;
+C 209 ; WX 600 ; N Ntilde ; B 7 -13 593 729 ;
+C 245 ; WX 600 ; N otilde ; B 62 -15 538 606 ;
+C -1 ; WX 600 ; N Rcommaaccent ; B 38 -250 588 562 ;
+C -1 ; WX 600 ; N Lcommaaccent ; B 47 -250 554 562 ;
+C 195 ; WX 600 ; N Atilde ; B 3 0 597 729 ;
+C -1 ; WX 600 ; N Aogonek ; B 3 -172 608 562 ;
+C 197 ; WX 600 ; N Aring ; B 3 0 597 750 ;
+C 213 ; WX 600 ; N Otilde ; B 43 -18 557 729 ;
+C -1 ; WX 600 ; N zdotaccent ; B 99 0 502 620 ;
+C -1 ; WX 600 ; N Ecaron ; B 53 0 550 802 ;
+C -1 ; WX 600 ; N Iogonek ; B 96 -172 504 562 ;
+C -1 ; WX 600 ; N kcommaaccent ; B 43 -250 580 629 ;
+C -1 ; WX 600 ; N minus ; B 80 232 520 283 ;
+C 206 ; WX 600 ; N Icircumflex ; B 96 0 504 787 ;
+C -1 ; WX 600 ; N ncaron ; B 26 0 575 669 ;
+C -1 ; WX 600 ; N tcommaaccent ; B 87 -250 530 561 ;
+C 172 ; WX 600 ; N logicalnot ; B 87 108 513 369 ;
+C 246 ; WX 600 ; N odieresis ; B 62 -15 538 620 ;
+C 252 ; WX 600 ; N udieresis ; B 21 -15 562 620 ;
+C -1 ; WX 600 ; N notequal ; B 15 -16 540 529 ;
+C -1 ; WX 600 ; N gcommaaccent ; B 45 -157 566 708 ;
+C 240 ; WX 600 ; N eth ; B 62 -15 538 629 ;
+C 158 ; WX 600 ; N zcaron ; B 99 0 502 669 ;
+C -1 ; WX 600 ; N ncommaaccent ; B 26 -250 575 441 ;
+C 185 ; WX 600 ; N onesuperior ; B 172 249 428 622 ;
+C -1 ; WX 600 ; N imacron ; B 95 0 505 565 ;
+C 128 ; WX 600 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ttf
new file mode 100644
index 0000000..6d65fa7
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ufm
new file mode 100644
index 0000000..e927992
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Bold.ufm
@@ -0,0 +1,6067 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans
+FontSubfamily Bold
+UniqueID DejaVu Sans Bold
+FullName DejaVu Sans Bold
+Version Version 2.37
+PostScriptName DejaVuSans-Bold
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Sans
+PreferredSubfamily Bold
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -1069 -415 1975 1174
+StartCharMetrics 6196
+U 32 ; WX 348 ; N space ; G 3
+U 33 ; WX 456 ; N exclam ; G 4
+U 34 ; WX 521 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 696 ; N dollar ; G 7
+U 37 ; WX 1002 ; N percent ; G 8
+U 38 ; WX 872 ; N ampersand ; G 9
+U 39 ; WX 306 ; N quotesingle ; G 10
+U 40 ; WX 457 ; N parenleft ; G 11
+U 41 ; WX 457 ; N parenright ; G 12
+U 42 ; WX 523 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 380 ; N comma ; G 15
+U 45 ; WX 415 ; N hyphen ; G 16
+U 46 ; WX 380 ; N period ; G 17
+U 47 ; WX 365 ; N slash ; G 18
+U 48 ; WX 696 ; N zero ; G 19
+U 49 ; WX 696 ; N one ; G 20
+U 50 ; WX 696 ; N two ; G 21
+U 51 ; WX 696 ; N three ; G 22
+U 52 ; WX 696 ; N four ; G 23
+U 53 ; WX 696 ; N five ; G 24
+U 54 ; WX 696 ; N six ; G 25
+U 55 ; WX 696 ; N seven ; G 26
+U 56 ; WX 696 ; N eight ; G 27
+U 57 ; WX 696 ; N nine ; G 28
+U 58 ; WX 400 ; N colon ; G 29
+U 59 ; WX 400 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 580 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 774 ; N A ; G 36
+U 66 ; WX 762 ; N B ; G 37
+U 67 ; WX 734 ; N C ; G 38
+U 68 ; WX 830 ; N D ; G 39
+U 69 ; WX 683 ; N E ; G 40
+U 70 ; WX 683 ; N F ; G 41
+U 71 ; WX 821 ; N G ; G 42
+U 72 ; WX 837 ; N H ; G 43
+U 73 ; WX 372 ; N I ; G 44
+U 74 ; WX 372 ; N J ; G 45
+U 75 ; WX 775 ; N K ; G 46
+U 76 ; WX 637 ; N L ; G 47
+U 77 ; WX 995 ; N M ; G 48
+U 78 ; WX 837 ; N N ; G 49
+U 79 ; WX 850 ; N O ; G 50
+U 80 ; WX 733 ; N P ; G 51
+U 81 ; WX 850 ; N Q ; G 52
+U 82 ; WX 770 ; N R ; G 53
+U 83 ; WX 720 ; N S ; G 54
+U 84 ; WX 682 ; N T ; G 55
+U 85 ; WX 812 ; N U ; G 56
+U 86 ; WX 774 ; N V ; G 57
+U 87 ; WX 1103 ; N W ; G 58
+U 88 ; WX 771 ; N X ; G 59
+U 89 ; WX 724 ; N Y ; G 60
+U 90 ; WX 725 ; N Z ; G 61
+U 91 ; WX 457 ; N bracketleft ; G 62
+U 92 ; WX 365 ; N backslash ; G 63
+U 93 ; WX 457 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 675 ; N a ; G 68
+U 98 ; WX 716 ; N b ; G 69
+U 99 ; WX 593 ; N c ; G 70
+U 100 ; WX 716 ; N d ; G 71
+U 101 ; WX 678 ; N e ; G 72
+U 102 ; WX 435 ; N f ; G 73
+U 103 ; WX 716 ; N g ; G 74
+U 104 ; WX 712 ; N h ; G 75
+U 105 ; WX 343 ; N i ; G 76
+U 106 ; WX 343 ; N j ; G 77
+U 107 ; WX 665 ; N k ; G 78
+U 108 ; WX 343 ; N l ; G 79
+U 109 ; WX 1042 ; N m ; G 80
+U 110 ; WX 712 ; N n ; G 81
+U 111 ; WX 687 ; N o ; G 82
+U 112 ; WX 716 ; N p ; G 83
+U 113 ; WX 716 ; N q ; G 84
+U 114 ; WX 493 ; N r ; G 85
+U 115 ; WX 595 ; N s ; G 86
+U 116 ; WX 478 ; N t ; G 87
+U 117 ; WX 712 ; N u ; G 88
+U 118 ; WX 652 ; N v ; G 89
+U 119 ; WX 924 ; N w ; G 90
+U 120 ; WX 645 ; N x ; G 91
+U 121 ; WX 652 ; N y ; G 92
+U 122 ; WX 582 ; N z ; G 93
+U 123 ; WX 712 ; N braceleft ; G 94
+U 124 ; WX 365 ; N bar ; G 95
+U 125 ; WX 712 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 348 ; N nbspace ; G 98
+U 161 ; WX 456 ; N exclamdown ; G 99
+U 162 ; WX 696 ; N cent ; G 100
+U 163 ; WX 696 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 696 ; N yen ; G 103
+U 166 ; WX 365 ; N brokenbar ; G 104
+U 167 ; WX 500 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 564 ; N ordfeminine ; G 108
+U 171 ; WX 646 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 415 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 438 ; N twosuperior ; G 116
+U 179 ; WX 438 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 736 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 380 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 438 ; N onesuperior ; G 123
+U 186 ; WX 564 ; N ordmasculine ; G 124
+U 187 ; WX 646 ; N guillemotright ; G 125
+U 188 ; WX 1035 ; N onequarter ; G 126
+U 189 ; WX 1035 ; N onehalf ; G 127
+U 190 ; WX 1035 ; N threequarters ; G 128
+U 191 ; WX 580 ; N questiondown ; G 129
+U 192 ; WX 774 ; N Agrave ; G 130
+U 193 ; WX 774 ; N Aacute ; G 131
+U 194 ; WX 774 ; N Acircumflex ; G 132
+U 195 ; WX 774 ; N Atilde ; G 133
+U 196 ; WX 774 ; N Adieresis ; G 134
+U 197 ; WX 774 ; N Aring ; G 135
+U 198 ; WX 1085 ; N AE ; G 136
+U 199 ; WX 734 ; N Ccedilla ; G 137
+U 200 ; WX 683 ; N Egrave ; G 138
+U 201 ; WX 683 ; N Eacute ; G 139
+U 202 ; WX 683 ; N Ecircumflex ; G 140
+U 203 ; WX 683 ; N Edieresis ; G 141
+U 204 ; WX 372 ; N Igrave ; G 142
+U 205 ; WX 372 ; N Iacute ; G 143
+U 206 ; WX 372 ; N Icircumflex ; G 144
+U 207 ; WX 372 ; N Idieresis ; G 145
+U 208 ; WX 838 ; N Eth ; G 146
+U 209 ; WX 837 ; N Ntilde ; G 147
+U 210 ; WX 850 ; N Ograve ; G 148
+U 211 ; WX 850 ; N Oacute ; G 149
+U 212 ; WX 850 ; N Ocircumflex ; G 150
+U 213 ; WX 850 ; N Otilde ; G 151
+U 214 ; WX 850 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 850 ; N Oslash ; G 154
+U 217 ; WX 812 ; N Ugrave ; G 155
+U 218 ; WX 812 ; N Uacute ; G 156
+U 219 ; WX 812 ; N Ucircumflex ; G 157
+U 220 ; WX 812 ; N Udieresis ; G 158
+U 221 ; WX 724 ; N Yacute ; G 159
+U 222 ; WX 738 ; N Thorn ; G 160
+U 223 ; WX 719 ; N germandbls ; G 161
+U 224 ; WX 675 ; N agrave ; G 162
+U 225 ; WX 675 ; N aacute ; G 163
+U 226 ; WX 675 ; N acircumflex ; G 164
+U 227 ; WX 675 ; N atilde ; G 165
+U 228 ; WX 675 ; N adieresis ; G 166
+U 229 ; WX 675 ; N aring ; G 167
+U 230 ; WX 1048 ; N ae ; G 168
+U 231 ; WX 593 ; N ccedilla ; G 169
+U 232 ; WX 678 ; N egrave ; G 170
+U 233 ; WX 678 ; N eacute ; G 171
+U 234 ; WX 678 ; N ecircumflex ; G 172
+U 235 ; WX 678 ; N edieresis ; G 173
+U 236 ; WX 343 ; N igrave ; G 174
+U 237 ; WX 343 ; N iacute ; G 175
+U 238 ; WX 343 ; N icircumflex ; G 176
+U 239 ; WX 343 ; N idieresis ; G 177
+U 240 ; WX 687 ; N eth ; G 178
+U 241 ; WX 712 ; N ntilde ; G 179
+U 242 ; WX 687 ; N ograve ; G 180
+U 243 ; WX 687 ; N oacute ; G 181
+U 244 ; WX 687 ; N ocircumflex ; G 182
+U 245 ; WX 687 ; N otilde ; G 183
+U 246 ; WX 687 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 687 ; N oslash ; G 186
+U 249 ; WX 712 ; N ugrave ; G 187
+U 250 ; WX 712 ; N uacute ; G 188
+U 251 ; WX 712 ; N ucircumflex ; G 189
+U 252 ; WX 712 ; N udieresis ; G 190
+U 253 ; WX 652 ; N yacute ; G 191
+U 254 ; WX 716 ; N thorn ; G 192
+U 255 ; WX 652 ; N ydieresis ; G 193
+U 256 ; WX 774 ; N Amacron ; G 194
+U 257 ; WX 675 ; N amacron ; G 195
+U 258 ; WX 774 ; N Abreve ; G 196
+U 259 ; WX 675 ; N abreve ; G 197
+U 260 ; WX 774 ; N Aogonek ; G 198
+U 261 ; WX 675 ; N aogonek ; G 199
+U 262 ; WX 734 ; N Cacute ; G 200
+U 263 ; WX 593 ; N cacute ; G 201
+U 264 ; WX 734 ; N Ccircumflex ; G 202
+U 265 ; WX 593 ; N ccircumflex ; G 203
+U 266 ; WX 734 ; N Cdotaccent ; G 204
+U 267 ; WX 593 ; N cdotaccent ; G 205
+U 268 ; WX 734 ; N Ccaron ; G 206
+U 269 ; WX 593 ; N ccaron ; G 207
+U 270 ; WX 830 ; N Dcaron ; G 208
+U 271 ; WX 716 ; N dcaron ; G 209
+U 272 ; WX 838 ; N Dcroat ; G 210
+U 273 ; WX 716 ; N dmacron ; G 211
+U 274 ; WX 683 ; N Emacron ; G 212
+U 275 ; WX 678 ; N emacron ; G 213
+U 276 ; WX 683 ; N Ebreve ; G 214
+U 277 ; WX 678 ; N ebreve ; G 215
+U 278 ; WX 683 ; N Edotaccent ; G 216
+U 279 ; WX 678 ; N edotaccent ; G 217
+U 280 ; WX 683 ; N Eogonek ; G 218
+U 281 ; WX 678 ; N eogonek ; G 219
+U 282 ; WX 683 ; N Ecaron ; G 220
+U 283 ; WX 678 ; N ecaron ; G 221
+U 284 ; WX 821 ; N Gcircumflex ; G 222
+U 285 ; WX 716 ; N gcircumflex ; G 223
+U 286 ; WX 821 ; N Gbreve ; G 224
+U 287 ; WX 716 ; N gbreve ; G 225
+U 288 ; WX 821 ; N Gdotaccent ; G 226
+U 289 ; WX 716 ; N gdotaccent ; G 227
+U 290 ; WX 821 ; N Gcommaaccent ; G 228
+U 291 ; WX 716 ; N gcommaaccent ; G 229
+U 292 ; WX 837 ; N Hcircumflex ; G 230
+U 293 ; WX 712 ; N hcircumflex ; G 231
+U 294 ; WX 974 ; N Hbar ; G 232
+U 295 ; WX 790 ; N hbar ; G 233
+U 296 ; WX 372 ; N Itilde ; G 234
+U 297 ; WX 343 ; N itilde ; G 235
+U 298 ; WX 372 ; N Imacron ; G 236
+U 299 ; WX 343 ; N imacron ; G 237
+U 300 ; WX 372 ; N Ibreve ; G 238
+U 301 ; WX 343 ; N ibreve ; G 239
+U 302 ; WX 372 ; N Iogonek ; G 240
+U 303 ; WX 343 ; N iogonek ; G 241
+U 304 ; WX 372 ; N Idot ; G 242
+U 305 ; WX 343 ; N dotlessi ; G 243
+U 306 ; WX 744 ; N IJ ; G 244
+U 307 ; WX 686 ; N ij ; G 245
+U 308 ; WX 372 ; N Jcircumflex ; G 246
+U 309 ; WX 343 ; N jcircumflex ; G 247
+U 310 ; WX 775 ; N Kcommaaccent ; G 248
+U 311 ; WX 665 ; N kcommaaccent ; G 249
+U 312 ; WX 665 ; N kgreenlandic ; G 250
+U 313 ; WX 637 ; N Lacute ; G 251
+U 314 ; WX 343 ; N lacute ; G 252
+U 315 ; WX 637 ; N Lcommaaccent ; G 253
+U 316 ; WX 343 ; N lcommaaccent ; G 254
+U 317 ; WX 637 ; N Lcaron ; G 255
+U 318 ; WX 479 ; N lcaron ; G 256
+U 319 ; WX 637 ; N Ldot ; G 257
+U 320 ; WX 557 ; N ldot ; G 258
+U 321 ; WX 642 ; N Lslash ; G 259
+U 322 ; WX 371 ; N lslash ; G 260
+U 323 ; WX 837 ; N Nacute ; G 261
+U 324 ; WX 712 ; N nacute ; G 262
+U 325 ; WX 837 ; N Ncommaaccent ; G 263
+U 326 ; WX 712 ; N ncommaaccent ; G 264
+U 327 ; WX 837 ; N Ncaron ; G 265
+U 328 ; WX 712 ; N ncaron ; G 266
+U 329 ; WX 983 ; N napostrophe ; G 267
+U 330 ; WX 837 ; N Eng ; G 268
+U 331 ; WX 712 ; N eng ; G 269
+U 332 ; WX 850 ; N Omacron ; G 270
+U 333 ; WX 687 ; N omacron ; G 271
+U 334 ; WX 850 ; N Obreve ; G 272
+U 335 ; WX 687 ; N obreve ; G 273
+U 336 ; WX 850 ; N Ohungarumlaut ; G 274
+U 337 ; WX 687 ; N ohungarumlaut ; G 275
+U 338 ; WX 1167 ; N OE ; G 276
+U 339 ; WX 1094 ; N oe ; G 277
+U 340 ; WX 770 ; N Racute ; G 278
+U 341 ; WX 493 ; N racute ; G 279
+U 342 ; WX 770 ; N Rcommaaccent ; G 280
+U 343 ; WX 493 ; N rcommaaccent ; G 281
+U 344 ; WX 770 ; N Rcaron ; G 282
+U 345 ; WX 493 ; N rcaron ; G 283
+U 346 ; WX 720 ; N Sacute ; G 284
+U 347 ; WX 595 ; N sacute ; G 285
+U 348 ; WX 720 ; N Scircumflex ; G 286
+U 349 ; WX 595 ; N scircumflex ; G 287
+U 350 ; WX 720 ; N Scedilla ; G 288
+U 351 ; WX 595 ; N scedilla ; G 289
+U 352 ; WX 720 ; N Scaron ; G 290
+U 353 ; WX 595 ; N scaron ; G 291
+U 354 ; WX 682 ; N Tcommaaccent ; G 292
+U 355 ; WX 478 ; N tcommaaccent ; G 293
+U 356 ; WX 682 ; N Tcaron ; G 294
+U 357 ; WX 478 ; N tcaron ; G 295
+U 358 ; WX 682 ; N Tbar ; G 296
+U 359 ; WX 478 ; N tbar ; G 297
+U 360 ; WX 812 ; N Utilde ; G 298
+U 361 ; WX 712 ; N utilde ; G 299
+U 362 ; WX 812 ; N Umacron ; G 300
+U 363 ; WX 712 ; N umacron ; G 301
+U 364 ; WX 812 ; N Ubreve ; G 302
+U 365 ; WX 712 ; N ubreve ; G 303
+U 366 ; WX 812 ; N Uring ; G 304
+U 367 ; WX 712 ; N uring ; G 305
+U 368 ; WX 812 ; N Uhungarumlaut ; G 306
+U 369 ; WX 712 ; N uhungarumlaut ; G 307
+U 370 ; WX 812 ; N Uogonek ; G 308
+U 371 ; WX 712 ; N uogonek ; G 309
+U 372 ; WX 1103 ; N Wcircumflex ; G 310
+U 373 ; WX 924 ; N wcircumflex ; G 311
+U 374 ; WX 724 ; N Ycircumflex ; G 312
+U 375 ; WX 652 ; N ycircumflex ; G 313
+U 376 ; WX 724 ; N Ydieresis ; G 314
+U 377 ; WX 725 ; N Zacute ; G 315
+U 378 ; WX 582 ; N zacute ; G 316
+U 379 ; WX 725 ; N Zdotaccent ; G 317
+U 380 ; WX 582 ; N zdotaccent ; G 318
+U 381 ; WX 725 ; N Zcaron ; G 319
+U 382 ; WX 582 ; N zcaron ; G 320
+U 383 ; WX 435 ; N longs ; G 321
+U 384 ; WX 716 ; N uni0180 ; G 322
+U 385 ; WX 811 ; N uni0181 ; G 323
+U 386 ; WX 762 ; N uni0182 ; G 324
+U 387 ; WX 716 ; N uni0183 ; G 325
+U 388 ; WX 762 ; N uni0184 ; G 326
+U 389 ; WX 716 ; N uni0185 ; G 327
+U 390 ; WX 734 ; N uni0186 ; G 328
+U 391 ; WX 734 ; N uni0187 ; G 329
+U 392 ; WX 593 ; N uni0188 ; G 330
+U 393 ; WX 838 ; N uni0189 ; G 331
+U 394 ; WX 879 ; N uni018A ; G 332
+U 395 ; WX 757 ; N uni018B ; G 333
+U 396 ; WX 716 ; N uni018C ; G 334
+U 397 ; WX 688 ; N uni018D ; G 335
+U 398 ; WX 683 ; N uni018E ; G 336
+U 399 ; WX 849 ; N uni018F ; G 337
+U 400 ; WX 696 ; N uni0190 ; G 338
+U 401 ; WX 683 ; N uni0191 ; G 339
+U 402 ; WX 435 ; N florin ; G 340
+U 403 ; WX 821 ; N uni0193 ; G 341
+U 404 ; WX 793 ; N uni0194 ; G 342
+U 405 ; WX 1045 ; N uni0195 ; G 343
+U 406 ; WX 436 ; N uni0196 ; G 344
+U 407 ; WX 389 ; N uni0197 ; G 345
+U 408 ; WX 775 ; N uni0198 ; G 346
+U 409 ; WX 665 ; N uni0199 ; G 347
+U 410 ; WX 360 ; N uni019A ; G 348
+U 411 ; WX 592 ; N uni019B ; G 349
+U 412 ; WX 1042 ; N uni019C ; G 350
+U 413 ; WX 837 ; N uni019D ; G 351
+U 414 ; WX 712 ; N uni019E ; G 352
+U 415 ; WX 850 ; N uni019F ; G 353
+U 416 ; WX 874 ; N Ohorn ; G 354
+U 417 ; WX 687 ; N ohorn ; G 355
+U 418 ; WX 1083 ; N uni01A2 ; G 356
+U 419 ; WX 912 ; N uni01A3 ; G 357
+U 420 ; WX 782 ; N uni01A4 ; G 358
+U 421 ; WX 716 ; N uni01A5 ; G 359
+U 422 ; WX 770 ; N uni01A6 ; G 360
+U 423 ; WX 720 ; N uni01A7 ; G 361
+U 424 ; WX 595 ; N uni01A8 ; G 362
+U 425 ; WX 683 ; N uni01A9 ; G 363
+U 426 ; WX 552 ; N uni01AA ; G 364
+U 427 ; WX 478 ; N uni01AB ; G 365
+U 428 ; WX 707 ; N uni01AC ; G 366
+U 429 ; WX 478 ; N uni01AD ; G 367
+U 430 ; WX 682 ; N uni01AE ; G 368
+U 431 ; WX 835 ; N Uhorn ; G 369
+U 432 ; WX 712 ; N uhorn ; G 370
+U 433 ; WX 850 ; N uni01B1 ; G 371
+U 434 ; WX 813 ; N uni01B2 ; G 372
+U 435 ; WX 797 ; N uni01B3 ; G 373
+U 436 ; WX 778 ; N uni01B4 ; G 374
+U 437 ; WX 725 ; N uni01B5 ; G 375
+U 438 ; WX 582 ; N uni01B6 ; G 376
+U 439 ; WX 772 ; N uni01B7 ; G 377
+U 440 ; WX 772 ; N uni01B8 ; G 378
+U 441 ; WX 641 ; N uni01B9 ; G 379
+U 442 ; WX 582 ; N uni01BA ; G 380
+U 443 ; WX 696 ; N uni01BB ; G 381
+U 444 ; WX 772 ; N uni01BC ; G 382
+U 445 ; WX 641 ; N uni01BD ; G 383
+U 446 ; WX 573 ; N uni01BE ; G 384
+U 447 ; WX 716 ; N uni01BF ; G 385
+U 448 ; WX 372 ; N uni01C0 ; G 386
+U 449 ; WX 659 ; N uni01C1 ; G 387
+U 450 ; WX 544 ; N uni01C2 ; G 388
+U 451 ; WX 372 ; N uni01C3 ; G 389
+U 452 ; WX 1555 ; N uni01C4 ; G 390
+U 453 ; WX 1412 ; N uni01C5 ; G 391
+U 454 ; WX 1298 ; N uni01C6 ; G 392
+U 455 ; WX 1009 ; N uni01C7 ; G 393
+U 456 ; WX 980 ; N uni01C8 ; G 394
+U 457 ; WX 686 ; N uni01C9 ; G 395
+U 458 ; WX 1209 ; N uni01CA ; G 396
+U 459 ; WX 1180 ; N uni01CB ; G 397
+U 460 ; WX 1055 ; N uni01CC ; G 398
+U 461 ; WX 774 ; N uni01CD ; G 399
+U 462 ; WX 675 ; N uni01CE ; G 400
+U 463 ; WX 372 ; N uni01CF ; G 401
+U 464 ; WX 343 ; N uni01D0 ; G 402
+U 465 ; WX 850 ; N uni01D1 ; G 403
+U 466 ; WX 687 ; N uni01D2 ; G 404
+U 467 ; WX 812 ; N uni01D3 ; G 405
+U 468 ; WX 712 ; N uni01D4 ; G 406
+U 469 ; WX 812 ; N uni01D5 ; G 407
+U 470 ; WX 712 ; N uni01D6 ; G 408
+U 471 ; WX 812 ; N uni01D7 ; G 409
+U 472 ; WX 712 ; N uni01D8 ; G 410
+U 473 ; WX 812 ; N uni01D9 ; G 411
+U 474 ; WX 712 ; N uni01DA ; G 412
+U 475 ; WX 812 ; N uni01DB ; G 413
+U 476 ; WX 712 ; N uni01DC ; G 414
+U 477 ; WX 678 ; N uni01DD ; G 415
+U 478 ; WX 774 ; N uni01DE ; G 416
+U 479 ; WX 675 ; N uni01DF ; G 417
+U 480 ; WX 774 ; N uni01E0 ; G 418
+U 481 ; WX 675 ; N uni01E1 ; G 419
+U 482 ; WX 1085 ; N uni01E2 ; G 420
+U 483 ; WX 1048 ; N uni01E3 ; G 421
+U 484 ; WX 821 ; N uni01E4 ; G 422
+U 485 ; WX 716 ; N uni01E5 ; G 423
+U 486 ; WX 821 ; N Gcaron ; G 424
+U 487 ; WX 716 ; N gcaron ; G 425
+U 488 ; WX 775 ; N uni01E8 ; G 426
+U 489 ; WX 665 ; N uni01E9 ; G 427
+U 490 ; WX 850 ; N uni01EA ; G 428
+U 491 ; WX 687 ; N uni01EB ; G 429
+U 492 ; WX 850 ; N uni01EC ; G 430
+U 493 ; WX 687 ; N uni01ED ; G 431
+U 494 ; WX 772 ; N uni01EE ; G 432
+U 495 ; WX 582 ; N uni01EF ; G 433
+U 496 ; WX 343 ; N uni01F0 ; G 434
+U 497 ; WX 1555 ; N uni01F1 ; G 435
+U 498 ; WX 1412 ; N uni01F2 ; G 436
+U 499 ; WX 1298 ; N uni01F3 ; G 437
+U 500 ; WX 821 ; N uni01F4 ; G 438
+U 501 ; WX 716 ; N uni01F5 ; G 439
+U 502 ; WX 1289 ; N uni01F6 ; G 440
+U 503 ; WX 787 ; N uni01F7 ; G 441
+U 504 ; WX 837 ; N uni01F8 ; G 442
+U 505 ; WX 712 ; N uni01F9 ; G 443
+U 506 ; WX 774 ; N Aringacute ; G 444
+U 507 ; WX 675 ; N aringacute ; G 445
+U 508 ; WX 1085 ; N AEacute ; G 446
+U 509 ; WX 1048 ; N aeacute ; G 447
+U 510 ; WX 850 ; N Oslashacute ; G 448
+U 511 ; WX 687 ; N oslashacute ; G 449
+U 512 ; WX 774 ; N uni0200 ; G 450
+U 513 ; WX 675 ; N uni0201 ; G 451
+U 514 ; WX 774 ; N uni0202 ; G 452
+U 515 ; WX 675 ; N uni0203 ; G 453
+U 516 ; WX 683 ; N uni0204 ; G 454
+U 517 ; WX 678 ; N uni0205 ; G 455
+U 518 ; WX 683 ; N uni0206 ; G 456
+U 519 ; WX 678 ; N uni0207 ; G 457
+U 520 ; WX 372 ; N uni0208 ; G 458
+U 521 ; WX 343 ; N uni0209 ; G 459
+U 522 ; WX 372 ; N uni020A ; G 460
+U 523 ; WX 343 ; N uni020B ; G 461
+U 524 ; WX 850 ; N uni020C ; G 462
+U 525 ; WX 687 ; N uni020D ; G 463
+U 526 ; WX 850 ; N uni020E ; G 464
+U 527 ; WX 687 ; N uni020F ; G 465
+U 528 ; WX 770 ; N uni0210 ; G 466
+U 529 ; WX 493 ; N uni0211 ; G 467
+U 530 ; WX 770 ; N uni0212 ; G 468
+U 531 ; WX 493 ; N uni0213 ; G 469
+U 532 ; WX 812 ; N uni0214 ; G 470
+U 533 ; WX 712 ; N uni0215 ; G 471
+U 534 ; WX 812 ; N uni0216 ; G 472
+U 535 ; WX 712 ; N uni0217 ; G 473
+U 536 ; WX 720 ; N Scommaaccent ; G 474
+U 537 ; WX 595 ; N scommaaccent ; G 475
+U 538 ; WX 682 ; N uni021A ; G 476
+U 539 ; WX 478 ; N uni021B ; G 477
+U 540 ; WX 690 ; N uni021C ; G 478
+U 541 ; WX 607 ; N uni021D ; G 479
+U 542 ; WX 837 ; N uni021E ; G 480
+U 543 ; WX 712 ; N uni021F ; G 481
+U 544 ; WX 837 ; N uni0220 ; G 482
+U 545 ; WX 865 ; N uni0221 ; G 483
+U 546 ; WX 809 ; N uni0222 ; G 484
+U 547 ; WX 659 ; N uni0223 ; G 485
+U 548 ; WX 725 ; N uni0224 ; G 486
+U 549 ; WX 582 ; N uni0225 ; G 487
+U 550 ; WX 774 ; N uni0226 ; G 488
+U 551 ; WX 675 ; N uni0227 ; G 489
+U 552 ; WX 683 ; N uni0228 ; G 490
+U 553 ; WX 678 ; N uni0229 ; G 491
+U 554 ; WX 850 ; N uni022A ; G 492
+U 555 ; WX 687 ; N uni022B ; G 493
+U 556 ; WX 850 ; N uni022C ; G 494
+U 557 ; WX 687 ; N uni022D ; G 495
+U 558 ; WX 850 ; N uni022E ; G 496
+U 559 ; WX 687 ; N uni022F ; G 497
+U 560 ; WX 850 ; N uni0230 ; G 498
+U 561 ; WX 687 ; N uni0231 ; G 499
+U 562 ; WX 724 ; N uni0232 ; G 500
+U 563 ; WX 652 ; N uni0233 ; G 501
+U 564 ; WX 492 ; N uni0234 ; G 502
+U 565 ; WX 867 ; N uni0235 ; G 503
+U 566 ; WX 512 ; N uni0236 ; G 504
+U 567 ; WX 343 ; N dotlessj ; G 505
+U 568 ; WX 1088 ; N uni0238 ; G 506
+U 569 ; WX 1088 ; N uni0239 ; G 507
+U 570 ; WX 774 ; N uni023A ; G 508
+U 571 ; WX 734 ; N uni023B ; G 509
+U 572 ; WX 593 ; N uni023C ; G 510
+U 573 ; WX 637 ; N uni023D ; G 511
+U 574 ; WX 682 ; N uni023E ; G 512
+U 575 ; WX 595 ; N uni023F ; G 513
+U 576 ; WX 582 ; N uni0240 ; G 514
+U 577 ; WX 782 ; N uni0241 ; G 515
+U 578 ; WX 614 ; N uni0242 ; G 516
+U 579 ; WX 762 ; N uni0243 ; G 517
+U 580 ; WX 812 ; N uni0244 ; G 518
+U 581 ; WX 774 ; N uni0245 ; G 519
+U 582 ; WX 683 ; N uni0246 ; G 520
+U 583 ; WX 678 ; N uni0247 ; G 521
+U 584 ; WX 372 ; N uni0248 ; G 522
+U 585 ; WX 343 ; N uni0249 ; G 523
+U 586 ; WX 860 ; N uni024A ; G 524
+U 587 ; WX 791 ; N uni024B ; G 525
+U 588 ; WX 770 ; N uni024C ; G 526
+U 589 ; WX 493 ; N uni024D ; G 527
+U 590 ; WX 724 ; N uni024E ; G 528
+U 591 ; WX 652 ; N uni024F ; G 529
+U 592 ; WX 675 ; N uni0250 ; G 530
+U 593 ; WX 716 ; N uni0251 ; G 531
+U 594 ; WX 716 ; N uni0252 ; G 532
+U 595 ; WX 716 ; N uni0253 ; G 533
+U 596 ; WX 593 ; N uni0254 ; G 534
+U 597 ; WX 593 ; N uni0255 ; G 535
+U 598 ; WX 717 ; N uni0256 ; G 536
+U 599 ; WX 792 ; N uni0257 ; G 537
+U 600 ; WX 678 ; N uni0258 ; G 538
+U 601 ; WX 678 ; N uni0259 ; G 539
+U 602 ; WX 876 ; N uni025A ; G 540
+U 603 ; WX 557 ; N uni025B ; G 541
+U 604 ; WX 545 ; N uni025C ; G 542
+U 605 ; WX 815 ; N uni025D ; G 543
+U 606 ; WX 731 ; N uni025E ; G 544
+U 607 ; WX 343 ; N uni025F ; G 545
+U 608 ; WX 792 ; N uni0260 ; G 546
+U 609 ; WX 716 ; N uni0261 ; G 547
+U 610 ; WX 627 ; N uni0262 ; G 548
+U 611 ; WX 644 ; N uni0263 ; G 549
+U 612 ; WX 635 ; N uni0264 ; G 550
+U 613 ; WX 712 ; N uni0265 ; G 551
+U 614 ; WX 712 ; N uni0266 ; G 552
+U 615 ; WX 712 ; N uni0267 ; G 553
+U 616 ; WX 545 ; N uni0268 ; G 554
+U 617 ; WX 440 ; N uni0269 ; G 555
+U 618 ; WX 545 ; N uni026A ; G 556
+U 619 ; WX 559 ; N uni026B ; G 557
+U 620 ; WX 693 ; N uni026C ; G 558
+U 621 ; WX 343 ; N uni026D ; G 559
+U 622 ; WX 841 ; N uni026E ; G 560
+U 623 ; WX 1042 ; N uni026F ; G 561
+U 624 ; WX 1042 ; N uni0270 ; G 562
+U 625 ; WX 1042 ; N uni0271 ; G 563
+U 626 ; WX 712 ; N uni0272 ; G 564
+U 627 ; WX 793 ; N uni0273 ; G 565
+U 628 ; WX 707 ; N uni0274 ; G 566
+U 629 ; WX 687 ; N uni0275 ; G 567
+U 630 ; WX 909 ; N uni0276 ; G 568
+U 631 ; WX 681 ; N uni0277 ; G 569
+U 632 ; WX 796 ; N uni0278 ; G 570
+U 633 ; WX 538 ; N uni0279 ; G 571
+U 634 ; WX 538 ; N uni027A ; G 572
+U 635 ; WX 650 ; N uni027B ; G 573
+U 636 ; WX 493 ; N uni027C ; G 574
+U 637 ; WX 493 ; N uni027D ; G 575
+U 638 ; WX 596 ; N uni027E ; G 576
+U 639 ; WX 596 ; N uni027F ; G 577
+U 640 ; WX 642 ; N uni0280 ; G 578
+U 641 ; WX 642 ; N uni0281 ; G 579
+U 642 ; WX 595 ; N uni0282 ; G 580
+U 643 ; WX 415 ; N uni0283 ; G 581
+U 644 ; WX 435 ; N uni0284 ; G 582
+U 645 ; WX 605 ; N uni0285 ; G 583
+U 646 ; WX 552 ; N uni0286 ; G 584
+U 647 ; WX 478 ; N uni0287 ; G 585
+U 648 ; WX 478 ; N uni0288 ; G 586
+U 649 ; WX 920 ; N uni0289 ; G 587
+U 650 ; WX 772 ; N uni028A ; G 588
+U 651 ; WX 670 ; N uni028B ; G 589
+U 652 ; WX 652 ; N uni028C ; G 590
+U 653 ; WX 924 ; N uni028D ; G 591
+U 654 ; WX 652 ; N uni028E ; G 592
+U 655 ; WX 724 ; N uni028F ; G 593
+U 656 ; WX 694 ; N uni0290 ; G 594
+U 657 ; WX 684 ; N uni0291 ; G 595
+U 658 ; WX 641 ; N uni0292 ; G 596
+U 659 ; WX 641 ; N uni0293 ; G 597
+U 660 ; WX 573 ; N uni0294 ; G 598
+U 661 ; WX 573 ; N uni0295 ; G 599
+U 662 ; WX 573 ; N uni0296 ; G 600
+U 663 ; WX 573 ; N uni0297 ; G 601
+U 664 ; WX 850 ; N uni0298 ; G 602
+U 665 ; WX 633 ; N uni0299 ; G 603
+U 666 ; WX 731 ; N uni029A ; G 604
+U 667 ; WX 685 ; N uni029B ; G 605
+U 668 ; WX 691 ; N uni029C ; G 606
+U 669 ; WX 343 ; N uni029D ; G 607
+U 670 ; WX 732 ; N uni029E ; G 608
+U 671 ; WX 539 ; N uni029F ; G 609
+U 672 ; WX 792 ; N uni02A0 ; G 610
+U 673 ; WX 573 ; N uni02A1 ; G 611
+U 674 ; WX 573 ; N uni02A2 ; G 612
+U 675 ; WX 1156 ; N uni02A3 ; G 613
+U 676 ; WX 1214 ; N uni02A4 ; G 614
+U 677 ; WX 1155 ; N uni02A5 ; G 615
+U 678 ; WX 975 ; N uni02A6 ; G 616
+U 679 ; WX 769 ; N uni02A7 ; G 617
+U 680 ; WX 929 ; N uni02A8 ; G 618
+U 681 ; WX 1026 ; N uni02A9 ; G 619
+U 682 ; WX 862 ; N uni02AA ; G 620
+U 683 ; WX 780 ; N uni02AB ; G 621
+U 684 ; WX 591 ; N uni02AC ; G 622
+U 685 ; WX 415 ; N uni02AD ; G 623
+U 686 ; WX 677 ; N uni02AE ; G 624
+U 687 ; WX 789 ; N uni02AF ; G 625
+U 688 ; WX 456 ; N uni02B0 ; G 626
+U 689 ; WX 456 ; N uni02B1 ; G 627
+U 690 ; WX 219 ; N uni02B2 ; G 628
+U 691 ; WX 315 ; N uni02B3 ; G 629
+U 692 ; WX 315 ; N uni02B4 ; G 630
+U 693 ; WX 315 ; N uni02B5 ; G 631
+U 694 ; WX 411 ; N uni02B6 ; G 632
+U 695 ; WX 591 ; N uni02B7 ; G 633
+U 696 ; WX 417 ; N uni02B8 ; G 634
+U 697 ; WX 302 ; N uni02B9 ; G 635
+U 698 ; WX 521 ; N uni02BA ; G 636
+U 699 ; WX 380 ; N uni02BB ; G 637
+U 700 ; WX 380 ; N uni02BC ; G 638
+U 701 ; WX 380 ; N uni02BD ; G 639
+U 702 ; WX 366 ; N uni02BE ; G 640
+U 703 ; WX 366 ; N uni02BF ; G 641
+U 704 ; WX 326 ; N uni02C0 ; G 642
+U 705 ; WX 326 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 306 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 306 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 718 ; WX 500 ; N uni02CE ; G 656
+U 719 ; WX 500 ; N uni02CF ; G 657
+U 720 ; WX 337 ; N uni02D0 ; G 658
+U 721 ; WX 337 ; N uni02D1 ; G 659
+U 722 ; WX 366 ; N uni02D2 ; G 660
+U 723 ; WX 366 ; N uni02D3 ; G 661
+U 724 ; WX 500 ; N uni02D4 ; G 662
+U 725 ; WX 500 ; N uni02D5 ; G 663
+U 726 ; WX 416 ; N uni02D6 ; G 664
+U 727 ; WX 328 ; N uni02D7 ; G 665
+U 728 ; WX 500 ; N breve ; G 666
+U 729 ; WX 500 ; N dotaccent ; G 667
+U 730 ; WX 500 ; N ring ; G 668
+U 731 ; WX 500 ; N ogonek ; G 669
+U 732 ; WX 500 ; N tilde ; G 670
+U 733 ; WX 500 ; N hungarumlaut ; G 671
+U 734 ; WX 351 ; N uni02DE ; G 672
+U 735 ; WX 500 ; N uni02DF ; G 673
+U 736 ; WX 412 ; N uni02E0 ; G 674
+U 737 ; WX 219 ; N uni02E1 ; G 675
+U 738 ; WX 381 ; N uni02E2 ; G 676
+U 739 ; WX 413 ; N uni02E3 ; G 677
+U 740 ; WX 326 ; N uni02E4 ; G 678
+U 741 ; WX 500 ; N uni02E5 ; G 679
+U 742 ; WX 500 ; N uni02E6 ; G 680
+U 743 ; WX 500 ; N uni02E7 ; G 681
+U 744 ; WX 500 ; N uni02E8 ; G 682
+U 745 ; WX 500 ; N uni02E9 ; G 683
+U 748 ; WX 500 ; N uni02EC ; G 684
+U 749 ; WX 500 ; N uni02ED ; G 685
+U 750 ; WX 657 ; N uni02EE ; G 686
+U 755 ; WX 500 ; N uni02F3 ; G 687
+U 759 ; WX 500 ; N uni02F7 ; G 688
+U 768 ; WX 0 ; N gravecomb ; G 689
+U 769 ; WX 0 ; N acutecomb ; G 690
+U 770 ; WX 0 ; N uni0302 ; G 691
+U 771 ; WX 0 ; N tildecomb ; G 692
+U 772 ; WX 0 ; N uni0304 ; G 693
+U 773 ; WX 0 ; N uni0305 ; G 694
+U 774 ; WX 0 ; N uni0306 ; G 695
+U 775 ; WX 0 ; N uni0307 ; G 696
+U 776 ; WX 0 ; N uni0308 ; G 697
+U 777 ; WX 0 ; N hookabovecomb ; G 698
+U 778 ; WX 0 ; N uni030A ; G 699
+U 779 ; WX 0 ; N uni030B ; G 700
+U 780 ; WX 0 ; N uni030C ; G 701
+U 781 ; WX 0 ; N uni030D ; G 702
+U 782 ; WX 0 ; N uni030E ; G 703
+U 783 ; WX 0 ; N uni030F ; G 704
+U 784 ; WX 0 ; N uni0310 ; G 705
+U 785 ; WX 0 ; N uni0311 ; G 706
+U 786 ; WX 0 ; N uni0312 ; G 707
+U 787 ; WX 0 ; N uni0313 ; G 708
+U 788 ; WX 0 ; N uni0314 ; G 709
+U 789 ; WX 0 ; N uni0315 ; G 710
+U 790 ; WX 0 ; N uni0316 ; G 711
+U 791 ; WX 0 ; N uni0317 ; G 712
+U 792 ; WX 0 ; N uni0318 ; G 713
+U 793 ; WX 0 ; N uni0319 ; G 714
+U 794 ; WX 0 ; N uni031A ; G 715
+U 795 ; WX 0 ; N uni031B ; G 716
+U 796 ; WX 0 ; N uni031C ; G 717
+U 797 ; WX 0 ; N uni031D ; G 718
+U 798 ; WX 0 ; N uni031E ; G 719
+U 799 ; WX 0 ; N uni031F ; G 720
+U 800 ; WX 0 ; N uni0320 ; G 721
+U 801 ; WX 0 ; N uni0321 ; G 722
+U 802 ; WX 0 ; N uni0322 ; G 723
+U 803 ; WX 0 ; N dotbelowcomb ; G 724
+U 804 ; WX 0 ; N uni0324 ; G 725
+U 805 ; WX 0 ; N uni0325 ; G 726
+U 806 ; WX 0 ; N uni0326 ; G 727
+U 807 ; WX 0 ; N uni0327 ; G 728
+U 808 ; WX 0 ; N uni0328 ; G 729
+U 809 ; WX 0 ; N uni0329 ; G 730
+U 810 ; WX 0 ; N uni032A ; G 731
+U 811 ; WX 0 ; N uni032B ; G 732
+U 812 ; WX 0 ; N uni032C ; G 733
+U 813 ; WX 0 ; N uni032D ; G 734
+U 814 ; WX 0 ; N uni032E ; G 735
+U 815 ; WX 0 ; N uni032F ; G 736
+U 816 ; WX 0 ; N uni0330 ; G 737
+U 817 ; WX 0 ; N uni0331 ; G 738
+U 818 ; WX 0 ; N uni0332 ; G 739
+U 819 ; WX 0 ; N uni0333 ; G 740
+U 820 ; WX 0 ; N uni0334 ; G 741
+U 821 ; WX 0 ; N uni0335 ; G 742
+U 822 ; WX 0 ; N uni0336 ; G 743
+U 823 ; WX 0 ; N uni0337 ; G 744
+U 824 ; WX 0 ; N uni0338 ; G 745
+U 825 ; WX 0 ; N uni0339 ; G 746
+U 826 ; WX 0 ; N uni033A ; G 747
+U 827 ; WX 0 ; N uni033B ; G 748
+U 828 ; WX 0 ; N uni033C ; G 749
+U 829 ; WX 0 ; N uni033D ; G 750
+U 830 ; WX 0 ; N uni033E ; G 751
+U 831 ; WX 0 ; N uni033F ; G 752
+U 832 ; WX 0 ; N uni0340 ; G 753
+U 833 ; WX 0 ; N uni0341 ; G 754
+U 834 ; WX 0 ; N uni0342 ; G 755
+U 835 ; WX 0 ; N uni0343 ; G 756
+U 836 ; WX 0 ; N uni0344 ; G 757
+U 837 ; WX 0 ; N uni0345 ; G 758
+U 838 ; WX 0 ; N uni0346 ; G 759
+U 839 ; WX 0 ; N uni0347 ; G 760
+U 840 ; WX 0 ; N uni0348 ; G 761
+U 841 ; WX 0 ; N uni0349 ; G 762
+U 842 ; WX 0 ; N uni034A ; G 763
+U 843 ; WX 0 ; N uni034B ; G 764
+U 844 ; WX 0 ; N uni034C ; G 765
+U 845 ; WX 0 ; N uni034D ; G 766
+U 846 ; WX 0 ; N uni034E ; G 767
+U 847 ; WX 0 ; N uni034F ; G 768
+U 849 ; WX 0 ; N uni0351 ; G 769
+U 850 ; WX 0 ; N uni0352 ; G 770
+U 851 ; WX 0 ; N uni0353 ; G 771
+U 855 ; WX 0 ; N uni0357 ; G 772
+U 856 ; WX 0 ; N uni0358 ; G 773
+U 858 ; WX 0 ; N uni035A ; G 774
+U 860 ; WX 0 ; N uni035C ; G 775
+U 861 ; WX 0 ; N uni035D ; G 776
+U 862 ; WX 0 ; N uni035E ; G 777
+U 863 ; WX 0 ; N uni035F ; G 778
+U 864 ; WX 0 ; N uni0360 ; G 779
+U 865 ; WX 0 ; N uni0361 ; G 780
+U 866 ; WX 0 ; N uni0362 ; G 781
+U 880 ; WX 698 ; N uni0370 ; G 782
+U 881 ; WX 565 ; N uni0371 ; G 783
+U 882 ; WX 1022 ; N uni0372 ; G 784
+U 883 ; WX 836 ; N uni0373 ; G 785
+U 884 ; WX 302 ; N uni0374 ; G 786
+U 885 ; WX 302 ; N uni0375 ; G 787
+U 886 ; WX 837 ; N uni0376 ; G 788
+U 887 ; WX 701 ; N uni0377 ; G 789
+U 890 ; WX 500 ; N uni037A ; G 790
+U 891 ; WX 593 ; N uni037B ; G 791
+U 892 ; WX 550 ; N uni037C ; G 792
+U 893 ; WX 549 ; N uni037D ; G 793
+U 894 ; WX 400 ; N uni037E ; G 794
+U 895 ; WX 372 ; N uni037F ; G 795
+U 900 ; WX 441 ; N tonos ; G 796
+U 901 ; WX 500 ; N dieresistonos ; G 797
+U 902 ; WX 797 ; N Alphatonos ; G 798
+U 903 ; WX 380 ; N anoteleia ; G 799
+U 904 ; WX 846 ; N Epsilontonos ; G 800
+U 905 ; WX 1009 ; N Etatonos ; G 801
+U 906 ; WX 563 ; N Iotatonos ; G 802
+U 908 ; WX 891 ; N Omicrontonos ; G 803
+U 910 ; WX 980 ; N Upsilontonos ; G 804
+U 911 ; WX 894 ; N Omegatonos ; G 805
+U 912 ; WX 390 ; N iotadieresistonos ; G 806
+U 913 ; WX 774 ; N Alpha ; G 807
+U 914 ; WX 762 ; N Beta ; G 808
+U 915 ; WX 637 ; N Gamma ; G 809
+U 916 ; WX 774 ; N uni0394 ; G 810
+U 917 ; WX 683 ; N Epsilon ; G 811
+U 918 ; WX 725 ; N Zeta ; G 812
+U 919 ; WX 837 ; N Eta ; G 813
+U 920 ; WX 850 ; N Theta ; G 814
+U 921 ; WX 372 ; N Iota ; G 815
+U 922 ; WX 775 ; N Kappa ; G 816
+U 923 ; WX 774 ; N Lambda ; G 817
+U 924 ; WX 995 ; N Mu ; G 818
+U 925 ; WX 837 ; N Nu ; G 819
+U 926 ; WX 632 ; N Xi ; G 820
+U 927 ; WX 850 ; N Omicron ; G 821
+U 928 ; WX 837 ; N Pi ; G 822
+U 929 ; WX 733 ; N Rho ; G 823
+U 931 ; WX 683 ; N Sigma ; G 824
+U 932 ; WX 682 ; N Tau ; G 825
+U 933 ; WX 724 ; N Upsilon ; G 826
+U 934 ; WX 850 ; N Phi ; G 827
+U 935 ; WX 771 ; N Chi ; G 828
+U 936 ; WX 850 ; N Psi ; G 829
+U 937 ; WX 850 ; N Omega ; G 830
+U 938 ; WX 372 ; N Iotadieresis ; G 831
+U 939 ; WX 724 ; N Upsilondieresis ; G 832
+U 940 ; WX 687 ; N alphatonos ; G 833
+U 941 ; WX 557 ; N epsilontonos ; G 834
+U 942 ; WX 712 ; N etatonos ; G 835
+U 943 ; WX 390 ; N iotatonos ; G 836
+U 944 ; WX 675 ; N upsilondieresistonos ; G 837
+U 945 ; WX 687 ; N alpha ; G 838
+U 946 ; WX 716 ; N beta ; G 839
+U 947 ; WX 681 ; N gamma ; G 840
+U 948 ; WX 687 ; N delta ; G 841
+U 949 ; WX 557 ; N epsilon ; G 842
+U 950 ; WX 591 ; N zeta ; G 843
+U 951 ; WX 712 ; N eta ; G 844
+U 952 ; WX 687 ; N theta ; G 845
+U 953 ; WX 390 ; N iota ; G 846
+U 954 ; WX 710 ; N kappa ; G 847
+U 955 ; WX 633 ; N lambda ; G 848
+U 956 ; WX 736 ; N uni03BC ; G 849
+U 957 ; WX 681 ; N nu ; G 850
+U 958 ; WX 591 ; N xi ; G 851
+U 959 ; WX 687 ; N omicron ; G 852
+U 960 ; WX 791 ; N pi ; G 853
+U 961 ; WX 716 ; N rho ; G 854
+U 962 ; WX 593 ; N sigma1 ; G 855
+U 963 ; WX 779 ; N sigma ; G 856
+U 964 ; WX 638 ; N tau ; G 857
+U 965 ; WX 675 ; N upsilon ; G 858
+U 966 ; WX 782 ; N phi ; G 859
+U 967 ; WX 645 ; N chi ; G 860
+U 968 ; WX 794 ; N psi ; G 861
+U 969 ; WX 869 ; N omega ; G 862
+U 970 ; WX 390 ; N iotadieresis ; G 863
+U 971 ; WX 675 ; N upsilondieresis ; G 864
+U 972 ; WX 687 ; N omicrontonos ; G 865
+U 973 ; WX 675 ; N upsilontonos ; G 866
+U 974 ; WX 869 ; N omegatonos ; G 867
+U 975 ; WX 775 ; N uni03CF ; G 868
+U 976 ; WX 651 ; N uni03D0 ; G 869
+U 977 ; WX 661 ; N theta1 ; G 870
+U 978 ; WX 746 ; N Upsilon1 ; G 871
+U 979 ; WX 981 ; N uni03D3 ; G 872
+U 980 ; WX 746 ; N uni03D4 ; G 873
+U 981 ; WX 796 ; N phi1 ; G 874
+U 982 ; WX 869 ; N omega1 ; G 875
+U 983 ; WX 744 ; N uni03D7 ; G 876
+U 984 ; WX 850 ; N uni03D8 ; G 877
+U 985 ; WX 687 ; N uni03D9 ; G 878
+U 986 ; WX 734 ; N uni03DA ; G 879
+U 987 ; WX 593 ; N uni03DB ; G 880
+U 988 ; WX 683 ; N uni03DC ; G 881
+U 989 ; WX 494 ; N uni03DD ; G 882
+U 990 ; WX 702 ; N uni03DE ; G 883
+U 991 ; WX 660 ; N uni03DF ; G 884
+U 992 ; WX 919 ; N uni03E0 ; G 885
+U 993 ; WX 627 ; N uni03E1 ; G 886
+U 994 ; WX 1093 ; N uni03E2 ; G 887
+U 995 ; WX 837 ; N uni03E3 ; G 888
+U 996 ; WX 832 ; N uni03E4 ; G 889
+U 997 ; WX 716 ; N uni03E5 ; G 890
+U 998 ; WX 928 ; N uni03E6 ; G 891
+U 999 ; WX 744 ; N uni03E7 ; G 892
+U 1000 ; WX 733 ; N uni03E8 ; G 893
+U 1001 ; WX 650 ; N uni03E9 ; G 894
+U 1002 ; WX 789 ; N uni03EA ; G 895
+U 1003 ; WX 671 ; N uni03EB ; G 896
+U 1004 ; WX 752 ; N uni03EC ; G 897
+U 1005 ; WX 716 ; N uni03ED ; G 898
+U 1006 ; WX 682 ; N uni03EE ; G 899
+U 1007 ; WX 590 ; N uni03EF ; G 900
+U 1008 ; WX 744 ; N uni03F0 ; G 901
+U 1009 ; WX 716 ; N uni03F1 ; G 902
+U 1010 ; WX 593 ; N uni03F2 ; G 903
+U 1011 ; WX 343 ; N uni03F3 ; G 904
+U 1012 ; WX 850 ; N uni03F4 ; G 905
+U 1013 ; WX 645 ; N uni03F5 ; G 906
+U 1014 ; WX 644 ; N uni03F6 ; G 907
+U 1015 ; WX 738 ; N uni03F7 ; G 908
+U 1016 ; WX 716 ; N uni03F8 ; G 909
+U 1017 ; WX 734 ; N uni03F9 ; G 910
+U 1018 ; WX 995 ; N uni03FA ; G 911
+U 1019 ; WX 732 ; N uni03FB ; G 912
+U 1020 ; WX 716 ; N uni03FC ; G 913
+U 1021 ; WX 698 ; N uni03FD ; G 914
+U 1022 ; WX 734 ; N uni03FE ; G 915
+U 1023 ; WX 698 ; N uni03FF ; G 916
+U 1024 ; WX 683 ; N uni0400 ; G 917
+U 1025 ; WX 683 ; N uni0401 ; G 918
+U 1026 ; WX 878 ; N uni0402 ; G 919
+U 1027 ; WX 637 ; N uni0403 ; G 920
+U 1028 ; WX 734 ; N uni0404 ; G 921
+U 1029 ; WX 720 ; N uni0405 ; G 922
+U 1030 ; WX 372 ; N uni0406 ; G 923
+U 1031 ; WX 372 ; N uni0407 ; G 924
+U 1032 ; WX 372 ; N uni0408 ; G 925
+U 1033 ; WX 1154 ; N uni0409 ; G 926
+U 1034 ; WX 1130 ; N uni040A ; G 927
+U 1035 ; WX 878 ; N uni040B ; G 928
+U 1036 ; WX 817 ; N uni040C ; G 929
+U 1037 ; WX 837 ; N uni040D ; G 930
+U 1038 ; WX 771 ; N uni040E ; G 931
+U 1039 ; WX 837 ; N uni040F ; G 932
+U 1040 ; WX 774 ; N uni0410 ; G 933
+U 1041 ; WX 762 ; N uni0411 ; G 934
+U 1042 ; WX 762 ; N uni0412 ; G 935
+U 1043 ; WX 637 ; N uni0413 ; G 936
+U 1044 ; WX 891 ; N uni0414 ; G 937
+U 1045 ; WX 683 ; N uni0415 ; G 938
+U 1046 ; WX 1224 ; N uni0416 ; G 939
+U 1047 ; WX 710 ; N uni0417 ; G 940
+U 1048 ; WX 837 ; N uni0418 ; G 941
+U 1049 ; WX 837 ; N uni0419 ; G 942
+U 1050 ; WX 817 ; N uni041A ; G 943
+U 1051 ; WX 831 ; N uni041B ; G 944
+U 1052 ; WX 995 ; N uni041C ; G 945
+U 1053 ; WX 837 ; N uni041D ; G 946
+U 1054 ; WX 850 ; N uni041E ; G 947
+U 1055 ; WX 837 ; N uni041F ; G 948
+U 1056 ; WX 733 ; N uni0420 ; G 949
+U 1057 ; WX 734 ; N uni0421 ; G 950
+U 1058 ; WX 682 ; N uni0422 ; G 951
+U 1059 ; WX 771 ; N uni0423 ; G 952
+U 1060 ; WX 992 ; N uni0424 ; G 953
+U 1061 ; WX 771 ; N uni0425 ; G 954
+U 1062 ; WX 928 ; N uni0426 ; G 955
+U 1063 ; WX 808 ; N uni0427 ; G 956
+U 1064 ; WX 1235 ; N uni0428 ; G 957
+U 1065 ; WX 1326 ; N uni0429 ; G 958
+U 1066 ; WX 939 ; N uni042A ; G 959
+U 1067 ; WX 1036 ; N uni042B ; G 960
+U 1068 ; WX 762 ; N uni042C ; G 961
+U 1069 ; WX 734 ; N uni042D ; G 962
+U 1070 ; WX 1174 ; N uni042E ; G 963
+U 1071 ; WX 770 ; N uni042F ; G 964
+U 1072 ; WX 675 ; N uni0430 ; G 965
+U 1073 ; WX 698 ; N uni0431 ; G 966
+U 1074 ; WX 633 ; N uni0432 ; G 967
+U 1075 ; WX 522 ; N uni0433 ; G 968
+U 1076 ; WX 808 ; N uni0434 ; G 969
+U 1077 ; WX 678 ; N uni0435 ; G 970
+U 1078 ; WX 995 ; N uni0436 ; G 971
+U 1079 ; WX 581 ; N uni0437 ; G 972
+U 1080 ; WX 701 ; N uni0438 ; G 973
+U 1081 ; WX 701 ; N uni0439 ; G 974
+U 1082 ; WX 679 ; N uni043A ; G 975
+U 1083 ; WX 732 ; N uni043B ; G 976
+U 1084 ; WX 817 ; N uni043C ; G 977
+U 1085 ; WX 691 ; N uni043D ; G 978
+U 1086 ; WX 687 ; N uni043E ; G 979
+U 1087 ; WX 691 ; N uni043F ; G 980
+U 1088 ; WX 716 ; N uni0440 ; G 981
+U 1089 ; WX 593 ; N uni0441 ; G 982
+U 1090 ; WX 580 ; N uni0442 ; G 983
+U 1091 ; WX 652 ; N uni0443 ; G 984
+U 1092 ; WX 992 ; N uni0444 ; G 985
+U 1093 ; WX 645 ; N uni0445 ; G 986
+U 1094 ; WX 741 ; N uni0446 ; G 987
+U 1095 ; WX 687 ; N uni0447 ; G 988
+U 1096 ; WX 1062 ; N uni0448 ; G 989
+U 1097 ; WX 1105 ; N uni0449 ; G 990
+U 1098 ; WX 751 ; N uni044A ; G 991
+U 1099 ; WX 904 ; N uni044B ; G 992
+U 1100 ; WX 632 ; N uni044C ; G 993
+U 1101 ; WX 593 ; N uni044D ; G 994
+U 1102 ; WX 972 ; N uni044E ; G 995
+U 1103 ; WX 642 ; N uni044F ; G 996
+U 1104 ; WX 678 ; N uni0450 ; G 997
+U 1105 ; WX 678 ; N uni0451 ; G 998
+U 1106 ; WX 714 ; N uni0452 ; G 999
+U 1107 ; WX 522 ; N uni0453 ; G 1000
+U 1108 ; WX 593 ; N uni0454 ; G 1001
+U 1109 ; WX 595 ; N uni0455 ; G 1002
+U 1110 ; WX 343 ; N uni0456 ; G 1003
+U 1111 ; WX 343 ; N uni0457 ; G 1004
+U 1112 ; WX 343 ; N uni0458 ; G 1005
+U 1113 ; WX 991 ; N uni0459 ; G 1006
+U 1114 ; WX 956 ; N uni045A ; G 1007
+U 1115 ; WX 734 ; N uni045B ; G 1008
+U 1116 ; WX 679 ; N uni045C ; G 1009
+U 1117 ; WX 701 ; N uni045D ; G 1010
+U 1118 ; WX 652 ; N uni045E ; G 1011
+U 1119 ; WX 691 ; N uni045F ; G 1012
+U 1120 ; WX 1093 ; N uni0460 ; G 1013
+U 1121 ; WX 869 ; N uni0461 ; G 1014
+U 1122 ; WX 840 ; N uni0462 ; G 1015
+U 1123 ; WX 736 ; N uni0463 ; G 1016
+U 1124 ; WX 1012 ; N uni0464 ; G 1017
+U 1125 ; WX 839 ; N uni0465 ; G 1018
+U 1126 ; WX 992 ; N uni0466 ; G 1019
+U 1127 ; WX 832 ; N uni0467 ; G 1020
+U 1128 ; WX 1358 ; N uni0468 ; G 1021
+U 1129 ; WX 1121 ; N uni0469 ; G 1022
+U 1130 ; WX 850 ; N uni046A ; G 1023
+U 1131 ; WX 687 ; N uni046B ; G 1024
+U 1132 ; WX 1236 ; N uni046C ; G 1025
+U 1133 ; WX 1007 ; N uni046D ; G 1026
+U 1134 ; WX 696 ; N uni046E ; G 1027
+U 1135 ; WX 557 ; N uni046F ; G 1028
+U 1136 ; WX 1075 ; N uni0470 ; G 1029
+U 1137 ; WX 1061 ; N uni0471 ; G 1030
+U 1138 ; WX 850 ; N uni0472 ; G 1031
+U 1139 ; WX 687 ; N uni0473 ; G 1032
+U 1140 ; WX 850 ; N uni0474 ; G 1033
+U 1141 ; WX 695 ; N uni0475 ; G 1034
+U 1142 ; WX 850 ; N uni0476 ; G 1035
+U 1143 ; WX 695 ; N uni0477 ; G 1036
+U 1144 ; WX 1148 ; N uni0478 ; G 1037
+U 1145 ; WX 1043 ; N uni0479 ; G 1038
+U 1146 ; WX 1074 ; N uni047A ; G 1039
+U 1147 ; WX 863 ; N uni047B ; G 1040
+U 1148 ; WX 1405 ; N uni047C ; G 1041
+U 1149 ; WX 1173 ; N uni047D ; G 1042
+U 1150 ; WX 1093 ; N uni047E ; G 1043
+U 1151 ; WX 869 ; N uni047F ; G 1044
+U 1152 ; WX 734 ; N uni0480 ; G 1045
+U 1153 ; WX 593 ; N uni0481 ; G 1046
+U 1154 ; WX 652 ; N uni0482 ; G 1047
+U 1155 ; WX 0 ; N uni0483 ; G 1048
+U 1156 ; WX 0 ; N uni0484 ; G 1049
+U 1157 ; WX 0 ; N uni0485 ; G 1050
+U 1158 ; WX 0 ; N uni0486 ; G 1051
+U 1159 ; WX 0 ; N uni0487 ; G 1052
+U 1160 ; WX 418 ; N uni0488 ; G 1053
+U 1161 ; WX 418 ; N uni0489 ; G 1054
+U 1162 ; WX 957 ; N uni048A ; G 1055
+U 1163 ; WX 807 ; N uni048B ; G 1056
+U 1164 ; WX 762 ; N uni048C ; G 1057
+U 1165 ; WX 611 ; N uni048D ; G 1058
+U 1166 ; WX 733 ; N uni048E ; G 1059
+U 1167 ; WX 716 ; N uni048F ; G 1060
+U 1168 ; WX 637 ; N uni0490 ; G 1061
+U 1169 ; WX 522 ; N uni0491 ; G 1062
+U 1170 ; WX 666 ; N uni0492 ; G 1063
+U 1171 ; WX 543 ; N uni0493 ; G 1064
+U 1172 ; WX 808 ; N uni0494 ; G 1065
+U 1173 ; WX 669 ; N uni0495 ; G 1066
+U 1174 ; WX 1224 ; N uni0496 ; G 1067
+U 1175 ; WX 995 ; N uni0497 ; G 1068
+U 1176 ; WX 710 ; N uni0498 ; G 1069
+U 1177 ; WX 581 ; N uni0499 ; G 1070
+U 1178 ; WX 775 ; N uni049A ; G 1071
+U 1179 ; WX 679 ; N uni049B ; G 1072
+U 1180 ; WX 817 ; N uni049C ; G 1073
+U 1181 ; WX 679 ; N uni049D ; G 1074
+U 1182 ; WX 817 ; N uni049E ; G 1075
+U 1183 ; WX 679 ; N uni049F ; G 1076
+U 1184 ; WX 1015 ; N uni04A0 ; G 1077
+U 1185 ; WX 826 ; N uni04A1 ; G 1078
+U 1186 ; WX 956 ; N uni04A2 ; G 1079
+U 1187 ; WX 808 ; N uni04A3 ; G 1080
+U 1188 ; WX 1103 ; N uni04A4 ; G 1081
+U 1189 ; WX 874 ; N uni04A5 ; G 1082
+U 1190 ; WX 1273 ; N uni04A6 ; G 1083
+U 1191 ; WX 1017 ; N uni04A7 ; G 1084
+U 1192 ; WX 952 ; N uni04A8 ; G 1085
+U 1193 ; WX 858 ; N uni04A9 ; G 1086
+U 1194 ; WX 734 ; N uni04AA ; G 1087
+U 1195 ; WX 593 ; N uni04AB ; G 1088
+U 1196 ; WX 682 ; N uni04AC ; G 1089
+U 1197 ; WX 580 ; N uni04AD ; G 1090
+U 1198 ; WX 724 ; N uni04AE ; G 1091
+U 1199 ; WX 652 ; N uni04AF ; G 1092
+U 1200 ; WX 724 ; N uni04B0 ; G 1093
+U 1201 ; WX 652 ; N uni04B1 ; G 1094
+U 1202 ; WX 771 ; N uni04B2 ; G 1095
+U 1203 ; WX 645 ; N uni04B3 ; G 1096
+U 1204 ; WX 1112 ; N uni04B4 ; G 1097
+U 1205 ; WX 1000 ; N uni04B5 ; G 1098
+U 1206 ; WX 808 ; N uni04B6 ; G 1099
+U 1207 ; WX 687 ; N uni04B7 ; G 1100
+U 1208 ; WX 808 ; N uni04B8 ; G 1101
+U 1209 ; WX 687 ; N uni04B9 ; G 1102
+U 1210 ; WX 808 ; N uni04BA ; G 1103
+U 1211 ; WX 712 ; N uni04BB ; G 1104
+U 1212 ; WX 1026 ; N uni04BC ; G 1105
+U 1213 ; WX 810 ; N uni04BD ; G 1106
+U 1214 ; WX 1026 ; N uni04BE ; G 1107
+U 1215 ; WX 810 ; N uni04BF ; G 1108
+U 1216 ; WX 372 ; N uni04C0 ; G 1109
+U 1217 ; WX 1224 ; N uni04C1 ; G 1110
+U 1218 ; WX 995 ; N uni04C2 ; G 1111
+U 1219 ; WX 775 ; N uni04C3 ; G 1112
+U 1220 ; WX 630 ; N uni04C4 ; G 1113
+U 1221 ; WX 951 ; N uni04C5 ; G 1114
+U 1222 ; WX 805 ; N uni04C6 ; G 1115
+U 1223 ; WX 837 ; N uni04C7 ; G 1116
+U 1224 ; WX 691 ; N uni04C8 ; G 1117
+U 1225 ; WX 957 ; N uni04C9 ; G 1118
+U 1226 ; WX 807 ; N uni04CA ; G 1119
+U 1227 ; WX 808 ; N uni04CB ; G 1120
+U 1228 ; WX 687 ; N uni04CC ; G 1121
+U 1229 ; WX 1115 ; N uni04CD ; G 1122
+U 1230 ; WX 933 ; N uni04CE ; G 1123
+U 1231 ; WX 343 ; N uni04CF ; G 1124
+U 1232 ; WX 774 ; N uni04D0 ; G 1125
+U 1233 ; WX 675 ; N uni04D1 ; G 1126
+U 1234 ; WX 774 ; N uni04D2 ; G 1127
+U 1235 ; WX 675 ; N uni04D3 ; G 1128
+U 1236 ; WX 1085 ; N uni04D4 ; G 1129
+U 1237 ; WX 1048 ; N uni04D5 ; G 1130
+U 1238 ; WX 683 ; N uni04D6 ; G 1131
+U 1239 ; WX 678 ; N uni04D7 ; G 1132
+U 1240 ; WX 849 ; N uni04D8 ; G 1133
+U 1241 ; WX 678 ; N uni04D9 ; G 1134
+U 1242 ; WX 849 ; N uni04DA ; G 1135
+U 1243 ; WX 678 ; N uni04DB ; G 1136
+U 1244 ; WX 1224 ; N uni04DC ; G 1137
+U 1245 ; WX 995 ; N uni04DD ; G 1138
+U 1246 ; WX 710 ; N uni04DE ; G 1139
+U 1247 ; WX 581 ; N uni04DF ; G 1140
+U 1248 ; WX 772 ; N uni04E0 ; G 1141
+U 1249 ; WX 641 ; N uni04E1 ; G 1142
+U 1250 ; WX 837 ; N uni04E2 ; G 1143
+U 1251 ; WX 701 ; N uni04E3 ; G 1144
+U 1252 ; WX 837 ; N uni04E4 ; G 1145
+U 1253 ; WX 701 ; N uni04E5 ; G 1146
+U 1254 ; WX 850 ; N uni04E6 ; G 1147
+U 1255 ; WX 687 ; N uni04E7 ; G 1148
+U 1256 ; WX 850 ; N uni04E8 ; G 1149
+U 1257 ; WX 687 ; N uni04E9 ; G 1150
+U 1258 ; WX 850 ; N uni04EA ; G 1151
+U 1259 ; WX 687 ; N uni04EB ; G 1152
+U 1260 ; WX 734 ; N uni04EC ; G 1153
+U 1261 ; WX 593 ; N uni04ED ; G 1154
+U 1262 ; WX 771 ; N uni04EE ; G 1155
+U 1263 ; WX 652 ; N uni04EF ; G 1156
+U 1264 ; WX 771 ; N uni04F0 ; G 1157
+U 1265 ; WX 652 ; N uni04F1 ; G 1158
+U 1266 ; WX 771 ; N uni04F2 ; G 1159
+U 1267 ; WX 652 ; N uni04F3 ; G 1160
+U 1268 ; WX 808 ; N uni04F4 ; G 1161
+U 1269 ; WX 687 ; N uni04F5 ; G 1162
+U 1270 ; WX 637 ; N uni04F6 ; G 1163
+U 1271 ; WX 522 ; N uni04F7 ; G 1164
+U 1272 ; WX 1036 ; N uni04F8 ; G 1165
+U 1273 ; WX 904 ; N uni04F9 ; G 1166
+U 1274 ; WX 666 ; N uni04FA ; G 1167
+U 1275 ; WX 543 ; N uni04FB ; G 1168
+U 1276 ; WX 771 ; N uni04FC ; G 1169
+U 1277 ; WX 645 ; N uni04FD ; G 1170
+U 1278 ; WX 771 ; N uni04FE ; G 1171
+U 1279 ; WX 645 ; N uni04FF ; G 1172
+U 1280 ; WX 762 ; N uni0500 ; G 1173
+U 1281 ; WX 608 ; N uni0501 ; G 1174
+U 1282 ; WX 1159 ; N uni0502 ; G 1175
+U 1283 ; WX 893 ; N uni0503 ; G 1176
+U 1284 ; WX 1119 ; N uni0504 ; G 1177
+U 1285 ; WX 920 ; N uni0505 ; G 1178
+U 1286 ; WX 828 ; N uni0506 ; G 1179
+U 1287 ; WX 693 ; N uni0507 ; G 1180
+U 1288 ; WX 1242 ; N uni0508 ; G 1181
+U 1289 ; WX 1017 ; N uni0509 ; G 1182
+U 1290 ; WX 1289 ; N uni050A ; G 1183
+U 1291 ; WX 1013 ; N uni050B ; G 1184
+U 1292 ; WX 839 ; N uni050C ; G 1185
+U 1293 ; WX 638 ; N uni050D ; G 1186
+U 1294 ; WX 938 ; N uni050E ; G 1187
+U 1295 ; WX 803 ; N uni050F ; G 1188
+U 1296 ; WX 696 ; N uni0510 ; G 1189
+U 1297 ; WX 557 ; N uni0511 ; G 1190
+U 1298 ; WX 831 ; N uni0512 ; G 1191
+U 1299 ; WX 732 ; N uni0513 ; G 1192
+U 1300 ; WX 1286 ; N uni0514 ; G 1193
+U 1301 ; WX 1068 ; N uni0515 ; G 1194
+U 1302 ; WX 1065 ; N uni0516 ; G 1195
+U 1303 ; WX 979 ; N uni0517 ; G 1196
+U 1304 ; WX 1082 ; N uni0518 ; G 1197
+U 1305 ; WX 1013 ; N uni0519 ; G 1198
+U 1306 ; WX 850 ; N uni051A ; G 1199
+U 1307 ; WX 716 ; N uni051B ; G 1200
+U 1308 ; WX 1103 ; N uni051C ; G 1201
+U 1309 ; WX 924 ; N uni051D ; G 1202
+U 1310 ; WX 817 ; N uni051E ; G 1203
+U 1311 ; WX 679 ; N uni051F ; G 1204
+U 1312 ; WX 1267 ; N uni0520 ; G 1205
+U 1313 ; WX 1059 ; N uni0521 ; G 1206
+U 1314 ; WX 1273 ; N uni0522 ; G 1207
+U 1315 ; WX 1017 ; N uni0523 ; G 1208
+U 1316 ; WX 957 ; N uni0524 ; G 1209
+U 1317 ; WX 807 ; N uni0525 ; G 1210
+U 1329 ; WX 813 ; N uni0531 ; G 1211
+U 1330 ; WX 729 ; N uni0532 ; G 1212
+U 1331 ; WX 728 ; N uni0533 ; G 1213
+U 1332 ; WX 731 ; N uni0534 ; G 1214
+U 1333 ; WX 729 ; N uni0535 ; G 1215
+U 1334 ; WX 733 ; N uni0536 ; G 1216
+U 1335 ; WX 652 ; N uni0537 ; G 1217
+U 1336 ; WX 720 ; N uni0538 ; G 1218
+U 1337 ; WX 903 ; N uni0539 ; G 1219
+U 1338 ; WX 728 ; N uni053A ; G 1220
+U 1339 ; WX 666 ; N uni053B ; G 1221
+U 1340 ; WX 558 ; N uni053C ; G 1222
+U 1341 ; WX 961 ; N uni053D ; G 1223
+U 1342 ; WX 788 ; N uni053E ; G 1224
+U 1343 ; WX 713 ; N uni053F ; G 1225
+U 1344 ; WX 651 ; N uni0540 ; G 1226
+U 1345 ; WX 730 ; N uni0541 ; G 1227
+U 1346 ; WX 715 ; N uni0542 ; G 1228
+U 1347 ; WX 704 ; N uni0543 ; G 1229
+U 1348 ; WX 780 ; N uni0544 ; G 1230
+U 1349 ; WX 689 ; N uni0545 ; G 1231
+U 1350 ; WX 715 ; N uni0546 ; G 1232
+U 1351 ; WX 708 ; N uni0547 ; G 1233
+U 1352 ; WX 731 ; N uni0548 ; G 1234
+U 1353 ; WX 677 ; N uni0549 ; G 1235
+U 1354 ; WX 867 ; N uni054A ; G 1236
+U 1355 ; WX 711 ; N uni054B ; G 1237
+U 1356 ; WX 780 ; N uni054C ; G 1238
+U 1357 ; WX 731 ; N uni054D ; G 1239
+U 1358 ; WX 715 ; N uni054E ; G 1240
+U 1359 ; WX 693 ; N uni054F ; G 1241
+U 1360 ; WX 666 ; N uni0550 ; G 1242
+U 1361 ; WX 698 ; N uni0551 ; G 1243
+U 1362 ; WX 576 ; N uni0552 ; G 1244
+U 1363 ; WX 833 ; N uni0553 ; G 1245
+U 1364 ; WX 698 ; N uni0554 ; G 1246
+U 1365 ; WX 763 ; N uni0555 ; G 1247
+U 1366 ; WX 855 ; N uni0556 ; G 1248
+U 1369 ; WX 330 ; N uni0559 ; G 1249
+U 1370 ; WX 342 ; N uni055A ; G 1250
+U 1371 ; WX 308 ; N uni055B ; G 1251
+U 1372 ; WX 374 ; N uni055C ; G 1252
+U 1373 ; WX 313 ; N uni055D ; G 1253
+U 1374 ; WX 461 ; N uni055E ; G 1254
+U 1375 ; WX 468 ; N uni055F ; G 1255
+U 1377 ; WX 938 ; N uni0561 ; G 1256
+U 1378 ; WX 642 ; N uni0562 ; G 1257
+U 1379 ; WX 704 ; N uni0563 ; G 1258
+U 1380 ; WX 708 ; N uni0564 ; G 1259
+U 1381 ; WX 642 ; N uni0565 ; G 1260
+U 1382 ; WX 644 ; N uni0566 ; G 1261
+U 1383 ; WX 565 ; N uni0567 ; G 1262
+U 1384 ; WX 642 ; N uni0568 ; G 1263
+U 1385 ; WX 756 ; N uni0569 ; G 1264
+U 1386 ; WX 704 ; N uni056A ; G 1265
+U 1387 ; WX 643 ; N uni056B ; G 1266
+U 1388 ; WX 310 ; N uni056C ; G 1267
+U 1389 ; WX 984 ; N uni056D ; G 1268
+U 1390 ; WX 638 ; N uni056E ; G 1269
+U 1391 ; WX 643 ; N uni056F ; G 1270
+U 1392 ; WX 643 ; N uni0570 ; G 1271
+U 1393 ; WX 603 ; N uni0571 ; G 1272
+U 1394 ; WX 643 ; N uni0572 ; G 1273
+U 1395 ; WX 642 ; N uni0573 ; G 1274
+U 1396 ; WX 643 ; N uni0574 ; G 1275
+U 1397 ; WX 309 ; N uni0575 ; G 1276
+U 1398 ; WX 643 ; N uni0576 ; G 1277
+U 1399 ; WX 486 ; N uni0577 ; G 1278
+U 1400 ; WX 643 ; N uni0578 ; G 1279
+U 1401 ; WX 366 ; N uni0579 ; G 1280
+U 1402 ; WX 938 ; N uni057A ; G 1281
+U 1403 ; WX 573 ; N uni057B ; G 1282
+U 1404 ; WX 666 ; N uni057C ; G 1283
+U 1405 ; WX 643 ; N uni057D ; G 1284
+U 1406 ; WX 643 ; N uni057E ; G 1285
+U 1407 ; WX 934 ; N uni057F ; G 1286
+U 1408 ; WX 643 ; N uni0580 ; G 1287
+U 1409 ; WX 643 ; N uni0581 ; G 1288
+U 1410 ; WX 479 ; N uni0582 ; G 1289
+U 1411 ; WX 934 ; N uni0583 ; G 1290
+U 1412 ; WX 648 ; N uni0584 ; G 1291
+U 1413 ; WX 620 ; N uni0585 ; G 1292
+U 1414 ; WX 813 ; N uni0586 ; G 1293
+U 1415 ; WX 812 ; N uni0587 ; G 1294
+U 1417 ; WX 360 ; N uni0589 ; G 1295
+U 1418 ; WX 374 ; N uni058A ; G 1296
+U 1456 ; WX 0 ; N uni05B0 ; G 1297
+U 1457 ; WX 0 ; N uni05B1 ; G 1298
+U 1458 ; WX 0 ; N uni05B2 ; G 1299
+U 1459 ; WX 0 ; N uni05B3 ; G 1300
+U 1460 ; WX 0 ; N uni05B4 ; G 1301
+U 1461 ; WX 0 ; N uni05B5 ; G 1302
+U 1462 ; WX 0 ; N uni05B6 ; G 1303
+U 1463 ; WX 0 ; N uni05B7 ; G 1304
+U 1464 ; WX 0 ; N uni05B8 ; G 1305
+U 1465 ; WX 0 ; N uni05B9 ; G 1306
+U 1466 ; WX 0 ; N uni05BA ; G 1307
+U 1467 ; WX 0 ; N uni05BB ; G 1308
+U 1468 ; WX 0 ; N uni05BC ; G 1309
+U 1469 ; WX 0 ; N uni05BD ; G 1310
+U 1470 ; WX 415 ; N uni05BE ; G 1311
+U 1471 ; WX 0 ; N uni05BF ; G 1312
+U 1472 ; WX 372 ; N uni05C0 ; G 1313
+U 1473 ; WX 0 ; N uni05C1 ; G 1314
+U 1474 ; WX 0 ; N uni05C2 ; G 1315
+U 1475 ; WX 372 ; N uni05C3 ; G 1316
+U 1478 ; WX 497 ; N uni05C6 ; G 1317
+U 1479 ; WX 0 ; N uni05C7 ; G 1318
+U 1488 ; WX 728 ; N uni05D0 ; G 1319
+U 1489 ; WX 610 ; N uni05D1 ; G 1320
+U 1490 ; WX 447 ; N uni05D2 ; G 1321
+U 1491 ; WX 588 ; N uni05D3 ; G 1322
+U 1492 ; WX 687 ; N uni05D4 ; G 1323
+U 1493 ; WX 343 ; N uni05D5 ; G 1324
+U 1494 ; WX 400 ; N uni05D6 ; G 1325
+U 1495 ; WX 687 ; N uni05D7 ; G 1326
+U 1496 ; WX 679 ; N uni05D8 ; G 1327
+U 1497 ; WX 294 ; N uni05D9 ; G 1328
+U 1498 ; WX 578 ; N uni05DA ; G 1329
+U 1499 ; WX 566 ; N uni05DB ; G 1330
+U 1500 ; WX 605 ; N uni05DC ; G 1331
+U 1501 ; WX 696 ; N uni05DD ; G 1332
+U 1502 ; WX 724 ; N uni05DE ; G 1333
+U 1503 ; WX 343 ; N uni05DF ; G 1334
+U 1504 ; WX 453 ; N uni05E0 ; G 1335
+U 1505 ; WX 680 ; N uni05E1 ; G 1336
+U 1506 ; WX 666 ; N uni05E2 ; G 1337
+U 1507 ; WX 675 ; N uni05E3 ; G 1338
+U 1508 ; WX 658 ; N uni05E4 ; G 1339
+U 1509 ; WX 661 ; N uni05E5 ; G 1340
+U 1510 ; WX 653 ; N uni05E6 ; G 1341
+U 1511 ; WX 736 ; N uni05E7 ; G 1342
+U 1512 ; WX 602 ; N uni05E8 ; G 1343
+U 1513 ; WX 758 ; N uni05E9 ; G 1344
+U 1514 ; WX 683 ; N uni05EA ; G 1345
+U 1520 ; WX 664 ; N uni05F0 ; G 1346
+U 1521 ; WX 567 ; N uni05F1 ; G 1347
+U 1522 ; WX 519 ; N uni05F2 ; G 1348
+U 1523 ; WX 444 ; N uni05F3 ; G 1349
+U 1524 ; WX 710 ; N uni05F4 ; G 1350
+U 1542 ; WX 667 ; N uni0606 ; G 1351
+U 1543 ; WX 667 ; N uni0607 ; G 1352
+U 1545 ; WX 884 ; N uni0609 ; G 1353
+U 1546 ; WX 1157 ; N uni060A ; G 1354
+U 1548 ; WX 380 ; N uni060C ; G 1355
+U 1557 ; WX 0 ; N uni0615 ; G 1356
+U 1563 ; WX 400 ; N uni061B ; G 1357
+U 1567 ; WX 580 ; N uni061F ; G 1358
+U 1569 ; WX 511 ; N uni0621 ; G 1359
+U 1570 ; WX 343 ; N uni0622 ; G 1360
+U 1571 ; WX 343 ; N uni0623 ; G 1361
+U 1572 ; WX 622 ; N uni0624 ; G 1362
+U 1573 ; WX 343 ; N uni0625 ; G 1363
+U 1574 ; WX 917 ; N uni0626 ; G 1364
+U 1575 ; WX 343 ; N uni0627 ; G 1365
+U 1576 ; WX 1005 ; N uni0628 ; G 1366
+U 1577 ; WX 590 ; N uni0629 ; G 1367
+U 1578 ; WX 1005 ; N uni062A ; G 1368
+U 1579 ; WX 1005 ; N uni062B ; G 1369
+U 1580 ; WX 721 ; N uni062C ; G 1370
+U 1581 ; WX 721 ; N uni062D ; G 1371
+U 1582 ; WX 721 ; N uni062E ; G 1372
+U 1583 ; WX 513 ; N uni062F ; G 1373
+U 1584 ; WX 513 ; N uni0630 ; G 1374
+U 1585 ; WX 576 ; N uni0631 ; G 1375
+U 1586 ; WX 576 ; N uni0632 ; G 1376
+U 1587 ; WX 1380 ; N uni0633 ; G 1377
+U 1588 ; WX 1380 ; N uni0634 ; G 1378
+U 1589 ; WX 1345 ; N uni0635 ; G 1379
+U 1590 ; WX 1345 ; N uni0636 ; G 1380
+U 1591 ; WX 1039 ; N uni0637 ; G 1381
+U 1592 ; WX 1039 ; N uni0638 ; G 1382
+U 1593 ; WX 683 ; N uni0639 ; G 1383
+U 1594 ; WX 683 ; N uni063A ; G 1384
+U 1600 ; WX 342 ; N uni0640 ; G 1385
+U 1601 ; WX 1162 ; N uni0641 ; G 1386
+U 1602 ; WX 894 ; N uni0642 ; G 1387
+U 1603 ; WX 917 ; N uni0643 ; G 1388
+U 1604 ; WX 868 ; N uni0644 ; G 1389
+U 1605 ; WX 733 ; N uni0645 ; G 1390
+U 1606 ; WX 854 ; N uni0646 ; G 1391
+U 1607 ; WX 590 ; N uni0647 ; G 1392
+U 1608 ; WX 622 ; N uni0648 ; G 1393
+U 1609 ; WX 917 ; N uni0649 ; G 1394
+U 1610 ; WX 917 ; N uni064A ; G 1395
+U 1611 ; WX 0 ; N uni064B ; G 1396
+U 1612 ; WX 0 ; N uni064C ; G 1397
+U 1613 ; WX 0 ; N uni064D ; G 1398
+U 1614 ; WX 0 ; N uni064E ; G 1399
+U 1615 ; WX 0 ; N uni064F ; G 1400
+U 1616 ; WX 0 ; N uni0650 ; G 1401
+U 1617 ; WX 0 ; N uni0651 ; G 1402
+U 1618 ; WX 0 ; N uni0652 ; G 1403
+U 1619 ; WX 0 ; N uni0653 ; G 1404
+U 1620 ; WX 0 ; N uni0654 ; G 1405
+U 1621 ; WX 0 ; N uni0655 ; G 1406
+U 1623 ; WX 0 ; N uni0657 ; G 1407
+U 1626 ; WX 500 ; N uni065A ; G 1408
+U 1632 ; WX 610 ; N uni0660 ; G 1409
+U 1633 ; WX 610 ; N uni0661 ; G 1410
+U 1634 ; WX 610 ; N uni0662 ; G 1411
+U 1635 ; WX 610 ; N uni0663 ; G 1412
+U 1636 ; WX 610 ; N uni0664 ; G 1413
+U 1637 ; WX 610 ; N uni0665 ; G 1414
+U 1638 ; WX 610 ; N uni0666 ; G 1415
+U 1639 ; WX 610 ; N uni0667 ; G 1416
+U 1640 ; WX 610 ; N uni0668 ; G 1417
+U 1641 ; WX 610 ; N uni0669 ; G 1418
+U 1642 ; WX 610 ; N uni066A ; G 1419
+U 1643 ; WX 374 ; N uni066B ; G 1420
+U 1644 ; WX 380 ; N uni066C ; G 1421
+U 1645 ; WX 545 ; N uni066D ; G 1422
+U 1646 ; WX 1005 ; N uni066E ; G 1423
+U 1647 ; WX 894 ; N uni066F ; G 1424
+U 1648 ; WX 0 ; N uni0670 ; G 1425
+U 1652 ; WX 292 ; N uni0674 ; G 1426
+U 1657 ; WX 1005 ; N uni0679 ; G 1427
+U 1658 ; WX 1005 ; N uni067A ; G 1428
+U 1659 ; WX 1005 ; N uni067B ; G 1429
+U 1660 ; WX 1005 ; N uni067C ; G 1430
+U 1661 ; WX 1005 ; N uni067D ; G 1431
+U 1662 ; WX 1005 ; N uni067E ; G 1432
+U 1663 ; WX 1005 ; N uni067F ; G 1433
+U 1664 ; WX 1005 ; N uni0680 ; G 1434
+U 1665 ; WX 721 ; N uni0681 ; G 1435
+U 1666 ; WX 721 ; N uni0682 ; G 1436
+U 1667 ; WX 721 ; N uni0683 ; G 1437
+U 1668 ; WX 721 ; N uni0684 ; G 1438
+U 1669 ; WX 721 ; N uni0685 ; G 1439
+U 1670 ; WX 721 ; N uni0686 ; G 1440
+U 1671 ; WX 721 ; N uni0687 ; G 1441
+U 1672 ; WX 445 ; N uni0688 ; G 1442
+U 1673 ; WX 445 ; N uni0689 ; G 1443
+U 1674 ; WX 445 ; N uni068A ; G 1444
+U 1675 ; WX 445 ; N uni068B ; G 1445
+U 1676 ; WX 445 ; N uni068C ; G 1446
+U 1677 ; WX 445 ; N uni068D ; G 1447
+U 1678 ; WX 445 ; N uni068E ; G 1448
+U 1679 ; WX 445 ; N uni068F ; G 1449
+U 1680 ; WX 445 ; N uni0690 ; G 1450
+U 1681 ; WX 576 ; N uni0691 ; G 1451
+U 1682 ; WX 576 ; N uni0692 ; G 1452
+U 1683 ; WX 576 ; N uni0693 ; G 1453
+U 1684 ; WX 576 ; N uni0694 ; G 1454
+U 1685 ; WX 681 ; N uni0695 ; G 1455
+U 1686 ; WX 576 ; N uni0696 ; G 1456
+U 1687 ; WX 576 ; N uni0697 ; G 1457
+U 1688 ; WX 576 ; N uni0698 ; G 1458
+U 1689 ; WX 576 ; N uni0699 ; G 1459
+U 1690 ; WX 1380 ; N uni069A ; G 1460
+U 1691 ; WX 1380 ; N uni069B ; G 1461
+U 1692 ; WX 1380 ; N uni069C ; G 1462
+U 1693 ; WX 1345 ; N uni069D ; G 1463
+U 1694 ; WX 1345 ; N uni069E ; G 1464
+U 1695 ; WX 1039 ; N uni069F ; G 1465
+U 1696 ; WX 683 ; N uni06A0 ; G 1466
+U 1697 ; WX 1162 ; N uni06A1 ; G 1467
+U 1698 ; WX 1162 ; N uni06A2 ; G 1468
+U 1699 ; WX 1162 ; N uni06A3 ; G 1469
+U 1700 ; WX 1162 ; N uni06A4 ; G 1470
+U 1701 ; WX 1162 ; N uni06A5 ; G 1471
+U 1702 ; WX 1162 ; N uni06A6 ; G 1472
+U 1703 ; WX 894 ; N uni06A7 ; G 1473
+U 1704 ; WX 894 ; N uni06A8 ; G 1474
+U 1705 ; WX 1024 ; N uni06A9 ; G 1475
+U 1706 ; WX 1271 ; N uni06AA ; G 1476
+U 1707 ; WX 1024 ; N uni06AB ; G 1477
+U 1708 ; WX 917 ; N uni06AC ; G 1478
+U 1709 ; WX 917 ; N uni06AD ; G 1479
+U 1710 ; WX 917 ; N uni06AE ; G 1480
+U 1711 ; WX 1024 ; N uni06AF ; G 1481
+U 1712 ; WX 1024 ; N uni06B0 ; G 1482
+U 1713 ; WX 1024 ; N uni06B1 ; G 1483
+U 1714 ; WX 1024 ; N uni06B2 ; G 1484
+U 1715 ; WX 1024 ; N uni06B3 ; G 1485
+U 1716 ; WX 1024 ; N uni06B4 ; G 1486
+U 1717 ; WX 868 ; N uni06B5 ; G 1487
+U 1718 ; WX 868 ; N uni06B6 ; G 1488
+U 1719 ; WX 868 ; N uni06B7 ; G 1489
+U 1720 ; WX 868 ; N uni06B8 ; G 1490
+U 1721 ; WX 854 ; N uni06B9 ; G 1491
+U 1722 ; WX 854 ; N uni06BA ; G 1492
+U 1723 ; WX 854 ; N uni06BB ; G 1493
+U 1724 ; WX 854 ; N uni06BC ; G 1494
+U 1725 ; WX 854 ; N uni06BD ; G 1495
+U 1726 ; WX 938 ; N uni06BE ; G 1496
+U 1727 ; WX 721 ; N uni06BF ; G 1497
+U 1734 ; WX 622 ; N uni06C6 ; G 1498
+U 1735 ; WX 622 ; N uni06C7 ; G 1499
+U 1736 ; WX 622 ; N uni06C8 ; G 1500
+U 1739 ; WX 622 ; N uni06CB ; G 1501
+U 1740 ; WX 917 ; N uni06CC ; G 1502
+U 1742 ; WX 917 ; N uni06CE ; G 1503
+U 1744 ; WX 917 ; N uni06D0 ; G 1504
+U 1749 ; WX 590 ; N uni06D5 ; G 1505
+U 1776 ; WX 610 ; N uni06F0 ; G 1506
+U 1777 ; WX 610 ; N uni06F1 ; G 1507
+U 1778 ; WX 610 ; N uni06F2 ; G 1508
+U 1779 ; WX 610 ; N uni06F3 ; G 1509
+U 1780 ; WX 610 ; N uni06F4 ; G 1510
+U 1781 ; WX 610 ; N uni06F5 ; G 1511
+U 1782 ; WX 610 ; N uni06F6 ; G 1512
+U 1783 ; WX 610 ; N uni06F7 ; G 1513
+U 1784 ; WX 610 ; N uni06F8 ; G 1514
+U 1785 ; WX 610 ; N uni06F9 ; G 1515
+U 1984 ; WX 696 ; N uni07C0 ; G 1516
+U 1985 ; WX 696 ; N uni07C1 ; G 1517
+U 1986 ; WX 696 ; N uni07C2 ; G 1518
+U 1987 ; WX 696 ; N uni07C3 ; G 1519
+U 1988 ; WX 696 ; N uni07C4 ; G 1520
+U 1989 ; WX 696 ; N uni07C5 ; G 1521
+U 1990 ; WX 696 ; N uni07C6 ; G 1522
+U 1991 ; WX 696 ; N uni07C7 ; G 1523
+U 1992 ; WX 696 ; N uni07C8 ; G 1524
+U 1993 ; WX 696 ; N uni07C9 ; G 1525
+U 1994 ; WX 343 ; N uni07CA ; G 1526
+U 1995 ; WX 547 ; N uni07CB ; G 1527
+U 1996 ; WX 543 ; N uni07CC ; G 1528
+U 1997 ; WX 652 ; N uni07CD ; G 1529
+U 1998 ; WX 691 ; N uni07CE ; G 1530
+U 1999 ; WX 691 ; N uni07CF ; G 1531
+U 2000 ; WX 594 ; N uni07D0 ; G 1532
+U 2001 ; WX 691 ; N uni07D1 ; G 1533
+U 2002 ; WX 904 ; N uni07D2 ; G 1534
+U 2003 ; WX 551 ; N uni07D3 ; G 1535
+U 2004 ; WX 551 ; N uni07D4 ; G 1536
+U 2005 ; WX 627 ; N uni07D5 ; G 1537
+U 2006 ; WX 688 ; N uni07D6 ; G 1538
+U 2007 ; WX 444 ; N uni07D7 ; G 1539
+U 2008 ; WX 1022 ; N uni07D8 ; G 1540
+U 2009 ; WX 506 ; N uni07D9 ; G 1541
+U 2010 ; WX 826 ; N uni07DA ; G 1542
+U 2011 ; WX 691 ; N uni07DB ; G 1543
+U 2012 ; WX 652 ; N uni07DC ; G 1544
+U 2013 ; WX 912 ; N uni07DD ; G 1545
+U 2014 ; WX 627 ; N uni07DE ; G 1546
+U 2015 ; WX 707 ; N uni07DF ; G 1547
+U 2016 ; WX 506 ; N uni07E0 ; G 1548
+U 2017 ; WX 652 ; N uni07E1 ; G 1549
+U 2018 ; WX 574 ; N uni07E2 ; G 1550
+U 2019 ; WX 627 ; N uni07E3 ; G 1551
+U 2020 ; WX 627 ; N uni07E4 ; G 1552
+U 2021 ; WX 627 ; N uni07E5 ; G 1553
+U 2022 ; WX 574 ; N uni07E6 ; G 1554
+U 2023 ; WX 574 ; N uni07E7 ; G 1555
+U 2027 ; WX 0 ; N uni07EB ; G 1556
+U 2028 ; WX 0 ; N uni07EC ; G 1557
+U 2029 ; WX 0 ; N uni07ED ; G 1558
+U 2030 ; WX 0 ; N uni07EE ; G 1559
+U 2031 ; WX 0 ; N uni07EF ; G 1560
+U 2032 ; WX 0 ; N uni07F0 ; G 1561
+U 2033 ; WX 0 ; N uni07F1 ; G 1562
+U 2034 ; WX 0 ; N uni07F2 ; G 1563
+U 2035 ; WX 0 ; N uni07F3 ; G 1564
+U 2036 ; WX 380 ; N uni07F4 ; G 1565
+U 2037 ; WX 380 ; N uni07F5 ; G 1566
+U 2040 ; WX 691 ; N uni07F8 ; G 1567
+U 2041 ; WX 691 ; N uni07F9 ; G 1568
+U 2042 ; WX 415 ; N uni07FA ; G 1569
+U 3647 ; WX 696 ; N uni0E3F ; G 1570
+U 3713 ; WX 790 ; N uni0E81 ; G 1571
+U 3714 ; WX 748 ; N uni0E82 ; G 1572
+U 3716 ; WX 749 ; N uni0E84 ; G 1573
+U 3719 ; WX 569 ; N uni0E87 ; G 1574
+U 3720 ; WX 742 ; N uni0E88 ; G 1575
+U 3722 ; WX 744 ; N uni0E8A ; G 1576
+U 3725 ; WX 761 ; N uni0E8D ; G 1577
+U 3732 ; WX 706 ; N uni0E94 ; G 1578
+U 3733 ; WX 704 ; N uni0E95 ; G 1579
+U 3734 ; WX 747 ; N uni0E96 ; G 1580
+U 3735 ; WX 819 ; N uni0E97 ; G 1581
+U 3737 ; WX 730 ; N uni0E99 ; G 1582
+U 3738 ; WX 727 ; N uni0E9A ; G 1583
+U 3739 ; WX 727 ; N uni0E9B ; G 1584
+U 3740 ; WX 922 ; N uni0E9C ; G 1585
+U 3741 ; WX 827 ; N uni0E9D ; G 1586
+U 3742 ; WX 866 ; N uni0E9E ; G 1587
+U 3743 ; WX 866 ; N uni0E9F ; G 1588
+U 3745 ; WX 836 ; N uni0EA1 ; G 1589
+U 3746 ; WX 761 ; N uni0EA2 ; G 1590
+U 3747 ; WX 770 ; N uni0EA3 ; G 1591
+U 3749 ; WX 769 ; N uni0EA5 ; G 1592
+U 3751 ; WX 713 ; N uni0EA7 ; G 1593
+U 3754 ; WX 827 ; N uni0EAA ; G 1594
+U 3755 ; WX 1031 ; N uni0EAB ; G 1595
+U 3757 ; WX 724 ; N uni0EAD ; G 1596
+U 3758 ; WX 784 ; N uni0EAE ; G 1597
+U 3759 ; WX 934 ; N uni0EAF ; G 1598
+U 3760 ; WX 688 ; N uni0EB0 ; G 1599
+U 3761 ; WX 0 ; N uni0EB1 ; G 1600
+U 3762 ; WX 610 ; N uni0EB2 ; G 1601
+U 3763 ; WX 610 ; N uni0EB3 ; G 1602
+U 3764 ; WX 0 ; N uni0EB4 ; G 1603
+U 3765 ; WX 0 ; N uni0EB5 ; G 1604
+U 3766 ; WX 0 ; N uni0EB6 ; G 1605
+U 3767 ; WX 0 ; N uni0EB7 ; G 1606
+U 3768 ; WX 0 ; N uni0EB8 ; G 1607
+U 3769 ; WX 0 ; N uni0EB9 ; G 1608
+U 3771 ; WX 0 ; N uni0EBB ; G 1609
+U 3772 ; WX 0 ; N uni0EBC ; G 1610
+U 3773 ; WX 670 ; N uni0EBD ; G 1611
+U 3776 ; WX 516 ; N uni0EC0 ; G 1612
+U 3777 ; WX 860 ; N uni0EC1 ; G 1613
+U 3778 ; WX 516 ; N uni0EC2 ; G 1614
+U 3779 ; WX 650 ; N uni0EC3 ; G 1615
+U 3780 ; WX 632 ; N uni0EC4 ; G 1616
+U 3782 ; WX 759 ; N uni0EC6 ; G 1617
+U 3784 ; WX 0 ; N uni0EC8 ; G 1618
+U 3785 ; WX 0 ; N uni0EC9 ; G 1619
+U 3786 ; WX 0 ; N uni0ECA ; G 1620
+U 3787 ; WX 0 ; N uni0ECB ; G 1621
+U 3788 ; WX 0 ; N uni0ECC ; G 1622
+U 3789 ; WX 0 ; N uni0ECD ; G 1623
+U 3792 ; WX 771 ; N uni0ED0 ; G 1624
+U 3793 ; WX 771 ; N uni0ED1 ; G 1625
+U 3794 ; WX 693 ; N uni0ED2 ; G 1626
+U 3795 ; WX 836 ; N uni0ED3 ; G 1627
+U 3796 ; WX 729 ; N uni0ED4 ; G 1628
+U 3797 ; WX 729 ; N uni0ED5 ; G 1629
+U 3798 ; WX 849 ; N uni0ED6 ; G 1630
+U 3799 ; WX 790 ; N uni0ED7 ; G 1631
+U 3800 ; WX 759 ; N uni0ED8 ; G 1632
+U 3801 ; WX 910 ; N uni0ED9 ; G 1633
+U 3804 ; WX 1363 ; N uni0EDC ; G 1634
+U 3805 ; WX 1363 ; N uni0EDD ; G 1635
+U 4256 ; WX 874 ; N uni10A0 ; G 1636
+U 4257 ; WX 733 ; N uni10A1 ; G 1637
+U 4258 ; WX 679 ; N uni10A2 ; G 1638
+U 4259 ; WX 834 ; N uni10A3 ; G 1639
+U 4260 ; WX 615 ; N uni10A4 ; G 1640
+U 4261 ; WX 768 ; N uni10A5 ; G 1641
+U 4262 ; WX 753 ; N uni10A6 ; G 1642
+U 4263 ; WX 914 ; N uni10A7 ; G 1643
+U 4264 ; WX 453 ; N uni10A8 ; G 1644
+U 4265 ; WX 620 ; N uni10A9 ; G 1645
+U 4266 ; WX 843 ; N uni10AA ; G 1646
+U 4267 ; WX 882 ; N uni10AB ; G 1647
+U 4268 ; WX 625 ; N uni10AC ; G 1648
+U 4269 ; WX 854 ; N uni10AD ; G 1649
+U 4270 ; WX 781 ; N uni10AE ; G 1650
+U 4271 ; WX 629 ; N uni10AF ; G 1651
+U 4272 ; WX 912 ; N uni10B0 ; G 1652
+U 4273 ; WX 621 ; N uni10B1 ; G 1653
+U 4274 ; WX 620 ; N uni10B2 ; G 1654
+U 4275 ; WX 854 ; N uni10B3 ; G 1655
+U 4276 ; WX 866 ; N uni10B4 ; G 1656
+U 4277 ; WX 724 ; N uni10B5 ; G 1657
+U 4278 ; WX 630 ; N uni10B6 ; G 1658
+U 4279 ; WX 621 ; N uni10B7 ; G 1659
+U 4280 ; WX 625 ; N uni10B8 ; G 1660
+U 4281 ; WX 620 ; N uni10B9 ; G 1661
+U 4282 ; WX 818 ; N uni10BA ; G 1662
+U 4283 ; WX 874 ; N uni10BB ; G 1663
+U 4284 ; WX 615 ; N uni10BC ; G 1664
+U 4285 ; WX 623 ; N uni10BD ; G 1665
+U 4286 ; WX 625 ; N uni10BE ; G 1666
+U 4287 ; WX 725 ; N uni10BF ; G 1667
+U 4288 ; WX 844 ; N uni10C0 ; G 1668
+U 4289 ; WX 596 ; N uni10C1 ; G 1669
+U 4290 ; WX 688 ; N uni10C2 ; G 1670
+U 4291 ; WX 596 ; N uni10C3 ; G 1671
+U 4292 ; WX 594 ; N uni10C4 ; G 1672
+U 4293 ; WX 738 ; N uni10C5 ; G 1673
+U 4304 ; WX 554 ; N uni10D0 ; G 1674
+U 4305 ; WX 563 ; N uni10D1 ; G 1675
+U 4306 ; WX 622 ; N uni10D2 ; G 1676
+U 4307 ; WX 834 ; N uni10D3 ; G 1677
+U 4308 ; WX 555 ; N uni10D4 ; G 1678
+U 4309 ; WX 564 ; N uni10D5 ; G 1679
+U 4310 ; WX 551 ; N uni10D6 ; G 1680
+U 4311 ; WX 828 ; N uni10D7 ; G 1681
+U 4312 ; WX 563 ; N uni10D8 ; G 1682
+U 4313 ; WX 556 ; N uni10D9 ; G 1683
+U 4314 ; WX 1074 ; N uni10DA ; G 1684
+U 4315 ; WX 568 ; N uni10DB ; G 1685
+U 4316 ; WX 568 ; N uni10DC ; G 1686
+U 4317 ; WX 814 ; N uni10DD ; G 1687
+U 4318 ; WX 554 ; N uni10DE ; G 1688
+U 4319 ; WX 563 ; N uni10DF ; G 1689
+U 4320 ; WX 823 ; N uni10E0 ; G 1690
+U 4321 ; WX 568 ; N uni10E1 ; G 1691
+U 4322 ; WX 700 ; N uni10E2 ; G 1692
+U 4323 ; WX 591 ; N uni10E3 ; G 1693
+U 4324 ; WX 852 ; N uni10E4 ; G 1694
+U 4325 ; WX 560 ; N uni10E5 ; G 1695
+U 4326 ; WX 814 ; N uni10E6 ; G 1696
+U 4327 ; WX 563 ; N uni10E7 ; G 1697
+U 4328 ; WX 553 ; N uni10E8 ; G 1698
+U 4329 ; WX 568 ; N uni10E9 ; G 1699
+U 4330 ; WX 622 ; N uni10EA ; G 1700
+U 4331 ; WX 568 ; N uni10EB ; G 1701
+U 4332 ; WX 553 ; N uni10EC ; G 1702
+U 4333 ; WX 566 ; N uni10ED ; G 1703
+U 4334 ; WX 568 ; N uni10EE ; G 1704
+U 4335 ; WX 540 ; N uni10EF ; G 1705
+U 4336 ; WX 554 ; N uni10F0 ; G 1706
+U 4337 ; WX 559 ; N uni10F1 ; G 1707
+U 4338 ; WX 553 ; N uni10F2 ; G 1708
+U 4339 ; WX 554 ; N uni10F3 ; G 1709
+U 4340 ; WX 553 ; N uni10F4 ; G 1710
+U 4341 ; WX 587 ; N uni10F5 ; G 1711
+U 4342 ; WX 853 ; N uni10F6 ; G 1712
+U 4343 ; WX 604 ; N uni10F7 ; G 1713
+U 4344 ; WX 563 ; N uni10F8 ; G 1714
+U 4345 ; WX 622 ; N uni10F9 ; G 1715
+U 4346 ; WX 554 ; N uni10FA ; G 1716
+U 4347 ; WX 448 ; N uni10FB ; G 1717
+U 4348 ; WX 324 ; N uni10FC ; G 1718
+U 5121 ; WX 774 ; N uni1401 ; G 1719
+U 5122 ; WX 774 ; N uni1402 ; G 1720
+U 5123 ; WX 774 ; N uni1403 ; G 1721
+U 5124 ; WX 774 ; N uni1404 ; G 1722
+U 5125 ; WX 905 ; N uni1405 ; G 1723
+U 5126 ; WX 905 ; N uni1406 ; G 1724
+U 5127 ; WX 905 ; N uni1407 ; G 1725
+U 5129 ; WX 905 ; N uni1409 ; G 1726
+U 5130 ; WX 905 ; N uni140A ; G 1727
+U 5131 ; WX 905 ; N uni140B ; G 1728
+U 5132 ; WX 1018 ; N uni140C ; G 1729
+U 5133 ; WX 1009 ; N uni140D ; G 1730
+U 5134 ; WX 1018 ; N uni140E ; G 1731
+U 5135 ; WX 1009 ; N uni140F ; G 1732
+U 5136 ; WX 1018 ; N uni1410 ; G 1733
+U 5137 ; WX 1009 ; N uni1411 ; G 1734
+U 5138 ; WX 1149 ; N uni1412 ; G 1735
+U 5139 ; WX 1140 ; N uni1413 ; G 1736
+U 5140 ; WX 1149 ; N uni1414 ; G 1737
+U 5141 ; WX 1140 ; N uni1415 ; G 1738
+U 5142 ; WX 905 ; N uni1416 ; G 1739
+U 5143 ; WX 1149 ; N uni1417 ; G 1740
+U 5144 ; WX 1142 ; N uni1418 ; G 1741
+U 5145 ; WX 1149 ; N uni1419 ; G 1742
+U 5146 ; WX 1142 ; N uni141A ; G 1743
+U 5147 ; WX 905 ; N uni141B ; G 1744
+U 5149 ; WX 310 ; N uni141D ; G 1745
+U 5150 ; WX 529 ; N uni141E ; G 1746
+U 5151 ; WX 425 ; N uni141F ; G 1747
+U 5152 ; WX 425 ; N uni1420 ; G 1748
+U 5153 ; WX 395 ; N uni1421 ; G 1749
+U 5154 ; WX 395 ; N uni1422 ; G 1750
+U 5155 ; WX 395 ; N uni1423 ; G 1751
+U 5156 ; WX 395 ; N uni1424 ; G 1752
+U 5157 ; WX 564 ; N uni1425 ; G 1753
+U 5158 ; WX 470 ; N uni1426 ; G 1754
+U 5159 ; WX 310 ; N uni1427 ; G 1755
+U 5160 ; WX 395 ; N uni1428 ; G 1756
+U 5161 ; WX 395 ; N uni1429 ; G 1757
+U 5162 ; WX 395 ; N uni142A ; G 1758
+U 5163 ; WX 1213 ; N uni142B ; G 1759
+U 5164 ; WX 986 ; N uni142C ; G 1760
+U 5165 ; WX 1216 ; N uni142D ; G 1761
+U 5166 ; WX 1297 ; N uni142E ; G 1762
+U 5167 ; WX 774 ; N uni142F ; G 1763
+U 5168 ; WX 774 ; N uni1430 ; G 1764
+U 5169 ; WX 774 ; N uni1431 ; G 1765
+U 5170 ; WX 774 ; N uni1432 ; G 1766
+U 5171 ; WX 886 ; N uni1433 ; G 1767
+U 5172 ; WX 886 ; N uni1434 ; G 1768
+U 5173 ; WX 886 ; N uni1435 ; G 1769
+U 5175 ; WX 886 ; N uni1437 ; G 1770
+U 5176 ; WX 886 ; N uni1438 ; G 1771
+U 5177 ; WX 886 ; N uni1439 ; G 1772
+U 5178 ; WX 1018 ; N uni143A ; G 1773
+U 5179 ; WX 1009 ; N uni143B ; G 1774
+U 5180 ; WX 1018 ; N uni143C ; G 1775
+U 5181 ; WX 1009 ; N uni143D ; G 1776
+U 5182 ; WX 1018 ; N uni143E ; G 1777
+U 5183 ; WX 1009 ; N uni143F ; G 1778
+U 5184 ; WX 1149 ; N uni1440 ; G 1779
+U 5185 ; WX 1140 ; N uni1441 ; G 1780
+U 5186 ; WX 1149 ; N uni1442 ; G 1781
+U 5187 ; WX 1140 ; N uni1443 ; G 1782
+U 5188 ; WX 1149 ; N uni1444 ; G 1783
+U 5189 ; WX 1142 ; N uni1445 ; G 1784
+U 5190 ; WX 1149 ; N uni1446 ; G 1785
+U 5191 ; WX 1142 ; N uni1447 ; G 1786
+U 5192 ; WX 886 ; N uni1448 ; G 1787
+U 5193 ; WX 576 ; N uni1449 ; G 1788
+U 5194 ; WX 229 ; N uni144A ; G 1789
+U 5196 ; WX 812 ; N uni144C ; G 1790
+U 5197 ; WX 812 ; N uni144D ; G 1791
+U 5198 ; WX 812 ; N uni144E ; G 1792
+U 5199 ; WX 812 ; N uni144F ; G 1793
+U 5200 ; WX 815 ; N uni1450 ; G 1794
+U 5201 ; WX 815 ; N uni1451 ; G 1795
+U 5202 ; WX 815 ; N uni1452 ; G 1796
+U 5204 ; WX 815 ; N uni1454 ; G 1797
+U 5205 ; WX 815 ; N uni1455 ; G 1798
+U 5206 ; WX 815 ; N uni1456 ; G 1799
+U 5207 ; WX 1056 ; N uni1457 ; G 1800
+U 5208 ; WX 1048 ; N uni1458 ; G 1801
+U 5209 ; WX 1056 ; N uni1459 ; G 1802
+U 5210 ; WX 1048 ; N uni145A ; G 1803
+U 5211 ; WX 1056 ; N uni145B ; G 1804
+U 5212 ; WX 1048 ; N uni145C ; G 1805
+U 5213 ; WX 1060 ; N uni145D ; G 1806
+U 5214 ; WX 1054 ; N uni145E ; G 1807
+U 5215 ; WX 1060 ; N uni145F ; G 1808
+U 5216 ; WX 1054 ; N uni1460 ; G 1809
+U 5217 ; WX 1060 ; N uni1461 ; G 1810
+U 5218 ; WX 1052 ; N uni1462 ; G 1811
+U 5219 ; WX 1060 ; N uni1463 ; G 1812
+U 5220 ; WX 1052 ; N uni1464 ; G 1813
+U 5221 ; WX 1060 ; N uni1465 ; G 1814
+U 5222 ; WX 483 ; N uni1466 ; G 1815
+U 5223 ; WX 1005 ; N uni1467 ; G 1816
+U 5224 ; WX 1005 ; N uni1468 ; G 1817
+U 5225 ; WX 1023 ; N uni1469 ; G 1818
+U 5226 ; WX 1017 ; N uni146A ; G 1819
+U 5227 ; WX 743 ; N uni146B ; G 1820
+U 5228 ; WX 743 ; N uni146C ; G 1821
+U 5229 ; WX 743 ; N uni146D ; G 1822
+U 5230 ; WX 743 ; N uni146E ; G 1823
+U 5231 ; WX 743 ; N uni146F ; G 1824
+U 5232 ; WX 743 ; N uni1470 ; G 1825
+U 5233 ; WX 743 ; N uni1471 ; G 1826
+U 5234 ; WX 743 ; N uni1472 ; G 1827
+U 5235 ; WX 743 ; N uni1473 ; G 1828
+U 5236 ; WX 1029 ; N uni1474 ; G 1829
+U 5237 ; WX 975 ; N uni1475 ; G 1830
+U 5238 ; WX 980 ; N uni1476 ; G 1831
+U 5239 ; WX 975 ; N uni1477 ; G 1832
+U 5240 ; WX 980 ; N uni1478 ; G 1833
+U 5241 ; WX 975 ; N uni1479 ; G 1834
+U 5242 ; WX 1029 ; N uni147A ; G 1835
+U 5243 ; WX 975 ; N uni147B ; G 1836
+U 5244 ; WX 1029 ; N uni147C ; G 1837
+U 5245 ; WX 975 ; N uni147D ; G 1838
+U 5246 ; WX 980 ; N uni147E ; G 1839
+U 5247 ; WX 975 ; N uni147F ; G 1840
+U 5248 ; WX 980 ; N uni1480 ; G 1841
+U 5249 ; WX 975 ; N uni1481 ; G 1842
+U 5250 ; WX 980 ; N uni1482 ; G 1843
+U 5251 ; WX 501 ; N uni1483 ; G 1844
+U 5252 ; WX 501 ; N uni1484 ; G 1845
+U 5253 ; WX 938 ; N uni1485 ; G 1846
+U 5254 ; WX 938 ; N uni1486 ; G 1847
+U 5255 ; WX 938 ; N uni1487 ; G 1848
+U 5256 ; WX 938 ; N uni1488 ; G 1849
+U 5257 ; WX 743 ; N uni1489 ; G 1850
+U 5258 ; WX 743 ; N uni148A ; G 1851
+U 5259 ; WX 743 ; N uni148B ; G 1852
+U 5260 ; WX 743 ; N uni148C ; G 1853
+U 5261 ; WX 743 ; N uni148D ; G 1854
+U 5262 ; WX 743 ; N uni148E ; G 1855
+U 5263 ; WX 743 ; N uni148F ; G 1856
+U 5264 ; WX 743 ; N uni1490 ; G 1857
+U 5265 ; WX 743 ; N uni1491 ; G 1858
+U 5266 ; WX 1029 ; N uni1492 ; G 1859
+U 5267 ; WX 975 ; N uni1493 ; G 1860
+U 5268 ; WX 1029 ; N uni1494 ; G 1861
+U 5269 ; WX 975 ; N uni1495 ; G 1862
+U 5270 ; WX 1029 ; N uni1496 ; G 1863
+U 5271 ; WX 975 ; N uni1497 ; G 1864
+U 5272 ; WX 1029 ; N uni1498 ; G 1865
+U 5273 ; WX 975 ; N uni1499 ; G 1866
+U 5274 ; WX 1029 ; N uni149A ; G 1867
+U 5275 ; WX 975 ; N uni149B ; G 1868
+U 5276 ; WX 1029 ; N uni149C ; G 1869
+U 5277 ; WX 975 ; N uni149D ; G 1870
+U 5278 ; WX 1029 ; N uni149E ; G 1871
+U 5279 ; WX 975 ; N uni149F ; G 1872
+U 5280 ; WX 1029 ; N uni14A0 ; G 1873
+U 5281 ; WX 501 ; N uni14A1 ; G 1874
+U 5282 ; WX 501 ; N uni14A2 ; G 1875
+U 5283 ; WX 626 ; N uni14A3 ; G 1876
+U 5284 ; WX 626 ; N uni14A4 ; G 1877
+U 5285 ; WX 626 ; N uni14A5 ; G 1878
+U 5286 ; WX 626 ; N uni14A6 ; G 1879
+U 5287 ; WX 626 ; N uni14A7 ; G 1880
+U 5288 ; WX 626 ; N uni14A8 ; G 1881
+U 5289 ; WX 626 ; N uni14A9 ; G 1882
+U 5290 ; WX 626 ; N uni14AA ; G 1883
+U 5291 ; WX 626 ; N uni14AB ; G 1884
+U 5292 ; WX 881 ; N uni14AC ; G 1885
+U 5293 ; WX 854 ; N uni14AD ; G 1886
+U 5294 ; WX 863 ; N uni14AE ; G 1887
+U 5295 ; WX 874 ; N uni14AF ; G 1888
+U 5296 ; WX 863 ; N uni14B0 ; G 1889
+U 5297 ; WX 874 ; N uni14B1 ; G 1890
+U 5298 ; WX 881 ; N uni14B2 ; G 1891
+U 5299 ; WX 874 ; N uni14B3 ; G 1892
+U 5300 ; WX 881 ; N uni14B4 ; G 1893
+U 5301 ; WX 874 ; N uni14B5 ; G 1894
+U 5302 ; WX 863 ; N uni14B6 ; G 1895
+U 5303 ; WX 874 ; N uni14B7 ; G 1896
+U 5304 ; WX 863 ; N uni14B8 ; G 1897
+U 5305 ; WX 874 ; N uni14B9 ; G 1898
+U 5306 ; WX 863 ; N uni14BA ; G 1899
+U 5307 ; WX 436 ; N uni14BB ; G 1900
+U 5308 ; WX 548 ; N uni14BC ; G 1901
+U 5309 ; WX 436 ; N uni14BD ; G 1902
+U 5312 ; WX 988 ; N uni14C0 ; G 1903
+U 5313 ; WX 988 ; N uni14C1 ; G 1904
+U 5314 ; WX 988 ; N uni14C2 ; G 1905
+U 5315 ; WX 988 ; N uni14C3 ; G 1906
+U 5316 ; WX 931 ; N uni14C4 ; G 1907
+U 5317 ; WX 931 ; N uni14C5 ; G 1908
+U 5318 ; WX 931 ; N uni14C6 ; G 1909
+U 5319 ; WX 931 ; N uni14C7 ; G 1910
+U 5320 ; WX 931 ; N uni14C8 ; G 1911
+U 5321 ; WX 1238 ; N uni14C9 ; G 1912
+U 5322 ; WX 1247 ; N uni14CA ; G 1913
+U 5323 ; WX 1200 ; N uni14CB ; G 1914
+U 5324 ; WX 1228 ; N uni14CC ; G 1915
+U 5325 ; WX 1200 ; N uni14CD ; G 1916
+U 5326 ; WX 1228 ; N uni14CE ; G 1917
+U 5327 ; WX 931 ; N uni14CF ; G 1918
+U 5328 ; WX 660 ; N uni14D0 ; G 1919
+U 5329 ; WX 497 ; N uni14D1 ; G 1920
+U 5330 ; WX 660 ; N uni14D2 ; G 1921
+U 5331 ; WX 988 ; N uni14D3 ; G 1922
+U 5332 ; WX 988 ; N uni14D4 ; G 1923
+U 5333 ; WX 988 ; N uni14D5 ; G 1924
+U 5334 ; WX 988 ; N uni14D6 ; G 1925
+U 5335 ; WX 931 ; N uni14D7 ; G 1926
+U 5336 ; WX 931 ; N uni14D8 ; G 1927
+U 5337 ; WX 931 ; N uni14D9 ; G 1928
+U 5338 ; WX 931 ; N uni14DA ; G 1929
+U 5339 ; WX 931 ; N uni14DB ; G 1930
+U 5340 ; WX 1231 ; N uni14DC ; G 1931
+U 5341 ; WX 1247 ; N uni14DD ; G 1932
+U 5342 ; WX 1283 ; N uni14DE ; G 1933
+U 5343 ; WX 1228 ; N uni14DF ; G 1934
+U 5344 ; WX 1283 ; N uni14E0 ; G 1935
+U 5345 ; WX 1228 ; N uni14E1 ; G 1936
+U 5346 ; WX 1228 ; N uni14E2 ; G 1937
+U 5347 ; WX 1214 ; N uni14E3 ; G 1938
+U 5348 ; WX 1228 ; N uni14E4 ; G 1939
+U 5349 ; WX 1214 ; N uni14E5 ; G 1940
+U 5350 ; WX 1283 ; N uni14E6 ; G 1941
+U 5351 ; WX 1228 ; N uni14E7 ; G 1942
+U 5352 ; WX 1283 ; N uni14E8 ; G 1943
+U 5353 ; WX 1228 ; N uni14E9 ; G 1944
+U 5354 ; WX 660 ; N uni14EA ; G 1945
+U 5356 ; WX 886 ; N uni14EC ; G 1946
+U 5357 ; WX 730 ; N uni14ED ; G 1947
+U 5358 ; WX 730 ; N uni14EE ; G 1948
+U 5359 ; WX 730 ; N uni14EF ; G 1949
+U 5360 ; WX 730 ; N uni14F0 ; G 1950
+U 5361 ; WX 730 ; N uni14F1 ; G 1951
+U 5362 ; WX 730 ; N uni14F2 ; G 1952
+U 5363 ; WX 730 ; N uni14F3 ; G 1953
+U 5364 ; WX 730 ; N uni14F4 ; G 1954
+U 5365 ; WX 730 ; N uni14F5 ; G 1955
+U 5366 ; WX 998 ; N uni14F6 ; G 1956
+U 5367 ; WX 958 ; N uni14F7 ; G 1957
+U 5368 ; WX 967 ; N uni14F8 ; G 1958
+U 5369 ; WX 989 ; N uni14F9 ; G 1959
+U 5370 ; WX 967 ; N uni14FA ; G 1960
+U 5371 ; WX 989 ; N uni14FB ; G 1961
+U 5372 ; WX 998 ; N uni14FC ; G 1962
+U 5373 ; WX 958 ; N uni14FD ; G 1963
+U 5374 ; WX 998 ; N uni14FE ; G 1964
+U 5375 ; WX 958 ; N uni14FF ; G 1965
+U 5376 ; WX 967 ; N uni1500 ; G 1966
+U 5377 ; WX 989 ; N uni1501 ; G 1967
+U 5378 ; WX 967 ; N uni1502 ; G 1968
+U 5379 ; WX 989 ; N uni1503 ; G 1969
+U 5380 ; WX 967 ; N uni1504 ; G 1970
+U 5381 ; WX 493 ; N uni1505 ; G 1971
+U 5382 ; WX 460 ; N uni1506 ; G 1972
+U 5383 ; WX 493 ; N uni1507 ; G 1973
+U 5392 ; WX 923 ; N uni1510 ; G 1974
+U 5393 ; WX 923 ; N uni1511 ; G 1975
+U 5394 ; WX 923 ; N uni1512 ; G 1976
+U 5395 ; WX 1136 ; N uni1513 ; G 1977
+U 5396 ; WX 1136 ; N uni1514 ; G 1978
+U 5397 ; WX 1136 ; N uni1515 ; G 1979
+U 5398 ; WX 1136 ; N uni1516 ; G 1980
+U 5399 ; WX 1209 ; N uni1517 ; G 1981
+U 5400 ; WX 1202 ; N uni1518 ; G 1982
+U 5401 ; WX 1209 ; N uni1519 ; G 1983
+U 5402 ; WX 1202 ; N uni151A ; G 1984
+U 5403 ; WX 1209 ; N uni151B ; G 1985
+U 5404 ; WX 1202 ; N uni151C ; G 1986
+U 5405 ; WX 1431 ; N uni151D ; G 1987
+U 5406 ; WX 1420 ; N uni151E ; G 1988
+U 5407 ; WX 1431 ; N uni151F ; G 1989
+U 5408 ; WX 1420 ; N uni1520 ; G 1990
+U 5409 ; WX 1431 ; N uni1521 ; G 1991
+U 5410 ; WX 1420 ; N uni1522 ; G 1992
+U 5411 ; WX 1431 ; N uni1523 ; G 1993
+U 5412 ; WX 1420 ; N uni1524 ; G 1994
+U 5413 ; WX 746 ; N uni1525 ; G 1995
+U 5414 ; WX 776 ; N uni1526 ; G 1996
+U 5415 ; WX 776 ; N uni1527 ; G 1997
+U 5416 ; WX 776 ; N uni1528 ; G 1998
+U 5417 ; WX 776 ; N uni1529 ; G 1999
+U 5418 ; WX 776 ; N uni152A ; G 2000
+U 5419 ; WX 776 ; N uni152B ; G 2001
+U 5420 ; WX 776 ; N uni152C ; G 2002
+U 5421 ; WX 776 ; N uni152D ; G 2003
+U 5422 ; WX 776 ; N uni152E ; G 2004
+U 5423 ; WX 1003 ; N uni152F ; G 2005
+U 5424 ; WX 1003 ; N uni1530 ; G 2006
+U 5425 ; WX 1013 ; N uni1531 ; G 2007
+U 5426 ; WX 996 ; N uni1532 ; G 2008
+U 5427 ; WX 1013 ; N uni1533 ; G 2009
+U 5428 ; WX 996 ; N uni1534 ; G 2010
+U 5429 ; WX 1003 ; N uni1535 ; G 2011
+U 5430 ; WX 1003 ; N uni1536 ; G 2012
+U 5431 ; WX 1003 ; N uni1537 ; G 2013
+U 5432 ; WX 1003 ; N uni1538 ; G 2014
+U 5433 ; WX 1013 ; N uni1539 ; G 2015
+U 5434 ; WX 996 ; N uni153A ; G 2016
+U 5435 ; WX 1013 ; N uni153B ; G 2017
+U 5436 ; WX 996 ; N uni153C ; G 2018
+U 5437 ; WX 1013 ; N uni153D ; G 2019
+U 5438 ; WX 495 ; N uni153E ; G 2020
+U 5440 ; WX 395 ; N uni1540 ; G 2021
+U 5441 ; WX 510 ; N uni1541 ; G 2022
+U 5442 ; WX 1033 ; N uni1542 ; G 2023
+U 5443 ; WX 1033 ; N uni1543 ; G 2024
+U 5444 ; WX 976 ; N uni1544 ; G 2025
+U 5445 ; WX 976 ; N uni1545 ; G 2026
+U 5446 ; WX 976 ; N uni1546 ; G 2027
+U 5447 ; WX 976 ; N uni1547 ; G 2028
+U 5448 ; WX 733 ; N uni1548 ; G 2029
+U 5449 ; WX 733 ; N uni1549 ; G 2030
+U 5450 ; WX 733 ; N uni154A ; G 2031
+U 5451 ; WX 733 ; N uni154B ; G 2032
+U 5452 ; WX 733 ; N uni154C ; G 2033
+U 5453 ; WX 733 ; N uni154D ; G 2034
+U 5454 ; WX 1003 ; N uni154E ; G 2035
+U 5455 ; WX 959 ; N uni154F ; G 2036
+U 5456 ; WX 495 ; N uni1550 ; G 2037
+U 5458 ; WX 886 ; N uni1552 ; G 2038
+U 5459 ; WX 774 ; N uni1553 ; G 2039
+U 5460 ; WX 774 ; N uni1554 ; G 2040
+U 5461 ; WX 774 ; N uni1555 ; G 2041
+U 5462 ; WX 774 ; N uni1556 ; G 2042
+U 5463 ; WX 928 ; N uni1557 ; G 2043
+U 5464 ; WX 928 ; N uni1558 ; G 2044
+U 5465 ; WX 928 ; N uni1559 ; G 2045
+U 5466 ; WX 928 ; N uni155A ; G 2046
+U 5467 ; WX 1172 ; N uni155B ; G 2047
+U 5468 ; WX 1142 ; N uni155C ; G 2048
+U 5469 ; WX 602 ; N uni155D ; G 2049
+U 5470 ; WX 812 ; N uni155E ; G 2050
+U 5471 ; WX 812 ; N uni155F ; G 2051
+U 5472 ; WX 812 ; N uni1560 ; G 2052
+U 5473 ; WX 812 ; N uni1561 ; G 2053
+U 5474 ; WX 812 ; N uni1562 ; G 2054
+U 5475 ; WX 812 ; N uni1563 ; G 2055
+U 5476 ; WX 815 ; N uni1564 ; G 2056
+U 5477 ; WX 815 ; N uni1565 ; G 2057
+U 5478 ; WX 815 ; N uni1566 ; G 2058
+U 5479 ; WX 815 ; N uni1567 ; G 2059
+U 5480 ; WX 1060 ; N uni1568 ; G 2060
+U 5481 ; WX 1052 ; N uni1569 ; G 2061
+U 5482 ; WX 548 ; N uni156A ; G 2062
+U 5492 ; WX 977 ; N uni1574 ; G 2063
+U 5493 ; WX 977 ; N uni1575 ; G 2064
+U 5494 ; WX 977 ; N uni1576 ; G 2065
+U 5495 ; WX 977 ; N uni1577 ; G 2066
+U 5496 ; WX 977 ; N uni1578 ; G 2067
+U 5497 ; WX 977 ; N uni1579 ; G 2068
+U 5498 ; WX 977 ; N uni157A ; G 2069
+U 5499 ; WX 618 ; N uni157B ; G 2070
+U 5500 ; WX 837 ; N uni157C ; G 2071
+U 5501 ; WX 510 ; N uni157D ; G 2072
+U 5502 ; WX 1238 ; N uni157E ; G 2073
+U 5503 ; WX 1238 ; N uni157F ; G 2074
+U 5504 ; WX 1238 ; N uni1580 ; G 2075
+U 5505 ; WX 1238 ; N uni1581 ; G 2076
+U 5506 ; WX 1238 ; N uni1582 ; G 2077
+U 5507 ; WX 1238 ; N uni1583 ; G 2078
+U 5508 ; WX 1238 ; N uni1584 ; G 2079
+U 5509 ; WX 989 ; N uni1585 ; G 2080
+U 5514 ; WX 977 ; N uni158A ; G 2081
+U 5515 ; WX 977 ; N uni158B ; G 2082
+U 5516 ; WX 977 ; N uni158C ; G 2083
+U 5517 ; WX 977 ; N uni158D ; G 2084
+U 5518 ; WX 1591 ; N uni158E ; G 2085
+U 5519 ; WX 1591 ; N uni158F ; G 2086
+U 5520 ; WX 1591 ; N uni1590 ; G 2087
+U 5521 ; WX 1295 ; N uni1591 ; G 2088
+U 5522 ; WX 1295 ; N uni1592 ; G 2089
+U 5523 ; WX 1591 ; N uni1593 ; G 2090
+U 5524 ; WX 1591 ; N uni1594 ; G 2091
+U 5525 ; WX 848 ; N uni1595 ; G 2092
+U 5526 ; WX 1273 ; N uni1596 ; G 2093
+U 5536 ; WX 988 ; N uni15A0 ; G 2094
+U 5537 ; WX 988 ; N uni15A1 ; G 2095
+U 5538 ; WX 931 ; N uni15A2 ; G 2096
+U 5539 ; WX 931 ; N uni15A3 ; G 2097
+U 5540 ; WX 931 ; N uni15A4 ; G 2098
+U 5541 ; WX 931 ; N uni15A5 ; G 2099
+U 5542 ; WX 660 ; N uni15A6 ; G 2100
+U 5543 ; WX 776 ; N uni15A7 ; G 2101
+U 5544 ; WX 776 ; N uni15A8 ; G 2102
+U 5545 ; WX 776 ; N uni15A9 ; G 2103
+U 5546 ; WX 776 ; N uni15AA ; G 2104
+U 5547 ; WX 776 ; N uni15AB ; G 2105
+U 5548 ; WX 776 ; N uni15AC ; G 2106
+U 5549 ; WX 776 ; N uni15AD ; G 2107
+U 5550 ; WX 495 ; N uni15AE ; G 2108
+U 5551 ; WX 743 ; N uni15AF ; G 2109
+U 5598 ; WX 830 ; N uni15DE ; G 2110
+U 5601 ; WX 830 ; N uni15E1 ; G 2111
+U 5702 ; WX 496 ; N uni1646 ; G 2112
+U 5703 ; WX 496 ; N uni1647 ; G 2113
+U 5742 ; WX 413 ; N uni166E ; G 2114
+U 5743 ; WX 1238 ; N uni166F ; G 2115
+U 5744 ; WX 1591 ; N uni1670 ; G 2116
+U 5745 ; WX 2016 ; N uni1671 ; G 2117
+U 5746 ; WX 2016 ; N uni1672 ; G 2118
+U 5747 ; WX 1720 ; N uni1673 ; G 2119
+U 5748 ; WX 1678 ; N uni1674 ; G 2120
+U 5749 ; WX 2016 ; N uni1675 ; G 2121
+U 5750 ; WX 2016 ; N uni1676 ; G 2122
+U 5760 ; WX 543 ; N uni1680 ; G 2123
+U 5761 ; WX 637 ; N uni1681 ; G 2124
+U 5762 ; WX 945 ; N uni1682 ; G 2125
+U 5763 ; WX 1254 ; N uni1683 ; G 2126
+U 5764 ; WX 1563 ; N uni1684 ; G 2127
+U 5765 ; WX 1871 ; N uni1685 ; G 2128
+U 5766 ; WX 627 ; N uni1686 ; G 2129
+U 5767 ; WX 936 ; N uni1687 ; G 2130
+U 5768 ; WX 1254 ; N uni1688 ; G 2131
+U 5769 ; WX 1559 ; N uni1689 ; G 2132
+U 5770 ; WX 1871 ; N uni168A ; G 2133
+U 5771 ; WX 569 ; N uni168B ; G 2134
+U 5772 ; WX 877 ; N uni168C ; G 2135
+U 5773 ; WX 1187 ; N uni168D ; G 2136
+U 5774 ; WX 1497 ; N uni168E ; G 2137
+U 5775 ; WX 1807 ; N uni168F ; G 2138
+U 5776 ; WX 637 ; N uni1690 ; G 2139
+U 5777 ; WX 945 ; N uni1691 ; G 2140
+U 5778 ; WX 1240 ; N uni1692 ; G 2141
+U 5779 ; WX 1555 ; N uni1693 ; G 2142
+U 5780 ; WX 1871 ; N uni1694 ; G 2143
+U 5781 ; WX 569 ; N uni1695 ; G 2144
+U 5782 ; WX 569 ; N uni1696 ; G 2145
+U 5783 ; WX 789 ; N uni1697 ; G 2146
+U 5784 ; WX 1234 ; N uni1698 ; G 2147
+U 5785 ; WX 1559 ; N uni1699 ; G 2148
+U 5786 ; WX 740 ; N uni169A ; G 2149
+U 5787 ; WX 638 ; N uni169B ; G 2150
+U 5788 ; WX 638 ; N uni169C ; G 2151
+U 7424 ; WX 652 ; N uni1D00 ; G 2152
+U 7425 ; WX 833 ; N uni1D01 ; G 2153
+U 7426 ; WX 1048 ; N uni1D02 ; G 2154
+U 7427 ; WX 608 ; N uni1D03 ; G 2155
+U 7428 ; WX 593 ; N uni1D04 ; G 2156
+U 7429 ; WX 676 ; N uni1D05 ; G 2157
+U 7430 ; WX 676 ; N uni1D06 ; G 2158
+U 7431 ; WX 559 ; N uni1D07 ; G 2159
+U 7432 ; WX 557 ; N uni1D08 ; G 2160
+U 7433 ; WX 343 ; N uni1D09 ; G 2161
+U 7434 ; WX 494 ; N uni1D0A ; G 2162
+U 7435 ; WX 665 ; N uni1D0B ; G 2163
+U 7436 ; WX 539 ; N uni1D0C ; G 2164
+U 7437 ; WX 817 ; N uni1D0D ; G 2165
+U 7438 ; WX 701 ; N uni1D0E ; G 2166
+U 7439 ; WX 687 ; N uni1D0F ; G 2167
+U 7440 ; WX 593 ; N uni1D10 ; G 2168
+U 7441 ; WX 660 ; N uni1D11 ; G 2169
+U 7442 ; WX 660 ; N uni1D12 ; G 2170
+U 7443 ; WX 660 ; N uni1D13 ; G 2171
+U 7444 ; WX 1094 ; N uni1D14 ; G 2172
+U 7446 ; WX 687 ; N uni1D16 ; G 2173
+U 7447 ; WX 687 ; N uni1D17 ; G 2174
+U 7448 ; WX 556 ; N uni1D18 ; G 2175
+U 7449 ; WX 642 ; N uni1D19 ; G 2176
+U 7450 ; WX 642 ; N uni1D1A ; G 2177
+U 7451 ; WX 580 ; N uni1D1B ; G 2178
+U 7452 ; WX 634 ; N uni1D1C ; G 2179
+U 7453 ; WX 737 ; N uni1D1D ; G 2180
+U 7454 ; WX 948 ; N uni1D1E ; G 2181
+U 7455 ; WX 695 ; N uni1D1F ; G 2182
+U 7456 ; WX 652 ; N uni1D20 ; G 2183
+U 7457 ; WX 924 ; N uni1D21 ; G 2184
+U 7458 ; WX 582 ; N uni1D22 ; G 2185
+U 7459 ; WX 646 ; N uni1D23 ; G 2186
+U 7462 ; WX 539 ; N uni1D26 ; G 2187
+U 7463 ; WX 652 ; N uni1D27 ; G 2188
+U 7464 ; WX 691 ; N uni1D28 ; G 2189
+U 7465 ; WX 556 ; N uni1D29 ; G 2190
+U 7466 ; WX 781 ; N uni1D2A ; G 2191
+U 7467 ; WX 732 ; N uni1D2B ; G 2192
+U 7468 ; WX 487 ; N uni1D2C ; G 2193
+U 7469 ; WX 683 ; N uni1D2D ; G 2194
+U 7470 ; WX 480 ; N uni1D2E ; G 2195
+U 7472 ; WX 523 ; N uni1D30 ; G 2196
+U 7473 ; WX 430 ; N uni1D31 ; G 2197
+U 7474 ; WX 430 ; N uni1D32 ; G 2198
+U 7475 ; WX 517 ; N uni1D33 ; G 2199
+U 7476 ; WX 527 ; N uni1D34 ; G 2200
+U 7477 ; WX 234 ; N uni1D35 ; G 2201
+U 7478 ; WX 234 ; N uni1D36 ; G 2202
+U 7479 ; WX 488 ; N uni1D37 ; G 2203
+U 7480 ; WX 401 ; N uni1D38 ; G 2204
+U 7481 ; WX 626 ; N uni1D39 ; G 2205
+U 7482 ; WX 527 ; N uni1D3A ; G 2206
+U 7483 ; WX 527 ; N uni1D3B ; G 2207
+U 7484 ; WX 535 ; N uni1D3C ; G 2208
+U 7485 ; WX 509 ; N uni1D3D ; G 2209
+U 7486 ; WX 461 ; N uni1D3E ; G 2210
+U 7487 ; WX 485 ; N uni1D3F ; G 2211
+U 7488 ; WX 430 ; N uni1D40 ; G 2212
+U 7489 ; WX 511 ; N uni1D41 ; G 2213
+U 7490 ; WX 695 ; N uni1D42 ; G 2214
+U 7491 ; WX 458 ; N uni1D43 ; G 2215
+U 7492 ; WX 458 ; N uni1D44 ; G 2216
+U 7493 ; WX 479 ; N uni1D45 ; G 2217
+U 7494 ; WX 712 ; N uni1D46 ; G 2218
+U 7495 ; WX 479 ; N uni1D47 ; G 2219
+U 7496 ; WX 479 ; N uni1D48 ; G 2220
+U 7497 ; WX 479 ; N uni1D49 ; G 2221
+U 7498 ; WX 479 ; N uni1D4A ; G 2222
+U 7499 ; WX 386 ; N uni1D4B ; G 2223
+U 7500 ; WX 386 ; N uni1D4C ; G 2224
+U 7501 ; WX 479 ; N uni1D4D ; G 2225
+U 7502 ; WX 219 ; N uni1D4E ; G 2226
+U 7503 ; WX 487 ; N uni1D4F ; G 2227
+U 7504 ; WX 664 ; N uni1D50 ; G 2228
+U 7505 ; WX 456 ; N uni1D51 ; G 2229
+U 7506 ; WX 488 ; N uni1D52 ; G 2230
+U 7507 ; WX 414 ; N uni1D53 ; G 2231
+U 7508 ; WX 488 ; N uni1D54 ; G 2232
+U 7509 ; WX 488 ; N uni1D55 ; G 2233
+U 7510 ; WX 479 ; N uni1D56 ; G 2234
+U 7511 ; WX 388 ; N uni1D57 ; G 2235
+U 7512 ; WX 456 ; N uni1D58 ; G 2236
+U 7513 ; WX 462 ; N uni1D59 ; G 2237
+U 7514 ; WX 664 ; N uni1D5A ; G 2238
+U 7515 ; WX 501 ; N uni1D5B ; G 2239
+U 7517 ; WX 451 ; N uni1D5D ; G 2240
+U 7518 ; WX 429 ; N uni1D5E ; G 2241
+U 7519 ; WX 433 ; N uni1D5F ; G 2242
+U 7520 ; WX 493 ; N uni1D60 ; G 2243
+U 7521 ; WX 406 ; N uni1D61 ; G 2244
+U 7522 ; WX 219 ; N uni1D62 ; G 2245
+U 7523 ; WX 315 ; N uni1D63 ; G 2246
+U 7524 ; WX 456 ; N uni1D64 ; G 2247
+U 7525 ; WX 501 ; N uni1D65 ; G 2248
+U 7526 ; WX 451 ; N uni1D66 ; G 2249
+U 7527 ; WX 429 ; N uni1D67 ; G 2250
+U 7528 ; WX 451 ; N uni1D68 ; G 2251
+U 7529 ; WX 493 ; N uni1D69 ; G 2252
+U 7530 ; WX 406 ; N uni1D6A ; G 2253
+U 7543 ; WX 716 ; N uni1D77 ; G 2254
+U 7544 ; WX 527 ; N uni1D78 ; G 2255
+U 7547 ; WX 545 ; N uni1D7B ; G 2256
+U 7549 ; WX 747 ; N uni1D7D ; G 2257
+U 7557 ; WX 514 ; N uni1D85 ; G 2258
+U 7579 ; WX 479 ; N uni1D9B ; G 2259
+U 7580 ; WX 414 ; N uni1D9C ; G 2260
+U 7581 ; WX 414 ; N uni1D9D ; G 2261
+U 7582 ; WX 488 ; N uni1D9E ; G 2262
+U 7583 ; WX 386 ; N uni1D9F ; G 2263
+U 7584 ; WX 377 ; N uni1DA0 ; G 2264
+U 7585 ; WX 348 ; N uni1DA1 ; G 2265
+U 7586 ; WX 479 ; N uni1DA2 ; G 2266
+U 7587 ; WX 456 ; N uni1DA3 ; G 2267
+U 7588 ; WX 347 ; N uni1DA4 ; G 2268
+U 7589 ; WX 281 ; N uni1DA5 ; G 2269
+U 7590 ; WX 347 ; N uni1DA6 ; G 2270
+U 7591 ; WX 347 ; N uni1DA7 ; G 2271
+U 7592 ; WX 431 ; N uni1DA8 ; G 2272
+U 7593 ; WX 326 ; N uni1DA9 ; G 2273
+U 7594 ; WX 330 ; N uni1DAA ; G 2274
+U 7595 ; WX 370 ; N uni1DAB ; G 2275
+U 7596 ; WX 664 ; N uni1DAC ; G 2276
+U 7597 ; WX 664 ; N uni1DAD ; G 2277
+U 7598 ; WX 562 ; N uni1DAE ; G 2278
+U 7599 ; WX 562 ; N uni1DAF ; G 2279
+U 7600 ; WX 448 ; N uni1DB0 ; G 2280
+U 7601 ; WX 488 ; N uni1DB1 ; G 2281
+U 7602 ; WX 542 ; N uni1DB2 ; G 2282
+U 7603 ; WX 422 ; N uni1DB3 ; G 2283
+U 7604 ; WX 396 ; N uni1DB4 ; G 2284
+U 7605 ; WX 388 ; N uni1DB5 ; G 2285
+U 7606 ; WX 583 ; N uni1DB6 ; G 2286
+U 7607 ; WX 494 ; N uni1DB7 ; G 2287
+U 7608 ; WX 399 ; N uni1DB8 ; G 2288
+U 7609 ; WX 451 ; N uni1DB9 ; G 2289
+U 7610 ; WX 501 ; N uni1DBA ; G 2290
+U 7611 ; WX 417 ; N uni1DBB ; G 2291
+U 7612 ; WX 523 ; N uni1DBC ; G 2292
+U 7613 ; WX 470 ; N uni1DBD ; G 2293
+U 7614 ; WX 455 ; N uni1DBE ; G 2294
+U 7615 ; WX 425 ; N uni1DBF ; G 2295
+U 7620 ; WX 0 ; N uni1DC4 ; G 2296
+U 7621 ; WX 0 ; N uni1DC5 ; G 2297
+U 7622 ; WX 0 ; N uni1DC6 ; G 2298
+U 7623 ; WX 0 ; N uni1DC7 ; G 2299
+U 7624 ; WX 0 ; N uni1DC8 ; G 2300
+U 7625 ; WX 0 ; N uni1DC9 ; G 2301
+U 7680 ; WX 774 ; N uni1E00 ; G 2302
+U 7681 ; WX 675 ; N uni1E01 ; G 2303
+U 7682 ; WX 762 ; N uni1E02 ; G 2304
+U 7683 ; WX 716 ; N uni1E03 ; G 2305
+U 7684 ; WX 762 ; N uni1E04 ; G 2306
+U 7685 ; WX 716 ; N uni1E05 ; G 2307
+U 7686 ; WX 762 ; N uni1E06 ; G 2308
+U 7687 ; WX 716 ; N uni1E07 ; G 2309
+U 7688 ; WX 734 ; N uni1E08 ; G 2310
+U 7689 ; WX 593 ; N uni1E09 ; G 2311
+U 7690 ; WX 830 ; N uni1E0A ; G 2312
+U 7691 ; WX 716 ; N uni1E0B ; G 2313
+U 7692 ; WX 830 ; N uni1E0C ; G 2314
+U 7693 ; WX 716 ; N uni1E0D ; G 2315
+U 7694 ; WX 830 ; N uni1E0E ; G 2316
+U 7695 ; WX 716 ; N uni1E0F ; G 2317
+U 7696 ; WX 830 ; N uni1E10 ; G 2318
+U 7697 ; WX 716 ; N uni1E11 ; G 2319
+U 7698 ; WX 830 ; N uni1E12 ; G 2320
+U 7699 ; WX 716 ; N uni1E13 ; G 2321
+U 7700 ; WX 683 ; N uni1E14 ; G 2322
+U 7701 ; WX 678 ; N uni1E15 ; G 2323
+U 7702 ; WX 683 ; N uni1E16 ; G 2324
+U 7703 ; WX 678 ; N uni1E17 ; G 2325
+U 7704 ; WX 683 ; N uni1E18 ; G 2326
+U 7705 ; WX 678 ; N uni1E19 ; G 2327
+U 7706 ; WX 683 ; N uni1E1A ; G 2328
+U 7707 ; WX 678 ; N uni1E1B ; G 2329
+U 7708 ; WX 683 ; N uni1E1C ; G 2330
+U 7709 ; WX 678 ; N uni1E1D ; G 2331
+U 7710 ; WX 683 ; N uni1E1E ; G 2332
+U 7711 ; WX 435 ; N uni1E1F ; G 2333
+U 7712 ; WX 821 ; N uni1E20 ; G 2334
+U 7713 ; WX 716 ; N uni1E21 ; G 2335
+U 7714 ; WX 837 ; N uni1E22 ; G 2336
+U 7715 ; WX 712 ; N uni1E23 ; G 2337
+U 7716 ; WX 837 ; N uni1E24 ; G 2338
+U 7717 ; WX 712 ; N uni1E25 ; G 2339
+U 7718 ; WX 837 ; N uni1E26 ; G 2340
+U 7719 ; WX 712 ; N uni1E27 ; G 2341
+U 7720 ; WX 837 ; N uni1E28 ; G 2342
+U 7721 ; WX 712 ; N uni1E29 ; G 2343
+U 7722 ; WX 837 ; N uni1E2A ; G 2344
+U 7723 ; WX 712 ; N uni1E2B ; G 2345
+U 7724 ; WX 372 ; N uni1E2C ; G 2346
+U 7725 ; WX 343 ; N uni1E2D ; G 2347
+U 7726 ; WX 372 ; N uni1E2E ; G 2348
+U 7727 ; WX 343 ; N uni1E2F ; G 2349
+U 7728 ; WX 775 ; N uni1E30 ; G 2350
+U 7729 ; WX 665 ; N uni1E31 ; G 2351
+U 7730 ; WX 775 ; N uni1E32 ; G 2352
+U 7731 ; WX 665 ; N uni1E33 ; G 2353
+U 7732 ; WX 775 ; N uni1E34 ; G 2354
+U 7733 ; WX 665 ; N uni1E35 ; G 2355
+U 7734 ; WX 637 ; N uni1E36 ; G 2356
+U 7735 ; WX 343 ; N uni1E37 ; G 2357
+U 7736 ; WX 637 ; N uni1E38 ; G 2358
+U 7737 ; WX 343 ; N uni1E39 ; G 2359
+U 7738 ; WX 637 ; N uni1E3A ; G 2360
+U 7739 ; WX 343 ; N uni1E3B ; G 2361
+U 7740 ; WX 637 ; N uni1E3C ; G 2362
+U 7741 ; WX 343 ; N uni1E3D ; G 2363
+U 7742 ; WX 995 ; N uni1E3E ; G 2364
+U 7743 ; WX 1042 ; N uni1E3F ; G 2365
+U 7744 ; WX 995 ; N uni1E40 ; G 2366
+U 7745 ; WX 1042 ; N uni1E41 ; G 2367
+U 7746 ; WX 995 ; N uni1E42 ; G 2368
+U 7747 ; WX 1042 ; N uni1E43 ; G 2369
+U 7748 ; WX 837 ; N uni1E44 ; G 2370
+U 7749 ; WX 712 ; N uni1E45 ; G 2371
+U 7750 ; WX 837 ; N uni1E46 ; G 2372
+U 7751 ; WX 712 ; N uni1E47 ; G 2373
+U 7752 ; WX 837 ; N uni1E48 ; G 2374
+U 7753 ; WX 712 ; N uni1E49 ; G 2375
+U 7754 ; WX 837 ; N uni1E4A ; G 2376
+U 7755 ; WX 712 ; N uni1E4B ; G 2377
+U 7756 ; WX 850 ; N uni1E4C ; G 2378
+U 7757 ; WX 687 ; N uni1E4D ; G 2379
+U 7758 ; WX 850 ; N uni1E4E ; G 2380
+U 7759 ; WX 687 ; N uni1E4F ; G 2381
+U 7760 ; WX 850 ; N uni1E50 ; G 2382
+U 7761 ; WX 687 ; N uni1E51 ; G 2383
+U 7762 ; WX 850 ; N uni1E52 ; G 2384
+U 7763 ; WX 687 ; N uni1E53 ; G 2385
+U 7764 ; WX 733 ; N uni1E54 ; G 2386
+U 7765 ; WX 716 ; N uni1E55 ; G 2387
+U 7766 ; WX 733 ; N uni1E56 ; G 2388
+U 7767 ; WX 716 ; N uni1E57 ; G 2389
+U 7768 ; WX 770 ; N uni1E58 ; G 2390
+U 7769 ; WX 493 ; N uni1E59 ; G 2391
+U 7770 ; WX 770 ; N uni1E5A ; G 2392
+U 7771 ; WX 493 ; N uni1E5B ; G 2393
+U 7772 ; WX 770 ; N uni1E5C ; G 2394
+U 7773 ; WX 493 ; N uni1E5D ; G 2395
+U 7774 ; WX 770 ; N uni1E5E ; G 2396
+U 7775 ; WX 493 ; N uni1E5F ; G 2397
+U 7776 ; WX 720 ; N uni1E60 ; G 2398
+U 7777 ; WX 595 ; N uni1E61 ; G 2399
+U 7778 ; WX 720 ; N uni1E62 ; G 2400
+U 7779 ; WX 595 ; N uni1E63 ; G 2401
+U 7780 ; WX 720 ; N uni1E64 ; G 2402
+U 7781 ; WX 595 ; N uni1E65 ; G 2403
+U 7782 ; WX 720 ; N uni1E66 ; G 2404
+U 7783 ; WX 595 ; N uni1E67 ; G 2405
+U 7784 ; WX 720 ; N uni1E68 ; G 2406
+U 7785 ; WX 595 ; N uni1E69 ; G 2407
+U 7786 ; WX 682 ; N uni1E6A ; G 2408
+U 7787 ; WX 478 ; N uni1E6B ; G 2409
+U 7788 ; WX 682 ; N uni1E6C ; G 2410
+U 7789 ; WX 478 ; N uni1E6D ; G 2411
+U 7790 ; WX 682 ; N uni1E6E ; G 2412
+U 7791 ; WX 478 ; N uni1E6F ; G 2413
+U 7792 ; WX 682 ; N uni1E70 ; G 2414
+U 7793 ; WX 478 ; N uni1E71 ; G 2415
+U 7794 ; WX 812 ; N uni1E72 ; G 2416
+U 7795 ; WX 712 ; N uni1E73 ; G 2417
+U 7796 ; WX 812 ; N uni1E74 ; G 2418
+U 7797 ; WX 712 ; N uni1E75 ; G 2419
+U 7798 ; WX 812 ; N uni1E76 ; G 2420
+U 7799 ; WX 712 ; N uni1E77 ; G 2421
+U 7800 ; WX 812 ; N uni1E78 ; G 2422
+U 7801 ; WX 712 ; N uni1E79 ; G 2423
+U 7802 ; WX 812 ; N uni1E7A ; G 2424
+U 7803 ; WX 712 ; N uni1E7B ; G 2425
+U 7804 ; WX 774 ; N uni1E7C ; G 2426
+U 7805 ; WX 652 ; N uni1E7D ; G 2427
+U 7806 ; WX 774 ; N uni1E7E ; G 2428
+U 7807 ; WX 652 ; N uni1E7F ; G 2429
+U 7808 ; WX 1103 ; N Wgrave ; G 2430
+U 7809 ; WX 924 ; N wgrave ; G 2431
+U 7810 ; WX 1103 ; N Wacute ; G 2432
+U 7811 ; WX 924 ; N wacute ; G 2433
+U 7812 ; WX 1103 ; N Wdieresis ; G 2434
+U 7813 ; WX 924 ; N wdieresis ; G 2435
+U 7814 ; WX 1103 ; N uni1E86 ; G 2436
+U 7815 ; WX 924 ; N uni1E87 ; G 2437
+U 7816 ; WX 1103 ; N uni1E88 ; G 2438
+U 7817 ; WX 924 ; N uni1E89 ; G 2439
+U 7818 ; WX 771 ; N uni1E8A ; G 2440
+U 7819 ; WX 645 ; N uni1E8B ; G 2441
+U 7820 ; WX 771 ; N uni1E8C ; G 2442
+U 7821 ; WX 645 ; N uni1E8D ; G 2443
+U 7822 ; WX 724 ; N uni1E8E ; G 2444
+U 7823 ; WX 652 ; N uni1E8F ; G 2445
+U 7824 ; WX 725 ; N uni1E90 ; G 2446
+U 7825 ; WX 582 ; N uni1E91 ; G 2447
+U 7826 ; WX 725 ; N uni1E92 ; G 2448
+U 7827 ; WX 582 ; N uni1E93 ; G 2449
+U 7828 ; WX 725 ; N uni1E94 ; G 2450
+U 7829 ; WX 582 ; N uni1E95 ; G 2451
+U 7830 ; WX 712 ; N uni1E96 ; G 2452
+U 7831 ; WX 478 ; N uni1E97 ; G 2453
+U 7832 ; WX 924 ; N uni1E98 ; G 2454
+U 7833 ; WX 652 ; N uni1E99 ; G 2455
+U 7834 ; WX 675 ; N uni1E9A ; G 2456
+U 7835 ; WX 435 ; N uni1E9B ; G 2457
+U 7836 ; WX 435 ; N uni1E9C ; G 2458
+U 7837 ; WX 435 ; N uni1E9D ; G 2459
+U 7838 ; WX 896 ; N uni1E9E ; G 2460
+U 7839 ; WX 687 ; N uni1E9F ; G 2461
+U 7840 ; WX 774 ; N uni1EA0 ; G 2462
+U 7841 ; WX 675 ; N uni1EA1 ; G 2463
+U 7842 ; WX 774 ; N uni1EA2 ; G 2464
+U 7843 ; WX 675 ; N uni1EA3 ; G 2465
+U 7844 ; WX 774 ; N uni1EA4 ; G 2466
+U 7845 ; WX 675 ; N uni1EA5 ; G 2467
+U 7846 ; WX 774 ; N uni1EA6 ; G 2468
+U 7847 ; WX 675 ; N uni1EA7 ; G 2469
+U 7848 ; WX 774 ; N uni1EA8 ; G 2470
+U 7849 ; WX 675 ; N uni1EA9 ; G 2471
+U 7850 ; WX 774 ; N uni1EAA ; G 2472
+U 7851 ; WX 675 ; N uni1EAB ; G 2473
+U 7852 ; WX 774 ; N uni1EAC ; G 2474
+U 7853 ; WX 675 ; N uni1EAD ; G 2475
+U 7854 ; WX 774 ; N uni1EAE ; G 2476
+U 7855 ; WX 675 ; N uni1EAF ; G 2477
+U 7856 ; WX 774 ; N uni1EB0 ; G 2478
+U 7857 ; WX 675 ; N uni1EB1 ; G 2479
+U 7858 ; WX 774 ; N uni1EB2 ; G 2480
+U 7859 ; WX 675 ; N uni1EB3 ; G 2481
+U 7860 ; WX 774 ; N uni1EB4 ; G 2482
+U 7861 ; WX 675 ; N uni1EB5 ; G 2483
+U 7862 ; WX 774 ; N uni1EB6 ; G 2484
+U 7863 ; WX 675 ; N uni1EB7 ; G 2485
+U 7864 ; WX 683 ; N uni1EB8 ; G 2486
+U 7865 ; WX 678 ; N uni1EB9 ; G 2487
+U 7866 ; WX 683 ; N uni1EBA ; G 2488
+U 7867 ; WX 678 ; N uni1EBB ; G 2489
+U 7868 ; WX 683 ; N uni1EBC ; G 2490
+U 7869 ; WX 678 ; N uni1EBD ; G 2491
+U 7870 ; WX 683 ; N uni1EBE ; G 2492
+U 7871 ; WX 678 ; N uni1EBF ; G 2493
+U 7872 ; WX 683 ; N uni1EC0 ; G 2494
+U 7873 ; WX 678 ; N uni1EC1 ; G 2495
+U 7874 ; WX 683 ; N uni1EC2 ; G 2496
+U 7875 ; WX 678 ; N uni1EC3 ; G 2497
+U 7876 ; WX 683 ; N uni1EC4 ; G 2498
+U 7877 ; WX 678 ; N uni1EC5 ; G 2499
+U 7878 ; WX 683 ; N uni1EC6 ; G 2500
+U 7879 ; WX 678 ; N uni1EC7 ; G 2501
+U 7880 ; WX 372 ; N uni1EC8 ; G 2502
+U 7881 ; WX 343 ; N uni1EC9 ; G 2503
+U 7882 ; WX 372 ; N uni1ECA ; G 2504
+U 7883 ; WX 343 ; N uni1ECB ; G 2505
+U 7884 ; WX 850 ; N uni1ECC ; G 2506
+U 7885 ; WX 687 ; N uni1ECD ; G 2507
+U 7886 ; WX 850 ; N uni1ECE ; G 2508
+U 7887 ; WX 687 ; N uni1ECF ; G 2509
+U 7888 ; WX 850 ; N uni1ED0 ; G 2510
+U 7889 ; WX 687 ; N uni1ED1 ; G 2511
+U 7890 ; WX 850 ; N uni1ED2 ; G 2512
+U 7891 ; WX 687 ; N uni1ED3 ; G 2513
+U 7892 ; WX 850 ; N uni1ED4 ; G 2514
+U 7893 ; WX 687 ; N uni1ED5 ; G 2515
+U 7894 ; WX 850 ; N uni1ED6 ; G 2516
+U 7895 ; WX 687 ; N uni1ED7 ; G 2517
+U 7896 ; WX 850 ; N uni1ED8 ; G 2518
+U 7897 ; WX 687 ; N uni1ED9 ; G 2519
+U 7898 ; WX 874 ; N uni1EDA ; G 2520
+U 7899 ; WX 687 ; N uni1EDB ; G 2521
+U 7900 ; WX 874 ; N uni1EDC ; G 2522
+U 7901 ; WX 687 ; N uni1EDD ; G 2523
+U 7902 ; WX 874 ; N uni1EDE ; G 2524
+U 7903 ; WX 687 ; N uni1EDF ; G 2525
+U 7904 ; WX 874 ; N uni1EE0 ; G 2526
+U 7905 ; WX 687 ; N uni1EE1 ; G 2527
+U 7906 ; WX 874 ; N uni1EE2 ; G 2528
+U 7907 ; WX 687 ; N uni1EE3 ; G 2529
+U 7908 ; WX 812 ; N uni1EE4 ; G 2530
+U 7909 ; WX 712 ; N uni1EE5 ; G 2531
+U 7910 ; WX 812 ; N uni1EE6 ; G 2532
+U 7911 ; WX 712 ; N uni1EE7 ; G 2533
+U 7912 ; WX 835 ; N uni1EE8 ; G 2534
+U 7913 ; WX 712 ; N uni1EE9 ; G 2535
+U 7914 ; WX 835 ; N uni1EEA ; G 2536
+U 7915 ; WX 712 ; N uni1EEB ; G 2537
+U 7916 ; WX 835 ; N uni1EEC ; G 2538
+U 7917 ; WX 712 ; N uni1EED ; G 2539
+U 7918 ; WX 835 ; N uni1EEE ; G 2540
+U 7919 ; WX 712 ; N uni1EEF ; G 2541
+U 7920 ; WX 835 ; N uni1EF0 ; G 2542
+U 7921 ; WX 712 ; N uni1EF1 ; G 2543
+U 7922 ; WX 724 ; N Ygrave ; G 2544
+U 7923 ; WX 652 ; N ygrave ; G 2545
+U 7924 ; WX 724 ; N uni1EF4 ; G 2546
+U 7925 ; WX 652 ; N uni1EF5 ; G 2547
+U 7926 ; WX 724 ; N uni1EF6 ; G 2548
+U 7927 ; WX 652 ; N uni1EF7 ; G 2549
+U 7928 ; WX 724 ; N uni1EF8 ; G 2550
+U 7929 ; WX 652 ; N uni1EF9 ; G 2551
+U 7930 ; WX 953 ; N uni1EFA ; G 2552
+U 7931 ; WX 644 ; N uni1EFB ; G 2553
+U 7936 ; WX 687 ; N uni1F00 ; G 2554
+U 7937 ; WX 687 ; N uni1F01 ; G 2555
+U 7938 ; WX 687 ; N uni1F02 ; G 2556
+U 7939 ; WX 687 ; N uni1F03 ; G 2557
+U 7940 ; WX 687 ; N uni1F04 ; G 2558
+U 7941 ; WX 687 ; N uni1F05 ; G 2559
+U 7942 ; WX 687 ; N uni1F06 ; G 2560
+U 7943 ; WX 687 ; N uni1F07 ; G 2561
+U 7944 ; WX 774 ; N uni1F08 ; G 2562
+U 7945 ; WX 774 ; N uni1F09 ; G 2563
+U 7946 ; WX 1041 ; N uni1F0A ; G 2564
+U 7947 ; WX 1043 ; N uni1F0B ; G 2565
+U 7948 ; WX 935 ; N uni1F0C ; G 2566
+U 7949 ; WX 963 ; N uni1F0D ; G 2567
+U 7950 ; WX 835 ; N uni1F0E ; G 2568
+U 7951 ; WX 859 ; N uni1F0F ; G 2569
+U 7952 ; WX 557 ; N uni1F10 ; G 2570
+U 7953 ; WX 557 ; N uni1F11 ; G 2571
+U 7954 ; WX 557 ; N uni1F12 ; G 2572
+U 7955 ; WX 557 ; N uni1F13 ; G 2573
+U 7956 ; WX 557 ; N uni1F14 ; G 2574
+U 7957 ; WX 557 ; N uni1F15 ; G 2575
+U 7960 ; WX 792 ; N uni1F18 ; G 2576
+U 7961 ; WX 794 ; N uni1F19 ; G 2577
+U 7962 ; WX 1100 ; N uni1F1A ; G 2578
+U 7963 ; WX 1096 ; N uni1F1B ; G 2579
+U 7964 ; WX 1023 ; N uni1F1C ; G 2580
+U 7965 ; WX 1052 ; N uni1F1D ; G 2581
+U 7968 ; WX 712 ; N uni1F20 ; G 2582
+U 7969 ; WX 712 ; N uni1F21 ; G 2583
+U 7970 ; WX 712 ; N uni1F22 ; G 2584
+U 7971 ; WX 712 ; N uni1F23 ; G 2585
+U 7972 ; WX 712 ; N uni1F24 ; G 2586
+U 7973 ; WX 712 ; N uni1F25 ; G 2587
+U 7974 ; WX 712 ; N uni1F26 ; G 2588
+U 7975 ; WX 712 ; N uni1F27 ; G 2589
+U 7976 ; WX 945 ; N uni1F28 ; G 2590
+U 7977 ; WX 951 ; N uni1F29 ; G 2591
+U 7978 ; WX 1250 ; N uni1F2A ; G 2592
+U 7979 ; WX 1250 ; N uni1F2B ; G 2593
+U 7980 ; WX 1180 ; N uni1F2C ; G 2594
+U 7981 ; WX 1206 ; N uni1F2D ; G 2595
+U 7982 ; WX 1054 ; N uni1F2E ; G 2596
+U 7983 ; WX 1063 ; N uni1F2F ; G 2597
+U 7984 ; WX 390 ; N uni1F30 ; G 2598
+U 7985 ; WX 390 ; N uni1F31 ; G 2599
+U 7986 ; WX 390 ; N uni1F32 ; G 2600
+U 7987 ; WX 390 ; N uni1F33 ; G 2601
+U 7988 ; WX 390 ; N uni1F34 ; G 2602
+U 7989 ; WX 390 ; N uni1F35 ; G 2603
+U 7990 ; WX 390 ; N uni1F36 ; G 2604
+U 7991 ; WX 390 ; N uni1F37 ; G 2605
+U 7992 ; WX 483 ; N uni1F38 ; G 2606
+U 7993 ; WX 489 ; N uni1F39 ; G 2607
+U 7994 ; WX 777 ; N uni1F3A ; G 2608
+U 7995 ; WX 785 ; N uni1F3B ; G 2609
+U 7996 ; WX 712 ; N uni1F3C ; G 2610
+U 7997 ; WX 738 ; N uni1F3D ; G 2611
+U 7998 ; WX 604 ; N uni1F3E ; G 2612
+U 7999 ; WX 604 ; N uni1F3F ; G 2613
+U 8000 ; WX 687 ; N uni1F40 ; G 2614
+U 8001 ; WX 687 ; N uni1F41 ; G 2615
+U 8002 ; WX 687 ; N uni1F42 ; G 2616
+U 8003 ; WX 687 ; N uni1F43 ; G 2617
+U 8004 ; WX 687 ; N uni1F44 ; G 2618
+U 8005 ; WX 687 ; N uni1F45 ; G 2619
+U 8008 ; WX 892 ; N uni1F48 ; G 2620
+U 8009 ; WX 933 ; N uni1F49 ; G 2621
+U 8010 ; WX 1221 ; N uni1F4A ; G 2622
+U 8011 ; WX 1224 ; N uni1F4B ; G 2623
+U 8012 ; WX 1053 ; N uni1F4C ; G 2624
+U 8013 ; WX 1082 ; N uni1F4D ; G 2625
+U 8016 ; WX 675 ; N uni1F50 ; G 2626
+U 8017 ; WX 675 ; N uni1F51 ; G 2627
+U 8018 ; WX 675 ; N uni1F52 ; G 2628
+U 8019 ; WX 675 ; N uni1F53 ; G 2629
+U 8020 ; WX 675 ; N uni1F54 ; G 2630
+U 8021 ; WX 675 ; N uni1F55 ; G 2631
+U 8022 ; WX 675 ; N uni1F56 ; G 2632
+U 8023 ; WX 675 ; N uni1F57 ; G 2633
+U 8025 ; WX 930 ; N uni1F59 ; G 2634
+U 8027 ; WX 1184 ; N uni1F5B ; G 2635
+U 8029 ; WX 1199 ; N uni1F5D ; G 2636
+U 8031 ; WX 1049 ; N uni1F5F ; G 2637
+U 8032 ; WX 869 ; N uni1F60 ; G 2638
+U 8033 ; WX 869 ; N uni1F61 ; G 2639
+U 8034 ; WX 869 ; N uni1F62 ; G 2640
+U 8035 ; WX 869 ; N uni1F63 ; G 2641
+U 8036 ; WX 869 ; N uni1F64 ; G 2642
+U 8037 ; WX 869 ; N uni1F65 ; G 2643
+U 8038 ; WX 869 ; N uni1F66 ; G 2644
+U 8039 ; WX 869 ; N uni1F67 ; G 2645
+U 8040 ; WX 909 ; N uni1F68 ; G 2646
+U 8041 ; WX 958 ; N uni1F69 ; G 2647
+U 8042 ; WX 1246 ; N uni1F6A ; G 2648
+U 8043 ; WX 1251 ; N uni1F6B ; G 2649
+U 8044 ; WX 1076 ; N uni1F6C ; G 2650
+U 8045 ; WX 1105 ; N uni1F6D ; G 2651
+U 8046 ; WX 1028 ; N uni1F6E ; G 2652
+U 8047 ; WX 1076 ; N uni1F6F ; G 2653
+U 8048 ; WX 687 ; N uni1F70 ; G 2654
+U 8049 ; WX 687 ; N uni1F71 ; G 2655
+U 8050 ; WX 557 ; N uni1F72 ; G 2656
+U 8051 ; WX 557 ; N uni1F73 ; G 2657
+U 8052 ; WX 712 ; N uni1F74 ; G 2658
+U 8053 ; WX 712 ; N uni1F75 ; G 2659
+U 8054 ; WX 390 ; N uni1F76 ; G 2660
+U 8055 ; WX 390 ; N uni1F77 ; G 2661
+U 8056 ; WX 687 ; N uni1F78 ; G 2662
+U 8057 ; WX 687 ; N uni1F79 ; G 2663
+U 8058 ; WX 675 ; N uni1F7A ; G 2664
+U 8059 ; WX 675 ; N uni1F7B ; G 2665
+U 8060 ; WX 869 ; N uni1F7C ; G 2666
+U 8061 ; WX 869 ; N uni1F7D ; G 2667
+U 8064 ; WX 687 ; N uni1F80 ; G 2668
+U 8065 ; WX 687 ; N uni1F81 ; G 2669
+U 8066 ; WX 687 ; N uni1F82 ; G 2670
+U 8067 ; WX 687 ; N uni1F83 ; G 2671
+U 8068 ; WX 687 ; N uni1F84 ; G 2672
+U 8069 ; WX 687 ; N uni1F85 ; G 2673
+U 8070 ; WX 687 ; N uni1F86 ; G 2674
+U 8071 ; WX 687 ; N uni1F87 ; G 2675
+U 8072 ; WX 774 ; N uni1F88 ; G 2676
+U 8073 ; WX 774 ; N uni1F89 ; G 2677
+U 8074 ; WX 1041 ; N uni1F8A ; G 2678
+U 8075 ; WX 1043 ; N uni1F8B ; G 2679
+U 8076 ; WX 935 ; N uni1F8C ; G 2680
+U 8077 ; WX 963 ; N uni1F8D ; G 2681
+U 8078 ; WX 835 ; N uni1F8E ; G 2682
+U 8079 ; WX 859 ; N uni1F8F ; G 2683
+U 8080 ; WX 712 ; N uni1F90 ; G 2684
+U 8081 ; WX 712 ; N uni1F91 ; G 2685
+U 8082 ; WX 712 ; N uni1F92 ; G 2686
+U 8083 ; WX 712 ; N uni1F93 ; G 2687
+U 8084 ; WX 712 ; N uni1F94 ; G 2688
+U 8085 ; WX 712 ; N uni1F95 ; G 2689
+U 8086 ; WX 712 ; N uni1F96 ; G 2690
+U 8087 ; WX 712 ; N uni1F97 ; G 2691
+U 8088 ; WX 945 ; N uni1F98 ; G 2692
+U 8089 ; WX 951 ; N uni1F99 ; G 2693
+U 8090 ; WX 1250 ; N uni1F9A ; G 2694
+U 8091 ; WX 1250 ; N uni1F9B ; G 2695
+U 8092 ; WX 1180 ; N uni1F9C ; G 2696
+U 8093 ; WX 1206 ; N uni1F9D ; G 2697
+U 8094 ; WX 1054 ; N uni1F9E ; G 2698
+U 8095 ; WX 1063 ; N uni1F9F ; G 2699
+U 8096 ; WX 869 ; N uni1FA0 ; G 2700
+U 8097 ; WX 869 ; N uni1FA1 ; G 2701
+U 8098 ; WX 869 ; N uni1FA2 ; G 2702
+U 8099 ; WX 869 ; N uni1FA3 ; G 2703
+U 8100 ; WX 869 ; N uni1FA4 ; G 2704
+U 8101 ; WX 869 ; N uni1FA5 ; G 2705
+U 8102 ; WX 869 ; N uni1FA6 ; G 2706
+U 8103 ; WX 869 ; N uni1FA7 ; G 2707
+U 8104 ; WX 909 ; N uni1FA8 ; G 2708
+U 8105 ; WX 958 ; N uni1FA9 ; G 2709
+U 8106 ; WX 1246 ; N uni1FAA ; G 2710
+U 8107 ; WX 1251 ; N uni1FAB ; G 2711
+U 8108 ; WX 1076 ; N uni1FAC ; G 2712
+U 8109 ; WX 1105 ; N uni1FAD ; G 2713
+U 8110 ; WX 1028 ; N uni1FAE ; G 2714
+U 8111 ; WX 1076 ; N uni1FAF ; G 2715
+U 8112 ; WX 687 ; N uni1FB0 ; G 2716
+U 8113 ; WX 687 ; N uni1FB1 ; G 2717
+U 8114 ; WX 687 ; N uni1FB2 ; G 2718
+U 8115 ; WX 687 ; N uni1FB3 ; G 2719
+U 8116 ; WX 687 ; N uni1FB4 ; G 2720
+U 8118 ; WX 687 ; N uni1FB6 ; G 2721
+U 8119 ; WX 687 ; N uni1FB7 ; G 2722
+U 8120 ; WX 774 ; N uni1FB8 ; G 2723
+U 8121 ; WX 774 ; N uni1FB9 ; G 2724
+U 8122 ; WX 876 ; N uni1FBA ; G 2725
+U 8123 ; WX 797 ; N uni1FBB ; G 2726
+U 8124 ; WX 774 ; N uni1FBC ; G 2727
+U 8125 ; WX 500 ; N uni1FBD ; G 2728
+U 8126 ; WX 500 ; N uni1FBE ; G 2729
+U 8127 ; WX 500 ; N uni1FBF ; G 2730
+U 8128 ; WX 500 ; N uni1FC0 ; G 2731
+U 8129 ; WX 500 ; N uni1FC1 ; G 2732
+U 8130 ; WX 712 ; N uni1FC2 ; G 2733
+U 8131 ; WX 712 ; N uni1FC3 ; G 2734
+U 8132 ; WX 712 ; N uni1FC4 ; G 2735
+U 8134 ; WX 712 ; N uni1FC6 ; G 2736
+U 8135 ; WX 712 ; N uni1FC7 ; G 2737
+U 8136 ; WX 929 ; N uni1FC8 ; G 2738
+U 8137 ; WX 846 ; N uni1FC9 ; G 2739
+U 8138 ; WX 1080 ; N uni1FCA ; G 2740
+U 8139 ; WX 1009 ; N uni1FCB ; G 2741
+U 8140 ; WX 837 ; N uni1FCC ; G 2742
+U 8141 ; WX 500 ; N uni1FCD ; G 2743
+U 8142 ; WX 500 ; N uni1FCE ; G 2744
+U 8143 ; WX 500 ; N uni1FCF ; G 2745
+U 8144 ; WX 390 ; N uni1FD0 ; G 2746
+U 8145 ; WX 390 ; N uni1FD1 ; G 2747
+U 8146 ; WX 390 ; N uni1FD2 ; G 2748
+U 8147 ; WX 390 ; N uni1FD3 ; G 2749
+U 8150 ; WX 390 ; N uni1FD6 ; G 2750
+U 8151 ; WX 390 ; N uni1FD7 ; G 2751
+U 8152 ; WX 372 ; N uni1FD8 ; G 2752
+U 8153 ; WX 372 ; N uni1FD9 ; G 2753
+U 8154 ; WX 621 ; N uni1FDA ; G 2754
+U 8155 ; WX 563 ; N uni1FDB ; G 2755
+U 8157 ; WX 500 ; N uni1FDD ; G 2756
+U 8158 ; WX 500 ; N uni1FDE ; G 2757
+U 8159 ; WX 500 ; N uni1FDF ; G 2758
+U 8160 ; WX 675 ; N uni1FE0 ; G 2759
+U 8161 ; WX 675 ; N uni1FE1 ; G 2760
+U 8162 ; WX 675 ; N uni1FE2 ; G 2761
+U 8163 ; WX 675 ; N uni1FE3 ; G 2762
+U 8164 ; WX 716 ; N uni1FE4 ; G 2763
+U 8165 ; WX 716 ; N uni1FE5 ; G 2764
+U 8166 ; WX 675 ; N uni1FE6 ; G 2765
+U 8167 ; WX 675 ; N uni1FE7 ; G 2766
+U 8168 ; WX 724 ; N uni1FE8 ; G 2767
+U 8169 ; WX 724 ; N uni1FE9 ; G 2768
+U 8170 ; WX 1020 ; N uni1FEA ; G 2769
+U 8171 ; WX 980 ; N uni1FEB ; G 2770
+U 8172 ; WX 838 ; N uni1FEC ; G 2771
+U 8173 ; WX 500 ; N uni1FED ; G 2772
+U 8174 ; WX 500 ; N uni1FEE ; G 2773
+U 8175 ; WX 500 ; N uni1FEF ; G 2774
+U 8178 ; WX 869 ; N uni1FF2 ; G 2775
+U 8179 ; WX 869 ; N uni1FF3 ; G 2776
+U 8180 ; WX 869 ; N uni1FF4 ; G 2777
+U 8182 ; WX 869 ; N uni1FF6 ; G 2778
+U 8183 ; WX 869 ; N uni1FF7 ; G 2779
+U 8184 ; WX 1065 ; N uni1FF8 ; G 2780
+U 8185 ; WX 891 ; N uni1FF9 ; G 2781
+U 8186 ; WX 1084 ; N uni1FFA ; G 2782
+U 8187 ; WX 894 ; N uni1FFB ; G 2783
+U 8188 ; WX 850 ; N uni1FFC ; G 2784
+U 8189 ; WX 500 ; N uni1FFD ; G 2785
+U 8190 ; WX 500 ; N uni1FFE ; G 2786
+U 8192 ; WX 500 ; N uni2000 ; G 2787
+U 8193 ; WX 1000 ; N uni2001 ; G 2788
+U 8194 ; WX 500 ; N uni2002 ; G 2789
+U 8195 ; WX 1000 ; N uni2003 ; G 2790
+U 8196 ; WX 330 ; N uni2004 ; G 2791
+U 8197 ; WX 250 ; N uni2005 ; G 2792
+U 8198 ; WX 167 ; N uni2006 ; G 2793
+U 8199 ; WX 696 ; N uni2007 ; G 2794
+U 8200 ; WX 380 ; N uni2008 ; G 2795
+U 8201 ; WX 200 ; N uni2009 ; G 2796
+U 8202 ; WX 100 ; N uni200A ; G 2797
+U 8203 ; WX 0 ; N uni200B ; G 2798
+U 8204 ; WX 0 ; N uni200C ; G 2799
+U 8205 ; WX 0 ; N uni200D ; G 2800
+U 8206 ; WX 0 ; N uni200E ; G 2801
+U 8207 ; WX 0 ; N uni200F ; G 2802
+U 8208 ; WX 415 ; N uni2010 ; G 2803
+U 8209 ; WX 415 ; N uni2011 ; G 2804
+U 8210 ; WX 696 ; N figuredash ; G 2805
+U 8211 ; WX 500 ; N endash ; G 2806
+U 8212 ; WX 1000 ; N emdash ; G 2807
+U 8213 ; WX 1000 ; N uni2015 ; G 2808
+U 8214 ; WX 500 ; N uni2016 ; G 2809
+U 8215 ; WX 500 ; N underscoredbl ; G 2810
+U 8216 ; WX 380 ; N quoteleft ; G 2811
+U 8217 ; WX 380 ; N quoteright ; G 2812
+U 8218 ; WX 380 ; N quotesinglbase ; G 2813
+U 8219 ; WX 380 ; N quotereversed ; G 2814
+U 8220 ; WX 657 ; N quotedblleft ; G 2815
+U 8221 ; WX 657 ; N quotedblright ; G 2816
+U 8222 ; WX 657 ; N quotedblbase ; G 2817
+U 8223 ; WX 657 ; N uni201F ; G 2818
+U 8224 ; WX 500 ; N dagger ; G 2819
+U 8225 ; WX 500 ; N daggerdbl ; G 2820
+U 8226 ; WX 639 ; N bullet ; G 2821
+U 8227 ; WX 639 ; N uni2023 ; G 2822
+U 8228 ; WX 333 ; N onedotenleader ; G 2823
+U 8229 ; WX 667 ; N twodotenleader ; G 2824
+U 8230 ; WX 1000 ; N ellipsis ; G 2825
+U 8231 ; WX 348 ; N uni2027 ; G 2826
+U 8232 ; WX 0 ; N uni2028 ; G 2827
+U 8233 ; WX 0 ; N uni2029 ; G 2828
+U 8234 ; WX 0 ; N uni202A ; G 2829
+U 8235 ; WX 0 ; N uni202B ; G 2830
+U 8236 ; WX 0 ; N uni202C ; G 2831
+U 8237 ; WX 0 ; N uni202D ; G 2832
+U 8238 ; WX 0 ; N uni202E ; G 2833
+U 8239 ; WX 200 ; N uni202F ; G 2834
+U 8240 ; WX 1440 ; N perthousand ; G 2835
+U 8241 ; WX 1887 ; N uni2031 ; G 2836
+U 8242 ; WX 264 ; N minute ; G 2837
+U 8243 ; WX 447 ; N second ; G 2838
+U 8244 ; WX 630 ; N uni2034 ; G 2839
+U 8245 ; WX 264 ; N uni2035 ; G 2840
+U 8246 ; WX 447 ; N uni2036 ; G 2841
+U 8247 ; WX 630 ; N uni2037 ; G 2842
+U 8248 ; WX 733 ; N uni2038 ; G 2843
+U 8249 ; WX 412 ; N guilsinglleft ; G 2844
+U 8250 ; WX 412 ; N guilsinglright ; G 2845
+U 8251 ; WX 972 ; N uni203B ; G 2846
+U 8252 ; WX 627 ; N exclamdbl ; G 2847
+U 8253 ; WX 580 ; N uni203D ; G 2848
+U 8254 ; WX 500 ; N uni203E ; G 2849
+U 8255 ; WX 828 ; N uni203F ; G 2850
+U 8256 ; WX 828 ; N uni2040 ; G 2851
+U 8257 ; WX 329 ; N uni2041 ; G 2852
+U 8258 ; WX 1023 ; N uni2042 ; G 2853
+U 8259 ; WX 500 ; N uni2043 ; G 2854
+U 8260 ; WX 167 ; N fraction ; G 2855
+U 8261 ; WX 457 ; N uni2045 ; G 2856
+U 8262 ; WX 457 ; N uni2046 ; G 2857
+U 8263 ; WX 1030 ; N uni2047 ; G 2858
+U 8264 ; WX 829 ; N uni2048 ; G 2859
+U 8265 ; WX 829 ; N uni2049 ; G 2860
+U 8266 ; WX 513 ; N uni204A ; G 2861
+U 8267 ; WX 636 ; N uni204B ; G 2862
+U 8268 ; WX 500 ; N uni204C ; G 2863
+U 8269 ; WX 500 ; N uni204D ; G 2864
+U 8270 ; WX 523 ; N uni204E ; G 2865
+U 8271 ; WX 400 ; N uni204F ; G 2866
+U 8272 ; WX 828 ; N uni2050 ; G 2867
+U 8273 ; WX 523 ; N uni2051 ; G 2868
+U 8274 ; WX 556 ; N uni2052 ; G 2869
+U 8275 ; WX 1000 ; N uni2053 ; G 2870
+U 8276 ; WX 828 ; N uni2054 ; G 2871
+U 8277 ; WX 838 ; N uni2055 ; G 2872
+U 8278 ; WX 684 ; N uni2056 ; G 2873
+U 8279 ; WX 813 ; N uni2057 ; G 2874
+U 8280 ; WX 838 ; N uni2058 ; G 2875
+U 8281 ; WX 838 ; N uni2059 ; G 2876
+U 8282 ; WX 380 ; N uni205A ; G 2877
+U 8283 ; WX 872 ; N uni205B ; G 2878
+U 8284 ; WX 838 ; N uni205C ; G 2879
+U 8285 ; WX 380 ; N uni205D ; G 2880
+U 8286 ; WX 380 ; N uni205E ; G 2881
+U 8287 ; WX 222 ; N uni205F ; G 2882
+U 8288 ; WX 0 ; N uni2060 ; G 2883
+U 8289 ; WX 0 ; N uni2061 ; G 2884
+U 8290 ; WX 0 ; N uni2062 ; G 2885
+U 8291 ; WX 0 ; N uni2063 ; G 2886
+U 8292 ; WX 0 ; N uni2064 ; G 2887
+U 8298 ; WX 0 ; N uni206A ; G 2888
+U 8299 ; WX 0 ; N uni206B ; G 2889
+U 8300 ; WX 0 ; N uni206C ; G 2890
+U 8301 ; WX 0 ; N uni206D ; G 2891
+U 8302 ; WX 0 ; N uni206E ; G 2892
+U 8303 ; WX 0 ; N uni206F ; G 2893
+U 8304 ; WX 438 ; N uni2070 ; G 2894
+U 8305 ; WX 219 ; N uni2071 ; G 2895
+U 8308 ; WX 438 ; N uni2074 ; G 2896
+U 8309 ; WX 438 ; N uni2075 ; G 2897
+U 8310 ; WX 438 ; N uni2076 ; G 2898
+U 8311 ; WX 438 ; N uni2077 ; G 2899
+U 8312 ; WX 438 ; N uni2078 ; G 2900
+U 8313 ; WX 438 ; N uni2079 ; G 2901
+U 8314 ; WX 528 ; N uni207A ; G 2902
+U 8315 ; WX 528 ; N uni207B ; G 2903
+U 8316 ; WX 528 ; N uni207C ; G 2904
+U 8317 ; WX 288 ; N uni207D ; G 2905
+U 8318 ; WX 288 ; N uni207E ; G 2906
+U 8319 ; WX 456 ; N uni207F ; G 2907
+U 8320 ; WX 438 ; N uni2080 ; G 2908
+U 8321 ; WX 438 ; N uni2081 ; G 2909
+U 8322 ; WX 438 ; N uni2082 ; G 2910
+U 8323 ; WX 438 ; N uni2083 ; G 2911
+U 8324 ; WX 438 ; N uni2084 ; G 2912
+U 8325 ; WX 438 ; N uni2085 ; G 2913
+U 8326 ; WX 438 ; N uni2086 ; G 2914
+U 8327 ; WX 438 ; N uni2087 ; G 2915
+U 8328 ; WX 438 ; N uni2088 ; G 2916
+U 8329 ; WX 438 ; N uni2089 ; G 2917
+U 8330 ; WX 528 ; N uni208A ; G 2918
+U 8331 ; WX 528 ; N uni208B ; G 2919
+U 8332 ; WX 528 ; N uni208C ; G 2920
+U 8333 ; WX 288 ; N uni208D ; G 2921
+U 8334 ; WX 288 ; N uni208E ; G 2922
+U 8336 ; WX 458 ; N uni2090 ; G 2923
+U 8337 ; WX 479 ; N uni2091 ; G 2924
+U 8338 ; WX 488 ; N uni2092 ; G 2925
+U 8339 ; WX 413 ; N uni2093 ; G 2926
+U 8340 ; WX 479 ; N uni2094 ; G 2927
+U 8341 ; WX 456 ; N uni2095 ; G 2928
+U 8342 ; WX 487 ; N uni2096 ; G 2929
+U 8343 ; WX 219 ; N uni2097 ; G 2930
+U 8344 ; WX 664 ; N uni2098 ; G 2931
+U 8345 ; WX 456 ; N uni2099 ; G 2932
+U 8346 ; WX 479 ; N uni209A ; G 2933
+U 8347 ; WX 381 ; N uni209B ; G 2934
+U 8348 ; WX 388 ; N uni209C ; G 2935
+U 8352 ; WX 929 ; N uni20A0 ; G 2936
+U 8353 ; WX 696 ; N colonmonetary ; G 2937
+U 8354 ; WX 696 ; N uni20A2 ; G 2938
+U 8355 ; WX 696 ; N franc ; G 2939
+U 8356 ; WX 696 ; N lira ; G 2940
+U 8357 ; WX 1042 ; N uni20A5 ; G 2941
+U 8358 ; WX 696 ; N uni20A6 ; G 2942
+U 8359 ; WX 1518 ; N peseta ; G 2943
+U 8360 ; WX 1205 ; N uni20A8 ; G 2944
+U 8361 ; WX 1103 ; N uni20A9 ; G 2945
+U 8362 ; WX 904 ; N uni20AA ; G 2946
+U 8363 ; WX 696 ; N dong ; G 2947
+U 8364 ; WX 696 ; N Euro ; G 2948
+U 8365 ; WX 696 ; N uni20AD ; G 2949
+U 8366 ; WX 696 ; N uni20AE ; G 2950
+U 8367 ; WX 1392 ; N uni20AF ; G 2951
+U 8368 ; WX 696 ; N uni20B0 ; G 2952
+U 8369 ; WX 696 ; N uni20B1 ; G 2953
+U 8370 ; WX 696 ; N uni20B2 ; G 2954
+U 8371 ; WX 696 ; N uni20B3 ; G 2955
+U 8372 ; WX 859 ; N uni20B4 ; G 2956
+U 8373 ; WX 696 ; N uni20B5 ; G 2957
+U 8376 ; WX 696 ; N uni20B8 ; G 2958
+U 8377 ; WX 696 ; N uni20B9 ; G 2959
+U 8378 ; WX 696 ; N uni20BA ; G 2960
+U 8381 ; WX 696 ; N uni20BD ; G 2961
+U 8400 ; WX 0 ; N uni20D0 ; G 2962
+U 8401 ; WX 0 ; N uni20D1 ; G 2963
+U 8406 ; WX 0 ; N uni20D6 ; G 2964
+U 8407 ; WX 0 ; N uni20D7 ; G 2965
+U 8411 ; WX 0 ; N uni20DB ; G 2966
+U 8412 ; WX 0 ; N uni20DC ; G 2967
+U 8417 ; WX 0 ; N uni20E1 ; G 2968
+U 8448 ; WX 1120 ; N uni2100 ; G 2969
+U 8449 ; WX 1170 ; N uni2101 ; G 2970
+U 8450 ; WX 734 ; N uni2102 ; G 2971
+U 8451 ; WX 1211 ; N uni2103 ; G 2972
+U 8452 ; WX 896 ; N uni2104 ; G 2973
+U 8453 ; WX 1091 ; N uni2105 ; G 2974
+U 8454 ; WX 1144 ; N uni2106 ; G 2975
+U 8455 ; WX 614 ; N uni2107 ; G 2976
+U 8456 ; WX 698 ; N uni2108 ; G 2977
+U 8457 ; WX 1086 ; N uni2109 ; G 2978
+U 8459 ; WX 1073 ; N uni210B ; G 2979
+U 8460 ; WX 913 ; N uni210C ; G 2980
+U 8461 ; WX 888 ; N uni210D ; G 2981
+U 8462 ; WX 712 ; N uni210E ; G 2982
+U 8463 ; WX 712 ; N uni210F ; G 2983
+U 8464 ; WX 597 ; N uni2110 ; G 2984
+U 8465 ; WX 697 ; N Ifraktur ; G 2985
+U 8466 ; WX 856 ; N uni2112 ; G 2986
+U 8467 ; WX 472 ; N uni2113 ; G 2987
+U 8468 ; WX 974 ; N uni2114 ; G 2988
+U 8469 ; WX 837 ; N uni2115 ; G 2989
+U 8470 ; WX 1203 ; N uni2116 ; G 2990
+U 8471 ; WX 1000 ; N uni2117 ; G 2991
+U 8472 ; WX 697 ; N weierstrass ; G 2992
+U 8473 ; WX 750 ; N uni2119 ; G 2993
+U 8474 ; WX 850 ; N uni211A ; G 2994
+U 8475 ; WX 938 ; N uni211B ; G 2995
+U 8476 ; WX 814 ; N Rfraktur ; G 2996
+U 8477 ; WX 801 ; N uni211D ; G 2997
+U 8478 ; WX 896 ; N prescription ; G 2998
+U 8479 ; WX 710 ; N uni211F ; G 2999
+U 8480 ; WX 1020 ; N uni2120 ; G 3000
+U 8481 ; WX 1281 ; N uni2121 ; G 3001
+U 8482 ; WX 1000 ; N trademark ; G 3002
+U 8483 ; WX 755 ; N uni2123 ; G 3003
+U 8484 ; WX 754 ; N uni2124 ; G 3004
+U 8485 ; WX 578 ; N uni2125 ; G 3005
+U 8486 ; WX 850 ; N uni2126 ; G 3006
+U 8487 ; WX 850 ; N uni2127 ; G 3007
+U 8488 ; WX 763 ; N uni2128 ; G 3008
+U 8489 ; WX 338 ; N uni2129 ; G 3009
+U 8490 ; WX 775 ; N uni212A ; G 3010
+U 8491 ; WX 774 ; N uni212B ; G 3011
+U 8492 ; WX 928 ; N uni212C ; G 3012
+U 8493 ; WX 818 ; N uni212D ; G 3013
+U 8494 ; WX 854 ; N estimated ; G 3014
+U 8495 ; WX 636 ; N uni212F ; G 3015
+U 8496 ; WX 729 ; N uni2130 ; G 3016
+U 8497 ; WX 808 ; N uni2131 ; G 3017
+U 8498 ; WX 683 ; N uni2132 ; G 3018
+U 8499 ; WX 1184 ; N uni2133 ; G 3019
+U 8500 ; WX 465 ; N uni2134 ; G 3020
+U 8501 ; WX 794 ; N aleph ; G 3021
+U 8502 ; WX 731 ; N uni2136 ; G 3022
+U 8503 ; WX 494 ; N uni2137 ; G 3023
+U 8504 ; WX 684 ; N uni2138 ; G 3024
+U 8505 ; WX 380 ; N uni2139 ; G 3025
+U 8506 ; WX 945 ; N uni213A ; G 3026
+U 8507 ; WX 1348 ; N uni213B ; G 3027
+U 8508 ; WX 790 ; N uni213C ; G 3028
+U 8509 ; WX 737 ; N uni213D ; G 3029
+U 8510 ; WX 654 ; N uni213E ; G 3030
+U 8511 ; WX 863 ; N uni213F ; G 3031
+U 8512 ; WX 840 ; N uni2140 ; G 3032
+U 8513 ; WX 775 ; N uni2141 ; G 3033
+U 8514 ; WX 557 ; N uni2142 ; G 3034
+U 8515 ; WX 637 ; N uni2143 ; G 3035
+U 8516 ; WX 760 ; N uni2144 ; G 3036
+U 8517 ; WX 830 ; N uni2145 ; G 3037
+U 8518 ; WX 716 ; N uni2146 ; G 3038
+U 8519 ; WX 678 ; N uni2147 ; G 3039
+U 8520 ; WX 343 ; N uni2148 ; G 3040
+U 8521 ; WX 343 ; N uni2149 ; G 3041
+U 8523 ; WX 872 ; N uni214B ; G 3042
+U 8526 ; WX 547 ; N uni214E ; G 3043
+U 8528 ; WX 1035 ; N uni2150 ; G 3044
+U 8529 ; WX 1035 ; N uni2151 ; G 3045
+U 8530 ; WX 1483 ; N uni2152 ; G 3046
+U 8531 ; WX 1035 ; N onethird ; G 3047
+U 8532 ; WX 1035 ; N twothirds ; G 3048
+U 8533 ; WX 1035 ; N uni2155 ; G 3049
+U 8534 ; WX 1035 ; N uni2156 ; G 3050
+U 8535 ; WX 1035 ; N uni2157 ; G 3051
+U 8536 ; WX 1035 ; N uni2158 ; G 3052
+U 8537 ; WX 1035 ; N uni2159 ; G 3053
+U 8538 ; WX 1035 ; N uni215A ; G 3054
+U 8539 ; WX 1035 ; N oneeighth ; G 3055
+U 8540 ; WX 1035 ; N threeeighths ; G 3056
+U 8541 ; WX 1035 ; N fiveeighths ; G 3057
+U 8542 ; WX 1035 ; N seveneighths ; G 3058
+U 8543 ; WX 615 ; N uni215F ; G 3059
+U 8544 ; WX 372 ; N uni2160 ; G 3060
+U 8545 ; WX 659 ; N uni2161 ; G 3061
+U 8546 ; WX 945 ; N uni2162 ; G 3062
+U 8547 ; WX 1099 ; N uni2163 ; G 3063
+U 8548 ; WX 774 ; N uni2164 ; G 3064
+U 8549 ; WX 1099 ; N uni2165 ; G 3065
+U 8550 ; WX 1386 ; N uni2166 ; G 3066
+U 8551 ; WX 1672 ; N uni2167 ; G 3067
+U 8552 ; WX 1121 ; N uni2168 ; G 3068
+U 8553 ; WX 771 ; N uni2169 ; G 3069
+U 8554 ; WX 1120 ; N uni216A ; G 3070
+U 8555 ; WX 1407 ; N uni216B ; G 3071
+U 8556 ; WX 637 ; N uni216C ; G 3072
+U 8557 ; WX 734 ; N uni216D ; G 3073
+U 8558 ; WX 830 ; N uni216E ; G 3074
+U 8559 ; WX 995 ; N uni216F ; G 3075
+U 8560 ; WX 343 ; N uni2170 ; G 3076
+U 8561 ; WX 607 ; N uni2171 ; G 3077
+U 8562 ; WX 872 ; N uni2172 ; G 3078
+U 8563 ; WX 984 ; N uni2173 ; G 3079
+U 8564 ; WX 652 ; N uni2174 ; G 3080
+U 8565 ; WX 962 ; N uni2175 ; G 3081
+U 8566 ; WX 1227 ; N uni2176 ; G 3082
+U 8567 ; WX 1491 ; N uni2177 ; G 3083
+U 8568 ; WX 969 ; N uni2178 ; G 3084
+U 8569 ; WX 645 ; N uni2179 ; G 3085
+U 8570 ; WX 969 ; N uni217A ; G 3086
+U 8571 ; WX 1233 ; N uni217B ; G 3087
+U 8572 ; WX 343 ; N uni217C ; G 3088
+U 8573 ; WX 593 ; N uni217D ; G 3089
+U 8574 ; WX 716 ; N uni217E ; G 3090
+U 8575 ; WX 1042 ; N uni217F ; G 3091
+U 8576 ; WX 1289 ; N uni2180 ; G 3092
+U 8577 ; WX 830 ; N uni2181 ; G 3093
+U 8578 ; WX 1289 ; N uni2182 ; G 3094
+U 8579 ; WX 734 ; N uni2183 ; G 3095
+U 8580 ; WX 593 ; N uni2184 ; G 3096
+U 8581 ; WX 734 ; N uni2185 ; G 3097
+U 8585 ; WX 1035 ; N uni2189 ; G 3098
+U 8592 ; WX 838 ; N arrowleft ; G 3099
+U 8593 ; WX 838 ; N arrowup ; G 3100
+U 8594 ; WX 838 ; N arrowright ; G 3101
+U 8595 ; WX 838 ; N arrowdown ; G 3102
+U 8596 ; WX 838 ; N arrowboth ; G 3103
+U 8597 ; WX 838 ; N arrowupdn ; G 3104
+U 8598 ; WX 838 ; N uni2196 ; G 3105
+U 8599 ; WX 838 ; N uni2197 ; G 3106
+U 8600 ; WX 838 ; N uni2198 ; G 3107
+U 8601 ; WX 838 ; N uni2199 ; G 3108
+U 8602 ; WX 838 ; N uni219A ; G 3109
+U 8603 ; WX 838 ; N uni219B ; G 3110
+U 8604 ; WX 838 ; N uni219C ; G 3111
+U 8605 ; WX 838 ; N uni219D ; G 3112
+U 8606 ; WX 838 ; N uni219E ; G 3113
+U 8607 ; WX 838 ; N uni219F ; G 3114
+U 8608 ; WX 838 ; N uni21A0 ; G 3115
+U 8609 ; WX 838 ; N uni21A1 ; G 3116
+U 8610 ; WX 838 ; N uni21A2 ; G 3117
+U 8611 ; WX 838 ; N uni21A3 ; G 3118
+U 8612 ; WX 838 ; N uni21A4 ; G 3119
+U 8613 ; WX 838 ; N uni21A5 ; G 3120
+U 8614 ; WX 838 ; N uni21A6 ; G 3121
+U 8615 ; WX 838 ; N uni21A7 ; G 3122
+U 8616 ; WX 838 ; N arrowupdnbse ; G 3123
+U 8617 ; WX 838 ; N uni21A9 ; G 3124
+U 8618 ; WX 838 ; N uni21AA ; G 3125
+U 8619 ; WX 838 ; N uni21AB ; G 3126
+U 8620 ; WX 838 ; N uni21AC ; G 3127
+U 8621 ; WX 838 ; N uni21AD ; G 3128
+U 8622 ; WX 838 ; N uni21AE ; G 3129
+U 8623 ; WX 838 ; N uni21AF ; G 3130
+U 8624 ; WX 838 ; N uni21B0 ; G 3131
+U 8625 ; WX 838 ; N uni21B1 ; G 3132
+U 8626 ; WX 838 ; N uni21B2 ; G 3133
+U 8627 ; WX 838 ; N uni21B3 ; G 3134
+U 8628 ; WX 838 ; N uni21B4 ; G 3135
+U 8629 ; WX 838 ; N carriagereturn ; G 3136
+U 8630 ; WX 838 ; N uni21B6 ; G 3137
+U 8631 ; WX 838 ; N uni21B7 ; G 3138
+U 8632 ; WX 838 ; N uni21B8 ; G 3139
+U 8633 ; WX 838 ; N uni21B9 ; G 3140
+U 8634 ; WX 838 ; N uni21BA ; G 3141
+U 8635 ; WX 838 ; N uni21BB ; G 3142
+U 8636 ; WX 838 ; N uni21BC ; G 3143
+U 8637 ; WX 838 ; N uni21BD ; G 3144
+U 8638 ; WX 838 ; N uni21BE ; G 3145
+U 8639 ; WX 838 ; N uni21BF ; G 3146
+U 8640 ; WX 838 ; N uni21C0 ; G 3147
+U 8641 ; WX 838 ; N uni21C1 ; G 3148
+U 8642 ; WX 838 ; N uni21C2 ; G 3149
+U 8643 ; WX 838 ; N uni21C3 ; G 3150
+U 8644 ; WX 838 ; N uni21C4 ; G 3151
+U 8645 ; WX 838 ; N uni21C5 ; G 3152
+U 8646 ; WX 838 ; N uni21C6 ; G 3153
+U 8647 ; WX 838 ; N uni21C7 ; G 3154
+U 8648 ; WX 838 ; N uni21C8 ; G 3155
+U 8649 ; WX 838 ; N uni21C9 ; G 3156
+U 8650 ; WX 838 ; N uni21CA ; G 3157
+U 8651 ; WX 838 ; N uni21CB ; G 3158
+U 8652 ; WX 838 ; N uni21CC ; G 3159
+U 8653 ; WX 838 ; N uni21CD ; G 3160
+U 8654 ; WX 838 ; N uni21CE ; G 3161
+U 8655 ; WX 838 ; N uni21CF ; G 3162
+U 8656 ; WX 838 ; N arrowdblleft ; G 3163
+U 8657 ; WX 838 ; N arrowdblup ; G 3164
+U 8658 ; WX 838 ; N arrowdblright ; G 3165
+U 8659 ; WX 838 ; N arrowdbldown ; G 3166
+U 8660 ; WX 838 ; N arrowdblboth ; G 3167
+U 8661 ; WX 838 ; N uni21D5 ; G 3168
+U 8662 ; WX 838 ; N uni21D6 ; G 3169
+U 8663 ; WX 838 ; N uni21D7 ; G 3170
+U 8664 ; WX 838 ; N uni21D8 ; G 3171
+U 8665 ; WX 838 ; N uni21D9 ; G 3172
+U 8666 ; WX 838 ; N uni21DA ; G 3173
+U 8667 ; WX 838 ; N uni21DB ; G 3174
+U 8668 ; WX 838 ; N uni21DC ; G 3175
+U 8669 ; WX 838 ; N uni21DD ; G 3176
+U 8670 ; WX 838 ; N uni21DE ; G 3177
+U 8671 ; WX 838 ; N uni21DF ; G 3178
+U 8672 ; WX 838 ; N uni21E0 ; G 3179
+U 8673 ; WX 838 ; N uni21E1 ; G 3180
+U 8674 ; WX 838 ; N uni21E2 ; G 3181
+U 8675 ; WX 838 ; N uni21E3 ; G 3182
+U 8676 ; WX 838 ; N uni21E4 ; G 3183
+U 8677 ; WX 838 ; N uni21E5 ; G 3184
+U 8678 ; WX 838 ; N uni21E6 ; G 3185
+U 8679 ; WX 838 ; N uni21E7 ; G 3186
+U 8680 ; WX 838 ; N uni21E8 ; G 3187
+U 8681 ; WX 838 ; N uni21E9 ; G 3188
+U 8682 ; WX 838 ; N uni21EA ; G 3189
+U 8683 ; WX 838 ; N uni21EB ; G 3190
+U 8684 ; WX 838 ; N uni21EC ; G 3191
+U 8685 ; WX 838 ; N uni21ED ; G 3192
+U 8686 ; WX 838 ; N uni21EE ; G 3193
+U 8687 ; WX 838 ; N uni21EF ; G 3194
+U 8688 ; WX 838 ; N uni21F0 ; G 3195
+U 8689 ; WX 838 ; N uni21F1 ; G 3196
+U 8690 ; WX 838 ; N uni21F2 ; G 3197
+U 8691 ; WX 838 ; N uni21F3 ; G 3198
+U 8692 ; WX 838 ; N uni21F4 ; G 3199
+U 8693 ; WX 838 ; N uni21F5 ; G 3200
+U 8694 ; WX 838 ; N uni21F6 ; G 3201
+U 8695 ; WX 838 ; N uni21F7 ; G 3202
+U 8696 ; WX 838 ; N uni21F8 ; G 3203
+U 8697 ; WX 838 ; N uni21F9 ; G 3204
+U 8698 ; WX 838 ; N uni21FA ; G 3205
+U 8699 ; WX 838 ; N uni21FB ; G 3206
+U 8700 ; WX 838 ; N uni21FC ; G 3207
+U 8701 ; WX 838 ; N uni21FD ; G 3208
+U 8702 ; WX 838 ; N uni21FE ; G 3209
+U 8703 ; WX 838 ; N uni21FF ; G 3210
+U 8704 ; WX 774 ; N universal ; G 3211
+U 8705 ; WX 696 ; N uni2201 ; G 3212
+U 8706 ; WX 544 ; N partialdiff ; G 3213
+U 8707 ; WX 683 ; N existential ; G 3214
+U 8708 ; WX 683 ; N uni2204 ; G 3215
+U 8709 ; WX 856 ; N emptyset ; G 3216
+U 8710 ; WX 697 ; N increment ; G 3217
+U 8711 ; WX 697 ; N gradient ; G 3218
+U 8712 ; WX 896 ; N element ; G 3219
+U 8713 ; WX 896 ; N notelement ; G 3220
+U 8714 ; WX 750 ; N uni220A ; G 3221
+U 8715 ; WX 896 ; N suchthat ; G 3222
+U 8716 ; WX 896 ; N uni220C ; G 3223
+U 8717 ; WX 750 ; N uni220D ; G 3224
+U 8718 ; WX 636 ; N uni220E ; G 3225
+U 8719 ; WX 787 ; N product ; G 3226
+U 8720 ; WX 787 ; N uni2210 ; G 3227
+U 8721 ; WX 718 ; N summation ; G 3228
+U 8722 ; WX 838 ; N minus ; G 3229
+U 8723 ; WX 838 ; N uni2213 ; G 3230
+U 8724 ; WX 696 ; N uni2214 ; G 3231
+U 8725 ; WX 365 ; N uni2215 ; G 3232
+U 8726 ; WX 696 ; N uni2216 ; G 3233
+U 8727 ; WX 838 ; N asteriskmath ; G 3234
+U 8728 ; WX 626 ; N uni2218 ; G 3235
+U 8729 ; WX 380 ; N uni2219 ; G 3236
+U 8730 ; WX 667 ; N radical ; G 3237
+U 8731 ; WX 667 ; N uni221B ; G 3238
+U 8732 ; WX 667 ; N uni221C ; G 3239
+U 8733 ; WX 712 ; N proportional ; G 3240
+U 8734 ; WX 833 ; N infinity ; G 3241
+U 8735 ; WX 838 ; N orthogonal ; G 3242
+U 8736 ; WX 896 ; N angle ; G 3243
+U 8737 ; WX 896 ; N uni2221 ; G 3244
+U 8738 ; WX 838 ; N uni2222 ; G 3245
+U 8739 ; WX 500 ; N uni2223 ; G 3246
+U 8740 ; WX 500 ; N uni2224 ; G 3247
+U 8741 ; WX 500 ; N uni2225 ; G 3248
+U 8742 ; WX 500 ; N uni2226 ; G 3249
+U 8743 ; WX 812 ; N logicaland ; G 3250
+U 8744 ; WX 812 ; N logicalor ; G 3251
+U 8745 ; WX 812 ; N intersection ; G 3252
+U 8746 ; WX 812 ; N union ; G 3253
+U 8747 ; WX 610 ; N integral ; G 3254
+U 8748 ; WX 929 ; N uni222C ; G 3255
+U 8749 ; WX 1295 ; N uni222D ; G 3256
+U 8750 ; WX 563 ; N uni222E ; G 3257
+U 8751 ; WX 977 ; N uni222F ; G 3258
+U 8752 ; WX 1313 ; N uni2230 ; G 3259
+U 8753 ; WX 563 ; N uni2231 ; G 3260
+U 8754 ; WX 563 ; N uni2232 ; G 3261
+U 8755 ; WX 563 ; N uni2233 ; G 3262
+U 8756 ; WX 696 ; N therefore ; G 3263
+U 8757 ; WX 696 ; N uni2235 ; G 3264
+U 8758 ; WX 294 ; N uni2236 ; G 3265
+U 8759 ; WX 696 ; N uni2237 ; G 3266
+U 8760 ; WX 838 ; N uni2238 ; G 3267
+U 8761 ; WX 838 ; N uni2239 ; G 3268
+U 8762 ; WX 838 ; N uni223A ; G 3269
+U 8763 ; WX 838 ; N uni223B ; G 3270
+U 8764 ; WX 838 ; N similar ; G 3271
+U 8765 ; WX 838 ; N uni223D ; G 3272
+U 8766 ; WX 838 ; N uni223E ; G 3273
+U 8767 ; WX 838 ; N uni223F ; G 3274
+U 8768 ; WX 375 ; N uni2240 ; G 3275
+U 8769 ; WX 838 ; N uni2241 ; G 3276
+U 8770 ; WX 838 ; N uni2242 ; G 3277
+U 8771 ; WX 838 ; N uni2243 ; G 3278
+U 8772 ; WX 838 ; N uni2244 ; G 3279
+U 8773 ; WX 838 ; N congruent ; G 3280
+U 8774 ; WX 838 ; N uni2246 ; G 3281
+U 8775 ; WX 838 ; N uni2247 ; G 3282
+U 8776 ; WX 838 ; N approxequal ; G 3283
+U 8777 ; WX 838 ; N uni2249 ; G 3284
+U 8778 ; WX 838 ; N uni224A ; G 3285
+U 8779 ; WX 838 ; N uni224B ; G 3286
+U 8780 ; WX 838 ; N uni224C ; G 3287
+U 8781 ; WX 838 ; N uni224D ; G 3288
+U 8782 ; WX 838 ; N uni224E ; G 3289
+U 8783 ; WX 838 ; N uni224F ; G 3290
+U 8784 ; WX 838 ; N uni2250 ; G 3291
+U 8785 ; WX 838 ; N uni2251 ; G 3292
+U 8786 ; WX 838 ; N uni2252 ; G 3293
+U 8787 ; WX 838 ; N uni2253 ; G 3294
+U 8788 ; WX 1063 ; N uni2254 ; G 3295
+U 8789 ; WX 1063 ; N uni2255 ; G 3296
+U 8790 ; WX 838 ; N uni2256 ; G 3297
+U 8791 ; WX 838 ; N uni2257 ; G 3298
+U 8792 ; WX 838 ; N uni2258 ; G 3299
+U 8793 ; WX 838 ; N uni2259 ; G 3300
+U 8794 ; WX 838 ; N uni225A ; G 3301
+U 8795 ; WX 838 ; N uni225B ; G 3302
+U 8796 ; WX 838 ; N uni225C ; G 3303
+U 8797 ; WX 838 ; N uni225D ; G 3304
+U 8798 ; WX 838 ; N uni225E ; G 3305
+U 8799 ; WX 838 ; N uni225F ; G 3306
+U 8800 ; WX 838 ; N notequal ; G 3307
+U 8801 ; WX 838 ; N equivalence ; G 3308
+U 8802 ; WX 838 ; N uni2262 ; G 3309
+U 8803 ; WX 838 ; N uni2263 ; G 3310
+U 8804 ; WX 838 ; N lessequal ; G 3311
+U 8805 ; WX 838 ; N greaterequal ; G 3312
+U 8806 ; WX 838 ; N uni2266 ; G 3313
+U 8807 ; WX 838 ; N uni2267 ; G 3314
+U 8808 ; WX 841 ; N uni2268 ; G 3315
+U 8809 ; WX 841 ; N uni2269 ; G 3316
+U 8810 ; WX 1047 ; N uni226A ; G 3317
+U 8811 ; WX 1047 ; N uni226B ; G 3318
+U 8812 ; WX 500 ; N uni226C ; G 3319
+U 8813 ; WX 838 ; N uni226D ; G 3320
+U 8814 ; WX 838 ; N uni226E ; G 3321
+U 8815 ; WX 838 ; N uni226F ; G 3322
+U 8816 ; WX 838 ; N uni2270 ; G 3323
+U 8817 ; WX 838 ; N uni2271 ; G 3324
+U 8818 ; WX 838 ; N uni2272 ; G 3325
+U 8819 ; WX 838 ; N uni2273 ; G 3326
+U 8820 ; WX 838 ; N uni2274 ; G 3327
+U 8821 ; WX 838 ; N uni2275 ; G 3328
+U 8822 ; WX 838 ; N uni2276 ; G 3329
+U 8823 ; WX 838 ; N uni2277 ; G 3330
+U 8824 ; WX 838 ; N uni2278 ; G 3331
+U 8825 ; WX 838 ; N uni2279 ; G 3332
+U 8826 ; WX 838 ; N uni227A ; G 3333
+U 8827 ; WX 838 ; N uni227B ; G 3334
+U 8828 ; WX 838 ; N uni227C ; G 3335
+U 8829 ; WX 838 ; N uni227D ; G 3336
+U 8830 ; WX 838 ; N uni227E ; G 3337
+U 8831 ; WX 838 ; N uni227F ; G 3338
+U 8832 ; WX 838 ; N uni2280 ; G 3339
+U 8833 ; WX 838 ; N uni2281 ; G 3340
+U 8834 ; WX 838 ; N propersubset ; G 3341
+U 8835 ; WX 838 ; N propersuperset ; G 3342
+U 8836 ; WX 838 ; N notsubset ; G 3343
+U 8837 ; WX 838 ; N uni2285 ; G 3344
+U 8838 ; WX 838 ; N reflexsubset ; G 3345
+U 8839 ; WX 838 ; N reflexsuperset ; G 3346
+U 8840 ; WX 838 ; N uni2288 ; G 3347
+U 8841 ; WX 838 ; N uni2289 ; G 3348
+U 8842 ; WX 838 ; N uni228A ; G 3349
+U 8843 ; WX 838 ; N uni228B ; G 3350
+U 8844 ; WX 812 ; N uni228C ; G 3351
+U 8845 ; WX 812 ; N uni228D ; G 3352
+U 8846 ; WX 812 ; N uni228E ; G 3353
+U 8847 ; WX 838 ; N uni228F ; G 3354
+U 8848 ; WX 838 ; N uni2290 ; G 3355
+U 8849 ; WX 838 ; N uni2291 ; G 3356
+U 8850 ; WX 838 ; N uni2292 ; G 3357
+U 8851 ; WX 796 ; N uni2293 ; G 3358
+U 8852 ; WX 796 ; N uni2294 ; G 3359
+U 8853 ; WX 838 ; N circleplus ; G 3360
+U 8854 ; WX 838 ; N uni2296 ; G 3361
+U 8855 ; WX 838 ; N circlemultiply ; G 3362
+U 8856 ; WX 838 ; N uni2298 ; G 3363
+U 8857 ; WX 838 ; N uni2299 ; G 3364
+U 8858 ; WX 838 ; N uni229A ; G 3365
+U 8859 ; WX 838 ; N uni229B ; G 3366
+U 8860 ; WX 838 ; N uni229C ; G 3367
+U 8861 ; WX 838 ; N uni229D ; G 3368
+U 8862 ; WX 838 ; N uni229E ; G 3369
+U 8863 ; WX 838 ; N uni229F ; G 3370
+U 8864 ; WX 838 ; N uni22A0 ; G 3371
+U 8865 ; WX 838 ; N uni22A1 ; G 3372
+U 8866 ; WX 914 ; N uni22A2 ; G 3373
+U 8867 ; WX 914 ; N uni22A3 ; G 3374
+U 8868 ; WX 914 ; N uni22A4 ; G 3375
+U 8869 ; WX 914 ; N perpendicular ; G 3376
+U 8870 ; WX 542 ; N uni22A6 ; G 3377
+U 8871 ; WX 542 ; N uni22A7 ; G 3378
+U 8872 ; WX 914 ; N uni22A8 ; G 3379
+U 8873 ; WX 914 ; N uni22A9 ; G 3380
+U 8874 ; WX 914 ; N uni22AA ; G 3381
+U 8875 ; WX 914 ; N uni22AB ; G 3382
+U 8876 ; WX 914 ; N uni22AC ; G 3383
+U 8877 ; WX 914 ; N uni22AD ; G 3384
+U 8878 ; WX 914 ; N uni22AE ; G 3385
+U 8879 ; WX 914 ; N uni22AF ; G 3386
+U 8880 ; WX 838 ; N uni22B0 ; G 3387
+U 8881 ; WX 838 ; N uni22B1 ; G 3388
+U 8882 ; WX 838 ; N uni22B2 ; G 3389
+U 8883 ; WX 838 ; N uni22B3 ; G 3390
+U 8884 ; WX 838 ; N uni22B4 ; G 3391
+U 8885 ; WX 838 ; N uni22B5 ; G 3392
+U 8886 ; WX 1000 ; N uni22B6 ; G 3393
+U 8887 ; WX 1000 ; N uni22B7 ; G 3394
+U 8888 ; WX 838 ; N uni22B8 ; G 3395
+U 8889 ; WX 838 ; N uni22B9 ; G 3396
+U 8890 ; WX 542 ; N uni22BA ; G 3397
+U 8891 ; WX 812 ; N uni22BB ; G 3398
+U 8892 ; WX 812 ; N uni22BC ; G 3399
+U 8893 ; WX 812 ; N uni22BD ; G 3400
+U 8894 ; WX 838 ; N uni22BE ; G 3401
+U 8895 ; WX 838 ; N uni22BF ; G 3402
+U 8896 ; WX 843 ; N uni22C0 ; G 3403
+U 8897 ; WX 843 ; N uni22C1 ; G 3404
+U 8898 ; WX 843 ; N uni22C2 ; G 3405
+U 8899 ; WX 843 ; N uni22C3 ; G 3406
+U 8900 ; WX 626 ; N uni22C4 ; G 3407
+U 8901 ; WX 380 ; N dotmath ; G 3408
+U 8902 ; WX 626 ; N uni22C6 ; G 3409
+U 8903 ; WX 838 ; N uni22C7 ; G 3410
+U 8904 ; WX 1000 ; N uni22C8 ; G 3411
+U 8905 ; WX 1000 ; N uni22C9 ; G 3412
+U 8906 ; WX 1000 ; N uni22CA ; G 3413
+U 8907 ; WX 1000 ; N uni22CB ; G 3414
+U 8908 ; WX 1000 ; N uni22CC ; G 3415
+U 8909 ; WX 838 ; N uni22CD ; G 3416
+U 8910 ; WX 812 ; N uni22CE ; G 3417
+U 8911 ; WX 812 ; N uni22CF ; G 3418
+U 8912 ; WX 838 ; N uni22D0 ; G 3419
+U 8913 ; WX 838 ; N uni22D1 ; G 3420
+U 8914 ; WX 838 ; N uni22D2 ; G 3421
+U 8915 ; WX 838 ; N uni22D3 ; G 3422
+U 8916 ; WX 838 ; N uni22D4 ; G 3423
+U 8917 ; WX 838 ; N uni22D5 ; G 3424
+U 8918 ; WX 838 ; N uni22D6 ; G 3425
+U 8919 ; WX 838 ; N uni22D7 ; G 3426
+U 8920 ; WX 1422 ; N uni22D8 ; G 3427
+U 8921 ; WX 1422 ; N uni22D9 ; G 3428
+U 8922 ; WX 838 ; N uni22DA ; G 3429
+U 8923 ; WX 838 ; N uni22DB ; G 3430
+U 8924 ; WX 838 ; N uni22DC ; G 3431
+U 8925 ; WX 838 ; N uni22DD ; G 3432
+U 8926 ; WX 838 ; N uni22DE ; G 3433
+U 8927 ; WX 838 ; N uni22DF ; G 3434
+U 8928 ; WX 838 ; N uni22E0 ; G 3435
+U 8929 ; WX 838 ; N uni22E1 ; G 3436
+U 8930 ; WX 838 ; N uni22E2 ; G 3437
+U 8931 ; WX 838 ; N uni22E3 ; G 3438
+U 8932 ; WX 838 ; N uni22E4 ; G 3439
+U 8933 ; WX 838 ; N uni22E5 ; G 3440
+U 8934 ; WX 838 ; N uni22E6 ; G 3441
+U 8935 ; WX 838 ; N uni22E7 ; G 3442
+U 8936 ; WX 838 ; N uni22E8 ; G 3443
+U 8937 ; WX 838 ; N uni22E9 ; G 3444
+U 8938 ; WX 838 ; N uni22EA ; G 3445
+U 8939 ; WX 838 ; N uni22EB ; G 3446
+U 8940 ; WX 838 ; N uni22EC ; G 3447
+U 8941 ; WX 838 ; N uni22ED ; G 3448
+U 8942 ; WX 1000 ; N uni22EE ; G 3449
+U 8943 ; WX 1000 ; N uni22EF ; G 3450
+U 8944 ; WX 1000 ; N uni22F0 ; G 3451
+U 8945 ; WX 1000 ; N uni22F1 ; G 3452
+U 8946 ; WX 1158 ; N uni22F2 ; G 3453
+U 8947 ; WX 896 ; N uni22F3 ; G 3454
+U 8948 ; WX 750 ; N uni22F4 ; G 3455
+U 8949 ; WX 896 ; N uni22F5 ; G 3456
+U 8950 ; WX 896 ; N uni22F6 ; G 3457
+U 8951 ; WX 750 ; N uni22F7 ; G 3458
+U 8952 ; WX 896 ; N uni22F8 ; G 3459
+U 8953 ; WX 896 ; N uni22F9 ; G 3460
+U 8954 ; WX 1158 ; N uni22FA ; G 3461
+U 8955 ; WX 896 ; N uni22FB ; G 3462
+U 8956 ; WX 750 ; N uni22FC ; G 3463
+U 8957 ; WX 896 ; N uni22FD ; G 3464
+U 8958 ; WX 750 ; N uni22FE ; G 3465
+U 8959 ; WX 896 ; N uni22FF ; G 3466
+U 8960 ; WX 602 ; N uni2300 ; G 3467
+U 8961 ; WX 602 ; N uni2301 ; G 3468
+U 8962 ; WX 716 ; N house ; G 3469
+U 8963 ; WX 838 ; N uni2303 ; G 3470
+U 8964 ; WX 838 ; N uni2304 ; G 3471
+U 8965 ; WX 838 ; N uni2305 ; G 3472
+U 8966 ; WX 838 ; N uni2306 ; G 3473
+U 8967 ; WX 488 ; N uni2307 ; G 3474
+U 8968 ; WX 457 ; N uni2308 ; G 3475
+U 8969 ; WX 457 ; N uni2309 ; G 3476
+U 8970 ; WX 457 ; N uni230A ; G 3477
+U 8971 ; WX 457 ; N uni230B ; G 3478
+U 8972 ; WX 809 ; N uni230C ; G 3479
+U 8973 ; WX 809 ; N uni230D ; G 3480
+U 8974 ; WX 809 ; N uni230E ; G 3481
+U 8975 ; WX 809 ; N uni230F ; G 3482
+U 8976 ; WX 838 ; N revlogicalnot ; G 3483
+U 8977 ; WX 539 ; N uni2311 ; G 3484
+U 8984 ; WX 928 ; N uni2318 ; G 3485
+U 8985 ; WX 838 ; N uni2319 ; G 3486
+U 8988 ; WX 469 ; N uni231C ; G 3487
+U 8989 ; WX 469 ; N uni231D ; G 3488
+U 8990 ; WX 469 ; N uni231E ; G 3489
+U 8991 ; WX 469 ; N uni231F ; G 3490
+U 8992 ; WX 610 ; N integraltp ; G 3491
+U 8993 ; WX 610 ; N integralbt ; G 3492
+U 8996 ; WX 1152 ; N uni2324 ; G 3493
+U 8997 ; WX 1152 ; N uni2325 ; G 3494
+U 8998 ; WX 1414 ; N uni2326 ; G 3495
+U 8999 ; WX 1152 ; N uni2327 ; G 3496
+U 9000 ; WX 1443 ; N uni2328 ; G 3497
+U 9003 ; WX 1414 ; N uni232B ; G 3498
+U 9004 ; WX 873 ; N uni232C ; G 3499
+U 9075 ; WX 390 ; N uni2373 ; G 3500
+U 9076 ; WX 716 ; N uni2374 ; G 3501
+U 9077 ; WX 869 ; N uni2375 ; G 3502
+U 9082 ; WX 687 ; N uni237A ; G 3503
+U 9085 ; WX 863 ; N uni237D ; G 3504
+U 9095 ; WX 1152 ; N uni2387 ; G 3505
+U 9108 ; WX 873 ; N uni2394 ; G 3506
+U 9115 ; WX 500 ; N uni239B ; G 3507
+U 9116 ; WX 500 ; N uni239C ; G 3508
+U 9117 ; WX 500 ; N uni239D ; G 3509
+U 9118 ; WX 500 ; N uni239E ; G 3510
+U 9119 ; WX 500 ; N uni239F ; G 3511
+U 9120 ; WX 500 ; N uni23A0 ; G 3512
+U 9121 ; WX 500 ; N uni23A1 ; G 3513
+U 9122 ; WX 500 ; N uni23A2 ; G 3514
+U 9123 ; WX 500 ; N uni23A3 ; G 3515
+U 9124 ; WX 500 ; N uni23A4 ; G 3516
+U 9125 ; WX 500 ; N uni23A5 ; G 3517
+U 9126 ; WX 500 ; N uni23A6 ; G 3518
+U 9127 ; WX 750 ; N uni23A7 ; G 3519
+U 9128 ; WX 750 ; N uni23A8 ; G 3520
+U 9129 ; WX 750 ; N uni23A9 ; G 3521
+U 9130 ; WX 750 ; N uni23AA ; G 3522
+U 9131 ; WX 750 ; N uni23AB ; G 3523
+U 9132 ; WX 750 ; N uni23AC ; G 3524
+U 9133 ; WX 750 ; N uni23AD ; G 3525
+U 9134 ; WX 610 ; N uni23AE ; G 3526
+U 9166 ; WX 838 ; N uni23CE ; G 3527
+U 9167 ; WX 945 ; N uni23CF ; G 3528
+U 9187 ; WX 873 ; N uni23E3 ; G 3529
+U 9189 ; WX 769 ; N uni23E5 ; G 3530
+U 9192 ; WX 696 ; N uni23E8 ; G 3531
+U 9250 ; WX 716 ; N uni2422 ; G 3532
+U 9251 ; WX 716 ; N uni2423 ; G 3533
+U 9312 ; WX 847 ; N uni2460 ; G 3534
+U 9313 ; WX 847 ; N uni2461 ; G 3535
+U 9314 ; WX 847 ; N uni2462 ; G 3536
+U 9315 ; WX 847 ; N uni2463 ; G 3537
+U 9316 ; WX 847 ; N uni2464 ; G 3538
+U 9317 ; WX 847 ; N uni2465 ; G 3539
+U 9318 ; WX 847 ; N uni2466 ; G 3540
+U 9319 ; WX 847 ; N uni2467 ; G 3541
+U 9320 ; WX 847 ; N uni2468 ; G 3542
+U 9321 ; WX 847 ; N uni2469 ; G 3543
+U 9472 ; WX 602 ; N SF100000 ; G 3544
+U 9473 ; WX 602 ; N uni2501 ; G 3545
+U 9474 ; WX 602 ; N SF110000 ; G 3546
+U 9475 ; WX 602 ; N uni2503 ; G 3547
+U 9476 ; WX 602 ; N uni2504 ; G 3548
+U 9477 ; WX 602 ; N uni2505 ; G 3549
+U 9478 ; WX 602 ; N uni2506 ; G 3550
+U 9479 ; WX 602 ; N uni2507 ; G 3551
+U 9480 ; WX 602 ; N uni2508 ; G 3552
+U 9481 ; WX 602 ; N uni2509 ; G 3553
+U 9482 ; WX 602 ; N uni250A ; G 3554
+U 9483 ; WX 602 ; N uni250B ; G 3555
+U 9484 ; WX 602 ; N SF010000 ; G 3556
+U 9485 ; WX 602 ; N uni250D ; G 3557
+U 9486 ; WX 602 ; N uni250E ; G 3558
+U 9487 ; WX 602 ; N uni250F ; G 3559
+U 9488 ; WX 602 ; N SF030000 ; G 3560
+U 9489 ; WX 602 ; N uni2511 ; G 3561
+U 9490 ; WX 602 ; N uni2512 ; G 3562
+U 9491 ; WX 602 ; N uni2513 ; G 3563
+U 9492 ; WX 602 ; N SF020000 ; G 3564
+U 9493 ; WX 602 ; N uni2515 ; G 3565
+U 9494 ; WX 602 ; N uni2516 ; G 3566
+U 9495 ; WX 602 ; N uni2517 ; G 3567
+U 9496 ; WX 602 ; N SF040000 ; G 3568
+U 9497 ; WX 602 ; N uni2519 ; G 3569
+U 9498 ; WX 602 ; N uni251A ; G 3570
+U 9499 ; WX 602 ; N uni251B ; G 3571
+U 9500 ; WX 602 ; N SF080000 ; G 3572
+U 9501 ; WX 602 ; N uni251D ; G 3573
+U 9502 ; WX 602 ; N uni251E ; G 3574
+U 9503 ; WX 602 ; N uni251F ; G 3575
+U 9504 ; WX 602 ; N uni2520 ; G 3576
+U 9505 ; WX 602 ; N uni2521 ; G 3577
+U 9506 ; WX 602 ; N uni2522 ; G 3578
+U 9507 ; WX 602 ; N uni2523 ; G 3579
+U 9508 ; WX 602 ; N SF090000 ; G 3580
+U 9509 ; WX 602 ; N uni2525 ; G 3581
+U 9510 ; WX 602 ; N uni2526 ; G 3582
+U 9511 ; WX 602 ; N uni2527 ; G 3583
+U 9512 ; WX 602 ; N uni2528 ; G 3584
+U 9513 ; WX 602 ; N uni2529 ; G 3585
+U 9514 ; WX 602 ; N uni252A ; G 3586
+U 9515 ; WX 602 ; N uni252B ; G 3587
+U 9516 ; WX 602 ; N SF060000 ; G 3588
+U 9517 ; WX 602 ; N uni252D ; G 3589
+U 9518 ; WX 602 ; N uni252E ; G 3590
+U 9519 ; WX 602 ; N uni252F ; G 3591
+U 9520 ; WX 602 ; N uni2530 ; G 3592
+U 9521 ; WX 602 ; N uni2531 ; G 3593
+U 9522 ; WX 602 ; N uni2532 ; G 3594
+U 9523 ; WX 602 ; N uni2533 ; G 3595
+U 9524 ; WX 602 ; N SF070000 ; G 3596
+U 9525 ; WX 602 ; N uni2535 ; G 3597
+U 9526 ; WX 602 ; N uni2536 ; G 3598
+U 9527 ; WX 602 ; N uni2537 ; G 3599
+U 9528 ; WX 602 ; N uni2538 ; G 3600
+U 9529 ; WX 602 ; N uni2539 ; G 3601
+U 9530 ; WX 602 ; N uni253A ; G 3602
+U 9531 ; WX 602 ; N uni253B ; G 3603
+U 9532 ; WX 602 ; N SF050000 ; G 3604
+U 9533 ; WX 602 ; N uni253D ; G 3605
+U 9534 ; WX 602 ; N uni253E ; G 3606
+U 9535 ; WX 602 ; N uni253F ; G 3607
+U 9536 ; WX 602 ; N uni2540 ; G 3608
+U 9537 ; WX 602 ; N uni2541 ; G 3609
+U 9538 ; WX 602 ; N uni2542 ; G 3610
+U 9539 ; WX 602 ; N uni2543 ; G 3611
+U 9540 ; WX 602 ; N uni2544 ; G 3612
+U 9541 ; WX 602 ; N uni2545 ; G 3613
+U 9542 ; WX 602 ; N uni2546 ; G 3614
+U 9543 ; WX 602 ; N uni2547 ; G 3615
+U 9544 ; WX 602 ; N uni2548 ; G 3616
+U 9545 ; WX 602 ; N uni2549 ; G 3617
+U 9546 ; WX 602 ; N uni254A ; G 3618
+U 9547 ; WX 602 ; N uni254B ; G 3619
+U 9548 ; WX 602 ; N uni254C ; G 3620
+U 9549 ; WX 602 ; N uni254D ; G 3621
+U 9550 ; WX 602 ; N uni254E ; G 3622
+U 9551 ; WX 602 ; N uni254F ; G 3623
+U 9552 ; WX 602 ; N SF430000 ; G 3624
+U 9553 ; WX 602 ; N SF240000 ; G 3625
+U 9554 ; WX 602 ; N SF510000 ; G 3626
+U 9555 ; WX 602 ; N SF520000 ; G 3627
+U 9556 ; WX 602 ; N SF390000 ; G 3628
+U 9557 ; WX 602 ; N SF220000 ; G 3629
+U 9558 ; WX 602 ; N SF210000 ; G 3630
+U 9559 ; WX 602 ; N SF250000 ; G 3631
+U 9560 ; WX 602 ; N SF500000 ; G 3632
+U 9561 ; WX 602 ; N SF490000 ; G 3633
+U 9562 ; WX 602 ; N SF380000 ; G 3634
+U 9563 ; WX 602 ; N SF280000 ; G 3635
+U 9564 ; WX 602 ; N SF270000 ; G 3636
+U 9565 ; WX 602 ; N SF260000 ; G 3637
+U 9566 ; WX 602 ; N SF360000 ; G 3638
+U 9567 ; WX 602 ; N SF370000 ; G 3639
+U 9568 ; WX 602 ; N SF420000 ; G 3640
+U 9569 ; WX 602 ; N SF190000 ; G 3641
+U 9570 ; WX 602 ; N SF200000 ; G 3642
+U 9571 ; WX 602 ; N SF230000 ; G 3643
+U 9572 ; WX 602 ; N SF470000 ; G 3644
+U 9573 ; WX 602 ; N SF480000 ; G 3645
+U 9574 ; WX 602 ; N SF410000 ; G 3646
+U 9575 ; WX 602 ; N SF450000 ; G 3647
+U 9576 ; WX 602 ; N SF460000 ; G 3648
+U 9577 ; WX 602 ; N SF400000 ; G 3649
+U 9578 ; WX 602 ; N SF540000 ; G 3650
+U 9579 ; WX 602 ; N SF530000 ; G 3651
+U 9580 ; WX 602 ; N SF440000 ; G 3652
+U 9581 ; WX 602 ; N uni256D ; G 3653
+U 9582 ; WX 602 ; N uni256E ; G 3654
+U 9583 ; WX 602 ; N uni256F ; G 3655
+U 9584 ; WX 602 ; N uni2570 ; G 3656
+U 9585 ; WX 602 ; N uni2571 ; G 3657
+U 9586 ; WX 602 ; N uni2572 ; G 3658
+U 9587 ; WX 602 ; N uni2573 ; G 3659
+U 9588 ; WX 602 ; N uni2574 ; G 3660
+U 9589 ; WX 602 ; N uni2575 ; G 3661
+U 9590 ; WX 602 ; N uni2576 ; G 3662
+U 9591 ; WX 602 ; N uni2577 ; G 3663
+U 9592 ; WX 602 ; N uni2578 ; G 3664
+U 9593 ; WX 602 ; N uni2579 ; G 3665
+U 9594 ; WX 602 ; N uni257A ; G 3666
+U 9595 ; WX 602 ; N uni257B ; G 3667
+U 9596 ; WX 602 ; N uni257C ; G 3668
+U 9597 ; WX 602 ; N uni257D ; G 3669
+U 9598 ; WX 602 ; N uni257E ; G 3670
+U 9599 ; WX 602 ; N uni257F ; G 3671
+U 9600 ; WX 769 ; N upblock ; G 3672
+U 9601 ; WX 769 ; N uni2581 ; G 3673
+U 9602 ; WX 769 ; N uni2582 ; G 3674
+U 9603 ; WX 769 ; N uni2583 ; G 3675
+U 9604 ; WX 769 ; N dnblock ; G 3676
+U 9605 ; WX 769 ; N uni2585 ; G 3677
+U 9606 ; WX 769 ; N uni2586 ; G 3678
+U 9607 ; WX 769 ; N uni2587 ; G 3679
+U 9608 ; WX 769 ; N block ; G 3680
+U 9609 ; WX 769 ; N uni2589 ; G 3681
+U 9610 ; WX 769 ; N uni258A ; G 3682
+U 9611 ; WX 769 ; N uni258B ; G 3683
+U 9612 ; WX 769 ; N lfblock ; G 3684
+U 9613 ; WX 769 ; N uni258D ; G 3685
+U 9614 ; WX 769 ; N uni258E ; G 3686
+U 9615 ; WX 769 ; N uni258F ; G 3687
+U 9616 ; WX 769 ; N rtblock ; G 3688
+U 9617 ; WX 769 ; N ltshade ; G 3689
+U 9618 ; WX 769 ; N shade ; G 3690
+U 9619 ; WX 769 ; N dkshade ; G 3691
+U 9620 ; WX 769 ; N uni2594 ; G 3692
+U 9621 ; WX 769 ; N uni2595 ; G 3693
+U 9622 ; WX 769 ; N uni2596 ; G 3694
+U 9623 ; WX 769 ; N uni2597 ; G 3695
+U 9624 ; WX 769 ; N uni2598 ; G 3696
+U 9625 ; WX 769 ; N uni2599 ; G 3697
+U 9626 ; WX 769 ; N uni259A ; G 3698
+U 9627 ; WX 769 ; N uni259B ; G 3699
+U 9628 ; WX 769 ; N uni259C ; G 3700
+U 9629 ; WX 769 ; N uni259D ; G 3701
+U 9630 ; WX 769 ; N uni259E ; G 3702
+U 9631 ; WX 769 ; N uni259F ; G 3703
+U 9632 ; WX 945 ; N filledbox ; G 3704
+U 9633 ; WX 945 ; N H22073 ; G 3705
+U 9634 ; WX 945 ; N uni25A2 ; G 3706
+U 9635 ; WX 945 ; N uni25A3 ; G 3707
+U 9636 ; WX 945 ; N uni25A4 ; G 3708
+U 9637 ; WX 945 ; N uni25A5 ; G 3709
+U 9638 ; WX 945 ; N uni25A6 ; G 3710
+U 9639 ; WX 945 ; N uni25A7 ; G 3711
+U 9640 ; WX 945 ; N uni25A8 ; G 3712
+U 9641 ; WX 945 ; N uni25A9 ; G 3713
+U 9642 ; WX 678 ; N H18543 ; G 3714
+U 9643 ; WX 678 ; N H18551 ; G 3715
+U 9644 ; WX 945 ; N filledrect ; G 3716
+U 9645 ; WX 945 ; N uni25AD ; G 3717
+U 9646 ; WX 550 ; N uni25AE ; G 3718
+U 9647 ; WX 550 ; N uni25AF ; G 3719
+U 9648 ; WX 769 ; N uni25B0 ; G 3720
+U 9649 ; WX 769 ; N uni25B1 ; G 3721
+U 9650 ; WX 769 ; N triagup ; G 3722
+U 9651 ; WX 769 ; N uni25B3 ; G 3723
+U 9652 ; WX 502 ; N uni25B4 ; G 3724
+U 9653 ; WX 502 ; N uni25B5 ; G 3725
+U 9654 ; WX 769 ; N uni25B6 ; G 3726
+U 9655 ; WX 769 ; N uni25B7 ; G 3727
+U 9656 ; WX 502 ; N uni25B8 ; G 3728
+U 9657 ; WX 502 ; N uni25B9 ; G 3729
+U 9658 ; WX 769 ; N triagrt ; G 3730
+U 9659 ; WX 769 ; N uni25BB ; G 3731
+U 9660 ; WX 769 ; N triagdn ; G 3732
+U 9661 ; WX 769 ; N uni25BD ; G 3733
+U 9662 ; WX 502 ; N uni25BE ; G 3734
+U 9663 ; WX 502 ; N uni25BF ; G 3735
+U 9664 ; WX 769 ; N uni25C0 ; G 3736
+U 9665 ; WX 769 ; N uni25C1 ; G 3737
+U 9666 ; WX 502 ; N uni25C2 ; G 3738
+U 9667 ; WX 502 ; N uni25C3 ; G 3739
+U 9668 ; WX 769 ; N triaglf ; G 3740
+U 9669 ; WX 769 ; N uni25C5 ; G 3741
+U 9670 ; WX 769 ; N uni25C6 ; G 3742
+U 9671 ; WX 769 ; N uni25C7 ; G 3743
+U 9672 ; WX 769 ; N uni25C8 ; G 3744
+U 9673 ; WX 873 ; N uni25C9 ; G 3745
+U 9674 ; WX 494 ; N lozenge ; G 3746
+U 9675 ; WX 873 ; N circle ; G 3747
+U 9676 ; WX 873 ; N uni25CC ; G 3748
+U 9677 ; WX 873 ; N uni25CD ; G 3749
+U 9678 ; WX 873 ; N uni25CE ; G 3750
+U 9679 ; WX 873 ; N H18533 ; G 3751
+U 9680 ; WX 873 ; N uni25D0 ; G 3752
+U 9681 ; WX 873 ; N uni25D1 ; G 3753
+U 9682 ; WX 873 ; N uni25D2 ; G 3754
+U 9683 ; WX 873 ; N uni25D3 ; G 3755
+U 9684 ; WX 873 ; N uni25D4 ; G 3756
+U 9685 ; WX 873 ; N uni25D5 ; G 3757
+U 9686 ; WX 527 ; N uni25D6 ; G 3758
+U 9687 ; WX 527 ; N uni25D7 ; G 3759
+U 9688 ; WX 840 ; N invbullet ; G 3760
+U 9689 ; WX 970 ; N invcircle ; G 3761
+U 9690 ; WX 970 ; N uni25DA ; G 3762
+U 9691 ; WX 970 ; N uni25DB ; G 3763
+U 9692 ; WX 387 ; N uni25DC ; G 3764
+U 9693 ; WX 387 ; N uni25DD ; G 3765
+U 9694 ; WX 387 ; N uni25DE ; G 3766
+U 9695 ; WX 387 ; N uni25DF ; G 3767
+U 9696 ; WX 769 ; N uni25E0 ; G 3768
+U 9697 ; WX 769 ; N uni25E1 ; G 3769
+U 9698 ; WX 769 ; N uni25E2 ; G 3770
+U 9699 ; WX 769 ; N uni25E3 ; G 3771
+U 9700 ; WX 769 ; N uni25E4 ; G 3772
+U 9701 ; WX 769 ; N uni25E5 ; G 3773
+U 9702 ; WX 639 ; N openbullet ; G 3774
+U 9703 ; WX 945 ; N uni25E7 ; G 3775
+U 9704 ; WX 945 ; N uni25E8 ; G 3776
+U 9705 ; WX 945 ; N uni25E9 ; G 3777
+U 9706 ; WX 945 ; N uni25EA ; G 3778
+U 9707 ; WX 945 ; N uni25EB ; G 3779
+U 9708 ; WX 769 ; N uni25EC ; G 3780
+U 9709 ; WX 769 ; N uni25ED ; G 3781
+U 9710 ; WX 769 ; N uni25EE ; G 3782
+U 9711 ; WX 1119 ; N uni25EF ; G 3783
+U 9712 ; WX 945 ; N uni25F0 ; G 3784
+U 9713 ; WX 945 ; N uni25F1 ; G 3785
+U 9714 ; WX 945 ; N uni25F2 ; G 3786
+U 9715 ; WX 945 ; N uni25F3 ; G 3787
+U 9716 ; WX 873 ; N uni25F4 ; G 3788
+U 9717 ; WX 873 ; N uni25F5 ; G 3789
+U 9718 ; WX 873 ; N uni25F6 ; G 3790
+U 9719 ; WX 873 ; N uni25F7 ; G 3791
+U 9720 ; WX 769 ; N uni25F8 ; G 3792
+U 9721 ; WX 769 ; N uni25F9 ; G 3793
+U 9722 ; WX 769 ; N uni25FA ; G 3794
+U 9723 ; WX 830 ; N uni25FB ; G 3795
+U 9724 ; WX 830 ; N uni25FC ; G 3796
+U 9725 ; WX 732 ; N uni25FD ; G 3797
+U 9726 ; WX 732 ; N uni25FE ; G 3798
+U 9727 ; WX 769 ; N uni25FF ; G 3799
+U 9728 ; WX 896 ; N uni2600 ; G 3800
+U 9729 ; WX 1000 ; N uni2601 ; G 3801
+U 9730 ; WX 896 ; N uni2602 ; G 3802
+U 9731 ; WX 896 ; N uni2603 ; G 3803
+U 9732 ; WX 896 ; N uni2604 ; G 3804
+U 9733 ; WX 896 ; N uni2605 ; G 3805
+U 9734 ; WX 896 ; N uni2606 ; G 3806
+U 9735 ; WX 573 ; N uni2607 ; G 3807
+U 9736 ; WX 896 ; N uni2608 ; G 3808
+U 9737 ; WX 896 ; N uni2609 ; G 3809
+U 9738 ; WX 888 ; N uni260A ; G 3810
+U 9739 ; WX 888 ; N uni260B ; G 3811
+U 9740 ; WX 671 ; N uni260C ; G 3812
+U 9741 ; WX 1013 ; N uni260D ; G 3813
+U 9742 ; WX 1246 ; N uni260E ; G 3814
+U 9743 ; WX 1250 ; N uni260F ; G 3815
+U 9744 ; WX 896 ; N uni2610 ; G 3816
+U 9745 ; WX 896 ; N uni2611 ; G 3817
+U 9746 ; WX 896 ; N uni2612 ; G 3818
+U 9747 ; WX 532 ; N uni2613 ; G 3819
+U 9748 ; WX 896 ; N uni2614 ; G 3820
+U 9749 ; WX 896 ; N uni2615 ; G 3821
+U 9750 ; WX 896 ; N uni2616 ; G 3822
+U 9751 ; WX 896 ; N uni2617 ; G 3823
+U 9752 ; WX 896 ; N uni2618 ; G 3824
+U 9753 ; WX 896 ; N uni2619 ; G 3825
+U 9754 ; WX 896 ; N uni261A ; G 3826
+U 9755 ; WX 896 ; N uni261B ; G 3827
+U 9756 ; WX 896 ; N uni261C ; G 3828
+U 9757 ; WX 609 ; N uni261D ; G 3829
+U 9758 ; WX 896 ; N uni261E ; G 3830
+U 9759 ; WX 609 ; N uni261F ; G 3831
+U 9760 ; WX 896 ; N uni2620 ; G 3832
+U 9761 ; WX 896 ; N uni2621 ; G 3833
+U 9762 ; WX 896 ; N uni2622 ; G 3834
+U 9763 ; WX 896 ; N uni2623 ; G 3835
+U 9764 ; WX 669 ; N uni2624 ; G 3836
+U 9765 ; WX 746 ; N uni2625 ; G 3837
+U 9766 ; WX 649 ; N uni2626 ; G 3838
+U 9767 ; WX 784 ; N uni2627 ; G 3839
+U 9768 ; WX 545 ; N uni2628 ; G 3840
+U 9769 ; WX 896 ; N uni2629 ; G 3841
+U 9770 ; WX 896 ; N uni262A ; G 3842
+U 9771 ; WX 896 ; N uni262B ; G 3843
+U 9772 ; WX 710 ; N uni262C ; G 3844
+U 9773 ; WX 896 ; N uni262D ; G 3845
+U 9774 ; WX 896 ; N uni262E ; G 3846
+U 9775 ; WX 896 ; N uni262F ; G 3847
+U 9776 ; WX 896 ; N uni2630 ; G 3848
+U 9777 ; WX 896 ; N uni2631 ; G 3849
+U 9778 ; WX 896 ; N uni2632 ; G 3850
+U 9779 ; WX 896 ; N uni2633 ; G 3851
+U 9780 ; WX 896 ; N uni2634 ; G 3852
+U 9781 ; WX 896 ; N uni2635 ; G 3853
+U 9782 ; WX 896 ; N uni2636 ; G 3854
+U 9783 ; WX 896 ; N uni2637 ; G 3855
+U 9784 ; WX 896 ; N uni2638 ; G 3856
+U 9785 ; WX 1042 ; N uni2639 ; G 3857
+U 9786 ; WX 1042 ; N smileface ; G 3858
+U 9787 ; WX 1042 ; N invsmileface ; G 3859
+U 9788 ; WX 896 ; N sun ; G 3860
+U 9789 ; WX 896 ; N uni263D ; G 3861
+U 9790 ; WX 896 ; N uni263E ; G 3862
+U 9791 ; WX 614 ; N uni263F ; G 3863
+U 9792 ; WX 732 ; N female ; G 3864
+U 9793 ; WX 732 ; N uni2641 ; G 3865
+U 9794 ; WX 896 ; N male ; G 3866
+U 9795 ; WX 896 ; N uni2643 ; G 3867
+U 9796 ; WX 896 ; N uni2644 ; G 3868
+U 9797 ; WX 896 ; N uni2645 ; G 3869
+U 9798 ; WX 896 ; N uni2646 ; G 3870
+U 9799 ; WX 896 ; N uni2647 ; G 3871
+U 9800 ; WX 896 ; N uni2648 ; G 3872
+U 9801 ; WX 896 ; N uni2649 ; G 3873
+U 9802 ; WX 896 ; N uni264A ; G 3874
+U 9803 ; WX 896 ; N uni264B ; G 3875
+U 9804 ; WX 896 ; N uni264C ; G 3876
+U 9805 ; WX 896 ; N uni264D ; G 3877
+U 9806 ; WX 896 ; N uni264E ; G 3878
+U 9807 ; WX 896 ; N uni264F ; G 3879
+U 9808 ; WX 896 ; N uni2650 ; G 3880
+U 9809 ; WX 896 ; N uni2651 ; G 3881
+U 9810 ; WX 896 ; N uni2652 ; G 3882
+U 9811 ; WX 896 ; N uni2653 ; G 3883
+U 9812 ; WX 896 ; N uni2654 ; G 3884
+U 9813 ; WX 896 ; N uni2655 ; G 3885
+U 9814 ; WX 896 ; N uni2656 ; G 3886
+U 9815 ; WX 896 ; N uni2657 ; G 3887
+U 9816 ; WX 896 ; N uni2658 ; G 3888
+U 9817 ; WX 896 ; N uni2659 ; G 3889
+U 9818 ; WX 896 ; N uni265A ; G 3890
+U 9819 ; WX 896 ; N uni265B ; G 3891
+U 9820 ; WX 896 ; N uni265C ; G 3892
+U 9821 ; WX 896 ; N uni265D ; G 3893
+U 9822 ; WX 896 ; N uni265E ; G 3894
+U 9823 ; WX 896 ; N uni265F ; G 3895
+U 9824 ; WX 896 ; N spade ; G 3896
+U 9825 ; WX 896 ; N uni2661 ; G 3897
+U 9826 ; WX 896 ; N uni2662 ; G 3898
+U 9827 ; WX 896 ; N club ; G 3899
+U 9828 ; WX 896 ; N uni2664 ; G 3900
+U 9829 ; WX 896 ; N heart ; G 3901
+U 9830 ; WX 896 ; N diamond ; G 3902
+U 9831 ; WX 896 ; N uni2667 ; G 3903
+U 9832 ; WX 896 ; N uni2668 ; G 3904
+U 9833 ; WX 472 ; N uni2669 ; G 3905
+U 9834 ; WX 638 ; N musicalnote ; G 3906
+U 9835 ; WX 896 ; N musicalnotedbl ; G 3907
+U 9836 ; WX 896 ; N uni266C ; G 3908
+U 9837 ; WX 472 ; N uni266D ; G 3909
+U 9838 ; WX 357 ; N uni266E ; G 3910
+U 9839 ; WX 484 ; N uni266F ; G 3911
+U 9840 ; WX 748 ; N uni2670 ; G 3912
+U 9841 ; WX 766 ; N uni2671 ; G 3913
+U 9842 ; WX 896 ; N uni2672 ; G 3914
+U 9843 ; WX 896 ; N uni2673 ; G 3915
+U 9844 ; WX 896 ; N uni2674 ; G 3916
+U 9845 ; WX 896 ; N uni2675 ; G 3917
+U 9846 ; WX 896 ; N uni2676 ; G 3918
+U 9847 ; WX 896 ; N uni2677 ; G 3919
+U 9848 ; WX 896 ; N uni2678 ; G 3920
+U 9849 ; WX 896 ; N uni2679 ; G 3921
+U 9850 ; WX 896 ; N uni267A ; G 3922
+U 9851 ; WX 896 ; N uni267B ; G 3923
+U 9852 ; WX 896 ; N uni267C ; G 3924
+U 9853 ; WX 896 ; N uni267D ; G 3925
+U 9854 ; WX 896 ; N uni267E ; G 3926
+U 9855 ; WX 896 ; N uni267F ; G 3927
+U 9856 ; WX 869 ; N uni2680 ; G 3928
+U 9857 ; WX 869 ; N uni2681 ; G 3929
+U 9858 ; WX 869 ; N uni2682 ; G 3930
+U 9859 ; WX 869 ; N uni2683 ; G 3931
+U 9860 ; WX 869 ; N uni2684 ; G 3932
+U 9861 ; WX 869 ; N uni2685 ; G 3933
+U 9862 ; WX 896 ; N uni2686 ; G 3934
+U 9863 ; WX 896 ; N uni2687 ; G 3935
+U 9864 ; WX 896 ; N uni2688 ; G 3936
+U 9865 ; WX 896 ; N uni2689 ; G 3937
+U 9866 ; WX 896 ; N uni268A ; G 3938
+U 9867 ; WX 896 ; N uni268B ; G 3939
+U 9868 ; WX 896 ; N uni268C ; G 3940
+U 9869 ; WX 896 ; N uni268D ; G 3941
+U 9870 ; WX 896 ; N uni268E ; G 3942
+U 9871 ; WX 896 ; N uni268F ; G 3943
+U 9872 ; WX 896 ; N uni2690 ; G 3944
+U 9873 ; WX 896 ; N uni2691 ; G 3945
+U 9874 ; WX 896 ; N uni2692 ; G 3946
+U 9875 ; WX 896 ; N uni2693 ; G 3947
+U 9876 ; WX 896 ; N uni2694 ; G 3948
+U 9877 ; WX 541 ; N uni2695 ; G 3949
+U 9878 ; WX 896 ; N uni2696 ; G 3950
+U 9879 ; WX 896 ; N uni2697 ; G 3951
+U 9880 ; WX 896 ; N uni2698 ; G 3952
+U 9881 ; WX 896 ; N uni2699 ; G 3953
+U 9882 ; WX 896 ; N uni269A ; G 3954
+U 9883 ; WX 896 ; N uni269B ; G 3955
+U 9884 ; WX 896 ; N uni269C ; G 3956
+U 9886 ; WX 896 ; N uni269E ; G 3957
+U 9887 ; WX 896 ; N uni269F ; G 3958
+U 9888 ; WX 896 ; N uni26A0 ; G 3959
+U 9889 ; WX 702 ; N uni26A1 ; G 3960
+U 9890 ; WX 1004 ; N uni26A2 ; G 3961
+U 9891 ; WX 1089 ; N uni26A3 ; G 3962
+U 9892 ; WX 1175 ; N uni26A4 ; G 3963
+U 9893 ; WX 903 ; N uni26A5 ; G 3964
+U 9894 ; WX 838 ; N uni26A6 ; G 3965
+U 9895 ; WX 838 ; N uni26A7 ; G 3966
+U 9896 ; WX 838 ; N uni26A8 ; G 3967
+U 9897 ; WX 838 ; N uni26A9 ; G 3968
+U 9898 ; WX 838 ; N uni26AA ; G 3969
+U 9899 ; WX 838 ; N uni26AB ; G 3970
+U 9900 ; WX 838 ; N uni26AC ; G 3971
+U 9901 ; WX 838 ; N uni26AD ; G 3972
+U 9902 ; WX 838 ; N uni26AE ; G 3973
+U 9903 ; WX 838 ; N uni26AF ; G 3974
+U 9904 ; WX 844 ; N uni26B0 ; G 3975
+U 9905 ; WX 838 ; N uni26B1 ; G 3976
+U 9906 ; WX 732 ; N uni26B2 ; G 3977
+U 9907 ; WX 732 ; N uni26B3 ; G 3978
+U 9908 ; WX 732 ; N uni26B4 ; G 3979
+U 9909 ; WX 732 ; N uni26B5 ; G 3980
+U 9910 ; WX 850 ; N uni26B6 ; G 3981
+U 9911 ; WX 732 ; N uni26B7 ; G 3982
+U 9912 ; WX 732 ; N uni26B8 ; G 3983
+U 9920 ; WX 838 ; N uni26C0 ; G 3984
+U 9921 ; WX 838 ; N uni26C1 ; G 3985
+U 9922 ; WX 838 ; N uni26C2 ; G 3986
+U 9923 ; WX 838 ; N uni26C3 ; G 3987
+U 9954 ; WX 732 ; N uni26E2 ; G 3988
+U 9985 ; WX 838 ; N uni2701 ; G 3989
+U 9986 ; WX 838 ; N uni2702 ; G 3990
+U 9987 ; WX 838 ; N uni2703 ; G 3991
+U 9988 ; WX 838 ; N uni2704 ; G 3992
+U 9990 ; WX 838 ; N uni2706 ; G 3993
+U 9991 ; WX 838 ; N uni2707 ; G 3994
+U 9992 ; WX 838 ; N uni2708 ; G 3995
+U 9993 ; WX 838 ; N uni2709 ; G 3996
+U 9996 ; WX 838 ; N uni270C ; G 3997
+U 9997 ; WX 838 ; N uni270D ; G 3998
+U 9998 ; WX 838 ; N uni270E ; G 3999
+U 9999 ; WX 838 ; N uni270F ; G 4000
+U 10000 ; WX 838 ; N uni2710 ; G 4001
+U 10001 ; WX 838 ; N uni2711 ; G 4002
+U 10002 ; WX 838 ; N uni2712 ; G 4003
+U 10003 ; WX 838 ; N uni2713 ; G 4004
+U 10004 ; WX 838 ; N uni2714 ; G 4005
+U 10005 ; WX 838 ; N uni2715 ; G 4006
+U 10006 ; WX 838 ; N uni2716 ; G 4007
+U 10007 ; WX 838 ; N uni2717 ; G 4008
+U 10008 ; WX 838 ; N uni2718 ; G 4009
+U 10009 ; WX 838 ; N uni2719 ; G 4010
+U 10010 ; WX 838 ; N uni271A ; G 4011
+U 10011 ; WX 838 ; N uni271B ; G 4012
+U 10012 ; WX 838 ; N uni271C ; G 4013
+U 10013 ; WX 838 ; N uni271D ; G 4014
+U 10014 ; WX 838 ; N uni271E ; G 4015
+U 10015 ; WX 838 ; N uni271F ; G 4016
+U 10016 ; WX 838 ; N uni2720 ; G 4017
+U 10017 ; WX 838 ; N uni2721 ; G 4018
+U 10018 ; WX 838 ; N uni2722 ; G 4019
+U 10019 ; WX 838 ; N uni2723 ; G 4020
+U 10020 ; WX 838 ; N uni2724 ; G 4021
+U 10021 ; WX 838 ; N uni2725 ; G 4022
+U 10022 ; WX 838 ; N uni2726 ; G 4023
+U 10023 ; WX 838 ; N uni2727 ; G 4024
+U 10025 ; WX 838 ; N uni2729 ; G 4025
+U 10026 ; WX 838 ; N uni272A ; G 4026
+U 10027 ; WX 838 ; N uni272B ; G 4027
+U 10028 ; WX 838 ; N uni272C ; G 4028
+U 10029 ; WX 838 ; N uni272D ; G 4029
+U 10030 ; WX 838 ; N uni272E ; G 4030
+U 10031 ; WX 838 ; N uni272F ; G 4031
+U 10032 ; WX 838 ; N uni2730 ; G 4032
+U 10033 ; WX 838 ; N uni2731 ; G 4033
+U 10034 ; WX 838 ; N uni2732 ; G 4034
+U 10035 ; WX 838 ; N uni2733 ; G 4035
+U 10036 ; WX 838 ; N uni2734 ; G 4036
+U 10037 ; WX 838 ; N uni2735 ; G 4037
+U 10038 ; WX 838 ; N uni2736 ; G 4038
+U 10039 ; WX 838 ; N uni2737 ; G 4039
+U 10040 ; WX 838 ; N uni2738 ; G 4040
+U 10041 ; WX 838 ; N uni2739 ; G 4041
+U 10042 ; WX 838 ; N uni273A ; G 4042
+U 10043 ; WX 838 ; N uni273B ; G 4043
+U 10044 ; WX 838 ; N uni273C ; G 4044
+U 10045 ; WX 838 ; N uni273D ; G 4045
+U 10046 ; WX 838 ; N uni273E ; G 4046
+U 10047 ; WX 838 ; N uni273F ; G 4047
+U 10048 ; WX 838 ; N uni2740 ; G 4048
+U 10049 ; WX 838 ; N uni2741 ; G 4049
+U 10050 ; WX 838 ; N uni2742 ; G 4050
+U 10051 ; WX 838 ; N uni2743 ; G 4051
+U 10052 ; WX 838 ; N uni2744 ; G 4052
+U 10053 ; WX 838 ; N uni2745 ; G 4053
+U 10054 ; WX 838 ; N uni2746 ; G 4054
+U 10055 ; WX 838 ; N uni2747 ; G 4055
+U 10056 ; WX 838 ; N uni2748 ; G 4056
+U 10057 ; WX 838 ; N uni2749 ; G 4057
+U 10058 ; WX 838 ; N uni274A ; G 4058
+U 10059 ; WX 838 ; N uni274B ; G 4059
+U 10061 ; WX 896 ; N uni274D ; G 4060
+U 10063 ; WX 896 ; N uni274F ; G 4061
+U 10064 ; WX 896 ; N uni2750 ; G 4062
+U 10065 ; WX 896 ; N uni2751 ; G 4063
+U 10066 ; WX 896 ; N uni2752 ; G 4064
+U 10070 ; WX 896 ; N uni2756 ; G 4065
+U 10072 ; WX 838 ; N uni2758 ; G 4066
+U 10073 ; WX 838 ; N uni2759 ; G 4067
+U 10074 ; WX 838 ; N uni275A ; G 4068
+U 10075 ; WX 347 ; N uni275B ; G 4069
+U 10076 ; WX 347 ; N uni275C ; G 4070
+U 10077 ; WX 587 ; N uni275D ; G 4071
+U 10078 ; WX 587 ; N uni275E ; G 4072
+U 10081 ; WX 838 ; N uni2761 ; G 4073
+U 10082 ; WX 838 ; N uni2762 ; G 4074
+U 10083 ; WX 838 ; N uni2763 ; G 4075
+U 10084 ; WX 838 ; N uni2764 ; G 4076
+U 10085 ; WX 838 ; N uni2765 ; G 4077
+U 10086 ; WX 838 ; N uni2766 ; G 4078
+U 10087 ; WX 838 ; N uni2767 ; G 4079
+U 10088 ; WX 838 ; N uni2768 ; G 4080
+U 10089 ; WX 838 ; N uni2769 ; G 4081
+U 10090 ; WX 838 ; N uni276A ; G 4082
+U 10091 ; WX 838 ; N uni276B ; G 4083
+U 10092 ; WX 838 ; N uni276C ; G 4084
+U 10093 ; WX 838 ; N uni276D ; G 4085
+U 10094 ; WX 838 ; N uni276E ; G 4086
+U 10095 ; WX 838 ; N uni276F ; G 4087
+U 10096 ; WX 838 ; N uni2770 ; G 4088
+U 10097 ; WX 838 ; N uni2771 ; G 4089
+U 10098 ; WX 838 ; N uni2772 ; G 4090
+U 10099 ; WX 838 ; N uni2773 ; G 4091
+U 10100 ; WX 838 ; N uni2774 ; G 4092
+U 10101 ; WX 838 ; N uni2775 ; G 4093
+U 10102 ; WX 847 ; N uni2776 ; G 4094
+U 10103 ; WX 847 ; N uni2777 ; G 4095
+U 10104 ; WX 847 ; N uni2778 ; G 4096
+U 10105 ; WX 847 ; N uni2779 ; G 4097
+U 10106 ; WX 847 ; N uni277A ; G 4098
+U 10107 ; WX 847 ; N uni277B ; G 4099
+U 10108 ; WX 847 ; N uni277C ; G 4100
+U 10109 ; WX 847 ; N uni277D ; G 4101
+U 10110 ; WX 847 ; N uni277E ; G 4102
+U 10111 ; WX 847 ; N uni277F ; G 4103
+U 10112 ; WX 838 ; N uni2780 ; G 4104
+U 10113 ; WX 838 ; N uni2781 ; G 4105
+U 10114 ; WX 838 ; N uni2782 ; G 4106
+U 10115 ; WX 838 ; N uni2783 ; G 4107
+U 10116 ; WX 838 ; N uni2784 ; G 4108
+U 10117 ; WX 838 ; N uni2785 ; G 4109
+U 10118 ; WX 838 ; N uni2786 ; G 4110
+U 10119 ; WX 838 ; N uni2787 ; G 4111
+U 10120 ; WX 838 ; N uni2788 ; G 4112
+U 10121 ; WX 838 ; N uni2789 ; G 4113
+U 10122 ; WX 838 ; N uni278A ; G 4114
+U 10123 ; WX 838 ; N uni278B ; G 4115
+U 10124 ; WX 838 ; N uni278C ; G 4116
+U 10125 ; WX 838 ; N uni278D ; G 4117
+U 10126 ; WX 838 ; N uni278E ; G 4118
+U 10127 ; WX 838 ; N uni278F ; G 4119
+U 10128 ; WX 838 ; N uni2790 ; G 4120
+U 10129 ; WX 838 ; N uni2791 ; G 4121
+U 10130 ; WX 838 ; N uni2792 ; G 4122
+U 10131 ; WX 838 ; N uni2793 ; G 4123
+U 10132 ; WX 838 ; N uni2794 ; G 4124
+U 10136 ; WX 838 ; N uni2798 ; G 4125
+U 10137 ; WX 838 ; N uni2799 ; G 4126
+U 10138 ; WX 838 ; N uni279A ; G 4127
+U 10139 ; WX 838 ; N uni279B ; G 4128
+U 10140 ; WX 838 ; N uni279C ; G 4129
+U 10141 ; WX 838 ; N uni279D ; G 4130
+U 10142 ; WX 838 ; N uni279E ; G 4131
+U 10143 ; WX 838 ; N uni279F ; G 4132
+U 10144 ; WX 838 ; N uni27A0 ; G 4133
+U 10145 ; WX 838 ; N uni27A1 ; G 4134
+U 10146 ; WX 838 ; N uni27A2 ; G 4135
+U 10147 ; WX 838 ; N uni27A3 ; G 4136
+U 10148 ; WX 838 ; N uni27A4 ; G 4137
+U 10149 ; WX 838 ; N uni27A5 ; G 4138
+U 10150 ; WX 838 ; N uni27A6 ; G 4139
+U 10151 ; WX 838 ; N uni27A7 ; G 4140
+U 10152 ; WX 838 ; N uni27A8 ; G 4141
+U 10153 ; WX 838 ; N uni27A9 ; G 4142
+U 10154 ; WX 838 ; N uni27AA ; G 4143
+U 10155 ; WX 838 ; N uni27AB ; G 4144
+U 10156 ; WX 838 ; N uni27AC ; G 4145
+U 10157 ; WX 838 ; N uni27AD ; G 4146
+U 10158 ; WX 838 ; N uni27AE ; G 4147
+U 10159 ; WX 838 ; N uni27AF ; G 4148
+U 10161 ; WX 838 ; N uni27B1 ; G 4149
+U 10162 ; WX 838 ; N uni27B2 ; G 4150
+U 10163 ; WX 838 ; N uni27B3 ; G 4151
+U 10164 ; WX 838 ; N uni27B4 ; G 4152
+U 10165 ; WX 838 ; N uni27B5 ; G 4153
+U 10166 ; WX 838 ; N uni27B6 ; G 4154
+U 10167 ; WX 838 ; N uni27B7 ; G 4155
+U 10168 ; WX 838 ; N uni27B8 ; G 4156
+U 10169 ; WX 838 ; N uni27B9 ; G 4157
+U 10170 ; WX 838 ; N uni27BA ; G 4158
+U 10171 ; WX 838 ; N uni27BB ; G 4159
+U 10172 ; WX 838 ; N uni27BC ; G 4160
+U 10173 ; WX 838 ; N uni27BD ; G 4161
+U 10174 ; WX 838 ; N uni27BE ; G 4162
+U 10181 ; WX 457 ; N uni27C5 ; G 4163
+U 10182 ; WX 457 ; N uni27C6 ; G 4164
+U 10208 ; WX 494 ; N uni27E0 ; G 4165
+U 10214 ; WX 487 ; N uni27E6 ; G 4166
+U 10215 ; WX 487 ; N uni27E7 ; G 4167
+U 10216 ; WX 457 ; N uni27E8 ; G 4168
+U 10217 ; WX 457 ; N uni27E9 ; G 4169
+U 10218 ; WX 721 ; N uni27EA ; G 4170
+U 10219 ; WX 721 ; N uni27EB ; G 4171
+U 10224 ; WX 838 ; N uni27F0 ; G 4172
+U 10225 ; WX 838 ; N uni27F1 ; G 4173
+U 10226 ; WX 838 ; N uni27F2 ; G 4174
+U 10227 ; WX 838 ; N uni27F3 ; G 4175
+U 10228 ; WX 1157 ; N uni27F4 ; G 4176
+U 10229 ; WX 1434 ; N uni27F5 ; G 4177
+U 10230 ; WX 1434 ; N uni27F6 ; G 4178
+U 10231 ; WX 1434 ; N uni27F7 ; G 4179
+U 10232 ; WX 1434 ; N uni27F8 ; G 4180
+U 10233 ; WX 1434 ; N uni27F9 ; G 4181
+U 10234 ; WX 1434 ; N uni27FA ; G 4182
+U 10235 ; WX 1434 ; N uni27FB ; G 4183
+U 10236 ; WX 1434 ; N uni27FC ; G 4184
+U 10237 ; WX 1434 ; N uni27FD ; G 4185
+U 10238 ; WX 1434 ; N uni27FE ; G 4186
+U 10239 ; WX 1434 ; N uni27FF ; G 4187
+U 10240 ; WX 781 ; N uni2800 ; G 4188
+U 10241 ; WX 781 ; N uni2801 ; G 4189
+U 10242 ; WX 781 ; N uni2802 ; G 4190
+U 10243 ; WX 781 ; N uni2803 ; G 4191
+U 10244 ; WX 781 ; N uni2804 ; G 4192
+U 10245 ; WX 781 ; N uni2805 ; G 4193
+U 10246 ; WX 781 ; N uni2806 ; G 4194
+U 10247 ; WX 781 ; N uni2807 ; G 4195
+U 10248 ; WX 781 ; N uni2808 ; G 4196
+U 10249 ; WX 781 ; N uni2809 ; G 4197
+U 10250 ; WX 781 ; N uni280A ; G 4198
+U 10251 ; WX 781 ; N uni280B ; G 4199
+U 10252 ; WX 781 ; N uni280C ; G 4200
+U 10253 ; WX 781 ; N uni280D ; G 4201
+U 10254 ; WX 781 ; N uni280E ; G 4202
+U 10255 ; WX 781 ; N uni280F ; G 4203
+U 10256 ; WX 781 ; N uni2810 ; G 4204
+U 10257 ; WX 781 ; N uni2811 ; G 4205
+U 10258 ; WX 781 ; N uni2812 ; G 4206
+U 10259 ; WX 781 ; N uni2813 ; G 4207
+U 10260 ; WX 781 ; N uni2814 ; G 4208
+U 10261 ; WX 781 ; N uni2815 ; G 4209
+U 10262 ; WX 781 ; N uni2816 ; G 4210
+U 10263 ; WX 781 ; N uni2817 ; G 4211
+U 10264 ; WX 781 ; N uni2818 ; G 4212
+U 10265 ; WX 781 ; N uni2819 ; G 4213
+U 10266 ; WX 781 ; N uni281A ; G 4214
+U 10267 ; WX 781 ; N uni281B ; G 4215
+U 10268 ; WX 781 ; N uni281C ; G 4216
+U 10269 ; WX 781 ; N uni281D ; G 4217
+U 10270 ; WX 781 ; N uni281E ; G 4218
+U 10271 ; WX 781 ; N uni281F ; G 4219
+U 10272 ; WX 781 ; N uni2820 ; G 4220
+U 10273 ; WX 781 ; N uni2821 ; G 4221
+U 10274 ; WX 781 ; N uni2822 ; G 4222
+U 10275 ; WX 781 ; N uni2823 ; G 4223
+U 10276 ; WX 781 ; N uni2824 ; G 4224
+U 10277 ; WX 781 ; N uni2825 ; G 4225
+U 10278 ; WX 781 ; N uni2826 ; G 4226
+U 10279 ; WX 781 ; N uni2827 ; G 4227
+U 10280 ; WX 781 ; N uni2828 ; G 4228
+U 10281 ; WX 781 ; N uni2829 ; G 4229
+U 10282 ; WX 781 ; N uni282A ; G 4230
+U 10283 ; WX 781 ; N uni282B ; G 4231
+U 10284 ; WX 781 ; N uni282C ; G 4232
+U 10285 ; WX 781 ; N uni282D ; G 4233
+U 10286 ; WX 781 ; N uni282E ; G 4234
+U 10287 ; WX 781 ; N uni282F ; G 4235
+U 10288 ; WX 781 ; N uni2830 ; G 4236
+U 10289 ; WX 781 ; N uni2831 ; G 4237
+U 10290 ; WX 781 ; N uni2832 ; G 4238
+U 10291 ; WX 781 ; N uni2833 ; G 4239
+U 10292 ; WX 781 ; N uni2834 ; G 4240
+U 10293 ; WX 781 ; N uni2835 ; G 4241
+U 10294 ; WX 781 ; N uni2836 ; G 4242
+U 10295 ; WX 781 ; N uni2837 ; G 4243
+U 10296 ; WX 781 ; N uni2838 ; G 4244
+U 10297 ; WX 781 ; N uni2839 ; G 4245
+U 10298 ; WX 781 ; N uni283A ; G 4246
+U 10299 ; WX 781 ; N uni283B ; G 4247
+U 10300 ; WX 781 ; N uni283C ; G 4248
+U 10301 ; WX 781 ; N uni283D ; G 4249
+U 10302 ; WX 781 ; N uni283E ; G 4250
+U 10303 ; WX 781 ; N uni283F ; G 4251
+U 10304 ; WX 781 ; N uni2840 ; G 4252
+U 10305 ; WX 781 ; N uni2841 ; G 4253
+U 10306 ; WX 781 ; N uni2842 ; G 4254
+U 10307 ; WX 781 ; N uni2843 ; G 4255
+U 10308 ; WX 781 ; N uni2844 ; G 4256
+U 10309 ; WX 781 ; N uni2845 ; G 4257
+U 10310 ; WX 781 ; N uni2846 ; G 4258
+U 10311 ; WX 781 ; N uni2847 ; G 4259
+U 10312 ; WX 781 ; N uni2848 ; G 4260
+U 10313 ; WX 781 ; N uni2849 ; G 4261
+U 10314 ; WX 781 ; N uni284A ; G 4262
+U 10315 ; WX 781 ; N uni284B ; G 4263
+U 10316 ; WX 781 ; N uni284C ; G 4264
+U 10317 ; WX 781 ; N uni284D ; G 4265
+U 10318 ; WX 781 ; N uni284E ; G 4266
+U 10319 ; WX 781 ; N uni284F ; G 4267
+U 10320 ; WX 781 ; N uni2850 ; G 4268
+U 10321 ; WX 781 ; N uni2851 ; G 4269
+U 10322 ; WX 781 ; N uni2852 ; G 4270
+U 10323 ; WX 781 ; N uni2853 ; G 4271
+U 10324 ; WX 781 ; N uni2854 ; G 4272
+U 10325 ; WX 781 ; N uni2855 ; G 4273
+U 10326 ; WX 781 ; N uni2856 ; G 4274
+U 10327 ; WX 781 ; N uni2857 ; G 4275
+U 10328 ; WX 781 ; N uni2858 ; G 4276
+U 10329 ; WX 781 ; N uni2859 ; G 4277
+U 10330 ; WX 781 ; N uni285A ; G 4278
+U 10331 ; WX 781 ; N uni285B ; G 4279
+U 10332 ; WX 781 ; N uni285C ; G 4280
+U 10333 ; WX 781 ; N uni285D ; G 4281
+U 10334 ; WX 781 ; N uni285E ; G 4282
+U 10335 ; WX 781 ; N uni285F ; G 4283
+U 10336 ; WX 781 ; N uni2860 ; G 4284
+U 10337 ; WX 781 ; N uni2861 ; G 4285
+U 10338 ; WX 781 ; N uni2862 ; G 4286
+U 10339 ; WX 781 ; N uni2863 ; G 4287
+U 10340 ; WX 781 ; N uni2864 ; G 4288
+U 10341 ; WX 781 ; N uni2865 ; G 4289
+U 10342 ; WX 781 ; N uni2866 ; G 4290
+U 10343 ; WX 781 ; N uni2867 ; G 4291
+U 10344 ; WX 781 ; N uni2868 ; G 4292
+U 10345 ; WX 781 ; N uni2869 ; G 4293
+U 10346 ; WX 781 ; N uni286A ; G 4294
+U 10347 ; WX 781 ; N uni286B ; G 4295
+U 10348 ; WX 781 ; N uni286C ; G 4296
+U 10349 ; WX 781 ; N uni286D ; G 4297
+U 10350 ; WX 781 ; N uni286E ; G 4298
+U 10351 ; WX 781 ; N uni286F ; G 4299
+U 10352 ; WX 781 ; N uni2870 ; G 4300
+U 10353 ; WX 781 ; N uni2871 ; G 4301
+U 10354 ; WX 781 ; N uni2872 ; G 4302
+U 10355 ; WX 781 ; N uni2873 ; G 4303
+U 10356 ; WX 781 ; N uni2874 ; G 4304
+U 10357 ; WX 781 ; N uni2875 ; G 4305
+U 10358 ; WX 781 ; N uni2876 ; G 4306
+U 10359 ; WX 781 ; N uni2877 ; G 4307
+U 10360 ; WX 781 ; N uni2878 ; G 4308
+U 10361 ; WX 781 ; N uni2879 ; G 4309
+U 10362 ; WX 781 ; N uni287A ; G 4310
+U 10363 ; WX 781 ; N uni287B ; G 4311
+U 10364 ; WX 781 ; N uni287C ; G 4312
+U 10365 ; WX 781 ; N uni287D ; G 4313
+U 10366 ; WX 781 ; N uni287E ; G 4314
+U 10367 ; WX 781 ; N uni287F ; G 4315
+U 10368 ; WX 781 ; N uni2880 ; G 4316
+U 10369 ; WX 781 ; N uni2881 ; G 4317
+U 10370 ; WX 781 ; N uni2882 ; G 4318
+U 10371 ; WX 781 ; N uni2883 ; G 4319
+U 10372 ; WX 781 ; N uni2884 ; G 4320
+U 10373 ; WX 781 ; N uni2885 ; G 4321
+U 10374 ; WX 781 ; N uni2886 ; G 4322
+U 10375 ; WX 781 ; N uni2887 ; G 4323
+U 10376 ; WX 781 ; N uni2888 ; G 4324
+U 10377 ; WX 781 ; N uni2889 ; G 4325
+U 10378 ; WX 781 ; N uni288A ; G 4326
+U 10379 ; WX 781 ; N uni288B ; G 4327
+U 10380 ; WX 781 ; N uni288C ; G 4328
+U 10381 ; WX 781 ; N uni288D ; G 4329
+U 10382 ; WX 781 ; N uni288E ; G 4330
+U 10383 ; WX 781 ; N uni288F ; G 4331
+U 10384 ; WX 781 ; N uni2890 ; G 4332
+U 10385 ; WX 781 ; N uni2891 ; G 4333
+U 10386 ; WX 781 ; N uni2892 ; G 4334
+U 10387 ; WX 781 ; N uni2893 ; G 4335
+U 10388 ; WX 781 ; N uni2894 ; G 4336
+U 10389 ; WX 781 ; N uni2895 ; G 4337
+U 10390 ; WX 781 ; N uni2896 ; G 4338
+U 10391 ; WX 781 ; N uni2897 ; G 4339
+U 10392 ; WX 781 ; N uni2898 ; G 4340
+U 10393 ; WX 781 ; N uni2899 ; G 4341
+U 10394 ; WX 781 ; N uni289A ; G 4342
+U 10395 ; WX 781 ; N uni289B ; G 4343
+U 10396 ; WX 781 ; N uni289C ; G 4344
+U 10397 ; WX 781 ; N uni289D ; G 4345
+U 10398 ; WX 781 ; N uni289E ; G 4346
+U 10399 ; WX 781 ; N uni289F ; G 4347
+U 10400 ; WX 781 ; N uni28A0 ; G 4348
+U 10401 ; WX 781 ; N uni28A1 ; G 4349
+U 10402 ; WX 781 ; N uni28A2 ; G 4350
+U 10403 ; WX 781 ; N uni28A3 ; G 4351
+U 10404 ; WX 781 ; N uni28A4 ; G 4352
+U 10405 ; WX 781 ; N uni28A5 ; G 4353
+U 10406 ; WX 781 ; N uni28A6 ; G 4354
+U 10407 ; WX 781 ; N uni28A7 ; G 4355
+U 10408 ; WX 781 ; N uni28A8 ; G 4356
+U 10409 ; WX 781 ; N uni28A9 ; G 4357
+U 10410 ; WX 781 ; N uni28AA ; G 4358
+U 10411 ; WX 781 ; N uni28AB ; G 4359
+U 10412 ; WX 781 ; N uni28AC ; G 4360
+U 10413 ; WX 781 ; N uni28AD ; G 4361
+U 10414 ; WX 781 ; N uni28AE ; G 4362
+U 10415 ; WX 781 ; N uni28AF ; G 4363
+U 10416 ; WX 781 ; N uni28B0 ; G 4364
+U 10417 ; WX 781 ; N uni28B1 ; G 4365
+U 10418 ; WX 781 ; N uni28B2 ; G 4366
+U 10419 ; WX 781 ; N uni28B3 ; G 4367
+U 10420 ; WX 781 ; N uni28B4 ; G 4368
+U 10421 ; WX 781 ; N uni28B5 ; G 4369
+U 10422 ; WX 781 ; N uni28B6 ; G 4370
+U 10423 ; WX 781 ; N uni28B7 ; G 4371
+U 10424 ; WX 781 ; N uni28B8 ; G 4372
+U 10425 ; WX 781 ; N uni28B9 ; G 4373
+U 10426 ; WX 781 ; N uni28BA ; G 4374
+U 10427 ; WX 781 ; N uni28BB ; G 4375
+U 10428 ; WX 781 ; N uni28BC ; G 4376
+U 10429 ; WX 781 ; N uni28BD ; G 4377
+U 10430 ; WX 781 ; N uni28BE ; G 4378
+U 10431 ; WX 781 ; N uni28BF ; G 4379
+U 10432 ; WX 781 ; N uni28C0 ; G 4380
+U 10433 ; WX 781 ; N uni28C1 ; G 4381
+U 10434 ; WX 781 ; N uni28C2 ; G 4382
+U 10435 ; WX 781 ; N uni28C3 ; G 4383
+U 10436 ; WX 781 ; N uni28C4 ; G 4384
+U 10437 ; WX 781 ; N uni28C5 ; G 4385
+U 10438 ; WX 781 ; N uni28C6 ; G 4386
+U 10439 ; WX 781 ; N uni28C7 ; G 4387
+U 10440 ; WX 781 ; N uni28C8 ; G 4388
+U 10441 ; WX 781 ; N uni28C9 ; G 4389
+U 10442 ; WX 781 ; N uni28CA ; G 4390
+U 10443 ; WX 781 ; N uni28CB ; G 4391
+U 10444 ; WX 781 ; N uni28CC ; G 4392
+U 10445 ; WX 781 ; N uni28CD ; G 4393
+U 10446 ; WX 781 ; N uni28CE ; G 4394
+U 10447 ; WX 781 ; N uni28CF ; G 4395
+U 10448 ; WX 781 ; N uni28D0 ; G 4396
+U 10449 ; WX 781 ; N uni28D1 ; G 4397
+U 10450 ; WX 781 ; N uni28D2 ; G 4398
+U 10451 ; WX 781 ; N uni28D3 ; G 4399
+U 10452 ; WX 781 ; N uni28D4 ; G 4400
+U 10453 ; WX 781 ; N uni28D5 ; G 4401
+U 10454 ; WX 781 ; N uni28D6 ; G 4402
+U 10455 ; WX 781 ; N uni28D7 ; G 4403
+U 10456 ; WX 781 ; N uni28D8 ; G 4404
+U 10457 ; WX 781 ; N uni28D9 ; G 4405
+U 10458 ; WX 781 ; N uni28DA ; G 4406
+U 10459 ; WX 781 ; N uni28DB ; G 4407
+U 10460 ; WX 781 ; N uni28DC ; G 4408
+U 10461 ; WX 781 ; N uni28DD ; G 4409
+U 10462 ; WX 781 ; N uni28DE ; G 4410
+U 10463 ; WX 781 ; N uni28DF ; G 4411
+U 10464 ; WX 781 ; N uni28E0 ; G 4412
+U 10465 ; WX 781 ; N uni28E1 ; G 4413
+U 10466 ; WX 781 ; N uni28E2 ; G 4414
+U 10467 ; WX 781 ; N uni28E3 ; G 4415
+U 10468 ; WX 781 ; N uni28E4 ; G 4416
+U 10469 ; WX 781 ; N uni28E5 ; G 4417
+U 10470 ; WX 781 ; N uni28E6 ; G 4418
+U 10471 ; WX 781 ; N uni28E7 ; G 4419
+U 10472 ; WX 781 ; N uni28E8 ; G 4420
+U 10473 ; WX 781 ; N uni28E9 ; G 4421
+U 10474 ; WX 781 ; N uni28EA ; G 4422
+U 10475 ; WX 781 ; N uni28EB ; G 4423
+U 10476 ; WX 781 ; N uni28EC ; G 4424
+U 10477 ; WX 781 ; N uni28ED ; G 4425
+U 10478 ; WX 781 ; N uni28EE ; G 4426
+U 10479 ; WX 781 ; N uni28EF ; G 4427
+U 10480 ; WX 781 ; N uni28F0 ; G 4428
+U 10481 ; WX 781 ; N uni28F1 ; G 4429
+U 10482 ; WX 781 ; N uni28F2 ; G 4430
+U 10483 ; WX 781 ; N uni28F3 ; G 4431
+U 10484 ; WX 781 ; N uni28F4 ; G 4432
+U 10485 ; WX 781 ; N uni28F5 ; G 4433
+U 10486 ; WX 781 ; N uni28F6 ; G 4434
+U 10487 ; WX 781 ; N uni28F7 ; G 4435
+U 10488 ; WX 781 ; N uni28F8 ; G 4436
+U 10489 ; WX 781 ; N uni28F9 ; G 4437
+U 10490 ; WX 781 ; N uni28FA ; G 4438
+U 10491 ; WX 781 ; N uni28FB ; G 4439
+U 10492 ; WX 781 ; N uni28FC ; G 4440
+U 10493 ; WX 781 ; N uni28FD ; G 4441
+U 10494 ; WX 781 ; N uni28FE ; G 4442
+U 10495 ; WX 781 ; N uni28FF ; G 4443
+U 10502 ; WX 838 ; N uni2906 ; G 4444
+U 10503 ; WX 838 ; N uni2907 ; G 4445
+U 10506 ; WX 838 ; N uni290A ; G 4446
+U 10507 ; WX 838 ; N uni290B ; G 4447
+U 10560 ; WX 838 ; N uni2940 ; G 4448
+U 10561 ; WX 838 ; N uni2941 ; G 4449
+U 10627 ; WX 753 ; N uni2983 ; G 4450
+U 10628 ; WX 753 ; N uni2984 ; G 4451
+U 10702 ; WX 838 ; N uni29CE ; G 4452
+U 10703 ; WX 1046 ; N uni29CF ; G 4453
+U 10704 ; WX 1046 ; N uni29D0 ; G 4454
+U 10705 ; WX 1000 ; N uni29D1 ; G 4455
+U 10706 ; WX 1000 ; N uni29D2 ; G 4456
+U 10707 ; WX 1000 ; N uni29D3 ; G 4457
+U 10708 ; WX 1000 ; N uni29D4 ; G 4458
+U 10709 ; WX 1000 ; N uni29D5 ; G 4459
+U 10731 ; WX 494 ; N uni29EB ; G 4460
+U 10746 ; WX 838 ; N uni29FA ; G 4461
+U 10747 ; WX 838 ; N uni29FB ; G 4462
+U 10752 ; WX 1000 ; N uni2A00 ; G 4463
+U 10753 ; WX 1000 ; N uni2A01 ; G 4464
+U 10754 ; WX 1000 ; N uni2A02 ; G 4465
+U 10764 ; WX 1661 ; N uni2A0C ; G 4466
+U 10765 ; WX 563 ; N uni2A0D ; G 4467
+U 10766 ; WX 563 ; N uni2A0E ; G 4468
+U 10767 ; WX 563 ; N uni2A0F ; G 4469
+U 10768 ; WX 563 ; N uni2A10 ; G 4470
+U 10769 ; WX 563 ; N uni2A11 ; G 4471
+U 10770 ; WX 563 ; N uni2A12 ; G 4472
+U 10771 ; WX 563 ; N uni2A13 ; G 4473
+U 10772 ; WX 563 ; N uni2A14 ; G 4474
+U 10773 ; WX 563 ; N uni2A15 ; G 4475
+U 10774 ; WX 563 ; N uni2A16 ; G 4476
+U 10775 ; WX 563 ; N uni2A17 ; G 4477
+U 10776 ; WX 563 ; N uni2A18 ; G 4478
+U 10777 ; WX 563 ; N uni2A19 ; G 4479
+U 10778 ; WX 563 ; N uni2A1A ; G 4480
+U 10779 ; WX 563 ; N uni2A1B ; G 4481
+U 10780 ; WX 563 ; N uni2A1C ; G 4482
+U 10799 ; WX 838 ; N uni2A2F ; G 4483
+U 10858 ; WX 838 ; N uni2A6A ; G 4484
+U 10859 ; WX 838 ; N uni2A6B ; G 4485
+U 10877 ; WX 838 ; N uni2A7D ; G 4486
+U 10878 ; WX 838 ; N uni2A7E ; G 4487
+U 10879 ; WX 838 ; N uni2A7F ; G 4488
+U 10880 ; WX 838 ; N uni2A80 ; G 4489
+U 10881 ; WX 838 ; N uni2A81 ; G 4490
+U 10882 ; WX 838 ; N uni2A82 ; G 4491
+U 10883 ; WX 838 ; N uni2A83 ; G 4492
+U 10884 ; WX 838 ; N uni2A84 ; G 4493
+U 10885 ; WX 838 ; N uni2A85 ; G 4494
+U 10886 ; WX 838 ; N uni2A86 ; G 4495
+U 10887 ; WX 838 ; N uni2A87 ; G 4496
+U 10888 ; WX 838 ; N uni2A88 ; G 4497
+U 10889 ; WX 838 ; N uni2A89 ; G 4498
+U 10890 ; WX 838 ; N uni2A8A ; G 4499
+U 10891 ; WX 838 ; N uni2A8B ; G 4500
+U 10892 ; WX 838 ; N uni2A8C ; G 4501
+U 10893 ; WX 838 ; N uni2A8D ; G 4502
+U 10894 ; WX 838 ; N uni2A8E ; G 4503
+U 10895 ; WX 838 ; N uni2A8F ; G 4504
+U 10896 ; WX 838 ; N uni2A90 ; G 4505
+U 10897 ; WX 838 ; N uni2A91 ; G 4506
+U 10898 ; WX 838 ; N uni2A92 ; G 4507
+U 10899 ; WX 838 ; N uni2A93 ; G 4508
+U 10900 ; WX 838 ; N uni2A94 ; G 4509
+U 10901 ; WX 838 ; N uni2A95 ; G 4510
+U 10902 ; WX 838 ; N uni2A96 ; G 4511
+U 10903 ; WX 838 ; N uni2A97 ; G 4512
+U 10904 ; WX 838 ; N uni2A98 ; G 4513
+U 10905 ; WX 838 ; N uni2A99 ; G 4514
+U 10906 ; WX 838 ; N uni2A9A ; G 4515
+U 10907 ; WX 838 ; N uni2A9B ; G 4516
+U 10908 ; WX 838 ; N uni2A9C ; G 4517
+U 10909 ; WX 838 ; N uni2A9D ; G 4518
+U 10910 ; WX 838 ; N uni2A9E ; G 4519
+U 10911 ; WX 838 ; N uni2A9F ; G 4520
+U 10912 ; WX 838 ; N uni2AA0 ; G 4521
+U 10926 ; WX 838 ; N uni2AAE ; G 4522
+U 10927 ; WX 838 ; N uni2AAF ; G 4523
+U 10928 ; WX 838 ; N uni2AB0 ; G 4524
+U 10929 ; WX 838 ; N uni2AB1 ; G 4525
+U 10930 ; WX 838 ; N uni2AB2 ; G 4526
+U 10931 ; WX 838 ; N uni2AB3 ; G 4527
+U 10932 ; WX 838 ; N uni2AB4 ; G 4528
+U 10933 ; WX 838 ; N uni2AB5 ; G 4529
+U 10934 ; WX 838 ; N uni2AB6 ; G 4530
+U 10935 ; WX 838 ; N uni2AB7 ; G 4531
+U 10936 ; WX 838 ; N uni2AB8 ; G 4532
+U 10937 ; WX 838 ; N uni2AB9 ; G 4533
+U 10938 ; WX 838 ; N uni2ABA ; G 4534
+U 11001 ; WX 838 ; N uni2AF9 ; G 4535
+U 11002 ; WX 838 ; N uni2AFA ; G 4536
+U 11008 ; WX 838 ; N uni2B00 ; G 4537
+U 11009 ; WX 838 ; N uni2B01 ; G 4538
+U 11010 ; WX 838 ; N uni2B02 ; G 4539
+U 11011 ; WX 838 ; N uni2B03 ; G 4540
+U 11012 ; WX 838 ; N uni2B04 ; G 4541
+U 11013 ; WX 838 ; N uni2B05 ; G 4542
+U 11014 ; WX 838 ; N uni2B06 ; G 4543
+U 11015 ; WX 838 ; N uni2B07 ; G 4544
+U 11016 ; WX 838 ; N uni2B08 ; G 4545
+U 11017 ; WX 838 ; N uni2B09 ; G 4546
+U 11018 ; WX 838 ; N uni2B0A ; G 4547
+U 11019 ; WX 838 ; N uni2B0B ; G 4548
+U 11020 ; WX 838 ; N uni2B0C ; G 4549
+U 11021 ; WX 838 ; N uni2B0D ; G 4550
+U 11022 ; WX 838 ; N uni2B0E ; G 4551
+U 11023 ; WX 838 ; N uni2B0F ; G 4552
+U 11024 ; WX 838 ; N uni2B10 ; G 4553
+U 11025 ; WX 838 ; N uni2B11 ; G 4554
+U 11026 ; WX 945 ; N uni2B12 ; G 4555
+U 11027 ; WX 945 ; N uni2B13 ; G 4556
+U 11028 ; WX 945 ; N uni2B14 ; G 4557
+U 11029 ; WX 945 ; N uni2B15 ; G 4558
+U 11030 ; WX 769 ; N uni2B16 ; G 4559
+U 11031 ; WX 769 ; N uni2B17 ; G 4560
+U 11032 ; WX 769 ; N uni2B18 ; G 4561
+U 11033 ; WX 769 ; N uni2B19 ; G 4562
+U 11034 ; WX 945 ; N uni2B1A ; G 4563
+U 11039 ; WX 869 ; N uni2B1F ; G 4564
+U 11040 ; WX 869 ; N uni2B20 ; G 4565
+U 11041 ; WX 873 ; N uni2B21 ; G 4566
+U 11042 ; WX 873 ; N uni2B22 ; G 4567
+U 11043 ; WX 873 ; N uni2B23 ; G 4568
+U 11044 ; WX 1119 ; N uni2B24 ; G 4569
+U 11091 ; WX 869 ; N uni2B53 ; G 4570
+U 11092 ; WX 869 ; N uni2B54 ; G 4571
+U 11360 ; WX 637 ; N uni2C60 ; G 4572
+U 11361 ; WX 360 ; N uni2C61 ; G 4573
+U 11362 ; WX 637 ; N uni2C62 ; G 4574
+U 11363 ; WX 733 ; N uni2C63 ; G 4575
+U 11364 ; WX 770 ; N uni2C64 ; G 4576
+U 11365 ; WX 675 ; N uni2C65 ; G 4577
+U 11366 ; WX 478 ; N uni2C66 ; G 4578
+U 11367 ; WX 956 ; N uni2C67 ; G 4579
+U 11368 ; WX 712 ; N uni2C68 ; G 4580
+U 11369 ; WX 775 ; N uni2C69 ; G 4581
+U 11370 ; WX 665 ; N uni2C6A ; G 4582
+U 11371 ; WX 725 ; N uni2C6B ; G 4583
+U 11372 ; WX 582 ; N uni2C6C ; G 4584
+U 11373 ; WX 860 ; N uni2C6D ; G 4585
+U 11374 ; WX 995 ; N uni2C6E ; G 4586
+U 11375 ; WX 774 ; N uni2C6F ; G 4587
+U 11376 ; WX 860 ; N uni2C70 ; G 4588
+U 11377 ; WX 778 ; N uni2C71 ; G 4589
+U 11378 ; WX 1221 ; N uni2C72 ; G 4590
+U 11379 ; WX 1056 ; N uni2C73 ; G 4591
+U 11380 ; WX 652 ; N uni2C74 ; G 4592
+U 11381 ; WX 698 ; N uni2C75 ; G 4593
+U 11382 ; WX 565 ; N uni2C76 ; G 4594
+U 11383 ; WX 782 ; N uni2C77 ; G 4595
+U 11385 ; WX 538 ; N uni2C79 ; G 4596
+U 11386 ; WX 687 ; N uni2C7A ; G 4597
+U 11387 ; WX 559 ; N uni2C7B ; G 4598
+U 11388 ; WX 219 ; N uni2C7C ; G 4599
+U 11389 ; WX 487 ; N uni2C7D ; G 4600
+U 11390 ; WX 720 ; N uni2C7E ; G 4601
+U 11391 ; WX 725 ; N uni2C7F ; G 4602
+U 11520 ; WX 663 ; N uni2D00 ; G 4603
+U 11521 ; WX 676 ; N uni2D01 ; G 4604
+U 11522 ; WX 661 ; N uni2D02 ; G 4605
+U 11523 ; WX 629 ; N uni2D03 ; G 4606
+U 11524 ; WX 661 ; N uni2D04 ; G 4607
+U 11525 ; WX 1032 ; N uni2D05 ; G 4608
+U 11526 ; WX 718 ; N uni2D06 ; G 4609
+U 11527 ; WX 1032 ; N uni2D07 ; G 4610
+U 11528 ; WX 648 ; N uni2D08 ; G 4611
+U 11529 ; WX 667 ; N uni2D09 ; G 4612
+U 11530 ; WX 1032 ; N uni2D0A ; G 4613
+U 11531 ; WX 673 ; N uni2D0B ; G 4614
+U 11532 ; WX 677 ; N uni2D0C ; G 4615
+U 11533 ; WX 1036 ; N uni2D0D ; G 4616
+U 11534 ; WX 680 ; N uni2D0E ; G 4617
+U 11535 ; WX 886 ; N uni2D0F ; G 4618
+U 11536 ; WX 1032 ; N uni2D10 ; G 4619
+U 11537 ; WX 683 ; N uni2D11 ; G 4620
+U 11538 ; WX 674 ; N uni2D12 ; G 4621
+U 11539 ; WX 1035 ; N uni2D13 ; G 4622
+U 11540 ; WX 1033 ; N uni2D14 ; G 4623
+U 11541 ; WX 1027 ; N uni2D15 ; G 4624
+U 11542 ; WX 676 ; N uni2D16 ; G 4625
+U 11543 ; WX 673 ; N uni2D17 ; G 4626
+U 11544 ; WX 667 ; N uni2D18 ; G 4627
+U 11545 ; WX 667 ; N uni2D19 ; G 4628
+U 11546 ; WX 660 ; N uni2D1A ; G 4629
+U 11547 ; WX 671 ; N uni2D1B ; G 4630
+U 11548 ; WX 1039 ; N uni2D1C ; G 4631
+U 11549 ; WX 673 ; N uni2D1D ; G 4632
+U 11550 ; WX 692 ; N uni2D1E ; G 4633
+U 11551 ; WX 659 ; N uni2D1F ; G 4634
+U 11552 ; WX 1048 ; N uni2D20 ; G 4635
+U 11553 ; WX 660 ; N uni2D21 ; G 4636
+U 11554 ; WX 654 ; N uni2D22 ; G 4637
+U 11555 ; WX 670 ; N uni2D23 ; G 4638
+U 11556 ; WX 733 ; N uni2D24 ; G 4639
+U 11557 ; WX 1017 ; N uni2D25 ; G 4640
+U 11568 ; WX 691 ; N uni2D30 ; G 4641
+U 11569 ; WX 941 ; N uni2D31 ; G 4642
+U 11570 ; WX 941 ; N uni2D32 ; G 4643
+U 11571 ; WX 725 ; N uni2D33 ; G 4644
+U 11572 ; WX 725 ; N uni2D34 ; G 4645
+U 11573 ; WX 725 ; N uni2D35 ; G 4646
+U 11574 ; WX 676 ; N uni2D36 ; G 4647
+U 11575 ; WX 774 ; N uni2D37 ; G 4648
+U 11576 ; WX 774 ; N uni2D38 ; G 4649
+U 11577 ; WX 683 ; N uni2D39 ; G 4650
+U 11578 ; WX 683 ; N uni2D3A ; G 4651
+U 11579 ; WX 802 ; N uni2D3B ; G 4652
+U 11580 ; WX 989 ; N uni2D3C ; G 4653
+U 11581 ; WX 761 ; N uni2D3D ; G 4654
+U 11582 ; WX 623 ; N uni2D3E ; G 4655
+U 11583 ; WX 761 ; N uni2D3F ; G 4656
+U 11584 ; WX 941 ; N uni2D40 ; G 4657
+U 11585 ; WX 941 ; N uni2D41 ; G 4658
+U 11586 ; WX 373 ; N uni2D42 ; G 4659
+U 11587 ; WX 740 ; N uni2D43 ; G 4660
+U 11588 ; WX 837 ; N uni2D44 ; G 4661
+U 11589 ; WX 914 ; N uni2D45 ; G 4662
+U 11590 ; WX 672 ; N uni2D46 ; G 4663
+U 11591 ; WX 737 ; N uni2D47 ; G 4664
+U 11592 ; WX 680 ; N uni2D48 ; G 4665
+U 11593 ; WX 683 ; N uni2D49 ; G 4666
+U 11594 ; WX 602 ; N uni2D4A ; G 4667
+U 11595 ; WX 1039 ; N uni2D4B ; G 4668
+U 11596 ; WX 778 ; N uni2D4C ; G 4669
+U 11597 ; WX 837 ; N uni2D4D ; G 4670
+U 11598 ; WX 683 ; N uni2D4E ; G 4671
+U 11599 ; WX 372 ; N uni2D4F ; G 4672
+U 11600 ; WX 778 ; N uni2D50 ; G 4673
+U 11601 ; WX 373 ; N uni2D51 ; G 4674
+U 11602 ; WX 725 ; N uni2D52 ; G 4675
+U 11603 ; WX 691 ; N uni2D53 ; G 4676
+U 11604 ; WX 941 ; N uni2D54 ; G 4677
+U 11605 ; WX 941 ; N uni2D55 ; G 4678
+U 11606 ; WX 837 ; N uni2D56 ; G 4679
+U 11607 ; WX 373 ; N uni2D57 ; G 4680
+U 11608 ; WX 836 ; N uni2D58 ; G 4681
+U 11609 ; WX 941 ; N uni2D59 ; G 4682
+U 11610 ; WX 941 ; N uni2D5A ; G 4683
+U 11611 ; WX 734 ; N uni2D5B ; G 4684
+U 11612 ; WX 876 ; N uni2D5C ; G 4685
+U 11613 ; WX 771 ; N uni2D5D ; G 4686
+U 11614 ; WX 734 ; N uni2D5E ; G 4687
+U 11615 ; WX 683 ; N uni2D5F ; G 4688
+U 11616 ; WX 774 ; N uni2D60 ; G 4689
+U 11617 ; WX 837 ; N uni2D61 ; G 4690
+U 11618 ; WX 683 ; N uni2D62 ; G 4691
+U 11619 ; WX 850 ; N uni2D63 ; G 4692
+U 11620 ; WX 697 ; N uni2D64 ; G 4693
+U 11621 ; WX 850 ; N uni2D65 ; G 4694
+U 11631 ; WX 716 ; N uni2D6F ; G 4695
+U 11800 ; WX 580 ; N uni2E18 ; G 4696
+U 11807 ; WX 838 ; N uni2E1F ; G 4697
+U 11810 ; WX 457 ; N uni2E22 ; G 4698
+U 11811 ; WX 457 ; N uni2E23 ; G 4699
+U 11812 ; WX 457 ; N uni2E24 ; G 4700
+U 11813 ; WX 457 ; N uni2E25 ; G 4701
+U 11822 ; WX 580 ; N uni2E2E ; G 4702
+U 19904 ; WX 896 ; N uni4DC0 ; G 4703
+U 19905 ; WX 896 ; N uni4DC1 ; G 4704
+U 19906 ; WX 896 ; N uni4DC2 ; G 4705
+U 19907 ; WX 896 ; N uni4DC3 ; G 4706
+U 19908 ; WX 896 ; N uni4DC4 ; G 4707
+U 19909 ; WX 896 ; N uni4DC5 ; G 4708
+U 19910 ; WX 896 ; N uni4DC6 ; G 4709
+U 19911 ; WX 896 ; N uni4DC7 ; G 4710
+U 19912 ; WX 896 ; N uni4DC8 ; G 4711
+U 19913 ; WX 896 ; N uni4DC9 ; G 4712
+U 19914 ; WX 896 ; N uni4DCA ; G 4713
+U 19915 ; WX 896 ; N uni4DCB ; G 4714
+U 19916 ; WX 896 ; N uni4DCC ; G 4715
+U 19917 ; WX 896 ; N uni4DCD ; G 4716
+U 19918 ; WX 896 ; N uni4DCE ; G 4717
+U 19919 ; WX 896 ; N uni4DCF ; G 4718
+U 19920 ; WX 896 ; N uni4DD0 ; G 4719
+U 19921 ; WX 896 ; N uni4DD1 ; G 4720
+U 19922 ; WX 896 ; N uni4DD2 ; G 4721
+U 19923 ; WX 896 ; N uni4DD3 ; G 4722
+U 19924 ; WX 896 ; N uni4DD4 ; G 4723
+U 19925 ; WX 896 ; N uni4DD5 ; G 4724
+U 19926 ; WX 896 ; N uni4DD6 ; G 4725
+U 19927 ; WX 896 ; N uni4DD7 ; G 4726
+U 19928 ; WX 896 ; N uni4DD8 ; G 4727
+U 19929 ; WX 896 ; N uni4DD9 ; G 4728
+U 19930 ; WX 896 ; N uni4DDA ; G 4729
+U 19931 ; WX 896 ; N uni4DDB ; G 4730
+U 19932 ; WX 896 ; N uni4DDC ; G 4731
+U 19933 ; WX 896 ; N uni4DDD ; G 4732
+U 19934 ; WX 896 ; N uni4DDE ; G 4733
+U 19935 ; WX 896 ; N uni4DDF ; G 4734
+U 19936 ; WX 896 ; N uni4DE0 ; G 4735
+U 19937 ; WX 896 ; N uni4DE1 ; G 4736
+U 19938 ; WX 896 ; N uni4DE2 ; G 4737
+U 19939 ; WX 896 ; N uni4DE3 ; G 4738
+U 19940 ; WX 896 ; N uni4DE4 ; G 4739
+U 19941 ; WX 896 ; N uni4DE5 ; G 4740
+U 19942 ; WX 896 ; N uni4DE6 ; G 4741
+U 19943 ; WX 896 ; N uni4DE7 ; G 4742
+U 19944 ; WX 896 ; N uni4DE8 ; G 4743
+U 19945 ; WX 896 ; N uni4DE9 ; G 4744
+U 19946 ; WX 896 ; N uni4DEA ; G 4745
+U 19947 ; WX 896 ; N uni4DEB ; G 4746
+U 19948 ; WX 896 ; N uni4DEC ; G 4747
+U 19949 ; WX 896 ; N uni4DED ; G 4748
+U 19950 ; WX 896 ; N uni4DEE ; G 4749
+U 19951 ; WX 896 ; N uni4DEF ; G 4750
+U 19952 ; WX 896 ; N uni4DF0 ; G 4751
+U 19953 ; WX 896 ; N uni4DF1 ; G 4752
+U 19954 ; WX 896 ; N uni4DF2 ; G 4753
+U 19955 ; WX 896 ; N uni4DF3 ; G 4754
+U 19956 ; WX 896 ; N uni4DF4 ; G 4755
+U 19957 ; WX 896 ; N uni4DF5 ; G 4756
+U 19958 ; WX 896 ; N uni4DF6 ; G 4757
+U 19959 ; WX 896 ; N uni4DF7 ; G 4758
+U 19960 ; WX 896 ; N uni4DF8 ; G 4759
+U 19961 ; WX 896 ; N uni4DF9 ; G 4760
+U 19962 ; WX 896 ; N uni4DFA ; G 4761
+U 19963 ; WX 896 ; N uni4DFB ; G 4762
+U 19964 ; WX 896 ; N uni4DFC ; G 4763
+U 19965 ; WX 896 ; N uni4DFD ; G 4764
+U 19966 ; WX 896 ; N uni4DFE ; G 4765
+U 19967 ; WX 896 ; N uni4DFF ; G 4766
+U 42192 ; WX 762 ; N uniA4D0 ; G 4767
+U 42193 ; WX 733 ; N uniA4D1 ; G 4768
+U 42194 ; WX 733 ; N uniA4D2 ; G 4769
+U 42195 ; WX 830 ; N uniA4D3 ; G 4770
+U 42196 ; WX 682 ; N uniA4D4 ; G 4771
+U 42197 ; WX 682 ; N uniA4D5 ; G 4772
+U 42198 ; WX 821 ; N uniA4D6 ; G 4773
+U 42199 ; WX 775 ; N uniA4D7 ; G 4774
+U 42200 ; WX 775 ; N uniA4D8 ; G 4775
+U 42201 ; WX 530 ; N uniA4D9 ; G 4776
+U 42202 ; WX 734 ; N uniA4DA ; G 4777
+U 42203 ; WX 734 ; N uniA4DB ; G 4778
+U 42204 ; WX 725 ; N uniA4DC ; G 4779
+U 42205 ; WX 683 ; N uniA4DD ; G 4780
+U 42206 ; WX 683 ; N uniA4DE ; G 4781
+U 42207 ; WX 995 ; N uniA4DF ; G 4782
+U 42208 ; WX 837 ; N uniA4E0 ; G 4783
+U 42209 ; WX 637 ; N uniA4E1 ; G 4784
+U 42210 ; WX 720 ; N uniA4E2 ; G 4785
+U 42211 ; WX 770 ; N uniA4E3 ; G 4786
+U 42212 ; WX 770 ; N uniA4E4 ; G 4787
+U 42213 ; WX 774 ; N uniA4E5 ; G 4788
+U 42214 ; WX 774 ; N uniA4E6 ; G 4789
+U 42215 ; WX 837 ; N uniA4E7 ; G 4790
+U 42216 ; WX 775 ; N uniA4E8 ; G 4791
+U 42217 ; WX 530 ; N uniA4E9 ; G 4792
+U 42218 ; WX 1103 ; N uniA4EA ; G 4793
+U 42219 ; WX 771 ; N uniA4EB ; G 4794
+U 42220 ; WX 724 ; N uniA4EC ; G 4795
+U 42221 ; WX 762 ; N uniA4ED ; G 4796
+U 42222 ; WX 774 ; N uniA4EE ; G 4797
+U 42223 ; WX 774 ; N uniA4EF ; G 4798
+U 42224 ; WX 683 ; N uniA4F0 ; G 4799
+U 42225 ; WX 683 ; N uniA4F1 ; G 4800
+U 42226 ; WX 372 ; N uniA4F2 ; G 4801
+U 42227 ; WX 850 ; N uniA4F3 ; G 4802
+U 42228 ; WX 812 ; N uniA4F4 ; G 4803
+U 42229 ; WX 812 ; N uniA4F5 ; G 4804
+U 42230 ; WX 557 ; N uniA4F6 ; G 4805
+U 42231 ; WX 830 ; N uniA4F7 ; G 4806
+U 42232 ; WX 322 ; N uniA4F8 ; G 4807
+U 42233 ; WX 322 ; N uniA4F9 ; G 4808
+U 42234 ; WX 674 ; N uniA4FA ; G 4809
+U 42235 ; WX 674 ; N uniA4FB ; G 4810
+U 42236 ; WX 322 ; N uniA4FC ; G 4811
+U 42237 ; WX 322 ; N uniA4FD ; G 4812
+U 42238 ; WX 588 ; N uniA4FE ; G 4813
+U 42239 ; WX 588 ; N uniA4FF ; G 4814
+U 42564 ; WX 720 ; N uniA644 ; G 4815
+U 42565 ; WX 595 ; N uniA645 ; G 4816
+U 42566 ; WX 436 ; N uniA646 ; G 4817
+U 42567 ; WX 440 ; N uniA647 ; G 4818
+U 42572 ; WX 1405 ; N uniA64C ; G 4819
+U 42573 ; WX 1173 ; N uniA64D ; G 4820
+U 42576 ; WX 1234 ; N uniA650 ; G 4821
+U 42577 ; WX 1027 ; N uniA651 ; G 4822
+U 42580 ; WX 1174 ; N uniA654 ; G 4823
+U 42581 ; WX 972 ; N uniA655 ; G 4824
+U 42582 ; WX 1093 ; N uniA656 ; G 4825
+U 42583 ; WX 958 ; N uniA657 ; G 4826
+U 42594 ; WX 1085 ; N uniA662 ; G 4827
+U 42595 ; WX 924 ; N uniA663 ; G 4828
+U 42596 ; WX 1096 ; N uniA664 ; G 4829
+U 42597 ; WX 912 ; N uniA665 ; G 4830
+U 42598 ; WX 1260 ; N uniA666 ; G 4831
+U 42599 ; WX 997 ; N uniA667 ; G 4832
+U 42600 ; WX 850 ; N uniA668 ; G 4833
+U 42601 ; WX 687 ; N uniA669 ; G 4834
+U 42602 ; WX 1037 ; N uniA66A ; G 4835
+U 42603 ; WX 868 ; N uniA66B ; G 4836
+U 42604 ; WX 1406 ; N uniA66C ; G 4837
+U 42605 ; WX 1106 ; N uniA66D ; G 4838
+U 42606 ; WX 961 ; N uniA66E ; G 4839
+U 42634 ; WX 963 ; N uniA68A ; G 4840
+U 42635 ; WX 787 ; N uniA68B ; G 4841
+U 42636 ; WX 682 ; N uniA68C ; G 4842
+U 42637 ; WX 580 ; N uniA68D ; G 4843
+U 42644 ; WX 808 ; N uniA694 ; G 4844
+U 42645 ; WX 712 ; N uniA695 ; G 4845
+U 42648 ; WX 1406 ; N uniA698 ; G 4846
+U 42649 ; WX 1106 ; N uniA699 ; G 4847
+U 42760 ; WX 500 ; N uniA708 ; G 4848
+U 42761 ; WX 500 ; N uniA709 ; G 4849
+U 42762 ; WX 500 ; N uniA70A ; G 4850
+U 42763 ; WX 500 ; N uniA70B ; G 4851
+U 42764 ; WX 500 ; N uniA70C ; G 4852
+U 42765 ; WX 500 ; N uniA70D ; G 4853
+U 42766 ; WX 500 ; N uniA70E ; G 4854
+U 42767 ; WX 500 ; N uniA70F ; G 4855
+U 42768 ; WX 500 ; N uniA710 ; G 4856
+U 42769 ; WX 500 ; N uniA711 ; G 4857
+U 42770 ; WX 500 ; N uniA712 ; G 4858
+U 42771 ; WX 500 ; N uniA713 ; G 4859
+U 42772 ; WX 500 ; N uniA714 ; G 4860
+U 42773 ; WX 500 ; N uniA715 ; G 4861
+U 42774 ; WX 500 ; N uniA716 ; G 4862
+U 42779 ; WX 400 ; N uniA71B ; G 4863
+U 42780 ; WX 400 ; N uniA71C ; G 4864
+U 42781 ; WX 287 ; N uniA71D ; G 4865
+U 42782 ; WX 287 ; N uniA71E ; G 4866
+U 42783 ; WX 287 ; N uniA71F ; G 4867
+U 42786 ; WX 444 ; N uniA722 ; G 4868
+U 42787 ; WX 390 ; N uniA723 ; G 4869
+U 42788 ; WX 540 ; N uniA724 ; G 4870
+U 42789 ; WX 540 ; N uniA725 ; G 4871
+U 42790 ; WX 837 ; N uniA726 ; G 4872
+U 42791 ; WX 712 ; N uniA727 ; G 4873
+U 42792 ; WX 1031 ; N uniA728 ; G 4874
+U 42793 ; WX 857 ; N uniA729 ; G 4875
+U 42794 ; WX 696 ; N uniA72A ; G 4876
+U 42795 ; WX 557 ; N uniA72B ; G 4877
+U 42800 ; WX 559 ; N uniA730 ; G 4878
+U 42801 ; WX 595 ; N uniA731 ; G 4879
+U 42802 ; WX 1349 ; N uniA732 ; G 4880
+U 42803 ; WX 1052 ; N uniA733 ; G 4881
+U 42804 ; WX 1284 ; N uniA734 ; G 4882
+U 42805 ; WX 1064 ; N uniA735 ; G 4883
+U 42806 ; WX 1216 ; N uniA736 ; G 4884
+U 42807 ; WX 1054 ; N uniA737 ; G 4885
+U 42808 ; WX 1079 ; N uniA738 ; G 4886
+U 42809 ; WX 922 ; N uniA739 ; G 4887
+U 42810 ; WX 1079 ; N uniA73A ; G 4888
+U 42811 ; WX 922 ; N uniA73B ; G 4889
+U 42812 ; WX 1035 ; N uniA73C ; G 4890
+U 42813 ; WX 922 ; N uniA73D ; G 4891
+U 42814 ; WX 698 ; N uniA73E ; G 4892
+U 42815 ; WX 549 ; N uniA73F ; G 4893
+U 42816 ; WX 656 ; N uniA740 ; G 4894
+U 42817 ; WX 688 ; N uniA741 ; G 4895
+U 42822 ; WX 850 ; N uniA746 ; G 4896
+U 42823 ; WX 542 ; N uniA747 ; G 4897
+U 42824 ; WX 683 ; N uniA748 ; G 4898
+U 42825 ; WX 531 ; N uniA749 ; G 4899
+U 42826 ; WX 918 ; N uniA74A ; G 4900
+U 42827 ; WX 814 ; N uniA74B ; G 4901
+U 42830 ; WX 1406 ; N uniA74E ; G 4902
+U 42831 ; WX 1106 ; N uniA74F ; G 4903
+U 42832 ; WX 733 ; N uniA750 ; G 4904
+U 42833 ; WX 716 ; N uniA751 ; G 4905
+U 42834 ; WX 948 ; N uniA752 ; G 4906
+U 42835 ; WX 937 ; N uniA753 ; G 4907
+U 42838 ; WX 850 ; N uniA756 ; G 4908
+U 42839 ; WX 716 ; N uniA757 ; G 4909
+U 42852 ; WX 738 ; N uniA764 ; G 4910
+U 42853 ; WX 716 ; N uniA765 ; G 4911
+U 42854 ; WX 738 ; N uniA766 ; G 4912
+U 42855 ; WX 716 ; N uniA767 ; G 4913
+U 42880 ; WX 637 ; N uniA780 ; G 4914
+U 42881 ; WX 343 ; N uniA781 ; G 4915
+U 42882 ; WX 837 ; N uniA782 ; G 4916
+U 42883 ; WX 712 ; N uniA783 ; G 4917
+U 42889 ; WX 400 ; N uniA789 ; G 4918
+U 42890 ; WX 386 ; N uniA78A ; G 4919
+U 42891 ; WX 456 ; N uniA78B ; G 4920
+U 42892 ; WX 306 ; N uniA78C ; G 4921
+U 42893 ; WX 808 ; N uniA78D ; G 4922
+U 42894 ; WX 693 ; N uniA78E ; G 4923
+U 42896 ; WX 928 ; N uniA790 ; G 4924
+U 42897 ; WX 768 ; N uniA791 ; G 4925
+U 42912 ; WX 821 ; N uniA7A0 ; G 4926
+U 42913 ; WX 716 ; N uniA7A1 ; G 4927
+U 42914 ; WX 775 ; N uniA7A2 ; G 4928
+U 42915 ; WX 665 ; N uniA7A3 ; G 4929
+U 42916 ; WX 837 ; N uniA7A4 ; G 4930
+U 42917 ; WX 712 ; N uniA7A5 ; G 4931
+U 42918 ; WX 770 ; N uniA7A6 ; G 4932
+U 42919 ; WX 493 ; N uniA7A7 ; G 4933
+U 42920 ; WX 720 ; N uniA7A8 ; G 4934
+U 42921 ; WX 595 ; N uniA7A9 ; G 4935
+U 42922 ; WX 886 ; N uniA7AA ; G 4936
+U 43000 ; WX 613 ; N uniA7F8 ; G 4937
+U 43001 ; WX 689 ; N uniA7F9 ; G 4938
+U 43002 ; WX 1062 ; N uniA7FA ; G 4939
+U 43003 ; WX 683 ; N uniA7FB ; G 4940
+U 43004 ; WX 733 ; N uniA7FC ; G 4941
+U 43005 ; WX 995 ; N uniA7FD ; G 4942
+U 43006 ; WX 372 ; N uniA7FE ; G 4943
+U 43007 ; WX 1325 ; N uniA7FF ; G 4944
+U 61184 ; WX 216 ; N uni02E5.5 ; G 4945
+U 61185 ; WX 242 ; N uni02E6.5 ; G 4946
+U 61186 ; WX 267 ; N uni02E7.5 ; G 4947
+U 61187 ; WX 277 ; N uni02E8.5 ; G 4948
+U 61188 ; WX 282 ; N uni02E9.5 ; G 4949
+U 61189 ; WX 242 ; N uni02E5.4 ; G 4950
+U 61190 ; WX 216 ; N uni02E6.4 ; G 4951
+U 61191 ; WX 242 ; N uni02E7.4 ; G 4952
+U 61192 ; WX 267 ; N uni02E8.4 ; G 4953
+U 61193 ; WX 277 ; N uni02E9.4 ; G 4954
+U 61194 ; WX 267 ; N uni02E5.3 ; G 4955
+U 61195 ; WX 242 ; N uni02E6.3 ; G 4956
+U 61196 ; WX 216 ; N uni02E7.3 ; G 4957
+U 61197 ; WX 242 ; N uni02E8.3 ; G 4958
+U 61198 ; WX 267 ; N uni02E9.3 ; G 4959
+U 61199 ; WX 277 ; N uni02E5.2 ; G 4960
+U 61200 ; WX 267 ; N uni02E6.2 ; G 4961
+U 61201 ; WX 242 ; N uni02E7.2 ; G 4962
+U 61202 ; WX 216 ; N uni02E8.2 ; G 4963
+U 61203 ; WX 242 ; N uni02E9.2 ; G 4964
+U 61204 ; WX 282 ; N uni02E5.1 ; G 4965
+U 61205 ; WX 277 ; N uni02E6.1 ; G 4966
+U 61206 ; WX 267 ; N uni02E7.1 ; G 4967
+U 61207 ; WX 242 ; N uni02E8.1 ; G 4968
+U 61208 ; WX 216 ; N uni02E9.1 ; G 4969
+U 61209 ; WX 282 ; N stem ; G 4970
+U 62464 ; WX 612 ; N uniF400 ; G 4971
+U 62465 ; WX 612 ; N uniF401 ; G 4972
+U 62466 ; WX 653 ; N uniF402 ; G 4973
+U 62467 ; WX 902 ; N uniF403 ; G 4974
+U 62468 ; WX 622 ; N uniF404 ; G 4975
+U 62469 ; WX 622 ; N uniF405 ; G 4976
+U 62470 ; WX 661 ; N uniF406 ; G 4977
+U 62471 ; WX 895 ; N uniF407 ; G 4978
+U 62472 ; WX 589 ; N uniF408 ; G 4979
+U 62473 ; WX 622 ; N uniF409 ; G 4980
+U 62474 ; WX 1163 ; N uniF40A ; G 4981
+U 62475 ; WX 626 ; N uniF40B ; G 4982
+U 62476 ; WX 627 ; N uniF40C ; G 4983
+U 62477 ; WX 893 ; N uniF40D ; G 4984
+U 62478 ; WX 612 ; N uniF40E ; G 4985
+U 62479 ; WX 626 ; N uniF40F ; G 4986
+U 62480 ; WX 924 ; N uniF410 ; G 4987
+U 62481 ; WX 627 ; N uniF411 ; G 4988
+U 62482 ; WX 744 ; N uniF412 ; G 4989
+U 62483 ; WX 634 ; N uniF413 ; G 4990
+U 62484 ; WX 886 ; N uniF414 ; G 4991
+U 62485 ; WX 626 ; N uniF415 ; G 4992
+U 62486 ; WX 907 ; N uniF416 ; G 4993
+U 62487 ; WX 626 ; N uniF417 ; G 4994
+U 62488 ; WX 621 ; N uniF418 ; G 4995
+U 62489 ; WX 628 ; N uniF419 ; G 4996
+U 62490 ; WX 677 ; N uniF41A ; G 4997
+U 62491 ; WX 626 ; N uniF41B ; G 4998
+U 62492 ; WX 621 ; N uniF41C ; G 4999
+U 62493 ; WX 630 ; N uniF41D ; G 5000
+U 62494 ; WX 627 ; N uniF41E ; G 5001
+U 62495 ; WX 571 ; N uniF41F ; G 5002
+U 62496 ; WX 622 ; N uniF420 ; G 5003
+U 62497 ; WX 631 ; N uniF421 ; G 5004
+U 62498 ; WX 612 ; N uniF422 ; G 5005
+U 62499 ; WX 611 ; N uniF423 ; G 5006
+U 62500 ; WX 618 ; N uniF424 ; G 5007
+U 62501 ; WX 671 ; N uniF425 ; G 5008
+U 62502 ; WX 963 ; N uniF426 ; G 5009
+U 62504 ; WX 1023 ; N uniF428 ; G 5010
+U 62505 ; WX 844 ; N uniF429 ; G 5011
+U 62506 ; WX 563 ; N uniF42A ; G 5012
+U 62507 ; WX 563 ; N uniF42B ; G 5013
+U 62508 ; WX 563 ; N uniF42C ; G 5014
+U 62509 ; WX 563 ; N uniF42D ; G 5015
+U 62510 ; WX 563 ; N uniF42E ; G 5016
+U 62511 ; WX 563 ; N uniF42F ; G 5017
+U 62512 ; WX 555 ; N uniF430 ; G 5018
+U 62513 ; WX 555 ; N uniF431 ; G 5019
+U 62514 ; WX 555 ; N uniF432 ; G 5020
+U 62515 ; WX 555 ; N uniF433 ; G 5021
+U 62516 ; WX 573 ; N uniF434 ; G 5022
+U 62517 ; WX 573 ; N uniF435 ; G 5023
+U 62518 ; WX 573 ; N uniF436 ; G 5024
+U 62519 ; WX 824 ; N uniF437 ; G 5025
+U 62520 ; WX 824 ; N uniF438 ; G 5026
+U 62521 ; WX 824 ; N uniF439 ; G 5027
+U 62522 ; WX 824 ; N uniF43A ; G 5028
+U 62523 ; WX 824 ; N uniF43B ; G 5029
+U 62524 ; WX 611 ; N uniF43C ; G 5030
+U 62525 ; WX 611 ; N uniF43D ; G 5031
+U 62526 ; WX 611 ; N uniF43E ; G 5032
+U 62527 ; WX 611 ; N uniF43F ; G 5033
+U 62528 ; WX 611 ; N uniF440 ; G 5034
+U 62529 ; WX 611 ; N uniF441 ; G 5035
+U 63173 ; WX 687 ; N uniF6C5 ; G 5036
+U 64256 ; WX 810 ; N uniFB00 ; G 5037
+U 64257 ; WX 741 ; N fi ; G 5038
+U 64258 ; WX 741 ; N fl ; G 5039
+U 64259 ; WX 1115 ; N uniFB03 ; G 5040
+U 64260 ; WX 1116 ; N uniFB04 ; G 5041
+U 64261 ; WX 808 ; N uniFB05 ; G 5042
+U 64262 ; WX 1020 ; N uniFB06 ; G 5043
+U 64275 ; WX 1388 ; N uniFB13 ; G 5044
+U 64276 ; WX 1384 ; N uniFB14 ; G 5045
+U 64277 ; WX 1378 ; N uniFB15 ; G 5046
+U 64278 ; WX 1384 ; N uniFB16 ; G 5047
+U 64279 ; WX 1713 ; N uniFB17 ; G 5048
+U 64285 ; WX 294 ; N uniFB1D ; G 5049
+U 64286 ; WX 0 ; N uniFB1E ; G 5050
+U 64287 ; WX 519 ; N uniFB1F ; G 5051
+U 64288 ; WX 665 ; N uniFB20 ; G 5052
+U 64289 ; WX 939 ; N uniFB21 ; G 5053
+U 64290 ; WX 788 ; N uniFB22 ; G 5054
+U 64291 ; WX 920 ; N uniFB23 ; G 5055
+U 64292 ; WX 786 ; N uniFB24 ; G 5056
+U 64293 ; WX 857 ; N uniFB25 ; G 5057
+U 64294 ; WX 869 ; N uniFB26 ; G 5058
+U 64295 ; WX 821 ; N uniFB27 ; G 5059
+U 64296 ; WX 890 ; N uniFB28 ; G 5060
+U 64297 ; WX 838 ; N uniFB29 ; G 5061
+U 64298 ; WX 758 ; N uniFB2A ; G 5062
+U 64299 ; WX 758 ; N uniFB2B ; G 5063
+U 64300 ; WX 758 ; N uniFB2C ; G 5064
+U 64301 ; WX 758 ; N uniFB2D ; G 5065
+U 64302 ; WX 728 ; N uniFB2E ; G 5066
+U 64303 ; WX 728 ; N uniFB2F ; G 5067
+U 64304 ; WX 728 ; N uniFB30 ; G 5068
+U 64305 ; WX 610 ; N uniFB31 ; G 5069
+U 64306 ; WX 447 ; N uniFB32 ; G 5070
+U 64307 ; WX 588 ; N uniFB33 ; G 5071
+U 64308 ; WX 687 ; N uniFB34 ; G 5072
+U 64309 ; WX 437 ; N uniFB35 ; G 5073
+U 64310 ; WX 485 ; N uniFB36 ; G 5074
+U 64312 ; WX 679 ; N uniFB38 ; G 5075
+U 64313 ; WX 435 ; N uniFB39 ; G 5076
+U 64314 ; WX 578 ; N uniFB3A ; G 5077
+U 64315 ; WX 566 ; N uniFB3B ; G 5078
+U 64316 ; WX 605 ; N uniFB3C ; G 5079
+U 64318 ; WX 724 ; N uniFB3E ; G 5080
+U 64320 ; WX 453 ; N uniFB40 ; G 5081
+U 64321 ; WX 680 ; N uniFB41 ; G 5082
+U 64323 ; WX 675 ; N uniFB43 ; G 5083
+U 64324 ; WX 658 ; N uniFB44 ; G 5084
+U 64326 ; WX 653 ; N uniFB46 ; G 5085
+U 64327 ; WX 736 ; N uniFB47 ; G 5086
+U 64328 ; WX 602 ; N uniFB48 ; G 5087
+U 64329 ; WX 758 ; N uniFB49 ; G 5088
+U 64330 ; WX 683 ; N uniFB4A ; G 5089
+U 64331 ; WX 343 ; N uniFB4B ; G 5090
+U 64332 ; WX 610 ; N uniFB4C ; G 5091
+U 64333 ; WX 566 ; N uniFB4D ; G 5092
+U 64334 ; WX 658 ; N uniFB4E ; G 5093
+U 64335 ; WX 710 ; N uniFB4F ; G 5094
+U 64338 ; WX 1005 ; N uniFB52 ; G 5095
+U 64339 ; WX 1059 ; N uniFB53 ; G 5096
+U 64340 ; WX 375 ; N uniFB54 ; G 5097
+U 64341 ; WX 408 ; N uniFB55 ; G 5098
+U 64342 ; WX 1005 ; N uniFB56 ; G 5099
+U 64343 ; WX 1059 ; N uniFB57 ; G 5100
+U 64344 ; WX 375 ; N uniFB58 ; G 5101
+U 64345 ; WX 408 ; N uniFB59 ; G 5102
+U 64346 ; WX 1005 ; N uniFB5A ; G 5103
+U 64347 ; WX 1059 ; N uniFB5B ; G 5104
+U 64348 ; WX 375 ; N uniFB5C ; G 5105
+U 64349 ; WX 408 ; N uniFB5D ; G 5106
+U 64350 ; WX 1005 ; N uniFB5E ; G 5107
+U 64351 ; WX 1059 ; N uniFB5F ; G 5108
+U 64352 ; WX 375 ; N uniFB60 ; G 5109
+U 64353 ; WX 408 ; N uniFB61 ; G 5110
+U 64354 ; WX 1005 ; N uniFB62 ; G 5111
+U 64355 ; WX 1059 ; N uniFB63 ; G 5112
+U 64356 ; WX 375 ; N uniFB64 ; G 5113
+U 64357 ; WX 408 ; N uniFB65 ; G 5114
+U 64358 ; WX 1005 ; N uniFB66 ; G 5115
+U 64359 ; WX 1059 ; N uniFB67 ; G 5116
+U 64360 ; WX 375 ; N uniFB68 ; G 5117
+U 64361 ; WX 408 ; N uniFB69 ; G 5118
+U 64362 ; WX 1162 ; N uniFB6A ; G 5119
+U 64363 ; WX 1191 ; N uniFB6B ; G 5120
+U 64364 ; WX 655 ; N uniFB6C ; G 5121
+U 64365 ; WX 720 ; N uniFB6D ; G 5122
+U 64366 ; WX 1162 ; N uniFB6E ; G 5123
+U 64367 ; WX 1191 ; N uniFB6F ; G 5124
+U 64368 ; WX 655 ; N uniFB70 ; G 5125
+U 64369 ; WX 720 ; N uniFB71 ; G 5126
+U 64370 ; WX 721 ; N uniFB72 ; G 5127
+U 64371 ; WX 721 ; N uniFB73 ; G 5128
+U 64372 ; WX 721 ; N uniFB74 ; G 5129
+U 64373 ; WX 721 ; N uniFB75 ; G 5130
+U 64374 ; WX 721 ; N uniFB76 ; G 5131
+U 64375 ; WX 721 ; N uniFB77 ; G 5132
+U 64376 ; WX 721 ; N uniFB78 ; G 5133
+U 64377 ; WX 721 ; N uniFB79 ; G 5134
+U 64378 ; WX 721 ; N uniFB7A ; G 5135
+U 64379 ; WX 721 ; N uniFB7B ; G 5136
+U 64380 ; WX 721 ; N uniFB7C ; G 5137
+U 64381 ; WX 721 ; N uniFB7D ; G 5138
+U 64382 ; WX 721 ; N uniFB7E ; G 5139
+U 64383 ; WX 721 ; N uniFB7F ; G 5140
+U 64384 ; WX 721 ; N uniFB80 ; G 5141
+U 64385 ; WX 721 ; N uniFB81 ; G 5142
+U 64386 ; WX 513 ; N uniFB82 ; G 5143
+U 64387 ; WX 578 ; N uniFB83 ; G 5144
+U 64388 ; WX 513 ; N uniFB84 ; G 5145
+U 64389 ; WX 578 ; N uniFB85 ; G 5146
+U 64390 ; WX 513 ; N uniFB86 ; G 5147
+U 64391 ; WX 578 ; N uniFB87 ; G 5148
+U 64392 ; WX 513 ; N uniFB88 ; G 5149
+U 64393 ; WX 578 ; N uniFB89 ; G 5150
+U 64394 ; WX 576 ; N uniFB8A ; G 5151
+U 64395 ; WX 622 ; N uniFB8B ; G 5152
+U 64396 ; WX 576 ; N uniFB8C ; G 5153
+U 64397 ; WX 622 ; N uniFB8D ; G 5154
+U 64398 ; WX 1024 ; N uniFB8E ; G 5155
+U 64399 ; WX 1024 ; N uniFB8F ; G 5156
+U 64400 ; WX 582 ; N uniFB90 ; G 5157
+U 64401 ; WX 582 ; N uniFB91 ; G 5158
+U 64402 ; WX 1024 ; N uniFB92 ; G 5159
+U 64403 ; WX 1024 ; N uniFB93 ; G 5160
+U 64404 ; WX 582 ; N uniFB94 ; G 5161
+U 64405 ; WX 582 ; N uniFB95 ; G 5162
+U 64406 ; WX 1024 ; N uniFB96 ; G 5163
+U 64407 ; WX 1024 ; N uniFB97 ; G 5164
+U 64408 ; WX 582 ; N uniFB98 ; G 5165
+U 64409 ; WX 582 ; N uniFB99 ; G 5166
+U 64410 ; WX 1024 ; N uniFB9A ; G 5167
+U 64411 ; WX 1024 ; N uniFB9B ; G 5168
+U 64412 ; WX 582 ; N uniFB9C ; G 5169
+U 64413 ; WX 582 ; N uniFB9D ; G 5170
+U 64414 ; WX 854 ; N uniFB9E ; G 5171
+U 64415 ; WX 900 ; N uniFB9F ; G 5172
+U 64416 ; WX 854 ; N uniFBA0 ; G 5173
+U 64417 ; WX 900 ; N uniFBA1 ; G 5174
+U 64418 ; WX 375 ; N uniFBA2 ; G 5175
+U 64419 ; WX 408 ; N uniFBA3 ; G 5176
+U 64426 ; WX 938 ; N uniFBAA ; G 5177
+U 64427 ; WX 880 ; N uniFBAB ; G 5178
+U 64428 ; WX 693 ; N uniFBAC ; G 5179
+U 64429 ; WX 660 ; N uniFBAD ; G 5180
+U 64467 ; WX 824 ; N uniFBD3 ; G 5181
+U 64468 ; WX 843 ; N uniFBD4 ; G 5182
+U 64469 ; WX 476 ; N uniFBD5 ; G 5183
+U 64470 ; WX 552 ; N uniFBD6 ; G 5184
+U 64471 ; WX 622 ; N uniFBD7 ; G 5185
+U 64472 ; WX 627 ; N uniFBD8 ; G 5186
+U 64473 ; WX 622 ; N uniFBD9 ; G 5187
+U 64474 ; WX 627 ; N uniFBDA ; G 5188
+U 64475 ; WX 622 ; N uniFBDB ; G 5189
+U 64476 ; WX 627 ; N uniFBDC ; G 5190
+U 64478 ; WX 622 ; N uniFBDE ; G 5191
+U 64479 ; WX 627 ; N uniFBDF ; G 5192
+U 64484 ; WX 917 ; N uniFBE4 ; G 5193
+U 64485 ; WX 1012 ; N uniFBE5 ; G 5194
+U 64486 ; WX 375 ; N uniFBE6 ; G 5195
+U 64487 ; WX 408 ; N uniFBE7 ; G 5196
+U 64488 ; WX 375 ; N uniFBE8 ; G 5197
+U 64489 ; WX 408 ; N uniFBE9 ; G 5198
+U 64508 ; WX 917 ; N uniFBFC ; G 5199
+U 64509 ; WX 1012 ; N uniFBFD ; G 5200
+U 64510 ; WX 375 ; N uniFBFE ; G 5201
+U 64511 ; WX 408 ; N uniFBFF ; G 5202
+U 65024 ; WX 0 ; N uniFE00 ; G 5203
+U 65025 ; WX 0 ; N uniFE01 ; G 5204
+U 65026 ; WX 0 ; N uniFE02 ; G 5205
+U 65027 ; WX 0 ; N uniFE03 ; G 5206
+U 65028 ; WX 0 ; N uniFE04 ; G 5207
+U 65029 ; WX 0 ; N uniFE05 ; G 5208
+U 65030 ; WX 0 ; N uniFE06 ; G 5209
+U 65031 ; WX 0 ; N uniFE07 ; G 5210
+U 65032 ; WX 0 ; N uniFE08 ; G 5211
+U 65033 ; WX 0 ; N uniFE09 ; G 5212
+U 65034 ; WX 0 ; N uniFE0A ; G 5213
+U 65035 ; WX 0 ; N uniFE0B ; G 5214
+U 65036 ; WX 0 ; N uniFE0C ; G 5215
+U 65037 ; WX 0 ; N uniFE0D ; G 5216
+U 65038 ; WX 0 ; N uniFE0E ; G 5217
+U 65039 ; WX 0 ; N uniFE0F ; G 5218
+U 65056 ; WX 0 ; N uniFE20 ; G 5219
+U 65057 ; WX 0 ; N uniFE21 ; G 5220
+U 65058 ; WX 0 ; N uniFE22 ; G 5221
+U 65059 ; WX 0 ; N uniFE23 ; G 5222
+U 65136 ; WX 342 ; N uniFE70 ; G 5223
+U 65137 ; WX 342 ; N uniFE71 ; G 5224
+U 65138 ; WX 342 ; N uniFE72 ; G 5225
+U 65139 ; WX 346 ; N uniFE73 ; G 5226
+U 65140 ; WX 342 ; N uniFE74 ; G 5227
+U 65142 ; WX 342 ; N uniFE76 ; G 5228
+U 65143 ; WX 342 ; N uniFE77 ; G 5229
+U 65144 ; WX 342 ; N uniFE78 ; G 5230
+U 65145 ; WX 342 ; N uniFE79 ; G 5231
+U 65146 ; WX 342 ; N uniFE7A ; G 5232
+U 65147 ; WX 342 ; N uniFE7B ; G 5233
+U 65148 ; WX 342 ; N uniFE7C ; G 5234
+U 65149 ; WX 342 ; N uniFE7D ; G 5235
+U 65150 ; WX 342 ; N uniFE7E ; G 5236
+U 65151 ; WX 342 ; N uniFE7F ; G 5237
+U 65152 ; WX 511 ; N uniFE80 ; G 5238
+U 65153 ; WX 343 ; N uniFE81 ; G 5239
+U 65154 ; WX 375 ; N uniFE82 ; G 5240
+U 65155 ; WX 343 ; N uniFE83 ; G 5241
+U 65156 ; WX 375 ; N uniFE84 ; G 5242
+U 65157 ; WX 622 ; N uniFE85 ; G 5243
+U 65158 ; WX 627 ; N uniFE86 ; G 5244
+U 65159 ; WX 343 ; N uniFE87 ; G 5245
+U 65160 ; WX 375 ; N uniFE88 ; G 5246
+U 65161 ; WX 917 ; N uniFE89 ; G 5247
+U 65162 ; WX 917 ; N uniFE8A ; G 5248
+U 65163 ; WX 375 ; N uniFE8B ; G 5249
+U 65164 ; WX 408 ; N uniFE8C ; G 5250
+U 65165 ; WX 343 ; N uniFE8D ; G 5251
+U 65166 ; WX 375 ; N uniFE8E ; G 5252
+U 65167 ; WX 1005 ; N uniFE8F ; G 5253
+U 65168 ; WX 1059 ; N uniFE90 ; G 5254
+U 65169 ; WX 375 ; N uniFE91 ; G 5255
+U 65170 ; WX 408 ; N uniFE92 ; G 5256
+U 65171 ; WX 590 ; N uniFE93 ; G 5257
+U 65172 ; WX 606 ; N uniFE94 ; G 5258
+U 65173 ; WX 1005 ; N uniFE95 ; G 5259
+U 65174 ; WX 1059 ; N uniFE96 ; G 5260
+U 65175 ; WX 375 ; N uniFE97 ; G 5261
+U 65176 ; WX 408 ; N uniFE98 ; G 5262
+U 65177 ; WX 1005 ; N uniFE99 ; G 5263
+U 65178 ; WX 1059 ; N uniFE9A ; G 5264
+U 65179 ; WX 375 ; N uniFE9B ; G 5265
+U 65180 ; WX 408 ; N uniFE9C ; G 5266
+U 65181 ; WX 721 ; N uniFE9D ; G 5267
+U 65182 ; WX 721 ; N uniFE9E ; G 5268
+U 65183 ; WX 721 ; N uniFE9F ; G 5269
+U 65184 ; WX 721 ; N uniFEA0 ; G 5270
+U 65185 ; WX 721 ; N uniFEA1 ; G 5271
+U 65186 ; WX 721 ; N uniFEA2 ; G 5272
+U 65187 ; WX 721 ; N uniFEA3 ; G 5273
+U 65188 ; WX 721 ; N uniFEA4 ; G 5274
+U 65189 ; WX 721 ; N uniFEA5 ; G 5275
+U 65190 ; WX 721 ; N uniFEA6 ; G 5276
+U 65191 ; WX 721 ; N uniFEA7 ; G 5277
+U 65192 ; WX 721 ; N uniFEA8 ; G 5278
+U 65193 ; WX 513 ; N uniFEA9 ; G 5279
+U 65194 ; WX 578 ; N uniFEAA ; G 5280
+U 65195 ; WX 513 ; N uniFEAB ; G 5281
+U 65196 ; WX 578 ; N uniFEAC ; G 5282
+U 65197 ; WX 576 ; N uniFEAD ; G 5283
+U 65198 ; WX 622 ; N uniFEAE ; G 5284
+U 65199 ; WX 576 ; N uniFEAF ; G 5285
+U 65200 ; WX 622 ; N uniFEB0 ; G 5286
+U 65201 ; WX 1380 ; N uniFEB1 ; G 5287
+U 65202 ; WX 1414 ; N uniFEB2 ; G 5288
+U 65203 ; WX 983 ; N uniFEB3 ; G 5289
+U 65204 ; WX 1018 ; N uniFEB4 ; G 5290
+U 65205 ; WX 1380 ; N uniFEB5 ; G 5291
+U 65206 ; WX 1414 ; N uniFEB6 ; G 5292
+U 65207 ; WX 983 ; N uniFEB7 ; G 5293
+U 65208 ; WX 1018 ; N uniFEB8 ; G 5294
+U 65209 ; WX 1345 ; N uniFEB9 ; G 5295
+U 65210 ; WX 1364 ; N uniFEBA ; G 5296
+U 65211 ; WX 966 ; N uniFEBB ; G 5297
+U 65212 ; WX 985 ; N uniFEBC ; G 5298
+U 65213 ; WX 1345 ; N uniFEBD ; G 5299
+U 65214 ; WX 1364 ; N uniFEBE ; G 5300
+U 65215 ; WX 966 ; N uniFEBF ; G 5301
+U 65216 ; WX 985 ; N uniFEC0 ; G 5302
+U 65217 ; WX 1039 ; N uniFEC1 ; G 5303
+U 65218 ; WX 1071 ; N uniFEC2 ; G 5304
+U 65219 ; WX 942 ; N uniFEC3 ; G 5305
+U 65220 ; WX 974 ; N uniFEC4 ; G 5306
+U 65221 ; WX 1039 ; N uniFEC5 ; G 5307
+U 65222 ; WX 1071 ; N uniFEC6 ; G 5308
+U 65223 ; WX 942 ; N uniFEC7 ; G 5309
+U 65224 ; WX 974 ; N uniFEC8 ; G 5310
+U 65225 ; WX 683 ; N uniFEC9 ; G 5311
+U 65226 ; WX 683 ; N uniFECA ; G 5312
+U 65227 ; WX 683 ; N uniFECB ; G 5313
+U 65228 ; WX 564 ; N uniFECC ; G 5314
+U 65229 ; WX 683 ; N uniFECD ; G 5315
+U 65230 ; WX 683 ; N uniFECE ; G 5316
+U 65231 ; WX 683 ; N uniFECF ; G 5317
+U 65232 ; WX 564 ; N uniFED0 ; G 5318
+U 65233 ; WX 1162 ; N uniFED1 ; G 5319
+U 65234 ; WX 1191 ; N uniFED2 ; G 5320
+U 65235 ; WX 655 ; N uniFED3 ; G 5321
+U 65236 ; WX 720 ; N uniFED4 ; G 5322
+U 65237 ; WX 894 ; N uniFED5 ; G 5323
+U 65238 ; WX 901 ; N uniFED6 ; G 5324
+U 65239 ; WX 655 ; N uniFED7 ; G 5325
+U 65240 ; WX 720 ; N uniFED8 ; G 5326
+U 65241 ; WX 917 ; N uniFED9 ; G 5327
+U 65242 ; WX 931 ; N uniFEDA ; G 5328
+U 65243 ; WX 582 ; N uniFEDB ; G 5329
+U 65244 ; WX 582 ; N uniFEDC ; G 5330
+U 65245 ; WX 868 ; N uniFEDD ; G 5331
+U 65246 ; WX 893 ; N uniFEDE ; G 5332
+U 65247 ; WX 375 ; N uniFEDF ; G 5333
+U 65248 ; WX 408 ; N uniFEE0 ; G 5334
+U 65249 ; WX 733 ; N uniFEE1 ; G 5335
+U 65250 ; WX 784 ; N uniFEE2 ; G 5336
+U 65251 ; WX 619 ; N uniFEE3 ; G 5337
+U 65252 ; WX 670 ; N uniFEE4 ; G 5338
+U 65253 ; WX 854 ; N uniFEE5 ; G 5339
+U 65254 ; WX 900 ; N uniFEE6 ; G 5340
+U 65255 ; WX 375 ; N uniFEE7 ; G 5341
+U 65256 ; WX 408 ; N uniFEE8 ; G 5342
+U 65257 ; WX 590 ; N uniFEE9 ; G 5343
+U 65258 ; WX 606 ; N uniFEEA ; G 5344
+U 65259 ; WX 693 ; N uniFEEB ; G 5345
+U 65260 ; WX 660 ; N uniFEEC ; G 5346
+U 65261 ; WX 622 ; N uniFEED ; G 5347
+U 65262 ; WX 627 ; N uniFEEE ; G 5348
+U 65263 ; WX 917 ; N uniFEEF ; G 5349
+U 65264 ; WX 1012 ; N uniFEF0 ; G 5350
+U 65265 ; WX 917 ; N uniFEF1 ; G 5351
+U 65266 ; WX 1012 ; N uniFEF2 ; G 5352
+U 65267 ; WX 375 ; N uniFEF3 ; G 5353
+U 65268 ; WX 408 ; N uniFEF4 ; G 5354
+U 65269 ; WX 745 ; N uniFEF5 ; G 5355
+U 65270 ; WX 759 ; N uniFEF6 ; G 5356
+U 65271 ; WX 745 ; N uniFEF7 ; G 5357
+U 65272 ; WX 759 ; N uniFEF8 ; G 5358
+U 65273 ; WX 745 ; N uniFEF9 ; G 5359
+U 65274 ; WX 759 ; N uniFEFA ; G 5360
+U 65275 ; WX 745 ; N uniFEFB ; G 5361
+U 65276 ; WX 759 ; N uniFEFC ; G 5362
+U 65279 ; WX 0 ; N uniFEFF ; G 5363
+U 65529 ; WX 0 ; N uniFFF9 ; G 5364
+U 65530 ; WX 0 ; N uniFFFA ; G 5365
+U 65531 ; WX 0 ; N uniFFFB ; G 5366
+U 65532 ; WX 0 ; N uniFFFC ; G 5367
+U 65533 ; WX 1113 ; N uniFFFD ; G 5368
+EndCharMetrics
+StartKernData
+StartKernPairs 1538
+
+KPX dollar seven -159
+KPX dollar eight -63
+KPX dollar nine -139
+KPX dollar colon -92
+KPX dollar less -196
+KPX dollar Y -73
+KPX dollar backslash -73
+KPX dollar questiondown -73
+KPX dollar Aacute -73
+KPX dollar Hcircumflex -159
+KPX dollar Hbar -159
+KPX dollar Imacron -63
+KPX dollar Ibreve -63
+KPX dollar Iogonek -63
+KPX dollar Idot -63
+KPX dollar IJ -63
+KPX dollar Kcommaaccent -92
+KPX dollar kgreenlandic -196
+KPX dollar Lacute -73
+KPX dollar lacute -196
+KPX dollar uni01DC -159
+KPX dollar uni01F4 -196
+KPX dollar uni01F5 -73
+
+KPX percent nine -83
+KPX percent colon -112
+KPX percent less -112
+KPX percent Kcommaaccent -112
+KPX percent kgreenlandic -112
+KPX percent lacute -112
+KPX percent uni01F4 -112
+
+KPX ampersand six 38
+KPX ampersand Gcircumflex 38
+KPX ampersand Gbreve 38
+KPX ampersand Gdotaccent 38
+KPX ampersand Gcommaaccent 38
+KPX ampersand uni01DA 38
+
+KPX quotesingle less -149
+KPX quotesingle kgreenlandic -149
+KPX quotesingle lacute -149
+KPX quotesingle uni01F4 -149
+
+KPX parenright dollar -235
+KPX parenright D -120
+KPX parenright H -83
+KPX parenright R -83
+KPX parenright U -131
+KPX parenright X -102
+KPX parenright backslash -112
+KPX parenright cent -120
+KPX parenright sterling -120
+KPX parenright currency -120
+KPX parenright yen -120
+KPX parenright brokenbar -120
+KPX parenright section -120
+KPX parenright dieresis -120
+KPX parenright ordfeminine -83
+KPX parenright guillemotleft -83
+KPX parenright logicalnot -83
+KPX parenright sfthyphen -83
+KPX parenright acute -83
+KPX parenright mu -83
+KPX parenright paragraph -83
+KPX parenright periodcentered -83
+KPX parenright cedilla -83
+KPX parenright ordmasculine -83
+KPX parenright guillemotright -102
+KPX parenright onequarter -102
+KPX parenright onehalf -102
+KPX parenright threequarters -102
+KPX parenright questiondown -112
+KPX parenright Aacute -112
+KPX parenright Acircumflex -235
+KPX parenright Atilde -120
+KPX parenright Adieresis -235
+KPX parenright Aring -120
+KPX parenright AE -235
+KPX parenright Ccedilla -120
+KPX parenright Otilde -83
+KPX parenright multiply -83
+KPX parenright Ugrave -83
+KPX parenright Ucircumflex -83
+KPX parenright Yacute -83
+KPX parenright dcaron -83
+KPX parenright dmacron -83
+KPX parenright emacron -83
+KPX parenright ebreve -83
+KPX parenright edotaccent -131
+KPX parenright eogonek -131
+KPX parenright ecaron -131
+KPX parenright imacron -102
+KPX parenright ibreve -102
+KPX parenright iogonek -102
+KPX parenright dotlessi -102
+KPX parenright ij -102
+KPX parenright jcircumflex -102
+KPX parenright Lacute -112
+KPX parenright uni01A5 -120
+KPX parenright uni01AD -83
+KPX parenright Uhorn -83
+KPX parenright uni01F1 -83
+KPX parenright uni01F5 -112
+
+KPX asterisk seven -36
+KPX asterisk less -45
+KPX asterisk Hbar -36
+KPX asterisk lacute -45
+
+KPX period ampersand -92
+KPX period two -92
+KPX period eight -36
+KPX period H -36
+KPX period R -36
+KPX period X -36
+KPX period backslash -131
+KPX period ordfeminine -36
+KPX period guillemotleft -36
+KPX period logicalnot -36
+KPX period sfthyphen -36
+KPX period acute -36
+KPX period mu -36
+KPX period paragraph -36
+KPX period periodcentered -36
+KPX period cedilla -36
+KPX period ordmasculine -36
+KPX period guillemotright -36
+KPX period onequarter -36
+KPX period onehalf -36
+KPX period threequarters -36
+KPX period questiondown -131
+KPX period Aacute -131
+KPX period Egrave -92
+KPX period Icircumflex -92
+KPX period Yacute -36
+KPX period Ebreve -102
+KPX period ebreve -36
+KPX period Idot -36
+KPX period dotlessi -36
+
+KPX slash two -73
+KPX slash seven -339
+KPX slash eight -73
+KPX slash nine -282
+KPX slash colon -159
+KPX slash less -319
+KPX slash backslash -139
+KPX slash questiondown -139
+KPX slash Aacute -139
+KPX slash Ebreve -73
+KPX slash Hbar -339
+KPX slash Idot -73
+KPX slash lacute -319
+
+KPX two dollar -55
+KPX two nine -55
+KPX two semicolon -73
+KPX two less -73
+KPX two lacute -73
+
+KPX three dollar -188
+KPX three D -55
+KPX three V -36
+KPX three backslash 38
+KPX three cent -55
+KPX three sterling -55
+KPX three currency -55
+KPX three yen -55
+KPX three brokenbar -55
+KPX three section -55
+KPX three dieresis -55
+KPX three questiondown 38
+KPX three Aacute 38
+KPX three gdotaccent -36
+KPX three gcommaaccent -36
+
+
+KPX five seven -92
+KPX five less -112
+KPX five backslash -92
+KPX five questiondown -92
+KPX five Aacute -92
+KPX five Hbar -92
+KPX five lacute -112
+
+KPX six six -92
+KPX six Gdotaccent -92
+KPX six Gcommaaccent -92
+
+KPX seven dollar -159
+KPX seven seven 47
+KPX seven D -264
+KPX seven F -272
+KPX seven H -272
+KPX seven R -272
+KPX seven U -225
+KPX seven V -272
+KPX seven X -225
+KPX seven Z -225
+KPX seven backslash -243
+KPX seven cent -164
+KPX seven sterling -264
+KPX seven currency -164
+KPX seven yen -164
+KPX seven brokenbar -164
+KPX seven section -164
+KPX seven dieresis -196
+KPX seven copyright -272
+KPX seven ordfeminine -212
+KPX seven guillemotleft -272
+KPX seven logicalnot -212
+KPX seven sfthyphen -212
+KPX seven acute -192
+KPX seven mu -272
+KPX seven paragraph -192
+KPX seven periodcentered -192
+KPX seven cedilla -192
+KPX seven ordmasculine -159
+KPX seven guillemotright -195
+KPX seven onequarter -225
+KPX seven onehalf -195
+KPX seven threequarters -195
+KPX seven questiondown -243
+KPX seven Aacute -243
+KPX seven Eacute -272
+KPX seven Idieresis -272
+KPX seven Yacute -272
+KPX seven ebreve -159
+KPX seven edotaccent -225
+KPX seven ecaron -225
+KPX seven gdotaccent -272
+KPX seven gcommaaccent -272
+KPX seven dotlessi -225
+
+KPX eight dollar -63
+
+KPX nine dollar -139
+KPX nine two -36
+KPX nine D -112
+KPX nine H -112
+KPX nine L -36
+KPX nine R -112
+KPX nine X -73
+KPX nine cent -112
+KPX nine sterling -112
+KPX nine currency -112
+KPX nine yen -112
+KPX nine brokenbar -112
+KPX nine section -112
+KPX nine dieresis -112
+KPX nine ordfeminine -112
+KPX nine guillemotleft -112
+KPX nine logicalnot -112
+KPX nine sfthyphen -112
+KPX nine acute -112
+KPX nine mu -112
+KPX nine paragraph -112
+KPX nine periodcentered -112
+KPX nine cedilla -112
+KPX nine ordmasculine -112
+KPX nine guillemotright -73
+KPX nine onequarter -73
+KPX nine onehalf -73
+KPX nine threequarters -73
+KPX nine Yacute -112
+KPX nine Ebreve -36
+KPX nine ebreve -112
+KPX nine dotlessi -73
+
+KPX colon dollar -92
+KPX colon D -73
+KPX colon H -73
+KPX colon R -73
+KPX colon U -36
+KPX colon cent -73
+KPX colon sterling -73
+KPX colon currency -73
+KPX colon yen -73
+KPX colon brokenbar -73
+KPX colon section -73
+KPX colon dieresis -73
+KPX colon ordfeminine -73
+KPX colon guillemotleft -73
+KPX colon logicalnot -73
+KPX colon sfthyphen -73
+KPX colon acute -73
+KPX colon mu -73
+KPX colon paragraph -73
+KPX colon periodcentered -73
+KPX colon cedilla -73
+KPX colon ordmasculine -73
+KPX colon Yacute -73
+KPX colon ebreve -73
+KPX colon edotaccent -36
+KPX colon ecaron -36
+
+KPX semicolon ampersand -73
+KPX semicolon two -73
+KPX semicolon H -55
+KPX semicolon ordfeminine -55
+KPX semicolon guillemotleft -55
+KPX semicolon logicalnot -55
+KPX semicolon sfthyphen -55
+KPX semicolon Egrave -73
+KPX semicolon Icircumflex -73
+KPX semicolon Yacute -55
+KPX semicolon Ebreve -73
+
+KPX less dollar -196
+KPX less ampersand -73
+KPX less two -73
+KPX less D -188
+KPX less H -188
+KPX less R -188
+KPX less X -149
+KPX less cent -188
+KPX less sterling -188
+KPX less currency -188
+KPX less yen -188
+KPX less brokenbar -188
+KPX less section -188
+KPX less dieresis -188
+KPX less ordfeminine -188
+KPX less guillemotleft -188
+KPX less logicalnot -188
+KPX less sfthyphen -188
+KPX less acute -188
+KPX less mu -188
+KPX less paragraph -188
+KPX less periodcentered -188
+KPX less cedilla -188
+KPX less ordmasculine -188
+KPX less guillemotright -149
+KPX less onequarter -149
+KPX less onehalf -149
+KPX less threequarters -149
+KPX less Egrave -73
+KPX less Icircumflex -73
+KPX less Yacute -188
+KPX less Ebreve -92
+KPX less ebreve -188
+KPX less dotlessi -149
+
+
+KPX D backslash -63
+KPX D questiondown -63
+KPX D Aacute -63
+
+
+KPX N H -55
+KPX N R -55
+KPX N ordfeminine -55
+KPX N guillemotleft -55
+KPX N logicalnot -55
+KPX N sfthyphen -55
+KPX N acute -55
+KPX N mu -55
+KPX N paragraph -55
+KPX N periodcentered -55
+KPX N cedilla -55
+KPX N ordmasculine -45
+KPX N Yacute -55
+KPX N ebreve -55
+
+
+
+
+
+KPX cent backslash -63
+KPX cent questiondown -63
+KPX cent Aacute -63
+
+KPX sterling backslash -63
+KPX sterling questiondown -63
+KPX sterling Aacute -63
+
+KPX currency backslash -63
+KPX currency questiondown -63
+KPX currency Aacute -63
+
+KPX yen backslash -63
+KPX yen questiondown -63
+KPX yen Aacute -63
+
+KPX brokenbar backslash -63
+KPX brokenbar questiondown -63
+KPX brokenbar Aacute -63
+
+KPX section backslash -63
+KPX section questiondown -63
+KPX section Aacute -63
+
+
+
+KPX Acircumflex seven -159
+KPX Acircumflex eight -63
+KPX Acircumflex nine -139
+KPX Acircumflex colon -92
+KPX Acircumflex less -196
+KPX Acircumflex Y -73
+KPX Acircumflex backslash -73
+KPX Acircumflex questiondown -73
+KPX Acircumflex Aacute -73
+KPX Acircumflex Hcircumflex -159
+KPX Acircumflex Hbar -159
+KPX Acircumflex Imacron -63
+KPX Acircumflex Ibreve -63
+KPX Acircumflex Iogonek -63
+KPX Acircumflex Idot -63
+KPX Acircumflex IJ -63
+KPX Acircumflex Kcommaaccent -92
+KPX Acircumflex kgreenlandic -196
+KPX Acircumflex Lacute -73
+KPX Acircumflex lacute -196
+KPX Acircumflex uni01DC -159
+KPX Acircumflex uni01F4 -196
+KPX Acircumflex uni01F5 -73
+
+KPX Adieresis seven -159
+KPX Adieresis eight -63
+KPX Adieresis nine -139
+KPX Adieresis colon -92
+KPX Adieresis less -196
+KPX Adieresis Y -73
+KPX Adieresis backslash -73
+KPX Adieresis questiondown -73
+KPX Adieresis Aacute -73
+KPX Adieresis Hcircumflex -159
+KPX Adieresis Hbar -159
+KPX Adieresis Imacron -63
+KPX Adieresis Ibreve -63
+KPX Adieresis Iogonek -63
+KPX Adieresis Idot -63
+KPX Adieresis IJ -63
+KPX Adieresis Kcommaaccent -92
+KPX Adieresis kgreenlandic -196
+KPX Adieresis Lacute -73
+KPX Adieresis lacute -196
+KPX Adieresis uni01DC -159
+KPX Adieresis uni01F4 -196
+KPX Adieresis uni01F5 -73
+
+KPX AE seven -159
+KPX AE eight -63
+KPX AE nine -139
+KPX AE colon -92
+KPX AE less -196
+KPX AE Y -73
+KPX AE backslash -73
+KPX AE questiondown -73
+KPX AE Aacute -73
+KPX AE Hcircumflex -159
+KPX AE Hbar -159
+KPX AE Imacron -63
+KPX AE Ibreve -63
+KPX AE Iogonek -63
+KPX AE Idot -63
+KPX AE IJ -63
+KPX AE Kcommaaccent -92
+KPX AE kgreenlandic -196
+KPX AE Lacute -73
+KPX AE lacute -196
+KPX AE uni01DC -159
+KPX AE uni01F4 -196
+KPX AE uni01F5 -73
+
+KPX Egrave six 38
+KPX Egrave Gcircumflex 38
+KPX Egrave Gbreve 38
+KPX Egrave Gdotaccent 38
+KPX Egrave Gcommaaccent 38
+KPX Egrave uni01DA 38
+
+KPX Ecircumflex six 38
+KPX Ecircumflex Gcircumflex 38
+KPX Ecircumflex Gbreve 38
+KPX Ecircumflex Gdotaccent 38
+KPX Ecircumflex Gcommaaccent 38
+KPX Ecircumflex uni01DA 38
+
+KPX Igrave six 38
+KPX Igrave Gcircumflex 38
+KPX Igrave Gbreve 38
+KPX Igrave Gdotaccent 38
+KPX Igrave Gcommaaccent 38
+KPX Igrave uni01DA 38
+
+KPX Icircumflex six 38
+KPX Icircumflex Gcircumflex 38
+KPX Icircumflex Gbreve 38
+KPX Icircumflex Gdotaccent 38
+KPX Icircumflex Gcommaaccent 38
+KPX Icircumflex uni01DA 38
+
+KPX Eth less -149
+KPX Eth kgreenlandic -149
+KPX Eth lacute -149
+KPX Eth uni01F4 -149
+
+KPX Ograve less -149
+KPX Ograve kgreenlandic -149
+KPX Ograve lacute -149
+KPX Ograve uni01F4 -149
+
+KPX agrave seven -36
+KPX agrave less -45
+KPX agrave Hbar -36
+KPX agrave lacute -45
+
+KPX ucircumflex two -73
+KPX ucircumflex seven -339
+KPX ucircumflex eight -73
+KPX ucircumflex nine -282
+KPX ucircumflex colon -159
+KPX ucircumflex less -319
+KPX ucircumflex backslash -139
+KPX ucircumflex questiondown -139
+KPX ucircumflex Aacute -139
+KPX ucircumflex Ebreve -73
+KPX ucircumflex Hbar -339
+KPX ucircumflex Idot -73
+KPX ucircumflex lacute -319
+
+KPX ydieresis two -73
+KPX ydieresis seven -339
+KPX ydieresis eight -73
+KPX ydieresis nine -282
+KPX ydieresis colon -159
+KPX ydieresis less -319
+KPX ydieresis backslash -139
+KPX ydieresis questiondown -139
+KPX ydieresis Aacute -139
+KPX ydieresis Ebreve -73
+KPX ydieresis Hbar -339
+KPX ydieresis Idot -73
+KPX ydieresis lacute -319
+
+KPX Abreve O -246
+
+KPX abreve two -73
+KPX abreve seven -339
+KPX abreve eight -73
+KPX abreve nine -282
+KPX abreve colon -159
+KPX abreve less -319
+KPX abreve backslash -139
+KPX abreve questiondown -139
+KPX abreve Aacute -139
+KPX abreve Ebreve -73
+KPX abreve Hbar -339
+KPX abreve Idot -73
+KPX abreve lacute -319
+
+KPX Edotaccent seven -92
+KPX Edotaccent less -112
+KPX Edotaccent backslash -92
+KPX Edotaccent questiondown -92
+KPX Edotaccent Aacute -92
+KPX Edotaccent Hbar -92
+KPX Edotaccent lacute -112
+
+
+KPX Ecaron seven -92
+KPX Ecaron less -112
+KPX Ecaron backslash -92
+KPX Ecaron questiondown -92
+KPX Ecaron Aacute -92
+KPX Ecaron Hbar -92
+KPX Ecaron lacute -112
+
+
+KPX Gdotaccent six -92
+KPX Gdotaccent Gdotaccent -92
+KPX Gdotaccent Gcommaaccent -92
+
+KPX Gcommaaccent six -92
+KPX Gcommaaccent Gdotaccent -92
+KPX Gcommaaccent Gcommaaccent -92
+
+KPX Hbar dollar -159
+KPX Hbar seven 47
+KPX Hbar D -264
+KPX Hbar F -272
+KPX Hbar H -272
+KPX Hbar R -272
+KPX Hbar U -225
+KPX Hbar V -272
+KPX Hbar X -225
+KPX Hbar Z -225
+KPX Hbar backslash -243
+KPX Hbar cent -264
+KPX Hbar sterling -264
+KPX Hbar currency -264
+KPX Hbar yen -264
+KPX Hbar brokenbar -264
+KPX Hbar section -264
+KPX Hbar dieresis -196
+KPX Hbar copyright -272
+KPX Hbar ordfeminine -272
+KPX Hbar guillemotleft -272
+KPX Hbar logicalnot -272
+KPX Hbar sfthyphen -272
+KPX Hbar acute -272
+KPX Hbar mu -272
+KPX Hbar paragraph -272
+KPX Hbar periodcentered -272
+KPX Hbar cedilla -272
+KPX Hbar ordmasculine -159
+KPX Hbar guillemotright -225
+KPX Hbar onequarter -225
+KPX Hbar onehalf -225
+KPX Hbar threequarters -225
+KPX Hbar questiondown -243
+KPX Hbar Aacute -243
+KPX Hbar Eacute -272
+KPX Hbar Idieresis -272
+KPX Hbar Yacute -272
+KPX Hbar ebreve -159
+KPX Hbar edotaccent -225
+KPX Hbar ecaron -225
+KPX Hbar gdotaccent -272
+KPX Hbar gcommaaccent -272
+KPX Hbar Hbar 47
+KPX Hbar dotlessi -225
+
+KPX Idot dollar -63
+
+KPX lacute dollar -196
+KPX lacute ampersand -73
+KPX lacute two -73
+KPX lacute D -188
+KPX lacute H -188
+KPX lacute R -188
+KPX lacute X -149
+KPX lacute cent -188
+KPX lacute sterling -188
+KPX lacute currency -188
+KPX lacute yen -188
+KPX lacute brokenbar -188
+KPX lacute section -188
+KPX lacute dieresis -188
+KPX lacute ordfeminine -188
+KPX lacute guillemotleft -188
+KPX lacute logicalnot -188
+KPX lacute sfthyphen -188
+KPX lacute acute -188
+KPX lacute mu -188
+KPX lacute paragraph -188
+KPX lacute periodcentered -188
+KPX lacute cedilla -188
+KPX lacute ordmasculine -188
+KPX lacute guillemotright -149
+KPX lacute onequarter -149
+KPX lacute onehalf -149
+KPX lacute threequarters -149
+KPX lacute Egrave -73
+KPX lacute Icircumflex -73
+KPX lacute Yacute -188
+KPX lacute Ebreve -92
+KPX lacute ebreve -188
+KPX lacute dotlessi -149
+
+
+KPX uni027D dollar -235
+KPX uni027D hyphen -92
+KPX uni027D nine 38
+KPX uni027D less 75
+KPX uni027D lacute 75
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ttf
new file mode 100644
index 0000000..753f2d8
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ufm
new file mode 100644
index 0000000..5f4dd7c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-BoldOblique.ufm
@@ -0,0 +1,5712 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans
+FontSubfamily Bold Oblique
+UniqueID DejaVu Sans Bold Oblique
+FullName DejaVu Sans Bold Oblique
+Version Version 2.37
+PostScriptName DejaVuSans-BoldOblique
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Sans
+PreferredSubfamily Bold Oblique
+Weight Bold
+ItalicAngle -11
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -1067 -385 1999 1121
+StartCharMetrics 5413
+U 32 ; WX 348 ; N space ; G 3
+U 33 ; WX 456 ; N exclam ; G 4
+U 34 ; WX 521 ; N quotedbl ; G 5
+U 35 ; WX 696 ; N numbersign ; G 6
+U 36 ; WX 696 ; N dollar ; G 7
+U 37 ; WX 1002 ; N percent ; G 8
+U 38 ; WX 872 ; N ampersand ; G 9
+U 39 ; WX 306 ; N quotesingle ; G 10
+U 40 ; WX 457 ; N parenleft ; G 11
+U 41 ; WX 457 ; N parenright ; G 12
+U 42 ; WX 523 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 380 ; N comma ; G 15
+U 45 ; WX 415 ; N hyphen ; G 16
+U 46 ; WX 380 ; N period ; G 17
+U 47 ; WX 365 ; N slash ; G 18
+U 48 ; WX 696 ; N zero ; G 19
+U 49 ; WX 696 ; N one ; G 20
+U 50 ; WX 696 ; N two ; G 21
+U 51 ; WX 696 ; N three ; G 22
+U 52 ; WX 696 ; N four ; G 23
+U 53 ; WX 696 ; N five ; G 24
+U 54 ; WX 696 ; N six ; G 25
+U 55 ; WX 696 ; N seven ; G 26
+U 56 ; WX 696 ; N eight ; G 27
+U 57 ; WX 696 ; N nine ; G 28
+U 58 ; WX 400 ; N colon ; G 29
+U 59 ; WX 400 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 580 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 774 ; N A ; G 36
+U 66 ; WX 762 ; N B ; G 37
+U 67 ; WX 734 ; N C ; G 38
+U 68 ; WX 830 ; N D ; G 39
+U 69 ; WX 683 ; N E ; G 40
+U 70 ; WX 683 ; N F ; G 41
+U 71 ; WX 821 ; N G ; G 42
+U 72 ; WX 837 ; N H ; G 43
+U 73 ; WX 372 ; N I ; G 44
+U 74 ; WX 372 ; N J ; G 45
+U 75 ; WX 775 ; N K ; G 46
+U 76 ; WX 637 ; N L ; G 47
+U 77 ; WX 995 ; N M ; G 48
+U 78 ; WX 837 ; N N ; G 49
+U 79 ; WX 850 ; N O ; G 50
+U 80 ; WX 733 ; N P ; G 51
+U 81 ; WX 850 ; N Q ; G 52
+U 82 ; WX 770 ; N R ; G 53
+U 83 ; WX 720 ; N S ; G 54
+U 84 ; WX 682 ; N T ; G 55
+U 85 ; WX 812 ; N U ; G 56
+U 86 ; WX 774 ; N V ; G 57
+U 87 ; WX 1103 ; N W ; G 58
+U 88 ; WX 771 ; N X ; G 59
+U 89 ; WX 724 ; N Y ; G 60
+U 90 ; WX 725 ; N Z ; G 61
+U 91 ; WX 457 ; N bracketleft ; G 62
+U 92 ; WX 365 ; N backslash ; G 63
+U 93 ; WX 457 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 675 ; N a ; G 68
+U 98 ; WX 716 ; N b ; G 69
+U 99 ; WX 593 ; N c ; G 70
+U 100 ; WX 716 ; N d ; G 71
+U 101 ; WX 678 ; N e ; G 72
+U 102 ; WX 435 ; N f ; G 73
+U 103 ; WX 716 ; N g ; G 74
+U 104 ; WX 712 ; N h ; G 75
+U 105 ; WX 343 ; N i ; G 76
+U 106 ; WX 343 ; N j ; G 77
+U 107 ; WX 665 ; N k ; G 78
+U 108 ; WX 343 ; N l ; G 79
+U 109 ; WX 1042 ; N m ; G 80
+U 110 ; WX 712 ; N n ; G 81
+U 111 ; WX 687 ; N o ; G 82
+U 112 ; WX 716 ; N p ; G 83
+U 113 ; WX 716 ; N q ; G 84
+U 114 ; WX 493 ; N r ; G 85
+U 115 ; WX 595 ; N s ; G 86
+U 116 ; WX 478 ; N t ; G 87
+U 117 ; WX 712 ; N u ; G 88
+U 118 ; WX 652 ; N v ; G 89
+U 119 ; WX 924 ; N w ; G 90
+U 120 ; WX 645 ; N x ; G 91
+U 121 ; WX 652 ; N y ; G 92
+U 122 ; WX 582 ; N z ; G 93
+U 123 ; WX 712 ; N braceleft ; G 94
+U 124 ; WX 365 ; N bar ; G 95
+U 125 ; WX 712 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 348 ; N nbspace ; G 98
+U 161 ; WX 456 ; N exclamdown ; G 99
+U 162 ; WX 696 ; N cent ; G 100
+U 163 ; WX 696 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 696 ; N yen ; G 103
+U 166 ; WX 365 ; N brokenbar ; G 104
+U 167 ; WX 500 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 564 ; N ordfeminine ; G 108
+U 171 ; WX 650 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 415 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 438 ; N twosuperior ; G 116
+U 179 ; WX 438 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 736 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 380 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 438 ; N onesuperior ; G 123
+U 186 ; WX 564 ; N ordmasculine ; G 124
+U 187 ; WX 650 ; N guillemotright ; G 125
+U 188 ; WX 1035 ; N onequarter ; G 126
+U 189 ; WX 1035 ; N onehalf ; G 127
+U 190 ; WX 1035 ; N threequarters ; G 128
+U 191 ; WX 580 ; N questiondown ; G 129
+U 192 ; WX 774 ; N Agrave ; G 130
+U 193 ; WX 774 ; N Aacute ; G 131
+U 194 ; WX 774 ; N Acircumflex ; G 132
+U 195 ; WX 774 ; N Atilde ; G 133
+U 196 ; WX 774 ; N Adieresis ; G 134
+U 197 ; WX 774 ; N Aring ; G 135
+U 198 ; WX 1085 ; N AE ; G 136
+U 199 ; WX 734 ; N Ccedilla ; G 137
+U 200 ; WX 683 ; N Egrave ; G 138
+U 201 ; WX 683 ; N Eacute ; G 139
+U 202 ; WX 683 ; N Ecircumflex ; G 140
+U 203 ; WX 683 ; N Edieresis ; G 141
+U 204 ; WX 372 ; N Igrave ; G 142
+U 205 ; WX 372 ; N Iacute ; G 143
+U 206 ; WX 372 ; N Icircumflex ; G 144
+U 207 ; WX 372 ; N Idieresis ; G 145
+U 208 ; WX 845 ; N Eth ; G 146
+U 209 ; WX 837 ; N Ntilde ; G 147
+U 210 ; WX 850 ; N Ograve ; G 148
+U 211 ; WX 850 ; N Oacute ; G 149
+U 212 ; WX 850 ; N Ocircumflex ; G 150
+U 213 ; WX 850 ; N Otilde ; G 151
+U 214 ; WX 850 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 850 ; N Oslash ; G 154
+U 217 ; WX 812 ; N Ugrave ; G 155
+U 218 ; WX 812 ; N Uacute ; G 156
+U 219 ; WX 812 ; N Ucircumflex ; G 157
+U 220 ; WX 812 ; N Udieresis ; G 158
+U 221 ; WX 724 ; N Yacute ; G 159
+U 222 ; WX 742 ; N Thorn ; G 160
+U 223 ; WX 719 ; N germandbls ; G 161
+U 224 ; WX 675 ; N agrave ; G 162
+U 225 ; WX 675 ; N aacute ; G 163
+U 226 ; WX 675 ; N acircumflex ; G 164
+U 227 ; WX 675 ; N atilde ; G 165
+U 228 ; WX 675 ; N adieresis ; G 166
+U 229 ; WX 675 ; N aring ; G 167
+U 230 ; WX 1048 ; N ae ; G 168
+U 231 ; WX 593 ; N ccedilla ; G 169
+U 232 ; WX 678 ; N egrave ; G 170
+U 233 ; WX 678 ; N eacute ; G 171
+U 234 ; WX 678 ; N ecircumflex ; G 172
+U 235 ; WX 678 ; N edieresis ; G 173
+U 236 ; WX 343 ; N igrave ; G 174
+U 237 ; WX 343 ; N iacute ; G 175
+U 238 ; WX 343 ; N icircumflex ; G 176
+U 239 ; WX 343 ; N idieresis ; G 177
+U 240 ; WX 687 ; N eth ; G 178
+U 241 ; WX 712 ; N ntilde ; G 179
+U 242 ; WX 687 ; N ograve ; G 180
+U 243 ; WX 687 ; N oacute ; G 181
+U 244 ; WX 687 ; N ocircumflex ; G 182
+U 245 ; WX 687 ; N otilde ; G 183
+U 246 ; WX 687 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 687 ; N oslash ; G 186
+U 249 ; WX 712 ; N ugrave ; G 187
+U 250 ; WX 712 ; N uacute ; G 188
+U 251 ; WX 712 ; N ucircumflex ; G 189
+U 252 ; WX 712 ; N udieresis ; G 190
+U 253 ; WX 652 ; N yacute ; G 191
+U 254 ; WX 716 ; N thorn ; G 192
+U 255 ; WX 652 ; N ydieresis ; G 193
+U 256 ; WX 774 ; N Amacron ; G 194
+U 257 ; WX 675 ; N amacron ; G 195
+U 258 ; WX 774 ; N Abreve ; G 196
+U 259 ; WX 675 ; N abreve ; G 197
+U 260 ; WX 774 ; N Aogonek ; G 198
+U 261 ; WX 675 ; N aogonek ; G 199
+U 262 ; WX 734 ; N Cacute ; G 200
+U 263 ; WX 593 ; N cacute ; G 201
+U 264 ; WX 734 ; N Ccircumflex ; G 202
+U 265 ; WX 593 ; N ccircumflex ; G 203
+U 266 ; WX 734 ; N Cdotaccent ; G 204
+U 267 ; WX 593 ; N cdotaccent ; G 205
+U 268 ; WX 734 ; N Ccaron ; G 206
+U 269 ; WX 593 ; N ccaron ; G 207
+U 270 ; WX 830 ; N Dcaron ; G 208
+U 271 ; WX 716 ; N dcaron ; G 209
+U 272 ; WX 845 ; N Dcroat ; G 210
+U 273 ; WX 716 ; N dmacron ; G 211
+U 274 ; WX 683 ; N Emacron ; G 212
+U 275 ; WX 678 ; N emacron ; G 213
+U 276 ; WX 683 ; N Ebreve ; G 214
+U 277 ; WX 678 ; N ebreve ; G 215
+U 278 ; WX 683 ; N Edotaccent ; G 216
+U 279 ; WX 678 ; N edotaccent ; G 217
+U 280 ; WX 683 ; N Eogonek ; G 218
+U 281 ; WX 678 ; N eogonek ; G 219
+U 282 ; WX 683 ; N Ecaron ; G 220
+U 283 ; WX 678 ; N ecaron ; G 221
+U 284 ; WX 821 ; N Gcircumflex ; G 222
+U 285 ; WX 716 ; N gcircumflex ; G 223
+U 286 ; WX 821 ; N Gbreve ; G 224
+U 287 ; WX 716 ; N gbreve ; G 225
+U 288 ; WX 821 ; N Gdotaccent ; G 226
+U 289 ; WX 716 ; N gdotaccent ; G 227
+U 290 ; WX 821 ; N Gcommaaccent ; G 228
+U 291 ; WX 716 ; N gcommaaccent ; G 229
+U 292 ; WX 837 ; N Hcircumflex ; G 230
+U 293 ; WX 712 ; N hcircumflex ; G 231
+U 294 ; WX 974 ; N Hbar ; G 232
+U 295 ; WX 790 ; N hbar ; G 233
+U 296 ; WX 372 ; N Itilde ; G 234
+U 297 ; WX 343 ; N itilde ; G 235
+U 298 ; WX 372 ; N Imacron ; G 236
+U 299 ; WX 343 ; N imacron ; G 237
+U 300 ; WX 372 ; N Ibreve ; G 238
+U 301 ; WX 343 ; N ibreve ; G 239
+U 302 ; WX 372 ; N Iogonek ; G 240
+U 303 ; WX 343 ; N iogonek ; G 241
+U 304 ; WX 372 ; N Idot ; G 242
+U 305 ; WX 343 ; N dotlessi ; G 243
+U 306 ; WX 744 ; N IJ ; G 244
+U 307 ; WX 686 ; N ij ; G 245
+U 308 ; WX 372 ; N Jcircumflex ; G 246
+U 309 ; WX 343 ; N jcircumflex ; G 247
+U 310 ; WX 775 ; N Kcommaaccent ; G 248
+U 311 ; WX 665 ; N kcommaaccent ; G 249
+U 312 ; WX 665 ; N kgreenlandic ; G 250
+U 313 ; WX 637 ; N Lacute ; G 251
+U 314 ; WX 343 ; N lacute ; G 252
+U 315 ; WX 637 ; N Lcommaaccent ; G 253
+U 316 ; WX 343 ; N lcommaaccent ; G 254
+U 317 ; WX 637 ; N Lcaron ; G 255
+U 318 ; WX 343 ; N lcaron ; G 256
+U 319 ; WX 637 ; N Ldot ; G 257
+U 320 ; WX 343 ; N ldot ; G 258
+U 321 ; WX 660 ; N Lslash ; G 259
+U 322 ; WX 375 ; N lslash ; G 260
+U 323 ; WX 837 ; N Nacute ; G 261
+U 324 ; WX 712 ; N nacute ; G 262
+U 325 ; WX 837 ; N Ncommaaccent ; G 263
+U 326 ; WX 712 ; N ncommaaccent ; G 264
+U 327 ; WX 837 ; N Ncaron ; G 265
+U 328 ; WX 712 ; N ncaron ; G 266
+U 329 ; WX 983 ; N napostrophe ; G 267
+U 330 ; WX 837 ; N Eng ; G 268
+U 331 ; WX 712 ; N eng ; G 269
+U 332 ; WX 850 ; N Omacron ; G 270
+U 333 ; WX 687 ; N omacron ; G 271
+U 334 ; WX 850 ; N Obreve ; G 272
+U 335 ; WX 687 ; N obreve ; G 273
+U 336 ; WX 850 ; N Ohungarumlaut ; G 274
+U 337 ; WX 687 ; N ohungarumlaut ; G 275
+U 338 ; WX 1167 ; N OE ; G 276
+U 339 ; WX 1094 ; N oe ; G 277
+U 340 ; WX 770 ; N Racute ; G 278
+U 341 ; WX 493 ; N racute ; G 279
+U 342 ; WX 770 ; N Rcommaaccent ; G 280
+U 343 ; WX 493 ; N rcommaaccent ; G 281
+U 344 ; WX 770 ; N Rcaron ; G 282
+U 345 ; WX 493 ; N rcaron ; G 283
+U 346 ; WX 720 ; N Sacute ; G 284
+U 347 ; WX 595 ; N sacute ; G 285
+U 348 ; WX 720 ; N Scircumflex ; G 286
+U 349 ; WX 595 ; N scircumflex ; G 287
+U 350 ; WX 720 ; N Scedilla ; G 288
+U 351 ; WX 595 ; N scedilla ; G 289
+U 352 ; WX 720 ; N Scaron ; G 290
+U 353 ; WX 595 ; N scaron ; G 291
+U 354 ; WX 682 ; N Tcommaaccent ; G 292
+U 355 ; WX 478 ; N tcommaaccent ; G 293
+U 356 ; WX 682 ; N Tcaron ; G 294
+U 357 ; WX 478 ; N tcaron ; G 295
+U 358 ; WX 682 ; N Tbar ; G 296
+U 359 ; WX 478 ; N tbar ; G 297
+U 360 ; WX 812 ; N Utilde ; G 298
+U 361 ; WX 712 ; N utilde ; G 299
+U 362 ; WX 812 ; N Umacron ; G 300
+U 363 ; WX 712 ; N umacron ; G 301
+U 364 ; WX 812 ; N Ubreve ; G 302
+U 365 ; WX 712 ; N ubreve ; G 303
+U 366 ; WX 812 ; N Uring ; G 304
+U 367 ; WX 712 ; N uring ; G 305
+U 368 ; WX 812 ; N Uhungarumlaut ; G 306
+U 369 ; WX 712 ; N uhungarumlaut ; G 307
+U 370 ; WX 812 ; N Uogonek ; G 308
+U 371 ; WX 712 ; N uogonek ; G 309
+U 372 ; WX 1103 ; N Wcircumflex ; G 310
+U 373 ; WX 924 ; N wcircumflex ; G 311
+U 374 ; WX 724 ; N Ycircumflex ; G 312
+U 375 ; WX 652 ; N ycircumflex ; G 313
+U 376 ; WX 724 ; N Ydieresis ; G 314
+U 377 ; WX 725 ; N Zacute ; G 315
+U 378 ; WX 582 ; N zacute ; G 316
+U 379 ; WX 725 ; N Zdotaccent ; G 317
+U 380 ; WX 582 ; N zdotaccent ; G 318
+U 381 ; WX 725 ; N Zcaron ; G 319
+U 382 ; WX 582 ; N zcaron ; G 320
+U 383 ; WX 435 ; N longs ; G 321
+U 384 ; WX 716 ; N uni0180 ; G 322
+U 385 ; WX 811 ; N uni0181 ; G 323
+U 386 ; WX 762 ; N uni0182 ; G 324
+U 387 ; WX 716 ; N uni0183 ; G 325
+U 388 ; WX 762 ; N uni0184 ; G 326
+U 389 ; WX 716 ; N uni0185 ; G 327
+U 390 ; WX 734 ; N uni0186 ; G 328
+U 391 ; WX 734 ; N uni0187 ; G 329
+U 392 ; WX 593 ; N uni0188 ; G 330
+U 393 ; WX 845 ; N uni0189 ; G 331
+U 394 ; WX 879 ; N uni018A ; G 332
+U 395 ; WX 762 ; N uni018B ; G 333
+U 396 ; WX 716 ; N uni018C ; G 334
+U 397 ; WX 687 ; N uni018D ; G 335
+U 398 ; WX 683 ; N uni018E ; G 336
+U 399 ; WX 850 ; N uni018F ; G 337
+U 400 ; WX 696 ; N uni0190 ; G 338
+U 401 ; WX 683 ; N uni0191 ; G 339
+U 402 ; WX 435 ; N florin ; G 340
+U 403 ; WX 821 ; N uni0193 ; G 341
+U 404 ; WX 793 ; N uni0194 ; G 342
+U 405 ; WX 1045 ; N uni0195 ; G 343
+U 406 ; WX 436 ; N uni0196 ; G 344
+U 407 ; WX 389 ; N uni0197 ; G 345
+U 408 ; WX 775 ; N uni0198 ; G 346
+U 409 ; WX 665 ; N uni0199 ; G 347
+U 410 ; WX 360 ; N uni019A ; G 348
+U 411 ; WX 592 ; N uni019B ; G 349
+U 412 ; WX 1042 ; N uni019C ; G 350
+U 413 ; WX 837 ; N uni019D ; G 351
+U 414 ; WX 712 ; N uni019E ; G 352
+U 415 ; WX 850 ; N uni019F ; G 353
+U 416 ; WX 850 ; N Ohorn ; G 354
+U 417 ; WX 687 ; N ohorn ; G 355
+U 418 ; WX 1114 ; N uni01A2 ; G 356
+U 419 ; WX 962 ; N uni01A3 ; G 357
+U 420 ; WX 782 ; N uni01A4 ; G 358
+U 421 ; WX 716 ; N uni01A5 ; G 359
+U 422 ; WX 770 ; N uni01A6 ; G 360
+U 423 ; WX 720 ; N uni01A7 ; G 361
+U 424 ; WX 595 ; N uni01A8 ; G 362
+U 425 ; WX 683 ; N uni01A9 ; G 363
+U 426 ; WX 552 ; N uni01AA ; G 364
+U 427 ; WX 478 ; N uni01AB ; G 365
+U 428 ; WX 707 ; N uni01AC ; G 366
+U 429 ; WX 478 ; N uni01AD ; G 367
+U 430 ; WX 682 ; N uni01AE ; G 368
+U 431 ; WX 812 ; N Uhorn ; G 369
+U 432 ; WX 712 ; N uhorn ; G 370
+U 433 ; WX 769 ; N uni01B1 ; G 371
+U 434 ; WX 813 ; N uni01B2 ; G 372
+U 435 ; WX 797 ; N uni01B3 ; G 373
+U 436 ; WX 778 ; N uni01B4 ; G 374
+U 437 ; WX 725 ; N uni01B5 ; G 375
+U 438 ; WX 582 ; N uni01B6 ; G 376
+U 439 ; WX 772 ; N uni01B7 ; G 377
+U 440 ; WX 772 ; N uni01B8 ; G 378
+U 441 ; WX 641 ; N uni01B9 ; G 379
+U 442 ; WX 582 ; N uni01BA ; G 380
+U 443 ; WX 696 ; N uni01BB ; G 381
+U 444 ; WX 772 ; N uni01BC ; G 382
+U 445 ; WX 641 ; N uni01BD ; G 383
+U 446 ; WX 573 ; N uni01BE ; G 384
+U 447 ; WX 716 ; N uni01BF ; G 385
+U 448 ; WX 372 ; N uni01C0 ; G 386
+U 449 ; WX 659 ; N uni01C1 ; G 387
+U 450 ; WX 544 ; N uni01C2 ; G 388
+U 451 ; WX 372 ; N uni01C3 ; G 389
+U 452 ; WX 1548 ; N uni01C4 ; G 390
+U 453 ; WX 1450 ; N uni01C5 ; G 391
+U 454 ; WX 1307 ; N uni01C6 ; G 392
+U 455 ; WX 977 ; N uni01C7 ; G 393
+U 456 ; WX 979 ; N uni01C8 ; G 394
+U 457 ; WX 670 ; N uni01C9 ; G 395
+U 458 ; WX 1193 ; N uni01CA ; G 396
+U 459 ; WX 1213 ; N uni01CB ; G 397
+U 460 ; WX 1063 ; N uni01CC ; G 398
+U 461 ; WX 774 ; N uni01CD ; G 399
+U 462 ; WX 675 ; N uni01CE ; G 400
+U 463 ; WX 372 ; N uni01CF ; G 401
+U 464 ; WX 343 ; N uni01D0 ; G 402
+U 465 ; WX 850 ; N uni01D1 ; G 403
+U 466 ; WX 687 ; N uni01D2 ; G 404
+U 467 ; WX 812 ; N uni01D3 ; G 405
+U 468 ; WX 712 ; N uni01D4 ; G 406
+U 469 ; WX 812 ; N uni01D5 ; G 407
+U 470 ; WX 712 ; N uni01D6 ; G 408
+U 471 ; WX 812 ; N uni01D7 ; G 409
+U 472 ; WX 712 ; N uni01D8 ; G 410
+U 473 ; WX 812 ; N uni01D9 ; G 411
+U 474 ; WX 712 ; N uni01DA ; G 412
+U 475 ; WX 812 ; N uni01DB ; G 413
+U 476 ; WX 712 ; N uni01DC ; G 414
+U 477 ; WX 678 ; N uni01DD ; G 415
+U 478 ; WX 774 ; N uni01DE ; G 416
+U 479 ; WX 675 ; N uni01DF ; G 417
+U 480 ; WX 774 ; N uni01E0 ; G 418
+U 481 ; WX 675 ; N uni01E1 ; G 419
+U 482 ; WX 1085 ; N uni01E2 ; G 420
+U 483 ; WX 1048 ; N uni01E3 ; G 421
+U 484 ; WX 821 ; N uni01E4 ; G 422
+U 485 ; WX 716 ; N uni01E5 ; G 423
+U 486 ; WX 821 ; N Gcaron ; G 424
+U 487 ; WX 716 ; N gcaron ; G 425
+U 488 ; WX 775 ; N uni01E8 ; G 426
+U 489 ; WX 665 ; N uni01E9 ; G 427
+U 490 ; WX 850 ; N uni01EA ; G 428
+U 491 ; WX 687 ; N uni01EB ; G 429
+U 492 ; WX 850 ; N uni01EC ; G 430
+U 493 ; WX 687 ; N uni01ED ; G 431
+U 494 ; WX 772 ; N uni01EE ; G 432
+U 495 ; WX 582 ; N uni01EF ; G 433
+U 496 ; WX 343 ; N uni01F0 ; G 434
+U 497 ; WX 1548 ; N uni01F1 ; G 435
+U 498 ; WX 1450 ; N uni01F2 ; G 436
+U 499 ; WX 1307 ; N uni01F3 ; G 437
+U 500 ; WX 821 ; N uni01F4 ; G 438
+U 501 ; WX 716 ; N uni01F5 ; G 439
+U 502 ; WX 1289 ; N uni01F6 ; G 440
+U 503 ; WX 787 ; N uni01F7 ; G 441
+U 504 ; WX 837 ; N uni01F8 ; G 442
+U 505 ; WX 712 ; N uni01F9 ; G 443
+U 506 ; WX 774 ; N Aringacute ; G 444
+U 507 ; WX 675 ; N aringacute ; G 445
+U 508 ; WX 1085 ; N AEacute ; G 446
+U 509 ; WX 1048 ; N aeacute ; G 447
+U 510 ; WX 850 ; N Oslashacute ; G 448
+U 511 ; WX 687 ; N oslashacute ; G 449
+U 512 ; WX 774 ; N uni0200 ; G 450
+U 513 ; WX 675 ; N uni0201 ; G 451
+U 514 ; WX 774 ; N uni0202 ; G 452
+U 515 ; WX 675 ; N uni0203 ; G 453
+U 516 ; WX 683 ; N uni0204 ; G 454
+U 517 ; WX 678 ; N uni0205 ; G 455
+U 518 ; WX 683 ; N uni0206 ; G 456
+U 519 ; WX 678 ; N uni0207 ; G 457
+U 520 ; WX 372 ; N uni0208 ; G 458
+U 521 ; WX 343 ; N uni0209 ; G 459
+U 522 ; WX 372 ; N uni020A ; G 460
+U 523 ; WX 343 ; N uni020B ; G 461
+U 524 ; WX 850 ; N uni020C ; G 462
+U 525 ; WX 687 ; N uni020D ; G 463
+U 526 ; WX 850 ; N uni020E ; G 464
+U 527 ; WX 687 ; N uni020F ; G 465
+U 528 ; WX 770 ; N uni0210 ; G 466
+U 529 ; WX 493 ; N uni0211 ; G 467
+U 530 ; WX 770 ; N uni0212 ; G 468
+U 531 ; WX 493 ; N uni0213 ; G 469
+U 532 ; WX 812 ; N uni0214 ; G 470
+U 533 ; WX 712 ; N uni0215 ; G 471
+U 534 ; WX 812 ; N uni0216 ; G 472
+U 535 ; WX 712 ; N uni0217 ; G 473
+U 536 ; WX 720 ; N Scommaaccent ; G 474
+U 537 ; WX 595 ; N scommaaccent ; G 475
+U 538 ; WX 682 ; N uni021A ; G 476
+U 539 ; WX 478 ; N uni021B ; G 477
+U 540 ; WX 690 ; N uni021C ; G 478
+U 541 ; WX 607 ; N uni021D ; G 479
+U 542 ; WX 837 ; N uni021E ; G 480
+U 543 ; WX 712 ; N uni021F ; G 481
+U 544 ; WX 837 ; N uni0220 ; G 482
+U 545 ; WX 865 ; N uni0221 ; G 483
+U 546 ; WX 809 ; N uni0222 ; G 484
+U 547 ; WX 659 ; N uni0223 ; G 485
+U 548 ; WX 725 ; N uni0224 ; G 486
+U 549 ; WX 582 ; N uni0225 ; G 487
+U 550 ; WX 774 ; N uni0226 ; G 488
+U 551 ; WX 675 ; N uni0227 ; G 489
+U 552 ; WX 683 ; N uni0228 ; G 490
+U 553 ; WX 678 ; N uni0229 ; G 491
+U 554 ; WX 850 ; N uni022A ; G 492
+U 555 ; WX 687 ; N uni022B ; G 493
+U 556 ; WX 850 ; N uni022C ; G 494
+U 557 ; WX 687 ; N uni022D ; G 495
+U 558 ; WX 850 ; N uni022E ; G 496
+U 559 ; WX 687 ; N uni022F ; G 497
+U 560 ; WX 850 ; N uni0230 ; G 498
+U 561 ; WX 687 ; N uni0231 ; G 499
+U 562 ; WX 724 ; N uni0232 ; G 500
+U 563 ; WX 652 ; N uni0233 ; G 501
+U 564 ; WX 492 ; N uni0234 ; G 502
+U 565 ; WX 867 ; N uni0235 ; G 503
+U 566 ; WX 512 ; N uni0236 ; G 504
+U 567 ; WX 343 ; N dotlessj ; G 505
+U 568 ; WX 1088 ; N uni0238 ; G 506
+U 569 ; WX 1088 ; N uni0239 ; G 507
+U 570 ; WX 774 ; N uni023A ; G 508
+U 571 ; WX 734 ; N uni023B ; G 509
+U 572 ; WX 593 ; N uni023C ; G 510
+U 573 ; WX 637 ; N uni023D ; G 511
+U 574 ; WX 682 ; N uni023E ; G 512
+U 575 ; WX 595 ; N uni023F ; G 513
+U 576 ; WX 582 ; N uni0240 ; G 514
+U 577 ; WX 782 ; N uni0241 ; G 515
+U 578 ; WX 614 ; N uni0242 ; G 516
+U 579 ; WX 762 ; N uni0243 ; G 517
+U 580 ; WX 812 ; N uni0244 ; G 518
+U 581 ; WX 774 ; N uni0245 ; G 519
+U 582 ; WX 683 ; N uni0246 ; G 520
+U 583 ; WX 678 ; N uni0247 ; G 521
+U 584 ; WX 372 ; N uni0248 ; G 522
+U 585 ; WX 343 ; N uni0249 ; G 523
+U 586 ; WX 860 ; N uni024A ; G 524
+U 587 ; WX 791 ; N uni024B ; G 525
+U 588 ; WX 770 ; N uni024C ; G 526
+U 589 ; WX 493 ; N uni024D ; G 527
+U 590 ; WX 724 ; N uni024E ; G 528
+U 591 ; WX 652 ; N uni024F ; G 529
+U 592 ; WX 675 ; N uni0250 ; G 530
+U 593 ; WX 716 ; N uni0251 ; G 531
+U 594 ; WX 716 ; N uni0252 ; G 532
+U 595 ; WX 716 ; N uni0253 ; G 533
+U 596 ; WX 593 ; N uni0254 ; G 534
+U 597 ; WX 593 ; N uni0255 ; G 535
+U 598 ; WX 791 ; N uni0256 ; G 536
+U 599 ; WX 792 ; N uni0257 ; G 537
+U 600 ; WX 678 ; N uni0258 ; G 538
+U 601 ; WX 678 ; N uni0259 ; G 539
+U 602 ; WX 876 ; N uni025A ; G 540
+U 603 ; WX 557 ; N uni025B ; G 541
+U 604 ; WX 545 ; N uni025C ; G 542
+U 605 ; WX 774 ; N uni025D ; G 543
+U 606 ; WX 731 ; N uni025E ; G 544
+U 607 ; WX 343 ; N uni025F ; G 545
+U 608 ; WX 792 ; N uni0260 ; G 546
+U 609 ; WX 716 ; N uni0261 ; G 547
+U 610 ; WX 627 ; N uni0262 ; G 548
+U 611 ; WX 735 ; N uni0263 ; G 549
+U 612 ; WX 635 ; N uni0264 ; G 550
+U 613 ; WX 712 ; N uni0265 ; G 551
+U 614 ; WX 712 ; N uni0266 ; G 552
+U 615 ; WX 712 ; N uni0267 ; G 553
+U 616 ; WX 545 ; N uni0268 ; G 554
+U 617 ; WX 440 ; N uni0269 ; G 555
+U 618 ; WX 545 ; N uni026A ; G 556
+U 619 ; WX 559 ; N uni026B ; G 557
+U 620 ; WX 693 ; N uni026C ; G 558
+U 621 ; WX 343 ; N uni026D ; G 559
+U 622 ; WX 841 ; N uni026E ; G 560
+U 623 ; WX 1042 ; N uni026F ; G 561
+U 624 ; WX 1042 ; N uni0270 ; G 562
+U 625 ; WX 1042 ; N uni0271 ; G 563
+U 626 ; WX 712 ; N uni0272 ; G 564
+U 627 ; WX 793 ; N uni0273 ; G 565
+U 628 ; WX 642 ; N uni0274 ; G 566
+U 629 ; WX 687 ; N uni0275 ; G 567
+U 630 ; WX 909 ; N uni0276 ; G 568
+U 631 ; WX 682 ; N uni0277 ; G 569
+U 632 ; WX 796 ; N uni0278 ; G 570
+U 633 ; WX 538 ; N uni0279 ; G 571
+U 634 ; WX 538 ; N uni027A ; G 572
+U 635 ; WX 650 ; N uni027B ; G 573
+U 636 ; WX 493 ; N uni027C ; G 574
+U 637 ; WX 493 ; N uni027D ; G 575
+U 638 ; WX 596 ; N uni027E ; G 576
+U 639 ; WX 596 ; N uni027F ; G 577
+U 640 ; WX 642 ; N uni0280 ; G 578
+U 641 ; WX 642 ; N uni0281 ; G 579
+U 642 ; WX 595 ; N uni0282 ; G 580
+U 643 ; WX 415 ; N uni0283 ; G 581
+U 644 ; WX 435 ; N uni0284 ; G 582
+U 645 ; WX 605 ; N uni0285 ; G 583
+U 646 ; WX 552 ; N uni0286 ; G 584
+U 647 ; WX 478 ; N uni0287 ; G 585
+U 648 ; WX 478 ; N uni0288 ; G 586
+U 649 ; WX 920 ; N uni0289 ; G 587
+U 650 ; WX 769 ; N uni028A ; G 588
+U 651 ; WX 670 ; N uni028B ; G 589
+U 652 ; WX 652 ; N uni028C ; G 590
+U 653 ; WX 924 ; N uni028D ; G 591
+U 654 ; WX 652 ; N uni028E ; G 592
+U 655 ; WX 724 ; N uni028F ; G 593
+U 656 ; WX 694 ; N uni0290 ; G 594
+U 657 ; WX 684 ; N uni0291 ; G 595
+U 658 ; WX 641 ; N uni0292 ; G 596
+U 659 ; WX 641 ; N uni0293 ; G 597
+U 660 ; WX 573 ; N uni0294 ; G 598
+U 661 ; WX 573 ; N uni0295 ; G 599
+U 662 ; WX 573 ; N uni0296 ; G 600
+U 663 ; WX 573 ; N uni0297 ; G 601
+U 664 ; WX 850 ; N uni0298 ; G 602
+U 665 ; WX 633 ; N uni0299 ; G 603
+U 666 ; WX 731 ; N uni029A ; G 604
+U 667 ; WX 685 ; N uni029B ; G 605
+U 668 ; WX 691 ; N uni029C ; G 606
+U 669 ; WX 343 ; N uni029D ; G 607
+U 670 ; WX 732 ; N uni029E ; G 608
+U 671 ; WX 539 ; N uni029F ; G 609
+U 672 ; WX 792 ; N uni02A0 ; G 610
+U 673 ; WX 573 ; N uni02A1 ; G 611
+U 674 ; WX 573 ; N uni02A2 ; G 612
+U 675 ; WX 1156 ; N uni02A3 ; G 613
+U 676 ; WX 1214 ; N uni02A4 ; G 614
+U 677 ; WX 1155 ; N uni02A5 ; G 615
+U 678 ; WX 975 ; N uni02A6 ; G 616
+U 679 ; WX 769 ; N uni02A7 ; G 617
+U 680 ; WX 929 ; N uni02A8 ; G 618
+U 681 ; WX 1026 ; N uni02A9 ; G 619
+U 682 ; WX 862 ; N uni02AA ; G 620
+U 683 ; WX 780 ; N uni02AB ; G 621
+U 684 ; WX 591 ; N uni02AC ; G 622
+U 685 ; WX 415 ; N uni02AD ; G 623
+U 686 ; WX 677 ; N uni02AE ; G 624
+U 687 ; WX 789 ; N uni02AF ; G 625
+U 688 ; WX 456 ; N uni02B0 ; G 626
+U 689 ; WX 456 ; N uni02B1 ; G 627
+U 690 ; WX 219 ; N uni02B2 ; G 628
+U 691 ; WX 315 ; N uni02B3 ; G 629
+U 692 ; WX 315 ; N uni02B4 ; G 630
+U 693 ; WX 315 ; N uni02B5 ; G 631
+U 694 ; WX 411 ; N uni02B6 ; G 632
+U 695 ; WX 591 ; N uni02B7 ; G 633
+U 696 ; WX 417 ; N uni02B8 ; G 634
+U 697 ; WX 302 ; N uni02B9 ; G 635
+U 698 ; WX 521 ; N uni02BA ; G 636
+U 699 ; WX 380 ; N uni02BB ; G 637
+U 700 ; WX 380 ; N uni02BC ; G 638
+U 701 ; WX 380 ; N uni02BD ; G 639
+U 702 ; WX 366 ; N uni02BE ; G 640
+U 703 ; WX 366 ; N uni02BF ; G 641
+U 704 ; WX 326 ; N uni02C0 ; G 642
+U 705 ; WX 326 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 306 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 306 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 718 ; WX 500 ; N uni02CE ; G 656
+U 719 ; WX 500 ; N uni02CF ; G 657
+U 720 ; WX 337 ; N uni02D0 ; G 658
+U 721 ; WX 337 ; N uni02D1 ; G 659
+U 722 ; WX 366 ; N uni02D2 ; G 660
+U 723 ; WX 366 ; N uni02D3 ; G 661
+U 724 ; WX 500 ; N uni02D4 ; G 662
+U 725 ; WX 500 ; N uni02D5 ; G 663
+U 726 ; WX 416 ; N uni02D6 ; G 664
+U 727 ; WX 328 ; N uni02D7 ; G 665
+U 728 ; WX 500 ; N breve ; G 666
+U 729 ; WX 500 ; N dotaccent ; G 667
+U 730 ; WX 500 ; N ring ; G 668
+U 731 ; WX 500 ; N ogonek ; G 669
+U 732 ; WX 500 ; N tilde ; G 670
+U 733 ; WX 500 ; N hungarumlaut ; G 671
+U 734 ; WX 351 ; N uni02DE ; G 672
+U 735 ; WX 500 ; N uni02DF ; G 673
+U 736 ; WX 412 ; N uni02E0 ; G 674
+U 737 ; WX 219 ; N uni02E1 ; G 675
+U 738 ; WX 381 ; N uni02E2 ; G 676
+U 739 ; WX 413 ; N uni02E3 ; G 677
+U 740 ; WX 326 ; N uni02E4 ; G 678
+U 741 ; WX 500 ; N uni02E5 ; G 679
+U 742 ; WX 500 ; N uni02E6 ; G 680
+U 743 ; WX 500 ; N uni02E7 ; G 681
+U 744 ; WX 500 ; N uni02E8 ; G 682
+U 745 ; WX 500 ; N uni02E9 ; G 683
+U 748 ; WX 500 ; N uni02EC ; G 684
+U 749 ; WX 500 ; N uni02ED ; G 685
+U 750 ; WX 644 ; N uni02EE ; G 686
+U 755 ; WX 500 ; N uni02F3 ; G 687
+U 759 ; WX 500 ; N uni02F7 ; G 688
+U 768 ; WX 0 ; N gravecomb ; G 689
+U 769 ; WX 0 ; N acutecomb ; G 690
+U 770 ; WX 0 ; N uni0302 ; G 691
+U 771 ; WX 0 ; N tildecomb ; G 692
+U 772 ; WX 0 ; N uni0304 ; G 693
+U 773 ; WX 0 ; N uni0305 ; G 694
+U 774 ; WX 0 ; N uni0306 ; G 695
+U 775 ; WX 0 ; N uni0307 ; G 696
+U 776 ; WX 0 ; N uni0308 ; G 697
+U 777 ; WX 0 ; N hookabovecomb ; G 698
+U 778 ; WX 0 ; N uni030A ; G 699
+U 779 ; WX 0 ; N uni030B ; G 700
+U 780 ; WX 0 ; N uni030C ; G 701
+U 781 ; WX 0 ; N uni030D ; G 702
+U 782 ; WX 0 ; N uni030E ; G 703
+U 783 ; WX 0 ; N uni030F ; G 704
+U 784 ; WX 0 ; N uni0310 ; G 705
+U 785 ; WX 0 ; N uni0311 ; G 706
+U 786 ; WX 0 ; N uni0312 ; G 707
+U 787 ; WX 0 ; N uni0313 ; G 708
+U 788 ; WX 0 ; N uni0314 ; G 709
+U 789 ; WX 0 ; N uni0315 ; G 710
+U 790 ; WX 0 ; N uni0316 ; G 711
+U 791 ; WX 0 ; N uni0317 ; G 712
+U 792 ; WX 0 ; N uni0318 ; G 713
+U 793 ; WX 0 ; N uni0319 ; G 714
+U 794 ; WX 0 ; N uni031A ; G 715
+U 795 ; WX 0 ; N uni031B ; G 716
+U 796 ; WX 0 ; N uni031C ; G 717
+U 797 ; WX 0 ; N uni031D ; G 718
+U 798 ; WX 0 ; N uni031E ; G 719
+U 799 ; WX 0 ; N uni031F ; G 720
+U 800 ; WX 0 ; N uni0320 ; G 721
+U 801 ; WX 0 ; N uni0321 ; G 722
+U 802 ; WX 0 ; N uni0322 ; G 723
+U 803 ; WX 0 ; N dotbelowcomb ; G 724
+U 804 ; WX 0 ; N uni0324 ; G 725
+U 805 ; WX 0 ; N uni0325 ; G 726
+U 806 ; WX 0 ; N uni0326 ; G 727
+U 807 ; WX 0 ; N uni0327 ; G 728
+U 808 ; WX 0 ; N uni0328 ; G 729
+U 809 ; WX 0 ; N uni0329 ; G 730
+U 810 ; WX 0 ; N uni032A ; G 731
+U 811 ; WX 0 ; N uni032B ; G 732
+U 812 ; WX 0 ; N uni032C ; G 733
+U 813 ; WX 0 ; N uni032D ; G 734
+U 814 ; WX 0 ; N uni032E ; G 735
+U 815 ; WX 0 ; N uni032F ; G 736
+U 816 ; WX 0 ; N uni0330 ; G 737
+U 817 ; WX 0 ; N uni0331 ; G 738
+U 818 ; WX 0 ; N uni0332 ; G 739
+U 819 ; WX 0 ; N uni0333 ; G 740
+U 820 ; WX 0 ; N uni0334 ; G 741
+U 821 ; WX 0 ; N uni0335 ; G 742
+U 822 ; WX 0 ; N uni0336 ; G 743
+U 823 ; WX 0 ; N uni0337 ; G 744
+U 824 ; WX 0 ; N uni0338 ; G 745
+U 825 ; WX 0 ; N uni0339 ; G 746
+U 826 ; WX 0 ; N uni033A ; G 747
+U 827 ; WX 0 ; N uni033B ; G 748
+U 828 ; WX 0 ; N uni033C ; G 749
+U 829 ; WX 0 ; N uni033D ; G 750
+U 830 ; WX 0 ; N uni033E ; G 751
+U 831 ; WX 0 ; N uni033F ; G 752
+U 832 ; WX 0 ; N uni0340 ; G 753
+U 833 ; WX 0 ; N uni0341 ; G 754
+U 834 ; WX 0 ; N uni0342 ; G 755
+U 835 ; WX 0 ; N uni0343 ; G 756
+U 836 ; WX 0 ; N uni0344 ; G 757
+U 837 ; WX 0 ; N uni0345 ; G 758
+U 838 ; WX 0 ; N uni0346 ; G 759
+U 839 ; WX 0 ; N uni0347 ; G 760
+U 840 ; WX 0 ; N uni0348 ; G 761
+U 841 ; WX 0 ; N uni0349 ; G 762
+U 842 ; WX 0 ; N uni034A ; G 763
+U 843 ; WX 0 ; N uni034B ; G 764
+U 844 ; WX 0 ; N uni034C ; G 765
+U 845 ; WX 0 ; N uni034D ; G 766
+U 846 ; WX 0 ; N uni034E ; G 767
+U 847 ; WX 0 ; N uni034F ; G 768
+U 849 ; WX 0 ; N uni0351 ; G 769
+U 850 ; WX 0 ; N uni0352 ; G 770
+U 851 ; WX 0 ; N uni0353 ; G 771
+U 855 ; WX 0 ; N uni0357 ; G 772
+U 856 ; WX 0 ; N uni0358 ; G 773
+U 858 ; WX 0 ; N uni035A ; G 774
+U 860 ; WX 0 ; N uni035C ; G 775
+U 861 ; WX 0 ; N uni035D ; G 776
+U 862 ; WX 0 ; N uni035E ; G 777
+U 863 ; WX 0 ; N uni035F ; G 778
+U 864 ; WX 0 ; N uni0360 ; G 779
+U 865 ; WX 0 ; N uni0361 ; G 780
+U 866 ; WX 0 ; N uni0362 ; G 781
+U 880 ; WX 698 ; N uni0370 ; G 782
+U 881 ; WX 565 ; N uni0371 ; G 783
+U 882 ; WX 1022 ; N uni0372 ; G 784
+U 883 ; WX 836 ; N uni0373 ; G 785
+U 884 ; WX 302 ; N uni0374 ; G 786
+U 885 ; WX 302 ; N uni0375 ; G 787
+U 886 ; WX 837 ; N uni0376 ; G 788
+U 887 ; WX 701 ; N uni0377 ; G 789
+U 890 ; WX 500 ; N uni037A ; G 790
+U 891 ; WX 593 ; N uni037B ; G 791
+U 892 ; WX 550 ; N uni037C ; G 792
+U 893 ; WX 549 ; N uni037D ; G 793
+U 894 ; WX 400 ; N uni037E ; G 794
+U 895 ; WX 372 ; N uni037F ; G 795
+U 900 ; WX 441 ; N tonos ; G 796
+U 901 ; WX 500 ; N dieresistonos ; G 797
+U 902 ; WX 797 ; N Alphatonos ; G 798
+U 903 ; WX 380 ; N anoteleia ; G 799
+U 904 ; WX 846 ; N Epsilontonos ; G 800
+U 905 ; WX 1009 ; N Etatonos ; G 801
+U 906 ; WX 563 ; N Iotatonos ; G 802
+U 908 ; WX 891 ; N Omicrontonos ; G 803
+U 910 ; WX 980 ; N Upsilontonos ; G 804
+U 911 ; WX 894 ; N Omegatonos ; G 805
+U 912 ; WX 390 ; N iotadieresistonos ; G 806
+U 913 ; WX 774 ; N Alpha ; G 807
+U 914 ; WX 762 ; N Beta ; G 808
+U 915 ; WX 637 ; N Gamma ; G 809
+U 916 ; WX 774 ; N uni0394 ; G 810
+U 917 ; WX 683 ; N Epsilon ; G 811
+U 918 ; WX 725 ; N Zeta ; G 812
+U 919 ; WX 837 ; N Eta ; G 813
+U 920 ; WX 850 ; N Theta ; G 814
+U 921 ; WX 372 ; N Iota ; G 815
+U 922 ; WX 775 ; N Kappa ; G 816
+U 923 ; WX 774 ; N Lambda ; G 817
+U 924 ; WX 995 ; N Mu ; G 818
+U 925 ; WX 837 ; N Nu ; G 819
+U 926 ; WX 632 ; N Xi ; G 820
+U 927 ; WX 850 ; N Omicron ; G 821
+U 928 ; WX 837 ; N Pi ; G 822
+U 929 ; WX 733 ; N Rho ; G 823
+U 931 ; WX 683 ; N Sigma ; G 824
+U 932 ; WX 682 ; N Tau ; G 825
+U 933 ; WX 724 ; N Upsilon ; G 826
+U 934 ; WX 850 ; N Phi ; G 827
+U 935 ; WX 771 ; N Chi ; G 828
+U 936 ; WX 850 ; N Psi ; G 829
+U 937 ; WX 850 ; N Omega ; G 830
+U 938 ; WX 372 ; N Iotadieresis ; G 831
+U 939 ; WX 724 ; N Upsilondieresis ; G 832
+U 940 ; WX 687 ; N alphatonos ; G 833
+U 941 ; WX 557 ; N epsilontonos ; G 834
+U 942 ; WX 712 ; N etatonos ; G 835
+U 943 ; WX 390 ; N iotatonos ; G 836
+U 944 ; WX 675 ; N upsilondieresistonos ; G 837
+U 945 ; WX 687 ; N alpha ; G 838
+U 946 ; WX 716 ; N beta ; G 839
+U 947 ; WX 681 ; N gamma ; G 840
+U 948 ; WX 687 ; N delta ; G 841
+U 949 ; WX 557 ; N epsilon ; G 842
+U 950 ; WX 591 ; N zeta ; G 843
+U 951 ; WX 712 ; N eta ; G 844
+U 952 ; WX 687 ; N theta ; G 845
+U 953 ; WX 390 ; N iota ; G 846
+U 954 ; WX 710 ; N kappa ; G 847
+U 955 ; WX 633 ; N lambda ; G 848
+U 956 ; WX 736 ; N uni03BC ; G 849
+U 957 ; WX 681 ; N nu ; G 850
+U 958 ; WX 591 ; N xi ; G 851
+U 959 ; WX 687 ; N omicron ; G 852
+U 960 ; WX 791 ; N pi ; G 853
+U 961 ; WX 716 ; N rho ; G 854
+U 962 ; WX 593 ; N sigma1 ; G 855
+U 963 ; WX 779 ; N sigma ; G 856
+U 964 ; WX 638 ; N tau ; G 857
+U 965 ; WX 675 ; N upsilon ; G 858
+U 966 ; WX 782 ; N phi ; G 859
+U 967 ; WX 645 ; N chi ; G 860
+U 968 ; WX 794 ; N psi ; G 861
+U 969 ; WX 869 ; N omega ; G 862
+U 970 ; WX 390 ; N iotadieresis ; G 863
+U 971 ; WX 675 ; N upsilondieresis ; G 864
+U 972 ; WX 687 ; N omicrontonos ; G 865
+U 973 ; WX 675 ; N upsilontonos ; G 866
+U 974 ; WX 869 ; N omegatonos ; G 867
+U 975 ; WX 775 ; N uni03CF ; G 868
+U 976 ; WX 651 ; N uni03D0 ; G 869
+U 977 ; WX 661 ; N theta1 ; G 870
+U 978 ; WX 746 ; N Upsilon1 ; G 871
+U 979 ; WX 981 ; N uni03D3 ; G 872
+U 980 ; WX 746 ; N uni03D4 ; G 873
+U 981 ; WX 796 ; N phi1 ; G 874
+U 982 ; WX 869 ; N omega1 ; G 875
+U 983 ; WX 744 ; N uni03D7 ; G 876
+U 984 ; WX 850 ; N uni03D8 ; G 877
+U 985 ; WX 687 ; N uni03D9 ; G 878
+U 986 ; WX 734 ; N uni03DA ; G 879
+U 987 ; WX 593 ; N uni03DB ; G 880
+U 988 ; WX 683 ; N uni03DC ; G 881
+U 989 ; WX 494 ; N uni03DD ; G 882
+U 990 ; WX 702 ; N uni03DE ; G 883
+U 991 ; WX 660 ; N uni03DF ; G 884
+U 992 ; WX 919 ; N uni03E0 ; G 885
+U 993 ; WX 627 ; N uni03E1 ; G 886
+U 994 ; WX 1093 ; N uni03E2 ; G 887
+U 995 ; WX 837 ; N uni03E3 ; G 888
+U 996 ; WX 832 ; N uni03E4 ; G 889
+U 997 ; WX 716 ; N uni03E5 ; G 890
+U 998 ; WX 928 ; N uni03E6 ; G 891
+U 999 ; WX 744 ; N uni03E7 ; G 892
+U 1000 ; WX 733 ; N uni03E8 ; G 893
+U 1001 ; WX 650 ; N uni03E9 ; G 894
+U 1002 ; WX 789 ; N uni03EA ; G 895
+U 1003 ; WX 671 ; N uni03EB ; G 896
+U 1004 ; WX 752 ; N uni03EC ; G 897
+U 1005 ; WX 716 ; N uni03ED ; G 898
+U 1006 ; WX 682 ; N uni03EE ; G 899
+U 1007 ; WX 590 ; N uni03EF ; G 900
+U 1008 ; WX 744 ; N uni03F0 ; G 901
+U 1009 ; WX 716 ; N uni03F1 ; G 902
+U 1010 ; WX 593 ; N uni03F2 ; G 903
+U 1011 ; WX 343 ; N uni03F3 ; G 904
+U 1012 ; WX 850 ; N uni03F4 ; G 905
+U 1013 ; WX 645 ; N uni03F5 ; G 906
+U 1014 ; WX 645 ; N uni03F6 ; G 907
+U 1015 ; WX 742 ; N uni03F7 ; G 908
+U 1016 ; WX 716 ; N uni03F8 ; G 909
+U 1017 ; WX 734 ; N uni03F9 ; G 910
+U 1018 ; WX 995 ; N uni03FA ; G 911
+U 1019 ; WX 732 ; N uni03FB ; G 912
+U 1020 ; WX 716 ; N uni03FC ; G 913
+U 1021 ; WX 734 ; N uni03FD ; G 914
+U 1022 ; WX 734 ; N uni03FE ; G 915
+U 1023 ; WX 698 ; N uni03FF ; G 916
+U 1024 ; WX 683 ; N uni0400 ; G 917
+U 1025 ; WX 683 ; N uni0401 ; G 918
+U 1026 ; WX 878 ; N uni0402 ; G 919
+U 1027 ; WX 637 ; N uni0403 ; G 920
+U 1028 ; WX 734 ; N uni0404 ; G 921
+U 1029 ; WX 720 ; N uni0405 ; G 922
+U 1030 ; WX 372 ; N uni0406 ; G 923
+U 1031 ; WX 372 ; N uni0407 ; G 924
+U 1032 ; WX 372 ; N uni0408 ; G 925
+U 1033 ; WX 1154 ; N uni0409 ; G 926
+U 1034 ; WX 1130 ; N uni040A ; G 927
+U 1035 ; WX 878 ; N uni040B ; G 928
+U 1036 ; WX 817 ; N uni040C ; G 929
+U 1037 ; WX 837 ; N uni040D ; G 930
+U 1038 ; WX 771 ; N uni040E ; G 931
+U 1039 ; WX 837 ; N uni040F ; G 932
+U 1040 ; WX 774 ; N uni0410 ; G 933
+U 1041 ; WX 762 ; N uni0411 ; G 934
+U 1042 ; WX 762 ; N uni0412 ; G 935
+U 1043 ; WX 637 ; N uni0413 ; G 936
+U 1044 ; WX 891 ; N uni0414 ; G 937
+U 1045 ; WX 683 ; N uni0415 ; G 938
+U 1046 ; WX 1224 ; N uni0416 ; G 939
+U 1047 ; WX 710 ; N uni0417 ; G 940
+U 1048 ; WX 837 ; N uni0418 ; G 941
+U 1049 ; WX 837 ; N uni0419 ; G 942
+U 1050 ; WX 817 ; N uni041A ; G 943
+U 1051 ; WX 831 ; N uni041B ; G 944
+U 1052 ; WX 995 ; N uni041C ; G 945
+U 1053 ; WX 837 ; N uni041D ; G 946
+U 1054 ; WX 850 ; N uni041E ; G 947
+U 1055 ; WX 837 ; N uni041F ; G 948
+U 1056 ; WX 733 ; N uni0420 ; G 949
+U 1057 ; WX 734 ; N uni0421 ; G 950
+U 1058 ; WX 682 ; N uni0422 ; G 951
+U 1059 ; WX 771 ; N uni0423 ; G 952
+U 1060 ; WX 992 ; N uni0424 ; G 953
+U 1061 ; WX 771 ; N uni0425 ; G 954
+U 1062 ; WX 928 ; N uni0426 ; G 955
+U 1063 ; WX 808 ; N uni0427 ; G 956
+U 1064 ; WX 1235 ; N uni0428 ; G 957
+U 1065 ; WX 1326 ; N uni0429 ; G 958
+U 1066 ; WX 939 ; N uni042A ; G 959
+U 1067 ; WX 1036 ; N uni042B ; G 960
+U 1068 ; WX 762 ; N uni042C ; G 961
+U 1069 ; WX 734 ; N uni042D ; G 962
+U 1070 ; WX 1174 ; N uni042E ; G 963
+U 1071 ; WX 770 ; N uni042F ; G 964
+U 1072 ; WX 675 ; N uni0430 ; G 965
+U 1073 ; WX 698 ; N uni0431 ; G 966
+U 1074 ; WX 633 ; N uni0432 ; G 967
+U 1075 ; WX 522 ; N uni0433 ; G 968
+U 1076 ; WX 808 ; N uni0434 ; G 969
+U 1077 ; WX 678 ; N uni0435 ; G 970
+U 1078 ; WX 995 ; N uni0436 ; G 971
+U 1079 ; WX 581 ; N uni0437 ; G 972
+U 1080 ; WX 701 ; N uni0438 ; G 973
+U 1081 ; WX 701 ; N uni0439 ; G 974
+U 1082 ; WX 679 ; N uni043A ; G 975
+U 1083 ; WX 732 ; N uni043B ; G 976
+U 1084 ; WX 817 ; N uni043C ; G 977
+U 1085 ; WX 691 ; N uni043D ; G 978
+U 1086 ; WX 687 ; N uni043E ; G 979
+U 1087 ; WX 691 ; N uni043F ; G 980
+U 1088 ; WX 716 ; N uni0440 ; G 981
+U 1089 ; WX 593 ; N uni0441 ; G 982
+U 1090 ; WX 580 ; N uni0442 ; G 983
+U 1091 ; WX 652 ; N uni0443 ; G 984
+U 1092 ; WX 992 ; N uni0444 ; G 985
+U 1093 ; WX 645 ; N uni0445 ; G 986
+U 1094 ; WX 741 ; N uni0446 ; G 987
+U 1095 ; WX 687 ; N uni0447 ; G 988
+U 1096 ; WX 1062 ; N uni0448 ; G 989
+U 1097 ; WX 1105 ; N uni0449 ; G 990
+U 1098 ; WX 751 ; N uni044A ; G 991
+U 1099 ; WX 904 ; N uni044B ; G 992
+U 1100 ; WX 632 ; N uni044C ; G 993
+U 1101 ; WX 593 ; N uni044D ; G 994
+U 1102 ; WX 972 ; N uni044E ; G 995
+U 1103 ; WX 642 ; N uni044F ; G 996
+U 1104 ; WX 678 ; N uni0450 ; G 997
+U 1105 ; WX 678 ; N uni0451 ; G 998
+U 1106 ; WX 714 ; N uni0452 ; G 999
+U 1107 ; WX 522 ; N uni0453 ; G 1000
+U 1108 ; WX 593 ; N uni0454 ; G 1001
+U 1109 ; WX 595 ; N uni0455 ; G 1002
+U 1110 ; WX 343 ; N uni0456 ; G 1003
+U 1111 ; WX 343 ; N uni0457 ; G 1004
+U 1112 ; WX 343 ; N uni0458 ; G 1005
+U 1113 ; WX 991 ; N uni0459 ; G 1006
+U 1114 ; WX 956 ; N uni045A ; G 1007
+U 1115 ; WX 734 ; N uni045B ; G 1008
+U 1116 ; WX 679 ; N uni045C ; G 1009
+U 1117 ; WX 701 ; N uni045D ; G 1010
+U 1118 ; WX 652 ; N uni045E ; G 1011
+U 1119 ; WX 691 ; N uni045F ; G 1012
+U 1120 ; WX 1093 ; N uni0460 ; G 1013
+U 1121 ; WX 869 ; N uni0461 ; G 1014
+U 1122 ; WX 840 ; N uni0462 ; G 1015
+U 1123 ; WX 736 ; N uni0463 ; G 1016
+U 1124 ; WX 1012 ; N uni0464 ; G 1017
+U 1125 ; WX 839 ; N uni0465 ; G 1018
+U 1126 ; WX 992 ; N uni0466 ; G 1019
+U 1127 ; WX 832 ; N uni0467 ; G 1020
+U 1128 ; WX 1358 ; N uni0468 ; G 1021
+U 1129 ; WX 1121 ; N uni0469 ; G 1022
+U 1130 ; WX 850 ; N uni046A ; G 1023
+U 1131 ; WX 687 ; N uni046B ; G 1024
+U 1132 ; WX 1236 ; N uni046C ; G 1025
+U 1133 ; WX 1007 ; N uni046D ; G 1026
+U 1134 ; WX 696 ; N uni046E ; G 1027
+U 1135 ; WX 557 ; N uni046F ; G 1028
+U 1136 ; WX 1075 ; N uni0470 ; G 1029
+U 1137 ; WX 1061 ; N uni0471 ; G 1030
+U 1138 ; WX 850 ; N uni0472 ; G 1031
+U 1139 ; WX 687 ; N uni0473 ; G 1032
+U 1140 ; WX 850 ; N uni0474 ; G 1033
+U 1141 ; WX 695 ; N uni0475 ; G 1034
+U 1142 ; WX 850 ; N uni0476 ; G 1035
+U 1143 ; WX 695 ; N uni0477 ; G 1036
+U 1144 ; WX 1148 ; N uni0478 ; G 1037
+U 1145 ; WX 1043 ; N uni0479 ; G 1038
+U 1146 ; WX 1074 ; N uni047A ; G 1039
+U 1147 ; WX 863 ; N uni047B ; G 1040
+U 1148 ; WX 1405 ; N uni047C ; G 1041
+U 1149 ; WX 1173 ; N uni047D ; G 1042
+U 1150 ; WX 1093 ; N uni047E ; G 1043
+U 1151 ; WX 869 ; N uni047F ; G 1044
+U 1152 ; WX 734 ; N uni0480 ; G 1045
+U 1153 ; WX 593 ; N uni0481 ; G 1046
+U 1154 ; WX 652 ; N uni0482 ; G 1047
+U 1155 ; WX 0 ; N uni0483 ; G 1048
+U 1156 ; WX 0 ; N uni0484 ; G 1049
+U 1157 ; WX 0 ; N uni0485 ; G 1050
+U 1158 ; WX 0 ; N uni0486 ; G 1051
+U 1159 ; WX 0 ; N uni0487 ; G 1052
+U 1160 ; WX 418 ; N uni0488 ; G 1053
+U 1161 ; WX 418 ; N uni0489 ; G 1054
+U 1162 ; WX 938 ; N uni048A ; G 1055
+U 1163 ; WX 806 ; N uni048B ; G 1056
+U 1164 ; WX 762 ; N uni048C ; G 1057
+U 1165 ; WX 611 ; N uni048D ; G 1058
+U 1166 ; WX 736 ; N uni048E ; G 1059
+U 1167 ; WX 718 ; N uni048F ; G 1060
+U 1168 ; WX 637 ; N uni0490 ; G 1061
+U 1169 ; WX 522 ; N uni0491 ; G 1062
+U 1170 ; WX 666 ; N uni0492 ; G 1063
+U 1171 ; WX 543 ; N uni0493 ; G 1064
+U 1172 ; WX 789 ; N uni0494 ; G 1065
+U 1173 ; WX 522 ; N uni0495 ; G 1066
+U 1174 ; WX 1224 ; N uni0496 ; G 1067
+U 1175 ; WX 995 ; N uni0497 ; G 1068
+U 1176 ; WX 710 ; N uni0498 ; G 1069
+U 1177 ; WX 581 ; N uni0499 ; G 1070
+U 1178 ; WX 775 ; N uni049A ; G 1071
+U 1179 ; WX 679 ; N uni049B ; G 1072
+U 1180 ; WX 817 ; N uni049C ; G 1073
+U 1181 ; WX 679 ; N uni049D ; G 1074
+U 1182 ; WX 817 ; N uni049E ; G 1075
+U 1183 ; WX 679 ; N uni049F ; G 1076
+U 1184 ; WX 1015 ; N uni04A0 ; G 1077
+U 1185 ; WX 826 ; N uni04A1 ; G 1078
+U 1186 ; WX 837 ; N uni04A2 ; G 1079
+U 1187 ; WX 691 ; N uni04A3 ; G 1080
+U 1188 ; WX 1103 ; N uni04A4 ; G 1081
+U 1189 ; WX 871 ; N uni04A5 ; G 1082
+U 1190 ; WX 1254 ; N uni04A6 ; G 1083
+U 1191 ; WX 979 ; N uni04A7 ; G 1084
+U 1192 ; WX 946 ; N uni04A8 ; G 1085
+U 1193 ; WX 859 ; N uni04A9 ; G 1086
+U 1194 ; WX 734 ; N uni04AA ; G 1087
+U 1195 ; WX 593 ; N uni04AB ; G 1088
+U 1196 ; WX 682 ; N uni04AC ; G 1089
+U 1197 ; WX 580 ; N uni04AD ; G 1090
+U 1198 ; WX 724 ; N uni04AE ; G 1091
+U 1199 ; WX 652 ; N uni04AF ; G 1092
+U 1200 ; WX 724 ; N uni04B0 ; G 1093
+U 1201 ; WX 652 ; N uni04B1 ; G 1094
+U 1202 ; WX 771 ; N uni04B2 ; G 1095
+U 1203 ; WX 645 ; N uni04B3 ; G 1096
+U 1204 ; WX 1104 ; N uni04B4 ; G 1097
+U 1205 ; WX 1001 ; N uni04B5 ; G 1098
+U 1206 ; WX 808 ; N uni04B6 ; G 1099
+U 1207 ; WX 687 ; N uni04B7 ; G 1100
+U 1208 ; WX 808 ; N uni04B8 ; G 1101
+U 1209 ; WX 687 ; N uni04B9 ; G 1102
+U 1210 ; WX 808 ; N uni04BA ; G 1103
+U 1211 ; WX 712 ; N uni04BB ; G 1104
+U 1212 ; WX 1026 ; N uni04BC ; G 1105
+U 1213 ; WX 810 ; N uni04BD ; G 1106
+U 1214 ; WX 1026 ; N uni04BE ; G 1107
+U 1215 ; WX 810 ; N uni04BF ; G 1108
+U 1216 ; WX 372 ; N uni04C0 ; G 1109
+U 1217 ; WX 1224 ; N uni04C1 ; G 1110
+U 1218 ; WX 995 ; N uni04C2 ; G 1111
+U 1219 ; WX 778 ; N uni04C3 ; G 1112
+U 1220 ; WX 629 ; N uni04C4 ; G 1113
+U 1221 ; WX 933 ; N uni04C5 ; G 1114
+U 1222 ; WX 804 ; N uni04C6 ; G 1115
+U 1223 ; WX 837 ; N uni04C7 ; G 1116
+U 1224 ; WX 691 ; N uni04C8 ; G 1117
+U 1225 ; WX 938 ; N uni04C9 ; G 1118
+U 1226 ; WX 806 ; N uni04CA ; G 1119
+U 1227 ; WX 808 ; N uni04CB ; G 1120
+U 1228 ; WX 687 ; N uni04CC ; G 1121
+U 1229 ; WX 1096 ; N uni04CD ; G 1122
+U 1230 ; WX 932 ; N uni04CE ; G 1123
+U 1231 ; WX 343 ; N uni04CF ; G 1124
+U 1232 ; WX 774 ; N uni04D0 ; G 1125
+U 1233 ; WX 675 ; N uni04D1 ; G 1126
+U 1234 ; WX 774 ; N uni04D2 ; G 1127
+U 1235 ; WX 675 ; N uni04D3 ; G 1128
+U 1236 ; WX 1085 ; N uni04D4 ; G 1129
+U 1237 ; WX 1048 ; N uni04D5 ; G 1130
+U 1238 ; WX 683 ; N uni04D6 ; G 1131
+U 1239 ; WX 678 ; N uni04D7 ; G 1132
+U 1240 ; WX 850 ; N uni04D8 ; G 1133
+U 1241 ; WX 678 ; N uni04D9 ; G 1134
+U 1242 ; WX 850 ; N uni04DA ; G 1135
+U 1243 ; WX 678 ; N uni04DB ; G 1136
+U 1244 ; WX 1224 ; N uni04DC ; G 1137
+U 1245 ; WX 995 ; N uni04DD ; G 1138
+U 1246 ; WX 710 ; N uni04DE ; G 1139
+U 1247 ; WX 581 ; N uni04DF ; G 1140
+U 1248 ; WX 772 ; N uni04E0 ; G 1141
+U 1249 ; WX 641 ; N uni04E1 ; G 1142
+U 1250 ; WX 837 ; N uni04E2 ; G 1143
+U 1251 ; WX 701 ; N uni04E3 ; G 1144
+U 1252 ; WX 837 ; N uni04E4 ; G 1145
+U 1253 ; WX 701 ; N uni04E5 ; G 1146
+U 1254 ; WX 850 ; N uni04E6 ; G 1147
+U 1255 ; WX 687 ; N uni04E7 ; G 1148
+U 1256 ; WX 850 ; N uni04E8 ; G 1149
+U 1257 ; WX 687 ; N uni04E9 ; G 1150
+U 1258 ; WX 850 ; N uni04EA ; G 1151
+U 1259 ; WX 687 ; N uni04EB ; G 1152
+U 1260 ; WX 734 ; N uni04EC ; G 1153
+U 1261 ; WX 593 ; N uni04ED ; G 1154
+U 1262 ; WX 771 ; N uni04EE ; G 1155
+U 1263 ; WX 652 ; N uni04EF ; G 1156
+U 1264 ; WX 771 ; N uni04F0 ; G 1157
+U 1265 ; WX 652 ; N uni04F1 ; G 1158
+U 1266 ; WX 771 ; N uni04F2 ; G 1159
+U 1267 ; WX 652 ; N uni04F3 ; G 1160
+U 1268 ; WX 808 ; N uni04F4 ; G 1161
+U 1269 ; WX 687 ; N uni04F5 ; G 1162
+U 1270 ; WX 637 ; N uni04F6 ; G 1163
+U 1271 ; WX 522 ; N uni04F7 ; G 1164
+U 1272 ; WX 1036 ; N uni04F8 ; G 1165
+U 1273 ; WX 904 ; N uni04F9 ; G 1166
+U 1274 ; WX 666 ; N uni04FA ; G 1167
+U 1275 ; WX 543 ; N uni04FB ; G 1168
+U 1276 ; WX 771 ; N uni04FC ; G 1169
+U 1277 ; WX 645 ; N uni04FD ; G 1170
+U 1278 ; WX 771 ; N uni04FE ; G 1171
+U 1279 ; WX 645 ; N uni04FF ; G 1172
+U 1280 ; WX 762 ; N uni0500 ; G 1173
+U 1281 ; WX 608 ; N uni0501 ; G 1174
+U 1282 ; WX 1159 ; N uni0502 ; G 1175
+U 1283 ; WX 893 ; N uni0503 ; G 1176
+U 1284 ; WX 1119 ; N uni0504 ; G 1177
+U 1285 ; WX 920 ; N uni0505 ; G 1178
+U 1286 ; WX 828 ; N uni0506 ; G 1179
+U 1287 ; WX 693 ; N uni0507 ; G 1180
+U 1288 ; WX 1242 ; N uni0508 ; G 1181
+U 1289 ; WX 1017 ; N uni0509 ; G 1182
+U 1290 ; WX 1289 ; N uni050A ; G 1183
+U 1291 ; WX 1013 ; N uni050B ; G 1184
+U 1292 ; WX 839 ; N uni050C ; G 1185
+U 1293 ; WX 638 ; N uni050D ; G 1186
+U 1294 ; WX 938 ; N uni050E ; G 1187
+U 1295 ; WX 803 ; N uni050F ; G 1188
+U 1296 ; WX 696 ; N uni0510 ; G 1189
+U 1297 ; WX 557 ; N uni0511 ; G 1190
+U 1298 ; WX 831 ; N uni0512 ; G 1191
+U 1299 ; WX 732 ; N uni0513 ; G 1192
+U 1300 ; WX 1286 ; N uni0514 ; G 1193
+U 1301 ; WX 1070 ; N uni0515 ; G 1194
+U 1302 ; WX 1065 ; N uni0516 ; G 1195
+U 1303 ; WX 982 ; N uni0517 ; G 1196
+U 1304 ; WX 1082 ; N uni0518 ; G 1197
+U 1305 ; WX 960 ; N uni0519 ; G 1198
+U 1306 ; WX 850 ; N uni051A ; G 1199
+U 1307 ; WX 716 ; N uni051B ; G 1200
+U 1308 ; WX 1103 ; N uni051C ; G 1201
+U 1309 ; WX 924 ; N uni051D ; G 1202
+U 1310 ; WX 817 ; N uni051E ; G 1203
+U 1311 ; WX 679 ; N uni051F ; G 1204
+U 1312 ; WX 1248 ; N uni0520 ; G 1205
+U 1313 ; WX 1022 ; N uni0521 ; G 1206
+U 1314 ; WX 1254 ; N uni0522 ; G 1207
+U 1315 ; WX 979 ; N uni0523 ; G 1208
+U 1316 ; WX 957 ; N uni0524 ; G 1209
+U 1317 ; WX 807 ; N uni0525 ; G 1210
+U 1329 ; WX 904 ; N uni0531 ; G 1211
+U 1330 ; WX 810 ; N uni0532 ; G 1212
+U 1331 ; WX 809 ; N uni0533 ; G 1213
+U 1332 ; WX 813 ; N uni0534 ; G 1214
+U 1333 ; WX 810 ; N uni0535 ; G 1215
+U 1334 ; WX 815 ; N uni0536 ; G 1216
+U 1335 ; WX 724 ; N uni0537 ; G 1217
+U 1336 ; WX 800 ; N uni0538 ; G 1218
+U 1337 ; WX 1004 ; N uni0539 ; G 1219
+U 1338 ; WX 809 ; N uni053A ; G 1220
+U 1339 ; WX 740 ; N uni053B ; G 1221
+U 1340 ; WX 620 ; N uni053C ; G 1222
+U 1341 ; WX 1068 ; N uni053D ; G 1223
+U 1342 ; WX 875 ; N uni053E ; G 1224
+U 1343 ; WX 792 ; N uni053F ; G 1225
+U 1344 ; WX 723 ; N uni0540 ; G 1226
+U 1345 ; WX 811 ; N uni0541 ; G 1227
+U 1346 ; WX 794 ; N uni0542 ; G 1228
+U 1347 ; WX 782 ; N uni0543 ; G 1229
+U 1348 ; WX 867 ; N uni0544 ; G 1230
+U 1349 ; WX 766 ; N uni0545 ; G 1231
+U 1350 ; WX 794 ; N uni0546 ; G 1232
+U 1351 ; WX 787 ; N uni0547 ; G 1233
+U 1352 ; WX 812 ; N uni0548 ; G 1234
+U 1353 ; WX 752 ; N uni0549 ; G 1235
+U 1354 ; WX 963 ; N uni054A ; G 1236
+U 1355 ; WX 790 ; N uni054B ; G 1237
+U 1356 ; WX 867 ; N uni054C ; G 1238
+U 1357 ; WX 812 ; N uni054D ; G 1239
+U 1358 ; WX 794 ; N uni054E ; G 1240
+U 1359 ; WX 771 ; N uni054F ; G 1241
+U 1360 ; WX 740 ; N uni0550 ; G 1242
+U 1361 ; WX 775 ; N uni0551 ; G 1243
+U 1362 ; WX 640 ; N uni0552 ; G 1244
+U 1363 ; WX 926 ; N uni0553 ; G 1245
+U 1364 ; WX 775 ; N uni0554 ; G 1246
+U 1365 ; WX 848 ; N uni0555 ; G 1247
+U 1366 ; WX 951 ; N uni0556 ; G 1248
+U 1369 ; WX 366 ; N uni0559 ; G 1249
+U 1370 ; WX 380 ; N uni055A ; G 1250
+U 1371 ; WX 342 ; N uni055B ; G 1251
+U 1372 ; WX 415 ; N uni055C ; G 1252
+U 1373 ; WX 348 ; N uni055D ; G 1253
+U 1374 ; WX 513 ; N uni055E ; G 1254
+U 1375 ; WX 521 ; N uni055F ; G 1255
+U 1377 ; WX 1043 ; N uni0561 ; G 1256
+U 1378 ; WX 713 ; N uni0562 ; G 1257
+U 1379 ; WX 782 ; N uni0563 ; G 1258
+U 1380 ; WX 786 ; N uni0564 ; G 1259
+U 1381 ; WX 713 ; N uni0565 ; G 1260
+U 1382 ; WX 715 ; N uni0566 ; G 1261
+U 1383 ; WX 628 ; N uni0567 ; G 1262
+U 1384 ; WX 713 ; N uni0568 ; G 1263
+U 1385 ; WX 840 ; N uni0569 ; G 1264
+U 1386 ; WX 782 ; N uni056A ; G 1265
+U 1387 ; WX 714 ; N uni056B ; G 1266
+U 1388 ; WX 344 ; N uni056C ; G 1267
+U 1389 ; WX 1094 ; N uni056D ; G 1268
+U 1390 ; WX 708 ; N uni056E ; G 1269
+U 1391 ; WX 714 ; N uni056F ; G 1270
+U 1392 ; WX 714 ; N uni0570 ; G 1271
+U 1393 ; WX 670 ; N uni0571 ; G 1272
+U 1394 ; WX 714 ; N uni0572 ; G 1273
+U 1395 ; WX 713 ; N uni0573 ; G 1274
+U 1396 ; WX 714 ; N uni0574 ; G 1275
+U 1397 ; WX 343 ; N uni0575 ; G 1276
+U 1398 ; WX 714 ; N uni0576 ; G 1277
+U 1399 ; WX 541 ; N uni0577 ; G 1278
+U 1400 ; WX 714 ; N uni0578 ; G 1279
+U 1401 ; WX 407 ; N uni0579 ; G 1280
+U 1402 ; WX 1043 ; N uni057A ; G 1281
+U 1403 ; WX 636 ; N uni057B ; G 1282
+U 1404 ; WX 740 ; N uni057C ; G 1283
+U 1405 ; WX 714 ; N uni057D ; G 1284
+U 1406 ; WX 714 ; N uni057E ; G 1285
+U 1407 ; WX 1038 ; N uni057F ; G 1286
+U 1408 ; WX 714 ; N uni0580 ; G 1287
+U 1409 ; WX 714 ; N uni0581 ; G 1288
+U 1410 ; WX 532 ; N uni0582 ; G 1289
+U 1411 ; WX 1038 ; N uni0583 ; G 1290
+U 1412 ; WX 720 ; N uni0584 ; G 1291
+U 1413 ; WX 689 ; N uni0585 ; G 1292
+U 1414 ; WX 904 ; N uni0586 ; G 1293
+U 1415 ; WX 902 ; N uni0587 ; G 1294
+U 1417 ; WX 400 ; N uni0589 ; G 1295
+U 1418 ; WX 415 ; N uni058A ; G 1296
+U 1456 ; WX 0 ; N uni05B0 ; G 1297
+U 1457 ; WX 0 ; N uni05B1 ; G 1298
+U 1458 ; WX 0 ; N uni05B2 ; G 1299
+U 1459 ; WX 0 ; N uni05B3 ; G 1300
+U 1460 ; WX 0 ; N uni05B4 ; G 1301
+U 1461 ; WX 0 ; N uni05B5 ; G 1302
+U 1462 ; WX 0 ; N uni05B6 ; G 1303
+U 1463 ; WX 0 ; N uni05B7 ; G 1304
+U 1464 ; WX 0 ; N uni05B8 ; G 1305
+U 1465 ; WX 0 ; N uni05B9 ; G 1306
+U 1466 ; WX 0 ; N uni05BA ; G 1307
+U 1467 ; WX 0 ; N uni05BB ; G 1308
+U 1468 ; WX 0 ; N uni05BC ; G 1309
+U 1469 ; WX 0 ; N uni05BD ; G 1310
+U 1470 ; WX 415 ; N uni05BE ; G 1311
+U 1471 ; WX 0 ; N uni05BF ; G 1312
+U 1472 ; WX 372 ; N uni05C0 ; G 1313
+U 1473 ; WX 0 ; N uni05C1 ; G 1314
+U 1474 ; WX 0 ; N uni05C2 ; G 1315
+U 1475 ; WX 372 ; N uni05C3 ; G 1316
+U 1478 ; WX 497 ; N uni05C6 ; G 1317
+U 1479 ; WX 0 ; N uni05C7 ; G 1318
+U 1488 ; WX 728 ; N uni05D0 ; G 1319
+U 1489 ; WX 610 ; N uni05D1 ; G 1320
+U 1490 ; WX 447 ; N uni05D2 ; G 1321
+U 1491 ; WX 588 ; N uni05D3 ; G 1322
+U 1492 ; WX 687 ; N uni05D4 ; G 1323
+U 1493 ; WX 343 ; N uni05D5 ; G 1324
+U 1494 ; WX 400 ; N uni05D6 ; G 1325
+U 1495 ; WX 687 ; N uni05D7 ; G 1326
+U 1496 ; WX 679 ; N uni05D8 ; G 1327
+U 1497 ; WX 294 ; N uni05D9 ; G 1328
+U 1498 ; WX 578 ; N uni05DA ; G 1329
+U 1499 ; WX 566 ; N uni05DB ; G 1330
+U 1500 ; WX 605 ; N uni05DC ; G 1331
+U 1501 ; WX 696 ; N uni05DD ; G 1332
+U 1502 ; WX 724 ; N uni05DE ; G 1333
+U 1503 ; WX 343 ; N uni05DF ; G 1334
+U 1504 ; WX 453 ; N uni05E0 ; G 1335
+U 1505 ; WX 680 ; N uni05E1 ; G 1336
+U 1506 ; WX 666 ; N uni05E2 ; G 1337
+U 1507 ; WX 675 ; N uni05E3 ; G 1338
+U 1508 ; WX 658 ; N uni05E4 ; G 1339
+U 1509 ; WX 661 ; N uni05E5 ; G 1340
+U 1510 ; WX 653 ; N uni05E6 ; G 1341
+U 1511 ; WX 736 ; N uni05E7 ; G 1342
+U 1512 ; WX 602 ; N uni05E8 ; G 1343
+U 1513 ; WX 749 ; N uni05E9 ; G 1344
+U 1514 ; WX 683 ; N uni05EA ; G 1345
+U 1520 ; WX 664 ; N uni05F0 ; G 1346
+U 1521 ; WX 664 ; N uni05F1 ; G 1347
+U 1522 ; WX 663 ; N uni05F2 ; G 1348
+U 1523 ; WX 444 ; N uni05F3 ; G 1349
+U 1524 ; WX 710 ; N uni05F4 ; G 1350
+U 3647 ; WX 696 ; N uni0E3F ; G 1351
+U 3713 ; WX 815 ; N uni0E81 ; G 1352
+U 3714 ; WX 748 ; N uni0E82 ; G 1353
+U 3716 ; WX 749 ; N uni0E84 ; G 1354
+U 3719 ; WX 569 ; N uni0E87 ; G 1355
+U 3720 ; WX 742 ; N uni0E88 ; G 1356
+U 3722 ; WX 744 ; N uni0E8A ; G 1357
+U 3725 ; WX 761 ; N uni0E8D ; G 1358
+U 3732 ; WX 706 ; N uni0E94 ; G 1359
+U 3733 ; WX 704 ; N uni0E95 ; G 1360
+U 3734 ; WX 747 ; N uni0E96 ; G 1361
+U 3735 ; WX 819 ; N uni0E97 ; G 1362
+U 3737 ; WX 730 ; N uni0E99 ; G 1363
+U 3738 ; WX 727 ; N uni0E9A ; G 1364
+U 3739 ; WX 727 ; N uni0E9B ; G 1365
+U 3740 ; WX 922 ; N uni0E9C ; G 1366
+U 3741 ; WX 827 ; N uni0E9D ; G 1367
+U 3742 ; WX 866 ; N uni0E9E ; G 1368
+U 3743 ; WX 866 ; N uni0E9F ; G 1369
+U 3745 ; WX 836 ; N uni0EA1 ; G 1370
+U 3746 ; WX 761 ; N uni0EA2 ; G 1371
+U 3747 ; WX 770 ; N uni0EA3 ; G 1372
+U 3749 ; WX 769 ; N uni0EA5 ; G 1373
+U 3751 ; WX 713 ; N uni0EA7 ; G 1374
+U 3754 ; WX 827 ; N uni0EAA ; G 1375
+U 3755 ; WX 1031 ; N uni0EAB ; G 1376
+U 3757 ; WX 724 ; N uni0EAD ; G 1377
+U 3758 ; WX 784 ; N uni0EAE ; G 1378
+U 3759 ; WX 934 ; N uni0EAF ; G 1379
+U 3760 ; WX 688 ; N uni0EB0 ; G 1380
+U 3761 ; WX 0 ; N uni0EB1 ; G 1381
+U 3762 ; WX 610 ; N uni0EB2 ; G 1382
+U 3763 ; WX 610 ; N uni0EB3 ; G 1383
+U 3764 ; WX 0 ; N uni0EB4 ; G 1384
+U 3765 ; WX 0 ; N uni0EB5 ; G 1385
+U 3766 ; WX 0 ; N uni0EB6 ; G 1386
+U 3767 ; WX 0 ; N uni0EB7 ; G 1387
+U 3768 ; WX 0 ; N uni0EB8 ; G 1388
+U 3769 ; WX 0 ; N uni0EB9 ; G 1389
+U 3771 ; WX 0 ; N uni0EBB ; G 1390
+U 3772 ; WX 0 ; N uni0EBC ; G 1391
+U 3773 ; WX 670 ; N uni0EBD ; G 1392
+U 3776 ; WX 516 ; N uni0EC0 ; G 1393
+U 3777 ; WX 860 ; N uni0EC1 ; G 1394
+U 3778 ; WX 516 ; N uni0EC2 ; G 1395
+U 3779 ; WX 650 ; N uni0EC3 ; G 1396
+U 3780 ; WX 632 ; N uni0EC4 ; G 1397
+U 3782 ; WX 759 ; N uni0EC6 ; G 1398
+U 3784 ; WX 0 ; N uni0EC8 ; G 1399
+U 3785 ; WX 0 ; N uni0EC9 ; G 1400
+U 3786 ; WX 0 ; N uni0ECA ; G 1401
+U 3787 ; WX 0 ; N uni0ECB ; G 1402
+U 3788 ; WX 0 ; N uni0ECC ; G 1403
+U 3789 ; WX 0 ; N uni0ECD ; G 1404
+U 3792 ; WX 771 ; N uni0ED0 ; G 1405
+U 3793 ; WX 771 ; N uni0ED1 ; G 1406
+U 3794 ; WX 693 ; N uni0ED2 ; G 1407
+U 3795 ; WX 836 ; N uni0ED3 ; G 1408
+U 3796 ; WX 729 ; N uni0ED4 ; G 1409
+U 3797 ; WX 729 ; N uni0ED5 ; G 1410
+U 3798 ; WX 849 ; N uni0ED6 ; G 1411
+U 3799 ; WX 790 ; N uni0ED7 ; G 1412
+U 3800 ; WX 759 ; N uni0ED8 ; G 1413
+U 3801 ; WX 910 ; N uni0ED9 ; G 1414
+U 3804 ; WX 1363 ; N uni0EDC ; G 1415
+U 3805 ; WX 1363 ; N uni0EDD ; G 1416
+U 4256 ; WX 874 ; N uni10A0 ; G 1417
+U 4257 ; WX 733 ; N uni10A1 ; G 1418
+U 4258 ; WX 679 ; N uni10A2 ; G 1419
+U 4259 ; WX 834 ; N uni10A3 ; G 1420
+U 4260 ; WX 615 ; N uni10A4 ; G 1421
+U 4261 ; WX 768 ; N uni10A5 ; G 1422
+U 4262 ; WX 753 ; N uni10A6 ; G 1423
+U 4263 ; WX 914 ; N uni10A7 ; G 1424
+U 4264 ; WX 453 ; N uni10A8 ; G 1425
+U 4265 ; WX 620 ; N uni10A9 ; G 1426
+U 4266 ; WX 843 ; N uni10AA ; G 1427
+U 4267 ; WX 882 ; N uni10AB ; G 1428
+U 4268 ; WX 625 ; N uni10AC ; G 1429
+U 4269 ; WX 854 ; N uni10AD ; G 1430
+U 4270 ; WX 781 ; N uni10AE ; G 1431
+U 4271 ; WX 629 ; N uni10AF ; G 1432
+U 4272 ; WX 912 ; N uni10B0 ; G 1433
+U 4273 ; WX 621 ; N uni10B1 ; G 1434
+U 4274 ; WX 620 ; N uni10B2 ; G 1435
+U 4275 ; WX 854 ; N uni10B3 ; G 1436
+U 4276 ; WX 866 ; N uni10B4 ; G 1437
+U 4277 ; WX 724 ; N uni10B5 ; G 1438
+U 4278 ; WX 630 ; N uni10B6 ; G 1439
+U 4279 ; WX 621 ; N uni10B7 ; G 1440
+U 4280 ; WX 625 ; N uni10B8 ; G 1441
+U 4281 ; WX 620 ; N uni10B9 ; G 1442
+U 4282 ; WX 818 ; N uni10BA ; G 1443
+U 4283 ; WX 874 ; N uni10BB ; G 1444
+U 4284 ; WX 615 ; N uni10BC ; G 1445
+U 4285 ; WX 623 ; N uni10BD ; G 1446
+U 4286 ; WX 625 ; N uni10BE ; G 1447
+U 4287 ; WX 725 ; N uni10BF ; G 1448
+U 4288 ; WX 844 ; N uni10C0 ; G 1449
+U 4289 ; WX 596 ; N uni10C1 ; G 1450
+U 4290 ; WX 688 ; N uni10C2 ; G 1451
+U 4291 ; WX 596 ; N uni10C3 ; G 1452
+U 4292 ; WX 594 ; N uni10C4 ; G 1453
+U 4293 ; WX 738 ; N uni10C5 ; G 1454
+U 4304 ; WX 554 ; N uni10D0 ; G 1455
+U 4305 ; WX 563 ; N uni10D1 ; G 1456
+U 4306 ; WX 622 ; N uni10D2 ; G 1457
+U 4307 ; WX 834 ; N uni10D3 ; G 1458
+U 4308 ; WX 550 ; N uni10D4 ; G 1459
+U 4309 ; WX 559 ; N uni10D5 ; G 1460
+U 4310 ; WX 546 ; N uni10D6 ; G 1461
+U 4311 ; WX 828 ; N uni10D7 ; G 1462
+U 4312 ; WX 563 ; N uni10D8 ; G 1463
+U 4313 ; WX 556 ; N uni10D9 ; G 1464
+U 4314 ; WX 1074 ; N uni10DA ; G 1465
+U 4315 ; WX 563 ; N uni10DB ; G 1466
+U 4316 ; WX 563 ; N uni10DC ; G 1467
+U 4317 ; WX 814 ; N uni10DD ; G 1468
+U 4318 ; WX 554 ; N uni10DE ; G 1469
+U 4319 ; WX 559 ; N uni10DF ; G 1470
+U 4320 ; WX 823 ; N uni10E0 ; G 1471
+U 4321 ; WX 563 ; N uni10E1 ; G 1472
+U 4322 ; WX 700 ; N uni10E2 ; G 1473
+U 4323 ; WX 582 ; N uni10E3 ; G 1474
+U 4324 ; WX 847 ; N uni10E4 ; G 1475
+U 4325 ; WX 555 ; N uni10E5 ; G 1476
+U 4326 ; WX 814 ; N uni10E6 ; G 1477
+U 4327 ; WX 559 ; N uni10E7 ; G 1478
+U 4328 ; WX 543 ; N uni10E8 ; G 1479
+U 4329 ; WX 563 ; N uni10E9 ; G 1480
+U 4330 ; WX 622 ; N uni10EA ; G 1481
+U 4331 ; WX 563 ; N uni10EB ; G 1482
+U 4332 ; WX 543 ; N uni10EC ; G 1483
+U 4333 ; WX 566 ; N uni10ED ; G 1484
+U 4334 ; WX 563 ; N uni10EE ; G 1485
+U 4335 ; WX 530 ; N uni10EF ; G 1486
+U 4336 ; WX 554 ; N uni10F0 ; G 1487
+U 4337 ; WX 554 ; N uni10F1 ; G 1488
+U 4338 ; WX 553 ; N uni10F2 ; G 1489
+U 4339 ; WX 554 ; N uni10F3 ; G 1490
+U 4340 ; WX 553 ; N uni10F4 ; G 1491
+U 4341 ; WX 583 ; N uni10F5 ; G 1492
+U 4342 ; WX 853 ; N uni10F6 ; G 1493
+U 4343 ; WX 604 ; N uni10F7 ; G 1494
+U 4344 ; WX 559 ; N uni10F8 ; G 1495
+U 4345 ; WX 632 ; N uni10F9 ; G 1496
+U 4346 ; WX 554 ; N uni10FA ; G 1497
+U 4347 ; WX 448 ; N uni10FB ; G 1498
+U 4348 ; WX 324 ; N uni10FC ; G 1499
+U 5121 ; WX 774 ; N uni1401 ; G 1500
+U 5122 ; WX 774 ; N uni1402 ; G 1501
+U 5123 ; WX 774 ; N uni1403 ; G 1502
+U 5124 ; WX 774 ; N uni1404 ; G 1503
+U 5125 ; WX 905 ; N uni1405 ; G 1504
+U 5126 ; WX 905 ; N uni1406 ; G 1505
+U 5127 ; WX 905 ; N uni1407 ; G 1506
+U 5129 ; WX 905 ; N uni1409 ; G 1507
+U 5130 ; WX 905 ; N uni140A ; G 1508
+U 5131 ; WX 905 ; N uni140B ; G 1509
+U 5132 ; WX 1018 ; N uni140C ; G 1510
+U 5133 ; WX 1009 ; N uni140D ; G 1511
+U 5134 ; WX 1018 ; N uni140E ; G 1512
+U 5135 ; WX 1009 ; N uni140F ; G 1513
+U 5136 ; WX 1018 ; N uni1410 ; G 1514
+U 5137 ; WX 1009 ; N uni1411 ; G 1515
+U 5138 ; WX 1149 ; N uni1412 ; G 1516
+U 5139 ; WX 1140 ; N uni1413 ; G 1517
+U 5140 ; WX 1149 ; N uni1414 ; G 1518
+U 5141 ; WX 1140 ; N uni1415 ; G 1519
+U 5142 ; WX 905 ; N uni1416 ; G 1520
+U 5143 ; WX 1149 ; N uni1417 ; G 1521
+U 5144 ; WX 1142 ; N uni1418 ; G 1522
+U 5145 ; WX 1149 ; N uni1419 ; G 1523
+U 5146 ; WX 1142 ; N uni141A ; G 1524
+U 5147 ; WX 905 ; N uni141B ; G 1525
+U 5149 ; WX 310 ; N uni141D ; G 1526
+U 5150 ; WX 529 ; N uni141E ; G 1527
+U 5151 ; WX 425 ; N uni141F ; G 1528
+U 5152 ; WX 425 ; N uni1420 ; G 1529
+U 5153 ; WX 395 ; N uni1421 ; G 1530
+U 5154 ; WX 395 ; N uni1422 ; G 1531
+U 5155 ; WX 395 ; N uni1423 ; G 1532
+U 5156 ; WX 395 ; N uni1424 ; G 1533
+U 5157 ; WX 564 ; N uni1425 ; G 1534
+U 5158 ; WX 470 ; N uni1426 ; G 1535
+U 5159 ; WX 310 ; N uni1427 ; G 1536
+U 5160 ; WX 395 ; N uni1428 ; G 1537
+U 5161 ; WX 395 ; N uni1429 ; G 1538
+U 5162 ; WX 395 ; N uni142A ; G 1539
+U 5163 ; WX 1213 ; N uni142B ; G 1540
+U 5164 ; WX 986 ; N uni142C ; G 1541
+U 5165 ; WX 1216 ; N uni142D ; G 1542
+U 5166 ; WX 1297 ; N uni142E ; G 1543
+U 5167 ; WX 774 ; N uni142F ; G 1544
+U 5168 ; WX 774 ; N uni1430 ; G 1545
+U 5169 ; WX 774 ; N uni1431 ; G 1546
+U 5170 ; WX 774 ; N uni1432 ; G 1547
+U 5171 ; WX 886 ; N uni1433 ; G 1548
+U 5172 ; WX 886 ; N uni1434 ; G 1549
+U 5173 ; WX 886 ; N uni1435 ; G 1550
+U 5175 ; WX 886 ; N uni1437 ; G 1551
+U 5176 ; WX 886 ; N uni1438 ; G 1552
+U 5177 ; WX 886 ; N uni1439 ; G 1553
+U 5178 ; WX 1018 ; N uni143A ; G 1554
+U 5179 ; WX 1009 ; N uni143B ; G 1555
+U 5180 ; WX 1018 ; N uni143C ; G 1556
+U 5181 ; WX 1009 ; N uni143D ; G 1557
+U 5182 ; WX 1018 ; N uni143E ; G 1558
+U 5183 ; WX 1009 ; N uni143F ; G 1559
+U 5184 ; WX 1149 ; N uni1440 ; G 1560
+U 5185 ; WX 1140 ; N uni1441 ; G 1561
+U 5186 ; WX 1149 ; N uni1442 ; G 1562
+U 5187 ; WX 1140 ; N uni1443 ; G 1563
+U 5188 ; WX 1149 ; N uni1444 ; G 1564
+U 5189 ; WX 1142 ; N uni1445 ; G 1565
+U 5190 ; WX 1149 ; N uni1446 ; G 1566
+U 5191 ; WX 1142 ; N uni1447 ; G 1567
+U 5192 ; WX 886 ; N uni1448 ; G 1568
+U 5193 ; WX 576 ; N uni1449 ; G 1569
+U 5194 ; WX 229 ; N uni144A ; G 1570
+U 5196 ; WX 812 ; N uni144C ; G 1571
+U 5197 ; WX 812 ; N uni144D ; G 1572
+U 5198 ; WX 812 ; N uni144E ; G 1573
+U 5199 ; WX 812 ; N uni144F ; G 1574
+U 5200 ; WX 815 ; N uni1450 ; G 1575
+U 5201 ; WX 815 ; N uni1451 ; G 1576
+U 5202 ; WX 815 ; N uni1452 ; G 1577
+U 5204 ; WX 815 ; N uni1454 ; G 1578
+U 5205 ; WX 815 ; N uni1455 ; G 1579
+U 5206 ; WX 815 ; N uni1456 ; G 1580
+U 5207 ; WX 1056 ; N uni1457 ; G 1581
+U 5208 ; WX 1048 ; N uni1458 ; G 1582
+U 5209 ; WX 1056 ; N uni1459 ; G 1583
+U 5210 ; WX 1048 ; N uni145A ; G 1584
+U 5211 ; WX 1056 ; N uni145B ; G 1585
+U 5212 ; WX 1048 ; N uni145C ; G 1586
+U 5213 ; WX 1060 ; N uni145D ; G 1587
+U 5214 ; WX 1054 ; N uni145E ; G 1588
+U 5215 ; WX 1060 ; N uni145F ; G 1589
+U 5216 ; WX 1054 ; N uni1460 ; G 1590
+U 5217 ; WX 1060 ; N uni1461 ; G 1591
+U 5218 ; WX 1052 ; N uni1462 ; G 1592
+U 5219 ; WX 1060 ; N uni1463 ; G 1593
+U 5220 ; WX 1052 ; N uni1464 ; G 1594
+U 5221 ; WX 1060 ; N uni1465 ; G 1595
+U 5222 ; WX 483 ; N uni1466 ; G 1596
+U 5223 ; WX 1005 ; N uni1467 ; G 1597
+U 5224 ; WX 1005 ; N uni1468 ; G 1598
+U 5225 ; WX 1023 ; N uni1469 ; G 1599
+U 5226 ; WX 1017 ; N uni146A ; G 1600
+U 5227 ; WX 743 ; N uni146B ; G 1601
+U 5228 ; WX 743 ; N uni146C ; G 1602
+U 5229 ; WX 743 ; N uni146D ; G 1603
+U 5230 ; WX 743 ; N uni146E ; G 1604
+U 5231 ; WX 743 ; N uni146F ; G 1605
+U 5232 ; WX 743 ; N uni1470 ; G 1606
+U 5233 ; WX 743 ; N uni1471 ; G 1607
+U 5234 ; WX 743 ; N uni1472 ; G 1608
+U 5235 ; WX 743 ; N uni1473 ; G 1609
+U 5236 ; WX 1029 ; N uni1474 ; G 1610
+U 5237 ; WX 975 ; N uni1475 ; G 1611
+U 5238 ; WX 980 ; N uni1476 ; G 1612
+U 5239 ; WX 975 ; N uni1477 ; G 1613
+U 5240 ; WX 980 ; N uni1478 ; G 1614
+U 5241 ; WX 975 ; N uni1479 ; G 1615
+U 5242 ; WX 1029 ; N uni147A ; G 1616
+U 5243 ; WX 975 ; N uni147B ; G 1617
+U 5244 ; WX 1029 ; N uni147C ; G 1618
+U 5245 ; WX 975 ; N uni147D ; G 1619
+U 5246 ; WX 980 ; N uni147E ; G 1620
+U 5247 ; WX 975 ; N uni147F ; G 1621
+U 5248 ; WX 980 ; N uni1480 ; G 1622
+U 5249 ; WX 975 ; N uni1481 ; G 1623
+U 5250 ; WX 980 ; N uni1482 ; G 1624
+U 5251 ; WX 501 ; N uni1483 ; G 1625
+U 5252 ; WX 501 ; N uni1484 ; G 1626
+U 5253 ; WX 938 ; N uni1485 ; G 1627
+U 5254 ; WX 938 ; N uni1486 ; G 1628
+U 5255 ; WX 938 ; N uni1487 ; G 1629
+U 5256 ; WX 938 ; N uni1488 ; G 1630
+U 5257 ; WX 743 ; N uni1489 ; G 1631
+U 5258 ; WX 743 ; N uni148A ; G 1632
+U 5259 ; WX 743 ; N uni148B ; G 1633
+U 5260 ; WX 743 ; N uni148C ; G 1634
+U 5261 ; WX 743 ; N uni148D ; G 1635
+U 5262 ; WX 743 ; N uni148E ; G 1636
+U 5263 ; WX 743 ; N uni148F ; G 1637
+U 5264 ; WX 743 ; N uni1490 ; G 1638
+U 5265 ; WX 743 ; N uni1491 ; G 1639
+U 5266 ; WX 1029 ; N uni1492 ; G 1640
+U 5267 ; WX 975 ; N uni1493 ; G 1641
+U 5268 ; WX 1029 ; N uni1494 ; G 1642
+U 5269 ; WX 975 ; N uni1495 ; G 1643
+U 5270 ; WX 1029 ; N uni1496 ; G 1644
+U 5271 ; WX 975 ; N uni1497 ; G 1645
+U 5272 ; WX 1029 ; N uni1498 ; G 1646
+U 5273 ; WX 975 ; N uni1499 ; G 1647
+U 5274 ; WX 1029 ; N uni149A ; G 1648
+U 5275 ; WX 975 ; N uni149B ; G 1649
+U 5276 ; WX 1029 ; N uni149C ; G 1650
+U 5277 ; WX 975 ; N uni149D ; G 1651
+U 5278 ; WX 1029 ; N uni149E ; G 1652
+U 5279 ; WX 975 ; N uni149F ; G 1653
+U 5280 ; WX 1029 ; N uni14A0 ; G 1654
+U 5281 ; WX 501 ; N uni14A1 ; G 1655
+U 5282 ; WX 501 ; N uni14A2 ; G 1656
+U 5283 ; WX 626 ; N uni14A3 ; G 1657
+U 5284 ; WX 626 ; N uni14A4 ; G 1658
+U 5285 ; WX 626 ; N uni14A5 ; G 1659
+U 5286 ; WX 626 ; N uni14A6 ; G 1660
+U 5287 ; WX 626 ; N uni14A7 ; G 1661
+U 5288 ; WX 626 ; N uni14A8 ; G 1662
+U 5289 ; WX 626 ; N uni14A9 ; G 1663
+U 5290 ; WX 626 ; N uni14AA ; G 1664
+U 5291 ; WX 626 ; N uni14AB ; G 1665
+U 5292 ; WX 881 ; N uni14AC ; G 1666
+U 5293 ; WX 854 ; N uni14AD ; G 1667
+U 5294 ; WX 863 ; N uni14AE ; G 1668
+U 5295 ; WX 874 ; N uni14AF ; G 1669
+U 5296 ; WX 863 ; N uni14B0 ; G 1670
+U 5297 ; WX 874 ; N uni14B1 ; G 1671
+U 5298 ; WX 881 ; N uni14B2 ; G 1672
+U 5299 ; WX 874 ; N uni14B3 ; G 1673
+U 5300 ; WX 881 ; N uni14B4 ; G 1674
+U 5301 ; WX 874 ; N uni14B5 ; G 1675
+U 5302 ; WX 863 ; N uni14B6 ; G 1676
+U 5303 ; WX 874 ; N uni14B7 ; G 1677
+U 5304 ; WX 863 ; N uni14B8 ; G 1678
+U 5305 ; WX 874 ; N uni14B9 ; G 1679
+U 5306 ; WX 863 ; N uni14BA ; G 1680
+U 5307 ; WX 436 ; N uni14BB ; G 1681
+U 5308 ; WX 548 ; N uni14BC ; G 1682
+U 5309 ; WX 436 ; N uni14BD ; G 1683
+U 5312 ; WX 988 ; N uni14C0 ; G 1684
+U 5313 ; WX 988 ; N uni14C1 ; G 1685
+U 5314 ; WX 988 ; N uni14C2 ; G 1686
+U 5315 ; WX 988 ; N uni14C3 ; G 1687
+U 5316 ; WX 931 ; N uni14C4 ; G 1688
+U 5317 ; WX 931 ; N uni14C5 ; G 1689
+U 5318 ; WX 931 ; N uni14C6 ; G 1690
+U 5319 ; WX 931 ; N uni14C7 ; G 1691
+U 5320 ; WX 931 ; N uni14C8 ; G 1692
+U 5321 ; WX 1238 ; N uni14C9 ; G 1693
+U 5322 ; WX 1247 ; N uni14CA ; G 1694
+U 5323 ; WX 1200 ; N uni14CB ; G 1695
+U 5324 ; WX 1228 ; N uni14CC ; G 1696
+U 5325 ; WX 1200 ; N uni14CD ; G 1697
+U 5326 ; WX 1228 ; N uni14CE ; G 1698
+U 5327 ; WX 931 ; N uni14CF ; G 1699
+U 5328 ; WX 660 ; N uni14D0 ; G 1700
+U 5329 ; WX 497 ; N uni14D1 ; G 1701
+U 5330 ; WX 660 ; N uni14D2 ; G 1702
+U 5331 ; WX 988 ; N uni14D3 ; G 1703
+U 5332 ; WX 988 ; N uni14D4 ; G 1704
+U 5333 ; WX 988 ; N uni14D5 ; G 1705
+U 5334 ; WX 988 ; N uni14D6 ; G 1706
+U 5335 ; WX 931 ; N uni14D7 ; G 1707
+U 5336 ; WX 931 ; N uni14D8 ; G 1708
+U 5337 ; WX 931 ; N uni14D9 ; G 1709
+U 5338 ; WX 931 ; N uni14DA ; G 1710
+U 5339 ; WX 931 ; N uni14DB ; G 1711
+U 5340 ; WX 1231 ; N uni14DC ; G 1712
+U 5341 ; WX 1247 ; N uni14DD ; G 1713
+U 5342 ; WX 1283 ; N uni14DE ; G 1714
+U 5343 ; WX 1228 ; N uni14DF ; G 1715
+U 5344 ; WX 1283 ; N uni14E0 ; G 1716
+U 5345 ; WX 1228 ; N uni14E1 ; G 1717
+U 5346 ; WX 1228 ; N uni14E2 ; G 1718
+U 5347 ; WX 1214 ; N uni14E3 ; G 1719
+U 5348 ; WX 1228 ; N uni14E4 ; G 1720
+U 5349 ; WX 1214 ; N uni14E5 ; G 1721
+U 5350 ; WX 1283 ; N uni14E6 ; G 1722
+U 5351 ; WX 1228 ; N uni14E7 ; G 1723
+U 5352 ; WX 1283 ; N uni14E8 ; G 1724
+U 5353 ; WX 1228 ; N uni14E9 ; G 1725
+U 5354 ; WX 660 ; N uni14EA ; G 1726
+U 5356 ; WX 886 ; N uni14EC ; G 1727
+U 5357 ; WX 730 ; N uni14ED ; G 1728
+U 5358 ; WX 730 ; N uni14EE ; G 1729
+U 5359 ; WX 730 ; N uni14EF ; G 1730
+U 5360 ; WX 730 ; N uni14F0 ; G 1731
+U 5361 ; WX 730 ; N uni14F1 ; G 1732
+U 5362 ; WX 730 ; N uni14F2 ; G 1733
+U 5363 ; WX 730 ; N uni14F3 ; G 1734
+U 5364 ; WX 730 ; N uni14F4 ; G 1735
+U 5365 ; WX 730 ; N uni14F5 ; G 1736
+U 5366 ; WX 998 ; N uni14F6 ; G 1737
+U 5367 ; WX 958 ; N uni14F7 ; G 1738
+U 5368 ; WX 967 ; N uni14F8 ; G 1739
+U 5369 ; WX 989 ; N uni14F9 ; G 1740
+U 5370 ; WX 967 ; N uni14FA ; G 1741
+U 5371 ; WX 989 ; N uni14FB ; G 1742
+U 5372 ; WX 998 ; N uni14FC ; G 1743
+U 5373 ; WX 958 ; N uni14FD ; G 1744
+U 5374 ; WX 998 ; N uni14FE ; G 1745
+U 5375 ; WX 958 ; N uni14FF ; G 1746
+U 5376 ; WX 967 ; N uni1500 ; G 1747
+U 5377 ; WX 989 ; N uni1501 ; G 1748
+U 5378 ; WX 967 ; N uni1502 ; G 1749
+U 5379 ; WX 989 ; N uni1503 ; G 1750
+U 5380 ; WX 967 ; N uni1504 ; G 1751
+U 5381 ; WX 493 ; N uni1505 ; G 1752
+U 5382 ; WX 460 ; N uni1506 ; G 1753
+U 5383 ; WX 493 ; N uni1507 ; G 1754
+U 5392 ; WX 923 ; N uni1510 ; G 1755
+U 5393 ; WX 923 ; N uni1511 ; G 1756
+U 5394 ; WX 923 ; N uni1512 ; G 1757
+U 5395 ; WX 1136 ; N uni1513 ; G 1758
+U 5396 ; WX 1136 ; N uni1514 ; G 1759
+U 5397 ; WX 1136 ; N uni1515 ; G 1760
+U 5398 ; WX 1136 ; N uni1516 ; G 1761
+U 5399 ; WX 1209 ; N uni1517 ; G 1762
+U 5400 ; WX 1202 ; N uni1518 ; G 1763
+U 5401 ; WX 1209 ; N uni1519 ; G 1764
+U 5402 ; WX 1202 ; N uni151A ; G 1765
+U 5403 ; WX 1209 ; N uni151B ; G 1766
+U 5404 ; WX 1202 ; N uni151C ; G 1767
+U 5405 ; WX 1431 ; N uni151D ; G 1768
+U 5406 ; WX 1420 ; N uni151E ; G 1769
+U 5407 ; WX 1431 ; N uni151F ; G 1770
+U 5408 ; WX 1420 ; N uni1520 ; G 1771
+U 5409 ; WX 1431 ; N uni1521 ; G 1772
+U 5410 ; WX 1420 ; N uni1522 ; G 1773
+U 5411 ; WX 1431 ; N uni1523 ; G 1774
+U 5412 ; WX 1420 ; N uni1524 ; G 1775
+U 5413 ; WX 746 ; N uni1525 ; G 1776
+U 5414 ; WX 776 ; N uni1526 ; G 1777
+U 5415 ; WX 776 ; N uni1527 ; G 1778
+U 5416 ; WX 776 ; N uni1528 ; G 1779
+U 5417 ; WX 776 ; N uni1529 ; G 1780
+U 5418 ; WX 776 ; N uni152A ; G 1781
+U 5419 ; WX 776 ; N uni152B ; G 1782
+U 5420 ; WX 776 ; N uni152C ; G 1783
+U 5421 ; WX 776 ; N uni152D ; G 1784
+U 5422 ; WX 776 ; N uni152E ; G 1785
+U 5423 ; WX 1003 ; N uni152F ; G 1786
+U 5424 ; WX 1003 ; N uni1530 ; G 1787
+U 5425 ; WX 1013 ; N uni1531 ; G 1788
+U 5426 ; WX 996 ; N uni1532 ; G 1789
+U 5427 ; WX 1013 ; N uni1533 ; G 1790
+U 5428 ; WX 996 ; N uni1534 ; G 1791
+U 5429 ; WX 1003 ; N uni1535 ; G 1792
+U 5430 ; WX 1003 ; N uni1536 ; G 1793
+U 5431 ; WX 1003 ; N uni1537 ; G 1794
+U 5432 ; WX 1003 ; N uni1538 ; G 1795
+U 5433 ; WX 1013 ; N uni1539 ; G 1796
+U 5434 ; WX 996 ; N uni153A ; G 1797
+U 5435 ; WX 1013 ; N uni153B ; G 1798
+U 5436 ; WX 996 ; N uni153C ; G 1799
+U 5437 ; WX 1013 ; N uni153D ; G 1800
+U 5438 ; WX 495 ; N uni153E ; G 1801
+U 5440 ; WX 395 ; N uni1540 ; G 1802
+U 5441 ; WX 510 ; N uni1541 ; G 1803
+U 5442 ; WX 1033 ; N uni1542 ; G 1804
+U 5443 ; WX 1033 ; N uni1543 ; G 1805
+U 5444 ; WX 976 ; N uni1544 ; G 1806
+U 5445 ; WX 976 ; N uni1545 ; G 1807
+U 5446 ; WX 976 ; N uni1546 ; G 1808
+U 5447 ; WX 976 ; N uni1547 ; G 1809
+U 5448 ; WX 733 ; N uni1548 ; G 1810
+U 5449 ; WX 733 ; N uni1549 ; G 1811
+U 5450 ; WX 733 ; N uni154A ; G 1812
+U 5451 ; WX 733 ; N uni154B ; G 1813
+U 5452 ; WX 733 ; N uni154C ; G 1814
+U 5453 ; WX 733 ; N uni154D ; G 1815
+U 5454 ; WX 1003 ; N uni154E ; G 1816
+U 5455 ; WX 959 ; N uni154F ; G 1817
+U 5456 ; WX 495 ; N uni1550 ; G 1818
+U 5458 ; WX 886 ; N uni1552 ; G 1819
+U 5459 ; WX 774 ; N uni1553 ; G 1820
+U 5460 ; WX 774 ; N uni1554 ; G 1821
+U 5461 ; WX 774 ; N uni1555 ; G 1822
+U 5462 ; WX 774 ; N uni1556 ; G 1823
+U 5463 ; WX 928 ; N uni1557 ; G 1824
+U 5464 ; WX 928 ; N uni1558 ; G 1825
+U 5465 ; WX 928 ; N uni1559 ; G 1826
+U 5466 ; WX 928 ; N uni155A ; G 1827
+U 5467 ; WX 1172 ; N uni155B ; G 1828
+U 5468 ; WX 1142 ; N uni155C ; G 1829
+U 5469 ; WX 602 ; N uni155D ; G 1830
+U 5470 ; WX 812 ; N uni155E ; G 1831
+U 5471 ; WX 812 ; N uni155F ; G 1832
+U 5472 ; WX 812 ; N uni1560 ; G 1833
+U 5473 ; WX 812 ; N uni1561 ; G 1834
+U 5474 ; WX 812 ; N uni1562 ; G 1835
+U 5475 ; WX 812 ; N uni1563 ; G 1836
+U 5476 ; WX 815 ; N uni1564 ; G 1837
+U 5477 ; WX 815 ; N uni1565 ; G 1838
+U 5478 ; WX 815 ; N uni1566 ; G 1839
+U 5479 ; WX 815 ; N uni1567 ; G 1840
+U 5480 ; WX 1060 ; N uni1568 ; G 1841
+U 5481 ; WX 1052 ; N uni1569 ; G 1842
+U 5482 ; WX 548 ; N uni156A ; G 1843
+U 5492 ; WX 977 ; N uni1574 ; G 1844
+U 5493 ; WX 977 ; N uni1575 ; G 1845
+U 5494 ; WX 977 ; N uni1576 ; G 1846
+U 5495 ; WX 977 ; N uni1577 ; G 1847
+U 5496 ; WX 977 ; N uni1578 ; G 1848
+U 5497 ; WX 977 ; N uni1579 ; G 1849
+U 5498 ; WX 977 ; N uni157A ; G 1850
+U 5499 ; WX 618 ; N uni157B ; G 1851
+U 5500 ; WX 837 ; N uni157C ; G 1852
+U 5501 ; WX 510 ; N uni157D ; G 1853
+U 5502 ; WX 1238 ; N uni157E ; G 1854
+U 5503 ; WX 1238 ; N uni157F ; G 1855
+U 5504 ; WX 1238 ; N uni1580 ; G 1856
+U 5505 ; WX 1238 ; N uni1581 ; G 1857
+U 5506 ; WX 1238 ; N uni1582 ; G 1858
+U 5507 ; WX 1238 ; N uni1583 ; G 1859
+U 5508 ; WX 1238 ; N uni1584 ; G 1860
+U 5509 ; WX 989 ; N uni1585 ; G 1861
+U 5514 ; WX 977 ; N uni158A ; G 1862
+U 5515 ; WX 977 ; N uni158B ; G 1863
+U 5516 ; WX 977 ; N uni158C ; G 1864
+U 5517 ; WX 977 ; N uni158D ; G 1865
+U 5518 ; WX 1591 ; N uni158E ; G 1866
+U 5519 ; WX 1591 ; N uni158F ; G 1867
+U 5520 ; WX 1591 ; N uni1590 ; G 1868
+U 5521 ; WX 1295 ; N uni1591 ; G 1869
+U 5522 ; WX 1295 ; N uni1592 ; G 1870
+U 5523 ; WX 1591 ; N uni1593 ; G 1871
+U 5524 ; WX 1591 ; N uni1594 ; G 1872
+U 5525 ; WX 848 ; N uni1595 ; G 1873
+U 5526 ; WX 1273 ; N uni1596 ; G 1874
+U 5536 ; WX 988 ; N uni15A0 ; G 1875
+U 5537 ; WX 988 ; N uni15A1 ; G 1876
+U 5538 ; WX 931 ; N uni15A2 ; G 1877
+U 5539 ; WX 931 ; N uni15A3 ; G 1878
+U 5540 ; WX 931 ; N uni15A4 ; G 1879
+U 5541 ; WX 931 ; N uni15A5 ; G 1880
+U 5542 ; WX 660 ; N uni15A6 ; G 1881
+U 5543 ; WX 776 ; N uni15A7 ; G 1882
+U 5544 ; WX 776 ; N uni15A8 ; G 1883
+U 5545 ; WX 776 ; N uni15A9 ; G 1884
+U 5546 ; WX 776 ; N uni15AA ; G 1885
+U 5547 ; WX 776 ; N uni15AB ; G 1886
+U 5548 ; WX 776 ; N uni15AC ; G 1887
+U 5549 ; WX 776 ; N uni15AD ; G 1888
+U 5550 ; WX 495 ; N uni15AE ; G 1889
+U 5551 ; WX 743 ; N uni15AF ; G 1890
+U 5598 ; WX 830 ; N uni15DE ; G 1891
+U 5601 ; WX 830 ; N uni15E1 ; G 1892
+U 5702 ; WX 496 ; N uni1646 ; G 1893
+U 5703 ; WX 496 ; N uni1647 ; G 1894
+U 5742 ; WX 413 ; N uni166E ; G 1895
+U 5743 ; WX 1238 ; N uni166F ; G 1896
+U 5744 ; WX 1591 ; N uni1670 ; G 1897
+U 5745 ; WX 2016 ; N uni1671 ; G 1898
+U 5746 ; WX 2016 ; N uni1672 ; G 1899
+U 5747 ; WX 1720 ; N uni1673 ; G 1900
+U 5748 ; WX 1678 ; N uni1674 ; G 1901
+U 5749 ; WX 2016 ; N uni1675 ; G 1902
+U 5750 ; WX 2016 ; N uni1676 ; G 1903
+U 7424 ; WX 652 ; N uni1D00 ; G 1904
+U 7425 ; WX 833 ; N uni1D01 ; G 1905
+U 7426 ; WX 1048 ; N uni1D02 ; G 1906
+U 7427 ; WX 608 ; N uni1D03 ; G 1907
+U 7428 ; WX 593 ; N uni1D04 ; G 1908
+U 7429 ; WX 676 ; N uni1D05 ; G 1909
+U 7430 ; WX 676 ; N uni1D06 ; G 1910
+U 7431 ; WX 559 ; N uni1D07 ; G 1911
+U 7432 ; WX 557 ; N uni1D08 ; G 1912
+U 7433 ; WX 343 ; N uni1D09 ; G 1913
+U 7434 ; WX 494 ; N uni1D0A ; G 1914
+U 7435 ; WX 665 ; N uni1D0B ; G 1915
+U 7436 ; WX 539 ; N uni1D0C ; G 1916
+U 7437 ; WX 817 ; N uni1D0D ; G 1917
+U 7438 ; WX 701 ; N uni1D0E ; G 1918
+U 7439 ; WX 687 ; N uni1D0F ; G 1919
+U 7440 ; WX 593 ; N uni1D10 ; G 1920
+U 7441 ; WX 660 ; N uni1D11 ; G 1921
+U 7442 ; WX 660 ; N uni1D12 ; G 1922
+U 7443 ; WX 660 ; N uni1D13 ; G 1923
+U 7444 ; WX 1094 ; N uni1D14 ; G 1924
+U 7446 ; WX 687 ; N uni1D16 ; G 1925
+U 7447 ; WX 687 ; N uni1D17 ; G 1926
+U 7448 ; WX 556 ; N uni1D18 ; G 1927
+U 7449 ; WX 642 ; N uni1D19 ; G 1928
+U 7450 ; WX 642 ; N uni1D1A ; G 1929
+U 7451 ; WX 580 ; N uni1D1B ; G 1930
+U 7452 ; WX 634 ; N uni1D1C ; G 1931
+U 7453 ; WX 737 ; N uni1D1D ; G 1932
+U 7454 ; WX 948 ; N uni1D1E ; G 1933
+U 7455 ; WX 695 ; N uni1D1F ; G 1934
+U 7456 ; WX 652 ; N uni1D20 ; G 1935
+U 7457 ; WX 924 ; N uni1D21 ; G 1936
+U 7458 ; WX 582 ; N uni1D22 ; G 1937
+U 7459 ; WX 646 ; N uni1D23 ; G 1938
+U 7462 ; WX 539 ; N uni1D26 ; G 1939
+U 7463 ; WX 652 ; N uni1D27 ; G 1940
+U 7464 ; WX 691 ; N uni1D28 ; G 1941
+U 7465 ; WX 556 ; N uni1D29 ; G 1942
+U 7466 ; WX 781 ; N uni1D2A ; G 1943
+U 7467 ; WX 732 ; N uni1D2B ; G 1944
+U 7468 ; WX 487 ; N uni1D2C ; G 1945
+U 7469 ; WX 683 ; N uni1D2D ; G 1946
+U 7470 ; WX 480 ; N uni1D2E ; G 1947
+U 7472 ; WX 523 ; N uni1D30 ; G 1948
+U 7473 ; WX 430 ; N uni1D31 ; G 1949
+U 7474 ; WX 430 ; N uni1D32 ; G 1950
+U 7475 ; WX 517 ; N uni1D33 ; G 1951
+U 7476 ; WX 527 ; N uni1D34 ; G 1952
+U 7477 ; WX 234 ; N uni1D35 ; G 1953
+U 7478 ; WX 234 ; N uni1D36 ; G 1954
+U 7479 ; WX 488 ; N uni1D37 ; G 1955
+U 7480 ; WX 401 ; N uni1D38 ; G 1956
+U 7481 ; WX 626 ; N uni1D39 ; G 1957
+U 7482 ; WX 527 ; N uni1D3A ; G 1958
+U 7483 ; WX 527 ; N uni1D3B ; G 1959
+U 7484 ; WX 535 ; N uni1D3C ; G 1960
+U 7485 ; WX 509 ; N uni1D3D ; G 1961
+U 7486 ; WX 461 ; N uni1D3E ; G 1962
+U 7487 ; WX 485 ; N uni1D3F ; G 1963
+U 7488 ; WX 430 ; N uni1D40 ; G 1964
+U 7489 ; WX 511 ; N uni1D41 ; G 1965
+U 7490 ; WX 695 ; N uni1D42 ; G 1966
+U 7491 ; WX 458 ; N uni1D43 ; G 1967
+U 7492 ; WX 458 ; N uni1D44 ; G 1968
+U 7493 ; WX 479 ; N uni1D45 ; G 1969
+U 7494 ; WX 712 ; N uni1D46 ; G 1970
+U 7495 ; WX 479 ; N uni1D47 ; G 1971
+U 7496 ; WX 479 ; N uni1D48 ; G 1972
+U 7497 ; WX 479 ; N uni1D49 ; G 1973
+U 7498 ; WX 479 ; N uni1D4A ; G 1974
+U 7499 ; WX 386 ; N uni1D4B ; G 1975
+U 7500 ; WX 386 ; N uni1D4C ; G 1976
+U 7501 ; WX 479 ; N uni1D4D ; G 1977
+U 7502 ; WX 219 ; N uni1D4E ; G 1978
+U 7503 ; WX 487 ; N uni1D4F ; G 1979
+U 7504 ; WX 664 ; N uni1D50 ; G 1980
+U 7505 ; WX 456 ; N uni1D51 ; G 1981
+U 7506 ; WX 488 ; N uni1D52 ; G 1982
+U 7507 ; WX 414 ; N uni1D53 ; G 1983
+U 7508 ; WX 488 ; N uni1D54 ; G 1984
+U 7509 ; WX 488 ; N uni1D55 ; G 1985
+U 7510 ; WX 479 ; N uni1D56 ; G 1986
+U 7511 ; WX 388 ; N uni1D57 ; G 1987
+U 7512 ; WX 456 ; N uni1D58 ; G 1988
+U 7513 ; WX 462 ; N uni1D59 ; G 1989
+U 7514 ; WX 664 ; N uni1D5A ; G 1990
+U 7515 ; WX 501 ; N uni1D5B ; G 1991
+U 7517 ; WX 451 ; N uni1D5D ; G 1992
+U 7518 ; WX 429 ; N uni1D5E ; G 1993
+U 7519 ; WX 433 ; N uni1D5F ; G 1994
+U 7520 ; WX 493 ; N uni1D60 ; G 1995
+U 7521 ; WX 406 ; N uni1D61 ; G 1996
+U 7522 ; WX 219 ; N uni1D62 ; G 1997
+U 7523 ; WX 315 ; N uni1D63 ; G 1998
+U 7524 ; WX 456 ; N uni1D64 ; G 1999
+U 7525 ; WX 501 ; N uni1D65 ; G 2000
+U 7526 ; WX 451 ; N uni1D66 ; G 2001
+U 7527 ; WX 429 ; N uni1D67 ; G 2002
+U 7528 ; WX 451 ; N uni1D68 ; G 2003
+U 7529 ; WX 493 ; N uni1D69 ; G 2004
+U 7530 ; WX 406 ; N uni1D6A ; G 2005
+U 7543 ; WX 716 ; N uni1D77 ; G 2006
+U 7544 ; WX 527 ; N uni1D78 ; G 2007
+U 7547 ; WX 545 ; N uni1D7B ; G 2008
+U 7549 ; WX 747 ; N uni1D7D ; G 2009
+U 7557 ; WX 514 ; N uni1D85 ; G 2010
+U 7579 ; WX 479 ; N uni1D9B ; G 2011
+U 7580 ; WX 414 ; N uni1D9C ; G 2012
+U 7581 ; WX 414 ; N uni1D9D ; G 2013
+U 7582 ; WX 488 ; N uni1D9E ; G 2014
+U 7583 ; WX 386 ; N uni1D9F ; G 2015
+U 7584 ; WX 377 ; N uni1DA0 ; G 2016
+U 7585 ; WX 348 ; N uni1DA1 ; G 2017
+U 7586 ; WX 479 ; N uni1DA2 ; G 2018
+U 7587 ; WX 456 ; N uni1DA3 ; G 2019
+U 7588 ; WX 347 ; N uni1DA4 ; G 2020
+U 7589 ; WX 281 ; N uni1DA5 ; G 2021
+U 7590 ; WX 347 ; N uni1DA6 ; G 2022
+U 7591 ; WX 347 ; N uni1DA7 ; G 2023
+U 7592 ; WX 431 ; N uni1DA8 ; G 2024
+U 7593 ; WX 326 ; N uni1DA9 ; G 2025
+U 7594 ; WX 330 ; N uni1DAA ; G 2026
+U 7595 ; WX 370 ; N uni1DAB ; G 2027
+U 7596 ; WX 664 ; N uni1DAC ; G 2028
+U 7597 ; WX 664 ; N uni1DAD ; G 2029
+U 7598 ; WX 562 ; N uni1DAE ; G 2030
+U 7599 ; WX 562 ; N uni1DAF ; G 2031
+U 7600 ; WX 448 ; N uni1DB0 ; G 2032
+U 7601 ; WX 488 ; N uni1DB1 ; G 2033
+U 7602 ; WX 542 ; N uni1DB2 ; G 2034
+U 7603 ; WX 422 ; N uni1DB3 ; G 2035
+U 7604 ; WX 396 ; N uni1DB4 ; G 2036
+U 7605 ; WX 388 ; N uni1DB5 ; G 2037
+U 7606 ; WX 583 ; N uni1DB6 ; G 2038
+U 7607 ; WX 494 ; N uni1DB7 ; G 2039
+U 7608 ; WX 399 ; N uni1DB8 ; G 2040
+U 7609 ; WX 451 ; N uni1DB9 ; G 2041
+U 7610 ; WX 501 ; N uni1DBA ; G 2042
+U 7611 ; WX 417 ; N uni1DBB ; G 2043
+U 7612 ; WX 523 ; N uni1DBC ; G 2044
+U 7613 ; WX 470 ; N uni1DBD ; G 2045
+U 7614 ; WX 455 ; N uni1DBE ; G 2046
+U 7615 ; WX 425 ; N uni1DBF ; G 2047
+U 7620 ; WX 0 ; N uni1DC4 ; G 2048
+U 7621 ; WX 0 ; N uni1DC5 ; G 2049
+U 7622 ; WX 0 ; N uni1DC6 ; G 2050
+U 7623 ; WX 0 ; N uni1DC7 ; G 2051
+U 7624 ; WX 0 ; N uni1DC8 ; G 2052
+U 7625 ; WX 0 ; N uni1DC9 ; G 2053
+U 7680 ; WX 774 ; N uni1E00 ; G 2054
+U 7681 ; WX 675 ; N uni1E01 ; G 2055
+U 7682 ; WX 762 ; N uni1E02 ; G 2056
+U 7683 ; WX 716 ; N uni1E03 ; G 2057
+U 7684 ; WX 762 ; N uni1E04 ; G 2058
+U 7685 ; WX 716 ; N uni1E05 ; G 2059
+U 7686 ; WX 762 ; N uni1E06 ; G 2060
+U 7687 ; WX 716 ; N uni1E07 ; G 2061
+U 7688 ; WX 734 ; N uni1E08 ; G 2062
+U 7689 ; WX 593 ; N uni1E09 ; G 2063
+U 7690 ; WX 830 ; N uni1E0A ; G 2064
+U 7691 ; WX 716 ; N uni1E0B ; G 2065
+U 7692 ; WX 830 ; N uni1E0C ; G 2066
+U 7693 ; WX 716 ; N uni1E0D ; G 2067
+U 7694 ; WX 830 ; N uni1E0E ; G 2068
+U 7695 ; WX 716 ; N uni1E0F ; G 2069
+U 7696 ; WX 830 ; N uni1E10 ; G 2070
+U 7697 ; WX 716 ; N uni1E11 ; G 2071
+U 7698 ; WX 830 ; N uni1E12 ; G 2072
+U 7699 ; WX 716 ; N uni1E13 ; G 2073
+U 7700 ; WX 683 ; N uni1E14 ; G 2074
+U 7701 ; WX 678 ; N uni1E15 ; G 2075
+U 7702 ; WX 683 ; N uni1E16 ; G 2076
+U 7703 ; WX 678 ; N uni1E17 ; G 2077
+U 7704 ; WX 683 ; N uni1E18 ; G 2078
+U 7705 ; WX 678 ; N uni1E19 ; G 2079
+U 7706 ; WX 683 ; N uni1E1A ; G 2080
+U 7707 ; WX 678 ; N uni1E1B ; G 2081
+U 7708 ; WX 683 ; N uni1E1C ; G 2082
+U 7709 ; WX 678 ; N uni1E1D ; G 2083
+U 7710 ; WX 683 ; N uni1E1E ; G 2084
+U 7711 ; WX 435 ; N uni1E1F ; G 2085
+U 7712 ; WX 821 ; N uni1E20 ; G 2086
+U 7713 ; WX 716 ; N uni1E21 ; G 2087
+U 7714 ; WX 837 ; N uni1E22 ; G 2088
+U 7715 ; WX 712 ; N uni1E23 ; G 2089
+U 7716 ; WX 837 ; N uni1E24 ; G 2090
+U 7717 ; WX 712 ; N uni1E25 ; G 2091
+U 7718 ; WX 837 ; N uni1E26 ; G 2092
+U 7719 ; WX 712 ; N uni1E27 ; G 2093
+U 7720 ; WX 837 ; N uni1E28 ; G 2094
+U 7721 ; WX 712 ; N uni1E29 ; G 2095
+U 7722 ; WX 837 ; N uni1E2A ; G 2096
+U 7723 ; WX 712 ; N uni1E2B ; G 2097
+U 7724 ; WX 372 ; N uni1E2C ; G 2098
+U 7725 ; WX 343 ; N uni1E2D ; G 2099
+U 7726 ; WX 372 ; N uni1E2E ; G 2100
+U 7727 ; WX 343 ; N uni1E2F ; G 2101
+U 7728 ; WX 775 ; N uni1E30 ; G 2102
+U 7729 ; WX 665 ; N uni1E31 ; G 2103
+U 7730 ; WX 775 ; N uni1E32 ; G 2104
+U 7731 ; WX 665 ; N uni1E33 ; G 2105
+U 7732 ; WX 775 ; N uni1E34 ; G 2106
+U 7733 ; WX 665 ; N uni1E35 ; G 2107
+U 7734 ; WX 637 ; N uni1E36 ; G 2108
+U 7735 ; WX 343 ; N uni1E37 ; G 2109
+U 7736 ; WX 637 ; N uni1E38 ; G 2110
+U 7737 ; WX 343 ; N uni1E39 ; G 2111
+U 7738 ; WX 637 ; N uni1E3A ; G 2112
+U 7739 ; WX 343 ; N uni1E3B ; G 2113
+U 7740 ; WX 637 ; N uni1E3C ; G 2114
+U 7741 ; WX 343 ; N uni1E3D ; G 2115
+U 7742 ; WX 995 ; N uni1E3E ; G 2116
+U 7743 ; WX 1042 ; N uni1E3F ; G 2117
+U 7744 ; WX 995 ; N uni1E40 ; G 2118
+U 7745 ; WX 1042 ; N uni1E41 ; G 2119
+U 7746 ; WX 995 ; N uni1E42 ; G 2120
+U 7747 ; WX 1042 ; N uni1E43 ; G 2121
+U 7748 ; WX 837 ; N uni1E44 ; G 2122
+U 7749 ; WX 712 ; N uni1E45 ; G 2123
+U 7750 ; WX 837 ; N uni1E46 ; G 2124
+U 7751 ; WX 712 ; N uni1E47 ; G 2125
+U 7752 ; WX 837 ; N uni1E48 ; G 2126
+U 7753 ; WX 712 ; N uni1E49 ; G 2127
+U 7754 ; WX 837 ; N uni1E4A ; G 2128
+U 7755 ; WX 712 ; N uni1E4B ; G 2129
+U 7756 ; WX 850 ; N uni1E4C ; G 2130
+U 7757 ; WX 687 ; N uni1E4D ; G 2131
+U 7758 ; WX 850 ; N uni1E4E ; G 2132
+U 7759 ; WX 687 ; N uni1E4F ; G 2133
+U 7760 ; WX 850 ; N uni1E50 ; G 2134
+U 7761 ; WX 687 ; N uni1E51 ; G 2135
+U 7762 ; WX 850 ; N uni1E52 ; G 2136
+U 7763 ; WX 687 ; N uni1E53 ; G 2137
+U 7764 ; WX 733 ; N uni1E54 ; G 2138
+U 7765 ; WX 716 ; N uni1E55 ; G 2139
+U 7766 ; WX 733 ; N uni1E56 ; G 2140
+U 7767 ; WX 716 ; N uni1E57 ; G 2141
+U 7768 ; WX 770 ; N uni1E58 ; G 2142
+U 7769 ; WX 493 ; N uni1E59 ; G 2143
+U 7770 ; WX 770 ; N uni1E5A ; G 2144
+U 7771 ; WX 493 ; N uni1E5B ; G 2145
+U 7772 ; WX 770 ; N uni1E5C ; G 2146
+U 7773 ; WX 493 ; N uni1E5D ; G 2147
+U 7774 ; WX 770 ; N uni1E5E ; G 2148
+U 7775 ; WX 493 ; N uni1E5F ; G 2149
+U 7776 ; WX 720 ; N uni1E60 ; G 2150
+U 7777 ; WX 595 ; N uni1E61 ; G 2151
+U 7778 ; WX 720 ; N uni1E62 ; G 2152
+U 7779 ; WX 595 ; N uni1E63 ; G 2153
+U 7780 ; WX 720 ; N uni1E64 ; G 2154
+U 7781 ; WX 595 ; N uni1E65 ; G 2155
+U 7782 ; WX 720 ; N uni1E66 ; G 2156
+U 7783 ; WX 595 ; N uni1E67 ; G 2157
+U 7784 ; WX 720 ; N uni1E68 ; G 2158
+U 7785 ; WX 595 ; N uni1E69 ; G 2159
+U 7786 ; WX 682 ; N uni1E6A ; G 2160
+U 7787 ; WX 478 ; N uni1E6B ; G 2161
+U 7788 ; WX 682 ; N uni1E6C ; G 2162
+U 7789 ; WX 478 ; N uni1E6D ; G 2163
+U 7790 ; WX 682 ; N uni1E6E ; G 2164
+U 7791 ; WX 478 ; N uni1E6F ; G 2165
+U 7792 ; WX 682 ; N uni1E70 ; G 2166
+U 7793 ; WX 478 ; N uni1E71 ; G 2167
+U 7794 ; WX 812 ; N uni1E72 ; G 2168
+U 7795 ; WX 712 ; N uni1E73 ; G 2169
+U 7796 ; WX 812 ; N uni1E74 ; G 2170
+U 7797 ; WX 712 ; N uni1E75 ; G 2171
+U 7798 ; WX 812 ; N uni1E76 ; G 2172
+U 7799 ; WX 712 ; N uni1E77 ; G 2173
+U 7800 ; WX 812 ; N uni1E78 ; G 2174
+U 7801 ; WX 712 ; N uni1E79 ; G 2175
+U 7802 ; WX 812 ; N uni1E7A ; G 2176
+U 7803 ; WX 712 ; N uni1E7B ; G 2177
+U 7804 ; WX 774 ; N uni1E7C ; G 2178
+U 7805 ; WX 652 ; N uni1E7D ; G 2179
+U 7806 ; WX 774 ; N uni1E7E ; G 2180
+U 7807 ; WX 652 ; N uni1E7F ; G 2181
+U 7808 ; WX 1103 ; N Wgrave ; G 2182
+U 7809 ; WX 924 ; N wgrave ; G 2183
+U 7810 ; WX 1103 ; N Wacute ; G 2184
+U 7811 ; WX 924 ; N wacute ; G 2185
+U 7812 ; WX 1103 ; N Wdieresis ; G 2186
+U 7813 ; WX 924 ; N wdieresis ; G 2187
+U 7814 ; WX 1103 ; N uni1E86 ; G 2188
+U 7815 ; WX 924 ; N uni1E87 ; G 2189
+U 7816 ; WX 1103 ; N uni1E88 ; G 2190
+U 7817 ; WX 924 ; N uni1E89 ; G 2191
+U 7818 ; WX 771 ; N uni1E8A ; G 2192
+U 7819 ; WX 645 ; N uni1E8B ; G 2193
+U 7820 ; WX 771 ; N uni1E8C ; G 2194
+U 7821 ; WX 645 ; N uni1E8D ; G 2195
+U 7822 ; WX 724 ; N uni1E8E ; G 2196
+U 7823 ; WX 652 ; N uni1E8F ; G 2197
+U 7824 ; WX 725 ; N uni1E90 ; G 2198
+U 7825 ; WX 582 ; N uni1E91 ; G 2199
+U 7826 ; WX 725 ; N uni1E92 ; G 2200
+U 7827 ; WX 582 ; N uni1E93 ; G 2201
+U 7828 ; WX 725 ; N uni1E94 ; G 2202
+U 7829 ; WX 582 ; N uni1E95 ; G 2203
+U 7830 ; WX 712 ; N uni1E96 ; G 2204
+U 7831 ; WX 478 ; N uni1E97 ; G 2205
+U 7832 ; WX 924 ; N uni1E98 ; G 2206
+U 7833 ; WX 652 ; N uni1E99 ; G 2207
+U 7834 ; WX 675 ; N uni1E9A ; G 2208
+U 7835 ; WX 435 ; N uni1E9B ; G 2209
+U 7836 ; WX 435 ; N uni1E9C ; G 2210
+U 7837 ; WX 435 ; N uni1E9D ; G 2211
+U 7838 ; WX 896 ; N uni1E9E ; G 2212
+U 7839 ; WX 687 ; N uni1E9F ; G 2213
+U 7840 ; WX 774 ; N uni1EA0 ; G 2214
+U 7841 ; WX 675 ; N uni1EA1 ; G 2215
+U 7842 ; WX 774 ; N uni1EA2 ; G 2216
+U 7843 ; WX 675 ; N uni1EA3 ; G 2217
+U 7844 ; WX 774 ; N uni1EA4 ; G 2218
+U 7845 ; WX 675 ; N uni1EA5 ; G 2219
+U 7846 ; WX 774 ; N uni1EA6 ; G 2220
+U 7847 ; WX 675 ; N uni1EA7 ; G 2221
+U 7848 ; WX 774 ; N uni1EA8 ; G 2222
+U 7849 ; WX 675 ; N uni1EA9 ; G 2223
+U 7850 ; WX 774 ; N uni1EAA ; G 2224
+U 7851 ; WX 675 ; N uni1EAB ; G 2225
+U 7852 ; WX 774 ; N uni1EAC ; G 2226
+U 7853 ; WX 675 ; N uni1EAD ; G 2227
+U 7854 ; WX 774 ; N uni1EAE ; G 2228
+U 7855 ; WX 675 ; N uni1EAF ; G 2229
+U 7856 ; WX 774 ; N uni1EB0 ; G 2230
+U 7857 ; WX 675 ; N uni1EB1 ; G 2231
+U 7858 ; WX 774 ; N uni1EB2 ; G 2232
+U 7859 ; WX 675 ; N uni1EB3 ; G 2233
+U 7860 ; WX 774 ; N uni1EB4 ; G 2234
+U 7861 ; WX 675 ; N uni1EB5 ; G 2235
+U 7862 ; WX 774 ; N uni1EB6 ; G 2236
+U 7863 ; WX 675 ; N uni1EB7 ; G 2237
+U 7864 ; WX 683 ; N uni1EB8 ; G 2238
+U 7865 ; WX 678 ; N uni1EB9 ; G 2239
+U 7866 ; WX 683 ; N uni1EBA ; G 2240
+U 7867 ; WX 678 ; N uni1EBB ; G 2241
+U 7868 ; WX 683 ; N uni1EBC ; G 2242
+U 7869 ; WX 678 ; N uni1EBD ; G 2243
+U 7870 ; WX 683 ; N uni1EBE ; G 2244
+U 7871 ; WX 678 ; N uni1EBF ; G 2245
+U 7872 ; WX 683 ; N uni1EC0 ; G 2246
+U 7873 ; WX 678 ; N uni1EC1 ; G 2247
+U 7874 ; WX 683 ; N uni1EC2 ; G 2248
+U 7875 ; WX 678 ; N uni1EC3 ; G 2249
+U 7876 ; WX 683 ; N uni1EC4 ; G 2250
+U 7877 ; WX 678 ; N uni1EC5 ; G 2251
+U 7878 ; WX 683 ; N uni1EC6 ; G 2252
+U 7879 ; WX 678 ; N uni1EC7 ; G 2253
+U 7880 ; WX 372 ; N uni1EC8 ; G 2254
+U 7881 ; WX 343 ; N uni1EC9 ; G 2255
+U 7882 ; WX 372 ; N uni1ECA ; G 2256
+U 7883 ; WX 343 ; N uni1ECB ; G 2257
+U 7884 ; WX 850 ; N uni1ECC ; G 2258
+U 7885 ; WX 687 ; N uni1ECD ; G 2259
+U 7886 ; WX 850 ; N uni1ECE ; G 2260
+U 7887 ; WX 687 ; N uni1ECF ; G 2261
+U 7888 ; WX 850 ; N uni1ED0 ; G 2262
+U 7889 ; WX 687 ; N uni1ED1 ; G 2263
+U 7890 ; WX 850 ; N uni1ED2 ; G 2264
+U 7891 ; WX 687 ; N uni1ED3 ; G 2265
+U 7892 ; WX 850 ; N uni1ED4 ; G 2266
+U 7893 ; WX 687 ; N uni1ED5 ; G 2267
+U 7894 ; WX 850 ; N uni1ED6 ; G 2268
+U 7895 ; WX 687 ; N uni1ED7 ; G 2269
+U 7896 ; WX 850 ; N uni1ED8 ; G 2270
+U 7897 ; WX 687 ; N uni1ED9 ; G 2271
+U 7898 ; WX 850 ; N uni1EDA ; G 2272
+U 7899 ; WX 687 ; N uni1EDB ; G 2273
+U 7900 ; WX 850 ; N uni1EDC ; G 2274
+U 7901 ; WX 687 ; N uni1EDD ; G 2275
+U 7902 ; WX 850 ; N uni1EDE ; G 2276
+U 7903 ; WX 687 ; N uni1EDF ; G 2277
+U 7904 ; WX 850 ; N uni1EE0 ; G 2278
+U 7905 ; WX 687 ; N uni1EE1 ; G 2279
+U 7906 ; WX 850 ; N uni1EE2 ; G 2280
+U 7907 ; WX 687 ; N uni1EE3 ; G 2281
+U 7908 ; WX 812 ; N uni1EE4 ; G 2282
+U 7909 ; WX 712 ; N uni1EE5 ; G 2283
+U 7910 ; WX 812 ; N uni1EE6 ; G 2284
+U 7911 ; WX 712 ; N uni1EE7 ; G 2285
+U 7912 ; WX 812 ; N uni1EE8 ; G 2286
+U 7913 ; WX 712 ; N uni1EE9 ; G 2287
+U 7914 ; WX 812 ; N uni1EEA ; G 2288
+U 7915 ; WX 712 ; N uni1EEB ; G 2289
+U 7916 ; WX 812 ; N uni1EEC ; G 2290
+U 7917 ; WX 712 ; N uni1EED ; G 2291
+U 7918 ; WX 812 ; N uni1EEE ; G 2292
+U 7919 ; WX 712 ; N uni1EEF ; G 2293
+U 7920 ; WX 812 ; N uni1EF0 ; G 2294
+U 7921 ; WX 712 ; N uni1EF1 ; G 2295
+U 7922 ; WX 724 ; N Ygrave ; G 2296
+U 7923 ; WX 652 ; N ygrave ; G 2297
+U 7924 ; WX 724 ; N uni1EF4 ; G 2298
+U 7925 ; WX 652 ; N uni1EF5 ; G 2299
+U 7926 ; WX 724 ; N uni1EF6 ; G 2300
+U 7927 ; WX 652 ; N uni1EF7 ; G 2301
+U 7928 ; WX 724 ; N uni1EF8 ; G 2302
+U 7929 ; WX 652 ; N uni1EF9 ; G 2303
+U 7930 ; WX 953 ; N uni1EFA ; G 2304
+U 7931 ; WX 644 ; N uni1EFB ; G 2305
+U 7936 ; WX 687 ; N uni1F00 ; G 2306
+U 7937 ; WX 687 ; N uni1F01 ; G 2307
+U 7938 ; WX 687 ; N uni1F02 ; G 2308
+U 7939 ; WX 687 ; N uni1F03 ; G 2309
+U 7940 ; WX 687 ; N uni1F04 ; G 2310
+U 7941 ; WX 687 ; N uni1F05 ; G 2311
+U 7942 ; WX 687 ; N uni1F06 ; G 2312
+U 7943 ; WX 687 ; N uni1F07 ; G 2313
+U 7944 ; WX 774 ; N uni1F08 ; G 2314
+U 7945 ; WX 774 ; N uni1F09 ; G 2315
+U 7946 ; WX 1041 ; N uni1F0A ; G 2316
+U 7947 ; WX 1043 ; N uni1F0B ; G 2317
+U 7948 ; WX 935 ; N uni1F0C ; G 2318
+U 7949 ; WX 963 ; N uni1F0D ; G 2319
+U 7950 ; WX 835 ; N uni1F0E ; G 2320
+U 7951 ; WX 859 ; N uni1F0F ; G 2321
+U 7952 ; WX 557 ; N uni1F10 ; G 2322
+U 7953 ; WX 557 ; N uni1F11 ; G 2323
+U 7954 ; WX 557 ; N uni1F12 ; G 2324
+U 7955 ; WX 557 ; N uni1F13 ; G 2325
+U 7956 ; WX 557 ; N uni1F14 ; G 2326
+U 7957 ; WX 557 ; N uni1F15 ; G 2327
+U 7960 ; WX 792 ; N uni1F18 ; G 2328
+U 7961 ; WX 794 ; N uni1F19 ; G 2329
+U 7962 ; WX 1100 ; N uni1F1A ; G 2330
+U 7963 ; WX 1096 ; N uni1F1B ; G 2331
+U 7964 ; WX 1023 ; N uni1F1C ; G 2332
+U 7965 ; WX 1052 ; N uni1F1D ; G 2333
+U 7968 ; WX 712 ; N uni1F20 ; G 2334
+U 7969 ; WX 712 ; N uni1F21 ; G 2335
+U 7970 ; WX 712 ; N uni1F22 ; G 2336
+U 7971 ; WX 712 ; N uni1F23 ; G 2337
+U 7972 ; WX 712 ; N uni1F24 ; G 2338
+U 7973 ; WX 712 ; N uni1F25 ; G 2339
+U 7974 ; WX 712 ; N uni1F26 ; G 2340
+U 7975 ; WX 712 ; N uni1F27 ; G 2341
+U 7976 ; WX 945 ; N uni1F28 ; G 2342
+U 7977 ; WX 951 ; N uni1F29 ; G 2343
+U 7978 ; WX 1250 ; N uni1F2A ; G 2344
+U 7979 ; WX 1250 ; N uni1F2B ; G 2345
+U 7980 ; WX 1180 ; N uni1F2C ; G 2346
+U 7981 ; WX 1206 ; N uni1F2D ; G 2347
+U 7982 ; WX 1054 ; N uni1F2E ; G 2348
+U 7983 ; WX 1063 ; N uni1F2F ; G 2349
+U 7984 ; WX 390 ; N uni1F30 ; G 2350
+U 7985 ; WX 390 ; N uni1F31 ; G 2351
+U 7986 ; WX 390 ; N uni1F32 ; G 2352
+U 7987 ; WX 390 ; N uni1F33 ; G 2353
+U 7988 ; WX 390 ; N uni1F34 ; G 2354
+U 7989 ; WX 390 ; N uni1F35 ; G 2355
+U 7990 ; WX 390 ; N uni1F36 ; G 2356
+U 7991 ; WX 390 ; N uni1F37 ; G 2357
+U 7992 ; WX 483 ; N uni1F38 ; G 2358
+U 7993 ; WX 489 ; N uni1F39 ; G 2359
+U 7994 ; WX 777 ; N uni1F3A ; G 2360
+U 7995 ; WX 785 ; N uni1F3B ; G 2361
+U 7996 ; WX 712 ; N uni1F3C ; G 2362
+U 7997 ; WX 738 ; N uni1F3D ; G 2363
+U 7998 ; WX 604 ; N uni1F3E ; G 2364
+U 7999 ; WX 604 ; N uni1F3F ; G 2365
+U 8000 ; WX 687 ; N uni1F40 ; G 2366
+U 8001 ; WX 687 ; N uni1F41 ; G 2367
+U 8002 ; WX 687 ; N uni1F42 ; G 2368
+U 8003 ; WX 687 ; N uni1F43 ; G 2369
+U 8004 ; WX 687 ; N uni1F44 ; G 2370
+U 8005 ; WX 687 ; N uni1F45 ; G 2371
+U 8008 ; WX 892 ; N uni1F48 ; G 2372
+U 8009 ; WX 933 ; N uni1F49 ; G 2373
+U 8010 ; WX 1221 ; N uni1F4A ; G 2374
+U 8011 ; WX 1224 ; N uni1F4B ; G 2375
+U 8012 ; WX 1053 ; N uni1F4C ; G 2376
+U 8013 ; WX 1082 ; N uni1F4D ; G 2377
+U 8016 ; WX 675 ; N uni1F50 ; G 2378
+U 8017 ; WX 675 ; N uni1F51 ; G 2379
+U 8018 ; WX 675 ; N uni1F52 ; G 2380
+U 8019 ; WX 675 ; N uni1F53 ; G 2381
+U 8020 ; WX 675 ; N uni1F54 ; G 2382
+U 8021 ; WX 675 ; N uni1F55 ; G 2383
+U 8022 ; WX 675 ; N uni1F56 ; G 2384
+U 8023 ; WX 675 ; N uni1F57 ; G 2385
+U 8025 ; WX 930 ; N uni1F59 ; G 2386
+U 8027 ; WX 1184 ; N uni1F5B ; G 2387
+U 8029 ; WX 1199 ; N uni1F5D ; G 2388
+U 8031 ; WX 1049 ; N uni1F5F ; G 2389
+U 8032 ; WX 869 ; N uni1F60 ; G 2390
+U 8033 ; WX 869 ; N uni1F61 ; G 2391
+U 8034 ; WX 869 ; N uni1F62 ; G 2392
+U 8035 ; WX 869 ; N uni1F63 ; G 2393
+U 8036 ; WX 869 ; N uni1F64 ; G 2394
+U 8037 ; WX 869 ; N uni1F65 ; G 2395
+U 8038 ; WX 869 ; N uni1F66 ; G 2396
+U 8039 ; WX 869 ; N uni1F67 ; G 2397
+U 8040 ; WX 909 ; N uni1F68 ; G 2398
+U 8041 ; WX 958 ; N uni1F69 ; G 2399
+U 8042 ; WX 1246 ; N uni1F6A ; G 2400
+U 8043 ; WX 1251 ; N uni1F6B ; G 2401
+U 8044 ; WX 1076 ; N uni1F6C ; G 2402
+U 8045 ; WX 1105 ; N uni1F6D ; G 2403
+U 8046 ; WX 1028 ; N uni1F6E ; G 2404
+U 8047 ; WX 1076 ; N uni1F6F ; G 2405
+U 8048 ; WX 687 ; N uni1F70 ; G 2406
+U 8049 ; WX 687 ; N uni1F71 ; G 2407
+U 8050 ; WX 557 ; N uni1F72 ; G 2408
+U 8051 ; WX 557 ; N uni1F73 ; G 2409
+U 8052 ; WX 712 ; N uni1F74 ; G 2410
+U 8053 ; WX 712 ; N uni1F75 ; G 2411
+U 8054 ; WX 390 ; N uni1F76 ; G 2412
+U 8055 ; WX 390 ; N uni1F77 ; G 2413
+U 8056 ; WX 687 ; N uni1F78 ; G 2414
+U 8057 ; WX 687 ; N uni1F79 ; G 2415
+U 8058 ; WX 675 ; N uni1F7A ; G 2416
+U 8059 ; WX 675 ; N uni1F7B ; G 2417
+U 8060 ; WX 869 ; N uni1F7C ; G 2418
+U 8061 ; WX 869 ; N uni1F7D ; G 2419
+U 8064 ; WX 687 ; N uni1F80 ; G 2420
+U 8065 ; WX 687 ; N uni1F81 ; G 2421
+U 8066 ; WX 687 ; N uni1F82 ; G 2422
+U 8067 ; WX 687 ; N uni1F83 ; G 2423
+U 8068 ; WX 687 ; N uni1F84 ; G 2424
+U 8069 ; WX 687 ; N uni1F85 ; G 2425
+U 8070 ; WX 687 ; N uni1F86 ; G 2426
+U 8071 ; WX 687 ; N uni1F87 ; G 2427
+U 8072 ; WX 774 ; N uni1F88 ; G 2428
+U 8073 ; WX 774 ; N uni1F89 ; G 2429
+U 8074 ; WX 1041 ; N uni1F8A ; G 2430
+U 8075 ; WX 1043 ; N uni1F8B ; G 2431
+U 8076 ; WX 935 ; N uni1F8C ; G 2432
+U 8077 ; WX 963 ; N uni1F8D ; G 2433
+U 8078 ; WX 835 ; N uni1F8E ; G 2434
+U 8079 ; WX 859 ; N uni1F8F ; G 2435
+U 8080 ; WX 712 ; N uni1F90 ; G 2436
+U 8081 ; WX 712 ; N uni1F91 ; G 2437
+U 8082 ; WX 712 ; N uni1F92 ; G 2438
+U 8083 ; WX 712 ; N uni1F93 ; G 2439
+U 8084 ; WX 712 ; N uni1F94 ; G 2440
+U 8085 ; WX 712 ; N uni1F95 ; G 2441
+U 8086 ; WX 712 ; N uni1F96 ; G 2442
+U 8087 ; WX 712 ; N uni1F97 ; G 2443
+U 8088 ; WX 945 ; N uni1F98 ; G 2444
+U 8089 ; WX 951 ; N uni1F99 ; G 2445
+U 8090 ; WX 1250 ; N uni1F9A ; G 2446
+U 8091 ; WX 1250 ; N uni1F9B ; G 2447
+U 8092 ; WX 1180 ; N uni1F9C ; G 2448
+U 8093 ; WX 1206 ; N uni1F9D ; G 2449
+U 8094 ; WX 1054 ; N uni1F9E ; G 2450
+U 8095 ; WX 1063 ; N uni1F9F ; G 2451
+U 8096 ; WX 869 ; N uni1FA0 ; G 2452
+U 8097 ; WX 869 ; N uni1FA1 ; G 2453
+U 8098 ; WX 869 ; N uni1FA2 ; G 2454
+U 8099 ; WX 869 ; N uni1FA3 ; G 2455
+U 8100 ; WX 869 ; N uni1FA4 ; G 2456
+U 8101 ; WX 869 ; N uni1FA5 ; G 2457
+U 8102 ; WX 869 ; N uni1FA6 ; G 2458
+U 8103 ; WX 869 ; N uni1FA7 ; G 2459
+U 8104 ; WX 909 ; N uni1FA8 ; G 2460
+U 8105 ; WX 958 ; N uni1FA9 ; G 2461
+U 8106 ; WX 1246 ; N uni1FAA ; G 2462
+U 8107 ; WX 1251 ; N uni1FAB ; G 2463
+U 8108 ; WX 1076 ; N uni1FAC ; G 2464
+U 8109 ; WX 1105 ; N uni1FAD ; G 2465
+U 8110 ; WX 1028 ; N uni1FAE ; G 2466
+U 8111 ; WX 1076 ; N uni1FAF ; G 2467
+U 8112 ; WX 687 ; N uni1FB0 ; G 2468
+U 8113 ; WX 687 ; N uni1FB1 ; G 2469
+U 8114 ; WX 687 ; N uni1FB2 ; G 2470
+U 8115 ; WX 687 ; N uni1FB3 ; G 2471
+U 8116 ; WX 687 ; N uni1FB4 ; G 2472
+U 8118 ; WX 687 ; N uni1FB6 ; G 2473
+U 8119 ; WX 687 ; N uni1FB7 ; G 2474
+U 8120 ; WX 774 ; N uni1FB8 ; G 2475
+U 8121 ; WX 774 ; N uni1FB9 ; G 2476
+U 8122 ; WX 876 ; N uni1FBA ; G 2477
+U 8123 ; WX 797 ; N uni1FBB ; G 2478
+U 8124 ; WX 774 ; N uni1FBC ; G 2479
+U 8125 ; WX 500 ; N uni1FBD ; G 2480
+U 8126 ; WX 500 ; N uni1FBE ; G 2481
+U 8127 ; WX 500 ; N uni1FBF ; G 2482
+U 8128 ; WX 500 ; N uni1FC0 ; G 2483
+U 8129 ; WX 500 ; N uni1FC1 ; G 2484
+U 8130 ; WX 712 ; N uni1FC2 ; G 2485
+U 8131 ; WX 712 ; N uni1FC3 ; G 2486
+U 8132 ; WX 712 ; N uni1FC4 ; G 2487
+U 8134 ; WX 712 ; N uni1FC6 ; G 2488
+U 8135 ; WX 712 ; N uni1FC7 ; G 2489
+U 8136 ; WX 929 ; N uni1FC8 ; G 2490
+U 8137 ; WX 846 ; N uni1FC9 ; G 2491
+U 8138 ; WX 1080 ; N uni1FCA ; G 2492
+U 8139 ; WX 1009 ; N uni1FCB ; G 2493
+U 8140 ; WX 837 ; N uni1FCC ; G 2494
+U 8141 ; WX 500 ; N uni1FCD ; G 2495
+U 8142 ; WX 500 ; N uni1FCE ; G 2496
+U 8143 ; WX 500 ; N uni1FCF ; G 2497
+U 8144 ; WX 390 ; N uni1FD0 ; G 2498
+U 8145 ; WX 390 ; N uni1FD1 ; G 2499
+U 8146 ; WX 390 ; N uni1FD2 ; G 2500
+U 8147 ; WX 390 ; N uni1FD3 ; G 2501
+U 8150 ; WX 390 ; N uni1FD6 ; G 2502
+U 8151 ; WX 390 ; N uni1FD7 ; G 2503
+U 8152 ; WX 372 ; N uni1FD8 ; G 2504
+U 8153 ; WX 372 ; N uni1FD9 ; G 2505
+U 8154 ; WX 621 ; N uni1FDA ; G 2506
+U 8155 ; WX 563 ; N uni1FDB ; G 2507
+U 8157 ; WX 500 ; N uni1FDD ; G 2508
+U 8158 ; WX 500 ; N uni1FDE ; G 2509
+U 8159 ; WX 500 ; N uni1FDF ; G 2510
+U 8160 ; WX 675 ; N uni1FE0 ; G 2511
+U 8161 ; WX 675 ; N uni1FE1 ; G 2512
+U 8162 ; WX 675 ; N uni1FE2 ; G 2513
+U 8163 ; WX 675 ; N uni1FE3 ; G 2514
+U 8164 ; WX 716 ; N uni1FE4 ; G 2515
+U 8165 ; WX 716 ; N uni1FE5 ; G 2516
+U 8166 ; WX 675 ; N uni1FE6 ; G 2517
+U 8167 ; WX 675 ; N uni1FE7 ; G 2518
+U 8168 ; WX 724 ; N uni1FE8 ; G 2519
+U 8169 ; WX 724 ; N uni1FE9 ; G 2520
+U 8170 ; WX 1020 ; N uni1FEA ; G 2521
+U 8171 ; WX 980 ; N uni1FEB ; G 2522
+U 8172 ; WX 838 ; N uni1FEC ; G 2523
+U 8173 ; WX 500 ; N uni1FED ; G 2524
+U 8174 ; WX 500 ; N uni1FEE ; G 2525
+U 8175 ; WX 500 ; N uni1FEF ; G 2526
+U 8178 ; WX 869 ; N uni1FF2 ; G 2527
+U 8179 ; WX 869 ; N uni1FF3 ; G 2528
+U 8180 ; WX 869 ; N uni1FF4 ; G 2529
+U 8182 ; WX 869 ; N uni1FF6 ; G 2530
+U 8183 ; WX 869 ; N uni1FF7 ; G 2531
+U 8184 ; WX 1065 ; N uni1FF8 ; G 2532
+U 8185 ; WX 891 ; N uni1FF9 ; G 2533
+U 8186 ; WX 1084 ; N uni1FFA ; G 2534
+U 8187 ; WX 894 ; N uni1FFB ; G 2535
+U 8188 ; WX 850 ; N uni1FFC ; G 2536
+U 8189 ; WX 500 ; N uni1FFD ; G 2537
+U 8190 ; WX 500 ; N uni1FFE ; G 2538
+U 8192 ; WX 500 ; N uni2000 ; G 2539
+U 8193 ; WX 1000 ; N uni2001 ; G 2540
+U 8194 ; WX 500 ; N uni2002 ; G 2541
+U 8195 ; WX 1000 ; N uni2003 ; G 2542
+U 8196 ; WX 330 ; N uni2004 ; G 2543
+U 8197 ; WX 250 ; N uni2005 ; G 2544
+U 8198 ; WX 167 ; N uni2006 ; G 2545
+U 8199 ; WX 696 ; N uni2007 ; G 2546
+U 8200 ; WX 380 ; N uni2008 ; G 2547
+U 8201 ; WX 200 ; N uni2009 ; G 2548
+U 8202 ; WX 100 ; N uni200A ; G 2549
+U 8203 ; WX 0 ; N uni200B ; G 2550
+U 8204 ; WX 0 ; N uni200C ; G 2551
+U 8205 ; WX 0 ; N uni200D ; G 2552
+U 8206 ; WX 0 ; N uni200E ; G 2553
+U 8207 ; WX 0 ; N uni200F ; G 2554
+U 8208 ; WX 415 ; N uni2010 ; G 2555
+U 8209 ; WX 415 ; N uni2011 ; G 2556
+U 8210 ; WX 696 ; N figuredash ; G 2557
+U 8211 ; WX 500 ; N endash ; G 2558
+U 8212 ; WX 1000 ; N emdash ; G 2559
+U 8213 ; WX 1000 ; N uni2015 ; G 2560
+U 8214 ; WX 500 ; N uni2016 ; G 2561
+U 8215 ; WX 500 ; N underscoredbl ; G 2562
+U 8216 ; WX 380 ; N quoteleft ; G 2563
+U 8217 ; WX 380 ; N quoteright ; G 2564
+U 8218 ; WX 380 ; N quotesinglbase ; G 2565
+U 8219 ; WX 380 ; N quotereversed ; G 2566
+U 8220 ; WX 644 ; N quotedblleft ; G 2567
+U 8221 ; WX 644 ; N quotedblright ; G 2568
+U 8222 ; WX 644 ; N quotedblbase ; G 2569
+U 8223 ; WX 657 ; N uni201F ; G 2570
+U 8224 ; WX 500 ; N dagger ; G 2571
+U 8225 ; WX 500 ; N daggerdbl ; G 2572
+U 8226 ; WX 639 ; N bullet ; G 2573
+U 8227 ; WX 639 ; N uni2023 ; G 2574
+U 8228 ; WX 380 ; N onedotenleader ; G 2575
+U 8229 ; WX 685 ; N twodotenleader ; G 2576
+U 8230 ; WX 1000 ; N ellipsis ; G 2577
+U 8231 ; WX 348 ; N uni2027 ; G 2578
+U 8232 ; WX 0 ; N uni2028 ; G 2579
+U 8233 ; WX 0 ; N uni2029 ; G 2580
+U 8234 ; WX 0 ; N uni202A ; G 2581
+U 8235 ; WX 0 ; N uni202B ; G 2582
+U 8236 ; WX 0 ; N uni202C ; G 2583
+U 8237 ; WX 0 ; N uni202D ; G 2584
+U 8238 ; WX 0 ; N uni202E ; G 2585
+U 8239 ; WX 200 ; N uni202F ; G 2586
+U 8240 ; WX 1454 ; N perthousand ; G 2587
+U 8241 ; WX 1908 ; N uni2031 ; G 2588
+U 8242 ; WX 264 ; N minute ; G 2589
+U 8243 ; WX 447 ; N second ; G 2590
+U 8244 ; WX 630 ; N uni2034 ; G 2591
+U 8245 ; WX 264 ; N uni2035 ; G 2592
+U 8246 ; WX 447 ; N uni2036 ; G 2593
+U 8247 ; WX 630 ; N uni2037 ; G 2594
+U 8248 ; WX 733 ; N uni2038 ; G 2595
+U 8249 ; WX 412 ; N guilsinglleft ; G 2596
+U 8250 ; WX 412 ; N guilsinglright ; G 2597
+U 8251 ; WX 972 ; N uni203B ; G 2598
+U 8252 ; WX 627 ; N exclamdbl ; G 2599
+U 8253 ; WX 580 ; N uni203D ; G 2600
+U 8254 ; WX 500 ; N uni203E ; G 2601
+U 8255 ; WX 828 ; N uni203F ; G 2602
+U 8256 ; WX 828 ; N uni2040 ; G 2603
+U 8257 ; WX 329 ; N uni2041 ; G 2604
+U 8258 ; WX 1023 ; N uni2042 ; G 2605
+U 8259 ; WX 500 ; N uni2043 ; G 2606
+U 8260 ; WX 167 ; N fraction ; G 2607
+U 8261 ; WX 457 ; N uni2045 ; G 2608
+U 8262 ; WX 457 ; N uni2046 ; G 2609
+U 8263 ; WX 1030 ; N uni2047 ; G 2610
+U 8264 ; WX 829 ; N uni2048 ; G 2611
+U 8265 ; WX 829 ; N uni2049 ; G 2612
+U 8266 ; WX 513 ; N uni204A ; G 2613
+U 8267 ; WX 687 ; N uni204B ; G 2614
+U 8268 ; WX 500 ; N uni204C ; G 2615
+U 8269 ; WX 500 ; N uni204D ; G 2616
+U 8270 ; WX 523 ; N uni204E ; G 2617
+U 8271 ; WX 400 ; N uni204F ; G 2618
+U 8272 ; WX 828 ; N uni2050 ; G 2619
+U 8273 ; WX 523 ; N uni2051 ; G 2620
+U 8274 ; WX 556 ; N uni2052 ; G 2621
+U 8275 ; WX 838 ; N uni2053 ; G 2622
+U 8276 ; WX 828 ; N uni2054 ; G 2623
+U 8277 ; WX 838 ; N uni2055 ; G 2624
+U 8278 ; WX 684 ; N uni2056 ; G 2625
+U 8279 ; WX 813 ; N uni2057 ; G 2626
+U 8280 ; WX 838 ; N uni2058 ; G 2627
+U 8281 ; WX 838 ; N uni2059 ; G 2628
+U 8282 ; WX 380 ; N uni205A ; G 2629
+U 8283 ; WX 872 ; N uni205B ; G 2630
+U 8284 ; WX 838 ; N uni205C ; G 2631
+U 8285 ; WX 380 ; N uni205D ; G 2632
+U 8286 ; WX 380 ; N uni205E ; G 2633
+U 8287 ; WX 222 ; N uni205F ; G 2634
+U 8288 ; WX 0 ; N uni2060 ; G 2635
+U 8289 ; WX 0 ; N uni2061 ; G 2636
+U 8290 ; WX 0 ; N uni2062 ; G 2637
+U 8291 ; WX 0 ; N uni2063 ; G 2638
+U 8292 ; WX 0 ; N uni2064 ; G 2639
+U 8298 ; WX 0 ; N uni206A ; G 2640
+U 8299 ; WX 0 ; N uni206B ; G 2641
+U 8300 ; WX 0 ; N uni206C ; G 2642
+U 8301 ; WX 0 ; N uni206D ; G 2643
+U 8302 ; WX 0 ; N uni206E ; G 2644
+U 8303 ; WX 0 ; N uni206F ; G 2645
+U 8304 ; WX 438 ; N uni2070 ; G 2646
+U 8305 ; WX 219 ; N uni2071 ; G 2647
+U 8308 ; WX 438 ; N uni2074 ; G 2648
+U 8309 ; WX 438 ; N uni2075 ; G 2649
+U 8310 ; WX 438 ; N uni2076 ; G 2650
+U 8311 ; WX 438 ; N uni2077 ; G 2651
+U 8312 ; WX 438 ; N uni2078 ; G 2652
+U 8313 ; WX 438 ; N uni2079 ; G 2653
+U 8314 ; WX 528 ; N uni207A ; G 2654
+U 8315 ; WX 528 ; N uni207B ; G 2655
+U 8316 ; WX 528 ; N uni207C ; G 2656
+U 8317 ; WX 288 ; N uni207D ; G 2657
+U 8318 ; WX 288 ; N uni207E ; G 2658
+U 8319 ; WX 456 ; N uni207F ; G 2659
+U 8320 ; WX 438 ; N uni2080 ; G 2660
+U 8321 ; WX 438 ; N uni2081 ; G 2661
+U 8322 ; WX 438 ; N uni2082 ; G 2662
+U 8323 ; WX 438 ; N uni2083 ; G 2663
+U 8324 ; WX 438 ; N uni2084 ; G 2664
+U 8325 ; WX 438 ; N uni2085 ; G 2665
+U 8326 ; WX 438 ; N uni2086 ; G 2666
+U 8327 ; WX 438 ; N uni2087 ; G 2667
+U 8328 ; WX 438 ; N uni2088 ; G 2668
+U 8329 ; WX 438 ; N uni2089 ; G 2669
+U 8330 ; WX 528 ; N uni208A ; G 2670
+U 8331 ; WX 528 ; N uni208B ; G 2671
+U 8332 ; WX 528 ; N uni208C ; G 2672
+U 8333 ; WX 288 ; N uni208D ; G 2673
+U 8334 ; WX 288 ; N uni208E ; G 2674
+U 8336 ; WX 458 ; N uni2090 ; G 2675
+U 8337 ; WX 479 ; N uni2091 ; G 2676
+U 8338 ; WX 488 ; N uni2092 ; G 2677
+U 8339 ; WX 413 ; N uni2093 ; G 2678
+U 8340 ; WX 479 ; N uni2094 ; G 2679
+U 8341 ; WX 456 ; N uni2095 ; G 2680
+U 8342 ; WX 487 ; N uni2096 ; G 2681
+U 8343 ; WX 219 ; N uni2097 ; G 2682
+U 8344 ; WX 664 ; N uni2098 ; G 2683
+U 8345 ; WX 456 ; N uni2099 ; G 2684
+U 8346 ; WX 479 ; N uni209A ; G 2685
+U 8347 ; WX 381 ; N uni209B ; G 2686
+U 8348 ; WX 388 ; N uni209C ; G 2687
+U 8352 ; WX 929 ; N uni20A0 ; G 2688
+U 8353 ; WX 696 ; N colonmonetary ; G 2689
+U 8354 ; WX 696 ; N uni20A2 ; G 2690
+U 8355 ; WX 696 ; N franc ; G 2691
+U 8356 ; WX 696 ; N lira ; G 2692
+U 8357 ; WX 1042 ; N uni20A5 ; G 2693
+U 8358 ; WX 696 ; N uni20A6 ; G 2694
+U 8359 ; WX 1488 ; N peseta ; G 2695
+U 8360 ; WX 1205 ; N uni20A8 ; G 2696
+U 8361 ; WX 1103 ; N uni20A9 ; G 2697
+U 8362 ; WX 854 ; N uni20AA ; G 2698
+U 8363 ; WX 696 ; N dong ; G 2699
+U 8364 ; WX 696 ; N Euro ; G 2700
+U 8365 ; WX 696 ; N uni20AD ; G 2701
+U 8366 ; WX 696 ; N uni20AE ; G 2702
+U 8367 ; WX 1392 ; N uni20AF ; G 2703
+U 8368 ; WX 696 ; N uni20B0 ; G 2704
+U 8369 ; WX 696 ; N uni20B1 ; G 2705
+U 8370 ; WX 696 ; N uni20B2 ; G 2706
+U 8371 ; WX 696 ; N uni20B3 ; G 2707
+U 8372 ; WX 859 ; N uni20B4 ; G 2708
+U 8373 ; WX 696 ; N uni20B5 ; G 2709
+U 8376 ; WX 696 ; N uni20B8 ; G 2710
+U 8377 ; WX 696 ; N uni20B9 ; G 2711
+U 8378 ; WX 696 ; N uni20BA ; G 2712
+U 8381 ; WX 696 ; N uni20BD ; G 2713
+U 8400 ; WX 0 ; N uni20D0 ; G 2714
+U 8401 ; WX 0 ; N uni20D1 ; G 2715
+U 8406 ; WX 0 ; N uni20D6 ; G 2716
+U 8407 ; WX 0 ; N uni20D7 ; G 2717
+U 8411 ; WX 0 ; N uni20DB ; G 2718
+U 8412 ; WX 0 ; N uni20DC ; G 2719
+U 8417 ; WX 0 ; N uni20E1 ; G 2720
+U 8448 ; WX 1106 ; N uni2100 ; G 2721
+U 8449 ; WX 1106 ; N uni2101 ; G 2722
+U 8450 ; WX 734 ; N uni2102 ; G 2723
+U 8451 ; WX 1211 ; N uni2103 ; G 2724
+U 8452 ; WX 896 ; N uni2104 ; G 2725
+U 8453 ; WX 1114 ; N uni2105 ; G 2726
+U 8454 ; WX 1148 ; N uni2106 ; G 2727
+U 8455 ; WX 696 ; N uni2107 ; G 2728
+U 8456 ; WX 698 ; N uni2108 ; G 2729
+U 8457 ; WX 952 ; N uni2109 ; G 2730
+U 8459 ; WX 1073 ; N uni210B ; G 2731
+U 8460 ; WX 913 ; N uni210C ; G 2732
+U 8461 ; WX 888 ; N uni210D ; G 2733
+U 8462 ; WX 712 ; N uni210E ; G 2734
+U 8463 ; WX 712 ; N uni210F ; G 2735
+U 8464 ; WX 597 ; N uni2110 ; G 2736
+U 8465 ; WX 697 ; N Ifraktur ; G 2737
+U 8466 ; WX 856 ; N uni2112 ; G 2738
+U 8467 ; WX 472 ; N uni2113 ; G 2739
+U 8468 ; WX 974 ; N uni2114 ; G 2740
+U 8469 ; WX 837 ; N uni2115 ; G 2741
+U 8470 ; WX 1203 ; N uni2116 ; G 2742
+U 8471 ; WX 1000 ; N uni2117 ; G 2743
+U 8472 ; WX 697 ; N weierstrass ; G 2744
+U 8473 ; WX 750 ; N uni2119 ; G 2745
+U 8474 ; WX 850 ; N uni211A ; G 2746
+U 8475 ; WX 938 ; N uni211B ; G 2747
+U 8476 ; WX 814 ; N Rfraktur ; G 2748
+U 8477 ; WX 801 ; N uni211D ; G 2749
+U 8478 ; WX 896 ; N prescription ; G 2750
+U 8479 ; WX 710 ; N uni211F ; G 2751
+U 8480 ; WX 1020 ; N uni2120 ; G 2752
+U 8481 ; WX 1239 ; N uni2121 ; G 2753
+U 8482 ; WX 1000 ; N trademark ; G 2754
+U 8483 ; WX 834 ; N uni2123 ; G 2755
+U 8484 ; WX 754 ; N uni2124 ; G 2756
+U 8485 ; WX 622 ; N uni2125 ; G 2757
+U 8486 ; WX 850 ; N uni2126 ; G 2758
+U 8487 ; WX 769 ; N uni2127 ; G 2759
+U 8488 ; WX 763 ; N uni2128 ; G 2760
+U 8489 ; WX 303 ; N uni2129 ; G 2761
+U 8490 ; WX 775 ; N uni212A ; G 2762
+U 8491 ; WX 774 ; N uni212B ; G 2763
+U 8492 ; WX 928 ; N uni212C ; G 2764
+U 8493 ; WX 818 ; N uni212D ; G 2765
+U 8494 ; WX 854 ; N estimated ; G 2766
+U 8495 ; WX 636 ; N uni212F ; G 2767
+U 8496 ; WX 729 ; N uni2130 ; G 2768
+U 8497 ; WX 808 ; N uni2131 ; G 2769
+U 8498 ; WX 683 ; N uni2132 ; G 2770
+U 8499 ; WX 1184 ; N uni2133 ; G 2771
+U 8500 ; WX 465 ; N uni2134 ; G 2772
+U 8501 ; WX 794 ; N aleph ; G 2773
+U 8502 ; WX 731 ; N uni2136 ; G 2774
+U 8503 ; WX 494 ; N uni2137 ; G 2775
+U 8504 ; WX 684 ; N uni2138 ; G 2776
+U 8505 ; WX 380 ; N uni2139 ; G 2777
+U 8506 ; WX 945 ; N uni213A ; G 2778
+U 8507 ; WX 1370 ; N uni213B ; G 2779
+U 8508 ; WX 790 ; N uni213C ; G 2780
+U 8509 ; WX 737 ; N uni213D ; G 2781
+U 8510 ; WX 654 ; N uni213E ; G 2782
+U 8511 ; WX 863 ; N uni213F ; G 2783
+U 8512 ; WX 840 ; N uni2140 ; G 2784
+U 8513 ; WX 786 ; N uni2141 ; G 2785
+U 8514 ; WX 576 ; N uni2142 ; G 2786
+U 8515 ; WX 637 ; N uni2143 ; G 2787
+U 8516 ; WX 760 ; N uni2144 ; G 2788
+U 8517 ; WX 830 ; N uni2145 ; G 2789
+U 8518 ; WX 716 ; N uni2146 ; G 2790
+U 8519 ; WX 678 ; N uni2147 ; G 2791
+U 8520 ; WX 343 ; N uni2148 ; G 2792
+U 8521 ; WX 343 ; N uni2149 ; G 2793
+U 8523 ; WX 872 ; N uni214B ; G 2794
+U 8526 ; WX 547 ; N uni214E ; G 2795
+U 8528 ; WX 1035 ; N uni2150 ; G 2796
+U 8529 ; WX 1035 ; N uni2151 ; G 2797
+U 8530 ; WX 1483 ; N uni2152 ; G 2798
+U 8531 ; WX 1035 ; N onethird ; G 2799
+U 8532 ; WX 1035 ; N twothirds ; G 2800
+U 8533 ; WX 1035 ; N uni2155 ; G 2801
+U 8534 ; WX 1035 ; N uni2156 ; G 2802
+U 8535 ; WX 1035 ; N uni2157 ; G 2803
+U 8536 ; WX 1035 ; N uni2158 ; G 2804
+U 8537 ; WX 1035 ; N uni2159 ; G 2805
+U 8538 ; WX 1035 ; N uni215A ; G 2806
+U 8539 ; WX 1035 ; N oneeighth ; G 2807
+U 8540 ; WX 1035 ; N threeeighths ; G 2808
+U 8541 ; WX 1035 ; N fiveeighths ; G 2809
+U 8542 ; WX 1035 ; N seveneighths ; G 2810
+U 8543 ; WX 615 ; N uni215F ; G 2811
+U 8544 ; WX 372 ; N uni2160 ; G 2812
+U 8545 ; WX 659 ; N uni2161 ; G 2813
+U 8546 ; WX 945 ; N uni2162 ; G 2814
+U 8547 ; WX 1099 ; N uni2163 ; G 2815
+U 8548 ; WX 774 ; N uni2164 ; G 2816
+U 8549 ; WX 1099 ; N uni2165 ; G 2817
+U 8550 ; WX 1386 ; N uni2166 ; G 2818
+U 8551 ; WX 1672 ; N uni2167 ; G 2819
+U 8552 ; WX 1121 ; N uni2168 ; G 2820
+U 8553 ; WX 771 ; N uni2169 ; G 2821
+U 8554 ; WX 1120 ; N uni216A ; G 2822
+U 8555 ; WX 1407 ; N uni216B ; G 2823
+U 8556 ; WX 637 ; N uni216C ; G 2824
+U 8557 ; WX 734 ; N uni216D ; G 2825
+U 8558 ; WX 830 ; N uni216E ; G 2826
+U 8559 ; WX 995 ; N uni216F ; G 2827
+U 8560 ; WX 343 ; N uni2170 ; G 2828
+U 8561 ; WX 607 ; N uni2171 ; G 2829
+U 8562 ; WX 872 ; N uni2172 ; G 2830
+U 8563 ; WX 984 ; N uni2173 ; G 2831
+U 8564 ; WX 652 ; N uni2174 ; G 2832
+U 8565 ; WX 962 ; N uni2175 ; G 2833
+U 8566 ; WX 1227 ; N uni2176 ; G 2834
+U 8567 ; WX 1491 ; N uni2177 ; G 2835
+U 8568 ; WX 969 ; N uni2178 ; G 2836
+U 8569 ; WX 645 ; N uni2179 ; G 2837
+U 8570 ; WX 969 ; N uni217A ; G 2838
+U 8571 ; WX 1233 ; N uni217B ; G 2839
+U 8572 ; WX 343 ; N uni217C ; G 2840
+U 8573 ; WX 593 ; N uni217D ; G 2841
+U 8574 ; WX 716 ; N uni217E ; G 2842
+U 8575 ; WX 1042 ; N uni217F ; G 2843
+U 8576 ; WX 1289 ; N uni2180 ; G 2844
+U 8577 ; WX 830 ; N uni2181 ; G 2845
+U 8578 ; WX 1289 ; N uni2182 ; G 2846
+U 8579 ; WX 734 ; N uni2183 ; G 2847
+U 8580 ; WX 593 ; N uni2184 ; G 2848
+U 8581 ; WX 734 ; N uni2185 ; G 2849
+U 8585 ; WX 1035 ; N uni2189 ; G 2850
+U 8592 ; WX 838 ; N arrowleft ; G 2851
+U 8593 ; WX 838 ; N arrowup ; G 2852
+U 8594 ; WX 838 ; N arrowright ; G 2853
+U 8595 ; WX 838 ; N arrowdown ; G 2854
+U 8596 ; WX 838 ; N arrowboth ; G 2855
+U 8597 ; WX 838 ; N arrowupdn ; G 2856
+U 8598 ; WX 838 ; N uni2196 ; G 2857
+U 8599 ; WX 838 ; N uni2197 ; G 2858
+U 8600 ; WX 838 ; N uni2198 ; G 2859
+U 8601 ; WX 838 ; N uni2199 ; G 2860
+U 8602 ; WX 838 ; N uni219A ; G 2861
+U 8603 ; WX 838 ; N uni219B ; G 2862
+U 8604 ; WX 838 ; N uni219C ; G 2863
+U 8605 ; WX 838 ; N uni219D ; G 2864
+U 8606 ; WX 838 ; N uni219E ; G 2865
+U 8607 ; WX 838 ; N uni219F ; G 2866
+U 8608 ; WX 838 ; N uni21A0 ; G 2867
+U 8609 ; WX 838 ; N uni21A1 ; G 2868
+U 8610 ; WX 838 ; N uni21A2 ; G 2869
+U 8611 ; WX 838 ; N uni21A3 ; G 2870
+U 8612 ; WX 838 ; N uni21A4 ; G 2871
+U 8613 ; WX 838 ; N uni21A5 ; G 2872
+U 8614 ; WX 838 ; N uni21A6 ; G 2873
+U 8615 ; WX 838 ; N uni21A7 ; G 2874
+U 8616 ; WX 838 ; N arrowupdnbse ; G 2875
+U 8617 ; WX 838 ; N uni21A9 ; G 2876
+U 8618 ; WX 838 ; N uni21AA ; G 2877
+U 8619 ; WX 838 ; N uni21AB ; G 2878
+U 8620 ; WX 838 ; N uni21AC ; G 2879
+U 8621 ; WX 838 ; N uni21AD ; G 2880
+U 8622 ; WX 838 ; N uni21AE ; G 2881
+U 8623 ; WX 838 ; N uni21AF ; G 2882
+U 8624 ; WX 838 ; N uni21B0 ; G 2883
+U 8625 ; WX 838 ; N uni21B1 ; G 2884
+U 8626 ; WX 838 ; N uni21B2 ; G 2885
+U 8627 ; WX 838 ; N uni21B3 ; G 2886
+U 8628 ; WX 838 ; N uni21B4 ; G 2887
+U 8629 ; WX 838 ; N carriagereturn ; G 2888
+U 8630 ; WX 838 ; N uni21B6 ; G 2889
+U 8631 ; WX 838 ; N uni21B7 ; G 2890
+U 8632 ; WX 838 ; N uni21B8 ; G 2891
+U 8633 ; WX 838 ; N uni21B9 ; G 2892
+U 8634 ; WX 838 ; N uni21BA ; G 2893
+U 8635 ; WX 838 ; N uni21BB ; G 2894
+U 8636 ; WX 838 ; N uni21BC ; G 2895
+U 8637 ; WX 838 ; N uni21BD ; G 2896
+U 8638 ; WX 838 ; N uni21BE ; G 2897
+U 8639 ; WX 838 ; N uni21BF ; G 2898
+U 8640 ; WX 838 ; N uni21C0 ; G 2899
+U 8641 ; WX 838 ; N uni21C1 ; G 2900
+U 8642 ; WX 838 ; N uni21C2 ; G 2901
+U 8643 ; WX 838 ; N uni21C3 ; G 2902
+U 8644 ; WX 838 ; N uni21C4 ; G 2903
+U 8645 ; WX 838 ; N uni21C5 ; G 2904
+U 8646 ; WX 838 ; N uni21C6 ; G 2905
+U 8647 ; WX 838 ; N uni21C7 ; G 2906
+U 8648 ; WX 838 ; N uni21C8 ; G 2907
+U 8649 ; WX 838 ; N uni21C9 ; G 2908
+U 8650 ; WX 838 ; N uni21CA ; G 2909
+U 8651 ; WX 838 ; N uni21CB ; G 2910
+U 8652 ; WX 838 ; N uni21CC ; G 2911
+U 8653 ; WX 838 ; N uni21CD ; G 2912
+U 8654 ; WX 838 ; N uni21CE ; G 2913
+U 8655 ; WX 838 ; N uni21CF ; G 2914
+U 8656 ; WX 838 ; N arrowdblleft ; G 2915
+U 8657 ; WX 838 ; N arrowdblup ; G 2916
+U 8658 ; WX 838 ; N arrowdblright ; G 2917
+U 8659 ; WX 838 ; N arrowdbldown ; G 2918
+U 8660 ; WX 838 ; N arrowdblboth ; G 2919
+U 8661 ; WX 838 ; N uni21D5 ; G 2920
+U 8662 ; WX 838 ; N uni21D6 ; G 2921
+U 8663 ; WX 838 ; N uni21D7 ; G 2922
+U 8664 ; WX 838 ; N uni21D8 ; G 2923
+U 8665 ; WX 838 ; N uni21D9 ; G 2924
+U 8666 ; WX 838 ; N uni21DA ; G 2925
+U 8667 ; WX 838 ; N uni21DB ; G 2926
+U 8668 ; WX 838 ; N uni21DC ; G 2927
+U 8669 ; WX 838 ; N uni21DD ; G 2928
+U 8670 ; WX 838 ; N uni21DE ; G 2929
+U 8671 ; WX 838 ; N uni21DF ; G 2930
+U 8672 ; WX 838 ; N uni21E0 ; G 2931
+U 8673 ; WX 838 ; N uni21E1 ; G 2932
+U 8674 ; WX 838 ; N uni21E2 ; G 2933
+U 8675 ; WX 838 ; N uni21E3 ; G 2934
+U 8676 ; WX 838 ; N uni21E4 ; G 2935
+U 8677 ; WX 838 ; N uni21E5 ; G 2936
+U 8678 ; WX 838 ; N uni21E6 ; G 2937
+U 8679 ; WX 838 ; N uni21E7 ; G 2938
+U 8680 ; WX 838 ; N uni21E8 ; G 2939
+U 8681 ; WX 838 ; N uni21E9 ; G 2940
+U 8682 ; WX 838 ; N uni21EA ; G 2941
+U 8683 ; WX 838 ; N uni21EB ; G 2942
+U 8684 ; WX 838 ; N uni21EC ; G 2943
+U 8685 ; WX 838 ; N uni21ED ; G 2944
+U 8686 ; WX 838 ; N uni21EE ; G 2945
+U 8687 ; WX 838 ; N uni21EF ; G 2946
+U 8688 ; WX 838 ; N uni21F0 ; G 2947
+U 8689 ; WX 838 ; N uni21F1 ; G 2948
+U 8690 ; WX 838 ; N uni21F2 ; G 2949
+U 8691 ; WX 838 ; N uni21F3 ; G 2950
+U 8692 ; WX 838 ; N uni21F4 ; G 2951
+U 8693 ; WX 838 ; N uni21F5 ; G 2952
+U 8694 ; WX 838 ; N uni21F6 ; G 2953
+U 8695 ; WX 838 ; N uni21F7 ; G 2954
+U 8696 ; WX 838 ; N uni21F8 ; G 2955
+U 8697 ; WX 838 ; N uni21F9 ; G 2956
+U 8698 ; WX 838 ; N uni21FA ; G 2957
+U 8699 ; WX 838 ; N uni21FB ; G 2958
+U 8700 ; WX 838 ; N uni21FC ; G 2959
+U 8701 ; WX 838 ; N uni21FD ; G 2960
+U 8702 ; WX 838 ; N uni21FE ; G 2961
+U 8703 ; WX 838 ; N uni21FF ; G 2962
+U 8704 ; WX 774 ; N universal ; G 2963
+U 8705 ; WX 696 ; N uni2201 ; G 2964
+U 8706 ; WX 544 ; N partialdiff ; G 2965
+U 8707 ; WX 683 ; N existential ; G 2966
+U 8708 ; WX 683 ; N uni2204 ; G 2967
+U 8709 ; WX 856 ; N emptyset ; G 2968
+U 8710 ; WX 697 ; N increment ; G 2969
+U 8711 ; WX 697 ; N gradient ; G 2970
+U 8712 ; WX 896 ; N element ; G 2971
+U 8713 ; WX 896 ; N notelement ; G 2972
+U 8714 ; WX 750 ; N uni220A ; G 2973
+U 8715 ; WX 896 ; N suchthat ; G 2974
+U 8716 ; WX 896 ; N uni220C ; G 2975
+U 8717 ; WX 750 ; N uni220D ; G 2976
+U 8718 ; WX 636 ; N uni220E ; G 2977
+U 8719 ; WX 787 ; N product ; G 2978
+U 8720 ; WX 787 ; N uni2210 ; G 2979
+U 8721 ; WX 718 ; N summation ; G 2980
+U 8722 ; WX 838 ; N minus ; G 2981
+U 8723 ; WX 838 ; N uni2213 ; G 2982
+U 8724 ; WX 696 ; N uni2214 ; G 2983
+U 8725 ; WX 365 ; N uni2215 ; G 2984
+U 8726 ; WX 696 ; N uni2216 ; G 2985
+U 8727 ; WX 838 ; N asteriskmath ; G 2986
+U 8728 ; WX 626 ; N uni2218 ; G 2987
+U 8729 ; WX 380 ; N uni2219 ; G 2988
+U 8730 ; WX 667 ; N radical ; G 2989
+U 8731 ; WX 667 ; N uni221B ; G 2990
+U 8732 ; WX 667 ; N uni221C ; G 2991
+U 8733 ; WX 712 ; N proportional ; G 2992
+U 8734 ; WX 833 ; N infinity ; G 2993
+U 8735 ; WX 838 ; N orthogonal ; G 2994
+U 8736 ; WX 896 ; N angle ; G 2995
+U 8737 ; WX 896 ; N uni2221 ; G 2996
+U 8738 ; WX 838 ; N uni2222 ; G 2997
+U 8739 ; WX 500 ; N uni2223 ; G 2998
+U 8740 ; WX 500 ; N uni2224 ; G 2999
+U 8741 ; WX 500 ; N uni2225 ; G 3000
+U 8742 ; WX 500 ; N uni2226 ; G 3001
+U 8743 ; WX 812 ; N logicaland ; G 3002
+U 8744 ; WX 812 ; N logicalor ; G 3003
+U 8745 ; WX 812 ; N intersection ; G 3004
+U 8746 ; WX 812 ; N union ; G 3005
+U 8747 ; WX 610 ; N integral ; G 3006
+U 8748 ; WX 929 ; N uni222C ; G 3007
+U 8749 ; WX 1295 ; N uni222D ; G 3008
+U 8750 ; WX 563 ; N uni222E ; G 3009
+U 8751 ; WX 977 ; N uni222F ; G 3010
+U 8752 ; WX 1313 ; N uni2230 ; G 3011
+U 8753 ; WX 563 ; N uni2231 ; G 3012
+U 8754 ; WX 563 ; N uni2232 ; G 3013
+U 8755 ; WX 563 ; N uni2233 ; G 3014
+U 8756 ; WX 696 ; N therefore ; G 3015
+U 8757 ; WX 696 ; N uni2235 ; G 3016
+U 8758 ; WX 294 ; N uni2236 ; G 3017
+U 8759 ; WX 696 ; N uni2237 ; G 3018
+U 8760 ; WX 838 ; N uni2238 ; G 3019
+U 8761 ; WX 838 ; N uni2239 ; G 3020
+U 8762 ; WX 838 ; N uni223A ; G 3021
+U 8763 ; WX 838 ; N uni223B ; G 3022
+U 8764 ; WX 838 ; N similar ; G 3023
+U 8765 ; WX 838 ; N uni223D ; G 3024
+U 8766 ; WX 838 ; N uni223E ; G 3025
+U 8767 ; WX 838 ; N uni223F ; G 3026
+U 8768 ; WX 375 ; N uni2240 ; G 3027
+U 8769 ; WX 838 ; N uni2241 ; G 3028
+U 8770 ; WX 838 ; N uni2242 ; G 3029
+U 8771 ; WX 838 ; N uni2243 ; G 3030
+U 8772 ; WX 838 ; N uni2244 ; G 3031
+U 8773 ; WX 838 ; N congruent ; G 3032
+U 8774 ; WX 838 ; N uni2246 ; G 3033
+U 8775 ; WX 838 ; N uni2247 ; G 3034
+U 8776 ; WX 838 ; N approxequal ; G 3035
+U 8777 ; WX 838 ; N uni2249 ; G 3036
+U 8778 ; WX 838 ; N uni224A ; G 3037
+U 8779 ; WX 838 ; N uni224B ; G 3038
+U 8780 ; WX 838 ; N uni224C ; G 3039
+U 8781 ; WX 838 ; N uni224D ; G 3040
+U 8782 ; WX 838 ; N uni224E ; G 3041
+U 8783 ; WX 838 ; N uni224F ; G 3042
+U 8784 ; WX 838 ; N uni2250 ; G 3043
+U 8785 ; WX 838 ; N uni2251 ; G 3044
+U 8786 ; WX 838 ; N uni2252 ; G 3045
+U 8787 ; WX 838 ; N uni2253 ; G 3046
+U 8788 ; WX 1063 ; N uni2254 ; G 3047
+U 8789 ; WX 1063 ; N uni2255 ; G 3048
+U 8790 ; WX 838 ; N uni2256 ; G 3049
+U 8791 ; WX 838 ; N uni2257 ; G 3050
+U 8792 ; WX 838 ; N uni2258 ; G 3051
+U 8793 ; WX 838 ; N uni2259 ; G 3052
+U 8794 ; WX 838 ; N uni225A ; G 3053
+U 8795 ; WX 838 ; N uni225B ; G 3054
+U 8796 ; WX 838 ; N uni225C ; G 3055
+U 8797 ; WX 838 ; N uni225D ; G 3056
+U 8798 ; WX 838 ; N uni225E ; G 3057
+U 8799 ; WX 838 ; N uni225F ; G 3058
+U 8800 ; WX 838 ; N notequal ; G 3059
+U 8801 ; WX 838 ; N equivalence ; G 3060
+U 8802 ; WX 838 ; N uni2262 ; G 3061
+U 8803 ; WX 838 ; N uni2263 ; G 3062
+U 8804 ; WX 838 ; N lessequal ; G 3063
+U 8805 ; WX 838 ; N greaterequal ; G 3064
+U 8806 ; WX 838 ; N uni2266 ; G 3065
+U 8807 ; WX 838 ; N uni2267 ; G 3066
+U 8808 ; WX 841 ; N uni2268 ; G 3067
+U 8809 ; WX 841 ; N uni2269 ; G 3068
+U 8810 ; WX 1047 ; N uni226A ; G 3069
+U 8811 ; WX 1047 ; N uni226B ; G 3070
+U 8812 ; WX 500 ; N uni226C ; G 3071
+U 8813 ; WX 838 ; N uni226D ; G 3072
+U 8814 ; WX 838 ; N uni226E ; G 3073
+U 8815 ; WX 838 ; N uni226F ; G 3074
+U 8816 ; WX 838 ; N uni2270 ; G 3075
+U 8817 ; WX 838 ; N uni2271 ; G 3076
+U 8818 ; WX 838 ; N uni2272 ; G 3077
+U 8819 ; WX 838 ; N uni2273 ; G 3078
+U 8820 ; WX 838 ; N uni2274 ; G 3079
+U 8821 ; WX 838 ; N uni2275 ; G 3080
+U 8822 ; WX 838 ; N uni2276 ; G 3081
+U 8823 ; WX 838 ; N uni2277 ; G 3082
+U 8824 ; WX 838 ; N uni2278 ; G 3083
+U 8825 ; WX 838 ; N uni2279 ; G 3084
+U 8826 ; WX 838 ; N uni227A ; G 3085
+U 8827 ; WX 838 ; N uni227B ; G 3086
+U 8828 ; WX 838 ; N uni227C ; G 3087
+U 8829 ; WX 838 ; N uni227D ; G 3088
+U 8830 ; WX 838 ; N uni227E ; G 3089
+U 8831 ; WX 838 ; N uni227F ; G 3090
+U 8832 ; WX 838 ; N uni2280 ; G 3091
+U 8833 ; WX 838 ; N uni2281 ; G 3092
+U 8834 ; WX 838 ; N propersubset ; G 3093
+U 8835 ; WX 838 ; N propersuperset ; G 3094
+U 8836 ; WX 838 ; N notsubset ; G 3095
+U 8837 ; WX 838 ; N uni2285 ; G 3096
+U 8838 ; WX 838 ; N reflexsubset ; G 3097
+U 8839 ; WX 838 ; N reflexsuperset ; G 3098
+U 8840 ; WX 838 ; N uni2288 ; G 3099
+U 8841 ; WX 838 ; N uni2289 ; G 3100
+U 8842 ; WX 838 ; N uni228A ; G 3101
+U 8843 ; WX 838 ; N uni228B ; G 3102
+U 8844 ; WX 812 ; N uni228C ; G 3103
+U 8845 ; WX 812 ; N uni228D ; G 3104
+U 8846 ; WX 812 ; N uni228E ; G 3105
+U 8847 ; WX 838 ; N uni228F ; G 3106
+U 8848 ; WX 838 ; N uni2290 ; G 3107
+U 8849 ; WX 838 ; N uni2291 ; G 3108
+U 8850 ; WX 838 ; N uni2292 ; G 3109
+U 8851 ; WX 796 ; N uni2293 ; G 3110
+U 8852 ; WX 796 ; N uni2294 ; G 3111
+U 8853 ; WX 838 ; N circleplus ; G 3112
+U 8854 ; WX 838 ; N uni2296 ; G 3113
+U 8855 ; WX 838 ; N circlemultiply ; G 3114
+U 8856 ; WX 838 ; N uni2298 ; G 3115
+U 8857 ; WX 838 ; N uni2299 ; G 3116
+U 8858 ; WX 838 ; N uni229A ; G 3117
+U 8859 ; WX 838 ; N uni229B ; G 3118
+U 8860 ; WX 838 ; N uni229C ; G 3119
+U 8861 ; WX 838 ; N uni229D ; G 3120
+U 8862 ; WX 838 ; N uni229E ; G 3121
+U 8863 ; WX 838 ; N uni229F ; G 3122
+U 8864 ; WX 838 ; N uni22A0 ; G 3123
+U 8865 ; WX 838 ; N uni22A1 ; G 3124
+U 8866 ; WX 914 ; N uni22A2 ; G 3125
+U 8867 ; WX 914 ; N uni22A3 ; G 3126
+U 8868 ; WX 914 ; N uni22A4 ; G 3127
+U 8869 ; WX 914 ; N perpendicular ; G 3128
+U 8870 ; WX 542 ; N uni22A6 ; G 3129
+U 8871 ; WX 542 ; N uni22A7 ; G 3130
+U 8872 ; WX 914 ; N uni22A8 ; G 3131
+U 8873 ; WX 914 ; N uni22A9 ; G 3132
+U 8874 ; WX 914 ; N uni22AA ; G 3133
+U 8875 ; WX 914 ; N uni22AB ; G 3134
+U 8876 ; WX 914 ; N uni22AC ; G 3135
+U 8877 ; WX 914 ; N uni22AD ; G 3136
+U 8878 ; WX 914 ; N uni22AE ; G 3137
+U 8879 ; WX 914 ; N uni22AF ; G 3138
+U 8880 ; WX 838 ; N uni22B0 ; G 3139
+U 8881 ; WX 838 ; N uni22B1 ; G 3140
+U 8882 ; WX 838 ; N uni22B2 ; G 3141
+U 8883 ; WX 838 ; N uni22B3 ; G 3142
+U 8884 ; WX 838 ; N uni22B4 ; G 3143
+U 8885 ; WX 838 ; N uni22B5 ; G 3144
+U 8886 ; WX 1000 ; N uni22B6 ; G 3145
+U 8887 ; WX 1000 ; N uni22B7 ; G 3146
+U 8888 ; WX 838 ; N uni22B8 ; G 3147
+U 8889 ; WX 838 ; N uni22B9 ; G 3148
+U 8890 ; WX 542 ; N uni22BA ; G 3149
+U 8891 ; WX 812 ; N uni22BB ; G 3150
+U 8892 ; WX 812 ; N uni22BC ; G 3151
+U 8893 ; WX 812 ; N uni22BD ; G 3152
+U 8894 ; WX 838 ; N uni22BE ; G 3153
+U 8895 ; WX 838 ; N uni22BF ; G 3154
+U 8896 ; WX 843 ; N uni22C0 ; G 3155
+U 8897 ; WX 843 ; N uni22C1 ; G 3156
+U 8898 ; WX 843 ; N uni22C2 ; G 3157
+U 8899 ; WX 843 ; N uni22C3 ; G 3158
+U 8900 ; WX 626 ; N uni22C4 ; G 3159
+U 8901 ; WX 380 ; N dotmath ; G 3160
+U 8902 ; WX 626 ; N uni22C6 ; G 3161
+U 8903 ; WX 838 ; N uni22C7 ; G 3162
+U 8904 ; WX 1000 ; N uni22C8 ; G 3163
+U 8905 ; WX 1000 ; N uni22C9 ; G 3164
+U 8906 ; WX 1000 ; N uni22CA ; G 3165
+U 8907 ; WX 1000 ; N uni22CB ; G 3166
+U 8908 ; WX 1000 ; N uni22CC ; G 3167
+U 8909 ; WX 838 ; N uni22CD ; G 3168
+U 8910 ; WX 812 ; N uni22CE ; G 3169
+U 8911 ; WX 812 ; N uni22CF ; G 3170
+U 8912 ; WX 838 ; N uni22D0 ; G 3171
+U 8913 ; WX 838 ; N uni22D1 ; G 3172
+U 8914 ; WX 838 ; N uni22D2 ; G 3173
+U 8915 ; WX 838 ; N uni22D3 ; G 3174
+U 8916 ; WX 838 ; N uni22D4 ; G 3175
+U 8917 ; WX 838 ; N uni22D5 ; G 3176
+U 8918 ; WX 838 ; N uni22D6 ; G 3177
+U 8919 ; WX 838 ; N uni22D7 ; G 3178
+U 8920 ; WX 1422 ; N uni22D8 ; G 3179
+U 8921 ; WX 1422 ; N uni22D9 ; G 3180
+U 8922 ; WX 838 ; N uni22DA ; G 3181
+U 8923 ; WX 838 ; N uni22DB ; G 3182
+U 8924 ; WX 838 ; N uni22DC ; G 3183
+U 8925 ; WX 838 ; N uni22DD ; G 3184
+U 8926 ; WX 838 ; N uni22DE ; G 3185
+U 8927 ; WX 838 ; N uni22DF ; G 3186
+U 8928 ; WX 838 ; N uni22E0 ; G 3187
+U 8929 ; WX 838 ; N uni22E1 ; G 3188
+U 8930 ; WX 838 ; N uni22E2 ; G 3189
+U 8931 ; WX 838 ; N uni22E3 ; G 3190
+U 8932 ; WX 838 ; N uni22E4 ; G 3191
+U 8933 ; WX 838 ; N uni22E5 ; G 3192
+U 8934 ; WX 838 ; N uni22E6 ; G 3193
+U 8935 ; WX 838 ; N uni22E7 ; G 3194
+U 8936 ; WX 838 ; N uni22E8 ; G 3195
+U 8937 ; WX 838 ; N uni22E9 ; G 3196
+U 8938 ; WX 838 ; N uni22EA ; G 3197
+U 8939 ; WX 838 ; N uni22EB ; G 3198
+U 8940 ; WX 838 ; N uni22EC ; G 3199
+U 8941 ; WX 838 ; N uni22ED ; G 3200
+U 8942 ; WX 1000 ; N uni22EE ; G 3201
+U 8943 ; WX 1000 ; N uni22EF ; G 3202
+U 8944 ; WX 1000 ; N uni22F0 ; G 3203
+U 8945 ; WX 1000 ; N uni22F1 ; G 3204
+U 8946 ; WX 1158 ; N uni22F2 ; G 3205
+U 8947 ; WX 896 ; N uni22F3 ; G 3206
+U 8948 ; WX 750 ; N uni22F4 ; G 3207
+U 8949 ; WX 896 ; N uni22F5 ; G 3208
+U 8950 ; WX 896 ; N uni22F6 ; G 3209
+U 8951 ; WX 750 ; N uni22F7 ; G 3210
+U 8952 ; WX 896 ; N uni22F8 ; G 3211
+U 8953 ; WX 896 ; N uni22F9 ; G 3212
+U 8954 ; WX 1158 ; N uni22FA ; G 3213
+U 8955 ; WX 896 ; N uni22FB ; G 3214
+U 8956 ; WX 750 ; N uni22FC ; G 3215
+U 8957 ; WX 896 ; N uni22FD ; G 3216
+U 8958 ; WX 750 ; N uni22FE ; G 3217
+U 8959 ; WX 896 ; N uni22FF ; G 3218
+U 8960 ; WX 602 ; N uni2300 ; G 3219
+U 8961 ; WX 602 ; N uni2301 ; G 3220
+U 8962 ; WX 716 ; N house ; G 3221
+U 8963 ; WX 838 ; N uni2303 ; G 3222
+U 8964 ; WX 838 ; N uni2304 ; G 3223
+U 8965 ; WX 838 ; N uni2305 ; G 3224
+U 8966 ; WX 838 ; N uni2306 ; G 3225
+U 8967 ; WX 488 ; N uni2307 ; G 3226
+U 8968 ; WX 457 ; N uni2308 ; G 3227
+U 8969 ; WX 457 ; N uni2309 ; G 3228
+U 8970 ; WX 457 ; N uni230A ; G 3229
+U 8971 ; WX 457 ; N uni230B ; G 3230
+U 8972 ; WX 809 ; N uni230C ; G 3231
+U 8973 ; WX 809 ; N uni230D ; G 3232
+U 8974 ; WX 809 ; N uni230E ; G 3233
+U 8975 ; WX 809 ; N uni230F ; G 3234
+U 8976 ; WX 838 ; N revlogicalnot ; G 3235
+U 8977 ; WX 539 ; N uni2311 ; G 3236
+U 8984 ; WX 928 ; N uni2318 ; G 3237
+U 8985 ; WX 838 ; N uni2319 ; G 3238
+U 8988 ; WX 469 ; N uni231C ; G 3239
+U 8989 ; WX 469 ; N uni231D ; G 3240
+U 8990 ; WX 469 ; N uni231E ; G 3241
+U 8991 ; WX 469 ; N uni231F ; G 3242
+U 8992 ; WX 610 ; N integraltp ; G 3243
+U 8993 ; WX 610 ; N integralbt ; G 3244
+U 8996 ; WX 1152 ; N uni2324 ; G 3245
+U 8997 ; WX 1152 ; N uni2325 ; G 3246
+U 8998 ; WX 1414 ; N uni2326 ; G 3247
+U 8999 ; WX 1152 ; N uni2327 ; G 3248
+U 9000 ; WX 1443 ; N uni2328 ; G 3249
+U 9003 ; WX 1414 ; N uni232B ; G 3250
+U 9004 ; WX 873 ; N uni232C ; G 3251
+U 9075 ; WX 390 ; N uni2373 ; G 3252
+U 9076 ; WX 716 ; N uni2374 ; G 3253
+U 9077 ; WX 869 ; N uni2375 ; G 3254
+U 9082 ; WX 687 ; N uni237A ; G 3255
+U 9085 ; WX 863 ; N uni237D ; G 3256
+U 9095 ; WX 1152 ; N uni2387 ; G 3257
+U 9108 ; WX 873 ; N uni2394 ; G 3258
+U 9115 ; WX 500 ; N uni239B ; G 3259
+U 9116 ; WX 500 ; N uni239C ; G 3260
+U 9117 ; WX 500 ; N uni239D ; G 3261
+U 9118 ; WX 500 ; N uni239E ; G 3262
+U 9119 ; WX 500 ; N uni239F ; G 3263
+U 9120 ; WX 500 ; N uni23A0 ; G 3264
+U 9121 ; WX 500 ; N uni23A1 ; G 3265
+U 9122 ; WX 500 ; N uni23A2 ; G 3266
+U 9123 ; WX 500 ; N uni23A3 ; G 3267
+U 9124 ; WX 500 ; N uni23A4 ; G 3268
+U 9125 ; WX 500 ; N uni23A5 ; G 3269
+U 9126 ; WX 500 ; N uni23A6 ; G 3270
+U 9127 ; WX 750 ; N uni23A7 ; G 3271
+U 9128 ; WX 750 ; N uni23A8 ; G 3272
+U 9129 ; WX 750 ; N uni23A9 ; G 3273
+U 9130 ; WX 750 ; N uni23AA ; G 3274
+U 9131 ; WX 750 ; N uni23AB ; G 3275
+U 9132 ; WX 750 ; N uni23AC ; G 3276
+U 9133 ; WX 750 ; N uni23AD ; G 3277
+U 9134 ; WX 610 ; N uni23AE ; G 3278
+U 9166 ; WX 838 ; N uni23CE ; G 3279
+U 9167 ; WX 945 ; N uni23CF ; G 3280
+U 9187 ; WX 873 ; N uni23E3 ; G 3281
+U 9189 ; WX 769 ; N uni23E5 ; G 3282
+U 9192 ; WX 696 ; N uni23E8 ; G 3283
+U 9250 ; WX 716 ; N uni2422 ; G 3284
+U 9251 ; WX 716 ; N uni2423 ; G 3285
+U 9312 ; WX 847 ; N uni2460 ; G 3286
+U 9313 ; WX 847 ; N uni2461 ; G 3287
+U 9314 ; WX 847 ; N uni2462 ; G 3288
+U 9315 ; WX 847 ; N uni2463 ; G 3289
+U 9316 ; WX 847 ; N uni2464 ; G 3290
+U 9317 ; WX 847 ; N uni2465 ; G 3291
+U 9318 ; WX 847 ; N uni2466 ; G 3292
+U 9319 ; WX 847 ; N uni2467 ; G 3293
+U 9320 ; WX 847 ; N uni2468 ; G 3294
+U 9321 ; WX 847 ; N uni2469 ; G 3295
+U 9472 ; WX 602 ; N SF100000 ; G 3296
+U 9473 ; WX 602 ; N uni2501 ; G 3297
+U 9474 ; WX 602 ; N SF110000 ; G 3298
+U 9475 ; WX 602 ; N uni2503 ; G 3299
+U 9476 ; WX 602 ; N uni2504 ; G 3300
+U 9477 ; WX 602 ; N uni2505 ; G 3301
+U 9478 ; WX 602 ; N uni2506 ; G 3302
+U 9479 ; WX 602 ; N uni2507 ; G 3303
+U 9480 ; WX 602 ; N uni2508 ; G 3304
+U 9481 ; WX 602 ; N uni2509 ; G 3305
+U 9482 ; WX 602 ; N uni250A ; G 3306
+U 9483 ; WX 602 ; N uni250B ; G 3307
+U 9484 ; WX 602 ; N SF010000 ; G 3308
+U 9485 ; WX 602 ; N uni250D ; G 3309
+U 9486 ; WX 602 ; N uni250E ; G 3310
+U 9487 ; WX 602 ; N uni250F ; G 3311
+U 9488 ; WX 602 ; N SF030000 ; G 3312
+U 9489 ; WX 602 ; N uni2511 ; G 3313
+U 9490 ; WX 602 ; N uni2512 ; G 3314
+U 9491 ; WX 602 ; N uni2513 ; G 3315
+U 9492 ; WX 602 ; N SF020000 ; G 3316
+U 9493 ; WX 602 ; N uni2515 ; G 3317
+U 9494 ; WX 602 ; N uni2516 ; G 3318
+U 9495 ; WX 602 ; N uni2517 ; G 3319
+U 9496 ; WX 602 ; N SF040000 ; G 3320
+U 9497 ; WX 602 ; N uni2519 ; G 3321
+U 9498 ; WX 602 ; N uni251A ; G 3322
+U 9499 ; WX 602 ; N uni251B ; G 3323
+U 9500 ; WX 602 ; N SF080000 ; G 3324
+U 9501 ; WX 602 ; N uni251D ; G 3325
+U 9502 ; WX 602 ; N uni251E ; G 3326
+U 9503 ; WX 602 ; N uni251F ; G 3327
+U 9504 ; WX 602 ; N uni2520 ; G 3328
+U 9505 ; WX 602 ; N uni2521 ; G 3329
+U 9506 ; WX 602 ; N uni2522 ; G 3330
+U 9507 ; WX 602 ; N uni2523 ; G 3331
+U 9508 ; WX 602 ; N SF090000 ; G 3332
+U 9509 ; WX 602 ; N uni2525 ; G 3333
+U 9510 ; WX 602 ; N uni2526 ; G 3334
+U 9511 ; WX 602 ; N uni2527 ; G 3335
+U 9512 ; WX 602 ; N uni2528 ; G 3336
+U 9513 ; WX 602 ; N uni2529 ; G 3337
+U 9514 ; WX 602 ; N uni252A ; G 3338
+U 9515 ; WX 602 ; N uni252B ; G 3339
+U 9516 ; WX 602 ; N SF060000 ; G 3340
+U 9517 ; WX 602 ; N uni252D ; G 3341
+U 9518 ; WX 602 ; N uni252E ; G 3342
+U 9519 ; WX 602 ; N uni252F ; G 3343
+U 9520 ; WX 602 ; N uni2530 ; G 3344
+U 9521 ; WX 602 ; N uni2531 ; G 3345
+U 9522 ; WX 602 ; N uni2532 ; G 3346
+U 9523 ; WX 602 ; N uni2533 ; G 3347
+U 9524 ; WX 602 ; N SF070000 ; G 3348
+U 9525 ; WX 602 ; N uni2535 ; G 3349
+U 9526 ; WX 602 ; N uni2536 ; G 3350
+U 9527 ; WX 602 ; N uni2537 ; G 3351
+U 9528 ; WX 602 ; N uni2538 ; G 3352
+U 9529 ; WX 602 ; N uni2539 ; G 3353
+U 9530 ; WX 602 ; N uni253A ; G 3354
+U 9531 ; WX 602 ; N uni253B ; G 3355
+U 9532 ; WX 602 ; N SF050000 ; G 3356
+U 9533 ; WX 602 ; N uni253D ; G 3357
+U 9534 ; WX 602 ; N uni253E ; G 3358
+U 9535 ; WX 602 ; N uni253F ; G 3359
+U 9536 ; WX 602 ; N uni2540 ; G 3360
+U 9537 ; WX 602 ; N uni2541 ; G 3361
+U 9538 ; WX 602 ; N uni2542 ; G 3362
+U 9539 ; WX 602 ; N uni2543 ; G 3363
+U 9540 ; WX 602 ; N uni2544 ; G 3364
+U 9541 ; WX 602 ; N uni2545 ; G 3365
+U 9542 ; WX 602 ; N uni2546 ; G 3366
+U 9543 ; WX 602 ; N uni2547 ; G 3367
+U 9544 ; WX 602 ; N uni2548 ; G 3368
+U 9545 ; WX 602 ; N uni2549 ; G 3369
+U 9546 ; WX 602 ; N uni254A ; G 3370
+U 9547 ; WX 602 ; N uni254B ; G 3371
+U 9548 ; WX 602 ; N uni254C ; G 3372
+U 9549 ; WX 602 ; N uni254D ; G 3373
+U 9550 ; WX 602 ; N uni254E ; G 3374
+U 9551 ; WX 602 ; N uni254F ; G 3375
+U 9552 ; WX 602 ; N SF430000 ; G 3376
+U 9553 ; WX 602 ; N SF240000 ; G 3377
+U 9554 ; WX 602 ; N SF510000 ; G 3378
+U 9555 ; WX 602 ; N SF520000 ; G 3379
+U 9556 ; WX 602 ; N SF390000 ; G 3380
+U 9557 ; WX 602 ; N SF220000 ; G 3381
+U 9558 ; WX 602 ; N SF210000 ; G 3382
+U 9559 ; WX 602 ; N SF250000 ; G 3383
+U 9560 ; WX 602 ; N SF500000 ; G 3384
+U 9561 ; WX 602 ; N SF490000 ; G 3385
+U 9562 ; WX 602 ; N SF380000 ; G 3386
+U 9563 ; WX 602 ; N SF280000 ; G 3387
+U 9564 ; WX 602 ; N SF270000 ; G 3388
+U 9565 ; WX 602 ; N SF260000 ; G 3389
+U 9566 ; WX 602 ; N SF360000 ; G 3390
+U 9567 ; WX 602 ; N SF370000 ; G 3391
+U 9568 ; WX 602 ; N SF420000 ; G 3392
+U 9569 ; WX 602 ; N SF190000 ; G 3393
+U 9570 ; WX 602 ; N SF200000 ; G 3394
+U 9571 ; WX 602 ; N SF230000 ; G 3395
+U 9572 ; WX 602 ; N SF470000 ; G 3396
+U 9573 ; WX 602 ; N SF480000 ; G 3397
+U 9574 ; WX 602 ; N SF410000 ; G 3398
+U 9575 ; WX 602 ; N SF450000 ; G 3399
+U 9576 ; WX 602 ; N SF460000 ; G 3400
+U 9577 ; WX 602 ; N SF400000 ; G 3401
+U 9578 ; WX 602 ; N SF540000 ; G 3402
+U 9579 ; WX 602 ; N SF530000 ; G 3403
+U 9580 ; WX 602 ; N SF440000 ; G 3404
+U 9581 ; WX 602 ; N uni256D ; G 3405
+U 9582 ; WX 602 ; N uni256E ; G 3406
+U 9583 ; WX 602 ; N uni256F ; G 3407
+U 9584 ; WX 602 ; N uni2570 ; G 3408
+U 9585 ; WX 602 ; N uni2571 ; G 3409
+U 9586 ; WX 602 ; N uni2572 ; G 3410
+U 9587 ; WX 602 ; N uni2573 ; G 3411
+U 9588 ; WX 602 ; N uni2574 ; G 3412
+U 9589 ; WX 602 ; N uni2575 ; G 3413
+U 9590 ; WX 602 ; N uni2576 ; G 3414
+U 9591 ; WX 602 ; N uni2577 ; G 3415
+U 9592 ; WX 602 ; N uni2578 ; G 3416
+U 9593 ; WX 602 ; N uni2579 ; G 3417
+U 9594 ; WX 602 ; N uni257A ; G 3418
+U 9595 ; WX 602 ; N uni257B ; G 3419
+U 9596 ; WX 602 ; N uni257C ; G 3420
+U 9597 ; WX 602 ; N uni257D ; G 3421
+U 9598 ; WX 602 ; N uni257E ; G 3422
+U 9599 ; WX 602 ; N uni257F ; G 3423
+U 9600 ; WX 769 ; N upblock ; G 3424
+U 9601 ; WX 769 ; N uni2581 ; G 3425
+U 9602 ; WX 769 ; N uni2582 ; G 3426
+U 9603 ; WX 769 ; N uni2583 ; G 3427
+U 9604 ; WX 769 ; N dnblock ; G 3428
+U 9605 ; WX 769 ; N uni2585 ; G 3429
+U 9606 ; WX 769 ; N uni2586 ; G 3430
+U 9607 ; WX 769 ; N uni2587 ; G 3431
+U 9608 ; WX 769 ; N block ; G 3432
+U 9609 ; WX 769 ; N uni2589 ; G 3433
+U 9610 ; WX 769 ; N uni258A ; G 3434
+U 9611 ; WX 769 ; N uni258B ; G 3435
+U 9612 ; WX 769 ; N lfblock ; G 3436
+U 9613 ; WX 769 ; N uni258D ; G 3437
+U 9614 ; WX 769 ; N uni258E ; G 3438
+U 9615 ; WX 769 ; N uni258F ; G 3439
+U 9616 ; WX 769 ; N rtblock ; G 3440
+U 9617 ; WX 769 ; N ltshade ; G 3441
+U 9618 ; WX 769 ; N shade ; G 3442
+U 9619 ; WX 769 ; N dkshade ; G 3443
+U 9620 ; WX 769 ; N uni2594 ; G 3444
+U 9621 ; WX 769 ; N uni2595 ; G 3445
+U 9622 ; WX 769 ; N uni2596 ; G 3446
+U 9623 ; WX 769 ; N uni2597 ; G 3447
+U 9624 ; WX 769 ; N uni2598 ; G 3448
+U 9625 ; WX 769 ; N uni2599 ; G 3449
+U 9626 ; WX 769 ; N uni259A ; G 3450
+U 9627 ; WX 769 ; N uni259B ; G 3451
+U 9628 ; WX 769 ; N uni259C ; G 3452
+U 9629 ; WX 769 ; N uni259D ; G 3453
+U 9630 ; WX 769 ; N uni259E ; G 3454
+U 9631 ; WX 769 ; N uni259F ; G 3455
+U 9632 ; WX 945 ; N filledbox ; G 3456
+U 9633 ; WX 945 ; N H22073 ; G 3457
+U 9634 ; WX 945 ; N uni25A2 ; G 3458
+U 9635 ; WX 945 ; N uni25A3 ; G 3459
+U 9636 ; WX 945 ; N uni25A4 ; G 3460
+U 9637 ; WX 945 ; N uni25A5 ; G 3461
+U 9638 ; WX 945 ; N uni25A6 ; G 3462
+U 9639 ; WX 945 ; N uni25A7 ; G 3463
+U 9640 ; WX 945 ; N uni25A8 ; G 3464
+U 9641 ; WX 945 ; N uni25A9 ; G 3465
+U 9642 ; WX 678 ; N H18543 ; G 3466
+U 9643 ; WX 678 ; N H18551 ; G 3467
+U 9644 ; WX 945 ; N filledrect ; G 3468
+U 9645 ; WX 945 ; N uni25AD ; G 3469
+U 9646 ; WX 550 ; N uni25AE ; G 3470
+U 9647 ; WX 550 ; N uni25AF ; G 3471
+U 9648 ; WX 769 ; N uni25B0 ; G 3472
+U 9649 ; WX 769 ; N uni25B1 ; G 3473
+U 9650 ; WX 769 ; N triagup ; G 3474
+U 9651 ; WX 769 ; N uni25B3 ; G 3475
+U 9652 ; WX 502 ; N uni25B4 ; G 3476
+U 9653 ; WX 502 ; N uni25B5 ; G 3477
+U 9654 ; WX 769 ; N uni25B6 ; G 3478
+U 9655 ; WX 769 ; N uni25B7 ; G 3479
+U 9656 ; WX 502 ; N uni25B8 ; G 3480
+U 9657 ; WX 502 ; N uni25B9 ; G 3481
+U 9658 ; WX 769 ; N triagrt ; G 3482
+U 9659 ; WX 769 ; N uni25BB ; G 3483
+U 9660 ; WX 769 ; N triagdn ; G 3484
+U 9661 ; WX 769 ; N uni25BD ; G 3485
+U 9662 ; WX 502 ; N uni25BE ; G 3486
+U 9663 ; WX 502 ; N uni25BF ; G 3487
+U 9664 ; WX 769 ; N uni25C0 ; G 3488
+U 9665 ; WX 769 ; N uni25C1 ; G 3489
+U 9666 ; WX 502 ; N uni25C2 ; G 3490
+U 9667 ; WX 502 ; N uni25C3 ; G 3491
+U 9668 ; WX 769 ; N triaglf ; G 3492
+U 9669 ; WX 769 ; N uni25C5 ; G 3493
+U 9670 ; WX 769 ; N uni25C6 ; G 3494
+U 9671 ; WX 769 ; N uni25C7 ; G 3495
+U 9672 ; WX 769 ; N uni25C8 ; G 3496
+U 9673 ; WX 873 ; N uni25C9 ; G 3497
+U 9674 ; WX 494 ; N lozenge ; G 3498
+U 9675 ; WX 873 ; N circle ; G 3499
+U 9676 ; WX 873 ; N uni25CC ; G 3500
+U 9677 ; WX 873 ; N uni25CD ; G 3501
+U 9678 ; WX 873 ; N uni25CE ; G 3502
+U 9679 ; WX 873 ; N H18533 ; G 3503
+U 9680 ; WX 873 ; N uni25D0 ; G 3504
+U 9681 ; WX 873 ; N uni25D1 ; G 3505
+U 9682 ; WX 873 ; N uni25D2 ; G 3506
+U 9683 ; WX 873 ; N uni25D3 ; G 3507
+U 9684 ; WX 873 ; N uni25D4 ; G 3508
+U 9685 ; WX 873 ; N uni25D5 ; G 3509
+U 9686 ; WX 527 ; N uni25D6 ; G 3510
+U 9687 ; WX 527 ; N uni25D7 ; G 3511
+U 9688 ; WX 840 ; N invbullet ; G 3512
+U 9689 ; WX 970 ; N invcircle ; G 3513
+U 9690 ; WX 970 ; N uni25DA ; G 3514
+U 9691 ; WX 970 ; N uni25DB ; G 3515
+U 9692 ; WX 387 ; N uni25DC ; G 3516
+U 9693 ; WX 387 ; N uni25DD ; G 3517
+U 9694 ; WX 387 ; N uni25DE ; G 3518
+U 9695 ; WX 387 ; N uni25DF ; G 3519
+U 9696 ; WX 769 ; N uni25E0 ; G 3520
+U 9697 ; WX 769 ; N uni25E1 ; G 3521
+U 9698 ; WX 769 ; N uni25E2 ; G 3522
+U 9699 ; WX 769 ; N uni25E3 ; G 3523
+U 9700 ; WX 769 ; N uni25E4 ; G 3524
+U 9701 ; WX 769 ; N uni25E5 ; G 3525
+U 9702 ; WX 639 ; N openbullet ; G 3526
+U 9703 ; WX 945 ; N uni25E7 ; G 3527
+U 9704 ; WX 945 ; N uni25E8 ; G 3528
+U 9705 ; WX 945 ; N uni25E9 ; G 3529
+U 9706 ; WX 945 ; N uni25EA ; G 3530
+U 9707 ; WX 945 ; N uni25EB ; G 3531
+U 9708 ; WX 769 ; N uni25EC ; G 3532
+U 9709 ; WX 769 ; N uni25ED ; G 3533
+U 9710 ; WX 769 ; N uni25EE ; G 3534
+U 9711 ; WX 1119 ; N uni25EF ; G 3535
+U 9712 ; WX 945 ; N uni25F0 ; G 3536
+U 9713 ; WX 945 ; N uni25F1 ; G 3537
+U 9714 ; WX 945 ; N uni25F2 ; G 3538
+U 9715 ; WX 945 ; N uni25F3 ; G 3539
+U 9716 ; WX 873 ; N uni25F4 ; G 3540
+U 9717 ; WX 873 ; N uni25F5 ; G 3541
+U 9718 ; WX 873 ; N uni25F6 ; G 3542
+U 9719 ; WX 873 ; N uni25F7 ; G 3543
+U 9720 ; WX 769 ; N uni25F8 ; G 3544
+U 9721 ; WX 769 ; N uni25F9 ; G 3545
+U 9722 ; WX 769 ; N uni25FA ; G 3546
+U 9723 ; WX 830 ; N uni25FB ; G 3547
+U 9724 ; WX 830 ; N uni25FC ; G 3548
+U 9725 ; WX 732 ; N uni25FD ; G 3549
+U 9726 ; WX 732 ; N uni25FE ; G 3550
+U 9727 ; WX 769 ; N uni25FF ; G 3551
+U 9728 ; WX 896 ; N uni2600 ; G 3552
+U 9729 ; WX 1000 ; N uni2601 ; G 3553
+U 9730 ; WX 896 ; N uni2602 ; G 3554
+U 9731 ; WX 896 ; N uni2603 ; G 3555
+U 9732 ; WX 896 ; N uni2604 ; G 3556
+U 9733 ; WX 896 ; N uni2605 ; G 3557
+U 9734 ; WX 896 ; N uni2606 ; G 3558
+U 9735 ; WX 573 ; N uni2607 ; G 3559
+U 9736 ; WX 896 ; N uni2608 ; G 3560
+U 9737 ; WX 896 ; N uni2609 ; G 3561
+U 9738 ; WX 888 ; N uni260A ; G 3562
+U 9739 ; WX 888 ; N uni260B ; G 3563
+U 9740 ; WX 671 ; N uni260C ; G 3564
+U 9741 ; WX 1013 ; N uni260D ; G 3565
+U 9742 ; WX 1246 ; N uni260E ; G 3566
+U 9743 ; WX 1250 ; N uni260F ; G 3567
+U 9744 ; WX 896 ; N uni2610 ; G 3568
+U 9745 ; WX 896 ; N uni2611 ; G 3569
+U 9746 ; WX 896 ; N uni2612 ; G 3570
+U 9747 ; WX 532 ; N uni2613 ; G 3571
+U 9748 ; WX 896 ; N uni2614 ; G 3572
+U 9749 ; WX 896 ; N uni2615 ; G 3573
+U 9750 ; WX 896 ; N uni2616 ; G 3574
+U 9751 ; WX 896 ; N uni2617 ; G 3575
+U 9752 ; WX 896 ; N uni2618 ; G 3576
+U 9753 ; WX 896 ; N uni2619 ; G 3577
+U 9754 ; WX 896 ; N uni261A ; G 3578
+U 9755 ; WX 896 ; N uni261B ; G 3579
+U 9756 ; WX 896 ; N uni261C ; G 3580
+U 9757 ; WX 609 ; N uni261D ; G 3581
+U 9758 ; WX 896 ; N uni261E ; G 3582
+U 9759 ; WX 609 ; N uni261F ; G 3583
+U 9760 ; WX 896 ; N uni2620 ; G 3584
+U 9761 ; WX 896 ; N uni2621 ; G 3585
+U 9762 ; WX 896 ; N uni2622 ; G 3586
+U 9763 ; WX 896 ; N uni2623 ; G 3587
+U 9764 ; WX 669 ; N uni2624 ; G 3588
+U 9765 ; WX 746 ; N uni2625 ; G 3589
+U 9766 ; WX 649 ; N uni2626 ; G 3590
+U 9767 ; WX 784 ; N uni2627 ; G 3591
+U 9768 ; WX 545 ; N uni2628 ; G 3592
+U 9769 ; WX 896 ; N uni2629 ; G 3593
+U 9770 ; WX 896 ; N uni262A ; G 3594
+U 9771 ; WX 896 ; N uni262B ; G 3595
+U 9772 ; WX 710 ; N uni262C ; G 3596
+U 9773 ; WX 896 ; N uni262D ; G 3597
+U 9774 ; WX 896 ; N uni262E ; G 3598
+U 9775 ; WX 896 ; N uni262F ; G 3599
+U 9776 ; WX 896 ; N uni2630 ; G 3600
+U 9777 ; WX 896 ; N uni2631 ; G 3601
+U 9778 ; WX 896 ; N uni2632 ; G 3602
+U 9779 ; WX 896 ; N uni2633 ; G 3603
+U 9780 ; WX 896 ; N uni2634 ; G 3604
+U 9781 ; WX 896 ; N uni2635 ; G 3605
+U 9782 ; WX 896 ; N uni2636 ; G 3606
+U 9783 ; WX 896 ; N uni2637 ; G 3607
+U 9784 ; WX 896 ; N uni2638 ; G 3608
+U 9785 ; WX 1042 ; N uni2639 ; G 3609
+U 9786 ; WX 1042 ; N smileface ; G 3610
+U 9787 ; WX 1042 ; N invsmileface ; G 3611
+U 9788 ; WX 896 ; N sun ; G 3612
+U 9789 ; WX 896 ; N uni263D ; G 3613
+U 9790 ; WX 896 ; N uni263E ; G 3614
+U 9791 ; WX 614 ; N uni263F ; G 3615
+U 9792 ; WX 732 ; N female ; G 3616
+U 9793 ; WX 732 ; N uni2641 ; G 3617
+U 9794 ; WX 896 ; N male ; G 3618
+U 9795 ; WX 896 ; N uni2643 ; G 3619
+U 9796 ; WX 896 ; N uni2644 ; G 3620
+U 9797 ; WX 896 ; N uni2645 ; G 3621
+U 9798 ; WX 896 ; N uni2646 ; G 3622
+U 9799 ; WX 896 ; N uni2647 ; G 3623
+U 9800 ; WX 896 ; N uni2648 ; G 3624
+U 9801 ; WX 896 ; N uni2649 ; G 3625
+U 9802 ; WX 896 ; N uni264A ; G 3626
+U 9803 ; WX 896 ; N uni264B ; G 3627
+U 9804 ; WX 896 ; N uni264C ; G 3628
+U 9805 ; WX 896 ; N uni264D ; G 3629
+U 9806 ; WX 896 ; N uni264E ; G 3630
+U 9807 ; WX 896 ; N uni264F ; G 3631
+U 9808 ; WX 896 ; N uni2650 ; G 3632
+U 9809 ; WX 896 ; N uni2651 ; G 3633
+U 9810 ; WX 896 ; N uni2652 ; G 3634
+U 9811 ; WX 896 ; N uni2653 ; G 3635
+U 9812 ; WX 896 ; N uni2654 ; G 3636
+U 9813 ; WX 896 ; N uni2655 ; G 3637
+U 9814 ; WX 896 ; N uni2656 ; G 3638
+U 9815 ; WX 896 ; N uni2657 ; G 3639
+U 9816 ; WX 896 ; N uni2658 ; G 3640
+U 9817 ; WX 896 ; N uni2659 ; G 3641
+U 9818 ; WX 896 ; N uni265A ; G 3642
+U 9819 ; WX 896 ; N uni265B ; G 3643
+U 9820 ; WX 896 ; N uni265C ; G 3644
+U 9821 ; WX 896 ; N uni265D ; G 3645
+U 9822 ; WX 896 ; N uni265E ; G 3646
+U 9823 ; WX 896 ; N uni265F ; G 3647
+U 9824 ; WX 896 ; N spade ; G 3648
+U 9825 ; WX 896 ; N uni2661 ; G 3649
+U 9826 ; WX 896 ; N uni2662 ; G 3650
+U 9827 ; WX 896 ; N club ; G 3651
+U 9828 ; WX 896 ; N uni2664 ; G 3652
+U 9829 ; WX 896 ; N heart ; G 3653
+U 9830 ; WX 896 ; N diamond ; G 3654
+U 9831 ; WX 896 ; N uni2667 ; G 3655
+U 9832 ; WX 896 ; N uni2668 ; G 3656
+U 9833 ; WX 472 ; N uni2669 ; G 3657
+U 9834 ; WX 638 ; N musicalnote ; G 3658
+U 9835 ; WX 896 ; N musicalnotedbl ; G 3659
+U 9836 ; WX 896 ; N uni266C ; G 3660
+U 9837 ; WX 472 ; N uni266D ; G 3661
+U 9838 ; WX 357 ; N uni266E ; G 3662
+U 9839 ; WX 484 ; N uni266F ; G 3663
+U 9840 ; WX 748 ; N uni2670 ; G 3664
+U 9841 ; WX 766 ; N uni2671 ; G 3665
+U 9842 ; WX 896 ; N uni2672 ; G 3666
+U 9843 ; WX 896 ; N uni2673 ; G 3667
+U 9844 ; WX 896 ; N uni2674 ; G 3668
+U 9845 ; WX 896 ; N uni2675 ; G 3669
+U 9846 ; WX 896 ; N uni2676 ; G 3670
+U 9847 ; WX 896 ; N uni2677 ; G 3671
+U 9848 ; WX 896 ; N uni2678 ; G 3672
+U 9849 ; WX 896 ; N uni2679 ; G 3673
+U 9850 ; WX 896 ; N uni267A ; G 3674
+U 9851 ; WX 896 ; N uni267B ; G 3675
+U 9852 ; WX 896 ; N uni267C ; G 3676
+U 9853 ; WX 896 ; N uni267D ; G 3677
+U 9854 ; WX 896 ; N uni267E ; G 3678
+U 9855 ; WX 896 ; N uni267F ; G 3679
+U 9856 ; WX 869 ; N uni2680 ; G 3680
+U 9857 ; WX 869 ; N uni2681 ; G 3681
+U 9858 ; WX 869 ; N uni2682 ; G 3682
+U 9859 ; WX 869 ; N uni2683 ; G 3683
+U 9860 ; WX 869 ; N uni2684 ; G 3684
+U 9861 ; WX 869 ; N uni2685 ; G 3685
+U 9862 ; WX 896 ; N uni2686 ; G 3686
+U 9863 ; WX 896 ; N uni2687 ; G 3687
+U 9864 ; WX 896 ; N uni2688 ; G 3688
+U 9865 ; WX 896 ; N uni2689 ; G 3689
+U 9866 ; WX 896 ; N uni268A ; G 3690
+U 9867 ; WX 896 ; N uni268B ; G 3691
+U 9868 ; WX 896 ; N uni268C ; G 3692
+U 9869 ; WX 896 ; N uni268D ; G 3693
+U 9870 ; WX 896 ; N uni268E ; G 3694
+U 9871 ; WX 896 ; N uni268F ; G 3695
+U 9872 ; WX 896 ; N uni2690 ; G 3696
+U 9873 ; WX 896 ; N uni2691 ; G 3697
+U 9874 ; WX 896 ; N uni2692 ; G 3698
+U 9875 ; WX 896 ; N uni2693 ; G 3699
+U 9876 ; WX 896 ; N uni2694 ; G 3700
+U 9877 ; WX 541 ; N uni2695 ; G 3701
+U 9878 ; WX 896 ; N uni2696 ; G 3702
+U 9879 ; WX 896 ; N uni2697 ; G 3703
+U 9880 ; WX 896 ; N uni2698 ; G 3704
+U 9881 ; WX 896 ; N uni2699 ; G 3705
+U 9882 ; WX 896 ; N uni269A ; G 3706
+U 9883 ; WX 896 ; N uni269B ; G 3707
+U 9884 ; WX 896 ; N uni269C ; G 3708
+U 9886 ; WX 896 ; N uni269E ; G 3709
+U 9887 ; WX 896 ; N uni269F ; G 3710
+U 9888 ; WX 896 ; N uni26A0 ; G 3711
+U 9889 ; WX 702 ; N uni26A1 ; G 3712
+U 9890 ; WX 1004 ; N uni26A2 ; G 3713
+U 9891 ; WX 1089 ; N uni26A3 ; G 3714
+U 9892 ; WX 1175 ; N uni26A4 ; G 3715
+U 9893 ; WX 903 ; N uni26A5 ; G 3716
+U 9894 ; WX 838 ; N uni26A6 ; G 3717
+U 9895 ; WX 838 ; N uni26A7 ; G 3718
+U 9896 ; WX 838 ; N uni26A8 ; G 3719
+U 9897 ; WX 838 ; N uni26A9 ; G 3720
+U 9898 ; WX 838 ; N uni26AA ; G 3721
+U 9899 ; WX 838 ; N uni26AB ; G 3722
+U 9900 ; WX 838 ; N uni26AC ; G 3723
+U 9901 ; WX 838 ; N uni26AD ; G 3724
+U 9902 ; WX 838 ; N uni26AE ; G 3725
+U 9903 ; WX 838 ; N uni26AF ; G 3726
+U 9904 ; WX 844 ; N uni26B0 ; G 3727
+U 9905 ; WX 838 ; N uni26B1 ; G 3728
+U 9906 ; WX 732 ; N uni26B2 ; G 3729
+U 9907 ; WX 732 ; N uni26B3 ; G 3730
+U 9908 ; WX 732 ; N uni26B4 ; G 3731
+U 9909 ; WX 732 ; N uni26B5 ; G 3732
+U 9910 ; WX 850 ; N uni26B6 ; G 3733
+U 9911 ; WX 732 ; N uni26B7 ; G 3734
+U 9912 ; WX 732 ; N uni26B8 ; G 3735
+U 9920 ; WX 838 ; N uni26C0 ; G 3736
+U 9921 ; WX 838 ; N uni26C1 ; G 3737
+U 9922 ; WX 838 ; N uni26C2 ; G 3738
+U 9923 ; WX 838 ; N uni26C3 ; G 3739
+U 9954 ; WX 732 ; N uni26E2 ; G 3740
+U 9985 ; WX 838 ; N uni2701 ; G 3741
+U 9986 ; WX 838 ; N uni2702 ; G 3742
+U 9987 ; WX 838 ; N uni2703 ; G 3743
+U 9988 ; WX 838 ; N uni2704 ; G 3744
+U 9990 ; WX 838 ; N uni2706 ; G 3745
+U 9991 ; WX 838 ; N uni2707 ; G 3746
+U 9992 ; WX 838 ; N uni2708 ; G 3747
+U 9993 ; WX 838 ; N uni2709 ; G 3748
+U 9996 ; WX 838 ; N uni270C ; G 3749
+U 9997 ; WX 838 ; N uni270D ; G 3750
+U 9998 ; WX 838 ; N uni270E ; G 3751
+U 9999 ; WX 838 ; N uni270F ; G 3752
+U 10000 ; WX 838 ; N uni2710 ; G 3753
+U 10001 ; WX 838 ; N uni2711 ; G 3754
+U 10002 ; WX 838 ; N uni2712 ; G 3755
+U 10003 ; WX 838 ; N uni2713 ; G 3756
+U 10004 ; WX 838 ; N uni2714 ; G 3757
+U 10005 ; WX 838 ; N uni2715 ; G 3758
+U 10006 ; WX 838 ; N uni2716 ; G 3759
+U 10007 ; WX 838 ; N uni2717 ; G 3760
+U 10008 ; WX 838 ; N uni2718 ; G 3761
+U 10009 ; WX 838 ; N uni2719 ; G 3762
+U 10010 ; WX 838 ; N uni271A ; G 3763
+U 10011 ; WX 838 ; N uni271B ; G 3764
+U 10012 ; WX 838 ; N uni271C ; G 3765
+U 10013 ; WX 838 ; N uni271D ; G 3766
+U 10014 ; WX 838 ; N uni271E ; G 3767
+U 10015 ; WX 838 ; N uni271F ; G 3768
+U 10016 ; WX 838 ; N uni2720 ; G 3769
+U 10017 ; WX 838 ; N uni2721 ; G 3770
+U 10018 ; WX 838 ; N uni2722 ; G 3771
+U 10019 ; WX 838 ; N uni2723 ; G 3772
+U 10020 ; WX 838 ; N uni2724 ; G 3773
+U 10021 ; WX 838 ; N uni2725 ; G 3774
+U 10022 ; WX 838 ; N uni2726 ; G 3775
+U 10023 ; WX 838 ; N uni2727 ; G 3776
+U 10025 ; WX 838 ; N uni2729 ; G 3777
+U 10026 ; WX 838 ; N uni272A ; G 3778
+U 10027 ; WX 838 ; N uni272B ; G 3779
+U 10028 ; WX 838 ; N uni272C ; G 3780
+U 10029 ; WX 838 ; N uni272D ; G 3781
+U 10030 ; WX 838 ; N uni272E ; G 3782
+U 10031 ; WX 838 ; N uni272F ; G 3783
+U 10032 ; WX 838 ; N uni2730 ; G 3784
+U 10033 ; WX 838 ; N uni2731 ; G 3785
+U 10034 ; WX 838 ; N uni2732 ; G 3786
+U 10035 ; WX 838 ; N uni2733 ; G 3787
+U 10036 ; WX 838 ; N uni2734 ; G 3788
+U 10037 ; WX 838 ; N uni2735 ; G 3789
+U 10038 ; WX 838 ; N uni2736 ; G 3790
+U 10039 ; WX 838 ; N uni2737 ; G 3791
+U 10040 ; WX 838 ; N uni2738 ; G 3792
+U 10041 ; WX 838 ; N uni2739 ; G 3793
+U 10042 ; WX 838 ; N uni273A ; G 3794
+U 10043 ; WX 838 ; N uni273B ; G 3795
+U 10044 ; WX 838 ; N uni273C ; G 3796
+U 10045 ; WX 838 ; N uni273D ; G 3797
+U 10046 ; WX 838 ; N uni273E ; G 3798
+U 10047 ; WX 838 ; N uni273F ; G 3799
+U 10048 ; WX 838 ; N uni2740 ; G 3800
+U 10049 ; WX 838 ; N uni2741 ; G 3801
+U 10050 ; WX 838 ; N uni2742 ; G 3802
+U 10051 ; WX 838 ; N uni2743 ; G 3803
+U 10052 ; WX 838 ; N uni2744 ; G 3804
+U 10053 ; WX 838 ; N uni2745 ; G 3805
+U 10054 ; WX 838 ; N uni2746 ; G 3806
+U 10055 ; WX 838 ; N uni2747 ; G 3807
+U 10056 ; WX 838 ; N uni2748 ; G 3808
+U 10057 ; WX 838 ; N uni2749 ; G 3809
+U 10058 ; WX 838 ; N uni274A ; G 3810
+U 10059 ; WX 838 ; N uni274B ; G 3811
+U 10061 ; WX 896 ; N uni274D ; G 3812
+U 10063 ; WX 896 ; N uni274F ; G 3813
+U 10064 ; WX 896 ; N uni2750 ; G 3814
+U 10065 ; WX 896 ; N uni2751 ; G 3815
+U 10066 ; WX 896 ; N uni2752 ; G 3816
+U 10070 ; WX 896 ; N uni2756 ; G 3817
+U 10072 ; WX 838 ; N uni2758 ; G 3818
+U 10073 ; WX 838 ; N uni2759 ; G 3819
+U 10074 ; WX 838 ; N uni275A ; G 3820
+U 10075 ; WX 322 ; N uni275B ; G 3821
+U 10076 ; WX 322 ; N uni275C ; G 3822
+U 10077 ; WX 538 ; N uni275D ; G 3823
+U 10078 ; WX 538 ; N uni275E ; G 3824
+U 10081 ; WX 838 ; N uni2761 ; G 3825
+U 10082 ; WX 838 ; N uni2762 ; G 3826
+U 10083 ; WX 838 ; N uni2763 ; G 3827
+U 10084 ; WX 838 ; N uni2764 ; G 3828
+U 10085 ; WX 838 ; N uni2765 ; G 3829
+U 10086 ; WX 838 ; N uni2766 ; G 3830
+U 10087 ; WX 838 ; N uni2767 ; G 3831
+U 10088 ; WX 838 ; N uni2768 ; G 3832
+U 10089 ; WX 838 ; N uni2769 ; G 3833
+U 10090 ; WX 838 ; N uni276A ; G 3834
+U 10091 ; WX 838 ; N uni276B ; G 3835
+U 10092 ; WX 838 ; N uni276C ; G 3836
+U 10093 ; WX 838 ; N uni276D ; G 3837
+U 10094 ; WX 838 ; N uni276E ; G 3838
+U 10095 ; WX 838 ; N uni276F ; G 3839
+U 10096 ; WX 838 ; N uni2770 ; G 3840
+U 10097 ; WX 838 ; N uni2771 ; G 3841
+U 10098 ; WX 838 ; N uni2772 ; G 3842
+U 10099 ; WX 838 ; N uni2773 ; G 3843
+U 10100 ; WX 838 ; N uni2774 ; G 3844
+U 10101 ; WX 838 ; N uni2775 ; G 3845
+U 10102 ; WX 847 ; N uni2776 ; G 3846
+U 10103 ; WX 847 ; N uni2777 ; G 3847
+U 10104 ; WX 847 ; N uni2778 ; G 3848
+U 10105 ; WX 847 ; N uni2779 ; G 3849
+U 10106 ; WX 847 ; N uni277A ; G 3850
+U 10107 ; WX 847 ; N uni277B ; G 3851
+U 10108 ; WX 847 ; N uni277C ; G 3852
+U 10109 ; WX 847 ; N uni277D ; G 3853
+U 10110 ; WX 847 ; N uni277E ; G 3854
+U 10111 ; WX 847 ; N uni277F ; G 3855
+U 10112 ; WX 838 ; N uni2780 ; G 3856
+U 10113 ; WX 838 ; N uni2781 ; G 3857
+U 10114 ; WX 838 ; N uni2782 ; G 3858
+U 10115 ; WX 838 ; N uni2783 ; G 3859
+U 10116 ; WX 838 ; N uni2784 ; G 3860
+U 10117 ; WX 838 ; N uni2785 ; G 3861
+U 10118 ; WX 838 ; N uni2786 ; G 3862
+U 10119 ; WX 838 ; N uni2787 ; G 3863
+U 10120 ; WX 838 ; N uni2788 ; G 3864
+U 10121 ; WX 838 ; N uni2789 ; G 3865
+U 10122 ; WX 838 ; N uni278A ; G 3866
+U 10123 ; WX 838 ; N uni278B ; G 3867
+U 10124 ; WX 838 ; N uni278C ; G 3868
+U 10125 ; WX 838 ; N uni278D ; G 3869
+U 10126 ; WX 838 ; N uni278E ; G 3870
+U 10127 ; WX 838 ; N uni278F ; G 3871
+U 10128 ; WX 838 ; N uni2790 ; G 3872
+U 10129 ; WX 838 ; N uni2791 ; G 3873
+U 10130 ; WX 838 ; N uni2792 ; G 3874
+U 10131 ; WX 838 ; N uni2793 ; G 3875
+U 10132 ; WX 838 ; N uni2794 ; G 3876
+U 10136 ; WX 838 ; N uni2798 ; G 3877
+U 10137 ; WX 838 ; N uni2799 ; G 3878
+U 10138 ; WX 838 ; N uni279A ; G 3879
+U 10139 ; WX 838 ; N uni279B ; G 3880
+U 10140 ; WX 838 ; N uni279C ; G 3881
+U 10141 ; WX 838 ; N uni279D ; G 3882
+U 10142 ; WX 838 ; N uni279E ; G 3883
+U 10143 ; WX 838 ; N uni279F ; G 3884
+U 10144 ; WX 838 ; N uni27A0 ; G 3885
+U 10145 ; WX 838 ; N uni27A1 ; G 3886
+U 10146 ; WX 838 ; N uni27A2 ; G 3887
+U 10147 ; WX 838 ; N uni27A3 ; G 3888
+U 10148 ; WX 838 ; N uni27A4 ; G 3889
+U 10149 ; WX 838 ; N uni27A5 ; G 3890
+U 10150 ; WX 838 ; N uni27A6 ; G 3891
+U 10151 ; WX 838 ; N uni27A7 ; G 3892
+U 10152 ; WX 838 ; N uni27A8 ; G 3893
+U 10153 ; WX 838 ; N uni27A9 ; G 3894
+U 10154 ; WX 838 ; N uni27AA ; G 3895
+U 10155 ; WX 838 ; N uni27AB ; G 3896
+U 10156 ; WX 838 ; N uni27AC ; G 3897
+U 10157 ; WX 838 ; N uni27AD ; G 3898
+U 10158 ; WX 838 ; N uni27AE ; G 3899
+U 10159 ; WX 838 ; N uni27AF ; G 3900
+U 10161 ; WX 838 ; N uni27B1 ; G 3901
+U 10162 ; WX 838 ; N uni27B2 ; G 3902
+U 10163 ; WX 838 ; N uni27B3 ; G 3903
+U 10164 ; WX 838 ; N uni27B4 ; G 3904
+U 10165 ; WX 838 ; N uni27B5 ; G 3905
+U 10166 ; WX 838 ; N uni27B6 ; G 3906
+U 10167 ; WX 838 ; N uni27B7 ; G 3907
+U 10168 ; WX 838 ; N uni27B8 ; G 3908
+U 10169 ; WX 838 ; N uni27B9 ; G 3909
+U 10170 ; WX 838 ; N uni27BA ; G 3910
+U 10171 ; WX 838 ; N uni27BB ; G 3911
+U 10172 ; WX 838 ; N uni27BC ; G 3912
+U 10173 ; WX 838 ; N uni27BD ; G 3913
+U 10174 ; WX 838 ; N uni27BE ; G 3914
+U 10181 ; WX 457 ; N uni27C5 ; G 3915
+U 10182 ; WX 457 ; N uni27C6 ; G 3916
+U 10208 ; WX 494 ; N uni27E0 ; G 3917
+U 10214 ; WX 487 ; N uni27E6 ; G 3918
+U 10215 ; WX 487 ; N uni27E7 ; G 3919
+U 10216 ; WX 457 ; N uni27E8 ; G 3920
+U 10217 ; WX 457 ; N uni27E9 ; G 3921
+U 10218 ; WX 721 ; N uni27EA ; G 3922
+U 10219 ; WX 721 ; N uni27EB ; G 3923
+U 10224 ; WX 838 ; N uni27F0 ; G 3924
+U 10225 ; WX 838 ; N uni27F1 ; G 3925
+U 10226 ; WX 838 ; N uni27F2 ; G 3926
+U 10227 ; WX 838 ; N uni27F3 ; G 3927
+U 10228 ; WX 1157 ; N uni27F4 ; G 3928
+U 10229 ; WX 1434 ; N uni27F5 ; G 3929
+U 10230 ; WX 1434 ; N uni27F6 ; G 3930
+U 10231 ; WX 1434 ; N uni27F7 ; G 3931
+U 10232 ; WX 1434 ; N uni27F8 ; G 3932
+U 10233 ; WX 1434 ; N uni27F9 ; G 3933
+U 10234 ; WX 1434 ; N uni27FA ; G 3934
+U 10235 ; WX 1434 ; N uni27FB ; G 3935
+U 10236 ; WX 1434 ; N uni27FC ; G 3936
+U 10237 ; WX 1434 ; N uni27FD ; G 3937
+U 10238 ; WX 1434 ; N uni27FE ; G 3938
+U 10239 ; WX 1434 ; N uni27FF ; G 3939
+U 10240 ; WX 781 ; N uni2800 ; G 3940
+U 10241 ; WX 781 ; N uni2801 ; G 3941
+U 10242 ; WX 781 ; N uni2802 ; G 3942
+U 10243 ; WX 781 ; N uni2803 ; G 3943
+U 10244 ; WX 781 ; N uni2804 ; G 3944
+U 10245 ; WX 781 ; N uni2805 ; G 3945
+U 10246 ; WX 781 ; N uni2806 ; G 3946
+U 10247 ; WX 781 ; N uni2807 ; G 3947
+U 10248 ; WX 781 ; N uni2808 ; G 3948
+U 10249 ; WX 781 ; N uni2809 ; G 3949
+U 10250 ; WX 781 ; N uni280A ; G 3950
+U 10251 ; WX 781 ; N uni280B ; G 3951
+U 10252 ; WX 781 ; N uni280C ; G 3952
+U 10253 ; WX 781 ; N uni280D ; G 3953
+U 10254 ; WX 781 ; N uni280E ; G 3954
+U 10255 ; WX 781 ; N uni280F ; G 3955
+U 10256 ; WX 781 ; N uni2810 ; G 3956
+U 10257 ; WX 781 ; N uni2811 ; G 3957
+U 10258 ; WX 781 ; N uni2812 ; G 3958
+U 10259 ; WX 781 ; N uni2813 ; G 3959
+U 10260 ; WX 781 ; N uni2814 ; G 3960
+U 10261 ; WX 781 ; N uni2815 ; G 3961
+U 10262 ; WX 781 ; N uni2816 ; G 3962
+U 10263 ; WX 781 ; N uni2817 ; G 3963
+U 10264 ; WX 781 ; N uni2818 ; G 3964
+U 10265 ; WX 781 ; N uni2819 ; G 3965
+U 10266 ; WX 781 ; N uni281A ; G 3966
+U 10267 ; WX 781 ; N uni281B ; G 3967
+U 10268 ; WX 781 ; N uni281C ; G 3968
+U 10269 ; WX 781 ; N uni281D ; G 3969
+U 10270 ; WX 781 ; N uni281E ; G 3970
+U 10271 ; WX 781 ; N uni281F ; G 3971
+U 10272 ; WX 781 ; N uni2820 ; G 3972
+U 10273 ; WX 781 ; N uni2821 ; G 3973
+U 10274 ; WX 781 ; N uni2822 ; G 3974
+U 10275 ; WX 781 ; N uni2823 ; G 3975
+U 10276 ; WX 781 ; N uni2824 ; G 3976
+U 10277 ; WX 781 ; N uni2825 ; G 3977
+U 10278 ; WX 781 ; N uni2826 ; G 3978
+U 10279 ; WX 781 ; N uni2827 ; G 3979
+U 10280 ; WX 781 ; N uni2828 ; G 3980
+U 10281 ; WX 781 ; N uni2829 ; G 3981
+U 10282 ; WX 781 ; N uni282A ; G 3982
+U 10283 ; WX 781 ; N uni282B ; G 3983
+U 10284 ; WX 781 ; N uni282C ; G 3984
+U 10285 ; WX 781 ; N uni282D ; G 3985
+U 10286 ; WX 781 ; N uni282E ; G 3986
+U 10287 ; WX 781 ; N uni282F ; G 3987
+U 10288 ; WX 781 ; N uni2830 ; G 3988
+U 10289 ; WX 781 ; N uni2831 ; G 3989
+U 10290 ; WX 781 ; N uni2832 ; G 3990
+U 10291 ; WX 781 ; N uni2833 ; G 3991
+U 10292 ; WX 781 ; N uni2834 ; G 3992
+U 10293 ; WX 781 ; N uni2835 ; G 3993
+U 10294 ; WX 781 ; N uni2836 ; G 3994
+U 10295 ; WX 781 ; N uni2837 ; G 3995
+U 10296 ; WX 781 ; N uni2838 ; G 3996
+U 10297 ; WX 781 ; N uni2839 ; G 3997
+U 10298 ; WX 781 ; N uni283A ; G 3998
+U 10299 ; WX 781 ; N uni283B ; G 3999
+U 10300 ; WX 781 ; N uni283C ; G 4000
+U 10301 ; WX 781 ; N uni283D ; G 4001
+U 10302 ; WX 781 ; N uni283E ; G 4002
+U 10303 ; WX 781 ; N uni283F ; G 4003
+U 10304 ; WX 781 ; N uni2840 ; G 4004
+U 10305 ; WX 781 ; N uni2841 ; G 4005
+U 10306 ; WX 781 ; N uni2842 ; G 4006
+U 10307 ; WX 781 ; N uni2843 ; G 4007
+U 10308 ; WX 781 ; N uni2844 ; G 4008
+U 10309 ; WX 781 ; N uni2845 ; G 4009
+U 10310 ; WX 781 ; N uni2846 ; G 4010
+U 10311 ; WX 781 ; N uni2847 ; G 4011
+U 10312 ; WX 781 ; N uni2848 ; G 4012
+U 10313 ; WX 781 ; N uni2849 ; G 4013
+U 10314 ; WX 781 ; N uni284A ; G 4014
+U 10315 ; WX 781 ; N uni284B ; G 4015
+U 10316 ; WX 781 ; N uni284C ; G 4016
+U 10317 ; WX 781 ; N uni284D ; G 4017
+U 10318 ; WX 781 ; N uni284E ; G 4018
+U 10319 ; WX 781 ; N uni284F ; G 4019
+U 10320 ; WX 781 ; N uni2850 ; G 4020
+U 10321 ; WX 781 ; N uni2851 ; G 4021
+U 10322 ; WX 781 ; N uni2852 ; G 4022
+U 10323 ; WX 781 ; N uni2853 ; G 4023
+U 10324 ; WX 781 ; N uni2854 ; G 4024
+U 10325 ; WX 781 ; N uni2855 ; G 4025
+U 10326 ; WX 781 ; N uni2856 ; G 4026
+U 10327 ; WX 781 ; N uni2857 ; G 4027
+U 10328 ; WX 781 ; N uni2858 ; G 4028
+U 10329 ; WX 781 ; N uni2859 ; G 4029
+U 10330 ; WX 781 ; N uni285A ; G 4030
+U 10331 ; WX 781 ; N uni285B ; G 4031
+U 10332 ; WX 781 ; N uni285C ; G 4032
+U 10333 ; WX 781 ; N uni285D ; G 4033
+U 10334 ; WX 781 ; N uni285E ; G 4034
+U 10335 ; WX 781 ; N uni285F ; G 4035
+U 10336 ; WX 781 ; N uni2860 ; G 4036
+U 10337 ; WX 781 ; N uni2861 ; G 4037
+U 10338 ; WX 781 ; N uni2862 ; G 4038
+U 10339 ; WX 781 ; N uni2863 ; G 4039
+U 10340 ; WX 781 ; N uni2864 ; G 4040
+U 10341 ; WX 781 ; N uni2865 ; G 4041
+U 10342 ; WX 781 ; N uni2866 ; G 4042
+U 10343 ; WX 781 ; N uni2867 ; G 4043
+U 10344 ; WX 781 ; N uni2868 ; G 4044
+U 10345 ; WX 781 ; N uni2869 ; G 4045
+U 10346 ; WX 781 ; N uni286A ; G 4046
+U 10347 ; WX 781 ; N uni286B ; G 4047
+U 10348 ; WX 781 ; N uni286C ; G 4048
+U 10349 ; WX 781 ; N uni286D ; G 4049
+U 10350 ; WX 781 ; N uni286E ; G 4050
+U 10351 ; WX 781 ; N uni286F ; G 4051
+U 10352 ; WX 781 ; N uni2870 ; G 4052
+U 10353 ; WX 781 ; N uni2871 ; G 4053
+U 10354 ; WX 781 ; N uni2872 ; G 4054
+U 10355 ; WX 781 ; N uni2873 ; G 4055
+U 10356 ; WX 781 ; N uni2874 ; G 4056
+U 10357 ; WX 781 ; N uni2875 ; G 4057
+U 10358 ; WX 781 ; N uni2876 ; G 4058
+U 10359 ; WX 781 ; N uni2877 ; G 4059
+U 10360 ; WX 781 ; N uni2878 ; G 4060
+U 10361 ; WX 781 ; N uni2879 ; G 4061
+U 10362 ; WX 781 ; N uni287A ; G 4062
+U 10363 ; WX 781 ; N uni287B ; G 4063
+U 10364 ; WX 781 ; N uni287C ; G 4064
+U 10365 ; WX 781 ; N uni287D ; G 4065
+U 10366 ; WX 781 ; N uni287E ; G 4066
+U 10367 ; WX 781 ; N uni287F ; G 4067
+U 10368 ; WX 781 ; N uni2880 ; G 4068
+U 10369 ; WX 781 ; N uni2881 ; G 4069
+U 10370 ; WX 781 ; N uni2882 ; G 4070
+U 10371 ; WX 781 ; N uni2883 ; G 4071
+U 10372 ; WX 781 ; N uni2884 ; G 4072
+U 10373 ; WX 781 ; N uni2885 ; G 4073
+U 10374 ; WX 781 ; N uni2886 ; G 4074
+U 10375 ; WX 781 ; N uni2887 ; G 4075
+U 10376 ; WX 781 ; N uni2888 ; G 4076
+U 10377 ; WX 781 ; N uni2889 ; G 4077
+U 10378 ; WX 781 ; N uni288A ; G 4078
+U 10379 ; WX 781 ; N uni288B ; G 4079
+U 10380 ; WX 781 ; N uni288C ; G 4080
+U 10381 ; WX 781 ; N uni288D ; G 4081
+U 10382 ; WX 781 ; N uni288E ; G 4082
+U 10383 ; WX 781 ; N uni288F ; G 4083
+U 10384 ; WX 781 ; N uni2890 ; G 4084
+U 10385 ; WX 781 ; N uni2891 ; G 4085
+U 10386 ; WX 781 ; N uni2892 ; G 4086
+U 10387 ; WX 781 ; N uni2893 ; G 4087
+U 10388 ; WX 781 ; N uni2894 ; G 4088
+U 10389 ; WX 781 ; N uni2895 ; G 4089
+U 10390 ; WX 781 ; N uni2896 ; G 4090
+U 10391 ; WX 781 ; N uni2897 ; G 4091
+U 10392 ; WX 781 ; N uni2898 ; G 4092
+U 10393 ; WX 781 ; N uni2899 ; G 4093
+U 10394 ; WX 781 ; N uni289A ; G 4094
+U 10395 ; WX 781 ; N uni289B ; G 4095
+U 10396 ; WX 781 ; N uni289C ; G 4096
+U 10397 ; WX 781 ; N uni289D ; G 4097
+U 10398 ; WX 781 ; N uni289E ; G 4098
+U 10399 ; WX 781 ; N uni289F ; G 4099
+U 10400 ; WX 781 ; N uni28A0 ; G 4100
+U 10401 ; WX 781 ; N uni28A1 ; G 4101
+U 10402 ; WX 781 ; N uni28A2 ; G 4102
+U 10403 ; WX 781 ; N uni28A3 ; G 4103
+U 10404 ; WX 781 ; N uni28A4 ; G 4104
+U 10405 ; WX 781 ; N uni28A5 ; G 4105
+U 10406 ; WX 781 ; N uni28A6 ; G 4106
+U 10407 ; WX 781 ; N uni28A7 ; G 4107
+U 10408 ; WX 781 ; N uni28A8 ; G 4108
+U 10409 ; WX 781 ; N uni28A9 ; G 4109
+U 10410 ; WX 781 ; N uni28AA ; G 4110
+U 10411 ; WX 781 ; N uni28AB ; G 4111
+U 10412 ; WX 781 ; N uni28AC ; G 4112
+U 10413 ; WX 781 ; N uni28AD ; G 4113
+U 10414 ; WX 781 ; N uni28AE ; G 4114
+U 10415 ; WX 781 ; N uni28AF ; G 4115
+U 10416 ; WX 781 ; N uni28B0 ; G 4116
+U 10417 ; WX 781 ; N uni28B1 ; G 4117
+U 10418 ; WX 781 ; N uni28B2 ; G 4118
+U 10419 ; WX 781 ; N uni28B3 ; G 4119
+U 10420 ; WX 781 ; N uni28B4 ; G 4120
+U 10421 ; WX 781 ; N uni28B5 ; G 4121
+U 10422 ; WX 781 ; N uni28B6 ; G 4122
+U 10423 ; WX 781 ; N uni28B7 ; G 4123
+U 10424 ; WX 781 ; N uni28B8 ; G 4124
+U 10425 ; WX 781 ; N uni28B9 ; G 4125
+U 10426 ; WX 781 ; N uni28BA ; G 4126
+U 10427 ; WX 781 ; N uni28BB ; G 4127
+U 10428 ; WX 781 ; N uni28BC ; G 4128
+U 10429 ; WX 781 ; N uni28BD ; G 4129
+U 10430 ; WX 781 ; N uni28BE ; G 4130
+U 10431 ; WX 781 ; N uni28BF ; G 4131
+U 10432 ; WX 781 ; N uni28C0 ; G 4132
+U 10433 ; WX 781 ; N uni28C1 ; G 4133
+U 10434 ; WX 781 ; N uni28C2 ; G 4134
+U 10435 ; WX 781 ; N uni28C3 ; G 4135
+U 10436 ; WX 781 ; N uni28C4 ; G 4136
+U 10437 ; WX 781 ; N uni28C5 ; G 4137
+U 10438 ; WX 781 ; N uni28C6 ; G 4138
+U 10439 ; WX 781 ; N uni28C7 ; G 4139
+U 10440 ; WX 781 ; N uni28C8 ; G 4140
+U 10441 ; WX 781 ; N uni28C9 ; G 4141
+U 10442 ; WX 781 ; N uni28CA ; G 4142
+U 10443 ; WX 781 ; N uni28CB ; G 4143
+U 10444 ; WX 781 ; N uni28CC ; G 4144
+U 10445 ; WX 781 ; N uni28CD ; G 4145
+U 10446 ; WX 781 ; N uni28CE ; G 4146
+U 10447 ; WX 781 ; N uni28CF ; G 4147
+U 10448 ; WX 781 ; N uni28D0 ; G 4148
+U 10449 ; WX 781 ; N uni28D1 ; G 4149
+U 10450 ; WX 781 ; N uni28D2 ; G 4150
+U 10451 ; WX 781 ; N uni28D3 ; G 4151
+U 10452 ; WX 781 ; N uni28D4 ; G 4152
+U 10453 ; WX 781 ; N uni28D5 ; G 4153
+U 10454 ; WX 781 ; N uni28D6 ; G 4154
+U 10455 ; WX 781 ; N uni28D7 ; G 4155
+U 10456 ; WX 781 ; N uni28D8 ; G 4156
+U 10457 ; WX 781 ; N uni28D9 ; G 4157
+U 10458 ; WX 781 ; N uni28DA ; G 4158
+U 10459 ; WX 781 ; N uni28DB ; G 4159
+U 10460 ; WX 781 ; N uni28DC ; G 4160
+U 10461 ; WX 781 ; N uni28DD ; G 4161
+U 10462 ; WX 781 ; N uni28DE ; G 4162
+U 10463 ; WX 781 ; N uni28DF ; G 4163
+U 10464 ; WX 781 ; N uni28E0 ; G 4164
+U 10465 ; WX 781 ; N uni28E1 ; G 4165
+U 10466 ; WX 781 ; N uni28E2 ; G 4166
+U 10467 ; WX 781 ; N uni28E3 ; G 4167
+U 10468 ; WX 781 ; N uni28E4 ; G 4168
+U 10469 ; WX 781 ; N uni28E5 ; G 4169
+U 10470 ; WX 781 ; N uni28E6 ; G 4170
+U 10471 ; WX 781 ; N uni28E7 ; G 4171
+U 10472 ; WX 781 ; N uni28E8 ; G 4172
+U 10473 ; WX 781 ; N uni28E9 ; G 4173
+U 10474 ; WX 781 ; N uni28EA ; G 4174
+U 10475 ; WX 781 ; N uni28EB ; G 4175
+U 10476 ; WX 781 ; N uni28EC ; G 4176
+U 10477 ; WX 781 ; N uni28ED ; G 4177
+U 10478 ; WX 781 ; N uni28EE ; G 4178
+U 10479 ; WX 781 ; N uni28EF ; G 4179
+U 10480 ; WX 781 ; N uni28F0 ; G 4180
+U 10481 ; WX 781 ; N uni28F1 ; G 4181
+U 10482 ; WX 781 ; N uni28F2 ; G 4182
+U 10483 ; WX 781 ; N uni28F3 ; G 4183
+U 10484 ; WX 781 ; N uni28F4 ; G 4184
+U 10485 ; WX 781 ; N uni28F5 ; G 4185
+U 10486 ; WX 781 ; N uni28F6 ; G 4186
+U 10487 ; WX 781 ; N uni28F7 ; G 4187
+U 10488 ; WX 781 ; N uni28F8 ; G 4188
+U 10489 ; WX 781 ; N uni28F9 ; G 4189
+U 10490 ; WX 781 ; N uni28FA ; G 4190
+U 10491 ; WX 781 ; N uni28FB ; G 4191
+U 10492 ; WX 781 ; N uni28FC ; G 4192
+U 10493 ; WX 781 ; N uni28FD ; G 4193
+U 10494 ; WX 781 ; N uni28FE ; G 4194
+U 10495 ; WX 781 ; N uni28FF ; G 4195
+U 10502 ; WX 838 ; N uni2906 ; G 4196
+U 10503 ; WX 838 ; N uni2907 ; G 4197
+U 10506 ; WX 838 ; N uni290A ; G 4198
+U 10507 ; WX 838 ; N uni290B ; G 4199
+U 10560 ; WX 838 ; N uni2940 ; G 4200
+U 10561 ; WX 838 ; N uni2941 ; G 4201
+U 10627 ; WX 753 ; N uni2983 ; G 4202
+U 10628 ; WX 753 ; N uni2984 ; G 4203
+U 10702 ; WX 838 ; N uni29CE ; G 4204
+U 10703 ; WX 1046 ; N uni29CF ; G 4205
+U 10704 ; WX 1046 ; N uni29D0 ; G 4206
+U 10705 ; WX 1000 ; N uni29D1 ; G 4207
+U 10706 ; WX 1000 ; N uni29D2 ; G 4208
+U 10707 ; WX 1000 ; N uni29D3 ; G 4209
+U 10708 ; WX 1000 ; N uni29D4 ; G 4210
+U 10709 ; WX 1000 ; N uni29D5 ; G 4211
+U 10731 ; WX 494 ; N uni29EB ; G 4212
+U 10746 ; WX 838 ; N uni29FA ; G 4213
+U 10747 ; WX 838 ; N uni29FB ; G 4214
+U 10752 ; WX 1000 ; N uni2A00 ; G 4215
+U 10753 ; WX 1000 ; N uni2A01 ; G 4216
+U 10754 ; WX 1000 ; N uni2A02 ; G 4217
+U 10764 ; WX 1661 ; N uni2A0C ; G 4218
+U 10765 ; WX 563 ; N uni2A0D ; G 4219
+U 10766 ; WX 563 ; N uni2A0E ; G 4220
+U 10767 ; WX 563 ; N uni2A0F ; G 4221
+U 10768 ; WX 563 ; N uni2A10 ; G 4222
+U 10769 ; WX 563 ; N uni2A11 ; G 4223
+U 10770 ; WX 563 ; N uni2A12 ; G 4224
+U 10771 ; WX 563 ; N uni2A13 ; G 4225
+U 10772 ; WX 563 ; N uni2A14 ; G 4226
+U 10773 ; WX 563 ; N uni2A15 ; G 4227
+U 10774 ; WX 563 ; N uni2A16 ; G 4228
+U 10775 ; WX 563 ; N uni2A17 ; G 4229
+U 10776 ; WX 563 ; N uni2A18 ; G 4230
+U 10777 ; WX 563 ; N uni2A19 ; G 4231
+U 10778 ; WX 563 ; N uni2A1A ; G 4232
+U 10779 ; WX 563 ; N uni2A1B ; G 4233
+U 10780 ; WX 563 ; N uni2A1C ; G 4234
+U 10799 ; WX 838 ; N uni2A2F ; G 4235
+U 10858 ; WX 838 ; N uni2A6A ; G 4236
+U 10859 ; WX 838 ; N uni2A6B ; G 4237
+U 10877 ; WX 838 ; N uni2A7D ; G 4238
+U 10878 ; WX 838 ; N uni2A7E ; G 4239
+U 10879 ; WX 838 ; N uni2A7F ; G 4240
+U 10880 ; WX 838 ; N uni2A80 ; G 4241
+U 10881 ; WX 838 ; N uni2A81 ; G 4242
+U 10882 ; WX 838 ; N uni2A82 ; G 4243
+U 10883 ; WX 838 ; N uni2A83 ; G 4244
+U 10884 ; WX 838 ; N uni2A84 ; G 4245
+U 10885 ; WX 838 ; N uni2A85 ; G 4246
+U 10886 ; WX 838 ; N uni2A86 ; G 4247
+U 10887 ; WX 838 ; N uni2A87 ; G 4248
+U 10888 ; WX 838 ; N uni2A88 ; G 4249
+U 10889 ; WX 838 ; N uni2A89 ; G 4250
+U 10890 ; WX 838 ; N uni2A8A ; G 4251
+U 10891 ; WX 838 ; N uni2A8B ; G 4252
+U 10892 ; WX 838 ; N uni2A8C ; G 4253
+U 10893 ; WX 838 ; N uni2A8D ; G 4254
+U 10894 ; WX 838 ; N uni2A8E ; G 4255
+U 10895 ; WX 838 ; N uni2A8F ; G 4256
+U 10896 ; WX 838 ; N uni2A90 ; G 4257
+U 10897 ; WX 838 ; N uni2A91 ; G 4258
+U 10898 ; WX 838 ; N uni2A92 ; G 4259
+U 10899 ; WX 838 ; N uni2A93 ; G 4260
+U 10900 ; WX 838 ; N uni2A94 ; G 4261
+U 10901 ; WX 838 ; N uni2A95 ; G 4262
+U 10902 ; WX 838 ; N uni2A96 ; G 4263
+U 10903 ; WX 838 ; N uni2A97 ; G 4264
+U 10904 ; WX 838 ; N uni2A98 ; G 4265
+U 10905 ; WX 838 ; N uni2A99 ; G 4266
+U 10906 ; WX 838 ; N uni2A9A ; G 4267
+U 10907 ; WX 838 ; N uni2A9B ; G 4268
+U 10908 ; WX 838 ; N uni2A9C ; G 4269
+U 10909 ; WX 838 ; N uni2A9D ; G 4270
+U 10910 ; WX 838 ; N uni2A9E ; G 4271
+U 10911 ; WX 838 ; N uni2A9F ; G 4272
+U 10912 ; WX 838 ; N uni2AA0 ; G 4273
+U 10926 ; WX 838 ; N uni2AAE ; G 4274
+U 10927 ; WX 838 ; N uni2AAF ; G 4275
+U 10928 ; WX 838 ; N uni2AB0 ; G 4276
+U 10929 ; WX 838 ; N uni2AB1 ; G 4277
+U 10930 ; WX 838 ; N uni2AB2 ; G 4278
+U 10931 ; WX 838 ; N uni2AB3 ; G 4279
+U 10932 ; WX 838 ; N uni2AB4 ; G 4280
+U 10933 ; WX 838 ; N uni2AB5 ; G 4281
+U 10934 ; WX 838 ; N uni2AB6 ; G 4282
+U 10935 ; WX 838 ; N uni2AB7 ; G 4283
+U 10936 ; WX 838 ; N uni2AB8 ; G 4284
+U 10937 ; WX 838 ; N uni2AB9 ; G 4285
+U 10938 ; WX 838 ; N uni2ABA ; G 4286
+U 11001 ; WX 838 ; N uni2AF9 ; G 4287
+U 11002 ; WX 838 ; N uni2AFA ; G 4288
+U 11008 ; WX 838 ; N uni2B00 ; G 4289
+U 11009 ; WX 838 ; N uni2B01 ; G 4290
+U 11010 ; WX 838 ; N uni2B02 ; G 4291
+U 11011 ; WX 838 ; N uni2B03 ; G 4292
+U 11012 ; WX 838 ; N uni2B04 ; G 4293
+U 11013 ; WX 838 ; N uni2B05 ; G 4294
+U 11014 ; WX 838 ; N uni2B06 ; G 4295
+U 11015 ; WX 838 ; N uni2B07 ; G 4296
+U 11016 ; WX 838 ; N uni2B08 ; G 4297
+U 11017 ; WX 838 ; N uni2B09 ; G 4298
+U 11018 ; WX 838 ; N uni2B0A ; G 4299
+U 11019 ; WX 838 ; N uni2B0B ; G 4300
+U 11020 ; WX 838 ; N uni2B0C ; G 4301
+U 11021 ; WX 838 ; N uni2B0D ; G 4302
+U 11022 ; WX 838 ; N uni2B0E ; G 4303
+U 11023 ; WX 838 ; N uni2B0F ; G 4304
+U 11024 ; WX 838 ; N uni2B10 ; G 4305
+U 11025 ; WX 838 ; N uni2B11 ; G 4306
+U 11026 ; WX 945 ; N uni2B12 ; G 4307
+U 11027 ; WX 945 ; N uni2B13 ; G 4308
+U 11028 ; WX 945 ; N uni2B14 ; G 4309
+U 11029 ; WX 945 ; N uni2B15 ; G 4310
+U 11030 ; WX 769 ; N uni2B16 ; G 4311
+U 11031 ; WX 769 ; N uni2B17 ; G 4312
+U 11032 ; WX 769 ; N uni2B18 ; G 4313
+U 11033 ; WX 769 ; N uni2B19 ; G 4314
+U 11034 ; WX 945 ; N uni2B1A ; G 4315
+U 11039 ; WX 869 ; N uni2B1F ; G 4316
+U 11040 ; WX 869 ; N uni2B20 ; G 4317
+U 11041 ; WX 873 ; N uni2B21 ; G 4318
+U 11042 ; WX 873 ; N uni2B22 ; G 4319
+U 11043 ; WX 873 ; N uni2B23 ; G 4320
+U 11044 ; WX 1119 ; N uni2B24 ; G 4321
+U 11091 ; WX 869 ; N uni2B53 ; G 4322
+U 11092 ; WX 869 ; N uni2B54 ; G 4323
+U 11360 ; WX 637 ; N uni2C60 ; G 4324
+U 11361 ; WX 360 ; N uni2C61 ; G 4325
+U 11362 ; WX 637 ; N uni2C62 ; G 4326
+U 11363 ; WX 733 ; N uni2C63 ; G 4327
+U 11364 ; WX 770 ; N uni2C64 ; G 4328
+U 11365 ; WX 675 ; N uni2C65 ; G 4329
+U 11366 ; WX 478 ; N uni2C66 ; G 4330
+U 11367 ; WX 956 ; N uni2C67 ; G 4331
+U 11368 ; WX 712 ; N uni2C68 ; G 4332
+U 11369 ; WX 775 ; N uni2C69 ; G 4333
+U 11370 ; WX 665 ; N uni2C6A ; G 4334
+U 11371 ; WX 725 ; N uni2C6B ; G 4335
+U 11372 ; WX 582 ; N uni2C6C ; G 4336
+U 11373 ; WX 860 ; N uni2C6D ; G 4337
+U 11374 ; WX 995 ; N uni2C6E ; G 4338
+U 11375 ; WX 774 ; N uni2C6F ; G 4339
+U 11376 ; WX 860 ; N uni2C70 ; G 4340
+U 11377 ; WX 778 ; N uni2C71 ; G 4341
+U 11378 ; WX 1221 ; N uni2C72 ; G 4342
+U 11379 ; WX 1056 ; N uni2C73 ; G 4343
+U 11380 ; WX 652 ; N uni2C74 ; G 4344
+U 11381 ; WX 698 ; N uni2C75 ; G 4345
+U 11382 ; WX 565 ; N uni2C76 ; G 4346
+U 11383 ; WX 782 ; N uni2C77 ; G 4347
+U 11385 ; WX 538 ; N uni2C79 ; G 4348
+U 11386 ; WX 687 ; N uni2C7A ; G 4349
+U 11387 ; WX 559 ; N uni2C7B ; G 4350
+U 11388 ; WX 219 ; N uni2C7C ; G 4351
+U 11389 ; WX 487 ; N uni2C7D ; G 4352
+U 11390 ; WX 720 ; N uni2C7E ; G 4353
+U 11391 ; WX 725 ; N uni2C7F ; G 4354
+U 11520 ; WX 663 ; N uni2D00 ; G 4355
+U 11521 ; WX 676 ; N uni2D01 ; G 4356
+U 11522 ; WX 661 ; N uni2D02 ; G 4357
+U 11523 ; WX 629 ; N uni2D03 ; G 4358
+U 11524 ; WX 661 ; N uni2D04 ; G 4359
+U 11525 ; WX 1032 ; N uni2D05 ; G 4360
+U 11526 ; WX 718 ; N uni2D06 ; G 4361
+U 11527 ; WX 1032 ; N uni2D07 ; G 4362
+U 11528 ; WX 648 ; N uni2D08 ; G 4363
+U 11529 ; WX 667 ; N uni2D09 ; G 4364
+U 11530 ; WX 1032 ; N uni2D0A ; G 4365
+U 11531 ; WX 673 ; N uni2D0B ; G 4366
+U 11532 ; WX 677 ; N uni2D0C ; G 4367
+U 11533 ; WX 1036 ; N uni2D0D ; G 4368
+U 11534 ; WX 680 ; N uni2D0E ; G 4369
+U 11535 ; WX 886 ; N uni2D0F ; G 4370
+U 11536 ; WX 1032 ; N uni2D10 ; G 4371
+U 11537 ; WX 683 ; N uni2D11 ; G 4372
+U 11538 ; WX 674 ; N uni2D12 ; G 4373
+U 11539 ; WX 1035 ; N uni2D13 ; G 4374
+U 11540 ; WX 1033 ; N uni2D14 ; G 4375
+U 11541 ; WX 1027 ; N uni2D15 ; G 4376
+U 11542 ; WX 676 ; N uni2D16 ; G 4377
+U 11543 ; WX 673 ; N uni2D17 ; G 4378
+U 11544 ; WX 667 ; N uni2D18 ; G 4379
+U 11545 ; WX 667 ; N uni2D19 ; G 4380
+U 11546 ; WX 660 ; N uni2D1A ; G 4381
+U 11547 ; WX 671 ; N uni2D1B ; G 4382
+U 11548 ; WX 1039 ; N uni2D1C ; G 4383
+U 11549 ; WX 673 ; N uni2D1D ; G 4384
+U 11550 ; WX 692 ; N uni2D1E ; G 4385
+U 11551 ; WX 659 ; N uni2D1F ; G 4386
+U 11552 ; WX 1048 ; N uni2D20 ; G 4387
+U 11553 ; WX 660 ; N uni2D21 ; G 4388
+U 11554 ; WX 654 ; N uni2D22 ; G 4389
+U 11555 ; WX 670 ; N uni2D23 ; G 4390
+U 11556 ; WX 733 ; N uni2D24 ; G 4391
+U 11557 ; WX 1017 ; N uni2D25 ; G 4392
+U 11800 ; WX 580 ; N uni2E18 ; G 4393
+U 11807 ; WX 838 ; N uni2E1F ; G 4394
+U 11810 ; WX 457 ; N uni2E22 ; G 4395
+U 11811 ; WX 457 ; N uni2E23 ; G 4396
+U 11812 ; WX 457 ; N uni2E24 ; G 4397
+U 11813 ; WX 457 ; N uni2E25 ; G 4398
+U 11822 ; WX 580 ; N uni2E2E ; G 4399
+U 19904 ; WX 896 ; N uni4DC0 ; G 4400
+U 19905 ; WX 896 ; N uni4DC1 ; G 4401
+U 19906 ; WX 896 ; N uni4DC2 ; G 4402
+U 19907 ; WX 896 ; N uni4DC3 ; G 4403
+U 19908 ; WX 896 ; N uni4DC4 ; G 4404
+U 19909 ; WX 896 ; N uni4DC5 ; G 4405
+U 19910 ; WX 896 ; N uni4DC6 ; G 4406
+U 19911 ; WX 896 ; N uni4DC7 ; G 4407
+U 19912 ; WX 896 ; N uni4DC8 ; G 4408
+U 19913 ; WX 896 ; N uni4DC9 ; G 4409
+U 19914 ; WX 896 ; N uni4DCA ; G 4410
+U 19915 ; WX 896 ; N uni4DCB ; G 4411
+U 19916 ; WX 896 ; N uni4DCC ; G 4412
+U 19917 ; WX 896 ; N uni4DCD ; G 4413
+U 19918 ; WX 896 ; N uni4DCE ; G 4414
+U 19919 ; WX 896 ; N uni4DCF ; G 4415
+U 19920 ; WX 896 ; N uni4DD0 ; G 4416
+U 19921 ; WX 896 ; N uni4DD1 ; G 4417
+U 19922 ; WX 896 ; N uni4DD2 ; G 4418
+U 19923 ; WX 896 ; N uni4DD3 ; G 4419
+U 19924 ; WX 896 ; N uni4DD4 ; G 4420
+U 19925 ; WX 896 ; N uni4DD5 ; G 4421
+U 19926 ; WX 896 ; N uni4DD6 ; G 4422
+U 19927 ; WX 896 ; N uni4DD7 ; G 4423
+U 19928 ; WX 896 ; N uni4DD8 ; G 4424
+U 19929 ; WX 896 ; N uni4DD9 ; G 4425
+U 19930 ; WX 896 ; N uni4DDA ; G 4426
+U 19931 ; WX 896 ; N uni4DDB ; G 4427
+U 19932 ; WX 896 ; N uni4DDC ; G 4428
+U 19933 ; WX 896 ; N uni4DDD ; G 4429
+U 19934 ; WX 896 ; N uni4DDE ; G 4430
+U 19935 ; WX 896 ; N uni4DDF ; G 4431
+U 19936 ; WX 896 ; N uni4DE0 ; G 4432
+U 19937 ; WX 896 ; N uni4DE1 ; G 4433
+U 19938 ; WX 896 ; N uni4DE2 ; G 4434
+U 19939 ; WX 896 ; N uni4DE3 ; G 4435
+U 19940 ; WX 896 ; N uni4DE4 ; G 4436
+U 19941 ; WX 896 ; N uni4DE5 ; G 4437
+U 19942 ; WX 896 ; N uni4DE6 ; G 4438
+U 19943 ; WX 896 ; N uni4DE7 ; G 4439
+U 19944 ; WX 896 ; N uni4DE8 ; G 4440
+U 19945 ; WX 896 ; N uni4DE9 ; G 4441
+U 19946 ; WX 896 ; N uni4DEA ; G 4442
+U 19947 ; WX 896 ; N uni4DEB ; G 4443
+U 19948 ; WX 896 ; N uni4DEC ; G 4444
+U 19949 ; WX 896 ; N uni4DED ; G 4445
+U 19950 ; WX 896 ; N uni4DEE ; G 4446
+U 19951 ; WX 896 ; N uni4DEF ; G 4447
+U 19952 ; WX 896 ; N uni4DF0 ; G 4448
+U 19953 ; WX 896 ; N uni4DF1 ; G 4449
+U 19954 ; WX 896 ; N uni4DF2 ; G 4450
+U 19955 ; WX 896 ; N uni4DF3 ; G 4451
+U 19956 ; WX 896 ; N uni4DF4 ; G 4452
+U 19957 ; WX 896 ; N uni4DF5 ; G 4453
+U 19958 ; WX 896 ; N uni4DF6 ; G 4454
+U 19959 ; WX 896 ; N uni4DF7 ; G 4455
+U 19960 ; WX 896 ; N uni4DF8 ; G 4456
+U 19961 ; WX 896 ; N uni4DF9 ; G 4457
+U 19962 ; WX 896 ; N uni4DFA ; G 4458
+U 19963 ; WX 896 ; N uni4DFB ; G 4459
+U 19964 ; WX 896 ; N uni4DFC ; G 4460
+U 19965 ; WX 896 ; N uni4DFD ; G 4461
+U 19966 ; WX 896 ; N uni4DFE ; G 4462
+U 19967 ; WX 896 ; N uni4DFF ; G 4463
+U 42192 ; WX 762 ; N uniA4D0 ; G 4464
+U 42193 ; WX 733 ; N uniA4D1 ; G 4465
+U 42194 ; WX 733 ; N uniA4D2 ; G 4466
+U 42195 ; WX 830 ; N uniA4D3 ; G 4467
+U 42196 ; WX 682 ; N uniA4D4 ; G 4468
+U 42197 ; WX 682 ; N uniA4D5 ; G 4469
+U 42198 ; WX 821 ; N uniA4D6 ; G 4470
+U 42199 ; WX 775 ; N uniA4D7 ; G 4471
+U 42200 ; WX 775 ; N uniA4D8 ; G 4472
+U 42201 ; WX 530 ; N uniA4D9 ; G 4473
+U 42202 ; WX 734 ; N uniA4DA ; G 4474
+U 42203 ; WX 734 ; N uniA4DB ; G 4475
+U 42204 ; WX 725 ; N uniA4DC ; G 4476
+U 42205 ; WX 683 ; N uniA4DD ; G 4477
+U 42206 ; WX 683 ; N uniA4DE ; G 4478
+U 42207 ; WX 995 ; N uniA4DF ; G 4479
+U 42208 ; WX 837 ; N uniA4E0 ; G 4480
+U 42209 ; WX 637 ; N uniA4E1 ; G 4481
+U 42210 ; WX 720 ; N uniA4E2 ; G 4482
+U 42211 ; WX 770 ; N uniA4E3 ; G 4483
+U 42212 ; WX 770 ; N uniA4E4 ; G 4484
+U 42213 ; WX 774 ; N uniA4E5 ; G 4485
+U 42214 ; WX 774 ; N uniA4E6 ; G 4486
+U 42215 ; WX 837 ; N uniA4E7 ; G 4487
+U 42216 ; WX 786 ; N uniA4E8 ; G 4488
+U 42217 ; WX 530 ; N uniA4E9 ; G 4489
+U 42218 ; WX 1103 ; N uniA4EA ; G 4490
+U 42219 ; WX 771 ; N uniA4EB ; G 4491
+U 42220 ; WX 724 ; N uniA4EC ; G 4492
+U 42221 ; WX 762 ; N uniA4ED ; G 4493
+U 42222 ; WX 774 ; N uniA4EE ; G 4494
+U 42223 ; WX 774 ; N uniA4EF ; G 4495
+U 42224 ; WX 683 ; N uniA4F0 ; G 4496
+U 42225 ; WX 683 ; N uniA4F1 ; G 4497
+U 42226 ; WX 372 ; N uniA4F2 ; G 4498
+U 42227 ; WX 850 ; N uniA4F3 ; G 4499
+U 42228 ; WX 812 ; N uniA4F4 ; G 4500
+U 42229 ; WX 812 ; N uniA4F5 ; G 4501
+U 42230 ; WX 576 ; N uniA4F6 ; G 4502
+U 42231 ; WX 830 ; N uniA4F7 ; G 4503
+U 42232 ; WX 322 ; N uniA4F8 ; G 4504
+U 42233 ; WX 322 ; N uniA4F9 ; G 4505
+U 42234 ; WX 674 ; N uniA4FA ; G 4506
+U 42235 ; WX 674 ; N uniA4FB ; G 4507
+U 42236 ; WX 322 ; N uniA4FC ; G 4508
+U 42237 ; WX 322 ; N uniA4FD ; G 4509
+U 42238 ; WX 588 ; N uniA4FE ; G 4510
+U 42239 ; WX 588 ; N uniA4FF ; G 4511
+U 42564 ; WX 720 ; N uniA644 ; G 4512
+U 42565 ; WX 595 ; N uniA645 ; G 4513
+U 42566 ; WX 436 ; N uniA646 ; G 4514
+U 42567 ; WX 440 ; N uniA647 ; G 4515
+U 42572 ; WX 1405 ; N uniA64C ; G 4516
+U 42573 ; WX 1173 ; N uniA64D ; G 4517
+U 42576 ; WX 1234 ; N uniA650 ; G 4518
+U 42577 ; WX 1027 ; N uniA651 ; G 4519
+U 42580 ; WX 1174 ; N uniA654 ; G 4520
+U 42581 ; WX 972 ; N uniA655 ; G 4521
+U 42582 ; WX 1100 ; N uniA656 ; G 4522
+U 42583 ; WX 969 ; N uniA657 ; G 4523
+U 42594 ; WX 1100 ; N uniA662 ; G 4524
+U 42595 ; WX 940 ; N uniA663 ; G 4525
+U 42596 ; WX 1096 ; N uniA664 ; G 4526
+U 42597 ; WX 915 ; N uniA665 ; G 4527
+U 42598 ; WX 1260 ; N uniA666 ; G 4528
+U 42599 ; WX 997 ; N uniA667 ; G 4529
+U 42600 ; WX 850 ; N uniA668 ; G 4530
+U 42601 ; WX 687 ; N uniA669 ; G 4531
+U 42602 ; WX 1037 ; N uniA66A ; G 4532
+U 42603 ; WX 868 ; N uniA66B ; G 4533
+U 42604 ; WX 1406 ; N uniA66C ; G 4534
+U 42605 ; WX 1106 ; N uniA66D ; G 4535
+U 42606 ; WX 961 ; N uniA66E ; G 4536
+U 42634 ; WX 944 ; N uniA68A ; G 4537
+U 42635 ; WX 749 ; N uniA68B ; G 4538
+U 42636 ; WX 682 ; N uniA68C ; G 4539
+U 42637 ; WX 580 ; N uniA68D ; G 4540
+U 42644 ; WX 808 ; N uniA694 ; G 4541
+U 42645 ; WX 712 ; N uniA695 ; G 4542
+U 42648 ; WX 1406 ; N uniA698 ; G 4543
+U 42649 ; WX 1106 ; N uniA699 ; G 4544
+U 42760 ; WX 500 ; N uniA708 ; G 4545
+U 42761 ; WX 500 ; N uniA709 ; G 4546
+U 42762 ; WX 500 ; N uniA70A ; G 4547
+U 42763 ; WX 500 ; N uniA70B ; G 4548
+U 42764 ; WX 500 ; N uniA70C ; G 4549
+U 42765 ; WX 500 ; N uniA70D ; G 4550
+U 42766 ; WX 500 ; N uniA70E ; G 4551
+U 42767 ; WX 500 ; N uniA70F ; G 4552
+U 42768 ; WX 500 ; N uniA710 ; G 4553
+U 42769 ; WX 500 ; N uniA711 ; G 4554
+U 42770 ; WX 500 ; N uniA712 ; G 4555
+U 42771 ; WX 500 ; N uniA713 ; G 4556
+U 42772 ; WX 500 ; N uniA714 ; G 4557
+U 42773 ; WX 500 ; N uniA715 ; G 4558
+U 42774 ; WX 500 ; N uniA716 ; G 4559
+U 42779 ; WX 400 ; N uniA71B ; G 4560
+U 42780 ; WX 400 ; N uniA71C ; G 4561
+U 42781 ; WX 287 ; N uniA71D ; G 4562
+U 42782 ; WX 287 ; N uniA71E ; G 4563
+U 42783 ; WX 287 ; N uniA71F ; G 4564
+U 42786 ; WX 444 ; N uniA722 ; G 4565
+U 42787 ; WX 390 ; N uniA723 ; G 4566
+U 42788 ; WX 540 ; N uniA724 ; G 4567
+U 42789 ; WX 540 ; N uniA725 ; G 4568
+U 42790 ; WX 837 ; N uniA726 ; G 4569
+U 42791 ; WX 712 ; N uniA727 ; G 4570
+U 42792 ; WX 1031 ; N uniA728 ; G 4571
+U 42793 ; WX 857 ; N uniA729 ; G 4572
+U 42794 ; WX 696 ; N uniA72A ; G 4573
+U 42795 ; WX 557 ; N uniA72B ; G 4574
+U 42800 ; WX 559 ; N uniA730 ; G 4575
+U 42801 ; WX 595 ; N uniA731 ; G 4576
+U 42802 ; WX 1349 ; N uniA732 ; G 4577
+U 42803 ; WX 1052 ; N uniA733 ; G 4578
+U 42804 ; WX 1285 ; N uniA734 ; G 4579
+U 42805 ; WX 1065 ; N uniA735 ; G 4580
+U 42806 ; WX 1245 ; N uniA736 ; G 4581
+U 42807 ; WX 1052 ; N uniA737 ; G 4582
+U 42808 ; WX 1079 ; N uniA738 ; G 4583
+U 42809 ; WX 922 ; N uniA739 ; G 4584
+U 42810 ; WX 1079 ; N uniA73A ; G 4585
+U 42811 ; WX 922 ; N uniA73B ; G 4586
+U 42812 ; WX 1035 ; N uniA73C ; G 4587
+U 42813 ; WX 922 ; N uniA73D ; G 4588
+U 42814 ; WX 698 ; N uniA73E ; G 4589
+U 42815 ; WX 549 ; N uniA73F ; G 4590
+U 42816 ; WX 656 ; N uniA740 ; G 4591
+U 42817 ; WX 579 ; N uniA741 ; G 4592
+U 42822 ; WX 850 ; N uniA746 ; G 4593
+U 42823 ; WX 542 ; N uniA747 ; G 4594
+U 42824 ; WX 683 ; N uniA748 ; G 4595
+U 42825 ; WX 531 ; N uniA749 ; G 4596
+U 42826 ; WX 918 ; N uniA74A ; G 4597
+U 42827 ; WX 814 ; N uniA74B ; G 4598
+U 42830 ; WX 1406 ; N uniA74E ; G 4599
+U 42831 ; WX 1106 ; N uniA74F ; G 4600
+U 42832 ; WX 733 ; N uniA750 ; G 4601
+U 42833 ; WX 716 ; N uniA751 ; G 4602
+U 42834 ; WX 948 ; N uniA752 ; G 4603
+U 42835 ; WX 937 ; N uniA753 ; G 4604
+U 42838 ; WX 850 ; N uniA756 ; G 4605
+U 42839 ; WX 716 ; N uniA757 ; G 4606
+U 42852 ; WX 738 ; N uniA764 ; G 4607
+U 42853 ; WX 716 ; N uniA765 ; G 4608
+U 42854 ; WX 738 ; N uniA766 ; G 4609
+U 42855 ; WX 716 ; N uniA767 ; G 4610
+U 42880 ; WX 637 ; N uniA780 ; G 4611
+U 42881 ; WX 343 ; N uniA781 ; G 4612
+U 42882 ; WX 837 ; N uniA782 ; G 4613
+U 42883 ; WX 712 ; N uniA783 ; G 4614
+U 42889 ; WX 400 ; N uniA789 ; G 4615
+U 42890 ; WX 396 ; N uniA78A ; G 4616
+U 42891 ; WX 456 ; N uniA78B ; G 4617
+U 42892 ; WX 306 ; N uniA78C ; G 4618
+U 42893 ; WX 808 ; N uniA78D ; G 4619
+U 42894 ; WX 693 ; N uniA78E ; G 4620
+U 42896 ; WX 928 ; N uniA790 ; G 4621
+U 42897 ; WX 768 ; N uniA791 ; G 4622
+U 42912 ; WX 821 ; N uniA7A0 ; G 4623
+U 42913 ; WX 716 ; N uniA7A1 ; G 4624
+U 42914 ; WX 775 ; N uniA7A2 ; G 4625
+U 42915 ; WX 665 ; N uniA7A3 ; G 4626
+U 42916 ; WX 837 ; N uniA7A4 ; G 4627
+U 42917 ; WX 712 ; N uniA7A5 ; G 4628
+U 42918 ; WX 770 ; N uniA7A6 ; G 4629
+U 42919 ; WX 493 ; N uniA7A7 ; G 4630
+U 42920 ; WX 720 ; N uniA7A8 ; G 4631
+U 42921 ; WX 595 ; N uniA7A9 ; G 4632
+U 42922 ; WX 886 ; N uniA7AA ; G 4633
+U 43000 ; WX 613 ; N uniA7F8 ; G 4634
+U 43001 ; WX 689 ; N uniA7F9 ; G 4635
+U 43002 ; WX 1062 ; N uniA7FA ; G 4636
+U 43003 ; WX 683 ; N uniA7FB ; G 4637
+U 43004 ; WX 733 ; N uniA7FC ; G 4638
+U 43005 ; WX 995 ; N uniA7FD ; G 4639
+U 43006 ; WX 372 ; N uniA7FE ; G 4640
+U 43007 ; WX 1325 ; N uniA7FF ; G 4641
+U 61184 ; WX 216 ; N uni02E5.5 ; G 4642
+U 61185 ; WX 242 ; N uni02E6.5 ; G 4643
+U 61186 ; WX 267 ; N uni02E7.5 ; G 4644
+U 61187 ; WX 277 ; N uni02E8.5 ; G 4645
+U 61188 ; WX 282 ; N uni02E9.5 ; G 4646
+U 61189 ; WX 242 ; N uni02E5.4 ; G 4647
+U 61190 ; WX 216 ; N uni02E6.4 ; G 4648
+U 61191 ; WX 242 ; N uni02E7.4 ; G 4649
+U 61192 ; WX 267 ; N uni02E8.4 ; G 4650
+U 61193 ; WX 277 ; N uni02E9.4 ; G 4651
+U 61194 ; WX 267 ; N uni02E5.3 ; G 4652
+U 61195 ; WX 242 ; N uni02E6.3 ; G 4653
+U 61196 ; WX 216 ; N uni02E7.3 ; G 4654
+U 61197 ; WX 242 ; N uni02E8.3 ; G 4655
+U 61198 ; WX 267 ; N uni02E9.3 ; G 4656
+U 61199 ; WX 277 ; N uni02E5.2 ; G 4657
+U 61200 ; WX 267 ; N uni02E6.2 ; G 4658
+U 61201 ; WX 242 ; N uni02E7.2 ; G 4659
+U 61202 ; WX 216 ; N uni02E8.2 ; G 4660
+U 61203 ; WX 242 ; N uni02E9.2 ; G 4661
+U 61204 ; WX 282 ; N uni02E5.1 ; G 4662
+U 61205 ; WX 277 ; N uni02E6.1 ; G 4663
+U 61206 ; WX 267 ; N uni02E7.1 ; G 4664
+U 61207 ; WX 242 ; N uni02E8.1 ; G 4665
+U 61208 ; WX 216 ; N uni02E9.1 ; G 4666
+U 61209 ; WX 282 ; N stem ; G 4667
+U 62464 ; WX 612 ; N uniF400 ; G 4668
+U 62465 ; WX 612 ; N uniF401 ; G 4669
+U 62466 ; WX 653 ; N uniF402 ; G 4670
+U 62467 ; WX 902 ; N uniF403 ; G 4671
+U 62468 ; WX 617 ; N uniF404 ; G 4672
+U 62469 ; WX 617 ; N uniF405 ; G 4673
+U 62470 ; WX 680 ; N uniF406 ; G 4674
+U 62471 ; WX 904 ; N uniF407 ; G 4675
+U 62472 ; WX 599 ; N uniF408 ; G 4676
+U 62473 ; WX 617 ; N uniF409 ; G 4677
+U 62474 ; WX 1163 ; N uniF40A ; G 4678
+U 62475 ; WX 621 ; N uniF40B ; G 4679
+U 62476 ; WX 622 ; N uniF40C ; G 4680
+U 62477 ; WX 893 ; N uniF40D ; G 4681
+U 62478 ; WX 612 ; N uniF40E ; G 4682
+U 62479 ; WX 622 ; N uniF40F ; G 4683
+U 62480 ; WX 924 ; N uniF410 ; G 4684
+U 62481 ; WX 622 ; N uniF411 ; G 4685
+U 62482 ; WX 754 ; N uniF412 ; G 4686
+U 62483 ; WX 624 ; N uniF413 ; G 4687
+U 62484 ; WX 886 ; N uniF414 ; G 4688
+U 62485 ; WX 622 ; N uniF415 ; G 4689
+U 62486 ; WX 907 ; N uniF416 ; G 4690
+U 62487 ; WX 621 ; N uniF417 ; G 4691
+U 62488 ; WX 611 ; N uniF418 ; G 4692
+U 62489 ; WX 624 ; N uniF419 ; G 4693
+U 62490 ; WX 677 ; N uniF41A ; G 4694
+U 62491 ; WX 621 ; N uniF41B ; G 4695
+U 62492 ; WX 611 ; N uniF41C ; G 4696
+U 62493 ; WX 630 ; N uniF41D ; G 4697
+U 62494 ; WX 622 ; N uniF41E ; G 4698
+U 62495 ; WX 561 ; N uniF41F ; G 4699
+U 62496 ; WX 612 ; N uniF420 ; G 4700
+U 62497 ; WX 626 ; N uniF421 ; G 4701
+U 62498 ; WX 612 ; N uniF422 ; G 4702
+U 62499 ; WX 611 ; N uniF423 ; G 4703
+U 62500 ; WX 618 ; N uniF424 ; G 4704
+U 62501 ; WX 667 ; N uniF425 ; G 4705
+U 62502 ; WX 963 ; N uniF426 ; G 4706
+U 62504 ; WX 1023 ; N uniF428 ; G 4707
+U 62505 ; WX 844 ; N uniF429 ; G 4708
+U 62506 ; WX 563 ; N uniF42A ; G 4709
+U 62507 ; WX 563 ; N uniF42B ; G 4710
+U 62508 ; WX 563 ; N uniF42C ; G 4711
+U 62509 ; WX 563 ; N uniF42D ; G 4712
+U 62510 ; WX 563 ; N uniF42E ; G 4713
+U 62511 ; WX 563 ; N uniF42F ; G 4714
+U 62512 ; WX 555 ; N uniF430 ; G 4715
+U 62513 ; WX 555 ; N uniF431 ; G 4716
+U 62514 ; WX 555 ; N uniF432 ; G 4717
+U 62515 ; WX 555 ; N uniF433 ; G 4718
+U 62516 ; WX 573 ; N uniF434 ; G 4719
+U 62517 ; WX 573 ; N uniF435 ; G 4720
+U 62518 ; WX 573 ; N uniF436 ; G 4721
+U 62519 ; WX 824 ; N uniF437 ; G 4722
+U 62520 ; WX 824 ; N uniF438 ; G 4723
+U 62521 ; WX 824 ; N uniF439 ; G 4724
+U 62522 ; WX 824 ; N uniF43A ; G 4725
+U 62523 ; WX 824 ; N uniF43B ; G 4726
+U 62524 ; WX 611 ; N uniF43C ; G 4727
+U 62525 ; WX 611 ; N uniF43D ; G 4728
+U 62526 ; WX 611 ; N uniF43E ; G 4729
+U 62527 ; WX 611 ; N uniF43F ; G 4730
+U 62528 ; WX 611 ; N uniF440 ; G 4731
+U 62529 ; WX 611 ; N uniF441 ; G 4732
+U 62917 ; WX 687 ; N uniF5C5 ; G 4733
+U 64256 ; WX 833 ; N uniFB00 ; G 4734
+U 64257 ; WX 787 ; N fi ; G 4735
+U 64258 ; WX 787 ; N fl ; G 4736
+U 64259 ; WX 1138 ; N uniFB03 ; G 4737
+U 64260 ; WX 1139 ; N uniFB04 ; G 4738
+U 64261 ; WX 808 ; N uniFB05 ; G 4739
+U 64262 ; WX 1020 ; N uniFB06 ; G 4740
+U 64275 ; WX 1388 ; N uniFB13 ; G 4741
+U 64276 ; WX 1384 ; N uniFB14 ; G 4742
+U 64277 ; WX 1378 ; N uniFB15 ; G 4743
+U 64278 ; WX 1384 ; N uniFB16 ; G 4744
+U 64279 ; WX 1713 ; N uniFB17 ; G 4745
+U 64285 ; WX 294 ; N uniFB1D ; G 4746
+U 64286 ; WX 0 ; N uniFB1E ; G 4747
+U 64287 ; WX 663 ; N uniFB1F ; G 4748
+U 64288 ; WX 665 ; N uniFB20 ; G 4749
+U 64289 ; WX 939 ; N uniFB21 ; G 4750
+U 64290 ; WX 788 ; N uniFB22 ; G 4751
+U 64291 ; WX 920 ; N uniFB23 ; G 4752
+U 64292 ; WX 786 ; N uniFB24 ; G 4753
+U 64293 ; WX 857 ; N uniFB25 ; G 4754
+U 64294 ; WX 869 ; N uniFB26 ; G 4755
+U 64295 ; WX 821 ; N uniFB27 ; G 4756
+U 64296 ; WX 890 ; N uniFB28 ; G 4757
+U 64297 ; WX 838 ; N uniFB29 ; G 4758
+U 64298 ; WX 749 ; N uniFB2A ; G 4759
+U 64299 ; WX 749 ; N uniFB2B ; G 4760
+U 64300 ; WX 749 ; N uniFB2C ; G 4761
+U 64301 ; WX 749 ; N uniFB2D ; G 4762
+U 64302 ; WX 728 ; N uniFB2E ; G 4763
+U 64303 ; WX 728 ; N uniFB2F ; G 4764
+U 64304 ; WX 728 ; N uniFB30 ; G 4765
+U 64305 ; WX 610 ; N uniFB31 ; G 4766
+U 64306 ; WX 447 ; N uniFB32 ; G 4767
+U 64307 ; WX 588 ; N uniFB33 ; G 4768
+U 64308 ; WX 687 ; N uniFB34 ; G 4769
+U 64309 ; WX 343 ; N uniFB35 ; G 4770
+U 64310 ; WX 400 ; N uniFB36 ; G 4771
+U 64311 ; WX 1000 ; N uniFB37 ; G 4772
+U 64312 ; WX 679 ; N uniFB38 ; G 4773
+U 64313 ; WX 436 ; N uniFB39 ; G 4774
+U 64314 ; WX 578 ; N uniFB3A ; G 4775
+U 64315 ; WX 566 ; N uniFB3B ; G 4776
+U 64316 ; WX 605 ; N uniFB3C ; G 4777
+U 64317 ; WX 1000 ; N uniFB3D ; G 4778
+U 64318 ; WX 724 ; N uniFB3E ; G 4779
+U 64319 ; WX 1000 ; N uniFB3F ; G 4780
+U 64320 ; WX 453 ; N uniFB40 ; G 4781
+U 64321 ; WX 680 ; N uniFB41 ; G 4782
+U 64322 ; WX 1000 ; N uniFB42 ; G 4783
+U 64323 ; WX 675 ; N uniFB43 ; G 4784
+U 64324 ; WX 658 ; N uniFB44 ; G 4785
+U 64325 ; WX 1000 ; N uniFB45 ; G 4786
+U 64326 ; WX 653 ; N uniFB46 ; G 4787
+U 64327 ; WX 736 ; N uniFB47 ; G 4788
+U 64328 ; WX 602 ; N uniFB48 ; G 4789
+U 64329 ; WX 749 ; N uniFB49 ; G 4790
+U 64330 ; WX 683 ; N uniFB4A ; G 4791
+U 64331 ; WX 343 ; N uniFB4B ; G 4792
+U 64332 ; WX 610 ; N uniFB4C ; G 4793
+U 64333 ; WX 566 ; N uniFB4D ; G 4794
+U 64334 ; WX 658 ; N uniFB4E ; G 4795
+U 64335 ; WX 710 ; N uniFB4F ; G 4796
+U 65024 ; WX 0 ; N uniFE00 ; G 4797
+U 65025 ; WX 0 ; N uniFE01 ; G 4798
+U 65026 ; WX 0 ; N uniFE02 ; G 4799
+U 65027 ; WX 0 ; N uniFE03 ; G 4800
+U 65028 ; WX 0 ; N uniFE04 ; G 4801
+U 65029 ; WX 0 ; N uniFE05 ; G 4802
+U 65030 ; WX 0 ; N uniFE06 ; G 4803
+U 65031 ; WX 0 ; N uniFE07 ; G 4804
+U 65032 ; WX 0 ; N uniFE08 ; G 4805
+U 65033 ; WX 0 ; N uniFE09 ; G 4806
+U 65034 ; WX 0 ; N uniFE0A ; G 4807
+U 65035 ; WX 0 ; N uniFE0B ; G 4808
+U 65036 ; WX 0 ; N uniFE0C ; G 4809
+U 65037 ; WX 0 ; N uniFE0D ; G 4810
+U 65038 ; WX 0 ; N uniFE0E ; G 4811
+U 65039 ; WX 0 ; N uniFE0F ; G 4812
+U 65056 ; WX 0 ; N uniFE20 ; G 4813
+U 65057 ; WX 0 ; N uniFE21 ; G 4814
+U 65058 ; WX 0 ; N uniFE22 ; G 4815
+U 65059 ; WX 0 ; N uniFE23 ; G 4816
+U 65529 ; WX 0 ; N uniFFF9 ; G 4817
+U 65530 ; WX 0 ; N uniFFFA ; G 4818
+U 65531 ; WX 0 ; N uniFFFB ; G 4819
+U 65532 ; WX 0 ; N uniFFFC ; G 4820
+U 65533 ; WX 1113 ; N uniFFFD ; G 4821
+EndCharMetrics
+StartKernData
+StartKernPairs 1921
+
+KPX dollar ampersand -63
+KPX dollar two -63
+KPX dollar seven -196
+KPX dollar eight -92
+KPX dollar nine -139
+KPX dollar colon -112
+KPX dollar less -235
+KPX dollar F -63
+KPX dollar G -63
+KPX dollar W -112
+KPX dollar Y -112
+KPX dollar Z -92
+KPX dollar backslash -149
+KPX dollar copyright -63
+KPX dollar questiondown -149
+KPX dollar Aacute -149
+KPX dollar Egrave -63
+KPX dollar Eacute -63
+KPX dollar Ecircumflex -63
+KPX dollar Edieresis -63
+KPX dollar Igrave -63
+KPX dollar Iacute -63
+KPX dollar Icircumflex -63
+KPX dollar Idieresis -63
+KPX dollar Ntilde -63
+KPX dollar Oacute -63
+KPX dollar Dcaron -63
+KPX dollar Dcroat -63
+KPX dollar Emacron -63
+KPX dollar Ebreve -63
+KPX dollar Hcircumflex -196
+KPX dollar hcircumflex -112
+KPX dollar Hbar -196
+KPX dollar hbar -112
+KPX dollar Imacron -92
+KPX dollar Ibreve -92
+KPX dollar Iogonek -92
+KPX dollar Idot -92
+KPX dollar IJ -92
+KPX dollar Jcircumflex -92
+KPX dollar Kcommaaccent -112
+KPX dollar kcommaaccent -92
+KPX dollar kgreenlandic -235
+KPX dollar Lacute -149
+KPX dollar lacute -235
+KPX dollar uni01AC -63
+KPX dollar uni01AE -63
+KPX dollar uni01DC -196
+KPX dollar uni01DD -112
+KPX dollar uni01F0 -63
+KPX dollar uni01F4 -235
+KPX dollar uni01F5 -149
+
+KPX percent nine -83
+KPX percent colon -112
+KPX percent less -112
+KPX percent Kcommaaccent -112
+KPX percent kgreenlandic -112
+KPX percent lacute -112
+KPX percent uni01F4 -112
+
+KPX ampersand six -73
+KPX ampersand Gcircumflex -73
+KPX ampersand Gbreve -73
+KPX ampersand Gdotaccent -73
+KPX ampersand Gcommaaccent -73
+KPX ampersand uni01DA -73
+
+KPX quotesingle less -159
+KPX quotesingle kgreenlandic -159
+KPX quotesingle lacute -159
+KPX quotesingle uni01F4 -159
+
+KPX parenright dollar -264
+KPX parenright D -235
+KPX parenright H -159
+KPX parenright R -159
+KPX parenright U -225
+KPX parenright X -196
+KPX parenright backslash -188
+KPX parenright cent -235
+KPX parenright sterling -235
+KPX parenright currency -235
+KPX parenright yen -235
+KPX parenright brokenbar -235
+KPX parenright section -235
+KPX parenright dieresis -235
+KPX parenright ordfeminine -159
+KPX parenright guillemotleft -159
+KPX parenright logicalnot -159
+KPX parenright sfthyphen -159
+KPX parenright acute -159
+KPX parenright mu -159
+KPX parenright paragraph -159
+KPX parenright periodcentered -159
+KPX parenright cedilla -159
+KPX parenright ordmasculine -159
+KPX parenright guillemotright -196
+KPX parenright onequarter -196
+KPX parenright onehalf -196
+KPX parenright threequarters -196
+KPX parenright questiondown -188
+KPX parenright Aacute -188
+KPX parenright Acircumflex -264
+KPX parenright Atilde -235
+KPX parenright Adieresis -264
+KPX parenright Aring -235
+KPX parenright AE -264
+KPX parenright Ccedilla -235
+KPX parenright Otilde -159
+KPX parenright multiply -159
+KPX parenright Ugrave -159
+KPX parenright Ucircumflex -159
+KPX parenright Yacute -159
+KPX parenright dcaron -159
+KPX parenright dmacron -159
+KPX parenright emacron -159
+KPX parenright ebreve -159
+KPX parenright edotaccent -225
+KPX parenright eogonek -225
+KPX parenright ecaron -225
+KPX parenright imacron -196
+KPX parenright ibreve -196
+KPX parenright iogonek -196
+KPX parenright dotlessi -196
+KPX parenright ij -196
+KPX parenright jcircumflex -196
+KPX parenright Lacute -188
+KPX parenright uni01A5 -235
+KPX parenright uni01AD -159
+KPX parenright Uhorn -159
+KPX parenright uni01F1 -159
+KPX parenright uni01F5 -188
+
+KPX asterisk seven -36
+KPX asterisk less -83
+KPX asterisk Hbar -36
+KPX asterisk lacute -83
+
+KPX period ampersand -131
+KPX period two -131
+KPX period eight -73
+KPX period colon -55
+KPX period H -112
+KPX period R -112
+KPX period X -112
+KPX period backslash -206
+KPX period ordfeminine -112
+KPX period guillemotleft -112
+KPX period logicalnot -112
+KPX period sfthyphen -112
+KPX period acute -112
+KPX period mu -112
+KPX period paragraph -112
+KPX period periodcentered -112
+KPX period cedilla -112
+KPX period ordmasculine -112
+KPX period guillemotright -112
+KPX period onequarter -112
+KPX period onehalf -112
+KPX period threequarters -112
+KPX period questiondown -206
+KPX period Aacute -206
+KPX period Egrave -131
+KPX period Icircumflex -131
+KPX period Yacute -112
+KPX period Ebreve -178
+KPX period ebreve -112
+KPX period Idot -73
+KPX period dotlessi -112
+
+KPX slash two -73
+KPX slash seven -339
+KPX slash eight -112
+KPX slash nine -282
+KPX slash colon -178
+KPX slash less -319
+KPX slash backslash -253
+KPX slash questiondown -253
+KPX slash Aacute -253
+KPX slash Ebreve -73
+KPX slash Hbar -339
+KPX slash Idot -112
+KPX slash lacute -319
+
+KPX two nine -73
+KPX two semicolon -73
+KPX two less -149
+KPX two lacute -149
+
+KPX three dollar -188
+KPX three D -131
+KPX three H -55
+KPX three U -63
+KPX three V -73
+KPX three X -73
+KPX three cent -131
+KPX three sterling -131
+KPX three currency -131
+KPX three yen -131
+KPX three brokenbar -131
+KPX three section -131
+KPX three dieresis -131
+KPX three ordfeminine -55
+KPX three guillemotleft -55
+KPX three logicalnot -55
+KPX three sfthyphen -55
+KPX three guillemotright -73
+KPX three onequarter -73
+KPX three onehalf -73
+KPX three threequarters -73
+KPX three Yacute -55
+KPX three edotaccent -63
+KPX three ecaron -63
+KPX three gdotaccent -73
+KPX three gcommaaccent -73
+KPX three dotlessi -73
+
+
+KPX five seven -92
+KPX five less -188
+KPX five H -102
+KPX five R -102
+KPX five X -112
+KPX five backslash -131
+KPX five ordfeminine -102
+KPX five guillemotleft -102
+KPX five logicalnot -102
+KPX five sfthyphen -102
+KPX five acute -102
+KPX five mu -102
+KPX five paragraph -102
+KPX five periodcentered -102
+KPX five cedilla -102
+KPX five ordmasculine -102
+KPX five guillemotright -112
+KPX five onequarter -112
+KPX five onehalf -112
+KPX five threequarters -112
+KPX five questiondown -131
+KPX five Aacute -131
+KPX five Yacute -102
+KPX five ebreve -102
+KPX five Hbar -92
+KPX five dotlessi -112
+KPX five lacute -188
+
+KPX six six -73
+KPX six Gdotaccent -73
+KPX six Gcommaaccent -73
+
+KPX seven dollar -159
+KPX seven seven 47
+KPX seven D -243
+KPX seven F -264
+KPX seven H -264
+KPX seven R -264
+KPX seven U -225
+KPX seven V -243
+KPX seven X -264
+KPX seven Z -282
+KPX seven backslash -339
+KPX seven cent -243
+KPX seven sterling -243
+KPX seven currency -243
+KPX seven yen -243
+KPX seven brokenbar -243
+KPX seven section -243
+KPX seven dieresis -243
+KPX seven copyright -264
+KPX seven ordfeminine -264
+KPX seven guillemotleft -264
+KPX seven logicalnot -264
+KPX seven sfthyphen -264
+KPX seven acute -264
+KPX seven mu -264
+KPX seven paragraph -264
+KPX seven periodcentered -264
+KPX seven cedilla -264
+KPX seven ordmasculine -264
+KPX seven guillemotright -264
+KPX seven onequarter -264
+KPX seven onehalf -264
+KPX seven threequarters -264
+KPX seven questiondown -339
+KPX seven Aacute -339
+KPX seven Eacute -264
+KPX seven Idieresis -264
+KPX seven Yacute -264
+KPX seven ebreve -264
+KPX seven edotaccent -225
+KPX seven ecaron -225
+KPX seven gdotaccent -243
+KPX seven gcommaaccent -243
+KPX seven Hbar 47
+KPX seven dotlessi -264
+
+KPX eight dollar -92
+
+KPX nine dollar -139
+KPX nine two -36
+KPX nine D -159
+KPX nine H -149
+KPX nine L -36
+KPX nine R -149
+KPX nine X -149
+KPX nine cent -159
+KPX nine sterling -159
+KPX nine currency -159
+KPX nine yen -159
+KPX nine brokenbar -159
+KPX nine section -159
+KPX nine dieresis -159
+KPX nine ordfeminine -149
+KPX nine guillemotleft -149
+KPX nine logicalnot -149
+KPX nine sfthyphen -149
+KPX nine acute -149
+KPX nine mu -149
+KPX nine paragraph -149
+KPX nine periodcentered -149
+KPX nine cedilla -149
+KPX nine ordmasculine -149
+KPX nine guillemotright -149
+KPX nine onequarter -149
+KPX nine onehalf -149
+KPX nine threequarters -149
+KPX nine Yacute -149
+KPX nine Ebreve -45
+KPX nine ebreve -149
+KPX nine dotlessi -149
+
+KPX colon dollar -73
+KPX colon D -139
+KPX colon H -131
+KPX colon R -112
+KPX colon U -120
+KPX colon cent -139
+KPX colon sterling -139
+KPX colon currency -139
+KPX colon yen -139
+KPX colon brokenbar -139
+KPX colon section -139
+KPX colon dieresis -139
+KPX colon ordfeminine -131
+KPX colon guillemotleft -131
+KPX colon logicalnot -131
+KPX colon sfthyphen -131
+KPX colon acute -112
+KPX colon mu -112
+KPX colon paragraph -112
+KPX colon periodcentered -112
+KPX colon cedilla -112
+KPX colon ordmasculine -112
+KPX colon Yacute -131
+KPX colon ebreve -112
+KPX colon edotaccent -120
+KPX colon ecaron -120
+
+KPX semicolon ampersand -73
+KPX semicolon two -73
+KPX semicolon H -131
+KPX semicolon ordfeminine -131
+KPX semicolon guillemotleft -131
+KPX semicolon logicalnot -131
+KPX semicolon sfthyphen -131
+KPX semicolon Egrave -73
+KPX semicolon Icircumflex -73
+KPX semicolon Yacute -131
+KPX semicolon Ebreve -112
+
+KPX less dollar -196
+KPX less ampersand -73
+KPX less two -73
+KPX less D -243
+KPX less H -264
+KPX less R -264
+KPX less X -225
+KPX less cent -243
+KPX less sterling -243
+KPX less currency -243
+KPX less yen -243
+KPX less brokenbar -243
+KPX less section -243
+KPX less dieresis -243
+KPX less ordfeminine -264
+KPX less guillemotleft -264
+KPX less logicalnot -264
+KPX less sfthyphen -264
+KPX less acute -264
+KPX less mu -264
+KPX less paragraph -264
+KPX less periodcentered -264
+KPX less cedilla -264
+KPX less ordmasculine -264
+KPX less guillemotright -225
+KPX less onequarter -225
+KPX less onehalf -225
+KPX less threequarters -225
+KPX less Egrave -73
+KPX less Icircumflex -73
+KPX less Yacute -264
+KPX less Ebreve -120
+KPX less ebreve -264
+KPX less dotlessi -225
+
+
+KPX D backslash -63
+KPX D questiondown -63
+KPX D Aacute -63
+
+
+KPX N H -73
+KPX N R -73
+KPX N ordfeminine -73
+KPX N guillemotleft -73
+KPX N logicalnot -73
+KPX N sfthyphen -73
+KPX N acute -73
+KPX N mu -73
+KPX N paragraph -73
+KPX N periodcentered -73
+KPX N cedilla -73
+KPX N ordmasculine -45
+KPX N Yacute -73
+KPX N ebreve -73
+
+
+
+
+
+KPX cent backslash -63
+KPX cent questiondown -63
+KPX cent Aacute -63
+
+KPX sterling backslash -63
+KPX sterling questiondown -63
+KPX sterling Aacute -63
+
+KPX currency backslash -63
+KPX currency questiondown -63
+KPX currency Aacute -63
+
+KPX yen backslash -63
+KPX yen questiondown -63
+KPX yen Aacute -63
+
+KPX brokenbar backslash -63
+KPX brokenbar questiondown -63
+KPX brokenbar Aacute -63
+
+KPX section backslash -63
+KPX section questiondown -63
+KPX section Aacute -63
+
+
+
+KPX Acircumflex ampersand -63
+KPX Acircumflex two -63
+KPX Acircumflex seven -196
+KPX Acircumflex eight -92
+KPX Acircumflex nine -139
+KPX Acircumflex colon -112
+KPX Acircumflex less -235
+KPX Acircumflex F -63
+KPX Acircumflex G -63
+KPX Acircumflex W -112
+KPX Acircumflex Y -112
+KPX Acircumflex Z -92
+KPX Acircumflex backslash -149
+KPX Acircumflex copyright -63
+KPX Acircumflex questiondown -149
+KPX Acircumflex Aacute -149
+KPX Acircumflex Egrave -63
+KPX Acircumflex Eacute -63
+KPX Acircumflex Ecircumflex -63
+KPX Acircumflex Edieresis -63
+KPX Acircumflex Igrave -63
+KPX Acircumflex Iacute -63
+KPX Acircumflex Icircumflex -63
+KPX Acircumflex Idieresis -63
+KPX Acircumflex Ntilde -63
+KPX Acircumflex Oacute -63
+KPX Acircumflex Dcaron -63
+KPX Acircumflex Dcroat -63
+KPX Acircumflex Emacron -63
+KPX Acircumflex Ebreve -63
+KPX Acircumflex Hcircumflex -196
+KPX Acircumflex hcircumflex -112
+KPX Acircumflex Hbar -196
+KPX Acircumflex hbar -112
+KPX Acircumflex Imacron -92
+KPX Acircumflex Ibreve -92
+KPX Acircumflex Iogonek -92
+KPX Acircumflex Idot -92
+KPX Acircumflex IJ -92
+KPX Acircumflex Jcircumflex -92
+KPX Acircumflex Kcommaaccent -112
+KPX Acircumflex kcommaaccent -92
+KPX Acircumflex kgreenlandic -235
+KPX Acircumflex Lacute -149
+KPX Acircumflex lacute -235
+KPX Acircumflex uni01AC -63
+KPX Acircumflex uni01AE -63
+KPX Acircumflex uni01DC -196
+KPX Acircumflex uni01DD -112
+KPX Acircumflex uni01F0 -63
+KPX Acircumflex uni01F4 -235
+KPX Acircumflex uni01F5 -149
+
+KPX Adieresis ampersand -63
+KPX Adieresis two -63
+KPX Adieresis seven -196
+KPX Adieresis eight -92
+KPX Adieresis nine -139
+KPX Adieresis colon -112
+KPX Adieresis less -235
+KPX Adieresis F -63
+KPX Adieresis G -63
+KPX Adieresis W -112
+KPX Adieresis Y -112
+KPX Adieresis Z -92
+KPX Adieresis backslash -149
+KPX Adieresis copyright -63
+KPX Adieresis questiondown -149
+KPX Adieresis Aacute -149
+KPX Adieresis Egrave -63
+KPX Adieresis Eacute -63
+KPX Adieresis Ecircumflex -63
+KPX Adieresis Edieresis -63
+KPX Adieresis Igrave -63
+KPX Adieresis Iacute -63
+KPX Adieresis Icircumflex -63
+KPX Adieresis Idieresis -63
+KPX Adieresis Ntilde -63
+KPX Adieresis Oacute -63
+KPX Adieresis Dcaron -63
+KPX Adieresis Dcroat -63
+KPX Adieresis Emacron -63
+KPX Adieresis Ebreve -63
+KPX Adieresis Hcircumflex -196
+KPX Adieresis hcircumflex -112
+KPX Adieresis Hbar -196
+KPX Adieresis hbar -112
+KPX Adieresis Imacron -92
+KPX Adieresis Ibreve -92
+KPX Adieresis Iogonek -92
+KPX Adieresis Idot -92
+KPX Adieresis IJ -92
+KPX Adieresis Jcircumflex -92
+KPX Adieresis Kcommaaccent -112
+KPX Adieresis kcommaaccent -92
+KPX Adieresis kgreenlandic -235
+KPX Adieresis Lacute -149
+KPX Adieresis lacute -235
+KPX Adieresis uni01AC -63
+KPX Adieresis uni01AE -63
+KPX Adieresis uni01DC -196
+KPX Adieresis uni01DD -112
+KPX Adieresis uni01F0 -63
+KPX Adieresis uni01F4 -235
+KPX Adieresis uni01F5 -149
+
+KPX AE ampersand -63
+KPX AE two -63
+KPX AE seven -196
+KPX AE eight -92
+KPX AE nine -139
+KPX AE colon -112
+KPX AE less -235
+KPX AE F -63
+KPX AE G -63
+KPX AE W -112
+KPX AE Y -112
+KPX AE Z -92
+KPX AE backslash -149
+KPX AE copyright -63
+KPX AE questiondown -149
+KPX AE Aacute -149
+KPX AE Egrave -63
+KPX AE Eacute -63
+KPX AE Ecircumflex -63
+KPX AE Edieresis -63
+KPX AE Igrave -63
+KPX AE Iacute -63
+KPX AE Icircumflex -63
+KPX AE Idieresis -63
+KPX AE Ntilde -63
+KPX AE Oacute -63
+KPX AE Dcaron -63
+KPX AE Dcroat -63
+KPX AE Emacron -63
+KPX AE Ebreve -63
+KPX AE Hcircumflex -196
+KPX AE hcircumflex -112
+KPX AE Hbar -196
+KPX AE hbar -112
+KPX AE Imacron -92
+KPX AE Ibreve -92
+KPX AE Iogonek -92
+KPX AE Idot -92
+KPX AE IJ -92
+KPX AE Jcircumflex -92
+KPX AE Kcommaaccent -112
+KPX AE kcommaaccent -92
+KPX AE kgreenlandic -235
+KPX AE Lacute -149
+KPX AE lacute -235
+KPX AE uni01AC -63
+KPX AE uni01AE -63
+KPX AE uni01DC -196
+KPX AE uni01DD -112
+KPX AE uni01F0 -63
+KPX AE uni01F4 -235
+KPX AE uni01F5 -149
+
+KPX Egrave six -73
+KPX Egrave Gcircumflex -73
+KPX Egrave Gbreve -73
+KPX Egrave Gdotaccent -73
+KPX Egrave Gcommaaccent -73
+KPX Egrave uni01DA -73
+
+KPX Ecircumflex six -73
+KPX Ecircumflex Gcircumflex -73
+KPX Ecircumflex Gbreve -73
+KPX Ecircumflex Gdotaccent -73
+KPX Ecircumflex Gcommaaccent -73
+KPX Ecircumflex uni01DA -73
+
+KPX Igrave six -73
+KPX Igrave Gcircumflex -73
+KPX Igrave Gbreve -73
+KPX Igrave Gdotaccent -73
+KPX Igrave Gcommaaccent -73
+KPX Igrave uni01DA -73
+
+KPX Icircumflex six -73
+KPX Icircumflex Gcircumflex -73
+KPX Icircumflex Gbreve -73
+KPX Icircumflex Gdotaccent -73
+KPX Icircumflex Gcommaaccent -73
+KPX Icircumflex uni01DA -73
+
+KPX Eth less -159
+KPX Eth kgreenlandic -159
+KPX Eth lacute -159
+KPX Eth uni01F4 -159
+
+KPX Ograve less -159
+KPX Ograve kgreenlandic -159
+KPX Ograve lacute -159
+KPX Ograve uni01F4 -159
+
+KPX agrave seven -36
+KPX agrave less -83
+KPX agrave Hbar -36
+KPX agrave lacute -83
+
+KPX ucircumflex two -73
+KPX ucircumflex seven -339
+KPX ucircumflex eight -112
+KPX ucircumflex nine -282
+KPX ucircumflex colon -178
+KPX ucircumflex less -319
+KPX ucircumflex backslash -253
+KPX ucircumflex questiondown -253
+KPX ucircumflex Aacute -253
+KPX ucircumflex Ebreve -73
+KPX ucircumflex Hbar -339
+KPX ucircumflex Idot -112
+KPX ucircumflex lacute -319
+
+KPX ydieresis two -73
+KPX ydieresis seven -339
+KPX ydieresis eight -112
+KPX ydieresis nine -282
+KPX ydieresis colon -178
+KPX ydieresis less -319
+KPX ydieresis backslash -253
+KPX ydieresis questiondown -253
+KPX ydieresis Aacute -253
+KPX ydieresis Ebreve -73
+KPX ydieresis Hbar -339
+KPX ydieresis Idot -112
+KPX ydieresis lacute -319
+
+KPX Abreve O -8
+
+KPX abreve two -73
+KPX abreve seven -339
+KPX abreve eight -73
+KPX abreve nine -282
+KPX abreve colon -159
+KPX abreve less -319
+KPX abreve backslash -253
+KPX abreve questiondown -253
+KPX abreve Aacute -253
+KPX abreve Ebreve -73
+KPX abreve Hbar -339
+KPX abreve Idot -73
+KPX abreve lacute -319
+
+KPX Edotaccent seven -92
+KPX Edotaccent less -188
+KPX Edotaccent H -102
+KPX Edotaccent R -102
+KPX Edotaccent X -112
+KPX Edotaccent backslash -131
+KPX Edotaccent ordfeminine -102
+KPX Edotaccent guillemotleft -102
+KPX Edotaccent logicalnot -102
+KPX Edotaccent sfthyphen -102
+KPX Edotaccent acute -102
+KPX Edotaccent mu -102
+KPX Edotaccent paragraph -102
+KPX Edotaccent periodcentered -102
+KPX Edotaccent cedilla -102
+KPX Edotaccent ordmasculine -102
+KPX Edotaccent guillemotright -112
+KPX Edotaccent onequarter -112
+KPX Edotaccent onehalf -112
+KPX Edotaccent threequarters -112
+KPX Edotaccent questiondown -131
+KPX Edotaccent Aacute -131
+KPX Edotaccent Yacute -102
+KPX Edotaccent ebreve -102
+KPX Edotaccent Hbar -92
+KPX Edotaccent dotlessi -112
+KPX Edotaccent lacute -188
+
+
+KPX Ecaron seven -92
+KPX Ecaron less -188
+KPX Ecaron H -102
+KPX Ecaron R -102
+KPX Ecaron X -112
+KPX Ecaron backslash -131
+KPX Ecaron ordfeminine -102
+KPX Ecaron guillemotleft -102
+KPX Ecaron logicalnot -102
+KPX Ecaron sfthyphen -102
+KPX Ecaron acute -102
+KPX Ecaron mu -102
+KPX Ecaron paragraph -102
+KPX Ecaron periodcentered -102
+KPX Ecaron cedilla -102
+KPX Ecaron ordmasculine -102
+KPX Ecaron guillemotright -112
+KPX Ecaron onequarter -112
+KPX Ecaron onehalf -112
+KPX Ecaron threequarters -112
+KPX Ecaron questiondown -131
+KPX Ecaron Aacute -131
+KPX Ecaron Yacute -102
+KPX Ecaron ebreve -102
+KPX Ecaron Hbar -92
+KPX Ecaron dotlessi -112
+KPX Ecaron lacute -188
+
+
+KPX Gdotaccent six -73
+KPX Gdotaccent Gdotaccent -73
+KPX Gdotaccent Gcommaaccent -73
+
+KPX Gcommaaccent six -73
+KPX Gcommaaccent Gdotaccent -73
+KPX Gcommaaccent Gcommaaccent -73
+
+KPX Hbar dollar -159
+KPX Hbar seven 47
+KPX Hbar D -243
+KPX Hbar F -264
+KPX Hbar H -264
+KPX Hbar R -264
+KPX Hbar U -225
+KPX Hbar V -243
+KPX Hbar X -264
+KPX Hbar Z -282
+KPX Hbar backslash -339
+KPX Hbar cent -243
+KPX Hbar sterling -243
+KPX Hbar currency -243
+KPX Hbar yen -243
+KPX Hbar brokenbar -243
+KPX Hbar section -243
+KPX Hbar dieresis -243
+KPX Hbar copyright -264
+KPX Hbar ordfeminine -264
+KPX Hbar guillemotleft -264
+KPX Hbar logicalnot -264
+KPX Hbar sfthyphen -264
+KPX Hbar acute -264
+KPX Hbar mu -264
+KPX Hbar paragraph -264
+KPX Hbar periodcentered -264
+KPX Hbar cedilla -264
+KPX Hbar ordmasculine -264
+KPX Hbar guillemotright -264
+KPX Hbar onequarter -264
+KPX Hbar onehalf -264
+KPX Hbar threequarters -264
+KPX Hbar questiondown -339
+KPX Hbar Aacute -339
+KPX Hbar Eacute -264
+KPX Hbar Idieresis -264
+KPX Hbar Yacute -264
+KPX Hbar ebreve -264
+KPX Hbar edotaccent -225
+KPX Hbar ecaron -225
+KPX Hbar gdotaccent -243
+KPX Hbar gcommaaccent -243
+KPX Hbar Hbar 47
+KPX Hbar dotlessi -264
+
+KPX hbar Hbar -112
+
+KPX Idot dollar -92
+KPX Idot Idot -92
+
+KPX lacute dollar -196
+KPX lacute ampersand -73
+KPX lacute two -73
+KPX lacute D -243
+KPX lacute H -264
+KPX lacute R -264
+KPX lacute X -225
+KPX lacute cent -243
+KPX lacute sterling -243
+KPX lacute currency -243
+KPX lacute yen -243
+KPX lacute brokenbar -243
+KPX lacute section -243
+KPX lacute dieresis -243
+KPX lacute ordfeminine -264
+KPX lacute guillemotleft -264
+KPX lacute logicalnot -264
+KPX lacute sfthyphen -264
+KPX lacute acute -264
+KPX lacute mu -264
+KPX lacute paragraph -264
+KPX lacute periodcentered -264
+KPX lacute cedilla -264
+KPX lacute ordmasculine -264
+KPX lacute guillemotright -225
+KPX lacute onequarter -225
+KPX lacute onehalf -225
+KPX lacute threequarters -225
+KPX lacute Egrave -73
+KPX lacute Icircumflex -73
+KPX lacute Yacute -264
+KPX lacute Ebreve -120
+KPX lacute ebreve -264
+KPX lacute dotlessi -225
+
+
+KPX uni027D dollar -272
+KPX uni027D hyphen -92
+KPX uni027D nine 38
+KPX uni027D less 75
+KPX uni027D lacute 75
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ttf
new file mode 100644
index 0000000..999bac7
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ufm
new file mode 100644
index 0000000..0b8d60e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans-Oblique.ufm
@@ -0,0 +1,5268 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans
+FontSubfamily Oblique
+UniqueID DejaVu Sans Oblique
+FullName DejaVu Sans Oblique
+Version Version 2.37
+PostScriptName DejaVuSans-Oblique
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Sans
+PreferredSubfamily Oblique
+Weight Medium
+ItalicAngle -11
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -1016 -350 1659 1068
+StartCharMetrics 5355
+U 32 ; WX 318 ; N space ; G 3
+U 33 ; WX 401 ; N exclam ; G 4
+U 34 ; WX 460 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 636 ; N dollar ; G 7
+U 37 ; WX 950 ; N percent ; G 8
+U 38 ; WX 780 ; N ampersand ; G 9
+U 39 ; WX 275 ; N quotesingle ; G 10
+U 40 ; WX 390 ; N parenleft ; G 11
+U 41 ; WX 390 ; N parenright ; G 12
+U 42 ; WX 500 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 318 ; N comma ; G 15
+U 45 ; WX 361 ; N hyphen ; G 16
+U 46 ; WX 318 ; N period ; G 17
+U 47 ; WX 337 ; N slash ; G 18
+U 48 ; WX 636 ; N zero ; G 19
+U 49 ; WX 636 ; N one ; G 20
+U 50 ; WX 636 ; N two ; G 21
+U 51 ; WX 636 ; N three ; G 22
+U 52 ; WX 636 ; N four ; G 23
+U 53 ; WX 636 ; N five ; G 24
+U 54 ; WX 636 ; N six ; G 25
+U 55 ; WX 636 ; N seven ; G 26
+U 56 ; WX 636 ; N eight ; G 27
+U 57 ; WX 636 ; N nine ; G 28
+U 58 ; WX 337 ; N colon ; G 29
+U 59 ; WX 337 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 531 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 684 ; N A ; G 36
+U 66 ; WX 686 ; N B ; G 37
+U 67 ; WX 698 ; N C ; G 38
+U 68 ; WX 770 ; N D ; G 39
+U 69 ; WX 632 ; N E ; G 40
+U 70 ; WX 575 ; N F ; G 41
+U 71 ; WX 775 ; N G ; G 42
+U 72 ; WX 752 ; N H ; G 43
+U 73 ; WX 295 ; N I ; G 44
+U 74 ; WX 295 ; N J ; G 45
+U 75 ; WX 656 ; N K ; G 46
+U 76 ; WX 557 ; N L ; G 47
+U 77 ; WX 863 ; N M ; G 48
+U 78 ; WX 748 ; N N ; G 49
+U 79 ; WX 787 ; N O ; G 50
+U 80 ; WX 603 ; N P ; G 51
+U 81 ; WX 787 ; N Q ; G 52
+U 82 ; WX 695 ; N R ; G 53
+U 83 ; WX 635 ; N S ; G 54
+U 84 ; WX 611 ; N T ; G 55
+U 85 ; WX 732 ; N U ; G 56
+U 86 ; WX 684 ; N V ; G 57
+U 87 ; WX 989 ; N W ; G 58
+U 88 ; WX 685 ; N X ; G 59
+U 89 ; WX 611 ; N Y ; G 60
+U 90 ; WX 685 ; N Z ; G 61
+U 91 ; WX 390 ; N bracketleft ; G 62
+U 92 ; WX 337 ; N backslash ; G 63
+U 93 ; WX 390 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 613 ; N a ; G 68
+U 98 ; WX 635 ; N b ; G 69
+U 99 ; WX 550 ; N c ; G 70
+U 100 ; WX 635 ; N d ; G 71
+U 101 ; WX 615 ; N e ; G 72
+U 102 ; WX 352 ; N f ; G 73
+U 103 ; WX 635 ; N g ; G 74
+U 104 ; WX 634 ; N h ; G 75
+U 105 ; WX 278 ; N i ; G 76
+U 106 ; WX 278 ; N j ; G 77
+U 107 ; WX 579 ; N k ; G 78
+U 108 ; WX 278 ; N l ; G 79
+U 109 ; WX 974 ; N m ; G 80
+U 110 ; WX 634 ; N n ; G 81
+U 111 ; WX 612 ; N o ; G 82
+U 112 ; WX 635 ; N p ; G 83
+U 113 ; WX 635 ; N q ; G 84
+U 114 ; WX 411 ; N r ; G 85
+U 115 ; WX 521 ; N s ; G 86
+U 116 ; WX 392 ; N t ; G 87
+U 117 ; WX 634 ; N u ; G 88
+U 118 ; WX 592 ; N v ; G 89
+U 119 ; WX 818 ; N w ; G 90
+U 120 ; WX 592 ; N x ; G 91
+U 121 ; WX 592 ; N y ; G 92
+U 122 ; WX 525 ; N z ; G 93
+U 123 ; WX 636 ; N braceleft ; G 94
+U 124 ; WX 337 ; N bar ; G 95
+U 125 ; WX 636 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 318 ; N nbspace ; G 98
+U 161 ; WX 401 ; N exclamdown ; G 99
+U 162 ; WX 636 ; N cent ; G 100
+U 163 ; WX 636 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 636 ; N yen ; G 103
+U 166 ; WX 337 ; N brokenbar ; G 104
+U 167 ; WX 500 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 471 ; N ordfeminine ; G 108
+U 171 ; WX 617 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 361 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 401 ; N twosuperior ; G 116
+U 179 ; WX 401 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 636 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 318 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 401 ; N onesuperior ; G 123
+U 186 ; WX 471 ; N ordmasculine ; G 124
+U 187 ; WX 617 ; N guillemotright ; G 125
+U 188 ; WX 969 ; N onequarter ; G 126
+U 189 ; WX 969 ; N onehalf ; G 127
+U 190 ; WX 969 ; N threequarters ; G 128
+U 191 ; WX 531 ; N questiondown ; G 129
+U 192 ; WX 684 ; N Agrave ; G 130
+U 193 ; WX 684 ; N Aacute ; G 131
+U 194 ; WX 684 ; N Acircumflex ; G 132
+U 195 ; WX 684 ; N Atilde ; G 133
+U 196 ; WX 684 ; N Adieresis ; G 134
+U 197 ; WX 684 ; N Aring ; G 135
+U 198 ; WX 974 ; N AE ; G 136
+U 199 ; WX 698 ; N Ccedilla ; G 137
+U 200 ; WX 632 ; N Egrave ; G 138
+U 201 ; WX 632 ; N Eacute ; G 139
+U 202 ; WX 632 ; N Ecircumflex ; G 140
+U 203 ; WX 632 ; N Edieresis ; G 141
+U 204 ; WX 295 ; N Igrave ; G 142
+U 205 ; WX 295 ; N Iacute ; G 143
+U 206 ; WX 295 ; N Icircumflex ; G 144
+U 207 ; WX 295 ; N Idieresis ; G 145
+U 208 ; WX 775 ; N Eth ; G 146
+U 209 ; WX 748 ; N Ntilde ; G 147
+U 210 ; WX 787 ; N Ograve ; G 148
+U 211 ; WX 787 ; N Oacute ; G 149
+U 212 ; WX 787 ; N Ocircumflex ; G 150
+U 213 ; WX 787 ; N Otilde ; G 151
+U 214 ; WX 787 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 787 ; N Oslash ; G 154
+U 217 ; WX 732 ; N Ugrave ; G 155
+U 218 ; WX 732 ; N Uacute ; G 156
+U 219 ; WX 732 ; N Ucircumflex ; G 157
+U 220 ; WX 732 ; N Udieresis ; G 158
+U 221 ; WX 611 ; N Yacute ; G 159
+U 222 ; WX 608 ; N Thorn ; G 160
+U 223 ; WX 630 ; N germandbls ; G 161
+U 224 ; WX 613 ; N agrave ; G 162
+U 225 ; WX 613 ; N aacute ; G 163
+U 226 ; WX 613 ; N acircumflex ; G 164
+U 227 ; WX 613 ; N atilde ; G 165
+U 228 ; WX 613 ; N adieresis ; G 166
+U 229 ; WX 613 ; N aring ; G 167
+U 230 ; WX 995 ; N ae ; G 168
+U 231 ; WX 550 ; N ccedilla ; G 169
+U 232 ; WX 615 ; N egrave ; G 170
+U 233 ; WX 615 ; N eacute ; G 171
+U 234 ; WX 615 ; N ecircumflex ; G 172
+U 235 ; WX 615 ; N edieresis ; G 173
+U 236 ; WX 278 ; N igrave ; G 174
+U 237 ; WX 278 ; N iacute ; G 175
+U 238 ; WX 278 ; N icircumflex ; G 176
+U 239 ; WX 278 ; N idieresis ; G 177
+U 240 ; WX 612 ; N eth ; G 178
+U 241 ; WX 634 ; N ntilde ; G 179
+U 242 ; WX 612 ; N ograve ; G 180
+U 243 ; WX 612 ; N oacute ; G 181
+U 244 ; WX 612 ; N ocircumflex ; G 182
+U 245 ; WX 612 ; N otilde ; G 183
+U 246 ; WX 612 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 612 ; N oslash ; G 186
+U 249 ; WX 634 ; N ugrave ; G 187
+U 250 ; WX 634 ; N uacute ; G 188
+U 251 ; WX 634 ; N ucircumflex ; G 189
+U 252 ; WX 634 ; N udieresis ; G 190
+U 253 ; WX 592 ; N yacute ; G 191
+U 254 ; WX 635 ; N thorn ; G 192
+U 255 ; WX 592 ; N ydieresis ; G 193
+U 256 ; WX 684 ; N Amacron ; G 194
+U 257 ; WX 613 ; N amacron ; G 195
+U 258 ; WX 684 ; N Abreve ; G 196
+U 259 ; WX 613 ; N abreve ; G 197
+U 260 ; WX 684 ; N Aogonek ; G 198
+U 261 ; WX 613 ; N aogonek ; G 199
+U 262 ; WX 698 ; N Cacute ; G 200
+U 263 ; WX 550 ; N cacute ; G 201
+U 264 ; WX 698 ; N Ccircumflex ; G 202
+U 265 ; WX 550 ; N ccircumflex ; G 203
+U 266 ; WX 698 ; N Cdotaccent ; G 204
+U 267 ; WX 550 ; N cdotaccent ; G 205
+U 268 ; WX 698 ; N Ccaron ; G 206
+U 269 ; WX 550 ; N ccaron ; G 207
+U 270 ; WX 770 ; N Dcaron ; G 208
+U 271 ; WX 635 ; N dcaron ; G 209
+U 272 ; WX 775 ; N Dcroat ; G 210
+U 273 ; WX 635 ; N dmacron ; G 211
+U 274 ; WX 632 ; N Emacron ; G 212
+U 275 ; WX 615 ; N emacron ; G 213
+U 276 ; WX 632 ; N Ebreve ; G 214
+U 277 ; WX 615 ; N ebreve ; G 215
+U 278 ; WX 632 ; N Edotaccent ; G 216
+U 279 ; WX 615 ; N edotaccent ; G 217
+U 280 ; WX 632 ; N Eogonek ; G 218
+U 281 ; WX 615 ; N eogonek ; G 219
+U 282 ; WX 632 ; N Ecaron ; G 220
+U 283 ; WX 615 ; N ecaron ; G 221
+U 284 ; WX 775 ; N Gcircumflex ; G 222
+U 285 ; WX 635 ; N gcircumflex ; G 223
+U 286 ; WX 775 ; N Gbreve ; G 224
+U 287 ; WX 635 ; N gbreve ; G 225
+U 288 ; WX 775 ; N Gdotaccent ; G 226
+U 289 ; WX 635 ; N gdotaccent ; G 227
+U 290 ; WX 775 ; N Gcommaaccent ; G 228
+U 291 ; WX 635 ; N gcommaaccent ; G 229
+U 292 ; WX 752 ; N Hcircumflex ; G 230
+U 293 ; WX 634 ; N hcircumflex ; G 231
+U 294 ; WX 916 ; N Hbar ; G 232
+U 295 ; WX 695 ; N hbar ; G 233
+U 296 ; WX 295 ; N Itilde ; G 234
+U 297 ; WX 278 ; N itilde ; G 235
+U 298 ; WX 295 ; N Imacron ; G 236
+U 299 ; WX 278 ; N imacron ; G 237
+U 300 ; WX 295 ; N Ibreve ; G 238
+U 301 ; WX 278 ; N ibreve ; G 239
+U 302 ; WX 295 ; N Iogonek ; G 240
+U 303 ; WX 278 ; N iogonek ; G 241
+U 304 ; WX 295 ; N Idot ; G 242
+U 305 ; WX 278 ; N dotlessi ; G 243
+U 306 ; WX 590 ; N IJ ; G 244
+U 307 ; WX 556 ; N ij ; G 245
+U 308 ; WX 295 ; N Jcircumflex ; G 246
+U 309 ; WX 278 ; N jcircumflex ; G 247
+U 310 ; WX 656 ; N Kcommaaccent ; G 248
+U 311 ; WX 579 ; N kcommaaccent ; G 249
+U 312 ; WX 579 ; N kgreenlandic ; G 250
+U 313 ; WX 557 ; N Lacute ; G 251
+U 314 ; WX 278 ; N lacute ; G 252
+U 315 ; WX 557 ; N Lcommaaccent ; G 253
+U 316 ; WX 278 ; N lcommaaccent ; G 254
+U 317 ; WX 557 ; N Lcaron ; G 255
+U 318 ; WX 278 ; N lcaron ; G 256
+U 319 ; WX 557 ; N Ldot ; G 257
+U 320 ; WX 278 ; N ldot ; G 258
+U 321 ; WX 562 ; N Lslash ; G 259
+U 322 ; WX 287 ; N lslash ; G 260
+U 323 ; WX 748 ; N Nacute ; G 261
+U 324 ; WX 634 ; N nacute ; G 262
+U 325 ; WX 748 ; N Ncommaaccent ; G 263
+U 326 ; WX 634 ; N ncommaaccent ; G 264
+U 327 ; WX 748 ; N Ncaron ; G 265
+U 328 ; WX 634 ; N ncaron ; G 266
+U 329 ; WX 813 ; N napostrophe ; G 267
+U 330 ; WX 748 ; N Eng ; G 268
+U 331 ; WX 634 ; N eng ; G 269
+U 332 ; WX 787 ; N Omacron ; G 270
+U 333 ; WX 612 ; N omacron ; G 271
+U 334 ; WX 787 ; N Obreve ; G 272
+U 335 ; WX 612 ; N obreve ; G 273
+U 336 ; WX 787 ; N Ohungarumlaut ; G 274
+U 337 ; WX 612 ; N ohungarumlaut ; G 275
+U 338 ; WX 1070 ; N OE ; G 276
+U 339 ; WX 1028 ; N oe ; G 277
+U 340 ; WX 695 ; N Racute ; G 278
+U 341 ; WX 411 ; N racute ; G 279
+U 342 ; WX 695 ; N Rcommaaccent ; G 280
+U 343 ; WX 411 ; N rcommaaccent ; G 281
+U 344 ; WX 695 ; N Rcaron ; G 282
+U 345 ; WX 411 ; N rcaron ; G 283
+U 346 ; WX 635 ; N Sacute ; G 284
+U 347 ; WX 521 ; N sacute ; G 285
+U 348 ; WX 635 ; N Scircumflex ; G 286
+U 349 ; WX 521 ; N scircumflex ; G 287
+U 350 ; WX 635 ; N Scedilla ; G 288
+U 351 ; WX 521 ; N scedilla ; G 289
+U 352 ; WX 635 ; N Scaron ; G 290
+U 353 ; WX 521 ; N scaron ; G 291
+U 354 ; WX 611 ; N Tcommaaccent ; G 292
+U 355 ; WX 392 ; N tcommaaccent ; G 293
+U 356 ; WX 611 ; N Tcaron ; G 294
+U 357 ; WX 392 ; N tcaron ; G 295
+U 358 ; WX 611 ; N Tbar ; G 296
+U 359 ; WX 392 ; N tbar ; G 297
+U 360 ; WX 732 ; N Utilde ; G 298
+U 361 ; WX 634 ; N utilde ; G 299
+U 362 ; WX 732 ; N Umacron ; G 300
+U 363 ; WX 634 ; N umacron ; G 301
+U 364 ; WX 732 ; N Ubreve ; G 302
+U 365 ; WX 634 ; N ubreve ; G 303
+U 366 ; WX 732 ; N Uring ; G 304
+U 367 ; WX 634 ; N uring ; G 305
+U 368 ; WX 732 ; N Uhungarumlaut ; G 306
+U 369 ; WX 634 ; N uhungarumlaut ; G 307
+U 370 ; WX 732 ; N Uogonek ; G 308
+U 371 ; WX 634 ; N uogonek ; G 309
+U 372 ; WX 989 ; N Wcircumflex ; G 310
+U 373 ; WX 818 ; N wcircumflex ; G 311
+U 374 ; WX 611 ; N Ycircumflex ; G 312
+U 375 ; WX 592 ; N ycircumflex ; G 313
+U 376 ; WX 611 ; N Ydieresis ; G 314
+U 377 ; WX 685 ; N Zacute ; G 315
+U 378 ; WX 525 ; N zacute ; G 316
+U 379 ; WX 685 ; N Zdotaccent ; G 317
+U 380 ; WX 525 ; N zdotaccent ; G 318
+U 381 ; WX 685 ; N Zcaron ; G 319
+U 382 ; WX 525 ; N zcaron ; G 320
+U 383 ; WX 352 ; N longs ; G 321
+U 384 ; WX 635 ; N uni0180 ; G 322
+U 385 ; WX 735 ; N uni0181 ; G 323
+U 386 ; WX 686 ; N uni0182 ; G 324
+U 387 ; WX 635 ; N uni0183 ; G 325
+U 388 ; WX 686 ; N uni0184 ; G 326
+U 389 ; WX 635 ; N uni0185 ; G 327
+U 390 ; WX 703 ; N uni0186 ; G 328
+U 391 ; WX 698 ; N uni0187 ; G 329
+U 392 ; WX 550 ; N uni0188 ; G 330
+U 393 ; WX 775 ; N uni0189 ; G 331
+U 394 ; WX 819 ; N uni018A ; G 332
+U 395 ; WX 686 ; N uni018B ; G 333
+U 396 ; WX 635 ; N uni018C ; G 334
+U 397 ; WX 612 ; N uni018D ; G 335
+U 398 ; WX 632 ; N uni018E ; G 336
+U 399 ; WX 787 ; N uni018F ; G 337
+U 400 ; WX 614 ; N uni0190 ; G 338
+U 401 ; WX 575 ; N uni0191 ; G 339
+U 402 ; WX 352 ; N florin ; G 340
+U 403 ; WX 775 ; N uni0193 ; G 341
+U 404 ; WX 687 ; N uni0194 ; G 342
+U 405 ; WX 984 ; N uni0195 ; G 343
+U 406 ; WX 354 ; N uni0196 ; G 344
+U 407 ; WX 295 ; N uni0197 ; G 345
+U 408 ; WX 746 ; N uni0198 ; G 346
+U 409 ; WX 579 ; N uni0199 ; G 347
+U 410 ; WX 278 ; N uni019A ; G 348
+U 411 ; WX 592 ; N uni019B ; G 349
+U 412 ; WX 974 ; N uni019C ; G 350
+U 413 ; WX 748 ; N uni019D ; G 351
+U 414 ; WX 634 ; N uni019E ; G 352
+U 415 ; WX 787 ; N uni019F ; G 353
+U 416 ; WX 913 ; N Ohorn ; G 354
+U 417 ; WX 612 ; N ohorn ; G 355
+U 418 ; WX 938 ; N uni01A2 ; G 356
+U 419 ; WX 737 ; N uni01A3 ; G 357
+U 420 ; WX 652 ; N uni01A4 ; G 358
+U 421 ; WX 635 ; N uni01A5 ; G 359
+U 422 ; WX 695 ; N uni01A6 ; G 360
+U 423 ; WX 635 ; N uni01A7 ; G 361
+U 424 ; WX 521 ; N uni01A8 ; G 362
+U 425 ; WX 632 ; N uni01A9 ; G 363
+U 426 ; WX 336 ; N uni01AA ; G 364
+U 427 ; WX 392 ; N uni01AB ; G 365
+U 428 ; WX 611 ; N uni01AC ; G 366
+U 429 ; WX 392 ; N uni01AD ; G 367
+U 430 ; WX 611 ; N uni01AE ; G 368
+U 431 ; WX 838 ; N Uhorn ; G 369
+U 432 ; WX 634 ; N uhorn ; G 370
+U 433 ; WX 764 ; N uni01B1 ; G 371
+U 434 ; WX 721 ; N uni01B2 ; G 372
+U 435 ; WX 744 ; N uni01B3 ; G 373
+U 436 ; WX 730 ; N uni01B4 ; G 374
+U 437 ; WX 685 ; N uni01B5 ; G 375
+U 438 ; WX 525 ; N uni01B6 ; G 376
+U 439 ; WX 666 ; N uni01B7 ; G 377
+U 440 ; WX 666 ; N uni01B8 ; G 378
+U 441 ; WX 578 ; N uni01B9 ; G 379
+U 442 ; WX 525 ; N uni01BA ; G 380
+U 443 ; WX 636 ; N uni01BB ; G 381
+U 444 ; WX 666 ; N uni01BC ; G 382
+U 445 ; WX 578 ; N uni01BD ; G 383
+U 446 ; WX 510 ; N uni01BE ; G 384
+U 447 ; WX 635 ; N uni01BF ; G 385
+U 448 ; WX 295 ; N uni01C0 ; G 386
+U 449 ; WX 492 ; N uni01C1 ; G 387
+U 450 ; WX 459 ; N uni01C2 ; G 388
+U 451 ; WX 295 ; N uni01C3 ; G 389
+U 452 ; WX 1455 ; N uni01C4 ; G 390
+U 453 ; WX 1295 ; N uni01C5 ; G 391
+U 454 ; WX 1160 ; N uni01C6 ; G 392
+U 455 ; WX 852 ; N uni01C7 ; G 393
+U 456 ; WX 835 ; N uni01C8 ; G 394
+U 457 ; WX 556 ; N uni01C9 ; G 395
+U 458 ; WX 1043 ; N uni01CA ; G 396
+U 459 ; WX 1026 ; N uni01CB ; G 397
+U 460 ; WX 912 ; N uni01CC ; G 398
+U 461 ; WX 684 ; N uni01CD ; G 399
+U 462 ; WX 613 ; N uni01CE ; G 400
+U 463 ; WX 295 ; N uni01CF ; G 401
+U 464 ; WX 278 ; N uni01D0 ; G 402
+U 465 ; WX 787 ; N uni01D1 ; G 403
+U 466 ; WX 612 ; N uni01D2 ; G 404
+U 467 ; WX 732 ; N uni01D3 ; G 405
+U 468 ; WX 634 ; N uni01D4 ; G 406
+U 469 ; WX 732 ; N uni01D5 ; G 407
+U 470 ; WX 634 ; N uni01D6 ; G 408
+U 471 ; WX 732 ; N uni01D7 ; G 409
+U 472 ; WX 634 ; N uni01D8 ; G 410
+U 473 ; WX 732 ; N uni01D9 ; G 411
+U 474 ; WX 634 ; N uni01DA ; G 412
+U 475 ; WX 732 ; N uni01DB ; G 413
+U 476 ; WX 634 ; N uni01DC ; G 414
+U 477 ; WX 615 ; N uni01DD ; G 415
+U 478 ; WX 684 ; N uni01DE ; G 416
+U 479 ; WX 613 ; N uni01DF ; G 417
+U 480 ; WX 684 ; N uni01E0 ; G 418
+U 481 ; WX 613 ; N uni01E1 ; G 419
+U 482 ; WX 974 ; N uni01E2 ; G 420
+U 483 ; WX 995 ; N uni01E3 ; G 421
+U 484 ; WX 775 ; N uni01E4 ; G 422
+U 485 ; WX 635 ; N uni01E5 ; G 423
+U 486 ; WX 775 ; N Gcaron ; G 424
+U 487 ; WX 635 ; N gcaron ; G 425
+U 488 ; WX 656 ; N uni01E8 ; G 426
+U 489 ; WX 579 ; N uni01E9 ; G 427
+U 490 ; WX 787 ; N uni01EA ; G 428
+U 491 ; WX 612 ; N uni01EB ; G 429
+U 492 ; WX 787 ; N uni01EC ; G 430
+U 493 ; WX 612 ; N uni01ED ; G 431
+U 494 ; WX 666 ; N uni01EE ; G 432
+U 495 ; WX 525 ; N uni01EF ; G 433
+U 496 ; WX 278 ; N uni01F0 ; G 434
+U 497 ; WX 1455 ; N uni01F1 ; G 435
+U 498 ; WX 1295 ; N uni01F2 ; G 436
+U 499 ; WX 1160 ; N uni01F3 ; G 437
+U 500 ; WX 775 ; N uni01F4 ; G 438
+U 501 ; WX 635 ; N uni01F5 ; G 439
+U 502 ; WX 1113 ; N uni01F6 ; G 440
+U 503 ; WX 682 ; N uni01F7 ; G 441
+U 504 ; WX 748 ; N uni01F8 ; G 442
+U 505 ; WX 634 ; N uni01F9 ; G 443
+U 506 ; WX 684 ; N Aringacute ; G 444
+U 507 ; WX 613 ; N aringacute ; G 445
+U 508 ; WX 974 ; N AEacute ; G 446
+U 509 ; WX 995 ; N aeacute ; G 447
+U 510 ; WX 787 ; N Oslashacute ; G 448
+U 511 ; WX 612 ; N oslashacute ; G 449
+U 512 ; WX 684 ; N uni0200 ; G 450
+U 513 ; WX 613 ; N uni0201 ; G 451
+U 514 ; WX 684 ; N uni0202 ; G 452
+U 515 ; WX 613 ; N uni0203 ; G 453
+U 516 ; WX 632 ; N uni0204 ; G 454
+U 517 ; WX 615 ; N uni0205 ; G 455
+U 518 ; WX 632 ; N uni0206 ; G 456
+U 519 ; WX 615 ; N uni0207 ; G 457
+U 520 ; WX 295 ; N uni0208 ; G 458
+U 521 ; WX 278 ; N uni0209 ; G 459
+U 522 ; WX 295 ; N uni020A ; G 460
+U 523 ; WX 278 ; N uni020B ; G 461
+U 524 ; WX 787 ; N uni020C ; G 462
+U 525 ; WX 612 ; N uni020D ; G 463
+U 526 ; WX 787 ; N uni020E ; G 464
+U 527 ; WX 612 ; N uni020F ; G 465
+U 528 ; WX 695 ; N uni0210 ; G 466
+U 529 ; WX 411 ; N uni0211 ; G 467
+U 530 ; WX 695 ; N uni0212 ; G 468
+U 531 ; WX 411 ; N uni0213 ; G 469
+U 532 ; WX 732 ; N uni0214 ; G 470
+U 533 ; WX 634 ; N uni0215 ; G 471
+U 534 ; WX 732 ; N uni0216 ; G 472
+U 535 ; WX 634 ; N uni0217 ; G 473
+U 536 ; WX 635 ; N Scommaaccent ; G 474
+U 537 ; WX 521 ; N scommaaccent ; G 475
+U 538 ; WX 611 ; N uni021A ; G 476
+U 539 ; WX 392 ; N uni021B ; G 477
+U 540 ; WX 627 ; N uni021C ; G 478
+U 541 ; WX 521 ; N uni021D ; G 479
+U 542 ; WX 752 ; N uni021E ; G 480
+U 543 ; WX 634 ; N uni021F ; G 481
+U 544 ; WX 735 ; N uni0220 ; G 482
+U 545 ; WX 838 ; N uni0221 ; G 483
+U 546 ; WX 698 ; N uni0222 ; G 484
+U 547 ; WX 610 ; N uni0223 ; G 485
+U 548 ; WX 685 ; N uni0224 ; G 486
+U 549 ; WX 525 ; N uni0225 ; G 487
+U 550 ; WX 684 ; N uni0226 ; G 488
+U 551 ; WX 613 ; N uni0227 ; G 489
+U 552 ; WX 632 ; N uni0228 ; G 490
+U 553 ; WX 615 ; N uni0229 ; G 491
+U 554 ; WX 787 ; N uni022A ; G 492
+U 555 ; WX 612 ; N uni022B ; G 493
+U 556 ; WX 787 ; N uni022C ; G 494
+U 557 ; WX 612 ; N uni022D ; G 495
+U 558 ; WX 787 ; N uni022E ; G 496
+U 559 ; WX 612 ; N uni022F ; G 497
+U 560 ; WX 787 ; N uni0230 ; G 498
+U 561 ; WX 612 ; N uni0231 ; G 499
+U 562 ; WX 611 ; N uni0232 ; G 500
+U 563 ; WX 592 ; N uni0233 ; G 501
+U 564 ; WX 475 ; N uni0234 ; G 502
+U 565 ; WX 843 ; N uni0235 ; G 503
+U 566 ; WX 477 ; N uni0236 ; G 504
+U 567 ; WX 278 ; N dotlessj ; G 505
+U 568 ; WX 998 ; N uni0238 ; G 506
+U 569 ; WX 998 ; N uni0239 ; G 507
+U 570 ; WX 684 ; N uni023A ; G 508
+U 571 ; WX 698 ; N uni023B ; G 509
+U 572 ; WX 550 ; N uni023C ; G 510
+U 573 ; WX 557 ; N uni023D ; G 511
+U 574 ; WX 611 ; N uni023E ; G 512
+U 575 ; WX 521 ; N uni023F ; G 513
+U 576 ; WX 525 ; N uni0240 ; G 514
+U 577 ; WX 603 ; N uni0241 ; G 515
+U 578 ; WX 479 ; N uni0242 ; G 516
+U 579 ; WX 686 ; N uni0243 ; G 517
+U 580 ; WX 732 ; N uni0244 ; G 518
+U 581 ; WX 684 ; N uni0245 ; G 519
+U 582 ; WX 632 ; N uni0246 ; G 520
+U 583 ; WX 615 ; N uni0247 ; G 521
+U 584 ; WX 295 ; N uni0248 ; G 522
+U 585 ; WX 278 ; N uni0249 ; G 523
+U 586 ; WX 781 ; N uni024A ; G 524
+U 587 ; WX 635 ; N uni024B ; G 525
+U 588 ; WX 695 ; N uni024C ; G 526
+U 589 ; WX 411 ; N uni024D ; G 527
+U 590 ; WX 611 ; N uni024E ; G 528
+U 591 ; WX 592 ; N uni024F ; G 529
+U 592 ; WX 613 ; N uni0250 ; G 530
+U 593 ; WX 635 ; N uni0251 ; G 531
+U 594 ; WX 635 ; N uni0252 ; G 532
+U 595 ; WX 635 ; N uni0253 ; G 533
+U 596 ; WX 550 ; N uni0254 ; G 534
+U 597 ; WX 550 ; N uni0255 ; G 535
+U 598 ; WX 635 ; N uni0256 ; G 536
+U 599 ; WX 727 ; N uni0257 ; G 537
+U 600 ; WX 615 ; N uni0258 ; G 538
+U 601 ; WX 615 ; N uni0259 ; G 539
+U 602 ; WX 844 ; N uni025A ; G 540
+U 603 ; WX 545 ; N uni025B ; G 541
+U 604 ; WX 545 ; N uni025C ; G 542
+U 605 ; WX 775 ; N uni025D ; G 543
+U 606 ; WX 664 ; N uni025E ; G 544
+U 607 ; WX 326 ; N uni025F ; G 545
+U 608 ; WX 696 ; N uni0260 ; G 546
+U 609 ; WX 635 ; N uni0261 ; G 547
+U 610 ; WX 629 ; N uni0262 ; G 548
+U 611 ; WX 596 ; N uni0263 ; G 549
+U 612 ; WX 596 ; N uni0264 ; G 550
+U 613 ; WX 634 ; N uni0265 ; G 551
+U 614 ; WX 634 ; N uni0266 ; G 552
+U 615 ; WX 634 ; N uni0267 ; G 553
+U 616 ; WX 372 ; N uni0268 ; G 554
+U 617 ; WX 387 ; N uni0269 ; G 555
+U 618 ; WX 372 ; N uni026A ; G 556
+U 619 ; WX 396 ; N uni026B ; G 557
+U 620 ; WX 487 ; N uni026C ; G 558
+U 621 ; WX 278 ; N uni026D ; G 559
+U 622 ; WX 706 ; N uni026E ; G 560
+U 623 ; WX 974 ; N uni026F ; G 561
+U 624 ; WX 974 ; N uni0270 ; G 562
+U 625 ; WX 974 ; N uni0271 ; G 563
+U 626 ; WX 646 ; N uni0272 ; G 564
+U 627 ; WX 642 ; N uni0273 ; G 565
+U 628 ; WX 634 ; N uni0274 ; G 566
+U 629 ; WX 612 ; N uni0275 ; G 567
+U 630 ; WX 858 ; N uni0276 ; G 568
+U 631 ; WX 728 ; N uni0277 ; G 569
+U 632 ; WX 660 ; N uni0278 ; G 570
+U 633 ; WX 469 ; N uni0279 ; G 571
+U 634 ; WX 469 ; N uni027A ; G 572
+U 635 ; WX 469 ; N uni027B ; G 573
+U 636 ; WX 469 ; N uni027C ; G 574
+U 637 ; WX 469 ; N uni027D ; G 575
+U 638 ; WX 530 ; N uni027E ; G 576
+U 639 ; WX 530 ; N uni027F ; G 577
+U 640 ; WX 602 ; N uni0280 ; G 578
+U 641 ; WX 602 ; N uni0281 ; G 579
+U 642 ; WX 521 ; N uni0282 ; G 580
+U 643 ; WX 336 ; N uni0283 ; G 581
+U 644 ; WX 336 ; N uni0284 ; G 582
+U 645 ; WX 461 ; N uni0285 ; G 583
+U 646 ; WX 336 ; N uni0286 ; G 584
+U 647 ; WX 392 ; N uni0287 ; G 585
+U 648 ; WX 392 ; N uni0288 ; G 586
+U 649 ; WX 634 ; N uni0289 ; G 587
+U 650 ; WX 618 ; N uni028A ; G 588
+U 651 ; WX 598 ; N uni028B ; G 589
+U 652 ; WX 592 ; N uni028C ; G 590
+U 653 ; WX 818 ; N uni028D ; G 591
+U 654 ; WX 592 ; N uni028E ; G 592
+U 655 ; WX 611 ; N uni028F ; G 593
+U 656 ; WX 525 ; N uni0290 ; G 594
+U 657 ; WX 525 ; N uni0291 ; G 595
+U 658 ; WX 578 ; N uni0292 ; G 596
+U 659 ; WX 578 ; N uni0293 ; G 597
+U 660 ; WX 510 ; N uni0294 ; G 598
+U 661 ; WX 510 ; N uni0295 ; G 599
+U 662 ; WX 510 ; N uni0296 ; G 600
+U 663 ; WX 510 ; N uni0297 ; G 601
+U 664 ; WX 787 ; N uni0298 ; G 602
+U 665 ; WX 580 ; N uni0299 ; G 603
+U 666 ; WX 664 ; N uni029A ; G 604
+U 667 ; WX 708 ; N uni029B ; G 605
+U 668 ; WX 654 ; N uni029C ; G 606
+U 669 ; WX 292 ; N uni029D ; G 607
+U 670 ; WX 667 ; N uni029E ; G 608
+U 671 ; WX 507 ; N uni029F ; G 609
+U 672 ; WX 727 ; N uni02A0 ; G 610
+U 673 ; WX 510 ; N uni02A1 ; G 611
+U 674 ; WX 510 ; N uni02A2 ; G 612
+U 675 ; WX 1014 ; N uni02A3 ; G 613
+U 676 ; WX 1058 ; N uni02A4 ; G 614
+U 677 ; WX 1013 ; N uni02A5 ; G 615
+U 678 ; WX 830 ; N uni02A6 ; G 616
+U 679 ; WX 610 ; N uni02A7 ; G 617
+U 680 ; WX 778 ; N uni02A8 ; G 618
+U 681 ; WX 848 ; N uni02A9 ; G 619
+U 682 ; WX 706 ; N uni02AA ; G 620
+U 683 ; WX 654 ; N uni02AB ; G 621
+U 684 ; WX 515 ; N uni02AC ; G 622
+U 685 ; WX 515 ; N uni02AD ; G 623
+U 686 ; WX 570 ; N uni02AE ; G 624
+U 687 ; WX 664 ; N uni02AF ; G 625
+U 688 ; WX 399 ; N uni02B0 ; G 626
+U 689 ; WX 399 ; N uni02B1 ; G 627
+U 690 ; WX 175 ; N uni02B2 ; G 628
+U 691 ; WX 259 ; N uni02B3 ; G 629
+U 692 ; WX 295 ; N uni02B4 ; G 630
+U 693 ; WX 296 ; N uni02B5 ; G 631
+U 694 ; WX 379 ; N uni02B6 ; G 632
+U 695 ; WX 515 ; N uni02B7 ; G 633
+U 696 ; WX 373 ; N uni02B8 ; G 634
+U 697 ; WX 278 ; N uni02B9 ; G 635
+U 698 ; WX 460 ; N uni02BA ; G 636
+U 699 ; WX 318 ; N uni02BB ; G 637
+U 700 ; WX 318 ; N uni02BC ; G 638
+U 701 ; WX 318 ; N uni02BD ; G 639
+U 702 ; WX 307 ; N uni02BE ; G 640
+U 703 ; WX 307 ; N uni02BF ; G 641
+U 704 ; WX 370 ; N uni02C0 ; G 642
+U 705 ; WX 370 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 275 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 275 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 718 ; WX 500 ; N uni02CE ; G 656
+U 719 ; WX 500 ; N uni02CF ; G 657
+U 720 ; WX 337 ; N uni02D0 ; G 658
+U 721 ; WX 337 ; N uni02D1 ; G 659
+U 722 ; WX 307 ; N uni02D2 ; G 660
+U 723 ; WX 307 ; N uni02D3 ; G 661
+U 724 ; WX 500 ; N uni02D4 ; G 662
+U 725 ; WX 500 ; N uni02D5 ; G 663
+U 726 ; WX 390 ; N uni02D6 ; G 664
+U 727 ; WX 317 ; N uni02D7 ; G 665
+U 728 ; WX 500 ; N breve ; G 666
+U 729 ; WX 500 ; N dotaccent ; G 667
+U 730 ; WX 500 ; N ring ; G 668
+U 731 ; WX 500 ; N ogonek ; G 669
+U 732 ; WX 500 ; N tilde ; G 670
+U 733 ; WX 500 ; N hungarumlaut ; G 671
+U 734 ; WX 315 ; N uni02DE ; G 672
+U 735 ; WX 500 ; N uni02DF ; G 673
+U 736 ; WX 426 ; N uni02E0 ; G 674
+U 737 ; WX 166 ; N uni02E1 ; G 675
+U 738 ; WX 373 ; N uni02E2 ; G 676
+U 739 ; WX 444 ; N uni02E3 ; G 677
+U 740 ; WX 370 ; N uni02E4 ; G 678
+U 741 ; WX 493 ; N uni02E5 ; G 679
+U 742 ; WX 493 ; N uni02E6 ; G 680
+U 743 ; WX 493 ; N uni02E7 ; G 681
+U 744 ; WX 493 ; N uni02E8 ; G 682
+U 745 ; WX 493 ; N uni02E9 ; G 683
+U 748 ; WX 500 ; N uni02EC ; G 684
+U 749 ; WX 500 ; N uni02ED ; G 685
+U 750 ; WX 518 ; N uni02EE ; G 686
+U 755 ; WX 500 ; N uni02F3 ; G 687
+U 759 ; WX 500 ; N uni02F7 ; G 688
+U 768 ; WX 0 ; N gravecomb ; G 689
+U 769 ; WX 0 ; N acutecomb ; G 690
+U 770 ; WX 0 ; N uni0302 ; G 691
+U 771 ; WX 0 ; N tildecomb ; G 692
+U 772 ; WX 0 ; N uni0304 ; G 693
+U 773 ; WX 0 ; N uni0305 ; G 694
+U 774 ; WX 0 ; N uni0306 ; G 695
+U 775 ; WX 0 ; N uni0307 ; G 696
+U 776 ; WX 0 ; N uni0308 ; G 697
+U 777 ; WX 0 ; N hookabovecomb ; G 698
+U 778 ; WX 0 ; N uni030A ; G 699
+U 779 ; WX 0 ; N uni030B ; G 700
+U 780 ; WX 0 ; N uni030C ; G 701
+U 781 ; WX 0 ; N uni030D ; G 702
+U 782 ; WX 0 ; N uni030E ; G 703
+U 783 ; WX 0 ; N uni030F ; G 704
+U 784 ; WX 0 ; N uni0310 ; G 705
+U 785 ; WX 0 ; N uni0311 ; G 706
+U 786 ; WX 0 ; N uni0312 ; G 707
+U 787 ; WX 0 ; N uni0313 ; G 708
+U 788 ; WX 0 ; N uni0314 ; G 709
+U 789 ; WX 0 ; N uni0315 ; G 710
+U 790 ; WX 0 ; N uni0316 ; G 711
+U 791 ; WX 0 ; N uni0317 ; G 712
+U 792 ; WX 0 ; N uni0318 ; G 713
+U 793 ; WX 0 ; N uni0319 ; G 714
+U 794 ; WX 0 ; N uni031A ; G 715
+U 795 ; WX 0 ; N uni031B ; G 716
+U 796 ; WX 0 ; N uni031C ; G 717
+U 797 ; WX 0 ; N uni031D ; G 718
+U 798 ; WX 0 ; N uni031E ; G 719
+U 799 ; WX 0 ; N uni031F ; G 720
+U 800 ; WX 0 ; N uni0320 ; G 721
+U 801 ; WX 0 ; N uni0321 ; G 722
+U 802 ; WX 0 ; N uni0322 ; G 723
+U 803 ; WX 0 ; N dotbelowcomb ; G 724
+U 804 ; WX 0 ; N uni0324 ; G 725
+U 805 ; WX 0 ; N uni0325 ; G 726
+U 806 ; WX 0 ; N uni0326 ; G 727
+U 807 ; WX 0 ; N uni0327 ; G 728
+U 808 ; WX 0 ; N uni0328 ; G 729
+U 809 ; WX 0 ; N uni0329 ; G 730
+U 810 ; WX 0 ; N uni032A ; G 731
+U 811 ; WX 0 ; N uni032B ; G 732
+U 812 ; WX 0 ; N uni032C ; G 733
+U 813 ; WX 0 ; N uni032D ; G 734
+U 814 ; WX 0 ; N uni032E ; G 735
+U 815 ; WX 0 ; N uni032F ; G 736
+U 816 ; WX 0 ; N uni0330 ; G 737
+U 817 ; WX 0 ; N uni0331 ; G 738
+U 818 ; WX 0 ; N uni0332 ; G 739
+U 819 ; WX 0 ; N uni0333 ; G 740
+U 820 ; WX 0 ; N uni0334 ; G 741
+U 821 ; WX 0 ; N uni0335 ; G 742
+U 822 ; WX 0 ; N uni0336 ; G 743
+U 823 ; WX 0 ; N uni0337 ; G 744
+U 824 ; WX 0 ; N uni0338 ; G 745
+U 825 ; WX 0 ; N uni0339 ; G 746
+U 826 ; WX 0 ; N uni033A ; G 747
+U 827 ; WX 0 ; N uni033B ; G 748
+U 828 ; WX 0 ; N uni033C ; G 749
+U 829 ; WX 0 ; N uni033D ; G 750
+U 830 ; WX 0 ; N uni033E ; G 751
+U 831 ; WX 0 ; N uni033F ; G 752
+U 832 ; WX 0 ; N uni0340 ; G 753
+U 833 ; WX 0 ; N uni0341 ; G 754
+U 834 ; WX 0 ; N uni0342 ; G 755
+U 835 ; WX 0 ; N uni0343 ; G 756
+U 836 ; WX 0 ; N uni0344 ; G 757
+U 837 ; WX 0 ; N uni0345 ; G 758
+U 838 ; WX 0 ; N uni0346 ; G 759
+U 839 ; WX 0 ; N uni0347 ; G 760
+U 840 ; WX 0 ; N uni0348 ; G 761
+U 841 ; WX 0 ; N uni0349 ; G 762
+U 842 ; WX 0 ; N uni034A ; G 763
+U 843 ; WX 0 ; N uni034B ; G 764
+U 844 ; WX 0 ; N uni034C ; G 765
+U 845 ; WX 0 ; N uni034D ; G 766
+U 846 ; WX 0 ; N uni034E ; G 767
+U 847 ; WX 0 ; N uni034F ; G 768
+U 849 ; WX 0 ; N uni0351 ; G 769
+U 850 ; WX 0 ; N uni0352 ; G 770
+U 851 ; WX 0 ; N uni0353 ; G 771
+U 855 ; WX 0 ; N uni0357 ; G 772
+U 856 ; WX 0 ; N uni0358 ; G 773
+U 858 ; WX 0 ; N uni035A ; G 774
+U 860 ; WX 0 ; N uni035C ; G 775
+U 861 ; WX 0 ; N uni035D ; G 776
+U 862 ; WX 0 ; N uni035E ; G 777
+U 863 ; WX 0 ; N uni035F ; G 778
+U 864 ; WX 0 ; N uni0360 ; G 779
+U 865 ; WX 0 ; N uni0361 ; G 780
+U 866 ; WX 0 ; N uni0362 ; G 781
+U 880 ; WX 654 ; N uni0370 ; G 782
+U 881 ; WX 568 ; N uni0371 ; G 783
+U 882 ; WX 862 ; N uni0372 ; G 784
+U 883 ; WX 647 ; N uni0373 ; G 785
+U 884 ; WX 278 ; N uni0374 ; G 786
+U 885 ; WX 278 ; N uni0375 ; G 787
+U 886 ; WX 748 ; N uni0376 ; G 788
+U 887 ; WX 650 ; N uni0377 ; G 789
+U 890 ; WX 500 ; N uni037A ; G 790
+U 891 ; WX 549 ; N uni037B ; G 791
+U 892 ; WX 550 ; N uni037C ; G 792
+U 893 ; WX 549 ; N uni037D ; G 793
+U 894 ; WX 337 ; N uni037E ; G 794
+U 895 ; WX 295 ; N uni037F ; G 795
+U 900 ; WX 500 ; N tonos ; G 796
+U 901 ; WX 500 ; N dieresistonos ; G 797
+U 902 ; WX 684 ; N Alphatonos ; G 798
+U 903 ; WX 318 ; N anoteleia ; G 799
+U 904 ; WX 767 ; N Epsilontonos ; G 800
+U 905 ; WX 903 ; N Etatonos ; G 801
+U 906 ; WX 435 ; N Iotatonos ; G 802
+U 908 ; WX 839 ; N Omicrontonos ; G 803
+U 910 ; WX 860 ; N Upsilontonos ; G 804
+U 911 ; WX 905 ; N Omegatonos ; G 805
+U 912 ; WX 338 ; N iotadieresistonos ; G 806
+U 913 ; WX 684 ; N Alpha ; G 807
+U 914 ; WX 686 ; N Beta ; G 808
+U 915 ; WX 557 ; N Gamma ; G 809
+U 916 ; WX 684 ; N uni0394 ; G 810
+U 917 ; WX 632 ; N Epsilon ; G 811
+U 918 ; WX 685 ; N Zeta ; G 812
+U 919 ; WX 752 ; N Eta ; G 813
+U 920 ; WX 787 ; N Theta ; G 814
+U 921 ; WX 295 ; N Iota ; G 815
+U 922 ; WX 656 ; N Kappa ; G 816
+U 923 ; WX 684 ; N Lambda ; G 817
+U 924 ; WX 863 ; N Mu ; G 818
+U 925 ; WX 748 ; N Nu ; G 819
+U 926 ; WX 632 ; N Xi ; G 820
+U 927 ; WX 787 ; N Omicron ; G 821
+U 928 ; WX 752 ; N Pi ; G 822
+U 929 ; WX 603 ; N Rho ; G 823
+U 931 ; WX 632 ; N Sigma ; G 824
+U 932 ; WX 611 ; N Tau ; G 825
+U 933 ; WX 611 ; N Upsilon ; G 826
+U 934 ; WX 787 ; N Phi ; G 827
+U 935 ; WX 685 ; N Chi ; G 828
+U 936 ; WX 787 ; N Psi ; G 829
+U 937 ; WX 764 ; N Omega ; G 830
+U 938 ; WX 295 ; N Iotadieresis ; G 831
+U 939 ; WX 611 ; N Upsilondieresis ; G 832
+U 940 ; WX 659 ; N alphatonos ; G 833
+U 941 ; WX 541 ; N epsilontonos ; G 834
+U 942 ; WX 634 ; N etatonos ; G 835
+U 943 ; WX 338 ; N iotatonos ; G 836
+U 944 ; WX 579 ; N upsilondieresistonos ; G 837
+U 945 ; WX 659 ; N alpha ; G 838
+U 946 ; WX 638 ; N beta ; G 839
+U 947 ; WX 592 ; N gamma ; G 840
+U 948 ; WX 612 ; N delta ; G 841
+U 949 ; WX 541 ; N epsilon ; G 842
+U 950 ; WX 544 ; N zeta ; G 843
+U 951 ; WX 634 ; N eta ; G 844
+U 952 ; WX 612 ; N theta ; G 845
+U 953 ; WX 338 ; N iota ; G 846
+U 954 ; WX 589 ; N kappa ; G 847
+U 955 ; WX 592 ; N lambda ; G 848
+U 956 ; WX 636 ; N uni03BC ; G 849
+U 957 ; WX 559 ; N nu ; G 850
+U 958 ; WX 558 ; N xi ; G 851
+U 959 ; WX 612 ; N omicron ; G 852
+U 960 ; WX 602 ; N pi ; G 853
+U 961 ; WX 635 ; N rho ; G 854
+U 962 ; WX 587 ; N sigma1 ; G 855
+U 963 ; WX 634 ; N sigma ; G 856
+U 964 ; WX 602 ; N tau ; G 857
+U 965 ; WX 579 ; N upsilon ; G 858
+U 966 ; WX 660 ; N phi ; G 859
+U 967 ; WX 592 ; N chi ; G 860
+U 968 ; WX 660 ; N psi ; G 861
+U 969 ; WX 837 ; N omega ; G 862
+U 970 ; WX 338 ; N iotadieresis ; G 863
+U 971 ; WX 579 ; N upsilondieresis ; G 864
+U 972 ; WX 612 ; N omicrontonos ; G 865
+U 973 ; WX 579 ; N upsilontonos ; G 866
+U 974 ; WX 837 ; N omegatonos ; G 867
+U 975 ; WX 656 ; N uni03CF ; G 868
+U 976 ; WX 614 ; N uni03D0 ; G 869
+U 977 ; WX 619 ; N theta1 ; G 870
+U 978 ; WX 699 ; N Upsilon1 ; G 871
+U 979 ; WX 842 ; N uni03D3 ; G 872
+U 980 ; WX 699 ; N uni03D4 ; G 873
+U 981 ; WX 660 ; N phi1 ; G 874
+U 982 ; WX 837 ; N omega1 ; G 875
+U 983 ; WX 664 ; N uni03D7 ; G 876
+U 984 ; WX 787 ; N uni03D8 ; G 877
+U 985 ; WX 612 ; N uni03D9 ; G 878
+U 986 ; WX 648 ; N uni03DA ; G 879
+U 987 ; WX 587 ; N uni03DB ; G 880
+U 988 ; WX 575 ; N uni03DC ; G 881
+U 989 ; WX 458 ; N uni03DD ; G 882
+U 990 ; WX 660 ; N uni03DE ; G 883
+U 991 ; WX 660 ; N uni03DF ; G 884
+U 992 ; WX 865 ; N uni03E0 ; G 885
+U 993 ; WX 627 ; N uni03E1 ; G 886
+U 994 ; WX 934 ; N uni03E2 ; G 887
+U 995 ; WX 837 ; N uni03E3 ; G 888
+U 996 ; WX 758 ; N uni03E4 ; G 889
+U 997 ; WX 659 ; N uni03E5 ; G 890
+U 998 ; WX 792 ; N uni03E6 ; G 891
+U 999 ; WX 615 ; N uni03E7 ; G 892
+U 1000 ; WX 687 ; N uni03E8 ; G 893
+U 1001 ; WX 607 ; N uni03E9 ; G 894
+U 1002 ; WX 768 ; N uni03EA ; G 895
+U 1003 ; WX 625 ; N uni03EB ; G 896
+U 1004 ; WX 699 ; N uni03EC ; G 897
+U 1005 ; WX 612 ; N uni03ED ; G 898
+U 1006 ; WX 611 ; N uni03EE ; G 899
+U 1007 ; WX 536 ; N uni03EF ; G 900
+U 1008 ; WX 664 ; N uni03F0 ; G 901
+U 1009 ; WX 635 ; N uni03F1 ; G 902
+U 1010 ; WX 550 ; N uni03F2 ; G 903
+U 1011 ; WX 278 ; N uni03F3 ; G 904
+U 1012 ; WX 787 ; N uni03F4 ; G 905
+U 1013 ; WX 615 ; N uni03F5 ; G 906
+U 1014 ; WX 615 ; N uni03F6 ; G 907
+U 1015 ; WX 608 ; N uni03F7 ; G 908
+U 1016 ; WX 635 ; N uni03F8 ; G 909
+U 1017 ; WX 698 ; N uni03F9 ; G 910
+U 1018 ; WX 863 ; N uni03FA ; G 911
+U 1019 ; WX 651 ; N uni03FB ; G 912
+U 1020 ; WX 635 ; N uni03FC ; G 913
+U 1021 ; WX 703 ; N uni03FD ; G 914
+U 1022 ; WX 698 ; N uni03FE ; G 915
+U 1023 ; WX 703 ; N uni03FF ; G 916
+U 1024 ; WX 632 ; N uni0400 ; G 917
+U 1025 ; WX 632 ; N uni0401 ; G 918
+U 1026 ; WX 786 ; N uni0402 ; G 919
+U 1027 ; WX 557 ; N uni0403 ; G 920
+U 1028 ; WX 698 ; N uni0404 ; G 921
+U 1029 ; WX 635 ; N uni0405 ; G 922
+U 1030 ; WX 295 ; N uni0406 ; G 923
+U 1031 ; WX 295 ; N uni0407 ; G 924
+U 1032 ; WX 295 ; N uni0408 ; G 925
+U 1033 ; WX 1094 ; N uni0409 ; G 926
+U 1034 ; WX 1045 ; N uni040A ; G 927
+U 1035 ; WX 786 ; N uni040B ; G 928
+U 1036 ; WX 710 ; N uni040C ; G 929
+U 1037 ; WX 748 ; N uni040D ; G 930
+U 1038 ; WX 609 ; N uni040E ; G 931
+U 1039 ; WX 752 ; N uni040F ; G 932
+U 1040 ; WX 684 ; N uni0410 ; G 933
+U 1041 ; WX 686 ; N uni0411 ; G 934
+U 1042 ; WX 686 ; N uni0412 ; G 935
+U 1043 ; WX 557 ; N uni0413 ; G 936
+U 1044 ; WX 781 ; N uni0414 ; G 937
+U 1045 ; WX 632 ; N uni0415 ; G 938
+U 1046 ; WX 1077 ; N uni0416 ; G 939
+U 1047 ; WX 641 ; N uni0417 ; G 940
+U 1048 ; WX 748 ; N uni0418 ; G 941
+U 1049 ; WX 748 ; N uni0419 ; G 942
+U 1050 ; WX 710 ; N uni041A ; G 943
+U 1051 ; WX 752 ; N uni041B ; G 944
+U 1052 ; WX 863 ; N uni041C ; G 945
+U 1053 ; WX 752 ; N uni041D ; G 946
+U 1054 ; WX 787 ; N uni041E ; G 947
+U 1055 ; WX 752 ; N uni041F ; G 948
+U 1056 ; WX 603 ; N uni0420 ; G 949
+U 1057 ; WX 698 ; N uni0421 ; G 950
+U 1058 ; WX 611 ; N uni0422 ; G 951
+U 1059 ; WX 609 ; N uni0423 ; G 952
+U 1060 ; WX 861 ; N uni0424 ; G 953
+U 1061 ; WX 685 ; N uni0425 ; G 954
+U 1062 ; WX 776 ; N uni0426 ; G 955
+U 1063 ; WX 686 ; N uni0427 ; G 956
+U 1064 ; WX 1069 ; N uni0428 ; G 957
+U 1065 ; WX 1094 ; N uni0429 ; G 958
+U 1066 ; WX 833 ; N uni042A ; G 959
+U 1067 ; WX 818 ; N uni042B ; G 960
+U 1068 ; WX 686 ; N uni042C ; G 961
+U 1069 ; WX 698 ; N uni042D ; G 962
+U 1070 ; WX 1080 ; N uni042E ; G 963
+U 1071 ; WX 695 ; N uni042F ; G 964
+U 1072 ; WX 613 ; N uni0430 ; G 965
+U 1073 ; WX 617 ; N uni0431 ; G 966
+U 1074 ; WX 589 ; N uni0432 ; G 967
+U 1075 ; WX 525 ; N uni0433 ; G 968
+U 1076 ; WX 691 ; N uni0434 ; G 969
+U 1077 ; WX 615 ; N uni0435 ; G 970
+U 1078 ; WX 901 ; N uni0436 ; G 971
+U 1079 ; WX 532 ; N uni0437 ; G 972
+U 1080 ; WX 650 ; N uni0438 ; G 973
+U 1081 ; WX 650 ; N uni0439 ; G 974
+U 1082 ; WX 604 ; N uni043A ; G 975
+U 1083 ; WX 639 ; N uni043B ; G 976
+U 1084 ; WX 754 ; N uni043C ; G 977
+U 1085 ; WX 654 ; N uni043D ; G 978
+U 1086 ; WX 612 ; N uni043E ; G 979
+U 1087 ; WX 654 ; N uni043F ; G 980
+U 1088 ; WX 635 ; N uni0440 ; G 981
+U 1089 ; WX 550 ; N uni0441 ; G 982
+U 1090 ; WX 583 ; N uni0442 ; G 983
+U 1091 ; WX 592 ; N uni0443 ; G 984
+U 1092 ; WX 855 ; N uni0444 ; G 985
+U 1093 ; WX 592 ; N uni0445 ; G 986
+U 1094 ; WX 681 ; N uni0446 ; G 987
+U 1095 ; WX 591 ; N uni0447 ; G 988
+U 1096 ; WX 915 ; N uni0448 ; G 989
+U 1097 ; WX 942 ; N uni0449 ; G 990
+U 1098 ; WX 707 ; N uni044A ; G 991
+U 1099 ; WX 790 ; N uni044B ; G 992
+U 1100 ; WX 589 ; N uni044C ; G 993
+U 1101 ; WX 549 ; N uni044D ; G 994
+U 1102 ; WX 842 ; N uni044E ; G 995
+U 1103 ; WX 602 ; N uni044F ; G 996
+U 1104 ; WX 615 ; N uni0450 ; G 997
+U 1105 ; WX 615 ; N uni0451 ; G 998
+U 1106 ; WX 625 ; N uni0452 ; G 999
+U 1107 ; WX 525 ; N uni0453 ; G 1000
+U 1108 ; WX 549 ; N uni0454 ; G 1001
+U 1109 ; WX 521 ; N uni0455 ; G 1002
+U 1110 ; WX 278 ; N uni0456 ; G 1003
+U 1111 ; WX 278 ; N uni0457 ; G 1004
+U 1112 ; WX 278 ; N uni0458 ; G 1005
+U 1113 ; WX 902 ; N uni0459 ; G 1006
+U 1114 ; WX 898 ; N uni045A ; G 1007
+U 1115 ; WX 652 ; N uni045B ; G 1008
+U 1116 ; WX 604 ; N uni045C ; G 1009
+U 1117 ; WX 650 ; N uni045D ; G 1010
+U 1118 ; WX 592 ; N uni045E ; G 1011
+U 1119 ; WX 654 ; N uni045F ; G 1012
+U 1120 ; WX 934 ; N uni0460 ; G 1013
+U 1121 ; WX 837 ; N uni0461 ; G 1014
+U 1122 ; WX 771 ; N uni0462 ; G 1015
+U 1123 ; WX 672 ; N uni0463 ; G 1016
+U 1124 ; WX 942 ; N uni0464 ; G 1017
+U 1125 ; WX 749 ; N uni0465 ; G 1018
+U 1126 ; WX 879 ; N uni0466 ; G 1019
+U 1127 ; WX 783 ; N uni0467 ; G 1020
+U 1128 ; WX 1160 ; N uni0468 ; G 1021
+U 1129 ; WX 1001 ; N uni0469 ; G 1022
+U 1130 ; WX 787 ; N uni046A ; G 1023
+U 1131 ; WX 612 ; N uni046B ; G 1024
+U 1132 ; WX 1027 ; N uni046C ; G 1025
+U 1133 ; WX 824 ; N uni046D ; G 1026
+U 1134 ; WX 636 ; N uni046E ; G 1027
+U 1135 ; WX 541 ; N uni046F ; G 1028
+U 1136 ; WX 856 ; N uni0470 ; G 1029
+U 1137 ; WX 876 ; N uni0471 ; G 1030
+U 1138 ; WX 787 ; N uni0472 ; G 1031
+U 1139 ; WX 612 ; N uni0473 ; G 1032
+U 1140 ; WX 781 ; N uni0474 ; G 1033
+U 1141 ; WX 665 ; N uni0475 ; G 1034
+U 1142 ; WX 781 ; N uni0476 ; G 1035
+U 1143 ; WX 665 ; N uni0477 ; G 1036
+U 1144 ; WX 992 ; N uni0478 ; G 1037
+U 1145 ; WX 904 ; N uni0479 ; G 1038
+U 1146 ; WX 953 ; N uni047A ; G 1039
+U 1147 ; WX 758 ; N uni047B ; G 1040
+U 1148 ; WX 1180 ; N uni047C ; G 1041
+U 1149 ; WX 1028 ; N uni047D ; G 1042
+U 1150 ; WX 934 ; N uni047E ; G 1043
+U 1151 ; WX 837 ; N uni047F ; G 1044
+U 1152 ; WX 698 ; N uni0480 ; G 1045
+U 1153 ; WX 550 ; N uni0481 ; G 1046
+U 1154 ; WX 502 ; N uni0482 ; G 1047
+U 1155 ; WX 0 ; N uni0483 ; G 1048
+U 1156 ; WX 0 ; N uni0484 ; G 1049
+U 1157 ; WX 0 ; N uni0485 ; G 1050
+U 1158 ; WX 0 ; N uni0486 ; G 1051
+U 1159 ; WX 0 ; N uni0487 ; G 1052
+U 1160 ; WX 418 ; N uni0488 ; G 1053
+U 1161 ; WX 418 ; N uni0489 ; G 1054
+U 1162 ; WX 748 ; N uni048A ; G 1055
+U 1163 ; WX 657 ; N uni048B ; G 1056
+U 1164 ; WX 686 ; N uni048C ; G 1057
+U 1165 ; WX 589 ; N uni048D ; G 1058
+U 1166 ; WX 603 ; N uni048E ; G 1059
+U 1167 ; WX 635 ; N uni048F ; G 1060
+U 1168 ; WX 610 ; N uni0490 ; G 1061
+U 1169 ; WX 525 ; N uni0491 ; G 1062
+U 1170 ; WX 675 ; N uni0492 ; G 1063
+U 1171 ; WX 556 ; N uni0493 ; G 1064
+U 1172 ; WX 557 ; N uni0494 ; G 1065
+U 1173 ; WX 491 ; N uni0495 ; G 1066
+U 1174 ; WX 1077 ; N uni0496 ; G 1067
+U 1175 ; WX 901 ; N uni0497 ; G 1068
+U 1176 ; WX 641 ; N uni0498 ; G 1069
+U 1177 ; WX 532 ; N uni0499 ; G 1070
+U 1178 ; WX 710 ; N uni049A ; G 1071
+U 1179 ; WX 604 ; N uni049B ; G 1072
+U 1180 ; WX 710 ; N uni049C ; G 1073
+U 1181 ; WX 604 ; N uni049D ; G 1074
+U 1182 ; WX 710 ; N uni049E ; G 1075
+U 1183 ; WX 604 ; N uni049F ; G 1076
+U 1184 ; WX 856 ; N uni04A0 ; G 1077
+U 1185 ; WX 832 ; N uni04A1 ; G 1078
+U 1186 ; WX 752 ; N uni04A2 ; G 1079
+U 1187 ; WX 661 ; N uni04A3 ; G 1080
+U 1188 ; WX 1014 ; N uni04A4 ; G 1081
+U 1189 ; WX 877 ; N uni04A5 ; G 1082
+U 1190 ; WX 1113 ; N uni04A6 ; G 1083
+U 1191 ; WX 950 ; N uni04A7 ; G 1084
+U 1192 ; WX 890 ; N uni04A8 ; G 1085
+U 1193 ; WX 707 ; N uni04A9 ; G 1086
+U 1194 ; WX 698 ; N uni04AA ; G 1087
+U 1195 ; WX 550 ; N uni04AB ; G 1088
+U 1196 ; WX 611 ; N uni04AC ; G 1089
+U 1197 ; WX 529 ; N uni04AD ; G 1090
+U 1198 ; WX 611 ; N uni04AE ; G 1091
+U 1199 ; WX 592 ; N uni04AF ; G 1092
+U 1200 ; WX 611 ; N uni04B0 ; G 1093
+U 1201 ; WX 592 ; N uni04B1 ; G 1094
+U 1202 ; WX 685 ; N uni04B2 ; G 1095
+U 1203 ; WX 592 ; N uni04B3 ; G 1096
+U 1204 ; WX 934 ; N uni04B4 ; G 1097
+U 1205 ; WX 807 ; N uni04B5 ; G 1098
+U 1206 ; WX 686 ; N uni04B6 ; G 1099
+U 1207 ; WX 591 ; N uni04B7 ; G 1100
+U 1208 ; WX 686 ; N uni04B8 ; G 1101
+U 1209 ; WX 591 ; N uni04B9 ; G 1102
+U 1210 ; WX 686 ; N uni04BA ; G 1103
+U 1211 ; WX 634 ; N uni04BB ; G 1104
+U 1212 ; WX 929 ; N uni04BC ; G 1105
+U 1213 ; WX 731 ; N uni04BD ; G 1106
+U 1214 ; WX 929 ; N uni04BE ; G 1107
+U 1215 ; WX 731 ; N uni04BF ; G 1108
+U 1216 ; WX 295 ; N uni04C0 ; G 1109
+U 1217 ; WX 1077 ; N uni04C1 ; G 1110
+U 1218 ; WX 901 ; N uni04C2 ; G 1111
+U 1219 ; WX 655 ; N uni04C3 ; G 1112
+U 1220 ; WX 604 ; N uni04C4 ; G 1113
+U 1221 ; WX 752 ; N uni04C5 ; G 1114
+U 1222 ; WX 639 ; N uni04C6 ; G 1115
+U 1223 ; WX 752 ; N uni04C7 ; G 1116
+U 1224 ; WX 661 ; N uni04C8 ; G 1117
+U 1225 ; WX 752 ; N uni04C9 ; G 1118
+U 1226 ; WX 661 ; N uni04CA ; G 1119
+U 1227 ; WX 686 ; N uni04CB ; G 1120
+U 1228 ; WX 591 ; N uni04CC ; G 1121
+U 1229 ; WX 863 ; N uni04CD ; G 1122
+U 1230 ; WX 754 ; N uni04CE ; G 1123
+U 1231 ; WX 278 ; N uni04CF ; G 1124
+U 1232 ; WX 684 ; N uni04D0 ; G 1125
+U 1233 ; WX 613 ; N uni04D1 ; G 1126
+U 1234 ; WX 684 ; N uni04D2 ; G 1127
+U 1235 ; WX 613 ; N uni04D3 ; G 1128
+U 1236 ; WX 974 ; N uni04D4 ; G 1129
+U 1237 ; WX 995 ; N uni04D5 ; G 1130
+U 1238 ; WX 632 ; N uni04D6 ; G 1131
+U 1239 ; WX 615 ; N uni04D7 ; G 1132
+U 1240 ; WX 787 ; N uni04D8 ; G 1133
+U 1241 ; WX 615 ; N uni04D9 ; G 1134
+U 1242 ; WX 787 ; N uni04DA ; G 1135
+U 1243 ; WX 615 ; N uni04DB ; G 1136
+U 1244 ; WX 1077 ; N uni04DC ; G 1137
+U 1245 ; WX 901 ; N uni04DD ; G 1138
+U 1246 ; WX 641 ; N uni04DE ; G 1139
+U 1247 ; WX 532 ; N uni04DF ; G 1140
+U 1248 ; WX 666 ; N uni04E0 ; G 1141
+U 1249 ; WX 578 ; N uni04E1 ; G 1142
+U 1250 ; WX 748 ; N uni04E2 ; G 1143
+U 1251 ; WX 650 ; N uni04E3 ; G 1144
+U 1252 ; WX 748 ; N uni04E4 ; G 1145
+U 1253 ; WX 650 ; N uni04E5 ; G 1146
+U 1254 ; WX 787 ; N uni04E6 ; G 1147
+U 1255 ; WX 612 ; N uni04E7 ; G 1148
+U 1256 ; WX 787 ; N uni04E8 ; G 1149
+U 1257 ; WX 612 ; N uni04E9 ; G 1150
+U 1258 ; WX 787 ; N uni04EA ; G 1151
+U 1259 ; WX 612 ; N uni04EB ; G 1152
+U 1260 ; WX 698 ; N uni04EC ; G 1153
+U 1261 ; WX 549 ; N uni04ED ; G 1154
+U 1262 ; WX 609 ; N uni04EE ; G 1155
+U 1263 ; WX 592 ; N uni04EF ; G 1156
+U 1264 ; WX 609 ; N uni04F0 ; G 1157
+U 1265 ; WX 592 ; N uni04F1 ; G 1158
+U 1266 ; WX 609 ; N uni04F2 ; G 1159
+U 1267 ; WX 592 ; N uni04F3 ; G 1160
+U 1268 ; WX 686 ; N uni04F4 ; G 1161
+U 1269 ; WX 591 ; N uni04F5 ; G 1162
+U 1270 ; WX 557 ; N uni04F6 ; G 1163
+U 1271 ; WX 491 ; N uni04F7 ; G 1164
+U 1272 ; WX 818 ; N uni04F8 ; G 1165
+U 1273 ; WX 790 ; N uni04F9 ; G 1166
+U 1274 ; WX 675 ; N uni04FA ; G 1167
+U 1275 ; WX 556 ; N uni04FB ; G 1168
+U 1276 ; WX 685 ; N uni04FC ; G 1169
+U 1277 ; WX 592 ; N uni04FD ; G 1170
+U 1278 ; WX 685 ; N uni04FE ; G 1171
+U 1279 ; WX 592 ; N uni04FF ; G 1172
+U 1280 ; WX 686 ; N uni0500 ; G 1173
+U 1281 ; WX 589 ; N uni0501 ; G 1174
+U 1282 ; WX 1006 ; N uni0502 ; G 1175
+U 1283 ; WX 897 ; N uni0503 ; G 1176
+U 1284 ; WX 975 ; N uni0504 ; G 1177
+U 1285 ; WX 869 ; N uni0505 ; G 1178
+U 1286 ; WX 679 ; N uni0506 ; G 1179
+U 1287 ; WX 588 ; N uni0507 ; G 1180
+U 1288 ; WX 1072 ; N uni0508 ; G 1181
+U 1289 ; WX 957 ; N uni0509 ; G 1182
+U 1290 ; WX 1113 ; N uni050A ; G 1183
+U 1291 ; WX 967 ; N uni050B ; G 1184
+U 1292 ; WX 775 ; N uni050C ; G 1185
+U 1293 ; WX 660 ; N uni050D ; G 1186
+U 1294 ; WX 773 ; N uni050E ; G 1187
+U 1295 ; WX 711 ; N uni050F ; G 1188
+U 1296 ; WX 614 ; N uni0510 ; G 1189
+U 1297 ; WX 541 ; N uni0511 ; G 1190
+U 1298 ; WX 752 ; N uni0512 ; G 1191
+U 1299 ; WX 639 ; N uni0513 ; G 1192
+U 1300 ; WX 1195 ; N uni0514 ; G 1193
+U 1301 ; WX 997 ; N uni0515 ; G 1194
+U 1302 ; WX 900 ; N uni0516 ; G 1195
+U 1303 ; WX 867 ; N uni0517 ; G 1196
+U 1304 ; WX 1031 ; N uni0518 ; G 1197
+U 1305 ; WX 989 ; N uni0519 ; G 1198
+U 1306 ; WX 787 ; N uni051A ; G 1199
+U 1307 ; WX 635 ; N uni051B ; G 1200
+U 1308 ; WX 989 ; N uni051C ; G 1201
+U 1309 ; WX 818 ; N uni051D ; G 1202
+U 1310 ; WX 710 ; N uni051E ; G 1203
+U 1311 ; WX 604 ; N uni051F ; G 1204
+U 1312 ; WX 1113 ; N uni0520 ; G 1205
+U 1313 ; WX 942 ; N uni0521 ; G 1206
+U 1314 ; WX 1113 ; N uni0522 ; G 1207
+U 1315 ; WX 949 ; N uni0523 ; G 1208
+U 1316 ; WX 793 ; N uni0524 ; G 1209
+U 1317 ; WX 683 ; N uni0525 ; G 1210
+U 1329 ; WX 766 ; N uni0531 ; G 1211
+U 1330 ; WX 732 ; N uni0532 ; G 1212
+U 1331 ; WX 753 ; N uni0533 ; G 1213
+U 1332 ; WX 753 ; N uni0534 ; G 1214
+U 1333 ; WX 732 ; N uni0535 ; G 1215
+U 1334 ; WX 772 ; N uni0536 ; G 1216
+U 1335 ; WX 640 ; N uni0537 ; G 1217
+U 1336 ; WX 732 ; N uni0538 ; G 1218
+U 1337 ; WX 859 ; N uni0539 ; G 1219
+U 1338 ; WX 753 ; N uni053A ; G 1220
+U 1339 ; WX 691 ; N uni053B ; G 1221
+U 1340 ; WX 533 ; N uni053C ; G 1222
+U 1341 ; WX 922 ; N uni053D ; G 1223
+U 1342 ; WX 863 ; N uni053E ; G 1224
+U 1343 ; WX 732 ; N uni053F ; G 1225
+U 1344 ; WX 716 ; N uni0540 ; G 1226
+U 1345 ; WX 766 ; N uni0541 ; G 1227
+U 1346 ; WX 753 ; N uni0542 ; G 1228
+U 1347 ; WX 767 ; N uni0543 ; G 1229
+U 1348 ; WX 792 ; N uni0544 ; G 1230
+U 1349 ; WX 728 ; N uni0545 ; G 1231
+U 1350 ; WX 729 ; N uni0546 ; G 1232
+U 1351 ; WX 757 ; N uni0547 ; G 1233
+U 1352 ; WX 732 ; N uni0548 ; G 1234
+U 1353 ; WX 713 ; N uni0549 ; G 1235
+U 1354 ; WX 800 ; N uni054A ; G 1236
+U 1355 ; WX 768 ; N uni054B ; G 1237
+U 1356 ; WX 792 ; N uni054C ; G 1238
+U 1357 ; WX 732 ; N uni054D ; G 1239
+U 1358 ; WX 753 ; N uni054E ; G 1240
+U 1359 ; WX 705 ; N uni054F ; G 1241
+U 1360 ; WX 694 ; N uni0550 ; G 1242
+U 1361 ; WX 744 ; N uni0551 ; G 1243
+U 1362 ; WX 538 ; N uni0552 ; G 1244
+U 1363 ; WX 811 ; N uni0553 ; G 1245
+U 1364 ; WX 757 ; N uni0554 ; G 1246
+U 1365 ; WX 787 ; N uni0555 ; G 1247
+U 1366 ; WX 790 ; N uni0556 ; G 1248
+U 1369 ; WX 307 ; N uni0559 ; G 1249
+U 1370 ; WX 318 ; N uni055A ; G 1250
+U 1371 ; WX 234 ; N uni055B ; G 1251
+U 1372 ; WX 361 ; N uni055C ; G 1252
+U 1373 ; WX 238 ; N uni055D ; G 1253
+U 1374 ; WX 405 ; N uni055E ; G 1254
+U 1375 ; WX 500 ; N uni055F ; G 1255
+U 1377 ; WX 974 ; N uni0561 ; G 1256
+U 1378 ; WX 634 ; N uni0562 ; G 1257
+U 1379 ; WX 658 ; N uni0563 ; G 1258
+U 1380 ; WX 663 ; N uni0564 ; G 1259
+U 1381 ; WX 634 ; N uni0565 ; G 1260
+U 1382 ; WX 635 ; N uni0566 ; G 1261
+U 1383 ; WX 515 ; N uni0567 ; G 1262
+U 1384 ; WX 634 ; N uni0568 ; G 1263
+U 1385 ; WX 738 ; N uni0569 ; G 1264
+U 1386 ; WX 658 ; N uni056A ; G 1265
+U 1387 ; WX 634 ; N uni056B ; G 1266
+U 1388 ; WX 271 ; N uni056C ; G 1267
+U 1389 ; WX 980 ; N uni056D ; G 1268
+U 1390 ; WX 623 ; N uni056E ; G 1269
+U 1391 ; WX 634 ; N uni056F ; G 1270
+U 1392 ; WX 634 ; N uni0570 ; G 1271
+U 1393 ; WX 608 ; N uni0571 ; G 1272
+U 1394 ; WX 634 ; N uni0572 ; G 1273
+U 1395 ; WX 629 ; N uni0573 ; G 1274
+U 1396 ; WX 634 ; N uni0574 ; G 1275
+U 1397 ; WX 278 ; N uni0575 ; G 1276
+U 1398 ; WX 634 ; N uni0576 ; G 1277
+U 1399 ; WX 499 ; N uni0577 ; G 1278
+U 1400 ; WX 634 ; N uni0578 ; G 1279
+U 1401 ; WX 404 ; N uni0579 ; G 1280
+U 1402 ; WX 974 ; N uni057A ; G 1281
+U 1403 ; WX 560 ; N uni057B ; G 1282
+U 1404 ; WX 648 ; N uni057C ; G 1283
+U 1405 ; WX 634 ; N uni057D ; G 1284
+U 1406 ; WX 634 ; N uni057E ; G 1285
+U 1407 ; WX 974 ; N uni057F ; G 1286
+U 1408 ; WX 634 ; N uni0580 ; G 1287
+U 1409 ; WX 635 ; N uni0581 ; G 1288
+U 1410 ; WX 435 ; N uni0582 ; G 1289
+U 1411 ; WX 974 ; N uni0583 ; G 1290
+U 1412 ; WX 636 ; N uni0584 ; G 1291
+U 1413 ; WX 612 ; N uni0585 ; G 1292
+U 1414 ; WX 805 ; N uni0586 ; G 1293
+U 1415 ; WX 812 ; N uni0587 ; G 1294
+U 1417 ; WX 337 ; N uni0589 ; G 1295
+U 1418 ; WX 361 ; N uni058A ; G 1296
+U 1456 ; WX 0 ; N uni05B0 ; G 1297
+U 1457 ; WX 0 ; N uni05B1 ; G 1298
+U 1458 ; WX 0 ; N uni05B2 ; G 1299
+U 1459 ; WX 0 ; N uni05B3 ; G 1300
+U 1460 ; WX 0 ; N uni05B4 ; G 1301
+U 1461 ; WX 0 ; N uni05B5 ; G 1302
+U 1462 ; WX 0 ; N uni05B6 ; G 1303
+U 1463 ; WX 0 ; N uni05B7 ; G 1304
+U 1464 ; WX 0 ; N uni05B8 ; G 1305
+U 1465 ; WX 0 ; N uni05B9 ; G 1306
+U 1466 ; WX 0 ; N uni05BA ; G 1307
+U 1467 ; WX 0 ; N uni05BB ; G 1308
+U 1468 ; WX 0 ; N uni05BC ; G 1309
+U 1469 ; WX 0 ; N uni05BD ; G 1310
+U 1470 ; WX 361 ; N uni05BE ; G 1311
+U 1471 ; WX 0 ; N uni05BF ; G 1312
+U 1472 ; WX 295 ; N uni05C0 ; G 1313
+U 1473 ; WX 0 ; N uni05C1 ; G 1314
+U 1474 ; WX 0 ; N uni05C2 ; G 1315
+U 1475 ; WX 295 ; N uni05C3 ; G 1316
+U 1478 ; WX 456 ; N uni05C6 ; G 1317
+U 1479 ; WX 0 ; N uni05C7 ; G 1318
+U 1488 ; WX 668 ; N uni05D0 ; G 1319
+U 1489 ; WX 578 ; N uni05D1 ; G 1320
+U 1490 ; WX 412 ; N uni05D2 ; G 1321
+U 1491 ; WX 546 ; N uni05D3 ; G 1322
+U 1492 ; WX 653 ; N uni05D4 ; G 1323
+U 1493 ; WX 272 ; N uni05D5 ; G 1324
+U 1494 ; WX 346 ; N uni05D6 ; G 1325
+U 1495 ; WX 653 ; N uni05D7 ; G 1326
+U 1496 ; WX 648 ; N uni05D8 ; G 1327
+U 1497 ; WX 224 ; N uni05D9 ; G 1328
+U 1498 ; WX 537 ; N uni05DA ; G 1329
+U 1499 ; WX 529 ; N uni05DB ; G 1330
+U 1500 ; WX 568 ; N uni05DC ; G 1331
+U 1501 ; WX 664 ; N uni05DD ; G 1332
+U 1502 ; WX 679 ; N uni05DE ; G 1333
+U 1503 ; WX 272 ; N uni05DF ; G 1334
+U 1504 ; WX 400 ; N uni05E0 ; G 1335
+U 1505 ; WX 649 ; N uni05E1 ; G 1336
+U 1506 ; WX 626 ; N uni05E2 ; G 1337
+U 1507 ; WX 640 ; N uni05E3 ; G 1338
+U 1508 ; WX 625 ; N uni05E4 ; G 1339
+U 1509 ; WX 540 ; N uni05E5 ; G 1340
+U 1510 ; WX 593 ; N uni05E6 ; G 1341
+U 1511 ; WX 709 ; N uni05E7 ; G 1342
+U 1512 ; WX 564 ; N uni05E8 ; G 1343
+U 1513 ; WX 708 ; N uni05E9 ; G 1344
+U 1514 ; WX 657 ; N uni05EA ; G 1345
+U 1520 ; WX 471 ; N uni05F0 ; G 1346
+U 1521 ; WX 454 ; N uni05F1 ; G 1347
+U 1522 ; WX 471 ; N uni05F2 ; G 1348
+U 1523 ; WX 416 ; N uni05F3 ; G 1349
+U 1524 ; WX 645 ; N uni05F4 ; G 1350
+U 3647 ; WX 636 ; N uni0E3F ; G 1351
+U 3713 ; WX 670 ; N uni0E81 ; G 1352
+U 3714 ; WX 684 ; N uni0E82 ; G 1353
+U 3716 ; WX 688 ; N uni0E84 ; G 1354
+U 3719 ; WX 482 ; N uni0E87 ; G 1355
+U 3720 ; WX 628 ; N uni0E88 ; G 1356
+U 3722 ; WX 684 ; N uni0E8A ; G 1357
+U 3725 ; WX 688 ; N uni0E8D ; G 1358
+U 3732 ; WX 642 ; N uni0E94 ; G 1359
+U 3733 ; WX 642 ; N uni0E95 ; G 1360
+U 3734 ; WX 672 ; N uni0E96 ; G 1361
+U 3735 ; WX 655 ; N uni0E97 ; G 1362
+U 3737 ; WX 641 ; N uni0E99 ; G 1363
+U 3738 ; WX 592 ; N uni0E9A ; G 1364
+U 3739 ; WX 592 ; N uni0E9B ; G 1365
+U 3740 ; WX 745 ; N uni0E9C ; G 1366
+U 3741 ; WX 767 ; N uni0E9D ; G 1367
+U 3742 ; WX 687 ; N uni0E9E ; G 1368
+U 3743 ; WX 687 ; N uni0E9F ; G 1369
+U 3745 ; WX 702 ; N uni0EA1 ; G 1370
+U 3746 ; WX 688 ; N uni0EA2 ; G 1371
+U 3747 ; WX 684 ; N uni0EA3 ; G 1372
+U 3749 ; WX 649 ; N uni0EA5 ; G 1373
+U 3751 ; WX 632 ; N uni0EA7 ; G 1374
+U 3754 ; WX 703 ; N uni0EAA ; G 1375
+U 3755 ; WX 819 ; N uni0EAB ; G 1376
+U 3757 ; WX 633 ; N uni0EAD ; G 1377
+U 3758 ; WX 684 ; N uni0EAE ; G 1378
+U 3759 ; WX 788 ; N uni0EAF ; G 1379
+U 3760 ; WX 632 ; N uni0EB0 ; G 1380
+U 3761 ; WX 0 ; N uni0EB1 ; G 1381
+U 3762 ; WX 539 ; N uni0EB2 ; G 1382
+U 3763 ; WX 539 ; N uni0EB3 ; G 1383
+U 3764 ; WX 0 ; N uni0EB4 ; G 1384
+U 3765 ; WX 0 ; N uni0EB5 ; G 1385
+U 3766 ; WX 0 ; N uni0EB6 ; G 1386
+U 3767 ; WX 0 ; N uni0EB7 ; G 1387
+U 3768 ; WX 0 ; N uni0EB8 ; G 1388
+U 3769 ; WX 0 ; N uni0EB9 ; G 1389
+U 3771 ; WX 0 ; N uni0EBB ; G 1390
+U 3772 ; WX 0 ; N uni0EBC ; G 1391
+U 3773 ; WX 663 ; N uni0EBD ; G 1392
+U 3776 ; WX 360 ; N uni0EC0 ; G 1393
+U 3777 ; WX 679 ; N uni0EC1 ; G 1394
+U 3778 ; WX 460 ; N uni0EC2 ; G 1395
+U 3779 ; WX 547 ; N uni0EC3 ; G 1396
+U 3780 ; WX 491 ; N uni0EC4 ; G 1397
+U 3782 ; WX 674 ; N uni0EC6 ; G 1398
+U 3784 ; WX 0 ; N uni0EC8 ; G 1399
+U 3785 ; WX 0 ; N uni0EC9 ; G 1400
+U 3786 ; WX 0 ; N uni0ECA ; G 1401
+U 3787 ; WX 0 ; N uni0ECB ; G 1402
+U 3788 ; WX 0 ; N uni0ECC ; G 1403
+U 3789 ; WX 0 ; N uni0ECD ; G 1404
+U 3792 ; WX 636 ; N uni0ED0 ; G 1405
+U 3793 ; WX 641 ; N uni0ED1 ; G 1406
+U 3794 ; WX 641 ; N uni0ED2 ; G 1407
+U 3795 ; WX 670 ; N uni0ED3 ; G 1408
+U 3796 ; WX 625 ; N uni0ED4 ; G 1409
+U 3797 ; WX 625 ; N uni0ED5 ; G 1410
+U 3798 ; WX 703 ; N uni0ED6 ; G 1411
+U 3799 ; WX 670 ; N uni0ED7 ; G 1412
+U 3800 ; WX 674 ; N uni0ED8 ; G 1413
+U 3801 ; WX 677 ; N uni0ED9 ; G 1414
+U 3804 ; WX 1028 ; N uni0EDC ; G 1415
+U 3805 ; WX 1028 ; N uni0EDD ; G 1416
+U 4256 ; WX 874 ; N uni10A0 ; G 1417
+U 4257 ; WX 733 ; N uni10A1 ; G 1418
+U 4258 ; WX 679 ; N uni10A2 ; G 1419
+U 4259 ; WX 834 ; N uni10A3 ; G 1420
+U 4260 ; WX 615 ; N uni10A4 ; G 1421
+U 4261 ; WX 768 ; N uni10A5 ; G 1422
+U 4262 ; WX 753 ; N uni10A6 ; G 1423
+U 4263 ; WX 914 ; N uni10A7 ; G 1424
+U 4264 ; WX 453 ; N uni10A8 ; G 1425
+U 4265 ; WX 620 ; N uni10A9 ; G 1426
+U 4266 ; WX 843 ; N uni10AA ; G 1427
+U 4267 ; WX 882 ; N uni10AB ; G 1428
+U 4268 ; WX 625 ; N uni10AC ; G 1429
+U 4269 ; WX 854 ; N uni10AD ; G 1430
+U 4270 ; WX 781 ; N uni10AE ; G 1431
+U 4271 ; WX 629 ; N uni10AF ; G 1432
+U 4272 ; WX 912 ; N uni10B0 ; G 1433
+U 4273 ; WX 621 ; N uni10B1 ; G 1434
+U 4274 ; WX 620 ; N uni10B2 ; G 1435
+U 4275 ; WX 854 ; N uni10B3 ; G 1436
+U 4276 ; WX 866 ; N uni10B4 ; G 1437
+U 4277 ; WX 724 ; N uni10B5 ; G 1438
+U 4278 ; WX 630 ; N uni10B6 ; G 1439
+U 4279 ; WX 621 ; N uni10B7 ; G 1440
+U 4280 ; WX 625 ; N uni10B8 ; G 1441
+U 4281 ; WX 620 ; N uni10B9 ; G 1442
+U 4282 ; WX 818 ; N uni10BA ; G 1443
+U 4283 ; WX 874 ; N uni10BB ; G 1444
+U 4284 ; WX 615 ; N uni10BC ; G 1445
+U 4285 ; WX 623 ; N uni10BD ; G 1446
+U 4286 ; WX 625 ; N uni10BE ; G 1447
+U 4287 ; WX 725 ; N uni10BF ; G 1448
+U 4288 ; WX 844 ; N uni10C0 ; G 1449
+U 4289 ; WX 596 ; N uni10C1 ; G 1450
+U 4290 ; WX 688 ; N uni10C2 ; G 1451
+U 4291 ; WX 596 ; N uni10C3 ; G 1452
+U 4292 ; WX 594 ; N uni10C4 ; G 1453
+U 4293 ; WX 738 ; N uni10C5 ; G 1454
+U 4304 ; WX 508 ; N uni10D0 ; G 1455
+U 4305 ; WX 518 ; N uni10D1 ; G 1456
+U 4306 ; WX 581 ; N uni10D2 ; G 1457
+U 4307 ; WX 818 ; N uni10D3 ; G 1458
+U 4308 ; WX 508 ; N uni10D4 ; G 1459
+U 4309 ; WX 513 ; N uni10D5 ; G 1460
+U 4310 ; WX 500 ; N uni10D6 ; G 1461
+U 4311 ; WX 801 ; N uni10D7 ; G 1462
+U 4312 ; WX 518 ; N uni10D8 ; G 1463
+U 4313 ; WX 510 ; N uni10D9 ; G 1464
+U 4314 ; WX 1064 ; N uni10DA ; G 1465
+U 4315 ; WX 522 ; N uni10DB ; G 1466
+U 4316 ; WX 522 ; N uni10DC ; G 1467
+U 4317 ; WX 786 ; N uni10DD ; G 1468
+U 4318 ; WX 508 ; N uni10DE ; G 1469
+U 4319 ; WX 518 ; N uni10DF ; G 1470
+U 4320 ; WX 796 ; N uni10E0 ; G 1471
+U 4321 ; WX 522 ; N uni10E1 ; G 1472
+U 4322 ; WX 654 ; N uni10E2 ; G 1473
+U 4323 ; WX 522 ; N uni10E3 ; G 1474
+U 4324 ; WX 825 ; N uni10E4 ; G 1475
+U 4325 ; WX 513 ; N uni10E5 ; G 1476
+U 4326 ; WX 786 ; N uni10E6 ; G 1477
+U 4327 ; WX 518 ; N uni10E7 ; G 1478
+U 4328 ; WX 518 ; N uni10E8 ; G 1479
+U 4329 ; WX 522 ; N uni10E9 ; G 1480
+U 4330 ; WX 571 ; N uni10EA ; G 1481
+U 4331 ; WX 522 ; N uni10EB ; G 1482
+U 4332 ; WX 518 ; N uni10EC ; G 1483
+U 4333 ; WX 520 ; N uni10ED ; G 1484
+U 4334 ; WX 522 ; N uni10EE ; G 1485
+U 4335 ; WX 454 ; N uni10EF ; G 1486
+U 4336 ; WX 508 ; N uni10F0 ; G 1487
+U 4337 ; WX 518 ; N uni10F1 ; G 1488
+U 4338 ; WX 508 ; N uni10F2 ; G 1489
+U 4339 ; WX 508 ; N uni10F3 ; G 1490
+U 4340 ; WX 518 ; N uni10F4 ; G 1491
+U 4341 ; WX 554 ; N uni10F5 ; G 1492
+U 4342 ; WX 828 ; N uni10F6 ; G 1493
+U 4343 ; WX 552 ; N uni10F7 ; G 1494
+U 4344 ; WX 508 ; N uni10F8 ; G 1495
+U 4345 ; WX 571 ; N uni10F9 ; G 1496
+U 4346 ; WX 508 ; N uni10FA ; G 1497
+U 4347 ; WX 448 ; N uni10FB ; G 1498
+U 4348 ; WX 324 ; N uni10FC ; G 1499
+U 5121 ; WX 684 ; N uni1401 ; G 1500
+U 5122 ; WX 684 ; N uni1402 ; G 1501
+U 5123 ; WX 684 ; N uni1403 ; G 1502
+U 5124 ; WX 684 ; N uni1404 ; G 1503
+U 5125 ; WX 769 ; N uni1405 ; G 1504
+U 5126 ; WX 769 ; N uni1406 ; G 1505
+U 5127 ; WX 769 ; N uni1407 ; G 1506
+U 5129 ; WX 769 ; N uni1409 ; G 1507
+U 5130 ; WX 769 ; N uni140A ; G 1508
+U 5131 ; WX 769 ; N uni140B ; G 1509
+U 5132 ; WX 835 ; N uni140C ; G 1510
+U 5133 ; WX 834 ; N uni140D ; G 1511
+U 5134 ; WX 835 ; N uni140E ; G 1512
+U 5135 ; WX 834 ; N uni140F ; G 1513
+U 5136 ; WX 835 ; N uni1410 ; G 1514
+U 5137 ; WX 834 ; N uni1411 ; G 1515
+U 5138 ; WX 967 ; N uni1412 ; G 1516
+U 5139 ; WX 1007 ; N uni1413 ; G 1517
+U 5140 ; WX 967 ; N uni1414 ; G 1518
+U 5141 ; WX 1007 ; N uni1415 ; G 1519
+U 5142 ; WX 769 ; N uni1416 ; G 1520
+U 5143 ; WX 967 ; N uni1417 ; G 1521
+U 5144 ; WX 1007 ; N uni1418 ; G 1522
+U 5145 ; WX 967 ; N uni1419 ; G 1523
+U 5146 ; WX 1007 ; N uni141A ; G 1524
+U 5147 ; WX 769 ; N uni141B ; G 1525
+U 5149 ; WX 256 ; N uni141D ; G 1526
+U 5150 ; WX 543 ; N uni141E ; G 1527
+U 5151 ; WX 423 ; N uni141F ; G 1528
+U 5152 ; WX 423 ; N uni1420 ; G 1529
+U 5153 ; WX 389 ; N uni1421 ; G 1530
+U 5154 ; WX 389 ; N uni1422 ; G 1531
+U 5155 ; WX 393 ; N uni1423 ; G 1532
+U 5156 ; WX 389 ; N uni1424 ; G 1533
+U 5157 ; WX 466 ; N uni1425 ; G 1534
+U 5158 ; WX 385 ; N uni1426 ; G 1535
+U 5159 ; WX 256 ; N uni1427 ; G 1536
+U 5160 ; WX 389 ; N uni1428 ; G 1537
+U 5161 ; WX 389 ; N uni1429 ; G 1538
+U 5162 ; WX 389 ; N uni142A ; G 1539
+U 5163 ; WX 1090 ; N uni142B ; G 1540
+U 5164 ; WX 909 ; N uni142C ; G 1541
+U 5165 ; WX 953 ; N uni142D ; G 1542
+U 5166 ; WX 1117 ; N uni142E ; G 1543
+U 5167 ; WX 684 ; N uni142F ; G 1544
+U 5168 ; WX 684 ; N uni1430 ; G 1545
+U 5169 ; WX 684 ; N uni1431 ; G 1546
+U 5170 ; WX 684 ; N uni1432 ; G 1547
+U 5171 ; WX 729 ; N uni1433 ; G 1548
+U 5172 ; WX 729 ; N uni1434 ; G 1549
+U 5173 ; WX 729 ; N uni1435 ; G 1550
+U 5175 ; WX 729 ; N uni1437 ; G 1551
+U 5176 ; WX 729 ; N uni1438 ; G 1552
+U 5177 ; WX 729 ; N uni1439 ; G 1553
+U 5178 ; WX 835 ; N uni143A ; G 1554
+U 5179 ; WX 684 ; N uni143B ; G 1555
+U 5180 ; WX 835 ; N uni143C ; G 1556
+U 5181 ; WX 834 ; N uni143D ; G 1557
+U 5182 ; WX 835 ; N uni143E ; G 1558
+U 5183 ; WX 834 ; N uni143F ; G 1559
+U 5184 ; WX 967 ; N uni1440 ; G 1560
+U 5185 ; WX 1007 ; N uni1441 ; G 1561
+U 5186 ; WX 967 ; N uni1442 ; G 1562
+U 5187 ; WX 1007 ; N uni1443 ; G 1563
+U 5188 ; WX 967 ; N uni1444 ; G 1564
+U 5189 ; WX 1007 ; N uni1445 ; G 1565
+U 5190 ; WX 967 ; N uni1446 ; G 1566
+U 5191 ; WX 1007 ; N uni1447 ; G 1567
+U 5192 ; WX 729 ; N uni1448 ; G 1568
+U 5193 ; WX 508 ; N uni1449 ; G 1569
+U 5194 ; WX 192 ; N uni144A ; G 1570
+U 5196 ; WX 732 ; N uni144C ; G 1571
+U 5197 ; WX 732 ; N uni144D ; G 1572
+U 5198 ; WX 732 ; N uni144E ; G 1573
+U 5199 ; WX 732 ; N uni144F ; G 1574
+U 5200 ; WX 730 ; N uni1450 ; G 1575
+U 5201 ; WX 730 ; N uni1451 ; G 1576
+U 5202 ; WX 730 ; N uni1452 ; G 1577
+U 5204 ; WX 730 ; N uni1454 ; G 1578
+U 5205 ; WX 730 ; N uni1455 ; G 1579
+U 5206 ; WX 730 ; N uni1456 ; G 1580
+U 5207 ; WX 921 ; N uni1457 ; G 1581
+U 5208 ; WX 889 ; N uni1458 ; G 1582
+U 5209 ; WX 921 ; N uni1459 ; G 1583
+U 5210 ; WX 889 ; N uni145A ; G 1584
+U 5211 ; WX 921 ; N uni145B ; G 1585
+U 5212 ; WX 889 ; N uni145C ; G 1586
+U 5213 ; WX 928 ; N uni145D ; G 1587
+U 5214 ; WX 900 ; N uni145E ; G 1588
+U 5215 ; WX 928 ; N uni145F ; G 1589
+U 5216 ; WX 900 ; N uni1460 ; G 1590
+U 5217 ; WX 947 ; N uni1461 ; G 1591
+U 5218 ; WX 900 ; N uni1462 ; G 1592
+U 5219 ; WX 947 ; N uni1463 ; G 1593
+U 5220 ; WX 900 ; N uni1464 ; G 1594
+U 5221 ; WX 947 ; N uni1465 ; G 1595
+U 5222 ; WX 434 ; N uni1466 ; G 1596
+U 5223 ; WX 877 ; N uni1467 ; G 1597
+U 5224 ; WX 877 ; N uni1468 ; G 1598
+U 5225 ; WX 866 ; N uni1469 ; G 1599
+U 5226 ; WX 890 ; N uni146A ; G 1600
+U 5227 ; WX 628 ; N uni146B ; G 1601
+U 5228 ; WX 628 ; N uni146C ; G 1602
+U 5229 ; WX 628 ; N uni146D ; G 1603
+U 5230 ; WX 628 ; N uni146E ; G 1604
+U 5231 ; WX 628 ; N uni146F ; G 1605
+U 5232 ; WX 628 ; N uni1470 ; G 1606
+U 5233 ; WX 628 ; N uni1471 ; G 1607
+U 5234 ; WX 628 ; N uni1472 ; G 1608
+U 5235 ; WX 628 ; N uni1473 ; G 1609
+U 5236 ; WX 860 ; N uni1474 ; G 1610
+U 5237 ; WX 771 ; N uni1475 ; G 1611
+U 5238 ; WX 815 ; N uni1476 ; G 1612
+U 5239 ; WX 816 ; N uni1477 ; G 1613
+U 5240 ; WX 815 ; N uni1478 ; G 1614
+U 5241 ; WX 816 ; N uni1479 ; G 1615
+U 5242 ; WX 860 ; N uni147A ; G 1616
+U 5243 ; WX 771 ; N uni147B ; G 1617
+U 5244 ; WX 860 ; N uni147C ; G 1618
+U 5245 ; WX 771 ; N uni147D ; G 1619
+U 5246 ; WX 815 ; N uni147E ; G 1620
+U 5247 ; WX 816 ; N uni147F ; G 1621
+U 5248 ; WX 815 ; N uni1480 ; G 1622
+U 5249 ; WX 816 ; N uni1481 ; G 1623
+U 5250 ; WX 815 ; N uni1482 ; G 1624
+U 5251 ; WX 407 ; N uni1483 ; G 1625
+U 5252 ; WX 407 ; N uni1484 ; G 1626
+U 5253 ; WX 750 ; N uni1485 ; G 1627
+U 5254 ; WX 775 ; N uni1486 ; G 1628
+U 5255 ; WX 750 ; N uni1487 ; G 1629
+U 5256 ; WX 775 ; N uni1488 ; G 1630
+U 5257 ; WX 628 ; N uni1489 ; G 1631
+U 5258 ; WX 628 ; N uni148A ; G 1632
+U 5259 ; WX 628 ; N uni148B ; G 1633
+U 5260 ; WX 628 ; N uni148C ; G 1634
+U 5261 ; WX 628 ; N uni148D ; G 1635
+U 5262 ; WX 628 ; N uni148E ; G 1636
+U 5263 ; WX 628 ; N uni148F ; G 1637
+U 5264 ; WX 628 ; N uni1490 ; G 1638
+U 5265 ; WX 628 ; N uni1491 ; G 1639
+U 5266 ; WX 860 ; N uni1492 ; G 1640
+U 5267 ; WX 771 ; N uni1493 ; G 1641
+U 5268 ; WX 815 ; N uni1494 ; G 1642
+U 5269 ; WX 816 ; N uni1495 ; G 1643
+U 5270 ; WX 815 ; N uni1496 ; G 1644
+U 5271 ; WX 816 ; N uni1497 ; G 1645
+U 5272 ; WX 860 ; N uni1498 ; G 1646
+U 5273 ; WX 771 ; N uni1499 ; G 1647
+U 5274 ; WX 860 ; N uni149A ; G 1648
+U 5275 ; WX 771 ; N uni149B ; G 1649
+U 5276 ; WX 815 ; N uni149C ; G 1650
+U 5277 ; WX 816 ; N uni149D ; G 1651
+U 5278 ; WX 815 ; N uni149E ; G 1652
+U 5279 ; WX 816 ; N uni149F ; G 1653
+U 5280 ; WX 815 ; N uni14A0 ; G 1654
+U 5281 ; WX 435 ; N uni14A1 ; G 1655
+U 5282 ; WX 435 ; N uni14A2 ; G 1656
+U 5283 ; WX 610 ; N uni14A3 ; G 1657
+U 5284 ; WX 557 ; N uni14A4 ; G 1658
+U 5285 ; WX 557 ; N uni14A5 ; G 1659
+U 5286 ; WX 557 ; N uni14A6 ; G 1660
+U 5287 ; WX 610 ; N uni14A7 ; G 1661
+U 5288 ; WX 610 ; N uni14A8 ; G 1662
+U 5289 ; WX 610 ; N uni14A9 ; G 1663
+U 5290 ; WX 557 ; N uni14AA ; G 1664
+U 5291 ; WX 557 ; N uni14AB ; G 1665
+U 5292 ; WX 749 ; N uni14AC ; G 1666
+U 5293 ; WX 769 ; N uni14AD ; G 1667
+U 5294 ; WX 746 ; N uni14AE ; G 1668
+U 5295 ; WX 764 ; N uni14AF ; G 1669
+U 5296 ; WX 746 ; N uni14B0 ; G 1670
+U 5297 ; WX 764 ; N uni14B1 ; G 1671
+U 5298 ; WX 749 ; N uni14B2 ; G 1672
+U 5299 ; WX 769 ; N uni14B3 ; G 1673
+U 5300 ; WX 749 ; N uni14B4 ; G 1674
+U 5301 ; WX 769 ; N uni14B5 ; G 1675
+U 5302 ; WX 746 ; N uni14B6 ; G 1676
+U 5303 ; WX 764 ; N uni14B7 ; G 1677
+U 5304 ; WX 746 ; N uni14B8 ; G 1678
+U 5305 ; WX 764 ; N uni14B9 ; G 1679
+U 5306 ; WX 746 ; N uni14BA ; G 1680
+U 5307 ; WX 386 ; N uni14BB ; G 1681
+U 5308 ; WX 508 ; N uni14BC ; G 1682
+U 5309 ; WX 386 ; N uni14BD ; G 1683
+U 5312 ; WX 852 ; N uni14C0 ; G 1684
+U 5313 ; WX 852 ; N uni14C1 ; G 1685
+U 5314 ; WX 852 ; N uni14C2 ; G 1686
+U 5315 ; WX 852 ; N uni14C3 ; G 1687
+U 5316 ; WX 852 ; N uni14C4 ; G 1688
+U 5317 ; WX 852 ; N uni14C5 ; G 1689
+U 5318 ; WX 852 ; N uni14C6 ; G 1690
+U 5319 ; WX 852 ; N uni14C7 ; G 1691
+U 5320 ; WX 852 ; N uni14C8 ; G 1692
+U 5321 ; WX 1069 ; N uni14C9 ; G 1693
+U 5322 ; WX 1035 ; N uni14CA ; G 1694
+U 5323 ; WX 1059 ; N uni14CB ; G 1695
+U 5324 ; WX 852 ; N uni14CC ; G 1696
+U 5325 ; WX 1059 ; N uni14CD ; G 1697
+U 5326 ; WX 852 ; N uni14CE ; G 1698
+U 5327 ; WX 852 ; N uni14CF ; G 1699
+U 5328 ; WX 600 ; N uni14D0 ; G 1700
+U 5329 ; WX 453 ; N uni14D1 ; G 1701
+U 5330 ; WX 600 ; N uni14D2 ; G 1702
+U 5331 ; WX 852 ; N uni14D3 ; G 1703
+U 5332 ; WX 852 ; N uni14D4 ; G 1704
+U 5333 ; WX 852 ; N uni14D5 ; G 1705
+U 5334 ; WX 852 ; N uni14D6 ; G 1706
+U 5335 ; WX 852 ; N uni14D7 ; G 1707
+U 5336 ; WX 852 ; N uni14D8 ; G 1708
+U 5337 ; WX 852 ; N uni14D9 ; G 1709
+U 5338 ; WX 852 ; N uni14DA ; G 1710
+U 5339 ; WX 852 ; N uni14DB ; G 1711
+U 5340 ; WX 1069 ; N uni14DC ; G 1712
+U 5341 ; WX 1035 ; N uni14DD ; G 1713
+U 5342 ; WX 1059 ; N uni14DE ; G 1714
+U 5343 ; WX 1030 ; N uni14DF ; G 1715
+U 5344 ; WX 1059 ; N uni14E0 ; G 1716
+U 5345 ; WX 1030 ; N uni14E1 ; G 1717
+U 5346 ; WX 1069 ; N uni14E2 ; G 1718
+U 5347 ; WX 1035 ; N uni14E3 ; G 1719
+U 5348 ; WX 1069 ; N uni14E4 ; G 1720
+U 5349 ; WX 1035 ; N uni14E5 ; G 1721
+U 5350 ; WX 1083 ; N uni14E6 ; G 1722
+U 5351 ; WX 1030 ; N uni14E7 ; G 1723
+U 5352 ; WX 1083 ; N uni14E8 ; G 1724
+U 5353 ; WX 1030 ; N uni14E9 ; G 1725
+U 5354 ; WX 600 ; N uni14EA ; G 1726
+U 5356 ; WX 729 ; N uni14EC ; G 1727
+U 5357 ; WX 603 ; N uni14ED ; G 1728
+U 5358 ; WX 603 ; N uni14EE ; G 1729
+U 5359 ; WX 603 ; N uni14EF ; G 1730
+U 5360 ; WX 603 ; N uni14F0 ; G 1731
+U 5361 ; WX 603 ; N uni14F1 ; G 1732
+U 5362 ; WX 603 ; N uni14F2 ; G 1733
+U 5363 ; WX 603 ; N uni14F3 ; G 1734
+U 5364 ; WX 603 ; N uni14F4 ; G 1735
+U 5365 ; WX 603 ; N uni14F5 ; G 1736
+U 5366 ; WX 834 ; N uni14F6 ; G 1737
+U 5367 ; WX 754 ; N uni14F7 ; G 1738
+U 5368 ; WX 792 ; N uni14F8 ; G 1739
+U 5369 ; WX 771 ; N uni14F9 ; G 1740
+U 5370 ; WX 792 ; N uni14FA ; G 1741
+U 5371 ; WX 771 ; N uni14FB ; G 1742
+U 5372 ; WX 834 ; N uni14FC ; G 1743
+U 5373 ; WX 754 ; N uni14FD ; G 1744
+U 5374 ; WX 834 ; N uni14FE ; G 1745
+U 5375 ; WX 754 ; N uni14FF ; G 1746
+U 5376 ; WX 792 ; N uni1500 ; G 1747
+U 5377 ; WX 771 ; N uni1501 ; G 1748
+U 5378 ; WX 792 ; N uni1502 ; G 1749
+U 5379 ; WX 771 ; N uni1503 ; G 1750
+U 5380 ; WX 792 ; N uni1504 ; G 1751
+U 5381 ; WX 418 ; N uni1505 ; G 1752
+U 5382 ; WX 420 ; N uni1506 ; G 1753
+U 5383 ; WX 418 ; N uni1507 ; G 1754
+U 5392 ; WX 712 ; N uni1510 ; G 1755
+U 5393 ; WX 712 ; N uni1511 ; G 1756
+U 5394 ; WX 712 ; N uni1512 ; G 1757
+U 5395 ; WX 892 ; N uni1513 ; G 1758
+U 5396 ; WX 892 ; N uni1514 ; G 1759
+U 5397 ; WX 892 ; N uni1515 ; G 1760
+U 5398 ; WX 892 ; N uni1516 ; G 1761
+U 5399 ; WX 910 ; N uni1517 ; G 1762
+U 5400 ; WX 872 ; N uni1518 ; G 1763
+U 5401 ; WX 910 ; N uni1519 ; G 1764
+U 5402 ; WX 872 ; N uni151A ; G 1765
+U 5403 ; WX 910 ; N uni151B ; G 1766
+U 5404 ; WX 872 ; N uni151C ; G 1767
+U 5405 ; WX 1140 ; N uni151D ; G 1768
+U 5406 ; WX 1100 ; N uni151E ; G 1769
+U 5407 ; WX 1140 ; N uni151F ; G 1770
+U 5408 ; WX 1100 ; N uni1520 ; G 1771
+U 5409 ; WX 1140 ; N uni1521 ; G 1772
+U 5410 ; WX 1100 ; N uni1522 ; G 1773
+U 5411 ; WX 1140 ; N uni1523 ; G 1774
+U 5412 ; WX 1100 ; N uni1524 ; G 1775
+U 5413 ; WX 641 ; N uni1525 ; G 1776
+U 5414 ; WX 627 ; N uni1526 ; G 1777
+U 5415 ; WX 627 ; N uni1527 ; G 1778
+U 5416 ; WX 627 ; N uni1528 ; G 1779
+U 5417 ; WX 627 ; N uni1529 ; G 1780
+U 5418 ; WX 627 ; N uni152A ; G 1781
+U 5419 ; WX 627 ; N uni152B ; G 1782
+U 5420 ; WX 627 ; N uni152C ; G 1783
+U 5421 ; WX 627 ; N uni152D ; G 1784
+U 5422 ; WX 627 ; N uni152E ; G 1785
+U 5423 ; WX 844 ; N uni152F ; G 1786
+U 5424 ; WX 781 ; N uni1530 ; G 1787
+U 5425 ; WX 816 ; N uni1531 ; G 1788
+U 5426 ; WX 818 ; N uni1532 ; G 1789
+U 5427 ; WX 816 ; N uni1533 ; G 1790
+U 5428 ; WX 818 ; N uni1534 ; G 1791
+U 5429 ; WX 844 ; N uni1535 ; G 1792
+U 5430 ; WX 781 ; N uni1536 ; G 1793
+U 5431 ; WX 844 ; N uni1537 ; G 1794
+U 5432 ; WX 781 ; N uni1538 ; G 1795
+U 5433 ; WX 816 ; N uni1539 ; G 1796
+U 5434 ; WX 818 ; N uni153A ; G 1797
+U 5435 ; WX 816 ; N uni153B ; G 1798
+U 5436 ; WX 818 ; N uni153C ; G 1799
+U 5437 ; WX 816 ; N uni153D ; G 1800
+U 5438 ; WX 418 ; N uni153E ; G 1801
+U 5440 ; WX 389 ; N uni1540 ; G 1802
+U 5441 ; WX 484 ; N uni1541 ; G 1803
+U 5442 ; WX 916 ; N uni1542 ; G 1804
+U 5443 ; WX 916 ; N uni1543 ; G 1805
+U 5444 ; WX 863 ; N uni1544 ; G 1806
+U 5445 ; WX 916 ; N uni1545 ; G 1807
+U 5446 ; WX 863 ; N uni1546 ; G 1808
+U 5447 ; WX 863 ; N uni1547 ; G 1809
+U 5448 ; WX 603 ; N uni1548 ; G 1810
+U 5449 ; WX 603 ; N uni1549 ; G 1811
+U 5450 ; WX 603 ; N uni154A ; G 1812
+U 5451 ; WX 603 ; N uni154B ; G 1813
+U 5452 ; WX 603 ; N uni154C ; G 1814
+U 5453 ; WX 603 ; N uni154D ; G 1815
+U 5454 ; WX 834 ; N uni154E ; G 1816
+U 5455 ; WX 754 ; N uni154F ; G 1817
+U 5456 ; WX 418 ; N uni1550 ; G 1818
+U 5458 ; WX 729 ; N uni1552 ; G 1819
+U 5459 ; WX 684 ; N uni1553 ; G 1820
+U 5460 ; WX 684 ; N uni1554 ; G 1821
+U 5461 ; WX 684 ; N uni1555 ; G 1822
+U 5462 ; WX 684 ; N uni1556 ; G 1823
+U 5463 ; WX 726 ; N uni1557 ; G 1824
+U 5464 ; WX 726 ; N uni1558 ; G 1825
+U 5465 ; WX 726 ; N uni1559 ; G 1826
+U 5466 ; WX 726 ; N uni155A ; G 1827
+U 5467 ; WX 924 ; N uni155B ; G 1828
+U 5468 ; WX 1007 ; N uni155C ; G 1829
+U 5469 ; WX 508 ; N uni155D ; G 1830
+U 5470 ; WX 732 ; N uni155E ; G 1831
+U 5471 ; WX 732 ; N uni155F ; G 1832
+U 5472 ; WX 732 ; N uni1560 ; G 1833
+U 5473 ; WX 732 ; N uni1561 ; G 1834
+U 5474 ; WX 732 ; N uni1562 ; G 1835
+U 5475 ; WX 732 ; N uni1563 ; G 1836
+U 5476 ; WX 730 ; N uni1564 ; G 1837
+U 5477 ; WX 730 ; N uni1565 ; G 1838
+U 5478 ; WX 730 ; N uni1566 ; G 1839
+U 5479 ; WX 730 ; N uni1567 ; G 1840
+U 5480 ; WX 947 ; N uni1568 ; G 1841
+U 5481 ; WX 900 ; N uni1569 ; G 1842
+U 5482 ; WX 508 ; N uni156A ; G 1843
+U 5492 ; WX 831 ; N uni1574 ; G 1844
+U 5493 ; WX 831 ; N uni1575 ; G 1845
+U 5494 ; WX 831 ; N uni1576 ; G 1846
+U 5495 ; WX 831 ; N uni1577 ; G 1847
+U 5496 ; WX 831 ; N uni1578 ; G 1848
+U 5497 ; WX 831 ; N uni1579 ; G 1849
+U 5498 ; WX 831 ; N uni157A ; G 1850
+U 5499 ; WX 563 ; N uni157B ; G 1851
+U 5500 ; WX 752 ; N uni157C ; G 1852
+U 5501 ; WX 484 ; N uni157D ; G 1853
+U 5502 ; WX 1047 ; N uni157E ; G 1854
+U 5503 ; WX 1047 ; N uni157F ; G 1855
+U 5504 ; WX 1047 ; N uni1580 ; G 1856
+U 5505 ; WX 1047 ; N uni1581 ; G 1857
+U 5506 ; WX 1047 ; N uni1582 ; G 1858
+U 5507 ; WX 1047 ; N uni1583 ; G 1859
+U 5508 ; WX 1047 ; N uni1584 ; G 1860
+U 5509 ; WX 825 ; N uni1585 ; G 1861
+U 5514 ; WX 831 ; N uni158A ; G 1862
+U 5515 ; WX 831 ; N uni158B ; G 1863
+U 5516 ; WX 831 ; N uni158C ; G 1864
+U 5517 ; WX 831 ; N uni158D ; G 1865
+U 5518 ; WX 1259 ; N uni158E ; G 1866
+U 5519 ; WX 1259 ; N uni158F ; G 1867
+U 5520 ; WX 1259 ; N uni1590 ; G 1868
+U 5521 ; WX 1002 ; N uni1591 ; G 1869
+U 5522 ; WX 1002 ; N uni1592 ; G 1870
+U 5523 ; WX 1259 ; N uni1593 ; G 1871
+U 5524 ; WX 1259 ; N uni1594 ; G 1872
+U 5525 ; WX 700 ; N uni1595 ; G 1873
+U 5526 ; WX 1073 ; N uni1596 ; G 1874
+U 5536 ; WX 852 ; N uni15A0 ; G 1875
+U 5537 ; WX 852 ; N uni15A1 ; G 1876
+U 5538 ; WX 799 ; N uni15A2 ; G 1877
+U 5539 ; WX 799 ; N uni15A3 ; G 1878
+U 5540 ; WX 799 ; N uni15A4 ; G 1879
+U 5541 ; WX 799 ; N uni15A5 ; G 1880
+U 5542 ; WX 600 ; N uni15A6 ; G 1881
+U 5543 ; WX 643 ; N uni15A7 ; G 1882
+U 5544 ; WX 643 ; N uni15A8 ; G 1883
+U 5545 ; WX 643 ; N uni15A9 ; G 1884
+U 5546 ; WX 643 ; N uni15AA ; G 1885
+U 5547 ; WX 643 ; N uni15AB ; G 1886
+U 5548 ; WX 643 ; N uni15AC ; G 1887
+U 5549 ; WX 643 ; N uni15AD ; G 1888
+U 5550 ; WX 418 ; N uni15AE ; G 1889
+U 5551 ; WX 628 ; N uni15AF ; G 1890
+U 5598 ; WX 770 ; N uni15DE ; G 1891
+U 5601 ; WX 770 ; N uni15E1 ; G 1892
+U 5702 ; WX 468 ; N uni1646 ; G 1893
+U 5703 ; WX 468 ; N uni1647 ; G 1894
+U 5742 ; WX 444 ; N uni166E ; G 1895
+U 5743 ; WX 1047 ; N uni166F ; G 1896
+U 5744 ; WX 1310 ; N uni1670 ; G 1897
+U 5745 ; WX 1632 ; N uni1671 ; G 1898
+U 5746 ; WX 1632 ; N uni1672 ; G 1899
+U 5747 ; WX 1375 ; N uni1673 ; G 1900
+U 5748 ; WX 1375 ; N uni1674 ; G 1901
+U 5749 ; WX 1632 ; N uni1675 ; G 1902
+U 5750 ; WX 1632 ; N uni1676 ; G 1903
+U 7424 ; WX 592 ; N uni1D00 ; G 1904
+U 7425 ; WX 717 ; N uni1D01 ; G 1905
+U 7426 ; WX 982 ; N uni1D02 ; G 1906
+U 7427 ; WX 586 ; N uni1D03 ; G 1907
+U 7428 ; WX 550 ; N uni1D04 ; G 1908
+U 7429 ; WX 605 ; N uni1D05 ; G 1909
+U 7430 ; WX 605 ; N uni1D06 ; G 1910
+U 7431 ; WX 491 ; N uni1D07 ; G 1911
+U 7432 ; WX 541 ; N uni1D08 ; G 1912
+U 7433 ; WX 278 ; N uni1D09 ; G 1913
+U 7434 ; WX 395 ; N uni1D0A ; G 1914
+U 7435 ; WX 579 ; N uni1D0B ; G 1915
+U 7436 ; WX 583 ; N uni1D0C ; G 1916
+U 7437 ; WX 754 ; N uni1D0D ; G 1917
+U 7438 ; WX 650 ; N uni1D0E ; G 1918
+U 7439 ; WX 612 ; N uni1D0F ; G 1919
+U 7440 ; WX 550 ; N uni1D10 ; G 1920
+U 7441 ; WX 684 ; N uni1D11 ; G 1921
+U 7442 ; WX 684 ; N uni1D12 ; G 1922
+U 7443 ; WX 684 ; N uni1D13 ; G 1923
+U 7444 ; WX 1023 ; N uni1D14 ; G 1924
+U 7446 ; WX 612 ; N uni1D16 ; G 1925
+U 7447 ; WX 612 ; N uni1D17 ; G 1926
+U 7448 ; WX 524 ; N uni1D18 ; G 1927
+U 7449 ; WX 602 ; N uni1D19 ; G 1928
+U 7450 ; WX 602 ; N uni1D1A ; G 1929
+U 7451 ; WX 583 ; N uni1D1B ; G 1930
+U 7452 ; WX 574 ; N uni1D1C ; G 1931
+U 7453 ; WX 737 ; N uni1D1D ; G 1932
+U 7454 ; WX 948 ; N uni1D1E ; G 1933
+U 7455 ; WX 638 ; N uni1D1F ; G 1934
+U 7456 ; WX 592 ; N uni1D20 ; G 1935
+U 7457 ; WX 818 ; N uni1D21 ; G 1936
+U 7458 ; WX 525 ; N uni1D22 ; G 1937
+U 7459 ; WX 526 ; N uni1D23 ; G 1938
+U 7462 ; WX 583 ; N uni1D26 ; G 1939
+U 7463 ; WX 592 ; N uni1D27 ; G 1940
+U 7464 ; WX 564 ; N uni1D28 ; G 1941
+U 7465 ; WX 524 ; N uni1D29 ; G 1942
+U 7466 ; WX 590 ; N uni1D2A ; G 1943
+U 7467 ; WX 639 ; N uni1D2B ; G 1944
+U 7468 ; WX 431 ; N uni1D2C ; G 1945
+U 7469 ; WX 613 ; N uni1D2D ; G 1946
+U 7470 ; WX 432 ; N uni1D2E ; G 1947
+U 7472 ; WX 485 ; N uni1D30 ; G 1948
+U 7473 ; WX 398 ; N uni1D31 ; G 1949
+U 7474 ; WX 398 ; N uni1D32 ; G 1950
+U 7475 ; WX 488 ; N uni1D33 ; G 1951
+U 7476 ; WX 474 ; N uni1D34 ; G 1952
+U 7477 ; WX 186 ; N uni1D35 ; G 1953
+U 7478 ; WX 186 ; N uni1D36 ; G 1954
+U 7479 ; WX 413 ; N uni1D37 ; G 1955
+U 7480 ; WX 351 ; N uni1D38 ; G 1956
+U 7481 ; WX 543 ; N uni1D39 ; G 1957
+U 7482 ; WX 471 ; N uni1D3A ; G 1958
+U 7483 ; WX 471 ; N uni1D3B ; G 1959
+U 7484 ; WX 496 ; N uni1D3C ; G 1960
+U 7485 ; WX 439 ; N uni1D3D ; G 1961
+U 7486 ; WX 380 ; N uni1D3E ; G 1962
+U 7487 ; WX 438 ; N uni1D3F ; G 1963
+U 7488 ; WX 385 ; N uni1D40 ; G 1964
+U 7489 ; WX 461 ; N uni1D41 ; G 1965
+U 7490 ; WX 623 ; N uni1D42 ; G 1966
+U 7491 ; WX 392 ; N uni1D43 ; G 1967
+U 7492 ; WX 392 ; N uni1D44 ; G 1968
+U 7493 ; WX 405 ; N uni1D45 ; G 1969
+U 7494 ; WX 648 ; N uni1D46 ; G 1970
+U 7495 ; WX 428 ; N uni1D47 ; G 1971
+U 7496 ; WX 405 ; N uni1D48 ; G 1972
+U 7497 ; WX 417 ; N uni1D49 ; G 1973
+U 7498 ; WX 417 ; N uni1D4A ; G 1974
+U 7499 ; WX 360 ; N uni1D4B ; G 1975
+U 7500 ; WX 359 ; N uni1D4C ; G 1976
+U 7501 ; WX 405 ; N uni1D4D ; G 1977
+U 7502 ; WX 179 ; N uni1D4E ; G 1978
+U 7503 ; WX 426 ; N uni1D4F ; G 1979
+U 7504 ; WX 623 ; N uni1D50 ; G 1980
+U 7505 ; WX 409 ; N uni1D51 ; G 1981
+U 7506 ; WX 414 ; N uni1D52 ; G 1982
+U 7507 ; WX 370 ; N uni1D53 ; G 1983
+U 7508 ; WX 414 ; N uni1D54 ; G 1984
+U 7509 ; WX 414 ; N uni1D55 ; G 1985
+U 7510 ; WX 428 ; N uni1D56 ; G 1986
+U 7511 ; WX 295 ; N uni1D57 ; G 1987
+U 7512 ; WX 405 ; N uni1D58 ; G 1988
+U 7513 ; WX 470 ; N uni1D59 ; G 1989
+U 7514 ; WX 623 ; N uni1D5A ; G 1990
+U 7515 ; WX 417 ; N uni1D5B ; G 1991
+U 7517 ; WX 402 ; N uni1D5D ; G 1992
+U 7518 ; WX 373 ; N uni1D5E ; G 1993
+U 7519 ; WX 385 ; N uni1D5F ; G 1994
+U 7520 ; WX 416 ; N uni1D60 ; G 1995
+U 7521 ; WX 364 ; N uni1D61 ; G 1996
+U 7522 ; WX 179 ; N uni1D62 ; G 1997
+U 7523 ; WX 259 ; N uni1D63 ; G 1998
+U 7524 ; WX 405 ; N uni1D64 ; G 1999
+U 7525 ; WX 417 ; N uni1D65 ; G 2000
+U 7526 ; WX 402 ; N uni1D66 ; G 2001
+U 7527 ; WX 373 ; N uni1D67 ; G 2002
+U 7528 ; WX 412 ; N uni1D68 ; G 2003
+U 7529 ; WX 416 ; N uni1D69 ; G 2004
+U 7530 ; WX 364 ; N uni1D6A ; G 2005
+U 7543 ; WX 635 ; N uni1D77 ; G 2006
+U 7544 ; WX 474 ; N uni1D78 ; G 2007
+U 7547 ; WX 372 ; N uni1D7B ; G 2008
+U 7549 ; WX 667 ; N uni1D7D ; G 2009
+U 7557 ; WX 278 ; N uni1D85 ; G 2010
+U 7579 ; WX 405 ; N uni1D9B ; G 2011
+U 7580 ; WX 370 ; N uni1D9C ; G 2012
+U 7581 ; WX 370 ; N uni1D9D ; G 2013
+U 7582 ; WX 414 ; N uni1D9E ; G 2014
+U 7583 ; WX 360 ; N uni1D9F ; G 2015
+U 7584 ; WX 296 ; N uni1DA0 ; G 2016
+U 7585 ; WX 233 ; N uni1DA1 ; G 2017
+U 7586 ; WX 405 ; N uni1DA2 ; G 2018
+U 7587 ; WX 405 ; N uni1DA3 ; G 2019
+U 7588 ; WX 261 ; N uni1DA4 ; G 2020
+U 7589 ; WX 250 ; N uni1DA5 ; G 2021
+U 7590 ; WX 261 ; N uni1DA6 ; G 2022
+U 7591 ; WX 261 ; N uni1DA7 ; G 2023
+U 7592 ; WX 234 ; N uni1DA8 ; G 2024
+U 7593 ; WX 250 ; N uni1DA9 ; G 2025
+U 7594 ; WX 235 ; N uni1DAA ; G 2026
+U 7595 ; WX 376 ; N uni1DAB ; G 2027
+U 7596 ; WX 623 ; N uni1DAC ; G 2028
+U 7597 ; WX 623 ; N uni1DAD ; G 2029
+U 7598 ; WX 411 ; N uni1DAE ; G 2030
+U 7599 ; WX 479 ; N uni1DAF ; G 2031
+U 7600 ; WX 409 ; N uni1DB0 ; G 2032
+U 7601 ; WX 414 ; N uni1DB1 ; G 2033
+U 7602 ; WX 414 ; N uni1DB2 ; G 2034
+U 7603 ; WX 360 ; N uni1DB3 ; G 2035
+U 7604 ; WX 287 ; N uni1DB4 ; G 2036
+U 7605 ; WX 295 ; N uni1DB5 ; G 2037
+U 7606 ; WX 508 ; N uni1DB6 ; G 2038
+U 7607 ; WX 418 ; N uni1DB7 ; G 2039
+U 7608 ; WX 361 ; N uni1DB8 ; G 2040
+U 7609 ; WX 406 ; N uni1DB9 ; G 2041
+U 7610 ; WX 417 ; N uni1DBA ; G 2042
+U 7611 ; WX 366 ; N uni1DBB ; G 2043
+U 7612 ; WX 437 ; N uni1DBC ; G 2044
+U 7613 ; WX 366 ; N uni1DBD ; G 2045
+U 7614 ; WX 392 ; N uni1DBE ; G 2046
+U 7615 ; WX 414 ; N uni1DBF ; G 2047
+U 7620 ; WX 0 ; N uni1DC4 ; G 2048
+U 7621 ; WX 0 ; N uni1DC5 ; G 2049
+U 7622 ; WX 0 ; N uni1DC6 ; G 2050
+U 7623 ; WX 0 ; N uni1DC7 ; G 2051
+U 7624 ; WX 0 ; N uni1DC8 ; G 2052
+U 7625 ; WX 0 ; N uni1DC9 ; G 2053
+U 7680 ; WX 684 ; N uni1E00 ; G 2054
+U 7681 ; WX 613 ; N uni1E01 ; G 2055
+U 7682 ; WX 686 ; N uni1E02 ; G 2056
+U 7683 ; WX 635 ; N uni1E03 ; G 2057
+U 7684 ; WX 686 ; N uni1E04 ; G 2058
+U 7685 ; WX 635 ; N uni1E05 ; G 2059
+U 7686 ; WX 686 ; N uni1E06 ; G 2060
+U 7687 ; WX 635 ; N uni1E07 ; G 2061
+U 7688 ; WX 698 ; N uni1E08 ; G 2062
+U 7689 ; WX 550 ; N uni1E09 ; G 2063
+U 7690 ; WX 770 ; N uni1E0A ; G 2064
+U 7691 ; WX 635 ; N uni1E0B ; G 2065
+U 7692 ; WX 770 ; N uni1E0C ; G 2066
+U 7693 ; WX 635 ; N uni1E0D ; G 2067
+U 7694 ; WX 770 ; N uni1E0E ; G 2068
+U 7695 ; WX 635 ; N uni1E0F ; G 2069
+U 7696 ; WX 770 ; N uni1E10 ; G 2070
+U 7697 ; WX 635 ; N uni1E11 ; G 2071
+U 7698 ; WX 770 ; N uni1E12 ; G 2072
+U 7699 ; WX 635 ; N uni1E13 ; G 2073
+U 7700 ; WX 632 ; N uni1E14 ; G 2074
+U 7701 ; WX 615 ; N uni1E15 ; G 2075
+U 7702 ; WX 632 ; N uni1E16 ; G 2076
+U 7703 ; WX 615 ; N uni1E17 ; G 2077
+U 7704 ; WX 632 ; N uni1E18 ; G 2078
+U 7705 ; WX 615 ; N uni1E19 ; G 2079
+U 7706 ; WX 632 ; N uni1E1A ; G 2080
+U 7707 ; WX 615 ; N uni1E1B ; G 2081
+U 7708 ; WX 632 ; N uni1E1C ; G 2082
+U 7709 ; WX 615 ; N uni1E1D ; G 2083
+U 7710 ; WX 575 ; N uni1E1E ; G 2084
+U 7711 ; WX 352 ; N uni1E1F ; G 2085
+U 7712 ; WX 775 ; N uni1E20 ; G 2086
+U 7713 ; WX 635 ; N uni1E21 ; G 2087
+U 7714 ; WX 752 ; N uni1E22 ; G 2088
+U 7715 ; WX 634 ; N uni1E23 ; G 2089
+U 7716 ; WX 752 ; N uni1E24 ; G 2090
+U 7717 ; WX 634 ; N uni1E25 ; G 2091
+U 7718 ; WX 752 ; N uni1E26 ; G 2092
+U 7719 ; WX 634 ; N uni1E27 ; G 2093
+U 7720 ; WX 752 ; N uni1E28 ; G 2094
+U 7721 ; WX 634 ; N uni1E29 ; G 2095
+U 7722 ; WX 752 ; N uni1E2A ; G 2096
+U 7723 ; WX 634 ; N uni1E2B ; G 2097
+U 7724 ; WX 295 ; N uni1E2C ; G 2098
+U 7725 ; WX 278 ; N uni1E2D ; G 2099
+U 7726 ; WX 295 ; N uni1E2E ; G 2100
+U 7727 ; WX 278 ; N uni1E2F ; G 2101
+U 7728 ; WX 656 ; N uni1E30 ; G 2102
+U 7729 ; WX 579 ; N uni1E31 ; G 2103
+U 7730 ; WX 656 ; N uni1E32 ; G 2104
+U 7731 ; WX 579 ; N uni1E33 ; G 2105
+U 7732 ; WX 656 ; N uni1E34 ; G 2106
+U 7733 ; WX 579 ; N uni1E35 ; G 2107
+U 7734 ; WX 557 ; N uni1E36 ; G 2108
+U 7735 ; WX 278 ; N uni1E37 ; G 2109
+U 7736 ; WX 557 ; N uni1E38 ; G 2110
+U 7737 ; WX 278 ; N uni1E39 ; G 2111
+U 7738 ; WX 557 ; N uni1E3A ; G 2112
+U 7739 ; WX 278 ; N uni1E3B ; G 2113
+U 7740 ; WX 557 ; N uni1E3C ; G 2114
+U 7741 ; WX 278 ; N uni1E3D ; G 2115
+U 7742 ; WX 863 ; N uni1E3E ; G 2116
+U 7743 ; WX 974 ; N uni1E3F ; G 2117
+U 7744 ; WX 863 ; N uni1E40 ; G 2118
+U 7745 ; WX 974 ; N uni1E41 ; G 2119
+U 7746 ; WX 863 ; N uni1E42 ; G 2120
+U 7747 ; WX 974 ; N uni1E43 ; G 2121
+U 7748 ; WX 748 ; N uni1E44 ; G 2122
+U 7749 ; WX 634 ; N uni1E45 ; G 2123
+U 7750 ; WX 748 ; N uni1E46 ; G 2124
+U 7751 ; WX 634 ; N uni1E47 ; G 2125
+U 7752 ; WX 748 ; N uni1E48 ; G 2126
+U 7753 ; WX 634 ; N uni1E49 ; G 2127
+U 7754 ; WX 748 ; N uni1E4A ; G 2128
+U 7755 ; WX 634 ; N uni1E4B ; G 2129
+U 7756 ; WX 787 ; N uni1E4C ; G 2130
+U 7757 ; WX 612 ; N uni1E4D ; G 2131
+U 7758 ; WX 787 ; N uni1E4E ; G 2132
+U 7759 ; WX 612 ; N uni1E4F ; G 2133
+U 7760 ; WX 787 ; N uni1E50 ; G 2134
+U 7761 ; WX 612 ; N uni1E51 ; G 2135
+U 7762 ; WX 787 ; N uni1E52 ; G 2136
+U 7763 ; WX 612 ; N uni1E53 ; G 2137
+U 7764 ; WX 603 ; N uni1E54 ; G 2138
+U 7765 ; WX 635 ; N uni1E55 ; G 2139
+U 7766 ; WX 603 ; N uni1E56 ; G 2140
+U 7767 ; WX 635 ; N uni1E57 ; G 2141
+U 7768 ; WX 695 ; N uni1E58 ; G 2142
+U 7769 ; WX 411 ; N uni1E59 ; G 2143
+U 7770 ; WX 695 ; N uni1E5A ; G 2144
+U 7771 ; WX 411 ; N uni1E5B ; G 2145
+U 7772 ; WX 695 ; N uni1E5C ; G 2146
+U 7773 ; WX 411 ; N uni1E5D ; G 2147
+U 7774 ; WX 695 ; N uni1E5E ; G 2148
+U 7775 ; WX 411 ; N uni1E5F ; G 2149
+U 7776 ; WX 635 ; N uni1E60 ; G 2150
+U 7777 ; WX 521 ; N uni1E61 ; G 2151
+U 7778 ; WX 635 ; N uni1E62 ; G 2152
+U 7779 ; WX 521 ; N uni1E63 ; G 2153
+U 7780 ; WX 635 ; N uni1E64 ; G 2154
+U 7781 ; WX 521 ; N uni1E65 ; G 2155
+U 7782 ; WX 635 ; N uni1E66 ; G 2156
+U 7783 ; WX 521 ; N uni1E67 ; G 2157
+U 7784 ; WX 635 ; N uni1E68 ; G 2158
+U 7785 ; WX 521 ; N uni1E69 ; G 2159
+U 7786 ; WX 611 ; N uni1E6A ; G 2160
+U 7787 ; WX 392 ; N uni1E6B ; G 2161
+U 7788 ; WX 611 ; N uni1E6C ; G 2162
+U 7789 ; WX 392 ; N uni1E6D ; G 2163
+U 7790 ; WX 611 ; N uni1E6E ; G 2164
+U 7791 ; WX 392 ; N uni1E6F ; G 2165
+U 7792 ; WX 611 ; N uni1E70 ; G 2166
+U 7793 ; WX 392 ; N uni1E71 ; G 2167
+U 7794 ; WX 732 ; N uni1E72 ; G 2168
+U 7795 ; WX 634 ; N uni1E73 ; G 2169
+U 7796 ; WX 732 ; N uni1E74 ; G 2170
+U 7797 ; WX 634 ; N uni1E75 ; G 2171
+U 7798 ; WX 732 ; N uni1E76 ; G 2172
+U 7799 ; WX 634 ; N uni1E77 ; G 2173
+U 7800 ; WX 732 ; N uni1E78 ; G 2174
+U 7801 ; WX 634 ; N uni1E79 ; G 2175
+U 7802 ; WX 732 ; N uni1E7A ; G 2176
+U 7803 ; WX 634 ; N uni1E7B ; G 2177
+U 7804 ; WX 684 ; N uni1E7C ; G 2178
+U 7805 ; WX 592 ; N uni1E7D ; G 2179
+U 7806 ; WX 684 ; N uni1E7E ; G 2180
+U 7807 ; WX 592 ; N uni1E7F ; G 2181
+U 7808 ; WX 989 ; N Wgrave ; G 2182
+U 7809 ; WX 818 ; N wgrave ; G 2183
+U 7810 ; WX 989 ; N Wacute ; G 2184
+U 7811 ; WX 818 ; N wacute ; G 2185
+U 7812 ; WX 989 ; N Wdieresis ; G 2186
+U 7813 ; WX 818 ; N wdieresis ; G 2187
+U 7814 ; WX 989 ; N uni1E86 ; G 2188
+U 7815 ; WX 818 ; N uni1E87 ; G 2189
+U 7816 ; WX 989 ; N uni1E88 ; G 2190
+U 7817 ; WX 818 ; N uni1E89 ; G 2191
+U 7818 ; WX 685 ; N uni1E8A ; G 2192
+U 7819 ; WX 592 ; N uni1E8B ; G 2193
+U 7820 ; WX 685 ; N uni1E8C ; G 2194
+U 7821 ; WX 592 ; N uni1E8D ; G 2195
+U 7822 ; WX 611 ; N uni1E8E ; G 2196
+U 7823 ; WX 592 ; N uni1E8F ; G 2197
+U 7824 ; WX 685 ; N uni1E90 ; G 2198
+U 7825 ; WX 525 ; N uni1E91 ; G 2199
+U 7826 ; WX 685 ; N uni1E92 ; G 2200
+U 7827 ; WX 525 ; N uni1E93 ; G 2201
+U 7828 ; WX 685 ; N uni1E94 ; G 2202
+U 7829 ; WX 525 ; N uni1E95 ; G 2203
+U 7830 ; WX 634 ; N uni1E96 ; G 2204
+U 7831 ; WX 392 ; N uni1E97 ; G 2205
+U 7832 ; WX 818 ; N uni1E98 ; G 2206
+U 7833 ; WX 592 ; N uni1E99 ; G 2207
+U 7834 ; WX 613 ; N uni1E9A ; G 2208
+U 7835 ; WX 352 ; N uni1E9B ; G 2209
+U 7836 ; WX 352 ; N uni1E9C ; G 2210
+U 7837 ; WX 352 ; N uni1E9D ; G 2211
+U 7838 ; WX 769 ; N uni1E9E ; G 2212
+U 7839 ; WX 612 ; N uni1E9F ; G 2213
+U 7840 ; WX 684 ; N uni1EA0 ; G 2214
+U 7841 ; WX 613 ; N uni1EA1 ; G 2215
+U 7842 ; WX 684 ; N uni1EA2 ; G 2216
+U 7843 ; WX 613 ; N uni1EA3 ; G 2217
+U 7844 ; WX 684 ; N uni1EA4 ; G 2218
+U 7845 ; WX 613 ; N uni1EA5 ; G 2219
+U 7846 ; WX 684 ; N uni1EA6 ; G 2220
+U 7847 ; WX 613 ; N uni1EA7 ; G 2221
+U 7848 ; WX 684 ; N uni1EA8 ; G 2222
+U 7849 ; WX 613 ; N uni1EA9 ; G 2223
+U 7850 ; WX 684 ; N uni1EAA ; G 2224
+U 7851 ; WX 613 ; N uni1EAB ; G 2225
+U 7852 ; WX 684 ; N uni1EAC ; G 2226
+U 7853 ; WX 613 ; N uni1EAD ; G 2227
+U 7854 ; WX 684 ; N uni1EAE ; G 2228
+U 7855 ; WX 613 ; N uni1EAF ; G 2229
+U 7856 ; WX 684 ; N uni1EB0 ; G 2230
+U 7857 ; WX 613 ; N uni1EB1 ; G 2231
+U 7858 ; WX 684 ; N uni1EB2 ; G 2232
+U 7859 ; WX 613 ; N uni1EB3 ; G 2233
+U 7860 ; WX 684 ; N uni1EB4 ; G 2234
+U 7861 ; WX 613 ; N uni1EB5 ; G 2235
+U 7862 ; WX 684 ; N uni1EB6 ; G 2236
+U 7863 ; WX 613 ; N uni1EB7 ; G 2237
+U 7864 ; WX 632 ; N uni1EB8 ; G 2238
+U 7865 ; WX 615 ; N uni1EB9 ; G 2239
+U 7866 ; WX 632 ; N uni1EBA ; G 2240
+U 7867 ; WX 615 ; N uni1EBB ; G 2241
+U 7868 ; WX 632 ; N uni1EBC ; G 2242
+U 7869 ; WX 615 ; N uni1EBD ; G 2243
+U 7870 ; WX 632 ; N uni1EBE ; G 2244
+U 7871 ; WX 615 ; N uni1EBF ; G 2245
+U 7872 ; WX 632 ; N uni1EC0 ; G 2246
+U 7873 ; WX 615 ; N uni1EC1 ; G 2247
+U 7874 ; WX 632 ; N uni1EC2 ; G 2248
+U 7875 ; WX 615 ; N uni1EC3 ; G 2249
+U 7876 ; WX 632 ; N uni1EC4 ; G 2250
+U 7877 ; WX 615 ; N uni1EC5 ; G 2251
+U 7878 ; WX 632 ; N uni1EC6 ; G 2252
+U 7879 ; WX 615 ; N uni1EC7 ; G 2253
+U 7880 ; WX 295 ; N uni1EC8 ; G 2254
+U 7881 ; WX 278 ; N uni1EC9 ; G 2255
+U 7882 ; WX 295 ; N uni1ECA ; G 2256
+U 7883 ; WX 278 ; N uni1ECB ; G 2257
+U 7884 ; WX 787 ; N uni1ECC ; G 2258
+U 7885 ; WX 612 ; N uni1ECD ; G 2259
+U 7886 ; WX 787 ; N uni1ECE ; G 2260
+U 7887 ; WX 612 ; N uni1ECF ; G 2261
+U 7888 ; WX 787 ; N uni1ED0 ; G 2262
+U 7889 ; WX 612 ; N uni1ED1 ; G 2263
+U 7890 ; WX 787 ; N uni1ED2 ; G 2264
+U 7891 ; WX 612 ; N uni1ED3 ; G 2265
+U 7892 ; WX 787 ; N uni1ED4 ; G 2266
+U 7893 ; WX 612 ; N uni1ED5 ; G 2267
+U 7894 ; WX 787 ; N uni1ED6 ; G 2268
+U 7895 ; WX 612 ; N uni1ED7 ; G 2269
+U 7896 ; WX 787 ; N uni1ED8 ; G 2270
+U 7897 ; WX 612 ; N uni1ED9 ; G 2271
+U 7898 ; WX 913 ; N uni1EDA ; G 2272
+U 7899 ; WX 612 ; N uni1EDB ; G 2273
+U 7900 ; WX 913 ; N uni1EDC ; G 2274
+U 7901 ; WX 612 ; N uni1EDD ; G 2275
+U 7902 ; WX 913 ; N uni1EDE ; G 2276
+U 7903 ; WX 612 ; N uni1EDF ; G 2277
+U 7904 ; WX 913 ; N uni1EE0 ; G 2278
+U 7905 ; WX 612 ; N uni1EE1 ; G 2279
+U 7906 ; WX 913 ; N uni1EE2 ; G 2280
+U 7907 ; WX 612 ; N uni1EE3 ; G 2281
+U 7908 ; WX 732 ; N uni1EE4 ; G 2282
+U 7909 ; WX 634 ; N uni1EE5 ; G 2283
+U 7910 ; WX 732 ; N uni1EE6 ; G 2284
+U 7911 ; WX 634 ; N uni1EE7 ; G 2285
+U 7912 ; WX 838 ; N uni1EE8 ; G 2286
+U 7913 ; WX 634 ; N uni1EE9 ; G 2287
+U 7914 ; WX 838 ; N uni1EEA ; G 2288
+U 7915 ; WX 634 ; N uni1EEB ; G 2289
+U 7916 ; WX 838 ; N uni1EEC ; G 2290
+U 7917 ; WX 634 ; N uni1EED ; G 2291
+U 7918 ; WX 838 ; N uni1EEE ; G 2292
+U 7919 ; WX 634 ; N uni1EEF ; G 2293
+U 7920 ; WX 838 ; N uni1EF0 ; G 2294
+U 7921 ; WX 634 ; N uni1EF1 ; G 2295
+U 7922 ; WX 611 ; N Ygrave ; G 2296
+U 7923 ; WX 592 ; N ygrave ; G 2297
+U 7924 ; WX 611 ; N uni1EF4 ; G 2298
+U 7925 ; WX 592 ; N uni1EF5 ; G 2299
+U 7926 ; WX 611 ; N uni1EF6 ; G 2300
+U 7927 ; WX 592 ; N uni1EF7 ; G 2301
+U 7928 ; WX 611 ; N uni1EF8 ; G 2302
+U 7929 ; WX 592 ; N uni1EF9 ; G 2303
+U 7930 ; WX 769 ; N uni1EFA ; G 2304
+U 7931 ; WX 477 ; N uni1EFB ; G 2305
+U 7936 ; WX 659 ; N uni1F00 ; G 2306
+U 7937 ; WX 659 ; N uni1F01 ; G 2307
+U 7938 ; WX 659 ; N uni1F02 ; G 2308
+U 7939 ; WX 659 ; N uni1F03 ; G 2309
+U 7940 ; WX 659 ; N uni1F04 ; G 2310
+U 7941 ; WX 659 ; N uni1F05 ; G 2311
+U 7942 ; WX 659 ; N uni1F06 ; G 2312
+U 7943 ; WX 659 ; N uni1F07 ; G 2313
+U 7944 ; WX 684 ; N uni1F08 ; G 2314
+U 7945 ; WX 684 ; N uni1F09 ; G 2315
+U 7946 ; WX 877 ; N uni1F0A ; G 2316
+U 7947 ; WX 877 ; N uni1F0B ; G 2317
+U 7948 ; WX 769 ; N uni1F0C ; G 2318
+U 7949 ; WX 801 ; N uni1F0D ; G 2319
+U 7950 ; WX 708 ; N uni1F0E ; G 2320
+U 7951 ; WX 743 ; N uni1F0F ; G 2321
+U 7952 ; WX 541 ; N uni1F10 ; G 2322
+U 7953 ; WX 541 ; N uni1F11 ; G 2323
+U 7954 ; WX 541 ; N uni1F12 ; G 2324
+U 7955 ; WX 541 ; N uni1F13 ; G 2325
+U 7956 ; WX 541 ; N uni1F14 ; G 2326
+U 7957 ; WX 541 ; N uni1F15 ; G 2327
+U 7960 ; WX 711 ; N uni1F18 ; G 2328
+U 7961 ; WX 711 ; N uni1F19 ; G 2329
+U 7962 ; WX 966 ; N uni1F1A ; G 2330
+U 7963 ; WX 975 ; N uni1F1B ; G 2331
+U 7964 ; WX 898 ; N uni1F1C ; G 2332
+U 7965 ; WX 928 ; N uni1F1D ; G 2333
+U 7968 ; WX 634 ; N uni1F20 ; G 2334
+U 7969 ; WX 634 ; N uni1F21 ; G 2335
+U 7970 ; WX 634 ; N uni1F22 ; G 2336
+U 7971 ; WX 634 ; N uni1F23 ; G 2337
+U 7972 ; WX 634 ; N uni1F24 ; G 2338
+U 7973 ; WX 634 ; N uni1F25 ; G 2339
+U 7974 ; WX 634 ; N uni1F26 ; G 2340
+U 7975 ; WX 634 ; N uni1F27 ; G 2341
+U 7976 ; WX 837 ; N uni1F28 ; G 2342
+U 7977 ; WX 835 ; N uni1F29 ; G 2343
+U 7978 ; WX 1086 ; N uni1F2A ; G 2344
+U 7979 ; WX 1089 ; N uni1F2B ; G 2345
+U 7980 ; WX 1027 ; N uni1F2C ; G 2346
+U 7981 ; WX 1051 ; N uni1F2D ; G 2347
+U 7982 ; WX 934 ; N uni1F2E ; G 2348
+U 7983 ; WX 947 ; N uni1F2F ; G 2349
+U 7984 ; WX 338 ; N uni1F30 ; G 2350
+U 7985 ; WX 338 ; N uni1F31 ; G 2351
+U 7986 ; WX 338 ; N uni1F32 ; G 2352
+U 7987 ; WX 338 ; N uni1F33 ; G 2353
+U 7988 ; WX 338 ; N uni1F34 ; G 2354
+U 7989 ; WX 338 ; N uni1F35 ; G 2355
+U 7990 ; WX 338 ; N uni1F36 ; G 2356
+U 7991 ; WX 338 ; N uni1F37 ; G 2357
+U 7992 ; WX 380 ; N uni1F38 ; G 2358
+U 7993 ; WX 374 ; N uni1F39 ; G 2359
+U 7994 ; WX 635 ; N uni1F3A ; G 2360
+U 7995 ; WX 635 ; N uni1F3B ; G 2361
+U 7996 ; WX 570 ; N uni1F3C ; G 2362
+U 7997 ; WX 600 ; N uni1F3D ; G 2363
+U 7998 ; WX 489 ; N uni1F3E ; G 2364
+U 7999 ; WX 493 ; N uni1F3F ; G 2365
+U 8000 ; WX 612 ; N uni1F40 ; G 2366
+U 8001 ; WX 612 ; N uni1F41 ; G 2367
+U 8002 ; WX 612 ; N uni1F42 ; G 2368
+U 8003 ; WX 612 ; N uni1F43 ; G 2369
+U 8004 ; WX 612 ; N uni1F44 ; G 2370
+U 8005 ; WX 612 ; N uni1F45 ; G 2371
+U 8008 ; WX 804 ; N uni1F48 ; G 2372
+U 8009 ; WX 848 ; N uni1F49 ; G 2373
+U 8010 ; WX 1095 ; N uni1F4A ; G 2374
+U 8011 ; WX 1100 ; N uni1F4B ; G 2375
+U 8012 ; WX 938 ; N uni1F4C ; G 2376
+U 8013 ; WX 970 ; N uni1F4D ; G 2377
+U 8016 ; WX 579 ; N uni1F50 ; G 2378
+U 8017 ; WX 579 ; N uni1F51 ; G 2379
+U 8018 ; WX 579 ; N uni1F52 ; G 2380
+U 8019 ; WX 579 ; N uni1F53 ; G 2381
+U 8020 ; WX 579 ; N uni1F54 ; G 2382
+U 8021 ; WX 579 ; N uni1F55 ; G 2383
+U 8022 ; WX 579 ; N uni1F56 ; G 2384
+U 8023 ; WX 579 ; N uni1F57 ; G 2385
+U 8025 ; WX 784 ; N uni1F59 ; G 2386
+U 8027 ; WX 998 ; N uni1F5B ; G 2387
+U 8029 ; WX 1012 ; N uni1F5D ; G 2388
+U 8031 ; WX 897 ; N uni1F5F ; G 2389
+U 8032 ; WX 837 ; N uni1F60 ; G 2390
+U 8033 ; WX 837 ; N uni1F61 ; G 2391
+U 8034 ; WX 837 ; N uni1F62 ; G 2392
+U 8035 ; WX 837 ; N uni1F63 ; G 2393
+U 8036 ; WX 837 ; N uni1F64 ; G 2394
+U 8037 ; WX 837 ; N uni1F65 ; G 2395
+U 8038 ; WX 837 ; N uni1F66 ; G 2396
+U 8039 ; WX 837 ; N uni1F67 ; G 2397
+U 8040 ; WX 802 ; N uni1F68 ; G 2398
+U 8041 ; WX 843 ; N uni1F69 ; G 2399
+U 8042 ; WX 1089 ; N uni1F6A ; G 2400
+U 8043 ; WX 1095 ; N uni1F6B ; G 2401
+U 8044 ; WX 946 ; N uni1F6C ; G 2402
+U 8045 ; WX 972 ; N uni1F6D ; G 2403
+U 8046 ; WX 921 ; N uni1F6E ; G 2404
+U 8047 ; WX 952 ; N uni1F6F ; G 2405
+U 8048 ; WX 659 ; N uni1F70 ; G 2406
+U 8049 ; WX 659 ; N uni1F71 ; G 2407
+U 8050 ; WX 541 ; N uni1F72 ; G 2408
+U 8051 ; WX 548 ; N uni1F73 ; G 2409
+U 8052 ; WX 634 ; N uni1F74 ; G 2410
+U 8053 ; WX 654 ; N uni1F75 ; G 2411
+U 8054 ; WX 338 ; N uni1F76 ; G 2412
+U 8055 ; WX 338 ; N uni1F77 ; G 2413
+U 8056 ; WX 612 ; N uni1F78 ; G 2414
+U 8057 ; WX 612 ; N uni1F79 ; G 2415
+U 8058 ; WX 579 ; N uni1F7A ; G 2416
+U 8059 ; WX 579 ; N uni1F7B ; G 2417
+U 8060 ; WX 837 ; N uni1F7C ; G 2418
+U 8061 ; WX 837 ; N uni1F7D ; G 2419
+U 8064 ; WX 659 ; N uni1F80 ; G 2420
+U 8065 ; WX 659 ; N uni1F81 ; G 2421
+U 8066 ; WX 659 ; N uni1F82 ; G 2422
+U 8067 ; WX 659 ; N uni1F83 ; G 2423
+U 8068 ; WX 659 ; N uni1F84 ; G 2424
+U 8069 ; WX 659 ; N uni1F85 ; G 2425
+U 8070 ; WX 659 ; N uni1F86 ; G 2426
+U 8071 ; WX 659 ; N uni1F87 ; G 2427
+U 8072 ; WX 684 ; N uni1F88 ; G 2428
+U 8073 ; WX 684 ; N uni1F89 ; G 2429
+U 8074 ; WX 877 ; N uni1F8A ; G 2430
+U 8075 ; WX 877 ; N uni1F8B ; G 2431
+U 8076 ; WX 769 ; N uni1F8C ; G 2432
+U 8077 ; WX 801 ; N uni1F8D ; G 2433
+U 8078 ; WX 708 ; N uni1F8E ; G 2434
+U 8079 ; WX 743 ; N uni1F8F ; G 2435
+U 8080 ; WX 634 ; N uni1F90 ; G 2436
+U 8081 ; WX 634 ; N uni1F91 ; G 2437
+U 8082 ; WX 634 ; N uni1F92 ; G 2438
+U 8083 ; WX 634 ; N uni1F93 ; G 2439
+U 8084 ; WX 634 ; N uni1F94 ; G 2440
+U 8085 ; WX 634 ; N uni1F95 ; G 2441
+U 8086 ; WX 634 ; N uni1F96 ; G 2442
+U 8087 ; WX 634 ; N uni1F97 ; G 2443
+U 8088 ; WX 837 ; N uni1F98 ; G 2444
+U 8089 ; WX 835 ; N uni1F99 ; G 2445
+U 8090 ; WX 1086 ; N uni1F9A ; G 2446
+U 8091 ; WX 1089 ; N uni1F9B ; G 2447
+U 8092 ; WX 1027 ; N uni1F9C ; G 2448
+U 8093 ; WX 1051 ; N uni1F9D ; G 2449
+U 8094 ; WX 934 ; N uni1F9E ; G 2450
+U 8095 ; WX 947 ; N uni1F9F ; G 2451
+U 8096 ; WX 837 ; N uni1FA0 ; G 2452
+U 8097 ; WX 837 ; N uni1FA1 ; G 2453
+U 8098 ; WX 837 ; N uni1FA2 ; G 2454
+U 8099 ; WX 837 ; N uni1FA3 ; G 2455
+U 8100 ; WX 837 ; N uni1FA4 ; G 2456
+U 8101 ; WX 837 ; N uni1FA5 ; G 2457
+U 8102 ; WX 837 ; N uni1FA6 ; G 2458
+U 8103 ; WX 837 ; N uni1FA7 ; G 2459
+U 8104 ; WX 802 ; N uni1FA8 ; G 2460
+U 8105 ; WX 843 ; N uni1FA9 ; G 2461
+U 8106 ; WX 1089 ; N uni1FAA ; G 2462
+U 8107 ; WX 1095 ; N uni1FAB ; G 2463
+U 8108 ; WX 946 ; N uni1FAC ; G 2464
+U 8109 ; WX 972 ; N uni1FAD ; G 2465
+U 8110 ; WX 921 ; N uni1FAE ; G 2466
+U 8111 ; WX 952 ; N uni1FAF ; G 2467
+U 8112 ; WX 659 ; N uni1FB0 ; G 2468
+U 8113 ; WX 659 ; N uni1FB1 ; G 2469
+U 8114 ; WX 659 ; N uni1FB2 ; G 2470
+U 8115 ; WX 659 ; N uni1FB3 ; G 2471
+U 8116 ; WX 659 ; N uni1FB4 ; G 2472
+U 8118 ; WX 659 ; N uni1FB6 ; G 2473
+U 8119 ; WX 659 ; N uni1FB7 ; G 2474
+U 8120 ; WX 684 ; N uni1FB8 ; G 2475
+U 8121 ; WX 684 ; N uni1FB9 ; G 2476
+U 8122 ; WX 716 ; N uni1FBA ; G 2477
+U 8123 ; WX 692 ; N uni1FBB ; G 2478
+U 8124 ; WX 684 ; N uni1FBC ; G 2479
+U 8125 ; WX 500 ; N uni1FBD ; G 2480
+U 8126 ; WX 500 ; N uni1FBE ; G 2481
+U 8127 ; WX 500 ; N uni1FBF ; G 2482
+U 8128 ; WX 500 ; N uni1FC0 ; G 2483
+U 8129 ; WX 500 ; N uni1FC1 ; G 2484
+U 8130 ; WX 634 ; N uni1FC2 ; G 2485
+U 8131 ; WX 634 ; N uni1FC3 ; G 2486
+U 8132 ; WX 654 ; N uni1FC4 ; G 2487
+U 8134 ; WX 634 ; N uni1FC6 ; G 2488
+U 8135 ; WX 634 ; N uni1FC7 ; G 2489
+U 8136 ; WX 805 ; N uni1FC8 ; G 2490
+U 8137 ; WX 746 ; N uni1FC9 ; G 2491
+U 8138 ; WX 931 ; N uni1FCA ; G 2492
+U 8139 ; WX 871 ; N uni1FCB ; G 2493
+U 8140 ; WX 752 ; N uni1FCC ; G 2494
+U 8141 ; WX 500 ; N uni1FCD ; G 2495
+U 8142 ; WX 500 ; N uni1FCE ; G 2496
+U 8143 ; WX 500 ; N uni1FCF ; G 2497
+U 8144 ; WX 338 ; N uni1FD0 ; G 2498
+U 8145 ; WX 338 ; N uni1FD1 ; G 2499
+U 8146 ; WX 338 ; N uni1FD2 ; G 2500
+U 8147 ; WX 338 ; N uni1FD3 ; G 2501
+U 8150 ; WX 338 ; N uni1FD6 ; G 2502
+U 8151 ; WX 338 ; N uni1FD7 ; G 2503
+U 8152 ; WX 295 ; N uni1FD8 ; G 2504
+U 8153 ; WX 295 ; N uni1FD9 ; G 2505
+U 8154 ; WX 475 ; N uni1FDA ; G 2506
+U 8155 ; WX 408 ; N uni1FDB ; G 2507
+U 8157 ; WX 500 ; N uni1FDD ; G 2508
+U 8158 ; WX 500 ; N uni1FDE ; G 2509
+U 8159 ; WX 500 ; N uni1FDF ; G 2510
+U 8160 ; WX 579 ; N uni1FE0 ; G 2511
+U 8161 ; WX 579 ; N uni1FE1 ; G 2512
+U 8162 ; WX 579 ; N uni1FE2 ; G 2513
+U 8163 ; WX 579 ; N uni1FE3 ; G 2514
+U 8164 ; WX 635 ; N uni1FE4 ; G 2515
+U 8165 ; WX 635 ; N uni1FE5 ; G 2516
+U 8166 ; WX 579 ; N uni1FE6 ; G 2517
+U 8167 ; WX 579 ; N uni1FE7 ; G 2518
+U 8168 ; WX 611 ; N uni1FE8 ; G 2519
+U 8169 ; WX 611 ; N uni1FE9 ; G 2520
+U 8170 ; WX 845 ; N uni1FEA ; G 2521
+U 8171 ; WX 825 ; N uni1FEB ; G 2522
+U 8172 ; WX 685 ; N uni1FEC ; G 2523
+U 8173 ; WX 500 ; N uni1FED ; G 2524
+U 8174 ; WX 500 ; N uni1FEE ; G 2525
+U 8175 ; WX 500 ; N uni1FEF ; G 2526
+U 8178 ; WX 837 ; N uni1FF2 ; G 2527
+U 8179 ; WX 837 ; N uni1FF3 ; G 2528
+U 8180 ; WX 837 ; N uni1FF4 ; G 2529
+U 8182 ; WX 837 ; N uni1FF6 ; G 2530
+U 8183 ; WX 837 ; N uni1FF7 ; G 2531
+U 8184 ; WX 941 ; N uni1FF8 ; G 2532
+U 8185 ; WX 813 ; N uni1FF9 ; G 2533
+U 8186 ; WX 922 ; N uni1FFA ; G 2534
+U 8187 ; WX 826 ; N uni1FFB ; G 2535
+U 8188 ; WX 764 ; N uni1FFC ; G 2536
+U 8189 ; WX 500 ; N uni1FFD ; G 2537
+U 8190 ; WX 500 ; N uni1FFE ; G 2538
+U 8192 ; WX 500 ; N uni2000 ; G 2539
+U 8193 ; WX 1000 ; N uni2001 ; G 2540
+U 8194 ; WX 500 ; N uni2002 ; G 2541
+U 8195 ; WX 1000 ; N uni2003 ; G 2542
+U 8196 ; WX 330 ; N uni2004 ; G 2543
+U 8197 ; WX 250 ; N uni2005 ; G 2544
+U 8198 ; WX 167 ; N uni2006 ; G 2545
+U 8199 ; WX 636 ; N uni2007 ; G 2546
+U 8200 ; WX 318 ; N uni2008 ; G 2547
+U 8201 ; WX 200 ; N uni2009 ; G 2548
+U 8202 ; WX 100 ; N uni200A ; G 2549
+U 8203 ; WX 0 ; N uni200B ; G 2550
+U 8204 ; WX 0 ; N uni200C ; G 2551
+U 8205 ; WX 0 ; N uni200D ; G 2552
+U 8206 ; WX 0 ; N uni200E ; G 2553
+U 8207 ; WX 0 ; N uni200F ; G 2554
+U 8208 ; WX 361 ; N uni2010 ; G 2555
+U 8209 ; WX 361 ; N uni2011 ; G 2556
+U 8210 ; WX 636 ; N figuredash ; G 2557
+U 8211 ; WX 500 ; N endash ; G 2558
+U 8212 ; WX 1000 ; N emdash ; G 2559
+U 8213 ; WX 1000 ; N uni2015 ; G 2560
+U 8214 ; WX 500 ; N uni2016 ; G 2561
+U 8215 ; WX 500 ; N underscoredbl ; G 2562
+U 8216 ; WX 318 ; N quoteleft ; G 2563
+U 8217 ; WX 318 ; N quoteright ; G 2564
+U 8218 ; WX 318 ; N quotesinglbase ; G 2565
+U 8219 ; WX 318 ; N quotereversed ; G 2566
+U 8220 ; WX 518 ; N quotedblleft ; G 2567
+U 8221 ; WX 518 ; N quotedblright ; G 2568
+U 8222 ; WX 518 ; N quotedblbase ; G 2569
+U 8223 ; WX 518 ; N uni201F ; G 2570
+U 8224 ; WX 500 ; N dagger ; G 2571
+U 8225 ; WX 500 ; N daggerdbl ; G 2572
+U 8226 ; WX 590 ; N bullet ; G 2573
+U 8227 ; WX 590 ; N uni2023 ; G 2574
+U 8228 ; WX 333 ; N onedotenleader ; G 2575
+U 8229 ; WX 667 ; N twodotenleader ; G 2576
+U 8230 ; WX 1000 ; N ellipsis ; G 2577
+U 8231 ; WX 318 ; N uni2027 ; G 2578
+U 8232 ; WX 0 ; N uni2028 ; G 2579
+U 8233 ; WX 0 ; N uni2029 ; G 2580
+U 8234 ; WX 0 ; N uni202A ; G 2581
+U 8235 ; WX 0 ; N uni202B ; G 2582
+U 8236 ; WX 0 ; N uni202C ; G 2583
+U 8237 ; WX 0 ; N uni202D ; G 2584
+U 8238 ; WX 0 ; N uni202E ; G 2585
+U 8239 ; WX 200 ; N uni202F ; G 2586
+U 8240 ; WX 1350 ; N perthousand ; G 2587
+U 8241 ; WX 1690 ; N uni2031 ; G 2588
+U 8242 ; WX 227 ; N minute ; G 2589
+U 8243 ; WX 374 ; N second ; G 2590
+U 8244 ; WX 520 ; N uni2034 ; G 2591
+U 8245 ; WX 227 ; N uni2035 ; G 2592
+U 8246 ; WX 374 ; N uni2036 ; G 2593
+U 8247 ; WX 520 ; N uni2037 ; G 2594
+U 8248 ; WX 339 ; N uni2038 ; G 2595
+U 8249 ; WX 400 ; N guilsinglleft ; G 2596
+U 8250 ; WX 400 ; N guilsinglright ; G 2597
+U 8251 ; WX 838 ; N uni203B ; G 2598
+U 8252 ; WX 485 ; N exclamdbl ; G 2599
+U 8253 ; WX 531 ; N uni203D ; G 2600
+U 8254 ; WX 500 ; N uni203E ; G 2601
+U 8255 ; WX 804 ; N uni203F ; G 2602
+U 8256 ; WX 804 ; N uni2040 ; G 2603
+U 8257 ; WX 250 ; N uni2041 ; G 2604
+U 8258 ; WX 1000 ; N uni2042 ; G 2605
+U 8259 ; WX 500 ; N uni2043 ; G 2606
+U 8260 ; WX 167 ; N fraction ; G 2607
+U 8261 ; WX 390 ; N uni2045 ; G 2608
+U 8262 ; WX 390 ; N uni2046 ; G 2609
+U 8263 ; WX 922 ; N uni2047 ; G 2610
+U 8264 ; WX 733 ; N uni2048 ; G 2611
+U 8265 ; WX 733 ; N uni2049 ; G 2612
+U 8266 ; WX 497 ; N uni204A ; G 2613
+U 8267 ; WX 636 ; N uni204B ; G 2614
+U 8268 ; WX 500 ; N uni204C ; G 2615
+U 8269 ; WX 500 ; N uni204D ; G 2616
+U 8270 ; WX 500 ; N uni204E ; G 2617
+U 8271 ; WX 337 ; N uni204F ; G 2618
+U 8272 ; WX 804 ; N uni2050 ; G 2619
+U 8273 ; WX 500 ; N uni2051 ; G 2620
+U 8274 ; WX 450 ; N uni2052 ; G 2621
+U 8275 ; WX 1000 ; N uni2053 ; G 2622
+U 8276 ; WX 804 ; N uni2054 ; G 2623
+U 8277 ; WX 838 ; N uni2055 ; G 2624
+U 8278 ; WX 586 ; N uni2056 ; G 2625
+U 8279 ; WX 663 ; N uni2057 ; G 2626
+U 8280 ; WX 838 ; N uni2058 ; G 2627
+U 8281 ; WX 838 ; N uni2059 ; G 2628
+U 8282 ; WX 318 ; N uni205A ; G 2629
+U 8283 ; WX 797 ; N uni205B ; G 2630
+U 8284 ; WX 838 ; N uni205C ; G 2631
+U 8285 ; WX 318 ; N uni205D ; G 2632
+U 8286 ; WX 318 ; N uni205E ; G 2633
+U 8287 ; WX 222 ; N uni205F ; G 2634
+U 8288 ; WX 0 ; N uni2060 ; G 2635
+U 8289 ; WX 0 ; N uni2061 ; G 2636
+U 8290 ; WX 0 ; N uni2062 ; G 2637
+U 8291 ; WX 0 ; N uni2063 ; G 2638
+U 8292 ; WX 0 ; N uni2064 ; G 2639
+U 8298 ; WX 0 ; N uni206A ; G 2640
+U 8299 ; WX 0 ; N uni206B ; G 2641
+U 8300 ; WX 0 ; N uni206C ; G 2642
+U 8301 ; WX 0 ; N uni206D ; G 2643
+U 8302 ; WX 0 ; N uni206E ; G 2644
+U 8303 ; WX 0 ; N uni206F ; G 2645
+U 8304 ; WX 401 ; N uni2070 ; G 2646
+U 8305 ; WX 179 ; N uni2071 ; G 2647
+U 8308 ; WX 401 ; N uni2074 ; G 2648
+U 8309 ; WX 401 ; N uni2075 ; G 2649
+U 8310 ; WX 401 ; N uni2076 ; G 2650
+U 8311 ; WX 401 ; N uni2077 ; G 2651
+U 8312 ; WX 401 ; N uni2078 ; G 2652
+U 8313 ; WX 401 ; N uni2079 ; G 2653
+U 8314 ; WX 528 ; N uni207A ; G 2654
+U 8315 ; WX 528 ; N uni207B ; G 2655
+U 8316 ; WX 528 ; N uni207C ; G 2656
+U 8317 ; WX 246 ; N uni207D ; G 2657
+U 8318 ; WX 246 ; N uni207E ; G 2658
+U 8319 ; WX 399 ; N uni207F ; G 2659
+U 8320 ; WX 401 ; N uni2080 ; G 2660
+U 8321 ; WX 401 ; N uni2081 ; G 2661
+U 8322 ; WX 401 ; N uni2082 ; G 2662
+U 8323 ; WX 401 ; N uni2083 ; G 2663
+U 8324 ; WX 401 ; N uni2084 ; G 2664
+U 8325 ; WX 401 ; N uni2085 ; G 2665
+U 8326 ; WX 401 ; N uni2086 ; G 2666
+U 8327 ; WX 401 ; N uni2087 ; G 2667
+U 8328 ; WX 401 ; N uni2088 ; G 2668
+U 8329 ; WX 401 ; N uni2089 ; G 2669
+U 8330 ; WX 528 ; N uni208A ; G 2670
+U 8331 ; WX 528 ; N uni208B ; G 2671
+U 8332 ; WX 528 ; N uni208C ; G 2672
+U 8333 ; WX 246 ; N uni208D ; G 2673
+U 8334 ; WX 246 ; N uni208E ; G 2674
+U 8336 ; WX 392 ; N uni2090 ; G 2675
+U 8337 ; WX 417 ; N uni2091 ; G 2676
+U 8338 ; WX 414 ; N uni2092 ; G 2677
+U 8339 ; WX 444 ; N uni2093 ; G 2678
+U 8340 ; WX 417 ; N uni2094 ; G 2679
+U 8341 ; WX 399 ; N uni2095 ; G 2680
+U 8342 ; WX 426 ; N uni2096 ; G 2681
+U 8343 ; WX 166 ; N uni2097 ; G 2682
+U 8344 ; WX 623 ; N uni2098 ; G 2683
+U 8345 ; WX 399 ; N uni2099 ; G 2684
+U 8346 ; WX 428 ; N uni209A ; G 2685
+U 8347 ; WX 373 ; N uni209B ; G 2686
+U 8348 ; WX 295 ; N uni209C ; G 2687
+U 8352 ; WX 877 ; N uni20A0 ; G 2688
+U 8353 ; WX 636 ; N colonmonetary ; G 2689
+U 8354 ; WX 636 ; N uni20A2 ; G 2690
+U 8355 ; WX 636 ; N franc ; G 2691
+U 8356 ; WX 636 ; N lira ; G 2692
+U 8357 ; WX 974 ; N uni20A5 ; G 2693
+U 8358 ; WX 636 ; N uni20A6 ; G 2694
+U 8359 ; WX 1271 ; N peseta ; G 2695
+U 8360 ; WX 1074 ; N uni20A8 ; G 2696
+U 8361 ; WX 989 ; N uni20A9 ; G 2697
+U 8362 ; WX 838 ; N uni20AA ; G 2698
+U 8363 ; WX 636 ; N dong ; G 2699
+U 8364 ; WX 636 ; N Euro ; G 2700
+U 8365 ; WX 636 ; N uni20AD ; G 2701
+U 8366 ; WX 636 ; N uni20AE ; G 2702
+U 8367 ; WX 1272 ; N uni20AF ; G 2703
+U 8368 ; WX 636 ; N uni20B0 ; G 2704
+U 8369 ; WX 636 ; N uni20B1 ; G 2705
+U 8370 ; WX 636 ; N uni20B2 ; G 2706
+U 8371 ; WX 636 ; N uni20B3 ; G 2707
+U 8372 ; WX 774 ; N uni20B4 ; G 2708
+U 8373 ; WX 636 ; N uni20B5 ; G 2709
+U 8376 ; WX 636 ; N uni20B8 ; G 2710
+U 8377 ; WX 636 ; N uni20B9 ; G 2711
+U 8378 ; WX 636 ; N uni20BA ; G 2712
+U 8381 ; WX 636 ; N uni20BD ; G 2713
+U 8400 ; WX 0 ; N uni20D0 ; G 2714
+U 8401 ; WX 0 ; N uni20D1 ; G 2715
+U 8406 ; WX 0 ; N uni20D6 ; G 2716
+U 8407 ; WX 0 ; N uni20D7 ; G 2717
+U 8411 ; WX 0 ; N uni20DB ; G 2718
+U 8412 ; WX 0 ; N uni20DC ; G 2719
+U 8417 ; WX 0 ; N uni20E1 ; G 2720
+U 8448 ; WX 970 ; N uni2100 ; G 2721
+U 8449 ; WX 970 ; N uni2101 ; G 2722
+U 8450 ; WX 698 ; N uni2102 ; G 2723
+U 8451 ; WX 1123 ; N uni2103 ; G 2724
+U 8452 ; WX 896 ; N uni2104 ; G 2725
+U 8453 ; WX 969 ; N uni2105 ; G 2726
+U 8454 ; WX 1032 ; N uni2106 ; G 2727
+U 8455 ; WX 614 ; N uni2107 ; G 2728
+U 8456 ; WX 698 ; N uni2108 ; G 2729
+U 8457 ; WX 952 ; N uni2109 ; G 2730
+U 8459 ; WX 988 ; N uni210B ; G 2731
+U 8460 ; WX 754 ; N uni210C ; G 2732
+U 8461 ; WX 850 ; N uni210D ; G 2733
+U 8462 ; WX 634 ; N uni210E ; G 2734
+U 8463 ; WX 634 ; N uni210F ; G 2735
+U 8464 ; WX 470 ; N uni2110 ; G 2736
+U 8465 ; WX 697 ; N Ifraktur ; G 2737
+U 8466 ; WX 720 ; N uni2112 ; G 2738
+U 8467 ; WX 413 ; N uni2113 ; G 2739
+U 8468 ; WX 818 ; N uni2114 ; G 2740
+U 8469 ; WX 801 ; N uni2115 ; G 2741
+U 8470 ; WX 1040 ; N uni2116 ; G 2742
+U 8471 ; WX 1000 ; N uni2117 ; G 2743
+U 8472 ; WX 697 ; N weierstrass ; G 2744
+U 8473 ; WX 701 ; N uni2119 ; G 2745
+U 8474 ; WX 787 ; N uni211A ; G 2746
+U 8475 ; WX 798 ; N uni211B ; G 2747
+U 8476 ; WX 814 ; N Rfraktur ; G 2748
+U 8477 ; WX 792 ; N uni211D ; G 2749
+U 8478 ; WX 896 ; N prescription ; G 2750
+U 8479 ; WX 684 ; N uni211F ; G 2751
+U 8480 ; WX 1020 ; N uni2120 ; G 2752
+U 8481 ; WX 1014 ; N uni2121 ; G 2753
+U 8482 ; WX 1000 ; N trademark ; G 2754
+U 8483 ; WX 684 ; N uni2123 ; G 2755
+U 8484 ; WX 745 ; N uni2124 ; G 2756
+U 8485 ; WX 578 ; N uni2125 ; G 2757
+U 8486 ; WX 764 ; N uni2126 ; G 2758
+U 8487 ; WX 764 ; N uni2127 ; G 2759
+U 8488 ; WX 616 ; N uni2128 ; G 2760
+U 8489 ; WX 338 ; N uni2129 ; G 2761
+U 8490 ; WX 656 ; N uni212A ; G 2762
+U 8491 ; WX 684 ; N uni212B ; G 2763
+U 8492 ; WX 786 ; N uni212C ; G 2764
+U 8493 ; WX 703 ; N uni212D ; G 2765
+U 8494 ; WX 854 ; N estimated ; G 2766
+U 8495 ; WX 592 ; N uni212F ; G 2767
+U 8496 ; WX 605 ; N uni2130 ; G 2768
+U 8497 ; WX 786 ; N uni2131 ; G 2769
+U 8498 ; WX 575 ; N uni2132 ; G 2770
+U 8499 ; WX 1069 ; N uni2133 ; G 2771
+U 8500 ; WX 462 ; N uni2134 ; G 2772
+U 8501 ; WX 745 ; N aleph ; G 2773
+U 8502 ; WX 674 ; N uni2136 ; G 2774
+U 8503 ; WX 466 ; N uni2137 ; G 2775
+U 8504 ; WX 645 ; N uni2138 ; G 2776
+U 8505 ; WX 380 ; N uni2139 ; G 2777
+U 8506 ; WX 926 ; N uni213A ; G 2778
+U 8507 ; WX 1157 ; N uni213B ; G 2779
+U 8508 ; WX 702 ; N uni213C ; G 2780
+U 8509 ; WX 728 ; N uni213D ; G 2781
+U 8510 ; WX 654 ; N uni213E ; G 2782
+U 8511 ; WX 849 ; N uni213F ; G 2783
+U 8512 ; WX 811 ; N uni2140 ; G 2784
+U 8513 ; WX 775 ; N uni2141 ; G 2785
+U 8514 ; WX 557 ; N uni2142 ; G 2786
+U 8515 ; WX 557 ; N uni2143 ; G 2787
+U 8516 ; WX 611 ; N uni2144 ; G 2788
+U 8517 ; WX 819 ; N uni2145 ; G 2789
+U 8518 ; WX 708 ; N uni2146 ; G 2790
+U 8519 ; WX 615 ; N uni2147 ; G 2791
+U 8520 ; WX 351 ; N uni2148 ; G 2792
+U 8521 ; WX 351 ; N uni2149 ; G 2793
+U 8523 ; WX 780 ; N uni214B ; G 2794
+U 8526 ; WX 526 ; N uni214E ; G 2795
+U 8528 ; WX 969 ; N uni2150 ; G 2796
+U 8529 ; WX 969 ; N uni2151 ; G 2797
+U 8530 ; WX 1370 ; N uni2152 ; G 2798
+U 8531 ; WX 969 ; N onethird ; G 2799
+U 8532 ; WX 969 ; N twothirds ; G 2800
+U 8533 ; WX 969 ; N uni2155 ; G 2801
+U 8534 ; WX 969 ; N uni2156 ; G 2802
+U 8535 ; WX 969 ; N uni2157 ; G 2803
+U 8536 ; WX 969 ; N uni2158 ; G 2804
+U 8537 ; WX 969 ; N uni2159 ; G 2805
+U 8538 ; WX 969 ; N uni215A ; G 2806
+U 8539 ; WX 969 ; N oneeighth ; G 2807
+U 8540 ; WX 969 ; N threeeighths ; G 2808
+U 8541 ; WX 969 ; N fiveeighths ; G 2809
+U 8542 ; WX 969 ; N seveneighths ; G 2810
+U 8543 ; WX 568 ; N uni215F ; G 2811
+U 8544 ; WX 295 ; N uni2160 ; G 2812
+U 8545 ; WX 492 ; N uni2161 ; G 2813
+U 8546 ; WX 689 ; N uni2162 ; G 2814
+U 8547 ; WX 923 ; N uni2163 ; G 2815
+U 8548 ; WX 684 ; N uni2164 ; G 2816
+U 8549 ; WX 922 ; N uni2165 ; G 2817
+U 8550 ; WX 1120 ; N uni2166 ; G 2818
+U 8551 ; WX 1317 ; N uni2167 ; G 2819
+U 8552 ; WX 917 ; N uni2168 ; G 2820
+U 8553 ; WX 685 ; N uni2169 ; G 2821
+U 8554 ; WX 933 ; N uni216A ; G 2822
+U 8555 ; WX 1131 ; N uni216B ; G 2823
+U 8556 ; WX 557 ; N uni216C ; G 2824
+U 8557 ; WX 698 ; N uni216D ; G 2825
+U 8558 ; WX 770 ; N uni216E ; G 2826
+U 8559 ; WX 863 ; N uni216F ; G 2827
+U 8560 ; WX 278 ; N uni2170 ; G 2828
+U 8561 ; WX 458 ; N uni2171 ; G 2829
+U 8562 ; WX 637 ; N uni2172 ; G 2830
+U 8563 ; WX 812 ; N uni2173 ; G 2831
+U 8564 ; WX 592 ; N uni2174 ; G 2832
+U 8565 ; WX 811 ; N uni2175 ; G 2833
+U 8566 ; WX 991 ; N uni2176 ; G 2834
+U 8567 ; WX 1170 ; N uni2177 ; G 2835
+U 8568 ; WX 819 ; N uni2178 ; G 2836
+U 8569 ; WX 592 ; N uni2179 ; G 2837
+U 8570 ; WX 822 ; N uni217A ; G 2838
+U 8571 ; WX 1002 ; N uni217B ; G 2839
+U 8572 ; WX 278 ; N uni217C ; G 2840
+U 8573 ; WX 550 ; N uni217D ; G 2841
+U 8574 ; WX 635 ; N uni217E ; G 2842
+U 8575 ; WX 974 ; N uni217F ; G 2843
+U 8576 ; WX 1245 ; N uni2180 ; G 2844
+U 8577 ; WX 770 ; N uni2181 ; G 2845
+U 8578 ; WX 1245 ; N uni2182 ; G 2846
+U 8579 ; WX 703 ; N uni2183 ; G 2847
+U 8580 ; WX 549 ; N uni2184 ; G 2848
+U 8581 ; WX 698 ; N uni2185 ; G 2849
+U 8585 ; WX 969 ; N uni2189 ; G 2850
+U 8592 ; WX 838 ; N arrowleft ; G 2851
+U 8593 ; WX 838 ; N arrowup ; G 2852
+U 8594 ; WX 838 ; N arrowright ; G 2853
+U 8595 ; WX 838 ; N arrowdown ; G 2854
+U 8596 ; WX 838 ; N arrowboth ; G 2855
+U 8597 ; WX 838 ; N arrowupdn ; G 2856
+U 8598 ; WX 838 ; N uni2196 ; G 2857
+U 8599 ; WX 838 ; N uni2197 ; G 2858
+U 8600 ; WX 838 ; N uni2198 ; G 2859
+U 8601 ; WX 838 ; N uni2199 ; G 2860
+U 8602 ; WX 838 ; N uni219A ; G 2861
+U 8603 ; WX 838 ; N uni219B ; G 2862
+U 8604 ; WX 838 ; N uni219C ; G 2863
+U 8605 ; WX 838 ; N uni219D ; G 2864
+U 8606 ; WX 838 ; N uni219E ; G 2865
+U 8607 ; WX 838 ; N uni219F ; G 2866
+U 8608 ; WX 838 ; N uni21A0 ; G 2867
+U 8609 ; WX 838 ; N uni21A1 ; G 2868
+U 8610 ; WX 838 ; N uni21A2 ; G 2869
+U 8611 ; WX 838 ; N uni21A3 ; G 2870
+U 8612 ; WX 838 ; N uni21A4 ; G 2871
+U 8613 ; WX 838 ; N uni21A5 ; G 2872
+U 8614 ; WX 838 ; N uni21A6 ; G 2873
+U 8615 ; WX 838 ; N uni21A7 ; G 2874
+U 8616 ; WX 838 ; N arrowupdnbse ; G 2875
+U 8617 ; WX 838 ; N uni21A9 ; G 2876
+U 8618 ; WX 838 ; N uni21AA ; G 2877
+U 8619 ; WX 838 ; N uni21AB ; G 2878
+U 8620 ; WX 838 ; N uni21AC ; G 2879
+U 8621 ; WX 838 ; N uni21AD ; G 2880
+U 8622 ; WX 838 ; N uni21AE ; G 2881
+U 8623 ; WX 838 ; N uni21AF ; G 2882
+U 8624 ; WX 838 ; N uni21B0 ; G 2883
+U 8625 ; WX 838 ; N uni21B1 ; G 2884
+U 8626 ; WX 838 ; N uni21B2 ; G 2885
+U 8627 ; WX 838 ; N uni21B3 ; G 2886
+U 8628 ; WX 838 ; N uni21B4 ; G 2887
+U 8629 ; WX 838 ; N carriagereturn ; G 2888
+U 8630 ; WX 838 ; N uni21B6 ; G 2889
+U 8631 ; WX 838 ; N uni21B7 ; G 2890
+U 8632 ; WX 838 ; N uni21B8 ; G 2891
+U 8633 ; WX 838 ; N uni21B9 ; G 2892
+U 8634 ; WX 838 ; N uni21BA ; G 2893
+U 8635 ; WX 838 ; N uni21BB ; G 2894
+U 8636 ; WX 838 ; N uni21BC ; G 2895
+U 8637 ; WX 838 ; N uni21BD ; G 2896
+U 8638 ; WX 838 ; N uni21BE ; G 2897
+U 8639 ; WX 838 ; N uni21BF ; G 2898
+U 8640 ; WX 838 ; N uni21C0 ; G 2899
+U 8641 ; WX 838 ; N uni21C1 ; G 2900
+U 8642 ; WX 838 ; N uni21C2 ; G 2901
+U 8643 ; WX 838 ; N uni21C3 ; G 2902
+U 8644 ; WX 838 ; N uni21C4 ; G 2903
+U 8645 ; WX 838 ; N uni21C5 ; G 2904
+U 8646 ; WX 838 ; N uni21C6 ; G 2905
+U 8647 ; WX 838 ; N uni21C7 ; G 2906
+U 8648 ; WX 838 ; N uni21C8 ; G 2907
+U 8649 ; WX 838 ; N uni21C9 ; G 2908
+U 8650 ; WX 838 ; N uni21CA ; G 2909
+U 8651 ; WX 838 ; N uni21CB ; G 2910
+U 8652 ; WX 838 ; N uni21CC ; G 2911
+U 8653 ; WX 838 ; N uni21CD ; G 2912
+U 8654 ; WX 838 ; N uni21CE ; G 2913
+U 8655 ; WX 838 ; N uni21CF ; G 2914
+U 8656 ; WX 838 ; N arrowdblleft ; G 2915
+U 8657 ; WX 838 ; N arrowdblup ; G 2916
+U 8658 ; WX 838 ; N arrowdblright ; G 2917
+U 8659 ; WX 838 ; N arrowdbldown ; G 2918
+U 8660 ; WX 838 ; N arrowdblboth ; G 2919
+U 8661 ; WX 838 ; N uni21D5 ; G 2920
+U 8662 ; WX 838 ; N uni21D6 ; G 2921
+U 8663 ; WX 838 ; N uni21D7 ; G 2922
+U 8664 ; WX 838 ; N uni21D8 ; G 2923
+U 8665 ; WX 838 ; N uni21D9 ; G 2924
+U 8666 ; WX 838 ; N uni21DA ; G 2925
+U 8667 ; WX 838 ; N uni21DB ; G 2926
+U 8668 ; WX 838 ; N uni21DC ; G 2927
+U 8669 ; WX 838 ; N uni21DD ; G 2928
+U 8670 ; WX 838 ; N uni21DE ; G 2929
+U 8671 ; WX 838 ; N uni21DF ; G 2930
+U 8672 ; WX 838 ; N uni21E0 ; G 2931
+U 8673 ; WX 838 ; N uni21E1 ; G 2932
+U 8674 ; WX 838 ; N uni21E2 ; G 2933
+U 8675 ; WX 838 ; N uni21E3 ; G 2934
+U 8676 ; WX 838 ; N uni21E4 ; G 2935
+U 8677 ; WX 838 ; N uni21E5 ; G 2936
+U 8678 ; WX 838 ; N uni21E6 ; G 2937
+U 8679 ; WX 838 ; N uni21E7 ; G 2938
+U 8680 ; WX 838 ; N uni21E8 ; G 2939
+U 8681 ; WX 838 ; N uni21E9 ; G 2940
+U 8682 ; WX 838 ; N uni21EA ; G 2941
+U 8683 ; WX 838 ; N uni21EB ; G 2942
+U 8684 ; WX 838 ; N uni21EC ; G 2943
+U 8685 ; WX 838 ; N uni21ED ; G 2944
+U 8686 ; WX 838 ; N uni21EE ; G 2945
+U 8687 ; WX 838 ; N uni21EF ; G 2946
+U 8688 ; WX 838 ; N uni21F0 ; G 2947
+U 8689 ; WX 838 ; N uni21F1 ; G 2948
+U 8690 ; WX 838 ; N uni21F2 ; G 2949
+U 8691 ; WX 838 ; N uni21F3 ; G 2950
+U 8692 ; WX 838 ; N uni21F4 ; G 2951
+U 8693 ; WX 838 ; N uni21F5 ; G 2952
+U 8694 ; WX 838 ; N uni21F6 ; G 2953
+U 8695 ; WX 838 ; N uni21F7 ; G 2954
+U 8696 ; WX 838 ; N uni21F8 ; G 2955
+U 8697 ; WX 838 ; N uni21F9 ; G 2956
+U 8698 ; WX 838 ; N uni21FA ; G 2957
+U 8699 ; WX 838 ; N uni21FB ; G 2958
+U 8700 ; WX 838 ; N uni21FC ; G 2959
+U 8701 ; WX 838 ; N uni21FD ; G 2960
+U 8702 ; WX 838 ; N uni21FE ; G 2961
+U 8703 ; WX 838 ; N uni21FF ; G 2962
+U 8704 ; WX 684 ; N universal ; G 2963
+U 8705 ; WX 636 ; N uni2201 ; G 2964
+U 8706 ; WX 517 ; N partialdiff ; G 2965
+U 8707 ; WX 632 ; N existential ; G 2966
+U 8708 ; WX 632 ; N uni2204 ; G 2967
+U 8709 ; WX 871 ; N emptyset ; G 2968
+U 8710 ; WX 669 ; N increment ; G 2969
+U 8711 ; WX 669 ; N gradient ; G 2970
+U 8712 ; WX 871 ; N element ; G 2971
+U 8713 ; WX 871 ; N notelement ; G 2972
+U 8714 ; WX 718 ; N uni220A ; G 2973
+U 8715 ; WX 871 ; N suchthat ; G 2974
+U 8716 ; WX 871 ; N uni220C ; G 2975
+U 8717 ; WX 718 ; N uni220D ; G 2976
+U 8718 ; WX 636 ; N uni220E ; G 2977
+U 8719 ; WX 757 ; N product ; G 2978
+U 8720 ; WX 757 ; N uni2210 ; G 2979
+U 8721 ; WX 674 ; N summation ; G 2980
+U 8722 ; WX 838 ; N minus ; G 2981
+U 8723 ; WX 838 ; N uni2213 ; G 2982
+U 8724 ; WX 838 ; N uni2214 ; G 2983
+U 8725 ; WX 337 ; N uni2215 ; G 2984
+U 8726 ; WX 637 ; N uni2216 ; G 2985
+U 8727 ; WX 838 ; N asteriskmath ; G 2986
+U 8728 ; WX 626 ; N uni2218 ; G 2987
+U 8729 ; WX 626 ; N uni2219 ; G 2988
+U 8730 ; WX 637 ; N radical ; G 2989
+U 8731 ; WX 637 ; N uni221B ; G 2990
+U 8732 ; WX 637 ; N uni221C ; G 2991
+U 8733 ; WX 714 ; N proportional ; G 2992
+U 8734 ; WX 833 ; N infinity ; G 2993
+U 8735 ; WX 838 ; N orthogonal ; G 2994
+U 8736 ; WX 896 ; N angle ; G 2995
+U 8737 ; WX 896 ; N uni2221 ; G 2996
+U 8738 ; WX 838 ; N uni2222 ; G 2997
+U 8739 ; WX 500 ; N uni2223 ; G 2998
+U 8740 ; WX 500 ; N uni2224 ; G 2999
+U 8741 ; WX 500 ; N uni2225 ; G 3000
+U 8742 ; WX 500 ; N uni2226 ; G 3001
+U 8743 ; WX 732 ; N logicaland ; G 3002
+U 8744 ; WX 732 ; N logicalor ; G 3003
+U 8745 ; WX 732 ; N intersection ; G 3004
+U 8746 ; WX 732 ; N union ; G 3005
+U 8747 ; WX 521 ; N integral ; G 3006
+U 8748 ; WX 789 ; N uni222C ; G 3007
+U 8749 ; WX 1057 ; N uni222D ; G 3008
+U 8750 ; WX 521 ; N uni222E ; G 3009
+U 8751 ; WX 789 ; N uni222F ; G 3010
+U 8752 ; WX 1057 ; N uni2230 ; G 3011
+U 8753 ; WX 521 ; N uni2231 ; G 3012
+U 8754 ; WX 521 ; N uni2232 ; G 3013
+U 8755 ; WX 521 ; N uni2233 ; G 3014
+U 8756 ; WX 636 ; N therefore ; G 3015
+U 8757 ; WX 636 ; N uni2235 ; G 3016
+U 8758 ; WX 260 ; N uni2236 ; G 3017
+U 8759 ; WX 636 ; N uni2237 ; G 3018
+U 8760 ; WX 838 ; N uni2238 ; G 3019
+U 8761 ; WX 838 ; N uni2239 ; G 3020
+U 8762 ; WX 838 ; N uni223A ; G 3021
+U 8763 ; WX 838 ; N uni223B ; G 3022
+U 8764 ; WX 838 ; N similar ; G 3023
+U 8765 ; WX 838 ; N uni223D ; G 3024
+U 8766 ; WX 838 ; N uni223E ; G 3025
+U 8767 ; WX 838 ; N uni223F ; G 3026
+U 8768 ; WX 375 ; N uni2240 ; G 3027
+U 8769 ; WX 838 ; N uni2241 ; G 3028
+U 8770 ; WX 838 ; N uni2242 ; G 3029
+U 8771 ; WX 838 ; N uni2243 ; G 3030
+U 8772 ; WX 838 ; N uni2244 ; G 3031
+U 8773 ; WX 838 ; N congruent ; G 3032
+U 8774 ; WX 838 ; N uni2246 ; G 3033
+U 8775 ; WX 838 ; N uni2247 ; G 3034
+U 8776 ; WX 838 ; N approxequal ; G 3035
+U 8777 ; WX 838 ; N uni2249 ; G 3036
+U 8778 ; WX 838 ; N uni224A ; G 3037
+U 8779 ; WX 838 ; N uni224B ; G 3038
+U 8780 ; WX 838 ; N uni224C ; G 3039
+U 8781 ; WX 838 ; N uni224D ; G 3040
+U 8782 ; WX 838 ; N uni224E ; G 3041
+U 8783 ; WX 838 ; N uni224F ; G 3042
+U 8784 ; WX 838 ; N uni2250 ; G 3043
+U 8785 ; WX 838 ; N uni2251 ; G 3044
+U 8786 ; WX 838 ; N uni2252 ; G 3045
+U 8787 ; WX 838 ; N uni2253 ; G 3046
+U 8788 ; WX 1000 ; N uni2254 ; G 3047
+U 8789 ; WX 1000 ; N uni2255 ; G 3048
+U 8790 ; WX 838 ; N uni2256 ; G 3049
+U 8791 ; WX 838 ; N uni2257 ; G 3050
+U 8792 ; WX 838 ; N uni2258 ; G 3051
+U 8793 ; WX 838 ; N uni2259 ; G 3052
+U 8794 ; WX 838 ; N uni225A ; G 3053
+U 8795 ; WX 838 ; N uni225B ; G 3054
+U 8796 ; WX 838 ; N uni225C ; G 3055
+U 8797 ; WX 838 ; N uni225D ; G 3056
+U 8798 ; WX 838 ; N uni225E ; G 3057
+U 8799 ; WX 838 ; N uni225F ; G 3058
+U 8800 ; WX 838 ; N notequal ; G 3059
+U 8801 ; WX 838 ; N equivalence ; G 3060
+U 8802 ; WX 838 ; N uni2262 ; G 3061
+U 8803 ; WX 838 ; N uni2263 ; G 3062
+U 8804 ; WX 838 ; N lessequal ; G 3063
+U 8805 ; WX 838 ; N greaterequal ; G 3064
+U 8806 ; WX 838 ; N uni2266 ; G 3065
+U 8807 ; WX 838 ; N uni2267 ; G 3066
+U 8808 ; WX 838 ; N uni2268 ; G 3067
+U 8809 ; WX 838 ; N uni2269 ; G 3068
+U 8810 ; WX 1047 ; N uni226A ; G 3069
+U 8811 ; WX 1047 ; N uni226B ; G 3070
+U 8812 ; WX 464 ; N uni226C ; G 3071
+U 8813 ; WX 838 ; N uni226D ; G 3072
+U 8814 ; WX 838 ; N uni226E ; G 3073
+U 8815 ; WX 838 ; N uni226F ; G 3074
+U 8816 ; WX 838 ; N uni2270 ; G 3075
+U 8817 ; WX 838 ; N uni2271 ; G 3076
+U 8818 ; WX 838 ; N uni2272 ; G 3077
+U 8819 ; WX 838 ; N uni2273 ; G 3078
+U 8820 ; WX 838 ; N uni2274 ; G 3079
+U 8821 ; WX 838 ; N uni2275 ; G 3080
+U 8822 ; WX 838 ; N uni2276 ; G 3081
+U 8823 ; WX 838 ; N uni2277 ; G 3082
+U 8824 ; WX 838 ; N uni2278 ; G 3083
+U 8825 ; WX 838 ; N uni2279 ; G 3084
+U 8826 ; WX 838 ; N uni227A ; G 3085
+U 8827 ; WX 838 ; N uni227B ; G 3086
+U 8828 ; WX 838 ; N uni227C ; G 3087
+U 8829 ; WX 838 ; N uni227D ; G 3088
+U 8830 ; WX 838 ; N uni227E ; G 3089
+U 8831 ; WX 838 ; N uni227F ; G 3090
+U 8832 ; WX 838 ; N uni2280 ; G 3091
+U 8833 ; WX 838 ; N uni2281 ; G 3092
+U 8834 ; WX 838 ; N propersubset ; G 3093
+U 8835 ; WX 838 ; N propersuperset ; G 3094
+U 8836 ; WX 838 ; N notsubset ; G 3095
+U 8837 ; WX 838 ; N uni2285 ; G 3096
+U 8838 ; WX 838 ; N reflexsubset ; G 3097
+U 8839 ; WX 838 ; N reflexsuperset ; G 3098
+U 8840 ; WX 838 ; N uni2288 ; G 3099
+U 8841 ; WX 838 ; N uni2289 ; G 3100
+U 8842 ; WX 838 ; N uni228A ; G 3101
+U 8843 ; WX 838 ; N uni228B ; G 3102
+U 8844 ; WX 732 ; N uni228C ; G 3103
+U 8845 ; WX 732 ; N uni228D ; G 3104
+U 8846 ; WX 732 ; N uni228E ; G 3105
+U 8847 ; WX 838 ; N uni228F ; G 3106
+U 8848 ; WX 838 ; N uni2290 ; G 3107
+U 8849 ; WX 838 ; N uni2291 ; G 3108
+U 8850 ; WX 838 ; N uni2292 ; G 3109
+U 8851 ; WX 780 ; N uni2293 ; G 3110
+U 8852 ; WX 780 ; N uni2294 ; G 3111
+U 8853 ; WX 838 ; N circleplus ; G 3112
+U 8854 ; WX 838 ; N uni2296 ; G 3113
+U 8855 ; WX 838 ; N circlemultiply ; G 3114
+U 8856 ; WX 838 ; N uni2298 ; G 3115
+U 8857 ; WX 838 ; N uni2299 ; G 3116
+U 8858 ; WX 838 ; N uni229A ; G 3117
+U 8859 ; WX 838 ; N uni229B ; G 3118
+U 8860 ; WX 838 ; N uni229C ; G 3119
+U 8861 ; WX 838 ; N uni229D ; G 3120
+U 8862 ; WX 838 ; N uni229E ; G 3121
+U 8863 ; WX 838 ; N uni229F ; G 3122
+U 8864 ; WX 838 ; N uni22A0 ; G 3123
+U 8865 ; WX 838 ; N uni22A1 ; G 3124
+U 8866 ; WX 871 ; N uni22A2 ; G 3125
+U 8867 ; WX 871 ; N uni22A3 ; G 3126
+U 8868 ; WX 871 ; N uni22A4 ; G 3127
+U 8869 ; WX 871 ; N perpendicular ; G 3128
+U 8870 ; WX 521 ; N uni22A6 ; G 3129
+U 8871 ; WX 521 ; N uni22A7 ; G 3130
+U 8872 ; WX 871 ; N uni22A8 ; G 3131
+U 8873 ; WX 871 ; N uni22A9 ; G 3132
+U 8874 ; WX 871 ; N uni22AA ; G 3133
+U 8875 ; WX 871 ; N uni22AB ; G 3134
+U 8876 ; WX 871 ; N uni22AC ; G 3135
+U 8877 ; WX 871 ; N uni22AD ; G 3136
+U 8878 ; WX 871 ; N uni22AE ; G 3137
+U 8879 ; WX 871 ; N uni22AF ; G 3138
+U 8880 ; WX 838 ; N uni22B0 ; G 3139
+U 8881 ; WX 838 ; N uni22B1 ; G 3140
+U 8882 ; WX 838 ; N uni22B2 ; G 3141
+U 8883 ; WX 838 ; N uni22B3 ; G 3142
+U 8884 ; WX 838 ; N uni22B4 ; G 3143
+U 8885 ; WX 838 ; N uni22B5 ; G 3144
+U 8886 ; WX 1000 ; N uni22B6 ; G 3145
+U 8887 ; WX 1000 ; N uni22B7 ; G 3146
+U 8888 ; WX 838 ; N uni22B8 ; G 3147
+U 8889 ; WX 838 ; N uni22B9 ; G 3148
+U 8890 ; WX 521 ; N uni22BA ; G 3149
+U 8891 ; WX 732 ; N uni22BB ; G 3150
+U 8892 ; WX 732 ; N uni22BC ; G 3151
+U 8893 ; WX 732 ; N uni22BD ; G 3152
+U 8894 ; WX 838 ; N uni22BE ; G 3153
+U 8895 ; WX 838 ; N uni22BF ; G 3154
+U 8896 ; WX 820 ; N uni22C0 ; G 3155
+U 8897 ; WX 820 ; N uni22C1 ; G 3156
+U 8898 ; WX 820 ; N uni22C2 ; G 3157
+U 8899 ; WX 820 ; N uni22C3 ; G 3158
+U 8900 ; WX 626 ; N uni22C4 ; G 3159
+U 8901 ; WX 318 ; N dotmath ; G 3160
+U 8902 ; WX 626 ; N uni22C6 ; G 3161
+U 8903 ; WX 838 ; N uni22C7 ; G 3162
+U 8904 ; WX 1000 ; N uni22C8 ; G 3163
+U 8905 ; WX 1000 ; N uni22C9 ; G 3164
+U 8906 ; WX 1000 ; N uni22CA ; G 3165
+U 8907 ; WX 1000 ; N uni22CB ; G 3166
+U 8908 ; WX 1000 ; N uni22CC ; G 3167
+U 8909 ; WX 838 ; N uni22CD ; G 3168
+U 8910 ; WX 732 ; N uni22CE ; G 3169
+U 8911 ; WX 732 ; N uni22CF ; G 3170
+U 8912 ; WX 838 ; N uni22D0 ; G 3171
+U 8913 ; WX 838 ; N uni22D1 ; G 3172
+U 8914 ; WX 838 ; N uni22D2 ; G 3173
+U 8915 ; WX 838 ; N uni22D3 ; G 3174
+U 8916 ; WX 838 ; N uni22D4 ; G 3175
+U 8917 ; WX 838 ; N uni22D5 ; G 3176
+U 8918 ; WX 838 ; N uni22D6 ; G 3177
+U 8919 ; WX 838 ; N uni22D7 ; G 3178
+U 8920 ; WX 1422 ; N uni22D8 ; G 3179
+U 8921 ; WX 1422 ; N uni22D9 ; G 3180
+U 8922 ; WX 838 ; N uni22DA ; G 3181
+U 8923 ; WX 838 ; N uni22DB ; G 3182
+U 8924 ; WX 838 ; N uni22DC ; G 3183
+U 8925 ; WX 838 ; N uni22DD ; G 3184
+U 8926 ; WX 838 ; N uni22DE ; G 3185
+U 8927 ; WX 838 ; N uni22DF ; G 3186
+U 8928 ; WX 838 ; N uni22E0 ; G 3187
+U 8929 ; WX 838 ; N uni22E1 ; G 3188
+U 8930 ; WX 838 ; N uni22E2 ; G 3189
+U 8931 ; WX 838 ; N uni22E3 ; G 3190
+U 8932 ; WX 838 ; N uni22E4 ; G 3191
+U 8933 ; WX 838 ; N uni22E5 ; G 3192
+U 8934 ; WX 838 ; N uni22E6 ; G 3193
+U 8935 ; WX 838 ; N uni22E7 ; G 3194
+U 8936 ; WX 838 ; N uni22E8 ; G 3195
+U 8937 ; WX 838 ; N uni22E9 ; G 3196
+U 8938 ; WX 838 ; N uni22EA ; G 3197
+U 8939 ; WX 838 ; N uni22EB ; G 3198
+U 8940 ; WX 838 ; N uni22EC ; G 3199
+U 8941 ; WX 838 ; N uni22ED ; G 3200
+U 8942 ; WX 1000 ; N uni22EE ; G 3201
+U 8943 ; WX 1000 ; N uni22EF ; G 3202
+U 8944 ; WX 1000 ; N uni22F0 ; G 3203
+U 8945 ; WX 1000 ; N uni22F1 ; G 3204
+U 8946 ; WX 1000 ; N uni22F2 ; G 3205
+U 8947 ; WX 871 ; N uni22F3 ; G 3206
+U 8948 ; WX 718 ; N uni22F4 ; G 3207
+U 8949 ; WX 871 ; N uni22F5 ; G 3208
+U 8950 ; WX 871 ; N uni22F6 ; G 3209
+U 8951 ; WX 718 ; N uni22F7 ; G 3210
+U 8952 ; WX 871 ; N uni22F8 ; G 3211
+U 8953 ; WX 871 ; N uni22F9 ; G 3212
+U 8954 ; WX 1000 ; N uni22FA ; G 3213
+U 8955 ; WX 871 ; N uni22FB ; G 3214
+U 8956 ; WX 718 ; N uni22FC ; G 3215
+U 8957 ; WX 871 ; N uni22FD ; G 3216
+U 8958 ; WX 718 ; N uni22FE ; G 3217
+U 8959 ; WX 871 ; N uni22FF ; G 3218
+U 8960 ; WX 602 ; N uni2300 ; G 3219
+U 8961 ; WX 602 ; N uni2301 ; G 3220
+U 8962 ; WX 635 ; N house ; G 3221
+U 8963 ; WX 838 ; N uni2303 ; G 3222
+U 8964 ; WX 838 ; N uni2304 ; G 3223
+U 8965 ; WX 838 ; N uni2305 ; G 3224
+U 8966 ; WX 838 ; N uni2306 ; G 3225
+U 8967 ; WX 488 ; N uni2307 ; G 3226
+U 8968 ; WX 390 ; N uni2308 ; G 3227
+U 8969 ; WX 390 ; N uni2309 ; G 3228
+U 8970 ; WX 390 ; N uni230A ; G 3229
+U 8971 ; WX 390 ; N uni230B ; G 3230
+U 8972 ; WX 809 ; N uni230C ; G 3231
+U 8973 ; WX 809 ; N uni230D ; G 3232
+U 8974 ; WX 809 ; N uni230E ; G 3233
+U 8975 ; WX 809 ; N uni230F ; G 3234
+U 8976 ; WX 838 ; N revlogicalnot ; G 3235
+U 8977 ; WX 513 ; N uni2311 ; G 3236
+U 8984 ; WX 1000 ; N uni2318 ; G 3237
+U 8985 ; WX 838 ; N uni2319 ; G 3238
+U 8988 ; WX 469 ; N uni231C ; G 3239
+U 8989 ; WX 469 ; N uni231D ; G 3240
+U 8990 ; WX 469 ; N uni231E ; G 3241
+U 8991 ; WX 469 ; N uni231F ; G 3242
+U 8992 ; WX 521 ; N integraltp ; G 3243
+U 8993 ; WX 521 ; N integralbt ; G 3244
+U 8996 ; WX 1152 ; N uni2324 ; G 3245
+U 8997 ; WX 1152 ; N uni2325 ; G 3246
+U 8998 ; WX 1414 ; N uni2326 ; G 3247
+U 8999 ; WX 1152 ; N uni2327 ; G 3248
+U 9000 ; WX 1443 ; N uni2328 ; G 3249
+U 9003 ; WX 1414 ; N uni232B ; G 3250
+U 9004 ; WX 873 ; N uni232C ; G 3251
+U 9075 ; WX 338 ; N uni2373 ; G 3252
+U 9076 ; WX 635 ; N uni2374 ; G 3253
+U 9077 ; WX 837 ; N uni2375 ; G 3254
+U 9082 ; WX 659 ; N uni237A ; G 3255
+U 9085 ; WX 757 ; N uni237D ; G 3256
+U 9095 ; WX 1152 ; N uni2387 ; G 3257
+U 9108 ; WX 873 ; N uni2394 ; G 3258
+U 9115 ; WX 500 ; N uni239B ; G 3259
+U 9116 ; WX 500 ; N uni239C ; G 3260
+U 9117 ; WX 500 ; N uni239D ; G 3261
+U 9118 ; WX 500 ; N uni239E ; G 3262
+U 9119 ; WX 500 ; N uni239F ; G 3263
+U 9120 ; WX 500 ; N uni23A0 ; G 3264
+U 9121 ; WX 500 ; N uni23A1 ; G 3265
+U 9122 ; WX 500 ; N uni23A2 ; G 3266
+U 9123 ; WX 500 ; N uni23A3 ; G 3267
+U 9124 ; WX 500 ; N uni23A4 ; G 3268
+U 9125 ; WX 500 ; N uni23A5 ; G 3269
+U 9126 ; WX 500 ; N uni23A6 ; G 3270
+U 9127 ; WX 750 ; N uni23A7 ; G 3271
+U 9128 ; WX 750 ; N uni23A8 ; G 3272
+U 9129 ; WX 750 ; N uni23A9 ; G 3273
+U 9130 ; WX 750 ; N uni23AA ; G 3274
+U 9131 ; WX 750 ; N uni23AB ; G 3275
+U 9132 ; WX 750 ; N uni23AC ; G 3276
+U 9133 ; WX 750 ; N uni23AD ; G 3277
+U 9134 ; WX 521 ; N uni23AE ; G 3278
+U 9166 ; WX 838 ; N uni23CE ; G 3279
+U 9167 ; WX 945 ; N uni23CF ; G 3280
+U 9187 ; WX 873 ; N uni23E3 ; G 3281
+U 9189 ; WX 769 ; N uni23E5 ; G 3282
+U 9192 ; WX 636 ; N uni23E8 ; G 3283
+U 9250 ; WX 635 ; N uni2422 ; G 3284
+U 9251 ; WX 635 ; N uni2423 ; G 3285
+U 9312 ; WX 896 ; N uni2460 ; G 3286
+U 9313 ; WX 896 ; N uni2461 ; G 3287
+U 9314 ; WX 896 ; N uni2462 ; G 3288
+U 9315 ; WX 896 ; N uni2463 ; G 3289
+U 9316 ; WX 896 ; N uni2464 ; G 3290
+U 9317 ; WX 896 ; N uni2465 ; G 3291
+U 9318 ; WX 896 ; N uni2466 ; G 3292
+U 9319 ; WX 896 ; N uni2467 ; G 3293
+U 9320 ; WX 896 ; N uni2468 ; G 3294
+U 9321 ; WX 896 ; N uni2469 ; G 3295
+U 9472 ; WX 602 ; N SF100000 ; G 3296
+U 9473 ; WX 602 ; N uni2501 ; G 3297
+U 9474 ; WX 602 ; N SF110000 ; G 3298
+U 9475 ; WX 602 ; N uni2503 ; G 3299
+U 9476 ; WX 602 ; N uni2504 ; G 3300
+U 9477 ; WX 602 ; N uni2505 ; G 3301
+U 9478 ; WX 602 ; N uni2506 ; G 3302
+U 9479 ; WX 602 ; N uni2507 ; G 3303
+U 9480 ; WX 602 ; N uni2508 ; G 3304
+U 9481 ; WX 602 ; N uni2509 ; G 3305
+U 9482 ; WX 602 ; N uni250A ; G 3306
+U 9483 ; WX 602 ; N uni250B ; G 3307
+U 9484 ; WX 602 ; N SF010000 ; G 3308
+U 9485 ; WX 602 ; N uni250D ; G 3309
+U 9486 ; WX 602 ; N uni250E ; G 3310
+U 9487 ; WX 602 ; N uni250F ; G 3311
+U 9488 ; WX 602 ; N SF030000 ; G 3312
+U 9489 ; WX 602 ; N uni2511 ; G 3313
+U 9490 ; WX 602 ; N uni2512 ; G 3314
+U 9491 ; WX 602 ; N uni2513 ; G 3315
+U 9492 ; WX 602 ; N SF020000 ; G 3316
+U 9493 ; WX 602 ; N uni2515 ; G 3317
+U 9494 ; WX 602 ; N uni2516 ; G 3318
+U 9495 ; WX 602 ; N uni2517 ; G 3319
+U 9496 ; WX 602 ; N SF040000 ; G 3320
+U 9497 ; WX 602 ; N uni2519 ; G 3321
+U 9498 ; WX 602 ; N uni251A ; G 3322
+U 9499 ; WX 602 ; N uni251B ; G 3323
+U 9500 ; WX 602 ; N SF080000 ; G 3324
+U 9501 ; WX 602 ; N uni251D ; G 3325
+U 9502 ; WX 602 ; N uni251E ; G 3326
+U 9503 ; WX 602 ; N uni251F ; G 3327
+U 9504 ; WX 602 ; N uni2520 ; G 3328
+U 9505 ; WX 602 ; N uni2521 ; G 3329
+U 9506 ; WX 602 ; N uni2522 ; G 3330
+U 9507 ; WX 602 ; N uni2523 ; G 3331
+U 9508 ; WX 602 ; N SF090000 ; G 3332
+U 9509 ; WX 602 ; N uni2525 ; G 3333
+U 9510 ; WX 602 ; N uni2526 ; G 3334
+U 9511 ; WX 602 ; N uni2527 ; G 3335
+U 9512 ; WX 602 ; N uni2528 ; G 3336
+U 9513 ; WX 602 ; N uni2529 ; G 3337
+U 9514 ; WX 602 ; N uni252A ; G 3338
+U 9515 ; WX 602 ; N uni252B ; G 3339
+U 9516 ; WX 602 ; N SF060000 ; G 3340
+U 9517 ; WX 602 ; N uni252D ; G 3341
+U 9518 ; WX 602 ; N uni252E ; G 3342
+U 9519 ; WX 602 ; N uni252F ; G 3343
+U 9520 ; WX 602 ; N uni2530 ; G 3344
+U 9521 ; WX 602 ; N uni2531 ; G 3345
+U 9522 ; WX 602 ; N uni2532 ; G 3346
+U 9523 ; WX 602 ; N uni2533 ; G 3347
+U 9524 ; WX 602 ; N SF070000 ; G 3348
+U 9525 ; WX 602 ; N uni2535 ; G 3349
+U 9526 ; WX 602 ; N uni2536 ; G 3350
+U 9527 ; WX 602 ; N uni2537 ; G 3351
+U 9528 ; WX 602 ; N uni2538 ; G 3352
+U 9529 ; WX 602 ; N uni2539 ; G 3353
+U 9530 ; WX 602 ; N uni253A ; G 3354
+U 9531 ; WX 602 ; N uni253B ; G 3355
+U 9532 ; WX 602 ; N SF050000 ; G 3356
+U 9533 ; WX 602 ; N uni253D ; G 3357
+U 9534 ; WX 602 ; N uni253E ; G 3358
+U 9535 ; WX 602 ; N uni253F ; G 3359
+U 9536 ; WX 602 ; N uni2540 ; G 3360
+U 9537 ; WX 602 ; N uni2541 ; G 3361
+U 9538 ; WX 602 ; N uni2542 ; G 3362
+U 9539 ; WX 602 ; N uni2543 ; G 3363
+U 9540 ; WX 602 ; N uni2544 ; G 3364
+U 9541 ; WX 602 ; N uni2545 ; G 3365
+U 9542 ; WX 602 ; N uni2546 ; G 3366
+U 9543 ; WX 602 ; N uni2547 ; G 3367
+U 9544 ; WX 602 ; N uni2548 ; G 3368
+U 9545 ; WX 602 ; N uni2549 ; G 3369
+U 9546 ; WX 602 ; N uni254A ; G 3370
+U 9547 ; WX 602 ; N uni254B ; G 3371
+U 9548 ; WX 602 ; N uni254C ; G 3372
+U 9549 ; WX 602 ; N uni254D ; G 3373
+U 9550 ; WX 602 ; N uni254E ; G 3374
+U 9551 ; WX 602 ; N uni254F ; G 3375
+U 9552 ; WX 602 ; N SF430000 ; G 3376
+U 9553 ; WX 602 ; N SF240000 ; G 3377
+U 9554 ; WX 602 ; N SF510000 ; G 3378
+U 9555 ; WX 602 ; N SF520000 ; G 3379
+U 9556 ; WX 602 ; N SF390000 ; G 3380
+U 9557 ; WX 602 ; N SF220000 ; G 3381
+U 9558 ; WX 602 ; N SF210000 ; G 3382
+U 9559 ; WX 602 ; N SF250000 ; G 3383
+U 9560 ; WX 602 ; N SF500000 ; G 3384
+U 9561 ; WX 602 ; N SF490000 ; G 3385
+U 9562 ; WX 602 ; N SF380000 ; G 3386
+U 9563 ; WX 602 ; N SF280000 ; G 3387
+U 9564 ; WX 602 ; N SF270000 ; G 3388
+U 9565 ; WX 602 ; N SF260000 ; G 3389
+U 9566 ; WX 602 ; N SF360000 ; G 3390
+U 9567 ; WX 602 ; N SF370000 ; G 3391
+U 9568 ; WX 602 ; N SF420000 ; G 3392
+U 9569 ; WX 602 ; N SF190000 ; G 3393
+U 9570 ; WX 602 ; N SF200000 ; G 3394
+U 9571 ; WX 602 ; N SF230000 ; G 3395
+U 9572 ; WX 602 ; N SF470000 ; G 3396
+U 9573 ; WX 602 ; N SF480000 ; G 3397
+U 9574 ; WX 602 ; N SF410000 ; G 3398
+U 9575 ; WX 602 ; N SF450000 ; G 3399
+U 9576 ; WX 602 ; N SF460000 ; G 3400
+U 9577 ; WX 602 ; N SF400000 ; G 3401
+U 9578 ; WX 602 ; N SF540000 ; G 3402
+U 9579 ; WX 602 ; N SF530000 ; G 3403
+U 9580 ; WX 602 ; N SF440000 ; G 3404
+U 9581 ; WX 602 ; N uni256D ; G 3405
+U 9582 ; WX 602 ; N uni256E ; G 3406
+U 9583 ; WX 602 ; N uni256F ; G 3407
+U 9584 ; WX 602 ; N uni2570 ; G 3408
+U 9585 ; WX 602 ; N uni2571 ; G 3409
+U 9586 ; WX 602 ; N uni2572 ; G 3410
+U 9587 ; WX 602 ; N uni2573 ; G 3411
+U 9588 ; WX 602 ; N uni2574 ; G 3412
+U 9589 ; WX 602 ; N uni2575 ; G 3413
+U 9590 ; WX 602 ; N uni2576 ; G 3414
+U 9591 ; WX 602 ; N uni2577 ; G 3415
+U 9592 ; WX 602 ; N uni2578 ; G 3416
+U 9593 ; WX 602 ; N uni2579 ; G 3417
+U 9594 ; WX 602 ; N uni257A ; G 3418
+U 9595 ; WX 602 ; N uni257B ; G 3419
+U 9596 ; WX 602 ; N uni257C ; G 3420
+U 9597 ; WX 602 ; N uni257D ; G 3421
+U 9598 ; WX 602 ; N uni257E ; G 3422
+U 9599 ; WX 602 ; N uni257F ; G 3423
+U 9600 ; WX 769 ; N upblock ; G 3424
+U 9601 ; WX 769 ; N uni2581 ; G 3425
+U 9602 ; WX 769 ; N uni2582 ; G 3426
+U 9603 ; WX 769 ; N uni2583 ; G 3427
+U 9604 ; WX 769 ; N dnblock ; G 3428
+U 9605 ; WX 769 ; N uni2585 ; G 3429
+U 9606 ; WX 769 ; N uni2586 ; G 3430
+U 9607 ; WX 769 ; N uni2587 ; G 3431
+U 9608 ; WX 769 ; N block ; G 3432
+U 9609 ; WX 769 ; N uni2589 ; G 3433
+U 9610 ; WX 769 ; N uni258A ; G 3434
+U 9611 ; WX 769 ; N uni258B ; G 3435
+U 9612 ; WX 769 ; N lfblock ; G 3436
+U 9613 ; WX 769 ; N uni258D ; G 3437
+U 9614 ; WX 769 ; N uni258E ; G 3438
+U 9615 ; WX 769 ; N uni258F ; G 3439
+U 9616 ; WX 769 ; N rtblock ; G 3440
+U 9617 ; WX 769 ; N ltshade ; G 3441
+U 9618 ; WX 769 ; N shade ; G 3442
+U 9619 ; WX 769 ; N dkshade ; G 3443
+U 9620 ; WX 769 ; N uni2594 ; G 3444
+U 9621 ; WX 769 ; N uni2595 ; G 3445
+U 9622 ; WX 769 ; N uni2596 ; G 3446
+U 9623 ; WX 769 ; N uni2597 ; G 3447
+U 9624 ; WX 769 ; N uni2598 ; G 3448
+U 9625 ; WX 769 ; N uni2599 ; G 3449
+U 9626 ; WX 769 ; N uni259A ; G 3450
+U 9627 ; WX 769 ; N uni259B ; G 3451
+U 9628 ; WX 769 ; N uni259C ; G 3452
+U 9629 ; WX 769 ; N uni259D ; G 3453
+U 9630 ; WX 769 ; N uni259E ; G 3454
+U 9631 ; WX 769 ; N uni259F ; G 3455
+U 9632 ; WX 945 ; N filledbox ; G 3456
+U 9633 ; WX 945 ; N H22073 ; G 3457
+U 9634 ; WX 945 ; N uni25A2 ; G 3458
+U 9635 ; WX 945 ; N uni25A3 ; G 3459
+U 9636 ; WX 945 ; N uni25A4 ; G 3460
+U 9637 ; WX 945 ; N uni25A5 ; G 3461
+U 9638 ; WX 945 ; N uni25A6 ; G 3462
+U 9639 ; WX 945 ; N uni25A7 ; G 3463
+U 9640 ; WX 945 ; N uni25A8 ; G 3464
+U 9641 ; WX 945 ; N uni25A9 ; G 3465
+U 9642 ; WX 678 ; N H18543 ; G 3466
+U 9643 ; WX 678 ; N H18551 ; G 3467
+U 9644 ; WX 945 ; N filledrect ; G 3468
+U 9645 ; WX 945 ; N uni25AD ; G 3469
+U 9646 ; WX 550 ; N uni25AE ; G 3470
+U 9647 ; WX 550 ; N uni25AF ; G 3471
+U 9648 ; WX 769 ; N uni25B0 ; G 3472
+U 9649 ; WX 769 ; N uni25B1 ; G 3473
+U 9650 ; WX 769 ; N triagup ; G 3474
+U 9651 ; WX 769 ; N uni25B3 ; G 3475
+U 9652 ; WX 502 ; N uni25B4 ; G 3476
+U 9653 ; WX 502 ; N uni25B5 ; G 3477
+U 9654 ; WX 769 ; N uni25B6 ; G 3478
+U 9655 ; WX 769 ; N uni25B7 ; G 3479
+U 9656 ; WX 502 ; N uni25B8 ; G 3480
+U 9657 ; WX 502 ; N uni25B9 ; G 3481
+U 9658 ; WX 769 ; N triagrt ; G 3482
+U 9659 ; WX 769 ; N uni25BB ; G 3483
+U 9660 ; WX 769 ; N triagdn ; G 3484
+U 9661 ; WX 769 ; N uni25BD ; G 3485
+U 9662 ; WX 502 ; N uni25BE ; G 3486
+U 9663 ; WX 502 ; N uni25BF ; G 3487
+U 9664 ; WX 769 ; N uni25C0 ; G 3488
+U 9665 ; WX 769 ; N uni25C1 ; G 3489
+U 9666 ; WX 502 ; N uni25C2 ; G 3490
+U 9667 ; WX 502 ; N uni25C3 ; G 3491
+U 9668 ; WX 769 ; N triaglf ; G 3492
+U 9669 ; WX 769 ; N uni25C5 ; G 3493
+U 9670 ; WX 769 ; N uni25C6 ; G 3494
+U 9671 ; WX 769 ; N uni25C7 ; G 3495
+U 9672 ; WX 769 ; N uni25C8 ; G 3496
+U 9673 ; WX 873 ; N uni25C9 ; G 3497
+U 9674 ; WX 494 ; N lozenge ; G 3498
+U 9675 ; WX 873 ; N circle ; G 3499
+U 9676 ; WX 873 ; N uni25CC ; G 3500
+U 9677 ; WX 873 ; N uni25CD ; G 3501
+U 9678 ; WX 873 ; N uni25CE ; G 3502
+U 9679 ; WX 873 ; N H18533 ; G 3503
+U 9680 ; WX 873 ; N uni25D0 ; G 3504
+U 9681 ; WX 873 ; N uni25D1 ; G 3505
+U 9682 ; WX 873 ; N uni25D2 ; G 3506
+U 9683 ; WX 873 ; N uni25D3 ; G 3507
+U 9684 ; WX 873 ; N uni25D4 ; G 3508
+U 9685 ; WX 873 ; N uni25D5 ; G 3509
+U 9686 ; WX 527 ; N uni25D6 ; G 3510
+U 9687 ; WX 527 ; N uni25D7 ; G 3511
+U 9688 ; WX 791 ; N invbullet ; G 3512
+U 9689 ; WX 970 ; N invcircle ; G 3513
+U 9690 ; WX 970 ; N uni25DA ; G 3514
+U 9691 ; WX 970 ; N uni25DB ; G 3515
+U 9692 ; WX 387 ; N uni25DC ; G 3516
+U 9693 ; WX 387 ; N uni25DD ; G 3517
+U 9694 ; WX 387 ; N uni25DE ; G 3518
+U 9695 ; WX 387 ; N uni25DF ; G 3519
+U 9696 ; WX 769 ; N uni25E0 ; G 3520
+U 9697 ; WX 769 ; N uni25E1 ; G 3521
+U 9698 ; WX 769 ; N uni25E2 ; G 3522
+U 9699 ; WX 769 ; N uni25E3 ; G 3523
+U 9700 ; WX 769 ; N uni25E4 ; G 3524
+U 9701 ; WX 769 ; N uni25E5 ; G 3525
+U 9702 ; WX 590 ; N openbullet ; G 3526
+U 9703 ; WX 945 ; N uni25E7 ; G 3527
+U 9704 ; WX 945 ; N uni25E8 ; G 3528
+U 9705 ; WX 945 ; N uni25E9 ; G 3529
+U 9706 ; WX 945 ; N uni25EA ; G 3530
+U 9707 ; WX 945 ; N uni25EB ; G 3531
+U 9708 ; WX 769 ; N uni25EC ; G 3532
+U 9709 ; WX 769 ; N uni25ED ; G 3533
+U 9710 ; WX 769 ; N uni25EE ; G 3534
+U 9711 ; WX 1119 ; N uni25EF ; G 3535
+U 9712 ; WX 945 ; N uni25F0 ; G 3536
+U 9713 ; WX 945 ; N uni25F1 ; G 3537
+U 9714 ; WX 945 ; N uni25F2 ; G 3538
+U 9715 ; WX 945 ; N uni25F3 ; G 3539
+U 9716 ; WX 873 ; N uni25F4 ; G 3540
+U 9717 ; WX 873 ; N uni25F5 ; G 3541
+U 9718 ; WX 873 ; N uni25F6 ; G 3542
+U 9719 ; WX 873 ; N uni25F7 ; G 3543
+U 9720 ; WX 769 ; N uni25F8 ; G 3544
+U 9721 ; WX 769 ; N uni25F9 ; G 3545
+U 9722 ; WX 769 ; N uni25FA ; G 3546
+U 9723 ; WX 830 ; N uni25FB ; G 3547
+U 9724 ; WX 830 ; N uni25FC ; G 3548
+U 9725 ; WX 732 ; N uni25FD ; G 3549
+U 9726 ; WX 732 ; N uni25FE ; G 3550
+U 9727 ; WX 769 ; N uni25FF ; G 3551
+U 9728 ; WX 896 ; N uni2600 ; G 3552
+U 9729 ; WX 1000 ; N uni2601 ; G 3553
+U 9730 ; WX 896 ; N uni2602 ; G 3554
+U 9731 ; WX 896 ; N uni2603 ; G 3555
+U 9732 ; WX 896 ; N uni2604 ; G 3556
+U 9733 ; WX 896 ; N uni2605 ; G 3557
+U 9734 ; WX 896 ; N uni2606 ; G 3558
+U 9735 ; WX 573 ; N uni2607 ; G 3559
+U 9736 ; WX 896 ; N uni2608 ; G 3560
+U 9737 ; WX 896 ; N uni2609 ; G 3561
+U 9738 ; WX 888 ; N uni260A ; G 3562
+U 9739 ; WX 888 ; N uni260B ; G 3563
+U 9740 ; WX 671 ; N uni260C ; G 3564
+U 9741 ; WX 1013 ; N uni260D ; G 3565
+U 9742 ; WX 1246 ; N uni260E ; G 3566
+U 9743 ; WX 1250 ; N uni260F ; G 3567
+U 9744 ; WX 896 ; N uni2610 ; G 3568
+U 9745 ; WX 896 ; N uni2611 ; G 3569
+U 9746 ; WX 896 ; N uni2612 ; G 3570
+U 9747 ; WX 532 ; N uni2613 ; G 3571
+U 9748 ; WX 896 ; N uni2614 ; G 3572
+U 9749 ; WX 896 ; N uni2615 ; G 3573
+U 9750 ; WX 896 ; N uni2616 ; G 3574
+U 9751 ; WX 896 ; N uni2617 ; G 3575
+U 9752 ; WX 896 ; N uni2618 ; G 3576
+U 9753 ; WX 896 ; N uni2619 ; G 3577
+U 9754 ; WX 896 ; N uni261A ; G 3578
+U 9755 ; WX 896 ; N uni261B ; G 3579
+U 9756 ; WX 896 ; N uni261C ; G 3580
+U 9757 ; WX 609 ; N uni261D ; G 3581
+U 9758 ; WX 896 ; N uni261E ; G 3582
+U 9759 ; WX 609 ; N uni261F ; G 3583
+U 9760 ; WX 896 ; N uni2620 ; G 3584
+U 9761 ; WX 896 ; N uni2621 ; G 3585
+U 9762 ; WX 896 ; N uni2622 ; G 3586
+U 9763 ; WX 896 ; N uni2623 ; G 3587
+U 9764 ; WX 669 ; N uni2624 ; G 3588
+U 9765 ; WX 746 ; N uni2625 ; G 3589
+U 9766 ; WX 649 ; N uni2626 ; G 3590
+U 9767 ; WX 784 ; N uni2627 ; G 3591
+U 9768 ; WX 545 ; N uni2628 ; G 3592
+U 9769 ; WX 896 ; N uni2629 ; G 3593
+U 9770 ; WX 896 ; N uni262A ; G 3594
+U 9771 ; WX 896 ; N uni262B ; G 3595
+U 9772 ; WX 710 ; N uni262C ; G 3596
+U 9773 ; WX 896 ; N uni262D ; G 3597
+U 9774 ; WX 896 ; N uni262E ; G 3598
+U 9775 ; WX 896 ; N uni262F ; G 3599
+U 9776 ; WX 890 ; N uni2630 ; G 3600
+U 9777 ; WX 890 ; N uni2631 ; G 3601
+U 9778 ; WX 890 ; N uni2632 ; G 3602
+U 9779 ; WX 890 ; N uni2633 ; G 3603
+U 9780 ; WX 890 ; N uni2634 ; G 3604
+U 9781 ; WX 890 ; N uni2635 ; G 3605
+U 9782 ; WX 890 ; N uni2636 ; G 3606
+U 9783 ; WX 890 ; N uni2637 ; G 3607
+U 9784 ; WX 896 ; N uni2638 ; G 3608
+U 9785 ; WX 1042 ; N uni2639 ; G 3609
+U 9786 ; WX 1042 ; N smileface ; G 3610
+U 9787 ; WX 1042 ; N invsmileface ; G 3611
+U 9788 ; WX 896 ; N sun ; G 3612
+U 9789 ; WX 896 ; N uni263D ; G 3613
+U 9790 ; WX 896 ; N uni263E ; G 3614
+U 9791 ; WX 614 ; N uni263F ; G 3615
+U 9792 ; WX 732 ; N female ; G 3616
+U 9793 ; WX 732 ; N uni2641 ; G 3617
+U 9794 ; WX 896 ; N male ; G 3618
+U 9795 ; WX 896 ; N uni2643 ; G 3619
+U 9796 ; WX 896 ; N uni2644 ; G 3620
+U 9797 ; WX 896 ; N uni2645 ; G 3621
+U 9798 ; WX 896 ; N uni2646 ; G 3622
+U 9799 ; WX 896 ; N uni2647 ; G 3623
+U 9800 ; WX 896 ; N uni2648 ; G 3624
+U 9801 ; WX 896 ; N uni2649 ; G 3625
+U 9802 ; WX 896 ; N uni264A ; G 3626
+U 9803 ; WX 896 ; N uni264B ; G 3627
+U 9804 ; WX 896 ; N uni264C ; G 3628
+U 9805 ; WX 896 ; N uni264D ; G 3629
+U 9806 ; WX 896 ; N uni264E ; G 3630
+U 9807 ; WX 896 ; N uni264F ; G 3631
+U 9808 ; WX 896 ; N uni2650 ; G 3632
+U 9809 ; WX 896 ; N uni2651 ; G 3633
+U 9810 ; WX 896 ; N uni2652 ; G 3634
+U 9811 ; WX 896 ; N uni2653 ; G 3635
+U 9812 ; WX 896 ; N uni2654 ; G 3636
+U 9813 ; WX 896 ; N uni2655 ; G 3637
+U 9814 ; WX 896 ; N uni2656 ; G 3638
+U 9815 ; WX 896 ; N uni2657 ; G 3639
+U 9816 ; WX 896 ; N uni2658 ; G 3640
+U 9817 ; WX 896 ; N uni2659 ; G 3641
+U 9818 ; WX 896 ; N uni265A ; G 3642
+U 9819 ; WX 896 ; N uni265B ; G 3643
+U 9820 ; WX 896 ; N uni265C ; G 3644
+U 9821 ; WX 896 ; N uni265D ; G 3645
+U 9822 ; WX 896 ; N uni265E ; G 3646
+U 9823 ; WX 896 ; N uni265F ; G 3647
+U 9824 ; WX 896 ; N spade ; G 3648
+U 9825 ; WX 896 ; N uni2661 ; G 3649
+U 9826 ; WX 896 ; N uni2662 ; G 3650
+U 9827 ; WX 896 ; N club ; G 3651
+U 9828 ; WX 896 ; N uni2664 ; G 3652
+U 9829 ; WX 896 ; N heart ; G 3653
+U 9830 ; WX 896 ; N diamond ; G 3654
+U 9831 ; WX 896 ; N uni2667 ; G 3655
+U 9832 ; WX 896 ; N uni2668 ; G 3656
+U 9833 ; WX 472 ; N uni2669 ; G 3657
+U 9834 ; WX 638 ; N musicalnote ; G 3658
+U 9835 ; WX 896 ; N musicalnotedbl ; G 3659
+U 9836 ; WX 896 ; N uni266C ; G 3660
+U 9837 ; WX 472 ; N uni266D ; G 3661
+U 9838 ; WX 357 ; N uni266E ; G 3662
+U 9839 ; WX 484 ; N uni266F ; G 3663
+U 9840 ; WX 748 ; N uni2670 ; G 3664
+U 9841 ; WX 766 ; N uni2671 ; G 3665
+U 9842 ; WX 896 ; N uni2672 ; G 3666
+U 9843 ; WX 896 ; N uni2673 ; G 3667
+U 9844 ; WX 896 ; N uni2674 ; G 3668
+U 9845 ; WX 896 ; N uni2675 ; G 3669
+U 9846 ; WX 896 ; N uni2676 ; G 3670
+U 9847 ; WX 896 ; N uni2677 ; G 3671
+U 9848 ; WX 896 ; N uni2678 ; G 3672
+U 9849 ; WX 896 ; N uni2679 ; G 3673
+U 9850 ; WX 896 ; N uni267A ; G 3674
+U 9851 ; WX 896 ; N uni267B ; G 3675
+U 9852 ; WX 896 ; N uni267C ; G 3676
+U 9853 ; WX 896 ; N uni267D ; G 3677
+U 9854 ; WX 896 ; N uni267E ; G 3678
+U 9855 ; WX 896 ; N uni267F ; G 3679
+U 9856 ; WX 869 ; N uni2680 ; G 3680
+U 9857 ; WX 869 ; N uni2681 ; G 3681
+U 9858 ; WX 869 ; N uni2682 ; G 3682
+U 9859 ; WX 869 ; N uni2683 ; G 3683
+U 9860 ; WX 869 ; N uni2684 ; G 3684
+U 9861 ; WX 869 ; N uni2685 ; G 3685
+U 9862 ; WX 890 ; N uni2686 ; G 3686
+U 9863 ; WX 890 ; N uni2687 ; G 3687
+U 9864 ; WX 890 ; N uni2688 ; G 3688
+U 9865 ; WX 890 ; N uni2689 ; G 3689
+U 9866 ; WX 890 ; N uni268A ; G 3690
+U 9867 ; WX 890 ; N uni268B ; G 3691
+U 9868 ; WX 890 ; N uni268C ; G 3692
+U 9869 ; WX 890 ; N uni268D ; G 3693
+U 9870 ; WX 890 ; N uni268E ; G 3694
+U 9871 ; WX 890 ; N uni268F ; G 3695
+U 9872 ; WX 750 ; N uni2690 ; G 3696
+U 9873 ; WX 750 ; N uni2691 ; G 3697
+U 9874 ; WX 890 ; N uni2692 ; G 3698
+U 9875 ; WX 816 ; N uni2693 ; G 3699
+U 9876 ; WX 716 ; N uni2694 ; G 3700
+U 9877 ; WX 537 ; N uni2695 ; G 3701
+U 9878 ; WX 852 ; N uni2696 ; G 3702
+U 9879 ; WX 890 ; N uni2697 ; G 3703
+U 9880 ; WX 684 ; N uni2698 ; G 3704
+U 9881 ; WX 896 ; N uni2699 ; G 3705
+U 9882 ; WX 708 ; N uni269A ; G 3706
+U 9883 ; WX 890 ; N uni269B ; G 3707
+U 9884 ; WX 890 ; N uni269C ; G 3708
+U 9886 ; WX 896 ; N uni269E ; G 3709
+U 9887 ; WX 896 ; N uni269F ; G 3710
+U 9888 ; WX 890 ; N uni26A0 ; G 3711
+U 9889 ; WX 702 ; N uni26A1 ; G 3712
+U 9890 ; WX 1004 ; N uni26A2 ; G 3713
+U 9891 ; WX 1089 ; N uni26A3 ; G 3714
+U 9892 ; WX 1175 ; N uni26A4 ; G 3715
+U 9893 ; WX 903 ; N uni26A5 ; G 3716
+U 9894 ; WX 838 ; N uni26A6 ; G 3717
+U 9895 ; WX 838 ; N uni26A7 ; G 3718
+U 9896 ; WX 838 ; N uni26A8 ; G 3719
+U 9897 ; WX 838 ; N uni26A9 ; G 3720
+U 9898 ; WX 838 ; N uni26AA ; G 3721
+U 9899 ; WX 838 ; N uni26AB ; G 3722
+U 9900 ; WX 838 ; N uni26AC ; G 3723
+U 9901 ; WX 838 ; N uni26AD ; G 3724
+U 9902 ; WX 838 ; N uni26AE ; G 3725
+U 9903 ; WX 838 ; N uni26AF ; G 3726
+U 9904 ; WX 844 ; N uni26B0 ; G 3727
+U 9905 ; WX 838 ; N uni26B1 ; G 3728
+U 9906 ; WX 732 ; N uni26B2 ; G 3729
+U 9907 ; WX 732 ; N uni26B3 ; G 3730
+U 9908 ; WX 732 ; N uni26B4 ; G 3731
+U 9909 ; WX 732 ; N uni26B5 ; G 3732
+U 9910 ; WX 850 ; N uni26B6 ; G 3733
+U 9911 ; WX 732 ; N uni26B7 ; G 3734
+U 9912 ; WX 732 ; N uni26B8 ; G 3735
+U 9920 ; WX 838 ; N uni26C0 ; G 3736
+U 9921 ; WX 838 ; N uni26C1 ; G 3737
+U 9922 ; WX 838 ; N uni26C2 ; G 3738
+U 9923 ; WX 838 ; N uni26C3 ; G 3739
+U 9954 ; WX 732 ; N uni26E2 ; G 3740
+U 9985 ; WX 838 ; N uni2701 ; G 3741
+U 9986 ; WX 838 ; N uni2702 ; G 3742
+U 9987 ; WX 838 ; N uni2703 ; G 3743
+U 9988 ; WX 838 ; N uni2704 ; G 3744
+U 9990 ; WX 838 ; N uni2706 ; G 3745
+U 9991 ; WX 838 ; N uni2707 ; G 3746
+U 9992 ; WX 838 ; N uni2708 ; G 3747
+U 9993 ; WX 838 ; N uni2709 ; G 3748
+U 9996 ; WX 838 ; N uni270C ; G 3749
+U 9997 ; WX 838 ; N uni270D ; G 3750
+U 9998 ; WX 838 ; N uni270E ; G 3751
+U 9999 ; WX 838 ; N uni270F ; G 3752
+U 10000 ; WX 838 ; N uni2710 ; G 3753
+U 10001 ; WX 838 ; N uni2711 ; G 3754
+U 10002 ; WX 838 ; N uni2712 ; G 3755
+U 10003 ; WX 838 ; N uni2713 ; G 3756
+U 10004 ; WX 838 ; N uni2714 ; G 3757
+U 10005 ; WX 838 ; N uni2715 ; G 3758
+U 10006 ; WX 838 ; N uni2716 ; G 3759
+U 10007 ; WX 838 ; N uni2717 ; G 3760
+U 10008 ; WX 838 ; N uni2718 ; G 3761
+U 10009 ; WX 838 ; N uni2719 ; G 3762
+U 10010 ; WX 838 ; N uni271A ; G 3763
+U 10011 ; WX 838 ; N uni271B ; G 3764
+U 10012 ; WX 838 ; N uni271C ; G 3765
+U 10013 ; WX 838 ; N uni271D ; G 3766
+U 10014 ; WX 838 ; N uni271E ; G 3767
+U 10015 ; WX 838 ; N uni271F ; G 3768
+U 10016 ; WX 838 ; N uni2720 ; G 3769
+U 10017 ; WX 838 ; N uni2721 ; G 3770
+U 10018 ; WX 838 ; N uni2722 ; G 3771
+U 10019 ; WX 838 ; N uni2723 ; G 3772
+U 10020 ; WX 838 ; N uni2724 ; G 3773
+U 10021 ; WX 838 ; N uni2725 ; G 3774
+U 10022 ; WX 838 ; N uni2726 ; G 3775
+U 10023 ; WX 838 ; N uni2727 ; G 3776
+U 10025 ; WX 838 ; N uni2729 ; G 3777
+U 10026 ; WX 838 ; N uni272A ; G 3778
+U 10027 ; WX 838 ; N uni272B ; G 3779
+U 10028 ; WX 838 ; N uni272C ; G 3780
+U 10029 ; WX 838 ; N uni272D ; G 3781
+U 10030 ; WX 838 ; N uni272E ; G 3782
+U 10031 ; WX 838 ; N uni272F ; G 3783
+U 10032 ; WX 838 ; N uni2730 ; G 3784
+U 10033 ; WX 838 ; N uni2731 ; G 3785
+U 10034 ; WX 838 ; N uni2732 ; G 3786
+U 10035 ; WX 838 ; N uni2733 ; G 3787
+U 10036 ; WX 838 ; N uni2734 ; G 3788
+U 10037 ; WX 838 ; N uni2735 ; G 3789
+U 10038 ; WX 838 ; N uni2736 ; G 3790
+U 10039 ; WX 838 ; N uni2737 ; G 3791
+U 10040 ; WX 838 ; N uni2738 ; G 3792
+U 10041 ; WX 838 ; N uni2739 ; G 3793
+U 10042 ; WX 838 ; N uni273A ; G 3794
+U 10043 ; WX 838 ; N uni273B ; G 3795
+U 10044 ; WX 838 ; N uni273C ; G 3796
+U 10045 ; WX 838 ; N uni273D ; G 3797
+U 10046 ; WX 838 ; N uni273E ; G 3798
+U 10047 ; WX 838 ; N uni273F ; G 3799
+U 10048 ; WX 838 ; N uni2740 ; G 3800
+U 10049 ; WX 838 ; N uni2741 ; G 3801
+U 10050 ; WX 838 ; N uni2742 ; G 3802
+U 10051 ; WX 838 ; N uni2743 ; G 3803
+U 10052 ; WX 838 ; N uni2744 ; G 3804
+U 10053 ; WX 838 ; N uni2745 ; G 3805
+U 10054 ; WX 838 ; N uni2746 ; G 3806
+U 10055 ; WX 838 ; N uni2747 ; G 3807
+U 10056 ; WX 838 ; N uni2748 ; G 3808
+U 10057 ; WX 838 ; N uni2749 ; G 3809
+U 10058 ; WX 838 ; N uni274A ; G 3810
+U 10059 ; WX 838 ; N uni274B ; G 3811
+U 10061 ; WX 896 ; N uni274D ; G 3812
+U 10063 ; WX 896 ; N uni274F ; G 3813
+U 10064 ; WX 896 ; N uni2750 ; G 3814
+U 10065 ; WX 896 ; N uni2751 ; G 3815
+U 10066 ; WX 896 ; N uni2752 ; G 3816
+U 10070 ; WX 896 ; N uni2756 ; G 3817
+U 10072 ; WX 838 ; N uni2758 ; G 3818
+U 10073 ; WX 838 ; N uni2759 ; G 3819
+U 10074 ; WX 838 ; N uni275A ; G 3820
+U 10075 ; WX 322 ; N uni275B ; G 3821
+U 10076 ; WX 322 ; N uni275C ; G 3822
+U 10077 ; WX 538 ; N uni275D ; G 3823
+U 10078 ; WX 538 ; N uni275E ; G 3824
+U 10081 ; WX 838 ; N uni2761 ; G 3825
+U 10082 ; WX 838 ; N uni2762 ; G 3826
+U 10083 ; WX 838 ; N uni2763 ; G 3827
+U 10084 ; WX 838 ; N uni2764 ; G 3828
+U 10085 ; WX 838 ; N uni2765 ; G 3829
+U 10086 ; WX 838 ; N uni2766 ; G 3830
+U 10087 ; WX 838 ; N uni2767 ; G 3831
+U 10088 ; WX 838 ; N uni2768 ; G 3832
+U 10089 ; WX 838 ; N uni2769 ; G 3833
+U 10090 ; WX 838 ; N uni276A ; G 3834
+U 10091 ; WX 838 ; N uni276B ; G 3835
+U 10092 ; WX 838 ; N uni276C ; G 3836
+U 10093 ; WX 838 ; N uni276D ; G 3837
+U 10094 ; WX 838 ; N uni276E ; G 3838
+U 10095 ; WX 838 ; N uni276F ; G 3839
+U 10096 ; WX 838 ; N uni2770 ; G 3840
+U 10097 ; WX 838 ; N uni2771 ; G 3841
+U 10098 ; WX 838 ; N uni2772 ; G 3842
+U 10099 ; WX 838 ; N uni2773 ; G 3843
+U 10100 ; WX 838 ; N uni2774 ; G 3844
+U 10101 ; WX 838 ; N uni2775 ; G 3845
+U 10102 ; WX 896 ; N uni2776 ; G 3846
+U 10103 ; WX 896 ; N uni2777 ; G 3847
+U 10104 ; WX 896 ; N uni2778 ; G 3848
+U 10105 ; WX 896 ; N uni2779 ; G 3849
+U 10106 ; WX 896 ; N uni277A ; G 3850
+U 10107 ; WX 896 ; N uni277B ; G 3851
+U 10108 ; WX 896 ; N uni277C ; G 3852
+U 10109 ; WX 896 ; N uni277D ; G 3853
+U 10110 ; WX 896 ; N uni277E ; G 3854
+U 10111 ; WX 896 ; N uni277F ; G 3855
+U 10112 ; WX 838 ; N uni2780 ; G 3856
+U 10113 ; WX 838 ; N uni2781 ; G 3857
+U 10114 ; WX 838 ; N uni2782 ; G 3858
+U 10115 ; WX 838 ; N uni2783 ; G 3859
+U 10116 ; WX 838 ; N uni2784 ; G 3860
+U 10117 ; WX 838 ; N uni2785 ; G 3861
+U 10118 ; WX 838 ; N uni2786 ; G 3862
+U 10119 ; WX 838 ; N uni2787 ; G 3863
+U 10120 ; WX 838 ; N uni2788 ; G 3864
+U 10121 ; WX 838 ; N uni2789 ; G 3865
+U 10122 ; WX 838 ; N uni278A ; G 3866
+U 10123 ; WX 838 ; N uni278B ; G 3867
+U 10124 ; WX 838 ; N uni278C ; G 3868
+U 10125 ; WX 838 ; N uni278D ; G 3869
+U 10126 ; WX 838 ; N uni278E ; G 3870
+U 10127 ; WX 838 ; N uni278F ; G 3871
+U 10128 ; WX 838 ; N uni2790 ; G 3872
+U 10129 ; WX 838 ; N uni2791 ; G 3873
+U 10130 ; WX 838 ; N uni2792 ; G 3874
+U 10131 ; WX 838 ; N uni2793 ; G 3875
+U 10132 ; WX 838 ; N uni2794 ; G 3876
+U 10136 ; WX 838 ; N uni2798 ; G 3877
+U 10137 ; WX 838 ; N uni2799 ; G 3878
+U 10138 ; WX 838 ; N uni279A ; G 3879
+U 10139 ; WX 838 ; N uni279B ; G 3880
+U 10140 ; WX 838 ; N uni279C ; G 3881
+U 10141 ; WX 838 ; N uni279D ; G 3882
+U 10142 ; WX 838 ; N uni279E ; G 3883
+U 10143 ; WX 838 ; N uni279F ; G 3884
+U 10144 ; WX 838 ; N uni27A0 ; G 3885
+U 10145 ; WX 838 ; N uni27A1 ; G 3886
+U 10146 ; WX 838 ; N uni27A2 ; G 3887
+U 10147 ; WX 838 ; N uni27A3 ; G 3888
+U 10148 ; WX 838 ; N uni27A4 ; G 3889
+U 10149 ; WX 838 ; N uni27A5 ; G 3890
+U 10150 ; WX 838 ; N uni27A6 ; G 3891
+U 10151 ; WX 838 ; N uni27A7 ; G 3892
+U 10152 ; WX 838 ; N uni27A8 ; G 3893
+U 10153 ; WX 838 ; N uni27A9 ; G 3894
+U 10154 ; WX 838 ; N uni27AA ; G 3895
+U 10155 ; WX 838 ; N uni27AB ; G 3896
+U 10156 ; WX 838 ; N uni27AC ; G 3897
+U 10157 ; WX 838 ; N uni27AD ; G 3898
+U 10158 ; WX 838 ; N uni27AE ; G 3899
+U 10159 ; WX 838 ; N uni27AF ; G 3900
+U 10161 ; WX 838 ; N uni27B1 ; G 3901
+U 10162 ; WX 838 ; N uni27B2 ; G 3902
+U 10163 ; WX 838 ; N uni27B3 ; G 3903
+U 10164 ; WX 838 ; N uni27B4 ; G 3904
+U 10165 ; WX 838 ; N uni27B5 ; G 3905
+U 10166 ; WX 838 ; N uni27B6 ; G 3906
+U 10167 ; WX 838 ; N uni27B7 ; G 3907
+U 10168 ; WX 838 ; N uni27B8 ; G 3908
+U 10169 ; WX 838 ; N uni27B9 ; G 3909
+U 10170 ; WX 838 ; N uni27BA ; G 3910
+U 10171 ; WX 838 ; N uni27BB ; G 3911
+U 10172 ; WX 838 ; N uni27BC ; G 3912
+U 10173 ; WX 838 ; N uni27BD ; G 3913
+U 10174 ; WX 838 ; N uni27BE ; G 3914
+U 10181 ; WX 390 ; N uni27C5 ; G 3915
+U 10182 ; WX 390 ; N uni27C6 ; G 3916
+U 10208 ; WX 494 ; N uni27E0 ; G 3917
+U 10214 ; WX 495 ; N uni27E6 ; G 3918
+U 10215 ; WX 495 ; N uni27E7 ; G 3919
+U 10216 ; WX 390 ; N uni27E8 ; G 3920
+U 10217 ; WX 390 ; N uni27E9 ; G 3921
+U 10218 ; WX 556 ; N uni27EA ; G 3922
+U 10219 ; WX 556 ; N uni27EB ; G 3923
+U 10224 ; WX 838 ; N uni27F0 ; G 3924
+U 10225 ; WX 838 ; N uni27F1 ; G 3925
+U 10226 ; WX 838 ; N uni27F2 ; G 3926
+U 10227 ; WX 838 ; N uni27F3 ; G 3927
+U 10228 ; WX 1157 ; N uni27F4 ; G 3928
+U 10229 ; WX 1434 ; N uni27F5 ; G 3929
+U 10230 ; WX 1434 ; N uni27F6 ; G 3930
+U 10231 ; WX 1434 ; N uni27F7 ; G 3931
+U 10232 ; WX 1434 ; N uni27F8 ; G 3932
+U 10233 ; WX 1434 ; N uni27F9 ; G 3933
+U 10234 ; WX 1434 ; N uni27FA ; G 3934
+U 10235 ; WX 1434 ; N uni27FB ; G 3935
+U 10236 ; WX 1434 ; N uni27FC ; G 3936
+U 10237 ; WX 1434 ; N uni27FD ; G 3937
+U 10238 ; WX 1434 ; N uni27FE ; G 3938
+U 10239 ; WX 1434 ; N uni27FF ; G 3939
+U 10240 ; WX 732 ; N uni2800 ; G 3940
+U 10241 ; WX 732 ; N uni2801 ; G 3941
+U 10242 ; WX 732 ; N uni2802 ; G 3942
+U 10243 ; WX 732 ; N uni2803 ; G 3943
+U 10244 ; WX 732 ; N uni2804 ; G 3944
+U 10245 ; WX 732 ; N uni2805 ; G 3945
+U 10246 ; WX 732 ; N uni2806 ; G 3946
+U 10247 ; WX 732 ; N uni2807 ; G 3947
+U 10248 ; WX 732 ; N uni2808 ; G 3948
+U 10249 ; WX 732 ; N uni2809 ; G 3949
+U 10250 ; WX 732 ; N uni280A ; G 3950
+U 10251 ; WX 732 ; N uni280B ; G 3951
+U 10252 ; WX 732 ; N uni280C ; G 3952
+U 10253 ; WX 732 ; N uni280D ; G 3953
+U 10254 ; WX 732 ; N uni280E ; G 3954
+U 10255 ; WX 732 ; N uni280F ; G 3955
+U 10256 ; WX 732 ; N uni2810 ; G 3956
+U 10257 ; WX 732 ; N uni2811 ; G 3957
+U 10258 ; WX 732 ; N uni2812 ; G 3958
+U 10259 ; WX 732 ; N uni2813 ; G 3959
+U 10260 ; WX 732 ; N uni2814 ; G 3960
+U 10261 ; WX 732 ; N uni2815 ; G 3961
+U 10262 ; WX 732 ; N uni2816 ; G 3962
+U 10263 ; WX 732 ; N uni2817 ; G 3963
+U 10264 ; WX 732 ; N uni2818 ; G 3964
+U 10265 ; WX 732 ; N uni2819 ; G 3965
+U 10266 ; WX 732 ; N uni281A ; G 3966
+U 10267 ; WX 732 ; N uni281B ; G 3967
+U 10268 ; WX 732 ; N uni281C ; G 3968
+U 10269 ; WX 732 ; N uni281D ; G 3969
+U 10270 ; WX 732 ; N uni281E ; G 3970
+U 10271 ; WX 732 ; N uni281F ; G 3971
+U 10272 ; WX 732 ; N uni2820 ; G 3972
+U 10273 ; WX 732 ; N uni2821 ; G 3973
+U 10274 ; WX 732 ; N uni2822 ; G 3974
+U 10275 ; WX 732 ; N uni2823 ; G 3975
+U 10276 ; WX 732 ; N uni2824 ; G 3976
+U 10277 ; WX 732 ; N uni2825 ; G 3977
+U 10278 ; WX 732 ; N uni2826 ; G 3978
+U 10279 ; WX 732 ; N uni2827 ; G 3979
+U 10280 ; WX 732 ; N uni2828 ; G 3980
+U 10281 ; WX 732 ; N uni2829 ; G 3981
+U 10282 ; WX 732 ; N uni282A ; G 3982
+U 10283 ; WX 732 ; N uni282B ; G 3983
+U 10284 ; WX 732 ; N uni282C ; G 3984
+U 10285 ; WX 732 ; N uni282D ; G 3985
+U 10286 ; WX 732 ; N uni282E ; G 3986
+U 10287 ; WX 732 ; N uni282F ; G 3987
+U 10288 ; WX 732 ; N uni2830 ; G 3988
+U 10289 ; WX 732 ; N uni2831 ; G 3989
+U 10290 ; WX 732 ; N uni2832 ; G 3990
+U 10291 ; WX 732 ; N uni2833 ; G 3991
+U 10292 ; WX 732 ; N uni2834 ; G 3992
+U 10293 ; WX 732 ; N uni2835 ; G 3993
+U 10294 ; WX 732 ; N uni2836 ; G 3994
+U 10295 ; WX 732 ; N uni2837 ; G 3995
+U 10296 ; WX 732 ; N uni2838 ; G 3996
+U 10297 ; WX 732 ; N uni2839 ; G 3997
+U 10298 ; WX 732 ; N uni283A ; G 3998
+U 10299 ; WX 732 ; N uni283B ; G 3999
+U 10300 ; WX 732 ; N uni283C ; G 4000
+U 10301 ; WX 732 ; N uni283D ; G 4001
+U 10302 ; WX 732 ; N uni283E ; G 4002
+U 10303 ; WX 732 ; N uni283F ; G 4003
+U 10304 ; WX 732 ; N uni2840 ; G 4004
+U 10305 ; WX 732 ; N uni2841 ; G 4005
+U 10306 ; WX 732 ; N uni2842 ; G 4006
+U 10307 ; WX 732 ; N uni2843 ; G 4007
+U 10308 ; WX 732 ; N uni2844 ; G 4008
+U 10309 ; WX 732 ; N uni2845 ; G 4009
+U 10310 ; WX 732 ; N uni2846 ; G 4010
+U 10311 ; WX 732 ; N uni2847 ; G 4011
+U 10312 ; WX 732 ; N uni2848 ; G 4012
+U 10313 ; WX 732 ; N uni2849 ; G 4013
+U 10314 ; WX 732 ; N uni284A ; G 4014
+U 10315 ; WX 732 ; N uni284B ; G 4015
+U 10316 ; WX 732 ; N uni284C ; G 4016
+U 10317 ; WX 732 ; N uni284D ; G 4017
+U 10318 ; WX 732 ; N uni284E ; G 4018
+U 10319 ; WX 732 ; N uni284F ; G 4019
+U 10320 ; WX 732 ; N uni2850 ; G 4020
+U 10321 ; WX 732 ; N uni2851 ; G 4021
+U 10322 ; WX 732 ; N uni2852 ; G 4022
+U 10323 ; WX 732 ; N uni2853 ; G 4023
+U 10324 ; WX 732 ; N uni2854 ; G 4024
+U 10325 ; WX 732 ; N uni2855 ; G 4025
+U 10326 ; WX 732 ; N uni2856 ; G 4026
+U 10327 ; WX 732 ; N uni2857 ; G 4027
+U 10328 ; WX 732 ; N uni2858 ; G 4028
+U 10329 ; WX 732 ; N uni2859 ; G 4029
+U 10330 ; WX 732 ; N uni285A ; G 4030
+U 10331 ; WX 732 ; N uni285B ; G 4031
+U 10332 ; WX 732 ; N uni285C ; G 4032
+U 10333 ; WX 732 ; N uni285D ; G 4033
+U 10334 ; WX 732 ; N uni285E ; G 4034
+U 10335 ; WX 732 ; N uni285F ; G 4035
+U 10336 ; WX 732 ; N uni2860 ; G 4036
+U 10337 ; WX 732 ; N uni2861 ; G 4037
+U 10338 ; WX 732 ; N uni2862 ; G 4038
+U 10339 ; WX 732 ; N uni2863 ; G 4039
+U 10340 ; WX 732 ; N uni2864 ; G 4040
+U 10341 ; WX 732 ; N uni2865 ; G 4041
+U 10342 ; WX 732 ; N uni2866 ; G 4042
+U 10343 ; WX 732 ; N uni2867 ; G 4043
+U 10344 ; WX 732 ; N uni2868 ; G 4044
+U 10345 ; WX 732 ; N uni2869 ; G 4045
+U 10346 ; WX 732 ; N uni286A ; G 4046
+U 10347 ; WX 732 ; N uni286B ; G 4047
+U 10348 ; WX 732 ; N uni286C ; G 4048
+U 10349 ; WX 732 ; N uni286D ; G 4049
+U 10350 ; WX 732 ; N uni286E ; G 4050
+U 10351 ; WX 732 ; N uni286F ; G 4051
+U 10352 ; WX 732 ; N uni2870 ; G 4052
+U 10353 ; WX 732 ; N uni2871 ; G 4053
+U 10354 ; WX 732 ; N uni2872 ; G 4054
+U 10355 ; WX 732 ; N uni2873 ; G 4055
+U 10356 ; WX 732 ; N uni2874 ; G 4056
+U 10357 ; WX 732 ; N uni2875 ; G 4057
+U 10358 ; WX 732 ; N uni2876 ; G 4058
+U 10359 ; WX 732 ; N uni2877 ; G 4059
+U 10360 ; WX 732 ; N uni2878 ; G 4060
+U 10361 ; WX 732 ; N uni2879 ; G 4061
+U 10362 ; WX 732 ; N uni287A ; G 4062
+U 10363 ; WX 732 ; N uni287B ; G 4063
+U 10364 ; WX 732 ; N uni287C ; G 4064
+U 10365 ; WX 732 ; N uni287D ; G 4065
+U 10366 ; WX 732 ; N uni287E ; G 4066
+U 10367 ; WX 732 ; N uni287F ; G 4067
+U 10368 ; WX 732 ; N uni2880 ; G 4068
+U 10369 ; WX 732 ; N uni2881 ; G 4069
+U 10370 ; WX 732 ; N uni2882 ; G 4070
+U 10371 ; WX 732 ; N uni2883 ; G 4071
+U 10372 ; WX 732 ; N uni2884 ; G 4072
+U 10373 ; WX 732 ; N uni2885 ; G 4073
+U 10374 ; WX 732 ; N uni2886 ; G 4074
+U 10375 ; WX 732 ; N uni2887 ; G 4075
+U 10376 ; WX 732 ; N uni2888 ; G 4076
+U 10377 ; WX 732 ; N uni2889 ; G 4077
+U 10378 ; WX 732 ; N uni288A ; G 4078
+U 10379 ; WX 732 ; N uni288B ; G 4079
+U 10380 ; WX 732 ; N uni288C ; G 4080
+U 10381 ; WX 732 ; N uni288D ; G 4081
+U 10382 ; WX 732 ; N uni288E ; G 4082
+U 10383 ; WX 732 ; N uni288F ; G 4083
+U 10384 ; WX 732 ; N uni2890 ; G 4084
+U 10385 ; WX 732 ; N uni2891 ; G 4085
+U 10386 ; WX 732 ; N uni2892 ; G 4086
+U 10387 ; WX 732 ; N uni2893 ; G 4087
+U 10388 ; WX 732 ; N uni2894 ; G 4088
+U 10389 ; WX 732 ; N uni2895 ; G 4089
+U 10390 ; WX 732 ; N uni2896 ; G 4090
+U 10391 ; WX 732 ; N uni2897 ; G 4091
+U 10392 ; WX 732 ; N uni2898 ; G 4092
+U 10393 ; WX 732 ; N uni2899 ; G 4093
+U 10394 ; WX 732 ; N uni289A ; G 4094
+U 10395 ; WX 732 ; N uni289B ; G 4095
+U 10396 ; WX 732 ; N uni289C ; G 4096
+U 10397 ; WX 732 ; N uni289D ; G 4097
+U 10398 ; WX 732 ; N uni289E ; G 4098
+U 10399 ; WX 732 ; N uni289F ; G 4099
+U 10400 ; WX 732 ; N uni28A0 ; G 4100
+U 10401 ; WX 732 ; N uni28A1 ; G 4101
+U 10402 ; WX 732 ; N uni28A2 ; G 4102
+U 10403 ; WX 732 ; N uni28A3 ; G 4103
+U 10404 ; WX 732 ; N uni28A4 ; G 4104
+U 10405 ; WX 732 ; N uni28A5 ; G 4105
+U 10406 ; WX 732 ; N uni28A6 ; G 4106
+U 10407 ; WX 732 ; N uni28A7 ; G 4107
+U 10408 ; WX 732 ; N uni28A8 ; G 4108
+U 10409 ; WX 732 ; N uni28A9 ; G 4109
+U 10410 ; WX 732 ; N uni28AA ; G 4110
+U 10411 ; WX 732 ; N uni28AB ; G 4111
+U 10412 ; WX 732 ; N uni28AC ; G 4112
+U 10413 ; WX 732 ; N uni28AD ; G 4113
+U 10414 ; WX 732 ; N uni28AE ; G 4114
+U 10415 ; WX 732 ; N uni28AF ; G 4115
+U 10416 ; WX 732 ; N uni28B0 ; G 4116
+U 10417 ; WX 732 ; N uni28B1 ; G 4117
+U 10418 ; WX 732 ; N uni28B2 ; G 4118
+U 10419 ; WX 732 ; N uni28B3 ; G 4119
+U 10420 ; WX 732 ; N uni28B4 ; G 4120
+U 10421 ; WX 732 ; N uni28B5 ; G 4121
+U 10422 ; WX 732 ; N uni28B6 ; G 4122
+U 10423 ; WX 732 ; N uni28B7 ; G 4123
+U 10424 ; WX 732 ; N uni28B8 ; G 4124
+U 10425 ; WX 732 ; N uni28B9 ; G 4125
+U 10426 ; WX 732 ; N uni28BA ; G 4126
+U 10427 ; WX 732 ; N uni28BB ; G 4127
+U 10428 ; WX 732 ; N uni28BC ; G 4128
+U 10429 ; WX 732 ; N uni28BD ; G 4129
+U 10430 ; WX 732 ; N uni28BE ; G 4130
+U 10431 ; WX 732 ; N uni28BF ; G 4131
+U 10432 ; WX 732 ; N uni28C0 ; G 4132
+U 10433 ; WX 732 ; N uni28C1 ; G 4133
+U 10434 ; WX 732 ; N uni28C2 ; G 4134
+U 10435 ; WX 732 ; N uni28C3 ; G 4135
+U 10436 ; WX 732 ; N uni28C4 ; G 4136
+U 10437 ; WX 732 ; N uni28C5 ; G 4137
+U 10438 ; WX 732 ; N uni28C6 ; G 4138
+U 10439 ; WX 732 ; N uni28C7 ; G 4139
+U 10440 ; WX 732 ; N uni28C8 ; G 4140
+U 10441 ; WX 732 ; N uni28C9 ; G 4141
+U 10442 ; WX 732 ; N uni28CA ; G 4142
+U 10443 ; WX 732 ; N uni28CB ; G 4143
+U 10444 ; WX 732 ; N uni28CC ; G 4144
+U 10445 ; WX 732 ; N uni28CD ; G 4145
+U 10446 ; WX 732 ; N uni28CE ; G 4146
+U 10447 ; WX 732 ; N uni28CF ; G 4147
+U 10448 ; WX 732 ; N uni28D0 ; G 4148
+U 10449 ; WX 732 ; N uni28D1 ; G 4149
+U 10450 ; WX 732 ; N uni28D2 ; G 4150
+U 10451 ; WX 732 ; N uni28D3 ; G 4151
+U 10452 ; WX 732 ; N uni28D4 ; G 4152
+U 10453 ; WX 732 ; N uni28D5 ; G 4153
+U 10454 ; WX 732 ; N uni28D6 ; G 4154
+U 10455 ; WX 732 ; N uni28D7 ; G 4155
+U 10456 ; WX 732 ; N uni28D8 ; G 4156
+U 10457 ; WX 732 ; N uni28D9 ; G 4157
+U 10458 ; WX 732 ; N uni28DA ; G 4158
+U 10459 ; WX 732 ; N uni28DB ; G 4159
+U 10460 ; WX 732 ; N uni28DC ; G 4160
+U 10461 ; WX 732 ; N uni28DD ; G 4161
+U 10462 ; WX 732 ; N uni28DE ; G 4162
+U 10463 ; WX 732 ; N uni28DF ; G 4163
+U 10464 ; WX 732 ; N uni28E0 ; G 4164
+U 10465 ; WX 732 ; N uni28E1 ; G 4165
+U 10466 ; WX 732 ; N uni28E2 ; G 4166
+U 10467 ; WX 732 ; N uni28E3 ; G 4167
+U 10468 ; WX 732 ; N uni28E4 ; G 4168
+U 10469 ; WX 732 ; N uni28E5 ; G 4169
+U 10470 ; WX 732 ; N uni28E6 ; G 4170
+U 10471 ; WX 732 ; N uni28E7 ; G 4171
+U 10472 ; WX 732 ; N uni28E8 ; G 4172
+U 10473 ; WX 732 ; N uni28E9 ; G 4173
+U 10474 ; WX 732 ; N uni28EA ; G 4174
+U 10475 ; WX 732 ; N uni28EB ; G 4175
+U 10476 ; WX 732 ; N uni28EC ; G 4176
+U 10477 ; WX 732 ; N uni28ED ; G 4177
+U 10478 ; WX 732 ; N uni28EE ; G 4178
+U 10479 ; WX 732 ; N uni28EF ; G 4179
+U 10480 ; WX 732 ; N uni28F0 ; G 4180
+U 10481 ; WX 732 ; N uni28F1 ; G 4181
+U 10482 ; WX 732 ; N uni28F2 ; G 4182
+U 10483 ; WX 732 ; N uni28F3 ; G 4183
+U 10484 ; WX 732 ; N uni28F4 ; G 4184
+U 10485 ; WX 732 ; N uni28F5 ; G 4185
+U 10486 ; WX 732 ; N uni28F6 ; G 4186
+U 10487 ; WX 732 ; N uni28F7 ; G 4187
+U 10488 ; WX 732 ; N uni28F8 ; G 4188
+U 10489 ; WX 732 ; N uni28F9 ; G 4189
+U 10490 ; WX 732 ; N uni28FA ; G 4190
+U 10491 ; WX 732 ; N uni28FB ; G 4191
+U 10492 ; WX 732 ; N uni28FC ; G 4192
+U 10493 ; WX 732 ; N uni28FD ; G 4193
+U 10494 ; WX 732 ; N uni28FE ; G 4194
+U 10495 ; WX 732 ; N uni28FF ; G 4195
+U 10502 ; WX 838 ; N uni2906 ; G 4196
+U 10503 ; WX 838 ; N uni2907 ; G 4197
+U 10506 ; WX 838 ; N uni290A ; G 4198
+U 10507 ; WX 838 ; N uni290B ; G 4199
+U 10560 ; WX 683 ; N uni2940 ; G 4200
+U 10561 ; WX 683 ; N uni2941 ; G 4201
+U 10627 ; WX 734 ; N uni2983 ; G 4202
+U 10628 ; WX 734 ; N uni2984 ; G 4203
+U 10702 ; WX 838 ; N uni29CE ; G 4204
+U 10703 ; WX 1000 ; N uni29CF ; G 4205
+U 10704 ; WX 1000 ; N uni29D0 ; G 4206
+U 10705 ; WX 1000 ; N uni29D1 ; G 4207
+U 10706 ; WX 1000 ; N uni29D2 ; G 4208
+U 10707 ; WX 1000 ; N uni29D3 ; G 4209
+U 10708 ; WX 1000 ; N uni29D4 ; G 4210
+U 10709 ; WX 1000 ; N uni29D5 ; G 4211
+U 10731 ; WX 494 ; N uni29EB ; G 4212
+U 10746 ; WX 838 ; N uni29FA ; G 4213
+U 10747 ; WX 838 ; N uni29FB ; G 4214
+U 10752 ; WX 1000 ; N uni2A00 ; G 4215
+U 10753 ; WX 1000 ; N uni2A01 ; G 4216
+U 10754 ; WX 1000 ; N uni2A02 ; G 4217
+U 10764 ; WX 1325 ; N uni2A0C ; G 4218
+U 10765 ; WX 521 ; N uni2A0D ; G 4219
+U 10766 ; WX 521 ; N uni2A0E ; G 4220
+U 10767 ; WX 521 ; N uni2A0F ; G 4221
+U 10768 ; WX 521 ; N uni2A10 ; G 4222
+U 10769 ; WX 521 ; N uni2A11 ; G 4223
+U 10770 ; WX 521 ; N uni2A12 ; G 4224
+U 10771 ; WX 521 ; N uni2A13 ; G 4225
+U 10772 ; WX 521 ; N uni2A14 ; G 4226
+U 10773 ; WX 521 ; N uni2A15 ; G 4227
+U 10774 ; WX 521 ; N uni2A16 ; G 4228
+U 10775 ; WX 521 ; N uni2A17 ; G 4229
+U 10776 ; WX 521 ; N uni2A18 ; G 4230
+U 10777 ; WX 521 ; N uni2A19 ; G 4231
+U 10778 ; WX 521 ; N uni2A1A ; G 4232
+U 10779 ; WX 521 ; N uni2A1B ; G 4233
+U 10780 ; WX 521 ; N uni2A1C ; G 4234
+U 10799 ; WX 838 ; N uni2A2F ; G 4235
+U 10858 ; WX 838 ; N uni2A6A ; G 4236
+U 10859 ; WX 838 ; N uni2A6B ; G 4237
+U 10877 ; WX 838 ; N uni2A7D ; G 4238
+U 10878 ; WX 838 ; N uni2A7E ; G 4239
+U 10879 ; WX 838 ; N uni2A7F ; G 4240
+U 10880 ; WX 838 ; N uni2A80 ; G 4241
+U 10881 ; WX 838 ; N uni2A81 ; G 4242
+U 10882 ; WX 838 ; N uni2A82 ; G 4243
+U 10883 ; WX 838 ; N uni2A83 ; G 4244
+U 10884 ; WX 838 ; N uni2A84 ; G 4245
+U 10885 ; WX 838 ; N uni2A85 ; G 4246
+U 10886 ; WX 838 ; N uni2A86 ; G 4247
+U 10887 ; WX 838 ; N uni2A87 ; G 4248
+U 10888 ; WX 838 ; N uni2A88 ; G 4249
+U 10889 ; WX 838 ; N uni2A89 ; G 4250
+U 10890 ; WX 838 ; N uni2A8A ; G 4251
+U 10891 ; WX 838 ; N uni2A8B ; G 4252
+U 10892 ; WX 838 ; N uni2A8C ; G 4253
+U 10893 ; WX 838 ; N uni2A8D ; G 4254
+U 10894 ; WX 838 ; N uni2A8E ; G 4255
+U 10895 ; WX 838 ; N uni2A8F ; G 4256
+U 10896 ; WX 838 ; N uni2A90 ; G 4257
+U 10897 ; WX 838 ; N uni2A91 ; G 4258
+U 10898 ; WX 838 ; N uni2A92 ; G 4259
+U 10899 ; WX 838 ; N uni2A93 ; G 4260
+U 10900 ; WX 838 ; N uni2A94 ; G 4261
+U 10901 ; WX 838 ; N uni2A95 ; G 4262
+U 10902 ; WX 838 ; N uni2A96 ; G 4263
+U 10903 ; WX 838 ; N uni2A97 ; G 4264
+U 10904 ; WX 838 ; N uni2A98 ; G 4265
+U 10905 ; WX 838 ; N uni2A99 ; G 4266
+U 10906 ; WX 838 ; N uni2A9A ; G 4267
+U 10907 ; WX 838 ; N uni2A9B ; G 4268
+U 10908 ; WX 838 ; N uni2A9C ; G 4269
+U 10909 ; WX 838 ; N uni2A9D ; G 4270
+U 10910 ; WX 838 ; N uni2A9E ; G 4271
+U 10911 ; WX 838 ; N uni2A9F ; G 4272
+U 10912 ; WX 838 ; N uni2AA0 ; G 4273
+U 10926 ; WX 838 ; N uni2AAE ; G 4274
+U 10927 ; WX 838 ; N uni2AAF ; G 4275
+U 10928 ; WX 838 ; N uni2AB0 ; G 4276
+U 10929 ; WX 838 ; N uni2AB1 ; G 4277
+U 10930 ; WX 838 ; N uni2AB2 ; G 4278
+U 10931 ; WX 838 ; N uni2AB3 ; G 4279
+U 10932 ; WX 838 ; N uni2AB4 ; G 4280
+U 10933 ; WX 838 ; N uni2AB5 ; G 4281
+U 10934 ; WX 838 ; N uni2AB6 ; G 4282
+U 10935 ; WX 838 ; N uni2AB7 ; G 4283
+U 10936 ; WX 838 ; N uni2AB8 ; G 4284
+U 10937 ; WX 838 ; N uni2AB9 ; G 4285
+U 10938 ; WX 838 ; N uni2ABA ; G 4286
+U 11001 ; WX 838 ; N uni2AF9 ; G 4287
+U 11002 ; WX 838 ; N uni2AFA ; G 4288
+U 11008 ; WX 838 ; N uni2B00 ; G 4289
+U 11009 ; WX 838 ; N uni2B01 ; G 4290
+U 11010 ; WX 838 ; N uni2B02 ; G 4291
+U 11011 ; WX 838 ; N uni2B03 ; G 4292
+U 11012 ; WX 838 ; N uni2B04 ; G 4293
+U 11013 ; WX 838 ; N uni2B05 ; G 4294
+U 11014 ; WX 838 ; N uni2B06 ; G 4295
+U 11015 ; WX 838 ; N uni2B07 ; G 4296
+U 11016 ; WX 838 ; N uni2B08 ; G 4297
+U 11017 ; WX 838 ; N uni2B09 ; G 4298
+U 11018 ; WX 838 ; N uni2B0A ; G 4299
+U 11019 ; WX 838 ; N uni2B0B ; G 4300
+U 11020 ; WX 838 ; N uni2B0C ; G 4301
+U 11021 ; WX 838 ; N uni2B0D ; G 4302
+U 11022 ; WX 836 ; N uni2B0E ; G 4303
+U 11023 ; WX 836 ; N uni2B0F ; G 4304
+U 11024 ; WX 836 ; N uni2B10 ; G 4305
+U 11025 ; WX 836 ; N uni2B11 ; G 4306
+U 11026 ; WX 945 ; N uni2B12 ; G 4307
+U 11027 ; WX 945 ; N uni2B13 ; G 4308
+U 11028 ; WX 945 ; N uni2B14 ; G 4309
+U 11029 ; WX 945 ; N uni2B15 ; G 4310
+U 11030 ; WX 769 ; N uni2B16 ; G 4311
+U 11031 ; WX 769 ; N uni2B17 ; G 4312
+U 11032 ; WX 769 ; N uni2B18 ; G 4313
+U 11033 ; WX 769 ; N uni2B19 ; G 4314
+U 11034 ; WX 945 ; N uni2B1A ; G 4315
+U 11039 ; WX 869 ; N uni2B1F ; G 4316
+U 11040 ; WX 869 ; N uni2B20 ; G 4317
+U 11041 ; WX 873 ; N uni2B21 ; G 4318
+U 11042 ; WX 873 ; N uni2B22 ; G 4319
+U 11043 ; WX 873 ; N uni2B23 ; G 4320
+U 11044 ; WX 1119 ; N uni2B24 ; G 4321
+U 11091 ; WX 869 ; N uni2B53 ; G 4322
+U 11092 ; WX 869 ; N uni2B54 ; G 4323
+U 11360 ; WX 557 ; N uni2C60 ; G 4324
+U 11361 ; WX 278 ; N uni2C61 ; G 4325
+U 11362 ; WX 557 ; N uni2C62 ; G 4326
+U 11363 ; WX 603 ; N uni2C63 ; G 4327
+U 11364 ; WX 695 ; N uni2C64 ; G 4328
+U 11365 ; WX 613 ; N uni2C65 ; G 4329
+U 11366 ; WX 392 ; N uni2C66 ; G 4330
+U 11367 ; WX 752 ; N uni2C67 ; G 4331
+U 11368 ; WX 634 ; N uni2C68 ; G 4332
+U 11369 ; WX 656 ; N uni2C69 ; G 4333
+U 11370 ; WX 579 ; N uni2C6A ; G 4334
+U 11371 ; WX 685 ; N uni2C6B ; G 4335
+U 11372 ; WX 525 ; N uni2C6C ; G 4336
+U 11373 ; WX 781 ; N uni2C6D ; G 4337
+U 11374 ; WX 863 ; N uni2C6E ; G 4338
+U 11375 ; WX 684 ; N uni2C6F ; G 4339
+U 11376 ; WX 781 ; N uni2C70 ; G 4340
+U 11377 ; WX 734 ; N uni2C71 ; G 4341
+U 11378 ; WX 1128 ; N uni2C72 ; G 4342
+U 11379 ; WX 961 ; N uni2C73 ; G 4343
+U 11380 ; WX 592 ; N uni2C74 ; G 4344
+U 11381 ; WX 654 ; N uni2C75 ; G 4345
+U 11382 ; WX 568 ; N uni2C76 ; G 4346
+U 11383 ; WX 660 ; N uni2C77 ; G 4347
+U 11385 ; WX 414 ; N uni2C79 ; G 4348
+U 11386 ; WX 612 ; N uni2C7A ; G 4349
+U 11387 ; WX 491 ; N uni2C7B ; G 4350
+U 11388 ; WX 175 ; N uni2C7C ; G 4351
+U 11389 ; WX 431 ; N uni2C7D ; G 4352
+U 11390 ; WX 635 ; N uni2C7E ; G 4353
+U 11391 ; WX 685 ; N uni2C7F ; G 4354
+U 11520 ; WX 591 ; N uni2D00 ; G 4355
+U 11521 ; WX 595 ; N uni2D01 ; G 4356
+U 11522 ; WX 564 ; N uni2D02 ; G 4357
+U 11523 ; WX 602 ; N uni2D03 ; G 4358
+U 11524 ; WX 587 ; N uni2D04 ; G 4359
+U 11525 ; WX 911 ; N uni2D05 ; G 4360
+U 11526 ; WX 626 ; N uni2D06 ; G 4361
+U 11527 ; WX 952 ; N uni2D07 ; G 4362
+U 11528 ; WX 595 ; N uni2D08 ; G 4363
+U 11529 ; WX 607 ; N uni2D09 ; G 4364
+U 11530 ; WX 954 ; N uni2D0A ; G 4365
+U 11531 ; WX 620 ; N uni2D0B ; G 4366
+U 11532 ; WX 595 ; N uni2D0C ; G 4367
+U 11533 ; WX 926 ; N uni2D0D ; G 4368
+U 11534 ; WX 595 ; N uni2D0E ; G 4369
+U 11535 ; WX 806 ; N uni2D0F ; G 4370
+U 11536 ; WX 931 ; N uni2D10 ; G 4371
+U 11537 ; WX 584 ; N uni2D11 ; G 4372
+U 11538 ; WX 592 ; N uni2D12 ; G 4373
+U 11539 ; WX 923 ; N uni2D13 ; G 4374
+U 11540 ; WX 953 ; N uni2D14 ; G 4375
+U 11541 ; WX 828 ; N uni2D15 ; G 4376
+U 11542 ; WX 596 ; N uni2D16 ; G 4377
+U 11543 ; WX 595 ; N uni2D17 ; G 4378
+U 11544 ; WX 590 ; N uni2D18 ; G 4379
+U 11545 ; WX 592 ; N uni2D19 ; G 4380
+U 11546 ; WX 592 ; N uni2D1A ; G 4381
+U 11547 ; WX 621 ; N uni2D1B ; G 4382
+U 11548 ; WX 920 ; N uni2D1C ; G 4383
+U 11549 ; WX 589 ; N uni2D1D ; G 4384
+U 11550 ; WX 586 ; N uni2D1E ; G 4385
+U 11551 ; WX 581 ; N uni2D1F ; G 4386
+U 11552 ; WX 914 ; N uni2D20 ; G 4387
+U 11553 ; WX 596 ; N uni2D21 ; G 4388
+U 11554 ; WX 595 ; N uni2D22 ; G 4389
+U 11555 ; WX 592 ; N uni2D23 ; G 4390
+U 11556 ; WX 642 ; N uni2D24 ; G 4391
+U 11557 ; WX 901 ; N uni2D25 ; G 4392
+U 11800 ; WX 531 ; N uni2E18 ; G 4393
+U 11807 ; WX 838 ; N uni2E1F ; G 4394
+U 11810 ; WX 390 ; N uni2E22 ; G 4395
+U 11811 ; WX 390 ; N uni2E23 ; G 4396
+U 11812 ; WX 390 ; N uni2E24 ; G 4397
+U 11813 ; WX 390 ; N uni2E25 ; G 4398
+U 11822 ; WX 531 ; N uni2E2E ; G 4399
+U 19904 ; WX 896 ; N uni4DC0 ; G 4400
+U 19905 ; WX 896 ; N uni4DC1 ; G 4401
+U 19906 ; WX 896 ; N uni4DC2 ; G 4402
+U 19907 ; WX 896 ; N uni4DC3 ; G 4403
+U 19908 ; WX 896 ; N uni4DC4 ; G 4404
+U 19909 ; WX 896 ; N uni4DC5 ; G 4405
+U 19910 ; WX 896 ; N uni4DC6 ; G 4406
+U 19911 ; WX 896 ; N uni4DC7 ; G 4407
+U 19912 ; WX 896 ; N uni4DC8 ; G 4408
+U 19913 ; WX 896 ; N uni4DC9 ; G 4409
+U 19914 ; WX 896 ; N uni4DCA ; G 4410
+U 19915 ; WX 896 ; N uni4DCB ; G 4411
+U 19916 ; WX 896 ; N uni4DCC ; G 4412
+U 19917 ; WX 896 ; N uni4DCD ; G 4413
+U 19918 ; WX 896 ; N uni4DCE ; G 4414
+U 19919 ; WX 896 ; N uni4DCF ; G 4415
+U 19920 ; WX 896 ; N uni4DD0 ; G 4416
+U 19921 ; WX 896 ; N uni4DD1 ; G 4417
+U 19922 ; WX 896 ; N uni4DD2 ; G 4418
+U 19923 ; WX 896 ; N uni4DD3 ; G 4419
+U 19924 ; WX 896 ; N uni4DD4 ; G 4420
+U 19925 ; WX 896 ; N uni4DD5 ; G 4421
+U 19926 ; WX 896 ; N uni4DD6 ; G 4422
+U 19927 ; WX 896 ; N uni4DD7 ; G 4423
+U 19928 ; WX 896 ; N uni4DD8 ; G 4424
+U 19929 ; WX 896 ; N uni4DD9 ; G 4425
+U 19930 ; WX 896 ; N uni4DDA ; G 4426
+U 19931 ; WX 896 ; N uni4DDB ; G 4427
+U 19932 ; WX 896 ; N uni4DDC ; G 4428
+U 19933 ; WX 896 ; N uni4DDD ; G 4429
+U 19934 ; WX 896 ; N uni4DDE ; G 4430
+U 19935 ; WX 896 ; N uni4DDF ; G 4431
+U 19936 ; WX 896 ; N uni4DE0 ; G 4432
+U 19937 ; WX 896 ; N uni4DE1 ; G 4433
+U 19938 ; WX 896 ; N uni4DE2 ; G 4434
+U 19939 ; WX 896 ; N uni4DE3 ; G 4435
+U 19940 ; WX 896 ; N uni4DE4 ; G 4436
+U 19941 ; WX 896 ; N uni4DE5 ; G 4437
+U 19942 ; WX 896 ; N uni4DE6 ; G 4438
+U 19943 ; WX 896 ; N uni4DE7 ; G 4439
+U 19944 ; WX 896 ; N uni4DE8 ; G 4440
+U 19945 ; WX 896 ; N uni4DE9 ; G 4441
+U 19946 ; WX 896 ; N uni4DEA ; G 4442
+U 19947 ; WX 896 ; N uni4DEB ; G 4443
+U 19948 ; WX 896 ; N uni4DEC ; G 4444
+U 19949 ; WX 896 ; N uni4DED ; G 4445
+U 19950 ; WX 896 ; N uni4DEE ; G 4446
+U 19951 ; WX 896 ; N uni4DEF ; G 4447
+U 19952 ; WX 896 ; N uni4DF0 ; G 4448
+U 19953 ; WX 896 ; N uni4DF1 ; G 4449
+U 19954 ; WX 896 ; N uni4DF2 ; G 4450
+U 19955 ; WX 896 ; N uni4DF3 ; G 4451
+U 19956 ; WX 896 ; N uni4DF4 ; G 4452
+U 19957 ; WX 896 ; N uni4DF5 ; G 4453
+U 19958 ; WX 896 ; N uni4DF6 ; G 4454
+U 19959 ; WX 896 ; N uni4DF7 ; G 4455
+U 19960 ; WX 896 ; N uni4DF8 ; G 4456
+U 19961 ; WX 896 ; N uni4DF9 ; G 4457
+U 19962 ; WX 896 ; N uni4DFA ; G 4458
+U 19963 ; WX 896 ; N uni4DFB ; G 4459
+U 19964 ; WX 896 ; N uni4DFC ; G 4460
+U 19965 ; WX 896 ; N uni4DFD ; G 4461
+U 19966 ; WX 896 ; N uni4DFE ; G 4462
+U 19967 ; WX 896 ; N uni4DFF ; G 4463
+U 42192 ; WX 686 ; N uniA4D0 ; G 4464
+U 42193 ; WX 603 ; N uniA4D1 ; G 4465
+U 42194 ; WX 603 ; N uniA4D2 ; G 4466
+U 42195 ; WX 770 ; N uniA4D3 ; G 4467
+U 42196 ; WX 611 ; N uniA4D4 ; G 4468
+U 42197 ; WX 611 ; N uniA4D5 ; G 4469
+U 42198 ; WX 775 ; N uniA4D6 ; G 4470
+U 42199 ; WX 656 ; N uniA4D7 ; G 4471
+U 42200 ; WX 656 ; N uniA4D8 ; G 4472
+U 42201 ; WX 512 ; N uniA4D9 ; G 4473
+U 42202 ; WX 698 ; N uniA4DA ; G 4474
+U 42203 ; WX 703 ; N uniA4DB ; G 4475
+U 42204 ; WX 685 ; N uniA4DC ; G 4476
+U 42205 ; WX 575 ; N uniA4DD ; G 4477
+U 42206 ; WX 575 ; N uniA4DE ; G 4478
+U 42207 ; WX 863 ; N uniA4DF ; G 4479
+U 42208 ; WX 748 ; N uniA4E0 ; G 4480
+U 42209 ; WX 557 ; N uniA4E1 ; G 4481
+U 42210 ; WX 635 ; N uniA4E2 ; G 4482
+U 42211 ; WX 695 ; N uniA4E3 ; G 4483
+U 42212 ; WX 695 ; N uniA4E4 ; G 4484
+U 42213 ; WX 684 ; N uniA4E5 ; G 4485
+U 42214 ; WX 684 ; N uniA4E6 ; G 4486
+U 42215 ; WX 752 ; N uniA4E7 ; G 4487
+U 42216 ; WX 775 ; N uniA4E8 ; G 4488
+U 42217 ; WX 512 ; N uniA4E9 ; G 4489
+U 42218 ; WX 989 ; N uniA4EA ; G 4490
+U 42219 ; WX 685 ; N uniA4EB ; G 4491
+U 42220 ; WX 611 ; N uniA4EC ; G 4492
+U 42221 ; WX 686 ; N uniA4ED ; G 4493
+U 42222 ; WX 684 ; N uniA4EE ; G 4494
+U 42223 ; WX 684 ; N uniA4EF ; G 4495
+U 42224 ; WX 632 ; N uniA4F0 ; G 4496
+U 42225 ; WX 632 ; N uniA4F1 ; G 4497
+U 42226 ; WX 295 ; N uniA4F2 ; G 4498
+U 42227 ; WX 787 ; N uniA4F3 ; G 4499
+U 42228 ; WX 732 ; N uniA4F4 ; G 4500
+U 42229 ; WX 732 ; N uniA4F5 ; G 4501
+U 42230 ; WX 557 ; N uniA4F6 ; G 4502
+U 42231 ; WX 767 ; N uniA4F7 ; G 4503
+U 42232 ; WX 300 ; N uniA4F8 ; G 4504
+U 42233 ; WX 300 ; N uniA4F9 ; G 4505
+U 42234 ; WX 596 ; N uniA4FA ; G 4506
+U 42235 ; WX 596 ; N uniA4FB ; G 4507
+U 42236 ; WX 300 ; N uniA4FC ; G 4508
+U 42237 ; WX 300 ; N uniA4FD ; G 4509
+U 42238 ; WX 588 ; N uniA4FE ; G 4510
+U 42239 ; WX 588 ; N uniA4FF ; G 4511
+U 42564 ; WX 635 ; N uniA644 ; G 4512
+U 42565 ; WX 521 ; N uniA645 ; G 4513
+U 42566 ; WX 354 ; N uniA646 ; G 4514
+U 42567 ; WX 338 ; N uniA647 ; G 4515
+U 42572 ; WX 1180 ; N uniA64C ; G 4516
+U 42573 ; WX 1028 ; N uniA64D ; G 4517
+U 42576 ; WX 1029 ; N uniA650 ; G 4518
+U 42577 ; WX 906 ; N uniA651 ; G 4519
+U 42580 ; WX 1080 ; N uniA654 ; G 4520
+U 42581 ; WX 842 ; N uniA655 ; G 4521
+U 42582 ; WX 985 ; N uniA656 ; G 4522
+U 42583 ; WX 847 ; N uniA657 ; G 4523
+U 42594 ; WX 1024 ; N uniA662 ; G 4524
+U 42595 ; WX 925 ; N uniA663 ; G 4525
+U 42596 ; WX 1014 ; N uniA664 ; G 4526
+U 42597 ; WX 900 ; N uniA665 ; G 4527
+U 42598 ; WX 863 ; N uniA666 ; G 4528
+U 42599 ; WX 1008 ; N uniA667 ; G 4529
+U 42600 ; WX 787 ; N uniA668 ; G 4530
+U 42601 ; WX 612 ; N uniA669 ; G 4531
+U 42602 ; WX 855 ; N uniA66A ; G 4532
+U 42603 ; WX 712 ; N uniA66B ; G 4533
+U 42604 ; WX 1358 ; N uniA66C ; G 4534
+U 42605 ; WX 1019 ; N uniA66D ; G 4535
+U 42606 ; WX 879 ; N uniA66E ; G 4536
+U 42634 ; WX 805 ; N uniA68A ; G 4537
+U 42635 ; WX 722 ; N uniA68B ; G 4538
+U 42636 ; WX 611 ; N uniA68C ; G 4539
+U 42637 ; WX 583 ; N uniA68D ; G 4540
+U 42644 ; WX 686 ; N uniA694 ; G 4541
+U 42645 ; WX 634 ; N uniA695 ; G 4542
+U 42648 ; WX 1358 ; N uniA698 ; G 4543
+U 42649 ; WX 1019 ; N uniA699 ; G 4544
+U 42760 ; WX 493 ; N uniA708 ; G 4545
+U 42761 ; WX 493 ; N uniA709 ; G 4546
+U 42762 ; WX 493 ; N uniA70A ; G 4547
+U 42763 ; WX 493 ; N uniA70B ; G 4548
+U 42764 ; WX 493 ; N uniA70C ; G 4549
+U 42765 ; WX 493 ; N uniA70D ; G 4550
+U 42766 ; WX 493 ; N uniA70E ; G 4551
+U 42767 ; WX 493 ; N uniA70F ; G 4552
+U 42768 ; WX 493 ; N uniA710 ; G 4553
+U 42769 ; WX 493 ; N uniA711 ; G 4554
+U 42770 ; WX 493 ; N uniA712 ; G 4555
+U 42771 ; WX 493 ; N uniA713 ; G 4556
+U 42772 ; WX 493 ; N uniA714 ; G 4557
+U 42773 ; WX 493 ; N uniA715 ; G 4558
+U 42774 ; WX 493 ; N uniA716 ; G 4559
+U 42779 ; WX 369 ; N uniA71B ; G 4560
+U 42780 ; WX 369 ; N uniA71C ; G 4561
+U 42781 ; WX 252 ; N uniA71D ; G 4562
+U 42782 ; WX 252 ; N uniA71E ; G 4563
+U 42783 ; WX 252 ; N uniA71F ; G 4564
+U 42786 ; WX 385 ; N uniA722 ; G 4565
+U 42787 ; WX 356 ; N uniA723 ; G 4566
+U 42788 ; WX 472 ; N uniA724 ; G 4567
+U 42789 ; WX 472 ; N uniA725 ; G 4568
+U 42790 ; WX 752 ; N uniA726 ; G 4569
+U 42791 ; WX 634 ; N uniA727 ; G 4570
+U 42792 ; WX 878 ; N uniA728 ; G 4571
+U 42793 ; WX 709 ; N uniA729 ; G 4572
+U 42794 ; WX 614 ; N uniA72A ; G 4573
+U 42795 ; WX 541 ; N uniA72B ; G 4574
+U 42800 ; WX 491 ; N uniA730 ; G 4575
+U 42801 ; WX 521 ; N uniA731 ; G 4576
+U 42802 ; WX 1250 ; N uniA732 ; G 4577
+U 42803 ; WX 985 ; N uniA733 ; G 4578
+U 42804 ; WX 1219 ; N uniA734 ; G 4579
+U 42805 ; WX 1000 ; N uniA735 ; G 4580
+U 42806 ; WX 1155 ; N uniA736 ; G 4581
+U 42807 ; WX 996 ; N uniA737 ; G 4582
+U 42808 ; WX 971 ; N uniA738 ; G 4583
+U 42809 ; WX 818 ; N uniA739 ; G 4584
+U 42810 ; WX 971 ; N uniA73A ; G 4585
+U 42811 ; WX 818 ; N uniA73B ; G 4586
+U 42812 ; WX 959 ; N uniA73C ; G 4587
+U 42813 ; WX 818 ; N uniA73D ; G 4588
+U 42814 ; WX 698 ; N uniA73E ; G 4589
+U 42815 ; WX 549 ; N uniA73F ; G 4590
+U 42816 ; WX 656 ; N uniA740 ; G 4591
+U 42817 ; WX 579 ; N uniA741 ; G 4592
+U 42822 ; WX 680 ; N uniA746 ; G 4593
+U 42823 ; WX 392 ; N uniA747 ; G 4594
+U 42824 ; WX 582 ; N uniA748 ; G 4595
+U 42825 ; WX 427 ; N uniA749 ; G 4596
+U 42826 ; WX 807 ; N uniA74A ; G 4597
+U 42827 ; WX 704 ; N uniA74B ; G 4598
+U 42830 ; WX 1358 ; N uniA74E ; G 4599
+U 42831 ; WX 1019 ; N uniA74F ; G 4600
+U 42832 ; WX 603 ; N uniA750 ; G 4601
+U 42833 ; WX 635 ; N uniA751 ; G 4602
+U 42834 ; WX 734 ; N uniA752 ; G 4603
+U 42835 ; WX 774 ; N uniA753 ; G 4604
+U 42838 ; WX 787 ; N uniA756 ; G 4605
+U 42839 ; WX 635 ; N uniA757 ; G 4606
+U 42852 ; WX 605 ; N uniA764 ; G 4607
+U 42853 ; WX 635 ; N uniA765 ; G 4608
+U 42854 ; WX 605 ; N uniA766 ; G 4609
+U 42855 ; WX 635 ; N uniA767 ; G 4610
+U 42880 ; WX 557 ; N uniA780 ; G 4611
+U 42881 ; WX 278 ; N uniA781 ; G 4612
+U 42882 ; WX 735 ; N uniA782 ; G 4613
+U 42883 ; WX 634 ; N uniA783 ; G 4614
+U 42889 ; WX 337 ; N uniA789 ; G 4615
+U 42890 ; WX 376 ; N uniA78A ; G 4616
+U 42891 ; WX 401 ; N uniA78B ; G 4617
+U 42892 ; WX 275 ; N uniA78C ; G 4618
+U 42893 ; WX 686 ; N uniA78D ; G 4619
+U 42894 ; WX 487 ; N uniA78E ; G 4620
+U 42896 ; WX 772 ; N uniA790 ; G 4621
+U 42897 ; WX 667 ; N uniA791 ; G 4622
+U 42912 ; WX 775 ; N uniA7A0 ; G 4623
+U 42913 ; WX 635 ; N uniA7A1 ; G 4624
+U 42914 ; WX 656 ; N uniA7A2 ; G 4625
+U 42915 ; WX 579 ; N uniA7A3 ; G 4626
+U 42916 ; WX 748 ; N uniA7A4 ; G 4627
+U 42917 ; WX 634 ; N uniA7A5 ; G 4628
+U 42918 ; WX 695 ; N uniA7A6 ; G 4629
+U 42919 ; WX 411 ; N uniA7A7 ; G 4630
+U 42920 ; WX 635 ; N uniA7A8 ; G 4631
+U 42921 ; WX 521 ; N uniA7A9 ; G 4632
+U 42922 ; WX 872 ; N uniA7AA ; G 4633
+U 43000 ; WX 577 ; N uniA7F8 ; G 4634
+U 43001 ; WX 644 ; N uniA7F9 ; G 4635
+U 43002 ; WX 915 ; N uniA7FA ; G 4636
+U 43003 ; WX 575 ; N uniA7FB ; G 4637
+U 43004 ; WX 603 ; N uniA7FC ; G 4638
+U 43005 ; WX 863 ; N uniA7FD ; G 4639
+U 43006 ; WX 295 ; N uniA7FE ; G 4640
+U 43007 ; WX 1199 ; N uniA7FF ; G 4641
+U 61184 ; WX 213 ; N uni02E5.5 ; G 4642
+U 61185 ; WX 238 ; N uni02E6.5 ; G 4643
+U 61186 ; WX 257 ; N uni02E7.5 ; G 4644
+U 61187 ; WX 264 ; N uni02E8.5 ; G 4645
+U 61188 ; WX 267 ; N uni02E9.5 ; G 4646
+U 61189 ; WX 238 ; N uni02E5.4 ; G 4647
+U 61190 ; WX 213 ; N uni02E6.4 ; G 4648
+U 61191 ; WX 238 ; N uni02E7.4 ; G 4649
+U 61192 ; WX 257 ; N uni02E8.4 ; G 4650
+U 61193 ; WX 264 ; N uni02E9.4 ; G 4651
+U 61194 ; WX 257 ; N uni02E5.3 ; G 4652
+U 61195 ; WX 238 ; N uni02E6.3 ; G 4653
+U 61196 ; WX 213 ; N uni02E7.3 ; G 4654
+U 61197 ; WX 238 ; N uni02E8.3 ; G 4655
+U 61198 ; WX 257 ; N uni02E9.3 ; G 4656
+U 61199 ; WX 264 ; N uni02E5.2 ; G 4657
+U 61200 ; WX 257 ; N uni02E6.2 ; G 4658
+U 61201 ; WX 238 ; N uni02E7.2 ; G 4659
+U 61202 ; WX 213 ; N uni02E8.2 ; G 4660
+U 61203 ; WX 238 ; N uni02E9.2 ; G 4661
+U 61204 ; WX 267 ; N uni02E5.1 ; G 4662
+U 61205 ; WX 264 ; N uni02E6.1 ; G 4663
+U 61206 ; WX 257 ; N uni02E7.1 ; G 4664
+U 61207 ; WX 238 ; N uni02E8.1 ; G 4665
+U 61208 ; WX 213 ; N uni02E9.1 ; G 4666
+U 61209 ; WX 275 ; N stem ; G 4667
+U 62464 ; WX 580 ; N uniF400 ; G 4668
+U 62465 ; WX 580 ; N uniF401 ; G 4669
+U 62466 ; WX 624 ; N uniF402 ; G 4670
+U 62467 ; WX 889 ; N uniF403 ; G 4671
+U 62468 ; WX 585 ; N uniF404 ; G 4672
+U 62469 ; WX 580 ; N uniF405 ; G 4673
+U 62470 ; WX 653 ; N uniF406 ; G 4674
+U 62471 ; WX 882 ; N uniF407 ; G 4675
+U 62472 ; WX 555 ; N uniF408 ; G 4676
+U 62473 ; WX 580 ; N uniF409 ; G 4677
+U 62474 ; WX 1168 ; N uniF40A ; G 4678
+U 62475 ; WX 589 ; N uniF40B ; G 4679
+U 62476 ; WX 590 ; N uniF40C ; G 4680
+U 62477 ; WX 869 ; N uniF40D ; G 4681
+U 62478 ; WX 580 ; N uniF40E ; G 4682
+U 62479 ; WX 589 ; N uniF40F ; G 4683
+U 62480 ; WX 914 ; N uniF410 ; G 4684
+U 62481 ; WX 590 ; N uniF411 ; G 4685
+U 62482 ; WX 731 ; N uniF412 ; G 4686
+U 62483 ; WX 583 ; N uniF413 ; G 4687
+U 62484 ; WX 872 ; N uniF414 ; G 4688
+U 62485 ; WX 589 ; N uniF415 ; G 4689
+U 62486 ; WX 895 ; N uniF416 ; G 4690
+U 62487 ; WX 589 ; N uniF417 ; G 4691
+U 62488 ; WX 589 ; N uniF418 ; G 4692
+U 62489 ; WX 590 ; N uniF419 ; G 4693
+U 62490 ; WX 649 ; N uniF41A ; G 4694
+U 62491 ; WX 589 ; N uniF41B ; G 4695
+U 62492 ; WX 589 ; N uniF41C ; G 4696
+U 62493 ; WX 599 ; N uniF41D ; G 4697
+U 62494 ; WX 590 ; N uniF41E ; G 4698
+U 62495 ; WX 516 ; N uniF41F ; G 4699
+U 62496 ; WX 580 ; N uniF420 ; G 4700
+U 62497 ; WX 584 ; N uniF421 ; G 4701
+U 62498 ; WX 580 ; N uniF422 ; G 4702
+U 62499 ; WX 580 ; N uniF423 ; G 4703
+U 62500 ; WX 581 ; N uniF424 ; G 4704
+U 62501 ; WX 638 ; N uniF425 ; G 4705
+U 62502 ; WX 955 ; N uniF426 ; G 4706
+U 62504 ; WX 931 ; N uniF428 ; G 4707
+U 62505 ; WX 808 ; N uniF429 ; G 4708
+U 62506 ; WX 508 ; N uniF42A ; G 4709
+U 62507 ; WX 508 ; N uniF42B ; G 4710
+U 62508 ; WX 508 ; N uniF42C ; G 4711
+U 62509 ; WX 508 ; N uniF42D ; G 4712
+U 62510 ; WX 508 ; N uniF42E ; G 4713
+U 62511 ; WX 508 ; N uniF42F ; G 4714
+U 62512 ; WX 508 ; N uniF430 ; G 4715
+U 62513 ; WX 508 ; N uniF431 ; G 4716
+U 62514 ; WX 508 ; N uniF432 ; G 4717
+U 62515 ; WX 508 ; N uniF433 ; G 4718
+U 62516 ; WX 518 ; N uniF434 ; G 4719
+U 62517 ; WX 518 ; N uniF435 ; G 4720
+U 62518 ; WX 518 ; N uniF436 ; G 4721
+U 62519 ; WX 787 ; N uniF437 ; G 4722
+U 62520 ; WX 787 ; N uniF438 ; G 4723
+U 62521 ; WX 787 ; N uniF439 ; G 4724
+U 62522 ; WX 787 ; N uniF43A ; G 4725
+U 62523 ; WX 787 ; N uniF43B ; G 4726
+U 62524 ; WX 546 ; N uniF43C ; G 4727
+U 62525 ; WX 546 ; N uniF43D ; G 4728
+U 62526 ; WX 546 ; N uniF43E ; G 4729
+U 62527 ; WX 546 ; N uniF43F ; G 4730
+U 62528 ; WX 546 ; N uniF440 ; G 4731
+U 62529 ; WX 546 ; N uniF441 ; G 4732
+U 63173 ; WX 612 ; N uniF6C5 ; G 4733
+U 64256 ; WX 722 ; N uniFB00 ; G 4734
+U 64257 ; WX 646 ; N fi ; G 4735
+U 64258 ; WX 646 ; N fl ; G 4736
+U 64259 ; WX 1000 ; N uniFB03 ; G 4737
+U 64260 ; WX 1000 ; N uniFB04 ; G 4738
+U 64261 ; WX 686 ; N uniFB05 ; G 4739
+U 64262 ; WX 861 ; N uniFB06 ; G 4740
+U 64275 ; WX 1202 ; N uniFB13 ; G 4741
+U 64276 ; WX 1202 ; N uniFB14 ; G 4742
+U 64277 ; WX 1196 ; N uniFB15 ; G 4743
+U 64278 ; WX 1186 ; N uniFB16 ; G 4744
+U 64279 ; WX 1529 ; N uniFB17 ; G 4745
+U 64285 ; WX 224 ; N uniFB1D ; G 4746
+U 64286 ; WX 0 ; N uniFB1E ; G 4747
+U 64287 ; WX 471 ; N uniFB1F ; G 4748
+U 64288 ; WX 636 ; N uniFB20 ; G 4749
+U 64289 ; WX 856 ; N uniFB21 ; G 4750
+U 64290 ; WX 774 ; N uniFB22 ; G 4751
+U 64291 ; WX 906 ; N uniFB23 ; G 4752
+U 64292 ; WX 771 ; N uniFB24 ; G 4753
+U 64293 ; WX 843 ; N uniFB25 ; G 4754
+U 64294 ; WX 855 ; N uniFB26 ; G 4755
+U 64295 ; WX 807 ; N uniFB27 ; G 4756
+U 64296 ; WX 875 ; N uniFB28 ; G 4757
+U 64297 ; WX 838 ; N uniFB29 ; G 4758
+U 64298 ; WX 708 ; N uniFB2A ; G 4759
+U 64299 ; WX 708 ; N uniFB2B ; G 4760
+U 64300 ; WX 708 ; N uniFB2C ; G 4761
+U 64301 ; WX 708 ; N uniFB2D ; G 4762
+U 64302 ; WX 668 ; N uniFB2E ; G 4763
+U 64303 ; WX 668 ; N uniFB2F ; G 4764
+U 64304 ; WX 668 ; N uniFB30 ; G 4765
+U 64305 ; WX 578 ; N uniFB31 ; G 4766
+U 64306 ; WX 412 ; N uniFB32 ; G 4767
+U 64307 ; WX 546 ; N uniFB33 ; G 4768
+U 64308 ; WX 653 ; N uniFB34 ; G 4769
+U 64309 ; WX 272 ; N uniFB35 ; G 4770
+U 64310 ; WX 346 ; N uniFB36 ; G 4771
+U 64311 ; WX 1000 ; N uniFB37 ; G 4772
+U 64312 ; WX 648 ; N uniFB38 ; G 4773
+U 64313 ; WX 307 ; N uniFB39 ; G 4774
+U 64314 ; WX 537 ; N uniFB3A ; G 4775
+U 64315 ; WX 529 ; N uniFB3B ; G 4776
+U 64316 ; WX 568 ; N uniFB3C ; G 4777
+U 64317 ; WX 1000 ; N uniFB3D ; G 4778
+U 64318 ; WX 679 ; N uniFB3E ; G 4779
+U 64319 ; WX 1000 ; N uniFB3F ; G 4780
+U 64320 ; WX 400 ; N uniFB40 ; G 4781
+U 64321 ; WX 649 ; N uniFB41 ; G 4782
+U 64322 ; WX 1000 ; N uniFB42 ; G 4783
+U 64323 ; WX 640 ; N uniFB43 ; G 4784
+U 64324 ; WX 625 ; N uniFB44 ; G 4785
+U 64325 ; WX 1000 ; N uniFB45 ; G 4786
+U 64326 ; WX 593 ; N uniFB46 ; G 4787
+U 64327 ; WX 709 ; N uniFB47 ; G 4788
+U 64328 ; WX 564 ; N uniFB48 ; G 4789
+U 64329 ; WX 708 ; N uniFB49 ; G 4790
+U 64330 ; WX 657 ; N uniFB4A ; G 4791
+U 64331 ; WX 272 ; N uniFB4B ; G 4792
+U 64332 ; WX 578 ; N uniFB4C ; G 4793
+U 64333 ; WX 529 ; N uniFB4D ; G 4794
+U 64334 ; WX 625 ; N uniFB4E ; G 4795
+U 64335 ; WX 629 ; N uniFB4F ; G 4796
+U 65024 ; WX 0 ; N uniFE00 ; G 4797
+U 65025 ; WX 0 ; N uniFE01 ; G 4798
+U 65026 ; WX 0 ; N uniFE02 ; G 4799
+U 65027 ; WX 0 ; N uniFE03 ; G 4800
+U 65028 ; WX 0 ; N uniFE04 ; G 4801
+U 65029 ; WX 0 ; N uniFE05 ; G 4802
+U 65030 ; WX 0 ; N uniFE06 ; G 4803
+U 65031 ; WX 0 ; N uniFE07 ; G 4804
+U 65032 ; WX 0 ; N uniFE08 ; G 4805
+U 65033 ; WX 0 ; N uniFE09 ; G 4806
+U 65034 ; WX 0 ; N uniFE0A ; G 4807
+U 65035 ; WX 0 ; N uniFE0B ; G 4808
+U 65036 ; WX 0 ; N uniFE0C ; G 4809
+U 65037 ; WX 0 ; N uniFE0D ; G 4810
+U 65038 ; WX 0 ; N uniFE0E ; G 4811
+U 65039 ; WX 0 ; N uniFE0F ; G 4812
+U 65056 ; WX 0 ; N uniFE20 ; G 4813
+U 65057 ; WX 0 ; N uniFE21 ; G 4814
+U 65058 ; WX 0 ; N uniFE22 ; G 4815
+U 65059 ; WX 0 ; N uniFE23 ; G 4816
+U 65529 ; WX 0 ; N uniFFF9 ; G 4817
+U 65530 ; WX 0 ; N uniFFFA ; G 4818
+U 65531 ; WX 0 ; N uniFFFB ; G 4819
+U 65532 ; WX 0 ; N uniFFFC ; G 4820
+U 65533 ; WX 1025 ; N uniFFFD ; G 4821
+EndCharMetrics
+StartKernData
+StartKernPairs 1029
+
+KPX dollar seven -149
+KPX dollar nine -102
+KPX dollar colon -36
+KPX dollar Hcircumflex -149
+KPX dollar Hbar -149
+KPX dollar Kcommaaccent -36
+KPX dollar uni01DC -149
+
+KPX percent less -36
+KPX percent kgreenlandic -36
+KPX percent lacute -36
+KPX percent uni01F4 -36
+
+KPX ampersand six 38
+KPX ampersand Gcircumflex 38
+KPX ampersand Gbreve 38
+KPX ampersand Gdotaccent 38
+KPX ampersand Gcommaaccent 38
+KPX ampersand uni01DA 38
+
+KPX parenright dollar -120
+KPX parenright X -83
+KPX parenright guillemotright -83
+KPX parenright onequarter -83
+KPX parenright onehalf -83
+KPX parenright threequarters -83
+KPX parenright Acircumflex -120
+KPX parenright Adieresis -120
+KPX parenright AE -120
+KPX parenright imacron -83
+KPX parenright ibreve -83
+KPX parenright iogonek -83
+KPX parenright dotlessi -83
+KPX parenright ij -83
+KPX parenright jcircumflex -83
+
+KPX period ampersand -55
+KPX period two -55
+KPX period eight -36
+KPX period D -73
+KPX period H -73
+KPX period R -73
+KPX period X -55
+KPX period backslash -55
+KPX period cent -73
+KPX period sterling -73
+KPX period currency -73
+KPX period yen -73
+KPX period brokenbar -73
+KPX period section -73
+KPX period dieresis -36
+KPX period ordfeminine -73
+KPX period guillemotleft -73
+KPX period logicalnot -73
+KPX period sfthyphen -73
+KPX period acute -73
+KPX period mu -73
+KPX period paragraph -73
+KPX period periodcentered -73
+KPX period cedilla -73
+KPX period ordmasculine -92
+KPX period guillemotright -55
+KPX period onequarter -55
+KPX period onehalf -55
+KPX period threequarters -55
+KPX period questiondown -55
+KPX period Aacute -55
+KPX period Egrave -55
+KPX period Icircumflex -55
+KPX period Yacute -73
+KPX period Ebreve -55
+KPX period ebreve -92
+KPX period Idot -36
+KPX period dotlessi -55
+
+KPX slash two -63
+KPX slash seven -139
+KPX slash nine -149
+KPX slash colon -83
+KPX slash less -196
+KPX slash backslash -73
+KPX slash questiondown -73
+KPX slash Aacute -73
+KPX slash Ebreve -63
+KPX slash Hbar -139
+KPX slash lacute -196
+
+KPX two semicolon -55
+
+KPX three dollar -102
+
+
+KPX six six -73
+KPX six Gdotaccent -73
+KPX six Gcommaaccent -73
+
+KPX seven dollar -188
+KPX seven D -215
+KPX seven F -253
+KPX seven H -253
+KPX seven R -253
+KPX seven U -159
+KPX seven V -243
+KPX seven X -206
+KPX seven Z -167
+KPX seven backslash -178
+KPX seven cent -215
+KPX seven sterling -215
+KPX seven currency -215
+KPX seven yen -215
+KPX seven brokenbar -215
+KPX seven section -215
+KPX seven dieresis -253
+KPX seven copyright -253
+KPX seven ordfeminine -253
+KPX seven guillemotleft -253
+KPX seven logicalnot -253
+KPX seven sfthyphen -253
+KPX seven acute -253
+KPX seven mu -253
+KPX seven paragraph -253
+KPX seven periodcentered -253
+KPX seven cedilla -253
+KPX seven ordmasculine -253
+KPX seven guillemotright -206
+KPX seven onequarter -206
+KPX seven onehalf -206
+KPX seven threequarters -206
+KPX seven questiondown -178
+KPX seven Aacute -178
+KPX seven Eacute -253
+KPX seven Idieresis -253
+KPX seven Yacute -253
+KPX seven ebreve -253
+KPX seven edotaccent -159
+KPX seven ecaron -159
+KPX seven gdotaccent -243
+KPX seven gcommaaccent -243
+KPX seven dotlessi -206
+
+KPX nine dollar -139
+KPX nine D -131
+KPX nine H -120
+KPX nine R -120
+KPX nine X -36
+KPX nine cent -131
+KPX nine sterling -131
+KPX nine currency -131
+KPX nine yen -131
+KPX nine brokenbar -131
+KPX nine section -131
+KPX nine dieresis -149
+KPX nine ordfeminine -120
+KPX nine guillemotleft -120
+KPX nine logicalnot -120
+KPX nine sfthyphen -120
+KPX nine acute -120
+KPX nine mu -120
+KPX nine paragraph -120
+KPX nine periodcentered -120
+KPX nine cedilla -120
+KPX nine ordmasculine -120
+KPX nine guillemotright -36
+KPX nine onequarter -36
+KPX nine onehalf -36
+KPX nine threequarters -36
+KPX nine Yacute -120
+KPX nine ebreve -120
+KPX nine dotlessi -36
+
+KPX colon dollar -102
+KPX colon D -112
+KPX colon U -36
+KPX colon cent -112
+KPX colon sterling -112
+KPX colon currency -112
+KPX colon yen -112
+KPX colon brokenbar -112
+KPX colon section -112
+KPX colon dieresis -112
+KPX colon edotaccent -36
+KPX colon ecaron -36
+
+KPX semicolon ampersand -36
+KPX semicolon two -73
+KPX semicolon Egrave -36
+KPX semicolon Icircumflex -36
+KPX semicolon Ebreve -55
+
+KPX less dollar -159
+KPX less ampersand -36
+KPX less two -36
+KPX less D -188
+KPX less H -225
+KPX less L -36
+KPX less R -225
+KPX less X -188
+KPX less cent -188
+KPX less sterling -188
+KPX less currency -188
+KPX less yen -188
+KPX less brokenbar -188
+KPX less section -188
+KPX less dieresis -188
+KPX less ordfeminine -225
+KPX less guillemotleft -225
+KPX less logicalnot -225
+KPX less sfthyphen -225
+KPX less acute -225
+KPX less mu -225
+KPX less paragraph -225
+KPX less periodcentered -225
+KPX less cedilla -225
+KPX less ordmasculine -225
+KPX less guillemotright -188
+KPX less onequarter -188
+KPX less onehalf -188
+KPX less threequarters -188
+KPX less Egrave -36
+KPX less Icircumflex -36
+KPX less Yacute -225
+KPX less Ebreve -36
+KPX less ebreve -225
+KPX less dotlessi -188
+
+
+
+
+
+
+
+
+
+
+KPX Acircumflex seven -149
+KPX Acircumflex nine -102
+KPX Acircumflex colon -36
+KPX Acircumflex Hcircumflex -149
+KPX Acircumflex Hbar -149
+KPX Acircumflex Kcommaaccent -36
+KPX Acircumflex uni01DC -149
+
+KPX Adieresis seven -149
+KPX Adieresis nine -102
+KPX Adieresis colon -36
+KPX Adieresis Hcircumflex -149
+KPX Adieresis Hbar -149
+KPX Adieresis Kcommaaccent -36
+KPX Adieresis uni01DC -149
+
+KPX AE seven -149
+KPX AE nine -102
+KPX AE colon -36
+KPX AE Hcircumflex -149
+KPX AE Hbar -149
+KPX AE Kcommaaccent -36
+KPX AE uni01DC -149
+
+KPX Egrave six 38
+KPX Egrave Gcircumflex 38
+KPX Egrave Gbreve 38
+KPX Egrave Gdotaccent 38
+KPX Egrave Gcommaaccent 38
+KPX Egrave uni01DA 38
+
+KPX Ecircumflex six 38
+KPX Ecircumflex Gcircumflex 38
+KPX Ecircumflex Gbreve 38
+KPX Ecircumflex Gdotaccent 38
+KPX Ecircumflex Gcommaaccent 38
+KPX Ecircumflex uni01DA 38
+
+KPX Igrave six 38
+KPX Igrave Gcircumflex 38
+KPX Igrave Gbreve 38
+KPX Igrave Gdotaccent 38
+KPX Igrave Gcommaaccent 38
+KPX Igrave uni01DA 38
+
+KPX Icircumflex six 38
+KPX Icircumflex Gcircumflex 38
+KPX Icircumflex Gbreve 38
+KPX Icircumflex Gdotaccent 38
+KPX Icircumflex Gcommaaccent 38
+KPX Icircumflex uni01DA 38
+
+KPX ucircumflex two -63
+KPX ucircumflex seven -139
+KPX ucircumflex nine -149
+KPX ucircumflex colon -83
+KPX ucircumflex less -196
+KPX ucircumflex backslash -73
+KPX ucircumflex questiondown -73
+KPX ucircumflex Aacute -73
+KPX ucircumflex Ebreve -63
+KPX ucircumflex Hbar -139
+KPX ucircumflex lacute -196
+
+KPX ydieresis two -63
+KPX ydieresis seven -139
+KPX ydieresis nine -149
+KPX ydieresis colon -83
+KPX ydieresis less -196
+KPX ydieresis backslash -73
+KPX ydieresis questiondown -73
+KPX ydieresis Aacute -73
+KPX ydieresis Ebreve -63
+KPX ydieresis Hbar -139
+KPX ydieresis lacute -196
+
+KPX abreve two -63
+KPX abreve seven -139
+KPX abreve nine -149
+KPX abreve colon -83
+KPX abreve less -196
+KPX abreve backslash -73
+KPX abreve questiondown -73
+KPX abreve Aacute -73
+KPX abreve Ebreve -63
+KPX abreve Hbar -139
+KPX abreve lacute -196
+
+
+
+KPX Gdotaccent six -73
+KPX Gdotaccent Gdotaccent -73
+KPX Gdotaccent Gcommaaccent -73
+
+KPX Gcommaaccent six -73
+KPX Gcommaaccent Gdotaccent -73
+KPX Gcommaaccent Gcommaaccent -73
+
+KPX Hbar dollar -188
+KPX Hbar D -215
+KPX Hbar F -253
+KPX Hbar H -253
+KPX Hbar R -253
+KPX Hbar U -159
+KPX Hbar V -243
+KPX Hbar X -206
+KPX Hbar Z -167
+KPX Hbar backslash -178
+KPX Hbar cent -215
+KPX Hbar sterling -215
+KPX Hbar currency -215
+KPX Hbar yen -215
+KPX Hbar brokenbar -215
+KPX Hbar section -215
+KPX Hbar dieresis -253
+KPX Hbar copyright -253
+KPX Hbar ordfeminine -253
+KPX Hbar guillemotleft -253
+KPX Hbar logicalnot -253
+KPX Hbar sfthyphen -253
+KPX Hbar acute -253
+KPX Hbar mu -253
+KPX Hbar paragraph -253
+KPX Hbar periodcentered -253
+KPX Hbar cedilla -253
+KPX Hbar ordmasculine -253
+KPX Hbar guillemotright -206
+KPX Hbar onequarter -206
+KPX Hbar onehalf -206
+KPX Hbar threequarters -206
+KPX Hbar questiondown -178
+KPX Hbar Aacute -178
+KPX Hbar Eacute -253
+KPX Hbar Idieresis -253
+KPX Hbar Yacute -253
+KPX Hbar ebreve -253
+KPX Hbar edotaccent -159
+KPX Hbar ecaron -159
+KPX Hbar gdotaccent -243
+KPX Hbar gcommaaccent -243
+KPX Hbar dotlessi -206
+
+KPX lacute dollar -159
+KPX lacute ampersand -36
+KPX lacute two -36
+KPX lacute D -188
+KPX lacute H -225
+KPX lacute L -36
+KPX lacute R -225
+KPX lacute X -188
+KPX lacute cent -188
+KPX lacute sterling -188
+KPX lacute currency -188
+KPX lacute yen -188
+KPX lacute brokenbar -188
+KPX lacute section -188
+KPX lacute dieresis -188
+KPX lacute ordfeminine -225
+KPX lacute guillemotleft -225
+KPX lacute logicalnot -225
+KPX lacute sfthyphen -225
+KPX lacute acute -225
+KPX lacute mu -225
+KPX lacute paragraph -225
+KPX lacute periodcentered -225
+KPX lacute cedilla -225
+KPX lacute ordmasculine -225
+KPX lacute guillemotright -188
+KPX lacute onequarter -188
+KPX lacute onehalf -188
+KPX lacute threequarters -188
+KPX lacute Egrave -36
+KPX lacute Icircumflex -36
+KPX lacute Yacute -225
+KPX lacute Ebreve -36
+KPX lacute ebreve -225
+KPX lacute dotlessi -188
+
+
+KPX uni027D dollar -243
+KPX uni027D nine 75
+KPX uni027D less 47
+KPX uni027D lacute 47
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttf
new file mode 100644
index 0000000..e5f7eec
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ufm
new file mode 100644
index 0000000..82dfd81
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSans.ufm
@@ -0,0 +1,6661 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans
+FontSubfamily Book
+UniqueID DejaVu Sans
+FullName DejaVu Sans
+Version Version 2.37
+PostScriptName DejaVuSans
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Sans
+PreferredSubfamily Book
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -1021 -463 1793 1232
+StartCharMetrics 6253
+U 32 ; WX 318 ; N space ; G 3
+U 33 ; WX 401 ; N exclam ; G 4
+U 34 ; WX 460 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 636 ; N dollar ; G 7
+U 37 ; WX 950 ; N percent ; G 8
+U 38 ; WX 780 ; N ampersand ; G 9
+U 39 ; WX 275 ; N quotesingle ; G 10
+U 40 ; WX 390 ; N parenleft ; G 11
+U 41 ; WX 390 ; N parenright ; G 12
+U 42 ; WX 500 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 318 ; N comma ; G 15
+U 45 ; WX 361 ; N hyphen ; G 16
+U 46 ; WX 318 ; N period ; G 17
+U 47 ; WX 337 ; N slash ; G 18
+U 48 ; WX 636 ; N zero ; G 19
+U 49 ; WX 636 ; N one ; G 20
+U 50 ; WX 636 ; N two ; G 21
+U 51 ; WX 636 ; N three ; G 22
+U 52 ; WX 636 ; N four ; G 23
+U 53 ; WX 636 ; N five ; G 24
+U 54 ; WX 636 ; N six ; G 25
+U 55 ; WX 636 ; N seven ; G 26
+U 56 ; WX 636 ; N eight ; G 27
+U 57 ; WX 636 ; N nine ; G 28
+U 58 ; WX 337 ; N colon ; G 29
+U 59 ; WX 337 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 531 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 684 ; N A ; G 36
+U 66 ; WX 686 ; N B ; G 37
+U 67 ; WX 698 ; N C ; G 38
+U 68 ; WX 770 ; N D ; G 39
+U 69 ; WX 632 ; N E ; G 40
+U 70 ; WX 575 ; N F ; G 41
+U 71 ; WX 775 ; N G ; G 42
+U 72 ; WX 752 ; N H ; G 43
+U 73 ; WX 295 ; N I ; G 44
+U 74 ; WX 295 ; N J ; G 45
+U 75 ; WX 656 ; N K ; G 46
+U 76 ; WX 557 ; N L ; G 47
+U 77 ; WX 863 ; N M ; G 48
+U 78 ; WX 748 ; N N ; G 49
+U 79 ; WX 787 ; N O ; G 50
+U 80 ; WX 603 ; N P ; G 51
+U 81 ; WX 787 ; N Q ; G 52
+U 82 ; WX 695 ; N R ; G 53
+U 83 ; WX 635 ; N S ; G 54
+U 84 ; WX 611 ; N T ; G 55
+U 85 ; WX 732 ; N U ; G 56
+U 86 ; WX 684 ; N V ; G 57
+U 87 ; WX 989 ; N W ; G 58
+U 88 ; WX 685 ; N X ; G 59
+U 89 ; WX 611 ; N Y ; G 60
+U 90 ; WX 685 ; N Z ; G 61
+U 91 ; WX 390 ; N bracketleft ; G 62
+U 92 ; WX 337 ; N backslash ; G 63
+U 93 ; WX 390 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 613 ; N a ; G 68
+U 98 ; WX 635 ; N b ; G 69
+U 99 ; WX 550 ; N c ; G 70
+U 100 ; WX 635 ; N d ; G 71
+U 101 ; WX 615 ; N e ; G 72
+U 102 ; WX 352 ; N f ; G 73
+U 103 ; WX 635 ; N g ; G 74
+U 104 ; WX 634 ; N h ; G 75
+U 105 ; WX 278 ; N i ; G 76
+U 106 ; WX 278 ; N j ; G 77
+U 107 ; WX 579 ; N k ; G 78
+U 108 ; WX 278 ; N l ; G 79
+U 109 ; WX 974 ; N m ; G 80
+U 110 ; WX 634 ; N n ; G 81
+U 111 ; WX 612 ; N o ; G 82
+U 112 ; WX 635 ; N p ; G 83
+U 113 ; WX 635 ; N q ; G 84
+U 114 ; WX 411 ; N r ; G 85
+U 115 ; WX 521 ; N s ; G 86
+U 116 ; WX 392 ; N t ; G 87
+U 117 ; WX 634 ; N u ; G 88
+U 118 ; WX 592 ; N v ; G 89
+U 119 ; WX 818 ; N w ; G 90
+U 120 ; WX 592 ; N x ; G 91
+U 121 ; WX 592 ; N y ; G 92
+U 122 ; WX 525 ; N z ; G 93
+U 123 ; WX 636 ; N braceleft ; G 94
+U 124 ; WX 337 ; N bar ; G 95
+U 125 ; WX 636 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 318 ; N nbspace ; G 98
+U 161 ; WX 401 ; N exclamdown ; G 99
+U 162 ; WX 636 ; N cent ; G 100
+U 163 ; WX 636 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 636 ; N yen ; G 103
+U 166 ; WX 337 ; N brokenbar ; G 104
+U 167 ; WX 500 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 471 ; N ordfeminine ; G 108
+U 171 ; WX 612 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 361 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 401 ; N twosuperior ; G 116
+U 179 ; WX 401 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 636 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 318 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 401 ; N onesuperior ; G 123
+U 186 ; WX 471 ; N ordmasculine ; G 124
+U 187 ; WX 612 ; N guillemotright ; G 125
+U 188 ; WX 969 ; N onequarter ; G 126
+U 189 ; WX 969 ; N onehalf ; G 127
+U 190 ; WX 969 ; N threequarters ; G 128
+U 191 ; WX 531 ; N questiondown ; G 129
+U 192 ; WX 684 ; N Agrave ; G 130
+U 193 ; WX 684 ; N Aacute ; G 131
+U 194 ; WX 684 ; N Acircumflex ; G 132
+U 195 ; WX 684 ; N Atilde ; G 133
+U 196 ; WX 684 ; N Adieresis ; G 134
+U 197 ; WX 684 ; N Aring ; G 135
+U 198 ; WX 974 ; N AE ; G 136
+U 199 ; WX 698 ; N Ccedilla ; G 137
+U 200 ; WX 632 ; N Egrave ; G 138
+U 201 ; WX 632 ; N Eacute ; G 139
+U 202 ; WX 632 ; N Ecircumflex ; G 140
+U 203 ; WX 632 ; N Edieresis ; G 141
+U 204 ; WX 295 ; N Igrave ; G 142
+U 205 ; WX 295 ; N Iacute ; G 143
+U 206 ; WX 295 ; N Icircumflex ; G 144
+U 207 ; WX 295 ; N Idieresis ; G 145
+U 208 ; WX 775 ; N Eth ; G 146
+U 209 ; WX 748 ; N Ntilde ; G 147
+U 210 ; WX 787 ; N Ograve ; G 148
+U 211 ; WX 787 ; N Oacute ; G 149
+U 212 ; WX 787 ; N Ocircumflex ; G 150
+U 213 ; WX 787 ; N Otilde ; G 151
+U 214 ; WX 787 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 787 ; N Oslash ; G 154
+U 217 ; WX 732 ; N Ugrave ; G 155
+U 218 ; WX 732 ; N Uacute ; G 156
+U 219 ; WX 732 ; N Ucircumflex ; G 157
+U 220 ; WX 732 ; N Udieresis ; G 158
+U 221 ; WX 611 ; N Yacute ; G 159
+U 222 ; WX 605 ; N Thorn ; G 160
+U 223 ; WX 630 ; N germandbls ; G 161
+U 224 ; WX 613 ; N agrave ; G 162
+U 225 ; WX 613 ; N aacute ; G 163
+U 226 ; WX 613 ; N acircumflex ; G 164
+U 227 ; WX 613 ; N atilde ; G 165
+U 228 ; WX 613 ; N adieresis ; G 166
+U 229 ; WX 613 ; N aring ; G 167
+U 230 ; WX 982 ; N ae ; G 168
+U 231 ; WX 550 ; N ccedilla ; G 169
+U 232 ; WX 615 ; N egrave ; G 170
+U 233 ; WX 615 ; N eacute ; G 171
+U 234 ; WX 615 ; N ecircumflex ; G 172
+U 235 ; WX 615 ; N edieresis ; G 173
+U 236 ; WX 278 ; N igrave ; G 174
+U 237 ; WX 278 ; N iacute ; G 175
+U 238 ; WX 278 ; N icircumflex ; G 176
+U 239 ; WX 278 ; N idieresis ; G 177
+U 240 ; WX 612 ; N eth ; G 178
+U 241 ; WX 634 ; N ntilde ; G 179
+U 242 ; WX 612 ; N ograve ; G 180
+U 243 ; WX 612 ; N oacute ; G 181
+U 244 ; WX 612 ; N ocircumflex ; G 182
+U 245 ; WX 612 ; N otilde ; G 183
+U 246 ; WX 612 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 612 ; N oslash ; G 186
+U 249 ; WX 634 ; N ugrave ; G 187
+U 250 ; WX 634 ; N uacute ; G 188
+U 251 ; WX 634 ; N ucircumflex ; G 189
+U 252 ; WX 634 ; N udieresis ; G 190
+U 253 ; WX 592 ; N yacute ; G 191
+U 254 ; WX 635 ; N thorn ; G 192
+U 255 ; WX 592 ; N ydieresis ; G 193
+U 256 ; WX 684 ; N Amacron ; G 194
+U 257 ; WX 613 ; N amacron ; G 195
+U 258 ; WX 684 ; N Abreve ; G 196
+U 259 ; WX 613 ; N abreve ; G 197
+U 260 ; WX 684 ; N Aogonek ; G 198
+U 261 ; WX 613 ; N aogonek ; G 199
+U 262 ; WX 698 ; N Cacute ; G 200
+U 263 ; WX 550 ; N cacute ; G 201
+U 264 ; WX 698 ; N Ccircumflex ; G 202
+U 265 ; WX 550 ; N ccircumflex ; G 203
+U 266 ; WX 698 ; N Cdotaccent ; G 204
+U 267 ; WX 550 ; N cdotaccent ; G 205
+U 268 ; WX 698 ; N Ccaron ; G 206
+U 269 ; WX 550 ; N ccaron ; G 207
+U 270 ; WX 770 ; N Dcaron ; G 208
+U 271 ; WX 635 ; N dcaron ; G 209
+U 272 ; WX 775 ; N Dcroat ; G 210
+U 273 ; WX 635 ; N dmacron ; G 211
+U 274 ; WX 632 ; N Emacron ; G 212
+U 275 ; WX 615 ; N emacron ; G 213
+U 276 ; WX 632 ; N Ebreve ; G 214
+U 277 ; WX 615 ; N ebreve ; G 215
+U 278 ; WX 632 ; N Edotaccent ; G 216
+U 279 ; WX 615 ; N edotaccent ; G 217
+U 280 ; WX 632 ; N Eogonek ; G 218
+U 281 ; WX 615 ; N eogonek ; G 219
+U 282 ; WX 632 ; N Ecaron ; G 220
+U 283 ; WX 615 ; N ecaron ; G 221
+U 284 ; WX 775 ; N Gcircumflex ; G 222
+U 285 ; WX 635 ; N gcircumflex ; G 223
+U 286 ; WX 775 ; N Gbreve ; G 224
+U 287 ; WX 635 ; N gbreve ; G 225
+U 288 ; WX 775 ; N Gdotaccent ; G 226
+U 289 ; WX 635 ; N gdotaccent ; G 227
+U 290 ; WX 775 ; N Gcommaaccent ; G 228
+U 291 ; WX 635 ; N gcommaaccent ; G 229
+U 292 ; WX 752 ; N Hcircumflex ; G 230
+U 293 ; WX 634 ; N hcircumflex ; G 231
+U 294 ; WX 916 ; N Hbar ; G 232
+U 295 ; WX 695 ; N hbar ; G 233
+U 296 ; WX 295 ; N Itilde ; G 234
+U 297 ; WX 278 ; N itilde ; G 235
+U 298 ; WX 295 ; N Imacron ; G 236
+U 299 ; WX 278 ; N imacron ; G 237
+U 300 ; WX 295 ; N Ibreve ; G 238
+U 301 ; WX 278 ; N ibreve ; G 239
+U 302 ; WX 295 ; N Iogonek ; G 240
+U 303 ; WX 278 ; N iogonek ; G 241
+U 304 ; WX 295 ; N Idot ; G 242
+U 305 ; WX 278 ; N dotlessi ; G 243
+U 306 ; WX 590 ; N IJ ; G 244
+U 307 ; WX 556 ; N ij ; G 245
+U 308 ; WX 295 ; N Jcircumflex ; G 246
+U 309 ; WX 278 ; N jcircumflex ; G 247
+U 310 ; WX 656 ; N Kcommaaccent ; G 248
+U 311 ; WX 579 ; N kcommaaccent ; G 249
+U 312 ; WX 579 ; N kgreenlandic ; G 250
+U 313 ; WX 557 ; N Lacute ; G 251
+U 314 ; WX 278 ; N lacute ; G 252
+U 315 ; WX 557 ; N Lcommaaccent ; G 253
+U 316 ; WX 278 ; N lcommaaccent ; G 254
+U 317 ; WX 557 ; N Lcaron ; G 255
+U 318 ; WX 375 ; N lcaron ; G 256
+U 319 ; WX 557 ; N Ldot ; G 257
+U 320 ; WX 342 ; N ldot ; G 258
+U 321 ; WX 562 ; N Lslash ; G 259
+U 322 ; WX 284 ; N lslash ; G 260
+U 323 ; WX 748 ; N Nacute ; G 261
+U 324 ; WX 634 ; N nacute ; G 262
+U 325 ; WX 748 ; N Ncommaaccent ; G 263
+U 326 ; WX 634 ; N ncommaaccent ; G 264
+U 327 ; WX 748 ; N Ncaron ; G 265
+U 328 ; WX 634 ; N ncaron ; G 266
+U 329 ; WX 813 ; N napostrophe ; G 267
+U 330 ; WX 748 ; N Eng ; G 268
+U 331 ; WX 634 ; N eng ; G 269
+U 332 ; WX 787 ; N Omacron ; G 270
+U 333 ; WX 612 ; N omacron ; G 271
+U 334 ; WX 787 ; N Obreve ; G 272
+U 335 ; WX 612 ; N obreve ; G 273
+U 336 ; WX 787 ; N Ohungarumlaut ; G 274
+U 337 ; WX 612 ; N ohungarumlaut ; G 275
+U 338 ; WX 1070 ; N OE ; G 276
+U 339 ; WX 1023 ; N oe ; G 277
+U 340 ; WX 695 ; N Racute ; G 278
+U 341 ; WX 411 ; N racute ; G 279
+U 342 ; WX 695 ; N Rcommaaccent ; G 280
+U 343 ; WX 411 ; N rcommaaccent ; G 281
+U 344 ; WX 695 ; N Rcaron ; G 282
+U 345 ; WX 411 ; N rcaron ; G 283
+U 346 ; WX 635 ; N Sacute ; G 284
+U 347 ; WX 521 ; N sacute ; G 285
+U 348 ; WX 635 ; N Scircumflex ; G 286
+U 349 ; WX 521 ; N scircumflex ; G 287
+U 350 ; WX 635 ; N Scedilla ; G 288
+U 351 ; WX 521 ; N scedilla ; G 289
+U 352 ; WX 635 ; N Scaron ; G 290
+U 353 ; WX 521 ; N scaron ; G 291
+U 354 ; WX 611 ; N Tcommaaccent ; G 292
+U 355 ; WX 392 ; N tcommaaccent ; G 293
+U 356 ; WX 611 ; N Tcaron ; G 294
+U 357 ; WX 392 ; N tcaron ; G 295
+U 358 ; WX 611 ; N Tbar ; G 296
+U 359 ; WX 392 ; N tbar ; G 297
+U 360 ; WX 732 ; N Utilde ; G 298
+U 361 ; WX 634 ; N utilde ; G 299
+U 362 ; WX 732 ; N Umacron ; G 300
+U 363 ; WX 634 ; N umacron ; G 301
+U 364 ; WX 732 ; N Ubreve ; G 302
+U 365 ; WX 634 ; N ubreve ; G 303
+U 366 ; WX 732 ; N Uring ; G 304
+U 367 ; WX 634 ; N uring ; G 305
+U 368 ; WX 732 ; N Uhungarumlaut ; G 306
+U 369 ; WX 634 ; N uhungarumlaut ; G 307
+U 370 ; WX 732 ; N Uogonek ; G 308
+U 371 ; WX 634 ; N uogonek ; G 309
+U 372 ; WX 989 ; N Wcircumflex ; G 310
+U 373 ; WX 818 ; N wcircumflex ; G 311
+U 374 ; WX 611 ; N Ycircumflex ; G 312
+U 375 ; WX 592 ; N ycircumflex ; G 313
+U 376 ; WX 611 ; N Ydieresis ; G 314
+U 377 ; WX 685 ; N Zacute ; G 315
+U 378 ; WX 525 ; N zacute ; G 316
+U 379 ; WX 685 ; N Zdotaccent ; G 317
+U 380 ; WX 525 ; N zdotaccent ; G 318
+U 381 ; WX 685 ; N Zcaron ; G 319
+U 382 ; WX 525 ; N zcaron ; G 320
+U 383 ; WX 352 ; N longs ; G 321
+U 384 ; WX 635 ; N uni0180 ; G 322
+U 385 ; WX 735 ; N uni0181 ; G 323
+U 386 ; WX 686 ; N uni0182 ; G 324
+U 387 ; WX 635 ; N uni0183 ; G 325
+U 388 ; WX 686 ; N uni0184 ; G 326
+U 389 ; WX 635 ; N uni0185 ; G 327
+U 390 ; WX 703 ; N uni0186 ; G 328
+U 391 ; WX 698 ; N uni0187 ; G 329
+U 392 ; WX 550 ; N uni0188 ; G 330
+U 393 ; WX 775 ; N uni0189 ; G 331
+U 394 ; WX 819 ; N uni018A ; G 332
+U 395 ; WX 686 ; N uni018B ; G 333
+U 396 ; WX 635 ; N uni018C ; G 334
+U 397 ; WX 612 ; N uni018D ; G 335
+U 398 ; WX 632 ; N uni018E ; G 336
+U 399 ; WX 787 ; N uni018F ; G 337
+U 400 ; WX 614 ; N uni0190 ; G 338
+U 401 ; WX 575 ; N uni0191 ; G 339
+U 402 ; WX 352 ; N florin ; G 340
+U 403 ; WX 775 ; N uni0193 ; G 341
+U 404 ; WX 687 ; N uni0194 ; G 342
+U 405 ; WX 984 ; N uni0195 ; G 343
+U 406 ; WX 354 ; N uni0196 ; G 344
+U 407 ; WX 295 ; N uni0197 ; G 345
+U 408 ; WX 746 ; N uni0198 ; G 346
+U 409 ; WX 579 ; N uni0199 ; G 347
+U 410 ; WX 278 ; N uni019A ; G 348
+U 411 ; WX 592 ; N uni019B ; G 349
+U 412 ; WX 974 ; N uni019C ; G 350
+U 413 ; WX 748 ; N uni019D ; G 351
+U 414 ; WX 634 ; N uni019E ; G 352
+U 415 ; WX 787 ; N uni019F ; G 353
+U 416 ; WX 913 ; N Ohorn ; G 354
+U 417 ; WX 612 ; N ohorn ; G 355
+U 418 ; WX 949 ; N uni01A2 ; G 356
+U 419 ; WX 759 ; N uni01A3 ; G 357
+U 420 ; WX 652 ; N uni01A4 ; G 358
+U 421 ; WX 635 ; N uni01A5 ; G 359
+U 422 ; WX 695 ; N uni01A6 ; G 360
+U 423 ; WX 635 ; N uni01A7 ; G 361
+U 424 ; WX 521 ; N uni01A8 ; G 362
+U 425 ; WX 632 ; N uni01A9 ; G 363
+U 426 ; WX 336 ; N uni01AA ; G 364
+U 427 ; WX 392 ; N uni01AB ; G 365
+U 428 ; WX 611 ; N uni01AC ; G 366
+U 429 ; WX 392 ; N uni01AD ; G 367
+U 430 ; WX 611 ; N uni01AE ; G 368
+U 431 ; WX 858 ; N Uhorn ; G 369
+U 432 ; WX 634 ; N uhorn ; G 370
+U 433 ; WX 764 ; N uni01B1 ; G 371
+U 434 ; WX 721 ; N uni01B2 ; G 372
+U 435 ; WX 744 ; N uni01B3 ; G 373
+U 436 ; WX 730 ; N uni01B4 ; G 374
+U 437 ; WX 685 ; N uni01B5 ; G 375
+U 438 ; WX 525 ; N uni01B6 ; G 376
+U 439 ; WX 666 ; N uni01B7 ; G 377
+U 440 ; WX 666 ; N uni01B8 ; G 378
+U 441 ; WX 578 ; N uni01B9 ; G 379
+U 442 ; WX 525 ; N uni01BA ; G 380
+U 443 ; WX 636 ; N uni01BB ; G 381
+U 444 ; WX 666 ; N uni01BC ; G 382
+U 445 ; WX 578 ; N uni01BD ; G 383
+U 446 ; WX 510 ; N uni01BE ; G 384
+U 447 ; WX 635 ; N uni01BF ; G 385
+U 448 ; WX 295 ; N uni01C0 ; G 386
+U 449 ; WX 492 ; N uni01C1 ; G 387
+U 450 ; WX 459 ; N uni01C2 ; G 388
+U 451 ; WX 295 ; N uni01C3 ; G 389
+U 452 ; WX 1422 ; N uni01C4 ; G 390
+U 453 ; WX 1299 ; N uni01C5 ; G 391
+U 454 ; WX 1154 ; N uni01C6 ; G 392
+U 455 ; WX 835 ; N uni01C7 ; G 393
+U 456 ; WX 787 ; N uni01C8 ; G 394
+U 457 ; WX 457 ; N uni01C9 ; G 395
+U 458 ; WX 931 ; N uni01CA ; G 396
+U 459 ; WX 924 ; N uni01CB ; G 397
+U 460 ; WX 797 ; N uni01CC ; G 398
+U 461 ; WX 684 ; N uni01CD ; G 399
+U 462 ; WX 613 ; N uni01CE ; G 400
+U 463 ; WX 295 ; N uni01CF ; G 401
+U 464 ; WX 278 ; N uni01D0 ; G 402
+U 465 ; WX 787 ; N uni01D1 ; G 403
+U 466 ; WX 612 ; N uni01D2 ; G 404
+U 467 ; WX 732 ; N uni01D3 ; G 405
+U 468 ; WX 634 ; N uni01D4 ; G 406
+U 469 ; WX 732 ; N uni01D5 ; G 407
+U 470 ; WX 634 ; N uni01D6 ; G 408
+U 471 ; WX 732 ; N uni01D7 ; G 409
+U 472 ; WX 634 ; N uni01D8 ; G 410
+U 473 ; WX 732 ; N uni01D9 ; G 411
+U 474 ; WX 634 ; N uni01DA ; G 412
+U 475 ; WX 732 ; N uni01DB ; G 413
+U 476 ; WX 634 ; N uni01DC ; G 414
+U 477 ; WX 615 ; N uni01DD ; G 415
+U 478 ; WX 684 ; N uni01DE ; G 416
+U 479 ; WX 613 ; N uni01DF ; G 417
+U 480 ; WX 684 ; N uni01E0 ; G 418
+U 481 ; WX 613 ; N uni01E1 ; G 419
+U 482 ; WX 974 ; N uni01E2 ; G 420
+U 483 ; WX 982 ; N uni01E3 ; G 421
+U 484 ; WX 775 ; N uni01E4 ; G 422
+U 485 ; WX 635 ; N uni01E5 ; G 423
+U 486 ; WX 775 ; N Gcaron ; G 424
+U 487 ; WX 635 ; N gcaron ; G 425
+U 488 ; WX 656 ; N uni01E8 ; G 426
+U 489 ; WX 579 ; N uni01E9 ; G 427
+U 490 ; WX 787 ; N uni01EA ; G 428
+U 491 ; WX 612 ; N uni01EB ; G 429
+U 492 ; WX 787 ; N uni01EC ; G 430
+U 493 ; WX 612 ; N uni01ED ; G 431
+U 494 ; WX 666 ; N uni01EE ; G 432
+U 495 ; WX 578 ; N uni01EF ; G 433
+U 496 ; WX 278 ; N uni01F0 ; G 434
+U 497 ; WX 1422 ; N uni01F1 ; G 435
+U 498 ; WX 1299 ; N uni01F2 ; G 436
+U 499 ; WX 1154 ; N uni01F3 ; G 437
+U 500 ; WX 775 ; N uni01F4 ; G 438
+U 501 ; WX 635 ; N uni01F5 ; G 439
+U 502 ; WX 1113 ; N uni01F6 ; G 440
+U 503 ; WX 682 ; N uni01F7 ; G 441
+U 504 ; WX 748 ; N uni01F8 ; G 442
+U 505 ; WX 634 ; N uni01F9 ; G 443
+U 506 ; WX 684 ; N Aringacute ; G 444
+U 507 ; WX 613 ; N aringacute ; G 445
+U 508 ; WX 974 ; N AEacute ; G 446
+U 509 ; WX 982 ; N aeacute ; G 447
+U 510 ; WX 787 ; N Oslashacute ; G 448
+U 511 ; WX 612 ; N oslashacute ; G 449
+U 512 ; WX 684 ; N uni0200 ; G 450
+U 513 ; WX 613 ; N uni0201 ; G 451
+U 514 ; WX 684 ; N uni0202 ; G 452
+U 515 ; WX 613 ; N uni0203 ; G 453
+U 516 ; WX 632 ; N uni0204 ; G 454
+U 517 ; WX 615 ; N uni0205 ; G 455
+U 518 ; WX 632 ; N uni0206 ; G 456
+U 519 ; WX 615 ; N uni0207 ; G 457
+U 520 ; WX 295 ; N uni0208 ; G 458
+U 521 ; WX 278 ; N uni0209 ; G 459
+U 522 ; WX 295 ; N uni020A ; G 460
+U 523 ; WX 278 ; N uni020B ; G 461
+U 524 ; WX 787 ; N uni020C ; G 462
+U 525 ; WX 612 ; N uni020D ; G 463
+U 526 ; WX 787 ; N uni020E ; G 464
+U 527 ; WX 612 ; N uni020F ; G 465
+U 528 ; WX 695 ; N uni0210 ; G 466
+U 529 ; WX 411 ; N uni0211 ; G 467
+U 530 ; WX 695 ; N uni0212 ; G 468
+U 531 ; WX 411 ; N uni0213 ; G 469
+U 532 ; WX 732 ; N uni0214 ; G 470
+U 533 ; WX 634 ; N uni0215 ; G 471
+U 534 ; WX 732 ; N uni0216 ; G 472
+U 535 ; WX 634 ; N uni0217 ; G 473
+U 536 ; WX 635 ; N Scommaaccent ; G 474
+U 537 ; WX 521 ; N scommaaccent ; G 475
+U 538 ; WX 611 ; N uni021A ; G 476
+U 539 ; WX 392 ; N uni021B ; G 477
+U 540 ; WX 627 ; N uni021C ; G 478
+U 541 ; WX 521 ; N uni021D ; G 479
+U 542 ; WX 752 ; N uni021E ; G 480
+U 543 ; WX 634 ; N uni021F ; G 481
+U 544 ; WX 735 ; N uni0220 ; G 482
+U 545 ; WX 838 ; N uni0221 ; G 483
+U 546 ; WX 698 ; N uni0222 ; G 484
+U 547 ; WX 610 ; N uni0223 ; G 485
+U 548 ; WX 685 ; N uni0224 ; G 486
+U 549 ; WX 525 ; N uni0225 ; G 487
+U 550 ; WX 684 ; N uni0226 ; G 488
+U 551 ; WX 613 ; N uni0227 ; G 489
+U 552 ; WX 632 ; N uni0228 ; G 490
+U 553 ; WX 615 ; N uni0229 ; G 491
+U 554 ; WX 787 ; N uni022A ; G 492
+U 555 ; WX 612 ; N uni022B ; G 493
+U 556 ; WX 787 ; N uni022C ; G 494
+U 557 ; WX 612 ; N uni022D ; G 495
+U 558 ; WX 787 ; N uni022E ; G 496
+U 559 ; WX 612 ; N uni022F ; G 497
+U 560 ; WX 787 ; N uni0230 ; G 498
+U 561 ; WX 612 ; N uni0231 ; G 499
+U 562 ; WX 611 ; N uni0232 ; G 500
+U 563 ; WX 592 ; N uni0233 ; G 501
+U 564 ; WX 475 ; N uni0234 ; G 502
+U 565 ; WX 843 ; N uni0235 ; G 503
+U 566 ; WX 477 ; N uni0236 ; G 504
+U 567 ; WX 278 ; N dotlessj ; G 505
+U 568 ; WX 998 ; N uni0238 ; G 506
+U 569 ; WX 998 ; N uni0239 ; G 507
+U 570 ; WX 684 ; N uni023A ; G 508
+U 571 ; WX 698 ; N uni023B ; G 509
+U 572 ; WX 550 ; N uni023C ; G 510
+U 573 ; WX 557 ; N uni023D ; G 511
+U 574 ; WX 611 ; N uni023E ; G 512
+U 575 ; WX 521 ; N uni023F ; G 513
+U 576 ; WX 525 ; N uni0240 ; G 514
+U 577 ; WX 603 ; N uni0241 ; G 515
+U 578 ; WX 479 ; N uni0242 ; G 516
+U 579 ; WX 686 ; N uni0243 ; G 517
+U 580 ; WX 732 ; N uni0244 ; G 518
+U 581 ; WX 684 ; N uni0245 ; G 519
+U 582 ; WX 632 ; N uni0246 ; G 520
+U 583 ; WX 615 ; N uni0247 ; G 521
+U 584 ; WX 295 ; N uni0248 ; G 522
+U 585 ; WX 278 ; N uni0249 ; G 523
+U 586 ; WX 781 ; N uni024A ; G 524
+U 587 ; WX 635 ; N uni024B ; G 525
+U 588 ; WX 695 ; N uni024C ; G 526
+U 589 ; WX 411 ; N uni024D ; G 527
+U 590 ; WX 611 ; N uni024E ; G 528
+U 591 ; WX 592 ; N uni024F ; G 529
+U 592 ; WX 600 ; N uni0250 ; G 530
+U 593 ; WX 635 ; N uni0251 ; G 531
+U 594 ; WX 635 ; N uni0252 ; G 532
+U 595 ; WX 635 ; N uni0253 ; G 533
+U 596 ; WX 549 ; N uni0254 ; G 534
+U 597 ; WX 550 ; N uni0255 ; G 535
+U 598 ; WX 635 ; N uni0256 ; G 536
+U 599 ; WX 696 ; N uni0257 ; G 537
+U 600 ; WX 615 ; N uni0258 ; G 538
+U 601 ; WX 615 ; N uni0259 ; G 539
+U 602 ; WX 819 ; N uni025A ; G 540
+U 603 ; WX 541 ; N uni025B ; G 541
+U 604 ; WX 532 ; N uni025C ; G 542
+U 605 ; WX 775 ; N uni025D ; G 543
+U 606 ; WX 664 ; N uni025E ; G 544
+U 607 ; WX 278 ; N uni025F ; G 545
+U 608 ; WX 696 ; N uni0260 ; G 546
+U 609 ; WX 635 ; N uni0261 ; G 547
+U 610 ; WX 629 ; N uni0262 ; G 548
+U 611 ; WX 596 ; N uni0263 ; G 549
+U 612 ; WX 596 ; N uni0264 ; G 550
+U 613 ; WX 634 ; N uni0265 ; G 551
+U 614 ; WX 634 ; N uni0266 ; G 552
+U 615 ; WX 634 ; N uni0267 ; G 553
+U 616 ; WX 278 ; N uni0268 ; G 554
+U 617 ; WX 338 ; N uni0269 ; G 555
+U 618 ; WX 372 ; N uni026A ; G 556
+U 619 ; WX 396 ; N uni026B ; G 557
+U 620 ; WX 487 ; N uni026C ; G 558
+U 621 ; WX 278 ; N uni026D ; G 559
+U 622 ; WX 706 ; N uni026E ; G 560
+U 623 ; WX 974 ; N uni026F ; G 561
+U 624 ; WX 974 ; N uni0270 ; G 562
+U 625 ; WX 974 ; N uni0271 ; G 563
+U 626 ; WX 646 ; N uni0272 ; G 564
+U 627 ; WX 642 ; N uni0273 ; G 565
+U 628 ; WX 634 ; N uni0274 ; G 566
+U 629 ; WX 612 ; N uni0275 ; G 567
+U 630 ; WX 858 ; N uni0276 ; G 568
+U 631 ; WX 728 ; N uni0277 ; G 569
+U 632 ; WX 660 ; N uni0278 ; G 570
+U 633 ; WX 414 ; N uni0279 ; G 571
+U 634 ; WX 414 ; N uni027A ; G 572
+U 635 ; WX 414 ; N uni027B ; G 573
+U 636 ; WX 411 ; N uni027C ; G 574
+U 637 ; WX 411 ; N uni027D ; G 575
+U 638 ; WX 530 ; N uni027E ; G 576
+U 639 ; WX 530 ; N uni027F ; G 577
+U 640 ; WX 604 ; N uni0280 ; G 578
+U 641 ; WX 604 ; N uni0281 ; G 579
+U 642 ; WX 521 ; N uni0282 ; G 580
+U 643 ; WX 336 ; N uni0283 ; G 581
+U 644 ; WX 336 ; N uni0284 ; G 582
+U 645 ; WX 461 ; N uni0285 ; G 583
+U 646 ; WX 336 ; N uni0286 ; G 584
+U 647 ; WX 392 ; N uni0287 ; G 585
+U 648 ; WX 392 ; N uni0288 ; G 586
+U 649 ; WX 634 ; N uni0289 ; G 587
+U 650 ; WX 618 ; N uni028A ; G 588
+U 651 ; WX 598 ; N uni028B ; G 589
+U 652 ; WX 592 ; N uni028C ; G 590
+U 653 ; WX 818 ; N uni028D ; G 591
+U 654 ; WX 592 ; N uni028E ; G 592
+U 655 ; WX 611 ; N uni028F ; G 593
+U 656 ; WX 525 ; N uni0290 ; G 594
+U 657 ; WX 525 ; N uni0291 ; G 595
+U 658 ; WX 578 ; N uni0292 ; G 596
+U 659 ; WX 578 ; N uni0293 ; G 597
+U 660 ; WX 510 ; N uni0294 ; G 598
+U 661 ; WX 510 ; N uni0295 ; G 599
+U 662 ; WX 510 ; N uni0296 ; G 600
+U 663 ; WX 510 ; N uni0297 ; G 601
+U 664 ; WX 787 ; N uni0298 ; G 602
+U 665 ; WX 580 ; N uni0299 ; G 603
+U 666 ; WX 664 ; N uni029A ; G 604
+U 667 ; WX 708 ; N uni029B ; G 605
+U 668 ; WX 654 ; N uni029C ; G 606
+U 669 ; WX 292 ; N uni029D ; G 607
+U 670 ; WX 667 ; N uni029E ; G 608
+U 671 ; WX 507 ; N uni029F ; G 609
+U 672 ; WX 727 ; N uni02A0 ; G 610
+U 673 ; WX 510 ; N uni02A1 ; G 611
+U 674 ; WX 510 ; N uni02A2 ; G 612
+U 675 ; WX 1014 ; N uni02A3 ; G 613
+U 676 ; WX 1058 ; N uni02A4 ; G 614
+U 677 ; WX 1013 ; N uni02A5 ; G 615
+U 678 ; WX 830 ; N uni02A6 ; G 616
+U 679 ; WX 610 ; N uni02A7 ; G 617
+U 680 ; WX 778 ; N uni02A8 ; G 618
+U 681 ; WX 848 ; N uni02A9 ; G 619
+U 682 ; WX 706 ; N uni02AA ; G 620
+U 683 ; WX 654 ; N uni02AB ; G 621
+U 684 ; WX 515 ; N uni02AC ; G 622
+U 685 ; WX 515 ; N uni02AD ; G 623
+U 686 ; WX 661 ; N uni02AE ; G 624
+U 687 ; WX 664 ; N uni02AF ; G 625
+U 688 ; WX 404 ; N uni02B0 ; G 626
+U 689 ; WX 399 ; N uni02B1 ; G 627
+U 690 ; WX 175 ; N uni02B2 ; G 628
+U 691 ; WX 259 ; N uni02B3 ; G 629
+U 692 ; WX 295 ; N uni02B4 ; G 630
+U 693 ; WX 296 ; N uni02B5 ; G 631
+U 694 ; WX 379 ; N uni02B6 ; G 632
+U 695 ; WX 515 ; N uni02B7 ; G 633
+U 696 ; WX 373 ; N uni02B8 ; G 634
+U 697 ; WX 278 ; N uni02B9 ; G 635
+U 698 ; WX 460 ; N uni02BA ; G 636
+U 699 ; WX 318 ; N uni02BB ; G 637
+U 700 ; WX 318 ; N uni02BC ; G 638
+U 701 ; WX 318 ; N uni02BD ; G 639
+U 702 ; WX 307 ; N uni02BE ; G 640
+U 703 ; WX 307 ; N uni02BF ; G 641
+U 704 ; WX 370 ; N uni02C0 ; G 642
+U 705 ; WX 370 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 275 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 275 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 718 ; WX 500 ; N uni02CE ; G 656
+U 719 ; WX 500 ; N uni02CF ; G 657
+U 720 ; WX 337 ; N uni02D0 ; G 658
+U 721 ; WX 337 ; N uni02D1 ; G 659
+U 722 ; WX 307 ; N uni02D2 ; G 660
+U 723 ; WX 307 ; N uni02D3 ; G 661
+U 724 ; WX 500 ; N uni02D4 ; G 662
+U 725 ; WX 500 ; N uni02D5 ; G 663
+U 726 ; WX 390 ; N uni02D6 ; G 664
+U 727 ; WX 317 ; N uni02D7 ; G 665
+U 728 ; WX 500 ; N breve ; G 666
+U 729 ; WX 500 ; N dotaccent ; G 667
+U 730 ; WX 500 ; N ring ; G 668
+U 731 ; WX 500 ; N ogonek ; G 669
+U 732 ; WX 500 ; N tilde ; G 670
+U 733 ; WX 500 ; N hungarumlaut ; G 671
+U 734 ; WX 315 ; N uni02DE ; G 672
+U 735 ; WX 500 ; N uni02DF ; G 673
+U 736 ; WX 426 ; N uni02E0 ; G 674
+U 737 ; WX 166 ; N uni02E1 ; G 675
+U 738 ; WX 373 ; N uni02E2 ; G 676
+U 739 ; WX 444 ; N uni02E3 ; G 677
+U 740 ; WX 370 ; N uni02E4 ; G 678
+U 741 ; WX 493 ; N uni02E5 ; G 679
+U 742 ; WX 493 ; N uni02E6 ; G 680
+U 743 ; WX 493 ; N uni02E7 ; G 681
+U 744 ; WX 493 ; N uni02E8 ; G 682
+U 745 ; WX 493 ; N uni02E9 ; G 683
+U 748 ; WX 500 ; N uni02EC ; G 684
+U 749 ; WX 500 ; N uni02ED ; G 685
+U 750 ; WX 518 ; N uni02EE ; G 686
+U 755 ; WX 500 ; N uni02F3 ; G 687
+U 759 ; WX 500 ; N uni02F7 ; G 688
+U 768 ; WX 0 ; N gravecomb ; G 689
+U 769 ; WX 0 ; N acutecomb ; G 690
+U 770 ; WX 0 ; N uni0302 ; G 691
+U 771 ; WX 0 ; N tildecomb ; G 692
+U 772 ; WX 0 ; N uni0304 ; G 693
+U 773 ; WX 0 ; N uni0305 ; G 694
+U 774 ; WX 0 ; N uni0306 ; G 695
+U 775 ; WX 0 ; N uni0307 ; G 696
+U 776 ; WX 0 ; N uni0308 ; G 697
+U 777 ; WX 0 ; N hookabovecomb ; G 698
+U 778 ; WX 0 ; N uni030A ; G 699
+U 779 ; WX 0 ; N uni030B ; G 700
+U 780 ; WX 0 ; N uni030C ; G 701
+U 781 ; WX 0 ; N uni030D ; G 702
+U 782 ; WX 0 ; N uni030E ; G 703
+U 783 ; WX 0 ; N uni030F ; G 704
+U 784 ; WX 0 ; N uni0310 ; G 705
+U 785 ; WX 0 ; N uni0311 ; G 706
+U 786 ; WX 0 ; N uni0312 ; G 707
+U 787 ; WX 0 ; N uni0313 ; G 708
+U 788 ; WX 0 ; N uni0314 ; G 709
+U 789 ; WX 0 ; N uni0315 ; G 710
+U 790 ; WX 0 ; N uni0316 ; G 711
+U 791 ; WX 0 ; N uni0317 ; G 712
+U 792 ; WX 0 ; N uni0318 ; G 713
+U 793 ; WX 0 ; N uni0319 ; G 714
+U 794 ; WX 0 ; N uni031A ; G 715
+U 795 ; WX 0 ; N uni031B ; G 716
+U 796 ; WX 0 ; N uni031C ; G 717
+U 797 ; WX 0 ; N uni031D ; G 718
+U 798 ; WX 0 ; N uni031E ; G 719
+U 799 ; WX 0 ; N uni031F ; G 720
+U 800 ; WX 0 ; N uni0320 ; G 721
+U 801 ; WX 0 ; N uni0321 ; G 722
+U 802 ; WX 0 ; N uni0322 ; G 723
+U 803 ; WX 0 ; N dotbelowcomb ; G 724
+U 804 ; WX 0 ; N uni0324 ; G 725
+U 805 ; WX 0 ; N uni0325 ; G 726
+U 806 ; WX 0 ; N uni0326 ; G 727
+U 807 ; WX 0 ; N uni0327 ; G 728
+U 808 ; WX 0 ; N uni0328 ; G 729
+U 809 ; WX 0 ; N uni0329 ; G 730
+U 810 ; WX 0 ; N uni032A ; G 731
+U 811 ; WX 0 ; N uni032B ; G 732
+U 812 ; WX 0 ; N uni032C ; G 733
+U 813 ; WX 0 ; N uni032D ; G 734
+U 814 ; WX 0 ; N uni032E ; G 735
+U 815 ; WX 0 ; N uni032F ; G 736
+U 816 ; WX 0 ; N uni0330 ; G 737
+U 817 ; WX 0 ; N uni0331 ; G 738
+U 818 ; WX 0 ; N uni0332 ; G 739
+U 819 ; WX 0 ; N uni0333 ; G 740
+U 820 ; WX 0 ; N uni0334 ; G 741
+U 821 ; WX 0 ; N uni0335 ; G 742
+U 822 ; WX 0 ; N uni0336 ; G 743
+U 823 ; WX 0 ; N uni0337 ; G 744
+U 824 ; WX 0 ; N uni0338 ; G 745
+U 825 ; WX 0 ; N uni0339 ; G 746
+U 826 ; WX 0 ; N uni033A ; G 747
+U 827 ; WX 0 ; N uni033B ; G 748
+U 828 ; WX 0 ; N uni033C ; G 749
+U 829 ; WX 0 ; N uni033D ; G 750
+U 830 ; WX 0 ; N uni033E ; G 751
+U 831 ; WX 0 ; N uni033F ; G 752
+U 832 ; WX 0 ; N uni0340 ; G 753
+U 833 ; WX 0 ; N uni0341 ; G 754
+U 834 ; WX 0 ; N uni0342 ; G 755
+U 835 ; WX 0 ; N uni0343 ; G 756
+U 836 ; WX 0 ; N uni0344 ; G 757
+U 837 ; WX 0 ; N uni0345 ; G 758
+U 838 ; WX 0 ; N uni0346 ; G 759
+U 839 ; WX 0 ; N uni0347 ; G 760
+U 840 ; WX 0 ; N uni0348 ; G 761
+U 841 ; WX 0 ; N uni0349 ; G 762
+U 842 ; WX 0 ; N uni034A ; G 763
+U 843 ; WX 0 ; N uni034B ; G 764
+U 844 ; WX 0 ; N uni034C ; G 765
+U 845 ; WX 0 ; N uni034D ; G 766
+U 846 ; WX 0 ; N uni034E ; G 767
+U 847 ; WX 0 ; N uni034F ; G 768
+U 849 ; WX 0 ; N uni0351 ; G 769
+U 850 ; WX 0 ; N uni0352 ; G 770
+U 851 ; WX 0 ; N uni0353 ; G 771
+U 855 ; WX 0 ; N uni0357 ; G 772
+U 856 ; WX 0 ; N uni0358 ; G 773
+U 858 ; WX 0 ; N uni035A ; G 774
+U 860 ; WX 0 ; N uni035C ; G 775
+U 861 ; WX 0 ; N uni035D ; G 776
+U 862 ; WX 0 ; N uni035E ; G 777
+U 863 ; WX 0 ; N uni035F ; G 778
+U 864 ; WX 0 ; N uni0360 ; G 779
+U 865 ; WX 0 ; N uni0361 ; G 780
+U 866 ; WX 0 ; N uni0362 ; G 781
+U 880 ; WX 654 ; N uni0370 ; G 782
+U 881 ; WX 568 ; N uni0371 ; G 783
+U 882 ; WX 862 ; N uni0372 ; G 784
+U 883 ; WX 647 ; N uni0373 ; G 785
+U 884 ; WX 278 ; N uni0374 ; G 786
+U 885 ; WX 278 ; N uni0375 ; G 787
+U 886 ; WX 748 ; N uni0376 ; G 788
+U 887 ; WX 650 ; N uni0377 ; G 789
+U 890 ; WX 500 ; N uni037A ; G 790
+U 891 ; WX 549 ; N uni037B ; G 791
+U 892 ; WX 550 ; N uni037C ; G 792
+U 893 ; WX 549 ; N uni037D ; G 793
+U 894 ; WX 337 ; N uni037E ; G 794
+U 895 ; WX 295 ; N uni037F ; G 795
+U 900 ; WX 500 ; N tonos ; G 796
+U 901 ; WX 500 ; N dieresistonos ; G 797
+U 902 ; WX 692 ; N Alphatonos ; G 798
+U 903 ; WX 318 ; N anoteleia ; G 799
+U 904 ; WX 746 ; N Epsilontonos ; G 800
+U 905 ; WX 871 ; N Etatonos ; G 801
+U 906 ; WX 408 ; N Iotatonos ; G 802
+U 908 ; WX 813 ; N Omicrontonos ; G 803
+U 910 ; WX 825 ; N Upsilontonos ; G 804
+U 911 ; WX 826 ; N Omegatonos ; G 805
+U 912 ; WX 338 ; N iotadieresistonos ; G 806
+U 913 ; WX 684 ; N Alpha ; G 807
+U 914 ; WX 686 ; N Beta ; G 808
+U 915 ; WX 557 ; N Gamma ; G 809
+U 916 ; WX 684 ; N uni0394 ; G 810
+U 917 ; WX 632 ; N Epsilon ; G 811
+U 918 ; WX 685 ; N Zeta ; G 812
+U 919 ; WX 752 ; N Eta ; G 813
+U 920 ; WX 787 ; N Theta ; G 814
+U 921 ; WX 295 ; N Iota ; G 815
+U 922 ; WX 656 ; N Kappa ; G 816
+U 923 ; WX 684 ; N Lambda ; G 817
+U 924 ; WX 863 ; N Mu ; G 818
+U 925 ; WX 748 ; N Nu ; G 819
+U 926 ; WX 632 ; N Xi ; G 820
+U 927 ; WX 787 ; N Omicron ; G 821
+U 928 ; WX 752 ; N Pi ; G 822
+U 929 ; WX 603 ; N Rho ; G 823
+U 931 ; WX 632 ; N Sigma ; G 824
+U 932 ; WX 611 ; N Tau ; G 825
+U 933 ; WX 611 ; N Upsilon ; G 826
+U 934 ; WX 787 ; N Phi ; G 827
+U 935 ; WX 685 ; N Chi ; G 828
+U 936 ; WX 787 ; N Psi ; G 829
+U 937 ; WX 764 ; N Omega ; G 830
+U 938 ; WX 295 ; N Iotadieresis ; G 831
+U 939 ; WX 611 ; N Upsilondieresis ; G 832
+U 940 ; WX 659 ; N alphatonos ; G 833
+U 941 ; WX 541 ; N epsilontonos ; G 834
+U 942 ; WX 634 ; N etatonos ; G 835
+U 943 ; WX 338 ; N iotatonos ; G 836
+U 944 ; WX 579 ; N upsilondieresistonos ; G 837
+U 945 ; WX 659 ; N alpha ; G 838
+U 946 ; WX 638 ; N beta ; G 839
+U 947 ; WX 592 ; N gamma ; G 840
+U 948 ; WX 612 ; N delta ; G 841
+U 949 ; WX 541 ; N epsilon ; G 842
+U 950 ; WX 544 ; N zeta ; G 843
+U 951 ; WX 634 ; N eta ; G 844
+U 952 ; WX 612 ; N theta ; G 845
+U 953 ; WX 338 ; N iota ; G 846
+U 954 ; WX 589 ; N kappa ; G 847
+U 955 ; WX 592 ; N lambda ; G 848
+U 956 ; WX 636 ; N uni03BC ; G 849
+U 957 ; WX 559 ; N nu ; G 850
+U 958 ; WX 558 ; N xi ; G 851
+U 959 ; WX 612 ; N omicron ; G 852
+U 960 ; WX 602 ; N pi ; G 853
+U 961 ; WX 635 ; N rho ; G 854
+U 962 ; WX 587 ; N sigma1 ; G 855
+U 963 ; WX 634 ; N sigma ; G 856
+U 964 ; WX 602 ; N tau ; G 857
+U 965 ; WX 579 ; N upsilon ; G 858
+U 966 ; WX 660 ; N phi ; G 859
+U 967 ; WX 578 ; N chi ; G 860
+U 968 ; WX 660 ; N psi ; G 861
+U 969 ; WX 837 ; N omega ; G 862
+U 970 ; WX 338 ; N iotadieresis ; G 863
+U 971 ; WX 579 ; N upsilondieresis ; G 864
+U 972 ; WX 612 ; N omicrontonos ; G 865
+U 973 ; WX 579 ; N upsilontonos ; G 866
+U 974 ; WX 837 ; N omegatonos ; G 867
+U 975 ; WX 656 ; N uni03CF ; G 868
+U 976 ; WX 614 ; N uni03D0 ; G 869
+U 977 ; WX 619 ; N theta1 ; G 870
+U 978 ; WX 699 ; N Upsilon1 ; G 871
+U 979 ; WX 842 ; N uni03D3 ; G 872
+U 980 ; WX 699 ; N uni03D4 ; G 873
+U 981 ; WX 660 ; N phi1 ; G 874
+U 982 ; WX 837 ; N omega1 ; G 875
+U 983 ; WX 664 ; N uni03D7 ; G 876
+U 984 ; WX 787 ; N uni03D8 ; G 877
+U 985 ; WX 612 ; N uni03D9 ; G 878
+U 986 ; WX 648 ; N uni03DA ; G 879
+U 987 ; WX 587 ; N uni03DB ; G 880
+U 988 ; WX 575 ; N uni03DC ; G 881
+U 989 ; WX 458 ; N uni03DD ; G 882
+U 990 ; WX 660 ; N uni03DE ; G 883
+U 991 ; WX 660 ; N uni03DF ; G 884
+U 992 ; WX 865 ; N uni03E0 ; G 885
+U 993 ; WX 627 ; N uni03E1 ; G 886
+U 994 ; WX 934 ; N uni03E2 ; G 887
+U 995 ; WX 837 ; N uni03E3 ; G 888
+U 996 ; WX 758 ; N uni03E4 ; G 889
+U 997 ; WX 659 ; N uni03E5 ; G 890
+U 998 ; WX 792 ; N uni03E6 ; G 891
+U 999 ; WX 615 ; N uni03E7 ; G 892
+U 1000 ; WX 687 ; N uni03E8 ; G 893
+U 1001 ; WX 607 ; N uni03E9 ; G 894
+U 1002 ; WX 768 ; N uni03EA ; G 895
+U 1003 ; WX 625 ; N uni03EB ; G 896
+U 1004 ; WX 699 ; N uni03EC ; G 897
+U 1005 ; WX 612 ; N uni03ED ; G 898
+U 1006 ; WX 611 ; N uni03EE ; G 899
+U 1007 ; WX 536 ; N uni03EF ; G 900
+U 1008 ; WX 664 ; N uni03F0 ; G 901
+U 1009 ; WX 635 ; N uni03F1 ; G 902
+U 1010 ; WX 550 ; N uni03F2 ; G 903
+U 1011 ; WX 278 ; N uni03F3 ; G 904
+U 1012 ; WX 787 ; N uni03F4 ; G 905
+U 1013 ; WX 615 ; N uni03F5 ; G 906
+U 1014 ; WX 615 ; N uni03F6 ; G 907
+U 1015 ; WX 605 ; N uni03F7 ; G 908
+U 1016 ; WX 635 ; N uni03F8 ; G 909
+U 1017 ; WX 698 ; N uni03F9 ; G 910
+U 1018 ; WX 863 ; N uni03FA ; G 911
+U 1019 ; WX 651 ; N uni03FB ; G 912
+U 1020 ; WX 635 ; N uni03FC ; G 913
+U 1021 ; WX 703 ; N uni03FD ; G 914
+U 1022 ; WX 698 ; N uni03FE ; G 915
+U 1023 ; WX 703 ; N uni03FF ; G 916
+U 1024 ; WX 632 ; N uni0400 ; G 917
+U 1025 ; WX 632 ; N uni0401 ; G 918
+U 1026 ; WX 786 ; N uni0402 ; G 919
+U 1027 ; WX 610 ; N uni0403 ; G 920
+U 1028 ; WX 698 ; N uni0404 ; G 921
+U 1029 ; WX 635 ; N uni0405 ; G 922
+U 1030 ; WX 295 ; N uni0406 ; G 923
+U 1031 ; WX 295 ; N uni0407 ; G 924
+U 1032 ; WX 295 ; N uni0408 ; G 925
+U 1033 ; WX 1094 ; N uni0409 ; G 926
+U 1034 ; WX 1045 ; N uni040A ; G 927
+U 1035 ; WX 786 ; N uni040B ; G 928
+U 1036 ; WX 710 ; N uni040C ; G 929
+U 1037 ; WX 748 ; N uni040D ; G 930
+U 1038 ; WX 609 ; N uni040E ; G 931
+U 1039 ; WX 752 ; N uni040F ; G 932
+U 1040 ; WX 684 ; N uni0410 ; G 933
+U 1041 ; WX 686 ; N uni0411 ; G 934
+U 1042 ; WX 686 ; N uni0412 ; G 935
+U 1043 ; WX 610 ; N uni0413 ; G 936
+U 1044 ; WX 781 ; N uni0414 ; G 937
+U 1045 ; WX 632 ; N uni0415 ; G 938
+U 1046 ; WX 1077 ; N uni0416 ; G 939
+U 1047 ; WX 641 ; N uni0417 ; G 940
+U 1048 ; WX 748 ; N uni0418 ; G 941
+U 1049 ; WX 748 ; N uni0419 ; G 942
+U 1050 ; WX 710 ; N uni041A ; G 943
+U 1051 ; WX 752 ; N uni041B ; G 944
+U 1052 ; WX 863 ; N uni041C ; G 945
+U 1053 ; WX 752 ; N uni041D ; G 946
+U 1054 ; WX 787 ; N uni041E ; G 947
+U 1055 ; WX 752 ; N uni041F ; G 948
+U 1056 ; WX 603 ; N uni0420 ; G 949
+U 1057 ; WX 698 ; N uni0421 ; G 950
+U 1058 ; WX 611 ; N uni0422 ; G 951
+U 1059 ; WX 609 ; N uni0423 ; G 952
+U 1060 ; WX 861 ; N uni0424 ; G 953
+U 1061 ; WX 685 ; N uni0425 ; G 954
+U 1062 ; WX 776 ; N uni0426 ; G 955
+U 1063 ; WX 686 ; N uni0427 ; G 956
+U 1064 ; WX 1069 ; N uni0428 ; G 957
+U 1065 ; WX 1094 ; N uni0429 ; G 958
+U 1066 ; WX 833 ; N uni042A ; G 959
+U 1067 ; WX 882 ; N uni042B ; G 960
+U 1068 ; WX 686 ; N uni042C ; G 961
+U 1069 ; WX 698 ; N uni042D ; G 962
+U 1070 ; WX 1080 ; N uni042E ; G 963
+U 1071 ; WX 695 ; N uni042F ; G 964
+U 1072 ; WX 613 ; N uni0430 ; G 965
+U 1073 ; WX 617 ; N uni0431 ; G 966
+U 1074 ; WX 589 ; N uni0432 ; G 967
+U 1075 ; WX 525 ; N uni0433 ; G 968
+U 1076 ; WX 691 ; N uni0434 ; G 969
+U 1077 ; WX 615 ; N uni0435 ; G 970
+U 1078 ; WX 901 ; N uni0436 ; G 971
+U 1079 ; WX 532 ; N uni0437 ; G 972
+U 1080 ; WX 650 ; N uni0438 ; G 973
+U 1081 ; WX 650 ; N uni0439 ; G 974
+U 1082 ; WX 604 ; N uni043A ; G 975
+U 1083 ; WX 639 ; N uni043B ; G 976
+U 1084 ; WX 754 ; N uni043C ; G 977
+U 1085 ; WX 654 ; N uni043D ; G 978
+U 1086 ; WX 612 ; N uni043E ; G 979
+U 1087 ; WX 654 ; N uni043F ; G 980
+U 1088 ; WX 635 ; N uni0440 ; G 981
+U 1089 ; WX 550 ; N uni0441 ; G 982
+U 1090 ; WX 583 ; N uni0442 ; G 983
+U 1091 ; WX 592 ; N uni0443 ; G 984
+U 1092 ; WX 855 ; N uni0444 ; G 985
+U 1093 ; WX 592 ; N uni0445 ; G 986
+U 1094 ; WX 681 ; N uni0446 ; G 987
+U 1095 ; WX 591 ; N uni0447 ; G 988
+U 1096 ; WX 915 ; N uni0448 ; G 989
+U 1097 ; WX 942 ; N uni0449 ; G 990
+U 1098 ; WX 707 ; N uni044A ; G 991
+U 1099 ; WX 790 ; N uni044B ; G 992
+U 1100 ; WX 589 ; N uni044C ; G 993
+U 1101 ; WX 549 ; N uni044D ; G 994
+U 1102 ; WX 842 ; N uni044E ; G 995
+U 1103 ; WX 602 ; N uni044F ; G 996
+U 1104 ; WX 615 ; N uni0450 ; G 997
+U 1105 ; WX 615 ; N uni0451 ; G 998
+U 1106 ; WX 625 ; N uni0452 ; G 999
+U 1107 ; WX 525 ; N uni0453 ; G 1000
+U 1108 ; WX 549 ; N uni0454 ; G 1001
+U 1109 ; WX 521 ; N uni0455 ; G 1002
+U 1110 ; WX 278 ; N uni0456 ; G 1003
+U 1111 ; WX 278 ; N uni0457 ; G 1004
+U 1112 ; WX 278 ; N uni0458 ; G 1005
+U 1113 ; WX 902 ; N uni0459 ; G 1006
+U 1114 ; WX 898 ; N uni045A ; G 1007
+U 1115 ; WX 652 ; N uni045B ; G 1008
+U 1116 ; WX 604 ; N uni045C ; G 1009
+U 1117 ; WX 650 ; N uni045D ; G 1010
+U 1118 ; WX 592 ; N uni045E ; G 1011
+U 1119 ; WX 654 ; N uni045F ; G 1012
+U 1120 ; WX 934 ; N uni0460 ; G 1013
+U 1121 ; WX 837 ; N uni0461 ; G 1014
+U 1122 ; WX 771 ; N uni0462 ; G 1015
+U 1123 ; WX 672 ; N uni0463 ; G 1016
+U 1124 ; WX 942 ; N uni0464 ; G 1017
+U 1125 ; WX 749 ; N uni0465 ; G 1018
+U 1126 ; WX 879 ; N uni0466 ; G 1019
+U 1127 ; WX 783 ; N uni0467 ; G 1020
+U 1128 ; WX 1160 ; N uni0468 ; G 1021
+U 1129 ; WX 1001 ; N uni0469 ; G 1022
+U 1130 ; WX 787 ; N uni046A ; G 1023
+U 1131 ; WX 612 ; N uni046B ; G 1024
+U 1132 ; WX 1027 ; N uni046C ; G 1025
+U 1133 ; WX 824 ; N uni046D ; G 1026
+U 1134 ; WX 636 ; N uni046E ; G 1027
+U 1135 ; WX 541 ; N uni046F ; G 1028
+U 1136 ; WX 856 ; N uni0470 ; G 1029
+U 1137 ; WX 876 ; N uni0471 ; G 1030
+U 1138 ; WX 787 ; N uni0472 ; G 1031
+U 1139 ; WX 612 ; N uni0473 ; G 1032
+U 1140 ; WX 781 ; N uni0474 ; G 1033
+U 1141 ; WX 665 ; N uni0475 ; G 1034
+U 1142 ; WX 781 ; N uni0476 ; G 1035
+U 1143 ; WX 665 ; N uni0477 ; G 1036
+U 1144 ; WX 992 ; N uni0478 ; G 1037
+U 1145 ; WX 904 ; N uni0479 ; G 1038
+U 1146 ; WX 953 ; N uni047A ; G 1039
+U 1147 ; WX 758 ; N uni047B ; G 1040
+U 1148 ; WX 1180 ; N uni047C ; G 1041
+U 1149 ; WX 1028 ; N uni047D ; G 1042
+U 1150 ; WX 934 ; N uni047E ; G 1043
+U 1151 ; WX 837 ; N uni047F ; G 1044
+U 1152 ; WX 698 ; N uni0480 ; G 1045
+U 1153 ; WX 550 ; N uni0481 ; G 1046
+U 1154 ; WX 502 ; N uni0482 ; G 1047
+U 1155 ; WX 0 ; N uni0483 ; G 1048
+U 1156 ; WX 0 ; N uni0484 ; G 1049
+U 1157 ; WX 0 ; N uni0485 ; G 1050
+U 1158 ; WX 0 ; N uni0486 ; G 1051
+U 1159 ; WX 0 ; N uni0487 ; G 1052
+U 1160 ; WX 418 ; N uni0488 ; G 1053
+U 1161 ; WX 418 ; N uni0489 ; G 1054
+U 1162 ; WX 772 ; N uni048A ; G 1055
+U 1163 ; WX 677 ; N uni048B ; G 1056
+U 1164 ; WX 686 ; N uni048C ; G 1057
+U 1165 ; WX 589 ; N uni048D ; G 1058
+U 1166 ; WX 603 ; N uni048E ; G 1059
+U 1167 ; WX 635 ; N uni048F ; G 1060
+U 1168 ; WX 610 ; N uni0490 ; G 1061
+U 1169 ; WX 525 ; N uni0491 ; G 1062
+U 1170 ; WX 675 ; N uni0492 ; G 1063
+U 1171 ; WX 590 ; N uni0493 ; G 1064
+U 1172 ; WX 624 ; N uni0494 ; G 1065
+U 1173 ; WX 530 ; N uni0495 ; G 1066
+U 1174 ; WX 1077 ; N uni0496 ; G 1067
+U 1175 ; WX 901 ; N uni0497 ; G 1068
+U 1176 ; WX 641 ; N uni0498 ; G 1069
+U 1177 ; WX 532 ; N uni0499 ; G 1070
+U 1178 ; WX 710 ; N uni049A ; G 1071
+U 1179 ; WX 604 ; N uni049B ; G 1072
+U 1180 ; WX 710 ; N uni049C ; G 1073
+U 1181 ; WX 604 ; N uni049D ; G 1074
+U 1182 ; WX 710 ; N uni049E ; G 1075
+U 1183 ; WX 604 ; N uni049F ; G 1076
+U 1184 ; WX 856 ; N uni04A0 ; G 1077
+U 1185 ; WX 832 ; N uni04A1 ; G 1078
+U 1186 ; WX 752 ; N uni04A2 ; G 1079
+U 1187 ; WX 661 ; N uni04A3 ; G 1080
+U 1188 ; WX 1014 ; N uni04A4 ; G 1081
+U 1189 ; WX 877 ; N uni04A5 ; G 1082
+U 1190 ; WX 1081 ; N uni04A6 ; G 1083
+U 1191 ; WX 916 ; N uni04A7 ; G 1084
+U 1192 ; WX 878 ; N uni04A8 ; G 1085
+U 1193 ; WX 693 ; N uni04A9 ; G 1086
+U 1194 ; WX 698 ; N uni04AA ; G 1087
+U 1195 ; WX 550 ; N uni04AB ; G 1088
+U 1196 ; WX 611 ; N uni04AC ; G 1089
+U 1197 ; WX 583 ; N uni04AD ; G 1090
+U 1198 ; WX 611 ; N uni04AE ; G 1091
+U 1199 ; WX 592 ; N uni04AF ; G 1092
+U 1200 ; WX 611 ; N uni04B0 ; G 1093
+U 1201 ; WX 592 ; N uni04B1 ; G 1094
+U 1202 ; WX 685 ; N uni04B2 ; G 1095
+U 1203 ; WX 592 ; N uni04B3 ; G 1096
+U 1204 ; WX 934 ; N uni04B4 ; G 1097
+U 1205 ; WX 807 ; N uni04B5 ; G 1098
+U 1206 ; WX 686 ; N uni04B6 ; G 1099
+U 1207 ; WX 591 ; N uni04B7 ; G 1100
+U 1208 ; WX 686 ; N uni04B8 ; G 1101
+U 1209 ; WX 591 ; N uni04B9 ; G 1102
+U 1210 ; WX 686 ; N uni04BA ; G 1103
+U 1211 ; WX 634 ; N uni04BB ; G 1104
+U 1212 ; WX 941 ; N uni04BC ; G 1105
+U 1213 ; WX 728 ; N uni04BD ; G 1106
+U 1214 ; WX 941 ; N uni04BE ; G 1107
+U 1215 ; WX 728 ; N uni04BF ; G 1108
+U 1216 ; WX 295 ; N uni04C0 ; G 1109
+U 1217 ; WX 1077 ; N uni04C1 ; G 1110
+U 1218 ; WX 901 ; N uni04C2 ; G 1111
+U 1219 ; WX 656 ; N uni04C3 ; G 1112
+U 1220 ; WX 604 ; N uni04C4 ; G 1113
+U 1221 ; WX 776 ; N uni04C5 ; G 1114
+U 1222 ; WX 670 ; N uni04C6 ; G 1115
+U 1223 ; WX 752 ; N uni04C7 ; G 1116
+U 1224 ; WX 661 ; N uni04C8 ; G 1117
+U 1225 ; WX 776 ; N uni04C9 ; G 1118
+U 1226 ; WX 681 ; N uni04CA ; G 1119
+U 1227 ; WX 686 ; N uni04CB ; G 1120
+U 1228 ; WX 591 ; N uni04CC ; G 1121
+U 1229 ; WX 888 ; N uni04CD ; G 1122
+U 1230 ; WX 774 ; N uni04CE ; G 1123
+U 1231 ; WX 278 ; N uni04CF ; G 1124
+U 1232 ; WX 684 ; N uni04D0 ; G 1125
+U 1233 ; WX 613 ; N uni04D1 ; G 1126
+U 1234 ; WX 684 ; N uni04D2 ; G 1127
+U 1235 ; WX 613 ; N uni04D3 ; G 1128
+U 1236 ; WX 974 ; N uni04D4 ; G 1129
+U 1237 ; WX 982 ; N uni04D5 ; G 1130
+U 1238 ; WX 632 ; N uni04D6 ; G 1131
+U 1239 ; WX 615 ; N uni04D7 ; G 1132
+U 1240 ; WX 787 ; N uni04D8 ; G 1133
+U 1241 ; WX 615 ; N uni04D9 ; G 1134
+U 1242 ; WX 787 ; N uni04DA ; G 1135
+U 1243 ; WX 615 ; N uni04DB ; G 1136
+U 1244 ; WX 1077 ; N uni04DC ; G 1137
+U 1245 ; WX 901 ; N uni04DD ; G 1138
+U 1246 ; WX 641 ; N uni04DE ; G 1139
+U 1247 ; WX 532 ; N uni04DF ; G 1140
+U 1248 ; WX 666 ; N uni04E0 ; G 1141
+U 1249 ; WX 578 ; N uni04E1 ; G 1142
+U 1250 ; WX 748 ; N uni04E2 ; G 1143
+U 1251 ; WX 650 ; N uni04E3 ; G 1144
+U 1252 ; WX 748 ; N uni04E4 ; G 1145
+U 1253 ; WX 650 ; N uni04E5 ; G 1146
+U 1254 ; WX 787 ; N uni04E6 ; G 1147
+U 1255 ; WX 612 ; N uni04E7 ; G 1148
+U 1256 ; WX 787 ; N uni04E8 ; G 1149
+U 1257 ; WX 612 ; N uni04E9 ; G 1150
+U 1258 ; WX 787 ; N uni04EA ; G 1151
+U 1259 ; WX 612 ; N uni04EB ; G 1152
+U 1260 ; WX 698 ; N uni04EC ; G 1153
+U 1261 ; WX 549 ; N uni04ED ; G 1154
+U 1262 ; WX 609 ; N uni04EE ; G 1155
+U 1263 ; WX 592 ; N uni04EF ; G 1156
+U 1264 ; WX 609 ; N uni04F0 ; G 1157
+U 1265 ; WX 592 ; N uni04F1 ; G 1158
+U 1266 ; WX 609 ; N uni04F2 ; G 1159
+U 1267 ; WX 592 ; N uni04F3 ; G 1160
+U 1268 ; WX 686 ; N uni04F4 ; G 1161
+U 1269 ; WX 591 ; N uni04F5 ; G 1162
+U 1270 ; WX 610 ; N uni04F6 ; G 1163
+U 1271 ; WX 525 ; N uni04F7 ; G 1164
+U 1272 ; WX 882 ; N uni04F8 ; G 1165
+U 1273 ; WX 790 ; N uni04F9 ; G 1166
+U 1274 ; WX 675 ; N uni04FA ; G 1167
+U 1275 ; WX 590 ; N uni04FB ; G 1168
+U 1276 ; WX 685 ; N uni04FC ; G 1169
+U 1277 ; WX 592 ; N uni04FD ; G 1170
+U 1278 ; WX 685 ; N uni04FE ; G 1171
+U 1279 ; WX 592 ; N uni04FF ; G 1172
+U 1280 ; WX 686 ; N uni0500 ; G 1173
+U 1281 ; WX 589 ; N uni0501 ; G 1174
+U 1282 ; WX 1006 ; N uni0502 ; G 1175
+U 1283 ; WX 897 ; N uni0503 ; G 1176
+U 1284 ; WX 975 ; N uni0504 ; G 1177
+U 1285 ; WX 869 ; N uni0505 ; G 1178
+U 1286 ; WX 679 ; N uni0506 ; G 1179
+U 1287 ; WX 588 ; N uni0507 ; G 1180
+U 1288 ; WX 1072 ; N uni0508 ; G 1181
+U 1289 ; WX 957 ; N uni0509 ; G 1182
+U 1290 ; WX 1113 ; N uni050A ; G 1183
+U 1291 ; WX 967 ; N uni050B ; G 1184
+U 1292 ; WX 775 ; N uni050C ; G 1185
+U 1293 ; WX 660 ; N uni050D ; G 1186
+U 1294 ; WX 773 ; N uni050E ; G 1187
+U 1295 ; WX 711 ; N uni050F ; G 1188
+U 1296 ; WX 614 ; N uni0510 ; G 1189
+U 1297 ; WX 541 ; N uni0511 ; G 1190
+U 1298 ; WX 752 ; N uni0512 ; G 1191
+U 1299 ; WX 639 ; N uni0513 ; G 1192
+U 1300 ; WX 1169 ; N uni0514 ; G 1193
+U 1301 ; WX 994 ; N uni0515 ; G 1194
+U 1302 ; WX 894 ; N uni0516 ; G 1195
+U 1303 ; WX 864 ; N uni0517 ; G 1196
+U 1304 ; WX 1032 ; N uni0518 ; G 1197
+U 1305 ; WX 986 ; N uni0519 ; G 1198
+U 1306 ; WX 787 ; N uni051A ; G 1199
+U 1307 ; WX 635 ; N uni051B ; G 1200
+U 1308 ; WX 989 ; N uni051C ; G 1201
+U 1309 ; WX 818 ; N uni051D ; G 1202
+U 1310 ; WX 710 ; N uni051E ; G 1203
+U 1311 ; WX 604 ; N uni051F ; G 1204
+U 1312 ; WX 1081 ; N uni0520 ; G 1205
+U 1313 ; WX 905 ; N uni0521 ; G 1206
+U 1314 ; WX 1081 ; N uni0522 ; G 1207
+U 1315 ; WX 912 ; N uni0523 ; G 1208
+U 1316 ; WX 793 ; N uni0524 ; G 1209
+U 1317 ; WX 683 ; N uni0525 ; G 1210
+U 1329 ; WX 766 ; N uni0531 ; G 1211
+U 1330 ; WX 732 ; N uni0532 ; G 1212
+U 1331 ; WX 753 ; N uni0533 ; G 1213
+U 1332 ; WX 753 ; N uni0534 ; G 1214
+U 1333 ; WX 732 ; N uni0535 ; G 1215
+U 1334 ; WX 772 ; N uni0536 ; G 1216
+U 1335 ; WX 640 ; N uni0537 ; G 1217
+U 1336 ; WX 732 ; N uni0538 ; G 1218
+U 1337 ; WX 859 ; N uni0539 ; G 1219
+U 1338 ; WX 753 ; N uni053A ; G 1220
+U 1339 ; WX 691 ; N uni053B ; G 1221
+U 1340 ; WX 533 ; N uni053C ; G 1222
+U 1341 ; WX 922 ; N uni053D ; G 1223
+U 1342 ; WX 863 ; N uni053E ; G 1224
+U 1343 ; WX 732 ; N uni053F ; G 1225
+U 1344 ; WX 716 ; N uni0540 ; G 1226
+U 1345 ; WX 766 ; N uni0541 ; G 1227
+U 1346 ; WX 753 ; N uni0542 ; G 1228
+U 1347 ; WX 767 ; N uni0543 ; G 1229
+U 1348 ; WX 792 ; N uni0544 ; G 1230
+U 1349 ; WX 728 ; N uni0545 ; G 1231
+U 1350 ; WX 729 ; N uni0546 ; G 1232
+U 1351 ; WX 757 ; N uni0547 ; G 1233
+U 1352 ; WX 732 ; N uni0548 ; G 1234
+U 1353 ; WX 713 ; N uni0549 ; G 1235
+U 1354 ; WX 800 ; N uni054A ; G 1236
+U 1355 ; WX 768 ; N uni054B ; G 1237
+U 1356 ; WX 792 ; N uni054C ; G 1238
+U 1357 ; WX 732 ; N uni054D ; G 1239
+U 1358 ; WX 753 ; N uni054E ; G 1240
+U 1359 ; WX 705 ; N uni054F ; G 1241
+U 1360 ; WX 694 ; N uni0550 ; G 1242
+U 1361 ; WX 744 ; N uni0551 ; G 1243
+U 1362 ; WX 538 ; N uni0552 ; G 1244
+U 1363 ; WX 811 ; N uni0553 ; G 1245
+U 1364 ; WX 757 ; N uni0554 ; G 1246
+U 1365 ; WX 787 ; N uni0555 ; G 1247
+U 1366 ; WX 790 ; N uni0556 ; G 1248
+U 1369 ; WX 307 ; N uni0559 ; G 1249
+U 1370 ; WX 318 ; N uni055A ; G 1250
+U 1371 ; WX 234 ; N uni055B ; G 1251
+U 1372 ; WX 361 ; N uni055C ; G 1252
+U 1373 ; WX 238 ; N uni055D ; G 1253
+U 1374 ; WX 405 ; N uni055E ; G 1254
+U 1375 ; WX 500 ; N uni055F ; G 1255
+U 1377 ; WX 974 ; N uni0561 ; G 1256
+U 1378 ; WX 634 ; N uni0562 ; G 1257
+U 1379 ; WX 658 ; N uni0563 ; G 1258
+U 1380 ; WX 663 ; N uni0564 ; G 1259
+U 1381 ; WX 634 ; N uni0565 ; G 1260
+U 1382 ; WX 635 ; N uni0566 ; G 1261
+U 1383 ; WX 515 ; N uni0567 ; G 1262
+U 1384 ; WX 634 ; N uni0568 ; G 1263
+U 1385 ; WX 738 ; N uni0569 ; G 1264
+U 1386 ; WX 658 ; N uni056A ; G 1265
+U 1387 ; WX 634 ; N uni056B ; G 1266
+U 1388 ; WX 271 ; N uni056C ; G 1267
+U 1389 ; WX 980 ; N uni056D ; G 1268
+U 1390 ; WX 623 ; N uni056E ; G 1269
+U 1391 ; WX 634 ; N uni056F ; G 1270
+U 1392 ; WX 634 ; N uni0570 ; G 1271
+U 1393 ; WX 608 ; N uni0571 ; G 1272
+U 1394 ; WX 634 ; N uni0572 ; G 1273
+U 1395 ; WX 629 ; N uni0573 ; G 1274
+U 1396 ; WX 634 ; N uni0574 ; G 1275
+U 1397 ; WX 271 ; N uni0575 ; G 1276
+U 1398 ; WX 634 ; N uni0576 ; G 1277
+U 1399 ; WX 499 ; N uni0577 ; G 1278
+U 1400 ; WX 634 ; N uni0578 ; G 1279
+U 1401 ; WX 404 ; N uni0579 ; G 1280
+U 1402 ; WX 974 ; N uni057A ; G 1281
+U 1403 ; WX 560 ; N uni057B ; G 1282
+U 1404 ; WX 648 ; N uni057C ; G 1283
+U 1405 ; WX 634 ; N uni057D ; G 1284
+U 1406 ; WX 634 ; N uni057E ; G 1285
+U 1407 ; WX 974 ; N uni057F ; G 1286
+U 1408 ; WX 634 ; N uni0580 ; G 1287
+U 1409 ; WX 633 ; N uni0581 ; G 1288
+U 1410 ; WX 435 ; N uni0582 ; G 1289
+U 1411 ; WX 974 ; N uni0583 ; G 1290
+U 1412 ; WX 636 ; N uni0584 ; G 1291
+U 1413 ; WX 609 ; N uni0585 ; G 1292
+U 1414 ; WX 805 ; N uni0586 ; G 1293
+U 1415 ; WX 812 ; N uni0587 ; G 1294
+U 1417 ; WX 337 ; N uni0589 ; G 1295
+U 1418 ; WX 361 ; N uni058A ; G 1296
+U 1456 ; WX 0 ; N uni05B0 ; G 1297
+U 1457 ; WX 0 ; N uni05B1 ; G 1298
+U 1458 ; WX 0 ; N uni05B2 ; G 1299
+U 1459 ; WX 0 ; N uni05B3 ; G 1300
+U 1460 ; WX 0 ; N uni05B4 ; G 1301
+U 1461 ; WX 0 ; N uni05B5 ; G 1302
+U 1462 ; WX 0 ; N uni05B6 ; G 1303
+U 1463 ; WX 0 ; N uni05B7 ; G 1304
+U 1464 ; WX 0 ; N uni05B8 ; G 1305
+U 1465 ; WX 0 ; N uni05B9 ; G 1306
+U 1466 ; WX 0 ; N uni05BA ; G 1307
+U 1467 ; WX 0 ; N uni05BB ; G 1308
+U 1468 ; WX 0 ; N uni05BC ; G 1309
+U 1469 ; WX 0 ; N uni05BD ; G 1310
+U 1470 ; WX 361 ; N uni05BE ; G 1311
+U 1471 ; WX 0 ; N uni05BF ; G 1312
+U 1472 ; WX 295 ; N uni05C0 ; G 1313
+U 1473 ; WX 0 ; N uni05C1 ; G 1314
+U 1474 ; WX 0 ; N uni05C2 ; G 1315
+U 1475 ; WX 295 ; N uni05C3 ; G 1316
+U 1478 ; WX 441 ; N uni05C6 ; G 1317
+U 1479 ; WX 0 ; N uni05C7 ; G 1318
+U 1488 ; WX 668 ; N uni05D0 ; G 1319
+U 1489 ; WX 578 ; N uni05D1 ; G 1320
+U 1490 ; WX 412 ; N uni05D2 ; G 1321
+U 1491 ; WX 546 ; N uni05D3 ; G 1322
+U 1492 ; WX 653 ; N uni05D4 ; G 1323
+U 1493 ; WX 272 ; N uni05D5 ; G 1324
+U 1494 ; WX 346 ; N uni05D6 ; G 1325
+U 1495 ; WX 653 ; N uni05D7 ; G 1326
+U 1496 ; WX 648 ; N uni05D8 ; G 1327
+U 1497 ; WX 224 ; N uni05D9 ; G 1328
+U 1498 ; WX 537 ; N uni05DA ; G 1329
+U 1499 ; WX 529 ; N uni05DB ; G 1330
+U 1500 ; WX 568 ; N uni05DC ; G 1331
+U 1501 ; WX 664 ; N uni05DD ; G 1332
+U 1502 ; WX 679 ; N uni05DE ; G 1333
+U 1503 ; WX 272 ; N uni05DF ; G 1334
+U 1504 ; WX 400 ; N uni05E0 ; G 1335
+U 1505 ; WX 649 ; N uni05E1 ; G 1336
+U 1506 ; WX 626 ; N uni05E2 ; G 1337
+U 1507 ; WX 640 ; N uni05E3 ; G 1338
+U 1508 ; WX 625 ; N uni05E4 ; G 1339
+U 1509 ; WX 540 ; N uni05E5 ; G 1340
+U 1510 ; WX 593 ; N uni05E6 ; G 1341
+U 1511 ; WX 709 ; N uni05E7 ; G 1342
+U 1512 ; WX 564 ; N uni05E8 ; G 1343
+U 1513 ; WX 708 ; N uni05E9 ; G 1344
+U 1514 ; WX 657 ; N uni05EA ; G 1345
+U 1520 ; WX 471 ; N uni05F0 ; G 1346
+U 1521 ; WX 423 ; N uni05F1 ; G 1347
+U 1522 ; WX 331 ; N uni05F2 ; G 1348
+U 1523 ; WX 416 ; N uni05F3 ; G 1349
+U 1524 ; WX 645 ; N uni05F4 ; G 1350
+U 1542 ; WX 637 ; N uni0606 ; G 1351
+U 1543 ; WX 637 ; N uni0607 ; G 1352
+U 1545 ; WX 757 ; N uni0609 ; G 1353
+U 1546 ; WX 977 ; N uni060A ; G 1354
+U 1548 ; WX 323 ; N uni060C ; G 1355
+U 1557 ; WX 0 ; N uni0615 ; G 1356
+U 1563 ; WX 318 ; N uni061B ; G 1357
+U 1567 ; WX 531 ; N uni061F ; G 1358
+U 1569 ; WX 470 ; N uni0621 ; G 1359
+U 1570 ; WX 278 ; N uni0622 ; G 1360
+U 1571 ; WX 278 ; N uni0623 ; G 1361
+U 1572 ; WX 483 ; N uni0624 ; G 1362
+U 1573 ; WX 278 ; N uni0625 ; G 1363
+U 1574 ; WX 783 ; N uni0626 ; G 1364
+U 1575 ; WX 278 ; N uni0627 ; G 1365
+U 1576 ; WX 941 ; N uni0628 ; G 1366
+U 1577 ; WX 524 ; N uni0629 ; G 1367
+U 1578 ; WX 941 ; N uni062A ; G 1368
+U 1579 ; WX 941 ; N uni062B ; G 1369
+U 1580 ; WX 646 ; N uni062C ; G 1370
+U 1581 ; WX 646 ; N uni062D ; G 1371
+U 1582 ; WX 646 ; N uni062E ; G 1372
+U 1583 ; WX 445 ; N uni062F ; G 1373
+U 1584 ; WX 445 ; N uni0630 ; G 1374
+U 1585 ; WX 483 ; N uni0631 ; G 1375
+U 1586 ; WX 483 ; N uni0632 ; G 1376
+U 1587 ; WX 1221 ; N uni0633 ; G 1377
+U 1588 ; WX 1221 ; N uni0634 ; G 1378
+U 1589 ; WX 1209 ; N uni0635 ; G 1379
+U 1590 ; WX 1209 ; N uni0636 ; G 1380
+U 1591 ; WX 925 ; N uni0637 ; G 1381
+U 1592 ; WX 925 ; N uni0638 ; G 1382
+U 1593 ; WX 597 ; N uni0639 ; G 1383
+U 1594 ; WX 597 ; N uni063A ; G 1384
+U 1600 ; WX 293 ; N uni0640 ; G 1385
+U 1601 ; WX 1037 ; N uni0641 ; G 1386
+U 1602 ; WX 776 ; N uni0642 ; G 1387
+U 1603 ; WX 824 ; N uni0643 ; G 1388
+U 1604 ; WX 727 ; N uni0644 ; G 1389
+U 1605 ; WX 619 ; N uni0645 ; G 1390
+U 1606 ; WX 734 ; N uni0646 ; G 1391
+U 1607 ; WX 524 ; N uni0647 ; G 1392
+U 1608 ; WX 483 ; N uni0648 ; G 1393
+U 1609 ; WX 783 ; N uni0649 ; G 1394
+U 1610 ; WX 783 ; N uni064A ; G 1395
+U 1611 ; WX 0 ; N uni064B ; G 1396
+U 1612 ; WX 0 ; N uni064C ; G 1397
+U 1613 ; WX 0 ; N uni064D ; G 1398
+U 1614 ; WX 0 ; N uni064E ; G 1399
+U 1615 ; WX 0 ; N uni064F ; G 1400
+U 1616 ; WX 0 ; N uni0650 ; G 1401
+U 1617 ; WX 0 ; N uni0651 ; G 1402
+U 1618 ; WX 0 ; N uni0652 ; G 1403
+U 1619 ; WX 0 ; N uni0653 ; G 1404
+U 1620 ; WX 0 ; N uni0654 ; G 1405
+U 1621 ; WX 0 ; N uni0655 ; G 1406
+U 1623 ; WX 0 ; N uni0657 ; G 1407
+U 1626 ; WX 500 ; N uni065A ; G 1408
+U 1632 ; WX 537 ; N uni0660 ; G 1409
+U 1633 ; WX 537 ; N uni0661 ; G 1410
+U 1634 ; WX 537 ; N uni0662 ; G 1411
+U 1635 ; WX 537 ; N uni0663 ; G 1412
+U 1636 ; WX 537 ; N uni0664 ; G 1413
+U 1637 ; WX 537 ; N uni0665 ; G 1414
+U 1638 ; WX 537 ; N uni0666 ; G 1415
+U 1639 ; WX 537 ; N uni0667 ; G 1416
+U 1640 ; WX 537 ; N uni0668 ; G 1417
+U 1641 ; WX 537 ; N uni0669 ; G 1418
+U 1642 ; WX 537 ; N uni066A ; G 1419
+U 1643 ; WX 325 ; N uni066B ; G 1420
+U 1644 ; WX 318 ; N uni066C ; G 1421
+U 1645 ; WX 545 ; N uni066D ; G 1422
+U 1646 ; WX 941 ; N uni066E ; G 1423
+U 1647 ; WX 776 ; N uni066F ; G 1424
+U 1648 ; WX 0 ; N uni0670 ; G 1425
+U 1652 ; WX 292 ; N uni0674 ; G 1426
+U 1657 ; WX 941 ; N uni0679 ; G 1427
+U 1658 ; WX 941 ; N uni067A ; G 1428
+U 1659 ; WX 941 ; N uni067B ; G 1429
+U 1660 ; WX 941 ; N uni067C ; G 1430
+U 1661 ; WX 941 ; N uni067D ; G 1431
+U 1662 ; WX 941 ; N uni067E ; G 1432
+U 1663 ; WX 941 ; N uni067F ; G 1433
+U 1664 ; WX 941 ; N uni0680 ; G 1434
+U 1665 ; WX 646 ; N uni0681 ; G 1435
+U 1666 ; WX 646 ; N uni0682 ; G 1436
+U 1667 ; WX 646 ; N uni0683 ; G 1437
+U 1668 ; WX 646 ; N uni0684 ; G 1438
+U 1669 ; WX 646 ; N uni0685 ; G 1439
+U 1670 ; WX 646 ; N uni0686 ; G 1440
+U 1671 ; WX 646 ; N uni0687 ; G 1441
+U 1672 ; WX 445 ; N uni0688 ; G 1442
+U 1673 ; WX 445 ; N uni0689 ; G 1443
+U 1674 ; WX 445 ; N uni068A ; G 1444
+U 1675 ; WX 445 ; N uni068B ; G 1445
+U 1676 ; WX 445 ; N uni068C ; G 1446
+U 1677 ; WX 445 ; N uni068D ; G 1447
+U 1678 ; WX 445 ; N uni068E ; G 1448
+U 1679 ; WX 445 ; N uni068F ; G 1449
+U 1680 ; WX 445 ; N uni0690 ; G 1450
+U 1681 ; WX 483 ; N uni0691 ; G 1451
+U 1682 ; WX 483 ; N uni0692 ; G 1452
+U 1683 ; WX 498 ; N uni0693 ; G 1453
+U 1684 ; WX 530 ; N uni0694 ; G 1454
+U 1685 ; WX 610 ; N uni0695 ; G 1455
+U 1686 ; WX 530 ; N uni0696 ; G 1456
+U 1687 ; WX 483 ; N uni0697 ; G 1457
+U 1688 ; WX 483 ; N uni0698 ; G 1458
+U 1689 ; WX 483 ; N uni0699 ; G 1459
+U 1690 ; WX 1221 ; N uni069A ; G 1460
+U 1691 ; WX 1221 ; N uni069B ; G 1461
+U 1692 ; WX 1221 ; N uni069C ; G 1462
+U 1693 ; WX 1209 ; N uni069D ; G 1463
+U 1694 ; WX 1209 ; N uni069E ; G 1464
+U 1695 ; WX 925 ; N uni069F ; G 1465
+U 1696 ; WX 597 ; N uni06A0 ; G 1466
+U 1697 ; WX 1037 ; N uni06A1 ; G 1467
+U 1698 ; WX 1037 ; N uni06A2 ; G 1468
+U 1699 ; WX 1037 ; N uni06A3 ; G 1469
+U 1700 ; WX 1037 ; N uni06A4 ; G 1470
+U 1701 ; WX 1037 ; N uni06A5 ; G 1471
+U 1702 ; WX 1037 ; N uni06A6 ; G 1472
+U 1703 ; WX 776 ; N uni06A7 ; G 1473
+U 1704 ; WX 776 ; N uni06A8 ; G 1474
+U 1705 ; WX 895 ; N uni06A9 ; G 1475
+U 1706 ; WX 1054 ; N uni06AA ; G 1476
+U 1707 ; WX 895 ; N uni06AB ; G 1477
+U 1708 ; WX 824 ; N uni06AC ; G 1478
+U 1709 ; WX 824 ; N uni06AD ; G 1479
+U 1710 ; WX 824 ; N uni06AE ; G 1480
+U 1711 ; WX 895 ; N uni06AF ; G 1481
+U 1712 ; WX 895 ; N uni06B0 ; G 1482
+U 1713 ; WX 895 ; N uni06B1 ; G 1483
+U 1714 ; WX 895 ; N uni06B2 ; G 1484
+U 1715 ; WX 895 ; N uni06B3 ; G 1485
+U 1716 ; WX 895 ; N uni06B4 ; G 1486
+U 1717 ; WX 727 ; N uni06B5 ; G 1487
+U 1718 ; WX 727 ; N uni06B6 ; G 1488
+U 1719 ; WX 727 ; N uni06B7 ; G 1489
+U 1720 ; WX 727 ; N uni06B8 ; G 1490
+U 1721 ; WX 734 ; N uni06B9 ; G 1491
+U 1722 ; WX 734 ; N uni06BA ; G 1492
+U 1723 ; WX 734 ; N uni06BB ; G 1493
+U 1724 ; WX 734 ; N uni06BC ; G 1494
+U 1725 ; WX 734 ; N uni06BD ; G 1495
+U 1726 ; WX 698 ; N uni06BE ; G 1496
+U 1727 ; WX 646 ; N uni06BF ; G 1497
+U 1734 ; WX 483 ; N uni06C6 ; G 1498
+U 1735 ; WX 483 ; N uni06C7 ; G 1499
+U 1736 ; WX 483 ; N uni06C8 ; G 1500
+U 1739 ; WX 483 ; N uni06CB ; G 1501
+U 1740 ; WX 783 ; N uni06CC ; G 1502
+U 1742 ; WX 783 ; N uni06CE ; G 1503
+U 1744 ; WX 783 ; N uni06D0 ; G 1504
+U 1749 ; WX 524 ; N uni06D5 ; G 1505
+U 1776 ; WX 537 ; N uni06F0 ; G 1506
+U 1777 ; WX 537 ; N uni06F1 ; G 1507
+U 1778 ; WX 537 ; N uni06F2 ; G 1508
+U 1779 ; WX 537 ; N uni06F3 ; G 1509
+U 1780 ; WX 537 ; N uni06F4 ; G 1510
+U 1781 ; WX 537 ; N uni06F5 ; G 1511
+U 1782 ; WX 537 ; N uni06F6 ; G 1512
+U 1783 ; WX 537 ; N uni06F7 ; G 1513
+U 1784 ; WX 537 ; N uni06F8 ; G 1514
+U 1785 ; WX 537 ; N uni06F9 ; G 1515
+U 1984 ; WX 636 ; N uni07C0 ; G 1516
+U 1985 ; WX 636 ; N uni07C1 ; G 1517
+U 1986 ; WX 636 ; N uni07C2 ; G 1518
+U 1987 ; WX 636 ; N uni07C3 ; G 1519
+U 1988 ; WX 636 ; N uni07C4 ; G 1520
+U 1989 ; WX 636 ; N uni07C5 ; G 1521
+U 1990 ; WX 636 ; N uni07C6 ; G 1522
+U 1991 ; WX 636 ; N uni07C7 ; G 1523
+U 1992 ; WX 636 ; N uni07C8 ; G 1524
+U 1993 ; WX 636 ; N uni07C9 ; G 1525
+U 1994 ; WX 278 ; N uni07CA ; G 1526
+U 1995 ; WX 571 ; N uni07CB ; G 1527
+U 1996 ; WX 424 ; N uni07CC ; G 1528
+U 1997 ; WX 592 ; N uni07CD ; G 1529
+U 1998 ; WX 654 ; N uni07CE ; G 1530
+U 1999 ; WX 654 ; N uni07CF ; G 1531
+U 2000 ; WX 594 ; N uni07D0 ; G 1532
+U 2001 ; WX 654 ; N uni07D1 ; G 1533
+U 2002 ; WX 829 ; N uni07D2 ; G 1534
+U 2003 ; WX 438 ; N uni07D3 ; G 1535
+U 2004 ; WX 438 ; N uni07D4 ; G 1536
+U 2005 ; WX 559 ; N uni07D5 ; G 1537
+U 2006 ; WX 612 ; N uni07D6 ; G 1538
+U 2007 ; WX 350 ; N uni07D7 ; G 1539
+U 2008 ; WX 959 ; N uni07D8 ; G 1540
+U 2009 ; WX 473 ; N uni07D9 ; G 1541
+U 2010 ; WX 783 ; N uni07DA ; G 1542
+U 2011 ; WX 654 ; N uni07DB ; G 1543
+U 2012 ; WX 625 ; N uni07DC ; G 1544
+U 2013 ; WX 734 ; N uni07DD ; G 1545
+U 2014 ; WX 530 ; N uni07DE ; G 1546
+U 2015 ; WX 724 ; N uni07DF ; G 1547
+U 2016 ; WX 473 ; N uni07E0 ; G 1548
+U 2017 ; WX 625 ; N uni07E1 ; G 1549
+U 2018 ; WX 594 ; N uni07E2 ; G 1550
+U 2019 ; WX 530 ; N uni07E3 ; G 1551
+U 2020 ; WX 530 ; N uni07E4 ; G 1552
+U 2021 ; WX 522 ; N uni07E5 ; G 1553
+U 2022 ; WX 594 ; N uni07E6 ; G 1554
+U 2023 ; WX 594 ; N uni07E7 ; G 1555
+U 2027 ; WX 0 ; N uni07EB ; G 1556
+U 2028 ; WX 0 ; N uni07EC ; G 1557
+U 2029 ; WX 0 ; N uni07ED ; G 1558
+U 2030 ; WX 0 ; N uni07EE ; G 1559
+U 2031 ; WX 0 ; N uni07EF ; G 1560
+U 2032 ; WX 0 ; N uni07F0 ; G 1561
+U 2033 ; WX 0 ; N uni07F1 ; G 1562
+U 2034 ; WX 0 ; N uni07F2 ; G 1563
+U 2035 ; WX 0 ; N uni07F3 ; G 1564
+U 2036 ; WX 313 ; N uni07F4 ; G 1565
+U 2037 ; WX 313 ; N uni07F5 ; G 1566
+U 2040 ; WX 560 ; N uni07F8 ; G 1567
+U 2041 ; WX 560 ; N uni07F9 ; G 1568
+U 2042 ; WX 361 ; N uni07FA ; G 1569
+U 3647 ; WX 636 ; N uni0E3F ; G 1570
+U 3713 ; WX 670 ; N uni0E81 ; G 1571
+U 3714 ; WX 684 ; N uni0E82 ; G 1572
+U 3716 ; WX 688 ; N uni0E84 ; G 1573
+U 3719 ; WX 482 ; N uni0E87 ; G 1574
+U 3720 ; WX 628 ; N uni0E88 ; G 1575
+U 3722 ; WX 684 ; N uni0E8A ; G 1576
+U 3725 ; WX 688 ; N uni0E8D ; G 1577
+U 3732 ; WX 669 ; N uni0E94 ; G 1578
+U 3733 ; WX 642 ; N uni0E95 ; G 1579
+U 3734 ; WX 645 ; N uni0E96 ; G 1580
+U 3735 ; WX 655 ; N uni0E97 ; G 1581
+U 3737 ; WX 659 ; N uni0E99 ; G 1582
+U 3738 ; WX 625 ; N uni0E9A ; G 1583
+U 3739 ; WX 625 ; N uni0E9B ; G 1584
+U 3740 ; WX 745 ; N uni0E9C ; G 1585
+U 3741 ; WX 767 ; N uni0E9D ; G 1586
+U 3742 ; WX 687 ; N uni0E9E ; G 1587
+U 3743 ; WX 687 ; N uni0E9F ; G 1588
+U 3745 ; WX 702 ; N uni0EA1 ; G 1589
+U 3746 ; WX 688 ; N uni0EA2 ; G 1590
+U 3747 ; WX 684 ; N uni0EA3 ; G 1591
+U 3749 ; WX 649 ; N uni0EA5 ; G 1592
+U 3751 ; WX 632 ; N uni0EA7 ; G 1593
+U 3754 ; WX 703 ; N uni0EAA ; G 1594
+U 3755 ; WX 819 ; N uni0EAB ; G 1595
+U 3757 ; WX 633 ; N uni0EAD ; G 1596
+U 3758 ; WX 684 ; N uni0EAE ; G 1597
+U 3759 ; WX 788 ; N uni0EAF ; G 1598
+U 3760 ; WX 632 ; N uni0EB0 ; G 1599
+U 3761 ; WX 0 ; N uni0EB1 ; G 1600
+U 3762 ; WX 539 ; N uni0EB2 ; G 1601
+U 3763 ; WX 539 ; N uni0EB3 ; G 1602
+U 3764 ; WX 0 ; N uni0EB4 ; G 1603
+U 3765 ; WX 0 ; N uni0EB5 ; G 1604
+U 3766 ; WX 0 ; N uni0EB6 ; G 1605
+U 3767 ; WX 0 ; N uni0EB7 ; G 1606
+U 3768 ; WX 0 ; N uni0EB8 ; G 1607
+U 3769 ; WX 0 ; N uni0EB9 ; G 1608
+U 3771 ; WX 0 ; N uni0EBB ; G 1609
+U 3772 ; WX 0 ; N uni0EBC ; G 1610
+U 3773 ; WX 663 ; N uni0EBD ; G 1611
+U 3776 ; WX 375 ; N uni0EC0 ; G 1612
+U 3777 ; WX 657 ; N uni0EC1 ; G 1613
+U 3778 ; WX 460 ; N uni0EC2 ; G 1614
+U 3779 ; WX 547 ; N uni0EC3 ; G 1615
+U 3780 ; WX 491 ; N uni0EC4 ; G 1616
+U 3782 ; WX 674 ; N uni0EC6 ; G 1617
+U 3784 ; WX 0 ; N uni0EC8 ; G 1618
+U 3785 ; WX 0 ; N uni0EC9 ; G 1619
+U 3786 ; WX 0 ; N uni0ECA ; G 1620
+U 3787 ; WX 0 ; N uni0ECB ; G 1621
+U 3788 ; WX 0 ; N uni0ECC ; G 1622
+U 3789 ; WX 0 ; N uni0ECD ; G 1623
+U 3792 ; WX 636 ; N uni0ED0 ; G 1624
+U 3793 ; WX 641 ; N uni0ED1 ; G 1625
+U 3794 ; WX 641 ; N uni0ED2 ; G 1626
+U 3795 ; WX 670 ; N uni0ED3 ; G 1627
+U 3796 ; WX 625 ; N uni0ED4 ; G 1628
+U 3797 ; WX 625 ; N uni0ED5 ; G 1629
+U 3798 ; WX 703 ; N uni0ED6 ; G 1630
+U 3799 ; WX 670 ; N uni0ED7 ; G 1631
+U 3800 ; WX 674 ; N uni0ED8 ; G 1632
+U 3801 ; WX 677 ; N uni0ED9 ; G 1633
+U 3804 ; WX 1028 ; N uni0EDC ; G 1634
+U 3805 ; WX 1028 ; N uni0EDD ; G 1635
+U 4256 ; WX 874 ; N uni10A0 ; G 1636
+U 4257 ; WX 733 ; N uni10A1 ; G 1637
+U 4258 ; WX 679 ; N uni10A2 ; G 1638
+U 4259 ; WX 834 ; N uni10A3 ; G 1639
+U 4260 ; WX 615 ; N uni10A4 ; G 1640
+U 4261 ; WX 768 ; N uni10A5 ; G 1641
+U 4262 ; WX 753 ; N uni10A6 ; G 1642
+U 4263 ; WX 914 ; N uni10A7 ; G 1643
+U 4264 ; WX 453 ; N uni10A8 ; G 1644
+U 4265 ; WX 620 ; N uni10A9 ; G 1645
+U 4266 ; WX 843 ; N uni10AA ; G 1646
+U 4267 ; WX 882 ; N uni10AB ; G 1647
+U 4268 ; WX 625 ; N uni10AC ; G 1648
+U 4269 ; WX 854 ; N uni10AD ; G 1649
+U 4270 ; WX 781 ; N uni10AE ; G 1650
+U 4271 ; WX 629 ; N uni10AF ; G 1651
+U 4272 ; WX 912 ; N uni10B0 ; G 1652
+U 4273 ; WX 621 ; N uni10B1 ; G 1653
+U 4274 ; WX 620 ; N uni10B2 ; G 1654
+U 4275 ; WX 854 ; N uni10B3 ; G 1655
+U 4276 ; WX 866 ; N uni10B4 ; G 1656
+U 4277 ; WX 724 ; N uni10B5 ; G 1657
+U 4278 ; WX 630 ; N uni10B6 ; G 1658
+U 4279 ; WX 621 ; N uni10B7 ; G 1659
+U 4280 ; WX 625 ; N uni10B8 ; G 1660
+U 4281 ; WX 620 ; N uni10B9 ; G 1661
+U 4282 ; WX 818 ; N uni10BA ; G 1662
+U 4283 ; WX 874 ; N uni10BB ; G 1663
+U 4284 ; WX 615 ; N uni10BC ; G 1664
+U 4285 ; WX 623 ; N uni10BD ; G 1665
+U 4286 ; WX 625 ; N uni10BE ; G 1666
+U 4287 ; WX 725 ; N uni10BF ; G 1667
+U 4288 ; WX 844 ; N uni10C0 ; G 1668
+U 4289 ; WX 596 ; N uni10C1 ; G 1669
+U 4290 ; WX 688 ; N uni10C2 ; G 1670
+U 4291 ; WX 596 ; N uni10C3 ; G 1671
+U 4292 ; WX 594 ; N uni10C4 ; G 1672
+U 4293 ; WX 738 ; N uni10C5 ; G 1673
+U 4304 ; WX 508 ; N uni10D0 ; G 1674
+U 4305 ; WX 518 ; N uni10D1 ; G 1675
+U 4306 ; WX 581 ; N uni10D2 ; G 1676
+U 4307 ; WX 818 ; N uni10D3 ; G 1677
+U 4308 ; WX 508 ; N uni10D4 ; G 1678
+U 4309 ; WX 513 ; N uni10D5 ; G 1679
+U 4310 ; WX 500 ; N uni10D6 ; G 1680
+U 4311 ; WX 801 ; N uni10D7 ; G 1681
+U 4312 ; WX 518 ; N uni10D8 ; G 1682
+U 4313 ; WX 510 ; N uni10D9 ; G 1683
+U 4314 ; WX 1064 ; N uni10DA ; G 1684
+U 4315 ; WX 522 ; N uni10DB ; G 1685
+U 4316 ; WX 522 ; N uni10DC ; G 1686
+U 4317 ; WX 786 ; N uni10DD ; G 1687
+U 4318 ; WX 508 ; N uni10DE ; G 1688
+U 4319 ; WX 518 ; N uni10DF ; G 1689
+U 4320 ; WX 796 ; N uni10E0 ; G 1690
+U 4321 ; WX 522 ; N uni10E1 ; G 1691
+U 4322 ; WX 654 ; N uni10E2 ; G 1692
+U 4323 ; WX 522 ; N uni10E3 ; G 1693
+U 4324 ; WX 825 ; N uni10E4 ; G 1694
+U 4325 ; WX 513 ; N uni10E5 ; G 1695
+U 4326 ; WX 786 ; N uni10E6 ; G 1696
+U 4327 ; WX 518 ; N uni10E7 ; G 1697
+U 4328 ; WX 518 ; N uni10E8 ; G 1698
+U 4329 ; WX 522 ; N uni10E9 ; G 1699
+U 4330 ; WX 571 ; N uni10EA ; G 1700
+U 4331 ; WX 522 ; N uni10EB ; G 1701
+U 4332 ; WX 518 ; N uni10EC ; G 1702
+U 4333 ; WX 520 ; N uni10ED ; G 1703
+U 4334 ; WX 522 ; N uni10EE ; G 1704
+U 4335 ; WX 454 ; N uni10EF ; G 1705
+U 4336 ; WX 508 ; N uni10F0 ; G 1706
+U 4337 ; WX 518 ; N uni10F1 ; G 1707
+U 4338 ; WX 508 ; N uni10F2 ; G 1708
+U 4339 ; WX 508 ; N uni10F3 ; G 1709
+U 4340 ; WX 518 ; N uni10F4 ; G 1710
+U 4341 ; WX 554 ; N uni10F5 ; G 1711
+U 4342 ; WX 828 ; N uni10F6 ; G 1712
+U 4343 ; WX 552 ; N uni10F7 ; G 1713
+U 4344 ; WX 508 ; N uni10F8 ; G 1714
+U 4345 ; WX 571 ; N uni10F9 ; G 1715
+U 4346 ; WX 508 ; N uni10FA ; G 1716
+U 4347 ; WX 448 ; N uni10FB ; G 1717
+U 4348 ; WX 324 ; N uni10FC ; G 1718
+U 5121 ; WX 684 ; N uni1401 ; G 1719
+U 5122 ; WX 684 ; N uni1402 ; G 1720
+U 5123 ; WX 684 ; N uni1403 ; G 1721
+U 5124 ; WX 684 ; N uni1404 ; G 1722
+U 5125 ; WX 769 ; N uni1405 ; G 1723
+U 5126 ; WX 769 ; N uni1406 ; G 1724
+U 5127 ; WX 769 ; N uni1407 ; G 1725
+U 5129 ; WX 769 ; N uni1409 ; G 1726
+U 5130 ; WX 769 ; N uni140A ; G 1727
+U 5131 ; WX 769 ; N uni140B ; G 1728
+U 5132 ; WX 835 ; N uni140C ; G 1729
+U 5133 ; WX 834 ; N uni140D ; G 1730
+U 5134 ; WX 835 ; N uni140E ; G 1731
+U 5135 ; WX 834 ; N uni140F ; G 1732
+U 5136 ; WX 835 ; N uni1410 ; G 1733
+U 5137 ; WX 834 ; N uni1411 ; G 1734
+U 5138 ; WX 967 ; N uni1412 ; G 1735
+U 5139 ; WX 1007 ; N uni1413 ; G 1736
+U 5140 ; WX 967 ; N uni1414 ; G 1737
+U 5141 ; WX 1007 ; N uni1415 ; G 1738
+U 5142 ; WX 769 ; N uni1416 ; G 1739
+U 5143 ; WX 967 ; N uni1417 ; G 1740
+U 5144 ; WX 1007 ; N uni1418 ; G 1741
+U 5145 ; WX 967 ; N uni1419 ; G 1742
+U 5146 ; WX 1007 ; N uni141A ; G 1743
+U 5147 ; WX 769 ; N uni141B ; G 1744
+U 5149 ; WX 256 ; N uni141D ; G 1745
+U 5150 ; WX 543 ; N uni141E ; G 1746
+U 5151 ; WX 423 ; N uni141F ; G 1747
+U 5152 ; WX 423 ; N uni1420 ; G 1748
+U 5153 ; WX 389 ; N uni1421 ; G 1749
+U 5154 ; WX 389 ; N uni1422 ; G 1750
+U 5155 ; WX 393 ; N uni1423 ; G 1751
+U 5156 ; WX 389 ; N uni1424 ; G 1752
+U 5157 ; WX 466 ; N uni1425 ; G 1753
+U 5158 ; WX 385 ; N uni1426 ; G 1754
+U 5159 ; WX 256 ; N uni1427 ; G 1755
+U 5160 ; WX 389 ; N uni1428 ; G 1756
+U 5161 ; WX 389 ; N uni1429 ; G 1757
+U 5162 ; WX 389 ; N uni142A ; G 1758
+U 5163 ; WX 1090 ; N uni142B ; G 1759
+U 5164 ; WX 909 ; N uni142C ; G 1760
+U 5165 ; WX 953 ; N uni142D ; G 1761
+U 5166 ; WX 1117 ; N uni142E ; G 1762
+U 5167 ; WX 684 ; N uni142F ; G 1763
+U 5168 ; WX 684 ; N uni1430 ; G 1764
+U 5169 ; WX 684 ; N uni1431 ; G 1765
+U 5170 ; WX 684 ; N uni1432 ; G 1766
+U 5171 ; WX 729 ; N uni1433 ; G 1767
+U 5172 ; WX 729 ; N uni1434 ; G 1768
+U 5173 ; WX 729 ; N uni1435 ; G 1769
+U 5175 ; WX 729 ; N uni1437 ; G 1770
+U 5176 ; WX 729 ; N uni1438 ; G 1771
+U 5177 ; WX 729 ; N uni1439 ; G 1772
+U 5178 ; WX 835 ; N uni143A ; G 1773
+U 5179 ; WX 684 ; N uni143B ; G 1774
+U 5180 ; WX 835 ; N uni143C ; G 1775
+U 5181 ; WX 834 ; N uni143D ; G 1776
+U 5182 ; WX 835 ; N uni143E ; G 1777
+U 5183 ; WX 834 ; N uni143F ; G 1778
+U 5184 ; WX 967 ; N uni1440 ; G 1779
+U 5185 ; WX 1007 ; N uni1441 ; G 1780
+U 5186 ; WX 967 ; N uni1442 ; G 1781
+U 5187 ; WX 1007 ; N uni1443 ; G 1782
+U 5188 ; WX 967 ; N uni1444 ; G 1783
+U 5189 ; WX 1007 ; N uni1445 ; G 1784
+U 5190 ; WX 967 ; N uni1446 ; G 1785
+U 5191 ; WX 1007 ; N uni1447 ; G 1786
+U 5192 ; WX 729 ; N uni1448 ; G 1787
+U 5193 ; WX 508 ; N uni1449 ; G 1788
+U 5194 ; WX 192 ; N uni144A ; G 1789
+U 5196 ; WX 732 ; N uni144C ; G 1790
+U 5197 ; WX 732 ; N uni144D ; G 1791
+U 5198 ; WX 732 ; N uni144E ; G 1792
+U 5199 ; WX 732 ; N uni144F ; G 1793
+U 5200 ; WX 730 ; N uni1450 ; G 1794
+U 5201 ; WX 730 ; N uni1451 ; G 1795
+U 5202 ; WX 730 ; N uni1452 ; G 1796
+U 5204 ; WX 730 ; N uni1454 ; G 1797
+U 5205 ; WX 730 ; N uni1455 ; G 1798
+U 5206 ; WX 730 ; N uni1456 ; G 1799
+U 5207 ; WX 921 ; N uni1457 ; G 1800
+U 5208 ; WX 889 ; N uni1458 ; G 1801
+U 5209 ; WX 921 ; N uni1459 ; G 1802
+U 5210 ; WX 889 ; N uni145A ; G 1803
+U 5211 ; WX 921 ; N uni145B ; G 1804
+U 5212 ; WX 889 ; N uni145C ; G 1805
+U 5213 ; WX 928 ; N uni145D ; G 1806
+U 5214 ; WX 900 ; N uni145E ; G 1807
+U 5215 ; WX 928 ; N uni145F ; G 1808
+U 5216 ; WX 900 ; N uni1460 ; G 1809
+U 5217 ; WX 947 ; N uni1461 ; G 1810
+U 5218 ; WX 900 ; N uni1462 ; G 1811
+U 5219 ; WX 947 ; N uni1463 ; G 1812
+U 5220 ; WX 900 ; N uni1464 ; G 1813
+U 5221 ; WX 947 ; N uni1465 ; G 1814
+U 5222 ; WX 434 ; N uni1466 ; G 1815
+U 5223 ; WX 877 ; N uni1467 ; G 1816
+U 5224 ; WX 877 ; N uni1468 ; G 1817
+U 5225 ; WX 866 ; N uni1469 ; G 1818
+U 5226 ; WX 890 ; N uni146A ; G 1819
+U 5227 ; WX 628 ; N uni146B ; G 1820
+U 5228 ; WX 628 ; N uni146C ; G 1821
+U 5229 ; WX 628 ; N uni146D ; G 1822
+U 5230 ; WX 628 ; N uni146E ; G 1823
+U 5231 ; WX 628 ; N uni146F ; G 1824
+U 5232 ; WX 628 ; N uni1470 ; G 1825
+U 5233 ; WX 628 ; N uni1471 ; G 1826
+U 5234 ; WX 628 ; N uni1472 ; G 1827
+U 5235 ; WX 628 ; N uni1473 ; G 1828
+U 5236 ; WX 860 ; N uni1474 ; G 1829
+U 5237 ; WX 771 ; N uni1475 ; G 1830
+U 5238 ; WX 815 ; N uni1476 ; G 1831
+U 5239 ; WX 816 ; N uni1477 ; G 1832
+U 5240 ; WX 815 ; N uni1478 ; G 1833
+U 5241 ; WX 816 ; N uni1479 ; G 1834
+U 5242 ; WX 860 ; N uni147A ; G 1835
+U 5243 ; WX 771 ; N uni147B ; G 1836
+U 5244 ; WX 860 ; N uni147C ; G 1837
+U 5245 ; WX 771 ; N uni147D ; G 1838
+U 5246 ; WX 815 ; N uni147E ; G 1839
+U 5247 ; WX 816 ; N uni147F ; G 1840
+U 5248 ; WX 815 ; N uni1480 ; G 1841
+U 5249 ; WX 816 ; N uni1481 ; G 1842
+U 5250 ; WX 815 ; N uni1482 ; G 1843
+U 5251 ; WX 407 ; N uni1483 ; G 1844
+U 5252 ; WX 407 ; N uni1484 ; G 1845
+U 5253 ; WX 750 ; N uni1485 ; G 1846
+U 5254 ; WX 775 ; N uni1486 ; G 1847
+U 5255 ; WX 750 ; N uni1487 ; G 1848
+U 5256 ; WX 775 ; N uni1488 ; G 1849
+U 5257 ; WX 628 ; N uni1489 ; G 1850
+U 5258 ; WX 628 ; N uni148A ; G 1851
+U 5259 ; WX 628 ; N uni148B ; G 1852
+U 5260 ; WX 628 ; N uni148C ; G 1853
+U 5261 ; WX 628 ; N uni148D ; G 1854
+U 5262 ; WX 628 ; N uni148E ; G 1855
+U 5263 ; WX 628 ; N uni148F ; G 1856
+U 5264 ; WX 628 ; N uni1490 ; G 1857
+U 5265 ; WX 628 ; N uni1491 ; G 1858
+U 5266 ; WX 860 ; N uni1492 ; G 1859
+U 5267 ; WX 771 ; N uni1493 ; G 1860
+U 5268 ; WX 815 ; N uni1494 ; G 1861
+U 5269 ; WX 816 ; N uni1495 ; G 1862
+U 5270 ; WX 815 ; N uni1496 ; G 1863
+U 5271 ; WX 816 ; N uni1497 ; G 1864
+U 5272 ; WX 860 ; N uni1498 ; G 1865
+U 5273 ; WX 771 ; N uni1499 ; G 1866
+U 5274 ; WX 860 ; N uni149A ; G 1867
+U 5275 ; WX 771 ; N uni149B ; G 1868
+U 5276 ; WX 815 ; N uni149C ; G 1869
+U 5277 ; WX 816 ; N uni149D ; G 1870
+U 5278 ; WX 815 ; N uni149E ; G 1871
+U 5279 ; WX 816 ; N uni149F ; G 1872
+U 5280 ; WX 815 ; N uni14A0 ; G 1873
+U 5281 ; WX 435 ; N uni14A1 ; G 1874
+U 5282 ; WX 435 ; N uni14A2 ; G 1875
+U 5283 ; WX 610 ; N uni14A3 ; G 1876
+U 5284 ; WX 557 ; N uni14A4 ; G 1877
+U 5285 ; WX 557 ; N uni14A5 ; G 1878
+U 5286 ; WX 557 ; N uni14A6 ; G 1879
+U 5287 ; WX 610 ; N uni14A7 ; G 1880
+U 5288 ; WX 610 ; N uni14A8 ; G 1881
+U 5289 ; WX 610 ; N uni14A9 ; G 1882
+U 5290 ; WX 557 ; N uni14AA ; G 1883
+U 5291 ; WX 557 ; N uni14AB ; G 1884
+U 5292 ; WX 749 ; N uni14AC ; G 1885
+U 5293 ; WX 769 ; N uni14AD ; G 1886
+U 5294 ; WX 746 ; N uni14AE ; G 1887
+U 5295 ; WX 764 ; N uni14AF ; G 1888
+U 5296 ; WX 746 ; N uni14B0 ; G 1889
+U 5297 ; WX 764 ; N uni14B1 ; G 1890
+U 5298 ; WX 749 ; N uni14B2 ; G 1891
+U 5299 ; WX 769 ; N uni14B3 ; G 1892
+U 5300 ; WX 749 ; N uni14B4 ; G 1893
+U 5301 ; WX 769 ; N uni14B5 ; G 1894
+U 5302 ; WX 746 ; N uni14B6 ; G 1895
+U 5303 ; WX 764 ; N uni14B7 ; G 1896
+U 5304 ; WX 746 ; N uni14B8 ; G 1897
+U 5305 ; WX 764 ; N uni14B9 ; G 1898
+U 5306 ; WX 746 ; N uni14BA ; G 1899
+U 5307 ; WX 386 ; N uni14BB ; G 1900
+U 5308 ; WX 508 ; N uni14BC ; G 1901
+U 5309 ; WX 386 ; N uni14BD ; G 1902
+U 5312 ; WX 852 ; N uni14C0 ; G 1903
+U 5313 ; WX 852 ; N uni14C1 ; G 1904
+U 5314 ; WX 852 ; N uni14C2 ; G 1905
+U 5315 ; WX 852 ; N uni14C3 ; G 1906
+U 5316 ; WX 852 ; N uni14C4 ; G 1907
+U 5317 ; WX 852 ; N uni14C5 ; G 1908
+U 5318 ; WX 852 ; N uni14C6 ; G 1909
+U 5319 ; WX 852 ; N uni14C7 ; G 1910
+U 5320 ; WX 852 ; N uni14C8 ; G 1911
+U 5321 ; WX 1069 ; N uni14C9 ; G 1912
+U 5322 ; WX 1035 ; N uni14CA ; G 1913
+U 5323 ; WX 1059 ; N uni14CB ; G 1914
+U 5324 ; WX 852 ; N uni14CC ; G 1915
+U 5325 ; WX 1059 ; N uni14CD ; G 1916
+U 5326 ; WX 852 ; N uni14CE ; G 1917
+U 5327 ; WX 852 ; N uni14CF ; G 1918
+U 5328 ; WX 600 ; N uni14D0 ; G 1919
+U 5329 ; WX 453 ; N uni14D1 ; G 1920
+U 5330 ; WX 600 ; N uni14D2 ; G 1921
+U 5331 ; WX 852 ; N uni14D3 ; G 1922
+U 5332 ; WX 852 ; N uni14D4 ; G 1923
+U 5333 ; WX 852 ; N uni14D5 ; G 1924
+U 5334 ; WX 852 ; N uni14D6 ; G 1925
+U 5335 ; WX 852 ; N uni14D7 ; G 1926
+U 5336 ; WX 852 ; N uni14D8 ; G 1927
+U 5337 ; WX 852 ; N uni14D9 ; G 1928
+U 5338 ; WX 852 ; N uni14DA ; G 1929
+U 5339 ; WX 852 ; N uni14DB ; G 1930
+U 5340 ; WX 1069 ; N uni14DC ; G 1931
+U 5341 ; WX 1035 ; N uni14DD ; G 1932
+U 5342 ; WX 1059 ; N uni14DE ; G 1933
+U 5343 ; WX 1030 ; N uni14DF ; G 1934
+U 5344 ; WX 1059 ; N uni14E0 ; G 1935
+U 5345 ; WX 1030 ; N uni14E1 ; G 1936
+U 5346 ; WX 1069 ; N uni14E2 ; G 1937
+U 5347 ; WX 1035 ; N uni14E3 ; G 1938
+U 5348 ; WX 1069 ; N uni14E4 ; G 1939
+U 5349 ; WX 1035 ; N uni14E5 ; G 1940
+U 5350 ; WX 1083 ; N uni14E6 ; G 1941
+U 5351 ; WX 1030 ; N uni14E7 ; G 1942
+U 5352 ; WX 1083 ; N uni14E8 ; G 1943
+U 5353 ; WX 1030 ; N uni14E9 ; G 1944
+U 5354 ; WX 600 ; N uni14EA ; G 1945
+U 5356 ; WX 729 ; N uni14EC ; G 1946
+U 5357 ; WX 603 ; N uni14ED ; G 1947
+U 5358 ; WX 603 ; N uni14EE ; G 1948
+U 5359 ; WX 603 ; N uni14EF ; G 1949
+U 5360 ; WX 603 ; N uni14F0 ; G 1950
+U 5361 ; WX 603 ; N uni14F1 ; G 1951
+U 5362 ; WX 603 ; N uni14F2 ; G 1952
+U 5363 ; WX 603 ; N uni14F3 ; G 1953
+U 5364 ; WX 603 ; N uni14F4 ; G 1954
+U 5365 ; WX 603 ; N uni14F5 ; G 1955
+U 5366 ; WX 834 ; N uni14F6 ; G 1956
+U 5367 ; WX 754 ; N uni14F7 ; G 1957
+U 5368 ; WX 792 ; N uni14F8 ; G 1958
+U 5369 ; WX 771 ; N uni14F9 ; G 1959
+U 5370 ; WX 792 ; N uni14FA ; G 1960
+U 5371 ; WX 771 ; N uni14FB ; G 1961
+U 5372 ; WX 834 ; N uni14FC ; G 1962
+U 5373 ; WX 754 ; N uni14FD ; G 1963
+U 5374 ; WX 834 ; N uni14FE ; G 1964
+U 5375 ; WX 754 ; N uni14FF ; G 1965
+U 5376 ; WX 792 ; N uni1500 ; G 1966
+U 5377 ; WX 771 ; N uni1501 ; G 1967
+U 5378 ; WX 792 ; N uni1502 ; G 1968
+U 5379 ; WX 771 ; N uni1503 ; G 1969
+U 5380 ; WX 792 ; N uni1504 ; G 1970
+U 5381 ; WX 418 ; N uni1505 ; G 1971
+U 5382 ; WX 420 ; N uni1506 ; G 1972
+U 5383 ; WX 418 ; N uni1507 ; G 1973
+U 5392 ; WX 712 ; N uni1510 ; G 1974
+U 5393 ; WX 712 ; N uni1511 ; G 1975
+U 5394 ; WX 712 ; N uni1512 ; G 1976
+U 5395 ; WX 892 ; N uni1513 ; G 1977
+U 5396 ; WX 892 ; N uni1514 ; G 1978
+U 5397 ; WX 892 ; N uni1515 ; G 1979
+U 5398 ; WX 892 ; N uni1516 ; G 1980
+U 5399 ; WX 910 ; N uni1517 ; G 1981
+U 5400 ; WX 872 ; N uni1518 ; G 1982
+U 5401 ; WX 910 ; N uni1519 ; G 1983
+U 5402 ; WX 872 ; N uni151A ; G 1984
+U 5403 ; WX 910 ; N uni151B ; G 1985
+U 5404 ; WX 872 ; N uni151C ; G 1986
+U 5405 ; WX 1140 ; N uni151D ; G 1987
+U 5406 ; WX 1100 ; N uni151E ; G 1988
+U 5407 ; WX 1140 ; N uni151F ; G 1989
+U 5408 ; WX 1100 ; N uni1520 ; G 1990
+U 5409 ; WX 1140 ; N uni1521 ; G 1991
+U 5410 ; WX 1100 ; N uni1522 ; G 1992
+U 5411 ; WX 1140 ; N uni1523 ; G 1993
+U 5412 ; WX 1100 ; N uni1524 ; G 1994
+U 5413 ; WX 641 ; N uni1525 ; G 1995
+U 5414 ; WX 627 ; N uni1526 ; G 1996
+U 5415 ; WX 627 ; N uni1527 ; G 1997
+U 5416 ; WX 627 ; N uni1528 ; G 1998
+U 5417 ; WX 627 ; N uni1529 ; G 1999
+U 5418 ; WX 627 ; N uni152A ; G 2000
+U 5419 ; WX 627 ; N uni152B ; G 2001
+U 5420 ; WX 627 ; N uni152C ; G 2002
+U 5421 ; WX 627 ; N uni152D ; G 2003
+U 5422 ; WX 627 ; N uni152E ; G 2004
+U 5423 ; WX 844 ; N uni152F ; G 2005
+U 5424 ; WX 781 ; N uni1530 ; G 2006
+U 5425 ; WX 816 ; N uni1531 ; G 2007
+U 5426 ; WX 818 ; N uni1532 ; G 2008
+U 5427 ; WX 816 ; N uni1533 ; G 2009
+U 5428 ; WX 818 ; N uni1534 ; G 2010
+U 5429 ; WX 844 ; N uni1535 ; G 2011
+U 5430 ; WX 781 ; N uni1536 ; G 2012
+U 5431 ; WX 844 ; N uni1537 ; G 2013
+U 5432 ; WX 781 ; N uni1538 ; G 2014
+U 5433 ; WX 816 ; N uni1539 ; G 2015
+U 5434 ; WX 818 ; N uni153A ; G 2016
+U 5435 ; WX 816 ; N uni153B ; G 2017
+U 5436 ; WX 818 ; N uni153C ; G 2018
+U 5437 ; WX 816 ; N uni153D ; G 2019
+U 5438 ; WX 418 ; N uni153E ; G 2020
+U 5440 ; WX 389 ; N uni1540 ; G 2021
+U 5441 ; WX 484 ; N uni1541 ; G 2022
+U 5442 ; WX 916 ; N uni1542 ; G 2023
+U 5443 ; WX 916 ; N uni1543 ; G 2024
+U 5444 ; WX 916 ; N uni1544 ; G 2025
+U 5445 ; WX 916 ; N uni1545 ; G 2026
+U 5446 ; WX 916 ; N uni1546 ; G 2027
+U 5447 ; WX 916 ; N uni1547 ; G 2028
+U 5448 ; WX 603 ; N uni1548 ; G 2029
+U 5449 ; WX 603 ; N uni1549 ; G 2030
+U 5450 ; WX 603 ; N uni154A ; G 2031
+U 5451 ; WX 603 ; N uni154B ; G 2032
+U 5452 ; WX 603 ; N uni154C ; G 2033
+U 5453 ; WX 603 ; N uni154D ; G 2034
+U 5454 ; WX 834 ; N uni154E ; G 2035
+U 5455 ; WX 754 ; N uni154F ; G 2036
+U 5456 ; WX 418 ; N uni1550 ; G 2037
+U 5458 ; WX 729 ; N uni1552 ; G 2038
+U 5459 ; WX 684 ; N uni1553 ; G 2039
+U 5460 ; WX 684 ; N uni1554 ; G 2040
+U 5461 ; WX 684 ; N uni1555 ; G 2041
+U 5462 ; WX 684 ; N uni1556 ; G 2042
+U 5463 ; WX 726 ; N uni1557 ; G 2043
+U 5464 ; WX 726 ; N uni1558 ; G 2044
+U 5465 ; WX 726 ; N uni1559 ; G 2045
+U 5466 ; WX 726 ; N uni155A ; G 2046
+U 5467 ; WX 924 ; N uni155B ; G 2047
+U 5468 ; WX 1007 ; N uni155C ; G 2048
+U 5469 ; WX 508 ; N uni155D ; G 2049
+U 5470 ; WX 732 ; N uni155E ; G 2050
+U 5471 ; WX 732 ; N uni155F ; G 2051
+U 5472 ; WX 732 ; N uni1560 ; G 2052
+U 5473 ; WX 732 ; N uni1561 ; G 2053
+U 5474 ; WX 732 ; N uni1562 ; G 2054
+U 5475 ; WX 732 ; N uni1563 ; G 2055
+U 5476 ; WX 730 ; N uni1564 ; G 2056
+U 5477 ; WX 730 ; N uni1565 ; G 2057
+U 5478 ; WX 730 ; N uni1566 ; G 2058
+U 5479 ; WX 730 ; N uni1567 ; G 2059
+U 5480 ; WX 947 ; N uni1568 ; G 2060
+U 5481 ; WX 900 ; N uni1569 ; G 2061
+U 5482 ; WX 508 ; N uni156A ; G 2062
+U 5492 ; WX 831 ; N uni1574 ; G 2063
+U 5493 ; WX 831 ; N uni1575 ; G 2064
+U 5494 ; WX 831 ; N uni1576 ; G 2065
+U 5495 ; WX 831 ; N uni1577 ; G 2066
+U 5496 ; WX 831 ; N uni1578 ; G 2067
+U 5497 ; WX 831 ; N uni1579 ; G 2068
+U 5498 ; WX 831 ; N uni157A ; G 2069
+U 5499 ; WX 563 ; N uni157B ; G 2070
+U 5500 ; WX 752 ; N uni157C ; G 2071
+U 5501 ; WX 484 ; N uni157D ; G 2072
+U 5502 ; WX 1047 ; N uni157E ; G 2073
+U 5503 ; WX 1047 ; N uni157F ; G 2074
+U 5504 ; WX 1047 ; N uni1580 ; G 2075
+U 5505 ; WX 1047 ; N uni1581 ; G 2076
+U 5506 ; WX 1047 ; N uni1582 ; G 2077
+U 5507 ; WX 1047 ; N uni1583 ; G 2078
+U 5508 ; WX 1047 ; N uni1584 ; G 2079
+U 5509 ; WX 825 ; N uni1585 ; G 2080
+U 5514 ; WX 831 ; N uni158A ; G 2081
+U 5515 ; WX 831 ; N uni158B ; G 2082
+U 5516 ; WX 831 ; N uni158C ; G 2083
+U 5517 ; WX 831 ; N uni158D ; G 2084
+U 5518 ; WX 1259 ; N uni158E ; G 2085
+U 5519 ; WX 1259 ; N uni158F ; G 2086
+U 5520 ; WX 1259 ; N uni1590 ; G 2087
+U 5521 ; WX 1002 ; N uni1591 ; G 2088
+U 5522 ; WX 1002 ; N uni1592 ; G 2089
+U 5523 ; WX 1259 ; N uni1593 ; G 2090
+U 5524 ; WX 1259 ; N uni1594 ; G 2091
+U 5525 ; WX 700 ; N uni1595 ; G 2092
+U 5526 ; WX 1073 ; N uni1596 ; G 2093
+U 5536 ; WX 852 ; N uni15A0 ; G 2094
+U 5537 ; WX 852 ; N uni15A1 ; G 2095
+U 5538 ; WX 852 ; N uni15A2 ; G 2096
+U 5539 ; WX 852 ; N uni15A3 ; G 2097
+U 5540 ; WX 852 ; N uni15A4 ; G 2098
+U 5541 ; WX 852 ; N uni15A5 ; G 2099
+U 5542 ; WX 600 ; N uni15A6 ; G 2100
+U 5543 ; WX 643 ; N uni15A7 ; G 2101
+U 5544 ; WX 643 ; N uni15A8 ; G 2102
+U 5545 ; WX 643 ; N uni15A9 ; G 2103
+U 5546 ; WX 643 ; N uni15AA ; G 2104
+U 5547 ; WX 643 ; N uni15AB ; G 2105
+U 5548 ; WX 643 ; N uni15AC ; G 2106
+U 5549 ; WX 643 ; N uni15AD ; G 2107
+U 5550 ; WX 418 ; N uni15AE ; G 2108
+U 5551 ; WX 628 ; N uni15AF ; G 2109
+U 5598 ; WX 770 ; N uni15DE ; G 2110
+U 5601 ; WX 767 ; N uni15E1 ; G 2111
+U 5702 ; WX 468 ; N uni1646 ; G 2112
+U 5703 ; WX 468 ; N uni1647 ; G 2113
+U 5742 ; WX 444 ; N uni166E ; G 2114
+U 5743 ; WX 1047 ; N uni166F ; G 2115
+U 5744 ; WX 1310 ; N uni1670 ; G 2116
+U 5745 ; WX 1632 ; N uni1671 ; G 2117
+U 5746 ; WX 1632 ; N uni1672 ; G 2118
+U 5747 ; WX 1375 ; N uni1673 ; G 2119
+U 5748 ; WX 1375 ; N uni1674 ; G 2120
+U 5749 ; WX 1632 ; N uni1675 ; G 2121
+U 5750 ; WX 1632 ; N uni1676 ; G 2122
+U 5760 ; WX 477 ; N uni1680 ; G 2123
+U 5761 ; WX 493 ; N uni1681 ; G 2124
+U 5762 ; WX 712 ; N uni1682 ; G 2125
+U 5763 ; WX 931 ; N uni1683 ; G 2126
+U 5764 ; WX 1150 ; N uni1684 ; G 2127
+U 5765 ; WX 1370 ; N uni1685 ; G 2128
+U 5766 ; WX 493 ; N uni1686 ; G 2129
+U 5767 ; WX 712 ; N uni1687 ; G 2130
+U 5768 ; WX 931 ; N uni1688 ; G 2131
+U 5769 ; WX 1150 ; N uni1689 ; G 2132
+U 5770 ; WX 1370 ; N uni168A ; G 2133
+U 5771 ; WX 498 ; N uni168B ; G 2134
+U 5772 ; WX 718 ; N uni168C ; G 2135
+U 5773 ; WX 938 ; N uni168D ; G 2136
+U 5774 ; WX 1159 ; N uni168E ; G 2137
+U 5775 ; WX 1379 ; N uni168F ; G 2138
+U 5776 ; WX 493 ; N uni1690 ; G 2139
+U 5777 ; WX 712 ; N uni1691 ; G 2140
+U 5778 ; WX 930 ; N uni1692 ; G 2141
+U 5779 ; WX 1149 ; N uni1693 ; G 2142
+U 5780 ; WX 1370 ; N uni1694 ; G 2143
+U 5781 ; WX 498 ; N uni1695 ; G 2144
+U 5782 ; WX 752 ; N uni1696 ; G 2145
+U 5783 ; WX 789 ; N uni1697 ; G 2146
+U 5784 ; WX 1205 ; N uni1698 ; G 2147
+U 5785 ; WX 1150 ; N uni1699 ; G 2148
+U 5786 ; WX 683 ; N uni169A ; G 2149
+U 5787 ; WX 507 ; N uni169B ; G 2150
+U 5788 ; WX 507 ; N uni169C ; G 2151
+U 7424 ; WX 592 ; N uni1D00 ; G 2152
+U 7425 ; WX 717 ; N uni1D01 ; G 2153
+U 7426 ; WX 982 ; N uni1D02 ; G 2154
+U 7427 ; WX 586 ; N uni1D03 ; G 2155
+U 7428 ; WX 550 ; N uni1D04 ; G 2156
+U 7429 ; WX 605 ; N uni1D05 ; G 2157
+U 7430 ; WX 605 ; N uni1D06 ; G 2158
+U 7431 ; WX 491 ; N uni1D07 ; G 2159
+U 7432 ; WX 541 ; N uni1D08 ; G 2160
+U 7433 ; WX 278 ; N uni1D09 ; G 2161
+U 7434 ; WX 395 ; N uni1D0A ; G 2162
+U 7435 ; WX 579 ; N uni1D0B ; G 2163
+U 7436 ; WX 583 ; N uni1D0C ; G 2164
+U 7437 ; WX 754 ; N uni1D0D ; G 2165
+U 7438 ; WX 650 ; N uni1D0E ; G 2166
+U 7439 ; WX 612 ; N uni1D0F ; G 2167
+U 7440 ; WX 550 ; N uni1D10 ; G 2168
+U 7441 ; WX 684 ; N uni1D11 ; G 2169
+U 7442 ; WX 684 ; N uni1D12 ; G 2170
+U 7443 ; WX 684 ; N uni1D13 ; G 2171
+U 7444 ; WX 1023 ; N uni1D14 ; G 2172
+U 7446 ; WX 612 ; N uni1D16 ; G 2173
+U 7447 ; WX 612 ; N uni1D17 ; G 2174
+U 7448 ; WX 524 ; N uni1D18 ; G 2175
+U 7449 ; WX 602 ; N uni1D19 ; G 2176
+U 7450 ; WX 602 ; N uni1D1A ; G 2177
+U 7451 ; WX 583 ; N uni1D1B ; G 2178
+U 7452 ; WX 574 ; N uni1D1C ; G 2179
+U 7453 ; WX 737 ; N uni1D1D ; G 2180
+U 7454 ; WX 948 ; N uni1D1E ; G 2181
+U 7455 ; WX 638 ; N uni1D1F ; G 2182
+U 7456 ; WX 592 ; N uni1D20 ; G 2183
+U 7457 ; WX 818 ; N uni1D21 ; G 2184
+U 7458 ; WX 525 ; N uni1D22 ; G 2185
+U 7459 ; WX 526 ; N uni1D23 ; G 2186
+U 7462 ; WX 583 ; N uni1D26 ; G 2187
+U 7463 ; WX 592 ; N uni1D27 ; G 2188
+U 7464 ; WX 564 ; N uni1D28 ; G 2189
+U 7465 ; WX 524 ; N uni1D29 ; G 2190
+U 7466 ; WX 590 ; N uni1D2A ; G 2191
+U 7467 ; WX 639 ; N uni1D2B ; G 2192
+U 7468 ; WX 431 ; N uni1D2C ; G 2193
+U 7469 ; WX 613 ; N uni1D2D ; G 2194
+U 7470 ; WX 432 ; N uni1D2E ; G 2195
+U 7472 ; WX 485 ; N uni1D30 ; G 2196
+U 7473 ; WX 398 ; N uni1D31 ; G 2197
+U 7474 ; WX 398 ; N uni1D32 ; G 2198
+U 7475 ; WX 488 ; N uni1D33 ; G 2199
+U 7476 ; WX 474 ; N uni1D34 ; G 2200
+U 7477 ; WX 186 ; N uni1D35 ; G 2201
+U 7478 ; WX 186 ; N uni1D36 ; G 2202
+U 7479 ; WX 413 ; N uni1D37 ; G 2203
+U 7480 ; WX 351 ; N uni1D38 ; G 2204
+U 7481 ; WX 543 ; N uni1D39 ; G 2205
+U 7482 ; WX 471 ; N uni1D3A ; G 2206
+U 7483 ; WX 471 ; N uni1D3B ; G 2207
+U 7484 ; WX 496 ; N uni1D3C ; G 2208
+U 7485 ; WX 439 ; N uni1D3D ; G 2209
+U 7486 ; WX 380 ; N uni1D3E ; G 2210
+U 7487 ; WX 438 ; N uni1D3F ; G 2211
+U 7488 ; WX 385 ; N uni1D40 ; G 2212
+U 7489 ; WX 461 ; N uni1D41 ; G 2213
+U 7490 ; WX 623 ; N uni1D42 ; G 2214
+U 7491 ; WX 392 ; N uni1D43 ; G 2215
+U 7492 ; WX 392 ; N uni1D44 ; G 2216
+U 7493 ; WX 405 ; N uni1D45 ; G 2217
+U 7494 ; WX 648 ; N uni1D46 ; G 2218
+U 7495 ; WX 428 ; N uni1D47 ; G 2219
+U 7496 ; WX 405 ; N uni1D48 ; G 2220
+U 7497 ; WX 417 ; N uni1D49 ; G 2221
+U 7498 ; WX 417 ; N uni1D4A ; G 2222
+U 7499 ; WX 360 ; N uni1D4B ; G 2223
+U 7500 ; WX 359 ; N uni1D4C ; G 2224
+U 7501 ; WX 405 ; N uni1D4D ; G 2225
+U 7502 ; WX 179 ; N uni1D4E ; G 2226
+U 7503 ; WX 426 ; N uni1D4F ; G 2227
+U 7504 ; WX 623 ; N uni1D50 ; G 2228
+U 7505 ; WX 409 ; N uni1D51 ; G 2229
+U 7506 ; WX 414 ; N uni1D52 ; G 2230
+U 7507 ; WX 370 ; N uni1D53 ; G 2231
+U 7508 ; WX 414 ; N uni1D54 ; G 2232
+U 7509 ; WX 414 ; N uni1D55 ; G 2233
+U 7510 ; WX 428 ; N uni1D56 ; G 2234
+U 7511 ; WX 295 ; N uni1D57 ; G 2235
+U 7512 ; WX 405 ; N uni1D58 ; G 2236
+U 7513 ; WX 470 ; N uni1D59 ; G 2237
+U 7514 ; WX 623 ; N uni1D5A ; G 2238
+U 7515 ; WX 417 ; N uni1D5B ; G 2239
+U 7517 ; WX 402 ; N uni1D5D ; G 2240
+U 7518 ; WX 373 ; N uni1D5E ; G 2241
+U 7519 ; WX 385 ; N uni1D5F ; G 2242
+U 7520 ; WX 416 ; N uni1D60 ; G 2243
+U 7521 ; WX 364 ; N uni1D61 ; G 2244
+U 7522 ; WX 179 ; N uni1D62 ; G 2245
+U 7523 ; WX 259 ; N uni1D63 ; G 2246
+U 7524 ; WX 405 ; N uni1D64 ; G 2247
+U 7525 ; WX 417 ; N uni1D65 ; G 2248
+U 7526 ; WX 402 ; N uni1D66 ; G 2249
+U 7527 ; WX 373 ; N uni1D67 ; G 2250
+U 7528 ; WX 412 ; N uni1D68 ; G 2251
+U 7529 ; WX 416 ; N uni1D69 ; G 2252
+U 7530 ; WX 364 ; N uni1D6A ; G 2253
+U 7543 ; WX 635 ; N uni1D77 ; G 2254
+U 7544 ; WX 474 ; N uni1D78 ; G 2255
+U 7547 ; WX 372 ; N uni1D7B ; G 2256
+U 7549 ; WX 667 ; N uni1D7D ; G 2257
+U 7557 ; WX 278 ; N uni1D85 ; G 2258
+U 7579 ; WX 405 ; N uni1D9B ; G 2259
+U 7580 ; WX 370 ; N uni1D9C ; G 2260
+U 7581 ; WX 370 ; N uni1D9D ; G 2261
+U 7582 ; WX 414 ; N uni1D9E ; G 2262
+U 7583 ; WX 360 ; N uni1D9F ; G 2263
+U 7584 ; WX 296 ; N uni1DA0 ; G 2264
+U 7585 ; WX 233 ; N uni1DA1 ; G 2265
+U 7586 ; WX 405 ; N uni1DA2 ; G 2266
+U 7587 ; WX 405 ; N uni1DA3 ; G 2267
+U 7588 ; WX 261 ; N uni1DA4 ; G 2268
+U 7589 ; WX 250 ; N uni1DA5 ; G 2269
+U 7590 ; WX 261 ; N uni1DA6 ; G 2270
+U 7591 ; WX 261 ; N uni1DA7 ; G 2271
+U 7592 ; WX 234 ; N uni1DA8 ; G 2272
+U 7593 ; WX 250 ; N uni1DA9 ; G 2273
+U 7594 ; WX 235 ; N uni1DAA ; G 2274
+U 7595 ; WX 376 ; N uni1DAB ; G 2275
+U 7596 ; WX 623 ; N uni1DAC ; G 2276
+U 7597 ; WX 623 ; N uni1DAD ; G 2277
+U 7598 ; WX 411 ; N uni1DAE ; G 2278
+U 7599 ; WX 479 ; N uni1DAF ; G 2279
+U 7600 ; WX 409 ; N uni1DB0 ; G 2280
+U 7601 ; WX 414 ; N uni1DB1 ; G 2281
+U 7602 ; WX 414 ; N uni1DB2 ; G 2282
+U 7603 ; WX 360 ; N uni1DB3 ; G 2283
+U 7604 ; WX 287 ; N uni1DB4 ; G 2284
+U 7605 ; WX 295 ; N uni1DB5 ; G 2285
+U 7606 ; WX 508 ; N uni1DB6 ; G 2286
+U 7607 ; WX 418 ; N uni1DB7 ; G 2287
+U 7608 ; WX 361 ; N uni1DB8 ; G 2288
+U 7609 ; WX 406 ; N uni1DB9 ; G 2289
+U 7610 ; WX 417 ; N uni1DBA ; G 2290
+U 7611 ; WX 366 ; N uni1DBB ; G 2291
+U 7612 ; WX 437 ; N uni1DBC ; G 2292
+U 7613 ; WX 366 ; N uni1DBD ; G 2293
+U 7614 ; WX 392 ; N uni1DBE ; G 2294
+U 7615 ; WX 414 ; N uni1DBF ; G 2295
+U 7620 ; WX 0 ; N uni1DC4 ; G 2296
+U 7621 ; WX 0 ; N uni1DC5 ; G 2297
+U 7622 ; WX 0 ; N uni1DC6 ; G 2298
+U 7623 ; WX 0 ; N uni1DC7 ; G 2299
+U 7624 ; WX 0 ; N uni1DC8 ; G 2300
+U 7625 ; WX 0 ; N uni1DC9 ; G 2301
+U 7680 ; WX 684 ; N uni1E00 ; G 2302
+U 7681 ; WX 613 ; N uni1E01 ; G 2303
+U 7682 ; WX 686 ; N uni1E02 ; G 2304
+U 7683 ; WX 635 ; N uni1E03 ; G 2305
+U 7684 ; WX 686 ; N uni1E04 ; G 2306
+U 7685 ; WX 635 ; N uni1E05 ; G 2307
+U 7686 ; WX 686 ; N uni1E06 ; G 2308
+U 7687 ; WX 635 ; N uni1E07 ; G 2309
+U 7688 ; WX 698 ; N uni1E08 ; G 2310
+U 7689 ; WX 550 ; N uni1E09 ; G 2311
+U 7690 ; WX 770 ; N uni1E0A ; G 2312
+U 7691 ; WX 635 ; N uni1E0B ; G 2313
+U 7692 ; WX 770 ; N uni1E0C ; G 2314
+U 7693 ; WX 635 ; N uni1E0D ; G 2315
+U 7694 ; WX 770 ; N uni1E0E ; G 2316
+U 7695 ; WX 635 ; N uni1E0F ; G 2317
+U 7696 ; WX 770 ; N uni1E10 ; G 2318
+U 7697 ; WX 635 ; N uni1E11 ; G 2319
+U 7698 ; WX 770 ; N uni1E12 ; G 2320
+U 7699 ; WX 635 ; N uni1E13 ; G 2321
+U 7700 ; WX 632 ; N uni1E14 ; G 2322
+U 7701 ; WX 615 ; N uni1E15 ; G 2323
+U 7702 ; WX 632 ; N uni1E16 ; G 2324
+U 7703 ; WX 615 ; N uni1E17 ; G 2325
+U 7704 ; WX 632 ; N uni1E18 ; G 2326
+U 7705 ; WX 615 ; N uni1E19 ; G 2327
+U 7706 ; WX 632 ; N uni1E1A ; G 2328
+U 7707 ; WX 615 ; N uni1E1B ; G 2329
+U 7708 ; WX 632 ; N uni1E1C ; G 2330
+U 7709 ; WX 615 ; N uni1E1D ; G 2331
+U 7710 ; WX 575 ; N uni1E1E ; G 2332
+U 7711 ; WX 352 ; N uni1E1F ; G 2333
+U 7712 ; WX 775 ; N uni1E20 ; G 2334
+U 7713 ; WX 635 ; N uni1E21 ; G 2335
+U 7714 ; WX 752 ; N uni1E22 ; G 2336
+U 7715 ; WX 634 ; N uni1E23 ; G 2337
+U 7716 ; WX 752 ; N uni1E24 ; G 2338
+U 7717 ; WX 634 ; N uni1E25 ; G 2339
+U 7718 ; WX 752 ; N uni1E26 ; G 2340
+U 7719 ; WX 634 ; N uni1E27 ; G 2341
+U 7720 ; WX 752 ; N uni1E28 ; G 2342
+U 7721 ; WX 634 ; N uni1E29 ; G 2343
+U 7722 ; WX 752 ; N uni1E2A ; G 2344
+U 7723 ; WX 634 ; N uni1E2B ; G 2345
+U 7724 ; WX 295 ; N uni1E2C ; G 2346
+U 7725 ; WX 278 ; N uni1E2D ; G 2347
+U 7726 ; WX 295 ; N uni1E2E ; G 2348
+U 7727 ; WX 278 ; N uni1E2F ; G 2349
+U 7728 ; WX 656 ; N uni1E30 ; G 2350
+U 7729 ; WX 579 ; N uni1E31 ; G 2351
+U 7730 ; WX 656 ; N uni1E32 ; G 2352
+U 7731 ; WX 579 ; N uni1E33 ; G 2353
+U 7732 ; WX 656 ; N uni1E34 ; G 2354
+U 7733 ; WX 579 ; N uni1E35 ; G 2355
+U 7734 ; WX 557 ; N uni1E36 ; G 2356
+U 7735 ; WX 288 ; N uni1E37 ; G 2357
+U 7736 ; WX 557 ; N uni1E38 ; G 2358
+U 7737 ; WX 288 ; N uni1E39 ; G 2359
+U 7738 ; WX 557 ; N uni1E3A ; G 2360
+U 7739 ; WX 278 ; N uni1E3B ; G 2361
+U 7740 ; WX 557 ; N uni1E3C ; G 2362
+U 7741 ; WX 278 ; N uni1E3D ; G 2363
+U 7742 ; WX 863 ; N uni1E3E ; G 2364
+U 7743 ; WX 974 ; N uni1E3F ; G 2365
+U 7744 ; WX 863 ; N uni1E40 ; G 2366
+U 7745 ; WX 974 ; N uni1E41 ; G 2367
+U 7746 ; WX 863 ; N uni1E42 ; G 2368
+U 7747 ; WX 974 ; N uni1E43 ; G 2369
+U 7748 ; WX 748 ; N uni1E44 ; G 2370
+U 7749 ; WX 634 ; N uni1E45 ; G 2371
+U 7750 ; WX 748 ; N uni1E46 ; G 2372
+U 7751 ; WX 634 ; N uni1E47 ; G 2373
+U 7752 ; WX 748 ; N uni1E48 ; G 2374
+U 7753 ; WX 634 ; N uni1E49 ; G 2375
+U 7754 ; WX 748 ; N uni1E4A ; G 2376
+U 7755 ; WX 634 ; N uni1E4B ; G 2377
+U 7756 ; WX 787 ; N uni1E4C ; G 2378
+U 7757 ; WX 612 ; N uni1E4D ; G 2379
+U 7758 ; WX 787 ; N uni1E4E ; G 2380
+U 7759 ; WX 612 ; N uni1E4F ; G 2381
+U 7760 ; WX 787 ; N uni1E50 ; G 2382
+U 7761 ; WX 612 ; N uni1E51 ; G 2383
+U 7762 ; WX 787 ; N uni1E52 ; G 2384
+U 7763 ; WX 612 ; N uni1E53 ; G 2385
+U 7764 ; WX 603 ; N uni1E54 ; G 2386
+U 7765 ; WX 635 ; N uni1E55 ; G 2387
+U 7766 ; WX 603 ; N uni1E56 ; G 2388
+U 7767 ; WX 635 ; N uni1E57 ; G 2389
+U 7768 ; WX 695 ; N uni1E58 ; G 2390
+U 7769 ; WX 411 ; N uni1E59 ; G 2391
+U 7770 ; WX 695 ; N uni1E5A ; G 2392
+U 7771 ; WX 411 ; N uni1E5B ; G 2393
+U 7772 ; WX 695 ; N uni1E5C ; G 2394
+U 7773 ; WX 411 ; N uni1E5D ; G 2395
+U 7774 ; WX 695 ; N uni1E5E ; G 2396
+U 7775 ; WX 411 ; N uni1E5F ; G 2397
+U 7776 ; WX 635 ; N uni1E60 ; G 2398
+U 7777 ; WX 521 ; N uni1E61 ; G 2399
+U 7778 ; WX 635 ; N uni1E62 ; G 2400
+U 7779 ; WX 521 ; N uni1E63 ; G 2401
+U 7780 ; WX 635 ; N uni1E64 ; G 2402
+U 7781 ; WX 521 ; N uni1E65 ; G 2403
+U 7782 ; WX 635 ; N uni1E66 ; G 2404
+U 7783 ; WX 521 ; N uni1E67 ; G 2405
+U 7784 ; WX 635 ; N uni1E68 ; G 2406
+U 7785 ; WX 521 ; N uni1E69 ; G 2407
+U 7786 ; WX 611 ; N uni1E6A ; G 2408
+U 7787 ; WX 392 ; N uni1E6B ; G 2409
+U 7788 ; WX 611 ; N uni1E6C ; G 2410
+U 7789 ; WX 392 ; N uni1E6D ; G 2411
+U 7790 ; WX 611 ; N uni1E6E ; G 2412
+U 7791 ; WX 392 ; N uni1E6F ; G 2413
+U 7792 ; WX 611 ; N uni1E70 ; G 2414
+U 7793 ; WX 392 ; N uni1E71 ; G 2415
+U 7794 ; WX 732 ; N uni1E72 ; G 2416
+U 7795 ; WX 634 ; N uni1E73 ; G 2417
+U 7796 ; WX 732 ; N uni1E74 ; G 2418
+U 7797 ; WX 634 ; N uni1E75 ; G 2419
+U 7798 ; WX 732 ; N uni1E76 ; G 2420
+U 7799 ; WX 634 ; N uni1E77 ; G 2421
+U 7800 ; WX 732 ; N uni1E78 ; G 2422
+U 7801 ; WX 634 ; N uni1E79 ; G 2423
+U 7802 ; WX 732 ; N uni1E7A ; G 2424
+U 7803 ; WX 634 ; N uni1E7B ; G 2425
+U 7804 ; WX 684 ; N uni1E7C ; G 2426
+U 7805 ; WX 592 ; N uni1E7D ; G 2427
+U 7806 ; WX 684 ; N uni1E7E ; G 2428
+U 7807 ; WX 592 ; N uni1E7F ; G 2429
+U 7808 ; WX 989 ; N Wgrave ; G 2430
+U 7809 ; WX 818 ; N wgrave ; G 2431
+U 7810 ; WX 989 ; N Wacute ; G 2432
+U 7811 ; WX 818 ; N wacute ; G 2433
+U 7812 ; WX 989 ; N Wdieresis ; G 2434
+U 7813 ; WX 818 ; N wdieresis ; G 2435
+U 7814 ; WX 989 ; N uni1E86 ; G 2436
+U 7815 ; WX 818 ; N uni1E87 ; G 2437
+U 7816 ; WX 989 ; N uni1E88 ; G 2438
+U 7817 ; WX 818 ; N uni1E89 ; G 2439
+U 7818 ; WX 685 ; N uni1E8A ; G 2440
+U 7819 ; WX 592 ; N uni1E8B ; G 2441
+U 7820 ; WX 685 ; N uni1E8C ; G 2442
+U 7821 ; WX 592 ; N uni1E8D ; G 2443
+U 7822 ; WX 611 ; N uni1E8E ; G 2444
+U 7823 ; WX 592 ; N uni1E8F ; G 2445
+U 7824 ; WX 685 ; N uni1E90 ; G 2446
+U 7825 ; WX 525 ; N uni1E91 ; G 2447
+U 7826 ; WX 685 ; N uni1E92 ; G 2448
+U 7827 ; WX 525 ; N uni1E93 ; G 2449
+U 7828 ; WX 685 ; N uni1E94 ; G 2450
+U 7829 ; WX 525 ; N uni1E95 ; G 2451
+U 7830 ; WX 634 ; N uni1E96 ; G 2452
+U 7831 ; WX 392 ; N uni1E97 ; G 2453
+U 7832 ; WX 818 ; N uni1E98 ; G 2454
+U 7833 ; WX 592 ; N uni1E99 ; G 2455
+U 7834 ; WX 613 ; N uni1E9A ; G 2456
+U 7835 ; WX 352 ; N uni1E9B ; G 2457
+U 7836 ; WX 352 ; N uni1E9C ; G 2458
+U 7837 ; WX 352 ; N uni1E9D ; G 2459
+U 7838 ; WX 769 ; N uni1E9E ; G 2460
+U 7839 ; WX 612 ; N uni1E9F ; G 2461
+U 7840 ; WX 684 ; N uni1EA0 ; G 2462
+U 7841 ; WX 613 ; N uni1EA1 ; G 2463
+U 7842 ; WX 684 ; N uni1EA2 ; G 2464
+U 7843 ; WX 613 ; N uni1EA3 ; G 2465
+U 7844 ; WX 684 ; N uni1EA4 ; G 2466
+U 7845 ; WX 613 ; N uni1EA5 ; G 2467
+U 7846 ; WX 684 ; N uni1EA6 ; G 2468
+U 7847 ; WX 613 ; N uni1EA7 ; G 2469
+U 7848 ; WX 684 ; N uni1EA8 ; G 2470
+U 7849 ; WX 613 ; N uni1EA9 ; G 2471
+U 7850 ; WX 684 ; N uni1EAA ; G 2472
+U 7851 ; WX 613 ; N uni1EAB ; G 2473
+U 7852 ; WX 684 ; N uni1EAC ; G 2474
+U 7853 ; WX 613 ; N uni1EAD ; G 2475
+U 7854 ; WX 684 ; N uni1EAE ; G 2476
+U 7855 ; WX 613 ; N uni1EAF ; G 2477
+U 7856 ; WX 684 ; N uni1EB0 ; G 2478
+U 7857 ; WX 613 ; N uni1EB1 ; G 2479
+U 7858 ; WX 684 ; N uni1EB2 ; G 2480
+U 7859 ; WX 613 ; N uni1EB3 ; G 2481
+U 7860 ; WX 684 ; N uni1EB4 ; G 2482
+U 7861 ; WX 613 ; N uni1EB5 ; G 2483
+U 7862 ; WX 684 ; N uni1EB6 ; G 2484
+U 7863 ; WX 613 ; N uni1EB7 ; G 2485
+U 7864 ; WX 632 ; N uni1EB8 ; G 2486
+U 7865 ; WX 615 ; N uni1EB9 ; G 2487
+U 7866 ; WX 632 ; N uni1EBA ; G 2488
+U 7867 ; WX 615 ; N uni1EBB ; G 2489
+U 7868 ; WX 632 ; N uni1EBC ; G 2490
+U 7869 ; WX 615 ; N uni1EBD ; G 2491
+U 7870 ; WX 632 ; N uni1EBE ; G 2492
+U 7871 ; WX 615 ; N uni1EBF ; G 2493
+U 7872 ; WX 632 ; N uni1EC0 ; G 2494
+U 7873 ; WX 615 ; N uni1EC1 ; G 2495
+U 7874 ; WX 632 ; N uni1EC2 ; G 2496
+U 7875 ; WX 615 ; N uni1EC3 ; G 2497
+U 7876 ; WX 632 ; N uni1EC4 ; G 2498
+U 7877 ; WX 615 ; N uni1EC5 ; G 2499
+U 7878 ; WX 632 ; N uni1EC6 ; G 2500
+U 7879 ; WX 615 ; N uni1EC7 ; G 2501
+U 7880 ; WX 295 ; N uni1EC8 ; G 2502
+U 7881 ; WX 278 ; N uni1EC9 ; G 2503
+U 7882 ; WX 295 ; N uni1ECA ; G 2504
+U 7883 ; WX 278 ; N uni1ECB ; G 2505
+U 7884 ; WX 787 ; N uni1ECC ; G 2506
+U 7885 ; WX 612 ; N uni1ECD ; G 2507
+U 7886 ; WX 787 ; N uni1ECE ; G 2508
+U 7887 ; WX 612 ; N uni1ECF ; G 2509
+U 7888 ; WX 787 ; N uni1ED0 ; G 2510
+U 7889 ; WX 612 ; N uni1ED1 ; G 2511
+U 7890 ; WX 787 ; N uni1ED2 ; G 2512
+U 7891 ; WX 612 ; N uni1ED3 ; G 2513
+U 7892 ; WX 787 ; N uni1ED4 ; G 2514
+U 7893 ; WX 612 ; N uni1ED5 ; G 2515
+U 7894 ; WX 787 ; N uni1ED6 ; G 2516
+U 7895 ; WX 612 ; N uni1ED7 ; G 2517
+U 7896 ; WX 787 ; N uni1ED8 ; G 2518
+U 7897 ; WX 612 ; N uni1ED9 ; G 2519
+U 7898 ; WX 913 ; N uni1EDA ; G 2520
+U 7899 ; WX 612 ; N uni1EDB ; G 2521
+U 7900 ; WX 913 ; N uni1EDC ; G 2522
+U 7901 ; WX 612 ; N uni1EDD ; G 2523
+U 7902 ; WX 913 ; N uni1EDE ; G 2524
+U 7903 ; WX 612 ; N uni1EDF ; G 2525
+U 7904 ; WX 913 ; N uni1EE0 ; G 2526
+U 7905 ; WX 612 ; N uni1EE1 ; G 2527
+U 7906 ; WX 913 ; N uni1EE2 ; G 2528
+U 7907 ; WX 612 ; N uni1EE3 ; G 2529
+U 7908 ; WX 732 ; N uni1EE4 ; G 2530
+U 7909 ; WX 634 ; N uni1EE5 ; G 2531
+U 7910 ; WX 732 ; N uni1EE6 ; G 2532
+U 7911 ; WX 634 ; N uni1EE7 ; G 2533
+U 7912 ; WX 858 ; N uni1EE8 ; G 2534
+U 7913 ; WX 634 ; N uni1EE9 ; G 2535
+U 7914 ; WX 858 ; N uni1EEA ; G 2536
+U 7915 ; WX 634 ; N uni1EEB ; G 2537
+U 7916 ; WX 858 ; N uni1EEC ; G 2538
+U 7917 ; WX 634 ; N uni1EED ; G 2539
+U 7918 ; WX 858 ; N uni1EEE ; G 2540
+U 7919 ; WX 634 ; N uni1EEF ; G 2541
+U 7920 ; WX 858 ; N uni1EF0 ; G 2542
+U 7921 ; WX 634 ; N uni1EF1 ; G 2543
+U 7922 ; WX 611 ; N Ygrave ; G 2544
+U 7923 ; WX 592 ; N ygrave ; G 2545
+U 7924 ; WX 611 ; N uni1EF4 ; G 2546
+U 7925 ; WX 592 ; N uni1EF5 ; G 2547
+U 7926 ; WX 611 ; N uni1EF6 ; G 2548
+U 7927 ; WX 592 ; N uni1EF7 ; G 2549
+U 7928 ; WX 611 ; N uni1EF8 ; G 2550
+U 7929 ; WX 592 ; N uni1EF9 ; G 2551
+U 7930 ; WX 769 ; N uni1EFA ; G 2552
+U 7931 ; WX 477 ; N uni1EFB ; G 2553
+U 7936 ; WX 659 ; N uni1F00 ; G 2554
+U 7937 ; WX 659 ; N uni1F01 ; G 2555
+U 7938 ; WX 659 ; N uni1F02 ; G 2556
+U 7939 ; WX 659 ; N uni1F03 ; G 2557
+U 7940 ; WX 659 ; N uni1F04 ; G 2558
+U 7941 ; WX 659 ; N uni1F05 ; G 2559
+U 7942 ; WX 659 ; N uni1F06 ; G 2560
+U 7943 ; WX 659 ; N uni1F07 ; G 2561
+U 7944 ; WX 684 ; N uni1F08 ; G 2562
+U 7945 ; WX 684 ; N uni1F09 ; G 2563
+U 7946 ; WX 877 ; N uni1F0A ; G 2564
+U 7947 ; WX 877 ; N uni1F0B ; G 2565
+U 7948 ; WX 769 ; N uni1F0C ; G 2566
+U 7949 ; WX 801 ; N uni1F0D ; G 2567
+U 7950 ; WX 708 ; N uni1F0E ; G 2568
+U 7951 ; WX 743 ; N uni1F0F ; G 2569
+U 7952 ; WX 541 ; N uni1F10 ; G 2570
+U 7953 ; WX 541 ; N uni1F11 ; G 2571
+U 7954 ; WX 541 ; N uni1F12 ; G 2572
+U 7955 ; WX 541 ; N uni1F13 ; G 2573
+U 7956 ; WX 541 ; N uni1F14 ; G 2574
+U 7957 ; WX 541 ; N uni1F15 ; G 2575
+U 7960 ; WX 711 ; N uni1F18 ; G 2576
+U 7961 ; WX 711 ; N uni1F19 ; G 2577
+U 7962 ; WX 966 ; N uni1F1A ; G 2578
+U 7963 ; WX 975 ; N uni1F1B ; G 2579
+U 7964 ; WX 898 ; N uni1F1C ; G 2580
+U 7965 ; WX 928 ; N uni1F1D ; G 2581
+U 7968 ; WX 634 ; N uni1F20 ; G 2582
+U 7969 ; WX 634 ; N uni1F21 ; G 2583
+U 7970 ; WX 634 ; N uni1F22 ; G 2584
+U 7971 ; WX 634 ; N uni1F23 ; G 2585
+U 7972 ; WX 634 ; N uni1F24 ; G 2586
+U 7973 ; WX 634 ; N uni1F25 ; G 2587
+U 7974 ; WX 634 ; N uni1F26 ; G 2588
+U 7975 ; WX 634 ; N uni1F27 ; G 2589
+U 7976 ; WX 837 ; N uni1F28 ; G 2590
+U 7977 ; WX 835 ; N uni1F29 ; G 2591
+U 7978 ; WX 1086 ; N uni1F2A ; G 2592
+U 7979 ; WX 1089 ; N uni1F2B ; G 2593
+U 7980 ; WX 1027 ; N uni1F2C ; G 2594
+U 7981 ; WX 1051 ; N uni1F2D ; G 2595
+U 7982 ; WX 934 ; N uni1F2E ; G 2596
+U 7983 ; WX 947 ; N uni1F2F ; G 2597
+U 7984 ; WX 338 ; N uni1F30 ; G 2598
+U 7985 ; WX 338 ; N uni1F31 ; G 2599
+U 7986 ; WX 338 ; N uni1F32 ; G 2600
+U 7987 ; WX 338 ; N uni1F33 ; G 2601
+U 7988 ; WX 338 ; N uni1F34 ; G 2602
+U 7989 ; WX 338 ; N uni1F35 ; G 2603
+U 7990 ; WX 338 ; N uni1F36 ; G 2604
+U 7991 ; WX 338 ; N uni1F37 ; G 2605
+U 7992 ; WX 380 ; N uni1F38 ; G 2606
+U 7993 ; WX 374 ; N uni1F39 ; G 2607
+U 7994 ; WX 635 ; N uni1F3A ; G 2608
+U 7995 ; WX 635 ; N uni1F3B ; G 2609
+U 7996 ; WX 570 ; N uni1F3C ; G 2610
+U 7997 ; WX 600 ; N uni1F3D ; G 2611
+U 7998 ; WX 489 ; N uni1F3E ; G 2612
+U 7999 ; WX 493 ; N uni1F3F ; G 2613
+U 8000 ; WX 612 ; N uni1F40 ; G 2614
+U 8001 ; WX 612 ; N uni1F41 ; G 2615
+U 8002 ; WX 612 ; N uni1F42 ; G 2616
+U 8003 ; WX 612 ; N uni1F43 ; G 2617
+U 8004 ; WX 612 ; N uni1F44 ; G 2618
+U 8005 ; WX 612 ; N uni1F45 ; G 2619
+U 8008 ; WX 804 ; N uni1F48 ; G 2620
+U 8009 ; WX 848 ; N uni1F49 ; G 2621
+U 8010 ; WX 1095 ; N uni1F4A ; G 2622
+U 8011 ; WX 1100 ; N uni1F4B ; G 2623
+U 8012 ; WX 938 ; N uni1F4C ; G 2624
+U 8013 ; WX 970 ; N uni1F4D ; G 2625
+U 8016 ; WX 579 ; N uni1F50 ; G 2626
+U 8017 ; WX 579 ; N uni1F51 ; G 2627
+U 8018 ; WX 579 ; N uni1F52 ; G 2628
+U 8019 ; WX 579 ; N uni1F53 ; G 2629
+U 8020 ; WX 579 ; N uni1F54 ; G 2630
+U 8021 ; WX 579 ; N uni1F55 ; G 2631
+U 8022 ; WX 579 ; N uni1F56 ; G 2632
+U 8023 ; WX 579 ; N uni1F57 ; G 2633
+U 8025 ; WX 784 ; N uni1F59 ; G 2634
+U 8027 ; WX 998 ; N uni1F5B ; G 2635
+U 8029 ; WX 1012 ; N uni1F5D ; G 2636
+U 8031 ; WX 897 ; N uni1F5F ; G 2637
+U 8032 ; WX 837 ; N uni1F60 ; G 2638
+U 8033 ; WX 837 ; N uni1F61 ; G 2639
+U 8034 ; WX 837 ; N uni1F62 ; G 2640
+U 8035 ; WX 837 ; N uni1F63 ; G 2641
+U 8036 ; WX 837 ; N uni1F64 ; G 2642
+U 8037 ; WX 837 ; N uni1F65 ; G 2643
+U 8038 ; WX 837 ; N uni1F66 ; G 2644
+U 8039 ; WX 837 ; N uni1F67 ; G 2645
+U 8040 ; WX 802 ; N uni1F68 ; G 2646
+U 8041 ; WX 843 ; N uni1F69 ; G 2647
+U 8042 ; WX 1089 ; N uni1F6A ; G 2648
+U 8043 ; WX 1095 ; N uni1F6B ; G 2649
+U 8044 ; WX 946 ; N uni1F6C ; G 2650
+U 8045 ; WX 972 ; N uni1F6D ; G 2651
+U 8046 ; WX 921 ; N uni1F6E ; G 2652
+U 8047 ; WX 952 ; N uni1F6F ; G 2653
+U 8048 ; WX 659 ; N uni1F70 ; G 2654
+U 8049 ; WX 659 ; N uni1F71 ; G 2655
+U 8050 ; WX 541 ; N uni1F72 ; G 2656
+U 8051 ; WX 548 ; N uni1F73 ; G 2657
+U 8052 ; WX 634 ; N uni1F74 ; G 2658
+U 8053 ; WX 654 ; N uni1F75 ; G 2659
+U 8054 ; WX 338 ; N uni1F76 ; G 2660
+U 8055 ; WX 338 ; N uni1F77 ; G 2661
+U 8056 ; WX 612 ; N uni1F78 ; G 2662
+U 8057 ; WX 612 ; N uni1F79 ; G 2663
+U 8058 ; WX 579 ; N uni1F7A ; G 2664
+U 8059 ; WX 579 ; N uni1F7B ; G 2665
+U 8060 ; WX 837 ; N uni1F7C ; G 2666
+U 8061 ; WX 837 ; N uni1F7D ; G 2667
+U 8064 ; WX 659 ; N uni1F80 ; G 2668
+U 8065 ; WX 659 ; N uni1F81 ; G 2669
+U 8066 ; WX 659 ; N uni1F82 ; G 2670
+U 8067 ; WX 659 ; N uni1F83 ; G 2671
+U 8068 ; WX 659 ; N uni1F84 ; G 2672
+U 8069 ; WX 659 ; N uni1F85 ; G 2673
+U 8070 ; WX 659 ; N uni1F86 ; G 2674
+U 8071 ; WX 659 ; N uni1F87 ; G 2675
+U 8072 ; WX 684 ; N uni1F88 ; G 2676
+U 8073 ; WX 684 ; N uni1F89 ; G 2677
+U 8074 ; WX 877 ; N uni1F8A ; G 2678
+U 8075 ; WX 877 ; N uni1F8B ; G 2679
+U 8076 ; WX 769 ; N uni1F8C ; G 2680
+U 8077 ; WX 801 ; N uni1F8D ; G 2681
+U 8078 ; WX 708 ; N uni1F8E ; G 2682
+U 8079 ; WX 743 ; N uni1F8F ; G 2683
+U 8080 ; WX 634 ; N uni1F90 ; G 2684
+U 8081 ; WX 634 ; N uni1F91 ; G 2685
+U 8082 ; WX 634 ; N uni1F92 ; G 2686
+U 8083 ; WX 634 ; N uni1F93 ; G 2687
+U 8084 ; WX 634 ; N uni1F94 ; G 2688
+U 8085 ; WX 634 ; N uni1F95 ; G 2689
+U 8086 ; WX 634 ; N uni1F96 ; G 2690
+U 8087 ; WX 634 ; N uni1F97 ; G 2691
+U 8088 ; WX 837 ; N uni1F98 ; G 2692
+U 8089 ; WX 835 ; N uni1F99 ; G 2693
+U 8090 ; WX 1086 ; N uni1F9A ; G 2694
+U 8091 ; WX 1089 ; N uni1F9B ; G 2695
+U 8092 ; WX 1027 ; N uni1F9C ; G 2696
+U 8093 ; WX 1051 ; N uni1F9D ; G 2697
+U 8094 ; WX 934 ; N uni1F9E ; G 2698
+U 8095 ; WX 947 ; N uni1F9F ; G 2699
+U 8096 ; WX 837 ; N uni1FA0 ; G 2700
+U 8097 ; WX 837 ; N uni1FA1 ; G 2701
+U 8098 ; WX 837 ; N uni1FA2 ; G 2702
+U 8099 ; WX 837 ; N uni1FA3 ; G 2703
+U 8100 ; WX 837 ; N uni1FA4 ; G 2704
+U 8101 ; WX 837 ; N uni1FA5 ; G 2705
+U 8102 ; WX 837 ; N uni1FA6 ; G 2706
+U 8103 ; WX 837 ; N uni1FA7 ; G 2707
+U 8104 ; WX 802 ; N uni1FA8 ; G 2708
+U 8105 ; WX 843 ; N uni1FA9 ; G 2709
+U 8106 ; WX 1089 ; N uni1FAA ; G 2710
+U 8107 ; WX 1095 ; N uni1FAB ; G 2711
+U 8108 ; WX 946 ; N uni1FAC ; G 2712
+U 8109 ; WX 972 ; N uni1FAD ; G 2713
+U 8110 ; WX 921 ; N uni1FAE ; G 2714
+U 8111 ; WX 952 ; N uni1FAF ; G 2715
+U 8112 ; WX 659 ; N uni1FB0 ; G 2716
+U 8113 ; WX 659 ; N uni1FB1 ; G 2717
+U 8114 ; WX 659 ; N uni1FB2 ; G 2718
+U 8115 ; WX 659 ; N uni1FB3 ; G 2719
+U 8116 ; WX 659 ; N uni1FB4 ; G 2720
+U 8118 ; WX 659 ; N uni1FB6 ; G 2721
+U 8119 ; WX 659 ; N uni1FB7 ; G 2722
+U 8120 ; WX 684 ; N uni1FB8 ; G 2723
+U 8121 ; WX 684 ; N uni1FB9 ; G 2724
+U 8122 ; WX 716 ; N uni1FBA ; G 2725
+U 8123 ; WX 692 ; N uni1FBB ; G 2726
+U 8124 ; WX 684 ; N uni1FBC ; G 2727
+U 8125 ; WX 500 ; N uni1FBD ; G 2728
+U 8126 ; WX 500 ; N uni1FBE ; G 2729
+U 8127 ; WX 500 ; N uni1FBF ; G 2730
+U 8128 ; WX 500 ; N uni1FC0 ; G 2731
+U 8129 ; WX 500 ; N uni1FC1 ; G 2732
+U 8130 ; WX 634 ; N uni1FC2 ; G 2733
+U 8131 ; WX 634 ; N uni1FC3 ; G 2734
+U 8132 ; WX 654 ; N uni1FC4 ; G 2735
+U 8134 ; WX 634 ; N uni1FC6 ; G 2736
+U 8135 ; WX 634 ; N uni1FC7 ; G 2737
+U 8136 ; WX 805 ; N uni1FC8 ; G 2738
+U 8137 ; WX 746 ; N uni1FC9 ; G 2739
+U 8138 ; WX 931 ; N uni1FCA ; G 2740
+U 8139 ; WX 871 ; N uni1FCB ; G 2741
+U 8140 ; WX 752 ; N uni1FCC ; G 2742
+U 8141 ; WX 500 ; N uni1FCD ; G 2743
+U 8142 ; WX 500 ; N uni1FCE ; G 2744
+U 8143 ; WX 500 ; N uni1FCF ; G 2745
+U 8144 ; WX 338 ; N uni1FD0 ; G 2746
+U 8145 ; WX 338 ; N uni1FD1 ; G 2747
+U 8146 ; WX 338 ; N uni1FD2 ; G 2748
+U 8147 ; WX 338 ; N uni1FD3 ; G 2749
+U 8150 ; WX 338 ; N uni1FD6 ; G 2750
+U 8151 ; WX 338 ; N uni1FD7 ; G 2751
+U 8152 ; WX 295 ; N uni1FD8 ; G 2752
+U 8153 ; WX 295 ; N uni1FD9 ; G 2753
+U 8154 ; WX 475 ; N uni1FDA ; G 2754
+U 8155 ; WX 408 ; N uni1FDB ; G 2755
+U 8157 ; WX 500 ; N uni1FDD ; G 2756
+U 8158 ; WX 500 ; N uni1FDE ; G 2757
+U 8159 ; WX 500 ; N uni1FDF ; G 2758
+U 8160 ; WX 579 ; N uni1FE0 ; G 2759
+U 8161 ; WX 579 ; N uni1FE1 ; G 2760
+U 8162 ; WX 579 ; N uni1FE2 ; G 2761
+U 8163 ; WX 579 ; N uni1FE3 ; G 2762
+U 8164 ; WX 635 ; N uni1FE4 ; G 2763
+U 8165 ; WX 635 ; N uni1FE5 ; G 2764
+U 8166 ; WX 579 ; N uni1FE6 ; G 2765
+U 8167 ; WX 579 ; N uni1FE7 ; G 2766
+U 8168 ; WX 611 ; N uni1FE8 ; G 2767
+U 8169 ; WX 611 ; N uni1FE9 ; G 2768
+U 8170 ; WX 845 ; N uni1FEA ; G 2769
+U 8171 ; WX 825 ; N uni1FEB ; G 2770
+U 8172 ; WX 685 ; N uni1FEC ; G 2771
+U 8173 ; WX 500 ; N uni1FED ; G 2772
+U 8174 ; WX 500 ; N uni1FEE ; G 2773
+U 8175 ; WX 500 ; N uni1FEF ; G 2774
+U 8178 ; WX 837 ; N uni1FF2 ; G 2775
+U 8179 ; WX 837 ; N uni1FF3 ; G 2776
+U 8180 ; WX 837 ; N uni1FF4 ; G 2777
+U 8182 ; WX 837 ; N uni1FF6 ; G 2778
+U 8183 ; WX 837 ; N uni1FF7 ; G 2779
+U 8184 ; WX 941 ; N uni1FF8 ; G 2780
+U 8185 ; WX 813 ; N uni1FF9 ; G 2781
+U 8186 ; WX 922 ; N uni1FFA ; G 2782
+U 8187 ; WX 826 ; N uni1FFB ; G 2783
+U 8188 ; WX 764 ; N uni1FFC ; G 2784
+U 8189 ; WX 500 ; N uni1FFD ; G 2785
+U 8190 ; WX 500 ; N uni1FFE ; G 2786
+U 8192 ; WX 500 ; N uni2000 ; G 2787
+U 8193 ; WX 1000 ; N uni2001 ; G 2788
+U 8194 ; WX 500 ; N uni2002 ; G 2789
+U 8195 ; WX 1000 ; N uni2003 ; G 2790
+U 8196 ; WX 330 ; N uni2004 ; G 2791
+U 8197 ; WX 250 ; N uni2005 ; G 2792
+U 8198 ; WX 167 ; N uni2006 ; G 2793
+U 8199 ; WX 636 ; N uni2007 ; G 2794
+U 8200 ; WX 318 ; N uni2008 ; G 2795
+U 8201 ; WX 200 ; N uni2009 ; G 2796
+U 8202 ; WX 100 ; N uni200A ; G 2797
+U 8203 ; WX 0 ; N uni200B ; G 2798
+U 8204 ; WX 0 ; N uni200C ; G 2799
+U 8205 ; WX 0 ; N uni200D ; G 2800
+U 8206 ; WX 0 ; N uni200E ; G 2801
+U 8207 ; WX 0 ; N uni200F ; G 2802
+U 8208 ; WX 361 ; N uni2010 ; G 2803
+U 8209 ; WX 361 ; N uni2011 ; G 2804
+U 8210 ; WX 636 ; N figuredash ; G 2805
+U 8211 ; WX 500 ; N endash ; G 2806
+U 8212 ; WX 1000 ; N emdash ; G 2807
+U 8213 ; WX 1000 ; N uni2015 ; G 2808
+U 8214 ; WX 500 ; N uni2016 ; G 2809
+U 8215 ; WX 500 ; N underscoredbl ; G 2810
+U 8216 ; WX 318 ; N quoteleft ; G 2811
+U 8217 ; WX 318 ; N quoteright ; G 2812
+U 8218 ; WX 318 ; N quotesinglbase ; G 2813
+U 8219 ; WX 318 ; N quotereversed ; G 2814
+U 8220 ; WX 518 ; N quotedblleft ; G 2815
+U 8221 ; WX 518 ; N quotedblright ; G 2816
+U 8222 ; WX 518 ; N quotedblbase ; G 2817
+U 8223 ; WX 518 ; N uni201F ; G 2818
+U 8224 ; WX 500 ; N dagger ; G 2819
+U 8225 ; WX 500 ; N daggerdbl ; G 2820
+U 8226 ; WX 590 ; N bullet ; G 2821
+U 8227 ; WX 590 ; N uni2023 ; G 2822
+U 8228 ; WX 334 ; N onedotenleader ; G 2823
+U 8229 ; WX 667 ; N twodotenleader ; G 2824
+U 8230 ; WX 1000 ; N ellipsis ; G 2825
+U 8231 ; WX 318 ; N uni2027 ; G 2826
+U 8232 ; WX 0 ; N uni2028 ; G 2827
+U 8233 ; WX 0 ; N uni2029 ; G 2828
+U 8234 ; WX 0 ; N uni202A ; G 2829
+U 8235 ; WX 0 ; N uni202B ; G 2830
+U 8236 ; WX 0 ; N uni202C ; G 2831
+U 8237 ; WX 0 ; N uni202D ; G 2832
+U 8238 ; WX 0 ; N uni202E ; G 2833
+U 8239 ; WX 200 ; N uni202F ; G 2834
+U 8240 ; WX 1342 ; N perthousand ; G 2835
+U 8241 ; WX 1735 ; N uni2031 ; G 2836
+U 8242 ; WX 227 ; N minute ; G 2837
+U 8243 ; WX 374 ; N second ; G 2838
+U 8244 ; WX 520 ; N uni2034 ; G 2839
+U 8245 ; WX 227 ; N uni2035 ; G 2840
+U 8246 ; WX 374 ; N uni2036 ; G 2841
+U 8247 ; WX 520 ; N uni2037 ; G 2842
+U 8248 ; WX 339 ; N uni2038 ; G 2843
+U 8249 ; WX 400 ; N guilsinglleft ; G 2844
+U 8250 ; WX 400 ; N guilsinglright ; G 2845
+U 8251 ; WX 838 ; N uni203B ; G 2846
+U 8252 ; WX 485 ; N exclamdbl ; G 2847
+U 8253 ; WX 531 ; N uni203D ; G 2848
+U 8254 ; WX 500 ; N uni203E ; G 2849
+U 8255 ; WX 804 ; N uni203F ; G 2850
+U 8256 ; WX 804 ; N uni2040 ; G 2851
+U 8257 ; WX 250 ; N uni2041 ; G 2852
+U 8258 ; WX 1000 ; N uni2042 ; G 2853
+U 8259 ; WX 500 ; N uni2043 ; G 2854
+U 8260 ; WX 167 ; N fraction ; G 2855
+U 8261 ; WX 390 ; N uni2045 ; G 2856
+U 8262 ; WX 390 ; N uni2046 ; G 2857
+U 8263 ; WX 922 ; N uni2047 ; G 2858
+U 8264 ; WX 733 ; N uni2048 ; G 2859
+U 8265 ; WX 733 ; N uni2049 ; G 2860
+U 8266 ; WX 497 ; N uni204A ; G 2861
+U 8267 ; WX 636 ; N uni204B ; G 2862
+U 8268 ; WX 500 ; N uni204C ; G 2863
+U 8269 ; WX 500 ; N uni204D ; G 2864
+U 8270 ; WX 500 ; N uni204E ; G 2865
+U 8271 ; WX 337 ; N uni204F ; G 2866
+U 8272 ; WX 804 ; N uni2050 ; G 2867
+U 8273 ; WX 500 ; N uni2051 ; G 2868
+U 8274 ; WX 450 ; N uni2052 ; G 2869
+U 8275 ; WX 1000 ; N uni2053 ; G 2870
+U 8276 ; WX 804 ; N uni2054 ; G 2871
+U 8277 ; WX 838 ; N uni2055 ; G 2872
+U 8278 ; WX 586 ; N uni2056 ; G 2873
+U 8279 ; WX 663 ; N uni2057 ; G 2874
+U 8280 ; WX 838 ; N uni2058 ; G 2875
+U 8281 ; WX 838 ; N uni2059 ; G 2876
+U 8282 ; WX 318 ; N uni205A ; G 2877
+U 8283 ; WX 797 ; N uni205B ; G 2878
+U 8284 ; WX 838 ; N uni205C ; G 2879
+U 8285 ; WX 318 ; N uni205D ; G 2880
+U 8286 ; WX 318 ; N uni205E ; G 2881
+U 8287 ; WX 222 ; N uni205F ; G 2882
+U 8288 ; WX 0 ; N uni2060 ; G 2883
+U 8289 ; WX 0 ; N uni2061 ; G 2884
+U 8290 ; WX 0 ; N uni2062 ; G 2885
+U 8291 ; WX 0 ; N uni2063 ; G 2886
+U 8292 ; WX 0 ; N uni2064 ; G 2887
+U 8298 ; WX 0 ; N uni206A ; G 2888
+U 8299 ; WX 0 ; N uni206B ; G 2889
+U 8300 ; WX 0 ; N uni206C ; G 2890
+U 8301 ; WX 0 ; N uni206D ; G 2891
+U 8302 ; WX 0 ; N uni206E ; G 2892
+U 8303 ; WX 0 ; N uni206F ; G 2893
+U 8304 ; WX 401 ; N uni2070 ; G 2894
+U 8305 ; WX 179 ; N uni2071 ; G 2895
+U 8308 ; WX 401 ; N uni2074 ; G 2896
+U 8309 ; WX 401 ; N uni2075 ; G 2897
+U 8310 ; WX 401 ; N uni2076 ; G 2898
+U 8311 ; WX 401 ; N uni2077 ; G 2899
+U 8312 ; WX 401 ; N uni2078 ; G 2900
+U 8313 ; WX 401 ; N uni2079 ; G 2901
+U 8314 ; WX 528 ; N uni207A ; G 2902
+U 8315 ; WX 528 ; N uni207B ; G 2903
+U 8316 ; WX 528 ; N uni207C ; G 2904
+U 8317 ; WX 246 ; N uni207D ; G 2905
+U 8318 ; WX 246 ; N uni207E ; G 2906
+U 8319 ; WX 398 ; N uni207F ; G 2907
+U 8320 ; WX 401 ; N uni2080 ; G 2908
+U 8321 ; WX 401 ; N uni2081 ; G 2909
+U 8322 ; WX 401 ; N uni2082 ; G 2910
+U 8323 ; WX 401 ; N uni2083 ; G 2911
+U 8324 ; WX 401 ; N uni2084 ; G 2912
+U 8325 ; WX 401 ; N uni2085 ; G 2913
+U 8326 ; WX 401 ; N uni2086 ; G 2914
+U 8327 ; WX 401 ; N uni2087 ; G 2915
+U 8328 ; WX 401 ; N uni2088 ; G 2916
+U 8329 ; WX 401 ; N uni2089 ; G 2917
+U 8330 ; WX 528 ; N uni208A ; G 2918
+U 8331 ; WX 528 ; N uni208B ; G 2919
+U 8332 ; WX 528 ; N uni208C ; G 2920
+U 8333 ; WX 246 ; N uni208D ; G 2921
+U 8334 ; WX 246 ; N uni208E ; G 2922
+U 8336 ; WX 392 ; N uni2090 ; G 2923
+U 8337 ; WX 417 ; N uni2091 ; G 2924
+U 8338 ; WX 414 ; N uni2092 ; G 2925
+U 8339 ; WX 444 ; N uni2093 ; G 2926
+U 8340 ; WX 417 ; N uni2094 ; G 2927
+U 8341 ; WX 404 ; N uni2095 ; G 2928
+U 8342 ; WX 426 ; N uni2096 ; G 2929
+U 8343 ; WX 166 ; N uni2097 ; G 2930
+U 8344 ; WX 623 ; N uni2098 ; G 2931
+U 8345 ; WX 398 ; N uni2099 ; G 2932
+U 8346 ; WX 428 ; N uni209A ; G 2933
+U 8347 ; WX 373 ; N uni209B ; G 2934
+U 8348 ; WX 295 ; N uni209C ; G 2935
+U 8352 ; WX 877 ; N uni20A0 ; G 2936
+U 8353 ; WX 636 ; N colonmonetary ; G 2937
+U 8354 ; WX 636 ; N uni20A2 ; G 2938
+U 8355 ; WX 636 ; N franc ; G 2939
+U 8356 ; WX 636 ; N lira ; G 2940
+U 8357 ; WX 974 ; N uni20A5 ; G 2941
+U 8358 ; WX 636 ; N uni20A6 ; G 2942
+U 8359 ; WX 1272 ; N peseta ; G 2943
+U 8360 ; WX 1074 ; N uni20A8 ; G 2944
+U 8361 ; WX 989 ; N uni20A9 ; G 2945
+U 8362 ; WX 784 ; N uni20AA ; G 2946
+U 8363 ; WX 636 ; N dong ; G 2947
+U 8364 ; WX 636 ; N Euro ; G 2948
+U 8365 ; WX 636 ; N uni20AD ; G 2949
+U 8366 ; WX 636 ; N uni20AE ; G 2950
+U 8367 ; WX 1272 ; N uni20AF ; G 2951
+U 8368 ; WX 636 ; N uni20B0 ; G 2952
+U 8369 ; WX 636 ; N uni20B1 ; G 2953
+U 8370 ; WX 636 ; N uni20B2 ; G 2954
+U 8371 ; WX 636 ; N uni20B3 ; G 2955
+U 8372 ; WX 774 ; N uni20B4 ; G 2956
+U 8373 ; WX 636 ; N uni20B5 ; G 2957
+U 8376 ; WX 636 ; N uni20B8 ; G 2958
+U 8377 ; WX 636 ; N uni20B9 ; G 2959
+U 8378 ; WX 636 ; N uni20BA ; G 2960
+U 8381 ; WX 636 ; N uni20BD ; G 2961
+U 8400 ; WX 0 ; N uni20D0 ; G 2962
+U 8401 ; WX 0 ; N uni20D1 ; G 2963
+U 8406 ; WX 0 ; N uni20D6 ; G 2964
+U 8407 ; WX 0 ; N uni20D7 ; G 2965
+U 8411 ; WX 0 ; N uni20DB ; G 2966
+U 8412 ; WX 0 ; N uni20DC ; G 2967
+U 8417 ; WX 0 ; N uni20E1 ; G 2968
+U 8448 ; WX 1019 ; N uni2100 ; G 2969
+U 8449 ; WX 1019 ; N uni2101 ; G 2970
+U 8450 ; WX 698 ; N uni2102 ; G 2971
+U 8451 ; WX 1123 ; N uni2103 ; G 2972
+U 8452 ; WX 642 ; N uni2104 ; G 2973
+U 8453 ; WX 1019 ; N uni2105 ; G 2974
+U 8454 ; WX 1067 ; N uni2106 ; G 2975
+U 8455 ; WX 614 ; N uni2107 ; G 2976
+U 8456 ; WX 698 ; N uni2108 ; G 2977
+U 8457 ; WX 952 ; N uni2109 ; G 2978
+U 8459 ; WX 988 ; N uni210B ; G 2979
+U 8460 ; WX 754 ; N uni210C ; G 2980
+U 8461 ; WX 850 ; N uni210D ; G 2981
+U 8462 ; WX 634 ; N uni210E ; G 2982
+U 8463 ; WX 634 ; N uni210F ; G 2983
+U 8464 ; WX 470 ; N uni2110 ; G 2984
+U 8465 ; WX 697 ; N Ifraktur ; G 2985
+U 8466 ; WX 720 ; N uni2112 ; G 2986
+U 8467 ; WX 413 ; N uni2113 ; G 2987
+U 8468 ; WX 818 ; N uni2114 ; G 2988
+U 8469 ; WX 801 ; N uni2115 ; G 2989
+U 8470 ; WX 1040 ; N uni2116 ; G 2990
+U 8471 ; WX 1000 ; N uni2117 ; G 2991
+U 8472 ; WX 697 ; N weierstrass ; G 2992
+U 8473 ; WX 701 ; N uni2119 ; G 2993
+U 8474 ; WX 787 ; N uni211A ; G 2994
+U 8475 ; WX 798 ; N uni211B ; G 2995
+U 8476 ; WX 814 ; N Rfraktur ; G 2996
+U 8477 ; WX 792 ; N uni211D ; G 2997
+U 8478 ; WX 896 ; N prescription ; G 2998
+U 8479 ; WX 684 ; N uni211F ; G 2999
+U 8480 ; WX 1020 ; N uni2120 ; G 3000
+U 8481 ; WX 1074 ; N uni2121 ; G 3001
+U 8482 ; WX 1000 ; N trademark ; G 3002
+U 8483 ; WX 684 ; N uni2123 ; G 3003
+U 8484 ; WX 745 ; N uni2124 ; G 3004
+U 8485 ; WX 578 ; N uni2125 ; G 3005
+U 8486 ; WX 764 ; N uni2126 ; G 3006
+U 8487 ; WX 764 ; N uni2127 ; G 3007
+U 8488 ; WX 616 ; N uni2128 ; G 3008
+U 8489 ; WX 338 ; N uni2129 ; G 3009
+U 8490 ; WX 656 ; N uni212A ; G 3010
+U 8491 ; WX 684 ; N uni212B ; G 3011
+U 8492 ; WX 786 ; N uni212C ; G 3012
+U 8493 ; WX 703 ; N uni212D ; G 3013
+U 8494 ; WX 854 ; N estimated ; G 3014
+U 8495 ; WX 592 ; N uni212F ; G 3015
+U 8496 ; WX 605 ; N uni2130 ; G 3016
+U 8497 ; WX 786 ; N uni2131 ; G 3017
+U 8498 ; WX 575 ; N uni2132 ; G 3018
+U 8499 ; WX 1069 ; N uni2133 ; G 3019
+U 8500 ; WX 462 ; N uni2134 ; G 3020
+U 8501 ; WX 745 ; N aleph ; G 3021
+U 8502 ; WX 674 ; N uni2136 ; G 3022
+U 8503 ; WX 466 ; N uni2137 ; G 3023
+U 8504 ; WX 645 ; N uni2138 ; G 3024
+U 8505 ; WX 380 ; N uni2139 ; G 3025
+U 8506 ; WX 926 ; N uni213A ; G 3026
+U 8507 ; WX 1194 ; N uni213B ; G 3027
+U 8508 ; WX 702 ; N uni213C ; G 3028
+U 8509 ; WX 728 ; N uni213D ; G 3029
+U 8510 ; WX 654 ; N uni213E ; G 3030
+U 8511 ; WX 849 ; N uni213F ; G 3031
+U 8512 ; WX 811 ; N uni2140 ; G 3032
+U 8513 ; WX 775 ; N uni2141 ; G 3033
+U 8514 ; WX 557 ; N uni2142 ; G 3034
+U 8515 ; WX 557 ; N uni2143 ; G 3035
+U 8516 ; WX 611 ; N uni2144 ; G 3036
+U 8517 ; WX 819 ; N uni2145 ; G 3037
+U 8518 ; WX 708 ; N uni2146 ; G 3038
+U 8519 ; WX 615 ; N uni2147 ; G 3039
+U 8520 ; WX 351 ; N uni2148 ; G 3040
+U 8521 ; WX 351 ; N uni2149 ; G 3041
+U 8523 ; WX 780 ; N uni214B ; G 3042
+U 8526 ; WX 526 ; N uni214E ; G 3043
+U 8528 ; WX 969 ; N uni2150 ; G 3044
+U 8529 ; WX 969 ; N uni2151 ; G 3045
+U 8530 ; WX 1370 ; N uni2152 ; G 3046
+U 8531 ; WX 969 ; N onethird ; G 3047
+U 8532 ; WX 969 ; N twothirds ; G 3048
+U 8533 ; WX 969 ; N uni2155 ; G 3049
+U 8534 ; WX 969 ; N uni2156 ; G 3050
+U 8535 ; WX 969 ; N uni2157 ; G 3051
+U 8536 ; WX 969 ; N uni2158 ; G 3052
+U 8537 ; WX 969 ; N uni2159 ; G 3053
+U 8538 ; WX 969 ; N uni215A ; G 3054
+U 8539 ; WX 969 ; N oneeighth ; G 3055
+U 8540 ; WX 969 ; N threeeighths ; G 3056
+U 8541 ; WX 969 ; N fiveeighths ; G 3057
+U 8542 ; WX 969 ; N seveneighths ; G 3058
+U 8543 ; WX 568 ; N uni215F ; G 3059
+U 8544 ; WX 295 ; N uni2160 ; G 3060
+U 8545 ; WX 492 ; N uni2161 ; G 3061
+U 8546 ; WX 689 ; N uni2162 ; G 3062
+U 8547 ; WX 923 ; N uni2163 ; G 3063
+U 8548 ; WX 684 ; N uni2164 ; G 3064
+U 8549 ; WX 922 ; N uni2165 ; G 3065
+U 8550 ; WX 1120 ; N uni2166 ; G 3066
+U 8551 ; WX 1317 ; N uni2167 ; G 3067
+U 8552 ; WX 917 ; N uni2168 ; G 3068
+U 8553 ; WX 685 ; N uni2169 ; G 3069
+U 8554 ; WX 933 ; N uni216A ; G 3070
+U 8555 ; WX 1131 ; N uni216B ; G 3071
+U 8556 ; WX 557 ; N uni216C ; G 3072
+U 8557 ; WX 698 ; N uni216D ; G 3073
+U 8558 ; WX 770 ; N uni216E ; G 3074
+U 8559 ; WX 863 ; N uni216F ; G 3075
+U 8560 ; WX 278 ; N uni2170 ; G 3076
+U 8561 ; WX 458 ; N uni2171 ; G 3077
+U 8562 ; WX 637 ; N uni2172 ; G 3078
+U 8563 ; WX 812 ; N uni2173 ; G 3079
+U 8564 ; WX 592 ; N uni2174 ; G 3080
+U 8565 ; WX 811 ; N uni2175 ; G 3081
+U 8566 ; WX 991 ; N uni2176 ; G 3082
+U 8567 ; WX 1170 ; N uni2177 ; G 3083
+U 8568 ; WX 819 ; N uni2178 ; G 3084
+U 8569 ; WX 592 ; N uni2179 ; G 3085
+U 8570 ; WX 822 ; N uni217A ; G 3086
+U 8571 ; WX 1002 ; N uni217B ; G 3087
+U 8572 ; WX 278 ; N uni217C ; G 3088
+U 8573 ; WX 550 ; N uni217D ; G 3089
+U 8574 ; WX 635 ; N uni217E ; G 3090
+U 8575 ; WX 974 ; N uni217F ; G 3091
+U 8576 ; WX 1245 ; N uni2180 ; G 3092
+U 8577 ; WX 770 ; N uni2181 ; G 3093
+U 8578 ; WX 1245 ; N uni2182 ; G 3094
+U 8579 ; WX 703 ; N uni2183 ; G 3095
+U 8580 ; WX 549 ; N uni2184 ; G 3096
+U 8581 ; WX 698 ; N uni2185 ; G 3097
+U 8585 ; WX 969 ; N uni2189 ; G 3098
+U 8592 ; WX 838 ; N arrowleft ; G 3099
+U 8593 ; WX 838 ; N arrowup ; G 3100
+U 8594 ; WX 838 ; N arrowright ; G 3101
+U 8595 ; WX 838 ; N arrowdown ; G 3102
+U 8596 ; WX 838 ; N arrowboth ; G 3103
+U 8597 ; WX 838 ; N arrowupdn ; G 3104
+U 8598 ; WX 838 ; N uni2196 ; G 3105
+U 8599 ; WX 838 ; N uni2197 ; G 3106
+U 8600 ; WX 838 ; N uni2198 ; G 3107
+U 8601 ; WX 838 ; N uni2199 ; G 3108
+U 8602 ; WX 838 ; N uni219A ; G 3109
+U 8603 ; WX 838 ; N uni219B ; G 3110
+U 8604 ; WX 838 ; N uni219C ; G 3111
+U 8605 ; WX 838 ; N uni219D ; G 3112
+U 8606 ; WX 838 ; N uni219E ; G 3113
+U 8607 ; WX 838 ; N uni219F ; G 3114
+U 8608 ; WX 838 ; N uni21A0 ; G 3115
+U 8609 ; WX 838 ; N uni21A1 ; G 3116
+U 8610 ; WX 838 ; N uni21A2 ; G 3117
+U 8611 ; WX 838 ; N uni21A3 ; G 3118
+U 8612 ; WX 838 ; N uni21A4 ; G 3119
+U 8613 ; WX 838 ; N uni21A5 ; G 3120
+U 8614 ; WX 838 ; N uni21A6 ; G 3121
+U 8615 ; WX 838 ; N uni21A7 ; G 3122
+U 8616 ; WX 838 ; N arrowupdnbse ; G 3123
+U 8617 ; WX 838 ; N uni21A9 ; G 3124
+U 8618 ; WX 838 ; N uni21AA ; G 3125
+U 8619 ; WX 838 ; N uni21AB ; G 3126
+U 8620 ; WX 838 ; N uni21AC ; G 3127
+U 8621 ; WX 838 ; N uni21AD ; G 3128
+U 8622 ; WX 838 ; N uni21AE ; G 3129
+U 8623 ; WX 838 ; N uni21AF ; G 3130
+U 8624 ; WX 838 ; N uni21B0 ; G 3131
+U 8625 ; WX 838 ; N uni21B1 ; G 3132
+U 8626 ; WX 838 ; N uni21B2 ; G 3133
+U 8627 ; WX 838 ; N uni21B3 ; G 3134
+U 8628 ; WX 838 ; N uni21B4 ; G 3135
+U 8629 ; WX 838 ; N carriagereturn ; G 3136
+U 8630 ; WX 838 ; N uni21B6 ; G 3137
+U 8631 ; WX 838 ; N uni21B7 ; G 3138
+U 8632 ; WX 838 ; N uni21B8 ; G 3139
+U 8633 ; WX 838 ; N uni21B9 ; G 3140
+U 8634 ; WX 838 ; N uni21BA ; G 3141
+U 8635 ; WX 838 ; N uni21BB ; G 3142
+U 8636 ; WX 838 ; N uni21BC ; G 3143
+U 8637 ; WX 838 ; N uni21BD ; G 3144
+U 8638 ; WX 838 ; N uni21BE ; G 3145
+U 8639 ; WX 838 ; N uni21BF ; G 3146
+U 8640 ; WX 838 ; N uni21C0 ; G 3147
+U 8641 ; WX 838 ; N uni21C1 ; G 3148
+U 8642 ; WX 838 ; N uni21C2 ; G 3149
+U 8643 ; WX 838 ; N uni21C3 ; G 3150
+U 8644 ; WX 838 ; N uni21C4 ; G 3151
+U 8645 ; WX 838 ; N uni21C5 ; G 3152
+U 8646 ; WX 838 ; N uni21C6 ; G 3153
+U 8647 ; WX 838 ; N uni21C7 ; G 3154
+U 8648 ; WX 838 ; N uni21C8 ; G 3155
+U 8649 ; WX 838 ; N uni21C9 ; G 3156
+U 8650 ; WX 838 ; N uni21CA ; G 3157
+U 8651 ; WX 838 ; N uni21CB ; G 3158
+U 8652 ; WX 838 ; N uni21CC ; G 3159
+U 8653 ; WX 838 ; N uni21CD ; G 3160
+U 8654 ; WX 838 ; N uni21CE ; G 3161
+U 8655 ; WX 838 ; N uni21CF ; G 3162
+U 8656 ; WX 838 ; N arrowdblleft ; G 3163
+U 8657 ; WX 838 ; N arrowdblup ; G 3164
+U 8658 ; WX 838 ; N arrowdblright ; G 3165
+U 8659 ; WX 838 ; N arrowdbldown ; G 3166
+U 8660 ; WX 838 ; N arrowdblboth ; G 3167
+U 8661 ; WX 838 ; N uni21D5 ; G 3168
+U 8662 ; WX 838 ; N uni21D6 ; G 3169
+U 8663 ; WX 838 ; N uni21D7 ; G 3170
+U 8664 ; WX 838 ; N uni21D8 ; G 3171
+U 8665 ; WX 838 ; N uni21D9 ; G 3172
+U 8666 ; WX 838 ; N uni21DA ; G 3173
+U 8667 ; WX 838 ; N uni21DB ; G 3174
+U 8668 ; WX 838 ; N uni21DC ; G 3175
+U 8669 ; WX 838 ; N uni21DD ; G 3176
+U 8670 ; WX 838 ; N uni21DE ; G 3177
+U 8671 ; WX 838 ; N uni21DF ; G 3178
+U 8672 ; WX 838 ; N uni21E0 ; G 3179
+U 8673 ; WX 838 ; N uni21E1 ; G 3180
+U 8674 ; WX 838 ; N uni21E2 ; G 3181
+U 8675 ; WX 838 ; N uni21E3 ; G 3182
+U 8676 ; WX 838 ; N uni21E4 ; G 3183
+U 8677 ; WX 838 ; N uni21E5 ; G 3184
+U 8678 ; WX 838 ; N uni21E6 ; G 3185
+U 8679 ; WX 838 ; N uni21E7 ; G 3186
+U 8680 ; WX 838 ; N uni21E8 ; G 3187
+U 8681 ; WX 838 ; N uni21E9 ; G 3188
+U 8682 ; WX 838 ; N uni21EA ; G 3189
+U 8683 ; WX 838 ; N uni21EB ; G 3190
+U 8684 ; WX 838 ; N uni21EC ; G 3191
+U 8685 ; WX 838 ; N uni21ED ; G 3192
+U 8686 ; WX 838 ; N uni21EE ; G 3193
+U 8687 ; WX 838 ; N uni21EF ; G 3194
+U 8688 ; WX 838 ; N uni21F0 ; G 3195
+U 8689 ; WX 838 ; N uni21F1 ; G 3196
+U 8690 ; WX 838 ; N uni21F2 ; G 3197
+U 8691 ; WX 838 ; N uni21F3 ; G 3198
+U 8692 ; WX 838 ; N uni21F4 ; G 3199
+U 8693 ; WX 838 ; N uni21F5 ; G 3200
+U 8694 ; WX 838 ; N uni21F6 ; G 3201
+U 8695 ; WX 838 ; N uni21F7 ; G 3202
+U 8696 ; WX 838 ; N uni21F8 ; G 3203
+U 8697 ; WX 838 ; N uni21F9 ; G 3204
+U 8698 ; WX 838 ; N uni21FA ; G 3205
+U 8699 ; WX 838 ; N uni21FB ; G 3206
+U 8700 ; WX 838 ; N uni21FC ; G 3207
+U 8701 ; WX 838 ; N uni21FD ; G 3208
+U 8702 ; WX 838 ; N uni21FE ; G 3209
+U 8703 ; WX 838 ; N uni21FF ; G 3210
+U 8704 ; WX 684 ; N universal ; G 3211
+U 8705 ; WX 636 ; N uni2201 ; G 3212
+U 8706 ; WX 517 ; N partialdiff ; G 3213
+U 8707 ; WX 632 ; N existential ; G 3214
+U 8708 ; WX 632 ; N uni2204 ; G 3215
+U 8709 ; WX 871 ; N emptyset ; G 3216
+U 8710 ; WX 669 ; N increment ; G 3217
+U 8711 ; WX 669 ; N gradient ; G 3218
+U 8712 ; WX 871 ; N element ; G 3219
+U 8713 ; WX 871 ; N notelement ; G 3220
+U 8714 ; WX 718 ; N uni220A ; G 3221
+U 8715 ; WX 871 ; N suchthat ; G 3222
+U 8716 ; WX 871 ; N uni220C ; G 3223
+U 8717 ; WX 718 ; N uni220D ; G 3224
+U 8718 ; WX 636 ; N uni220E ; G 3225
+U 8719 ; WX 757 ; N product ; G 3226
+U 8720 ; WX 757 ; N uni2210 ; G 3227
+U 8721 ; WX 674 ; N summation ; G 3228
+U 8722 ; WX 838 ; N minus ; G 3229
+U 8723 ; WX 838 ; N uni2213 ; G 3230
+U 8724 ; WX 838 ; N uni2214 ; G 3231
+U 8725 ; WX 337 ; N uni2215 ; G 3232
+U 8726 ; WX 637 ; N uni2216 ; G 3233
+U 8727 ; WX 838 ; N asteriskmath ; G 3234
+U 8728 ; WX 626 ; N uni2218 ; G 3235
+U 8729 ; WX 626 ; N uni2219 ; G 3236
+U 8730 ; WX 637 ; N radical ; G 3237
+U 8731 ; WX 637 ; N uni221B ; G 3238
+U 8732 ; WX 637 ; N uni221C ; G 3239
+U 8733 ; WX 714 ; N proportional ; G 3240
+U 8734 ; WX 833 ; N infinity ; G 3241
+U 8735 ; WX 838 ; N orthogonal ; G 3242
+U 8736 ; WX 896 ; N angle ; G 3243
+U 8737 ; WX 896 ; N uni2221 ; G 3244
+U 8738 ; WX 838 ; N uni2222 ; G 3245
+U 8739 ; WX 500 ; N uni2223 ; G 3246
+U 8740 ; WX 500 ; N uni2224 ; G 3247
+U 8741 ; WX 500 ; N uni2225 ; G 3248
+U 8742 ; WX 500 ; N uni2226 ; G 3249
+U 8743 ; WX 732 ; N logicaland ; G 3250
+U 8744 ; WX 732 ; N logicalor ; G 3251
+U 8745 ; WX 732 ; N intersection ; G 3252
+U 8746 ; WX 732 ; N union ; G 3253
+U 8747 ; WX 521 ; N integral ; G 3254
+U 8748 ; WX 789 ; N uni222C ; G 3255
+U 8749 ; WX 1057 ; N uni222D ; G 3256
+U 8750 ; WX 521 ; N uni222E ; G 3257
+U 8751 ; WX 789 ; N uni222F ; G 3258
+U 8752 ; WX 1057 ; N uni2230 ; G 3259
+U 8753 ; WX 521 ; N uni2231 ; G 3260
+U 8754 ; WX 521 ; N uni2232 ; G 3261
+U 8755 ; WX 521 ; N uni2233 ; G 3262
+U 8756 ; WX 636 ; N therefore ; G 3263
+U 8757 ; WX 636 ; N uni2235 ; G 3264
+U 8758 ; WX 260 ; N uni2236 ; G 3265
+U 8759 ; WX 636 ; N uni2237 ; G 3266
+U 8760 ; WX 838 ; N uni2238 ; G 3267
+U 8761 ; WX 838 ; N uni2239 ; G 3268
+U 8762 ; WX 838 ; N uni223A ; G 3269
+U 8763 ; WX 838 ; N uni223B ; G 3270
+U 8764 ; WX 838 ; N similar ; G 3271
+U 8765 ; WX 838 ; N uni223D ; G 3272
+U 8766 ; WX 838 ; N uni223E ; G 3273
+U 8767 ; WX 838 ; N uni223F ; G 3274
+U 8768 ; WX 375 ; N uni2240 ; G 3275
+U 8769 ; WX 838 ; N uni2241 ; G 3276
+U 8770 ; WX 838 ; N uni2242 ; G 3277
+U 8771 ; WX 838 ; N uni2243 ; G 3278
+U 8772 ; WX 838 ; N uni2244 ; G 3279
+U 8773 ; WX 838 ; N congruent ; G 3280
+U 8774 ; WX 838 ; N uni2246 ; G 3281
+U 8775 ; WX 838 ; N uni2247 ; G 3282
+U 8776 ; WX 838 ; N approxequal ; G 3283
+U 8777 ; WX 838 ; N uni2249 ; G 3284
+U 8778 ; WX 838 ; N uni224A ; G 3285
+U 8779 ; WX 838 ; N uni224B ; G 3286
+U 8780 ; WX 838 ; N uni224C ; G 3287
+U 8781 ; WX 838 ; N uni224D ; G 3288
+U 8782 ; WX 838 ; N uni224E ; G 3289
+U 8783 ; WX 838 ; N uni224F ; G 3290
+U 8784 ; WX 838 ; N uni2250 ; G 3291
+U 8785 ; WX 838 ; N uni2251 ; G 3292
+U 8786 ; WX 839 ; N uni2252 ; G 3293
+U 8787 ; WX 839 ; N uni2253 ; G 3294
+U 8788 ; WX 1000 ; N uni2254 ; G 3295
+U 8789 ; WX 1000 ; N uni2255 ; G 3296
+U 8790 ; WX 838 ; N uni2256 ; G 3297
+U 8791 ; WX 838 ; N uni2257 ; G 3298
+U 8792 ; WX 838 ; N uni2258 ; G 3299
+U 8793 ; WX 838 ; N uni2259 ; G 3300
+U 8794 ; WX 838 ; N uni225A ; G 3301
+U 8795 ; WX 838 ; N uni225B ; G 3302
+U 8796 ; WX 838 ; N uni225C ; G 3303
+U 8797 ; WX 838 ; N uni225D ; G 3304
+U 8798 ; WX 838 ; N uni225E ; G 3305
+U 8799 ; WX 838 ; N uni225F ; G 3306
+U 8800 ; WX 838 ; N notequal ; G 3307
+U 8801 ; WX 838 ; N equivalence ; G 3308
+U 8802 ; WX 838 ; N uni2262 ; G 3309
+U 8803 ; WX 838 ; N uni2263 ; G 3310
+U 8804 ; WX 838 ; N lessequal ; G 3311
+U 8805 ; WX 838 ; N greaterequal ; G 3312
+U 8806 ; WX 838 ; N uni2266 ; G 3313
+U 8807 ; WX 838 ; N uni2267 ; G 3314
+U 8808 ; WX 838 ; N uni2268 ; G 3315
+U 8809 ; WX 838 ; N uni2269 ; G 3316
+U 8810 ; WX 1047 ; N uni226A ; G 3317
+U 8811 ; WX 1047 ; N uni226B ; G 3318
+U 8812 ; WX 464 ; N uni226C ; G 3319
+U 8813 ; WX 838 ; N uni226D ; G 3320
+U 8814 ; WX 838 ; N uni226E ; G 3321
+U 8815 ; WX 838 ; N uni226F ; G 3322
+U 8816 ; WX 838 ; N uni2270 ; G 3323
+U 8817 ; WX 838 ; N uni2271 ; G 3324
+U 8818 ; WX 838 ; N uni2272 ; G 3325
+U 8819 ; WX 838 ; N uni2273 ; G 3326
+U 8820 ; WX 838 ; N uni2274 ; G 3327
+U 8821 ; WX 838 ; N uni2275 ; G 3328
+U 8822 ; WX 838 ; N uni2276 ; G 3329
+U 8823 ; WX 838 ; N uni2277 ; G 3330
+U 8824 ; WX 838 ; N uni2278 ; G 3331
+U 8825 ; WX 838 ; N uni2279 ; G 3332
+U 8826 ; WX 838 ; N uni227A ; G 3333
+U 8827 ; WX 838 ; N uni227B ; G 3334
+U 8828 ; WX 838 ; N uni227C ; G 3335
+U 8829 ; WX 838 ; N uni227D ; G 3336
+U 8830 ; WX 838 ; N uni227E ; G 3337
+U 8831 ; WX 838 ; N uni227F ; G 3338
+U 8832 ; WX 838 ; N uni2280 ; G 3339
+U 8833 ; WX 838 ; N uni2281 ; G 3340
+U 8834 ; WX 838 ; N propersubset ; G 3341
+U 8835 ; WX 838 ; N propersuperset ; G 3342
+U 8836 ; WX 838 ; N notsubset ; G 3343
+U 8837 ; WX 838 ; N uni2285 ; G 3344
+U 8838 ; WX 838 ; N reflexsubset ; G 3345
+U 8839 ; WX 838 ; N reflexsuperset ; G 3346
+U 8840 ; WX 838 ; N uni2288 ; G 3347
+U 8841 ; WX 838 ; N uni2289 ; G 3348
+U 8842 ; WX 838 ; N uni228A ; G 3349
+U 8843 ; WX 838 ; N uni228B ; G 3350
+U 8844 ; WX 732 ; N uni228C ; G 3351
+U 8845 ; WX 732 ; N uni228D ; G 3352
+U 8846 ; WX 732 ; N uni228E ; G 3353
+U 8847 ; WX 838 ; N uni228F ; G 3354
+U 8848 ; WX 838 ; N uni2290 ; G 3355
+U 8849 ; WX 838 ; N uni2291 ; G 3356
+U 8850 ; WX 838 ; N uni2292 ; G 3357
+U 8851 ; WX 780 ; N uni2293 ; G 3358
+U 8852 ; WX 780 ; N uni2294 ; G 3359
+U 8853 ; WX 838 ; N circleplus ; G 3360
+U 8854 ; WX 838 ; N uni2296 ; G 3361
+U 8855 ; WX 838 ; N circlemultiply ; G 3362
+U 8856 ; WX 838 ; N uni2298 ; G 3363
+U 8857 ; WX 838 ; N uni2299 ; G 3364
+U 8858 ; WX 838 ; N uni229A ; G 3365
+U 8859 ; WX 838 ; N uni229B ; G 3366
+U 8860 ; WX 838 ; N uni229C ; G 3367
+U 8861 ; WX 838 ; N uni229D ; G 3368
+U 8862 ; WX 838 ; N uni229E ; G 3369
+U 8863 ; WX 838 ; N uni229F ; G 3370
+U 8864 ; WX 838 ; N uni22A0 ; G 3371
+U 8865 ; WX 838 ; N uni22A1 ; G 3372
+U 8866 ; WX 871 ; N uni22A2 ; G 3373
+U 8867 ; WX 871 ; N uni22A3 ; G 3374
+U 8868 ; WX 871 ; N uni22A4 ; G 3375
+U 8869 ; WX 871 ; N perpendicular ; G 3376
+U 8870 ; WX 521 ; N uni22A6 ; G 3377
+U 8871 ; WX 521 ; N uni22A7 ; G 3378
+U 8872 ; WX 871 ; N uni22A8 ; G 3379
+U 8873 ; WX 871 ; N uni22A9 ; G 3380
+U 8874 ; WX 871 ; N uni22AA ; G 3381
+U 8875 ; WX 871 ; N uni22AB ; G 3382
+U 8876 ; WX 871 ; N uni22AC ; G 3383
+U 8877 ; WX 871 ; N uni22AD ; G 3384
+U 8878 ; WX 871 ; N uni22AE ; G 3385
+U 8879 ; WX 871 ; N uni22AF ; G 3386
+U 8880 ; WX 838 ; N uni22B0 ; G 3387
+U 8881 ; WX 838 ; N uni22B1 ; G 3388
+U 8882 ; WX 838 ; N uni22B2 ; G 3389
+U 8883 ; WX 838 ; N uni22B3 ; G 3390
+U 8884 ; WX 838 ; N uni22B4 ; G 3391
+U 8885 ; WX 838 ; N uni22B5 ; G 3392
+U 8886 ; WX 1000 ; N uni22B6 ; G 3393
+U 8887 ; WX 1000 ; N uni22B7 ; G 3394
+U 8888 ; WX 838 ; N uni22B8 ; G 3395
+U 8889 ; WX 838 ; N uni22B9 ; G 3396
+U 8890 ; WX 521 ; N uni22BA ; G 3397
+U 8891 ; WX 732 ; N uni22BB ; G 3398
+U 8892 ; WX 732 ; N uni22BC ; G 3399
+U 8893 ; WX 732 ; N uni22BD ; G 3400
+U 8894 ; WX 838 ; N uni22BE ; G 3401
+U 8895 ; WX 838 ; N uni22BF ; G 3402
+U 8896 ; WX 820 ; N uni22C0 ; G 3403
+U 8897 ; WX 820 ; N uni22C1 ; G 3404
+U 8898 ; WX 820 ; N uni22C2 ; G 3405
+U 8899 ; WX 820 ; N uni22C3 ; G 3406
+U 8900 ; WX 626 ; N uni22C4 ; G 3407
+U 8901 ; WX 318 ; N dotmath ; G 3408
+U 8902 ; WX 626 ; N uni22C6 ; G 3409
+U 8903 ; WX 838 ; N uni22C7 ; G 3410
+U 8904 ; WX 1000 ; N uni22C8 ; G 3411
+U 8905 ; WX 1000 ; N uni22C9 ; G 3412
+U 8906 ; WX 1000 ; N uni22CA ; G 3413
+U 8907 ; WX 1000 ; N uni22CB ; G 3414
+U 8908 ; WX 1000 ; N uni22CC ; G 3415
+U 8909 ; WX 838 ; N uni22CD ; G 3416
+U 8910 ; WX 732 ; N uni22CE ; G 3417
+U 8911 ; WX 732 ; N uni22CF ; G 3418
+U 8912 ; WX 838 ; N uni22D0 ; G 3419
+U 8913 ; WX 838 ; N uni22D1 ; G 3420
+U 8914 ; WX 838 ; N uni22D2 ; G 3421
+U 8915 ; WX 838 ; N uni22D3 ; G 3422
+U 8916 ; WX 838 ; N uni22D4 ; G 3423
+U 8917 ; WX 838 ; N uni22D5 ; G 3424
+U 8918 ; WX 838 ; N uni22D6 ; G 3425
+U 8919 ; WX 838 ; N uni22D7 ; G 3426
+U 8920 ; WX 1422 ; N uni22D8 ; G 3427
+U 8921 ; WX 1422 ; N uni22D9 ; G 3428
+U 8922 ; WX 838 ; N uni22DA ; G 3429
+U 8923 ; WX 838 ; N uni22DB ; G 3430
+U 8924 ; WX 838 ; N uni22DC ; G 3431
+U 8925 ; WX 838 ; N uni22DD ; G 3432
+U 8926 ; WX 838 ; N uni22DE ; G 3433
+U 8927 ; WX 838 ; N uni22DF ; G 3434
+U 8928 ; WX 838 ; N uni22E0 ; G 3435
+U 8929 ; WX 838 ; N uni22E1 ; G 3436
+U 8930 ; WX 838 ; N uni22E2 ; G 3437
+U 8931 ; WX 838 ; N uni22E3 ; G 3438
+U 8932 ; WX 838 ; N uni22E4 ; G 3439
+U 8933 ; WX 838 ; N uni22E5 ; G 3440
+U 8934 ; WX 838 ; N uni22E6 ; G 3441
+U 8935 ; WX 838 ; N uni22E7 ; G 3442
+U 8936 ; WX 838 ; N uni22E8 ; G 3443
+U 8937 ; WX 838 ; N uni22E9 ; G 3444
+U 8938 ; WX 838 ; N uni22EA ; G 3445
+U 8939 ; WX 838 ; N uni22EB ; G 3446
+U 8940 ; WX 838 ; N uni22EC ; G 3447
+U 8941 ; WX 838 ; N uni22ED ; G 3448
+U 8942 ; WX 1000 ; N uni22EE ; G 3449
+U 8943 ; WX 1000 ; N uni22EF ; G 3450
+U 8944 ; WX 1000 ; N uni22F0 ; G 3451
+U 8945 ; WX 1000 ; N uni22F1 ; G 3452
+U 8946 ; WX 1000 ; N uni22F2 ; G 3453
+U 8947 ; WX 871 ; N uni22F3 ; G 3454
+U 8948 ; WX 718 ; N uni22F4 ; G 3455
+U 8949 ; WX 871 ; N uni22F5 ; G 3456
+U 8950 ; WX 871 ; N uni22F6 ; G 3457
+U 8951 ; WX 718 ; N uni22F7 ; G 3458
+U 8952 ; WX 871 ; N uni22F8 ; G 3459
+U 8953 ; WX 871 ; N uni22F9 ; G 3460
+U 8954 ; WX 1000 ; N uni22FA ; G 3461
+U 8955 ; WX 871 ; N uni22FB ; G 3462
+U 8956 ; WX 718 ; N uni22FC ; G 3463
+U 8957 ; WX 871 ; N uni22FD ; G 3464
+U 8958 ; WX 718 ; N uni22FE ; G 3465
+U 8959 ; WX 871 ; N uni22FF ; G 3466
+U 8960 ; WX 602 ; N uni2300 ; G 3467
+U 8961 ; WX 602 ; N uni2301 ; G 3468
+U 8962 ; WX 635 ; N house ; G 3469
+U 8963 ; WX 838 ; N uni2303 ; G 3470
+U 8964 ; WX 838 ; N uni2304 ; G 3471
+U 8965 ; WX 838 ; N uni2305 ; G 3472
+U 8966 ; WX 838 ; N uni2306 ; G 3473
+U 8967 ; WX 488 ; N uni2307 ; G 3474
+U 8968 ; WX 390 ; N uni2308 ; G 3475
+U 8969 ; WX 390 ; N uni2309 ; G 3476
+U 8970 ; WX 390 ; N uni230A ; G 3477
+U 8971 ; WX 390 ; N uni230B ; G 3478
+U 8972 ; WX 809 ; N uni230C ; G 3479
+U 8973 ; WX 809 ; N uni230D ; G 3480
+U 8974 ; WX 809 ; N uni230E ; G 3481
+U 8975 ; WX 809 ; N uni230F ; G 3482
+U 8976 ; WX 838 ; N revlogicalnot ; G 3483
+U 8977 ; WX 513 ; N uni2311 ; G 3484
+U 8984 ; WX 1000 ; N uni2318 ; G 3485
+U 8985 ; WX 838 ; N uni2319 ; G 3486
+U 8988 ; WX 469 ; N uni231C ; G 3487
+U 8989 ; WX 469 ; N uni231D ; G 3488
+U 8990 ; WX 469 ; N uni231E ; G 3489
+U 8991 ; WX 469 ; N uni231F ; G 3490
+U 8992 ; WX 521 ; N integraltp ; G 3491
+U 8993 ; WX 521 ; N integralbt ; G 3492
+U 8996 ; WX 1152 ; N uni2324 ; G 3493
+U 8997 ; WX 1152 ; N uni2325 ; G 3494
+U 8998 ; WX 1414 ; N uni2326 ; G 3495
+U 8999 ; WX 1152 ; N uni2327 ; G 3496
+U 9000 ; WX 1443 ; N uni2328 ; G 3497
+U 9003 ; WX 1414 ; N uni232B ; G 3498
+U 9004 ; WX 873 ; N uni232C ; G 3499
+U 9075 ; WX 338 ; N uni2373 ; G 3500
+U 9076 ; WX 635 ; N uni2374 ; G 3501
+U 9077 ; WX 837 ; N uni2375 ; G 3502
+U 9082 ; WX 659 ; N uni237A ; G 3503
+U 9085 ; WX 757 ; N uni237D ; G 3504
+U 9095 ; WX 1152 ; N uni2387 ; G 3505
+U 9108 ; WX 873 ; N uni2394 ; G 3506
+U 9115 ; WX 500 ; N uni239B ; G 3507
+U 9116 ; WX 500 ; N uni239C ; G 3508
+U 9117 ; WX 500 ; N uni239D ; G 3509
+U 9118 ; WX 500 ; N uni239E ; G 3510
+U 9119 ; WX 500 ; N uni239F ; G 3511
+U 9120 ; WX 500 ; N uni23A0 ; G 3512
+U 9121 ; WX 500 ; N uni23A1 ; G 3513
+U 9122 ; WX 500 ; N uni23A2 ; G 3514
+U 9123 ; WX 500 ; N uni23A3 ; G 3515
+U 9124 ; WX 500 ; N uni23A4 ; G 3516
+U 9125 ; WX 500 ; N uni23A5 ; G 3517
+U 9126 ; WX 500 ; N uni23A6 ; G 3518
+U 9127 ; WX 750 ; N uni23A7 ; G 3519
+U 9128 ; WX 750 ; N uni23A8 ; G 3520
+U 9129 ; WX 750 ; N uni23A9 ; G 3521
+U 9130 ; WX 750 ; N uni23AA ; G 3522
+U 9131 ; WX 750 ; N uni23AB ; G 3523
+U 9132 ; WX 750 ; N uni23AC ; G 3524
+U 9133 ; WX 750 ; N uni23AD ; G 3525
+U 9134 ; WX 521 ; N uni23AE ; G 3526
+U 9166 ; WX 838 ; N uni23CE ; G 3527
+U 9167 ; WX 945 ; N uni23CF ; G 3528
+U 9187 ; WX 873 ; N uni23E3 ; G 3529
+U 9189 ; WX 769 ; N uni23E5 ; G 3530
+U 9192 ; WX 636 ; N uni23E8 ; G 3531
+U 9250 ; WX 635 ; N uni2422 ; G 3532
+U 9251 ; WX 635 ; N uni2423 ; G 3533
+U 9312 ; WX 896 ; N uni2460 ; G 3534
+U 9313 ; WX 896 ; N uni2461 ; G 3535
+U 9314 ; WX 896 ; N uni2462 ; G 3536
+U 9315 ; WX 896 ; N uni2463 ; G 3537
+U 9316 ; WX 896 ; N uni2464 ; G 3538
+U 9317 ; WX 896 ; N uni2465 ; G 3539
+U 9318 ; WX 896 ; N uni2466 ; G 3540
+U 9319 ; WX 896 ; N uni2467 ; G 3541
+U 9320 ; WX 896 ; N uni2468 ; G 3542
+U 9321 ; WX 896 ; N uni2469 ; G 3543
+U 9472 ; WX 602 ; N SF100000 ; G 3544
+U 9473 ; WX 602 ; N uni2501 ; G 3545
+U 9474 ; WX 602 ; N SF110000 ; G 3546
+U 9475 ; WX 602 ; N uni2503 ; G 3547
+U 9476 ; WX 602 ; N uni2504 ; G 3548
+U 9477 ; WX 602 ; N uni2505 ; G 3549
+U 9478 ; WX 602 ; N uni2506 ; G 3550
+U 9479 ; WX 602 ; N uni2507 ; G 3551
+U 9480 ; WX 602 ; N uni2508 ; G 3552
+U 9481 ; WX 602 ; N uni2509 ; G 3553
+U 9482 ; WX 602 ; N uni250A ; G 3554
+U 9483 ; WX 602 ; N uni250B ; G 3555
+U 9484 ; WX 602 ; N SF010000 ; G 3556
+U 9485 ; WX 602 ; N uni250D ; G 3557
+U 9486 ; WX 602 ; N uni250E ; G 3558
+U 9487 ; WX 602 ; N uni250F ; G 3559
+U 9488 ; WX 602 ; N SF030000 ; G 3560
+U 9489 ; WX 602 ; N uni2511 ; G 3561
+U 9490 ; WX 602 ; N uni2512 ; G 3562
+U 9491 ; WX 602 ; N uni2513 ; G 3563
+U 9492 ; WX 602 ; N SF020000 ; G 3564
+U 9493 ; WX 602 ; N uni2515 ; G 3565
+U 9494 ; WX 602 ; N uni2516 ; G 3566
+U 9495 ; WX 602 ; N uni2517 ; G 3567
+U 9496 ; WX 602 ; N SF040000 ; G 3568
+U 9497 ; WX 602 ; N uni2519 ; G 3569
+U 9498 ; WX 602 ; N uni251A ; G 3570
+U 9499 ; WX 602 ; N uni251B ; G 3571
+U 9500 ; WX 602 ; N SF080000 ; G 3572
+U 9501 ; WX 602 ; N uni251D ; G 3573
+U 9502 ; WX 602 ; N uni251E ; G 3574
+U 9503 ; WX 602 ; N uni251F ; G 3575
+U 9504 ; WX 602 ; N uni2520 ; G 3576
+U 9505 ; WX 602 ; N uni2521 ; G 3577
+U 9506 ; WX 602 ; N uni2522 ; G 3578
+U 9507 ; WX 602 ; N uni2523 ; G 3579
+U 9508 ; WX 602 ; N SF090000 ; G 3580
+U 9509 ; WX 602 ; N uni2525 ; G 3581
+U 9510 ; WX 602 ; N uni2526 ; G 3582
+U 9511 ; WX 602 ; N uni2527 ; G 3583
+U 9512 ; WX 602 ; N uni2528 ; G 3584
+U 9513 ; WX 602 ; N uni2529 ; G 3585
+U 9514 ; WX 602 ; N uni252A ; G 3586
+U 9515 ; WX 602 ; N uni252B ; G 3587
+U 9516 ; WX 602 ; N SF060000 ; G 3588
+U 9517 ; WX 602 ; N uni252D ; G 3589
+U 9518 ; WX 602 ; N uni252E ; G 3590
+U 9519 ; WX 602 ; N uni252F ; G 3591
+U 9520 ; WX 602 ; N uni2530 ; G 3592
+U 9521 ; WX 602 ; N uni2531 ; G 3593
+U 9522 ; WX 602 ; N uni2532 ; G 3594
+U 9523 ; WX 602 ; N uni2533 ; G 3595
+U 9524 ; WX 602 ; N SF070000 ; G 3596
+U 9525 ; WX 602 ; N uni2535 ; G 3597
+U 9526 ; WX 602 ; N uni2536 ; G 3598
+U 9527 ; WX 602 ; N uni2537 ; G 3599
+U 9528 ; WX 602 ; N uni2538 ; G 3600
+U 9529 ; WX 602 ; N uni2539 ; G 3601
+U 9530 ; WX 602 ; N uni253A ; G 3602
+U 9531 ; WX 602 ; N uni253B ; G 3603
+U 9532 ; WX 602 ; N SF050000 ; G 3604
+U 9533 ; WX 602 ; N uni253D ; G 3605
+U 9534 ; WX 602 ; N uni253E ; G 3606
+U 9535 ; WX 602 ; N uni253F ; G 3607
+U 9536 ; WX 602 ; N uni2540 ; G 3608
+U 9537 ; WX 602 ; N uni2541 ; G 3609
+U 9538 ; WX 602 ; N uni2542 ; G 3610
+U 9539 ; WX 602 ; N uni2543 ; G 3611
+U 9540 ; WX 602 ; N uni2544 ; G 3612
+U 9541 ; WX 602 ; N uni2545 ; G 3613
+U 9542 ; WX 602 ; N uni2546 ; G 3614
+U 9543 ; WX 602 ; N uni2547 ; G 3615
+U 9544 ; WX 602 ; N uni2548 ; G 3616
+U 9545 ; WX 602 ; N uni2549 ; G 3617
+U 9546 ; WX 602 ; N uni254A ; G 3618
+U 9547 ; WX 602 ; N uni254B ; G 3619
+U 9548 ; WX 602 ; N uni254C ; G 3620
+U 9549 ; WX 602 ; N uni254D ; G 3621
+U 9550 ; WX 602 ; N uni254E ; G 3622
+U 9551 ; WX 602 ; N uni254F ; G 3623
+U 9552 ; WX 602 ; N SF430000 ; G 3624
+U 9553 ; WX 602 ; N SF240000 ; G 3625
+U 9554 ; WX 602 ; N SF510000 ; G 3626
+U 9555 ; WX 602 ; N SF520000 ; G 3627
+U 9556 ; WX 602 ; N SF390000 ; G 3628
+U 9557 ; WX 602 ; N SF220000 ; G 3629
+U 9558 ; WX 602 ; N SF210000 ; G 3630
+U 9559 ; WX 602 ; N SF250000 ; G 3631
+U 9560 ; WX 602 ; N SF500000 ; G 3632
+U 9561 ; WX 602 ; N SF490000 ; G 3633
+U 9562 ; WX 602 ; N SF380000 ; G 3634
+U 9563 ; WX 602 ; N SF280000 ; G 3635
+U 9564 ; WX 602 ; N SF270000 ; G 3636
+U 9565 ; WX 602 ; N SF260000 ; G 3637
+U 9566 ; WX 602 ; N SF360000 ; G 3638
+U 9567 ; WX 602 ; N SF370000 ; G 3639
+U 9568 ; WX 602 ; N SF420000 ; G 3640
+U 9569 ; WX 602 ; N SF190000 ; G 3641
+U 9570 ; WX 602 ; N SF200000 ; G 3642
+U 9571 ; WX 602 ; N SF230000 ; G 3643
+U 9572 ; WX 602 ; N SF470000 ; G 3644
+U 9573 ; WX 602 ; N SF480000 ; G 3645
+U 9574 ; WX 602 ; N SF410000 ; G 3646
+U 9575 ; WX 602 ; N SF450000 ; G 3647
+U 9576 ; WX 602 ; N SF460000 ; G 3648
+U 9577 ; WX 602 ; N SF400000 ; G 3649
+U 9578 ; WX 602 ; N SF540000 ; G 3650
+U 9579 ; WX 602 ; N SF530000 ; G 3651
+U 9580 ; WX 602 ; N SF440000 ; G 3652
+U 9581 ; WX 602 ; N uni256D ; G 3653
+U 9582 ; WX 602 ; N uni256E ; G 3654
+U 9583 ; WX 602 ; N uni256F ; G 3655
+U 9584 ; WX 602 ; N uni2570 ; G 3656
+U 9585 ; WX 602 ; N uni2571 ; G 3657
+U 9586 ; WX 602 ; N uni2572 ; G 3658
+U 9587 ; WX 602 ; N uni2573 ; G 3659
+U 9588 ; WX 602 ; N uni2574 ; G 3660
+U 9589 ; WX 602 ; N uni2575 ; G 3661
+U 9590 ; WX 602 ; N uni2576 ; G 3662
+U 9591 ; WX 602 ; N uni2577 ; G 3663
+U 9592 ; WX 602 ; N uni2578 ; G 3664
+U 9593 ; WX 602 ; N uni2579 ; G 3665
+U 9594 ; WX 602 ; N uni257A ; G 3666
+U 9595 ; WX 602 ; N uni257B ; G 3667
+U 9596 ; WX 602 ; N uni257C ; G 3668
+U 9597 ; WX 602 ; N uni257D ; G 3669
+U 9598 ; WX 602 ; N uni257E ; G 3670
+U 9599 ; WX 602 ; N uni257F ; G 3671
+U 9600 ; WX 769 ; N upblock ; G 3672
+U 9601 ; WX 769 ; N uni2581 ; G 3673
+U 9602 ; WX 769 ; N uni2582 ; G 3674
+U 9603 ; WX 769 ; N uni2583 ; G 3675
+U 9604 ; WX 769 ; N dnblock ; G 3676
+U 9605 ; WX 769 ; N uni2585 ; G 3677
+U 9606 ; WX 769 ; N uni2586 ; G 3678
+U 9607 ; WX 769 ; N uni2587 ; G 3679
+U 9608 ; WX 769 ; N block ; G 3680
+U 9609 ; WX 769 ; N uni2589 ; G 3681
+U 9610 ; WX 769 ; N uni258A ; G 3682
+U 9611 ; WX 769 ; N uni258B ; G 3683
+U 9612 ; WX 769 ; N lfblock ; G 3684
+U 9613 ; WX 769 ; N uni258D ; G 3685
+U 9614 ; WX 769 ; N uni258E ; G 3686
+U 9615 ; WX 769 ; N uni258F ; G 3687
+U 9616 ; WX 769 ; N rtblock ; G 3688
+U 9617 ; WX 769 ; N ltshade ; G 3689
+U 9618 ; WX 769 ; N shade ; G 3690
+U 9619 ; WX 769 ; N dkshade ; G 3691
+U 9620 ; WX 769 ; N uni2594 ; G 3692
+U 9621 ; WX 769 ; N uni2595 ; G 3693
+U 9622 ; WX 769 ; N uni2596 ; G 3694
+U 9623 ; WX 769 ; N uni2597 ; G 3695
+U 9624 ; WX 769 ; N uni2598 ; G 3696
+U 9625 ; WX 769 ; N uni2599 ; G 3697
+U 9626 ; WX 769 ; N uni259A ; G 3698
+U 9627 ; WX 769 ; N uni259B ; G 3699
+U 9628 ; WX 769 ; N uni259C ; G 3700
+U 9629 ; WX 769 ; N uni259D ; G 3701
+U 9630 ; WX 769 ; N uni259E ; G 3702
+U 9631 ; WX 769 ; N uni259F ; G 3703
+U 9632 ; WX 945 ; N filledbox ; G 3704
+U 9633 ; WX 945 ; N H22073 ; G 3705
+U 9634 ; WX 945 ; N uni25A2 ; G 3706
+U 9635 ; WX 945 ; N uni25A3 ; G 3707
+U 9636 ; WX 945 ; N uni25A4 ; G 3708
+U 9637 ; WX 945 ; N uni25A5 ; G 3709
+U 9638 ; WX 945 ; N uni25A6 ; G 3710
+U 9639 ; WX 945 ; N uni25A7 ; G 3711
+U 9640 ; WX 945 ; N uni25A8 ; G 3712
+U 9641 ; WX 945 ; N uni25A9 ; G 3713
+U 9642 ; WX 678 ; N H18543 ; G 3714
+U 9643 ; WX 678 ; N H18551 ; G 3715
+U 9644 ; WX 945 ; N filledrect ; G 3716
+U 9645 ; WX 945 ; N uni25AD ; G 3717
+U 9646 ; WX 550 ; N uni25AE ; G 3718
+U 9647 ; WX 550 ; N uni25AF ; G 3719
+U 9648 ; WX 769 ; N uni25B0 ; G 3720
+U 9649 ; WX 769 ; N uni25B1 ; G 3721
+U 9650 ; WX 769 ; N triagup ; G 3722
+U 9651 ; WX 769 ; N uni25B3 ; G 3723
+U 9652 ; WX 502 ; N uni25B4 ; G 3724
+U 9653 ; WX 502 ; N uni25B5 ; G 3725
+U 9654 ; WX 769 ; N uni25B6 ; G 3726
+U 9655 ; WX 769 ; N uni25B7 ; G 3727
+U 9656 ; WX 502 ; N uni25B8 ; G 3728
+U 9657 ; WX 502 ; N uni25B9 ; G 3729
+U 9658 ; WX 769 ; N triagrt ; G 3730
+U 9659 ; WX 769 ; N uni25BB ; G 3731
+U 9660 ; WX 769 ; N triagdn ; G 3732
+U 9661 ; WX 769 ; N uni25BD ; G 3733
+U 9662 ; WX 502 ; N uni25BE ; G 3734
+U 9663 ; WX 502 ; N uni25BF ; G 3735
+U 9664 ; WX 769 ; N uni25C0 ; G 3736
+U 9665 ; WX 769 ; N uni25C1 ; G 3737
+U 9666 ; WX 502 ; N uni25C2 ; G 3738
+U 9667 ; WX 502 ; N uni25C3 ; G 3739
+U 9668 ; WX 769 ; N triaglf ; G 3740
+U 9669 ; WX 769 ; N uni25C5 ; G 3741
+U 9670 ; WX 769 ; N uni25C6 ; G 3742
+U 9671 ; WX 769 ; N uni25C7 ; G 3743
+U 9672 ; WX 769 ; N uni25C8 ; G 3744
+U 9673 ; WX 873 ; N uni25C9 ; G 3745
+U 9674 ; WX 494 ; N lozenge ; G 3746
+U 9675 ; WX 873 ; N circle ; G 3747
+U 9676 ; WX 873 ; N uni25CC ; G 3748
+U 9677 ; WX 873 ; N uni25CD ; G 3749
+U 9678 ; WX 873 ; N uni25CE ; G 3750
+U 9679 ; WX 873 ; N H18533 ; G 3751
+U 9680 ; WX 873 ; N uni25D0 ; G 3752
+U 9681 ; WX 873 ; N uni25D1 ; G 3753
+U 9682 ; WX 873 ; N uni25D2 ; G 3754
+U 9683 ; WX 873 ; N uni25D3 ; G 3755
+U 9684 ; WX 873 ; N uni25D4 ; G 3756
+U 9685 ; WX 873 ; N uni25D5 ; G 3757
+U 9686 ; WX 527 ; N uni25D6 ; G 3758
+U 9687 ; WX 527 ; N uni25D7 ; G 3759
+U 9688 ; WX 791 ; N invbullet ; G 3760
+U 9689 ; WX 970 ; N invcircle ; G 3761
+U 9690 ; WX 970 ; N uni25DA ; G 3762
+U 9691 ; WX 970 ; N uni25DB ; G 3763
+U 9692 ; WX 387 ; N uni25DC ; G 3764
+U 9693 ; WX 387 ; N uni25DD ; G 3765
+U 9694 ; WX 387 ; N uni25DE ; G 3766
+U 9695 ; WX 387 ; N uni25DF ; G 3767
+U 9696 ; WX 873 ; N uni25E0 ; G 3768
+U 9697 ; WX 873 ; N uni25E1 ; G 3769
+U 9698 ; WX 769 ; N uni25E2 ; G 3770
+U 9699 ; WX 769 ; N uni25E3 ; G 3771
+U 9700 ; WX 769 ; N uni25E4 ; G 3772
+U 9701 ; WX 769 ; N uni25E5 ; G 3773
+U 9702 ; WX 590 ; N openbullet ; G 3774
+U 9703 ; WX 945 ; N uni25E7 ; G 3775
+U 9704 ; WX 945 ; N uni25E8 ; G 3776
+U 9705 ; WX 945 ; N uni25E9 ; G 3777
+U 9706 ; WX 945 ; N uni25EA ; G 3778
+U 9707 ; WX 945 ; N uni25EB ; G 3779
+U 9708 ; WX 769 ; N uni25EC ; G 3780
+U 9709 ; WX 769 ; N uni25ED ; G 3781
+U 9710 ; WX 769 ; N uni25EE ; G 3782
+U 9711 ; WX 1119 ; N uni25EF ; G 3783
+U 9712 ; WX 945 ; N uni25F0 ; G 3784
+U 9713 ; WX 945 ; N uni25F1 ; G 3785
+U 9714 ; WX 945 ; N uni25F2 ; G 3786
+U 9715 ; WX 945 ; N uni25F3 ; G 3787
+U 9716 ; WX 873 ; N uni25F4 ; G 3788
+U 9717 ; WX 873 ; N uni25F5 ; G 3789
+U 9718 ; WX 873 ; N uni25F6 ; G 3790
+U 9719 ; WX 873 ; N uni25F7 ; G 3791
+U 9720 ; WX 769 ; N uni25F8 ; G 3792
+U 9721 ; WX 769 ; N uni25F9 ; G 3793
+U 9722 ; WX 769 ; N uni25FA ; G 3794
+U 9723 ; WX 830 ; N uni25FB ; G 3795
+U 9724 ; WX 830 ; N uni25FC ; G 3796
+U 9725 ; WX 732 ; N uni25FD ; G 3797
+U 9726 ; WX 732 ; N uni25FE ; G 3798
+U 9727 ; WX 769 ; N uni25FF ; G 3799
+U 9728 ; WX 896 ; N uni2600 ; G 3800
+U 9729 ; WX 1000 ; N uni2601 ; G 3801
+U 9730 ; WX 896 ; N uni2602 ; G 3802
+U 9731 ; WX 896 ; N uni2603 ; G 3803
+U 9732 ; WX 896 ; N uni2604 ; G 3804
+U 9733 ; WX 896 ; N uni2605 ; G 3805
+U 9734 ; WX 896 ; N uni2606 ; G 3806
+U 9735 ; WX 573 ; N uni2607 ; G 3807
+U 9736 ; WX 896 ; N uni2608 ; G 3808
+U 9737 ; WX 896 ; N uni2609 ; G 3809
+U 9738 ; WX 888 ; N uni260A ; G 3810
+U 9739 ; WX 888 ; N uni260B ; G 3811
+U 9740 ; WX 671 ; N uni260C ; G 3812
+U 9741 ; WX 1013 ; N uni260D ; G 3813
+U 9742 ; WX 1246 ; N uni260E ; G 3814
+U 9743 ; WX 1250 ; N uni260F ; G 3815
+U 9744 ; WX 896 ; N uni2610 ; G 3816
+U 9745 ; WX 896 ; N uni2611 ; G 3817
+U 9746 ; WX 896 ; N uni2612 ; G 3818
+U 9747 ; WX 532 ; N uni2613 ; G 3819
+U 9748 ; WX 896 ; N uni2614 ; G 3820
+U 9749 ; WX 896 ; N uni2615 ; G 3821
+U 9750 ; WX 896 ; N uni2616 ; G 3822
+U 9751 ; WX 896 ; N uni2617 ; G 3823
+U 9752 ; WX 896 ; N uni2618 ; G 3824
+U 9753 ; WX 896 ; N uni2619 ; G 3825
+U 9754 ; WX 896 ; N uni261A ; G 3826
+U 9755 ; WX 896 ; N uni261B ; G 3827
+U 9756 ; WX 896 ; N uni261C ; G 3828
+U 9757 ; WX 609 ; N uni261D ; G 3829
+U 9758 ; WX 896 ; N uni261E ; G 3830
+U 9759 ; WX 609 ; N uni261F ; G 3831
+U 9760 ; WX 896 ; N uni2620 ; G 3832
+U 9761 ; WX 896 ; N uni2621 ; G 3833
+U 9762 ; WX 896 ; N uni2622 ; G 3834
+U 9763 ; WX 896 ; N uni2623 ; G 3835
+U 9764 ; WX 669 ; N uni2624 ; G 3836
+U 9765 ; WX 746 ; N uni2625 ; G 3837
+U 9766 ; WX 649 ; N uni2626 ; G 3838
+U 9767 ; WX 784 ; N uni2627 ; G 3839
+U 9768 ; WX 545 ; N uni2628 ; G 3840
+U 9769 ; WX 896 ; N uni2629 ; G 3841
+U 9770 ; WX 896 ; N uni262A ; G 3842
+U 9771 ; WX 896 ; N uni262B ; G 3843
+U 9772 ; WX 710 ; N uni262C ; G 3844
+U 9773 ; WX 896 ; N uni262D ; G 3845
+U 9774 ; WX 896 ; N uni262E ; G 3846
+U 9775 ; WX 896 ; N uni262F ; G 3847
+U 9776 ; WX 896 ; N uni2630 ; G 3848
+U 9777 ; WX 896 ; N uni2631 ; G 3849
+U 9778 ; WX 896 ; N uni2632 ; G 3850
+U 9779 ; WX 896 ; N uni2633 ; G 3851
+U 9780 ; WX 896 ; N uni2634 ; G 3852
+U 9781 ; WX 896 ; N uni2635 ; G 3853
+U 9782 ; WX 896 ; N uni2636 ; G 3854
+U 9783 ; WX 896 ; N uni2637 ; G 3855
+U 9784 ; WX 896 ; N uni2638 ; G 3856
+U 9785 ; WX 1042 ; N uni2639 ; G 3857
+U 9786 ; WX 1042 ; N smileface ; G 3858
+U 9787 ; WX 1042 ; N invsmileface ; G 3859
+U 9788 ; WX 896 ; N sun ; G 3860
+U 9789 ; WX 896 ; N uni263D ; G 3861
+U 9790 ; WX 896 ; N uni263E ; G 3862
+U 9791 ; WX 614 ; N uni263F ; G 3863
+U 9792 ; WX 732 ; N female ; G 3864
+U 9793 ; WX 732 ; N uni2641 ; G 3865
+U 9794 ; WX 896 ; N male ; G 3866
+U 9795 ; WX 896 ; N uni2643 ; G 3867
+U 9796 ; WX 896 ; N uni2644 ; G 3868
+U 9797 ; WX 896 ; N uni2645 ; G 3869
+U 9798 ; WX 896 ; N uni2646 ; G 3870
+U 9799 ; WX 896 ; N uni2647 ; G 3871
+U 9800 ; WX 896 ; N uni2648 ; G 3872
+U 9801 ; WX 896 ; N uni2649 ; G 3873
+U 9802 ; WX 896 ; N uni264A ; G 3874
+U 9803 ; WX 896 ; N uni264B ; G 3875
+U 9804 ; WX 896 ; N uni264C ; G 3876
+U 9805 ; WX 896 ; N uni264D ; G 3877
+U 9806 ; WX 896 ; N uni264E ; G 3878
+U 9807 ; WX 896 ; N uni264F ; G 3879
+U 9808 ; WX 896 ; N uni2650 ; G 3880
+U 9809 ; WX 896 ; N uni2651 ; G 3881
+U 9810 ; WX 896 ; N uni2652 ; G 3882
+U 9811 ; WX 896 ; N uni2653 ; G 3883
+U 9812 ; WX 896 ; N uni2654 ; G 3884
+U 9813 ; WX 896 ; N uni2655 ; G 3885
+U 9814 ; WX 896 ; N uni2656 ; G 3886
+U 9815 ; WX 896 ; N uni2657 ; G 3887
+U 9816 ; WX 896 ; N uni2658 ; G 3888
+U 9817 ; WX 896 ; N uni2659 ; G 3889
+U 9818 ; WX 896 ; N uni265A ; G 3890
+U 9819 ; WX 896 ; N uni265B ; G 3891
+U 9820 ; WX 896 ; N uni265C ; G 3892
+U 9821 ; WX 896 ; N uni265D ; G 3893
+U 9822 ; WX 896 ; N uni265E ; G 3894
+U 9823 ; WX 896 ; N uni265F ; G 3895
+U 9824 ; WX 896 ; N spade ; G 3896
+U 9825 ; WX 896 ; N uni2661 ; G 3897
+U 9826 ; WX 896 ; N uni2662 ; G 3898
+U 9827 ; WX 896 ; N club ; G 3899
+U 9828 ; WX 896 ; N uni2664 ; G 3900
+U 9829 ; WX 896 ; N heart ; G 3901
+U 9830 ; WX 896 ; N diamond ; G 3902
+U 9831 ; WX 896 ; N uni2667 ; G 3903
+U 9832 ; WX 896 ; N uni2668 ; G 3904
+U 9833 ; WX 472 ; N uni2669 ; G 3905
+U 9834 ; WX 638 ; N musicalnote ; G 3906
+U 9835 ; WX 896 ; N musicalnotedbl ; G 3907
+U 9836 ; WX 896 ; N uni266C ; G 3908
+U 9837 ; WX 472 ; N uni266D ; G 3909
+U 9838 ; WX 357 ; N uni266E ; G 3910
+U 9839 ; WX 484 ; N uni266F ; G 3911
+U 9840 ; WX 748 ; N uni2670 ; G 3912
+U 9841 ; WX 766 ; N uni2671 ; G 3913
+U 9842 ; WX 896 ; N uni2672 ; G 3914
+U 9843 ; WX 896 ; N uni2673 ; G 3915
+U 9844 ; WX 896 ; N uni2674 ; G 3916
+U 9845 ; WX 896 ; N uni2675 ; G 3917
+U 9846 ; WX 896 ; N uni2676 ; G 3918
+U 9847 ; WX 896 ; N uni2677 ; G 3919
+U 9848 ; WX 896 ; N uni2678 ; G 3920
+U 9849 ; WX 896 ; N uni2679 ; G 3921
+U 9850 ; WX 896 ; N uni267A ; G 3922
+U 9851 ; WX 896 ; N uni267B ; G 3923
+U 9852 ; WX 896 ; N uni267C ; G 3924
+U 9853 ; WX 896 ; N uni267D ; G 3925
+U 9854 ; WX 896 ; N uni267E ; G 3926
+U 9855 ; WX 896 ; N uni267F ; G 3927
+U 9856 ; WX 869 ; N uni2680 ; G 3928
+U 9857 ; WX 869 ; N uni2681 ; G 3929
+U 9858 ; WX 869 ; N uni2682 ; G 3930
+U 9859 ; WX 869 ; N uni2683 ; G 3931
+U 9860 ; WX 869 ; N uni2684 ; G 3932
+U 9861 ; WX 869 ; N uni2685 ; G 3933
+U 9862 ; WX 896 ; N uni2686 ; G 3934
+U 9863 ; WX 896 ; N uni2687 ; G 3935
+U 9864 ; WX 896 ; N uni2688 ; G 3936
+U 9865 ; WX 896 ; N uni2689 ; G 3937
+U 9866 ; WX 896 ; N uni268A ; G 3938
+U 9867 ; WX 896 ; N uni268B ; G 3939
+U 9868 ; WX 896 ; N uni268C ; G 3940
+U 9869 ; WX 896 ; N uni268D ; G 3941
+U 9870 ; WX 896 ; N uni268E ; G 3942
+U 9871 ; WX 896 ; N uni268F ; G 3943
+U 9872 ; WX 896 ; N uni2690 ; G 3944
+U 9873 ; WX 896 ; N uni2691 ; G 3945
+U 9874 ; WX 896 ; N uni2692 ; G 3946
+U 9875 ; WX 896 ; N uni2693 ; G 3947
+U 9876 ; WX 896 ; N uni2694 ; G 3948
+U 9877 ; WX 541 ; N uni2695 ; G 3949
+U 9878 ; WX 896 ; N uni2696 ; G 3950
+U 9879 ; WX 896 ; N uni2697 ; G 3951
+U 9880 ; WX 896 ; N uni2698 ; G 3952
+U 9881 ; WX 896 ; N uni2699 ; G 3953
+U 9882 ; WX 896 ; N uni269A ; G 3954
+U 9883 ; WX 896 ; N uni269B ; G 3955
+U 9884 ; WX 896 ; N uni269C ; G 3956
+U 9886 ; WX 896 ; N uni269E ; G 3957
+U 9887 ; WX 896 ; N uni269F ; G 3958
+U 9888 ; WX 896 ; N uni26A0 ; G 3959
+U 9889 ; WX 702 ; N uni26A1 ; G 3960
+U 9890 ; WX 1004 ; N uni26A2 ; G 3961
+U 9891 ; WX 1089 ; N uni26A3 ; G 3962
+U 9892 ; WX 1175 ; N uni26A4 ; G 3963
+U 9893 ; WX 903 ; N uni26A5 ; G 3964
+U 9894 ; WX 838 ; N uni26A6 ; G 3965
+U 9895 ; WX 838 ; N uni26A7 ; G 3966
+U 9896 ; WX 838 ; N uni26A8 ; G 3967
+U 9897 ; WX 838 ; N uni26A9 ; G 3968
+U 9898 ; WX 838 ; N uni26AA ; G 3969
+U 9899 ; WX 838 ; N uni26AB ; G 3970
+U 9900 ; WX 838 ; N uni26AC ; G 3971
+U 9901 ; WX 838 ; N uni26AD ; G 3972
+U 9902 ; WX 838 ; N uni26AE ; G 3973
+U 9903 ; WX 838 ; N uni26AF ; G 3974
+U 9904 ; WX 844 ; N uni26B0 ; G 3975
+U 9905 ; WX 838 ; N uni26B1 ; G 3976
+U 9906 ; WX 732 ; N uni26B2 ; G 3977
+U 9907 ; WX 732 ; N uni26B3 ; G 3978
+U 9908 ; WX 732 ; N uni26B4 ; G 3979
+U 9909 ; WX 732 ; N uni26B5 ; G 3980
+U 9910 ; WX 850 ; N uni26B6 ; G 3981
+U 9911 ; WX 732 ; N uni26B7 ; G 3982
+U 9912 ; WX 732 ; N uni26B8 ; G 3983
+U 9920 ; WX 838 ; N uni26C0 ; G 3984
+U 9921 ; WX 838 ; N uni26C1 ; G 3985
+U 9922 ; WX 838 ; N uni26C2 ; G 3986
+U 9923 ; WX 838 ; N uni26C3 ; G 3987
+U 9954 ; WX 732 ; N uni26E2 ; G 3988
+U 9985 ; WX 838 ; N uni2701 ; G 3989
+U 9986 ; WX 838 ; N uni2702 ; G 3990
+U 9987 ; WX 838 ; N uni2703 ; G 3991
+U 9988 ; WX 838 ; N uni2704 ; G 3992
+U 9990 ; WX 838 ; N uni2706 ; G 3993
+U 9991 ; WX 838 ; N uni2707 ; G 3994
+U 9992 ; WX 838 ; N uni2708 ; G 3995
+U 9993 ; WX 838 ; N uni2709 ; G 3996
+U 9996 ; WX 838 ; N uni270C ; G 3997
+U 9997 ; WX 838 ; N uni270D ; G 3998
+U 9998 ; WX 838 ; N uni270E ; G 3999
+U 9999 ; WX 838 ; N uni270F ; G 4000
+U 10000 ; WX 838 ; N uni2710 ; G 4001
+U 10001 ; WX 838 ; N uni2711 ; G 4002
+U 10002 ; WX 838 ; N uni2712 ; G 4003
+U 10003 ; WX 838 ; N uni2713 ; G 4004
+U 10004 ; WX 838 ; N uni2714 ; G 4005
+U 10005 ; WX 838 ; N uni2715 ; G 4006
+U 10006 ; WX 838 ; N uni2716 ; G 4007
+U 10007 ; WX 838 ; N uni2717 ; G 4008
+U 10008 ; WX 838 ; N uni2718 ; G 4009
+U 10009 ; WX 838 ; N uni2719 ; G 4010
+U 10010 ; WX 838 ; N uni271A ; G 4011
+U 10011 ; WX 838 ; N uni271B ; G 4012
+U 10012 ; WX 838 ; N uni271C ; G 4013
+U 10013 ; WX 838 ; N uni271D ; G 4014
+U 10014 ; WX 838 ; N uni271E ; G 4015
+U 10015 ; WX 838 ; N uni271F ; G 4016
+U 10016 ; WX 838 ; N uni2720 ; G 4017
+U 10017 ; WX 838 ; N uni2721 ; G 4018
+U 10018 ; WX 838 ; N uni2722 ; G 4019
+U 10019 ; WX 838 ; N uni2723 ; G 4020
+U 10020 ; WX 838 ; N uni2724 ; G 4021
+U 10021 ; WX 838 ; N uni2725 ; G 4022
+U 10022 ; WX 838 ; N uni2726 ; G 4023
+U 10023 ; WX 838 ; N uni2727 ; G 4024
+U 10025 ; WX 838 ; N uni2729 ; G 4025
+U 10026 ; WX 838 ; N uni272A ; G 4026
+U 10027 ; WX 838 ; N uni272B ; G 4027
+U 10028 ; WX 838 ; N uni272C ; G 4028
+U 10029 ; WX 838 ; N uni272D ; G 4029
+U 10030 ; WX 838 ; N uni272E ; G 4030
+U 10031 ; WX 838 ; N uni272F ; G 4031
+U 10032 ; WX 838 ; N uni2730 ; G 4032
+U 10033 ; WX 838 ; N uni2731 ; G 4033
+U 10034 ; WX 838 ; N uni2732 ; G 4034
+U 10035 ; WX 838 ; N uni2733 ; G 4035
+U 10036 ; WX 838 ; N uni2734 ; G 4036
+U 10037 ; WX 838 ; N uni2735 ; G 4037
+U 10038 ; WX 838 ; N uni2736 ; G 4038
+U 10039 ; WX 838 ; N uni2737 ; G 4039
+U 10040 ; WX 838 ; N uni2738 ; G 4040
+U 10041 ; WX 838 ; N uni2739 ; G 4041
+U 10042 ; WX 838 ; N uni273A ; G 4042
+U 10043 ; WX 838 ; N uni273B ; G 4043
+U 10044 ; WX 838 ; N uni273C ; G 4044
+U 10045 ; WX 838 ; N uni273D ; G 4045
+U 10046 ; WX 838 ; N uni273E ; G 4046
+U 10047 ; WX 838 ; N uni273F ; G 4047
+U 10048 ; WX 838 ; N uni2740 ; G 4048
+U 10049 ; WX 838 ; N uni2741 ; G 4049
+U 10050 ; WX 838 ; N uni2742 ; G 4050
+U 10051 ; WX 838 ; N uni2743 ; G 4051
+U 10052 ; WX 838 ; N uni2744 ; G 4052
+U 10053 ; WX 838 ; N uni2745 ; G 4053
+U 10054 ; WX 838 ; N uni2746 ; G 4054
+U 10055 ; WX 838 ; N uni2747 ; G 4055
+U 10056 ; WX 838 ; N uni2748 ; G 4056
+U 10057 ; WX 838 ; N uni2749 ; G 4057
+U 10058 ; WX 838 ; N uni274A ; G 4058
+U 10059 ; WX 838 ; N uni274B ; G 4059
+U 10061 ; WX 896 ; N uni274D ; G 4060
+U 10063 ; WX 896 ; N uni274F ; G 4061
+U 10064 ; WX 896 ; N uni2750 ; G 4062
+U 10065 ; WX 896 ; N uni2751 ; G 4063
+U 10066 ; WX 896 ; N uni2752 ; G 4064
+U 10070 ; WX 896 ; N uni2756 ; G 4065
+U 10072 ; WX 838 ; N uni2758 ; G 4066
+U 10073 ; WX 838 ; N uni2759 ; G 4067
+U 10074 ; WX 838 ; N uni275A ; G 4068
+U 10075 ; WX 322 ; N uni275B ; G 4069
+U 10076 ; WX 322 ; N uni275C ; G 4070
+U 10077 ; WX 538 ; N uni275D ; G 4071
+U 10078 ; WX 538 ; N uni275E ; G 4072
+U 10081 ; WX 838 ; N uni2761 ; G 4073
+U 10082 ; WX 838 ; N uni2762 ; G 4074
+U 10083 ; WX 838 ; N uni2763 ; G 4075
+U 10084 ; WX 838 ; N uni2764 ; G 4076
+U 10085 ; WX 838 ; N uni2765 ; G 4077
+U 10086 ; WX 838 ; N uni2766 ; G 4078
+U 10087 ; WX 838 ; N uni2767 ; G 4079
+U 10088 ; WX 838 ; N uni2768 ; G 4080
+U 10089 ; WX 838 ; N uni2769 ; G 4081
+U 10090 ; WX 838 ; N uni276A ; G 4082
+U 10091 ; WX 838 ; N uni276B ; G 4083
+U 10092 ; WX 838 ; N uni276C ; G 4084
+U 10093 ; WX 838 ; N uni276D ; G 4085
+U 10094 ; WX 838 ; N uni276E ; G 4086
+U 10095 ; WX 838 ; N uni276F ; G 4087
+U 10096 ; WX 838 ; N uni2770 ; G 4088
+U 10097 ; WX 838 ; N uni2771 ; G 4089
+U 10098 ; WX 838 ; N uni2772 ; G 4090
+U 10099 ; WX 838 ; N uni2773 ; G 4091
+U 10100 ; WX 838 ; N uni2774 ; G 4092
+U 10101 ; WX 838 ; N uni2775 ; G 4093
+U 10102 ; WX 896 ; N uni2776 ; G 4094
+U 10103 ; WX 896 ; N uni2777 ; G 4095
+U 10104 ; WX 896 ; N uni2778 ; G 4096
+U 10105 ; WX 896 ; N uni2779 ; G 4097
+U 10106 ; WX 896 ; N uni277A ; G 4098
+U 10107 ; WX 896 ; N uni277B ; G 4099
+U 10108 ; WX 896 ; N uni277C ; G 4100
+U 10109 ; WX 896 ; N uni277D ; G 4101
+U 10110 ; WX 896 ; N uni277E ; G 4102
+U 10111 ; WX 896 ; N uni277F ; G 4103
+U 10112 ; WX 838 ; N uni2780 ; G 4104
+U 10113 ; WX 838 ; N uni2781 ; G 4105
+U 10114 ; WX 838 ; N uni2782 ; G 4106
+U 10115 ; WX 838 ; N uni2783 ; G 4107
+U 10116 ; WX 838 ; N uni2784 ; G 4108
+U 10117 ; WX 838 ; N uni2785 ; G 4109
+U 10118 ; WX 838 ; N uni2786 ; G 4110
+U 10119 ; WX 838 ; N uni2787 ; G 4111
+U 10120 ; WX 838 ; N uni2788 ; G 4112
+U 10121 ; WX 838 ; N uni2789 ; G 4113
+U 10122 ; WX 838 ; N uni278A ; G 4114
+U 10123 ; WX 838 ; N uni278B ; G 4115
+U 10124 ; WX 838 ; N uni278C ; G 4116
+U 10125 ; WX 838 ; N uni278D ; G 4117
+U 10126 ; WX 838 ; N uni278E ; G 4118
+U 10127 ; WX 838 ; N uni278F ; G 4119
+U 10128 ; WX 838 ; N uni2790 ; G 4120
+U 10129 ; WX 838 ; N uni2791 ; G 4121
+U 10130 ; WX 838 ; N uni2792 ; G 4122
+U 10131 ; WX 838 ; N uni2793 ; G 4123
+U 10132 ; WX 838 ; N uni2794 ; G 4124
+U 10136 ; WX 838 ; N uni2798 ; G 4125
+U 10137 ; WX 838 ; N uni2799 ; G 4126
+U 10138 ; WX 838 ; N uni279A ; G 4127
+U 10139 ; WX 838 ; N uni279B ; G 4128
+U 10140 ; WX 838 ; N uni279C ; G 4129
+U 10141 ; WX 838 ; N uni279D ; G 4130
+U 10142 ; WX 838 ; N uni279E ; G 4131
+U 10143 ; WX 838 ; N uni279F ; G 4132
+U 10144 ; WX 838 ; N uni27A0 ; G 4133
+U 10145 ; WX 838 ; N uni27A1 ; G 4134
+U 10146 ; WX 838 ; N uni27A2 ; G 4135
+U 10147 ; WX 838 ; N uni27A3 ; G 4136
+U 10148 ; WX 838 ; N uni27A4 ; G 4137
+U 10149 ; WX 838 ; N uni27A5 ; G 4138
+U 10150 ; WX 838 ; N uni27A6 ; G 4139
+U 10151 ; WX 838 ; N uni27A7 ; G 4140
+U 10152 ; WX 838 ; N uni27A8 ; G 4141
+U 10153 ; WX 838 ; N uni27A9 ; G 4142
+U 10154 ; WX 838 ; N uni27AA ; G 4143
+U 10155 ; WX 838 ; N uni27AB ; G 4144
+U 10156 ; WX 838 ; N uni27AC ; G 4145
+U 10157 ; WX 838 ; N uni27AD ; G 4146
+U 10158 ; WX 838 ; N uni27AE ; G 4147
+U 10159 ; WX 838 ; N uni27AF ; G 4148
+U 10161 ; WX 838 ; N uni27B1 ; G 4149
+U 10162 ; WX 838 ; N uni27B2 ; G 4150
+U 10163 ; WX 838 ; N uni27B3 ; G 4151
+U 10164 ; WX 838 ; N uni27B4 ; G 4152
+U 10165 ; WX 838 ; N uni27B5 ; G 4153
+U 10166 ; WX 838 ; N uni27B6 ; G 4154
+U 10167 ; WX 838 ; N uni27B7 ; G 4155
+U 10168 ; WX 838 ; N uni27B8 ; G 4156
+U 10169 ; WX 838 ; N uni27B9 ; G 4157
+U 10170 ; WX 838 ; N uni27BA ; G 4158
+U 10171 ; WX 838 ; N uni27BB ; G 4159
+U 10172 ; WX 838 ; N uni27BC ; G 4160
+U 10173 ; WX 838 ; N uni27BD ; G 4161
+U 10174 ; WX 838 ; N uni27BE ; G 4162
+U 10181 ; WX 390 ; N uni27C5 ; G 4163
+U 10182 ; WX 390 ; N uni27C6 ; G 4164
+U 10208 ; WX 494 ; N uni27E0 ; G 4165
+U 10214 ; WX 495 ; N uni27E6 ; G 4166
+U 10215 ; WX 495 ; N uni27E7 ; G 4167
+U 10216 ; WX 390 ; N uni27E8 ; G 4168
+U 10217 ; WX 390 ; N uni27E9 ; G 4169
+U 10218 ; WX 556 ; N uni27EA ; G 4170
+U 10219 ; WX 556 ; N uni27EB ; G 4171
+U 10224 ; WX 838 ; N uni27F0 ; G 4172
+U 10225 ; WX 838 ; N uni27F1 ; G 4173
+U 10226 ; WX 838 ; N uni27F2 ; G 4174
+U 10227 ; WX 838 ; N uni27F3 ; G 4175
+U 10228 ; WX 1157 ; N uni27F4 ; G 4176
+U 10229 ; WX 1434 ; N uni27F5 ; G 4177
+U 10230 ; WX 1434 ; N uni27F6 ; G 4178
+U 10231 ; WX 1434 ; N uni27F7 ; G 4179
+U 10232 ; WX 1434 ; N uni27F8 ; G 4180
+U 10233 ; WX 1434 ; N uni27F9 ; G 4181
+U 10234 ; WX 1434 ; N uni27FA ; G 4182
+U 10235 ; WX 1434 ; N uni27FB ; G 4183
+U 10236 ; WX 1434 ; N uni27FC ; G 4184
+U 10237 ; WX 1434 ; N uni27FD ; G 4185
+U 10238 ; WX 1434 ; N uni27FE ; G 4186
+U 10239 ; WX 1434 ; N uni27FF ; G 4187
+U 10240 ; WX 732 ; N uni2800 ; G 4188
+U 10241 ; WX 732 ; N uni2801 ; G 4189
+U 10242 ; WX 732 ; N uni2802 ; G 4190
+U 10243 ; WX 732 ; N uni2803 ; G 4191
+U 10244 ; WX 732 ; N uni2804 ; G 4192
+U 10245 ; WX 732 ; N uni2805 ; G 4193
+U 10246 ; WX 732 ; N uni2806 ; G 4194
+U 10247 ; WX 732 ; N uni2807 ; G 4195
+U 10248 ; WX 732 ; N uni2808 ; G 4196
+U 10249 ; WX 732 ; N uni2809 ; G 4197
+U 10250 ; WX 732 ; N uni280A ; G 4198
+U 10251 ; WX 732 ; N uni280B ; G 4199
+U 10252 ; WX 732 ; N uni280C ; G 4200
+U 10253 ; WX 732 ; N uni280D ; G 4201
+U 10254 ; WX 732 ; N uni280E ; G 4202
+U 10255 ; WX 732 ; N uni280F ; G 4203
+U 10256 ; WX 732 ; N uni2810 ; G 4204
+U 10257 ; WX 732 ; N uni2811 ; G 4205
+U 10258 ; WX 732 ; N uni2812 ; G 4206
+U 10259 ; WX 732 ; N uni2813 ; G 4207
+U 10260 ; WX 732 ; N uni2814 ; G 4208
+U 10261 ; WX 732 ; N uni2815 ; G 4209
+U 10262 ; WX 732 ; N uni2816 ; G 4210
+U 10263 ; WX 732 ; N uni2817 ; G 4211
+U 10264 ; WX 732 ; N uni2818 ; G 4212
+U 10265 ; WX 732 ; N uni2819 ; G 4213
+U 10266 ; WX 732 ; N uni281A ; G 4214
+U 10267 ; WX 732 ; N uni281B ; G 4215
+U 10268 ; WX 732 ; N uni281C ; G 4216
+U 10269 ; WX 732 ; N uni281D ; G 4217
+U 10270 ; WX 732 ; N uni281E ; G 4218
+U 10271 ; WX 732 ; N uni281F ; G 4219
+U 10272 ; WX 732 ; N uni2820 ; G 4220
+U 10273 ; WX 732 ; N uni2821 ; G 4221
+U 10274 ; WX 732 ; N uni2822 ; G 4222
+U 10275 ; WX 732 ; N uni2823 ; G 4223
+U 10276 ; WX 732 ; N uni2824 ; G 4224
+U 10277 ; WX 732 ; N uni2825 ; G 4225
+U 10278 ; WX 732 ; N uni2826 ; G 4226
+U 10279 ; WX 732 ; N uni2827 ; G 4227
+U 10280 ; WX 732 ; N uni2828 ; G 4228
+U 10281 ; WX 732 ; N uni2829 ; G 4229
+U 10282 ; WX 732 ; N uni282A ; G 4230
+U 10283 ; WX 732 ; N uni282B ; G 4231
+U 10284 ; WX 732 ; N uni282C ; G 4232
+U 10285 ; WX 732 ; N uni282D ; G 4233
+U 10286 ; WX 732 ; N uni282E ; G 4234
+U 10287 ; WX 732 ; N uni282F ; G 4235
+U 10288 ; WX 732 ; N uni2830 ; G 4236
+U 10289 ; WX 732 ; N uni2831 ; G 4237
+U 10290 ; WX 732 ; N uni2832 ; G 4238
+U 10291 ; WX 732 ; N uni2833 ; G 4239
+U 10292 ; WX 732 ; N uni2834 ; G 4240
+U 10293 ; WX 732 ; N uni2835 ; G 4241
+U 10294 ; WX 732 ; N uni2836 ; G 4242
+U 10295 ; WX 732 ; N uni2837 ; G 4243
+U 10296 ; WX 732 ; N uni2838 ; G 4244
+U 10297 ; WX 732 ; N uni2839 ; G 4245
+U 10298 ; WX 732 ; N uni283A ; G 4246
+U 10299 ; WX 732 ; N uni283B ; G 4247
+U 10300 ; WX 732 ; N uni283C ; G 4248
+U 10301 ; WX 732 ; N uni283D ; G 4249
+U 10302 ; WX 732 ; N uni283E ; G 4250
+U 10303 ; WX 732 ; N uni283F ; G 4251
+U 10304 ; WX 732 ; N uni2840 ; G 4252
+U 10305 ; WX 732 ; N uni2841 ; G 4253
+U 10306 ; WX 732 ; N uni2842 ; G 4254
+U 10307 ; WX 732 ; N uni2843 ; G 4255
+U 10308 ; WX 732 ; N uni2844 ; G 4256
+U 10309 ; WX 732 ; N uni2845 ; G 4257
+U 10310 ; WX 732 ; N uni2846 ; G 4258
+U 10311 ; WX 732 ; N uni2847 ; G 4259
+U 10312 ; WX 732 ; N uni2848 ; G 4260
+U 10313 ; WX 732 ; N uni2849 ; G 4261
+U 10314 ; WX 732 ; N uni284A ; G 4262
+U 10315 ; WX 732 ; N uni284B ; G 4263
+U 10316 ; WX 732 ; N uni284C ; G 4264
+U 10317 ; WX 732 ; N uni284D ; G 4265
+U 10318 ; WX 732 ; N uni284E ; G 4266
+U 10319 ; WX 732 ; N uni284F ; G 4267
+U 10320 ; WX 732 ; N uni2850 ; G 4268
+U 10321 ; WX 732 ; N uni2851 ; G 4269
+U 10322 ; WX 732 ; N uni2852 ; G 4270
+U 10323 ; WX 732 ; N uni2853 ; G 4271
+U 10324 ; WX 732 ; N uni2854 ; G 4272
+U 10325 ; WX 732 ; N uni2855 ; G 4273
+U 10326 ; WX 732 ; N uni2856 ; G 4274
+U 10327 ; WX 732 ; N uni2857 ; G 4275
+U 10328 ; WX 732 ; N uni2858 ; G 4276
+U 10329 ; WX 732 ; N uni2859 ; G 4277
+U 10330 ; WX 732 ; N uni285A ; G 4278
+U 10331 ; WX 732 ; N uni285B ; G 4279
+U 10332 ; WX 732 ; N uni285C ; G 4280
+U 10333 ; WX 732 ; N uni285D ; G 4281
+U 10334 ; WX 732 ; N uni285E ; G 4282
+U 10335 ; WX 732 ; N uni285F ; G 4283
+U 10336 ; WX 732 ; N uni2860 ; G 4284
+U 10337 ; WX 732 ; N uni2861 ; G 4285
+U 10338 ; WX 732 ; N uni2862 ; G 4286
+U 10339 ; WX 732 ; N uni2863 ; G 4287
+U 10340 ; WX 732 ; N uni2864 ; G 4288
+U 10341 ; WX 732 ; N uni2865 ; G 4289
+U 10342 ; WX 732 ; N uni2866 ; G 4290
+U 10343 ; WX 732 ; N uni2867 ; G 4291
+U 10344 ; WX 732 ; N uni2868 ; G 4292
+U 10345 ; WX 732 ; N uni2869 ; G 4293
+U 10346 ; WX 732 ; N uni286A ; G 4294
+U 10347 ; WX 732 ; N uni286B ; G 4295
+U 10348 ; WX 732 ; N uni286C ; G 4296
+U 10349 ; WX 732 ; N uni286D ; G 4297
+U 10350 ; WX 732 ; N uni286E ; G 4298
+U 10351 ; WX 732 ; N uni286F ; G 4299
+U 10352 ; WX 732 ; N uni2870 ; G 4300
+U 10353 ; WX 732 ; N uni2871 ; G 4301
+U 10354 ; WX 732 ; N uni2872 ; G 4302
+U 10355 ; WX 732 ; N uni2873 ; G 4303
+U 10356 ; WX 732 ; N uni2874 ; G 4304
+U 10357 ; WX 732 ; N uni2875 ; G 4305
+U 10358 ; WX 732 ; N uni2876 ; G 4306
+U 10359 ; WX 732 ; N uni2877 ; G 4307
+U 10360 ; WX 732 ; N uni2878 ; G 4308
+U 10361 ; WX 732 ; N uni2879 ; G 4309
+U 10362 ; WX 732 ; N uni287A ; G 4310
+U 10363 ; WX 732 ; N uni287B ; G 4311
+U 10364 ; WX 732 ; N uni287C ; G 4312
+U 10365 ; WX 732 ; N uni287D ; G 4313
+U 10366 ; WX 732 ; N uni287E ; G 4314
+U 10367 ; WX 732 ; N uni287F ; G 4315
+U 10368 ; WX 732 ; N uni2880 ; G 4316
+U 10369 ; WX 732 ; N uni2881 ; G 4317
+U 10370 ; WX 732 ; N uni2882 ; G 4318
+U 10371 ; WX 732 ; N uni2883 ; G 4319
+U 10372 ; WX 732 ; N uni2884 ; G 4320
+U 10373 ; WX 732 ; N uni2885 ; G 4321
+U 10374 ; WX 732 ; N uni2886 ; G 4322
+U 10375 ; WX 732 ; N uni2887 ; G 4323
+U 10376 ; WX 732 ; N uni2888 ; G 4324
+U 10377 ; WX 732 ; N uni2889 ; G 4325
+U 10378 ; WX 732 ; N uni288A ; G 4326
+U 10379 ; WX 732 ; N uni288B ; G 4327
+U 10380 ; WX 732 ; N uni288C ; G 4328
+U 10381 ; WX 732 ; N uni288D ; G 4329
+U 10382 ; WX 732 ; N uni288E ; G 4330
+U 10383 ; WX 732 ; N uni288F ; G 4331
+U 10384 ; WX 732 ; N uni2890 ; G 4332
+U 10385 ; WX 732 ; N uni2891 ; G 4333
+U 10386 ; WX 732 ; N uni2892 ; G 4334
+U 10387 ; WX 732 ; N uni2893 ; G 4335
+U 10388 ; WX 732 ; N uni2894 ; G 4336
+U 10389 ; WX 732 ; N uni2895 ; G 4337
+U 10390 ; WX 732 ; N uni2896 ; G 4338
+U 10391 ; WX 732 ; N uni2897 ; G 4339
+U 10392 ; WX 732 ; N uni2898 ; G 4340
+U 10393 ; WX 732 ; N uni2899 ; G 4341
+U 10394 ; WX 732 ; N uni289A ; G 4342
+U 10395 ; WX 732 ; N uni289B ; G 4343
+U 10396 ; WX 732 ; N uni289C ; G 4344
+U 10397 ; WX 732 ; N uni289D ; G 4345
+U 10398 ; WX 732 ; N uni289E ; G 4346
+U 10399 ; WX 732 ; N uni289F ; G 4347
+U 10400 ; WX 732 ; N uni28A0 ; G 4348
+U 10401 ; WX 732 ; N uni28A1 ; G 4349
+U 10402 ; WX 732 ; N uni28A2 ; G 4350
+U 10403 ; WX 732 ; N uni28A3 ; G 4351
+U 10404 ; WX 732 ; N uni28A4 ; G 4352
+U 10405 ; WX 732 ; N uni28A5 ; G 4353
+U 10406 ; WX 732 ; N uni28A6 ; G 4354
+U 10407 ; WX 732 ; N uni28A7 ; G 4355
+U 10408 ; WX 732 ; N uni28A8 ; G 4356
+U 10409 ; WX 732 ; N uni28A9 ; G 4357
+U 10410 ; WX 732 ; N uni28AA ; G 4358
+U 10411 ; WX 732 ; N uni28AB ; G 4359
+U 10412 ; WX 732 ; N uni28AC ; G 4360
+U 10413 ; WX 732 ; N uni28AD ; G 4361
+U 10414 ; WX 732 ; N uni28AE ; G 4362
+U 10415 ; WX 732 ; N uni28AF ; G 4363
+U 10416 ; WX 732 ; N uni28B0 ; G 4364
+U 10417 ; WX 732 ; N uni28B1 ; G 4365
+U 10418 ; WX 732 ; N uni28B2 ; G 4366
+U 10419 ; WX 732 ; N uni28B3 ; G 4367
+U 10420 ; WX 732 ; N uni28B4 ; G 4368
+U 10421 ; WX 732 ; N uni28B5 ; G 4369
+U 10422 ; WX 732 ; N uni28B6 ; G 4370
+U 10423 ; WX 732 ; N uni28B7 ; G 4371
+U 10424 ; WX 732 ; N uni28B8 ; G 4372
+U 10425 ; WX 732 ; N uni28B9 ; G 4373
+U 10426 ; WX 732 ; N uni28BA ; G 4374
+U 10427 ; WX 732 ; N uni28BB ; G 4375
+U 10428 ; WX 732 ; N uni28BC ; G 4376
+U 10429 ; WX 732 ; N uni28BD ; G 4377
+U 10430 ; WX 732 ; N uni28BE ; G 4378
+U 10431 ; WX 732 ; N uni28BF ; G 4379
+U 10432 ; WX 732 ; N uni28C0 ; G 4380
+U 10433 ; WX 732 ; N uni28C1 ; G 4381
+U 10434 ; WX 732 ; N uni28C2 ; G 4382
+U 10435 ; WX 732 ; N uni28C3 ; G 4383
+U 10436 ; WX 732 ; N uni28C4 ; G 4384
+U 10437 ; WX 732 ; N uni28C5 ; G 4385
+U 10438 ; WX 732 ; N uni28C6 ; G 4386
+U 10439 ; WX 732 ; N uni28C7 ; G 4387
+U 10440 ; WX 732 ; N uni28C8 ; G 4388
+U 10441 ; WX 732 ; N uni28C9 ; G 4389
+U 10442 ; WX 732 ; N uni28CA ; G 4390
+U 10443 ; WX 732 ; N uni28CB ; G 4391
+U 10444 ; WX 732 ; N uni28CC ; G 4392
+U 10445 ; WX 732 ; N uni28CD ; G 4393
+U 10446 ; WX 732 ; N uni28CE ; G 4394
+U 10447 ; WX 732 ; N uni28CF ; G 4395
+U 10448 ; WX 732 ; N uni28D0 ; G 4396
+U 10449 ; WX 732 ; N uni28D1 ; G 4397
+U 10450 ; WX 732 ; N uni28D2 ; G 4398
+U 10451 ; WX 732 ; N uni28D3 ; G 4399
+U 10452 ; WX 732 ; N uni28D4 ; G 4400
+U 10453 ; WX 732 ; N uni28D5 ; G 4401
+U 10454 ; WX 732 ; N uni28D6 ; G 4402
+U 10455 ; WX 732 ; N uni28D7 ; G 4403
+U 10456 ; WX 732 ; N uni28D8 ; G 4404
+U 10457 ; WX 732 ; N uni28D9 ; G 4405
+U 10458 ; WX 732 ; N uni28DA ; G 4406
+U 10459 ; WX 732 ; N uni28DB ; G 4407
+U 10460 ; WX 732 ; N uni28DC ; G 4408
+U 10461 ; WX 732 ; N uni28DD ; G 4409
+U 10462 ; WX 732 ; N uni28DE ; G 4410
+U 10463 ; WX 732 ; N uni28DF ; G 4411
+U 10464 ; WX 732 ; N uni28E0 ; G 4412
+U 10465 ; WX 732 ; N uni28E1 ; G 4413
+U 10466 ; WX 732 ; N uni28E2 ; G 4414
+U 10467 ; WX 732 ; N uni28E3 ; G 4415
+U 10468 ; WX 732 ; N uni28E4 ; G 4416
+U 10469 ; WX 732 ; N uni28E5 ; G 4417
+U 10470 ; WX 732 ; N uni28E6 ; G 4418
+U 10471 ; WX 732 ; N uni28E7 ; G 4419
+U 10472 ; WX 732 ; N uni28E8 ; G 4420
+U 10473 ; WX 732 ; N uni28E9 ; G 4421
+U 10474 ; WX 732 ; N uni28EA ; G 4422
+U 10475 ; WX 732 ; N uni28EB ; G 4423
+U 10476 ; WX 732 ; N uni28EC ; G 4424
+U 10477 ; WX 732 ; N uni28ED ; G 4425
+U 10478 ; WX 732 ; N uni28EE ; G 4426
+U 10479 ; WX 732 ; N uni28EF ; G 4427
+U 10480 ; WX 732 ; N uni28F0 ; G 4428
+U 10481 ; WX 732 ; N uni28F1 ; G 4429
+U 10482 ; WX 732 ; N uni28F2 ; G 4430
+U 10483 ; WX 732 ; N uni28F3 ; G 4431
+U 10484 ; WX 732 ; N uni28F4 ; G 4432
+U 10485 ; WX 732 ; N uni28F5 ; G 4433
+U 10486 ; WX 732 ; N uni28F6 ; G 4434
+U 10487 ; WX 732 ; N uni28F7 ; G 4435
+U 10488 ; WX 732 ; N uni28F8 ; G 4436
+U 10489 ; WX 732 ; N uni28F9 ; G 4437
+U 10490 ; WX 732 ; N uni28FA ; G 4438
+U 10491 ; WX 732 ; N uni28FB ; G 4439
+U 10492 ; WX 732 ; N uni28FC ; G 4440
+U 10493 ; WX 732 ; N uni28FD ; G 4441
+U 10494 ; WX 732 ; N uni28FE ; G 4442
+U 10495 ; WX 732 ; N uni28FF ; G 4443
+U 10502 ; WX 838 ; N uni2906 ; G 4444
+U 10503 ; WX 838 ; N uni2907 ; G 4445
+U 10506 ; WX 838 ; N uni290A ; G 4446
+U 10507 ; WX 838 ; N uni290B ; G 4447
+U 10560 ; WX 683 ; N uni2940 ; G 4448
+U 10561 ; WX 683 ; N uni2941 ; G 4449
+U 10627 ; WX 734 ; N uni2983 ; G 4450
+U 10628 ; WX 734 ; N uni2984 ; G 4451
+U 10702 ; WX 838 ; N uni29CE ; G 4452
+U 10703 ; WX 1000 ; N uni29CF ; G 4453
+U 10704 ; WX 1000 ; N uni29D0 ; G 4454
+U 10705 ; WX 1000 ; N uni29D1 ; G 4455
+U 10706 ; WX 1000 ; N uni29D2 ; G 4456
+U 10707 ; WX 1000 ; N uni29D3 ; G 4457
+U 10708 ; WX 1000 ; N uni29D4 ; G 4458
+U 10709 ; WX 1000 ; N uni29D5 ; G 4459
+U 10731 ; WX 494 ; N uni29EB ; G 4460
+U 10746 ; WX 838 ; N uni29FA ; G 4461
+U 10747 ; WX 838 ; N uni29FB ; G 4462
+U 10752 ; WX 1000 ; N uni2A00 ; G 4463
+U 10753 ; WX 1000 ; N uni2A01 ; G 4464
+U 10754 ; WX 1000 ; N uni2A02 ; G 4465
+U 10764 ; WX 1325 ; N uni2A0C ; G 4466
+U 10765 ; WX 521 ; N uni2A0D ; G 4467
+U 10766 ; WX 521 ; N uni2A0E ; G 4468
+U 10767 ; WX 521 ; N uni2A0F ; G 4469
+U 10768 ; WX 521 ; N uni2A10 ; G 4470
+U 10769 ; WX 521 ; N uni2A11 ; G 4471
+U 10770 ; WX 521 ; N uni2A12 ; G 4472
+U 10771 ; WX 521 ; N uni2A13 ; G 4473
+U 10772 ; WX 521 ; N uni2A14 ; G 4474
+U 10773 ; WX 521 ; N uni2A15 ; G 4475
+U 10774 ; WX 521 ; N uni2A16 ; G 4476
+U 10775 ; WX 521 ; N uni2A17 ; G 4477
+U 10776 ; WX 521 ; N uni2A18 ; G 4478
+U 10777 ; WX 521 ; N uni2A19 ; G 4479
+U 10778 ; WX 521 ; N uni2A1A ; G 4480
+U 10779 ; WX 521 ; N uni2A1B ; G 4481
+U 10780 ; WX 521 ; N uni2A1C ; G 4482
+U 10799 ; WX 838 ; N uni2A2F ; G 4483
+U 10858 ; WX 838 ; N uni2A6A ; G 4484
+U 10859 ; WX 838 ; N uni2A6B ; G 4485
+U 10877 ; WX 838 ; N uni2A7D ; G 4486
+U 10878 ; WX 838 ; N uni2A7E ; G 4487
+U 10879 ; WX 838 ; N uni2A7F ; G 4488
+U 10880 ; WX 838 ; N uni2A80 ; G 4489
+U 10881 ; WX 838 ; N uni2A81 ; G 4490
+U 10882 ; WX 838 ; N uni2A82 ; G 4491
+U 10883 ; WX 838 ; N uni2A83 ; G 4492
+U 10884 ; WX 838 ; N uni2A84 ; G 4493
+U 10885 ; WX 838 ; N uni2A85 ; G 4494
+U 10886 ; WX 838 ; N uni2A86 ; G 4495
+U 10887 ; WX 838 ; N uni2A87 ; G 4496
+U 10888 ; WX 838 ; N uni2A88 ; G 4497
+U 10889 ; WX 838 ; N uni2A89 ; G 4498
+U 10890 ; WX 838 ; N uni2A8A ; G 4499
+U 10891 ; WX 838 ; N uni2A8B ; G 4500
+U 10892 ; WX 838 ; N uni2A8C ; G 4501
+U 10893 ; WX 838 ; N uni2A8D ; G 4502
+U 10894 ; WX 838 ; N uni2A8E ; G 4503
+U 10895 ; WX 838 ; N uni2A8F ; G 4504
+U 10896 ; WX 838 ; N uni2A90 ; G 4505
+U 10897 ; WX 838 ; N uni2A91 ; G 4506
+U 10898 ; WX 838 ; N uni2A92 ; G 4507
+U 10899 ; WX 838 ; N uni2A93 ; G 4508
+U 10900 ; WX 838 ; N uni2A94 ; G 4509
+U 10901 ; WX 838 ; N uni2A95 ; G 4510
+U 10902 ; WX 838 ; N uni2A96 ; G 4511
+U 10903 ; WX 838 ; N uni2A97 ; G 4512
+U 10904 ; WX 838 ; N uni2A98 ; G 4513
+U 10905 ; WX 838 ; N uni2A99 ; G 4514
+U 10906 ; WX 838 ; N uni2A9A ; G 4515
+U 10907 ; WX 838 ; N uni2A9B ; G 4516
+U 10908 ; WX 838 ; N uni2A9C ; G 4517
+U 10909 ; WX 838 ; N uni2A9D ; G 4518
+U 10910 ; WX 838 ; N uni2A9E ; G 4519
+U 10911 ; WX 838 ; N uni2A9F ; G 4520
+U 10912 ; WX 838 ; N uni2AA0 ; G 4521
+U 10926 ; WX 838 ; N uni2AAE ; G 4522
+U 10927 ; WX 838 ; N uni2AAF ; G 4523
+U 10928 ; WX 838 ; N uni2AB0 ; G 4524
+U 10929 ; WX 838 ; N uni2AB1 ; G 4525
+U 10930 ; WX 838 ; N uni2AB2 ; G 4526
+U 10931 ; WX 838 ; N uni2AB3 ; G 4527
+U 10932 ; WX 838 ; N uni2AB4 ; G 4528
+U 10933 ; WX 838 ; N uni2AB5 ; G 4529
+U 10934 ; WX 838 ; N uni2AB6 ; G 4530
+U 10935 ; WX 838 ; N uni2AB7 ; G 4531
+U 10936 ; WX 838 ; N uni2AB8 ; G 4532
+U 10937 ; WX 838 ; N uni2AB9 ; G 4533
+U 10938 ; WX 838 ; N uni2ABA ; G 4534
+U 11001 ; WX 838 ; N uni2AF9 ; G 4535
+U 11002 ; WX 838 ; N uni2AFA ; G 4536
+U 11008 ; WX 838 ; N uni2B00 ; G 4537
+U 11009 ; WX 838 ; N uni2B01 ; G 4538
+U 11010 ; WX 838 ; N uni2B02 ; G 4539
+U 11011 ; WX 838 ; N uni2B03 ; G 4540
+U 11012 ; WX 838 ; N uni2B04 ; G 4541
+U 11013 ; WX 838 ; N uni2B05 ; G 4542
+U 11014 ; WX 838 ; N uni2B06 ; G 4543
+U 11015 ; WX 838 ; N uni2B07 ; G 4544
+U 11016 ; WX 838 ; N uni2B08 ; G 4545
+U 11017 ; WX 838 ; N uni2B09 ; G 4546
+U 11018 ; WX 838 ; N uni2B0A ; G 4547
+U 11019 ; WX 838 ; N uni2B0B ; G 4548
+U 11020 ; WX 838 ; N uni2B0C ; G 4549
+U 11021 ; WX 838 ; N uni2B0D ; G 4550
+U 11022 ; WX 836 ; N uni2B0E ; G 4551
+U 11023 ; WX 836 ; N uni2B0F ; G 4552
+U 11024 ; WX 836 ; N uni2B10 ; G 4553
+U 11025 ; WX 836 ; N uni2B11 ; G 4554
+U 11026 ; WX 945 ; N uni2B12 ; G 4555
+U 11027 ; WX 945 ; N uni2B13 ; G 4556
+U 11028 ; WX 945 ; N uni2B14 ; G 4557
+U 11029 ; WX 945 ; N uni2B15 ; G 4558
+U 11030 ; WX 769 ; N uni2B16 ; G 4559
+U 11031 ; WX 769 ; N uni2B17 ; G 4560
+U 11032 ; WX 769 ; N uni2B18 ; G 4561
+U 11033 ; WX 769 ; N uni2B19 ; G 4562
+U 11034 ; WX 945 ; N uni2B1A ; G 4563
+U 11039 ; WX 869 ; N uni2B1F ; G 4564
+U 11040 ; WX 869 ; N uni2B20 ; G 4565
+U 11041 ; WX 873 ; N uni2B21 ; G 4566
+U 11042 ; WX 873 ; N uni2B22 ; G 4567
+U 11043 ; WX 873 ; N uni2B23 ; G 4568
+U 11044 ; WX 1119 ; N uni2B24 ; G 4569
+U 11091 ; WX 869 ; N uni2B53 ; G 4570
+U 11092 ; WX 869 ; N uni2B54 ; G 4571
+U 11360 ; WX 557 ; N uni2C60 ; G 4572
+U 11361 ; WX 278 ; N uni2C61 ; G 4573
+U 11362 ; WX 557 ; N uni2C62 ; G 4574
+U 11363 ; WX 603 ; N uni2C63 ; G 4575
+U 11364 ; WX 695 ; N uni2C64 ; G 4576
+U 11365 ; WX 613 ; N uni2C65 ; G 4577
+U 11366 ; WX 392 ; N uni2C66 ; G 4578
+U 11367 ; WX 752 ; N uni2C67 ; G 4579
+U 11368 ; WX 634 ; N uni2C68 ; G 4580
+U 11369 ; WX 656 ; N uni2C69 ; G 4581
+U 11370 ; WX 579 ; N uni2C6A ; G 4582
+U 11371 ; WX 685 ; N uni2C6B ; G 4583
+U 11372 ; WX 525 ; N uni2C6C ; G 4584
+U 11373 ; WX 781 ; N uni2C6D ; G 4585
+U 11374 ; WX 863 ; N uni2C6E ; G 4586
+U 11375 ; WX 684 ; N uni2C6F ; G 4587
+U 11376 ; WX 781 ; N uni2C70 ; G 4588
+U 11377 ; WX 734 ; N uni2C71 ; G 4589
+U 11378 ; WX 1128 ; N uni2C72 ; G 4590
+U 11379 ; WX 961 ; N uni2C73 ; G 4591
+U 11380 ; WX 592 ; N uni2C74 ; G 4592
+U 11381 ; WX 654 ; N uni2C75 ; G 4593
+U 11382 ; WX 568 ; N uni2C76 ; G 4594
+U 11383 ; WX 660 ; N uni2C77 ; G 4595
+U 11385 ; WX 414 ; N uni2C79 ; G 4596
+U 11386 ; WX 612 ; N uni2C7A ; G 4597
+U 11387 ; WX 491 ; N uni2C7B ; G 4598
+U 11388 ; WX 175 ; N uni2C7C ; G 4599
+U 11389 ; WX 431 ; N uni2C7D ; G 4600
+U 11390 ; WX 635 ; N uni2C7E ; G 4601
+U 11391 ; WX 685 ; N uni2C7F ; G 4602
+U 11520 ; WX 591 ; N uni2D00 ; G 4603
+U 11521 ; WX 595 ; N uni2D01 ; G 4604
+U 11522 ; WX 564 ; N uni2D02 ; G 4605
+U 11523 ; WX 602 ; N uni2D03 ; G 4606
+U 11524 ; WX 587 ; N uni2D04 ; G 4607
+U 11525 ; WX 911 ; N uni2D05 ; G 4608
+U 11526 ; WX 626 ; N uni2D06 ; G 4609
+U 11527 ; WX 952 ; N uni2D07 ; G 4610
+U 11528 ; WX 595 ; N uni2D08 ; G 4611
+U 11529 ; WX 607 ; N uni2D09 ; G 4612
+U 11530 ; WX 954 ; N uni2D0A ; G 4613
+U 11531 ; WX 620 ; N uni2D0B ; G 4614
+U 11532 ; WX 595 ; N uni2D0C ; G 4615
+U 11533 ; WX 926 ; N uni2D0D ; G 4616
+U 11534 ; WX 595 ; N uni2D0E ; G 4617
+U 11535 ; WX 806 ; N uni2D0F ; G 4618
+U 11536 ; WX 931 ; N uni2D10 ; G 4619
+U 11537 ; WX 584 ; N uni2D11 ; G 4620
+U 11538 ; WX 592 ; N uni2D12 ; G 4621
+U 11539 ; WX 923 ; N uni2D13 ; G 4622
+U 11540 ; WX 953 ; N uni2D14 ; G 4623
+U 11541 ; WX 828 ; N uni2D15 ; G 4624
+U 11542 ; WX 596 ; N uni2D16 ; G 4625
+U 11543 ; WX 595 ; N uni2D17 ; G 4626
+U 11544 ; WX 590 ; N uni2D18 ; G 4627
+U 11545 ; WX 592 ; N uni2D19 ; G 4628
+U 11546 ; WX 592 ; N uni2D1A ; G 4629
+U 11547 ; WX 621 ; N uni2D1B ; G 4630
+U 11548 ; WX 920 ; N uni2D1C ; G 4631
+U 11549 ; WX 589 ; N uni2D1D ; G 4632
+U 11550 ; WX 586 ; N uni2D1E ; G 4633
+U 11551 ; WX 581 ; N uni2D1F ; G 4634
+U 11552 ; WX 914 ; N uni2D20 ; G 4635
+U 11553 ; WX 596 ; N uni2D21 ; G 4636
+U 11554 ; WX 595 ; N uni2D22 ; G 4637
+U 11555 ; WX 592 ; N uni2D23 ; G 4638
+U 11556 ; WX 642 ; N uni2D24 ; G 4639
+U 11557 ; WX 901 ; N uni2D25 ; G 4640
+U 11568 ; WX 646 ; N uni2D30 ; G 4641
+U 11569 ; WX 888 ; N uni2D31 ; G 4642
+U 11570 ; WX 888 ; N uni2D32 ; G 4643
+U 11571 ; WX 682 ; N uni2D33 ; G 4644
+U 11572 ; WX 684 ; N uni2D34 ; G 4645
+U 11573 ; WX 635 ; N uni2D35 ; G 4646
+U 11574 ; WX 562 ; N uni2D36 ; G 4647
+U 11575 ; WX 684 ; N uni2D37 ; G 4648
+U 11576 ; WX 684 ; N uni2D38 ; G 4649
+U 11577 ; WX 632 ; N uni2D39 ; G 4650
+U 11578 ; WX 632 ; N uni2D3A ; G 4651
+U 11579 ; WX 683 ; N uni2D3B ; G 4652
+U 11580 ; WX 875 ; N uni2D3C ; G 4653
+U 11581 ; WX 685 ; N uni2D3D ; G 4654
+U 11582 ; WX 491 ; N uni2D3E ; G 4655
+U 11583 ; WX 685 ; N uni2D3F ; G 4656
+U 11584 ; WX 888 ; N uni2D40 ; G 4657
+U 11585 ; WX 888 ; N uni2D41 ; G 4658
+U 11586 ; WX 300 ; N uni2D42 ; G 4659
+U 11587 ; WX 627 ; N uni2D43 ; G 4660
+U 11588 ; WX 752 ; N uni2D44 ; G 4661
+U 11589 ; WX 656 ; N uni2D45 ; G 4662
+U 11590 ; WX 527 ; N uni2D46 ; G 4663
+U 11591 ; WX 685 ; N uni2D47 ; G 4664
+U 11592 ; WX 645 ; N uni2D48 ; G 4665
+U 11593 ; WX 632 ; N uni2D49 ; G 4666
+U 11594 ; WX 502 ; N uni2D4A ; G 4667
+U 11595 ; WX 953 ; N uni2D4B ; G 4668
+U 11596 ; WX 778 ; N uni2D4C ; G 4669
+U 11597 ; WX 748 ; N uni2D4D ; G 4670
+U 11598 ; WX 621 ; N uni2D4E ; G 4671
+U 11599 ; WX 295 ; N uni2D4F ; G 4672
+U 11600 ; WX 778 ; N uni2D50 ; G 4673
+U 11601 ; WX 295 ; N uni2D51 ; G 4674
+U 11602 ; WX 752 ; N uni2D52 ; G 4675
+U 11603 ; WX 633 ; N uni2D53 ; G 4676
+U 11604 ; WX 888 ; N uni2D54 ; G 4677
+U 11605 ; WX 888 ; N uni2D55 ; G 4678
+U 11606 ; WX 752 ; N uni2D56 ; G 4679
+U 11607 ; WX 320 ; N uni2D57 ; G 4680
+U 11608 ; WX 749 ; N uni2D58 ; G 4681
+U 11609 ; WX 888 ; N uni2D59 ; G 4682
+U 11610 ; WX 888 ; N uni2D5A ; G 4683
+U 11611 ; WX 698 ; N uni2D5B ; G 4684
+U 11612 ; WX 768 ; N uni2D5C ; G 4685
+U 11613 ; WX 685 ; N uni2D5D ; G 4686
+U 11614 ; WX 698 ; N uni2D5E ; G 4687
+U 11615 ; WX 622 ; N uni2D5F ; G 4688
+U 11616 ; WX 684 ; N uni2D60 ; G 4689
+U 11617 ; WX 752 ; N uni2D61 ; G 4690
+U 11618 ; WX 632 ; N uni2D62 ; G 4691
+U 11619 ; WX 788 ; N uni2D63 ; G 4692
+U 11620 ; WX 567 ; N uni2D64 ; G 4693
+U 11621 ; WX 788 ; N uni2D65 ; G 4694
+U 11631 ; WX 515 ; N uni2D6F ; G 4695
+U 11800 ; WX 531 ; N uni2E18 ; G 4696
+U 11807 ; WX 838 ; N uni2E1F ; G 4697
+U 11810 ; WX 390 ; N uni2E22 ; G 4698
+U 11811 ; WX 390 ; N uni2E23 ; G 4699
+U 11812 ; WX 390 ; N uni2E24 ; G 4700
+U 11813 ; WX 390 ; N uni2E25 ; G 4701
+U 11822 ; WX 531 ; N uni2E2E ; G 4702
+U 19904 ; WX 896 ; N uni4DC0 ; G 4703
+U 19905 ; WX 896 ; N uni4DC1 ; G 4704
+U 19906 ; WX 896 ; N uni4DC2 ; G 4705
+U 19907 ; WX 896 ; N uni4DC3 ; G 4706
+U 19908 ; WX 896 ; N uni4DC4 ; G 4707
+U 19909 ; WX 896 ; N uni4DC5 ; G 4708
+U 19910 ; WX 896 ; N uni4DC6 ; G 4709
+U 19911 ; WX 896 ; N uni4DC7 ; G 4710
+U 19912 ; WX 896 ; N uni4DC8 ; G 4711
+U 19913 ; WX 896 ; N uni4DC9 ; G 4712
+U 19914 ; WX 896 ; N uni4DCA ; G 4713
+U 19915 ; WX 896 ; N uni4DCB ; G 4714
+U 19916 ; WX 896 ; N uni4DCC ; G 4715
+U 19917 ; WX 896 ; N uni4DCD ; G 4716
+U 19918 ; WX 896 ; N uni4DCE ; G 4717
+U 19919 ; WX 896 ; N uni4DCF ; G 4718
+U 19920 ; WX 896 ; N uni4DD0 ; G 4719
+U 19921 ; WX 896 ; N uni4DD1 ; G 4720
+U 19922 ; WX 896 ; N uni4DD2 ; G 4721
+U 19923 ; WX 896 ; N uni4DD3 ; G 4722
+U 19924 ; WX 896 ; N uni4DD4 ; G 4723
+U 19925 ; WX 896 ; N uni4DD5 ; G 4724
+U 19926 ; WX 896 ; N uni4DD6 ; G 4725
+U 19927 ; WX 896 ; N uni4DD7 ; G 4726
+U 19928 ; WX 896 ; N uni4DD8 ; G 4727
+U 19929 ; WX 896 ; N uni4DD9 ; G 4728
+U 19930 ; WX 896 ; N uni4DDA ; G 4729
+U 19931 ; WX 896 ; N uni4DDB ; G 4730
+U 19932 ; WX 896 ; N uni4DDC ; G 4731
+U 19933 ; WX 896 ; N uni4DDD ; G 4732
+U 19934 ; WX 896 ; N uni4DDE ; G 4733
+U 19935 ; WX 896 ; N uni4DDF ; G 4734
+U 19936 ; WX 896 ; N uni4DE0 ; G 4735
+U 19937 ; WX 896 ; N uni4DE1 ; G 4736
+U 19938 ; WX 896 ; N uni4DE2 ; G 4737
+U 19939 ; WX 896 ; N uni4DE3 ; G 4738
+U 19940 ; WX 896 ; N uni4DE4 ; G 4739
+U 19941 ; WX 896 ; N uni4DE5 ; G 4740
+U 19942 ; WX 896 ; N uni4DE6 ; G 4741
+U 19943 ; WX 896 ; N uni4DE7 ; G 4742
+U 19944 ; WX 896 ; N uni4DE8 ; G 4743
+U 19945 ; WX 896 ; N uni4DE9 ; G 4744
+U 19946 ; WX 896 ; N uni4DEA ; G 4745
+U 19947 ; WX 896 ; N uni4DEB ; G 4746
+U 19948 ; WX 896 ; N uni4DEC ; G 4747
+U 19949 ; WX 896 ; N uni4DED ; G 4748
+U 19950 ; WX 896 ; N uni4DEE ; G 4749
+U 19951 ; WX 896 ; N uni4DEF ; G 4750
+U 19952 ; WX 896 ; N uni4DF0 ; G 4751
+U 19953 ; WX 896 ; N uni4DF1 ; G 4752
+U 19954 ; WX 896 ; N uni4DF2 ; G 4753
+U 19955 ; WX 896 ; N uni4DF3 ; G 4754
+U 19956 ; WX 896 ; N uni4DF4 ; G 4755
+U 19957 ; WX 896 ; N uni4DF5 ; G 4756
+U 19958 ; WX 896 ; N uni4DF6 ; G 4757
+U 19959 ; WX 896 ; N uni4DF7 ; G 4758
+U 19960 ; WX 896 ; N uni4DF8 ; G 4759
+U 19961 ; WX 896 ; N uni4DF9 ; G 4760
+U 19962 ; WX 896 ; N uni4DFA ; G 4761
+U 19963 ; WX 896 ; N uni4DFB ; G 4762
+U 19964 ; WX 896 ; N uni4DFC ; G 4763
+U 19965 ; WX 896 ; N uni4DFD ; G 4764
+U 19966 ; WX 896 ; N uni4DFE ; G 4765
+U 19967 ; WX 896 ; N uni4DFF ; G 4766
+U 42192 ; WX 686 ; N uniA4D0 ; G 4767
+U 42193 ; WX 603 ; N uniA4D1 ; G 4768
+U 42194 ; WX 603 ; N uniA4D2 ; G 4769
+U 42195 ; WX 770 ; N uniA4D3 ; G 4770
+U 42196 ; WX 611 ; N uniA4D4 ; G 4771
+U 42197 ; WX 611 ; N uniA4D5 ; G 4772
+U 42198 ; WX 775 ; N uniA4D6 ; G 4773
+U 42199 ; WX 656 ; N uniA4D7 ; G 4774
+U 42200 ; WX 656 ; N uniA4D8 ; G 4775
+U 42201 ; WX 512 ; N uniA4D9 ; G 4776
+U 42202 ; WX 698 ; N uniA4DA ; G 4777
+U 42203 ; WX 703 ; N uniA4DB ; G 4778
+U 42204 ; WX 685 ; N uniA4DC ; G 4779
+U 42205 ; WX 575 ; N uniA4DD ; G 4780
+U 42206 ; WX 575 ; N uniA4DE ; G 4781
+U 42207 ; WX 863 ; N uniA4DF ; G 4782
+U 42208 ; WX 748 ; N uniA4E0 ; G 4783
+U 42209 ; WX 557 ; N uniA4E1 ; G 4784
+U 42210 ; WX 635 ; N uniA4E2 ; G 4785
+U 42211 ; WX 695 ; N uniA4E3 ; G 4786
+U 42212 ; WX 695 ; N uniA4E4 ; G 4787
+U 42213 ; WX 684 ; N uniA4E5 ; G 4788
+U 42214 ; WX 684 ; N uniA4E6 ; G 4789
+U 42215 ; WX 752 ; N uniA4E7 ; G 4790
+U 42216 ; WX 775 ; N uniA4E8 ; G 4791
+U 42217 ; WX 512 ; N uniA4E9 ; G 4792
+U 42218 ; WX 989 ; N uniA4EA ; G 4793
+U 42219 ; WX 685 ; N uniA4EB ; G 4794
+U 42220 ; WX 611 ; N uniA4EC ; G 4795
+U 42221 ; WX 686 ; N uniA4ED ; G 4796
+U 42222 ; WX 684 ; N uniA4EE ; G 4797
+U 42223 ; WX 684 ; N uniA4EF ; G 4798
+U 42224 ; WX 632 ; N uniA4F0 ; G 4799
+U 42225 ; WX 632 ; N uniA4F1 ; G 4800
+U 42226 ; WX 295 ; N uniA4F2 ; G 4801
+U 42227 ; WX 787 ; N uniA4F3 ; G 4802
+U 42228 ; WX 732 ; N uniA4F4 ; G 4803
+U 42229 ; WX 732 ; N uniA4F5 ; G 4804
+U 42230 ; WX 557 ; N uniA4F6 ; G 4805
+U 42231 ; WX 767 ; N uniA4F7 ; G 4806
+U 42232 ; WX 300 ; N uniA4F8 ; G 4807
+U 42233 ; WX 300 ; N uniA4F9 ; G 4808
+U 42234 ; WX 596 ; N uniA4FA ; G 4809
+U 42235 ; WX 596 ; N uniA4FB ; G 4810
+U 42236 ; WX 300 ; N uniA4FC ; G 4811
+U 42237 ; WX 300 ; N uniA4FD ; G 4812
+U 42238 ; WX 588 ; N uniA4FE ; G 4813
+U 42239 ; WX 588 ; N uniA4FF ; G 4814
+U 42564 ; WX 635 ; N uniA644 ; G 4815
+U 42565 ; WX 521 ; N uniA645 ; G 4816
+U 42566 ; WX 354 ; N uniA646 ; G 4817
+U 42567 ; WX 338 ; N uniA647 ; G 4818
+U 42572 ; WX 1180 ; N uniA64C ; G 4819
+U 42573 ; WX 1028 ; N uniA64D ; G 4820
+U 42576 ; WX 1029 ; N uniA650 ; G 4821
+U 42577 ; WX 906 ; N uniA651 ; G 4822
+U 42580 ; WX 1080 ; N uniA654 ; G 4823
+U 42581 ; WX 842 ; N uniA655 ; G 4824
+U 42582 ; WX 977 ; N uniA656 ; G 4825
+U 42583 ; WX 843 ; N uniA657 ; G 4826
+U 42594 ; WX 1062 ; N uniA662 ; G 4827
+U 42595 ; WX 912 ; N uniA663 ; G 4828
+U 42596 ; WX 1066 ; N uniA664 ; G 4829
+U 42597 ; WX 901 ; N uniA665 ; G 4830
+U 42598 ; WX 1178 ; N uniA666 ; G 4831
+U 42599 ; WX 1008 ; N uniA667 ; G 4832
+U 42600 ; WX 787 ; N uniA668 ; G 4833
+U 42601 ; WX 612 ; N uniA669 ; G 4834
+U 42602 ; WX 855 ; N uniA66A ; G 4835
+U 42603 ; WX 712 ; N uniA66B ; G 4836
+U 42604 ; WX 1358 ; N uniA66C ; G 4837
+U 42605 ; WX 1019 ; N uniA66D ; G 4838
+U 42606 ; WX 879 ; N uniA66E ; G 4839
+U 42634 ; WX 782 ; N uniA68A ; G 4840
+U 42635 ; WX 685 ; N uniA68B ; G 4841
+U 42636 ; WX 611 ; N uniA68C ; G 4842
+U 42637 ; WX 583 ; N uniA68D ; G 4843
+U 42644 ; WX 686 ; N uniA694 ; G 4844
+U 42645 ; WX 634 ; N uniA695 ; G 4845
+U 42648 ; WX 1358 ; N uniA698 ; G 4846
+U 42649 ; WX 1019 ; N uniA699 ; G 4847
+U 42760 ; WX 493 ; N uniA708 ; G 4848
+U 42761 ; WX 493 ; N uniA709 ; G 4849
+U 42762 ; WX 493 ; N uniA70A ; G 4850
+U 42763 ; WX 493 ; N uniA70B ; G 4851
+U 42764 ; WX 493 ; N uniA70C ; G 4852
+U 42765 ; WX 493 ; N uniA70D ; G 4853
+U 42766 ; WX 493 ; N uniA70E ; G 4854
+U 42767 ; WX 493 ; N uniA70F ; G 4855
+U 42768 ; WX 493 ; N uniA710 ; G 4856
+U 42769 ; WX 493 ; N uniA711 ; G 4857
+U 42770 ; WX 493 ; N uniA712 ; G 4858
+U 42771 ; WX 493 ; N uniA713 ; G 4859
+U 42772 ; WX 493 ; N uniA714 ; G 4860
+U 42773 ; WX 493 ; N uniA715 ; G 4861
+U 42774 ; WX 493 ; N uniA716 ; G 4862
+U 42779 ; WX 369 ; N uniA71B ; G 4863
+U 42780 ; WX 369 ; N uniA71C ; G 4864
+U 42781 ; WX 252 ; N uniA71D ; G 4865
+U 42782 ; WX 252 ; N uniA71E ; G 4866
+U 42783 ; WX 252 ; N uniA71F ; G 4867
+U 42786 ; WX 385 ; N uniA722 ; G 4868
+U 42787 ; WX 356 ; N uniA723 ; G 4869
+U 42788 ; WX 472 ; N uniA724 ; G 4870
+U 42789 ; WX 472 ; N uniA725 ; G 4871
+U 42790 ; WX 752 ; N uniA726 ; G 4872
+U 42791 ; WX 634 ; N uniA727 ; G 4873
+U 42792 ; WX 878 ; N uniA728 ; G 4874
+U 42793 ; WX 709 ; N uniA729 ; G 4875
+U 42794 ; WX 614 ; N uniA72A ; G 4876
+U 42795 ; WX 541 ; N uniA72B ; G 4877
+U 42800 ; WX 491 ; N uniA730 ; G 4878
+U 42801 ; WX 521 ; N uniA731 ; G 4879
+U 42802 ; WX 1250 ; N uniA732 ; G 4880
+U 42803 ; WX 985 ; N uniA733 ; G 4881
+U 42804 ; WX 1203 ; N uniA734 ; G 4882
+U 42805 ; WX 990 ; N uniA735 ; G 4883
+U 42806 ; WX 1142 ; N uniA736 ; G 4884
+U 42807 ; WX 981 ; N uniA737 ; G 4885
+U 42808 ; WX 971 ; N uniA738 ; G 4886
+U 42809 ; WX 818 ; N uniA739 ; G 4887
+U 42810 ; WX 971 ; N uniA73A ; G 4888
+U 42811 ; WX 818 ; N uniA73B ; G 4889
+U 42812 ; WX 959 ; N uniA73C ; G 4890
+U 42813 ; WX 818 ; N uniA73D ; G 4891
+U 42814 ; WX 703 ; N uniA73E ; G 4892
+U 42815 ; WX 549 ; N uniA73F ; G 4893
+U 42816 ; WX 656 ; N uniA740 ; G 4894
+U 42817 ; WX 583 ; N uniA741 ; G 4895
+U 42822 ; WX 680 ; N uniA746 ; G 4896
+U 42823 ; WX 392 ; N uniA747 ; G 4897
+U 42824 ; WX 582 ; N uniA748 ; G 4898
+U 42825 ; WX 427 ; N uniA749 ; G 4899
+U 42826 ; WX 807 ; N uniA74A ; G 4900
+U 42827 ; WX 704 ; N uniA74B ; G 4901
+U 42830 ; WX 1358 ; N uniA74E ; G 4902
+U 42831 ; WX 1019 ; N uniA74F ; G 4903
+U 42832 ; WX 603 ; N uniA750 ; G 4904
+U 42833 ; WX 635 ; N uniA751 ; G 4905
+U 42834 ; WX 734 ; N uniA752 ; G 4906
+U 42835 ; WX 774 ; N uniA753 ; G 4907
+U 42838 ; WX 787 ; N uniA756 ; G 4908
+U 42839 ; WX 635 ; N uniA757 ; G 4909
+U 42852 ; WX 605 ; N uniA764 ; G 4910
+U 42853 ; WX 635 ; N uniA765 ; G 4911
+U 42854 ; WX 605 ; N uniA766 ; G 4912
+U 42855 ; WX 635 ; N uniA767 ; G 4913
+U 42880 ; WX 557 ; N uniA780 ; G 4914
+U 42881 ; WX 278 ; N uniA781 ; G 4915
+U 42882 ; WX 735 ; N uniA782 ; G 4916
+U 42883 ; WX 634 ; N uniA783 ; G 4917
+U 42889 ; WX 337 ; N uniA789 ; G 4918
+U 42890 ; WX 376 ; N uniA78A ; G 4919
+U 42891 ; WX 401 ; N uniA78B ; G 4920
+U 42892 ; WX 275 ; N uniA78C ; G 4921
+U 42893 ; WX 686 ; N uniA78D ; G 4922
+U 42894 ; WX 487 ; N uniA78E ; G 4923
+U 42896 ; WX 772 ; N uniA790 ; G 4924
+U 42897 ; WX 667 ; N uniA791 ; G 4925
+U 42912 ; WX 775 ; N uniA7A0 ; G 4926
+U 42913 ; WX 635 ; N uniA7A1 ; G 4927
+U 42914 ; WX 656 ; N uniA7A2 ; G 4928
+U 42915 ; WX 579 ; N uniA7A3 ; G 4929
+U 42916 ; WX 748 ; N uniA7A4 ; G 4930
+U 42917 ; WX 634 ; N uniA7A5 ; G 4931
+U 42918 ; WX 695 ; N uniA7A6 ; G 4932
+U 42919 ; WX 411 ; N uniA7A7 ; G 4933
+U 42920 ; WX 635 ; N uniA7A8 ; G 4934
+U 42921 ; WX 521 ; N uniA7A9 ; G 4935
+U 42922 ; WX 801 ; N uniA7AA ; G 4936
+U 43000 ; WX 577 ; N uniA7F8 ; G 4937
+U 43001 ; WX 644 ; N uniA7F9 ; G 4938
+U 43002 ; WX 915 ; N uniA7FA ; G 4939
+U 43003 ; WX 575 ; N uniA7FB ; G 4940
+U 43004 ; WX 603 ; N uniA7FC ; G 4941
+U 43005 ; WX 863 ; N uniA7FD ; G 4942
+U 43006 ; WX 295 ; N uniA7FE ; G 4943
+U 43007 ; WX 1199 ; N uniA7FF ; G 4944
+U 61184 ; WX 213 ; N uni02E5.5 ; G 4945
+U 61185 ; WX 238 ; N uni02E6.5 ; G 4946
+U 61186 ; WX 257 ; N uni02E7.5 ; G 4947
+U 61187 ; WX 264 ; N uni02E8.5 ; G 4948
+U 61188 ; WX 267 ; N uni02E9.5 ; G 4949
+U 61189 ; WX 238 ; N uni02E5.4 ; G 4950
+U 61190 ; WX 213 ; N uni02E6.4 ; G 4951
+U 61191 ; WX 238 ; N uni02E7.4 ; G 4952
+U 61192 ; WX 257 ; N uni02E8.4 ; G 4953
+U 61193 ; WX 264 ; N uni02E9.4 ; G 4954
+U 61194 ; WX 257 ; N uni02E5.3 ; G 4955
+U 61195 ; WX 238 ; N uni02E6.3 ; G 4956
+U 61196 ; WX 213 ; N uni02E7.3 ; G 4957
+U 61197 ; WX 238 ; N uni02E8.3 ; G 4958
+U 61198 ; WX 257 ; N uni02E9.3 ; G 4959
+U 61199 ; WX 264 ; N uni02E5.2 ; G 4960
+U 61200 ; WX 257 ; N uni02E6.2 ; G 4961
+U 61201 ; WX 238 ; N uni02E7.2 ; G 4962
+U 61202 ; WX 213 ; N uni02E8.2 ; G 4963
+U 61203 ; WX 238 ; N uni02E9.2 ; G 4964
+U 61204 ; WX 267 ; N uni02E5.1 ; G 4965
+U 61205 ; WX 264 ; N uni02E6.1 ; G 4966
+U 61206 ; WX 257 ; N uni02E7.1 ; G 4967
+U 61207 ; WX 238 ; N uni02E8.1 ; G 4968
+U 61208 ; WX 213 ; N uni02E9.1 ; G 4969
+U 61209 ; WX 275 ; N stem ; G 4970
+U 61440 ; WX 977 ; N uniF000 ; G 4971
+U 61441 ; WX 977 ; N uniF001 ; G 4972
+U 61442 ; WX 977 ; N uniF002 ; G 4973
+U 61443 ; WX 977 ; N uniF003 ; G 4974
+U 62464 ; WX 580 ; N uniF400 ; G 4975
+U 62465 ; WX 580 ; N uniF401 ; G 4976
+U 62466 ; WX 624 ; N uniF402 ; G 4977
+U 62467 ; WX 889 ; N uniF403 ; G 4978
+U 62468 ; WX 585 ; N uniF404 ; G 4979
+U 62469 ; WX 580 ; N uniF405 ; G 4980
+U 62470 ; WX 653 ; N uniF406 ; G 4981
+U 62471 ; WX 882 ; N uniF407 ; G 4982
+U 62472 ; WX 555 ; N uniF408 ; G 4983
+U 62473 ; WX 580 ; N uniF409 ; G 4984
+U 62474 ; WX 1168 ; N uniF40A ; G 4985
+U 62475 ; WX 589 ; N uniF40B ; G 4986
+U 62476 ; WX 590 ; N uniF40C ; G 4987
+U 62477 ; WX 869 ; N uniF40D ; G 4988
+U 62478 ; WX 580 ; N uniF40E ; G 4989
+U 62479 ; WX 589 ; N uniF40F ; G 4990
+U 62480 ; WX 914 ; N uniF410 ; G 4991
+U 62481 ; WX 590 ; N uniF411 ; G 4992
+U 62482 ; WX 731 ; N uniF412 ; G 4993
+U 62483 ; WX 583 ; N uniF413 ; G 4994
+U 62484 ; WX 872 ; N uniF414 ; G 4995
+U 62485 ; WX 589 ; N uniF415 ; G 4996
+U 62486 ; WX 895 ; N uniF416 ; G 4997
+U 62487 ; WX 589 ; N uniF417 ; G 4998
+U 62488 ; WX 589 ; N uniF418 ; G 4999
+U 62489 ; WX 590 ; N uniF419 ; G 5000
+U 62490 ; WX 649 ; N uniF41A ; G 5001
+U 62491 ; WX 589 ; N uniF41B ; G 5002
+U 62492 ; WX 589 ; N uniF41C ; G 5003
+U 62493 ; WX 599 ; N uniF41D ; G 5004
+U 62494 ; WX 590 ; N uniF41E ; G 5005
+U 62495 ; WX 516 ; N uniF41F ; G 5006
+U 62496 ; WX 580 ; N uniF420 ; G 5007
+U 62497 ; WX 584 ; N uniF421 ; G 5008
+U 62498 ; WX 580 ; N uniF422 ; G 5009
+U 62499 ; WX 580 ; N uniF423 ; G 5010
+U 62500 ; WX 581 ; N uniF424 ; G 5011
+U 62501 ; WX 638 ; N uniF425 ; G 5012
+U 62502 ; WX 955 ; N uniF426 ; G 5013
+U 62504 ; WX 931 ; N uniF428 ; G 5014
+U 62505 ; WX 808 ; N uniF429 ; G 5015
+U 62506 ; WX 508 ; N uniF42A ; G 5016
+U 62507 ; WX 508 ; N uniF42B ; G 5017
+U 62508 ; WX 508 ; N uniF42C ; G 5018
+U 62509 ; WX 508 ; N uniF42D ; G 5019
+U 62510 ; WX 508 ; N uniF42E ; G 5020
+U 62511 ; WX 508 ; N uniF42F ; G 5021
+U 62512 ; WX 508 ; N uniF430 ; G 5022
+U 62513 ; WX 508 ; N uniF431 ; G 5023
+U 62514 ; WX 508 ; N uniF432 ; G 5024
+U 62515 ; WX 508 ; N uniF433 ; G 5025
+U 62516 ; WX 518 ; N uniF434 ; G 5026
+U 62517 ; WX 518 ; N uniF435 ; G 5027
+U 62518 ; WX 518 ; N uniF436 ; G 5028
+U 62519 ; WX 787 ; N uniF437 ; G 5029
+U 62520 ; WX 787 ; N uniF438 ; G 5030
+U 62521 ; WX 787 ; N uniF439 ; G 5031
+U 62522 ; WX 787 ; N uniF43A ; G 5032
+U 62523 ; WX 787 ; N uniF43B ; G 5033
+U 62524 ; WX 546 ; N uniF43C ; G 5034
+U 62525 ; WX 546 ; N uniF43D ; G 5035
+U 62526 ; WX 546 ; N uniF43E ; G 5036
+U 62527 ; WX 546 ; N uniF43F ; G 5037
+U 62528 ; WX 546 ; N uniF440 ; G 5038
+U 62529 ; WX 546 ; N uniF441 ; G 5039
+U 63173 ; WX 612 ; N uniF6C5 ; G 5040
+U 64256 ; WX 689 ; N uniFB00 ; G 5041
+U 64257 ; WX 630 ; N fi ; G 5042
+U 64258 ; WX 630 ; N fl ; G 5043
+U 64259 ; WX 967 ; N uniFB03 ; G 5044
+U 64260 ; WX 967 ; N uniFB04 ; G 5045
+U 64261 ; WX 686 ; N uniFB05 ; G 5046
+U 64262 ; WX 861 ; N uniFB06 ; G 5047
+U 64275 ; WX 1202 ; N uniFB13 ; G 5048
+U 64276 ; WX 1202 ; N uniFB14 ; G 5049
+U 64277 ; WX 1196 ; N uniFB15 ; G 5050
+U 64278 ; WX 1186 ; N uniFB16 ; G 5051
+U 64279 ; WX 1529 ; N uniFB17 ; G 5052
+U 64285 ; WX 224 ; N uniFB1D ; G 5053
+U 64286 ; WX 0 ; N uniFB1E ; G 5054
+U 64287 ; WX 331 ; N uniFB1F ; G 5055
+U 64288 ; WX 636 ; N uniFB20 ; G 5056
+U 64289 ; WX 856 ; N uniFB21 ; G 5057
+U 64290 ; WX 774 ; N uniFB22 ; G 5058
+U 64291 ; WX 906 ; N uniFB23 ; G 5059
+U 64292 ; WX 771 ; N uniFB24 ; G 5060
+U 64293 ; WX 843 ; N uniFB25 ; G 5061
+U 64294 ; WX 855 ; N uniFB26 ; G 5062
+U 64295 ; WX 807 ; N uniFB27 ; G 5063
+U 64296 ; WX 875 ; N uniFB28 ; G 5064
+U 64297 ; WX 838 ; N uniFB29 ; G 5065
+U 64298 ; WX 708 ; N uniFB2A ; G 5066
+U 64299 ; WX 708 ; N uniFB2B ; G 5067
+U 64300 ; WX 708 ; N uniFB2C ; G 5068
+U 64301 ; WX 708 ; N uniFB2D ; G 5069
+U 64302 ; WX 668 ; N uniFB2E ; G 5070
+U 64303 ; WX 668 ; N uniFB2F ; G 5071
+U 64304 ; WX 668 ; N uniFB30 ; G 5072
+U 64305 ; WX 578 ; N uniFB31 ; G 5073
+U 64306 ; WX 412 ; N uniFB32 ; G 5074
+U 64307 ; WX 546 ; N uniFB33 ; G 5075
+U 64308 ; WX 653 ; N uniFB34 ; G 5076
+U 64309 ; WX 355 ; N uniFB35 ; G 5077
+U 64310 ; WX 406 ; N uniFB36 ; G 5078
+U 64312 ; WX 648 ; N uniFB38 ; G 5079
+U 64313 ; WX 330 ; N uniFB39 ; G 5080
+U 64314 ; WX 537 ; N uniFB3A ; G 5081
+U 64315 ; WX 529 ; N uniFB3B ; G 5082
+U 64316 ; WX 568 ; N uniFB3C ; G 5083
+U 64318 ; WX 679 ; N uniFB3E ; G 5084
+U 64320 ; WX 399 ; N uniFB40 ; G 5085
+U 64321 ; WX 649 ; N uniFB41 ; G 5086
+U 64323 ; WX 640 ; N uniFB43 ; G 5087
+U 64324 ; WX 625 ; N uniFB44 ; G 5088
+U 64326 ; WX 593 ; N uniFB46 ; G 5089
+U 64327 ; WX 709 ; N uniFB47 ; G 5090
+U 64328 ; WX 564 ; N uniFB48 ; G 5091
+U 64329 ; WX 708 ; N uniFB49 ; G 5092
+U 64330 ; WX 657 ; N uniFB4A ; G 5093
+U 64331 ; WX 272 ; N uniFB4B ; G 5094
+U 64332 ; WX 578 ; N uniFB4C ; G 5095
+U 64333 ; WX 529 ; N uniFB4D ; G 5096
+U 64334 ; WX 625 ; N uniFB4E ; G 5097
+U 64335 ; WX 629 ; N uniFB4F ; G 5098
+U 64338 ; WX 941 ; N uniFB52 ; G 5099
+U 64339 ; WX 982 ; N uniFB53 ; G 5100
+U 64340 ; WX 278 ; N uniFB54 ; G 5101
+U 64341 ; WX 302 ; N uniFB55 ; G 5102
+U 64342 ; WX 941 ; N uniFB56 ; G 5103
+U 64343 ; WX 982 ; N uniFB57 ; G 5104
+U 64344 ; WX 278 ; N uniFB58 ; G 5105
+U 64345 ; WX 302 ; N uniFB59 ; G 5106
+U 64346 ; WX 941 ; N uniFB5A ; G 5107
+U 64347 ; WX 982 ; N uniFB5B ; G 5108
+U 64348 ; WX 278 ; N uniFB5C ; G 5109
+U 64349 ; WX 302 ; N uniFB5D ; G 5110
+U 64350 ; WX 941 ; N uniFB5E ; G 5111
+U 64351 ; WX 982 ; N uniFB5F ; G 5112
+U 64352 ; WX 278 ; N uniFB60 ; G 5113
+U 64353 ; WX 302 ; N uniFB61 ; G 5114
+U 64354 ; WX 941 ; N uniFB62 ; G 5115
+U 64355 ; WX 982 ; N uniFB63 ; G 5116
+U 64356 ; WX 278 ; N uniFB64 ; G 5117
+U 64357 ; WX 302 ; N uniFB65 ; G 5118
+U 64358 ; WX 941 ; N uniFB66 ; G 5119
+U 64359 ; WX 982 ; N uniFB67 ; G 5120
+U 64360 ; WX 278 ; N uniFB68 ; G 5121
+U 64361 ; WX 302 ; N uniFB69 ; G 5122
+U 64362 ; WX 1037 ; N uniFB6A ; G 5123
+U 64363 ; WX 1035 ; N uniFB6B ; G 5124
+U 64364 ; WX 478 ; N uniFB6C ; G 5125
+U 64365 ; WX 506 ; N uniFB6D ; G 5126
+U 64366 ; WX 1037 ; N uniFB6E ; G 5127
+U 64367 ; WX 1035 ; N uniFB6F ; G 5128
+U 64368 ; WX 478 ; N uniFB70 ; G 5129
+U 64369 ; WX 506 ; N uniFB71 ; G 5130
+U 64370 ; WX 646 ; N uniFB72 ; G 5131
+U 64371 ; WX 646 ; N uniFB73 ; G 5132
+U 64372 ; WX 618 ; N uniFB74 ; G 5133
+U 64373 ; WX 646 ; N uniFB75 ; G 5134
+U 64374 ; WX 646 ; N uniFB76 ; G 5135
+U 64375 ; WX 646 ; N uniFB77 ; G 5136
+U 64376 ; WX 618 ; N uniFB78 ; G 5137
+U 64377 ; WX 646 ; N uniFB79 ; G 5138
+U 64378 ; WX 646 ; N uniFB7A ; G 5139
+U 64379 ; WX 646 ; N uniFB7B ; G 5140
+U 64380 ; WX 618 ; N uniFB7C ; G 5141
+U 64381 ; WX 646 ; N uniFB7D ; G 5142
+U 64382 ; WX 646 ; N uniFB7E ; G 5143
+U 64383 ; WX 646 ; N uniFB7F ; G 5144
+U 64384 ; WX 618 ; N uniFB80 ; G 5145
+U 64385 ; WX 646 ; N uniFB81 ; G 5146
+U 64386 ; WX 445 ; N uniFB82 ; G 5147
+U 64387 ; WX 525 ; N uniFB83 ; G 5148
+U 64388 ; WX 445 ; N uniFB84 ; G 5149
+U 64389 ; WX 525 ; N uniFB85 ; G 5150
+U 64390 ; WX 445 ; N uniFB86 ; G 5151
+U 64391 ; WX 525 ; N uniFB87 ; G 5152
+U 64392 ; WX 445 ; N uniFB88 ; G 5153
+U 64393 ; WX 525 ; N uniFB89 ; G 5154
+U 64394 ; WX 483 ; N uniFB8A ; G 5155
+U 64395 ; WX 552 ; N uniFB8B ; G 5156
+U 64396 ; WX 483 ; N uniFB8C ; G 5157
+U 64397 ; WX 552 ; N uniFB8D ; G 5158
+U 64398 ; WX 895 ; N uniFB8E ; G 5159
+U 64399 ; WX 895 ; N uniFB8F ; G 5160
+U 64400 ; WX 476 ; N uniFB90 ; G 5161
+U 64401 ; WX 552 ; N uniFB91 ; G 5162
+U 64402 ; WX 895 ; N uniFB92 ; G 5163
+U 64403 ; WX 895 ; N uniFB93 ; G 5164
+U 64404 ; WX 476 ; N uniFB94 ; G 5165
+U 64405 ; WX 552 ; N uniFB95 ; G 5166
+U 64406 ; WX 895 ; N uniFB96 ; G 5167
+U 64407 ; WX 895 ; N uniFB97 ; G 5168
+U 64408 ; WX 476 ; N uniFB98 ; G 5169
+U 64409 ; WX 552 ; N uniFB99 ; G 5170
+U 64410 ; WX 895 ; N uniFB9A ; G 5171
+U 64411 ; WX 895 ; N uniFB9B ; G 5172
+U 64412 ; WX 476 ; N uniFB9C ; G 5173
+U 64413 ; WX 552 ; N uniFB9D ; G 5174
+U 64414 ; WX 734 ; N uniFB9E ; G 5175
+U 64415 ; WX 761 ; N uniFB9F ; G 5176
+U 64416 ; WX 734 ; N uniFBA0 ; G 5177
+U 64417 ; WX 761 ; N uniFBA1 ; G 5178
+U 64418 ; WX 278 ; N uniFBA2 ; G 5179
+U 64419 ; WX 302 ; N uniFBA3 ; G 5180
+U 64426 ; WX 698 ; N uniFBAA ; G 5181
+U 64427 ; WX 632 ; N uniFBAB ; G 5182
+U 64428 ; WX 527 ; N uniFBAC ; G 5183
+U 64429 ; WX 461 ; N uniFBAD ; G 5184
+U 64467 ; WX 824 ; N uniFBD3 ; G 5185
+U 64468 ; WX 843 ; N uniFBD4 ; G 5186
+U 64469 ; WX 476 ; N uniFBD5 ; G 5187
+U 64470 ; WX 552 ; N uniFBD6 ; G 5188
+U 64471 ; WX 483 ; N uniFBD7 ; G 5189
+U 64472 ; WX 517 ; N uniFBD8 ; G 5190
+U 64473 ; WX 483 ; N uniFBD9 ; G 5191
+U 64474 ; WX 517 ; N uniFBDA ; G 5192
+U 64475 ; WX 483 ; N uniFBDB ; G 5193
+U 64476 ; WX 517 ; N uniFBDC ; G 5194
+U 64478 ; WX 483 ; N uniFBDE ; G 5195
+U 64479 ; WX 517 ; N uniFBDF ; G 5196
+U 64484 ; WX 783 ; N uniFBE4 ; G 5197
+U 64485 ; WX 833 ; N uniFBE5 ; G 5198
+U 64486 ; WX 278 ; N uniFBE6 ; G 5199
+U 64487 ; WX 302 ; N uniFBE7 ; G 5200
+U 64488 ; WX 278 ; N uniFBE8 ; G 5201
+U 64489 ; WX 302 ; N uniFBE9 ; G 5202
+U 64508 ; WX 783 ; N uniFBFC ; G 5203
+U 64509 ; WX 833 ; N uniFBFD ; G 5204
+U 64510 ; WX 278 ; N uniFBFE ; G 5205
+U 64511 ; WX 302 ; N uniFBFF ; G 5206
+U 65024 ; WX 0 ; N uniFE00 ; G 5207
+U 65025 ; WX 0 ; N uniFE01 ; G 5208
+U 65026 ; WX 0 ; N uniFE02 ; G 5209
+U 65027 ; WX 0 ; N uniFE03 ; G 5210
+U 65028 ; WX 0 ; N uniFE04 ; G 5211
+U 65029 ; WX 0 ; N uniFE05 ; G 5212
+U 65030 ; WX 0 ; N uniFE06 ; G 5213
+U 65031 ; WX 0 ; N uniFE07 ; G 5214
+U 65032 ; WX 0 ; N uniFE08 ; G 5215
+U 65033 ; WX 0 ; N uniFE09 ; G 5216
+U 65034 ; WX 0 ; N uniFE0A ; G 5217
+U 65035 ; WX 0 ; N uniFE0B ; G 5218
+U 65036 ; WX 0 ; N uniFE0C ; G 5219
+U 65037 ; WX 0 ; N uniFE0D ; G 5220
+U 65038 ; WX 0 ; N uniFE0E ; G 5221
+U 65039 ; WX 0 ; N uniFE0F ; G 5222
+U 65056 ; WX 0 ; N uniFE20 ; G 5223
+U 65057 ; WX 0 ; N uniFE21 ; G 5224
+U 65058 ; WX 0 ; N uniFE22 ; G 5225
+U 65059 ; WX 0 ; N uniFE23 ; G 5226
+U 65136 ; WX 293 ; N uniFE70 ; G 5227
+U 65137 ; WX 293 ; N uniFE71 ; G 5228
+U 65138 ; WX 293 ; N uniFE72 ; G 5229
+U 65139 ; WX 262 ; N uniFE73 ; G 5230
+U 65140 ; WX 293 ; N uniFE74 ; G 5231
+U 65142 ; WX 293 ; N uniFE76 ; G 5232
+U 65143 ; WX 293 ; N uniFE77 ; G 5233
+U 65144 ; WX 293 ; N uniFE78 ; G 5234
+U 65145 ; WX 293 ; N uniFE79 ; G 5235
+U 65146 ; WX 293 ; N uniFE7A ; G 5236
+U 65147 ; WX 293 ; N uniFE7B ; G 5237
+U 65148 ; WX 293 ; N uniFE7C ; G 5238
+U 65149 ; WX 293 ; N uniFE7D ; G 5239
+U 65150 ; WX 293 ; N uniFE7E ; G 5240
+U 65151 ; WX 293 ; N uniFE7F ; G 5241
+U 65152 ; WX 470 ; N uniFE80 ; G 5242
+U 65153 ; WX 278 ; N uniFE81 ; G 5243
+U 65154 ; WX 305 ; N uniFE82 ; G 5244
+U 65155 ; WX 278 ; N uniFE83 ; G 5245
+U 65156 ; WX 305 ; N uniFE84 ; G 5246
+U 65157 ; WX 483 ; N uniFE85 ; G 5247
+U 65158 ; WX 517 ; N uniFE86 ; G 5248
+U 65159 ; WX 278 ; N uniFE87 ; G 5249
+U 65160 ; WX 305 ; N uniFE88 ; G 5250
+U 65161 ; WX 783 ; N uniFE89 ; G 5251
+U 65162 ; WX 833 ; N uniFE8A ; G 5252
+U 65163 ; WX 278 ; N uniFE8B ; G 5253
+U 65164 ; WX 302 ; N uniFE8C ; G 5254
+U 65165 ; WX 278 ; N uniFE8D ; G 5255
+U 65166 ; WX 305 ; N uniFE8E ; G 5256
+U 65167 ; WX 941 ; N uniFE8F ; G 5257
+U 65168 ; WX 982 ; N uniFE90 ; G 5258
+U 65169 ; WX 278 ; N uniFE91 ; G 5259
+U 65170 ; WX 302 ; N uniFE92 ; G 5260
+U 65171 ; WX 524 ; N uniFE93 ; G 5261
+U 65172 ; WX 536 ; N uniFE94 ; G 5262
+U 65173 ; WX 941 ; N uniFE95 ; G 5263
+U 65174 ; WX 982 ; N uniFE96 ; G 5264
+U 65175 ; WX 278 ; N uniFE97 ; G 5265
+U 65176 ; WX 302 ; N uniFE98 ; G 5266
+U 65177 ; WX 941 ; N uniFE99 ; G 5267
+U 65178 ; WX 982 ; N uniFE9A ; G 5268
+U 65179 ; WX 278 ; N uniFE9B ; G 5269
+U 65180 ; WX 302 ; N uniFE9C ; G 5270
+U 65181 ; WX 646 ; N uniFE9D ; G 5271
+U 65182 ; WX 646 ; N uniFE9E ; G 5272
+U 65183 ; WX 618 ; N uniFE9F ; G 5273
+U 65184 ; WX 646 ; N uniFEA0 ; G 5274
+U 65185 ; WX 646 ; N uniFEA1 ; G 5275
+U 65186 ; WX 646 ; N uniFEA2 ; G 5276
+U 65187 ; WX 618 ; N uniFEA3 ; G 5277
+U 65188 ; WX 646 ; N uniFEA4 ; G 5278
+U 65189 ; WX 646 ; N uniFEA5 ; G 5279
+U 65190 ; WX 646 ; N uniFEA6 ; G 5280
+U 65191 ; WX 618 ; N uniFEA7 ; G 5281
+U 65192 ; WX 646 ; N uniFEA8 ; G 5282
+U 65193 ; WX 445 ; N uniFEA9 ; G 5283
+U 65194 ; WX 525 ; N uniFEAA ; G 5284
+U 65195 ; WX 445 ; N uniFEAB ; G 5285
+U 65196 ; WX 525 ; N uniFEAC ; G 5286
+U 65197 ; WX 483 ; N uniFEAD ; G 5287
+U 65198 ; WX 552 ; N uniFEAE ; G 5288
+U 65199 ; WX 483 ; N uniFEAF ; G 5289
+U 65200 ; WX 552 ; N uniFEB0 ; G 5290
+U 65201 ; WX 1221 ; N uniFEB1 ; G 5291
+U 65202 ; WX 1275 ; N uniFEB2 ; G 5292
+U 65203 ; WX 838 ; N uniFEB3 ; G 5293
+U 65204 ; WX 892 ; N uniFEB4 ; G 5294
+U 65205 ; WX 1221 ; N uniFEB5 ; G 5295
+U 65206 ; WX 1275 ; N uniFEB6 ; G 5296
+U 65207 ; WX 838 ; N uniFEB7 ; G 5297
+U 65208 ; WX 892 ; N uniFEB8 ; G 5298
+U 65209 ; WX 1209 ; N uniFEB9 ; G 5299
+U 65210 ; WX 1225 ; N uniFEBA ; G 5300
+U 65211 ; WX 849 ; N uniFEBB ; G 5301
+U 65212 ; WX 867 ; N uniFEBC ; G 5302
+U 65213 ; WX 1209 ; N uniFEBD ; G 5303
+U 65214 ; WX 1225 ; N uniFEBE ; G 5304
+U 65215 ; WX 849 ; N uniFEBF ; G 5305
+U 65216 ; WX 867 ; N uniFEC0 ; G 5306
+U 65217 ; WX 925 ; N uniFEC1 ; G 5307
+U 65218 ; WX 949 ; N uniFEC2 ; G 5308
+U 65219 ; WX 796 ; N uniFEC3 ; G 5309
+U 65220 ; WX 820 ; N uniFEC4 ; G 5310
+U 65221 ; WX 925 ; N uniFEC5 ; G 5311
+U 65222 ; WX 949 ; N uniFEC6 ; G 5312
+U 65223 ; WX 796 ; N uniFEC7 ; G 5313
+U 65224 ; WX 820 ; N uniFEC8 ; G 5314
+U 65225 ; WX 597 ; N uniFEC9 ; G 5315
+U 65226 ; WX 532 ; N uniFECA ; G 5316
+U 65227 ; WX 597 ; N uniFECB ; G 5317
+U 65228 ; WX 482 ; N uniFECC ; G 5318
+U 65229 ; WX 597 ; N uniFECD ; G 5319
+U 65230 ; WX 532 ; N uniFECE ; G 5320
+U 65231 ; WX 523 ; N uniFECF ; G 5321
+U 65232 ; WX 482 ; N uniFED0 ; G 5322
+U 65233 ; WX 1037 ; N uniFED1 ; G 5323
+U 65234 ; WX 1035 ; N uniFED2 ; G 5324
+U 65235 ; WX 478 ; N uniFED3 ; G 5325
+U 65236 ; WX 506 ; N uniFED4 ; G 5326
+U 65237 ; WX 776 ; N uniFED5 ; G 5327
+U 65238 ; WX 834 ; N uniFED6 ; G 5328
+U 65239 ; WX 478 ; N uniFED7 ; G 5329
+U 65240 ; WX 506 ; N uniFED8 ; G 5330
+U 65241 ; WX 824 ; N uniFED9 ; G 5331
+U 65242 ; WX 843 ; N uniFEDA ; G 5332
+U 65243 ; WX 476 ; N uniFEDB ; G 5333
+U 65244 ; WX 552 ; N uniFEDC ; G 5334
+U 65245 ; WX 727 ; N uniFEDD ; G 5335
+U 65246 ; WX 757 ; N uniFEDE ; G 5336
+U 65247 ; WX 305 ; N uniFEDF ; G 5337
+U 65248 ; WX 331 ; N uniFEE0 ; G 5338
+U 65249 ; WX 619 ; N uniFEE1 ; G 5339
+U 65250 ; WX 666 ; N uniFEE2 ; G 5340
+U 65251 ; WX 536 ; N uniFEE3 ; G 5341
+U 65252 ; WX 578 ; N uniFEE4 ; G 5342
+U 65253 ; WX 734 ; N uniFEE5 ; G 5343
+U 65254 ; WX 761 ; N uniFEE6 ; G 5344
+U 65255 ; WX 278 ; N uniFEE7 ; G 5345
+U 65256 ; WX 302 ; N uniFEE8 ; G 5346
+U 65257 ; WX 524 ; N uniFEE9 ; G 5347
+U 65258 ; WX 536 ; N uniFEEA ; G 5348
+U 65259 ; WX 527 ; N uniFEEB ; G 5349
+U 65260 ; WX 461 ; N uniFEEC ; G 5350
+U 65261 ; WX 483 ; N uniFEED ; G 5351
+U 65262 ; WX 517 ; N uniFEEE ; G 5352
+U 65263 ; WX 783 ; N uniFEEF ; G 5353
+U 65264 ; WX 833 ; N uniFEF0 ; G 5354
+U 65265 ; WX 783 ; N uniFEF1 ; G 5355
+U 65266 ; WX 833 ; N uniFEF2 ; G 5356
+U 65267 ; WX 278 ; N uniFEF3 ; G 5357
+U 65268 ; WX 302 ; N uniFEF4 ; G 5358
+U 65269 ; WX 570 ; N uniFEF5 ; G 5359
+U 65270 ; WX 597 ; N uniFEF6 ; G 5360
+U 65271 ; WX 570 ; N uniFEF7 ; G 5361
+U 65272 ; WX 597 ; N uniFEF8 ; G 5362
+U 65273 ; WX 570 ; N uniFEF9 ; G 5363
+U 65274 ; WX 597 ; N uniFEFA ; G 5364
+U 65275 ; WX 570 ; N uniFEFB ; G 5365
+U 65276 ; WX 597 ; N uniFEFC ; G 5366
+U 65279 ; WX 0 ; N uniFEFF ; G 5367
+U 65529 ; WX 0 ; N uniFFF9 ; G 5368
+U 65530 ; WX 0 ; N uniFFFA ; G 5369
+U 65531 ; WX 0 ; N uniFFFB ; G 5370
+U 65532 ; WX 0 ; N uniFFFC ; G 5371
+U 65533 ; WX 1025 ; N uniFFFD ; G 5372
+EndCharMetrics
+StartKernData
+StartKernPairs 2727
+
+KPX dollar dollar 57
+KPX dollar ampersand -36
+KPX dollar asterisk -36
+KPX dollar two -36
+KPX dollar four -36
+KPX dollar seven -159
+KPX dollar nine -131
+KPX dollar colon -112
+KPX dollar less -159
+KPX dollar F -36
+KPX dollar G -36
+KPX dollar H -36
+KPX dollar I -73
+KPX dollar R -36
+KPX dollar T -36
+KPX dollar W -36
+KPX dollar Y -120
+KPX dollar Z -83
+KPX dollar backslash -139
+KPX dollar m -73
+KPX dollar copyright -36
+KPX dollar ordfeminine -36
+KPX dollar guillemotleft -36
+KPX dollar logicalnot -36
+KPX dollar sfthyphen -36
+KPX dollar acute -36
+KPX dollar mu -36
+KPX dollar paragraph -36
+KPX dollar periodcentered -36
+KPX dollar cedilla -36
+KPX dollar questiondown -139
+KPX dollar Aacute -139
+KPX dollar Acircumflex 57
+KPX dollar Adieresis 57
+KPX dollar AE 57
+KPX dollar Egrave -36
+KPX dollar Eacute -36
+KPX dollar Ecircumflex -36
+KPX dollar Edieresis -36
+KPX dollar Igrave -36
+KPX dollar Iacute -36
+KPX dollar Icircumflex -36
+KPX dollar Idieresis -36
+KPX dollar Ntilde -36
+KPX dollar Oacute -36
+KPX dollar Otilde -36
+KPX dollar multiply -36
+KPX dollar Ugrave -36
+KPX dollar Ucircumflex -36
+KPX dollar Yacute -36
+KPX dollar Thorn -36
+KPX dollar agrave -36
+KPX dollar acircumflex -36
+KPX dollar Dcaron -36
+KPX dollar dcaron -36
+KPX dollar Dcroat -36
+KPX dollar dmacron -36
+KPX dollar Emacron -36
+KPX dollar emacron -36
+KPX dollar Hcircumflex -159
+KPX dollar hcircumflex -36
+KPX dollar Hbar -159
+KPX dollar hbar -36
+KPX dollar Kcommaaccent -112
+KPX dollar kcommaaccent -83
+KPX dollar kgreenlandic -159
+KPX dollar Lacute -139
+KPX dollar lacute -159
+KPX dollar uni0188 -36
+KPX dollar uni01AC -36
+KPX dollar uni01AD -36
+KPX dollar uni01AE -36
+KPX dollar Uhorn -36
+KPX dollar uni01DC -159
+KPX dollar uni01DD -36
+KPX dollar uni01F0 -36
+KPX dollar uni01F3 -36
+KPX dollar uni01F4 -159
+KPX dollar uni01F5 -139
+
+KPX percent ampersand -36
+KPX percent asterisk -36
+KPX percent two -36
+KPX percent six -36
+KPX percent nine -63
+KPX percent colon -73
+KPX percent less -112
+KPX percent m -63
+KPX percent braceright -36
+KPX percent Egrave -36
+KPX percent Ecircumflex -36
+KPX percent Igrave -36
+KPX percent Icircumflex -36
+KPX percent Thorn -36
+KPX percent agrave -36
+KPX percent acircumflex -36
+KPX percent adieresis -36
+KPX percent Dcaron -36
+KPX percent Dcroat -36
+KPX percent Emacron -36
+KPX percent Gcircumflex -36
+KPX percent Gbreve -36
+KPX percent Gdotaccent -36
+KPX percent Gcommaaccent -36
+KPX percent Kcommaaccent -73
+KPX percent kgreenlandic -112
+KPX percent lacute -112
+KPX percent uni01AC -36
+KPX percent uni01AE -36
+KPX percent uni01DA -36
+KPX percent uni01F0 -36
+
+KPX ampersand less -36
+KPX ampersand m -36
+KPX ampersand braceright -36
+KPX ampersand kgreenlandic -36
+KPX ampersand lacute -36
+KPX ampersand uni01F4 -36
+
+KPX quotesingle dollar -36
+KPX quotesingle nine -36
+KPX quotesingle less -112
+KPX quotesingle m -36
+KPX quotesingle braceright -36
+KPX quotesingle Acircumflex -36
+KPX quotesingle Adieresis -36
+KPX quotesingle AE -36
+KPX quotesingle kgreenlandic -112
+KPX quotesingle lacute -112
+KPX quotesingle uni01F4 -112
+
+KPX parenright dollar -188
+KPX parenright six -36
+KPX parenright seven -36
+KPX parenright D -188
+KPX parenright H -112
+KPX parenright L -149
+KPX parenright R -73
+KPX parenright U -149
+KPX parenright X -112
+KPX parenright backslash -188
+KPX parenright cent -188
+KPX parenright sterling -188
+KPX parenright currency -188
+KPX parenright yen -188
+KPX parenright brokenbar -188
+KPX parenright section -188
+KPX parenright ordfeminine -112
+KPX parenright guillemotleft -112
+KPX parenright logicalnot -112
+KPX parenright sfthyphen -112
+KPX parenright acute -73
+KPX parenright mu -73
+KPX parenright paragraph -73
+KPX parenright periodcentered -73
+KPX parenright cedilla -73
+KPX parenright guillemotright -112
+KPX parenright onequarter -112
+KPX parenright onehalf -112
+KPX parenright threequarters -112
+KPX parenright questiondown -188
+KPX parenright Aacute -188
+KPX parenright Acircumflex -188
+KPX parenright Atilde -188
+KPX parenright Adieresis -188
+KPX parenright Aring -188
+KPX parenright AE -188
+KPX parenright Ccedilla -188
+KPX parenright Otilde -112
+KPX parenright multiply -112
+KPX parenright Ugrave -112
+KPX parenright Ucircumflex -112
+KPX parenright Yacute -112
+KPX parenright ntilde -149
+KPX parenright otilde -149
+KPX parenright dcaron -73
+KPX parenright dmacron -73
+KPX parenright emacron -73
+KPX parenright edotaccent -149
+KPX parenright eogonek -149
+KPX parenright ecaron -149
+KPX parenright Gcircumflex -36
+KPX parenright Gbreve -36
+KPX parenright Gdotaccent -36
+KPX parenright Gcommaaccent -36
+KPX parenright Hcircumflex -36
+KPX parenright Hbar -36
+KPX parenright Itilde -36
+KPX parenright imacron -112
+KPX parenright ibreve -112
+KPX parenright iogonek -112
+KPX parenright dotlessi -112
+KPX parenright ij -112
+KPX parenright jcircumflex -112
+KPX parenright Lacute -188
+KPX parenright uni01AD -73
+KPX parenright Uhorn -73
+KPX parenright uni01DA -36
+KPX parenright uni01DC -36
+KPX parenright uni01F1 -73
+KPX parenright uni01F5 -188
+
+KPX asterisk seven -73
+KPX asterisk less -102
+KPX asterisk m -36
+KPX asterisk braceright -36
+KPX asterisk Hbar -73
+KPX asterisk lacute -102
+
+
+KPX hyphen dollar -36
+KPX hyphen m -36
+KPX hyphen braceright -36
+
+KPX period dollar -36
+KPX period ampersand -112
+KPX period two -112
+KPX period seven -159
+KPX period eight -55
+KPX period colon -73
+KPX period less -73
+KPX period D -36
+KPX period H -102
+KPX period R -102
+KPX period X -102
+KPX period backslash -149
+KPX period m -131
+KPX period cent -36
+KPX period sterling -36
+KPX period currency -36
+KPX period yen -36
+KPX period brokenbar -36
+KPX period section -36
+KPX period ordfeminine -102
+KPX period guillemotleft -102
+KPX period logicalnot -102
+KPX period sfthyphen -102
+KPX period acute -102
+KPX period mu -102
+KPX period paragraph -102
+KPX period periodcentered -102
+KPX period cedilla -102
+KPX period guillemotright -102
+KPX period onequarter -102
+KPX period onehalf -102
+KPX period threequarters -102
+KPX period questiondown -149
+KPX period Aacute -149
+KPX period Egrave -112
+KPX period Icircumflex -112
+KPX period Yacute -102
+KPX period Hbar -159
+KPX period Idot -55
+KPX period dotlessi -102
+KPX period lacute -73
+
+KPX slash dollar 47
+KPX slash two -73
+KPX slash seven -282
+KPX slash eight -102
+KPX slash nine -225
+KPX slash colon -188
+KPX slash less -272
+KPX slash H -36
+KPX slash R -36
+KPX slash X -36
+KPX slash backslash -188
+KPX slash ordfeminine -36
+KPX slash guillemotleft -36
+KPX slash logicalnot -36
+KPX slash sfthyphen -36
+KPX slash acute -36
+KPX slash mu -36
+KPX slash paragraph -36
+KPX slash periodcentered -36
+KPX slash cedilla -36
+KPX slash guillemotright -36
+KPX slash onequarter -36
+KPX slash onehalf -36
+KPX slash threequarters -36
+KPX slash questiondown -188
+KPX slash Aacute -188
+KPX slash Yacute -36
+KPX slash Hbar -282
+KPX slash Idot -102
+KPX slash dotlessi -36
+KPX slash lacute -272
+
+KPX two dollar -36
+KPX two nine -36
+KPX two semicolon -131
+KPX two less -112
+KPX two m -36
+KPX two lacute -112
+
+KPX three dollar -131
+KPX three less -45
+KPX three D -92
+KPX three H -73
+KPX three L -45
+KPX three Q -36
+KPX three R -73
+KPX three U -36
+KPX three V -36
+KPX three X -36
+KPX three m -36
+KPX three cent -92
+KPX three sterling -92
+KPX three currency -92
+KPX three yen -92
+KPX three brokenbar -92
+KPX three section -92
+KPX three ordfeminine -73
+KPX three guillemotleft -73
+KPX three logicalnot -73
+KPX three sfthyphen -73
+KPX three threesuperior -36
+KPX three acute -73
+KPX three mu -73
+KPX three paragraph -73
+KPX three periodcentered -73
+KPX three cedilla -73
+KPX three guillemotright -36
+KPX three onequarter -36
+KPX three onehalf -36
+KPX three threequarters -36
+KPX three Yacute -73
+KPX three Cdotaccent -36
+KPX three edotaccent -36
+KPX three ecaron -36
+KPX three gdotaccent -36
+KPX three gcommaaccent -36
+KPX three dotlessi -36
+KPX three lacute -45
+
+
+KPX five dollar -83
+KPX five ampersand -102
+KPX five seven -149
+KPX five nine -112
+KPX five colon -83
+KPX five less -131
+KPX five D -45
+KPX five H -92
+KPX five R -92
+KPX five X -92
+KPX five backslash -112
+KPX five m -112
+KPX five braceright -36
+KPX five cent -45
+KPX five sterling -45
+KPX five currency -45
+KPX five yen -45
+KPX five brokenbar -45
+KPX five section -45
+KPX five ordfeminine -92
+KPX five guillemotleft -92
+KPX five logicalnot -92
+KPX five sfthyphen -92
+KPX five acute -92
+KPX five mu -92
+KPX five paragraph -92
+KPX five periodcentered -92
+KPX five cedilla -92
+KPX five guillemotright -92
+KPX five onequarter -92
+KPX five onehalf -92
+KPX five threequarters -92
+KPX five questiondown -112
+KPX five Aacute -112
+KPX five Egrave -102
+KPX five Icircumflex -102
+KPX five Yacute -92
+KPX five Hbar -149
+KPX five dotlessi -92
+KPX five lacute -131
+
+KPX six dollar 38
+
+KPX seven dollar -159
+KPX seven ampersand -120
+KPX seven seven -36
+KPX seven D -339
+KPX seven F -348
+KPX seven H -348
+KPX seven L -63
+KPX seven R -348
+KPX seven U -301
+KPX seven V -339
+KPX seven X -311
+KPX seven Z -339
+KPX seven backslash -319
+KPX seven m -188
+KPX seven braceright -112
+KPX seven cent -239
+KPX seven sterling -339
+KPX seven currency -239
+KPX seven yen -239
+KPX seven brokenbar -239
+KPX seven section -239
+KPX seven copyright -348
+KPX seven ordfeminine -288
+KPX seven guillemotleft -348
+KPX seven logicalnot -288
+KPX seven sfthyphen -288
+KPX seven acute -268
+KPX seven mu -348
+KPX seven paragraph -268
+KPX seven periodcentered -268
+KPX seven cedilla -268
+KPX seven guillemotright -281
+KPX seven onequarter -311
+KPX seven onehalf -281
+KPX seven threequarters -281
+KPX seven questiondown -319
+KPX seven Aacute -319
+KPX seven Egrave -120
+KPX seven Eacute -348
+KPX seven Icircumflex -120
+KPX seven Idieresis -348
+KPX seven Yacute -348
+KPX seven edotaccent -301
+KPX seven ecaron -301
+KPX seven gdotaccent -339
+KPX seven gcommaaccent -339
+KPX seven Hbar -36
+KPX seven dotlessi -311
+
+KPX eight equal -36
+KPX eight Ldot -36
+
+KPX nine dollar -131
+KPX nine two -36
+KPX nine D -159
+KPX nine H -159
+KPX nine L -45
+KPX nine R -159
+KPX nine X -139
+KPX nine backslash -55
+KPX nine m -178
+KPX nine braceright -112
+KPX nine cent -159
+KPX nine sterling -159
+KPX nine currency -159
+KPX nine yen -159
+KPX nine brokenbar -159
+KPX nine section -159
+KPX nine ordfeminine -159
+KPX nine guillemotleft -159
+KPX nine logicalnot -159
+KPX nine sfthyphen -159
+KPX nine acute -159
+KPX nine mu -159
+KPX nine paragraph -159
+KPX nine periodcentered -159
+KPX nine cedilla -159
+KPX nine guillemotright -139
+KPX nine onequarter -139
+KPX nine onehalf -139
+KPX nine threequarters -139
+KPX nine questiondown -55
+KPX nine Aacute -55
+KPX nine Yacute -159
+KPX nine dotlessi -139
+
+KPX colon dollar -112
+KPX colon D -131
+KPX colon H -120
+KPX colon L -45
+KPX colon R -120
+KPX colon U -92
+KPX colon X -73
+KPX colon backslash -36
+KPX colon m -112
+KPX colon braceright -36
+KPX colon cent -131
+KPX colon sterling -131
+KPX colon currency -131
+KPX colon yen -131
+KPX colon brokenbar -131
+KPX colon section -131
+KPX colon ordfeminine -120
+KPX colon guillemotleft -120
+KPX colon logicalnot -120
+KPX colon sfthyphen -120
+KPX colon acute -120
+KPX colon mu -120
+KPX colon paragraph -120
+KPX colon periodcentered -120
+KPX colon cedilla -120
+KPX colon guillemotright -73
+KPX colon onequarter -73
+KPX colon onehalf -73
+KPX colon threequarters -73
+KPX colon questiondown -36
+KPX colon Aacute -36
+KPX colon Yacute -120
+KPX colon edotaccent -92
+KPX colon ecaron -92
+KPX colon dotlessi -73
+
+KPX semicolon ampersand -149
+KPX semicolon two -131
+KPX semicolon seven -36
+KPX semicolon H -92
+KPX semicolon m -112
+KPX semicolon ordfeminine -92
+KPX semicolon guillemotleft -92
+KPX semicolon logicalnot -92
+KPX semicolon sfthyphen -92
+KPX semicolon Egrave -149
+KPX semicolon Icircumflex -149
+KPX semicolon Yacute -92
+KPX semicolon Hbar -36
+
+KPX less dollar -159
+KPX less ampersand -112
+KPX less two -112
+KPX less D -282
+KPX less H -272
+KPX less L -73
+KPX less R -272
+KPX less X -235
+KPX less m -225
+KPX less braceright -149
+KPX less cent -282
+KPX less sterling -282
+KPX less currency -282
+KPX less yen -282
+KPX less brokenbar -282
+KPX less section -282
+KPX less ordfeminine -272
+KPX less guillemotleft -272
+KPX less logicalnot -272
+KPX less sfthyphen -272
+KPX less acute -272
+KPX less mu -272
+KPX less paragraph -272
+KPX less periodcentered -272
+KPX less cedilla -272
+KPX less guillemotright -235
+KPX less onequarter -235
+KPX less onehalf -235
+KPX less threequarters -235
+KPX less Egrave -112
+KPX less Icircumflex -112
+KPX less Yacute -272
+KPX less dotlessi -235
+
+
+KPX H bracketleft -36
+
+KPX I W -36
+KPX I Z -36
+KPX I backslash -36
+KPX I m -73
+KPX I braceright -36
+KPX I questiondown -36
+KPX I Aacute -36
+KPX I hbar -36
+
+KPX N D -36
+KPX N H -73
+KPX N R -73
+KPX N X -63
+KPX N backslash -73
+KPX N cent -36
+KPX N sterling -36
+KPX N currency -36
+KPX N yen -36
+KPX N brokenbar -36
+KPX N section -36
+KPX N ordfeminine -73
+KPX N guillemotleft -73
+KPX N logicalnot -73
+KPX N sfthyphen -73
+KPX N acute -73
+KPX N mu -73
+KPX N paragraph -73
+KPX N periodcentered -73
+KPX N cedilla -73
+KPX N guillemotright -63
+KPX N onequarter -63
+KPX N onehalf -63
+KPX N threequarters -63
+KPX N questiondown -73
+KPX N Aacute -73
+KPX N Yacute -73
+KPX N dotlessi -63
+
+
+KPX R bracketleft -63
+
+KPX U F -45
+KPX U G -36
+KPX U H -45
+KPX U J -36
+KPX U K -36
+KPX U P -36
+KPX U Q -36
+KPX U R -45
+KPX U T -36
+KPX U U -36
+KPX U bracketleft -55
+KPX U m -73
+KPX U copyright -45
+KPX U ordfeminine -45
+KPX U guillemotleft -45
+KPX U logicalnot -45
+KPX U sfthyphen -45
+KPX U threesuperior -36
+KPX U acute -45
+KPX U mu -45
+KPX U paragraph -45
+KPX U periodcentered -45
+KPX U cedilla -45
+KPX U Eacute -45
+KPX U Idieresis -45
+KPX U Ntilde 72
+KPX U Yacute -45
+KPX U aacute -36
+KPX U Cdotaccent -36
+KPX U edotaccent -36
+KPX U ecaron -36
+
+KPX Y m -36
+KPX Y braceright -36
+
+KPX Z m -36
+KPX Z braceright -36
+
+KPX bracketleft F -36
+KPX bracketleft H -63
+KPX bracketleft R -63
+KPX bracketleft copyright -36
+KPX bracketleft ordfeminine -63
+KPX bracketleft guillemotleft -63
+KPX bracketleft logicalnot -63
+KPX bracketleft sfthyphen -63
+KPX bracketleft acute -63
+KPX bracketleft mu -63
+KPX bracketleft paragraph -63
+KPX bracketleft periodcentered -63
+KPX bracketleft cedilla -63
+KPX bracketleft Eacute -36
+KPX bracketleft Idieresis -36
+KPX bracketleft Yacute -63
+
+KPX backslash m -36
+KPX backslash braceright -36
+
+KPX m percent -36
+KPX m ampersand -36
+KPX m quotesingle -36
+KPX m asterisk -36
+KPX m hyphen -36
+KPX m seven -112
+KPX m nine -112
+KPX m colon -36
+KPX m less -149
+KPX m Y -36
+KPX m Z -36
+KPX m backslash -36
+KPX m questiondown -36
+KPX m Aacute -36
+KPX m Egrave -36
+KPX m Icircumflex -36
+KPX m Eth -36
+KPX m agrave -36
+KPX m Hbar -112
+KPX m lacute -149
+
+KPX braceright dollar -73
+KPX braceright percent -73
+KPX braceright ampersand -36
+KPX braceright quotesingle -36
+KPX braceright hyphen -36
+KPX braceright two -36
+KPX braceright seven -188
+KPX braceright nine -178
+KPX braceright colon -112
+KPX braceright semicolon -112
+KPX braceright less -225
+KPX braceright Y -36
+KPX braceright Z -36
+KPX braceright backslash -36
+KPX braceright questiondown -36
+KPX braceright Aacute -36
+KPX braceright Egrave -36
+KPX braceright Icircumflex -36
+KPX braceright Eth -36
+KPX braceright Hbar -188
+KPX braceright lacute -225
+
+
+
+KPX ordfeminine bracketleft -36
+
+KPX guillemotleft bracketleft -36
+
+KPX logicalnot bracketleft -36
+
+KPX sfthyphen bracketleft -36
+
+
+
+KPX acute bracketleft -63
+
+KPX mu bracketleft -63
+
+KPX paragraph bracketleft -63
+
+KPX periodcentered bracketleft -63
+
+KPX cedilla bracketleft -63
+
+KPX questiondown m -36
+KPX questiondown braceright -36
+
+KPX Aacute m -36
+KPX Aacute braceright -36
+
+KPX Acircumflex dollar 57
+KPX Acircumflex ampersand -36
+KPX Acircumflex asterisk -36
+KPX Acircumflex two -36
+KPX Acircumflex four -36
+KPX Acircumflex seven -159
+KPX Acircumflex nine -131
+KPX Acircumflex colon -112
+KPX Acircumflex less -159
+KPX Acircumflex F -36
+KPX Acircumflex G -36
+KPX Acircumflex H -36
+KPX Acircumflex I -73
+KPX Acircumflex R -36
+KPX Acircumflex T -36
+KPX Acircumflex W -36
+KPX Acircumflex Y -120
+KPX Acircumflex Z -83
+KPX Acircumflex backslash -139
+KPX Acircumflex m -73
+KPX Acircumflex copyright -36
+KPX Acircumflex ordfeminine -36
+KPX Acircumflex guillemotleft -36
+KPX Acircumflex logicalnot -36
+KPX Acircumflex sfthyphen -36
+KPX Acircumflex acute -36
+KPX Acircumflex mu -36
+KPX Acircumflex paragraph -36
+KPX Acircumflex periodcentered -36
+KPX Acircumflex cedilla -36
+KPX Acircumflex questiondown -139
+KPX Acircumflex Aacute -139
+KPX Acircumflex Acircumflex 57
+KPX Acircumflex Adieresis 57
+KPX Acircumflex AE 57
+KPX Acircumflex Egrave -36
+KPX Acircumflex Ecircumflex -36
+KPX Acircumflex Igrave -36
+KPX Acircumflex Iacute -36
+KPX Acircumflex Icircumflex -36
+KPX Acircumflex Ntilde -36
+KPX Acircumflex Oacute -36
+KPX Acircumflex Otilde -36
+KPX Acircumflex multiply -36
+KPX Acircumflex Ugrave -36
+KPX Acircumflex Ucircumflex -36
+KPX Acircumflex Yacute -36
+KPX Acircumflex Thorn -36
+KPX Acircumflex acircumflex -36
+KPX Acircumflex Dcaron -36
+KPX Acircumflex dcaron -36
+KPX Acircumflex Dcroat -36
+KPX Acircumflex dmacron -36
+KPX Acircumflex Emacron -36
+KPX Acircumflex emacron -36
+KPX Acircumflex Hcircumflex -159
+KPX Acircumflex hcircumflex -36
+KPX Acircumflex Hbar -159
+KPX Acircumflex hbar -36
+KPX Acircumflex Kcommaaccent -112
+KPX Acircumflex kcommaaccent -83
+KPX Acircumflex kgreenlandic -159
+KPX Acircumflex Lacute -139
+KPX Acircumflex lacute -159
+KPX Acircumflex uni01F0 -36
+KPX Acircumflex uni01F1 -36
+
+KPX Adieresis dollar 57
+KPX Adieresis ampersand -36
+KPX Adieresis asterisk -36
+KPX Adieresis two -36
+KPX Adieresis four -36
+KPX Adieresis seven -159
+KPX Adieresis nine -131
+KPX Adieresis colon -112
+KPX Adieresis less -159
+KPX Adieresis F -36
+KPX Adieresis G -36
+KPX Adieresis H -36
+KPX Adieresis I -73
+KPX Adieresis R -36
+KPX Adieresis T -36
+KPX Adieresis W -36
+KPX Adieresis Y -120
+KPX Adieresis Z -83
+KPX Adieresis backslash -139
+KPX Adieresis m -73
+KPX Adieresis copyright -36
+KPX Adieresis ordfeminine -36
+KPX Adieresis guillemotleft -36
+KPX Adieresis logicalnot -36
+KPX Adieresis sfthyphen -36
+KPX Adieresis acute -36
+KPX Adieresis mu -36
+KPX Adieresis paragraph -36
+KPX Adieresis periodcentered -36
+KPX Adieresis cedilla -36
+KPX Adieresis questiondown -139
+KPX Adieresis Aacute -139
+KPX Adieresis Acircumflex 57
+KPX Adieresis Adieresis 57
+KPX Adieresis AE 57
+KPX Adieresis Egrave -36
+KPX Adieresis Ecircumflex -36
+KPX Adieresis Igrave -36
+KPX Adieresis Iacute -36
+KPX Adieresis Icircumflex -36
+KPX Adieresis Ntilde -36
+KPX Adieresis Oacute -36
+KPX Adieresis Otilde -36
+KPX Adieresis multiply -36
+KPX Adieresis Ugrave -36
+KPX Adieresis Ucircumflex -36
+KPX Adieresis Yacute -36
+KPX Adieresis Thorn -36
+KPX Adieresis acircumflex -36
+KPX Adieresis Dcaron -36
+KPX Adieresis dcaron -36
+KPX Adieresis Dcroat -36
+KPX Adieresis dmacron -36
+KPX Adieresis Emacron -36
+KPX Adieresis emacron -36
+KPX Adieresis Hcircumflex -159
+KPX Adieresis hcircumflex -36
+KPX Adieresis Hbar -159
+KPX Adieresis hbar -36
+KPX Adieresis Kcommaaccent -112
+KPX Adieresis kcommaaccent -83
+KPX Adieresis kgreenlandic -159
+KPX Adieresis Lacute -139
+KPX Adieresis lacute -159
+KPX Adieresis uni01F0 -36
+KPX Adieresis uni01F1 -36
+
+KPX AE dollar 57
+KPX AE ampersand -36
+KPX AE asterisk -36
+KPX AE two -36
+KPX AE four -36
+KPX AE seven -159
+KPX AE nine -131
+KPX AE colon -112
+KPX AE less -159
+KPX AE F -36
+KPX AE G -36
+KPX AE H -36
+KPX AE I -73
+KPX AE R -36
+KPX AE T -36
+KPX AE W -36
+KPX AE Y -120
+KPX AE Z -83
+KPX AE m -73
+KPX AE copyright -36
+KPX AE ordfeminine -36
+KPX AE guillemotleft -36
+KPX AE logicalnot -36
+KPX AE sfthyphen -36
+KPX AE acute -36
+KPX AE mu -36
+KPX AE paragraph -36
+KPX AE periodcentered -36
+KPX AE cedilla -36
+KPX AE Acircumflex 57
+KPX AE Adieresis 57
+KPX AE AE 57
+KPX AE Egrave -36
+KPX AE Ecircumflex -36
+KPX AE Igrave -36
+KPX AE Iacute -36
+KPX AE Icircumflex -36
+KPX AE Ntilde -36
+KPX AE Oacute -36
+KPX AE Otilde -36
+KPX AE multiply -36
+KPX AE Ugrave -36
+KPX AE Ucircumflex -36
+KPX AE Yacute -36
+KPX AE Thorn -36
+KPX AE acircumflex -36
+KPX AE Dcaron -36
+KPX AE dcaron -36
+KPX AE Dcroat -36
+KPX AE dmacron -36
+KPX AE emacron -36
+KPX AE Hcircumflex -159
+KPX AE hcircumflex -36
+KPX AE Hbar -159
+KPX AE hbar -36
+KPX AE Kcommaaccent -112
+KPX AE kcommaaccent -83
+KPX AE kgreenlandic -159
+KPX AE lacute -159
+KPX AE uni01F0 -36
+KPX AE uni01F1 -36
+
+KPX Egrave less -36
+KPX Egrave m -36
+KPX Egrave braceright -36
+KPX Egrave lacute -36
+
+KPX Icircumflex less -36
+KPX Icircumflex m -36
+KPX Icircumflex braceright -36
+KPX Icircumflex lacute -36
+
+KPX Eth dollar -36
+KPX Eth nine -36
+KPX Eth less -112
+KPX Eth m -36
+KPX Eth braceright -36
+KPX Eth Acircumflex -36
+KPX Eth Adieresis -36
+KPX Eth AE -36
+KPX Eth kgreenlandic -112
+KPX Eth lacute -112
+KPX Eth uni01F4 -112
+
+KPX Ograve dollar -36
+KPX Ograve nine -36
+KPX Ograve less -112
+KPX Ograve m -36
+KPX Ograve braceright -36
+KPX Ograve lacute -112
+
+KPX Yacute bracketleft -36
+
+KPX agrave seven -73
+KPX agrave less -102
+KPX agrave m -36
+KPX agrave braceright -36
+KPX agrave Hbar -73
+KPX agrave lacute -102
+
+KPX ucircumflex dollar 47
+KPX ucircumflex two -73
+KPX ucircumflex seven -282
+KPX ucircumflex eight -102
+KPX ucircumflex nine -225
+KPX ucircumflex colon -188
+KPX ucircumflex less -272
+KPX ucircumflex H -36
+KPX ucircumflex R -36
+KPX ucircumflex X -36
+KPX ucircumflex backslash -188
+KPX ucircumflex ordfeminine -36
+KPX ucircumflex guillemotleft -36
+KPX ucircumflex logicalnot -36
+KPX ucircumflex sfthyphen -36
+KPX ucircumflex acute -36
+KPX ucircumflex mu -36
+KPX ucircumflex paragraph -36
+KPX ucircumflex periodcentered -36
+KPX ucircumflex cedilla -36
+KPX ucircumflex guillemotright -36
+KPX ucircumflex onequarter -36
+KPX ucircumflex onehalf -36
+KPX ucircumflex threequarters -36
+KPX ucircumflex questiondown -188
+KPX ucircumflex Aacute -188
+KPX ucircumflex Yacute -36
+KPX ucircumflex Hbar -282
+KPX ucircumflex Idot -102
+KPX ucircumflex dotlessi -36
+KPX ucircumflex lacute -272
+
+KPX ydieresis dollar 47
+KPX ydieresis two -73
+KPX ydieresis seven -282
+KPX ydieresis eight -102
+KPX ydieresis nine -225
+KPX ydieresis colon -188
+KPX ydieresis less -272
+KPX ydieresis H -36
+KPX ydieresis R -36
+KPX ydieresis X -36
+KPX ydieresis backslash -188
+KPX ydieresis ordfeminine -36
+KPX ydieresis guillemotleft -36
+KPX ydieresis logicalnot -36
+KPX ydieresis sfthyphen -36
+KPX ydieresis acute -36
+KPX ydieresis mu -36
+KPX ydieresis paragraph -36
+KPX ydieresis periodcentered -36
+KPX ydieresis cedilla -36
+KPX ydieresis guillemotright -36
+KPX ydieresis onequarter -36
+KPX ydieresis onehalf -36
+KPX ydieresis threequarters -36
+KPX ydieresis questiondown -188
+KPX ydieresis Aacute -188
+KPX ydieresis Yacute -36
+KPX ydieresis Hbar -282
+KPX ydieresis Idot -102
+KPX ydieresis dotlessi -36
+KPX ydieresis lacute -272
+
+KPX Abreve O -193
+
+
+KPX Edotaccent dollar -83
+KPX Edotaccent ampersand -102
+KPX Edotaccent seven -149
+KPX Edotaccent nine -112
+KPX Edotaccent colon -83
+KPX Edotaccent less -131
+KPX Edotaccent D -45
+KPX Edotaccent H -92
+KPX Edotaccent R -92
+KPX Edotaccent X -92
+KPX Edotaccent backslash -112
+KPX Edotaccent m -112
+KPX Edotaccent braceright -36
+KPX Edotaccent cent -45
+KPX Edotaccent sterling -45
+KPX Edotaccent currency -45
+KPX Edotaccent yen -45
+KPX Edotaccent brokenbar -45
+KPX Edotaccent section -45
+KPX Edotaccent ordfeminine -92
+KPX Edotaccent guillemotleft -92
+KPX Edotaccent logicalnot -92
+KPX Edotaccent sfthyphen -92
+KPX Edotaccent acute -92
+KPX Edotaccent mu -92
+KPX Edotaccent paragraph -92
+KPX Edotaccent periodcentered -92
+KPX Edotaccent cedilla -92
+KPX Edotaccent guillemotright -92
+KPX Edotaccent onequarter -92
+KPX Edotaccent onehalf -92
+KPX Edotaccent threequarters -92
+KPX Edotaccent questiondown -112
+KPX Edotaccent Aacute -112
+KPX Edotaccent Egrave -102
+KPX Edotaccent Icircumflex -102
+KPX Edotaccent Yacute -92
+KPX Edotaccent Hbar -149
+KPX Edotaccent dotlessi -92
+KPX Edotaccent lacute -131
+
+KPX edotaccent F -45
+KPX edotaccent G -36
+KPX edotaccent H -45
+KPX edotaccent J -36
+KPX edotaccent K -36
+KPX edotaccent P -36
+KPX edotaccent Q -36
+KPX edotaccent R -45
+KPX edotaccent T -36
+KPX edotaccent U -36
+KPX edotaccent bracketleft -55
+KPX edotaccent m -73
+KPX edotaccent copyright -45
+KPX edotaccent ordfeminine -45
+KPX edotaccent guillemotleft -45
+KPX edotaccent logicalnot -45
+KPX edotaccent sfthyphen -45
+KPX edotaccent threesuperior -36
+KPX edotaccent acute -45
+KPX edotaccent mu -45
+KPX edotaccent paragraph -45
+KPX edotaccent periodcentered -45
+KPX edotaccent cedilla -45
+KPX edotaccent Eacute -45
+KPX edotaccent Idieresis -45
+KPX edotaccent Ntilde 72
+KPX edotaccent Yacute -45
+KPX edotaccent aacute -36
+KPX edotaccent Cdotaccent -36
+KPX edotaccent edotaccent -36
+KPX edotaccent ecaron -36
+
+KPX Ecaron dollar -83
+KPX Ecaron ampersand -102
+KPX Ecaron seven -149
+KPX Ecaron nine -112
+KPX Ecaron colon -83
+KPX Ecaron less -131
+KPX Ecaron D -45
+KPX Ecaron H -92
+KPX Ecaron R -92
+KPX Ecaron X -92
+KPX Ecaron backslash -112
+KPX Ecaron m -112
+KPX Ecaron braceright -36
+KPX Ecaron cent -45
+KPX Ecaron sterling -45
+KPX Ecaron currency -45
+KPX Ecaron yen -45
+KPX Ecaron brokenbar -45
+KPX Ecaron section -45
+KPX Ecaron ordfeminine -92
+KPX Ecaron guillemotleft -92
+KPX Ecaron logicalnot -92
+KPX Ecaron sfthyphen -92
+KPX Ecaron acute -92
+KPX Ecaron mu -92
+KPX Ecaron paragraph -92
+KPX Ecaron periodcentered -92
+KPX Ecaron cedilla -92
+KPX Ecaron guillemotright -92
+KPX Ecaron onequarter -92
+KPX Ecaron onehalf -92
+KPX Ecaron threequarters -92
+KPX Ecaron questiondown -112
+KPX Ecaron Aacute -112
+KPX Ecaron Egrave -102
+KPX Ecaron Icircumflex -102
+KPX Ecaron Yacute -92
+KPX Ecaron Hbar -149
+KPX Ecaron dotlessi -92
+KPX Ecaron lacute -131
+
+KPX ecaron F -45
+KPX ecaron G -36
+KPX ecaron H -45
+KPX ecaron J -36
+KPX ecaron K -36
+KPX ecaron P -36
+KPX ecaron Q -36
+KPX ecaron R -45
+KPX ecaron T -36
+KPX ecaron U -36
+KPX ecaron bracketleft -55
+KPX ecaron m -73
+KPX ecaron copyright -45
+KPX ecaron ordfeminine -45
+KPX ecaron guillemotleft -45
+KPX ecaron logicalnot -45
+KPX ecaron sfthyphen -45
+KPX ecaron threesuperior -36
+KPX ecaron acute -45
+KPX ecaron mu -45
+KPX ecaron paragraph -45
+KPX ecaron periodcentered -45
+KPX ecaron cedilla -45
+KPX ecaron Eacute -45
+KPX ecaron Idieresis -45
+KPX ecaron Ntilde -36
+KPX ecaron Yacute -45
+KPX ecaron aacute -36
+KPX ecaron Cdotaccent -36
+KPX ecaron edotaccent -36
+KPX ecaron ecaron -36
+
+KPX Gdotaccent dollar 38
+
+KPX Gcommaaccent dollar 38
+
+KPX Hbar dollar -159
+KPX Hbar ampersand -120
+KPX Hbar seven -36
+KPX Hbar D -339
+KPX Hbar F -348
+KPX Hbar H -348
+KPX Hbar L -63
+KPX Hbar R -348
+KPX Hbar U -301
+KPX Hbar V -339
+KPX Hbar X -311
+KPX Hbar Z -339
+KPX Hbar backslash -319
+KPX Hbar m -188
+KPX Hbar braceright -112
+KPX Hbar cent -339
+KPX Hbar sterling -339
+KPX Hbar currency -339
+KPX Hbar yen -339
+KPX Hbar brokenbar -339
+KPX Hbar section -339
+KPX Hbar copyright -348
+KPX Hbar ordfeminine -348
+KPX Hbar guillemotleft -348
+KPX Hbar logicalnot -348
+KPX Hbar sfthyphen -348
+KPX Hbar acute -348
+KPX Hbar mu -348
+KPX Hbar paragraph -348
+KPX Hbar periodcentered -348
+KPX Hbar cedilla -348
+KPX Hbar guillemotright -311
+KPX Hbar onequarter -311
+KPX Hbar onehalf -311
+KPX Hbar threequarters -311
+KPX Hbar questiondown -319
+KPX Hbar Aacute -319
+KPX Hbar Egrave -120
+KPX Hbar Eacute -348
+KPX Hbar Icircumflex -120
+KPX Hbar Idieresis -348
+KPX Hbar Yacute -348
+KPX Hbar edotaccent -301
+KPX Hbar ecaron -301
+KPX Hbar gdotaccent -339
+KPX Hbar gcommaaccent -339
+KPX Hbar Hbar -36
+KPX Hbar dotlessi -311
+
+KPX Idot equal -36
+KPX Idot Ldot -36
+
+KPX lacute dollar -159
+KPX lacute ampersand -112
+KPX lacute two -112
+KPX lacute D -282
+KPX lacute H -272
+KPX lacute L -73
+KPX lacute R -272
+KPX lacute X -235
+KPX lacute m -225
+KPX lacute braceright -149
+KPX lacute cent -282
+KPX lacute sterling -282
+KPX lacute currency -282
+KPX lacute yen -282
+KPX lacute brokenbar -282
+KPX lacute section -282
+KPX lacute ordfeminine -272
+KPX lacute guillemotleft -272
+KPX lacute logicalnot -272
+KPX lacute sfthyphen -272
+KPX lacute acute -272
+KPX lacute mu -272
+KPX lacute paragraph -272
+KPX lacute periodcentered -272
+KPX lacute cedilla -272
+KPX lacute guillemotright -235
+KPX lacute onequarter -235
+KPX lacute onehalf -235
+KPX lacute threequarters -235
+KPX lacute Egrave -112
+KPX lacute Icircumflex -112
+KPX lacute Yacute -272
+KPX lacute dotlessi -235
+
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ttf
new file mode 100644
index 0000000..8184ced
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ufm
new file mode 100644
index 0000000..d598e20
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Bold.ufm
@@ -0,0 +1,3285 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans Mono
+FontSubfamily Bold
+UniqueID DejaVu Sans Mono Bold
+FullName DejaVu Sans Mono Bold
+Version Version 2.37
+PostScriptName DejaVuSansMono-Bold
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+Weight Bold
+ItalicAngle 0
+IsFixedPitch true
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -447 -394 731 1041
+StartCharMetrics 3316
+U 32 ; WX 602 ; N space ; G 3
+U 33 ; WX 602 ; N exclam ; G 4
+U 34 ; WX 602 ; N quotedbl ; G 5
+U 35 ; WX 602 ; N numbersign ; G 6
+U 36 ; WX 602 ; N dollar ; G 7
+U 37 ; WX 602 ; N percent ; G 8
+U 38 ; WX 602 ; N ampersand ; G 9
+U 39 ; WX 602 ; N quotesingle ; G 10
+U 40 ; WX 602 ; N parenleft ; G 11
+U 41 ; WX 602 ; N parenright ; G 12
+U 42 ; WX 602 ; N asterisk ; G 13
+U 43 ; WX 602 ; N plus ; G 14
+U 44 ; WX 602 ; N comma ; G 15
+U 45 ; WX 602 ; N hyphen ; G 16
+U 46 ; WX 602 ; N period ; G 17
+U 47 ; WX 602 ; N slash ; G 18
+U 48 ; WX 602 ; N zero ; G 19
+U 49 ; WX 602 ; N one ; G 20
+U 50 ; WX 602 ; N two ; G 21
+U 51 ; WX 602 ; N three ; G 22
+U 52 ; WX 602 ; N four ; G 23
+U 53 ; WX 602 ; N five ; G 24
+U 54 ; WX 602 ; N six ; G 25
+U 55 ; WX 602 ; N seven ; G 26
+U 56 ; WX 602 ; N eight ; G 27
+U 57 ; WX 602 ; N nine ; G 28
+U 58 ; WX 602 ; N colon ; G 29
+U 59 ; WX 602 ; N semicolon ; G 30
+U 60 ; WX 602 ; N less ; G 31
+U 61 ; WX 602 ; N equal ; G 32
+U 62 ; WX 602 ; N greater ; G 33
+U 63 ; WX 602 ; N question ; G 34
+U 64 ; WX 602 ; N at ; G 35
+U 65 ; WX 602 ; N A ; G 36
+U 66 ; WX 602 ; N B ; G 37
+U 67 ; WX 602 ; N C ; G 38
+U 68 ; WX 602 ; N D ; G 39
+U 69 ; WX 602 ; N E ; G 40
+U 70 ; WX 602 ; N F ; G 41
+U 71 ; WX 602 ; N G ; G 42
+U 72 ; WX 602 ; N H ; G 43
+U 73 ; WX 602 ; N I ; G 44
+U 74 ; WX 602 ; N J ; G 45
+U 75 ; WX 602 ; N K ; G 46
+U 76 ; WX 602 ; N L ; G 47
+U 77 ; WX 602 ; N M ; G 48
+U 78 ; WX 602 ; N N ; G 49
+U 79 ; WX 602 ; N O ; G 50
+U 80 ; WX 602 ; N P ; G 51
+U 81 ; WX 602 ; N Q ; G 52
+U 82 ; WX 602 ; N R ; G 53
+U 83 ; WX 602 ; N S ; G 54
+U 84 ; WX 602 ; N T ; G 55
+U 85 ; WX 602 ; N U ; G 56
+U 86 ; WX 602 ; N V ; G 57
+U 87 ; WX 602 ; N W ; G 58
+U 88 ; WX 602 ; N X ; G 59
+U 89 ; WX 602 ; N Y ; G 60
+U 90 ; WX 602 ; N Z ; G 61
+U 91 ; WX 602 ; N bracketleft ; G 62
+U 92 ; WX 602 ; N backslash ; G 63
+U 93 ; WX 602 ; N bracketright ; G 64
+U 94 ; WX 602 ; N asciicircum ; G 65
+U 95 ; WX 602 ; N underscore ; G 66
+U 96 ; WX 602 ; N grave ; G 67
+U 97 ; WX 602 ; N a ; G 68
+U 98 ; WX 602 ; N b ; G 69
+U 99 ; WX 602 ; N c ; G 70
+U 100 ; WX 602 ; N d ; G 71
+U 101 ; WX 602 ; N e ; G 72
+U 102 ; WX 602 ; N f ; G 73
+U 103 ; WX 602 ; N g ; G 74
+U 104 ; WX 602 ; N h ; G 75
+U 105 ; WX 602 ; N i ; G 76
+U 106 ; WX 602 ; N j ; G 77
+U 107 ; WX 602 ; N k ; G 78
+U 108 ; WX 602 ; N l ; G 79
+U 109 ; WX 602 ; N m ; G 80
+U 110 ; WX 602 ; N n ; G 81
+U 111 ; WX 602 ; N o ; G 82
+U 112 ; WX 602 ; N p ; G 83
+U 113 ; WX 602 ; N q ; G 84
+U 114 ; WX 602 ; N r ; G 85
+U 115 ; WX 602 ; N s ; G 86
+U 116 ; WX 602 ; N t ; G 87
+U 117 ; WX 602 ; N u ; G 88
+U 118 ; WX 602 ; N v ; G 89
+U 119 ; WX 602 ; N w ; G 90
+U 120 ; WX 602 ; N x ; G 91
+U 121 ; WX 602 ; N y ; G 92
+U 122 ; WX 602 ; N z ; G 93
+U 123 ; WX 602 ; N braceleft ; G 94
+U 124 ; WX 602 ; N bar ; G 95
+U 125 ; WX 602 ; N braceright ; G 96
+U 126 ; WX 602 ; N asciitilde ; G 97
+U 160 ; WX 602 ; N nbspace ; G 98
+U 161 ; WX 602 ; N exclamdown ; G 99
+U 162 ; WX 602 ; N cent ; G 100
+U 163 ; WX 602 ; N sterling ; G 101
+U 164 ; WX 602 ; N currency ; G 102
+U 165 ; WX 602 ; N yen ; G 103
+U 166 ; WX 602 ; N brokenbar ; G 104
+U 167 ; WX 602 ; N section ; G 105
+U 168 ; WX 602 ; N dieresis ; G 106
+U 169 ; WX 602 ; N copyright ; G 107
+U 170 ; WX 602 ; N ordfeminine ; G 108
+U 171 ; WX 602 ; N guillemotleft ; G 109
+U 172 ; WX 602 ; N logicalnot ; G 110
+U 173 ; WX 602 ; N sfthyphen ; G 111
+U 174 ; WX 602 ; N registered ; G 112
+U 175 ; WX 602 ; N macron ; G 113
+U 176 ; WX 602 ; N degree ; G 114
+U 177 ; WX 602 ; N plusminus ; G 115
+U 178 ; WX 602 ; N twosuperior ; G 116
+U 179 ; WX 602 ; N threesuperior ; G 117
+U 180 ; WX 602 ; N acute ; G 118
+U 181 ; WX 602 ; N mu ; G 119
+U 182 ; WX 602 ; N paragraph ; G 120
+U 183 ; WX 602 ; N periodcentered ; G 121
+U 184 ; WX 602 ; N cedilla ; G 122
+U 185 ; WX 602 ; N onesuperior ; G 123
+U 186 ; WX 602 ; N ordmasculine ; G 124
+U 187 ; WX 602 ; N guillemotright ; G 125
+U 188 ; WX 602 ; N onequarter ; G 126
+U 189 ; WX 602 ; N onehalf ; G 127
+U 190 ; WX 602 ; N threequarters ; G 128
+U 191 ; WX 602 ; N questiondown ; G 129
+U 192 ; WX 602 ; N Agrave ; G 130
+U 193 ; WX 602 ; N Aacute ; G 131
+U 194 ; WX 602 ; N Acircumflex ; G 132
+U 195 ; WX 602 ; N Atilde ; G 133
+U 196 ; WX 602 ; N Adieresis ; G 134
+U 197 ; WX 602 ; N Aring ; G 135
+U 198 ; WX 602 ; N AE ; G 136
+U 199 ; WX 602 ; N Ccedilla ; G 137
+U 200 ; WX 602 ; N Egrave ; G 138
+U 201 ; WX 602 ; N Eacute ; G 139
+U 202 ; WX 602 ; N Ecircumflex ; G 140
+U 203 ; WX 602 ; N Edieresis ; G 141
+U 204 ; WX 602 ; N Igrave ; G 142
+U 205 ; WX 602 ; N Iacute ; G 143
+U 206 ; WX 602 ; N Icircumflex ; G 144
+U 207 ; WX 602 ; N Idieresis ; G 145
+U 208 ; WX 602 ; N Eth ; G 146
+U 209 ; WX 602 ; N Ntilde ; G 147
+U 210 ; WX 602 ; N Ograve ; G 148
+U 211 ; WX 602 ; N Oacute ; G 149
+U 212 ; WX 602 ; N Ocircumflex ; G 150
+U 213 ; WX 602 ; N Otilde ; G 151
+U 214 ; WX 602 ; N Odieresis ; G 152
+U 215 ; WX 602 ; N multiply ; G 153
+U 216 ; WX 602 ; N Oslash ; G 154
+U 217 ; WX 602 ; N Ugrave ; G 155
+U 218 ; WX 602 ; N Uacute ; G 156
+U 219 ; WX 602 ; N Ucircumflex ; G 157
+U 220 ; WX 602 ; N Udieresis ; G 158
+U 221 ; WX 602 ; N Yacute ; G 159
+U 222 ; WX 602 ; N Thorn ; G 160
+U 223 ; WX 602 ; N germandbls ; G 161
+U 224 ; WX 602 ; N agrave ; G 162
+U 225 ; WX 602 ; N aacute ; G 163
+U 226 ; WX 602 ; N acircumflex ; G 164
+U 227 ; WX 602 ; N atilde ; G 165
+U 228 ; WX 602 ; N adieresis ; G 166
+U 229 ; WX 602 ; N aring ; G 167
+U 230 ; WX 602 ; N ae ; G 168
+U 231 ; WX 602 ; N ccedilla ; G 169
+U 232 ; WX 602 ; N egrave ; G 170
+U 233 ; WX 602 ; N eacute ; G 171
+U 234 ; WX 602 ; N ecircumflex ; G 172
+U 235 ; WX 602 ; N edieresis ; G 173
+U 236 ; WX 602 ; N igrave ; G 174
+U 237 ; WX 602 ; N iacute ; G 175
+U 238 ; WX 602 ; N icircumflex ; G 176
+U 239 ; WX 602 ; N idieresis ; G 177
+U 240 ; WX 602 ; N eth ; G 178
+U 241 ; WX 602 ; N ntilde ; G 179
+U 242 ; WX 602 ; N ograve ; G 180
+U 243 ; WX 602 ; N oacute ; G 181
+U 244 ; WX 602 ; N ocircumflex ; G 182
+U 245 ; WX 602 ; N otilde ; G 183
+U 246 ; WX 602 ; N odieresis ; G 184
+U 247 ; WX 602 ; N divide ; G 185
+U 248 ; WX 602 ; N oslash ; G 186
+U 249 ; WX 602 ; N ugrave ; G 187
+U 250 ; WX 602 ; N uacute ; G 188
+U 251 ; WX 602 ; N ucircumflex ; G 189
+U 252 ; WX 602 ; N udieresis ; G 190
+U 253 ; WX 602 ; N yacute ; G 191
+U 254 ; WX 602 ; N thorn ; G 192
+U 255 ; WX 602 ; N ydieresis ; G 193
+U 256 ; WX 602 ; N Amacron ; G 194
+U 257 ; WX 602 ; N amacron ; G 195
+U 258 ; WX 602 ; N Abreve ; G 196
+U 259 ; WX 602 ; N abreve ; G 197
+U 260 ; WX 602 ; N Aogonek ; G 198
+U 261 ; WX 602 ; N aogonek ; G 199
+U 262 ; WX 602 ; N Cacute ; G 200
+U 263 ; WX 602 ; N cacute ; G 201
+U 264 ; WX 602 ; N Ccircumflex ; G 202
+U 265 ; WX 602 ; N ccircumflex ; G 203
+U 266 ; WX 602 ; N Cdotaccent ; G 204
+U 267 ; WX 602 ; N cdotaccent ; G 205
+U 268 ; WX 602 ; N Ccaron ; G 206
+U 269 ; WX 602 ; N ccaron ; G 207
+U 270 ; WX 602 ; N Dcaron ; G 208
+U 271 ; WX 602 ; N dcaron ; G 209
+U 272 ; WX 602 ; N Dcroat ; G 210
+U 273 ; WX 602 ; N dmacron ; G 211
+U 274 ; WX 602 ; N Emacron ; G 212
+U 275 ; WX 602 ; N emacron ; G 213
+U 276 ; WX 602 ; N Ebreve ; G 214
+U 277 ; WX 602 ; N ebreve ; G 215
+U 278 ; WX 602 ; N Edotaccent ; G 216
+U 279 ; WX 602 ; N edotaccent ; G 217
+U 280 ; WX 602 ; N Eogonek ; G 218
+U 281 ; WX 602 ; N eogonek ; G 219
+U 282 ; WX 602 ; N Ecaron ; G 220
+U 283 ; WX 602 ; N ecaron ; G 221
+U 284 ; WX 602 ; N Gcircumflex ; G 222
+U 285 ; WX 602 ; N gcircumflex ; G 223
+U 286 ; WX 602 ; N Gbreve ; G 224
+U 287 ; WX 602 ; N gbreve ; G 225
+U 288 ; WX 602 ; N Gdotaccent ; G 226
+U 289 ; WX 602 ; N gdotaccent ; G 227
+U 290 ; WX 602 ; N Gcommaaccent ; G 228
+U 291 ; WX 602 ; N gcommaaccent ; G 229
+U 292 ; WX 602 ; N Hcircumflex ; G 230
+U 293 ; WX 602 ; N hcircumflex ; G 231
+U 294 ; WX 602 ; N Hbar ; G 232
+U 295 ; WX 602 ; N hbar ; G 233
+U 296 ; WX 602 ; N Itilde ; G 234
+U 297 ; WX 602 ; N itilde ; G 235
+U 298 ; WX 602 ; N Imacron ; G 236
+U 299 ; WX 602 ; N imacron ; G 237
+U 300 ; WX 602 ; N Ibreve ; G 238
+U 301 ; WX 602 ; N ibreve ; G 239
+U 302 ; WX 602 ; N Iogonek ; G 240
+U 303 ; WX 602 ; N iogonek ; G 241
+U 304 ; WX 602 ; N Idot ; G 242
+U 305 ; WX 602 ; N dotlessi ; G 243
+U 306 ; WX 602 ; N IJ ; G 244
+U 307 ; WX 602 ; N ij ; G 245
+U 308 ; WX 602 ; N Jcircumflex ; G 246
+U 309 ; WX 602 ; N jcircumflex ; G 247
+U 310 ; WX 602 ; N Kcommaaccent ; G 248
+U 311 ; WX 602 ; N kcommaaccent ; G 249
+U 312 ; WX 602 ; N kgreenlandic ; G 250
+U 313 ; WX 602 ; N Lacute ; G 251
+U 314 ; WX 602 ; N lacute ; G 252
+U 315 ; WX 602 ; N Lcommaaccent ; G 253
+U 316 ; WX 602 ; N lcommaaccent ; G 254
+U 317 ; WX 602 ; N Lcaron ; G 255
+U 318 ; WX 602 ; N lcaron ; G 256
+U 319 ; WX 602 ; N Ldot ; G 257
+U 320 ; WX 602 ; N ldot ; G 258
+U 321 ; WX 602 ; N Lslash ; G 259
+U 322 ; WX 602 ; N lslash ; G 260
+U 323 ; WX 602 ; N Nacute ; G 261
+U 324 ; WX 602 ; N nacute ; G 262
+U 325 ; WX 602 ; N Ncommaaccent ; G 263
+U 326 ; WX 602 ; N ncommaaccent ; G 264
+U 327 ; WX 602 ; N Ncaron ; G 265
+U 328 ; WX 602 ; N ncaron ; G 266
+U 329 ; WX 602 ; N napostrophe ; G 267
+U 330 ; WX 602 ; N Eng ; G 268
+U 331 ; WX 602 ; N eng ; G 269
+U 332 ; WX 602 ; N Omacron ; G 270
+U 333 ; WX 602 ; N omacron ; G 271
+U 334 ; WX 602 ; N Obreve ; G 272
+U 335 ; WX 602 ; N obreve ; G 273
+U 336 ; WX 602 ; N Ohungarumlaut ; G 274
+U 337 ; WX 602 ; N ohungarumlaut ; G 275
+U 338 ; WX 602 ; N OE ; G 276
+U 339 ; WX 602 ; N oe ; G 277
+U 340 ; WX 602 ; N Racute ; G 278
+U 341 ; WX 602 ; N racute ; G 279
+U 342 ; WX 602 ; N Rcommaaccent ; G 280
+U 343 ; WX 602 ; N rcommaaccent ; G 281
+U 344 ; WX 602 ; N Rcaron ; G 282
+U 345 ; WX 602 ; N rcaron ; G 283
+U 346 ; WX 602 ; N Sacute ; G 284
+U 347 ; WX 602 ; N sacute ; G 285
+U 348 ; WX 602 ; N Scircumflex ; G 286
+U 349 ; WX 602 ; N scircumflex ; G 287
+U 350 ; WX 602 ; N Scedilla ; G 288
+U 351 ; WX 602 ; N scedilla ; G 289
+U 352 ; WX 602 ; N Scaron ; G 290
+U 353 ; WX 602 ; N scaron ; G 291
+U 354 ; WX 602 ; N Tcommaaccent ; G 292
+U 355 ; WX 602 ; N tcommaaccent ; G 293
+U 356 ; WX 602 ; N Tcaron ; G 294
+U 357 ; WX 602 ; N tcaron ; G 295
+U 358 ; WX 602 ; N Tbar ; G 296
+U 359 ; WX 602 ; N tbar ; G 297
+U 360 ; WX 602 ; N Utilde ; G 298
+U 361 ; WX 602 ; N utilde ; G 299
+U 362 ; WX 602 ; N Umacron ; G 300
+U 363 ; WX 602 ; N umacron ; G 301
+U 364 ; WX 602 ; N Ubreve ; G 302
+U 365 ; WX 602 ; N ubreve ; G 303
+U 366 ; WX 602 ; N Uring ; G 304
+U 367 ; WX 602 ; N uring ; G 305
+U 368 ; WX 602 ; N Uhungarumlaut ; G 306
+U 369 ; WX 602 ; N uhungarumlaut ; G 307
+U 370 ; WX 602 ; N Uogonek ; G 308
+U 371 ; WX 602 ; N uogonek ; G 309
+U 372 ; WX 602 ; N Wcircumflex ; G 310
+U 373 ; WX 602 ; N wcircumflex ; G 311
+U 374 ; WX 602 ; N Ycircumflex ; G 312
+U 375 ; WX 602 ; N ycircumflex ; G 313
+U 376 ; WX 602 ; N Ydieresis ; G 314
+U 377 ; WX 602 ; N Zacute ; G 315
+U 378 ; WX 602 ; N zacute ; G 316
+U 379 ; WX 602 ; N Zdotaccent ; G 317
+U 380 ; WX 602 ; N zdotaccent ; G 318
+U 381 ; WX 602 ; N Zcaron ; G 319
+U 382 ; WX 602 ; N zcaron ; G 320
+U 383 ; WX 602 ; N longs ; G 321
+U 384 ; WX 602 ; N uni0180 ; G 322
+U 385 ; WX 602 ; N uni0181 ; G 323
+U 386 ; WX 602 ; N uni0182 ; G 324
+U 387 ; WX 602 ; N uni0183 ; G 325
+U 388 ; WX 602 ; N uni0184 ; G 326
+U 389 ; WX 602 ; N uni0185 ; G 327
+U 390 ; WX 602 ; N uni0186 ; G 328
+U 391 ; WX 602 ; N uni0187 ; G 329
+U 392 ; WX 602 ; N uni0188 ; G 330
+U 393 ; WX 602 ; N uni0189 ; G 331
+U 394 ; WX 602 ; N uni018A ; G 332
+U 395 ; WX 602 ; N uni018B ; G 333
+U 396 ; WX 602 ; N uni018C ; G 334
+U 397 ; WX 602 ; N uni018D ; G 335
+U 398 ; WX 602 ; N uni018E ; G 336
+U 399 ; WX 602 ; N uni018F ; G 337
+U 400 ; WX 602 ; N uni0190 ; G 338
+U 401 ; WX 602 ; N uni0191 ; G 339
+U 402 ; WX 602 ; N florin ; G 340
+U 403 ; WX 602 ; N uni0193 ; G 341
+U 404 ; WX 602 ; N uni0194 ; G 342
+U 405 ; WX 602 ; N uni0195 ; G 343
+U 406 ; WX 602 ; N uni0196 ; G 344
+U 407 ; WX 602 ; N uni0197 ; G 345
+U 408 ; WX 602 ; N uni0198 ; G 346
+U 409 ; WX 602 ; N uni0199 ; G 347
+U 410 ; WX 602 ; N uni019A ; G 348
+U 411 ; WX 602 ; N uni019B ; G 349
+U 412 ; WX 602 ; N uni019C ; G 350
+U 413 ; WX 602 ; N uni019D ; G 351
+U 414 ; WX 602 ; N uni019E ; G 352
+U 415 ; WX 602 ; N uni019F ; G 353
+U 416 ; WX 602 ; N Ohorn ; G 354
+U 417 ; WX 602 ; N ohorn ; G 355
+U 418 ; WX 602 ; N uni01A2 ; G 356
+U 419 ; WX 602 ; N uni01A3 ; G 357
+U 420 ; WX 602 ; N uni01A4 ; G 358
+U 421 ; WX 602 ; N uni01A5 ; G 359
+U 422 ; WX 602 ; N uni01A6 ; G 360
+U 423 ; WX 602 ; N uni01A7 ; G 361
+U 424 ; WX 602 ; N uni01A8 ; G 362
+U 425 ; WX 602 ; N uni01A9 ; G 363
+U 426 ; WX 602 ; N uni01AA ; G 364
+U 427 ; WX 602 ; N uni01AB ; G 365
+U 428 ; WX 602 ; N uni01AC ; G 366
+U 429 ; WX 602 ; N uni01AD ; G 367
+U 430 ; WX 602 ; N uni01AE ; G 368
+U 431 ; WX 602 ; N Uhorn ; G 369
+U 432 ; WX 602 ; N uhorn ; G 370
+U 433 ; WX 602 ; N uni01B1 ; G 371
+U 434 ; WX 602 ; N uni01B2 ; G 372
+U 435 ; WX 602 ; N uni01B3 ; G 373
+U 436 ; WX 602 ; N uni01B4 ; G 374
+U 437 ; WX 602 ; N uni01B5 ; G 375
+U 438 ; WX 602 ; N uni01B6 ; G 376
+U 439 ; WX 602 ; N uni01B7 ; G 377
+U 440 ; WX 602 ; N uni01B8 ; G 378
+U 441 ; WX 602 ; N uni01B9 ; G 379
+U 442 ; WX 602 ; N uni01BA ; G 380
+U 443 ; WX 602 ; N uni01BB ; G 381
+U 444 ; WX 602 ; N uni01BC ; G 382
+U 445 ; WX 602 ; N uni01BD ; G 383
+U 446 ; WX 602 ; N uni01BE ; G 384
+U 447 ; WX 602 ; N uni01BF ; G 385
+U 448 ; WX 602 ; N uni01C0 ; G 386
+U 449 ; WX 602 ; N uni01C1 ; G 387
+U 450 ; WX 602 ; N uni01C2 ; G 388
+U 451 ; WX 602 ; N uni01C3 ; G 389
+U 461 ; WX 602 ; N uni01CD ; G 390
+U 462 ; WX 602 ; N uni01CE ; G 391
+U 463 ; WX 602 ; N uni01CF ; G 392
+U 464 ; WX 602 ; N uni01D0 ; G 393
+U 465 ; WX 602 ; N uni01D1 ; G 394
+U 466 ; WX 602 ; N uni01D2 ; G 395
+U 467 ; WX 602 ; N uni01D3 ; G 396
+U 468 ; WX 602 ; N uni01D4 ; G 397
+U 469 ; WX 602 ; N uni01D5 ; G 398
+U 470 ; WX 602 ; N uni01D6 ; G 399
+U 471 ; WX 602 ; N uni01D7 ; G 400
+U 472 ; WX 602 ; N uni01D8 ; G 401
+U 473 ; WX 602 ; N uni01D9 ; G 402
+U 474 ; WX 602 ; N uni01DA ; G 403
+U 475 ; WX 602 ; N uni01DB ; G 404
+U 476 ; WX 602 ; N uni01DC ; G 405
+U 477 ; WX 602 ; N uni01DD ; G 406
+U 478 ; WX 602 ; N uni01DE ; G 407
+U 479 ; WX 602 ; N uni01DF ; G 408
+U 480 ; WX 602 ; N uni01E0 ; G 409
+U 481 ; WX 602 ; N uni01E1 ; G 410
+U 482 ; WX 602 ; N uni01E2 ; G 411
+U 483 ; WX 602 ; N uni01E3 ; G 412
+U 486 ; WX 602 ; N Gcaron ; G 413
+U 487 ; WX 602 ; N gcaron ; G 414
+U 488 ; WX 602 ; N uni01E8 ; G 415
+U 489 ; WX 602 ; N uni01E9 ; G 416
+U 490 ; WX 602 ; N uni01EA ; G 417
+U 491 ; WX 602 ; N uni01EB ; G 418
+U 492 ; WX 602 ; N uni01EC ; G 419
+U 493 ; WX 602 ; N uni01ED ; G 420
+U 494 ; WX 602 ; N uni01EE ; G 421
+U 495 ; WX 602 ; N uni01EF ; G 422
+U 496 ; WX 602 ; N uni01F0 ; G 423
+U 500 ; WX 602 ; N uni01F4 ; G 424
+U 501 ; WX 602 ; N uni01F5 ; G 425
+U 502 ; WX 602 ; N uni01F6 ; G 426
+U 504 ; WX 602 ; N uni01F8 ; G 427
+U 505 ; WX 602 ; N uni01F9 ; G 428
+U 508 ; WX 602 ; N AEacute ; G 429
+U 509 ; WX 602 ; N aeacute ; G 430
+U 510 ; WX 602 ; N Oslashacute ; G 431
+U 511 ; WX 602 ; N oslashacute ; G 432
+U 512 ; WX 602 ; N uni0200 ; G 433
+U 513 ; WX 602 ; N uni0201 ; G 434
+U 514 ; WX 602 ; N uni0202 ; G 435
+U 515 ; WX 602 ; N uni0203 ; G 436
+U 516 ; WX 602 ; N uni0204 ; G 437
+U 517 ; WX 602 ; N uni0205 ; G 438
+U 518 ; WX 602 ; N uni0206 ; G 439
+U 519 ; WX 602 ; N uni0207 ; G 440
+U 520 ; WX 602 ; N uni0208 ; G 441
+U 521 ; WX 602 ; N uni0209 ; G 442
+U 522 ; WX 602 ; N uni020A ; G 443
+U 523 ; WX 602 ; N uni020B ; G 444
+U 524 ; WX 602 ; N uni020C ; G 445
+U 525 ; WX 602 ; N uni020D ; G 446
+U 526 ; WX 602 ; N uni020E ; G 447
+U 527 ; WX 602 ; N uni020F ; G 448
+U 528 ; WX 602 ; N uni0210 ; G 449
+U 529 ; WX 602 ; N uni0211 ; G 450
+U 530 ; WX 602 ; N uni0212 ; G 451
+U 531 ; WX 602 ; N uni0213 ; G 452
+U 532 ; WX 602 ; N uni0214 ; G 453
+U 533 ; WX 602 ; N uni0215 ; G 454
+U 534 ; WX 602 ; N uni0216 ; G 455
+U 535 ; WX 602 ; N uni0217 ; G 456
+U 536 ; WX 602 ; N Scommaaccent ; G 457
+U 537 ; WX 602 ; N scommaaccent ; G 458
+U 538 ; WX 602 ; N uni021A ; G 459
+U 539 ; WX 602 ; N uni021B ; G 460
+U 540 ; WX 602 ; N uni021C ; G 461
+U 541 ; WX 602 ; N uni021D ; G 462
+U 542 ; WX 602 ; N uni021E ; G 463
+U 543 ; WX 602 ; N uni021F ; G 464
+U 544 ; WX 602 ; N uni0220 ; G 465
+U 545 ; WX 602 ; N uni0221 ; G 466
+U 548 ; WX 602 ; N uni0224 ; G 467
+U 549 ; WX 602 ; N uni0225 ; G 468
+U 550 ; WX 602 ; N uni0226 ; G 469
+U 551 ; WX 602 ; N uni0227 ; G 470
+U 552 ; WX 602 ; N uni0228 ; G 471
+U 553 ; WX 602 ; N uni0229 ; G 472
+U 554 ; WX 602 ; N uni022A ; G 473
+U 555 ; WX 602 ; N uni022B ; G 474
+U 556 ; WX 602 ; N uni022C ; G 475
+U 557 ; WX 602 ; N uni022D ; G 476
+U 558 ; WX 602 ; N uni022E ; G 477
+U 559 ; WX 602 ; N uni022F ; G 478
+U 560 ; WX 602 ; N uni0230 ; G 479
+U 561 ; WX 602 ; N uni0231 ; G 480
+U 562 ; WX 602 ; N uni0232 ; G 481
+U 563 ; WX 602 ; N uni0233 ; G 482
+U 564 ; WX 602 ; N uni0234 ; G 483
+U 565 ; WX 602 ; N uni0235 ; G 484
+U 566 ; WX 602 ; N uni0236 ; G 485
+U 567 ; WX 602 ; N dotlessj ; G 486
+U 568 ; WX 602 ; N uni0238 ; G 487
+U 569 ; WX 602 ; N uni0239 ; G 488
+U 570 ; WX 602 ; N uni023A ; G 489
+U 571 ; WX 602 ; N uni023B ; G 490
+U 572 ; WX 602 ; N uni023C ; G 491
+U 573 ; WX 602 ; N uni023D ; G 492
+U 574 ; WX 602 ; N uni023E ; G 493
+U 575 ; WX 602 ; N uni023F ; G 494
+U 576 ; WX 602 ; N uni0240 ; G 495
+U 577 ; WX 602 ; N uni0241 ; G 496
+U 579 ; WX 602 ; N uni0243 ; G 497
+U 580 ; WX 602 ; N uni0244 ; G 498
+U 581 ; WX 602 ; N uni0245 ; G 499
+U 588 ; WX 602 ; N uni024C ; G 500
+U 589 ; WX 602 ; N uni024D ; G 501
+U 592 ; WX 602 ; N uni0250 ; G 502
+U 593 ; WX 602 ; N uni0251 ; G 503
+U 594 ; WX 602 ; N uni0252 ; G 504
+U 595 ; WX 602 ; N uni0253 ; G 505
+U 596 ; WX 602 ; N uni0254 ; G 506
+U 597 ; WX 602 ; N uni0255 ; G 507
+U 598 ; WX 602 ; N uni0256 ; G 508
+U 599 ; WX 602 ; N uni0257 ; G 509
+U 600 ; WX 602 ; N uni0258 ; G 510
+U 601 ; WX 602 ; N uni0259 ; G 511
+U 602 ; WX 602 ; N uni025A ; G 512
+U 603 ; WX 602 ; N uni025B ; G 513
+U 604 ; WX 602 ; N uni025C ; G 514
+U 605 ; WX 602 ; N uni025D ; G 515
+U 606 ; WX 602 ; N uni025E ; G 516
+U 607 ; WX 602 ; N uni025F ; G 517
+U 608 ; WX 602 ; N uni0260 ; G 518
+U 609 ; WX 602 ; N uni0261 ; G 519
+U 610 ; WX 602 ; N uni0262 ; G 520
+U 611 ; WX 602 ; N uni0263 ; G 521
+U 612 ; WX 602 ; N uni0264 ; G 522
+U 613 ; WX 602 ; N uni0265 ; G 523
+U 614 ; WX 602 ; N uni0266 ; G 524
+U 615 ; WX 602 ; N uni0267 ; G 525
+U 616 ; WX 602 ; N uni0268 ; G 526
+U 617 ; WX 602 ; N uni0269 ; G 527
+U 618 ; WX 602 ; N uni026A ; G 528
+U 619 ; WX 602 ; N uni026B ; G 529
+U 620 ; WX 602 ; N uni026C ; G 530
+U 621 ; WX 602 ; N uni026D ; G 531
+U 622 ; WX 602 ; N uni026E ; G 532
+U 623 ; WX 602 ; N uni026F ; G 533
+U 624 ; WX 602 ; N uni0270 ; G 534
+U 625 ; WX 602 ; N uni0271 ; G 535
+U 626 ; WX 602 ; N uni0272 ; G 536
+U 627 ; WX 602 ; N uni0273 ; G 537
+U 628 ; WX 602 ; N uni0274 ; G 538
+U 629 ; WX 602 ; N uni0275 ; G 539
+U 630 ; WX 602 ; N uni0276 ; G 540
+U 631 ; WX 602 ; N uni0277 ; G 541
+U 632 ; WX 602 ; N uni0278 ; G 542
+U 633 ; WX 602 ; N uni0279 ; G 543
+U 634 ; WX 602 ; N uni027A ; G 544
+U 635 ; WX 602 ; N uni027B ; G 545
+U 636 ; WX 602 ; N uni027C ; G 546
+U 637 ; WX 602 ; N uni027D ; G 547
+U 638 ; WX 602 ; N uni027E ; G 548
+U 639 ; WX 602 ; N uni027F ; G 549
+U 640 ; WX 602 ; N uni0280 ; G 550
+U 641 ; WX 602 ; N uni0281 ; G 551
+U 642 ; WX 602 ; N uni0282 ; G 552
+U 643 ; WX 602 ; N uni0283 ; G 553
+U 644 ; WX 602 ; N uni0284 ; G 554
+U 645 ; WX 602 ; N uni0285 ; G 555
+U 646 ; WX 602 ; N uni0286 ; G 556
+U 647 ; WX 602 ; N uni0287 ; G 557
+U 648 ; WX 602 ; N uni0288 ; G 558
+U 649 ; WX 602 ; N uni0289 ; G 559
+U 650 ; WX 602 ; N uni028A ; G 560
+U 651 ; WX 602 ; N uni028B ; G 561
+U 652 ; WX 602 ; N uni028C ; G 562
+U 653 ; WX 602 ; N uni028D ; G 563
+U 654 ; WX 602 ; N uni028E ; G 564
+U 655 ; WX 602 ; N uni028F ; G 565
+U 656 ; WX 602 ; N uni0290 ; G 566
+U 657 ; WX 602 ; N uni0291 ; G 567
+U 658 ; WX 602 ; N uni0292 ; G 568
+U 659 ; WX 602 ; N uni0293 ; G 569
+U 660 ; WX 602 ; N uni0294 ; G 570
+U 661 ; WX 602 ; N uni0295 ; G 571
+U 662 ; WX 602 ; N uni0296 ; G 572
+U 663 ; WX 602 ; N uni0297 ; G 573
+U 664 ; WX 602 ; N uni0298 ; G 574
+U 665 ; WX 602 ; N uni0299 ; G 575
+U 666 ; WX 602 ; N uni029A ; G 576
+U 667 ; WX 602 ; N uni029B ; G 577
+U 668 ; WX 602 ; N uni029C ; G 578
+U 669 ; WX 602 ; N uni029D ; G 579
+U 670 ; WX 602 ; N uni029E ; G 580
+U 671 ; WX 602 ; N uni029F ; G 581
+U 672 ; WX 602 ; N uni02A0 ; G 582
+U 673 ; WX 602 ; N uni02A1 ; G 583
+U 674 ; WX 602 ; N uni02A2 ; G 584
+U 675 ; WX 602 ; N uni02A3 ; G 585
+U 676 ; WX 602 ; N uni02A4 ; G 586
+U 677 ; WX 602 ; N uni02A5 ; G 587
+U 678 ; WX 602 ; N uni02A6 ; G 588
+U 679 ; WX 602 ; N uni02A7 ; G 589
+U 680 ; WX 602 ; N uni02A8 ; G 590
+U 681 ; WX 602 ; N uni02A9 ; G 591
+U 682 ; WX 602 ; N uni02AA ; G 592
+U 683 ; WX 602 ; N uni02AB ; G 593
+U 684 ; WX 602 ; N uni02AC ; G 594
+U 685 ; WX 602 ; N uni02AD ; G 595
+U 686 ; WX 602 ; N uni02AE ; G 596
+U 687 ; WX 602 ; N uni02AF ; G 597
+U 688 ; WX 602 ; N uni02B0 ; G 598
+U 689 ; WX 602 ; N uni02B1 ; G 599
+U 690 ; WX 602 ; N uni02B2 ; G 600
+U 691 ; WX 602 ; N uni02B3 ; G 601
+U 692 ; WX 602 ; N uni02B4 ; G 602
+U 693 ; WX 602 ; N uni02B5 ; G 603
+U 694 ; WX 602 ; N uni02B6 ; G 604
+U 695 ; WX 602 ; N uni02B7 ; G 605
+U 696 ; WX 602 ; N uni02B8 ; G 606
+U 697 ; WX 602 ; N uni02B9 ; G 607
+U 699 ; WX 602 ; N uni02BB ; G 608
+U 700 ; WX 602 ; N uni02BC ; G 609
+U 701 ; WX 602 ; N uni02BD ; G 610
+U 702 ; WX 602 ; N uni02BE ; G 611
+U 703 ; WX 602 ; N uni02BF ; G 612
+U 704 ; WX 602 ; N uni02C0 ; G 613
+U 705 ; WX 602 ; N uni02C1 ; G 614
+U 710 ; WX 602 ; N circumflex ; G 615
+U 711 ; WX 602 ; N caron ; G 616
+U 712 ; WX 602 ; N uni02C8 ; G 617
+U 713 ; WX 602 ; N uni02C9 ; G 618
+U 716 ; WX 602 ; N uni02CC ; G 619
+U 717 ; WX 602 ; N uni02CD ; G 620
+U 718 ; WX 602 ; N uni02CE ; G 621
+U 719 ; WX 602 ; N uni02CF ; G 622
+U 720 ; WX 602 ; N uni02D0 ; G 623
+U 721 ; WX 602 ; N uni02D1 ; G 624
+U 722 ; WX 602 ; N uni02D2 ; G 625
+U 723 ; WX 602 ; N uni02D3 ; G 626
+U 726 ; WX 602 ; N uni02D6 ; G 627
+U 727 ; WX 602 ; N uni02D7 ; G 628
+U 728 ; WX 602 ; N breve ; G 629
+U 729 ; WX 602 ; N dotaccent ; G 630
+U 730 ; WX 602 ; N ring ; G 631
+U 731 ; WX 602 ; N ogonek ; G 632
+U 732 ; WX 602 ; N tilde ; G 633
+U 733 ; WX 602 ; N hungarumlaut ; G 634
+U 734 ; WX 602 ; N uni02DE ; G 635
+U 736 ; WX 602 ; N uni02E0 ; G 636
+U 737 ; WX 602 ; N uni02E1 ; G 637
+U 738 ; WX 602 ; N uni02E2 ; G 638
+U 739 ; WX 602 ; N uni02E3 ; G 639
+U 740 ; WX 602 ; N uni02E4 ; G 640
+U 741 ; WX 602 ; N uni02E5 ; G 641
+U 742 ; WX 602 ; N uni02E6 ; G 642
+U 743 ; WX 602 ; N uni02E7 ; G 643
+U 744 ; WX 602 ; N uni02E8 ; G 644
+U 745 ; WX 602 ; N uni02E9 ; G 645
+U 750 ; WX 602 ; N uni02EE ; G 646
+U 755 ; WX 602 ; N uni02F3 ; G 647
+U 768 ; WX 602 ; N gravecomb ; G 648
+U 769 ; WX 602 ; N acutecomb ; G 649
+U 770 ; WX 602 ; N uni0302 ; G 650
+U 771 ; WX 602 ; N tildecomb ; G 651
+U 772 ; WX 602 ; N uni0304 ; G 652
+U 773 ; WX 602 ; N uni0305 ; G 653
+U 774 ; WX 602 ; N uni0306 ; G 654
+U 775 ; WX 602 ; N uni0307 ; G 655
+U 776 ; WX 602 ; N uni0308 ; G 656
+U 777 ; WX 602 ; N hookabovecomb ; G 657
+U 778 ; WX 602 ; N uni030A ; G 658
+U 779 ; WX 602 ; N uni030B ; G 659
+U 780 ; WX 602 ; N uni030C ; G 660
+U 781 ; WX 602 ; N uni030D ; G 661
+U 782 ; WX 602 ; N uni030E ; G 662
+U 783 ; WX 602 ; N uni030F ; G 663
+U 784 ; WX 602 ; N uni0310 ; G 664
+U 785 ; WX 602 ; N uni0311 ; G 665
+U 786 ; WX 602 ; N uni0312 ; G 666
+U 787 ; WX 602 ; N uni0313 ; G 667
+U 788 ; WX 602 ; N uni0314 ; G 668
+U 789 ; WX 602 ; N uni0315 ; G 669
+U 790 ; WX 602 ; N uni0316 ; G 670
+U 791 ; WX 602 ; N uni0317 ; G 671
+U 792 ; WX 602 ; N uni0318 ; G 672
+U 793 ; WX 602 ; N uni0319 ; G 673
+U 794 ; WX 602 ; N uni031A ; G 674
+U 795 ; WX 602 ; N uni031B ; G 675
+U 796 ; WX 602 ; N uni031C ; G 676
+U 797 ; WX 602 ; N uni031D ; G 677
+U 798 ; WX 602 ; N uni031E ; G 678
+U 799 ; WX 602 ; N uni031F ; G 679
+U 800 ; WX 602 ; N uni0320 ; G 680
+U 801 ; WX 602 ; N uni0321 ; G 681
+U 802 ; WX 602 ; N uni0322 ; G 682
+U 803 ; WX 602 ; N dotbelowcomb ; G 683
+U 804 ; WX 602 ; N uni0324 ; G 684
+U 805 ; WX 602 ; N uni0325 ; G 685
+U 806 ; WX 602 ; N uni0326 ; G 686
+U 807 ; WX 602 ; N uni0327 ; G 687
+U 808 ; WX 602 ; N uni0328 ; G 688
+U 809 ; WX 602 ; N uni0329 ; G 689
+U 810 ; WX 602 ; N uni032A ; G 690
+U 811 ; WX 602 ; N uni032B ; G 691
+U 812 ; WX 602 ; N uni032C ; G 692
+U 813 ; WX 602 ; N uni032D ; G 693
+U 814 ; WX 602 ; N uni032E ; G 694
+U 815 ; WX 602 ; N uni032F ; G 695
+U 816 ; WX 602 ; N uni0330 ; G 696
+U 817 ; WX 602 ; N uni0331 ; G 697
+U 818 ; WX 602 ; N uni0332 ; G 698
+U 819 ; WX 602 ; N uni0333 ; G 699
+U 820 ; WX 602 ; N uni0334 ; G 700
+U 821 ; WX 602 ; N uni0335 ; G 701
+U 822 ; WX 602 ; N uni0336 ; G 702
+U 823 ; WX 602 ; N uni0337 ; G 703
+U 824 ; WX 602 ; N uni0338 ; G 704
+U 825 ; WX 602 ; N uni0339 ; G 705
+U 826 ; WX 602 ; N uni033A ; G 706
+U 827 ; WX 602 ; N uni033B ; G 707
+U 828 ; WX 602 ; N uni033C ; G 708
+U 829 ; WX 602 ; N uni033D ; G 709
+U 830 ; WX 602 ; N uni033E ; G 710
+U 831 ; WX 602 ; N uni033F ; G 711
+U 835 ; WX 602 ; N uni0343 ; G 712
+U 856 ; WX 602 ; N uni0358 ; G 713
+U 865 ; WX 602 ; N uni0361 ; G 714
+U 884 ; WX 602 ; N uni0374 ; G 715
+U 885 ; WX 602 ; N uni0375 ; G 716
+U 886 ; WX 602 ; N uni0376 ; G 717
+U 887 ; WX 602 ; N uni0377 ; G 718
+U 890 ; WX 602 ; N uni037A ; G 719
+U 891 ; WX 602 ; N uni037B ; G 720
+U 892 ; WX 602 ; N uni037C ; G 721
+U 893 ; WX 602 ; N uni037D ; G 722
+U 894 ; WX 602 ; N uni037E ; G 723
+U 895 ; WX 602 ; N uni037F ; G 724
+U 900 ; WX 602 ; N tonos ; G 725
+U 901 ; WX 602 ; N dieresistonos ; G 726
+U 902 ; WX 602 ; N Alphatonos ; G 727
+U 903 ; WX 602 ; N anoteleia ; G 728
+U 904 ; WX 602 ; N Epsilontonos ; G 729
+U 905 ; WX 602 ; N Etatonos ; G 730
+U 906 ; WX 602 ; N Iotatonos ; G 731
+U 908 ; WX 602 ; N Omicrontonos ; G 732
+U 910 ; WX 602 ; N Upsilontonos ; G 733
+U 911 ; WX 602 ; N Omegatonos ; G 734
+U 912 ; WX 602 ; N iotadieresistonos ; G 735
+U 913 ; WX 602 ; N Alpha ; G 736
+U 914 ; WX 602 ; N Beta ; G 737
+U 915 ; WX 602 ; N Gamma ; G 738
+U 916 ; WX 602 ; N uni0394 ; G 739
+U 917 ; WX 602 ; N Epsilon ; G 740
+U 918 ; WX 602 ; N Zeta ; G 741
+U 919 ; WX 602 ; N Eta ; G 742
+U 920 ; WX 602 ; N Theta ; G 743
+U 921 ; WX 602 ; N Iota ; G 744
+U 922 ; WX 602 ; N Kappa ; G 745
+U 923 ; WX 602 ; N Lambda ; G 746
+U 924 ; WX 602 ; N Mu ; G 747
+U 925 ; WX 602 ; N Nu ; G 748
+U 926 ; WX 602 ; N Xi ; G 749
+U 927 ; WX 602 ; N Omicron ; G 750
+U 928 ; WX 602 ; N Pi ; G 751
+U 929 ; WX 602 ; N Rho ; G 752
+U 931 ; WX 602 ; N Sigma ; G 753
+U 932 ; WX 602 ; N Tau ; G 754
+U 933 ; WX 602 ; N Upsilon ; G 755
+U 934 ; WX 602 ; N Phi ; G 756
+U 935 ; WX 602 ; N Chi ; G 757
+U 936 ; WX 602 ; N Psi ; G 758
+U 937 ; WX 602 ; N Omega ; G 759
+U 938 ; WX 602 ; N Iotadieresis ; G 760
+U 939 ; WX 602 ; N Upsilondieresis ; G 761
+U 940 ; WX 602 ; N alphatonos ; G 762
+U 941 ; WX 602 ; N epsilontonos ; G 763
+U 942 ; WX 602 ; N etatonos ; G 764
+U 943 ; WX 602 ; N iotatonos ; G 765
+U 944 ; WX 602 ; N upsilondieresistonos ; G 766
+U 945 ; WX 602 ; N alpha ; G 767
+U 946 ; WX 602 ; N beta ; G 768
+U 947 ; WX 602 ; N gamma ; G 769
+U 948 ; WX 602 ; N delta ; G 770
+U 949 ; WX 602 ; N epsilon ; G 771
+U 950 ; WX 602 ; N zeta ; G 772
+U 951 ; WX 602 ; N eta ; G 773
+U 952 ; WX 602 ; N theta ; G 774
+U 953 ; WX 602 ; N iota ; G 775
+U 954 ; WX 602 ; N kappa ; G 776
+U 955 ; WX 602 ; N lambda ; G 777
+U 956 ; WX 602 ; N uni03BC ; G 778
+U 957 ; WX 602 ; N nu ; G 779
+U 958 ; WX 602 ; N xi ; G 780
+U 959 ; WX 602 ; N omicron ; G 781
+U 960 ; WX 602 ; N pi ; G 782
+U 961 ; WX 602 ; N rho ; G 783
+U 962 ; WX 602 ; N sigma1 ; G 784
+U 963 ; WX 602 ; N sigma ; G 785
+U 964 ; WX 602 ; N tau ; G 786
+U 965 ; WX 602 ; N upsilon ; G 787
+U 966 ; WX 602 ; N phi ; G 788
+U 967 ; WX 602 ; N chi ; G 789
+U 968 ; WX 602 ; N psi ; G 790
+U 969 ; WX 602 ; N omega ; G 791
+U 970 ; WX 602 ; N iotadieresis ; G 792
+U 971 ; WX 602 ; N upsilondieresis ; G 793
+U 972 ; WX 602 ; N omicrontonos ; G 794
+U 973 ; WX 602 ; N upsilontonos ; G 795
+U 974 ; WX 602 ; N omegatonos ; G 796
+U 976 ; WX 602 ; N uni03D0 ; G 797
+U 977 ; WX 602 ; N theta1 ; G 798
+U 978 ; WX 602 ; N Upsilon1 ; G 799
+U 979 ; WX 602 ; N uni03D3 ; G 800
+U 980 ; WX 602 ; N uni03D4 ; G 801
+U 981 ; WX 602 ; N phi1 ; G 802
+U 982 ; WX 602 ; N omega1 ; G 803
+U 983 ; WX 602 ; N uni03D7 ; G 804
+U 984 ; WX 602 ; N uni03D8 ; G 805
+U 985 ; WX 602 ; N uni03D9 ; G 806
+U 986 ; WX 602 ; N uni03DA ; G 807
+U 987 ; WX 602 ; N uni03DB ; G 808
+U 988 ; WX 602 ; N uni03DC ; G 809
+U 989 ; WX 602 ; N uni03DD ; G 810
+U 990 ; WX 602 ; N uni03DE ; G 811
+U 991 ; WX 602 ; N uni03DF ; G 812
+U 992 ; WX 602 ; N uni03E0 ; G 813
+U 993 ; WX 602 ; N uni03E1 ; G 814
+U 1008 ; WX 602 ; N uni03F0 ; G 815
+U 1009 ; WX 602 ; N uni03F1 ; G 816
+U 1010 ; WX 602 ; N uni03F2 ; G 817
+U 1011 ; WX 602 ; N uni03F3 ; G 818
+U 1012 ; WX 602 ; N uni03F4 ; G 819
+U 1013 ; WX 602 ; N uni03F5 ; G 820
+U 1014 ; WX 602 ; N uni03F6 ; G 821
+U 1015 ; WX 602 ; N uni03F7 ; G 822
+U 1016 ; WX 602 ; N uni03F8 ; G 823
+U 1017 ; WX 602 ; N uni03F9 ; G 824
+U 1018 ; WX 602 ; N uni03FA ; G 825
+U 1019 ; WX 602 ; N uni03FB ; G 826
+U 1020 ; WX 602 ; N uni03FC ; G 827
+U 1021 ; WX 602 ; N uni03FD ; G 828
+U 1022 ; WX 602 ; N uni03FE ; G 829
+U 1023 ; WX 602 ; N uni03FF ; G 830
+U 1024 ; WX 602 ; N uni0400 ; G 831
+U 1025 ; WX 602 ; N uni0401 ; G 832
+U 1026 ; WX 602 ; N uni0402 ; G 833
+U 1027 ; WX 602 ; N uni0403 ; G 834
+U 1028 ; WX 602 ; N uni0404 ; G 835
+U 1029 ; WX 602 ; N uni0405 ; G 836
+U 1030 ; WX 602 ; N uni0406 ; G 837
+U 1031 ; WX 602 ; N uni0407 ; G 838
+U 1032 ; WX 602 ; N uni0408 ; G 839
+U 1033 ; WX 602 ; N uni0409 ; G 840
+U 1034 ; WX 602 ; N uni040A ; G 841
+U 1035 ; WX 602 ; N uni040B ; G 842
+U 1036 ; WX 602 ; N uni040C ; G 843
+U 1037 ; WX 602 ; N uni040D ; G 844
+U 1038 ; WX 602 ; N uni040E ; G 845
+U 1039 ; WX 602 ; N uni040F ; G 846
+U 1040 ; WX 602 ; N uni0410 ; G 847
+U 1041 ; WX 602 ; N uni0411 ; G 848
+U 1042 ; WX 602 ; N uni0412 ; G 849
+U 1043 ; WX 602 ; N uni0413 ; G 850
+U 1044 ; WX 602 ; N uni0414 ; G 851
+U 1045 ; WX 602 ; N uni0415 ; G 852
+U 1046 ; WX 602 ; N uni0416 ; G 853
+U 1047 ; WX 602 ; N uni0417 ; G 854
+U 1048 ; WX 602 ; N uni0418 ; G 855
+U 1049 ; WX 602 ; N uni0419 ; G 856
+U 1050 ; WX 602 ; N uni041A ; G 857
+U 1051 ; WX 602 ; N uni041B ; G 858
+U 1052 ; WX 602 ; N uni041C ; G 859
+U 1053 ; WX 602 ; N uni041D ; G 860
+U 1054 ; WX 602 ; N uni041E ; G 861
+U 1055 ; WX 602 ; N uni041F ; G 862
+U 1056 ; WX 602 ; N uni0420 ; G 863
+U 1057 ; WX 602 ; N uni0421 ; G 864
+U 1058 ; WX 602 ; N uni0422 ; G 865
+U 1059 ; WX 602 ; N uni0423 ; G 866
+U 1060 ; WX 602 ; N uni0424 ; G 867
+U 1061 ; WX 602 ; N uni0425 ; G 868
+U 1062 ; WX 602 ; N uni0426 ; G 869
+U 1063 ; WX 602 ; N uni0427 ; G 870
+U 1064 ; WX 602 ; N uni0428 ; G 871
+U 1065 ; WX 602 ; N uni0429 ; G 872
+U 1066 ; WX 602 ; N uni042A ; G 873
+U 1067 ; WX 602 ; N uni042B ; G 874
+U 1068 ; WX 602 ; N uni042C ; G 875
+U 1069 ; WX 602 ; N uni042D ; G 876
+U 1070 ; WX 602 ; N uni042E ; G 877
+U 1071 ; WX 602 ; N uni042F ; G 878
+U 1072 ; WX 602 ; N uni0430 ; G 879
+U 1073 ; WX 602 ; N uni0431 ; G 880
+U 1074 ; WX 602 ; N uni0432 ; G 881
+U 1075 ; WX 602 ; N uni0433 ; G 882
+U 1076 ; WX 602 ; N uni0434 ; G 883
+U 1077 ; WX 602 ; N uni0435 ; G 884
+U 1078 ; WX 602 ; N uni0436 ; G 885
+U 1079 ; WX 602 ; N uni0437 ; G 886
+U 1080 ; WX 602 ; N uni0438 ; G 887
+U 1081 ; WX 602 ; N uni0439 ; G 888
+U 1082 ; WX 602 ; N uni043A ; G 889
+U 1083 ; WX 602 ; N uni043B ; G 890
+U 1084 ; WX 602 ; N uni043C ; G 891
+U 1085 ; WX 602 ; N uni043D ; G 892
+U 1086 ; WX 602 ; N uni043E ; G 893
+U 1087 ; WX 602 ; N uni043F ; G 894
+U 1088 ; WX 602 ; N uni0440 ; G 895
+U 1089 ; WX 602 ; N uni0441 ; G 896
+U 1090 ; WX 602 ; N uni0442 ; G 897
+U 1091 ; WX 602 ; N uni0443 ; G 898
+U 1092 ; WX 602 ; N uni0444 ; G 899
+U 1093 ; WX 602 ; N uni0445 ; G 900
+U 1094 ; WX 602 ; N uni0446 ; G 901
+U 1095 ; WX 602 ; N uni0447 ; G 902
+U 1096 ; WX 602 ; N uni0448 ; G 903
+U 1097 ; WX 602 ; N uni0449 ; G 904
+U 1098 ; WX 602 ; N uni044A ; G 905
+U 1099 ; WX 602 ; N uni044B ; G 906
+U 1100 ; WX 602 ; N uni044C ; G 907
+U 1101 ; WX 602 ; N uni044D ; G 908
+U 1102 ; WX 602 ; N uni044E ; G 909
+U 1103 ; WX 602 ; N uni044F ; G 910
+U 1104 ; WX 602 ; N uni0450 ; G 911
+U 1105 ; WX 602 ; N uni0451 ; G 912
+U 1106 ; WX 602 ; N uni0452 ; G 913
+U 1107 ; WX 602 ; N uni0453 ; G 914
+U 1108 ; WX 602 ; N uni0454 ; G 915
+U 1109 ; WX 602 ; N uni0455 ; G 916
+U 1110 ; WX 602 ; N uni0456 ; G 917
+U 1111 ; WX 602 ; N uni0457 ; G 918
+U 1112 ; WX 602 ; N uni0458 ; G 919
+U 1113 ; WX 602 ; N uni0459 ; G 920
+U 1114 ; WX 602 ; N uni045A ; G 921
+U 1115 ; WX 602 ; N uni045B ; G 922
+U 1116 ; WX 602 ; N uni045C ; G 923
+U 1117 ; WX 602 ; N uni045D ; G 924
+U 1118 ; WX 602 ; N uni045E ; G 925
+U 1119 ; WX 602 ; N uni045F ; G 926
+U 1122 ; WX 602 ; N uni0462 ; G 927
+U 1123 ; WX 602 ; N uni0463 ; G 928
+U 1138 ; WX 602 ; N uni0472 ; G 929
+U 1139 ; WX 602 ; N uni0473 ; G 930
+U 1168 ; WX 602 ; N uni0490 ; G 931
+U 1169 ; WX 602 ; N uni0491 ; G 932
+U 1170 ; WX 602 ; N uni0492 ; G 933
+U 1171 ; WX 602 ; N uni0493 ; G 934
+U 1172 ; WX 602 ; N uni0494 ; G 935
+U 1173 ; WX 602 ; N uni0495 ; G 936
+U 1174 ; WX 602 ; N uni0496 ; G 937
+U 1175 ; WX 602 ; N uni0497 ; G 938
+U 1176 ; WX 602 ; N uni0498 ; G 939
+U 1177 ; WX 602 ; N uni0499 ; G 940
+U 1178 ; WX 602 ; N uni049A ; G 941
+U 1179 ; WX 602 ; N uni049B ; G 942
+U 1186 ; WX 602 ; N uni04A2 ; G 943
+U 1187 ; WX 602 ; N uni04A3 ; G 944
+U 1188 ; WX 602 ; N uni04A4 ; G 945
+U 1189 ; WX 602 ; N uni04A5 ; G 946
+U 1194 ; WX 602 ; N uni04AA ; G 947
+U 1195 ; WX 602 ; N uni04AB ; G 948
+U 1196 ; WX 602 ; N uni04AC ; G 949
+U 1197 ; WX 602 ; N uni04AD ; G 950
+U 1198 ; WX 602 ; N uni04AE ; G 951
+U 1199 ; WX 602 ; N uni04AF ; G 952
+U 1200 ; WX 602 ; N uni04B0 ; G 953
+U 1201 ; WX 602 ; N uni04B1 ; G 954
+U 1202 ; WX 602 ; N uni04B2 ; G 955
+U 1203 ; WX 602 ; N uni04B3 ; G 956
+U 1210 ; WX 602 ; N uni04BA ; G 957
+U 1211 ; WX 602 ; N uni04BB ; G 958
+U 1216 ; WX 602 ; N uni04C0 ; G 959
+U 1217 ; WX 602 ; N uni04C1 ; G 960
+U 1218 ; WX 602 ; N uni04C2 ; G 961
+U 1219 ; WX 602 ; N uni04C3 ; G 962
+U 1220 ; WX 602 ; N uni04C4 ; G 963
+U 1223 ; WX 602 ; N uni04C7 ; G 964
+U 1224 ; WX 602 ; N uni04C8 ; G 965
+U 1227 ; WX 602 ; N uni04CB ; G 966
+U 1228 ; WX 602 ; N uni04CC ; G 967
+U 1231 ; WX 602 ; N uni04CF ; G 968
+U 1232 ; WX 602 ; N uni04D0 ; G 969
+U 1233 ; WX 602 ; N uni04D1 ; G 970
+U 1234 ; WX 602 ; N uni04D2 ; G 971
+U 1235 ; WX 602 ; N uni04D3 ; G 972
+U 1236 ; WX 602 ; N uni04D4 ; G 973
+U 1237 ; WX 602 ; N uni04D5 ; G 974
+U 1238 ; WX 602 ; N uni04D6 ; G 975
+U 1239 ; WX 602 ; N uni04D7 ; G 976
+U 1240 ; WX 602 ; N uni04D8 ; G 977
+U 1241 ; WX 602 ; N uni04D9 ; G 978
+U 1242 ; WX 602 ; N uni04DA ; G 979
+U 1243 ; WX 602 ; N uni04DB ; G 980
+U 1244 ; WX 602 ; N uni04DC ; G 981
+U 1245 ; WX 602 ; N uni04DD ; G 982
+U 1246 ; WX 602 ; N uni04DE ; G 983
+U 1247 ; WX 602 ; N uni04DF ; G 984
+U 1248 ; WX 602 ; N uni04E0 ; G 985
+U 1249 ; WX 602 ; N uni04E1 ; G 986
+U 1250 ; WX 602 ; N uni04E2 ; G 987
+U 1251 ; WX 602 ; N uni04E3 ; G 988
+U 1252 ; WX 602 ; N uni04E4 ; G 989
+U 1253 ; WX 602 ; N uni04E5 ; G 990
+U 1254 ; WX 602 ; N uni04E6 ; G 991
+U 1255 ; WX 602 ; N uni04E7 ; G 992
+U 1256 ; WX 602 ; N uni04E8 ; G 993
+U 1257 ; WX 602 ; N uni04E9 ; G 994
+U 1258 ; WX 602 ; N uni04EA ; G 995
+U 1259 ; WX 602 ; N uni04EB ; G 996
+U 1260 ; WX 602 ; N uni04EC ; G 997
+U 1261 ; WX 602 ; N uni04ED ; G 998
+U 1262 ; WX 602 ; N uni04EE ; G 999
+U 1263 ; WX 602 ; N uni04EF ; G 1000
+U 1264 ; WX 602 ; N uni04F0 ; G 1001
+U 1265 ; WX 602 ; N uni04F1 ; G 1002
+U 1266 ; WX 602 ; N uni04F2 ; G 1003
+U 1267 ; WX 602 ; N uni04F3 ; G 1004
+U 1268 ; WX 602 ; N uni04F4 ; G 1005
+U 1269 ; WX 602 ; N uni04F5 ; G 1006
+U 1270 ; WX 602 ; N uni04F6 ; G 1007
+U 1271 ; WX 602 ; N uni04F7 ; G 1008
+U 1272 ; WX 602 ; N uni04F8 ; G 1009
+U 1273 ; WX 602 ; N uni04F9 ; G 1010
+U 1296 ; WX 602 ; N uni0510 ; G 1011
+U 1297 ; WX 602 ; N uni0511 ; G 1012
+U 1306 ; WX 602 ; N uni051A ; G 1013
+U 1307 ; WX 602 ; N uni051B ; G 1014
+U 1308 ; WX 602 ; N uni051C ; G 1015
+U 1309 ; WX 602 ; N uni051D ; G 1016
+U 1329 ; WX 602 ; N uni0531 ; G 1017
+U 1330 ; WX 602 ; N uni0532 ; G 1018
+U 1331 ; WX 602 ; N uni0533 ; G 1019
+U 1332 ; WX 602 ; N uni0534 ; G 1020
+U 1333 ; WX 602 ; N uni0535 ; G 1021
+U 1334 ; WX 602 ; N uni0536 ; G 1022
+U 1335 ; WX 602 ; N uni0537 ; G 1023
+U 1336 ; WX 602 ; N uni0538 ; G 1024
+U 1337 ; WX 602 ; N uni0539 ; G 1025
+U 1338 ; WX 602 ; N uni053A ; G 1026
+U 1339 ; WX 602 ; N uni053B ; G 1027
+U 1340 ; WX 602 ; N uni053C ; G 1028
+U 1341 ; WX 602 ; N uni053D ; G 1029
+U 1342 ; WX 602 ; N uni053E ; G 1030
+U 1343 ; WX 602 ; N uni053F ; G 1031
+U 1344 ; WX 602 ; N uni0540 ; G 1032
+U 1345 ; WX 602 ; N uni0541 ; G 1033
+U 1346 ; WX 602 ; N uni0542 ; G 1034
+U 1347 ; WX 602 ; N uni0543 ; G 1035
+U 1348 ; WX 602 ; N uni0544 ; G 1036
+U 1349 ; WX 602 ; N uni0545 ; G 1037
+U 1350 ; WX 602 ; N uni0546 ; G 1038
+U 1351 ; WX 602 ; N uni0547 ; G 1039
+U 1352 ; WX 602 ; N uni0548 ; G 1040
+U 1353 ; WX 602 ; N uni0549 ; G 1041
+U 1354 ; WX 602 ; N uni054A ; G 1042
+U 1355 ; WX 602 ; N uni054B ; G 1043
+U 1356 ; WX 602 ; N uni054C ; G 1044
+U 1357 ; WX 602 ; N uni054D ; G 1045
+U 1358 ; WX 602 ; N uni054E ; G 1046
+U 1359 ; WX 602 ; N uni054F ; G 1047
+U 1360 ; WX 602 ; N uni0550 ; G 1048
+U 1361 ; WX 602 ; N uni0551 ; G 1049
+U 1362 ; WX 602 ; N uni0552 ; G 1050
+U 1363 ; WX 602 ; N uni0553 ; G 1051
+U 1364 ; WX 602 ; N uni0554 ; G 1052
+U 1365 ; WX 602 ; N uni0555 ; G 1053
+U 1366 ; WX 602 ; N uni0556 ; G 1054
+U 1369 ; WX 602 ; N uni0559 ; G 1055
+U 1370 ; WX 602 ; N uni055A ; G 1056
+U 1371 ; WX 602 ; N uni055B ; G 1057
+U 1372 ; WX 602 ; N uni055C ; G 1058
+U 1373 ; WX 602 ; N uni055D ; G 1059
+U 1374 ; WX 602 ; N uni055E ; G 1060
+U 1375 ; WX 602 ; N uni055F ; G 1061
+U 1377 ; WX 602 ; N uni0561 ; G 1062
+U 1378 ; WX 602 ; N uni0562 ; G 1063
+U 1379 ; WX 602 ; N uni0563 ; G 1064
+U 1380 ; WX 602 ; N uni0564 ; G 1065
+U 1381 ; WX 602 ; N uni0565 ; G 1066
+U 1382 ; WX 602 ; N uni0566 ; G 1067
+U 1383 ; WX 602 ; N uni0567 ; G 1068
+U 1384 ; WX 602 ; N uni0568 ; G 1069
+U 1385 ; WX 602 ; N uni0569 ; G 1070
+U 1386 ; WX 602 ; N uni056A ; G 1071
+U 1387 ; WX 602 ; N uni056B ; G 1072
+U 1388 ; WX 602 ; N uni056C ; G 1073
+U 1389 ; WX 602 ; N uni056D ; G 1074
+U 1390 ; WX 602 ; N uni056E ; G 1075
+U 1391 ; WX 602 ; N uni056F ; G 1076
+U 1392 ; WX 602 ; N uni0570 ; G 1077
+U 1393 ; WX 602 ; N uni0571 ; G 1078
+U 1394 ; WX 602 ; N uni0572 ; G 1079
+U 1395 ; WX 602 ; N uni0573 ; G 1080
+U 1396 ; WX 602 ; N uni0574 ; G 1081
+U 1397 ; WX 602 ; N uni0575 ; G 1082
+U 1398 ; WX 602 ; N uni0576 ; G 1083
+U 1399 ; WX 602 ; N uni0577 ; G 1084
+U 1400 ; WX 602 ; N uni0578 ; G 1085
+U 1401 ; WX 602 ; N uni0579 ; G 1086
+U 1402 ; WX 602 ; N uni057A ; G 1087
+U 1403 ; WX 602 ; N uni057B ; G 1088
+U 1404 ; WX 602 ; N uni057C ; G 1089
+U 1405 ; WX 602 ; N uni057D ; G 1090
+U 1406 ; WX 602 ; N uni057E ; G 1091
+U 1407 ; WX 602 ; N uni057F ; G 1092
+U 1408 ; WX 602 ; N uni0580 ; G 1093
+U 1409 ; WX 602 ; N uni0581 ; G 1094
+U 1410 ; WX 602 ; N uni0582 ; G 1095
+U 1411 ; WX 602 ; N uni0583 ; G 1096
+U 1412 ; WX 602 ; N uni0584 ; G 1097
+U 1413 ; WX 602 ; N uni0585 ; G 1098
+U 1414 ; WX 602 ; N uni0586 ; G 1099
+U 1415 ; WX 602 ; N uni0587 ; G 1100
+U 1417 ; WX 602 ; N uni0589 ; G 1101
+U 1418 ; WX 602 ; N uni058A ; G 1102
+U 1542 ; WX 602 ; N uni0606 ; G 1103
+U 1543 ; WX 602 ; N uni0607 ; G 1104
+U 1545 ; WX 602 ; N uni0609 ; G 1105
+U 1546 ; WX 602 ; N uni060A ; G 1106
+U 1548 ; WX 602 ; N uni060C ; G 1107
+U 1557 ; WX 602 ; N uni0615 ; G 1108
+U 1563 ; WX 602 ; N uni061B ; G 1109
+U 1567 ; WX 602 ; N uni061F ; G 1110
+U 1569 ; WX 602 ; N uni0621 ; G 1111
+U 1570 ; WX 602 ; N uni0622 ; G 1112
+U 1571 ; WX 602 ; N uni0623 ; G 1113
+U 1572 ; WX 602 ; N uni0624 ; G 1114
+U 1573 ; WX 602 ; N uni0625 ; G 1115
+U 1574 ; WX 602 ; N uni0626 ; G 1116
+U 1575 ; WX 602 ; N uni0627 ; G 1117
+U 1576 ; WX 602 ; N uni0628 ; G 1118
+U 1577 ; WX 602 ; N uni0629 ; G 1119
+U 1578 ; WX 602 ; N uni062A ; G 1120
+U 1579 ; WX 602 ; N uni062B ; G 1121
+U 1580 ; WX 602 ; N uni062C ; G 1122
+U 1581 ; WX 602 ; N uni062D ; G 1123
+U 1582 ; WX 602 ; N uni062E ; G 1124
+U 1583 ; WX 602 ; N uni062F ; G 1125
+U 1584 ; WX 602 ; N uni0630 ; G 1126
+U 1585 ; WX 602 ; N uni0631 ; G 1127
+U 1586 ; WX 602 ; N uni0632 ; G 1128
+U 1587 ; WX 602 ; N uni0633 ; G 1129
+U 1588 ; WX 602 ; N uni0634 ; G 1130
+U 1589 ; WX 602 ; N uni0635 ; G 1131
+U 1590 ; WX 602 ; N uni0636 ; G 1132
+U 1591 ; WX 602 ; N uni0637 ; G 1133
+U 1592 ; WX 602 ; N uni0638 ; G 1134
+U 1593 ; WX 602 ; N uni0639 ; G 1135
+U 1594 ; WX 602 ; N uni063A ; G 1136
+U 1600 ; WX 602 ; N uni0640 ; G 1137
+U 1601 ; WX 602 ; N uni0641 ; G 1138
+U 1602 ; WX 602 ; N uni0642 ; G 1139
+U 1603 ; WX 602 ; N uni0643 ; G 1140
+U 1604 ; WX 602 ; N uni0644 ; G 1141
+U 1605 ; WX 602 ; N uni0645 ; G 1142
+U 1606 ; WX 602 ; N uni0646 ; G 1143
+U 1607 ; WX 602 ; N uni0647 ; G 1144
+U 1608 ; WX 602 ; N uni0648 ; G 1145
+U 1609 ; WX 602 ; N uni0649 ; G 1146
+U 1610 ; WX 602 ; N uni064A ; G 1147
+U 1611 ; WX 602 ; N uni064B ; G 1148
+U 1612 ; WX 602 ; N uni064C ; G 1149
+U 1613 ; WX 602 ; N uni064D ; G 1150
+U 1614 ; WX 602 ; N uni064E ; G 1151
+U 1615 ; WX 602 ; N uni064F ; G 1152
+U 1616 ; WX 602 ; N uni0650 ; G 1153
+U 1617 ; WX 602 ; N uni0651 ; G 1154
+U 1618 ; WX 602 ; N uni0652 ; G 1155
+U 1619 ; WX 602 ; N uni0653 ; G 1156
+U 1620 ; WX 602 ; N uni0654 ; G 1157
+U 1621 ; WX 602 ; N uni0655 ; G 1158
+U 1626 ; WX 602 ; N uni065A ; G 1159
+U 1632 ; WX 602 ; N uni0660 ; G 1160
+U 1633 ; WX 602 ; N uni0661 ; G 1161
+U 1634 ; WX 602 ; N uni0662 ; G 1162
+U 1635 ; WX 602 ; N uni0663 ; G 1163
+U 1636 ; WX 602 ; N uni0664 ; G 1164
+U 1637 ; WX 602 ; N uni0665 ; G 1165
+U 1638 ; WX 602 ; N uni0666 ; G 1166
+U 1639 ; WX 602 ; N uni0667 ; G 1167
+U 1640 ; WX 602 ; N uni0668 ; G 1168
+U 1641 ; WX 602 ; N uni0669 ; G 1169
+U 1642 ; WX 602 ; N uni066A ; G 1170
+U 1643 ; WX 602 ; N uni066B ; G 1171
+U 1644 ; WX 602 ; N uni066C ; G 1172
+U 1645 ; WX 602 ; N uni066D ; G 1173
+U 1652 ; WX 602 ; N uni0674 ; G 1174
+U 1657 ; WX 602 ; N uni0679 ; G 1175
+U 1658 ; WX 602 ; N uni067A ; G 1176
+U 1659 ; WX 602 ; N uni067B ; G 1177
+U 1662 ; WX 602 ; N uni067E ; G 1178
+U 1663 ; WX 602 ; N uni067F ; G 1179
+U 1664 ; WX 602 ; N uni0680 ; G 1180
+U 1667 ; WX 602 ; N uni0683 ; G 1181
+U 1668 ; WX 602 ; N uni0684 ; G 1182
+U 1670 ; WX 602 ; N uni0686 ; G 1183
+U 1671 ; WX 602 ; N uni0687 ; G 1184
+U 1681 ; WX 602 ; N uni0691 ; G 1185
+U 1688 ; WX 602 ; N uni0698 ; G 1186
+U 1700 ; WX 602 ; N uni06A4 ; G 1187
+U 1705 ; WX 602 ; N uni06A9 ; G 1188
+U 1711 ; WX 602 ; N uni06AF ; G 1189
+U 1726 ; WX 602 ; N uni06BE ; G 1190
+U 1740 ; WX 602 ; N uni06CC ; G 1191
+U 1776 ; WX 602 ; N uni06F0 ; G 1192
+U 1777 ; WX 602 ; N uni06F1 ; G 1193
+U 1778 ; WX 602 ; N uni06F2 ; G 1194
+U 1779 ; WX 602 ; N uni06F3 ; G 1195
+U 1780 ; WX 602 ; N uni06F4 ; G 1196
+U 1781 ; WX 602 ; N uni06F5 ; G 1197
+U 1782 ; WX 602 ; N uni06F6 ; G 1198
+U 1783 ; WX 602 ; N uni06F7 ; G 1199
+U 1784 ; WX 602 ; N uni06F8 ; G 1200
+U 1785 ; WX 602 ; N uni06F9 ; G 1201
+U 3647 ; WX 602 ; N uni0E3F ; G 1202
+U 3713 ; WX 602 ; N uni0E81 ; G 1203
+U 3714 ; WX 602 ; N uni0E82 ; G 1204
+U 3716 ; WX 602 ; N uni0E84 ; G 1205
+U 3719 ; WX 602 ; N uni0E87 ; G 1206
+U 3720 ; WX 602 ; N uni0E88 ; G 1207
+U 3722 ; WX 602 ; N uni0E8A ; G 1208
+U 3725 ; WX 602 ; N uni0E8D ; G 1209
+U 3732 ; WX 602 ; N uni0E94 ; G 1210
+U 3733 ; WX 602 ; N uni0E95 ; G 1211
+U 3734 ; WX 602 ; N uni0E96 ; G 1212
+U 3735 ; WX 602 ; N uni0E97 ; G 1213
+U 3737 ; WX 602 ; N uni0E99 ; G 1214
+U 3738 ; WX 602 ; N uni0E9A ; G 1215
+U 3739 ; WX 602 ; N uni0E9B ; G 1216
+U 3740 ; WX 602 ; N uni0E9C ; G 1217
+U 3741 ; WX 602 ; N uni0E9D ; G 1218
+U 3742 ; WX 602 ; N uni0E9E ; G 1219
+U 3743 ; WX 602 ; N uni0E9F ; G 1220
+U 3745 ; WX 602 ; N uni0EA1 ; G 1221
+U 3746 ; WX 602 ; N uni0EA2 ; G 1222
+U 3747 ; WX 602 ; N uni0EA3 ; G 1223
+U 3749 ; WX 602 ; N uni0EA5 ; G 1224
+U 3751 ; WX 602 ; N uni0EA7 ; G 1225
+U 3754 ; WX 602 ; N uni0EAA ; G 1226
+U 3755 ; WX 602 ; N uni0EAB ; G 1227
+U 3757 ; WX 602 ; N uni0EAD ; G 1228
+U 3758 ; WX 602 ; N uni0EAE ; G 1229
+U 3759 ; WX 602 ; N uni0EAF ; G 1230
+U 3760 ; WX 602 ; N uni0EB0 ; G 1231
+U 3761 ; WX 602 ; N uni0EB1 ; G 1232
+U 3762 ; WX 602 ; N uni0EB2 ; G 1233
+U 3763 ; WX 602 ; N uni0EB3 ; G 1234
+U 3764 ; WX 602 ; N uni0EB4 ; G 1235
+U 3765 ; WX 602 ; N uni0EB5 ; G 1236
+U 3766 ; WX 602 ; N uni0EB6 ; G 1237
+U 3767 ; WX 602 ; N uni0EB7 ; G 1238
+U 3768 ; WX 602 ; N uni0EB8 ; G 1239
+U 3769 ; WX 602 ; N uni0EB9 ; G 1240
+U 3771 ; WX 602 ; N uni0EBB ; G 1241
+U 3772 ; WX 602 ; N uni0EBC ; G 1242
+U 3784 ; WX 602 ; N uni0EC8 ; G 1243
+U 3785 ; WX 602 ; N uni0EC9 ; G 1244
+U 3786 ; WX 602 ; N uni0ECA ; G 1245
+U 3787 ; WX 602 ; N uni0ECB ; G 1246
+U 3788 ; WX 602 ; N uni0ECC ; G 1247
+U 3789 ; WX 602 ; N uni0ECD ; G 1248
+U 4304 ; WX 602 ; N uni10D0 ; G 1249
+U 4305 ; WX 602 ; N uni10D1 ; G 1250
+U 4306 ; WX 602 ; N uni10D2 ; G 1251
+U 4307 ; WX 602 ; N uni10D3 ; G 1252
+U 4308 ; WX 602 ; N uni10D4 ; G 1253
+U 4309 ; WX 602 ; N uni10D5 ; G 1254
+U 4310 ; WX 602 ; N uni10D6 ; G 1255
+U 4311 ; WX 602 ; N uni10D7 ; G 1256
+U 4312 ; WX 602 ; N uni10D8 ; G 1257
+U 4313 ; WX 602 ; N uni10D9 ; G 1258
+U 4314 ; WX 602 ; N uni10DA ; G 1259
+U 4315 ; WX 602 ; N uni10DB ; G 1260
+U 4316 ; WX 602 ; N uni10DC ; G 1261
+U 4317 ; WX 602 ; N uni10DD ; G 1262
+U 4318 ; WX 602 ; N uni10DE ; G 1263
+U 4319 ; WX 602 ; N uni10DF ; G 1264
+U 4320 ; WX 602 ; N uni10E0 ; G 1265
+U 4321 ; WX 602 ; N uni10E1 ; G 1266
+U 4322 ; WX 602 ; N uni10E2 ; G 1267
+U 4323 ; WX 602 ; N uni10E3 ; G 1268
+U 4324 ; WX 602 ; N uni10E4 ; G 1269
+U 4325 ; WX 602 ; N uni10E5 ; G 1270
+U 4326 ; WX 602 ; N uni10E6 ; G 1271
+U 4327 ; WX 602 ; N uni10E7 ; G 1272
+U 4328 ; WX 602 ; N uni10E8 ; G 1273
+U 4329 ; WX 602 ; N uni10E9 ; G 1274
+U 4330 ; WX 602 ; N uni10EA ; G 1275
+U 4331 ; WX 602 ; N uni10EB ; G 1276
+U 4332 ; WX 602 ; N uni10EC ; G 1277
+U 4333 ; WX 602 ; N uni10ED ; G 1278
+U 4334 ; WX 602 ; N uni10EE ; G 1279
+U 4335 ; WX 602 ; N uni10EF ; G 1280
+U 4336 ; WX 602 ; N uni10F0 ; G 1281
+U 4337 ; WX 602 ; N uni10F1 ; G 1282
+U 4338 ; WX 602 ; N uni10F2 ; G 1283
+U 4339 ; WX 602 ; N uni10F3 ; G 1284
+U 4340 ; WX 602 ; N uni10F4 ; G 1285
+U 4341 ; WX 602 ; N uni10F5 ; G 1286
+U 4342 ; WX 602 ; N uni10F6 ; G 1287
+U 4343 ; WX 602 ; N uni10F7 ; G 1288
+U 4344 ; WX 602 ; N uni10F8 ; G 1289
+U 4345 ; WX 602 ; N uni10F9 ; G 1290
+U 4346 ; WX 602 ; N uni10FA ; G 1291
+U 4347 ; WX 602 ; N uni10FB ; G 1292
+U 4348 ; WX 602 ; N uni10FC ; G 1293
+U 7426 ; WX 602 ; N uni1D02 ; G 1294
+U 7432 ; WX 602 ; N uni1D08 ; G 1295
+U 7433 ; WX 602 ; N uni1D09 ; G 1296
+U 7444 ; WX 602 ; N uni1D14 ; G 1297
+U 7446 ; WX 602 ; N uni1D16 ; G 1298
+U 7447 ; WX 602 ; N uni1D17 ; G 1299
+U 7453 ; WX 602 ; N uni1D1D ; G 1300
+U 7454 ; WX 602 ; N uni1D1E ; G 1301
+U 7455 ; WX 602 ; N uni1D1F ; G 1302
+U 7468 ; WX 602 ; N uni1D2C ; G 1303
+U 7469 ; WX 602 ; N uni1D2D ; G 1304
+U 7470 ; WX 602 ; N uni1D2E ; G 1305
+U 7472 ; WX 602 ; N uni1D30 ; G 1306
+U 7473 ; WX 602 ; N uni1D31 ; G 1307
+U 7474 ; WX 602 ; N uni1D32 ; G 1308
+U 7475 ; WX 602 ; N uni1D33 ; G 1309
+U 7476 ; WX 602 ; N uni1D34 ; G 1310
+U 7477 ; WX 602 ; N uni1D35 ; G 1311
+U 7478 ; WX 602 ; N uni1D36 ; G 1312
+U 7479 ; WX 602 ; N uni1D37 ; G 1313
+U 7480 ; WX 602 ; N uni1D38 ; G 1314
+U 7481 ; WX 602 ; N uni1D39 ; G 1315
+U 7482 ; WX 602 ; N uni1D3A ; G 1316
+U 7483 ; WX 602 ; N uni1D3B ; G 1317
+U 7484 ; WX 602 ; N uni1D3C ; G 1318
+U 7486 ; WX 602 ; N uni1D3E ; G 1319
+U 7487 ; WX 602 ; N uni1D3F ; G 1320
+U 7488 ; WX 602 ; N uni1D40 ; G 1321
+U 7489 ; WX 602 ; N uni1D41 ; G 1322
+U 7490 ; WX 602 ; N uni1D42 ; G 1323
+U 7491 ; WX 602 ; N uni1D43 ; G 1324
+U 7492 ; WX 602 ; N uni1D44 ; G 1325
+U 7493 ; WX 602 ; N uni1D45 ; G 1326
+U 7494 ; WX 602 ; N uni1D46 ; G 1327
+U 7495 ; WX 602 ; N uni1D47 ; G 1328
+U 7496 ; WX 602 ; N uni1D48 ; G 1329
+U 7497 ; WX 602 ; N uni1D49 ; G 1330
+U 7498 ; WX 602 ; N uni1D4A ; G 1331
+U 7499 ; WX 602 ; N uni1D4B ; G 1332
+U 7500 ; WX 602 ; N uni1D4C ; G 1333
+U 7501 ; WX 602 ; N uni1D4D ; G 1334
+U 7502 ; WX 602 ; N uni1D4E ; G 1335
+U 7503 ; WX 602 ; N uni1D4F ; G 1336
+U 7504 ; WX 602 ; N uni1D50 ; G 1337
+U 7505 ; WX 602 ; N uni1D51 ; G 1338
+U 7506 ; WX 602 ; N uni1D52 ; G 1339
+U 7507 ; WX 602 ; N uni1D53 ; G 1340
+U 7508 ; WX 602 ; N uni1D54 ; G 1341
+U 7509 ; WX 602 ; N uni1D55 ; G 1342
+U 7510 ; WX 602 ; N uni1D56 ; G 1343
+U 7511 ; WX 602 ; N uni1D57 ; G 1344
+U 7512 ; WX 602 ; N uni1D58 ; G 1345
+U 7513 ; WX 602 ; N uni1D59 ; G 1346
+U 7514 ; WX 602 ; N uni1D5A ; G 1347
+U 7515 ; WX 602 ; N uni1D5B ; G 1348
+U 7522 ; WX 602 ; N uni1D62 ; G 1349
+U 7523 ; WX 602 ; N uni1D63 ; G 1350
+U 7524 ; WX 602 ; N uni1D64 ; G 1351
+U 7525 ; WX 602 ; N uni1D65 ; G 1352
+U 7543 ; WX 602 ; N uni1D77 ; G 1353
+U 7544 ; WX 602 ; N uni1D78 ; G 1354
+U 7547 ; WX 602 ; N uni1D7B ; G 1355
+U 7557 ; WX 602 ; N uni1D85 ; G 1356
+U 7579 ; WX 602 ; N uni1D9B ; G 1357
+U 7580 ; WX 602 ; N uni1D9C ; G 1358
+U 7581 ; WX 602 ; N uni1D9D ; G 1359
+U 7582 ; WX 602 ; N uni1D9E ; G 1360
+U 7583 ; WX 602 ; N uni1D9F ; G 1361
+U 7584 ; WX 602 ; N uni1DA0 ; G 1362
+U 7585 ; WX 602 ; N uni1DA1 ; G 1363
+U 7586 ; WX 602 ; N uni1DA2 ; G 1364
+U 7587 ; WX 602 ; N uni1DA3 ; G 1365
+U 7588 ; WX 602 ; N uni1DA4 ; G 1366
+U 7589 ; WX 602 ; N uni1DA5 ; G 1367
+U 7590 ; WX 602 ; N uni1DA6 ; G 1368
+U 7591 ; WX 602 ; N uni1DA7 ; G 1369
+U 7592 ; WX 602 ; N uni1DA8 ; G 1370
+U 7593 ; WX 602 ; N uni1DA9 ; G 1371
+U 7594 ; WX 602 ; N uni1DAA ; G 1372
+U 7595 ; WX 602 ; N uni1DAB ; G 1373
+U 7596 ; WX 602 ; N uni1DAC ; G 1374
+U 7597 ; WX 602 ; N uni1DAD ; G 1375
+U 7598 ; WX 602 ; N uni1DAE ; G 1376
+U 7599 ; WX 602 ; N uni1DAF ; G 1377
+U 7600 ; WX 602 ; N uni1DB0 ; G 1378
+U 7601 ; WX 602 ; N uni1DB1 ; G 1379
+U 7602 ; WX 602 ; N uni1DB2 ; G 1380
+U 7603 ; WX 602 ; N uni1DB3 ; G 1381
+U 7604 ; WX 602 ; N uni1DB4 ; G 1382
+U 7605 ; WX 602 ; N uni1DB5 ; G 1383
+U 7606 ; WX 602 ; N uni1DB6 ; G 1384
+U 7607 ; WX 602 ; N uni1DB7 ; G 1385
+U 7609 ; WX 602 ; N uni1DB9 ; G 1386
+U 7610 ; WX 602 ; N uni1DBA ; G 1387
+U 7611 ; WX 602 ; N uni1DBB ; G 1388
+U 7612 ; WX 602 ; N uni1DBC ; G 1389
+U 7613 ; WX 602 ; N uni1DBD ; G 1390
+U 7614 ; WX 602 ; N uni1DBE ; G 1391
+U 7615 ; WX 602 ; N uni1DBF ; G 1392
+U 7680 ; WX 602 ; N uni1E00 ; G 1393
+U 7681 ; WX 602 ; N uni1E01 ; G 1394
+U 7682 ; WX 602 ; N uni1E02 ; G 1395
+U 7683 ; WX 602 ; N uni1E03 ; G 1396
+U 7684 ; WX 602 ; N uni1E04 ; G 1397
+U 7685 ; WX 602 ; N uni1E05 ; G 1398
+U 7686 ; WX 602 ; N uni1E06 ; G 1399
+U 7687 ; WX 602 ; N uni1E07 ; G 1400
+U 7688 ; WX 602 ; N uni1E08 ; G 1401
+U 7689 ; WX 602 ; N uni1E09 ; G 1402
+U 7690 ; WX 602 ; N uni1E0A ; G 1403
+U 7691 ; WX 602 ; N uni1E0B ; G 1404
+U 7692 ; WX 602 ; N uni1E0C ; G 1405
+U 7693 ; WX 602 ; N uni1E0D ; G 1406
+U 7694 ; WX 602 ; N uni1E0E ; G 1407
+U 7695 ; WX 602 ; N uni1E0F ; G 1408
+U 7696 ; WX 602 ; N uni1E10 ; G 1409
+U 7697 ; WX 602 ; N uni1E11 ; G 1410
+U 7698 ; WX 602 ; N uni1E12 ; G 1411
+U 7699 ; WX 602 ; N uni1E13 ; G 1412
+U 7704 ; WX 602 ; N uni1E18 ; G 1413
+U 7705 ; WX 602 ; N uni1E19 ; G 1414
+U 7706 ; WX 602 ; N uni1E1A ; G 1415
+U 7707 ; WX 602 ; N uni1E1B ; G 1416
+U 7708 ; WX 602 ; N uni1E1C ; G 1417
+U 7709 ; WX 602 ; N uni1E1D ; G 1418
+U 7710 ; WX 602 ; N uni1E1E ; G 1419
+U 7711 ; WX 602 ; N uni1E1F ; G 1420
+U 7712 ; WX 602 ; N uni1E20 ; G 1421
+U 7713 ; WX 602 ; N uni1E21 ; G 1422
+U 7714 ; WX 602 ; N uni1E22 ; G 1423
+U 7715 ; WX 602 ; N uni1E23 ; G 1424
+U 7716 ; WX 602 ; N uni1E24 ; G 1425
+U 7717 ; WX 602 ; N uni1E25 ; G 1426
+U 7718 ; WX 602 ; N uni1E26 ; G 1427
+U 7719 ; WX 602 ; N uni1E27 ; G 1428
+U 7720 ; WX 602 ; N uni1E28 ; G 1429
+U 7721 ; WX 602 ; N uni1E29 ; G 1430
+U 7722 ; WX 602 ; N uni1E2A ; G 1431
+U 7723 ; WX 602 ; N uni1E2B ; G 1432
+U 7724 ; WX 602 ; N uni1E2C ; G 1433
+U 7725 ; WX 602 ; N uni1E2D ; G 1434
+U 7728 ; WX 602 ; N uni1E30 ; G 1435
+U 7729 ; WX 602 ; N uni1E31 ; G 1436
+U 7730 ; WX 602 ; N uni1E32 ; G 1437
+U 7731 ; WX 602 ; N uni1E33 ; G 1438
+U 7732 ; WX 602 ; N uni1E34 ; G 1439
+U 7733 ; WX 602 ; N uni1E35 ; G 1440
+U 7734 ; WX 602 ; N uni1E36 ; G 1441
+U 7735 ; WX 602 ; N uni1E37 ; G 1442
+U 7736 ; WX 602 ; N uni1E38 ; G 1443
+U 7737 ; WX 602 ; N uni1E39 ; G 1444
+U 7738 ; WX 602 ; N uni1E3A ; G 1445
+U 7739 ; WX 602 ; N uni1E3B ; G 1446
+U 7740 ; WX 602 ; N uni1E3C ; G 1447
+U 7741 ; WX 602 ; N uni1E3D ; G 1448
+U 7742 ; WX 602 ; N uni1E3E ; G 1449
+U 7743 ; WX 602 ; N uni1E3F ; G 1450
+U 7744 ; WX 602 ; N uni1E40 ; G 1451
+U 7745 ; WX 602 ; N uni1E41 ; G 1452
+U 7746 ; WX 602 ; N uni1E42 ; G 1453
+U 7747 ; WX 602 ; N uni1E43 ; G 1454
+U 7748 ; WX 602 ; N uni1E44 ; G 1455
+U 7749 ; WX 602 ; N uni1E45 ; G 1456
+U 7750 ; WX 602 ; N uni1E46 ; G 1457
+U 7751 ; WX 602 ; N uni1E47 ; G 1458
+U 7752 ; WX 602 ; N uni1E48 ; G 1459
+U 7753 ; WX 602 ; N uni1E49 ; G 1460
+U 7754 ; WX 602 ; N uni1E4A ; G 1461
+U 7755 ; WX 602 ; N uni1E4B ; G 1462
+U 7756 ; WX 602 ; N uni1E4C ; G 1463
+U 7757 ; WX 602 ; N uni1E4D ; G 1464
+U 7764 ; WX 602 ; N uni1E54 ; G 1465
+U 7765 ; WX 602 ; N uni1E55 ; G 1466
+U 7766 ; WX 602 ; N uni1E56 ; G 1467
+U 7767 ; WX 602 ; N uni1E57 ; G 1468
+U 7768 ; WX 602 ; N uni1E58 ; G 1469
+U 7769 ; WX 602 ; N uni1E59 ; G 1470
+U 7770 ; WX 602 ; N uni1E5A ; G 1471
+U 7771 ; WX 602 ; N uni1E5B ; G 1472
+U 7772 ; WX 602 ; N uni1E5C ; G 1473
+U 7773 ; WX 602 ; N uni1E5D ; G 1474
+U 7774 ; WX 602 ; N uni1E5E ; G 1475
+U 7775 ; WX 602 ; N uni1E5F ; G 1476
+U 7776 ; WX 602 ; N uni1E60 ; G 1477
+U 7777 ; WX 602 ; N uni1E61 ; G 1478
+U 7778 ; WX 602 ; N uni1E62 ; G 1479
+U 7779 ; WX 602 ; N uni1E63 ; G 1480
+U 7784 ; WX 602 ; N uni1E68 ; G 1481
+U 7785 ; WX 602 ; N uni1E69 ; G 1482
+U 7786 ; WX 602 ; N uni1E6A ; G 1483
+U 7787 ; WX 602 ; N uni1E6B ; G 1484
+U 7788 ; WX 602 ; N uni1E6C ; G 1485
+U 7789 ; WX 602 ; N uni1E6D ; G 1486
+U 7790 ; WX 602 ; N uni1E6E ; G 1487
+U 7791 ; WX 602 ; N uni1E6F ; G 1488
+U 7792 ; WX 602 ; N uni1E70 ; G 1489
+U 7793 ; WX 602 ; N uni1E71 ; G 1490
+U 7794 ; WX 602 ; N uni1E72 ; G 1491
+U 7795 ; WX 602 ; N uni1E73 ; G 1492
+U 7796 ; WX 602 ; N uni1E74 ; G 1493
+U 7797 ; WX 602 ; N uni1E75 ; G 1494
+U 7798 ; WX 602 ; N uni1E76 ; G 1495
+U 7799 ; WX 602 ; N uni1E77 ; G 1496
+U 7800 ; WX 602 ; N uni1E78 ; G 1497
+U 7801 ; WX 602 ; N uni1E79 ; G 1498
+U 7804 ; WX 602 ; N uni1E7C ; G 1499
+U 7805 ; WX 602 ; N uni1E7D ; G 1500
+U 7806 ; WX 602 ; N uni1E7E ; G 1501
+U 7807 ; WX 602 ; N uni1E7F ; G 1502
+U 7808 ; WX 602 ; N Wgrave ; G 1503
+U 7809 ; WX 602 ; N wgrave ; G 1504
+U 7810 ; WX 602 ; N Wacute ; G 1505
+U 7811 ; WX 602 ; N wacute ; G 1506
+U 7812 ; WX 602 ; N Wdieresis ; G 1507
+U 7813 ; WX 602 ; N wdieresis ; G 1508
+U 7814 ; WX 602 ; N uni1E86 ; G 1509
+U 7815 ; WX 602 ; N uni1E87 ; G 1510
+U 7816 ; WX 602 ; N uni1E88 ; G 1511
+U 7817 ; WX 602 ; N uni1E89 ; G 1512
+U 7818 ; WX 602 ; N uni1E8A ; G 1513
+U 7819 ; WX 602 ; N uni1E8B ; G 1514
+U 7820 ; WX 602 ; N uni1E8C ; G 1515
+U 7821 ; WX 602 ; N uni1E8D ; G 1516
+U 7822 ; WX 602 ; N uni1E8E ; G 1517
+U 7823 ; WX 602 ; N uni1E8F ; G 1518
+U 7824 ; WX 602 ; N uni1E90 ; G 1519
+U 7825 ; WX 602 ; N uni1E91 ; G 1520
+U 7826 ; WX 602 ; N uni1E92 ; G 1521
+U 7827 ; WX 602 ; N uni1E93 ; G 1522
+U 7828 ; WX 602 ; N uni1E94 ; G 1523
+U 7829 ; WX 602 ; N uni1E95 ; G 1524
+U 7830 ; WX 602 ; N uni1E96 ; G 1525
+U 7831 ; WX 602 ; N uni1E97 ; G 1526
+U 7832 ; WX 602 ; N uni1E98 ; G 1527
+U 7833 ; WX 602 ; N uni1E99 ; G 1528
+U 7835 ; WX 602 ; N uni1E9B ; G 1529
+U 7839 ; WX 602 ; N uni1E9F ; G 1530
+U 7840 ; WX 602 ; N uni1EA0 ; G 1531
+U 7841 ; WX 602 ; N uni1EA1 ; G 1532
+U 7852 ; WX 602 ; N uni1EAC ; G 1533
+U 7853 ; WX 602 ; N uni1EAD ; G 1534
+U 7856 ; WX 602 ; N uni1EB0 ; G 1535
+U 7857 ; WX 602 ; N uni1EB1 ; G 1536
+U 7862 ; WX 602 ; N uni1EB6 ; G 1537
+U 7863 ; WX 602 ; N uni1EB7 ; G 1538
+U 7864 ; WX 602 ; N uni1EB8 ; G 1539
+U 7865 ; WX 602 ; N uni1EB9 ; G 1540
+U 7868 ; WX 602 ; N uni1EBC ; G 1541
+U 7869 ; WX 602 ; N uni1EBD ; G 1542
+U 7878 ; WX 602 ; N uni1EC6 ; G 1543
+U 7879 ; WX 602 ; N uni1EC7 ; G 1544
+U 7882 ; WX 602 ; N uni1ECA ; G 1545
+U 7883 ; WX 602 ; N uni1ECB ; G 1546
+U 7884 ; WX 602 ; N uni1ECC ; G 1547
+U 7885 ; WX 602 ; N uni1ECD ; G 1548
+U 7896 ; WX 602 ; N uni1ED8 ; G 1549
+U 7897 ; WX 602 ; N uni1ED9 ; G 1550
+U 7898 ; WX 602 ; N uni1EDA ; G 1551
+U 7899 ; WX 602 ; N uni1EDB ; G 1552
+U 7900 ; WX 602 ; N uni1EDC ; G 1553
+U 7901 ; WX 602 ; N uni1EDD ; G 1554
+U 7904 ; WX 602 ; N uni1EE0 ; G 1555
+U 7905 ; WX 602 ; N uni1EE1 ; G 1556
+U 7906 ; WX 602 ; N uni1EE2 ; G 1557
+U 7907 ; WX 602 ; N uni1EE3 ; G 1558
+U 7908 ; WX 602 ; N uni1EE4 ; G 1559
+U 7909 ; WX 602 ; N uni1EE5 ; G 1560
+U 7912 ; WX 602 ; N uni1EE8 ; G 1561
+U 7913 ; WX 602 ; N uni1EE9 ; G 1562
+U 7914 ; WX 602 ; N uni1EEA ; G 1563
+U 7915 ; WX 602 ; N uni1EEB ; G 1564
+U 7918 ; WX 602 ; N uni1EEE ; G 1565
+U 7919 ; WX 602 ; N uni1EEF ; G 1566
+U 7920 ; WX 602 ; N uni1EF0 ; G 1567
+U 7921 ; WX 602 ; N uni1EF1 ; G 1568
+U 7922 ; WX 602 ; N Ygrave ; G 1569
+U 7923 ; WX 602 ; N ygrave ; G 1570
+U 7924 ; WX 602 ; N uni1EF4 ; G 1571
+U 7925 ; WX 602 ; N uni1EF5 ; G 1572
+U 7928 ; WX 602 ; N uni1EF8 ; G 1573
+U 7929 ; WX 602 ; N uni1EF9 ; G 1574
+U 7936 ; WX 602 ; N uni1F00 ; G 1575
+U 7937 ; WX 602 ; N uni1F01 ; G 1576
+U 7938 ; WX 602 ; N uni1F02 ; G 1577
+U 7939 ; WX 602 ; N uni1F03 ; G 1578
+U 7940 ; WX 602 ; N uni1F04 ; G 1579
+U 7941 ; WX 602 ; N uni1F05 ; G 1580
+U 7942 ; WX 602 ; N uni1F06 ; G 1581
+U 7943 ; WX 602 ; N uni1F07 ; G 1582
+U 7944 ; WX 602 ; N uni1F08 ; G 1583
+U 7945 ; WX 602 ; N uni1F09 ; G 1584
+U 7946 ; WX 602 ; N uni1F0A ; G 1585
+U 7947 ; WX 602 ; N uni1F0B ; G 1586
+U 7948 ; WX 602 ; N uni1F0C ; G 1587
+U 7949 ; WX 602 ; N uni1F0D ; G 1588
+U 7950 ; WX 602 ; N uni1F0E ; G 1589
+U 7951 ; WX 602 ; N uni1F0F ; G 1590
+U 7952 ; WX 602 ; N uni1F10 ; G 1591
+U 7953 ; WX 602 ; N uni1F11 ; G 1592
+U 7954 ; WX 602 ; N uni1F12 ; G 1593
+U 7955 ; WX 602 ; N uni1F13 ; G 1594
+U 7956 ; WX 602 ; N uni1F14 ; G 1595
+U 7957 ; WX 602 ; N uni1F15 ; G 1596
+U 7960 ; WX 602 ; N uni1F18 ; G 1597
+U 7961 ; WX 602 ; N uni1F19 ; G 1598
+U 7962 ; WX 602 ; N uni1F1A ; G 1599
+U 7963 ; WX 602 ; N uni1F1B ; G 1600
+U 7964 ; WX 602 ; N uni1F1C ; G 1601
+U 7965 ; WX 602 ; N uni1F1D ; G 1602
+U 7968 ; WX 602 ; N uni1F20 ; G 1603
+U 7969 ; WX 602 ; N uni1F21 ; G 1604
+U 7970 ; WX 602 ; N uni1F22 ; G 1605
+U 7971 ; WX 602 ; N uni1F23 ; G 1606
+U 7972 ; WX 602 ; N uni1F24 ; G 1607
+U 7973 ; WX 602 ; N uni1F25 ; G 1608
+U 7974 ; WX 602 ; N uni1F26 ; G 1609
+U 7975 ; WX 602 ; N uni1F27 ; G 1610
+U 7976 ; WX 602 ; N uni1F28 ; G 1611
+U 7977 ; WX 602 ; N uni1F29 ; G 1612
+U 7978 ; WX 602 ; N uni1F2A ; G 1613
+U 7979 ; WX 602 ; N uni1F2B ; G 1614
+U 7980 ; WX 602 ; N uni1F2C ; G 1615
+U 7981 ; WX 602 ; N uni1F2D ; G 1616
+U 7982 ; WX 602 ; N uni1F2E ; G 1617
+U 7983 ; WX 602 ; N uni1F2F ; G 1618
+U 7984 ; WX 602 ; N uni1F30 ; G 1619
+U 7985 ; WX 602 ; N uni1F31 ; G 1620
+U 7986 ; WX 602 ; N uni1F32 ; G 1621
+U 7987 ; WX 602 ; N uni1F33 ; G 1622
+U 7988 ; WX 602 ; N uni1F34 ; G 1623
+U 7989 ; WX 602 ; N uni1F35 ; G 1624
+U 7990 ; WX 602 ; N uni1F36 ; G 1625
+U 7991 ; WX 602 ; N uni1F37 ; G 1626
+U 7992 ; WX 602 ; N uni1F38 ; G 1627
+U 7993 ; WX 602 ; N uni1F39 ; G 1628
+U 7994 ; WX 602 ; N uni1F3A ; G 1629
+U 7995 ; WX 602 ; N uni1F3B ; G 1630
+U 7996 ; WX 602 ; N uni1F3C ; G 1631
+U 7997 ; WX 602 ; N uni1F3D ; G 1632
+U 7998 ; WX 602 ; N uni1F3E ; G 1633
+U 7999 ; WX 602 ; N uni1F3F ; G 1634
+U 8000 ; WX 602 ; N uni1F40 ; G 1635
+U 8001 ; WX 602 ; N uni1F41 ; G 1636
+U 8002 ; WX 602 ; N uni1F42 ; G 1637
+U 8003 ; WX 602 ; N uni1F43 ; G 1638
+U 8004 ; WX 602 ; N uni1F44 ; G 1639
+U 8005 ; WX 602 ; N uni1F45 ; G 1640
+U 8008 ; WX 602 ; N uni1F48 ; G 1641
+U 8009 ; WX 602 ; N uni1F49 ; G 1642
+U 8010 ; WX 602 ; N uni1F4A ; G 1643
+U 8011 ; WX 602 ; N uni1F4B ; G 1644
+U 8012 ; WX 602 ; N uni1F4C ; G 1645
+U 8013 ; WX 602 ; N uni1F4D ; G 1646
+U 8016 ; WX 602 ; N uni1F50 ; G 1647
+U 8017 ; WX 602 ; N uni1F51 ; G 1648
+U 8018 ; WX 602 ; N uni1F52 ; G 1649
+U 8019 ; WX 602 ; N uni1F53 ; G 1650
+U 8020 ; WX 602 ; N uni1F54 ; G 1651
+U 8021 ; WX 602 ; N uni1F55 ; G 1652
+U 8022 ; WX 602 ; N uni1F56 ; G 1653
+U 8023 ; WX 602 ; N uni1F57 ; G 1654
+U 8025 ; WX 602 ; N uni1F59 ; G 1655
+U 8027 ; WX 602 ; N uni1F5B ; G 1656
+U 8029 ; WX 602 ; N uni1F5D ; G 1657
+U 8031 ; WX 602 ; N uni1F5F ; G 1658
+U 8032 ; WX 602 ; N uni1F60 ; G 1659
+U 8033 ; WX 602 ; N uni1F61 ; G 1660
+U 8034 ; WX 602 ; N uni1F62 ; G 1661
+U 8035 ; WX 602 ; N uni1F63 ; G 1662
+U 8036 ; WX 602 ; N uni1F64 ; G 1663
+U 8037 ; WX 602 ; N uni1F65 ; G 1664
+U 8038 ; WX 602 ; N uni1F66 ; G 1665
+U 8039 ; WX 602 ; N uni1F67 ; G 1666
+U 8040 ; WX 602 ; N uni1F68 ; G 1667
+U 8041 ; WX 602 ; N uni1F69 ; G 1668
+U 8042 ; WX 602 ; N uni1F6A ; G 1669
+U 8043 ; WX 602 ; N uni1F6B ; G 1670
+U 8044 ; WX 602 ; N uni1F6C ; G 1671
+U 8045 ; WX 602 ; N uni1F6D ; G 1672
+U 8046 ; WX 602 ; N uni1F6E ; G 1673
+U 8047 ; WX 602 ; N uni1F6F ; G 1674
+U 8048 ; WX 602 ; N uni1F70 ; G 1675
+U 8049 ; WX 602 ; N uni1F71 ; G 1676
+U 8050 ; WX 602 ; N uni1F72 ; G 1677
+U 8051 ; WX 602 ; N uni1F73 ; G 1678
+U 8052 ; WX 602 ; N uni1F74 ; G 1679
+U 8053 ; WX 602 ; N uni1F75 ; G 1680
+U 8054 ; WX 602 ; N uni1F76 ; G 1681
+U 8055 ; WX 602 ; N uni1F77 ; G 1682
+U 8056 ; WX 602 ; N uni1F78 ; G 1683
+U 8057 ; WX 602 ; N uni1F79 ; G 1684
+U 8058 ; WX 602 ; N uni1F7A ; G 1685
+U 8059 ; WX 602 ; N uni1F7B ; G 1686
+U 8060 ; WX 602 ; N uni1F7C ; G 1687
+U 8061 ; WX 602 ; N uni1F7D ; G 1688
+U 8064 ; WX 602 ; N uni1F80 ; G 1689
+U 8065 ; WX 602 ; N uni1F81 ; G 1690
+U 8066 ; WX 602 ; N uni1F82 ; G 1691
+U 8067 ; WX 602 ; N uni1F83 ; G 1692
+U 8068 ; WX 602 ; N uni1F84 ; G 1693
+U 8069 ; WX 602 ; N uni1F85 ; G 1694
+U 8070 ; WX 602 ; N uni1F86 ; G 1695
+U 8071 ; WX 602 ; N uni1F87 ; G 1696
+U 8072 ; WX 602 ; N uni1F88 ; G 1697
+U 8073 ; WX 602 ; N uni1F89 ; G 1698
+U 8074 ; WX 602 ; N uni1F8A ; G 1699
+U 8075 ; WX 602 ; N uni1F8B ; G 1700
+U 8076 ; WX 602 ; N uni1F8C ; G 1701
+U 8077 ; WX 602 ; N uni1F8D ; G 1702
+U 8078 ; WX 602 ; N uni1F8E ; G 1703
+U 8079 ; WX 602 ; N uni1F8F ; G 1704
+U 8080 ; WX 602 ; N uni1F90 ; G 1705
+U 8081 ; WX 602 ; N uni1F91 ; G 1706
+U 8082 ; WX 602 ; N uni1F92 ; G 1707
+U 8083 ; WX 602 ; N uni1F93 ; G 1708
+U 8084 ; WX 602 ; N uni1F94 ; G 1709
+U 8085 ; WX 602 ; N uni1F95 ; G 1710
+U 8086 ; WX 602 ; N uni1F96 ; G 1711
+U 8087 ; WX 602 ; N uni1F97 ; G 1712
+U 8088 ; WX 602 ; N uni1F98 ; G 1713
+U 8089 ; WX 602 ; N uni1F99 ; G 1714
+U 8090 ; WX 602 ; N uni1F9A ; G 1715
+U 8091 ; WX 602 ; N uni1F9B ; G 1716
+U 8092 ; WX 602 ; N uni1F9C ; G 1717
+U 8093 ; WX 602 ; N uni1F9D ; G 1718
+U 8094 ; WX 602 ; N uni1F9E ; G 1719
+U 8095 ; WX 602 ; N uni1F9F ; G 1720
+U 8096 ; WX 602 ; N uni1FA0 ; G 1721
+U 8097 ; WX 602 ; N uni1FA1 ; G 1722
+U 8098 ; WX 602 ; N uni1FA2 ; G 1723
+U 8099 ; WX 602 ; N uni1FA3 ; G 1724
+U 8100 ; WX 602 ; N uni1FA4 ; G 1725
+U 8101 ; WX 602 ; N uni1FA5 ; G 1726
+U 8102 ; WX 602 ; N uni1FA6 ; G 1727
+U 8103 ; WX 602 ; N uni1FA7 ; G 1728
+U 8104 ; WX 602 ; N uni1FA8 ; G 1729
+U 8105 ; WX 602 ; N uni1FA9 ; G 1730
+U 8106 ; WX 602 ; N uni1FAA ; G 1731
+U 8107 ; WX 602 ; N uni1FAB ; G 1732
+U 8108 ; WX 602 ; N uni1FAC ; G 1733
+U 8109 ; WX 602 ; N uni1FAD ; G 1734
+U 8110 ; WX 602 ; N uni1FAE ; G 1735
+U 8111 ; WX 602 ; N uni1FAF ; G 1736
+U 8112 ; WX 602 ; N uni1FB0 ; G 1737
+U 8113 ; WX 602 ; N uni1FB1 ; G 1738
+U 8114 ; WX 602 ; N uni1FB2 ; G 1739
+U 8115 ; WX 602 ; N uni1FB3 ; G 1740
+U 8116 ; WX 602 ; N uni1FB4 ; G 1741
+U 8118 ; WX 602 ; N uni1FB6 ; G 1742
+U 8119 ; WX 602 ; N uni1FB7 ; G 1743
+U 8120 ; WX 602 ; N uni1FB8 ; G 1744
+U 8121 ; WX 602 ; N uni1FB9 ; G 1745
+U 8122 ; WX 602 ; N uni1FBA ; G 1746
+U 8123 ; WX 602 ; N uni1FBB ; G 1747
+U 8124 ; WX 602 ; N uni1FBC ; G 1748
+U 8125 ; WX 602 ; N uni1FBD ; G 1749
+U 8126 ; WX 602 ; N uni1FBE ; G 1750
+U 8127 ; WX 602 ; N uni1FBF ; G 1751
+U 8128 ; WX 602 ; N uni1FC0 ; G 1752
+U 8129 ; WX 602 ; N uni1FC1 ; G 1753
+U 8130 ; WX 602 ; N uni1FC2 ; G 1754
+U 8131 ; WX 602 ; N uni1FC3 ; G 1755
+U 8132 ; WX 602 ; N uni1FC4 ; G 1756
+U 8134 ; WX 602 ; N uni1FC6 ; G 1757
+U 8135 ; WX 602 ; N uni1FC7 ; G 1758
+U 8136 ; WX 602 ; N uni1FC8 ; G 1759
+U 8137 ; WX 602 ; N uni1FC9 ; G 1760
+U 8138 ; WX 602 ; N uni1FCA ; G 1761
+U 8139 ; WX 602 ; N uni1FCB ; G 1762
+U 8140 ; WX 602 ; N uni1FCC ; G 1763
+U 8141 ; WX 602 ; N uni1FCD ; G 1764
+U 8142 ; WX 602 ; N uni1FCE ; G 1765
+U 8143 ; WX 602 ; N uni1FCF ; G 1766
+U 8144 ; WX 602 ; N uni1FD0 ; G 1767
+U 8145 ; WX 602 ; N uni1FD1 ; G 1768
+U 8146 ; WX 602 ; N uni1FD2 ; G 1769
+U 8147 ; WX 602 ; N uni1FD3 ; G 1770
+U 8150 ; WX 602 ; N uni1FD6 ; G 1771
+U 8151 ; WX 602 ; N uni1FD7 ; G 1772
+U 8152 ; WX 602 ; N uni1FD8 ; G 1773
+U 8153 ; WX 602 ; N uni1FD9 ; G 1774
+U 8154 ; WX 602 ; N uni1FDA ; G 1775
+U 8155 ; WX 602 ; N uni1FDB ; G 1776
+U 8157 ; WX 602 ; N uni1FDD ; G 1777
+U 8158 ; WX 602 ; N uni1FDE ; G 1778
+U 8159 ; WX 602 ; N uni1FDF ; G 1779
+U 8160 ; WX 602 ; N uni1FE0 ; G 1780
+U 8161 ; WX 602 ; N uni1FE1 ; G 1781
+U 8162 ; WX 602 ; N uni1FE2 ; G 1782
+U 8163 ; WX 602 ; N uni1FE3 ; G 1783
+U 8164 ; WX 602 ; N uni1FE4 ; G 1784
+U 8165 ; WX 602 ; N uni1FE5 ; G 1785
+U 8166 ; WX 602 ; N uni1FE6 ; G 1786
+U 8167 ; WX 602 ; N uni1FE7 ; G 1787
+U 8168 ; WX 602 ; N uni1FE8 ; G 1788
+U 8169 ; WX 602 ; N uni1FE9 ; G 1789
+U 8170 ; WX 602 ; N uni1FEA ; G 1790
+U 8171 ; WX 602 ; N uni1FEB ; G 1791
+U 8172 ; WX 602 ; N uni1FEC ; G 1792
+U 8173 ; WX 602 ; N uni1FED ; G 1793
+U 8174 ; WX 602 ; N uni1FEE ; G 1794
+U 8175 ; WX 602 ; N uni1FEF ; G 1795
+U 8178 ; WX 602 ; N uni1FF2 ; G 1796
+U 8179 ; WX 602 ; N uni1FF3 ; G 1797
+U 8180 ; WX 602 ; N uni1FF4 ; G 1798
+U 8182 ; WX 602 ; N uni1FF6 ; G 1799
+U 8183 ; WX 602 ; N uni1FF7 ; G 1800
+U 8184 ; WX 602 ; N uni1FF8 ; G 1801
+U 8185 ; WX 602 ; N uni1FF9 ; G 1802
+U 8186 ; WX 602 ; N uni1FFA ; G 1803
+U 8187 ; WX 602 ; N uni1FFB ; G 1804
+U 8188 ; WX 602 ; N uni1FFC ; G 1805
+U 8189 ; WX 602 ; N uni1FFD ; G 1806
+U 8190 ; WX 602 ; N uni1FFE ; G 1807
+U 8192 ; WX 602 ; N uni2000 ; G 1808
+U 8193 ; WX 602 ; N uni2001 ; G 1809
+U 8194 ; WX 602 ; N uni2002 ; G 1810
+U 8195 ; WX 602 ; N uni2003 ; G 1811
+U 8196 ; WX 602 ; N uni2004 ; G 1812
+U 8197 ; WX 602 ; N uni2005 ; G 1813
+U 8198 ; WX 602 ; N uni2006 ; G 1814
+U 8199 ; WX 602 ; N uni2007 ; G 1815
+U 8200 ; WX 602 ; N uni2008 ; G 1816
+U 8201 ; WX 602 ; N uni2009 ; G 1817
+U 8202 ; WX 602 ; N uni200A ; G 1818
+U 8208 ; WX 602 ; N uni2010 ; G 1819
+U 8209 ; WX 602 ; N uni2011 ; G 1820
+U 8210 ; WX 602 ; N figuredash ; G 1821
+U 8211 ; WX 602 ; N endash ; G 1822
+U 8212 ; WX 602 ; N emdash ; G 1823
+U 8213 ; WX 602 ; N uni2015 ; G 1824
+U 8214 ; WX 602 ; N uni2016 ; G 1825
+U 8215 ; WX 602 ; N underscoredbl ; G 1826
+U 8216 ; WX 602 ; N quoteleft ; G 1827
+U 8217 ; WX 602 ; N quoteright ; G 1828
+U 8218 ; WX 602 ; N quotesinglbase ; G 1829
+U 8219 ; WX 602 ; N quotereversed ; G 1830
+U 8220 ; WX 602 ; N quotedblleft ; G 1831
+U 8221 ; WX 602 ; N quotedblright ; G 1832
+U 8222 ; WX 602 ; N quotedblbase ; G 1833
+U 8223 ; WX 602 ; N uni201F ; G 1834
+U 8224 ; WX 602 ; N dagger ; G 1835
+U 8225 ; WX 602 ; N daggerdbl ; G 1836
+U 8226 ; WX 602 ; N bullet ; G 1837
+U 8227 ; WX 602 ; N uni2023 ; G 1838
+U 8230 ; WX 602 ; N ellipsis ; G 1839
+U 8239 ; WX 602 ; N uni202F ; G 1840
+U 8240 ; WX 602 ; N perthousand ; G 1841
+U 8241 ; WX 602 ; N uni2031 ; G 1842
+U 8242 ; WX 602 ; N minute ; G 1843
+U 8243 ; WX 602 ; N second ; G 1844
+U 8244 ; WX 602 ; N uni2034 ; G 1845
+U 8245 ; WX 602 ; N uni2035 ; G 1846
+U 8246 ; WX 602 ; N uni2036 ; G 1847
+U 8247 ; WX 602 ; N uni2037 ; G 1848
+U 8249 ; WX 602 ; N guilsinglleft ; G 1849
+U 8250 ; WX 602 ; N guilsinglright ; G 1850
+U 8252 ; WX 602 ; N exclamdbl ; G 1851
+U 8253 ; WX 602 ; N uni203D ; G 1852
+U 8254 ; WX 602 ; N uni203E ; G 1853
+U 8255 ; WX 602 ; N uni203F ; G 1854
+U 8261 ; WX 602 ; N uni2045 ; G 1855
+U 8262 ; WX 602 ; N uni2046 ; G 1856
+U 8263 ; WX 602 ; N uni2047 ; G 1857
+U 8264 ; WX 602 ; N uni2048 ; G 1858
+U 8265 ; WX 602 ; N uni2049 ; G 1859
+U 8267 ; WX 602 ; N uni204B ; G 1860
+U 8287 ; WX 602 ; N uni205F ; G 1861
+U 8304 ; WX 602 ; N uni2070 ; G 1862
+U 8305 ; WX 602 ; N uni2071 ; G 1863
+U 8308 ; WX 602 ; N uni2074 ; G 1864
+U 8309 ; WX 602 ; N uni2075 ; G 1865
+U 8310 ; WX 602 ; N uni2076 ; G 1866
+U 8311 ; WX 602 ; N uni2077 ; G 1867
+U 8312 ; WX 602 ; N uni2078 ; G 1868
+U 8313 ; WX 602 ; N uni2079 ; G 1869
+U 8314 ; WX 602 ; N uni207A ; G 1870
+U 8315 ; WX 602 ; N uni207B ; G 1871
+U 8316 ; WX 602 ; N uni207C ; G 1872
+U 8317 ; WX 602 ; N uni207D ; G 1873
+U 8318 ; WX 602 ; N uni207E ; G 1874
+U 8319 ; WX 602 ; N uni207F ; G 1875
+U 8320 ; WX 602 ; N uni2080 ; G 1876
+U 8321 ; WX 602 ; N uni2081 ; G 1877
+U 8322 ; WX 602 ; N uni2082 ; G 1878
+U 8323 ; WX 602 ; N uni2083 ; G 1879
+U 8324 ; WX 602 ; N uni2084 ; G 1880
+U 8325 ; WX 602 ; N uni2085 ; G 1881
+U 8326 ; WX 602 ; N uni2086 ; G 1882
+U 8327 ; WX 602 ; N uni2087 ; G 1883
+U 8328 ; WX 602 ; N uni2088 ; G 1884
+U 8329 ; WX 602 ; N uni2089 ; G 1885
+U 8330 ; WX 602 ; N uni208A ; G 1886
+U 8331 ; WX 602 ; N uni208B ; G 1887
+U 8332 ; WX 602 ; N uni208C ; G 1888
+U 8333 ; WX 602 ; N uni208D ; G 1889
+U 8334 ; WX 602 ; N uni208E ; G 1890
+U 8336 ; WX 602 ; N uni2090 ; G 1891
+U 8337 ; WX 602 ; N uni2091 ; G 1892
+U 8338 ; WX 602 ; N uni2092 ; G 1893
+U 8339 ; WX 602 ; N uni2093 ; G 1894
+U 8340 ; WX 602 ; N uni2094 ; G 1895
+U 8341 ; WX 602 ; N uni2095 ; G 1896
+U 8342 ; WX 602 ; N uni2096 ; G 1897
+U 8343 ; WX 602 ; N uni2097 ; G 1898
+U 8344 ; WX 602 ; N uni2098 ; G 1899
+U 8345 ; WX 602 ; N uni2099 ; G 1900
+U 8346 ; WX 602 ; N uni209A ; G 1901
+U 8347 ; WX 602 ; N uni209B ; G 1902
+U 8348 ; WX 602 ; N uni209C ; G 1903
+U 8352 ; WX 602 ; N uni20A0 ; G 1904
+U 8353 ; WX 602 ; N colonmonetary ; G 1905
+U 8354 ; WX 602 ; N uni20A2 ; G 1906
+U 8355 ; WX 602 ; N franc ; G 1907
+U 8356 ; WX 602 ; N lira ; G 1908
+U 8357 ; WX 602 ; N uni20A5 ; G 1909
+U 8358 ; WX 602 ; N uni20A6 ; G 1910
+U 8359 ; WX 602 ; N peseta ; G 1911
+U 8360 ; WX 602 ; N uni20A8 ; G 1912
+U 8361 ; WX 602 ; N uni20A9 ; G 1913
+U 8362 ; WX 602 ; N uni20AA ; G 1914
+U 8363 ; WX 602 ; N dong ; G 1915
+U 8364 ; WX 602 ; N Euro ; G 1916
+U 8365 ; WX 602 ; N uni20AD ; G 1917
+U 8366 ; WX 602 ; N uni20AE ; G 1918
+U 8367 ; WX 602 ; N uni20AF ; G 1919
+U 8368 ; WX 602 ; N uni20B0 ; G 1920
+U 8369 ; WX 602 ; N uni20B1 ; G 1921
+U 8370 ; WX 602 ; N uni20B2 ; G 1922
+U 8371 ; WX 602 ; N uni20B3 ; G 1923
+U 8372 ; WX 602 ; N uni20B4 ; G 1924
+U 8373 ; WX 602 ; N uni20B5 ; G 1925
+U 8376 ; WX 602 ; N uni20B8 ; G 1926
+U 8377 ; WX 602 ; N uni20B9 ; G 1927
+U 8378 ; WX 602 ; N uni20BA ; G 1928
+U 8381 ; WX 602 ; N uni20BD ; G 1929
+U 8450 ; WX 602 ; N uni2102 ; G 1930
+U 8453 ; WX 602 ; N uni2105 ; G 1931
+U 8461 ; WX 602 ; N uni210D ; G 1932
+U 8462 ; WX 602 ; N uni210E ; G 1933
+U 8463 ; WX 602 ; N uni210F ; G 1934
+U 8469 ; WX 602 ; N uni2115 ; G 1935
+U 8470 ; WX 602 ; N uni2116 ; G 1936
+U 8471 ; WX 602 ; N uni2117 ; G 1937
+U 8473 ; WX 602 ; N uni2119 ; G 1938
+U 8474 ; WX 602 ; N uni211A ; G 1939
+U 8477 ; WX 602 ; N uni211D ; G 1940
+U 8482 ; WX 602 ; N trademark ; G 1941
+U 8484 ; WX 602 ; N uni2124 ; G 1942
+U 8486 ; WX 602 ; N uni2126 ; G 1943
+U 8490 ; WX 602 ; N uni212A ; G 1944
+U 8491 ; WX 602 ; N uni212B ; G 1945
+U 8494 ; WX 602 ; N estimated ; G 1946
+U 8520 ; WX 602 ; N uni2148 ; G 1947
+U 8528 ; WX 602 ; N uni2150 ; G 1948
+U 8529 ; WX 602 ; N uni2151 ; G 1949
+U 8531 ; WX 602 ; N onethird ; G 1950
+U 8532 ; WX 602 ; N twothirds ; G 1951
+U 8533 ; WX 602 ; N uni2155 ; G 1952
+U 8534 ; WX 602 ; N uni2156 ; G 1953
+U 8535 ; WX 602 ; N uni2157 ; G 1954
+U 8536 ; WX 602 ; N uni2158 ; G 1955
+U 8537 ; WX 602 ; N uni2159 ; G 1956
+U 8538 ; WX 602 ; N uni215A ; G 1957
+U 8539 ; WX 602 ; N oneeighth ; G 1958
+U 8540 ; WX 602 ; N threeeighths ; G 1959
+U 8541 ; WX 602 ; N fiveeighths ; G 1960
+U 8542 ; WX 602 ; N seveneighths ; G 1961
+U 8543 ; WX 602 ; N uni215F ; G 1962
+U 8585 ; WX 602 ; N uni2189 ; G 1963
+U 8592 ; WX 602 ; N arrowleft ; G 1964
+U 8593 ; WX 602 ; N arrowup ; G 1965
+U 8594 ; WX 602 ; N arrowright ; G 1966
+U 8595 ; WX 602 ; N arrowdown ; G 1967
+U 8596 ; WX 602 ; N arrowboth ; G 1968
+U 8597 ; WX 602 ; N arrowupdn ; G 1969
+U 8598 ; WX 602 ; N uni2196 ; G 1970
+U 8599 ; WX 602 ; N uni2197 ; G 1971
+U 8600 ; WX 602 ; N uni2198 ; G 1972
+U 8601 ; WX 602 ; N uni2199 ; G 1973
+U 8602 ; WX 602 ; N uni219A ; G 1974
+U 8603 ; WX 602 ; N uni219B ; G 1975
+U 8604 ; WX 602 ; N uni219C ; G 1976
+U 8605 ; WX 602 ; N uni219D ; G 1977
+U 8606 ; WX 602 ; N uni219E ; G 1978
+U 8607 ; WX 602 ; N uni219F ; G 1979
+U 8608 ; WX 602 ; N uni21A0 ; G 1980
+U 8609 ; WX 602 ; N uni21A1 ; G 1981
+U 8610 ; WX 602 ; N uni21A2 ; G 1982
+U 8611 ; WX 602 ; N uni21A3 ; G 1983
+U 8612 ; WX 602 ; N uni21A4 ; G 1984
+U 8613 ; WX 602 ; N uni21A5 ; G 1985
+U 8614 ; WX 602 ; N uni21A6 ; G 1986
+U 8615 ; WX 602 ; N uni21A7 ; G 1987
+U 8616 ; WX 602 ; N arrowupdnbse ; G 1988
+U 8617 ; WX 602 ; N uni21A9 ; G 1989
+U 8618 ; WX 602 ; N uni21AA ; G 1990
+U 8619 ; WX 602 ; N uni21AB ; G 1991
+U 8620 ; WX 602 ; N uni21AC ; G 1992
+U 8621 ; WX 602 ; N uni21AD ; G 1993
+U 8622 ; WX 602 ; N uni21AE ; G 1994
+U 8623 ; WX 602 ; N uni21AF ; G 1995
+U 8624 ; WX 602 ; N uni21B0 ; G 1996
+U 8625 ; WX 602 ; N uni21B1 ; G 1997
+U 8626 ; WX 602 ; N uni21B2 ; G 1998
+U 8627 ; WX 602 ; N uni21B3 ; G 1999
+U 8628 ; WX 602 ; N uni21B4 ; G 2000
+U 8629 ; WX 602 ; N carriagereturn ; G 2001
+U 8630 ; WX 602 ; N uni21B6 ; G 2002
+U 8631 ; WX 602 ; N uni21B7 ; G 2003
+U 8632 ; WX 602 ; N uni21B8 ; G 2004
+U 8633 ; WX 602 ; N uni21B9 ; G 2005
+U 8634 ; WX 602 ; N uni21BA ; G 2006
+U 8635 ; WX 602 ; N uni21BB ; G 2007
+U 8636 ; WX 602 ; N uni21BC ; G 2008
+U 8637 ; WX 602 ; N uni21BD ; G 2009
+U 8638 ; WX 602 ; N uni21BE ; G 2010
+U 8639 ; WX 602 ; N uni21BF ; G 2011
+U 8640 ; WX 602 ; N uni21C0 ; G 2012
+U 8641 ; WX 602 ; N uni21C1 ; G 2013
+U 8642 ; WX 602 ; N uni21C2 ; G 2014
+U 8643 ; WX 602 ; N uni21C3 ; G 2015
+U 8644 ; WX 602 ; N uni21C4 ; G 2016
+U 8645 ; WX 602 ; N uni21C5 ; G 2017
+U 8646 ; WX 602 ; N uni21C6 ; G 2018
+U 8647 ; WX 602 ; N uni21C7 ; G 2019
+U 8648 ; WX 602 ; N uni21C8 ; G 2020
+U 8649 ; WX 602 ; N uni21C9 ; G 2021
+U 8650 ; WX 602 ; N uni21CA ; G 2022
+U 8651 ; WX 602 ; N uni21CB ; G 2023
+U 8652 ; WX 602 ; N uni21CC ; G 2024
+U 8653 ; WX 602 ; N uni21CD ; G 2025
+U 8654 ; WX 602 ; N uni21CE ; G 2026
+U 8655 ; WX 602 ; N uni21CF ; G 2027
+U 8656 ; WX 602 ; N arrowdblleft ; G 2028
+U 8657 ; WX 602 ; N arrowdblup ; G 2029
+U 8658 ; WX 602 ; N arrowdblright ; G 2030
+U 8659 ; WX 602 ; N arrowdbldown ; G 2031
+U 8660 ; WX 602 ; N arrowdblboth ; G 2032
+U 8661 ; WX 602 ; N uni21D5 ; G 2033
+U 8662 ; WX 602 ; N uni21D6 ; G 2034
+U 8663 ; WX 602 ; N uni21D7 ; G 2035
+U 8664 ; WX 602 ; N uni21D8 ; G 2036
+U 8665 ; WX 602 ; N uni21D9 ; G 2037
+U 8666 ; WX 602 ; N uni21DA ; G 2038
+U 8667 ; WX 602 ; N uni21DB ; G 2039
+U 8668 ; WX 602 ; N uni21DC ; G 2040
+U 8669 ; WX 602 ; N uni21DD ; G 2041
+U 8670 ; WX 602 ; N uni21DE ; G 2042
+U 8671 ; WX 602 ; N uni21DF ; G 2043
+U 8672 ; WX 602 ; N uni21E0 ; G 2044
+U 8673 ; WX 602 ; N uni21E1 ; G 2045
+U 8674 ; WX 602 ; N uni21E2 ; G 2046
+U 8675 ; WX 602 ; N uni21E3 ; G 2047
+U 8676 ; WX 602 ; N uni21E4 ; G 2048
+U 8677 ; WX 602 ; N uni21E5 ; G 2049
+U 8678 ; WX 602 ; N uni21E6 ; G 2050
+U 8679 ; WX 602 ; N uni21E7 ; G 2051
+U 8680 ; WX 602 ; N uni21E8 ; G 2052
+U 8681 ; WX 602 ; N uni21E9 ; G 2053
+U 8682 ; WX 602 ; N uni21EA ; G 2054
+U 8683 ; WX 602 ; N uni21EB ; G 2055
+U 8684 ; WX 602 ; N uni21EC ; G 2056
+U 8685 ; WX 602 ; N uni21ED ; G 2057
+U 8686 ; WX 602 ; N uni21EE ; G 2058
+U 8687 ; WX 602 ; N uni21EF ; G 2059
+U 8688 ; WX 602 ; N uni21F0 ; G 2060
+U 8689 ; WX 602 ; N uni21F1 ; G 2061
+U 8690 ; WX 602 ; N uni21F2 ; G 2062
+U 8691 ; WX 602 ; N uni21F3 ; G 2063
+U 8692 ; WX 602 ; N uni21F4 ; G 2064
+U 8693 ; WX 602 ; N uni21F5 ; G 2065
+U 8694 ; WX 602 ; N uni21F6 ; G 2066
+U 8695 ; WX 602 ; N uni21F7 ; G 2067
+U 8696 ; WX 602 ; N uni21F8 ; G 2068
+U 8697 ; WX 602 ; N uni21F9 ; G 2069
+U 8698 ; WX 602 ; N uni21FA ; G 2070
+U 8699 ; WX 602 ; N uni21FB ; G 2071
+U 8700 ; WX 602 ; N uni21FC ; G 2072
+U 8701 ; WX 602 ; N uni21FD ; G 2073
+U 8702 ; WX 602 ; N uni21FE ; G 2074
+U 8703 ; WX 602 ; N uni21FF ; G 2075
+U 8704 ; WX 602 ; N universal ; G 2076
+U 8705 ; WX 602 ; N uni2201 ; G 2077
+U 8706 ; WX 602 ; N partialdiff ; G 2078
+U 8707 ; WX 602 ; N existential ; G 2079
+U 8708 ; WX 602 ; N uni2204 ; G 2080
+U 8709 ; WX 602 ; N emptyset ; G 2081
+U 8710 ; WX 602 ; N increment ; G 2082
+U 8711 ; WX 602 ; N gradient ; G 2083
+U 8712 ; WX 602 ; N element ; G 2084
+U 8713 ; WX 602 ; N notelement ; G 2085
+U 8714 ; WX 602 ; N uni220A ; G 2086
+U 8715 ; WX 602 ; N suchthat ; G 2087
+U 8716 ; WX 602 ; N uni220C ; G 2088
+U 8717 ; WX 602 ; N uni220D ; G 2089
+U 8718 ; WX 602 ; N uni220E ; G 2090
+U 8719 ; WX 602 ; N product ; G 2091
+U 8720 ; WX 602 ; N uni2210 ; G 2092
+U 8721 ; WX 602 ; N summation ; G 2093
+U 8722 ; WX 602 ; N minus ; G 2094
+U 8723 ; WX 602 ; N uni2213 ; G 2095
+U 8725 ; WX 602 ; N uni2215 ; G 2096
+U 8727 ; WX 602 ; N asteriskmath ; G 2097
+U 8728 ; WX 602 ; N uni2218 ; G 2098
+U 8729 ; WX 602 ; N uni2219 ; G 2099
+U 8730 ; WX 602 ; N radical ; G 2100
+U 8731 ; WX 602 ; N uni221B ; G 2101
+U 8732 ; WX 602 ; N uni221C ; G 2102
+U 8733 ; WX 602 ; N proportional ; G 2103
+U 8734 ; WX 602 ; N infinity ; G 2104
+U 8735 ; WX 602 ; N orthogonal ; G 2105
+U 8736 ; WX 602 ; N angle ; G 2106
+U 8739 ; WX 602 ; N uni2223 ; G 2107
+U 8743 ; WX 602 ; N logicaland ; G 2108
+U 8744 ; WX 602 ; N logicalor ; G 2109
+U 8745 ; WX 602 ; N intersection ; G 2110
+U 8746 ; WX 602 ; N union ; G 2111
+U 8747 ; WX 602 ; N integral ; G 2112
+U 8748 ; WX 602 ; N uni222C ; G 2113
+U 8749 ; WX 602 ; N uni222D ; G 2114
+U 8756 ; WX 602 ; N therefore ; G 2115
+U 8757 ; WX 602 ; N uni2235 ; G 2116
+U 8758 ; WX 602 ; N uni2236 ; G 2117
+U 8759 ; WX 602 ; N uni2237 ; G 2118
+U 8760 ; WX 602 ; N uni2238 ; G 2119
+U 8761 ; WX 602 ; N uni2239 ; G 2120
+U 8762 ; WX 602 ; N uni223A ; G 2121
+U 8763 ; WX 602 ; N uni223B ; G 2122
+U 8764 ; WX 602 ; N similar ; G 2123
+U 8765 ; WX 602 ; N uni223D ; G 2124
+U 8769 ; WX 602 ; N uni2241 ; G 2125
+U 8770 ; WX 602 ; N uni2242 ; G 2126
+U 8771 ; WX 602 ; N uni2243 ; G 2127
+U 8772 ; WX 602 ; N uni2244 ; G 2128
+U 8773 ; WX 602 ; N congruent ; G 2129
+U 8774 ; WX 602 ; N uni2246 ; G 2130
+U 8775 ; WX 602 ; N uni2247 ; G 2131
+U 8776 ; WX 602 ; N approxequal ; G 2132
+U 8777 ; WX 602 ; N uni2249 ; G 2133
+U 8778 ; WX 602 ; N uni224A ; G 2134
+U 8779 ; WX 602 ; N uni224B ; G 2135
+U 8780 ; WX 602 ; N uni224C ; G 2136
+U 8781 ; WX 602 ; N uni224D ; G 2137
+U 8782 ; WX 602 ; N uni224E ; G 2138
+U 8783 ; WX 602 ; N uni224F ; G 2139
+U 8784 ; WX 602 ; N uni2250 ; G 2140
+U 8785 ; WX 602 ; N uni2251 ; G 2141
+U 8786 ; WX 602 ; N uni2252 ; G 2142
+U 8787 ; WX 602 ; N uni2253 ; G 2143
+U 8788 ; WX 602 ; N uni2254 ; G 2144
+U 8789 ; WX 602 ; N uni2255 ; G 2145
+U 8790 ; WX 602 ; N uni2256 ; G 2146
+U 8791 ; WX 602 ; N uni2257 ; G 2147
+U 8792 ; WX 602 ; N uni2258 ; G 2148
+U 8793 ; WX 602 ; N uni2259 ; G 2149
+U 8794 ; WX 602 ; N uni225A ; G 2150
+U 8795 ; WX 602 ; N uni225B ; G 2151
+U 8796 ; WX 602 ; N uni225C ; G 2152
+U 8797 ; WX 602 ; N uni225D ; G 2153
+U 8798 ; WX 602 ; N uni225E ; G 2154
+U 8799 ; WX 602 ; N uni225F ; G 2155
+U 8800 ; WX 602 ; N notequal ; G 2156
+U 8801 ; WX 602 ; N equivalence ; G 2157
+U 8802 ; WX 602 ; N uni2262 ; G 2158
+U 8803 ; WX 602 ; N uni2263 ; G 2159
+U 8804 ; WX 602 ; N lessequal ; G 2160
+U 8805 ; WX 602 ; N greaterequal ; G 2161
+U 8806 ; WX 602 ; N uni2266 ; G 2162
+U 8807 ; WX 602 ; N uni2267 ; G 2163
+U 8808 ; WX 602 ; N uni2268 ; G 2164
+U 8809 ; WX 602 ; N uni2269 ; G 2165
+U 8813 ; WX 602 ; N uni226D ; G 2166
+U 8814 ; WX 602 ; N uni226E ; G 2167
+U 8815 ; WX 602 ; N uni226F ; G 2168
+U 8816 ; WX 602 ; N uni2270 ; G 2169
+U 8817 ; WX 602 ; N uni2271 ; G 2170
+U 8818 ; WX 602 ; N uni2272 ; G 2171
+U 8819 ; WX 602 ; N uni2273 ; G 2172
+U 8820 ; WX 602 ; N uni2274 ; G 2173
+U 8821 ; WX 602 ; N uni2275 ; G 2174
+U 8822 ; WX 602 ; N uni2276 ; G 2175
+U 8823 ; WX 602 ; N uni2277 ; G 2176
+U 8824 ; WX 602 ; N uni2278 ; G 2177
+U 8825 ; WX 602 ; N uni2279 ; G 2178
+U 8826 ; WX 602 ; N uni227A ; G 2179
+U 8827 ; WX 602 ; N uni227B ; G 2180
+U 8828 ; WX 602 ; N uni227C ; G 2181
+U 8829 ; WX 602 ; N uni227D ; G 2182
+U 8830 ; WX 602 ; N uni227E ; G 2183
+U 8831 ; WX 602 ; N uni227F ; G 2184
+U 8832 ; WX 602 ; N uni2280 ; G 2185
+U 8833 ; WX 602 ; N uni2281 ; G 2186
+U 8834 ; WX 602 ; N propersubset ; G 2187
+U 8835 ; WX 602 ; N propersuperset ; G 2188
+U 8836 ; WX 602 ; N notsubset ; G 2189
+U 8837 ; WX 602 ; N uni2285 ; G 2190
+U 8838 ; WX 602 ; N reflexsubset ; G 2191
+U 8839 ; WX 602 ; N reflexsuperset ; G 2192
+U 8840 ; WX 602 ; N uni2288 ; G 2193
+U 8841 ; WX 602 ; N uni2289 ; G 2194
+U 8842 ; WX 602 ; N uni228A ; G 2195
+U 8843 ; WX 602 ; N uni228B ; G 2196
+U 8845 ; WX 602 ; N uni228D ; G 2197
+U 8846 ; WX 602 ; N uni228E ; G 2198
+U 8847 ; WX 602 ; N uni228F ; G 2199
+U 8848 ; WX 602 ; N uni2290 ; G 2200
+U 8849 ; WX 602 ; N uni2291 ; G 2201
+U 8850 ; WX 602 ; N uni2292 ; G 2202
+U 8851 ; WX 602 ; N uni2293 ; G 2203
+U 8852 ; WX 602 ; N uni2294 ; G 2204
+U 8853 ; WX 602 ; N circleplus ; G 2205
+U 8854 ; WX 602 ; N uni2296 ; G 2206
+U 8855 ; WX 602 ; N circlemultiply ; G 2207
+U 8856 ; WX 602 ; N uni2298 ; G 2208
+U 8857 ; WX 602 ; N uni2299 ; G 2209
+U 8858 ; WX 602 ; N uni229A ; G 2210
+U 8859 ; WX 602 ; N uni229B ; G 2211
+U 8860 ; WX 602 ; N uni229C ; G 2212
+U 8861 ; WX 602 ; N uni229D ; G 2213
+U 8862 ; WX 602 ; N uni229E ; G 2214
+U 8863 ; WX 602 ; N uni229F ; G 2215
+U 8864 ; WX 602 ; N uni22A0 ; G 2216
+U 8865 ; WX 602 ; N uni22A1 ; G 2217
+U 8866 ; WX 602 ; N uni22A2 ; G 2218
+U 8867 ; WX 602 ; N uni22A3 ; G 2219
+U 8868 ; WX 602 ; N uni22A4 ; G 2220
+U 8869 ; WX 602 ; N perpendicular ; G 2221
+U 8882 ; WX 602 ; N uni22B2 ; G 2222
+U 8883 ; WX 602 ; N uni22B3 ; G 2223
+U 8884 ; WX 602 ; N uni22B4 ; G 2224
+U 8885 ; WX 602 ; N uni22B5 ; G 2225
+U 8888 ; WX 602 ; N uni22B8 ; G 2226
+U 8898 ; WX 602 ; N uni22C2 ; G 2227
+U 8899 ; WX 602 ; N uni22C3 ; G 2228
+U 8900 ; WX 602 ; N uni22C4 ; G 2229
+U 8901 ; WX 602 ; N dotmath ; G 2230
+U 8902 ; WX 602 ; N uni22C6 ; G 2231
+U 8909 ; WX 602 ; N uni22CD ; G 2232
+U 8910 ; WX 602 ; N uni22CE ; G 2233
+U 8911 ; WX 602 ; N uni22CF ; G 2234
+U 8912 ; WX 602 ; N uni22D0 ; G 2235
+U 8913 ; WX 602 ; N uni22D1 ; G 2236
+U 8922 ; WX 602 ; N uni22DA ; G 2237
+U 8923 ; WX 602 ; N uni22DB ; G 2238
+U 8924 ; WX 602 ; N uni22DC ; G 2239
+U 8925 ; WX 602 ; N uni22DD ; G 2240
+U 8926 ; WX 602 ; N uni22DE ; G 2241
+U 8927 ; WX 602 ; N uni22DF ; G 2242
+U 8928 ; WX 602 ; N uni22E0 ; G 2243
+U 8929 ; WX 602 ; N uni22E1 ; G 2244
+U 8930 ; WX 602 ; N uni22E2 ; G 2245
+U 8931 ; WX 602 ; N uni22E3 ; G 2246
+U 8932 ; WX 602 ; N uni22E4 ; G 2247
+U 8933 ; WX 602 ; N uni22E5 ; G 2248
+U 8934 ; WX 602 ; N uni22E6 ; G 2249
+U 8935 ; WX 602 ; N uni22E7 ; G 2250
+U 8936 ; WX 602 ; N uni22E8 ; G 2251
+U 8937 ; WX 602 ; N uni22E9 ; G 2252
+U 8943 ; WX 602 ; N uni22EF ; G 2253
+U 8960 ; WX 602 ; N uni2300 ; G 2254
+U 8961 ; WX 602 ; N uni2301 ; G 2255
+U 8962 ; WX 602 ; N house ; G 2256
+U 8963 ; WX 602 ; N uni2303 ; G 2257
+U 8964 ; WX 602 ; N uni2304 ; G 2258
+U 8965 ; WX 602 ; N uni2305 ; G 2259
+U 8966 ; WX 602 ; N uni2306 ; G 2260
+U 8968 ; WX 602 ; N uni2308 ; G 2261
+U 8969 ; WX 602 ; N uni2309 ; G 2262
+U 8970 ; WX 602 ; N uni230A ; G 2263
+U 8971 ; WX 602 ; N uni230B ; G 2264
+U 8972 ; WX 602 ; N uni230C ; G 2265
+U 8973 ; WX 602 ; N uni230D ; G 2266
+U 8974 ; WX 602 ; N uni230E ; G 2267
+U 8975 ; WX 602 ; N uni230F ; G 2268
+U 8976 ; WX 602 ; N revlogicalnot ; G 2269
+U 8977 ; WX 602 ; N uni2311 ; G 2270
+U 8978 ; WX 602 ; N uni2312 ; G 2271
+U 8979 ; WX 602 ; N uni2313 ; G 2272
+U 8980 ; WX 602 ; N uni2314 ; G 2273
+U 8981 ; WX 602 ; N uni2315 ; G 2274
+U 8984 ; WX 602 ; N uni2318 ; G 2275
+U 8985 ; WX 602 ; N uni2319 ; G 2276
+U 8988 ; WX 602 ; N uni231C ; G 2277
+U 8989 ; WX 602 ; N uni231D ; G 2278
+U 8990 ; WX 602 ; N uni231E ; G 2279
+U 8991 ; WX 602 ; N uni231F ; G 2280
+U 8992 ; WX 602 ; N integraltp ; G 2281
+U 8993 ; WX 602 ; N integralbt ; G 2282
+U 8997 ; WX 602 ; N uni2325 ; G 2283
+U 8998 ; WX 602 ; N uni2326 ; G 2284
+U 8999 ; WX 602 ; N uni2327 ; G 2285
+U 9000 ; WX 602 ; N uni2328 ; G 2286
+U 9003 ; WX 602 ; N uni232B ; G 2287
+U 9013 ; WX 602 ; N uni2335 ; G 2288
+U 9014 ; WX 602 ; N uni2336 ; G 2289
+U 9015 ; WX 602 ; N uni2337 ; G 2290
+U 9016 ; WX 602 ; N uni2338 ; G 2291
+U 9017 ; WX 602 ; N uni2339 ; G 2292
+U 9018 ; WX 602 ; N uni233A ; G 2293
+U 9019 ; WX 602 ; N uni233B ; G 2294
+U 9020 ; WX 602 ; N uni233C ; G 2295
+U 9021 ; WX 602 ; N uni233D ; G 2296
+U 9022 ; WX 602 ; N uni233E ; G 2297
+U 9023 ; WX 602 ; N uni233F ; G 2298
+U 9024 ; WX 602 ; N uni2340 ; G 2299
+U 9025 ; WX 602 ; N uni2341 ; G 2300
+U 9026 ; WX 602 ; N uni2342 ; G 2301
+U 9027 ; WX 602 ; N uni2343 ; G 2302
+U 9028 ; WX 602 ; N uni2344 ; G 2303
+U 9029 ; WX 602 ; N uni2345 ; G 2304
+U 9030 ; WX 602 ; N uni2346 ; G 2305
+U 9031 ; WX 602 ; N uni2347 ; G 2306
+U 9032 ; WX 602 ; N uni2348 ; G 2307
+U 9033 ; WX 602 ; N uni2349 ; G 2308
+U 9034 ; WX 602 ; N uni234A ; G 2309
+U 9035 ; WX 602 ; N uni234B ; G 2310
+U 9036 ; WX 602 ; N uni234C ; G 2311
+U 9037 ; WX 602 ; N uni234D ; G 2312
+U 9038 ; WX 602 ; N uni234E ; G 2313
+U 9039 ; WX 602 ; N uni234F ; G 2314
+U 9040 ; WX 602 ; N uni2350 ; G 2315
+U 9041 ; WX 602 ; N uni2351 ; G 2316
+U 9042 ; WX 602 ; N uni2352 ; G 2317
+U 9043 ; WX 602 ; N uni2353 ; G 2318
+U 9044 ; WX 602 ; N uni2354 ; G 2319
+U 9045 ; WX 602 ; N uni2355 ; G 2320
+U 9046 ; WX 602 ; N uni2356 ; G 2321
+U 9047 ; WX 602 ; N uni2357 ; G 2322
+U 9048 ; WX 602 ; N uni2358 ; G 2323
+U 9049 ; WX 602 ; N uni2359 ; G 2324
+U 9050 ; WX 602 ; N uni235A ; G 2325
+U 9051 ; WX 602 ; N uni235B ; G 2326
+U 9052 ; WX 602 ; N uni235C ; G 2327
+U 9053 ; WX 602 ; N uni235D ; G 2328
+U 9054 ; WX 602 ; N uni235E ; G 2329
+U 9055 ; WX 602 ; N uni235F ; G 2330
+U 9056 ; WX 602 ; N uni2360 ; G 2331
+U 9057 ; WX 602 ; N uni2361 ; G 2332
+U 9058 ; WX 602 ; N uni2362 ; G 2333
+U 9059 ; WX 602 ; N uni2363 ; G 2334
+U 9060 ; WX 602 ; N uni2364 ; G 2335
+U 9061 ; WX 602 ; N uni2365 ; G 2336
+U 9062 ; WX 602 ; N uni2366 ; G 2337
+U 9063 ; WX 602 ; N uni2367 ; G 2338
+U 9064 ; WX 602 ; N uni2368 ; G 2339
+U 9065 ; WX 602 ; N uni2369 ; G 2340
+U 9066 ; WX 602 ; N uni236A ; G 2341
+U 9067 ; WX 602 ; N uni236B ; G 2342
+U 9068 ; WX 602 ; N uni236C ; G 2343
+U 9069 ; WX 602 ; N uni236D ; G 2344
+U 9070 ; WX 602 ; N uni236E ; G 2345
+U 9071 ; WX 602 ; N uni236F ; G 2346
+U 9072 ; WX 602 ; N uni2370 ; G 2347
+U 9073 ; WX 602 ; N uni2371 ; G 2348
+U 9074 ; WX 602 ; N uni2372 ; G 2349
+U 9075 ; WX 602 ; N uni2373 ; G 2350
+U 9076 ; WX 602 ; N uni2374 ; G 2351
+U 9077 ; WX 602 ; N uni2375 ; G 2352
+U 9078 ; WX 602 ; N uni2376 ; G 2353
+U 9079 ; WX 602 ; N uni2377 ; G 2354
+U 9080 ; WX 602 ; N uni2378 ; G 2355
+U 9081 ; WX 602 ; N uni2379 ; G 2356
+U 9082 ; WX 602 ; N uni237A ; G 2357
+U 9085 ; WX 602 ; N uni237D ; G 2358
+U 9088 ; WX 602 ; N uni2380 ; G 2359
+U 9089 ; WX 602 ; N uni2381 ; G 2360
+U 9090 ; WX 602 ; N uni2382 ; G 2361
+U 9091 ; WX 602 ; N uni2383 ; G 2362
+U 9096 ; WX 602 ; N uni2388 ; G 2363
+U 9097 ; WX 602 ; N uni2389 ; G 2364
+U 9098 ; WX 602 ; N uni238A ; G 2365
+U 9099 ; WX 602 ; N uni238B ; G 2366
+U 9109 ; WX 602 ; N uni2395 ; G 2367
+U 9115 ; WX 602 ; N uni239B ; G 2368
+U 9116 ; WX 602 ; N uni239C ; G 2369
+U 9117 ; WX 602 ; N uni239D ; G 2370
+U 9118 ; WX 602 ; N uni239E ; G 2371
+U 9119 ; WX 602 ; N uni239F ; G 2372
+U 9120 ; WX 602 ; N uni23A0 ; G 2373
+U 9121 ; WX 602 ; N uni23A1 ; G 2374
+U 9122 ; WX 602 ; N uni23A2 ; G 2375
+U 9123 ; WX 602 ; N uni23A3 ; G 2376
+U 9124 ; WX 602 ; N uni23A4 ; G 2377
+U 9125 ; WX 602 ; N uni23A5 ; G 2378
+U 9126 ; WX 602 ; N uni23A6 ; G 2379
+U 9127 ; WX 602 ; N uni23A7 ; G 2380
+U 9128 ; WX 602 ; N uni23A8 ; G 2381
+U 9129 ; WX 602 ; N uni23A9 ; G 2382
+U 9130 ; WX 602 ; N uni23AA ; G 2383
+U 9131 ; WX 602 ; N uni23AB ; G 2384
+U 9132 ; WX 602 ; N uni23AC ; G 2385
+U 9133 ; WX 602 ; N uni23AD ; G 2386
+U 9134 ; WX 602 ; N uni23AE ; G 2387
+U 9166 ; WX 602 ; N uni23CE ; G 2388
+U 9167 ; WX 602 ; N uni23CF ; G 2389
+U 9251 ; WX 602 ; N uni2423 ; G 2390
+U 9472 ; WX 602 ; N SF100000 ; G 2391
+U 9473 ; WX 602 ; N uni2501 ; G 2392
+U 9474 ; WX 602 ; N SF110000 ; G 2393
+U 9475 ; WX 602 ; N uni2503 ; G 2394
+U 9476 ; WX 602 ; N uni2504 ; G 2395
+U 9477 ; WX 602 ; N uni2505 ; G 2396
+U 9478 ; WX 602 ; N uni2506 ; G 2397
+U 9479 ; WX 602 ; N uni2507 ; G 2398
+U 9480 ; WX 602 ; N uni2508 ; G 2399
+U 9481 ; WX 602 ; N uni2509 ; G 2400
+U 9482 ; WX 602 ; N uni250A ; G 2401
+U 9483 ; WX 602 ; N uni250B ; G 2402
+U 9484 ; WX 602 ; N SF010000 ; G 2403
+U 9485 ; WX 602 ; N uni250D ; G 2404
+U 9486 ; WX 602 ; N uni250E ; G 2405
+U 9487 ; WX 602 ; N uni250F ; G 2406
+U 9488 ; WX 602 ; N SF030000 ; G 2407
+U 9489 ; WX 602 ; N uni2511 ; G 2408
+U 9490 ; WX 602 ; N uni2512 ; G 2409
+U 9491 ; WX 602 ; N uni2513 ; G 2410
+U 9492 ; WX 602 ; N SF020000 ; G 2411
+U 9493 ; WX 602 ; N uni2515 ; G 2412
+U 9494 ; WX 602 ; N uni2516 ; G 2413
+U 9495 ; WX 602 ; N uni2517 ; G 2414
+U 9496 ; WX 602 ; N SF040000 ; G 2415
+U 9497 ; WX 602 ; N uni2519 ; G 2416
+U 9498 ; WX 602 ; N uni251A ; G 2417
+U 9499 ; WX 602 ; N uni251B ; G 2418
+U 9500 ; WX 602 ; N SF080000 ; G 2419
+U 9501 ; WX 602 ; N uni251D ; G 2420
+U 9502 ; WX 602 ; N uni251E ; G 2421
+U 9503 ; WX 602 ; N uni251F ; G 2422
+U 9504 ; WX 602 ; N uni2520 ; G 2423
+U 9505 ; WX 602 ; N uni2521 ; G 2424
+U 9506 ; WX 602 ; N uni2522 ; G 2425
+U 9507 ; WX 602 ; N uni2523 ; G 2426
+U 9508 ; WX 602 ; N SF090000 ; G 2427
+U 9509 ; WX 602 ; N uni2525 ; G 2428
+U 9510 ; WX 602 ; N uni2526 ; G 2429
+U 9511 ; WX 602 ; N uni2527 ; G 2430
+U 9512 ; WX 602 ; N uni2528 ; G 2431
+U 9513 ; WX 602 ; N uni2529 ; G 2432
+U 9514 ; WX 602 ; N uni252A ; G 2433
+U 9515 ; WX 602 ; N uni252B ; G 2434
+U 9516 ; WX 602 ; N SF060000 ; G 2435
+U 9517 ; WX 602 ; N uni252D ; G 2436
+U 9518 ; WX 602 ; N uni252E ; G 2437
+U 9519 ; WX 602 ; N uni252F ; G 2438
+U 9520 ; WX 602 ; N uni2530 ; G 2439
+U 9521 ; WX 602 ; N uni2531 ; G 2440
+U 9522 ; WX 602 ; N uni2532 ; G 2441
+U 9523 ; WX 602 ; N uni2533 ; G 2442
+U 9524 ; WX 602 ; N SF070000 ; G 2443
+U 9525 ; WX 602 ; N uni2535 ; G 2444
+U 9526 ; WX 602 ; N uni2536 ; G 2445
+U 9527 ; WX 602 ; N uni2537 ; G 2446
+U 9528 ; WX 602 ; N uni2538 ; G 2447
+U 9529 ; WX 602 ; N uni2539 ; G 2448
+U 9530 ; WX 602 ; N uni253A ; G 2449
+U 9531 ; WX 602 ; N uni253B ; G 2450
+U 9532 ; WX 602 ; N SF050000 ; G 2451
+U 9533 ; WX 602 ; N uni253D ; G 2452
+U 9534 ; WX 602 ; N uni253E ; G 2453
+U 9535 ; WX 602 ; N uni253F ; G 2454
+U 9536 ; WX 602 ; N uni2540 ; G 2455
+U 9537 ; WX 602 ; N uni2541 ; G 2456
+U 9538 ; WX 602 ; N uni2542 ; G 2457
+U 9539 ; WX 602 ; N uni2543 ; G 2458
+U 9540 ; WX 602 ; N uni2544 ; G 2459
+U 9541 ; WX 602 ; N uni2545 ; G 2460
+U 9542 ; WX 602 ; N uni2546 ; G 2461
+U 9543 ; WX 602 ; N uni2547 ; G 2462
+U 9544 ; WX 602 ; N uni2548 ; G 2463
+U 9545 ; WX 602 ; N uni2549 ; G 2464
+U 9546 ; WX 602 ; N uni254A ; G 2465
+U 9547 ; WX 602 ; N uni254B ; G 2466
+U 9548 ; WX 602 ; N uni254C ; G 2467
+U 9549 ; WX 602 ; N uni254D ; G 2468
+U 9550 ; WX 602 ; N uni254E ; G 2469
+U 9551 ; WX 602 ; N uni254F ; G 2470
+U 9552 ; WX 602 ; N SF430000 ; G 2471
+U 9553 ; WX 602 ; N SF240000 ; G 2472
+U 9554 ; WX 602 ; N SF510000 ; G 2473
+U 9555 ; WX 602 ; N SF520000 ; G 2474
+U 9556 ; WX 602 ; N SF390000 ; G 2475
+U 9557 ; WX 602 ; N SF220000 ; G 2476
+U 9558 ; WX 602 ; N SF210000 ; G 2477
+U 9559 ; WX 602 ; N SF250000 ; G 2478
+U 9560 ; WX 602 ; N SF500000 ; G 2479
+U 9561 ; WX 602 ; N SF490000 ; G 2480
+U 9562 ; WX 602 ; N SF380000 ; G 2481
+U 9563 ; WX 602 ; N SF280000 ; G 2482
+U 9564 ; WX 602 ; N SF270000 ; G 2483
+U 9565 ; WX 602 ; N SF260000 ; G 2484
+U 9566 ; WX 602 ; N SF360000 ; G 2485
+U 9567 ; WX 602 ; N SF370000 ; G 2486
+U 9568 ; WX 602 ; N SF420000 ; G 2487
+U 9569 ; WX 602 ; N SF190000 ; G 2488
+U 9570 ; WX 602 ; N SF200000 ; G 2489
+U 9571 ; WX 602 ; N SF230000 ; G 2490
+U 9572 ; WX 602 ; N SF470000 ; G 2491
+U 9573 ; WX 602 ; N SF480000 ; G 2492
+U 9574 ; WX 602 ; N SF410000 ; G 2493
+U 9575 ; WX 602 ; N SF450000 ; G 2494
+U 9576 ; WX 602 ; N SF460000 ; G 2495
+U 9577 ; WX 602 ; N SF400000 ; G 2496
+U 9578 ; WX 602 ; N SF540000 ; G 2497
+U 9579 ; WX 602 ; N SF530000 ; G 2498
+U 9580 ; WX 602 ; N SF440000 ; G 2499
+U 9581 ; WX 602 ; N uni256D ; G 2500
+U 9582 ; WX 602 ; N uni256E ; G 2501
+U 9583 ; WX 602 ; N uni256F ; G 2502
+U 9584 ; WX 602 ; N uni2570 ; G 2503
+U 9585 ; WX 602 ; N uni2571 ; G 2504
+U 9586 ; WX 602 ; N uni2572 ; G 2505
+U 9587 ; WX 602 ; N uni2573 ; G 2506
+U 9588 ; WX 602 ; N uni2574 ; G 2507
+U 9589 ; WX 602 ; N uni2575 ; G 2508
+U 9590 ; WX 602 ; N uni2576 ; G 2509
+U 9591 ; WX 602 ; N uni2577 ; G 2510
+U 9592 ; WX 602 ; N uni2578 ; G 2511
+U 9593 ; WX 602 ; N uni2579 ; G 2512
+U 9594 ; WX 602 ; N uni257A ; G 2513
+U 9595 ; WX 602 ; N uni257B ; G 2514
+U 9596 ; WX 602 ; N uni257C ; G 2515
+U 9597 ; WX 602 ; N uni257D ; G 2516
+U 9598 ; WX 602 ; N uni257E ; G 2517
+U 9599 ; WX 602 ; N uni257F ; G 2518
+U 9600 ; WX 602 ; N upblock ; G 2519
+U 9601 ; WX 602 ; N uni2581 ; G 2520
+U 9602 ; WX 602 ; N uni2582 ; G 2521
+U 9603 ; WX 602 ; N uni2583 ; G 2522
+U 9604 ; WX 602 ; N dnblock ; G 2523
+U 9605 ; WX 602 ; N uni2585 ; G 2524
+U 9606 ; WX 602 ; N uni2586 ; G 2525
+U 9607 ; WX 602 ; N uni2587 ; G 2526
+U 9608 ; WX 602 ; N block ; G 2527
+U 9609 ; WX 602 ; N uni2589 ; G 2528
+U 9610 ; WX 602 ; N uni258A ; G 2529
+U 9611 ; WX 602 ; N uni258B ; G 2530
+U 9612 ; WX 602 ; N lfblock ; G 2531
+U 9613 ; WX 602 ; N uni258D ; G 2532
+U 9614 ; WX 602 ; N uni258E ; G 2533
+U 9615 ; WX 602 ; N uni258F ; G 2534
+U 9616 ; WX 602 ; N rtblock ; G 2535
+U 9617 ; WX 602 ; N ltshade ; G 2536
+U 9618 ; WX 602 ; N shade ; G 2537
+U 9619 ; WX 602 ; N dkshade ; G 2538
+U 9620 ; WX 602 ; N uni2594 ; G 2539
+U 9621 ; WX 602 ; N uni2595 ; G 2540
+U 9622 ; WX 602 ; N uni2596 ; G 2541
+U 9623 ; WX 602 ; N uni2597 ; G 2542
+U 9624 ; WX 602 ; N uni2598 ; G 2543
+U 9625 ; WX 602 ; N uni2599 ; G 2544
+U 9626 ; WX 602 ; N uni259A ; G 2545
+U 9627 ; WX 602 ; N uni259B ; G 2546
+U 9628 ; WX 602 ; N uni259C ; G 2547
+U 9629 ; WX 602 ; N uni259D ; G 2548
+U 9630 ; WX 602 ; N uni259E ; G 2549
+U 9631 ; WX 602 ; N uni259F ; G 2550
+U 9632 ; WX 602 ; N filledbox ; G 2551
+U 9633 ; WX 602 ; N H22073 ; G 2552
+U 9634 ; WX 602 ; N uni25A2 ; G 2553
+U 9635 ; WX 602 ; N uni25A3 ; G 2554
+U 9636 ; WX 602 ; N uni25A4 ; G 2555
+U 9637 ; WX 602 ; N uni25A5 ; G 2556
+U 9638 ; WX 602 ; N uni25A6 ; G 2557
+U 9639 ; WX 602 ; N uni25A7 ; G 2558
+U 9640 ; WX 602 ; N uni25A8 ; G 2559
+U 9641 ; WX 602 ; N uni25A9 ; G 2560
+U 9642 ; WX 602 ; N H18543 ; G 2561
+U 9643 ; WX 602 ; N H18551 ; G 2562
+U 9644 ; WX 602 ; N filledrect ; G 2563
+U 9645 ; WX 602 ; N uni25AD ; G 2564
+U 9646 ; WX 602 ; N uni25AE ; G 2565
+U 9647 ; WX 602 ; N uni25AF ; G 2566
+U 9648 ; WX 602 ; N uni25B0 ; G 2567
+U 9649 ; WX 602 ; N uni25B1 ; G 2568
+U 9650 ; WX 602 ; N triagup ; G 2569
+U 9651 ; WX 602 ; N uni25B3 ; G 2570
+U 9652 ; WX 602 ; N uni25B4 ; G 2571
+U 9653 ; WX 602 ; N uni25B5 ; G 2572
+U 9654 ; WX 602 ; N uni25B6 ; G 2573
+U 9655 ; WX 602 ; N uni25B7 ; G 2574
+U 9656 ; WX 602 ; N uni25B8 ; G 2575
+U 9657 ; WX 602 ; N uni25B9 ; G 2576
+U 9658 ; WX 602 ; N triagrt ; G 2577
+U 9659 ; WX 602 ; N uni25BB ; G 2578
+U 9660 ; WX 602 ; N triagdn ; G 2579
+U 9661 ; WX 602 ; N uni25BD ; G 2580
+U 9662 ; WX 602 ; N uni25BE ; G 2581
+U 9663 ; WX 602 ; N uni25BF ; G 2582
+U 9664 ; WX 602 ; N uni25C0 ; G 2583
+U 9665 ; WX 602 ; N uni25C1 ; G 2584
+U 9666 ; WX 602 ; N uni25C2 ; G 2585
+U 9667 ; WX 602 ; N uni25C3 ; G 2586
+U 9668 ; WX 602 ; N triaglf ; G 2587
+U 9669 ; WX 602 ; N uni25C5 ; G 2588
+U 9670 ; WX 602 ; N uni25C6 ; G 2589
+U 9671 ; WX 602 ; N uni25C7 ; G 2590
+U 9672 ; WX 602 ; N uni25C8 ; G 2591
+U 9673 ; WX 602 ; N uni25C9 ; G 2592
+U 9674 ; WX 602 ; N lozenge ; G 2593
+U 9675 ; WX 602 ; N circle ; G 2594
+U 9676 ; WX 602 ; N uni25CC ; G 2595
+U 9677 ; WX 602 ; N uni25CD ; G 2596
+U 9678 ; WX 602 ; N uni25CE ; G 2597
+U 9679 ; WX 602 ; N H18533 ; G 2598
+U 9680 ; WX 602 ; N uni25D0 ; G 2599
+U 9681 ; WX 602 ; N uni25D1 ; G 2600
+U 9682 ; WX 602 ; N uni25D2 ; G 2601
+U 9683 ; WX 602 ; N uni25D3 ; G 2602
+U 9684 ; WX 602 ; N uni25D4 ; G 2603
+U 9685 ; WX 602 ; N uni25D5 ; G 2604
+U 9686 ; WX 602 ; N uni25D6 ; G 2605
+U 9687 ; WX 602 ; N uni25D7 ; G 2606
+U 9688 ; WX 602 ; N invbullet ; G 2607
+U 9689 ; WX 602 ; N invcircle ; G 2608
+U 9690 ; WX 602 ; N uni25DA ; G 2609
+U 9691 ; WX 602 ; N uni25DB ; G 2610
+U 9692 ; WX 602 ; N uni25DC ; G 2611
+U 9693 ; WX 602 ; N uni25DD ; G 2612
+U 9694 ; WX 602 ; N uni25DE ; G 2613
+U 9695 ; WX 602 ; N uni25DF ; G 2614
+U 9696 ; WX 602 ; N uni25E0 ; G 2615
+U 9697 ; WX 602 ; N uni25E1 ; G 2616
+U 9698 ; WX 602 ; N uni25E2 ; G 2617
+U 9699 ; WX 602 ; N uni25E3 ; G 2618
+U 9700 ; WX 602 ; N uni25E4 ; G 2619
+U 9701 ; WX 602 ; N uni25E5 ; G 2620
+U 9702 ; WX 602 ; N openbullet ; G 2621
+U 9703 ; WX 602 ; N uni25E7 ; G 2622
+U 9704 ; WX 602 ; N uni25E8 ; G 2623
+U 9705 ; WX 602 ; N uni25E9 ; G 2624
+U 9706 ; WX 602 ; N uni25EA ; G 2625
+U 9707 ; WX 602 ; N uni25EB ; G 2626
+U 9708 ; WX 602 ; N uni25EC ; G 2627
+U 9709 ; WX 602 ; N uni25ED ; G 2628
+U 9710 ; WX 602 ; N uni25EE ; G 2629
+U 9711 ; WX 602 ; N uni25EF ; G 2630
+U 9712 ; WX 602 ; N uni25F0 ; G 2631
+U 9713 ; WX 602 ; N uni25F1 ; G 2632
+U 9714 ; WX 602 ; N uni25F2 ; G 2633
+U 9715 ; WX 602 ; N uni25F3 ; G 2634
+U 9716 ; WX 602 ; N uni25F4 ; G 2635
+U 9717 ; WX 602 ; N uni25F5 ; G 2636
+U 9718 ; WX 602 ; N uni25F6 ; G 2637
+U 9719 ; WX 602 ; N uni25F7 ; G 2638
+U 9720 ; WX 602 ; N uni25F8 ; G 2639
+U 9721 ; WX 602 ; N uni25F9 ; G 2640
+U 9722 ; WX 602 ; N uni25FA ; G 2641
+U 9723 ; WX 602 ; N uni25FB ; G 2642
+U 9724 ; WX 602 ; N uni25FC ; G 2643
+U 9725 ; WX 602 ; N uni25FD ; G 2644
+U 9726 ; WX 602 ; N uni25FE ; G 2645
+U 9727 ; WX 602 ; N uni25FF ; G 2646
+U 9728 ; WX 602 ; N uni2600 ; G 2647
+U 9729 ; WX 602 ; N uni2601 ; G 2648
+U 9730 ; WX 602 ; N uni2602 ; G 2649
+U 9731 ; WX 602 ; N uni2603 ; G 2650
+U 9732 ; WX 602 ; N uni2604 ; G 2651
+U 9733 ; WX 602 ; N uni2605 ; G 2652
+U 9734 ; WX 602 ; N uni2606 ; G 2653
+U 9735 ; WX 602 ; N uni2607 ; G 2654
+U 9736 ; WX 602 ; N uni2608 ; G 2655
+U 9737 ; WX 602 ; N uni2609 ; G 2656
+U 9738 ; WX 602 ; N uni260A ; G 2657
+U 9739 ; WX 602 ; N uni260B ; G 2658
+U 9740 ; WX 602 ; N uni260C ; G 2659
+U 9741 ; WX 602 ; N uni260D ; G 2660
+U 9742 ; WX 602 ; N uni260E ; G 2661
+U 9743 ; WX 602 ; N uni260F ; G 2662
+U 9744 ; WX 602 ; N uni2610 ; G 2663
+U 9745 ; WX 602 ; N uni2611 ; G 2664
+U 9746 ; WX 602 ; N uni2612 ; G 2665
+U 9747 ; WX 602 ; N uni2613 ; G 2666
+U 9748 ; WX 602 ; N uni2614 ; G 2667
+U 9749 ; WX 602 ; N uni2615 ; G 2668
+U 9750 ; WX 602 ; N uni2616 ; G 2669
+U 9751 ; WX 602 ; N uni2617 ; G 2670
+U 9752 ; WX 602 ; N uni2618 ; G 2671
+U 9753 ; WX 602 ; N uni2619 ; G 2672
+U 9754 ; WX 602 ; N uni261A ; G 2673
+U 9755 ; WX 602 ; N uni261B ; G 2674
+U 9756 ; WX 602 ; N uni261C ; G 2675
+U 9757 ; WX 602 ; N uni261D ; G 2676
+U 9758 ; WX 602 ; N uni261E ; G 2677
+U 9759 ; WX 602 ; N uni261F ; G 2678
+U 9760 ; WX 602 ; N uni2620 ; G 2679
+U 9761 ; WX 602 ; N uni2621 ; G 2680
+U 9762 ; WX 602 ; N uni2622 ; G 2681
+U 9763 ; WX 602 ; N uni2623 ; G 2682
+U 9764 ; WX 602 ; N uni2624 ; G 2683
+U 9765 ; WX 602 ; N uni2625 ; G 2684
+U 9766 ; WX 602 ; N uni2626 ; G 2685
+U 9767 ; WX 602 ; N uni2627 ; G 2686
+U 9768 ; WX 602 ; N uni2628 ; G 2687
+U 9769 ; WX 602 ; N uni2629 ; G 2688
+U 9770 ; WX 602 ; N uni262A ; G 2689
+U 9771 ; WX 602 ; N uni262B ; G 2690
+U 9772 ; WX 602 ; N uni262C ; G 2691
+U 9773 ; WX 602 ; N uni262D ; G 2692
+U 9774 ; WX 602 ; N uni262E ; G 2693
+U 9775 ; WX 602 ; N uni262F ; G 2694
+U 9784 ; WX 602 ; N uni2638 ; G 2695
+U 9785 ; WX 602 ; N uni2639 ; G 2696
+U 9786 ; WX 602 ; N smileface ; G 2697
+U 9787 ; WX 602 ; N invsmileface ; G 2698
+U 9788 ; WX 602 ; N sun ; G 2699
+U 9789 ; WX 602 ; N uni263D ; G 2700
+U 9790 ; WX 602 ; N uni263E ; G 2701
+U 9791 ; WX 602 ; N uni263F ; G 2702
+U 9792 ; WX 602 ; N female ; G 2703
+U 9793 ; WX 602 ; N uni2641 ; G 2704
+U 9794 ; WX 602 ; N male ; G 2705
+U 9795 ; WX 602 ; N uni2643 ; G 2706
+U 9796 ; WX 602 ; N uni2644 ; G 2707
+U 9797 ; WX 602 ; N uni2645 ; G 2708
+U 9798 ; WX 602 ; N uni2646 ; G 2709
+U 9799 ; WX 602 ; N uni2647 ; G 2710
+U 9800 ; WX 602 ; N uni2648 ; G 2711
+U 9801 ; WX 602 ; N uni2649 ; G 2712
+U 9802 ; WX 602 ; N uni264A ; G 2713
+U 9803 ; WX 602 ; N uni264B ; G 2714
+U 9804 ; WX 602 ; N uni264C ; G 2715
+U 9805 ; WX 602 ; N uni264D ; G 2716
+U 9806 ; WX 602 ; N uni264E ; G 2717
+U 9807 ; WX 602 ; N uni264F ; G 2718
+U 9808 ; WX 602 ; N uni2650 ; G 2719
+U 9809 ; WX 602 ; N uni2651 ; G 2720
+U 9810 ; WX 602 ; N uni2652 ; G 2721
+U 9811 ; WX 602 ; N uni2653 ; G 2722
+U 9812 ; WX 602 ; N uni2654 ; G 2723
+U 9813 ; WX 602 ; N uni2655 ; G 2724
+U 9814 ; WX 602 ; N uni2656 ; G 2725
+U 9815 ; WX 602 ; N uni2657 ; G 2726
+U 9816 ; WX 602 ; N uni2658 ; G 2727
+U 9817 ; WX 602 ; N uni2659 ; G 2728
+U 9818 ; WX 602 ; N uni265A ; G 2729
+U 9819 ; WX 602 ; N uni265B ; G 2730
+U 9820 ; WX 602 ; N uni265C ; G 2731
+U 9821 ; WX 602 ; N uni265D ; G 2732
+U 9822 ; WX 602 ; N uni265E ; G 2733
+U 9823 ; WX 602 ; N uni265F ; G 2734
+U 9824 ; WX 602 ; N spade ; G 2735
+U 9825 ; WX 602 ; N uni2661 ; G 2736
+U 9826 ; WX 602 ; N uni2662 ; G 2737
+U 9827 ; WX 602 ; N club ; G 2738
+U 9828 ; WX 602 ; N uni2664 ; G 2739
+U 9829 ; WX 602 ; N heart ; G 2740
+U 9830 ; WX 602 ; N diamond ; G 2741
+U 9831 ; WX 602 ; N uni2667 ; G 2742
+U 9832 ; WX 602 ; N uni2668 ; G 2743
+U 9833 ; WX 602 ; N uni2669 ; G 2744
+U 9834 ; WX 602 ; N musicalnote ; G 2745
+U 9835 ; WX 602 ; N musicalnotedbl ; G 2746
+U 9836 ; WX 602 ; N uni266C ; G 2747
+U 9837 ; WX 602 ; N uni266D ; G 2748
+U 9838 ; WX 602 ; N uni266E ; G 2749
+U 9839 ; WX 602 ; N uni266F ; G 2750
+U 9840 ; WX 602 ; N uni2670 ; G 2751
+U 9841 ; WX 602 ; N uni2671 ; G 2752
+U 9842 ; WX 602 ; N uni2672 ; G 2753
+U 9843 ; WX 602 ; N uni2673 ; G 2754
+U 9844 ; WX 602 ; N uni2674 ; G 2755
+U 9845 ; WX 602 ; N uni2675 ; G 2756
+U 9846 ; WX 602 ; N uni2676 ; G 2757
+U 9847 ; WX 602 ; N uni2677 ; G 2758
+U 9848 ; WX 602 ; N uni2678 ; G 2759
+U 9849 ; WX 602 ; N uni2679 ; G 2760
+U 9850 ; WX 602 ; N uni267A ; G 2761
+U 9851 ; WX 602 ; N uni267B ; G 2762
+U 9852 ; WX 602 ; N uni267C ; G 2763
+U 9853 ; WX 602 ; N uni267D ; G 2764
+U 9854 ; WX 602 ; N uni267E ; G 2765
+U 9855 ; WX 602 ; N uni267F ; G 2766
+U 9856 ; WX 602 ; N uni2680 ; G 2767
+U 9857 ; WX 602 ; N uni2681 ; G 2768
+U 9858 ; WX 602 ; N uni2682 ; G 2769
+U 9859 ; WX 602 ; N uni2683 ; G 2770
+U 9860 ; WX 602 ; N uni2684 ; G 2771
+U 9861 ; WX 602 ; N uni2685 ; G 2772
+U 9862 ; WX 602 ; N uni2686 ; G 2773
+U 9863 ; WX 602 ; N uni2687 ; G 2774
+U 9864 ; WX 602 ; N uni2688 ; G 2775
+U 9865 ; WX 602 ; N uni2689 ; G 2776
+U 9866 ; WX 602 ; N uni268A ; G 2777
+U 9867 ; WX 602 ; N uni268B ; G 2778
+U 9872 ; WX 602 ; N uni2690 ; G 2779
+U 9873 ; WX 602 ; N uni2691 ; G 2780
+U 9874 ; WX 602 ; N uni2692 ; G 2781
+U 9875 ; WX 602 ; N uni2693 ; G 2782
+U 9876 ; WX 602 ; N uni2694 ; G 2783
+U 9877 ; WX 602 ; N uni2695 ; G 2784
+U 9878 ; WX 602 ; N uni2696 ; G 2785
+U 9879 ; WX 602 ; N uni2697 ; G 2786
+U 9880 ; WX 602 ; N uni2698 ; G 2787
+U 9881 ; WX 602 ; N uni2699 ; G 2788
+U 9882 ; WX 602 ; N uni269A ; G 2789
+U 9883 ; WX 602 ; N uni269B ; G 2790
+U 9884 ; WX 602 ; N uni269C ; G 2791
+U 9888 ; WX 602 ; N uni26A0 ; G 2792
+U 9889 ; WX 602 ; N uni26A1 ; G 2793
+U 9904 ; WX 602 ; N uni26B0 ; G 2794
+U 9905 ; WX 602 ; N uni26B1 ; G 2795
+U 9985 ; WX 602 ; N uni2701 ; G 2796
+U 9986 ; WX 602 ; N uni2702 ; G 2797
+U 9987 ; WX 602 ; N uni2703 ; G 2798
+U 9988 ; WX 602 ; N uni2704 ; G 2799
+U 9990 ; WX 602 ; N uni2706 ; G 2800
+U 9991 ; WX 602 ; N uni2707 ; G 2801
+U 9992 ; WX 602 ; N uni2708 ; G 2802
+U 9993 ; WX 602 ; N uni2709 ; G 2803
+U 9996 ; WX 602 ; N uni270C ; G 2804
+U 9997 ; WX 602 ; N uni270D ; G 2805
+U 9998 ; WX 602 ; N uni270E ; G 2806
+U 9999 ; WX 602 ; N uni270F ; G 2807
+U 10000 ; WX 602 ; N uni2710 ; G 2808
+U 10001 ; WX 602 ; N uni2711 ; G 2809
+U 10002 ; WX 602 ; N uni2712 ; G 2810
+U 10003 ; WX 602 ; N uni2713 ; G 2811
+U 10004 ; WX 602 ; N uni2714 ; G 2812
+U 10005 ; WX 602 ; N uni2715 ; G 2813
+U 10006 ; WX 602 ; N uni2716 ; G 2814
+U 10007 ; WX 602 ; N uni2717 ; G 2815
+U 10008 ; WX 602 ; N uni2718 ; G 2816
+U 10009 ; WX 602 ; N uni2719 ; G 2817
+U 10010 ; WX 602 ; N uni271A ; G 2818
+U 10011 ; WX 602 ; N uni271B ; G 2819
+U 10012 ; WX 602 ; N uni271C ; G 2820
+U 10013 ; WX 602 ; N uni271D ; G 2821
+U 10014 ; WX 602 ; N uni271E ; G 2822
+U 10015 ; WX 602 ; N uni271F ; G 2823
+U 10016 ; WX 602 ; N uni2720 ; G 2824
+U 10017 ; WX 602 ; N uni2721 ; G 2825
+U 10018 ; WX 602 ; N uni2722 ; G 2826
+U 10019 ; WX 602 ; N uni2723 ; G 2827
+U 10020 ; WX 602 ; N uni2724 ; G 2828
+U 10021 ; WX 602 ; N uni2725 ; G 2829
+U 10022 ; WX 602 ; N uni2726 ; G 2830
+U 10023 ; WX 602 ; N uni2727 ; G 2831
+U 10025 ; WX 602 ; N uni2729 ; G 2832
+U 10026 ; WX 602 ; N uni272A ; G 2833
+U 10027 ; WX 602 ; N uni272B ; G 2834
+U 10028 ; WX 602 ; N uni272C ; G 2835
+U 10029 ; WX 602 ; N uni272D ; G 2836
+U 10030 ; WX 602 ; N uni272E ; G 2837
+U 10031 ; WX 602 ; N uni272F ; G 2838
+U 10032 ; WX 602 ; N uni2730 ; G 2839
+U 10033 ; WX 602 ; N uni2731 ; G 2840
+U 10034 ; WX 602 ; N uni2732 ; G 2841
+U 10035 ; WX 602 ; N uni2733 ; G 2842
+U 10036 ; WX 602 ; N uni2734 ; G 2843
+U 10037 ; WX 602 ; N uni2735 ; G 2844
+U 10038 ; WX 602 ; N uni2736 ; G 2845
+U 10039 ; WX 602 ; N uni2737 ; G 2846
+U 10040 ; WX 602 ; N uni2738 ; G 2847
+U 10041 ; WX 602 ; N uni2739 ; G 2848
+U 10042 ; WX 602 ; N uni273A ; G 2849
+U 10043 ; WX 602 ; N uni273B ; G 2850
+U 10044 ; WX 602 ; N uni273C ; G 2851
+U 10045 ; WX 602 ; N uni273D ; G 2852
+U 10046 ; WX 602 ; N uni273E ; G 2853
+U 10047 ; WX 602 ; N uni273F ; G 2854
+U 10048 ; WX 602 ; N uni2740 ; G 2855
+U 10049 ; WX 602 ; N uni2741 ; G 2856
+U 10050 ; WX 602 ; N uni2742 ; G 2857
+U 10051 ; WX 602 ; N uni2743 ; G 2858
+U 10052 ; WX 602 ; N uni2744 ; G 2859
+U 10053 ; WX 602 ; N uni2745 ; G 2860
+U 10054 ; WX 602 ; N uni2746 ; G 2861
+U 10055 ; WX 602 ; N uni2747 ; G 2862
+U 10056 ; WX 602 ; N uni2748 ; G 2863
+U 10057 ; WX 602 ; N uni2749 ; G 2864
+U 10058 ; WX 602 ; N uni274A ; G 2865
+U 10059 ; WX 602 ; N uni274B ; G 2866
+U 10061 ; WX 602 ; N uni274D ; G 2867
+U 10063 ; WX 602 ; N uni274F ; G 2868
+U 10064 ; WX 602 ; N uni2750 ; G 2869
+U 10065 ; WX 602 ; N uni2751 ; G 2870
+U 10066 ; WX 602 ; N uni2752 ; G 2871
+U 10070 ; WX 602 ; N uni2756 ; G 2872
+U 10072 ; WX 602 ; N uni2758 ; G 2873
+U 10073 ; WX 602 ; N uni2759 ; G 2874
+U 10074 ; WX 602 ; N uni275A ; G 2875
+U 10075 ; WX 602 ; N uni275B ; G 2876
+U 10076 ; WX 602 ; N uni275C ; G 2877
+U 10077 ; WX 602 ; N uni275D ; G 2878
+U 10078 ; WX 602 ; N uni275E ; G 2879
+U 10081 ; WX 602 ; N uni2761 ; G 2880
+U 10082 ; WX 602 ; N uni2762 ; G 2881
+U 10083 ; WX 602 ; N uni2763 ; G 2882
+U 10084 ; WX 602 ; N uni2764 ; G 2883
+U 10085 ; WX 602 ; N uni2765 ; G 2884
+U 10086 ; WX 602 ; N uni2766 ; G 2885
+U 10087 ; WX 602 ; N uni2767 ; G 2886
+U 10088 ; WX 602 ; N uni2768 ; G 2887
+U 10089 ; WX 602 ; N uni2769 ; G 2888
+U 10090 ; WX 602 ; N uni276A ; G 2889
+U 10091 ; WX 602 ; N uni276B ; G 2890
+U 10092 ; WX 602 ; N uni276C ; G 2891
+U 10093 ; WX 602 ; N uni276D ; G 2892
+U 10094 ; WX 602 ; N uni276E ; G 2893
+U 10095 ; WX 602 ; N uni276F ; G 2894
+U 10096 ; WX 602 ; N uni2770 ; G 2895
+U 10097 ; WX 602 ; N uni2771 ; G 2896
+U 10098 ; WX 602 ; N uni2772 ; G 2897
+U 10099 ; WX 602 ; N uni2773 ; G 2898
+U 10100 ; WX 602 ; N uni2774 ; G 2899
+U 10101 ; WX 602 ; N uni2775 ; G 2900
+U 10132 ; WX 602 ; N uni2794 ; G 2901
+U 10136 ; WX 602 ; N uni2798 ; G 2902
+U 10137 ; WX 602 ; N uni2799 ; G 2903
+U 10138 ; WX 602 ; N uni279A ; G 2904
+U 10139 ; WX 602 ; N uni279B ; G 2905
+U 10140 ; WX 602 ; N uni279C ; G 2906
+U 10141 ; WX 602 ; N uni279D ; G 2907
+U 10142 ; WX 602 ; N uni279E ; G 2908
+U 10143 ; WX 602 ; N uni279F ; G 2909
+U 10144 ; WX 602 ; N uni27A0 ; G 2910
+U 10145 ; WX 602 ; N uni27A1 ; G 2911
+U 10146 ; WX 602 ; N uni27A2 ; G 2912
+U 10147 ; WX 602 ; N uni27A3 ; G 2913
+U 10148 ; WX 602 ; N uni27A4 ; G 2914
+U 10149 ; WX 602 ; N uni27A5 ; G 2915
+U 10150 ; WX 602 ; N uni27A6 ; G 2916
+U 10151 ; WX 602 ; N uni27A7 ; G 2917
+U 10152 ; WX 602 ; N uni27A8 ; G 2918
+U 10153 ; WX 602 ; N uni27A9 ; G 2919
+U 10154 ; WX 602 ; N uni27AA ; G 2920
+U 10155 ; WX 602 ; N uni27AB ; G 2921
+U 10156 ; WX 602 ; N uni27AC ; G 2922
+U 10157 ; WX 602 ; N uni27AD ; G 2923
+U 10158 ; WX 602 ; N uni27AE ; G 2924
+U 10159 ; WX 602 ; N uni27AF ; G 2925
+U 10161 ; WX 602 ; N uni27B1 ; G 2926
+U 10162 ; WX 602 ; N uni27B2 ; G 2927
+U 10163 ; WX 602 ; N uni27B3 ; G 2928
+U 10164 ; WX 602 ; N uni27B4 ; G 2929
+U 10165 ; WX 602 ; N uni27B5 ; G 2930
+U 10166 ; WX 602 ; N uni27B6 ; G 2931
+U 10167 ; WX 602 ; N uni27B7 ; G 2932
+U 10168 ; WX 602 ; N uni27B8 ; G 2933
+U 10169 ; WX 602 ; N uni27B9 ; G 2934
+U 10170 ; WX 602 ; N uni27BA ; G 2935
+U 10171 ; WX 602 ; N uni27BB ; G 2936
+U 10172 ; WX 602 ; N uni27BC ; G 2937
+U 10173 ; WX 602 ; N uni27BD ; G 2938
+U 10174 ; WX 602 ; N uni27BE ; G 2939
+U 10175 ; WX 602 ; N uni27BF ; G 2940
+U 10178 ; WX 602 ; N uni27C2 ; G 2941
+U 10181 ; WX 602 ; N uni27C5 ; G 2942
+U 10182 ; WX 602 ; N uni27C6 ; G 2943
+U 10204 ; WX 602 ; N uni27DC ; G 2944
+U 10208 ; WX 602 ; N uni27E0 ; G 2945
+U 10214 ; WX 602 ; N uni27E6 ; G 2946
+U 10215 ; WX 602 ; N uni27E7 ; G 2947
+U 10216 ; WX 602 ; N uni27E8 ; G 2948
+U 10217 ; WX 602 ; N uni27E9 ; G 2949
+U 10218 ; WX 602 ; N uni27EA ; G 2950
+U 10219 ; WX 602 ; N uni27EB ; G 2951
+U 10229 ; WX 602 ; N uni27F5 ; G 2952
+U 10230 ; WX 602 ; N uni27F6 ; G 2953
+U 10231 ; WX 602 ; N uni27F7 ; G 2954
+U 10631 ; WX 602 ; N uni2987 ; G 2955
+U 10632 ; WX 602 ; N uni2988 ; G 2956
+U 10647 ; WX 602 ; N uni2997 ; G 2957
+U 10648 ; WX 602 ; N uni2998 ; G 2958
+U 10731 ; WX 602 ; N uni29EB ; G 2959
+U 10746 ; WX 602 ; N uni29FA ; G 2960
+U 10747 ; WX 602 ; N uni29FB ; G 2961
+U 10752 ; WX 602 ; N uni2A00 ; G 2962
+U 10799 ; WX 602 ; N uni2A2F ; G 2963
+U 10858 ; WX 602 ; N uni2A6A ; G 2964
+U 10859 ; WX 602 ; N uni2A6B ; G 2965
+U 11013 ; WX 602 ; N uni2B05 ; G 2966
+U 11014 ; WX 602 ; N uni2B06 ; G 2967
+U 11015 ; WX 602 ; N uni2B07 ; G 2968
+U 11016 ; WX 602 ; N uni2B08 ; G 2969
+U 11017 ; WX 602 ; N uni2B09 ; G 2970
+U 11018 ; WX 602 ; N uni2B0A ; G 2971
+U 11019 ; WX 602 ; N uni2B0B ; G 2972
+U 11020 ; WX 602 ; N uni2B0C ; G 2973
+U 11021 ; WX 602 ; N uni2B0D ; G 2974
+U 11026 ; WX 602 ; N uni2B12 ; G 2975
+U 11027 ; WX 602 ; N uni2B13 ; G 2976
+U 11028 ; WX 602 ; N uni2B14 ; G 2977
+U 11029 ; WX 602 ; N uni2B15 ; G 2978
+U 11030 ; WX 602 ; N uni2B16 ; G 2979
+U 11031 ; WX 602 ; N uni2B17 ; G 2980
+U 11032 ; WX 602 ; N uni2B18 ; G 2981
+U 11033 ; WX 602 ; N uni2B19 ; G 2982
+U 11034 ; WX 602 ; N uni2B1A ; G 2983
+U 11364 ; WX 602 ; N uni2C64 ; G 2984
+U 11373 ; WX 602 ; N uni2C6D ; G 2985
+U 11374 ; WX 602 ; N uni2C6E ; G 2986
+U 11375 ; WX 602 ; N uni2C6F ; G 2987
+U 11376 ; WX 602 ; N uni2C70 ; G 2988
+U 11381 ; WX 602 ; N uni2C75 ; G 2989
+U 11382 ; WX 602 ; N uni2C76 ; G 2990
+U 11383 ; WX 602 ; N uni2C77 ; G 2991
+U 11385 ; WX 602 ; N uni2C79 ; G 2992
+U 11386 ; WX 602 ; N uni2C7A ; G 2993
+U 11388 ; WX 602 ; N uni2C7C ; G 2994
+U 11389 ; WX 602 ; N uni2C7D ; G 2995
+U 11390 ; WX 602 ; N uni2C7E ; G 2996
+U 11391 ; WX 602 ; N uni2C7F ; G 2997
+U 11800 ; WX 602 ; N uni2E18 ; G 2998
+U 11807 ; WX 602 ; N uni2E1F ; G 2999
+U 11810 ; WX 602 ; N uni2E22 ; G 3000
+U 11811 ; WX 602 ; N uni2E23 ; G 3001
+U 11812 ; WX 602 ; N uni2E24 ; G 3002
+U 11813 ; WX 602 ; N uni2E25 ; G 3003
+U 11822 ; WX 602 ; N uni2E2E ; G 3004
+U 42760 ; WX 602 ; N uniA708 ; G 3005
+U 42761 ; WX 602 ; N uniA709 ; G 3006
+U 42762 ; WX 602 ; N uniA70A ; G 3007
+U 42763 ; WX 602 ; N uniA70B ; G 3008
+U 42764 ; WX 602 ; N uniA70C ; G 3009
+U 42765 ; WX 602 ; N uniA70D ; G 3010
+U 42766 ; WX 602 ; N uniA70E ; G 3011
+U 42767 ; WX 602 ; N uniA70F ; G 3012
+U 42768 ; WX 602 ; N uniA710 ; G 3013
+U 42769 ; WX 602 ; N uniA711 ; G 3014
+U 42770 ; WX 602 ; N uniA712 ; G 3015
+U 42771 ; WX 602 ; N uniA713 ; G 3016
+U 42772 ; WX 602 ; N uniA714 ; G 3017
+U 42773 ; WX 602 ; N uniA715 ; G 3018
+U 42774 ; WX 602 ; N uniA716 ; G 3019
+U 42779 ; WX 602 ; N uniA71B ; G 3020
+U 42780 ; WX 602 ; N uniA71C ; G 3021
+U 42781 ; WX 602 ; N uniA71D ; G 3022
+U 42782 ; WX 602 ; N uniA71E ; G 3023
+U 42783 ; WX 602 ; N uniA71F ; G 3024
+U 42786 ; WX 602 ; N uniA722 ; G 3025
+U 42787 ; WX 602 ; N uniA723 ; G 3026
+U 42788 ; WX 602 ; N uniA724 ; G 3027
+U 42789 ; WX 602 ; N uniA725 ; G 3028
+U 42790 ; WX 602 ; N uniA726 ; G 3029
+U 42791 ; WX 602 ; N uniA727 ; G 3030
+U 42889 ; WX 602 ; N uniA789 ; G 3031
+U 42890 ; WX 602 ; N uniA78A ; G 3032
+U 42891 ; WX 602 ; N uniA78B ; G 3033
+U 42892 ; WX 602 ; N uniA78C ; G 3034
+U 42893 ; WX 602 ; N uniA78D ; G 3035
+U 42894 ; WX 602 ; N uniA78E ; G 3036
+U 42896 ; WX 602 ; N uniA790 ; G 3037
+U 42897 ; WX 602 ; N uniA791 ; G 3038
+U 42922 ; WX 602 ; N uniA7AA ; G 3039
+U 43000 ; WX 602 ; N uniA7F8 ; G 3040
+U 43001 ; WX 602 ; N uniA7F9 ; G 3041
+U 63173 ; WX 602 ; N uniF6C5 ; G 3042
+U 64257 ; WX 602 ; N fi ; G 3043
+U 64258 ; WX 602 ; N fl ; G 3044
+U 64338 ; WX 602 ; N uniFB52 ; G 3045
+U 64339 ; WX 602 ; N uniFB53 ; G 3046
+U 64340 ; WX 602 ; N uniFB54 ; G 3047
+U 64341 ; WX 602 ; N uniFB55 ; G 3048
+U 64342 ; WX 602 ; N uniFB56 ; G 3049
+U 64343 ; WX 602 ; N uniFB57 ; G 3050
+U 64344 ; WX 602 ; N uniFB58 ; G 3051
+U 64345 ; WX 602 ; N uniFB59 ; G 3052
+U 64346 ; WX 602 ; N uniFB5A ; G 3053
+U 64347 ; WX 602 ; N uniFB5B ; G 3054
+U 64348 ; WX 602 ; N uniFB5C ; G 3055
+U 64349 ; WX 602 ; N uniFB5D ; G 3056
+U 64350 ; WX 602 ; N uniFB5E ; G 3057
+U 64351 ; WX 602 ; N uniFB5F ; G 3058
+U 64352 ; WX 602 ; N uniFB60 ; G 3059
+U 64353 ; WX 602 ; N uniFB61 ; G 3060
+U 64354 ; WX 602 ; N uniFB62 ; G 3061
+U 64355 ; WX 602 ; N uniFB63 ; G 3062
+U 64356 ; WX 602 ; N uniFB64 ; G 3063
+U 64357 ; WX 602 ; N uniFB65 ; G 3064
+U 64358 ; WX 602 ; N uniFB66 ; G 3065
+U 64359 ; WX 602 ; N uniFB67 ; G 3066
+U 64360 ; WX 602 ; N uniFB68 ; G 3067
+U 64361 ; WX 602 ; N uniFB69 ; G 3068
+U 64362 ; WX 602 ; N uniFB6A ; G 3069
+U 64363 ; WX 602 ; N uniFB6B ; G 3070
+U 64364 ; WX 602 ; N uniFB6C ; G 3071
+U 64365 ; WX 602 ; N uniFB6D ; G 3072
+U 64366 ; WX 602 ; N uniFB6E ; G 3073
+U 64367 ; WX 602 ; N uniFB6F ; G 3074
+U 64368 ; WX 602 ; N uniFB70 ; G 3075
+U 64369 ; WX 602 ; N uniFB71 ; G 3076
+U 64370 ; WX 602 ; N uniFB72 ; G 3077
+U 64371 ; WX 602 ; N uniFB73 ; G 3078
+U 64372 ; WX 602 ; N uniFB74 ; G 3079
+U 64373 ; WX 602 ; N uniFB75 ; G 3080
+U 64374 ; WX 602 ; N uniFB76 ; G 3081
+U 64375 ; WX 602 ; N uniFB77 ; G 3082
+U 64376 ; WX 602 ; N uniFB78 ; G 3083
+U 64377 ; WX 602 ; N uniFB79 ; G 3084
+U 64378 ; WX 602 ; N uniFB7A ; G 3085
+U 64379 ; WX 602 ; N uniFB7B ; G 3086
+U 64380 ; WX 602 ; N uniFB7C ; G 3087
+U 64381 ; WX 602 ; N uniFB7D ; G 3088
+U 64382 ; WX 602 ; N uniFB7E ; G 3089
+U 64383 ; WX 602 ; N uniFB7F ; G 3090
+U 64384 ; WX 602 ; N uniFB80 ; G 3091
+U 64385 ; WX 602 ; N uniFB81 ; G 3092
+U 64394 ; WX 602 ; N uniFB8A ; G 3093
+U 64395 ; WX 602 ; N uniFB8B ; G 3094
+U 64396 ; WX 602 ; N uniFB8C ; G 3095
+U 64397 ; WX 602 ; N uniFB8D ; G 3096
+U 64398 ; WX 602 ; N uniFB8E ; G 3097
+U 64399 ; WX 602 ; N uniFB8F ; G 3098
+U 64400 ; WX 602 ; N uniFB90 ; G 3099
+U 64401 ; WX 602 ; N uniFB91 ; G 3100
+U 64402 ; WX 602 ; N uniFB92 ; G 3101
+U 64403 ; WX 602 ; N uniFB93 ; G 3102
+U 64404 ; WX 602 ; N uniFB94 ; G 3103
+U 64405 ; WX 602 ; N uniFB95 ; G 3104
+U 64414 ; WX 602 ; N uniFB9E ; G 3105
+U 64415 ; WX 602 ; N uniFB9F ; G 3106
+U 64426 ; WX 602 ; N uniFBAA ; G 3107
+U 64427 ; WX 602 ; N uniFBAB ; G 3108
+U 64428 ; WX 602 ; N uniFBAC ; G 3109
+U 64429 ; WX 602 ; N uniFBAD ; G 3110
+U 64488 ; WX 602 ; N uniFBE8 ; G 3111
+U 64489 ; WX 602 ; N uniFBE9 ; G 3112
+U 64508 ; WX 602 ; N uniFBFC ; G 3113
+U 64509 ; WX 602 ; N uniFBFD ; G 3114
+U 64510 ; WX 602 ; N uniFBFE ; G 3115
+U 64511 ; WX 602 ; N uniFBFF ; G 3116
+U 65136 ; WX 602 ; N uniFE70 ; G 3117
+U 65137 ; WX 602 ; N uniFE71 ; G 3118
+U 65138 ; WX 602 ; N uniFE72 ; G 3119
+U 65139 ; WX 602 ; N uniFE73 ; G 3120
+U 65140 ; WX 602 ; N uniFE74 ; G 3121
+U 65142 ; WX 602 ; N uniFE76 ; G 3122
+U 65143 ; WX 602 ; N uniFE77 ; G 3123
+U 65144 ; WX 602 ; N uniFE78 ; G 3124
+U 65145 ; WX 602 ; N uniFE79 ; G 3125
+U 65146 ; WX 602 ; N uniFE7A ; G 3126
+U 65147 ; WX 602 ; N uniFE7B ; G 3127
+U 65148 ; WX 602 ; N uniFE7C ; G 3128
+U 65149 ; WX 602 ; N uniFE7D ; G 3129
+U 65150 ; WX 602 ; N uniFE7E ; G 3130
+U 65151 ; WX 602 ; N uniFE7F ; G 3131
+U 65152 ; WX 602 ; N uniFE80 ; G 3132
+U 65153 ; WX 602 ; N uniFE81 ; G 3133
+U 65154 ; WX 602 ; N uniFE82 ; G 3134
+U 65155 ; WX 602 ; N uniFE83 ; G 3135
+U 65156 ; WX 602 ; N uniFE84 ; G 3136
+U 65157 ; WX 602 ; N uniFE85 ; G 3137
+U 65158 ; WX 602 ; N uniFE86 ; G 3138
+U 65159 ; WX 602 ; N uniFE87 ; G 3139
+U 65160 ; WX 602 ; N uniFE88 ; G 3140
+U 65161 ; WX 602 ; N uniFE89 ; G 3141
+U 65162 ; WX 602 ; N uniFE8A ; G 3142
+U 65163 ; WX 602 ; N uniFE8B ; G 3143
+U 65164 ; WX 602 ; N uniFE8C ; G 3144
+U 65165 ; WX 602 ; N uniFE8D ; G 3145
+U 65166 ; WX 602 ; N uniFE8E ; G 3146
+U 65167 ; WX 602 ; N uniFE8F ; G 3147
+U 65168 ; WX 602 ; N uniFE90 ; G 3148
+U 65169 ; WX 602 ; N uniFE91 ; G 3149
+U 65170 ; WX 602 ; N uniFE92 ; G 3150
+U 65171 ; WX 602 ; N uniFE93 ; G 3151
+U 65172 ; WX 602 ; N uniFE94 ; G 3152
+U 65173 ; WX 602 ; N uniFE95 ; G 3153
+U 65174 ; WX 602 ; N uniFE96 ; G 3154
+U 65175 ; WX 602 ; N uniFE97 ; G 3155
+U 65176 ; WX 602 ; N uniFE98 ; G 3156
+U 65177 ; WX 602 ; N uniFE99 ; G 3157
+U 65178 ; WX 602 ; N uniFE9A ; G 3158
+U 65179 ; WX 602 ; N uniFE9B ; G 3159
+U 65180 ; WX 602 ; N uniFE9C ; G 3160
+U 65181 ; WX 602 ; N uniFE9D ; G 3161
+U 65182 ; WX 602 ; N uniFE9E ; G 3162
+U 65183 ; WX 602 ; N uniFE9F ; G 3163
+U 65184 ; WX 602 ; N uniFEA0 ; G 3164
+U 65185 ; WX 602 ; N uniFEA1 ; G 3165
+U 65186 ; WX 602 ; N uniFEA2 ; G 3166
+U 65187 ; WX 602 ; N uniFEA3 ; G 3167
+U 65188 ; WX 602 ; N uniFEA4 ; G 3168
+U 65189 ; WX 602 ; N uniFEA5 ; G 3169
+U 65190 ; WX 602 ; N uniFEA6 ; G 3170
+U 65191 ; WX 602 ; N uniFEA7 ; G 3171
+U 65192 ; WX 602 ; N uniFEA8 ; G 3172
+U 65193 ; WX 602 ; N uniFEA9 ; G 3173
+U 65194 ; WX 602 ; N uniFEAA ; G 3174
+U 65195 ; WX 602 ; N uniFEAB ; G 3175
+U 65196 ; WX 602 ; N uniFEAC ; G 3176
+U 65197 ; WX 602 ; N uniFEAD ; G 3177
+U 65198 ; WX 602 ; N uniFEAE ; G 3178
+U 65199 ; WX 602 ; N uniFEAF ; G 3179
+U 65200 ; WX 602 ; N uniFEB0 ; G 3180
+U 65201 ; WX 602 ; N uniFEB1 ; G 3181
+U 65202 ; WX 602 ; N uniFEB2 ; G 3182
+U 65203 ; WX 602 ; N uniFEB3 ; G 3183
+U 65204 ; WX 602 ; N uniFEB4 ; G 3184
+U 65205 ; WX 602 ; N uniFEB5 ; G 3185
+U 65206 ; WX 602 ; N uniFEB6 ; G 3186
+U 65207 ; WX 602 ; N uniFEB7 ; G 3187
+U 65208 ; WX 602 ; N uniFEB8 ; G 3188
+U 65209 ; WX 602 ; N uniFEB9 ; G 3189
+U 65210 ; WX 602 ; N uniFEBA ; G 3190
+U 65211 ; WX 602 ; N uniFEBB ; G 3191
+U 65212 ; WX 602 ; N uniFEBC ; G 3192
+U 65213 ; WX 602 ; N uniFEBD ; G 3193
+U 65214 ; WX 602 ; N uniFEBE ; G 3194
+U 65215 ; WX 602 ; N uniFEBF ; G 3195
+U 65216 ; WX 602 ; N uniFEC0 ; G 3196
+U 65217 ; WX 602 ; N uniFEC1 ; G 3197
+U 65218 ; WX 602 ; N uniFEC2 ; G 3198
+U 65219 ; WX 602 ; N uniFEC3 ; G 3199
+U 65220 ; WX 602 ; N uniFEC4 ; G 3200
+U 65221 ; WX 602 ; N uniFEC5 ; G 3201
+U 65222 ; WX 602 ; N uniFEC6 ; G 3202
+U 65223 ; WX 602 ; N uniFEC7 ; G 3203
+U 65224 ; WX 602 ; N uniFEC8 ; G 3204
+U 65225 ; WX 602 ; N uniFEC9 ; G 3205
+U 65226 ; WX 602 ; N uniFECA ; G 3206
+U 65227 ; WX 602 ; N uniFECB ; G 3207
+U 65228 ; WX 602 ; N uniFECC ; G 3208
+U 65229 ; WX 602 ; N uniFECD ; G 3209
+U 65230 ; WX 602 ; N uniFECE ; G 3210
+U 65231 ; WX 602 ; N uniFECF ; G 3211
+U 65232 ; WX 602 ; N uniFED0 ; G 3212
+U 65233 ; WX 602 ; N uniFED1 ; G 3213
+U 65234 ; WX 602 ; N uniFED2 ; G 3214
+U 65235 ; WX 602 ; N uniFED3 ; G 3215
+U 65236 ; WX 602 ; N uniFED4 ; G 3216
+U 65237 ; WX 602 ; N uniFED5 ; G 3217
+U 65238 ; WX 602 ; N uniFED6 ; G 3218
+U 65239 ; WX 602 ; N uniFED7 ; G 3219
+U 65240 ; WX 602 ; N uniFED8 ; G 3220
+U 65241 ; WX 602 ; N uniFED9 ; G 3221
+U 65242 ; WX 602 ; N uniFEDA ; G 3222
+U 65243 ; WX 602 ; N uniFEDB ; G 3223
+U 65244 ; WX 602 ; N uniFEDC ; G 3224
+U 65245 ; WX 602 ; N uniFEDD ; G 3225
+U 65246 ; WX 602 ; N uniFEDE ; G 3226
+U 65247 ; WX 602 ; N uniFEDF ; G 3227
+U 65248 ; WX 602 ; N uniFEE0 ; G 3228
+U 65249 ; WX 602 ; N uniFEE1 ; G 3229
+U 65250 ; WX 602 ; N uniFEE2 ; G 3230
+U 65251 ; WX 602 ; N uniFEE3 ; G 3231
+U 65252 ; WX 602 ; N uniFEE4 ; G 3232
+U 65253 ; WX 602 ; N uniFEE5 ; G 3233
+U 65254 ; WX 602 ; N uniFEE6 ; G 3234
+U 65255 ; WX 602 ; N uniFEE7 ; G 3235
+U 65256 ; WX 602 ; N uniFEE8 ; G 3236
+U 65257 ; WX 602 ; N uniFEE9 ; G 3237
+U 65258 ; WX 602 ; N uniFEEA ; G 3238
+U 65259 ; WX 602 ; N uniFEEB ; G 3239
+U 65260 ; WX 602 ; N uniFEEC ; G 3240
+U 65261 ; WX 602 ; N uniFEED ; G 3241
+U 65262 ; WX 602 ; N uniFEEE ; G 3242
+U 65263 ; WX 602 ; N uniFEEF ; G 3243
+U 65264 ; WX 602 ; N uniFEF0 ; G 3244
+U 65265 ; WX 602 ; N uniFEF1 ; G 3245
+U 65266 ; WX 602 ; N uniFEF2 ; G 3246
+U 65267 ; WX 602 ; N uniFEF3 ; G 3247
+U 65268 ; WX 602 ; N uniFEF4 ; G 3248
+U 65269 ; WX 602 ; N uniFEF5 ; G 3249
+U 65270 ; WX 602 ; N uniFEF6 ; G 3250
+U 65271 ; WX 602 ; N uniFEF7 ; G 3251
+U 65272 ; WX 602 ; N uniFEF8 ; G 3252
+U 65273 ; WX 602 ; N uniFEF9 ; G 3253
+U 65274 ; WX 602 ; N uniFEFA ; G 3254
+U 65275 ; WX 602 ; N uniFEFB ; G 3255
+U 65276 ; WX 602 ; N uniFEFC ; G 3256
+U 65279 ; WX 602 ; N uniFEFF ; G 3257
+U 65529 ; WX 602 ; N uniFFF9 ; G 3258
+U 65530 ; WX 602 ; N uniFFFA ; G 3259
+U 65531 ; WX 602 ; N uniFFFB ; G 3260
+U 65532 ; WX 602 ; N uniFFFC ; G 3261
+U 65533 ; WX 602 ; N uniFFFD ; G 3262
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ttf
new file mode 100644
index 0000000..754dca7
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ufm
new file mode 100644
index 0000000..3ae612a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-BoldOblique.ufm
@@ -0,0 +1,2707 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans Mono
+FontSubfamily Bold Oblique
+UniqueID DejaVu Sans Mono Bold Oblique
+FullName DejaVu Sans Mono Bold Oblique
+Version Version 2.37
+PostScriptName DejaVuSansMono-BoldOblique
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+Weight Bold
+ItalicAngle -11
+IsFixedPitch true
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -425 -394 808 1008
+StartCharMetrics 2711
+U 32 ; WX 602 ; N space ; G 3
+U 33 ; WX 602 ; N exclam ; G 4
+U 34 ; WX 602 ; N quotedbl ; G 5
+U 35 ; WX 602 ; N numbersign ; G 6
+U 36 ; WX 602 ; N dollar ; G 7
+U 37 ; WX 602 ; N percent ; G 8
+U 38 ; WX 602 ; N ampersand ; G 9
+U 39 ; WX 602 ; N quotesingle ; G 10
+U 40 ; WX 602 ; N parenleft ; G 11
+U 41 ; WX 602 ; N parenright ; G 12
+U 42 ; WX 602 ; N asterisk ; G 13
+U 43 ; WX 602 ; N plus ; G 14
+U 44 ; WX 602 ; N comma ; G 15
+U 45 ; WX 602 ; N hyphen ; G 16
+U 46 ; WX 602 ; N period ; G 17
+U 47 ; WX 602 ; N slash ; G 18
+U 48 ; WX 602 ; N zero ; G 19
+U 49 ; WX 602 ; N one ; G 20
+U 50 ; WX 602 ; N two ; G 21
+U 51 ; WX 602 ; N three ; G 22
+U 52 ; WX 602 ; N four ; G 23
+U 53 ; WX 602 ; N five ; G 24
+U 54 ; WX 602 ; N six ; G 25
+U 55 ; WX 602 ; N seven ; G 26
+U 56 ; WX 602 ; N eight ; G 27
+U 57 ; WX 602 ; N nine ; G 28
+U 58 ; WX 602 ; N colon ; G 29
+U 59 ; WX 602 ; N semicolon ; G 30
+U 60 ; WX 602 ; N less ; G 31
+U 61 ; WX 602 ; N equal ; G 32
+U 62 ; WX 602 ; N greater ; G 33
+U 63 ; WX 602 ; N question ; G 34
+U 64 ; WX 602 ; N at ; G 35
+U 65 ; WX 602 ; N A ; G 36
+U 66 ; WX 602 ; N B ; G 37
+U 67 ; WX 602 ; N C ; G 38
+U 68 ; WX 602 ; N D ; G 39
+U 69 ; WX 602 ; N E ; G 40
+U 70 ; WX 602 ; N F ; G 41
+U 71 ; WX 602 ; N G ; G 42
+U 72 ; WX 602 ; N H ; G 43
+U 73 ; WX 602 ; N I ; G 44
+U 74 ; WX 602 ; N J ; G 45
+U 75 ; WX 602 ; N K ; G 46
+U 76 ; WX 602 ; N L ; G 47
+U 77 ; WX 602 ; N M ; G 48
+U 78 ; WX 602 ; N N ; G 49
+U 79 ; WX 602 ; N O ; G 50
+U 80 ; WX 602 ; N P ; G 51
+U 81 ; WX 602 ; N Q ; G 52
+U 82 ; WX 602 ; N R ; G 53
+U 83 ; WX 602 ; N S ; G 54
+U 84 ; WX 602 ; N T ; G 55
+U 85 ; WX 602 ; N U ; G 56
+U 86 ; WX 602 ; N V ; G 57
+U 87 ; WX 602 ; N W ; G 58
+U 88 ; WX 602 ; N X ; G 59
+U 89 ; WX 602 ; N Y ; G 60
+U 90 ; WX 602 ; N Z ; G 61
+U 91 ; WX 602 ; N bracketleft ; G 62
+U 92 ; WX 602 ; N backslash ; G 63
+U 93 ; WX 602 ; N bracketright ; G 64
+U 94 ; WX 602 ; N asciicircum ; G 65
+U 95 ; WX 602 ; N underscore ; G 66
+U 96 ; WX 602 ; N grave ; G 67
+U 97 ; WX 602 ; N a ; G 68
+U 98 ; WX 602 ; N b ; G 69
+U 99 ; WX 602 ; N c ; G 70
+U 100 ; WX 602 ; N d ; G 71
+U 101 ; WX 602 ; N e ; G 72
+U 102 ; WX 602 ; N f ; G 73
+U 103 ; WX 602 ; N g ; G 74
+U 104 ; WX 602 ; N h ; G 75
+U 105 ; WX 602 ; N i ; G 76
+U 106 ; WX 602 ; N j ; G 77
+U 107 ; WX 602 ; N k ; G 78
+U 108 ; WX 602 ; N l ; G 79
+U 109 ; WX 602 ; N m ; G 80
+U 110 ; WX 602 ; N n ; G 81
+U 111 ; WX 602 ; N o ; G 82
+U 112 ; WX 602 ; N p ; G 83
+U 113 ; WX 602 ; N q ; G 84
+U 114 ; WX 602 ; N r ; G 85
+U 115 ; WX 602 ; N s ; G 86
+U 116 ; WX 602 ; N t ; G 87
+U 117 ; WX 602 ; N u ; G 88
+U 118 ; WX 602 ; N v ; G 89
+U 119 ; WX 602 ; N w ; G 90
+U 120 ; WX 602 ; N x ; G 91
+U 121 ; WX 602 ; N y ; G 92
+U 122 ; WX 602 ; N z ; G 93
+U 123 ; WX 602 ; N braceleft ; G 94
+U 124 ; WX 602 ; N bar ; G 95
+U 125 ; WX 602 ; N braceright ; G 96
+U 126 ; WX 602 ; N asciitilde ; G 97
+U 160 ; WX 602 ; N nbspace ; G 98
+U 161 ; WX 602 ; N exclamdown ; G 99
+U 162 ; WX 602 ; N cent ; G 100
+U 163 ; WX 602 ; N sterling ; G 101
+U 164 ; WX 602 ; N currency ; G 102
+U 165 ; WX 602 ; N yen ; G 103
+U 166 ; WX 602 ; N brokenbar ; G 104
+U 167 ; WX 602 ; N section ; G 105
+U 168 ; WX 602 ; N dieresis ; G 106
+U 169 ; WX 602 ; N copyright ; G 107
+U 170 ; WX 602 ; N ordfeminine ; G 108
+U 171 ; WX 602 ; N guillemotleft ; G 109
+U 172 ; WX 602 ; N logicalnot ; G 110
+U 173 ; WX 602 ; N sfthyphen ; G 111
+U 174 ; WX 602 ; N registered ; G 112
+U 175 ; WX 602 ; N macron ; G 113
+U 176 ; WX 602 ; N degree ; G 114
+U 177 ; WX 602 ; N plusminus ; G 115
+U 178 ; WX 602 ; N twosuperior ; G 116
+U 179 ; WX 602 ; N threesuperior ; G 117
+U 180 ; WX 602 ; N acute ; G 118
+U 181 ; WX 602 ; N mu ; G 119
+U 182 ; WX 602 ; N paragraph ; G 120
+U 183 ; WX 602 ; N periodcentered ; G 121
+U 184 ; WX 602 ; N cedilla ; G 122
+U 185 ; WX 602 ; N onesuperior ; G 123
+U 186 ; WX 602 ; N ordmasculine ; G 124
+U 187 ; WX 602 ; N guillemotright ; G 125
+U 188 ; WX 602 ; N onequarter ; G 126
+U 189 ; WX 602 ; N onehalf ; G 127
+U 190 ; WX 602 ; N threequarters ; G 128
+U 191 ; WX 602 ; N questiondown ; G 129
+U 192 ; WX 602 ; N Agrave ; G 130
+U 193 ; WX 602 ; N Aacute ; G 131
+U 194 ; WX 602 ; N Acircumflex ; G 132
+U 195 ; WX 602 ; N Atilde ; G 133
+U 196 ; WX 602 ; N Adieresis ; G 134
+U 197 ; WX 602 ; N Aring ; G 135
+U 198 ; WX 602 ; N AE ; G 136
+U 199 ; WX 602 ; N Ccedilla ; G 137
+U 200 ; WX 602 ; N Egrave ; G 138
+U 201 ; WX 602 ; N Eacute ; G 139
+U 202 ; WX 602 ; N Ecircumflex ; G 140
+U 203 ; WX 602 ; N Edieresis ; G 141
+U 204 ; WX 602 ; N Igrave ; G 142
+U 205 ; WX 602 ; N Iacute ; G 143
+U 206 ; WX 602 ; N Icircumflex ; G 144
+U 207 ; WX 602 ; N Idieresis ; G 145
+U 208 ; WX 602 ; N Eth ; G 146
+U 209 ; WX 602 ; N Ntilde ; G 147
+U 210 ; WX 602 ; N Ograve ; G 148
+U 211 ; WX 602 ; N Oacute ; G 149
+U 212 ; WX 602 ; N Ocircumflex ; G 150
+U 213 ; WX 602 ; N Otilde ; G 151
+U 214 ; WX 602 ; N Odieresis ; G 152
+U 215 ; WX 602 ; N multiply ; G 153
+U 216 ; WX 602 ; N Oslash ; G 154
+U 217 ; WX 602 ; N Ugrave ; G 155
+U 218 ; WX 602 ; N Uacute ; G 156
+U 219 ; WX 602 ; N Ucircumflex ; G 157
+U 220 ; WX 602 ; N Udieresis ; G 158
+U 221 ; WX 602 ; N Yacute ; G 159
+U 222 ; WX 602 ; N Thorn ; G 160
+U 223 ; WX 602 ; N germandbls ; G 161
+U 224 ; WX 602 ; N agrave ; G 162
+U 225 ; WX 602 ; N aacute ; G 163
+U 226 ; WX 602 ; N acircumflex ; G 164
+U 227 ; WX 602 ; N atilde ; G 165
+U 228 ; WX 602 ; N adieresis ; G 166
+U 229 ; WX 602 ; N aring ; G 167
+U 230 ; WX 602 ; N ae ; G 168
+U 231 ; WX 602 ; N ccedilla ; G 169
+U 232 ; WX 602 ; N egrave ; G 170
+U 233 ; WX 602 ; N eacute ; G 171
+U 234 ; WX 602 ; N ecircumflex ; G 172
+U 235 ; WX 602 ; N edieresis ; G 173
+U 236 ; WX 602 ; N igrave ; G 174
+U 237 ; WX 602 ; N iacute ; G 175
+U 238 ; WX 602 ; N icircumflex ; G 176
+U 239 ; WX 602 ; N idieresis ; G 177
+U 240 ; WX 602 ; N eth ; G 178
+U 241 ; WX 602 ; N ntilde ; G 179
+U 242 ; WX 602 ; N ograve ; G 180
+U 243 ; WX 602 ; N oacute ; G 181
+U 244 ; WX 602 ; N ocircumflex ; G 182
+U 245 ; WX 602 ; N otilde ; G 183
+U 246 ; WX 602 ; N odieresis ; G 184
+U 247 ; WX 602 ; N divide ; G 185
+U 248 ; WX 602 ; N oslash ; G 186
+U 249 ; WX 602 ; N ugrave ; G 187
+U 250 ; WX 602 ; N uacute ; G 188
+U 251 ; WX 602 ; N ucircumflex ; G 189
+U 252 ; WX 602 ; N udieresis ; G 190
+U 253 ; WX 602 ; N yacute ; G 191
+U 254 ; WX 602 ; N thorn ; G 192
+U 255 ; WX 602 ; N ydieresis ; G 193
+U 256 ; WX 602 ; N Amacron ; G 194
+U 257 ; WX 602 ; N amacron ; G 195
+U 258 ; WX 602 ; N Abreve ; G 196
+U 259 ; WX 602 ; N abreve ; G 197
+U 260 ; WX 602 ; N Aogonek ; G 198
+U 261 ; WX 602 ; N aogonek ; G 199
+U 262 ; WX 602 ; N Cacute ; G 200
+U 263 ; WX 602 ; N cacute ; G 201
+U 264 ; WX 602 ; N Ccircumflex ; G 202
+U 265 ; WX 602 ; N ccircumflex ; G 203
+U 266 ; WX 602 ; N Cdotaccent ; G 204
+U 267 ; WX 602 ; N cdotaccent ; G 205
+U 268 ; WX 602 ; N Ccaron ; G 206
+U 269 ; WX 602 ; N ccaron ; G 207
+U 270 ; WX 602 ; N Dcaron ; G 208
+U 271 ; WX 602 ; N dcaron ; G 209
+U 272 ; WX 602 ; N Dcroat ; G 210
+U 273 ; WX 602 ; N dmacron ; G 211
+U 274 ; WX 602 ; N Emacron ; G 212
+U 275 ; WX 602 ; N emacron ; G 213
+U 276 ; WX 602 ; N Ebreve ; G 214
+U 277 ; WX 602 ; N ebreve ; G 215
+U 278 ; WX 602 ; N Edotaccent ; G 216
+U 279 ; WX 602 ; N edotaccent ; G 217
+U 280 ; WX 602 ; N Eogonek ; G 218
+U 281 ; WX 602 ; N eogonek ; G 219
+U 282 ; WX 602 ; N Ecaron ; G 220
+U 283 ; WX 602 ; N ecaron ; G 221
+U 284 ; WX 602 ; N Gcircumflex ; G 222
+U 285 ; WX 602 ; N gcircumflex ; G 223
+U 286 ; WX 602 ; N Gbreve ; G 224
+U 287 ; WX 602 ; N gbreve ; G 225
+U 288 ; WX 602 ; N Gdotaccent ; G 226
+U 289 ; WX 602 ; N gdotaccent ; G 227
+U 290 ; WX 602 ; N Gcommaaccent ; G 228
+U 291 ; WX 602 ; N gcommaaccent ; G 229
+U 292 ; WX 602 ; N Hcircumflex ; G 230
+U 293 ; WX 602 ; N hcircumflex ; G 231
+U 294 ; WX 602 ; N Hbar ; G 232
+U 295 ; WX 602 ; N hbar ; G 233
+U 296 ; WX 602 ; N Itilde ; G 234
+U 297 ; WX 602 ; N itilde ; G 235
+U 298 ; WX 602 ; N Imacron ; G 236
+U 299 ; WX 602 ; N imacron ; G 237
+U 300 ; WX 602 ; N Ibreve ; G 238
+U 301 ; WX 602 ; N ibreve ; G 239
+U 302 ; WX 602 ; N Iogonek ; G 240
+U 303 ; WX 602 ; N iogonek ; G 241
+U 304 ; WX 602 ; N Idot ; G 242
+U 305 ; WX 602 ; N dotlessi ; G 243
+U 306 ; WX 602 ; N IJ ; G 244
+U 307 ; WX 602 ; N ij ; G 245
+U 308 ; WX 602 ; N Jcircumflex ; G 246
+U 309 ; WX 602 ; N jcircumflex ; G 247
+U 310 ; WX 602 ; N Kcommaaccent ; G 248
+U 311 ; WX 602 ; N kcommaaccent ; G 249
+U 312 ; WX 602 ; N kgreenlandic ; G 250
+U 313 ; WX 602 ; N Lacute ; G 251
+U 314 ; WX 602 ; N lacute ; G 252
+U 315 ; WX 602 ; N Lcommaaccent ; G 253
+U 316 ; WX 602 ; N lcommaaccent ; G 254
+U 317 ; WX 602 ; N Lcaron ; G 255
+U 318 ; WX 602 ; N lcaron ; G 256
+U 319 ; WX 602 ; N Ldot ; G 257
+U 320 ; WX 602 ; N ldot ; G 258
+U 321 ; WX 602 ; N Lslash ; G 259
+U 322 ; WX 602 ; N lslash ; G 260
+U 323 ; WX 602 ; N Nacute ; G 261
+U 324 ; WX 602 ; N nacute ; G 262
+U 325 ; WX 602 ; N Ncommaaccent ; G 263
+U 326 ; WX 602 ; N ncommaaccent ; G 264
+U 327 ; WX 602 ; N Ncaron ; G 265
+U 328 ; WX 602 ; N ncaron ; G 266
+U 329 ; WX 602 ; N napostrophe ; G 267
+U 330 ; WX 602 ; N Eng ; G 268
+U 331 ; WX 602 ; N eng ; G 269
+U 332 ; WX 602 ; N Omacron ; G 270
+U 333 ; WX 602 ; N omacron ; G 271
+U 334 ; WX 602 ; N Obreve ; G 272
+U 335 ; WX 602 ; N obreve ; G 273
+U 336 ; WX 602 ; N Ohungarumlaut ; G 274
+U 337 ; WX 602 ; N ohungarumlaut ; G 275
+U 338 ; WX 602 ; N OE ; G 276
+U 339 ; WX 602 ; N oe ; G 277
+U 340 ; WX 602 ; N Racute ; G 278
+U 341 ; WX 602 ; N racute ; G 279
+U 342 ; WX 602 ; N Rcommaaccent ; G 280
+U 343 ; WX 602 ; N rcommaaccent ; G 281
+U 344 ; WX 602 ; N Rcaron ; G 282
+U 345 ; WX 602 ; N rcaron ; G 283
+U 346 ; WX 602 ; N Sacute ; G 284
+U 347 ; WX 602 ; N sacute ; G 285
+U 348 ; WX 602 ; N Scircumflex ; G 286
+U 349 ; WX 602 ; N scircumflex ; G 287
+U 350 ; WX 602 ; N Scedilla ; G 288
+U 351 ; WX 602 ; N scedilla ; G 289
+U 352 ; WX 602 ; N Scaron ; G 290
+U 353 ; WX 602 ; N scaron ; G 291
+U 354 ; WX 602 ; N Tcommaaccent ; G 292
+U 355 ; WX 602 ; N tcommaaccent ; G 293
+U 356 ; WX 602 ; N Tcaron ; G 294
+U 357 ; WX 602 ; N tcaron ; G 295
+U 358 ; WX 602 ; N Tbar ; G 296
+U 359 ; WX 602 ; N tbar ; G 297
+U 360 ; WX 602 ; N Utilde ; G 298
+U 361 ; WX 602 ; N utilde ; G 299
+U 362 ; WX 602 ; N Umacron ; G 300
+U 363 ; WX 602 ; N umacron ; G 301
+U 364 ; WX 602 ; N Ubreve ; G 302
+U 365 ; WX 602 ; N ubreve ; G 303
+U 366 ; WX 602 ; N Uring ; G 304
+U 367 ; WX 602 ; N uring ; G 305
+U 368 ; WX 602 ; N Uhungarumlaut ; G 306
+U 369 ; WX 602 ; N uhungarumlaut ; G 307
+U 370 ; WX 602 ; N Uogonek ; G 308
+U 371 ; WX 602 ; N uogonek ; G 309
+U 372 ; WX 602 ; N Wcircumflex ; G 310
+U 373 ; WX 602 ; N wcircumflex ; G 311
+U 374 ; WX 602 ; N Ycircumflex ; G 312
+U 375 ; WX 602 ; N ycircumflex ; G 313
+U 376 ; WX 602 ; N Ydieresis ; G 314
+U 377 ; WX 602 ; N Zacute ; G 315
+U 378 ; WX 602 ; N zacute ; G 316
+U 379 ; WX 602 ; N Zdotaccent ; G 317
+U 380 ; WX 602 ; N zdotaccent ; G 318
+U 381 ; WX 602 ; N Zcaron ; G 319
+U 382 ; WX 602 ; N zcaron ; G 320
+U 383 ; WX 602 ; N longs ; G 321
+U 384 ; WX 602 ; N uni0180 ; G 322
+U 385 ; WX 602 ; N uni0181 ; G 323
+U 386 ; WX 602 ; N uni0182 ; G 324
+U 387 ; WX 602 ; N uni0183 ; G 325
+U 388 ; WX 602 ; N uni0184 ; G 326
+U 389 ; WX 602 ; N uni0185 ; G 327
+U 390 ; WX 602 ; N uni0186 ; G 328
+U 391 ; WX 602 ; N uni0187 ; G 329
+U 392 ; WX 602 ; N uni0188 ; G 330
+U 393 ; WX 602 ; N uni0189 ; G 331
+U 394 ; WX 602 ; N uni018A ; G 332
+U 395 ; WX 602 ; N uni018B ; G 333
+U 396 ; WX 602 ; N uni018C ; G 334
+U 397 ; WX 602 ; N uni018D ; G 335
+U 398 ; WX 602 ; N uni018E ; G 336
+U 399 ; WX 602 ; N uni018F ; G 337
+U 400 ; WX 602 ; N uni0190 ; G 338
+U 401 ; WX 602 ; N uni0191 ; G 339
+U 402 ; WX 602 ; N florin ; G 340
+U 403 ; WX 602 ; N uni0193 ; G 341
+U 404 ; WX 602 ; N uni0194 ; G 342
+U 405 ; WX 602 ; N uni0195 ; G 343
+U 406 ; WX 602 ; N uni0196 ; G 344
+U 407 ; WX 602 ; N uni0197 ; G 345
+U 408 ; WX 602 ; N uni0198 ; G 346
+U 409 ; WX 602 ; N uni0199 ; G 347
+U 410 ; WX 602 ; N uni019A ; G 348
+U 411 ; WX 602 ; N uni019B ; G 349
+U 412 ; WX 602 ; N uni019C ; G 350
+U 413 ; WX 602 ; N uni019D ; G 351
+U 414 ; WX 602 ; N uni019E ; G 352
+U 415 ; WX 602 ; N uni019F ; G 353
+U 416 ; WX 602 ; N Ohorn ; G 354
+U 417 ; WX 602 ; N ohorn ; G 355
+U 418 ; WX 602 ; N uni01A2 ; G 356
+U 419 ; WX 602 ; N uni01A3 ; G 357
+U 420 ; WX 602 ; N uni01A4 ; G 358
+U 421 ; WX 602 ; N uni01A5 ; G 359
+U 422 ; WX 602 ; N uni01A6 ; G 360
+U 423 ; WX 602 ; N uni01A7 ; G 361
+U 424 ; WX 602 ; N uni01A8 ; G 362
+U 425 ; WX 602 ; N uni01A9 ; G 363
+U 426 ; WX 602 ; N uni01AA ; G 364
+U 427 ; WX 602 ; N uni01AB ; G 365
+U 428 ; WX 602 ; N uni01AC ; G 366
+U 429 ; WX 602 ; N uni01AD ; G 367
+U 430 ; WX 602 ; N uni01AE ; G 368
+U 431 ; WX 602 ; N Uhorn ; G 369
+U 432 ; WX 602 ; N uhorn ; G 370
+U 433 ; WX 602 ; N uni01B1 ; G 371
+U 434 ; WX 602 ; N uni01B2 ; G 372
+U 435 ; WX 602 ; N uni01B3 ; G 373
+U 436 ; WX 602 ; N uni01B4 ; G 374
+U 437 ; WX 602 ; N uni01B5 ; G 375
+U 438 ; WX 602 ; N uni01B6 ; G 376
+U 439 ; WX 602 ; N uni01B7 ; G 377
+U 440 ; WX 602 ; N uni01B8 ; G 378
+U 441 ; WX 602 ; N uni01B9 ; G 379
+U 442 ; WX 602 ; N uni01BA ; G 380
+U 443 ; WX 602 ; N uni01BB ; G 381
+U 444 ; WX 602 ; N uni01BC ; G 382
+U 445 ; WX 602 ; N uni01BD ; G 383
+U 446 ; WX 602 ; N uni01BE ; G 384
+U 447 ; WX 602 ; N uni01BF ; G 385
+U 448 ; WX 602 ; N uni01C0 ; G 386
+U 449 ; WX 602 ; N uni01C1 ; G 387
+U 450 ; WX 602 ; N uni01C2 ; G 388
+U 451 ; WX 602 ; N uni01C3 ; G 389
+U 461 ; WX 602 ; N uni01CD ; G 390
+U 462 ; WX 602 ; N uni01CE ; G 391
+U 463 ; WX 602 ; N uni01CF ; G 392
+U 464 ; WX 602 ; N uni01D0 ; G 393
+U 465 ; WX 602 ; N uni01D1 ; G 394
+U 466 ; WX 602 ; N uni01D2 ; G 395
+U 467 ; WX 602 ; N uni01D3 ; G 396
+U 468 ; WX 602 ; N uni01D4 ; G 397
+U 469 ; WX 602 ; N uni01D5 ; G 398
+U 470 ; WX 602 ; N uni01D6 ; G 399
+U 471 ; WX 602 ; N uni01D7 ; G 400
+U 472 ; WX 602 ; N uni01D8 ; G 401
+U 473 ; WX 602 ; N uni01D9 ; G 402
+U 474 ; WX 602 ; N uni01DA ; G 403
+U 475 ; WX 602 ; N uni01DB ; G 404
+U 476 ; WX 602 ; N uni01DC ; G 405
+U 477 ; WX 602 ; N uni01DD ; G 406
+U 478 ; WX 602 ; N uni01DE ; G 407
+U 479 ; WX 602 ; N uni01DF ; G 408
+U 480 ; WX 602 ; N uni01E0 ; G 409
+U 481 ; WX 602 ; N uni01E1 ; G 410
+U 482 ; WX 602 ; N uni01E2 ; G 411
+U 483 ; WX 602 ; N uni01E3 ; G 412
+U 486 ; WX 602 ; N Gcaron ; G 413
+U 487 ; WX 602 ; N gcaron ; G 414
+U 488 ; WX 602 ; N uni01E8 ; G 415
+U 489 ; WX 602 ; N uni01E9 ; G 416
+U 490 ; WX 602 ; N uni01EA ; G 417
+U 491 ; WX 602 ; N uni01EB ; G 418
+U 492 ; WX 602 ; N uni01EC ; G 419
+U 493 ; WX 602 ; N uni01ED ; G 420
+U 494 ; WX 602 ; N uni01EE ; G 421
+U 495 ; WX 602 ; N uni01EF ; G 422
+U 500 ; WX 602 ; N uni01F4 ; G 423
+U 501 ; WX 602 ; N uni01F5 ; G 424
+U 502 ; WX 602 ; N uni01F6 ; G 425
+U 504 ; WX 602 ; N uni01F8 ; G 426
+U 505 ; WX 602 ; N uni01F9 ; G 427
+U 508 ; WX 602 ; N AEacute ; G 428
+U 509 ; WX 602 ; N aeacute ; G 429
+U 510 ; WX 602 ; N Oslashacute ; G 430
+U 511 ; WX 602 ; N oslashacute ; G 431
+U 512 ; WX 602 ; N uni0200 ; G 432
+U 513 ; WX 602 ; N uni0201 ; G 433
+U 514 ; WX 602 ; N uni0202 ; G 434
+U 515 ; WX 602 ; N uni0203 ; G 435
+U 516 ; WX 602 ; N uni0204 ; G 436
+U 517 ; WX 602 ; N uni0205 ; G 437
+U 518 ; WX 602 ; N uni0206 ; G 438
+U 519 ; WX 602 ; N uni0207 ; G 439
+U 520 ; WX 602 ; N uni0208 ; G 440
+U 521 ; WX 602 ; N uni0209 ; G 441
+U 522 ; WX 602 ; N uni020A ; G 442
+U 523 ; WX 602 ; N uni020B ; G 443
+U 524 ; WX 602 ; N uni020C ; G 444
+U 525 ; WX 602 ; N uni020D ; G 445
+U 526 ; WX 602 ; N uni020E ; G 446
+U 527 ; WX 602 ; N uni020F ; G 447
+U 528 ; WX 602 ; N uni0210 ; G 448
+U 529 ; WX 602 ; N uni0211 ; G 449
+U 530 ; WX 602 ; N uni0212 ; G 450
+U 531 ; WX 602 ; N uni0213 ; G 451
+U 532 ; WX 602 ; N uni0214 ; G 452
+U 533 ; WX 602 ; N uni0215 ; G 453
+U 534 ; WX 602 ; N uni0216 ; G 454
+U 535 ; WX 602 ; N uni0217 ; G 455
+U 536 ; WX 602 ; N Scommaaccent ; G 456
+U 537 ; WX 602 ; N scommaaccent ; G 457
+U 538 ; WX 602 ; N uni021A ; G 458
+U 539 ; WX 602 ; N uni021B ; G 459
+U 540 ; WX 602 ; N uni021C ; G 460
+U 541 ; WX 602 ; N uni021D ; G 461
+U 542 ; WX 602 ; N uni021E ; G 462
+U 543 ; WX 602 ; N uni021F ; G 463
+U 545 ; WX 602 ; N uni0221 ; G 464
+U 548 ; WX 602 ; N uni0224 ; G 465
+U 549 ; WX 602 ; N uni0225 ; G 466
+U 550 ; WX 602 ; N uni0226 ; G 467
+U 551 ; WX 602 ; N uni0227 ; G 468
+U 552 ; WX 602 ; N uni0228 ; G 469
+U 553 ; WX 602 ; N uni0229 ; G 470
+U 554 ; WX 602 ; N uni022A ; G 471
+U 555 ; WX 602 ; N uni022B ; G 472
+U 556 ; WX 602 ; N uni022C ; G 473
+U 557 ; WX 602 ; N uni022D ; G 474
+U 558 ; WX 602 ; N uni022E ; G 475
+U 559 ; WX 602 ; N uni022F ; G 476
+U 560 ; WX 602 ; N uni0230 ; G 477
+U 561 ; WX 602 ; N uni0231 ; G 478
+U 562 ; WX 602 ; N uni0232 ; G 479
+U 563 ; WX 602 ; N uni0233 ; G 480
+U 564 ; WX 602 ; N uni0234 ; G 481
+U 565 ; WX 602 ; N uni0235 ; G 482
+U 566 ; WX 602 ; N uni0236 ; G 483
+U 567 ; WX 602 ; N dotlessj ; G 484
+U 568 ; WX 602 ; N uni0238 ; G 485
+U 569 ; WX 602 ; N uni0239 ; G 486
+U 570 ; WX 602 ; N uni023A ; G 487
+U 571 ; WX 602 ; N uni023B ; G 488
+U 572 ; WX 602 ; N uni023C ; G 489
+U 573 ; WX 602 ; N uni023D ; G 490
+U 574 ; WX 602 ; N uni023E ; G 491
+U 575 ; WX 602 ; N uni023F ; G 492
+U 576 ; WX 602 ; N uni0240 ; G 493
+U 577 ; WX 602 ; N uni0241 ; G 494
+U 579 ; WX 602 ; N uni0243 ; G 495
+U 580 ; WX 602 ; N uni0244 ; G 496
+U 581 ; WX 602 ; N uni0245 ; G 497
+U 588 ; WX 602 ; N uni024C ; G 498
+U 589 ; WX 602 ; N uni024D ; G 499
+U 592 ; WX 602 ; N uni0250 ; G 500
+U 593 ; WX 602 ; N uni0251 ; G 501
+U 594 ; WX 602 ; N uni0252 ; G 502
+U 595 ; WX 602 ; N uni0253 ; G 503
+U 596 ; WX 602 ; N uni0254 ; G 504
+U 597 ; WX 602 ; N uni0255 ; G 505
+U 598 ; WX 602 ; N uni0256 ; G 506
+U 599 ; WX 602 ; N uni0257 ; G 507
+U 600 ; WX 602 ; N uni0258 ; G 508
+U 601 ; WX 602 ; N uni0259 ; G 509
+U 602 ; WX 602 ; N uni025A ; G 510
+U 603 ; WX 602 ; N uni025B ; G 511
+U 604 ; WX 602 ; N uni025C ; G 512
+U 605 ; WX 602 ; N uni025D ; G 513
+U 606 ; WX 602 ; N uni025E ; G 514
+U 607 ; WX 602 ; N uni025F ; G 515
+U 608 ; WX 602 ; N uni0260 ; G 516
+U 609 ; WX 602 ; N uni0261 ; G 517
+U 610 ; WX 602 ; N uni0262 ; G 518
+U 611 ; WX 602 ; N uni0263 ; G 519
+U 612 ; WX 602 ; N uni0264 ; G 520
+U 613 ; WX 602 ; N uni0265 ; G 521
+U 614 ; WX 602 ; N uni0266 ; G 522
+U 615 ; WX 602 ; N uni0267 ; G 523
+U 616 ; WX 602 ; N uni0268 ; G 524
+U 617 ; WX 602 ; N uni0269 ; G 525
+U 618 ; WX 602 ; N uni026A ; G 526
+U 619 ; WX 602 ; N uni026B ; G 527
+U 620 ; WX 602 ; N uni026C ; G 528
+U 621 ; WX 602 ; N uni026D ; G 529
+U 622 ; WX 602 ; N uni026E ; G 530
+U 623 ; WX 602 ; N uni026F ; G 531
+U 624 ; WX 602 ; N uni0270 ; G 532
+U 625 ; WX 602 ; N uni0271 ; G 533
+U 626 ; WX 602 ; N uni0272 ; G 534
+U 627 ; WX 602 ; N uni0273 ; G 535
+U 628 ; WX 602 ; N uni0274 ; G 536
+U 629 ; WX 602 ; N uni0275 ; G 537
+U 630 ; WX 602 ; N uni0276 ; G 538
+U 631 ; WX 602 ; N uni0277 ; G 539
+U 632 ; WX 602 ; N uni0278 ; G 540
+U 633 ; WX 602 ; N uni0279 ; G 541
+U 634 ; WX 602 ; N uni027A ; G 542
+U 635 ; WX 602 ; N uni027B ; G 543
+U 636 ; WX 602 ; N uni027C ; G 544
+U 637 ; WX 602 ; N uni027D ; G 545
+U 638 ; WX 602 ; N uni027E ; G 546
+U 639 ; WX 602 ; N uni027F ; G 547
+U 640 ; WX 602 ; N uni0280 ; G 548
+U 641 ; WX 602 ; N uni0281 ; G 549
+U 642 ; WX 602 ; N uni0282 ; G 550
+U 643 ; WX 602 ; N uni0283 ; G 551
+U 644 ; WX 602 ; N uni0284 ; G 552
+U 645 ; WX 602 ; N uni0285 ; G 553
+U 646 ; WX 602 ; N uni0286 ; G 554
+U 647 ; WX 602 ; N uni0287 ; G 555
+U 648 ; WX 602 ; N uni0288 ; G 556
+U 649 ; WX 602 ; N uni0289 ; G 557
+U 650 ; WX 602 ; N uni028A ; G 558
+U 651 ; WX 602 ; N uni028B ; G 559
+U 652 ; WX 602 ; N uni028C ; G 560
+U 653 ; WX 602 ; N uni028D ; G 561
+U 654 ; WX 602 ; N uni028E ; G 562
+U 655 ; WX 602 ; N uni028F ; G 563
+U 656 ; WX 602 ; N uni0290 ; G 564
+U 657 ; WX 602 ; N uni0291 ; G 565
+U 658 ; WX 602 ; N uni0292 ; G 566
+U 659 ; WX 602 ; N uni0293 ; G 567
+U 660 ; WX 602 ; N uni0294 ; G 568
+U 661 ; WX 602 ; N uni0295 ; G 569
+U 662 ; WX 602 ; N uni0296 ; G 570
+U 663 ; WX 602 ; N uni0297 ; G 571
+U 664 ; WX 602 ; N uni0298 ; G 572
+U 665 ; WX 602 ; N uni0299 ; G 573
+U 666 ; WX 602 ; N uni029A ; G 574
+U 667 ; WX 602 ; N uni029B ; G 575
+U 668 ; WX 602 ; N uni029C ; G 576
+U 669 ; WX 602 ; N uni029D ; G 577
+U 670 ; WX 602 ; N uni029E ; G 578
+U 671 ; WX 602 ; N uni029F ; G 579
+U 672 ; WX 602 ; N uni02A0 ; G 580
+U 673 ; WX 602 ; N uni02A1 ; G 581
+U 674 ; WX 602 ; N uni02A2 ; G 582
+U 675 ; WX 602 ; N uni02A3 ; G 583
+U 676 ; WX 602 ; N uni02A4 ; G 584
+U 677 ; WX 602 ; N uni02A5 ; G 585
+U 678 ; WX 602 ; N uni02A6 ; G 586
+U 679 ; WX 602 ; N uni02A7 ; G 587
+U 680 ; WX 602 ; N uni02A8 ; G 588
+U 681 ; WX 602 ; N uni02A9 ; G 589
+U 682 ; WX 602 ; N uni02AA ; G 590
+U 683 ; WX 602 ; N uni02AB ; G 591
+U 684 ; WX 602 ; N uni02AC ; G 592
+U 685 ; WX 602 ; N uni02AD ; G 593
+U 686 ; WX 602 ; N uni02AE ; G 594
+U 687 ; WX 602 ; N uni02AF ; G 595
+U 688 ; WX 602 ; N uni02B0 ; G 596
+U 689 ; WX 602 ; N uni02B1 ; G 597
+U 690 ; WX 602 ; N uni02B2 ; G 598
+U 691 ; WX 602 ; N uni02B3 ; G 599
+U 692 ; WX 602 ; N uni02B4 ; G 600
+U 693 ; WX 602 ; N uni02B5 ; G 601
+U 694 ; WX 602 ; N uni02B6 ; G 602
+U 695 ; WX 602 ; N uni02B7 ; G 603
+U 696 ; WX 602 ; N uni02B8 ; G 604
+U 697 ; WX 602 ; N uni02B9 ; G 605
+U 699 ; WX 602 ; N uni02BB ; G 606
+U 700 ; WX 602 ; N uni02BC ; G 607
+U 701 ; WX 602 ; N uni02BD ; G 608
+U 702 ; WX 602 ; N uni02BE ; G 609
+U 703 ; WX 602 ; N uni02BF ; G 610
+U 704 ; WX 602 ; N uni02C0 ; G 611
+U 705 ; WX 602 ; N uni02C1 ; G 612
+U 710 ; WX 602 ; N circumflex ; G 613
+U 711 ; WX 602 ; N caron ; G 614
+U 712 ; WX 602 ; N uni02C8 ; G 615
+U 713 ; WX 602 ; N uni02C9 ; G 616
+U 716 ; WX 602 ; N uni02CC ; G 617
+U 717 ; WX 602 ; N uni02CD ; G 618
+U 718 ; WX 602 ; N uni02CE ; G 619
+U 719 ; WX 602 ; N uni02CF ; G 620
+U 720 ; WX 602 ; N uni02D0 ; G 621
+U 721 ; WX 602 ; N uni02D1 ; G 622
+U 722 ; WX 602 ; N uni02D2 ; G 623
+U 723 ; WX 602 ; N uni02D3 ; G 624
+U 726 ; WX 602 ; N uni02D6 ; G 625
+U 727 ; WX 602 ; N uni02D7 ; G 626
+U 728 ; WX 602 ; N breve ; G 627
+U 729 ; WX 602 ; N dotaccent ; G 628
+U 730 ; WX 602 ; N ring ; G 629
+U 731 ; WX 602 ; N ogonek ; G 630
+U 732 ; WX 602 ; N tilde ; G 631
+U 733 ; WX 602 ; N hungarumlaut ; G 632
+U 734 ; WX 602 ; N uni02DE ; G 633
+U 736 ; WX 602 ; N uni02E0 ; G 634
+U 737 ; WX 602 ; N uni02E1 ; G 635
+U 738 ; WX 602 ; N uni02E2 ; G 636
+U 739 ; WX 602 ; N uni02E3 ; G 637
+U 740 ; WX 602 ; N uni02E4 ; G 638
+U 741 ; WX 602 ; N uni02E5 ; G 639
+U 742 ; WX 602 ; N uni02E6 ; G 640
+U 743 ; WX 602 ; N uni02E7 ; G 641
+U 744 ; WX 602 ; N uni02E8 ; G 642
+U 745 ; WX 602 ; N uni02E9 ; G 643
+U 750 ; WX 602 ; N uni02EE ; G 644
+U 755 ; WX 602 ; N uni02F3 ; G 645
+U 768 ; WX 602 ; N gravecomb ; G 646
+U 769 ; WX 602 ; N acutecomb ; G 647
+U 770 ; WX 602 ; N uni0302 ; G 648
+U 771 ; WX 602 ; N tildecomb ; G 649
+U 772 ; WX 602 ; N uni0304 ; G 650
+U 773 ; WX 602 ; N uni0305 ; G 651
+U 774 ; WX 602 ; N uni0306 ; G 652
+U 775 ; WX 602 ; N uni0307 ; G 653
+U 776 ; WX 602 ; N uni0308 ; G 654
+U 777 ; WX 602 ; N hookabovecomb ; G 655
+U 778 ; WX 602 ; N uni030A ; G 656
+U 779 ; WX 602 ; N uni030B ; G 657
+U 780 ; WX 602 ; N uni030C ; G 658
+U 781 ; WX 602 ; N uni030D ; G 659
+U 782 ; WX 602 ; N uni030E ; G 660
+U 783 ; WX 602 ; N uni030F ; G 661
+U 784 ; WX 602 ; N uni0310 ; G 662
+U 785 ; WX 602 ; N uni0311 ; G 663
+U 786 ; WX 602 ; N uni0312 ; G 664
+U 787 ; WX 602 ; N uni0313 ; G 665
+U 788 ; WX 602 ; N uni0314 ; G 666
+U 789 ; WX 602 ; N uni0315 ; G 667
+U 790 ; WX 602 ; N uni0316 ; G 668
+U 791 ; WX 602 ; N uni0317 ; G 669
+U 792 ; WX 602 ; N uni0318 ; G 670
+U 793 ; WX 602 ; N uni0319 ; G 671
+U 794 ; WX 602 ; N uni031A ; G 672
+U 795 ; WX 602 ; N uni031B ; G 673
+U 796 ; WX 602 ; N uni031C ; G 674
+U 797 ; WX 602 ; N uni031D ; G 675
+U 798 ; WX 602 ; N uni031E ; G 676
+U 799 ; WX 602 ; N uni031F ; G 677
+U 800 ; WX 602 ; N uni0320 ; G 678
+U 801 ; WX 602 ; N uni0321 ; G 679
+U 802 ; WX 602 ; N uni0322 ; G 680
+U 803 ; WX 602 ; N dotbelowcomb ; G 681
+U 804 ; WX 602 ; N uni0324 ; G 682
+U 805 ; WX 602 ; N uni0325 ; G 683
+U 806 ; WX 602 ; N uni0326 ; G 684
+U 807 ; WX 602 ; N uni0327 ; G 685
+U 808 ; WX 602 ; N uni0328 ; G 686
+U 809 ; WX 602 ; N uni0329 ; G 687
+U 810 ; WX 602 ; N uni032A ; G 688
+U 811 ; WX 602 ; N uni032B ; G 689
+U 812 ; WX 602 ; N uni032C ; G 690
+U 813 ; WX 602 ; N uni032D ; G 691
+U 814 ; WX 602 ; N uni032E ; G 692
+U 815 ; WX 602 ; N uni032F ; G 693
+U 816 ; WX 602 ; N uni0330 ; G 694
+U 817 ; WX 602 ; N uni0331 ; G 695
+U 818 ; WX 602 ; N uni0332 ; G 696
+U 819 ; WX 602 ; N uni0333 ; G 697
+U 820 ; WX 602 ; N uni0334 ; G 698
+U 821 ; WX 602 ; N uni0335 ; G 699
+U 822 ; WX 602 ; N uni0336 ; G 700
+U 823 ; WX 602 ; N uni0337 ; G 701
+U 824 ; WX 602 ; N uni0338 ; G 702
+U 825 ; WX 602 ; N uni0339 ; G 703
+U 826 ; WX 602 ; N uni033A ; G 704
+U 827 ; WX 602 ; N uni033B ; G 705
+U 828 ; WX 602 ; N uni033C ; G 706
+U 829 ; WX 602 ; N uni033D ; G 707
+U 830 ; WX 602 ; N uni033E ; G 708
+U 831 ; WX 602 ; N uni033F ; G 709
+U 835 ; WX 602 ; N uni0343 ; G 710
+U 856 ; WX 602 ; N uni0358 ; G 711
+U 865 ; WX 602 ; N uni0361 ; G 712
+U 884 ; WX 602 ; N uni0374 ; G 713
+U 885 ; WX 602 ; N uni0375 ; G 714
+U 886 ; WX 602 ; N uni0376 ; G 715
+U 887 ; WX 602 ; N uni0377 ; G 716
+U 890 ; WX 602 ; N uni037A ; G 717
+U 891 ; WX 602 ; N uni037B ; G 718
+U 892 ; WX 602 ; N uni037C ; G 719
+U 893 ; WX 602 ; N uni037D ; G 720
+U 894 ; WX 602 ; N uni037E ; G 721
+U 895 ; WX 602 ; N uni037F ; G 722
+U 900 ; WX 602 ; N tonos ; G 723
+U 901 ; WX 602 ; N dieresistonos ; G 724
+U 902 ; WX 602 ; N Alphatonos ; G 725
+U 903 ; WX 602 ; N anoteleia ; G 726
+U 904 ; WX 602 ; N Epsilontonos ; G 727
+U 905 ; WX 602 ; N Etatonos ; G 728
+U 906 ; WX 602 ; N Iotatonos ; G 729
+U 908 ; WX 602 ; N Omicrontonos ; G 730
+U 910 ; WX 602 ; N Upsilontonos ; G 731
+U 911 ; WX 602 ; N Omegatonos ; G 732
+U 912 ; WX 602 ; N iotadieresistonos ; G 733
+U 913 ; WX 602 ; N Alpha ; G 734
+U 914 ; WX 602 ; N Beta ; G 735
+U 915 ; WX 602 ; N Gamma ; G 736
+U 916 ; WX 602 ; N uni0394 ; G 737
+U 917 ; WX 602 ; N Epsilon ; G 738
+U 918 ; WX 602 ; N Zeta ; G 739
+U 919 ; WX 602 ; N Eta ; G 740
+U 920 ; WX 602 ; N Theta ; G 741
+U 921 ; WX 602 ; N Iota ; G 742
+U 922 ; WX 602 ; N Kappa ; G 743
+U 923 ; WX 602 ; N Lambda ; G 744
+U 924 ; WX 602 ; N Mu ; G 745
+U 925 ; WX 602 ; N Nu ; G 746
+U 926 ; WX 602 ; N Xi ; G 747
+U 927 ; WX 602 ; N Omicron ; G 748
+U 928 ; WX 602 ; N Pi ; G 749
+U 929 ; WX 602 ; N Rho ; G 750
+U 931 ; WX 602 ; N Sigma ; G 751
+U 932 ; WX 602 ; N Tau ; G 752
+U 933 ; WX 602 ; N Upsilon ; G 753
+U 934 ; WX 602 ; N Phi ; G 754
+U 935 ; WX 602 ; N Chi ; G 755
+U 936 ; WX 602 ; N Psi ; G 756
+U 937 ; WX 602 ; N Omega ; G 757
+U 938 ; WX 602 ; N Iotadieresis ; G 758
+U 939 ; WX 602 ; N Upsilondieresis ; G 759
+U 940 ; WX 602 ; N alphatonos ; G 760
+U 941 ; WX 602 ; N epsilontonos ; G 761
+U 942 ; WX 602 ; N etatonos ; G 762
+U 943 ; WX 602 ; N iotatonos ; G 763
+U 944 ; WX 602 ; N upsilondieresistonos ; G 764
+U 945 ; WX 602 ; N alpha ; G 765
+U 946 ; WX 602 ; N beta ; G 766
+U 947 ; WX 602 ; N gamma ; G 767
+U 948 ; WX 602 ; N delta ; G 768
+U 949 ; WX 602 ; N epsilon ; G 769
+U 950 ; WX 602 ; N zeta ; G 770
+U 951 ; WX 602 ; N eta ; G 771
+U 952 ; WX 602 ; N theta ; G 772
+U 953 ; WX 602 ; N iota ; G 773
+U 954 ; WX 602 ; N kappa ; G 774
+U 955 ; WX 602 ; N lambda ; G 775
+U 956 ; WX 602 ; N uni03BC ; G 776
+U 957 ; WX 602 ; N nu ; G 777
+U 958 ; WX 602 ; N xi ; G 778
+U 959 ; WX 602 ; N omicron ; G 779
+U 960 ; WX 602 ; N pi ; G 780
+U 961 ; WX 602 ; N rho ; G 781
+U 962 ; WX 602 ; N sigma1 ; G 782
+U 963 ; WX 602 ; N sigma ; G 783
+U 964 ; WX 602 ; N tau ; G 784
+U 965 ; WX 602 ; N upsilon ; G 785
+U 966 ; WX 602 ; N phi ; G 786
+U 967 ; WX 602 ; N chi ; G 787
+U 968 ; WX 602 ; N psi ; G 788
+U 969 ; WX 602 ; N omega ; G 789
+U 970 ; WX 602 ; N iotadieresis ; G 790
+U 971 ; WX 602 ; N upsilondieresis ; G 791
+U 972 ; WX 602 ; N omicrontonos ; G 792
+U 973 ; WX 602 ; N upsilontonos ; G 793
+U 974 ; WX 602 ; N omegatonos ; G 794
+U 976 ; WX 602 ; N uni03D0 ; G 795
+U 977 ; WX 602 ; N theta1 ; G 796
+U 978 ; WX 602 ; N Upsilon1 ; G 797
+U 979 ; WX 602 ; N uni03D3 ; G 798
+U 980 ; WX 602 ; N uni03D4 ; G 799
+U 981 ; WX 602 ; N phi1 ; G 800
+U 982 ; WX 602 ; N omega1 ; G 801
+U 983 ; WX 602 ; N uni03D7 ; G 802
+U 984 ; WX 602 ; N uni03D8 ; G 803
+U 985 ; WX 602 ; N uni03D9 ; G 804
+U 986 ; WX 602 ; N uni03DA ; G 805
+U 987 ; WX 602 ; N uni03DB ; G 806
+U 988 ; WX 602 ; N uni03DC ; G 807
+U 989 ; WX 602 ; N uni03DD ; G 808
+U 990 ; WX 602 ; N uni03DE ; G 809
+U 991 ; WX 602 ; N uni03DF ; G 810
+U 992 ; WX 602 ; N uni03E0 ; G 811
+U 993 ; WX 602 ; N uni03E1 ; G 812
+U 1008 ; WX 602 ; N uni03F0 ; G 813
+U 1009 ; WX 602 ; N uni03F1 ; G 814
+U 1010 ; WX 602 ; N uni03F2 ; G 815
+U 1011 ; WX 602 ; N uni03F3 ; G 816
+U 1012 ; WX 602 ; N uni03F4 ; G 817
+U 1013 ; WX 602 ; N uni03F5 ; G 818
+U 1014 ; WX 602 ; N uni03F6 ; G 819
+U 1015 ; WX 602 ; N uni03F7 ; G 820
+U 1016 ; WX 602 ; N uni03F8 ; G 821
+U 1017 ; WX 602 ; N uni03F9 ; G 822
+U 1018 ; WX 602 ; N uni03FA ; G 823
+U 1019 ; WX 602 ; N uni03FB ; G 824
+U 1020 ; WX 602 ; N uni03FC ; G 825
+U 1021 ; WX 602 ; N uni03FD ; G 826
+U 1022 ; WX 602 ; N uni03FE ; G 827
+U 1023 ; WX 602 ; N uni03FF ; G 828
+U 1024 ; WX 602 ; N uni0400 ; G 829
+U 1025 ; WX 602 ; N uni0401 ; G 830
+U 1026 ; WX 602 ; N uni0402 ; G 831
+U 1027 ; WX 602 ; N uni0403 ; G 832
+U 1028 ; WX 602 ; N uni0404 ; G 833
+U 1029 ; WX 602 ; N uni0405 ; G 834
+U 1030 ; WX 602 ; N uni0406 ; G 835
+U 1031 ; WX 602 ; N uni0407 ; G 836
+U 1032 ; WX 602 ; N uni0408 ; G 837
+U 1033 ; WX 602 ; N uni0409 ; G 838
+U 1034 ; WX 602 ; N uni040A ; G 839
+U 1035 ; WX 602 ; N uni040B ; G 840
+U 1036 ; WX 602 ; N uni040C ; G 841
+U 1037 ; WX 602 ; N uni040D ; G 842
+U 1038 ; WX 602 ; N uni040E ; G 843
+U 1039 ; WX 602 ; N uni040F ; G 844
+U 1040 ; WX 602 ; N uni0410 ; G 845
+U 1041 ; WX 602 ; N uni0411 ; G 846
+U 1042 ; WX 602 ; N uni0412 ; G 847
+U 1043 ; WX 602 ; N uni0413 ; G 848
+U 1044 ; WX 602 ; N uni0414 ; G 849
+U 1045 ; WX 602 ; N uni0415 ; G 850
+U 1046 ; WX 602 ; N uni0416 ; G 851
+U 1047 ; WX 602 ; N uni0417 ; G 852
+U 1048 ; WX 602 ; N uni0418 ; G 853
+U 1049 ; WX 602 ; N uni0419 ; G 854
+U 1050 ; WX 602 ; N uni041A ; G 855
+U 1051 ; WX 602 ; N uni041B ; G 856
+U 1052 ; WX 602 ; N uni041C ; G 857
+U 1053 ; WX 602 ; N uni041D ; G 858
+U 1054 ; WX 602 ; N uni041E ; G 859
+U 1055 ; WX 602 ; N uni041F ; G 860
+U 1056 ; WX 602 ; N uni0420 ; G 861
+U 1057 ; WX 602 ; N uni0421 ; G 862
+U 1058 ; WX 602 ; N uni0422 ; G 863
+U 1059 ; WX 602 ; N uni0423 ; G 864
+U 1060 ; WX 602 ; N uni0424 ; G 865
+U 1061 ; WX 602 ; N uni0425 ; G 866
+U 1062 ; WX 602 ; N uni0426 ; G 867
+U 1063 ; WX 602 ; N uni0427 ; G 868
+U 1064 ; WX 602 ; N uni0428 ; G 869
+U 1065 ; WX 602 ; N uni0429 ; G 870
+U 1066 ; WX 602 ; N uni042A ; G 871
+U 1067 ; WX 602 ; N uni042B ; G 872
+U 1068 ; WX 602 ; N uni042C ; G 873
+U 1069 ; WX 602 ; N uni042D ; G 874
+U 1070 ; WX 602 ; N uni042E ; G 875
+U 1071 ; WX 602 ; N uni042F ; G 876
+U 1072 ; WX 602 ; N uni0430 ; G 877
+U 1073 ; WX 602 ; N uni0431 ; G 878
+U 1074 ; WX 602 ; N uni0432 ; G 879
+U 1075 ; WX 602 ; N uni0433 ; G 880
+U 1076 ; WX 602 ; N uni0434 ; G 881
+U 1077 ; WX 602 ; N uni0435 ; G 882
+U 1078 ; WX 602 ; N uni0436 ; G 883
+U 1079 ; WX 602 ; N uni0437 ; G 884
+U 1080 ; WX 602 ; N uni0438 ; G 885
+U 1081 ; WX 602 ; N uni0439 ; G 886
+U 1082 ; WX 602 ; N uni043A ; G 887
+U 1083 ; WX 602 ; N uni043B ; G 888
+U 1084 ; WX 602 ; N uni043C ; G 889
+U 1085 ; WX 602 ; N uni043D ; G 890
+U 1086 ; WX 602 ; N uni043E ; G 891
+U 1087 ; WX 602 ; N uni043F ; G 892
+U 1088 ; WX 602 ; N uni0440 ; G 893
+U 1089 ; WX 602 ; N uni0441 ; G 894
+U 1090 ; WX 602 ; N uni0442 ; G 895
+U 1091 ; WX 602 ; N uni0443 ; G 896
+U 1092 ; WX 602 ; N uni0444 ; G 897
+U 1093 ; WX 602 ; N uni0445 ; G 898
+U 1094 ; WX 602 ; N uni0446 ; G 899
+U 1095 ; WX 602 ; N uni0447 ; G 900
+U 1096 ; WX 602 ; N uni0448 ; G 901
+U 1097 ; WX 602 ; N uni0449 ; G 902
+U 1098 ; WX 602 ; N uni044A ; G 903
+U 1099 ; WX 602 ; N uni044B ; G 904
+U 1100 ; WX 602 ; N uni044C ; G 905
+U 1101 ; WX 602 ; N uni044D ; G 906
+U 1102 ; WX 602 ; N uni044E ; G 907
+U 1103 ; WX 602 ; N uni044F ; G 908
+U 1104 ; WX 602 ; N uni0450 ; G 909
+U 1105 ; WX 602 ; N uni0451 ; G 910
+U 1106 ; WX 602 ; N uni0452 ; G 911
+U 1107 ; WX 602 ; N uni0453 ; G 912
+U 1108 ; WX 602 ; N uni0454 ; G 913
+U 1109 ; WX 602 ; N uni0455 ; G 914
+U 1110 ; WX 602 ; N uni0456 ; G 915
+U 1111 ; WX 602 ; N uni0457 ; G 916
+U 1112 ; WX 602 ; N uni0458 ; G 917
+U 1113 ; WX 602 ; N uni0459 ; G 918
+U 1114 ; WX 602 ; N uni045A ; G 919
+U 1115 ; WX 602 ; N uni045B ; G 920
+U 1116 ; WX 602 ; N uni045C ; G 921
+U 1117 ; WX 602 ; N uni045D ; G 922
+U 1118 ; WX 602 ; N uni045E ; G 923
+U 1119 ; WX 602 ; N uni045F ; G 924
+U 1122 ; WX 602 ; N uni0462 ; G 925
+U 1123 ; WX 602 ; N uni0463 ; G 926
+U 1138 ; WX 602 ; N uni0472 ; G 927
+U 1139 ; WX 602 ; N uni0473 ; G 928
+U 1168 ; WX 602 ; N uni0490 ; G 929
+U 1169 ; WX 602 ; N uni0491 ; G 930
+U 1170 ; WX 602 ; N uni0492 ; G 931
+U 1171 ; WX 602 ; N uni0493 ; G 932
+U 1172 ; WX 602 ; N uni0494 ; G 933
+U 1173 ; WX 602 ; N uni0495 ; G 934
+U 1174 ; WX 602 ; N uni0496 ; G 935
+U 1175 ; WX 602 ; N uni0497 ; G 936
+U 1176 ; WX 602 ; N uni0498 ; G 937
+U 1177 ; WX 602 ; N uni0499 ; G 938
+U 1178 ; WX 602 ; N uni049A ; G 939
+U 1179 ; WX 602 ; N uni049B ; G 940
+U 1186 ; WX 602 ; N uni04A2 ; G 941
+U 1187 ; WX 602 ; N uni04A3 ; G 942
+U 1188 ; WX 602 ; N uni04A4 ; G 943
+U 1189 ; WX 602 ; N uni04A5 ; G 944
+U 1194 ; WX 602 ; N uni04AA ; G 945
+U 1195 ; WX 602 ; N uni04AB ; G 946
+U 1196 ; WX 602 ; N uni04AC ; G 947
+U 1197 ; WX 602 ; N uni04AD ; G 948
+U 1198 ; WX 602 ; N uni04AE ; G 949
+U 1199 ; WX 602 ; N uni04AF ; G 950
+U 1200 ; WX 602 ; N uni04B0 ; G 951
+U 1201 ; WX 602 ; N uni04B1 ; G 952
+U 1202 ; WX 602 ; N uni04B2 ; G 953
+U 1203 ; WX 602 ; N uni04B3 ; G 954
+U 1210 ; WX 602 ; N uni04BA ; G 955
+U 1211 ; WX 602 ; N uni04BB ; G 956
+U 1216 ; WX 602 ; N uni04C0 ; G 957
+U 1217 ; WX 602 ; N uni04C1 ; G 958
+U 1218 ; WX 602 ; N uni04C2 ; G 959
+U 1219 ; WX 602 ; N uni04C3 ; G 960
+U 1220 ; WX 602 ; N uni04C4 ; G 961
+U 1223 ; WX 602 ; N uni04C7 ; G 962
+U 1224 ; WX 602 ; N uni04C8 ; G 963
+U 1227 ; WX 602 ; N uni04CB ; G 964
+U 1228 ; WX 602 ; N uni04CC ; G 965
+U 1231 ; WX 602 ; N uni04CF ; G 966
+U 1232 ; WX 602 ; N uni04D0 ; G 967
+U 1233 ; WX 602 ; N uni04D1 ; G 968
+U 1234 ; WX 602 ; N uni04D2 ; G 969
+U 1235 ; WX 602 ; N uni04D3 ; G 970
+U 1236 ; WX 602 ; N uni04D4 ; G 971
+U 1237 ; WX 602 ; N uni04D5 ; G 972
+U 1238 ; WX 602 ; N uni04D6 ; G 973
+U 1239 ; WX 602 ; N uni04D7 ; G 974
+U 1240 ; WX 602 ; N uni04D8 ; G 975
+U 1241 ; WX 602 ; N uni04D9 ; G 976
+U 1242 ; WX 602 ; N uni04DA ; G 977
+U 1243 ; WX 602 ; N uni04DB ; G 978
+U 1244 ; WX 602 ; N uni04DC ; G 979
+U 1245 ; WX 602 ; N uni04DD ; G 980
+U 1246 ; WX 602 ; N uni04DE ; G 981
+U 1247 ; WX 602 ; N uni04DF ; G 982
+U 1248 ; WX 602 ; N uni04E0 ; G 983
+U 1249 ; WX 602 ; N uni04E1 ; G 984
+U 1250 ; WX 602 ; N uni04E2 ; G 985
+U 1251 ; WX 602 ; N uni04E3 ; G 986
+U 1252 ; WX 602 ; N uni04E4 ; G 987
+U 1253 ; WX 602 ; N uni04E5 ; G 988
+U 1254 ; WX 602 ; N uni04E6 ; G 989
+U 1255 ; WX 602 ; N uni04E7 ; G 990
+U 1256 ; WX 602 ; N uni04E8 ; G 991
+U 1257 ; WX 602 ; N uni04E9 ; G 992
+U 1258 ; WX 602 ; N uni04EA ; G 993
+U 1259 ; WX 602 ; N uni04EB ; G 994
+U 1260 ; WX 602 ; N uni04EC ; G 995
+U 1261 ; WX 602 ; N uni04ED ; G 996
+U 1262 ; WX 602 ; N uni04EE ; G 997
+U 1263 ; WX 602 ; N uni04EF ; G 998
+U 1264 ; WX 602 ; N uni04F0 ; G 999
+U 1265 ; WX 602 ; N uni04F1 ; G 1000
+U 1266 ; WX 602 ; N uni04F2 ; G 1001
+U 1267 ; WX 602 ; N uni04F3 ; G 1002
+U 1268 ; WX 602 ; N uni04F4 ; G 1003
+U 1269 ; WX 602 ; N uni04F5 ; G 1004
+U 1270 ; WX 602 ; N uni04F6 ; G 1005
+U 1271 ; WX 602 ; N uni04F7 ; G 1006
+U 1272 ; WX 602 ; N uni04F8 ; G 1007
+U 1273 ; WX 602 ; N uni04F9 ; G 1008
+U 1296 ; WX 602 ; N uni0510 ; G 1009
+U 1297 ; WX 602 ; N uni0511 ; G 1010
+U 1306 ; WX 602 ; N uni051A ; G 1011
+U 1307 ; WX 602 ; N uni051B ; G 1012
+U 1308 ; WX 602 ; N uni051C ; G 1013
+U 1309 ; WX 602 ; N uni051D ; G 1014
+U 1329 ; WX 602 ; N uni0531 ; G 1015
+U 1330 ; WX 602 ; N uni0532 ; G 1016
+U 1331 ; WX 602 ; N uni0533 ; G 1017
+U 1332 ; WX 602 ; N uni0534 ; G 1018
+U 1333 ; WX 602 ; N uni0535 ; G 1019
+U 1334 ; WX 602 ; N uni0536 ; G 1020
+U 1335 ; WX 602 ; N uni0537 ; G 1021
+U 1336 ; WX 602 ; N uni0538 ; G 1022
+U 1337 ; WX 602 ; N uni0539 ; G 1023
+U 1338 ; WX 602 ; N uni053A ; G 1024
+U 1339 ; WX 602 ; N uni053B ; G 1025
+U 1340 ; WX 602 ; N uni053C ; G 1026
+U 1341 ; WX 602 ; N uni053D ; G 1027
+U 1342 ; WX 602 ; N uni053E ; G 1028
+U 1343 ; WX 602 ; N uni053F ; G 1029
+U 1344 ; WX 602 ; N uni0540 ; G 1030
+U 1345 ; WX 602 ; N uni0541 ; G 1031
+U 1346 ; WX 602 ; N uni0542 ; G 1032
+U 1347 ; WX 602 ; N uni0543 ; G 1033
+U 1348 ; WX 602 ; N uni0544 ; G 1034
+U 1349 ; WX 602 ; N uni0545 ; G 1035
+U 1350 ; WX 602 ; N uni0546 ; G 1036
+U 1351 ; WX 602 ; N uni0547 ; G 1037
+U 1352 ; WX 602 ; N uni0548 ; G 1038
+U 1353 ; WX 602 ; N uni0549 ; G 1039
+U 1354 ; WX 602 ; N uni054A ; G 1040
+U 1355 ; WX 602 ; N uni054B ; G 1041
+U 1356 ; WX 602 ; N uni054C ; G 1042
+U 1357 ; WX 602 ; N uni054D ; G 1043
+U 1358 ; WX 602 ; N uni054E ; G 1044
+U 1359 ; WX 602 ; N uni054F ; G 1045
+U 1360 ; WX 602 ; N uni0550 ; G 1046
+U 1361 ; WX 602 ; N uni0551 ; G 1047
+U 1362 ; WX 602 ; N uni0552 ; G 1048
+U 1363 ; WX 602 ; N uni0553 ; G 1049
+U 1364 ; WX 602 ; N uni0554 ; G 1050
+U 1365 ; WX 602 ; N uni0555 ; G 1051
+U 1366 ; WX 602 ; N uni0556 ; G 1052
+U 1369 ; WX 602 ; N uni0559 ; G 1053
+U 1370 ; WX 602 ; N uni055A ; G 1054
+U 1371 ; WX 602 ; N uni055B ; G 1055
+U 1372 ; WX 602 ; N uni055C ; G 1056
+U 1373 ; WX 602 ; N uni055D ; G 1057
+U 1374 ; WX 602 ; N uni055E ; G 1058
+U 1375 ; WX 602 ; N uni055F ; G 1059
+U 1377 ; WX 602 ; N uni0561 ; G 1060
+U 1378 ; WX 602 ; N uni0562 ; G 1061
+U 1379 ; WX 602 ; N uni0563 ; G 1062
+U 1380 ; WX 602 ; N uni0564 ; G 1063
+U 1381 ; WX 602 ; N uni0565 ; G 1064
+U 1382 ; WX 602 ; N uni0566 ; G 1065
+U 1383 ; WX 602 ; N uni0567 ; G 1066
+U 1384 ; WX 602 ; N uni0568 ; G 1067
+U 1385 ; WX 602 ; N uni0569 ; G 1068
+U 1386 ; WX 602 ; N uni056A ; G 1069
+U 1387 ; WX 602 ; N uni056B ; G 1070
+U 1388 ; WX 602 ; N uni056C ; G 1071
+U 1389 ; WX 602 ; N uni056D ; G 1072
+U 1390 ; WX 602 ; N uni056E ; G 1073
+U 1391 ; WX 602 ; N uni056F ; G 1074
+U 1392 ; WX 602 ; N uni0570 ; G 1075
+U 1393 ; WX 602 ; N uni0571 ; G 1076
+U 1394 ; WX 602 ; N uni0572 ; G 1077
+U 1395 ; WX 602 ; N uni0573 ; G 1078
+U 1396 ; WX 602 ; N uni0574 ; G 1079
+U 1397 ; WX 602 ; N uni0575 ; G 1080
+U 1398 ; WX 602 ; N uni0576 ; G 1081
+U 1399 ; WX 602 ; N uni0577 ; G 1082
+U 1400 ; WX 602 ; N uni0578 ; G 1083
+U 1401 ; WX 602 ; N uni0579 ; G 1084
+U 1402 ; WX 602 ; N uni057A ; G 1085
+U 1403 ; WX 602 ; N uni057B ; G 1086
+U 1404 ; WX 602 ; N uni057C ; G 1087
+U 1405 ; WX 602 ; N uni057D ; G 1088
+U 1406 ; WX 602 ; N uni057E ; G 1089
+U 1407 ; WX 602 ; N uni057F ; G 1090
+U 1408 ; WX 602 ; N uni0580 ; G 1091
+U 1409 ; WX 602 ; N uni0581 ; G 1092
+U 1410 ; WX 602 ; N uni0582 ; G 1093
+U 1411 ; WX 602 ; N uni0583 ; G 1094
+U 1412 ; WX 602 ; N uni0584 ; G 1095
+U 1413 ; WX 602 ; N uni0585 ; G 1096
+U 1414 ; WX 602 ; N uni0586 ; G 1097
+U 1415 ; WX 602 ; N uni0587 ; G 1098
+U 1417 ; WX 602 ; N uni0589 ; G 1099
+U 1418 ; WX 602 ; N uni058A ; G 1100
+U 3647 ; WX 602 ; N uni0E3F ; G 1101
+U 3713 ; WX 602 ; N uni0E81 ; G 1102
+U 3714 ; WX 602 ; N uni0E82 ; G 1103
+U 3716 ; WX 602 ; N uni0E84 ; G 1104
+U 3719 ; WX 602 ; N uni0E87 ; G 1105
+U 3720 ; WX 602 ; N uni0E88 ; G 1106
+U 3722 ; WX 602 ; N uni0E8A ; G 1107
+U 3725 ; WX 602 ; N uni0E8D ; G 1108
+U 3732 ; WX 602 ; N uni0E94 ; G 1109
+U 3733 ; WX 602 ; N uni0E95 ; G 1110
+U 3734 ; WX 602 ; N uni0E96 ; G 1111
+U 3735 ; WX 602 ; N uni0E97 ; G 1112
+U 3737 ; WX 602 ; N uni0E99 ; G 1113
+U 3738 ; WX 602 ; N uni0E9A ; G 1114
+U 3739 ; WX 602 ; N uni0E9B ; G 1115
+U 3740 ; WX 602 ; N uni0E9C ; G 1116
+U 3741 ; WX 602 ; N uni0E9D ; G 1117
+U 3742 ; WX 602 ; N uni0E9E ; G 1118
+U 3743 ; WX 602 ; N uni0E9F ; G 1119
+U 3745 ; WX 602 ; N uni0EA1 ; G 1120
+U 3746 ; WX 602 ; N uni0EA2 ; G 1121
+U 3747 ; WX 602 ; N uni0EA3 ; G 1122
+U 3749 ; WX 602 ; N uni0EA5 ; G 1123
+U 3751 ; WX 602 ; N uni0EA7 ; G 1124
+U 3754 ; WX 602 ; N uni0EAA ; G 1125
+U 3755 ; WX 602 ; N uni0EAB ; G 1126
+U 3757 ; WX 602 ; N uni0EAD ; G 1127
+U 3758 ; WX 602 ; N uni0EAE ; G 1128
+U 3759 ; WX 602 ; N uni0EAF ; G 1129
+U 3760 ; WX 602 ; N uni0EB0 ; G 1130
+U 3761 ; WX 602 ; N uni0EB1 ; G 1131
+U 3762 ; WX 602 ; N uni0EB2 ; G 1132
+U 3763 ; WX 602 ; N uni0EB3 ; G 1133
+U 3764 ; WX 602 ; N uni0EB4 ; G 1134
+U 3765 ; WX 602 ; N uni0EB5 ; G 1135
+U 3766 ; WX 602 ; N uni0EB6 ; G 1136
+U 3767 ; WX 602 ; N uni0EB7 ; G 1137
+U 3768 ; WX 602 ; N uni0EB8 ; G 1138
+U 3769 ; WX 602 ; N uni0EB9 ; G 1139
+U 3771 ; WX 602 ; N uni0EBB ; G 1140
+U 3772 ; WX 602 ; N uni0EBC ; G 1141
+U 3784 ; WX 602 ; N uni0EC8 ; G 1142
+U 3785 ; WX 602 ; N uni0EC9 ; G 1143
+U 3786 ; WX 602 ; N uni0ECA ; G 1144
+U 3787 ; WX 602 ; N uni0ECB ; G 1145
+U 3788 ; WX 602 ; N uni0ECC ; G 1146
+U 3789 ; WX 602 ; N uni0ECD ; G 1147
+U 4304 ; WX 602 ; N uni10D0 ; G 1148
+U 4305 ; WX 602 ; N uni10D1 ; G 1149
+U 4306 ; WX 602 ; N uni10D2 ; G 1150
+U 4307 ; WX 602 ; N uni10D3 ; G 1151
+U 4308 ; WX 602 ; N uni10D4 ; G 1152
+U 4309 ; WX 602 ; N uni10D5 ; G 1153
+U 4310 ; WX 602 ; N uni10D6 ; G 1154
+U 4311 ; WX 602 ; N uni10D7 ; G 1155
+U 4312 ; WX 602 ; N uni10D8 ; G 1156
+U 4313 ; WX 602 ; N uni10D9 ; G 1157
+U 4314 ; WX 602 ; N uni10DA ; G 1158
+U 4315 ; WX 602 ; N uni10DB ; G 1159
+U 4316 ; WX 602 ; N uni10DC ; G 1160
+U 4317 ; WX 602 ; N uni10DD ; G 1161
+U 4318 ; WX 602 ; N uni10DE ; G 1162
+U 4319 ; WX 602 ; N uni10DF ; G 1163
+U 4320 ; WX 602 ; N uni10E0 ; G 1164
+U 4321 ; WX 602 ; N uni10E1 ; G 1165
+U 4322 ; WX 602 ; N uni10E2 ; G 1166
+U 4323 ; WX 602 ; N uni10E3 ; G 1167
+U 4324 ; WX 602 ; N uni10E4 ; G 1168
+U 4325 ; WX 602 ; N uni10E5 ; G 1169
+U 4326 ; WX 602 ; N uni10E6 ; G 1170
+U 4327 ; WX 602 ; N uni10E7 ; G 1171
+U 4328 ; WX 602 ; N uni10E8 ; G 1172
+U 4329 ; WX 602 ; N uni10E9 ; G 1173
+U 4330 ; WX 602 ; N uni10EA ; G 1174
+U 4331 ; WX 602 ; N uni10EB ; G 1175
+U 4332 ; WX 602 ; N uni10EC ; G 1176
+U 4333 ; WX 602 ; N uni10ED ; G 1177
+U 4334 ; WX 602 ; N uni10EE ; G 1178
+U 4335 ; WX 602 ; N uni10EF ; G 1179
+U 4336 ; WX 602 ; N uni10F0 ; G 1180
+U 4337 ; WX 602 ; N uni10F1 ; G 1181
+U 4338 ; WX 602 ; N uni10F2 ; G 1182
+U 4339 ; WX 602 ; N uni10F3 ; G 1183
+U 4340 ; WX 602 ; N uni10F4 ; G 1184
+U 4341 ; WX 602 ; N uni10F5 ; G 1185
+U 4342 ; WX 602 ; N uni10F6 ; G 1186
+U 4343 ; WX 602 ; N uni10F7 ; G 1187
+U 4344 ; WX 602 ; N uni10F8 ; G 1188
+U 4345 ; WX 602 ; N uni10F9 ; G 1189
+U 4346 ; WX 602 ; N uni10FA ; G 1190
+U 4347 ; WX 602 ; N uni10FB ; G 1191
+U 4348 ; WX 602 ; N uni10FC ; G 1192
+U 7426 ; WX 602 ; N uni1D02 ; G 1193
+U 7432 ; WX 602 ; N uni1D08 ; G 1194
+U 7433 ; WX 602 ; N uni1D09 ; G 1195
+U 7444 ; WX 602 ; N uni1D14 ; G 1196
+U 7446 ; WX 602 ; N uni1D16 ; G 1197
+U 7447 ; WX 602 ; N uni1D17 ; G 1198
+U 7453 ; WX 602 ; N uni1D1D ; G 1199
+U 7454 ; WX 602 ; N uni1D1E ; G 1200
+U 7455 ; WX 602 ; N uni1D1F ; G 1201
+U 7468 ; WX 602 ; N uni1D2C ; G 1202
+U 7469 ; WX 602 ; N uni1D2D ; G 1203
+U 7470 ; WX 602 ; N uni1D2E ; G 1204
+U 7472 ; WX 602 ; N uni1D30 ; G 1205
+U 7473 ; WX 602 ; N uni1D31 ; G 1206
+U 7474 ; WX 602 ; N uni1D32 ; G 1207
+U 7475 ; WX 602 ; N uni1D33 ; G 1208
+U 7476 ; WX 602 ; N uni1D34 ; G 1209
+U 7477 ; WX 602 ; N uni1D35 ; G 1210
+U 7478 ; WX 602 ; N uni1D36 ; G 1211
+U 7479 ; WX 602 ; N uni1D37 ; G 1212
+U 7480 ; WX 602 ; N uni1D38 ; G 1213
+U 7481 ; WX 602 ; N uni1D39 ; G 1214
+U 7482 ; WX 602 ; N uni1D3A ; G 1215
+U 7483 ; WX 602 ; N uni1D3B ; G 1216
+U 7484 ; WX 602 ; N uni1D3C ; G 1217
+U 7486 ; WX 602 ; N uni1D3E ; G 1218
+U 7487 ; WX 602 ; N uni1D3F ; G 1219
+U 7488 ; WX 602 ; N uni1D40 ; G 1220
+U 7489 ; WX 602 ; N uni1D41 ; G 1221
+U 7490 ; WX 602 ; N uni1D42 ; G 1222
+U 7491 ; WX 602 ; N uni1D43 ; G 1223
+U 7492 ; WX 602 ; N uni1D44 ; G 1224
+U 7493 ; WX 602 ; N uni1D45 ; G 1225
+U 7494 ; WX 602 ; N uni1D46 ; G 1226
+U 7495 ; WX 602 ; N uni1D47 ; G 1227
+U 7496 ; WX 602 ; N uni1D48 ; G 1228
+U 7497 ; WX 602 ; N uni1D49 ; G 1229
+U 7498 ; WX 602 ; N uni1D4A ; G 1230
+U 7499 ; WX 602 ; N uni1D4B ; G 1231
+U 7500 ; WX 602 ; N uni1D4C ; G 1232
+U 7501 ; WX 602 ; N uni1D4D ; G 1233
+U 7502 ; WX 602 ; N uni1D4E ; G 1234
+U 7503 ; WX 602 ; N uni1D4F ; G 1235
+U 7504 ; WX 602 ; N uni1D50 ; G 1236
+U 7505 ; WX 602 ; N uni1D51 ; G 1237
+U 7506 ; WX 602 ; N uni1D52 ; G 1238
+U 7507 ; WX 602 ; N uni1D53 ; G 1239
+U 7508 ; WX 602 ; N uni1D54 ; G 1240
+U 7509 ; WX 602 ; N uni1D55 ; G 1241
+U 7510 ; WX 602 ; N uni1D56 ; G 1242
+U 7511 ; WX 602 ; N uni1D57 ; G 1243
+U 7512 ; WX 602 ; N uni1D58 ; G 1244
+U 7513 ; WX 602 ; N uni1D59 ; G 1245
+U 7514 ; WX 602 ; N uni1D5A ; G 1246
+U 7515 ; WX 602 ; N uni1D5B ; G 1247
+U 7522 ; WX 602 ; N uni1D62 ; G 1248
+U 7523 ; WX 602 ; N uni1D63 ; G 1249
+U 7524 ; WX 602 ; N uni1D64 ; G 1250
+U 7525 ; WX 602 ; N uni1D65 ; G 1251
+U 7543 ; WX 602 ; N uni1D77 ; G 1252
+U 7544 ; WX 602 ; N uni1D78 ; G 1253
+U 7547 ; WX 602 ; N uni1D7B ; G 1254
+U 7557 ; WX 602 ; N uni1D85 ; G 1255
+U 7579 ; WX 602 ; N uni1D9B ; G 1256
+U 7580 ; WX 602 ; N uni1D9C ; G 1257
+U 7581 ; WX 602 ; N uni1D9D ; G 1258
+U 7582 ; WX 602 ; N uni1D9E ; G 1259
+U 7583 ; WX 602 ; N uni1D9F ; G 1260
+U 7584 ; WX 602 ; N uni1DA0 ; G 1261
+U 7585 ; WX 602 ; N uni1DA1 ; G 1262
+U 7586 ; WX 602 ; N uni1DA2 ; G 1263
+U 7587 ; WX 602 ; N uni1DA3 ; G 1264
+U 7588 ; WX 602 ; N uni1DA4 ; G 1265
+U 7589 ; WX 602 ; N uni1DA5 ; G 1266
+U 7590 ; WX 602 ; N uni1DA6 ; G 1267
+U 7591 ; WX 602 ; N uni1DA7 ; G 1268
+U 7592 ; WX 602 ; N uni1DA8 ; G 1269
+U 7593 ; WX 602 ; N uni1DA9 ; G 1270
+U 7594 ; WX 602 ; N uni1DAA ; G 1271
+U 7595 ; WX 602 ; N uni1DAB ; G 1272
+U 7596 ; WX 602 ; N uni1DAC ; G 1273
+U 7597 ; WX 602 ; N uni1DAD ; G 1274
+U 7598 ; WX 602 ; N uni1DAE ; G 1275
+U 7599 ; WX 602 ; N uni1DAF ; G 1276
+U 7600 ; WX 602 ; N uni1DB0 ; G 1277
+U 7601 ; WX 602 ; N uni1DB1 ; G 1278
+U 7602 ; WX 602 ; N uni1DB2 ; G 1279
+U 7603 ; WX 602 ; N uni1DB3 ; G 1280
+U 7604 ; WX 602 ; N uni1DB4 ; G 1281
+U 7605 ; WX 602 ; N uni1DB5 ; G 1282
+U 7606 ; WX 602 ; N uni1DB6 ; G 1283
+U 7607 ; WX 602 ; N uni1DB7 ; G 1284
+U 7609 ; WX 602 ; N uni1DB9 ; G 1285
+U 7610 ; WX 602 ; N uni1DBA ; G 1286
+U 7611 ; WX 602 ; N uni1DBB ; G 1287
+U 7612 ; WX 602 ; N uni1DBC ; G 1288
+U 7613 ; WX 602 ; N uni1DBD ; G 1289
+U 7614 ; WX 602 ; N uni1DBE ; G 1290
+U 7615 ; WX 602 ; N uni1DBF ; G 1291
+U 7680 ; WX 602 ; N uni1E00 ; G 1292
+U 7681 ; WX 602 ; N uni1E01 ; G 1293
+U 7682 ; WX 602 ; N uni1E02 ; G 1294
+U 7683 ; WX 602 ; N uni1E03 ; G 1295
+U 7684 ; WX 602 ; N uni1E04 ; G 1296
+U 7685 ; WX 602 ; N uni1E05 ; G 1297
+U 7686 ; WX 602 ; N uni1E06 ; G 1298
+U 7687 ; WX 602 ; N uni1E07 ; G 1299
+U 7688 ; WX 602 ; N uni1E08 ; G 1300
+U 7689 ; WX 602 ; N uni1E09 ; G 1301
+U 7690 ; WX 602 ; N uni1E0A ; G 1302
+U 7691 ; WX 602 ; N uni1E0B ; G 1303
+U 7692 ; WX 602 ; N uni1E0C ; G 1304
+U 7693 ; WX 602 ; N uni1E0D ; G 1305
+U 7694 ; WX 602 ; N uni1E0E ; G 1306
+U 7695 ; WX 602 ; N uni1E0F ; G 1307
+U 7696 ; WX 602 ; N uni1E10 ; G 1308
+U 7697 ; WX 602 ; N uni1E11 ; G 1309
+U 7698 ; WX 602 ; N uni1E12 ; G 1310
+U 7699 ; WX 602 ; N uni1E13 ; G 1311
+U 7704 ; WX 602 ; N uni1E18 ; G 1312
+U 7705 ; WX 602 ; N uni1E19 ; G 1313
+U 7706 ; WX 602 ; N uni1E1A ; G 1314
+U 7707 ; WX 602 ; N uni1E1B ; G 1315
+U 7708 ; WX 602 ; N uni1E1C ; G 1316
+U 7709 ; WX 602 ; N uni1E1D ; G 1317
+U 7710 ; WX 602 ; N uni1E1E ; G 1318
+U 7711 ; WX 602 ; N uni1E1F ; G 1319
+U 7712 ; WX 602 ; N uni1E20 ; G 1320
+U 7713 ; WX 602 ; N uni1E21 ; G 1321
+U 7714 ; WX 602 ; N uni1E22 ; G 1322
+U 7715 ; WX 602 ; N uni1E23 ; G 1323
+U 7716 ; WX 602 ; N uni1E24 ; G 1324
+U 7717 ; WX 602 ; N uni1E25 ; G 1325
+U 7718 ; WX 602 ; N uni1E26 ; G 1326
+U 7719 ; WX 602 ; N uni1E27 ; G 1327
+U 7720 ; WX 602 ; N uni1E28 ; G 1328
+U 7721 ; WX 602 ; N uni1E29 ; G 1329
+U 7722 ; WX 602 ; N uni1E2A ; G 1330
+U 7723 ; WX 602 ; N uni1E2B ; G 1331
+U 7724 ; WX 602 ; N uni1E2C ; G 1332
+U 7725 ; WX 602 ; N uni1E2D ; G 1333
+U 7728 ; WX 602 ; N uni1E30 ; G 1334
+U 7729 ; WX 602 ; N uni1E31 ; G 1335
+U 7730 ; WX 602 ; N uni1E32 ; G 1336
+U 7731 ; WX 602 ; N uni1E33 ; G 1337
+U 7732 ; WX 602 ; N uni1E34 ; G 1338
+U 7733 ; WX 602 ; N uni1E35 ; G 1339
+U 7734 ; WX 602 ; N uni1E36 ; G 1340
+U 7735 ; WX 602 ; N uni1E37 ; G 1341
+U 7736 ; WX 602 ; N uni1E38 ; G 1342
+U 7737 ; WX 602 ; N uni1E39 ; G 1343
+U 7738 ; WX 602 ; N uni1E3A ; G 1344
+U 7739 ; WX 602 ; N uni1E3B ; G 1345
+U 7740 ; WX 602 ; N uni1E3C ; G 1346
+U 7741 ; WX 602 ; N uni1E3D ; G 1347
+U 7742 ; WX 602 ; N uni1E3E ; G 1348
+U 7743 ; WX 602 ; N uni1E3F ; G 1349
+U 7744 ; WX 602 ; N uni1E40 ; G 1350
+U 7745 ; WX 602 ; N uni1E41 ; G 1351
+U 7746 ; WX 602 ; N uni1E42 ; G 1352
+U 7747 ; WX 602 ; N uni1E43 ; G 1353
+U 7748 ; WX 602 ; N uni1E44 ; G 1354
+U 7749 ; WX 602 ; N uni1E45 ; G 1355
+U 7750 ; WX 602 ; N uni1E46 ; G 1356
+U 7751 ; WX 602 ; N uni1E47 ; G 1357
+U 7752 ; WX 602 ; N uni1E48 ; G 1358
+U 7753 ; WX 602 ; N uni1E49 ; G 1359
+U 7754 ; WX 602 ; N uni1E4A ; G 1360
+U 7755 ; WX 602 ; N uni1E4B ; G 1361
+U 7756 ; WX 602 ; N uni1E4C ; G 1362
+U 7757 ; WX 602 ; N uni1E4D ; G 1363
+U 7764 ; WX 602 ; N uni1E54 ; G 1364
+U 7765 ; WX 602 ; N uni1E55 ; G 1365
+U 7766 ; WX 602 ; N uni1E56 ; G 1366
+U 7767 ; WX 602 ; N uni1E57 ; G 1367
+U 7768 ; WX 602 ; N uni1E58 ; G 1368
+U 7769 ; WX 602 ; N uni1E59 ; G 1369
+U 7770 ; WX 602 ; N uni1E5A ; G 1370
+U 7771 ; WX 602 ; N uni1E5B ; G 1371
+U 7772 ; WX 602 ; N uni1E5C ; G 1372
+U 7773 ; WX 602 ; N uni1E5D ; G 1373
+U 7774 ; WX 602 ; N uni1E5E ; G 1374
+U 7775 ; WX 602 ; N uni1E5F ; G 1375
+U 7776 ; WX 602 ; N uni1E60 ; G 1376
+U 7777 ; WX 602 ; N uni1E61 ; G 1377
+U 7778 ; WX 602 ; N uni1E62 ; G 1378
+U 7779 ; WX 602 ; N uni1E63 ; G 1379
+U 7784 ; WX 602 ; N uni1E68 ; G 1380
+U 7785 ; WX 602 ; N uni1E69 ; G 1381
+U 7786 ; WX 602 ; N uni1E6A ; G 1382
+U 7787 ; WX 602 ; N uni1E6B ; G 1383
+U 7788 ; WX 602 ; N uni1E6C ; G 1384
+U 7789 ; WX 602 ; N uni1E6D ; G 1385
+U 7790 ; WX 602 ; N uni1E6E ; G 1386
+U 7791 ; WX 602 ; N uni1E6F ; G 1387
+U 7792 ; WX 602 ; N uni1E70 ; G 1388
+U 7793 ; WX 602 ; N uni1E71 ; G 1389
+U 7794 ; WX 602 ; N uni1E72 ; G 1390
+U 7795 ; WX 602 ; N uni1E73 ; G 1391
+U 7796 ; WX 602 ; N uni1E74 ; G 1392
+U 7797 ; WX 602 ; N uni1E75 ; G 1393
+U 7798 ; WX 602 ; N uni1E76 ; G 1394
+U 7799 ; WX 602 ; N uni1E77 ; G 1395
+U 7800 ; WX 602 ; N uni1E78 ; G 1396
+U 7801 ; WX 602 ; N uni1E79 ; G 1397
+U 7804 ; WX 602 ; N uni1E7C ; G 1398
+U 7805 ; WX 602 ; N uni1E7D ; G 1399
+U 7806 ; WX 602 ; N uni1E7E ; G 1400
+U 7807 ; WX 602 ; N uni1E7F ; G 1401
+U 7808 ; WX 602 ; N Wgrave ; G 1402
+U 7809 ; WX 602 ; N wgrave ; G 1403
+U 7810 ; WX 602 ; N Wacute ; G 1404
+U 7811 ; WX 602 ; N wacute ; G 1405
+U 7812 ; WX 602 ; N Wdieresis ; G 1406
+U 7813 ; WX 602 ; N wdieresis ; G 1407
+U 7814 ; WX 602 ; N uni1E86 ; G 1408
+U 7815 ; WX 602 ; N uni1E87 ; G 1409
+U 7816 ; WX 602 ; N uni1E88 ; G 1410
+U 7817 ; WX 602 ; N uni1E89 ; G 1411
+U 7818 ; WX 602 ; N uni1E8A ; G 1412
+U 7819 ; WX 602 ; N uni1E8B ; G 1413
+U 7820 ; WX 602 ; N uni1E8C ; G 1414
+U 7821 ; WX 602 ; N uni1E8D ; G 1415
+U 7822 ; WX 602 ; N uni1E8E ; G 1416
+U 7823 ; WX 602 ; N uni1E8F ; G 1417
+U 7824 ; WX 602 ; N uni1E90 ; G 1418
+U 7825 ; WX 602 ; N uni1E91 ; G 1419
+U 7826 ; WX 602 ; N uni1E92 ; G 1420
+U 7827 ; WX 602 ; N uni1E93 ; G 1421
+U 7828 ; WX 602 ; N uni1E94 ; G 1422
+U 7829 ; WX 602 ; N uni1E95 ; G 1423
+U 7830 ; WX 602 ; N uni1E96 ; G 1424
+U 7831 ; WX 602 ; N uni1E97 ; G 1425
+U 7832 ; WX 602 ; N uni1E98 ; G 1426
+U 7833 ; WX 602 ; N uni1E99 ; G 1427
+U 7835 ; WX 602 ; N uni1E9B ; G 1428
+U 7839 ; WX 602 ; N uni1E9F ; G 1429
+U 7840 ; WX 602 ; N uni1EA0 ; G 1430
+U 7841 ; WX 602 ; N uni1EA1 ; G 1431
+U 7852 ; WX 602 ; N uni1EAC ; G 1432
+U 7853 ; WX 602 ; N uni1EAD ; G 1433
+U 7856 ; WX 602 ; N uni1EB0 ; G 1434
+U 7857 ; WX 602 ; N uni1EB1 ; G 1435
+U 7862 ; WX 602 ; N uni1EB6 ; G 1436
+U 7863 ; WX 602 ; N uni1EB7 ; G 1437
+U 7864 ; WX 602 ; N uni1EB8 ; G 1438
+U 7865 ; WX 602 ; N uni1EB9 ; G 1439
+U 7868 ; WX 602 ; N uni1EBC ; G 1440
+U 7869 ; WX 602 ; N uni1EBD ; G 1441
+U 7878 ; WX 602 ; N uni1EC6 ; G 1442
+U 7879 ; WX 602 ; N uni1EC7 ; G 1443
+U 7882 ; WX 602 ; N uni1ECA ; G 1444
+U 7883 ; WX 602 ; N uni1ECB ; G 1445
+U 7884 ; WX 602 ; N uni1ECC ; G 1446
+U 7885 ; WX 602 ; N uni1ECD ; G 1447
+U 7896 ; WX 602 ; N uni1ED8 ; G 1448
+U 7897 ; WX 602 ; N uni1ED9 ; G 1449
+U 7898 ; WX 602 ; N uni1EDA ; G 1450
+U 7899 ; WX 602 ; N uni1EDB ; G 1451
+U 7900 ; WX 602 ; N uni1EDC ; G 1452
+U 7901 ; WX 602 ; N uni1EDD ; G 1453
+U 7904 ; WX 602 ; N uni1EE0 ; G 1454
+U 7905 ; WX 602 ; N uni1EE1 ; G 1455
+U 7906 ; WX 602 ; N uni1EE2 ; G 1456
+U 7907 ; WX 602 ; N uni1EE3 ; G 1457
+U 7908 ; WX 602 ; N uni1EE4 ; G 1458
+U 7909 ; WX 602 ; N uni1EE5 ; G 1459
+U 7912 ; WX 602 ; N uni1EE8 ; G 1460
+U 7913 ; WX 602 ; N uni1EE9 ; G 1461
+U 7914 ; WX 602 ; N uni1EEA ; G 1462
+U 7915 ; WX 602 ; N uni1EEB ; G 1463
+U 7918 ; WX 602 ; N uni1EEE ; G 1464
+U 7919 ; WX 602 ; N uni1EEF ; G 1465
+U 7920 ; WX 602 ; N uni1EF0 ; G 1466
+U 7921 ; WX 602 ; N uni1EF1 ; G 1467
+U 7922 ; WX 602 ; N Ygrave ; G 1468
+U 7923 ; WX 602 ; N ygrave ; G 1469
+U 7924 ; WX 602 ; N uni1EF4 ; G 1470
+U 7925 ; WX 602 ; N uni1EF5 ; G 1471
+U 7928 ; WX 602 ; N uni1EF8 ; G 1472
+U 7929 ; WX 602 ; N uni1EF9 ; G 1473
+U 7936 ; WX 602 ; N uni1F00 ; G 1474
+U 7937 ; WX 602 ; N uni1F01 ; G 1475
+U 7938 ; WX 602 ; N uni1F02 ; G 1476
+U 7939 ; WX 602 ; N uni1F03 ; G 1477
+U 7940 ; WX 602 ; N uni1F04 ; G 1478
+U 7941 ; WX 602 ; N uni1F05 ; G 1479
+U 7942 ; WX 602 ; N uni1F06 ; G 1480
+U 7943 ; WX 602 ; N uni1F07 ; G 1481
+U 7944 ; WX 602 ; N uni1F08 ; G 1482
+U 7945 ; WX 602 ; N uni1F09 ; G 1483
+U 7946 ; WX 602 ; N uni1F0A ; G 1484
+U 7947 ; WX 602 ; N uni1F0B ; G 1485
+U 7948 ; WX 602 ; N uni1F0C ; G 1486
+U 7949 ; WX 602 ; N uni1F0D ; G 1487
+U 7950 ; WX 602 ; N uni1F0E ; G 1488
+U 7951 ; WX 602 ; N uni1F0F ; G 1489
+U 7952 ; WX 602 ; N uni1F10 ; G 1490
+U 7953 ; WX 602 ; N uni1F11 ; G 1491
+U 7954 ; WX 602 ; N uni1F12 ; G 1492
+U 7955 ; WX 602 ; N uni1F13 ; G 1493
+U 7956 ; WX 602 ; N uni1F14 ; G 1494
+U 7957 ; WX 602 ; N uni1F15 ; G 1495
+U 7960 ; WX 602 ; N uni1F18 ; G 1496
+U 7961 ; WX 602 ; N uni1F19 ; G 1497
+U 7962 ; WX 602 ; N uni1F1A ; G 1498
+U 7963 ; WX 602 ; N uni1F1B ; G 1499
+U 7964 ; WX 602 ; N uni1F1C ; G 1500
+U 7965 ; WX 602 ; N uni1F1D ; G 1501
+U 7968 ; WX 602 ; N uni1F20 ; G 1502
+U 7969 ; WX 602 ; N uni1F21 ; G 1503
+U 7970 ; WX 602 ; N uni1F22 ; G 1504
+U 7971 ; WX 602 ; N uni1F23 ; G 1505
+U 7972 ; WX 602 ; N uni1F24 ; G 1506
+U 7973 ; WX 602 ; N uni1F25 ; G 1507
+U 7974 ; WX 602 ; N uni1F26 ; G 1508
+U 7975 ; WX 602 ; N uni1F27 ; G 1509
+U 7976 ; WX 602 ; N uni1F28 ; G 1510
+U 7977 ; WX 602 ; N uni1F29 ; G 1511
+U 7978 ; WX 602 ; N uni1F2A ; G 1512
+U 7979 ; WX 602 ; N uni1F2B ; G 1513
+U 7980 ; WX 602 ; N uni1F2C ; G 1514
+U 7981 ; WX 602 ; N uni1F2D ; G 1515
+U 7982 ; WX 602 ; N uni1F2E ; G 1516
+U 7983 ; WX 602 ; N uni1F2F ; G 1517
+U 7984 ; WX 602 ; N uni1F30 ; G 1518
+U 7985 ; WX 602 ; N uni1F31 ; G 1519
+U 7986 ; WX 602 ; N uni1F32 ; G 1520
+U 7987 ; WX 602 ; N uni1F33 ; G 1521
+U 7988 ; WX 602 ; N uni1F34 ; G 1522
+U 7989 ; WX 602 ; N uni1F35 ; G 1523
+U 7990 ; WX 602 ; N uni1F36 ; G 1524
+U 7991 ; WX 602 ; N uni1F37 ; G 1525
+U 7992 ; WX 602 ; N uni1F38 ; G 1526
+U 7993 ; WX 602 ; N uni1F39 ; G 1527
+U 7994 ; WX 602 ; N uni1F3A ; G 1528
+U 7995 ; WX 602 ; N uni1F3B ; G 1529
+U 7996 ; WX 602 ; N uni1F3C ; G 1530
+U 7997 ; WX 602 ; N uni1F3D ; G 1531
+U 7998 ; WX 602 ; N uni1F3E ; G 1532
+U 7999 ; WX 602 ; N uni1F3F ; G 1533
+U 8000 ; WX 602 ; N uni1F40 ; G 1534
+U 8001 ; WX 602 ; N uni1F41 ; G 1535
+U 8002 ; WX 602 ; N uni1F42 ; G 1536
+U 8003 ; WX 602 ; N uni1F43 ; G 1537
+U 8004 ; WX 602 ; N uni1F44 ; G 1538
+U 8005 ; WX 602 ; N uni1F45 ; G 1539
+U 8008 ; WX 602 ; N uni1F48 ; G 1540
+U 8009 ; WX 602 ; N uni1F49 ; G 1541
+U 8010 ; WX 602 ; N uni1F4A ; G 1542
+U 8011 ; WX 602 ; N uni1F4B ; G 1543
+U 8012 ; WX 602 ; N uni1F4C ; G 1544
+U 8013 ; WX 602 ; N uni1F4D ; G 1545
+U 8016 ; WX 602 ; N uni1F50 ; G 1546
+U 8017 ; WX 602 ; N uni1F51 ; G 1547
+U 8018 ; WX 602 ; N uni1F52 ; G 1548
+U 8019 ; WX 602 ; N uni1F53 ; G 1549
+U 8020 ; WX 602 ; N uni1F54 ; G 1550
+U 8021 ; WX 602 ; N uni1F55 ; G 1551
+U 8022 ; WX 602 ; N uni1F56 ; G 1552
+U 8023 ; WX 602 ; N uni1F57 ; G 1553
+U 8025 ; WX 602 ; N uni1F59 ; G 1554
+U 8027 ; WX 602 ; N uni1F5B ; G 1555
+U 8029 ; WX 602 ; N uni1F5D ; G 1556
+U 8031 ; WX 602 ; N uni1F5F ; G 1557
+U 8032 ; WX 602 ; N uni1F60 ; G 1558
+U 8033 ; WX 602 ; N uni1F61 ; G 1559
+U 8034 ; WX 602 ; N uni1F62 ; G 1560
+U 8035 ; WX 602 ; N uni1F63 ; G 1561
+U 8036 ; WX 602 ; N uni1F64 ; G 1562
+U 8037 ; WX 602 ; N uni1F65 ; G 1563
+U 8038 ; WX 602 ; N uni1F66 ; G 1564
+U 8039 ; WX 602 ; N uni1F67 ; G 1565
+U 8040 ; WX 602 ; N uni1F68 ; G 1566
+U 8041 ; WX 602 ; N uni1F69 ; G 1567
+U 8042 ; WX 602 ; N uni1F6A ; G 1568
+U 8043 ; WX 602 ; N uni1F6B ; G 1569
+U 8044 ; WX 602 ; N uni1F6C ; G 1570
+U 8045 ; WX 602 ; N uni1F6D ; G 1571
+U 8046 ; WX 602 ; N uni1F6E ; G 1572
+U 8047 ; WX 602 ; N uni1F6F ; G 1573
+U 8048 ; WX 602 ; N uni1F70 ; G 1574
+U 8049 ; WX 602 ; N uni1F71 ; G 1575
+U 8050 ; WX 602 ; N uni1F72 ; G 1576
+U 8051 ; WX 602 ; N uni1F73 ; G 1577
+U 8052 ; WX 602 ; N uni1F74 ; G 1578
+U 8053 ; WX 602 ; N uni1F75 ; G 1579
+U 8054 ; WX 602 ; N uni1F76 ; G 1580
+U 8055 ; WX 602 ; N uni1F77 ; G 1581
+U 8056 ; WX 602 ; N uni1F78 ; G 1582
+U 8057 ; WX 602 ; N uni1F79 ; G 1583
+U 8058 ; WX 602 ; N uni1F7A ; G 1584
+U 8059 ; WX 602 ; N uni1F7B ; G 1585
+U 8060 ; WX 602 ; N uni1F7C ; G 1586
+U 8061 ; WX 602 ; N uni1F7D ; G 1587
+U 8064 ; WX 602 ; N uni1F80 ; G 1588
+U 8065 ; WX 602 ; N uni1F81 ; G 1589
+U 8066 ; WX 602 ; N uni1F82 ; G 1590
+U 8067 ; WX 602 ; N uni1F83 ; G 1591
+U 8068 ; WX 602 ; N uni1F84 ; G 1592
+U 8069 ; WX 602 ; N uni1F85 ; G 1593
+U 8070 ; WX 602 ; N uni1F86 ; G 1594
+U 8071 ; WX 602 ; N uni1F87 ; G 1595
+U 8072 ; WX 602 ; N uni1F88 ; G 1596
+U 8073 ; WX 602 ; N uni1F89 ; G 1597
+U 8074 ; WX 602 ; N uni1F8A ; G 1598
+U 8075 ; WX 602 ; N uni1F8B ; G 1599
+U 8076 ; WX 602 ; N uni1F8C ; G 1600
+U 8077 ; WX 602 ; N uni1F8D ; G 1601
+U 8078 ; WX 602 ; N uni1F8E ; G 1602
+U 8079 ; WX 602 ; N uni1F8F ; G 1603
+U 8080 ; WX 602 ; N uni1F90 ; G 1604
+U 8081 ; WX 602 ; N uni1F91 ; G 1605
+U 8082 ; WX 602 ; N uni1F92 ; G 1606
+U 8083 ; WX 602 ; N uni1F93 ; G 1607
+U 8084 ; WX 602 ; N uni1F94 ; G 1608
+U 8085 ; WX 602 ; N uni1F95 ; G 1609
+U 8086 ; WX 602 ; N uni1F96 ; G 1610
+U 8087 ; WX 602 ; N uni1F97 ; G 1611
+U 8088 ; WX 602 ; N uni1F98 ; G 1612
+U 8089 ; WX 602 ; N uni1F99 ; G 1613
+U 8090 ; WX 602 ; N uni1F9A ; G 1614
+U 8091 ; WX 602 ; N uni1F9B ; G 1615
+U 8092 ; WX 602 ; N uni1F9C ; G 1616
+U 8093 ; WX 602 ; N uni1F9D ; G 1617
+U 8094 ; WX 602 ; N uni1F9E ; G 1618
+U 8095 ; WX 602 ; N uni1F9F ; G 1619
+U 8096 ; WX 602 ; N uni1FA0 ; G 1620
+U 8097 ; WX 602 ; N uni1FA1 ; G 1621
+U 8098 ; WX 602 ; N uni1FA2 ; G 1622
+U 8099 ; WX 602 ; N uni1FA3 ; G 1623
+U 8100 ; WX 602 ; N uni1FA4 ; G 1624
+U 8101 ; WX 602 ; N uni1FA5 ; G 1625
+U 8102 ; WX 602 ; N uni1FA6 ; G 1626
+U 8103 ; WX 602 ; N uni1FA7 ; G 1627
+U 8104 ; WX 602 ; N uni1FA8 ; G 1628
+U 8105 ; WX 602 ; N uni1FA9 ; G 1629
+U 8106 ; WX 602 ; N uni1FAA ; G 1630
+U 8107 ; WX 602 ; N uni1FAB ; G 1631
+U 8108 ; WX 602 ; N uni1FAC ; G 1632
+U 8109 ; WX 602 ; N uni1FAD ; G 1633
+U 8110 ; WX 602 ; N uni1FAE ; G 1634
+U 8111 ; WX 602 ; N uni1FAF ; G 1635
+U 8112 ; WX 602 ; N uni1FB0 ; G 1636
+U 8113 ; WX 602 ; N uni1FB1 ; G 1637
+U 8114 ; WX 602 ; N uni1FB2 ; G 1638
+U 8115 ; WX 602 ; N uni1FB3 ; G 1639
+U 8116 ; WX 602 ; N uni1FB4 ; G 1640
+U 8118 ; WX 602 ; N uni1FB6 ; G 1641
+U 8119 ; WX 602 ; N uni1FB7 ; G 1642
+U 8120 ; WX 602 ; N uni1FB8 ; G 1643
+U 8121 ; WX 602 ; N uni1FB9 ; G 1644
+U 8122 ; WX 602 ; N uni1FBA ; G 1645
+U 8123 ; WX 602 ; N uni1FBB ; G 1646
+U 8124 ; WX 602 ; N uni1FBC ; G 1647
+U 8125 ; WX 602 ; N uni1FBD ; G 1648
+U 8126 ; WX 602 ; N uni1FBE ; G 1649
+U 8127 ; WX 602 ; N uni1FBF ; G 1650
+U 8128 ; WX 602 ; N uni1FC0 ; G 1651
+U 8129 ; WX 602 ; N uni1FC1 ; G 1652
+U 8130 ; WX 602 ; N uni1FC2 ; G 1653
+U 8131 ; WX 602 ; N uni1FC3 ; G 1654
+U 8132 ; WX 602 ; N uni1FC4 ; G 1655
+U 8134 ; WX 602 ; N uni1FC6 ; G 1656
+U 8135 ; WX 602 ; N uni1FC7 ; G 1657
+U 8136 ; WX 602 ; N uni1FC8 ; G 1658
+U 8137 ; WX 602 ; N uni1FC9 ; G 1659
+U 8138 ; WX 602 ; N uni1FCA ; G 1660
+U 8139 ; WX 602 ; N uni1FCB ; G 1661
+U 8140 ; WX 602 ; N uni1FCC ; G 1662
+U 8141 ; WX 602 ; N uni1FCD ; G 1663
+U 8142 ; WX 602 ; N uni1FCE ; G 1664
+U 8143 ; WX 602 ; N uni1FCF ; G 1665
+U 8144 ; WX 602 ; N uni1FD0 ; G 1666
+U 8145 ; WX 602 ; N uni1FD1 ; G 1667
+U 8146 ; WX 602 ; N uni1FD2 ; G 1668
+U 8147 ; WX 602 ; N uni1FD3 ; G 1669
+U 8150 ; WX 602 ; N uni1FD6 ; G 1670
+U 8151 ; WX 602 ; N uni1FD7 ; G 1671
+U 8152 ; WX 602 ; N uni1FD8 ; G 1672
+U 8153 ; WX 602 ; N uni1FD9 ; G 1673
+U 8154 ; WX 602 ; N uni1FDA ; G 1674
+U 8155 ; WX 602 ; N uni1FDB ; G 1675
+U 8157 ; WX 602 ; N uni1FDD ; G 1676
+U 8158 ; WX 602 ; N uni1FDE ; G 1677
+U 8159 ; WX 602 ; N uni1FDF ; G 1678
+U 8160 ; WX 602 ; N uni1FE0 ; G 1679
+U 8161 ; WX 602 ; N uni1FE1 ; G 1680
+U 8162 ; WX 602 ; N uni1FE2 ; G 1681
+U 8163 ; WX 602 ; N uni1FE3 ; G 1682
+U 8164 ; WX 602 ; N uni1FE4 ; G 1683
+U 8165 ; WX 602 ; N uni1FE5 ; G 1684
+U 8166 ; WX 602 ; N uni1FE6 ; G 1685
+U 8167 ; WX 602 ; N uni1FE7 ; G 1686
+U 8168 ; WX 602 ; N uni1FE8 ; G 1687
+U 8169 ; WX 602 ; N uni1FE9 ; G 1688
+U 8170 ; WX 602 ; N uni1FEA ; G 1689
+U 8171 ; WX 602 ; N uni1FEB ; G 1690
+U 8172 ; WX 602 ; N uni1FEC ; G 1691
+U 8173 ; WX 602 ; N uni1FED ; G 1692
+U 8174 ; WX 602 ; N uni1FEE ; G 1693
+U 8175 ; WX 602 ; N uni1FEF ; G 1694
+U 8178 ; WX 602 ; N uni1FF2 ; G 1695
+U 8179 ; WX 602 ; N uni1FF3 ; G 1696
+U 8180 ; WX 602 ; N uni1FF4 ; G 1697
+U 8182 ; WX 602 ; N uni1FF6 ; G 1698
+U 8183 ; WX 602 ; N uni1FF7 ; G 1699
+U 8184 ; WX 602 ; N uni1FF8 ; G 1700
+U 8185 ; WX 602 ; N uni1FF9 ; G 1701
+U 8186 ; WX 602 ; N uni1FFA ; G 1702
+U 8187 ; WX 602 ; N uni1FFB ; G 1703
+U 8188 ; WX 602 ; N uni1FFC ; G 1704
+U 8189 ; WX 602 ; N uni1FFD ; G 1705
+U 8190 ; WX 602 ; N uni1FFE ; G 1706
+U 8192 ; WX 602 ; N uni2000 ; G 1707
+U 8193 ; WX 602 ; N uni2001 ; G 1708
+U 8194 ; WX 602 ; N uni2002 ; G 1709
+U 8195 ; WX 602 ; N uni2003 ; G 1710
+U 8196 ; WX 602 ; N uni2004 ; G 1711
+U 8197 ; WX 602 ; N uni2005 ; G 1712
+U 8198 ; WX 602 ; N uni2006 ; G 1713
+U 8199 ; WX 602 ; N uni2007 ; G 1714
+U 8200 ; WX 602 ; N uni2008 ; G 1715
+U 8201 ; WX 602 ; N uni2009 ; G 1716
+U 8202 ; WX 602 ; N uni200A ; G 1717
+U 8208 ; WX 602 ; N uni2010 ; G 1718
+U 8209 ; WX 602 ; N uni2011 ; G 1719
+U 8210 ; WX 602 ; N figuredash ; G 1720
+U 8211 ; WX 602 ; N endash ; G 1721
+U 8212 ; WX 602 ; N emdash ; G 1722
+U 8213 ; WX 602 ; N uni2015 ; G 1723
+U 8214 ; WX 602 ; N uni2016 ; G 1724
+U 8215 ; WX 602 ; N underscoredbl ; G 1725
+U 8216 ; WX 602 ; N quoteleft ; G 1726
+U 8217 ; WX 602 ; N quoteright ; G 1727
+U 8218 ; WX 602 ; N quotesinglbase ; G 1728
+U 8219 ; WX 602 ; N quotereversed ; G 1729
+U 8220 ; WX 602 ; N quotedblleft ; G 1730
+U 8221 ; WX 602 ; N quotedblright ; G 1731
+U 8222 ; WX 602 ; N quotedblbase ; G 1732
+U 8223 ; WX 602 ; N uni201F ; G 1733
+U 8224 ; WX 602 ; N dagger ; G 1734
+U 8225 ; WX 602 ; N daggerdbl ; G 1735
+U 8226 ; WX 602 ; N bullet ; G 1736
+U 8227 ; WX 602 ; N uni2023 ; G 1737
+U 8230 ; WX 602 ; N ellipsis ; G 1738
+U 8239 ; WX 602 ; N uni202F ; G 1739
+U 8240 ; WX 602 ; N perthousand ; G 1740
+U 8241 ; WX 602 ; N uni2031 ; G 1741
+U 8242 ; WX 602 ; N minute ; G 1742
+U 8243 ; WX 602 ; N second ; G 1743
+U 8244 ; WX 602 ; N uni2034 ; G 1744
+U 8245 ; WX 602 ; N uni2035 ; G 1745
+U 8246 ; WX 602 ; N uni2036 ; G 1746
+U 8247 ; WX 602 ; N uni2037 ; G 1747
+U 8249 ; WX 602 ; N guilsinglleft ; G 1748
+U 8250 ; WX 602 ; N guilsinglright ; G 1749
+U 8252 ; WX 602 ; N exclamdbl ; G 1750
+U 8253 ; WX 602 ; N uni203D ; G 1751
+U 8254 ; WX 602 ; N uni203E ; G 1752
+U 8255 ; WX 602 ; N uni203F ; G 1753
+U 8261 ; WX 602 ; N uni2045 ; G 1754
+U 8262 ; WX 602 ; N uni2046 ; G 1755
+U 8263 ; WX 602 ; N uni2047 ; G 1756
+U 8264 ; WX 602 ; N uni2048 ; G 1757
+U 8265 ; WX 602 ; N uni2049 ; G 1758
+U 8267 ; WX 602 ; N uni204B ; G 1759
+U 8287 ; WX 602 ; N uni205F ; G 1760
+U 8304 ; WX 602 ; N uni2070 ; G 1761
+U 8305 ; WX 602 ; N uni2071 ; G 1762
+U 8308 ; WX 602 ; N uni2074 ; G 1763
+U 8309 ; WX 602 ; N uni2075 ; G 1764
+U 8310 ; WX 602 ; N uni2076 ; G 1765
+U 8311 ; WX 602 ; N uni2077 ; G 1766
+U 8312 ; WX 602 ; N uni2078 ; G 1767
+U 8313 ; WX 602 ; N uni2079 ; G 1768
+U 8314 ; WX 602 ; N uni207A ; G 1769
+U 8315 ; WX 602 ; N uni207B ; G 1770
+U 8316 ; WX 602 ; N uni207C ; G 1771
+U 8317 ; WX 602 ; N uni207D ; G 1772
+U 8318 ; WX 602 ; N uni207E ; G 1773
+U 8319 ; WX 602 ; N uni207F ; G 1774
+U 8320 ; WX 602 ; N uni2080 ; G 1775
+U 8321 ; WX 602 ; N uni2081 ; G 1776
+U 8322 ; WX 602 ; N uni2082 ; G 1777
+U 8323 ; WX 602 ; N uni2083 ; G 1778
+U 8324 ; WX 602 ; N uni2084 ; G 1779
+U 8325 ; WX 602 ; N uni2085 ; G 1780
+U 8326 ; WX 602 ; N uni2086 ; G 1781
+U 8327 ; WX 602 ; N uni2087 ; G 1782
+U 8328 ; WX 602 ; N uni2088 ; G 1783
+U 8329 ; WX 602 ; N uni2089 ; G 1784
+U 8330 ; WX 602 ; N uni208A ; G 1785
+U 8331 ; WX 602 ; N uni208B ; G 1786
+U 8332 ; WX 602 ; N uni208C ; G 1787
+U 8333 ; WX 602 ; N uni208D ; G 1788
+U 8334 ; WX 602 ; N uni208E ; G 1789
+U 8336 ; WX 602 ; N uni2090 ; G 1790
+U 8337 ; WX 602 ; N uni2091 ; G 1791
+U 8338 ; WX 602 ; N uni2092 ; G 1792
+U 8339 ; WX 602 ; N uni2093 ; G 1793
+U 8340 ; WX 602 ; N uni2094 ; G 1794
+U 8341 ; WX 602 ; N uni2095 ; G 1795
+U 8342 ; WX 602 ; N uni2096 ; G 1796
+U 8343 ; WX 602 ; N uni2097 ; G 1797
+U 8344 ; WX 602 ; N uni2098 ; G 1798
+U 8345 ; WX 602 ; N uni2099 ; G 1799
+U 8346 ; WX 602 ; N uni209A ; G 1800
+U 8347 ; WX 602 ; N uni209B ; G 1801
+U 8348 ; WX 602 ; N uni209C ; G 1802
+U 8352 ; WX 602 ; N uni20A0 ; G 1803
+U 8353 ; WX 602 ; N colonmonetary ; G 1804
+U 8354 ; WX 602 ; N uni20A2 ; G 1805
+U 8355 ; WX 602 ; N franc ; G 1806
+U 8356 ; WX 602 ; N lira ; G 1807
+U 8357 ; WX 602 ; N uni20A5 ; G 1808
+U 8358 ; WX 602 ; N uni20A6 ; G 1809
+U 8359 ; WX 602 ; N peseta ; G 1810
+U 8360 ; WX 602 ; N uni20A8 ; G 1811
+U 8361 ; WX 602 ; N uni20A9 ; G 1812
+U 8362 ; WX 602 ; N uni20AA ; G 1813
+U 8363 ; WX 602 ; N dong ; G 1814
+U 8364 ; WX 602 ; N Euro ; G 1815
+U 8365 ; WX 602 ; N uni20AD ; G 1816
+U 8366 ; WX 602 ; N uni20AE ; G 1817
+U 8367 ; WX 602 ; N uni20AF ; G 1818
+U 8368 ; WX 602 ; N uni20B0 ; G 1819
+U 8369 ; WX 602 ; N uni20B1 ; G 1820
+U 8370 ; WX 602 ; N uni20B2 ; G 1821
+U 8371 ; WX 602 ; N uni20B3 ; G 1822
+U 8372 ; WX 602 ; N uni20B4 ; G 1823
+U 8373 ; WX 602 ; N uni20B5 ; G 1824
+U 8376 ; WX 602 ; N uni20B8 ; G 1825
+U 8377 ; WX 602 ; N uni20B9 ; G 1826
+U 8378 ; WX 602 ; N uni20BA ; G 1827
+U 8381 ; WX 602 ; N uni20BD ; G 1828
+U 8450 ; WX 602 ; N uni2102 ; G 1829
+U 8453 ; WX 602 ; N uni2105 ; G 1830
+U 8461 ; WX 602 ; N uni210D ; G 1831
+U 8462 ; WX 602 ; N uni210E ; G 1832
+U 8463 ; WX 602 ; N uni210F ; G 1833
+U 8469 ; WX 602 ; N uni2115 ; G 1834
+U 8470 ; WX 602 ; N uni2116 ; G 1835
+U 8471 ; WX 602 ; N uni2117 ; G 1836
+U 8473 ; WX 602 ; N uni2119 ; G 1837
+U 8474 ; WX 602 ; N uni211A ; G 1838
+U 8477 ; WX 602 ; N uni211D ; G 1839
+U 8482 ; WX 602 ; N trademark ; G 1840
+U 8484 ; WX 602 ; N uni2124 ; G 1841
+U 8486 ; WX 602 ; N uni2126 ; G 1842
+U 8490 ; WX 602 ; N uni212A ; G 1843
+U 8491 ; WX 602 ; N uni212B ; G 1844
+U 8494 ; WX 602 ; N estimated ; G 1845
+U 8520 ; WX 602 ; N uni2148 ; G 1846
+U 8528 ; WX 602 ; N uni2150 ; G 1847
+U 8529 ; WX 602 ; N uni2151 ; G 1848
+U 8531 ; WX 602 ; N onethird ; G 1849
+U 8532 ; WX 602 ; N twothirds ; G 1850
+U 8533 ; WX 602 ; N uni2155 ; G 1851
+U 8534 ; WX 602 ; N uni2156 ; G 1852
+U 8535 ; WX 602 ; N uni2157 ; G 1853
+U 8536 ; WX 602 ; N uni2158 ; G 1854
+U 8537 ; WX 602 ; N uni2159 ; G 1855
+U 8538 ; WX 602 ; N uni215A ; G 1856
+U 8539 ; WX 602 ; N oneeighth ; G 1857
+U 8540 ; WX 602 ; N threeeighths ; G 1858
+U 8541 ; WX 602 ; N fiveeighths ; G 1859
+U 8542 ; WX 602 ; N seveneighths ; G 1860
+U 8543 ; WX 602 ; N uni215F ; G 1861
+U 8585 ; WX 602 ; N uni2189 ; G 1862
+U 8592 ; WX 602 ; N arrowleft ; G 1863
+U 8593 ; WX 602 ; N arrowup ; G 1864
+U 8594 ; WX 602 ; N arrowright ; G 1865
+U 8595 ; WX 602 ; N arrowdown ; G 1866
+U 8596 ; WX 602 ; N arrowboth ; G 1867
+U 8597 ; WX 602 ; N arrowupdn ; G 1868
+U 8598 ; WX 602 ; N uni2196 ; G 1869
+U 8599 ; WX 602 ; N uni2197 ; G 1870
+U 8600 ; WX 602 ; N uni2198 ; G 1871
+U 8601 ; WX 602 ; N uni2199 ; G 1872
+U 8602 ; WX 602 ; N uni219A ; G 1873
+U 8603 ; WX 602 ; N uni219B ; G 1874
+U 8604 ; WX 602 ; N uni219C ; G 1875
+U 8605 ; WX 602 ; N uni219D ; G 1876
+U 8606 ; WX 602 ; N uni219E ; G 1877
+U 8607 ; WX 602 ; N uni219F ; G 1878
+U 8608 ; WX 602 ; N uni21A0 ; G 1879
+U 8609 ; WX 602 ; N uni21A1 ; G 1880
+U 8610 ; WX 602 ; N uni21A2 ; G 1881
+U 8611 ; WX 602 ; N uni21A3 ; G 1882
+U 8612 ; WX 602 ; N uni21A4 ; G 1883
+U 8613 ; WX 602 ; N uni21A5 ; G 1884
+U 8614 ; WX 602 ; N uni21A6 ; G 1885
+U 8615 ; WX 602 ; N uni21A7 ; G 1886
+U 8616 ; WX 602 ; N arrowupdnbse ; G 1887
+U 8617 ; WX 602 ; N uni21A9 ; G 1888
+U 8618 ; WX 602 ; N uni21AA ; G 1889
+U 8619 ; WX 602 ; N uni21AB ; G 1890
+U 8620 ; WX 602 ; N uni21AC ; G 1891
+U 8621 ; WX 602 ; N uni21AD ; G 1892
+U 8622 ; WX 602 ; N uni21AE ; G 1893
+U 8623 ; WX 602 ; N uni21AF ; G 1894
+U 8624 ; WX 602 ; N uni21B0 ; G 1895
+U 8625 ; WX 602 ; N uni21B1 ; G 1896
+U 8626 ; WX 602 ; N uni21B2 ; G 1897
+U 8627 ; WX 602 ; N uni21B3 ; G 1898
+U 8628 ; WX 602 ; N uni21B4 ; G 1899
+U 8629 ; WX 602 ; N carriagereturn ; G 1900
+U 8630 ; WX 602 ; N uni21B6 ; G 1901
+U 8631 ; WX 602 ; N uni21B7 ; G 1902
+U 8632 ; WX 602 ; N uni21B8 ; G 1903
+U 8633 ; WX 602 ; N uni21B9 ; G 1904
+U 8634 ; WX 602 ; N uni21BA ; G 1905
+U 8635 ; WX 602 ; N uni21BB ; G 1906
+U 8636 ; WX 602 ; N uni21BC ; G 1907
+U 8637 ; WX 602 ; N uni21BD ; G 1908
+U 8638 ; WX 602 ; N uni21BE ; G 1909
+U 8639 ; WX 602 ; N uni21BF ; G 1910
+U 8640 ; WX 602 ; N uni21C0 ; G 1911
+U 8641 ; WX 602 ; N uni21C1 ; G 1912
+U 8642 ; WX 602 ; N uni21C2 ; G 1913
+U 8643 ; WX 602 ; N uni21C3 ; G 1914
+U 8644 ; WX 602 ; N uni21C4 ; G 1915
+U 8645 ; WX 602 ; N uni21C5 ; G 1916
+U 8646 ; WX 602 ; N uni21C6 ; G 1917
+U 8647 ; WX 602 ; N uni21C7 ; G 1918
+U 8648 ; WX 602 ; N uni21C8 ; G 1919
+U 8649 ; WX 602 ; N uni21C9 ; G 1920
+U 8650 ; WX 602 ; N uni21CA ; G 1921
+U 8651 ; WX 602 ; N uni21CB ; G 1922
+U 8652 ; WX 602 ; N uni21CC ; G 1923
+U 8653 ; WX 602 ; N uni21CD ; G 1924
+U 8654 ; WX 602 ; N uni21CE ; G 1925
+U 8655 ; WX 602 ; N uni21CF ; G 1926
+U 8656 ; WX 602 ; N arrowdblleft ; G 1927
+U 8657 ; WX 602 ; N arrowdblup ; G 1928
+U 8658 ; WX 602 ; N arrowdblright ; G 1929
+U 8659 ; WX 602 ; N arrowdbldown ; G 1930
+U 8660 ; WX 602 ; N arrowdblboth ; G 1931
+U 8661 ; WX 602 ; N uni21D5 ; G 1932
+U 8662 ; WX 602 ; N uni21D6 ; G 1933
+U 8663 ; WX 602 ; N uni21D7 ; G 1934
+U 8664 ; WX 602 ; N uni21D8 ; G 1935
+U 8665 ; WX 602 ; N uni21D9 ; G 1936
+U 8666 ; WX 602 ; N uni21DA ; G 1937
+U 8667 ; WX 602 ; N uni21DB ; G 1938
+U 8668 ; WX 602 ; N uni21DC ; G 1939
+U 8669 ; WX 602 ; N uni21DD ; G 1940
+U 8670 ; WX 602 ; N uni21DE ; G 1941
+U 8671 ; WX 602 ; N uni21DF ; G 1942
+U 8672 ; WX 602 ; N uni21E0 ; G 1943
+U 8673 ; WX 602 ; N uni21E1 ; G 1944
+U 8674 ; WX 602 ; N uni21E2 ; G 1945
+U 8675 ; WX 602 ; N uni21E3 ; G 1946
+U 8676 ; WX 602 ; N uni21E4 ; G 1947
+U 8677 ; WX 602 ; N uni21E5 ; G 1948
+U 8678 ; WX 602 ; N uni21E6 ; G 1949
+U 8679 ; WX 602 ; N uni21E7 ; G 1950
+U 8680 ; WX 602 ; N uni21E8 ; G 1951
+U 8681 ; WX 602 ; N uni21E9 ; G 1952
+U 8682 ; WX 602 ; N uni21EA ; G 1953
+U 8683 ; WX 602 ; N uni21EB ; G 1954
+U 8684 ; WX 602 ; N uni21EC ; G 1955
+U 8685 ; WX 602 ; N uni21ED ; G 1956
+U 8686 ; WX 602 ; N uni21EE ; G 1957
+U 8687 ; WX 602 ; N uni21EF ; G 1958
+U 8688 ; WX 602 ; N uni21F0 ; G 1959
+U 8689 ; WX 602 ; N uni21F1 ; G 1960
+U 8690 ; WX 602 ; N uni21F2 ; G 1961
+U 8691 ; WX 602 ; N uni21F3 ; G 1962
+U 8692 ; WX 602 ; N uni21F4 ; G 1963
+U 8693 ; WX 602 ; N uni21F5 ; G 1964
+U 8694 ; WX 602 ; N uni21F6 ; G 1965
+U 8695 ; WX 602 ; N uni21F7 ; G 1966
+U 8696 ; WX 602 ; N uni21F8 ; G 1967
+U 8697 ; WX 602 ; N uni21F9 ; G 1968
+U 8698 ; WX 602 ; N uni21FA ; G 1969
+U 8699 ; WX 602 ; N uni21FB ; G 1970
+U 8700 ; WX 602 ; N uni21FC ; G 1971
+U 8701 ; WX 602 ; N uni21FD ; G 1972
+U 8702 ; WX 602 ; N uni21FE ; G 1973
+U 8703 ; WX 602 ; N uni21FF ; G 1974
+U 8704 ; WX 602 ; N universal ; G 1975
+U 8705 ; WX 602 ; N uni2201 ; G 1976
+U 8706 ; WX 602 ; N partialdiff ; G 1977
+U 8707 ; WX 602 ; N existential ; G 1978
+U 8708 ; WX 602 ; N uni2204 ; G 1979
+U 8709 ; WX 602 ; N emptyset ; G 1980
+U 8710 ; WX 602 ; N increment ; G 1981
+U 8711 ; WX 602 ; N gradient ; G 1982
+U 8712 ; WX 602 ; N element ; G 1983
+U 8713 ; WX 602 ; N notelement ; G 1984
+U 8714 ; WX 602 ; N uni220A ; G 1985
+U 8715 ; WX 602 ; N suchthat ; G 1986
+U 8716 ; WX 602 ; N uni220C ; G 1987
+U 8717 ; WX 602 ; N uni220D ; G 1988
+U 8718 ; WX 602 ; N uni220E ; G 1989
+U 8719 ; WX 602 ; N product ; G 1990
+U 8720 ; WX 602 ; N uni2210 ; G 1991
+U 8721 ; WX 602 ; N summation ; G 1992
+U 8722 ; WX 602 ; N minus ; G 1993
+U 8723 ; WX 602 ; N uni2213 ; G 1994
+U 8725 ; WX 602 ; N uni2215 ; G 1995
+U 8727 ; WX 602 ; N asteriskmath ; G 1996
+U 8728 ; WX 602 ; N uni2218 ; G 1997
+U 8729 ; WX 602 ; N uni2219 ; G 1998
+U 8730 ; WX 602 ; N radical ; G 1999
+U 8731 ; WX 602 ; N uni221B ; G 2000
+U 8732 ; WX 602 ; N uni221C ; G 2001
+U 8733 ; WX 602 ; N proportional ; G 2002
+U 8734 ; WX 602 ; N infinity ; G 2003
+U 8735 ; WX 602 ; N orthogonal ; G 2004
+U 8736 ; WX 602 ; N angle ; G 2005
+U 8739 ; WX 602 ; N uni2223 ; G 2006
+U 8743 ; WX 602 ; N logicaland ; G 2007
+U 8744 ; WX 602 ; N logicalor ; G 2008
+U 8745 ; WX 602 ; N intersection ; G 2009
+U 8746 ; WX 602 ; N union ; G 2010
+U 8747 ; WX 602 ; N integral ; G 2011
+U 8748 ; WX 602 ; N uni222C ; G 2012
+U 8749 ; WX 602 ; N uni222D ; G 2013
+U 8756 ; WX 602 ; N therefore ; G 2014
+U 8757 ; WX 602 ; N uni2235 ; G 2015
+U 8758 ; WX 602 ; N uni2236 ; G 2016
+U 8759 ; WX 602 ; N uni2237 ; G 2017
+U 8760 ; WX 602 ; N uni2238 ; G 2018
+U 8761 ; WX 602 ; N uni2239 ; G 2019
+U 8762 ; WX 602 ; N uni223A ; G 2020
+U 8763 ; WX 602 ; N uni223B ; G 2021
+U 8764 ; WX 602 ; N similar ; G 2022
+U 8765 ; WX 602 ; N uni223D ; G 2023
+U 8769 ; WX 602 ; N uni2241 ; G 2024
+U 8770 ; WX 602 ; N uni2242 ; G 2025
+U 8771 ; WX 602 ; N uni2243 ; G 2026
+U 8772 ; WX 602 ; N uni2244 ; G 2027
+U 8773 ; WX 602 ; N congruent ; G 2028
+U 8774 ; WX 602 ; N uni2246 ; G 2029
+U 8775 ; WX 602 ; N uni2247 ; G 2030
+U 8776 ; WX 602 ; N approxequal ; G 2031
+U 8777 ; WX 602 ; N uni2249 ; G 2032
+U 8778 ; WX 602 ; N uni224A ; G 2033
+U 8779 ; WX 602 ; N uni224B ; G 2034
+U 8780 ; WX 602 ; N uni224C ; G 2035
+U 8781 ; WX 602 ; N uni224D ; G 2036
+U 8782 ; WX 602 ; N uni224E ; G 2037
+U 8783 ; WX 602 ; N uni224F ; G 2038
+U 8784 ; WX 602 ; N uni2250 ; G 2039
+U 8785 ; WX 602 ; N uni2251 ; G 2040
+U 8786 ; WX 602 ; N uni2252 ; G 2041
+U 8787 ; WX 602 ; N uni2253 ; G 2042
+U 8788 ; WX 602 ; N uni2254 ; G 2043
+U 8789 ; WX 602 ; N uni2255 ; G 2044
+U 8790 ; WX 602 ; N uni2256 ; G 2045
+U 8791 ; WX 602 ; N uni2257 ; G 2046
+U 8792 ; WX 602 ; N uni2258 ; G 2047
+U 8793 ; WX 602 ; N uni2259 ; G 2048
+U 8794 ; WX 602 ; N uni225A ; G 2049
+U 8795 ; WX 602 ; N uni225B ; G 2050
+U 8796 ; WX 602 ; N uni225C ; G 2051
+U 8797 ; WX 602 ; N uni225D ; G 2052
+U 8798 ; WX 602 ; N uni225E ; G 2053
+U 8799 ; WX 602 ; N uni225F ; G 2054
+U 8800 ; WX 602 ; N notequal ; G 2055
+U 8801 ; WX 602 ; N equivalence ; G 2056
+U 8802 ; WX 602 ; N uni2262 ; G 2057
+U 8803 ; WX 602 ; N uni2263 ; G 2058
+U 8804 ; WX 602 ; N lessequal ; G 2059
+U 8805 ; WX 602 ; N greaterequal ; G 2060
+U 8806 ; WX 602 ; N uni2266 ; G 2061
+U 8807 ; WX 602 ; N uni2267 ; G 2062
+U 8808 ; WX 602 ; N uni2268 ; G 2063
+U 8809 ; WX 602 ; N uni2269 ; G 2064
+U 8813 ; WX 602 ; N uni226D ; G 2065
+U 8814 ; WX 602 ; N uni226E ; G 2066
+U 8815 ; WX 602 ; N uni226F ; G 2067
+U 8816 ; WX 602 ; N uni2270 ; G 2068
+U 8817 ; WX 602 ; N uni2271 ; G 2069
+U 8818 ; WX 602 ; N uni2272 ; G 2070
+U 8819 ; WX 602 ; N uni2273 ; G 2071
+U 8820 ; WX 602 ; N uni2274 ; G 2072
+U 8821 ; WX 602 ; N uni2275 ; G 2073
+U 8822 ; WX 602 ; N uni2276 ; G 2074
+U 8823 ; WX 602 ; N uni2277 ; G 2075
+U 8824 ; WX 602 ; N uni2278 ; G 2076
+U 8825 ; WX 602 ; N uni2279 ; G 2077
+U 8826 ; WX 602 ; N uni227A ; G 2078
+U 8827 ; WX 602 ; N uni227B ; G 2079
+U 8828 ; WX 602 ; N uni227C ; G 2080
+U 8829 ; WX 602 ; N uni227D ; G 2081
+U 8830 ; WX 602 ; N uni227E ; G 2082
+U 8831 ; WX 602 ; N uni227F ; G 2083
+U 8832 ; WX 602 ; N uni2280 ; G 2084
+U 8833 ; WX 602 ; N uni2281 ; G 2085
+U 8834 ; WX 602 ; N propersubset ; G 2086
+U 8835 ; WX 602 ; N propersuperset ; G 2087
+U 8836 ; WX 602 ; N notsubset ; G 2088
+U 8837 ; WX 602 ; N uni2285 ; G 2089
+U 8838 ; WX 602 ; N reflexsubset ; G 2090
+U 8839 ; WX 602 ; N reflexsuperset ; G 2091
+U 8840 ; WX 602 ; N uni2288 ; G 2092
+U 8841 ; WX 602 ; N uni2289 ; G 2093
+U 8842 ; WX 602 ; N uni228A ; G 2094
+U 8843 ; WX 602 ; N uni228B ; G 2095
+U 8845 ; WX 602 ; N uni228D ; G 2096
+U 8846 ; WX 602 ; N uni228E ; G 2097
+U 8847 ; WX 602 ; N uni228F ; G 2098
+U 8848 ; WX 602 ; N uni2290 ; G 2099
+U 8849 ; WX 602 ; N uni2291 ; G 2100
+U 8850 ; WX 602 ; N uni2292 ; G 2101
+U 8851 ; WX 602 ; N uni2293 ; G 2102
+U 8852 ; WX 602 ; N uni2294 ; G 2103
+U 8853 ; WX 602 ; N circleplus ; G 2104
+U 8854 ; WX 602 ; N uni2296 ; G 2105
+U 8855 ; WX 602 ; N circlemultiply ; G 2106
+U 8856 ; WX 602 ; N uni2298 ; G 2107
+U 8857 ; WX 602 ; N uni2299 ; G 2108
+U 8858 ; WX 602 ; N uni229A ; G 2109
+U 8859 ; WX 602 ; N uni229B ; G 2110
+U 8860 ; WX 602 ; N uni229C ; G 2111
+U 8861 ; WX 602 ; N uni229D ; G 2112
+U 8862 ; WX 602 ; N uni229E ; G 2113
+U 8863 ; WX 602 ; N uni229F ; G 2114
+U 8864 ; WX 602 ; N uni22A0 ; G 2115
+U 8865 ; WX 602 ; N uni22A1 ; G 2116
+U 8866 ; WX 602 ; N uni22A2 ; G 2117
+U 8867 ; WX 602 ; N uni22A3 ; G 2118
+U 8868 ; WX 602 ; N uni22A4 ; G 2119
+U 8869 ; WX 602 ; N perpendicular ; G 2120
+U 8882 ; WX 602 ; N uni22B2 ; G 2121
+U 8883 ; WX 602 ; N uni22B3 ; G 2122
+U 8884 ; WX 602 ; N uni22B4 ; G 2123
+U 8885 ; WX 602 ; N uni22B5 ; G 2124
+U 8888 ; WX 602 ; N uni22B8 ; G 2125
+U 8898 ; WX 602 ; N uni22C2 ; G 2126
+U 8899 ; WX 602 ; N uni22C3 ; G 2127
+U 8900 ; WX 602 ; N uni22C4 ; G 2128
+U 8901 ; WX 602 ; N dotmath ; G 2129
+U 8902 ; WX 602 ; N uni22C6 ; G 2130
+U 8909 ; WX 602 ; N uni22CD ; G 2131
+U 8910 ; WX 602 ; N uni22CE ; G 2132
+U 8911 ; WX 602 ; N uni22CF ; G 2133
+U 8912 ; WX 602 ; N uni22D0 ; G 2134
+U 8913 ; WX 602 ; N uni22D1 ; G 2135
+U 8922 ; WX 602 ; N uni22DA ; G 2136
+U 8923 ; WX 602 ; N uni22DB ; G 2137
+U 8924 ; WX 602 ; N uni22DC ; G 2138
+U 8925 ; WX 602 ; N uni22DD ; G 2139
+U 8926 ; WX 602 ; N uni22DE ; G 2140
+U 8927 ; WX 602 ; N uni22DF ; G 2141
+U 8928 ; WX 602 ; N uni22E0 ; G 2142
+U 8929 ; WX 602 ; N uni22E1 ; G 2143
+U 8930 ; WX 602 ; N uni22E2 ; G 2144
+U 8931 ; WX 602 ; N uni22E3 ; G 2145
+U 8932 ; WX 602 ; N uni22E4 ; G 2146
+U 8933 ; WX 602 ; N uni22E5 ; G 2147
+U 8934 ; WX 602 ; N uni22E6 ; G 2148
+U 8935 ; WX 602 ; N uni22E7 ; G 2149
+U 8936 ; WX 602 ; N uni22E8 ; G 2150
+U 8937 ; WX 602 ; N uni22E9 ; G 2151
+U 8943 ; WX 602 ; N uni22EF ; G 2152
+U 8960 ; WX 602 ; N uni2300 ; G 2153
+U 8961 ; WX 602 ; N uni2301 ; G 2154
+U 8962 ; WX 602 ; N house ; G 2155
+U 8963 ; WX 602 ; N uni2303 ; G 2156
+U 8964 ; WX 602 ; N uni2304 ; G 2157
+U 8965 ; WX 602 ; N uni2305 ; G 2158
+U 8966 ; WX 602 ; N uni2306 ; G 2159
+U 8968 ; WX 602 ; N uni2308 ; G 2160
+U 8969 ; WX 602 ; N uni2309 ; G 2161
+U 8970 ; WX 602 ; N uni230A ; G 2162
+U 8971 ; WX 602 ; N uni230B ; G 2163
+U 8972 ; WX 602 ; N uni230C ; G 2164
+U 8973 ; WX 602 ; N uni230D ; G 2165
+U 8974 ; WX 602 ; N uni230E ; G 2166
+U 8975 ; WX 602 ; N uni230F ; G 2167
+U 8976 ; WX 602 ; N revlogicalnot ; G 2168
+U 8977 ; WX 602 ; N uni2311 ; G 2169
+U 8978 ; WX 602 ; N uni2312 ; G 2170
+U 8979 ; WX 602 ; N uni2313 ; G 2171
+U 8980 ; WX 602 ; N uni2314 ; G 2172
+U 8981 ; WX 602 ; N uni2315 ; G 2173
+U 8984 ; WX 602 ; N uni2318 ; G 2174
+U 8985 ; WX 602 ; N uni2319 ; G 2175
+U 8988 ; WX 602 ; N uni231C ; G 2176
+U 8989 ; WX 602 ; N uni231D ; G 2177
+U 8990 ; WX 602 ; N uni231E ; G 2178
+U 8991 ; WX 602 ; N uni231F ; G 2179
+U 8992 ; WX 602 ; N integraltp ; G 2180
+U 8993 ; WX 602 ; N integralbt ; G 2181
+U 8997 ; WX 602 ; N uni2325 ; G 2182
+U 8998 ; WX 602 ; N uni2326 ; G 2183
+U 8999 ; WX 602 ; N uni2327 ; G 2184
+U 9000 ; WX 602 ; N uni2328 ; G 2185
+U 9003 ; WX 602 ; N uni232B ; G 2186
+U 9013 ; WX 602 ; N uni2335 ; G 2187
+U 9014 ; WX 602 ; N uni2336 ; G 2188
+U 9015 ; WX 602 ; N uni2337 ; G 2189
+U 9016 ; WX 602 ; N uni2338 ; G 2190
+U 9017 ; WX 602 ; N uni2339 ; G 2191
+U 9018 ; WX 602 ; N uni233A ; G 2192
+U 9019 ; WX 602 ; N uni233B ; G 2193
+U 9020 ; WX 602 ; N uni233C ; G 2194
+U 9021 ; WX 602 ; N uni233D ; G 2195
+U 9022 ; WX 602 ; N uni233E ; G 2196
+U 9023 ; WX 602 ; N uni233F ; G 2197
+U 9024 ; WX 602 ; N uni2340 ; G 2198
+U 9025 ; WX 602 ; N uni2341 ; G 2199
+U 9026 ; WX 602 ; N uni2342 ; G 2200
+U 9027 ; WX 602 ; N uni2343 ; G 2201
+U 9028 ; WX 602 ; N uni2344 ; G 2202
+U 9029 ; WX 602 ; N uni2345 ; G 2203
+U 9030 ; WX 602 ; N uni2346 ; G 2204
+U 9031 ; WX 602 ; N uni2347 ; G 2205
+U 9032 ; WX 602 ; N uni2348 ; G 2206
+U 9033 ; WX 602 ; N uni2349 ; G 2207
+U 9034 ; WX 602 ; N uni234A ; G 2208
+U 9035 ; WX 602 ; N uni234B ; G 2209
+U 9036 ; WX 602 ; N uni234C ; G 2210
+U 9037 ; WX 602 ; N uni234D ; G 2211
+U 9038 ; WX 602 ; N uni234E ; G 2212
+U 9039 ; WX 602 ; N uni234F ; G 2213
+U 9040 ; WX 602 ; N uni2350 ; G 2214
+U 9041 ; WX 602 ; N uni2351 ; G 2215
+U 9042 ; WX 602 ; N uni2352 ; G 2216
+U 9043 ; WX 602 ; N uni2353 ; G 2217
+U 9044 ; WX 602 ; N uni2354 ; G 2218
+U 9045 ; WX 602 ; N uni2355 ; G 2219
+U 9046 ; WX 602 ; N uni2356 ; G 2220
+U 9047 ; WX 602 ; N uni2357 ; G 2221
+U 9048 ; WX 602 ; N uni2358 ; G 2222
+U 9049 ; WX 602 ; N uni2359 ; G 2223
+U 9050 ; WX 602 ; N uni235A ; G 2224
+U 9051 ; WX 602 ; N uni235B ; G 2225
+U 9052 ; WX 602 ; N uni235C ; G 2226
+U 9053 ; WX 602 ; N uni235D ; G 2227
+U 9054 ; WX 602 ; N uni235E ; G 2228
+U 9055 ; WX 602 ; N uni235F ; G 2229
+U 9056 ; WX 602 ; N uni2360 ; G 2230
+U 9057 ; WX 602 ; N uni2361 ; G 2231
+U 9058 ; WX 602 ; N uni2362 ; G 2232
+U 9059 ; WX 602 ; N uni2363 ; G 2233
+U 9060 ; WX 602 ; N uni2364 ; G 2234
+U 9061 ; WX 602 ; N uni2365 ; G 2235
+U 9062 ; WX 602 ; N uni2366 ; G 2236
+U 9063 ; WX 602 ; N uni2367 ; G 2237
+U 9064 ; WX 602 ; N uni2368 ; G 2238
+U 9065 ; WX 602 ; N uni2369 ; G 2239
+U 9066 ; WX 602 ; N uni236A ; G 2240
+U 9067 ; WX 602 ; N uni236B ; G 2241
+U 9068 ; WX 602 ; N uni236C ; G 2242
+U 9069 ; WX 602 ; N uni236D ; G 2243
+U 9070 ; WX 602 ; N uni236E ; G 2244
+U 9071 ; WX 602 ; N uni236F ; G 2245
+U 9072 ; WX 602 ; N uni2370 ; G 2246
+U 9073 ; WX 602 ; N uni2371 ; G 2247
+U 9074 ; WX 602 ; N uni2372 ; G 2248
+U 9075 ; WX 602 ; N uni2373 ; G 2249
+U 9076 ; WX 602 ; N uni2374 ; G 2250
+U 9077 ; WX 602 ; N uni2375 ; G 2251
+U 9078 ; WX 602 ; N uni2376 ; G 2252
+U 9079 ; WX 602 ; N uni2377 ; G 2253
+U 9080 ; WX 602 ; N uni2378 ; G 2254
+U 9081 ; WX 602 ; N uni2379 ; G 2255
+U 9082 ; WX 602 ; N uni237A ; G 2256
+U 9085 ; WX 602 ; N uni237D ; G 2257
+U 9088 ; WX 602 ; N uni2380 ; G 2258
+U 9089 ; WX 602 ; N uni2381 ; G 2259
+U 9090 ; WX 602 ; N uni2382 ; G 2260
+U 9091 ; WX 602 ; N uni2383 ; G 2261
+U 9096 ; WX 602 ; N uni2388 ; G 2262
+U 9097 ; WX 602 ; N uni2389 ; G 2263
+U 9098 ; WX 602 ; N uni238A ; G 2264
+U 9099 ; WX 602 ; N uni238B ; G 2265
+U 9109 ; WX 602 ; N uni2395 ; G 2266
+U 9115 ; WX 602 ; N uni239B ; G 2267
+U 9116 ; WX 602 ; N uni239C ; G 2268
+U 9117 ; WX 602 ; N uni239D ; G 2269
+U 9118 ; WX 602 ; N uni239E ; G 2270
+U 9119 ; WX 602 ; N uni239F ; G 2271
+U 9120 ; WX 602 ; N uni23A0 ; G 2272
+U 9121 ; WX 602 ; N uni23A1 ; G 2273
+U 9122 ; WX 602 ; N uni23A2 ; G 2274
+U 9123 ; WX 602 ; N uni23A3 ; G 2275
+U 9124 ; WX 602 ; N uni23A4 ; G 2276
+U 9125 ; WX 602 ; N uni23A5 ; G 2277
+U 9126 ; WX 602 ; N uni23A6 ; G 2278
+U 9127 ; WX 602 ; N uni23A7 ; G 2279
+U 9128 ; WX 602 ; N uni23A8 ; G 2280
+U 9129 ; WX 602 ; N uni23A9 ; G 2281
+U 9130 ; WX 602 ; N uni23AA ; G 2282
+U 9131 ; WX 602 ; N uni23AB ; G 2283
+U 9132 ; WX 602 ; N uni23AC ; G 2284
+U 9133 ; WX 602 ; N uni23AD ; G 2285
+U 9134 ; WX 602 ; N uni23AE ; G 2286
+U 9166 ; WX 602 ; N uni23CE ; G 2287
+U 9167 ; WX 602 ; N uni23CF ; G 2288
+U 9251 ; WX 602 ; N uni2423 ; G 2289
+U 9472 ; WX 602 ; N SF100000 ; G 2290
+U 9473 ; WX 602 ; N uni2501 ; G 2291
+U 9474 ; WX 602 ; N SF110000 ; G 2292
+U 9475 ; WX 602 ; N uni2503 ; G 2293
+U 9476 ; WX 602 ; N uni2504 ; G 2294
+U 9477 ; WX 602 ; N uni2505 ; G 2295
+U 9478 ; WX 602 ; N uni2506 ; G 2296
+U 9479 ; WX 602 ; N uni2507 ; G 2297
+U 9480 ; WX 602 ; N uni2508 ; G 2298
+U 9481 ; WX 602 ; N uni2509 ; G 2299
+U 9482 ; WX 602 ; N uni250A ; G 2300
+U 9483 ; WX 602 ; N uni250B ; G 2301
+U 9484 ; WX 602 ; N SF010000 ; G 2302
+U 9485 ; WX 602 ; N uni250D ; G 2303
+U 9486 ; WX 602 ; N uni250E ; G 2304
+U 9487 ; WX 602 ; N uni250F ; G 2305
+U 9488 ; WX 602 ; N SF030000 ; G 2306
+U 9489 ; WX 602 ; N uni2511 ; G 2307
+U 9490 ; WX 602 ; N uni2512 ; G 2308
+U 9491 ; WX 602 ; N uni2513 ; G 2309
+U 9492 ; WX 602 ; N SF020000 ; G 2310
+U 9493 ; WX 602 ; N uni2515 ; G 2311
+U 9494 ; WX 602 ; N uni2516 ; G 2312
+U 9495 ; WX 602 ; N uni2517 ; G 2313
+U 9496 ; WX 602 ; N SF040000 ; G 2314
+U 9497 ; WX 602 ; N uni2519 ; G 2315
+U 9498 ; WX 602 ; N uni251A ; G 2316
+U 9499 ; WX 602 ; N uni251B ; G 2317
+U 9500 ; WX 602 ; N SF080000 ; G 2318
+U 9501 ; WX 602 ; N uni251D ; G 2319
+U 9502 ; WX 602 ; N uni251E ; G 2320
+U 9503 ; WX 602 ; N uni251F ; G 2321
+U 9504 ; WX 602 ; N uni2520 ; G 2322
+U 9505 ; WX 602 ; N uni2521 ; G 2323
+U 9506 ; WX 602 ; N uni2522 ; G 2324
+U 9507 ; WX 602 ; N uni2523 ; G 2325
+U 9508 ; WX 602 ; N SF090000 ; G 2326
+U 9509 ; WX 602 ; N uni2525 ; G 2327
+U 9510 ; WX 602 ; N uni2526 ; G 2328
+U 9511 ; WX 602 ; N uni2527 ; G 2329
+U 9512 ; WX 602 ; N uni2528 ; G 2330
+U 9513 ; WX 602 ; N uni2529 ; G 2331
+U 9514 ; WX 602 ; N uni252A ; G 2332
+U 9515 ; WX 602 ; N uni252B ; G 2333
+U 9516 ; WX 602 ; N SF060000 ; G 2334
+U 9517 ; WX 602 ; N uni252D ; G 2335
+U 9518 ; WX 602 ; N uni252E ; G 2336
+U 9519 ; WX 602 ; N uni252F ; G 2337
+U 9520 ; WX 602 ; N uni2530 ; G 2338
+U 9521 ; WX 602 ; N uni2531 ; G 2339
+U 9522 ; WX 602 ; N uni2532 ; G 2340
+U 9523 ; WX 602 ; N uni2533 ; G 2341
+U 9524 ; WX 602 ; N SF070000 ; G 2342
+U 9525 ; WX 602 ; N uni2535 ; G 2343
+U 9526 ; WX 602 ; N uni2536 ; G 2344
+U 9527 ; WX 602 ; N uni2537 ; G 2345
+U 9528 ; WX 602 ; N uni2538 ; G 2346
+U 9529 ; WX 602 ; N uni2539 ; G 2347
+U 9530 ; WX 602 ; N uni253A ; G 2348
+U 9531 ; WX 602 ; N uni253B ; G 2349
+U 9532 ; WX 602 ; N SF050000 ; G 2350
+U 9533 ; WX 602 ; N uni253D ; G 2351
+U 9534 ; WX 602 ; N uni253E ; G 2352
+U 9535 ; WX 602 ; N uni253F ; G 2353
+U 9536 ; WX 602 ; N uni2540 ; G 2354
+U 9537 ; WX 602 ; N uni2541 ; G 2355
+U 9538 ; WX 602 ; N uni2542 ; G 2356
+U 9539 ; WX 602 ; N uni2543 ; G 2357
+U 9540 ; WX 602 ; N uni2544 ; G 2358
+U 9541 ; WX 602 ; N uni2545 ; G 2359
+U 9542 ; WX 602 ; N uni2546 ; G 2360
+U 9543 ; WX 602 ; N uni2547 ; G 2361
+U 9544 ; WX 602 ; N uni2548 ; G 2362
+U 9545 ; WX 602 ; N uni2549 ; G 2363
+U 9546 ; WX 602 ; N uni254A ; G 2364
+U 9547 ; WX 602 ; N uni254B ; G 2365
+U 9548 ; WX 602 ; N uni254C ; G 2366
+U 9549 ; WX 602 ; N uni254D ; G 2367
+U 9550 ; WX 602 ; N uni254E ; G 2368
+U 9551 ; WX 602 ; N uni254F ; G 2369
+U 9552 ; WX 602 ; N SF430000 ; G 2370
+U 9553 ; WX 602 ; N SF240000 ; G 2371
+U 9554 ; WX 602 ; N SF510000 ; G 2372
+U 9555 ; WX 602 ; N SF520000 ; G 2373
+U 9556 ; WX 602 ; N SF390000 ; G 2374
+U 9557 ; WX 602 ; N SF220000 ; G 2375
+U 9558 ; WX 602 ; N SF210000 ; G 2376
+U 9559 ; WX 602 ; N SF250000 ; G 2377
+U 9560 ; WX 602 ; N SF500000 ; G 2378
+U 9561 ; WX 602 ; N SF490000 ; G 2379
+U 9562 ; WX 602 ; N SF380000 ; G 2380
+U 9563 ; WX 602 ; N SF280000 ; G 2381
+U 9564 ; WX 602 ; N SF270000 ; G 2382
+U 9565 ; WX 602 ; N SF260000 ; G 2383
+U 9566 ; WX 602 ; N SF360000 ; G 2384
+U 9567 ; WX 602 ; N SF370000 ; G 2385
+U 9568 ; WX 602 ; N SF420000 ; G 2386
+U 9569 ; WX 602 ; N SF190000 ; G 2387
+U 9570 ; WX 602 ; N SF200000 ; G 2388
+U 9571 ; WX 602 ; N SF230000 ; G 2389
+U 9572 ; WX 602 ; N SF470000 ; G 2390
+U 9573 ; WX 602 ; N SF480000 ; G 2391
+U 9574 ; WX 602 ; N SF410000 ; G 2392
+U 9575 ; WX 602 ; N SF450000 ; G 2393
+U 9576 ; WX 602 ; N SF460000 ; G 2394
+U 9577 ; WX 602 ; N SF400000 ; G 2395
+U 9578 ; WX 602 ; N SF540000 ; G 2396
+U 9579 ; WX 602 ; N SF530000 ; G 2397
+U 9580 ; WX 602 ; N SF440000 ; G 2398
+U 9581 ; WX 602 ; N uni256D ; G 2399
+U 9582 ; WX 602 ; N uni256E ; G 2400
+U 9583 ; WX 602 ; N uni256F ; G 2401
+U 9584 ; WX 602 ; N uni2570 ; G 2402
+U 9585 ; WX 602 ; N uni2571 ; G 2403
+U 9586 ; WX 602 ; N uni2572 ; G 2404
+U 9587 ; WX 602 ; N uni2573 ; G 2405
+U 9588 ; WX 602 ; N uni2574 ; G 2406
+U 9589 ; WX 602 ; N uni2575 ; G 2407
+U 9590 ; WX 602 ; N uni2576 ; G 2408
+U 9591 ; WX 602 ; N uni2577 ; G 2409
+U 9592 ; WX 602 ; N uni2578 ; G 2410
+U 9593 ; WX 602 ; N uni2579 ; G 2411
+U 9594 ; WX 602 ; N uni257A ; G 2412
+U 9595 ; WX 602 ; N uni257B ; G 2413
+U 9596 ; WX 602 ; N uni257C ; G 2414
+U 9597 ; WX 602 ; N uni257D ; G 2415
+U 9598 ; WX 602 ; N uni257E ; G 2416
+U 9599 ; WX 602 ; N uni257F ; G 2417
+U 9600 ; WX 602 ; N upblock ; G 2418
+U 9601 ; WX 602 ; N uni2581 ; G 2419
+U 9602 ; WX 602 ; N uni2582 ; G 2420
+U 9603 ; WX 602 ; N uni2583 ; G 2421
+U 9604 ; WX 602 ; N dnblock ; G 2422
+U 9605 ; WX 602 ; N uni2585 ; G 2423
+U 9606 ; WX 602 ; N uni2586 ; G 2424
+U 9607 ; WX 602 ; N uni2587 ; G 2425
+U 9608 ; WX 602 ; N block ; G 2426
+U 9609 ; WX 602 ; N uni2589 ; G 2427
+U 9610 ; WX 602 ; N uni258A ; G 2428
+U 9611 ; WX 602 ; N uni258B ; G 2429
+U 9612 ; WX 602 ; N lfblock ; G 2430
+U 9613 ; WX 602 ; N uni258D ; G 2431
+U 9614 ; WX 602 ; N uni258E ; G 2432
+U 9615 ; WX 602 ; N uni258F ; G 2433
+U 9616 ; WX 602 ; N rtblock ; G 2434
+U 9617 ; WX 602 ; N ltshade ; G 2435
+U 9618 ; WX 602 ; N shade ; G 2436
+U 9619 ; WX 602 ; N dkshade ; G 2437
+U 9620 ; WX 602 ; N uni2594 ; G 2438
+U 9621 ; WX 602 ; N uni2595 ; G 2439
+U 9622 ; WX 602 ; N uni2596 ; G 2440
+U 9623 ; WX 602 ; N uni2597 ; G 2441
+U 9624 ; WX 602 ; N uni2598 ; G 2442
+U 9625 ; WX 602 ; N uni2599 ; G 2443
+U 9626 ; WX 602 ; N uni259A ; G 2444
+U 9627 ; WX 602 ; N uni259B ; G 2445
+U 9628 ; WX 602 ; N uni259C ; G 2446
+U 9629 ; WX 602 ; N uni259D ; G 2447
+U 9630 ; WX 602 ; N uni259E ; G 2448
+U 9631 ; WX 602 ; N uni259F ; G 2449
+U 9632 ; WX 602 ; N filledbox ; G 2450
+U 9633 ; WX 602 ; N H22073 ; G 2451
+U 9634 ; WX 602 ; N uni25A2 ; G 2452
+U 9635 ; WX 602 ; N uni25A3 ; G 2453
+U 9636 ; WX 602 ; N uni25A4 ; G 2454
+U 9637 ; WX 602 ; N uni25A5 ; G 2455
+U 9638 ; WX 602 ; N uni25A6 ; G 2456
+U 9639 ; WX 602 ; N uni25A7 ; G 2457
+U 9640 ; WX 602 ; N uni25A8 ; G 2458
+U 9641 ; WX 602 ; N uni25A9 ; G 2459
+U 9642 ; WX 602 ; N H18543 ; G 2460
+U 9643 ; WX 602 ; N H18551 ; G 2461
+U 9644 ; WX 602 ; N filledrect ; G 2462
+U 9645 ; WX 602 ; N uni25AD ; G 2463
+U 9646 ; WX 602 ; N uni25AE ; G 2464
+U 9647 ; WX 602 ; N uni25AF ; G 2465
+U 9648 ; WX 602 ; N uni25B0 ; G 2466
+U 9649 ; WX 602 ; N uni25B1 ; G 2467
+U 9650 ; WX 602 ; N triagup ; G 2468
+U 9651 ; WX 602 ; N uni25B3 ; G 2469
+U 9652 ; WX 602 ; N uni25B4 ; G 2470
+U 9653 ; WX 602 ; N uni25B5 ; G 2471
+U 9654 ; WX 602 ; N uni25B6 ; G 2472
+U 9655 ; WX 602 ; N uni25B7 ; G 2473
+U 9656 ; WX 602 ; N uni25B8 ; G 2474
+U 9657 ; WX 602 ; N uni25B9 ; G 2475
+U 9658 ; WX 602 ; N triagrt ; G 2476
+U 9659 ; WX 602 ; N uni25BB ; G 2477
+U 9660 ; WX 602 ; N triagdn ; G 2478
+U 9661 ; WX 602 ; N uni25BD ; G 2479
+U 9662 ; WX 602 ; N uni25BE ; G 2480
+U 9663 ; WX 602 ; N uni25BF ; G 2481
+U 9664 ; WX 602 ; N uni25C0 ; G 2482
+U 9665 ; WX 602 ; N uni25C1 ; G 2483
+U 9666 ; WX 602 ; N uni25C2 ; G 2484
+U 9667 ; WX 602 ; N uni25C3 ; G 2485
+U 9668 ; WX 602 ; N triaglf ; G 2486
+U 9669 ; WX 602 ; N uni25C5 ; G 2487
+U 9670 ; WX 602 ; N uni25C6 ; G 2488
+U 9671 ; WX 602 ; N uni25C7 ; G 2489
+U 9672 ; WX 602 ; N uni25C8 ; G 2490
+U 9673 ; WX 602 ; N uni25C9 ; G 2491
+U 9674 ; WX 602 ; N lozenge ; G 2492
+U 9675 ; WX 602 ; N circle ; G 2493
+U 9676 ; WX 602 ; N uni25CC ; G 2494
+U 9677 ; WX 602 ; N uni25CD ; G 2495
+U 9678 ; WX 602 ; N uni25CE ; G 2496
+U 9679 ; WX 602 ; N H18533 ; G 2497
+U 9680 ; WX 602 ; N uni25D0 ; G 2498
+U 9681 ; WX 602 ; N uni25D1 ; G 2499
+U 9682 ; WX 602 ; N uni25D2 ; G 2500
+U 9683 ; WX 602 ; N uni25D3 ; G 2501
+U 9684 ; WX 602 ; N uni25D4 ; G 2502
+U 9685 ; WX 602 ; N uni25D5 ; G 2503
+U 9686 ; WX 602 ; N uni25D6 ; G 2504
+U 9687 ; WX 602 ; N uni25D7 ; G 2505
+U 9688 ; WX 602 ; N invbullet ; G 2506
+U 9689 ; WX 602 ; N invcircle ; G 2507
+U 9690 ; WX 602 ; N uni25DA ; G 2508
+U 9691 ; WX 602 ; N uni25DB ; G 2509
+U 9692 ; WX 602 ; N uni25DC ; G 2510
+U 9693 ; WX 602 ; N uni25DD ; G 2511
+U 9694 ; WX 602 ; N uni25DE ; G 2512
+U 9695 ; WX 602 ; N uni25DF ; G 2513
+U 9696 ; WX 602 ; N uni25E0 ; G 2514
+U 9697 ; WX 602 ; N uni25E1 ; G 2515
+U 9698 ; WX 602 ; N uni25E2 ; G 2516
+U 9699 ; WX 602 ; N uni25E3 ; G 2517
+U 9700 ; WX 602 ; N uni25E4 ; G 2518
+U 9701 ; WX 602 ; N uni25E5 ; G 2519
+U 9702 ; WX 602 ; N openbullet ; G 2520
+U 9703 ; WX 602 ; N uni25E7 ; G 2521
+U 9704 ; WX 602 ; N uni25E8 ; G 2522
+U 9705 ; WX 602 ; N uni25E9 ; G 2523
+U 9706 ; WX 602 ; N uni25EA ; G 2524
+U 9707 ; WX 602 ; N uni25EB ; G 2525
+U 9708 ; WX 602 ; N uni25EC ; G 2526
+U 9709 ; WX 602 ; N uni25ED ; G 2527
+U 9710 ; WX 602 ; N uni25EE ; G 2528
+U 9711 ; WX 602 ; N uni25EF ; G 2529
+U 9712 ; WX 602 ; N uni25F0 ; G 2530
+U 9713 ; WX 602 ; N uni25F1 ; G 2531
+U 9714 ; WX 602 ; N uni25F2 ; G 2532
+U 9715 ; WX 602 ; N uni25F3 ; G 2533
+U 9716 ; WX 602 ; N uni25F4 ; G 2534
+U 9717 ; WX 602 ; N uni25F5 ; G 2535
+U 9718 ; WX 602 ; N uni25F6 ; G 2536
+U 9719 ; WX 602 ; N uni25F7 ; G 2537
+U 9720 ; WX 602 ; N uni25F8 ; G 2538
+U 9721 ; WX 602 ; N uni25F9 ; G 2539
+U 9722 ; WX 602 ; N uni25FA ; G 2540
+U 9723 ; WX 602 ; N uni25FB ; G 2541
+U 9724 ; WX 602 ; N uni25FC ; G 2542
+U 9725 ; WX 602 ; N uni25FD ; G 2543
+U 9726 ; WX 602 ; N uni25FE ; G 2544
+U 9727 ; WX 602 ; N uni25FF ; G 2545
+U 9728 ; WX 602 ; N uni2600 ; G 2546
+U 9784 ; WX 602 ; N uni2638 ; G 2547
+U 9785 ; WX 602 ; N uni2639 ; G 2548
+U 9786 ; WX 602 ; N smileface ; G 2549
+U 9787 ; WX 602 ; N invsmileface ; G 2550
+U 9788 ; WX 602 ; N sun ; G 2551
+U 9791 ; WX 602 ; N uni263F ; G 2552
+U 9792 ; WX 602 ; N female ; G 2553
+U 9793 ; WX 602 ; N uni2641 ; G 2554
+U 9794 ; WX 602 ; N male ; G 2555
+U 9795 ; WX 602 ; N uni2643 ; G 2556
+U 9796 ; WX 602 ; N uni2644 ; G 2557
+U 9797 ; WX 602 ; N uni2645 ; G 2558
+U 9798 ; WX 602 ; N uni2646 ; G 2559
+U 9799 ; WX 602 ; N uni2647 ; G 2560
+U 9824 ; WX 602 ; N spade ; G 2561
+U 9825 ; WX 602 ; N uni2661 ; G 2562
+U 9826 ; WX 602 ; N uni2662 ; G 2563
+U 9827 ; WX 602 ; N club ; G 2564
+U 9828 ; WX 602 ; N uni2664 ; G 2565
+U 9829 ; WX 602 ; N heart ; G 2566
+U 9830 ; WX 602 ; N diamond ; G 2567
+U 9831 ; WX 602 ; N uni2667 ; G 2568
+U 9833 ; WX 602 ; N uni2669 ; G 2569
+U 9834 ; WX 602 ; N musicalnote ; G 2570
+U 9835 ; WX 602 ; N musicalnotedbl ; G 2571
+U 9836 ; WX 602 ; N uni266C ; G 2572
+U 9837 ; WX 602 ; N uni266D ; G 2573
+U 9838 ; WX 602 ; N uni266E ; G 2574
+U 9839 ; WX 602 ; N uni266F ; G 2575
+U 10178 ; WX 602 ; N uni27C2 ; G 2576
+U 10181 ; WX 602 ; N uni27C5 ; G 2577
+U 10182 ; WX 602 ; N uni27C6 ; G 2578
+U 10204 ; WX 602 ; N uni27DC ; G 2579
+U 10208 ; WX 602 ; N uni27E0 ; G 2580
+U 10214 ; WX 602 ; N uni27E6 ; G 2581
+U 10215 ; WX 602 ; N uni27E7 ; G 2582
+U 10216 ; WX 602 ; N uni27E8 ; G 2583
+U 10217 ; WX 602 ; N uni27E9 ; G 2584
+U 10218 ; WX 602 ; N uni27EA ; G 2585
+U 10219 ; WX 602 ; N uni27EB ; G 2586
+U 10229 ; WX 602 ; N uni27F5 ; G 2587
+U 10230 ; WX 602 ; N uni27F6 ; G 2588
+U 10231 ; WX 602 ; N uni27F7 ; G 2589
+U 10631 ; WX 602 ; N uni2987 ; G 2590
+U 10632 ; WX 602 ; N uni2988 ; G 2591
+U 10647 ; WX 602 ; N uni2997 ; G 2592
+U 10648 ; WX 602 ; N uni2998 ; G 2593
+U 10731 ; WX 602 ; N uni29EB ; G 2594
+U 10746 ; WX 602 ; N uni29FA ; G 2595
+U 10747 ; WX 602 ; N uni29FB ; G 2596
+U 10752 ; WX 602 ; N uni2A00 ; G 2597
+U 10799 ; WX 602 ; N uni2A2F ; G 2598
+U 10858 ; WX 602 ; N uni2A6A ; G 2599
+U 10859 ; WX 602 ; N uni2A6B ; G 2600
+U 11013 ; WX 602 ; N uni2B05 ; G 2601
+U 11014 ; WX 602 ; N uni2B06 ; G 2602
+U 11015 ; WX 602 ; N uni2B07 ; G 2603
+U 11016 ; WX 602 ; N uni2B08 ; G 2604
+U 11017 ; WX 602 ; N uni2B09 ; G 2605
+U 11018 ; WX 602 ; N uni2B0A ; G 2606
+U 11019 ; WX 602 ; N uni2B0B ; G 2607
+U 11020 ; WX 602 ; N uni2B0C ; G 2608
+U 11021 ; WX 602 ; N uni2B0D ; G 2609
+U 11026 ; WX 602 ; N uni2B12 ; G 2610
+U 11027 ; WX 602 ; N uni2B13 ; G 2611
+U 11028 ; WX 602 ; N uni2B14 ; G 2612
+U 11029 ; WX 602 ; N uni2B15 ; G 2613
+U 11030 ; WX 602 ; N uni2B16 ; G 2614
+U 11031 ; WX 602 ; N uni2B17 ; G 2615
+U 11032 ; WX 602 ; N uni2B18 ; G 2616
+U 11033 ; WX 602 ; N uni2B19 ; G 2617
+U 11034 ; WX 602 ; N uni2B1A ; G 2618
+U 11364 ; WX 602 ; N uni2C64 ; G 2619
+U 11373 ; WX 602 ; N uni2C6D ; G 2620
+U 11374 ; WX 602 ; N uni2C6E ; G 2621
+U 11375 ; WX 602 ; N uni2C6F ; G 2622
+U 11376 ; WX 602 ; N uni2C70 ; G 2623
+U 11381 ; WX 602 ; N uni2C75 ; G 2624
+U 11382 ; WX 602 ; N uni2C76 ; G 2625
+U 11383 ; WX 602 ; N uni2C77 ; G 2626
+U 11385 ; WX 602 ; N uni2C79 ; G 2627
+U 11386 ; WX 602 ; N uni2C7A ; G 2628
+U 11388 ; WX 602 ; N uni2C7C ; G 2629
+U 11389 ; WX 602 ; N uni2C7D ; G 2630
+U 11390 ; WX 602 ; N uni2C7E ; G 2631
+U 11391 ; WX 602 ; N uni2C7F ; G 2632
+U 11800 ; WX 602 ; N uni2E18 ; G 2633
+U 11807 ; WX 602 ; N uni2E1F ; G 2634
+U 11810 ; WX 602 ; N uni2E22 ; G 2635
+U 11811 ; WX 602 ; N uni2E23 ; G 2636
+U 11812 ; WX 602 ; N uni2E24 ; G 2637
+U 11813 ; WX 602 ; N uni2E25 ; G 2638
+U 11822 ; WX 602 ; N uni2E2E ; G 2639
+U 42760 ; WX 602 ; N uniA708 ; G 2640
+U 42761 ; WX 602 ; N uniA709 ; G 2641
+U 42762 ; WX 602 ; N uniA70A ; G 2642
+U 42763 ; WX 602 ; N uniA70B ; G 2643
+U 42764 ; WX 602 ; N uniA70C ; G 2644
+U 42765 ; WX 602 ; N uniA70D ; G 2645
+U 42766 ; WX 602 ; N uniA70E ; G 2646
+U 42767 ; WX 602 ; N uniA70F ; G 2647
+U 42768 ; WX 602 ; N uniA710 ; G 2648
+U 42769 ; WX 602 ; N uniA711 ; G 2649
+U 42770 ; WX 602 ; N uniA712 ; G 2650
+U 42771 ; WX 602 ; N uniA713 ; G 2651
+U 42772 ; WX 602 ; N uniA714 ; G 2652
+U 42773 ; WX 602 ; N uniA715 ; G 2653
+U 42774 ; WX 602 ; N uniA716 ; G 2654
+U 42779 ; WX 602 ; N uniA71B ; G 2655
+U 42780 ; WX 602 ; N uniA71C ; G 2656
+U 42781 ; WX 602 ; N uniA71D ; G 2657
+U 42782 ; WX 602 ; N uniA71E ; G 2658
+U 42783 ; WX 602 ; N uniA71F ; G 2659
+U 42786 ; WX 602 ; N uniA722 ; G 2660
+U 42787 ; WX 602 ; N uniA723 ; G 2661
+U 42788 ; WX 602 ; N uniA724 ; G 2662
+U 42789 ; WX 602 ; N uniA725 ; G 2663
+U 42790 ; WX 602 ; N uniA726 ; G 2664
+U 42791 ; WX 602 ; N uniA727 ; G 2665
+U 42889 ; WX 602 ; N uniA789 ; G 2666
+U 42890 ; WX 602 ; N uniA78A ; G 2667
+U 42891 ; WX 602 ; N uniA78B ; G 2668
+U 42892 ; WX 602 ; N uniA78C ; G 2669
+U 42893 ; WX 602 ; N uniA78D ; G 2670
+U 42894 ; WX 602 ; N uniA78E ; G 2671
+U 42896 ; WX 602 ; N uniA790 ; G 2672
+U 42897 ; WX 602 ; N uniA791 ; G 2673
+U 42922 ; WX 602 ; N uniA7AA ; G 2674
+U 43000 ; WX 602 ; N uniA7F8 ; G 2675
+U 43001 ; WX 602 ; N uniA7F9 ; G 2676
+U 63173 ; WX 602 ; N uniF6C5 ; G 2677
+U 64257 ; WX 602 ; N fi ; G 2678
+U 64258 ; WX 602 ; N fl ; G 2679
+U 65529 ; WX 602 ; N uniFFF9 ; G 2680
+U 65530 ; WX 602 ; N uniFFFA ; G 2681
+U 65531 ; WX 602 ; N uniFFFB ; G 2682
+U 65532 ; WX 602 ; N uniFFFC ; G 2683
+U 65533 ; WX 602 ; N uniFFFD ; G 2684
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ttf
new file mode 100644
index 0000000..4c858d4
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ufm
new file mode 100644
index 0000000..4cd3d2a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono-Oblique.ufm
@@ -0,0 +1,2707 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans Mono
+FontSubfamily Oblique
+UniqueID DejaVu Sans Mono Oblique
+FullName DejaVu Sans Mono Oblique
+Version Version 2.37
+PostScriptName DejaVuSansMono-Oblique
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+Weight Medium
+ItalicAngle -11
+IsFixedPitch true
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -403 -375 746 998
+StartCharMetrics 2710
+U 32 ; WX 602 ; N space ; G 3
+U 33 ; WX 602 ; N exclam ; G 4
+U 34 ; WX 602 ; N quotedbl ; G 5
+U 35 ; WX 602 ; N numbersign ; G 6
+U 36 ; WX 602 ; N dollar ; G 7
+U 37 ; WX 602 ; N percent ; G 8
+U 38 ; WX 602 ; N ampersand ; G 9
+U 39 ; WX 602 ; N quotesingle ; G 10
+U 40 ; WX 602 ; N parenleft ; G 11
+U 41 ; WX 602 ; N parenright ; G 12
+U 42 ; WX 602 ; N asterisk ; G 13
+U 43 ; WX 602 ; N plus ; G 14
+U 44 ; WX 602 ; N comma ; G 15
+U 45 ; WX 602 ; N hyphen ; G 16
+U 46 ; WX 602 ; N period ; G 17
+U 47 ; WX 602 ; N slash ; G 18
+U 48 ; WX 602 ; N zero ; G 19
+U 49 ; WX 602 ; N one ; G 20
+U 50 ; WX 602 ; N two ; G 21
+U 51 ; WX 602 ; N three ; G 22
+U 52 ; WX 602 ; N four ; G 23
+U 53 ; WX 602 ; N five ; G 24
+U 54 ; WX 602 ; N six ; G 25
+U 55 ; WX 602 ; N seven ; G 26
+U 56 ; WX 602 ; N eight ; G 27
+U 57 ; WX 602 ; N nine ; G 28
+U 58 ; WX 602 ; N colon ; G 29
+U 59 ; WX 602 ; N semicolon ; G 30
+U 60 ; WX 602 ; N less ; G 31
+U 61 ; WX 602 ; N equal ; G 32
+U 62 ; WX 602 ; N greater ; G 33
+U 63 ; WX 602 ; N question ; G 34
+U 64 ; WX 602 ; N at ; G 35
+U 65 ; WX 602 ; N A ; G 36
+U 66 ; WX 602 ; N B ; G 37
+U 67 ; WX 602 ; N C ; G 38
+U 68 ; WX 602 ; N D ; G 39
+U 69 ; WX 602 ; N E ; G 40
+U 70 ; WX 602 ; N F ; G 41
+U 71 ; WX 602 ; N G ; G 42
+U 72 ; WX 602 ; N H ; G 43
+U 73 ; WX 602 ; N I ; G 44
+U 74 ; WX 602 ; N J ; G 45
+U 75 ; WX 602 ; N K ; G 46
+U 76 ; WX 602 ; N L ; G 47
+U 77 ; WX 602 ; N M ; G 48
+U 78 ; WX 602 ; N N ; G 49
+U 79 ; WX 602 ; N O ; G 50
+U 80 ; WX 602 ; N P ; G 51
+U 81 ; WX 602 ; N Q ; G 52
+U 82 ; WX 602 ; N R ; G 53
+U 83 ; WX 602 ; N S ; G 54
+U 84 ; WX 602 ; N T ; G 55
+U 85 ; WX 602 ; N U ; G 56
+U 86 ; WX 602 ; N V ; G 57
+U 87 ; WX 602 ; N W ; G 58
+U 88 ; WX 602 ; N X ; G 59
+U 89 ; WX 602 ; N Y ; G 60
+U 90 ; WX 602 ; N Z ; G 61
+U 91 ; WX 602 ; N bracketleft ; G 62
+U 92 ; WX 602 ; N backslash ; G 63
+U 93 ; WX 602 ; N bracketright ; G 64
+U 94 ; WX 602 ; N asciicircum ; G 65
+U 95 ; WX 602 ; N underscore ; G 66
+U 96 ; WX 602 ; N grave ; G 67
+U 97 ; WX 602 ; N a ; G 68
+U 98 ; WX 602 ; N b ; G 69
+U 99 ; WX 602 ; N c ; G 70
+U 100 ; WX 602 ; N d ; G 71
+U 101 ; WX 602 ; N e ; G 72
+U 102 ; WX 602 ; N f ; G 73
+U 103 ; WX 602 ; N g ; G 74
+U 104 ; WX 602 ; N h ; G 75
+U 105 ; WX 602 ; N i ; G 76
+U 106 ; WX 602 ; N j ; G 77
+U 107 ; WX 602 ; N k ; G 78
+U 108 ; WX 602 ; N l ; G 79
+U 109 ; WX 602 ; N m ; G 80
+U 110 ; WX 602 ; N n ; G 81
+U 111 ; WX 602 ; N o ; G 82
+U 112 ; WX 602 ; N p ; G 83
+U 113 ; WX 602 ; N q ; G 84
+U 114 ; WX 602 ; N r ; G 85
+U 115 ; WX 602 ; N s ; G 86
+U 116 ; WX 602 ; N t ; G 87
+U 117 ; WX 602 ; N u ; G 88
+U 118 ; WX 602 ; N v ; G 89
+U 119 ; WX 602 ; N w ; G 90
+U 120 ; WX 602 ; N x ; G 91
+U 121 ; WX 602 ; N y ; G 92
+U 122 ; WX 602 ; N z ; G 93
+U 123 ; WX 602 ; N braceleft ; G 94
+U 124 ; WX 602 ; N bar ; G 95
+U 125 ; WX 602 ; N braceright ; G 96
+U 126 ; WX 602 ; N asciitilde ; G 97
+U 160 ; WX 602 ; N nbspace ; G 98
+U 161 ; WX 602 ; N exclamdown ; G 99
+U 162 ; WX 602 ; N cent ; G 100
+U 163 ; WX 602 ; N sterling ; G 101
+U 164 ; WX 602 ; N currency ; G 102
+U 165 ; WX 602 ; N yen ; G 103
+U 166 ; WX 602 ; N brokenbar ; G 104
+U 167 ; WX 602 ; N section ; G 105
+U 168 ; WX 602 ; N dieresis ; G 106
+U 169 ; WX 602 ; N copyright ; G 107
+U 170 ; WX 602 ; N ordfeminine ; G 108
+U 171 ; WX 602 ; N guillemotleft ; G 109
+U 172 ; WX 602 ; N logicalnot ; G 110
+U 173 ; WX 602 ; N sfthyphen ; G 111
+U 174 ; WX 602 ; N registered ; G 112
+U 175 ; WX 602 ; N macron ; G 113
+U 176 ; WX 602 ; N degree ; G 114
+U 177 ; WX 602 ; N plusminus ; G 115
+U 178 ; WX 602 ; N twosuperior ; G 116
+U 179 ; WX 602 ; N threesuperior ; G 117
+U 180 ; WX 602 ; N acute ; G 118
+U 181 ; WX 602 ; N mu ; G 119
+U 182 ; WX 602 ; N paragraph ; G 120
+U 183 ; WX 602 ; N periodcentered ; G 121
+U 184 ; WX 602 ; N cedilla ; G 122
+U 185 ; WX 602 ; N onesuperior ; G 123
+U 186 ; WX 602 ; N ordmasculine ; G 124
+U 187 ; WX 602 ; N guillemotright ; G 125
+U 188 ; WX 602 ; N onequarter ; G 126
+U 189 ; WX 602 ; N onehalf ; G 127
+U 190 ; WX 602 ; N threequarters ; G 128
+U 191 ; WX 602 ; N questiondown ; G 129
+U 192 ; WX 602 ; N Agrave ; G 130
+U 193 ; WX 602 ; N Aacute ; G 131
+U 194 ; WX 602 ; N Acircumflex ; G 132
+U 195 ; WX 602 ; N Atilde ; G 133
+U 196 ; WX 602 ; N Adieresis ; G 134
+U 197 ; WX 602 ; N Aring ; G 135
+U 198 ; WX 602 ; N AE ; G 136
+U 199 ; WX 602 ; N Ccedilla ; G 137
+U 200 ; WX 602 ; N Egrave ; G 138
+U 201 ; WX 602 ; N Eacute ; G 139
+U 202 ; WX 602 ; N Ecircumflex ; G 140
+U 203 ; WX 602 ; N Edieresis ; G 141
+U 204 ; WX 602 ; N Igrave ; G 142
+U 205 ; WX 602 ; N Iacute ; G 143
+U 206 ; WX 602 ; N Icircumflex ; G 144
+U 207 ; WX 602 ; N Idieresis ; G 145
+U 208 ; WX 602 ; N Eth ; G 146
+U 209 ; WX 602 ; N Ntilde ; G 147
+U 210 ; WX 602 ; N Ograve ; G 148
+U 211 ; WX 602 ; N Oacute ; G 149
+U 212 ; WX 602 ; N Ocircumflex ; G 150
+U 213 ; WX 602 ; N Otilde ; G 151
+U 214 ; WX 602 ; N Odieresis ; G 152
+U 215 ; WX 602 ; N multiply ; G 153
+U 216 ; WX 602 ; N Oslash ; G 154
+U 217 ; WX 602 ; N Ugrave ; G 155
+U 218 ; WX 602 ; N Uacute ; G 156
+U 219 ; WX 602 ; N Ucircumflex ; G 157
+U 220 ; WX 602 ; N Udieresis ; G 158
+U 221 ; WX 602 ; N Yacute ; G 159
+U 222 ; WX 602 ; N Thorn ; G 160
+U 223 ; WX 602 ; N germandbls ; G 161
+U 224 ; WX 602 ; N agrave ; G 162
+U 225 ; WX 602 ; N aacute ; G 163
+U 226 ; WX 602 ; N acircumflex ; G 164
+U 227 ; WX 602 ; N atilde ; G 165
+U 228 ; WX 602 ; N adieresis ; G 166
+U 229 ; WX 602 ; N aring ; G 167
+U 230 ; WX 602 ; N ae ; G 168
+U 231 ; WX 602 ; N ccedilla ; G 169
+U 232 ; WX 602 ; N egrave ; G 170
+U 233 ; WX 602 ; N eacute ; G 171
+U 234 ; WX 602 ; N ecircumflex ; G 172
+U 235 ; WX 602 ; N edieresis ; G 173
+U 236 ; WX 602 ; N igrave ; G 174
+U 237 ; WX 602 ; N iacute ; G 175
+U 238 ; WX 602 ; N icircumflex ; G 176
+U 239 ; WX 602 ; N idieresis ; G 177
+U 240 ; WX 602 ; N eth ; G 178
+U 241 ; WX 602 ; N ntilde ; G 179
+U 242 ; WX 602 ; N ograve ; G 180
+U 243 ; WX 602 ; N oacute ; G 181
+U 244 ; WX 602 ; N ocircumflex ; G 182
+U 245 ; WX 602 ; N otilde ; G 183
+U 246 ; WX 602 ; N odieresis ; G 184
+U 247 ; WX 602 ; N divide ; G 185
+U 248 ; WX 602 ; N oslash ; G 186
+U 249 ; WX 602 ; N ugrave ; G 187
+U 250 ; WX 602 ; N uacute ; G 188
+U 251 ; WX 602 ; N ucircumflex ; G 189
+U 252 ; WX 602 ; N udieresis ; G 190
+U 253 ; WX 602 ; N yacute ; G 191
+U 254 ; WX 602 ; N thorn ; G 192
+U 255 ; WX 602 ; N ydieresis ; G 193
+U 256 ; WX 602 ; N Amacron ; G 194
+U 257 ; WX 602 ; N amacron ; G 195
+U 258 ; WX 602 ; N Abreve ; G 196
+U 259 ; WX 602 ; N abreve ; G 197
+U 260 ; WX 602 ; N Aogonek ; G 198
+U 261 ; WX 602 ; N aogonek ; G 199
+U 262 ; WX 602 ; N Cacute ; G 200
+U 263 ; WX 602 ; N cacute ; G 201
+U 264 ; WX 602 ; N Ccircumflex ; G 202
+U 265 ; WX 602 ; N ccircumflex ; G 203
+U 266 ; WX 602 ; N Cdotaccent ; G 204
+U 267 ; WX 602 ; N cdotaccent ; G 205
+U 268 ; WX 602 ; N Ccaron ; G 206
+U 269 ; WX 602 ; N ccaron ; G 207
+U 270 ; WX 602 ; N Dcaron ; G 208
+U 271 ; WX 602 ; N dcaron ; G 209
+U 272 ; WX 602 ; N Dcroat ; G 210
+U 273 ; WX 602 ; N dmacron ; G 211
+U 274 ; WX 602 ; N Emacron ; G 212
+U 275 ; WX 602 ; N emacron ; G 213
+U 276 ; WX 602 ; N Ebreve ; G 214
+U 277 ; WX 602 ; N ebreve ; G 215
+U 278 ; WX 602 ; N Edotaccent ; G 216
+U 279 ; WX 602 ; N edotaccent ; G 217
+U 280 ; WX 602 ; N Eogonek ; G 218
+U 281 ; WX 602 ; N eogonek ; G 219
+U 282 ; WX 602 ; N Ecaron ; G 220
+U 283 ; WX 602 ; N ecaron ; G 221
+U 284 ; WX 602 ; N Gcircumflex ; G 222
+U 285 ; WX 602 ; N gcircumflex ; G 223
+U 286 ; WX 602 ; N Gbreve ; G 224
+U 287 ; WX 602 ; N gbreve ; G 225
+U 288 ; WX 602 ; N Gdotaccent ; G 226
+U 289 ; WX 602 ; N gdotaccent ; G 227
+U 290 ; WX 602 ; N Gcommaaccent ; G 228
+U 291 ; WX 602 ; N gcommaaccent ; G 229
+U 292 ; WX 602 ; N Hcircumflex ; G 230
+U 293 ; WX 602 ; N hcircumflex ; G 231
+U 294 ; WX 602 ; N Hbar ; G 232
+U 295 ; WX 602 ; N hbar ; G 233
+U 296 ; WX 602 ; N Itilde ; G 234
+U 297 ; WX 602 ; N itilde ; G 235
+U 298 ; WX 602 ; N Imacron ; G 236
+U 299 ; WX 602 ; N imacron ; G 237
+U 300 ; WX 602 ; N Ibreve ; G 238
+U 301 ; WX 602 ; N ibreve ; G 239
+U 302 ; WX 602 ; N Iogonek ; G 240
+U 303 ; WX 602 ; N iogonek ; G 241
+U 304 ; WX 602 ; N Idot ; G 242
+U 305 ; WX 602 ; N dotlessi ; G 243
+U 306 ; WX 602 ; N IJ ; G 244
+U 307 ; WX 602 ; N ij ; G 245
+U 308 ; WX 602 ; N Jcircumflex ; G 246
+U 309 ; WX 602 ; N jcircumflex ; G 247
+U 310 ; WX 602 ; N Kcommaaccent ; G 248
+U 311 ; WX 602 ; N kcommaaccent ; G 249
+U 312 ; WX 602 ; N kgreenlandic ; G 250
+U 313 ; WX 602 ; N Lacute ; G 251
+U 314 ; WX 602 ; N lacute ; G 252
+U 315 ; WX 602 ; N Lcommaaccent ; G 253
+U 316 ; WX 602 ; N lcommaaccent ; G 254
+U 317 ; WX 602 ; N Lcaron ; G 255
+U 318 ; WX 602 ; N lcaron ; G 256
+U 319 ; WX 602 ; N Ldot ; G 257
+U 320 ; WX 602 ; N ldot ; G 258
+U 321 ; WX 602 ; N Lslash ; G 259
+U 322 ; WX 602 ; N lslash ; G 260
+U 323 ; WX 602 ; N Nacute ; G 261
+U 324 ; WX 602 ; N nacute ; G 262
+U 325 ; WX 602 ; N Ncommaaccent ; G 263
+U 326 ; WX 602 ; N ncommaaccent ; G 264
+U 327 ; WX 602 ; N Ncaron ; G 265
+U 328 ; WX 602 ; N ncaron ; G 266
+U 329 ; WX 602 ; N napostrophe ; G 267
+U 330 ; WX 602 ; N Eng ; G 268
+U 331 ; WX 602 ; N eng ; G 269
+U 332 ; WX 602 ; N Omacron ; G 270
+U 333 ; WX 602 ; N omacron ; G 271
+U 334 ; WX 602 ; N Obreve ; G 272
+U 335 ; WX 602 ; N obreve ; G 273
+U 336 ; WX 602 ; N Ohungarumlaut ; G 274
+U 337 ; WX 602 ; N ohungarumlaut ; G 275
+U 338 ; WX 602 ; N OE ; G 276
+U 339 ; WX 602 ; N oe ; G 277
+U 340 ; WX 602 ; N Racute ; G 278
+U 341 ; WX 602 ; N racute ; G 279
+U 342 ; WX 602 ; N Rcommaaccent ; G 280
+U 343 ; WX 602 ; N rcommaaccent ; G 281
+U 344 ; WX 602 ; N Rcaron ; G 282
+U 345 ; WX 602 ; N rcaron ; G 283
+U 346 ; WX 602 ; N Sacute ; G 284
+U 347 ; WX 602 ; N sacute ; G 285
+U 348 ; WX 602 ; N Scircumflex ; G 286
+U 349 ; WX 602 ; N scircumflex ; G 287
+U 350 ; WX 602 ; N Scedilla ; G 288
+U 351 ; WX 602 ; N scedilla ; G 289
+U 352 ; WX 602 ; N Scaron ; G 290
+U 353 ; WX 602 ; N scaron ; G 291
+U 354 ; WX 602 ; N Tcommaaccent ; G 292
+U 355 ; WX 602 ; N tcommaaccent ; G 293
+U 356 ; WX 602 ; N Tcaron ; G 294
+U 357 ; WX 602 ; N tcaron ; G 295
+U 358 ; WX 602 ; N Tbar ; G 296
+U 359 ; WX 602 ; N tbar ; G 297
+U 360 ; WX 602 ; N Utilde ; G 298
+U 361 ; WX 602 ; N utilde ; G 299
+U 362 ; WX 602 ; N Umacron ; G 300
+U 363 ; WX 602 ; N umacron ; G 301
+U 364 ; WX 602 ; N Ubreve ; G 302
+U 365 ; WX 602 ; N ubreve ; G 303
+U 366 ; WX 602 ; N Uring ; G 304
+U 367 ; WX 602 ; N uring ; G 305
+U 368 ; WX 602 ; N Uhungarumlaut ; G 306
+U 369 ; WX 602 ; N uhungarumlaut ; G 307
+U 370 ; WX 602 ; N Uogonek ; G 308
+U 371 ; WX 602 ; N uogonek ; G 309
+U 372 ; WX 602 ; N Wcircumflex ; G 310
+U 373 ; WX 602 ; N wcircumflex ; G 311
+U 374 ; WX 602 ; N Ycircumflex ; G 312
+U 375 ; WX 602 ; N ycircumflex ; G 313
+U 376 ; WX 602 ; N Ydieresis ; G 314
+U 377 ; WX 602 ; N Zacute ; G 315
+U 378 ; WX 602 ; N zacute ; G 316
+U 379 ; WX 602 ; N Zdotaccent ; G 317
+U 380 ; WX 602 ; N zdotaccent ; G 318
+U 381 ; WX 602 ; N Zcaron ; G 319
+U 382 ; WX 602 ; N zcaron ; G 320
+U 383 ; WX 602 ; N longs ; G 321
+U 384 ; WX 602 ; N uni0180 ; G 322
+U 385 ; WX 602 ; N uni0181 ; G 323
+U 386 ; WX 602 ; N uni0182 ; G 324
+U 387 ; WX 602 ; N uni0183 ; G 325
+U 388 ; WX 602 ; N uni0184 ; G 326
+U 389 ; WX 602 ; N uni0185 ; G 327
+U 390 ; WX 602 ; N uni0186 ; G 328
+U 391 ; WX 602 ; N uni0187 ; G 329
+U 392 ; WX 602 ; N uni0188 ; G 330
+U 393 ; WX 602 ; N uni0189 ; G 331
+U 394 ; WX 602 ; N uni018A ; G 332
+U 395 ; WX 602 ; N uni018B ; G 333
+U 396 ; WX 602 ; N uni018C ; G 334
+U 397 ; WX 602 ; N uni018D ; G 335
+U 398 ; WX 602 ; N uni018E ; G 336
+U 399 ; WX 602 ; N uni018F ; G 337
+U 400 ; WX 602 ; N uni0190 ; G 338
+U 401 ; WX 602 ; N uni0191 ; G 339
+U 402 ; WX 602 ; N florin ; G 340
+U 403 ; WX 602 ; N uni0193 ; G 341
+U 404 ; WX 602 ; N uni0194 ; G 342
+U 405 ; WX 602 ; N uni0195 ; G 343
+U 406 ; WX 602 ; N uni0196 ; G 344
+U 407 ; WX 602 ; N uni0197 ; G 345
+U 408 ; WX 602 ; N uni0198 ; G 346
+U 409 ; WX 602 ; N uni0199 ; G 347
+U 410 ; WX 602 ; N uni019A ; G 348
+U 411 ; WX 602 ; N uni019B ; G 349
+U 412 ; WX 602 ; N uni019C ; G 350
+U 413 ; WX 602 ; N uni019D ; G 351
+U 414 ; WX 602 ; N uni019E ; G 352
+U 415 ; WX 602 ; N uni019F ; G 353
+U 416 ; WX 602 ; N Ohorn ; G 354
+U 417 ; WX 602 ; N ohorn ; G 355
+U 418 ; WX 602 ; N uni01A2 ; G 356
+U 419 ; WX 602 ; N uni01A3 ; G 357
+U 420 ; WX 602 ; N uni01A4 ; G 358
+U 421 ; WX 602 ; N uni01A5 ; G 359
+U 422 ; WX 602 ; N uni01A6 ; G 360
+U 423 ; WX 602 ; N uni01A7 ; G 361
+U 424 ; WX 602 ; N uni01A8 ; G 362
+U 425 ; WX 602 ; N uni01A9 ; G 363
+U 426 ; WX 602 ; N uni01AA ; G 364
+U 427 ; WX 602 ; N uni01AB ; G 365
+U 428 ; WX 602 ; N uni01AC ; G 366
+U 429 ; WX 602 ; N uni01AD ; G 367
+U 430 ; WX 602 ; N uni01AE ; G 368
+U 431 ; WX 602 ; N Uhorn ; G 369
+U 432 ; WX 602 ; N uhorn ; G 370
+U 433 ; WX 602 ; N uni01B1 ; G 371
+U 434 ; WX 602 ; N uni01B2 ; G 372
+U 435 ; WX 602 ; N uni01B3 ; G 373
+U 436 ; WX 602 ; N uni01B4 ; G 374
+U 437 ; WX 602 ; N uni01B5 ; G 375
+U 438 ; WX 602 ; N uni01B6 ; G 376
+U 439 ; WX 602 ; N uni01B7 ; G 377
+U 440 ; WX 602 ; N uni01B8 ; G 378
+U 441 ; WX 602 ; N uni01B9 ; G 379
+U 442 ; WX 602 ; N uni01BA ; G 380
+U 443 ; WX 602 ; N uni01BB ; G 381
+U 444 ; WX 602 ; N uni01BC ; G 382
+U 445 ; WX 602 ; N uni01BD ; G 383
+U 446 ; WX 602 ; N uni01BE ; G 384
+U 447 ; WX 602 ; N uni01BF ; G 385
+U 448 ; WX 602 ; N uni01C0 ; G 386
+U 449 ; WX 602 ; N uni01C1 ; G 387
+U 450 ; WX 602 ; N uni01C2 ; G 388
+U 451 ; WX 602 ; N uni01C3 ; G 389
+U 461 ; WX 602 ; N uni01CD ; G 390
+U 462 ; WX 602 ; N uni01CE ; G 391
+U 463 ; WX 602 ; N uni01CF ; G 392
+U 464 ; WX 602 ; N uni01D0 ; G 393
+U 465 ; WX 602 ; N uni01D1 ; G 394
+U 466 ; WX 602 ; N uni01D2 ; G 395
+U 467 ; WX 602 ; N uni01D3 ; G 396
+U 468 ; WX 602 ; N uni01D4 ; G 397
+U 469 ; WX 602 ; N uni01D5 ; G 398
+U 470 ; WX 602 ; N uni01D6 ; G 399
+U 471 ; WX 602 ; N uni01D7 ; G 400
+U 472 ; WX 602 ; N uni01D8 ; G 401
+U 473 ; WX 602 ; N uni01D9 ; G 402
+U 474 ; WX 602 ; N uni01DA ; G 403
+U 475 ; WX 602 ; N uni01DB ; G 404
+U 476 ; WX 602 ; N uni01DC ; G 405
+U 477 ; WX 602 ; N uni01DD ; G 406
+U 479 ; WX 602 ; N uni01DF ; G 407
+U 480 ; WX 602 ; N uni01E0 ; G 408
+U 481 ; WX 602 ; N uni01E1 ; G 409
+U 482 ; WX 602 ; N uni01E2 ; G 410
+U 483 ; WX 602 ; N uni01E3 ; G 411
+U 486 ; WX 602 ; N Gcaron ; G 412
+U 487 ; WX 602 ; N gcaron ; G 413
+U 488 ; WX 602 ; N uni01E8 ; G 414
+U 489 ; WX 602 ; N uni01E9 ; G 415
+U 490 ; WX 602 ; N uni01EA ; G 416
+U 491 ; WX 602 ; N uni01EB ; G 417
+U 492 ; WX 602 ; N uni01EC ; G 418
+U 493 ; WX 602 ; N uni01ED ; G 419
+U 494 ; WX 602 ; N uni01EE ; G 420
+U 495 ; WX 602 ; N uni01EF ; G 421
+U 500 ; WX 602 ; N uni01F4 ; G 422
+U 501 ; WX 602 ; N uni01F5 ; G 423
+U 502 ; WX 602 ; N uni01F6 ; G 424
+U 504 ; WX 602 ; N uni01F8 ; G 425
+U 505 ; WX 602 ; N uni01F9 ; G 426
+U 508 ; WX 602 ; N AEacute ; G 427
+U 509 ; WX 602 ; N aeacute ; G 428
+U 510 ; WX 602 ; N Oslashacute ; G 429
+U 511 ; WX 602 ; N oslashacute ; G 430
+U 512 ; WX 602 ; N uni0200 ; G 431
+U 513 ; WX 602 ; N uni0201 ; G 432
+U 514 ; WX 602 ; N uni0202 ; G 433
+U 515 ; WX 602 ; N uni0203 ; G 434
+U 516 ; WX 602 ; N uni0204 ; G 435
+U 517 ; WX 602 ; N uni0205 ; G 436
+U 518 ; WX 602 ; N uni0206 ; G 437
+U 519 ; WX 602 ; N uni0207 ; G 438
+U 520 ; WX 602 ; N uni0208 ; G 439
+U 521 ; WX 602 ; N uni0209 ; G 440
+U 522 ; WX 602 ; N uni020A ; G 441
+U 523 ; WX 602 ; N uni020B ; G 442
+U 524 ; WX 602 ; N uni020C ; G 443
+U 525 ; WX 602 ; N uni020D ; G 444
+U 526 ; WX 602 ; N uni020E ; G 445
+U 527 ; WX 602 ; N uni020F ; G 446
+U 528 ; WX 602 ; N uni0210 ; G 447
+U 529 ; WX 602 ; N uni0211 ; G 448
+U 530 ; WX 602 ; N uni0212 ; G 449
+U 531 ; WX 602 ; N uni0213 ; G 450
+U 532 ; WX 602 ; N uni0214 ; G 451
+U 533 ; WX 602 ; N uni0215 ; G 452
+U 534 ; WX 602 ; N uni0216 ; G 453
+U 535 ; WX 602 ; N uni0217 ; G 454
+U 536 ; WX 602 ; N Scommaaccent ; G 455
+U 537 ; WX 602 ; N scommaaccent ; G 456
+U 538 ; WX 602 ; N uni021A ; G 457
+U 539 ; WX 602 ; N uni021B ; G 458
+U 540 ; WX 602 ; N uni021C ; G 459
+U 541 ; WX 602 ; N uni021D ; G 460
+U 542 ; WX 602 ; N uni021E ; G 461
+U 543 ; WX 602 ; N uni021F ; G 462
+U 545 ; WX 602 ; N uni0221 ; G 463
+U 548 ; WX 602 ; N uni0224 ; G 464
+U 549 ; WX 602 ; N uni0225 ; G 465
+U 550 ; WX 602 ; N uni0226 ; G 466
+U 551 ; WX 602 ; N uni0227 ; G 467
+U 552 ; WX 602 ; N uni0228 ; G 468
+U 553 ; WX 602 ; N uni0229 ; G 469
+U 554 ; WX 602 ; N uni022A ; G 470
+U 555 ; WX 602 ; N uni022B ; G 471
+U 556 ; WX 602 ; N uni022C ; G 472
+U 557 ; WX 602 ; N uni022D ; G 473
+U 558 ; WX 602 ; N uni022E ; G 474
+U 559 ; WX 602 ; N uni022F ; G 475
+U 560 ; WX 602 ; N uni0230 ; G 476
+U 561 ; WX 602 ; N uni0231 ; G 477
+U 562 ; WX 602 ; N uni0232 ; G 478
+U 563 ; WX 602 ; N uni0233 ; G 479
+U 564 ; WX 602 ; N uni0234 ; G 480
+U 565 ; WX 602 ; N uni0235 ; G 481
+U 566 ; WX 602 ; N uni0236 ; G 482
+U 567 ; WX 602 ; N dotlessj ; G 483
+U 568 ; WX 602 ; N uni0238 ; G 484
+U 569 ; WX 602 ; N uni0239 ; G 485
+U 570 ; WX 602 ; N uni023A ; G 486
+U 571 ; WX 602 ; N uni023B ; G 487
+U 572 ; WX 602 ; N uni023C ; G 488
+U 573 ; WX 602 ; N uni023D ; G 489
+U 574 ; WX 602 ; N uni023E ; G 490
+U 575 ; WX 602 ; N uni023F ; G 491
+U 576 ; WX 602 ; N uni0240 ; G 492
+U 577 ; WX 602 ; N uni0241 ; G 493
+U 579 ; WX 602 ; N uni0243 ; G 494
+U 580 ; WX 602 ; N uni0244 ; G 495
+U 581 ; WX 602 ; N uni0245 ; G 496
+U 588 ; WX 602 ; N uni024C ; G 497
+U 589 ; WX 602 ; N uni024D ; G 498
+U 592 ; WX 602 ; N uni0250 ; G 499
+U 593 ; WX 602 ; N uni0251 ; G 500
+U 594 ; WX 602 ; N uni0252 ; G 501
+U 595 ; WX 602 ; N uni0253 ; G 502
+U 596 ; WX 602 ; N uni0254 ; G 503
+U 597 ; WX 602 ; N uni0255 ; G 504
+U 598 ; WX 602 ; N uni0256 ; G 505
+U 599 ; WX 602 ; N uni0257 ; G 506
+U 600 ; WX 602 ; N uni0258 ; G 507
+U 601 ; WX 602 ; N uni0259 ; G 508
+U 602 ; WX 602 ; N uni025A ; G 509
+U 603 ; WX 602 ; N uni025B ; G 510
+U 604 ; WX 602 ; N uni025C ; G 511
+U 605 ; WX 602 ; N uni025D ; G 512
+U 606 ; WX 602 ; N uni025E ; G 513
+U 607 ; WX 602 ; N uni025F ; G 514
+U 608 ; WX 602 ; N uni0260 ; G 515
+U 609 ; WX 602 ; N uni0261 ; G 516
+U 610 ; WX 602 ; N uni0262 ; G 517
+U 611 ; WX 602 ; N uni0263 ; G 518
+U 612 ; WX 602 ; N uni0264 ; G 519
+U 613 ; WX 602 ; N uni0265 ; G 520
+U 614 ; WX 602 ; N uni0266 ; G 521
+U 615 ; WX 602 ; N uni0267 ; G 522
+U 616 ; WX 602 ; N uni0268 ; G 523
+U 617 ; WX 602 ; N uni0269 ; G 524
+U 618 ; WX 602 ; N uni026A ; G 525
+U 619 ; WX 602 ; N uni026B ; G 526
+U 620 ; WX 602 ; N uni026C ; G 527
+U 621 ; WX 602 ; N uni026D ; G 528
+U 622 ; WX 602 ; N uni026E ; G 529
+U 623 ; WX 602 ; N uni026F ; G 530
+U 624 ; WX 602 ; N uni0270 ; G 531
+U 625 ; WX 602 ; N uni0271 ; G 532
+U 626 ; WX 602 ; N uni0272 ; G 533
+U 627 ; WX 602 ; N uni0273 ; G 534
+U 628 ; WX 602 ; N uni0274 ; G 535
+U 629 ; WX 602 ; N uni0275 ; G 536
+U 630 ; WX 602 ; N uni0276 ; G 537
+U 631 ; WX 602 ; N uni0277 ; G 538
+U 632 ; WX 602 ; N uni0278 ; G 539
+U 633 ; WX 602 ; N uni0279 ; G 540
+U 634 ; WX 602 ; N uni027A ; G 541
+U 635 ; WX 602 ; N uni027B ; G 542
+U 636 ; WX 602 ; N uni027C ; G 543
+U 637 ; WX 602 ; N uni027D ; G 544
+U 638 ; WX 602 ; N uni027E ; G 545
+U 639 ; WX 602 ; N uni027F ; G 546
+U 640 ; WX 602 ; N uni0280 ; G 547
+U 641 ; WX 602 ; N uni0281 ; G 548
+U 642 ; WX 602 ; N uni0282 ; G 549
+U 643 ; WX 602 ; N uni0283 ; G 550
+U 644 ; WX 602 ; N uni0284 ; G 551
+U 645 ; WX 602 ; N uni0285 ; G 552
+U 646 ; WX 602 ; N uni0286 ; G 553
+U 647 ; WX 602 ; N uni0287 ; G 554
+U 648 ; WX 602 ; N uni0288 ; G 555
+U 649 ; WX 602 ; N uni0289 ; G 556
+U 650 ; WX 602 ; N uni028A ; G 557
+U 651 ; WX 602 ; N uni028B ; G 558
+U 652 ; WX 602 ; N uni028C ; G 559
+U 653 ; WX 602 ; N uni028D ; G 560
+U 654 ; WX 602 ; N uni028E ; G 561
+U 655 ; WX 602 ; N uni028F ; G 562
+U 656 ; WX 602 ; N uni0290 ; G 563
+U 657 ; WX 602 ; N uni0291 ; G 564
+U 658 ; WX 602 ; N uni0292 ; G 565
+U 659 ; WX 602 ; N uni0293 ; G 566
+U 660 ; WX 602 ; N uni0294 ; G 567
+U 661 ; WX 602 ; N uni0295 ; G 568
+U 662 ; WX 602 ; N uni0296 ; G 569
+U 663 ; WX 602 ; N uni0297 ; G 570
+U 664 ; WX 602 ; N uni0298 ; G 571
+U 665 ; WX 602 ; N uni0299 ; G 572
+U 666 ; WX 602 ; N uni029A ; G 573
+U 667 ; WX 602 ; N uni029B ; G 574
+U 668 ; WX 602 ; N uni029C ; G 575
+U 669 ; WX 602 ; N uni029D ; G 576
+U 670 ; WX 602 ; N uni029E ; G 577
+U 671 ; WX 602 ; N uni029F ; G 578
+U 672 ; WX 602 ; N uni02A0 ; G 579
+U 673 ; WX 602 ; N uni02A1 ; G 580
+U 674 ; WX 602 ; N uni02A2 ; G 581
+U 675 ; WX 602 ; N uni02A3 ; G 582
+U 676 ; WX 602 ; N uni02A4 ; G 583
+U 677 ; WX 602 ; N uni02A5 ; G 584
+U 678 ; WX 602 ; N uni02A6 ; G 585
+U 679 ; WX 602 ; N uni02A7 ; G 586
+U 680 ; WX 602 ; N uni02A8 ; G 587
+U 681 ; WX 602 ; N uni02A9 ; G 588
+U 682 ; WX 602 ; N uni02AA ; G 589
+U 683 ; WX 602 ; N uni02AB ; G 590
+U 684 ; WX 602 ; N uni02AC ; G 591
+U 685 ; WX 602 ; N uni02AD ; G 592
+U 686 ; WX 602 ; N uni02AE ; G 593
+U 687 ; WX 602 ; N uni02AF ; G 594
+U 688 ; WX 602 ; N uni02B0 ; G 595
+U 689 ; WX 602 ; N uni02B1 ; G 596
+U 690 ; WX 602 ; N uni02B2 ; G 597
+U 691 ; WX 602 ; N uni02B3 ; G 598
+U 692 ; WX 602 ; N uni02B4 ; G 599
+U 693 ; WX 602 ; N uni02B5 ; G 600
+U 694 ; WX 602 ; N uni02B6 ; G 601
+U 695 ; WX 602 ; N uni02B7 ; G 602
+U 696 ; WX 602 ; N uni02B8 ; G 603
+U 697 ; WX 602 ; N uni02B9 ; G 604
+U 699 ; WX 602 ; N uni02BB ; G 605
+U 700 ; WX 602 ; N uni02BC ; G 606
+U 701 ; WX 602 ; N uni02BD ; G 607
+U 702 ; WX 602 ; N uni02BE ; G 608
+U 703 ; WX 602 ; N uni02BF ; G 609
+U 704 ; WX 602 ; N uni02C0 ; G 610
+U 705 ; WX 602 ; N uni02C1 ; G 611
+U 710 ; WX 602 ; N circumflex ; G 612
+U 711 ; WX 602 ; N caron ; G 613
+U 712 ; WX 602 ; N uni02C8 ; G 614
+U 713 ; WX 602 ; N uni02C9 ; G 615
+U 716 ; WX 602 ; N uni02CC ; G 616
+U 717 ; WX 602 ; N uni02CD ; G 617
+U 718 ; WX 602 ; N uni02CE ; G 618
+U 719 ; WX 602 ; N uni02CF ; G 619
+U 720 ; WX 602 ; N uni02D0 ; G 620
+U 721 ; WX 602 ; N uni02D1 ; G 621
+U 722 ; WX 602 ; N uni02D2 ; G 622
+U 723 ; WX 602 ; N uni02D3 ; G 623
+U 726 ; WX 602 ; N uni02D6 ; G 624
+U 727 ; WX 602 ; N uni02D7 ; G 625
+U 728 ; WX 602 ; N breve ; G 626
+U 729 ; WX 602 ; N dotaccent ; G 627
+U 730 ; WX 602 ; N ring ; G 628
+U 731 ; WX 602 ; N ogonek ; G 629
+U 732 ; WX 602 ; N tilde ; G 630
+U 733 ; WX 602 ; N hungarumlaut ; G 631
+U 734 ; WX 602 ; N uni02DE ; G 632
+U 736 ; WX 602 ; N uni02E0 ; G 633
+U 737 ; WX 602 ; N uni02E1 ; G 634
+U 738 ; WX 602 ; N uni02E2 ; G 635
+U 739 ; WX 602 ; N uni02E3 ; G 636
+U 740 ; WX 602 ; N uni02E4 ; G 637
+U 741 ; WX 602 ; N uni02E5 ; G 638
+U 742 ; WX 602 ; N uni02E6 ; G 639
+U 743 ; WX 602 ; N uni02E7 ; G 640
+U 744 ; WX 602 ; N uni02E8 ; G 641
+U 745 ; WX 602 ; N uni02E9 ; G 642
+U 750 ; WX 602 ; N uni02EE ; G 643
+U 755 ; WX 602 ; N uni02F3 ; G 644
+U 768 ; WX 602 ; N gravecomb ; G 645
+U 769 ; WX 602 ; N acutecomb ; G 646
+U 770 ; WX 602 ; N uni0302 ; G 647
+U 771 ; WX 602 ; N tildecomb ; G 648
+U 772 ; WX 602 ; N uni0304 ; G 649
+U 773 ; WX 602 ; N uni0305 ; G 650
+U 774 ; WX 602 ; N uni0306 ; G 651
+U 775 ; WX 602 ; N uni0307 ; G 652
+U 776 ; WX 602 ; N uni0308 ; G 653
+U 777 ; WX 602 ; N hookabovecomb ; G 654
+U 778 ; WX 602 ; N uni030A ; G 655
+U 779 ; WX 602 ; N uni030B ; G 656
+U 780 ; WX 602 ; N uni030C ; G 657
+U 781 ; WX 602 ; N uni030D ; G 658
+U 782 ; WX 602 ; N uni030E ; G 659
+U 783 ; WX 602 ; N uni030F ; G 660
+U 784 ; WX 602 ; N uni0310 ; G 661
+U 785 ; WX 602 ; N uni0311 ; G 662
+U 786 ; WX 602 ; N uni0312 ; G 663
+U 787 ; WX 602 ; N uni0313 ; G 664
+U 788 ; WX 602 ; N uni0314 ; G 665
+U 789 ; WX 602 ; N uni0315 ; G 666
+U 790 ; WX 602 ; N uni0316 ; G 667
+U 791 ; WX 602 ; N uni0317 ; G 668
+U 792 ; WX 602 ; N uni0318 ; G 669
+U 793 ; WX 602 ; N uni0319 ; G 670
+U 794 ; WX 602 ; N uni031A ; G 671
+U 795 ; WX 602 ; N uni031B ; G 672
+U 796 ; WX 602 ; N uni031C ; G 673
+U 797 ; WX 602 ; N uni031D ; G 674
+U 798 ; WX 602 ; N uni031E ; G 675
+U 799 ; WX 602 ; N uni031F ; G 676
+U 800 ; WX 602 ; N uni0320 ; G 677
+U 801 ; WX 602 ; N uni0321 ; G 678
+U 802 ; WX 602 ; N uni0322 ; G 679
+U 803 ; WX 602 ; N dotbelowcomb ; G 680
+U 804 ; WX 602 ; N uni0324 ; G 681
+U 805 ; WX 602 ; N uni0325 ; G 682
+U 806 ; WX 602 ; N uni0326 ; G 683
+U 807 ; WX 602 ; N uni0327 ; G 684
+U 808 ; WX 602 ; N uni0328 ; G 685
+U 809 ; WX 602 ; N uni0329 ; G 686
+U 810 ; WX 602 ; N uni032A ; G 687
+U 811 ; WX 602 ; N uni032B ; G 688
+U 812 ; WX 602 ; N uni032C ; G 689
+U 813 ; WX 602 ; N uni032D ; G 690
+U 814 ; WX 602 ; N uni032E ; G 691
+U 815 ; WX 602 ; N uni032F ; G 692
+U 816 ; WX 602 ; N uni0330 ; G 693
+U 817 ; WX 602 ; N uni0331 ; G 694
+U 818 ; WX 602 ; N uni0332 ; G 695
+U 819 ; WX 602 ; N uni0333 ; G 696
+U 820 ; WX 602 ; N uni0334 ; G 697
+U 821 ; WX 602 ; N uni0335 ; G 698
+U 822 ; WX 602 ; N uni0336 ; G 699
+U 823 ; WX 602 ; N uni0337 ; G 700
+U 824 ; WX 602 ; N uni0338 ; G 701
+U 825 ; WX 602 ; N uni0339 ; G 702
+U 826 ; WX 602 ; N uni033A ; G 703
+U 827 ; WX 602 ; N uni033B ; G 704
+U 828 ; WX 602 ; N uni033C ; G 705
+U 829 ; WX 602 ; N uni033D ; G 706
+U 830 ; WX 602 ; N uni033E ; G 707
+U 831 ; WX 602 ; N uni033F ; G 708
+U 835 ; WX 602 ; N uni0343 ; G 709
+U 856 ; WX 602 ; N uni0358 ; G 710
+U 865 ; WX 602 ; N uni0361 ; G 711
+U 884 ; WX 602 ; N uni0374 ; G 712
+U 885 ; WX 602 ; N uni0375 ; G 713
+U 886 ; WX 602 ; N uni0376 ; G 714
+U 887 ; WX 602 ; N uni0377 ; G 715
+U 890 ; WX 602 ; N uni037A ; G 716
+U 891 ; WX 602 ; N uni037B ; G 717
+U 892 ; WX 602 ; N uni037C ; G 718
+U 893 ; WX 602 ; N uni037D ; G 719
+U 894 ; WX 602 ; N uni037E ; G 720
+U 895 ; WX 602 ; N uni037F ; G 721
+U 900 ; WX 602 ; N tonos ; G 722
+U 901 ; WX 602 ; N dieresistonos ; G 723
+U 902 ; WX 602 ; N Alphatonos ; G 724
+U 903 ; WX 602 ; N anoteleia ; G 725
+U 904 ; WX 602 ; N Epsilontonos ; G 726
+U 905 ; WX 602 ; N Etatonos ; G 727
+U 906 ; WX 602 ; N Iotatonos ; G 728
+U 908 ; WX 602 ; N Omicrontonos ; G 729
+U 910 ; WX 602 ; N Upsilontonos ; G 730
+U 911 ; WX 602 ; N Omegatonos ; G 731
+U 912 ; WX 602 ; N iotadieresistonos ; G 732
+U 913 ; WX 602 ; N Alpha ; G 733
+U 914 ; WX 602 ; N Beta ; G 734
+U 915 ; WX 602 ; N Gamma ; G 735
+U 916 ; WX 602 ; N uni0394 ; G 736
+U 917 ; WX 602 ; N Epsilon ; G 737
+U 918 ; WX 602 ; N Zeta ; G 738
+U 919 ; WX 602 ; N Eta ; G 739
+U 920 ; WX 602 ; N Theta ; G 740
+U 921 ; WX 602 ; N Iota ; G 741
+U 922 ; WX 602 ; N Kappa ; G 742
+U 923 ; WX 602 ; N Lambda ; G 743
+U 924 ; WX 602 ; N Mu ; G 744
+U 925 ; WX 602 ; N Nu ; G 745
+U 926 ; WX 602 ; N Xi ; G 746
+U 927 ; WX 602 ; N Omicron ; G 747
+U 928 ; WX 602 ; N Pi ; G 748
+U 929 ; WX 602 ; N Rho ; G 749
+U 931 ; WX 602 ; N Sigma ; G 750
+U 932 ; WX 602 ; N Tau ; G 751
+U 933 ; WX 602 ; N Upsilon ; G 752
+U 934 ; WX 602 ; N Phi ; G 753
+U 935 ; WX 602 ; N Chi ; G 754
+U 936 ; WX 602 ; N Psi ; G 755
+U 937 ; WX 602 ; N Omega ; G 756
+U 938 ; WX 602 ; N Iotadieresis ; G 757
+U 939 ; WX 602 ; N Upsilondieresis ; G 758
+U 940 ; WX 602 ; N alphatonos ; G 759
+U 941 ; WX 602 ; N epsilontonos ; G 760
+U 942 ; WX 602 ; N etatonos ; G 761
+U 943 ; WX 602 ; N iotatonos ; G 762
+U 944 ; WX 602 ; N upsilondieresistonos ; G 763
+U 945 ; WX 602 ; N alpha ; G 764
+U 946 ; WX 602 ; N beta ; G 765
+U 947 ; WX 602 ; N gamma ; G 766
+U 948 ; WX 602 ; N delta ; G 767
+U 949 ; WX 602 ; N epsilon ; G 768
+U 950 ; WX 602 ; N zeta ; G 769
+U 951 ; WX 602 ; N eta ; G 770
+U 952 ; WX 602 ; N theta ; G 771
+U 953 ; WX 602 ; N iota ; G 772
+U 954 ; WX 602 ; N kappa ; G 773
+U 955 ; WX 602 ; N lambda ; G 774
+U 956 ; WX 602 ; N uni03BC ; G 775
+U 957 ; WX 602 ; N nu ; G 776
+U 958 ; WX 602 ; N xi ; G 777
+U 959 ; WX 602 ; N omicron ; G 778
+U 960 ; WX 602 ; N pi ; G 779
+U 961 ; WX 602 ; N rho ; G 780
+U 962 ; WX 602 ; N sigma1 ; G 781
+U 963 ; WX 602 ; N sigma ; G 782
+U 964 ; WX 602 ; N tau ; G 783
+U 965 ; WX 602 ; N upsilon ; G 784
+U 966 ; WX 602 ; N phi ; G 785
+U 967 ; WX 602 ; N chi ; G 786
+U 968 ; WX 602 ; N psi ; G 787
+U 969 ; WX 602 ; N omega ; G 788
+U 970 ; WX 602 ; N iotadieresis ; G 789
+U 971 ; WX 602 ; N upsilondieresis ; G 790
+U 972 ; WX 602 ; N omicrontonos ; G 791
+U 973 ; WX 602 ; N upsilontonos ; G 792
+U 974 ; WX 602 ; N omegatonos ; G 793
+U 976 ; WX 602 ; N uni03D0 ; G 794
+U 977 ; WX 602 ; N theta1 ; G 795
+U 978 ; WX 602 ; N Upsilon1 ; G 796
+U 979 ; WX 602 ; N uni03D3 ; G 797
+U 980 ; WX 602 ; N uni03D4 ; G 798
+U 981 ; WX 602 ; N phi1 ; G 799
+U 982 ; WX 602 ; N omega1 ; G 800
+U 983 ; WX 602 ; N uni03D7 ; G 801
+U 984 ; WX 602 ; N uni03D8 ; G 802
+U 985 ; WX 602 ; N uni03D9 ; G 803
+U 986 ; WX 602 ; N uni03DA ; G 804
+U 987 ; WX 602 ; N uni03DB ; G 805
+U 988 ; WX 602 ; N uni03DC ; G 806
+U 989 ; WX 602 ; N uni03DD ; G 807
+U 990 ; WX 602 ; N uni03DE ; G 808
+U 991 ; WX 602 ; N uni03DF ; G 809
+U 992 ; WX 602 ; N uni03E0 ; G 810
+U 993 ; WX 602 ; N uni03E1 ; G 811
+U 1008 ; WX 602 ; N uni03F0 ; G 812
+U 1009 ; WX 602 ; N uni03F1 ; G 813
+U 1010 ; WX 602 ; N uni03F2 ; G 814
+U 1011 ; WX 602 ; N uni03F3 ; G 815
+U 1012 ; WX 602 ; N uni03F4 ; G 816
+U 1013 ; WX 602 ; N uni03F5 ; G 817
+U 1014 ; WX 602 ; N uni03F6 ; G 818
+U 1015 ; WX 602 ; N uni03F7 ; G 819
+U 1016 ; WX 602 ; N uni03F8 ; G 820
+U 1017 ; WX 602 ; N uni03F9 ; G 821
+U 1018 ; WX 602 ; N uni03FA ; G 822
+U 1019 ; WX 602 ; N uni03FB ; G 823
+U 1020 ; WX 602 ; N uni03FC ; G 824
+U 1021 ; WX 602 ; N uni03FD ; G 825
+U 1022 ; WX 602 ; N uni03FE ; G 826
+U 1023 ; WX 602 ; N uni03FF ; G 827
+U 1024 ; WX 602 ; N uni0400 ; G 828
+U 1025 ; WX 602 ; N uni0401 ; G 829
+U 1026 ; WX 602 ; N uni0402 ; G 830
+U 1027 ; WX 602 ; N uni0403 ; G 831
+U 1028 ; WX 602 ; N uni0404 ; G 832
+U 1029 ; WX 602 ; N uni0405 ; G 833
+U 1030 ; WX 602 ; N uni0406 ; G 834
+U 1031 ; WX 602 ; N uni0407 ; G 835
+U 1032 ; WX 602 ; N uni0408 ; G 836
+U 1033 ; WX 602 ; N uni0409 ; G 837
+U 1034 ; WX 602 ; N uni040A ; G 838
+U 1035 ; WX 602 ; N uni040B ; G 839
+U 1036 ; WX 602 ; N uni040C ; G 840
+U 1037 ; WX 602 ; N uni040D ; G 841
+U 1038 ; WX 602 ; N uni040E ; G 842
+U 1039 ; WX 602 ; N uni040F ; G 843
+U 1040 ; WX 602 ; N uni0410 ; G 844
+U 1041 ; WX 602 ; N uni0411 ; G 845
+U 1042 ; WX 602 ; N uni0412 ; G 846
+U 1043 ; WX 602 ; N uni0413 ; G 847
+U 1044 ; WX 602 ; N uni0414 ; G 848
+U 1045 ; WX 602 ; N uni0415 ; G 849
+U 1046 ; WX 602 ; N uni0416 ; G 850
+U 1047 ; WX 602 ; N uni0417 ; G 851
+U 1048 ; WX 602 ; N uni0418 ; G 852
+U 1049 ; WX 602 ; N uni0419 ; G 853
+U 1050 ; WX 602 ; N uni041A ; G 854
+U 1051 ; WX 602 ; N uni041B ; G 855
+U 1052 ; WX 602 ; N uni041C ; G 856
+U 1053 ; WX 602 ; N uni041D ; G 857
+U 1054 ; WX 602 ; N uni041E ; G 858
+U 1055 ; WX 602 ; N uni041F ; G 859
+U 1056 ; WX 602 ; N uni0420 ; G 860
+U 1057 ; WX 602 ; N uni0421 ; G 861
+U 1058 ; WX 602 ; N uni0422 ; G 862
+U 1059 ; WX 602 ; N uni0423 ; G 863
+U 1060 ; WX 602 ; N uni0424 ; G 864
+U 1061 ; WX 602 ; N uni0425 ; G 865
+U 1062 ; WX 602 ; N uni0426 ; G 866
+U 1063 ; WX 602 ; N uni0427 ; G 867
+U 1064 ; WX 602 ; N uni0428 ; G 868
+U 1065 ; WX 602 ; N uni0429 ; G 869
+U 1066 ; WX 602 ; N uni042A ; G 870
+U 1067 ; WX 602 ; N uni042B ; G 871
+U 1068 ; WX 602 ; N uni042C ; G 872
+U 1069 ; WX 602 ; N uni042D ; G 873
+U 1070 ; WX 602 ; N uni042E ; G 874
+U 1071 ; WX 602 ; N uni042F ; G 875
+U 1072 ; WX 602 ; N uni0430 ; G 876
+U 1073 ; WX 602 ; N uni0431 ; G 877
+U 1074 ; WX 602 ; N uni0432 ; G 878
+U 1075 ; WX 602 ; N uni0433 ; G 879
+U 1076 ; WX 602 ; N uni0434 ; G 880
+U 1077 ; WX 602 ; N uni0435 ; G 881
+U 1078 ; WX 602 ; N uni0436 ; G 882
+U 1079 ; WX 602 ; N uni0437 ; G 883
+U 1080 ; WX 602 ; N uni0438 ; G 884
+U 1081 ; WX 602 ; N uni0439 ; G 885
+U 1082 ; WX 602 ; N uni043A ; G 886
+U 1083 ; WX 602 ; N uni043B ; G 887
+U 1084 ; WX 602 ; N uni043C ; G 888
+U 1085 ; WX 602 ; N uni043D ; G 889
+U 1086 ; WX 602 ; N uni043E ; G 890
+U 1087 ; WX 602 ; N uni043F ; G 891
+U 1088 ; WX 602 ; N uni0440 ; G 892
+U 1089 ; WX 602 ; N uni0441 ; G 893
+U 1090 ; WX 602 ; N uni0442 ; G 894
+U 1091 ; WX 602 ; N uni0443 ; G 895
+U 1092 ; WX 602 ; N uni0444 ; G 896
+U 1093 ; WX 602 ; N uni0445 ; G 897
+U 1094 ; WX 602 ; N uni0446 ; G 898
+U 1095 ; WX 602 ; N uni0447 ; G 899
+U 1096 ; WX 602 ; N uni0448 ; G 900
+U 1097 ; WX 602 ; N uni0449 ; G 901
+U 1098 ; WX 602 ; N uni044A ; G 902
+U 1099 ; WX 602 ; N uni044B ; G 903
+U 1100 ; WX 602 ; N uni044C ; G 904
+U 1101 ; WX 602 ; N uni044D ; G 905
+U 1102 ; WX 602 ; N uni044E ; G 906
+U 1103 ; WX 602 ; N uni044F ; G 907
+U 1104 ; WX 602 ; N uni0450 ; G 908
+U 1105 ; WX 602 ; N uni0451 ; G 909
+U 1106 ; WX 602 ; N uni0452 ; G 910
+U 1107 ; WX 602 ; N uni0453 ; G 911
+U 1108 ; WX 602 ; N uni0454 ; G 912
+U 1109 ; WX 602 ; N uni0455 ; G 913
+U 1110 ; WX 602 ; N uni0456 ; G 914
+U 1111 ; WX 602 ; N uni0457 ; G 915
+U 1112 ; WX 602 ; N uni0458 ; G 916
+U 1113 ; WX 602 ; N uni0459 ; G 917
+U 1114 ; WX 602 ; N uni045A ; G 918
+U 1115 ; WX 602 ; N uni045B ; G 919
+U 1116 ; WX 602 ; N uni045C ; G 920
+U 1117 ; WX 602 ; N uni045D ; G 921
+U 1118 ; WX 602 ; N uni045E ; G 922
+U 1119 ; WX 602 ; N uni045F ; G 923
+U 1122 ; WX 602 ; N uni0462 ; G 924
+U 1123 ; WX 602 ; N uni0463 ; G 925
+U 1138 ; WX 602 ; N uni0472 ; G 926
+U 1139 ; WX 602 ; N uni0473 ; G 927
+U 1168 ; WX 602 ; N uni0490 ; G 928
+U 1169 ; WX 602 ; N uni0491 ; G 929
+U 1170 ; WX 602 ; N uni0492 ; G 930
+U 1171 ; WX 602 ; N uni0493 ; G 931
+U 1172 ; WX 602 ; N uni0494 ; G 932
+U 1173 ; WX 602 ; N uni0495 ; G 933
+U 1174 ; WX 602 ; N uni0496 ; G 934
+U 1175 ; WX 602 ; N uni0497 ; G 935
+U 1176 ; WX 602 ; N uni0498 ; G 936
+U 1177 ; WX 602 ; N uni0499 ; G 937
+U 1178 ; WX 602 ; N uni049A ; G 938
+U 1179 ; WX 602 ; N uni049B ; G 939
+U 1186 ; WX 602 ; N uni04A2 ; G 940
+U 1187 ; WX 602 ; N uni04A3 ; G 941
+U 1188 ; WX 602 ; N uni04A4 ; G 942
+U 1189 ; WX 602 ; N uni04A5 ; G 943
+U 1194 ; WX 602 ; N uni04AA ; G 944
+U 1195 ; WX 602 ; N uni04AB ; G 945
+U 1196 ; WX 602 ; N uni04AC ; G 946
+U 1197 ; WX 602 ; N uni04AD ; G 947
+U 1198 ; WX 602 ; N uni04AE ; G 948
+U 1199 ; WX 602 ; N uni04AF ; G 949
+U 1200 ; WX 602 ; N uni04B0 ; G 950
+U 1201 ; WX 602 ; N uni04B1 ; G 951
+U 1202 ; WX 602 ; N uni04B2 ; G 952
+U 1203 ; WX 602 ; N uni04B3 ; G 953
+U 1210 ; WX 602 ; N uni04BA ; G 954
+U 1211 ; WX 602 ; N uni04BB ; G 955
+U 1216 ; WX 602 ; N uni04C0 ; G 956
+U 1217 ; WX 602 ; N uni04C1 ; G 957
+U 1218 ; WX 602 ; N uni04C2 ; G 958
+U 1219 ; WX 602 ; N uni04C3 ; G 959
+U 1220 ; WX 602 ; N uni04C4 ; G 960
+U 1223 ; WX 602 ; N uni04C7 ; G 961
+U 1224 ; WX 602 ; N uni04C8 ; G 962
+U 1227 ; WX 602 ; N uni04CB ; G 963
+U 1228 ; WX 602 ; N uni04CC ; G 964
+U 1231 ; WX 602 ; N uni04CF ; G 965
+U 1232 ; WX 602 ; N uni04D0 ; G 966
+U 1233 ; WX 602 ; N uni04D1 ; G 967
+U 1234 ; WX 602 ; N uni04D2 ; G 968
+U 1235 ; WX 602 ; N uni04D3 ; G 969
+U 1236 ; WX 602 ; N uni04D4 ; G 970
+U 1237 ; WX 602 ; N uni04D5 ; G 971
+U 1238 ; WX 602 ; N uni04D6 ; G 972
+U 1239 ; WX 602 ; N uni04D7 ; G 973
+U 1240 ; WX 602 ; N uni04D8 ; G 974
+U 1241 ; WX 602 ; N uni04D9 ; G 975
+U 1242 ; WX 602 ; N uni04DA ; G 976
+U 1243 ; WX 602 ; N uni04DB ; G 977
+U 1244 ; WX 602 ; N uni04DC ; G 978
+U 1245 ; WX 602 ; N uni04DD ; G 979
+U 1246 ; WX 602 ; N uni04DE ; G 980
+U 1247 ; WX 602 ; N uni04DF ; G 981
+U 1248 ; WX 602 ; N uni04E0 ; G 982
+U 1249 ; WX 602 ; N uni04E1 ; G 983
+U 1250 ; WX 602 ; N uni04E2 ; G 984
+U 1251 ; WX 602 ; N uni04E3 ; G 985
+U 1252 ; WX 602 ; N uni04E4 ; G 986
+U 1253 ; WX 602 ; N uni04E5 ; G 987
+U 1254 ; WX 602 ; N uni04E6 ; G 988
+U 1255 ; WX 602 ; N uni04E7 ; G 989
+U 1256 ; WX 602 ; N uni04E8 ; G 990
+U 1257 ; WX 602 ; N uni04E9 ; G 991
+U 1258 ; WX 602 ; N uni04EA ; G 992
+U 1259 ; WX 602 ; N uni04EB ; G 993
+U 1260 ; WX 602 ; N uni04EC ; G 994
+U 1261 ; WX 602 ; N uni04ED ; G 995
+U 1262 ; WX 602 ; N uni04EE ; G 996
+U 1263 ; WX 602 ; N uni04EF ; G 997
+U 1264 ; WX 602 ; N uni04F0 ; G 998
+U 1265 ; WX 602 ; N uni04F1 ; G 999
+U 1266 ; WX 602 ; N uni04F2 ; G 1000
+U 1267 ; WX 602 ; N uni04F3 ; G 1001
+U 1268 ; WX 602 ; N uni04F4 ; G 1002
+U 1269 ; WX 602 ; N uni04F5 ; G 1003
+U 1270 ; WX 602 ; N uni04F6 ; G 1004
+U 1271 ; WX 602 ; N uni04F7 ; G 1005
+U 1272 ; WX 602 ; N uni04F8 ; G 1006
+U 1273 ; WX 602 ; N uni04F9 ; G 1007
+U 1296 ; WX 602 ; N uni0510 ; G 1008
+U 1297 ; WX 602 ; N uni0511 ; G 1009
+U 1306 ; WX 602 ; N uni051A ; G 1010
+U 1307 ; WX 602 ; N uni051B ; G 1011
+U 1308 ; WX 602 ; N uni051C ; G 1012
+U 1309 ; WX 602 ; N uni051D ; G 1013
+U 1329 ; WX 602 ; N uni0531 ; G 1014
+U 1330 ; WX 602 ; N uni0532 ; G 1015
+U 1331 ; WX 602 ; N uni0533 ; G 1016
+U 1332 ; WX 602 ; N uni0534 ; G 1017
+U 1333 ; WX 602 ; N uni0535 ; G 1018
+U 1334 ; WX 602 ; N uni0536 ; G 1019
+U 1335 ; WX 602 ; N uni0537 ; G 1020
+U 1336 ; WX 602 ; N uni0538 ; G 1021
+U 1337 ; WX 602 ; N uni0539 ; G 1022
+U 1338 ; WX 602 ; N uni053A ; G 1023
+U 1339 ; WX 602 ; N uni053B ; G 1024
+U 1340 ; WX 602 ; N uni053C ; G 1025
+U 1341 ; WX 602 ; N uni053D ; G 1026
+U 1342 ; WX 602 ; N uni053E ; G 1027
+U 1343 ; WX 602 ; N uni053F ; G 1028
+U 1344 ; WX 602 ; N uni0540 ; G 1029
+U 1345 ; WX 602 ; N uni0541 ; G 1030
+U 1346 ; WX 602 ; N uni0542 ; G 1031
+U 1347 ; WX 602 ; N uni0543 ; G 1032
+U 1348 ; WX 602 ; N uni0544 ; G 1033
+U 1349 ; WX 602 ; N uni0545 ; G 1034
+U 1350 ; WX 602 ; N uni0546 ; G 1035
+U 1351 ; WX 602 ; N uni0547 ; G 1036
+U 1352 ; WX 602 ; N uni0548 ; G 1037
+U 1353 ; WX 602 ; N uni0549 ; G 1038
+U 1354 ; WX 602 ; N uni054A ; G 1039
+U 1355 ; WX 602 ; N uni054B ; G 1040
+U 1356 ; WX 602 ; N uni054C ; G 1041
+U 1357 ; WX 602 ; N uni054D ; G 1042
+U 1358 ; WX 602 ; N uni054E ; G 1043
+U 1359 ; WX 602 ; N uni054F ; G 1044
+U 1360 ; WX 602 ; N uni0550 ; G 1045
+U 1361 ; WX 602 ; N uni0551 ; G 1046
+U 1362 ; WX 602 ; N uni0552 ; G 1047
+U 1363 ; WX 602 ; N uni0553 ; G 1048
+U 1364 ; WX 602 ; N uni0554 ; G 1049
+U 1365 ; WX 602 ; N uni0555 ; G 1050
+U 1366 ; WX 602 ; N uni0556 ; G 1051
+U 1369 ; WX 602 ; N uni0559 ; G 1052
+U 1370 ; WX 602 ; N uni055A ; G 1053
+U 1371 ; WX 602 ; N uni055B ; G 1054
+U 1372 ; WX 602 ; N uni055C ; G 1055
+U 1373 ; WX 602 ; N uni055D ; G 1056
+U 1374 ; WX 602 ; N uni055E ; G 1057
+U 1375 ; WX 602 ; N uni055F ; G 1058
+U 1377 ; WX 602 ; N uni0561 ; G 1059
+U 1378 ; WX 602 ; N uni0562 ; G 1060
+U 1379 ; WX 602 ; N uni0563 ; G 1061
+U 1380 ; WX 602 ; N uni0564 ; G 1062
+U 1381 ; WX 602 ; N uni0565 ; G 1063
+U 1382 ; WX 602 ; N uni0566 ; G 1064
+U 1383 ; WX 602 ; N uni0567 ; G 1065
+U 1384 ; WX 602 ; N uni0568 ; G 1066
+U 1385 ; WX 602 ; N uni0569 ; G 1067
+U 1386 ; WX 602 ; N uni056A ; G 1068
+U 1387 ; WX 602 ; N uni056B ; G 1069
+U 1388 ; WX 602 ; N uni056C ; G 1070
+U 1389 ; WX 602 ; N uni056D ; G 1071
+U 1390 ; WX 602 ; N uni056E ; G 1072
+U 1391 ; WX 602 ; N uni056F ; G 1073
+U 1392 ; WX 602 ; N uni0570 ; G 1074
+U 1393 ; WX 602 ; N uni0571 ; G 1075
+U 1394 ; WX 602 ; N uni0572 ; G 1076
+U 1395 ; WX 602 ; N uni0573 ; G 1077
+U 1396 ; WX 602 ; N uni0574 ; G 1078
+U 1397 ; WX 602 ; N uni0575 ; G 1079
+U 1398 ; WX 602 ; N uni0576 ; G 1080
+U 1399 ; WX 602 ; N uni0577 ; G 1081
+U 1400 ; WX 602 ; N uni0578 ; G 1082
+U 1401 ; WX 602 ; N uni0579 ; G 1083
+U 1402 ; WX 602 ; N uni057A ; G 1084
+U 1403 ; WX 602 ; N uni057B ; G 1085
+U 1404 ; WX 602 ; N uni057C ; G 1086
+U 1405 ; WX 602 ; N uni057D ; G 1087
+U 1406 ; WX 602 ; N uni057E ; G 1088
+U 1407 ; WX 602 ; N uni057F ; G 1089
+U 1408 ; WX 602 ; N uni0580 ; G 1090
+U 1409 ; WX 602 ; N uni0581 ; G 1091
+U 1410 ; WX 602 ; N uni0582 ; G 1092
+U 1411 ; WX 602 ; N uni0583 ; G 1093
+U 1412 ; WX 602 ; N uni0584 ; G 1094
+U 1413 ; WX 602 ; N uni0585 ; G 1095
+U 1414 ; WX 602 ; N uni0586 ; G 1096
+U 1415 ; WX 602 ; N uni0587 ; G 1097
+U 1417 ; WX 602 ; N uni0589 ; G 1098
+U 1418 ; WX 602 ; N uni058A ; G 1099
+U 3647 ; WX 602 ; N uni0E3F ; G 1100
+U 3713 ; WX 602 ; N uni0E81 ; G 1101
+U 3714 ; WX 602 ; N uni0E82 ; G 1102
+U 3716 ; WX 602 ; N uni0E84 ; G 1103
+U 3719 ; WX 602 ; N uni0E87 ; G 1104
+U 3720 ; WX 602 ; N uni0E88 ; G 1105
+U 3722 ; WX 602 ; N uni0E8A ; G 1106
+U 3725 ; WX 602 ; N uni0E8D ; G 1107
+U 3732 ; WX 602 ; N uni0E94 ; G 1108
+U 3733 ; WX 602 ; N uni0E95 ; G 1109
+U 3734 ; WX 602 ; N uni0E96 ; G 1110
+U 3735 ; WX 602 ; N uni0E97 ; G 1111
+U 3737 ; WX 602 ; N uni0E99 ; G 1112
+U 3738 ; WX 602 ; N uni0E9A ; G 1113
+U 3739 ; WX 602 ; N uni0E9B ; G 1114
+U 3740 ; WX 602 ; N uni0E9C ; G 1115
+U 3741 ; WX 602 ; N uni0E9D ; G 1116
+U 3742 ; WX 602 ; N uni0E9E ; G 1117
+U 3743 ; WX 602 ; N uni0E9F ; G 1118
+U 3745 ; WX 602 ; N uni0EA1 ; G 1119
+U 3746 ; WX 602 ; N uni0EA2 ; G 1120
+U 3747 ; WX 602 ; N uni0EA3 ; G 1121
+U 3749 ; WX 602 ; N uni0EA5 ; G 1122
+U 3751 ; WX 602 ; N uni0EA7 ; G 1123
+U 3754 ; WX 602 ; N uni0EAA ; G 1124
+U 3755 ; WX 602 ; N uni0EAB ; G 1125
+U 3757 ; WX 602 ; N uni0EAD ; G 1126
+U 3758 ; WX 602 ; N uni0EAE ; G 1127
+U 3759 ; WX 602 ; N uni0EAF ; G 1128
+U 3760 ; WX 602 ; N uni0EB0 ; G 1129
+U 3761 ; WX 602 ; N uni0EB1 ; G 1130
+U 3762 ; WX 602 ; N uni0EB2 ; G 1131
+U 3763 ; WX 602 ; N uni0EB3 ; G 1132
+U 3764 ; WX 602 ; N uni0EB4 ; G 1133
+U 3765 ; WX 602 ; N uni0EB5 ; G 1134
+U 3766 ; WX 602 ; N uni0EB6 ; G 1135
+U 3767 ; WX 602 ; N uni0EB7 ; G 1136
+U 3768 ; WX 602 ; N uni0EB8 ; G 1137
+U 3769 ; WX 602 ; N uni0EB9 ; G 1138
+U 3771 ; WX 602 ; N uni0EBB ; G 1139
+U 3772 ; WX 602 ; N uni0EBC ; G 1140
+U 3784 ; WX 602 ; N uni0EC8 ; G 1141
+U 3785 ; WX 602 ; N uni0EC9 ; G 1142
+U 3786 ; WX 602 ; N uni0ECA ; G 1143
+U 3787 ; WX 602 ; N uni0ECB ; G 1144
+U 3788 ; WX 602 ; N uni0ECC ; G 1145
+U 3789 ; WX 602 ; N uni0ECD ; G 1146
+U 4304 ; WX 602 ; N uni10D0 ; G 1147
+U 4305 ; WX 602 ; N uni10D1 ; G 1148
+U 4306 ; WX 602 ; N uni10D2 ; G 1149
+U 4307 ; WX 602 ; N uni10D3 ; G 1150
+U 4308 ; WX 602 ; N uni10D4 ; G 1151
+U 4309 ; WX 602 ; N uni10D5 ; G 1152
+U 4310 ; WX 602 ; N uni10D6 ; G 1153
+U 4311 ; WX 602 ; N uni10D7 ; G 1154
+U 4312 ; WX 602 ; N uni10D8 ; G 1155
+U 4313 ; WX 602 ; N uni10D9 ; G 1156
+U 4314 ; WX 602 ; N uni10DA ; G 1157
+U 4315 ; WX 602 ; N uni10DB ; G 1158
+U 4316 ; WX 602 ; N uni10DC ; G 1159
+U 4317 ; WX 602 ; N uni10DD ; G 1160
+U 4318 ; WX 602 ; N uni10DE ; G 1161
+U 4319 ; WX 602 ; N uni10DF ; G 1162
+U 4320 ; WX 602 ; N uni10E0 ; G 1163
+U 4321 ; WX 602 ; N uni10E1 ; G 1164
+U 4322 ; WX 602 ; N uni10E2 ; G 1165
+U 4323 ; WX 602 ; N uni10E3 ; G 1166
+U 4324 ; WX 602 ; N uni10E4 ; G 1167
+U 4325 ; WX 602 ; N uni10E5 ; G 1168
+U 4326 ; WX 602 ; N uni10E6 ; G 1169
+U 4327 ; WX 602 ; N uni10E7 ; G 1170
+U 4328 ; WX 602 ; N uni10E8 ; G 1171
+U 4329 ; WX 602 ; N uni10E9 ; G 1172
+U 4330 ; WX 602 ; N uni10EA ; G 1173
+U 4331 ; WX 602 ; N uni10EB ; G 1174
+U 4332 ; WX 602 ; N uni10EC ; G 1175
+U 4333 ; WX 602 ; N uni10ED ; G 1176
+U 4334 ; WX 602 ; N uni10EE ; G 1177
+U 4335 ; WX 602 ; N uni10EF ; G 1178
+U 4336 ; WX 602 ; N uni10F0 ; G 1179
+U 4337 ; WX 602 ; N uni10F1 ; G 1180
+U 4338 ; WX 602 ; N uni10F2 ; G 1181
+U 4339 ; WX 602 ; N uni10F3 ; G 1182
+U 4340 ; WX 602 ; N uni10F4 ; G 1183
+U 4341 ; WX 602 ; N uni10F5 ; G 1184
+U 4342 ; WX 602 ; N uni10F6 ; G 1185
+U 4343 ; WX 602 ; N uni10F7 ; G 1186
+U 4344 ; WX 602 ; N uni10F8 ; G 1187
+U 4345 ; WX 602 ; N uni10F9 ; G 1188
+U 4346 ; WX 602 ; N uni10FA ; G 1189
+U 4347 ; WX 602 ; N uni10FB ; G 1190
+U 4348 ; WX 602 ; N uni10FC ; G 1191
+U 7426 ; WX 602 ; N uni1D02 ; G 1192
+U 7432 ; WX 602 ; N uni1D08 ; G 1193
+U 7433 ; WX 602 ; N uni1D09 ; G 1194
+U 7444 ; WX 602 ; N uni1D14 ; G 1195
+U 7446 ; WX 602 ; N uni1D16 ; G 1196
+U 7447 ; WX 602 ; N uni1D17 ; G 1197
+U 7453 ; WX 602 ; N uni1D1D ; G 1198
+U 7454 ; WX 602 ; N uni1D1E ; G 1199
+U 7455 ; WX 602 ; N uni1D1F ; G 1200
+U 7468 ; WX 602 ; N uni1D2C ; G 1201
+U 7469 ; WX 602 ; N uni1D2D ; G 1202
+U 7470 ; WX 602 ; N uni1D2E ; G 1203
+U 7472 ; WX 602 ; N uni1D30 ; G 1204
+U 7473 ; WX 602 ; N uni1D31 ; G 1205
+U 7474 ; WX 602 ; N uni1D32 ; G 1206
+U 7475 ; WX 602 ; N uni1D33 ; G 1207
+U 7476 ; WX 602 ; N uni1D34 ; G 1208
+U 7477 ; WX 602 ; N uni1D35 ; G 1209
+U 7478 ; WX 602 ; N uni1D36 ; G 1210
+U 7479 ; WX 602 ; N uni1D37 ; G 1211
+U 7480 ; WX 602 ; N uni1D38 ; G 1212
+U 7481 ; WX 602 ; N uni1D39 ; G 1213
+U 7482 ; WX 602 ; N uni1D3A ; G 1214
+U 7483 ; WX 602 ; N uni1D3B ; G 1215
+U 7484 ; WX 602 ; N uni1D3C ; G 1216
+U 7485 ; WX 602 ; N uni1D3D ; G 1217
+U 7486 ; WX 602 ; N uni1D3E ; G 1218
+U 7487 ; WX 602 ; N uni1D3F ; G 1219
+U 7488 ; WX 602 ; N uni1D40 ; G 1220
+U 7489 ; WX 602 ; N uni1D41 ; G 1221
+U 7490 ; WX 602 ; N uni1D42 ; G 1222
+U 7491 ; WX 602 ; N uni1D43 ; G 1223
+U 7492 ; WX 602 ; N uni1D44 ; G 1224
+U 7493 ; WX 602 ; N uni1D45 ; G 1225
+U 7494 ; WX 602 ; N uni1D46 ; G 1226
+U 7495 ; WX 602 ; N uni1D47 ; G 1227
+U 7496 ; WX 602 ; N uni1D48 ; G 1228
+U 7497 ; WX 602 ; N uni1D49 ; G 1229
+U 7498 ; WX 602 ; N uni1D4A ; G 1230
+U 7499 ; WX 602 ; N uni1D4B ; G 1231
+U 7500 ; WX 602 ; N uni1D4C ; G 1232
+U 7501 ; WX 602 ; N uni1D4D ; G 1233
+U 7502 ; WX 602 ; N uni1D4E ; G 1234
+U 7503 ; WX 602 ; N uni1D4F ; G 1235
+U 7504 ; WX 602 ; N uni1D50 ; G 1236
+U 7505 ; WX 602 ; N uni1D51 ; G 1237
+U 7506 ; WX 602 ; N uni1D52 ; G 1238
+U 7507 ; WX 602 ; N uni1D53 ; G 1239
+U 7508 ; WX 602 ; N uni1D54 ; G 1240
+U 7509 ; WX 602 ; N uni1D55 ; G 1241
+U 7510 ; WX 602 ; N uni1D56 ; G 1242
+U 7511 ; WX 602 ; N uni1D57 ; G 1243
+U 7512 ; WX 602 ; N uni1D58 ; G 1244
+U 7513 ; WX 602 ; N uni1D59 ; G 1245
+U 7514 ; WX 602 ; N uni1D5A ; G 1246
+U 7515 ; WX 602 ; N uni1D5B ; G 1247
+U 7522 ; WX 602 ; N uni1D62 ; G 1248
+U 7523 ; WX 602 ; N uni1D63 ; G 1249
+U 7524 ; WX 602 ; N uni1D64 ; G 1250
+U 7525 ; WX 602 ; N uni1D65 ; G 1251
+U 7543 ; WX 602 ; N uni1D77 ; G 1252
+U 7544 ; WX 602 ; N uni1D78 ; G 1253
+U 7547 ; WX 602 ; N uni1D7B ; G 1254
+U 7557 ; WX 602 ; N uni1D85 ; G 1255
+U 7579 ; WX 602 ; N uni1D9B ; G 1256
+U 7580 ; WX 602 ; N uni1D9C ; G 1257
+U 7581 ; WX 602 ; N uni1D9D ; G 1258
+U 7582 ; WX 602 ; N uni1D9E ; G 1259
+U 7583 ; WX 602 ; N uni1D9F ; G 1260
+U 7584 ; WX 602 ; N uni1DA0 ; G 1261
+U 7585 ; WX 602 ; N uni1DA1 ; G 1262
+U 7586 ; WX 602 ; N uni1DA2 ; G 1263
+U 7587 ; WX 602 ; N uni1DA3 ; G 1264
+U 7588 ; WX 602 ; N uni1DA4 ; G 1265
+U 7589 ; WX 602 ; N uni1DA5 ; G 1266
+U 7590 ; WX 602 ; N uni1DA6 ; G 1267
+U 7591 ; WX 602 ; N uni1DA7 ; G 1268
+U 7592 ; WX 602 ; N uni1DA8 ; G 1269
+U 7593 ; WX 602 ; N uni1DA9 ; G 1270
+U 7594 ; WX 602 ; N uni1DAA ; G 1271
+U 7595 ; WX 602 ; N uni1DAB ; G 1272
+U 7596 ; WX 602 ; N uni1DAC ; G 1273
+U 7597 ; WX 602 ; N uni1DAD ; G 1274
+U 7598 ; WX 602 ; N uni1DAE ; G 1275
+U 7599 ; WX 602 ; N uni1DAF ; G 1276
+U 7600 ; WX 602 ; N uni1DB0 ; G 1277
+U 7601 ; WX 602 ; N uni1DB1 ; G 1278
+U 7602 ; WX 602 ; N uni1DB2 ; G 1279
+U 7603 ; WX 602 ; N uni1DB3 ; G 1280
+U 7604 ; WX 602 ; N uni1DB4 ; G 1281
+U 7605 ; WX 602 ; N uni1DB5 ; G 1282
+U 7606 ; WX 602 ; N uni1DB6 ; G 1283
+U 7607 ; WX 602 ; N uni1DB7 ; G 1284
+U 7609 ; WX 602 ; N uni1DB9 ; G 1285
+U 7610 ; WX 602 ; N uni1DBA ; G 1286
+U 7611 ; WX 602 ; N uni1DBB ; G 1287
+U 7612 ; WX 602 ; N uni1DBC ; G 1288
+U 7613 ; WX 602 ; N uni1DBD ; G 1289
+U 7614 ; WX 602 ; N uni1DBE ; G 1290
+U 7615 ; WX 602 ; N uni1DBF ; G 1291
+U 7680 ; WX 602 ; N uni1E00 ; G 1292
+U 7681 ; WX 602 ; N uni1E01 ; G 1293
+U 7682 ; WX 602 ; N uni1E02 ; G 1294
+U 7683 ; WX 602 ; N uni1E03 ; G 1295
+U 7684 ; WX 602 ; N uni1E04 ; G 1296
+U 7685 ; WX 602 ; N uni1E05 ; G 1297
+U 7686 ; WX 602 ; N uni1E06 ; G 1298
+U 7687 ; WX 602 ; N uni1E07 ; G 1299
+U 7688 ; WX 602 ; N uni1E08 ; G 1300
+U 7689 ; WX 602 ; N uni1E09 ; G 1301
+U 7690 ; WX 602 ; N uni1E0A ; G 1302
+U 7691 ; WX 602 ; N uni1E0B ; G 1303
+U 7692 ; WX 602 ; N uni1E0C ; G 1304
+U 7693 ; WX 602 ; N uni1E0D ; G 1305
+U 7694 ; WX 602 ; N uni1E0E ; G 1306
+U 7695 ; WX 602 ; N uni1E0F ; G 1307
+U 7696 ; WX 602 ; N uni1E10 ; G 1308
+U 7697 ; WX 602 ; N uni1E11 ; G 1309
+U 7698 ; WX 602 ; N uni1E12 ; G 1310
+U 7699 ; WX 602 ; N uni1E13 ; G 1311
+U 7704 ; WX 602 ; N uni1E18 ; G 1312
+U 7705 ; WX 602 ; N uni1E19 ; G 1313
+U 7706 ; WX 602 ; N uni1E1A ; G 1314
+U 7707 ; WX 602 ; N uni1E1B ; G 1315
+U 7708 ; WX 602 ; N uni1E1C ; G 1316
+U 7709 ; WX 602 ; N uni1E1D ; G 1317
+U 7710 ; WX 602 ; N uni1E1E ; G 1318
+U 7711 ; WX 602 ; N uni1E1F ; G 1319
+U 7712 ; WX 602 ; N uni1E20 ; G 1320
+U 7713 ; WX 602 ; N uni1E21 ; G 1321
+U 7714 ; WX 602 ; N uni1E22 ; G 1322
+U 7715 ; WX 602 ; N uni1E23 ; G 1323
+U 7716 ; WX 602 ; N uni1E24 ; G 1324
+U 7717 ; WX 602 ; N uni1E25 ; G 1325
+U 7718 ; WX 602 ; N uni1E26 ; G 1326
+U 7719 ; WX 602 ; N uni1E27 ; G 1327
+U 7720 ; WX 602 ; N uni1E28 ; G 1328
+U 7721 ; WX 602 ; N uni1E29 ; G 1329
+U 7722 ; WX 602 ; N uni1E2A ; G 1330
+U 7723 ; WX 602 ; N uni1E2B ; G 1331
+U 7724 ; WX 602 ; N uni1E2C ; G 1332
+U 7725 ; WX 602 ; N uni1E2D ; G 1333
+U 7728 ; WX 602 ; N uni1E30 ; G 1334
+U 7729 ; WX 602 ; N uni1E31 ; G 1335
+U 7730 ; WX 602 ; N uni1E32 ; G 1336
+U 7731 ; WX 602 ; N uni1E33 ; G 1337
+U 7732 ; WX 602 ; N uni1E34 ; G 1338
+U 7733 ; WX 602 ; N uni1E35 ; G 1339
+U 7734 ; WX 602 ; N uni1E36 ; G 1340
+U 7735 ; WX 602 ; N uni1E37 ; G 1341
+U 7736 ; WX 602 ; N uni1E38 ; G 1342
+U 7737 ; WX 602 ; N uni1E39 ; G 1343
+U 7738 ; WX 602 ; N uni1E3A ; G 1344
+U 7739 ; WX 602 ; N uni1E3B ; G 1345
+U 7740 ; WX 602 ; N uni1E3C ; G 1346
+U 7741 ; WX 602 ; N uni1E3D ; G 1347
+U 7742 ; WX 602 ; N uni1E3E ; G 1348
+U 7743 ; WX 602 ; N uni1E3F ; G 1349
+U 7744 ; WX 602 ; N uni1E40 ; G 1350
+U 7745 ; WX 602 ; N uni1E41 ; G 1351
+U 7746 ; WX 602 ; N uni1E42 ; G 1352
+U 7747 ; WX 602 ; N uni1E43 ; G 1353
+U 7748 ; WX 602 ; N uni1E44 ; G 1354
+U 7749 ; WX 602 ; N uni1E45 ; G 1355
+U 7750 ; WX 602 ; N uni1E46 ; G 1356
+U 7751 ; WX 602 ; N uni1E47 ; G 1357
+U 7752 ; WX 602 ; N uni1E48 ; G 1358
+U 7753 ; WX 602 ; N uni1E49 ; G 1359
+U 7754 ; WX 602 ; N uni1E4A ; G 1360
+U 7755 ; WX 602 ; N uni1E4B ; G 1361
+U 7756 ; WX 602 ; N uni1E4C ; G 1362
+U 7757 ; WX 602 ; N uni1E4D ; G 1363
+U 7764 ; WX 602 ; N uni1E54 ; G 1364
+U 7765 ; WX 602 ; N uni1E55 ; G 1365
+U 7766 ; WX 602 ; N uni1E56 ; G 1366
+U 7767 ; WX 602 ; N uni1E57 ; G 1367
+U 7768 ; WX 602 ; N uni1E58 ; G 1368
+U 7769 ; WX 602 ; N uni1E59 ; G 1369
+U 7770 ; WX 602 ; N uni1E5A ; G 1370
+U 7771 ; WX 602 ; N uni1E5B ; G 1371
+U 7772 ; WX 602 ; N uni1E5C ; G 1372
+U 7773 ; WX 602 ; N uni1E5D ; G 1373
+U 7774 ; WX 602 ; N uni1E5E ; G 1374
+U 7775 ; WX 602 ; N uni1E5F ; G 1375
+U 7776 ; WX 602 ; N uni1E60 ; G 1376
+U 7777 ; WX 602 ; N uni1E61 ; G 1377
+U 7778 ; WX 602 ; N uni1E62 ; G 1378
+U 7779 ; WX 602 ; N uni1E63 ; G 1379
+U 7784 ; WX 602 ; N uni1E68 ; G 1380
+U 7785 ; WX 602 ; N uni1E69 ; G 1381
+U 7786 ; WX 602 ; N uni1E6A ; G 1382
+U 7787 ; WX 602 ; N uni1E6B ; G 1383
+U 7788 ; WX 602 ; N uni1E6C ; G 1384
+U 7789 ; WX 602 ; N uni1E6D ; G 1385
+U 7790 ; WX 602 ; N uni1E6E ; G 1386
+U 7791 ; WX 602 ; N uni1E6F ; G 1387
+U 7792 ; WX 602 ; N uni1E70 ; G 1388
+U 7793 ; WX 602 ; N uni1E71 ; G 1389
+U 7794 ; WX 602 ; N uni1E72 ; G 1390
+U 7795 ; WX 602 ; N uni1E73 ; G 1391
+U 7796 ; WX 602 ; N uni1E74 ; G 1392
+U 7797 ; WX 602 ; N uni1E75 ; G 1393
+U 7798 ; WX 602 ; N uni1E76 ; G 1394
+U 7799 ; WX 602 ; N uni1E77 ; G 1395
+U 7800 ; WX 602 ; N uni1E78 ; G 1396
+U 7801 ; WX 602 ; N uni1E79 ; G 1397
+U 7804 ; WX 602 ; N uni1E7C ; G 1398
+U 7805 ; WX 602 ; N uni1E7D ; G 1399
+U 7806 ; WX 602 ; N uni1E7E ; G 1400
+U 7807 ; WX 602 ; N uni1E7F ; G 1401
+U 7808 ; WX 602 ; N Wgrave ; G 1402
+U 7809 ; WX 602 ; N wgrave ; G 1403
+U 7810 ; WX 602 ; N Wacute ; G 1404
+U 7811 ; WX 602 ; N wacute ; G 1405
+U 7812 ; WX 602 ; N Wdieresis ; G 1406
+U 7813 ; WX 602 ; N wdieresis ; G 1407
+U 7814 ; WX 602 ; N uni1E86 ; G 1408
+U 7815 ; WX 602 ; N uni1E87 ; G 1409
+U 7816 ; WX 602 ; N uni1E88 ; G 1410
+U 7817 ; WX 602 ; N uni1E89 ; G 1411
+U 7818 ; WX 602 ; N uni1E8A ; G 1412
+U 7819 ; WX 602 ; N uni1E8B ; G 1413
+U 7820 ; WX 602 ; N uni1E8C ; G 1414
+U 7821 ; WX 602 ; N uni1E8D ; G 1415
+U 7822 ; WX 602 ; N uni1E8E ; G 1416
+U 7823 ; WX 602 ; N uni1E8F ; G 1417
+U 7824 ; WX 602 ; N uni1E90 ; G 1418
+U 7825 ; WX 602 ; N uni1E91 ; G 1419
+U 7826 ; WX 602 ; N uni1E92 ; G 1420
+U 7827 ; WX 602 ; N uni1E93 ; G 1421
+U 7828 ; WX 602 ; N uni1E94 ; G 1422
+U 7829 ; WX 602 ; N uni1E95 ; G 1423
+U 7830 ; WX 602 ; N uni1E96 ; G 1424
+U 7831 ; WX 602 ; N uni1E97 ; G 1425
+U 7832 ; WX 602 ; N uni1E98 ; G 1426
+U 7833 ; WX 602 ; N uni1E99 ; G 1427
+U 7835 ; WX 602 ; N uni1E9B ; G 1428
+U 7839 ; WX 602 ; N uni1E9F ; G 1429
+U 7840 ; WX 602 ; N uni1EA0 ; G 1430
+U 7841 ; WX 602 ; N uni1EA1 ; G 1431
+U 7852 ; WX 602 ; N uni1EAC ; G 1432
+U 7853 ; WX 602 ; N uni1EAD ; G 1433
+U 7856 ; WX 602 ; N uni1EB0 ; G 1434
+U 7857 ; WX 602 ; N uni1EB1 ; G 1435
+U 7862 ; WX 602 ; N uni1EB6 ; G 1436
+U 7863 ; WX 602 ; N uni1EB7 ; G 1437
+U 7864 ; WX 602 ; N uni1EB8 ; G 1438
+U 7865 ; WX 602 ; N uni1EB9 ; G 1439
+U 7868 ; WX 602 ; N uni1EBC ; G 1440
+U 7869 ; WX 602 ; N uni1EBD ; G 1441
+U 7878 ; WX 602 ; N uni1EC6 ; G 1442
+U 7879 ; WX 602 ; N uni1EC7 ; G 1443
+U 7882 ; WX 602 ; N uni1ECA ; G 1444
+U 7883 ; WX 602 ; N uni1ECB ; G 1445
+U 7884 ; WX 602 ; N uni1ECC ; G 1446
+U 7885 ; WX 602 ; N uni1ECD ; G 1447
+U 7896 ; WX 602 ; N uni1ED8 ; G 1448
+U 7897 ; WX 602 ; N uni1ED9 ; G 1449
+U 7898 ; WX 602 ; N uni1EDA ; G 1450
+U 7899 ; WX 602 ; N uni1EDB ; G 1451
+U 7900 ; WX 602 ; N uni1EDC ; G 1452
+U 7901 ; WX 602 ; N uni1EDD ; G 1453
+U 7904 ; WX 602 ; N uni1EE0 ; G 1454
+U 7905 ; WX 602 ; N uni1EE1 ; G 1455
+U 7906 ; WX 602 ; N uni1EE2 ; G 1456
+U 7907 ; WX 602 ; N uni1EE3 ; G 1457
+U 7908 ; WX 602 ; N uni1EE4 ; G 1458
+U 7909 ; WX 602 ; N uni1EE5 ; G 1459
+U 7912 ; WX 602 ; N uni1EE8 ; G 1460
+U 7913 ; WX 602 ; N uni1EE9 ; G 1461
+U 7914 ; WX 602 ; N uni1EEA ; G 1462
+U 7915 ; WX 602 ; N uni1EEB ; G 1463
+U 7918 ; WX 602 ; N uni1EEE ; G 1464
+U 7919 ; WX 602 ; N uni1EEF ; G 1465
+U 7920 ; WX 602 ; N uni1EF0 ; G 1466
+U 7921 ; WX 602 ; N uni1EF1 ; G 1467
+U 7922 ; WX 602 ; N Ygrave ; G 1468
+U 7923 ; WX 602 ; N ygrave ; G 1469
+U 7924 ; WX 602 ; N uni1EF4 ; G 1470
+U 7925 ; WX 602 ; N uni1EF5 ; G 1471
+U 7928 ; WX 602 ; N uni1EF8 ; G 1472
+U 7929 ; WX 602 ; N uni1EF9 ; G 1473
+U 7936 ; WX 602 ; N uni1F00 ; G 1474
+U 7937 ; WX 602 ; N uni1F01 ; G 1475
+U 7938 ; WX 602 ; N uni1F02 ; G 1476
+U 7939 ; WX 602 ; N uni1F03 ; G 1477
+U 7940 ; WX 602 ; N uni1F04 ; G 1478
+U 7941 ; WX 602 ; N uni1F05 ; G 1479
+U 7942 ; WX 602 ; N uni1F06 ; G 1480
+U 7943 ; WX 602 ; N uni1F07 ; G 1481
+U 7944 ; WX 602 ; N uni1F08 ; G 1482
+U 7945 ; WX 602 ; N uni1F09 ; G 1483
+U 7946 ; WX 602 ; N uni1F0A ; G 1484
+U 7947 ; WX 602 ; N uni1F0B ; G 1485
+U 7948 ; WX 602 ; N uni1F0C ; G 1486
+U 7949 ; WX 602 ; N uni1F0D ; G 1487
+U 7950 ; WX 602 ; N uni1F0E ; G 1488
+U 7951 ; WX 602 ; N uni1F0F ; G 1489
+U 7952 ; WX 602 ; N uni1F10 ; G 1490
+U 7953 ; WX 602 ; N uni1F11 ; G 1491
+U 7954 ; WX 602 ; N uni1F12 ; G 1492
+U 7955 ; WX 602 ; N uni1F13 ; G 1493
+U 7956 ; WX 602 ; N uni1F14 ; G 1494
+U 7957 ; WX 602 ; N uni1F15 ; G 1495
+U 7960 ; WX 602 ; N uni1F18 ; G 1496
+U 7961 ; WX 602 ; N uni1F19 ; G 1497
+U 7962 ; WX 602 ; N uni1F1A ; G 1498
+U 7963 ; WX 602 ; N uni1F1B ; G 1499
+U 7964 ; WX 602 ; N uni1F1C ; G 1500
+U 7965 ; WX 602 ; N uni1F1D ; G 1501
+U 7968 ; WX 602 ; N uni1F20 ; G 1502
+U 7969 ; WX 602 ; N uni1F21 ; G 1503
+U 7970 ; WX 602 ; N uni1F22 ; G 1504
+U 7971 ; WX 602 ; N uni1F23 ; G 1505
+U 7972 ; WX 602 ; N uni1F24 ; G 1506
+U 7973 ; WX 602 ; N uni1F25 ; G 1507
+U 7974 ; WX 602 ; N uni1F26 ; G 1508
+U 7975 ; WX 602 ; N uni1F27 ; G 1509
+U 7976 ; WX 602 ; N uni1F28 ; G 1510
+U 7977 ; WX 602 ; N uni1F29 ; G 1511
+U 7978 ; WX 602 ; N uni1F2A ; G 1512
+U 7979 ; WX 602 ; N uni1F2B ; G 1513
+U 7980 ; WX 602 ; N uni1F2C ; G 1514
+U 7981 ; WX 602 ; N uni1F2D ; G 1515
+U 7982 ; WX 602 ; N uni1F2E ; G 1516
+U 7983 ; WX 602 ; N uni1F2F ; G 1517
+U 7984 ; WX 602 ; N uni1F30 ; G 1518
+U 7985 ; WX 602 ; N uni1F31 ; G 1519
+U 7986 ; WX 602 ; N uni1F32 ; G 1520
+U 7987 ; WX 602 ; N uni1F33 ; G 1521
+U 7988 ; WX 602 ; N uni1F34 ; G 1522
+U 7989 ; WX 602 ; N uni1F35 ; G 1523
+U 7990 ; WX 602 ; N uni1F36 ; G 1524
+U 7991 ; WX 602 ; N uni1F37 ; G 1525
+U 7992 ; WX 602 ; N uni1F38 ; G 1526
+U 7993 ; WX 602 ; N uni1F39 ; G 1527
+U 7994 ; WX 602 ; N uni1F3A ; G 1528
+U 7995 ; WX 602 ; N uni1F3B ; G 1529
+U 7996 ; WX 602 ; N uni1F3C ; G 1530
+U 7997 ; WX 602 ; N uni1F3D ; G 1531
+U 7998 ; WX 602 ; N uni1F3E ; G 1532
+U 7999 ; WX 602 ; N uni1F3F ; G 1533
+U 8000 ; WX 602 ; N uni1F40 ; G 1534
+U 8001 ; WX 602 ; N uni1F41 ; G 1535
+U 8002 ; WX 602 ; N uni1F42 ; G 1536
+U 8003 ; WX 602 ; N uni1F43 ; G 1537
+U 8004 ; WX 602 ; N uni1F44 ; G 1538
+U 8005 ; WX 602 ; N uni1F45 ; G 1539
+U 8008 ; WX 602 ; N uni1F48 ; G 1540
+U 8009 ; WX 602 ; N uni1F49 ; G 1541
+U 8010 ; WX 602 ; N uni1F4A ; G 1542
+U 8011 ; WX 602 ; N uni1F4B ; G 1543
+U 8012 ; WX 602 ; N uni1F4C ; G 1544
+U 8013 ; WX 602 ; N uni1F4D ; G 1545
+U 8016 ; WX 602 ; N uni1F50 ; G 1546
+U 8017 ; WX 602 ; N uni1F51 ; G 1547
+U 8018 ; WX 602 ; N uni1F52 ; G 1548
+U 8019 ; WX 602 ; N uni1F53 ; G 1549
+U 8020 ; WX 602 ; N uni1F54 ; G 1550
+U 8021 ; WX 602 ; N uni1F55 ; G 1551
+U 8022 ; WX 602 ; N uni1F56 ; G 1552
+U 8023 ; WX 602 ; N uni1F57 ; G 1553
+U 8025 ; WX 602 ; N uni1F59 ; G 1554
+U 8027 ; WX 602 ; N uni1F5B ; G 1555
+U 8029 ; WX 602 ; N uni1F5D ; G 1556
+U 8031 ; WX 602 ; N uni1F5F ; G 1557
+U 8032 ; WX 602 ; N uni1F60 ; G 1558
+U 8033 ; WX 602 ; N uni1F61 ; G 1559
+U 8034 ; WX 602 ; N uni1F62 ; G 1560
+U 8035 ; WX 602 ; N uni1F63 ; G 1561
+U 8036 ; WX 602 ; N uni1F64 ; G 1562
+U 8037 ; WX 602 ; N uni1F65 ; G 1563
+U 8038 ; WX 602 ; N uni1F66 ; G 1564
+U 8039 ; WX 602 ; N uni1F67 ; G 1565
+U 8040 ; WX 602 ; N uni1F68 ; G 1566
+U 8041 ; WX 602 ; N uni1F69 ; G 1567
+U 8042 ; WX 602 ; N uni1F6A ; G 1568
+U 8043 ; WX 602 ; N uni1F6B ; G 1569
+U 8044 ; WX 602 ; N uni1F6C ; G 1570
+U 8045 ; WX 602 ; N uni1F6D ; G 1571
+U 8046 ; WX 602 ; N uni1F6E ; G 1572
+U 8047 ; WX 602 ; N uni1F6F ; G 1573
+U 8048 ; WX 602 ; N uni1F70 ; G 1574
+U 8049 ; WX 602 ; N uni1F71 ; G 1575
+U 8050 ; WX 602 ; N uni1F72 ; G 1576
+U 8051 ; WX 602 ; N uni1F73 ; G 1577
+U 8052 ; WX 602 ; N uni1F74 ; G 1578
+U 8053 ; WX 602 ; N uni1F75 ; G 1579
+U 8054 ; WX 602 ; N uni1F76 ; G 1580
+U 8055 ; WX 602 ; N uni1F77 ; G 1581
+U 8056 ; WX 602 ; N uni1F78 ; G 1582
+U 8057 ; WX 602 ; N uni1F79 ; G 1583
+U 8058 ; WX 602 ; N uni1F7A ; G 1584
+U 8059 ; WX 602 ; N uni1F7B ; G 1585
+U 8060 ; WX 602 ; N uni1F7C ; G 1586
+U 8061 ; WX 602 ; N uni1F7D ; G 1587
+U 8064 ; WX 602 ; N uni1F80 ; G 1588
+U 8065 ; WX 602 ; N uni1F81 ; G 1589
+U 8066 ; WX 602 ; N uni1F82 ; G 1590
+U 8067 ; WX 602 ; N uni1F83 ; G 1591
+U 8068 ; WX 602 ; N uni1F84 ; G 1592
+U 8069 ; WX 602 ; N uni1F85 ; G 1593
+U 8070 ; WX 602 ; N uni1F86 ; G 1594
+U 8071 ; WX 602 ; N uni1F87 ; G 1595
+U 8072 ; WX 602 ; N uni1F88 ; G 1596
+U 8073 ; WX 602 ; N uni1F89 ; G 1597
+U 8074 ; WX 602 ; N uni1F8A ; G 1598
+U 8075 ; WX 602 ; N uni1F8B ; G 1599
+U 8076 ; WX 602 ; N uni1F8C ; G 1600
+U 8077 ; WX 602 ; N uni1F8D ; G 1601
+U 8078 ; WX 602 ; N uni1F8E ; G 1602
+U 8079 ; WX 602 ; N uni1F8F ; G 1603
+U 8080 ; WX 602 ; N uni1F90 ; G 1604
+U 8081 ; WX 602 ; N uni1F91 ; G 1605
+U 8082 ; WX 602 ; N uni1F92 ; G 1606
+U 8083 ; WX 602 ; N uni1F93 ; G 1607
+U 8084 ; WX 602 ; N uni1F94 ; G 1608
+U 8085 ; WX 602 ; N uni1F95 ; G 1609
+U 8086 ; WX 602 ; N uni1F96 ; G 1610
+U 8087 ; WX 602 ; N uni1F97 ; G 1611
+U 8088 ; WX 602 ; N uni1F98 ; G 1612
+U 8089 ; WX 602 ; N uni1F99 ; G 1613
+U 8090 ; WX 602 ; N uni1F9A ; G 1614
+U 8091 ; WX 602 ; N uni1F9B ; G 1615
+U 8092 ; WX 602 ; N uni1F9C ; G 1616
+U 8093 ; WX 602 ; N uni1F9D ; G 1617
+U 8094 ; WX 602 ; N uni1F9E ; G 1618
+U 8095 ; WX 602 ; N uni1F9F ; G 1619
+U 8096 ; WX 602 ; N uni1FA0 ; G 1620
+U 8097 ; WX 602 ; N uni1FA1 ; G 1621
+U 8098 ; WX 602 ; N uni1FA2 ; G 1622
+U 8099 ; WX 602 ; N uni1FA3 ; G 1623
+U 8100 ; WX 602 ; N uni1FA4 ; G 1624
+U 8101 ; WX 602 ; N uni1FA5 ; G 1625
+U 8102 ; WX 602 ; N uni1FA6 ; G 1626
+U 8103 ; WX 602 ; N uni1FA7 ; G 1627
+U 8104 ; WX 602 ; N uni1FA8 ; G 1628
+U 8105 ; WX 602 ; N uni1FA9 ; G 1629
+U 8106 ; WX 602 ; N uni1FAA ; G 1630
+U 8107 ; WX 602 ; N uni1FAB ; G 1631
+U 8108 ; WX 602 ; N uni1FAC ; G 1632
+U 8109 ; WX 602 ; N uni1FAD ; G 1633
+U 8110 ; WX 602 ; N uni1FAE ; G 1634
+U 8111 ; WX 602 ; N uni1FAF ; G 1635
+U 8112 ; WX 602 ; N uni1FB0 ; G 1636
+U 8113 ; WX 602 ; N uni1FB1 ; G 1637
+U 8114 ; WX 602 ; N uni1FB2 ; G 1638
+U 8115 ; WX 602 ; N uni1FB3 ; G 1639
+U 8116 ; WX 602 ; N uni1FB4 ; G 1640
+U 8118 ; WX 602 ; N uni1FB6 ; G 1641
+U 8119 ; WX 602 ; N uni1FB7 ; G 1642
+U 8120 ; WX 602 ; N uni1FB8 ; G 1643
+U 8121 ; WX 602 ; N uni1FB9 ; G 1644
+U 8122 ; WX 602 ; N uni1FBA ; G 1645
+U 8123 ; WX 602 ; N uni1FBB ; G 1646
+U 8124 ; WX 602 ; N uni1FBC ; G 1647
+U 8125 ; WX 602 ; N uni1FBD ; G 1648
+U 8126 ; WX 602 ; N uni1FBE ; G 1649
+U 8127 ; WX 602 ; N uni1FBF ; G 1650
+U 8128 ; WX 602 ; N uni1FC0 ; G 1651
+U 8129 ; WX 602 ; N uni1FC1 ; G 1652
+U 8130 ; WX 602 ; N uni1FC2 ; G 1653
+U 8131 ; WX 602 ; N uni1FC3 ; G 1654
+U 8132 ; WX 602 ; N uni1FC4 ; G 1655
+U 8134 ; WX 602 ; N uni1FC6 ; G 1656
+U 8135 ; WX 602 ; N uni1FC7 ; G 1657
+U 8136 ; WX 602 ; N uni1FC8 ; G 1658
+U 8137 ; WX 602 ; N uni1FC9 ; G 1659
+U 8138 ; WX 602 ; N uni1FCA ; G 1660
+U 8139 ; WX 602 ; N uni1FCB ; G 1661
+U 8140 ; WX 602 ; N uni1FCC ; G 1662
+U 8141 ; WX 602 ; N uni1FCD ; G 1663
+U 8142 ; WX 602 ; N uni1FCE ; G 1664
+U 8143 ; WX 602 ; N uni1FCF ; G 1665
+U 8144 ; WX 602 ; N uni1FD0 ; G 1666
+U 8145 ; WX 602 ; N uni1FD1 ; G 1667
+U 8146 ; WX 602 ; N uni1FD2 ; G 1668
+U 8147 ; WX 602 ; N uni1FD3 ; G 1669
+U 8150 ; WX 602 ; N uni1FD6 ; G 1670
+U 8151 ; WX 602 ; N uni1FD7 ; G 1671
+U 8152 ; WX 602 ; N uni1FD8 ; G 1672
+U 8153 ; WX 602 ; N uni1FD9 ; G 1673
+U 8154 ; WX 602 ; N uni1FDA ; G 1674
+U 8155 ; WX 602 ; N uni1FDB ; G 1675
+U 8157 ; WX 602 ; N uni1FDD ; G 1676
+U 8158 ; WX 602 ; N uni1FDE ; G 1677
+U 8159 ; WX 602 ; N uni1FDF ; G 1678
+U 8160 ; WX 602 ; N uni1FE0 ; G 1679
+U 8161 ; WX 602 ; N uni1FE1 ; G 1680
+U 8162 ; WX 602 ; N uni1FE2 ; G 1681
+U 8163 ; WX 602 ; N uni1FE3 ; G 1682
+U 8164 ; WX 602 ; N uni1FE4 ; G 1683
+U 8165 ; WX 602 ; N uni1FE5 ; G 1684
+U 8166 ; WX 602 ; N uni1FE6 ; G 1685
+U 8167 ; WX 602 ; N uni1FE7 ; G 1686
+U 8168 ; WX 602 ; N uni1FE8 ; G 1687
+U 8169 ; WX 602 ; N uni1FE9 ; G 1688
+U 8170 ; WX 602 ; N uni1FEA ; G 1689
+U 8171 ; WX 602 ; N uni1FEB ; G 1690
+U 8172 ; WX 602 ; N uni1FEC ; G 1691
+U 8173 ; WX 602 ; N uni1FED ; G 1692
+U 8174 ; WX 602 ; N uni1FEE ; G 1693
+U 8175 ; WX 602 ; N uni1FEF ; G 1694
+U 8178 ; WX 602 ; N uni1FF2 ; G 1695
+U 8179 ; WX 602 ; N uni1FF3 ; G 1696
+U 8180 ; WX 602 ; N uni1FF4 ; G 1697
+U 8182 ; WX 602 ; N uni1FF6 ; G 1698
+U 8183 ; WX 602 ; N uni1FF7 ; G 1699
+U 8184 ; WX 602 ; N uni1FF8 ; G 1700
+U 8185 ; WX 602 ; N uni1FF9 ; G 1701
+U 8186 ; WX 602 ; N uni1FFA ; G 1702
+U 8187 ; WX 602 ; N uni1FFB ; G 1703
+U 8188 ; WX 602 ; N uni1FFC ; G 1704
+U 8189 ; WX 602 ; N uni1FFD ; G 1705
+U 8190 ; WX 602 ; N uni1FFE ; G 1706
+U 8192 ; WX 602 ; N uni2000 ; G 1707
+U 8193 ; WX 602 ; N uni2001 ; G 1708
+U 8194 ; WX 602 ; N uni2002 ; G 1709
+U 8195 ; WX 602 ; N uni2003 ; G 1710
+U 8196 ; WX 602 ; N uni2004 ; G 1711
+U 8197 ; WX 602 ; N uni2005 ; G 1712
+U 8198 ; WX 602 ; N uni2006 ; G 1713
+U 8199 ; WX 602 ; N uni2007 ; G 1714
+U 8200 ; WX 602 ; N uni2008 ; G 1715
+U 8201 ; WX 602 ; N uni2009 ; G 1716
+U 8202 ; WX 602 ; N uni200A ; G 1717
+U 8208 ; WX 602 ; N uni2010 ; G 1718
+U 8209 ; WX 602 ; N uni2011 ; G 1719
+U 8210 ; WX 602 ; N figuredash ; G 1720
+U 8211 ; WX 602 ; N endash ; G 1721
+U 8212 ; WX 602 ; N emdash ; G 1722
+U 8213 ; WX 602 ; N uni2015 ; G 1723
+U 8214 ; WX 602 ; N uni2016 ; G 1724
+U 8215 ; WX 602 ; N underscoredbl ; G 1725
+U 8216 ; WX 602 ; N quoteleft ; G 1726
+U 8217 ; WX 602 ; N quoteright ; G 1727
+U 8218 ; WX 602 ; N quotesinglbase ; G 1728
+U 8219 ; WX 602 ; N quotereversed ; G 1729
+U 8220 ; WX 602 ; N quotedblleft ; G 1730
+U 8221 ; WX 602 ; N quotedblright ; G 1731
+U 8222 ; WX 602 ; N quotedblbase ; G 1732
+U 8223 ; WX 602 ; N uni201F ; G 1733
+U 8224 ; WX 602 ; N dagger ; G 1734
+U 8225 ; WX 602 ; N daggerdbl ; G 1735
+U 8226 ; WX 602 ; N bullet ; G 1736
+U 8227 ; WX 602 ; N uni2023 ; G 1737
+U 8230 ; WX 602 ; N ellipsis ; G 1738
+U 8239 ; WX 602 ; N uni202F ; G 1739
+U 8240 ; WX 602 ; N perthousand ; G 1740
+U 8241 ; WX 602 ; N uni2031 ; G 1741
+U 8242 ; WX 602 ; N minute ; G 1742
+U 8243 ; WX 602 ; N second ; G 1743
+U 8244 ; WX 602 ; N uni2034 ; G 1744
+U 8245 ; WX 602 ; N uni2035 ; G 1745
+U 8246 ; WX 602 ; N uni2036 ; G 1746
+U 8247 ; WX 602 ; N uni2037 ; G 1747
+U 8249 ; WX 602 ; N guilsinglleft ; G 1748
+U 8250 ; WX 602 ; N guilsinglright ; G 1749
+U 8252 ; WX 602 ; N exclamdbl ; G 1750
+U 8253 ; WX 602 ; N uni203D ; G 1751
+U 8254 ; WX 602 ; N uni203E ; G 1752
+U 8255 ; WX 602 ; N uni203F ; G 1753
+U 8261 ; WX 602 ; N uni2045 ; G 1754
+U 8262 ; WX 602 ; N uni2046 ; G 1755
+U 8263 ; WX 602 ; N uni2047 ; G 1756
+U 8264 ; WX 602 ; N uni2048 ; G 1757
+U 8265 ; WX 602 ; N uni2049 ; G 1758
+U 8267 ; WX 602 ; N uni204B ; G 1759
+U 8287 ; WX 602 ; N uni205F ; G 1760
+U 8304 ; WX 602 ; N uni2070 ; G 1761
+U 8305 ; WX 602 ; N uni2071 ; G 1762
+U 8308 ; WX 602 ; N uni2074 ; G 1763
+U 8309 ; WX 602 ; N uni2075 ; G 1764
+U 8310 ; WX 602 ; N uni2076 ; G 1765
+U 8311 ; WX 602 ; N uni2077 ; G 1766
+U 8312 ; WX 602 ; N uni2078 ; G 1767
+U 8313 ; WX 602 ; N uni2079 ; G 1768
+U 8314 ; WX 602 ; N uni207A ; G 1769
+U 8315 ; WX 602 ; N uni207B ; G 1770
+U 8316 ; WX 602 ; N uni207C ; G 1771
+U 8317 ; WX 602 ; N uni207D ; G 1772
+U 8318 ; WX 602 ; N uni207E ; G 1773
+U 8319 ; WX 602 ; N uni207F ; G 1774
+U 8320 ; WX 602 ; N uni2080 ; G 1775
+U 8321 ; WX 602 ; N uni2081 ; G 1776
+U 8322 ; WX 602 ; N uni2082 ; G 1777
+U 8323 ; WX 602 ; N uni2083 ; G 1778
+U 8324 ; WX 602 ; N uni2084 ; G 1779
+U 8325 ; WX 602 ; N uni2085 ; G 1780
+U 8326 ; WX 602 ; N uni2086 ; G 1781
+U 8327 ; WX 602 ; N uni2087 ; G 1782
+U 8328 ; WX 602 ; N uni2088 ; G 1783
+U 8329 ; WX 602 ; N uni2089 ; G 1784
+U 8330 ; WX 602 ; N uni208A ; G 1785
+U 8331 ; WX 602 ; N uni208B ; G 1786
+U 8332 ; WX 602 ; N uni208C ; G 1787
+U 8333 ; WX 602 ; N uni208D ; G 1788
+U 8334 ; WX 602 ; N uni208E ; G 1789
+U 8336 ; WX 602 ; N uni2090 ; G 1790
+U 8337 ; WX 602 ; N uni2091 ; G 1791
+U 8338 ; WX 602 ; N uni2092 ; G 1792
+U 8339 ; WX 602 ; N uni2093 ; G 1793
+U 8340 ; WX 602 ; N uni2094 ; G 1794
+U 8341 ; WX 602 ; N uni2095 ; G 1795
+U 8342 ; WX 602 ; N uni2096 ; G 1796
+U 8343 ; WX 602 ; N uni2097 ; G 1797
+U 8344 ; WX 602 ; N uni2098 ; G 1798
+U 8345 ; WX 602 ; N uni2099 ; G 1799
+U 8346 ; WX 602 ; N uni209A ; G 1800
+U 8347 ; WX 602 ; N uni209B ; G 1801
+U 8348 ; WX 602 ; N uni209C ; G 1802
+U 8352 ; WX 602 ; N uni20A0 ; G 1803
+U 8353 ; WX 602 ; N colonmonetary ; G 1804
+U 8354 ; WX 602 ; N uni20A2 ; G 1805
+U 8355 ; WX 602 ; N franc ; G 1806
+U 8356 ; WX 602 ; N lira ; G 1807
+U 8357 ; WX 602 ; N uni20A5 ; G 1808
+U 8358 ; WX 602 ; N uni20A6 ; G 1809
+U 8359 ; WX 602 ; N peseta ; G 1810
+U 8360 ; WX 602 ; N uni20A8 ; G 1811
+U 8361 ; WX 602 ; N uni20A9 ; G 1812
+U 8362 ; WX 602 ; N uni20AA ; G 1813
+U 8363 ; WX 602 ; N dong ; G 1814
+U 8364 ; WX 602 ; N Euro ; G 1815
+U 8365 ; WX 602 ; N uni20AD ; G 1816
+U 8366 ; WX 602 ; N uni20AE ; G 1817
+U 8367 ; WX 602 ; N uni20AF ; G 1818
+U 8368 ; WX 602 ; N uni20B0 ; G 1819
+U 8369 ; WX 602 ; N uni20B1 ; G 1820
+U 8370 ; WX 602 ; N uni20B2 ; G 1821
+U 8371 ; WX 602 ; N uni20B3 ; G 1822
+U 8372 ; WX 602 ; N uni20B4 ; G 1823
+U 8373 ; WX 602 ; N uni20B5 ; G 1824
+U 8376 ; WX 602 ; N uni20B8 ; G 1825
+U 8377 ; WX 602 ; N uni20B9 ; G 1826
+U 8378 ; WX 602 ; N uni20BA ; G 1827
+U 8381 ; WX 602 ; N uni20BD ; G 1828
+U 8450 ; WX 602 ; N uni2102 ; G 1829
+U 8453 ; WX 602 ; N uni2105 ; G 1830
+U 8461 ; WX 602 ; N uni210D ; G 1831
+U 8462 ; WX 602 ; N uni210E ; G 1832
+U 8463 ; WX 602 ; N uni210F ; G 1833
+U 8469 ; WX 602 ; N uni2115 ; G 1834
+U 8470 ; WX 602 ; N uni2116 ; G 1835
+U 8471 ; WX 602 ; N uni2117 ; G 1836
+U 8473 ; WX 602 ; N uni2119 ; G 1837
+U 8474 ; WX 602 ; N uni211A ; G 1838
+U 8477 ; WX 602 ; N uni211D ; G 1839
+U 8482 ; WX 602 ; N trademark ; G 1840
+U 8484 ; WX 602 ; N uni2124 ; G 1841
+U 8486 ; WX 602 ; N uni2126 ; G 1842
+U 8490 ; WX 602 ; N uni212A ; G 1843
+U 8491 ; WX 602 ; N uni212B ; G 1844
+U 8494 ; WX 602 ; N estimated ; G 1845
+U 8520 ; WX 602 ; N uni2148 ; G 1846
+U 8528 ; WX 602 ; N uni2150 ; G 1847
+U 8529 ; WX 602 ; N uni2151 ; G 1848
+U 8531 ; WX 602 ; N onethird ; G 1849
+U 8532 ; WX 602 ; N twothirds ; G 1850
+U 8533 ; WX 602 ; N uni2155 ; G 1851
+U 8534 ; WX 602 ; N uni2156 ; G 1852
+U 8535 ; WX 602 ; N uni2157 ; G 1853
+U 8536 ; WX 602 ; N uni2158 ; G 1854
+U 8537 ; WX 602 ; N uni2159 ; G 1855
+U 8538 ; WX 602 ; N uni215A ; G 1856
+U 8539 ; WX 602 ; N oneeighth ; G 1857
+U 8540 ; WX 602 ; N threeeighths ; G 1858
+U 8541 ; WX 602 ; N fiveeighths ; G 1859
+U 8542 ; WX 602 ; N seveneighths ; G 1860
+U 8543 ; WX 602 ; N uni215F ; G 1861
+U 8585 ; WX 602 ; N uni2189 ; G 1862
+U 8592 ; WX 602 ; N arrowleft ; G 1863
+U 8593 ; WX 602 ; N arrowup ; G 1864
+U 8594 ; WX 602 ; N arrowright ; G 1865
+U 8595 ; WX 602 ; N arrowdown ; G 1866
+U 8596 ; WX 602 ; N arrowboth ; G 1867
+U 8597 ; WX 602 ; N arrowupdn ; G 1868
+U 8598 ; WX 602 ; N uni2196 ; G 1869
+U 8599 ; WX 602 ; N uni2197 ; G 1870
+U 8600 ; WX 602 ; N uni2198 ; G 1871
+U 8601 ; WX 602 ; N uni2199 ; G 1872
+U 8602 ; WX 602 ; N uni219A ; G 1873
+U 8603 ; WX 602 ; N uni219B ; G 1874
+U 8604 ; WX 602 ; N uni219C ; G 1875
+U 8605 ; WX 602 ; N uni219D ; G 1876
+U 8606 ; WX 602 ; N uni219E ; G 1877
+U 8607 ; WX 602 ; N uni219F ; G 1878
+U 8608 ; WX 602 ; N uni21A0 ; G 1879
+U 8609 ; WX 602 ; N uni21A1 ; G 1880
+U 8610 ; WX 602 ; N uni21A2 ; G 1881
+U 8611 ; WX 602 ; N uni21A3 ; G 1882
+U 8612 ; WX 602 ; N uni21A4 ; G 1883
+U 8613 ; WX 602 ; N uni21A5 ; G 1884
+U 8614 ; WX 602 ; N uni21A6 ; G 1885
+U 8615 ; WX 602 ; N uni21A7 ; G 1886
+U 8616 ; WX 602 ; N arrowupdnbse ; G 1887
+U 8617 ; WX 602 ; N uni21A9 ; G 1888
+U 8618 ; WX 602 ; N uni21AA ; G 1889
+U 8619 ; WX 602 ; N uni21AB ; G 1890
+U 8620 ; WX 602 ; N uni21AC ; G 1891
+U 8621 ; WX 602 ; N uni21AD ; G 1892
+U 8622 ; WX 602 ; N uni21AE ; G 1893
+U 8623 ; WX 602 ; N uni21AF ; G 1894
+U 8624 ; WX 602 ; N uni21B0 ; G 1895
+U 8625 ; WX 602 ; N uni21B1 ; G 1896
+U 8626 ; WX 602 ; N uni21B2 ; G 1897
+U 8627 ; WX 602 ; N uni21B3 ; G 1898
+U 8628 ; WX 602 ; N uni21B4 ; G 1899
+U 8629 ; WX 602 ; N carriagereturn ; G 1900
+U 8630 ; WX 602 ; N uni21B6 ; G 1901
+U 8631 ; WX 602 ; N uni21B7 ; G 1902
+U 8632 ; WX 602 ; N uni21B8 ; G 1903
+U 8633 ; WX 602 ; N uni21B9 ; G 1904
+U 8634 ; WX 602 ; N uni21BA ; G 1905
+U 8635 ; WX 602 ; N uni21BB ; G 1906
+U 8636 ; WX 602 ; N uni21BC ; G 1907
+U 8637 ; WX 602 ; N uni21BD ; G 1908
+U 8638 ; WX 602 ; N uni21BE ; G 1909
+U 8639 ; WX 602 ; N uni21BF ; G 1910
+U 8640 ; WX 602 ; N uni21C0 ; G 1911
+U 8641 ; WX 602 ; N uni21C1 ; G 1912
+U 8642 ; WX 602 ; N uni21C2 ; G 1913
+U 8643 ; WX 602 ; N uni21C3 ; G 1914
+U 8644 ; WX 602 ; N uni21C4 ; G 1915
+U 8645 ; WX 602 ; N uni21C5 ; G 1916
+U 8646 ; WX 602 ; N uni21C6 ; G 1917
+U 8647 ; WX 602 ; N uni21C7 ; G 1918
+U 8648 ; WX 602 ; N uni21C8 ; G 1919
+U 8649 ; WX 602 ; N uni21C9 ; G 1920
+U 8650 ; WX 602 ; N uni21CA ; G 1921
+U 8651 ; WX 602 ; N uni21CB ; G 1922
+U 8652 ; WX 602 ; N uni21CC ; G 1923
+U 8653 ; WX 602 ; N uni21CD ; G 1924
+U 8654 ; WX 602 ; N uni21CE ; G 1925
+U 8655 ; WX 602 ; N uni21CF ; G 1926
+U 8656 ; WX 602 ; N arrowdblleft ; G 1927
+U 8657 ; WX 602 ; N arrowdblup ; G 1928
+U 8658 ; WX 602 ; N arrowdblright ; G 1929
+U 8659 ; WX 602 ; N arrowdbldown ; G 1930
+U 8660 ; WX 602 ; N arrowdblboth ; G 1931
+U 8661 ; WX 602 ; N uni21D5 ; G 1932
+U 8662 ; WX 602 ; N uni21D6 ; G 1933
+U 8663 ; WX 602 ; N uni21D7 ; G 1934
+U 8664 ; WX 602 ; N uni21D8 ; G 1935
+U 8665 ; WX 602 ; N uni21D9 ; G 1936
+U 8666 ; WX 602 ; N uni21DA ; G 1937
+U 8667 ; WX 602 ; N uni21DB ; G 1938
+U 8668 ; WX 602 ; N uni21DC ; G 1939
+U 8669 ; WX 602 ; N uni21DD ; G 1940
+U 8670 ; WX 602 ; N uni21DE ; G 1941
+U 8671 ; WX 602 ; N uni21DF ; G 1942
+U 8672 ; WX 602 ; N uni21E0 ; G 1943
+U 8673 ; WX 602 ; N uni21E1 ; G 1944
+U 8674 ; WX 602 ; N uni21E2 ; G 1945
+U 8675 ; WX 602 ; N uni21E3 ; G 1946
+U 8676 ; WX 602 ; N uni21E4 ; G 1947
+U 8677 ; WX 602 ; N uni21E5 ; G 1948
+U 8678 ; WX 602 ; N uni21E6 ; G 1949
+U 8679 ; WX 602 ; N uni21E7 ; G 1950
+U 8680 ; WX 602 ; N uni21E8 ; G 1951
+U 8681 ; WX 602 ; N uni21E9 ; G 1952
+U 8682 ; WX 602 ; N uni21EA ; G 1953
+U 8683 ; WX 602 ; N uni21EB ; G 1954
+U 8684 ; WX 602 ; N uni21EC ; G 1955
+U 8685 ; WX 602 ; N uni21ED ; G 1956
+U 8686 ; WX 602 ; N uni21EE ; G 1957
+U 8687 ; WX 602 ; N uni21EF ; G 1958
+U 8688 ; WX 602 ; N uni21F0 ; G 1959
+U 8689 ; WX 602 ; N uni21F1 ; G 1960
+U 8690 ; WX 602 ; N uni21F2 ; G 1961
+U 8691 ; WX 602 ; N uni21F3 ; G 1962
+U 8692 ; WX 602 ; N uni21F4 ; G 1963
+U 8693 ; WX 602 ; N uni21F5 ; G 1964
+U 8694 ; WX 602 ; N uni21F6 ; G 1965
+U 8695 ; WX 602 ; N uni21F7 ; G 1966
+U 8696 ; WX 602 ; N uni21F8 ; G 1967
+U 8697 ; WX 602 ; N uni21F9 ; G 1968
+U 8698 ; WX 602 ; N uni21FA ; G 1969
+U 8699 ; WX 602 ; N uni21FB ; G 1970
+U 8700 ; WX 602 ; N uni21FC ; G 1971
+U 8701 ; WX 602 ; N uni21FD ; G 1972
+U 8702 ; WX 602 ; N uni21FE ; G 1973
+U 8703 ; WX 602 ; N uni21FF ; G 1974
+U 8704 ; WX 602 ; N universal ; G 1975
+U 8705 ; WX 602 ; N uni2201 ; G 1976
+U 8706 ; WX 602 ; N partialdiff ; G 1977
+U 8707 ; WX 602 ; N existential ; G 1978
+U 8708 ; WX 602 ; N uni2204 ; G 1979
+U 8709 ; WX 602 ; N emptyset ; G 1980
+U 8710 ; WX 602 ; N increment ; G 1981
+U 8711 ; WX 602 ; N gradient ; G 1982
+U 8712 ; WX 602 ; N element ; G 1983
+U 8713 ; WX 602 ; N notelement ; G 1984
+U 8714 ; WX 602 ; N uni220A ; G 1985
+U 8715 ; WX 602 ; N suchthat ; G 1986
+U 8716 ; WX 602 ; N uni220C ; G 1987
+U 8717 ; WX 602 ; N uni220D ; G 1988
+U 8718 ; WX 602 ; N uni220E ; G 1989
+U 8719 ; WX 602 ; N product ; G 1990
+U 8720 ; WX 602 ; N uni2210 ; G 1991
+U 8721 ; WX 602 ; N summation ; G 1992
+U 8722 ; WX 602 ; N minus ; G 1993
+U 8723 ; WX 602 ; N uni2213 ; G 1994
+U 8725 ; WX 602 ; N uni2215 ; G 1995
+U 8727 ; WX 602 ; N asteriskmath ; G 1996
+U 8728 ; WX 602 ; N uni2218 ; G 1997
+U 8729 ; WX 602 ; N uni2219 ; G 1998
+U 8730 ; WX 602 ; N radical ; G 1999
+U 8731 ; WX 602 ; N uni221B ; G 2000
+U 8732 ; WX 602 ; N uni221C ; G 2001
+U 8733 ; WX 602 ; N proportional ; G 2002
+U 8734 ; WX 602 ; N infinity ; G 2003
+U 8735 ; WX 602 ; N orthogonal ; G 2004
+U 8736 ; WX 602 ; N angle ; G 2005
+U 8739 ; WX 602 ; N uni2223 ; G 2006
+U 8743 ; WX 602 ; N logicaland ; G 2007
+U 8744 ; WX 602 ; N logicalor ; G 2008
+U 8745 ; WX 602 ; N intersection ; G 2009
+U 8746 ; WX 602 ; N union ; G 2010
+U 8747 ; WX 602 ; N integral ; G 2011
+U 8748 ; WX 602 ; N uni222C ; G 2012
+U 8749 ; WX 602 ; N uni222D ; G 2013
+U 8756 ; WX 602 ; N therefore ; G 2014
+U 8757 ; WX 602 ; N uni2235 ; G 2015
+U 8758 ; WX 602 ; N uni2236 ; G 2016
+U 8759 ; WX 602 ; N uni2237 ; G 2017
+U 8760 ; WX 602 ; N uni2238 ; G 2018
+U 8761 ; WX 602 ; N uni2239 ; G 2019
+U 8762 ; WX 602 ; N uni223A ; G 2020
+U 8763 ; WX 602 ; N uni223B ; G 2021
+U 8764 ; WX 602 ; N similar ; G 2022
+U 8765 ; WX 602 ; N uni223D ; G 2023
+U 8769 ; WX 602 ; N uni2241 ; G 2024
+U 8770 ; WX 602 ; N uni2242 ; G 2025
+U 8771 ; WX 602 ; N uni2243 ; G 2026
+U 8772 ; WX 602 ; N uni2244 ; G 2027
+U 8773 ; WX 602 ; N congruent ; G 2028
+U 8774 ; WX 602 ; N uni2246 ; G 2029
+U 8775 ; WX 602 ; N uni2247 ; G 2030
+U 8776 ; WX 602 ; N approxequal ; G 2031
+U 8777 ; WX 602 ; N uni2249 ; G 2032
+U 8778 ; WX 602 ; N uni224A ; G 2033
+U 8779 ; WX 602 ; N uni224B ; G 2034
+U 8780 ; WX 602 ; N uni224C ; G 2035
+U 8781 ; WX 602 ; N uni224D ; G 2036
+U 8782 ; WX 602 ; N uni224E ; G 2037
+U 8783 ; WX 602 ; N uni224F ; G 2038
+U 8784 ; WX 602 ; N uni2250 ; G 2039
+U 8785 ; WX 602 ; N uni2251 ; G 2040
+U 8786 ; WX 602 ; N uni2252 ; G 2041
+U 8787 ; WX 602 ; N uni2253 ; G 2042
+U 8788 ; WX 602 ; N uni2254 ; G 2043
+U 8789 ; WX 602 ; N uni2255 ; G 2044
+U 8790 ; WX 602 ; N uni2256 ; G 2045
+U 8791 ; WX 602 ; N uni2257 ; G 2046
+U 8792 ; WX 602 ; N uni2258 ; G 2047
+U 8793 ; WX 602 ; N uni2259 ; G 2048
+U 8794 ; WX 602 ; N uni225A ; G 2049
+U 8795 ; WX 602 ; N uni225B ; G 2050
+U 8796 ; WX 602 ; N uni225C ; G 2051
+U 8797 ; WX 602 ; N uni225D ; G 2052
+U 8798 ; WX 602 ; N uni225E ; G 2053
+U 8799 ; WX 602 ; N uni225F ; G 2054
+U 8800 ; WX 602 ; N notequal ; G 2055
+U 8801 ; WX 602 ; N equivalence ; G 2056
+U 8802 ; WX 602 ; N uni2262 ; G 2057
+U 8803 ; WX 602 ; N uni2263 ; G 2058
+U 8804 ; WX 602 ; N lessequal ; G 2059
+U 8805 ; WX 602 ; N greaterequal ; G 2060
+U 8806 ; WX 602 ; N uni2266 ; G 2061
+U 8807 ; WX 602 ; N uni2267 ; G 2062
+U 8808 ; WX 602 ; N uni2268 ; G 2063
+U 8809 ; WX 602 ; N uni2269 ; G 2064
+U 8813 ; WX 602 ; N uni226D ; G 2065
+U 8814 ; WX 602 ; N uni226E ; G 2066
+U 8815 ; WX 602 ; N uni226F ; G 2067
+U 8816 ; WX 602 ; N uni2270 ; G 2068
+U 8817 ; WX 602 ; N uni2271 ; G 2069
+U 8818 ; WX 602 ; N uni2272 ; G 2070
+U 8819 ; WX 602 ; N uni2273 ; G 2071
+U 8820 ; WX 602 ; N uni2274 ; G 2072
+U 8821 ; WX 602 ; N uni2275 ; G 2073
+U 8822 ; WX 602 ; N uni2276 ; G 2074
+U 8823 ; WX 602 ; N uni2277 ; G 2075
+U 8824 ; WX 602 ; N uni2278 ; G 2076
+U 8825 ; WX 602 ; N uni2279 ; G 2077
+U 8826 ; WX 602 ; N uni227A ; G 2078
+U 8827 ; WX 602 ; N uni227B ; G 2079
+U 8828 ; WX 602 ; N uni227C ; G 2080
+U 8829 ; WX 602 ; N uni227D ; G 2081
+U 8830 ; WX 602 ; N uni227E ; G 2082
+U 8831 ; WX 602 ; N uni227F ; G 2083
+U 8832 ; WX 602 ; N uni2280 ; G 2084
+U 8833 ; WX 602 ; N uni2281 ; G 2085
+U 8834 ; WX 602 ; N propersubset ; G 2086
+U 8835 ; WX 602 ; N propersuperset ; G 2087
+U 8836 ; WX 602 ; N notsubset ; G 2088
+U 8837 ; WX 602 ; N uni2285 ; G 2089
+U 8838 ; WX 602 ; N reflexsubset ; G 2090
+U 8839 ; WX 602 ; N reflexsuperset ; G 2091
+U 8840 ; WX 602 ; N uni2288 ; G 2092
+U 8841 ; WX 602 ; N uni2289 ; G 2093
+U 8842 ; WX 602 ; N uni228A ; G 2094
+U 8843 ; WX 602 ; N uni228B ; G 2095
+U 8845 ; WX 602 ; N uni228D ; G 2096
+U 8846 ; WX 602 ; N uni228E ; G 2097
+U 8847 ; WX 602 ; N uni228F ; G 2098
+U 8848 ; WX 602 ; N uni2290 ; G 2099
+U 8849 ; WX 602 ; N uni2291 ; G 2100
+U 8850 ; WX 602 ; N uni2292 ; G 2101
+U 8851 ; WX 602 ; N uni2293 ; G 2102
+U 8852 ; WX 602 ; N uni2294 ; G 2103
+U 8853 ; WX 602 ; N circleplus ; G 2104
+U 8854 ; WX 602 ; N uni2296 ; G 2105
+U 8855 ; WX 602 ; N circlemultiply ; G 2106
+U 8856 ; WX 602 ; N uni2298 ; G 2107
+U 8857 ; WX 602 ; N uni2299 ; G 2108
+U 8858 ; WX 602 ; N uni229A ; G 2109
+U 8859 ; WX 602 ; N uni229B ; G 2110
+U 8860 ; WX 602 ; N uni229C ; G 2111
+U 8861 ; WX 602 ; N uni229D ; G 2112
+U 8862 ; WX 602 ; N uni229E ; G 2113
+U 8863 ; WX 602 ; N uni229F ; G 2114
+U 8864 ; WX 602 ; N uni22A0 ; G 2115
+U 8865 ; WX 602 ; N uni22A1 ; G 2116
+U 8866 ; WX 602 ; N uni22A2 ; G 2117
+U 8867 ; WX 602 ; N uni22A3 ; G 2118
+U 8868 ; WX 602 ; N uni22A4 ; G 2119
+U 8869 ; WX 602 ; N perpendicular ; G 2120
+U 8882 ; WX 602 ; N uni22B2 ; G 2121
+U 8883 ; WX 602 ; N uni22B3 ; G 2122
+U 8884 ; WX 602 ; N uni22B4 ; G 2123
+U 8885 ; WX 602 ; N uni22B5 ; G 2124
+U 8888 ; WX 602 ; N uni22B8 ; G 2125
+U 8898 ; WX 602 ; N uni22C2 ; G 2126
+U 8899 ; WX 602 ; N uni22C3 ; G 2127
+U 8900 ; WX 602 ; N uni22C4 ; G 2128
+U 8901 ; WX 602 ; N dotmath ; G 2129
+U 8902 ; WX 602 ; N uni22C6 ; G 2130
+U 8909 ; WX 602 ; N uni22CD ; G 2131
+U 8910 ; WX 602 ; N uni22CE ; G 2132
+U 8911 ; WX 602 ; N uni22CF ; G 2133
+U 8912 ; WX 602 ; N uni22D0 ; G 2134
+U 8913 ; WX 602 ; N uni22D1 ; G 2135
+U 8922 ; WX 602 ; N uni22DA ; G 2136
+U 8923 ; WX 602 ; N uni22DB ; G 2137
+U 8924 ; WX 602 ; N uni22DC ; G 2138
+U 8925 ; WX 602 ; N uni22DD ; G 2139
+U 8926 ; WX 602 ; N uni22DE ; G 2140
+U 8927 ; WX 602 ; N uni22DF ; G 2141
+U 8928 ; WX 602 ; N uni22E0 ; G 2142
+U 8929 ; WX 602 ; N uni22E1 ; G 2143
+U 8930 ; WX 602 ; N uni22E2 ; G 2144
+U 8931 ; WX 602 ; N uni22E3 ; G 2145
+U 8932 ; WX 602 ; N uni22E4 ; G 2146
+U 8933 ; WX 602 ; N uni22E5 ; G 2147
+U 8934 ; WX 602 ; N uni22E6 ; G 2148
+U 8935 ; WX 602 ; N uni22E7 ; G 2149
+U 8936 ; WX 602 ; N uni22E8 ; G 2150
+U 8937 ; WX 602 ; N uni22E9 ; G 2151
+U 8943 ; WX 602 ; N uni22EF ; G 2152
+U 8960 ; WX 602 ; N uni2300 ; G 2153
+U 8961 ; WX 602 ; N uni2301 ; G 2154
+U 8962 ; WX 602 ; N house ; G 2155
+U 8963 ; WX 602 ; N uni2303 ; G 2156
+U 8964 ; WX 602 ; N uni2304 ; G 2157
+U 8965 ; WX 602 ; N uni2305 ; G 2158
+U 8966 ; WX 602 ; N uni2306 ; G 2159
+U 8968 ; WX 602 ; N uni2308 ; G 2160
+U 8969 ; WX 602 ; N uni2309 ; G 2161
+U 8970 ; WX 602 ; N uni230A ; G 2162
+U 8971 ; WX 602 ; N uni230B ; G 2163
+U 8972 ; WX 602 ; N uni230C ; G 2164
+U 8973 ; WX 602 ; N uni230D ; G 2165
+U 8974 ; WX 602 ; N uni230E ; G 2166
+U 8975 ; WX 602 ; N uni230F ; G 2167
+U 8976 ; WX 602 ; N revlogicalnot ; G 2168
+U 8977 ; WX 602 ; N uni2311 ; G 2169
+U 8978 ; WX 602 ; N uni2312 ; G 2170
+U 8979 ; WX 602 ; N uni2313 ; G 2171
+U 8980 ; WX 602 ; N uni2314 ; G 2172
+U 8981 ; WX 602 ; N uni2315 ; G 2173
+U 8984 ; WX 602 ; N uni2318 ; G 2174
+U 8985 ; WX 602 ; N uni2319 ; G 2175
+U 8988 ; WX 602 ; N uni231C ; G 2176
+U 8989 ; WX 602 ; N uni231D ; G 2177
+U 8990 ; WX 602 ; N uni231E ; G 2178
+U 8991 ; WX 602 ; N uni231F ; G 2179
+U 8992 ; WX 602 ; N integraltp ; G 2180
+U 8993 ; WX 602 ; N integralbt ; G 2181
+U 8997 ; WX 602 ; N uni2325 ; G 2182
+U 8998 ; WX 602 ; N uni2326 ; G 2183
+U 8999 ; WX 602 ; N uni2327 ; G 2184
+U 9000 ; WX 602 ; N uni2328 ; G 2185
+U 9003 ; WX 602 ; N uni232B ; G 2186
+U 9013 ; WX 602 ; N uni2335 ; G 2187
+U 9014 ; WX 602 ; N uni2336 ; G 2188
+U 9015 ; WX 602 ; N uni2337 ; G 2189
+U 9016 ; WX 602 ; N uni2338 ; G 2190
+U 9017 ; WX 602 ; N uni2339 ; G 2191
+U 9018 ; WX 602 ; N uni233A ; G 2192
+U 9019 ; WX 602 ; N uni233B ; G 2193
+U 9020 ; WX 602 ; N uni233C ; G 2194
+U 9021 ; WX 602 ; N uni233D ; G 2195
+U 9022 ; WX 602 ; N uni233E ; G 2196
+U 9023 ; WX 602 ; N uni233F ; G 2197
+U 9024 ; WX 602 ; N uni2340 ; G 2198
+U 9025 ; WX 602 ; N uni2341 ; G 2199
+U 9026 ; WX 602 ; N uni2342 ; G 2200
+U 9027 ; WX 602 ; N uni2343 ; G 2201
+U 9028 ; WX 602 ; N uni2344 ; G 2202
+U 9029 ; WX 602 ; N uni2345 ; G 2203
+U 9030 ; WX 602 ; N uni2346 ; G 2204
+U 9031 ; WX 602 ; N uni2347 ; G 2205
+U 9032 ; WX 602 ; N uni2348 ; G 2206
+U 9033 ; WX 602 ; N uni2349 ; G 2207
+U 9034 ; WX 602 ; N uni234A ; G 2208
+U 9035 ; WX 602 ; N uni234B ; G 2209
+U 9036 ; WX 602 ; N uni234C ; G 2210
+U 9037 ; WX 602 ; N uni234D ; G 2211
+U 9038 ; WX 602 ; N uni234E ; G 2212
+U 9039 ; WX 602 ; N uni234F ; G 2213
+U 9040 ; WX 602 ; N uni2350 ; G 2214
+U 9041 ; WX 602 ; N uni2351 ; G 2215
+U 9042 ; WX 602 ; N uni2352 ; G 2216
+U 9043 ; WX 602 ; N uni2353 ; G 2217
+U 9044 ; WX 602 ; N uni2354 ; G 2218
+U 9045 ; WX 602 ; N uni2355 ; G 2219
+U 9046 ; WX 602 ; N uni2356 ; G 2220
+U 9047 ; WX 602 ; N uni2357 ; G 2221
+U 9048 ; WX 602 ; N uni2358 ; G 2222
+U 9049 ; WX 602 ; N uni2359 ; G 2223
+U 9050 ; WX 602 ; N uni235A ; G 2224
+U 9051 ; WX 602 ; N uni235B ; G 2225
+U 9052 ; WX 602 ; N uni235C ; G 2226
+U 9053 ; WX 602 ; N uni235D ; G 2227
+U 9054 ; WX 602 ; N uni235E ; G 2228
+U 9055 ; WX 602 ; N uni235F ; G 2229
+U 9056 ; WX 602 ; N uni2360 ; G 2230
+U 9057 ; WX 602 ; N uni2361 ; G 2231
+U 9058 ; WX 602 ; N uni2362 ; G 2232
+U 9059 ; WX 602 ; N uni2363 ; G 2233
+U 9060 ; WX 602 ; N uni2364 ; G 2234
+U 9061 ; WX 602 ; N uni2365 ; G 2235
+U 9062 ; WX 602 ; N uni2366 ; G 2236
+U 9063 ; WX 602 ; N uni2367 ; G 2237
+U 9064 ; WX 602 ; N uni2368 ; G 2238
+U 9065 ; WX 602 ; N uni2369 ; G 2239
+U 9066 ; WX 602 ; N uni236A ; G 2240
+U 9067 ; WX 602 ; N uni236B ; G 2241
+U 9068 ; WX 602 ; N uni236C ; G 2242
+U 9069 ; WX 602 ; N uni236D ; G 2243
+U 9070 ; WX 602 ; N uni236E ; G 2244
+U 9071 ; WX 602 ; N uni236F ; G 2245
+U 9072 ; WX 602 ; N uni2370 ; G 2246
+U 9073 ; WX 602 ; N uni2371 ; G 2247
+U 9074 ; WX 602 ; N uni2372 ; G 2248
+U 9075 ; WX 602 ; N uni2373 ; G 2249
+U 9076 ; WX 602 ; N uni2374 ; G 2250
+U 9077 ; WX 602 ; N uni2375 ; G 2251
+U 9078 ; WX 602 ; N uni2376 ; G 2252
+U 9079 ; WX 602 ; N uni2377 ; G 2253
+U 9080 ; WX 602 ; N uni2378 ; G 2254
+U 9081 ; WX 602 ; N uni2379 ; G 2255
+U 9082 ; WX 602 ; N uni237A ; G 2256
+U 9085 ; WX 602 ; N uni237D ; G 2257
+U 9088 ; WX 602 ; N uni2380 ; G 2258
+U 9089 ; WX 602 ; N uni2381 ; G 2259
+U 9090 ; WX 602 ; N uni2382 ; G 2260
+U 9091 ; WX 602 ; N uni2383 ; G 2261
+U 9096 ; WX 602 ; N uni2388 ; G 2262
+U 9097 ; WX 602 ; N uni2389 ; G 2263
+U 9098 ; WX 602 ; N uni238A ; G 2264
+U 9099 ; WX 602 ; N uni238B ; G 2265
+U 9109 ; WX 602 ; N uni2395 ; G 2266
+U 9115 ; WX 602 ; N uni239B ; G 2267
+U 9116 ; WX 602 ; N uni239C ; G 2268
+U 9117 ; WX 602 ; N uni239D ; G 2269
+U 9118 ; WX 602 ; N uni239E ; G 2270
+U 9119 ; WX 602 ; N uni239F ; G 2271
+U 9120 ; WX 602 ; N uni23A0 ; G 2272
+U 9121 ; WX 602 ; N uni23A1 ; G 2273
+U 9122 ; WX 602 ; N uni23A2 ; G 2274
+U 9123 ; WX 602 ; N uni23A3 ; G 2275
+U 9124 ; WX 602 ; N uni23A4 ; G 2276
+U 9125 ; WX 602 ; N uni23A5 ; G 2277
+U 9126 ; WX 602 ; N uni23A6 ; G 2278
+U 9127 ; WX 602 ; N uni23A7 ; G 2279
+U 9128 ; WX 602 ; N uni23A8 ; G 2280
+U 9129 ; WX 602 ; N uni23A9 ; G 2281
+U 9130 ; WX 602 ; N uni23AA ; G 2282
+U 9131 ; WX 602 ; N uni23AB ; G 2283
+U 9132 ; WX 602 ; N uni23AC ; G 2284
+U 9133 ; WX 602 ; N uni23AD ; G 2285
+U 9134 ; WX 602 ; N uni23AE ; G 2286
+U 9166 ; WX 602 ; N uni23CE ; G 2287
+U 9167 ; WX 602 ; N uni23CF ; G 2288
+U 9251 ; WX 602 ; N uni2423 ; G 2289
+U 9472 ; WX 602 ; N SF100000 ; G 2290
+U 9473 ; WX 602 ; N uni2501 ; G 2291
+U 9474 ; WX 602 ; N SF110000 ; G 2292
+U 9475 ; WX 602 ; N uni2503 ; G 2293
+U 9476 ; WX 602 ; N uni2504 ; G 2294
+U 9477 ; WX 602 ; N uni2505 ; G 2295
+U 9478 ; WX 602 ; N uni2506 ; G 2296
+U 9479 ; WX 602 ; N uni2507 ; G 2297
+U 9480 ; WX 602 ; N uni2508 ; G 2298
+U 9481 ; WX 602 ; N uni2509 ; G 2299
+U 9482 ; WX 602 ; N uni250A ; G 2300
+U 9483 ; WX 602 ; N uni250B ; G 2301
+U 9484 ; WX 602 ; N SF010000 ; G 2302
+U 9485 ; WX 602 ; N uni250D ; G 2303
+U 9486 ; WX 602 ; N uni250E ; G 2304
+U 9487 ; WX 602 ; N uni250F ; G 2305
+U 9488 ; WX 602 ; N SF030000 ; G 2306
+U 9489 ; WX 602 ; N uni2511 ; G 2307
+U 9490 ; WX 602 ; N uni2512 ; G 2308
+U 9491 ; WX 602 ; N uni2513 ; G 2309
+U 9492 ; WX 602 ; N SF020000 ; G 2310
+U 9493 ; WX 602 ; N uni2515 ; G 2311
+U 9494 ; WX 602 ; N uni2516 ; G 2312
+U 9495 ; WX 602 ; N uni2517 ; G 2313
+U 9496 ; WX 602 ; N SF040000 ; G 2314
+U 9497 ; WX 602 ; N uni2519 ; G 2315
+U 9498 ; WX 602 ; N uni251A ; G 2316
+U 9499 ; WX 602 ; N uni251B ; G 2317
+U 9500 ; WX 602 ; N SF080000 ; G 2318
+U 9501 ; WX 602 ; N uni251D ; G 2319
+U 9502 ; WX 602 ; N uni251E ; G 2320
+U 9503 ; WX 602 ; N uni251F ; G 2321
+U 9504 ; WX 602 ; N uni2520 ; G 2322
+U 9505 ; WX 602 ; N uni2521 ; G 2323
+U 9506 ; WX 602 ; N uni2522 ; G 2324
+U 9507 ; WX 602 ; N uni2523 ; G 2325
+U 9508 ; WX 602 ; N SF090000 ; G 2326
+U 9509 ; WX 602 ; N uni2525 ; G 2327
+U 9510 ; WX 602 ; N uni2526 ; G 2328
+U 9511 ; WX 602 ; N uni2527 ; G 2329
+U 9512 ; WX 602 ; N uni2528 ; G 2330
+U 9513 ; WX 602 ; N uni2529 ; G 2331
+U 9514 ; WX 602 ; N uni252A ; G 2332
+U 9515 ; WX 602 ; N uni252B ; G 2333
+U 9516 ; WX 602 ; N SF060000 ; G 2334
+U 9517 ; WX 602 ; N uni252D ; G 2335
+U 9518 ; WX 602 ; N uni252E ; G 2336
+U 9519 ; WX 602 ; N uni252F ; G 2337
+U 9520 ; WX 602 ; N uni2530 ; G 2338
+U 9521 ; WX 602 ; N uni2531 ; G 2339
+U 9522 ; WX 602 ; N uni2532 ; G 2340
+U 9523 ; WX 602 ; N uni2533 ; G 2341
+U 9524 ; WX 602 ; N SF070000 ; G 2342
+U 9525 ; WX 602 ; N uni2535 ; G 2343
+U 9526 ; WX 602 ; N uni2536 ; G 2344
+U 9527 ; WX 602 ; N uni2537 ; G 2345
+U 9528 ; WX 602 ; N uni2538 ; G 2346
+U 9529 ; WX 602 ; N uni2539 ; G 2347
+U 9530 ; WX 602 ; N uni253A ; G 2348
+U 9531 ; WX 602 ; N uni253B ; G 2349
+U 9532 ; WX 602 ; N SF050000 ; G 2350
+U 9533 ; WX 602 ; N uni253D ; G 2351
+U 9534 ; WX 602 ; N uni253E ; G 2352
+U 9535 ; WX 602 ; N uni253F ; G 2353
+U 9536 ; WX 602 ; N uni2540 ; G 2354
+U 9537 ; WX 602 ; N uni2541 ; G 2355
+U 9538 ; WX 602 ; N uni2542 ; G 2356
+U 9539 ; WX 602 ; N uni2543 ; G 2357
+U 9540 ; WX 602 ; N uni2544 ; G 2358
+U 9541 ; WX 602 ; N uni2545 ; G 2359
+U 9542 ; WX 602 ; N uni2546 ; G 2360
+U 9543 ; WX 602 ; N uni2547 ; G 2361
+U 9544 ; WX 602 ; N uni2548 ; G 2362
+U 9545 ; WX 602 ; N uni2549 ; G 2363
+U 9546 ; WX 602 ; N uni254A ; G 2364
+U 9547 ; WX 602 ; N uni254B ; G 2365
+U 9548 ; WX 602 ; N uni254C ; G 2366
+U 9549 ; WX 602 ; N uni254D ; G 2367
+U 9550 ; WX 602 ; N uni254E ; G 2368
+U 9551 ; WX 602 ; N uni254F ; G 2369
+U 9552 ; WX 602 ; N SF430000 ; G 2370
+U 9553 ; WX 602 ; N SF240000 ; G 2371
+U 9554 ; WX 602 ; N SF510000 ; G 2372
+U 9555 ; WX 602 ; N SF520000 ; G 2373
+U 9556 ; WX 602 ; N SF390000 ; G 2374
+U 9557 ; WX 602 ; N SF220000 ; G 2375
+U 9558 ; WX 602 ; N SF210000 ; G 2376
+U 9559 ; WX 602 ; N SF250000 ; G 2377
+U 9560 ; WX 602 ; N SF500000 ; G 2378
+U 9561 ; WX 602 ; N SF490000 ; G 2379
+U 9562 ; WX 602 ; N SF380000 ; G 2380
+U 9563 ; WX 602 ; N SF280000 ; G 2381
+U 9564 ; WX 602 ; N SF270000 ; G 2382
+U 9565 ; WX 602 ; N SF260000 ; G 2383
+U 9566 ; WX 602 ; N SF360000 ; G 2384
+U 9567 ; WX 602 ; N SF370000 ; G 2385
+U 9568 ; WX 602 ; N SF420000 ; G 2386
+U 9569 ; WX 602 ; N SF190000 ; G 2387
+U 9570 ; WX 602 ; N SF200000 ; G 2388
+U 9571 ; WX 602 ; N SF230000 ; G 2389
+U 9572 ; WX 602 ; N SF470000 ; G 2390
+U 9573 ; WX 602 ; N SF480000 ; G 2391
+U 9574 ; WX 602 ; N SF410000 ; G 2392
+U 9575 ; WX 602 ; N SF450000 ; G 2393
+U 9576 ; WX 602 ; N SF460000 ; G 2394
+U 9577 ; WX 602 ; N SF400000 ; G 2395
+U 9578 ; WX 602 ; N SF540000 ; G 2396
+U 9579 ; WX 602 ; N SF530000 ; G 2397
+U 9580 ; WX 602 ; N SF440000 ; G 2398
+U 9581 ; WX 602 ; N uni256D ; G 2399
+U 9582 ; WX 602 ; N uni256E ; G 2400
+U 9583 ; WX 602 ; N uni256F ; G 2401
+U 9584 ; WX 602 ; N uni2570 ; G 2402
+U 9585 ; WX 602 ; N uni2571 ; G 2403
+U 9586 ; WX 602 ; N uni2572 ; G 2404
+U 9587 ; WX 602 ; N uni2573 ; G 2405
+U 9588 ; WX 602 ; N uni2574 ; G 2406
+U 9589 ; WX 602 ; N uni2575 ; G 2407
+U 9590 ; WX 602 ; N uni2576 ; G 2408
+U 9591 ; WX 602 ; N uni2577 ; G 2409
+U 9592 ; WX 602 ; N uni2578 ; G 2410
+U 9593 ; WX 602 ; N uni2579 ; G 2411
+U 9594 ; WX 602 ; N uni257A ; G 2412
+U 9595 ; WX 602 ; N uni257B ; G 2413
+U 9596 ; WX 602 ; N uni257C ; G 2414
+U 9597 ; WX 602 ; N uni257D ; G 2415
+U 9598 ; WX 602 ; N uni257E ; G 2416
+U 9599 ; WX 602 ; N uni257F ; G 2417
+U 9600 ; WX 602 ; N upblock ; G 2418
+U 9601 ; WX 602 ; N uni2581 ; G 2419
+U 9602 ; WX 602 ; N uni2582 ; G 2420
+U 9603 ; WX 602 ; N uni2583 ; G 2421
+U 9604 ; WX 602 ; N dnblock ; G 2422
+U 9605 ; WX 602 ; N uni2585 ; G 2423
+U 9606 ; WX 602 ; N uni2586 ; G 2424
+U 9607 ; WX 602 ; N uni2587 ; G 2425
+U 9608 ; WX 602 ; N block ; G 2426
+U 9609 ; WX 602 ; N uni2589 ; G 2427
+U 9610 ; WX 602 ; N uni258A ; G 2428
+U 9611 ; WX 602 ; N uni258B ; G 2429
+U 9612 ; WX 602 ; N lfblock ; G 2430
+U 9613 ; WX 602 ; N uni258D ; G 2431
+U 9614 ; WX 602 ; N uni258E ; G 2432
+U 9615 ; WX 602 ; N uni258F ; G 2433
+U 9616 ; WX 602 ; N rtblock ; G 2434
+U 9617 ; WX 602 ; N ltshade ; G 2435
+U 9618 ; WX 602 ; N shade ; G 2436
+U 9619 ; WX 602 ; N dkshade ; G 2437
+U 9620 ; WX 602 ; N uni2594 ; G 2438
+U 9621 ; WX 602 ; N uni2595 ; G 2439
+U 9622 ; WX 602 ; N uni2596 ; G 2440
+U 9623 ; WX 602 ; N uni2597 ; G 2441
+U 9624 ; WX 602 ; N uni2598 ; G 2442
+U 9625 ; WX 602 ; N uni2599 ; G 2443
+U 9626 ; WX 602 ; N uni259A ; G 2444
+U 9627 ; WX 602 ; N uni259B ; G 2445
+U 9628 ; WX 602 ; N uni259C ; G 2446
+U 9629 ; WX 602 ; N uni259D ; G 2447
+U 9630 ; WX 602 ; N uni259E ; G 2448
+U 9631 ; WX 602 ; N uni259F ; G 2449
+U 9632 ; WX 602 ; N filledbox ; G 2450
+U 9633 ; WX 602 ; N H22073 ; G 2451
+U 9634 ; WX 602 ; N uni25A2 ; G 2452
+U 9635 ; WX 602 ; N uni25A3 ; G 2453
+U 9636 ; WX 602 ; N uni25A4 ; G 2454
+U 9637 ; WX 602 ; N uni25A5 ; G 2455
+U 9638 ; WX 602 ; N uni25A6 ; G 2456
+U 9639 ; WX 602 ; N uni25A7 ; G 2457
+U 9640 ; WX 602 ; N uni25A8 ; G 2458
+U 9641 ; WX 602 ; N uni25A9 ; G 2459
+U 9642 ; WX 602 ; N H18543 ; G 2460
+U 9643 ; WX 602 ; N H18551 ; G 2461
+U 9644 ; WX 602 ; N filledrect ; G 2462
+U 9645 ; WX 602 ; N uni25AD ; G 2463
+U 9646 ; WX 602 ; N uni25AE ; G 2464
+U 9647 ; WX 602 ; N uni25AF ; G 2465
+U 9648 ; WX 602 ; N uni25B0 ; G 2466
+U 9649 ; WX 602 ; N uni25B1 ; G 2467
+U 9650 ; WX 602 ; N triagup ; G 2468
+U 9651 ; WX 602 ; N uni25B3 ; G 2469
+U 9652 ; WX 602 ; N uni25B4 ; G 2470
+U 9653 ; WX 602 ; N uni25B5 ; G 2471
+U 9654 ; WX 602 ; N uni25B6 ; G 2472
+U 9655 ; WX 602 ; N uni25B7 ; G 2473
+U 9656 ; WX 602 ; N uni25B8 ; G 2474
+U 9657 ; WX 602 ; N uni25B9 ; G 2475
+U 9658 ; WX 602 ; N triagrt ; G 2476
+U 9659 ; WX 602 ; N uni25BB ; G 2477
+U 9660 ; WX 602 ; N triagdn ; G 2478
+U 9661 ; WX 602 ; N uni25BD ; G 2479
+U 9662 ; WX 602 ; N uni25BE ; G 2480
+U 9663 ; WX 602 ; N uni25BF ; G 2481
+U 9664 ; WX 602 ; N uni25C0 ; G 2482
+U 9665 ; WX 602 ; N uni25C1 ; G 2483
+U 9666 ; WX 602 ; N uni25C2 ; G 2484
+U 9667 ; WX 602 ; N uni25C3 ; G 2485
+U 9668 ; WX 602 ; N triaglf ; G 2486
+U 9669 ; WX 602 ; N uni25C5 ; G 2487
+U 9670 ; WX 602 ; N uni25C6 ; G 2488
+U 9671 ; WX 602 ; N uni25C7 ; G 2489
+U 9672 ; WX 602 ; N uni25C8 ; G 2490
+U 9673 ; WX 602 ; N uni25C9 ; G 2491
+U 9674 ; WX 602 ; N lozenge ; G 2492
+U 9675 ; WX 602 ; N circle ; G 2493
+U 9676 ; WX 602 ; N uni25CC ; G 2494
+U 9677 ; WX 602 ; N uni25CD ; G 2495
+U 9678 ; WX 602 ; N uni25CE ; G 2496
+U 9679 ; WX 602 ; N H18533 ; G 2497
+U 9680 ; WX 602 ; N uni25D0 ; G 2498
+U 9681 ; WX 602 ; N uni25D1 ; G 2499
+U 9682 ; WX 602 ; N uni25D2 ; G 2500
+U 9683 ; WX 602 ; N uni25D3 ; G 2501
+U 9684 ; WX 602 ; N uni25D4 ; G 2502
+U 9685 ; WX 602 ; N uni25D5 ; G 2503
+U 9686 ; WX 602 ; N uni25D6 ; G 2504
+U 9687 ; WX 602 ; N uni25D7 ; G 2505
+U 9688 ; WX 602 ; N invbullet ; G 2506
+U 9689 ; WX 602 ; N invcircle ; G 2507
+U 9690 ; WX 602 ; N uni25DA ; G 2508
+U 9691 ; WX 602 ; N uni25DB ; G 2509
+U 9692 ; WX 602 ; N uni25DC ; G 2510
+U 9693 ; WX 602 ; N uni25DD ; G 2511
+U 9694 ; WX 602 ; N uni25DE ; G 2512
+U 9695 ; WX 602 ; N uni25DF ; G 2513
+U 9696 ; WX 602 ; N uni25E0 ; G 2514
+U 9697 ; WX 602 ; N uni25E1 ; G 2515
+U 9698 ; WX 602 ; N uni25E2 ; G 2516
+U 9699 ; WX 602 ; N uni25E3 ; G 2517
+U 9700 ; WX 602 ; N uni25E4 ; G 2518
+U 9701 ; WX 602 ; N uni25E5 ; G 2519
+U 9702 ; WX 602 ; N openbullet ; G 2520
+U 9703 ; WX 602 ; N uni25E7 ; G 2521
+U 9704 ; WX 602 ; N uni25E8 ; G 2522
+U 9705 ; WX 602 ; N uni25E9 ; G 2523
+U 9706 ; WX 602 ; N uni25EA ; G 2524
+U 9707 ; WX 602 ; N uni25EB ; G 2525
+U 9708 ; WX 602 ; N uni25EC ; G 2526
+U 9709 ; WX 602 ; N uni25ED ; G 2527
+U 9710 ; WX 602 ; N uni25EE ; G 2528
+U 9711 ; WX 602 ; N uni25EF ; G 2529
+U 9712 ; WX 602 ; N uni25F0 ; G 2530
+U 9713 ; WX 602 ; N uni25F1 ; G 2531
+U 9714 ; WX 602 ; N uni25F2 ; G 2532
+U 9715 ; WX 602 ; N uni25F3 ; G 2533
+U 9716 ; WX 602 ; N uni25F4 ; G 2534
+U 9717 ; WX 602 ; N uni25F5 ; G 2535
+U 9718 ; WX 602 ; N uni25F6 ; G 2536
+U 9719 ; WX 602 ; N uni25F7 ; G 2537
+U 9720 ; WX 602 ; N uni25F8 ; G 2538
+U 9721 ; WX 602 ; N uni25F9 ; G 2539
+U 9722 ; WX 602 ; N uni25FA ; G 2540
+U 9723 ; WX 602 ; N uni25FB ; G 2541
+U 9724 ; WX 602 ; N uni25FC ; G 2542
+U 9725 ; WX 602 ; N uni25FD ; G 2543
+U 9726 ; WX 602 ; N uni25FE ; G 2544
+U 9727 ; WX 602 ; N uni25FF ; G 2545
+U 9728 ; WX 602 ; N uni2600 ; G 2546
+U 9784 ; WX 602 ; N uni2638 ; G 2547
+U 9785 ; WX 602 ; N uni2639 ; G 2548
+U 9786 ; WX 602 ; N smileface ; G 2549
+U 9787 ; WX 602 ; N invsmileface ; G 2550
+U 9788 ; WX 602 ; N sun ; G 2551
+U 9791 ; WX 602 ; N uni263F ; G 2552
+U 9792 ; WX 602 ; N female ; G 2553
+U 9793 ; WX 602 ; N uni2641 ; G 2554
+U 9794 ; WX 602 ; N male ; G 2555
+U 9795 ; WX 602 ; N uni2643 ; G 2556
+U 9796 ; WX 602 ; N uni2644 ; G 2557
+U 9797 ; WX 602 ; N uni2645 ; G 2558
+U 9798 ; WX 602 ; N uni2646 ; G 2559
+U 9799 ; WX 602 ; N uni2647 ; G 2560
+U 9824 ; WX 602 ; N spade ; G 2561
+U 9825 ; WX 602 ; N uni2661 ; G 2562
+U 9826 ; WX 602 ; N uni2662 ; G 2563
+U 9827 ; WX 602 ; N club ; G 2564
+U 9828 ; WX 602 ; N uni2664 ; G 2565
+U 9829 ; WX 602 ; N heart ; G 2566
+U 9830 ; WX 602 ; N diamond ; G 2567
+U 9831 ; WX 602 ; N uni2667 ; G 2568
+U 9833 ; WX 602 ; N uni2669 ; G 2569
+U 9834 ; WX 602 ; N musicalnote ; G 2570
+U 9835 ; WX 602 ; N musicalnotedbl ; G 2571
+U 9836 ; WX 602 ; N uni266C ; G 2572
+U 9837 ; WX 602 ; N uni266D ; G 2573
+U 9838 ; WX 602 ; N uni266E ; G 2574
+U 9839 ; WX 602 ; N uni266F ; G 2575
+U 10178 ; WX 602 ; N uni27C2 ; G 2576
+U 10181 ; WX 602 ; N uni27C5 ; G 2577
+U 10182 ; WX 602 ; N uni27C6 ; G 2578
+U 10204 ; WX 602 ; N uni27DC ; G 2579
+U 10208 ; WX 602 ; N uni27E0 ; G 2580
+U 10214 ; WX 602 ; N uni27E6 ; G 2581
+U 10215 ; WX 602 ; N uni27E7 ; G 2582
+U 10216 ; WX 602 ; N uni27E8 ; G 2583
+U 10217 ; WX 602 ; N uni27E9 ; G 2584
+U 10218 ; WX 602 ; N uni27EA ; G 2585
+U 10219 ; WX 602 ; N uni27EB ; G 2586
+U 10229 ; WX 602 ; N uni27F5 ; G 2587
+U 10230 ; WX 602 ; N uni27F6 ; G 2588
+U 10231 ; WX 602 ; N uni27F7 ; G 2589
+U 10631 ; WX 602 ; N uni2987 ; G 2590
+U 10632 ; WX 602 ; N uni2988 ; G 2591
+U 10647 ; WX 602 ; N uni2997 ; G 2592
+U 10648 ; WX 602 ; N uni2998 ; G 2593
+U 10731 ; WX 602 ; N uni29EB ; G 2594
+U 10746 ; WX 602 ; N uni29FA ; G 2595
+U 10747 ; WX 602 ; N uni29FB ; G 2596
+U 10752 ; WX 602 ; N uni2A00 ; G 2597
+U 10799 ; WX 602 ; N uni2A2F ; G 2598
+U 10858 ; WX 602 ; N uni2A6A ; G 2599
+U 10859 ; WX 602 ; N uni2A6B ; G 2600
+U 11013 ; WX 602 ; N uni2B05 ; G 2601
+U 11014 ; WX 602 ; N uni2B06 ; G 2602
+U 11015 ; WX 602 ; N uni2B07 ; G 2603
+U 11016 ; WX 602 ; N uni2B08 ; G 2604
+U 11017 ; WX 602 ; N uni2B09 ; G 2605
+U 11018 ; WX 602 ; N uni2B0A ; G 2606
+U 11019 ; WX 602 ; N uni2B0B ; G 2607
+U 11020 ; WX 602 ; N uni2B0C ; G 2608
+U 11021 ; WX 602 ; N uni2B0D ; G 2609
+U 11026 ; WX 602 ; N uni2B12 ; G 2610
+U 11027 ; WX 602 ; N uni2B13 ; G 2611
+U 11028 ; WX 602 ; N uni2B14 ; G 2612
+U 11029 ; WX 602 ; N uni2B15 ; G 2613
+U 11030 ; WX 602 ; N uni2B16 ; G 2614
+U 11031 ; WX 602 ; N uni2B17 ; G 2615
+U 11032 ; WX 602 ; N uni2B18 ; G 2616
+U 11033 ; WX 602 ; N uni2B19 ; G 2617
+U 11034 ; WX 602 ; N uni2B1A ; G 2618
+U 11364 ; WX 602 ; N uni2C64 ; G 2619
+U 11373 ; WX 602 ; N uni2C6D ; G 2620
+U 11374 ; WX 602 ; N uni2C6E ; G 2621
+U 11375 ; WX 602 ; N uni2C6F ; G 2622
+U 11376 ; WX 602 ; N uni2C70 ; G 2623
+U 11381 ; WX 602 ; N uni2C75 ; G 2624
+U 11382 ; WX 602 ; N uni2C76 ; G 2625
+U 11383 ; WX 602 ; N uni2C77 ; G 2626
+U 11385 ; WX 602 ; N uni2C79 ; G 2627
+U 11386 ; WX 602 ; N uni2C7A ; G 2628
+U 11388 ; WX 602 ; N uni2C7C ; G 2629
+U 11389 ; WX 602 ; N uni2C7D ; G 2630
+U 11390 ; WX 602 ; N uni2C7E ; G 2631
+U 11391 ; WX 602 ; N uni2C7F ; G 2632
+U 11800 ; WX 602 ; N uni2E18 ; G 2633
+U 11807 ; WX 602 ; N uni2E1F ; G 2634
+U 11810 ; WX 602 ; N uni2E22 ; G 2635
+U 11811 ; WX 602 ; N uni2E23 ; G 2636
+U 11812 ; WX 602 ; N uni2E24 ; G 2637
+U 11813 ; WX 602 ; N uni2E25 ; G 2638
+U 11822 ; WX 602 ; N uni2E2E ; G 2639
+U 42760 ; WX 602 ; N uniA708 ; G 2640
+U 42761 ; WX 602 ; N uniA709 ; G 2641
+U 42762 ; WX 602 ; N uniA70A ; G 2642
+U 42763 ; WX 602 ; N uniA70B ; G 2643
+U 42764 ; WX 602 ; N uniA70C ; G 2644
+U 42765 ; WX 602 ; N uniA70D ; G 2645
+U 42766 ; WX 602 ; N uniA70E ; G 2646
+U 42767 ; WX 602 ; N uniA70F ; G 2647
+U 42768 ; WX 602 ; N uniA710 ; G 2648
+U 42769 ; WX 602 ; N uniA711 ; G 2649
+U 42770 ; WX 602 ; N uniA712 ; G 2650
+U 42771 ; WX 602 ; N uniA713 ; G 2651
+U 42772 ; WX 602 ; N uniA714 ; G 2652
+U 42773 ; WX 602 ; N uniA715 ; G 2653
+U 42774 ; WX 602 ; N uniA716 ; G 2654
+U 42779 ; WX 602 ; N uniA71B ; G 2655
+U 42780 ; WX 602 ; N uniA71C ; G 2656
+U 42781 ; WX 602 ; N uniA71D ; G 2657
+U 42782 ; WX 602 ; N uniA71E ; G 2658
+U 42783 ; WX 602 ; N uniA71F ; G 2659
+U 42786 ; WX 602 ; N uniA722 ; G 2660
+U 42787 ; WX 602 ; N uniA723 ; G 2661
+U 42788 ; WX 602 ; N uniA724 ; G 2662
+U 42789 ; WX 602 ; N uniA725 ; G 2663
+U 42790 ; WX 602 ; N uniA726 ; G 2664
+U 42791 ; WX 602 ; N uniA727 ; G 2665
+U 42889 ; WX 602 ; N uniA789 ; G 2666
+U 42890 ; WX 602 ; N uniA78A ; G 2667
+U 42891 ; WX 602 ; N uniA78B ; G 2668
+U 42892 ; WX 602 ; N uniA78C ; G 2669
+U 42893 ; WX 602 ; N uniA78D ; G 2670
+U 42894 ; WX 602 ; N uniA78E ; G 2671
+U 42896 ; WX 602 ; N uniA790 ; G 2672
+U 42897 ; WX 602 ; N uniA791 ; G 2673
+U 42922 ; WX 602 ; N uniA7AA ; G 2674
+U 43000 ; WX 602 ; N uniA7F8 ; G 2675
+U 43001 ; WX 602 ; N uniA7F9 ; G 2676
+U 63173 ; WX 602 ; N uniF6C5 ; G 2677
+U 64257 ; WX 602 ; N fi ; G 2678
+U 64258 ; WX 602 ; N fl ; G 2679
+U 65529 ; WX 602 ; N uniFFF9 ; G 2680
+U 65530 ; WX 602 ; N uniFFFA ; G 2681
+U 65531 ; WX 602 ; N uniFFFB ; G 2682
+U 65532 ; WX 602 ; N uniFFFC ; G 2683
+U 65533 ; WX 602 ; N uniFFFD ; G 2684
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ttf
new file mode 100644
index 0000000..f578602
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ufm
new file mode 100644
index 0000000..6b2d4ac
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSansMono.ufm
@@ -0,0 +1,3284 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Sans Mono
+FontSubfamily Book
+UniqueID DejaVu Sans Mono
+FullName DejaVu Sans Mono
+Version Version 2.37
+PostScriptName DejaVuSansMono
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+Weight Medium
+ItalicAngle 0
+IsFixedPitch true
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -558 -375 718 1028
+StartCharMetrics 3377
+U 32 ; WX 602 ; N space ; G 3
+U 33 ; WX 602 ; N exclam ; G 4
+U 34 ; WX 602 ; N quotedbl ; G 5
+U 35 ; WX 602 ; N numbersign ; G 6
+U 36 ; WX 602 ; N dollar ; G 7
+U 37 ; WX 602 ; N percent ; G 8
+U 38 ; WX 602 ; N ampersand ; G 9
+U 39 ; WX 602 ; N quotesingle ; G 10
+U 40 ; WX 602 ; N parenleft ; G 11
+U 41 ; WX 602 ; N parenright ; G 12
+U 42 ; WX 602 ; N asterisk ; G 13
+U 43 ; WX 602 ; N plus ; G 14
+U 44 ; WX 602 ; N comma ; G 15
+U 45 ; WX 602 ; N hyphen ; G 16
+U 46 ; WX 602 ; N period ; G 17
+U 47 ; WX 602 ; N slash ; G 18
+U 48 ; WX 602 ; N zero ; G 19
+U 49 ; WX 602 ; N one ; G 20
+U 50 ; WX 602 ; N two ; G 21
+U 51 ; WX 602 ; N three ; G 22
+U 52 ; WX 602 ; N four ; G 23
+U 53 ; WX 602 ; N five ; G 24
+U 54 ; WX 602 ; N six ; G 25
+U 55 ; WX 602 ; N seven ; G 26
+U 56 ; WX 602 ; N eight ; G 27
+U 57 ; WX 602 ; N nine ; G 28
+U 58 ; WX 602 ; N colon ; G 29
+U 59 ; WX 602 ; N semicolon ; G 30
+U 60 ; WX 602 ; N less ; G 31
+U 61 ; WX 602 ; N equal ; G 32
+U 62 ; WX 602 ; N greater ; G 33
+U 63 ; WX 602 ; N question ; G 34
+U 64 ; WX 602 ; N at ; G 35
+U 65 ; WX 602 ; N A ; G 36
+U 66 ; WX 602 ; N B ; G 37
+U 67 ; WX 602 ; N C ; G 38
+U 68 ; WX 602 ; N D ; G 39
+U 69 ; WX 602 ; N E ; G 40
+U 70 ; WX 602 ; N F ; G 41
+U 71 ; WX 602 ; N G ; G 42
+U 72 ; WX 602 ; N H ; G 43
+U 73 ; WX 602 ; N I ; G 44
+U 74 ; WX 602 ; N J ; G 45
+U 75 ; WX 602 ; N K ; G 46
+U 76 ; WX 602 ; N L ; G 47
+U 77 ; WX 602 ; N M ; G 48
+U 78 ; WX 602 ; N N ; G 49
+U 79 ; WX 602 ; N O ; G 50
+U 80 ; WX 602 ; N P ; G 51
+U 81 ; WX 602 ; N Q ; G 52
+U 82 ; WX 602 ; N R ; G 53
+U 83 ; WX 602 ; N S ; G 54
+U 84 ; WX 602 ; N T ; G 55
+U 85 ; WX 602 ; N U ; G 56
+U 86 ; WX 602 ; N V ; G 57
+U 87 ; WX 602 ; N W ; G 58
+U 88 ; WX 602 ; N X ; G 59
+U 89 ; WX 602 ; N Y ; G 60
+U 90 ; WX 602 ; N Z ; G 61
+U 91 ; WX 602 ; N bracketleft ; G 62
+U 92 ; WX 602 ; N backslash ; G 63
+U 93 ; WX 602 ; N bracketright ; G 64
+U 94 ; WX 602 ; N asciicircum ; G 65
+U 95 ; WX 602 ; N underscore ; G 66
+U 96 ; WX 602 ; N grave ; G 67
+U 97 ; WX 602 ; N a ; G 68
+U 98 ; WX 602 ; N b ; G 69
+U 99 ; WX 602 ; N c ; G 70
+U 100 ; WX 602 ; N d ; G 71
+U 101 ; WX 602 ; N e ; G 72
+U 102 ; WX 602 ; N f ; G 73
+U 103 ; WX 602 ; N g ; G 74
+U 104 ; WX 602 ; N h ; G 75
+U 105 ; WX 602 ; N i ; G 76
+U 106 ; WX 602 ; N j ; G 77
+U 107 ; WX 602 ; N k ; G 78
+U 108 ; WX 602 ; N l ; G 79
+U 109 ; WX 602 ; N m ; G 80
+U 110 ; WX 602 ; N n ; G 81
+U 111 ; WX 602 ; N o ; G 82
+U 112 ; WX 602 ; N p ; G 83
+U 113 ; WX 602 ; N q ; G 84
+U 114 ; WX 602 ; N r ; G 85
+U 115 ; WX 602 ; N s ; G 86
+U 116 ; WX 602 ; N t ; G 87
+U 117 ; WX 602 ; N u ; G 88
+U 118 ; WX 602 ; N v ; G 89
+U 119 ; WX 602 ; N w ; G 90
+U 120 ; WX 602 ; N x ; G 91
+U 121 ; WX 602 ; N y ; G 92
+U 122 ; WX 602 ; N z ; G 93
+U 123 ; WX 602 ; N braceleft ; G 94
+U 124 ; WX 602 ; N bar ; G 95
+U 125 ; WX 602 ; N braceright ; G 96
+U 126 ; WX 602 ; N asciitilde ; G 97
+U 160 ; WX 602 ; N nbspace ; G 98
+U 161 ; WX 602 ; N exclamdown ; G 99
+U 162 ; WX 602 ; N cent ; G 100
+U 163 ; WX 602 ; N sterling ; G 101
+U 164 ; WX 602 ; N currency ; G 102
+U 165 ; WX 602 ; N yen ; G 103
+U 166 ; WX 602 ; N brokenbar ; G 104
+U 167 ; WX 602 ; N section ; G 105
+U 168 ; WX 602 ; N dieresis ; G 106
+U 169 ; WX 602 ; N copyright ; G 107
+U 170 ; WX 602 ; N ordfeminine ; G 108
+U 171 ; WX 602 ; N guillemotleft ; G 109
+U 172 ; WX 602 ; N logicalnot ; G 110
+U 173 ; WX 602 ; N sfthyphen ; G 111
+U 174 ; WX 602 ; N registered ; G 112
+U 175 ; WX 602 ; N macron ; G 113
+U 176 ; WX 602 ; N degree ; G 114
+U 177 ; WX 602 ; N plusminus ; G 115
+U 178 ; WX 602 ; N twosuperior ; G 116
+U 179 ; WX 602 ; N threesuperior ; G 117
+U 180 ; WX 602 ; N acute ; G 118
+U 181 ; WX 602 ; N mu ; G 119
+U 182 ; WX 602 ; N paragraph ; G 120
+U 183 ; WX 602 ; N periodcentered ; G 121
+U 184 ; WX 602 ; N cedilla ; G 122
+U 185 ; WX 602 ; N onesuperior ; G 123
+U 186 ; WX 602 ; N ordmasculine ; G 124
+U 187 ; WX 602 ; N guillemotright ; G 125
+U 188 ; WX 602 ; N onequarter ; G 126
+U 189 ; WX 602 ; N onehalf ; G 127
+U 190 ; WX 602 ; N threequarters ; G 128
+U 191 ; WX 602 ; N questiondown ; G 129
+U 192 ; WX 602 ; N Agrave ; G 130
+U 193 ; WX 602 ; N Aacute ; G 131
+U 194 ; WX 602 ; N Acircumflex ; G 132
+U 195 ; WX 602 ; N Atilde ; G 133
+U 196 ; WX 602 ; N Adieresis ; G 134
+U 197 ; WX 602 ; N Aring ; G 135
+U 198 ; WX 602 ; N AE ; G 136
+U 199 ; WX 602 ; N Ccedilla ; G 137
+U 200 ; WX 602 ; N Egrave ; G 138
+U 201 ; WX 602 ; N Eacute ; G 139
+U 202 ; WX 602 ; N Ecircumflex ; G 140
+U 203 ; WX 602 ; N Edieresis ; G 141
+U 204 ; WX 602 ; N Igrave ; G 142
+U 205 ; WX 602 ; N Iacute ; G 143
+U 206 ; WX 602 ; N Icircumflex ; G 144
+U 207 ; WX 602 ; N Idieresis ; G 145
+U 208 ; WX 602 ; N Eth ; G 146
+U 209 ; WX 602 ; N Ntilde ; G 147
+U 210 ; WX 602 ; N Ograve ; G 148
+U 211 ; WX 602 ; N Oacute ; G 149
+U 212 ; WX 602 ; N Ocircumflex ; G 150
+U 213 ; WX 602 ; N Otilde ; G 151
+U 214 ; WX 602 ; N Odieresis ; G 152
+U 215 ; WX 602 ; N multiply ; G 153
+U 216 ; WX 602 ; N Oslash ; G 154
+U 217 ; WX 602 ; N Ugrave ; G 155
+U 218 ; WX 602 ; N Uacute ; G 156
+U 219 ; WX 602 ; N Ucircumflex ; G 157
+U 220 ; WX 602 ; N Udieresis ; G 158
+U 221 ; WX 602 ; N Yacute ; G 159
+U 222 ; WX 602 ; N Thorn ; G 160
+U 223 ; WX 602 ; N germandbls ; G 161
+U 224 ; WX 602 ; N agrave ; G 162
+U 225 ; WX 602 ; N aacute ; G 163
+U 226 ; WX 602 ; N acircumflex ; G 164
+U 227 ; WX 602 ; N atilde ; G 165
+U 228 ; WX 602 ; N adieresis ; G 166
+U 229 ; WX 602 ; N aring ; G 167
+U 230 ; WX 602 ; N ae ; G 168
+U 231 ; WX 602 ; N ccedilla ; G 169
+U 232 ; WX 602 ; N egrave ; G 170
+U 233 ; WX 602 ; N eacute ; G 171
+U 234 ; WX 602 ; N ecircumflex ; G 172
+U 235 ; WX 602 ; N edieresis ; G 173
+U 236 ; WX 602 ; N igrave ; G 174
+U 237 ; WX 602 ; N iacute ; G 175
+U 238 ; WX 602 ; N icircumflex ; G 176
+U 239 ; WX 602 ; N idieresis ; G 177
+U 240 ; WX 602 ; N eth ; G 178
+U 241 ; WX 602 ; N ntilde ; G 179
+U 242 ; WX 602 ; N ograve ; G 180
+U 243 ; WX 602 ; N oacute ; G 181
+U 244 ; WX 602 ; N ocircumflex ; G 182
+U 245 ; WX 602 ; N otilde ; G 183
+U 246 ; WX 602 ; N odieresis ; G 184
+U 247 ; WX 602 ; N divide ; G 185
+U 248 ; WX 602 ; N oslash ; G 186
+U 249 ; WX 602 ; N ugrave ; G 187
+U 250 ; WX 602 ; N uacute ; G 188
+U 251 ; WX 602 ; N ucircumflex ; G 189
+U 252 ; WX 602 ; N udieresis ; G 190
+U 253 ; WX 602 ; N yacute ; G 191
+U 254 ; WX 602 ; N thorn ; G 192
+U 255 ; WX 602 ; N ydieresis ; G 193
+U 256 ; WX 602 ; N Amacron ; G 194
+U 257 ; WX 602 ; N amacron ; G 195
+U 258 ; WX 602 ; N Abreve ; G 196
+U 259 ; WX 602 ; N abreve ; G 197
+U 260 ; WX 602 ; N Aogonek ; G 198
+U 261 ; WX 602 ; N aogonek ; G 199
+U 262 ; WX 602 ; N Cacute ; G 200
+U 263 ; WX 602 ; N cacute ; G 201
+U 264 ; WX 602 ; N Ccircumflex ; G 202
+U 265 ; WX 602 ; N ccircumflex ; G 203
+U 266 ; WX 602 ; N Cdotaccent ; G 204
+U 267 ; WX 602 ; N cdotaccent ; G 205
+U 268 ; WX 602 ; N Ccaron ; G 206
+U 269 ; WX 602 ; N ccaron ; G 207
+U 270 ; WX 602 ; N Dcaron ; G 208
+U 271 ; WX 602 ; N dcaron ; G 209
+U 272 ; WX 602 ; N Dcroat ; G 210
+U 273 ; WX 602 ; N dmacron ; G 211
+U 274 ; WX 602 ; N Emacron ; G 212
+U 275 ; WX 602 ; N emacron ; G 213
+U 276 ; WX 602 ; N Ebreve ; G 214
+U 277 ; WX 602 ; N ebreve ; G 215
+U 278 ; WX 602 ; N Edotaccent ; G 216
+U 279 ; WX 602 ; N edotaccent ; G 217
+U 280 ; WX 602 ; N Eogonek ; G 218
+U 281 ; WX 602 ; N eogonek ; G 219
+U 282 ; WX 602 ; N Ecaron ; G 220
+U 283 ; WX 602 ; N ecaron ; G 221
+U 284 ; WX 602 ; N Gcircumflex ; G 222
+U 285 ; WX 602 ; N gcircumflex ; G 223
+U 286 ; WX 602 ; N Gbreve ; G 224
+U 287 ; WX 602 ; N gbreve ; G 225
+U 288 ; WX 602 ; N Gdotaccent ; G 226
+U 289 ; WX 602 ; N gdotaccent ; G 227
+U 290 ; WX 602 ; N Gcommaaccent ; G 228
+U 291 ; WX 602 ; N gcommaaccent ; G 229
+U 292 ; WX 602 ; N Hcircumflex ; G 230
+U 293 ; WX 602 ; N hcircumflex ; G 231
+U 294 ; WX 602 ; N Hbar ; G 232
+U 295 ; WX 602 ; N hbar ; G 233
+U 296 ; WX 602 ; N Itilde ; G 234
+U 297 ; WX 602 ; N itilde ; G 235
+U 298 ; WX 602 ; N Imacron ; G 236
+U 299 ; WX 602 ; N imacron ; G 237
+U 300 ; WX 602 ; N Ibreve ; G 238
+U 301 ; WX 602 ; N ibreve ; G 239
+U 302 ; WX 602 ; N Iogonek ; G 240
+U 303 ; WX 602 ; N iogonek ; G 241
+U 304 ; WX 602 ; N Idot ; G 242
+U 305 ; WX 602 ; N dotlessi ; G 243
+U 306 ; WX 602 ; N IJ ; G 244
+U 307 ; WX 602 ; N ij ; G 245
+U 308 ; WX 602 ; N Jcircumflex ; G 246
+U 309 ; WX 602 ; N jcircumflex ; G 247
+U 310 ; WX 602 ; N Kcommaaccent ; G 248
+U 311 ; WX 602 ; N kcommaaccent ; G 249
+U 312 ; WX 602 ; N kgreenlandic ; G 250
+U 313 ; WX 602 ; N Lacute ; G 251
+U 314 ; WX 602 ; N lacute ; G 252
+U 315 ; WX 602 ; N Lcommaaccent ; G 253
+U 316 ; WX 602 ; N lcommaaccent ; G 254
+U 317 ; WX 602 ; N Lcaron ; G 255
+U 318 ; WX 602 ; N lcaron ; G 256
+U 319 ; WX 602 ; N Ldot ; G 257
+U 320 ; WX 602 ; N ldot ; G 258
+U 321 ; WX 602 ; N Lslash ; G 259
+U 322 ; WX 602 ; N lslash ; G 260
+U 323 ; WX 602 ; N Nacute ; G 261
+U 324 ; WX 602 ; N nacute ; G 262
+U 325 ; WX 602 ; N Ncommaaccent ; G 263
+U 326 ; WX 602 ; N ncommaaccent ; G 264
+U 327 ; WX 602 ; N Ncaron ; G 265
+U 328 ; WX 602 ; N ncaron ; G 266
+U 329 ; WX 602 ; N napostrophe ; G 267
+U 330 ; WX 602 ; N Eng ; G 268
+U 331 ; WX 602 ; N eng ; G 269
+U 332 ; WX 602 ; N Omacron ; G 270
+U 333 ; WX 602 ; N omacron ; G 271
+U 334 ; WX 602 ; N Obreve ; G 272
+U 335 ; WX 602 ; N obreve ; G 273
+U 336 ; WX 602 ; N Ohungarumlaut ; G 274
+U 337 ; WX 602 ; N ohungarumlaut ; G 275
+U 338 ; WX 602 ; N OE ; G 276
+U 339 ; WX 602 ; N oe ; G 277
+U 340 ; WX 602 ; N Racute ; G 278
+U 341 ; WX 602 ; N racute ; G 279
+U 342 ; WX 602 ; N Rcommaaccent ; G 280
+U 343 ; WX 602 ; N rcommaaccent ; G 281
+U 344 ; WX 602 ; N Rcaron ; G 282
+U 345 ; WX 602 ; N rcaron ; G 283
+U 346 ; WX 602 ; N Sacute ; G 284
+U 347 ; WX 602 ; N sacute ; G 285
+U 348 ; WX 602 ; N Scircumflex ; G 286
+U 349 ; WX 602 ; N scircumflex ; G 287
+U 350 ; WX 602 ; N Scedilla ; G 288
+U 351 ; WX 602 ; N scedilla ; G 289
+U 352 ; WX 602 ; N Scaron ; G 290
+U 353 ; WX 602 ; N scaron ; G 291
+U 354 ; WX 602 ; N Tcommaaccent ; G 292
+U 355 ; WX 602 ; N tcommaaccent ; G 293
+U 356 ; WX 602 ; N Tcaron ; G 294
+U 357 ; WX 602 ; N tcaron ; G 295
+U 358 ; WX 602 ; N Tbar ; G 296
+U 359 ; WX 602 ; N tbar ; G 297
+U 360 ; WX 602 ; N Utilde ; G 298
+U 361 ; WX 602 ; N utilde ; G 299
+U 362 ; WX 602 ; N Umacron ; G 300
+U 363 ; WX 602 ; N umacron ; G 301
+U 364 ; WX 602 ; N Ubreve ; G 302
+U 365 ; WX 602 ; N ubreve ; G 303
+U 366 ; WX 602 ; N Uring ; G 304
+U 367 ; WX 602 ; N uring ; G 305
+U 368 ; WX 602 ; N Uhungarumlaut ; G 306
+U 369 ; WX 602 ; N uhungarumlaut ; G 307
+U 370 ; WX 602 ; N Uogonek ; G 308
+U 371 ; WX 602 ; N uogonek ; G 309
+U 372 ; WX 602 ; N Wcircumflex ; G 310
+U 373 ; WX 602 ; N wcircumflex ; G 311
+U 374 ; WX 602 ; N Ycircumflex ; G 312
+U 375 ; WX 602 ; N ycircumflex ; G 313
+U 376 ; WX 602 ; N Ydieresis ; G 314
+U 377 ; WX 602 ; N Zacute ; G 315
+U 378 ; WX 602 ; N zacute ; G 316
+U 379 ; WX 602 ; N Zdotaccent ; G 317
+U 380 ; WX 602 ; N zdotaccent ; G 318
+U 381 ; WX 602 ; N Zcaron ; G 319
+U 382 ; WX 602 ; N zcaron ; G 320
+U 383 ; WX 602 ; N longs ; G 321
+U 384 ; WX 602 ; N uni0180 ; G 322
+U 385 ; WX 602 ; N uni0181 ; G 323
+U 386 ; WX 602 ; N uni0182 ; G 324
+U 387 ; WX 602 ; N uni0183 ; G 325
+U 388 ; WX 602 ; N uni0184 ; G 326
+U 389 ; WX 602 ; N uni0185 ; G 327
+U 390 ; WX 602 ; N uni0186 ; G 328
+U 391 ; WX 602 ; N uni0187 ; G 329
+U 392 ; WX 602 ; N uni0188 ; G 330
+U 393 ; WX 602 ; N uni0189 ; G 331
+U 394 ; WX 602 ; N uni018A ; G 332
+U 395 ; WX 602 ; N uni018B ; G 333
+U 396 ; WX 602 ; N uni018C ; G 334
+U 397 ; WX 602 ; N uni018D ; G 335
+U 398 ; WX 602 ; N uni018E ; G 336
+U 399 ; WX 602 ; N uni018F ; G 337
+U 400 ; WX 602 ; N uni0190 ; G 338
+U 401 ; WX 602 ; N uni0191 ; G 339
+U 402 ; WX 602 ; N florin ; G 340
+U 403 ; WX 602 ; N uni0193 ; G 341
+U 404 ; WX 602 ; N uni0194 ; G 342
+U 405 ; WX 602 ; N uni0195 ; G 343
+U 406 ; WX 602 ; N uni0196 ; G 344
+U 407 ; WX 602 ; N uni0197 ; G 345
+U 408 ; WX 602 ; N uni0198 ; G 346
+U 409 ; WX 602 ; N uni0199 ; G 347
+U 410 ; WX 602 ; N uni019A ; G 348
+U 411 ; WX 602 ; N uni019B ; G 349
+U 412 ; WX 602 ; N uni019C ; G 350
+U 413 ; WX 602 ; N uni019D ; G 351
+U 414 ; WX 602 ; N uni019E ; G 352
+U 415 ; WX 602 ; N uni019F ; G 353
+U 416 ; WX 602 ; N Ohorn ; G 354
+U 417 ; WX 602 ; N ohorn ; G 355
+U 418 ; WX 602 ; N uni01A2 ; G 356
+U 419 ; WX 602 ; N uni01A3 ; G 357
+U 420 ; WX 602 ; N uni01A4 ; G 358
+U 421 ; WX 602 ; N uni01A5 ; G 359
+U 422 ; WX 602 ; N uni01A6 ; G 360
+U 423 ; WX 602 ; N uni01A7 ; G 361
+U 424 ; WX 602 ; N uni01A8 ; G 362
+U 425 ; WX 602 ; N uni01A9 ; G 363
+U 426 ; WX 602 ; N uni01AA ; G 364
+U 427 ; WX 602 ; N uni01AB ; G 365
+U 428 ; WX 602 ; N uni01AC ; G 366
+U 429 ; WX 602 ; N uni01AD ; G 367
+U 430 ; WX 602 ; N uni01AE ; G 368
+U 431 ; WX 602 ; N Uhorn ; G 369
+U 432 ; WX 602 ; N uhorn ; G 370
+U 433 ; WX 602 ; N uni01B1 ; G 371
+U 434 ; WX 602 ; N uni01B2 ; G 372
+U 435 ; WX 602 ; N uni01B3 ; G 373
+U 436 ; WX 602 ; N uni01B4 ; G 374
+U 437 ; WX 602 ; N uni01B5 ; G 375
+U 438 ; WX 602 ; N uni01B6 ; G 376
+U 439 ; WX 602 ; N uni01B7 ; G 377
+U 440 ; WX 602 ; N uni01B8 ; G 378
+U 441 ; WX 602 ; N uni01B9 ; G 379
+U 442 ; WX 602 ; N uni01BA ; G 380
+U 443 ; WX 602 ; N uni01BB ; G 381
+U 444 ; WX 602 ; N uni01BC ; G 382
+U 445 ; WX 602 ; N uni01BD ; G 383
+U 446 ; WX 602 ; N uni01BE ; G 384
+U 447 ; WX 602 ; N uni01BF ; G 385
+U 448 ; WX 602 ; N uni01C0 ; G 386
+U 449 ; WX 602 ; N uni01C1 ; G 387
+U 450 ; WX 602 ; N uni01C2 ; G 388
+U 451 ; WX 602 ; N uni01C3 ; G 389
+U 461 ; WX 602 ; N uni01CD ; G 390
+U 462 ; WX 602 ; N uni01CE ; G 391
+U 463 ; WX 602 ; N uni01CF ; G 392
+U 464 ; WX 602 ; N uni01D0 ; G 393
+U 465 ; WX 602 ; N uni01D1 ; G 394
+U 466 ; WX 602 ; N uni01D2 ; G 395
+U 467 ; WX 602 ; N uni01D3 ; G 396
+U 468 ; WX 602 ; N uni01D4 ; G 397
+U 469 ; WX 602 ; N uni01D5 ; G 398
+U 470 ; WX 602 ; N uni01D6 ; G 399
+U 471 ; WX 602 ; N uni01D7 ; G 400
+U 472 ; WX 602 ; N uni01D8 ; G 401
+U 473 ; WX 602 ; N uni01D9 ; G 402
+U 474 ; WX 602 ; N uni01DA ; G 403
+U 475 ; WX 602 ; N uni01DB ; G 404
+U 476 ; WX 602 ; N uni01DC ; G 405
+U 477 ; WX 602 ; N uni01DD ; G 406
+U 478 ; WX 602 ; N uni01DE ; G 407
+U 479 ; WX 602 ; N uni01DF ; G 408
+U 480 ; WX 602 ; N uni01E0 ; G 409
+U 481 ; WX 602 ; N uni01E1 ; G 410
+U 482 ; WX 602 ; N uni01E2 ; G 411
+U 483 ; WX 602 ; N uni01E3 ; G 412
+U 486 ; WX 602 ; N Gcaron ; G 413
+U 487 ; WX 602 ; N gcaron ; G 414
+U 488 ; WX 602 ; N uni01E8 ; G 415
+U 489 ; WX 602 ; N uni01E9 ; G 416
+U 490 ; WX 602 ; N uni01EA ; G 417
+U 491 ; WX 602 ; N uni01EB ; G 418
+U 492 ; WX 602 ; N uni01EC ; G 419
+U 493 ; WX 602 ; N uni01ED ; G 420
+U 494 ; WX 602 ; N uni01EE ; G 421
+U 495 ; WX 602 ; N uni01EF ; G 422
+U 496 ; WX 602 ; N uni01F0 ; G 423
+U 500 ; WX 602 ; N uni01F4 ; G 424
+U 501 ; WX 602 ; N uni01F5 ; G 425
+U 502 ; WX 602 ; N uni01F6 ; G 426
+U 504 ; WX 602 ; N uni01F8 ; G 427
+U 505 ; WX 602 ; N uni01F9 ; G 428
+U 508 ; WX 602 ; N AEacute ; G 429
+U 509 ; WX 602 ; N aeacute ; G 430
+U 510 ; WX 602 ; N Oslashacute ; G 431
+U 511 ; WX 602 ; N oslashacute ; G 432
+U 512 ; WX 602 ; N uni0200 ; G 433
+U 513 ; WX 602 ; N uni0201 ; G 434
+U 514 ; WX 602 ; N uni0202 ; G 435
+U 515 ; WX 602 ; N uni0203 ; G 436
+U 516 ; WX 602 ; N uni0204 ; G 437
+U 517 ; WX 602 ; N uni0205 ; G 438
+U 518 ; WX 602 ; N uni0206 ; G 439
+U 519 ; WX 602 ; N uni0207 ; G 440
+U 520 ; WX 602 ; N uni0208 ; G 441
+U 521 ; WX 602 ; N uni0209 ; G 442
+U 522 ; WX 602 ; N uni020A ; G 443
+U 523 ; WX 602 ; N uni020B ; G 444
+U 524 ; WX 602 ; N uni020C ; G 445
+U 525 ; WX 602 ; N uni020D ; G 446
+U 526 ; WX 602 ; N uni020E ; G 447
+U 527 ; WX 602 ; N uni020F ; G 448
+U 528 ; WX 602 ; N uni0210 ; G 449
+U 529 ; WX 602 ; N uni0211 ; G 450
+U 530 ; WX 602 ; N uni0212 ; G 451
+U 531 ; WX 602 ; N uni0213 ; G 452
+U 532 ; WX 602 ; N uni0214 ; G 453
+U 533 ; WX 602 ; N uni0215 ; G 454
+U 534 ; WX 602 ; N uni0216 ; G 455
+U 535 ; WX 602 ; N uni0217 ; G 456
+U 536 ; WX 602 ; N Scommaaccent ; G 457
+U 537 ; WX 602 ; N scommaaccent ; G 458
+U 538 ; WX 602 ; N uni021A ; G 459
+U 539 ; WX 602 ; N uni021B ; G 460
+U 540 ; WX 602 ; N uni021C ; G 461
+U 541 ; WX 602 ; N uni021D ; G 462
+U 542 ; WX 602 ; N uni021E ; G 463
+U 543 ; WX 602 ; N uni021F ; G 464
+U 544 ; WX 602 ; N uni0220 ; G 465
+U 545 ; WX 602 ; N uni0221 ; G 466
+U 548 ; WX 602 ; N uni0224 ; G 467
+U 549 ; WX 602 ; N uni0225 ; G 468
+U 550 ; WX 602 ; N uni0226 ; G 469
+U 551 ; WX 602 ; N uni0227 ; G 470
+U 552 ; WX 602 ; N uni0228 ; G 471
+U 553 ; WX 602 ; N uni0229 ; G 472
+U 554 ; WX 602 ; N uni022A ; G 473
+U 555 ; WX 602 ; N uni022B ; G 474
+U 556 ; WX 602 ; N uni022C ; G 475
+U 557 ; WX 602 ; N uni022D ; G 476
+U 558 ; WX 602 ; N uni022E ; G 477
+U 559 ; WX 602 ; N uni022F ; G 478
+U 560 ; WX 602 ; N uni0230 ; G 479
+U 561 ; WX 602 ; N uni0231 ; G 480
+U 562 ; WX 602 ; N uni0232 ; G 481
+U 563 ; WX 602 ; N uni0233 ; G 482
+U 564 ; WX 602 ; N uni0234 ; G 483
+U 565 ; WX 602 ; N uni0235 ; G 484
+U 566 ; WX 602 ; N uni0236 ; G 485
+U 567 ; WX 602 ; N dotlessj ; G 486
+U 568 ; WX 602 ; N uni0238 ; G 487
+U 569 ; WX 602 ; N uni0239 ; G 488
+U 570 ; WX 602 ; N uni023A ; G 489
+U 571 ; WX 602 ; N uni023B ; G 490
+U 572 ; WX 602 ; N uni023C ; G 491
+U 573 ; WX 602 ; N uni023D ; G 492
+U 574 ; WX 602 ; N uni023E ; G 493
+U 575 ; WX 602 ; N uni023F ; G 494
+U 576 ; WX 602 ; N uni0240 ; G 495
+U 577 ; WX 602 ; N uni0241 ; G 496
+U 579 ; WX 602 ; N uni0243 ; G 497
+U 580 ; WX 602 ; N uni0244 ; G 498
+U 581 ; WX 602 ; N uni0245 ; G 499
+U 588 ; WX 602 ; N uni024C ; G 500
+U 589 ; WX 602 ; N uni024D ; G 501
+U 592 ; WX 602 ; N uni0250 ; G 502
+U 593 ; WX 602 ; N uni0251 ; G 503
+U 594 ; WX 602 ; N uni0252 ; G 504
+U 595 ; WX 602 ; N uni0253 ; G 505
+U 596 ; WX 602 ; N uni0254 ; G 506
+U 597 ; WX 602 ; N uni0255 ; G 507
+U 598 ; WX 602 ; N uni0256 ; G 508
+U 599 ; WX 602 ; N uni0257 ; G 509
+U 600 ; WX 602 ; N uni0258 ; G 510
+U 601 ; WX 602 ; N uni0259 ; G 511
+U 602 ; WX 602 ; N uni025A ; G 512
+U 603 ; WX 602 ; N uni025B ; G 513
+U 604 ; WX 602 ; N uni025C ; G 514
+U 605 ; WX 602 ; N uni025D ; G 515
+U 606 ; WX 602 ; N uni025E ; G 516
+U 607 ; WX 602 ; N uni025F ; G 517
+U 608 ; WX 602 ; N uni0260 ; G 518
+U 609 ; WX 602 ; N uni0261 ; G 519
+U 610 ; WX 602 ; N uni0262 ; G 520
+U 611 ; WX 602 ; N uni0263 ; G 521
+U 612 ; WX 602 ; N uni0264 ; G 522
+U 613 ; WX 602 ; N uni0265 ; G 523
+U 614 ; WX 602 ; N uni0266 ; G 524
+U 615 ; WX 602 ; N uni0267 ; G 525
+U 616 ; WX 602 ; N uni0268 ; G 526
+U 617 ; WX 602 ; N uni0269 ; G 527
+U 618 ; WX 602 ; N uni026A ; G 528
+U 619 ; WX 602 ; N uni026B ; G 529
+U 620 ; WX 602 ; N uni026C ; G 530
+U 621 ; WX 602 ; N uni026D ; G 531
+U 622 ; WX 602 ; N uni026E ; G 532
+U 623 ; WX 602 ; N uni026F ; G 533
+U 624 ; WX 602 ; N uni0270 ; G 534
+U 625 ; WX 602 ; N uni0271 ; G 535
+U 626 ; WX 602 ; N uni0272 ; G 536
+U 627 ; WX 602 ; N uni0273 ; G 537
+U 628 ; WX 602 ; N uni0274 ; G 538
+U 629 ; WX 602 ; N uni0275 ; G 539
+U 630 ; WX 602 ; N uni0276 ; G 540
+U 631 ; WX 602 ; N uni0277 ; G 541
+U 632 ; WX 602 ; N uni0278 ; G 542
+U 633 ; WX 602 ; N uni0279 ; G 543
+U 634 ; WX 602 ; N uni027A ; G 544
+U 635 ; WX 602 ; N uni027B ; G 545
+U 636 ; WX 602 ; N uni027C ; G 546
+U 637 ; WX 602 ; N uni027D ; G 547
+U 638 ; WX 602 ; N uni027E ; G 548
+U 639 ; WX 602 ; N uni027F ; G 549
+U 640 ; WX 602 ; N uni0280 ; G 550
+U 641 ; WX 602 ; N uni0281 ; G 551
+U 642 ; WX 602 ; N uni0282 ; G 552
+U 643 ; WX 602 ; N uni0283 ; G 553
+U 644 ; WX 602 ; N uni0284 ; G 554
+U 645 ; WX 602 ; N uni0285 ; G 555
+U 646 ; WX 602 ; N uni0286 ; G 556
+U 647 ; WX 602 ; N uni0287 ; G 557
+U 648 ; WX 602 ; N uni0288 ; G 558
+U 649 ; WX 602 ; N uni0289 ; G 559
+U 650 ; WX 602 ; N uni028A ; G 560
+U 651 ; WX 602 ; N uni028B ; G 561
+U 652 ; WX 602 ; N uni028C ; G 562
+U 653 ; WX 602 ; N uni028D ; G 563
+U 654 ; WX 602 ; N uni028E ; G 564
+U 655 ; WX 602 ; N uni028F ; G 565
+U 656 ; WX 602 ; N uni0290 ; G 566
+U 657 ; WX 602 ; N uni0291 ; G 567
+U 658 ; WX 602 ; N uni0292 ; G 568
+U 659 ; WX 602 ; N uni0293 ; G 569
+U 660 ; WX 602 ; N uni0294 ; G 570
+U 661 ; WX 602 ; N uni0295 ; G 571
+U 662 ; WX 602 ; N uni0296 ; G 572
+U 663 ; WX 602 ; N uni0297 ; G 573
+U 664 ; WX 602 ; N uni0298 ; G 574
+U 665 ; WX 602 ; N uni0299 ; G 575
+U 666 ; WX 602 ; N uni029A ; G 576
+U 667 ; WX 602 ; N uni029B ; G 577
+U 668 ; WX 602 ; N uni029C ; G 578
+U 669 ; WX 602 ; N uni029D ; G 579
+U 670 ; WX 602 ; N uni029E ; G 580
+U 671 ; WX 602 ; N uni029F ; G 581
+U 672 ; WX 602 ; N uni02A0 ; G 582
+U 673 ; WX 602 ; N uni02A1 ; G 583
+U 674 ; WX 602 ; N uni02A2 ; G 584
+U 675 ; WX 602 ; N uni02A3 ; G 585
+U 676 ; WX 602 ; N uni02A4 ; G 586
+U 677 ; WX 602 ; N uni02A5 ; G 587
+U 678 ; WX 602 ; N uni02A6 ; G 588
+U 679 ; WX 602 ; N uni02A7 ; G 589
+U 680 ; WX 602 ; N uni02A8 ; G 590
+U 681 ; WX 602 ; N uni02A9 ; G 591
+U 682 ; WX 602 ; N uni02AA ; G 592
+U 683 ; WX 602 ; N uni02AB ; G 593
+U 684 ; WX 602 ; N uni02AC ; G 594
+U 685 ; WX 602 ; N uni02AD ; G 595
+U 686 ; WX 602 ; N uni02AE ; G 596
+U 687 ; WX 602 ; N uni02AF ; G 597
+U 688 ; WX 602 ; N uni02B0 ; G 598
+U 689 ; WX 602 ; N uni02B1 ; G 599
+U 690 ; WX 602 ; N uni02B2 ; G 600
+U 691 ; WX 602 ; N uni02B3 ; G 601
+U 692 ; WX 602 ; N uni02B4 ; G 602
+U 693 ; WX 602 ; N uni02B5 ; G 603
+U 694 ; WX 602 ; N uni02B6 ; G 604
+U 695 ; WX 602 ; N uni02B7 ; G 605
+U 696 ; WX 602 ; N uni02B8 ; G 606
+U 697 ; WX 602 ; N uni02B9 ; G 607
+U 699 ; WX 602 ; N uni02BB ; G 608
+U 700 ; WX 602 ; N uni02BC ; G 609
+U 701 ; WX 602 ; N uni02BD ; G 610
+U 702 ; WX 602 ; N uni02BE ; G 611
+U 703 ; WX 602 ; N uni02BF ; G 612
+U 704 ; WX 602 ; N uni02C0 ; G 613
+U 705 ; WX 602 ; N uni02C1 ; G 614
+U 710 ; WX 602 ; N circumflex ; G 615
+U 711 ; WX 602 ; N caron ; G 616
+U 712 ; WX 602 ; N uni02C8 ; G 617
+U 713 ; WX 602 ; N uni02C9 ; G 618
+U 716 ; WX 602 ; N uni02CC ; G 619
+U 717 ; WX 602 ; N uni02CD ; G 620
+U 718 ; WX 602 ; N uni02CE ; G 621
+U 719 ; WX 602 ; N uni02CF ; G 622
+U 720 ; WX 602 ; N uni02D0 ; G 623
+U 721 ; WX 602 ; N uni02D1 ; G 624
+U 722 ; WX 602 ; N uni02D2 ; G 625
+U 723 ; WX 602 ; N uni02D3 ; G 626
+U 726 ; WX 602 ; N uni02D6 ; G 627
+U 727 ; WX 602 ; N uni02D7 ; G 628
+U 728 ; WX 602 ; N breve ; G 629
+U 729 ; WX 602 ; N dotaccent ; G 630
+U 730 ; WX 602 ; N ring ; G 631
+U 731 ; WX 602 ; N ogonek ; G 632
+U 732 ; WX 602 ; N tilde ; G 633
+U 733 ; WX 602 ; N hungarumlaut ; G 634
+U 734 ; WX 602 ; N uni02DE ; G 635
+U 736 ; WX 602 ; N uni02E0 ; G 636
+U 737 ; WX 602 ; N uni02E1 ; G 637
+U 738 ; WX 602 ; N uni02E2 ; G 638
+U 739 ; WX 602 ; N uni02E3 ; G 639
+U 740 ; WX 602 ; N uni02E4 ; G 640
+U 741 ; WX 602 ; N uni02E5 ; G 641
+U 742 ; WX 602 ; N uni02E6 ; G 642
+U 743 ; WX 602 ; N uni02E7 ; G 643
+U 744 ; WX 602 ; N uni02E8 ; G 644
+U 745 ; WX 602 ; N uni02E9 ; G 645
+U 750 ; WX 602 ; N uni02EE ; G 646
+U 755 ; WX 602 ; N uni02F3 ; G 647
+U 768 ; WX 602 ; N gravecomb ; G 648
+U 769 ; WX 602 ; N acutecomb ; G 649
+U 770 ; WX 602 ; N uni0302 ; G 650
+U 771 ; WX 602 ; N tildecomb ; G 651
+U 772 ; WX 602 ; N uni0304 ; G 652
+U 773 ; WX 602 ; N uni0305 ; G 653
+U 774 ; WX 602 ; N uni0306 ; G 654
+U 775 ; WX 602 ; N uni0307 ; G 655
+U 776 ; WX 602 ; N uni0308 ; G 656
+U 777 ; WX 602 ; N hookabovecomb ; G 657
+U 778 ; WX 602 ; N uni030A ; G 658
+U 779 ; WX 602 ; N uni030B ; G 659
+U 780 ; WX 602 ; N uni030C ; G 660
+U 781 ; WX 602 ; N uni030D ; G 661
+U 782 ; WX 602 ; N uni030E ; G 662
+U 783 ; WX 602 ; N uni030F ; G 663
+U 784 ; WX 602 ; N uni0310 ; G 664
+U 785 ; WX 602 ; N uni0311 ; G 665
+U 786 ; WX 602 ; N uni0312 ; G 666
+U 787 ; WX 602 ; N uni0313 ; G 667
+U 788 ; WX 602 ; N uni0314 ; G 668
+U 789 ; WX 602 ; N uni0315 ; G 669
+U 790 ; WX 602 ; N uni0316 ; G 670
+U 791 ; WX 602 ; N uni0317 ; G 671
+U 792 ; WX 602 ; N uni0318 ; G 672
+U 793 ; WX 602 ; N uni0319 ; G 673
+U 794 ; WX 602 ; N uni031A ; G 674
+U 795 ; WX 602 ; N uni031B ; G 675
+U 796 ; WX 602 ; N uni031C ; G 676
+U 797 ; WX 602 ; N uni031D ; G 677
+U 798 ; WX 602 ; N uni031E ; G 678
+U 799 ; WX 602 ; N uni031F ; G 679
+U 800 ; WX 602 ; N uni0320 ; G 680
+U 801 ; WX 602 ; N uni0321 ; G 681
+U 802 ; WX 602 ; N uni0322 ; G 682
+U 803 ; WX 602 ; N dotbelowcomb ; G 683
+U 804 ; WX 602 ; N uni0324 ; G 684
+U 805 ; WX 602 ; N uni0325 ; G 685
+U 806 ; WX 602 ; N uni0326 ; G 686
+U 807 ; WX 602 ; N uni0327 ; G 687
+U 808 ; WX 602 ; N uni0328 ; G 688
+U 809 ; WX 602 ; N uni0329 ; G 689
+U 810 ; WX 602 ; N uni032A ; G 690
+U 811 ; WX 602 ; N uni032B ; G 691
+U 812 ; WX 602 ; N uni032C ; G 692
+U 813 ; WX 602 ; N uni032D ; G 693
+U 814 ; WX 602 ; N uni032E ; G 694
+U 815 ; WX 602 ; N uni032F ; G 695
+U 816 ; WX 602 ; N uni0330 ; G 696
+U 817 ; WX 602 ; N uni0331 ; G 697
+U 818 ; WX 602 ; N uni0332 ; G 698
+U 819 ; WX 602 ; N uni0333 ; G 699
+U 820 ; WX 602 ; N uni0334 ; G 700
+U 821 ; WX 602 ; N uni0335 ; G 701
+U 822 ; WX 602 ; N uni0336 ; G 702
+U 823 ; WX 602 ; N uni0337 ; G 703
+U 824 ; WX 602 ; N uni0338 ; G 704
+U 825 ; WX 602 ; N uni0339 ; G 705
+U 826 ; WX 602 ; N uni033A ; G 706
+U 827 ; WX 602 ; N uni033B ; G 707
+U 828 ; WX 602 ; N uni033C ; G 708
+U 829 ; WX 602 ; N uni033D ; G 709
+U 830 ; WX 602 ; N uni033E ; G 710
+U 831 ; WX 602 ; N uni033F ; G 711
+U 835 ; WX 602 ; N uni0343 ; G 712
+U 856 ; WX 602 ; N uni0358 ; G 713
+U 865 ; WX 602 ; N uni0361 ; G 714
+U 884 ; WX 602 ; N uni0374 ; G 715
+U 885 ; WX 602 ; N uni0375 ; G 716
+U 886 ; WX 602 ; N uni0376 ; G 717
+U 887 ; WX 602 ; N uni0377 ; G 718
+U 890 ; WX 602 ; N uni037A ; G 719
+U 891 ; WX 602 ; N uni037B ; G 720
+U 892 ; WX 602 ; N uni037C ; G 721
+U 893 ; WX 602 ; N uni037D ; G 722
+U 894 ; WX 602 ; N uni037E ; G 723
+U 895 ; WX 602 ; N uni037F ; G 724
+U 900 ; WX 602 ; N tonos ; G 725
+U 901 ; WX 602 ; N dieresistonos ; G 726
+U 902 ; WX 602 ; N Alphatonos ; G 727
+U 903 ; WX 602 ; N anoteleia ; G 728
+U 904 ; WX 602 ; N Epsilontonos ; G 729
+U 905 ; WX 602 ; N Etatonos ; G 730
+U 906 ; WX 602 ; N Iotatonos ; G 731
+U 908 ; WX 602 ; N Omicrontonos ; G 732
+U 910 ; WX 602 ; N Upsilontonos ; G 733
+U 911 ; WX 602 ; N Omegatonos ; G 734
+U 912 ; WX 602 ; N iotadieresistonos ; G 735
+U 913 ; WX 602 ; N Alpha ; G 736
+U 914 ; WX 602 ; N Beta ; G 737
+U 915 ; WX 602 ; N Gamma ; G 738
+U 916 ; WX 602 ; N uni0394 ; G 739
+U 917 ; WX 602 ; N Epsilon ; G 740
+U 918 ; WX 602 ; N Zeta ; G 741
+U 919 ; WX 602 ; N Eta ; G 742
+U 920 ; WX 602 ; N Theta ; G 743
+U 921 ; WX 602 ; N Iota ; G 744
+U 922 ; WX 602 ; N Kappa ; G 745
+U 923 ; WX 602 ; N Lambda ; G 746
+U 924 ; WX 602 ; N Mu ; G 747
+U 925 ; WX 602 ; N Nu ; G 748
+U 926 ; WX 602 ; N Xi ; G 749
+U 927 ; WX 602 ; N Omicron ; G 750
+U 928 ; WX 602 ; N Pi ; G 751
+U 929 ; WX 602 ; N Rho ; G 752
+U 931 ; WX 602 ; N Sigma ; G 753
+U 932 ; WX 602 ; N Tau ; G 754
+U 933 ; WX 602 ; N Upsilon ; G 755
+U 934 ; WX 602 ; N Phi ; G 756
+U 935 ; WX 602 ; N Chi ; G 757
+U 936 ; WX 602 ; N Psi ; G 758
+U 937 ; WX 602 ; N Omega ; G 759
+U 938 ; WX 602 ; N Iotadieresis ; G 760
+U 939 ; WX 602 ; N Upsilondieresis ; G 761
+U 940 ; WX 602 ; N alphatonos ; G 762
+U 941 ; WX 602 ; N epsilontonos ; G 763
+U 942 ; WX 602 ; N etatonos ; G 764
+U 943 ; WX 602 ; N iotatonos ; G 765
+U 944 ; WX 602 ; N upsilondieresistonos ; G 766
+U 945 ; WX 602 ; N alpha ; G 767
+U 946 ; WX 602 ; N beta ; G 768
+U 947 ; WX 602 ; N gamma ; G 769
+U 948 ; WX 602 ; N delta ; G 770
+U 949 ; WX 602 ; N epsilon ; G 771
+U 950 ; WX 602 ; N zeta ; G 772
+U 951 ; WX 602 ; N eta ; G 773
+U 952 ; WX 602 ; N theta ; G 774
+U 953 ; WX 602 ; N iota ; G 775
+U 954 ; WX 602 ; N kappa ; G 776
+U 955 ; WX 602 ; N lambda ; G 777
+U 956 ; WX 602 ; N uni03BC ; G 778
+U 957 ; WX 602 ; N nu ; G 779
+U 958 ; WX 602 ; N xi ; G 780
+U 959 ; WX 602 ; N omicron ; G 781
+U 960 ; WX 602 ; N pi ; G 782
+U 961 ; WX 602 ; N rho ; G 783
+U 962 ; WX 602 ; N sigma1 ; G 784
+U 963 ; WX 602 ; N sigma ; G 785
+U 964 ; WX 602 ; N tau ; G 786
+U 965 ; WX 602 ; N upsilon ; G 787
+U 966 ; WX 602 ; N phi ; G 788
+U 967 ; WX 602 ; N chi ; G 789
+U 968 ; WX 602 ; N psi ; G 790
+U 969 ; WX 602 ; N omega ; G 791
+U 970 ; WX 602 ; N iotadieresis ; G 792
+U 971 ; WX 602 ; N upsilondieresis ; G 793
+U 972 ; WX 602 ; N omicrontonos ; G 794
+U 973 ; WX 602 ; N upsilontonos ; G 795
+U 974 ; WX 602 ; N omegatonos ; G 796
+U 976 ; WX 602 ; N uni03D0 ; G 797
+U 977 ; WX 602 ; N theta1 ; G 798
+U 978 ; WX 602 ; N Upsilon1 ; G 799
+U 979 ; WX 602 ; N uni03D3 ; G 800
+U 980 ; WX 602 ; N uni03D4 ; G 801
+U 981 ; WX 602 ; N phi1 ; G 802
+U 982 ; WX 602 ; N omega1 ; G 803
+U 983 ; WX 602 ; N uni03D7 ; G 804
+U 984 ; WX 602 ; N uni03D8 ; G 805
+U 985 ; WX 602 ; N uni03D9 ; G 806
+U 986 ; WX 602 ; N uni03DA ; G 807
+U 987 ; WX 602 ; N uni03DB ; G 808
+U 988 ; WX 602 ; N uni03DC ; G 809
+U 989 ; WX 602 ; N uni03DD ; G 810
+U 990 ; WX 602 ; N uni03DE ; G 811
+U 991 ; WX 602 ; N uni03DF ; G 812
+U 992 ; WX 602 ; N uni03E0 ; G 813
+U 993 ; WX 602 ; N uni03E1 ; G 814
+U 1008 ; WX 602 ; N uni03F0 ; G 815
+U 1009 ; WX 602 ; N uni03F1 ; G 816
+U 1010 ; WX 602 ; N uni03F2 ; G 817
+U 1011 ; WX 602 ; N uni03F3 ; G 818
+U 1012 ; WX 602 ; N uni03F4 ; G 819
+U 1013 ; WX 602 ; N uni03F5 ; G 820
+U 1014 ; WX 602 ; N uni03F6 ; G 821
+U 1015 ; WX 602 ; N uni03F7 ; G 822
+U 1016 ; WX 602 ; N uni03F8 ; G 823
+U 1017 ; WX 602 ; N uni03F9 ; G 824
+U 1018 ; WX 602 ; N uni03FA ; G 825
+U 1019 ; WX 602 ; N uni03FB ; G 826
+U 1020 ; WX 602 ; N uni03FC ; G 827
+U 1021 ; WX 602 ; N uni03FD ; G 828
+U 1022 ; WX 602 ; N uni03FE ; G 829
+U 1023 ; WX 602 ; N uni03FF ; G 830
+U 1024 ; WX 602 ; N uni0400 ; G 831
+U 1025 ; WX 602 ; N uni0401 ; G 832
+U 1026 ; WX 602 ; N uni0402 ; G 833
+U 1027 ; WX 602 ; N uni0403 ; G 834
+U 1028 ; WX 602 ; N uni0404 ; G 835
+U 1029 ; WX 602 ; N uni0405 ; G 836
+U 1030 ; WX 602 ; N uni0406 ; G 837
+U 1031 ; WX 602 ; N uni0407 ; G 838
+U 1032 ; WX 602 ; N uni0408 ; G 839
+U 1033 ; WX 602 ; N uni0409 ; G 840
+U 1034 ; WX 602 ; N uni040A ; G 841
+U 1035 ; WX 602 ; N uni040B ; G 842
+U 1036 ; WX 602 ; N uni040C ; G 843
+U 1037 ; WX 602 ; N uni040D ; G 844
+U 1038 ; WX 602 ; N uni040E ; G 845
+U 1039 ; WX 602 ; N uni040F ; G 846
+U 1040 ; WX 602 ; N uni0410 ; G 847
+U 1041 ; WX 602 ; N uni0411 ; G 848
+U 1042 ; WX 602 ; N uni0412 ; G 849
+U 1043 ; WX 602 ; N uni0413 ; G 850
+U 1044 ; WX 602 ; N uni0414 ; G 851
+U 1045 ; WX 602 ; N uni0415 ; G 852
+U 1046 ; WX 602 ; N uni0416 ; G 853
+U 1047 ; WX 602 ; N uni0417 ; G 854
+U 1048 ; WX 602 ; N uni0418 ; G 855
+U 1049 ; WX 602 ; N uni0419 ; G 856
+U 1050 ; WX 602 ; N uni041A ; G 857
+U 1051 ; WX 602 ; N uni041B ; G 858
+U 1052 ; WX 602 ; N uni041C ; G 859
+U 1053 ; WX 602 ; N uni041D ; G 860
+U 1054 ; WX 602 ; N uni041E ; G 861
+U 1055 ; WX 602 ; N uni041F ; G 862
+U 1056 ; WX 602 ; N uni0420 ; G 863
+U 1057 ; WX 602 ; N uni0421 ; G 864
+U 1058 ; WX 602 ; N uni0422 ; G 865
+U 1059 ; WX 602 ; N uni0423 ; G 866
+U 1060 ; WX 602 ; N uni0424 ; G 867
+U 1061 ; WX 602 ; N uni0425 ; G 868
+U 1062 ; WX 602 ; N uni0426 ; G 869
+U 1063 ; WX 602 ; N uni0427 ; G 870
+U 1064 ; WX 602 ; N uni0428 ; G 871
+U 1065 ; WX 602 ; N uni0429 ; G 872
+U 1066 ; WX 602 ; N uni042A ; G 873
+U 1067 ; WX 602 ; N uni042B ; G 874
+U 1068 ; WX 602 ; N uni042C ; G 875
+U 1069 ; WX 602 ; N uni042D ; G 876
+U 1070 ; WX 602 ; N uni042E ; G 877
+U 1071 ; WX 602 ; N uni042F ; G 878
+U 1072 ; WX 602 ; N uni0430 ; G 879
+U 1073 ; WX 602 ; N uni0431 ; G 880
+U 1074 ; WX 602 ; N uni0432 ; G 881
+U 1075 ; WX 602 ; N uni0433 ; G 882
+U 1076 ; WX 602 ; N uni0434 ; G 883
+U 1077 ; WX 602 ; N uni0435 ; G 884
+U 1078 ; WX 602 ; N uni0436 ; G 885
+U 1079 ; WX 602 ; N uni0437 ; G 886
+U 1080 ; WX 602 ; N uni0438 ; G 887
+U 1081 ; WX 602 ; N uni0439 ; G 888
+U 1082 ; WX 602 ; N uni043A ; G 889
+U 1083 ; WX 602 ; N uni043B ; G 890
+U 1084 ; WX 602 ; N uni043C ; G 891
+U 1085 ; WX 602 ; N uni043D ; G 892
+U 1086 ; WX 602 ; N uni043E ; G 893
+U 1087 ; WX 602 ; N uni043F ; G 894
+U 1088 ; WX 602 ; N uni0440 ; G 895
+U 1089 ; WX 602 ; N uni0441 ; G 896
+U 1090 ; WX 602 ; N uni0442 ; G 897
+U 1091 ; WX 602 ; N uni0443 ; G 898
+U 1092 ; WX 602 ; N uni0444 ; G 899
+U 1093 ; WX 602 ; N uni0445 ; G 900
+U 1094 ; WX 602 ; N uni0446 ; G 901
+U 1095 ; WX 602 ; N uni0447 ; G 902
+U 1096 ; WX 602 ; N uni0448 ; G 903
+U 1097 ; WX 602 ; N uni0449 ; G 904
+U 1098 ; WX 602 ; N uni044A ; G 905
+U 1099 ; WX 602 ; N uni044B ; G 906
+U 1100 ; WX 602 ; N uni044C ; G 907
+U 1101 ; WX 602 ; N uni044D ; G 908
+U 1102 ; WX 602 ; N uni044E ; G 909
+U 1103 ; WX 602 ; N uni044F ; G 910
+U 1104 ; WX 602 ; N uni0450 ; G 911
+U 1105 ; WX 602 ; N uni0451 ; G 912
+U 1106 ; WX 602 ; N uni0452 ; G 913
+U 1107 ; WX 602 ; N uni0453 ; G 914
+U 1108 ; WX 602 ; N uni0454 ; G 915
+U 1109 ; WX 602 ; N uni0455 ; G 916
+U 1110 ; WX 602 ; N uni0456 ; G 917
+U 1111 ; WX 602 ; N uni0457 ; G 918
+U 1112 ; WX 602 ; N uni0458 ; G 919
+U 1113 ; WX 602 ; N uni0459 ; G 920
+U 1114 ; WX 602 ; N uni045A ; G 921
+U 1115 ; WX 602 ; N uni045B ; G 922
+U 1116 ; WX 602 ; N uni045C ; G 923
+U 1117 ; WX 602 ; N uni045D ; G 924
+U 1118 ; WX 602 ; N uni045E ; G 925
+U 1119 ; WX 602 ; N uni045F ; G 926
+U 1122 ; WX 602 ; N uni0462 ; G 927
+U 1123 ; WX 602 ; N uni0463 ; G 928
+U 1138 ; WX 602 ; N uni0472 ; G 929
+U 1139 ; WX 602 ; N uni0473 ; G 930
+U 1168 ; WX 602 ; N uni0490 ; G 931
+U 1169 ; WX 602 ; N uni0491 ; G 932
+U 1170 ; WX 602 ; N uni0492 ; G 933
+U 1171 ; WX 602 ; N uni0493 ; G 934
+U 1172 ; WX 602 ; N uni0494 ; G 935
+U 1173 ; WX 602 ; N uni0495 ; G 936
+U 1174 ; WX 602 ; N uni0496 ; G 937
+U 1175 ; WX 602 ; N uni0497 ; G 938
+U 1176 ; WX 602 ; N uni0498 ; G 939
+U 1177 ; WX 602 ; N uni0499 ; G 940
+U 1178 ; WX 602 ; N uni049A ; G 941
+U 1179 ; WX 602 ; N uni049B ; G 942
+U 1186 ; WX 602 ; N uni04A2 ; G 943
+U 1187 ; WX 602 ; N uni04A3 ; G 944
+U 1188 ; WX 602 ; N uni04A4 ; G 945
+U 1189 ; WX 602 ; N uni04A5 ; G 946
+U 1194 ; WX 602 ; N uni04AA ; G 947
+U 1195 ; WX 602 ; N uni04AB ; G 948
+U 1196 ; WX 602 ; N uni04AC ; G 949
+U 1197 ; WX 602 ; N uni04AD ; G 950
+U 1198 ; WX 602 ; N uni04AE ; G 951
+U 1199 ; WX 602 ; N uni04AF ; G 952
+U 1200 ; WX 602 ; N uni04B0 ; G 953
+U 1201 ; WX 602 ; N uni04B1 ; G 954
+U 1202 ; WX 602 ; N uni04B2 ; G 955
+U 1203 ; WX 602 ; N uni04B3 ; G 956
+U 1210 ; WX 602 ; N uni04BA ; G 957
+U 1211 ; WX 602 ; N uni04BB ; G 958
+U 1216 ; WX 602 ; N uni04C0 ; G 959
+U 1217 ; WX 602 ; N uni04C1 ; G 960
+U 1218 ; WX 602 ; N uni04C2 ; G 961
+U 1219 ; WX 602 ; N uni04C3 ; G 962
+U 1220 ; WX 602 ; N uni04C4 ; G 963
+U 1223 ; WX 602 ; N uni04C7 ; G 964
+U 1224 ; WX 602 ; N uni04C8 ; G 965
+U 1227 ; WX 602 ; N uni04CB ; G 966
+U 1228 ; WX 602 ; N uni04CC ; G 967
+U 1231 ; WX 602 ; N uni04CF ; G 968
+U 1232 ; WX 602 ; N uni04D0 ; G 969
+U 1233 ; WX 602 ; N uni04D1 ; G 970
+U 1234 ; WX 602 ; N uni04D2 ; G 971
+U 1235 ; WX 602 ; N uni04D3 ; G 972
+U 1236 ; WX 602 ; N uni04D4 ; G 973
+U 1237 ; WX 602 ; N uni04D5 ; G 974
+U 1238 ; WX 602 ; N uni04D6 ; G 975
+U 1239 ; WX 602 ; N uni04D7 ; G 976
+U 1240 ; WX 602 ; N uni04D8 ; G 977
+U 1241 ; WX 602 ; N uni04D9 ; G 978
+U 1242 ; WX 602 ; N uni04DA ; G 979
+U 1243 ; WX 602 ; N uni04DB ; G 980
+U 1244 ; WX 602 ; N uni04DC ; G 981
+U 1245 ; WX 602 ; N uni04DD ; G 982
+U 1246 ; WX 602 ; N uni04DE ; G 983
+U 1247 ; WX 602 ; N uni04DF ; G 984
+U 1248 ; WX 602 ; N uni04E0 ; G 985
+U 1249 ; WX 602 ; N uni04E1 ; G 986
+U 1250 ; WX 602 ; N uni04E2 ; G 987
+U 1251 ; WX 602 ; N uni04E3 ; G 988
+U 1252 ; WX 602 ; N uni04E4 ; G 989
+U 1253 ; WX 602 ; N uni04E5 ; G 990
+U 1254 ; WX 602 ; N uni04E6 ; G 991
+U 1255 ; WX 602 ; N uni04E7 ; G 992
+U 1256 ; WX 602 ; N uni04E8 ; G 993
+U 1257 ; WX 602 ; N uni04E9 ; G 994
+U 1258 ; WX 602 ; N uni04EA ; G 995
+U 1259 ; WX 602 ; N uni04EB ; G 996
+U 1260 ; WX 602 ; N uni04EC ; G 997
+U 1261 ; WX 602 ; N uni04ED ; G 998
+U 1262 ; WX 602 ; N uni04EE ; G 999
+U 1263 ; WX 602 ; N uni04EF ; G 1000
+U 1264 ; WX 602 ; N uni04F0 ; G 1001
+U 1265 ; WX 602 ; N uni04F1 ; G 1002
+U 1266 ; WX 602 ; N uni04F2 ; G 1003
+U 1267 ; WX 602 ; N uni04F3 ; G 1004
+U 1268 ; WX 602 ; N uni04F4 ; G 1005
+U 1269 ; WX 602 ; N uni04F5 ; G 1006
+U 1270 ; WX 602 ; N uni04F6 ; G 1007
+U 1271 ; WX 602 ; N uni04F7 ; G 1008
+U 1272 ; WX 602 ; N uni04F8 ; G 1009
+U 1273 ; WX 602 ; N uni04F9 ; G 1010
+U 1296 ; WX 602 ; N uni0510 ; G 1011
+U 1297 ; WX 602 ; N uni0511 ; G 1012
+U 1306 ; WX 602 ; N uni051A ; G 1013
+U 1307 ; WX 602 ; N uni051B ; G 1014
+U 1308 ; WX 602 ; N uni051C ; G 1015
+U 1309 ; WX 602 ; N uni051D ; G 1016
+U 1329 ; WX 602 ; N uni0531 ; G 1017
+U 1330 ; WX 602 ; N uni0532 ; G 1018
+U 1331 ; WX 602 ; N uni0533 ; G 1019
+U 1332 ; WX 602 ; N uni0534 ; G 1020
+U 1333 ; WX 602 ; N uni0535 ; G 1021
+U 1334 ; WX 602 ; N uni0536 ; G 1022
+U 1335 ; WX 602 ; N uni0537 ; G 1023
+U 1336 ; WX 602 ; N uni0538 ; G 1024
+U 1337 ; WX 602 ; N uni0539 ; G 1025
+U 1338 ; WX 602 ; N uni053A ; G 1026
+U 1339 ; WX 602 ; N uni053B ; G 1027
+U 1340 ; WX 602 ; N uni053C ; G 1028
+U 1341 ; WX 602 ; N uni053D ; G 1029
+U 1342 ; WX 602 ; N uni053E ; G 1030
+U 1343 ; WX 602 ; N uni053F ; G 1031
+U 1344 ; WX 602 ; N uni0540 ; G 1032
+U 1345 ; WX 602 ; N uni0541 ; G 1033
+U 1346 ; WX 602 ; N uni0542 ; G 1034
+U 1347 ; WX 602 ; N uni0543 ; G 1035
+U 1348 ; WX 602 ; N uni0544 ; G 1036
+U 1349 ; WX 602 ; N uni0545 ; G 1037
+U 1350 ; WX 602 ; N uni0546 ; G 1038
+U 1351 ; WX 602 ; N uni0547 ; G 1039
+U 1352 ; WX 602 ; N uni0548 ; G 1040
+U 1353 ; WX 602 ; N uni0549 ; G 1041
+U 1354 ; WX 602 ; N uni054A ; G 1042
+U 1355 ; WX 602 ; N uni054B ; G 1043
+U 1356 ; WX 602 ; N uni054C ; G 1044
+U 1357 ; WX 602 ; N uni054D ; G 1045
+U 1358 ; WX 602 ; N uni054E ; G 1046
+U 1359 ; WX 602 ; N uni054F ; G 1047
+U 1360 ; WX 602 ; N uni0550 ; G 1048
+U 1361 ; WX 602 ; N uni0551 ; G 1049
+U 1362 ; WX 602 ; N uni0552 ; G 1050
+U 1363 ; WX 602 ; N uni0553 ; G 1051
+U 1364 ; WX 602 ; N uni0554 ; G 1052
+U 1365 ; WX 602 ; N uni0555 ; G 1053
+U 1366 ; WX 602 ; N uni0556 ; G 1054
+U 1369 ; WX 602 ; N uni0559 ; G 1055
+U 1370 ; WX 602 ; N uni055A ; G 1056
+U 1371 ; WX 602 ; N uni055B ; G 1057
+U 1372 ; WX 602 ; N uni055C ; G 1058
+U 1373 ; WX 602 ; N uni055D ; G 1059
+U 1374 ; WX 602 ; N uni055E ; G 1060
+U 1375 ; WX 602 ; N uni055F ; G 1061
+U 1377 ; WX 602 ; N uni0561 ; G 1062
+U 1378 ; WX 602 ; N uni0562 ; G 1063
+U 1379 ; WX 602 ; N uni0563 ; G 1064
+U 1380 ; WX 602 ; N uni0564 ; G 1065
+U 1381 ; WX 602 ; N uni0565 ; G 1066
+U 1382 ; WX 602 ; N uni0566 ; G 1067
+U 1383 ; WX 602 ; N uni0567 ; G 1068
+U 1384 ; WX 602 ; N uni0568 ; G 1069
+U 1385 ; WX 602 ; N uni0569 ; G 1070
+U 1386 ; WX 602 ; N uni056A ; G 1071
+U 1387 ; WX 602 ; N uni056B ; G 1072
+U 1388 ; WX 602 ; N uni056C ; G 1073
+U 1389 ; WX 602 ; N uni056D ; G 1074
+U 1390 ; WX 602 ; N uni056E ; G 1075
+U 1391 ; WX 602 ; N uni056F ; G 1076
+U 1392 ; WX 602 ; N uni0570 ; G 1077
+U 1393 ; WX 602 ; N uni0571 ; G 1078
+U 1394 ; WX 602 ; N uni0572 ; G 1079
+U 1395 ; WX 602 ; N uni0573 ; G 1080
+U 1396 ; WX 602 ; N uni0574 ; G 1081
+U 1397 ; WX 602 ; N uni0575 ; G 1082
+U 1398 ; WX 602 ; N uni0576 ; G 1083
+U 1399 ; WX 602 ; N uni0577 ; G 1084
+U 1400 ; WX 602 ; N uni0578 ; G 1085
+U 1401 ; WX 602 ; N uni0579 ; G 1086
+U 1402 ; WX 602 ; N uni057A ; G 1087
+U 1403 ; WX 602 ; N uni057B ; G 1088
+U 1404 ; WX 602 ; N uni057C ; G 1089
+U 1405 ; WX 602 ; N uni057D ; G 1090
+U 1406 ; WX 602 ; N uni057E ; G 1091
+U 1407 ; WX 602 ; N uni057F ; G 1092
+U 1408 ; WX 602 ; N uni0580 ; G 1093
+U 1409 ; WX 602 ; N uni0581 ; G 1094
+U 1410 ; WX 602 ; N uni0582 ; G 1095
+U 1411 ; WX 602 ; N uni0583 ; G 1096
+U 1412 ; WX 602 ; N uni0584 ; G 1097
+U 1413 ; WX 602 ; N uni0585 ; G 1098
+U 1414 ; WX 602 ; N uni0586 ; G 1099
+U 1415 ; WX 602 ; N uni0587 ; G 1100
+U 1417 ; WX 602 ; N uni0589 ; G 1101
+U 1418 ; WX 602 ; N uni058A ; G 1102
+U 1542 ; WX 602 ; N uni0606 ; G 1103
+U 1543 ; WX 602 ; N uni0607 ; G 1104
+U 1545 ; WX 602 ; N uni0609 ; G 1105
+U 1546 ; WX 602 ; N uni060A ; G 1106
+U 1548 ; WX 602 ; N uni060C ; G 1107
+U 1557 ; WX 602 ; N uni0615 ; G 1108
+U 1563 ; WX 602 ; N uni061B ; G 1109
+U 1567 ; WX 602 ; N uni061F ; G 1110
+U 1569 ; WX 602 ; N uni0621 ; G 1111
+U 1570 ; WX 602 ; N uni0622 ; G 1112
+U 1571 ; WX 602 ; N uni0623 ; G 1113
+U 1572 ; WX 602 ; N uni0624 ; G 1114
+U 1573 ; WX 602 ; N uni0625 ; G 1115
+U 1574 ; WX 602 ; N uni0626 ; G 1116
+U 1575 ; WX 602 ; N uni0627 ; G 1117
+U 1576 ; WX 602 ; N uni0628 ; G 1118
+U 1577 ; WX 602 ; N uni0629 ; G 1119
+U 1578 ; WX 602 ; N uni062A ; G 1120
+U 1579 ; WX 602 ; N uni062B ; G 1121
+U 1580 ; WX 602 ; N uni062C ; G 1122
+U 1581 ; WX 602 ; N uni062D ; G 1123
+U 1582 ; WX 602 ; N uni062E ; G 1124
+U 1583 ; WX 602 ; N uni062F ; G 1125
+U 1584 ; WX 602 ; N uni0630 ; G 1126
+U 1585 ; WX 602 ; N uni0631 ; G 1127
+U 1586 ; WX 602 ; N uni0632 ; G 1128
+U 1587 ; WX 602 ; N uni0633 ; G 1129
+U 1588 ; WX 602 ; N uni0634 ; G 1130
+U 1589 ; WX 602 ; N uni0635 ; G 1131
+U 1590 ; WX 602 ; N uni0636 ; G 1132
+U 1591 ; WX 602 ; N uni0637 ; G 1133
+U 1592 ; WX 602 ; N uni0638 ; G 1134
+U 1593 ; WX 602 ; N uni0639 ; G 1135
+U 1594 ; WX 602 ; N uni063A ; G 1136
+U 1600 ; WX 602 ; N uni0640 ; G 1137
+U 1601 ; WX 602 ; N uni0641 ; G 1138
+U 1602 ; WX 602 ; N uni0642 ; G 1139
+U 1603 ; WX 602 ; N uni0643 ; G 1140
+U 1604 ; WX 602 ; N uni0644 ; G 1141
+U 1605 ; WX 602 ; N uni0645 ; G 1142
+U 1606 ; WX 602 ; N uni0646 ; G 1143
+U 1607 ; WX 602 ; N uni0647 ; G 1144
+U 1608 ; WX 602 ; N uni0648 ; G 1145
+U 1609 ; WX 602 ; N uni0649 ; G 1146
+U 1610 ; WX 602 ; N uni064A ; G 1147
+U 1611 ; WX 602 ; N uni064B ; G 1148
+U 1612 ; WX 602 ; N uni064C ; G 1149
+U 1613 ; WX 602 ; N uni064D ; G 1150
+U 1614 ; WX 602 ; N uni064E ; G 1151
+U 1615 ; WX 602 ; N uni064F ; G 1152
+U 1616 ; WX 602 ; N uni0650 ; G 1153
+U 1617 ; WX 602 ; N uni0651 ; G 1154
+U 1618 ; WX 602 ; N uni0652 ; G 1155
+U 1619 ; WX 602 ; N uni0653 ; G 1156
+U 1620 ; WX 602 ; N uni0654 ; G 1157
+U 1621 ; WX 602 ; N uni0655 ; G 1158
+U 1626 ; WX 602 ; N uni065A ; G 1159
+U 1632 ; WX 602 ; N uni0660 ; G 1160
+U 1633 ; WX 602 ; N uni0661 ; G 1161
+U 1634 ; WX 602 ; N uni0662 ; G 1162
+U 1635 ; WX 602 ; N uni0663 ; G 1163
+U 1636 ; WX 602 ; N uni0664 ; G 1164
+U 1637 ; WX 602 ; N uni0665 ; G 1165
+U 1638 ; WX 602 ; N uni0666 ; G 1166
+U 1639 ; WX 602 ; N uni0667 ; G 1167
+U 1640 ; WX 602 ; N uni0668 ; G 1168
+U 1641 ; WX 602 ; N uni0669 ; G 1169
+U 1642 ; WX 602 ; N uni066A ; G 1170
+U 1643 ; WX 602 ; N uni066B ; G 1171
+U 1644 ; WX 602 ; N uni066C ; G 1172
+U 1645 ; WX 602 ; N uni066D ; G 1173
+U 1652 ; WX 602 ; N uni0674 ; G 1174
+U 1657 ; WX 602 ; N uni0679 ; G 1175
+U 1658 ; WX 602 ; N uni067A ; G 1176
+U 1659 ; WX 602 ; N uni067B ; G 1177
+U 1662 ; WX 602 ; N uni067E ; G 1178
+U 1663 ; WX 602 ; N uni067F ; G 1179
+U 1664 ; WX 602 ; N uni0680 ; G 1180
+U 1667 ; WX 602 ; N uni0683 ; G 1181
+U 1668 ; WX 602 ; N uni0684 ; G 1182
+U 1670 ; WX 602 ; N uni0686 ; G 1183
+U 1671 ; WX 602 ; N uni0687 ; G 1184
+U 1681 ; WX 602 ; N uni0691 ; G 1185
+U 1688 ; WX 602 ; N uni0698 ; G 1186
+U 1700 ; WX 602 ; N uni06A4 ; G 1187
+U 1705 ; WX 602 ; N uni06A9 ; G 1188
+U 1711 ; WX 602 ; N uni06AF ; G 1189
+U 1726 ; WX 602 ; N uni06BE ; G 1190
+U 1740 ; WX 602 ; N uni06CC ; G 1191
+U 1776 ; WX 602 ; N uni06F0 ; G 1192
+U 1777 ; WX 602 ; N uni06F1 ; G 1193
+U 1778 ; WX 602 ; N uni06F2 ; G 1194
+U 1779 ; WX 602 ; N uni06F3 ; G 1195
+U 1780 ; WX 602 ; N uni06F4 ; G 1196
+U 1781 ; WX 602 ; N uni06F5 ; G 1197
+U 1782 ; WX 602 ; N uni06F6 ; G 1198
+U 1783 ; WX 602 ; N uni06F7 ; G 1199
+U 1784 ; WX 602 ; N uni06F8 ; G 1200
+U 1785 ; WX 602 ; N uni06F9 ; G 1201
+U 3647 ; WX 602 ; N uni0E3F ; G 1202
+U 3713 ; WX 602 ; N uni0E81 ; G 1203
+U 3714 ; WX 602 ; N uni0E82 ; G 1204
+U 3716 ; WX 602 ; N uni0E84 ; G 1205
+U 3719 ; WX 602 ; N uni0E87 ; G 1206
+U 3720 ; WX 602 ; N uni0E88 ; G 1207
+U 3722 ; WX 602 ; N uni0E8A ; G 1208
+U 3725 ; WX 602 ; N uni0E8D ; G 1209
+U 3732 ; WX 602 ; N uni0E94 ; G 1210
+U 3733 ; WX 602 ; N uni0E95 ; G 1211
+U 3734 ; WX 602 ; N uni0E96 ; G 1212
+U 3735 ; WX 602 ; N uni0E97 ; G 1213
+U 3737 ; WX 602 ; N uni0E99 ; G 1214
+U 3738 ; WX 602 ; N uni0E9A ; G 1215
+U 3739 ; WX 602 ; N uni0E9B ; G 1216
+U 3740 ; WX 602 ; N uni0E9C ; G 1217
+U 3741 ; WX 602 ; N uni0E9D ; G 1218
+U 3742 ; WX 602 ; N uni0E9E ; G 1219
+U 3743 ; WX 602 ; N uni0E9F ; G 1220
+U 3745 ; WX 602 ; N uni0EA1 ; G 1221
+U 3746 ; WX 602 ; N uni0EA2 ; G 1222
+U 3747 ; WX 602 ; N uni0EA3 ; G 1223
+U 3749 ; WX 602 ; N uni0EA5 ; G 1224
+U 3751 ; WX 602 ; N uni0EA7 ; G 1225
+U 3754 ; WX 602 ; N uni0EAA ; G 1226
+U 3755 ; WX 602 ; N uni0EAB ; G 1227
+U 3757 ; WX 602 ; N uni0EAD ; G 1228
+U 3758 ; WX 602 ; N uni0EAE ; G 1229
+U 3759 ; WX 602 ; N uni0EAF ; G 1230
+U 3760 ; WX 602 ; N uni0EB0 ; G 1231
+U 3761 ; WX 602 ; N uni0EB1 ; G 1232
+U 3762 ; WX 602 ; N uni0EB2 ; G 1233
+U 3763 ; WX 602 ; N uni0EB3 ; G 1234
+U 3764 ; WX 602 ; N uni0EB4 ; G 1235
+U 3765 ; WX 602 ; N uni0EB5 ; G 1236
+U 3766 ; WX 602 ; N uni0EB6 ; G 1237
+U 3767 ; WX 602 ; N uni0EB7 ; G 1238
+U 3768 ; WX 602 ; N uni0EB8 ; G 1239
+U 3769 ; WX 602 ; N uni0EB9 ; G 1240
+U 3771 ; WX 602 ; N uni0EBB ; G 1241
+U 3772 ; WX 602 ; N uni0EBC ; G 1242
+U 3784 ; WX 602 ; N uni0EC8 ; G 1243
+U 3785 ; WX 602 ; N uni0EC9 ; G 1244
+U 3786 ; WX 602 ; N uni0ECA ; G 1245
+U 3787 ; WX 602 ; N uni0ECB ; G 1246
+U 3788 ; WX 602 ; N uni0ECC ; G 1247
+U 3789 ; WX 602 ; N uni0ECD ; G 1248
+U 4304 ; WX 602 ; N uni10D0 ; G 1249
+U 4305 ; WX 602 ; N uni10D1 ; G 1250
+U 4306 ; WX 602 ; N uni10D2 ; G 1251
+U 4307 ; WX 602 ; N uni10D3 ; G 1252
+U 4308 ; WX 602 ; N uni10D4 ; G 1253
+U 4309 ; WX 602 ; N uni10D5 ; G 1254
+U 4310 ; WX 602 ; N uni10D6 ; G 1255
+U 4311 ; WX 602 ; N uni10D7 ; G 1256
+U 4312 ; WX 602 ; N uni10D8 ; G 1257
+U 4313 ; WX 602 ; N uni10D9 ; G 1258
+U 4314 ; WX 602 ; N uni10DA ; G 1259
+U 4315 ; WX 602 ; N uni10DB ; G 1260
+U 4316 ; WX 602 ; N uni10DC ; G 1261
+U 4317 ; WX 602 ; N uni10DD ; G 1262
+U 4318 ; WX 602 ; N uni10DE ; G 1263
+U 4319 ; WX 602 ; N uni10DF ; G 1264
+U 4320 ; WX 602 ; N uni10E0 ; G 1265
+U 4321 ; WX 602 ; N uni10E1 ; G 1266
+U 4322 ; WX 602 ; N uni10E2 ; G 1267
+U 4323 ; WX 602 ; N uni10E3 ; G 1268
+U 4324 ; WX 602 ; N uni10E4 ; G 1269
+U 4325 ; WX 602 ; N uni10E5 ; G 1270
+U 4326 ; WX 602 ; N uni10E6 ; G 1271
+U 4327 ; WX 602 ; N uni10E7 ; G 1272
+U 4328 ; WX 602 ; N uni10E8 ; G 1273
+U 4329 ; WX 602 ; N uni10E9 ; G 1274
+U 4330 ; WX 602 ; N uni10EA ; G 1275
+U 4331 ; WX 602 ; N uni10EB ; G 1276
+U 4332 ; WX 602 ; N uni10EC ; G 1277
+U 4333 ; WX 602 ; N uni10ED ; G 1278
+U 4334 ; WX 602 ; N uni10EE ; G 1279
+U 4335 ; WX 602 ; N uni10EF ; G 1280
+U 4336 ; WX 602 ; N uni10F0 ; G 1281
+U 4337 ; WX 602 ; N uni10F1 ; G 1282
+U 4338 ; WX 602 ; N uni10F2 ; G 1283
+U 4339 ; WX 602 ; N uni10F3 ; G 1284
+U 4340 ; WX 602 ; N uni10F4 ; G 1285
+U 4341 ; WX 602 ; N uni10F5 ; G 1286
+U 4342 ; WX 602 ; N uni10F6 ; G 1287
+U 4343 ; WX 602 ; N uni10F7 ; G 1288
+U 4344 ; WX 602 ; N uni10F8 ; G 1289
+U 4345 ; WX 602 ; N uni10F9 ; G 1290
+U 4346 ; WX 602 ; N uni10FA ; G 1291
+U 4347 ; WX 602 ; N uni10FB ; G 1292
+U 4348 ; WX 602 ; N uni10FC ; G 1293
+U 7426 ; WX 602 ; N uni1D02 ; G 1294
+U 7432 ; WX 602 ; N uni1D08 ; G 1295
+U 7433 ; WX 602 ; N uni1D09 ; G 1296
+U 7444 ; WX 602 ; N uni1D14 ; G 1297
+U 7446 ; WX 602 ; N uni1D16 ; G 1298
+U 7447 ; WX 602 ; N uni1D17 ; G 1299
+U 7453 ; WX 602 ; N uni1D1D ; G 1300
+U 7454 ; WX 602 ; N uni1D1E ; G 1301
+U 7455 ; WX 602 ; N uni1D1F ; G 1302
+U 7468 ; WX 602 ; N uni1D2C ; G 1303
+U 7469 ; WX 602 ; N uni1D2D ; G 1304
+U 7470 ; WX 602 ; N uni1D2E ; G 1305
+U 7472 ; WX 602 ; N uni1D30 ; G 1306
+U 7473 ; WX 602 ; N uni1D31 ; G 1307
+U 7474 ; WX 602 ; N uni1D32 ; G 1308
+U 7475 ; WX 602 ; N uni1D33 ; G 1309
+U 7476 ; WX 602 ; N uni1D34 ; G 1310
+U 7477 ; WX 602 ; N uni1D35 ; G 1311
+U 7478 ; WX 602 ; N uni1D36 ; G 1312
+U 7479 ; WX 602 ; N uni1D37 ; G 1313
+U 7480 ; WX 602 ; N uni1D38 ; G 1314
+U 7481 ; WX 602 ; N uni1D39 ; G 1315
+U 7482 ; WX 602 ; N uni1D3A ; G 1316
+U 7483 ; WX 602 ; N uni1D3B ; G 1317
+U 7484 ; WX 602 ; N uni1D3C ; G 1318
+U 7486 ; WX 602 ; N uni1D3E ; G 1319
+U 7487 ; WX 602 ; N uni1D3F ; G 1320
+U 7488 ; WX 602 ; N uni1D40 ; G 1321
+U 7489 ; WX 602 ; N uni1D41 ; G 1322
+U 7490 ; WX 602 ; N uni1D42 ; G 1323
+U 7491 ; WX 602 ; N uni1D43 ; G 1324
+U 7492 ; WX 602 ; N uni1D44 ; G 1325
+U 7493 ; WX 602 ; N uni1D45 ; G 1326
+U 7494 ; WX 602 ; N uni1D46 ; G 1327
+U 7495 ; WX 602 ; N uni1D47 ; G 1328
+U 7496 ; WX 602 ; N uni1D48 ; G 1329
+U 7497 ; WX 602 ; N uni1D49 ; G 1330
+U 7498 ; WX 602 ; N uni1D4A ; G 1331
+U 7499 ; WX 602 ; N uni1D4B ; G 1332
+U 7500 ; WX 602 ; N uni1D4C ; G 1333
+U 7501 ; WX 602 ; N uni1D4D ; G 1334
+U 7502 ; WX 602 ; N uni1D4E ; G 1335
+U 7503 ; WX 602 ; N uni1D4F ; G 1336
+U 7504 ; WX 602 ; N uni1D50 ; G 1337
+U 7505 ; WX 602 ; N uni1D51 ; G 1338
+U 7506 ; WX 602 ; N uni1D52 ; G 1339
+U 7507 ; WX 602 ; N uni1D53 ; G 1340
+U 7508 ; WX 602 ; N uni1D54 ; G 1341
+U 7509 ; WX 602 ; N uni1D55 ; G 1342
+U 7510 ; WX 602 ; N uni1D56 ; G 1343
+U 7511 ; WX 602 ; N uni1D57 ; G 1344
+U 7512 ; WX 602 ; N uni1D58 ; G 1345
+U 7513 ; WX 602 ; N uni1D59 ; G 1346
+U 7514 ; WX 602 ; N uni1D5A ; G 1347
+U 7515 ; WX 602 ; N uni1D5B ; G 1348
+U 7522 ; WX 602 ; N uni1D62 ; G 1349
+U 7523 ; WX 602 ; N uni1D63 ; G 1350
+U 7524 ; WX 602 ; N uni1D64 ; G 1351
+U 7525 ; WX 602 ; N uni1D65 ; G 1352
+U 7543 ; WX 602 ; N uni1D77 ; G 1353
+U 7544 ; WX 602 ; N uni1D78 ; G 1354
+U 7547 ; WX 602 ; N uni1D7B ; G 1355
+U 7557 ; WX 602 ; N uni1D85 ; G 1356
+U 7579 ; WX 602 ; N uni1D9B ; G 1357
+U 7580 ; WX 602 ; N uni1D9C ; G 1358
+U 7581 ; WX 602 ; N uni1D9D ; G 1359
+U 7582 ; WX 602 ; N uni1D9E ; G 1360
+U 7583 ; WX 602 ; N uni1D9F ; G 1361
+U 7584 ; WX 602 ; N uni1DA0 ; G 1362
+U 7585 ; WX 602 ; N uni1DA1 ; G 1363
+U 7586 ; WX 602 ; N uni1DA2 ; G 1364
+U 7587 ; WX 602 ; N uni1DA3 ; G 1365
+U 7588 ; WX 602 ; N uni1DA4 ; G 1366
+U 7589 ; WX 602 ; N uni1DA5 ; G 1367
+U 7590 ; WX 602 ; N uni1DA6 ; G 1368
+U 7591 ; WX 602 ; N uni1DA7 ; G 1369
+U 7592 ; WX 602 ; N uni1DA8 ; G 1370
+U 7593 ; WX 602 ; N uni1DA9 ; G 1371
+U 7594 ; WX 602 ; N uni1DAA ; G 1372
+U 7595 ; WX 602 ; N uni1DAB ; G 1373
+U 7596 ; WX 602 ; N uni1DAC ; G 1374
+U 7597 ; WX 602 ; N uni1DAD ; G 1375
+U 7598 ; WX 602 ; N uni1DAE ; G 1376
+U 7599 ; WX 602 ; N uni1DAF ; G 1377
+U 7600 ; WX 602 ; N uni1DB0 ; G 1378
+U 7601 ; WX 602 ; N uni1DB1 ; G 1379
+U 7602 ; WX 602 ; N uni1DB2 ; G 1380
+U 7603 ; WX 602 ; N uni1DB3 ; G 1381
+U 7604 ; WX 602 ; N uni1DB4 ; G 1382
+U 7605 ; WX 602 ; N uni1DB5 ; G 1383
+U 7606 ; WX 602 ; N uni1DB6 ; G 1384
+U 7607 ; WX 602 ; N uni1DB7 ; G 1385
+U 7609 ; WX 602 ; N uni1DB9 ; G 1386
+U 7610 ; WX 602 ; N uni1DBA ; G 1387
+U 7611 ; WX 602 ; N uni1DBB ; G 1388
+U 7612 ; WX 602 ; N uni1DBC ; G 1389
+U 7613 ; WX 602 ; N uni1DBD ; G 1390
+U 7614 ; WX 602 ; N uni1DBE ; G 1391
+U 7615 ; WX 602 ; N uni1DBF ; G 1392
+U 7680 ; WX 602 ; N uni1E00 ; G 1393
+U 7681 ; WX 602 ; N uni1E01 ; G 1394
+U 7682 ; WX 602 ; N uni1E02 ; G 1395
+U 7683 ; WX 602 ; N uni1E03 ; G 1396
+U 7684 ; WX 602 ; N uni1E04 ; G 1397
+U 7685 ; WX 602 ; N uni1E05 ; G 1398
+U 7686 ; WX 602 ; N uni1E06 ; G 1399
+U 7687 ; WX 602 ; N uni1E07 ; G 1400
+U 7688 ; WX 602 ; N uni1E08 ; G 1401
+U 7689 ; WX 602 ; N uni1E09 ; G 1402
+U 7690 ; WX 602 ; N uni1E0A ; G 1403
+U 7691 ; WX 602 ; N uni1E0B ; G 1404
+U 7692 ; WX 602 ; N uni1E0C ; G 1405
+U 7693 ; WX 602 ; N uni1E0D ; G 1406
+U 7694 ; WX 602 ; N uni1E0E ; G 1407
+U 7695 ; WX 602 ; N uni1E0F ; G 1408
+U 7696 ; WX 602 ; N uni1E10 ; G 1409
+U 7697 ; WX 602 ; N uni1E11 ; G 1410
+U 7698 ; WX 602 ; N uni1E12 ; G 1411
+U 7699 ; WX 602 ; N uni1E13 ; G 1412
+U 7704 ; WX 602 ; N uni1E18 ; G 1413
+U 7705 ; WX 602 ; N uni1E19 ; G 1414
+U 7706 ; WX 602 ; N uni1E1A ; G 1415
+U 7707 ; WX 602 ; N uni1E1B ; G 1416
+U 7708 ; WX 602 ; N uni1E1C ; G 1417
+U 7709 ; WX 602 ; N uni1E1D ; G 1418
+U 7710 ; WX 602 ; N uni1E1E ; G 1419
+U 7711 ; WX 602 ; N uni1E1F ; G 1420
+U 7712 ; WX 602 ; N uni1E20 ; G 1421
+U 7713 ; WX 602 ; N uni1E21 ; G 1422
+U 7714 ; WX 602 ; N uni1E22 ; G 1423
+U 7715 ; WX 602 ; N uni1E23 ; G 1424
+U 7716 ; WX 602 ; N uni1E24 ; G 1425
+U 7717 ; WX 602 ; N uni1E25 ; G 1426
+U 7718 ; WX 602 ; N uni1E26 ; G 1427
+U 7719 ; WX 602 ; N uni1E27 ; G 1428
+U 7720 ; WX 602 ; N uni1E28 ; G 1429
+U 7721 ; WX 602 ; N uni1E29 ; G 1430
+U 7722 ; WX 602 ; N uni1E2A ; G 1431
+U 7723 ; WX 602 ; N uni1E2B ; G 1432
+U 7724 ; WX 602 ; N uni1E2C ; G 1433
+U 7725 ; WX 602 ; N uni1E2D ; G 1434
+U 7728 ; WX 602 ; N uni1E30 ; G 1435
+U 7729 ; WX 602 ; N uni1E31 ; G 1436
+U 7730 ; WX 602 ; N uni1E32 ; G 1437
+U 7731 ; WX 602 ; N uni1E33 ; G 1438
+U 7732 ; WX 602 ; N uni1E34 ; G 1439
+U 7733 ; WX 602 ; N uni1E35 ; G 1440
+U 7734 ; WX 602 ; N uni1E36 ; G 1441
+U 7735 ; WX 602 ; N uni1E37 ; G 1442
+U 7736 ; WX 602 ; N uni1E38 ; G 1443
+U 7737 ; WX 602 ; N uni1E39 ; G 1444
+U 7738 ; WX 602 ; N uni1E3A ; G 1445
+U 7739 ; WX 602 ; N uni1E3B ; G 1446
+U 7740 ; WX 602 ; N uni1E3C ; G 1447
+U 7741 ; WX 602 ; N uni1E3D ; G 1448
+U 7742 ; WX 602 ; N uni1E3E ; G 1449
+U 7743 ; WX 602 ; N uni1E3F ; G 1450
+U 7744 ; WX 602 ; N uni1E40 ; G 1451
+U 7745 ; WX 602 ; N uni1E41 ; G 1452
+U 7746 ; WX 602 ; N uni1E42 ; G 1453
+U 7747 ; WX 602 ; N uni1E43 ; G 1454
+U 7748 ; WX 602 ; N uni1E44 ; G 1455
+U 7749 ; WX 602 ; N uni1E45 ; G 1456
+U 7750 ; WX 602 ; N uni1E46 ; G 1457
+U 7751 ; WX 602 ; N uni1E47 ; G 1458
+U 7752 ; WX 602 ; N uni1E48 ; G 1459
+U 7753 ; WX 602 ; N uni1E49 ; G 1460
+U 7754 ; WX 602 ; N uni1E4A ; G 1461
+U 7755 ; WX 602 ; N uni1E4B ; G 1462
+U 7756 ; WX 602 ; N uni1E4C ; G 1463
+U 7757 ; WX 602 ; N uni1E4D ; G 1464
+U 7764 ; WX 602 ; N uni1E54 ; G 1465
+U 7765 ; WX 602 ; N uni1E55 ; G 1466
+U 7766 ; WX 602 ; N uni1E56 ; G 1467
+U 7767 ; WX 602 ; N uni1E57 ; G 1468
+U 7768 ; WX 602 ; N uni1E58 ; G 1469
+U 7769 ; WX 602 ; N uni1E59 ; G 1470
+U 7770 ; WX 602 ; N uni1E5A ; G 1471
+U 7771 ; WX 602 ; N uni1E5B ; G 1472
+U 7772 ; WX 602 ; N uni1E5C ; G 1473
+U 7773 ; WX 602 ; N uni1E5D ; G 1474
+U 7774 ; WX 602 ; N uni1E5E ; G 1475
+U 7775 ; WX 602 ; N uni1E5F ; G 1476
+U 7776 ; WX 602 ; N uni1E60 ; G 1477
+U 7777 ; WX 602 ; N uni1E61 ; G 1478
+U 7778 ; WX 602 ; N uni1E62 ; G 1479
+U 7779 ; WX 602 ; N uni1E63 ; G 1480
+U 7784 ; WX 602 ; N uni1E68 ; G 1481
+U 7785 ; WX 602 ; N uni1E69 ; G 1482
+U 7786 ; WX 602 ; N uni1E6A ; G 1483
+U 7787 ; WX 602 ; N uni1E6B ; G 1484
+U 7788 ; WX 602 ; N uni1E6C ; G 1485
+U 7789 ; WX 602 ; N uni1E6D ; G 1486
+U 7790 ; WX 602 ; N uni1E6E ; G 1487
+U 7791 ; WX 602 ; N uni1E6F ; G 1488
+U 7792 ; WX 602 ; N uni1E70 ; G 1489
+U 7793 ; WX 602 ; N uni1E71 ; G 1490
+U 7794 ; WX 602 ; N uni1E72 ; G 1491
+U 7795 ; WX 602 ; N uni1E73 ; G 1492
+U 7796 ; WX 602 ; N uni1E74 ; G 1493
+U 7797 ; WX 602 ; N uni1E75 ; G 1494
+U 7798 ; WX 602 ; N uni1E76 ; G 1495
+U 7799 ; WX 602 ; N uni1E77 ; G 1496
+U 7800 ; WX 602 ; N uni1E78 ; G 1497
+U 7801 ; WX 602 ; N uni1E79 ; G 1498
+U 7804 ; WX 602 ; N uni1E7C ; G 1499
+U 7805 ; WX 602 ; N uni1E7D ; G 1500
+U 7806 ; WX 602 ; N uni1E7E ; G 1501
+U 7807 ; WX 602 ; N uni1E7F ; G 1502
+U 7808 ; WX 602 ; N Wgrave ; G 1503
+U 7809 ; WX 602 ; N wgrave ; G 1504
+U 7810 ; WX 602 ; N Wacute ; G 1505
+U 7811 ; WX 602 ; N wacute ; G 1506
+U 7812 ; WX 602 ; N Wdieresis ; G 1507
+U 7813 ; WX 602 ; N wdieresis ; G 1508
+U 7814 ; WX 602 ; N uni1E86 ; G 1509
+U 7815 ; WX 602 ; N uni1E87 ; G 1510
+U 7816 ; WX 602 ; N uni1E88 ; G 1511
+U 7817 ; WX 602 ; N uni1E89 ; G 1512
+U 7818 ; WX 602 ; N uni1E8A ; G 1513
+U 7819 ; WX 602 ; N uni1E8B ; G 1514
+U 7820 ; WX 602 ; N uni1E8C ; G 1515
+U 7821 ; WX 602 ; N uni1E8D ; G 1516
+U 7822 ; WX 602 ; N uni1E8E ; G 1517
+U 7823 ; WX 602 ; N uni1E8F ; G 1518
+U 7824 ; WX 602 ; N uni1E90 ; G 1519
+U 7825 ; WX 602 ; N uni1E91 ; G 1520
+U 7826 ; WX 602 ; N uni1E92 ; G 1521
+U 7827 ; WX 602 ; N uni1E93 ; G 1522
+U 7828 ; WX 602 ; N uni1E94 ; G 1523
+U 7829 ; WX 602 ; N uni1E95 ; G 1524
+U 7830 ; WX 602 ; N uni1E96 ; G 1525
+U 7831 ; WX 602 ; N uni1E97 ; G 1526
+U 7832 ; WX 602 ; N uni1E98 ; G 1527
+U 7833 ; WX 602 ; N uni1E99 ; G 1528
+U 7835 ; WX 602 ; N uni1E9B ; G 1529
+U 7839 ; WX 602 ; N uni1E9F ; G 1530
+U 7840 ; WX 602 ; N uni1EA0 ; G 1531
+U 7841 ; WX 602 ; N uni1EA1 ; G 1532
+U 7852 ; WX 602 ; N uni1EAC ; G 1533
+U 7853 ; WX 602 ; N uni1EAD ; G 1534
+U 7856 ; WX 602 ; N uni1EB0 ; G 1535
+U 7857 ; WX 602 ; N uni1EB1 ; G 1536
+U 7862 ; WX 602 ; N uni1EB6 ; G 1537
+U 7863 ; WX 602 ; N uni1EB7 ; G 1538
+U 7864 ; WX 602 ; N uni1EB8 ; G 1539
+U 7865 ; WX 602 ; N uni1EB9 ; G 1540
+U 7868 ; WX 602 ; N uni1EBC ; G 1541
+U 7869 ; WX 602 ; N uni1EBD ; G 1542
+U 7878 ; WX 602 ; N uni1EC6 ; G 1543
+U 7879 ; WX 602 ; N uni1EC7 ; G 1544
+U 7882 ; WX 602 ; N uni1ECA ; G 1545
+U 7883 ; WX 602 ; N uni1ECB ; G 1546
+U 7884 ; WX 602 ; N uni1ECC ; G 1547
+U 7885 ; WX 602 ; N uni1ECD ; G 1548
+U 7896 ; WX 602 ; N uni1ED8 ; G 1549
+U 7897 ; WX 602 ; N uni1ED9 ; G 1550
+U 7898 ; WX 602 ; N uni1EDA ; G 1551
+U 7899 ; WX 602 ; N uni1EDB ; G 1552
+U 7900 ; WX 602 ; N uni1EDC ; G 1553
+U 7901 ; WX 602 ; N uni1EDD ; G 1554
+U 7904 ; WX 602 ; N uni1EE0 ; G 1555
+U 7905 ; WX 602 ; N uni1EE1 ; G 1556
+U 7906 ; WX 602 ; N uni1EE2 ; G 1557
+U 7907 ; WX 602 ; N uni1EE3 ; G 1558
+U 7908 ; WX 602 ; N uni1EE4 ; G 1559
+U 7909 ; WX 602 ; N uni1EE5 ; G 1560
+U 7912 ; WX 602 ; N uni1EE8 ; G 1561
+U 7913 ; WX 602 ; N uni1EE9 ; G 1562
+U 7914 ; WX 602 ; N uni1EEA ; G 1563
+U 7915 ; WX 602 ; N uni1EEB ; G 1564
+U 7918 ; WX 602 ; N uni1EEE ; G 1565
+U 7919 ; WX 602 ; N uni1EEF ; G 1566
+U 7920 ; WX 602 ; N uni1EF0 ; G 1567
+U 7921 ; WX 602 ; N uni1EF1 ; G 1568
+U 7922 ; WX 602 ; N Ygrave ; G 1569
+U 7923 ; WX 602 ; N ygrave ; G 1570
+U 7924 ; WX 602 ; N uni1EF4 ; G 1571
+U 7925 ; WX 602 ; N uni1EF5 ; G 1572
+U 7928 ; WX 602 ; N uni1EF8 ; G 1573
+U 7929 ; WX 602 ; N uni1EF9 ; G 1574
+U 7936 ; WX 602 ; N uni1F00 ; G 1575
+U 7937 ; WX 602 ; N uni1F01 ; G 1576
+U 7938 ; WX 602 ; N uni1F02 ; G 1577
+U 7939 ; WX 602 ; N uni1F03 ; G 1578
+U 7940 ; WX 602 ; N uni1F04 ; G 1579
+U 7941 ; WX 602 ; N uni1F05 ; G 1580
+U 7942 ; WX 602 ; N uni1F06 ; G 1581
+U 7943 ; WX 602 ; N uni1F07 ; G 1582
+U 7944 ; WX 602 ; N uni1F08 ; G 1583
+U 7945 ; WX 602 ; N uni1F09 ; G 1584
+U 7946 ; WX 602 ; N uni1F0A ; G 1585
+U 7947 ; WX 602 ; N uni1F0B ; G 1586
+U 7948 ; WX 602 ; N uni1F0C ; G 1587
+U 7949 ; WX 602 ; N uni1F0D ; G 1588
+U 7950 ; WX 602 ; N uni1F0E ; G 1589
+U 7951 ; WX 602 ; N uni1F0F ; G 1590
+U 7952 ; WX 602 ; N uni1F10 ; G 1591
+U 7953 ; WX 602 ; N uni1F11 ; G 1592
+U 7954 ; WX 602 ; N uni1F12 ; G 1593
+U 7955 ; WX 602 ; N uni1F13 ; G 1594
+U 7956 ; WX 602 ; N uni1F14 ; G 1595
+U 7957 ; WX 602 ; N uni1F15 ; G 1596
+U 7960 ; WX 602 ; N uni1F18 ; G 1597
+U 7961 ; WX 602 ; N uni1F19 ; G 1598
+U 7962 ; WX 602 ; N uni1F1A ; G 1599
+U 7963 ; WX 602 ; N uni1F1B ; G 1600
+U 7964 ; WX 602 ; N uni1F1C ; G 1601
+U 7965 ; WX 602 ; N uni1F1D ; G 1602
+U 7968 ; WX 602 ; N uni1F20 ; G 1603
+U 7969 ; WX 602 ; N uni1F21 ; G 1604
+U 7970 ; WX 602 ; N uni1F22 ; G 1605
+U 7971 ; WX 602 ; N uni1F23 ; G 1606
+U 7972 ; WX 602 ; N uni1F24 ; G 1607
+U 7973 ; WX 602 ; N uni1F25 ; G 1608
+U 7974 ; WX 602 ; N uni1F26 ; G 1609
+U 7975 ; WX 602 ; N uni1F27 ; G 1610
+U 7976 ; WX 602 ; N uni1F28 ; G 1611
+U 7977 ; WX 602 ; N uni1F29 ; G 1612
+U 7978 ; WX 602 ; N uni1F2A ; G 1613
+U 7979 ; WX 602 ; N uni1F2B ; G 1614
+U 7980 ; WX 602 ; N uni1F2C ; G 1615
+U 7981 ; WX 602 ; N uni1F2D ; G 1616
+U 7982 ; WX 602 ; N uni1F2E ; G 1617
+U 7983 ; WX 602 ; N uni1F2F ; G 1618
+U 7984 ; WX 602 ; N uni1F30 ; G 1619
+U 7985 ; WX 602 ; N uni1F31 ; G 1620
+U 7986 ; WX 602 ; N uni1F32 ; G 1621
+U 7987 ; WX 602 ; N uni1F33 ; G 1622
+U 7988 ; WX 602 ; N uni1F34 ; G 1623
+U 7989 ; WX 602 ; N uni1F35 ; G 1624
+U 7990 ; WX 602 ; N uni1F36 ; G 1625
+U 7991 ; WX 602 ; N uni1F37 ; G 1626
+U 7992 ; WX 602 ; N uni1F38 ; G 1627
+U 7993 ; WX 602 ; N uni1F39 ; G 1628
+U 7994 ; WX 602 ; N uni1F3A ; G 1629
+U 7995 ; WX 602 ; N uni1F3B ; G 1630
+U 7996 ; WX 602 ; N uni1F3C ; G 1631
+U 7997 ; WX 602 ; N uni1F3D ; G 1632
+U 7998 ; WX 602 ; N uni1F3E ; G 1633
+U 7999 ; WX 602 ; N uni1F3F ; G 1634
+U 8000 ; WX 602 ; N uni1F40 ; G 1635
+U 8001 ; WX 602 ; N uni1F41 ; G 1636
+U 8002 ; WX 602 ; N uni1F42 ; G 1637
+U 8003 ; WX 602 ; N uni1F43 ; G 1638
+U 8004 ; WX 602 ; N uni1F44 ; G 1639
+U 8005 ; WX 602 ; N uni1F45 ; G 1640
+U 8008 ; WX 602 ; N uni1F48 ; G 1641
+U 8009 ; WX 602 ; N uni1F49 ; G 1642
+U 8010 ; WX 602 ; N uni1F4A ; G 1643
+U 8011 ; WX 602 ; N uni1F4B ; G 1644
+U 8012 ; WX 602 ; N uni1F4C ; G 1645
+U 8013 ; WX 602 ; N uni1F4D ; G 1646
+U 8016 ; WX 602 ; N uni1F50 ; G 1647
+U 8017 ; WX 602 ; N uni1F51 ; G 1648
+U 8018 ; WX 602 ; N uni1F52 ; G 1649
+U 8019 ; WX 602 ; N uni1F53 ; G 1650
+U 8020 ; WX 602 ; N uni1F54 ; G 1651
+U 8021 ; WX 602 ; N uni1F55 ; G 1652
+U 8022 ; WX 602 ; N uni1F56 ; G 1653
+U 8023 ; WX 602 ; N uni1F57 ; G 1654
+U 8025 ; WX 602 ; N uni1F59 ; G 1655
+U 8027 ; WX 602 ; N uni1F5B ; G 1656
+U 8029 ; WX 602 ; N uni1F5D ; G 1657
+U 8031 ; WX 602 ; N uni1F5F ; G 1658
+U 8032 ; WX 602 ; N uni1F60 ; G 1659
+U 8033 ; WX 602 ; N uni1F61 ; G 1660
+U 8034 ; WX 602 ; N uni1F62 ; G 1661
+U 8035 ; WX 602 ; N uni1F63 ; G 1662
+U 8036 ; WX 602 ; N uni1F64 ; G 1663
+U 8037 ; WX 602 ; N uni1F65 ; G 1664
+U 8038 ; WX 602 ; N uni1F66 ; G 1665
+U 8039 ; WX 602 ; N uni1F67 ; G 1666
+U 8040 ; WX 602 ; N uni1F68 ; G 1667
+U 8041 ; WX 602 ; N uni1F69 ; G 1668
+U 8042 ; WX 602 ; N uni1F6A ; G 1669
+U 8043 ; WX 602 ; N uni1F6B ; G 1670
+U 8044 ; WX 602 ; N uni1F6C ; G 1671
+U 8045 ; WX 602 ; N uni1F6D ; G 1672
+U 8046 ; WX 602 ; N uni1F6E ; G 1673
+U 8047 ; WX 602 ; N uni1F6F ; G 1674
+U 8048 ; WX 602 ; N uni1F70 ; G 1675
+U 8049 ; WX 602 ; N uni1F71 ; G 1676
+U 8050 ; WX 602 ; N uni1F72 ; G 1677
+U 8051 ; WX 602 ; N uni1F73 ; G 1678
+U 8052 ; WX 602 ; N uni1F74 ; G 1679
+U 8053 ; WX 602 ; N uni1F75 ; G 1680
+U 8054 ; WX 602 ; N uni1F76 ; G 1681
+U 8055 ; WX 602 ; N uni1F77 ; G 1682
+U 8056 ; WX 602 ; N uni1F78 ; G 1683
+U 8057 ; WX 602 ; N uni1F79 ; G 1684
+U 8058 ; WX 602 ; N uni1F7A ; G 1685
+U 8059 ; WX 602 ; N uni1F7B ; G 1686
+U 8060 ; WX 602 ; N uni1F7C ; G 1687
+U 8061 ; WX 602 ; N uni1F7D ; G 1688
+U 8064 ; WX 602 ; N uni1F80 ; G 1689
+U 8065 ; WX 602 ; N uni1F81 ; G 1690
+U 8066 ; WX 602 ; N uni1F82 ; G 1691
+U 8067 ; WX 602 ; N uni1F83 ; G 1692
+U 8068 ; WX 602 ; N uni1F84 ; G 1693
+U 8069 ; WX 602 ; N uni1F85 ; G 1694
+U 8070 ; WX 602 ; N uni1F86 ; G 1695
+U 8071 ; WX 602 ; N uni1F87 ; G 1696
+U 8072 ; WX 602 ; N uni1F88 ; G 1697
+U 8073 ; WX 602 ; N uni1F89 ; G 1698
+U 8074 ; WX 602 ; N uni1F8A ; G 1699
+U 8075 ; WX 602 ; N uni1F8B ; G 1700
+U 8076 ; WX 602 ; N uni1F8C ; G 1701
+U 8077 ; WX 602 ; N uni1F8D ; G 1702
+U 8078 ; WX 602 ; N uni1F8E ; G 1703
+U 8079 ; WX 602 ; N uni1F8F ; G 1704
+U 8080 ; WX 602 ; N uni1F90 ; G 1705
+U 8081 ; WX 602 ; N uni1F91 ; G 1706
+U 8082 ; WX 602 ; N uni1F92 ; G 1707
+U 8083 ; WX 602 ; N uni1F93 ; G 1708
+U 8084 ; WX 602 ; N uni1F94 ; G 1709
+U 8085 ; WX 602 ; N uni1F95 ; G 1710
+U 8086 ; WX 602 ; N uni1F96 ; G 1711
+U 8087 ; WX 602 ; N uni1F97 ; G 1712
+U 8088 ; WX 602 ; N uni1F98 ; G 1713
+U 8089 ; WX 602 ; N uni1F99 ; G 1714
+U 8090 ; WX 602 ; N uni1F9A ; G 1715
+U 8091 ; WX 602 ; N uni1F9B ; G 1716
+U 8092 ; WX 602 ; N uni1F9C ; G 1717
+U 8093 ; WX 602 ; N uni1F9D ; G 1718
+U 8094 ; WX 602 ; N uni1F9E ; G 1719
+U 8095 ; WX 602 ; N uni1F9F ; G 1720
+U 8096 ; WX 602 ; N uni1FA0 ; G 1721
+U 8097 ; WX 602 ; N uni1FA1 ; G 1722
+U 8098 ; WX 602 ; N uni1FA2 ; G 1723
+U 8099 ; WX 602 ; N uni1FA3 ; G 1724
+U 8100 ; WX 602 ; N uni1FA4 ; G 1725
+U 8101 ; WX 602 ; N uni1FA5 ; G 1726
+U 8102 ; WX 602 ; N uni1FA6 ; G 1727
+U 8103 ; WX 602 ; N uni1FA7 ; G 1728
+U 8104 ; WX 602 ; N uni1FA8 ; G 1729
+U 8105 ; WX 602 ; N uni1FA9 ; G 1730
+U 8106 ; WX 602 ; N uni1FAA ; G 1731
+U 8107 ; WX 602 ; N uni1FAB ; G 1732
+U 8108 ; WX 602 ; N uni1FAC ; G 1733
+U 8109 ; WX 602 ; N uni1FAD ; G 1734
+U 8110 ; WX 602 ; N uni1FAE ; G 1735
+U 8111 ; WX 602 ; N uni1FAF ; G 1736
+U 8112 ; WX 602 ; N uni1FB0 ; G 1737
+U 8113 ; WX 602 ; N uni1FB1 ; G 1738
+U 8114 ; WX 602 ; N uni1FB2 ; G 1739
+U 8115 ; WX 602 ; N uni1FB3 ; G 1740
+U 8116 ; WX 602 ; N uni1FB4 ; G 1741
+U 8118 ; WX 602 ; N uni1FB6 ; G 1742
+U 8119 ; WX 602 ; N uni1FB7 ; G 1743
+U 8120 ; WX 602 ; N uni1FB8 ; G 1744
+U 8121 ; WX 602 ; N uni1FB9 ; G 1745
+U 8122 ; WX 602 ; N uni1FBA ; G 1746
+U 8123 ; WX 602 ; N uni1FBB ; G 1747
+U 8124 ; WX 602 ; N uni1FBC ; G 1748
+U 8125 ; WX 602 ; N uni1FBD ; G 1749
+U 8126 ; WX 602 ; N uni1FBE ; G 1750
+U 8127 ; WX 602 ; N uni1FBF ; G 1751
+U 8128 ; WX 602 ; N uni1FC0 ; G 1752
+U 8129 ; WX 602 ; N uni1FC1 ; G 1753
+U 8130 ; WX 602 ; N uni1FC2 ; G 1754
+U 8131 ; WX 602 ; N uni1FC3 ; G 1755
+U 8132 ; WX 602 ; N uni1FC4 ; G 1756
+U 8134 ; WX 602 ; N uni1FC6 ; G 1757
+U 8135 ; WX 602 ; N uni1FC7 ; G 1758
+U 8136 ; WX 602 ; N uni1FC8 ; G 1759
+U 8137 ; WX 602 ; N uni1FC9 ; G 1760
+U 8138 ; WX 602 ; N uni1FCA ; G 1761
+U 8139 ; WX 602 ; N uni1FCB ; G 1762
+U 8140 ; WX 602 ; N uni1FCC ; G 1763
+U 8141 ; WX 602 ; N uni1FCD ; G 1764
+U 8142 ; WX 602 ; N uni1FCE ; G 1765
+U 8143 ; WX 602 ; N uni1FCF ; G 1766
+U 8144 ; WX 602 ; N uni1FD0 ; G 1767
+U 8145 ; WX 602 ; N uni1FD1 ; G 1768
+U 8146 ; WX 602 ; N uni1FD2 ; G 1769
+U 8147 ; WX 602 ; N uni1FD3 ; G 1770
+U 8150 ; WX 602 ; N uni1FD6 ; G 1771
+U 8151 ; WX 602 ; N uni1FD7 ; G 1772
+U 8152 ; WX 602 ; N uni1FD8 ; G 1773
+U 8153 ; WX 602 ; N uni1FD9 ; G 1774
+U 8154 ; WX 602 ; N uni1FDA ; G 1775
+U 8155 ; WX 602 ; N uni1FDB ; G 1776
+U 8157 ; WX 602 ; N uni1FDD ; G 1777
+U 8158 ; WX 602 ; N uni1FDE ; G 1778
+U 8159 ; WX 602 ; N uni1FDF ; G 1779
+U 8160 ; WX 602 ; N uni1FE0 ; G 1780
+U 8161 ; WX 602 ; N uni1FE1 ; G 1781
+U 8162 ; WX 602 ; N uni1FE2 ; G 1782
+U 8163 ; WX 602 ; N uni1FE3 ; G 1783
+U 8164 ; WX 602 ; N uni1FE4 ; G 1784
+U 8165 ; WX 602 ; N uni1FE5 ; G 1785
+U 8166 ; WX 602 ; N uni1FE6 ; G 1786
+U 8167 ; WX 602 ; N uni1FE7 ; G 1787
+U 8168 ; WX 602 ; N uni1FE8 ; G 1788
+U 8169 ; WX 602 ; N uni1FE9 ; G 1789
+U 8170 ; WX 602 ; N uni1FEA ; G 1790
+U 8171 ; WX 602 ; N uni1FEB ; G 1791
+U 8172 ; WX 602 ; N uni1FEC ; G 1792
+U 8173 ; WX 602 ; N uni1FED ; G 1793
+U 8174 ; WX 602 ; N uni1FEE ; G 1794
+U 8175 ; WX 602 ; N uni1FEF ; G 1795
+U 8178 ; WX 602 ; N uni1FF2 ; G 1796
+U 8179 ; WX 602 ; N uni1FF3 ; G 1797
+U 8180 ; WX 602 ; N uni1FF4 ; G 1798
+U 8182 ; WX 602 ; N uni1FF6 ; G 1799
+U 8183 ; WX 602 ; N uni1FF7 ; G 1800
+U 8184 ; WX 602 ; N uni1FF8 ; G 1801
+U 8185 ; WX 602 ; N uni1FF9 ; G 1802
+U 8186 ; WX 602 ; N uni1FFA ; G 1803
+U 8187 ; WX 602 ; N uni1FFB ; G 1804
+U 8188 ; WX 602 ; N uni1FFC ; G 1805
+U 8189 ; WX 602 ; N uni1FFD ; G 1806
+U 8190 ; WX 602 ; N uni1FFE ; G 1807
+U 8192 ; WX 602 ; N uni2000 ; G 1808
+U 8193 ; WX 602 ; N uni2001 ; G 1809
+U 8194 ; WX 602 ; N uni2002 ; G 1810
+U 8195 ; WX 602 ; N uni2003 ; G 1811
+U 8196 ; WX 602 ; N uni2004 ; G 1812
+U 8197 ; WX 602 ; N uni2005 ; G 1813
+U 8198 ; WX 602 ; N uni2006 ; G 1814
+U 8199 ; WX 602 ; N uni2007 ; G 1815
+U 8200 ; WX 602 ; N uni2008 ; G 1816
+U 8201 ; WX 602 ; N uni2009 ; G 1817
+U 8202 ; WX 602 ; N uni200A ; G 1818
+U 8208 ; WX 602 ; N uni2010 ; G 1819
+U 8209 ; WX 602 ; N uni2011 ; G 1820
+U 8210 ; WX 602 ; N figuredash ; G 1821
+U 8211 ; WX 602 ; N endash ; G 1822
+U 8212 ; WX 602 ; N emdash ; G 1823
+U 8213 ; WX 602 ; N uni2015 ; G 1824
+U 8214 ; WX 602 ; N uni2016 ; G 1825
+U 8215 ; WX 602 ; N underscoredbl ; G 1826
+U 8216 ; WX 602 ; N quoteleft ; G 1827
+U 8217 ; WX 602 ; N quoteright ; G 1828
+U 8218 ; WX 602 ; N quotesinglbase ; G 1829
+U 8219 ; WX 602 ; N quotereversed ; G 1830
+U 8220 ; WX 602 ; N quotedblleft ; G 1831
+U 8221 ; WX 602 ; N quotedblright ; G 1832
+U 8222 ; WX 602 ; N quotedblbase ; G 1833
+U 8223 ; WX 602 ; N uni201F ; G 1834
+U 8224 ; WX 602 ; N dagger ; G 1835
+U 8225 ; WX 602 ; N daggerdbl ; G 1836
+U 8226 ; WX 602 ; N bullet ; G 1837
+U 8227 ; WX 602 ; N uni2023 ; G 1838
+U 8230 ; WX 602 ; N ellipsis ; G 1839
+U 8239 ; WX 602 ; N uni202F ; G 1840
+U 8240 ; WX 602 ; N perthousand ; G 1841
+U 8241 ; WX 602 ; N uni2031 ; G 1842
+U 8242 ; WX 602 ; N minute ; G 1843
+U 8243 ; WX 602 ; N second ; G 1844
+U 8244 ; WX 602 ; N uni2034 ; G 1845
+U 8245 ; WX 602 ; N uni2035 ; G 1846
+U 8246 ; WX 602 ; N uni2036 ; G 1847
+U 8247 ; WX 602 ; N uni2037 ; G 1848
+U 8249 ; WX 602 ; N guilsinglleft ; G 1849
+U 8250 ; WX 602 ; N guilsinglright ; G 1850
+U 8252 ; WX 602 ; N exclamdbl ; G 1851
+U 8253 ; WX 602 ; N uni203D ; G 1852
+U 8254 ; WX 602 ; N uni203E ; G 1853
+U 8255 ; WX 602 ; N uni203F ; G 1854
+U 8261 ; WX 602 ; N uni2045 ; G 1855
+U 8262 ; WX 602 ; N uni2046 ; G 1856
+U 8263 ; WX 602 ; N uni2047 ; G 1857
+U 8264 ; WX 602 ; N uni2048 ; G 1858
+U 8265 ; WX 602 ; N uni2049 ; G 1859
+U 8267 ; WX 602 ; N uni204B ; G 1860
+U 8287 ; WX 602 ; N uni205F ; G 1861
+U 8304 ; WX 602 ; N uni2070 ; G 1862
+U 8305 ; WX 602 ; N uni2071 ; G 1863
+U 8308 ; WX 602 ; N uni2074 ; G 1864
+U 8309 ; WX 602 ; N uni2075 ; G 1865
+U 8310 ; WX 602 ; N uni2076 ; G 1866
+U 8311 ; WX 602 ; N uni2077 ; G 1867
+U 8312 ; WX 602 ; N uni2078 ; G 1868
+U 8313 ; WX 602 ; N uni2079 ; G 1869
+U 8314 ; WX 602 ; N uni207A ; G 1870
+U 8315 ; WX 602 ; N uni207B ; G 1871
+U 8316 ; WX 602 ; N uni207C ; G 1872
+U 8317 ; WX 602 ; N uni207D ; G 1873
+U 8318 ; WX 602 ; N uni207E ; G 1874
+U 8319 ; WX 602 ; N uni207F ; G 1875
+U 8320 ; WX 602 ; N uni2080 ; G 1876
+U 8321 ; WX 602 ; N uni2081 ; G 1877
+U 8322 ; WX 602 ; N uni2082 ; G 1878
+U 8323 ; WX 602 ; N uni2083 ; G 1879
+U 8324 ; WX 602 ; N uni2084 ; G 1880
+U 8325 ; WX 602 ; N uni2085 ; G 1881
+U 8326 ; WX 602 ; N uni2086 ; G 1882
+U 8327 ; WX 602 ; N uni2087 ; G 1883
+U 8328 ; WX 602 ; N uni2088 ; G 1884
+U 8329 ; WX 602 ; N uni2089 ; G 1885
+U 8330 ; WX 602 ; N uni208A ; G 1886
+U 8331 ; WX 602 ; N uni208B ; G 1887
+U 8332 ; WX 602 ; N uni208C ; G 1888
+U 8333 ; WX 602 ; N uni208D ; G 1889
+U 8334 ; WX 602 ; N uni208E ; G 1890
+U 8336 ; WX 602 ; N uni2090 ; G 1891
+U 8337 ; WX 602 ; N uni2091 ; G 1892
+U 8338 ; WX 602 ; N uni2092 ; G 1893
+U 8339 ; WX 602 ; N uni2093 ; G 1894
+U 8340 ; WX 602 ; N uni2094 ; G 1895
+U 8341 ; WX 602 ; N uni2095 ; G 1896
+U 8342 ; WX 602 ; N uni2096 ; G 1897
+U 8343 ; WX 602 ; N uni2097 ; G 1898
+U 8344 ; WX 602 ; N uni2098 ; G 1899
+U 8345 ; WX 602 ; N uni2099 ; G 1900
+U 8346 ; WX 602 ; N uni209A ; G 1901
+U 8347 ; WX 602 ; N uni209B ; G 1902
+U 8348 ; WX 602 ; N uni209C ; G 1903
+U 8352 ; WX 602 ; N uni20A0 ; G 1904
+U 8353 ; WX 602 ; N colonmonetary ; G 1905
+U 8354 ; WX 602 ; N uni20A2 ; G 1906
+U 8355 ; WX 602 ; N franc ; G 1907
+U 8356 ; WX 602 ; N lira ; G 1908
+U 8357 ; WX 602 ; N uni20A5 ; G 1909
+U 8358 ; WX 602 ; N uni20A6 ; G 1910
+U 8359 ; WX 602 ; N peseta ; G 1911
+U 8360 ; WX 602 ; N uni20A8 ; G 1912
+U 8361 ; WX 602 ; N uni20A9 ; G 1913
+U 8362 ; WX 602 ; N uni20AA ; G 1914
+U 8363 ; WX 602 ; N dong ; G 1915
+U 8364 ; WX 602 ; N Euro ; G 1916
+U 8365 ; WX 602 ; N uni20AD ; G 1917
+U 8366 ; WX 602 ; N uni20AE ; G 1918
+U 8367 ; WX 602 ; N uni20AF ; G 1919
+U 8368 ; WX 602 ; N uni20B0 ; G 1920
+U 8369 ; WX 602 ; N uni20B1 ; G 1921
+U 8370 ; WX 602 ; N uni20B2 ; G 1922
+U 8371 ; WX 602 ; N uni20B3 ; G 1923
+U 8372 ; WX 602 ; N uni20B4 ; G 1924
+U 8373 ; WX 602 ; N uni20B5 ; G 1925
+U 8376 ; WX 602 ; N uni20B8 ; G 1926
+U 8377 ; WX 602 ; N uni20B9 ; G 1927
+U 8378 ; WX 602 ; N uni20BA ; G 1928
+U 8381 ; WX 602 ; N uni20BD ; G 1929
+U 8450 ; WX 602 ; N uni2102 ; G 1930
+U 8453 ; WX 602 ; N uni2105 ; G 1931
+U 8461 ; WX 602 ; N uni210D ; G 1932
+U 8462 ; WX 602 ; N uni210E ; G 1933
+U 8463 ; WX 602 ; N uni210F ; G 1934
+U 8469 ; WX 602 ; N uni2115 ; G 1935
+U 8470 ; WX 602 ; N uni2116 ; G 1936
+U 8471 ; WX 602 ; N uni2117 ; G 1937
+U 8473 ; WX 602 ; N uni2119 ; G 1938
+U 8474 ; WX 602 ; N uni211A ; G 1939
+U 8477 ; WX 602 ; N uni211D ; G 1940
+U 8482 ; WX 602 ; N trademark ; G 1941
+U 8484 ; WX 602 ; N uni2124 ; G 1942
+U 8486 ; WX 602 ; N uni2126 ; G 1943
+U 8490 ; WX 602 ; N uni212A ; G 1944
+U 8491 ; WX 602 ; N uni212B ; G 1945
+U 8494 ; WX 602 ; N estimated ; G 1946
+U 8520 ; WX 602 ; N uni2148 ; G 1947
+U 8528 ; WX 602 ; N uni2150 ; G 1948
+U 8529 ; WX 602 ; N uni2151 ; G 1949
+U 8531 ; WX 602 ; N onethird ; G 1950
+U 8532 ; WX 602 ; N twothirds ; G 1951
+U 8533 ; WX 602 ; N uni2155 ; G 1952
+U 8534 ; WX 602 ; N uni2156 ; G 1953
+U 8535 ; WX 602 ; N uni2157 ; G 1954
+U 8536 ; WX 602 ; N uni2158 ; G 1955
+U 8537 ; WX 602 ; N uni2159 ; G 1956
+U 8538 ; WX 602 ; N uni215A ; G 1957
+U 8539 ; WX 602 ; N oneeighth ; G 1958
+U 8540 ; WX 602 ; N threeeighths ; G 1959
+U 8541 ; WX 602 ; N fiveeighths ; G 1960
+U 8542 ; WX 602 ; N seveneighths ; G 1961
+U 8543 ; WX 602 ; N uni215F ; G 1962
+U 8585 ; WX 602 ; N uni2189 ; G 1963
+U 8592 ; WX 602 ; N arrowleft ; G 1964
+U 8593 ; WX 602 ; N arrowup ; G 1965
+U 8594 ; WX 602 ; N arrowright ; G 1966
+U 8595 ; WX 602 ; N arrowdown ; G 1967
+U 8596 ; WX 602 ; N arrowboth ; G 1968
+U 8597 ; WX 602 ; N arrowupdn ; G 1969
+U 8598 ; WX 602 ; N uni2196 ; G 1970
+U 8599 ; WX 602 ; N uni2197 ; G 1971
+U 8600 ; WX 602 ; N uni2198 ; G 1972
+U 8601 ; WX 602 ; N uni2199 ; G 1973
+U 8602 ; WX 602 ; N uni219A ; G 1974
+U 8603 ; WX 602 ; N uni219B ; G 1975
+U 8604 ; WX 602 ; N uni219C ; G 1976
+U 8605 ; WX 602 ; N uni219D ; G 1977
+U 8606 ; WX 602 ; N uni219E ; G 1978
+U 8607 ; WX 602 ; N uni219F ; G 1979
+U 8608 ; WX 602 ; N uni21A0 ; G 1980
+U 8609 ; WX 602 ; N uni21A1 ; G 1981
+U 8610 ; WX 602 ; N uni21A2 ; G 1982
+U 8611 ; WX 602 ; N uni21A3 ; G 1983
+U 8612 ; WX 602 ; N uni21A4 ; G 1984
+U 8613 ; WX 602 ; N uni21A5 ; G 1985
+U 8614 ; WX 602 ; N uni21A6 ; G 1986
+U 8615 ; WX 602 ; N uni21A7 ; G 1987
+U 8616 ; WX 602 ; N arrowupdnbse ; G 1988
+U 8617 ; WX 602 ; N uni21A9 ; G 1989
+U 8618 ; WX 602 ; N uni21AA ; G 1990
+U 8619 ; WX 602 ; N uni21AB ; G 1991
+U 8620 ; WX 602 ; N uni21AC ; G 1992
+U 8621 ; WX 602 ; N uni21AD ; G 1993
+U 8622 ; WX 602 ; N uni21AE ; G 1994
+U 8623 ; WX 602 ; N uni21AF ; G 1995
+U 8624 ; WX 602 ; N uni21B0 ; G 1996
+U 8625 ; WX 602 ; N uni21B1 ; G 1997
+U 8626 ; WX 602 ; N uni21B2 ; G 1998
+U 8627 ; WX 602 ; N uni21B3 ; G 1999
+U 8628 ; WX 602 ; N uni21B4 ; G 2000
+U 8629 ; WX 602 ; N carriagereturn ; G 2001
+U 8630 ; WX 602 ; N uni21B6 ; G 2002
+U 8631 ; WX 602 ; N uni21B7 ; G 2003
+U 8632 ; WX 602 ; N uni21B8 ; G 2004
+U 8633 ; WX 602 ; N uni21B9 ; G 2005
+U 8634 ; WX 602 ; N uni21BA ; G 2006
+U 8635 ; WX 602 ; N uni21BB ; G 2007
+U 8636 ; WX 602 ; N uni21BC ; G 2008
+U 8637 ; WX 602 ; N uni21BD ; G 2009
+U 8638 ; WX 602 ; N uni21BE ; G 2010
+U 8639 ; WX 602 ; N uni21BF ; G 2011
+U 8640 ; WX 602 ; N uni21C0 ; G 2012
+U 8641 ; WX 602 ; N uni21C1 ; G 2013
+U 8642 ; WX 602 ; N uni21C2 ; G 2014
+U 8643 ; WX 602 ; N uni21C3 ; G 2015
+U 8644 ; WX 602 ; N uni21C4 ; G 2016
+U 8645 ; WX 602 ; N uni21C5 ; G 2017
+U 8646 ; WX 602 ; N uni21C6 ; G 2018
+U 8647 ; WX 602 ; N uni21C7 ; G 2019
+U 8648 ; WX 602 ; N uni21C8 ; G 2020
+U 8649 ; WX 602 ; N uni21C9 ; G 2021
+U 8650 ; WX 602 ; N uni21CA ; G 2022
+U 8651 ; WX 602 ; N uni21CB ; G 2023
+U 8652 ; WX 602 ; N uni21CC ; G 2024
+U 8653 ; WX 602 ; N uni21CD ; G 2025
+U 8654 ; WX 602 ; N uni21CE ; G 2026
+U 8655 ; WX 602 ; N uni21CF ; G 2027
+U 8656 ; WX 602 ; N arrowdblleft ; G 2028
+U 8657 ; WX 602 ; N arrowdblup ; G 2029
+U 8658 ; WX 602 ; N arrowdblright ; G 2030
+U 8659 ; WX 602 ; N arrowdbldown ; G 2031
+U 8660 ; WX 602 ; N arrowdblboth ; G 2032
+U 8661 ; WX 602 ; N uni21D5 ; G 2033
+U 8662 ; WX 602 ; N uni21D6 ; G 2034
+U 8663 ; WX 602 ; N uni21D7 ; G 2035
+U 8664 ; WX 602 ; N uni21D8 ; G 2036
+U 8665 ; WX 602 ; N uni21D9 ; G 2037
+U 8666 ; WX 602 ; N uni21DA ; G 2038
+U 8667 ; WX 602 ; N uni21DB ; G 2039
+U 8668 ; WX 602 ; N uni21DC ; G 2040
+U 8669 ; WX 602 ; N uni21DD ; G 2041
+U 8670 ; WX 602 ; N uni21DE ; G 2042
+U 8671 ; WX 602 ; N uni21DF ; G 2043
+U 8672 ; WX 602 ; N uni21E0 ; G 2044
+U 8673 ; WX 602 ; N uni21E1 ; G 2045
+U 8674 ; WX 602 ; N uni21E2 ; G 2046
+U 8675 ; WX 602 ; N uni21E3 ; G 2047
+U 8676 ; WX 602 ; N uni21E4 ; G 2048
+U 8677 ; WX 602 ; N uni21E5 ; G 2049
+U 8678 ; WX 602 ; N uni21E6 ; G 2050
+U 8679 ; WX 602 ; N uni21E7 ; G 2051
+U 8680 ; WX 602 ; N uni21E8 ; G 2052
+U 8681 ; WX 602 ; N uni21E9 ; G 2053
+U 8682 ; WX 602 ; N uni21EA ; G 2054
+U 8683 ; WX 602 ; N uni21EB ; G 2055
+U 8684 ; WX 602 ; N uni21EC ; G 2056
+U 8685 ; WX 602 ; N uni21ED ; G 2057
+U 8686 ; WX 602 ; N uni21EE ; G 2058
+U 8687 ; WX 602 ; N uni21EF ; G 2059
+U 8688 ; WX 602 ; N uni21F0 ; G 2060
+U 8689 ; WX 602 ; N uni21F1 ; G 2061
+U 8690 ; WX 602 ; N uni21F2 ; G 2062
+U 8691 ; WX 602 ; N uni21F3 ; G 2063
+U 8692 ; WX 602 ; N uni21F4 ; G 2064
+U 8693 ; WX 602 ; N uni21F5 ; G 2065
+U 8694 ; WX 602 ; N uni21F6 ; G 2066
+U 8695 ; WX 602 ; N uni21F7 ; G 2067
+U 8696 ; WX 602 ; N uni21F8 ; G 2068
+U 8697 ; WX 602 ; N uni21F9 ; G 2069
+U 8698 ; WX 602 ; N uni21FA ; G 2070
+U 8699 ; WX 602 ; N uni21FB ; G 2071
+U 8700 ; WX 602 ; N uni21FC ; G 2072
+U 8701 ; WX 602 ; N uni21FD ; G 2073
+U 8702 ; WX 602 ; N uni21FE ; G 2074
+U 8703 ; WX 602 ; N uni21FF ; G 2075
+U 8704 ; WX 602 ; N universal ; G 2076
+U 8705 ; WX 602 ; N uni2201 ; G 2077
+U 8706 ; WX 602 ; N partialdiff ; G 2078
+U 8707 ; WX 602 ; N existential ; G 2079
+U 8708 ; WX 602 ; N uni2204 ; G 2080
+U 8709 ; WX 602 ; N emptyset ; G 2081
+U 8710 ; WX 602 ; N increment ; G 2082
+U 8711 ; WX 602 ; N gradient ; G 2083
+U 8712 ; WX 602 ; N element ; G 2084
+U 8713 ; WX 602 ; N notelement ; G 2085
+U 8714 ; WX 602 ; N uni220A ; G 2086
+U 8715 ; WX 602 ; N suchthat ; G 2087
+U 8716 ; WX 602 ; N uni220C ; G 2088
+U 8717 ; WX 602 ; N uni220D ; G 2089
+U 8718 ; WX 602 ; N uni220E ; G 2090
+U 8719 ; WX 602 ; N product ; G 2091
+U 8720 ; WX 602 ; N uni2210 ; G 2092
+U 8721 ; WX 602 ; N summation ; G 2093
+U 8722 ; WX 602 ; N minus ; G 2094
+U 8723 ; WX 602 ; N uni2213 ; G 2095
+U 8725 ; WX 602 ; N uni2215 ; G 2096
+U 8727 ; WX 602 ; N asteriskmath ; G 2097
+U 8728 ; WX 602 ; N uni2218 ; G 2098
+U 8729 ; WX 602 ; N uni2219 ; G 2099
+U 8730 ; WX 602 ; N radical ; G 2100
+U 8731 ; WX 602 ; N uni221B ; G 2101
+U 8732 ; WX 602 ; N uni221C ; G 2102
+U 8733 ; WX 602 ; N proportional ; G 2103
+U 8734 ; WX 602 ; N infinity ; G 2104
+U 8735 ; WX 602 ; N orthogonal ; G 2105
+U 8736 ; WX 602 ; N angle ; G 2106
+U 8739 ; WX 602 ; N uni2223 ; G 2107
+U 8743 ; WX 602 ; N logicaland ; G 2108
+U 8744 ; WX 602 ; N logicalor ; G 2109
+U 8745 ; WX 602 ; N intersection ; G 2110
+U 8746 ; WX 602 ; N union ; G 2111
+U 8747 ; WX 602 ; N integral ; G 2112
+U 8748 ; WX 602 ; N uni222C ; G 2113
+U 8749 ; WX 602 ; N uni222D ; G 2114
+U 8756 ; WX 602 ; N therefore ; G 2115
+U 8757 ; WX 602 ; N uni2235 ; G 2116
+U 8758 ; WX 602 ; N uni2236 ; G 2117
+U 8759 ; WX 602 ; N uni2237 ; G 2118
+U 8760 ; WX 602 ; N uni2238 ; G 2119
+U 8761 ; WX 602 ; N uni2239 ; G 2120
+U 8762 ; WX 602 ; N uni223A ; G 2121
+U 8763 ; WX 602 ; N uni223B ; G 2122
+U 8764 ; WX 602 ; N similar ; G 2123
+U 8765 ; WX 602 ; N uni223D ; G 2124
+U 8769 ; WX 602 ; N uni2241 ; G 2125
+U 8770 ; WX 602 ; N uni2242 ; G 2126
+U 8771 ; WX 602 ; N uni2243 ; G 2127
+U 8772 ; WX 602 ; N uni2244 ; G 2128
+U 8773 ; WX 602 ; N congruent ; G 2129
+U 8774 ; WX 602 ; N uni2246 ; G 2130
+U 8775 ; WX 602 ; N uni2247 ; G 2131
+U 8776 ; WX 602 ; N approxequal ; G 2132
+U 8777 ; WX 602 ; N uni2249 ; G 2133
+U 8778 ; WX 602 ; N uni224A ; G 2134
+U 8779 ; WX 602 ; N uni224B ; G 2135
+U 8780 ; WX 602 ; N uni224C ; G 2136
+U 8781 ; WX 602 ; N uni224D ; G 2137
+U 8782 ; WX 602 ; N uni224E ; G 2138
+U 8783 ; WX 602 ; N uni224F ; G 2139
+U 8784 ; WX 602 ; N uni2250 ; G 2140
+U 8785 ; WX 602 ; N uni2251 ; G 2141
+U 8786 ; WX 602 ; N uni2252 ; G 2142
+U 8787 ; WX 602 ; N uni2253 ; G 2143
+U 8788 ; WX 602 ; N uni2254 ; G 2144
+U 8789 ; WX 602 ; N uni2255 ; G 2145
+U 8790 ; WX 602 ; N uni2256 ; G 2146
+U 8791 ; WX 602 ; N uni2257 ; G 2147
+U 8792 ; WX 602 ; N uni2258 ; G 2148
+U 8793 ; WX 602 ; N uni2259 ; G 2149
+U 8794 ; WX 602 ; N uni225A ; G 2150
+U 8795 ; WX 602 ; N uni225B ; G 2151
+U 8796 ; WX 602 ; N uni225C ; G 2152
+U 8797 ; WX 602 ; N uni225D ; G 2153
+U 8798 ; WX 602 ; N uni225E ; G 2154
+U 8799 ; WX 602 ; N uni225F ; G 2155
+U 8800 ; WX 602 ; N notequal ; G 2156
+U 8801 ; WX 602 ; N equivalence ; G 2157
+U 8802 ; WX 602 ; N uni2262 ; G 2158
+U 8803 ; WX 602 ; N uni2263 ; G 2159
+U 8804 ; WX 602 ; N lessequal ; G 2160
+U 8805 ; WX 602 ; N greaterequal ; G 2161
+U 8806 ; WX 602 ; N uni2266 ; G 2162
+U 8807 ; WX 602 ; N uni2267 ; G 2163
+U 8808 ; WX 602 ; N uni2268 ; G 2164
+U 8809 ; WX 602 ; N uni2269 ; G 2165
+U 8813 ; WX 602 ; N uni226D ; G 2166
+U 8814 ; WX 602 ; N uni226E ; G 2167
+U 8815 ; WX 602 ; N uni226F ; G 2168
+U 8816 ; WX 602 ; N uni2270 ; G 2169
+U 8817 ; WX 602 ; N uni2271 ; G 2170
+U 8818 ; WX 602 ; N uni2272 ; G 2171
+U 8819 ; WX 602 ; N uni2273 ; G 2172
+U 8820 ; WX 602 ; N uni2274 ; G 2173
+U 8821 ; WX 602 ; N uni2275 ; G 2174
+U 8822 ; WX 602 ; N uni2276 ; G 2175
+U 8823 ; WX 602 ; N uni2277 ; G 2176
+U 8824 ; WX 602 ; N uni2278 ; G 2177
+U 8825 ; WX 602 ; N uni2279 ; G 2178
+U 8826 ; WX 602 ; N uni227A ; G 2179
+U 8827 ; WX 602 ; N uni227B ; G 2180
+U 8828 ; WX 602 ; N uni227C ; G 2181
+U 8829 ; WX 602 ; N uni227D ; G 2182
+U 8830 ; WX 602 ; N uni227E ; G 2183
+U 8831 ; WX 602 ; N uni227F ; G 2184
+U 8832 ; WX 602 ; N uni2280 ; G 2185
+U 8833 ; WX 602 ; N uni2281 ; G 2186
+U 8834 ; WX 602 ; N propersubset ; G 2187
+U 8835 ; WX 602 ; N propersuperset ; G 2188
+U 8836 ; WX 602 ; N notsubset ; G 2189
+U 8837 ; WX 602 ; N uni2285 ; G 2190
+U 8838 ; WX 602 ; N reflexsubset ; G 2191
+U 8839 ; WX 602 ; N reflexsuperset ; G 2192
+U 8840 ; WX 602 ; N uni2288 ; G 2193
+U 8841 ; WX 602 ; N uni2289 ; G 2194
+U 8842 ; WX 602 ; N uni228A ; G 2195
+U 8843 ; WX 602 ; N uni228B ; G 2196
+U 8845 ; WX 602 ; N uni228D ; G 2197
+U 8846 ; WX 602 ; N uni228E ; G 2198
+U 8847 ; WX 602 ; N uni228F ; G 2199
+U 8848 ; WX 602 ; N uni2290 ; G 2200
+U 8849 ; WX 602 ; N uni2291 ; G 2201
+U 8850 ; WX 602 ; N uni2292 ; G 2202
+U 8851 ; WX 602 ; N uni2293 ; G 2203
+U 8852 ; WX 602 ; N uni2294 ; G 2204
+U 8853 ; WX 602 ; N circleplus ; G 2205
+U 8854 ; WX 602 ; N uni2296 ; G 2206
+U 8855 ; WX 602 ; N circlemultiply ; G 2207
+U 8856 ; WX 602 ; N uni2298 ; G 2208
+U 8857 ; WX 602 ; N uni2299 ; G 2209
+U 8858 ; WX 602 ; N uni229A ; G 2210
+U 8859 ; WX 602 ; N uni229B ; G 2211
+U 8860 ; WX 602 ; N uni229C ; G 2212
+U 8861 ; WX 602 ; N uni229D ; G 2213
+U 8862 ; WX 602 ; N uni229E ; G 2214
+U 8863 ; WX 602 ; N uni229F ; G 2215
+U 8864 ; WX 602 ; N uni22A0 ; G 2216
+U 8865 ; WX 602 ; N uni22A1 ; G 2217
+U 8866 ; WX 602 ; N uni22A2 ; G 2218
+U 8867 ; WX 602 ; N uni22A3 ; G 2219
+U 8868 ; WX 602 ; N uni22A4 ; G 2220
+U 8869 ; WX 602 ; N perpendicular ; G 2221
+U 8882 ; WX 602 ; N uni22B2 ; G 2222
+U 8883 ; WX 602 ; N uni22B3 ; G 2223
+U 8884 ; WX 602 ; N uni22B4 ; G 2224
+U 8885 ; WX 602 ; N uni22B5 ; G 2225
+U 8888 ; WX 602 ; N uni22B8 ; G 2226
+U 8898 ; WX 602 ; N uni22C2 ; G 2227
+U 8899 ; WX 602 ; N uni22C3 ; G 2228
+U 8900 ; WX 602 ; N uni22C4 ; G 2229
+U 8901 ; WX 602 ; N dotmath ; G 2230
+U 8902 ; WX 602 ; N uni22C6 ; G 2231
+U 8909 ; WX 602 ; N uni22CD ; G 2232
+U 8910 ; WX 602 ; N uni22CE ; G 2233
+U 8911 ; WX 602 ; N uni22CF ; G 2234
+U 8912 ; WX 602 ; N uni22D0 ; G 2235
+U 8913 ; WX 602 ; N uni22D1 ; G 2236
+U 8922 ; WX 602 ; N uni22DA ; G 2237
+U 8923 ; WX 602 ; N uni22DB ; G 2238
+U 8924 ; WX 602 ; N uni22DC ; G 2239
+U 8925 ; WX 602 ; N uni22DD ; G 2240
+U 8926 ; WX 602 ; N uni22DE ; G 2241
+U 8927 ; WX 602 ; N uni22DF ; G 2242
+U 8928 ; WX 602 ; N uni22E0 ; G 2243
+U 8929 ; WX 602 ; N uni22E1 ; G 2244
+U 8930 ; WX 602 ; N uni22E2 ; G 2245
+U 8931 ; WX 602 ; N uni22E3 ; G 2246
+U 8932 ; WX 602 ; N uni22E4 ; G 2247
+U 8933 ; WX 602 ; N uni22E5 ; G 2248
+U 8934 ; WX 602 ; N uni22E6 ; G 2249
+U 8935 ; WX 602 ; N uni22E7 ; G 2250
+U 8936 ; WX 602 ; N uni22E8 ; G 2251
+U 8937 ; WX 602 ; N uni22E9 ; G 2252
+U 8943 ; WX 602 ; N uni22EF ; G 2253
+U 8960 ; WX 602 ; N uni2300 ; G 2254
+U 8961 ; WX 602 ; N uni2301 ; G 2255
+U 8962 ; WX 602 ; N house ; G 2256
+U 8963 ; WX 602 ; N uni2303 ; G 2257
+U 8964 ; WX 602 ; N uni2304 ; G 2258
+U 8965 ; WX 602 ; N uni2305 ; G 2259
+U 8966 ; WX 602 ; N uni2306 ; G 2260
+U 8968 ; WX 602 ; N uni2308 ; G 2261
+U 8969 ; WX 602 ; N uni2309 ; G 2262
+U 8970 ; WX 602 ; N uni230A ; G 2263
+U 8971 ; WX 602 ; N uni230B ; G 2264
+U 8972 ; WX 602 ; N uni230C ; G 2265
+U 8973 ; WX 602 ; N uni230D ; G 2266
+U 8974 ; WX 602 ; N uni230E ; G 2267
+U 8975 ; WX 602 ; N uni230F ; G 2268
+U 8976 ; WX 602 ; N revlogicalnot ; G 2269
+U 8977 ; WX 602 ; N uni2311 ; G 2270
+U 8978 ; WX 602 ; N uni2312 ; G 2271
+U 8979 ; WX 602 ; N uni2313 ; G 2272
+U 8980 ; WX 602 ; N uni2314 ; G 2273
+U 8981 ; WX 602 ; N uni2315 ; G 2274
+U 8984 ; WX 602 ; N uni2318 ; G 2275
+U 8985 ; WX 602 ; N uni2319 ; G 2276
+U 8988 ; WX 602 ; N uni231C ; G 2277
+U 8989 ; WX 602 ; N uni231D ; G 2278
+U 8990 ; WX 602 ; N uni231E ; G 2279
+U 8991 ; WX 602 ; N uni231F ; G 2280
+U 8992 ; WX 602 ; N integraltp ; G 2281
+U 8993 ; WX 602 ; N integralbt ; G 2282
+U 8997 ; WX 602 ; N uni2325 ; G 2283
+U 8998 ; WX 602 ; N uni2326 ; G 2284
+U 8999 ; WX 602 ; N uni2327 ; G 2285
+U 9000 ; WX 602 ; N uni2328 ; G 2286
+U 9003 ; WX 602 ; N uni232B ; G 2287
+U 9013 ; WX 602 ; N uni2335 ; G 2288
+U 9014 ; WX 602 ; N uni2336 ; G 2289
+U 9015 ; WX 602 ; N uni2337 ; G 2290
+U 9016 ; WX 602 ; N uni2338 ; G 2291
+U 9017 ; WX 602 ; N uni2339 ; G 2292
+U 9018 ; WX 602 ; N uni233A ; G 2293
+U 9019 ; WX 602 ; N uni233B ; G 2294
+U 9020 ; WX 602 ; N uni233C ; G 2295
+U 9021 ; WX 602 ; N uni233D ; G 2296
+U 9022 ; WX 602 ; N uni233E ; G 2297
+U 9023 ; WX 602 ; N uni233F ; G 2298
+U 9024 ; WX 602 ; N uni2340 ; G 2299
+U 9025 ; WX 602 ; N uni2341 ; G 2300
+U 9026 ; WX 602 ; N uni2342 ; G 2301
+U 9027 ; WX 602 ; N uni2343 ; G 2302
+U 9028 ; WX 602 ; N uni2344 ; G 2303
+U 9029 ; WX 602 ; N uni2345 ; G 2304
+U 9030 ; WX 602 ; N uni2346 ; G 2305
+U 9031 ; WX 602 ; N uni2347 ; G 2306
+U 9032 ; WX 602 ; N uni2348 ; G 2307
+U 9033 ; WX 602 ; N uni2349 ; G 2308
+U 9034 ; WX 602 ; N uni234A ; G 2309
+U 9035 ; WX 602 ; N uni234B ; G 2310
+U 9036 ; WX 602 ; N uni234C ; G 2311
+U 9037 ; WX 602 ; N uni234D ; G 2312
+U 9038 ; WX 602 ; N uni234E ; G 2313
+U 9039 ; WX 602 ; N uni234F ; G 2314
+U 9040 ; WX 602 ; N uni2350 ; G 2315
+U 9041 ; WX 602 ; N uni2351 ; G 2316
+U 9042 ; WX 602 ; N uni2352 ; G 2317
+U 9043 ; WX 602 ; N uni2353 ; G 2318
+U 9044 ; WX 602 ; N uni2354 ; G 2319
+U 9045 ; WX 602 ; N uni2355 ; G 2320
+U 9046 ; WX 602 ; N uni2356 ; G 2321
+U 9047 ; WX 602 ; N uni2357 ; G 2322
+U 9048 ; WX 602 ; N uni2358 ; G 2323
+U 9049 ; WX 602 ; N uni2359 ; G 2324
+U 9050 ; WX 602 ; N uni235A ; G 2325
+U 9051 ; WX 602 ; N uni235B ; G 2326
+U 9052 ; WX 602 ; N uni235C ; G 2327
+U 9053 ; WX 602 ; N uni235D ; G 2328
+U 9054 ; WX 602 ; N uni235E ; G 2329
+U 9055 ; WX 602 ; N uni235F ; G 2330
+U 9056 ; WX 602 ; N uni2360 ; G 2331
+U 9057 ; WX 602 ; N uni2361 ; G 2332
+U 9058 ; WX 602 ; N uni2362 ; G 2333
+U 9059 ; WX 602 ; N uni2363 ; G 2334
+U 9060 ; WX 602 ; N uni2364 ; G 2335
+U 9061 ; WX 602 ; N uni2365 ; G 2336
+U 9062 ; WX 602 ; N uni2366 ; G 2337
+U 9063 ; WX 602 ; N uni2367 ; G 2338
+U 9064 ; WX 602 ; N uni2368 ; G 2339
+U 9065 ; WX 602 ; N uni2369 ; G 2340
+U 9066 ; WX 602 ; N uni236A ; G 2341
+U 9067 ; WX 602 ; N uni236B ; G 2342
+U 9068 ; WX 602 ; N uni236C ; G 2343
+U 9069 ; WX 602 ; N uni236D ; G 2344
+U 9070 ; WX 602 ; N uni236E ; G 2345
+U 9071 ; WX 602 ; N uni236F ; G 2346
+U 9072 ; WX 602 ; N uni2370 ; G 2347
+U 9073 ; WX 602 ; N uni2371 ; G 2348
+U 9074 ; WX 602 ; N uni2372 ; G 2349
+U 9075 ; WX 602 ; N uni2373 ; G 2350
+U 9076 ; WX 602 ; N uni2374 ; G 2351
+U 9077 ; WX 602 ; N uni2375 ; G 2352
+U 9078 ; WX 602 ; N uni2376 ; G 2353
+U 9079 ; WX 602 ; N uni2377 ; G 2354
+U 9080 ; WX 602 ; N uni2378 ; G 2355
+U 9081 ; WX 602 ; N uni2379 ; G 2356
+U 9082 ; WX 602 ; N uni237A ; G 2357
+U 9085 ; WX 602 ; N uni237D ; G 2358
+U 9088 ; WX 602 ; N uni2380 ; G 2359
+U 9089 ; WX 602 ; N uni2381 ; G 2360
+U 9090 ; WX 602 ; N uni2382 ; G 2361
+U 9091 ; WX 602 ; N uni2383 ; G 2362
+U 9096 ; WX 602 ; N uni2388 ; G 2363
+U 9097 ; WX 602 ; N uni2389 ; G 2364
+U 9098 ; WX 602 ; N uni238A ; G 2365
+U 9099 ; WX 602 ; N uni238B ; G 2366
+U 9109 ; WX 602 ; N uni2395 ; G 2367
+U 9115 ; WX 602 ; N uni239B ; G 2368
+U 9116 ; WX 602 ; N uni239C ; G 2369
+U 9117 ; WX 602 ; N uni239D ; G 2370
+U 9118 ; WX 602 ; N uni239E ; G 2371
+U 9119 ; WX 602 ; N uni239F ; G 2372
+U 9120 ; WX 602 ; N uni23A0 ; G 2373
+U 9121 ; WX 602 ; N uni23A1 ; G 2374
+U 9122 ; WX 602 ; N uni23A2 ; G 2375
+U 9123 ; WX 602 ; N uni23A3 ; G 2376
+U 9124 ; WX 602 ; N uni23A4 ; G 2377
+U 9125 ; WX 602 ; N uni23A5 ; G 2378
+U 9126 ; WX 602 ; N uni23A6 ; G 2379
+U 9127 ; WX 602 ; N uni23A7 ; G 2380
+U 9128 ; WX 602 ; N uni23A8 ; G 2381
+U 9129 ; WX 602 ; N uni23A9 ; G 2382
+U 9130 ; WX 602 ; N uni23AA ; G 2383
+U 9131 ; WX 602 ; N uni23AB ; G 2384
+U 9132 ; WX 602 ; N uni23AC ; G 2385
+U 9133 ; WX 602 ; N uni23AD ; G 2386
+U 9134 ; WX 602 ; N uni23AE ; G 2387
+U 9166 ; WX 602 ; N uni23CE ; G 2388
+U 9167 ; WX 602 ; N uni23CF ; G 2389
+U 9251 ; WX 602 ; N uni2423 ; G 2390
+U 9472 ; WX 602 ; N SF100000 ; G 2391
+U 9473 ; WX 602 ; N uni2501 ; G 2392
+U 9474 ; WX 602 ; N SF110000 ; G 2393
+U 9475 ; WX 602 ; N uni2503 ; G 2394
+U 9476 ; WX 602 ; N uni2504 ; G 2395
+U 9477 ; WX 602 ; N uni2505 ; G 2396
+U 9478 ; WX 602 ; N uni2506 ; G 2397
+U 9479 ; WX 602 ; N uni2507 ; G 2398
+U 9480 ; WX 602 ; N uni2508 ; G 2399
+U 9481 ; WX 602 ; N uni2509 ; G 2400
+U 9482 ; WX 602 ; N uni250A ; G 2401
+U 9483 ; WX 602 ; N uni250B ; G 2402
+U 9484 ; WX 602 ; N SF010000 ; G 2403
+U 9485 ; WX 602 ; N uni250D ; G 2404
+U 9486 ; WX 602 ; N uni250E ; G 2405
+U 9487 ; WX 602 ; N uni250F ; G 2406
+U 9488 ; WX 602 ; N SF030000 ; G 2407
+U 9489 ; WX 602 ; N uni2511 ; G 2408
+U 9490 ; WX 602 ; N uni2512 ; G 2409
+U 9491 ; WX 602 ; N uni2513 ; G 2410
+U 9492 ; WX 602 ; N SF020000 ; G 2411
+U 9493 ; WX 602 ; N uni2515 ; G 2412
+U 9494 ; WX 602 ; N uni2516 ; G 2413
+U 9495 ; WX 602 ; N uni2517 ; G 2414
+U 9496 ; WX 602 ; N SF040000 ; G 2415
+U 9497 ; WX 602 ; N uni2519 ; G 2416
+U 9498 ; WX 602 ; N uni251A ; G 2417
+U 9499 ; WX 602 ; N uni251B ; G 2418
+U 9500 ; WX 602 ; N SF080000 ; G 2419
+U 9501 ; WX 602 ; N uni251D ; G 2420
+U 9502 ; WX 602 ; N uni251E ; G 2421
+U 9503 ; WX 602 ; N uni251F ; G 2422
+U 9504 ; WX 602 ; N uni2520 ; G 2423
+U 9505 ; WX 602 ; N uni2521 ; G 2424
+U 9506 ; WX 602 ; N uni2522 ; G 2425
+U 9507 ; WX 602 ; N uni2523 ; G 2426
+U 9508 ; WX 602 ; N SF090000 ; G 2427
+U 9509 ; WX 602 ; N uni2525 ; G 2428
+U 9510 ; WX 602 ; N uni2526 ; G 2429
+U 9511 ; WX 602 ; N uni2527 ; G 2430
+U 9512 ; WX 602 ; N uni2528 ; G 2431
+U 9513 ; WX 602 ; N uni2529 ; G 2432
+U 9514 ; WX 602 ; N uni252A ; G 2433
+U 9515 ; WX 602 ; N uni252B ; G 2434
+U 9516 ; WX 602 ; N SF060000 ; G 2435
+U 9517 ; WX 602 ; N uni252D ; G 2436
+U 9518 ; WX 602 ; N uni252E ; G 2437
+U 9519 ; WX 602 ; N uni252F ; G 2438
+U 9520 ; WX 602 ; N uni2530 ; G 2439
+U 9521 ; WX 602 ; N uni2531 ; G 2440
+U 9522 ; WX 602 ; N uni2532 ; G 2441
+U 9523 ; WX 602 ; N uni2533 ; G 2442
+U 9524 ; WX 602 ; N SF070000 ; G 2443
+U 9525 ; WX 602 ; N uni2535 ; G 2444
+U 9526 ; WX 602 ; N uni2536 ; G 2445
+U 9527 ; WX 602 ; N uni2537 ; G 2446
+U 9528 ; WX 602 ; N uni2538 ; G 2447
+U 9529 ; WX 602 ; N uni2539 ; G 2448
+U 9530 ; WX 602 ; N uni253A ; G 2449
+U 9531 ; WX 602 ; N uni253B ; G 2450
+U 9532 ; WX 602 ; N SF050000 ; G 2451
+U 9533 ; WX 602 ; N uni253D ; G 2452
+U 9534 ; WX 602 ; N uni253E ; G 2453
+U 9535 ; WX 602 ; N uni253F ; G 2454
+U 9536 ; WX 602 ; N uni2540 ; G 2455
+U 9537 ; WX 602 ; N uni2541 ; G 2456
+U 9538 ; WX 602 ; N uni2542 ; G 2457
+U 9539 ; WX 602 ; N uni2543 ; G 2458
+U 9540 ; WX 602 ; N uni2544 ; G 2459
+U 9541 ; WX 602 ; N uni2545 ; G 2460
+U 9542 ; WX 602 ; N uni2546 ; G 2461
+U 9543 ; WX 602 ; N uni2547 ; G 2462
+U 9544 ; WX 602 ; N uni2548 ; G 2463
+U 9545 ; WX 602 ; N uni2549 ; G 2464
+U 9546 ; WX 602 ; N uni254A ; G 2465
+U 9547 ; WX 602 ; N uni254B ; G 2466
+U 9548 ; WX 602 ; N uni254C ; G 2467
+U 9549 ; WX 602 ; N uni254D ; G 2468
+U 9550 ; WX 602 ; N uni254E ; G 2469
+U 9551 ; WX 602 ; N uni254F ; G 2470
+U 9552 ; WX 602 ; N SF430000 ; G 2471
+U 9553 ; WX 602 ; N SF240000 ; G 2472
+U 9554 ; WX 602 ; N SF510000 ; G 2473
+U 9555 ; WX 602 ; N SF520000 ; G 2474
+U 9556 ; WX 602 ; N SF390000 ; G 2475
+U 9557 ; WX 602 ; N SF220000 ; G 2476
+U 9558 ; WX 602 ; N SF210000 ; G 2477
+U 9559 ; WX 602 ; N SF250000 ; G 2478
+U 9560 ; WX 602 ; N SF500000 ; G 2479
+U 9561 ; WX 602 ; N SF490000 ; G 2480
+U 9562 ; WX 602 ; N SF380000 ; G 2481
+U 9563 ; WX 602 ; N SF280000 ; G 2482
+U 9564 ; WX 602 ; N SF270000 ; G 2483
+U 9565 ; WX 602 ; N SF260000 ; G 2484
+U 9566 ; WX 602 ; N SF360000 ; G 2485
+U 9567 ; WX 602 ; N SF370000 ; G 2486
+U 9568 ; WX 602 ; N SF420000 ; G 2487
+U 9569 ; WX 602 ; N SF190000 ; G 2488
+U 9570 ; WX 602 ; N SF200000 ; G 2489
+U 9571 ; WX 602 ; N SF230000 ; G 2490
+U 9572 ; WX 602 ; N SF470000 ; G 2491
+U 9573 ; WX 602 ; N SF480000 ; G 2492
+U 9574 ; WX 602 ; N SF410000 ; G 2493
+U 9575 ; WX 602 ; N SF450000 ; G 2494
+U 9576 ; WX 602 ; N SF460000 ; G 2495
+U 9577 ; WX 602 ; N SF400000 ; G 2496
+U 9578 ; WX 602 ; N SF540000 ; G 2497
+U 9579 ; WX 602 ; N SF530000 ; G 2498
+U 9580 ; WX 602 ; N SF440000 ; G 2499
+U 9581 ; WX 602 ; N uni256D ; G 2500
+U 9582 ; WX 602 ; N uni256E ; G 2501
+U 9583 ; WX 602 ; N uni256F ; G 2502
+U 9584 ; WX 602 ; N uni2570 ; G 2503
+U 9585 ; WX 602 ; N uni2571 ; G 2504
+U 9586 ; WX 602 ; N uni2572 ; G 2505
+U 9587 ; WX 602 ; N uni2573 ; G 2506
+U 9588 ; WX 602 ; N uni2574 ; G 2507
+U 9589 ; WX 602 ; N uni2575 ; G 2508
+U 9590 ; WX 602 ; N uni2576 ; G 2509
+U 9591 ; WX 602 ; N uni2577 ; G 2510
+U 9592 ; WX 602 ; N uni2578 ; G 2511
+U 9593 ; WX 602 ; N uni2579 ; G 2512
+U 9594 ; WX 602 ; N uni257A ; G 2513
+U 9595 ; WX 602 ; N uni257B ; G 2514
+U 9596 ; WX 602 ; N uni257C ; G 2515
+U 9597 ; WX 602 ; N uni257D ; G 2516
+U 9598 ; WX 602 ; N uni257E ; G 2517
+U 9599 ; WX 602 ; N uni257F ; G 2518
+U 9600 ; WX 602 ; N upblock ; G 2519
+U 9601 ; WX 602 ; N uni2581 ; G 2520
+U 9602 ; WX 602 ; N uni2582 ; G 2521
+U 9603 ; WX 602 ; N uni2583 ; G 2522
+U 9604 ; WX 602 ; N dnblock ; G 2523
+U 9605 ; WX 602 ; N uni2585 ; G 2524
+U 9606 ; WX 602 ; N uni2586 ; G 2525
+U 9607 ; WX 602 ; N uni2587 ; G 2526
+U 9608 ; WX 602 ; N block ; G 2527
+U 9609 ; WX 602 ; N uni2589 ; G 2528
+U 9610 ; WX 602 ; N uni258A ; G 2529
+U 9611 ; WX 602 ; N uni258B ; G 2530
+U 9612 ; WX 602 ; N lfblock ; G 2531
+U 9613 ; WX 602 ; N uni258D ; G 2532
+U 9614 ; WX 602 ; N uni258E ; G 2533
+U 9615 ; WX 602 ; N uni258F ; G 2534
+U 9616 ; WX 602 ; N rtblock ; G 2535
+U 9617 ; WX 602 ; N ltshade ; G 2536
+U 9618 ; WX 602 ; N shade ; G 2537
+U 9619 ; WX 602 ; N dkshade ; G 2538
+U 9620 ; WX 602 ; N uni2594 ; G 2539
+U 9621 ; WX 602 ; N uni2595 ; G 2540
+U 9622 ; WX 602 ; N uni2596 ; G 2541
+U 9623 ; WX 602 ; N uni2597 ; G 2542
+U 9624 ; WX 602 ; N uni2598 ; G 2543
+U 9625 ; WX 602 ; N uni2599 ; G 2544
+U 9626 ; WX 602 ; N uni259A ; G 2545
+U 9627 ; WX 602 ; N uni259B ; G 2546
+U 9628 ; WX 602 ; N uni259C ; G 2547
+U 9629 ; WX 602 ; N uni259D ; G 2548
+U 9630 ; WX 602 ; N uni259E ; G 2549
+U 9631 ; WX 602 ; N uni259F ; G 2550
+U 9632 ; WX 602 ; N filledbox ; G 2551
+U 9633 ; WX 602 ; N H22073 ; G 2552
+U 9634 ; WX 602 ; N uni25A2 ; G 2553
+U 9635 ; WX 602 ; N uni25A3 ; G 2554
+U 9636 ; WX 602 ; N uni25A4 ; G 2555
+U 9637 ; WX 602 ; N uni25A5 ; G 2556
+U 9638 ; WX 602 ; N uni25A6 ; G 2557
+U 9639 ; WX 602 ; N uni25A7 ; G 2558
+U 9640 ; WX 602 ; N uni25A8 ; G 2559
+U 9641 ; WX 602 ; N uni25A9 ; G 2560
+U 9642 ; WX 602 ; N H18543 ; G 2561
+U 9643 ; WX 602 ; N H18551 ; G 2562
+U 9644 ; WX 602 ; N filledrect ; G 2563
+U 9645 ; WX 602 ; N uni25AD ; G 2564
+U 9646 ; WX 602 ; N uni25AE ; G 2565
+U 9647 ; WX 602 ; N uni25AF ; G 2566
+U 9648 ; WX 602 ; N uni25B0 ; G 2567
+U 9649 ; WX 602 ; N uni25B1 ; G 2568
+U 9650 ; WX 602 ; N triagup ; G 2569
+U 9651 ; WX 602 ; N uni25B3 ; G 2570
+U 9652 ; WX 602 ; N uni25B4 ; G 2571
+U 9653 ; WX 602 ; N uni25B5 ; G 2572
+U 9654 ; WX 602 ; N uni25B6 ; G 2573
+U 9655 ; WX 602 ; N uni25B7 ; G 2574
+U 9656 ; WX 602 ; N uni25B8 ; G 2575
+U 9657 ; WX 602 ; N uni25B9 ; G 2576
+U 9658 ; WX 602 ; N triagrt ; G 2577
+U 9659 ; WX 602 ; N uni25BB ; G 2578
+U 9660 ; WX 602 ; N triagdn ; G 2579
+U 9661 ; WX 602 ; N uni25BD ; G 2580
+U 9662 ; WX 602 ; N uni25BE ; G 2581
+U 9663 ; WX 602 ; N uni25BF ; G 2582
+U 9664 ; WX 602 ; N uni25C0 ; G 2583
+U 9665 ; WX 602 ; N uni25C1 ; G 2584
+U 9666 ; WX 602 ; N uni25C2 ; G 2585
+U 9667 ; WX 602 ; N uni25C3 ; G 2586
+U 9668 ; WX 602 ; N triaglf ; G 2587
+U 9669 ; WX 602 ; N uni25C5 ; G 2588
+U 9670 ; WX 602 ; N uni25C6 ; G 2589
+U 9671 ; WX 602 ; N uni25C7 ; G 2590
+U 9672 ; WX 602 ; N uni25C8 ; G 2591
+U 9673 ; WX 602 ; N uni25C9 ; G 2592
+U 9674 ; WX 602 ; N lozenge ; G 2593
+U 9675 ; WX 602 ; N circle ; G 2594
+U 9676 ; WX 602 ; N uni25CC ; G 2595
+U 9677 ; WX 602 ; N uni25CD ; G 2596
+U 9678 ; WX 602 ; N uni25CE ; G 2597
+U 9679 ; WX 602 ; N H18533 ; G 2598
+U 9680 ; WX 602 ; N uni25D0 ; G 2599
+U 9681 ; WX 602 ; N uni25D1 ; G 2600
+U 9682 ; WX 602 ; N uni25D2 ; G 2601
+U 9683 ; WX 602 ; N uni25D3 ; G 2602
+U 9684 ; WX 602 ; N uni25D4 ; G 2603
+U 9685 ; WX 602 ; N uni25D5 ; G 2604
+U 9686 ; WX 602 ; N uni25D6 ; G 2605
+U 9687 ; WX 602 ; N uni25D7 ; G 2606
+U 9688 ; WX 602 ; N invbullet ; G 2607
+U 9689 ; WX 602 ; N invcircle ; G 2608
+U 9690 ; WX 602 ; N uni25DA ; G 2609
+U 9691 ; WX 602 ; N uni25DB ; G 2610
+U 9692 ; WX 602 ; N uni25DC ; G 2611
+U 9693 ; WX 602 ; N uni25DD ; G 2612
+U 9694 ; WX 602 ; N uni25DE ; G 2613
+U 9695 ; WX 602 ; N uni25DF ; G 2614
+U 9696 ; WX 602 ; N uni25E0 ; G 2615
+U 9697 ; WX 602 ; N uni25E1 ; G 2616
+U 9698 ; WX 602 ; N uni25E2 ; G 2617
+U 9699 ; WX 602 ; N uni25E3 ; G 2618
+U 9700 ; WX 602 ; N uni25E4 ; G 2619
+U 9701 ; WX 602 ; N uni25E5 ; G 2620
+U 9702 ; WX 602 ; N openbullet ; G 2621
+U 9703 ; WX 602 ; N uni25E7 ; G 2622
+U 9704 ; WX 602 ; N uni25E8 ; G 2623
+U 9705 ; WX 602 ; N uni25E9 ; G 2624
+U 9706 ; WX 602 ; N uni25EA ; G 2625
+U 9707 ; WX 602 ; N uni25EB ; G 2626
+U 9708 ; WX 602 ; N uni25EC ; G 2627
+U 9709 ; WX 602 ; N uni25ED ; G 2628
+U 9710 ; WX 602 ; N uni25EE ; G 2629
+U 9711 ; WX 602 ; N uni25EF ; G 2630
+U 9712 ; WX 602 ; N uni25F0 ; G 2631
+U 9713 ; WX 602 ; N uni25F1 ; G 2632
+U 9714 ; WX 602 ; N uni25F2 ; G 2633
+U 9715 ; WX 602 ; N uni25F3 ; G 2634
+U 9716 ; WX 602 ; N uni25F4 ; G 2635
+U 9717 ; WX 602 ; N uni25F5 ; G 2636
+U 9718 ; WX 602 ; N uni25F6 ; G 2637
+U 9719 ; WX 602 ; N uni25F7 ; G 2638
+U 9720 ; WX 602 ; N uni25F8 ; G 2639
+U 9721 ; WX 602 ; N uni25F9 ; G 2640
+U 9722 ; WX 602 ; N uni25FA ; G 2641
+U 9723 ; WX 602 ; N uni25FB ; G 2642
+U 9724 ; WX 602 ; N uni25FC ; G 2643
+U 9725 ; WX 602 ; N uni25FD ; G 2644
+U 9726 ; WX 602 ; N uni25FE ; G 2645
+U 9727 ; WX 602 ; N uni25FF ; G 2646
+U 9728 ; WX 602 ; N uni2600 ; G 2647
+U 9729 ; WX 602 ; N uni2601 ; G 2648
+U 9730 ; WX 602 ; N uni2602 ; G 2649
+U 9731 ; WX 602 ; N uni2603 ; G 2650
+U 9732 ; WX 602 ; N uni2604 ; G 2651
+U 9733 ; WX 602 ; N uni2605 ; G 2652
+U 9734 ; WX 602 ; N uni2606 ; G 2653
+U 9735 ; WX 602 ; N uni2607 ; G 2654
+U 9736 ; WX 602 ; N uni2608 ; G 2655
+U 9737 ; WX 602 ; N uni2609 ; G 2656
+U 9738 ; WX 602 ; N uni260A ; G 2657
+U 9739 ; WX 602 ; N uni260B ; G 2658
+U 9740 ; WX 602 ; N uni260C ; G 2659
+U 9741 ; WX 602 ; N uni260D ; G 2660
+U 9742 ; WX 602 ; N uni260E ; G 2661
+U 9743 ; WX 602 ; N uni260F ; G 2662
+U 9744 ; WX 602 ; N uni2610 ; G 2663
+U 9745 ; WX 602 ; N uni2611 ; G 2664
+U 9746 ; WX 602 ; N uni2612 ; G 2665
+U 9747 ; WX 602 ; N uni2613 ; G 2666
+U 9748 ; WX 602 ; N uni2614 ; G 2667
+U 9749 ; WX 602 ; N uni2615 ; G 2668
+U 9750 ; WX 602 ; N uni2616 ; G 2669
+U 9751 ; WX 602 ; N uni2617 ; G 2670
+U 9752 ; WX 602 ; N uni2618 ; G 2671
+U 9753 ; WX 602 ; N uni2619 ; G 2672
+U 9754 ; WX 602 ; N uni261A ; G 2673
+U 9755 ; WX 602 ; N uni261B ; G 2674
+U 9756 ; WX 602 ; N uni261C ; G 2675
+U 9757 ; WX 602 ; N uni261D ; G 2676
+U 9758 ; WX 602 ; N uni261E ; G 2677
+U 9759 ; WX 602 ; N uni261F ; G 2678
+U 9760 ; WX 602 ; N uni2620 ; G 2679
+U 9761 ; WX 602 ; N uni2621 ; G 2680
+U 9762 ; WX 602 ; N uni2622 ; G 2681
+U 9763 ; WX 602 ; N uni2623 ; G 2682
+U 9764 ; WX 602 ; N uni2624 ; G 2683
+U 9765 ; WX 602 ; N uni2625 ; G 2684
+U 9766 ; WX 602 ; N uni2626 ; G 2685
+U 9767 ; WX 602 ; N uni2627 ; G 2686
+U 9768 ; WX 602 ; N uni2628 ; G 2687
+U 9769 ; WX 602 ; N uni2629 ; G 2688
+U 9770 ; WX 602 ; N uni262A ; G 2689
+U 9771 ; WX 602 ; N uni262B ; G 2690
+U 9772 ; WX 602 ; N uni262C ; G 2691
+U 9773 ; WX 602 ; N uni262D ; G 2692
+U 9774 ; WX 602 ; N uni262E ; G 2693
+U 9775 ; WX 602 ; N uni262F ; G 2694
+U 9784 ; WX 602 ; N uni2638 ; G 2695
+U 9785 ; WX 602 ; N uni2639 ; G 2696
+U 9786 ; WX 602 ; N smileface ; G 2697
+U 9787 ; WX 602 ; N invsmileface ; G 2698
+U 9788 ; WX 602 ; N sun ; G 2699
+U 9789 ; WX 602 ; N uni263D ; G 2700
+U 9790 ; WX 602 ; N uni263E ; G 2701
+U 9791 ; WX 602 ; N uni263F ; G 2702
+U 9792 ; WX 602 ; N female ; G 2703
+U 9793 ; WX 602 ; N uni2641 ; G 2704
+U 9794 ; WX 602 ; N male ; G 2705
+U 9795 ; WX 602 ; N uni2643 ; G 2706
+U 9796 ; WX 602 ; N uni2644 ; G 2707
+U 9797 ; WX 602 ; N uni2645 ; G 2708
+U 9798 ; WX 602 ; N uni2646 ; G 2709
+U 9799 ; WX 602 ; N uni2647 ; G 2710
+U 9800 ; WX 602 ; N uni2648 ; G 2711
+U 9801 ; WX 602 ; N uni2649 ; G 2712
+U 9802 ; WX 602 ; N uni264A ; G 2713
+U 9803 ; WX 602 ; N uni264B ; G 2714
+U 9804 ; WX 602 ; N uni264C ; G 2715
+U 9805 ; WX 602 ; N uni264D ; G 2716
+U 9806 ; WX 602 ; N uni264E ; G 2717
+U 9807 ; WX 602 ; N uni264F ; G 2718
+U 9808 ; WX 602 ; N uni2650 ; G 2719
+U 9809 ; WX 602 ; N uni2651 ; G 2720
+U 9810 ; WX 602 ; N uni2652 ; G 2721
+U 9811 ; WX 602 ; N uni2653 ; G 2722
+U 9812 ; WX 602 ; N uni2654 ; G 2723
+U 9813 ; WX 602 ; N uni2655 ; G 2724
+U 9814 ; WX 602 ; N uni2656 ; G 2725
+U 9815 ; WX 602 ; N uni2657 ; G 2726
+U 9816 ; WX 602 ; N uni2658 ; G 2727
+U 9817 ; WX 602 ; N uni2659 ; G 2728
+U 9818 ; WX 602 ; N uni265A ; G 2729
+U 9819 ; WX 602 ; N uni265B ; G 2730
+U 9820 ; WX 602 ; N uni265C ; G 2731
+U 9821 ; WX 602 ; N uni265D ; G 2732
+U 9822 ; WX 602 ; N uni265E ; G 2733
+U 9823 ; WX 602 ; N uni265F ; G 2734
+U 9824 ; WX 602 ; N spade ; G 2735
+U 9825 ; WX 602 ; N uni2661 ; G 2736
+U 9826 ; WX 602 ; N uni2662 ; G 2737
+U 9827 ; WX 602 ; N club ; G 2738
+U 9828 ; WX 602 ; N uni2664 ; G 2739
+U 9829 ; WX 602 ; N heart ; G 2740
+U 9830 ; WX 602 ; N diamond ; G 2741
+U 9831 ; WX 602 ; N uni2667 ; G 2742
+U 9832 ; WX 602 ; N uni2668 ; G 2743
+U 9833 ; WX 602 ; N uni2669 ; G 2744
+U 9834 ; WX 602 ; N musicalnote ; G 2745
+U 9835 ; WX 602 ; N musicalnotedbl ; G 2746
+U 9836 ; WX 602 ; N uni266C ; G 2747
+U 9837 ; WX 602 ; N uni266D ; G 2748
+U 9838 ; WX 602 ; N uni266E ; G 2749
+U 9839 ; WX 602 ; N uni266F ; G 2750
+U 9840 ; WX 602 ; N uni2670 ; G 2751
+U 9841 ; WX 602 ; N uni2671 ; G 2752
+U 9842 ; WX 602 ; N uni2672 ; G 2753
+U 9843 ; WX 602 ; N uni2673 ; G 2754
+U 9844 ; WX 602 ; N uni2674 ; G 2755
+U 9845 ; WX 602 ; N uni2675 ; G 2756
+U 9846 ; WX 602 ; N uni2676 ; G 2757
+U 9847 ; WX 602 ; N uni2677 ; G 2758
+U 9848 ; WX 602 ; N uni2678 ; G 2759
+U 9849 ; WX 602 ; N uni2679 ; G 2760
+U 9850 ; WX 602 ; N uni267A ; G 2761
+U 9851 ; WX 602 ; N uni267B ; G 2762
+U 9852 ; WX 602 ; N uni267C ; G 2763
+U 9853 ; WX 602 ; N uni267D ; G 2764
+U 9854 ; WX 602 ; N uni267E ; G 2765
+U 9855 ; WX 602 ; N uni267F ; G 2766
+U 9856 ; WX 602 ; N uni2680 ; G 2767
+U 9857 ; WX 602 ; N uni2681 ; G 2768
+U 9858 ; WX 602 ; N uni2682 ; G 2769
+U 9859 ; WX 602 ; N uni2683 ; G 2770
+U 9860 ; WX 602 ; N uni2684 ; G 2771
+U 9861 ; WX 602 ; N uni2685 ; G 2772
+U 9862 ; WX 602 ; N uni2686 ; G 2773
+U 9863 ; WX 602 ; N uni2687 ; G 2774
+U 9864 ; WX 602 ; N uni2688 ; G 2775
+U 9865 ; WX 602 ; N uni2689 ; G 2776
+U 9866 ; WX 602 ; N uni268A ; G 2777
+U 9867 ; WX 602 ; N uni268B ; G 2778
+U 9872 ; WX 602 ; N uni2690 ; G 2779
+U 9873 ; WX 602 ; N uni2691 ; G 2780
+U 9874 ; WX 602 ; N uni2692 ; G 2781
+U 9875 ; WX 602 ; N uni2693 ; G 2782
+U 9876 ; WX 602 ; N uni2694 ; G 2783
+U 9877 ; WX 602 ; N uni2695 ; G 2784
+U 9878 ; WX 602 ; N uni2696 ; G 2785
+U 9879 ; WX 602 ; N uni2697 ; G 2786
+U 9880 ; WX 602 ; N uni2698 ; G 2787
+U 9881 ; WX 602 ; N uni2699 ; G 2788
+U 9882 ; WX 602 ; N uni269A ; G 2789
+U 9883 ; WX 602 ; N uni269B ; G 2790
+U 9884 ; WX 602 ; N uni269C ; G 2791
+U 9888 ; WX 602 ; N uni26A0 ; G 2792
+U 9889 ; WX 602 ; N uni26A1 ; G 2793
+U 9904 ; WX 602 ; N uni26B0 ; G 2794
+U 9905 ; WX 602 ; N uni26B1 ; G 2795
+U 9985 ; WX 602 ; N uni2701 ; G 2796
+U 9986 ; WX 602 ; N uni2702 ; G 2797
+U 9987 ; WX 602 ; N uni2703 ; G 2798
+U 9988 ; WX 602 ; N uni2704 ; G 2799
+U 9990 ; WX 602 ; N uni2706 ; G 2800
+U 9991 ; WX 602 ; N uni2707 ; G 2801
+U 9992 ; WX 602 ; N uni2708 ; G 2802
+U 9993 ; WX 602 ; N uni2709 ; G 2803
+U 9996 ; WX 602 ; N uni270C ; G 2804
+U 9997 ; WX 602 ; N uni270D ; G 2805
+U 9998 ; WX 602 ; N uni270E ; G 2806
+U 9999 ; WX 602 ; N uni270F ; G 2807
+U 10000 ; WX 602 ; N uni2710 ; G 2808
+U 10001 ; WX 602 ; N uni2711 ; G 2809
+U 10002 ; WX 602 ; N uni2712 ; G 2810
+U 10003 ; WX 602 ; N uni2713 ; G 2811
+U 10004 ; WX 602 ; N uni2714 ; G 2812
+U 10005 ; WX 602 ; N uni2715 ; G 2813
+U 10006 ; WX 602 ; N uni2716 ; G 2814
+U 10007 ; WX 602 ; N uni2717 ; G 2815
+U 10008 ; WX 602 ; N uni2718 ; G 2816
+U 10009 ; WX 602 ; N uni2719 ; G 2817
+U 10010 ; WX 602 ; N uni271A ; G 2818
+U 10011 ; WX 602 ; N uni271B ; G 2819
+U 10012 ; WX 602 ; N uni271C ; G 2820
+U 10013 ; WX 602 ; N uni271D ; G 2821
+U 10014 ; WX 602 ; N uni271E ; G 2822
+U 10015 ; WX 602 ; N uni271F ; G 2823
+U 10016 ; WX 602 ; N uni2720 ; G 2824
+U 10017 ; WX 602 ; N uni2721 ; G 2825
+U 10018 ; WX 602 ; N uni2722 ; G 2826
+U 10019 ; WX 602 ; N uni2723 ; G 2827
+U 10020 ; WX 602 ; N uni2724 ; G 2828
+U 10021 ; WX 602 ; N uni2725 ; G 2829
+U 10022 ; WX 602 ; N uni2726 ; G 2830
+U 10023 ; WX 602 ; N uni2727 ; G 2831
+U 10025 ; WX 602 ; N uni2729 ; G 2832
+U 10026 ; WX 602 ; N uni272A ; G 2833
+U 10027 ; WX 602 ; N uni272B ; G 2834
+U 10028 ; WX 602 ; N uni272C ; G 2835
+U 10029 ; WX 602 ; N uni272D ; G 2836
+U 10030 ; WX 602 ; N uni272E ; G 2837
+U 10031 ; WX 602 ; N uni272F ; G 2838
+U 10032 ; WX 602 ; N uni2730 ; G 2839
+U 10033 ; WX 602 ; N uni2731 ; G 2840
+U 10034 ; WX 602 ; N uni2732 ; G 2841
+U 10035 ; WX 602 ; N uni2733 ; G 2842
+U 10036 ; WX 602 ; N uni2734 ; G 2843
+U 10037 ; WX 602 ; N uni2735 ; G 2844
+U 10038 ; WX 602 ; N uni2736 ; G 2845
+U 10039 ; WX 602 ; N uni2737 ; G 2846
+U 10040 ; WX 602 ; N uni2738 ; G 2847
+U 10041 ; WX 602 ; N uni2739 ; G 2848
+U 10042 ; WX 602 ; N uni273A ; G 2849
+U 10043 ; WX 602 ; N uni273B ; G 2850
+U 10044 ; WX 602 ; N uni273C ; G 2851
+U 10045 ; WX 602 ; N uni273D ; G 2852
+U 10046 ; WX 602 ; N uni273E ; G 2853
+U 10047 ; WX 602 ; N uni273F ; G 2854
+U 10048 ; WX 602 ; N uni2740 ; G 2855
+U 10049 ; WX 602 ; N uni2741 ; G 2856
+U 10050 ; WX 602 ; N uni2742 ; G 2857
+U 10051 ; WX 602 ; N uni2743 ; G 2858
+U 10052 ; WX 602 ; N uni2744 ; G 2859
+U 10053 ; WX 602 ; N uni2745 ; G 2860
+U 10054 ; WX 602 ; N uni2746 ; G 2861
+U 10055 ; WX 602 ; N uni2747 ; G 2862
+U 10056 ; WX 602 ; N uni2748 ; G 2863
+U 10057 ; WX 602 ; N uni2749 ; G 2864
+U 10058 ; WX 602 ; N uni274A ; G 2865
+U 10059 ; WX 602 ; N uni274B ; G 2866
+U 10061 ; WX 602 ; N uni274D ; G 2867
+U 10063 ; WX 602 ; N uni274F ; G 2868
+U 10064 ; WX 602 ; N uni2750 ; G 2869
+U 10065 ; WX 602 ; N uni2751 ; G 2870
+U 10066 ; WX 602 ; N uni2752 ; G 2871
+U 10070 ; WX 602 ; N uni2756 ; G 2872
+U 10072 ; WX 602 ; N uni2758 ; G 2873
+U 10073 ; WX 602 ; N uni2759 ; G 2874
+U 10074 ; WX 602 ; N uni275A ; G 2875
+U 10075 ; WX 602 ; N uni275B ; G 2876
+U 10076 ; WX 602 ; N uni275C ; G 2877
+U 10077 ; WX 602 ; N uni275D ; G 2878
+U 10078 ; WX 602 ; N uni275E ; G 2879
+U 10081 ; WX 602 ; N uni2761 ; G 2880
+U 10082 ; WX 602 ; N uni2762 ; G 2881
+U 10083 ; WX 602 ; N uni2763 ; G 2882
+U 10084 ; WX 602 ; N uni2764 ; G 2883
+U 10085 ; WX 602 ; N uni2765 ; G 2884
+U 10086 ; WX 602 ; N uni2766 ; G 2885
+U 10087 ; WX 602 ; N uni2767 ; G 2886
+U 10088 ; WX 602 ; N uni2768 ; G 2887
+U 10089 ; WX 602 ; N uni2769 ; G 2888
+U 10090 ; WX 602 ; N uni276A ; G 2889
+U 10091 ; WX 602 ; N uni276B ; G 2890
+U 10092 ; WX 602 ; N uni276C ; G 2891
+U 10093 ; WX 602 ; N uni276D ; G 2892
+U 10094 ; WX 602 ; N uni276E ; G 2893
+U 10095 ; WX 602 ; N uni276F ; G 2894
+U 10096 ; WX 602 ; N uni2770 ; G 2895
+U 10097 ; WX 602 ; N uni2771 ; G 2896
+U 10098 ; WX 602 ; N uni2772 ; G 2897
+U 10099 ; WX 602 ; N uni2773 ; G 2898
+U 10100 ; WX 602 ; N uni2774 ; G 2899
+U 10101 ; WX 602 ; N uni2775 ; G 2900
+U 10132 ; WX 602 ; N uni2794 ; G 2901
+U 10136 ; WX 602 ; N uni2798 ; G 2902
+U 10137 ; WX 602 ; N uni2799 ; G 2903
+U 10138 ; WX 602 ; N uni279A ; G 2904
+U 10139 ; WX 602 ; N uni279B ; G 2905
+U 10140 ; WX 602 ; N uni279C ; G 2906
+U 10141 ; WX 602 ; N uni279D ; G 2907
+U 10142 ; WX 602 ; N uni279E ; G 2908
+U 10143 ; WX 602 ; N uni279F ; G 2909
+U 10144 ; WX 602 ; N uni27A0 ; G 2910
+U 10145 ; WX 602 ; N uni27A1 ; G 2911
+U 10146 ; WX 602 ; N uni27A2 ; G 2912
+U 10147 ; WX 602 ; N uni27A3 ; G 2913
+U 10148 ; WX 602 ; N uni27A4 ; G 2914
+U 10149 ; WX 602 ; N uni27A5 ; G 2915
+U 10150 ; WX 602 ; N uni27A6 ; G 2916
+U 10151 ; WX 602 ; N uni27A7 ; G 2917
+U 10152 ; WX 602 ; N uni27A8 ; G 2918
+U 10153 ; WX 602 ; N uni27A9 ; G 2919
+U 10154 ; WX 602 ; N uni27AA ; G 2920
+U 10155 ; WX 602 ; N uni27AB ; G 2921
+U 10156 ; WX 602 ; N uni27AC ; G 2922
+U 10157 ; WX 602 ; N uni27AD ; G 2923
+U 10158 ; WX 602 ; N uni27AE ; G 2924
+U 10159 ; WX 602 ; N uni27AF ; G 2925
+U 10161 ; WX 602 ; N uni27B1 ; G 2926
+U 10162 ; WX 602 ; N uni27B2 ; G 2927
+U 10163 ; WX 602 ; N uni27B3 ; G 2928
+U 10164 ; WX 602 ; N uni27B4 ; G 2929
+U 10165 ; WX 602 ; N uni27B5 ; G 2930
+U 10166 ; WX 602 ; N uni27B6 ; G 2931
+U 10167 ; WX 602 ; N uni27B7 ; G 2932
+U 10168 ; WX 602 ; N uni27B8 ; G 2933
+U 10169 ; WX 602 ; N uni27B9 ; G 2934
+U 10170 ; WX 602 ; N uni27BA ; G 2935
+U 10171 ; WX 602 ; N uni27BB ; G 2936
+U 10172 ; WX 602 ; N uni27BC ; G 2937
+U 10173 ; WX 602 ; N uni27BD ; G 2938
+U 10174 ; WX 602 ; N uni27BE ; G 2939
+U 10178 ; WX 602 ; N uni27C2 ; G 2940
+U 10181 ; WX 602 ; N uni27C5 ; G 2941
+U 10182 ; WX 602 ; N uni27C6 ; G 2942
+U 10204 ; WX 602 ; N uni27DC ; G 2943
+U 10208 ; WX 602 ; N uni27E0 ; G 2944
+U 10214 ; WX 602 ; N uni27E6 ; G 2945
+U 10215 ; WX 602 ; N uni27E7 ; G 2946
+U 10216 ; WX 602 ; N uni27E8 ; G 2947
+U 10217 ; WX 602 ; N uni27E9 ; G 2948
+U 10218 ; WX 602 ; N uni27EA ; G 2949
+U 10219 ; WX 602 ; N uni27EB ; G 2950
+U 10229 ; WX 602 ; N uni27F5 ; G 2951
+U 10230 ; WX 602 ; N uni27F6 ; G 2952
+U 10231 ; WX 602 ; N uni27F7 ; G 2953
+U 10631 ; WX 602 ; N uni2987 ; G 2954
+U 10632 ; WX 602 ; N uni2988 ; G 2955
+U 10647 ; WX 602 ; N uni2997 ; G 2956
+U 10648 ; WX 602 ; N uni2998 ; G 2957
+U 10731 ; WX 602 ; N uni29EB ; G 2958
+U 10746 ; WX 602 ; N uni29FA ; G 2959
+U 10747 ; WX 602 ; N uni29FB ; G 2960
+U 10752 ; WX 602 ; N uni2A00 ; G 2961
+U 10799 ; WX 602 ; N uni2A2F ; G 2962
+U 10858 ; WX 602 ; N uni2A6A ; G 2963
+U 10859 ; WX 602 ; N uni2A6B ; G 2964
+U 11013 ; WX 602 ; N uni2B05 ; G 2965
+U 11014 ; WX 602 ; N uni2B06 ; G 2966
+U 11015 ; WX 602 ; N uni2B07 ; G 2967
+U 11016 ; WX 602 ; N uni2B08 ; G 2968
+U 11017 ; WX 602 ; N uni2B09 ; G 2969
+U 11018 ; WX 602 ; N uni2B0A ; G 2970
+U 11019 ; WX 602 ; N uni2B0B ; G 2971
+U 11020 ; WX 602 ; N uni2B0C ; G 2972
+U 11021 ; WX 602 ; N uni2B0D ; G 2973
+U 11026 ; WX 602 ; N uni2B12 ; G 2974
+U 11027 ; WX 602 ; N uni2B13 ; G 2975
+U 11028 ; WX 602 ; N uni2B14 ; G 2976
+U 11029 ; WX 602 ; N uni2B15 ; G 2977
+U 11030 ; WX 602 ; N uni2B16 ; G 2978
+U 11031 ; WX 602 ; N uni2B17 ; G 2979
+U 11032 ; WX 602 ; N uni2B18 ; G 2980
+U 11033 ; WX 602 ; N uni2B19 ; G 2981
+U 11034 ; WX 602 ; N uni2B1A ; G 2982
+U 11364 ; WX 602 ; N uni2C64 ; G 2983
+U 11373 ; WX 602 ; N uni2C6D ; G 2984
+U 11374 ; WX 602 ; N uni2C6E ; G 2985
+U 11375 ; WX 602 ; N uni2C6F ; G 2986
+U 11376 ; WX 602 ; N uni2C70 ; G 2987
+U 11381 ; WX 602 ; N uni2C75 ; G 2988
+U 11382 ; WX 602 ; N uni2C76 ; G 2989
+U 11383 ; WX 602 ; N uni2C77 ; G 2990
+U 11385 ; WX 602 ; N uni2C79 ; G 2991
+U 11386 ; WX 602 ; N uni2C7A ; G 2992
+U 11388 ; WX 602 ; N uni2C7C ; G 2993
+U 11389 ; WX 602 ; N uni2C7D ; G 2994
+U 11390 ; WX 602 ; N uni2C7E ; G 2995
+U 11391 ; WX 602 ; N uni2C7F ; G 2996
+U 11800 ; WX 602 ; N uni2E18 ; G 2997
+U 11807 ; WX 602 ; N uni2E1F ; G 2998
+U 11810 ; WX 602 ; N uni2E22 ; G 2999
+U 11811 ; WX 602 ; N uni2E23 ; G 3000
+U 11812 ; WX 602 ; N uni2E24 ; G 3001
+U 11813 ; WX 602 ; N uni2E25 ; G 3002
+U 11822 ; WX 602 ; N uni2E2E ; G 3003
+U 42760 ; WX 602 ; N uniA708 ; G 3004
+U 42761 ; WX 602 ; N uniA709 ; G 3005
+U 42762 ; WX 602 ; N uniA70A ; G 3006
+U 42763 ; WX 602 ; N uniA70B ; G 3007
+U 42764 ; WX 602 ; N uniA70C ; G 3008
+U 42765 ; WX 602 ; N uniA70D ; G 3009
+U 42766 ; WX 602 ; N uniA70E ; G 3010
+U 42767 ; WX 602 ; N uniA70F ; G 3011
+U 42768 ; WX 602 ; N uniA710 ; G 3012
+U 42769 ; WX 602 ; N uniA711 ; G 3013
+U 42770 ; WX 602 ; N uniA712 ; G 3014
+U 42771 ; WX 602 ; N uniA713 ; G 3015
+U 42772 ; WX 602 ; N uniA714 ; G 3016
+U 42773 ; WX 602 ; N uniA715 ; G 3017
+U 42774 ; WX 602 ; N uniA716 ; G 3018
+U 42779 ; WX 602 ; N uniA71B ; G 3019
+U 42780 ; WX 602 ; N uniA71C ; G 3020
+U 42781 ; WX 602 ; N uniA71D ; G 3021
+U 42782 ; WX 602 ; N uniA71E ; G 3022
+U 42783 ; WX 602 ; N uniA71F ; G 3023
+U 42786 ; WX 602 ; N uniA722 ; G 3024
+U 42787 ; WX 602 ; N uniA723 ; G 3025
+U 42788 ; WX 602 ; N uniA724 ; G 3026
+U 42789 ; WX 602 ; N uniA725 ; G 3027
+U 42790 ; WX 602 ; N uniA726 ; G 3028
+U 42791 ; WX 602 ; N uniA727 ; G 3029
+U 42889 ; WX 602 ; N uniA789 ; G 3030
+U 42890 ; WX 602 ; N uniA78A ; G 3031
+U 42891 ; WX 602 ; N uniA78B ; G 3032
+U 42892 ; WX 602 ; N uniA78C ; G 3033
+U 42893 ; WX 602 ; N uniA78D ; G 3034
+U 42894 ; WX 602 ; N uniA78E ; G 3035
+U 42896 ; WX 602 ; N uniA790 ; G 3036
+U 42897 ; WX 602 ; N uniA791 ; G 3037
+U 42922 ; WX 602 ; N uniA7AA ; G 3038
+U 43000 ; WX 602 ; N uniA7F8 ; G 3039
+U 43001 ; WX 602 ; N uniA7F9 ; G 3040
+U 63173 ; WX 602 ; N uniF6C5 ; G 3041
+U 64257 ; WX 602 ; N fi ; G 3042
+U 64258 ; WX 602 ; N fl ; G 3043
+U 64338 ; WX 602 ; N uniFB52 ; G 3044
+U 64339 ; WX 602 ; N uniFB53 ; G 3045
+U 64340 ; WX 602 ; N uniFB54 ; G 3046
+U 64341 ; WX 602 ; N uniFB55 ; G 3047
+U 64342 ; WX 602 ; N uniFB56 ; G 3048
+U 64343 ; WX 602 ; N uniFB57 ; G 3049
+U 64344 ; WX 602 ; N uniFB58 ; G 3050
+U 64345 ; WX 602 ; N uniFB59 ; G 3051
+U 64346 ; WX 602 ; N uniFB5A ; G 3052
+U 64347 ; WX 602 ; N uniFB5B ; G 3053
+U 64348 ; WX 602 ; N uniFB5C ; G 3054
+U 64349 ; WX 602 ; N uniFB5D ; G 3055
+U 64350 ; WX 602 ; N uniFB5E ; G 3056
+U 64351 ; WX 602 ; N uniFB5F ; G 3057
+U 64352 ; WX 602 ; N uniFB60 ; G 3058
+U 64353 ; WX 602 ; N uniFB61 ; G 3059
+U 64354 ; WX 602 ; N uniFB62 ; G 3060
+U 64355 ; WX 602 ; N uniFB63 ; G 3061
+U 64356 ; WX 602 ; N uniFB64 ; G 3062
+U 64357 ; WX 602 ; N uniFB65 ; G 3063
+U 64358 ; WX 602 ; N uniFB66 ; G 3064
+U 64359 ; WX 602 ; N uniFB67 ; G 3065
+U 64360 ; WX 602 ; N uniFB68 ; G 3066
+U 64361 ; WX 602 ; N uniFB69 ; G 3067
+U 64362 ; WX 602 ; N uniFB6A ; G 3068
+U 64363 ; WX 602 ; N uniFB6B ; G 3069
+U 64364 ; WX 602 ; N uniFB6C ; G 3070
+U 64365 ; WX 602 ; N uniFB6D ; G 3071
+U 64366 ; WX 602 ; N uniFB6E ; G 3072
+U 64367 ; WX 602 ; N uniFB6F ; G 3073
+U 64368 ; WX 602 ; N uniFB70 ; G 3074
+U 64369 ; WX 602 ; N uniFB71 ; G 3075
+U 64370 ; WX 602 ; N uniFB72 ; G 3076
+U 64371 ; WX 602 ; N uniFB73 ; G 3077
+U 64372 ; WX 602 ; N uniFB74 ; G 3078
+U 64373 ; WX 602 ; N uniFB75 ; G 3079
+U 64374 ; WX 602 ; N uniFB76 ; G 3080
+U 64375 ; WX 602 ; N uniFB77 ; G 3081
+U 64376 ; WX 602 ; N uniFB78 ; G 3082
+U 64377 ; WX 602 ; N uniFB79 ; G 3083
+U 64378 ; WX 602 ; N uniFB7A ; G 3084
+U 64379 ; WX 602 ; N uniFB7B ; G 3085
+U 64380 ; WX 602 ; N uniFB7C ; G 3086
+U 64381 ; WX 602 ; N uniFB7D ; G 3087
+U 64382 ; WX 602 ; N uniFB7E ; G 3088
+U 64383 ; WX 602 ; N uniFB7F ; G 3089
+U 64384 ; WX 602 ; N uniFB80 ; G 3090
+U 64385 ; WX 602 ; N uniFB81 ; G 3091
+U 64394 ; WX 602 ; N uniFB8A ; G 3092
+U 64395 ; WX 602 ; N uniFB8B ; G 3093
+U 64396 ; WX 602 ; N uniFB8C ; G 3094
+U 64397 ; WX 602 ; N uniFB8D ; G 3095
+U 64398 ; WX 602 ; N uniFB8E ; G 3096
+U 64399 ; WX 602 ; N uniFB8F ; G 3097
+U 64400 ; WX 602 ; N uniFB90 ; G 3098
+U 64401 ; WX 602 ; N uniFB91 ; G 3099
+U 64402 ; WX 602 ; N uniFB92 ; G 3100
+U 64403 ; WX 602 ; N uniFB93 ; G 3101
+U 64404 ; WX 602 ; N uniFB94 ; G 3102
+U 64405 ; WX 602 ; N uniFB95 ; G 3103
+U 64414 ; WX 602 ; N uniFB9E ; G 3104
+U 64415 ; WX 602 ; N uniFB9F ; G 3105
+U 64426 ; WX 602 ; N uniFBAA ; G 3106
+U 64427 ; WX 602 ; N uniFBAB ; G 3107
+U 64428 ; WX 602 ; N uniFBAC ; G 3108
+U 64429 ; WX 602 ; N uniFBAD ; G 3109
+U 64488 ; WX 602 ; N uniFBE8 ; G 3110
+U 64489 ; WX 602 ; N uniFBE9 ; G 3111
+U 64508 ; WX 602 ; N uniFBFC ; G 3112
+U 64509 ; WX 602 ; N uniFBFD ; G 3113
+U 64510 ; WX 602 ; N uniFBFE ; G 3114
+U 64511 ; WX 602 ; N uniFBFF ; G 3115
+U 65136 ; WX 602 ; N uniFE70 ; G 3116
+U 65137 ; WX 602 ; N uniFE71 ; G 3117
+U 65138 ; WX 602 ; N uniFE72 ; G 3118
+U 65139 ; WX 602 ; N uniFE73 ; G 3119
+U 65140 ; WX 602 ; N uniFE74 ; G 3120
+U 65142 ; WX 602 ; N uniFE76 ; G 3121
+U 65143 ; WX 602 ; N uniFE77 ; G 3122
+U 65144 ; WX 602 ; N uniFE78 ; G 3123
+U 65145 ; WX 602 ; N uniFE79 ; G 3124
+U 65146 ; WX 602 ; N uniFE7A ; G 3125
+U 65147 ; WX 602 ; N uniFE7B ; G 3126
+U 65148 ; WX 602 ; N uniFE7C ; G 3127
+U 65149 ; WX 602 ; N uniFE7D ; G 3128
+U 65150 ; WX 602 ; N uniFE7E ; G 3129
+U 65151 ; WX 602 ; N uniFE7F ; G 3130
+U 65152 ; WX 602 ; N uniFE80 ; G 3131
+U 65153 ; WX 602 ; N uniFE81 ; G 3132
+U 65154 ; WX 602 ; N uniFE82 ; G 3133
+U 65155 ; WX 602 ; N uniFE83 ; G 3134
+U 65156 ; WX 602 ; N uniFE84 ; G 3135
+U 65157 ; WX 602 ; N uniFE85 ; G 3136
+U 65158 ; WX 602 ; N uniFE86 ; G 3137
+U 65159 ; WX 602 ; N uniFE87 ; G 3138
+U 65160 ; WX 602 ; N uniFE88 ; G 3139
+U 65161 ; WX 602 ; N uniFE89 ; G 3140
+U 65162 ; WX 602 ; N uniFE8A ; G 3141
+U 65163 ; WX 602 ; N uniFE8B ; G 3142
+U 65164 ; WX 602 ; N uniFE8C ; G 3143
+U 65165 ; WX 602 ; N uniFE8D ; G 3144
+U 65166 ; WX 602 ; N uniFE8E ; G 3145
+U 65167 ; WX 602 ; N uniFE8F ; G 3146
+U 65168 ; WX 602 ; N uniFE90 ; G 3147
+U 65169 ; WX 602 ; N uniFE91 ; G 3148
+U 65170 ; WX 602 ; N uniFE92 ; G 3149
+U 65171 ; WX 602 ; N uniFE93 ; G 3150
+U 65172 ; WX 602 ; N uniFE94 ; G 3151
+U 65173 ; WX 602 ; N uniFE95 ; G 3152
+U 65174 ; WX 602 ; N uniFE96 ; G 3153
+U 65175 ; WX 602 ; N uniFE97 ; G 3154
+U 65176 ; WX 602 ; N uniFE98 ; G 3155
+U 65177 ; WX 602 ; N uniFE99 ; G 3156
+U 65178 ; WX 602 ; N uniFE9A ; G 3157
+U 65179 ; WX 602 ; N uniFE9B ; G 3158
+U 65180 ; WX 602 ; N uniFE9C ; G 3159
+U 65181 ; WX 602 ; N uniFE9D ; G 3160
+U 65182 ; WX 602 ; N uniFE9E ; G 3161
+U 65183 ; WX 602 ; N uniFE9F ; G 3162
+U 65184 ; WX 602 ; N uniFEA0 ; G 3163
+U 65185 ; WX 602 ; N uniFEA1 ; G 3164
+U 65186 ; WX 602 ; N uniFEA2 ; G 3165
+U 65187 ; WX 602 ; N uniFEA3 ; G 3166
+U 65188 ; WX 602 ; N uniFEA4 ; G 3167
+U 65189 ; WX 602 ; N uniFEA5 ; G 3168
+U 65190 ; WX 602 ; N uniFEA6 ; G 3169
+U 65191 ; WX 602 ; N uniFEA7 ; G 3170
+U 65192 ; WX 602 ; N uniFEA8 ; G 3171
+U 65193 ; WX 602 ; N uniFEA9 ; G 3172
+U 65194 ; WX 602 ; N uniFEAA ; G 3173
+U 65195 ; WX 602 ; N uniFEAB ; G 3174
+U 65196 ; WX 602 ; N uniFEAC ; G 3175
+U 65197 ; WX 602 ; N uniFEAD ; G 3176
+U 65198 ; WX 602 ; N uniFEAE ; G 3177
+U 65199 ; WX 602 ; N uniFEAF ; G 3178
+U 65200 ; WX 602 ; N uniFEB0 ; G 3179
+U 65201 ; WX 602 ; N uniFEB1 ; G 3180
+U 65202 ; WX 602 ; N uniFEB2 ; G 3181
+U 65203 ; WX 602 ; N uniFEB3 ; G 3182
+U 65204 ; WX 602 ; N uniFEB4 ; G 3183
+U 65205 ; WX 602 ; N uniFEB5 ; G 3184
+U 65206 ; WX 602 ; N uniFEB6 ; G 3185
+U 65207 ; WX 602 ; N uniFEB7 ; G 3186
+U 65208 ; WX 602 ; N uniFEB8 ; G 3187
+U 65209 ; WX 602 ; N uniFEB9 ; G 3188
+U 65210 ; WX 602 ; N uniFEBA ; G 3189
+U 65211 ; WX 602 ; N uniFEBB ; G 3190
+U 65212 ; WX 602 ; N uniFEBC ; G 3191
+U 65213 ; WX 602 ; N uniFEBD ; G 3192
+U 65214 ; WX 602 ; N uniFEBE ; G 3193
+U 65215 ; WX 602 ; N uniFEBF ; G 3194
+U 65216 ; WX 602 ; N uniFEC0 ; G 3195
+U 65217 ; WX 602 ; N uniFEC1 ; G 3196
+U 65218 ; WX 602 ; N uniFEC2 ; G 3197
+U 65219 ; WX 602 ; N uniFEC3 ; G 3198
+U 65220 ; WX 602 ; N uniFEC4 ; G 3199
+U 65221 ; WX 602 ; N uniFEC5 ; G 3200
+U 65222 ; WX 602 ; N uniFEC6 ; G 3201
+U 65223 ; WX 602 ; N uniFEC7 ; G 3202
+U 65224 ; WX 602 ; N uniFEC8 ; G 3203
+U 65225 ; WX 602 ; N uniFEC9 ; G 3204
+U 65226 ; WX 602 ; N uniFECA ; G 3205
+U 65227 ; WX 602 ; N uniFECB ; G 3206
+U 65228 ; WX 602 ; N uniFECC ; G 3207
+U 65229 ; WX 602 ; N uniFECD ; G 3208
+U 65230 ; WX 602 ; N uniFECE ; G 3209
+U 65231 ; WX 602 ; N uniFECF ; G 3210
+U 65232 ; WX 602 ; N uniFED0 ; G 3211
+U 65233 ; WX 602 ; N uniFED1 ; G 3212
+U 65234 ; WX 602 ; N uniFED2 ; G 3213
+U 65235 ; WX 602 ; N uniFED3 ; G 3214
+U 65236 ; WX 602 ; N uniFED4 ; G 3215
+U 65237 ; WX 602 ; N uniFED5 ; G 3216
+U 65238 ; WX 602 ; N uniFED6 ; G 3217
+U 65239 ; WX 602 ; N uniFED7 ; G 3218
+U 65240 ; WX 602 ; N uniFED8 ; G 3219
+U 65241 ; WX 602 ; N uniFED9 ; G 3220
+U 65242 ; WX 602 ; N uniFEDA ; G 3221
+U 65243 ; WX 602 ; N uniFEDB ; G 3222
+U 65244 ; WX 602 ; N uniFEDC ; G 3223
+U 65245 ; WX 602 ; N uniFEDD ; G 3224
+U 65246 ; WX 602 ; N uniFEDE ; G 3225
+U 65247 ; WX 602 ; N uniFEDF ; G 3226
+U 65248 ; WX 602 ; N uniFEE0 ; G 3227
+U 65249 ; WX 602 ; N uniFEE1 ; G 3228
+U 65250 ; WX 602 ; N uniFEE2 ; G 3229
+U 65251 ; WX 602 ; N uniFEE3 ; G 3230
+U 65252 ; WX 602 ; N uniFEE4 ; G 3231
+U 65253 ; WX 602 ; N uniFEE5 ; G 3232
+U 65254 ; WX 602 ; N uniFEE6 ; G 3233
+U 65255 ; WX 602 ; N uniFEE7 ; G 3234
+U 65256 ; WX 602 ; N uniFEE8 ; G 3235
+U 65257 ; WX 602 ; N uniFEE9 ; G 3236
+U 65258 ; WX 602 ; N uniFEEA ; G 3237
+U 65259 ; WX 602 ; N uniFEEB ; G 3238
+U 65260 ; WX 602 ; N uniFEEC ; G 3239
+U 65261 ; WX 602 ; N uniFEED ; G 3240
+U 65262 ; WX 602 ; N uniFEEE ; G 3241
+U 65263 ; WX 602 ; N uniFEEF ; G 3242
+U 65264 ; WX 602 ; N uniFEF0 ; G 3243
+U 65265 ; WX 602 ; N uniFEF1 ; G 3244
+U 65266 ; WX 602 ; N uniFEF2 ; G 3245
+U 65267 ; WX 602 ; N uniFEF3 ; G 3246
+U 65268 ; WX 602 ; N uniFEF4 ; G 3247
+U 65269 ; WX 602 ; N uniFEF5 ; G 3248
+U 65270 ; WX 602 ; N uniFEF6 ; G 3249
+U 65271 ; WX 602 ; N uniFEF7 ; G 3250
+U 65272 ; WX 602 ; N uniFEF8 ; G 3251
+U 65273 ; WX 602 ; N uniFEF9 ; G 3252
+U 65274 ; WX 602 ; N uniFEFA ; G 3253
+U 65275 ; WX 602 ; N uniFEFB ; G 3254
+U 65276 ; WX 602 ; N uniFEFC ; G 3255
+U 65279 ; WX 602 ; N uniFEFF ; G 3256
+U 65529 ; WX 602 ; N uniFFF9 ; G 3257
+U 65530 ; WX 602 ; N uniFFFA ; G 3258
+U 65531 ; WX 602 ; N uniFFFB ; G 3259
+U 65532 ; WX 602 ; N uniFFFC ; G 3260
+U 65533 ; WX 602 ; N uniFFFD ; G 3261
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ttf
new file mode 100644
index 0000000..3bb755f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ufm
new file mode 100644
index 0000000..7420dab
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Bold.ufm
@@ -0,0 +1,4013 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Serif
+FontSubfamily Bold
+UniqueID DejaVu Serif Bold
+FullName DejaVu Serif Bold
+Version Version 2.37
+PostScriptName DejaVuSerif-Bold
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Serif
+PreferredSubfamily Bold
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 939
+Descender -236
+FontBBox -836 -389 1854 1145
+StartCharMetrics 3506
+U 32 ; WX 348 ; N space ; G 3
+U 33 ; WX 439 ; N exclam ; G 4
+U 34 ; WX 521 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 696 ; N dollar ; G 7
+U 37 ; WX 950 ; N percent ; G 8
+U 38 ; WX 903 ; N ampersand ; G 9
+U 39 ; WX 306 ; N quotesingle ; G 10
+U 40 ; WX 473 ; N parenleft ; G 11
+U 41 ; WX 473 ; N parenright ; G 12
+U 42 ; WX 523 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 348 ; N comma ; G 15
+U 45 ; WX 415 ; N hyphen ; G 16
+U 46 ; WX 348 ; N period ; G 17
+U 47 ; WX 365 ; N slash ; G 18
+U 48 ; WX 696 ; N zero ; G 19
+U 49 ; WX 696 ; N one ; G 20
+U 50 ; WX 696 ; N two ; G 21
+U 51 ; WX 696 ; N three ; G 22
+U 52 ; WX 696 ; N four ; G 23
+U 53 ; WX 696 ; N five ; G 24
+U 54 ; WX 696 ; N six ; G 25
+U 55 ; WX 696 ; N seven ; G 26
+U 56 ; WX 696 ; N eight ; G 27
+U 57 ; WX 696 ; N nine ; G 28
+U 58 ; WX 369 ; N colon ; G 29
+U 59 ; WX 369 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 586 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 776 ; N A ; G 36
+U 66 ; WX 845 ; N B ; G 37
+U 67 ; WX 796 ; N C ; G 38
+U 68 ; WX 867 ; N D ; G 39
+U 69 ; WX 762 ; N E ; G 40
+U 70 ; WX 710 ; N F ; G 41
+U 71 ; WX 854 ; N G ; G 42
+U 72 ; WX 945 ; N H ; G 43
+U 73 ; WX 468 ; N I ; G 44
+U 74 ; WX 473 ; N J ; G 45
+U 75 ; WX 869 ; N K ; G 46
+U 76 ; WX 703 ; N L ; G 47
+U 77 ; WX 1107 ; N M ; G 48
+U 78 ; WX 914 ; N N ; G 49
+U 79 ; WX 871 ; N O ; G 50
+U 80 ; WX 752 ; N P ; G 51
+U 81 ; WX 871 ; N Q ; G 52
+U 82 ; WX 831 ; N R ; G 53
+U 83 ; WX 722 ; N S ; G 54
+U 84 ; WX 744 ; N T ; G 55
+U 85 ; WX 872 ; N U ; G 56
+U 86 ; WX 776 ; N V ; G 57
+U 87 ; WX 1123 ; N W ; G 58
+U 88 ; WX 776 ; N X ; G 59
+U 89 ; WX 714 ; N Y ; G 60
+U 90 ; WX 730 ; N Z ; G 61
+U 91 ; WX 473 ; N bracketleft ; G 62
+U 92 ; WX 365 ; N backslash ; G 63
+U 93 ; WX 473 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 648 ; N a ; G 68
+U 98 ; WX 699 ; N b ; G 69
+U 99 ; WX 609 ; N c ; G 70
+U 100 ; WX 699 ; N d ; G 71
+U 101 ; WX 636 ; N e ; G 72
+U 102 ; WX 430 ; N f ; G 73
+U 103 ; WX 699 ; N g ; G 74
+U 104 ; WX 727 ; N h ; G 75
+U 105 ; WX 380 ; N i ; G 76
+U 106 ; WX 362 ; N j ; G 77
+U 107 ; WX 693 ; N k ; G 78
+U 108 ; WX 380 ; N l ; G 79
+U 109 ; WX 1058 ; N m ; G 80
+U 110 ; WX 727 ; N n ; G 81
+U 111 ; WX 667 ; N o ; G 82
+U 112 ; WX 699 ; N p ; G 83
+U 113 ; WX 699 ; N q ; G 84
+U 114 ; WX 527 ; N r ; G 85
+U 115 ; WX 563 ; N s ; G 86
+U 116 ; WX 462 ; N t ; G 87
+U 117 ; WX 727 ; N u ; G 88
+U 118 ; WX 581 ; N v ; G 89
+U 119 ; WX 861 ; N w ; G 90
+U 120 ; WX 596 ; N x ; G 91
+U 121 ; WX 581 ; N y ; G 92
+U 122 ; WX 568 ; N z ; G 93
+U 123 ; WX 643 ; N braceleft ; G 94
+U 124 ; WX 364 ; N bar ; G 95
+U 125 ; WX 643 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 348 ; N nbspace ; G 98
+U 161 ; WX 439 ; N exclamdown ; G 99
+U 162 ; WX 696 ; N cent ; G 100
+U 163 ; WX 696 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 696 ; N yen ; G 103
+U 166 ; WX 364 ; N brokenbar ; G 104
+U 167 ; WX 523 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 487 ; N ordfeminine ; G 108
+U 171 ; WX 625 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 415 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 438 ; N twosuperior ; G 116
+U 179 ; WX 438 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 732 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 348 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 438 ; N onesuperior ; G 123
+U 186 ; WX 500 ; N ordmasculine ; G 124
+U 187 ; WX 625 ; N guillemotright ; G 125
+U 188 ; WX 1043 ; N onequarter ; G 126
+U 189 ; WX 1043 ; N onehalf ; G 127
+U 190 ; WX 1043 ; N threequarters ; G 128
+U 191 ; WX 586 ; N questiondown ; G 129
+U 192 ; WX 776 ; N Agrave ; G 130
+U 193 ; WX 776 ; N Aacute ; G 131
+U 194 ; WX 776 ; N Acircumflex ; G 132
+U 195 ; WX 776 ; N Atilde ; G 133
+U 196 ; WX 776 ; N Adieresis ; G 134
+U 197 ; WX 776 ; N Aring ; G 135
+U 198 ; WX 1034 ; N AE ; G 136
+U 199 ; WX 796 ; N Ccedilla ; G 137
+U 200 ; WX 762 ; N Egrave ; G 138
+U 201 ; WX 762 ; N Eacute ; G 139
+U 202 ; WX 762 ; N Ecircumflex ; G 140
+U 203 ; WX 762 ; N Edieresis ; G 141
+U 204 ; WX 468 ; N Igrave ; G 142
+U 205 ; WX 468 ; N Iacute ; G 143
+U 206 ; WX 468 ; N Icircumflex ; G 144
+U 207 ; WX 468 ; N Idieresis ; G 145
+U 208 ; WX 874 ; N Eth ; G 146
+U 209 ; WX 914 ; N Ntilde ; G 147
+U 210 ; WX 871 ; N Ograve ; G 148
+U 211 ; WX 871 ; N Oacute ; G 149
+U 212 ; WX 871 ; N Ocircumflex ; G 150
+U 213 ; WX 871 ; N Otilde ; G 151
+U 214 ; WX 871 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 871 ; N Oslash ; G 154
+U 217 ; WX 872 ; N Ugrave ; G 155
+U 218 ; WX 872 ; N Uacute ; G 156
+U 219 ; WX 872 ; N Ucircumflex ; G 157
+U 220 ; WX 872 ; N Udieresis ; G 158
+U 221 ; WX 714 ; N Yacute ; G 159
+U 222 ; WX 757 ; N Thorn ; G 160
+U 223 ; WX 760 ; N germandbls ; G 161
+U 224 ; WX 648 ; N agrave ; G 162
+U 225 ; WX 648 ; N aacute ; G 163
+U 226 ; WX 648 ; N acircumflex ; G 164
+U 227 ; WX 648 ; N atilde ; G 165
+U 228 ; WX 648 ; N adieresis ; G 166
+U 229 ; WX 648 ; N aring ; G 167
+U 230 ; WX 975 ; N ae ; G 168
+U 231 ; WX 609 ; N ccedilla ; G 169
+U 232 ; WX 636 ; N egrave ; G 170
+U 233 ; WX 636 ; N eacute ; G 171
+U 234 ; WX 636 ; N ecircumflex ; G 172
+U 235 ; WX 636 ; N edieresis ; G 173
+U 236 ; WX 380 ; N igrave ; G 174
+U 237 ; WX 380 ; N iacute ; G 175
+U 238 ; WX 380 ; N icircumflex ; G 176
+U 239 ; WX 380 ; N idieresis ; G 177
+U 240 ; WX 667 ; N eth ; G 178
+U 241 ; WX 727 ; N ntilde ; G 179
+U 242 ; WX 667 ; N ograve ; G 180
+U 243 ; WX 667 ; N oacute ; G 181
+U 244 ; WX 667 ; N ocircumflex ; G 182
+U 245 ; WX 667 ; N otilde ; G 183
+U 246 ; WX 667 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 667 ; N oslash ; G 186
+U 249 ; WX 727 ; N ugrave ; G 187
+U 250 ; WX 727 ; N uacute ; G 188
+U 251 ; WX 727 ; N ucircumflex ; G 189
+U 252 ; WX 727 ; N udieresis ; G 190
+U 253 ; WX 581 ; N yacute ; G 191
+U 254 ; WX 699 ; N thorn ; G 192
+U 255 ; WX 581 ; N ydieresis ; G 193
+U 256 ; WX 776 ; N Amacron ; G 194
+U 257 ; WX 648 ; N amacron ; G 195
+U 258 ; WX 776 ; N Abreve ; G 196
+U 259 ; WX 648 ; N abreve ; G 197
+U 260 ; WX 776 ; N Aogonek ; G 198
+U 261 ; WX 648 ; N aogonek ; G 199
+U 262 ; WX 796 ; N Cacute ; G 200
+U 263 ; WX 609 ; N cacute ; G 201
+U 264 ; WX 796 ; N Ccircumflex ; G 202
+U 265 ; WX 609 ; N ccircumflex ; G 203
+U 266 ; WX 796 ; N Cdotaccent ; G 204
+U 267 ; WX 609 ; N cdotaccent ; G 205
+U 268 ; WX 796 ; N Ccaron ; G 206
+U 269 ; WX 609 ; N ccaron ; G 207
+U 270 ; WX 867 ; N Dcaron ; G 208
+U 271 ; WX 699 ; N dcaron ; G 209
+U 272 ; WX 874 ; N Dcroat ; G 210
+U 273 ; WX 699 ; N dmacron ; G 211
+U 274 ; WX 762 ; N Emacron ; G 212
+U 275 ; WX 636 ; N emacron ; G 213
+U 276 ; WX 762 ; N Ebreve ; G 214
+U 277 ; WX 636 ; N ebreve ; G 215
+U 278 ; WX 762 ; N Edotaccent ; G 216
+U 279 ; WX 636 ; N edotaccent ; G 217
+U 280 ; WX 762 ; N Eogonek ; G 218
+U 281 ; WX 636 ; N eogonek ; G 219
+U 282 ; WX 762 ; N Ecaron ; G 220
+U 283 ; WX 636 ; N ecaron ; G 221
+U 284 ; WX 854 ; N Gcircumflex ; G 222
+U 285 ; WX 699 ; N gcircumflex ; G 223
+U 286 ; WX 854 ; N Gbreve ; G 224
+U 287 ; WX 699 ; N gbreve ; G 225
+U 288 ; WX 854 ; N Gdotaccent ; G 226
+U 289 ; WX 699 ; N gdotaccent ; G 227
+U 290 ; WX 854 ; N Gcommaaccent ; G 228
+U 291 ; WX 699 ; N gcommaaccent ; G 229
+U 292 ; WX 945 ; N Hcircumflex ; G 230
+U 293 ; WX 727 ; N hcircumflex ; G 231
+U 294 ; WX 945 ; N Hbar ; G 232
+U 295 ; WX 727 ; N hbar ; G 233
+U 296 ; WX 468 ; N Itilde ; G 234
+U 297 ; WX 380 ; N itilde ; G 235
+U 298 ; WX 468 ; N Imacron ; G 236
+U 299 ; WX 380 ; N imacron ; G 237
+U 300 ; WX 468 ; N Ibreve ; G 238
+U 301 ; WX 380 ; N ibreve ; G 239
+U 302 ; WX 468 ; N Iogonek ; G 240
+U 303 ; WX 380 ; N iogonek ; G 241
+U 304 ; WX 468 ; N Idot ; G 242
+U 305 ; WX 380 ; N dotlessi ; G 243
+U 306 ; WX 942 ; N IJ ; G 244
+U 307 ; WX 751 ; N ij ; G 245
+U 308 ; WX 473 ; N Jcircumflex ; G 246
+U 309 ; WX 362 ; N jcircumflex ; G 247
+U 310 ; WX 869 ; N Kcommaaccent ; G 248
+U 311 ; WX 693 ; N kcommaaccent ; G 249
+U 312 ; WX 693 ; N kgreenlandic ; G 250
+U 313 ; WX 703 ; N Lacute ; G 251
+U 314 ; WX 380 ; N lacute ; G 252
+U 315 ; WX 703 ; N Lcommaaccent ; G 253
+U 316 ; WX 380 ; N lcommaaccent ; G 254
+U 317 ; WX 703 ; N Lcaron ; G 255
+U 318 ; WX 380 ; N lcaron ; G 256
+U 319 ; WX 703 ; N Ldot ; G 257
+U 320 ; WX 380 ; N ldot ; G 258
+U 321 ; WX 710 ; N Lslash ; G 259
+U 322 ; WX 385 ; N lslash ; G 260
+U 323 ; WX 914 ; N Nacute ; G 261
+U 324 ; WX 727 ; N nacute ; G 262
+U 325 ; WX 914 ; N Ncommaaccent ; G 263
+U 326 ; WX 727 ; N ncommaaccent ; G 264
+U 327 ; WX 914 ; N Ncaron ; G 265
+U 328 ; WX 727 ; N ncaron ; G 266
+U 329 ; WX 1008 ; N napostrophe ; G 267
+U 330 ; WX 872 ; N Eng ; G 268
+U 331 ; WX 727 ; N eng ; G 269
+U 332 ; WX 871 ; N Omacron ; G 270
+U 333 ; WX 667 ; N omacron ; G 271
+U 334 ; WX 871 ; N Obreve ; G 272
+U 335 ; WX 667 ; N obreve ; G 273
+U 336 ; WX 871 ; N Ohungarumlaut ; G 274
+U 337 ; WX 667 ; N ohungarumlaut ; G 275
+U 338 ; WX 1180 ; N OE ; G 276
+U 339 ; WX 1028 ; N oe ; G 277
+U 340 ; WX 831 ; N Racute ; G 278
+U 341 ; WX 527 ; N racute ; G 279
+U 342 ; WX 831 ; N Rcommaaccent ; G 280
+U 343 ; WX 527 ; N rcommaaccent ; G 281
+U 344 ; WX 831 ; N Rcaron ; G 282
+U 345 ; WX 527 ; N rcaron ; G 283
+U 346 ; WX 722 ; N Sacute ; G 284
+U 347 ; WX 563 ; N sacute ; G 285
+U 348 ; WX 722 ; N Scircumflex ; G 286
+U 349 ; WX 563 ; N scircumflex ; G 287
+U 350 ; WX 722 ; N Scedilla ; G 288
+U 351 ; WX 563 ; N scedilla ; G 289
+U 352 ; WX 722 ; N Scaron ; G 290
+U 353 ; WX 563 ; N scaron ; G 291
+U 354 ; WX 744 ; N Tcommaaccent ; G 292
+U 355 ; WX 462 ; N tcommaaccent ; G 293
+U 356 ; WX 744 ; N Tcaron ; G 294
+U 357 ; WX 462 ; N tcaron ; G 295
+U 358 ; WX 744 ; N Tbar ; G 296
+U 359 ; WX 462 ; N tbar ; G 297
+U 360 ; WX 872 ; N Utilde ; G 298
+U 361 ; WX 727 ; N utilde ; G 299
+U 362 ; WX 872 ; N Umacron ; G 300
+U 363 ; WX 727 ; N umacron ; G 301
+U 364 ; WX 872 ; N Ubreve ; G 302
+U 365 ; WX 727 ; N ubreve ; G 303
+U 366 ; WX 872 ; N Uring ; G 304
+U 367 ; WX 727 ; N uring ; G 305
+U 368 ; WX 872 ; N Uhungarumlaut ; G 306
+U 369 ; WX 727 ; N uhungarumlaut ; G 307
+U 370 ; WX 872 ; N Uogonek ; G 308
+U 371 ; WX 727 ; N uogonek ; G 309
+U 372 ; WX 1123 ; N Wcircumflex ; G 310
+U 373 ; WX 861 ; N wcircumflex ; G 311
+U 374 ; WX 714 ; N Ycircumflex ; G 312
+U 375 ; WX 581 ; N ycircumflex ; G 313
+U 376 ; WX 714 ; N Ydieresis ; G 314
+U 377 ; WX 730 ; N Zacute ; G 315
+U 378 ; WX 568 ; N zacute ; G 316
+U 379 ; WX 730 ; N Zdotaccent ; G 317
+U 380 ; WX 568 ; N zdotaccent ; G 318
+U 381 ; WX 730 ; N Zcaron ; G 319
+U 382 ; WX 568 ; N zcaron ; G 320
+U 383 ; WX 430 ; N longs ; G 321
+U 384 ; WX 699 ; N uni0180 ; G 322
+U 385 ; WX 845 ; N uni0181 ; G 323
+U 386 ; WX 854 ; N uni0182 ; G 324
+U 387 ; WX 699 ; N uni0183 ; G 325
+U 388 ; WX 854 ; N uni0184 ; G 326
+U 389 ; WX 699 ; N uni0185 ; G 327
+U 390 ; WX 796 ; N uni0186 ; G 328
+U 391 ; WX 796 ; N uni0187 ; G 329
+U 392 ; WX 609 ; N uni0188 ; G 330
+U 393 ; WX 874 ; N uni0189 ; G 331
+U 394 ; WX 867 ; N uni018A ; G 332
+U 395 ; WX 854 ; N uni018B ; G 333
+U 396 ; WX 699 ; N uni018C ; G 334
+U 397 ; WX 667 ; N uni018D ; G 335
+U 398 ; WX 762 ; N uni018E ; G 336
+U 399 ; WX 871 ; N uni018F ; G 337
+U 400 ; WX 721 ; N uni0190 ; G 338
+U 401 ; WX 710 ; N uni0191 ; G 339
+U 402 ; WX 430 ; N florin ; G 340
+U 403 ; WX 854 ; N uni0193 ; G 341
+U 404 ; WX 771 ; N uni0194 ; G 342
+U 405 ; WX 1043 ; N uni0195 ; G 343
+U 406 ; WX 468 ; N uni0196 ; G 344
+U 407 ; WX 468 ; N uni0197 ; G 345
+U 408 ; WX 869 ; N uni0198 ; G 346
+U 409 ; WX 693 ; N uni0199 ; G 347
+U 410 ; WX 380 ; N uni019A ; G 348
+U 411 ; WX 701 ; N uni019B ; G 349
+U 412 ; WX 1058 ; N uni019C ; G 350
+U 413 ; WX 914 ; N uni019D ; G 351
+U 414 ; WX 727 ; N uni019E ; G 352
+U 415 ; WX 871 ; N uni019F ; G 353
+U 416 ; WX 871 ; N Ohorn ; G 354
+U 417 ; WX 667 ; N ohorn ; G 355
+U 418 ; WX 1200 ; N uni01A2 ; G 356
+U 419 ; WX 943 ; N uni01A3 ; G 357
+U 420 ; WX 752 ; N uni01A4 ; G 358
+U 421 ; WX 699 ; N uni01A5 ; G 359
+U 422 ; WX 831 ; N uni01A6 ; G 360
+U 423 ; WX 722 ; N uni01A7 ; G 361
+U 424 ; WX 563 ; N uni01A8 ; G 362
+U 425 ; WX 707 ; N uni01A9 ; G 363
+U 426 ; WX 331 ; N uni01AA ; G 364
+U 427 ; WX 462 ; N uni01AB ; G 365
+U 428 ; WX 744 ; N uni01AC ; G 366
+U 429 ; WX 462 ; N uni01AD ; G 367
+U 430 ; WX 744 ; N uni01AE ; G 368
+U 431 ; WX 872 ; N Uhorn ; G 369
+U 432 ; WX 727 ; N uhorn ; G 370
+U 433 ; WX 890 ; N uni01B1 ; G 371
+U 434 ; WX 890 ; N uni01B2 ; G 372
+U 435 ; WX 714 ; N uni01B3 ; G 373
+U 436 ; WX 708 ; N uni01B4 ; G 374
+U 437 ; WX 730 ; N uni01B5 ; G 375
+U 438 ; WX 568 ; N uni01B6 ; G 376
+U 439 ; WX 657 ; N uni01B7 ; G 377
+U 440 ; WX 657 ; N uni01B8 ; G 378
+U 441 ; WX 657 ; N uni01B9 ; G 379
+U 442 ; WX 657 ; N uni01BA ; G 380
+U 443 ; WX 696 ; N uni01BB ; G 381
+U 444 ; WX 754 ; N uni01BC ; G 382
+U 445 ; WX 568 ; N uni01BD ; G 383
+U 446 ; WX 536 ; N uni01BE ; G 384
+U 447 ; WX 716 ; N uni01BF ; G 385
+U 448 ; WX 295 ; N uni01C0 ; G 386
+U 449 ; WX 492 ; N uni01C1 ; G 387
+U 450 ; WX 459 ; N uni01C2 ; G 388
+U 451 ; WX 295 ; N uni01C3 ; G 389
+U 452 ; WX 1597 ; N uni01C4 ; G 390
+U 453 ; WX 1435 ; N uni01C5 ; G 391
+U 454 ; WX 1267 ; N uni01C6 ; G 392
+U 455 ; WX 1176 ; N uni01C7 ; G 393
+U 456 ; WX 1065 ; N uni01C8 ; G 394
+U 457 ; WX 742 ; N uni01C9 ; G 395
+U 458 ; WX 1387 ; N uni01CA ; G 396
+U 459 ; WX 1276 ; N uni01CB ; G 397
+U 460 ; WX 1089 ; N uni01CC ; G 398
+U 461 ; WX 776 ; N uni01CD ; G 399
+U 462 ; WX 648 ; N uni01CE ; G 400
+U 463 ; WX 468 ; N uni01CF ; G 401
+U 464 ; WX 380 ; N uni01D0 ; G 402
+U 465 ; WX 871 ; N uni01D1 ; G 403
+U 466 ; WX 667 ; N uni01D2 ; G 404
+U 467 ; WX 872 ; N uni01D3 ; G 405
+U 468 ; WX 727 ; N uni01D4 ; G 406
+U 469 ; WX 872 ; N uni01D5 ; G 407
+U 470 ; WX 727 ; N uni01D6 ; G 408
+U 471 ; WX 872 ; N uni01D7 ; G 409
+U 472 ; WX 727 ; N uni01D8 ; G 410
+U 473 ; WX 872 ; N uni01D9 ; G 411
+U 474 ; WX 727 ; N uni01DA ; G 412
+U 475 ; WX 872 ; N uni01DB ; G 413
+U 476 ; WX 727 ; N uni01DC ; G 414
+U 477 ; WX 636 ; N uni01DD ; G 415
+U 478 ; WX 776 ; N uni01DE ; G 416
+U 479 ; WX 648 ; N uni01DF ; G 417
+U 480 ; WX 776 ; N uni01E0 ; G 418
+U 481 ; WX 648 ; N uni01E1 ; G 419
+U 482 ; WX 1034 ; N uni01E2 ; G 420
+U 483 ; WX 975 ; N uni01E3 ; G 421
+U 484 ; WX 896 ; N uni01E4 ; G 422
+U 485 ; WX 699 ; N uni01E5 ; G 423
+U 486 ; WX 854 ; N Gcaron ; G 424
+U 487 ; WX 699 ; N gcaron ; G 425
+U 488 ; WX 869 ; N uni01E8 ; G 426
+U 489 ; WX 693 ; N uni01E9 ; G 427
+U 490 ; WX 871 ; N uni01EA ; G 428
+U 491 ; WX 667 ; N uni01EB ; G 429
+U 492 ; WX 871 ; N uni01EC ; G 430
+U 493 ; WX 667 ; N uni01ED ; G 431
+U 494 ; WX 657 ; N uni01EE ; G 432
+U 495 ; WX 568 ; N uni01EF ; G 433
+U 496 ; WX 380 ; N uni01F0 ; G 434
+U 497 ; WX 1597 ; N uni01F1 ; G 435
+U 498 ; WX 1435 ; N uni01F2 ; G 436
+U 499 ; WX 1267 ; N uni01F3 ; G 437
+U 500 ; WX 854 ; N uni01F4 ; G 438
+U 501 ; WX 699 ; N uni01F5 ; G 439
+U 502 ; WX 1221 ; N uni01F6 ; G 440
+U 503 ; WX 787 ; N uni01F7 ; G 441
+U 504 ; WX 914 ; N uni01F8 ; G 442
+U 505 ; WX 727 ; N uni01F9 ; G 443
+U 506 ; WX 776 ; N Aringacute ; G 444
+U 507 ; WX 648 ; N aringacute ; G 445
+U 508 ; WX 1034 ; N AEacute ; G 446
+U 509 ; WX 975 ; N aeacute ; G 447
+U 510 ; WX 871 ; N Oslashacute ; G 448
+U 511 ; WX 667 ; N oslashacute ; G 449
+U 512 ; WX 776 ; N uni0200 ; G 450
+U 513 ; WX 648 ; N uni0201 ; G 451
+U 514 ; WX 776 ; N uni0202 ; G 452
+U 515 ; WX 648 ; N uni0203 ; G 453
+U 516 ; WX 762 ; N uni0204 ; G 454
+U 517 ; WX 636 ; N uni0205 ; G 455
+U 518 ; WX 762 ; N uni0206 ; G 456
+U 519 ; WX 636 ; N uni0207 ; G 457
+U 520 ; WX 468 ; N uni0208 ; G 458
+U 521 ; WX 380 ; N uni0209 ; G 459
+U 522 ; WX 468 ; N uni020A ; G 460
+U 523 ; WX 380 ; N uni020B ; G 461
+U 524 ; WX 871 ; N uni020C ; G 462
+U 525 ; WX 667 ; N uni020D ; G 463
+U 526 ; WX 871 ; N uni020E ; G 464
+U 527 ; WX 667 ; N uni020F ; G 465
+U 528 ; WX 831 ; N uni0210 ; G 466
+U 529 ; WX 527 ; N uni0211 ; G 467
+U 530 ; WX 831 ; N uni0212 ; G 468
+U 531 ; WX 527 ; N uni0213 ; G 469
+U 532 ; WX 872 ; N uni0214 ; G 470
+U 533 ; WX 727 ; N uni0215 ; G 471
+U 534 ; WX 872 ; N uni0216 ; G 472
+U 535 ; WX 727 ; N uni0217 ; G 473
+U 536 ; WX 722 ; N Scommaaccent ; G 474
+U 537 ; WX 563 ; N scommaaccent ; G 475
+U 538 ; WX 744 ; N uni021A ; G 476
+U 539 ; WX 462 ; N uni021B ; G 477
+U 540 ; WX 690 ; N uni021C ; G 478
+U 541 ; WX 607 ; N uni021D ; G 479
+U 542 ; WX 945 ; N uni021E ; G 480
+U 543 ; WX 727 ; N uni021F ; G 481
+U 544 ; WX 872 ; N uni0220 ; G 482
+U 545 ; WX 791 ; N uni0221 ; G 483
+U 546 ; WX 703 ; N uni0222 ; G 484
+U 547 ; WX 616 ; N uni0223 ; G 485
+U 548 ; WX 730 ; N uni0224 ; G 486
+U 549 ; WX 568 ; N uni0225 ; G 487
+U 550 ; WX 776 ; N uni0226 ; G 488
+U 551 ; WX 648 ; N uni0227 ; G 489
+U 552 ; WX 762 ; N uni0228 ; G 490
+U 553 ; WX 636 ; N uni0229 ; G 491
+U 554 ; WX 871 ; N uni022A ; G 492
+U 555 ; WX 667 ; N uni022B ; G 493
+U 556 ; WX 871 ; N uni022C ; G 494
+U 557 ; WX 667 ; N uni022D ; G 495
+U 558 ; WX 871 ; N uni022E ; G 496
+U 559 ; WX 667 ; N uni022F ; G 497
+U 560 ; WX 871 ; N uni0230 ; G 498
+U 561 ; WX 667 ; N uni0231 ; G 499
+U 562 ; WX 714 ; N uni0232 ; G 500
+U 563 ; WX 581 ; N uni0233 ; G 501
+U 564 ; WX 573 ; N uni0234 ; G 502
+U 565 ; WX 922 ; N uni0235 ; G 503
+U 566 ; WX 564 ; N uni0236 ; G 504
+U 567 ; WX 362 ; N dotlessj ; G 505
+U 568 ; WX 1031 ; N uni0238 ; G 506
+U 569 ; WX 1031 ; N uni0239 ; G 507
+U 570 ; WX 776 ; N uni023A ; G 508
+U 571 ; WX 796 ; N uni023B ; G 509
+U 572 ; WX 609 ; N uni023C ; G 510
+U 573 ; WX 703 ; N uni023D ; G 511
+U 574 ; WX 744 ; N uni023E ; G 512
+U 575 ; WX 563 ; N uni023F ; G 513
+U 576 ; WX 568 ; N uni0240 ; G 514
+U 577 ; WX 660 ; N uni0241 ; G 515
+U 578 ; WX 547 ; N uni0242 ; G 516
+U 579 ; WX 845 ; N uni0243 ; G 517
+U 580 ; WX 872 ; N uni0244 ; G 518
+U 581 ; WX 776 ; N uni0245 ; G 519
+U 582 ; WX 762 ; N uni0246 ; G 520
+U 583 ; WX 636 ; N uni0247 ; G 521
+U 584 ; WX 473 ; N uni0248 ; G 522
+U 585 ; WX 387 ; N uni0249 ; G 523
+U 586 ; WX 848 ; N uni024A ; G 524
+U 587 ; WX 699 ; N uni024B ; G 525
+U 588 ; WX 831 ; N uni024C ; G 526
+U 589 ; WX 527 ; N uni024D ; G 527
+U 590 ; WX 714 ; N uni024E ; G 528
+U 591 ; WX 581 ; N uni024F ; G 529
+U 592 ; WX 648 ; N uni0250 ; G 530
+U 593 ; WX 699 ; N uni0251 ; G 531
+U 594 ; WX 699 ; N uni0252 ; G 532
+U 595 ; WX 699 ; N uni0253 ; G 533
+U 596 ; WX 609 ; N uni0254 ; G 534
+U 597 ; WX 609 ; N uni0255 ; G 535
+U 598 ; WX 699 ; N uni0256 ; G 536
+U 599 ; WX 730 ; N uni0257 ; G 537
+U 600 ; WX 636 ; N uni0258 ; G 538
+U 601 ; WX 636 ; N uni0259 ; G 539
+U 602 ; WX 907 ; N uni025A ; G 540
+U 603 ; WX 608 ; N uni025B ; G 541
+U 604 ; WX 562 ; N uni025C ; G 542
+U 605 ; WX 907 ; N uni025D ; G 543
+U 606 ; WX 714 ; N uni025E ; G 544
+U 607 ; WX 387 ; N uni025F ; G 545
+U 608 ; WX 699 ; N uni0260 ; G 546
+U 609 ; WX 699 ; N uni0261 ; G 547
+U 610 ; WX 638 ; N uni0262 ; G 548
+U 611 ; WX 601 ; N uni0263 ; G 549
+U 612 ; WX 627 ; N uni0264 ; G 550
+U 613 ; WX 727 ; N uni0265 ; G 551
+U 614 ; WX 727 ; N uni0266 ; G 552
+U 615 ; WX 727 ; N uni0267 ; G 553
+U 616 ; WX 380 ; N uni0268 ; G 554
+U 617 ; WX 380 ; N uni0269 ; G 555
+U 618 ; WX 380 ; N uni026A ; G 556
+U 619 ; WX 409 ; N uni026B ; G 557
+U 620 ; WX 514 ; N uni026C ; G 558
+U 621 ; WX 380 ; N uni026D ; G 559
+U 622 ; WX 795 ; N uni026E ; G 560
+U 623 ; WX 1058 ; N uni026F ; G 561
+U 624 ; WX 1058 ; N uni0270 ; G 562
+U 625 ; WX 1058 ; N uni0271 ; G 563
+U 626 ; WX 727 ; N uni0272 ; G 564
+U 627 ; WX 727 ; N uni0273 ; G 565
+U 628 ; WX 712 ; N uni0274 ; G 566
+U 629 ; WX 667 ; N uni0275 ; G 567
+U 630 ; WX 1061 ; N uni0276 ; G 568
+U 631 ; WX 944 ; N uni0277 ; G 569
+U 632 ; WX 797 ; N uni0278 ; G 570
+U 633 ; WX 571 ; N uni0279 ; G 571
+U 634 ; WX 571 ; N uni027A ; G 572
+U 635 ; WX 571 ; N uni027B ; G 573
+U 636 ; WX 527 ; N uni027C ; G 574
+U 637 ; WX 527 ; N uni027D ; G 575
+U 638 ; WX 452 ; N uni027E ; G 576
+U 639 ; WX 487 ; N uni027F ; G 577
+U 640 ; WX 694 ; N uni0280 ; G 578
+U 641 ; WX 694 ; N uni0281 ; G 579
+U 642 ; WX 563 ; N uni0282 ; G 580
+U 643 ; WX 331 ; N uni0283 ; G 581
+U 644 ; WX 430 ; N uni0284 ; G 582
+U 645 ; WX 540 ; N uni0285 ; G 583
+U 646 ; WX 331 ; N uni0286 ; G 584
+U 647 ; WX 492 ; N uni0287 ; G 585
+U 648 ; WX 462 ; N uni0288 ; G 586
+U 649 ; WX 727 ; N uni0289 ; G 587
+U 650 ; WX 679 ; N uni028A ; G 588
+U 651 ; WX 694 ; N uni028B ; G 589
+U 652 ; WX 641 ; N uni028C ; G 590
+U 653 ; WX 907 ; N uni028D ; G 591
+U 654 ; WX 635 ; N uni028E ; G 592
+U 655 ; WX 727 ; N uni028F ; G 593
+U 656 ; WX 568 ; N uni0290 ; G 594
+U 657 ; WX 568 ; N uni0291 ; G 595
+U 658 ; WX 568 ; N uni0292 ; G 596
+U 659 ; WX 568 ; N uni0293 ; G 597
+U 660 ; WX 551 ; N uni0294 ; G 598
+U 661 ; WX 551 ; N uni0295 ; G 599
+U 662 ; WX 551 ; N uni0296 ; G 600
+U 663 ; WX 545 ; N uni0297 ; G 601
+U 664 ; WX 871 ; N uni0298 ; G 602
+U 665 ; WX 695 ; N uni0299 ; G 603
+U 666 ; WX 714 ; N uni029A ; G 604
+U 667 ; WX 689 ; N uni029B ; G 605
+U 668 ; WX 732 ; N uni029C ; G 606
+U 669 ; WX 384 ; N uni029D ; G 607
+U 670 ; WX 740 ; N uni029E ; G 608
+U 671 ; WX 617 ; N uni029F ; G 609
+U 672 ; WX 699 ; N uni02A0 ; G 610
+U 673 ; WX 551 ; N uni02A1 ; G 611
+U 674 ; WX 551 ; N uni02A2 ; G 612
+U 675 ; WX 1117 ; N uni02A3 ; G 613
+U 676 ; WX 1179 ; N uni02A4 ; G 614
+U 677 ; WX 1117 ; N uni02A5 ; G 615
+U 678 ; WX 938 ; N uni02A6 ; G 616
+U 679 ; WX 715 ; N uni02A7 ; G 617
+U 680 ; WX 946 ; N uni02A8 ; G 618
+U 681 ; WX 1039 ; N uni02A9 ; G 619
+U 682 ; WX 870 ; N uni02AA ; G 620
+U 683 ; WX 795 ; N uni02AB ; G 621
+U 684 ; WX 662 ; N uni02AC ; G 622
+U 685 ; WX 443 ; N uni02AD ; G 623
+U 686 ; WX 613 ; N uni02AE ; G 624
+U 687 ; WX 717 ; N uni02AF ; G 625
+U 688 ; WX 521 ; N uni02B0 ; G 626
+U 689 ; WX 519 ; N uni02B1 ; G 627
+U 690 ; WX 313 ; N uni02B2 ; G 628
+U 691 ; WX 414 ; N uni02B3 ; G 629
+U 692 ; WX 414 ; N uni02B4 ; G 630
+U 693 ; WX 480 ; N uni02B5 ; G 631
+U 694 ; WX 527 ; N uni02B6 ; G 632
+U 695 ; WX 662 ; N uni02B7 ; G 633
+U 696 ; WX 485 ; N uni02B8 ; G 634
+U 697 ; WX 302 ; N uni02B9 ; G 635
+U 698 ; WX 521 ; N uni02BA ; G 636
+U 699 ; WX 348 ; N uni02BB ; G 637
+U 700 ; WX 348 ; N uni02BC ; G 638
+U 701 ; WX 348 ; N uni02BD ; G 639
+U 702 ; WX 366 ; N uni02BE ; G 640
+U 703 ; WX 366 ; N uni02BF ; G 641
+U 704 ; WX 313 ; N uni02C0 ; G 642
+U 705 ; WX 313 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 282 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 282 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 720 ; WX 369 ; N uni02D0 ; G 656
+U 721 ; WX 369 ; N uni02D1 ; G 657
+U 722 ; WX 366 ; N uni02D2 ; G 658
+U 723 ; WX 366 ; N uni02D3 ; G 659
+U 726 ; WX 392 ; N uni02D6 ; G 660
+U 727 ; WX 392 ; N uni02D7 ; G 661
+U 728 ; WX 500 ; N breve ; G 662
+U 729 ; WX 500 ; N dotaccent ; G 663
+U 730 ; WX 500 ; N ring ; G 664
+U 731 ; WX 500 ; N ogonek ; G 665
+U 732 ; WX 500 ; N tilde ; G 666
+U 733 ; WX 500 ; N hungarumlaut ; G 667
+U 734 ; WX 417 ; N uni02DE ; G 668
+U 736 ; WX 378 ; N uni02E0 ; G 669
+U 737 ; WX 292 ; N uni02E1 ; G 670
+U 738 ; WX 395 ; N uni02E2 ; G 671
+U 739 ; WX 475 ; N uni02E3 ; G 672
+U 740 ; WX 313 ; N uni02E4 ; G 673
+U 741 ; WX 500 ; N uni02E5 ; G 674
+U 742 ; WX 500 ; N uni02E6 ; G 675
+U 743 ; WX 500 ; N uni02E7 ; G 676
+U 744 ; WX 500 ; N uni02E8 ; G 677
+U 745 ; WX 500 ; N uni02E9 ; G 678
+U 748 ; WX 500 ; N uni02EC ; G 679
+U 750 ; WX 553 ; N uni02EE ; G 680
+U 751 ; WX 500 ; N uni02EF ; G 681
+U 752 ; WX 500 ; N uni02F0 ; G 682
+U 755 ; WX 500 ; N uni02F3 ; G 683
+U 759 ; WX 500 ; N uni02F7 ; G 684
+U 768 ; WX 0 ; N gravecomb ; G 685
+U 769 ; WX 0 ; N acutecomb ; G 686
+U 770 ; WX 0 ; N uni0302 ; G 687
+U 771 ; WX 0 ; N tildecomb ; G 688
+U 772 ; WX 0 ; N uni0304 ; G 689
+U 773 ; WX 0 ; N uni0305 ; G 690
+U 774 ; WX 0 ; N uni0306 ; G 691
+U 775 ; WX 0 ; N uni0307 ; G 692
+U 776 ; WX 0 ; N uni0308 ; G 693
+U 777 ; WX 0 ; N hookabovecomb ; G 694
+U 778 ; WX 0 ; N uni030A ; G 695
+U 779 ; WX 0 ; N uni030B ; G 696
+U 780 ; WX 0 ; N uni030C ; G 697
+U 781 ; WX 0 ; N uni030D ; G 698
+U 782 ; WX 0 ; N uni030E ; G 699
+U 783 ; WX 0 ; N uni030F ; G 700
+U 784 ; WX 0 ; N uni0310 ; G 701
+U 785 ; WX 0 ; N uni0311 ; G 702
+U 786 ; WX 0 ; N uni0312 ; G 703
+U 787 ; WX 0 ; N uni0313 ; G 704
+U 788 ; WX 0 ; N uni0314 ; G 705
+U 789 ; WX 0 ; N uni0315 ; G 706
+U 790 ; WX 0 ; N uni0316 ; G 707
+U 791 ; WX 0 ; N uni0317 ; G 708
+U 792 ; WX 0 ; N uni0318 ; G 709
+U 793 ; WX 0 ; N uni0319 ; G 710
+U 794 ; WX 0 ; N uni031A ; G 711
+U 795 ; WX 0 ; N uni031B ; G 712
+U 796 ; WX 0 ; N uni031C ; G 713
+U 797 ; WX 0 ; N uni031D ; G 714
+U 798 ; WX 0 ; N uni031E ; G 715
+U 799 ; WX 0 ; N uni031F ; G 716
+U 800 ; WX 0 ; N uni0320 ; G 717
+U 801 ; WX 0 ; N uni0321 ; G 718
+U 802 ; WX 0 ; N uni0322 ; G 719
+U 803 ; WX 0 ; N dotbelowcomb ; G 720
+U 804 ; WX 0 ; N uni0324 ; G 721
+U 805 ; WX 0 ; N uni0325 ; G 722
+U 806 ; WX 0 ; N uni0326 ; G 723
+U 807 ; WX 0 ; N uni0327 ; G 724
+U 808 ; WX 0 ; N uni0328 ; G 725
+U 809 ; WX 0 ; N uni0329 ; G 726
+U 810 ; WX 0 ; N uni032A ; G 727
+U 811 ; WX 0 ; N uni032B ; G 728
+U 812 ; WX 0 ; N uni032C ; G 729
+U 813 ; WX 0 ; N uni032D ; G 730
+U 814 ; WX 0 ; N uni032E ; G 731
+U 815 ; WX 0 ; N uni032F ; G 732
+U 816 ; WX 0 ; N uni0330 ; G 733
+U 817 ; WX 0 ; N uni0331 ; G 734
+U 818 ; WX 0 ; N uni0332 ; G 735
+U 819 ; WX 0 ; N uni0333 ; G 736
+U 820 ; WX 0 ; N uni0334 ; G 737
+U 821 ; WX 0 ; N uni0335 ; G 738
+U 822 ; WX 0 ; N uni0336 ; G 739
+U 823 ; WX 0 ; N uni0337 ; G 740
+U 824 ; WX 0 ; N uni0338 ; G 741
+U 825 ; WX 0 ; N uni0339 ; G 742
+U 826 ; WX 0 ; N uni033A ; G 743
+U 827 ; WX 0 ; N uni033B ; G 744
+U 828 ; WX 0 ; N uni033C ; G 745
+U 829 ; WX 0 ; N uni033D ; G 746
+U 830 ; WX 0 ; N uni033E ; G 747
+U 831 ; WX 0 ; N uni033F ; G 748
+U 835 ; WX 0 ; N uni0343 ; G 749
+U 847 ; WX 0 ; N uni034F ; G 750
+U 856 ; WX 0 ; N uni0358 ; G 751
+U 864 ; WX 0 ; N uni0360 ; G 752
+U 865 ; WX 0 ; N uni0361 ; G 753
+U 880 ; WX 779 ; N uni0370 ; G 754
+U 881 ; WX 576 ; N uni0371 ; G 755
+U 882 ; WX 803 ; N uni0372 ; G 756
+U 883 ; WX 777 ; N uni0373 ; G 757
+U 884 ; WX 302 ; N uni0374 ; G 758
+U 885 ; WX 302 ; N uni0375 ; G 759
+U 886 ; WX 963 ; N uni0376 ; G 760
+U 887 ; WX 737 ; N uni0377 ; G 761
+U 890 ; WX 500 ; N uni037A ; G 762
+U 891 ; WX 609 ; N uni037B ; G 763
+U 892 ; WX 609 ; N uni037C ; G 764
+U 893 ; WX 609 ; N uni037D ; G 765
+U 894 ; WX 369 ; N uni037E ; G 766
+U 895 ; WX 473 ; N uni037F ; G 767
+U 900 ; WX 500 ; N tonos ; G 768
+U 901 ; WX 500 ; N dieresistonos ; G 769
+U 902 ; WX 776 ; N Alphatonos ; G 770
+U 903 ; WX 348 ; N anoteleia ; G 771
+U 904 ; WX 947 ; N Epsilontonos ; G 772
+U 905 ; WX 1118 ; N Etatonos ; G 773
+U 906 ; WX 662 ; N Iotatonos ; G 774
+U 908 ; WX 887 ; N Omicrontonos ; G 775
+U 910 ; WX 953 ; N Upsilontonos ; G 776
+U 911 ; WX 911 ; N Omegatonos ; G 777
+U 912 ; WX 484 ; N iotadieresistonos ; G 778
+U 913 ; WX 776 ; N Alpha ; G 779
+U 914 ; WX 845 ; N Beta ; G 780
+U 915 ; WX 710 ; N Gamma ; G 781
+U 916 ; WX 776 ; N uni0394 ; G 782
+U 917 ; WX 762 ; N Epsilon ; G 783
+U 918 ; WX 730 ; N Zeta ; G 784
+U 919 ; WX 945 ; N Eta ; G 785
+U 920 ; WX 871 ; N Theta ; G 786
+U 921 ; WX 468 ; N Iota ; G 787
+U 922 ; WX 869 ; N Kappa ; G 788
+U 923 ; WX 776 ; N Lambda ; G 789
+U 924 ; WX 1107 ; N Mu ; G 790
+U 925 ; WX 914 ; N Nu ; G 791
+U 926 ; WX 704 ; N Xi ; G 792
+U 927 ; WX 871 ; N Omicron ; G 793
+U 928 ; WX 944 ; N Pi ; G 794
+U 929 ; WX 752 ; N Rho ; G 795
+U 931 ; WX 707 ; N Sigma ; G 796
+U 932 ; WX 744 ; N Tau ; G 797
+U 933 ; WX 714 ; N Upsilon ; G 798
+U 934 ; WX 871 ; N Phi ; G 799
+U 935 ; WX 776 ; N Chi ; G 800
+U 936 ; WX 913 ; N Psi ; G 801
+U 937 ; WX 890 ; N Omega ; G 802
+U 938 ; WX 468 ; N Iotadieresis ; G 803
+U 939 ; WX 714 ; N Upsilondieresis ; G 804
+U 940 ; WX 770 ; N alphatonos ; G 805
+U 941 ; WX 608 ; N epsilontonos ; G 806
+U 942 ; WX 727 ; N etatonos ; G 807
+U 943 ; WX 484 ; N iotatonos ; G 808
+U 944 ; WX 694 ; N upsilondieresistonos ; G 809
+U 945 ; WX 770 ; N alpha ; G 810
+U 946 ; WX 664 ; N beta ; G 811
+U 947 ; WX 660 ; N gamma ; G 812
+U 948 ; WX 667 ; N delta ; G 813
+U 949 ; WX 608 ; N epsilon ; G 814
+U 950 ; WX 592 ; N zeta ; G 815
+U 951 ; WX 727 ; N eta ; G 816
+U 952 ; WX 667 ; N theta ; G 817
+U 953 ; WX 484 ; N iota ; G 818
+U 954 ; WX 750 ; N kappa ; G 819
+U 955 ; WX 701 ; N lambda ; G 820
+U 956 ; WX 732 ; N uni03BC ; G 821
+U 957 ; WX 694 ; N nu ; G 822
+U 958 ; WX 592 ; N xi ; G 823
+U 959 ; WX 667 ; N omicron ; G 824
+U 960 ; WX 732 ; N pi ; G 825
+U 961 ; WX 665 ; N rho ; G 826
+U 962 ; WX 609 ; N sigma1 ; G 827
+U 963 ; WX 737 ; N sigma ; G 828
+U 964 ; WX 673 ; N tau ; G 829
+U 965 ; WX 694 ; N upsilon ; G 830
+U 966 ; WX 905 ; N phi ; G 831
+U 967 ; WX 658 ; N chi ; G 832
+U 968 ; WX 941 ; N psi ; G 833
+U 969 ; WX 952 ; N omega ; G 834
+U 970 ; WX 484 ; N iotadieresis ; G 835
+U 971 ; WX 694 ; N upsilondieresis ; G 836
+U 972 ; WX 667 ; N omicrontonos ; G 837
+U 973 ; WX 694 ; N upsilontonos ; G 838
+U 974 ; WX 952 ; N omegatonos ; G 839
+U 975 ; WX 869 ; N uni03CF ; G 840
+U 976 ; WX 667 ; N uni03D0 ; G 841
+U 977 ; WX 849 ; N theta1 ; G 842
+U 978 ; WX 764 ; N Upsilon1 ; G 843
+U 979 ; WX 969 ; N uni03D3 ; G 844
+U 980 ; WX 764 ; N uni03D4 ; G 845
+U 981 ; WX 941 ; N phi1 ; G 846
+U 982 ; WX 952 ; N omega1 ; G 847
+U 983 ; WX 655 ; N uni03D7 ; G 848
+U 984 ; WX 871 ; N uni03D8 ; G 849
+U 985 ; WX 667 ; N uni03D9 ; G 850
+U 986 ; WX 796 ; N uni03DA ; G 851
+U 987 ; WX 609 ; N uni03DB ; G 852
+U 988 ; WX 710 ; N uni03DC ; G 853
+U 989 ; WX 527 ; N uni03DD ; G 854
+U 990 ; WX 590 ; N uni03DE ; G 855
+U 991 ; WX 660 ; N uni03DF ; G 856
+U 992 ; WX 796 ; N uni03E0 ; G 857
+U 993 ; WX 667 ; N uni03E1 ; G 858
+U 1008 ; WX 655 ; N uni03F0 ; G 859
+U 1009 ; WX 665 ; N uni03F1 ; G 860
+U 1010 ; WX 609 ; N uni03F2 ; G 861
+U 1011 ; WX 362 ; N uni03F3 ; G 862
+U 1012 ; WX 871 ; N uni03F4 ; G 863
+U 1013 ; WX 609 ; N uni03F5 ; G 864
+U 1014 ; WX 609 ; N uni03F6 ; G 865
+U 1015 ; WX 757 ; N uni03F7 ; G 866
+U 1016 ; WX 699 ; N uni03F8 ; G 867
+U 1017 ; WX 796 ; N uni03F9 ; G 868
+U 1018 ; WX 1107 ; N uni03FA ; G 869
+U 1019 ; WX 860 ; N uni03FB ; G 870
+U 1020 ; WX 692 ; N uni03FC ; G 871
+U 1021 ; WX 796 ; N uni03FD ; G 872
+U 1022 ; WX 796 ; N uni03FE ; G 873
+U 1023 ; WX 796 ; N uni03FF ; G 874
+U 1024 ; WX 762 ; N uni0400 ; G 875
+U 1025 ; WX 762 ; N uni0401 ; G 876
+U 1026 ; WX 901 ; N uni0402 ; G 877
+U 1027 ; WX 690 ; N uni0403 ; G 878
+U 1028 ; WX 795 ; N uni0404 ; G 879
+U 1029 ; WX 722 ; N uni0405 ; G 880
+U 1030 ; WX 468 ; N uni0406 ; G 881
+U 1031 ; WX 468 ; N uni0407 ; G 882
+U 1032 ; WX 473 ; N uni0408 ; G 883
+U 1033 ; WX 1202 ; N uni0409 ; G 884
+U 1034 ; WX 1262 ; N uni040A ; G 885
+U 1035 ; WX 963 ; N uni040B ; G 886
+U 1036 ; WX 910 ; N uni040C ; G 887
+U 1037 ; WX 945 ; N uni040D ; G 888
+U 1038 ; WX 812 ; N uni040E ; G 889
+U 1039 ; WX 945 ; N uni040F ; G 890
+U 1040 ; WX 814 ; N uni0410 ; G 891
+U 1041 ; WX 854 ; N uni0411 ; G 892
+U 1042 ; WX 845 ; N uni0412 ; G 893
+U 1043 ; WX 690 ; N uni0413 ; G 894
+U 1044 ; WX 889 ; N uni0414 ; G 895
+U 1045 ; WX 762 ; N uni0415 ; G 896
+U 1046 ; WX 1312 ; N uni0416 ; G 897
+U 1047 ; WX 721 ; N uni0417 ; G 898
+U 1048 ; WX 945 ; N uni0418 ; G 899
+U 1049 ; WX 945 ; N uni0419 ; G 900
+U 1050 ; WX 910 ; N uni041A ; G 901
+U 1051 ; WX 884 ; N uni041B ; G 902
+U 1052 ; WX 1107 ; N uni041C ; G 903
+U 1053 ; WX 945 ; N uni041D ; G 904
+U 1054 ; WX 871 ; N uni041E ; G 905
+U 1055 ; WX 944 ; N uni041F ; G 906
+U 1056 ; WX 752 ; N uni0420 ; G 907
+U 1057 ; WX 796 ; N uni0421 ; G 908
+U 1058 ; WX 744 ; N uni0422 ; G 909
+U 1059 ; WX 812 ; N uni0423 ; G 910
+U 1060 ; WX 949 ; N uni0424 ; G 911
+U 1061 ; WX 776 ; N uni0425 ; G 912
+U 1062 ; WX 966 ; N uni0426 ; G 913
+U 1063 ; WX 913 ; N uni0427 ; G 914
+U 1064 ; WX 1268 ; N uni0428 ; G 915
+U 1065 ; WX 1293 ; N uni0429 ; G 916
+U 1066 ; WX 957 ; N uni042A ; G 917
+U 1067 ; WX 1202 ; N uni042B ; G 918
+U 1068 ; WX 825 ; N uni042C ; G 919
+U 1069 ; WX 795 ; N uni042D ; G 920
+U 1070 ; WX 1287 ; N uni042E ; G 921
+U 1071 ; WX 882 ; N uni042F ; G 922
+U 1072 ; WX 648 ; N uni0430 ; G 923
+U 1073 ; WX 667 ; N uni0431 ; G 924
+U 1074 ; WX 695 ; N uni0432 ; G 925
+U 1075 ; WX 613 ; N uni0433 ; G 926
+U 1076 ; WX 667 ; N uni0434 ; G 927
+U 1077 ; WX 636 ; N uni0435 ; G 928
+U 1078 ; WX 1010 ; N uni0436 ; G 929
+U 1079 ; WX 638 ; N uni0437 ; G 930
+U 1080 ; WX 742 ; N uni0438 ; G 931
+U 1081 ; WX 742 ; N uni0439 ; G 932
+U 1082 ; WX 722 ; N uni043A ; G 933
+U 1083 ; WX 705 ; N uni043B ; G 934
+U 1084 ; WX 869 ; N uni043C ; G 935
+U 1085 ; WX 732 ; N uni043D ; G 936
+U 1086 ; WX 667 ; N uni043E ; G 937
+U 1087 ; WX 732 ; N uni043F ; G 938
+U 1088 ; WX 699 ; N uni0440 ; G 939
+U 1089 ; WX 609 ; N uni0441 ; G 940
+U 1090 ; WX 620 ; N uni0442 ; G 941
+U 1091 ; WX 640 ; N uni0443 ; G 942
+U 1092 ; WX 902 ; N uni0444 ; G 943
+U 1093 ; WX 596 ; N uni0445 ; G 944
+U 1094 ; WX 739 ; N uni0446 ; G 945
+U 1095 ; WX 732 ; N uni0447 ; G 946
+U 1096 ; WX 1075 ; N uni0448 ; G 947
+U 1097 ; WX 1082 ; N uni0449 ; G 948
+U 1098 ; WX 767 ; N uni044A ; G 949
+U 1099 ; WX 1002 ; N uni044B ; G 950
+U 1100 ; WX 679 ; N uni044C ; G 951
+U 1101 ; WX 609 ; N uni044D ; G 952
+U 1102 ; WX 1025 ; N uni044E ; G 953
+U 1103 ; WX 739 ; N uni044F ; G 954
+U 1104 ; WX 636 ; N uni0450 ; G 955
+U 1105 ; WX 636 ; N uni0451 ; G 956
+U 1106 ; WX 719 ; N uni0452 ; G 957
+U 1107 ; WX 613 ; N uni0453 ; G 958
+U 1108 ; WX 609 ; N uni0454 ; G 959
+U 1109 ; WX 563 ; N uni0455 ; G 960
+U 1110 ; WX 380 ; N uni0456 ; G 961
+U 1111 ; WX 380 ; N uni0457 ; G 962
+U 1112 ; WX 362 ; N uni0458 ; G 963
+U 1113 ; WX 988 ; N uni0459 ; G 964
+U 1114 ; WX 1015 ; N uni045A ; G 965
+U 1115 ; WX 727 ; N uni045B ; G 966
+U 1116 ; WX 722 ; N uni045C ; G 967
+U 1117 ; WX 742 ; N uni045D ; G 968
+U 1118 ; WX 640 ; N uni045E ; G 969
+U 1119 ; WX 732 ; N uni045F ; G 970
+U 1122 ; WX 880 ; N uni0462 ; G 971
+U 1123 ; WX 703 ; N uni0463 ; G 972
+U 1124 ; WX 1195 ; N uni0464 ; G 973
+U 1125 ; WX 963 ; N uni0465 ; G 974
+U 1130 ; WX 1312 ; N uni046A ; G 975
+U 1131 ; WX 1010 ; N uni046B ; G 976
+U 1132 ; WX 1630 ; N uni046C ; G 977
+U 1133 ; WX 1297 ; N uni046D ; G 978
+U 1136 ; WX 1096 ; N uni0470 ; G 979
+U 1137 ; WX 1105 ; N uni0471 ; G 980
+U 1138 ; WX 871 ; N uni0472 ; G 981
+U 1139 ; WX 652 ; N uni0473 ; G 982
+U 1140 ; WX 916 ; N uni0474 ; G 983
+U 1141 ; WX 749 ; N uni0475 ; G 984
+U 1142 ; WX 916 ; N uni0476 ; G 985
+U 1143 ; WX 749 ; N uni0477 ; G 986
+U 1164 ; WX 846 ; N uni048C ; G 987
+U 1165 ; WX 673 ; N uni048D ; G 988
+U 1168 ; WX 700 ; N uni0490 ; G 989
+U 1169 ; WX 618 ; N uni0491 ; G 990
+U 1170 ; WX 690 ; N uni0492 ; G 991
+U 1171 ; WX 613 ; N uni0493 ; G 992
+U 1172 ; WX 868 ; N uni0494 ; G 993
+U 1173 ; WX 716 ; N uni0495 ; G 994
+U 1174 ; WX 1312 ; N uni0496 ; G 995
+U 1175 ; WX 1010 ; N uni0497 ; G 996
+U 1176 ; WX 721 ; N uni0498 ; G 997
+U 1177 ; WX 638 ; N uni0499 ; G 998
+U 1178 ; WX 947 ; N uni049A ; G 999
+U 1179 ; WX 744 ; N uni049B ; G 1000
+U 1182 ; WX 910 ; N uni049E ; G 1001
+U 1183 ; WX 722 ; N uni049F ; G 1002
+U 1184 ; WX 1041 ; N uni04A0 ; G 1003
+U 1185 ; WX 827 ; N uni04A1 ; G 1004
+U 1186 ; WX 966 ; N uni04A2 ; G 1005
+U 1187 ; WX 739 ; N uni04A3 ; G 1006
+U 1188 ; WX 1167 ; N uni04A4 ; G 1007
+U 1189 ; WX 956 ; N uni04A5 ; G 1008
+U 1190 ; WX 1345 ; N uni04A6 ; G 1009
+U 1191 ; WX 1059 ; N uni04A7 ; G 1010
+U 1194 ; WX 796 ; N uni04AA ; G 1011
+U 1195 ; WX 609 ; N uni04AB ; G 1012
+U 1196 ; WX 744 ; N uni04AC ; G 1013
+U 1197 ; WX 620 ; N uni04AD ; G 1014
+U 1198 ; WX 714 ; N uni04AE ; G 1015
+U 1199 ; WX 581 ; N uni04AF ; G 1016
+U 1200 ; WX 714 ; N uni04B0 ; G 1017
+U 1201 ; WX 581 ; N uni04B1 ; G 1018
+U 1202 ; WX 866 ; N uni04B2 ; G 1019
+U 1203 ; WX 649 ; N uni04B3 ; G 1020
+U 1204 ; WX 1022 ; N uni04B4 ; G 1021
+U 1205 ; WX 807 ; N uni04B5 ; G 1022
+U 1206 ; WX 928 ; N uni04B6 ; G 1023
+U 1207 ; WX 739 ; N uni04B7 ; G 1024
+U 1210 ; WX 910 ; N uni04BA ; G 1025
+U 1211 ; WX 727 ; N uni04BB ; G 1026
+U 1216 ; WX 468 ; N uni04C0 ; G 1027
+U 1217 ; WX 1312 ; N uni04C1 ; G 1028
+U 1218 ; WX 1010 ; N uni04C2 ; G 1029
+U 1219 ; WX 869 ; N uni04C3 ; G 1030
+U 1220 ; WX 693 ; N uni04C4 ; G 1031
+U 1223 ; WX 945 ; N uni04C7 ; G 1032
+U 1224 ; WX 732 ; N uni04C8 ; G 1033
+U 1227 ; WX 913 ; N uni04CB ; G 1034
+U 1228 ; WX 732 ; N uni04CC ; G 1035
+U 1231 ; WX 380 ; N uni04CF ; G 1036
+U 1232 ; WX 814 ; N uni04D0 ; G 1037
+U 1233 ; WX 648 ; N uni04D1 ; G 1038
+U 1234 ; WX 814 ; N uni04D2 ; G 1039
+U 1235 ; WX 648 ; N uni04D3 ; G 1040
+U 1236 ; WX 1034 ; N uni04D4 ; G 1041
+U 1237 ; WX 975 ; N uni04D5 ; G 1042
+U 1238 ; WX 762 ; N uni04D6 ; G 1043
+U 1239 ; WX 636 ; N uni04D7 ; G 1044
+U 1240 ; WX 871 ; N uni04D8 ; G 1045
+U 1241 ; WX 636 ; N uni04D9 ; G 1046
+U 1242 ; WX 871 ; N uni04DA ; G 1047
+U 1243 ; WX 636 ; N uni04DB ; G 1048
+U 1244 ; WX 1312 ; N uni04DC ; G 1049
+U 1245 ; WX 1010 ; N uni04DD ; G 1050
+U 1246 ; WX 721 ; N uni04DE ; G 1051
+U 1247 ; WX 638 ; N uni04DF ; G 1052
+U 1248 ; WX 657 ; N uni04E0 ; G 1053
+U 1249 ; WX 568 ; N uni04E1 ; G 1054
+U 1250 ; WX 945 ; N uni04E2 ; G 1055
+U 1251 ; WX 742 ; N uni04E3 ; G 1056
+U 1252 ; WX 945 ; N uni04E4 ; G 1057
+U 1253 ; WX 742 ; N uni04E5 ; G 1058
+U 1254 ; WX 871 ; N uni04E6 ; G 1059
+U 1255 ; WX 667 ; N uni04E7 ; G 1060
+U 1256 ; WX 871 ; N uni04E8 ; G 1061
+U 1257 ; WX 667 ; N uni04E9 ; G 1062
+U 1258 ; WX 871 ; N uni04EA ; G 1063
+U 1259 ; WX 667 ; N uni04EB ; G 1064
+U 1260 ; WX 795 ; N uni04EC ; G 1065
+U 1261 ; WX 609 ; N uni04ED ; G 1066
+U 1262 ; WX 812 ; N uni04EE ; G 1067
+U 1263 ; WX 640 ; N uni04EF ; G 1068
+U 1264 ; WX 812 ; N uni04F0 ; G 1069
+U 1265 ; WX 640 ; N uni04F1 ; G 1070
+U 1266 ; WX 812 ; N uni04F2 ; G 1071
+U 1267 ; WX 640 ; N uni04F3 ; G 1072
+U 1268 ; WX 913 ; N uni04F4 ; G 1073
+U 1269 ; WX 732 ; N uni04F5 ; G 1074
+U 1270 ; WX 690 ; N uni04F6 ; G 1075
+U 1271 ; WX 613 ; N uni04F7 ; G 1076
+U 1272 ; WX 1202 ; N uni04F8 ; G 1077
+U 1273 ; WX 1002 ; N uni04F9 ; G 1078
+U 1296 ; WX 721 ; N uni0510 ; G 1079
+U 1297 ; WX 638 ; N uni0511 ; G 1080
+U 1298 ; WX 884 ; N uni0512 ; G 1081
+U 1299 ; WX 705 ; N uni0513 ; G 1082
+U 1300 ; WX 1248 ; N uni0514 ; G 1083
+U 1301 ; WX 945 ; N uni0515 ; G 1084
+U 1306 ; WX 820 ; N uni051A ; G 1085
+U 1307 ; WX 640 ; N uni051B ; G 1086
+U 1308 ; WX 1028 ; N uni051C ; G 1087
+U 1309 ; WX 856 ; N uni051D ; G 1088
+U 1329 ; WX 942 ; N uni0531 ; G 1089
+U 1330 ; WX 832 ; N uni0532 ; G 1090
+U 1331 ; WX 894 ; N uni0533 ; G 1091
+U 1332 ; WX 909 ; N uni0534 ; G 1092
+U 1333 ; WX 822 ; N uni0535 ; G 1093
+U 1334 ; WX 821 ; N uni0536 ; G 1094
+U 1335 ; WX 747 ; N uni0537 ; G 1095
+U 1336 ; WX 832 ; N uni0538 ; G 1096
+U 1337 ; WX 1125 ; N uni0539 ; G 1097
+U 1338 ; WX 894 ; N uni053A ; G 1098
+U 1339 ; WX 803 ; N uni053B ; G 1099
+U 1340 ; WX 722 ; N uni053C ; G 1100
+U 1341 ; WX 1188 ; N uni053D ; G 1101
+U 1342 ; WX 887 ; N uni053E ; G 1102
+U 1343 ; WX 842 ; N uni053F ; G 1103
+U 1344 ; WX 737 ; N uni0540 ; G 1104
+U 1345 ; WX 863 ; N uni0541 ; G 1105
+U 1346 ; WX 918 ; N uni0542 ; G 1106
+U 1347 ; WX 851 ; N uni0543 ; G 1107
+U 1348 ; WX 977 ; N uni0544 ; G 1108
+U 1349 ; WX 833 ; N uni0545 ; G 1109
+U 1350 ; WX 914 ; N uni0546 ; G 1110
+U 1351 ; WX 843 ; N uni0547 ; G 1111
+U 1352 ; WX 871 ; N uni0548 ; G 1112
+U 1353 ; WX 818 ; N uni0549 ; G 1113
+U 1354 ; WX 1034 ; N uni054A ; G 1114
+U 1355 ; WX 846 ; N uni054B ; G 1115
+U 1356 ; WX 964 ; N uni054C ; G 1116
+U 1357 ; WX 871 ; N uni054D ; G 1117
+U 1358 ; WX 914 ; N uni054E ; G 1118
+U 1359 ; WX 808 ; N uni054F ; G 1119
+U 1360 ; WX 808 ; N uni0550 ; G 1120
+U 1361 ; WX 836 ; N uni0551 ; G 1121
+U 1362 ; WX 710 ; N uni0552 ; G 1122
+U 1363 ; WX 955 ; N uni0553 ; G 1123
+U 1364 ; WX 891 ; N uni0554 ; G 1124
+U 1365 ; WX 871 ; N uni0555 ; G 1125
+U 1366 ; WX 963 ; N uni0556 ; G 1126
+U 1369 ; WX 307 ; N uni0559 ; G 1127
+U 1370 ; WX 264 ; N uni055A ; G 1128
+U 1371 ; WX 293 ; N uni055B ; G 1129
+U 1372 ; WX 391 ; N uni055C ; G 1130
+U 1373 ; WX 323 ; N uni055D ; G 1131
+U 1374 ; WX 439 ; N uni055E ; G 1132
+U 1375 ; WX 500 ; N uni055F ; G 1133
+U 1377 ; WX 1055 ; N uni0561 ; G 1134
+U 1378 ; WX 695 ; N uni0562 ; G 1135
+U 1379 ; WX 776 ; N uni0563 ; G 1136
+U 1380 ; WX 801 ; N uni0564 ; G 1137
+U 1381 ; WX 729 ; N uni0565 ; G 1138
+U 1382 ; WX 742 ; N uni0566 ; G 1139
+U 1383 ; WX 599 ; N uni0567 ; G 1140
+U 1384 ; WX 733 ; N uni0568 ; G 1141
+U 1385 ; WX 909 ; N uni0569 ; G 1142
+U 1386 ; WX 768 ; N uni056A ; G 1143
+U 1387 ; WX 724 ; N uni056B ; G 1144
+U 1388 ; WX 398 ; N uni056C ; G 1145
+U 1389 ; WX 1087 ; N uni056D ; G 1146
+U 1390 ; WX 695 ; N uni056E ; G 1147
+U 1391 ; WX 719 ; N uni056F ; G 1148
+U 1392 ; WX 737 ; N uni0570 ; G 1149
+U 1393 ; WX 684 ; N uni0571 ; G 1150
+U 1394 ; WX 738 ; N uni0572 ; G 1151
+U 1395 ; WX 703 ; N uni0573 ; G 1152
+U 1396 ; WX 724 ; N uni0574 ; G 1153
+U 1397 ; WX 359 ; N uni0575 ; G 1154
+U 1398 ; WX 719 ; N uni0576 ; G 1155
+U 1399 ; WX 496 ; N uni0577 ; G 1156
+U 1400 ; WX 738 ; N uni0578 ; G 1157
+U 1401 ; WX 428 ; N uni0579 ; G 1158
+U 1402 ; WX 1059 ; N uni057A ; G 1159
+U 1403 ; WX 668 ; N uni057B ; G 1160
+U 1404 ; WX 744 ; N uni057C ; G 1161
+U 1405 ; WX 724 ; N uni057D ; G 1162
+U 1406 ; WX 724 ; N uni057E ; G 1163
+U 1407 ; WX 1040 ; N uni057F ; G 1164
+U 1408 ; WX 724 ; N uni0580 ; G 1165
+U 1409 ; WX 713 ; N uni0581 ; G 1166
+U 1410 ; WX 493 ; N uni0582 ; G 1167
+U 1411 ; WX 1040 ; N uni0583 ; G 1168
+U 1412 ; WX 734 ; N uni0584 ; G 1169
+U 1413 ; WX 693 ; N uni0585 ; G 1170
+U 1414 ; WX 956 ; N uni0586 ; G 1171
+U 1415 ; WX 833 ; N uni0587 ; G 1172
+U 1417 ; WX 340 ; N uni0589 ; G 1173
+U 1418 ; WX 388 ; N uni058A ; G 1174
+U 3647 ; WX 696 ; N uni0E3F ; G 1175
+U 4256 ; WX 755 ; N uni10A0 ; G 1176
+U 4257 ; WX 936 ; N uni10A1 ; G 1177
+U 4258 ; WX 866 ; N uni10A2 ; G 1178
+U 4259 ; WX 874 ; N uni10A3 ; G 1179
+U 4260 ; WX 781 ; N uni10A4 ; G 1180
+U 4261 ; WX 1078 ; N uni10A5 ; G 1181
+U 4262 ; WX 1014 ; N uni10A6 ; G 1182
+U 4263 ; WX 1213 ; N uni10A7 ; G 1183
+U 4264 ; WX 643 ; N uni10A8 ; G 1184
+U 4265 ; WX 818 ; N uni10A9 ; G 1185
+U 4266 ; WX 1051 ; N uni10AA ; G 1186
+U 4267 ; WX 1051 ; N uni10AB ; G 1187
+U 4268 ; WX 796 ; N uni10AC ; G 1188
+U 4269 ; WX 1135 ; N uni10AD ; G 1189
+U 4270 ; WX 969 ; N uni10AE ; G 1190
+U 4271 ; WX 902 ; N uni10AF ; G 1191
+U 4272 ; WX 1109 ; N uni10B0 ; G 1192
+U 4273 ; WX 792 ; N uni10B1 ; G 1193
+U 4274 ; WX 756 ; N uni10B2 ; G 1194
+U 4275 ; WX 1076 ; N uni10B3 ; G 1195
+U 4276 ; WX 976 ; N uni10B4 ; G 1196
+U 4277 ; WX 1066 ; N uni10B5 ; G 1197
+U 4278 ; WX 811 ; N uni10B6 ; G 1198
+U 4279 ; WX 833 ; N uni10B7 ; G 1199
+U 4280 ; WX 821 ; N uni10B8 ; G 1200
+U 4281 ; WX 833 ; N uni10B9 ; G 1201
+U 4282 ; WX 908 ; N uni10BA ; G 1202
+U 4283 ; WX 1077 ; N uni10BB ; G 1203
+U 4284 ; WX 769 ; N uni10BC ; G 1204
+U 4285 ; WX 822 ; N uni10BD ; G 1205
+U 4286 ; WX 813 ; N uni10BE ; G 1206
+U 4287 ; WX 1111 ; N uni10BF ; G 1207
+U 4288 ; WX 1123 ; N uni10C0 ; G 1208
+U 4289 ; WX 802 ; N uni10C1 ; G 1209
+U 4290 ; WX 892 ; N uni10C2 ; G 1210
+U 4291 ; WX 802 ; N uni10C3 ; G 1211
+U 4292 ; WX 880 ; N uni10C4 ; G 1212
+U 4293 ; WX 1063 ; N uni10C5 ; G 1213
+U 4304 ; WX 594 ; N uni10D0 ; G 1214
+U 4305 ; WX 625 ; N uni10D1 ; G 1215
+U 4306 ; WX 643 ; N uni10D2 ; G 1216
+U 4307 ; WX 887 ; N uni10D3 ; G 1217
+U 4308 ; WX 615 ; N uni10D4 ; G 1218
+U 4309 ; WX 611 ; N uni10D5 ; G 1219
+U 4310 ; WX 667 ; N uni10D6 ; G 1220
+U 4311 ; WX 915 ; N uni10D7 ; G 1221
+U 4312 ; WX 613 ; N uni10D8 ; G 1222
+U 4313 ; WX 600 ; N uni10D9 ; G 1223
+U 4314 ; WX 1120 ; N uni10DA ; G 1224
+U 4315 ; WX 640 ; N uni10DB ; G 1225
+U 4316 ; WX 640 ; N uni10DC ; G 1226
+U 4317 ; WX 879 ; N uni10DD ; G 1227
+U 4318 ; WX 624 ; N uni10DE ; G 1228
+U 4319 ; WX 634 ; N uni10DF ; G 1229
+U 4320 ; WX 877 ; N uni10E0 ; G 1230
+U 4321 ; WX 666 ; N uni10E1 ; G 1231
+U 4322 ; WX 780 ; N uni10E2 ; G 1232
+U 4323 ; WX 751 ; N uni10E3 ; G 1233
+U 4324 ; WX 869 ; N uni10E4 ; G 1234
+U 4325 ; WX 639 ; N uni10E5 ; G 1235
+U 4326 ; WX 912 ; N uni10E6 ; G 1236
+U 4327 ; WX 622 ; N uni10E7 ; G 1237
+U 4328 ; WX 647 ; N uni10E8 ; G 1238
+U 4329 ; WX 640 ; N uni10E9 ; G 1239
+U 4330 ; WX 729 ; N uni10EA ; G 1240
+U 4331 ; WX 641 ; N uni10EB ; G 1241
+U 4332 ; WX 630 ; N uni10EC ; G 1242
+U 4333 ; WX 629 ; N uni10ED ; G 1243
+U 4334 ; WX 670 ; N uni10EE ; G 1244
+U 4335 ; WX 753 ; N uni10EF ; G 1245
+U 4336 ; WX 625 ; N uni10F0 ; G 1246
+U 4337 ; WX 657 ; N uni10F1 ; G 1247
+U 4338 ; WX 625 ; N uni10F2 ; G 1248
+U 4339 ; WX 625 ; N uni10F3 ; G 1249
+U 4340 ; WX 624 ; N uni10F4 ; G 1250
+U 4341 ; WX 670 ; N uni10F5 ; G 1251
+U 4342 ; WX 940 ; N uni10F6 ; G 1252
+U 4343 ; WX 680 ; N uni10F7 ; G 1253
+U 4344 ; WX 636 ; N uni10F8 ; G 1254
+U 4345 ; WX 672 ; N uni10F9 ; G 1255
+U 4346 ; WX 625 ; N uni10FA ; G 1256
+U 4347 ; WX 588 ; N uni10FB ; G 1257
+U 4348 ; WX 354 ; N uni10FC ; G 1258
+U 7424 ; WX 641 ; N uni1D00 ; G 1259
+U 7425 ; WX 892 ; N uni1D01 ; G 1260
+U 7426 ; WX 940 ; N uni1D02 ; G 1261
+U 7427 ; WX 695 ; N uni1D03 ; G 1262
+U 7428 ; WX 609 ; N uni1D04 ; G 1263
+U 7429 ; WX 675 ; N uni1D05 ; G 1264
+U 7430 ; WX 675 ; N uni1D06 ; G 1265
+U 7431 ; WX 617 ; N uni1D07 ; G 1266
+U 7432 ; WX 509 ; N uni1D08 ; G 1267
+U 7433 ; WX 320 ; N uni1D09 ; G 1268
+U 7434 ; WX 561 ; N uni1D0A ; G 1269
+U 7435 ; WX 722 ; N uni1D0B ; G 1270
+U 7436 ; WX 617 ; N uni1D0C ; G 1271
+U 7437 ; WX 869 ; N uni1D0D ; G 1272
+U 7438 ; WX 737 ; N uni1D0E ; G 1273
+U 7439 ; WX 667 ; N uni1D0F ; G 1274
+U 7440 ; WX 609 ; N uni1D10 ; G 1275
+U 7441 ; WX 628 ; N uni1D11 ; G 1276
+U 7442 ; WX 628 ; N uni1D12 ; G 1277
+U 7443 ; WX 667 ; N uni1D13 ; G 1278
+U 7444 ; WX 989 ; N uni1D14 ; G 1279
+U 7445 ; WX 598 ; N uni1D15 ; G 1280
+U 7446 ; WX 667 ; N uni1D16 ; G 1281
+U 7447 ; WX 667 ; N uni1D17 ; G 1282
+U 7448 ; WX 586 ; N uni1D18 ; G 1283
+U 7449 ; WX 801 ; N uni1D19 ; G 1284
+U 7450 ; WX 801 ; N uni1D1A ; G 1285
+U 7451 ; WX 620 ; N uni1D1B ; G 1286
+U 7452 ; WX 647 ; N uni1D1C ; G 1287
+U 7453 ; WX 664 ; N uni1D1D ; G 1288
+U 7454 ; WX 923 ; N uni1D1E ; G 1289
+U 7455 ; WX 655 ; N uni1D1F ; G 1290
+U 7456 ; WX 581 ; N uni1D20 ; G 1291
+U 7457 ; WX 861 ; N uni1D21 ; G 1292
+U 7458 ; WX 568 ; N uni1D22 ; G 1293
+U 7459 ; WX 568 ; N uni1D23 ; G 1294
+U 7460 ; WX 588 ; N uni1D24 ; G 1295
+U 7461 ; WX 802 ; N uni1D25 ; G 1296
+U 7462 ; WX 586 ; N uni1D26 ; G 1297
+U 7463 ; WX 641 ; N uni1D27 ; G 1298
+U 7464 ; WX 732 ; N uni1D28 ; G 1299
+U 7465 ; WX 586 ; N uni1D29 ; G 1300
+U 7466 ; WX 854 ; N uni1D2A ; G 1301
+U 7467 ; WX 705 ; N uni1D2B ; G 1302
+U 7468 ; WX 489 ; N uni1D2C ; G 1303
+U 7469 ; WX 651 ; N uni1D2D ; G 1304
+U 7470 ; WX 532 ; N uni1D2E ; G 1305
+U 7471 ; WX 532 ; N uni1D2F ; G 1306
+U 7472 ; WX 546 ; N uni1D30 ; G 1307
+U 7473 ; WX 480 ; N uni1D31 ; G 1308
+U 7474 ; WX 480 ; N uni1D32 ; G 1309
+U 7475 ; WX 538 ; N uni1D33 ; G 1310
+U 7476 ; WX 595 ; N uni1D34 ; G 1311
+U 7477 ; WX 294 ; N uni1D35 ; G 1312
+U 7478 ; WX 298 ; N uni1D36 ; G 1313
+U 7479 ; WX 547 ; N uni1D37 ; G 1314
+U 7480 ; WX 443 ; N uni1D38 ; G 1315
+U 7481 ; WX 697 ; N uni1D39 ; G 1316
+U 7482 ; WX 576 ; N uni1D3A ; G 1317
+U 7483 ; WX 606 ; N uni1D3B ; G 1318
+U 7484 ; WX 548 ; N uni1D3C ; G 1319
+U 7485 ; WX 442 ; N uni1D3D ; G 1320
+U 7486 ; WX 474 ; N uni1D3E ; G 1321
+U 7487 ; WX 523 ; N uni1D3F ; G 1322
+U 7488 ; WX 455 ; N uni1D40 ; G 1323
+U 7489 ; WX 469 ; N uni1D41 ; G 1324
+U 7490 ; WX 549 ; N uni1D42 ; G 1325
+U 7491 ; WX 466 ; N uni1D43 ; G 1326
+U 7492 ; WX 466 ; N uni1D44 ; G 1327
+U 7493 ; WX 498 ; N uni1D45 ; G 1328
+U 7494 ; WX 657 ; N uni1D46 ; G 1329
+U 7495 ; WX 499 ; N uni1D47 ; G 1330
+U 7496 ; WX 498 ; N uni1D48 ; G 1331
+U 7497 ; WX 444 ; N uni1D49 ; G 1332
+U 7498 ; WX 444 ; N uni1D4A ; G 1333
+U 7499 ; WX 412 ; N uni1D4B ; G 1334
+U 7500 ; WX 412 ; N uni1D4C ; G 1335
+U 7501 ; WX 498 ; N uni1D4D ; G 1336
+U 7502 ; WX 300 ; N uni1D4E ; G 1337
+U 7503 ; WX 523 ; N uni1D4F ; G 1338
+U 7504 ; WX 729 ; N uni1D50 ; G 1339
+U 7505 ; WX 473 ; N uni1D51 ; G 1340
+U 7506 ; WX 467 ; N uni1D52 ; G 1341
+U 7507 ; WX 427 ; N uni1D53 ; G 1342
+U 7508 ; WX 467 ; N uni1D54 ; G 1343
+U 7509 ; WX 467 ; N uni1D55 ; G 1344
+U 7510 ; WX 499 ; N uni1D56 ; G 1345
+U 7511 ; WX 371 ; N uni1D57 ; G 1346
+U 7512 ; WX 520 ; N uni1D58 ; G 1347
+U 7513 ; WX 418 ; N uni1D59 ; G 1348
+U 7514 ; WX 729 ; N uni1D5A ; G 1349
+U 7515 ; WX 491 ; N uni1D5B ; G 1350
+U 7516 ; WX 505 ; N uni1D5C ; G 1351
+U 7517 ; WX 418 ; N uni1D5D ; G 1352
+U 7518 ; WX 416 ; N uni1D5E ; G 1353
+U 7519 ; WX 420 ; N uni1D5F ; G 1354
+U 7520 ; WX 570 ; N uni1D60 ; G 1355
+U 7521 ; WX 414 ; N uni1D61 ; G 1356
+U 7522 ; WX 239 ; N uni1D62 ; G 1357
+U 7523 ; WX 414 ; N uni1D63 ; G 1358
+U 7524 ; WX 520 ; N uni1D64 ; G 1359
+U 7525 ; WX 491 ; N uni1D65 ; G 1360
+U 7526 ; WX 418 ; N uni1D66 ; G 1361
+U 7527 ; WX 416 ; N uni1D67 ; G 1362
+U 7528 ; WX 419 ; N uni1D68 ; G 1363
+U 7529 ; WX 570 ; N uni1D69 ; G 1364
+U 7530 ; WX 414 ; N uni1D6A ; G 1365
+U 7531 ; WX 1041 ; N uni1D6B ; G 1366
+U 7543 ; WX 640 ; N uni1D77 ; G 1367
+U 7544 ; WX 595 ; N uni1D78 ; G 1368
+U 7547 ; WX 380 ; N uni1D7B ; G 1369
+U 7548 ; WX 380 ; N uni1D7C ; G 1370
+U 7549 ; WX 699 ; N uni1D7D ; G 1371
+U 7550 ; WX 647 ; N uni1D7E ; G 1372
+U 7551 ; WX 679 ; N uni1D7F ; G 1373
+U 7557 ; WX 380 ; N uni1D85 ; G 1374
+U 7579 ; WX 498 ; N uni1D9B ; G 1375
+U 7580 ; WX 427 ; N uni1D9C ; G 1376
+U 7581 ; WX 427 ; N uni1D9D ; G 1377
+U 7582 ; WX 467 ; N uni1D9E ; G 1378
+U 7583 ; WX 412 ; N uni1D9F ; G 1379
+U 7584 ; WX 383 ; N uni1DA0 ; G 1380
+U 7585 ; WX 373 ; N uni1DA1 ; G 1381
+U 7586 ; WX 498 ; N uni1DA2 ; G 1382
+U 7587 ; WX 522 ; N uni1DA3 ; G 1383
+U 7588 ; WX 300 ; N uni1DA4 ; G 1384
+U 7589 ; WX 307 ; N uni1DA5 ; G 1385
+U 7590 ; WX 300 ; N uni1DA6 ; G 1386
+U 7591 ; WX 300 ; N uni1DA7 ; G 1387
+U 7592 ; WX 370 ; N uni1DA8 ; G 1388
+U 7593 ; WX 368 ; N uni1DA9 ; G 1389
+U 7594 ; WX 321 ; N uni1DAA ; G 1390
+U 7595 ; WX 430 ; N uni1DAB ; G 1391
+U 7596 ; WX 682 ; N uni1DAC ; G 1392
+U 7597 ; WX 729 ; N uni1DAD ; G 1393
+U 7598 ; WX 588 ; N uni1DAE ; G 1394
+U 7599 ; WX 587 ; N uni1DAF ; G 1395
+U 7600 ; WX 472 ; N uni1DB0 ; G 1396
+U 7601 ; WX 467 ; N uni1DB1 ; G 1397
+U 7602 ; WX 522 ; N uni1DB2 ; G 1398
+U 7603 ; WX 400 ; N uni1DB3 ; G 1399
+U 7604 ; WX 387 ; N uni1DB4 ; G 1400
+U 7605 ; WX 371 ; N uni1DB5 ; G 1401
+U 7606 ; WX 520 ; N uni1DB6 ; G 1402
+U 7607 ; WX 475 ; N uni1DB7 ; G 1403
+U 7608 ; WX 408 ; N uni1DB8 ; G 1404
+U 7609 ; WX 489 ; N uni1DB9 ; G 1405
+U 7610 ; WX 491 ; N uni1DBA ; G 1406
+U 7611 ; WX 412 ; N uni1DBB ; G 1407
+U 7612 ; WX 527 ; N uni1DBC ; G 1408
+U 7613 ; WX 412 ; N uni1DBD ; G 1409
+U 7614 ; WX 452 ; N uni1DBE ; G 1410
+U 7615 ; WX 467 ; N uni1DBF ; G 1411
+U 7620 ; WX 0 ; N uni1DC4 ; G 1412
+U 7621 ; WX 0 ; N uni1DC5 ; G 1413
+U 7622 ; WX 0 ; N uni1DC6 ; G 1414
+U 7623 ; WX 0 ; N uni1DC7 ; G 1415
+U 7624 ; WX 0 ; N uni1DC8 ; G 1416
+U 7625 ; WX 0 ; N uni1DC9 ; G 1417
+U 7680 ; WX 776 ; N uni1E00 ; G 1418
+U 7681 ; WX 648 ; N uni1E01 ; G 1419
+U 7682 ; WX 845 ; N uni1E02 ; G 1420
+U 7683 ; WX 699 ; N uni1E03 ; G 1421
+U 7684 ; WX 845 ; N uni1E04 ; G 1422
+U 7685 ; WX 699 ; N uni1E05 ; G 1423
+U 7686 ; WX 845 ; N uni1E06 ; G 1424
+U 7687 ; WX 699 ; N uni1E07 ; G 1425
+U 7688 ; WX 796 ; N uni1E08 ; G 1426
+U 7689 ; WX 609 ; N uni1E09 ; G 1427
+U 7690 ; WX 867 ; N uni1E0A ; G 1428
+U 7691 ; WX 699 ; N uni1E0B ; G 1429
+U 7692 ; WX 867 ; N uni1E0C ; G 1430
+U 7693 ; WX 699 ; N uni1E0D ; G 1431
+U 7694 ; WX 867 ; N uni1E0E ; G 1432
+U 7695 ; WX 699 ; N uni1E0F ; G 1433
+U 7696 ; WX 867 ; N uni1E10 ; G 1434
+U 7697 ; WX 699 ; N uni1E11 ; G 1435
+U 7698 ; WX 867 ; N uni1E12 ; G 1436
+U 7699 ; WX 699 ; N uni1E13 ; G 1437
+U 7700 ; WX 762 ; N uni1E14 ; G 1438
+U 7701 ; WX 636 ; N uni1E15 ; G 1439
+U 7702 ; WX 762 ; N uni1E16 ; G 1440
+U 7703 ; WX 636 ; N uni1E17 ; G 1441
+U 7704 ; WX 762 ; N uni1E18 ; G 1442
+U 7705 ; WX 636 ; N uni1E19 ; G 1443
+U 7706 ; WX 762 ; N uni1E1A ; G 1444
+U 7707 ; WX 636 ; N uni1E1B ; G 1445
+U 7708 ; WX 762 ; N uni1E1C ; G 1446
+U 7709 ; WX 636 ; N uni1E1D ; G 1447
+U 7710 ; WX 710 ; N uni1E1E ; G 1448
+U 7711 ; WX 430 ; N uni1E1F ; G 1449
+U 7712 ; WX 854 ; N uni1E20 ; G 1450
+U 7713 ; WX 699 ; N uni1E21 ; G 1451
+U 7714 ; WX 945 ; N uni1E22 ; G 1452
+U 7715 ; WX 727 ; N uni1E23 ; G 1453
+U 7716 ; WX 945 ; N uni1E24 ; G 1454
+U 7717 ; WX 727 ; N uni1E25 ; G 1455
+U 7718 ; WX 945 ; N uni1E26 ; G 1456
+U 7719 ; WX 727 ; N uni1E27 ; G 1457
+U 7720 ; WX 945 ; N uni1E28 ; G 1458
+U 7721 ; WX 727 ; N uni1E29 ; G 1459
+U 7722 ; WX 945 ; N uni1E2A ; G 1460
+U 7723 ; WX 727 ; N uni1E2B ; G 1461
+U 7724 ; WX 468 ; N uni1E2C ; G 1462
+U 7725 ; WX 380 ; N uni1E2D ; G 1463
+U 7726 ; WX 468 ; N uni1E2E ; G 1464
+U 7727 ; WX 380 ; N uni1E2F ; G 1465
+U 7728 ; WX 869 ; N uni1E30 ; G 1466
+U 7729 ; WX 693 ; N uni1E31 ; G 1467
+U 7730 ; WX 869 ; N uni1E32 ; G 1468
+U 7731 ; WX 693 ; N uni1E33 ; G 1469
+U 7732 ; WX 869 ; N uni1E34 ; G 1470
+U 7733 ; WX 693 ; N uni1E35 ; G 1471
+U 7734 ; WX 703 ; N uni1E36 ; G 1472
+U 7735 ; WX 380 ; N uni1E37 ; G 1473
+U 7736 ; WX 703 ; N uni1E38 ; G 1474
+U 7737 ; WX 380 ; N uni1E39 ; G 1475
+U 7738 ; WX 703 ; N uni1E3A ; G 1476
+U 7739 ; WX 380 ; N uni1E3B ; G 1477
+U 7740 ; WX 703 ; N uni1E3C ; G 1478
+U 7741 ; WX 380 ; N uni1E3D ; G 1479
+U 7742 ; WX 1107 ; N uni1E3E ; G 1480
+U 7743 ; WX 1058 ; N uni1E3F ; G 1481
+U 7744 ; WX 1107 ; N uni1E40 ; G 1482
+U 7745 ; WX 1058 ; N uni1E41 ; G 1483
+U 7746 ; WX 1107 ; N uni1E42 ; G 1484
+U 7747 ; WX 1058 ; N uni1E43 ; G 1485
+U 7748 ; WX 914 ; N uni1E44 ; G 1486
+U 7749 ; WX 727 ; N uni1E45 ; G 1487
+U 7750 ; WX 914 ; N uni1E46 ; G 1488
+U 7751 ; WX 727 ; N uni1E47 ; G 1489
+U 7752 ; WX 914 ; N uni1E48 ; G 1490
+U 7753 ; WX 727 ; N uni1E49 ; G 1491
+U 7754 ; WX 914 ; N uni1E4A ; G 1492
+U 7755 ; WX 727 ; N uni1E4B ; G 1493
+U 7756 ; WX 871 ; N uni1E4C ; G 1494
+U 7757 ; WX 667 ; N uni1E4D ; G 1495
+U 7758 ; WX 871 ; N uni1E4E ; G 1496
+U 7759 ; WX 667 ; N uni1E4F ; G 1497
+U 7760 ; WX 871 ; N uni1E50 ; G 1498
+U 7761 ; WX 667 ; N uni1E51 ; G 1499
+U 7762 ; WX 871 ; N uni1E52 ; G 1500
+U 7763 ; WX 667 ; N uni1E53 ; G 1501
+U 7764 ; WX 752 ; N uni1E54 ; G 1502
+U 7765 ; WX 699 ; N uni1E55 ; G 1503
+U 7766 ; WX 752 ; N uni1E56 ; G 1504
+U 7767 ; WX 699 ; N uni1E57 ; G 1505
+U 7768 ; WX 831 ; N uni1E58 ; G 1506
+U 7769 ; WX 527 ; N uni1E59 ; G 1507
+U 7770 ; WX 831 ; N uni1E5A ; G 1508
+U 7771 ; WX 527 ; N uni1E5B ; G 1509
+U 7772 ; WX 831 ; N uni1E5C ; G 1510
+U 7773 ; WX 527 ; N uni1E5D ; G 1511
+U 7774 ; WX 831 ; N uni1E5E ; G 1512
+U 7775 ; WX 527 ; N uni1E5F ; G 1513
+U 7776 ; WX 722 ; N uni1E60 ; G 1514
+U 7777 ; WX 563 ; N uni1E61 ; G 1515
+U 7778 ; WX 722 ; N uni1E62 ; G 1516
+U 7779 ; WX 563 ; N uni1E63 ; G 1517
+U 7780 ; WX 722 ; N uni1E64 ; G 1518
+U 7781 ; WX 563 ; N uni1E65 ; G 1519
+U 7782 ; WX 722 ; N uni1E66 ; G 1520
+U 7783 ; WX 563 ; N uni1E67 ; G 1521
+U 7784 ; WX 722 ; N uni1E68 ; G 1522
+U 7785 ; WX 563 ; N uni1E69 ; G 1523
+U 7786 ; WX 744 ; N uni1E6A ; G 1524
+U 7787 ; WX 462 ; N uni1E6B ; G 1525
+U 7788 ; WX 744 ; N uni1E6C ; G 1526
+U 7789 ; WX 462 ; N uni1E6D ; G 1527
+U 7790 ; WX 744 ; N uni1E6E ; G 1528
+U 7791 ; WX 462 ; N uni1E6F ; G 1529
+U 7792 ; WX 744 ; N uni1E70 ; G 1530
+U 7793 ; WX 462 ; N uni1E71 ; G 1531
+U 7794 ; WX 872 ; N uni1E72 ; G 1532
+U 7795 ; WX 727 ; N uni1E73 ; G 1533
+U 7796 ; WX 872 ; N uni1E74 ; G 1534
+U 7797 ; WX 727 ; N uni1E75 ; G 1535
+U 7798 ; WX 872 ; N uni1E76 ; G 1536
+U 7799 ; WX 727 ; N uni1E77 ; G 1537
+U 7800 ; WX 872 ; N uni1E78 ; G 1538
+U 7801 ; WX 727 ; N uni1E79 ; G 1539
+U 7802 ; WX 872 ; N uni1E7A ; G 1540
+U 7803 ; WX 727 ; N uni1E7B ; G 1541
+U 7804 ; WX 776 ; N uni1E7C ; G 1542
+U 7805 ; WX 581 ; N uni1E7D ; G 1543
+U 7806 ; WX 776 ; N uni1E7E ; G 1544
+U 7807 ; WX 581 ; N uni1E7F ; G 1545
+U 7808 ; WX 1123 ; N Wgrave ; G 1546
+U 7809 ; WX 861 ; N wgrave ; G 1547
+U 7810 ; WX 1123 ; N Wacute ; G 1548
+U 7811 ; WX 861 ; N wacute ; G 1549
+U 7812 ; WX 1123 ; N Wdieresis ; G 1550
+U 7813 ; WX 861 ; N wdieresis ; G 1551
+U 7814 ; WX 1123 ; N uni1E86 ; G 1552
+U 7815 ; WX 861 ; N uni1E87 ; G 1553
+U 7816 ; WX 1123 ; N uni1E88 ; G 1554
+U 7817 ; WX 861 ; N uni1E89 ; G 1555
+U 7818 ; WX 776 ; N uni1E8A ; G 1556
+U 7819 ; WX 596 ; N uni1E8B ; G 1557
+U 7820 ; WX 776 ; N uni1E8C ; G 1558
+U 7821 ; WX 596 ; N uni1E8D ; G 1559
+U 7822 ; WX 714 ; N uni1E8E ; G 1560
+U 7823 ; WX 581 ; N uni1E8F ; G 1561
+U 7824 ; WX 730 ; N uni1E90 ; G 1562
+U 7825 ; WX 568 ; N uni1E91 ; G 1563
+U 7826 ; WX 730 ; N uni1E92 ; G 1564
+U 7827 ; WX 568 ; N uni1E93 ; G 1565
+U 7828 ; WX 730 ; N uni1E94 ; G 1566
+U 7829 ; WX 568 ; N uni1E95 ; G 1567
+U 7830 ; WX 727 ; N uni1E96 ; G 1568
+U 7831 ; WX 462 ; N uni1E97 ; G 1569
+U 7832 ; WX 861 ; N uni1E98 ; G 1570
+U 7833 ; WX 581 ; N uni1E99 ; G 1571
+U 7834 ; WX 1014 ; N uni1E9A ; G 1572
+U 7835 ; WX 430 ; N uni1E9B ; G 1573
+U 7836 ; WX 430 ; N uni1E9C ; G 1574
+U 7837 ; WX 430 ; N uni1E9D ; G 1575
+U 7838 ; WX 947 ; N uni1E9E ; G 1576
+U 7839 ; WX 667 ; N uni1E9F ; G 1577
+U 7840 ; WX 776 ; N uni1EA0 ; G 1578
+U 7841 ; WX 648 ; N uni1EA1 ; G 1579
+U 7842 ; WX 776 ; N uni1EA2 ; G 1580
+U 7843 ; WX 648 ; N uni1EA3 ; G 1581
+U 7844 ; WX 776 ; N uni1EA4 ; G 1582
+U 7845 ; WX 648 ; N uni1EA5 ; G 1583
+U 7846 ; WX 776 ; N uni1EA6 ; G 1584
+U 7847 ; WX 648 ; N uni1EA7 ; G 1585
+U 7848 ; WX 776 ; N uni1EA8 ; G 1586
+U 7849 ; WX 648 ; N uni1EA9 ; G 1587
+U 7850 ; WX 776 ; N uni1EAA ; G 1588
+U 7851 ; WX 648 ; N uni1EAB ; G 1589
+U 7852 ; WX 776 ; N uni1EAC ; G 1590
+U 7853 ; WX 648 ; N uni1EAD ; G 1591
+U 7854 ; WX 776 ; N uni1EAE ; G 1592
+U 7855 ; WX 648 ; N uni1EAF ; G 1593
+U 7856 ; WX 776 ; N uni1EB0 ; G 1594
+U 7857 ; WX 648 ; N uni1EB1 ; G 1595
+U 7858 ; WX 776 ; N uni1EB2 ; G 1596
+U 7859 ; WX 648 ; N uni1EB3 ; G 1597
+U 7860 ; WX 776 ; N uni1EB4 ; G 1598
+U 7861 ; WX 648 ; N uni1EB5 ; G 1599
+U 7862 ; WX 776 ; N uni1EB6 ; G 1600
+U 7863 ; WX 648 ; N uni1EB7 ; G 1601
+U 7864 ; WX 762 ; N uni1EB8 ; G 1602
+U 7865 ; WX 636 ; N uni1EB9 ; G 1603
+U 7866 ; WX 762 ; N uni1EBA ; G 1604
+U 7867 ; WX 636 ; N uni1EBB ; G 1605
+U 7868 ; WX 762 ; N uni1EBC ; G 1606
+U 7869 ; WX 636 ; N uni1EBD ; G 1607
+U 7870 ; WX 762 ; N uni1EBE ; G 1608
+U 7871 ; WX 636 ; N uni1EBF ; G 1609
+U 7872 ; WX 762 ; N uni1EC0 ; G 1610
+U 7873 ; WX 636 ; N uni1EC1 ; G 1611
+U 7874 ; WX 762 ; N uni1EC2 ; G 1612
+U 7875 ; WX 636 ; N uni1EC3 ; G 1613
+U 7876 ; WX 762 ; N uni1EC4 ; G 1614
+U 7877 ; WX 636 ; N uni1EC5 ; G 1615
+U 7878 ; WX 762 ; N uni1EC6 ; G 1616
+U 7879 ; WX 636 ; N uni1EC7 ; G 1617
+U 7880 ; WX 468 ; N uni1EC8 ; G 1618
+U 7881 ; WX 380 ; N uni1EC9 ; G 1619
+U 7882 ; WX 468 ; N uni1ECA ; G 1620
+U 7883 ; WX 380 ; N uni1ECB ; G 1621
+U 7884 ; WX 871 ; N uni1ECC ; G 1622
+U 7885 ; WX 667 ; N uni1ECD ; G 1623
+U 7886 ; WX 871 ; N uni1ECE ; G 1624
+U 7887 ; WX 667 ; N uni1ECF ; G 1625
+U 7888 ; WX 871 ; N uni1ED0 ; G 1626
+U 7889 ; WX 667 ; N uni1ED1 ; G 1627
+U 7890 ; WX 871 ; N uni1ED2 ; G 1628
+U 7891 ; WX 667 ; N uni1ED3 ; G 1629
+U 7892 ; WX 871 ; N uni1ED4 ; G 1630
+U 7893 ; WX 667 ; N uni1ED5 ; G 1631
+U 7894 ; WX 871 ; N uni1ED6 ; G 1632
+U 7895 ; WX 667 ; N uni1ED7 ; G 1633
+U 7896 ; WX 871 ; N uni1ED8 ; G 1634
+U 7897 ; WX 667 ; N uni1ED9 ; G 1635
+U 7898 ; WX 871 ; N uni1EDA ; G 1636
+U 7899 ; WX 667 ; N uni1EDB ; G 1637
+U 7900 ; WX 871 ; N uni1EDC ; G 1638
+U 7901 ; WX 667 ; N uni1EDD ; G 1639
+U 7902 ; WX 871 ; N uni1EDE ; G 1640
+U 7903 ; WX 667 ; N uni1EDF ; G 1641
+U 7904 ; WX 871 ; N uni1EE0 ; G 1642
+U 7905 ; WX 667 ; N uni1EE1 ; G 1643
+U 7906 ; WX 871 ; N uni1EE2 ; G 1644
+U 7907 ; WX 667 ; N uni1EE3 ; G 1645
+U 7908 ; WX 872 ; N uni1EE4 ; G 1646
+U 7909 ; WX 727 ; N uni1EE5 ; G 1647
+U 7910 ; WX 872 ; N uni1EE6 ; G 1648
+U 7911 ; WX 727 ; N uni1EE7 ; G 1649
+U 7912 ; WX 872 ; N uni1EE8 ; G 1650
+U 7913 ; WX 727 ; N uni1EE9 ; G 1651
+U 7914 ; WX 872 ; N uni1EEA ; G 1652
+U 7915 ; WX 727 ; N uni1EEB ; G 1653
+U 7916 ; WX 872 ; N uni1EEC ; G 1654
+U 7917 ; WX 727 ; N uni1EED ; G 1655
+U 7918 ; WX 872 ; N uni1EEE ; G 1656
+U 7919 ; WX 727 ; N uni1EEF ; G 1657
+U 7920 ; WX 872 ; N uni1EF0 ; G 1658
+U 7921 ; WX 727 ; N uni1EF1 ; G 1659
+U 7922 ; WX 714 ; N Ygrave ; G 1660
+U 7923 ; WX 581 ; N ygrave ; G 1661
+U 7924 ; WX 714 ; N uni1EF4 ; G 1662
+U 7925 ; WX 581 ; N uni1EF5 ; G 1663
+U 7926 ; WX 714 ; N uni1EF6 ; G 1664
+U 7927 ; WX 581 ; N uni1EF7 ; G 1665
+U 7928 ; WX 714 ; N uni1EF8 ; G 1666
+U 7929 ; WX 581 ; N uni1EF9 ; G 1667
+U 7930 ; WX 1078 ; N uni1EFA ; G 1668
+U 7931 ; WX 701 ; N uni1EFB ; G 1669
+U 7936 ; WX 770 ; N uni1F00 ; G 1670
+U 7937 ; WX 770 ; N uni1F01 ; G 1671
+U 7938 ; WX 770 ; N uni1F02 ; G 1672
+U 7939 ; WX 770 ; N uni1F03 ; G 1673
+U 7940 ; WX 770 ; N uni1F04 ; G 1674
+U 7941 ; WX 770 ; N uni1F05 ; G 1675
+U 7942 ; WX 770 ; N uni1F06 ; G 1676
+U 7943 ; WX 770 ; N uni1F07 ; G 1677
+U 7944 ; WX 776 ; N uni1F08 ; G 1678
+U 7945 ; WX 776 ; N uni1F09 ; G 1679
+U 7946 ; WX 978 ; N uni1F0A ; G 1680
+U 7947 ; WX 978 ; N uni1F0B ; G 1681
+U 7948 ; WX 832 ; N uni1F0C ; G 1682
+U 7949 ; WX 849 ; N uni1F0D ; G 1683
+U 7950 ; WX 776 ; N uni1F0E ; G 1684
+U 7951 ; WX 776 ; N uni1F0F ; G 1685
+U 7952 ; WX 608 ; N uni1F10 ; G 1686
+U 7953 ; WX 608 ; N uni1F11 ; G 1687
+U 7954 ; WX 608 ; N uni1F12 ; G 1688
+U 7955 ; WX 608 ; N uni1F13 ; G 1689
+U 7956 ; WX 608 ; N uni1F14 ; G 1690
+U 7957 ; WX 608 ; N uni1F15 ; G 1691
+U 7960 ; WX 917 ; N uni1F18 ; G 1692
+U 7961 ; WX 909 ; N uni1F19 ; G 1693
+U 7962 ; WX 1169 ; N uni1F1A ; G 1694
+U 7963 ; WX 1169 ; N uni1F1B ; G 1695
+U 7964 ; WX 1093 ; N uni1F1C ; G 1696
+U 7965 ; WX 1120 ; N uni1F1D ; G 1697
+U 7968 ; WX 727 ; N uni1F20 ; G 1698
+U 7969 ; WX 727 ; N uni1F21 ; G 1699
+U 7970 ; WX 727 ; N uni1F22 ; G 1700
+U 7971 ; WX 727 ; N uni1F23 ; G 1701
+U 7972 ; WX 727 ; N uni1F24 ; G 1702
+U 7973 ; WX 727 ; N uni1F25 ; G 1703
+U 7974 ; WX 727 ; N uni1F26 ; G 1704
+U 7975 ; WX 727 ; N uni1F27 ; G 1705
+U 7976 ; WX 1100 ; N uni1F28 ; G 1706
+U 7977 ; WX 1094 ; N uni1F29 ; G 1707
+U 7978 ; WX 1358 ; N uni1F2A ; G 1708
+U 7979 ; WX 1361 ; N uni1F2B ; G 1709
+U 7980 ; WX 1279 ; N uni1F2C ; G 1710
+U 7981 ; WX 1308 ; N uni1F2D ; G 1711
+U 7982 ; WX 1197 ; N uni1F2E ; G 1712
+U 7983 ; WX 1194 ; N uni1F2F ; G 1713
+U 7984 ; WX 484 ; N uni1F30 ; G 1714
+U 7985 ; WX 484 ; N uni1F31 ; G 1715
+U 7986 ; WX 484 ; N uni1F32 ; G 1716
+U 7987 ; WX 484 ; N uni1F33 ; G 1717
+U 7988 ; WX 484 ; N uni1F34 ; G 1718
+U 7989 ; WX 484 ; N uni1F35 ; G 1719
+U 7990 ; WX 484 ; N uni1F36 ; G 1720
+U 7991 ; WX 484 ; N uni1F37 ; G 1721
+U 7992 ; WX 629 ; N uni1F38 ; G 1722
+U 7993 ; WX 617 ; N uni1F39 ; G 1723
+U 7994 ; WX 878 ; N uni1F3A ; G 1724
+U 7995 ; WX 881 ; N uni1F3B ; G 1725
+U 7996 ; WX 799 ; N uni1F3C ; G 1726
+U 7997 ; WX 831 ; N uni1F3D ; G 1727
+U 7998 ; WX 723 ; N uni1F3E ; G 1728
+U 7999 ; WX 714 ; N uni1F3F ; G 1729
+U 8000 ; WX 667 ; N uni1F40 ; G 1730
+U 8001 ; WX 667 ; N uni1F41 ; G 1731
+U 8002 ; WX 667 ; N uni1F42 ; G 1732
+U 8003 ; WX 667 ; N uni1F43 ; G 1733
+U 8004 ; WX 667 ; N uni1F44 ; G 1734
+U 8005 ; WX 667 ; N uni1F45 ; G 1735
+U 8008 ; WX 900 ; N uni1F48 ; G 1736
+U 8009 ; WX 935 ; N uni1F49 ; G 1737
+U 8010 ; WX 1240 ; N uni1F4A ; G 1738
+U 8011 ; WX 1237 ; N uni1F4B ; G 1739
+U 8012 ; WX 1035 ; N uni1F4C ; G 1740
+U 8013 ; WX 1066 ; N uni1F4D ; G 1741
+U 8016 ; WX 694 ; N uni1F50 ; G 1742
+U 8017 ; WX 694 ; N uni1F51 ; G 1743
+U 8018 ; WX 694 ; N uni1F52 ; G 1744
+U 8019 ; WX 694 ; N uni1F53 ; G 1745
+U 8020 ; WX 694 ; N uni1F54 ; G 1746
+U 8021 ; WX 694 ; N uni1F55 ; G 1747
+U 8022 ; WX 694 ; N uni1F56 ; G 1748
+U 8023 ; WX 694 ; N uni1F57 ; G 1749
+U 8025 ; WX 922 ; N uni1F59 ; G 1750
+U 8027 ; WX 1186 ; N uni1F5B ; G 1751
+U 8029 ; WX 1133 ; N uni1F5D ; G 1752
+U 8031 ; WX 1019 ; N uni1F5F ; G 1753
+U 8032 ; WX 952 ; N uni1F60 ; G 1754
+U 8033 ; WX 952 ; N uni1F61 ; G 1755
+U 8034 ; WX 952 ; N uni1F62 ; G 1756
+U 8035 ; WX 952 ; N uni1F63 ; G 1757
+U 8036 ; WX 952 ; N uni1F64 ; G 1758
+U 8037 ; WX 952 ; N uni1F65 ; G 1759
+U 8038 ; WX 952 ; N uni1F66 ; G 1760
+U 8039 ; WX 952 ; N uni1F67 ; G 1761
+U 8040 ; WX 931 ; N uni1F68 ; G 1762
+U 8041 ; WX 963 ; N uni1F69 ; G 1763
+U 8042 ; WX 1268 ; N uni1F6A ; G 1764
+U 8043 ; WX 1274 ; N uni1F6B ; G 1765
+U 8044 ; WX 1054 ; N uni1F6C ; G 1766
+U 8045 ; WX 1088 ; N uni1F6D ; G 1767
+U 8046 ; WX 1023 ; N uni1F6E ; G 1768
+U 8047 ; WX 1060 ; N uni1F6F ; G 1769
+U 8048 ; WX 770 ; N uni1F70 ; G 1770
+U 8049 ; WX 770 ; N uni1F71 ; G 1771
+U 8050 ; WX 608 ; N uni1F72 ; G 1772
+U 8051 ; WX 608 ; N uni1F73 ; G 1773
+U 8052 ; WX 727 ; N uni1F74 ; G 1774
+U 8053 ; WX 727 ; N uni1F75 ; G 1775
+U 8054 ; WX 484 ; N uni1F76 ; G 1776
+U 8055 ; WX 484 ; N uni1F77 ; G 1777
+U 8056 ; WX 667 ; N uni1F78 ; G 1778
+U 8057 ; WX 667 ; N uni1F79 ; G 1779
+U 8058 ; WX 694 ; N uni1F7A ; G 1780
+U 8059 ; WX 694 ; N uni1F7B ; G 1781
+U 8060 ; WX 952 ; N uni1F7C ; G 1782
+U 8061 ; WX 952 ; N uni1F7D ; G 1783
+U 8064 ; WX 770 ; N uni1F80 ; G 1784
+U 8065 ; WX 770 ; N uni1F81 ; G 1785
+U 8066 ; WX 770 ; N uni1F82 ; G 1786
+U 8067 ; WX 770 ; N uni1F83 ; G 1787
+U 8068 ; WX 770 ; N uni1F84 ; G 1788
+U 8069 ; WX 770 ; N uni1F85 ; G 1789
+U 8070 ; WX 770 ; N uni1F86 ; G 1790
+U 8071 ; WX 770 ; N uni1F87 ; G 1791
+U 8072 ; WX 776 ; N uni1F88 ; G 1792
+U 8073 ; WX 776 ; N uni1F89 ; G 1793
+U 8074 ; WX 978 ; N uni1F8A ; G 1794
+U 8075 ; WX 978 ; N uni1F8B ; G 1795
+U 8076 ; WX 832 ; N uni1F8C ; G 1796
+U 8077 ; WX 849 ; N uni1F8D ; G 1797
+U 8078 ; WX 776 ; N uni1F8E ; G 1798
+U 8079 ; WX 776 ; N uni1F8F ; G 1799
+U 8080 ; WX 727 ; N uni1F90 ; G 1800
+U 8081 ; WX 727 ; N uni1F91 ; G 1801
+U 8082 ; WX 727 ; N uni1F92 ; G 1802
+U 8083 ; WX 727 ; N uni1F93 ; G 1803
+U 8084 ; WX 727 ; N uni1F94 ; G 1804
+U 8085 ; WX 727 ; N uni1F95 ; G 1805
+U 8086 ; WX 727 ; N uni1F96 ; G 1806
+U 8087 ; WX 727 ; N uni1F97 ; G 1807
+U 8088 ; WX 1100 ; N uni1F98 ; G 1808
+U 8089 ; WX 1094 ; N uni1F99 ; G 1809
+U 8090 ; WX 1358 ; N uni1F9A ; G 1810
+U 8091 ; WX 1361 ; N uni1F9B ; G 1811
+U 8092 ; WX 1279 ; N uni1F9C ; G 1812
+U 8093 ; WX 1308 ; N uni1F9D ; G 1813
+U 8094 ; WX 1197 ; N uni1F9E ; G 1814
+U 8095 ; WX 1194 ; N uni1F9F ; G 1815
+U 8096 ; WX 952 ; N uni1FA0 ; G 1816
+U 8097 ; WX 952 ; N uni1FA1 ; G 1817
+U 8098 ; WX 952 ; N uni1FA2 ; G 1818
+U 8099 ; WX 952 ; N uni1FA3 ; G 1819
+U 8100 ; WX 952 ; N uni1FA4 ; G 1820
+U 8101 ; WX 952 ; N uni1FA5 ; G 1821
+U 8102 ; WX 952 ; N uni1FA6 ; G 1822
+U 8103 ; WX 952 ; N uni1FA7 ; G 1823
+U 8104 ; WX 931 ; N uni1FA8 ; G 1824
+U 8105 ; WX 963 ; N uni1FA9 ; G 1825
+U 8106 ; WX 1268 ; N uni1FAA ; G 1826
+U 8107 ; WX 1274 ; N uni1FAB ; G 1827
+U 8108 ; WX 1054 ; N uni1FAC ; G 1828
+U 8109 ; WX 1088 ; N uni1FAD ; G 1829
+U 8110 ; WX 1023 ; N uni1FAE ; G 1830
+U 8111 ; WX 1060 ; N uni1FAF ; G 1831
+U 8112 ; WX 770 ; N uni1FB0 ; G 1832
+U 8113 ; WX 770 ; N uni1FB1 ; G 1833
+U 8114 ; WX 770 ; N uni1FB2 ; G 1834
+U 8115 ; WX 770 ; N uni1FB3 ; G 1835
+U 8116 ; WX 770 ; N uni1FB4 ; G 1836
+U 8118 ; WX 770 ; N uni1FB6 ; G 1837
+U 8119 ; WX 770 ; N uni1FB7 ; G 1838
+U 8120 ; WX 776 ; N uni1FB8 ; G 1839
+U 8121 ; WX 776 ; N uni1FB9 ; G 1840
+U 8122 ; WX 811 ; N uni1FBA ; G 1841
+U 8123 ; WX 776 ; N uni1FBB ; G 1842
+U 8124 ; WX 776 ; N uni1FBC ; G 1843
+U 8125 ; WX 500 ; N uni1FBD ; G 1844
+U 8126 ; WX 500 ; N uni1FBE ; G 1845
+U 8127 ; WX 500 ; N uni1FBF ; G 1846
+U 8128 ; WX 500 ; N uni1FC0 ; G 1847
+U 8129 ; WX 500 ; N uni1FC1 ; G 1848
+U 8130 ; WX 727 ; N uni1FC2 ; G 1849
+U 8131 ; WX 727 ; N uni1FC3 ; G 1850
+U 8132 ; WX 727 ; N uni1FC4 ; G 1851
+U 8134 ; WX 727 ; N uni1FC6 ; G 1852
+U 8135 ; WX 727 ; N uni1FC7 ; G 1853
+U 8136 ; WX 1000 ; N uni1FC8 ; G 1854
+U 8137 ; WX 947 ; N uni1FC9 ; G 1855
+U 8138 ; WX 1191 ; N uni1FCA ; G 1856
+U 8139 ; WX 1118 ; N uni1FCB ; G 1857
+U 8140 ; WX 945 ; N uni1FCC ; G 1858
+U 8141 ; WX 500 ; N uni1FCD ; G 1859
+U 8142 ; WX 500 ; N uni1FCE ; G 1860
+U 8143 ; WX 500 ; N uni1FCF ; G 1861
+U 8144 ; WX 484 ; N uni1FD0 ; G 1862
+U 8145 ; WX 484 ; N uni1FD1 ; G 1863
+U 8146 ; WX 484 ; N uni1FD2 ; G 1864
+U 8147 ; WX 484 ; N uni1FD3 ; G 1865
+U 8150 ; WX 484 ; N uni1FD6 ; G 1866
+U 8151 ; WX 484 ; N uni1FD7 ; G 1867
+U 8152 ; WX 468 ; N uni1FD8 ; G 1868
+U 8153 ; WX 468 ; N uni1FD9 ; G 1869
+U 8154 ; WX 714 ; N uni1FDA ; G 1870
+U 8155 ; WX 662 ; N uni1FDB ; G 1871
+U 8157 ; WX 500 ; N uni1FDD ; G 1872
+U 8158 ; WX 500 ; N uni1FDE ; G 1873
+U 8159 ; WX 500 ; N uni1FDF ; G 1874
+U 8160 ; WX 694 ; N uni1FE0 ; G 1875
+U 8161 ; WX 694 ; N uni1FE1 ; G 1876
+U 8162 ; WX 694 ; N uni1FE2 ; G 1877
+U 8163 ; WX 694 ; N uni1FE3 ; G 1878
+U 8164 ; WX 665 ; N uni1FE4 ; G 1879
+U 8165 ; WX 665 ; N uni1FE5 ; G 1880
+U 8166 ; WX 694 ; N uni1FE6 ; G 1881
+U 8167 ; WX 694 ; N uni1FE7 ; G 1882
+U 8168 ; WX 714 ; N uni1FE8 ; G 1883
+U 8169 ; WX 714 ; N uni1FE9 ; G 1884
+U 8170 ; WX 1019 ; N uni1FEA ; G 1885
+U 8171 ; WX 953 ; N uni1FEB ; G 1886
+U 8172 ; WX 910 ; N uni1FEC ; G 1887
+U 8173 ; WX 500 ; N uni1FED ; G 1888
+U 8174 ; WX 500 ; N uni1FEE ; G 1889
+U 8175 ; WX 500 ; N uni1FEF ; G 1890
+U 8178 ; WX 952 ; N uni1FF2 ; G 1891
+U 8179 ; WX 952 ; N uni1FF3 ; G 1892
+U 8180 ; WX 952 ; N uni1FF4 ; G 1893
+U 8182 ; WX 952 ; N uni1FF6 ; G 1894
+U 8183 ; WX 952 ; N uni1FF7 ; G 1895
+U 8184 ; WX 1069 ; N uni1FF8 ; G 1896
+U 8185 ; WX 887 ; N uni1FF9 ; G 1897
+U 8186 ; WX 1101 ; N uni1FFA ; G 1898
+U 8187 ; WX 911 ; N uni1FFB ; G 1899
+U 8188 ; WX 890 ; N uni1FFC ; G 1900
+U 8189 ; WX 500 ; N uni1FFD ; G 1901
+U 8190 ; WX 500 ; N uni1FFE ; G 1902
+U 8192 ; WX 500 ; N uni2000 ; G 1903
+U 8193 ; WX 1000 ; N uni2001 ; G 1904
+U 8194 ; WX 500 ; N uni2002 ; G 1905
+U 8195 ; WX 1000 ; N uni2003 ; G 1906
+U 8196 ; WX 330 ; N uni2004 ; G 1907
+U 8197 ; WX 250 ; N uni2005 ; G 1908
+U 8198 ; WX 167 ; N uni2006 ; G 1909
+U 8199 ; WX 696 ; N uni2007 ; G 1910
+U 8200 ; WX 348 ; N uni2008 ; G 1911
+U 8201 ; WX 200 ; N uni2009 ; G 1912
+U 8202 ; WX 100 ; N uni200A ; G 1913
+U 8203 ; WX 0 ; N uni200B ; G 1914
+U 8204 ; WX 0 ; N uni200C ; G 1915
+U 8205 ; WX 0 ; N uni200D ; G 1916
+U 8206 ; WX 0 ; N uni200E ; G 1917
+U 8207 ; WX 0 ; N uni200F ; G 1918
+U 8208 ; WX 415 ; N uni2010 ; G 1919
+U 8209 ; WX 415 ; N uni2011 ; G 1920
+U 8210 ; WX 696 ; N figuredash ; G 1921
+U 8211 ; WX 500 ; N endash ; G 1922
+U 8212 ; WX 1000 ; N emdash ; G 1923
+U 8213 ; WX 1000 ; N uni2015 ; G 1924
+U 8214 ; WX 500 ; N uni2016 ; G 1925
+U 8215 ; WX 500 ; N underscoredbl ; G 1926
+U 8216 ; WX 348 ; N quoteleft ; G 1927
+U 8217 ; WX 348 ; N quoteright ; G 1928
+U 8218 ; WX 348 ; N quotesinglbase ; G 1929
+U 8219 ; WX 348 ; N quotereversed ; G 1930
+U 8220 ; WX 575 ; N quotedblleft ; G 1931
+U 8221 ; WX 575 ; N quotedblright ; G 1932
+U 8222 ; WX 575 ; N quotedblbase ; G 1933
+U 8223 ; WX 575 ; N uni201F ; G 1934
+U 8224 ; WX 523 ; N dagger ; G 1935
+U 8225 ; WX 523 ; N daggerdbl ; G 1936
+U 8226 ; WX 639 ; N bullet ; G 1937
+U 8227 ; WX 639 ; N uni2023 ; G 1938
+U 8228 ; WX 348 ; N onedotenleader ; G 1939
+U 8229 ; WX 674 ; N twodotenleader ; G 1940
+U 8230 ; WX 1000 ; N ellipsis ; G 1941
+U 8234 ; WX 0 ; N uni202A ; G 1942
+U 8235 ; WX 0 ; N uni202B ; G 1943
+U 8236 ; WX 0 ; N uni202C ; G 1944
+U 8237 ; WX 0 ; N uni202D ; G 1945
+U 8238 ; WX 0 ; N uni202E ; G 1946
+U 8239 ; WX 200 ; N uni202F ; G 1947
+U 8240 ; WX 1385 ; N perthousand ; G 1948
+U 8241 ; WX 1820 ; N uni2031 ; G 1949
+U 8242 ; WX 264 ; N minute ; G 1950
+U 8243 ; WX 447 ; N second ; G 1951
+U 8244 ; WX 630 ; N uni2034 ; G 1952
+U 8245 ; WX 264 ; N uni2035 ; G 1953
+U 8246 ; WX 447 ; N uni2036 ; G 1954
+U 8247 ; WX 630 ; N uni2037 ; G 1955
+U 8248 ; WX 733 ; N uni2038 ; G 1956
+U 8249 ; WX 400 ; N guilsinglleft ; G 1957
+U 8250 ; WX 400 ; N guilsinglright ; G 1958
+U 8252 ; WX 629 ; N exclamdbl ; G 1959
+U 8253 ; WX 586 ; N uni203D ; G 1960
+U 8254 ; WX 500 ; N uni203E ; G 1961
+U 8258 ; WX 1023 ; N uni2042 ; G 1962
+U 8260 ; WX 167 ; N fraction ; G 1963
+U 8261 ; WX 473 ; N uni2045 ; G 1964
+U 8262 ; WX 473 ; N uni2046 ; G 1965
+U 8263 ; WX 1082 ; N uni2047 ; G 1966
+U 8264 ; WX 856 ; N uni2048 ; G 1967
+U 8265 ; WX 856 ; N uni2049 ; G 1968
+U 8267 ; WX 636 ; N uni204B ; G 1969
+U 8268 ; WX 500 ; N uni204C ; G 1970
+U 8269 ; WX 500 ; N uni204D ; G 1971
+U 8270 ; WX 523 ; N uni204E ; G 1972
+U 8271 ; WX 369 ; N uni204F ; G 1973
+U 8273 ; WX 523 ; N uni2051 ; G 1974
+U 8274 ; WX 556 ; N uni2052 ; G 1975
+U 8275 ; WX 1000 ; N uni2053 ; G 1976
+U 8279 ; WX 813 ; N uni2057 ; G 1977
+U 8287 ; WX 222 ; N uni205F ; G 1978
+U 8288 ; WX 0 ; N uni2060 ; G 1979
+U 8289 ; WX 0 ; N uni2061 ; G 1980
+U 8290 ; WX 0 ; N uni2062 ; G 1981
+U 8291 ; WX 0 ; N uni2063 ; G 1982
+U 8292 ; WX 0 ; N uni2064 ; G 1983
+U 8298 ; WX 0 ; N uni206A ; G 1984
+U 8299 ; WX 0 ; N uni206B ; G 1985
+U 8300 ; WX 0 ; N uni206C ; G 1986
+U 8301 ; WX 0 ; N uni206D ; G 1987
+U 8302 ; WX 0 ; N uni206E ; G 1988
+U 8303 ; WX 0 ; N uni206F ; G 1989
+U 8304 ; WX 438 ; N uni2070 ; G 1990
+U 8305 ; WX 239 ; N uni2071 ; G 1991
+U 8308 ; WX 438 ; N uni2074 ; G 1992
+U 8309 ; WX 438 ; N uni2075 ; G 1993
+U 8310 ; WX 438 ; N uni2076 ; G 1994
+U 8311 ; WX 438 ; N uni2077 ; G 1995
+U 8312 ; WX 438 ; N uni2078 ; G 1996
+U 8313 ; WX 438 ; N uni2079 ; G 1997
+U 8314 ; WX 528 ; N uni207A ; G 1998
+U 8315 ; WX 528 ; N uni207B ; G 1999
+U 8316 ; WX 528 ; N uni207C ; G 2000
+U 8317 ; WX 298 ; N uni207D ; G 2001
+U 8318 ; WX 298 ; N uni207E ; G 2002
+U 8319 ; WX 519 ; N uni207F ; G 2003
+U 8320 ; WX 438 ; N uni2080 ; G 2004
+U 8321 ; WX 438 ; N uni2081 ; G 2005
+U 8322 ; WX 438 ; N uni2082 ; G 2006
+U 8323 ; WX 438 ; N uni2083 ; G 2007
+U 8324 ; WX 438 ; N uni2084 ; G 2008
+U 8325 ; WX 438 ; N uni2085 ; G 2009
+U 8326 ; WX 438 ; N uni2086 ; G 2010
+U 8327 ; WX 438 ; N uni2087 ; G 2011
+U 8328 ; WX 438 ; N uni2088 ; G 2012
+U 8329 ; WX 438 ; N uni2089 ; G 2013
+U 8330 ; WX 528 ; N uni208A ; G 2014
+U 8331 ; WX 528 ; N uni208B ; G 2015
+U 8332 ; WX 528 ; N uni208C ; G 2016
+U 8333 ; WX 298 ; N uni208D ; G 2017
+U 8334 ; WX 298 ; N uni208E ; G 2018
+U 8336 ; WX 466 ; N uni2090 ; G 2019
+U 8337 ; WX 444 ; N uni2091 ; G 2020
+U 8338 ; WX 467 ; N uni2092 ; G 2021
+U 8339 ; WX 475 ; N uni2093 ; G 2022
+U 8340 ; WX 444 ; N uni2094 ; G 2023
+U 8341 ; WX 521 ; N uni2095 ; G 2024
+U 8342 ; WX 523 ; N uni2096 ; G 2025
+U 8343 ; WX 292 ; N uni2097 ; G 2026
+U 8344 ; WX 729 ; N uni2098 ; G 2027
+U 8345 ; WX 519 ; N uni2099 ; G 2028
+U 8346 ; WX 499 ; N uni209A ; G 2029
+U 8347 ; WX 395 ; N uni209B ; G 2030
+U 8348 ; WX 371 ; N uni209C ; G 2031
+U 8358 ; WX 696 ; N uni20A6 ; G 2032
+U 8364 ; WX 696 ; N Euro ; G 2033
+U 8367 ; WX 1155 ; N uni20AF ; G 2034
+U 8369 ; WX 790 ; N uni20B1 ; G 2035
+U 8372 ; WX 876 ; N uni20B4 ; G 2036
+U 8373 ; WX 696 ; N uni20B5 ; G 2037
+U 8376 ; WX 696 ; N uni20B8 ; G 2038
+U 8377 ; WX 696 ; N uni20B9 ; G 2039
+U 8378 ; WX 696 ; N uni20BA ; G 2040
+U 8381 ; WX 696 ; N uni20BD ; G 2041
+U 8451 ; WX 1198 ; N uni2103 ; G 2042
+U 8457 ; WX 1112 ; N uni2109 ; G 2043
+U 8462 ; WX 727 ; N uni210E ; G 2044
+U 8463 ; WX 727 ; N uni210F ; G 2045
+U 8470 ; WX 1087 ; N uni2116 ; G 2046
+U 8482 ; WX 1000 ; N trademark ; G 2047
+U 8486 ; WX 890 ; N uni2126 ; G 2048
+U 8487 ; WX 890 ; N uni2127 ; G 2049
+U 8490 ; WX 869 ; N uni212A ; G 2050
+U 8491 ; WX 776 ; N uni212B ; G 2051
+U 8498 ; WX 710 ; N uni2132 ; G 2052
+U 8513 ; WX 775 ; N uni2141 ; G 2053
+U 8514 ; WX 557 ; N uni2142 ; G 2054
+U 8515 ; WX 637 ; N uni2143 ; G 2055
+U 8516 ; WX 760 ; N uni2144 ; G 2056
+U 8523 ; WX 903 ; N uni214B ; G 2057
+U 8526 ; WX 592 ; N uni214E ; G 2058
+U 8528 ; WX 1035 ; N uni2150 ; G 2059
+U 8529 ; WX 1035 ; N uni2151 ; G 2060
+U 8530 ; WX 1473 ; N uni2152 ; G 2061
+U 8531 ; WX 1035 ; N onethird ; G 2062
+U 8532 ; WX 1035 ; N twothirds ; G 2063
+U 8533 ; WX 1035 ; N uni2155 ; G 2064
+U 8534 ; WX 1035 ; N uni2156 ; G 2065
+U 8535 ; WX 1035 ; N uni2157 ; G 2066
+U 8536 ; WX 1035 ; N uni2158 ; G 2067
+U 8537 ; WX 1035 ; N uni2159 ; G 2068
+U 8538 ; WX 1035 ; N uni215A ; G 2069
+U 8539 ; WX 1035 ; N oneeighth ; G 2070
+U 8540 ; WX 1035 ; N threeeighths ; G 2071
+U 8541 ; WX 1035 ; N fiveeighths ; G 2072
+U 8542 ; WX 1035 ; N seveneighths ; G 2073
+U 8543 ; WX 615 ; N uni215F ; G 2074
+U 8544 ; WX 468 ; N uni2160 ; G 2075
+U 8545 ; WX 843 ; N uni2161 ; G 2076
+U 8546 ; WX 1218 ; N uni2162 ; G 2077
+U 8547 ; WX 1135 ; N uni2163 ; G 2078
+U 8548 ; WX 776 ; N uni2164 ; G 2079
+U 8549 ; WX 1150 ; N uni2165 ; G 2080
+U 8550 ; WX 1525 ; N uni2166 ; G 2081
+U 8551 ; WX 1900 ; N uni2167 ; G 2082
+U 8552 ; WX 1126 ; N uni2168 ; G 2083
+U 8553 ; WX 776 ; N uni2169 ; G 2084
+U 8554 ; WX 1127 ; N uni216A ; G 2085
+U 8555 ; WX 1502 ; N uni216B ; G 2086
+U 8556 ; WX 703 ; N uni216C ; G 2087
+U 8557 ; WX 796 ; N uni216D ; G 2088
+U 8558 ; WX 867 ; N uni216E ; G 2089
+U 8559 ; WX 1107 ; N uni216F ; G 2090
+U 8560 ; WX 380 ; N uni2170 ; G 2091
+U 8561 ; WX 760 ; N uni2171 ; G 2092
+U 8562 ; WX 1140 ; N uni2172 ; G 2093
+U 8563 ; WX 961 ; N uni2173 ; G 2094
+U 8564 ; WX 581 ; N uni2174 ; G 2095
+U 8565 ; WX 961 ; N uni2175 ; G 2096
+U 8566 ; WX 1341 ; N uni2176 ; G 2097
+U 8567 ; WX 1721 ; N uni2177 ; G 2098
+U 8568 ; WX 976 ; N uni2178 ; G 2099
+U 8569 ; WX 596 ; N uni2179 ; G 2100
+U 8570 ; WX 976 ; N uni217A ; G 2101
+U 8571 ; WX 1356 ; N uni217B ; G 2102
+U 8572 ; WX 380 ; N uni217C ; G 2103
+U 8573 ; WX 609 ; N uni217D ; G 2104
+U 8574 ; WX 699 ; N uni217E ; G 2105
+U 8575 ; WX 1058 ; N uni217F ; G 2106
+U 8576 ; WX 1255 ; N uni2180 ; G 2107
+U 8577 ; WX 867 ; N uni2181 ; G 2108
+U 8578 ; WX 1268 ; N uni2182 ; G 2109
+U 8579 ; WX 796 ; N uni2183 ; G 2110
+U 8580 ; WX 609 ; N uni2184 ; G 2111
+U 8581 ; WX 796 ; N uni2185 ; G 2112
+U 8585 ; WX 1035 ; N uni2189 ; G 2113
+U 8592 ; WX 838 ; N arrowleft ; G 2114
+U 8593 ; WX 838 ; N arrowup ; G 2115
+U 8594 ; WX 838 ; N arrowright ; G 2116
+U 8595 ; WX 838 ; N arrowdown ; G 2117
+U 8596 ; WX 838 ; N arrowboth ; G 2118
+U 8597 ; WX 838 ; N arrowupdn ; G 2119
+U 8598 ; WX 838 ; N uni2196 ; G 2120
+U 8599 ; WX 838 ; N uni2197 ; G 2121
+U 8600 ; WX 838 ; N uni2198 ; G 2122
+U 8601 ; WX 838 ; N uni2199 ; G 2123
+U 8602 ; WX 838 ; N uni219A ; G 2124
+U 8603 ; WX 838 ; N uni219B ; G 2125
+U 8604 ; WX 838 ; N uni219C ; G 2126
+U 8605 ; WX 838 ; N uni219D ; G 2127
+U 8606 ; WX 838 ; N uni219E ; G 2128
+U 8607 ; WX 838 ; N uni219F ; G 2129
+U 8608 ; WX 838 ; N uni21A0 ; G 2130
+U 8609 ; WX 838 ; N uni21A1 ; G 2131
+U 8610 ; WX 838 ; N uni21A2 ; G 2132
+U 8611 ; WX 838 ; N uni21A3 ; G 2133
+U 8612 ; WX 838 ; N uni21A4 ; G 2134
+U 8613 ; WX 838 ; N uni21A5 ; G 2135
+U 8614 ; WX 838 ; N uni21A6 ; G 2136
+U 8615 ; WX 838 ; N uni21A7 ; G 2137
+U 8616 ; WX 838 ; N arrowupdnbse ; G 2138
+U 8617 ; WX 838 ; N uni21A9 ; G 2139
+U 8618 ; WX 838 ; N uni21AA ; G 2140
+U 8619 ; WX 838 ; N uni21AB ; G 2141
+U 8620 ; WX 838 ; N uni21AC ; G 2142
+U 8621 ; WX 838 ; N uni21AD ; G 2143
+U 8622 ; WX 838 ; N uni21AE ; G 2144
+U 8623 ; WX 850 ; N uni21AF ; G 2145
+U 8624 ; WX 838 ; N uni21B0 ; G 2146
+U 8625 ; WX 838 ; N uni21B1 ; G 2147
+U 8626 ; WX 838 ; N uni21B2 ; G 2148
+U 8627 ; WX 838 ; N uni21B3 ; G 2149
+U 8628 ; WX 838 ; N uni21B4 ; G 2150
+U 8629 ; WX 838 ; N carriagereturn ; G 2151
+U 8630 ; WX 838 ; N uni21B6 ; G 2152
+U 8631 ; WX 838 ; N uni21B7 ; G 2153
+U 8632 ; WX 838 ; N uni21B8 ; G 2154
+U 8633 ; WX 838 ; N uni21B9 ; G 2155
+U 8634 ; WX 838 ; N uni21BA ; G 2156
+U 8635 ; WX 838 ; N uni21BB ; G 2157
+U 8636 ; WX 838 ; N uni21BC ; G 2158
+U 8637 ; WX 838 ; N uni21BD ; G 2159
+U 8638 ; WX 838 ; N uni21BE ; G 2160
+U 8639 ; WX 838 ; N uni21BF ; G 2161
+U 8640 ; WX 838 ; N uni21C0 ; G 2162
+U 8641 ; WX 838 ; N uni21C1 ; G 2163
+U 8642 ; WX 838 ; N uni21C2 ; G 2164
+U 8643 ; WX 838 ; N uni21C3 ; G 2165
+U 8644 ; WX 838 ; N uni21C4 ; G 2166
+U 8645 ; WX 838 ; N uni21C5 ; G 2167
+U 8646 ; WX 838 ; N uni21C6 ; G 2168
+U 8647 ; WX 838 ; N uni21C7 ; G 2169
+U 8648 ; WX 838 ; N uni21C8 ; G 2170
+U 8649 ; WX 838 ; N uni21C9 ; G 2171
+U 8650 ; WX 838 ; N uni21CA ; G 2172
+U 8651 ; WX 838 ; N uni21CB ; G 2173
+U 8652 ; WX 838 ; N uni21CC ; G 2174
+U 8653 ; WX 838 ; N uni21CD ; G 2175
+U 8654 ; WX 838 ; N uni21CE ; G 2176
+U 8655 ; WX 838 ; N uni21CF ; G 2177
+U 8656 ; WX 838 ; N arrowdblleft ; G 2178
+U 8657 ; WX 838 ; N arrowdblup ; G 2179
+U 8658 ; WX 838 ; N arrowdblright ; G 2180
+U 8659 ; WX 838 ; N arrowdbldown ; G 2181
+U 8660 ; WX 838 ; N arrowdblboth ; G 2182
+U 8661 ; WX 838 ; N uni21D5 ; G 2183
+U 8662 ; WX 838 ; N uni21D6 ; G 2184
+U 8663 ; WX 838 ; N uni21D7 ; G 2185
+U 8664 ; WX 838 ; N uni21D8 ; G 2186
+U 8665 ; WX 838 ; N uni21D9 ; G 2187
+U 8666 ; WX 838 ; N uni21DA ; G 2188
+U 8667 ; WX 838 ; N uni21DB ; G 2189
+U 8668 ; WX 838 ; N uni21DC ; G 2190
+U 8669 ; WX 838 ; N uni21DD ; G 2191
+U 8670 ; WX 838 ; N uni21DE ; G 2192
+U 8671 ; WX 838 ; N uni21DF ; G 2193
+U 8672 ; WX 838 ; N uni21E0 ; G 2194
+U 8673 ; WX 838 ; N uni21E1 ; G 2195
+U 8674 ; WX 838 ; N uni21E2 ; G 2196
+U 8675 ; WX 838 ; N uni21E3 ; G 2197
+U 8676 ; WX 838 ; N uni21E4 ; G 2198
+U 8677 ; WX 838 ; N uni21E5 ; G 2199
+U 8678 ; WX 838 ; N uni21E6 ; G 2200
+U 8679 ; WX 838 ; N uni21E7 ; G 2201
+U 8680 ; WX 838 ; N uni21E8 ; G 2202
+U 8681 ; WX 838 ; N uni21E9 ; G 2203
+U 8682 ; WX 838 ; N uni21EA ; G 2204
+U 8683 ; WX 838 ; N uni21EB ; G 2205
+U 8684 ; WX 838 ; N uni21EC ; G 2206
+U 8685 ; WX 838 ; N uni21ED ; G 2207
+U 8686 ; WX 838 ; N uni21EE ; G 2208
+U 8687 ; WX 838 ; N uni21EF ; G 2209
+U 8688 ; WX 838 ; N uni21F0 ; G 2210
+U 8689 ; WX 838 ; N uni21F1 ; G 2211
+U 8690 ; WX 838 ; N uni21F2 ; G 2212
+U 8691 ; WX 838 ; N uni21F3 ; G 2213
+U 8692 ; WX 838 ; N uni21F4 ; G 2214
+U 8693 ; WX 838 ; N uni21F5 ; G 2215
+U 8694 ; WX 838 ; N uni21F6 ; G 2216
+U 8695 ; WX 838 ; N uni21F7 ; G 2217
+U 8696 ; WX 838 ; N uni21F8 ; G 2218
+U 8697 ; WX 838 ; N uni21F9 ; G 2219
+U 8698 ; WX 838 ; N uni21FA ; G 2220
+U 8699 ; WX 838 ; N uni21FB ; G 2221
+U 8700 ; WX 838 ; N uni21FC ; G 2222
+U 8701 ; WX 838 ; N uni21FD ; G 2223
+U 8702 ; WX 838 ; N uni21FE ; G 2224
+U 8703 ; WX 838 ; N uni21FF ; G 2225
+U 8704 ; WX 641 ; N universal ; G 2226
+U 8706 ; WX 534 ; N partialdiff ; G 2227
+U 8707 ; WX 620 ; N existential ; G 2228
+U 8708 ; WX 620 ; N uni2204 ; G 2229
+U 8710 ; WX 753 ; N increment ; G 2230
+U 8711 ; WX 753 ; N gradient ; G 2231
+U 8712 ; WX 740 ; N element ; G 2232
+U 8713 ; WX 740 ; N notelement ; G 2233
+U 8715 ; WX 740 ; N suchthat ; G 2234
+U 8716 ; WX 740 ; N uni220C ; G 2235
+U 8719 ; WX 842 ; N product ; G 2236
+U 8720 ; WX 842 ; N uni2210 ; G 2237
+U 8721 ; WX 753 ; N summation ; G 2238
+U 8722 ; WX 838 ; N minus ; G 2239
+U 8723 ; WX 838 ; N uni2213 ; G 2240
+U 8724 ; WX 838 ; N uni2214 ; G 2241
+U 8725 ; WX 365 ; N uni2215 ; G 2242
+U 8727 ; WX 691 ; N asteriskmath ; G 2243
+U 8728 ; WX 519 ; N uni2218 ; G 2244
+U 8729 ; WX 519 ; N uni2219 ; G 2245
+U 8730 ; WX 657 ; N radical ; G 2246
+U 8731 ; WX 657 ; N uni221B ; G 2247
+U 8732 ; WX 657 ; N uni221C ; G 2248
+U 8733 ; WX 672 ; N proportional ; G 2249
+U 8734 ; WX 833 ; N infinity ; G 2250
+U 8735 ; WX 838 ; N orthogonal ; G 2251
+U 8736 ; WX 838 ; N angle ; G 2252
+U 8739 ; WX 324 ; N uni2223 ; G 2253
+U 8740 ; WX 607 ; N uni2224 ; G 2254
+U 8741 ; WX 529 ; N uni2225 ; G 2255
+U 8742 ; WX 773 ; N uni2226 ; G 2256
+U 8743 ; WX 812 ; N logicaland ; G 2257
+U 8744 ; WX 812 ; N logicalor ; G 2258
+U 8745 ; WX 838 ; N intersection ; G 2259
+U 8746 ; WX 838 ; N union ; G 2260
+U 8747 ; WX 579 ; N integral ; G 2261
+U 8748 ; WX 1000 ; N uni222C ; G 2262
+U 8749 ; WX 1391 ; N uni222D ; G 2263
+U 8760 ; WX 838 ; N uni2238 ; G 2264
+U 8761 ; WX 838 ; N uni2239 ; G 2265
+U 8762 ; WX 838 ; N uni223A ; G 2266
+U 8763 ; WX 838 ; N uni223B ; G 2267
+U 8764 ; WX 838 ; N similar ; G 2268
+U 8765 ; WX 838 ; N uni223D ; G 2269
+U 8770 ; WX 838 ; N uni2242 ; G 2270
+U 8771 ; WX 838 ; N uni2243 ; G 2271
+U 8776 ; WX 838 ; N approxequal ; G 2272
+U 8784 ; WX 838 ; N uni2250 ; G 2273
+U 8785 ; WX 838 ; N uni2251 ; G 2274
+U 8786 ; WX 838 ; N uni2252 ; G 2275
+U 8787 ; WX 838 ; N uni2253 ; G 2276
+U 8788 ; WX 1082 ; N uni2254 ; G 2277
+U 8789 ; WX 1082 ; N uni2255 ; G 2278
+U 8800 ; WX 838 ; N notequal ; G 2279
+U 8801 ; WX 838 ; N equivalence ; G 2280
+U 8804 ; WX 838 ; N lessequal ; G 2281
+U 8805 ; WX 838 ; N greaterequal ; G 2282
+U 8834 ; WX 838 ; N propersubset ; G 2283
+U 8835 ; WX 838 ; N propersuperset ; G 2284
+U 8836 ; WX 838 ; N notsubset ; G 2285
+U 8837 ; WX 838 ; N uni2285 ; G 2286
+U 8838 ; WX 838 ; N reflexsubset ; G 2287
+U 8839 ; WX 838 ; N reflexsuperset ; G 2288
+U 8844 ; WX 838 ; N uni228C ; G 2289
+U 8845 ; WX 838 ; N uni228D ; G 2290
+U 8846 ; WX 838 ; N uni228E ; G 2291
+U 8847 ; WX 838 ; N uni228F ; G 2292
+U 8848 ; WX 838 ; N uni2290 ; G 2293
+U 8849 ; WX 838 ; N uni2291 ; G 2294
+U 8850 ; WX 838 ; N uni2292 ; G 2295
+U 8851 ; WX 838 ; N uni2293 ; G 2296
+U 8852 ; WX 838 ; N uni2294 ; G 2297
+U 8853 ; WX 838 ; N circleplus ; G 2298
+U 8854 ; WX 838 ; N uni2296 ; G 2299
+U 8855 ; WX 838 ; N circlemultiply ; G 2300
+U 8856 ; WX 838 ; N uni2298 ; G 2301
+U 8857 ; WX 838 ; N uni2299 ; G 2302
+U 8858 ; WX 838 ; N uni229A ; G 2303
+U 8859 ; WX 838 ; N uni229B ; G 2304
+U 8860 ; WX 838 ; N uni229C ; G 2305
+U 8861 ; WX 838 ; N uni229D ; G 2306
+U 8862 ; WX 838 ; N uni229E ; G 2307
+U 8863 ; WX 838 ; N uni229F ; G 2308
+U 8864 ; WX 838 ; N uni22A0 ; G 2309
+U 8865 ; WX 838 ; N uni22A1 ; G 2310
+U 8866 ; WX 884 ; N uni22A2 ; G 2311
+U 8867 ; WX 884 ; N uni22A3 ; G 2312
+U 8868 ; WX 960 ; N uni22A4 ; G 2313
+U 8869 ; WX 960 ; N perpendicular ; G 2314
+U 8870 ; WX 616 ; N uni22A6 ; G 2315
+U 8871 ; WX 616 ; N uni22A7 ; G 2316
+U 8872 ; WX 884 ; N uni22A8 ; G 2317
+U 8873 ; WX 884 ; N uni22A9 ; G 2318
+U 8874 ; WX 884 ; N uni22AA ; G 2319
+U 8875 ; WX 1080 ; N uni22AB ; G 2320
+U 8876 ; WX 884 ; N uni22AC ; G 2321
+U 8877 ; WX 884 ; N uni22AD ; G 2322
+U 8878 ; WX 884 ; N uni22AE ; G 2323
+U 8879 ; WX 1080 ; N uni22AF ; G 2324
+U 8900 ; WX 626 ; N uni22C4 ; G 2325
+U 8901 ; WX 398 ; N dotmath ; G 2326
+U 8962 ; WX 834 ; N house ; G 2327
+U 8968 ; WX 473 ; N uni2308 ; G 2328
+U 8969 ; WX 473 ; N uni2309 ; G 2329
+U 8970 ; WX 473 ; N uni230A ; G 2330
+U 8971 ; WX 473 ; N uni230B ; G 2331
+U 8976 ; WX 838 ; N revlogicalnot ; G 2332
+U 8977 ; WX 539 ; N uni2311 ; G 2333
+U 8984 ; WX 928 ; N uni2318 ; G 2334
+U 8985 ; WX 838 ; N uni2319 ; G 2335
+U 8992 ; WX 579 ; N integraltp ; G 2336
+U 8993 ; WX 579 ; N integralbt ; G 2337
+U 8997 ; WX 1000 ; N uni2325 ; G 2338
+U 9000 ; WX 1443 ; N uni2328 ; G 2339
+U 9085 ; WX 1008 ; N uni237D ; G 2340
+U 9115 ; WX 500 ; N uni239B ; G 2341
+U 9116 ; WX 500 ; N uni239C ; G 2342
+U 9117 ; WX 500 ; N uni239D ; G 2343
+U 9118 ; WX 500 ; N uni239E ; G 2344
+U 9119 ; WX 500 ; N uni239F ; G 2345
+U 9120 ; WX 500 ; N uni23A0 ; G 2346
+U 9121 ; WX 500 ; N uni23A1 ; G 2347
+U 9122 ; WX 500 ; N uni23A2 ; G 2348
+U 9123 ; WX 500 ; N uni23A3 ; G 2349
+U 9124 ; WX 500 ; N uni23A4 ; G 2350
+U 9125 ; WX 500 ; N uni23A5 ; G 2351
+U 9126 ; WX 500 ; N uni23A6 ; G 2352
+U 9127 ; WX 750 ; N uni23A7 ; G 2353
+U 9128 ; WX 750 ; N uni23A8 ; G 2354
+U 9129 ; WX 750 ; N uni23A9 ; G 2355
+U 9130 ; WX 750 ; N uni23AA ; G 2356
+U 9131 ; WX 750 ; N uni23AB ; G 2357
+U 9132 ; WX 750 ; N uni23AC ; G 2358
+U 9133 ; WX 750 ; N uni23AD ; G 2359
+U 9134 ; WX 579 ; N uni23AE ; G 2360
+U 9167 ; WX 945 ; N uni23CF ; G 2361
+U 9251 ; WX 834 ; N uni2423 ; G 2362
+U 9472 ; WX 602 ; N SF100000 ; G 2363
+U 9473 ; WX 602 ; N uni2501 ; G 2364
+U 9474 ; WX 602 ; N SF110000 ; G 2365
+U 9475 ; WX 602 ; N uni2503 ; G 2366
+U 9476 ; WX 602 ; N uni2504 ; G 2367
+U 9477 ; WX 602 ; N uni2505 ; G 2368
+U 9478 ; WX 602 ; N uni2506 ; G 2369
+U 9479 ; WX 602 ; N uni2507 ; G 2370
+U 9480 ; WX 602 ; N uni2508 ; G 2371
+U 9481 ; WX 602 ; N uni2509 ; G 2372
+U 9482 ; WX 602 ; N uni250A ; G 2373
+U 9483 ; WX 602 ; N uni250B ; G 2374
+U 9484 ; WX 602 ; N SF010000 ; G 2375
+U 9485 ; WX 602 ; N uni250D ; G 2376
+U 9486 ; WX 602 ; N uni250E ; G 2377
+U 9487 ; WX 602 ; N uni250F ; G 2378
+U 9488 ; WX 602 ; N SF030000 ; G 2379
+U 9489 ; WX 602 ; N uni2511 ; G 2380
+U 9490 ; WX 602 ; N uni2512 ; G 2381
+U 9491 ; WX 602 ; N uni2513 ; G 2382
+U 9492 ; WX 602 ; N SF020000 ; G 2383
+U 9493 ; WX 602 ; N uni2515 ; G 2384
+U 9494 ; WX 602 ; N uni2516 ; G 2385
+U 9495 ; WX 602 ; N uni2517 ; G 2386
+U 9496 ; WX 602 ; N SF040000 ; G 2387
+U 9497 ; WX 602 ; N uni2519 ; G 2388
+U 9498 ; WX 602 ; N uni251A ; G 2389
+U 9499 ; WX 602 ; N uni251B ; G 2390
+U 9500 ; WX 602 ; N SF080000 ; G 2391
+U 9501 ; WX 602 ; N uni251D ; G 2392
+U 9502 ; WX 602 ; N uni251E ; G 2393
+U 9503 ; WX 602 ; N uni251F ; G 2394
+U 9504 ; WX 602 ; N uni2520 ; G 2395
+U 9505 ; WX 602 ; N uni2521 ; G 2396
+U 9506 ; WX 602 ; N uni2522 ; G 2397
+U 9507 ; WX 602 ; N uni2523 ; G 2398
+U 9508 ; WX 602 ; N SF090000 ; G 2399
+U 9509 ; WX 602 ; N uni2525 ; G 2400
+U 9510 ; WX 602 ; N uni2526 ; G 2401
+U 9511 ; WX 602 ; N uni2527 ; G 2402
+U 9512 ; WX 602 ; N uni2528 ; G 2403
+U 9513 ; WX 602 ; N uni2529 ; G 2404
+U 9514 ; WX 602 ; N uni252A ; G 2405
+U 9515 ; WX 602 ; N uni252B ; G 2406
+U 9516 ; WX 602 ; N SF060000 ; G 2407
+U 9517 ; WX 602 ; N uni252D ; G 2408
+U 9518 ; WX 602 ; N uni252E ; G 2409
+U 9519 ; WX 602 ; N uni252F ; G 2410
+U 9520 ; WX 602 ; N uni2530 ; G 2411
+U 9521 ; WX 602 ; N uni2531 ; G 2412
+U 9522 ; WX 602 ; N uni2532 ; G 2413
+U 9523 ; WX 602 ; N uni2533 ; G 2414
+U 9524 ; WX 602 ; N SF070000 ; G 2415
+U 9525 ; WX 602 ; N uni2535 ; G 2416
+U 9526 ; WX 602 ; N uni2536 ; G 2417
+U 9527 ; WX 602 ; N uni2537 ; G 2418
+U 9528 ; WX 602 ; N uni2538 ; G 2419
+U 9529 ; WX 602 ; N uni2539 ; G 2420
+U 9530 ; WX 602 ; N uni253A ; G 2421
+U 9531 ; WX 602 ; N uni253B ; G 2422
+U 9532 ; WX 602 ; N SF050000 ; G 2423
+U 9533 ; WX 602 ; N uni253D ; G 2424
+U 9534 ; WX 602 ; N uni253E ; G 2425
+U 9535 ; WX 602 ; N uni253F ; G 2426
+U 9536 ; WX 602 ; N uni2540 ; G 2427
+U 9537 ; WX 602 ; N uni2541 ; G 2428
+U 9538 ; WX 602 ; N uni2542 ; G 2429
+U 9539 ; WX 602 ; N uni2543 ; G 2430
+U 9540 ; WX 602 ; N uni2544 ; G 2431
+U 9541 ; WX 602 ; N uni2545 ; G 2432
+U 9542 ; WX 602 ; N uni2546 ; G 2433
+U 9543 ; WX 602 ; N uni2547 ; G 2434
+U 9544 ; WX 602 ; N uni2548 ; G 2435
+U 9545 ; WX 602 ; N uni2549 ; G 2436
+U 9546 ; WX 602 ; N uni254A ; G 2437
+U 9547 ; WX 602 ; N uni254B ; G 2438
+U 9548 ; WX 602 ; N uni254C ; G 2439
+U 9549 ; WX 602 ; N uni254D ; G 2440
+U 9550 ; WX 602 ; N uni254E ; G 2441
+U 9551 ; WX 602 ; N uni254F ; G 2442
+U 9552 ; WX 602 ; N SF430000 ; G 2443
+U 9553 ; WX 602 ; N SF240000 ; G 2444
+U 9554 ; WX 602 ; N SF510000 ; G 2445
+U 9555 ; WX 602 ; N SF520000 ; G 2446
+U 9556 ; WX 602 ; N SF390000 ; G 2447
+U 9557 ; WX 602 ; N SF220000 ; G 2448
+U 9558 ; WX 602 ; N SF210000 ; G 2449
+U 9559 ; WX 602 ; N SF250000 ; G 2450
+U 9560 ; WX 602 ; N SF500000 ; G 2451
+U 9561 ; WX 602 ; N SF490000 ; G 2452
+U 9562 ; WX 602 ; N SF380000 ; G 2453
+U 9563 ; WX 602 ; N SF280000 ; G 2454
+U 9564 ; WX 602 ; N SF270000 ; G 2455
+U 9565 ; WX 602 ; N SF260000 ; G 2456
+U 9566 ; WX 602 ; N SF360000 ; G 2457
+U 9567 ; WX 602 ; N SF370000 ; G 2458
+U 9568 ; WX 602 ; N SF420000 ; G 2459
+U 9569 ; WX 602 ; N SF190000 ; G 2460
+U 9570 ; WX 602 ; N SF200000 ; G 2461
+U 9571 ; WX 602 ; N SF230000 ; G 2462
+U 9572 ; WX 602 ; N SF470000 ; G 2463
+U 9573 ; WX 602 ; N SF480000 ; G 2464
+U 9574 ; WX 602 ; N SF410000 ; G 2465
+U 9575 ; WX 602 ; N SF450000 ; G 2466
+U 9576 ; WX 602 ; N SF460000 ; G 2467
+U 9577 ; WX 602 ; N SF400000 ; G 2468
+U 9578 ; WX 602 ; N SF540000 ; G 2469
+U 9579 ; WX 602 ; N SF530000 ; G 2470
+U 9580 ; WX 602 ; N SF440000 ; G 2471
+U 9581 ; WX 602 ; N uni256D ; G 2472
+U 9582 ; WX 602 ; N uni256E ; G 2473
+U 9583 ; WX 602 ; N uni256F ; G 2474
+U 9584 ; WX 602 ; N uni2570 ; G 2475
+U 9585 ; WX 602 ; N uni2571 ; G 2476
+U 9586 ; WX 602 ; N uni2572 ; G 2477
+U 9587 ; WX 602 ; N uni2573 ; G 2478
+U 9588 ; WX 602 ; N uni2574 ; G 2479
+U 9589 ; WX 602 ; N uni2575 ; G 2480
+U 9590 ; WX 602 ; N uni2576 ; G 2481
+U 9591 ; WX 602 ; N uni2577 ; G 2482
+U 9592 ; WX 602 ; N uni2578 ; G 2483
+U 9593 ; WX 602 ; N uni2579 ; G 2484
+U 9594 ; WX 602 ; N uni257A ; G 2485
+U 9595 ; WX 602 ; N uni257B ; G 2486
+U 9596 ; WX 602 ; N uni257C ; G 2487
+U 9597 ; WX 602 ; N uni257D ; G 2488
+U 9598 ; WX 602 ; N uni257E ; G 2489
+U 9599 ; WX 602 ; N uni257F ; G 2490
+U 9600 ; WX 769 ; N upblock ; G 2491
+U 9601 ; WX 769 ; N uni2581 ; G 2492
+U 9602 ; WX 769 ; N uni2582 ; G 2493
+U 9603 ; WX 769 ; N uni2583 ; G 2494
+U 9604 ; WX 769 ; N dnblock ; G 2495
+U 9605 ; WX 769 ; N uni2585 ; G 2496
+U 9606 ; WX 769 ; N uni2586 ; G 2497
+U 9607 ; WX 769 ; N uni2587 ; G 2498
+U 9608 ; WX 769 ; N block ; G 2499
+U 9609 ; WX 769 ; N uni2589 ; G 2500
+U 9610 ; WX 769 ; N uni258A ; G 2501
+U 9611 ; WX 769 ; N uni258B ; G 2502
+U 9612 ; WX 769 ; N lfblock ; G 2503
+U 9613 ; WX 769 ; N uni258D ; G 2504
+U 9614 ; WX 769 ; N uni258E ; G 2505
+U 9615 ; WX 769 ; N uni258F ; G 2506
+U 9616 ; WX 769 ; N rtblock ; G 2507
+U 9617 ; WX 769 ; N ltshade ; G 2508
+U 9618 ; WX 769 ; N shade ; G 2509
+U 9619 ; WX 769 ; N dkshade ; G 2510
+U 9620 ; WX 769 ; N uni2594 ; G 2511
+U 9621 ; WX 769 ; N uni2595 ; G 2512
+U 9622 ; WX 769 ; N uni2596 ; G 2513
+U 9623 ; WX 769 ; N uni2597 ; G 2514
+U 9624 ; WX 769 ; N uni2598 ; G 2515
+U 9625 ; WX 769 ; N uni2599 ; G 2516
+U 9626 ; WX 769 ; N uni259A ; G 2517
+U 9627 ; WX 769 ; N uni259B ; G 2518
+U 9628 ; WX 769 ; N uni259C ; G 2519
+U 9629 ; WX 769 ; N uni259D ; G 2520
+U 9630 ; WX 769 ; N uni259E ; G 2521
+U 9631 ; WX 769 ; N uni259F ; G 2522
+U 9632 ; WX 945 ; N filledbox ; G 2523
+U 9633 ; WX 945 ; N H22073 ; G 2524
+U 9634 ; WX 945 ; N uni25A2 ; G 2525
+U 9635 ; WX 945 ; N uni25A3 ; G 2526
+U 9636 ; WX 945 ; N uni25A4 ; G 2527
+U 9637 ; WX 945 ; N uni25A5 ; G 2528
+U 9638 ; WX 945 ; N uni25A6 ; G 2529
+U 9639 ; WX 945 ; N uni25A7 ; G 2530
+U 9640 ; WX 945 ; N uni25A8 ; G 2531
+U 9641 ; WX 945 ; N uni25A9 ; G 2532
+U 9642 ; WX 678 ; N H18543 ; G 2533
+U 9643 ; WX 678 ; N H18551 ; G 2534
+U 9644 ; WX 945 ; N filledrect ; G 2535
+U 9645 ; WX 945 ; N uni25AD ; G 2536
+U 9646 ; WX 550 ; N uni25AE ; G 2537
+U 9647 ; WX 550 ; N uni25AF ; G 2538
+U 9648 ; WX 769 ; N uni25B0 ; G 2539
+U 9649 ; WX 769 ; N uni25B1 ; G 2540
+U 9650 ; WX 769 ; N triagup ; G 2541
+U 9651 ; WX 769 ; N uni25B3 ; G 2542
+U 9652 ; WX 502 ; N uni25B4 ; G 2543
+U 9653 ; WX 502 ; N uni25B5 ; G 2544
+U 9654 ; WX 769 ; N uni25B6 ; G 2545
+U 9655 ; WX 769 ; N uni25B7 ; G 2546
+U 9656 ; WX 502 ; N uni25B8 ; G 2547
+U 9657 ; WX 502 ; N uni25B9 ; G 2548
+U 9658 ; WX 769 ; N triagrt ; G 2549
+U 9659 ; WX 769 ; N uni25BB ; G 2550
+U 9660 ; WX 769 ; N triagdn ; G 2551
+U 9661 ; WX 769 ; N uni25BD ; G 2552
+U 9662 ; WX 502 ; N uni25BE ; G 2553
+U 9663 ; WX 502 ; N uni25BF ; G 2554
+U 9664 ; WX 769 ; N uni25C0 ; G 2555
+U 9665 ; WX 769 ; N uni25C1 ; G 2556
+U 9666 ; WX 502 ; N uni25C2 ; G 2557
+U 9667 ; WX 502 ; N uni25C3 ; G 2558
+U 9668 ; WX 769 ; N triaglf ; G 2559
+U 9669 ; WX 769 ; N uni25C5 ; G 2560
+U 9670 ; WX 769 ; N uni25C6 ; G 2561
+U 9671 ; WX 769 ; N uni25C7 ; G 2562
+U 9672 ; WX 769 ; N uni25C8 ; G 2563
+U 9673 ; WX 873 ; N uni25C9 ; G 2564
+U 9674 ; WX 494 ; N lozenge ; G 2565
+U 9675 ; WX 873 ; N circle ; G 2566
+U 9676 ; WX 873 ; N uni25CC ; G 2567
+U 9677 ; WX 873 ; N uni25CD ; G 2568
+U 9678 ; WX 873 ; N uni25CE ; G 2569
+U 9679 ; WX 873 ; N H18533 ; G 2570
+U 9680 ; WX 873 ; N uni25D0 ; G 2571
+U 9681 ; WX 873 ; N uni25D1 ; G 2572
+U 9682 ; WX 873 ; N uni25D2 ; G 2573
+U 9683 ; WX 873 ; N uni25D3 ; G 2574
+U 9684 ; WX 873 ; N uni25D4 ; G 2575
+U 9685 ; WX 873 ; N uni25D5 ; G 2576
+U 9686 ; WX 527 ; N uni25D6 ; G 2577
+U 9687 ; WX 527 ; N uni25D7 ; G 2578
+U 9688 ; WX 791 ; N invbullet ; G 2579
+U 9689 ; WX 970 ; N invcircle ; G 2580
+U 9690 ; WX 970 ; N uni25DA ; G 2581
+U 9691 ; WX 970 ; N uni25DB ; G 2582
+U 9692 ; WX 387 ; N uni25DC ; G 2583
+U 9693 ; WX 387 ; N uni25DD ; G 2584
+U 9694 ; WX 387 ; N uni25DE ; G 2585
+U 9695 ; WX 387 ; N uni25DF ; G 2586
+U 9696 ; WX 873 ; N uni25E0 ; G 2587
+U 9697 ; WX 873 ; N uni25E1 ; G 2588
+U 9698 ; WX 769 ; N uni25E2 ; G 2589
+U 9699 ; WX 769 ; N uni25E3 ; G 2590
+U 9700 ; WX 769 ; N uni25E4 ; G 2591
+U 9701 ; WX 769 ; N uni25E5 ; G 2592
+U 9702 ; WX 590 ; N openbullet ; G 2593
+U 9703 ; WX 945 ; N uni25E7 ; G 2594
+U 9704 ; WX 945 ; N uni25E8 ; G 2595
+U 9705 ; WX 945 ; N uni25E9 ; G 2596
+U 9706 ; WX 945 ; N uni25EA ; G 2597
+U 9707 ; WX 945 ; N uni25EB ; G 2598
+U 9708 ; WX 769 ; N uni25EC ; G 2599
+U 9709 ; WX 769 ; N uni25ED ; G 2600
+U 9710 ; WX 769 ; N uni25EE ; G 2601
+U 9711 ; WX 1119 ; N uni25EF ; G 2602
+U 9712 ; WX 945 ; N uni25F0 ; G 2603
+U 9713 ; WX 945 ; N uni25F1 ; G 2604
+U 9714 ; WX 945 ; N uni25F2 ; G 2605
+U 9715 ; WX 945 ; N uni25F3 ; G 2606
+U 9716 ; WX 873 ; N uni25F4 ; G 2607
+U 9717 ; WX 873 ; N uni25F5 ; G 2608
+U 9718 ; WX 873 ; N uni25F6 ; G 2609
+U 9719 ; WX 873 ; N uni25F7 ; G 2610
+U 9720 ; WX 769 ; N uni25F8 ; G 2611
+U 9721 ; WX 769 ; N uni25F9 ; G 2612
+U 9722 ; WX 769 ; N uni25FA ; G 2613
+U 9723 ; WX 830 ; N uni25FB ; G 2614
+U 9724 ; WX 830 ; N uni25FC ; G 2615
+U 9725 ; WX 732 ; N uni25FD ; G 2616
+U 9726 ; WX 732 ; N uni25FE ; G 2617
+U 9727 ; WX 769 ; N uni25FF ; G 2618
+U 9728 ; WX 896 ; N uni2600 ; G 2619
+U 9784 ; WX 896 ; N uni2638 ; G 2620
+U 9785 ; WX 896 ; N uni2639 ; G 2621
+U 9786 ; WX 896 ; N smileface ; G 2622
+U 9787 ; WX 896 ; N invsmileface ; G 2623
+U 9788 ; WX 896 ; N sun ; G 2624
+U 9791 ; WX 614 ; N uni263F ; G 2625
+U 9792 ; WX 731 ; N female ; G 2626
+U 9793 ; WX 731 ; N uni2641 ; G 2627
+U 9794 ; WX 896 ; N male ; G 2628
+U 9795 ; WX 896 ; N uni2643 ; G 2629
+U 9796 ; WX 896 ; N uni2644 ; G 2630
+U 9797 ; WX 896 ; N uni2645 ; G 2631
+U 9798 ; WX 896 ; N uni2646 ; G 2632
+U 9799 ; WX 896 ; N uni2647 ; G 2633
+U 9824 ; WX 896 ; N spade ; G 2634
+U 9825 ; WX 896 ; N uni2661 ; G 2635
+U 9826 ; WX 896 ; N uni2662 ; G 2636
+U 9827 ; WX 896 ; N club ; G 2637
+U 9828 ; WX 896 ; N uni2664 ; G 2638
+U 9829 ; WX 896 ; N heart ; G 2639
+U 9830 ; WX 896 ; N diamond ; G 2640
+U 9831 ; WX 896 ; N uni2667 ; G 2641
+U 9833 ; WX 472 ; N uni2669 ; G 2642
+U 9834 ; WX 638 ; N musicalnote ; G 2643
+U 9835 ; WX 896 ; N musicalnotedbl ; G 2644
+U 9836 ; WX 896 ; N uni266C ; G 2645
+U 9837 ; WX 472 ; N uni266D ; G 2646
+U 9838 ; WX 357 ; N uni266E ; G 2647
+U 9839 ; WX 484 ; N uni266F ; G 2648
+U 10145 ; WX 838 ; N uni27A1 ; G 2649
+U 10181 ; WX 457 ; N uni27C5 ; G 2650
+U 10182 ; WX 457 ; N uni27C6 ; G 2651
+U 10208 ; WX 494 ; N uni27E0 ; G 2652
+U 10216 ; WX 457 ; N uni27E8 ; G 2653
+U 10217 ; WX 457 ; N uni27E9 ; G 2654
+U 10224 ; WX 838 ; N uni27F0 ; G 2655
+U 10225 ; WX 838 ; N uni27F1 ; G 2656
+U 10226 ; WX 838 ; N uni27F2 ; G 2657
+U 10227 ; WX 838 ; N uni27F3 ; G 2658
+U 10228 ; WX 1033 ; N uni27F4 ; G 2659
+U 10229 ; WX 1434 ; N uni27F5 ; G 2660
+U 10230 ; WX 1434 ; N uni27F6 ; G 2661
+U 10231 ; WX 1434 ; N uni27F7 ; G 2662
+U 10232 ; WX 1434 ; N uni27F8 ; G 2663
+U 10233 ; WX 1434 ; N uni27F9 ; G 2664
+U 10234 ; WX 1434 ; N uni27FA ; G 2665
+U 10235 ; WX 1434 ; N uni27FB ; G 2666
+U 10236 ; WX 1434 ; N uni27FC ; G 2667
+U 10237 ; WX 1434 ; N uni27FD ; G 2668
+U 10238 ; WX 1434 ; N uni27FE ; G 2669
+U 10239 ; WX 1434 ; N uni27FF ; G 2670
+U 10240 ; WX 781 ; N uni2800 ; G 2671
+U 10241 ; WX 781 ; N uni2801 ; G 2672
+U 10242 ; WX 781 ; N uni2802 ; G 2673
+U 10243 ; WX 781 ; N uni2803 ; G 2674
+U 10244 ; WX 781 ; N uni2804 ; G 2675
+U 10245 ; WX 781 ; N uni2805 ; G 2676
+U 10246 ; WX 781 ; N uni2806 ; G 2677
+U 10247 ; WX 781 ; N uni2807 ; G 2678
+U 10248 ; WX 781 ; N uni2808 ; G 2679
+U 10249 ; WX 781 ; N uni2809 ; G 2680
+U 10250 ; WX 781 ; N uni280A ; G 2681
+U 10251 ; WX 781 ; N uni280B ; G 2682
+U 10252 ; WX 781 ; N uni280C ; G 2683
+U 10253 ; WX 781 ; N uni280D ; G 2684
+U 10254 ; WX 781 ; N uni280E ; G 2685
+U 10255 ; WX 781 ; N uni280F ; G 2686
+U 10256 ; WX 781 ; N uni2810 ; G 2687
+U 10257 ; WX 781 ; N uni2811 ; G 2688
+U 10258 ; WX 781 ; N uni2812 ; G 2689
+U 10259 ; WX 781 ; N uni2813 ; G 2690
+U 10260 ; WX 781 ; N uni2814 ; G 2691
+U 10261 ; WX 781 ; N uni2815 ; G 2692
+U 10262 ; WX 781 ; N uni2816 ; G 2693
+U 10263 ; WX 781 ; N uni2817 ; G 2694
+U 10264 ; WX 781 ; N uni2818 ; G 2695
+U 10265 ; WX 781 ; N uni2819 ; G 2696
+U 10266 ; WX 781 ; N uni281A ; G 2697
+U 10267 ; WX 781 ; N uni281B ; G 2698
+U 10268 ; WX 781 ; N uni281C ; G 2699
+U 10269 ; WX 781 ; N uni281D ; G 2700
+U 10270 ; WX 781 ; N uni281E ; G 2701
+U 10271 ; WX 781 ; N uni281F ; G 2702
+U 10272 ; WX 781 ; N uni2820 ; G 2703
+U 10273 ; WX 781 ; N uni2821 ; G 2704
+U 10274 ; WX 781 ; N uni2822 ; G 2705
+U 10275 ; WX 781 ; N uni2823 ; G 2706
+U 10276 ; WX 781 ; N uni2824 ; G 2707
+U 10277 ; WX 781 ; N uni2825 ; G 2708
+U 10278 ; WX 781 ; N uni2826 ; G 2709
+U 10279 ; WX 781 ; N uni2827 ; G 2710
+U 10280 ; WX 781 ; N uni2828 ; G 2711
+U 10281 ; WX 781 ; N uni2829 ; G 2712
+U 10282 ; WX 781 ; N uni282A ; G 2713
+U 10283 ; WX 781 ; N uni282B ; G 2714
+U 10284 ; WX 781 ; N uni282C ; G 2715
+U 10285 ; WX 781 ; N uni282D ; G 2716
+U 10286 ; WX 781 ; N uni282E ; G 2717
+U 10287 ; WX 781 ; N uni282F ; G 2718
+U 10288 ; WX 781 ; N uni2830 ; G 2719
+U 10289 ; WX 781 ; N uni2831 ; G 2720
+U 10290 ; WX 781 ; N uni2832 ; G 2721
+U 10291 ; WX 781 ; N uni2833 ; G 2722
+U 10292 ; WX 781 ; N uni2834 ; G 2723
+U 10293 ; WX 781 ; N uni2835 ; G 2724
+U 10294 ; WX 781 ; N uni2836 ; G 2725
+U 10295 ; WX 781 ; N uni2837 ; G 2726
+U 10296 ; WX 781 ; N uni2838 ; G 2727
+U 10297 ; WX 781 ; N uni2839 ; G 2728
+U 10298 ; WX 781 ; N uni283A ; G 2729
+U 10299 ; WX 781 ; N uni283B ; G 2730
+U 10300 ; WX 781 ; N uni283C ; G 2731
+U 10301 ; WX 781 ; N uni283D ; G 2732
+U 10302 ; WX 781 ; N uni283E ; G 2733
+U 10303 ; WX 781 ; N uni283F ; G 2734
+U 10304 ; WX 781 ; N uni2840 ; G 2735
+U 10305 ; WX 781 ; N uni2841 ; G 2736
+U 10306 ; WX 781 ; N uni2842 ; G 2737
+U 10307 ; WX 781 ; N uni2843 ; G 2738
+U 10308 ; WX 781 ; N uni2844 ; G 2739
+U 10309 ; WX 781 ; N uni2845 ; G 2740
+U 10310 ; WX 781 ; N uni2846 ; G 2741
+U 10311 ; WX 781 ; N uni2847 ; G 2742
+U 10312 ; WX 781 ; N uni2848 ; G 2743
+U 10313 ; WX 781 ; N uni2849 ; G 2744
+U 10314 ; WX 781 ; N uni284A ; G 2745
+U 10315 ; WX 781 ; N uni284B ; G 2746
+U 10316 ; WX 781 ; N uni284C ; G 2747
+U 10317 ; WX 781 ; N uni284D ; G 2748
+U 10318 ; WX 781 ; N uni284E ; G 2749
+U 10319 ; WX 781 ; N uni284F ; G 2750
+U 10320 ; WX 781 ; N uni2850 ; G 2751
+U 10321 ; WX 781 ; N uni2851 ; G 2752
+U 10322 ; WX 781 ; N uni2852 ; G 2753
+U 10323 ; WX 781 ; N uni2853 ; G 2754
+U 10324 ; WX 781 ; N uni2854 ; G 2755
+U 10325 ; WX 781 ; N uni2855 ; G 2756
+U 10326 ; WX 781 ; N uni2856 ; G 2757
+U 10327 ; WX 781 ; N uni2857 ; G 2758
+U 10328 ; WX 781 ; N uni2858 ; G 2759
+U 10329 ; WX 781 ; N uni2859 ; G 2760
+U 10330 ; WX 781 ; N uni285A ; G 2761
+U 10331 ; WX 781 ; N uni285B ; G 2762
+U 10332 ; WX 781 ; N uni285C ; G 2763
+U 10333 ; WX 781 ; N uni285D ; G 2764
+U 10334 ; WX 781 ; N uni285E ; G 2765
+U 10335 ; WX 781 ; N uni285F ; G 2766
+U 10336 ; WX 781 ; N uni2860 ; G 2767
+U 10337 ; WX 781 ; N uni2861 ; G 2768
+U 10338 ; WX 781 ; N uni2862 ; G 2769
+U 10339 ; WX 781 ; N uni2863 ; G 2770
+U 10340 ; WX 781 ; N uni2864 ; G 2771
+U 10341 ; WX 781 ; N uni2865 ; G 2772
+U 10342 ; WX 781 ; N uni2866 ; G 2773
+U 10343 ; WX 781 ; N uni2867 ; G 2774
+U 10344 ; WX 781 ; N uni2868 ; G 2775
+U 10345 ; WX 781 ; N uni2869 ; G 2776
+U 10346 ; WX 781 ; N uni286A ; G 2777
+U 10347 ; WX 781 ; N uni286B ; G 2778
+U 10348 ; WX 781 ; N uni286C ; G 2779
+U 10349 ; WX 781 ; N uni286D ; G 2780
+U 10350 ; WX 781 ; N uni286E ; G 2781
+U 10351 ; WX 781 ; N uni286F ; G 2782
+U 10352 ; WX 781 ; N uni2870 ; G 2783
+U 10353 ; WX 781 ; N uni2871 ; G 2784
+U 10354 ; WX 781 ; N uni2872 ; G 2785
+U 10355 ; WX 781 ; N uni2873 ; G 2786
+U 10356 ; WX 781 ; N uni2874 ; G 2787
+U 10357 ; WX 781 ; N uni2875 ; G 2788
+U 10358 ; WX 781 ; N uni2876 ; G 2789
+U 10359 ; WX 781 ; N uni2877 ; G 2790
+U 10360 ; WX 781 ; N uni2878 ; G 2791
+U 10361 ; WX 781 ; N uni2879 ; G 2792
+U 10362 ; WX 781 ; N uni287A ; G 2793
+U 10363 ; WX 781 ; N uni287B ; G 2794
+U 10364 ; WX 781 ; N uni287C ; G 2795
+U 10365 ; WX 781 ; N uni287D ; G 2796
+U 10366 ; WX 781 ; N uni287E ; G 2797
+U 10367 ; WX 781 ; N uni287F ; G 2798
+U 10368 ; WX 781 ; N uni2880 ; G 2799
+U 10369 ; WX 781 ; N uni2881 ; G 2800
+U 10370 ; WX 781 ; N uni2882 ; G 2801
+U 10371 ; WX 781 ; N uni2883 ; G 2802
+U 10372 ; WX 781 ; N uni2884 ; G 2803
+U 10373 ; WX 781 ; N uni2885 ; G 2804
+U 10374 ; WX 781 ; N uni2886 ; G 2805
+U 10375 ; WX 781 ; N uni2887 ; G 2806
+U 10376 ; WX 781 ; N uni2888 ; G 2807
+U 10377 ; WX 781 ; N uni2889 ; G 2808
+U 10378 ; WX 781 ; N uni288A ; G 2809
+U 10379 ; WX 781 ; N uni288B ; G 2810
+U 10380 ; WX 781 ; N uni288C ; G 2811
+U 10381 ; WX 781 ; N uni288D ; G 2812
+U 10382 ; WX 781 ; N uni288E ; G 2813
+U 10383 ; WX 781 ; N uni288F ; G 2814
+U 10384 ; WX 781 ; N uni2890 ; G 2815
+U 10385 ; WX 781 ; N uni2891 ; G 2816
+U 10386 ; WX 781 ; N uni2892 ; G 2817
+U 10387 ; WX 781 ; N uni2893 ; G 2818
+U 10388 ; WX 781 ; N uni2894 ; G 2819
+U 10389 ; WX 781 ; N uni2895 ; G 2820
+U 10390 ; WX 781 ; N uni2896 ; G 2821
+U 10391 ; WX 781 ; N uni2897 ; G 2822
+U 10392 ; WX 781 ; N uni2898 ; G 2823
+U 10393 ; WX 781 ; N uni2899 ; G 2824
+U 10394 ; WX 781 ; N uni289A ; G 2825
+U 10395 ; WX 781 ; N uni289B ; G 2826
+U 10396 ; WX 781 ; N uni289C ; G 2827
+U 10397 ; WX 781 ; N uni289D ; G 2828
+U 10398 ; WX 781 ; N uni289E ; G 2829
+U 10399 ; WX 781 ; N uni289F ; G 2830
+U 10400 ; WX 781 ; N uni28A0 ; G 2831
+U 10401 ; WX 781 ; N uni28A1 ; G 2832
+U 10402 ; WX 781 ; N uni28A2 ; G 2833
+U 10403 ; WX 781 ; N uni28A3 ; G 2834
+U 10404 ; WX 781 ; N uni28A4 ; G 2835
+U 10405 ; WX 781 ; N uni28A5 ; G 2836
+U 10406 ; WX 781 ; N uni28A6 ; G 2837
+U 10407 ; WX 781 ; N uni28A7 ; G 2838
+U 10408 ; WX 781 ; N uni28A8 ; G 2839
+U 10409 ; WX 781 ; N uni28A9 ; G 2840
+U 10410 ; WX 781 ; N uni28AA ; G 2841
+U 10411 ; WX 781 ; N uni28AB ; G 2842
+U 10412 ; WX 781 ; N uni28AC ; G 2843
+U 10413 ; WX 781 ; N uni28AD ; G 2844
+U 10414 ; WX 781 ; N uni28AE ; G 2845
+U 10415 ; WX 781 ; N uni28AF ; G 2846
+U 10416 ; WX 781 ; N uni28B0 ; G 2847
+U 10417 ; WX 781 ; N uni28B1 ; G 2848
+U 10418 ; WX 781 ; N uni28B2 ; G 2849
+U 10419 ; WX 781 ; N uni28B3 ; G 2850
+U 10420 ; WX 781 ; N uni28B4 ; G 2851
+U 10421 ; WX 781 ; N uni28B5 ; G 2852
+U 10422 ; WX 781 ; N uni28B6 ; G 2853
+U 10423 ; WX 781 ; N uni28B7 ; G 2854
+U 10424 ; WX 781 ; N uni28B8 ; G 2855
+U 10425 ; WX 781 ; N uni28B9 ; G 2856
+U 10426 ; WX 781 ; N uni28BA ; G 2857
+U 10427 ; WX 781 ; N uni28BB ; G 2858
+U 10428 ; WX 781 ; N uni28BC ; G 2859
+U 10429 ; WX 781 ; N uni28BD ; G 2860
+U 10430 ; WX 781 ; N uni28BE ; G 2861
+U 10431 ; WX 781 ; N uni28BF ; G 2862
+U 10432 ; WX 781 ; N uni28C0 ; G 2863
+U 10433 ; WX 781 ; N uni28C1 ; G 2864
+U 10434 ; WX 781 ; N uni28C2 ; G 2865
+U 10435 ; WX 781 ; N uni28C3 ; G 2866
+U 10436 ; WX 781 ; N uni28C4 ; G 2867
+U 10437 ; WX 781 ; N uni28C5 ; G 2868
+U 10438 ; WX 781 ; N uni28C6 ; G 2869
+U 10439 ; WX 781 ; N uni28C7 ; G 2870
+U 10440 ; WX 781 ; N uni28C8 ; G 2871
+U 10441 ; WX 781 ; N uni28C9 ; G 2872
+U 10442 ; WX 781 ; N uni28CA ; G 2873
+U 10443 ; WX 781 ; N uni28CB ; G 2874
+U 10444 ; WX 781 ; N uni28CC ; G 2875
+U 10445 ; WX 781 ; N uni28CD ; G 2876
+U 10446 ; WX 781 ; N uni28CE ; G 2877
+U 10447 ; WX 781 ; N uni28CF ; G 2878
+U 10448 ; WX 781 ; N uni28D0 ; G 2879
+U 10449 ; WX 781 ; N uni28D1 ; G 2880
+U 10450 ; WX 781 ; N uni28D2 ; G 2881
+U 10451 ; WX 781 ; N uni28D3 ; G 2882
+U 10452 ; WX 781 ; N uni28D4 ; G 2883
+U 10453 ; WX 781 ; N uni28D5 ; G 2884
+U 10454 ; WX 781 ; N uni28D6 ; G 2885
+U 10455 ; WX 781 ; N uni28D7 ; G 2886
+U 10456 ; WX 781 ; N uni28D8 ; G 2887
+U 10457 ; WX 781 ; N uni28D9 ; G 2888
+U 10458 ; WX 781 ; N uni28DA ; G 2889
+U 10459 ; WX 781 ; N uni28DB ; G 2890
+U 10460 ; WX 781 ; N uni28DC ; G 2891
+U 10461 ; WX 781 ; N uni28DD ; G 2892
+U 10462 ; WX 781 ; N uni28DE ; G 2893
+U 10463 ; WX 781 ; N uni28DF ; G 2894
+U 10464 ; WX 781 ; N uni28E0 ; G 2895
+U 10465 ; WX 781 ; N uni28E1 ; G 2896
+U 10466 ; WX 781 ; N uni28E2 ; G 2897
+U 10467 ; WX 781 ; N uni28E3 ; G 2898
+U 10468 ; WX 781 ; N uni28E4 ; G 2899
+U 10469 ; WX 781 ; N uni28E5 ; G 2900
+U 10470 ; WX 781 ; N uni28E6 ; G 2901
+U 10471 ; WX 781 ; N uni28E7 ; G 2902
+U 10472 ; WX 781 ; N uni28E8 ; G 2903
+U 10473 ; WX 781 ; N uni28E9 ; G 2904
+U 10474 ; WX 781 ; N uni28EA ; G 2905
+U 10475 ; WX 781 ; N uni28EB ; G 2906
+U 10476 ; WX 781 ; N uni28EC ; G 2907
+U 10477 ; WX 781 ; N uni28ED ; G 2908
+U 10478 ; WX 781 ; N uni28EE ; G 2909
+U 10479 ; WX 781 ; N uni28EF ; G 2910
+U 10480 ; WX 781 ; N uni28F0 ; G 2911
+U 10481 ; WX 781 ; N uni28F1 ; G 2912
+U 10482 ; WX 781 ; N uni28F2 ; G 2913
+U 10483 ; WX 781 ; N uni28F3 ; G 2914
+U 10484 ; WX 781 ; N uni28F4 ; G 2915
+U 10485 ; WX 781 ; N uni28F5 ; G 2916
+U 10486 ; WX 781 ; N uni28F6 ; G 2917
+U 10487 ; WX 781 ; N uni28F7 ; G 2918
+U 10488 ; WX 781 ; N uni28F8 ; G 2919
+U 10489 ; WX 781 ; N uni28F9 ; G 2920
+U 10490 ; WX 781 ; N uni28FA ; G 2921
+U 10491 ; WX 781 ; N uni28FB ; G 2922
+U 10492 ; WX 781 ; N uni28FC ; G 2923
+U 10493 ; WX 781 ; N uni28FD ; G 2924
+U 10494 ; WX 781 ; N uni28FE ; G 2925
+U 10495 ; WX 781 ; N uni28FF ; G 2926
+U 10496 ; WX 838 ; N uni2900 ; G 2927
+U 10497 ; WX 838 ; N uni2901 ; G 2928
+U 10498 ; WX 838 ; N uni2902 ; G 2929
+U 10499 ; WX 838 ; N uni2903 ; G 2930
+U 10500 ; WX 838 ; N uni2904 ; G 2931
+U 10501 ; WX 838 ; N uni2905 ; G 2932
+U 10502 ; WX 838 ; N uni2906 ; G 2933
+U 10503 ; WX 838 ; N uni2907 ; G 2934
+U 10504 ; WX 838 ; N uni2908 ; G 2935
+U 10505 ; WX 838 ; N uni2909 ; G 2936
+U 10506 ; WX 838 ; N uni290A ; G 2937
+U 10507 ; WX 838 ; N uni290B ; G 2938
+U 10508 ; WX 838 ; N uni290C ; G 2939
+U 10509 ; WX 838 ; N uni290D ; G 2940
+U 10510 ; WX 838 ; N uni290E ; G 2941
+U 10511 ; WX 838 ; N uni290F ; G 2942
+U 10512 ; WX 838 ; N uni2910 ; G 2943
+U 10513 ; WX 838 ; N uni2911 ; G 2944
+U 10514 ; WX 838 ; N uni2912 ; G 2945
+U 10515 ; WX 838 ; N uni2913 ; G 2946
+U 10516 ; WX 838 ; N uni2914 ; G 2947
+U 10517 ; WX 838 ; N uni2915 ; G 2948
+U 10518 ; WX 838 ; N uni2916 ; G 2949
+U 10519 ; WX 838 ; N uni2917 ; G 2950
+U 10520 ; WX 838 ; N uni2918 ; G 2951
+U 10521 ; WX 838 ; N uni2919 ; G 2952
+U 10522 ; WX 838 ; N uni291A ; G 2953
+U 10523 ; WX 838 ; N uni291B ; G 2954
+U 10524 ; WX 838 ; N uni291C ; G 2955
+U 10525 ; WX 838 ; N uni291D ; G 2956
+U 10526 ; WX 838 ; N uni291E ; G 2957
+U 10527 ; WX 838 ; N uni291F ; G 2958
+U 10528 ; WX 838 ; N uni2920 ; G 2959
+U 10529 ; WX 838 ; N uni2921 ; G 2960
+U 10530 ; WX 838 ; N uni2922 ; G 2961
+U 10531 ; WX 838 ; N uni2923 ; G 2962
+U 10532 ; WX 838 ; N uni2924 ; G 2963
+U 10533 ; WX 838 ; N uni2925 ; G 2964
+U 10534 ; WX 838 ; N uni2926 ; G 2965
+U 10535 ; WX 838 ; N uni2927 ; G 2966
+U 10536 ; WX 838 ; N uni2928 ; G 2967
+U 10537 ; WX 838 ; N uni2929 ; G 2968
+U 10538 ; WX 838 ; N uni292A ; G 2969
+U 10539 ; WX 838 ; N uni292B ; G 2970
+U 10540 ; WX 838 ; N uni292C ; G 2971
+U 10541 ; WX 838 ; N uni292D ; G 2972
+U 10542 ; WX 838 ; N uni292E ; G 2973
+U 10543 ; WX 838 ; N uni292F ; G 2974
+U 10544 ; WX 838 ; N uni2930 ; G 2975
+U 10545 ; WX 838 ; N uni2931 ; G 2976
+U 10546 ; WX 838 ; N uni2932 ; G 2977
+U 10547 ; WX 838 ; N uni2933 ; G 2978
+U 10548 ; WX 838 ; N uni2934 ; G 2979
+U 10549 ; WX 838 ; N uni2935 ; G 2980
+U 10550 ; WX 838 ; N uni2936 ; G 2981
+U 10551 ; WX 838 ; N uni2937 ; G 2982
+U 10552 ; WX 838 ; N uni2938 ; G 2983
+U 10553 ; WX 838 ; N uni2939 ; G 2984
+U 10554 ; WX 838 ; N uni293A ; G 2985
+U 10555 ; WX 838 ; N uni293B ; G 2986
+U 10556 ; WX 838 ; N uni293C ; G 2987
+U 10557 ; WX 838 ; N uni293D ; G 2988
+U 10558 ; WX 838 ; N uni293E ; G 2989
+U 10559 ; WX 838 ; N uni293F ; G 2990
+U 10560 ; WX 838 ; N uni2940 ; G 2991
+U 10561 ; WX 838 ; N uni2941 ; G 2992
+U 10562 ; WX 838 ; N uni2942 ; G 2993
+U 10563 ; WX 838 ; N uni2943 ; G 2994
+U 10564 ; WX 838 ; N uni2944 ; G 2995
+U 10565 ; WX 838 ; N uni2945 ; G 2996
+U 10566 ; WX 838 ; N uni2946 ; G 2997
+U 10567 ; WX 838 ; N uni2947 ; G 2998
+U 10568 ; WX 838 ; N uni2948 ; G 2999
+U 10569 ; WX 838 ; N uni2949 ; G 3000
+U 10570 ; WX 838 ; N uni294A ; G 3001
+U 10571 ; WX 838 ; N uni294B ; G 3002
+U 10572 ; WX 838 ; N uni294C ; G 3003
+U 10573 ; WX 838 ; N uni294D ; G 3004
+U 10574 ; WX 838 ; N uni294E ; G 3005
+U 10575 ; WX 838 ; N uni294F ; G 3006
+U 10576 ; WX 838 ; N uni2950 ; G 3007
+U 10577 ; WX 838 ; N uni2951 ; G 3008
+U 10578 ; WX 838 ; N uni2952 ; G 3009
+U 10579 ; WX 838 ; N uni2953 ; G 3010
+U 10580 ; WX 838 ; N uni2954 ; G 3011
+U 10581 ; WX 838 ; N uni2955 ; G 3012
+U 10582 ; WX 838 ; N uni2956 ; G 3013
+U 10583 ; WX 838 ; N uni2957 ; G 3014
+U 10584 ; WX 838 ; N uni2958 ; G 3015
+U 10585 ; WX 838 ; N uni2959 ; G 3016
+U 10586 ; WX 838 ; N uni295A ; G 3017
+U 10587 ; WX 838 ; N uni295B ; G 3018
+U 10588 ; WX 838 ; N uni295C ; G 3019
+U 10589 ; WX 838 ; N uni295D ; G 3020
+U 10590 ; WX 838 ; N uni295E ; G 3021
+U 10591 ; WX 838 ; N uni295F ; G 3022
+U 10592 ; WX 838 ; N uni2960 ; G 3023
+U 10593 ; WX 838 ; N uni2961 ; G 3024
+U 10594 ; WX 838 ; N uni2962 ; G 3025
+U 10595 ; WX 838 ; N uni2963 ; G 3026
+U 10596 ; WX 838 ; N uni2964 ; G 3027
+U 10597 ; WX 838 ; N uni2965 ; G 3028
+U 10598 ; WX 838 ; N uni2966 ; G 3029
+U 10599 ; WX 838 ; N uni2967 ; G 3030
+U 10600 ; WX 838 ; N uni2968 ; G 3031
+U 10601 ; WX 838 ; N uni2969 ; G 3032
+U 10602 ; WX 838 ; N uni296A ; G 3033
+U 10603 ; WX 838 ; N uni296B ; G 3034
+U 10604 ; WX 838 ; N uni296C ; G 3035
+U 10605 ; WX 838 ; N uni296D ; G 3036
+U 10606 ; WX 838 ; N uni296E ; G 3037
+U 10607 ; WX 838 ; N uni296F ; G 3038
+U 10608 ; WX 838 ; N uni2970 ; G 3039
+U 10609 ; WX 838 ; N uni2971 ; G 3040
+U 10610 ; WX 838 ; N uni2972 ; G 3041
+U 10611 ; WX 838 ; N uni2973 ; G 3042
+U 10612 ; WX 838 ; N uni2974 ; G 3043
+U 10613 ; WX 838 ; N uni2975 ; G 3044
+U 10614 ; WX 838 ; N uni2976 ; G 3045
+U 10615 ; WX 1032 ; N uni2977 ; G 3046
+U 10616 ; WX 838 ; N uni2978 ; G 3047
+U 10617 ; WX 838 ; N uni2979 ; G 3048
+U 10618 ; WX 960 ; N uni297A ; G 3049
+U 10619 ; WX 838 ; N uni297B ; G 3050
+U 10620 ; WX 838 ; N uni297C ; G 3051
+U 10621 ; WX 838 ; N uni297D ; G 3052
+U 10622 ; WX 838 ; N uni297E ; G 3053
+U 10623 ; WX 838 ; N uni297F ; G 3054
+U 10731 ; WX 494 ; N uni29EB ; G 3055
+U 10764 ; WX 1782 ; N uni2A0C ; G 3056
+U 10765 ; WX 610 ; N uni2A0D ; G 3057
+U 10766 ; WX 610 ; N uni2A0E ; G 3058
+U 10799 ; WX 838 ; N uni2A2F ; G 3059
+U 10858 ; WX 838 ; N uni2A6A ; G 3060
+U 10859 ; WX 838 ; N uni2A6B ; G 3061
+U 11008 ; WX 838 ; N uni2B00 ; G 3062
+U 11009 ; WX 838 ; N uni2B01 ; G 3063
+U 11010 ; WX 838 ; N uni2B02 ; G 3064
+U 11011 ; WX 838 ; N uni2B03 ; G 3065
+U 11012 ; WX 838 ; N uni2B04 ; G 3066
+U 11013 ; WX 838 ; N uni2B05 ; G 3067
+U 11014 ; WX 838 ; N uni2B06 ; G 3068
+U 11015 ; WX 838 ; N uni2B07 ; G 3069
+U 11016 ; WX 838 ; N uni2B08 ; G 3070
+U 11017 ; WX 838 ; N uni2B09 ; G 3071
+U 11018 ; WX 838 ; N uni2B0A ; G 3072
+U 11019 ; WX 838 ; N uni2B0B ; G 3073
+U 11020 ; WX 838 ; N uni2B0C ; G 3074
+U 11021 ; WX 838 ; N uni2B0D ; G 3075
+U 11022 ; WX 838 ; N uni2B0E ; G 3076
+U 11023 ; WX 838 ; N uni2B0F ; G 3077
+U 11024 ; WX 838 ; N uni2B10 ; G 3078
+U 11025 ; WX 838 ; N uni2B11 ; G 3079
+U 11026 ; WX 945 ; N uni2B12 ; G 3080
+U 11027 ; WX 945 ; N uni2B13 ; G 3081
+U 11028 ; WX 945 ; N uni2B14 ; G 3082
+U 11029 ; WX 945 ; N uni2B15 ; G 3083
+U 11030 ; WX 769 ; N uni2B16 ; G 3084
+U 11031 ; WX 769 ; N uni2B17 ; G 3085
+U 11032 ; WX 769 ; N uni2B18 ; G 3086
+U 11033 ; WX 769 ; N uni2B19 ; G 3087
+U 11034 ; WX 945 ; N uni2B1A ; G 3088
+U 11360 ; WX 703 ; N uni2C60 ; G 3089
+U 11361 ; WX 380 ; N uni2C61 ; G 3090
+U 11363 ; WX 752 ; N uni2C63 ; G 3091
+U 11364 ; WX 831 ; N uni2C64 ; G 3092
+U 11367 ; WX 945 ; N uni2C67 ; G 3093
+U 11368 ; WX 727 ; N uni2C68 ; G 3094
+U 11369 ; WX 869 ; N uni2C69 ; G 3095
+U 11370 ; WX 693 ; N uni2C6A ; G 3096
+U 11371 ; WX 730 ; N uni2C6B ; G 3097
+U 11372 ; WX 568 ; N uni2C6C ; G 3098
+U 11373 ; WX 848 ; N uni2C6D ; G 3099
+U 11374 ; WX 1107 ; N uni2C6E ; G 3100
+U 11375 ; WX 776 ; N uni2C6F ; G 3101
+U 11376 ; WX 848 ; N uni2C70 ; G 3102
+U 11377 ; WX 709 ; N uni2C71 ; G 3103
+U 11378 ; WX 1221 ; N uni2C72 ; G 3104
+U 11379 ; WX 984 ; N uni2C73 ; G 3105
+U 11381 ; WX 779 ; N uni2C75 ; G 3106
+U 11382 ; WX 601 ; N uni2C76 ; G 3107
+U 11383 ; WX 905 ; N uni2C77 ; G 3108
+U 11385 ; WX 571 ; N uni2C79 ; G 3109
+U 11386 ; WX 667 ; N uni2C7A ; G 3110
+U 11387 ; WX 617 ; N uni2C7B ; G 3111
+U 11388 ; WX 313 ; N uni2C7C ; G 3112
+U 11389 ; WX 489 ; N uni2C7D ; G 3113
+U 11390 ; WX 722 ; N uni2C7E ; G 3114
+U 11391 ; WX 730 ; N uni2C7F ; G 3115
+U 11520 ; WX 773 ; N uni2D00 ; G 3116
+U 11521 ; WX 635 ; N uni2D01 ; G 3117
+U 11522 ; WX 804 ; N uni2D02 ; G 3118
+U 11523 ; WX 658 ; N uni2D03 ; G 3119
+U 11524 ; WX 788 ; N uni2D04 ; G 3120
+U 11525 ; WX 962 ; N uni2D05 ; G 3121
+U 11526 ; WX 756 ; N uni2D06 ; G 3122
+U 11527 ; WX 960 ; N uni2D07 ; G 3123
+U 11528 ; WX 617 ; N uni2D08 ; G 3124
+U 11529 ; WX 646 ; N uni2D09 ; G 3125
+U 11530 ; WX 962 ; N uni2D0A ; G 3126
+U 11531 ; WX 631 ; N uni2D0B ; G 3127
+U 11532 ; WX 646 ; N uni2D0C ; G 3128
+U 11533 ; WX 962 ; N uni2D0D ; G 3129
+U 11534 ; WX 846 ; N uni2D0E ; G 3130
+U 11535 ; WX 866 ; N uni2D0F ; G 3131
+U 11536 ; WX 961 ; N uni2D10 ; G 3132
+U 11537 ; WX 645 ; N uni2D11 ; G 3133
+U 11538 ; WX 645 ; N uni2D12 ; G 3134
+U 11539 ; WX 959 ; N uni2D13 ; G 3135
+U 11540 ; WX 945 ; N uni2D14 ; G 3136
+U 11541 ; WX 863 ; N uni2D15 ; G 3137
+U 11542 ; WX 644 ; N uni2D16 ; G 3138
+U 11543 ; WX 646 ; N uni2D17 ; G 3139
+U 11544 ; WX 645 ; N uni2D18 ; G 3140
+U 11545 ; WX 649 ; N uni2D19 ; G 3141
+U 11546 ; WX 688 ; N uni2D1A ; G 3142
+U 11547 ; WX 936 ; N uni2D1B ; G 3143
+U 11548 ; WX 982 ; N uni2D1C ; G 3144
+U 11549 ; WX 681 ; N uni2D1D ; G 3145
+U 11550 ; WX 676 ; N uni2D1E ; G 3146
+U 11551 ; WX 852 ; N uni2D1F ; G 3147
+U 11552 ; WX 1113 ; N uni2D20 ; G 3148
+U 11553 ; WX 632 ; N uni2D21 ; G 3149
+U 11554 ; WX 645 ; N uni2D22 ; G 3150
+U 11555 ; WX 646 ; N uni2D23 ; G 3151
+U 11556 ; WX 749 ; N uni2D24 ; G 3152
+U 11557 ; WX 914 ; N uni2D25 ; G 3153
+U 11800 ; WX 586 ; N uni2E18 ; G 3154
+U 11807 ; WX 838 ; N uni2E1F ; G 3155
+U 11810 ; WX 473 ; N uni2E22 ; G 3156
+U 11811 ; WX 473 ; N uni2E23 ; G 3157
+U 11812 ; WX 473 ; N uni2E24 ; G 3158
+U 11813 ; WX 473 ; N uni2E25 ; G 3159
+U 11822 ; WX 586 ; N uni2E2E ; G 3160
+U 42564 ; WX 722 ; N uniA644 ; G 3161
+U 42565 ; WX 563 ; N uniA645 ; G 3162
+U 42566 ; WX 468 ; N uniA646 ; G 3163
+U 42567 ; WX 380 ; N uniA647 ; G 3164
+U 42576 ; WX 1333 ; N uniA650 ; G 3165
+U 42577 ; WX 1092 ; N uniA651 ; G 3166
+U 42580 ; WX 1287 ; N uniA654 ; G 3167
+U 42581 ; WX 1025 ; N uniA655 ; G 3168
+U 42582 ; WX 1287 ; N uniA656 ; G 3169
+U 42583 ; WX 1039 ; N uniA657 ; G 3170
+U 42648 ; WX 1448 ; N uniA698 ; G 3171
+U 42649 ; WX 1060 ; N uniA699 ; G 3172
+U 42760 ; WX 500 ; N uniA708 ; G 3173
+U 42761 ; WX 500 ; N uniA709 ; G 3174
+U 42762 ; WX 500 ; N uniA70A ; G 3175
+U 42763 ; WX 500 ; N uniA70B ; G 3176
+U 42764 ; WX 500 ; N uniA70C ; G 3177
+U 42765 ; WX 500 ; N uniA70D ; G 3178
+U 42766 ; WX 500 ; N uniA70E ; G 3179
+U 42767 ; WX 500 ; N uniA70F ; G 3180
+U 42768 ; WX 500 ; N uniA710 ; G 3181
+U 42769 ; WX 500 ; N uniA711 ; G 3182
+U 42770 ; WX 500 ; N uniA712 ; G 3183
+U 42771 ; WX 500 ; N uniA713 ; G 3184
+U 42772 ; WX 500 ; N uniA714 ; G 3185
+U 42773 ; WX 500 ; N uniA715 ; G 3186
+U 42774 ; WX 500 ; N uniA716 ; G 3187
+U 42779 ; WX 384 ; N uniA71B ; G 3188
+U 42780 ; WX 384 ; N uniA71C ; G 3189
+U 42781 ; WX 276 ; N uniA71D ; G 3190
+U 42782 ; WX 276 ; N uniA71E ; G 3191
+U 42783 ; WX 276 ; N uniA71F ; G 3192
+U 42790 ; WX 945 ; N uniA726 ; G 3193
+U 42791 ; WX 712 ; N uniA727 ; G 3194
+U 42792 ; WX 1003 ; N uniA728 ; G 3195
+U 42793 ; WX 909 ; N uniA729 ; G 3196
+U 42794 ; WX 696 ; N uniA72A ; G 3197
+U 42795 ; WX 609 ; N uniA72B ; G 3198
+U 42796 ; WX 634 ; N uniA72C ; G 3199
+U 42797 ; WX 598 ; N uniA72D ; G 3200
+U 42798 ; WX 741 ; N uniA72E ; G 3201
+U 42799 ; WX 706 ; N uniA72F ; G 3202
+U 42800 ; WX 592 ; N uniA730 ; G 3203
+U 42801 ; WX 563 ; N uniA731 ; G 3204
+U 42802 ; WX 1301 ; N uniA732 ; G 3205
+U 42803 ; WX 986 ; N uniA733 ; G 3206
+U 42804 ; WX 1261 ; N uniA734 ; G 3207
+U 42805 ; WX 1004 ; N uniA735 ; G 3208
+U 42806 ; WX 1168 ; N uniA736 ; G 3209
+U 42807 ; WX 1008 ; N uniA737 ; G 3210
+U 42808 ; WX 1016 ; N uniA738 ; G 3211
+U 42809 ; WX 813 ; N uniA739 ; G 3212
+U 42810 ; WX 1016 ; N uniA73A ; G 3213
+U 42811 ; WX 813 ; N uniA73B ; G 3214
+U 42812 ; WX 994 ; N uniA73C ; G 3215
+U 42813 ; WX 847 ; N uniA73D ; G 3216
+U 42814 ; WX 796 ; N uniA73E ; G 3217
+U 42815 ; WX 609 ; N uniA73F ; G 3218
+U 42816 ; WX 910 ; N uniA740 ; G 3219
+U 42817 ; WX 722 ; N uniA741 ; G 3220
+U 42822 ; WX 916 ; N uniA746 ; G 3221
+U 42823 ; WX 581 ; N uniA747 ; G 3222
+U 42826 ; WX 1010 ; N uniA74A ; G 3223
+U 42827 ; WX 770 ; N uniA74B ; G 3224
+U 42830 ; WX 1448 ; N uniA74E ; G 3225
+U 42831 ; WX 1060 ; N uniA74F ; G 3226
+U 42856 ; WX 787 ; N uniA768 ; G 3227
+U 42857 ; WX 716 ; N uniA769 ; G 3228
+U 42875 ; WX 694 ; N uniA77B ; G 3229
+U 42876 ; WX 527 ; N uniA77C ; G 3230
+U 42880 ; WX 703 ; N uniA780 ; G 3231
+U 42881 ; WX 380 ; N uniA781 ; G 3232
+U 42882 ; WX 872 ; N uniA782 ; G 3233
+U 42883 ; WX 727 ; N uniA783 ; G 3234
+U 42884 ; WX 694 ; N uniA784 ; G 3235
+U 42885 ; WX 527 ; N uniA785 ; G 3236
+U 42886 ; WX 796 ; N uniA786 ; G 3237
+U 42887 ; WX 609 ; N uniA787 ; G 3238
+U 42891 ; WX 439 ; N uniA78B ; G 3239
+U 42892 ; WX 306 ; N uniA78C ; G 3240
+U 42893 ; WX 913 ; N uniA78D ; G 3241
+U 42896 ; WX 914 ; N uniA790 ; G 3242
+U 42897 ; WX 727 ; N uniA791 ; G 3243
+U 42922 ; WX 945 ; N uniA7AA ; G 3244
+U 43000 ; WX 595 ; N uniA7F8 ; G 3245
+U 43001 ; WX 647 ; N uniA7F9 ; G 3246
+U 43002 ; WX 1069 ; N uniA7FA ; G 3247
+U 43003 ; WX 710 ; N uniA7FB ; G 3248
+U 43004 ; WX 752 ; N uniA7FC ; G 3249
+U 43005 ; WX 1107 ; N uniA7FD ; G 3250
+U 43006 ; WX 468 ; N uniA7FE ; G 3251
+U 43007 ; WX 1286 ; N uniA7FF ; G 3252
+U 62464 ; WX 705 ; N uniF400 ; G 3253
+U 62465 ; WX 716 ; N uniF401 ; G 3254
+U 62466 ; WX 765 ; N uniF402 ; G 3255
+U 62467 ; WX 999 ; N uniF403 ; G 3256
+U 62468 ; WX 716 ; N uniF404 ; G 3257
+U 62469 ; WX 710 ; N uniF405 ; G 3258
+U 62470 ; WX 776 ; N uniF406 ; G 3259
+U 62471 ; WX 1038 ; N uniF407 ; G 3260
+U 62472 ; WX 716 ; N uniF408 ; G 3261
+U 62473 ; WX 716 ; N uniF409 ; G 3262
+U 62474 ; WX 1309 ; N uniF40A ; G 3263
+U 62475 ; WX 734 ; N uniF40B ; G 3264
+U 62476 ; WX 733 ; N uniF40C ; G 3265
+U 62477 ; WX 1004 ; N uniF40D ; G 3266
+U 62478 ; WX 716 ; N uniF40E ; G 3267
+U 62479 ; WX 733 ; N uniF40F ; G 3268
+U 62480 ; WX 1050 ; N uniF410 ; G 3269
+U 62481 ; WX 797 ; N uniF411 ; G 3270
+U 62482 ; WX 850 ; N uniF412 ; G 3271
+U 62483 ; WX 799 ; N uniF413 ; G 3272
+U 62484 ; WX 996 ; N uniF414 ; G 3273
+U 62485 ; WX 732 ; N uniF415 ; G 3274
+U 62486 ; WX 987 ; N uniF416 ; G 3275
+U 62487 ; WX 731 ; N uniF417 ; G 3276
+U 62488 ; WX 739 ; N uniF418 ; G 3277
+U 62489 ; WX 733 ; N uniF419 ; G 3278
+U 62490 ; WX 780 ; N uniF41A ; G 3279
+U 62491 ; WX 733 ; N uniF41B ; G 3280
+U 62492 ; WX 739 ; N uniF41C ; G 3281
+U 62493 ; WX 717 ; N uniF41D ; G 3282
+U 62494 ; WX 780 ; N uniF41E ; G 3283
+U 62495 ; WX 936 ; N uniF41F ; G 3284
+U 62496 ; WX 716 ; N uniF420 ; G 3285
+U 62497 ; WX 826 ; N uniF421 ; G 3286
+U 62498 ; WX 717 ; N uniF422 ; G 3287
+U 62499 ; WX 716 ; N uniF423 ; G 3288
+U 62500 ; WX 716 ; N uniF424 ; G 3289
+U 62501 ; WX 773 ; N uniF425 ; G 3290
+U 62502 ; WX 1013 ; N uniF426 ; G 3291
+U 62504 ; WX 904 ; N uniF428 ; G 3292
+U 63173 ; WX 667 ; N uniF6C5 ; G 3293
+U 63185 ; WX 500 ; N cyrBreve ; G 3294
+U 63188 ; WX 500 ; N cyrbreve ; G 3295
+U 64256 ; WX 821 ; N uniFB00 ; G 3296
+U 64257 ; WX 727 ; N fi ; G 3297
+U 64258 ; WX 727 ; N fl ; G 3298
+U 64259 ; WX 1120 ; N uniFB03 ; G 3299
+U 64260 ; WX 1117 ; N uniFB04 ; G 3300
+U 64261 ; WX 871 ; N uniFB05 ; G 3301
+U 64262 ; WX 971 ; N uniFB06 ; G 3302
+U 65024 ; WX 0 ; N uniFE00 ; G 3303
+U 65025 ; WX 0 ; N uniFE01 ; G 3304
+U 65026 ; WX 0 ; N uniFE02 ; G 3305
+U 65027 ; WX 0 ; N uniFE03 ; G 3306
+U 65028 ; WX 0 ; N uniFE04 ; G 3307
+U 65029 ; WX 0 ; N uniFE05 ; G 3308
+U 65030 ; WX 0 ; N uniFE06 ; G 3309
+U 65031 ; WX 0 ; N uniFE07 ; G 3310
+U 65032 ; WX 0 ; N uniFE08 ; G 3311
+U 65033 ; WX 0 ; N uniFE09 ; G 3312
+U 65034 ; WX 0 ; N uniFE0A ; G 3313
+U 65035 ; WX 0 ; N uniFE0B ; G 3314
+U 65036 ; WX 0 ; N uniFE0C ; G 3315
+U 65037 ; WX 0 ; N uniFE0D ; G 3316
+U 65038 ; WX 0 ; N uniFE0E ; G 3317
+U 65039 ; WX 0 ; N uniFE0F ; G 3318
+U 65529 ; WX 0 ; N uniFFF9 ; G 3319
+U 65530 ; WX 0 ; N uniFFFA ; G 3320
+U 65531 ; WX 0 ; N uniFFFB ; G 3321
+U 65532 ; WX 0 ; N uniFFFC ; G 3322
+U 65533 ; WX 1113 ; N uniFFFD ; G 3323
+EndCharMetrics
+StartKernData
+StartKernPairs 1408
+
+KPX dollar seven -112
+KPX dollar nine -149
+KPX dollar colon -102
+KPX dollar less -102
+KPX dollar I -36
+KPX dollar W -36
+KPX dollar Y -83
+KPX dollar Z -83
+KPX dollar backslash -83
+KPX dollar questiondown -83
+KPX dollar Aacute -83
+KPX dollar Hcircumflex -112
+KPX dollar hcircumflex -36
+KPX dollar Hbar -112
+KPX dollar hbar -36
+KPX dollar Kcommaaccent -102
+KPX dollar kcommaaccent -83
+KPX dollar kgreenlandic -102
+KPX dollar Lacute -83
+KPX dollar lacute -102
+KPX dollar uni01DC -112
+KPX dollar uni01DD -36
+KPX dollar uni01F4 -102
+KPX dollar uni01F5 -83
+
+KPX percent ampersand 38
+KPX percent asterisk 38
+KPX percent two 38
+KPX percent less -36
+KPX percent Egrave 38
+KPX percent Ecircumflex 38
+KPX percent Igrave 38
+KPX percent Icircumflex 38
+KPX percent Thorn 38
+KPX percent agrave 38
+KPX percent acircumflex 38
+KPX percent adieresis 38
+KPX percent Dcaron 38
+KPX percent Dcroat 38
+KPX percent Emacron 38
+KPX percent Ebreve 38
+KPX percent kgreenlandic -36
+KPX percent lacute -36
+KPX percent uni01AC 38
+KPX percent uni01AE 38
+KPX percent uni01F0 38
+KPX percent uni01F4 -36
+
+
+KPX quotesingle nine -36
+
+
+KPX parenright dollar -120
+KPX parenright D -112
+KPX parenright H -112
+KPX parenright R -112
+KPX parenright U -36
+KPX parenright X -36
+KPX parenright cent -112
+KPX parenright sterling -112
+KPX parenright currency -112
+KPX parenright yen -112
+KPX parenright brokenbar -112
+KPX parenright section -112
+KPX parenright dieresis -112
+KPX parenright ordfeminine -112
+KPX parenright guillemotleft -112
+KPX parenright logicalnot -112
+KPX parenright sfthyphen -112
+KPX parenright acute -112
+KPX parenright mu -112
+KPX parenright paragraph -112
+KPX parenright periodcentered -112
+KPX parenright cedilla -112
+KPX parenright ordmasculine -112
+KPX parenright guillemotright -36
+KPX parenright onequarter -36
+KPX parenright onehalf -36
+KPX parenright threequarters -36
+KPX parenright Acircumflex -120
+KPX parenright Atilde -112
+KPX parenright Adieresis -120
+KPX parenright Aring -112
+KPX parenright AE -120
+KPX parenright Ccedilla -112
+KPX parenright Otilde -112
+KPX parenright multiply -112
+KPX parenright Ugrave -112
+KPX parenright Ucircumflex -112
+KPX parenright Yacute -112
+KPX parenright dcaron -112
+KPX parenright dmacron -112
+KPX parenright emacron -112
+KPX parenright ebreve -112
+KPX parenright edotaccent -36
+KPX parenright eogonek -36
+KPX parenright ecaron -36
+KPX parenright imacron -36
+KPX parenright ibreve -36
+KPX parenright iogonek -36
+KPX parenright dotlessi -36
+KPX parenright ij -36
+KPX parenright jcircumflex -36
+KPX parenright uni01A5 -112
+KPX parenright uni01AD -112
+KPX parenright Uhorn -112
+KPX parenright uni01F1 -112
+
+
+
+KPX period dollar -83
+KPX period ampersand -55
+KPX period two -55
+KPX period eight -73
+KPX period colon -73
+KPX period less -55
+KPX period H -45
+KPX period R -45
+KPX period X -45
+KPX period backslash -92
+KPX period ordfeminine -45
+KPX period guillemotleft -45
+KPX period logicalnot -45
+KPX period sfthyphen -45
+KPX period acute -45
+KPX period mu -45
+KPX period paragraph -45
+KPX period periodcentered -45
+KPX period cedilla -45
+KPX period ordmasculine -36
+KPX period guillemotright -45
+KPX period onequarter -45
+KPX period onehalf -45
+KPX period threequarters -45
+KPX period questiondown -92
+KPX period Aacute -92
+KPX period Egrave -55
+KPX period Icircumflex -55
+KPX period Yacute -45
+KPX period Ebreve -55
+KPX period ebreve -45
+KPX period Idot -73
+KPX period dotlessi -45
+KPX period lacute -55
+
+KPX slash seven -167
+KPX slash eight -112
+KPX slash nine -243
+KPX slash colon -139
+KPX slash less -131
+KPX slash backslash -73
+KPX slash questiondown -73
+KPX slash Aacute -73
+KPX slash Hbar -167
+KPX slash Idot -112
+KPX slash lacute -131
+
+
+KPX two nine -36
+KPX two semicolon -36
+
+KPX three dollar -149
+KPX three D -55
+KPX three H -55
+KPX three R -55
+KPX three cent -55
+KPX three sterling -55
+KPX three currency -55
+KPX three yen -55
+KPX three brokenbar -55
+KPX three section -55
+KPX three dieresis -55
+KPX three ordfeminine -55
+KPX three guillemotleft -55
+KPX three logicalnot -55
+KPX three sfthyphen -55
+KPX three acute -55
+KPX three mu -55
+KPX three paragraph -55
+KPX three periodcentered -55
+KPX three cedilla -55
+KPX three ordmasculine -55
+KPX three Yacute -55
+KPX three ebreve -55
+
+
+KPX five seven -36
+KPX five nine -73
+KPX five colon -45
+KPX five less -63
+KPX five D 47
+KPX five backslash -36
+KPX five cent 47
+KPX five sterling 47
+KPX five currency 47
+KPX five yen 47
+KPX five brokenbar 47
+KPX five section 47
+KPX five dieresis 47
+KPX five ordmasculine 38
+KPX five questiondown -36
+KPX five Aacute -36
+KPX five Hbar -36
+KPX five lacute -63
+
+KPX six six -45
+KPX six Gdotaccent -45
+KPX six Gcommaaccent -45
+
+KPX seven dollar -112
+KPX seven seven -73
+KPX seven D -196
+KPX seven F -235
+KPX seven H -235
+KPX seven R -235
+KPX seven U -149
+KPX seven V -188
+KPX seven X -188
+KPX seven Z -225
+KPX seven backslash -225
+KPX seven m -149
+KPX seven braceright -149
+KPX seven cent -96
+KPX seven sterling -196
+KPX seven currency -96
+KPX seven yen -96
+KPX seven brokenbar -96
+KPX seven section -96
+KPX seven dieresis -159
+KPX seven copyright -235
+KPX seven ordfeminine -175
+KPX seven guillemotleft -235
+KPX seven logicalnot -175
+KPX seven sfthyphen -175
+KPX seven acute -155
+KPX seven mu -235
+KPX seven paragraph -155
+KPX seven periodcentered -155
+KPX seven cedilla -155
+KPX seven ordmasculine -159
+KPX seven guillemotright -158
+KPX seven onequarter -188
+KPX seven onehalf -158
+KPX seven threequarters -158
+KPX seven questiondown -225
+KPX seven Aacute -225
+KPX seven Eacute -235
+KPX seven Idieresis -235
+KPX seven Yacute -235
+KPX seven ebreve -159
+KPX seven edotaccent -149
+KPX seven ecaron -149
+KPX seven gdotaccent -188
+KPX seven gcommaaccent -188
+KPX seven Hbar -73
+KPX seven dotlessi -188
+
+KPX eight dollar -63
+
+KPX nine dollar -159
+KPX nine two -36
+KPX nine D -188
+KPX nine H -188
+KPX nine L -36
+KPX nine R -188
+KPX nine X -131
+KPX nine backslash -83
+KPX nine cent -188
+KPX nine sterling -188
+KPX nine currency -188
+KPX nine yen -188
+KPX nine brokenbar -188
+KPX nine section -188
+KPX nine dieresis -188
+KPX nine ordfeminine -188
+KPX nine guillemotleft -188
+KPX nine logicalnot -188
+KPX nine sfthyphen -188
+KPX nine acute -188
+KPX nine mu -188
+KPX nine paragraph -188
+KPX nine periodcentered -188
+KPX nine cedilla -188
+KPX nine ordmasculine -188
+KPX nine guillemotright -131
+KPX nine onequarter -131
+KPX nine onehalf -131
+KPX nine threequarters -131
+KPX nine questiondown -83
+KPX nine Aacute -83
+KPX nine Yacute -188
+KPX nine Ebreve -36
+KPX nine ebreve -188
+KPX nine dotlessi -131
+
+KPX colon dollar -131
+KPX colon D -178
+KPX colon H -167
+KPX colon L -36
+KPX colon R -167
+KPX colon U -92
+KPX colon X -83
+KPX colon backslash -45
+KPX colon cent -178
+KPX colon sterling -178
+KPX colon currency -178
+KPX colon yen -178
+KPX colon brokenbar -178
+KPX colon section -178
+KPX colon dieresis -139
+KPX colon ordfeminine -167
+KPX colon guillemotleft -167
+KPX colon logicalnot -167
+KPX colon sfthyphen -167
+KPX colon acute -167
+KPX colon mu -167
+KPX colon paragraph -167
+KPX colon periodcentered -167
+KPX colon cedilla -167
+KPX colon ordmasculine -167
+KPX colon guillemotright -83
+KPX colon onequarter -83
+KPX colon onehalf -83
+KPX colon threequarters -83
+KPX colon questiondown -45
+KPX colon Aacute -45
+KPX colon Yacute -167
+KPX colon ebreve -167
+KPX colon edotaccent -92
+KPX colon ecaron -92
+KPX colon dotlessi -83
+
+KPX semicolon dollar -73
+KPX semicolon ampersand -36
+KPX semicolon two -36
+KPX semicolon Egrave -36
+KPX semicolon Icircumflex -36
+KPX semicolon Ebreve -36
+
+KPX less dollar -131
+KPX less ampersand -36
+KPX less D -159
+KPX less H -178
+KPX less L -36
+KPX less R -178
+KPX less X -178
+KPX less cent -159
+KPX less sterling -159
+KPX less currency -159
+KPX less yen -159
+KPX less brokenbar -159
+KPX less section -159
+KPX less dieresis -159
+KPX less ordfeminine -178
+KPX less guillemotleft -178
+KPX less logicalnot -178
+KPX less sfthyphen -178
+KPX less acute -178
+KPX less mu -178
+KPX less paragraph -178
+KPX less periodcentered -178
+KPX less cedilla -178
+KPX less ordmasculine -178
+KPX less guillemotright -178
+KPX less onequarter -178
+KPX less onehalf -178
+KPX less threequarters -178
+KPX less Egrave -36
+KPX less Icircumflex -36
+KPX less Yacute -178
+KPX less ebreve -178
+KPX less dotlessi -178
+
+
+
+
+
+
+
+
+
+
+KPX m hyphen -73
+KPX m seven -149
+KPX m Hbar -149
+
+KPX braceright hyphen -73
+KPX braceright seven -149
+KPX braceright Hbar -149
+
+
+
+
+
+
+
+
+
+
+
+
+KPX Acircumflex seven -112
+KPX Acircumflex nine -149
+KPX Acircumflex colon -102
+KPX Acircumflex less -102
+KPX Acircumflex I -36
+KPX Acircumflex W -36
+KPX Acircumflex Y -83
+KPX Acircumflex Z -83
+KPX Acircumflex backslash -83
+KPX Acircumflex questiondown -83
+KPX Acircumflex Aacute -83
+KPX Acircumflex Hcircumflex -112
+KPX Acircumflex hcircumflex -36
+KPX Acircumflex Hbar -112
+KPX Acircumflex hbar -36
+KPX Acircumflex Kcommaaccent -102
+KPX Acircumflex kcommaaccent -83
+KPX Acircumflex kgreenlandic -102
+KPX Acircumflex Lacute -83
+KPX Acircumflex lacute -102
+KPX Acircumflex uni01DC -112
+KPX Acircumflex uni01DD -36
+KPX Acircumflex uni01F4 -102
+KPX Acircumflex uni01F5 -83
+
+KPX Adieresis seven -112
+KPX Adieresis nine -149
+KPX Adieresis colon -102
+KPX Adieresis less -102
+KPX Adieresis I -36
+KPX Adieresis W -36
+KPX Adieresis Y -83
+KPX Adieresis Z -83
+KPX Adieresis backslash -83
+KPX Adieresis questiondown -83
+KPX Adieresis Aacute -83
+KPX Adieresis Hcircumflex -112
+KPX Adieresis hcircumflex -36
+KPX Adieresis Hbar -112
+KPX Adieresis hbar -36
+KPX Adieresis Kcommaaccent -102
+KPX Adieresis kcommaaccent -83
+KPX Adieresis kgreenlandic -102
+KPX Adieresis Lacute -83
+KPX Adieresis lacute -102
+KPX Adieresis uni01DC -112
+KPX Adieresis uni01DD -36
+KPX Adieresis uni01F4 -102
+KPX Adieresis uni01F5 -83
+
+KPX AE seven -112
+KPX AE nine -149
+KPX AE colon -102
+KPX AE less -102
+KPX AE I -36
+KPX AE W -36
+KPX AE Y -83
+KPX AE Z -83
+KPX AE backslash -83
+KPX AE questiondown -83
+KPX AE Aacute -83
+KPX AE Hcircumflex -112
+KPX AE hcircumflex -36
+KPX AE Hbar -112
+KPX AE hbar -36
+KPX AE Kcommaaccent -102
+KPX AE kcommaaccent -83
+KPX AE kgreenlandic -102
+KPX AE Lacute -83
+KPX AE lacute -102
+KPX AE uni01DC -112
+KPX AE uni01DD -36
+KPX AE uni01F4 -102
+KPX AE uni01F5 -83
+
+
+
+
+
+KPX Eth nine -36
+
+KPX Ograve nine -36
+
+
+
+KPX ucircumflex seven -167
+KPX ucircumflex eight -112
+KPX ucircumflex nine -243
+KPX ucircumflex colon -139
+KPX ucircumflex less -131
+KPX ucircumflex backslash -73
+KPX ucircumflex questiondown -73
+KPX ucircumflex Aacute -73
+KPX ucircumflex Hbar -167
+KPX ucircumflex Idot -112
+KPX ucircumflex lacute -131
+
+KPX ydieresis seven -167
+KPX ydieresis eight -112
+KPX ydieresis nine -243
+KPX ydieresis colon -139
+KPX ydieresis less -131
+KPX ydieresis backslash -73
+KPX ydieresis questiondown -73
+KPX ydieresis Aacute -73
+KPX ydieresis Hbar -167
+KPX ydieresis Idot -112
+KPX ydieresis lacute -131
+
+KPX Abreve O -241
+
+KPX abreve seven -167
+KPX abreve eight -112
+KPX abreve nine -243
+KPX abreve colon -139
+KPX abreve less -131
+KPX abreve backslash -73
+KPX abreve questiondown -73
+KPX abreve Aacute -73
+KPX abreve Hbar -167
+KPX abreve Idot -112
+KPX abreve lacute -131
+
+
+
+KPX Edotaccent seven -36
+KPX Edotaccent nine -73
+KPX Edotaccent colon -45
+KPX Edotaccent less -63
+KPX Edotaccent D 47
+KPX Edotaccent backslash -36
+KPX Edotaccent cent 47
+KPX Edotaccent sterling 47
+KPX Edotaccent currency 47
+KPX Edotaccent yen 47
+KPX Edotaccent brokenbar 47
+KPX Edotaccent section 47
+KPX Edotaccent dieresis 47
+KPX Edotaccent ordmasculine 38
+KPX Edotaccent questiondown -36
+KPX Edotaccent Aacute -36
+KPX Edotaccent Hbar -36
+KPX Edotaccent lacute -63
+
+
+KPX Ecaron seven -36
+KPX Ecaron nine -73
+KPX Ecaron colon -45
+KPX Ecaron less -63
+KPX Ecaron D 47
+KPX Ecaron backslash -36
+KPX Ecaron cent 47
+KPX Ecaron sterling 47
+KPX Ecaron currency 47
+KPX Ecaron yen 47
+KPX Ecaron brokenbar 47
+KPX Ecaron section 47
+KPX Ecaron dieresis 47
+KPX Ecaron ordmasculine 38
+KPX Ecaron questiondown -36
+KPX Ecaron Aacute -36
+KPX Ecaron Hbar -36
+KPX Ecaron lacute -63
+
+
+KPX Gdotaccent six -45
+KPX Gdotaccent Gdotaccent -45
+KPX Gdotaccent Gcommaaccent -45
+
+KPX Gcommaaccent six -45
+KPX Gcommaaccent Gdotaccent -45
+KPX Gcommaaccent Gcommaaccent -45
+
+KPX Hbar dollar -112
+KPX Hbar seven -73
+KPX Hbar D -196
+KPX Hbar F -235
+KPX Hbar H -235
+KPX Hbar R -235
+KPX Hbar U -149
+KPX Hbar V -188
+KPX Hbar X -188
+KPX Hbar Z -225
+KPX Hbar backslash -225
+KPX Hbar m -149
+KPX Hbar braceright -149
+KPX Hbar cent -196
+KPX Hbar sterling -196
+KPX Hbar currency -196
+KPX Hbar yen -196
+KPX Hbar brokenbar -196
+KPX Hbar section -196
+KPX Hbar dieresis -159
+KPX Hbar copyright -235
+KPX Hbar ordfeminine -235
+KPX Hbar guillemotleft -235
+KPX Hbar logicalnot -235
+KPX Hbar sfthyphen -235
+KPX Hbar acute -235
+KPX Hbar mu -235
+KPX Hbar paragraph -235
+KPX Hbar periodcentered -235
+KPX Hbar cedilla -235
+KPX Hbar ordmasculine -159
+KPX Hbar guillemotright -188
+KPX Hbar onequarter -188
+KPX Hbar onehalf -188
+KPX Hbar threequarters -188
+KPX Hbar questiondown -225
+KPX Hbar Aacute -225
+KPX Hbar Eacute -235
+KPX Hbar Idieresis -235
+KPX Hbar Yacute -235
+KPX Hbar ebreve -159
+KPX Hbar edotaccent -149
+KPX Hbar ecaron -149
+KPX Hbar gdotaccent -188
+KPX Hbar gcommaaccent -188
+KPX Hbar Hbar -73
+KPX Hbar dotlessi -188
+
+KPX Idot dollar -63
+
+KPX lacute dollar -131
+KPX lacute ampersand -36
+KPX lacute D -159
+KPX lacute H -178
+KPX lacute L -36
+KPX lacute R -178
+KPX lacute X -178
+KPX lacute cent -159
+KPX lacute sterling -159
+KPX lacute currency -159
+KPX lacute yen -159
+KPX lacute brokenbar -159
+KPX lacute section -159
+KPX lacute dieresis -159
+KPX lacute ordfeminine -178
+KPX lacute guillemotleft -178
+KPX lacute logicalnot -178
+KPX lacute sfthyphen -178
+KPX lacute acute -178
+KPX lacute mu -178
+KPX lacute paragraph -178
+KPX lacute periodcentered -178
+KPX lacute cedilla -178
+KPX lacute ordmasculine -178
+KPX lacute guillemotright -178
+KPX lacute onequarter -178
+KPX lacute onehalf -178
+KPX lacute threequarters -178
+KPX lacute Egrave -36
+KPX lacute Icircumflex -36
+KPX lacute Yacute -178
+KPX lacute ebreve -178
+KPX lacute dotlessi -178
+
+
+KPX uni027D dollar -282
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ttf
new file mode 100644
index 0000000..a36dd4b
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ufm
new file mode 100644
index 0000000..f6db21d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-BoldItalic.ufm
@@ -0,0 +1,3892 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Serif
+FontSubfamily Bold Italic
+UniqueID DejaVu Serif Bold Italic
+FullName DejaVu Serif Bold Italic
+Version Version 2.37
+PostScriptName DejaVuSerif-BoldItalic
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Serif
+PreferredSubfamily Bold Italic
+Weight Bold
+ItalicAngle -11
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 939
+Descender -236
+FontBBox -906 -389 1925 1145
+StartCharMetrics 3506
+U 32 ; WX 348 ; N space ; G 3
+U 33 ; WX 439 ; N exclam ; G 4
+U 34 ; WX 521 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 696 ; N dollar ; G 7
+U 37 ; WX 950 ; N percent ; G 8
+U 38 ; WX 903 ; N ampersand ; G 9
+U 39 ; WX 306 ; N quotesingle ; G 10
+U 40 ; WX 473 ; N parenleft ; G 11
+U 41 ; WX 473 ; N parenright ; G 12
+U 42 ; WX 523 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 348 ; N comma ; G 15
+U 45 ; WX 415 ; N hyphen ; G 16
+U 46 ; WX 348 ; N period ; G 17
+U 47 ; WX 365 ; N slash ; G 18
+U 48 ; WX 696 ; N zero ; G 19
+U 49 ; WX 696 ; N one ; G 20
+U 50 ; WX 696 ; N two ; G 21
+U 51 ; WX 696 ; N three ; G 22
+U 52 ; WX 696 ; N four ; G 23
+U 53 ; WX 696 ; N five ; G 24
+U 54 ; WX 696 ; N six ; G 25
+U 55 ; WX 696 ; N seven ; G 26
+U 56 ; WX 696 ; N eight ; G 27
+U 57 ; WX 696 ; N nine ; G 28
+U 58 ; WX 369 ; N colon ; G 29
+U 59 ; WX 369 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 586 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 776 ; N A ; G 36
+U 66 ; WX 845 ; N B ; G 37
+U 67 ; WX 796 ; N C ; G 38
+U 68 ; WX 867 ; N D ; G 39
+U 69 ; WX 762 ; N E ; G 40
+U 70 ; WX 710 ; N F ; G 41
+U 71 ; WX 854 ; N G ; G 42
+U 72 ; WX 945 ; N H ; G 43
+U 73 ; WX 468 ; N I ; G 44
+U 74 ; WX 473 ; N J ; G 45
+U 75 ; WX 869 ; N K ; G 46
+U 76 ; WX 703 ; N L ; G 47
+U 77 ; WX 1107 ; N M ; G 48
+U 78 ; WX 914 ; N N ; G 49
+U 79 ; WX 871 ; N O ; G 50
+U 80 ; WX 752 ; N P ; G 51
+U 81 ; WX 871 ; N Q ; G 52
+U 82 ; WX 831 ; N R ; G 53
+U 83 ; WX 722 ; N S ; G 54
+U 84 ; WX 744 ; N T ; G 55
+U 85 ; WX 872 ; N U ; G 56
+U 86 ; WX 776 ; N V ; G 57
+U 87 ; WX 1123 ; N W ; G 58
+U 88 ; WX 776 ; N X ; G 59
+U 89 ; WX 714 ; N Y ; G 60
+U 90 ; WX 730 ; N Z ; G 61
+U 91 ; WX 473 ; N bracketleft ; G 62
+U 92 ; WX 365 ; N backslash ; G 63
+U 93 ; WX 473 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 648 ; N a ; G 68
+U 98 ; WX 699 ; N b ; G 69
+U 99 ; WX 609 ; N c ; G 70
+U 100 ; WX 699 ; N d ; G 71
+U 101 ; WX 636 ; N e ; G 72
+U 102 ; WX 430 ; N f ; G 73
+U 103 ; WX 699 ; N g ; G 74
+U 104 ; WX 727 ; N h ; G 75
+U 105 ; WX 380 ; N i ; G 76
+U 106 ; WX 362 ; N j ; G 77
+U 107 ; WX 693 ; N k ; G 78
+U 108 ; WX 380 ; N l ; G 79
+U 109 ; WX 1058 ; N m ; G 80
+U 110 ; WX 727 ; N n ; G 81
+U 111 ; WX 667 ; N o ; G 82
+U 112 ; WX 699 ; N p ; G 83
+U 113 ; WX 699 ; N q ; G 84
+U 114 ; WX 527 ; N r ; G 85
+U 115 ; WX 563 ; N s ; G 86
+U 116 ; WX 462 ; N t ; G 87
+U 117 ; WX 727 ; N u ; G 88
+U 118 ; WX 581 ; N v ; G 89
+U 119 ; WX 861 ; N w ; G 90
+U 120 ; WX 596 ; N x ; G 91
+U 121 ; WX 581 ; N y ; G 92
+U 122 ; WX 568 ; N z ; G 93
+U 123 ; WX 643 ; N braceleft ; G 94
+U 124 ; WX 364 ; N bar ; G 95
+U 125 ; WX 643 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 348 ; N nbspace ; G 98
+U 161 ; WX 439 ; N exclamdown ; G 99
+U 162 ; WX 696 ; N cent ; G 100
+U 163 ; WX 696 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 696 ; N yen ; G 103
+U 166 ; WX 364 ; N brokenbar ; G 104
+U 167 ; WX 523 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 487 ; N ordfeminine ; G 108
+U 171 ; WX 625 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 415 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 438 ; N twosuperior ; G 116
+U 179 ; WX 438 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 732 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 348 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 438 ; N onesuperior ; G 123
+U 186 ; WX 500 ; N ordmasculine ; G 124
+U 187 ; WX 625 ; N guillemotright ; G 125
+U 188 ; WX 1043 ; N onequarter ; G 126
+U 189 ; WX 1043 ; N onehalf ; G 127
+U 190 ; WX 1043 ; N threequarters ; G 128
+U 191 ; WX 586 ; N questiondown ; G 129
+U 192 ; WX 776 ; N Agrave ; G 130
+U 193 ; WX 776 ; N Aacute ; G 131
+U 194 ; WX 776 ; N Acircumflex ; G 132
+U 195 ; WX 776 ; N Atilde ; G 133
+U 196 ; WX 776 ; N Adieresis ; G 134
+U 197 ; WX 776 ; N Aring ; G 135
+U 198 ; WX 1034 ; N AE ; G 136
+U 199 ; WX 796 ; N Ccedilla ; G 137
+U 200 ; WX 762 ; N Egrave ; G 138
+U 201 ; WX 762 ; N Eacute ; G 139
+U 202 ; WX 762 ; N Ecircumflex ; G 140
+U 203 ; WX 762 ; N Edieresis ; G 141
+U 204 ; WX 468 ; N Igrave ; G 142
+U 205 ; WX 468 ; N Iacute ; G 143
+U 206 ; WX 468 ; N Icircumflex ; G 144
+U 207 ; WX 468 ; N Idieresis ; G 145
+U 208 ; WX 874 ; N Eth ; G 146
+U 209 ; WX 914 ; N Ntilde ; G 147
+U 210 ; WX 871 ; N Ograve ; G 148
+U 211 ; WX 871 ; N Oacute ; G 149
+U 212 ; WX 871 ; N Ocircumflex ; G 150
+U 213 ; WX 871 ; N Otilde ; G 151
+U 214 ; WX 871 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 871 ; N Oslash ; G 154
+U 217 ; WX 872 ; N Ugrave ; G 155
+U 218 ; WX 872 ; N Uacute ; G 156
+U 219 ; WX 872 ; N Ucircumflex ; G 157
+U 220 ; WX 872 ; N Udieresis ; G 158
+U 221 ; WX 714 ; N Yacute ; G 159
+U 222 ; WX 757 ; N Thorn ; G 160
+U 223 ; WX 760 ; N germandbls ; G 161
+U 224 ; WX 648 ; N agrave ; G 162
+U 225 ; WX 648 ; N aacute ; G 163
+U 226 ; WX 648 ; N acircumflex ; G 164
+U 227 ; WX 648 ; N atilde ; G 165
+U 228 ; WX 648 ; N adieresis ; G 166
+U 229 ; WX 648 ; N aring ; G 167
+U 230 ; WX 932 ; N ae ; G 168
+U 231 ; WX 609 ; N ccedilla ; G 169
+U 232 ; WX 636 ; N egrave ; G 170
+U 233 ; WX 636 ; N eacute ; G 171
+U 234 ; WX 636 ; N ecircumflex ; G 172
+U 235 ; WX 636 ; N edieresis ; G 173
+U 236 ; WX 380 ; N igrave ; G 174
+U 237 ; WX 380 ; N iacute ; G 175
+U 238 ; WX 380 ; N icircumflex ; G 176
+U 239 ; WX 380 ; N idieresis ; G 177
+U 240 ; WX 667 ; N eth ; G 178
+U 241 ; WX 727 ; N ntilde ; G 179
+U 242 ; WX 667 ; N ograve ; G 180
+U 243 ; WX 667 ; N oacute ; G 181
+U 244 ; WX 667 ; N ocircumflex ; G 182
+U 245 ; WX 667 ; N otilde ; G 183
+U 246 ; WX 667 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 667 ; N oslash ; G 186
+U 249 ; WX 727 ; N ugrave ; G 187
+U 250 ; WX 727 ; N uacute ; G 188
+U 251 ; WX 727 ; N ucircumflex ; G 189
+U 252 ; WX 727 ; N udieresis ; G 190
+U 253 ; WX 581 ; N yacute ; G 191
+U 254 ; WX 699 ; N thorn ; G 192
+U 255 ; WX 581 ; N ydieresis ; G 193
+U 256 ; WX 776 ; N Amacron ; G 194
+U 257 ; WX 648 ; N amacron ; G 195
+U 258 ; WX 776 ; N Abreve ; G 196
+U 259 ; WX 648 ; N abreve ; G 197
+U 260 ; WX 776 ; N Aogonek ; G 198
+U 261 ; WX 648 ; N aogonek ; G 199
+U 262 ; WX 796 ; N Cacute ; G 200
+U 263 ; WX 609 ; N cacute ; G 201
+U 264 ; WX 796 ; N Ccircumflex ; G 202
+U 265 ; WX 609 ; N ccircumflex ; G 203
+U 266 ; WX 796 ; N Cdotaccent ; G 204
+U 267 ; WX 609 ; N cdotaccent ; G 205
+U 268 ; WX 796 ; N Ccaron ; G 206
+U 269 ; WX 609 ; N ccaron ; G 207
+U 270 ; WX 867 ; N Dcaron ; G 208
+U 271 ; WX 699 ; N dcaron ; G 209
+U 272 ; WX 874 ; N Dcroat ; G 210
+U 273 ; WX 699 ; N dmacron ; G 211
+U 274 ; WX 762 ; N Emacron ; G 212
+U 275 ; WX 636 ; N emacron ; G 213
+U 276 ; WX 762 ; N Ebreve ; G 214
+U 277 ; WX 636 ; N ebreve ; G 215
+U 278 ; WX 762 ; N Edotaccent ; G 216
+U 279 ; WX 636 ; N edotaccent ; G 217
+U 280 ; WX 762 ; N Eogonek ; G 218
+U 281 ; WX 636 ; N eogonek ; G 219
+U 282 ; WX 762 ; N Ecaron ; G 220
+U 283 ; WX 636 ; N ecaron ; G 221
+U 284 ; WX 854 ; N Gcircumflex ; G 222
+U 285 ; WX 699 ; N gcircumflex ; G 223
+U 286 ; WX 854 ; N Gbreve ; G 224
+U 287 ; WX 699 ; N gbreve ; G 225
+U 288 ; WX 854 ; N Gdotaccent ; G 226
+U 289 ; WX 699 ; N gdotaccent ; G 227
+U 290 ; WX 854 ; N Gcommaaccent ; G 228
+U 291 ; WX 699 ; N gcommaaccent ; G 229
+U 292 ; WX 945 ; N Hcircumflex ; G 230
+U 293 ; WX 727 ; N hcircumflex ; G 231
+U 294 ; WX 945 ; N Hbar ; G 232
+U 295 ; WX 727 ; N hbar ; G 233
+U 296 ; WX 468 ; N Itilde ; G 234
+U 297 ; WX 380 ; N itilde ; G 235
+U 298 ; WX 468 ; N Imacron ; G 236
+U 299 ; WX 380 ; N imacron ; G 237
+U 300 ; WX 468 ; N Ibreve ; G 238
+U 301 ; WX 380 ; N ibreve ; G 239
+U 302 ; WX 468 ; N Iogonek ; G 240
+U 303 ; WX 380 ; N iogonek ; G 241
+U 304 ; WX 468 ; N Idot ; G 242
+U 305 ; WX 380 ; N dotlessi ; G 243
+U 306 ; WX 942 ; N IJ ; G 244
+U 307 ; WX 751 ; N ij ; G 245
+U 308 ; WX 473 ; N Jcircumflex ; G 246
+U 309 ; WX 362 ; N jcircumflex ; G 247
+U 310 ; WX 869 ; N Kcommaaccent ; G 248
+U 311 ; WX 693 ; N kcommaaccent ; G 249
+U 312 ; WX 693 ; N kgreenlandic ; G 250
+U 313 ; WX 703 ; N Lacute ; G 251
+U 314 ; WX 380 ; N lacute ; G 252
+U 315 ; WX 703 ; N Lcommaaccent ; G 253
+U 316 ; WX 380 ; N lcommaaccent ; G 254
+U 317 ; WX 703 ; N Lcaron ; G 255
+U 318 ; WX 508 ; N lcaron ; G 256
+U 319 ; WX 703 ; N Ldot ; G 257
+U 320 ; WX 557 ; N ldot ; G 258
+U 321 ; WX 710 ; N Lslash ; G 259
+U 322 ; WX 385 ; N lslash ; G 260
+U 323 ; WX 914 ; N Nacute ; G 261
+U 324 ; WX 727 ; N nacute ; G 262
+U 325 ; WX 914 ; N Ncommaaccent ; G 263
+U 326 ; WX 727 ; N ncommaaccent ; G 264
+U 327 ; WX 914 ; N Ncaron ; G 265
+U 328 ; WX 727 ; N ncaron ; G 266
+U 329 ; WX 1008 ; N napostrophe ; G 267
+U 330 ; WX 872 ; N Eng ; G 268
+U 331 ; WX 727 ; N eng ; G 269
+U 332 ; WX 871 ; N Omacron ; G 270
+U 333 ; WX 667 ; N omacron ; G 271
+U 334 ; WX 871 ; N Obreve ; G 272
+U 335 ; WX 667 ; N obreve ; G 273
+U 336 ; WX 871 ; N Ohungarumlaut ; G 274
+U 337 ; WX 667 ; N ohungarumlaut ; G 275
+U 338 ; WX 1180 ; N OE ; G 276
+U 339 ; WX 1028 ; N oe ; G 277
+U 340 ; WX 831 ; N Racute ; G 278
+U 341 ; WX 527 ; N racute ; G 279
+U 342 ; WX 831 ; N Rcommaaccent ; G 280
+U 343 ; WX 527 ; N rcommaaccent ; G 281
+U 344 ; WX 831 ; N Rcaron ; G 282
+U 345 ; WX 527 ; N rcaron ; G 283
+U 346 ; WX 722 ; N Sacute ; G 284
+U 347 ; WX 563 ; N sacute ; G 285
+U 348 ; WX 722 ; N Scircumflex ; G 286
+U 349 ; WX 563 ; N scircumflex ; G 287
+U 350 ; WX 722 ; N Scedilla ; G 288
+U 351 ; WX 563 ; N scedilla ; G 289
+U 352 ; WX 722 ; N Scaron ; G 290
+U 353 ; WX 563 ; N scaron ; G 291
+U 354 ; WX 744 ; N Tcommaaccent ; G 292
+U 355 ; WX 462 ; N tcommaaccent ; G 293
+U 356 ; WX 744 ; N Tcaron ; G 294
+U 357 ; WX 462 ; N tcaron ; G 295
+U 358 ; WX 744 ; N Tbar ; G 296
+U 359 ; WX 462 ; N tbar ; G 297
+U 360 ; WX 872 ; N Utilde ; G 298
+U 361 ; WX 727 ; N utilde ; G 299
+U 362 ; WX 872 ; N Umacron ; G 300
+U 363 ; WX 727 ; N umacron ; G 301
+U 364 ; WX 872 ; N Ubreve ; G 302
+U 365 ; WX 727 ; N ubreve ; G 303
+U 366 ; WX 872 ; N Uring ; G 304
+U 367 ; WX 727 ; N uring ; G 305
+U 368 ; WX 872 ; N Uhungarumlaut ; G 306
+U 369 ; WX 727 ; N uhungarumlaut ; G 307
+U 370 ; WX 872 ; N Uogonek ; G 308
+U 371 ; WX 727 ; N uogonek ; G 309
+U 372 ; WX 1123 ; N Wcircumflex ; G 310
+U 373 ; WX 861 ; N wcircumflex ; G 311
+U 374 ; WX 714 ; N Ycircumflex ; G 312
+U 375 ; WX 581 ; N ycircumflex ; G 313
+U 376 ; WX 714 ; N Ydieresis ; G 314
+U 377 ; WX 730 ; N Zacute ; G 315
+U 378 ; WX 568 ; N zacute ; G 316
+U 379 ; WX 730 ; N Zdotaccent ; G 317
+U 380 ; WX 568 ; N zdotaccent ; G 318
+U 381 ; WX 730 ; N Zcaron ; G 319
+U 382 ; WX 568 ; N zcaron ; G 320
+U 383 ; WX 430 ; N longs ; G 321
+U 384 ; WX 699 ; N uni0180 ; G 322
+U 385 ; WX 845 ; N uni0181 ; G 323
+U 386 ; WX 854 ; N uni0182 ; G 324
+U 387 ; WX 699 ; N uni0183 ; G 325
+U 388 ; WX 854 ; N uni0184 ; G 326
+U 389 ; WX 699 ; N uni0185 ; G 327
+U 390 ; WX 796 ; N uni0186 ; G 328
+U 391 ; WX 796 ; N uni0187 ; G 329
+U 392 ; WX 609 ; N uni0188 ; G 330
+U 393 ; WX 874 ; N uni0189 ; G 331
+U 394 ; WX 867 ; N uni018A ; G 332
+U 395 ; WX 854 ; N uni018B ; G 333
+U 396 ; WX 699 ; N uni018C ; G 334
+U 397 ; WX 667 ; N uni018D ; G 335
+U 398 ; WX 762 ; N uni018E ; G 336
+U 399 ; WX 871 ; N uni018F ; G 337
+U 400 ; WX 721 ; N uni0190 ; G 338
+U 401 ; WX 710 ; N uni0191 ; G 339
+U 402 ; WX 430 ; N florin ; G 340
+U 403 ; WX 854 ; N uni0193 ; G 341
+U 404 ; WX 771 ; N uni0194 ; G 342
+U 405 ; WX 1043 ; N uni0195 ; G 343
+U 406 ; WX 468 ; N uni0196 ; G 344
+U 407 ; WX 468 ; N uni0197 ; G 345
+U 408 ; WX 869 ; N uni0198 ; G 346
+U 409 ; WX 693 ; N uni0199 ; G 347
+U 410 ; WX 380 ; N uni019A ; G 348
+U 411 ; WX 701 ; N uni019B ; G 349
+U 412 ; WX 1058 ; N uni019C ; G 350
+U 413 ; WX 914 ; N uni019D ; G 351
+U 414 ; WX 727 ; N uni019E ; G 352
+U 415 ; WX 871 ; N uni019F ; G 353
+U 416 ; WX 871 ; N Ohorn ; G 354
+U 417 ; WX 667 ; N ohorn ; G 355
+U 418 ; WX 1200 ; N uni01A2 ; G 356
+U 419 ; WX 943 ; N uni01A3 ; G 357
+U 420 ; WX 752 ; N uni01A4 ; G 358
+U 421 ; WX 699 ; N uni01A5 ; G 359
+U 422 ; WX 831 ; N uni01A6 ; G 360
+U 423 ; WX 722 ; N uni01A7 ; G 361
+U 424 ; WX 563 ; N uni01A8 ; G 362
+U 425 ; WX 707 ; N uni01A9 ; G 363
+U 426 ; WX 331 ; N uni01AA ; G 364
+U 427 ; WX 462 ; N uni01AB ; G 365
+U 428 ; WX 744 ; N uni01AC ; G 366
+U 429 ; WX 462 ; N uni01AD ; G 367
+U 430 ; WX 744 ; N uni01AE ; G 368
+U 431 ; WX 872 ; N Uhorn ; G 369
+U 432 ; WX 727 ; N uhorn ; G 370
+U 433 ; WX 890 ; N uni01B1 ; G 371
+U 434 ; WX 890 ; N uni01B2 ; G 372
+U 435 ; WX 714 ; N uni01B3 ; G 373
+U 436 ; WX 699 ; N uni01B4 ; G 374
+U 437 ; WX 730 ; N uni01B5 ; G 375
+U 438 ; WX 568 ; N uni01B6 ; G 376
+U 439 ; WX 657 ; N uni01B7 ; G 377
+U 440 ; WX 657 ; N uni01B8 ; G 378
+U 441 ; WX 657 ; N uni01B9 ; G 379
+U 442 ; WX 657 ; N uni01BA ; G 380
+U 443 ; WX 696 ; N uni01BB ; G 381
+U 444 ; WX 754 ; N uni01BC ; G 382
+U 445 ; WX 568 ; N uni01BD ; G 383
+U 446 ; WX 536 ; N uni01BE ; G 384
+U 447 ; WX 716 ; N uni01BF ; G 385
+U 448 ; WX 295 ; N uni01C0 ; G 386
+U 449 ; WX 492 ; N uni01C1 ; G 387
+U 450 ; WX 459 ; N uni01C2 ; G 388
+U 451 ; WX 295 ; N uni01C3 ; G 389
+U 452 ; WX 1597 ; N uni01C4 ; G 390
+U 453 ; WX 1435 ; N uni01C5 ; G 391
+U 454 ; WX 1267 ; N uni01C6 ; G 392
+U 455 ; WX 1176 ; N uni01C7 ; G 393
+U 456 ; WX 1065 ; N uni01C8 ; G 394
+U 457 ; WX 742 ; N uni01C9 ; G 395
+U 458 ; WX 1387 ; N uni01CA ; G 396
+U 459 ; WX 1276 ; N uni01CB ; G 397
+U 460 ; WX 1089 ; N uni01CC ; G 398
+U 461 ; WX 776 ; N uni01CD ; G 399
+U 462 ; WX 648 ; N uni01CE ; G 400
+U 463 ; WX 468 ; N uni01CF ; G 401
+U 464 ; WX 380 ; N uni01D0 ; G 402
+U 465 ; WX 871 ; N uni01D1 ; G 403
+U 466 ; WX 667 ; N uni01D2 ; G 404
+U 467 ; WX 872 ; N uni01D3 ; G 405
+U 468 ; WX 727 ; N uni01D4 ; G 406
+U 469 ; WX 872 ; N uni01D5 ; G 407
+U 470 ; WX 727 ; N uni01D6 ; G 408
+U 471 ; WX 872 ; N uni01D7 ; G 409
+U 472 ; WX 727 ; N uni01D8 ; G 410
+U 473 ; WX 872 ; N uni01D9 ; G 411
+U 474 ; WX 727 ; N uni01DA ; G 412
+U 475 ; WX 872 ; N uni01DB ; G 413
+U 476 ; WX 727 ; N uni01DC ; G 414
+U 477 ; WX 636 ; N uni01DD ; G 415
+U 478 ; WX 776 ; N uni01DE ; G 416
+U 479 ; WX 648 ; N uni01DF ; G 417
+U 480 ; WX 776 ; N uni01E0 ; G 418
+U 481 ; WX 648 ; N uni01E1 ; G 419
+U 482 ; WX 1034 ; N uni01E2 ; G 420
+U 483 ; WX 975 ; N uni01E3 ; G 421
+U 484 ; WX 896 ; N uni01E4 ; G 422
+U 485 ; WX 699 ; N uni01E5 ; G 423
+U 486 ; WX 854 ; N Gcaron ; G 424
+U 487 ; WX 699 ; N gcaron ; G 425
+U 488 ; WX 869 ; N uni01E8 ; G 426
+U 489 ; WX 693 ; N uni01E9 ; G 427
+U 490 ; WX 871 ; N uni01EA ; G 428
+U 491 ; WX 667 ; N uni01EB ; G 429
+U 492 ; WX 871 ; N uni01EC ; G 430
+U 493 ; WX 667 ; N uni01ED ; G 431
+U 494 ; WX 657 ; N uni01EE ; G 432
+U 495 ; WX 568 ; N uni01EF ; G 433
+U 496 ; WX 362 ; N uni01F0 ; G 434
+U 497 ; WX 1597 ; N uni01F1 ; G 435
+U 498 ; WX 1435 ; N uni01F2 ; G 436
+U 499 ; WX 1267 ; N uni01F3 ; G 437
+U 500 ; WX 854 ; N uni01F4 ; G 438
+U 501 ; WX 699 ; N uni01F5 ; G 439
+U 502 ; WX 1221 ; N uni01F6 ; G 440
+U 503 ; WX 787 ; N uni01F7 ; G 441
+U 504 ; WX 914 ; N uni01F8 ; G 442
+U 505 ; WX 727 ; N uni01F9 ; G 443
+U 506 ; WX 776 ; N Aringacute ; G 444
+U 507 ; WX 648 ; N aringacute ; G 445
+U 508 ; WX 1034 ; N AEacute ; G 446
+U 509 ; WX 932 ; N aeacute ; G 447
+U 510 ; WX 871 ; N Oslashacute ; G 448
+U 511 ; WX 667 ; N oslashacute ; G 449
+U 512 ; WX 776 ; N uni0200 ; G 450
+U 513 ; WX 648 ; N uni0201 ; G 451
+U 514 ; WX 776 ; N uni0202 ; G 452
+U 515 ; WX 648 ; N uni0203 ; G 453
+U 516 ; WX 762 ; N uni0204 ; G 454
+U 517 ; WX 636 ; N uni0205 ; G 455
+U 518 ; WX 762 ; N uni0206 ; G 456
+U 519 ; WX 636 ; N uni0207 ; G 457
+U 520 ; WX 468 ; N uni0208 ; G 458
+U 521 ; WX 380 ; N uni0209 ; G 459
+U 522 ; WX 468 ; N uni020A ; G 460
+U 523 ; WX 380 ; N uni020B ; G 461
+U 524 ; WX 871 ; N uni020C ; G 462
+U 525 ; WX 667 ; N uni020D ; G 463
+U 526 ; WX 871 ; N uni020E ; G 464
+U 527 ; WX 667 ; N uni020F ; G 465
+U 528 ; WX 831 ; N uni0210 ; G 466
+U 529 ; WX 527 ; N uni0211 ; G 467
+U 530 ; WX 831 ; N uni0212 ; G 468
+U 531 ; WX 527 ; N uni0213 ; G 469
+U 532 ; WX 872 ; N uni0214 ; G 470
+U 533 ; WX 727 ; N uni0215 ; G 471
+U 534 ; WX 872 ; N uni0216 ; G 472
+U 535 ; WX 727 ; N uni0217 ; G 473
+U 536 ; WX 722 ; N Scommaaccent ; G 474
+U 537 ; WX 563 ; N scommaaccent ; G 475
+U 538 ; WX 744 ; N uni021A ; G 476
+U 539 ; WX 462 ; N uni021B ; G 477
+U 540 ; WX 690 ; N uni021C ; G 478
+U 541 ; WX 607 ; N uni021D ; G 479
+U 542 ; WX 945 ; N uni021E ; G 480
+U 543 ; WX 727 ; N uni021F ; G 481
+U 544 ; WX 872 ; N uni0220 ; G 482
+U 545 ; WX 791 ; N uni0221 ; G 483
+U 546 ; WX 703 ; N uni0222 ; G 484
+U 547 ; WX 616 ; N uni0223 ; G 485
+U 548 ; WX 730 ; N uni0224 ; G 486
+U 549 ; WX 568 ; N uni0225 ; G 487
+U 550 ; WX 776 ; N uni0226 ; G 488
+U 551 ; WX 648 ; N uni0227 ; G 489
+U 552 ; WX 762 ; N uni0228 ; G 490
+U 553 ; WX 636 ; N uni0229 ; G 491
+U 554 ; WX 871 ; N uni022A ; G 492
+U 555 ; WX 667 ; N uni022B ; G 493
+U 556 ; WX 871 ; N uni022C ; G 494
+U 557 ; WX 667 ; N uni022D ; G 495
+U 558 ; WX 871 ; N uni022E ; G 496
+U 559 ; WX 667 ; N uni022F ; G 497
+U 560 ; WX 871 ; N uni0230 ; G 498
+U 561 ; WX 667 ; N uni0231 ; G 499
+U 562 ; WX 714 ; N uni0232 ; G 500
+U 563 ; WX 581 ; N uni0233 ; G 501
+U 564 ; WX 573 ; N uni0234 ; G 502
+U 565 ; WX 922 ; N uni0235 ; G 503
+U 566 ; WX 564 ; N uni0236 ; G 504
+U 567 ; WX 362 ; N dotlessj ; G 505
+U 568 ; WX 1031 ; N uni0238 ; G 506
+U 569 ; WX 1031 ; N uni0239 ; G 507
+U 570 ; WX 776 ; N uni023A ; G 508
+U 571 ; WX 796 ; N uni023B ; G 509
+U 572 ; WX 609 ; N uni023C ; G 510
+U 573 ; WX 703 ; N uni023D ; G 511
+U 574 ; WX 744 ; N uni023E ; G 512
+U 575 ; WX 563 ; N uni023F ; G 513
+U 576 ; WX 568 ; N uni0240 ; G 514
+U 577 ; WX 660 ; N uni0241 ; G 515
+U 578 ; WX 547 ; N uni0242 ; G 516
+U 579 ; WX 845 ; N uni0243 ; G 517
+U 580 ; WX 872 ; N uni0244 ; G 518
+U 581 ; WX 776 ; N uni0245 ; G 519
+U 582 ; WX 762 ; N uni0246 ; G 520
+U 583 ; WX 636 ; N uni0247 ; G 521
+U 584 ; WX 473 ; N uni0248 ; G 522
+U 585 ; WX 387 ; N uni0249 ; G 523
+U 586 ; WX 848 ; N uni024A ; G 524
+U 587 ; WX 699 ; N uni024B ; G 525
+U 588 ; WX 831 ; N uni024C ; G 526
+U 589 ; WX 527 ; N uni024D ; G 527
+U 590 ; WX 714 ; N uni024E ; G 528
+U 591 ; WX 581 ; N uni024F ; G 529
+U 592 ; WX 648 ; N uni0250 ; G 530
+U 593 ; WX 770 ; N uni0251 ; G 531
+U 594 ; WX 770 ; N uni0252 ; G 532
+U 595 ; WX 699 ; N uni0253 ; G 533
+U 596 ; WX 609 ; N uni0254 ; G 534
+U 597 ; WX 609 ; N uni0255 ; G 535
+U 598 ; WX 699 ; N uni0256 ; G 536
+U 599 ; WX 730 ; N uni0257 ; G 537
+U 600 ; WX 636 ; N uni0258 ; G 538
+U 601 ; WX 636 ; N uni0259 ; G 539
+U 602 ; WX 907 ; N uni025A ; G 540
+U 603 ; WX 608 ; N uni025B ; G 541
+U 604 ; WX 562 ; N uni025C ; G 542
+U 605 ; WX 907 ; N uni025D ; G 543
+U 606 ; WX 714 ; N uni025E ; G 544
+U 607 ; WX 387 ; N uni025F ; G 545
+U 608 ; WX 699 ; N uni0260 ; G 546
+U 609 ; WX 699 ; N uni0261 ; G 547
+U 610 ; WX 638 ; N uni0262 ; G 548
+U 611 ; WX 601 ; N uni0263 ; G 549
+U 612 ; WX 627 ; N uni0264 ; G 550
+U 613 ; WX 727 ; N uni0265 ; G 551
+U 614 ; WX 727 ; N uni0266 ; G 552
+U 615 ; WX 727 ; N uni0267 ; G 553
+U 616 ; WX 380 ; N uni0268 ; G 554
+U 617 ; WX 380 ; N uni0269 ; G 555
+U 618 ; WX 380 ; N uni026A ; G 556
+U 619 ; WX 409 ; N uni026B ; G 557
+U 620 ; WX 514 ; N uni026C ; G 558
+U 621 ; WX 380 ; N uni026D ; G 559
+U 622 ; WX 795 ; N uni026E ; G 560
+U 623 ; WX 1058 ; N uni026F ; G 561
+U 624 ; WX 1058 ; N uni0270 ; G 562
+U 625 ; WX 1058 ; N uni0271 ; G 563
+U 626 ; WX 727 ; N uni0272 ; G 564
+U 627 ; WX 727 ; N uni0273 ; G 565
+U 628 ; WX 712 ; N uni0274 ; G 566
+U 629 ; WX 667 ; N uni0275 ; G 567
+U 630 ; WX 1061 ; N uni0276 ; G 568
+U 631 ; WX 944 ; N uni0277 ; G 569
+U 632 ; WX 797 ; N uni0278 ; G 570
+U 633 ; WX 571 ; N uni0279 ; G 571
+U 634 ; WX 571 ; N uni027A ; G 572
+U 635 ; WX 571 ; N uni027B ; G 573
+U 636 ; WX 527 ; N uni027C ; G 574
+U 637 ; WX 527 ; N uni027D ; G 575
+U 638 ; WX 452 ; N uni027E ; G 576
+U 639 ; WX 487 ; N uni027F ; G 577
+U 640 ; WX 694 ; N uni0280 ; G 578
+U 641 ; WX 694 ; N uni0281 ; G 579
+U 642 ; WX 563 ; N uni0282 ; G 580
+U 643 ; WX 331 ; N uni0283 ; G 581
+U 644 ; WX 430 ; N uni0284 ; G 582
+U 645 ; WX 540 ; N uni0285 ; G 583
+U 646 ; WX 331 ; N uni0286 ; G 584
+U 647 ; WX 492 ; N uni0287 ; G 585
+U 648 ; WX 462 ; N uni0288 ; G 586
+U 649 ; WX 727 ; N uni0289 ; G 587
+U 650 ; WX 679 ; N uni028A ; G 588
+U 651 ; WX 694 ; N uni028B ; G 589
+U 652 ; WX 581 ; N uni028C ; G 590
+U 653 ; WX 861 ; N uni028D ; G 591
+U 654 ; WX 635 ; N uni028E ; G 592
+U 655 ; WX 727 ; N uni028F ; G 593
+U 656 ; WX 568 ; N uni0290 ; G 594
+U 657 ; WX 568 ; N uni0291 ; G 595
+U 658 ; WX 568 ; N uni0292 ; G 596
+U 659 ; WX 568 ; N uni0293 ; G 597
+U 660 ; WX 551 ; N uni0294 ; G 598
+U 661 ; WX 551 ; N uni0295 ; G 599
+U 662 ; WX 551 ; N uni0296 ; G 600
+U 663 ; WX 545 ; N uni0297 ; G 601
+U 664 ; WX 871 ; N uni0298 ; G 602
+U 665 ; WX 695 ; N uni0299 ; G 603
+U 666 ; WX 714 ; N uni029A ; G 604
+U 667 ; WX 689 ; N uni029B ; G 605
+U 668 ; WX 732 ; N uni029C ; G 606
+U 669 ; WX 384 ; N uni029D ; G 607
+U 670 ; WX 740 ; N uni029E ; G 608
+U 671 ; WX 617 ; N uni029F ; G 609
+U 672 ; WX 699 ; N uni02A0 ; G 610
+U 673 ; WX 551 ; N uni02A1 ; G 611
+U 674 ; WX 551 ; N uni02A2 ; G 612
+U 675 ; WX 1117 ; N uni02A3 ; G 613
+U 676 ; WX 1179 ; N uni02A4 ; G 614
+U 677 ; WX 1117 ; N uni02A5 ; G 615
+U 678 ; WX 938 ; N uni02A6 ; G 616
+U 679 ; WX 715 ; N uni02A7 ; G 617
+U 680 ; WX 946 ; N uni02A8 ; G 618
+U 681 ; WX 1039 ; N uni02A9 ; G 619
+U 682 ; WX 870 ; N uni02AA ; G 620
+U 683 ; WX 795 ; N uni02AB ; G 621
+U 684 ; WX 662 ; N uni02AC ; G 622
+U 685 ; WX 443 ; N uni02AD ; G 623
+U 686 ; WX 613 ; N uni02AE ; G 624
+U 687 ; WX 717 ; N uni02AF ; G 625
+U 688 ; WX 521 ; N uni02B0 ; G 626
+U 689 ; WX 519 ; N uni02B1 ; G 627
+U 690 ; WX 313 ; N uni02B2 ; G 628
+U 691 ; WX 414 ; N uni02B3 ; G 629
+U 692 ; WX 414 ; N uni02B4 ; G 630
+U 693 ; WX 480 ; N uni02B5 ; G 631
+U 694 ; WX 527 ; N uni02B6 ; G 632
+U 695 ; WX 542 ; N uni02B7 ; G 633
+U 696 ; WX 366 ; N uni02B8 ; G 634
+U 697 ; WX 302 ; N uni02B9 ; G 635
+U 698 ; WX 521 ; N uni02BA ; G 636
+U 699 ; WX 348 ; N uni02BB ; G 637
+U 700 ; WX 348 ; N uni02BC ; G 638
+U 701 ; WX 348 ; N uni02BD ; G 639
+U 702 ; WX 366 ; N uni02BE ; G 640
+U 703 ; WX 366 ; N uni02BF ; G 641
+U 704 ; WX 313 ; N uni02C0 ; G 642
+U 705 ; WX 313 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 282 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 282 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 720 ; WX 369 ; N uni02D0 ; G 656
+U 721 ; WX 369 ; N uni02D1 ; G 657
+U 722 ; WX 366 ; N uni02D2 ; G 658
+U 723 ; WX 366 ; N uni02D3 ; G 659
+U 726 ; WX 392 ; N uni02D6 ; G 660
+U 727 ; WX 392 ; N uni02D7 ; G 661
+U 728 ; WX 500 ; N breve ; G 662
+U 729 ; WX 500 ; N dotaccent ; G 663
+U 730 ; WX 500 ; N ring ; G 664
+U 731 ; WX 500 ; N ogonek ; G 665
+U 732 ; WX 500 ; N tilde ; G 666
+U 733 ; WX 500 ; N hungarumlaut ; G 667
+U 734 ; WX 417 ; N uni02DE ; G 668
+U 736 ; WX 378 ; N uni02E0 ; G 669
+U 737 ; WX 292 ; N uni02E1 ; G 670
+U 738 ; WX 395 ; N uni02E2 ; G 671
+U 739 ; WX 375 ; N uni02E3 ; G 672
+U 740 ; WX 313 ; N uni02E4 ; G 673
+U 741 ; WX 500 ; N uni02E5 ; G 674
+U 742 ; WX 500 ; N uni02E6 ; G 675
+U 743 ; WX 500 ; N uni02E7 ; G 676
+U 744 ; WX 500 ; N uni02E8 ; G 677
+U 745 ; WX 500 ; N uni02E9 ; G 678
+U 748 ; WX 500 ; N uni02EC ; G 679
+U 750 ; WX 553 ; N uni02EE ; G 680
+U 751 ; WX 500 ; N uni02EF ; G 681
+U 752 ; WX 500 ; N uni02F0 ; G 682
+U 755 ; WX 500 ; N uni02F3 ; G 683
+U 759 ; WX 500 ; N uni02F7 ; G 684
+U 768 ; WX 0 ; N gravecomb ; G 685
+U 769 ; WX 0 ; N acutecomb ; G 686
+U 770 ; WX 0 ; N uni0302 ; G 687
+U 771 ; WX 0 ; N tildecomb ; G 688
+U 772 ; WX 0 ; N uni0304 ; G 689
+U 773 ; WX 0 ; N uni0305 ; G 690
+U 774 ; WX 0 ; N uni0306 ; G 691
+U 775 ; WX 0 ; N uni0307 ; G 692
+U 776 ; WX 0 ; N uni0308 ; G 693
+U 777 ; WX 0 ; N hookabovecomb ; G 694
+U 778 ; WX 0 ; N uni030A ; G 695
+U 779 ; WX 0 ; N uni030B ; G 696
+U 780 ; WX 0 ; N uni030C ; G 697
+U 781 ; WX 0 ; N uni030D ; G 698
+U 782 ; WX 0 ; N uni030E ; G 699
+U 783 ; WX 0 ; N uni030F ; G 700
+U 784 ; WX 0 ; N uni0310 ; G 701
+U 785 ; WX 0 ; N uni0311 ; G 702
+U 786 ; WX 0 ; N uni0312 ; G 703
+U 787 ; WX 0 ; N uni0313 ; G 704
+U 788 ; WX 0 ; N uni0314 ; G 705
+U 789 ; WX 0 ; N uni0315 ; G 706
+U 790 ; WX 0 ; N uni0316 ; G 707
+U 791 ; WX 0 ; N uni0317 ; G 708
+U 792 ; WX 0 ; N uni0318 ; G 709
+U 793 ; WX 0 ; N uni0319 ; G 710
+U 794 ; WX 0 ; N uni031A ; G 711
+U 795 ; WX 0 ; N uni031B ; G 712
+U 796 ; WX 0 ; N uni031C ; G 713
+U 797 ; WX 0 ; N uni031D ; G 714
+U 798 ; WX 0 ; N uni031E ; G 715
+U 799 ; WX 0 ; N uni031F ; G 716
+U 800 ; WX 0 ; N uni0320 ; G 717
+U 801 ; WX 0 ; N uni0321 ; G 718
+U 802 ; WX 0 ; N uni0322 ; G 719
+U 803 ; WX 0 ; N dotbelowcomb ; G 720
+U 804 ; WX 0 ; N uni0324 ; G 721
+U 805 ; WX 0 ; N uni0325 ; G 722
+U 806 ; WX 0 ; N uni0326 ; G 723
+U 807 ; WX 0 ; N uni0327 ; G 724
+U 808 ; WX 0 ; N uni0328 ; G 725
+U 809 ; WX 0 ; N uni0329 ; G 726
+U 810 ; WX 0 ; N uni032A ; G 727
+U 811 ; WX 0 ; N uni032B ; G 728
+U 812 ; WX 0 ; N uni032C ; G 729
+U 813 ; WX 0 ; N uni032D ; G 730
+U 814 ; WX 0 ; N uni032E ; G 731
+U 815 ; WX 0 ; N uni032F ; G 732
+U 816 ; WX 0 ; N uni0330 ; G 733
+U 817 ; WX 0 ; N uni0331 ; G 734
+U 818 ; WX 0 ; N uni0332 ; G 735
+U 819 ; WX 0 ; N uni0333 ; G 736
+U 820 ; WX 0 ; N uni0334 ; G 737
+U 821 ; WX 0 ; N uni0335 ; G 738
+U 822 ; WX 0 ; N uni0336 ; G 739
+U 823 ; WX 0 ; N uni0337 ; G 740
+U 824 ; WX 0 ; N uni0338 ; G 741
+U 825 ; WX 0 ; N uni0339 ; G 742
+U 826 ; WX 0 ; N uni033A ; G 743
+U 827 ; WX 0 ; N uni033B ; G 744
+U 828 ; WX 0 ; N uni033C ; G 745
+U 829 ; WX 0 ; N uni033D ; G 746
+U 830 ; WX 0 ; N uni033E ; G 747
+U 831 ; WX 0 ; N uni033F ; G 748
+U 835 ; WX 0 ; N uni0343 ; G 749
+U 847 ; WX 0 ; N uni034F ; G 750
+U 856 ; WX 0 ; N uni0358 ; G 751
+U 864 ; WX 0 ; N uni0360 ; G 752
+U 865 ; WX 0 ; N uni0361 ; G 753
+U 880 ; WX 779 ; N uni0370 ; G 754
+U 881 ; WX 576 ; N uni0371 ; G 755
+U 882 ; WX 803 ; N uni0372 ; G 756
+U 883 ; WX 777 ; N uni0373 ; G 757
+U 884 ; WX 302 ; N uni0374 ; G 758
+U 885 ; WX 302 ; N uni0375 ; G 759
+U 886 ; WX 963 ; N uni0376 ; G 760
+U 887 ; WX 737 ; N uni0377 ; G 761
+U 890 ; WX 500 ; N uni037A ; G 762
+U 891 ; WX 609 ; N uni037B ; G 763
+U 892 ; WX 609 ; N uni037C ; G 764
+U 893 ; WX 609 ; N uni037D ; G 765
+U 894 ; WX 369 ; N uni037E ; G 766
+U 895 ; WX 473 ; N uni037F ; G 767
+U 900 ; WX 500 ; N tonos ; G 768
+U 901 ; WX 500 ; N dieresistonos ; G 769
+U 902 ; WX 776 ; N Alphatonos ; G 770
+U 903 ; WX 348 ; N anoteleia ; G 771
+U 904 ; WX 947 ; N Epsilontonos ; G 772
+U 905 ; WX 1136 ; N Etatonos ; G 773
+U 906 ; WX 662 ; N Iotatonos ; G 774
+U 908 ; WX 887 ; N Omicrontonos ; G 775
+U 910 ; WX 953 ; N Upsilontonos ; G 776
+U 911 ; WX 911 ; N Omegatonos ; G 777
+U 912 ; WX 484 ; N iotadieresistonos ; G 778
+U 913 ; WX 776 ; N Alpha ; G 779
+U 914 ; WX 845 ; N Beta ; G 780
+U 915 ; WX 710 ; N Gamma ; G 781
+U 916 ; WX 776 ; N uni0394 ; G 782
+U 917 ; WX 762 ; N Epsilon ; G 783
+U 918 ; WX 730 ; N Zeta ; G 784
+U 919 ; WX 945 ; N Eta ; G 785
+U 920 ; WX 871 ; N Theta ; G 786
+U 921 ; WX 468 ; N Iota ; G 787
+U 922 ; WX 869 ; N Kappa ; G 788
+U 923 ; WX 776 ; N Lambda ; G 789
+U 924 ; WX 1107 ; N Mu ; G 790
+U 925 ; WX 914 ; N Nu ; G 791
+U 926 ; WX 704 ; N Xi ; G 792
+U 927 ; WX 871 ; N Omicron ; G 793
+U 928 ; WX 945 ; N Pi ; G 794
+U 929 ; WX 752 ; N Rho ; G 795
+U 931 ; WX 707 ; N Sigma ; G 796
+U 932 ; WX 744 ; N Tau ; G 797
+U 933 ; WX 714 ; N Upsilon ; G 798
+U 934 ; WX 871 ; N Phi ; G 799
+U 935 ; WX 776 ; N Chi ; G 800
+U 936 ; WX 913 ; N Psi ; G 801
+U 937 ; WX 890 ; N Omega ; G 802
+U 938 ; WX 468 ; N Iotadieresis ; G 803
+U 939 ; WX 714 ; N Upsilondieresis ; G 804
+U 940 ; WX 770 ; N alphatonos ; G 805
+U 941 ; WX 608 ; N epsilontonos ; G 806
+U 942 ; WX 727 ; N etatonos ; G 807
+U 943 ; WX 484 ; N iotatonos ; G 808
+U 944 ; WX 694 ; N upsilondieresistonos ; G 809
+U 945 ; WX 770 ; N alpha ; G 810
+U 946 ; WX 664 ; N beta ; G 811
+U 947 ; WX 660 ; N gamma ; G 812
+U 948 ; WX 667 ; N delta ; G 813
+U 949 ; WX 608 ; N epsilon ; G 814
+U 950 ; WX 592 ; N zeta ; G 815
+U 951 ; WX 727 ; N eta ; G 816
+U 952 ; WX 667 ; N theta ; G 817
+U 953 ; WX 484 ; N iota ; G 818
+U 954 ; WX 750 ; N kappa ; G 819
+U 955 ; WX 701 ; N lambda ; G 820
+U 956 ; WX 732 ; N uni03BC ; G 821
+U 957 ; WX 694 ; N nu ; G 822
+U 958 ; WX 592 ; N xi ; G 823
+U 959 ; WX 667 ; N omicron ; G 824
+U 960 ; WX 732 ; N pi ; G 825
+U 961 ; WX 665 ; N rho ; G 826
+U 962 ; WX 609 ; N sigma1 ; G 827
+U 963 ; WX 737 ; N sigma ; G 828
+U 964 ; WX 673 ; N tau ; G 829
+U 965 ; WX 694 ; N upsilon ; G 830
+U 966 ; WX 905 ; N phi ; G 831
+U 967 ; WX 658 ; N chi ; G 832
+U 968 ; WX 941 ; N psi ; G 833
+U 969 ; WX 952 ; N omega ; G 834
+U 970 ; WX 484 ; N iotadieresis ; G 835
+U 971 ; WX 694 ; N upsilondieresis ; G 836
+U 972 ; WX 667 ; N omicrontonos ; G 837
+U 973 ; WX 694 ; N upsilontonos ; G 838
+U 974 ; WX 952 ; N omegatonos ; G 839
+U 975 ; WX 869 ; N uni03CF ; G 840
+U 976 ; WX 667 ; N uni03D0 ; G 841
+U 977 ; WX 849 ; N theta1 ; G 842
+U 978 ; WX 764 ; N Upsilon1 ; G 843
+U 979 ; WX 969 ; N uni03D3 ; G 844
+U 980 ; WX 764 ; N uni03D4 ; G 845
+U 981 ; WX 941 ; N phi1 ; G 846
+U 982 ; WX 952 ; N omega1 ; G 847
+U 983 ; WX 655 ; N uni03D7 ; G 848
+U 984 ; WX 871 ; N uni03D8 ; G 849
+U 985 ; WX 667 ; N uni03D9 ; G 850
+U 986 ; WX 796 ; N uni03DA ; G 851
+U 987 ; WX 609 ; N uni03DB ; G 852
+U 988 ; WX 710 ; N uni03DC ; G 853
+U 989 ; WX 527 ; N uni03DD ; G 854
+U 990 ; WX 590 ; N uni03DE ; G 855
+U 991 ; WX 660 ; N uni03DF ; G 856
+U 992 ; WX 796 ; N uni03E0 ; G 857
+U 993 ; WX 667 ; N uni03E1 ; G 858
+U 1008 ; WX 655 ; N uni03F0 ; G 859
+U 1009 ; WX 665 ; N uni03F1 ; G 860
+U 1010 ; WX 609 ; N uni03F2 ; G 861
+U 1011 ; WX 362 ; N uni03F3 ; G 862
+U 1012 ; WX 871 ; N uni03F4 ; G 863
+U 1013 ; WX 609 ; N uni03F5 ; G 864
+U 1014 ; WX 609 ; N uni03F6 ; G 865
+U 1015 ; WX 757 ; N uni03F7 ; G 866
+U 1016 ; WX 699 ; N uni03F8 ; G 867
+U 1017 ; WX 796 ; N uni03F9 ; G 868
+U 1018 ; WX 1107 ; N uni03FA ; G 869
+U 1019 ; WX 860 ; N uni03FB ; G 870
+U 1020 ; WX 692 ; N uni03FC ; G 871
+U 1021 ; WX 796 ; N uni03FD ; G 872
+U 1022 ; WX 796 ; N uni03FE ; G 873
+U 1023 ; WX 796 ; N uni03FF ; G 874
+U 1024 ; WX 762 ; N uni0400 ; G 875
+U 1025 ; WX 762 ; N uni0401 ; G 876
+U 1026 ; WX 901 ; N uni0402 ; G 877
+U 1027 ; WX 690 ; N uni0403 ; G 878
+U 1028 ; WX 795 ; N uni0404 ; G 879
+U 1029 ; WX 722 ; N uni0405 ; G 880
+U 1030 ; WX 468 ; N uni0406 ; G 881
+U 1031 ; WX 468 ; N uni0407 ; G 882
+U 1032 ; WX 473 ; N uni0408 ; G 883
+U 1033 ; WX 1202 ; N uni0409 ; G 884
+U 1034 ; WX 1262 ; N uni040A ; G 885
+U 1035 ; WX 963 ; N uni040B ; G 886
+U 1036 ; WX 910 ; N uni040C ; G 887
+U 1037 ; WX 945 ; N uni040D ; G 888
+U 1038 ; WX 812 ; N uni040E ; G 889
+U 1039 ; WX 945 ; N uni040F ; G 890
+U 1040 ; WX 814 ; N uni0410 ; G 891
+U 1041 ; WX 854 ; N uni0411 ; G 892
+U 1042 ; WX 845 ; N uni0412 ; G 893
+U 1043 ; WX 690 ; N uni0413 ; G 894
+U 1044 ; WX 889 ; N uni0414 ; G 895
+U 1045 ; WX 762 ; N uni0415 ; G 896
+U 1046 ; WX 1312 ; N uni0416 ; G 897
+U 1047 ; WX 721 ; N uni0417 ; G 898
+U 1048 ; WX 945 ; N uni0418 ; G 899
+U 1049 ; WX 945 ; N uni0419 ; G 900
+U 1050 ; WX 910 ; N uni041A ; G 901
+U 1051 ; WX 884 ; N uni041B ; G 902
+U 1052 ; WX 1107 ; N uni041C ; G 903
+U 1053 ; WX 945 ; N uni041D ; G 904
+U 1054 ; WX 871 ; N uni041E ; G 905
+U 1055 ; WX 945 ; N uni041F ; G 906
+U 1056 ; WX 752 ; N uni0420 ; G 907
+U 1057 ; WX 796 ; N uni0421 ; G 908
+U 1058 ; WX 744 ; N uni0422 ; G 909
+U 1059 ; WX 812 ; N uni0423 ; G 910
+U 1060 ; WX 949 ; N uni0424 ; G 911
+U 1061 ; WX 776 ; N uni0425 ; G 912
+U 1062 ; WX 966 ; N uni0426 ; G 913
+U 1063 ; WX 913 ; N uni0427 ; G 914
+U 1064 ; WX 1268 ; N uni0428 ; G 915
+U 1065 ; WX 1293 ; N uni0429 ; G 916
+U 1066 ; WX 957 ; N uni042A ; G 917
+U 1067 ; WX 1202 ; N uni042B ; G 918
+U 1068 ; WX 825 ; N uni042C ; G 919
+U 1069 ; WX 795 ; N uni042D ; G 920
+U 1070 ; WX 1287 ; N uni042E ; G 921
+U 1071 ; WX 882 ; N uni042F ; G 922
+U 1072 ; WX 648 ; N uni0430 ; G 923
+U 1073 ; WX 722 ; N uni0431 ; G 924
+U 1074 ; WX 657 ; N uni0432 ; G 925
+U 1075 ; WX 563 ; N uni0433 ; G 926
+U 1076 ; WX 695 ; N uni0434 ; G 927
+U 1077 ; WX 636 ; N uni0435 ; G 928
+U 1078 ; WX 1306 ; N uni0436 ; G 929
+U 1079 ; WX 638 ; N uni0437 ; G 930
+U 1080 ; WX 727 ; N uni0438 ; G 931
+U 1081 ; WX 727 ; N uni0439 ; G 932
+U 1082 ; WX 677 ; N uni043A ; G 933
+U 1083 ; WX 732 ; N uni043B ; G 934
+U 1084 ; WX 951 ; N uni043C ; G 935
+U 1085 ; WX 729 ; N uni043D ; G 936
+U 1086 ; WX 667 ; N uni043E ; G 937
+U 1087 ; WX 727 ; N uni043F ; G 938
+U 1088 ; WX 699 ; N uni0440 ; G 939
+U 1089 ; WX 609 ; N uni0441 ; G 940
+U 1090 ; WX 1058 ; N uni0442 ; G 941
+U 1091 ; WX 598 ; N uni0443 ; G 942
+U 1092 ; WX 902 ; N uni0444 ; G 943
+U 1093 ; WX 596 ; N uni0445 ; G 944
+U 1094 ; WX 803 ; N uni0446 ; G 945
+U 1095 ; WX 715 ; N uni0447 ; G 946
+U 1096 ; WX 1058 ; N uni0448 ; G 947
+U 1097 ; WX 1134 ; N uni0449 ; G 948
+U 1098 ; WX 727 ; N uni044A ; G 949
+U 1099 ; WX 1018 ; N uni044B ; G 950
+U 1100 ; WX 660 ; N uni044C ; G 951
+U 1101 ; WX 645 ; N uni044D ; G 952
+U 1102 ; WX 1001 ; N uni044E ; G 953
+U 1103 ; WX 796 ; N uni044F ; G 954
+U 1104 ; WX 636 ; N uni0450 ; G 955
+U 1105 ; WX 636 ; N uni0451 ; G 956
+U 1106 ; WX 719 ; N uni0452 ; G 957
+U 1107 ; WX 563 ; N uni0453 ; G 958
+U 1108 ; WX 609 ; N uni0454 ; G 959
+U 1109 ; WX 563 ; N uni0455 ; G 960
+U 1110 ; WX 380 ; N uni0456 ; G 961
+U 1111 ; WX 380 ; N uni0457 ; G 962
+U 1112 ; WX 362 ; N uni0458 ; G 963
+U 1113 ; WX 1014 ; N uni0459 ; G 964
+U 1114 ; WX 1011 ; N uni045A ; G 965
+U 1115 ; WX 727 ; N uni045B ; G 966
+U 1116 ; WX 677 ; N uni045C ; G 967
+U 1117 ; WX 727 ; N uni045D ; G 968
+U 1118 ; WX 598 ; N uni045E ; G 969
+U 1119 ; WX 727 ; N uni045F ; G 970
+U 1122 ; WX 880 ; N uni0462 ; G 971
+U 1123 ; WX 1050 ; N uni0463 ; G 972
+U 1124 ; WX 1195 ; N uni0464 ; G 973
+U 1125 ; WX 963 ; N uni0465 ; G 974
+U 1130 ; WX 1312 ; N uni046A ; G 975
+U 1131 ; WX 1010 ; N uni046B ; G 976
+U 1132 ; WX 1630 ; N uni046C ; G 977
+U 1133 ; WX 1247 ; N uni046D ; G 978
+U 1136 ; WX 1096 ; N uni0470 ; G 979
+U 1137 ; WX 1105 ; N uni0471 ; G 980
+U 1138 ; WX 871 ; N uni0472 ; G 981
+U 1139 ; WX 652 ; N uni0473 ; G 982
+U 1140 ; WX 916 ; N uni0474 ; G 983
+U 1141 ; WX 749 ; N uni0475 ; G 984
+U 1142 ; WX 916 ; N uni0476 ; G 985
+U 1143 ; WX 749 ; N uni0477 ; G 986
+U 1164 ; WX 846 ; N uni048C ; G 987
+U 1165 ; WX 673 ; N uni048D ; G 988
+U 1168 ; WX 700 ; N uni0490 ; G 989
+U 1169 ; WX 618 ; N uni0491 ; G 990
+U 1170 ; WX 690 ; N uni0492 ; G 991
+U 1171 ; WX 563 ; N uni0493 ; G 992
+U 1172 ; WX 854 ; N uni0494 ; G 993
+U 1173 ; WX 705 ; N uni0495 ; G 994
+U 1174 ; WX 1312 ; N uni0496 ; G 995
+U 1175 ; WX 1306 ; N uni0497 ; G 996
+U 1176 ; WX 721 ; N uni0498 ; G 997
+U 1177 ; WX 638 ; N uni0499 ; G 998
+U 1178 ; WX 902 ; N uni049A ; G 999
+U 1179 ; WX 703 ; N uni049B ; G 1000
+U 1182 ; WX 910 ; N uni049E ; G 1001
+U 1183 ; WX 677 ; N uni049F ; G 1002
+U 1184 ; WX 1041 ; N uni04A0 ; G 1003
+U 1185 ; WX 760 ; N uni04A1 ; G 1004
+U 1186 ; WX 952 ; N uni04A2 ; G 1005
+U 1187 ; WX 805 ; N uni04A3 ; G 1006
+U 1188 ; WX 1167 ; N uni04A4 ; G 1007
+U 1189 ; WX 955 ; N uni04A5 ; G 1008
+U 1190 ; WX 1324 ; N uni04A6 ; G 1009
+U 1191 ; WX 1013 ; N uni04A7 ; G 1010
+U 1194 ; WX 796 ; N uni04AA ; G 1011
+U 1195 ; WX 609 ; N uni04AB ; G 1012
+U 1196 ; WX 744 ; N uni04AC ; G 1013
+U 1197 ; WX 1142 ; N uni04AD ; G 1014
+U 1198 ; WX 714 ; N uni04AE ; G 1015
+U 1199 ; WX 572 ; N uni04AF ; G 1016
+U 1200 ; WX 713 ; N uni04B0 ; G 1017
+U 1201 ; WX 572 ; N uni04B1 ; G 1018
+U 1202 ; WX 789 ; N uni04B2 ; G 1019
+U 1203 ; WX 596 ; N uni04B3 ; G 1020
+U 1204 ; WX 1010 ; N uni04B4 ; G 1021
+U 1205 ; WX 833 ; N uni04B5 ; G 1022
+U 1206 ; WX 913 ; N uni04B6 ; G 1023
+U 1207 ; WX 792 ; N uni04B7 ; G 1024
+U 1210 ; WX 910 ; N uni04BA ; G 1025
+U 1211 ; WX 727 ; N uni04BB ; G 1026
+U 1216 ; WX 468 ; N uni04C0 ; G 1027
+U 1217 ; WX 1312 ; N uni04C1 ; G 1028
+U 1218 ; WX 1306 ; N uni04C2 ; G 1029
+U 1219 ; WX 869 ; N uni04C3 ; G 1030
+U 1220 ; WX 693 ; N uni04C4 ; G 1031
+U 1223 ; WX 945 ; N uni04C7 ; G 1032
+U 1224 ; WX 732 ; N uni04C8 ; G 1033
+U 1227 ; WX 984 ; N uni04CB ; G 1034
+U 1228 ; WX 732 ; N uni04CC ; G 1035
+U 1231 ; WX 380 ; N uni04CF ; G 1036
+U 1232 ; WX 814 ; N uni04D0 ; G 1037
+U 1233 ; WX 648 ; N uni04D1 ; G 1038
+U 1234 ; WX 814 ; N uni04D2 ; G 1039
+U 1235 ; WX 648 ; N uni04D3 ; G 1040
+U 1236 ; WX 1034 ; N uni04D4 ; G 1041
+U 1237 ; WX 975 ; N uni04D5 ; G 1042
+U 1238 ; WX 762 ; N uni04D6 ; G 1043
+U 1239 ; WX 636 ; N uni04D7 ; G 1044
+U 1240 ; WX 871 ; N uni04D8 ; G 1045
+U 1241 ; WX 636 ; N uni04D9 ; G 1046
+U 1242 ; WX 871 ; N uni04DA ; G 1047
+U 1243 ; WX 636 ; N uni04DB ; G 1048
+U 1244 ; WX 1312 ; N uni04DC ; G 1049
+U 1245 ; WX 1306 ; N uni04DD ; G 1050
+U 1246 ; WX 721 ; N uni04DE ; G 1051
+U 1247 ; WX 638 ; N uni04DF ; G 1052
+U 1248 ; WX 657 ; N uni04E0 ; G 1053
+U 1249 ; WX 568 ; N uni04E1 ; G 1054
+U 1250 ; WX 945 ; N uni04E2 ; G 1055
+U 1251 ; WX 727 ; N uni04E3 ; G 1056
+U 1252 ; WX 945 ; N uni04E4 ; G 1057
+U 1253 ; WX 727 ; N uni04E5 ; G 1058
+U 1254 ; WX 871 ; N uni04E6 ; G 1059
+U 1255 ; WX 667 ; N uni04E7 ; G 1060
+U 1256 ; WX 871 ; N uni04E8 ; G 1061
+U 1257 ; WX 667 ; N uni04E9 ; G 1062
+U 1258 ; WX 871 ; N uni04EA ; G 1063
+U 1259 ; WX 667 ; N uni04EB ; G 1064
+U 1260 ; WX 795 ; N uni04EC ; G 1065
+U 1261 ; WX 645 ; N uni04ED ; G 1066
+U 1262 ; WX 812 ; N uni04EE ; G 1067
+U 1263 ; WX 598 ; N uni04EF ; G 1068
+U 1264 ; WX 812 ; N uni04F0 ; G 1069
+U 1265 ; WX 598 ; N uni04F1 ; G 1070
+U 1266 ; WX 812 ; N uni04F2 ; G 1071
+U 1267 ; WX 598 ; N uni04F3 ; G 1072
+U 1268 ; WX 913 ; N uni04F4 ; G 1073
+U 1269 ; WX 715 ; N uni04F5 ; G 1074
+U 1270 ; WX 690 ; N uni04F6 ; G 1075
+U 1271 ; WX 563 ; N uni04F7 ; G 1076
+U 1272 ; WX 1202 ; N uni04F8 ; G 1077
+U 1273 ; WX 1018 ; N uni04F9 ; G 1078
+U 1296 ; WX 721 ; N uni0510 ; G 1079
+U 1297 ; WX 638 ; N uni0511 ; G 1080
+U 1298 ; WX 884 ; N uni0512 ; G 1081
+U 1299 ; WX 732 ; N uni0513 ; G 1082
+U 1300 ; WX 1248 ; N uni0514 ; G 1083
+U 1301 ; WX 1005 ; N uni0515 ; G 1084
+U 1306 ; WX 820 ; N uni051A ; G 1085
+U 1307 ; WX 640 ; N uni051B ; G 1086
+U 1308 ; WX 1028 ; N uni051C ; G 1087
+U 1309 ; WX 856 ; N uni051D ; G 1088
+U 1329 ; WX 942 ; N uni0531 ; G 1089
+U 1330 ; WX 832 ; N uni0532 ; G 1090
+U 1331 ; WX 894 ; N uni0533 ; G 1091
+U 1332 ; WX 909 ; N uni0534 ; G 1092
+U 1333 ; WX 822 ; N uni0535 ; G 1093
+U 1334 ; WX 821 ; N uni0536 ; G 1094
+U 1335 ; WX 747 ; N uni0537 ; G 1095
+U 1336 ; WX 832 ; N uni0538 ; G 1096
+U 1337 ; WX 1125 ; N uni0539 ; G 1097
+U 1338 ; WX 894 ; N uni053A ; G 1098
+U 1339 ; WX 803 ; N uni053B ; G 1099
+U 1340 ; WX 722 ; N uni053C ; G 1100
+U 1341 ; WX 1188 ; N uni053D ; G 1101
+U 1342 ; WX 887 ; N uni053E ; G 1102
+U 1343 ; WX 842 ; N uni053F ; G 1103
+U 1344 ; WX 737 ; N uni0540 ; G 1104
+U 1345 ; WX 863 ; N uni0541 ; G 1105
+U 1346 ; WX 918 ; N uni0542 ; G 1106
+U 1347 ; WX 851 ; N uni0543 ; G 1107
+U 1348 ; WX 977 ; N uni0544 ; G 1108
+U 1349 ; WX 833 ; N uni0545 ; G 1109
+U 1350 ; WX 914 ; N uni0546 ; G 1110
+U 1351 ; WX 843 ; N uni0547 ; G 1111
+U 1352 ; WX 871 ; N uni0548 ; G 1112
+U 1353 ; WX 818 ; N uni0549 ; G 1113
+U 1354 ; WX 1034 ; N uni054A ; G 1114
+U 1355 ; WX 846 ; N uni054B ; G 1115
+U 1356 ; WX 964 ; N uni054C ; G 1116
+U 1357 ; WX 871 ; N uni054D ; G 1117
+U 1358 ; WX 914 ; N uni054E ; G 1118
+U 1359 ; WX 808 ; N uni054F ; G 1119
+U 1360 ; WX 808 ; N uni0550 ; G 1120
+U 1361 ; WX 836 ; N uni0551 ; G 1121
+U 1362 ; WX 710 ; N uni0552 ; G 1122
+U 1363 ; WX 955 ; N uni0553 ; G 1123
+U 1364 ; WX 891 ; N uni0554 ; G 1124
+U 1365 ; WX 871 ; N uni0555 ; G 1125
+U 1366 ; WX 963 ; N uni0556 ; G 1126
+U 1369 ; WX 307 ; N uni0559 ; G 1127
+U 1370 ; WX 264 ; N uni055A ; G 1128
+U 1371 ; WX 293 ; N uni055B ; G 1129
+U 1372 ; WX 391 ; N uni055C ; G 1130
+U 1373 ; WX 323 ; N uni055D ; G 1131
+U 1374 ; WX 439 ; N uni055E ; G 1132
+U 1375 ; WX 500 ; N uni055F ; G 1133
+U 1377 ; WX 1055 ; N uni0561 ; G 1134
+U 1378 ; WX 695 ; N uni0562 ; G 1135
+U 1379 ; WX 776 ; N uni0563 ; G 1136
+U 1380 ; WX 801 ; N uni0564 ; G 1137
+U 1381 ; WX 729 ; N uni0565 ; G 1138
+U 1382 ; WX 742 ; N uni0566 ; G 1139
+U 1383 ; WX 599 ; N uni0567 ; G 1140
+U 1384 ; WX 733 ; N uni0568 ; G 1141
+U 1385 ; WX 909 ; N uni0569 ; G 1142
+U 1386 ; WX 768 ; N uni056A ; G 1143
+U 1387 ; WX 724 ; N uni056B ; G 1144
+U 1388 ; WX 398 ; N uni056C ; G 1145
+U 1389 ; WX 1087 ; N uni056D ; G 1146
+U 1390 ; WX 695 ; N uni056E ; G 1147
+U 1391 ; WX 719 ; N uni056F ; G 1148
+U 1392 ; WX 737 ; N uni0570 ; G 1149
+U 1393 ; WX 684 ; N uni0571 ; G 1150
+U 1394 ; WX 738 ; N uni0572 ; G 1151
+U 1395 ; WX 703 ; N uni0573 ; G 1152
+U 1396 ; WX 724 ; N uni0574 ; G 1153
+U 1397 ; WX 359 ; N uni0575 ; G 1154
+U 1398 ; WX 719 ; N uni0576 ; G 1155
+U 1399 ; WX 496 ; N uni0577 ; G 1156
+U 1400 ; WX 738 ; N uni0578 ; G 1157
+U 1401 ; WX 428 ; N uni0579 ; G 1158
+U 1402 ; WX 1059 ; N uni057A ; G 1159
+U 1403 ; WX 668 ; N uni057B ; G 1160
+U 1404 ; WX 744 ; N uni057C ; G 1161
+U 1405 ; WX 724 ; N uni057D ; G 1162
+U 1406 ; WX 724 ; N uni057E ; G 1163
+U 1407 ; WX 1040 ; N uni057F ; G 1164
+U 1408 ; WX 724 ; N uni0580 ; G 1165
+U 1409 ; WX 713 ; N uni0581 ; G 1166
+U 1410 ; WX 493 ; N uni0582 ; G 1167
+U 1411 ; WX 1040 ; N uni0583 ; G 1168
+U 1412 ; WX 734 ; N uni0584 ; G 1169
+U 1413 ; WX 693 ; N uni0585 ; G 1170
+U 1414 ; WX 956 ; N uni0586 ; G 1171
+U 1415 ; WX 833 ; N uni0587 ; G 1172
+U 1417 ; WX 340 ; N uni0589 ; G 1173
+U 1418 ; WX 388 ; N uni058A ; G 1174
+U 3647 ; WX 696 ; N uni0E3F ; G 1175
+U 4256 ; WX 765 ; N uni10A0 ; G 1176
+U 4257 ; WX 945 ; N uni10A1 ; G 1177
+U 4258 ; WX 876 ; N uni10A2 ; G 1178
+U 4259 ; WX 884 ; N uni10A3 ; G 1179
+U 4260 ; WX 791 ; N uni10A4 ; G 1180
+U 4261 ; WX 1087 ; N uni10A5 ; G 1181
+U 4262 ; WX 1024 ; N uni10A6 ; G 1182
+U 4263 ; WX 1223 ; N uni10A7 ; G 1183
+U 4264 ; WX 653 ; N uni10A8 ; G 1184
+U 4265 ; WX 828 ; N uni10A9 ; G 1185
+U 4266 ; WX 1061 ; N uni10AA ; G 1186
+U 4267 ; WX 1061 ; N uni10AB ; G 1187
+U 4268 ; WX 806 ; N uni10AC ; G 1188
+U 4269 ; WX 1145 ; N uni10AD ; G 1189
+U 4270 ; WX 979 ; N uni10AE ; G 1190
+U 4271 ; WX 912 ; N uni10AF ; G 1191
+U 4272 ; WX 1119 ; N uni10B0 ; G 1192
+U 4273 ; WX 802 ; N uni10B1 ; G 1193
+U 4274 ; WX 766 ; N uni10B2 ; G 1194
+U 4275 ; WX 1085 ; N uni10B3 ; G 1195
+U 4276 ; WX 986 ; N uni10B4 ; G 1196
+U 4277 ; WX 1076 ; N uni10B5 ; G 1197
+U 4278 ; WX 820 ; N uni10B6 ; G 1198
+U 4279 ; WX 843 ; N uni10B7 ; G 1199
+U 4280 ; WX 831 ; N uni10B8 ; G 1200
+U 4281 ; WX 843 ; N uni10B9 ; G 1201
+U 4282 ; WX 918 ; N uni10BA ; G 1202
+U 4283 ; WX 1086 ; N uni10BB ; G 1203
+U 4284 ; WX 779 ; N uni10BC ; G 1204
+U 4285 ; WX 832 ; N uni10BD ; G 1205
+U 4286 ; WX 822 ; N uni10BE ; G 1206
+U 4287 ; WX 1121 ; N uni10BF ; G 1207
+U 4288 ; WX 1132 ; N uni10C0 ; G 1208
+U 4289 ; WX 812 ; N uni10C1 ; G 1209
+U 4290 ; WX 902 ; N uni10C2 ; G 1210
+U 4291 ; WX 812 ; N uni10C3 ; G 1211
+U 4292 ; WX 890 ; N uni10C4 ; G 1212
+U 4293 ; WX 1073 ; N uni10C5 ; G 1213
+U 4304 ; WX 594 ; N uni10D0 ; G 1214
+U 4305 ; WX 625 ; N uni10D1 ; G 1215
+U 4306 ; WX 643 ; N uni10D2 ; G 1216
+U 4307 ; WX 887 ; N uni10D3 ; G 1217
+U 4308 ; WX 615 ; N uni10D4 ; G 1218
+U 4309 ; WX 611 ; N uni10D5 ; G 1219
+U 4310 ; WX 666 ; N uni10D6 ; G 1220
+U 4311 ; WX 915 ; N uni10D7 ; G 1221
+U 4312 ; WX 613 ; N uni10D8 ; G 1222
+U 4313 ; WX 600 ; N uni10D9 ; G 1223
+U 4314 ; WX 1120 ; N uni10DA ; G 1224
+U 4315 ; WX 654 ; N uni10DB ; G 1225
+U 4316 ; WX 640 ; N uni10DC ; G 1226
+U 4317 ; WX 879 ; N uni10DD ; G 1227
+U 4318 ; WX 624 ; N uni10DE ; G 1228
+U 4319 ; WX 634 ; N uni10DF ; G 1229
+U 4320 ; WX 877 ; N uni10E0 ; G 1230
+U 4321 ; WX 657 ; N uni10E1 ; G 1231
+U 4322 ; WX 802 ; N uni10E2 ; G 1232
+U 4323 ; WX 751 ; N uni10E3 ; G 1233
+U 4324 ; WX 869 ; N uni10E4 ; G 1234
+U 4325 ; WX 639 ; N uni10E5 ; G 1235
+U 4326 ; WX 912 ; N uni10E6 ; G 1236
+U 4327 ; WX 622 ; N uni10E7 ; G 1237
+U 4328 ; WX 647 ; N uni10E8 ; G 1238
+U 4329 ; WX 640 ; N uni10E9 ; G 1239
+U 4330 ; WX 729 ; N uni10EA ; G 1240
+U 4331 ; WX 641 ; N uni10EB ; G 1241
+U 4332 ; WX 639 ; N uni10EC ; G 1242
+U 4333 ; WX 629 ; N uni10ED ; G 1243
+U 4334 ; WX 674 ; N uni10EE ; G 1244
+U 4335 ; WX 737 ; N uni10EF ; G 1245
+U 4336 ; WX 625 ; N uni10F0 ; G 1246
+U 4337 ; WX 657 ; N uni10F1 ; G 1247
+U 4338 ; WX 625 ; N uni10F2 ; G 1248
+U 4339 ; WX 625 ; N uni10F3 ; G 1249
+U 4340 ; WX 624 ; N uni10F4 ; G 1250
+U 4341 ; WX 670 ; N uni10F5 ; G 1251
+U 4342 ; WX 940 ; N uni10F6 ; G 1252
+U 4343 ; WX 680 ; N uni10F7 ; G 1253
+U 4344 ; WX 636 ; N uni10F8 ; G 1254
+U 4345 ; WX 672 ; N uni10F9 ; G 1255
+U 4346 ; WX 625 ; N uni10FA ; G 1256
+U 4347 ; WX 446 ; N uni10FB ; G 1257
+U 4348 ; WX 363 ; N uni10FC ; G 1258
+U 7424 ; WX 641 ; N uni1D00 ; G 1259
+U 7425 ; WX 892 ; N uni1D01 ; G 1260
+U 7426 ; WX 932 ; N uni1D02 ; G 1261
+U 7427 ; WX 695 ; N uni1D03 ; G 1262
+U 7428 ; WX 609 ; N uni1D04 ; G 1263
+U 7429 ; WX 675 ; N uni1D05 ; G 1264
+U 7430 ; WX 675 ; N uni1D06 ; G 1265
+U 7431 ; WX 617 ; N uni1D07 ; G 1266
+U 7432 ; WX 509 ; N uni1D08 ; G 1267
+U 7433 ; WX 320 ; N uni1D09 ; G 1268
+U 7434 ; WX 561 ; N uni1D0A ; G 1269
+U 7435 ; WX 722 ; N uni1D0B ; G 1270
+U 7436 ; WX 617 ; N uni1D0C ; G 1271
+U 7437 ; WX 869 ; N uni1D0D ; G 1272
+U 7438 ; WX 737 ; N uni1D0E ; G 1273
+U 7439 ; WX 667 ; N uni1D0F ; G 1274
+U 7440 ; WX 609 ; N uni1D10 ; G 1275
+U 7441 ; WX 628 ; N uni1D11 ; G 1276
+U 7442 ; WX 628 ; N uni1D12 ; G 1277
+U 7443 ; WX 667 ; N uni1D13 ; G 1278
+U 7444 ; WX 1028 ; N uni1D14 ; G 1279
+U 7445 ; WX 598 ; N uni1D15 ; G 1280
+U 7446 ; WX 667 ; N uni1D16 ; G 1281
+U 7447 ; WX 667 ; N uni1D17 ; G 1282
+U 7448 ; WX 586 ; N uni1D18 ; G 1283
+U 7449 ; WX 801 ; N uni1D19 ; G 1284
+U 7450 ; WX 801 ; N uni1D1A ; G 1285
+U 7451 ; WX 620 ; N uni1D1B ; G 1286
+U 7452 ; WX 647 ; N uni1D1C ; G 1287
+U 7453 ; WX 664 ; N uni1D1D ; G 1288
+U 7454 ; WX 923 ; N uni1D1E ; G 1289
+U 7455 ; WX 655 ; N uni1D1F ; G 1290
+U 7456 ; WX 581 ; N uni1D20 ; G 1291
+U 7457 ; WX 861 ; N uni1D21 ; G 1292
+U 7458 ; WX 568 ; N uni1D22 ; G 1293
+U 7459 ; WX 568 ; N uni1D23 ; G 1294
+U 7460 ; WX 588 ; N uni1D24 ; G 1295
+U 7461 ; WX 802 ; N uni1D25 ; G 1296
+U 7462 ; WX 586 ; N uni1D26 ; G 1297
+U 7463 ; WX 641 ; N uni1D27 ; G 1298
+U 7464 ; WX 732 ; N uni1D28 ; G 1299
+U 7465 ; WX 586 ; N uni1D29 ; G 1300
+U 7466 ; WX 854 ; N uni1D2A ; G 1301
+U 7467 ; WX 705 ; N uni1D2B ; G 1302
+U 7468 ; WX 489 ; N uni1D2C ; G 1303
+U 7469 ; WX 651 ; N uni1D2D ; G 1304
+U 7470 ; WX 532 ; N uni1D2E ; G 1305
+U 7471 ; WX 532 ; N uni1D2F ; G 1306
+U 7472 ; WX 546 ; N uni1D30 ; G 1307
+U 7473 ; WX 480 ; N uni1D31 ; G 1308
+U 7474 ; WX 480 ; N uni1D32 ; G 1309
+U 7475 ; WX 538 ; N uni1D33 ; G 1310
+U 7476 ; WX 595 ; N uni1D34 ; G 1311
+U 7477 ; WX 294 ; N uni1D35 ; G 1312
+U 7478 ; WX 298 ; N uni1D36 ; G 1313
+U 7479 ; WX 547 ; N uni1D37 ; G 1314
+U 7480 ; WX 443 ; N uni1D38 ; G 1315
+U 7481 ; WX 697 ; N uni1D39 ; G 1316
+U 7482 ; WX 576 ; N uni1D3A ; G 1317
+U 7483 ; WX 606 ; N uni1D3B ; G 1318
+U 7484 ; WX 548 ; N uni1D3C ; G 1319
+U 7485 ; WX 442 ; N uni1D3D ; G 1320
+U 7486 ; WX 474 ; N uni1D3E ; G 1321
+U 7487 ; WX 523 ; N uni1D3F ; G 1322
+U 7488 ; WX 469 ; N uni1D40 ; G 1323
+U 7489 ; WX 549 ; N uni1D41 ; G 1324
+U 7490 ; WX 708 ; N uni1D42 ; G 1325
+U 7491 ; WX 408 ; N uni1D43 ; G 1326
+U 7492 ; WX 408 ; N uni1D44 ; G 1327
+U 7493 ; WX 484 ; N uni1D45 ; G 1328
+U 7494 ; WX 587 ; N uni1D46 ; G 1329
+U 7495 ; WX 499 ; N uni1D47 ; G 1330
+U 7496 ; WX 498 ; N uni1D48 ; G 1331
+U 7497 ; WX 444 ; N uni1D49 ; G 1332
+U 7498 ; WX 444 ; N uni1D4A ; G 1333
+U 7499 ; WX 412 ; N uni1D4B ; G 1334
+U 7500 ; WX 412 ; N uni1D4C ; G 1335
+U 7501 ; WX 498 ; N uni1D4D ; G 1336
+U 7502 ; WX 300 ; N uni1D4E ; G 1337
+U 7503 ; WX 523 ; N uni1D4F ; G 1338
+U 7504 ; WX 729 ; N uni1D50 ; G 1339
+U 7505 ; WX 473 ; N uni1D51 ; G 1340
+U 7506 ; WX 467 ; N uni1D52 ; G 1341
+U 7507 ; WX 427 ; N uni1D53 ; G 1342
+U 7508 ; WX 467 ; N uni1D54 ; G 1343
+U 7509 ; WX 467 ; N uni1D55 ; G 1344
+U 7510 ; WX 499 ; N uni1D56 ; G 1345
+U 7511 ; WX 371 ; N uni1D57 ; G 1346
+U 7512 ; WX 520 ; N uni1D58 ; G 1347
+U 7513 ; WX 418 ; N uni1D59 ; G 1348
+U 7514 ; WX 729 ; N uni1D5A ; G 1349
+U 7515 ; WX 491 ; N uni1D5B ; G 1350
+U 7516 ; WX 505 ; N uni1D5C ; G 1351
+U 7517 ; WX 418 ; N uni1D5D ; G 1352
+U 7518 ; WX 416 ; N uni1D5E ; G 1353
+U 7519 ; WX 420 ; N uni1D5F ; G 1354
+U 7520 ; WX 570 ; N uni1D60 ; G 1355
+U 7521 ; WX 414 ; N uni1D61 ; G 1356
+U 7522 ; WX 239 ; N uni1D62 ; G 1357
+U 7523 ; WX 414 ; N uni1D63 ; G 1358
+U 7524 ; WX 520 ; N uni1D64 ; G 1359
+U 7525 ; WX 491 ; N uni1D65 ; G 1360
+U 7526 ; WX 418 ; N uni1D66 ; G 1361
+U 7527 ; WX 416 ; N uni1D67 ; G 1362
+U 7528 ; WX 419 ; N uni1D68 ; G 1363
+U 7529 ; WX 570 ; N uni1D69 ; G 1364
+U 7530 ; WX 414 ; N uni1D6A ; G 1365
+U 7531 ; WX 1042 ; N uni1D6B ; G 1366
+U 7543 ; WX 640 ; N uni1D77 ; G 1367
+U 7544 ; WX 595 ; N uni1D78 ; G 1368
+U 7547 ; WX 380 ; N uni1D7B ; G 1369
+U 7548 ; WX 380 ; N uni1D7C ; G 1370
+U 7549 ; WX 699 ; N uni1D7D ; G 1371
+U 7550 ; WX 647 ; N uni1D7E ; G 1372
+U 7551 ; WX 679 ; N uni1D7F ; G 1373
+U 7557 ; WX 380 ; N uni1D85 ; G 1374
+U 7579 ; WX 484 ; N uni1D9B ; G 1375
+U 7580 ; WX 427 ; N uni1D9C ; G 1376
+U 7581 ; WX 427 ; N uni1D9D ; G 1377
+U 7582 ; WX 467 ; N uni1D9E ; G 1378
+U 7583 ; WX 412 ; N uni1D9F ; G 1379
+U 7584 ; WX 271 ; N uni1DA0 ; G 1380
+U 7585 ; WX 373 ; N uni1DA1 ; G 1381
+U 7586 ; WX 498 ; N uni1DA2 ; G 1382
+U 7587 ; WX 522 ; N uni1DA3 ; G 1383
+U 7588 ; WX 300 ; N uni1DA4 ; G 1384
+U 7589 ; WX 307 ; N uni1DA5 ; G 1385
+U 7590 ; WX 300 ; N uni1DA6 ; G 1386
+U 7591 ; WX 300 ; N uni1DA7 ; G 1387
+U 7592 ; WX 370 ; N uni1DA8 ; G 1388
+U 7593 ; WX 368 ; N uni1DA9 ; G 1389
+U 7594 ; WX 321 ; N uni1DAA ; G 1390
+U 7595 ; WX 430 ; N uni1DAB ; G 1391
+U 7596 ; WX 682 ; N uni1DAC ; G 1392
+U 7597 ; WX 729 ; N uni1DAD ; G 1393
+U 7598 ; WX 588 ; N uni1DAE ; G 1394
+U 7599 ; WX 587 ; N uni1DAF ; G 1395
+U 7600 ; WX 472 ; N uni1DB0 ; G 1396
+U 7601 ; WX 467 ; N uni1DB1 ; G 1397
+U 7602 ; WX 522 ; N uni1DB2 ; G 1398
+U 7603 ; WX 400 ; N uni1DB3 ; G 1399
+U 7604 ; WX 387 ; N uni1DB4 ; G 1400
+U 7605 ; WX 371 ; N uni1DB5 ; G 1401
+U 7606 ; WX 520 ; N uni1DB6 ; G 1402
+U 7607 ; WX 475 ; N uni1DB7 ; G 1403
+U 7608 ; WX 408 ; N uni1DB8 ; G 1404
+U 7609 ; WX 489 ; N uni1DB9 ; G 1405
+U 7610 ; WX 366 ; N uni1DBA ; G 1406
+U 7611 ; WX 357 ; N uni1DBB ; G 1407
+U 7612 ; WX 527 ; N uni1DBC ; G 1408
+U 7613 ; WX 412 ; N uni1DBD ; G 1409
+U 7614 ; WX 452 ; N uni1DBE ; G 1410
+U 7615 ; WX 467 ; N uni1DBF ; G 1411
+U 7620 ; WX 0 ; N uni1DC4 ; G 1412
+U 7621 ; WX 0 ; N uni1DC5 ; G 1413
+U 7622 ; WX 0 ; N uni1DC6 ; G 1414
+U 7623 ; WX 0 ; N uni1DC7 ; G 1415
+U 7624 ; WX 0 ; N uni1DC8 ; G 1416
+U 7625 ; WX 0 ; N uni1DC9 ; G 1417
+U 7680 ; WX 776 ; N uni1E00 ; G 1418
+U 7681 ; WX 648 ; N uni1E01 ; G 1419
+U 7682 ; WX 845 ; N uni1E02 ; G 1420
+U 7683 ; WX 699 ; N uni1E03 ; G 1421
+U 7684 ; WX 845 ; N uni1E04 ; G 1422
+U 7685 ; WX 699 ; N uni1E05 ; G 1423
+U 7686 ; WX 845 ; N uni1E06 ; G 1424
+U 7687 ; WX 699 ; N uni1E07 ; G 1425
+U 7688 ; WX 796 ; N uni1E08 ; G 1426
+U 7689 ; WX 609 ; N uni1E09 ; G 1427
+U 7690 ; WX 867 ; N uni1E0A ; G 1428
+U 7691 ; WX 699 ; N uni1E0B ; G 1429
+U 7692 ; WX 867 ; N uni1E0C ; G 1430
+U 7693 ; WX 699 ; N uni1E0D ; G 1431
+U 7694 ; WX 867 ; N uni1E0E ; G 1432
+U 7695 ; WX 699 ; N uni1E0F ; G 1433
+U 7696 ; WX 867 ; N uni1E10 ; G 1434
+U 7697 ; WX 699 ; N uni1E11 ; G 1435
+U 7698 ; WX 867 ; N uni1E12 ; G 1436
+U 7699 ; WX 699 ; N uni1E13 ; G 1437
+U 7700 ; WX 762 ; N uni1E14 ; G 1438
+U 7701 ; WX 636 ; N uni1E15 ; G 1439
+U 7702 ; WX 762 ; N uni1E16 ; G 1440
+U 7703 ; WX 636 ; N uni1E17 ; G 1441
+U 7704 ; WX 762 ; N uni1E18 ; G 1442
+U 7705 ; WX 636 ; N uni1E19 ; G 1443
+U 7706 ; WX 762 ; N uni1E1A ; G 1444
+U 7707 ; WX 636 ; N uni1E1B ; G 1445
+U 7708 ; WX 762 ; N uni1E1C ; G 1446
+U 7709 ; WX 636 ; N uni1E1D ; G 1447
+U 7710 ; WX 710 ; N uni1E1E ; G 1448
+U 7711 ; WX 430 ; N uni1E1F ; G 1449
+U 7712 ; WX 854 ; N uni1E20 ; G 1450
+U 7713 ; WX 699 ; N uni1E21 ; G 1451
+U 7714 ; WX 945 ; N uni1E22 ; G 1452
+U 7715 ; WX 727 ; N uni1E23 ; G 1453
+U 7716 ; WX 945 ; N uni1E24 ; G 1454
+U 7717 ; WX 727 ; N uni1E25 ; G 1455
+U 7718 ; WX 945 ; N uni1E26 ; G 1456
+U 7719 ; WX 727 ; N uni1E27 ; G 1457
+U 7720 ; WX 945 ; N uni1E28 ; G 1458
+U 7721 ; WX 727 ; N uni1E29 ; G 1459
+U 7722 ; WX 945 ; N uni1E2A ; G 1460
+U 7723 ; WX 727 ; N uni1E2B ; G 1461
+U 7724 ; WX 468 ; N uni1E2C ; G 1462
+U 7725 ; WX 380 ; N uni1E2D ; G 1463
+U 7726 ; WX 468 ; N uni1E2E ; G 1464
+U 7727 ; WX 380 ; N uni1E2F ; G 1465
+U 7728 ; WX 869 ; N uni1E30 ; G 1466
+U 7729 ; WX 693 ; N uni1E31 ; G 1467
+U 7730 ; WX 869 ; N uni1E32 ; G 1468
+U 7731 ; WX 693 ; N uni1E33 ; G 1469
+U 7732 ; WX 869 ; N uni1E34 ; G 1470
+U 7733 ; WX 693 ; N uni1E35 ; G 1471
+U 7734 ; WX 703 ; N uni1E36 ; G 1472
+U 7735 ; WX 380 ; N uni1E37 ; G 1473
+U 7736 ; WX 703 ; N uni1E38 ; G 1474
+U 7737 ; WX 380 ; N uni1E39 ; G 1475
+U 7738 ; WX 703 ; N uni1E3A ; G 1476
+U 7739 ; WX 380 ; N uni1E3B ; G 1477
+U 7740 ; WX 703 ; N uni1E3C ; G 1478
+U 7741 ; WX 380 ; N uni1E3D ; G 1479
+U 7742 ; WX 1107 ; N uni1E3E ; G 1480
+U 7743 ; WX 1058 ; N uni1E3F ; G 1481
+U 7744 ; WX 1107 ; N uni1E40 ; G 1482
+U 7745 ; WX 1058 ; N uni1E41 ; G 1483
+U 7746 ; WX 1107 ; N uni1E42 ; G 1484
+U 7747 ; WX 1058 ; N uni1E43 ; G 1485
+U 7748 ; WX 914 ; N uni1E44 ; G 1486
+U 7749 ; WX 727 ; N uni1E45 ; G 1487
+U 7750 ; WX 914 ; N uni1E46 ; G 1488
+U 7751 ; WX 727 ; N uni1E47 ; G 1489
+U 7752 ; WX 914 ; N uni1E48 ; G 1490
+U 7753 ; WX 727 ; N uni1E49 ; G 1491
+U 7754 ; WX 914 ; N uni1E4A ; G 1492
+U 7755 ; WX 727 ; N uni1E4B ; G 1493
+U 7756 ; WX 871 ; N uni1E4C ; G 1494
+U 7757 ; WX 667 ; N uni1E4D ; G 1495
+U 7758 ; WX 871 ; N uni1E4E ; G 1496
+U 7759 ; WX 667 ; N uni1E4F ; G 1497
+U 7760 ; WX 871 ; N uni1E50 ; G 1498
+U 7761 ; WX 667 ; N uni1E51 ; G 1499
+U 7762 ; WX 871 ; N uni1E52 ; G 1500
+U 7763 ; WX 667 ; N uni1E53 ; G 1501
+U 7764 ; WX 752 ; N uni1E54 ; G 1502
+U 7765 ; WX 699 ; N uni1E55 ; G 1503
+U 7766 ; WX 752 ; N uni1E56 ; G 1504
+U 7767 ; WX 699 ; N uni1E57 ; G 1505
+U 7768 ; WX 831 ; N uni1E58 ; G 1506
+U 7769 ; WX 527 ; N uni1E59 ; G 1507
+U 7770 ; WX 831 ; N uni1E5A ; G 1508
+U 7771 ; WX 527 ; N uni1E5B ; G 1509
+U 7772 ; WX 831 ; N uni1E5C ; G 1510
+U 7773 ; WX 527 ; N uni1E5D ; G 1511
+U 7774 ; WX 831 ; N uni1E5E ; G 1512
+U 7775 ; WX 527 ; N uni1E5F ; G 1513
+U 7776 ; WX 722 ; N uni1E60 ; G 1514
+U 7777 ; WX 563 ; N uni1E61 ; G 1515
+U 7778 ; WX 722 ; N uni1E62 ; G 1516
+U 7779 ; WX 563 ; N uni1E63 ; G 1517
+U 7780 ; WX 722 ; N uni1E64 ; G 1518
+U 7781 ; WX 563 ; N uni1E65 ; G 1519
+U 7782 ; WX 722 ; N uni1E66 ; G 1520
+U 7783 ; WX 563 ; N uni1E67 ; G 1521
+U 7784 ; WX 722 ; N uni1E68 ; G 1522
+U 7785 ; WX 563 ; N uni1E69 ; G 1523
+U 7786 ; WX 744 ; N uni1E6A ; G 1524
+U 7787 ; WX 462 ; N uni1E6B ; G 1525
+U 7788 ; WX 744 ; N uni1E6C ; G 1526
+U 7789 ; WX 462 ; N uni1E6D ; G 1527
+U 7790 ; WX 744 ; N uni1E6E ; G 1528
+U 7791 ; WX 462 ; N uni1E6F ; G 1529
+U 7792 ; WX 744 ; N uni1E70 ; G 1530
+U 7793 ; WX 462 ; N uni1E71 ; G 1531
+U 7794 ; WX 872 ; N uni1E72 ; G 1532
+U 7795 ; WX 727 ; N uni1E73 ; G 1533
+U 7796 ; WX 872 ; N uni1E74 ; G 1534
+U 7797 ; WX 727 ; N uni1E75 ; G 1535
+U 7798 ; WX 872 ; N uni1E76 ; G 1536
+U 7799 ; WX 727 ; N uni1E77 ; G 1537
+U 7800 ; WX 872 ; N uni1E78 ; G 1538
+U 7801 ; WX 727 ; N uni1E79 ; G 1539
+U 7802 ; WX 872 ; N uni1E7A ; G 1540
+U 7803 ; WX 727 ; N uni1E7B ; G 1541
+U 7804 ; WX 776 ; N uni1E7C ; G 1542
+U 7805 ; WX 581 ; N uni1E7D ; G 1543
+U 7806 ; WX 776 ; N uni1E7E ; G 1544
+U 7807 ; WX 581 ; N uni1E7F ; G 1545
+U 7808 ; WX 1123 ; N Wgrave ; G 1546
+U 7809 ; WX 861 ; N wgrave ; G 1547
+U 7810 ; WX 1123 ; N Wacute ; G 1548
+U 7811 ; WX 861 ; N wacute ; G 1549
+U 7812 ; WX 1123 ; N Wdieresis ; G 1550
+U 7813 ; WX 861 ; N wdieresis ; G 1551
+U 7814 ; WX 1123 ; N uni1E86 ; G 1552
+U 7815 ; WX 861 ; N uni1E87 ; G 1553
+U 7816 ; WX 1123 ; N uni1E88 ; G 1554
+U 7817 ; WX 861 ; N uni1E89 ; G 1555
+U 7818 ; WX 776 ; N uni1E8A ; G 1556
+U 7819 ; WX 596 ; N uni1E8B ; G 1557
+U 7820 ; WX 776 ; N uni1E8C ; G 1558
+U 7821 ; WX 596 ; N uni1E8D ; G 1559
+U 7822 ; WX 714 ; N uni1E8E ; G 1560
+U 7823 ; WX 581 ; N uni1E8F ; G 1561
+U 7824 ; WX 730 ; N uni1E90 ; G 1562
+U 7825 ; WX 568 ; N uni1E91 ; G 1563
+U 7826 ; WX 730 ; N uni1E92 ; G 1564
+U 7827 ; WX 568 ; N uni1E93 ; G 1565
+U 7828 ; WX 730 ; N uni1E94 ; G 1566
+U 7829 ; WX 568 ; N uni1E95 ; G 1567
+U 7830 ; WX 727 ; N uni1E96 ; G 1568
+U 7831 ; WX 462 ; N uni1E97 ; G 1569
+U 7832 ; WX 861 ; N uni1E98 ; G 1570
+U 7833 ; WX 581 ; N uni1E99 ; G 1571
+U 7834 ; WX 1014 ; N uni1E9A ; G 1572
+U 7835 ; WX 430 ; N uni1E9B ; G 1573
+U 7836 ; WX 430 ; N uni1E9C ; G 1574
+U 7837 ; WX 430 ; N uni1E9D ; G 1575
+U 7838 ; WX 947 ; N uni1E9E ; G 1576
+U 7839 ; WX 667 ; N uni1E9F ; G 1577
+U 7840 ; WX 776 ; N uni1EA0 ; G 1578
+U 7841 ; WX 648 ; N uni1EA1 ; G 1579
+U 7842 ; WX 776 ; N uni1EA2 ; G 1580
+U 7843 ; WX 648 ; N uni1EA3 ; G 1581
+U 7844 ; WX 776 ; N uni1EA4 ; G 1582
+U 7845 ; WX 648 ; N uni1EA5 ; G 1583
+U 7846 ; WX 776 ; N uni1EA6 ; G 1584
+U 7847 ; WX 648 ; N uni1EA7 ; G 1585
+U 7848 ; WX 776 ; N uni1EA8 ; G 1586
+U 7849 ; WX 648 ; N uni1EA9 ; G 1587
+U 7850 ; WX 776 ; N uni1EAA ; G 1588
+U 7851 ; WX 648 ; N uni1EAB ; G 1589
+U 7852 ; WX 776 ; N uni1EAC ; G 1590
+U 7853 ; WX 648 ; N uni1EAD ; G 1591
+U 7854 ; WX 776 ; N uni1EAE ; G 1592
+U 7855 ; WX 648 ; N uni1EAF ; G 1593
+U 7856 ; WX 776 ; N uni1EB0 ; G 1594
+U 7857 ; WX 648 ; N uni1EB1 ; G 1595
+U 7858 ; WX 776 ; N uni1EB2 ; G 1596
+U 7859 ; WX 648 ; N uni1EB3 ; G 1597
+U 7860 ; WX 776 ; N uni1EB4 ; G 1598
+U 7861 ; WX 648 ; N uni1EB5 ; G 1599
+U 7862 ; WX 776 ; N uni1EB6 ; G 1600
+U 7863 ; WX 648 ; N uni1EB7 ; G 1601
+U 7864 ; WX 762 ; N uni1EB8 ; G 1602
+U 7865 ; WX 636 ; N uni1EB9 ; G 1603
+U 7866 ; WX 762 ; N uni1EBA ; G 1604
+U 7867 ; WX 636 ; N uni1EBB ; G 1605
+U 7868 ; WX 762 ; N uni1EBC ; G 1606
+U 7869 ; WX 636 ; N uni1EBD ; G 1607
+U 7870 ; WX 762 ; N uni1EBE ; G 1608
+U 7871 ; WX 636 ; N uni1EBF ; G 1609
+U 7872 ; WX 762 ; N uni1EC0 ; G 1610
+U 7873 ; WX 636 ; N uni1EC1 ; G 1611
+U 7874 ; WX 762 ; N uni1EC2 ; G 1612
+U 7875 ; WX 636 ; N uni1EC3 ; G 1613
+U 7876 ; WX 762 ; N uni1EC4 ; G 1614
+U 7877 ; WX 636 ; N uni1EC5 ; G 1615
+U 7878 ; WX 762 ; N uni1EC6 ; G 1616
+U 7879 ; WX 636 ; N uni1EC7 ; G 1617
+U 7880 ; WX 468 ; N uni1EC8 ; G 1618
+U 7881 ; WX 380 ; N uni1EC9 ; G 1619
+U 7882 ; WX 468 ; N uni1ECA ; G 1620
+U 7883 ; WX 380 ; N uni1ECB ; G 1621
+U 7884 ; WX 871 ; N uni1ECC ; G 1622
+U 7885 ; WX 667 ; N uni1ECD ; G 1623
+U 7886 ; WX 871 ; N uni1ECE ; G 1624
+U 7887 ; WX 667 ; N uni1ECF ; G 1625
+U 7888 ; WX 871 ; N uni1ED0 ; G 1626
+U 7889 ; WX 667 ; N uni1ED1 ; G 1627
+U 7890 ; WX 871 ; N uni1ED2 ; G 1628
+U 7891 ; WX 667 ; N uni1ED3 ; G 1629
+U 7892 ; WX 871 ; N uni1ED4 ; G 1630
+U 7893 ; WX 667 ; N uni1ED5 ; G 1631
+U 7894 ; WX 871 ; N uni1ED6 ; G 1632
+U 7895 ; WX 667 ; N uni1ED7 ; G 1633
+U 7896 ; WX 871 ; N uni1ED8 ; G 1634
+U 7897 ; WX 667 ; N uni1ED9 ; G 1635
+U 7898 ; WX 871 ; N uni1EDA ; G 1636
+U 7899 ; WX 667 ; N uni1EDB ; G 1637
+U 7900 ; WX 871 ; N uni1EDC ; G 1638
+U 7901 ; WX 667 ; N uni1EDD ; G 1639
+U 7902 ; WX 871 ; N uni1EDE ; G 1640
+U 7903 ; WX 667 ; N uni1EDF ; G 1641
+U 7904 ; WX 871 ; N uni1EE0 ; G 1642
+U 7905 ; WX 667 ; N uni1EE1 ; G 1643
+U 7906 ; WX 871 ; N uni1EE2 ; G 1644
+U 7907 ; WX 667 ; N uni1EE3 ; G 1645
+U 7908 ; WX 872 ; N uni1EE4 ; G 1646
+U 7909 ; WX 727 ; N uni1EE5 ; G 1647
+U 7910 ; WX 872 ; N uni1EE6 ; G 1648
+U 7911 ; WX 727 ; N uni1EE7 ; G 1649
+U 7912 ; WX 872 ; N uni1EE8 ; G 1650
+U 7913 ; WX 727 ; N uni1EE9 ; G 1651
+U 7914 ; WX 872 ; N uni1EEA ; G 1652
+U 7915 ; WX 727 ; N uni1EEB ; G 1653
+U 7916 ; WX 872 ; N uni1EEC ; G 1654
+U 7917 ; WX 727 ; N uni1EED ; G 1655
+U 7918 ; WX 872 ; N uni1EEE ; G 1656
+U 7919 ; WX 727 ; N uni1EEF ; G 1657
+U 7920 ; WX 872 ; N uni1EF0 ; G 1658
+U 7921 ; WX 727 ; N uni1EF1 ; G 1659
+U 7922 ; WX 714 ; N Ygrave ; G 1660
+U 7923 ; WX 581 ; N ygrave ; G 1661
+U 7924 ; WX 714 ; N uni1EF4 ; G 1662
+U 7925 ; WX 581 ; N uni1EF5 ; G 1663
+U 7926 ; WX 714 ; N uni1EF6 ; G 1664
+U 7927 ; WX 581 ; N uni1EF7 ; G 1665
+U 7928 ; WX 714 ; N uni1EF8 ; G 1666
+U 7929 ; WX 581 ; N uni1EF9 ; G 1667
+U 7930 ; WX 1078 ; N uni1EFA ; G 1668
+U 7931 ; WX 701 ; N uni1EFB ; G 1669
+U 7936 ; WX 770 ; N uni1F00 ; G 1670
+U 7937 ; WX 770 ; N uni1F01 ; G 1671
+U 7938 ; WX 770 ; N uni1F02 ; G 1672
+U 7939 ; WX 770 ; N uni1F03 ; G 1673
+U 7940 ; WX 770 ; N uni1F04 ; G 1674
+U 7941 ; WX 770 ; N uni1F05 ; G 1675
+U 7942 ; WX 770 ; N uni1F06 ; G 1676
+U 7943 ; WX 770 ; N uni1F07 ; G 1677
+U 7944 ; WX 776 ; N uni1F08 ; G 1678
+U 7945 ; WX 776 ; N uni1F09 ; G 1679
+U 7946 ; WX 978 ; N uni1F0A ; G 1680
+U 7947 ; WX 978 ; N uni1F0B ; G 1681
+U 7948 ; WX 832 ; N uni1F0C ; G 1682
+U 7949 ; WX 849 ; N uni1F0D ; G 1683
+U 7950 ; WX 776 ; N uni1F0E ; G 1684
+U 7951 ; WX 776 ; N uni1F0F ; G 1685
+U 7952 ; WX 608 ; N uni1F10 ; G 1686
+U 7953 ; WX 608 ; N uni1F11 ; G 1687
+U 7954 ; WX 608 ; N uni1F12 ; G 1688
+U 7955 ; WX 608 ; N uni1F13 ; G 1689
+U 7956 ; WX 608 ; N uni1F14 ; G 1690
+U 7957 ; WX 608 ; N uni1F15 ; G 1691
+U 7960 ; WX 917 ; N uni1F18 ; G 1692
+U 7961 ; WX 909 ; N uni1F19 ; G 1693
+U 7962 ; WX 1169 ; N uni1F1A ; G 1694
+U 7963 ; WX 1169 ; N uni1F1B ; G 1695
+U 7964 ; WX 1093 ; N uni1F1C ; G 1696
+U 7965 ; WX 1120 ; N uni1F1D ; G 1697
+U 7968 ; WX 727 ; N uni1F20 ; G 1698
+U 7969 ; WX 727 ; N uni1F21 ; G 1699
+U 7970 ; WX 727 ; N uni1F22 ; G 1700
+U 7971 ; WX 727 ; N uni1F23 ; G 1701
+U 7972 ; WX 727 ; N uni1F24 ; G 1702
+U 7973 ; WX 727 ; N uni1F25 ; G 1703
+U 7974 ; WX 727 ; N uni1F26 ; G 1704
+U 7975 ; WX 727 ; N uni1F27 ; G 1705
+U 7976 ; WX 1100 ; N uni1F28 ; G 1706
+U 7977 ; WX 1094 ; N uni1F29 ; G 1707
+U 7978 ; WX 1358 ; N uni1F2A ; G 1708
+U 7979 ; WX 1361 ; N uni1F2B ; G 1709
+U 7980 ; WX 1279 ; N uni1F2C ; G 1710
+U 7981 ; WX 1308 ; N uni1F2D ; G 1711
+U 7982 ; WX 1197 ; N uni1F2E ; G 1712
+U 7983 ; WX 1194 ; N uni1F2F ; G 1713
+U 7984 ; WX 484 ; N uni1F30 ; G 1714
+U 7985 ; WX 484 ; N uni1F31 ; G 1715
+U 7986 ; WX 484 ; N uni1F32 ; G 1716
+U 7987 ; WX 484 ; N uni1F33 ; G 1717
+U 7988 ; WX 484 ; N uni1F34 ; G 1718
+U 7989 ; WX 484 ; N uni1F35 ; G 1719
+U 7990 ; WX 484 ; N uni1F36 ; G 1720
+U 7991 ; WX 484 ; N uni1F37 ; G 1721
+U 7992 ; WX 629 ; N uni1F38 ; G 1722
+U 7993 ; WX 617 ; N uni1F39 ; G 1723
+U 7994 ; WX 878 ; N uni1F3A ; G 1724
+U 7995 ; WX 881 ; N uni1F3B ; G 1725
+U 7996 ; WX 799 ; N uni1F3C ; G 1726
+U 7997 ; WX 831 ; N uni1F3D ; G 1727
+U 7998 ; WX 723 ; N uni1F3E ; G 1728
+U 7999 ; WX 714 ; N uni1F3F ; G 1729
+U 8000 ; WX 667 ; N uni1F40 ; G 1730
+U 8001 ; WX 667 ; N uni1F41 ; G 1731
+U 8002 ; WX 667 ; N uni1F42 ; G 1732
+U 8003 ; WX 667 ; N uni1F43 ; G 1733
+U 8004 ; WX 667 ; N uni1F44 ; G 1734
+U 8005 ; WX 667 ; N uni1F45 ; G 1735
+U 8008 ; WX 900 ; N uni1F48 ; G 1736
+U 8009 ; WX 935 ; N uni1F49 ; G 1737
+U 8010 ; WX 1240 ; N uni1F4A ; G 1738
+U 8011 ; WX 1237 ; N uni1F4B ; G 1739
+U 8012 ; WX 1035 ; N uni1F4C ; G 1740
+U 8013 ; WX 1066 ; N uni1F4D ; G 1741
+U 8016 ; WX 694 ; N uni1F50 ; G 1742
+U 8017 ; WX 694 ; N uni1F51 ; G 1743
+U 8018 ; WX 694 ; N uni1F52 ; G 1744
+U 8019 ; WX 694 ; N uni1F53 ; G 1745
+U 8020 ; WX 694 ; N uni1F54 ; G 1746
+U 8021 ; WX 694 ; N uni1F55 ; G 1747
+U 8022 ; WX 694 ; N uni1F56 ; G 1748
+U 8023 ; WX 694 ; N uni1F57 ; G 1749
+U 8025 ; WX 922 ; N uni1F59 ; G 1750
+U 8027 ; WX 1186 ; N uni1F5B ; G 1751
+U 8029 ; WX 1133 ; N uni1F5D ; G 1752
+U 8031 ; WX 1019 ; N uni1F5F ; G 1753
+U 8032 ; WX 952 ; N uni1F60 ; G 1754
+U 8033 ; WX 952 ; N uni1F61 ; G 1755
+U 8034 ; WX 952 ; N uni1F62 ; G 1756
+U 8035 ; WX 952 ; N uni1F63 ; G 1757
+U 8036 ; WX 952 ; N uni1F64 ; G 1758
+U 8037 ; WX 952 ; N uni1F65 ; G 1759
+U 8038 ; WX 952 ; N uni1F66 ; G 1760
+U 8039 ; WX 952 ; N uni1F67 ; G 1761
+U 8040 ; WX 931 ; N uni1F68 ; G 1762
+U 8041 ; WX 963 ; N uni1F69 ; G 1763
+U 8042 ; WX 1268 ; N uni1F6A ; G 1764
+U 8043 ; WX 1274 ; N uni1F6B ; G 1765
+U 8044 ; WX 1054 ; N uni1F6C ; G 1766
+U 8045 ; WX 1088 ; N uni1F6D ; G 1767
+U 8046 ; WX 1023 ; N uni1F6E ; G 1768
+U 8047 ; WX 1060 ; N uni1F6F ; G 1769
+U 8048 ; WX 770 ; N uni1F70 ; G 1770
+U 8049 ; WX 770 ; N uni1F71 ; G 1771
+U 8050 ; WX 608 ; N uni1F72 ; G 1772
+U 8051 ; WX 608 ; N uni1F73 ; G 1773
+U 8052 ; WX 727 ; N uni1F74 ; G 1774
+U 8053 ; WX 727 ; N uni1F75 ; G 1775
+U 8054 ; WX 484 ; N uni1F76 ; G 1776
+U 8055 ; WX 484 ; N uni1F77 ; G 1777
+U 8056 ; WX 667 ; N uni1F78 ; G 1778
+U 8057 ; WX 667 ; N uni1F79 ; G 1779
+U 8058 ; WX 694 ; N uni1F7A ; G 1780
+U 8059 ; WX 694 ; N uni1F7B ; G 1781
+U 8060 ; WX 952 ; N uni1F7C ; G 1782
+U 8061 ; WX 952 ; N uni1F7D ; G 1783
+U 8064 ; WX 770 ; N uni1F80 ; G 1784
+U 8065 ; WX 770 ; N uni1F81 ; G 1785
+U 8066 ; WX 770 ; N uni1F82 ; G 1786
+U 8067 ; WX 770 ; N uni1F83 ; G 1787
+U 8068 ; WX 770 ; N uni1F84 ; G 1788
+U 8069 ; WX 770 ; N uni1F85 ; G 1789
+U 8070 ; WX 770 ; N uni1F86 ; G 1790
+U 8071 ; WX 770 ; N uni1F87 ; G 1791
+U 8072 ; WX 776 ; N uni1F88 ; G 1792
+U 8073 ; WX 776 ; N uni1F89 ; G 1793
+U 8074 ; WX 978 ; N uni1F8A ; G 1794
+U 8075 ; WX 978 ; N uni1F8B ; G 1795
+U 8076 ; WX 832 ; N uni1F8C ; G 1796
+U 8077 ; WX 849 ; N uni1F8D ; G 1797
+U 8078 ; WX 776 ; N uni1F8E ; G 1798
+U 8079 ; WX 776 ; N uni1F8F ; G 1799
+U 8080 ; WX 727 ; N uni1F90 ; G 1800
+U 8081 ; WX 727 ; N uni1F91 ; G 1801
+U 8082 ; WX 727 ; N uni1F92 ; G 1802
+U 8083 ; WX 727 ; N uni1F93 ; G 1803
+U 8084 ; WX 727 ; N uni1F94 ; G 1804
+U 8085 ; WX 727 ; N uni1F95 ; G 1805
+U 8086 ; WX 727 ; N uni1F96 ; G 1806
+U 8087 ; WX 727 ; N uni1F97 ; G 1807
+U 8088 ; WX 1100 ; N uni1F98 ; G 1808
+U 8089 ; WX 1094 ; N uni1F99 ; G 1809
+U 8090 ; WX 1358 ; N uni1F9A ; G 1810
+U 8091 ; WX 1361 ; N uni1F9B ; G 1811
+U 8092 ; WX 1279 ; N uni1F9C ; G 1812
+U 8093 ; WX 1308 ; N uni1F9D ; G 1813
+U 8094 ; WX 1197 ; N uni1F9E ; G 1814
+U 8095 ; WX 1194 ; N uni1F9F ; G 1815
+U 8096 ; WX 952 ; N uni1FA0 ; G 1816
+U 8097 ; WX 952 ; N uni1FA1 ; G 1817
+U 8098 ; WX 952 ; N uni1FA2 ; G 1818
+U 8099 ; WX 952 ; N uni1FA3 ; G 1819
+U 8100 ; WX 952 ; N uni1FA4 ; G 1820
+U 8101 ; WX 952 ; N uni1FA5 ; G 1821
+U 8102 ; WX 952 ; N uni1FA6 ; G 1822
+U 8103 ; WX 952 ; N uni1FA7 ; G 1823
+U 8104 ; WX 931 ; N uni1FA8 ; G 1824
+U 8105 ; WX 963 ; N uni1FA9 ; G 1825
+U 8106 ; WX 1268 ; N uni1FAA ; G 1826
+U 8107 ; WX 1274 ; N uni1FAB ; G 1827
+U 8108 ; WX 1054 ; N uni1FAC ; G 1828
+U 8109 ; WX 1088 ; N uni1FAD ; G 1829
+U 8110 ; WX 1023 ; N uni1FAE ; G 1830
+U 8111 ; WX 1060 ; N uni1FAF ; G 1831
+U 8112 ; WX 770 ; N uni1FB0 ; G 1832
+U 8113 ; WX 770 ; N uni1FB1 ; G 1833
+U 8114 ; WX 770 ; N uni1FB2 ; G 1834
+U 8115 ; WX 770 ; N uni1FB3 ; G 1835
+U 8116 ; WX 770 ; N uni1FB4 ; G 1836
+U 8118 ; WX 770 ; N uni1FB6 ; G 1837
+U 8119 ; WX 770 ; N uni1FB7 ; G 1838
+U 8120 ; WX 776 ; N uni1FB8 ; G 1839
+U 8121 ; WX 776 ; N uni1FB9 ; G 1840
+U 8122 ; WX 811 ; N uni1FBA ; G 1841
+U 8123 ; WX 776 ; N uni1FBB ; G 1842
+U 8124 ; WX 776 ; N uni1FBC ; G 1843
+U 8125 ; WX 500 ; N uni1FBD ; G 1844
+U 8126 ; WX 500 ; N uni1FBE ; G 1845
+U 8127 ; WX 500 ; N uni1FBF ; G 1846
+U 8128 ; WX 500 ; N uni1FC0 ; G 1847
+U 8129 ; WX 500 ; N uni1FC1 ; G 1848
+U 8130 ; WX 727 ; N uni1FC2 ; G 1849
+U 8131 ; WX 727 ; N uni1FC3 ; G 1850
+U 8132 ; WX 727 ; N uni1FC4 ; G 1851
+U 8134 ; WX 727 ; N uni1FC6 ; G 1852
+U 8135 ; WX 727 ; N uni1FC7 ; G 1853
+U 8136 ; WX 1000 ; N uni1FC8 ; G 1854
+U 8137 ; WX 947 ; N uni1FC9 ; G 1855
+U 8138 ; WX 1191 ; N uni1FCA ; G 1856
+U 8139 ; WX 1118 ; N uni1FCB ; G 1857
+U 8140 ; WX 945 ; N uni1FCC ; G 1858
+U 8141 ; WX 500 ; N uni1FCD ; G 1859
+U 8142 ; WX 500 ; N uni1FCE ; G 1860
+U 8143 ; WX 500 ; N uni1FCF ; G 1861
+U 8144 ; WX 484 ; N uni1FD0 ; G 1862
+U 8145 ; WX 484 ; N uni1FD1 ; G 1863
+U 8146 ; WX 484 ; N uni1FD2 ; G 1864
+U 8147 ; WX 484 ; N uni1FD3 ; G 1865
+U 8150 ; WX 484 ; N uni1FD6 ; G 1866
+U 8151 ; WX 484 ; N uni1FD7 ; G 1867
+U 8152 ; WX 468 ; N uni1FD8 ; G 1868
+U 8153 ; WX 468 ; N uni1FD9 ; G 1869
+U 8154 ; WX 714 ; N uni1FDA ; G 1870
+U 8155 ; WX 662 ; N uni1FDB ; G 1871
+U 8157 ; WX 500 ; N uni1FDD ; G 1872
+U 8158 ; WX 500 ; N uni1FDE ; G 1873
+U 8159 ; WX 500 ; N uni1FDF ; G 1874
+U 8160 ; WX 694 ; N uni1FE0 ; G 1875
+U 8161 ; WX 694 ; N uni1FE1 ; G 1876
+U 8162 ; WX 694 ; N uni1FE2 ; G 1877
+U 8163 ; WX 694 ; N uni1FE3 ; G 1878
+U 8164 ; WX 665 ; N uni1FE4 ; G 1879
+U 8165 ; WX 665 ; N uni1FE5 ; G 1880
+U 8166 ; WX 694 ; N uni1FE6 ; G 1881
+U 8167 ; WX 694 ; N uni1FE7 ; G 1882
+U 8168 ; WX 714 ; N uni1FE8 ; G 1883
+U 8169 ; WX 714 ; N uni1FE9 ; G 1884
+U 8170 ; WX 1019 ; N uni1FEA ; G 1885
+U 8171 ; WX 953 ; N uni1FEB ; G 1886
+U 8172 ; WX 910 ; N uni1FEC ; G 1887
+U 8173 ; WX 500 ; N uni1FED ; G 1888
+U 8174 ; WX 500 ; N uni1FEE ; G 1889
+U 8175 ; WX 500 ; N uni1FEF ; G 1890
+U 8178 ; WX 952 ; N uni1FF2 ; G 1891
+U 8179 ; WX 952 ; N uni1FF3 ; G 1892
+U 8180 ; WX 952 ; N uni1FF4 ; G 1893
+U 8182 ; WX 952 ; N uni1FF6 ; G 1894
+U 8183 ; WX 952 ; N uni1FF7 ; G 1895
+U 8184 ; WX 1069 ; N uni1FF8 ; G 1896
+U 8185 ; WX 887 ; N uni1FF9 ; G 1897
+U 8186 ; WX 1101 ; N uni1FFA ; G 1898
+U 8187 ; WX 911 ; N uni1FFB ; G 1899
+U 8188 ; WX 890 ; N uni1FFC ; G 1900
+U 8189 ; WX 500 ; N uni1FFD ; G 1901
+U 8190 ; WX 500 ; N uni1FFE ; G 1902
+U 8192 ; WX 500 ; N uni2000 ; G 1903
+U 8193 ; WX 1000 ; N uni2001 ; G 1904
+U 8194 ; WX 500 ; N uni2002 ; G 1905
+U 8195 ; WX 1000 ; N uni2003 ; G 1906
+U 8196 ; WX 330 ; N uni2004 ; G 1907
+U 8197 ; WX 250 ; N uni2005 ; G 1908
+U 8198 ; WX 167 ; N uni2006 ; G 1909
+U 8199 ; WX 696 ; N uni2007 ; G 1910
+U 8200 ; WX 348 ; N uni2008 ; G 1911
+U 8201 ; WX 200 ; N uni2009 ; G 1912
+U 8202 ; WX 100 ; N uni200A ; G 1913
+U 8203 ; WX 0 ; N uni200B ; G 1914
+U 8204 ; WX 0 ; N uni200C ; G 1915
+U 8205 ; WX 0 ; N uni200D ; G 1916
+U 8206 ; WX 0 ; N uni200E ; G 1917
+U 8207 ; WX 0 ; N uni200F ; G 1918
+U 8208 ; WX 415 ; N uni2010 ; G 1919
+U 8209 ; WX 415 ; N uni2011 ; G 1920
+U 8210 ; WX 696 ; N figuredash ; G 1921
+U 8211 ; WX 500 ; N endash ; G 1922
+U 8212 ; WX 1000 ; N emdash ; G 1923
+U 8213 ; WX 1000 ; N uni2015 ; G 1924
+U 8214 ; WX 500 ; N uni2016 ; G 1925
+U 8215 ; WX 500 ; N underscoredbl ; G 1926
+U 8216 ; WX 348 ; N quoteleft ; G 1927
+U 8217 ; WX 348 ; N quoteright ; G 1928
+U 8218 ; WX 348 ; N quotesinglbase ; G 1929
+U 8219 ; WX 348 ; N quotereversed ; G 1930
+U 8220 ; WX 575 ; N quotedblleft ; G 1931
+U 8221 ; WX 575 ; N quotedblright ; G 1932
+U 8222 ; WX 575 ; N quotedblbase ; G 1933
+U 8223 ; WX 575 ; N uni201F ; G 1934
+U 8224 ; WX 523 ; N dagger ; G 1935
+U 8225 ; WX 523 ; N daggerdbl ; G 1936
+U 8226 ; WX 639 ; N bullet ; G 1937
+U 8227 ; WX 639 ; N uni2023 ; G 1938
+U 8228 ; WX 348 ; N onedotenleader ; G 1939
+U 8229 ; WX 674 ; N twodotenleader ; G 1940
+U 8230 ; WX 1000 ; N ellipsis ; G 1941
+U 8234 ; WX 0 ; N uni202A ; G 1942
+U 8235 ; WX 0 ; N uni202B ; G 1943
+U 8236 ; WX 0 ; N uni202C ; G 1944
+U 8237 ; WX 0 ; N uni202D ; G 1945
+U 8238 ; WX 0 ; N uni202E ; G 1946
+U 8239 ; WX 200 ; N uni202F ; G 1947
+U 8240 ; WX 1385 ; N perthousand ; G 1948
+U 8241 ; WX 1813 ; N uni2031 ; G 1949
+U 8242 ; WX 264 ; N minute ; G 1950
+U 8243 ; WX 447 ; N second ; G 1951
+U 8244 ; WX 630 ; N uni2034 ; G 1952
+U 8245 ; WX 264 ; N uni2035 ; G 1953
+U 8246 ; WX 447 ; N uni2036 ; G 1954
+U 8247 ; WX 630 ; N uni2037 ; G 1955
+U 8248 ; WX 733 ; N uni2038 ; G 1956
+U 8249 ; WX 400 ; N guilsinglleft ; G 1957
+U 8250 ; WX 400 ; N guilsinglright ; G 1958
+U 8252 ; WX 629 ; N exclamdbl ; G 1959
+U 8253 ; WX 586 ; N uni203D ; G 1960
+U 8254 ; WX 500 ; N uni203E ; G 1961
+U 8258 ; WX 1023 ; N uni2042 ; G 1962
+U 8260 ; WX 167 ; N fraction ; G 1963
+U 8261 ; WX 473 ; N uni2045 ; G 1964
+U 8262 ; WX 473 ; N uni2046 ; G 1965
+U 8263 ; WX 1082 ; N uni2047 ; G 1966
+U 8264 ; WX 856 ; N uni2048 ; G 1967
+U 8265 ; WX 856 ; N uni2049 ; G 1968
+U 8267 ; WX 636 ; N uni204B ; G 1969
+U 8268 ; WX 500 ; N uni204C ; G 1970
+U 8269 ; WX 500 ; N uni204D ; G 1971
+U 8270 ; WX 523 ; N uni204E ; G 1972
+U 8271 ; WX 369 ; N uni204F ; G 1973
+U 8273 ; WX 523 ; N uni2051 ; G 1974
+U 8274 ; WX 556 ; N uni2052 ; G 1975
+U 8275 ; WX 1000 ; N uni2053 ; G 1976
+U 8279 ; WX 813 ; N uni2057 ; G 1977
+U 8287 ; WX 222 ; N uni205F ; G 1978
+U 8288 ; WX 0 ; N uni2060 ; G 1979
+U 8289 ; WX 0 ; N uni2061 ; G 1980
+U 8290 ; WX 0 ; N uni2062 ; G 1981
+U 8291 ; WX 0 ; N uni2063 ; G 1982
+U 8292 ; WX 0 ; N uni2064 ; G 1983
+U 8298 ; WX 0 ; N uni206A ; G 1984
+U 8299 ; WX 0 ; N uni206B ; G 1985
+U 8300 ; WX 0 ; N uni206C ; G 1986
+U 8301 ; WX 0 ; N uni206D ; G 1987
+U 8302 ; WX 0 ; N uni206E ; G 1988
+U 8303 ; WX 0 ; N uni206F ; G 1989
+U 8304 ; WX 438 ; N uni2070 ; G 1990
+U 8305 ; WX 239 ; N uni2071 ; G 1991
+U 8308 ; WX 438 ; N uni2074 ; G 1992
+U 8309 ; WX 438 ; N uni2075 ; G 1993
+U 8310 ; WX 438 ; N uni2076 ; G 1994
+U 8311 ; WX 438 ; N uni2077 ; G 1995
+U 8312 ; WX 438 ; N uni2078 ; G 1996
+U 8313 ; WX 438 ; N uni2079 ; G 1997
+U 8314 ; WX 528 ; N uni207A ; G 1998
+U 8315 ; WX 528 ; N uni207B ; G 1999
+U 8316 ; WX 528 ; N uni207C ; G 2000
+U 8317 ; WX 298 ; N uni207D ; G 2001
+U 8318 ; WX 298 ; N uni207E ; G 2002
+U 8319 ; WX 458 ; N uni207F ; G 2003
+U 8320 ; WX 438 ; N uni2080 ; G 2004
+U 8321 ; WX 438 ; N uni2081 ; G 2005
+U 8322 ; WX 438 ; N uni2082 ; G 2006
+U 8323 ; WX 438 ; N uni2083 ; G 2007
+U 8324 ; WX 438 ; N uni2084 ; G 2008
+U 8325 ; WX 438 ; N uni2085 ; G 2009
+U 8326 ; WX 438 ; N uni2086 ; G 2010
+U 8327 ; WX 438 ; N uni2087 ; G 2011
+U 8328 ; WX 438 ; N uni2088 ; G 2012
+U 8329 ; WX 438 ; N uni2089 ; G 2013
+U 8330 ; WX 528 ; N uni208A ; G 2014
+U 8331 ; WX 528 ; N uni208B ; G 2015
+U 8332 ; WX 528 ; N uni208C ; G 2016
+U 8333 ; WX 298 ; N uni208D ; G 2017
+U 8334 ; WX 298 ; N uni208E ; G 2018
+U 8336 ; WX 408 ; N uni2090 ; G 2019
+U 8337 ; WX 444 ; N uni2091 ; G 2020
+U 8338 ; WX 467 ; N uni2092 ; G 2021
+U 8339 ; WX 375 ; N uni2093 ; G 2022
+U 8340 ; WX 444 ; N uni2094 ; G 2023
+U 8341 ; WX 521 ; N uni2095 ; G 2024
+U 8342 ; WX 523 ; N uni2096 ; G 2025
+U 8343 ; WX 292 ; N uni2097 ; G 2026
+U 8344 ; WX 729 ; N uni2098 ; G 2027
+U 8345 ; WX 458 ; N uni2099 ; G 2028
+U 8346 ; WX 499 ; N uni209A ; G 2029
+U 8347 ; WX 395 ; N uni209B ; G 2030
+U 8348 ; WX 371 ; N uni209C ; G 2031
+U 8358 ; WX 696 ; N uni20A6 ; G 2032
+U 8364 ; WX 696 ; N Euro ; G 2033
+U 8367 ; WX 1155 ; N uni20AF ; G 2034
+U 8369 ; WX 790 ; N uni20B1 ; G 2035
+U 8372 ; WX 876 ; N uni20B4 ; G 2036
+U 8373 ; WX 696 ; N uni20B5 ; G 2037
+U 8376 ; WX 696 ; N uni20B8 ; G 2038
+U 8377 ; WX 696 ; N uni20B9 ; G 2039
+U 8378 ; WX 696 ; N uni20BA ; G 2040
+U 8381 ; WX 696 ; N uni20BD ; G 2041
+U 8451 ; WX 1198 ; N uni2103 ; G 2042
+U 8457 ; WX 1112 ; N uni2109 ; G 2043
+U 8462 ; WX 727 ; N uni210E ; G 2044
+U 8463 ; WX 727 ; N uni210F ; G 2045
+U 8470 ; WX 1087 ; N uni2116 ; G 2046
+U 8482 ; WX 1000 ; N trademark ; G 2047
+U 8486 ; WX 890 ; N uni2126 ; G 2048
+U 8487 ; WX 890 ; N uni2127 ; G 2049
+U 8490 ; WX 869 ; N uni212A ; G 2050
+U 8491 ; WX 776 ; N uni212B ; G 2051
+U 8498 ; WX 710 ; N uni2132 ; G 2052
+U 8513 ; WX 786 ; N uni2141 ; G 2053
+U 8514 ; WX 576 ; N uni2142 ; G 2054
+U 8515 ; WX 637 ; N uni2143 ; G 2055
+U 8516 ; WX 760 ; N uni2144 ; G 2056
+U 8523 ; WX 903 ; N uni214B ; G 2057
+U 8526 ; WX 592 ; N uni214E ; G 2058
+U 8528 ; WX 1035 ; N uni2150 ; G 2059
+U 8529 ; WX 1035 ; N uni2151 ; G 2060
+U 8530 ; WX 1473 ; N uni2152 ; G 2061
+U 8531 ; WX 1035 ; N onethird ; G 2062
+U 8532 ; WX 1035 ; N twothirds ; G 2063
+U 8533 ; WX 1035 ; N uni2155 ; G 2064
+U 8534 ; WX 1035 ; N uni2156 ; G 2065
+U 8535 ; WX 1035 ; N uni2157 ; G 2066
+U 8536 ; WX 1035 ; N uni2158 ; G 2067
+U 8537 ; WX 1035 ; N uni2159 ; G 2068
+U 8538 ; WX 1035 ; N uni215A ; G 2069
+U 8539 ; WX 1035 ; N oneeighth ; G 2070
+U 8540 ; WX 1035 ; N threeeighths ; G 2071
+U 8541 ; WX 1035 ; N fiveeighths ; G 2072
+U 8542 ; WX 1035 ; N seveneighths ; G 2073
+U 8543 ; WX 615 ; N uni215F ; G 2074
+U 8544 ; WX 468 ; N uni2160 ; G 2075
+U 8545 ; WX 843 ; N uni2161 ; G 2076
+U 8546 ; WX 1218 ; N uni2162 ; G 2077
+U 8547 ; WX 1135 ; N uni2163 ; G 2078
+U 8548 ; WX 776 ; N uni2164 ; G 2079
+U 8549 ; WX 1150 ; N uni2165 ; G 2080
+U 8550 ; WX 1525 ; N uni2166 ; G 2081
+U 8551 ; WX 1900 ; N uni2167 ; G 2082
+U 8552 ; WX 1126 ; N uni2168 ; G 2083
+U 8553 ; WX 776 ; N uni2169 ; G 2084
+U 8554 ; WX 1127 ; N uni216A ; G 2085
+U 8555 ; WX 1502 ; N uni216B ; G 2086
+U 8556 ; WX 703 ; N uni216C ; G 2087
+U 8557 ; WX 796 ; N uni216D ; G 2088
+U 8558 ; WX 867 ; N uni216E ; G 2089
+U 8559 ; WX 1107 ; N uni216F ; G 2090
+U 8560 ; WX 380 ; N uni2170 ; G 2091
+U 8561 ; WX 760 ; N uni2171 ; G 2092
+U 8562 ; WX 1140 ; N uni2172 ; G 2093
+U 8563 ; WX 961 ; N uni2173 ; G 2094
+U 8564 ; WX 581 ; N uni2174 ; G 2095
+U 8565 ; WX 961 ; N uni2175 ; G 2096
+U 8566 ; WX 1341 ; N uni2176 ; G 2097
+U 8567 ; WX 1721 ; N uni2177 ; G 2098
+U 8568 ; WX 976 ; N uni2178 ; G 2099
+U 8569 ; WX 596 ; N uni2179 ; G 2100
+U 8570 ; WX 976 ; N uni217A ; G 2101
+U 8571 ; WX 1356 ; N uni217B ; G 2102
+U 8572 ; WX 380 ; N uni217C ; G 2103
+U 8573 ; WX 609 ; N uni217D ; G 2104
+U 8574 ; WX 699 ; N uni217E ; G 2105
+U 8575 ; WX 1058 ; N uni217F ; G 2106
+U 8576 ; WX 1255 ; N uni2180 ; G 2107
+U 8577 ; WX 867 ; N uni2181 ; G 2108
+U 8578 ; WX 1268 ; N uni2182 ; G 2109
+U 8579 ; WX 796 ; N uni2183 ; G 2110
+U 8580 ; WX 609 ; N uni2184 ; G 2111
+U 8581 ; WX 796 ; N uni2185 ; G 2112
+U 8585 ; WX 1035 ; N uni2189 ; G 2113
+U 8592 ; WX 838 ; N arrowleft ; G 2114
+U 8593 ; WX 838 ; N arrowup ; G 2115
+U 8594 ; WX 838 ; N arrowright ; G 2116
+U 8595 ; WX 838 ; N arrowdown ; G 2117
+U 8596 ; WX 838 ; N arrowboth ; G 2118
+U 8597 ; WX 838 ; N arrowupdn ; G 2119
+U 8598 ; WX 838 ; N uni2196 ; G 2120
+U 8599 ; WX 838 ; N uni2197 ; G 2121
+U 8600 ; WX 838 ; N uni2198 ; G 2122
+U 8601 ; WX 838 ; N uni2199 ; G 2123
+U 8602 ; WX 838 ; N uni219A ; G 2124
+U 8603 ; WX 838 ; N uni219B ; G 2125
+U 8604 ; WX 838 ; N uni219C ; G 2126
+U 8605 ; WX 838 ; N uni219D ; G 2127
+U 8606 ; WX 838 ; N uni219E ; G 2128
+U 8607 ; WX 838 ; N uni219F ; G 2129
+U 8608 ; WX 838 ; N uni21A0 ; G 2130
+U 8609 ; WX 838 ; N uni21A1 ; G 2131
+U 8610 ; WX 838 ; N uni21A2 ; G 2132
+U 8611 ; WX 838 ; N uni21A3 ; G 2133
+U 8612 ; WX 838 ; N uni21A4 ; G 2134
+U 8613 ; WX 838 ; N uni21A5 ; G 2135
+U 8614 ; WX 838 ; N uni21A6 ; G 2136
+U 8615 ; WX 838 ; N uni21A7 ; G 2137
+U 8616 ; WX 838 ; N arrowupdnbse ; G 2138
+U 8617 ; WX 838 ; N uni21A9 ; G 2139
+U 8618 ; WX 838 ; N uni21AA ; G 2140
+U 8619 ; WX 838 ; N uni21AB ; G 2141
+U 8620 ; WX 838 ; N uni21AC ; G 2142
+U 8621 ; WX 838 ; N uni21AD ; G 2143
+U 8622 ; WX 838 ; N uni21AE ; G 2144
+U 8623 ; WX 850 ; N uni21AF ; G 2145
+U 8624 ; WX 838 ; N uni21B0 ; G 2146
+U 8625 ; WX 838 ; N uni21B1 ; G 2147
+U 8626 ; WX 838 ; N uni21B2 ; G 2148
+U 8627 ; WX 838 ; N uni21B3 ; G 2149
+U 8628 ; WX 838 ; N uni21B4 ; G 2150
+U 8629 ; WX 838 ; N carriagereturn ; G 2151
+U 8630 ; WX 838 ; N uni21B6 ; G 2152
+U 8631 ; WX 838 ; N uni21B7 ; G 2153
+U 8632 ; WX 838 ; N uni21B8 ; G 2154
+U 8633 ; WX 838 ; N uni21B9 ; G 2155
+U 8634 ; WX 838 ; N uni21BA ; G 2156
+U 8635 ; WX 838 ; N uni21BB ; G 2157
+U 8636 ; WX 838 ; N uni21BC ; G 2158
+U 8637 ; WX 838 ; N uni21BD ; G 2159
+U 8638 ; WX 838 ; N uni21BE ; G 2160
+U 8639 ; WX 838 ; N uni21BF ; G 2161
+U 8640 ; WX 838 ; N uni21C0 ; G 2162
+U 8641 ; WX 838 ; N uni21C1 ; G 2163
+U 8642 ; WX 838 ; N uni21C2 ; G 2164
+U 8643 ; WX 838 ; N uni21C3 ; G 2165
+U 8644 ; WX 838 ; N uni21C4 ; G 2166
+U 8645 ; WX 838 ; N uni21C5 ; G 2167
+U 8646 ; WX 838 ; N uni21C6 ; G 2168
+U 8647 ; WX 838 ; N uni21C7 ; G 2169
+U 8648 ; WX 838 ; N uni21C8 ; G 2170
+U 8649 ; WX 838 ; N uni21C9 ; G 2171
+U 8650 ; WX 838 ; N uni21CA ; G 2172
+U 8651 ; WX 838 ; N uni21CB ; G 2173
+U 8652 ; WX 838 ; N uni21CC ; G 2174
+U 8653 ; WX 838 ; N uni21CD ; G 2175
+U 8654 ; WX 838 ; N uni21CE ; G 2176
+U 8655 ; WX 838 ; N uni21CF ; G 2177
+U 8656 ; WX 838 ; N arrowdblleft ; G 2178
+U 8657 ; WX 838 ; N arrowdblup ; G 2179
+U 8658 ; WX 838 ; N arrowdblright ; G 2180
+U 8659 ; WX 838 ; N arrowdbldown ; G 2181
+U 8660 ; WX 838 ; N arrowdblboth ; G 2182
+U 8661 ; WX 838 ; N uni21D5 ; G 2183
+U 8662 ; WX 838 ; N uni21D6 ; G 2184
+U 8663 ; WX 838 ; N uni21D7 ; G 2185
+U 8664 ; WX 838 ; N uni21D8 ; G 2186
+U 8665 ; WX 838 ; N uni21D9 ; G 2187
+U 8666 ; WX 838 ; N uni21DA ; G 2188
+U 8667 ; WX 838 ; N uni21DB ; G 2189
+U 8668 ; WX 838 ; N uni21DC ; G 2190
+U 8669 ; WX 838 ; N uni21DD ; G 2191
+U 8670 ; WX 838 ; N uni21DE ; G 2192
+U 8671 ; WX 838 ; N uni21DF ; G 2193
+U 8672 ; WX 838 ; N uni21E0 ; G 2194
+U 8673 ; WX 838 ; N uni21E1 ; G 2195
+U 8674 ; WX 838 ; N uni21E2 ; G 2196
+U 8675 ; WX 838 ; N uni21E3 ; G 2197
+U 8676 ; WX 838 ; N uni21E4 ; G 2198
+U 8677 ; WX 838 ; N uni21E5 ; G 2199
+U 8678 ; WX 838 ; N uni21E6 ; G 2200
+U 8679 ; WX 838 ; N uni21E7 ; G 2201
+U 8680 ; WX 838 ; N uni21E8 ; G 2202
+U 8681 ; WX 838 ; N uni21E9 ; G 2203
+U 8682 ; WX 838 ; N uni21EA ; G 2204
+U 8683 ; WX 838 ; N uni21EB ; G 2205
+U 8684 ; WX 838 ; N uni21EC ; G 2206
+U 8685 ; WX 838 ; N uni21ED ; G 2207
+U 8686 ; WX 838 ; N uni21EE ; G 2208
+U 8687 ; WX 838 ; N uni21EF ; G 2209
+U 8688 ; WX 838 ; N uni21F0 ; G 2210
+U 8689 ; WX 838 ; N uni21F1 ; G 2211
+U 8690 ; WX 838 ; N uni21F2 ; G 2212
+U 8691 ; WX 838 ; N uni21F3 ; G 2213
+U 8692 ; WX 838 ; N uni21F4 ; G 2214
+U 8693 ; WX 838 ; N uni21F5 ; G 2215
+U 8694 ; WX 838 ; N uni21F6 ; G 2216
+U 8695 ; WX 838 ; N uni21F7 ; G 2217
+U 8696 ; WX 838 ; N uni21F8 ; G 2218
+U 8697 ; WX 838 ; N uni21F9 ; G 2219
+U 8698 ; WX 838 ; N uni21FA ; G 2220
+U 8699 ; WX 838 ; N uni21FB ; G 2221
+U 8700 ; WX 838 ; N uni21FC ; G 2222
+U 8701 ; WX 838 ; N uni21FD ; G 2223
+U 8702 ; WX 838 ; N uni21FE ; G 2224
+U 8703 ; WX 838 ; N uni21FF ; G 2225
+U 8704 ; WX 641 ; N universal ; G 2226
+U 8706 ; WX 534 ; N partialdiff ; G 2227
+U 8707 ; WX 620 ; N existential ; G 2228
+U 8708 ; WX 620 ; N uni2204 ; G 2229
+U 8710 ; WX 753 ; N increment ; G 2230
+U 8711 ; WX 753 ; N gradient ; G 2231
+U 8712 ; WX 740 ; N element ; G 2232
+U 8713 ; WX 740 ; N notelement ; G 2233
+U 8715 ; WX 740 ; N suchthat ; G 2234
+U 8716 ; WX 740 ; N uni220C ; G 2235
+U 8719 ; WX 842 ; N product ; G 2236
+U 8720 ; WX 842 ; N uni2210 ; G 2237
+U 8721 ; WX 753 ; N summation ; G 2238
+U 8722 ; WX 838 ; N minus ; G 2239
+U 8723 ; WX 838 ; N uni2213 ; G 2240
+U 8724 ; WX 838 ; N uni2214 ; G 2241
+U 8725 ; WX 365 ; N uni2215 ; G 2242
+U 8727 ; WX 691 ; N asteriskmath ; G 2243
+U 8728 ; WX 519 ; N uni2218 ; G 2244
+U 8729 ; WX 519 ; N uni2219 ; G 2245
+U 8730 ; WX 657 ; N radical ; G 2246
+U 8731 ; WX 657 ; N uni221B ; G 2247
+U 8732 ; WX 657 ; N uni221C ; G 2248
+U 8733 ; WX 672 ; N proportional ; G 2249
+U 8734 ; WX 833 ; N infinity ; G 2250
+U 8735 ; WX 838 ; N orthogonal ; G 2251
+U 8736 ; WX 838 ; N angle ; G 2252
+U 8739 ; WX 324 ; N uni2223 ; G 2253
+U 8740 ; WX 607 ; N uni2224 ; G 2254
+U 8741 ; WX 529 ; N uni2225 ; G 2255
+U 8742 ; WX 773 ; N uni2226 ; G 2256
+U 8743 ; WX 812 ; N logicaland ; G 2257
+U 8744 ; WX 812 ; N logicalor ; G 2258
+U 8745 ; WX 838 ; N intersection ; G 2259
+U 8746 ; WX 838 ; N union ; G 2260
+U 8747 ; WX 579 ; N integral ; G 2261
+U 8748 ; WX 1000 ; N uni222C ; G 2262
+U 8749 ; WX 1391 ; N uni222D ; G 2263
+U 8760 ; WX 838 ; N uni2238 ; G 2264
+U 8761 ; WX 838 ; N uni2239 ; G 2265
+U 8762 ; WX 838 ; N uni223A ; G 2266
+U 8763 ; WX 838 ; N uni223B ; G 2267
+U 8764 ; WX 838 ; N similar ; G 2268
+U 8765 ; WX 838 ; N uni223D ; G 2269
+U 8770 ; WX 838 ; N uni2242 ; G 2270
+U 8771 ; WX 838 ; N uni2243 ; G 2271
+U 8776 ; WX 838 ; N approxequal ; G 2272
+U 8784 ; WX 838 ; N uni2250 ; G 2273
+U 8785 ; WX 838 ; N uni2251 ; G 2274
+U 8786 ; WX 838 ; N uni2252 ; G 2275
+U 8787 ; WX 838 ; N uni2253 ; G 2276
+U 8788 ; WX 1082 ; N uni2254 ; G 2277
+U 8789 ; WX 1082 ; N uni2255 ; G 2278
+U 8800 ; WX 838 ; N notequal ; G 2279
+U 8801 ; WX 838 ; N equivalence ; G 2280
+U 8804 ; WX 838 ; N lessequal ; G 2281
+U 8805 ; WX 838 ; N greaterequal ; G 2282
+U 8834 ; WX 838 ; N propersubset ; G 2283
+U 8835 ; WX 838 ; N propersuperset ; G 2284
+U 8836 ; WX 838 ; N notsubset ; G 2285
+U 8837 ; WX 838 ; N uni2285 ; G 2286
+U 8838 ; WX 838 ; N reflexsubset ; G 2287
+U 8839 ; WX 838 ; N reflexsuperset ; G 2288
+U 8844 ; WX 838 ; N uni228C ; G 2289
+U 8845 ; WX 838 ; N uni228D ; G 2290
+U 8846 ; WX 838 ; N uni228E ; G 2291
+U 8847 ; WX 838 ; N uni228F ; G 2292
+U 8848 ; WX 838 ; N uni2290 ; G 2293
+U 8849 ; WX 838 ; N uni2291 ; G 2294
+U 8850 ; WX 838 ; N uni2292 ; G 2295
+U 8851 ; WX 838 ; N uni2293 ; G 2296
+U 8852 ; WX 838 ; N uni2294 ; G 2297
+U 8853 ; WX 838 ; N circleplus ; G 2298
+U 8854 ; WX 838 ; N uni2296 ; G 2299
+U 8855 ; WX 838 ; N circlemultiply ; G 2300
+U 8856 ; WX 838 ; N uni2298 ; G 2301
+U 8857 ; WX 838 ; N uni2299 ; G 2302
+U 8858 ; WX 838 ; N uni229A ; G 2303
+U 8859 ; WX 838 ; N uni229B ; G 2304
+U 8860 ; WX 838 ; N uni229C ; G 2305
+U 8861 ; WX 838 ; N uni229D ; G 2306
+U 8862 ; WX 838 ; N uni229E ; G 2307
+U 8863 ; WX 838 ; N uni229F ; G 2308
+U 8864 ; WX 838 ; N uni22A0 ; G 2309
+U 8865 ; WX 838 ; N uni22A1 ; G 2310
+U 8866 ; WX 884 ; N uni22A2 ; G 2311
+U 8867 ; WX 884 ; N uni22A3 ; G 2312
+U 8868 ; WX 960 ; N uni22A4 ; G 2313
+U 8869 ; WX 960 ; N perpendicular ; G 2314
+U 8870 ; WX 616 ; N uni22A6 ; G 2315
+U 8871 ; WX 616 ; N uni22A7 ; G 2316
+U 8872 ; WX 884 ; N uni22A8 ; G 2317
+U 8873 ; WX 884 ; N uni22A9 ; G 2318
+U 8874 ; WX 884 ; N uni22AA ; G 2319
+U 8875 ; WX 1080 ; N uni22AB ; G 2320
+U 8876 ; WX 884 ; N uni22AC ; G 2321
+U 8877 ; WX 884 ; N uni22AD ; G 2322
+U 8878 ; WX 884 ; N uni22AE ; G 2323
+U 8879 ; WX 1080 ; N uni22AF ; G 2324
+U 8900 ; WX 626 ; N uni22C4 ; G 2325
+U 8901 ; WX 398 ; N dotmath ; G 2326
+U 8962 ; WX 834 ; N house ; G 2327
+U 8968 ; WX 473 ; N uni2308 ; G 2328
+U 8969 ; WX 473 ; N uni2309 ; G 2329
+U 8970 ; WX 473 ; N uni230A ; G 2330
+U 8971 ; WX 473 ; N uni230B ; G 2331
+U 8976 ; WX 838 ; N revlogicalnot ; G 2332
+U 8977 ; WX 539 ; N uni2311 ; G 2333
+U 8984 ; WX 928 ; N uni2318 ; G 2334
+U 8985 ; WX 838 ; N uni2319 ; G 2335
+U 8992 ; WX 579 ; N integraltp ; G 2336
+U 8993 ; WX 579 ; N integralbt ; G 2337
+U 8997 ; WX 1000 ; N uni2325 ; G 2338
+U 9000 ; WX 1443 ; N uni2328 ; G 2339
+U 9085 ; WX 1008 ; N uni237D ; G 2340
+U 9115 ; WX 500 ; N uni239B ; G 2341
+U 9116 ; WX 500 ; N uni239C ; G 2342
+U 9117 ; WX 500 ; N uni239D ; G 2343
+U 9118 ; WX 500 ; N uni239E ; G 2344
+U 9119 ; WX 500 ; N uni239F ; G 2345
+U 9120 ; WX 500 ; N uni23A0 ; G 2346
+U 9121 ; WX 500 ; N uni23A1 ; G 2347
+U 9122 ; WX 500 ; N uni23A2 ; G 2348
+U 9123 ; WX 500 ; N uni23A3 ; G 2349
+U 9124 ; WX 500 ; N uni23A4 ; G 2350
+U 9125 ; WX 500 ; N uni23A5 ; G 2351
+U 9126 ; WX 500 ; N uni23A6 ; G 2352
+U 9127 ; WX 750 ; N uni23A7 ; G 2353
+U 9128 ; WX 750 ; N uni23A8 ; G 2354
+U 9129 ; WX 750 ; N uni23A9 ; G 2355
+U 9130 ; WX 750 ; N uni23AA ; G 2356
+U 9131 ; WX 750 ; N uni23AB ; G 2357
+U 9132 ; WX 750 ; N uni23AC ; G 2358
+U 9133 ; WX 750 ; N uni23AD ; G 2359
+U 9134 ; WX 579 ; N uni23AE ; G 2360
+U 9167 ; WX 945 ; N uni23CF ; G 2361
+U 9251 ; WX 834 ; N uni2423 ; G 2362
+U 9472 ; WX 602 ; N SF100000 ; G 2363
+U 9473 ; WX 602 ; N uni2501 ; G 2364
+U 9474 ; WX 602 ; N SF110000 ; G 2365
+U 9475 ; WX 602 ; N uni2503 ; G 2366
+U 9476 ; WX 602 ; N uni2504 ; G 2367
+U 9477 ; WX 602 ; N uni2505 ; G 2368
+U 9478 ; WX 602 ; N uni2506 ; G 2369
+U 9479 ; WX 602 ; N uni2507 ; G 2370
+U 9480 ; WX 602 ; N uni2508 ; G 2371
+U 9481 ; WX 602 ; N uni2509 ; G 2372
+U 9482 ; WX 602 ; N uni250A ; G 2373
+U 9483 ; WX 602 ; N uni250B ; G 2374
+U 9484 ; WX 602 ; N SF010000 ; G 2375
+U 9485 ; WX 602 ; N uni250D ; G 2376
+U 9486 ; WX 602 ; N uni250E ; G 2377
+U 9487 ; WX 602 ; N uni250F ; G 2378
+U 9488 ; WX 602 ; N SF030000 ; G 2379
+U 9489 ; WX 602 ; N uni2511 ; G 2380
+U 9490 ; WX 602 ; N uni2512 ; G 2381
+U 9491 ; WX 602 ; N uni2513 ; G 2382
+U 9492 ; WX 602 ; N SF020000 ; G 2383
+U 9493 ; WX 602 ; N uni2515 ; G 2384
+U 9494 ; WX 602 ; N uni2516 ; G 2385
+U 9495 ; WX 602 ; N uni2517 ; G 2386
+U 9496 ; WX 602 ; N SF040000 ; G 2387
+U 9497 ; WX 602 ; N uni2519 ; G 2388
+U 9498 ; WX 602 ; N uni251A ; G 2389
+U 9499 ; WX 602 ; N uni251B ; G 2390
+U 9500 ; WX 602 ; N SF080000 ; G 2391
+U 9501 ; WX 602 ; N uni251D ; G 2392
+U 9502 ; WX 602 ; N uni251E ; G 2393
+U 9503 ; WX 602 ; N uni251F ; G 2394
+U 9504 ; WX 602 ; N uni2520 ; G 2395
+U 9505 ; WX 602 ; N uni2521 ; G 2396
+U 9506 ; WX 602 ; N uni2522 ; G 2397
+U 9507 ; WX 602 ; N uni2523 ; G 2398
+U 9508 ; WX 602 ; N SF090000 ; G 2399
+U 9509 ; WX 602 ; N uni2525 ; G 2400
+U 9510 ; WX 602 ; N uni2526 ; G 2401
+U 9511 ; WX 602 ; N uni2527 ; G 2402
+U 9512 ; WX 602 ; N uni2528 ; G 2403
+U 9513 ; WX 602 ; N uni2529 ; G 2404
+U 9514 ; WX 602 ; N uni252A ; G 2405
+U 9515 ; WX 602 ; N uni252B ; G 2406
+U 9516 ; WX 602 ; N SF060000 ; G 2407
+U 9517 ; WX 602 ; N uni252D ; G 2408
+U 9518 ; WX 602 ; N uni252E ; G 2409
+U 9519 ; WX 602 ; N uni252F ; G 2410
+U 9520 ; WX 602 ; N uni2530 ; G 2411
+U 9521 ; WX 602 ; N uni2531 ; G 2412
+U 9522 ; WX 602 ; N uni2532 ; G 2413
+U 9523 ; WX 602 ; N uni2533 ; G 2414
+U 9524 ; WX 602 ; N SF070000 ; G 2415
+U 9525 ; WX 602 ; N uni2535 ; G 2416
+U 9526 ; WX 602 ; N uni2536 ; G 2417
+U 9527 ; WX 602 ; N uni2537 ; G 2418
+U 9528 ; WX 602 ; N uni2538 ; G 2419
+U 9529 ; WX 602 ; N uni2539 ; G 2420
+U 9530 ; WX 602 ; N uni253A ; G 2421
+U 9531 ; WX 602 ; N uni253B ; G 2422
+U 9532 ; WX 602 ; N SF050000 ; G 2423
+U 9533 ; WX 602 ; N uni253D ; G 2424
+U 9534 ; WX 602 ; N uni253E ; G 2425
+U 9535 ; WX 602 ; N uni253F ; G 2426
+U 9536 ; WX 602 ; N uni2540 ; G 2427
+U 9537 ; WX 602 ; N uni2541 ; G 2428
+U 9538 ; WX 602 ; N uni2542 ; G 2429
+U 9539 ; WX 602 ; N uni2543 ; G 2430
+U 9540 ; WX 602 ; N uni2544 ; G 2431
+U 9541 ; WX 602 ; N uni2545 ; G 2432
+U 9542 ; WX 602 ; N uni2546 ; G 2433
+U 9543 ; WX 602 ; N uni2547 ; G 2434
+U 9544 ; WX 602 ; N uni2548 ; G 2435
+U 9545 ; WX 602 ; N uni2549 ; G 2436
+U 9546 ; WX 602 ; N uni254A ; G 2437
+U 9547 ; WX 602 ; N uni254B ; G 2438
+U 9548 ; WX 602 ; N uni254C ; G 2439
+U 9549 ; WX 602 ; N uni254D ; G 2440
+U 9550 ; WX 602 ; N uni254E ; G 2441
+U 9551 ; WX 602 ; N uni254F ; G 2442
+U 9552 ; WX 602 ; N SF430000 ; G 2443
+U 9553 ; WX 602 ; N SF240000 ; G 2444
+U 9554 ; WX 602 ; N SF510000 ; G 2445
+U 9555 ; WX 602 ; N SF520000 ; G 2446
+U 9556 ; WX 602 ; N SF390000 ; G 2447
+U 9557 ; WX 602 ; N SF220000 ; G 2448
+U 9558 ; WX 602 ; N SF210000 ; G 2449
+U 9559 ; WX 602 ; N SF250000 ; G 2450
+U 9560 ; WX 602 ; N SF500000 ; G 2451
+U 9561 ; WX 602 ; N SF490000 ; G 2452
+U 9562 ; WX 602 ; N SF380000 ; G 2453
+U 9563 ; WX 602 ; N SF280000 ; G 2454
+U 9564 ; WX 602 ; N SF270000 ; G 2455
+U 9565 ; WX 602 ; N SF260000 ; G 2456
+U 9566 ; WX 602 ; N SF360000 ; G 2457
+U 9567 ; WX 602 ; N SF370000 ; G 2458
+U 9568 ; WX 602 ; N SF420000 ; G 2459
+U 9569 ; WX 602 ; N SF190000 ; G 2460
+U 9570 ; WX 602 ; N SF200000 ; G 2461
+U 9571 ; WX 602 ; N SF230000 ; G 2462
+U 9572 ; WX 602 ; N SF470000 ; G 2463
+U 9573 ; WX 602 ; N SF480000 ; G 2464
+U 9574 ; WX 602 ; N SF410000 ; G 2465
+U 9575 ; WX 602 ; N SF450000 ; G 2466
+U 9576 ; WX 602 ; N SF460000 ; G 2467
+U 9577 ; WX 602 ; N SF400000 ; G 2468
+U 9578 ; WX 602 ; N SF540000 ; G 2469
+U 9579 ; WX 602 ; N SF530000 ; G 2470
+U 9580 ; WX 602 ; N SF440000 ; G 2471
+U 9581 ; WX 602 ; N uni256D ; G 2472
+U 9582 ; WX 602 ; N uni256E ; G 2473
+U 9583 ; WX 602 ; N uni256F ; G 2474
+U 9584 ; WX 602 ; N uni2570 ; G 2475
+U 9585 ; WX 602 ; N uni2571 ; G 2476
+U 9586 ; WX 602 ; N uni2572 ; G 2477
+U 9587 ; WX 602 ; N uni2573 ; G 2478
+U 9588 ; WX 602 ; N uni2574 ; G 2479
+U 9589 ; WX 602 ; N uni2575 ; G 2480
+U 9590 ; WX 602 ; N uni2576 ; G 2481
+U 9591 ; WX 602 ; N uni2577 ; G 2482
+U 9592 ; WX 602 ; N uni2578 ; G 2483
+U 9593 ; WX 602 ; N uni2579 ; G 2484
+U 9594 ; WX 602 ; N uni257A ; G 2485
+U 9595 ; WX 602 ; N uni257B ; G 2486
+U 9596 ; WX 602 ; N uni257C ; G 2487
+U 9597 ; WX 602 ; N uni257D ; G 2488
+U 9598 ; WX 602 ; N uni257E ; G 2489
+U 9599 ; WX 602 ; N uni257F ; G 2490
+U 9600 ; WX 769 ; N upblock ; G 2491
+U 9601 ; WX 769 ; N uni2581 ; G 2492
+U 9602 ; WX 769 ; N uni2582 ; G 2493
+U 9603 ; WX 769 ; N uni2583 ; G 2494
+U 9604 ; WX 769 ; N dnblock ; G 2495
+U 9605 ; WX 769 ; N uni2585 ; G 2496
+U 9606 ; WX 769 ; N uni2586 ; G 2497
+U 9607 ; WX 769 ; N uni2587 ; G 2498
+U 9608 ; WX 769 ; N block ; G 2499
+U 9609 ; WX 769 ; N uni2589 ; G 2500
+U 9610 ; WX 769 ; N uni258A ; G 2501
+U 9611 ; WX 769 ; N uni258B ; G 2502
+U 9612 ; WX 769 ; N lfblock ; G 2503
+U 9613 ; WX 769 ; N uni258D ; G 2504
+U 9614 ; WX 769 ; N uni258E ; G 2505
+U 9615 ; WX 769 ; N uni258F ; G 2506
+U 9616 ; WX 769 ; N rtblock ; G 2507
+U 9617 ; WX 769 ; N ltshade ; G 2508
+U 9618 ; WX 769 ; N shade ; G 2509
+U 9619 ; WX 769 ; N dkshade ; G 2510
+U 9620 ; WX 769 ; N uni2594 ; G 2511
+U 9621 ; WX 769 ; N uni2595 ; G 2512
+U 9622 ; WX 769 ; N uni2596 ; G 2513
+U 9623 ; WX 769 ; N uni2597 ; G 2514
+U 9624 ; WX 769 ; N uni2598 ; G 2515
+U 9625 ; WX 769 ; N uni2599 ; G 2516
+U 9626 ; WX 769 ; N uni259A ; G 2517
+U 9627 ; WX 769 ; N uni259B ; G 2518
+U 9628 ; WX 769 ; N uni259C ; G 2519
+U 9629 ; WX 769 ; N uni259D ; G 2520
+U 9630 ; WX 769 ; N uni259E ; G 2521
+U 9631 ; WX 769 ; N uni259F ; G 2522
+U 9632 ; WX 945 ; N filledbox ; G 2523
+U 9633 ; WX 945 ; N H22073 ; G 2524
+U 9634 ; WX 945 ; N uni25A2 ; G 2525
+U 9635 ; WX 945 ; N uni25A3 ; G 2526
+U 9636 ; WX 945 ; N uni25A4 ; G 2527
+U 9637 ; WX 945 ; N uni25A5 ; G 2528
+U 9638 ; WX 945 ; N uni25A6 ; G 2529
+U 9639 ; WX 945 ; N uni25A7 ; G 2530
+U 9640 ; WX 945 ; N uni25A8 ; G 2531
+U 9641 ; WX 945 ; N uni25A9 ; G 2532
+U 9642 ; WX 678 ; N H18543 ; G 2533
+U 9643 ; WX 678 ; N H18551 ; G 2534
+U 9644 ; WX 945 ; N filledrect ; G 2535
+U 9645 ; WX 945 ; N uni25AD ; G 2536
+U 9646 ; WX 550 ; N uni25AE ; G 2537
+U 9647 ; WX 550 ; N uni25AF ; G 2538
+U 9648 ; WX 769 ; N uni25B0 ; G 2539
+U 9649 ; WX 769 ; N uni25B1 ; G 2540
+U 9650 ; WX 769 ; N triagup ; G 2541
+U 9651 ; WX 769 ; N uni25B3 ; G 2542
+U 9652 ; WX 502 ; N uni25B4 ; G 2543
+U 9653 ; WX 502 ; N uni25B5 ; G 2544
+U 9654 ; WX 769 ; N uni25B6 ; G 2545
+U 9655 ; WX 769 ; N uni25B7 ; G 2546
+U 9656 ; WX 502 ; N uni25B8 ; G 2547
+U 9657 ; WX 502 ; N uni25B9 ; G 2548
+U 9658 ; WX 769 ; N triagrt ; G 2549
+U 9659 ; WX 769 ; N uni25BB ; G 2550
+U 9660 ; WX 769 ; N triagdn ; G 2551
+U 9661 ; WX 769 ; N uni25BD ; G 2552
+U 9662 ; WX 502 ; N uni25BE ; G 2553
+U 9663 ; WX 502 ; N uni25BF ; G 2554
+U 9664 ; WX 769 ; N uni25C0 ; G 2555
+U 9665 ; WX 769 ; N uni25C1 ; G 2556
+U 9666 ; WX 502 ; N uni25C2 ; G 2557
+U 9667 ; WX 502 ; N uni25C3 ; G 2558
+U 9668 ; WX 769 ; N triaglf ; G 2559
+U 9669 ; WX 769 ; N uni25C5 ; G 2560
+U 9670 ; WX 769 ; N uni25C6 ; G 2561
+U 9671 ; WX 769 ; N uni25C7 ; G 2562
+U 9672 ; WX 769 ; N uni25C8 ; G 2563
+U 9673 ; WX 873 ; N uni25C9 ; G 2564
+U 9674 ; WX 494 ; N lozenge ; G 2565
+U 9675 ; WX 873 ; N circle ; G 2566
+U 9676 ; WX 873 ; N uni25CC ; G 2567
+U 9677 ; WX 873 ; N uni25CD ; G 2568
+U 9678 ; WX 873 ; N uni25CE ; G 2569
+U 9679 ; WX 873 ; N H18533 ; G 2570
+U 9680 ; WX 873 ; N uni25D0 ; G 2571
+U 9681 ; WX 873 ; N uni25D1 ; G 2572
+U 9682 ; WX 873 ; N uni25D2 ; G 2573
+U 9683 ; WX 873 ; N uni25D3 ; G 2574
+U 9684 ; WX 873 ; N uni25D4 ; G 2575
+U 9685 ; WX 873 ; N uni25D5 ; G 2576
+U 9686 ; WX 527 ; N uni25D6 ; G 2577
+U 9687 ; WX 527 ; N uni25D7 ; G 2578
+U 9688 ; WX 791 ; N invbullet ; G 2579
+U 9689 ; WX 970 ; N invcircle ; G 2580
+U 9690 ; WX 970 ; N uni25DA ; G 2581
+U 9691 ; WX 970 ; N uni25DB ; G 2582
+U 9692 ; WX 387 ; N uni25DC ; G 2583
+U 9693 ; WX 387 ; N uni25DD ; G 2584
+U 9694 ; WX 387 ; N uni25DE ; G 2585
+U 9695 ; WX 387 ; N uni25DF ; G 2586
+U 9696 ; WX 873 ; N uni25E0 ; G 2587
+U 9697 ; WX 873 ; N uni25E1 ; G 2588
+U 9698 ; WX 769 ; N uni25E2 ; G 2589
+U 9699 ; WX 769 ; N uni25E3 ; G 2590
+U 9700 ; WX 769 ; N uni25E4 ; G 2591
+U 9701 ; WX 769 ; N uni25E5 ; G 2592
+U 9702 ; WX 590 ; N openbullet ; G 2593
+U 9703 ; WX 945 ; N uni25E7 ; G 2594
+U 9704 ; WX 945 ; N uni25E8 ; G 2595
+U 9705 ; WX 945 ; N uni25E9 ; G 2596
+U 9706 ; WX 945 ; N uni25EA ; G 2597
+U 9707 ; WX 945 ; N uni25EB ; G 2598
+U 9708 ; WX 769 ; N uni25EC ; G 2599
+U 9709 ; WX 769 ; N uni25ED ; G 2600
+U 9710 ; WX 769 ; N uni25EE ; G 2601
+U 9711 ; WX 1119 ; N uni25EF ; G 2602
+U 9712 ; WX 945 ; N uni25F0 ; G 2603
+U 9713 ; WX 945 ; N uni25F1 ; G 2604
+U 9714 ; WX 945 ; N uni25F2 ; G 2605
+U 9715 ; WX 945 ; N uni25F3 ; G 2606
+U 9716 ; WX 873 ; N uni25F4 ; G 2607
+U 9717 ; WX 873 ; N uni25F5 ; G 2608
+U 9718 ; WX 873 ; N uni25F6 ; G 2609
+U 9719 ; WX 873 ; N uni25F7 ; G 2610
+U 9720 ; WX 769 ; N uni25F8 ; G 2611
+U 9721 ; WX 769 ; N uni25F9 ; G 2612
+U 9722 ; WX 769 ; N uni25FA ; G 2613
+U 9723 ; WX 830 ; N uni25FB ; G 2614
+U 9724 ; WX 830 ; N uni25FC ; G 2615
+U 9725 ; WX 732 ; N uni25FD ; G 2616
+U 9726 ; WX 732 ; N uni25FE ; G 2617
+U 9727 ; WX 769 ; N uni25FF ; G 2618
+U 9728 ; WX 896 ; N uni2600 ; G 2619
+U 9784 ; WX 896 ; N uni2638 ; G 2620
+U 9785 ; WX 896 ; N uni2639 ; G 2621
+U 9786 ; WX 896 ; N smileface ; G 2622
+U 9787 ; WX 896 ; N invsmileface ; G 2623
+U 9788 ; WX 896 ; N sun ; G 2624
+U 9791 ; WX 614 ; N uni263F ; G 2625
+U 9792 ; WX 731 ; N female ; G 2626
+U 9793 ; WX 731 ; N uni2641 ; G 2627
+U 9794 ; WX 896 ; N male ; G 2628
+U 9795 ; WX 896 ; N uni2643 ; G 2629
+U 9796 ; WX 896 ; N uni2644 ; G 2630
+U 9797 ; WX 896 ; N uni2645 ; G 2631
+U 9798 ; WX 896 ; N uni2646 ; G 2632
+U 9799 ; WX 896 ; N uni2647 ; G 2633
+U 9824 ; WX 896 ; N spade ; G 2634
+U 9825 ; WX 896 ; N uni2661 ; G 2635
+U 9826 ; WX 896 ; N uni2662 ; G 2636
+U 9827 ; WX 896 ; N club ; G 2637
+U 9828 ; WX 896 ; N uni2664 ; G 2638
+U 9829 ; WX 896 ; N heart ; G 2639
+U 9830 ; WX 896 ; N diamond ; G 2640
+U 9831 ; WX 896 ; N uni2667 ; G 2641
+U 9833 ; WX 472 ; N uni2669 ; G 2642
+U 9834 ; WX 638 ; N musicalnote ; G 2643
+U 9835 ; WX 896 ; N musicalnotedbl ; G 2644
+U 9836 ; WX 896 ; N uni266C ; G 2645
+U 9837 ; WX 472 ; N uni266D ; G 2646
+U 9838 ; WX 357 ; N uni266E ; G 2647
+U 9839 ; WX 484 ; N uni266F ; G 2648
+U 10145 ; WX 838 ; N uni27A1 ; G 2649
+U 10181 ; WX 457 ; N uni27C5 ; G 2650
+U 10182 ; WX 457 ; N uni27C6 ; G 2651
+U 10208 ; WX 494 ; N uni27E0 ; G 2652
+U 10216 ; WX 457 ; N uni27E8 ; G 2653
+U 10217 ; WX 457 ; N uni27E9 ; G 2654
+U 10224 ; WX 838 ; N uni27F0 ; G 2655
+U 10225 ; WX 838 ; N uni27F1 ; G 2656
+U 10226 ; WX 838 ; N uni27F2 ; G 2657
+U 10227 ; WX 838 ; N uni27F3 ; G 2658
+U 10228 ; WX 1033 ; N uni27F4 ; G 2659
+U 10229 ; WX 1434 ; N uni27F5 ; G 2660
+U 10230 ; WX 1434 ; N uni27F6 ; G 2661
+U 10231 ; WX 1434 ; N uni27F7 ; G 2662
+U 10232 ; WX 1434 ; N uni27F8 ; G 2663
+U 10233 ; WX 1434 ; N uni27F9 ; G 2664
+U 10234 ; WX 1434 ; N uni27FA ; G 2665
+U 10235 ; WX 1434 ; N uni27FB ; G 2666
+U 10236 ; WX 1434 ; N uni27FC ; G 2667
+U 10237 ; WX 1434 ; N uni27FD ; G 2668
+U 10238 ; WX 1434 ; N uni27FE ; G 2669
+U 10239 ; WX 1434 ; N uni27FF ; G 2670
+U 10240 ; WX 781 ; N uni2800 ; G 2671
+U 10241 ; WX 781 ; N uni2801 ; G 2672
+U 10242 ; WX 781 ; N uni2802 ; G 2673
+U 10243 ; WX 781 ; N uni2803 ; G 2674
+U 10244 ; WX 781 ; N uni2804 ; G 2675
+U 10245 ; WX 781 ; N uni2805 ; G 2676
+U 10246 ; WX 781 ; N uni2806 ; G 2677
+U 10247 ; WX 781 ; N uni2807 ; G 2678
+U 10248 ; WX 781 ; N uni2808 ; G 2679
+U 10249 ; WX 781 ; N uni2809 ; G 2680
+U 10250 ; WX 781 ; N uni280A ; G 2681
+U 10251 ; WX 781 ; N uni280B ; G 2682
+U 10252 ; WX 781 ; N uni280C ; G 2683
+U 10253 ; WX 781 ; N uni280D ; G 2684
+U 10254 ; WX 781 ; N uni280E ; G 2685
+U 10255 ; WX 781 ; N uni280F ; G 2686
+U 10256 ; WX 781 ; N uni2810 ; G 2687
+U 10257 ; WX 781 ; N uni2811 ; G 2688
+U 10258 ; WX 781 ; N uni2812 ; G 2689
+U 10259 ; WX 781 ; N uni2813 ; G 2690
+U 10260 ; WX 781 ; N uni2814 ; G 2691
+U 10261 ; WX 781 ; N uni2815 ; G 2692
+U 10262 ; WX 781 ; N uni2816 ; G 2693
+U 10263 ; WX 781 ; N uni2817 ; G 2694
+U 10264 ; WX 781 ; N uni2818 ; G 2695
+U 10265 ; WX 781 ; N uni2819 ; G 2696
+U 10266 ; WX 781 ; N uni281A ; G 2697
+U 10267 ; WX 781 ; N uni281B ; G 2698
+U 10268 ; WX 781 ; N uni281C ; G 2699
+U 10269 ; WX 781 ; N uni281D ; G 2700
+U 10270 ; WX 781 ; N uni281E ; G 2701
+U 10271 ; WX 781 ; N uni281F ; G 2702
+U 10272 ; WX 781 ; N uni2820 ; G 2703
+U 10273 ; WX 781 ; N uni2821 ; G 2704
+U 10274 ; WX 781 ; N uni2822 ; G 2705
+U 10275 ; WX 781 ; N uni2823 ; G 2706
+U 10276 ; WX 781 ; N uni2824 ; G 2707
+U 10277 ; WX 781 ; N uni2825 ; G 2708
+U 10278 ; WX 781 ; N uni2826 ; G 2709
+U 10279 ; WX 781 ; N uni2827 ; G 2710
+U 10280 ; WX 781 ; N uni2828 ; G 2711
+U 10281 ; WX 781 ; N uni2829 ; G 2712
+U 10282 ; WX 781 ; N uni282A ; G 2713
+U 10283 ; WX 781 ; N uni282B ; G 2714
+U 10284 ; WX 781 ; N uni282C ; G 2715
+U 10285 ; WX 781 ; N uni282D ; G 2716
+U 10286 ; WX 781 ; N uni282E ; G 2717
+U 10287 ; WX 781 ; N uni282F ; G 2718
+U 10288 ; WX 781 ; N uni2830 ; G 2719
+U 10289 ; WX 781 ; N uni2831 ; G 2720
+U 10290 ; WX 781 ; N uni2832 ; G 2721
+U 10291 ; WX 781 ; N uni2833 ; G 2722
+U 10292 ; WX 781 ; N uni2834 ; G 2723
+U 10293 ; WX 781 ; N uni2835 ; G 2724
+U 10294 ; WX 781 ; N uni2836 ; G 2725
+U 10295 ; WX 781 ; N uni2837 ; G 2726
+U 10296 ; WX 781 ; N uni2838 ; G 2727
+U 10297 ; WX 781 ; N uni2839 ; G 2728
+U 10298 ; WX 781 ; N uni283A ; G 2729
+U 10299 ; WX 781 ; N uni283B ; G 2730
+U 10300 ; WX 781 ; N uni283C ; G 2731
+U 10301 ; WX 781 ; N uni283D ; G 2732
+U 10302 ; WX 781 ; N uni283E ; G 2733
+U 10303 ; WX 781 ; N uni283F ; G 2734
+U 10304 ; WX 781 ; N uni2840 ; G 2735
+U 10305 ; WX 781 ; N uni2841 ; G 2736
+U 10306 ; WX 781 ; N uni2842 ; G 2737
+U 10307 ; WX 781 ; N uni2843 ; G 2738
+U 10308 ; WX 781 ; N uni2844 ; G 2739
+U 10309 ; WX 781 ; N uni2845 ; G 2740
+U 10310 ; WX 781 ; N uni2846 ; G 2741
+U 10311 ; WX 781 ; N uni2847 ; G 2742
+U 10312 ; WX 781 ; N uni2848 ; G 2743
+U 10313 ; WX 781 ; N uni2849 ; G 2744
+U 10314 ; WX 781 ; N uni284A ; G 2745
+U 10315 ; WX 781 ; N uni284B ; G 2746
+U 10316 ; WX 781 ; N uni284C ; G 2747
+U 10317 ; WX 781 ; N uni284D ; G 2748
+U 10318 ; WX 781 ; N uni284E ; G 2749
+U 10319 ; WX 781 ; N uni284F ; G 2750
+U 10320 ; WX 781 ; N uni2850 ; G 2751
+U 10321 ; WX 781 ; N uni2851 ; G 2752
+U 10322 ; WX 781 ; N uni2852 ; G 2753
+U 10323 ; WX 781 ; N uni2853 ; G 2754
+U 10324 ; WX 781 ; N uni2854 ; G 2755
+U 10325 ; WX 781 ; N uni2855 ; G 2756
+U 10326 ; WX 781 ; N uni2856 ; G 2757
+U 10327 ; WX 781 ; N uni2857 ; G 2758
+U 10328 ; WX 781 ; N uni2858 ; G 2759
+U 10329 ; WX 781 ; N uni2859 ; G 2760
+U 10330 ; WX 781 ; N uni285A ; G 2761
+U 10331 ; WX 781 ; N uni285B ; G 2762
+U 10332 ; WX 781 ; N uni285C ; G 2763
+U 10333 ; WX 781 ; N uni285D ; G 2764
+U 10334 ; WX 781 ; N uni285E ; G 2765
+U 10335 ; WX 781 ; N uni285F ; G 2766
+U 10336 ; WX 781 ; N uni2860 ; G 2767
+U 10337 ; WX 781 ; N uni2861 ; G 2768
+U 10338 ; WX 781 ; N uni2862 ; G 2769
+U 10339 ; WX 781 ; N uni2863 ; G 2770
+U 10340 ; WX 781 ; N uni2864 ; G 2771
+U 10341 ; WX 781 ; N uni2865 ; G 2772
+U 10342 ; WX 781 ; N uni2866 ; G 2773
+U 10343 ; WX 781 ; N uni2867 ; G 2774
+U 10344 ; WX 781 ; N uni2868 ; G 2775
+U 10345 ; WX 781 ; N uni2869 ; G 2776
+U 10346 ; WX 781 ; N uni286A ; G 2777
+U 10347 ; WX 781 ; N uni286B ; G 2778
+U 10348 ; WX 781 ; N uni286C ; G 2779
+U 10349 ; WX 781 ; N uni286D ; G 2780
+U 10350 ; WX 781 ; N uni286E ; G 2781
+U 10351 ; WX 781 ; N uni286F ; G 2782
+U 10352 ; WX 781 ; N uni2870 ; G 2783
+U 10353 ; WX 781 ; N uni2871 ; G 2784
+U 10354 ; WX 781 ; N uni2872 ; G 2785
+U 10355 ; WX 781 ; N uni2873 ; G 2786
+U 10356 ; WX 781 ; N uni2874 ; G 2787
+U 10357 ; WX 781 ; N uni2875 ; G 2788
+U 10358 ; WX 781 ; N uni2876 ; G 2789
+U 10359 ; WX 781 ; N uni2877 ; G 2790
+U 10360 ; WX 781 ; N uni2878 ; G 2791
+U 10361 ; WX 781 ; N uni2879 ; G 2792
+U 10362 ; WX 781 ; N uni287A ; G 2793
+U 10363 ; WX 781 ; N uni287B ; G 2794
+U 10364 ; WX 781 ; N uni287C ; G 2795
+U 10365 ; WX 781 ; N uni287D ; G 2796
+U 10366 ; WX 781 ; N uni287E ; G 2797
+U 10367 ; WX 781 ; N uni287F ; G 2798
+U 10368 ; WX 781 ; N uni2880 ; G 2799
+U 10369 ; WX 781 ; N uni2881 ; G 2800
+U 10370 ; WX 781 ; N uni2882 ; G 2801
+U 10371 ; WX 781 ; N uni2883 ; G 2802
+U 10372 ; WX 781 ; N uni2884 ; G 2803
+U 10373 ; WX 781 ; N uni2885 ; G 2804
+U 10374 ; WX 781 ; N uni2886 ; G 2805
+U 10375 ; WX 781 ; N uni2887 ; G 2806
+U 10376 ; WX 781 ; N uni2888 ; G 2807
+U 10377 ; WX 781 ; N uni2889 ; G 2808
+U 10378 ; WX 781 ; N uni288A ; G 2809
+U 10379 ; WX 781 ; N uni288B ; G 2810
+U 10380 ; WX 781 ; N uni288C ; G 2811
+U 10381 ; WX 781 ; N uni288D ; G 2812
+U 10382 ; WX 781 ; N uni288E ; G 2813
+U 10383 ; WX 781 ; N uni288F ; G 2814
+U 10384 ; WX 781 ; N uni2890 ; G 2815
+U 10385 ; WX 781 ; N uni2891 ; G 2816
+U 10386 ; WX 781 ; N uni2892 ; G 2817
+U 10387 ; WX 781 ; N uni2893 ; G 2818
+U 10388 ; WX 781 ; N uni2894 ; G 2819
+U 10389 ; WX 781 ; N uni2895 ; G 2820
+U 10390 ; WX 781 ; N uni2896 ; G 2821
+U 10391 ; WX 781 ; N uni2897 ; G 2822
+U 10392 ; WX 781 ; N uni2898 ; G 2823
+U 10393 ; WX 781 ; N uni2899 ; G 2824
+U 10394 ; WX 781 ; N uni289A ; G 2825
+U 10395 ; WX 781 ; N uni289B ; G 2826
+U 10396 ; WX 781 ; N uni289C ; G 2827
+U 10397 ; WX 781 ; N uni289D ; G 2828
+U 10398 ; WX 781 ; N uni289E ; G 2829
+U 10399 ; WX 781 ; N uni289F ; G 2830
+U 10400 ; WX 781 ; N uni28A0 ; G 2831
+U 10401 ; WX 781 ; N uni28A1 ; G 2832
+U 10402 ; WX 781 ; N uni28A2 ; G 2833
+U 10403 ; WX 781 ; N uni28A3 ; G 2834
+U 10404 ; WX 781 ; N uni28A4 ; G 2835
+U 10405 ; WX 781 ; N uni28A5 ; G 2836
+U 10406 ; WX 781 ; N uni28A6 ; G 2837
+U 10407 ; WX 781 ; N uni28A7 ; G 2838
+U 10408 ; WX 781 ; N uni28A8 ; G 2839
+U 10409 ; WX 781 ; N uni28A9 ; G 2840
+U 10410 ; WX 781 ; N uni28AA ; G 2841
+U 10411 ; WX 781 ; N uni28AB ; G 2842
+U 10412 ; WX 781 ; N uni28AC ; G 2843
+U 10413 ; WX 781 ; N uni28AD ; G 2844
+U 10414 ; WX 781 ; N uni28AE ; G 2845
+U 10415 ; WX 781 ; N uni28AF ; G 2846
+U 10416 ; WX 781 ; N uni28B0 ; G 2847
+U 10417 ; WX 781 ; N uni28B1 ; G 2848
+U 10418 ; WX 781 ; N uni28B2 ; G 2849
+U 10419 ; WX 781 ; N uni28B3 ; G 2850
+U 10420 ; WX 781 ; N uni28B4 ; G 2851
+U 10421 ; WX 781 ; N uni28B5 ; G 2852
+U 10422 ; WX 781 ; N uni28B6 ; G 2853
+U 10423 ; WX 781 ; N uni28B7 ; G 2854
+U 10424 ; WX 781 ; N uni28B8 ; G 2855
+U 10425 ; WX 781 ; N uni28B9 ; G 2856
+U 10426 ; WX 781 ; N uni28BA ; G 2857
+U 10427 ; WX 781 ; N uni28BB ; G 2858
+U 10428 ; WX 781 ; N uni28BC ; G 2859
+U 10429 ; WX 781 ; N uni28BD ; G 2860
+U 10430 ; WX 781 ; N uni28BE ; G 2861
+U 10431 ; WX 781 ; N uni28BF ; G 2862
+U 10432 ; WX 781 ; N uni28C0 ; G 2863
+U 10433 ; WX 781 ; N uni28C1 ; G 2864
+U 10434 ; WX 781 ; N uni28C2 ; G 2865
+U 10435 ; WX 781 ; N uni28C3 ; G 2866
+U 10436 ; WX 781 ; N uni28C4 ; G 2867
+U 10437 ; WX 781 ; N uni28C5 ; G 2868
+U 10438 ; WX 781 ; N uni28C6 ; G 2869
+U 10439 ; WX 781 ; N uni28C7 ; G 2870
+U 10440 ; WX 781 ; N uni28C8 ; G 2871
+U 10441 ; WX 781 ; N uni28C9 ; G 2872
+U 10442 ; WX 781 ; N uni28CA ; G 2873
+U 10443 ; WX 781 ; N uni28CB ; G 2874
+U 10444 ; WX 781 ; N uni28CC ; G 2875
+U 10445 ; WX 781 ; N uni28CD ; G 2876
+U 10446 ; WX 781 ; N uni28CE ; G 2877
+U 10447 ; WX 781 ; N uni28CF ; G 2878
+U 10448 ; WX 781 ; N uni28D0 ; G 2879
+U 10449 ; WX 781 ; N uni28D1 ; G 2880
+U 10450 ; WX 781 ; N uni28D2 ; G 2881
+U 10451 ; WX 781 ; N uni28D3 ; G 2882
+U 10452 ; WX 781 ; N uni28D4 ; G 2883
+U 10453 ; WX 781 ; N uni28D5 ; G 2884
+U 10454 ; WX 781 ; N uni28D6 ; G 2885
+U 10455 ; WX 781 ; N uni28D7 ; G 2886
+U 10456 ; WX 781 ; N uni28D8 ; G 2887
+U 10457 ; WX 781 ; N uni28D9 ; G 2888
+U 10458 ; WX 781 ; N uni28DA ; G 2889
+U 10459 ; WX 781 ; N uni28DB ; G 2890
+U 10460 ; WX 781 ; N uni28DC ; G 2891
+U 10461 ; WX 781 ; N uni28DD ; G 2892
+U 10462 ; WX 781 ; N uni28DE ; G 2893
+U 10463 ; WX 781 ; N uni28DF ; G 2894
+U 10464 ; WX 781 ; N uni28E0 ; G 2895
+U 10465 ; WX 781 ; N uni28E1 ; G 2896
+U 10466 ; WX 781 ; N uni28E2 ; G 2897
+U 10467 ; WX 781 ; N uni28E3 ; G 2898
+U 10468 ; WX 781 ; N uni28E4 ; G 2899
+U 10469 ; WX 781 ; N uni28E5 ; G 2900
+U 10470 ; WX 781 ; N uni28E6 ; G 2901
+U 10471 ; WX 781 ; N uni28E7 ; G 2902
+U 10472 ; WX 781 ; N uni28E8 ; G 2903
+U 10473 ; WX 781 ; N uni28E9 ; G 2904
+U 10474 ; WX 781 ; N uni28EA ; G 2905
+U 10475 ; WX 781 ; N uni28EB ; G 2906
+U 10476 ; WX 781 ; N uni28EC ; G 2907
+U 10477 ; WX 781 ; N uni28ED ; G 2908
+U 10478 ; WX 781 ; N uni28EE ; G 2909
+U 10479 ; WX 781 ; N uni28EF ; G 2910
+U 10480 ; WX 781 ; N uni28F0 ; G 2911
+U 10481 ; WX 781 ; N uni28F1 ; G 2912
+U 10482 ; WX 781 ; N uni28F2 ; G 2913
+U 10483 ; WX 781 ; N uni28F3 ; G 2914
+U 10484 ; WX 781 ; N uni28F4 ; G 2915
+U 10485 ; WX 781 ; N uni28F5 ; G 2916
+U 10486 ; WX 781 ; N uni28F6 ; G 2917
+U 10487 ; WX 781 ; N uni28F7 ; G 2918
+U 10488 ; WX 781 ; N uni28F8 ; G 2919
+U 10489 ; WX 781 ; N uni28F9 ; G 2920
+U 10490 ; WX 781 ; N uni28FA ; G 2921
+U 10491 ; WX 781 ; N uni28FB ; G 2922
+U 10492 ; WX 781 ; N uni28FC ; G 2923
+U 10493 ; WX 781 ; N uni28FD ; G 2924
+U 10494 ; WX 781 ; N uni28FE ; G 2925
+U 10495 ; WX 781 ; N uni28FF ; G 2926
+U 10496 ; WX 838 ; N uni2900 ; G 2927
+U 10497 ; WX 838 ; N uni2901 ; G 2928
+U 10498 ; WX 838 ; N uni2902 ; G 2929
+U 10499 ; WX 838 ; N uni2903 ; G 2930
+U 10500 ; WX 838 ; N uni2904 ; G 2931
+U 10501 ; WX 838 ; N uni2905 ; G 2932
+U 10502 ; WX 838 ; N uni2906 ; G 2933
+U 10503 ; WX 838 ; N uni2907 ; G 2934
+U 10504 ; WX 838 ; N uni2908 ; G 2935
+U 10505 ; WX 838 ; N uni2909 ; G 2936
+U 10506 ; WX 838 ; N uni290A ; G 2937
+U 10507 ; WX 838 ; N uni290B ; G 2938
+U 10508 ; WX 838 ; N uni290C ; G 2939
+U 10509 ; WX 838 ; N uni290D ; G 2940
+U 10510 ; WX 838 ; N uni290E ; G 2941
+U 10511 ; WX 838 ; N uni290F ; G 2942
+U 10512 ; WX 838 ; N uni2910 ; G 2943
+U 10513 ; WX 838 ; N uni2911 ; G 2944
+U 10514 ; WX 838 ; N uni2912 ; G 2945
+U 10515 ; WX 838 ; N uni2913 ; G 2946
+U 10516 ; WX 838 ; N uni2914 ; G 2947
+U 10517 ; WX 838 ; N uni2915 ; G 2948
+U 10518 ; WX 838 ; N uni2916 ; G 2949
+U 10519 ; WX 838 ; N uni2917 ; G 2950
+U 10520 ; WX 838 ; N uni2918 ; G 2951
+U 10521 ; WX 838 ; N uni2919 ; G 2952
+U 10522 ; WX 838 ; N uni291A ; G 2953
+U 10523 ; WX 838 ; N uni291B ; G 2954
+U 10524 ; WX 838 ; N uni291C ; G 2955
+U 10525 ; WX 838 ; N uni291D ; G 2956
+U 10526 ; WX 838 ; N uni291E ; G 2957
+U 10527 ; WX 838 ; N uni291F ; G 2958
+U 10528 ; WX 838 ; N uni2920 ; G 2959
+U 10529 ; WX 838 ; N uni2921 ; G 2960
+U 10530 ; WX 838 ; N uni2922 ; G 2961
+U 10531 ; WX 838 ; N uni2923 ; G 2962
+U 10532 ; WX 838 ; N uni2924 ; G 2963
+U 10533 ; WX 838 ; N uni2925 ; G 2964
+U 10534 ; WX 838 ; N uni2926 ; G 2965
+U 10535 ; WX 838 ; N uni2927 ; G 2966
+U 10536 ; WX 838 ; N uni2928 ; G 2967
+U 10537 ; WX 838 ; N uni2929 ; G 2968
+U 10538 ; WX 838 ; N uni292A ; G 2969
+U 10539 ; WX 838 ; N uni292B ; G 2970
+U 10540 ; WX 838 ; N uni292C ; G 2971
+U 10541 ; WX 838 ; N uni292D ; G 2972
+U 10542 ; WX 838 ; N uni292E ; G 2973
+U 10543 ; WX 838 ; N uni292F ; G 2974
+U 10544 ; WX 838 ; N uni2930 ; G 2975
+U 10545 ; WX 838 ; N uni2931 ; G 2976
+U 10546 ; WX 838 ; N uni2932 ; G 2977
+U 10547 ; WX 838 ; N uni2933 ; G 2978
+U 10548 ; WX 838 ; N uni2934 ; G 2979
+U 10549 ; WX 838 ; N uni2935 ; G 2980
+U 10550 ; WX 838 ; N uni2936 ; G 2981
+U 10551 ; WX 838 ; N uni2937 ; G 2982
+U 10552 ; WX 838 ; N uni2938 ; G 2983
+U 10553 ; WX 838 ; N uni2939 ; G 2984
+U 10554 ; WX 838 ; N uni293A ; G 2985
+U 10555 ; WX 838 ; N uni293B ; G 2986
+U 10556 ; WX 838 ; N uni293C ; G 2987
+U 10557 ; WX 838 ; N uni293D ; G 2988
+U 10558 ; WX 838 ; N uni293E ; G 2989
+U 10559 ; WX 838 ; N uni293F ; G 2990
+U 10560 ; WX 838 ; N uni2940 ; G 2991
+U 10561 ; WX 838 ; N uni2941 ; G 2992
+U 10562 ; WX 838 ; N uni2942 ; G 2993
+U 10563 ; WX 838 ; N uni2943 ; G 2994
+U 10564 ; WX 838 ; N uni2944 ; G 2995
+U 10565 ; WX 838 ; N uni2945 ; G 2996
+U 10566 ; WX 838 ; N uni2946 ; G 2997
+U 10567 ; WX 838 ; N uni2947 ; G 2998
+U 10568 ; WX 838 ; N uni2948 ; G 2999
+U 10569 ; WX 838 ; N uni2949 ; G 3000
+U 10570 ; WX 838 ; N uni294A ; G 3001
+U 10571 ; WX 838 ; N uni294B ; G 3002
+U 10572 ; WX 838 ; N uni294C ; G 3003
+U 10573 ; WX 838 ; N uni294D ; G 3004
+U 10574 ; WX 838 ; N uni294E ; G 3005
+U 10575 ; WX 838 ; N uni294F ; G 3006
+U 10576 ; WX 838 ; N uni2950 ; G 3007
+U 10577 ; WX 838 ; N uni2951 ; G 3008
+U 10578 ; WX 838 ; N uni2952 ; G 3009
+U 10579 ; WX 838 ; N uni2953 ; G 3010
+U 10580 ; WX 838 ; N uni2954 ; G 3011
+U 10581 ; WX 838 ; N uni2955 ; G 3012
+U 10582 ; WX 838 ; N uni2956 ; G 3013
+U 10583 ; WX 838 ; N uni2957 ; G 3014
+U 10584 ; WX 838 ; N uni2958 ; G 3015
+U 10585 ; WX 838 ; N uni2959 ; G 3016
+U 10586 ; WX 838 ; N uni295A ; G 3017
+U 10587 ; WX 838 ; N uni295B ; G 3018
+U 10588 ; WX 838 ; N uni295C ; G 3019
+U 10589 ; WX 838 ; N uni295D ; G 3020
+U 10590 ; WX 838 ; N uni295E ; G 3021
+U 10591 ; WX 838 ; N uni295F ; G 3022
+U 10592 ; WX 838 ; N uni2960 ; G 3023
+U 10593 ; WX 838 ; N uni2961 ; G 3024
+U 10594 ; WX 838 ; N uni2962 ; G 3025
+U 10595 ; WX 838 ; N uni2963 ; G 3026
+U 10596 ; WX 838 ; N uni2964 ; G 3027
+U 10597 ; WX 838 ; N uni2965 ; G 3028
+U 10598 ; WX 838 ; N uni2966 ; G 3029
+U 10599 ; WX 838 ; N uni2967 ; G 3030
+U 10600 ; WX 838 ; N uni2968 ; G 3031
+U 10601 ; WX 838 ; N uni2969 ; G 3032
+U 10602 ; WX 838 ; N uni296A ; G 3033
+U 10603 ; WX 838 ; N uni296B ; G 3034
+U 10604 ; WX 838 ; N uni296C ; G 3035
+U 10605 ; WX 838 ; N uni296D ; G 3036
+U 10606 ; WX 838 ; N uni296E ; G 3037
+U 10607 ; WX 838 ; N uni296F ; G 3038
+U 10608 ; WX 838 ; N uni2970 ; G 3039
+U 10609 ; WX 838 ; N uni2971 ; G 3040
+U 10610 ; WX 838 ; N uni2972 ; G 3041
+U 10611 ; WX 838 ; N uni2973 ; G 3042
+U 10612 ; WX 838 ; N uni2974 ; G 3043
+U 10613 ; WX 838 ; N uni2975 ; G 3044
+U 10614 ; WX 838 ; N uni2976 ; G 3045
+U 10615 ; WX 1032 ; N uni2977 ; G 3046
+U 10616 ; WX 838 ; N uni2978 ; G 3047
+U 10617 ; WX 838 ; N uni2979 ; G 3048
+U 10618 ; WX 960 ; N uni297A ; G 3049
+U 10619 ; WX 838 ; N uni297B ; G 3050
+U 10620 ; WX 838 ; N uni297C ; G 3051
+U 10621 ; WX 838 ; N uni297D ; G 3052
+U 10622 ; WX 838 ; N uni297E ; G 3053
+U 10623 ; WX 838 ; N uni297F ; G 3054
+U 10731 ; WX 494 ; N uni29EB ; G 3055
+U 10764 ; WX 1782 ; N uni2A0C ; G 3056
+U 10765 ; WX 610 ; N uni2A0D ; G 3057
+U 10766 ; WX 610 ; N uni2A0E ; G 3058
+U 10799 ; WX 838 ; N uni2A2F ; G 3059
+U 10858 ; WX 838 ; N uni2A6A ; G 3060
+U 10859 ; WX 838 ; N uni2A6B ; G 3061
+U 11008 ; WX 838 ; N uni2B00 ; G 3062
+U 11009 ; WX 838 ; N uni2B01 ; G 3063
+U 11010 ; WX 838 ; N uni2B02 ; G 3064
+U 11011 ; WX 838 ; N uni2B03 ; G 3065
+U 11012 ; WX 838 ; N uni2B04 ; G 3066
+U 11013 ; WX 838 ; N uni2B05 ; G 3067
+U 11014 ; WX 838 ; N uni2B06 ; G 3068
+U 11015 ; WX 838 ; N uni2B07 ; G 3069
+U 11016 ; WX 838 ; N uni2B08 ; G 3070
+U 11017 ; WX 838 ; N uni2B09 ; G 3071
+U 11018 ; WX 838 ; N uni2B0A ; G 3072
+U 11019 ; WX 838 ; N uni2B0B ; G 3073
+U 11020 ; WX 838 ; N uni2B0C ; G 3074
+U 11021 ; WX 838 ; N uni2B0D ; G 3075
+U 11022 ; WX 838 ; N uni2B0E ; G 3076
+U 11023 ; WX 838 ; N uni2B0F ; G 3077
+U 11024 ; WX 838 ; N uni2B10 ; G 3078
+U 11025 ; WX 838 ; N uni2B11 ; G 3079
+U 11026 ; WX 945 ; N uni2B12 ; G 3080
+U 11027 ; WX 945 ; N uni2B13 ; G 3081
+U 11028 ; WX 945 ; N uni2B14 ; G 3082
+U 11029 ; WX 945 ; N uni2B15 ; G 3083
+U 11030 ; WX 769 ; N uni2B16 ; G 3084
+U 11031 ; WX 769 ; N uni2B17 ; G 3085
+U 11032 ; WX 769 ; N uni2B18 ; G 3086
+U 11033 ; WX 769 ; N uni2B19 ; G 3087
+U 11034 ; WX 945 ; N uni2B1A ; G 3088
+U 11360 ; WX 703 ; N uni2C60 ; G 3089
+U 11361 ; WX 380 ; N uni2C61 ; G 3090
+U 11363 ; WX 752 ; N uni2C63 ; G 3091
+U 11364 ; WX 831 ; N uni2C64 ; G 3092
+U 11367 ; WX 945 ; N uni2C67 ; G 3093
+U 11368 ; WX 727 ; N uni2C68 ; G 3094
+U 11369 ; WX 869 ; N uni2C69 ; G 3095
+U 11370 ; WX 693 ; N uni2C6A ; G 3096
+U 11371 ; WX 730 ; N uni2C6B ; G 3097
+U 11372 ; WX 568 ; N uni2C6C ; G 3098
+U 11373 ; WX 848 ; N uni2C6D ; G 3099
+U 11374 ; WX 1107 ; N uni2C6E ; G 3100
+U 11375 ; WX 776 ; N uni2C6F ; G 3101
+U 11376 ; WX 848 ; N uni2C70 ; G 3102
+U 11377 ; WX 709 ; N uni2C71 ; G 3103
+U 11378 ; WX 1221 ; N uni2C72 ; G 3104
+U 11379 ; WX 984 ; N uni2C73 ; G 3105
+U 11381 ; WX 779 ; N uni2C75 ; G 3106
+U 11382 ; WX 576 ; N uni2C76 ; G 3107
+U 11383 ; WX 905 ; N uni2C77 ; G 3108
+U 11385 ; WX 571 ; N uni2C79 ; G 3109
+U 11386 ; WX 667 ; N uni2C7A ; G 3110
+U 11387 ; WX 617 ; N uni2C7B ; G 3111
+U 11388 ; WX 313 ; N uni2C7C ; G 3112
+U 11389 ; WX 489 ; N uni2C7D ; G 3113
+U 11390 ; WX 722 ; N uni2C7E ; G 3114
+U 11391 ; WX 730 ; N uni2C7F ; G 3115
+U 11520 ; WX 773 ; N uni2D00 ; G 3116
+U 11521 ; WX 635 ; N uni2D01 ; G 3117
+U 11522 ; WX 804 ; N uni2D02 ; G 3118
+U 11523 ; WX 658 ; N uni2D03 ; G 3119
+U 11524 ; WX 788 ; N uni2D04 ; G 3120
+U 11525 ; WX 962 ; N uni2D05 ; G 3121
+U 11526 ; WX 756 ; N uni2D06 ; G 3122
+U 11527 ; WX 960 ; N uni2D07 ; G 3123
+U 11528 ; WX 617 ; N uni2D08 ; G 3124
+U 11529 ; WX 646 ; N uni2D09 ; G 3125
+U 11530 ; WX 962 ; N uni2D0A ; G 3126
+U 11531 ; WX 631 ; N uni2D0B ; G 3127
+U 11532 ; WX 646 ; N uni2D0C ; G 3128
+U 11533 ; WX 962 ; N uni2D0D ; G 3129
+U 11534 ; WX 846 ; N uni2D0E ; G 3130
+U 11535 ; WX 866 ; N uni2D0F ; G 3131
+U 11536 ; WX 961 ; N uni2D10 ; G 3132
+U 11537 ; WX 645 ; N uni2D11 ; G 3133
+U 11538 ; WX 645 ; N uni2D12 ; G 3134
+U 11539 ; WX 959 ; N uni2D13 ; G 3135
+U 11540 ; WX 945 ; N uni2D14 ; G 3136
+U 11541 ; WX 863 ; N uni2D15 ; G 3137
+U 11542 ; WX 644 ; N uni2D16 ; G 3138
+U 11543 ; WX 646 ; N uni2D17 ; G 3139
+U 11544 ; WX 645 ; N uni2D18 ; G 3140
+U 11545 ; WX 649 ; N uni2D19 ; G 3141
+U 11546 ; WX 688 ; N uni2D1A ; G 3142
+U 11547 ; WX 936 ; N uni2D1B ; G 3143
+U 11548 ; WX 982 ; N uni2D1C ; G 3144
+U 11549 ; WX 681 ; N uni2D1D ; G 3145
+U 11550 ; WX 676 ; N uni2D1E ; G 3146
+U 11551 ; WX 852 ; N uni2D1F ; G 3147
+U 11552 ; WX 1113 ; N uni2D20 ; G 3148
+U 11553 ; WX 632 ; N uni2D21 ; G 3149
+U 11554 ; WX 645 ; N uni2D22 ; G 3150
+U 11555 ; WX 646 ; N uni2D23 ; G 3151
+U 11556 ; WX 749 ; N uni2D24 ; G 3152
+U 11557 ; WX 914 ; N uni2D25 ; G 3153
+U 11800 ; WX 586 ; N uni2E18 ; G 3154
+U 11807 ; WX 838 ; N uni2E1F ; G 3155
+U 11810 ; WX 473 ; N uni2E22 ; G 3156
+U 11811 ; WX 473 ; N uni2E23 ; G 3157
+U 11812 ; WX 473 ; N uni2E24 ; G 3158
+U 11813 ; WX 473 ; N uni2E25 ; G 3159
+U 11822 ; WX 586 ; N uni2E2E ; G 3160
+U 42564 ; WX 722 ; N uniA644 ; G 3161
+U 42565 ; WX 563 ; N uniA645 ; G 3162
+U 42566 ; WX 468 ; N uniA646 ; G 3163
+U 42567 ; WX 380 ; N uniA647 ; G 3164
+U 42576 ; WX 1333 ; N uniA650 ; G 3165
+U 42577 ; WX 1085 ; N uniA651 ; G 3166
+U 42580 ; WX 1287 ; N uniA654 ; G 3167
+U 42581 ; WX 1025 ; N uniA655 ; G 3168
+U 42582 ; WX 1287 ; N uniA656 ; G 3169
+U 42583 ; WX 1029 ; N uniA657 ; G 3170
+U 42648 ; WX 1448 ; N uniA698 ; G 3171
+U 42649 ; WX 1060 ; N uniA699 ; G 3172
+U 42760 ; WX 500 ; N uniA708 ; G 3173
+U 42761 ; WX 500 ; N uniA709 ; G 3174
+U 42762 ; WX 500 ; N uniA70A ; G 3175
+U 42763 ; WX 500 ; N uniA70B ; G 3176
+U 42764 ; WX 500 ; N uniA70C ; G 3177
+U 42765 ; WX 500 ; N uniA70D ; G 3178
+U 42766 ; WX 500 ; N uniA70E ; G 3179
+U 42767 ; WX 500 ; N uniA70F ; G 3180
+U 42768 ; WX 500 ; N uniA710 ; G 3181
+U 42769 ; WX 500 ; N uniA711 ; G 3182
+U 42770 ; WX 500 ; N uniA712 ; G 3183
+U 42771 ; WX 500 ; N uniA713 ; G 3184
+U 42772 ; WX 500 ; N uniA714 ; G 3185
+U 42773 ; WX 500 ; N uniA715 ; G 3186
+U 42774 ; WX 500 ; N uniA716 ; G 3187
+U 42779 ; WX 384 ; N uniA71B ; G 3188
+U 42780 ; WX 384 ; N uniA71C ; G 3189
+U 42781 ; WX 276 ; N uniA71D ; G 3190
+U 42782 ; WX 276 ; N uniA71E ; G 3191
+U 42783 ; WX 276 ; N uniA71F ; G 3192
+U 42790 ; WX 945 ; N uniA726 ; G 3193
+U 42791 ; WX 712 ; N uniA727 ; G 3194
+U 42792 ; WX 1003 ; N uniA728 ; G 3195
+U 42793 ; WX 909 ; N uniA729 ; G 3196
+U 42794 ; WX 696 ; N uniA72A ; G 3197
+U 42795 ; WX 609 ; N uniA72B ; G 3198
+U 42796 ; WX 634 ; N uniA72C ; G 3199
+U 42797 ; WX 598 ; N uniA72D ; G 3200
+U 42798 ; WX 741 ; N uniA72E ; G 3201
+U 42799 ; WX 706 ; N uniA72F ; G 3202
+U 42800 ; WX 592 ; N uniA730 ; G 3203
+U 42801 ; WX 563 ; N uniA731 ; G 3204
+U 42802 ; WX 1301 ; N uniA732 ; G 3205
+U 42803 ; WX 983 ; N uniA733 ; G 3206
+U 42804 ; WX 1261 ; N uniA734 ; G 3207
+U 42805 ; WX 985 ; N uniA735 ; G 3208
+U 42806 ; WX 1168 ; N uniA736 ; G 3209
+U 42807 ; WX 1007 ; N uniA737 ; G 3210
+U 42808 ; WX 1016 ; N uniA738 ; G 3211
+U 42809 ; WX 832 ; N uniA739 ; G 3212
+U 42810 ; WX 1016 ; N uniA73A ; G 3213
+U 42811 ; WX 832 ; N uniA73B ; G 3214
+U 42812 ; WX 994 ; N uniA73C ; G 3215
+U 42813 ; WX 746 ; N uniA73D ; G 3216
+U 42814 ; WX 796 ; N uniA73E ; G 3217
+U 42815 ; WX 609 ; N uniA73F ; G 3218
+U 42816 ; WX 869 ; N uniA740 ; G 3219
+U 42817 ; WX 693 ; N uniA741 ; G 3220
+U 42822 ; WX 916 ; N uniA746 ; G 3221
+U 42823 ; WX 581 ; N uniA747 ; G 3222
+U 42826 ; WX 1010 ; N uniA74A ; G 3223
+U 42827 ; WX 770 ; N uniA74B ; G 3224
+U 42830 ; WX 1448 ; N uniA74E ; G 3225
+U 42831 ; WX 1060 ; N uniA74F ; G 3226
+U 42856 ; WX 787 ; N uniA768 ; G 3227
+U 42857 ; WX 716 ; N uniA769 ; G 3228
+U 42875 ; WX 694 ; N uniA77B ; G 3229
+U 42876 ; WX 527 ; N uniA77C ; G 3230
+U 42880 ; WX 703 ; N uniA780 ; G 3231
+U 42881 ; WX 380 ; N uniA781 ; G 3232
+U 42882 ; WX 872 ; N uniA782 ; G 3233
+U 42883 ; WX 727 ; N uniA783 ; G 3234
+U 42884 ; WX 694 ; N uniA784 ; G 3235
+U 42885 ; WX 527 ; N uniA785 ; G 3236
+U 42886 ; WX 796 ; N uniA786 ; G 3237
+U 42887 ; WX 609 ; N uniA787 ; G 3238
+U 42891 ; WX 439 ; N uniA78B ; G 3239
+U 42892 ; WX 306 ; N uniA78C ; G 3240
+U 42893 ; WX 913 ; N uniA78D ; G 3241
+U 42896 ; WX 914 ; N uniA790 ; G 3242
+U 42897 ; WX 812 ; N uniA791 ; G 3243
+U 42922 ; WX 945 ; N uniA7AA ; G 3244
+U 43000 ; WX 595 ; N uniA7F8 ; G 3245
+U 43001 ; WX 647 ; N uniA7F9 ; G 3246
+U 43002 ; WX 1068 ; N uniA7FA ; G 3247
+U 43003 ; WX 710 ; N uniA7FB ; G 3248
+U 43004 ; WX 752 ; N uniA7FC ; G 3249
+U 43005 ; WX 1107 ; N uniA7FD ; G 3250
+U 43006 ; WX 468 ; N uniA7FE ; G 3251
+U 43007 ; WX 1286 ; N uniA7FF ; G 3252
+U 62464 ; WX 726 ; N uniF400 ; G 3253
+U 62465 ; WX 737 ; N uniF401 ; G 3254
+U 62466 ; WX 786 ; N uniF402 ; G 3255
+U 62467 ; WX 1019 ; N uniF403 ; G 3256
+U 62468 ; WX 737 ; N uniF404 ; G 3257
+U 62469 ; WX 731 ; N uniF405 ; G 3258
+U 62470 ; WX 796 ; N uniF406 ; G 3259
+U 62471 ; WX 1058 ; N uniF407 ; G 3260
+U 62472 ; WX 737 ; N uniF408 ; G 3261
+U 62473 ; WX 737 ; N uniF409 ; G 3262
+U 62474 ; WX 1329 ; N uniF40A ; G 3263
+U 62475 ; WX 754 ; N uniF40B ; G 3264
+U 62476 ; WX 753 ; N uniF40C ; G 3265
+U 62477 ; WX 1024 ; N uniF40D ; G 3266
+U 62478 ; WX 737 ; N uniF40E ; G 3267
+U 62479 ; WX 753 ; N uniF40F ; G 3268
+U 62480 ; WX 1070 ; N uniF410 ; G 3269
+U 62481 ; WX 818 ; N uniF411 ; G 3270
+U 62482 ; WX 870 ; N uniF412 ; G 3271
+U 62483 ; WX 819 ; N uniF413 ; G 3272
+U 62484 ; WX 1016 ; N uniF414 ; G 3273
+U 62485 ; WX 753 ; N uniF415 ; G 3274
+U 62486 ; WX 1008 ; N uniF416 ; G 3275
+U 62487 ; WX 752 ; N uniF417 ; G 3276
+U 62488 ; WX 760 ; N uniF418 ; G 3277
+U 62489 ; WX 753 ; N uniF419 ; G 3278
+U 62490 ; WX 800 ; N uniF41A ; G 3279
+U 62491 ; WX 753 ; N uniF41B ; G 3280
+U 62492 ; WX 760 ; N uniF41C ; G 3281
+U 62493 ; WX 738 ; N uniF41D ; G 3282
+U 62494 ; WX 801 ; N uniF41E ; G 3283
+U 62495 ; WX 956 ; N uniF41F ; G 3284
+U 62496 ; WX 736 ; N uniF420 ; G 3285
+U 62497 ; WX 847 ; N uniF421 ; G 3286
+U 62498 ; WX 737 ; N uniF422 ; G 3287
+U 62499 ; WX 737 ; N uniF423 ; G 3288
+U 62500 ; WX 737 ; N uniF424 ; G 3289
+U 62501 ; WX 793 ; N uniF425 ; G 3290
+U 62502 ; WX 1033 ; N uniF426 ; G 3291
+U 62504 ; WX 904 ; N uniF428 ; G 3292
+U 63172 ; WX 380 ; N uniF6C4 ; G 3293
+U 63173 ; WX 667 ; N uniF6C5 ; G 3294
+U 63174 ; WX 699 ; N uniF6C6 ; G 3295
+U 63175 ; WX 727 ; N uniF6C7 ; G 3296
+U 63176 ; WX 1058 ; N uniF6C8 ; G 3297
+U 63185 ; WX 500 ; N cyrBreve ; G 3298
+U 63188 ; WX 500 ; N cyrbreve ; G 3299
+U 64256 ; WX 827 ; N uniFB00 ; G 3300
+U 64257 ; WX 727 ; N fi ; G 3301
+U 64258 ; WX 727 ; N fl ; G 3302
+U 64259 ; WX 1108 ; N uniFB03 ; G 3303
+U 64260 ; WX 1146 ; N uniFB04 ; G 3304
+U 64261 ; WX 879 ; N uniFB05 ; G 3305
+U 64262 ; WX 971 ; N uniFB06 ; G 3306
+U 65024 ; WX 0 ; N uniFE00 ; G 3307
+U 65025 ; WX 0 ; N uniFE01 ; G 3308
+U 65026 ; WX 0 ; N uniFE02 ; G 3309
+U 65027 ; WX 0 ; N uniFE03 ; G 3310
+U 65028 ; WX 0 ; N uniFE04 ; G 3311
+U 65029 ; WX 0 ; N uniFE05 ; G 3312
+U 65030 ; WX 0 ; N uniFE06 ; G 3313
+U 65031 ; WX 0 ; N uniFE07 ; G 3314
+U 65032 ; WX 0 ; N uniFE08 ; G 3315
+U 65033 ; WX 0 ; N uniFE09 ; G 3316
+U 65034 ; WX 0 ; N uniFE0A ; G 3317
+U 65035 ; WX 0 ; N uniFE0B ; G 3318
+U 65036 ; WX 0 ; N uniFE0C ; G 3319
+U 65037 ; WX 0 ; N uniFE0D ; G 3320
+U 65038 ; WX 0 ; N uniFE0E ; G 3321
+U 65039 ; WX 0 ; N uniFE0F ; G 3322
+U 65529 ; WX 0 ; N uniFFF9 ; G 3323
+U 65530 ; WX 0 ; N uniFFFA ; G 3324
+U 65531 ; WX 0 ; N uniFFFB ; G 3325
+U 65532 ; WX 0 ; N uniFFFC ; G 3326
+U 65533 ; WX 1113 ; N uniFFFD ; G 3327
+EndCharMetrics
+StartKernData
+StartKernPairs 1153
+
+KPX dollar seven -112
+KPX dollar nine -149
+KPX dollar colon -102
+KPX dollar less -102
+KPX dollar I -36
+KPX dollar W -36
+KPX dollar Y -83
+KPX dollar Z -83
+KPX dollar backslash -83
+KPX dollar questiondown -83
+KPX dollar Aacute -83
+KPX dollar Hbar -112
+KPX dollar hbar -36
+KPX dollar lacute -102
+
+KPX percent ampersand 38
+KPX percent asterisk 38
+KPX percent two 38
+KPX percent less -36
+KPX percent Egrave 38
+KPX percent Icircumflex 38
+KPX percent agrave 38
+KPX percent Ebreve 38
+KPX percent lacute -36
+
+
+KPX quotesingle nine -36
+
+
+KPX parenright dollar -120
+KPX parenright D -112
+KPX parenright H -112
+KPX parenright R -112
+KPX parenright U -36
+KPX parenright X -36
+KPX parenright cent -112
+KPX parenright sterling -112
+KPX parenright currency -112
+KPX parenright yen -112
+KPX parenright brokenbar -112
+KPX parenright section -112
+KPX parenright dieresis -112
+KPX parenright ordfeminine -112
+KPX parenright guillemotleft -112
+KPX parenright logicalnot -112
+KPX parenright sfthyphen -112
+KPX parenright acute -112
+KPX parenright mu -112
+KPX parenright paragraph -112
+KPX parenright periodcentered -112
+KPX parenright cedilla -112
+KPX parenright ordmasculine -112
+KPX parenright guillemotright -36
+KPX parenright onequarter -36
+KPX parenright onehalf -36
+KPX parenright threequarters -36
+KPX parenright Yacute -112
+KPX parenright ebreve -112
+KPX parenright edotaccent -36
+KPX parenright ecaron -36
+KPX parenright dotlessi -36
+
+
+
+KPX period dollar -83
+KPX period ampersand -55
+KPX period two -55
+KPX period eight -73
+KPX period colon -73
+KPX period less -55
+KPX period H -45
+KPX period R -45
+KPX period X -45
+KPX period backslash -92
+KPX period ordfeminine -45
+KPX period guillemotleft -45
+KPX period logicalnot -45
+KPX period sfthyphen -45
+KPX period acute -45
+KPX period mu -45
+KPX period paragraph -45
+KPX period periodcentered -45
+KPX period cedilla -45
+KPX period ordmasculine -36
+KPX period guillemotright -45
+KPX period onequarter -45
+KPX period onehalf -45
+KPX period threequarters -45
+KPX period questiondown -92
+KPX period Aacute -92
+KPX period Egrave -55
+KPX period Icircumflex -55
+KPX period Yacute -45
+KPX period Ebreve -55
+KPX period ebreve -45
+KPX period Idot -73
+KPX period dotlessi -45
+KPX period lacute -55
+
+KPX slash seven -167
+KPX slash eight -112
+KPX slash nine -243
+KPX slash colon -139
+KPX slash less -131
+KPX slash backslash -73
+KPX slash questiondown -73
+KPX slash Aacute -73
+KPX slash Hbar -167
+KPX slash Idot -112
+KPX slash lacute -131
+
+
+KPX two nine -36
+KPX two semicolon -36
+
+KPX three dollar -149
+KPX three D -55
+KPX three H -55
+KPX three R -55
+KPX three cent -55
+KPX three sterling -55
+KPX three currency -55
+KPX three yen -55
+KPX three brokenbar -55
+KPX three section -55
+KPX three dieresis -55
+KPX three ordfeminine -55
+KPX three guillemotleft -55
+KPX three logicalnot -55
+KPX three sfthyphen -55
+KPX three acute -55
+KPX three mu -55
+KPX three paragraph -55
+KPX three periodcentered -55
+KPX three cedilla -55
+KPX three ordmasculine -55
+KPX three Yacute -55
+KPX three ebreve -55
+
+
+KPX five seven -36
+KPX five nine -73
+KPX five colon -45
+KPX five less -63
+KPX five D 47
+KPX five backslash -36
+KPX five cent 47
+KPX five sterling 47
+KPX five currency 47
+KPX five yen 47
+KPX five brokenbar 47
+KPX five section 47
+KPX five dieresis 47
+KPX five ordmasculine 38
+KPX five questiondown -36
+KPX five Aacute -36
+KPX five Hbar -36
+KPX five lacute -63
+
+KPX six six -45
+KPX six Gdotaccent -45
+KPX six Gcommaaccent -45
+
+KPX seven dollar -112
+KPX seven seven -73
+KPX seven D -196
+KPX seven F -235
+KPX seven H -235
+KPX seven R -235
+KPX seven U -149
+KPX seven V -188
+KPX seven X -188
+KPX seven Z -225
+KPX seven backslash -225
+KPX seven m -149
+KPX seven braceright -149
+KPX seven cent -196
+KPX seven sterling -196
+KPX seven currency -196
+KPX seven yen -196
+KPX seven brokenbar -196
+KPX seven section -196
+KPX seven dieresis -159
+KPX seven copyright -235
+KPX seven ordfeminine -235
+KPX seven guillemotleft -235
+KPX seven logicalnot -235
+KPX seven sfthyphen -235
+KPX seven acute -235
+KPX seven mu -235
+KPX seven paragraph -235
+KPX seven periodcentered -235
+KPX seven cedilla -235
+KPX seven ordmasculine -159
+KPX seven guillemotright -188
+KPX seven onequarter -188
+KPX seven onehalf -188
+KPX seven threequarters -188
+KPX seven questiondown -225
+KPX seven Aacute -225
+KPX seven Eacute -235
+KPX seven Idieresis -235
+KPX seven Yacute -235
+KPX seven ebreve -159
+KPX seven edotaccent -149
+KPX seven ecaron -149
+KPX seven gdotaccent -188
+KPX seven gcommaaccent -188
+KPX seven Hbar -73
+KPX seven dotlessi -188
+
+KPX eight dollar -63
+
+KPX nine dollar -159
+KPX nine two -36
+KPX nine D -188
+KPX nine H -188
+KPX nine L -36
+KPX nine R -188
+KPX nine X -131
+KPX nine backslash -83
+KPX nine cent -188
+KPX nine sterling -188
+KPX nine currency -188
+KPX nine yen -188
+KPX nine brokenbar -188
+KPX nine section -188
+KPX nine dieresis -188
+KPX nine ordfeminine -188
+KPX nine guillemotleft -188
+KPX nine logicalnot -188
+KPX nine sfthyphen -188
+KPX nine acute -188
+KPX nine mu -188
+KPX nine paragraph -188
+KPX nine periodcentered -188
+KPX nine cedilla -188
+KPX nine ordmasculine -188
+KPX nine guillemotright -131
+KPX nine onequarter -131
+KPX nine onehalf -131
+KPX nine threequarters -131
+KPX nine questiondown -83
+KPX nine Aacute -83
+KPX nine Yacute -188
+KPX nine Ebreve -36
+KPX nine ebreve -188
+KPX nine dotlessi -131
+
+KPX colon dollar -131
+KPX colon D -178
+KPX colon H -167
+KPX colon L -36
+KPX colon R -167
+KPX colon U -92
+KPX colon X -83
+KPX colon backslash -45
+KPX colon cent -178
+KPX colon sterling -178
+KPX colon currency -178
+KPX colon yen -178
+KPX colon brokenbar -178
+KPX colon section -178
+KPX colon dieresis -139
+KPX colon ordfeminine -167
+KPX colon guillemotleft -167
+KPX colon logicalnot -167
+KPX colon sfthyphen -167
+KPX colon acute -167
+KPX colon mu -167
+KPX colon paragraph -167
+KPX colon periodcentered -167
+KPX colon cedilla -167
+KPX colon ordmasculine -167
+KPX colon guillemotright -83
+KPX colon onequarter -83
+KPX colon onehalf -83
+KPX colon threequarters -83
+KPX colon questiondown -45
+KPX colon Aacute -45
+KPX colon Yacute -167
+KPX colon ebreve -167
+KPX colon edotaccent -92
+KPX colon ecaron -92
+KPX colon dotlessi -83
+
+KPX semicolon dollar -73
+KPX semicolon ampersand -36
+KPX semicolon two -36
+KPX semicolon Egrave -36
+KPX semicolon Icircumflex -36
+KPX semicolon Ebreve -36
+
+KPX less dollar -131
+KPX less ampersand -36
+KPX less D -159
+KPX less H -178
+KPX less L -36
+KPX less R -178
+KPX less X -178
+KPX less cent -159
+KPX less sterling -159
+KPX less currency -159
+KPX less yen -159
+KPX less brokenbar -159
+KPX less section -159
+KPX less dieresis -159
+KPX less ordfeminine -178
+KPX less guillemotleft -178
+KPX less logicalnot -178
+KPX less sfthyphen -178
+KPX less acute -178
+KPX less mu -178
+KPX less paragraph -178
+KPX less periodcentered -178
+KPX less cedilla -178
+KPX less ordmasculine -178
+KPX less guillemotright -178
+KPX less onequarter -178
+KPX less onehalf -178
+KPX less threequarters -178
+KPX less Egrave -36
+KPX less Icircumflex -36
+KPX less Yacute -178
+KPX less ebreve -178
+KPX less dotlessi -178
+
+
+
+
+
+
+
+
+
+
+KPX m hyphen -73
+KPX m seven -149
+KPX m Hbar -149
+
+KPX braceright hyphen -73
+KPX braceright seven -149
+KPX braceright Hbar -149
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+KPX Eth nine -36
+
+
+
+KPX ucircumflex seven -167
+KPX ucircumflex eight -112
+KPX ucircumflex nine -243
+KPX ucircumflex colon -139
+KPX ucircumflex less -131
+KPX ucircumflex backslash -73
+KPX ucircumflex questiondown -73
+KPX ucircumflex Aacute -73
+KPX ucircumflex Hbar -167
+KPX ucircumflex Idot -112
+KPX ucircumflex lacute -131
+
+KPX ydieresis seven -167
+KPX ydieresis eight -112
+KPX ydieresis nine -243
+KPX ydieresis colon -139
+KPX ydieresis less -131
+KPX ydieresis backslash -73
+KPX ydieresis questiondown -73
+KPX ydieresis Aacute -73
+KPX ydieresis Hbar -167
+KPX ydieresis Idot -112
+KPX ydieresis lacute -131
+
+KPX Abreve O -241
+
+KPX abreve seven -167
+KPX abreve eight -112
+KPX abreve nine -243
+KPX abreve colon -139
+KPX abreve less -131
+KPX abreve backslash -73
+KPX abreve questiondown -73
+KPX abreve Aacute -73
+KPX abreve Hbar -167
+KPX abreve Idot -112
+KPX abreve lacute -131
+
+
+
+KPX Edotaccent seven -36
+KPX Edotaccent nine -73
+KPX Edotaccent colon -45
+KPX Edotaccent less -63
+KPX Edotaccent D 47
+KPX Edotaccent backslash -36
+KPX Edotaccent cent 47
+KPX Edotaccent sterling 47
+KPX Edotaccent currency 47
+KPX Edotaccent yen 47
+KPX Edotaccent brokenbar 47
+KPX Edotaccent section 47
+KPX Edotaccent dieresis 47
+KPX Edotaccent ordmasculine 38
+KPX Edotaccent questiondown -36
+KPX Edotaccent Aacute -36
+KPX Edotaccent Hbar -36
+KPX Edotaccent lacute -63
+
+
+KPX Ecaron seven -36
+KPX Ecaron nine -73
+KPX Ecaron colon -45
+KPX Ecaron less -63
+KPX Ecaron D 47
+KPX Ecaron backslash -36
+KPX Ecaron cent 47
+KPX Ecaron sterling 47
+KPX Ecaron currency 47
+KPX Ecaron yen 47
+KPX Ecaron brokenbar 47
+KPX Ecaron section 47
+KPX Ecaron dieresis 47
+KPX Ecaron ordmasculine 38
+KPX Ecaron questiondown -36
+KPX Ecaron Aacute -36
+KPX Ecaron Hbar -36
+KPX Ecaron lacute -63
+
+
+KPX Gdotaccent six -45
+KPX Gdotaccent Gdotaccent -45
+KPX Gdotaccent Gcommaaccent -45
+
+KPX Gcommaaccent six -45
+KPX Gcommaaccent Gdotaccent -45
+KPX Gcommaaccent Gcommaaccent -45
+
+KPX Hbar dollar -112
+KPX Hbar seven -73
+KPX Hbar D -196
+KPX Hbar F -235
+KPX Hbar H -235
+KPX Hbar R -235
+KPX Hbar U -149
+KPX Hbar V -188
+KPX Hbar X -188
+KPX Hbar Z -225
+KPX Hbar backslash -225
+KPX Hbar m -149
+KPX Hbar braceright -149
+KPX Hbar cent -196
+KPX Hbar sterling -196
+KPX Hbar currency -196
+KPX Hbar yen -196
+KPX Hbar brokenbar -196
+KPX Hbar section -196
+KPX Hbar dieresis -159
+KPX Hbar copyright -235
+KPX Hbar ordfeminine -235
+KPX Hbar guillemotleft -235
+KPX Hbar logicalnot -235
+KPX Hbar sfthyphen -235
+KPX Hbar acute -235
+KPX Hbar mu -235
+KPX Hbar paragraph -235
+KPX Hbar periodcentered -235
+KPX Hbar cedilla -235
+KPX Hbar ordmasculine -159
+KPX Hbar guillemotright -188
+KPX Hbar onequarter -188
+KPX Hbar onehalf -188
+KPX Hbar threequarters -188
+KPX Hbar questiondown -225
+KPX Hbar Aacute -225
+KPX Hbar Eacute -235
+KPX Hbar Idieresis -235
+KPX Hbar Yacute -235
+KPX Hbar ebreve -159
+KPX Hbar edotaccent -149
+KPX Hbar ecaron -149
+KPX Hbar gdotaccent -188
+KPX Hbar gcommaaccent -188
+KPX Hbar Hbar -73
+KPX Hbar dotlessi -188
+
+KPX Idot dollar -63
+
+KPX lacute dollar -131
+KPX lacute ampersand -36
+KPX lacute D -159
+KPX lacute H -178
+KPX lacute L -36
+KPX lacute R -178
+KPX lacute X -178
+KPX lacute cent -159
+KPX lacute sterling -159
+KPX lacute currency -159
+KPX lacute yen -159
+KPX lacute brokenbar -159
+KPX lacute section -159
+KPX lacute dieresis -159
+KPX lacute ordfeminine -178
+KPX lacute guillemotleft -178
+KPX lacute logicalnot -178
+KPX lacute sfthyphen -178
+KPX lacute acute -178
+KPX lacute mu -178
+KPX lacute paragraph -178
+KPX lacute periodcentered -178
+KPX lacute cedilla -178
+KPX lacute ordmasculine -178
+KPX lacute guillemotright -178
+KPX lacute onequarter -178
+KPX lacute onehalf -178
+KPX lacute threequarters -178
+KPX lacute Egrave -36
+KPX lacute Icircumflex -36
+KPX lacute Yacute -178
+KPX lacute ebreve -178
+KPX lacute dotlessi -178
+
+
+KPX uni027D dollar -282
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ttf
new file mode 100644
index 0000000..805daf2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ufm
new file mode 100644
index 0000000..e9d62b8
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif-Italic.ufm
@@ -0,0 +1,3883 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Serif
+FontSubfamily Italic
+UniqueID DejaVu Serif Italic
+FullName DejaVu Serif Italic
+Version Version 2.37
+PostScriptName DejaVuSerif-Italic
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Serif
+PreferredSubfamily Italic
+Weight Medium
+ItalicAngle -11
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -839 -347 1645 1109
+StartCharMetrics 3507
+U 32 ; WX 318 ; N space ; G 3
+U 33 ; WX 402 ; N exclam ; G 4
+U 34 ; WX 460 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 636 ; N dollar ; G 7
+U 37 ; WX 950 ; N percent ; G 8
+U 38 ; WX 890 ; N ampersand ; G 9
+U 39 ; WX 275 ; N quotesingle ; G 10
+U 40 ; WX 390 ; N parenleft ; G 11
+U 41 ; WX 390 ; N parenright ; G 12
+U 42 ; WX 500 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 318 ; N comma ; G 15
+U 45 ; WX 338 ; N hyphen ; G 16
+U 46 ; WX 318 ; N period ; G 17
+U 47 ; WX 337 ; N slash ; G 18
+U 48 ; WX 636 ; N zero ; G 19
+U 49 ; WX 636 ; N one ; G 20
+U 50 ; WX 636 ; N two ; G 21
+U 51 ; WX 636 ; N three ; G 22
+U 52 ; WX 636 ; N four ; G 23
+U 53 ; WX 636 ; N five ; G 24
+U 54 ; WX 636 ; N six ; G 25
+U 55 ; WX 636 ; N seven ; G 26
+U 56 ; WX 636 ; N eight ; G 27
+U 57 ; WX 636 ; N nine ; G 28
+U 58 ; WX 337 ; N colon ; G 29
+U 59 ; WX 337 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 536 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 722 ; N A ; G 36
+U 66 ; WX 735 ; N B ; G 37
+U 67 ; WX 765 ; N C ; G 38
+U 68 ; WX 802 ; N D ; G 39
+U 69 ; WX 730 ; N E ; G 40
+U 70 ; WX 694 ; N F ; G 41
+U 71 ; WX 799 ; N G ; G 42
+U 72 ; WX 872 ; N H ; G 43
+U 73 ; WX 395 ; N I ; G 44
+U 74 ; WX 401 ; N J ; G 45
+U 75 ; WX 747 ; N K ; G 46
+U 76 ; WX 664 ; N L ; G 47
+U 77 ; WX 1024 ; N M ; G 48
+U 78 ; WX 875 ; N N ; G 49
+U 79 ; WX 820 ; N O ; G 50
+U 80 ; WX 673 ; N P ; G 51
+U 81 ; WX 820 ; N Q ; G 52
+U 82 ; WX 753 ; N R ; G 53
+U 83 ; WX 685 ; N S ; G 54
+U 84 ; WX 667 ; N T ; G 55
+U 85 ; WX 843 ; N U ; G 56
+U 86 ; WX 722 ; N V ; G 57
+U 87 ; WX 1028 ; N W ; G 58
+U 88 ; WX 712 ; N X ; G 59
+U 89 ; WX 660 ; N Y ; G 60
+U 90 ; WX 695 ; N Z ; G 61
+U 91 ; WX 390 ; N bracketleft ; G 62
+U 92 ; WX 337 ; N backslash ; G 63
+U 93 ; WX 390 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 596 ; N a ; G 68
+U 98 ; WX 640 ; N b ; G 69
+U 99 ; WX 560 ; N c ; G 70
+U 100 ; WX 640 ; N d ; G 71
+U 101 ; WX 592 ; N e ; G 72
+U 102 ; WX 370 ; N f ; G 73
+U 103 ; WX 640 ; N g ; G 74
+U 104 ; WX 644 ; N h ; G 75
+U 105 ; WX 320 ; N i ; G 76
+U 106 ; WX 310 ; N j ; G 77
+U 107 ; WX 606 ; N k ; G 78
+U 108 ; WX 320 ; N l ; G 79
+U 109 ; WX 948 ; N m ; G 80
+U 110 ; WX 644 ; N n ; G 81
+U 111 ; WX 602 ; N o ; G 82
+U 112 ; WX 640 ; N p ; G 83
+U 113 ; WX 640 ; N q ; G 84
+U 114 ; WX 478 ; N r ; G 85
+U 115 ; WX 513 ; N s ; G 86
+U 116 ; WX 402 ; N t ; G 87
+U 117 ; WX 644 ; N u ; G 88
+U 118 ; WX 565 ; N v ; G 89
+U 119 ; WX 856 ; N w ; G 90
+U 120 ; WX 564 ; N x ; G 91
+U 121 ; WX 565 ; N y ; G 92
+U 122 ; WX 527 ; N z ; G 93
+U 123 ; WX 636 ; N braceleft ; G 94
+U 124 ; WX 337 ; N bar ; G 95
+U 125 ; WX 636 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 318 ; N nbspace ; G 98
+U 161 ; WX 402 ; N exclamdown ; G 99
+U 162 ; WX 636 ; N cent ; G 100
+U 163 ; WX 636 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 636 ; N yen ; G 103
+U 166 ; WX 337 ; N brokenbar ; G 104
+U 167 ; WX 500 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 475 ; N ordfeminine ; G 108
+U 171 ; WX 612 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 338 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 401 ; N twosuperior ; G 116
+U 179 ; WX 401 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 650 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 318 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 401 ; N onesuperior ; G 123
+U 186 ; WX 470 ; N ordmasculine ; G 124
+U 187 ; WX 612 ; N guillemotright ; G 125
+U 188 ; WX 969 ; N onequarter ; G 126
+U 189 ; WX 969 ; N onehalf ; G 127
+U 190 ; WX 969 ; N threequarters ; G 128
+U 191 ; WX 536 ; N questiondown ; G 129
+U 192 ; WX 722 ; N Agrave ; G 130
+U 193 ; WX 722 ; N Aacute ; G 131
+U 194 ; WX 722 ; N Acircumflex ; G 132
+U 195 ; WX 722 ; N Atilde ; G 133
+U 196 ; WX 722 ; N Adieresis ; G 134
+U 197 ; WX 722 ; N Aring ; G 135
+U 198 ; WX 1001 ; N AE ; G 136
+U 199 ; WX 765 ; N Ccedilla ; G 137
+U 200 ; WX 730 ; N Egrave ; G 138
+U 201 ; WX 730 ; N Eacute ; G 139
+U 202 ; WX 730 ; N Ecircumflex ; G 140
+U 203 ; WX 730 ; N Edieresis ; G 141
+U 204 ; WX 395 ; N Igrave ; G 142
+U 205 ; WX 395 ; N Iacute ; G 143
+U 206 ; WX 395 ; N Icircumflex ; G 144
+U 207 ; WX 395 ; N Idieresis ; G 145
+U 208 ; WX 807 ; N Eth ; G 146
+U 209 ; WX 875 ; N Ntilde ; G 147
+U 210 ; WX 820 ; N Ograve ; G 148
+U 211 ; WX 820 ; N Oacute ; G 149
+U 212 ; WX 820 ; N Ocircumflex ; G 150
+U 213 ; WX 820 ; N Otilde ; G 151
+U 214 ; WX 820 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 820 ; N Oslash ; G 154
+U 217 ; WX 843 ; N Ugrave ; G 155
+U 218 ; WX 843 ; N Uacute ; G 156
+U 219 ; WX 843 ; N Ucircumflex ; G 157
+U 220 ; WX 843 ; N Udieresis ; G 158
+U 221 ; WX 660 ; N Yacute ; G 159
+U 222 ; WX 676 ; N Thorn ; G 160
+U 223 ; WX 668 ; N germandbls ; G 161
+U 224 ; WX 596 ; N agrave ; G 162
+U 225 ; WX 596 ; N aacute ; G 163
+U 226 ; WX 596 ; N acircumflex ; G 164
+U 227 ; WX 596 ; N atilde ; G 165
+U 228 ; WX 596 ; N adieresis ; G 166
+U 229 ; WX 596 ; N aring ; G 167
+U 230 ; WX 940 ; N ae ; G 168
+U 231 ; WX 560 ; N ccedilla ; G 169
+U 232 ; WX 592 ; N egrave ; G 170
+U 233 ; WX 592 ; N eacute ; G 171
+U 234 ; WX 592 ; N ecircumflex ; G 172
+U 235 ; WX 592 ; N edieresis ; G 173
+U 236 ; WX 320 ; N igrave ; G 174
+U 237 ; WX 320 ; N iacute ; G 175
+U 238 ; WX 320 ; N icircumflex ; G 176
+U 239 ; WX 320 ; N idieresis ; G 177
+U 240 ; WX 602 ; N eth ; G 178
+U 241 ; WX 644 ; N ntilde ; G 179
+U 242 ; WX 602 ; N ograve ; G 180
+U 243 ; WX 602 ; N oacute ; G 181
+U 244 ; WX 602 ; N ocircumflex ; G 182
+U 245 ; WX 602 ; N otilde ; G 183
+U 246 ; WX 602 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 602 ; N oslash ; G 186
+U 249 ; WX 644 ; N ugrave ; G 187
+U 250 ; WX 644 ; N uacute ; G 188
+U 251 ; WX 644 ; N ucircumflex ; G 189
+U 252 ; WX 644 ; N udieresis ; G 190
+U 253 ; WX 565 ; N yacute ; G 191
+U 254 ; WX 640 ; N thorn ; G 192
+U 255 ; WX 565 ; N ydieresis ; G 193
+U 256 ; WX 722 ; N Amacron ; G 194
+U 257 ; WX 596 ; N amacron ; G 195
+U 258 ; WX 722 ; N Abreve ; G 196
+U 259 ; WX 596 ; N abreve ; G 197
+U 260 ; WX 722 ; N Aogonek ; G 198
+U 261 ; WX 596 ; N aogonek ; G 199
+U 262 ; WX 765 ; N Cacute ; G 200
+U 263 ; WX 560 ; N cacute ; G 201
+U 264 ; WX 765 ; N Ccircumflex ; G 202
+U 265 ; WX 560 ; N ccircumflex ; G 203
+U 266 ; WX 765 ; N Cdotaccent ; G 204
+U 267 ; WX 560 ; N cdotaccent ; G 205
+U 268 ; WX 765 ; N Ccaron ; G 206
+U 269 ; WX 560 ; N ccaron ; G 207
+U 270 ; WX 802 ; N Dcaron ; G 208
+U 271 ; WX 640 ; N dcaron ; G 209
+U 272 ; WX 807 ; N Dcroat ; G 210
+U 273 ; WX 640 ; N dmacron ; G 211
+U 274 ; WX 730 ; N Emacron ; G 212
+U 275 ; WX 592 ; N emacron ; G 213
+U 276 ; WX 730 ; N Ebreve ; G 214
+U 277 ; WX 592 ; N ebreve ; G 215
+U 278 ; WX 730 ; N Edotaccent ; G 216
+U 279 ; WX 592 ; N edotaccent ; G 217
+U 280 ; WX 730 ; N Eogonek ; G 218
+U 281 ; WX 592 ; N eogonek ; G 219
+U 282 ; WX 730 ; N Ecaron ; G 220
+U 283 ; WX 592 ; N ecaron ; G 221
+U 284 ; WX 799 ; N Gcircumflex ; G 222
+U 285 ; WX 640 ; N gcircumflex ; G 223
+U 286 ; WX 799 ; N Gbreve ; G 224
+U 287 ; WX 640 ; N gbreve ; G 225
+U 288 ; WX 799 ; N Gdotaccent ; G 226
+U 289 ; WX 640 ; N gdotaccent ; G 227
+U 290 ; WX 799 ; N Gcommaaccent ; G 228
+U 291 ; WX 640 ; N gcommaaccent ; G 229
+U 292 ; WX 872 ; N Hcircumflex ; G 230
+U 293 ; WX 644 ; N hcircumflex ; G 231
+U 294 ; WX 872 ; N Hbar ; G 232
+U 295 ; WX 644 ; N hbar ; G 233
+U 296 ; WX 395 ; N Itilde ; G 234
+U 297 ; WX 320 ; N itilde ; G 235
+U 298 ; WX 395 ; N Imacron ; G 236
+U 299 ; WX 320 ; N imacron ; G 237
+U 300 ; WX 395 ; N Ibreve ; G 238
+U 301 ; WX 320 ; N ibreve ; G 239
+U 302 ; WX 395 ; N Iogonek ; G 240
+U 303 ; WX 320 ; N iogonek ; G 241
+U 304 ; WX 395 ; N Idot ; G 242
+U 305 ; WX 320 ; N dotlessi ; G 243
+U 306 ; WX 801 ; N IJ ; G 244
+U 307 ; WX 533 ; N ij ; G 245
+U 308 ; WX 401 ; N Jcircumflex ; G 246
+U 309 ; WX 310 ; N jcircumflex ; G 247
+U 310 ; WX 747 ; N Kcommaaccent ; G 248
+U 311 ; WX 606 ; N kcommaaccent ; G 249
+U 312 ; WX 606 ; N kgreenlandic ; G 250
+U 313 ; WX 664 ; N Lacute ; G 251
+U 314 ; WX 320 ; N lacute ; G 252
+U 315 ; WX 664 ; N Lcommaaccent ; G 253
+U 316 ; WX 320 ; N lcommaaccent ; G 254
+U 317 ; WX 664 ; N Lcaron ; G 255
+U 318 ; WX 400 ; N lcaron ; G 256
+U 319 ; WX 671 ; N Ldot ; G 257
+U 320 ; WX 465 ; N ldot ; G 258
+U 321 ; WX 669 ; N Lslash ; G 259
+U 322 ; WX 324 ; N lslash ; G 260
+U 323 ; WX 875 ; N Nacute ; G 261
+U 324 ; WX 644 ; N nacute ; G 262
+U 325 ; WX 875 ; N Ncommaaccent ; G 263
+U 326 ; WX 644 ; N ncommaaccent ; G 264
+U 327 ; WX 875 ; N Ncaron ; G 265
+U 328 ; WX 644 ; N ncaron ; G 266
+U 329 ; WX 866 ; N napostrophe ; G 267
+U 330 ; WX 843 ; N Eng ; G 268
+U 331 ; WX 644 ; N eng ; G 269
+U 332 ; WX 820 ; N Omacron ; G 270
+U 333 ; WX 602 ; N omacron ; G 271
+U 334 ; WX 820 ; N Obreve ; G 272
+U 335 ; WX 602 ; N obreve ; G 273
+U 336 ; WX 820 ; N Ohungarumlaut ; G 274
+U 337 ; WX 602 ; N ohungarumlaut ; G 275
+U 338 ; WX 1137 ; N OE ; G 276
+U 339 ; WX 989 ; N oe ; G 277
+U 340 ; WX 753 ; N Racute ; G 278
+U 341 ; WX 478 ; N racute ; G 279
+U 342 ; WX 753 ; N Rcommaaccent ; G 280
+U 343 ; WX 478 ; N rcommaaccent ; G 281
+U 344 ; WX 753 ; N Rcaron ; G 282
+U 345 ; WX 478 ; N rcaron ; G 283
+U 346 ; WX 685 ; N Sacute ; G 284
+U 347 ; WX 513 ; N sacute ; G 285
+U 348 ; WX 685 ; N Scircumflex ; G 286
+U 349 ; WX 513 ; N scircumflex ; G 287
+U 350 ; WX 685 ; N Scedilla ; G 288
+U 351 ; WX 513 ; N scedilla ; G 289
+U 352 ; WX 685 ; N Scaron ; G 290
+U 353 ; WX 513 ; N scaron ; G 291
+U 354 ; WX 667 ; N Tcommaaccent ; G 292
+U 355 ; WX 402 ; N tcommaaccent ; G 293
+U 356 ; WX 667 ; N Tcaron ; G 294
+U 357 ; WX 402 ; N tcaron ; G 295
+U 358 ; WX 667 ; N Tbar ; G 296
+U 359 ; WX 402 ; N tbar ; G 297
+U 360 ; WX 843 ; N Utilde ; G 298
+U 361 ; WX 644 ; N utilde ; G 299
+U 362 ; WX 843 ; N Umacron ; G 300
+U 363 ; WX 644 ; N umacron ; G 301
+U 364 ; WX 843 ; N Ubreve ; G 302
+U 365 ; WX 644 ; N ubreve ; G 303
+U 366 ; WX 843 ; N Uring ; G 304
+U 367 ; WX 644 ; N uring ; G 305
+U 368 ; WX 843 ; N Uhungarumlaut ; G 306
+U 369 ; WX 644 ; N uhungarumlaut ; G 307
+U 370 ; WX 843 ; N Uogonek ; G 308
+U 371 ; WX 644 ; N uogonek ; G 309
+U 372 ; WX 1028 ; N Wcircumflex ; G 310
+U 373 ; WX 856 ; N wcircumflex ; G 311
+U 374 ; WX 660 ; N Ycircumflex ; G 312
+U 375 ; WX 565 ; N ycircumflex ; G 313
+U 376 ; WX 660 ; N Ydieresis ; G 314
+U 377 ; WX 695 ; N Zacute ; G 315
+U 378 ; WX 527 ; N zacute ; G 316
+U 379 ; WX 695 ; N Zdotaccent ; G 317
+U 380 ; WX 527 ; N zdotaccent ; G 318
+U 381 ; WX 695 ; N Zcaron ; G 319
+U 382 ; WX 527 ; N zcaron ; G 320
+U 383 ; WX 370 ; N longs ; G 321
+U 384 ; WX 640 ; N uni0180 ; G 322
+U 385 ; WX 735 ; N uni0181 ; G 323
+U 386 ; WX 735 ; N uni0182 ; G 324
+U 387 ; WX 640 ; N uni0183 ; G 325
+U 388 ; WX 735 ; N uni0184 ; G 326
+U 389 ; WX 640 ; N uni0185 ; G 327
+U 390 ; WX 765 ; N uni0186 ; G 328
+U 391 ; WX 765 ; N uni0187 ; G 329
+U 392 ; WX 560 ; N uni0188 ; G 330
+U 393 ; WX 807 ; N uni0189 ; G 331
+U 394 ; WX 802 ; N uni018A ; G 332
+U 395 ; WX 735 ; N uni018B ; G 333
+U 396 ; WX 640 ; N uni018C ; G 334
+U 397 ; WX 602 ; N uni018D ; G 335
+U 398 ; WX 730 ; N uni018E ; G 336
+U 399 ; WX 820 ; N uni018F ; G 337
+U 400 ; WX 623 ; N uni0190 ; G 338
+U 401 ; WX 694 ; N uni0191 ; G 339
+U 402 ; WX 370 ; N florin ; G 340
+U 403 ; WX 799 ; N uni0193 ; G 341
+U 404 ; WX 712 ; N uni0194 ; G 342
+U 405 ; WX 932 ; N uni0195 ; G 343
+U 406 ; WX 395 ; N uni0196 ; G 344
+U 407 ; WX 395 ; N uni0197 ; G 345
+U 408 ; WX 747 ; N uni0198 ; G 346
+U 409 ; WX 606 ; N uni0199 ; G 347
+U 410 ; WX 320 ; N uni019A ; G 348
+U 411 ; WX 634 ; N uni019B ; G 349
+U 412 ; WX 948 ; N uni019C ; G 350
+U 413 ; WX 875 ; N uni019D ; G 351
+U 414 ; WX 644 ; N uni019E ; G 352
+U 415 ; WX 820 ; N uni019F ; G 353
+U 416 ; WX 820 ; N Ohorn ; G 354
+U 417 ; WX 602 ; N ohorn ; G 355
+U 418 ; WX 1040 ; N uni01A2 ; G 356
+U 419 ; WX 807 ; N uni01A3 ; G 357
+U 420 ; WX 673 ; N uni01A4 ; G 358
+U 421 ; WX 640 ; N uni01A5 ; G 359
+U 422 ; WX 753 ; N uni01A6 ; G 360
+U 423 ; WX 685 ; N uni01A7 ; G 361
+U 424 ; WX 513 ; N uni01A8 ; G 362
+U 425 ; WX 707 ; N uni01A9 ; G 363
+U 426 ; WX 324 ; N uni01AA ; G 364
+U 427 ; WX 402 ; N uni01AB ; G 365
+U 428 ; WX 667 ; N uni01AC ; G 366
+U 429 ; WX 402 ; N uni01AD ; G 367
+U 430 ; WX 667 ; N uni01AE ; G 368
+U 431 ; WX 843 ; N Uhorn ; G 369
+U 432 ; WX 644 ; N uhorn ; G 370
+U 433 ; WX 829 ; N uni01B1 ; G 371
+U 434 ; WX 760 ; N uni01B2 ; G 372
+U 435 ; WX 738 ; N uni01B3 ; G 373
+U 436 ; WX 745 ; N uni01B4 ; G 374
+U 437 ; WX 695 ; N uni01B5 ; G 375
+U 438 ; WX 527 ; N uni01B6 ; G 376
+U 439 ; WX 564 ; N uni01B7 ; G 377
+U 440 ; WX 564 ; N uni01B8 ; G 378
+U 441 ; WX 564 ; N uni01B9 ; G 379
+U 442 ; WX 564 ; N uni01BA ; G 380
+U 443 ; WX 636 ; N uni01BB ; G 381
+U 444 ; WX 687 ; N uni01BC ; G 382
+U 445 ; WX 564 ; N uni01BD ; G 383
+U 446 ; WX 536 ; N uni01BE ; G 384
+U 447 ; WX 635 ; N uni01BF ; G 385
+U 448 ; WX 295 ; N uni01C0 ; G 386
+U 449 ; WX 492 ; N uni01C1 ; G 387
+U 450 ; WX 459 ; N uni01C2 ; G 388
+U 451 ; WX 295 ; N uni01C3 ; G 389
+U 452 ; WX 1497 ; N uni01C4 ; G 390
+U 453 ; WX 1329 ; N uni01C5 ; G 391
+U 454 ; WX 1167 ; N uni01C6 ; G 392
+U 455 ; WX 1065 ; N uni01C7 ; G 393
+U 456 ; WX 974 ; N uni01C8 ; G 394
+U 457 ; WX 630 ; N uni01C9 ; G 395
+U 458 ; WX 1276 ; N uni01CA ; G 396
+U 459 ; WX 1185 ; N uni01CB ; G 397
+U 460 ; WX 954 ; N uni01CC ; G 398
+U 461 ; WX 722 ; N uni01CD ; G 399
+U 462 ; WX 596 ; N uni01CE ; G 400
+U 463 ; WX 395 ; N uni01CF ; G 401
+U 464 ; WX 320 ; N uni01D0 ; G 402
+U 465 ; WX 820 ; N uni01D1 ; G 403
+U 466 ; WX 602 ; N uni01D2 ; G 404
+U 467 ; WX 843 ; N uni01D3 ; G 405
+U 468 ; WX 644 ; N uni01D4 ; G 406
+U 469 ; WX 843 ; N uni01D5 ; G 407
+U 470 ; WX 644 ; N uni01D6 ; G 408
+U 471 ; WX 843 ; N uni01D7 ; G 409
+U 472 ; WX 644 ; N uni01D8 ; G 410
+U 473 ; WX 843 ; N uni01D9 ; G 411
+U 474 ; WX 644 ; N uni01DA ; G 412
+U 475 ; WX 843 ; N uni01DB ; G 413
+U 476 ; WX 644 ; N uni01DC ; G 414
+U 477 ; WX 592 ; N uni01DD ; G 415
+U 478 ; WX 722 ; N uni01DE ; G 416
+U 479 ; WX 596 ; N uni01DF ; G 417
+U 480 ; WX 722 ; N uni01E0 ; G 418
+U 481 ; WX 596 ; N uni01E1 ; G 419
+U 482 ; WX 1001 ; N uni01E2 ; G 420
+U 483 ; WX 940 ; N uni01E3 ; G 421
+U 484 ; WX 848 ; N uni01E4 ; G 422
+U 485 ; WX 640 ; N uni01E5 ; G 423
+U 486 ; WX 799 ; N Gcaron ; G 424
+U 487 ; WX 640 ; N gcaron ; G 425
+U 488 ; WX 747 ; N uni01E8 ; G 426
+U 489 ; WX 606 ; N uni01E9 ; G 427
+U 490 ; WX 820 ; N uni01EA ; G 428
+U 491 ; WX 602 ; N uni01EB ; G 429
+U 492 ; WX 820 ; N uni01EC ; G 430
+U 493 ; WX 602 ; N uni01ED ; G 431
+U 494 ; WX 564 ; N uni01EE ; G 432
+U 495 ; WX 564 ; N uni01EF ; G 433
+U 496 ; WX 320 ; N uni01F0 ; G 434
+U 497 ; WX 1497 ; N uni01F1 ; G 435
+U 498 ; WX 1329 ; N uni01F2 ; G 436
+U 499 ; WX 1167 ; N uni01F3 ; G 437
+U 500 ; WX 799 ; N uni01F4 ; G 438
+U 501 ; WX 640 ; N uni01F5 ; G 439
+U 502 ; WX 1154 ; N uni01F6 ; G 440
+U 503 ; WX 707 ; N uni01F7 ; G 441
+U 504 ; WX 875 ; N uni01F8 ; G 442
+U 505 ; WX 644 ; N uni01F9 ; G 443
+U 506 ; WX 722 ; N Aringacute ; G 444
+U 507 ; WX 596 ; N aringacute ; G 445
+U 508 ; WX 1001 ; N AEacute ; G 446
+U 509 ; WX 940 ; N aeacute ; G 447
+U 510 ; WX 820 ; N Oslashacute ; G 448
+U 511 ; WX 602 ; N oslashacute ; G 449
+U 512 ; WX 722 ; N uni0200 ; G 450
+U 513 ; WX 596 ; N uni0201 ; G 451
+U 514 ; WX 722 ; N uni0202 ; G 452
+U 515 ; WX 596 ; N uni0203 ; G 453
+U 516 ; WX 730 ; N uni0204 ; G 454
+U 517 ; WX 592 ; N uni0205 ; G 455
+U 518 ; WX 730 ; N uni0206 ; G 456
+U 519 ; WX 592 ; N uni0207 ; G 457
+U 520 ; WX 395 ; N uni0208 ; G 458
+U 521 ; WX 320 ; N uni0209 ; G 459
+U 522 ; WX 395 ; N uni020A ; G 460
+U 523 ; WX 320 ; N uni020B ; G 461
+U 524 ; WX 820 ; N uni020C ; G 462
+U 525 ; WX 602 ; N uni020D ; G 463
+U 526 ; WX 820 ; N uni020E ; G 464
+U 527 ; WX 602 ; N uni020F ; G 465
+U 528 ; WX 753 ; N uni0210 ; G 466
+U 529 ; WX 478 ; N uni0211 ; G 467
+U 530 ; WX 753 ; N uni0212 ; G 468
+U 531 ; WX 478 ; N uni0213 ; G 469
+U 532 ; WX 843 ; N uni0214 ; G 470
+U 533 ; WX 644 ; N uni0215 ; G 471
+U 534 ; WX 843 ; N uni0216 ; G 472
+U 535 ; WX 644 ; N uni0217 ; G 473
+U 536 ; WX 685 ; N Scommaaccent ; G 474
+U 537 ; WX 513 ; N scommaaccent ; G 475
+U 538 ; WX 667 ; N uni021A ; G 476
+U 539 ; WX 402 ; N uni021B ; G 477
+U 540 ; WX 627 ; N uni021C ; G 478
+U 541 ; WX 521 ; N uni021D ; G 479
+U 542 ; WX 872 ; N uni021E ; G 480
+U 543 ; WX 644 ; N uni021F ; G 481
+U 544 ; WX 843 ; N uni0220 ; G 482
+U 545 ; WX 814 ; N uni0221 ; G 483
+U 546 ; WX 572 ; N uni0222 ; G 484
+U 547 ; WX 552 ; N uni0223 ; G 485
+U 548 ; WX 695 ; N uni0224 ; G 486
+U 549 ; WX 527 ; N uni0225 ; G 487
+U 550 ; WX 722 ; N uni0226 ; G 488
+U 551 ; WX 596 ; N uni0227 ; G 489
+U 552 ; WX 730 ; N uni0228 ; G 490
+U 553 ; WX 592 ; N uni0229 ; G 491
+U 554 ; WX 820 ; N uni022A ; G 492
+U 555 ; WX 602 ; N uni022B ; G 493
+U 556 ; WX 820 ; N uni022C ; G 494
+U 557 ; WX 602 ; N uni022D ; G 495
+U 558 ; WX 820 ; N uni022E ; G 496
+U 559 ; WX 602 ; N uni022F ; G 497
+U 560 ; WX 820 ; N uni0230 ; G 498
+U 561 ; WX 602 ; N uni0231 ; G 499
+U 562 ; WX 660 ; N uni0232 ; G 500
+U 563 ; WX 565 ; N uni0233 ; G 501
+U 564 ; WX 500 ; N uni0234 ; G 502
+U 565 ; WX 832 ; N uni0235 ; G 503
+U 566 ; WX 494 ; N uni0236 ; G 504
+U 567 ; WX 310 ; N dotlessj ; G 505
+U 568 ; WX 960 ; N uni0238 ; G 506
+U 569 ; WX 960 ; N uni0239 ; G 507
+U 570 ; WX 722 ; N uni023A ; G 508
+U 571 ; WX 765 ; N uni023B ; G 509
+U 572 ; WX 560 ; N uni023C ; G 510
+U 573 ; WX 664 ; N uni023D ; G 511
+U 574 ; WX 667 ; N uni023E ; G 512
+U 575 ; WX 513 ; N uni023F ; G 513
+U 576 ; WX 527 ; N uni0240 ; G 514
+U 577 ; WX 583 ; N uni0241 ; G 515
+U 578 ; WX 464 ; N uni0242 ; G 516
+U 579 ; WX 735 ; N uni0243 ; G 517
+U 580 ; WX 843 ; N uni0244 ; G 518
+U 581 ; WX 722 ; N uni0245 ; G 519
+U 582 ; WX 730 ; N uni0246 ; G 520
+U 583 ; WX 592 ; N uni0247 ; G 521
+U 584 ; WX 401 ; N uni0248 ; G 522
+U 585 ; WX 315 ; N uni0249 ; G 523
+U 586 ; WX 782 ; N uni024A ; G 524
+U 587 ; WX 640 ; N uni024B ; G 525
+U 588 ; WX 753 ; N uni024C ; G 526
+U 589 ; WX 478 ; N uni024D ; G 527
+U 590 ; WX 660 ; N uni024E ; G 528
+U 591 ; WX 565 ; N uni024F ; G 529
+U 592 ; WX 596 ; N uni0250 ; G 530
+U 593 ; WX 675 ; N uni0251 ; G 531
+U 594 ; WX 675 ; N uni0252 ; G 532
+U 595 ; WX 640 ; N uni0253 ; G 533
+U 596 ; WX 560 ; N uni0254 ; G 534
+U 597 ; WX 560 ; N uni0255 ; G 535
+U 598 ; WX 647 ; N uni0256 ; G 536
+U 599 ; WX 683 ; N uni0257 ; G 537
+U 600 ; WX 592 ; N uni0258 ; G 538
+U 601 ; WX 592 ; N uni0259 ; G 539
+U 602 ; WX 843 ; N uni025A ; G 540
+U 603 ; WX 537 ; N uni025B ; G 541
+U 604 ; WX 509 ; N uni025C ; G 542
+U 605 ; WX 773 ; N uni025D ; G 543
+U 606 ; WX 613 ; N uni025E ; G 544
+U 607 ; WX 315 ; N uni025F ; G 545
+U 608 ; WX 683 ; N uni0260 ; G 546
+U 609 ; WX 640 ; N uni0261 ; G 547
+U 610 ; WX 580 ; N uni0262 ; G 548
+U 611 ; WX 599 ; N uni0263 ; G 549
+U 612 ; WX 564 ; N uni0264 ; G 550
+U 613 ; WX 644 ; N uni0265 ; G 551
+U 614 ; WX 644 ; N uni0266 ; G 552
+U 615 ; WX 644 ; N uni0267 ; G 553
+U 616 ; WX 320 ; N uni0268 ; G 554
+U 617 ; WX 392 ; N uni0269 ; G 555
+U 618 ; WX 320 ; N uni026A ; G 556
+U 619 ; WX 380 ; N uni026B ; G 557
+U 620 ; WX 454 ; N uni026C ; G 558
+U 621 ; WX 363 ; N uni026D ; G 559
+U 622 ; WX 704 ; N uni026E ; G 560
+U 623 ; WX 948 ; N uni026F ; G 561
+U 624 ; WX 948 ; N uni0270 ; G 562
+U 625 ; WX 948 ; N uni0271 ; G 563
+U 626 ; WX 644 ; N uni0272 ; G 564
+U 627 ; WX 694 ; N uni0273 ; G 565
+U 628 ; WX 646 ; N uni0274 ; G 566
+U 629 ; WX 602 ; N uni0275 ; G 567
+U 630 ; WX 790 ; N uni0276 ; G 568
+U 631 ; WX 821 ; N uni0277 ; G 569
+U 632 ; WX 692 ; N uni0278 ; G 570
+U 633 ; WX 501 ; N uni0279 ; G 571
+U 634 ; WX 501 ; N uni027A ; G 572
+U 635 ; WX 551 ; N uni027B ; G 573
+U 636 ; WX 478 ; N uni027C ; G 574
+U 637 ; WX 478 ; N uni027D ; G 575
+U 638 ; WX 453 ; N uni027E ; G 576
+U 639 ; WX 453 ; N uni027F ; G 577
+U 640 ; WX 581 ; N uni0280 ; G 578
+U 641 ; WX 581 ; N uni0281 ; G 579
+U 642 ; WX 513 ; N uni0282 ; G 580
+U 643 ; WX 271 ; N uni0283 ; G 581
+U 644 ; WX 370 ; N uni0284 ; G 582
+U 645 ; WX 487 ; N uni0285 ; G 583
+U 646 ; WX 324 ; N uni0286 ; G 584
+U 647 ; WX 402 ; N uni0287 ; G 585
+U 648 ; WX 402 ; N uni0288 ; G 586
+U 649 ; WX 644 ; N uni0289 ; G 587
+U 650 ; WX 620 ; N uni028A ; G 588
+U 651 ; WX 608 ; N uni028B ; G 589
+U 652 ; WX 565 ; N uni028C ; G 590
+U 653 ; WX 856 ; N uni028D ; G 591
+U 654 ; WX 565 ; N uni028E ; G 592
+U 655 ; WX 655 ; N uni028F ; G 593
+U 656 ; WX 597 ; N uni0290 ; G 594
+U 657 ; WX 560 ; N uni0291 ; G 595
+U 658 ; WX 564 ; N uni0292 ; G 596
+U 659 ; WX 560 ; N uni0293 ; G 597
+U 660 ; WX 536 ; N uni0294 ; G 598
+U 661 ; WX 536 ; N uni0295 ; G 599
+U 662 ; WX 536 ; N uni0296 ; G 600
+U 663 ; WX 420 ; N uni0297 ; G 601
+U 664 ; WX 820 ; N uni0298 ; G 602
+U 665 ; WX 563 ; N uni0299 ; G 603
+U 666 ; WX 613 ; N uni029A ; G 604
+U 667 ; WX 660 ; N uni029B ; G 605
+U 668 ; WX 667 ; N uni029C ; G 606
+U 669 ; WX 366 ; N uni029D ; G 607
+U 670 ; WX 606 ; N uni029E ; G 608
+U 671 ; WX 543 ; N uni029F ; G 609
+U 672 ; WX 683 ; N uni02A0 ; G 610
+U 673 ; WX 536 ; N uni02A1 ; G 611
+U 674 ; WX 536 ; N uni02A2 ; G 612
+U 675 ; WX 996 ; N uni02A3 ; G 613
+U 676 ; WX 1033 ; N uni02A4 ; G 614
+U 677 ; WX 998 ; N uni02A5 ; G 615
+U 678 ; WX 823 ; N uni02A6 ; G 616
+U 679 ; WX 598 ; N uni02A7 ; G 617
+U 680 ; WX 825 ; N uni02A8 ; G 618
+U 681 ; WX 894 ; N uni02A9 ; G 619
+U 682 ; WX 725 ; N uni02AA ; G 620
+U 683 ; WX 676 ; N uni02AB ; G 621
+U 684 ; WX 598 ; N uni02AC ; G 622
+U 685 ; WX 443 ; N uni02AD ; G 623
+U 686 ; WX 781 ; N uni02AE ; G 624
+U 687 ; WX 767 ; N uni02AF ; G 625
+U 688 ; WX 433 ; N uni02B0 ; G 626
+U 689 ; WX 430 ; N uni02B1 ; G 627
+U 690 ; WX 264 ; N uni02B2 ; G 628
+U 691 ; WX 347 ; N uni02B3 ; G 629
+U 692 ; WX 347 ; N uni02B4 ; G 630
+U 693 ; WX 430 ; N uni02B5 ; G 631
+U 694 ; WX 392 ; N uni02B6 ; G 632
+U 695 ; WX 539 ; N uni02B7 ; G 633
+U 696 ; WX 355 ; N uni02B8 ; G 634
+U 697 ; WX 278 ; N uni02B9 ; G 635
+U 698 ; WX 460 ; N uni02BA ; G 636
+U 699 ; WX 318 ; N uni02BB ; G 637
+U 700 ; WX 318 ; N uni02BC ; G 638
+U 701 ; WX 318 ; N uni02BD ; G 639
+U 702 ; WX 307 ; N uni02BE ; G 640
+U 703 ; WX 307 ; N uni02BF ; G 641
+U 704 ; WX 280 ; N uni02C0 ; G 642
+U 705 ; WX 281 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 282 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 282 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 720 ; WX 337 ; N uni02D0 ; G 656
+U 721 ; WX 337 ; N uni02D1 ; G 657
+U 722 ; WX 307 ; N uni02D2 ; G 658
+U 723 ; WX 307 ; N uni02D3 ; G 659
+U 726 ; WX 392 ; N uni02D6 ; G 660
+U 727 ; WX 392 ; N uni02D7 ; G 661
+U 728 ; WX 500 ; N breve ; G 662
+U 729 ; WX 500 ; N dotaccent ; G 663
+U 730 ; WX 500 ; N ring ; G 664
+U 731 ; WX 500 ; N ogonek ; G 665
+U 732 ; WX 500 ; N tilde ; G 666
+U 733 ; WX 500 ; N hungarumlaut ; G 667
+U 734 ; WX 417 ; N uni02DE ; G 668
+U 736 ; WX 377 ; N uni02E0 ; G 669
+U 737 ; WX 243 ; N uni02E1 ; G 670
+U 738 ; WX 337 ; N uni02E2 ; G 671
+U 739 ; WX 355 ; N uni02E3 ; G 672
+U 740 ; WX 281 ; N uni02E4 ; G 673
+U 741 ; WX 493 ; N uni02E5 ; G 674
+U 742 ; WX 493 ; N uni02E6 ; G 675
+U 743 ; WX 493 ; N uni02E7 ; G 676
+U 744 ; WX 493 ; N uni02E8 ; G 677
+U 745 ; WX 493 ; N uni02E9 ; G 678
+U 748 ; WX 500 ; N uni02EC ; G 679
+U 750 ; WX 484 ; N uni02EE ; G 680
+U 751 ; WX 500 ; N uni02EF ; G 681
+U 752 ; WX 500 ; N uni02F0 ; G 682
+U 755 ; WX 500 ; N uni02F3 ; G 683
+U 759 ; WX 500 ; N uni02F7 ; G 684
+U 768 ; WX 0 ; N gravecomb ; G 685
+U 769 ; WX 0 ; N acutecomb ; G 686
+U 770 ; WX 0 ; N uni0302 ; G 687
+U 771 ; WX 0 ; N tildecomb ; G 688
+U 772 ; WX 0 ; N uni0304 ; G 689
+U 773 ; WX 0 ; N uni0305 ; G 690
+U 774 ; WX 0 ; N uni0306 ; G 691
+U 775 ; WX 0 ; N uni0307 ; G 692
+U 776 ; WX 0 ; N uni0308 ; G 693
+U 777 ; WX 0 ; N hookabovecomb ; G 694
+U 778 ; WX 0 ; N uni030A ; G 695
+U 779 ; WX 0 ; N uni030B ; G 696
+U 780 ; WX 0 ; N uni030C ; G 697
+U 781 ; WX 0 ; N uni030D ; G 698
+U 782 ; WX 0 ; N uni030E ; G 699
+U 783 ; WX 0 ; N uni030F ; G 700
+U 784 ; WX 0 ; N uni0310 ; G 701
+U 785 ; WX 0 ; N uni0311 ; G 702
+U 786 ; WX 0 ; N uni0312 ; G 703
+U 787 ; WX 0 ; N uni0313 ; G 704
+U 788 ; WX 0 ; N uni0314 ; G 705
+U 789 ; WX 0 ; N uni0315 ; G 706
+U 790 ; WX 0 ; N uni0316 ; G 707
+U 791 ; WX 0 ; N uni0317 ; G 708
+U 792 ; WX 0 ; N uni0318 ; G 709
+U 793 ; WX 0 ; N uni0319 ; G 710
+U 794 ; WX 0 ; N uni031A ; G 711
+U 795 ; WX 0 ; N uni031B ; G 712
+U 796 ; WX 0 ; N uni031C ; G 713
+U 797 ; WX 0 ; N uni031D ; G 714
+U 798 ; WX 0 ; N uni031E ; G 715
+U 799 ; WX 0 ; N uni031F ; G 716
+U 800 ; WX 0 ; N uni0320 ; G 717
+U 801 ; WX 0 ; N uni0321 ; G 718
+U 802 ; WX 0 ; N uni0322 ; G 719
+U 803 ; WX 0 ; N dotbelowcomb ; G 720
+U 804 ; WX 0 ; N uni0324 ; G 721
+U 805 ; WX 0 ; N uni0325 ; G 722
+U 806 ; WX 0 ; N uni0326 ; G 723
+U 807 ; WX 0 ; N uni0327 ; G 724
+U 808 ; WX 0 ; N uni0328 ; G 725
+U 809 ; WX 0 ; N uni0329 ; G 726
+U 810 ; WX 0 ; N uni032A ; G 727
+U 811 ; WX 0 ; N uni032B ; G 728
+U 812 ; WX 0 ; N uni032C ; G 729
+U 813 ; WX 0 ; N uni032D ; G 730
+U 814 ; WX 0 ; N uni032E ; G 731
+U 815 ; WX 0 ; N uni032F ; G 732
+U 816 ; WX 0 ; N uni0330 ; G 733
+U 817 ; WX 0 ; N uni0331 ; G 734
+U 818 ; WX 0 ; N uni0332 ; G 735
+U 819 ; WX 0 ; N uni0333 ; G 736
+U 820 ; WX 0 ; N uni0334 ; G 737
+U 821 ; WX 0 ; N uni0335 ; G 738
+U 822 ; WX 0 ; N uni0336 ; G 739
+U 823 ; WX 0 ; N uni0337 ; G 740
+U 824 ; WX 0 ; N uni0338 ; G 741
+U 825 ; WX 0 ; N uni0339 ; G 742
+U 826 ; WX 0 ; N uni033A ; G 743
+U 827 ; WX 0 ; N uni033B ; G 744
+U 828 ; WX 0 ; N uni033C ; G 745
+U 829 ; WX 0 ; N uni033D ; G 746
+U 830 ; WX 0 ; N uni033E ; G 747
+U 831 ; WX 0 ; N uni033F ; G 748
+U 835 ; WX 0 ; N uni0343 ; G 749
+U 847 ; WX 0 ; N uni034F ; G 750
+U 856 ; WX 0 ; N uni0358 ; G 751
+U 864 ; WX 0 ; N uni0360 ; G 752
+U 865 ; WX 0 ; N uni0361 ; G 753
+U 880 ; WX 740 ; N uni0370 ; G 754
+U 881 ; WX 531 ; N uni0371 ; G 755
+U 882 ; WX 667 ; N uni0372 ; G 756
+U 883 ; WX 553 ; N uni0373 ; G 757
+U 884 ; WX 278 ; N uni0374 ; G 758
+U 885 ; WX 278 ; N uni0375 ; G 759
+U 886 ; WX 875 ; N uni0376 ; G 760
+U 887 ; WX 667 ; N uni0377 ; G 761
+U 890 ; WX 500 ; N uni037A ; G 762
+U 891 ; WX 560 ; N uni037B ; G 763
+U 892 ; WX 560 ; N uni037C ; G 764
+U 893 ; WX 560 ; N uni037D ; G 765
+U 894 ; WX 337 ; N uni037E ; G 766
+U 895 ; WX 401 ; N uni037F ; G 767
+U 900 ; WX 500 ; N tonos ; G 768
+U 901 ; WX 500 ; N dieresistonos ; G 769
+U 902 ; WX 722 ; N Alphatonos ; G 770
+U 903 ; WX 318 ; N anoteleia ; G 771
+U 904 ; WX 900 ; N Epsilontonos ; G 772
+U 905 ; WX 1039 ; N Etatonos ; G 773
+U 906 ; WX 562 ; N Iotatonos ; G 774
+U 908 ; WX 835 ; N Omicrontonos ; G 775
+U 910 ; WX 897 ; N Upsilontonos ; G 776
+U 911 ; WX 853 ; N Omegatonos ; G 777
+U 912 ; WX 392 ; N iotadieresistonos ; G 778
+U 913 ; WX 722 ; N Alpha ; G 779
+U 914 ; WX 735 ; N Beta ; G 780
+U 915 ; WX 694 ; N Gamma ; G 781
+U 916 ; WX 722 ; N uni0394 ; G 782
+U 917 ; WX 730 ; N Epsilon ; G 783
+U 918 ; WX 695 ; N Zeta ; G 784
+U 919 ; WX 872 ; N Eta ; G 785
+U 920 ; WX 820 ; N Theta ; G 786
+U 921 ; WX 395 ; N Iota ; G 787
+U 922 ; WX 747 ; N Kappa ; G 788
+U 923 ; WX 722 ; N Lambda ; G 789
+U 924 ; WX 1024 ; N Mu ; G 790
+U 925 ; WX 875 ; N Nu ; G 791
+U 926 ; WX 704 ; N Xi ; G 792
+U 927 ; WX 820 ; N Omicron ; G 793
+U 928 ; WX 872 ; N Pi ; G 794
+U 929 ; WX 673 ; N Rho ; G 795
+U 931 ; WX 707 ; N Sigma ; G 796
+U 932 ; WX 667 ; N Tau ; G 797
+U 933 ; WX 660 ; N Upsilon ; G 798
+U 934 ; WX 820 ; N Phi ; G 799
+U 935 ; WX 712 ; N Chi ; G 800
+U 936 ; WX 877 ; N Psi ; G 801
+U 937 ; WX 829 ; N Omega ; G 802
+U 938 ; WX 395 ; N Iotadieresis ; G 803
+U 939 ; WX 660 ; N Upsilondieresis ; G 804
+U 940 ; WX 675 ; N alphatonos ; G 805
+U 941 ; WX 537 ; N epsilontonos ; G 806
+U 942 ; WX 599 ; N etatonos ; G 807
+U 943 ; WX 392 ; N iotatonos ; G 808
+U 944 ; WX 608 ; N upsilondieresistonos ; G 809
+U 945 ; WX 675 ; N alpha ; G 810
+U 946 ; WX 578 ; N beta ; G 811
+U 947 ; WX 598 ; N gamma ; G 812
+U 948 ; WX 602 ; N delta ; G 813
+U 949 ; WX 537 ; N epsilon ; G 814
+U 950 ; WX 542 ; N zeta ; G 815
+U 951 ; WX 599 ; N eta ; G 816
+U 952 ; WX 602 ; N theta ; G 817
+U 953 ; WX 392 ; N iota ; G 818
+U 954 ; WX 656 ; N kappa ; G 819
+U 955 ; WX 634 ; N lambda ; G 820
+U 956 ; WX 650 ; N uni03BC ; G 821
+U 957 ; WX 608 ; N nu ; G 822
+U 958 ; WX 551 ; N xi ; G 823
+U 959 ; WX 602 ; N omicron ; G 824
+U 960 ; WX 657 ; N pi ; G 825
+U 961 ; WX 588 ; N rho ; G 826
+U 962 ; WX 560 ; N sigma1 ; G 827
+U 963 ; WX 683 ; N sigma ; G 828
+U 964 ; WX 553 ; N tau ; G 829
+U 965 ; WX 608 ; N upsilon ; G 830
+U 966 ; WX 700 ; N phi ; G 831
+U 967 ; WX 606 ; N chi ; G 832
+U 968 ; WX 784 ; N psi ; G 833
+U 969 ; WX 815 ; N omega ; G 834
+U 970 ; WX 392 ; N iotadieresis ; G 835
+U 971 ; WX 608 ; N upsilondieresis ; G 836
+U 972 ; WX 602 ; N omicrontonos ; G 837
+U 973 ; WX 608 ; N upsilontonos ; G 838
+U 974 ; WX 815 ; N omegatonos ; G 839
+U 975 ; WX 747 ; N uni03CF ; G 840
+U 976 ; WX 583 ; N uni03D0 ; G 841
+U 977 ; WX 715 ; N theta1 ; G 842
+U 978 ; WX 687 ; N Upsilon1 ; G 843
+U 979 ; WX 874 ; N uni03D3 ; G 844
+U 980 ; WX 687 ; N uni03D4 ; G 845
+U 981 ; WX 682 ; N phi1 ; G 846
+U 982 ; WX 815 ; N omega1 ; G 847
+U 983 ; WX 624 ; N uni03D7 ; G 848
+U 984 ; WX 820 ; N uni03D8 ; G 849
+U 985 ; WX 602 ; N uni03D9 ; G 850
+U 986 ; WX 765 ; N uni03DA ; G 851
+U 987 ; WX 560 ; N uni03DB ; G 852
+U 988 ; WX 694 ; N uni03DC ; G 853
+U 989 ; WX 463 ; N uni03DD ; G 854
+U 990 ; WX 590 ; N uni03DE ; G 855
+U 991 ; WX 660 ; N uni03DF ; G 856
+U 992 ; WX 782 ; N uni03E0 ; G 857
+U 993 ; WX 577 ; N uni03E1 ; G 858
+U 1008 ; WX 624 ; N uni03F0 ; G 859
+U 1009 ; WX 588 ; N uni03F1 ; G 860
+U 1010 ; WX 560 ; N uni03F2 ; G 861
+U 1011 ; WX 310 ; N uni03F3 ; G 862
+U 1012 ; WX 820 ; N uni03F4 ; G 863
+U 1013 ; WX 560 ; N uni03F5 ; G 864
+U 1014 ; WX 560 ; N uni03F6 ; G 865
+U 1015 ; WX 676 ; N uni03F7 ; G 866
+U 1016 ; WX 640 ; N uni03F8 ; G 867
+U 1017 ; WX 765 ; N uni03F9 ; G 868
+U 1018 ; WX 1024 ; N uni03FA ; G 869
+U 1019 ; WX 708 ; N uni03FB ; G 870
+U 1020 ; WX 588 ; N uni03FC ; G 871
+U 1021 ; WX 765 ; N uni03FD ; G 872
+U 1022 ; WX 765 ; N uni03FE ; G 873
+U 1023 ; WX 765 ; N uni03FF ; G 874
+U 1024 ; WX 730 ; N uni0400 ; G 875
+U 1025 ; WX 730 ; N uni0401 ; G 876
+U 1026 ; WX 799 ; N uni0402 ; G 877
+U 1027 ; WX 662 ; N uni0403 ; G 878
+U 1028 ; WX 765 ; N uni0404 ; G 879
+U 1029 ; WX 685 ; N uni0405 ; G 880
+U 1030 ; WX 395 ; N uni0406 ; G 881
+U 1031 ; WX 395 ; N uni0407 ; G 882
+U 1032 ; WX 401 ; N uni0408 ; G 883
+U 1033 ; WX 1084 ; N uni0409 ; G 884
+U 1034 ; WX 1118 ; N uni040A ; G 885
+U 1035 ; WX 872 ; N uni040B ; G 886
+U 1036 ; WX 774 ; N uni040C ; G 887
+U 1037 ; WX 872 ; N uni040D ; G 888
+U 1038 ; WX 723 ; N uni040E ; G 889
+U 1039 ; WX 872 ; N uni040F ; G 890
+U 1040 ; WX 757 ; N uni0410 ; G 891
+U 1041 ; WX 735 ; N uni0411 ; G 892
+U 1042 ; WX 735 ; N uni0412 ; G 893
+U 1043 ; WX 662 ; N uni0413 ; G 894
+U 1044 ; WX 813 ; N uni0414 ; G 895
+U 1045 ; WX 730 ; N uni0415 ; G 896
+U 1046 ; WX 1124 ; N uni0416 ; G 897
+U 1047 ; WX 623 ; N uni0417 ; G 898
+U 1048 ; WX 872 ; N uni0418 ; G 899
+U 1049 ; WX 872 ; N uni0419 ; G 900
+U 1050 ; WX 774 ; N uni041A ; G 901
+U 1051 ; WX 834 ; N uni041B ; G 902
+U 1052 ; WX 1024 ; N uni041C ; G 903
+U 1053 ; WX 872 ; N uni041D ; G 904
+U 1054 ; WX 820 ; N uni041E ; G 905
+U 1055 ; WX 872 ; N uni041F ; G 906
+U 1056 ; WX 673 ; N uni0420 ; G 907
+U 1057 ; WX 765 ; N uni0421 ; G 908
+U 1058 ; WX 667 ; N uni0422 ; G 909
+U 1059 ; WX 723 ; N uni0423 ; G 910
+U 1060 ; WX 830 ; N uni0424 ; G 911
+U 1061 ; WX 712 ; N uni0425 ; G 912
+U 1062 ; WX 872 ; N uni0426 ; G 913
+U 1063 ; WX 773 ; N uni0427 ; G 914
+U 1064 ; WX 1141 ; N uni0428 ; G 915
+U 1065 ; WX 1141 ; N uni0429 ; G 916
+U 1066 ; WX 794 ; N uni042A ; G 917
+U 1067 ; WX 984 ; N uni042B ; G 918
+U 1068 ; WX 674 ; N uni042C ; G 919
+U 1069 ; WX 765 ; N uni042D ; G 920
+U 1070 ; WX 1193 ; N uni042E ; G 921
+U 1071 ; WX 808 ; N uni042F ; G 922
+U 1072 ; WX 596 ; N uni0430 ; G 923
+U 1073 ; WX 610 ; N uni0431 ; G 924
+U 1074 ; WX 582 ; N uni0432 ; G 925
+U 1075 ; WX 505 ; N uni0433 ; G 926
+U 1076 ; WX 634 ; N uni0434 ; G 927
+U 1077 ; WX 592 ; N uni0435 ; G 928
+U 1078 ; WX 1137 ; N uni0436 ; G 929
+U 1079 ; WX 545 ; N uni0437 ; G 930
+U 1080 ; WX 644 ; N uni0438 ; G 931
+U 1081 ; WX 644 ; N uni0439 ; G 932
+U 1082 ; WX 597 ; N uni043A ; G 933
+U 1083 ; WX 637 ; N uni043B ; G 934
+U 1084 ; WX 829 ; N uni043C ; G 935
+U 1085 ; WX 659 ; N uni043D ; G 936
+U 1086 ; WX 602 ; N uni043E ; G 937
+U 1087 ; WX 644 ; N uni043F ; G 938
+U 1088 ; WX 640 ; N uni0440 ; G 939
+U 1089 ; WX 560 ; N uni0441 ; G 940
+U 1090 ; WX 948 ; N uni0442 ; G 941
+U 1091 ; WX 580 ; N uni0443 ; G 942
+U 1092 ; WX 783 ; N uni0444 ; G 943
+U 1093 ; WX 564 ; N uni0445 ; G 944
+U 1094 ; WX 698 ; N uni0446 ; G 945
+U 1095 ; WX 622 ; N uni0447 ; G 946
+U 1096 ; WX 947 ; N uni0448 ; G 947
+U 1097 ; WX 1001 ; N uni0449 ; G 948
+U 1098 ; WX 667 ; N uni044A ; G 949
+U 1099 ; WX 814 ; N uni044B ; G 950
+U 1100 ; WX 544 ; N uni044C ; G 951
+U 1101 ; WX 560 ; N uni044D ; G 952
+U 1102 ; WX 880 ; N uni044E ; G 953
+U 1103 ; WX 662 ; N uni044F ; G 954
+U 1104 ; WX 592 ; N uni0450 ; G 955
+U 1105 ; WX 592 ; N uni0451 ; G 956
+U 1106 ; WX 624 ; N uni0452 ; G 957
+U 1107 ; WX 505 ; N uni0453 ; G 958
+U 1108 ; WX 560 ; N uni0454 ; G 959
+U 1109 ; WX 513 ; N uni0455 ; G 960
+U 1110 ; WX 320 ; N uni0456 ; G 961
+U 1111 ; WX 320 ; N uni0457 ; G 962
+U 1112 ; WX 310 ; N uni0458 ; G 963
+U 1113 ; WX 859 ; N uni0459 ; G 964
+U 1114 ; WX 878 ; N uni045A ; G 965
+U 1115 ; WX 644 ; N uni045B ; G 966
+U 1116 ; WX 597 ; N uni045C ; G 967
+U 1117 ; WX 644 ; N uni045D ; G 968
+U 1118 ; WX 580 ; N uni045E ; G 969
+U 1119 ; WX 644 ; N uni045F ; G 970
+U 1122 ; WX 762 ; N uni0462 ; G 971
+U 1123 ; WX 882 ; N uni0463 ; G 972
+U 1124 ; WX 1129 ; N uni0464 ; G 973
+U 1125 ; WX 834 ; N uni0465 ; G 974
+U 1130 ; WX 1124 ; N uni046A ; G 975
+U 1131 ; WX 920 ; N uni046B ; G 976
+U 1132 ; WX 1359 ; N uni046C ; G 977
+U 1133 ; WX 1063 ; N uni046D ; G 978
+U 1136 ; WX 944 ; N uni0470 ; G 979
+U 1137 ; WX 902 ; N uni0471 ; G 980
+U 1138 ; WX 820 ; N uni0472 ; G 981
+U 1139 ; WX 552 ; N uni0473 ; G 982
+U 1140 ; WX 859 ; N uni0474 ; G 983
+U 1141 ; WX 678 ; N uni0475 ; G 984
+U 1142 ; WX 859 ; N uni0476 ; G 985
+U 1143 ; WX 678 ; N uni0477 ; G 986
+U 1164 ; WX 707 ; N uni048C ; G 987
+U 1165 ; WX 544 ; N uni048D ; G 988
+U 1168 ; WX 672 ; N uni0490 ; G 989
+U 1169 ; WX 529 ; N uni0491 ; G 990
+U 1170 ; WX 662 ; N uni0492 ; G 991
+U 1171 ; WX 505 ; N uni0493 ; G 992
+U 1172 ; WX 730 ; N uni0494 ; G 993
+U 1173 ; WX 614 ; N uni0495 ; G 994
+U 1174 ; WX 1124 ; N uni0496 ; G 995
+U 1175 ; WX 1137 ; N uni0497 ; G 996
+U 1176 ; WX 623 ; N uni0498 ; G 997
+U 1177 ; WX 545 ; N uni0499 ; G 998
+U 1178 ; WX 774 ; N uni049A ; G 999
+U 1179 ; WX 604 ; N uni049B ; G 1000
+U 1182 ; WX 774 ; N uni049E ; G 1001
+U 1183 ; WX 597 ; N uni049F ; G 1002
+U 1184 ; WX 892 ; N uni04A0 ; G 1003
+U 1185 ; WX 669 ; N uni04A1 ; G 1004
+U 1186 ; WX 872 ; N uni04A2 ; G 1005
+U 1187 ; WX 712 ; N uni04A3 ; G 1006
+U 1188 ; WX 1139 ; N uni04A4 ; G 1007
+U 1189 ; WX 857 ; N uni04A5 ; G 1008
+U 1190 ; WX 1206 ; N uni04A6 ; G 1009
+U 1191 ; WX 943 ; N uni04A7 ; G 1010
+U 1194 ; WX 765 ; N uni04AA ; G 1011
+U 1195 ; WX 560 ; N uni04AB ; G 1012
+U 1196 ; WX 667 ; N uni04AC ; G 1013
+U 1197 ; WX 1013 ; N uni04AD ; G 1014
+U 1198 ; WX 660 ; N uni04AE ; G 1015
+U 1199 ; WX 571 ; N uni04AF ; G 1016
+U 1200 ; WX 660 ; N uni04B0 ; G 1017
+U 1201 ; WX 571 ; N uni04B1 ; G 1018
+U 1202 ; WX 712 ; N uni04B2 ; G 1019
+U 1203 ; WX 629 ; N uni04B3 ; G 1020
+U 1204 ; WX 936 ; N uni04B4 ; G 1021
+U 1205 ; WX 732 ; N uni04B5 ; G 1022
+U 1206 ; WX 749 ; N uni04B6 ; G 1023
+U 1207 ; WX 677 ; N uni04B7 ; G 1024
+U 1210 ; WX 749 ; N uni04BA ; G 1025
+U 1211 ; WX 644 ; N uni04BB ; G 1026
+U 1216 ; WX 395 ; N uni04C0 ; G 1027
+U 1217 ; WX 1124 ; N uni04C1 ; G 1028
+U 1218 ; WX 1137 ; N uni04C2 ; G 1029
+U 1219 ; WX 747 ; N uni04C3 ; G 1030
+U 1220 ; WX 606 ; N uni04C4 ; G 1031
+U 1223 ; WX 872 ; N uni04C7 ; G 1032
+U 1224 ; WX 667 ; N uni04C8 ; G 1033
+U 1227 ; WX 749 ; N uni04CB ; G 1034
+U 1228 ; WX 667 ; N uni04CC ; G 1035
+U 1231 ; WX 320 ; N uni04CF ; G 1036
+U 1232 ; WX 757 ; N uni04D0 ; G 1037
+U 1233 ; WX 596 ; N uni04D1 ; G 1038
+U 1234 ; WX 757 ; N uni04D2 ; G 1039
+U 1235 ; WX 596 ; N uni04D3 ; G 1040
+U 1236 ; WX 1001 ; N uni04D4 ; G 1041
+U 1237 ; WX 940 ; N uni04D5 ; G 1042
+U 1238 ; WX 730 ; N uni04D6 ; G 1043
+U 1239 ; WX 592 ; N uni04D7 ; G 1044
+U 1240 ; WX 820 ; N uni04D8 ; G 1045
+U 1241 ; WX 592 ; N uni04D9 ; G 1046
+U 1242 ; WX 820 ; N uni04DA ; G 1047
+U 1243 ; WX 592 ; N uni04DB ; G 1048
+U 1244 ; WX 1124 ; N uni04DC ; G 1049
+U 1245 ; WX 1137 ; N uni04DD ; G 1050
+U 1246 ; WX 623 ; N uni04DE ; G 1051
+U 1247 ; WX 545 ; N uni04DF ; G 1052
+U 1248 ; WX 564 ; N uni04E0 ; G 1053
+U 1249 ; WX 564 ; N uni04E1 ; G 1054
+U 1250 ; WX 872 ; N uni04E2 ; G 1055
+U 1251 ; WX 644 ; N uni04E3 ; G 1056
+U 1252 ; WX 872 ; N uni04E4 ; G 1057
+U 1253 ; WX 644 ; N uni04E5 ; G 1058
+U 1254 ; WX 820 ; N uni04E6 ; G 1059
+U 1255 ; WX 602 ; N uni04E7 ; G 1060
+U 1256 ; WX 820 ; N uni04E8 ; G 1061
+U 1257 ; WX 602 ; N uni04E9 ; G 1062
+U 1258 ; WX 820 ; N uni04EA ; G 1063
+U 1259 ; WX 602 ; N uni04EB ; G 1064
+U 1260 ; WX 765 ; N uni04EC ; G 1065
+U 1261 ; WX 560 ; N uni04ED ; G 1066
+U 1262 ; WX 723 ; N uni04EE ; G 1067
+U 1263 ; WX 580 ; N uni04EF ; G 1068
+U 1264 ; WX 723 ; N uni04F0 ; G 1069
+U 1265 ; WX 580 ; N uni04F1 ; G 1070
+U 1266 ; WX 723 ; N uni04F2 ; G 1071
+U 1267 ; WX 580 ; N uni04F3 ; G 1072
+U 1268 ; WX 773 ; N uni04F4 ; G 1073
+U 1269 ; WX 622 ; N uni04F5 ; G 1074
+U 1270 ; WX 662 ; N uni04F6 ; G 1075
+U 1271 ; WX 505 ; N uni04F7 ; G 1076
+U 1272 ; WX 984 ; N uni04F8 ; G 1077
+U 1273 ; WX 814 ; N uni04F9 ; G 1078
+U 1296 ; WX 623 ; N uni0510 ; G 1079
+U 1297 ; WX 545 ; N uni0511 ; G 1080
+U 1298 ; WX 834 ; N uni0512 ; G 1081
+U 1299 ; WX 637 ; N uni0513 ; G 1082
+U 1300 ; WX 1199 ; N uni0514 ; G 1083
+U 1301 ; WX 939 ; N uni0515 ; G 1084
+U 1306 ; WX 820 ; N uni051A ; G 1085
+U 1307 ; WX 640 ; N uni051B ; G 1086
+U 1308 ; WX 1028 ; N uni051C ; G 1087
+U 1309 ; WX 856 ; N uni051D ; G 1088
+U 1329 ; WX 810 ; N uni0531 ; G 1089
+U 1330 ; WX 811 ; N uni0532 ; G 1090
+U 1331 ; WX 806 ; N uni0533 ; G 1091
+U 1332 ; WX 828 ; N uni0534 ; G 1092
+U 1333 ; WX 806 ; N uni0535 ; G 1093
+U 1334 ; WX 826 ; N uni0536 ; G 1094
+U 1335 ; WX 761 ; N uni0537 ; G 1095
+U 1336 ; WX 811 ; N uni0538 ; G 1096
+U 1337 ; WX 968 ; N uni0539 ; G 1097
+U 1338 ; WX 816 ; N uni053A ; G 1098
+U 1339 ; WX 772 ; N uni053B ; G 1099
+U 1340 ; WX 682 ; N uni053C ; G 1100
+U 1341 ; WX 1097 ; N uni053D ; G 1101
+U 1342 ; WX 845 ; N uni053E ; G 1102
+U 1343 ; WX 804 ; N uni053F ; G 1103
+U 1344 ; WX 719 ; N uni0540 ; G 1104
+U 1345 ; WX 810 ; N uni0541 ; G 1105
+U 1346 ; WX 833 ; N uni0542 ; G 1106
+U 1347 ; WX 831 ; N uni0543 ; G 1107
+U 1348 ; WX 897 ; N uni0544 ; G 1108
+U 1349 ; WX 763 ; N uni0545 ; G 1109
+U 1350 ; WX 794 ; N uni0546 ; G 1110
+U 1351 ; WX 754 ; N uni0547 ; G 1111
+U 1352 ; WX 799 ; N uni0548 ; G 1112
+U 1353 ; WX 797 ; N uni0549 ; G 1113
+U 1354 ; WX 875 ; N uni054A ; G 1114
+U 1355 ; WX 830 ; N uni054B ; G 1115
+U 1356 ; WX 864 ; N uni054C ; G 1116
+U 1357 ; WX 799 ; N uni054D ; G 1117
+U 1358 ; WX 802 ; N uni054E ; G 1118
+U 1359 ; WX 731 ; N uni054F ; G 1119
+U 1360 ; WX 774 ; N uni0550 ; G 1120
+U 1361 ; WX 749 ; N uni0551 ; G 1121
+U 1362 ; WX 633 ; N uni0552 ; G 1122
+U 1363 ; WX 845 ; N uni0553 ; G 1123
+U 1364 ; WX 843 ; N uni0554 ; G 1124
+U 1365 ; WX 835 ; N uni0555 ; G 1125
+U 1366 ; WX 821 ; N uni0556 ; G 1126
+U 1369 ; WX 307 ; N uni0559 ; G 1127
+U 1370 ; WX 264 ; N uni055A ; G 1128
+U 1371 ; WX 229 ; N uni055B ; G 1129
+U 1372 ; WX 391 ; N uni055C ; G 1130
+U 1373 ; WX 364 ; N uni055D ; G 1131
+U 1374 ; WX 386 ; N uni055E ; G 1132
+U 1375 ; WX 500 ; N uni055F ; G 1133
+U 1377 ; WX 949 ; N uni0561 ; G 1134
+U 1378 ; WX 618 ; N uni0562 ; G 1135
+U 1379 ; WX 695 ; N uni0563 ; G 1136
+U 1380 ; WX 695 ; N uni0564 ; G 1137
+U 1381 ; WX 628 ; N uni0565 ; G 1138
+U 1382 ; WX 688 ; N uni0566 ; G 1139
+U 1383 ; WX 510 ; N uni0567 ; G 1140
+U 1384 ; WX 636 ; N uni0568 ; G 1141
+U 1385 ; WX 791 ; N uni0569 ; G 1142
+U 1386 ; WX 671 ; N uni056A ; G 1143
+U 1387 ; WX 635 ; N uni056B ; G 1144
+U 1388 ; WX 305 ; N uni056C ; G 1145
+U 1389 ; WX 973 ; N uni056D ; G 1146
+U 1390 ; WX 614 ; N uni056E ; G 1147
+U 1391 ; WX 628 ; N uni056F ; G 1148
+U 1392 ; WX 636 ; N uni0570 ; G 1149
+U 1393 ; WX 630 ; N uni0571 ; G 1150
+U 1394 ; WX 636 ; N uni0572 ; G 1151
+U 1395 ; WX 654 ; N uni0573 ; G 1152
+U 1396 ; WX 644 ; N uni0574 ; G 1153
+U 1397 ; WX 309 ; N uni0575 ; G 1154
+U 1398 ; WX 636 ; N uni0576 ; G 1155
+U 1399 ; WX 461 ; N uni0577 ; G 1156
+U 1400 ; WX 649 ; N uni0578 ; G 1157
+U 1401 ; WX 365 ; N uni0579 ; G 1158
+U 1402 ; WX 940 ; N uni057A ; G 1159
+U 1403 ; WX 562 ; N uni057B ; G 1160
+U 1404 ; WX 657 ; N uni057C ; G 1161
+U 1405 ; WX 644 ; N uni057D ; G 1162
+U 1406 ; WX 630 ; N uni057E ; G 1163
+U 1407 ; WX 930 ; N uni057F ; G 1164
+U 1408 ; WX 644 ; N uni0580 ; G 1165
+U 1409 ; WX 643 ; N uni0581 ; G 1166
+U 1410 ; WX 483 ; N uni0582 ; G 1167
+U 1411 ; WX 930 ; N uni0583 ; G 1168
+U 1412 ; WX 636 ; N uni0584 ; G 1169
+U 1413 ; WX 609 ; N uni0585 ; G 1170
+U 1414 ; WX 809 ; N uni0586 ; G 1171
+U 1415 ; WX 789 ; N uni0587 ; G 1172
+U 1417 ; WX 340 ; N uni0589 ; G 1173
+U 1418 ; WX 334 ; N uni058A ; G 1174
+U 3647 ; WX 636 ; N uni0E3F ; G 1175
+U 4256 ; WX 732 ; N uni10A0 ; G 1176
+U 4257 ; WX 860 ; N uni10A1 ; G 1177
+U 4258 ; WX 837 ; N uni10A2 ; G 1178
+U 4259 ; WX 869 ; N uni10A3 ; G 1179
+U 4260 ; WX 743 ; N uni10A4 ; G 1180
+U 4261 ; WX 991 ; N uni10A5 ; G 1181
+U 4262 ; WX 925 ; N uni10A6 ; G 1182
+U 4263 ; WX 1111 ; N uni10A7 ; G 1183
+U 4264 ; WX 576 ; N uni10A8 ; G 1184
+U 4265 ; WX 760 ; N uni10A9 ; G 1185
+U 4266 ; WX 972 ; N uni10AA ; G 1186
+U 4267 ; WX 951 ; N uni10AB ; G 1187
+U 4268 ; WX 753 ; N uni10AC ; G 1188
+U 4269 ; WX 1084 ; N uni10AD ; G 1189
+U 4270 ; WX 906 ; N uni10AE ; G 1190
+U 4271 ; WX 838 ; N uni10AF ; G 1191
+U 4272 ; WX 1049 ; N uni10B0 ; G 1192
+U 4273 ; WX 743 ; N uni10B1 ; G 1193
+U 4274 ; WX 679 ; N uni10B2 ; G 1194
+U 4275 ; WX 1025 ; N uni10B3 ; G 1195
+U 4276 ; WX 946 ; N uni10B4 ; G 1196
+U 4277 ; WX 1029 ; N uni10B5 ; G 1197
+U 4278 ; WX 741 ; N uni10B6 ; G 1198
+U 4279 ; WX 743 ; N uni10B7 ; G 1199
+U 4280 ; WX 742 ; N uni10B8 ; G 1200
+U 4281 ; WX 743 ; N uni10B9 ; G 1201
+U 4282 ; WX 889 ; N uni10BA ; G 1202
+U 4283 ; WX 946 ; N uni10BB ; G 1203
+U 4284 ; WX 724 ; N uni10BC ; G 1204
+U 4285 ; WX 765 ; N uni10BD ; G 1205
+U 4286 ; WX 743 ; N uni10BE ; G 1206
+U 4287 ; WX 968 ; N uni10BF ; G 1207
+U 4288 ; WX 1010 ; N uni10C0 ; G 1208
+U 4289 ; WX 712 ; N uni10C1 ; G 1209
+U 4290 ; WX 874 ; N uni10C2 ; G 1210
+U 4291 ; WX 744 ; N uni10C3 ; G 1211
+U 4292 ; WX 847 ; N uni10C4 ; G 1212
+U 4293 ; WX 960 ; N uni10C5 ; G 1213
+U 4304 ; WX 550 ; N uni10D0 ; G 1214
+U 4305 ; WX 581 ; N uni10D1 ; G 1215
+U 4306 ; WX 599 ; N uni10D2 ; G 1216
+U 4307 ; WX 843 ; N uni10D3 ; G 1217
+U 4308 ; WX 571 ; N uni10D4 ; G 1218
+U 4309 ; WX 567 ; N uni10D5 ; G 1219
+U 4310 ; WX 620 ; N uni10D6 ; G 1220
+U 4311 ; WX 871 ; N uni10D7 ; G 1221
+U 4312 ; WX 569 ; N uni10D8 ; G 1222
+U 4313 ; WX 556 ; N uni10D9 ; G 1223
+U 4314 ; WX 1076 ; N uni10DA ; G 1224
+U 4315 ; WX 596 ; N uni10DB ; G 1225
+U 4316 ; WX 596 ; N uni10DC ; G 1226
+U 4317 ; WX 835 ; N uni10DD ; G 1227
+U 4318 ; WX 580 ; N uni10DE ; G 1228
+U 4319 ; WX 590 ; N uni10DF ; G 1229
+U 4320 ; WX 833 ; N uni10E0 ; G 1230
+U 4321 ; WX 607 ; N uni10E1 ; G 1231
+U 4322 ; WX 758 ; N uni10E2 ; G 1232
+U 4323 ; WX 701 ; N uni10E3 ; G 1233
+U 4324 ; WX 825 ; N uni10E4 ; G 1234
+U 4325 ; WX 595 ; N uni10E5 ; G 1235
+U 4326 ; WX 868 ; N uni10E6 ; G 1236
+U 4327 ; WX 578 ; N uni10E7 ; G 1237
+U 4328 ; WX 604 ; N uni10E8 ; G 1238
+U 4329 ; WX 596 ; N uni10E9 ; G 1239
+U 4330 ; WX 685 ; N uni10EA ; G 1240
+U 4331 ; WX 597 ; N uni10EB ; G 1241
+U 4332 ; WX 557 ; N uni10EC ; G 1242
+U 4333 ; WX 585 ; N uni10ED ; G 1243
+U 4334 ; WX 625 ; N uni10EE ; G 1244
+U 4335 ; WX 693 ; N uni10EF ; G 1245
+U 4336 ; WX 582 ; N uni10F0 ; G 1246
+U 4337 ; WX 613 ; N uni10F1 ; G 1247
+U 4338 ; WX 581 ; N uni10F2 ; G 1248
+U 4339 ; WX 582 ; N uni10F3 ; G 1249
+U 4340 ; WX 580 ; N uni10F4 ; G 1250
+U 4341 ; WX 659 ; N uni10F5 ; G 1251
+U 4342 ; WX 896 ; N uni10F6 ; G 1252
+U 4343 ; WX 636 ; N uni10F7 ; G 1253
+U 4344 ; WX 592 ; N uni10F8 ; G 1254
+U 4345 ; WX 628 ; N uni10F9 ; G 1255
+U 4346 ; WX 581 ; N uni10FA ; G 1256
+U 4347 ; WX 456 ; N uni10FB ; G 1257
+U 4348 ; WX 373 ; N uni10FC ; G 1258
+U 7424 ; WX 565 ; N uni1D00 ; G 1259
+U 7425 ; WX 774 ; N uni1D01 ; G 1260
+U 7426 ; WX 940 ; N uni1D02 ; G 1261
+U 7427 ; WX 563 ; N uni1D03 ; G 1262
+U 7428 ; WX 560 ; N uni1D04 ; G 1263
+U 7429 ; WX 585 ; N uni1D05 ; G 1264
+U 7430 ; WX 585 ; N uni1D06 ; G 1265
+U 7431 ; WX 553 ; N uni1D07 ; G 1266
+U 7432 ; WX 509 ; N uni1D08 ; G 1267
+U 7433 ; WX 320 ; N uni1D09 ; G 1268
+U 7434 ; WX 499 ; N uni1D0A ; G 1269
+U 7435 ; WX 597 ; N uni1D0B ; G 1270
+U 7436 ; WX 543 ; N uni1D0C ; G 1271
+U 7437 ; WX 778 ; N uni1D0D ; G 1272
+U 7438 ; WX 667 ; N uni1D0E ; G 1273
+U 7439 ; WX 602 ; N uni1D0F ; G 1274
+U 7440 ; WX 560 ; N uni1D10 ; G 1275
+U 7441 ; WX 647 ; N uni1D11 ; G 1276
+U 7442 ; WX 647 ; N uni1D12 ; G 1277
+U 7443 ; WX 647 ; N uni1D13 ; G 1278
+U 7444 ; WX 989 ; N uni1D14 ; G 1279
+U 7445 ; WX 512 ; N uni1D15 ; G 1280
+U 7446 ; WX 602 ; N uni1D16 ; G 1281
+U 7447 ; WX 602 ; N uni1D17 ; G 1282
+U 7448 ; WX 553 ; N uni1D18 ; G 1283
+U 7449 ; WX 594 ; N uni1D19 ; G 1284
+U 7450 ; WX 594 ; N uni1D1A ; G 1285
+U 7451 ; WX 553 ; N uni1D1B ; G 1286
+U 7452 ; WX 585 ; N uni1D1C ; G 1287
+U 7453 ; WX 664 ; N uni1D1D ; G 1288
+U 7454 ; WX 923 ; N uni1D1E ; G 1289
+U 7455 ; WX 655 ; N uni1D1F ; G 1290
+U 7456 ; WX 565 ; N uni1D20 ; G 1291
+U 7457 ; WX 856 ; N uni1D21 ; G 1292
+U 7458 ; WX 527 ; N uni1D22 ; G 1293
+U 7459 ; WX 527 ; N uni1D23 ; G 1294
+U 7460 ; WX 531 ; N uni1D24 ; G 1295
+U 7461 ; WX 743 ; N uni1D25 ; G 1296
+U 7462 ; WX 524 ; N uni1D26 ; G 1297
+U 7463 ; WX 565 ; N uni1D27 ; G 1298
+U 7464 ; WX 657 ; N uni1D28 ; G 1299
+U 7465 ; WX 553 ; N uni1D29 ; G 1300
+U 7466 ; WX 703 ; N uni1D2A ; G 1301
+U 7467 ; WX 635 ; N uni1D2B ; G 1302
+U 7468 ; WX 455 ; N uni1D2C ; G 1303
+U 7469 ; WX 630 ; N uni1D2D ; G 1304
+U 7470 ; WX 463 ; N uni1D2E ; G 1305
+U 7471 ; WX 463 ; N uni1D2F ; G 1306
+U 7472 ; WX 505 ; N uni1D30 ; G 1307
+U 7473 ; WX 459 ; N uni1D31 ; G 1308
+U 7474 ; WX 459 ; N uni1D32 ; G 1309
+U 7475 ; WX 503 ; N uni1D33 ; G 1310
+U 7476 ; WX 549 ; N uni1D34 ; G 1311
+U 7477 ; WX 249 ; N uni1D35 ; G 1312
+U 7478 ; WX 252 ; N uni1D36 ; G 1313
+U 7479 ; WX 470 ; N uni1D37 ; G 1314
+U 7480 ; WX 418 ; N uni1D38 ; G 1315
+U 7481 ; WX 645 ; N uni1D39 ; G 1316
+U 7482 ; WX 551 ; N uni1D3A ; G 1317
+U 7483 ; WX 551 ; N uni1D3B ; G 1318
+U 7484 ; WX 516 ; N uni1D3C ; G 1319
+U 7485 ; WX 369 ; N uni1D3D ; G 1320
+U 7486 ; WX 424 ; N uni1D3E ; G 1321
+U 7487 ; WX 474 ; N uni1D3F ; G 1322
+U 7488 ; WX 420 ; N uni1D40 ; G 1323
+U 7489 ; WX 531 ; N uni1D41 ; G 1324
+U 7490 ; WX 647 ; N uni1D42 ; G 1325
+U 7491 ; WX 375 ; N uni1D43 ; G 1326
+U 7492 ; WX 375 ; N uni1D44 ; G 1327
+U 7493 ; WX 425 ; N uni1D45 ; G 1328
+U 7494 ; WX 592 ; N uni1D46 ; G 1329
+U 7495 ; WX 400 ; N uni1D47 ; G 1330
+U 7496 ; WX 400 ; N uni1D48 ; G 1331
+U 7497 ; WX 387 ; N uni1D49 ; G 1332
+U 7498 ; WX 387 ; N uni1D4A ; G 1333
+U 7499 ; WX 428 ; N uni1D4B ; G 1334
+U 7500 ; WX 340 ; N uni1D4C ; G 1335
+U 7501 ; WX 400 ; N uni1D4D ; G 1336
+U 7502 ; WX 175 ; N uni1D4E ; G 1337
+U 7503 ; WX 365 ; N uni1D4F ; G 1338
+U 7504 ; WX 613 ; N uni1D50 ; G 1339
+U 7505 ; WX 399 ; N uni1D51 ; G 1340
+U 7506 ; WX 385 ; N uni1D52 ; G 1341
+U 7507 ; WX 346 ; N uni1D53 ; G 1342
+U 7508 ; WX 385 ; N uni1D54 ; G 1343
+U 7509 ; WX 385 ; N uni1D55 ; G 1344
+U 7510 ; WX 400 ; N uni1D56 ; G 1345
+U 7511 ; WX 247 ; N uni1D57 ; G 1346
+U 7512 ; WX 399 ; N uni1D58 ; G 1347
+U 7513 ; WX 418 ; N uni1D59 ; G 1348
+U 7514 ; WX 613 ; N uni1D5A ; G 1349
+U 7515 ; WX 373 ; N uni1D5B ; G 1350
+U 7516 ; WX 468 ; N uni1D5C ; G 1351
+U 7517 ; WX 364 ; N uni1D5D ; G 1352
+U 7518 ; WX 376 ; N uni1D5E ; G 1353
+U 7519 ; WX 379 ; N uni1D5F ; G 1354
+U 7520 ; WX 441 ; N uni1D60 ; G 1355
+U 7521 ; WX 381 ; N uni1D61 ; G 1356
+U 7522 ; WX 201 ; N uni1D62 ; G 1357
+U 7523 ; WX 347 ; N uni1D63 ; G 1358
+U 7524 ; WX 399 ; N uni1D64 ; G 1359
+U 7525 ; WX 373 ; N uni1D65 ; G 1360
+U 7526 ; WX 364 ; N uni1D66 ; G 1361
+U 7527 ; WX 376 ; N uni1D67 ; G 1362
+U 7528 ; WX 370 ; N uni1D68 ; G 1363
+U 7529 ; WX 441 ; N uni1D69 ; G 1364
+U 7530 ; WX 381 ; N uni1D6A ; G 1365
+U 7531 ; WX 974 ; N uni1D6B ; G 1366
+U 7543 ; WX 640 ; N uni1D77 ; G 1367
+U 7544 ; WX 549 ; N uni1D78 ; G 1368
+U 7547 ; WX 320 ; N uni1D7B ; G 1369
+U 7548 ; WX 392 ; N uni1D7C ; G 1370
+U 7549 ; WX 640 ; N uni1D7D ; G 1371
+U 7550 ; WX 585 ; N uni1D7E ; G 1372
+U 7551 ; WX 620 ; N uni1D7F ; G 1373
+U 7557 ; WX 320 ; N uni1D85 ; G 1374
+U 7579 ; WX 425 ; N uni1D9B ; G 1375
+U 7580 ; WX 353 ; N uni1D9C ; G 1376
+U 7581 ; WX 353 ; N uni1D9D ; G 1377
+U 7582 ; WX 473 ; N uni1D9E ; G 1378
+U 7583 ; WX 428 ; N uni1D9F ; G 1379
+U 7584 ; WX 233 ; N uni1DA0 ; G 1380
+U 7585 ; WX 316 ; N uni1DA1 ; G 1381
+U 7586 ; WX 488 ; N uni1DA2 ; G 1382
+U 7587 ; WX 399 ; N uni1DA3 ; G 1383
+U 7588 ; WX 201 ; N uni1DA4 ; G 1384
+U 7589 ; WX 201 ; N uni1DA5 ; G 1385
+U 7590 ; WX 201 ; N uni1DA6 ; G 1386
+U 7591 ; WX 201 ; N uni1DA7 ; G 1387
+U 7592 ; WX 318 ; N uni1DA8 ; G 1388
+U 7593 ; WX 263 ; N uni1DA9 ; G 1389
+U 7594 ; WX 263 ; N uni1DAA ; G 1390
+U 7595 ; WX 455 ; N uni1DAB ; G 1391
+U 7596 ; WX 613 ; N uni1DAC ; G 1392
+U 7597 ; WX 613 ; N uni1DAD ; G 1393
+U 7598 ; WX 495 ; N uni1DAE ; G 1394
+U 7599 ; WX 492 ; N uni1DAF ; G 1395
+U 7600 ; WX 487 ; N uni1DB0 ; G 1396
+U 7601 ; WX 385 ; N uni1DB1 ; G 1397
+U 7602 ; WX 473 ; N uni1DB2 ; G 1398
+U 7603 ; WX 328 ; N uni1DB3 ; G 1399
+U 7604 ; WX 299 ; N uni1DB4 ; G 1400
+U 7605 ; WX 334 ; N uni1DB5 ; G 1401
+U 7606 ; WX 399 ; N uni1DB6 ; G 1402
+U 7607 ; WX 477 ; N uni1DB7 ; G 1403
+U 7608 ; WX 368 ; N uni1DB8 ; G 1404
+U 7609 ; WX 464 ; N uni1DB9 ; G 1405
+U 7610 ; WX 355 ; N uni1DBA ; G 1406
+U 7611 ; WX 332 ; N uni1DBB ; G 1407
+U 7612 ; WX 418 ; N uni1DBC ; G 1408
+U 7613 ; WX 418 ; N uni1DBD ; G 1409
+U 7614 ; WX 452 ; N uni1DBE ; G 1410
+U 7615 ; WX 473 ; N uni1DBF ; G 1411
+U 7620 ; WX 0 ; N uni1DC4 ; G 1412
+U 7621 ; WX 0 ; N uni1DC5 ; G 1413
+U 7622 ; WX 0 ; N uni1DC6 ; G 1414
+U 7623 ; WX 0 ; N uni1DC7 ; G 1415
+U 7624 ; WX 0 ; N uni1DC8 ; G 1416
+U 7625 ; WX 0 ; N uni1DC9 ; G 1417
+U 7680 ; WX 722 ; N uni1E00 ; G 1418
+U 7681 ; WX 596 ; N uni1E01 ; G 1419
+U 7682 ; WX 735 ; N uni1E02 ; G 1420
+U 7683 ; WX 640 ; N uni1E03 ; G 1421
+U 7684 ; WX 735 ; N uni1E04 ; G 1422
+U 7685 ; WX 640 ; N uni1E05 ; G 1423
+U 7686 ; WX 735 ; N uni1E06 ; G 1424
+U 7687 ; WX 640 ; N uni1E07 ; G 1425
+U 7688 ; WX 765 ; N uni1E08 ; G 1426
+U 7689 ; WX 560 ; N uni1E09 ; G 1427
+U 7690 ; WX 802 ; N uni1E0A ; G 1428
+U 7691 ; WX 640 ; N uni1E0B ; G 1429
+U 7692 ; WX 802 ; N uni1E0C ; G 1430
+U 7693 ; WX 640 ; N uni1E0D ; G 1431
+U 7694 ; WX 802 ; N uni1E0E ; G 1432
+U 7695 ; WX 640 ; N uni1E0F ; G 1433
+U 7696 ; WX 802 ; N uni1E10 ; G 1434
+U 7697 ; WX 640 ; N uni1E11 ; G 1435
+U 7698 ; WX 802 ; N uni1E12 ; G 1436
+U 7699 ; WX 640 ; N uni1E13 ; G 1437
+U 7700 ; WX 730 ; N uni1E14 ; G 1438
+U 7701 ; WX 592 ; N uni1E15 ; G 1439
+U 7702 ; WX 730 ; N uni1E16 ; G 1440
+U 7703 ; WX 592 ; N uni1E17 ; G 1441
+U 7704 ; WX 730 ; N uni1E18 ; G 1442
+U 7705 ; WX 592 ; N uni1E19 ; G 1443
+U 7706 ; WX 730 ; N uni1E1A ; G 1444
+U 7707 ; WX 592 ; N uni1E1B ; G 1445
+U 7708 ; WX 730 ; N uni1E1C ; G 1446
+U 7709 ; WX 592 ; N uni1E1D ; G 1447
+U 7710 ; WX 694 ; N uni1E1E ; G 1448
+U 7711 ; WX 370 ; N uni1E1F ; G 1449
+U 7712 ; WX 799 ; N uni1E20 ; G 1450
+U 7713 ; WX 640 ; N uni1E21 ; G 1451
+U 7714 ; WX 872 ; N uni1E22 ; G 1452
+U 7715 ; WX 644 ; N uni1E23 ; G 1453
+U 7716 ; WX 872 ; N uni1E24 ; G 1454
+U 7717 ; WX 644 ; N uni1E25 ; G 1455
+U 7718 ; WX 872 ; N uni1E26 ; G 1456
+U 7719 ; WX 644 ; N uni1E27 ; G 1457
+U 7720 ; WX 872 ; N uni1E28 ; G 1458
+U 7721 ; WX 644 ; N uni1E29 ; G 1459
+U 7722 ; WX 872 ; N uni1E2A ; G 1460
+U 7723 ; WX 644 ; N uni1E2B ; G 1461
+U 7724 ; WX 395 ; N uni1E2C ; G 1462
+U 7725 ; WX 320 ; N uni1E2D ; G 1463
+U 7726 ; WX 395 ; N uni1E2E ; G 1464
+U 7727 ; WX 320 ; N uni1E2F ; G 1465
+U 7728 ; WX 747 ; N uni1E30 ; G 1466
+U 7729 ; WX 606 ; N uni1E31 ; G 1467
+U 7730 ; WX 747 ; N uni1E32 ; G 1468
+U 7731 ; WX 606 ; N uni1E33 ; G 1469
+U 7732 ; WX 747 ; N uni1E34 ; G 1470
+U 7733 ; WX 606 ; N uni1E35 ; G 1471
+U 7734 ; WX 664 ; N uni1E36 ; G 1472
+U 7735 ; WX 320 ; N uni1E37 ; G 1473
+U 7736 ; WX 664 ; N uni1E38 ; G 1474
+U 7737 ; WX 320 ; N uni1E39 ; G 1475
+U 7738 ; WX 664 ; N uni1E3A ; G 1476
+U 7739 ; WX 320 ; N uni1E3B ; G 1477
+U 7740 ; WX 664 ; N uni1E3C ; G 1478
+U 7741 ; WX 320 ; N uni1E3D ; G 1479
+U 7742 ; WX 1024 ; N uni1E3E ; G 1480
+U 7743 ; WX 948 ; N uni1E3F ; G 1481
+U 7744 ; WX 1024 ; N uni1E40 ; G 1482
+U 7745 ; WX 948 ; N uni1E41 ; G 1483
+U 7746 ; WX 1024 ; N uni1E42 ; G 1484
+U 7747 ; WX 953 ; N uni1E43 ; G 1485
+U 7748 ; WX 875 ; N uni1E44 ; G 1486
+U 7749 ; WX 644 ; N uni1E45 ; G 1487
+U 7750 ; WX 875 ; N uni1E46 ; G 1488
+U 7751 ; WX 644 ; N uni1E47 ; G 1489
+U 7752 ; WX 875 ; N uni1E48 ; G 1490
+U 7753 ; WX 644 ; N uni1E49 ; G 1491
+U 7754 ; WX 875 ; N uni1E4A ; G 1492
+U 7755 ; WX 644 ; N uni1E4B ; G 1493
+U 7756 ; WX 820 ; N uni1E4C ; G 1494
+U 7757 ; WX 602 ; N uni1E4D ; G 1495
+U 7758 ; WX 820 ; N uni1E4E ; G 1496
+U 7759 ; WX 602 ; N uni1E4F ; G 1497
+U 7760 ; WX 820 ; N uni1E50 ; G 1498
+U 7761 ; WX 602 ; N uni1E51 ; G 1499
+U 7762 ; WX 820 ; N uni1E52 ; G 1500
+U 7763 ; WX 602 ; N uni1E53 ; G 1501
+U 7764 ; WX 673 ; N uni1E54 ; G 1502
+U 7765 ; WX 640 ; N uni1E55 ; G 1503
+U 7766 ; WX 673 ; N uni1E56 ; G 1504
+U 7767 ; WX 640 ; N uni1E57 ; G 1505
+U 7768 ; WX 753 ; N uni1E58 ; G 1506
+U 7769 ; WX 478 ; N uni1E59 ; G 1507
+U 7770 ; WX 753 ; N uni1E5A ; G 1508
+U 7771 ; WX 478 ; N uni1E5B ; G 1509
+U 7772 ; WX 753 ; N uni1E5C ; G 1510
+U 7773 ; WX 478 ; N uni1E5D ; G 1511
+U 7774 ; WX 753 ; N uni1E5E ; G 1512
+U 7775 ; WX 478 ; N uni1E5F ; G 1513
+U 7776 ; WX 685 ; N uni1E60 ; G 1514
+U 7777 ; WX 513 ; N uni1E61 ; G 1515
+U 7778 ; WX 685 ; N uni1E62 ; G 1516
+U 7779 ; WX 513 ; N uni1E63 ; G 1517
+U 7780 ; WX 685 ; N uni1E64 ; G 1518
+U 7781 ; WX 513 ; N uni1E65 ; G 1519
+U 7782 ; WX 685 ; N uni1E66 ; G 1520
+U 7783 ; WX 521 ; N uni1E67 ; G 1521
+U 7784 ; WX 685 ; N uni1E68 ; G 1522
+U 7785 ; WX 513 ; N uni1E69 ; G 1523
+U 7786 ; WX 667 ; N uni1E6A ; G 1524
+U 7787 ; WX 402 ; N uni1E6B ; G 1525
+U 7788 ; WX 667 ; N uni1E6C ; G 1526
+U 7789 ; WX 402 ; N uni1E6D ; G 1527
+U 7790 ; WX 667 ; N uni1E6E ; G 1528
+U 7791 ; WX 402 ; N uni1E6F ; G 1529
+U 7792 ; WX 667 ; N uni1E70 ; G 1530
+U 7793 ; WX 402 ; N uni1E71 ; G 1531
+U 7794 ; WX 843 ; N uni1E72 ; G 1532
+U 7795 ; WX 644 ; N uni1E73 ; G 1533
+U 7796 ; WX 843 ; N uni1E74 ; G 1534
+U 7797 ; WX 644 ; N uni1E75 ; G 1535
+U 7798 ; WX 843 ; N uni1E76 ; G 1536
+U 7799 ; WX 644 ; N uni1E77 ; G 1537
+U 7800 ; WX 843 ; N uni1E78 ; G 1538
+U 7801 ; WX 644 ; N uni1E79 ; G 1539
+U 7802 ; WX 843 ; N uni1E7A ; G 1540
+U 7803 ; WX 644 ; N uni1E7B ; G 1541
+U 7804 ; WX 722 ; N uni1E7C ; G 1542
+U 7805 ; WX 565 ; N uni1E7D ; G 1543
+U 7806 ; WX 722 ; N uni1E7E ; G 1544
+U 7807 ; WX 565 ; N uni1E7F ; G 1545
+U 7808 ; WX 1028 ; N Wgrave ; G 1546
+U 7809 ; WX 856 ; N wgrave ; G 1547
+U 7810 ; WX 1028 ; N Wacute ; G 1548
+U 7811 ; WX 856 ; N wacute ; G 1549
+U 7812 ; WX 1028 ; N Wdieresis ; G 1550
+U 7813 ; WX 856 ; N wdieresis ; G 1551
+U 7814 ; WX 1028 ; N uni1E86 ; G 1552
+U 7815 ; WX 856 ; N uni1E87 ; G 1553
+U 7816 ; WX 1028 ; N uni1E88 ; G 1554
+U 7817 ; WX 856 ; N uni1E89 ; G 1555
+U 7818 ; WX 712 ; N uni1E8A ; G 1556
+U 7819 ; WX 564 ; N uni1E8B ; G 1557
+U 7820 ; WX 712 ; N uni1E8C ; G 1558
+U 7821 ; WX 564 ; N uni1E8D ; G 1559
+U 7822 ; WX 660 ; N uni1E8E ; G 1560
+U 7823 ; WX 565 ; N uni1E8F ; G 1561
+U 7824 ; WX 695 ; N uni1E90 ; G 1562
+U 7825 ; WX 527 ; N uni1E91 ; G 1563
+U 7826 ; WX 695 ; N uni1E92 ; G 1564
+U 7827 ; WX 527 ; N uni1E93 ; G 1565
+U 7828 ; WX 695 ; N uni1E94 ; G 1566
+U 7829 ; WX 527 ; N uni1E95 ; G 1567
+U 7830 ; WX 644 ; N uni1E96 ; G 1568
+U 7831 ; WX 402 ; N uni1E97 ; G 1569
+U 7832 ; WX 856 ; N uni1E98 ; G 1570
+U 7833 ; WX 565 ; N uni1E99 ; G 1571
+U 7834 ; WX 903 ; N uni1E9A ; G 1572
+U 7835 ; WX 370 ; N uni1E9B ; G 1573
+U 7836 ; WX 370 ; N uni1E9C ; G 1574
+U 7837 ; WX 370 ; N uni1E9D ; G 1575
+U 7838 ; WX 829 ; N uni1E9E ; G 1576
+U 7839 ; WX 602 ; N uni1E9F ; G 1577
+U 7840 ; WX 722 ; N uni1EA0 ; G 1578
+U 7841 ; WX 596 ; N uni1EA1 ; G 1579
+U 7842 ; WX 722 ; N uni1EA2 ; G 1580
+U 7843 ; WX 596 ; N uni1EA3 ; G 1581
+U 7844 ; WX 722 ; N uni1EA4 ; G 1582
+U 7845 ; WX 613 ; N uni1EA5 ; G 1583
+U 7846 ; WX 722 ; N uni1EA6 ; G 1584
+U 7847 ; WX 613 ; N uni1EA7 ; G 1585
+U 7848 ; WX 722 ; N uni1EA8 ; G 1586
+U 7849 ; WX 613 ; N uni1EA9 ; G 1587
+U 7850 ; WX 722 ; N uni1EAA ; G 1588
+U 7851 ; WX 613 ; N uni1EAB ; G 1589
+U 7852 ; WX 722 ; N uni1EAC ; G 1590
+U 7853 ; WX 596 ; N uni1EAD ; G 1591
+U 7854 ; WX 722 ; N uni1EAE ; G 1592
+U 7855 ; WX 596 ; N uni1EAF ; G 1593
+U 7856 ; WX 722 ; N uni1EB0 ; G 1594
+U 7857 ; WX 596 ; N uni1EB1 ; G 1595
+U 7858 ; WX 722 ; N uni1EB2 ; G 1596
+U 7859 ; WX 596 ; N uni1EB3 ; G 1597
+U 7860 ; WX 722 ; N uni1EB4 ; G 1598
+U 7861 ; WX 596 ; N uni1EB5 ; G 1599
+U 7862 ; WX 722 ; N uni1EB6 ; G 1600
+U 7863 ; WX 596 ; N uni1EB7 ; G 1601
+U 7864 ; WX 730 ; N uni1EB8 ; G 1602
+U 7865 ; WX 592 ; N uni1EB9 ; G 1603
+U 7866 ; WX 730 ; N uni1EBA ; G 1604
+U 7867 ; WX 592 ; N uni1EBB ; G 1605
+U 7868 ; WX 730 ; N uni1EBC ; G 1606
+U 7869 ; WX 592 ; N uni1EBD ; G 1607
+U 7870 ; WX 730 ; N uni1EBE ; G 1608
+U 7871 ; WX 615 ; N uni1EBF ; G 1609
+U 7872 ; WX 730 ; N uni1EC0 ; G 1610
+U 7873 ; WX 615 ; N uni1EC1 ; G 1611
+U 7874 ; WX 730 ; N uni1EC2 ; G 1612
+U 7875 ; WX 615 ; N uni1EC3 ; G 1613
+U 7876 ; WX 730 ; N uni1EC4 ; G 1614
+U 7877 ; WX 615 ; N uni1EC5 ; G 1615
+U 7878 ; WX 730 ; N uni1EC6 ; G 1616
+U 7879 ; WX 592 ; N uni1EC7 ; G 1617
+U 7880 ; WX 395 ; N uni1EC8 ; G 1618
+U 7881 ; WX 320 ; N uni1EC9 ; G 1619
+U 7882 ; WX 395 ; N uni1ECA ; G 1620
+U 7883 ; WX 320 ; N uni1ECB ; G 1621
+U 7884 ; WX 820 ; N uni1ECC ; G 1622
+U 7885 ; WX 602 ; N uni1ECD ; G 1623
+U 7886 ; WX 820 ; N uni1ECE ; G 1624
+U 7887 ; WX 602 ; N uni1ECF ; G 1625
+U 7888 ; WX 820 ; N uni1ED0 ; G 1626
+U 7889 ; WX 612 ; N uni1ED1 ; G 1627
+U 7890 ; WX 820 ; N uni1ED2 ; G 1628
+U 7891 ; WX 612 ; N uni1ED3 ; G 1629
+U 7892 ; WX 820 ; N uni1ED4 ; G 1630
+U 7893 ; WX 612 ; N uni1ED5 ; G 1631
+U 7894 ; WX 820 ; N uni1ED6 ; G 1632
+U 7895 ; WX 612 ; N uni1ED7 ; G 1633
+U 7896 ; WX 820 ; N uni1ED8 ; G 1634
+U 7897 ; WX 602 ; N uni1ED9 ; G 1635
+U 7898 ; WX 820 ; N uni1EDA ; G 1636
+U 7899 ; WX 602 ; N uni1EDB ; G 1637
+U 7900 ; WX 820 ; N uni1EDC ; G 1638
+U 7901 ; WX 602 ; N uni1EDD ; G 1639
+U 7902 ; WX 820 ; N uni1EDE ; G 1640
+U 7903 ; WX 602 ; N uni1EDF ; G 1641
+U 7904 ; WX 820 ; N uni1EE0 ; G 1642
+U 7905 ; WX 602 ; N uni1EE1 ; G 1643
+U 7906 ; WX 820 ; N uni1EE2 ; G 1644
+U 7907 ; WX 602 ; N uni1EE3 ; G 1645
+U 7908 ; WX 843 ; N uni1EE4 ; G 1646
+U 7909 ; WX 644 ; N uni1EE5 ; G 1647
+U 7910 ; WX 843 ; N uni1EE6 ; G 1648
+U 7911 ; WX 644 ; N uni1EE7 ; G 1649
+U 7912 ; WX 843 ; N uni1EE8 ; G 1650
+U 7913 ; WX 644 ; N uni1EE9 ; G 1651
+U 7914 ; WX 843 ; N uni1EEA ; G 1652
+U 7915 ; WX 644 ; N uni1EEB ; G 1653
+U 7916 ; WX 843 ; N uni1EEC ; G 1654
+U 7917 ; WX 644 ; N uni1EED ; G 1655
+U 7918 ; WX 843 ; N uni1EEE ; G 1656
+U 7919 ; WX 644 ; N uni1EEF ; G 1657
+U 7920 ; WX 843 ; N uni1EF0 ; G 1658
+U 7921 ; WX 644 ; N uni1EF1 ; G 1659
+U 7922 ; WX 660 ; N Ygrave ; G 1660
+U 7923 ; WX 565 ; N ygrave ; G 1661
+U 7924 ; WX 660 ; N uni1EF4 ; G 1662
+U 7925 ; WX 565 ; N uni1EF5 ; G 1663
+U 7926 ; WX 660 ; N uni1EF6 ; G 1664
+U 7927 ; WX 565 ; N uni1EF7 ; G 1665
+U 7928 ; WX 660 ; N uni1EF8 ; G 1666
+U 7929 ; WX 565 ; N uni1EF9 ; G 1667
+U 7930 ; WX 949 ; N uni1EFA ; G 1668
+U 7931 ; WX 581 ; N uni1EFB ; G 1669
+U 7936 ; WX 675 ; N uni1F00 ; G 1670
+U 7937 ; WX 675 ; N uni1F01 ; G 1671
+U 7938 ; WX 675 ; N uni1F02 ; G 1672
+U 7939 ; WX 675 ; N uni1F03 ; G 1673
+U 7940 ; WX 675 ; N uni1F04 ; G 1674
+U 7941 ; WX 675 ; N uni1F05 ; G 1675
+U 7942 ; WX 675 ; N uni1F06 ; G 1676
+U 7943 ; WX 675 ; N uni1F07 ; G 1677
+U 7944 ; WX 722 ; N uni1F08 ; G 1678
+U 7945 ; WX 722 ; N uni1F09 ; G 1679
+U 7946 ; WX 869 ; N uni1F0A ; G 1680
+U 7947 ; WX 869 ; N uni1F0B ; G 1681
+U 7948 ; WX 734 ; N uni1F0C ; G 1682
+U 7949 ; WX 763 ; N uni1F0D ; G 1683
+U 7950 ; WX 722 ; N uni1F0E ; G 1684
+U 7951 ; WX 722 ; N uni1F0F ; G 1685
+U 7952 ; WX 537 ; N uni1F10 ; G 1686
+U 7953 ; WX 537 ; N uni1F11 ; G 1687
+U 7954 ; WX 537 ; N uni1F12 ; G 1688
+U 7955 ; WX 537 ; N uni1F13 ; G 1689
+U 7956 ; WX 537 ; N uni1F14 ; G 1690
+U 7957 ; WX 537 ; N uni1F15 ; G 1691
+U 7960 ; WX 853 ; N uni1F18 ; G 1692
+U 7961 ; WX 841 ; N uni1F19 ; G 1693
+U 7962 ; WX 1067 ; N uni1F1A ; G 1694
+U 7963 ; WX 1077 ; N uni1F1B ; G 1695
+U 7964 ; WX 1008 ; N uni1F1C ; G 1696
+U 7965 ; WX 1035 ; N uni1F1D ; G 1697
+U 7968 ; WX 599 ; N uni1F20 ; G 1698
+U 7969 ; WX 599 ; N uni1F21 ; G 1699
+U 7970 ; WX 599 ; N uni1F22 ; G 1700
+U 7971 ; WX 599 ; N uni1F23 ; G 1701
+U 7972 ; WX 599 ; N uni1F24 ; G 1702
+U 7973 ; WX 599 ; N uni1F25 ; G 1703
+U 7974 ; WX 599 ; N uni1F26 ; G 1704
+U 7975 ; WX 599 ; N uni1F27 ; G 1705
+U 7976 ; WX 998 ; N uni1F28 ; G 1706
+U 7977 ; WX 992 ; N uni1F29 ; G 1707
+U 7978 ; WX 1212 ; N uni1F2A ; G 1708
+U 7979 ; WX 1224 ; N uni1F2B ; G 1709
+U 7980 ; WX 1159 ; N uni1F2C ; G 1710
+U 7981 ; WX 1183 ; N uni1F2D ; G 1711
+U 7982 ; WX 1098 ; N uni1F2E ; G 1712
+U 7983 ; WX 1095 ; N uni1F2F ; G 1713
+U 7984 ; WX 392 ; N uni1F30 ; G 1714
+U 7985 ; WX 392 ; N uni1F31 ; G 1715
+U 7986 ; WX 392 ; N uni1F32 ; G 1716
+U 7987 ; WX 392 ; N uni1F33 ; G 1717
+U 7988 ; WX 392 ; N uni1F34 ; G 1718
+U 7989 ; WX 392 ; N uni1F35 ; G 1719
+U 7990 ; WX 392 ; N uni1F36 ; G 1720
+U 7991 ; WX 392 ; N uni1F37 ; G 1721
+U 7992 ; WX 521 ; N uni1F38 ; G 1722
+U 7993 ; WX 512 ; N uni1F39 ; G 1723
+U 7994 ; WX 735 ; N uni1F3A ; G 1724
+U 7995 ; WX 738 ; N uni1F3B ; G 1725
+U 7996 ; WX 679 ; N uni1F3C ; G 1726
+U 7997 ; WX 706 ; N uni1F3D ; G 1727
+U 7998 ; WX 624 ; N uni1F3E ; G 1728
+U 7999 ; WX 615 ; N uni1F3F ; G 1729
+U 8000 ; WX 602 ; N uni1F40 ; G 1730
+U 8001 ; WX 602 ; N uni1F41 ; G 1731
+U 8002 ; WX 602 ; N uni1F42 ; G 1732
+U 8003 ; WX 602 ; N uni1F43 ; G 1733
+U 8004 ; WX 602 ; N uni1F44 ; G 1734
+U 8005 ; WX 602 ; N uni1F45 ; G 1735
+U 8008 ; WX 820 ; N uni1F48 ; G 1736
+U 8009 ; WX 859 ; N uni1F49 ; G 1737
+U 8010 ; WX 1120 ; N uni1F4A ; G 1738
+U 8011 ; WX 1127 ; N uni1F4B ; G 1739
+U 8012 ; WX 937 ; N uni1F4C ; G 1740
+U 8013 ; WX 964 ; N uni1F4D ; G 1741
+U 8016 ; WX 608 ; N uni1F50 ; G 1742
+U 8017 ; WX 608 ; N uni1F51 ; G 1743
+U 8018 ; WX 608 ; N uni1F52 ; G 1744
+U 8019 ; WX 608 ; N uni1F53 ; G 1745
+U 8020 ; WX 608 ; N uni1F54 ; G 1746
+U 8021 ; WX 608 ; N uni1F55 ; G 1747
+U 8022 ; WX 608 ; N uni1F56 ; G 1748
+U 8023 ; WX 608 ; N uni1F57 ; G 1749
+U 8025 ; WX 851 ; N uni1F59 ; G 1750
+U 8027 ; WX 1079 ; N uni1F5B ; G 1751
+U 8029 ; WX 1044 ; N uni1F5D ; G 1752
+U 8031 ; WX 953 ; N uni1F5F ; G 1753
+U 8032 ; WX 815 ; N uni1F60 ; G 1754
+U 8033 ; WX 815 ; N uni1F61 ; G 1755
+U 8034 ; WX 815 ; N uni1F62 ; G 1756
+U 8035 ; WX 815 ; N uni1F63 ; G 1757
+U 8036 ; WX 815 ; N uni1F64 ; G 1758
+U 8037 ; WX 815 ; N uni1F65 ; G 1759
+U 8038 ; WX 815 ; N uni1F66 ; G 1760
+U 8039 ; WX 815 ; N uni1F67 ; G 1761
+U 8040 ; WX 829 ; N uni1F68 ; G 1762
+U 8041 ; WX 870 ; N uni1F69 ; G 1763
+U 8042 ; WX 1131 ; N uni1F6A ; G 1764
+U 8043 ; WX 1137 ; N uni1F6B ; G 1765
+U 8044 ; WX 946 ; N uni1F6C ; G 1766
+U 8045 ; WX 976 ; N uni1F6D ; G 1767
+U 8046 ; WX 938 ; N uni1F6E ; G 1768
+U 8047 ; WX 970 ; N uni1F6F ; G 1769
+U 8048 ; WX 675 ; N uni1F70 ; G 1770
+U 8049 ; WX 675 ; N uni1F71 ; G 1771
+U 8050 ; WX 537 ; N uni1F72 ; G 1772
+U 8051 ; WX 537 ; N uni1F73 ; G 1773
+U 8052 ; WX 599 ; N uni1F74 ; G 1774
+U 8053 ; WX 599 ; N uni1F75 ; G 1775
+U 8054 ; WX 392 ; N uni1F76 ; G 1776
+U 8055 ; WX 392 ; N uni1F77 ; G 1777
+U 8056 ; WX 602 ; N uni1F78 ; G 1778
+U 8057 ; WX 602 ; N uni1F79 ; G 1779
+U 8058 ; WX 608 ; N uni1F7A ; G 1780
+U 8059 ; WX 608 ; N uni1F7B ; G 1781
+U 8060 ; WX 815 ; N uni1F7C ; G 1782
+U 8061 ; WX 815 ; N uni1F7D ; G 1783
+U 8064 ; WX 675 ; N uni1F80 ; G 1784
+U 8065 ; WX 675 ; N uni1F81 ; G 1785
+U 8066 ; WX 675 ; N uni1F82 ; G 1786
+U 8067 ; WX 675 ; N uni1F83 ; G 1787
+U 8068 ; WX 675 ; N uni1F84 ; G 1788
+U 8069 ; WX 675 ; N uni1F85 ; G 1789
+U 8070 ; WX 675 ; N uni1F86 ; G 1790
+U 8071 ; WX 675 ; N uni1F87 ; G 1791
+U 8072 ; WX 722 ; N uni1F88 ; G 1792
+U 8073 ; WX 722 ; N uni1F89 ; G 1793
+U 8074 ; WX 869 ; N uni1F8A ; G 1794
+U 8075 ; WX 869 ; N uni1F8B ; G 1795
+U 8076 ; WX 734 ; N uni1F8C ; G 1796
+U 8077 ; WX 763 ; N uni1F8D ; G 1797
+U 8078 ; WX 722 ; N uni1F8E ; G 1798
+U 8079 ; WX 722 ; N uni1F8F ; G 1799
+U 8080 ; WX 599 ; N uni1F90 ; G 1800
+U 8081 ; WX 599 ; N uni1F91 ; G 1801
+U 8082 ; WX 599 ; N uni1F92 ; G 1802
+U 8083 ; WX 599 ; N uni1F93 ; G 1803
+U 8084 ; WX 599 ; N uni1F94 ; G 1804
+U 8085 ; WX 599 ; N uni1F95 ; G 1805
+U 8086 ; WX 599 ; N uni1F96 ; G 1806
+U 8087 ; WX 599 ; N uni1F97 ; G 1807
+U 8088 ; WX 998 ; N uni1F98 ; G 1808
+U 8089 ; WX 992 ; N uni1F99 ; G 1809
+U 8090 ; WX 1212 ; N uni1F9A ; G 1810
+U 8091 ; WX 1224 ; N uni1F9B ; G 1811
+U 8092 ; WX 1159 ; N uni1F9C ; G 1812
+U 8093 ; WX 1183 ; N uni1F9D ; G 1813
+U 8094 ; WX 1098 ; N uni1F9E ; G 1814
+U 8095 ; WX 1095 ; N uni1F9F ; G 1815
+U 8096 ; WX 815 ; N uni1FA0 ; G 1816
+U 8097 ; WX 815 ; N uni1FA1 ; G 1817
+U 8098 ; WX 815 ; N uni1FA2 ; G 1818
+U 8099 ; WX 815 ; N uni1FA3 ; G 1819
+U 8100 ; WX 815 ; N uni1FA4 ; G 1820
+U 8101 ; WX 815 ; N uni1FA5 ; G 1821
+U 8102 ; WX 815 ; N uni1FA6 ; G 1822
+U 8103 ; WX 815 ; N uni1FA7 ; G 1823
+U 8104 ; WX 829 ; N uni1FA8 ; G 1824
+U 8105 ; WX 870 ; N uni1FA9 ; G 1825
+U 8106 ; WX 1131 ; N uni1FAA ; G 1826
+U 8107 ; WX 1137 ; N uni1FAB ; G 1827
+U 8108 ; WX 946 ; N uni1FAC ; G 1828
+U 8109 ; WX 976 ; N uni1FAD ; G 1829
+U 8110 ; WX 938 ; N uni1FAE ; G 1830
+U 8111 ; WX 970 ; N uni1FAF ; G 1831
+U 8112 ; WX 675 ; N uni1FB0 ; G 1832
+U 8113 ; WX 675 ; N uni1FB1 ; G 1833
+U 8114 ; WX 675 ; N uni1FB2 ; G 1834
+U 8115 ; WX 675 ; N uni1FB3 ; G 1835
+U 8116 ; WX 675 ; N uni1FB4 ; G 1836
+U 8118 ; WX 675 ; N uni1FB6 ; G 1837
+U 8119 ; WX 675 ; N uni1FB7 ; G 1838
+U 8120 ; WX 722 ; N uni1FB8 ; G 1839
+U 8121 ; WX 722 ; N uni1FB9 ; G 1840
+U 8122 ; WX 722 ; N uni1FBA ; G 1841
+U 8123 ; WX 722 ; N uni1FBB ; G 1842
+U 8124 ; WX 722 ; N uni1FBC ; G 1843
+U 8125 ; WX 500 ; N uni1FBD ; G 1844
+U 8126 ; WX 500 ; N uni1FBE ; G 1845
+U 8127 ; WX 500 ; N uni1FBF ; G 1846
+U 8128 ; WX 500 ; N uni1FC0 ; G 1847
+U 8129 ; WX 500 ; N uni1FC1 ; G 1848
+U 8130 ; WX 599 ; N uni1FC2 ; G 1849
+U 8131 ; WX 599 ; N uni1FC3 ; G 1850
+U 8132 ; WX 599 ; N uni1FC4 ; G 1851
+U 8134 ; WX 599 ; N uni1FC6 ; G 1852
+U 8135 ; WX 599 ; N uni1FC7 ; G 1853
+U 8136 ; WX 912 ; N uni1FC8 ; G 1854
+U 8137 ; WX 900 ; N uni1FC9 ; G 1855
+U 8138 ; WX 1063 ; N uni1FCA ; G 1856
+U 8139 ; WX 1039 ; N uni1FCB ; G 1857
+U 8140 ; WX 872 ; N uni1FCC ; G 1858
+U 8141 ; WX 500 ; N uni1FCD ; G 1859
+U 8142 ; WX 500 ; N uni1FCE ; G 1860
+U 8143 ; WX 500 ; N uni1FCF ; G 1861
+U 8144 ; WX 392 ; N uni1FD0 ; G 1862
+U 8145 ; WX 392 ; N uni1FD1 ; G 1863
+U 8146 ; WX 392 ; N uni1FD2 ; G 1864
+U 8147 ; WX 392 ; N uni1FD3 ; G 1865
+U 8150 ; WX 392 ; N uni1FD6 ; G 1866
+U 8151 ; WX 392 ; N uni1FD7 ; G 1867
+U 8152 ; WX 395 ; N uni1FD8 ; G 1868
+U 8153 ; WX 395 ; N uni1FD9 ; G 1869
+U 8154 ; WX 588 ; N uni1FDA ; G 1870
+U 8155 ; WX 562 ; N uni1FDB ; G 1871
+U 8157 ; WX 500 ; N uni1FDD ; G 1872
+U 8158 ; WX 500 ; N uni1FDE ; G 1873
+U 8159 ; WX 500 ; N uni1FDF ; G 1874
+U 8160 ; WX 608 ; N uni1FE0 ; G 1875
+U 8161 ; WX 608 ; N uni1FE1 ; G 1876
+U 8162 ; WX 608 ; N uni1FE2 ; G 1877
+U 8163 ; WX 608 ; N uni1FE3 ; G 1878
+U 8164 ; WX 588 ; N uni1FE4 ; G 1879
+U 8165 ; WX 588 ; N uni1FE5 ; G 1880
+U 8166 ; WX 608 ; N uni1FE6 ; G 1881
+U 8167 ; WX 608 ; N uni1FE7 ; G 1882
+U 8168 ; WX 660 ; N uni1FE8 ; G 1883
+U 8169 ; WX 660 ; N uni1FE9 ; G 1884
+U 8170 ; WX 921 ; N uni1FEA ; G 1885
+U 8171 ; WX 897 ; N uni1FEB ; G 1886
+U 8172 ; WX 790 ; N uni1FEC ; G 1887
+U 8173 ; WX 500 ; N uni1FED ; G 1888
+U 8174 ; WX 500 ; N uni1FEE ; G 1889
+U 8175 ; WX 500 ; N uni1FEF ; G 1890
+U 8178 ; WX 815 ; N uni1FF2 ; G 1891
+U 8179 ; WX 815 ; N uni1FF3 ; G 1892
+U 8180 ; WX 815 ; N uni1FF4 ; G 1893
+U 8182 ; WX 815 ; N uni1FF6 ; G 1894
+U 8183 ; WX 815 ; N uni1FF7 ; G 1895
+U 8184 ; WX 961 ; N uni1FF8 ; G 1896
+U 8185 ; WX 835 ; N uni1FF9 ; G 1897
+U 8186 ; WX 984 ; N uni1FFA ; G 1898
+U 8187 ; WX 853 ; N uni1FFB ; G 1899
+U 8188 ; WX 829 ; N uni1FFC ; G 1900
+U 8189 ; WX 500 ; N uni1FFD ; G 1901
+U 8190 ; WX 500 ; N uni1FFE ; G 1902
+U 8192 ; WX 500 ; N uni2000 ; G 1903
+U 8193 ; WX 1000 ; N uni2001 ; G 1904
+U 8194 ; WX 500 ; N uni2002 ; G 1905
+U 8195 ; WX 1000 ; N uni2003 ; G 1906
+U 8196 ; WX 330 ; N uni2004 ; G 1907
+U 8197 ; WX 250 ; N uni2005 ; G 1908
+U 8198 ; WX 167 ; N uni2006 ; G 1909
+U 8199 ; WX 636 ; N uni2007 ; G 1910
+U 8200 ; WX 318 ; N uni2008 ; G 1911
+U 8201 ; WX 200 ; N uni2009 ; G 1912
+U 8202 ; WX 100 ; N uni200A ; G 1913
+U 8203 ; WX 0 ; N uni200B ; G 1914
+U 8204 ; WX 0 ; N uni200C ; G 1915
+U 8205 ; WX 0 ; N uni200D ; G 1916
+U 8206 ; WX 0 ; N uni200E ; G 1917
+U 8207 ; WX 0 ; N uni200F ; G 1918
+U 8208 ; WX 338 ; N uni2010 ; G 1919
+U 8209 ; WX 338 ; N uni2011 ; G 1920
+U 8210 ; WX 636 ; N figuredash ; G 1921
+U 8211 ; WX 500 ; N endash ; G 1922
+U 8212 ; WX 1000 ; N emdash ; G 1923
+U 8213 ; WX 1000 ; N uni2015 ; G 1924
+U 8214 ; WX 500 ; N uni2016 ; G 1925
+U 8215 ; WX 500 ; N underscoredbl ; G 1926
+U 8216 ; WX 318 ; N quoteleft ; G 1927
+U 8217 ; WX 318 ; N quoteright ; G 1928
+U 8218 ; WX 318 ; N quotesinglbase ; G 1929
+U 8219 ; WX 318 ; N quotereversed ; G 1930
+U 8220 ; WX 511 ; N quotedblleft ; G 1931
+U 8221 ; WX 511 ; N quotedblright ; G 1932
+U 8222 ; WX 518 ; N quotedblbase ; G 1933
+U 8223 ; WX 511 ; N uni201F ; G 1934
+U 8224 ; WX 500 ; N dagger ; G 1935
+U 8225 ; WX 500 ; N daggerdbl ; G 1936
+U 8226 ; WX 590 ; N bullet ; G 1937
+U 8227 ; WX 590 ; N uni2023 ; G 1938
+U 8228 ; WX 334 ; N onedotenleader ; G 1939
+U 8229 ; WX 667 ; N twodotenleader ; G 1940
+U 8230 ; WX 1000 ; N ellipsis ; G 1941
+U 8234 ; WX 0 ; N uni202A ; G 1942
+U 8235 ; WX 0 ; N uni202B ; G 1943
+U 8236 ; WX 0 ; N uni202C ; G 1944
+U 8237 ; WX 0 ; N uni202D ; G 1945
+U 8238 ; WX 0 ; N uni202E ; G 1946
+U 8239 ; WX 200 ; N uni202F ; G 1947
+U 8240 ; WX 1342 ; N perthousand ; G 1948
+U 8241 ; WX 1734 ; N uni2031 ; G 1949
+U 8242 ; WX 227 ; N minute ; G 1950
+U 8243 ; WX 374 ; N second ; G 1951
+U 8244 ; WX 520 ; N uni2034 ; G 1952
+U 8245 ; WX 227 ; N uni2035 ; G 1953
+U 8246 ; WX 374 ; N uni2036 ; G 1954
+U 8247 ; WX 520 ; N uni2037 ; G 1955
+U 8248 ; WX 339 ; N uni2038 ; G 1956
+U 8249 ; WX 400 ; N guilsinglleft ; G 1957
+U 8250 ; WX 400 ; N guilsinglright ; G 1958
+U 8252 ; WX 527 ; N exclamdbl ; G 1959
+U 8253 ; WX 536 ; N uni203D ; G 1960
+U 8254 ; WX 500 ; N uni203E ; G 1961
+U 8258 ; WX 1000 ; N uni2042 ; G 1962
+U 8260 ; WX 167 ; N fraction ; G 1963
+U 8261 ; WX 390 ; N uni2045 ; G 1964
+U 8262 ; WX 390 ; N uni2046 ; G 1965
+U 8263 ; WX 976 ; N uni2047 ; G 1966
+U 8264 ; WX 753 ; N uni2048 ; G 1967
+U 8265 ; WX 753 ; N uni2049 ; G 1968
+U 8267 ; WX 636 ; N uni204B ; G 1969
+U 8268 ; WX 500 ; N uni204C ; G 1970
+U 8269 ; WX 500 ; N uni204D ; G 1971
+U 8270 ; WX 500 ; N uni204E ; G 1972
+U 8271 ; WX 337 ; N uni204F ; G 1973
+U 8273 ; WX 500 ; N uni2051 ; G 1974
+U 8274 ; WX 450 ; N uni2052 ; G 1975
+U 8275 ; WX 1000 ; N uni2053 ; G 1976
+U 8279 ; WX 663 ; N uni2057 ; G 1977
+U 8287 ; WX 222 ; N uni205F ; G 1978
+U 8288 ; WX 0 ; N uni2060 ; G 1979
+U 8289 ; WX 0 ; N uni2061 ; G 1980
+U 8290 ; WX 0 ; N uni2062 ; G 1981
+U 8291 ; WX 0 ; N uni2063 ; G 1982
+U 8292 ; WX 0 ; N uni2064 ; G 1983
+U 8298 ; WX 0 ; N uni206A ; G 1984
+U 8299 ; WX 0 ; N uni206B ; G 1985
+U 8300 ; WX 0 ; N uni206C ; G 1986
+U 8301 ; WX 0 ; N uni206D ; G 1987
+U 8302 ; WX 0 ; N uni206E ; G 1988
+U 8303 ; WX 0 ; N uni206F ; G 1989
+U 8304 ; WX 401 ; N uni2070 ; G 1990
+U 8305 ; WX 201 ; N uni2071 ; G 1991
+U 8308 ; WX 401 ; N uni2074 ; G 1992
+U 8309 ; WX 401 ; N uni2075 ; G 1993
+U 8310 ; WX 401 ; N uni2076 ; G 1994
+U 8311 ; WX 401 ; N uni2077 ; G 1995
+U 8312 ; WX 401 ; N uni2078 ; G 1996
+U 8313 ; WX 401 ; N uni2079 ; G 1997
+U 8314 ; WX 528 ; N uni207A ; G 1998
+U 8315 ; WX 528 ; N uni207B ; G 1999
+U 8316 ; WX 528 ; N uni207C ; G 2000
+U 8317 ; WX 246 ; N uni207D ; G 2001
+U 8318 ; WX 246 ; N uni207E ; G 2002
+U 8319 ; WX 405 ; N uni207F ; G 2003
+U 8320 ; WX 401 ; N uni2080 ; G 2004
+U 8321 ; WX 401 ; N uni2081 ; G 2005
+U 8322 ; WX 401 ; N uni2082 ; G 2006
+U 8323 ; WX 401 ; N uni2083 ; G 2007
+U 8324 ; WX 401 ; N uni2084 ; G 2008
+U 8325 ; WX 401 ; N uni2085 ; G 2009
+U 8326 ; WX 401 ; N uni2086 ; G 2010
+U 8327 ; WX 401 ; N uni2087 ; G 2011
+U 8328 ; WX 401 ; N uni2088 ; G 2012
+U 8329 ; WX 401 ; N uni2089 ; G 2013
+U 8330 ; WX 528 ; N uni208A ; G 2014
+U 8331 ; WX 528 ; N uni208B ; G 2015
+U 8332 ; WX 528 ; N uni208C ; G 2016
+U 8333 ; WX 246 ; N uni208D ; G 2017
+U 8334 ; WX 246 ; N uni208E ; G 2018
+U 8336 ; WX 375 ; N uni2090 ; G 2019
+U 8337 ; WX 387 ; N uni2091 ; G 2020
+U 8338 ; WX 385 ; N uni2092 ; G 2021
+U 8339 ; WX 355 ; N uni2093 ; G 2022
+U 8340 ; WX 387 ; N uni2094 ; G 2023
+U 8341 ; WX 433 ; N uni2095 ; G 2024
+U 8342 ; WX 365 ; N uni2096 ; G 2025
+U 8343 ; WX 243 ; N uni2097 ; G 2026
+U 8344 ; WX 613 ; N uni2098 ; G 2027
+U 8345 ; WX 405 ; N uni2099 ; G 2028
+U 8346 ; WX 400 ; N uni209A ; G 2029
+U 8347 ; WX 337 ; N uni209B ; G 2030
+U 8348 ; WX 247 ; N uni209C ; G 2031
+U 8358 ; WX 636 ; N uni20A6 ; G 2032
+U 8364 ; WX 636 ; N Euro ; G 2033
+U 8367 ; WX 1057 ; N uni20AF ; G 2034
+U 8369 ; WX 706 ; N uni20B1 ; G 2035
+U 8372 ; WX 780 ; N uni20B4 ; G 2036
+U 8373 ; WX 636 ; N uni20B5 ; G 2037
+U 8376 ; WX 636 ; N uni20B8 ; G 2038
+U 8377 ; WX 636 ; N uni20B9 ; G 2039
+U 8378 ; WX 636 ; N uni20BA ; G 2040
+U 8381 ; WX 636 ; N uni20BD ; G 2041
+U 8451 ; WX 1119 ; N uni2103 ; G 2042
+U 8457 ; WX 1047 ; N uni2109 ; G 2043
+U 8462 ; WX 644 ; N uni210E ; G 2044
+U 8463 ; WX 644 ; N uni210F ; G 2045
+U 8470 ; WX 946 ; N uni2116 ; G 2046
+U 8482 ; WX 1000 ; N trademark ; G 2047
+U 8486 ; WX 829 ; N uni2126 ; G 2048
+U 8487 ; WX 829 ; N uni2127 ; G 2049
+U 8490 ; WX 747 ; N uni212A ; G 2050
+U 8491 ; WX 722 ; N uni212B ; G 2051
+U 8498 ; WX 694 ; N uni2132 ; G 2052
+U 8513 ; WX 775 ; N uni2141 ; G 2053
+U 8514 ; WX 557 ; N uni2142 ; G 2054
+U 8515 ; WX 557 ; N uni2143 ; G 2055
+U 8516 ; WX 611 ; N uni2144 ; G 2056
+U 8523 ; WX 890 ; N uni214B ; G 2057
+U 8526 ; WX 514 ; N uni214E ; G 2058
+U 8528 ; WX 969 ; N uni2150 ; G 2059
+U 8529 ; WX 969 ; N uni2151 ; G 2060
+U 8530 ; WX 1370 ; N uni2152 ; G 2061
+U 8531 ; WX 969 ; N onethird ; G 2062
+U 8532 ; WX 969 ; N twothirds ; G 2063
+U 8533 ; WX 969 ; N uni2155 ; G 2064
+U 8534 ; WX 969 ; N uni2156 ; G 2065
+U 8535 ; WX 969 ; N uni2157 ; G 2066
+U 8536 ; WX 969 ; N uni2158 ; G 2067
+U 8537 ; WX 969 ; N uni2159 ; G 2068
+U 8538 ; WX 969 ; N uni215A ; G 2069
+U 8539 ; WX 969 ; N oneeighth ; G 2070
+U 8540 ; WX 969 ; N threeeighths ; G 2071
+U 8541 ; WX 969 ; N fiveeighths ; G 2072
+U 8542 ; WX 969 ; N seveneighths ; G 2073
+U 8543 ; WX 568 ; N uni215F ; G 2074
+U 8544 ; WX 395 ; N uni2160 ; G 2075
+U 8545 ; WX 680 ; N uni2161 ; G 2076
+U 8546 ; WX 964 ; N uni2162 ; G 2077
+U 8547 ; WX 999 ; N uni2163 ; G 2078
+U 8548 ; WX 722 ; N uni2164 ; G 2079
+U 8549 ; WX 1006 ; N uni2165 ; G 2080
+U 8550 ; WX 1291 ; N uni2166 ; G 2081
+U 8551 ; WX 1575 ; N uni2167 ; G 2082
+U 8552 ; WX 965 ; N uni2168 ; G 2083
+U 8553 ; WX 712 ; N uni2169 ; G 2084
+U 8554 ; WX 969 ; N uni216A ; G 2085
+U 8555 ; WX 1253 ; N uni216B ; G 2086
+U 8556 ; WX 664 ; N uni216C ; G 2087
+U 8557 ; WX 765 ; N uni216D ; G 2088
+U 8558 ; WX 802 ; N uni216E ; G 2089
+U 8559 ; WX 1024 ; N uni216F ; G 2090
+U 8560 ; WX 320 ; N uni2170 ; G 2091
+U 8561 ; WX 640 ; N uni2171 ; G 2092
+U 8562 ; WX 959 ; N uni2172 ; G 2093
+U 8563 ; WX 885 ; N uni2173 ; G 2094
+U 8564 ; WX 565 ; N uni2174 ; G 2095
+U 8565 ; WX 885 ; N uni2175 ; G 2096
+U 8566 ; WX 1205 ; N uni2176 ; G 2097
+U 8567 ; WX 1524 ; N uni2177 ; G 2098
+U 8568 ; WX 884 ; N uni2178 ; G 2099
+U 8569 ; WX 564 ; N uni2179 ; G 2100
+U 8570 ; WX 884 ; N uni217A ; G 2101
+U 8571 ; WX 1204 ; N uni217B ; G 2102
+U 8572 ; WX 320 ; N uni217C ; G 2103
+U 8573 ; WX 560 ; N uni217D ; G 2104
+U 8574 ; WX 640 ; N uni217E ; G 2105
+U 8575 ; WX 948 ; N uni217F ; G 2106
+U 8576 ; WX 1206 ; N uni2180 ; G 2107
+U 8577 ; WX 802 ; N uni2181 ; G 2108
+U 8578 ; WX 1206 ; N uni2182 ; G 2109
+U 8579 ; WX 765 ; N uni2183 ; G 2110
+U 8580 ; WX 560 ; N uni2184 ; G 2111
+U 8581 ; WX 765 ; N uni2185 ; G 2112
+U 8585 ; WX 969 ; N uni2189 ; G 2113
+U 8592 ; WX 838 ; N arrowleft ; G 2114
+U 8593 ; WX 838 ; N arrowup ; G 2115
+U 8594 ; WX 838 ; N arrowright ; G 2116
+U 8595 ; WX 838 ; N arrowdown ; G 2117
+U 8596 ; WX 838 ; N arrowboth ; G 2118
+U 8597 ; WX 838 ; N arrowupdn ; G 2119
+U 8598 ; WX 838 ; N uni2196 ; G 2120
+U 8599 ; WX 838 ; N uni2197 ; G 2121
+U 8600 ; WX 838 ; N uni2198 ; G 2122
+U 8601 ; WX 838 ; N uni2199 ; G 2123
+U 8602 ; WX 838 ; N uni219A ; G 2124
+U 8603 ; WX 838 ; N uni219B ; G 2125
+U 8604 ; WX 838 ; N uni219C ; G 2126
+U 8605 ; WX 838 ; N uni219D ; G 2127
+U 8606 ; WX 838 ; N uni219E ; G 2128
+U 8607 ; WX 838 ; N uni219F ; G 2129
+U 8608 ; WX 838 ; N uni21A0 ; G 2130
+U 8609 ; WX 838 ; N uni21A1 ; G 2131
+U 8610 ; WX 838 ; N uni21A2 ; G 2132
+U 8611 ; WX 838 ; N uni21A3 ; G 2133
+U 8612 ; WX 838 ; N uni21A4 ; G 2134
+U 8613 ; WX 838 ; N uni21A5 ; G 2135
+U 8614 ; WX 838 ; N uni21A6 ; G 2136
+U 8615 ; WX 838 ; N uni21A7 ; G 2137
+U 8616 ; WX 838 ; N arrowupdnbse ; G 2138
+U 8617 ; WX 838 ; N uni21A9 ; G 2139
+U 8618 ; WX 838 ; N uni21AA ; G 2140
+U 8619 ; WX 838 ; N uni21AB ; G 2141
+U 8620 ; WX 838 ; N uni21AC ; G 2142
+U 8621 ; WX 838 ; N uni21AD ; G 2143
+U 8622 ; WX 838 ; N uni21AE ; G 2144
+U 8623 ; WX 838 ; N uni21AF ; G 2145
+U 8624 ; WX 838 ; N uni21B0 ; G 2146
+U 8625 ; WX 838 ; N uni21B1 ; G 2147
+U 8626 ; WX 838 ; N uni21B2 ; G 2148
+U 8627 ; WX 838 ; N uni21B3 ; G 2149
+U 8628 ; WX 838 ; N uni21B4 ; G 2150
+U 8629 ; WX 838 ; N carriagereturn ; G 2151
+U 8630 ; WX 838 ; N uni21B6 ; G 2152
+U 8631 ; WX 838 ; N uni21B7 ; G 2153
+U 8632 ; WX 838 ; N uni21B8 ; G 2154
+U 8633 ; WX 838 ; N uni21B9 ; G 2155
+U 8634 ; WX 838 ; N uni21BA ; G 2156
+U 8635 ; WX 838 ; N uni21BB ; G 2157
+U 8636 ; WX 838 ; N uni21BC ; G 2158
+U 8637 ; WX 838 ; N uni21BD ; G 2159
+U 8638 ; WX 838 ; N uni21BE ; G 2160
+U 8639 ; WX 838 ; N uni21BF ; G 2161
+U 8640 ; WX 838 ; N uni21C0 ; G 2162
+U 8641 ; WX 838 ; N uni21C1 ; G 2163
+U 8642 ; WX 838 ; N uni21C2 ; G 2164
+U 8643 ; WX 838 ; N uni21C3 ; G 2165
+U 8644 ; WX 838 ; N uni21C4 ; G 2166
+U 8645 ; WX 838 ; N uni21C5 ; G 2167
+U 8646 ; WX 838 ; N uni21C6 ; G 2168
+U 8647 ; WX 838 ; N uni21C7 ; G 2169
+U 8648 ; WX 838 ; N uni21C8 ; G 2170
+U 8649 ; WX 838 ; N uni21C9 ; G 2171
+U 8650 ; WX 838 ; N uni21CA ; G 2172
+U 8651 ; WX 838 ; N uni21CB ; G 2173
+U 8652 ; WX 838 ; N uni21CC ; G 2174
+U 8653 ; WX 838 ; N uni21CD ; G 2175
+U 8654 ; WX 838 ; N uni21CE ; G 2176
+U 8655 ; WX 838 ; N uni21CF ; G 2177
+U 8656 ; WX 838 ; N arrowdblleft ; G 2178
+U 8657 ; WX 838 ; N arrowdblup ; G 2179
+U 8658 ; WX 838 ; N arrowdblright ; G 2180
+U 8659 ; WX 838 ; N arrowdbldown ; G 2181
+U 8660 ; WX 838 ; N arrowdblboth ; G 2182
+U 8661 ; WX 838 ; N uni21D5 ; G 2183
+U 8662 ; WX 838 ; N uni21D6 ; G 2184
+U 8663 ; WX 838 ; N uni21D7 ; G 2185
+U 8664 ; WX 838 ; N uni21D8 ; G 2186
+U 8665 ; WX 838 ; N uni21D9 ; G 2187
+U 8666 ; WX 838 ; N uni21DA ; G 2188
+U 8667 ; WX 838 ; N uni21DB ; G 2189
+U 8668 ; WX 838 ; N uni21DC ; G 2190
+U 8669 ; WX 838 ; N uni21DD ; G 2191
+U 8670 ; WX 838 ; N uni21DE ; G 2192
+U 8671 ; WX 838 ; N uni21DF ; G 2193
+U 8672 ; WX 838 ; N uni21E0 ; G 2194
+U 8673 ; WX 838 ; N uni21E1 ; G 2195
+U 8674 ; WX 838 ; N uni21E2 ; G 2196
+U 8675 ; WX 838 ; N uni21E3 ; G 2197
+U 8676 ; WX 838 ; N uni21E4 ; G 2198
+U 8677 ; WX 838 ; N uni21E5 ; G 2199
+U 8678 ; WX 838 ; N uni21E6 ; G 2200
+U 8679 ; WX 838 ; N uni21E7 ; G 2201
+U 8680 ; WX 838 ; N uni21E8 ; G 2202
+U 8681 ; WX 838 ; N uni21E9 ; G 2203
+U 8682 ; WX 838 ; N uni21EA ; G 2204
+U 8683 ; WX 838 ; N uni21EB ; G 2205
+U 8684 ; WX 838 ; N uni21EC ; G 2206
+U 8685 ; WX 838 ; N uni21ED ; G 2207
+U 8686 ; WX 838 ; N uni21EE ; G 2208
+U 8687 ; WX 838 ; N uni21EF ; G 2209
+U 8688 ; WX 838 ; N uni21F0 ; G 2210
+U 8689 ; WX 838 ; N uni21F1 ; G 2211
+U 8690 ; WX 838 ; N uni21F2 ; G 2212
+U 8691 ; WX 838 ; N uni21F3 ; G 2213
+U 8692 ; WX 838 ; N uni21F4 ; G 2214
+U 8693 ; WX 838 ; N uni21F5 ; G 2215
+U 8694 ; WX 838 ; N uni21F6 ; G 2216
+U 8695 ; WX 838 ; N uni21F7 ; G 2217
+U 8696 ; WX 838 ; N uni21F8 ; G 2218
+U 8697 ; WX 838 ; N uni21F9 ; G 2219
+U 8698 ; WX 838 ; N uni21FA ; G 2220
+U 8699 ; WX 838 ; N uni21FB ; G 2221
+U 8700 ; WX 838 ; N uni21FC ; G 2222
+U 8701 ; WX 838 ; N uni21FD ; G 2223
+U 8702 ; WX 838 ; N uni21FE ; G 2224
+U 8703 ; WX 838 ; N uni21FF ; G 2225
+U 8704 ; WX 604 ; N universal ; G 2226
+U 8706 ; WX 517 ; N partialdiff ; G 2227
+U 8707 ; WX 542 ; N existential ; G 2228
+U 8708 ; WX 542 ; N uni2204 ; G 2229
+U 8710 ; WX 698 ; N increment ; G 2230
+U 8711 ; WX 698 ; N gradient ; G 2231
+U 8712 ; WX 740 ; N element ; G 2232
+U 8713 ; WX 740 ; N notelement ; G 2233
+U 8715 ; WX 740 ; N suchthat ; G 2234
+U 8716 ; WX 740 ; N uni220C ; G 2235
+U 8719 ; WX 796 ; N product ; G 2236
+U 8720 ; WX 796 ; N uni2210 ; G 2237
+U 8721 ; WX 714 ; N summation ; G 2238
+U 8722 ; WX 838 ; N minus ; G 2239
+U 8723 ; WX 838 ; N uni2213 ; G 2240
+U 8724 ; WX 838 ; N uni2214 ; G 2241
+U 8725 ; WX 337 ; N uni2215 ; G 2242
+U 8727 ; WX 680 ; N asteriskmath ; G 2243
+U 8728 ; WX 490 ; N uni2218 ; G 2244
+U 8729 ; WX 490 ; N uni2219 ; G 2245
+U 8730 ; WX 637 ; N radical ; G 2246
+U 8731 ; WX 637 ; N uni221B ; G 2247
+U 8732 ; WX 637 ; N uni221C ; G 2248
+U 8733 ; WX 677 ; N proportional ; G 2249
+U 8734 ; WX 833 ; N infinity ; G 2250
+U 8735 ; WX 838 ; N orthogonal ; G 2251
+U 8736 ; WX 838 ; N angle ; G 2252
+U 8739 ; WX 291 ; N uni2223 ; G 2253
+U 8740 ; WX 479 ; N uni2224 ; G 2254
+U 8741 ; WX 462 ; N uni2225 ; G 2255
+U 8742 ; WX 634 ; N uni2226 ; G 2256
+U 8743 ; WX 732 ; N logicaland ; G 2257
+U 8744 ; WX 732 ; N logicalor ; G 2258
+U 8745 ; WX 838 ; N intersection ; G 2259
+U 8746 ; WX 838 ; N union ; G 2260
+U 8747 ; WX 521 ; N integral ; G 2261
+U 8748 ; WX 852 ; N uni222C ; G 2262
+U 8749 ; WX 1182 ; N uni222D ; G 2263
+U 8760 ; WX 838 ; N uni2238 ; G 2264
+U 8761 ; WX 838 ; N uni2239 ; G 2265
+U 8762 ; WX 838 ; N uni223A ; G 2266
+U 8763 ; WX 838 ; N uni223B ; G 2267
+U 8764 ; WX 838 ; N similar ; G 2268
+U 8765 ; WX 838 ; N uni223D ; G 2269
+U 8770 ; WX 838 ; N uni2242 ; G 2270
+U 8771 ; WX 838 ; N uni2243 ; G 2271
+U 8776 ; WX 838 ; N approxequal ; G 2272
+U 8784 ; WX 838 ; N uni2250 ; G 2273
+U 8785 ; WX 838 ; N uni2251 ; G 2274
+U 8786 ; WX 838 ; N uni2252 ; G 2275
+U 8787 ; WX 838 ; N uni2253 ; G 2276
+U 8788 ; WX 1033 ; N uni2254 ; G 2277
+U 8789 ; WX 1033 ; N uni2255 ; G 2278
+U 8800 ; WX 838 ; N notequal ; G 2279
+U 8801 ; WX 838 ; N equivalence ; G 2280
+U 8804 ; WX 838 ; N lessequal ; G 2281
+U 8805 ; WX 838 ; N greaterequal ; G 2282
+U 8834 ; WX 838 ; N propersubset ; G 2283
+U 8835 ; WX 838 ; N propersuperset ; G 2284
+U 8836 ; WX 838 ; N notsubset ; G 2285
+U 8837 ; WX 838 ; N uni2285 ; G 2286
+U 8838 ; WX 838 ; N reflexsubset ; G 2287
+U 8839 ; WX 838 ; N reflexsuperset ; G 2288
+U 8844 ; WX 838 ; N uni228C ; G 2289
+U 8845 ; WX 838 ; N uni228D ; G 2290
+U 8846 ; WX 838 ; N uni228E ; G 2291
+U 8847 ; WX 846 ; N uni228F ; G 2292
+U 8848 ; WX 846 ; N uni2290 ; G 2293
+U 8849 ; WX 846 ; N uni2291 ; G 2294
+U 8850 ; WX 846 ; N uni2292 ; G 2295
+U 8851 ; WX 838 ; N uni2293 ; G 2296
+U 8852 ; WX 838 ; N uni2294 ; G 2297
+U 8853 ; WX 838 ; N circleplus ; G 2298
+U 8854 ; WX 838 ; N uni2296 ; G 2299
+U 8855 ; WX 838 ; N circlemultiply ; G 2300
+U 8856 ; WX 838 ; N uni2298 ; G 2301
+U 8857 ; WX 838 ; N uni2299 ; G 2302
+U 8858 ; WX 838 ; N uni229A ; G 2303
+U 8859 ; WX 838 ; N uni229B ; G 2304
+U 8860 ; WX 838 ; N uni229C ; G 2305
+U 8861 ; WX 838 ; N uni229D ; G 2306
+U 8862 ; WX 838 ; N uni229E ; G 2307
+U 8863 ; WX 838 ; N uni229F ; G 2308
+U 8864 ; WX 838 ; N uni22A0 ; G 2309
+U 8865 ; WX 838 ; N uni22A1 ; G 2310
+U 8866 ; WX 860 ; N uni22A2 ; G 2311
+U 8867 ; WX 860 ; N uni22A3 ; G 2312
+U 8868 ; WX 940 ; N uni22A4 ; G 2313
+U 8869 ; WX 940 ; N perpendicular ; G 2314
+U 8870 ; WX 567 ; N uni22A6 ; G 2315
+U 8871 ; WX 567 ; N uni22A7 ; G 2316
+U 8872 ; WX 860 ; N uni22A8 ; G 2317
+U 8873 ; WX 860 ; N uni22A9 ; G 2318
+U 8874 ; WX 860 ; N uni22AA ; G 2319
+U 8875 ; WX 1031 ; N uni22AB ; G 2320
+U 8876 ; WX 860 ; N uni22AC ; G 2321
+U 8877 ; WX 860 ; N uni22AD ; G 2322
+U 8878 ; WX 860 ; N uni22AE ; G 2323
+U 8879 ; WX 1031 ; N uni22AF ; G 2324
+U 8900 ; WX 626 ; N uni22C4 ; G 2325
+U 8901 ; WX 342 ; N dotmath ; G 2326
+U 8962 ; WX 764 ; N house ; G 2327
+U 8968 ; WX 390 ; N uni2308 ; G 2328
+U 8969 ; WX 390 ; N uni2309 ; G 2329
+U 8970 ; WX 390 ; N uni230A ; G 2330
+U 8971 ; WX 390 ; N uni230B ; G 2331
+U 8976 ; WX 838 ; N revlogicalnot ; G 2332
+U 8977 ; WX 513 ; N uni2311 ; G 2333
+U 8984 ; WX 1000 ; N uni2318 ; G 2334
+U 8985 ; WX 838 ; N uni2319 ; G 2335
+U 8992 ; WX 521 ; N integraltp ; G 2336
+U 8993 ; WX 521 ; N integralbt ; G 2337
+U 8997 ; WX 1000 ; N uni2325 ; G 2338
+U 9000 ; WX 1443 ; N uni2328 ; G 2339
+U 9085 ; WX 919 ; N uni237D ; G 2340
+U 9115 ; WX 500 ; N uni239B ; G 2341
+U 9116 ; WX 500 ; N uni239C ; G 2342
+U 9117 ; WX 500 ; N uni239D ; G 2343
+U 9118 ; WX 500 ; N uni239E ; G 2344
+U 9119 ; WX 500 ; N uni239F ; G 2345
+U 9120 ; WX 500 ; N uni23A0 ; G 2346
+U 9121 ; WX 500 ; N uni23A1 ; G 2347
+U 9122 ; WX 500 ; N uni23A2 ; G 2348
+U 9123 ; WX 500 ; N uni23A3 ; G 2349
+U 9124 ; WX 500 ; N uni23A4 ; G 2350
+U 9125 ; WX 500 ; N uni23A5 ; G 2351
+U 9126 ; WX 500 ; N uni23A6 ; G 2352
+U 9127 ; WX 750 ; N uni23A7 ; G 2353
+U 9128 ; WX 750 ; N uni23A8 ; G 2354
+U 9129 ; WX 750 ; N uni23A9 ; G 2355
+U 9130 ; WX 750 ; N uni23AA ; G 2356
+U 9131 ; WX 750 ; N uni23AB ; G 2357
+U 9132 ; WX 750 ; N uni23AC ; G 2358
+U 9133 ; WX 750 ; N uni23AD ; G 2359
+U 9134 ; WX 521 ; N uni23AE ; G 2360
+U 9167 ; WX 945 ; N uni23CF ; G 2361
+U 9251 ; WX 764 ; N uni2423 ; G 2362
+U 9472 ; WX 602 ; N SF100000 ; G 2363
+U 9473 ; WX 602 ; N uni2501 ; G 2364
+U 9474 ; WX 602 ; N SF110000 ; G 2365
+U 9475 ; WX 602 ; N uni2503 ; G 2366
+U 9476 ; WX 602 ; N uni2504 ; G 2367
+U 9477 ; WX 602 ; N uni2505 ; G 2368
+U 9478 ; WX 602 ; N uni2506 ; G 2369
+U 9479 ; WX 602 ; N uni2507 ; G 2370
+U 9480 ; WX 602 ; N uni2508 ; G 2371
+U 9481 ; WX 602 ; N uni2509 ; G 2372
+U 9482 ; WX 602 ; N uni250A ; G 2373
+U 9483 ; WX 602 ; N uni250B ; G 2374
+U 9484 ; WX 602 ; N SF010000 ; G 2375
+U 9485 ; WX 602 ; N uni250D ; G 2376
+U 9486 ; WX 602 ; N uni250E ; G 2377
+U 9487 ; WX 602 ; N uni250F ; G 2378
+U 9488 ; WX 602 ; N SF030000 ; G 2379
+U 9489 ; WX 602 ; N uni2511 ; G 2380
+U 9490 ; WX 602 ; N uni2512 ; G 2381
+U 9491 ; WX 602 ; N uni2513 ; G 2382
+U 9492 ; WX 602 ; N SF020000 ; G 2383
+U 9493 ; WX 602 ; N uni2515 ; G 2384
+U 9494 ; WX 602 ; N uni2516 ; G 2385
+U 9495 ; WX 602 ; N uni2517 ; G 2386
+U 9496 ; WX 602 ; N SF040000 ; G 2387
+U 9497 ; WX 602 ; N uni2519 ; G 2388
+U 9498 ; WX 602 ; N uni251A ; G 2389
+U 9499 ; WX 602 ; N uni251B ; G 2390
+U 9500 ; WX 602 ; N SF080000 ; G 2391
+U 9501 ; WX 602 ; N uni251D ; G 2392
+U 9502 ; WX 602 ; N uni251E ; G 2393
+U 9503 ; WX 602 ; N uni251F ; G 2394
+U 9504 ; WX 602 ; N uni2520 ; G 2395
+U 9505 ; WX 602 ; N uni2521 ; G 2396
+U 9506 ; WX 602 ; N uni2522 ; G 2397
+U 9507 ; WX 602 ; N uni2523 ; G 2398
+U 9508 ; WX 602 ; N SF090000 ; G 2399
+U 9509 ; WX 602 ; N uni2525 ; G 2400
+U 9510 ; WX 602 ; N uni2526 ; G 2401
+U 9511 ; WX 602 ; N uni2527 ; G 2402
+U 9512 ; WX 602 ; N uni2528 ; G 2403
+U 9513 ; WX 602 ; N uni2529 ; G 2404
+U 9514 ; WX 602 ; N uni252A ; G 2405
+U 9515 ; WX 602 ; N uni252B ; G 2406
+U 9516 ; WX 602 ; N SF060000 ; G 2407
+U 9517 ; WX 602 ; N uni252D ; G 2408
+U 9518 ; WX 602 ; N uni252E ; G 2409
+U 9519 ; WX 602 ; N uni252F ; G 2410
+U 9520 ; WX 602 ; N uni2530 ; G 2411
+U 9521 ; WX 602 ; N uni2531 ; G 2412
+U 9522 ; WX 602 ; N uni2532 ; G 2413
+U 9523 ; WX 602 ; N uni2533 ; G 2414
+U 9524 ; WX 602 ; N SF070000 ; G 2415
+U 9525 ; WX 602 ; N uni2535 ; G 2416
+U 9526 ; WX 602 ; N uni2536 ; G 2417
+U 9527 ; WX 602 ; N uni2537 ; G 2418
+U 9528 ; WX 602 ; N uni2538 ; G 2419
+U 9529 ; WX 602 ; N uni2539 ; G 2420
+U 9530 ; WX 602 ; N uni253A ; G 2421
+U 9531 ; WX 602 ; N uni253B ; G 2422
+U 9532 ; WX 602 ; N SF050000 ; G 2423
+U 9533 ; WX 602 ; N uni253D ; G 2424
+U 9534 ; WX 602 ; N uni253E ; G 2425
+U 9535 ; WX 602 ; N uni253F ; G 2426
+U 9536 ; WX 602 ; N uni2540 ; G 2427
+U 9537 ; WX 602 ; N uni2541 ; G 2428
+U 9538 ; WX 602 ; N uni2542 ; G 2429
+U 9539 ; WX 602 ; N uni2543 ; G 2430
+U 9540 ; WX 602 ; N uni2544 ; G 2431
+U 9541 ; WX 602 ; N uni2545 ; G 2432
+U 9542 ; WX 602 ; N uni2546 ; G 2433
+U 9543 ; WX 602 ; N uni2547 ; G 2434
+U 9544 ; WX 602 ; N uni2548 ; G 2435
+U 9545 ; WX 602 ; N uni2549 ; G 2436
+U 9546 ; WX 602 ; N uni254A ; G 2437
+U 9547 ; WX 602 ; N uni254B ; G 2438
+U 9548 ; WX 602 ; N uni254C ; G 2439
+U 9549 ; WX 602 ; N uni254D ; G 2440
+U 9550 ; WX 602 ; N uni254E ; G 2441
+U 9551 ; WX 602 ; N uni254F ; G 2442
+U 9552 ; WX 602 ; N SF430000 ; G 2443
+U 9553 ; WX 602 ; N SF240000 ; G 2444
+U 9554 ; WX 602 ; N SF510000 ; G 2445
+U 9555 ; WX 602 ; N SF520000 ; G 2446
+U 9556 ; WX 602 ; N SF390000 ; G 2447
+U 9557 ; WX 602 ; N SF220000 ; G 2448
+U 9558 ; WX 602 ; N SF210000 ; G 2449
+U 9559 ; WX 602 ; N SF250000 ; G 2450
+U 9560 ; WX 602 ; N SF500000 ; G 2451
+U 9561 ; WX 602 ; N SF490000 ; G 2452
+U 9562 ; WX 602 ; N SF380000 ; G 2453
+U 9563 ; WX 602 ; N SF280000 ; G 2454
+U 9564 ; WX 602 ; N SF270000 ; G 2455
+U 9565 ; WX 602 ; N SF260000 ; G 2456
+U 9566 ; WX 602 ; N SF360000 ; G 2457
+U 9567 ; WX 602 ; N SF370000 ; G 2458
+U 9568 ; WX 602 ; N SF420000 ; G 2459
+U 9569 ; WX 602 ; N SF190000 ; G 2460
+U 9570 ; WX 602 ; N SF200000 ; G 2461
+U 9571 ; WX 602 ; N SF230000 ; G 2462
+U 9572 ; WX 602 ; N SF470000 ; G 2463
+U 9573 ; WX 602 ; N SF480000 ; G 2464
+U 9574 ; WX 602 ; N SF410000 ; G 2465
+U 9575 ; WX 602 ; N SF450000 ; G 2466
+U 9576 ; WX 602 ; N SF460000 ; G 2467
+U 9577 ; WX 602 ; N SF400000 ; G 2468
+U 9578 ; WX 602 ; N SF540000 ; G 2469
+U 9579 ; WX 602 ; N SF530000 ; G 2470
+U 9580 ; WX 602 ; N SF440000 ; G 2471
+U 9581 ; WX 602 ; N uni256D ; G 2472
+U 9582 ; WX 602 ; N uni256E ; G 2473
+U 9583 ; WX 602 ; N uni256F ; G 2474
+U 9584 ; WX 602 ; N uni2570 ; G 2475
+U 9585 ; WX 602 ; N uni2571 ; G 2476
+U 9586 ; WX 602 ; N uni2572 ; G 2477
+U 9587 ; WX 602 ; N uni2573 ; G 2478
+U 9588 ; WX 602 ; N uni2574 ; G 2479
+U 9589 ; WX 602 ; N uni2575 ; G 2480
+U 9590 ; WX 602 ; N uni2576 ; G 2481
+U 9591 ; WX 602 ; N uni2577 ; G 2482
+U 9592 ; WX 602 ; N uni2578 ; G 2483
+U 9593 ; WX 602 ; N uni2579 ; G 2484
+U 9594 ; WX 602 ; N uni257A ; G 2485
+U 9595 ; WX 602 ; N uni257B ; G 2486
+U 9596 ; WX 602 ; N uni257C ; G 2487
+U 9597 ; WX 602 ; N uni257D ; G 2488
+U 9598 ; WX 602 ; N uni257E ; G 2489
+U 9599 ; WX 602 ; N uni257F ; G 2490
+U 9600 ; WX 769 ; N upblock ; G 2491
+U 9601 ; WX 769 ; N uni2581 ; G 2492
+U 9602 ; WX 769 ; N uni2582 ; G 2493
+U 9603 ; WX 769 ; N uni2583 ; G 2494
+U 9604 ; WX 769 ; N dnblock ; G 2495
+U 9605 ; WX 769 ; N uni2585 ; G 2496
+U 9606 ; WX 769 ; N uni2586 ; G 2497
+U 9607 ; WX 769 ; N uni2587 ; G 2498
+U 9608 ; WX 769 ; N block ; G 2499
+U 9609 ; WX 769 ; N uni2589 ; G 2500
+U 9610 ; WX 769 ; N uni258A ; G 2501
+U 9611 ; WX 769 ; N uni258B ; G 2502
+U 9612 ; WX 769 ; N lfblock ; G 2503
+U 9613 ; WX 769 ; N uni258D ; G 2504
+U 9614 ; WX 769 ; N uni258E ; G 2505
+U 9615 ; WX 769 ; N uni258F ; G 2506
+U 9616 ; WX 769 ; N rtblock ; G 2507
+U 9617 ; WX 769 ; N ltshade ; G 2508
+U 9618 ; WX 769 ; N shade ; G 2509
+U 9619 ; WX 769 ; N dkshade ; G 2510
+U 9620 ; WX 769 ; N uni2594 ; G 2511
+U 9621 ; WX 769 ; N uni2595 ; G 2512
+U 9622 ; WX 769 ; N uni2596 ; G 2513
+U 9623 ; WX 769 ; N uni2597 ; G 2514
+U 9624 ; WX 769 ; N uni2598 ; G 2515
+U 9625 ; WX 769 ; N uni2599 ; G 2516
+U 9626 ; WX 769 ; N uni259A ; G 2517
+U 9627 ; WX 769 ; N uni259B ; G 2518
+U 9628 ; WX 769 ; N uni259C ; G 2519
+U 9629 ; WX 769 ; N uni259D ; G 2520
+U 9630 ; WX 769 ; N uni259E ; G 2521
+U 9631 ; WX 769 ; N uni259F ; G 2522
+U 9632 ; WX 945 ; N filledbox ; G 2523
+U 9633 ; WX 945 ; N H22073 ; G 2524
+U 9634 ; WX 945 ; N uni25A2 ; G 2525
+U 9635 ; WX 945 ; N uni25A3 ; G 2526
+U 9636 ; WX 945 ; N uni25A4 ; G 2527
+U 9637 ; WX 945 ; N uni25A5 ; G 2528
+U 9638 ; WX 945 ; N uni25A6 ; G 2529
+U 9639 ; WX 945 ; N uni25A7 ; G 2530
+U 9640 ; WX 945 ; N uni25A8 ; G 2531
+U 9641 ; WX 945 ; N uni25A9 ; G 2532
+U 9642 ; WX 678 ; N H18543 ; G 2533
+U 9643 ; WX 678 ; N H18551 ; G 2534
+U 9644 ; WX 945 ; N filledrect ; G 2535
+U 9645 ; WX 945 ; N uni25AD ; G 2536
+U 9646 ; WX 550 ; N uni25AE ; G 2537
+U 9647 ; WX 550 ; N uni25AF ; G 2538
+U 9648 ; WX 769 ; N uni25B0 ; G 2539
+U 9649 ; WX 769 ; N uni25B1 ; G 2540
+U 9650 ; WX 769 ; N triagup ; G 2541
+U 9651 ; WX 769 ; N uni25B3 ; G 2542
+U 9652 ; WX 502 ; N uni25B4 ; G 2543
+U 9653 ; WX 502 ; N uni25B5 ; G 2544
+U 9654 ; WX 769 ; N uni25B6 ; G 2545
+U 9655 ; WX 769 ; N uni25B7 ; G 2546
+U 9656 ; WX 502 ; N uni25B8 ; G 2547
+U 9657 ; WX 502 ; N uni25B9 ; G 2548
+U 9658 ; WX 769 ; N triagrt ; G 2549
+U 9659 ; WX 769 ; N uni25BB ; G 2550
+U 9660 ; WX 769 ; N triagdn ; G 2551
+U 9661 ; WX 769 ; N uni25BD ; G 2552
+U 9662 ; WX 502 ; N uni25BE ; G 2553
+U 9663 ; WX 502 ; N uni25BF ; G 2554
+U 9664 ; WX 769 ; N uni25C0 ; G 2555
+U 9665 ; WX 769 ; N uni25C1 ; G 2556
+U 9666 ; WX 502 ; N uni25C2 ; G 2557
+U 9667 ; WX 502 ; N uni25C3 ; G 2558
+U 9668 ; WX 769 ; N triaglf ; G 2559
+U 9669 ; WX 769 ; N uni25C5 ; G 2560
+U 9670 ; WX 769 ; N uni25C6 ; G 2561
+U 9671 ; WX 769 ; N uni25C7 ; G 2562
+U 9672 ; WX 769 ; N uni25C8 ; G 2563
+U 9673 ; WX 873 ; N uni25C9 ; G 2564
+U 9674 ; WX 494 ; N lozenge ; G 2565
+U 9675 ; WX 873 ; N circle ; G 2566
+U 9676 ; WX 873 ; N uni25CC ; G 2567
+U 9677 ; WX 873 ; N uni25CD ; G 2568
+U 9678 ; WX 873 ; N uni25CE ; G 2569
+U 9679 ; WX 873 ; N H18533 ; G 2570
+U 9680 ; WX 873 ; N uni25D0 ; G 2571
+U 9681 ; WX 873 ; N uni25D1 ; G 2572
+U 9682 ; WX 873 ; N uni25D2 ; G 2573
+U 9683 ; WX 873 ; N uni25D3 ; G 2574
+U 9684 ; WX 873 ; N uni25D4 ; G 2575
+U 9685 ; WX 873 ; N uni25D5 ; G 2576
+U 9686 ; WX 527 ; N uni25D6 ; G 2577
+U 9687 ; WX 527 ; N uni25D7 ; G 2578
+U 9688 ; WX 791 ; N invbullet ; G 2579
+U 9689 ; WX 970 ; N invcircle ; G 2580
+U 9690 ; WX 970 ; N uni25DA ; G 2581
+U 9691 ; WX 970 ; N uni25DB ; G 2582
+U 9692 ; WX 387 ; N uni25DC ; G 2583
+U 9693 ; WX 387 ; N uni25DD ; G 2584
+U 9694 ; WX 387 ; N uni25DE ; G 2585
+U 9695 ; WX 387 ; N uni25DF ; G 2586
+U 9696 ; WX 873 ; N uni25E0 ; G 2587
+U 9697 ; WX 873 ; N uni25E1 ; G 2588
+U 9698 ; WX 769 ; N uni25E2 ; G 2589
+U 9699 ; WX 769 ; N uni25E3 ; G 2590
+U 9700 ; WX 769 ; N uni25E4 ; G 2591
+U 9701 ; WX 769 ; N uni25E5 ; G 2592
+U 9702 ; WX 590 ; N openbullet ; G 2593
+U 9703 ; WX 945 ; N uni25E7 ; G 2594
+U 9704 ; WX 945 ; N uni25E8 ; G 2595
+U 9705 ; WX 945 ; N uni25E9 ; G 2596
+U 9706 ; WX 945 ; N uni25EA ; G 2597
+U 9707 ; WX 945 ; N uni25EB ; G 2598
+U 9708 ; WX 769 ; N uni25EC ; G 2599
+U 9709 ; WX 769 ; N uni25ED ; G 2600
+U 9710 ; WX 769 ; N uni25EE ; G 2601
+U 9711 ; WX 1119 ; N uni25EF ; G 2602
+U 9712 ; WX 945 ; N uni25F0 ; G 2603
+U 9713 ; WX 945 ; N uni25F1 ; G 2604
+U 9714 ; WX 945 ; N uni25F2 ; G 2605
+U 9715 ; WX 945 ; N uni25F3 ; G 2606
+U 9716 ; WX 873 ; N uni25F4 ; G 2607
+U 9717 ; WX 873 ; N uni25F5 ; G 2608
+U 9718 ; WX 873 ; N uni25F6 ; G 2609
+U 9719 ; WX 873 ; N uni25F7 ; G 2610
+U 9720 ; WX 769 ; N uni25F8 ; G 2611
+U 9721 ; WX 769 ; N uni25F9 ; G 2612
+U 9722 ; WX 769 ; N uni25FA ; G 2613
+U 9723 ; WX 830 ; N uni25FB ; G 2614
+U 9724 ; WX 830 ; N uni25FC ; G 2615
+U 9725 ; WX 732 ; N uni25FD ; G 2616
+U 9726 ; WX 732 ; N uni25FE ; G 2617
+U 9727 ; WX 769 ; N uni25FF ; G 2618
+U 9728 ; WX 896 ; N uni2600 ; G 2619
+U 9784 ; WX 896 ; N uni2638 ; G 2620
+U 9785 ; WX 896 ; N uni2639 ; G 2621
+U 9786 ; WX 896 ; N smileface ; G 2622
+U 9787 ; WX 896 ; N invsmileface ; G 2623
+U 9788 ; WX 896 ; N sun ; G 2624
+U 9791 ; WX 614 ; N uni263F ; G 2625
+U 9792 ; WX 731 ; N female ; G 2626
+U 9793 ; WX 731 ; N uni2641 ; G 2627
+U 9794 ; WX 896 ; N male ; G 2628
+U 9795 ; WX 896 ; N uni2643 ; G 2629
+U 9796 ; WX 896 ; N uni2644 ; G 2630
+U 9797 ; WX 896 ; N uni2645 ; G 2631
+U 9798 ; WX 896 ; N uni2646 ; G 2632
+U 9799 ; WX 896 ; N uni2647 ; G 2633
+U 9824 ; WX 896 ; N spade ; G 2634
+U 9825 ; WX 896 ; N uni2661 ; G 2635
+U 9826 ; WX 896 ; N uni2662 ; G 2636
+U 9827 ; WX 896 ; N club ; G 2637
+U 9828 ; WX 896 ; N uni2664 ; G 2638
+U 9829 ; WX 896 ; N heart ; G 2639
+U 9830 ; WX 896 ; N diamond ; G 2640
+U 9831 ; WX 896 ; N uni2667 ; G 2641
+U 9833 ; WX 472 ; N uni2669 ; G 2642
+U 9834 ; WX 638 ; N musicalnote ; G 2643
+U 9835 ; WX 896 ; N musicalnotedbl ; G 2644
+U 9836 ; WX 896 ; N uni266C ; G 2645
+U 9837 ; WX 472 ; N uni266D ; G 2646
+U 9838 ; WX 357 ; N uni266E ; G 2647
+U 9839 ; WX 484 ; N uni266F ; G 2648
+U 10145 ; WX 838 ; N uni27A1 ; G 2649
+U 10181 ; WX 390 ; N uni27C5 ; G 2650
+U 10182 ; WX 390 ; N uni27C6 ; G 2651
+U 10208 ; WX 494 ; N uni27E0 ; G 2652
+U 10216 ; WX 390 ; N uni27E8 ; G 2653
+U 10217 ; WX 390 ; N uni27E9 ; G 2654
+U 10224 ; WX 838 ; N uni27F0 ; G 2655
+U 10225 ; WX 838 ; N uni27F1 ; G 2656
+U 10226 ; WX 838 ; N uni27F2 ; G 2657
+U 10227 ; WX 838 ; N uni27F3 ; G 2658
+U 10228 ; WX 1033 ; N uni27F4 ; G 2659
+U 10229 ; WX 1434 ; N uni27F5 ; G 2660
+U 10230 ; WX 1434 ; N uni27F6 ; G 2661
+U 10231 ; WX 1434 ; N uni27F7 ; G 2662
+U 10232 ; WX 1434 ; N uni27F8 ; G 2663
+U 10233 ; WX 1434 ; N uni27F9 ; G 2664
+U 10234 ; WX 1434 ; N uni27FA ; G 2665
+U 10235 ; WX 1434 ; N uni27FB ; G 2666
+U 10236 ; WX 1434 ; N uni27FC ; G 2667
+U 10237 ; WX 1434 ; N uni27FD ; G 2668
+U 10238 ; WX 1434 ; N uni27FE ; G 2669
+U 10239 ; WX 1434 ; N uni27FF ; G 2670
+U 10240 ; WX 732 ; N uni2800 ; G 2671
+U 10241 ; WX 732 ; N uni2801 ; G 2672
+U 10242 ; WX 732 ; N uni2802 ; G 2673
+U 10243 ; WX 732 ; N uni2803 ; G 2674
+U 10244 ; WX 732 ; N uni2804 ; G 2675
+U 10245 ; WX 732 ; N uni2805 ; G 2676
+U 10246 ; WX 732 ; N uni2806 ; G 2677
+U 10247 ; WX 732 ; N uni2807 ; G 2678
+U 10248 ; WX 732 ; N uni2808 ; G 2679
+U 10249 ; WX 732 ; N uni2809 ; G 2680
+U 10250 ; WX 732 ; N uni280A ; G 2681
+U 10251 ; WX 732 ; N uni280B ; G 2682
+U 10252 ; WX 732 ; N uni280C ; G 2683
+U 10253 ; WX 732 ; N uni280D ; G 2684
+U 10254 ; WX 732 ; N uni280E ; G 2685
+U 10255 ; WX 732 ; N uni280F ; G 2686
+U 10256 ; WX 732 ; N uni2810 ; G 2687
+U 10257 ; WX 732 ; N uni2811 ; G 2688
+U 10258 ; WX 732 ; N uni2812 ; G 2689
+U 10259 ; WX 732 ; N uni2813 ; G 2690
+U 10260 ; WX 732 ; N uni2814 ; G 2691
+U 10261 ; WX 732 ; N uni2815 ; G 2692
+U 10262 ; WX 732 ; N uni2816 ; G 2693
+U 10263 ; WX 732 ; N uni2817 ; G 2694
+U 10264 ; WX 732 ; N uni2818 ; G 2695
+U 10265 ; WX 732 ; N uni2819 ; G 2696
+U 10266 ; WX 732 ; N uni281A ; G 2697
+U 10267 ; WX 732 ; N uni281B ; G 2698
+U 10268 ; WX 732 ; N uni281C ; G 2699
+U 10269 ; WX 732 ; N uni281D ; G 2700
+U 10270 ; WX 732 ; N uni281E ; G 2701
+U 10271 ; WX 732 ; N uni281F ; G 2702
+U 10272 ; WX 732 ; N uni2820 ; G 2703
+U 10273 ; WX 732 ; N uni2821 ; G 2704
+U 10274 ; WX 732 ; N uni2822 ; G 2705
+U 10275 ; WX 732 ; N uni2823 ; G 2706
+U 10276 ; WX 732 ; N uni2824 ; G 2707
+U 10277 ; WX 732 ; N uni2825 ; G 2708
+U 10278 ; WX 732 ; N uni2826 ; G 2709
+U 10279 ; WX 732 ; N uni2827 ; G 2710
+U 10280 ; WX 732 ; N uni2828 ; G 2711
+U 10281 ; WX 732 ; N uni2829 ; G 2712
+U 10282 ; WX 732 ; N uni282A ; G 2713
+U 10283 ; WX 732 ; N uni282B ; G 2714
+U 10284 ; WX 732 ; N uni282C ; G 2715
+U 10285 ; WX 732 ; N uni282D ; G 2716
+U 10286 ; WX 732 ; N uni282E ; G 2717
+U 10287 ; WX 732 ; N uni282F ; G 2718
+U 10288 ; WX 732 ; N uni2830 ; G 2719
+U 10289 ; WX 732 ; N uni2831 ; G 2720
+U 10290 ; WX 732 ; N uni2832 ; G 2721
+U 10291 ; WX 732 ; N uni2833 ; G 2722
+U 10292 ; WX 732 ; N uni2834 ; G 2723
+U 10293 ; WX 732 ; N uni2835 ; G 2724
+U 10294 ; WX 732 ; N uni2836 ; G 2725
+U 10295 ; WX 732 ; N uni2837 ; G 2726
+U 10296 ; WX 732 ; N uni2838 ; G 2727
+U 10297 ; WX 732 ; N uni2839 ; G 2728
+U 10298 ; WX 732 ; N uni283A ; G 2729
+U 10299 ; WX 732 ; N uni283B ; G 2730
+U 10300 ; WX 732 ; N uni283C ; G 2731
+U 10301 ; WX 732 ; N uni283D ; G 2732
+U 10302 ; WX 732 ; N uni283E ; G 2733
+U 10303 ; WX 732 ; N uni283F ; G 2734
+U 10304 ; WX 732 ; N uni2840 ; G 2735
+U 10305 ; WX 732 ; N uni2841 ; G 2736
+U 10306 ; WX 732 ; N uni2842 ; G 2737
+U 10307 ; WX 732 ; N uni2843 ; G 2738
+U 10308 ; WX 732 ; N uni2844 ; G 2739
+U 10309 ; WX 732 ; N uni2845 ; G 2740
+U 10310 ; WX 732 ; N uni2846 ; G 2741
+U 10311 ; WX 732 ; N uni2847 ; G 2742
+U 10312 ; WX 732 ; N uni2848 ; G 2743
+U 10313 ; WX 732 ; N uni2849 ; G 2744
+U 10314 ; WX 732 ; N uni284A ; G 2745
+U 10315 ; WX 732 ; N uni284B ; G 2746
+U 10316 ; WX 732 ; N uni284C ; G 2747
+U 10317 ; WX 732 ; N uni284D ; G 2748
+U 10318 ; WX 732 ; N uni284E ; G 2749
+U 10319 ; WX 732 ; N uni284F ; G 2750
+U 10320 ; WX 732 ; N uni2850 ; G 2751
+U 10321 ; WX 732 ; N uni2851 ; G 2752
+U 10322 ; WX 732 ; N uni2852 ; G 2753
+U 10323 ; WX 732 ; N uni2853 ; G 2754
+U 10324 ; WX 732 ; N uni2854 ; G 2755
+U 10325 ; WX 732 ; N uni2855 ; G 2756
+U 10326 ; WX 732 ; N uni2856 ; G 2757
+U 10327 ; WX 732 ; N uni2857 ; G 2758
+U 10328 ; WX 732 ; N uni2858 ; G 2759
+U 10329 ; WX 732 ; N uni2859 ; G 2760
+U 10330 ; WX 732 ; N uni285A ; G 2761
+U 10331 ; WX 732 ; N uni285B ; G 2762
+U 10332 ; WX 732 ; N uni285C ; G 2763
+U 10333 ; WX 732 ; N uni285D ; G 2764
+U 10334 ; WX 732 ; N uni285E ; G 2765
+U 10335 ; WX 732 ; N uni285F ; G 2766
+U 10336 ; WX 732 ; N uni2860 ; G 2767
+U 10337 ; WX 732 ; N uni2861 ; G 2768
+U 10338 ; WX 732 ; N uni2862 ; G 2769
+U 10339 ; WX 732 ; N uni2863 ; G 2770
+U 10340 ; WX 732 ; N uni2864 ; G 2771
+U 10341 ; WX 732 ; N uni2865 ; G 2772
+U 10342 ; WX 732 ; N uni2866 ; G 2773
+U 10343 ; WX 732 ; N uni2867 ; G 2774
+U 10344 ; WX 732 ; N uni2868 ; G 2775
+U 10345 ; WX 732 ; N uni2869 ; G 2776
+U 10346 ; WX 732 ; N uni286A ; G 2777
+U 10347 ; WX 732 ; N uni286B ; G 2778
+U 10348 ; WX 732 ; N uni286C ; G 2779
+U 10349 ; WX 732 ; N uni286D ; G 2780
+U 10350 ; WX 732 ; N uni286E ; G 2781
+U 10351 ; WX 732 ; N uni286F ; G 2782
+U 10352 ; WX 732 ; N uni2870 ; G 2783
+U 10353 ; WX 732 ; N uni2871 ; G 2784
+U 10354 ; WX 732 ; N uni2872 ; G 2785
+U 10355 ; WX 732 ; N uni2873 ; G 2786
+U 10356 ; WX 732 ; N uni2874 ; G 2787
+U 10357 ; WX 732 ; N uni2875 ; G 2788
+U 10358 ; WX 732 ; N uni2876 ; G 2789
+U 10359 ; WX 732 ; N uni2877 ; G 2790
+U 10360 ; WX 732 ; N uni2878 ; G 2791
+U 10361 ; WX 732 ; N uni2879 ; G 2792
+U 10362 ; WX 732 ; N uni287A ; G 2793
+U 10363 ; WX 732 ; N uni287B ; G 2794
+U 10364 ; WX 732 ; N uni287C ; G 2795
+U 10365 ; WX 732 ; N uni287D ; G 2796
+U 10366 ; WX 732 ; N uni287E ; G 2797
+U 10367 ; WX 732 ; N uni287F ; G 2798
+U 10368 ; WX 732 ; N uni2880 ; G 2799
+U 10369 ; WX 732 ; N uni2881 ; G 2800
+U 10370 ; WX 732 ; N uni2882 ; G 2801
+U 10371 ; WX 732 ; N uni2883 ; G 2802
+U 10372 ; WX 732 ; N uni2884 ; G 2803
+U 10373 ; WX 732 ; N uni2885 ; G 2804
+U 10374 ; WX 732 ; N uni2886 ; G 2805
+U 10375 ; WX 732 ; N uni2887 ; G 2806
+U 10376 ; WX 732 ; N uni2888 ; G 2807
+U 10377 ; WX 732 ; N uni2889 ; G 2808
+U 10378 ; WX 732 ; N uni288A ; G 2809
+U 10379 ; WX 732 ; N uni288B ; G 2810
+U 10380 ; WX 732 ; N uni288C ; G 2811
+U 10381 ; WX 732 ; N uni288D ; G 2812
+U 10382 ; WX 732 ; N uni288E ; G 2813
+U 10383 ; WX 732 ; N uni288F ; G 2814
+U 10384 ; WX 732 ; N uni2890 ; G 2815
+U 10385 ; WX 732 ; N uni2891 ; G 2816
+U 10386 ; WX 732 ; N uni2892 ; G 2817
+U 10387 ; WX 732 ; N uni2893 ; G 2818
+U 10388 ; WX 732 ; N uni2894 ; G 2819
+U 10389 ; WX 732 ; N uni2895 ; G 2820
+U 10390 ; WX 732 ; N uni2896 ; G 2821
+U 10391 ; WX 732 ; N uni2897 ; G 2822
+U 10392 ; WX 732 ; N uni2898 ; G 2823
+U 10393 ; WX 732 ; N uni2899 ; G 2824
+U 10394 ; WX 732 ; N uni289A ; G 2825
+U 10395 ; WX 732 ; N uni289B ; G 2826
+U 10396 ; WX 732 ; N uni289C ; G 2827
+U 10397 ; WX 732 ; N uni289D ; G 2828
+U 10398 ; WX 732 ; N uni289E ; G 2829
+U 10399 ; WX 732 ; N uni289F ; G 2830
+U 10400 ; WX 732 ; N uni28A0 ; G 2831
+U 10401 ; WX 732 ; N uni28A1 ; G 2832
+U 10402 ; WX 732 ; N uni28A2 ; G 2833
+U 10403 ; WX 732 ; N uni28A3 ; G 2834
+U 10404 ; WX 732 ; N uni28A4 ; G 2835
+U 10405 ; WX 732 ; N uni28A5 ; G 2836
+U 10406 ; WX 732 ; N uni28A6 ; G 2837
+U 10407 ; WX 732 ; N uni28A7 ; G 2838
+U 10408 ; WX 732 ; N uni28A8 ; G 2839
+U 10409 ; WX 732 ; N uni28A9 ; G 2840
+U 10410 ; WX 732 ; N uni28AA ; G 2841
+U 10411 ; WX 732 ; N uni28AB ; G 2842
+U 10412 ; WX 732 ; N uni28AC ; G 2843
+U 10413 ; WX 732 ; N uni28AD ; G 2844
+U 10414 ; WX 732 ; N uni28AE ; G 2845
+U 10415 ; WX 732 ; N uni28AF ; G 2846
+U 10416 ; WX 732 ; N uni28B0 ; G 2847
+U 10417 ; WX 732 ; N uni28B1 ; G 2848
+U 10418 ; WX 732 ; N uni28B2 ; G 2849
+U 10419 ; WX 732 ; N uni28B3 ; G 2850
+U 10420 ; WX 732 ; N uni28B4 ; G 2851
+U 10421 ; WX 732 ; N uni28B5 ; G 2852
+U 10422 ; WX 732 ; N uni28B6 ; G 2853
+U 10423 ; WX 732 ; N uni28B7 ; G 2854
+U 10424 ; WX 732 ; N uni28B8 ; G 2855
+U 10425 ; WX 732 ; N uni28B9 ; G 2856
+U 10426 ; WX 732 ; N uni28BA ; G 2857
+U 10427 ; WX 732 ; N uni28BB ; G 2858
+U 10428 ; WX 732 ; N uni28BC ; G 2859
+U 10429 ; WX 732 ; N uni28BD ; G 2860
+U 10430 ; WX 732 ; N uni28BE ; G 2861
+U 10431 ; WX 732 ; N uni28BF ; G 2862
+U 10432 ; WX 732 ; N uni28C0 ; G 2863
+U 10433 ; WX 732 ; N uni28C1 ; G 2864
+U 10434 ; WX 732 ; N uni28C2 ; G 2865
+U 10435 ; WX 732 ; N uni28C3 ; G 2866
+U 10436 ; WX 732 ; N uni28C4 ; G 2867
+U 10437 ; WX 732 ; N uni28C5 ; G 2868
+U 10438 ; WX 732 ; N uni28C6 ; G 2869
+U 10439 ; WX 732 ; N uni28C7 ; G 2870
+U 10440 ; WX 732 ; N uni28C8 ; G 2871
+U 10441 ; WX 732 ; N uni28C9 ; G 2872
+U 10442 ; WX 732 ; N uni28CA ; G 2873
+U 10443 ; WX 732 ; N uni28CB ; G 2874
+U 10444 ; WX 732 ; N uni28CC ; G 2875
+U 10445 ; WX 732 ; N uni28CD ; G 2876
+U 10446 ; WX 732 ; N uni28CE ; G 2877
+U 10447 ; WX 732 ; N uni28CF ; G 2878
+U 10448 ; WX 732 ; N uni28D0 ; G 2879
+U 10449 ; WX 732 ; N uni28D1 ; G 2880
+U 10450 ; WX 732 ; N uni28D2 ; G 2881
+U 10451 ; WX 732 ; N uni28D3 ; G 2882
+U 10452 ; WX 732 ; N uni28D4 ; G 2883
+U 10453 ; WX 732 ; N uni28D5 ; G 2884
+U 10454 ; WX 732 ; N uni28D6 ; G 2885
+U 10455 ; WX 732 ; N uni28D7 ; G 2886
+U 10456 ; WX 732 ; N uni28D8 ; G 2887
+U 10457 ; WX 732 ; N uni28D9 ; G 2888
+U 10458 ; WX 732 ; N uni28DA ; G 2889
+U 10459 ; WX 732 ; N uni28DB ; G 2890
+U 10460 ; WX 732 ; N uni28DC ; G 2891
+U 10461 ; WX 732 ; N uni28DD ; G 2892
+U 10462 ; WX 732 ; N uni28DE ; G 2893
+U 10463 ; WX 732 ; N uni28DF ; G 2894
+U 10464 ; WX 732 ; N uni28E0 ; G 2895
+U 10465 ; WX 732 ; N uni28E1 ; G 2896
+U 10466 ; WX 732 ; N uni28E2 ; G 2897
+U 10467 ; WX 732 ; N uni28E3 ; G 2898
+U 10468 ; WX 732 ; N uni28E4 ; G 2899
+U 10469 ; WX 732 ; N uni28E5 ; G 2900
+U 10470 ; WX 732 ; N uni28E6 ; G 2901
+U 10471 ; WX 732 ; N uni28E7 ; G 2902
+U 10472 ; WX 732 ; N uni28E8 ; G 2903
+U 10473 ; WX 732 ; N uni28E9 ; G 2904
+U 10474 ; WX 732 ; N uni28EA ; G 2905
+U 10475 ; WX 732 ; N uni28EB ; G 2906
+U 10476 ; WX 732 ; N uni28EC ; G 2907
+U 10477 ; WX 732 ; N uni28ED ; G 2908
+U 10478 ; WX 732 ; N uni28EE ; G 2909
+U 10479 ; WX 732 ; N uni28EF ; G 2910
+U 10480 ; WX 732 ; N uni28F0 ; G 2911
+U 10481 ; WX 732 ; N uni28F1 ; G 2912
+U 10482 ; WX 732 ; N uni28F2 ; G 2913
+U 10483 ; WX 732 ; N uni28F3 ; G 2914
+U 10484 ; WX 732 ; N uni28F4 ; G 2915
+U 10485 ; WX 732 ; N uni28F5 ; G 2916
+U 10486 ; WX 732 ; N uni28F6 ; G 2917
+U 10487 ; WX 732 ; N uni28F7 ; G 2918
+U 10488 ; WX 732 ; N uni28F8 ; G 2919
+U 10489 ; WX 732 ; N uni28F9 ; G 2920
+U 10490 ; WX 732 ; N uni28FA ; G 2921
+U 10491 ; WX 732 ; N uni28FB ; G 2922
+U 10492 ; WX 732 ; N uni28FC ; G 2923
+U 10493 ; WX 732 ; N uni28FD ; G 2924
+U 10494 ; WX 732 ; N uni28FE ; G 2925
+U 10495 ; WX 732 ; N uni28FF ; G 2926
+U 10496 ; WX 838 ; N uni2900 ; G 2927
+U 10497 ; WX 838 ; N uni2901 ; G 2928
+U 10498 ; WX 838 ; N uni2902 ; G 2929
+U 10499 ; WX 838 ; N uni2903 ; G 2930
+U 10500 ; WX 838 ; N uni2904 ; G 2931
+U 10501 ; WX 838 ; N uni2905 ; G 2932
+U 10502 ; WX 838 ; N uni2906 ; G 2933
+U 10503 ; WX 838 ; N uni2907 ; G 2934
+U 10504 ; WX 838 ; N uni2908 ; G 2935
+U 10505 ; WX 838 ; N uni2909 ; G 2936
+U 10506 ; WX 838 ; N uni290A ; G 2937
+U 10507 ; WX 838 ; N uni290B ; G 2938
+U 10508 ; WX 838 ; N uni290C ; G 2939
+U 10509 ; WX 838 ; N uni290D ; G 2940
+U 10510 ; WX 838 ; N uni290E ; G 2941
+U 10511 ; WX 838 ; N uni290F ; G 2942
+U 10512 ; WX 838 ; N uni2910 ; G 2943
+U 10513 ; WX 838 ; N uni2911 ; G 2944
+U 10514 ; WX 838 ; N uni2912 ; G 2945
+U 10515 ; WX 838 ; N uni2913 ; G 2946
+U 10516 ; WX 838 ; N uni2914 ; G 2947
+U 10517 ; WX 838 ; N uni2915 ; G 2948
+U 10518 ; WX 838 ; N uni2916 ; G 2949
+U 10519 ; WX 838 ; N uni2917 ; G 2950
+U 10520 ; WX 838 ; N uni2918 ; G 2951
+U 10521 ; WX 838 ; N uni2919 ; G 2952
+U 10522 ; WX 838 ; N uni291A ; G 2953
+U 10523 ; WX 838 ; N uni291B ; G 2954
+U 10524 ; WX 838 ; N uni291C ; G 2955
+U 10525 ; WX 838 ; N uni291D ; G 2956
+U 10526 ; WX 838 ; N uni291E ; G 2957
+U 10527 ; WX 838 ; N uni291F ; G 2958
+U 10528 ; WX 838 ; N uni2920 ; G 2959
+U 10529 ; WX 838 ; N uni2921 ; G 2960
+U 10530 ; WX 838 ; N uni2922 ; G 2961
+U 10531 ; WX 838 ; N uni2923 ; G 2962
+U 10532 ; WX 838 ; N uni2924 ; G 2963
+U 10533 ; WX 838 ; N uni2925 ; G 2964
+U 10534 ; WX 838 ; N uni2926 ; G 2965
+U 10535 ; WX 838 ; N uni2927 ; G 2966
+U 10536 ; WX 838 ; N uni2928 ; G 2967
+U 10537 ; WX 838 ; N uni2929 ; G 2968
+U 10538 ; WX 838 ; N uni292A ; G 2969
+U 10539 ; WX 838 ; N uni292B ; G 2970
+U 10540 ; WX 838 ; N uni292C ; G 2971
+U 10541 ; WX 838 ; N uni292D ; G 2972
+U 10542 ; WX 838 ; N uni292E ; G 2973
+U 10543 ; WX 838 ; N uni292F ; G 2974
+U 10544 ; WX 838 ; N uni2930 ; G 2975
+U 10545 ; WX 838 ; N uni2931 ; G 2976
+U 10546 ; WX 838 ; N uni2932 ; G 2977
+U 10547 ; WX 838 ; N uni2933 ; G 2978
+U 10548 ; WX 838 ; N uni2934 ; G 2979
+U 10549 ; WX 838 ; N uni2935 ; G 2980
+U 10550 ; WX 838 ; N uni2936 ; G 2981
+U 10551 ; WX 838 ; N uni2937 ; G 2982
+U 10552 ; WX 838 ; N uni2938 ; G 2983
+U 10553 ; WX 838 ; N uni2939 ; G 2984
+U 10554 ; WX 838 ; N uni293A ; G 2985
+U 10555 ; WX 838 ; N uni293B ; G 2986
+U 10556 ; WX 838 ; N uni293C ; G 2987
+U 10557 ; WX 838 ; N uni293D ; G 2988
+U 10558 ; WX 838 ; N uni293E ; G 2989
+U 10559 ; WX 838 ; N uni293F ; G 2990
+U 10560 ; WX 838 ; N uni2940 ; G 2991
+U 10561 ; WX 838 ; N uni2941 ; G 2992
+U 10562 ; WX 838 ; N uni2942 ; G 2993
+U 10563 ; WX 838 ; N uni2943 ; G 2994
+U 10564 ; WX 838 ; N uni2944 ; G 2995
+U 10565 ; WX 838 ; N uni2945 ; G 2996
+U 10566 ; WX 838 ; N uni2946 ; G 2997
+U 10567 ; WX 838 ; N uni2947 ; G 2998
+U 10568 ; WX 838 ; N uni2948 ; G 2999
+U 10569 ; WX 838 ; N uni2949 ; G 3000
+U 10570 ; WX 838 ; N uni294A ; G 3001
+U 10571 ; WX 838 ; N uni294B ; G 3002
+U 10572 ; WX 838 ; N uni294C ; G 3003
+U 10573 ; WX 838 ; N uni294D ; G 3004
+U 10574 ; WX 838 ; N uni294E ; G 3005
+U 10575 ; WX 838 ; N uni294F ; G 3006
+U 10576 ; WX 838 ; N uni2950 ; G 3007
+U 10577 ; WX 838 ; N uni2951 ; G 3008
+U 10578 ; WX 838 ; N uni2952 ; G 3009
+U 10579 ; WX 838 ; N uni2953 ; G 3010
+U 10580 ; WX 838 ; N uni2954 ; G 3011
+U 10581 ; WX 838 ; N uni2955 ; G 3012
+U 10582 ; WX 838 ; N uni2956 ; G 3013
+U 10583 ; WX 838 ; N uni2957 ; G 3014
+U 10584 ; WX 838 ; N uni2958 ; G 3015
+U 10585 ; WX 838 ; N uni2959 ; G 3016
+U 10586 ; WX 838 ; N uni295A ; G 3017
+U 10587 ; WX 838 ; N uni295B ; G 3018
+U 10588 ; WX 838 ; N uni295C ; G 3019
+U 10589 ; WX 838 ; N uni295D ; G 3020
+U 10590 ; WX 838 ; N uni295E ; G 3021
+U 10591 ; WX 838 ; N uni295F ; G 3022
+U 10592 ; WX 838 ; N uni2960 ; G 3023
+U 10593 ; WX 838 ; N uni2961 ; G 3024
+U 10594 ; WX 838 ; N uni2962 ; G 3025
+U 10595 ; WX 838 ; N uni2963 ; G 3026
+U 10596 ; WX 838 ; N uni2964 ; G 3027
+U 10597 ; WX 838 ; N uni2965 ; G 3028
+U 10598 ; WX 838 ; N uni2966 ; G 3029
+U 10599 ; WX 838 ; N uni2967 ; G 3030
+U 10600 ; WX 838 ; N uni2968 ; G 3031
+U 10601 ; WX 838 ; N uni2969 ; G 3032
+U 10602 ; WX 838 ; N uni296A ; G 3033
+U 10603 ; WX 838 ; N uni296B ; G 3034
+U 10604 ; WX 838 ; N uni296C ; G 3035
+U 10605 ; WX 838 ; N uni296D ; G 3036
+U 10606 ; WX 838 ; N uni296E ; G 3037
+U 10607 ; WX 838 ; N uni296F ; G 3038
+U 10608 ; WX 838 ; N uni2970 ; G 3039
+U 10609 ; WX 838 ; N uni2971 ; G 3040
+U 10610 ; WX 838 ; N uni2972 ; G 3041
+U 10611 ; WX 838 ; N uni2973 ; G 3042
+U 10612 ; WX 838 ; N uni2974 ; G 3043
+U 10613 ; WX 838 ; N uni2975 ; G 3044
+U 10614 ; WX 838 ; N uni2976 ; G 3045
+U 10615 ; WX 981 ; N uni2977 ; G 3046
+U 10616 ; WX 838 ; N uni2978 ; G 3047
+U 10617 ; WX 838 ; N uni2979 ; G 3048
+U 10618 ; WX 984 ; N uni297A ; G 3049
+U 10619 ; WX 838 ; N uni297B ; G 3050
+U 10620 ; WX 838 ; N uni297C ; G 3051
+U 10621 ; WX 838 ; N uni297D ; G 3052
+U 10622 ; WX 838 ; N uni297E ; G 3053
+U 10623 ; WX 838 ; N uni297F ; G 3054
+U 10731 ; WX 494 ; N uni29EB ; G 3055
+U 10764 ; WX 1513 ; N uni2A0C ; G 3056
+U 10765 ; WX 521 ; N uni2A0D ; G 3057
+U 10766 ; WX 521 ; N uni2A0E ; G 3058
+U 10799 ; WX 838 ; N uni2A2F ; G 3059
+U 10858 ; WX 838 ; N uni2A6A ; G 3060
+U 10859 ; WX 838 ; N uni2A6B ; G 3061
+U 11008 ; WX 838 ; N uni2B00 ; G 3062
+U 11009 ; WX 838 ; N uni2B01 ; G 3063
+U 11010 ; WX 838 ; N uni2B02 ; G 3064
+U 11011 ; WX 838 ; N uni2B03 ; G 3065
+U 11012 ; WX 838 ; N uni2B04 ; G 3066
+U 11013 ; WX 838 ; N uni2B05 ; G 3067
+U 11014 ; WX 838 ; N uni2B06 ; G 3068
+U 11015 ; WX 838 ; N uni2B07 ; G 3069
+U 11016 ; WX 838 ; N uni2B08 ; G 3070
+U 11017 ; WX 838 ; N uni2B09 ; G 3071
+U 11018 ; WX 838 ; N uni2B0A ; G 3072
+U 11019 ; WX 838 ; N uni2B0B ; G 3073
+U 11020 ; WX 838 ; N uni2B0C ; G 3074
+U 11021 ; WX 838 ; N uni2B0D ; G 3075
+U 11022 ; WX 838 ; N uni2B0E ; G 3076
+U 11023 ; WX 838 ; N uni2B0F ; G 3077
+U 11024 ; WX 838 ; N uni2B10 ; G 3078
+U 11025 ; WX 838 ; N uni2B11 ; G 3079
+U 11026 ; WX 945 ; N uni2B12 ; G 3080
+U 11027 ; WX 945 ; N uni2B13 ; G 3081
+U 11028 ; WX 945 ; N uni2B14 ; G 3082
+U 11029 ; WX 945 ; N uni2B15 ; G 3083
+U 11030 ; WX 769 ; N uni2B16 ; G 3084
+U 11031 ; WX 769 ; N uni2B17 ; G 3085
+U 11032 ; WX 769 ; N uni2B18 ; G 3086
+U 11033 ; WX 769 ; N uni2B19 ; G 3087
+U 11034 ; WX 945 ; N uni2B1A ; G 3088
+U 11360 ; WX 664 ; N uni2C60 ; G 3089
+U 11361 ; WX 320 ; N uni2C61 ; G 3090
+U 11363 ; WX 673 ; N uni2C63 ; G 3091
+U 11364 ; WX 753 ; N uni2C64 ; G 3092
+U 11367 ; WX 872 ; N uni2C67 ; G 3093
+U 11368 ; WX 644 ; N uni2C68 ; G 3094
+U 11369 ; WX 747 ; N uni2C69 ; G 3095
+U 11370 ; WX 606 ; N uni2C6A ; G 3096
+U 11371 ; WX 695 ; N uni2C6B ; G 3097
+U 11372 ; WX 527 ; N uni2C6C ; G 3098
+U 11373 ; WX 782 ; N uni2C6D ; G 3099
+U 11374 ; WX 1024 ; N uni2C6E ; G 3100
+U 11375 ; WX 722 ; N uni2C6F ; G 3101
+U 11376 ; WX 782 ; N uni2C70 ; G 3102
+U 11377 ; WX 663 ; N uni2C71 ; G 3103
+U 11378 ; WX 1130 ; N uni2C72 ; G 3104
+U 11379 ; WX 939 ; N uni2C73 ; G 3105
+U 11381 ; WX 740 ; N uni2C75 ; G 3106
+U 11382 ; WX 531 ; N uni2C76 ; G 3107
+U 11383 ; WX 700 ; N uni2C77 ; G 3108
+U 11385 ; WX 501 ; N uni2C79 ; G 3109
+U 11386 ; WX 602 ; N uni2C7A ; G 3110
+U 11387 ; WX 553 ; N uni2C7B ; G 3111
+U 11388 ; WX 264 ; N uni2C7C ; G 3112
+U 11389 ; WX 455 ; N uni2C7D ; G 3113
+U 11390 ; WX 685 ; N uni2C7E ; G 3114
+U 11391 ; WX 695 ; N uni2C7F ; G 3115
+U 11520 ; WX 773 ; N uni2D00 ; G 3116
+U 11521 ; WX 635 ; N uni2D01 ; G 3117
+U 11522 ; WX 633 ; N uni2D02 ; G 3118
+U 11523 ; WX 658 ; N uni2D03 ; G 3119
+U 11524 ; WX 631 ; N uni2D04 ; G 3120
+U 11525 ; WX 962 ; N uni2D05 ; G 3121
+U 11526 ; WX 756 ; N uni2D06 ; G 3122
+U 11527 ; WX 960 ; N uni2D07 ; G 3123
+U 11528 ; WX 617 ; N uni2D08 ; G 3124
+U 11529 ; WX 646 ; N uni2D09 ; G 3125
+U 11530 ; WX 962 ; N uni2D0A ; G 3126
+U 11531 ; WX 632 ; N uni2D0B ; G 3127
+U 11532 ; WX 646 ; N uni2D0C ; G 3128
+U 11533 ; WX 962 ; N uni2D0D ; G 3129
+U 11534 ; WX 645 ; N uni2D0E ; G 3130
+U 11535 ; WX 866 ; N uni2D0F ; G 3131
+U 11536 ; WX 961 ; N uni2D10 ; G 3132
+U 11537 ; WX 645 ; N uni2D11 ; G 3133
+U 11538 ; WX 645 ; N uni2D12 ; G 3134
+U 11539 ; WX 959 ; N uni2D13 ; G 3135
+U 11540 ; WX 945 ; N uni2D14 ; G 3136
+U 11541 ; WX 863 ; N uni2D15 ; G 3137
+U 11542 ; WX 644 ; N uni2D16 ; G 3138
+U 11543 ; WX 646 ; N uni2D17 ; G 3139
+U 11544 ; WX 645 ; N uni2D18 ; G 3140
+U 11545 ; WX 649 ; N uni2D19 ; G 3141
+U 11546 ; WX 688 ; N uni2D1A ; G 3142
+U 11547 ; WX 634 ; N uni2D1B ; G 3143
+U 11548 ; WX 982 ; N uni2D1C ; G 3144
+U 11549 ; WX 681 ; N uni2D1D ; G 3145
+U 11550 ; WX 676 ; N uni2D1E ; G 3146
+U 11551 ; WX 852 ; N uni2D1F ; G 3147
+U 11552 ; WX 957 ; N uni2D20 ; G 3148
+U 11553 ; WX 632 ; N uni2D21 ; G 3149
+U 11554 ; WX 645 ; N uni2D22 ; G 3150
+U 11555 ; WX 646 ; N uni2D23 ; G 3151
+U 11556 ; WX 749 ; N uni2D24 ; G 3152
+U 11557 ; WX 914 ; N uni2D25 ; G 3153
+U 11800 ; WX 536 ; N uni2E18 ; G 3154
+U 11807 ; WX 838 ; N uni2E1F ; G 3155
+U 11810 ; WX 390 ; N uni2E22 ; G 3156
+U 11811 ; WX 390 ; N uni2E23 ; G 3157
+U 11812 ; WX 390 ; N uni2E24 ; G 3158
+U 11813 ; WX 390 ; N uni2E25 ; G 3159
+U 11822 ; WX 536 ; N uni2E2E ; G 3160
+U 42564 ; WX 685 ; N uniA644 ; G 3161
+U 42565 ; WX 513 ; N uniA645 ; G 3162
+U 42566 ; WX 395 ; N uniA646 ; G 3163
+U 42567 ; WX 392 ; N uniA647 ; G 3164
+U 42576 ; WX 1104 ; N uniA650 ; G 3165
+U 42577 ; WX 939 ; N uniA651 ; G 3166
+U 42580 ; WX 1193 ; N uniA654 ; G 3167
+U 42581 ; WX 871 ; N uniA655 ; G 3168
+U 42582 ; WX 1140 ; N uniA656 ; G 3169
+U 42583 ; WX 875 ; N uniA657 ; G 3170
+U 42648 ; WX 1416 ; N uniA698 ; G 3171
+U 42649 ; WX 999 ; N uniA699 ; G 3172
+U 42760 ; WX 493 ; N uniA708 ; G 3173
+U 42761 ; WX 493 ; N uniA709 ; G 3174
+U 42762 ; WX 493 ; N uniA70A ; G 3175
+U 42763 ; WX 493 ; N uniA70B ; G 3176
+U 42764 ; WX 493 ; N uniA70C ; G 3177
+U 42765 ; WX 493 ; N uniA70D ; G 3178
+U 42766 ; WX 493 ; N uniA70E ; G 3179
+U 42767 ; WX 493 ; N uniA70F ; G 3180
+U 42768 ; WX 493 ; N uniA710 ; G 3181
+U 42769 ; WX 493 ; N uniA711 ; G 3182
+U 42770 ; WX 493 ; N uniA712 ; G 3183
+U 42771 ; WX 493 ; N uniA713 ; G 3184
+U 42772 ; WX 493 ; N uniA714 ; G 3185
+U 42773 ; WX 493 ; N uniA715 ; G 3186
+U 42774 ; WX 493 ; N uniA716 ; G 3187
+U 42779 ; WX 369 ; N uniA71B ; G 3188
+U 42780 ; WX 369 ; N uniA71C ; G 3189
+U 42781 ; WX 253 ; N uniA71D ; G 3190
+U 42782 ; WX 253 ; N uniA71E ; G 3191
+U 42783 ; WX 253 ; N uniA71F ; G 3192
+U 42790 ; WX 872 ; N uniA726 ; G 3193
+U 42791 ; WX 634 ; N uniA727 ; G 3194
+U 42792 ; WX 843 ; N uniA728 ; G 3195
+U 42793 ; WX 754 ; N uniA729 ; G 3196
+U 42794 ; WX 612 ; N uniA72A ; G 3197
+U 42795 ; WX 560 ; N uniA72B ; G 3198
+U 42796 ; WX 548 ; N uniA72C ; G 3199
+U 42797 ; WX 531 ; N uniA72D ; G 3200
+U 42798 ; WX 629 ; N uniA72E ; G 3201
+U 42799 ; WX 610 ; N uniA72F ; G 3202
+U 42800 ; WX 514 ; N uniA730 ; G 3203
+U 42801 ; WX 513 ; N uniA731 ; G 3204
+U 42802 ; WX 1195 ; N uniA732 ; G 3205
+U 42803 ; WX 943 ; N uniA733 ; G 3206
+U 42804 ; WX 1226 ; N uniA734 ; G 3207
+U 42805 ; WX 950 ; N uniA735 ; G 3208
+U 42806 ; WX 1149 ; N uniA736 ; G 3209
+U 42807 ; WX 933 ; N uniA737 ; G 3210
+U 42808 ; WX 968 ; N uniA738 ; G 3211
+U 42809 ; WX 784 ; N uniA739 ; G 3212
+U 42810 ; WX 968 ; N uniA73A ; G 3213
+U 42811 ; WX 784 ; N uniA73B ; G 3214
+U 42812 ; WX 962 ; N uniA73C ; G 3215
+U 42813 ; WX 759 ; N uniA73D ; G 3216
+U 42814 ; WX 765 ; N uniA73E ; G 3217
+U 42815 ; WX 560 ; N uniA73F ; G 3218
+U 42816 ; WX 747 ; N uniA740 ; G 3219
+U 42817 ; WX 606 ; N uniA741 ; G 3220
+U 42822 ; WX 787 ; N uniA746 ; G 3221
+U 42823 ; WX 434 ; N uniA747 ; G 3222
+U 42826 ; WX 932 ; N uniA74A ; G 3223
+U 42827 ; WX 711 ; N uniA74B ; G 3224
+U 42830 ; WX 1416 ; N uniA74E ; G 3225
+U 42831 ; WX 999 ; N uniA74F ; G 3226
+U 42856 ; WX 707 ; N uniA768 ; G 3227
+U 42857 ; WX 610 ; N uniA769 ; G 3228
+U 42875 ; WX 612 ; N uniA77B ; G 3229
+U 42876 ; WX 478 ; N uniA77C ; G 3230
+U 42880 ; WX 664 ; N uniA780 ; G 3231
+U 42881 ; WX 320 ; N uniA781 ; G 3232
+U 42882 ; WX 843 ; N uniA782 ; G 3233
+U 42883 ; WX 644 ; N uniA783 ; G 3234
+U 42884 ; WX 612 ; N uniA784 ; G 3235
+U 42885 ; WX 478 ; N uniA785 ; G 3236
+U 42886 ; WX 765 ; N uniA786 ; G 3237
+U 42887 ; WX 560 ; N uniA787 ; G 3238
+U 42891 ; WX 402 ; N uniA78B ; G 3239
+U 42892 ; WX 275 ; N uniA78C ; G 3240
+U 42893 ; WX 773 ; N uniA78D ; G 3241
+U 42896 ; WX 875 ; N uniA790 ; G 3242
+U 42897 ; WX 698 ; N uniA791 ; G 3243
+U 42922 ; WX 872 ; N uniA7AA ; G 3244
+U 43000 ; WX 549 ; N uniA7F8 ; G 3245
+U 43001 ; WX 623 ; N uniA7F9 ; G 3246
+U 43002 ; WX 957 ; N uniA7FA ; G 3247
+U 43003 ; WX 694 ; N uniA7FB ; G 3248
+U 43004 ; WX 673 ; N uniA7FC ; G 3249
+U 43005 ; WX 1024 ; N uniA7FD ; G 3250
+U 43006 ; WX 395 ; N uniA7FE ; G 3251
+U 43007 ; WX 1201 ; N uniA7FF ; G 3252
+U 62464 ; WX 664 ; N uniF400 ; G 3253
+U 62465 ; WX 675 ; N uniF401 ; G 3254
+U 62466 ; WX 724 ; N uniF402 ; G 3255
+U 62467 ; WX 958 ; N uniF403 ; G 3256
+U 62468 ; WX 675 ; N uniF404 ; G 3257
+U 62469 ; WX 669 ; N uniF405 ; G 3258
+U 62470 ; WX 735 ; N uniF406 ; G 3259
+U 62471 ; WX 997 ; N uniF407 ; G 3260
+U 62472 ; WX 675 ; N uniF408 ; G 3261
+U 62473 ; WX 675 ; N uniF409 ; G 3262
+U 62474 ; WX 1268 ; N uniF40A ; G 3263
+U 62475 ; WX 693 ; N uniF40B ; G 3264
+U 62476 ; WX 692 ; N uniF40C ; G 3265
+U 62477 ; WX 963 ; N uniF40D ; G 3266
+U 62478 ; WX 675 ; N uniF40E ; G 3267
+U 62479 ; WX 692 ; N uniF40F ; G 3268
+U 62480 ; WX 1009 ; N uniF410 ; G 3269
+U 62481 ; WX 756 ; N uniF411 ; G 3270
+U 62482 ; WX 809 ; N uniF412 ; G 3271
+U 62483 ; WX 758 ; N uniF413 ; G 3272
+U 62484 ; WX 955 ; N uniF414 ; G 3273
+U 62485 ; WX 691 ; N uniF415 ; G 3274
+U 62486 ; WX 946 ; N uniF416 ; G 3275
+U 62487 ; WX 690 ; N uniF417 ; G 3276
+U 62488 ; WX 698 ; N uniF418 ; G 3277
+U 62489 ; WX 692 ; N uniF419 ; G 3278
+U 62490 ; WX 739 ; N uniF41A ; G 3279
+U 62491 ; WX 692 ; N uniF41B ; G 3280
+U 62492 ; WX 698 ; N uniF41C ; G 3281
+U 62493 ; WX 676 ; N uniF41D ; G 3282
+U 62494 ; WX 739 ; N uniF41E ; G 3283
+U 62495 ; WX 895 ; N uniF41F ; G 3284
+U 62496 ; WX 675 ; N uniF420 ; G 3285
+U 62497 ; WX 785 ; N uniF421 ; G 3286
+U 62498 ; WX 676 ; N uniF422 ; G 3287
+U 62499 ; WX 675 ; N uniF423 ; G 3288
+U 62500 ; WX 675 ; N uniF424 ; G 3289
+U 62501 ; WX 732 ; N uniF425 ; G 3290
+U 62502 ; WX 972 ; N uniF426 ; G 3291
+U 62504 ; WX 904 ; N uniF428 ; G 3292
+U 63172 ; WX 320 ; N uniF6C4 ; G 3293
+U 63173 ; WX 602 ; N uniF6C5 ; G 3294
+U 63174 ; WX 640 ; N uniF6C6 ; G 3295
+U 63175 ; WX 644 ; N uniF6C7 ; G 3296
+U 63176 ; WX 947 ; N uniF6C8 ; G 3297
+U 63185 ; WX 500 ; N cyrBreve ; G 3298
+U 63188 ; WX 500 ; N cyrbreve ; G 3299
+U 64256 ; WX 708 ; N uniFB00 ; G 3300
+U 64257 ; WX 667 ; N fi ; G 3301
+U 64258 ; WX 667 ; N fl ; G 3302
+U 64259 ; WX 941 ; N uniFB03 ; G 3303
+U 64260 ; WX 986 ; N uniFB04 ; G 3304
+U 64261 ; WX 744 ; N uniFB05 ; G 3305
+U 64262 ; WX 916 ; N uniFB06 ; G 3306
+U 65024 ; WX 0 ; N uniFE00 ; G 3307
+U 65025 ; WX 0 ; N uniFE01 ; G 3308
+U 65026 ; WX 0 ; N uniFE02 ; G 3309
+U 65027 ; WX 0 ; N uniFE03 ; G 3310
+U 65028 ; WX 0 ; N uniFE04 ; G 3311
+U 65029 ; WX 0 ; N uniFE05 ; G 3312
+U 65030 ; WX 0 ; N uniFE06 ; G 3313
+U 65031 ; WX 0 ; N uniFE07 ; G 3314
+U 65032 ; WX 0 ; N uniFE08 ; G 3315
+U 65033 ; WX 0 ; N uniFE09 ; G 3316
+U 65034 ; WX 0 ; N uniFE0A ; G 3317
+U 65035 ; WX 0 ; N uniFE0B ; G 3318
+U 65036 ; WX 0 ; N uniFE0C ; G 3319
+U 65037 ; WX 0 ; N uniFE0D ; G 3320
+U 65038 ; WX 0 ; N uniFE0E ; G 3321
+U 65039 ; WX 0 ; N uniFE0F ; G 3322
+U 65529 ; WX 0 ; N uniFFF9 ; G 3323
+U 65530 ; WX 0 ; N uniFFFA ; G 3324
+U 65531 ; WX 0 ; N uniFFFB ; G 3325
+U 65532 ; WX 0 ; N uniFFFC ; G 3326
+U 65533 ; WX 1025 ; N uniFFFD ; G 3327
+EndCharMetrics
+StartKernData
+StartKernPairs 1103
+
+KPX dollar seven -112
+KPX dollar nine -102
+KPX dollar colon -83
+KPX dollar less -83
+KPX dollar I -36
+KPX dollar W -36
+KPX dollar Y -83
+KPX dollar Z -92
+KPX dollar backslash -83
+KPX dollar questiondown -83
+KPX dollar Aacute -83
+KPX dollar Hbar -112
+KPX dollar hbar -36
+KPX dollar lacute -83
+
+KPX percent ampersand 38
+KPX percent asterisk 38
+KPX percent two 38
+KPX percent less -36
+KPX percent Egrave 38
+KPX percent Icircumflex 38
+KPX percent agrave 38
+KPX percent Ebreve 38
+KPX percent lacute -36
+
+
+KPX quotesingle nine -36
+
+
+KPX parenright dollar -178
+KPX parenright D -139
+KPX parenright H -112
+KPX parenright R -112
+KPX parenright cent -139
+KPX parenright sterling -139
+KPX parenright currency -139
+KPX parenright yen -139
+KPX parenright brokenbar -139
+KPX parenright section -139
+KPX parenright dieresis -139
+KPX parenright ordfeminine -112
+KPX parenright guillemotleft -112
+KPX parenright logicalnot -112
+KPX parenright sfthyphen -112
+KPX parenright acute -112
+KPX parenright mu -112
+KPX parenright paragraph -112
+KPX parenright periodcentered -112
+KPX parenright cedilla -112
+KPX parenright ordmasculine -112
+KPX parenright Yacute -112
+KPX parenright ebreve -112
+
+KPX asterisk less -36
+KPX asterisk lacute -36
+
+
+KPX period dollar -83
+KPX period ampersand -55
+KPX period two -55
+KPX period eight -73
+KPX period colon -73
+KPX period less -55
+KPX period H -55
+KPX period R -55
+KPX period X -45
+KPX period backslash -131
+KPX period ordfeminine -55
+KPX period guillemotleft -55
+KPX period logicalnot -55
+KPX period sfthyphen -55
+KPX period acute -55
+KPX period mu -55
+KPX period paragraph -55
+KPX period periodcentered -55
+KPX period cedilla -55
+KPX period ordmasculine -36
+KPX period guillemotright -45
+KPX period onequarter -45
+KPX period onehalf -45
+KPX period threequarters -45
+KPX period questiondown -131
+KPX period Aacute -131
+KPX period Egrave -55
+KPX period Icircumflex -55
+KPX period Yacute -55
+KPX period Ebreve -55
+KPX period ebreve -55
+KPX period Idot -73
+KPX period dotlessi -45
+KPX period lacute -55
+
+KPX slash seven -167
+KPX slash eight -112
+KPX slash nine -243
+KPX slash colon -178
+KPX slash less -131
+KPX slash backslash -36
+KPX slash questiondown -36
+KPX slash Aacute -36
+KPX slash Hbar -167
+KPX slash Idot -112
+KPX slash lacute -131
+
+
+KPX two nine -36
+KPX two semicolon -36
+
+KPX three dollar -188
+KPX three eight -36
+KPX three D -92
+KPX three H -92
+KPX three R -83
+KPX three V -55
+KPX three cent -92
+KPX three sterling -92
+KPX three currency -92
+KPX three yen -92
+KPX three brokenbar -92
+KPX three section -92
+KPX three dieresis -92
+KPX three ordfeminine -92
+KPX three guillemotleft -92
+KPX three logicalnot -92
+KPX three sfthyphen -92
+KPX three acute -83
+KPX three mu -83
+KPX three paragraph -83
+KPX three periodcentered -83
+KPX three cedilla -83
+KPX three ordmasculine -83
+KPX three Yacute -92
+KPX three ebreve -83
+KPX three gdotaccent -55
+KPX three gcommaaccent -55
+KPX three Idot -36
+
+
+KPX five seven -36
+KPX five nine -73
+KPX five colon -45
+KPX five less -63
+KPX five D 47
+KPX five backslash -36
+KPX five cent 47
+KPX five sterling 47
+KPX five currency 47
+KPX five yen 47
+KPX five brokenbar 47
+KPX five section 47
+KPX five dieresis 47
+KPX five ordmasculine 38
+KPX five questiondown -36
+KPX five Aacute -36
+KPX five Hbar -36
+KPX five lacute -63
+
+KPX six six -36
+KPX six Gdotaccent -36
+KPX six Gcommaaccent -36
+
+KPX seven dollar -112
+KPX seven seven 38
+KPX seven D -159
+KPX seven F -159
+KPX seven H -159
+KPX seven R -159
+KPX seven V -149
+KPX seven Z -73
+KPX seven cent -159
+KPX seven sterling -159
+KPX seven currency -159
+KPX seven yen -159
+KPX seven brokenbar -159
+KPX seven section -159
+KPX seven dieresis -159
+KPX seven copyright -159
+KPX seven ordfeminine -159
+KPX seven guillemotleft -159
+KPX seven logicalnot -159
+KPX seven sfthyphen -159
+KPX seven acute -159
+KPX seven mu -159
+KPX seven paragraph -159
+KPX seven periodcentered -159
+KPX seven cedilla -159
+KPX seven ordmasculine -159
+KPX seven Eacute -159
+KPX seven Idieresis -159
+KPX seven Yacute -159
+KPX seven ebreve -159
+KPX seven gdotaccent -149
+KPX seven gcommaaccent -149
+KPX seven Hbar 38
+
+KPX eight dollar -63
+KPX eight hyphen -55
+
+KPX nine dollar -139
+KPX nine two -36
+KPX nine D -188
+KPX nine H -188
+KPX nine L -36
+KPX nine R -188
+KPX nine X -131
+KPX nine backslash -83
+KPX nine cent -188
+KPX nine sterling -188
+KPX nine currency -188
+KPX nine yen -188
+KPX nine brokenbar -188
+KPX nine section -188
+KPX nine dieresis -188
+KPX nine ordfeminine -188
+KPX nine guillemotleft -188
+KPX nine logicalnot -188
+KPX nine sfthyphen -188
+KPX nine acute -188
+KPX nine mu -188
+KPX nine paragraph -188
+KPX nine periodcentered -188
+KPX nine cedilla -188
+KPX nine ordmasculine -188
+KPX nine guillemotright -131
+KPX nine onequarter -131
+KPX nine onehalf -131
+KPX nine threequarters -131
+KPX nine questiondown -83
+KPX nine Aacute -83
+KPX nine Yacute -188
+KPX nine Ebreve -36
+KPX nine ebreve -188
+KPX nine dotlessi -131
+
+KPX colon dollar -102
+KPX colon D -178
+KPX colon H -167
+KPX colon L -36
+KPX colon R -139
+KPX colon U -92
+KPX colon X -83
+KPX colon backslash -45
+KPX colon cent -178
+KPX colon sterling -178
+KPX colon currency -178
+KPX colon yen -178
+KPX colon brokenbar -178
+KPX colon section -178
+KPX colon dieresis -139
+KPX colon ordfeminine -167
+KPX colon guillemotleft -167
+KPX colon logicalnot -167
+KPX colon sfthyphen -167
+KPX colon acute -139
+KPX colon mu -139
+KPX colon paragraph -139
+KPX colon periodcentered -139
+KPX colon cedilla -139
+KPX colon ordmasculine -139
+KPX colon guillemotright -83
+KPX colon onequarter -83
+KPX colon onehalf -83
+KPX colon threequarters -83
+KPX colon questiondown -45
+KPX colon Aacute -45
+KPX colon Yacute -167
+KPX colon ebreve -139
+KPX colon edotaccent -92
+KPX colon ecaron -92
+KPX colon dotlessi -83
+
+KPX semicolon dollar -73
+KPX semicolon ampersand -36
+KPX semicolon two -36
+KPX semicolon Egrave -36
+KPX semicolon Icircumflex -36
+KPX semicolon Ebreve -36
+
+KPX less dollar -159
+KPX less ampersand -36
+KPX less D -159
+KPX less H -178
+KPX less L -36
+KPX less R -178
+KPX less X -178
+KPX less cent -159
+KPX less sterling -159
+KPX less currency -159
+KPX less yen -159
+KPX less brokenbar -159
+KPX less section -159
+KPX less dieresis -196
+KPX less ordfeminine -178
+KPX less guillemotleft -178
+KPX less logicalnot -178
+KPX less sfthyphen -178
+KPX less acute -178
+KPX less mu -178
+KPX less paragraph -178
+KPX less periodcentered -178
+KPX less cedilla -178
+KPX less ordmasculine -178
+KPX less guillemotright -178
+KPX less onequarter -178
+KPX less onehalf -178
+KPX less threequarters -178
+KPX less Egrave -36
+KPX less Icircumflex -36
+KPX less Yacute -178
+KPX less ebreve -215
+KPX less dotlessi -178
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+KPX Eth nine -36
+
+
+KPX agrave less -36
+KPX agrave lacute -36
+
+KPX ucircumflex seven -167
+KPX ucircumflex eight -112
+KPX ucircumflex nine -243
+KPX ucircumflex colon -178
+KPX ucircumflex less -131
+KPX ucircumflex backslash -36
+KPX ucircumflex questiondown -36
+KPX ucircumflex Aacute -36
+KPX ucircumflex Hbar -167
+KPX ucircumflex Idot -112
+KPX ucircumflex lacute -131
+
+KPX ydieresis seven -167
+KPX ydieresis eight -112
+KPX ydieresis nine -243
+KPX ydieresis colon -178
+KPX ydieresis less -131
+KPX ydieresis backslash -36
+KPX ydieresis questiondown -36
+KPX ydieresis Aacute -36
+KPX ydieresis Hbar -167
+KPX ydieresis Idot -112
+KPX ydieresis lacute -131
+
+KPX Abreve O -227
+
+KPX abreve seven -167
+KPX abreve eight -36
+KPX abreve nine -243
+KPX abreve colon -178
+KPX abreve less -206
+KPX abreve backslash -36
+KPX abreve questiondown -36
+KPX abreve Aacute -36
+KPX abreve Hbar -167
+KPX abreve Idot -36
+KPX abreve lacute -206
+
+
+
+KPX Edotaccent seven -36
+KPX Edotaccent nine -73
+KPX Edotaccent colon -45
+KPX Edotaccent less -63
+KPX Edotaccent D 47
+KPX Edotaccent backslash -36
+KPX Edotaccent cent 47
+KPX Edotaccent sterling 47
+KPX Edotaccent currency 47
+KPX Edotaccent yen 47
+KPX Edotaccent brokenbar 47
+KPX Edotaccent section 47
+KPX Edotaccent dieresis 47
+KPX Edotaccent ordmasculine 38
+KPX Edotaccent questiondown -36
+KPX Edotaccent Aacute -36
+KPX Edotaccent Hbar -36
+KPX Edotaccent lacute -63
+
+
+KPX Ecaron seven -36
+KPX Ecaron nine -73
+KPX Ecaron colon -45
+KPX Ecaron less -63
+KPX Ecaron D 47
+KPX Ecaron backslash -36
+KPX Ecaron cent 47
+KPX Ecaron sterling 47
+KPX Ecaron currency 47
+KPX Ecaron yen 47
+KPX Ecaron brokenbar 47
+KPX Ecaron section 47
+KPX Ecaron dieresis 47
+KPX Ecaron ordmasculine 38
+KPX Ecaron questiondown -36
+KPX Ecaron Aacute -36
+KPX Ecaron Hbar -36
+KPX Ecaron lacute -63
+
+
+KPX Gdotaccent six -36
+KPX Gdotaccent Gdotaccent -36
+KPX Gdotaccent Gcommaaccent -36
+
+KPX Gcommaaccent six -36
+KPX Gcommaaccent Gdotaccent -36
+KPX Gcommaaccent Gcommaaccent -36
+
+KPX Hbar dollar -112
+KPX Hbar seven 38
+KPX Hbar D -159
+KPX Hbar F -159
+KPX Hbar H -159
+KPX Hbar R -159
+KPX Hbar V -149
+KPX Hbar Z -73
+KPX Hbar cent -159
+KPX Hbar sterling -159
+KPX Hbar currency -159
+KPX Hbar yen -159
+KPX Hbar brokenbar -159
+KPX Hbar section -159
+KPX Hbar dieresis -159
+KPX Hbar copyright -159
+KPX Hbar ordfeminine -159
+KPX Hbar guillemotleft -159
+KPX Hbar logicalnot -159
+KPX Hbar sfthyphen -159
+KPX Hbar acute -159
+KPX Hbar mu -159
+KPX Hbar paragraph -159
+KPX Hbar periodcentered -159
+KPX Hbar cedilla -159
+KPX Hbar ordmasculine -159
+KPX Hbar Eacute -159
+KPX Hbar Idieresis -159
+KPX Hbar Yacute -159
+KPX Hbar ebreve -159
+KPX Hbar gdotaccent -149
+KPX Hbar gcommaaccent -149
+KPX Hbar Hbar 38
+
+KPX Idot dollar -63
+KPX Idot hyphen -55
+
+KPX kcommaaccent D 110
+KPX kcommaaccent F 85
+KPX kcommaaccent G 97
+KPX kcommaaccent H 86
+KPX kcommaaccent I 220
+KPX kcommaaccent J 97
+KPX kcommaaccent L 220
+KPX kcommaaccent M 218
+KPX kcommaaccent P 125
+KPX kcommaaccent Q 125
+KPX kcommaaccent R 85
+KPX kcommaaccent S 140
+KPX kcommaaccent T 97
+KPX kcommaaccent U 125
+KPX kcommaaccent V 155
+KPX kcommaaccent W 235
+KPX kcommaaccent X 144
+KPX kcommaaccent Y 205
+KPX kcommaaccent Z 166
+KPX kcommaaccent bracketleft 174
+KPX kcommaaccent backslash 205
+KPX kcommaaccent bracketright 179
+KPX kcommaaccent kcommaaccent 261
+
+KPX lacute dollar -159
+KPX lacute ampersand -36
+KPX lacute D -159
+KPX lacute H -178
+KPX lacute L -36
+KPX lacute R -178
+KPX lacute X -178
+KPX lacute cent -159
+KPX lacute sterling -159
+KPX lacute currency -159
+KPX lacute yen -159
+KPX lacute brokenbar -159
+KPX lacute section -159
+KPX lacute dieresis -196
+KPX lacute ordfeminine -178
+KPX lacute guillemotleft -178
+KPX lacute logicalnot -178
+KPX lacute sfthyphen -178
+KPX lacute acute -178
+KPX lacute mu -178
+KPX lacute paragraph -178
+KPX lacute periodcentered -178
+KPX lacute cedilla -178
+KPX lacute ordmasculine -178
+KPX lacute guillemotright -178
+KPX lacute onequarter -178
+KPX lacute onehalf -178
+KPX lacute threequarters -178
+KPX lacute Egrave -36
+KPX lacute Icircumflex -36
+KPX lacute Yacute -178
+KPX lacute ebreve -215
+KPX lacute dotlessi -178
+
+
+KPX uni027D dollar -264
+KPX uni027D hyphen 47
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ttf b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ttf
new file mode 100644
index 0000000..0b803d2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ttf
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ufm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ufm
new file mode 100644
index 0000000..358b026
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/DejaVuSerif.ufm
@@ -0,0 +1,4012 @@
+StartFontMetrics 4.1
+Notice Converted by PHP-font-lib
+Comment https://github.com/PhenX/php-font-lib
+EncodingScheme FontSpecific
+FontName DejaVu Serif
+FontSubfamily Book
+UniqueID DejaVu Serif
+FullName DejaVu Serif
+Version Version 2.37
+PostScriptName DejaVuSerif
+Manufacturer DejaVu fonts team
+FontVendorURL http://dejavu.sourceforge.net
+LicenseURL http://dejavu.sourceforge.net/wiki/index.php/License
+PreferredFamily DejaVu Serif
+PreferredSubfamily Book
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+UnderlineThickness 44
+UnderlinePosition -63
+FontHeightOffset 0
+Ascender 928
+Descender -236
+FontBBox -770 -347 2105 1109
+StartCharMetrics 3528
+U 32 ; WX 318 ; N space ; G 3
+U 33 ; WX 402 ; N exclam ; G 4
+U 34 ; WX 460 ; N quotedbl ; G 5
+U 35 ; WX 838 ; N numbersign ; G 6
+U 36 ; WX 636 ; N dollar ; G 7
+U 37 ; WX 950 ; N percent ; G 8
+U 38 ; WX 890 ; N ampersand ; G 9
+U 39 ; WX 275 ; N quotesingle ; G 10
+U 40 ; WX 390 ; N parenleft ; G 11
+U 41 ; WX 390 ; N parenright ; G 12
+U 42 ; WX 500 ; N asterisk ; G 13
+U 43 ; WX 838 ; N plus ; G 14
+U 44 ; WX 318 ; N comma ; G 15
+U 45 ; WX 338 ; N hyphen ; G 16
+U 46 ; WX 318 ; N period ; G 17
+U 47 ; WX 337 ; N slash ; G 18
+U 48 ; WX 636 ; N zero ; G 19
+U 49 ; WX 636 ; N one ; G 20
+U 50 ; WX 636 ; N two ; G 21
+U 51 ; WX 636 ; N three ; G 22
+U 52 ; WX 636 ; N four ; G 23
+U 53 ; WX 636 ; N five ; G 24
+U 54 ; WX 636 ; N six ; G 25
+U 55 ; WX 636 ; N seven ; G 26
+U 56 ; WX 636 ; N eight ; G 27
+U 57 ; WX 636 ; N nine ; G 28
+U 58 ; WX 337 ; N colon ; G 29
+U 59 ; WX 337 ; N semicolon ; G 30
+U 60 ; WX 838 ; N less ; G 31
+U 61 ; WX 838 ; N equal ; G 32
+U 62 ; WX 838 ; N greater ; G 33
+U 63 ; WX 536 ; N question ; G 34
+U 64 ; WX 1000 ; N at ; G 35
+U 65 ; WX 722 ; N A ; G 36
+U 66 ; WX 735 ; N B ; G 37
+U 67 ; WX 765 ; N C ; G 38
+U 68 ; WX 802 ; N D ; G 39
+U 69 ; WX 730 ; N E ; G 40
+U 70 ; WX 694 ; N F ; G 41
+U 71 ; WX 799 ; N G ; G 42
+U 72 ; WX 872 ; N H ; G 43
+U 73 ; WX 395 ; N I ; G 44
+U 74 ; WX 401 ; N J ; G 45
+U 75 ; WX 747 ; N K ; G 46
+U 76 ; WX 664 ; N L ; G 47
+U 77 ; WX 1024 ; N M ; G 48
+U 78 ; WX 875 ; N N ; G 49
+U 79 ; WX 820 ; N O ; G 50
+U 80 ; WX 673 ; N P ; G 51
+U 81 ; WX 820 ; N Q ; G 52
+U 82 ; WX 753 ; N R ; G 53
+U 83 ; WX 685 ; N S ; G 54
+U 84 ; WX 667 ; N T ; G 55
+U 85 ; WX 843 ; N U ; G 56
+U 86 ; WX 722 ; N V ; G 57
+U 87 ; WX 1028 ; N W ; G 58
+U 88 ; WX 712 ; N X ; G 59
+U 89 ; WX 660 ; N Y ; G 60
+U 90 ; WX 695 ; N Z ; G 61
+U 91 ; WX 390 ; N bracketleft ; G 62
+U 92 ; WX 337 ; N backslash ; G 63
+U 93 ; WX 390 ; N bracketright ; G 64
+U 94 ; WX 838 ; N asciicircum ; G 65
+U 95 ; WX 500 ; N underscore ; G 66
+U 96 ; WX 500 ; N grave ; G 67
+U 97 ; WX 596 ; N a ; G 68
+U 98 ; WX 640 ; N b ; G 69
+U 99 ; WX 560 ; N c ; G 70
+U 100 ; WX 640 ; N d ; G 71
+U 101 ; WX 592 ; N e ; G 72
+U 102 ; WX 370 ; N f ; G 73
+U 103 ; WX 640 ; N g ; G 74
+U 104 ; WX 644 ; N h ; G 75
+U 105 ; WX 320 ; N i ; G 76
+U 106 ; WX 310 ; N j ; G 77
+U 107 ; WX 606 ; N k ; G 78
+U 108 ; WX 320 ; N l ; G 79
+U 109 ; WX 948 ; N m ; G 80
+U 110 ; WX 644 ; N n ; G 81
+U 111 ; WX 602 ; N o ; G 82
+U 112 ; WX 640 ; N p ; G 83
+U 113 ; WX 640 ; N q ; G 84
+U 114 ; WX 478 ; N r ; G 85
+U 115 ; WX 513 ; N s ; G 86
+U 116 ; WX 402 ; N t ; G 87
+U 117 ; WX 644 ; N u ; G 88
+U 118 ; WX 565 ; N v ; G 89
+U 119 ; WX 856 ; N w ; G 90
+U 120 ; WX 564 ; N x ; G 91
+U 121 ; WX 565 ; N y ; G 92
+U 122 ; WX 527 ; N z ; G 93
+U 123 ; WX 636 ; N braceleft ; G 94
+U 124 ; WX 337 ; N bar ; G 95
+U 125 ; WX 636 ; N braceright ; G 96
+U 126 ; WX 838 ; N asciitilde ; G 97
+U 160 ; WX 318 ; N nbspace ; G 98
+U 161 ; WX 402 ; N exclamdown ; G 99
+U 162 ; WX 636 ; N cent ; G 100
+U 163 ; WX 636 ; N sterling ; G 101
+U 164 ; WX 636 ; N currency ; G 102
+U 165 ; WX 636 ; N yen ; G 103
+U 166 ; WX 337 ; N brokenbar ; G 104
+U 167 ; WX 500 ; N section ; G 105
+U 168 ; WX 500 ; N dieresis ; G 106
+U 169 ; WX 1000 ; N copyright ; G 107
+U 170 ; WX 475 ; N ordfeminine ; G 108
+U 171 ; WX 612 ; N guillemotleft ; G 109
+U 172 ; WX 838 ; N logicalnot ; G 110
+U 173 ; WX 338 ; N sfthyphen ; G 111
+U 174 ; WX 1000 ; N registered ; G 112
+U 175 ; WX 500 ; N macron ; G 113
+U 176 ; WX 500 ; N degree ; G 114
+U 177 ; WX 838 ; N plusminus ; G 115
+U 178 ; WX 401 ; N twosuperior ; G 116
+U 179 ; WX 401 ; N threesuperior ; G 117
+U 180 ; WX 500 ; N acute ; G 118
+U 181 ; WX 650 ; N mu ; G 119
+U 182 ; WX 636 ; N paragraph ; G 120
+U 183 ; WX 318 ; N periodcentered ; G 121
+U 184 ; WX 500 ; N cedilla ; G 122
+U 185 ; WX 401 ; N onesuperior ; G 123
+U 186 ; WX 470 ; N ordmasculine ; G 124
+U 187 ; WX 612 ; N guillemotright ; G 125
+U 188 ; WX 969 ; N onequarter ; G 126
+U 189 ; WX 969 ; N onehalf ; G 127
+U 190 ; WX 969 ; N threequarters ; G 128
+U 191 ; WX 536 ; N questiondown ; G 129
+U 192 ; WX 722 ; N Agrave ; G 130
+U 193 ; WX 722 ; N Aacute ; G 131
+U 194 ; WX 722 ; N Acircumflex ; G 132
+U 195 ; WX 722 ; N Atilde ; G 133
+U 196 ; WX 722 ; N Adieresis ; G 134
+U 197 ; WX 722 ; N Aring ; G 135
+U 198 ; WX 1001 ; N AE ; G 136
+U 199 ; WX 765 ; N Ccedilla ; G 137
+U 200 ; WX 730 ; N Egrave ; G 138
+U 201 ; WX 730 ; N Eacute ; G 139
+U 202 ; WX 730 ; N Ecircumflex ; G 140
+U 203 ; WX 730 ; N Edieresis ; G 141
+U 204 ; WX 395 ; N Igrave ; G 142
+U 205 ; WX 395 ; N Iacute ; G 143
+U 206 ; WX 395 ; N Icircumflex ; G 144
+U 207 ; WX 395 ; N Idieresis ; G 145
+U 208 ; WX 807 ; N Eth ; G 146
+U 209 ; WX 875 ; N Ntilde ; G 147
+U 210 ; WX 820 ; N Ograve ; G 148
+U 211 ; WX 820 ; N Oacute ; G 149
+U 212 ; WX 820 ; N Ocircumflex ; G 150
+U 213 ; WX 820 ; N Otilde ; G 151
+U 214 ; WX 820 ; N Odieresis ; G 152
+U 215 ; WX 838 ; N multiply ; G 153
+U 216 ; WX 820 ; N Oslash ; G 154
+U 217 ; WX 843 ; N Ugrave ; G 155
+U 218 ; WX 843 ; N Uacute ; G 156
+U 219 ; WX 843 ; N Ucircumflex ; G 157
+U 220 ; WX 843 ; N Udieresis ; G 158
+U 221 ; WX 660 ; N Yacute ; G 159
+U 222 ; WX 676 ; N Thorn ; G 160
+U 223 ; WX 668 ; N germandbls ; G 161
+U 224 ; WX 596 ; N agrave ; G 162
+U 225 ; WX 596 ; N aacute ; G 163
+U 226 ; WX 596 ; N acircumflex ; G 164
+U 227 ; WX 596 ; N atilde ; G 165
+U 228 ; WX 596 ; N adieresis ; G 166
+U 229 ; WX 596 ; N aring ; G 167
+U 230 ; WX 940 ; N ae ; G 168
+U 231 ; WX 560 ; N ccedilla ; G 169
+U 232 ; WX 592 ; N egrave ; G 170
+U 233 ; WX 592 ; N eacute ; G 171
+U 234 ; WX 592 ; N ecircumflex ; G 172
+U 235 ; WX 592 ; N edieresis ; G 173
+U 236 ; WX 320 ; N igrave ; G 174
+U 237 ; WX 320 ; N iacute ; G 175
+U 238 ; WX 320 ; N icircumflex ; G 176
+U 239 ; WX 320 ; N idieresis ; G 177
+U 240 ; WX 602 ; N eth ; G 178
+U 241 ; WX 644 ; N ntilde ; G 179
+U 242 ; WX 602 ; N ograve ; G 180
+U 243 ; WX 602 ; N oacute ; G 181
+U 244 ; WX 602 ; N ocircumflex ; G 182
+U 245 ; WX 602 ; N otilde ; G 183
+U 246 ; WX 602 ; N odieresis ; G 184
+U 247 ; WX 838 ; N divide ; G 185
+U 248 ; WX 602 ; N oslash ; G 186
+U 249 ; WX 644 ; N ugrave ; G 187
+U 250 ; WX 644 ; N uacute ; G 188
+U 251 ; WX 644 ; N ucircumflex ; G 189
+U 252 ; WX 644 ; N udieresis ; G 190
+U 253 ; WX 565 ; N yacute ; G 191
+U 254 ; WX 640 ; N thorn ; G 192
+U 255 ; WX 565 ; N ydieresis ; G 193
+U 256 ; WX 722 ; N Amacron ; G 194
+U 257 ; WX 596 ; N amacron ; G 195
+U 258 ; WX 722 ; N Abreve ; G 196
+U 259 ; WX 596 ; N abreve ; G 197
+U 260 ; WX 722 ; N Aogonek ; G 198
+U 261 ; WX 596 ; N aogonek ; G 199
+U 262 ; WX 765 ; N Cacute ; G 200
+U 263 ; WX 560 ; N cacute ; G 201
+U 264 ; WX 765 ; N Ccircumflex ; G 202
+U 265 ; WX 560 ; N ccircumflex ; G 203
+U 266 ; WX 765 ; N Cdotaccent ; G 204
+U 267 ; WX 560 ; N cdotaccent ; G 205
+U 268 ; WX 765 ; N Ccaron ; G 206
+U 269 ; WX 560 ; N ccaron ; G 207
+U 270 ; WX 802 ; N Dcaron ; G 208
+U 271 ; WX 640 ; N dcaron ; G 209
+U 272 ; WX 807 ; N Dcroat ; G 210
+U 273 ; WX 640 ; N dmacron ; G 211
+U 274 ; WX 730 ; N Emacron ; G 212
+U 275 ; WX 592 ; N emacron ; G 213
+U 276 ; WX 730 ; N Ebreve ; G 214
+U 277 ; WX 592 ; N ebreve ; G 215
+U 278 ; WX 730 ; N Edotaccent ; G 216
+U 279 ; WX 592 ; N edotaccent ; G 217
+U 280 ; WX 730 ; N Eogonek ; G 218
+U 281 ; WX 592 ; N eogonek ; G 219
+U 282 ; WX 730 ; N Ecaron ; G 220
+U 283 ; WX 592 ; N ecaron ; G 221
+U 284 ; WX 799 ; N Gcircumflex ; G 222
+U 285 ; WX 640 ; N gcircumflex ; G 223
+U 286 ; WX 799 ; N Gbreve ; G 224
+U 287 ; WX 640 ; N gbreve ; G 225
+U 288 ; WX 799 ; N Gdotaccent ; G 226
+U 289 ; WX 640 ; N gdotaccent ; G 227
+U 290 ; WX 799 ; N Gcommaaccent ; G 228
+U 291 ; WX 640 ; N gcommaaccent ; G 229
+U 292 ; WX 872 ; N Hcircumflex ; G 230
+U 293 ; WX 644 ; N hcircumflex ; G 231
+U 294 ; WX 872 ; N Hbar ; G 232
+U 295 ; WX 644 ; N hbar ; G 233
+U 296 ; WX 395 ; N Itilde ; G 234
+U 297 ; WX 320 ; N itilde ; G 235
+U 298 ; WX 395 ; N Imacron ; G 236
+U 299 ; WX 320 ; N imacron ; G 237
+U 300 ; WX 395 ; N Ibreve ; G 238
+U 301 ; WX 320 ; N ibreve ; G 239
+U 302 ; WX 395 ; N Iogonek ; G 240
+U 303 ; WX 320 ; N iogonek ; G 241
+U 304 ; WX 395 ; N Idot ; G 242
+U 305 ; WX 320 ; N dotlessi ; G 243
+U 306 ; WX 801 ; N IJ ; G 244
+U 307 ; WX 533 ; N ij ; G 245
+U 308 ; WX 401 ; N Jcircumflex ; G 246
+U 309 ; WX 310 ; N jcircumflex ; G 247
+U 310 ; WX 747 ; N Kcommaaccent ; G 248
+U 311 ; WX 606 ; N kcommaaccent ; G 249
+U 312 ; WX 606 ; N kgreenlandic ; G 250
+U 313 ; WX 664 ; N Lacute ; G 251
+U 314 ; WX 320 ; N lacute ; G 252
+U 315 ; WX 664 ; N Lcommaaccent ; G 253
+U 316 ; WX 320 ; N lcommaaccent ; G 254
+U 317 ; WX 664 ; N Lcaron ; G 255
+U 318 ; WX 320 ; N lcaron ; G 256
+U 319 ; WX 664 ; N Ldot ; G 257
+U 320 ; WX 320 ; N ldot ; G 258
+U 321 ; WX 669 ; N Lslash ; G 259
+U 322 ; WX 324 ; N lslash ; G 260
+U 323 ; WX 875 ; N Nacute ; G 261
+U 324 ; WX 644 ; N nacute ; G 262
+U 325 ; WX 875 ; N Ncommaaccent ; G 263
+U 326 ; WX 644 ; N ncommaaccent ; G 264
+U 327 ; WX 875 ; N Ncaron ; G 265
+U 328 ; WX 644 ; N ncaron ; G 266
+U 329 ; WX 866 ; N napostrophe ; G 267
+U 330 ; WX 843 ; N Eng ; G 268
+U 331 ; WX 644 ; N eng ; G 269
+U 332 ; WX 820 ; N Omacron ; G 270
+U 333 ; WX 602 ; N omacron ; G 271
+U 334 ; WX 820 ; N Obreve ; G 272
+U 335 ; WX 602 ; N obreve ; G 273
+U 336 ; WX 820 ; N Ohungarumlaut ; G 274
+U 337 ; WX 602 ; N ohungarumlaut ; G 275
+U 338 ; WX 1137 ; N OE ; G 276
+U 339 ; WX 989 ; N oe ; G 277
+U 340 ; WX 753 ; N Racute ; G 278
+U 341 ; WX 478 ; N racute ; G 279
+U 342 ; WX 753 ; N Rcommaaccent ; G 280
+U 343 ; WX 478 ; N rcommaaccent ; G 281
+U 344 ; WX 753 ; N Rcaron ; G 282
+U 345 ; WX 478 ; N rcaron ; G 283
+U 346 ; WX 685 ; N Sacute ; G 284
+U 347 ; WX 513 ; N sacute ; G 285
+U 348 ; WX 685 ; N Scircumflex ; G 286
+U 349 ; WX 513 ; N scircumflex ; G 287
+U 350 ; WX 685 ; N Scedilla ; G 288
+U 351 ; WX 513 ; N scedilla ; G 289
+U 352 ; WX 685 ; N Scaron ; G 290
+U 353 ; WX 513 ; N scaron ; G 291
+U 354 ; WX 667 ; N Tcommaaccent ; G 292
+U 355 ; WX 402 ; N tcommaaccent ; G 293
+U 356 ; WX 667 ; N Tcaron ; G 294
+U 357 ; WX 402 ; N tcaron ; G 295
+U 358 ; WX 667 ; N Tbar ; G 296
+U 359 ; WX 402 ; N tbar ; G 297
+U 360 ; WX 843 ; N Utilde ; G 298
+U 361 ; WX 644 ; N utilde ; G 299
+U 362 ; WX 843 ; N Umacron ; G 300
+U 363 ; WX 644 ; N umacron ; G 301
+U 364 ; WX 843 ; N Ubreve ; G 302
+U 365 ; WX 644 ; N ubreve ; G 303
+U 366 ; WX 843 ; N Uring ; G 304
+U 367 ; WX 644 ; N uring ; G 305
+U 368 ; WX 843 ; N Uhungarumlaut ; G 306
+U 369 ; WX 644 ; N uhungarumlaut ; G 307
+U 370 ; WX 843 ; N Uogonek ; G 308
+U 371 ; WX 644 ; N uogonek ; G 309
+U 372 ; WX 1028 ; N Wcircumflex ; G 310
+U 373 ; WX 856 ; N wcircumflex ; G 311
+U 374 ; WX 660 ; N Ycircumflex ; G 312
+U 375 ; WX 565 ; N ycircumflex ; G 313
+U 376 ; WX 660 ; N Ydieresis ; G 314
+U 377 ; WX 695 ; N Zacute ; G 315
+U 378 ; WX 527 ; N zacute ; G 316
+U 379 ; WX 695 ; N Zdotaccent ; G 317
+U 380 ; WX 527 ; N zdotaccent ; G 318
+U 381 ; WX 695 ; N Zcaron ; G 319
+U 382 ; WX 527 ; N zcaron ; G 320
+U 383 ; WX 370 ; N longs ; G 321
+U 384 ; WX 640 ; N uni0180 ; G 322
+U 385 ; WX 735 ; N uni0181 ; G 323
+U 386 ; WX 735 ; N uni0182 ; G 324
+U 387 ; WX 640 ; N uni0183 ; G 325
+U 388 ; WX 735 ; N uni0184 ; G 326
+U 389 ; WX 640 ; N uni0185 ; G 327
+U 390 ; WX 765 ; N uni0186 ; G 328
+U 391 ; WX 765 ; N uni0187 ; G 329
+U 392 ; WX 560 ; N uni0188 ; G 330
+U 393 ; WX 807 ; N uni0189 ; G 331
+U 394 ; WX 802 ; N uni018A ; G 332
+U 395 ; WX 735 ; N uni018B ; G 333
+U 396 ; WX 640 ; N uni018C ; G 334
+U 397 ; WX 602 ; N uni018D ; G 335
+U 398 ; WX 730 ; N uni018E ; G 336
+U 399 ; WX 820 ; N uni018F ; G 337
+U 400 ; WX 623 ; N uni0190 ; G 338
+U 401 ; WX 694 ; N uni0191 ; G 339
+U 402 ; WX 370 ; N florin ; G 340
+U 403 ; WX 799 ; N uni0193 ; G 341
+U 404 ; WX 712 ; N uni0194 ; G 342
+U 405 ; WX 932 ; N uni0195 ; G 343
+U 406 ; WX 395 ; N uni0196 ; G 344
+U 407 ; WX 395 ; N uni0197 ; G 345
+U 408 ; WX 747 ; N uni0198 ; G 346
+U 409 ; WX 606 ; N uni0199 ; G 347
+U 410 ; WX 320 ; N uni019A ; G 348
+U 411 ; WX 634 ; N uni019B ; G 349
+U 412 ; WX 948 ; N uni019C ; G 350
+U 413 ; WX 875 ; N uni019D ; G 351
+U 414 ; WX 644 ; N uni019E ; G 352
+U 415 ; WX 820 ; N uni019F ; G 353
+U 416 ; WX 820 ; N Ohorn ; G 354
+U 417 ; WX 602 ; N ohorn ; G 355
+U 418 ; WX 1040 ; N uni01A2 ; G 356
+U 419 ; WX 807 ; N uni01A3 ; G 357
+U 420 ; WX 673 ; N uni01A4 ; G 358
+U 421 ; WX 640 ; N uni01A5 ; G 359
+U 422 ; WX 753 ; N uni01A6 ; G 360
+U 423 ; WX 685 ; N uni01A7 ; G 361
+U 424 ; WX 513 ; N uni01A8 ; G 362
+U 425 ; WX 707 ; N uni01A9 ; G 363
+U 426 ; WX 324 ; N uni01AA ; G 364
+U 427 ; WX 402 ; N uni01AB ; G 365
+U 428 ; WX 667 ; N uni01AC ; G 366
+U 429 ; WX 402 ; N uni01AD ; G 367
+U 430 ; WX 667 ; N uni01AE ; G 368
+U 431 ; WX 843 ; N Uhorn ; G 369
+U 432 ; WX 644 ; N uhorn ; G 370
+U 433 ; WX 829 ; N uni01B1 ; G 371
+U 434 ; WX 760 ; N uni01B2 ; G 372
+U 435 ; WX 738 ; N uni01B3 ; G 373
+U 436 ; WX 663 ; N uni01B4 ; G 374
+U 437 ; WX 695 ; N uni01B5 ; G 375
+U 438 ; WX 527 ; N uni01B6 ; G 376
+U 439 ; WX 564 ; N uni01B7 ; G 377
+U 440 ; WX 564 ; N uni01B8 ; G 378
+U 441 ; WX 564 ; N uni01B9 ; G 379
+U 442 ; WX 564 ; N uni01BA ; G 380
+U 443 ; WX 636 ; N uni01BB ; G 381
+U 444 ; WX 687 ; N uni01BC ; G 382
+U 445 ; WX 564 ; N uni01BD ; G 383
+U 446 ; WX 536 ; N uni01BE ; G 384
+U 447 ; WX 635 ; N uni01BF ; G 385
+U 448 ; WX 295 ; N uni01C0 ; G 386
+U 449 ; WX 492 ; N uni01C1 ; G 387
+U 450 ; WX 459 ; N uni01C2 ; G 388
+U 451 ; WX 295 ; N uni01C3 ; G 389
+U 452 ; WX 1497 ; N uni01C4 ; G 390
+U 453 ; WX 1329 ; N uni01C5 ; G 391
+U 454 ; WX 1167 ; N uni01C6 ; G 392
+U 455 ; WX 1065 ; N uni01C7 ; G 393
+U 456 ; WX 974 ; N uni01C8 ; G 394
+U 457 ; WX 630 ; N uni01C9 ; G 395
+U 458 ; WX 1276 ; N uni01CA ; G 396
+U 459 ; WX 1185 ; N uni01CB ; G 397
+U 460 ; WX 954 ; N uni01CC ; G 398
+U 461 ; WX 722 ; N uni01CD ; G 399
+U 462 ; WX 596 ; N uni01CE ; G 400
+U 463 ; WX 395 ; N uni01CF ; G 401
+U 464 ; WX 320 ; N uni01D0 ; G 402
+U 465 ; WX 820 ; N uni01D1 ; G 403
+U 466 ; WX 602 ; N uni01D2 ; G 404
+U 467 ; WX 843 ; N uni01D3 ; G 405
+U 468 ; WX 644 ; N uni01D4 ; G 406
+U 469 ; WX 843 ; N uni01D5 ; G 407
+U 470 ; WX 644 ; N uni01D6 ; G 408
+U 471 ; WX 843 ; N uni01D7 ; G 409
+U 472 ; WX 644 ; N uni01D8 ; G 410
+U 473 ; WX 843 ; N uni01D9 ; G 411
+U 474 ; WX 644 ; N uni01DA ; G 412
+U 475 ; WX 843 ; N uni01DB ; G 413
+U 476 ; WX 644 ; N uni01DC ; G 414
+U 477 ; WX 592 ; N uni01DD ; G 415
+U 478 ; WX 722 ; N uni01DE ; G 416
+U 479 ; WX 596 ; N uni01DF ; G 417
+U 480 ; WX 722 ; N uni01E0 ; G 418
+U 481 ; WX 596 ; N uni01E1 ; G 419
+U 482 ; WX 1001 ; N uni01E2 ; G 420
+U 483 ; WX 940 ; N uni01E3 ; G 421
+U 484 ; WX 848 ; N uni01E4 ; G 422
+U 485 ; WX 640 ; N uni01E5 ; G 423
+U 486 ; WX 799 ; N Gcaron ; G 424
+U 487 ; WX 640 ; N gcaron ; G 425
+U 488 ; WX 747 ; N uni01E8 ; G 426
+U 489 ; WX 606 ; N uni01E9 ; G 427
+U 490 ; WX 820 ; N uni01EA ; G 428
+U 491 ; WX 602 ; N uni01EB ; G 429
+U 492 ; WX 820 ; N uni01EC ; G 430
+U 493 ; WX 602 ; N uni01ED ; G 431
+U 494 ; WX 564 ; N uni01EE ; G 432
+U 495 ; WX 564 ; N uni01EF ; G 433
+U 496 ; WX 320 ; N uni01F0 ; G 434
+U 497 ; WX 1497 ; N uni01F1 ; G 435
+U 498 ; WX 1329 ; N uni01F2 ; G 436
+U 499 ; WX 1167 ; N uni01F3 ; G 437
+U 500 ; WX 799 ; N uni01F4 ; G 438
+U 501 ; WX 640 ; N uni01F5 ; G 439
+U 502 ; WX 1154 ; N uni01F6 ; G 440
+U 503 ; WX 707 ; N uni01F7 ; G 441
+U 504 ; WX 875 ; N uni01F8 ; G 442
+U 505 ; WX 644 ; N uni01F9 ; G 443
+U 506 ; WX 722 ; N Aringacute ; G 444
+U 507 ; WX 596 ; N aringacute ; G 445
+U 508 ; WX 1001 ; N AEacute ; G 446
+U 509 ; WX 940 ; N aeacute ; G 447
+U 510 ; WX 820 ; N Oslashacute ; G 448
+U 511 ; WX 602 ; N oslashacute ; G 449
+U 512 ; WX 722 ; N uni0200 ; G 450
+U 513 ; WX 596 ; N uni0201 ; G 451
+U 514 ; WX 722 ; N uni0202 ; G 452
+U 515 ; WX 596 ; N uni0203 ; G 453
+U 516 ; WX 730 ; N uni0204 ; G 454
+U 517 ; WX 592 ; N uni0205 ; G 455
+U 518 ; WX 730 ; N uni0206 ; G 456
+U 519 ; WX 592 ; N uni0207 ; G 457
+U 520 ; WX 395 ; N uni0208 ; G 458
+U 521 ; WX 320 ; N uni0209 ; G 459
+U 522 ; WX 395 ; N uni020A ; G 460
+U 523 ; WX 320 ; N uni020B ; G 461
+U 524 ; WX 820 ; N uni020C ; G 462
+U 525 ; WX 602 ; N uni020D ; G 463
+U 526 ; WX 820 ; N uni020E ; G 464
+U 527 ; WX 602 ; N uni020F ; G 465
+U 528 ; WX 753 ; N uni0210 ; G 466
+U 529 ; WX 478 ; N uni0211 ; G 467
+U 530 ; WX 753 ; N uni0212 ; G 468
+U 531 ; WX 478 ; N uni0213 ; G 469
+U 532 ; WX 843 ; N uni0214 ; G 470
+U 533 ; WX 644 ; N uni0215 ; G 471
+U 534 ; WX 843 ; N uni0216 ; G 472
+U 535 ; WX 644 ; N uni0217 ; G 473
+U 536 ; WX 685 ; N Scommaaccent ; G 474
+U 537 ; WX 513 ; N scommaaccent ; G 475
+U 538 ; WX 667 ; N uni021A ; G 476
+U 539 ; WX 402 ; N uni021B ; G 477
+U 540 ; WX 627 ; N uni021C ; G 478
+U 541 ; WX 521 ; N uni021D ; G 479
+U 542 ; WX 872 ; N uni021E ; G 480
+U 543 ; WX 644 ; N uni021F ; G 481
+U 544 ; WX 843 ; N uni0220 ; G 482
+U 545 ; WX 814 ; N uni0221 ; G 483
+U 546 ; WX 572 ; N uni0222 ; G 484
+U 547 ; WX 552 ; N uni0223 ; G 485
+U 548 ; WX 695 ; N uni0224 ; G 486
+U 549 ; WX 527 ; N uni0225 ; G 487
+U 550 ; WX 722 ; N uni0226 ; G 488
+U 551 ; WX 596 ; N uni0227 ; G 489
+U 552 ; WX 730 ; N uni0228 ; G 490
+U 553 ; WX 592 ; N uni0229 ; G 491
+U 554 ; WX 820 ; N uni022A ; G 492
+U 555 ; WX 602 ; N uni022B ; G 493
+U 556 ; WX 820 ; N uni022C ; G 494
+U 557 ; WX 602 ; N uni022D ; G 495
+U 558 ; WX 820 ; N uni022E ; G 496
+U 559 ; WX 602 ; N uni022F ; G 497
+U 560 ; WX 820 ; N uni0230 ; G 498
+U 561 ; WX 602 ; N uni0231 ; G 499
+U 562 ; WX 660 ; N uni0232 ; G 500
+U 563 ; WX 565 ; N uni0233 ; G 501
+U 564 ; WX 500 ; N uni0234 ; G 502
+U 565 ; WX 832 ; N uni0235 ; G 503
+U 566 ; WX 494 ; N uni0236 ; G 504
+U 567 ; WX 310 ; N dotlessj ; G 505
+U 568 ; WX 960 ; N uni0238 ; G 506
+U 569 ; WX 960 ; N uni0239 ; G 507
+U 570 ; WX 722 ; N uni023A ; G 508
+U 571 ; WX 765 ; N uni023B ; G 509
+U 572 ; WX 560 ; N uni023C ; G 510
+U 573 ; WX 664 ; N uni023D ; G 511
+U 574 ; WX 667 ; N uni023E ; G 512
+U 575 ; WX 513 ; N uni023F ; G 513
+U 576 ; WX 527 ; N uni0240 ; G 514
+U 577 ; WX 583 ; N uni0241 ; G 515
+U 578 ; WX 464 ; N uni0242 ; G 516
+U 579 ; WX 735 ; N uni0243 ; G 517
+U 580 ; WX 843 ; N uni0244 ; G 518
+U 581 ; WX 722 ; N uni0245 ; G 519
+U 582 ; WX 730 ; N uni0246 ; G 520
+U 583 ; WX 592 ; N uni0247 ; G 521
+U 584 ; WX 401 ; N uni0248 ; G 522
+U 585 ; WX 315 ; N uni0249 ; G 523
+U 586 ; WX 782 ; N uni024A ; G 524
+U 587 ; WX 640 ; N uni024B ; G 525
+U 588 ; WX 753 ; N uni024C ; G 526
+U 589 ; WX 478 ; N uni024D ; G 527
+U 590 ; WX 660 ; N uni024E ; G 528
+U 591 ; WX 565 ; N uni024F ; G 529
+U 592 ; WX 596 ; N uni0250 ; G 530
+U 593 ; WX 640 ; N uni0251 ; G 531
+U 594 ; WX 640 ; N uni0252 ; G 532
+U 595 ; WX 640 ; N uni0253 ; G 533
+U 596 ; WX 560 ; N uni0254 ; G 534
+U 597 ; WX 560 ; N uni0255 ; G 535
+U 598 ; WX 647 ; N uni0256 ; G 536
+U 599 ; WX 683 ; N uni0257 ; G 537
+U 600 ; WX 592 ; N uni0258 ; G 538
+U 601 ; WX 592 ; N uni0259 ; G 539
+U 602 ; WX 843 ; N uni025A ; G 540
+U 603 ; WX 518 ; N uni025B ; G 541
+U 604 ; WX 509 ; N uni025C ; G 542
+U 605 ; WX 773 ; N uni025D ; G 543
+U 606 ; WX 613 ; N uni025E ; G 544
+U 607 ; WX 315 ; N uni025F ; G 545
+U 608 ; WX 683 ; N uni0260 ; G 546
+U 609 ; WX 640 ; N uni0261 ; G 547
+U 610 ; WX 580 ; N uni0262 ; G 548
+U 611 ; WX 599 ; N uni0263 ; G 549
+U 612 ; WX 564 ; N uni0264 ; G 550
+U 613 ; WX 644 ; N uni0265 ; G 551
+U 614 ; WX 644 ; N uni0266 ; G 552
+U 615 ; WX 644 ; N uni0267 ; G 553
+U 616 ; WX 320 ; N uni0268 ; G 554
+U 617 ; WX 392 ; N uni0269 ; G 555
+U 618 ; WX 320 ; N uni026A ; G 556
+U 619 ; WX 380 ; N uni026B ; G 557
+U 620 ; WX 454 ; N uni026C ; G 558
+U 621 ; WX 363 ; N uni026D ; G 559
+U 622 ; WX 704 ; N uni026E ; G 560
+U 623 ; WX 948 ; N uni026F ; G 561
+U 624 ; WX 948 ; N uni0270 ; G 562
+U 625 ; WX 948 ; N uni0271 ; G 563
+U 626 ; WX 644 ; N uni0272 ; G 564
+U 627 ; WX 694 ; N uni0273 ; G 565
+U 628 ; WX 646 ; N uni0274 ; G 566
+U 629 ; WX 602 ; N uni0275 ; G 567
+U 630 ; WX 790 ; N uni0276 ; G 568
+U 631 ; WX 821 ; N uni0277 ; G 569
+U 632 ; WX 692 ; N uni0278 ; G 570
+U 633 ; WX 501 ; N uni0279 ; G 571
+U 634 ; WX 501 ; N uni027A ; G 572
+U 635 ; WX 551 ; N uni027B ; G 573
+U 636 ; WX 478 ; N uni027C ; G 574
+U 637 ; WX 478 ; N uni027D ; G 575
+U 638 ; WX 453 ; N uni027E ; G 576
+U 639 ; WX 453 ; N uni027F ; G 577
+U 640 ; WX 581 ; N uni0280 ; G 578
+U 641 ; WX 581 ; N uni0281 ; G 579
+U 642 ; WX 513 ; N uni0282 ; G 580
+U 643 ; WX 271 ; N uni0283 ; G 581
+U 644 ; WX 370 ; N uni0284 ; G 582
+U 645 ; WX 487 ; N uni0285 ; G 583
+U 646 ; WX 324 ; N uni0286 ; G 584
+U 647 ; WX 402 ; N uni0287 ; G 585
+U 648 ; WX 402 ; N uni0288 ; G 586
+U 649 ; WX 644 ; N uni0289 ; G 587
+U 650 ; WX 620 ; N uni028A ; G 588
+U 651 ; WX 608 ; N uni028B ; G 589
+U 652 ; WX 565 ; N uni028C ; G 590
+U 653 ; WX 856 ; N uni028D ; G 591
+U 654 ; WX 565 ; N uni028E ; G 592
+U 655 ; WX 655 ; N uni028F ; G 593
+U 656 ; WX 597 ; N uni0290 ; G 594
+U 657 ; WX 560 ; N uni0291 ; G 595
+U 658 ; WX 564 ; N uni0292 ; G 596
+U 659 ; WX 560 ; N uni0293 ; G 597
+U 660 ; WX 536 ; N uni0294 ; G 598
+U 661 ; WX 536 ; N uni0295 ; G 599
+U 662 ; WX 536 ; N uni0296 ; G 600
+U 663 ; WX 420 ; N uni0297 ; G 601
+U 664 ; WX 820 ; N uni0298 ; G 602
+U 665 ; WX 563 ; N uni0299 ; G 603
+U 666 ; WX 613 ; N uni029A ; G 604
+U 667 ; WX 660 ; N uni029B ; G 605
+U 668 ; WX 667 ; N uni029C ; G 606
+U 669 ; WX 366 ; N uni029D ; G 607
+U 670 ; WX 606 ; N uni029E ; G 608
+U 671 ; WX 543 ; N uni029F ; G 609
+U 672 ; WX 683 ; N uni02A0 ; G 610
+U 673 ; WX 536 ; N uni02A1 ; G 611
+U 674 ; WX 536 ; N uni02A2 ; G 612
+U 675 ; WX 996 ; N uni02A3 ; G 613
+U 676 ; WX 1033 ; N uni02A4 ; G 614
+U 677 ; WX 998 ; N uni02A5 ; G 615
+U 678 ; WX 823 ; N uni02A6 ; G 616
+U 679 ; WX 598 ; N uni02A7 ; G 617
+U 680 ; WX 825 ; N uni02A8 ; G 618
+U 681 ; WX 894 ; N uni02A9 ; G 619
+U 682 ; WX 725 ; N uni02AA ; G 620
+U 683 ; WX 676 ; N uni02AB ; G 621
+U 684 ; WX 598 ; N uni02AC ; G 622
+U 685 ; WX 443 ; N uni02AD ; G 623
+U 686 ; WX 781 ; N uni02AE ; G 624
+U 687 ; WX 767 ; N uni02AF ; G 625
+U 688 ; WX 433 ; N uni02B0 ; G 626
+U 689 ; WX 430 ; N uni02B1 ; G 627
+U 690 ; WX 264 ; N uni02B2 ; G 628
+U 691 ; WX 347 ; N uni02B3 ; G 629
+U 692 ; WX 347 ; N uni02B4 ; G 630
+U 693 ; WX 430 ; N uni02B5 ; G 631
+U 694 ; WX 392 ; N uni02B6 ; G 632
+U 695 ; WX 585 ; N uni02B7 ; G 633
+U 696 ; WX 423 ; N uni02B8 ; G 634
+U 697 ; WX 278 ; N uni02B9 ; G 635
+U 698 ; WX 460 ; N uni02BA ; G 636
+U 699 ; WX 318 ; N uni02BB ; G 637
+U 700 ; WX 318 ; N uni02BC ; G 638
+U 701 ; WX 318 ; N uni02BD ; G 639
+U 702 ; WX 307 ; N uni02BE ; G 640
+U 703 ; WX 307 ; N uni02BF ; G 641
+U 704 ; WX 280 ; N uni02C0 ; G 642
+U 705 ; WX 281 ; N uni02C1 ; G 643
+U 706 ; WX 500 ; N uni02C2 ; G 644
+U 707 ; WX 500 ; N uni02C3 ; G 645
+U 708 ; WX 500 ; N uni02C4 ; G 646
+U 709 ; WX 500 ; N uni02C5 ; G 647
+U 710 ; WX 500 ; N circumflex ; G 648
+U 711 ; WX 500 ; N caron ; G 649
+U 712 ; WX 275 ; N uni02C8 ; G 650
+U 713 ; WX 500 ; N uni02C9 ; G 651
+U 714 ; WX 500 ; N uni02CA ; G 652
+U 715 ; WX 500 ; N uni02CB ; G 653
+U 716 ; WX 275 ; N uni02CC ; G 654
+U 717 ; WX 500 ; N uni02CD ; G 655
+U 720 ; WX 337 ; N uni02D0 ; G 656
+U 721 ; WX 337 ; N uni02D1 ; G 657
+U 722 ; WX 307 ; N uni02D2 ; G 658
+U 723 ; WX 307 ; N uni02D3 ; G 659
+U 726 ; WX 329 ; N uni02D6 ; G 660
+U 727 ; WX 329 ; N uni02D7 ; G 661
+U 728 ; WX 500 ; N breve ; G 662
+U 729 ; WX 500 ; N dotaccent ; G 663
+U 730 ; WX 500 ; N ring ; G 664
+U 731 ; WX 500 ; N ogonek ; G 665
+U 732 ; WX 500 ; N tilde ; G 666
+U 733 ; WX 500 ; N hungarumlaut ; G 667
+U 734 ; WX 417 ; N uni02DE ; G 668
+U 736 ; WX 377 ; N uni02E0 ; G 669
+U 737 ; WX 243 ; N uni02E1 ; G 670
+U 738 ; WX 337 ; N uni02E2 ; G 671
+U 739 ; WX 424 ; N uni02E3 ; G 672
+U 740 ; WX 281 ; N uni02E4 ; G 673
+U 741 ; WX 493 ; N uni02E5 ; G 674
+U 742 ; WX 493 ; N uni02E6 ; G 675
+U 743 ; WX 493 ; N uni02E7 ; G 676
+U 744 ; WX 493 ; N uni02E8 ; G 677
+U 745 ; WX 493 ; N uni02E9 ; G 678
+U 748 ; WX 500 ; N uni02EC ; G 679
+U 750 ; WX 484 ; N uni02EE ; G 680
+U 751 ; WX 500 ; N uni02EF ; G 681
+U 752 ; WX 500 ; N uni02F0 ; G 682
+U 755 ; WX 500 ; N uni02F3 ; G 683
+U 759 ; WX 500 ; N uni02F7 ; G 684
+U 768 ; WX 0 ; N gravecomb ; G 685
+U 769 ; WX 0 ; N acutecomb ; G 686
+U 770 ; WX 0 ; N uni0302 ; G 687
+U 771 ; WX 0 ; N tildecomb ; G 688
+U 772 ; WX 0 ; N uni0304 ; G 689
+U 773 ; WX 0 ; N uni0305 ; G 690
+U 774 ; WX 0 ; N uni0306 ; G 691
+U 775 ; WX 0 ; N uni0307 ; G 692
+U 776 ; WX 0 ; N uni0308 ; G 693
+U 777 ; WX 0 ; N hookabovecomb ; G 694
+U 778 ; WX 0 ; N uni030A ; G 695
+U 779 ; WX 0 ; N uni030B ; G 696
+U 780 ; WX 0 ; N uni030C ; G 697
+U 781 ; WX 0 ; N uni030D ; G 698
+U 782 ; WX 0 ; N uni030E ; G 699
+U 783 ; WX 0 ; N uni030F ; G 700
+U 784 ; WX 0 ; N uni0310 ; G 701
+U 785 ; WX 0 ; N uni0311 ; G 702
+U 786 ; WX 0 ; N uni0312 ; G 703
+U 787 ; WX 0 ; N uni0313 ; G 704
+U 788 ; WX 0 ; N uni0314 ; G 705
+U 789 ; WX 0 ; N uni0315 ; G 706
+U 790 ; WX 0 ; N uni0316 ; G 707
+U 791 ; WX 0 ; N uni0317 ; G 708
+U 792 ; WX 0 ; N uni0318 ; G 709
+U 793 ; WX 0 ; N uni0319 ; G 710
+U 794 ; WX 0 ; N uni031A ; G 711
+U 795 ; WX 0 ; N uni031B ; G 712
+U 796 ; WX 0 ; N uni031C ; G 713
+U 797 ; WX 0 ; N uni031D ; G 714
+U 798 ; WX 0 ; N uni031E ; G 715
+U 799 ; WX 0 ; N uni031F ; G 716
+U 800 ; WX 0 ; N uni0320 ; G 717
+U 801 ; WX 0 ; N uni0321 ; G 718
+U 802 ; WX 0 ; N uni0322 ; G 719
+U 803 ; WX 0 ; N dotbelowcomb ; G 720
+U 804 ; WX 0 ; N uni0324 ; G 721
+U 805 ; WX 0 ; N uni0325 ; G 722
+U 806 ; WX 0 ; N uni0326 ; G 723
+U 807 ; WX 0 ; N uni0327 ; G 724
+U 808 ; WX 0 ; N uni0328 ; G 725
+U 809 ; WX 0 ; N uni0329 ; G 726
+U 810 ; WX 0 ; N uni032A ; G 727
+U 811 ; WX 0 ; N uni032B ; G 728
+U 812 ; WX 0 ; N uni032C ; G 729
+U 813 ; WX 0 ; N uni032D ; G 730
+U 814 ; WX 0 ; N uni032E ; G 731
+U 815 ; WX 0 ; N uni032F ; G 732
+U 816 ; WX 0 ; N uni0330 ; G 733
+U 817 ; WX 0 ; N uni0331 ; G 734
+U 818 ; WX 0 ; N uni0332 ; G 735
+U 819 ; WX 0 ; N uni0333 ; G 736
+U 820 ; WX 0 ; N uni0334 ; G 737
+U 821 ; WX 0 ; N uni0335 ; G 738
+U 822 ; WX 0 ; N uni0336 ; G 739
+U 823 ; WX 0 ; N uni0337 ; G 740
+U 824 ; WX 0 ; N uni0338 ; G 741
+U 825 ; WX 0 ; N uni0339 ; G 742
+U 826 ; WX 0 ; N uni033A ; G 743
+U 827 ; WX 0 ; N uni033B ; G 744
+U 828 ; WX 0 ; N uni033C ; G 745
+U 829 ; WX 0 ; N uni033D ; G 746
+U 830 ; WX 0 ; N uni033E ; G 747
+U 831 ; WX 0 ; N uni033F ; G 748
+U 835 ; WX 0 ; N uni0343 ; G 749
+U 847 ; WX 0 ; N uni034F ; G 750
+U 856 ; WX 0 ; N uni0358 ; G 751
+U 864 ; WX 0 ; N uni0360 ; G 752
+U 865 ; WX 0 ; N uni0361 ; G 753
+U 880 ; WX 740 ; N uni0370 ; G 754
+U 881 ; WX 531 ; N uni0371 ; G 755
+U 882 ; WX 667 ; N uni0372 ; G 756
+U 883 ; WX 553 ; N uni0373 ; G 757
+U 884 ; WX 278 ; N uni0374 ; G 758
+U 885 ; WX 278 ; N uni0375 ; G 759
+U 886 ; WX 875 ; N uni0376 ; G 760
+U 887 ; WX 667 ; N uni0377 ; G 761
+U 890 ; WX 500 ; N uni037A ; G 762
+U 891 ; WX 560 ; N uni037B ; G 763
+U 892 ; WX 560 ; N uni037C ; G 764
+U 893 ; WX 560 ; N uni037D ; G 765
+U 894 ; WX 337 ; N uni037E ; G 766
+U 895 ; WX 401 ; N uni037F ; G 767
+U 900 ; WX 500 ; N tonos ; G 768
+U 901 ; WX 500 ; N dieresistonos ; G 769
+U 902 ; WX 722 ; N Alphatonos ; G 770
+U 903 ; WX 318 ; N anoteleia ; G 771
+U 904 ; WX 900 ; N Epsilontonos ; G 772
+U 905 ; WX 1039 ; N Etatonos ; G 773
+U 906 ; WX 562 ; N Iotatonos ; G 774
+U 908 ; WX 835 ; N Omicrontonos ; G 775
+U 910 ; WX 897 ; N Upsilontonos ; G 776
+U 911 ; WX 853 ; N Omegatonos ; G 777
+U 912 ; WX 392 ; N iotadieresistonos ; G 778
+U 913 ; WX 722 ; N Alpha ; G 779
+U 914 ; WX 735 ; N Beta ; G 780
+U 915 ; WX 694 ; N Gamma ; G 781
+U 916 ; WX 722 ; N uni0394 ; G 782
+U 917 ; WX 730 ; N Epsilon ; G 783
+U 918 ; WX 695 ; N Zeta ; G 784
+U 919 ; WX 872 ; N Eta ; G 785
+U 920 ; WX 820 ; N Theta ; G 786
+U 921 ; WX 395 ; N Iota ; G 787
+U 922 ; WX 747 ; N Kappa ; G 788
+U 923 ; WX 722 ; N Lambda ; G 789
+U 924 ; WX 1024 ; N Mu ; G 790
+U 925 ; WX 875 ; N Nu ; G 791
+U 926 ; WX 704 ; N Xi ; G 792
+U 927 ; WX 820 ; N Omicron ; G 793
+U 928 ; WX 872 ; N Pi ; G 794
+U 929 ; WX 673 ; N Rho ; G 795
+U 931 ; WX 707 ; N Sigma ; G 796
+U 932 ; WX 667 ; N Tau ; G 797
+U 933 ; WX 660 ; N Upsilon ; G 798
+U 934 ; WX 820 ; N Phi ; G 799
+U 935 ; WX 712 ; N Chi ; G 800
+U 936 ; WX 877 ; N Psi ; G 801
+U 937 ; WX 829 ; N Omega ; G 802
+U 938 ; WX 395 ; N Iotadieresis ; G 803
+U 939 ; WX 660 ; N Upsilondieresis ; G 804
+U 940 ; WX 675 ; N alphatonos ; G 805
+U 941 ; WX 518 ; N epsilontonos ; G 806
+U 942 ; WX 599 ; N etatonos ; G 807
+U 943 ; WX 392 ; N iotatonos ; G 808
+U 944 ; WX 608 ; N upsilondieresistonos ; G 809
+U 945 ; WX 675 ; N alpha ; G 810
+U 946 ; WX 578 ; N beta ; G 811
+U 947 ; WX 598 ; N gamma ; G 812
+U 948 ; WX 602 ; N delta ; G 813
+U 949 ; WX 518 ; N epsilon ; G 814
+U 950 ; WX 542 ; N zeta ; G 815
+U 951 ; WX 599 ; N eta ; G 816
+U 952 ; WX 602 ; N theta ; G 817
+U 953 ; WX 392 ; N iota ; G 818
+U 954 ; WX 625 ; N kappa ; G 819
+U 955 ; WX 634 ; N lambda ; G 820
+U 956 ; WX 650 ; N uni03BC ; G 821
+U 957 ; WX 608 ; N nu ; G 822
+U 958 ; WX 551 ; N xi ; G 823
+U 959 ; WX 602 ; N omicron ; G 824
+U 960 ; WX 657 ; N pi ; G 825
+U 961 ; WX 588 ; N rho ; G 826
+U 962 ; WX 560 ; N sigma1 ; G 827
+U 963 ; WX 683 ; N sigma ; G 828
+U 964 ; WX 553 ; N tau ; G 829
+U 965 ; WX 608 ; N upsilon ; G 830
+U 966 ; WX 700 ; N phi ; G 831
+U 967 ; WX 606 ; N chi ; G 832
+U 968 ; WX 784 ; N psi ; G 833
+U 969 ; WX 815 ; N omega ; G 834
+U 970 ; WX 392 ; N iotadieresis ; G 835
+U 971 ; WX 608 ; N upsilondieresis ; G 836
+U 972 ; WX 602 ; N omicrontonos ; G 837
+U 973 ; WX 608 ; N upsilontonos ; G 838
+U 974 ; WX 815 ; N omegatonos ; G 839
+U 975 ; WX 747 ; N uni03CF ; G 840
+U 976 ; WX 583 ; N uni03D0 ; G 841
+U 977 ; WX 715 ; N theta1 ; G 842
+U 978 ; WX 687 ; N Upsilon1 ; G 843
+U 979 ; WX 874 ; N uni03D3 ; G 844
+U 980 ; WX 687 ; N uni03D4 ; G 845
+U 981 ; WX 682 ; N phi1 ; G 846
+U 982 ; WX 815 ; N omega1 ; G 847
+U 983 ; WX 624 ; N uni03D7 ; G 848
+U 984 ; WX 820 ; N uni03D8 ; G 849
+U 985 ; WX 602 ; N uni03D9 ; G 850
+U 986 ; WX 765 ; N uni03DA ; G 851
+U 987 ; WX 560 ; N uni03DB ; G 852
+U 988 ; WX 694 ; N uni03DC ; G 853
+U 989 ; WX 463 ; N uni03DD ; G 854
+U 990 ; WX 590 ; N uni03DE ; G 855
+U 991 ; WX 660 ; N uni03DF ; G 856
+U 992 ; WX 782 ; N uni03E0 ; G 857
+U 993 ; WX 577 ; N uni03E1 ; G 858
+U 1008 ; WX 624 ; N uni03F0 ; G 859
+U 1009 ; WX 588 ; N uni03F1 ; G 860
+U 1010 ; WX 560 ; N uni03F2 ; G 861
+U 1011 ; WX 310 ; N uni03F3 ; G 862
+U 1012 ; WX 820 ; N uni03F4 ; G 863
+U 1013 ; WX 560 ; N uni03F5 ; G 864
+U 1014 ; WX 560 ; N uni03F6 ; G 865
+U 1015 ; WX 676 ; N uni03F7 ; G 866
+U 1016 ; WX 640 ; N uni03F8 ; G 867
+U 1017 ; WX 765 ; N uni03F9 ; G 868
+U 1018 ; WX 1024 ; N uni03FA ; G 869
+U 1019 ; WX 708 ; N uni03FB ; G 870
+U 1020 ; WX 588 ; N uni03FC ; G 871
+U 1021 ; WX 765 ; N uni03FD ; G 872
+U 1022 ; WX 765 ; N uni03FE ; G 873
+U 1023 ; WX 765 ; N uni03FF ; G 874
+U 1024 ; WX 730 ; N uni0400 ; G 875
+U 1025 ; WX 730 ; N uni0401 ; G 876
+U 1026 ; WX 799 ; N uni0402 ; G 877
+U 1027 ; WX 662 ; N uni0403 ; G 878
+U 1028 ; WX 765 ; N uni0404 ; G 879
+U 1029 ; WX 685 ; N uni0405 ; G 880
+U 1030 ; WX 395 ; N uni0406 ; G 881
+U 1031 ; WX 395 ; N uni0407 ; G 882
+U 1032 ; WX 401 ; N uni0408 ; G 883
+U 1033 ; WX 1084 ; N uni0409 ; G 884
+U 1034 ; WX 1118 ; N uni040A ; G 885
+U 1035 ; WX 872 ; N uni040B ; G 886
+U 1036 ; WX 774 ; N uni040C ; G 887
+U 1037 ; WX 872 ; N uni040D ; G 888
+U 1038 ; WX 723 ; N uni040E ; G 889
+U 1039 ; WX 872 ; N uni040F ; G 890
+U 1040 ; WX 757 ; N uni0410 ; G 891
+U 1041 ; WX 735 ; N uni0411 ; G 892
+U 1042 ; WX 735 ; N uni0412 ; G 893
+U 1043 ; WX 662 ; N uni0413 ; G 894
+U 1044 ; WX 813 ; N uni0414 ; G 895
+U 1045 ; WX 730 ; N uni0415 ; G 896
+U 1046 ; WX 1124 ; N uni0416 ; G 897
+U 1047 ; WX 623 ; N uni0417 ; G 898
+U 1048 ; WX 872 ; N uni0418 ; G 899
+U 1049 ; WX 872 ; N uni0419 ; G 900
+U 1050 ; WX 774 ; N uni041A ; G 901
+U 1051 ; WX 834 ; N uni041B ; G 902
+U 1052 ; WX 1024 ; N uni041C ; G 903
+U 1053 ; WX 872 ; N uni041D ; G 904
+U 1054 ; WX 820 ; N uni041E ; G 905
+U 1055 ; WX 872 ; N uni041F ; G 906
+U 1056 ; WX 673 ; N uni0420 ; G 907
+U 1057 ; WX 765 ; N uni0421 ; G 908
+U 1058 ; WX 667 ; N uni0422 ; G 909
+U 1059 ; WX 723 ; N uni0423 ; G 910
+U 1060 ; WX 830 ; N uni0424 ; G 911
+U 1061 ; WX 712 ; N uni0425 ; G 912
+U 1062 ; WX 872 ; N uni0426 ; G 913
+U 1063 ; WX 773 ; N uni0427 ; G 914
+U 1064 ; WX 1141 ; N uni0428 ; G 915
+U 1065 ; WX 1141 ; N uni0429 ; G 916
+U 1066 ; WX 794 ; N uni042A ; G 917
+U 1067 ; WX 984 ; N uni042B ; G 918
+U 1068 ; WX 674 ; N uni042C ; G 919
+U 1069 ; WX 765 ; N uni042D ; G 920
+U 1070 ; WX 1193 ; N uni042E ; G 921
+U 1071 ; WX 808 ; N uni042F ; G 922
+U 1072 ; WX 596 ; N uni0430 ; G 923
+U 1073 ; WX 602 ; N uni0431 ; G 924
+U 1074 ; WX 563 ; N uni0432 ; G 925
+U 1075 ; WX 524 ; N uni0433 ; G 926
+U 1076 ; WX 616 ; N uni0434 ; G 927
+U 1077 ; WX 592 ; N uni0435 ; G 928
+U 1078 ; WX 920 ; N uni0436 ; G 929
+U 1079 ; WX 545 ; N uni0437 ; G 930
+U 1080 ; WX 667 ; N uni0438 ; G 931
+U 1081 ; WX 667 ; N uni0439 ; G 932
+U 1082 ; WX 625 ; N uni043A ; G 933
+U 1083 ; WX 635 ; N uni043B ; G 934
+U 1084 ; WX 778 ; N uni043C ; G 935
+U 1085 ; WX 667 ; N uni043D ; G 936
+U 1086 ; WX 602 ; N uni043E ; G 937
+U 1087 ; WX 667 ; N uni043F ; G 938
+U 1088 ; WX 640 ; N uni0440 ; G 939
+U 1089 ; WX 560 ; N uni0441 ; G 940
+U 1090 ; WX 553 ; N uni0442 ; G 941
+U 1091 ; WX 588 ; N uni0443 ; G 942
+U 1092 ; WX 783 ; N uni0444 ; G 943
+U 1093 ; WX 564 ; N uni0445 ; G 944
+U 1094 ; WX 643 ; N uni0446 ; G 945
+U 1095 ; WX 661 ; N uni0447 ; G 946
+U 1096 ; WX 930 ; N uni0448 ; G 947
+U 1097 ; WX 930 ; N uni0449 ; G 948
+U 1098 ; WX 636 ; N uni044A ; G 949
+U 1099 ; WX 796 ; N uni044B ; G 950
+U 1100 ; WX 544 ; N uni044C ; G 951
+U 1101 ; WX 560 ; N uni044D ; G 952
+U 1102 ; WX 871 ; N uni044E ; G 953
+U 1103 ; WX 631 ; N uni044F ; G 954
+U 1104 ; WX 592 ; N uni0450 ; G 955
+U 1105 ; WX 592 ; N uni0451 ; G 956
+U 1106 ; WX 624 ; N uni0452 ; G 957
+U 1107 ; WX 524 ; N uni0453 ; G 958
+U 1108 ; WX 560 ; N uni0454 ; G 959
+U 1109 ; WX 513 ; N uni0455 ; G 960
+U 1110 ; WX 320 ; N uni0456 ; G 961
+U 1111 ; WX 320 ; N uni0457 ; G 962
+U 1112 ; WX 310 ; N uni0458 ; G 963
+U 1113 ; WX 843 ; N uni0459 ; G 964
+U 1114 ; WX 860 ; N uni045A ; G 965
+U 1115 ; WX 644 ; N uni045B ; G 966
+U 1116 ; WX 625 ; N uni045C ; G 967
+U 1117 ; WX 667 ; N uni045D ; G 968
+U 1118 ; WX 588 ; N uni045E ; G 969
+U 1119 ; WX 656 ; N uni045F ; G 970
+U 1122 ; WX 762 ; N uni0462 ; G 971
+U 1123 ; WX 603 ; N uni0463 ; G 972
+U 1124 ; WX 1129 ; N uni0464 ; G 973
+U 1125 ; WX 834 ; N uni0465 ; G 974
+U 1130 ; WX 1124 ; N uni046A ; G 975
+U 1131 ; WX 920 ; N uni046B ; G 976
+U 1132 ; WX 1359 ; N uni046C ; G 977
+U 1133 ; WX 1113 ; N uni046D ; G 978
+U 1136 ; WX 944 ; N uni0470 ; G 979
+U 1137 ; WX 902 ; N uni0471 ; G 980
+U 1138 ; WX 820 ; N uni0472 ; G 981
+U 1139 ; WX 552 ; N uni0473 ; G 982
+U 1140 ; WX 859 ; N uni0474 ; G 983
+U 1141 ; WX 678 ; N uni0475 ; G 984
+U 1142 ; WX 859 ; N uni0476 ; G 985
+U 1143 ; WX 678 ; N uni0477 ; G 986
+U 1164 ; WX 707 ; N uni048C ; G 987
+U 1165 ; WX 544 ; N uni048D ; G 988
+U 1168 ; WX 672 ; N uni0490 ; G 989
+U 1169 ; WX 529 ; N uni0491 ; G 990
+U 1170 ; WX 662 ; N uni0492 ; G 991
+U 1171 ; WX 523 ; N uni0493 ; G 992
+U 1172 ; WX 728 ; N uni0494 ; G 993
+U 1173 ; WX 614 ; N uni0495 ; G 994
+U 1174 ; WX 1124 ; N uni0496 ; G 995
+U 1175 ; WX 920 ; N uni0497 ; G 996
+U 1176 ; WX 636 ; N uni0498 ; G 997
+U 1177 ; WX 537 ; N uni0499 ; G 998
+U 1178 ; WX 774 ; N uni049A ; G 999
+U 1179 ; WX 606 ; N uni049B ; G 1000
+U 1182 ; WX 774 ; N uni049E ; G 1001
+U 1183 ; WX 625 ; N uni049F ; G 1002
+U 1184 ; WX 891 ; N uni04A0 ; G 1003
+U 1185 ; WX 717 ; N uni04A1 ; G 1004
+U 1186 ; WX 872 ; N uni04A2 ; G 1005
+U 1187 ; WX 641 ; N uni04A3 ; G 1006
+U 1188 ; WX 1139 ; N uni04A4 ; G 1007
+U 1189 ; WX 852 ; N uni04A5 ; G 1008
+U 1190 ; WX 1205 ; N uni04A6 ; G 1009
+U 1191 ; WX 941 ; N uni04A7 ; G 1010
+U 1194 ; WX 765 ; N uni04AA ; G 1011
+U 1195 ; WX 560 ; N uni04AB ; G 1012
+U 1196 ; WX 667 ; N uni04AC ; G 1013
+U 1197 ; WX 553 ; N uni04AD ; G 1014
+U 1198 ; WX 660 ; N uni04AE ; G 1015
+U 1199 ; WX 565 ; N uni04AF ; G 1016
+U 1200 ; WX 660 ; N uni04B0 ; G 1017
+U 1201 ; WX 565 ; N uni04B1 ; G 1018
+U 1202 ; WX 712 ; N uni04B2 ; G 1019
+U 1203 ; WX 564 ; N uni04B3 ; G 1020
+U 1204 ; WX 952 ; N uni04B4 ; G 1021
+U 1205 ; WX 732 ; N uni04B5 ; G 1022
+U 1206 ; WX 749 ; N uni04B6 ; G 1023
+U 1207 ; WX 690 ; N uni04B7 ; G 1024
+U 1210 ; WX 749 ; N uni04BA ; G 1025
+U 1211 ; WX 644 ; N uni04BB ; G 1026
+U 1216 ; WX 395 ; N uni04C0 ; G 1027
+U 1217 ; WX 1124 ; N uni04C1 ; G 1028
+U 1218 ; WX 920 ; N uni04C2 ; G 1029
+U 1219 ; WX 747 ; N uni04C3 ; G 1030
+U 1220 ; WX 606 ; N uni04C4 ; G 1031
+U 1223 ; WX 872 ; N uni04C7 ; G 1032
+U 1224 ; WX 667 ; N uni04C8 ; G 1033
+U 1227 ; WX 749 ; N uni04CB ; G 1034
+U 1228 ; WX 667 ; N uni04CC ; G 1035
+U 1231 ; WX 320 ; N uni04CF ; G 1036
+U 1232 ; WX 757 ; N uni04D0 ; G 1037
+U 1233 ; WX 596 ; N uni04D1 ; G 1038
+U 1234 ; WX 757 ; N uni04D2 ; G 1039
+U 1235 ; WX 596 ; N uni04D3 ; G 1040
+U 1236 ; WX 1001 ; N uni04D4 ; G 1041
+U 1237 ; WX 940 ; N uni04D5 ; G 1042
+U 1238 ; WX 730 ; N uni04D6 ; G 1043
+U 1239 ; WX 592 ; N uni04D7 ; G 1044
+U 1240 ; WX 820 ; N uni04D8 ; G 1045
+U 1241 ; WX 592 ; N uni04D9 ; G 1046
+U 1242 ; WX 820 ; N uni04DA ; G 1047
+U 1243 ; WX 592 ; N uni04DB ; G 1048
+U 1244 ; WX 1124 ; N uni04DC ; G 1049
+U 1245 ; WX 920 ; N uni04DD ; G 1050
+U 1246 ; WX 623 ; N uni04DE ; G 1051
+U 1247 ; WX 545 ; N uni04DF ; G 1052
+U 1248 ; WX 564 ; N uni04E0 ; G 1053
+U 1249 ; WX 564 ; N uni04E1 ; G 1054
+U 1250 ; WX 872 ; N uni04E2 ; G 1055
+U 1251 ; WX 667 ; N uni04E3 ; G 1056
+U 1252 ; WX 872 ; N uni04E4 ; G 1057
+U 1253 ; WX 667 ; N uni04E5 ; G 1058
+U 1254 ; WX 820 ; N uni04E6 ; G 1059
+U 1255 ; WX 602 ; N uni04E7 ; G 1060
+U 1256 ; WX 820 ; N uni04E8 ; G 1061
+U 1257 ; WX 602 ; N uni04E9 ; G 1062
+U 1258 ; WX 820 ; N uni04EA ; G 1063
+U 1259 ; WX 602 ; N uni04EB ; G 1064
+U 1260 ; WX 765 ; N uni04EC ; G 1065
+U 1261 ; WX 560 ; N uni04ED ; G 1066
+U 1262 ; WX 723 ; N uni04EE ; G 1067
+U 1263 ; WX 588 ; N uni04EF ; G 1068
+U 1264 ; WX 723 ; N uni04F0 ; G 1069
+U 1265 ; WX 588 ; N uni04F1 ; G 1070
+U 1266 ; WX 723 ; N uni04F2 ; G 1071
+U 1267 ; WX 588 ; N uni04F3 ; G 1072
+U 1268 ; WX 773 ; N uni04F4 ; G 1073
+U 1269 ; WX 661 ; N uni04F5 ; G 1074
+U 1270 ; WX 662 ; N uni04F6 ; G 1075
+U 1271 ; WX 524 ; N uni04F7 ; G 1076
+U 1272 ; WX 984 ; N uni04F8 ; G 1077
+U 1273 ; WX 796 ; N uni04F9 ; G 1078
+U 1296 ; WX 623 ; N uni0510 ; G 1079
+U 1297 ; WX 545 ; N uni0511 ; G 1080
+U 1298 ; WX 834 ; N uni0512 ; G 1081
+U 1299 ; WX 635 ; N uni0513 ; G 1082
+U 1300 ; WX 1198 ; N uni0514 ; G 1083
+U 1301 ; WX 919 ; N uni0515 ; G 1084
+U 1306 ; WX 820 ; N uni051A ; G 1085
+U 1307 ; WX 640 ; N uni051B ; G 1086
+U 1308 ; WX 1028 ; N uni051C ; G 1087
+U 1309 ; WX 856 ; N uni051D ; G 1088
+U 1329 ; WX 810 ; N uni0531 ; G 1089
+U 1330 ; WX 811 ; N uni0532 ; G 1090
+U 1331 ; WX 826 ; N uni0533 ; G 1091
+U 1332 ; WX 847 ; N uni0534 ; G 1092
+U 1333 ; WX 806 ; N uni0535 ; G 1093
+U 1334 ; WX 826 ; N uni0536 ; G 1094
+U 1335 ; WX 761 ; N uni0537 ; G 1095
+U 1336 ; WX 811 ; N uni0538 ; G 1096
+U 1337 ; WX 968 ; N uni0539 ; G 1097
+U 1338 ; WX 816 ; N uni053A ; G 1098
+U 1339 ; WX 772 ; N uni053B ; G 1099
+U 1340 ; WX 682 ; N uni053C ; G 1100
+U 1341 ; WX 1097 ; N uni053D ; G 1101
+U 1342 ; WX 845 ; N uni053E ; G 1102
+U 1343 ; WX 804 ; N uni053F ; G 1103
+U 1344 ; WX 719 ; N uni0540 ; G 1104
+U 1345 ; WX 810 ; N uni0541 ; G 1105
+U 1346 ; WX 833 ; N uni0542 ; G 1106
+U 1347 ; WX 843 ; N uni0543 ; G 1107
+U 1348 ; WX 897 ; N uni0544 ; G 1108
+U 1349 ; WX 763 ; N uni0545 ; G 1109
+U 1350 ; WX 794 ; N uni0546 ; G 1110
+U 1351 ; WX 754 ; N uni0547 ; G 1111
+U 1352 ; WX 799 ; N uni0548 ; G 1112
+U 1353 ; WX 797 ; N uni0549 ; G 1113
+U 1354 ; WX 875 ; N uni054A ; G 1114
+U 1355 ; WX 830 ; N uni054B ; G 1115
+U 1356 ; WX 884 ; N uni054C ; G 1116
+U 1357 ; WX 799 ; N uni054D ; G 1117
+U 1358 ; WX 802 ; N uni054E ; G 1118
+U 1359 ; WX 731 ; N uni054F ; G 1119
+U 1360 ; WX 774 ; N uni0550 ; G 1120
+U 1361 ; WX 749 ; N uni0551 ; G 1121
+U 1362 ; WX 633 ; N uni0552 ; G 1122
+U 1363 ; WX 845 ; N uni0553 ; G 1123
+U 1364 ; WX 843 ; N uni0554 ; G 1124
+U 1365 ; WX 835 ; N uni0555 ; G 1125
+U 1366 ; WX 821 ; N uni0556 ; G 1126
+U 1369 ; WX 307 ; N uni0559 ; G 1127
+U 1370 ; WX 264 ; N uni055A ; G 1128
+U 1371 ; WX 229 ; N uni055B ; G 1129
+U 1372 ; WX 391 ; N uni055C ; G 1130
+U 1373 ; WX 364 ; N uni055D ; G 1131
+U 1374 ; WX 386 ; N uni055E ; G 1132
+U 1375 ; WX 500 ; N uni055F ; G 1133
+U 1377 ; WX 949 ; N uni0561 ; G 1134
+U 1378 ; WX 618 ; N uni0562 ; G 1135
+U 1379 ; WX 695 ; N uni0563 ; G 1136
+U 1380 ; WX 695 ; N uni0564 ; G 1137
+U 1381 ; WX 628 ; N uni0565 ; G 1138
+U 1382 ; WX 688 ; N uni0566 ; G 1139
+U 1383 ; WX 510 ; N uni0567 ; G 1140
+U 1384 ; WX 636 ; N uni0568 ; G 1141
+U 1385 ; WX 791 ; N uni0569 ; G 1142
+U 1386 ; WX 671 ; N uni056A ; G 1143
+U 1387 ; WX 635 ; N uni056B ; G 1144
+U 1388 ; WX 305 ; N uni056C ; G 1145
+U 1389 ; WX 973 ; N uni056D ; G 1146
+U 1390 ; WX 614 ; N uni056E ; G 1147
+U 1391 ; WX 628 ; N uni056F ; G 1148
+U 1392 ; WX 636 ; N uni0570 ; G 1149
+U 1393 ; WX 630 ; N uni0571 ; G 1150
+U 1394 ; WX 636 ; N uni0572 ; G 1151
+U 1395 ; WX 654 ; N uni0573 ; G 1152
+U 1396 ; WX 644 ; N uni0574 ; G 1153
+U 1397 ; WX 309 ; N uni0575 ; G 1154
+U 1398 ; WX 636 ; N uni0576 ; G 1155
+U 1399 ; WX 461 ; N uni0577 ; G 1156
+U 1400 ; WX 649 ; N uni0578 ; G 1157
+U 1401 ; WX 365 ; N uni0579 ; G 1158
+U 1402 ; WX 940 ; N uni057A ; G 1159
+U 1403 ; WX 562 ; N uni057B ; G 1160
+U 1404 ; WX 657 ; N uni057C ; G 1161
+U 1405 ; WX 644 ; N uni057D ; G 1162
+U 1406 ; WX 630 ; N uni057E ; G 1163
+U 1407 ; WX 930 ; N uni057F ; G 1164
+U 1408 ; WX 644 ; N uni0580 ; G 1165
+U 1409 ; WX 643 ; N uni0581 ; G 1166
+U 1410 ; WX 483 ; N uni0582 ; G 1167
+U 1411 ; WX 930 ; N uni0583 ; G 1168
+U 1412 ; WX 636 ; N uni0584 ; G 1169
+U 1413 ; WX 609 ; N uni0585 ; G 1170
+U 1414 ; WX 809 ; N uni0586 ; G 1171
+U 1415 ; WX 789 ; N uni0587 ; G 1172
+U 1417 ; WX 340 ; N uni0589 ; G 1173
+U 1418 ; WX 334 ; N uni058A ; G 1174
+U 3647 ; WX 636 ; N uni0E3F ; G 1175
+U 4256 ; WX 723 ; N uni10A0 ; G 1176
+U 4257 ; WX 850 ; N uni10A1 ; G 1177
+U 4258 ; WX 828 ; N uni10A2 ; G 1178
+U 4259 ; WX 859 ; N uni10A3 ; G 1179
+U 4260 ; WX 733 ; N uni10A4 ; G 1180
+U 4261 ; WX 981 ; N uni10A5 ; G 1181
+U 4262 ; WX 916 ; N uni10A6 ; G 1182
+U 4263 ; WX 1101 ; N uni10A7 ; G 1183
+U 4264 ; WX 566 ; N uni10A8 ; G 1184
+U 4265 ; WX 750 ; N uni10A9 ; G 1185
+U 4266 ; WX 962 ; N uni10AA ; G 1186
+U 4267 ; WX 941 ; N uni10AB ; G 1187
+U 4268 ; WX 743 ; N uni10AC ; G 1188
+U 4269 ; WX 1075 ; N uni10AD ; G 1189
+U 4270 ; WX 896 ; N uni10AE ; G 1190
+U 4271 ; WX 829 ; N uni10AF ; G 1191
+U 4272 ; WX 1040 ; N uni10B0 ; G 1192
+U 4273 ; WX 733 ; N uni10B1 ; G 1193
+U 4274 ; WX 669 ; N uni10B2 ; G 1194
+U 4275 ; WX 1015 ; N uni10B3 ; G 1195
+U 4276 ; WX 937 ; N uni10B4 ; G 1196
+U 4277 ; WX 1020 ; N uni10B5 ; G 1197
+U 4278 ; WX 731 ; N uni10B6 ; G 1198
+U 4279 ; WX 733 ; N uni10B7 ; G 1199
+U 4280 ; WX 732 ; N uni10B8 ; G 1200
+U 4281 ; WX 733 ; N uni10B9 ; G 1201
+U 4282 ; WX 879 ; N uni10BA ; G 1202
+U 4283 ; WX 937 ; N uni10BB ; G 1203
+U 4284 ; WX 714 ; N uni10BC ; G 1204
+U 4285 ; WX 755 ; N uni10BD ; G 1205
+U 4286 ; WX 733 ; N uni10BE ; G 1206
+U 4287 ; WX 958 ; N uni10BF ; G 1207
+U 4288 ; WX 1000 ; N uni10C0 ; G 1208
+U 4289 ; WX 702 ; N uni10C1 ; G 1209
+U 4290 ; WX 864 ; N uni10C2 ; G 1210
+U 4291 ; WX 734 ; N uni10C3 ; G 1211
+U 4292 ; WX 837 ; N uni10C4 ; G 1212
+U 4293 ; WX 951 ; N uni10C5 ; G 1213
+U 4304 ; WX 541 ; N uni10D0 ; G 1214
+U 4305 ; WX 571 ; N uni10D1 ; G 1215
+U 4306 ; WX 589 ; N uni10D2 ; G 1216
+U 4307 ; WX 833 ; N uni10D3 ; G 1217
+U 4308 ; WX 561 ; N uni10D4 ; G 1218
+U 4309 ; WX 557 ; N uni10D5 ; G 1219
+U 4310 ; WX 618 ; N uni10D6 ; G 1220
+U 4311 ; WX 861 ; N uni10D7 ; G 1221
+U 4312 ; WX 560 ; N uni10D8 ; G 1222
+U 4313 ; WX 546 ; N uni10D9 ; G 1223
+U 4314 ; WX 1066 ; N uni10DA ; G 1224
+U 4315 ; WX 586 ; N uni10DB ; G 1225
+U 4316 ; WX 586 ; N uni10DC ; G 1226
+U 4317 ; WX 825 ; N uni10DD ; G 1227
+U 4318 ; WX 570 ; N uni10DE ; G 1228
+U 4319 ; WX 581 ; N uni10DF ; G 1229
+U 4320 ; WX 824 ; N uni10E0 ; G 1230
+U 4321 ; WX 607 ; N uni10E1 ; G 1231
+U 4322 ; WX 748 ; N uni10E2 ; G 1232
+U 4323 ; WX 698 ; N uni10E3 ; G 1233
+U 4324 ; WX 815 ; N uni10E4 ; G 1234
+U 4325 ; WX 585 ; N uni10E5 ; G 1235
+U 4326 ; WX 858 ; N uni10E6 ; G 1236
+U 4327 ; WX 568 ; N uni10E7 ; G 1237
+U 4328 ; WX 594 ; N uni10E8 ; G 1238
+U 4329 ; WX 586 ; N uni10E9 ; G 1239
+U 4330 ; WX 675 ; N uni10EA ; G 1240
+U 4331 ; WX 587 ; N uni10EB ; G 1241
+U 4332 ; WX 582 ; N uni10EC ; G 1242
+U 4333 ; WX 576 ; N uni10ED ; G 1243
+U 4334 ; WX 612 ; N uni10EE ; G 1244
+U 4335 ; WX 683 ; N uni10EF ; G 1245
+U 4336 ; WX 572 ; N uni10F0 ; G 1246
+U 4337 ; WX 603 ; N uni10F1 ; G 1247
+U 4338 ; WX 571 ; N uni10F2 ; G 1248
+U 4339 ; WX 572 ; N uni10F3 ; G 1249
+U 4340 ; WX 570 ; N uni10F4 ; G 1250
+U 4341 ; WX 649 ; N uni10F5 ; G 1251
+U 4342 ; WX 886 ; N uni10F6 ; G 1252
+U 4343 ; WX 626 ; N uni10F7 ; G 1253
+U 4344 ; WX 582 ; N uni10F8 ; G 1254
+U 4345 ; WX 619 ; N uni10F9 ; G 1255
+U 4346 ; WX 571 ; N uni10FA ; G 1256
+U 4347 ; WX 437 ; N uni10FB ; G 1257
+U 4348 ; WX 354 ; N uni10FC ; G 1258
+U 7424 ; WX 565 ; N uni1D00 ; G 1259
+U 7425 ; WX 774 ; N uni1D01 ; G 1260
+U 7426 ; WX 940 ; N uni1D02 ; G 1261
+U 7427 ; WX 563 ; N uni1D03 ; G 1262
+U 7428 ; WX 560 ; N uni1D04 ; G 1263
+U 7429 ; WX 585 ; N uni1D05 ; G 1264
+U 7430 ; WX 585 ; N uni1D06 ; G 1265
+U 7431 ; WX 553 ; N uni1D07 ; G 1266
+U 7432 ; WX 509 ; N uni1D08 ; G 1267
+U 7433 ; WX 320 ; N uni1D09 ; G 1268
+U 7434 ; WX 499 ; N uni1D0A ; G 1269
+U 7435 ; WX 625 ; N uni1D0B ; G 1270
+U 7436 ; WX 543 ; N uni1D0C ; G 1271
+U 7437 ; WX 778 ; N uni1D0D ; G 1272
+U 7438 ; WX 667 ; N uni1D0E ; G 1273
+U 7439 ; WX 602 ; N uni1D0F ; G 1274
+U 7440 ; WX 560 ; N uni1D10 ; G 1275
+U 7441 ; WX 647 ; N uni1D11 ; G 1276
+U 7442 ; WX 647 ; N uni1D12 ; G 1277
+U 7443 ; WX 647 ; N uni1D13 ; G 1278
+U 7444 ; WX 989 ; N uni1D14 ; G 1279
+U 7445 ; WX 512 ; N uni1D15 ; G 1280
+U 7446 ; WX 602 ; N uni1D16 ; G 1281
+U 7447 ; WX 602 ; N uni1D17 ; G 1282
+U 7448 ; WX 553 ; N uni1D18 ; G 1283
+U 7449 ; WX 594 ; N uni1D19 ; G 1284
+U 7450 ; WX 594 ; N uni1D1A ; G 1285
+U 7451 ; WX 553 ; N uni1D1B ; G 1286
+U 7452 ; WX 585 ; N uni1D1C ; G 1287
+U 7453 ; WX 664 ; N uni1D1D ; G 1288
+U 7454 ; WX 923 ; N uni1D1E ; G 1289
+U 7455 ; WX 655 ; N uni1D1F ; G 1290
+U 7456 ; WX 565 ; N uni1D20 ; G 1291
+U 7457 ; WX 856 ; N uni1D21 ; G 1292
+U 7458 ; WX 527 ; N uni1D22 ; G 1293
+U 7459 ; WX 527 ; N uni1D23 ; G 1294
+U 7460 ; WX 531 ; N uni1D24 ; G 1295
+U 7461 ; WX 743 ; N uni1D25 ; G 1296
+U 7462 ; WX 524 ; N uni1D26 ; G 1297
+U 7463 ; WX 565 ; N uni1D27 ; G 1298
+U 7464 ; WX 657 ; N uni1D28 ; G 1299
+U 7465 ; WX 553 ; N uni1D29 ; G 1300
+U 7466 ; WX 703 ; N uni1D2A ; G 1301
+U 7467 ; WX 635 ; N uni1D2B ; G 1302
+U 7468 ; WX 455 ; N uni1D2C ; G 1303
+U 7469 ; WX 630 ; N uni1D2D ; G 1304
+U 7470 ; WX 463 ; N uni1D2E ; G 1305
+U 7471 ; WX 463 ; N uni1D2F ; G 1306
+U 7472 ; WX 505 ; N uni1D30 ; G 1307
+U 7473 ; WX 459 ; N uni1D31 ; G 1308
+U 7474 ; WX 459 ; N uni1D32 ; G 1309
+U 7475 ; WX 503 ; N uni1D33 ; G 1310
+U 7476 ; WX 549 ; N uni1D34 ; G 1311
+U 7477 ; WX 249 ; N uni1D35 ; G 1312
+U 7478 ; WX 252 ; N uni1D36 ; G 1313
+U 7479 ; WX 470 ; N uni1D37 ; G 1314
+U 7480 ; WX 418 ; N uni1D38 ; G 1315
+U 7481 ; WX 645 ; N uni1D39 ; G 1316
+U 7482 ; WX 551 ; N uni1D3A ; G 1317
+U 7483 ; WX 551 ; N uni1D3B ; G 1318
+U 7484 ; WX 516 ; N uni1D3C ; G 1319
+U 7485 ; WX 369 ; N uni1D3D ; G 1320
+U 7486 ; WX 424 ; N uni1D3E ; G 1321
+U 7487 ; WX 474 ; N uni1D3F ; G 1322
+U 7488 ; WX 420 ; N uni1D40 ; G 1323
+U 7489 ; WX 531 ; N uni1D41 ; G 1324
+U 7490 ; WX 647 ; N uni1D42 ; G 1325
+U 7491 ; WX 386 ; N uni1D43 ; G 1326
+U 7492 ; WX 386 ; N uni1D44 ; G 1327
+U 7493 ; WX 400 ; N uni1D45 ; G 1328
+U 7494 ; WX 618 ; N uni1D46 ; G 1329
+U 7495 ; WX 400 ; N uni1D47 ; G 1330
+U 7496 ; WX 400 ; N uni1D48 ; G 1331
+U 7497 ; WX 387 ; N uni1D49 ; G 1332
+U 7498 ; WX 387 ; N uni1D4A ; G 1333
+U 7499 ; WX 340 ; N uni1D4B ; G 1334
+U 7500 ; WX 340 ; N uni1D4C ; G 1335
+U 7501 ; WX 400 ; N uni1D4D ; G 1336
+U 7502 ; WX 175 ; N uni1D4E ; G 1337
+U 7503 ; WX 365 ; N uni1D4F ; G 1338
+U 7504 ; WX 613 ; N uni1D50 ; G 1339
+U 7505 ; WX 399 ; N uni1D51 ; G 1340
+U 7506 ; WX 385 ; N uni1D52 ; G 1341
+U 7507 ; WX 346 ; N uni1D53 ; G 1342
+U 7508 ; WX 385 ; N uni1D54 ; G 1343
+U 7509 ; WX 385 ; N uni1D55 ; G 1344
+U 7510 ; WX 400 ; N uni1D56 ; G 1345
+U 7511 ; WX 247 ; N uni1D57 ; G 1346
+U 7512 ; WX 399 ; N uni1D58 ; G 1347
+U 7513 ; WX 418 ; N uni1D59 ; G 1348
+U 7514 ; WX 613 ; N uni1D5A ; G 1349
+U 7515 ; WX 373 ; N uni1D5B ; G 1350
+U 7516 ; WX 468 ; N uni1D5C ; G 1351
+U 7517 ; WX 364 ; N uni1D5D ; G 1352
+U 7518 ; WX 376 ; N uni1D5E ; G 1353
+U 7519 ; WX 379 ; N uni1D5F ; G 1354
+U 7520 ; WX 441 ; N uni1D60 ; G 1355
+U 7521 ; WX 381 ; N uni1D61 ; G 1356
+U 7522 ; WX 201 ; N uni1D62 ; G 1357
+U 7523 ; WX 347 ; N uni1D63 ; G 1358
+U 7524 ; WX 399 ; N uni1D64 ; G 1359
+U 7525 ; WX 373 ; N uni1D65 ; G 1360
+U 7526 ; WX 364 ; N uni1D66 ; G 1361
+U 7527 ; WX 376 ; N uni1D67 ; G 1362
+U 7528 ; WX 370 ; N uni1D68 ; G 1363
+U 7529 ; WX 441 ; N uni1D69 ; G 1364
+U 7530 ; WX 381 ; N uni1D6A ; G 1365
+U 7531 ; WX 974 ; N uni1D6B ; G 1366
+U 7543 ; WX 640 ; N uni1D77 ; G 1367
+U 7544 ; WX 549 ; N uni1D78 ; G 1368
+U 7547 ; WX 320 ; N uni1D7B ; G 1369
+U 7548 ; WX 392 ; N uni1D7C ; G 1370
+U 7549 ; WX 640 ; N uni1D7D ; G 1371
+U 7550 ; WX 585 ; N uni1D7E ; G 1372
+U 7551 ; WX 620 ; N uni1D7F ; G 1373
+U 7557 ; WX 320 ; N uni1D85 ; G 1374
+U 7579 ; WX 400 ; N uni1D9B ; G 1375
+U 7580 ; WX 346 ; N uni1D9C ; G 1376
+U 7581 ; WX 346 ; N uni1D9D ; G 1377
+U 7582 ; WX 385 ; N uni1D9E ; G 1378
+U 7583 ; WX 340 ; N uni1D9F ; G 1379
+U 7584 ; WX 222 ; N uni1DA0 ; G 1380
+U 7585 ; WX 229 ; N uni1DA1 ; G 1381
+U 7586 ; WX 400 ; N uni1DA2 ; G 1382
+U 7587 ; WX 399 ; N uni1DA3 ; G 1383
+U 7588 ; WX 234 ; N uni1DA4 ; G 1384
+U 7589 ; WX 244 ; N uni1DA5 ; G 1385
+U 7590 ; WX 234 ; N uni1DA6 ; G 1386
+U 7591 ; WX 234 ; N uni1DA7 ; G 1387
+U 7592 ; WX 230 ; N uni1DA8 ; G 1388
+U 7593 ; WX 175 ; N uni1DA9 ; G 1389
+U 7594 ; WX 175 ; N uni1DAA ; G 1390
+U 7595 ; WX 367 ; N uni1DAB ; G 1391
+U 7596 ; WX 613 ; N uni1DAC ; G 1392
+U 7597 ; WX 613 ; N uni1DAD ; G 1393
+U 7598 ; WX 407 ; N uni1DAE ; G 1394
+U 7599 ; WX 404 ; N uni1DAF ; G 1395
+U 7600 ; WX 399 ; N uni1DB0 ; G 1396
+U 7601 ; WX 385 ; N uni1DB1 ; G 1397
+U 7602 ; WX 385 ; N uni1DB2 ; G 1398
+U 7603 ; WX 328 ; N uni1DB3 ; G 1399
+U 7604 ; WX 211 ; N uni1DB4 ; G 1400
+U 7605 ; WX 247 ; N uni1DB5 ; G 1401
+U 7606 ; WX 399 ; N uni1DB6 ; G 1402
+U 7607 ; WX 389 ; N uni1DB7 ; G 1403
+U 7608 ; WX 368 ; N uni1DB8 ; G 1404
+U 7609 ; WX 376 ; N uni1DB9 ; G 1405
+U 7610 ; WX 373 ; N uni1DBA ; G 1406
+U 7611 ; WX 331 ; N uni1DBB ; G 1407
+U 7612 ; WX 331 ; N uni1DBC ; G 1408
+U 7613 ; WX 331 ; N uni1DBD ; G 1409
+U 7614 ; WX 364 ; N uni1DBE ; G 1410
+U 7615 ; WX 385 ; N uni1DBF ; G 1411
+U 7620 ; WX 0 ; N uni1DC4 ; G 1412
+U 7621 ; WX 0 ; N uni1DC5 ; G 1413
+U 7622 ; WX 0 ; N uni1DC6 ; G 1414
+U 7623 ; WX 0 ; N uni1DC7 ; G 1415
+U 7624 ; WX 0 ; N uni1DC8 ; G 1416
+U 7625 ; WX 0 ; N uni1DC9 ; G 1417
+U 7680 ; WX 722 ; N uni1E00 ; G 1418
+U 7681 ; WX 596 ; N uni1E01 ; G 1419
+U 7682 ; WX 735 ; N uni1E02 ; G 1420
+U 7683 ; WX 640 ; N uni1E03 ; G 1421
+U 7684 ; WX 735 ; N uni1E04 ; G 1422
+U 7685 ; WX 640 ; N uni1E05 ; G 1423
+U 7686 ; WX 735 ; N uni1E06 ; G 1424
+U 7687 ; WX 640 ; N uni1E07 ; G 1425
+U 7688 ; WX 765 ; N uni1E08 ; G 1426
+U 7689 ; WX 560 ; N uni1E09 ; G 1427
+U 7690 ; WX 802 ; N uni1E0A ; G 1428
+U 7691 ; WX 640 ; N uni1E0B ; G 1429
+U 7692 ; WX 802 ; N uni1E0C ; G 1430
+U 7693 ; WX 640 ; N uni1E0D ; G 1431
+U 7694 ; WX 802 ; N uni1E0E ; G 1432
+U 7695 ; WX 640 ; N uni1E0F ; G 1433
+U 7696 ; WX 802 ; N uni1E10 ; G 1434
+U 7697 ; WX 640 ; N uni1E11 ; G 1435
+U 7698 ; WX 802 ; N uni1E12 ; G 1436
+U 7699 ; WX 640 ; N uni1E13 ; G 1437
+U 7700 ; WX 730 ; N uni1E14 ; G 1438
+U 7701 ; WX 592 ; N uni1E15 ; G 1439
+U 7702 ; WX 730 ; N uni1E16 ; G 1440
+U 7703 ; WX 592 ; N uni1E17 ; G 1441
+U 7704 ; WX 730 ; N uni1E18 ; G 1442
+U 7705 ; WX 592 ; N uni1E19 ; G 1443
+U 7706 ; WX 730 ; N uni1E1A ; G 1444
+U 7707 ; WX 592 ; N uni1E1B ; G 1445
+U 7708 ; WX 730 ; N uni1E1C ; G 1446
+U 7709 ; WX 592 ; N uni1E1D ; G 1447
+U 7710 ; WX 694 ; N uni1E1E ; G 1448
+U 7711 ; WX 370 ; N uni1E1F ; G 1449
+U 7712 ; WX 799 ; N uni1E20 ; G 1450
+U 7713 ; WX 640 ; N uni1E21 ; G 1451
+U 7714 ; WX 872 ; N uni1E22 ; G 1452
+U 7715 ; WX 644 ; N uni1E23 ; G 1453
+U 7716 ; WX 872 ; N uni1E24 ; G 1454
+U 7717 ; WX 644 ; N uni1E25 ; G 1455
+U 7718 ; WX 872 ; N uni1E26 ; G 1456
+U 7719 ; WX 644 ; N uni1E27 ; G 1457
+U 7720 ; WX 872 ; N uni1E28 ; G 1458
+U 7721 ; WX 644 ; N uni1E29 ; G 1459
+U 7722 ; WX 872 ; N uni1E2A ; G 1460
+U 7723 ; WX 644 ; N uni1E2B ; G 1461
+U 7724 ; WX 395 ; N uni1E2C ; G 1462
+U 7725 ; WX 320 ; N uni1E2D ; G 1463
+U 7726 ; WX 395 ; N uni1E2E ; G 1464
+U 7727 ; WX 320 ; N uni1E2F ; G 1465
+U 7728 ; WX 747 ; N uni1E30 ; G 1466
+U 7729 ; WX 606 ; N uni1E31 ; G 1467
+U 7730 ; WX 747 ; N uni1E32 ; G 1468
+U 7731 ; WX 606 ; N uni1E33 ; G 1469
+U 7732 ; WX 747 ; N uni1E34 ; G 1470
+U 7733 ; WX 606 ; N uni1E35 ; G 1471
+U 7734 ; WX 664 ; N uni1E36 ; G 1472
+U 7735 ; WX 320 ; N uni1E37 ; G 1473
+U 7736 ; WX 664 ; N uni1E38 ; G 1474
+U 7737 ; WX 320 ; N uni1E39 ; G 1475
+U 7738 ; WX 664 ; N uni1E3A ; G 1476
+U 7739 ; WX 320 ; N uni1E3B ; G 1477
+U 7740 ; WX 664 ; N uni1E3C ; G 1478
+U 7741 ; WX 320 ; N uni1E3D ; G 1479
+U 7742 ; WX 1024 ; N uni1E3E ; G 1480
+U 7743 ; WX 948 ; N uni1E3F ; G 1481
+U 7744 ; WX 1024 ; N uni1E40 ; G 1482
+U 7745 ; WX 948 ; N uni1E41 ; G 1483
+U 7746 ; WX 1024 ; N uni1E42 ; G 1484
+U 7747 ; WX 948 ; N uni1E43 ; G 1485
+U 7748 ; WX 875 ; N uni1E44 ; G 1486
+U 7749 ; WX 644 ; N uni1E45 ; G 1487
+U 7750 ; WX 875 ; N uni1E46 ; G 1488
+U 7751 ; WX 644 ; N uni1E47 ; G 1489
+U 7752 ; WX 875 ; N uni1E48 ; G 1490
+U 7753 ; WX 644 ; N uni1E49 ; G 1491
+U 7754 ; WX 875 ; N uni1E4A ; G 1492
+U 7755 ; WX 644 ; N uni1E4B ; G 1493
+U 7756 ; WX 820 ; N uni1E4C ; G 1494
+U 7757 ; WX 602 ; N uni1E4D ; G 1495
+U 7758 ; WX 820 ; N uni1E4E ; G 1496
+U 7759 ; WX 602 ; N uni1E4F ; G 1497
+U 7760 ; WX 820 ; N uni1E50 ; G 1498
+U 7761 ; WX 602 ; N uni1E51 ; G 1499
+U 7762 ; WX 820 ; N uni1E52 ; G 1500
+U 7763 ; WX 602 ; N uni1E53 ; G 1501
+U 7764 ; WX 673 ; N uni1E54 ; G 1502
+U 7765 ; WX 640 ; N uni1E55 ; G 1503
+U 7766 ; WX 673 ; N uni1E56 ; G 1504
+U 7767 ; WX 640 ; N uni1E57 ; G 1505
+U 7768 ; WX 753 ; N uni1E58 ; G 1506
+U 7769 ; WX 478 ; N uni1E59 ; G 1507
+U 7770 ; WX 753 ; N uni1E5A ; G 1508
+U 7771 ; WX 478 ; N uni1E5B ; G 1509
+U 7772 ; WX 753 ; N uni1E5C ; G 1510
+U 7773 ; WX 478 ; N uni1E5D ; G 1511
+U 7774 ; WX 753 ; N uni1E5E ; G 1512
+U 7775 ; WX 478 ; N uni1E5F ; G 1513
+U 7776 ; WX 685 ; N uni1E60 ; G 1514
+U 7777 ; WX 513 ; N uni1E61 ; G 1515
+U 7778 ; WX 685 ; N uni1E62 ; G 1516
+U 7779 ; WX 513 ; N uni1E63 ; G 1517
+U 7780 ; WX 685 ; N uni1E64 ; G 1518
+U 7781 ; WX 513 ; N uni1E65 ; G 1519
+U 7782 ; WX 685 ; N uni1E66 ; G 1520
+U 7783 ; WX 521 ; N uni1E67 ; G 1521
+U 7784 ; WX 685 ; N uni1E68 ; G 1522
+U 7785 ; WX 513 ; N uni1E69 ; G 1523
+U 7786 ; WX 667 ; N uni1E6A ; G 1524
+U 7787 ; WX 402 ; N uni1E6B ; G 1525
+U 7788 ; WX 667 ; N uni1E6C ; G 1526
+U 7789 ; WX 402 ; N uni1E6D ; G 1527
+U 7790 ; WX 667 ; N uni1E6E ; G 1528
+U 7791 ; WX 402 ; N uni1E6F ; G 1529
+U 7792 ; WX 667 ; N uni1E70 ; G 1530
+U 7793 ; WX 402 ; N uni1E71 ; G 1531
+U 7794 ; WX 843 ; N uni1E72 ; G 1532
+U 7795 ; WX 644 ; N uni1E73 ; G 1533
+U 7796 ; WX 843 ; N uni1E74 ; G 1534
+U 7797 ; WX 644 ; N uni1E75 ; G 1535
+U 7798 ; WX 843 ; N uni1E76 ; G 1536
+U 7799 ; WX 644 ; N uni1E77 ; G 1537
+U 7800 ; WX 843 ; N uni1E78 ; G 1538
+U 7801 ; WX 644 ; N uni1E79 ; G 1539
+U 7802 ; WX 843 ; N uni1E7A ; G 1540
+U 7803 ; WX 644 ; N uni1E7B ; G 1541
+U 7804 ; WX 722 ; N uni1E7C ; G 1542
+U 7805 ; WX 565 ; N uni1E7D ; G 1543
+U 7806 ; WX 722 ; N uni1E7E ; G 1544
+U 7807 ; WX 565 ; N uni1E7F ; G 1545
+U 7808 ; WX 1028 ; N Wgrave ; G 1546
+U 7809 ; WX 856 ; N wgrave ; G 1547
+U 7810 ; WX 1028 ; N Wacute ; G 1548
+U 7811 ; WX 856 ; N wacute ; G 1549
+U 7812 ; WX 1028 ; N Wdieresis ; G 1550
+U 7813 ; WX 856 ; N wdieresis ; G 1551
+U 7814 ; WX 1028 ; N uni1E86 ; G 1552
+U 7815 ; WX 856 ; N uni1E87 ; G 1553
+U 7816 ; WX 1028 ; N uni1E88 ; G 1554
+U 7817 ; WX 856 ; N uni1E89 ; G 1555
+U 7818 ; WX 712 ; N uni1E8A ; G 1556
+U 7819 ; WX 564 ; N uni1E8B ; G 1557
+U 7820 ; WX 712 ; N uni1E8C ; G 1558
+U 7821 ; WX 564 ; N uni1E8D ; G 1559
+U 7822 ; WX 660 ; N uni1E8E ; G 1560
+U 7823 ; WX 565 ; N uni1E8F ; G 1561
+U 7824 ; WX 695 ; N uni1E90 ; G 1562
+U 7825 ; WX 527 ; N uni1E91 ; G 1563
+U 7826 ; WX 695 ; N uni1E92 ; G 1564
+U 7827 ; WX 527 ; N uni1E93 ; G 1565
+U 7828 ; WX 695 ; N uni1E94 ; G 1566
+U 7829 ; WX 527 ; N uni1E95 ; G 1567
+U 7830 ; WX 644 ; N uni1E96 ; G 1568
+U 7831 ; WX 402 ; N uni1E97 ; G 1569
+U 7832 ; WX 856 ; N uni1E98 ; G 1570
+U 7833 ; WX 565 ; N uni1E99 ; G 1571
+U 7834 ; WX 903 ; N uni1E9A ; G 1572
+U 7835 ; WX 370 ; N uni1E9B ; G 1573
+U 7836 ; WX 370 ; N uni1E9C ; G 1574
+U 7837 ; WX 370 ; N uni1E9D ; G 1575
+U 7838 ; WX 829 ; N uni1E9E ; G 1576
+U 7839 ; WX 602 ; N uni1E9F ; G 1577
+U 7840 ; WX 722 ; N uni1EA0 ; G 1578
+U 7841 ; WX 596 ; N uni1EA1 ; G 1579
+U 7842 ; WX 722 ; N uni1EA2 ; G 1580
+U 7843 ; WX 596 ; N uni1EA3 ; G 1581
+U 7844 ; WX 722 ; N uni1EA4 ; G 1582
+U 7845 ; WX 613 ; N uni1EA5 ; G 1583
+U 7846 ; WX 722 ; N uni1EA6 ; G 1584
+U 7847 ; WX 613 ; N uni1EA7 ; G 1585
+U 7848 ; WX 722 ; N uni1EA8 ; G 1586
+U 7849 ; WX 613 ; N uni1EA9 ; G 1587
+U 7850 ; WX 722 ; N uni1EAA ; G 1588
+U 7851 ; WX 613 ; N uni1EAB ; G 1589
+U 7852 ; WX 722 ; N uni1EAC ; G 1590
+U 7853 ; WX 596 ; N uni1EAD ; G 1591
+U 7854 ; WX 722 ; N uni1EAE ; G 1592
+U 7855 ; WX 596 ; N uni1EAF ; G 1593
+U 7856 ; WX 722 ; N uni1EB0 ; G 1594
+U 7857 ; WX 596 ; N uni1EB1 ; G 1595
+U 7858 ; WX 722 ; N uni1EB2 ; G 1596
+U 7859 ; WX 596 ; N uni1EB3 ; G 1597
+U 7860 ; WX 722 ; N uni1EB4 ; G 1598
+U 7861 ; WX 596 ; N uni1EB5 ; G 1599
+U 7862 ; WX 722 ; N uni1EB6 ; G 1600
+U 7863 ; WX 596 ; N uni1EB7 ; G 1601
+U 7864 ; WX 730 ; N uni1EB8 ; G 1602
+U 7865 ; WX 592 ; N uni1EB9 ; G 1603
+U 7866 ; WX 730 ; N uni1EBA ; G 1604
+U 7867 ; WX 592 ; N uni1EBB ; G 1605
+U 7868 ; WX 730 ; N uni1EBC ; G 1606
+U 7869 ; WX 592 ; N uni1EBD ; G 1607
+U 7870 ; WX 730 ; N uni1ebe ; G 1608
+U 7871 ; WX 615 ; N uni1ebF ; G 1609
+U 7872 ; WX 730 ; N uni1EC0 ; G 1610
+U 7873 ; WX 615 ; N uni1EC1 ; G 1611
+U 7874 ; WX 730 ; N uni1EC2 ; G 1612
+U 7875 ; WX 615 ; N uni1EC3 ; G 1613
+U 7876 ; WX 730 ; N uni1EC4 ; G 1614
+U 7877 ; WX 615 ; N uni1EC5 ; G 1615
+U 7878 ; WX 730 ; N uni1EC6 ; G 1616
+U 7879 ; WX 592 ; N uni1EC7 ; G 1617
+U 7880 ; WX 395 ; N uni1EC8 ; G 1618
+U 7881 ; WX 320 ; N uni1EC9 ; G 1619
+U 7882 ; WX 395 ; N uni1ECA ; G 1620
+U 7883 ; WX 320 ; N uni1ECB ; G 1621
+U 7884 ; WX 820 ; N uni1ECC ; G 1622
+U 7885 ; WX 602 ; N uni1ECD ; G 1623
+U 7886 ; WX 820 ; N uni1ECE ; G 1624
+U 7887 ; WX 602 ; N uni1ECF ; G 1625
+U 7888 ; WX 820 ; N uni1ED0 ; G 1626
+U 7889 ; WX 612 ; N uni1ED1 ; G 1627
+U 7890 ; WX 820 ; N uni1ED2 ; G 1628
+U 7891 ; WX 612 ; N uni1ED3 ; G 1629
+U 7892 ; WX 820 ; N uni1ED4 ; G 1630
+U 7893 ; WX 612 ; N uni1ED5 ; G 1631
+U 7894 ; WX 820 ; N uni1ED6 ; G 1632
+U 7895 ; WX 612 ; N uni1ED7 ; G 1633
+U 7896 ; WX 820 ; N uni1ED8 ; G 1634
+U 7897 ; WX 602 ; N uni1ED9 ; G 1635
+U 7898 ; WX 820 ; N uni1EDA ; G 1636
+U 7899 ; WX 602 ; N uni1EDB ; G 1637
+U 7900 ; WX 820 ; N uni1EDC ; G 1638
+U 7901 ; WX 602 ; N uni1EDD ; G 1639
+U 7902 ; WX 820 ; N uni1EDE ; G 1640
+U 7903 ; WX 602 ; N uni1EDF ; G 1641
+U 7904 ; WX 820 ; N uni1EE0 ; G 1642
+U 7905 ; WX 602 ; N uni1EE1 ; G 1643
+U 7906 ; WX 820 ; N uni1EE2 ; G 1644
+U 7907 ; WX 602 ; N uni1EE3 ; G 1645
+U 7908 ; WX 843 ; N uni1EE4 ; G 1646
+U 7909 ; WX 644 ; N uni1EE5 ; G 1647
+U 7910 ; WX 843 ; N uni1EE6 ; G 1648
+U 7911 ; WX 644 ; N uni1EE7 ; G 1649
+U 7912 ; WX 843 ; N uni1EE8 ; G 1650
+U 7913 ; WX 644 ; N uni1EE9 ; G 1651
+U 7914 ; WX 843 ; N uni1EEA ; G 1652
+U 7915 ; WX 644 ; N uni1EEB ; G 1653
+U 7916 ; WX 843 ; N uni1EEC ; G 1654
+U 7917 ; WX 644 ; N uni1EED ; G 1655
+U 7918 ; WX 843 ; N uni1EEE ; G 1656
+U 7919 ; WX 644 ; N uni1EEF ; G 1657
+U 7920 ; WX 843 ; N uni1EF0 ; G 1658
+U 7921 ; WX 644 ; N uni1EF1 ; G 1659
+U 7922 ; WX 660 ; N Ygrave ; G 1660
+U 7923 ; WX 565 ; N ygrave ; G 1661
+U 7924 ; WX 660 ; N uni1EF4 ; G 1662
+U 7925 ; WX 565 ; N uni1EF5 ; G 1663
+U 7926 ; WX 660 ; N uni1EF6 ; G 1664
+U 7927 ; WX 565 ; N uni1EF7 ; G 1665
+U 7928 ; WX 660 ; N uni1EF8 ; G 1666
+U 7929 ; WX 565 ; N uni1EF9 ; G 1667
+U 7930 ; WX 949 ; N uni1EFA ; G 1668
+U 7931 ; WX 581 ; N uni1EFB ; G 1669
+U 7936 ; WX 675 ; N uni1F00 ; G 1670
+U 7937 ; WX 675 ; N uni1F01 ; G 1671
+U 7938 ; WX 675 ; N uni1F02 ; G 1672
+U 7939 ; WX 675 ; N uni1F03 ; G 1673
+U 7940 ; WX 675 ; N uni1F04 ; G 1674
+U 7941 ; WX 675 ; N uni1F05 ; G 1675
+U 7942 ; WX 675 ; N uni1F06 ; G 1676
+U 7943 ; WX 675 ; N uni1F07 ; G 1677
+U 7944 ; WX 722 ; N uni1F08 ; G 1678
+U 7945 ; WX 722 ; N uni1F09 ; G 1679
+U 7946 ; WX 869 ; N uni1F0A ; G 1680
+U 7947 ; WX 869 ; N uni1F0B ; G 1681
+U 7948 ; WX 734 ; N uni1F0C ; G 1682
+U 7949 ; WX 763 ; N uni1F0D ; G 1683
+U 7950 ; WX 722 ; N uni1F0E ; G 1684
+U 7951 ; WX 722 ; N uni1F0F ; G 1685
+U 7952 ; WX 537 ; N uni1F10 ; G 1686
+U 7953 ; WX 537 ; N uni1F11 ; G 1687
+U 7954 ; WX 537 ; N uni1F12 ; G 1688
+U 7955 ; WX 537 ; N uni1F13 ; G 1689
+U 7956 ; WX 537 ; N uni1F14 ; G 1690
+U 7957 ; WX 537 ; N uni1F15 ; G 1691
+U 7960 ; WX 853 ; N uni1F18 ; G 1692
+U 7961 ; WX 841 ; N uni1F19 ; G 1693
+U 7962 ; WX 1067 ; N uni1F1A ; G 1694
+U 7963 ; WX 1077 ; N uni1F1B ; G 1695
+U 7964 ; WX 1008 ; N uni1F1C ; G 1696
+U 7965 ; WX 1035 ; N uni1F1D ; G 1697
+U 7968 ; WX 599 ; N uni1F20 ; G 1698
+U 7969 ; WX 599 ; N uni1F21 ; G 1699
+U 7970 ; WX 599 ; N uni1F22 ; G 1700
+U 7971 ; WX 599 ; N uni1F23 ; G 1701
+U 7972 ; WX 599 ; N uni1F24 ; G 1702
+U 7973 ; WX 599 ; N uni1F25 ; G 1703
+U 7974 ; WX 599 ; N uni1F26 ; G 1704
+U 7975 ; WX 599 ; N uni1F27 ; G 1705
+U 7976 ; WX 998 ; N uni1F28 ; G 1706
+U 7977 ; WX 992 ; N uni1F29 ; G 1707
+U 7978 ; WX 1212 ; N uni1F2A ; G 1708
+U 7979 ; WX 1224 ; N uni1F2B ; G 1709
+U 7980 ; WX 1159 ; N uni1F2C ; G 1710
+U 7981 ; WX 1183 ; N uni1F2D ; G 1711
+U 7982 ; WX 1098 ; N uni1F2E ; G 1712
+U 7983 ; WX 1095 ; N uni1F2F ; G 1713
+U 7984 ; WX 392 ; N uni1F30 ; G 1714
+U 7985 ; WX 392 ; N uni1F31 ; G 1715
+U 7986 ; WX 392 ; N uni1F32 ; G 1716
+U 7987 ; WX 392 ; N uni1F33 ; G 1717
+U 7988 ; WX 392 ; N uni1F34 ; G 1718
+U 7989 ; WX 392 ; N uni1F35 ; G 1719
+U 7990 ; WX 392 ; N uni1F36 ; G 1720
+U 7991 ; WX 392 ; N uni1F37 ; G 1721
+U 7992 ; WX 521 ; N uni1F38 ; G 1722
+U 7993 ; WX 512 ; N uni1F39 ; G 1723
+U 7994 ; WX 735 ; N uni1F3A ; G 1724
+U 7995 ; WX 738 ; N uni1F3B ; G 1725
+U 7996 ; WX 679 ; N uni1F3C ; G 1726
+U 7997 ; WX 706 ; N uni1F3D ; G 1727
+U 7998 ; WX 624 ; N uni1F3E ; G 1728
+U 7999 ; WX 615 ; N uni1F3F ; G 1729
+U 8000 ; WX 602 ; N uni1F40 ; G 1730
+U 8001 ; WX 602 ; N uni1F41 ; G 1731
+U 8002 ; WX 602 ; N uni1F42 ; G 1732
+U 8003 ; WX 602 ; N uni1F43 ; G 1733
+U 8004 ; WX 602 ; N uni1F44 ; G 1734
+U 8005 ; WX 602 ; N uni1F45 ; G 1735
+U 8008 ; WX 820 ; N uni1F48 ; G 1736
+U 8009 ; WX 859 ; N uni1F49 ; G 1737
+U 8010 ; WX 1120 ; N uni1F4A ; G 1738
+U 8011 ; WX 1127 ; N uni1F4B ; G 1739
+U 8012 ; WX 937 ; N uni1F4C ; G 1740
+U 8013 ; WX 964 ; N uni1F4D ; G 1741
+U 8016 ; WX 608 ; N uni1F50 ; G 1742
+U 8017 ; WX 608 ; N uni1F51 ; G 1743
+U 8018 ; WX 608 ; N uni1F52 ; G 1744
+U 8019 ; WX 608 ; N uni1F53 ; G 1745
+U 8020 ; WX 608 ; N uni1F54 ; G 1746
+U 8021 ; WX 608 ; N uni1F55 ; G 1747
+U 8022 ; WX 608 ; N uni1F56 ; G 1748
+U 8023 ; WX 608 ; N uni1F57 ; G 1749
+U 8025 ; WX 851 ; N uni1F59 ; G 1750
+U 8027 ; WX 1079 ; N uni1F5B ; G 1751
+U 8029 ; WX 1044 ; N uni1F5D ; G 1752
+U 8031 ; WX 953 ; N uni1F5F ; G 1753
+U 8032 ; WX 815 ; N uni1F60 ; G 1754
+U 8033 ; WX 815 ; N uni1F61 ; G 1755
+U 8034 ; WX 815 ; N uni1F62 ; G 1756
+U 8035 ; WX 815 ; N uni1F63 ; G 1757
+U 8036 ; WX 815 ; N uni1F64 ; G 1758
+U 8037 ; WX 815 ; N uni1F65 ; G 1759
+U 8038 ; WX 815 ; N uni1F66 ; G 1760
+U 8039 ; WX 815 ; N uni1F67 ; G 1761
+U 8040 ; WX 829 ; N uni1F68 ; G 1762
+U 8041 ; WX 870 ; N uni1F69 ; G 1763
+U 8042 ; WX 1131 ; N uni1F6A ; G 1764
+U 8043 ; WX 1137 ; N uni1F6B ; G 1765
+U 8044 ; WX 946 ; N uni1F6C ; G 1766
+U 8045 ; WX 976 ; N uni1F6D ; G 1767
+U 8046 ; WX 938 ; N uni1F6E ; G 1768
+U 8047 ; WX 970 ; N uni1F6F ; G 1769
+U 8048 ; WX 675 ; N uni1F70 ; G 1770
+U 8049 ; WX 675 ; N uni1F71 ; G 1771
+U 8050 ; WX 537 ; N uni1F72 ; G 1772
+U 8051 ; WX 537 ; N uni1F73 ; G 1773
+U 8052 ; WX 599 ; N uni1F74 ; G 1774
+U 8053 ; WX 599 ; N uni1F75 ; G 1775
+U 8054 ; WX 392 ; N uni1F76 ; G 1776
+U 8055 ; WX 392 ; N uni1F77 ; G 1777
+U 8056 ; WX 602 ; N uni1F78 ; G 1778
+U 8057 ; WX 602 ; N uni1F79 ; G 1779
+U 8058 ; WX 608 ; N uni1F7A ; G 1780
+U 8059 ; WX 608 ; N uni1F7B ; G 1781
+U 8060 ; WX 815 ; N uni1F7C ; G 1782
+U 8061 ; WX 815 ; N uni1F7D ; G 1783
+U 8064 ; WX 675 ; N uni1F80 ; G 1784
+U 8065 ; WX 675 ; N uni1F81 ; G 1785
+U 8066 ; WX 675 ; N uni1F82 ; G 1786
+U 8067 ; WX 675 ; N uni1F83 ; G 1787
+U 8068 ; WX 675 ; N uni1F84 ; G 1788
+U 8069 ; WX 675 ; N uni1F85 ; G 1789
+U 8070 ; WX 675 ; N uni1F86 ; G 1790
+U 8071 ; WX 675 ; N uni1F87 ; G 1791
+U 8072 ; WX 722 ; N uni1F88 ; G 1792
+U 8073 ; WX 722 ; N uni1F89 ; G 1793
+U 8074 ; WX 869 ; N uni1F8A ; G 1794
+U 8075 ; WX 869 ; N uni1F8B ; G 1795
+U 8076 ; WX 734 ; N uni1F8C ; G 1796
+U 8077 ; WX 763 ; N uni1F8D ; G 1797
+U 8078 ; WX 722 ; N uni1F8E ; G 1798
+U 8079 ; WX 722 ; N uni1F8F ; G 1799
+U 8080 ; WX 599 ; N uni1F90 ; G 1800
+U 8081 ; WX 599 ; N uni1F91 ; G 1801
+U 8082 ; WX 599 ; N uni1F92 ; G 1802
+U 8083 ; WX 599 ; N uni1F93 ; G 1803
+U 8084 ; WX 599 ; N uni1F94 ; G 1804
+U 8085 ; WX 599 ; N uni1F95 ; G 1805
+U 8086 ; WX 599 ; N uni1F96 ; G 1806
+U 8087 ; WX 599 ; N uni1F97 ; G 1807
+U 8088 ; WX 998 ; N uni1F98 ; G 1808
+U 8089 ; WX 992 ; N uni1F99 ; G 1809
+U 8090 ; WX 1212 ; N uni1F9A ; G 1810
+U 8091 ; WX 1224 ; N uni1F9B ; G 1811
+U 8092 ; WX 1159 ; N uni1F9C ; G 1812
+U 8093 ; WX 1183 ; N uni1F9D ; G 1813
+U 8094 ; WX 1098 ; N uni1F9E ; G 1814
+U 8095 ; WX 1095 ; N uni1F9F ; G 1815
+U 8096 ; WX 815 ; N uni1FA0 ; G 1816
+U 8097 ; WX 815 ; N uni1FA1 ; G 1817
+U 8098 ; WX 815 ; N uni1FA2 ; G 1818
+U 8099 ; WX 815 ; N uni1FA3 ; G 1819
+U 8100 ; WX 815 ; N uni1FA4 ; G 1820
+U 8101 ; WX 815 ; N uni1FA5 ; G 1821
+U 8102 ; WX 815 ; N uni1FA6 ; G 1822
+U 8103 ; WX 815 ; N uni1FA7 ; G 1823
+U 8104 ; WX 829 ; N uni1FA8 ; G 1824
+U 8105 ; WX 870 ; N uni1FA9 ; G 1825
+U 8106 ; WX 1131 ; N uni1FAA ; G 1826
+U 8107 ; WX 1137 ; N uni1FAB ; G 1827
+U 8108 ; WX 946 ; N uni1FAC ; G 1828
+U 8109 ; WX 976 ; N uni1FAD ; G 1829
+U 8110 ; WX 938 ; N uni1FAE ; G 1830
+U 8111 ; WX 970 ; N uni1FAF ; G 1831
+U 8112 ; WX 675 ; N uni1FB0 ; G 1832
+U 8113 ; WX 675 ; N uni1FB1 ; G 1833
+U 8114 ; WX 675 ; N uni1FB2 ; G 1834
+U 8115 ; WX 675 ; N uni1FB3 ; G 1835
+U 8116 ; WX 675 ; N uni1FB4 ; G 1836
+U 8118 ; WX 675 ; N uni1FB6 ; G 1837
+U 8119 ; WX 675 ; N uni1FB7 ; G 1838
+U 8120 ; WX 722 ; N uni1FB8 ; G 1839
+U 8121 ; WX 722 ; N uni1FB9 ; G 1840
+U 8122 ; WX 722 ; N uni1FBA ; G 1841
+U 8123 ; WX 722 ; N uni1FBB ; G 1842
+U 8124 ; WX 722 ; N uni1FBC ; G 1843
+U 8125 ; WX 500 ; N uni1FBD ; G 1844
+U 8126 ; WX 500 ; N uni1FBE ; G 1845
+U 8127 ; WX 500 ; N uni1FBF ; G 1846
+U 8128 ; WX 500 ; N uni1FC0 ; G 1847
+U 8129 ; WX 500 ; N uni1FC1 ; G 1848
+U 8130 ; WX 599 ; N uni1FC2 ; G 1849
+U 8131 ; WX 599 ; N uni1FC3 ; G 1850
+U 8132 ; WX 599 ; N uni1FC4 ; G 1851
+U 8134 ; WX 599 ; N uni1FC6 ; G 1852
+U 8135 ; WX 599 ; N uni1FC7 ; G 1853
+U 8136 ; WX 912 ; N uni1FC8 ; G 1854
+U 8137 ; WX 900 ; N uni1FC9 ; G 1855
+U 8138 ; WX 1063 ; N uni1FCA ; G 1856
+U 8139 ; WX 1039 ; N uni1FCB ; G 1857
+U 8140 ; WX 872 ; N uni1FCC ; G 1858
+U 8141 ; WX 500 ; N uni1FCD ; G 1859
+U 8142 ; WX 500 ; N uni1FCE ; G 1860
+U 8143 ; WX 500 ; N uni1FCF ; G 1861
+U 8144 ; WX 392 ; N uni1FD0 ; G 1862
+U 8145 ; WX 392 ; N uni1FD1 ; G 1863
+U 8146 ; WX 392 ; N uni1FD2 ; G 1864
+U 8147 ; WX 392 ; N uni1FD3 ; G 1865
+U 8150 ; WX 392 ; N uni1FD6 ; G 1866
+U 8151 ; WX 392 ; N uni1FD7 ; G 1867
+U 8152 ; WX 395 ; N uni1FD8 ; G 1868
+U 8153 ; WX 395 ; N uni1FD9 ; G 1869
+U 8154 ; WX 588 ; N uni1FDA ; G 1870
+U 8155 ; WX 562 ; N uni1FDB ; G 1871
+U 8157 ; WX 500 ; N uni1FDD ; G 1872
+U 8158 ; WX 500 ; N uni1FDE ; G 1873
+U 8159 ; WX 500 ; N uni1FDF ; G 1874
+U 8160 ; WX 608 ; N uni1FE0 ; G 1875
+U 8161 ; WX 608 ; N uni1FE1 ; G 1876
+U 8162 ; WX 608 ; N uni1FE2 ; G 1877
+U 8163 ; WX 608 ; N uni1FE3 ; G 1878
+U 8164 ; WX 588 ; N uni1FE4 ; G 1879
+U 8165 ; WX 588 ; N uni1FE5 ; G 1880
+U 8166 ; WX 608 ; N uni1FE6 ; G 1881
+U 8167 ; WX 608 ; N uni1FE7 ; G 1882
+U 8168 ; WX 660 ; N uni1FE8 ; G 1883
+U 8169 ; WX 660 ; N uni1FE9 ; G 1884
+U 8170 ; WX 921 ; N uni1FEA ; G 1885
+U 8171 ; WX 897 ; N uni1FEB ; G 1886
+U 8172 ; WX 790 ; N uni1FEC ; G 1887
+U 8173 ; WX 500 ; N uni1FED ; G 1888
+U 8174 ; WX 500 ; N uni1FEE ; G 1889
+U 8175 ; WX 500 ; N uni1FEF ; G 1890
+U 8178 ; WX 815 ; N uni1FF2 ; G 1891
+U 8179 ; WX 815 ; N uni1FF3 ; G 1892
+U 8180 ; WX 815 ; N uni1FF4 ; G 1893
+U 8182 ; WX 815 ; N uni1FF6 ; G 1894
+U 8183 ; WX 815 ; N uni1FF7 ; G 1895
+U 8184 ; WX 961 ; N uni1FF8 ; G 1896
+U 8185 ; WX 835 ; N uni1FF9 ; G 1897
+U 8186 ; WX 984 ; N uni1FFA ; G 1898
+U 8187 ; WX 853 ; N uni1FFB ; G 1899
+U 8188 ; WX 829 ; N uni1FFC ; G 1900
+U 8189 ; WX 500 ; N uni1FFD ; G 1901
+U 8190 ; WX 500 ; N uni1FFE ; G 1902
+U 8192 ; WX 500 ; N uni2000 ; G 1903
+U 8193 ; WX 1000 ; N uni2001 ; G 1904
+U 8194 ; WX 500 ; N uni2002 ; G 1905
+U 8195 ; WX 1000 ; N uni2003 ; G 1906
+U 8196 ; WX 330 ; N uni2004 ; G 1907
+U 8197 ; WX 250 ; N uni2005 ; G 1908
+U 8198 ; WX 167 ; N uni2006 ; G 1909
+U 8199 ; WX 636 ; N uni2007 ; G 1910
+U 8200 ; WX 318 ; N uni2008 ; G 1911
+U 8201 ; WX 200 ; N uni2009 ; G 1912
+U 8202 ; WX 100 ; N uni200A ; G 1913
+U 8203 ; WX 0 ; N uni200B ; G 1914
+U 8204 ; WX 0 ; N uni200C ; G 1915
+U 8205 ; WX 0 ; N uni200D ; G 1916
+U 8206 ; WX 0 ; N uni200E ; G 1917
+U 8207 ; WX 0 ; N uni200F ; G 1918
+U 8208 ; WX 338 ; N uni2010 ; G 1919
+U 8209 ; WX 338 ; N uni2011 ; G 1920
+U 8210 ; WX 636 ; N figuredash ; G 1921
+U 8211 ; WX 500 ; N endash ; G 1922
+U 8212 ; WX 1000 ; N emdash ; G 1923
+U 8213 ; WX 1000 ; N uni2015 ; G 1924
+U 8214 ; WX 500 ; N uni2016 ; G 1925
+U 8215 ; WX 500 ; N underscoredbl ; G 1926
+U 8216 ; WX 318 ; N quoteleft ; G 1927
+U 8217 ; WX 318 ; N quoteright ; G 1928
+U 8218 ; WX 318 ; N quotesinglbase ; G 1929
+U 8219 ; WX 318 ; N quotereversed ; G 1930
+U 8220 ; WX 511 ; N quotedblleft ; G 1931
+U 8221 ; WX 511 ; N quotedblright ; G 1932
+U 8222 ; WX 518 ; N quotedblbase ; G 1933
+U 8223 ; WX 511 ; N uni201F ; G 1934
+U 8224 ; WX 500 ; N dagger ; G 1935
+U 8225 ; WX 500 ; N daggerdbl ; G 1936
+U 8226 ; WX 590 ; N bullet ; G 1937
+U 8227 ; WX 590 ; N uni2023 ; G 1938
+U 8228 ; WX 334 ; N onedotenleader ; G 1939
+U 8229 ; WX 667 ; N twodotenleader ; G 1940
+U 8230 ; WX 1000 ; N ellipsis ; G 1941
+U 8234 ; WX 0 ; N uni202A ; G 1942
+U 8235 ; WX 0 ; N uni202B ; G 1943
+U 8236 ; WX 0 ; N uni202C ; G 1944
+U 8237 ; WX 0 ; N uni202D ; G 1945
+U 8238 ; WX 0 ; N uni202E ; G 1946
+U 8239 ; WX 200 ; N uni202F ; G 1947
+U 8240 ; WX 1342 ; N perthousand ; G 1948
+U 8241 ; WX 1734 ; N uni2031 ; G 1949
+U 8242 ; WX 227 ; N minute ; G 1950
+U 8243 ; WX 374 ; N second ; G 1951
+U 8244 ; WX 520 ; N uni2034 ; G 1952
+U 8245 ; WX 227 ; N uni2035 ; G 1953
+U 8246 ; WX 374 ; N uni2036 ; G 1954
+U 8247 ; WX 520 ; N uni2037 ; G 1955
+U 8248 ; WX 339 ; N uni2038 ; G 1956
+U 8249 ; WX 400 ; N guilsinglleft ; G 1957
+U 8250 ; WX 400 ; N guilsinglright ; G 1958
+U 8252 ; WX 527 ; N exclamdbl ; G 1959
+U 8253 ; WX 536 ; N uni203D ; G 1960
+U 8254 ; WX 500 ; N uni203E ; G 1961
+U 8258 ; WX 1000 ; N uni2042 ; G 1962
+U 8260 ; WX 167 ; N fraction ; G 1963
+U 8261 ; WX 390 ; N uni2045 ; G 1964
+U 8262 ; WX 390 ; N uni2046 ; G 1965
+U 8263 ; WX 976 ; N uni2047 ; G 1966
+U 8264 ; WX 753 ; N uni2048 ; G 1967
+U 8265 ; WX 753 ; N uni2049 ; G 1968
+U 8267 ; WX 636 ; N uni204B ; G 1969
+U 8268 ; WX 500 ; N uni204C ; G 1970
+U 8269 ; WX 500 ; N uni204D ; G 1971
+U 8270 ; WX 500 ; N uni204E ; G 1972
+U 8271 ; WX 337 ; N uni204F ; G 1973
+U 8273 ; WX 500 ; N uni2051 ; G 1974
+U 8274 ; WX 450 ; N uni2052 ; G 1975
+U 8275 ; WX 1000 ; N uni2053 ; G 1976
+U 8279 ; WX 663 ; N uni2057 ; G 1977
+U 8287 ; WX 222 ; N uni205F ; G 1978
+U 8288 ; WX 0 ; N uni2060 ; G 1979
+U 8289 ; WX 0 ; N uni2061 ; G 1980
+U 8290 ; WX 0 ; N uni2062 ; G 1981
+U 8291 ; WX 0 ; N uni2063 ; G 1982
+U 8292 ; WX 0 ; N uni2064 ; G 1983
+U 8298 ; WX 0 ; N uni206A ; G 1984
+U 8299 ; WX 0 ; N uni206B ; G 1985
+U 8300 ; WX 0 ; N uni206C ; G 1986
+U 8301 ; WX 0 ; N uni206D ; G 1987
+U 8302 ; WX 0 ; N uni206E ; G 1988
+U 8303 ; WX 0 ; N uni206F ; G 1989
+U 8304 ; WX 401 ; N uni2070 ; G 1990
+U 8305 ; WX 201 ; N uni2071 ; G 1991
+U 8308 ; WX 401 ; N uni2074 ; G 1992
+U 8309 ; WX 401 ; N uni2075 ; G 1993
+U 8310 ; WX 401 ; N uni2076 ; G 1994
+U 8311 ; WX 401 ; N uni2077 ; G 1995
+U 8312 ; WX 401 ; N uni2078 ; G 1996
+U 8313 ; WX 401 ; N uni2079 ; G 1997
+U 8314 ; WX 528 ; N uni207A ; G 1998
+U 8315 ; WX 528 ; N uni207B ; G 1999
+U 8316 ; WX 528 ; N uni207C ; G 2000
+U 8317 ; WX 246 ; N uni207D ; G 2001
+U 8318 ; WX 246 ; N uni207E ; G 2002
+U 8319 ; WX 433 ; N uni207F ; G 2003
+U 8320 ; WX 401 ; N uni2080 ; G 2004
+U 8321 ; WX 401 ; N uni2081 ; G 2005
+U 8322 ; WX 401 ; N uni2082 ; G 2006
+U 8323 ; WX 401 ; N uni2083 ; G 2007
+U 8324 ; WX 401 ; N uni2084 ; G 2008
+U 8325 ; WX 401 ; N uni2085 ; G 2009
+U 8326 ; WX 401 ; N uni2086 ; G 2010
+U 8327 ; WX 401 ; N uni2087 ; G 2011
+U 8328 ; WX 401 ; N uni2088 ; G 2012
+U 8329 ; WX 401 ; N uni2089 ; G 2013
+U 8330 ; WX 528 ; N uni208A ; G 2014
+U 8331 ; WX 528 ; N uni208B ; G 2015
+U 8332 ; WX 528 ; N uni208C ; G 2016
+U 8333 ; WX 246 ; N uni208D ; G 2017
+U 8334 ; WX 246 ; N uni208E ; G 2018
+U 8336 ; WX 386 ; N uni2090 ; G 2019
+U 8337 ; WX 387 ; N uni2091 ; G 2020
+U 8338 ; WX 385 ; N uni2092 ; G 2021
+U 8339 ; WX 424 ; N uni2093 ; G 2022
+U 8340 ; WX 387 ; N uni2094 ; G 2023
+U 8341 ; WX 433 ; N uni2095 ; G 2024
+U 8342 ; WX 365 ; N uni2096 ; G 2025
+U 8343 ; WX 243 ; N uni2097 ; G 2026
+U 8344 ; WX 613 ; N uni2098 ; G 2027
+U 8345 ; WX 433 ; N uni2099 ; G 2028
+U 8346 ; WX 400 ; N uni209A ; G 2029
+U 8347 ; WX 337 ; N uni209B ; G 2030
+U 8348 ; WX 247 ; N uni209C ; G 2031
+U 8358 ; WX 636 ; N uni20A6 ; G 2032
+U 8364 ; WX 636 ; N Euro ; G 2033
+U 8367 ; WX 1057 ; N uni20AF ; G 2034
+U 8369 ; WX 706 ; N uni20B1 ; G 2035
+U 8372 ; WX 780 ; N uni20B4 ; G 2036
+U 8373 ; WX 636 ; N uni20B5 ; G 2037
+U 8376 ; WX 636 ; N uni20B8 ; G 2038
+U 8377 ; WX 636 ; N uni20B9 ; G 2039
+U 8378 ; WX 636 ; N uni20BA ; G 2040
+U 8381 ; WX 636 ; N uni20BD ; G 2041
+U 8450 ; WX 796 ; N uni2102 ; G 2042
+U 8451 ; WX 1119 ; N uni2103 ; G 2043
+U 8457 ; WX 1047 ; N uni2109 ; G 2044
+U 8461 ; WX 945 ; N uni210D ; G 2045
+U 8462 ; WX 644 ; N uni210E ; G 2046
+U 8463 ; WX 644 ; N uni210F ; G 2047
+U 8469 ; WX 914 ; N uni2115 ; G 2048
+U 8470 ; WX 946 ; N uni2116 ; G 2049
+U 8473 ; WX 752 ; N uni2119 ; G 2050
+U 8474 ; WX 871 ; N uni211A ; G 2051
+U 8477 ; WX 831 ; N uni211D ; G 2052
+U 8482 ; WX 1000 ; N trademark ; G 2053
+U 8484 ; WX 730 ; N uni2124 ; G 2054
+U 8486 ; WX 829 ; N uni2126 ; G 2055
+U 8487 ; WX 829 ; N uni2127 ; G 2056
+U 8490 ; WX 747 ; N uni212A ; G 2057
+U 8491 ; WX 722 ; N uni212B ; G 2058
+U 8498 ; WX 694 ; N uni2132 ; G 2059
+U 8508 ; WX 732 ; N uni213C ; G 2060
+U 8509 ; WX 660 ; N uni213D ; G 2061
+U 8510 ; WX 710 ; N uni213E ; G 2062
+U 8511 ; WX 944 ; N uni213F ; G 2063
+U 8512 ; WX 714 ; N uni2140 ; G 2064
+U 8513 ; WX 775 ; N uni2141 ; G 2065
+U 8514 ; WX 557 ; N uni2142 ; G 2066
+U 8515 ; WX 557 ; N uni2143 ; G 2067
+U 8516 ; WX 611 ; N uni2144 ; G 2068
+U 8517 ; WX 867 ; N uni2145 ; G 2069
+U 8518 ; WX 699 ; N uni2146 ; G 2070
+U 8519 ; WX 636 ; N uni2147 ; G 2071
+U 8520 ; WX 380 ; N uni2148 ; G 2072
+U 8521 ; WX 362 ; N uni2149 ; G 2073
+U 8523 ; WX 890 ; N uni214B ; G 2074
+U 8526 ; WX 514 ; N uni214E ; G 2075
+U 8528 ; WX 969 ; N uni2150 ; G 2076
+U 8529 ; WX 969 ; N uni2151 ; G 2077
+U 8530 ; WX 1370 ; N uni2152 ; G 2078
+U 8531 ; WX 969 ; N onethird ; G 2079
+U 8532 ; WX 969 ; N twothirds ; G 2080
+U 8533 ; WX 969 ; N uni2155 ; G 2081
+U 8534 ; WX 969 ; N uni2156 ; G 2082
+U 8535 ; WX 969 ; N uni2157 ; G 2083
+U 8536 ; WX 969 ; N uni2158 ; G 2084
+U 8537 ; WX 969 ; N uni2159 ; G 2085
+U 8538 ; WX 969 ; N uni215A ; G 2086
+U 8539 ; WX 969 ; N oneeighth ; G 2087
+U 8540 ; WX 969 ; N threeeighths ; G 2088
+U 8541 ; WX 969 ; N fiveeighths ; G 2089
+U 8542 ; WX 969 ; N seveneighths ; G 2090
+U 8543 ; WX 568 ; N uni215F ; G 2091
+U 8544 ; WX 395 ; N uni2160 ; G 2092
+U 8545 ; WX 680 ; N uni2161 ; G 2093
+U 8546 ; WX 964 ; N uni2162 ; G 2094
+U 8547 ; WX 999 ; N uni2163 ; G 2095
+U 8548 ; WX 722 ; N uni2164 ; G 2096
+U 8549 ; WX 1006 ; N uni2165 ; G 2097
+U 8550 ; WX 1291 ; N uni2166 ; G 2098
+U 8551 ; WX 1575 ; N uni2167 ; G 2099
+U 8552 ; WX 965 ; N uni2168 ; G 2100
+U 8553 ; WX 712 ; N uni2169 ; G 2101
+U 8554 ; WX 969 ; N uni216A ; G 2102
+U 8555 ; WX 1253 ; N uni216B ; G 2103
+U 8556 ; WX 664 ; N uni216C ; G 2104
+U 8557 ; WX 765 ; N uni216D ; G 2105
+U 8558 ; WX 802 ; N uni216E ; G 2106
+U 8559 ; WX 1024 ; N uni216F ; G 2107
+U 8560 ; WX 320 ; N uni2170 ; G 2108
+U 8561 ; WX 640 ; N uni2171 ; G 2109
+U 8562 ; WX 959 ; N uni2172 ; G 2110
+U 8563 ; WX 885 ; N uni2173 ; G 2111
+U 8564 ; WX 565 ; N uni2174 ; G 2112
+U 8565 ; WX 885 ; N uni2175 ; G 2113
+U 8566 ; WX 1205 ; N uni2176 ; G 2114
+U 8567 ; WX 1524 ; N uni2177 ; G 2115
+U 8568 ; WX 884 ; N uni2178 ; G 2116
+U 8569 ; WX 564 ; N uni2179 ; G 2117
+U 8570 ; WX 884 ; N uni217A ; G 2118
+U 8571 ; WX 1204 ; N uni217B ; G 2119
+U 8572 ; WX 320 ; N uni217C ; G 2120
+U 8573 ; WX 560 ; N uni217D ; G 2121
+U 8574 ; WX 640 ; N uni217E ; G 2122
+U 8575 ; WX 948 ; N uni217F ; G 2123
+U 8576 ; WX 1206 ; N uni2180 ; G 2124
+U 8577 ; WX 802 ; N uni2181 ; G 2125
+U 8578 ; WX 1206 ; N uni2182 ; G 2126
+U 8579 ; WX 765 ; N uni2183 ; G 2127
+U 8580 ; WX 560 ; N uni2184 ; G 2128
+U 8581 ; WX 765 ; N uni2185 ; G 2129
+U 8585 ; WX 969 ; N uni2189 ; G 2130
+U 8592 ; WX 838 ; N arrowleft ; G 2131
+U 8593 ; WX 838 ; N arrowup ; G 2132
+U 8594 ; WX 838 ; N arrowright ; G 2133
+U 8595 ; WX 838 ; N arrowdown ; G 2134
+U 8596 ; WX 838 ; N arrowboth ; G 2135
+U 8597 ; WX 838 ; N arrowupdn ; G 2136
+U 8598 ; WX 838 ; N uni2196 ; G 2137
+U 8599 ; WX 838 ; N uni2197 ; G 2138
+U 8600 ; WX 838 ; N uni2198 ; G 2139
+U 8601 ; WX 838 ; N uni2199 ; G 2140
+U 8602 ; WX 838 ; N uni219A ; G 2141
+U 8603 ; WX 838 ; N uni219B ; G 2142
+U 8604 ; WX 838 ; N uni219C ; G 2143
+U 8605 ; WX 838 ; N uni219D ; G 2144
+U 8606 ; WX 838 ; N uni219E ; G 2145
+U 8607 ; WX 838 ; N uni219F ; G 2146
+U 8608 ; WX 838 ; N uni21A0 ; G 2147
+U 8609 ; WX 838 ; N uni21A1 ; G 2148
+U 8610 ; WX 838 ; N uni21A2 ; G 2149
+U 8611 ; WX 838 ; N uni21A3 ; G 2150
+U 8612 ; WX 838 ; N uni21A4 ; G 2151
+U 8613 ; WX 838 ; N uni21A5 ; G 2152
+U 8614 ; WX 838 ; N uni21A6 ; G 2153
+U 8615 ; WX 838 ; N uni21A7 ; G 2154
+U 8616 ; WX 838 ; N arrowupdnbse ; G 2155
+U 8617 ; WX 838 ; N uni21A9 ; G 2156
+U 8618 ; WX 838 ; N uni21AA ; G 2157
+U 8619 ; WX 838 ; N uni21AB ; G 2158
+U 8620 ; WX 838 ; N uni21AC ; G 2159
+U 8621 ; WX 838 ; N uni21AD ; G 2160
+U 8622 ; WX 838 ; N uni21AE ; G 2161
+U 8623 ; WX 838 ; N uni21AF ; G 2162
+U 8624 ; WX 838 ; N uni21B0 ; G 2163
+U 8625 ; WX 838 ; N uni21B1 ; G 2164
+U 8626 ; WX 838 ; N uni21B2 ; G 2165
+U 8627 ; WX 838 ; N uni21B3 ; G 2166
+U 8628 ; WX 838 ; N uni21B4 ; G 2167
+U 8629 ; WX 838 ; N carriagereturn ; G 2168
+U 8630 ; WX 838 ; N uni21B6 ; G 2169
+U 8631 ; WX 838 ; N uni21B7 ; G 2170
+U 8632 ; WX 838 ; N uni21B8 ; G 2171
+U 8633 ; WX 838 ; N uni21B9 ; G 2172
+U 8634 ; WX 838 ; N uni21BA ; G 2173
+U 8635 ; WX 838 ; N uni21BB ; G 2174
+U 8636 ; WX 838 ; N uni21BC ; G 2175
+U 8637 ; WX 838 ; N uni21BD ; G 2176
+U 8638 ; WX 838 ; N uni21BE ; G 2177
+U 8639 ; WX 838 ; N uni21BF ; G 2178
+U 8640 ; WX 838 ; N uni21C0 ; G 2179
+U 8641 ; WX 838 ; N uni21C1 ; G 2180
+U 8642 ; WX 838 ; N uni21C2 ; G 2181
+U 8643 ; WX 838 ; N uni21C3 ; G 2182
+U 8644 ; WX 838 ; N uni21C4 ; G 2183
+U 8645 ; WX 838 ; N uni21C5 ; G 2184
+U 8646 ; WX 838 ; N uni21C6 ; G 2185
+U 8647 ; WX 838 ; N uni21C7 ; G 2186
+U 8648 ; WX 838 ; N uni21C8 ; G 2187
+U 8649 ; WX 838 ; N uni21C9 ; G 2188
+U 8650 ; WX 838 ; N uni21CA ; G 2189
+U 8651 ; WX 838 ; N uni21CB ; G 2190
+U 8652 ; WX 838 ; N uni21CC ; G 2191
+U 8653 ; WX 838 ; N uni21CD ; G 2192
+U 8654 ; WX 838 ; N uni21CE ; G 2193
+U 8655 ; WX 838 ; N uni21CF ; G 2194
+U 8656 ; WX 838 ; N arrowdblleft ; G 2195
+U 8657 ; WX 838 ; N arrowdblup ; G 2196
+U 8658 ; WX 838 ; N arrowdblright ; G 2197
+U 8659 ; WX 838 ; N arrowdbldown ; G 2198
+U 8660 ; WX 838 ; N arrowdblboth ; G 2199
+U 8661 ; WX 838 ; N uni21D5 ; G 2200
+U 8662 ; WX 838 ; N uni21D6 ; G 2201
+U 8663 ; WX 838 ; N uni21D7 ; G 2202
+U 8664 ; WX 838 ; N uni21D8 ; G 2203
+U 8665 ; WX 838 ; N uni21D9 ; G 2204
+U 8666 ; WX 838 ; N uni21DA ; G 2205
+U 8667 ; WX 838 ; N uni21DB ; G 2206
+U 8668 ; WX 838 ; N uni21DC ; G 2207
+U 8669 ; WX 838 ; N uni21DD ; G 2208
+U 8670 ; WX 838 ; N uni21DE ; G 2209
+U 8671 ; WX 838 ; N uni21DF ; G 2210
+U 8672 ; WX 838 ; N uni21E0 ; G 2211
+U 8673 ; WX 838 ; N uni21E1 ; G 2212
+U 8674 ; WX 838 ; N uni21E2 ; G 2213
+U 8675 ; WX 838 ; N uni21E3 ; G 2214
+U 8676 ; WX 838 ; N uni21E4 ; G 2215
+U 8677 ; WX 838 ; N uni21E5 ; G 2216
+U 8678 ; WX 838 ; N uni21E6 ; G 2217
+U 8679 ; WX 838 ; N uni21E7 ; G 2218
+U 8680 ; WX 838 ; N uni21E8 ; G 2219
+U 8681 ; WX 838 ; N uni21E9 ; G 2220
+U 8682 ; WX 838 ; N uni21EA ; G 2221
+U 8683 ; WX 838 ; N uni21EB ; G 2222
+U 8684 ; WX 838 ; N uni21EC ; G 2223
+U 8685 ; WX 838 ; N uni21ED ; G 2224
+U 8686 ; WX 838 ; N uni21EE ; G 2225
+U 8687 ; WX 838 ; N uni21EF ; G 2226
+U 8688 ; WX 838 ; N uni21F0 ; G 2227
+U 8689 ; WX 838 ; N uni21F1 ; G 2228
+U 8690 ; WX 838 ; N uni21F2 ; G 2229
+U 8691 ; WX 838 ; N uni21F3 ; G 2230
+U 8692 ; WX 838 ; N uni21F4 ; G 2231
+U 8693 ; WX 838 ; N uni21F5 ; G 2232
+U 8694 ; WX 838 ; N uni21F6 ; G 2233
+U 8695 ; WX 838 ; N uni21F7 ; G 2234
+U 8696 ; WX 838 ; N uni21F8 ; G 2235
+U 8697 ; WX 838 ; N uni21F9 ; G 2236
+U 8698 ; WX 838 ; N uni21FA ; G 2237
+U 8699 ; WX 838 ; N uni21FB ; G 2238
+U 8700 ; WX 838 ; N uni21FC ; G 2239
+U 8701 ; WX 838 ; N uni21FD ; G 2240
+U 8702 ; WX 838 ; N uni21FE ; G 2241
+U 8703 ; WX 838 ; N uni21FF ; G 2242
+U 8704 ; WX 604 ; N universal ; G 2243
+U 8706 ; WX 517 ; N partialdiff ; G 2244
+U 8707 ; WX 542 ; N existential ; G 2245
+U 8708 ; WX 542 ; N uni2204 ; G 2246
+U 8710 ; WX 698 ; N increment ; G 2247
+U 8711 ; WX 698 ; N gradient ; G 2248
+U 8712 ; WX 740 ; N element ; G 2249
+U 8713 ; WX 740 ; N notelement ; G 2250
+U 8715 ; WX 740 ; N suchthat ; G 2251
+U 8716 ; WX 740 ; N uni220C ; G 2252
+U 8719 ; WX 796 ; N product ; G 2253
+U 8720 ; WX 796 ; N uni2210 ; G 2254
+U 8721 ; WX 714 ; N summation ; G 2255
+U 8722 ; WX 838 ; N minus ; G 2256
+U 8723 ; WX 838 ; N uni2213 ; G 2257
+U 8724 ; WX 838 ; N uni2214 ; G 2258
+U 8725 ; WX 337 ; N uni2215 ; G 2259
+U 8727 ; WX 680 ; N asteriskmath ; G 2260
+U 8728 ; WX 490 ; N uni2218 ; G 2261
+U 8729 ; WX 490 ; N uni2219 ; G 2262
+U 8730 ; WX 637 ; N radical ; G 2263
+U 8731 ; WX 637 ; N uni221B ; G 2264
+U 8732 ; WX 637 ; N uni221C ; G 2265
+U 8733 ; WX 677 ; N proportional ; G 2266
+U 8734 ; WX 833 ; N infinity ; G 2267
+U 8735 ; WX 838 ; N orthogonal ; G 2268
+U 8736 ; WX 838 ; N angle ; G 2269
+U 8739 ; WX 291 ; N uni2223 ; G 2270
+U 8740 ; WX 479 ; N uni2224 ; G 2271
+U 8741 ; WX 462 ; N uni2225 ; G 2272
+U 8742 ; WX 634 ; N uni2226 ; G 2273
+U 8743 ; WX 732 ; N logicaland ; G 2274
+U 8744 ; WX 732 ; N logicalor ; G 2275
+U 8745 ; WX 838 ; N intersection ; G 2276
+U 8746 ; WX 838 ; N union ; G 2277
+U 8747 ; WX 521 ; N integral ; G 2278
+U 8748 ; WX 852 ; N uni222C ; G 2279
+U 8749 ; WX 1182 ; N uni222D ; G 2280
+U 8760 ; WX 838 ; N uni2238 ; G 2281
+U 8761 ; WX 838 ; N uni2239 ; G 2282
+U 8762 ; WX 838 ; N uni223A ; G 2283
+U 8763 ; WX 838 ; N uni223B ; G 2284
+U 8764 ; WX 838 ; N similar ; G 2285
+U 8765 ; WX 838 ; N uni223D ; G 2286
+U 8770 ; WX 838 ; N uni2242 ; G 2287
+U 8771 ; WX 838 ; N uni2243 ; G 2288
+U 8776 ; WX 838 ; N approxequal ; G 2289
+U 8784 ; WX 838 ; N uni2250 ; G 2290
+U 8785 ; WX 838 ; N uni2251 ; G 2291
+U 8786 ; WX 838 ; N uni2252 ; G 2292
+U 8787 ; WX 838 ; N uni2253 ; G 2293
+U 8788 ; WX 1033 ; N uni2254 ; G 2294
+U 8789 ; WX 1033 ; N uni2255 ; G 2295
+U 8800 ; WX 838 ; N notequal ; G 2296
+U 8801 ; WX 838 ; N equivalence ; G 2297
+U 8804 ; WX 838 ; N lessequal ; G 2298
+U 8805 ; WX 838 ; N greaterequal ; G 2299
+U 8834 ; WX 838 ; N propersubset ; G 2300
+U 8835 ; WX 838 ; N propersuperset ; G 2301
+U 8836 ; WX 838 ; N notsubset ; G 2302
+U 8837 ; WX 838 ; N uni2285 ; G 2303
+U 8838 ; WX 838 ; N reflexsubset ; G 2304
+U 8839 ; WX 838 ; N reflexsuperset ; G 2305
+U 8844 ; WX 838 ; N uni228C ; G 2306
+U 8845 ; WX 838 ; N uni228D ; G 2307
+U 8846 ; WX 838 ; N uni228E ; G 2308
+U 8847 ; WX 846 ; N uni228F ; G 2309
+U 8848 ; WX 846 ; N uni2290 ; G 2310
+U 8849 ; WX 846 ; N uni2291 ; G 2311
+U 8850 ; WX 846 ; N uni2292 ; G 2312
+U 8851 ; WX 838 ; N uni2293 ; G 2313
+U 8852 ; WX 838 ; N uni2294 ; G 2314
+U 8853 ; WX 838 ; N circleplus ; G 2315
+U 8854 ; WX 838 ; N uni2296 ; G 2316
+U 8855 ; WX 838 ; N circlemultiply ; G 2317
+U 8856 ; WX 838 ; N uni2298 ; G 2318
+U 8857 ; WX 838 ; N uni2299 ; G 2319
+U 8858 ; WX 838 ; N uni229A ; G 2320
+U 8859 ; WX 838 ; N uni229B ; G 2321
+U 8860 ; WX 838 ; N uni229C ; G 2322
+U 8861 ; WX 838 ; N uni229D ; G 2323
+U 8862 ; WX 838 ; N uni229E ; G 2324
+U 8863 ; WX 838 ; N uni229F ; G 2325
+U 8864 ; WX 838 ; N uni22A0 ; G 2326
+U 8865 ; WX 838 ; N uni22A1 ; G 2327
+U 8866 ; WX 860 ; N uni22A2 ; G 2328
+U 8867 ; WX 860 ; N uni22A3 ; G 2329
+U 8868 ; WX 940 ; N uni22A4 ; G 2330
+U 8869 ; WX 940 ; N perpendicular ; G 2331
+U 8870 ; WX 567 ; N uni22A6 ; G 2332
+U 8871 ; WX 567 ; N uni22A7 ; G 2333
+U 8872 ; WX 860 ; N uni22A8 ; G 2334
+U 8873 ; WX 860 ; N uni22A9 ; G 2335
+U 8874 ; WX 860 ; N uni22AA ; G 2336
+U 8875 ; WX 1031 ; N uni22AB ; G 2337
+U 8876 ; WX 860 ; N uni22AC ; G 2338
+U 8877 ; WX 860 ; N uni22AD ; G 2339
+U 8878 ; WX 860 ; N uni22AE ; G 2340
+U 8879 ; WX 1031 ; N uni22AF ; G 2341
+U 8900 ; WX 626 ; N uni22C4 ; G 2342
+U 8901 ; WX 342 ; N dotmath ; G 2343
+U 8962 ; WX 764 ; N house ; G 2344
+U 8968 ; WX 390 ; N uni2308 ; G 2345
+U 8969 ; WX 390 ; N uni2309 ; G 2346
+U 8970 ; WX 390 ; N uni230A ; G 2347
+U 8971 ; WX 390 ; N uni230B ; G 2348
+U 8976 ; WX 838 ; N revlogicalnot ; G 2349
+U 8977 ; WX 513 ; N uni2311 ; G 2350
+U 8984 ; WX 1000 ; N uni2318 ; G 2351
+U 8985 ; WX 838 ; N uni2319 ; G 2352
+U 8992 ; WX 521 ; N integraltp ; G 2353
+U 8993 ; WX 521 ; N integralbt ; G 2354
+U 8997 ; WX 1000 ; N uni2325 ; G 2355
+U 9000 ; WX 1443 ; N uni2328 ; G 2356
+U 9085 ; WX 919 ; N uni237D ; G 2357
+U 9115 ; WX 500 ; N uni239B ; G 2358
+U 9116 ; WX 500 ; N uni239C ; G 2359
+U 9117 ; WX 500 ; N uni239D ; G 2360
+U 9118 ; WX 500 ; N uni239E ; G 2361
+U 9119 ; WX 500 ; N uni239F ; G 2362
+U 9120 ; WX 500 ; N uni23A0 ; G 2363
+U 9121 ; WX 500 ; N uni23A1 ; G 2364
+U 9122 ; WX 500 ; N uni23A2 ; G 2365
+U 9123 ; WX 500 ; N uni23A3 ; G 2366
+U 9124 ; WX 500 ; N uni23A4 ; G 2367
+U 9125 ; WX 500 ; N uni23A5 ; G 2368
+U 9126 ; WX 500 ; N uni23A6 ; G 2369
+U 9127 ; WX 750 ; N uni23A7 ; G 2370
+U 9128 ; WX 750 ; N uni23A8 ; G 2371
+U 9129 ; WX 750 ; N uni23A9 ; G 2372
+U 9130 ; WX 750 ; N uni23AA ; G 2373
+U 9131 ; WX 750 ; N uni23AB ; G 2374
+U 9132 ; WX 750 ; N uni23AC ; G 2375
+U 9133 ; WX 750 ; N uni23AD ; G 2376
+U 9134 ; WX 521 ; N uni23AE ; G 2377
+U 9143 ; WX 637 ; N uni23B7 ; G 2378
+U 9167 ; WX 945 ; N uni23CF ; G 2379
+U 9251 ; WX 764 ; N uni2423 ; G 2380
+U 9472 ; WX 602 ; N SF100000 ; G 2381
+U 9473 ; WX 602 ; N uni2501 ; G 2382
+U 9474 ; WX 602 ; N SF110000 ; G 2383
+U 9475 ; WX 602 ; N uni2503 ; G 2384
+U 9476 ; WX 602 ; N uni2504 ; G 2385
+U 9477 ; WX 602 ; N uni2505 ; G 2386
+U 9478 ; WX 602 ; N uni2506 ; G 2387
+U 9479 ; WX 602 ; N uni2507 ; G 2388
+U 9480 ; WX 602 ; N uni2508 ; G 2389
+U 9481 ; WX 602 ; N uni2509 ; G 2390
+U 9482 ; WX 602 ; N uni250A ; G 2391
+U 9483 ; WX 602 ; N uni250B ; G 2392
+U 9484 ; WX 602 ; N SF010000 ; G 2393
+U 9485 ; WX 602 ; N uni250D ; G 2394
+U 9486 ; WX 602 ; N uni250E ; G 2395
+U 9487 ; WX 602 ; N uni250F ; G 2396
+U 9488 ; WX 602 ; N SF030000 ; G 2397
+U 9489 ; WX 602 ; N uni2511 ; G 2398
+U 9490 ; WX 602 ; N uni2512 ; G 2399
+U 9491 ; WX 602 ; N uni2513 ; G 2400
+U 9492 ; WX 602 ; N SF020000 ; G 2401
+U 9493 ; WX 602 ; N uni2515 ; G 2402
+U 9494 ; WX 602 ; N uni2516 ; G 2403
+U 9495 ; WX 602 ; N uni2517 ; G 2404
+U 9496 ; WX 602 ; N SF040000 ; G 2405
+U 9497 ; WX 602 ; N uni2519 ; G 2406
+U 9498 ; WX 602 ; N uni251A ; G 2407
+U 9499 ; WX 602 ; N uni251B ; G 2408
+U 9500 ; WX 602 ; N SF080000 ; G 2409
+U 9501 ; WX 602 ; N uni251D ; G 2410
+U 9502 ; WX 602 ; N uni251E ; G 2411
+U 9503 ; WX 602 ; N uni251F ; G 2412
+U 9504 ; WX 602 ; N uni2520 ; G 2413
+U 9505 ; WX 602 ; N uni2521 ; G 2414
+U 9506 ; WX 602 ; N uni2522 ; G 2415
+U 9507 ; WX 602 ; N uni2523 ; G 2416
+U 9508 ; WX 602 ; N SF090000 ; G 2417
+U 9509 ; WX 602 ; N uni2525 ; G 2418
+U 9510 ; WX 602 ; N uni2526 ; G 2419
+U 9511 ; WX 602 ; N uni2527 ; G 2420
+U 9512 ; WX 602 ; N uni2528 ; G 2421
+U 9513 ; WX 602 ; N uni2529 ; G 2422
+U 9514 ; WX 602 ; N uni252A ; G 2423
+U 9515 ; WX 602 ; N uni252B ; G 2424
+U 9516 ; WX 602 ; N SF060000 ; G 2425
+U 9517 ; WX 602 ; N uni252D ; G 2426
+U 9518 ; WX 602 ; N uni252E ; G 2427
+U 9519 ; WX 602 ; N uni252F ; G 2428
+U 9520 ; WX 602 ; N uni2530 ; G 2429
+U 9521 ; WX 602 ; N uni2531 ; G 2430
+U 9522 ; WX 602 ; N uni2532 ; G 2431
+U 9523 ; WX 602 ; N uni2533 ; G 2432
+U 9524 ; WX 602 ; N SF070000 ; G 2433
+U 9525 ; WX 602 ; N uni2535 ; G 2434
+U 9526 ; WX 602 ; N uni2536 ; G 2435
+U 9527 ; WX 602 ; N uni2537 ; G 2436
+U 9528 ; WX 602 ; N uni2538 ; G 2437
+U 9529 ; WX 602 ; N uni2539 ; G 2438
+U 9530 ; WX 602 ; N uni253A ; G 2439
+U 9531 ; WX 602 ; N uni253B ; G 2440
+U 9532 ; WX 602 ; N SF050000 ; G 2441
+U 9533 ; WX 602 ; N uni253D ; G 2442
+U 9534 ; WX 602 ; N uni253E ; G 2443
+U 9535 ; WX 602 ; N uni253F ; G 2444
+U 9536 ; WX 602 ; N uni2540 ; G 2445
+U 9537 ; WX 602 ; N uni2541 ; G 2446
+U 9538 ; WX 602 ; N uni2542 ; G 2447
+U 9539 ; WX 602 ; N uni2543 ; G 2448
+U 9540 ; WX 602 ; N uni2544 ; G 2449
+U 9541 ; WX 602 ; N uni2545 ; G 2450
+U 9542 ; WX 602 ; N uni2546 ; G 2451
+U 9543 ; WX 602 ; N uni2547 ; G 2452
+U 9544 ; WX 602 ; N uni2548 ; G 2453
+U 9545 ; WX 602 ; N uni2549 ; G 2454
+U 9546 ; WX 602 ; N uni254A ; G 2455
+U 9547 ; WX 602 ; N uni254B ; G 2456
+U 9548 ; WX 602 ; N uni254C ; G 2457
+U 9549 ; WX 602 ; N uni254D ; G 2458
+U 9550 ; WX 602 ; N uni254E ; G 2459
+U 9551 ; WX 602 ; N uni254F ; G 2460
+U 9552 ; WX 602 ; N SF430000 ; G 2461
+U 9553 ; WX 602 ; N SF240000 ; G 2462
+U 9554 ; WX 602 ; N SF510000 ; G 2463
+U 9555 ; WX 602 ; N SF520000 ; G 2464
+U 9556 ; WX 602 ; N SF390000 ; G 2465
+U 9557 ; WX 602 ; N SF220000 ; G 2466
+U 9558 ; WX 602 ; N SF210000 ; G 2467
+U 9559 ; WX 602 ; N SF250000 ; G 2468
+U 9560 ; WX 602 ; N SF500000 ; G 2469
+U 9561 ; WX 602 ; N SF490000 ; G 2470
+U 9562 ; WX 602 ; N SF380000 ; G 2471
+U 9563 ; WX 602 ; N SF280000 ; G 2472
+U 9564 ; WX 602 ; N SF270000 ; G 2473
+U 9565 ; WX 602 ; N SF260000 ; G 2474
+U 9566 ; WX 602 ; N SF360000 ; G 2475
+U 9567 ; WX 602 ; N SF370000 ; G 2476
+U 9568 ; WX 602 ; N SF420000 ; G 2477
+U 9569 ; WX 602 ; N SF190000 ; G 2478
+U 9570 ; WX 602 ; N SF200000 ; G 2479
+U 9571 ; WX 602 ; N SF230000 ; G 2480
+U 9572 ; WX 602 ; N SF470000 ; G 2481
+U 9573 ; WX 602 ; N SF480000 ; G 2482
+U 9574 ; WX 602 ; N SF410000 ; G 2483
+U 9575 ; WX 602 ; N SF450000 ; G 2484
+U 9576 ; WX 602 ; N SF460000 ; G 2485
+U 9577 ; WX 602 ; N SF400000 ; G 2486
+U 9578 ; WX 602 ; N SF540000 ; G 2487
+U 9579 ; WX 602 ; N SF530000 ; G 2488
+U 9580 ; WX 602 ; N SF440000 ; G 2489
+U 9581 ; WX 602 ; N uni256D ; G 2490
+U 9582 ; WX 602 ; N uni256E ; G 2491
+U 9583 ; WX 602 ; N uni256F ; G 2492
+U 9584 ; WX 602 ; N uni2570 ; G 2493
+U 9585 ; WX 602 ; N uni2571 ; G 2494
+U 9586 ; WX 602 ; N uni2572 ; G 2495
+U 9587 ; WX 602 ; N uni2573 ; G 2496
+U 9588 ; WX 602 ; N uni2574 ; G 2497
+U 9589 ; WX 602 ; N uni2575 ; G 2498
+U 9590 ; WX 602 ; N uni2576 ; G 2499
+U 9591 ; WX 602 ; N uni2577 ; G 2500
+U 9592 ; WX 602 ; N uni2578 ; G 2501
+U 9593 ; WX 602 ; N uni2579 ; G 2502
+U 9594 ; WX 602 ; N uni257A ; G 2503
+U 9595 ; WX 602 ; N uni257B ; G 2504
+U 9596 ; WX 602 ; N uni257C ; G 2505
+U 9597 ; WX 602 ; N uni257D ; G 2506
+U 9598 ; WX 602 ; N uni257E ; G 2507
+U 9599 ; WX 602 ; N uni257F ; G 2508
+U 9600 ; WX 769 ; N upblock ; G 2509
+U 9601 ; WX 769 ; N uni2581 ; G 2510
+U 9602 ; WX 769 ; N uni2582 ; G 2511
+U 9603 ; WX 769 ; N uni2583 ; G 2512
+U 9604 ; WX 769 ; N dnblock ; G 2513
+U 9605 ; WX 769 ; N uni2585 ; G 2514
+U 9606 ; WX 769 ; N uni2586 ; G 2515
+U 9607 ; WX 769 ; N uni2587 ; G 2516
+U 9608 ; WX 769 ; N block ; G 2517
+U 9609 ; WX 769 ; N uni2589 ; G 2518
+U 9610 ; WX 769 ; N uni258A ; G 2519
+U 9611 ; WX 769 ; N uni258B ; G 2520
+U 9612 ; WX 769 ; N lfblock ; G 2521
+U 9613 ; WX 769 ; N uni258D ; G 2522
+U 9614 ; WX 769 ; N uni258E ; G 2523
+U 9615 ; WX 769 ; N uni258F ; G 2524
+U 9616 ; WX 769 ; N rtblock ; G 2525
+U 9617 ; WX 769 ; N ltshade ; G 2526
+U 9618 ; WX 769 ; N shade ; G 2527
+U 9619 ; WX 769 ; N dkshade ; G 2528
+U 9620 ; WX 769 ; N uni2594 ; G 2529
+U 9621 ; WX 769 ; N uni2595 ; G 2530
+U 9622 ; WX 769 ; N uni2596 ; G 2531
+U 9623 ; WX 769 ; N uni2597 ; G 2532
+U 9624 ; WX 769 ; N uni2598 ; G 2533
+U 9625 ; WX 769 ; N uni2599 ; G 2534
+U 9626 ; WX 769 ; N uni259A ; G 2535
+U 9627 ; WX 769 ; N uni259B ; G 2536
+U 9628 ; WX 769 ; N uni259C ; G 2537
+U 9629 ; WX 769 ; N uni259D ; G 2538
+U 9630 ; WX 769 ; N uni259E ; G 2539
+U 9631 ; WX 769 ; N uni259F ; G 2540
+U 9632 ; WX 945 ; N filledbox ; G 2541
+U 9633 ; WX 945 ; N H22073 ; G 2542
+U 9634 ; WX 945 ; N uni25A2 ; G 2543
+U 9635 ; WX 945 ; N uni25A3 ; G 2544
+U 9636 ; WX 945 ; N uni25A4 ; G 2545
+U 9637 ; WX 945 ; N uni25A5 ; G 2546
+U 9638 ; WX 945 ; N uni25A6 ; G 2547
+U 9639 ; WX 945 ; N uni25A7 ; G 2548
+U 9640 ; WX 945 ; N uni25A8 ; G 2549
+U 9641 ; WX 945 ; N uni25A9 ; G 2550
+U 9642 ; WX 678 ; N H18543 ; G 2551
+U 9643 ; WX 678 ; N H18551 ; G 2552
+U 9644 ; WX 945 ; N filledrect ; G 2553
+U 9645 ; WX 945 ; N uni25AD ; G 2554
+U 9646 ; WX 550 ; N uni25AE ; G 2555
+U 9647 ; WX 550 ; N uni25AF ; G 2556
+U 9648 ; WX 769 ; N uni25B0 ; G 2557
+U 9649 ; WX 769 ; N uni25B1 ; G 2558
+U 9650 ; WX 769 ; N triagup ; G 2559
+U 9651 ; WX 769 ; N uni25B3 ; G 2560
+U 9652 ; WX 502 ; N uni25B4 ; G 2561
+U 9653 ; WX 502 ; N uni25B5 ; G 2562
+U 9654 ; WX 769 ; N uni25B6 ; G 2563
+U 9655 ; WX 769 ; N uni25B7 ; G 2564
+U 9656 ; WX 502 ; N uni25B8 ; G 2565
+U 9657 ; WX 502 ; N uni25B9 ; G 2566
+U 9658 ; WX 769 ; N triagrt ; G 2567
+U 9659 ; WX 769 ; N uni25BB ; G 2568
+U 9660 ; WX 769 ; N triagdn ; G 2569
+U 9661 ; WX 769 ; N uni25BD ; G 2570
+U 9662 ; WX 502 ; N uni25BE ; G 2571
+U 9663 ; WX 502 ; N uni25BF ; G 2572
+U 9664 ; WX 769 ; N uni25C0 ; G 2573
+U 9665 ; WX 769 ; N uni25C1 ; G 2574
+U 9666 ; WX 502 ; N uni25C2 ; G 2575
+U 9667 ; WX 502 ; N uni25C3 ; G 2576
+U 9668 ; WX 769 ; N triaglf ; G 2577
+U 9669 ; WX 769 ; N uni25C5 ; G 2578
+U 9670 ; WX 769 ; N uni25C6 ; G 2579
+U 9671 ; WX 769 ; N uni25C7 ; G 2580
+U 9672 ; WX 769 ; N uni25C8 ; G 2581
+U 9673 ; WX 873 ; N uni25C9 ; G 2582
+U 9674 ; WX 494 ; N lozenge ; G 2583
+U 9675 ; WX 873 ; N circle ; G 2584
+U 9676 ; WX 873 ; N uni25CC ; G 2585
+U 9677 ; WX 873 ; N uni25CD ; G 2586
+U 9678 ; WX 873 ; N uni25CE ; G 2587
+U 9679 ; WX 873 ; N H18533 ; G 2588
+U 9680 ; WX 873 ; N uni25D0 ; G 2589
+U 9681 ; WX 873 ; N uni25D1 ; G 2590
+U 9682 ; WX 873 ; N uni25D2 ; G 2591
+U 9683 ; WX 873 ; N uni25D3 ; G 2592
+U 9684 ; WX 873 ; N uni25D4 ; G 2593
+U 9685 ; WX 873 ; N uni25D5 ; G 2594
+U 9686 ; WX 527 ; N uni25D6 ; G 2595
+U 9687 ; WX 527 ; N uni25D7 ; G 2596
+U 9688 ; WX 791 ; N invbullet ; G 2597
+U 9689 ; WX 970 ; N invcircle ; G 2598
+U 9690 ; WX 970 ; N uni25DA ; G 2599
+U 9691 ; WX 970 ; N uni25DB ; G 2600
+U 9692 ; WX 387 ; N uni25DC ; G 2601
+U 9693 ; WX 387 ; N uni25DD ; G 2602
+U 9694 ; WX 387 ; N uni25DE ; G 2603
+U 9695 ; WX 387 ; N uni25DF ; G 2604
+U 9696 ; WX 873 ; N uni25E0 ; G 2605
+U 9697 ; WX 873 ; N uni25E1 ; G 2606
+U 9698 ; WX 769 ; N uni25E2 ; G 2607
+U 9699 ; WX 769 ; N uni25E3 ; G 2608
+U 9700 ; WX 769 ; N uni25E4 ; G 2609
+U 9701 ; WX 769 ; N uni25E5 ; G 2610
+U 9702 ; WX 590 ; N openbullet ; G 2611
+U 9703 ; WX 945 ; N uni25E7 ; G 2612
+U 9704 ; WX 945 ; N uni25E8 ; G 2613
+U 9705 ; WX 945 ; N uni25E9 ; G 2614
+U 9706 ; WX 945 ; N uni25EA ; G 2615
+U 9707 ; WX 945 ; N uni25EB ; G 2616
+U 9708 ; WX 769 ; N uni25EC ; G 2617
+U 9709 ; WX 769 ; N uni25ED ; G 2618
+U 9710 ; WX 769 ; N uni25EE ; G 2619
+U 9711 ; WX 1119 ; N uni25EF ; G 2620
+U 9712 ; WX 945 ; N uni25F0 ; G 2621
+U 9713 ; WX 945 ; N uni25F1 ; G 2622
+U 9714 ; WX 945 ; N uni25F2 ; G 2623
+U 9715 ; WX 945 ; N uni25F3 ; G 2624
+U 9716 ; WX 873 ; N uni25F4 ; G 2625
+U 9717 ; WX 873 ; N uni25F5 ; G 2626
+U 9718 ; WX 873 ; N uni25F6 ; G 2627
+U 9719 ; WX 873 ; N uni25F7 ; G 2628
+U 9720 ; WX 769 ; N uni25F8 ; G 2629
+U 9721 ; WX 769 ; N uni25F9 ; G 2630
+U 9722 ; WX 769 ; N uni25FA ; G 2631
+U 9723 ; WX 830 ; N uni25FB ; G 2632
+U 9724 ; WX 830 ; N uni25FC ; G 2633
+U 9725 ; WX 732 ; N uni25FD ; G 2634
+U 9726 ; WX 732 ; N uni25FE ; G 2635
+U 9727 ; WX 769 ; N uni25FF ; G 2636
+U 9728 ; WX 896 ; N uni2600 ; G 2637
+U 9784 ; WX 896 ; N uni2638 ; G 2638
+U 9785 ; WX 896 ; N uni2639 ; G 2639
+U 9786 ; WX 896 ; N smileface ; G 2640
+U 9787 ; WX 896 ; N invsmileface ; G 2641
+U 9788 ; WX 896 ; N sun ; G 2642
+U 9791 ; WX 614 ; N uni263F ; G 2643
+U 9792 ; WX 731 ; N female ; G 2644
+U 9793 ; WX 731 ; N uni2641 ; G 2645
+U 9794 ; WX 896 ; N male ; G 2646
+U 9795 ; WX 896 ; N uni2643 ; G 2647
+U 9796 ; WX 896 ; N uni2644 ; G 2648
+U 9797 ; WX 896 ; N uni2645 ; G 2649
+U 9798 ; WX 896 ; N uni2646 ; G 2650
+U 9799 ; WX 896 ; N uni2647 ; G 2651
+U 9824 ; WX 896 ; N spade ; G 2652
+U 9825 ; WX 896 ; N uni2661 ; G 2653
+U 9826 ; WX 896 ; N uni2662 ; G 2654
+U 9827 ; WX 896 ; N club ; G 2655
+U 9828 ; WX 896 ; N uni2664 ; G 2656
+U 9829 ; WX 896 ; N heart ; G 2657
+U 9830 ; WX 896 ; N diamond ; G 2658
+U 9831 ; WX 896 ; N uni2667 ; G 2659
+U 9833 ; WX 472 ; N uni2669 ; G 2660
+U 9834 ; WX 638 ; N musicalnote ; G 2661
+U 9835 ; WX 896 ; N musicalnotedbl ; G 2662
+U 9836 ; WX 896 ; N uni266C ; G 2663
+U 9837 ; WX 472 ; N uni266D ; G 2664
+U 9838 ; WX 357 ; N uni266E ; G 2665
+U 9839 ; WX 484 ; N uni266F ; G 2666
+U 10145 ; WX 838 ; N uni27A1 ; G 2667
+U 10181 ; WX 390 ; N uni27C5 ; G 2668
+U 10182 ; WX 390 ; N uni27C6 ; G 2669
+U 10208 ; WX 494 ; N uni27E0 ; G 2670
+U 10216 ; WX 390 ; N uni27E8 ; G 2671
+U 10217 ; WX 390 ; N uni27E9 ; G 2672
+U 10224 ; WX 838 ; N uni27F0 ; G 2673
+U 10225 ; WX 838 ; N uni27F1 ; G 2674
+U 10226 ; WX 838 ; N uni27F2 ; G 2675
+U 10227 ; WX 838 ; N uni27F3 ; G 2676
+U 10228 ; WX 1033 ; N uni27F4 ; G 2677
+U 10229 ; WX 1434 ; N uni27F5 ; G 2678
+U 10230 ; WX 1434 ; N uni27F6 ; G 2679
+U 10231 ; WX 1434 ; N uni27F7 ; G 2680
+U 10232 ; WX 1434 ; N uni27F8 ; G 2681
+U 10233 ; WX 1434 ; N uni27F9 ; G 2682
+U 10234 ; WX 1434 ; N uni27FA ; G 2683
+U 10235 ; WX 1434 ; N uni27FB ; G 2684
+U 10236 ; WX 1434 ; N uni27FC ; G 2685
+U 10237 ; WX 1434 ; N uni27FD ; G 2686
+U 10238 ; WX 1434 ; N uni27FE ; G 2687
+U 10239 ; WX 1434 ; N uni27FF ; G 2688
+U 10240 ; WX 732 ; N uni2800 ; G 2689
+U 10241 ; WX 732 ; N uni2801 ; G 2690
+U 10242 ; WX 732 ; N uni2802 ; G 2691
+U 10243 ; WX 732 ; N uni2803 ; G 2692
+U 10244 ; WX 732 ; N uni2804 ; G 2693
+U 10245 ; WX 732 ; N uni2805 ; G 2694
+U 10246 ; WX 732 ; N uni2806 ; G 2695
+U 10247 ; WX 732 ; N uni2807 ; G 2696
+U 10248 ; WX 732 ; N uni2808 ; G 2697
+U 10249 ; WX 732 ; N uni2809 ; G 2698
+U 10250 ; WX 732 ; N uni280A ; G 2699
+U 10251 ; WX 732 ; N uni280B ; G 2700
+U 10252 ; WX 732 ; N uni280C ; G 2701
+U 10253 ; WX 732 ; N uni280D ; G 2702
+U 10254 ; WX 732 ; N uni280E ; G 2703
+U 10255 ; WX 732 ; N uni280F ; G 2704
+U 10256 ; WX 732 ; N uni2810 ; G 2705
+U 10257 ; WX 732 ; N uni2811 ; G 2706
+U 10258 ; WX 732 ; N uni2812 ; G 2707
+U 10259 ; WX 732 ; N uni2813 ; G 2708
+U 10260 ; WX 732 ; N uni2814 ; G 2709
+U 10261 ; WX 732 ; N uni2815 ; G 2710
+U 10262 ; WX 732 ; N uni2816 ; G 2711
+U 10263 ; WX 732 ; N uni2817 ; G 2712
+U 10264 ; WX 732 ; N uni2818 ; G 2713
+U 10265 ; WX 732 ; N uni2819 ; G 2714
+U 10266 ; WX 732 ; N uni281A ; G 2715
+U 10267 ; WX 732 ; N uni281B ; G 2716
+U 10268 ; WX 732 ; N uni281C ; G 2717
+U 10269 ; WX 732 ; N uni281D ; G 2718
+U 10270 ; WX 732 ; N uni281E ; G 2719
+U 10271 ; WX 732 ; N uni281F ; G 2720
+U 10272 ; WX 732 ; N uni2820 ; G 2721
+U 10273 ; WX 732 ; N uni2821 ; G 2722
+U 10274 ; WX 732 ; N uni2822 ; G 2723
+U 10275 ; WX 732 ; N uni2823 ; G 2724
+U 10276 ; WX 732 ; N uni2824 ; G 2725
+U 10277 ; WX 732 ; N uni2825 ; G 2726
+U 10278 ; WX 732 ; N uni2826 ; G 2727
+U 10279 ; WX 732 ; N uni2827 ; G 2728
+U 10280 ; WX 732 ; N uni2828 ; G 2729
+U 10281 ; WX 732 ; N uni2829 ; G 2730
+U 10282 ; WX 732 ; N uni282A ; G 2731
+U 10283 ; WX 732 ; N uni282B ; G 2732
+U 10284 ; WX 732 ; N uni282C ; G 2733
+U 10285 ; WX 732 ; N uni282D ; G 2734
+U 10286 ; WX 732 ; N uni282E ; G 2735
+U 10287 ; WX 732 ; N uni282F ; G 2736
+U 10288 ; WX 732 ; N uni2830 ; G 2737
+U 10289 ; WX 732 ; N uni2831 ; G 2738
+U 10290 ; WX 732 ; N uni2832 ; G 2739
+U 10291 ; WX 732 ; N uni2833 ; G 2740
+U 10292 ; WX 732 ; N uni2834 ; G 2741
+U 10293 ; WX 732 ; N uni2835 ; G 2742
+U 10294 ; WX 732 ; N uni2836 ; G 2743
+U 10295 ; WX 732 ; N uni2837 ; G 2744
+U 10296 ; WX 732 ; N uni2838 ; G 2745
+U 10297 ; WX 732 ; N uni2839 ; G 2746
+U 10298 ; WX 732 ; N uni283A ; G 2747
+U 10299 ; WX 732 ; N uni283B ; G 2748
+U 10300 ; WX 732 ; N uni283C ; G 2749
+U 10301 ; WX 732 ; N uni283D ; G 2750
+U 10302 ; WX 732 ; N uni283E ; G 2751
+U 10303 ; WX 732 ; N uni283F ; G 2752
+U 10304 ; WX 732 ; N uni2840 ; G 2753
+U 10305 ; WX 732 ; N uni2841 ; G 2754
+U 10306 ; WX 732 ; N uni2842 ; G 2755
+U 10307 ; WX 732 ; N uni2843 ; G 2756
+U 10308 ; WX 732 ; N uni2844 ; G 2757
+U 10309 ; WX 732 ; N uni2845 ; G 2758
+U 10310 ; WX 732 ; N uni2846 ; G 2759
+U 10311 ; WX 732 ; N uni2847 ; G 2760
+U 10312 ; WX 732 ; N uni2848 ; G 2761
+U 10313 ; WX 732 ; N uni2849 ; G 2762
+U 10314 ; WX 732 ; N uni284A ; G 2763
+U 10315 ; WX 732 ; N uni284B ; G 2764
+U 10316 ; WX 732 ; N uni284C ; G 2765
+U 10317 ; WX 732 ; N uni284D ; G 2766
+U 10318 ; WX 732 ; N uni284E ; G 2767
+U 10319 ; WX 732 ; N uni284F ; G 2768
+U 10320 ; WX 732 ; N uni2850 ; G 2769
+U 10321 ; WX 732 ; N uni2851 ; G 2770
+U 10322 ; WX 732 ; N uni2852 ; G 2771
+U 10323 ; WX 732 ; N uni2853 ; G 2772
+U 10324 ; WX 732 ; N uni2854 ; G 2773
+U 10325 ; WX 732 ; N uni2855 ; G 2774
+U 10326 ; WX 732 ; N uni2856 ; G 2775
+U 10327 ; WX 732 ; N uni2857 ; G 2776
+U 10328 ; WX 732 ; N uni2858 ; G 2777
+U 10329 ; WX 732 ; N uni2859 ; G 2778
+U 10330 ; WX 732 ; N uni285A ; G 2779
+U 10331 ; WX 732 ; N uni285B ; G 2780
+U 10332 ; WX 732 ; N uni285C ; G 2781
+U 10333 ; WX 732 ; N uni285D ; G 2782
+U 10334 ; WX 732 ; N uni285E ; G 2783
+U 10335 ; WX 732 ; N uni285F ; G 2784
+U 10336 ; WX 732 ; N uni2860 ; G 2785
+U 10337 ; WX 732 ; N uni2861 ; G 2786
+U 10338 ; WX 732 ; N uni2862 ; G 2787
+U 10339 ; WX 732 ; N uni2863 ; G 2788
+U 10340 ; WX 732 ; N uni2864 ; G 2789
+U 10341 ; WX 732 ; N uni2865 ; G 2790
+U 10342 ; WX 732 ; N uni2866 ; G 2791
+U 10343 ; WX 732 ; N uni2867 ; G 2792
+U 10344 ; WX 732 ; N uni2868 ; G 2793
+U 10345 ; WX 732 ; N uni2869 ; G 2794
+U 10346 ; WX 732 ; N uni286A ; G 2795
+U 10347 ; WX 732 ; N uni286B ; G 2796
+U 10348 ; WX 732 ; N uni286C ; G 2797
+U 10349 ; WX 732 ; N uni286D ; G 2798
+U 10350 ; WX 732 ; N uni286E ; G 2799
+U 10351 ; WX 732 ; N uni286F ; G 2800
+U 10352 ; WX 732 ; N uni2870 ; G 2801
+U 10353 ; WX 732 ; N uni2871 ; G 2802
+U 10354 ; WX 732 ; N uni2872 ; G 2803
+U 10355 ; WX 732 ; N uni2873 ; G 2804
+U 10356 ; WX 732 ; N uni2874 ; G 2805
+U 10357 ; WX 732 ; N uni2875 ; G 2806
+U 10358 ; WX 732 ; N uni2876 ; G 2807
+U 10359 ; WX 732 ; N uni2877 ; G 2808
+U 10360 ; WX 732 ; N uni2878 ; G 2809
+U 10361 ; WX 732 ; N uni2879 ; G 2810
+U 10362 ; WX 732 ; N uni287A ; G 2811
+U 10363 ; WX 732 ; N uni287B ; G 2812
+U 10364 ; WX 732 ; N uni287C ; G 2813
+U 10365 ; WX 732 ; N uni287D ; G 2814
+U 10366 ; WX 732 ; N uni287E ; G 2815
+U 10367 ; WX 732 ; N uni287F ; G 2816
+U 10368 ; WX 732 ; N uni2880 ; G 2817
+U 10369 ; WX 732 ; N uni2881 ; G 2818
+U 10370 ; WX 732 ; N uni2882 ; G 2819
+U 10371 ; WX 732 ; N uni2883 ; G 2820
+U 10372 ; WX 732 ; N uni2884 ; G 2821
+U 10373 ; WX 732 ; N uni2885 ; G 2822
+U 10374 ; WX 732 ; N uni2886 ; G 2823
+U 10375 ; WX 732 ; N uni2887 ; G 2824
+U 10376 ; WX 732 ; N uni2888 ; G 2825
+U 10377 ; WX 732 ; N uni2889 ; G 2826
+U 10378 ; WX 732 ; N uni288A ; G 2827
+U 10379 ; WX 732 ; N uni288B ; G 2828
+U 10380 ; WX 732 ; N uni288C ; G 2829
+U 10381 ; WX 732 ; N uni288D ; G 2830
+U 10382 ; WX 732 ; N uni288E ; G 2831
+U 10383 ; WX 732 ; N uni288F ; G 2832
+U 10384 ; WX 732 ; N uni2890 ; G 2833
+U 10385 ; WX 732 ; N uni2891 ; G 2834
+U 10386 ; WX 732 ; N uni2892 ; G 2835
+U 10387 ; WX 732 ; N uni2893 ; G 2836
+U 10388 ; WX 732 ; N uni2894 ; G 2837
+U 10389 ; WX 732 ; N uni2895 ; G 2838
+U 10390 ; WX 732 ; N uni2896 ; G 2839
+U 10391 ; WX 732 ; N uni2897 ; G 2840
+U 10392 ; WX 732 ; N uni2898 ; G 2841
+U 10393 ; WX 732 ; N uni2899 ; G 2842
+U 10394 ; WX 732 ; N uni289A ; G 2843
+U 10395 ; WX 732 ; N uni289B ; G 2844
+U 10396 ; WX 732 ; N uni289C ; G 2845
+U 10397 ; WX 732 ; N uni289D ; G 2846
+U 10398 ; WX 732 ; N uni289E ; G 2847
+U 10399 ; WX 732 ; N uni289F ; G 2848
+U 10400 ; WX 732 ; N uni28A0 ; G 2849
+U 10401 ; WX 732 ; N uni28A1 ; G 2850
+U 10402 ; WX 732 ; N uni28A2 ; G 2851
+U 10403 ; WX 732 ; N uni28A3 ; G 2852
+U 10404 ; WX 732 ; N uni28A4 ; G 2853
+U 10405 ; WX 732 ; N uni28A5 ; G 2854
+U 10406 ; WX 732 ; N uni28A6 ; G 2855
+U 10407 ; WX 732 ; N uni28A7 ; G 2856
+U 10408 ; WX 732 ; N uni28A8 ; G 2857
+U 10409 ; WX 732 ; N uni28A9 ; G 2858
+U 10410 ; WX 732 ; N uni28AA ; G 2859
+U 10411 ; WX 732 ; N uni28AB ; G 2860
+U 10412 ; WX 732 ; N uni28AC ; G 2861
+U 10413 ; WX 732 ; N uni28AD ; G 2862
+U 10414 ; WX 732 ; N uni28AE ; G 2863
+U 10415 ; WX 732 ; N uni28AF ; G 2864
+U 10416 ; WX 732 ; N uni28B0 ; G 2865
+U 10417 ; WX 732 ; N uni28B1 ; G 2866
+U 10418 ; WX 732 ; N uni28B2 ; G 2867
+U 10419 ; WX 732 ; N uni28B3 ; G 2868
+U 10420 ; WX 732 ; N uni28B4 ; G 2869
+U 10421 ; WX 732 ; N uni28B5 ; G 2870
+U 10422 ; WX 732 ; N uni28B6 ; G 2871
+U 10423 ; WX 732 ; N uni28B7 ; G 2872
+U 10424 ; WX 732 ; N uni28B8 ; G 2873
+U 10425 ; WX 732 ; N uni28B9 ; G 2874
+U 10426 ; WX 732 ; N uni28BA ; G 2875
+U 10427 ; WX 732 ; N uni28BB ; G 2876
+U 10428 ; WX 732 ; N uni28BC ; G 2877
+U 10429 ; WX 732 ; N uni28BD ; G 2878
+U 10430 ; WX 732 ; N uni28BE ; G 2879
+U 10431 ; WX 732 ; N uni28BF ; G 2880
+U 10432 ; WX 732 ; N uni28C0 ; G 2881
+U 10433 ; WX 732 ; N uni28C1 ; G 2882
+U 10434 ; WX 732 ; N uni28C2 ; G 2883
+U 10435 ; WX 732 ; N uni28C3 ; G 2884
+U 10436 ; WX 732 ; N uni28C4 ; G 2885
+U 10437 ; WX 732 ; N uni28C5 ; G 2886
+U 10438 ; WX 732 ; N uni28C6 ; G 2887
+U 10439 ; WX 732 ; N uni28C7 ; G 2888
+U 10440 ; WX 732 ; N uni28C8 ; G 2889
+U 10441 ; WX 732 ; N uni28C9 ; G 2890
+U 10442 ; WX 732 ; N uni28CA ; G 2891
+U 10443 ; WX 732 ; N uni28CB ; G 2892
+U 10444 ; WX 732 ; N uni28CC ; G 2893
+U 10445 ; WX 732 ; N uni28CD ; G 2894
+U 10446 ; WX 732 ; N uni28CE ; G 2895
+U 10447 ; WX 732 ; N uni28CF ; G 2896
+U 10448 ; WX 732 ; N uni28D0 ; G 2897
+U 10449 ; WX 732 ; N uni28D1 ; G 2898
+U 10450 ; WX 732 ; N uni28D2 ; G 2899
+U 10451 ; WX 732 ; N uni28D3 ; G 2900
+U 10452 ; WX 732 ; N uni28D4 ; G 2901
+U 10453 ; WX 732 ; N uni28D5 ; G 2902
+U 10454 ; WX 732 ; N uni28D6 ; G 2903
+U 10455 ; WX 732 ; N uni28D7 ; G 2904
+U 10456 ; WX 732 ; N uni28D8 ; G 2905
+U 10457 ; WX 732 ; N uni28D9 ; G 2906
+U 10458 ; WX 732 ; N uni28DA ; G 2907
+U 10459 ; WX 732 ; N uni28DB ; G 2908
+U 10460 ; WX 732 ; N uni28DC ; G 2909
+U 10461 ; WX 732 ; N uni28DD ; G 2910
+U 10462 ; WX 732 ; N uni28DE ; G 2911
+U 10463 ; WX 732 ; N uni28DF ; G 2912
+U 10464 ; WX 732 ; N uni28E0 ; G 2913
+U 10465 ; WX 732 ; N uni28E1 ; G 2914
+U 10466 ; WX 732 ; N uni28E2 ; G 2915
+U 10467 ; WX 732 ; N uni28E3 ; G 2916
+U 10468 ; WX 732 ; N uni28E4 ; G 2917
+U 10469 ; WX 732 ; N uni28E5 ; G 2918
+U 10470 ; WX 732 ; N uni28E6 ; G 2919
+U 10471 ; WX 732 ; N uni28E7 ; G 2920
+U 10472 ; WX 732 ; N uni28E8 ; G 2921
+U 10473 ; WX 732 ; N uni28E9 ; G 2922
+U 10474 ; WX 732 ; N uni28EA ; G 2923
+U 10475 ; WX 732 ; N uni28EB ; G 2924
+U 10476 ; WX 732 ; N uni28EC ; G 2925
+U 10477 ; WX 732 ; N uni28ED ; G 2926
+U 10478 ; WX 732 ; N uni28EE ; G 2927
+U 10479 ; WX 732 ; N uni28EF ; G 2928
+U 10480 ; WX 732 ; N uni28F0 ; G 2929
+U 10481 ; WX 732 ; N uni28F1 ; G 2930
+U 10482 ; WX 732 ; N uni28F2 ; G 2931
+U 10483 ; WX 732 ; N uni28F3 ; G 2932
+U 10484 ; WX 732 ; N uni28F4 ; G 2933
+U 10485 ; WX 732 ; N uni28F5 ; G 2934
+U 10486 ; WX 732 ; N uni28F6 ; G 2935
+U 10487 ; WX 732 ; N uni28F7 ; G 2936
+U 10488 ; WX 732 ; N uni28F8 ; G 2937
+U 10489 ; WX 732 ; N uni28F9 ; G 2938
+U 10490 ; WX 732 ; N uni28FA ; G 2939
+U 10491 ; WX 732 ; N uni28FB ; G 2940
+U 10492 ; WX 732 ; N uni28FC ; G 2941
+U 10493 ; WX 732 ; N uni28FD ; G 2942
+U 10494 ; WX 732 ; N uni28FE ; G 2943
+U 10495 ; WX 732 ; N uni28FF ; G 2944
+U 10496 ; WX 838 ; N uni2900 ; G 2945
+U 10497 ; WX 838 ; N uni2901 ; G 2946
+U 10498 ; WX 838 ; N uni2902 ; G 2947
+U 10499 ; WX 838 ; N uni2903 ; G 2948
+U 10500 ; WX 838 ; N uni2904 ; G 2949
+U 10501 ; WX 838 ; N uni2905 ; G 2950
+U 10502 ; WX 838 ; N uni2906 ; G 2951
+U 10503 ; WX 838 ; N uni2907 ; G 2952
+U 10504 ; WX 838 ; N uni2908 ; G 2953
+U 10505 ; WX 838 ; N uni2909 ; G 2954
+U 10506 ; WX 838 ; N uni290A ; G 2955
+U 10507 ; WX 838 ; N uni290B ; G 2956
+U 10508 ; WX 838 ; N uni290C ; G 2957
+U 10509 ; WX 838 ; N uni290D ; G 2958
+U 10510 ; WX 838 ; N uni290E ; G 2959
+U 10511 ; WX 838 ; N uni290F ; G 2960
+U 10512 ; WX 838 ; N uni2910 ; G 2961
+U 10513 ; WX 838 ; N uni2911 ; G 2962
+U 10514 ; WX 838 ; N uni2912 ; G 2963
+U 10515 ; WX 838 ; N uni2913 ; G 2964
+U 10516 ; WX 838 ; N uni2914 ; G 2965
+U 10517 ; WX 838 ; N uni2915 ; G 2966
+U 10518 ; WX 838 ; N uni2916 ; G 2967
+U 10519 ; WX 838 ; N uni2917 ; G 2968
+U 10520 ; WX 838 ; N uni2918 ; G 2969
+U 10521 ; WX 838 ; N uni2919 ; G 2970
+U 10522 ; WX 838 ; N uni291A ; G 2971
+U 10523 ; WX 838 ; N uni291B ; G 2972
+U 10524 ; WX 838 ; N uni291C ; G 2973
+U 10525 ; WX 838 ; N uni291D ; G 2974
+U 10526 ; WX 838 ; N uni291E ; G 2975
+U 10527 ; WX 838 ; N uni291F ; G 2976
+U 10528 ; WX 838 ; N uni2920 ; G 2977
+U 10529 ; WX 838 ; N uni2921 ; G 2978
+U 10530 ; WX 838 ; N uni2922 ; G 2979
+U 10531 ; WX 838 ; N uni2923 ; G 2980
+U 10532 ; WX 838 ; N uni2924 ; G 2981
+U 10533 ; WX 838 ; N uni2925 ; G 2982
+U 10534 ; WX 838 ; N uni2926 ; G 2983
+U 10535 ; WX 838 ; N uni2927 ; G 2984
+U 10536 ; WX 838 ; N uni2928 ; G 2985
+U 10537 ; WX 838 ; N uni2929 ; G 2986
+U 10538 ; WX 838 ; N uni292A ; G 2987
+U 10539 ; WX 838 ; N uni292B ; G 2988
+U 10540 ; WX 838 ; N uni292C ; G 2989
+U 10541 ; WX 838 ; N uni292D ; G 2990
+U 10542 ; WX 838 ; N uni292E ; G 2991
+U 10543 ; WX 838 ; N uni292F ; G 2992
+U 10544 ; WX 838 ; N uni2930 ; G 2993
+U 10545 ; WX 838 ; N uni2931 ; G 2994
+U 10546 ; WX 838 ; N uni2932 ; G 2995
+U 10547 ; WX 838 ; N uni2933 ; G 2996
+U 10548 ; WX 838 ; N uni2934 ; G 2997
+U 10549 ; WX 838 ; N uni2935 ; G 2998
+U 10550 ; WX 838 ; N uni2936 ; G 2999
+U 10551 ; WX 838 ; N uni2937 ; G 3000
+U 10552 ; WX 838 ; N uni2938 ; G 3001
+U 10553 ; WX 838 ; N uni2939 ; G 3002
+U 10554 ; WX 838 ; N uni293A ; G 3003
+U 10555 ; WX 838 ; N uni293B ; G 3004
+U 10556 ; WX 838 ; N uni293C ; G 3005
+U 10557 ; WX 838 ; N uni293D ; G 3006
+U 10558 ; WX 838 ; N uni293E ; G 3007
+U 10559 ; WX 838 ; N uni293F ; G 3008
+U 10560 ; WX 838 ; N uni2940 ; G 3009
+U 10561 ; WX 838 ; N uni2941 ; G 3010
+U 10562 ; WX 838 ; N uni2942 ; G 3011
+U 10563 ; WX 838 ; N uni2943 ; G 3012
+U 10564 ; WX 838 ; N uni2944 ; G 3013
+U 10565 ; WX 838 ; N uni2945 ; G 3014
+U 10566 ; WX 838 ; N uni2946 ; G 3015
+U 10567 ; WX 838 ; N uni2947 ; G 3016
+U 10568 ; WX 838 ; N uni2948 ; G 3017
+U 10569 ; WX 838 ; N uni2949 ; G 3018
+U 10570 ; WX 838 ; N uni294A ; G 3019
+U 10571 ; WX 838 ; N uni294B ; G 3020
+U 10572 ; WX 838 ; N uni294C ; G 3021
+U 10573 ; WX 838 ; N uni294D ; G 3022
+U 10574 ; WX 838 ; N uni294E ; G 3023
+U 10575 ; WX 838 ; N uni294F ; G 3024
+U 10576 ; WX 838 ; N uni2950 ; G 3025
+U 10577 ; WX 838 ; N uni2951 ; G 3026
+U 10578 ; WX 838 ; N uni2952 ; G 3027
+U 10579 ; WX 838 ; N uni2953 ; G 3028
+U 10580 ; WX 838 ; N uni2954 ; G 3029
+U 10581 ; WX 838 ; N uni2955 ; G 3030
+U 10582 ; WX 838 ; N uni2956 ; G 3031
+U 10583 ; WX 838 ; N uni2957 ; G 3032
+U 10584 ; WX 838 ; N uni2958 ; G 3033
+U 10585 ; WX 838 ; N uni2959 ; G 3034
+U 10586 ; WX 838 ; N uni295A ; G 3035
+U 10587 ; WX 838 ; N uni295B ; G 3036
+U 10588 ; WX 838 ; N uni295C ; G 3037
+U 10589 ; WX 838 ; N uni295D ; G 3038
+U 10590 ; WX 838 ; N uni295E ; G 3039
+U 10591 ; WX 838 ; N uni295F ; G 3040
+U 10592 ; WX 838 ; N uni2960 ; G 3041
+U 10593 ; WX 838 ; N uni2961 ; G 3042
+U 10594 ; WX 838 ; N uni2962 ; G 3043
+U 10595 ; WX 838 ; N uni2963 ; G 3044
+U 10596 ; WX 838 ; N uni2964 ; G 3045
+U 10597 ; WX 838 ; N uni2965 ; G 3046
+U 10598 ; WX 838 ; N uni2966 ; G 3047
+U 10599 ; WX 838 ; N uni2967 ; G 3048
+U 10600 ; WX 838 ; N uni2968 ; G 3049
+U 10601 ; WX 838 ; N uni2969 ; G 3050
+U 10602 ; WX 838 ; N uni296A ; G 3051
+U 10603 ; WX 838 ; N uni296B ; G 3052
+U 10604 ; WX 838 ; N uni296C ; G 3053
+U 10605 ; WX 838 ; N uni296D ; G 3054
+U 10606 ; WX 838 ; N uni296E ; G 3055
+U 10607 ; WX 838 ; N uni296F ; G 3056
+U 10608 ; WX 838 ; N uni2970 ; G 3057
+U 10609 ; WX 838 ; N uni2971 ; G 3058
+U 10610 ; WX 838 ; N uni2972 ; G 3059
+U 10611 ; WX 838 ; N uni2973 ; G 3060
+U 10612 ; WX 838 ; N uni2974 ; G 3061
+U 10613 ; WX 838 ; N uni2975 ; G 3062
+U 10614 ; WX 838 ; N uni2976 ; G 3063
+U 10615 ; WX 981 ; N uni2977 ; G 3064
+U 10616 ; WX 838 ; N uni2978 ; G 3065
+U 10617 ; WX 838 ; N uni2979 ; G 3066
+U 10618 ; WX 984 ; N uni297A ; G 3067
+U 10619 ; WX 838 ; N uni297B ; G 3068
+U 10620 ; WX 838 ; N uni297C ; G 3069
+U 10621 ; WX 838 ; N uni297D ; G 3070
+U 10622 ; WX 838 ; N uni297E ; G 3071
+U 10623 ; WX 838 ; N uni297F ; G 3072
+U 10731 ; WX 494 ; N uni29EB ; G 3073
+U 10764 ; WX 1513 ; N uni2A0C ; G 3074
+U 10765 ; WX 521 ; N uni2A0D ; G 3075
+U 10766 ; WX 521 ; N uni2A0E ; G 3076
+U 10799 ; WX 838 ; N uni2A2F ; G 3077
+U 10858 ; WX 838 ; N uni2A6A ; G 3078
+U 10859 ; WX 838 ; N uni2A6B ; G 3079
+U 11008 ; WX 838 ; N uni2B00 ; G 3080
+U 11009 ; WX 838 ; N uni2B01 ; G 3081
+U 11010 ; WX 838 ; N uni2B02 ; G 3082
+U 11011 ; WX 838 ; N uni2B03 ; G 3083
+U 11012 ; WX 838 ; N uni2B04 ; G 3084
+U 11013 ; WX 838 ; N uni2B05 ; G 3085
+U 11014 ; WX 838 ; N uni2B06 ; G 3086
+U 11015 ; WX 838 ; N uni2B07 ; G 3087
+U 11016 ; WX 838 ; N uni2B08 ; G 3088
+U 11017 ; WX 838 ; N uni2B09 ; G 3089
+U 11018 ; WX 838 ; N uni2B0A ; G 3090
+U 11019 ; WX 838 ; N uni2B0B ; G 3091
+U 11020 ; WX 838 ; N uni2B0C ; G 3092
+U 11021 ; WX 838 ; N uni2B0D ; G 3093
+U 11022 ; WX 838 ; N uni2B0E ; G 3094
+U 11023 ; WX 838 ; N uni2B0F ; G 3095
+U 11024 ; WX 838 ; N uni2B10 ; G 3096
+U 11025 ; WX 838 ; N uni2B11 ; G 3097
+U 11026 ; WX 945 ; N uni2B12 ; G 3098
+U 11027 ; WX 945 ; N uni2B13 ; G 3099
+U 11028 ; WX 945 ; N uni2B14 ; G 3100
+U 11029 ; WX 945 ; N uni2B15 ; G 3101
+U 11030 ; WX 769 ; N uni2B16 ; G 3102
+U 11031 ; WX 769 ; N uni2B17 ; G 3103
+U 11032 ; WX 769 ; N uni2B18 ; G 3104
+U 11033 ; WX 769 ; N uni2B19 ; G 3105
+U 11034 ; WX 945 ; N uni2B1A ; G 3106
+U 11360 ; WX 664 ; N uni2C60 ; G 3107
+U 11361 ; WX 320 ; N uni2C61 ; G 3108
+U 11363 ; WX 673 ; N uni2C63 ; G 3109
+U 11364 ; WX 753 ; N uni2C64 ; G 3110
+U 11367 ; WX 872 ; N uni2C67 ; G 3111
+U 11368 ; WX 644 ; N uni2C68 ; G 3112
+U 11369 ; WX 747 ; N uni2C69 ; G 3113
+U 11370 ; WX 606 ; N uni2C6A ; G 3114
+U 11371 ; WX 695 ; N uni2C6B ; G 3115
+U 11372 ; WX 527 ; N uni2C6C ; G 3116
+U 11373 ; WX 782 ; N uni2C6D ; G 3117
+U 11374 ; WX 1024 ; N uni2C6E ; G 3118
+U 11375 ; WX 722 ; N uni2C6F ; G 3119
+U 11376 ; WX 782 ; N uni2C70 ; G 3120
+U 11377 ; WX 663 ; N uni2C71 ; G 3121
+U 11378 ; WX 1130 ; N uni2C72 ; G 3122
+U 11379 ; WX 939 ; N uni2C73 ; G 3123
+U 11381 ; WX 740 ; N uni2C75 ; G 3124
+U 11382 ; WX 556 ; N uni2C76 ; G 3125
+U 11383 ; WX 700 ; N uni2C77 ; G 3126
+U 11385 ; WX 501 ; N uni2C79 ; G 3127
+U 11386 ; WX 602 ; N uni2C7A ; G 3128
+U 11387 ; WX 553 ; N uni2C7B ; G 3129
+U 11388 ; WX 264 ; N uni2C7C ; G 3130
+U 11389 ; WX 455 ; N uni2C7D ; G 3131
+U 11390 ; WX 685 ; N uni2C7E ; G 3132
+U 11391 ; WX 695 ; N uni2C7F ; G 3133
+U 11520 ; WX 773 ; N uni2D00 ; G 3134
+U 11521 ; WX 635 ; N uni2D01 ; G 3135
+U 11522 ; WX 633 ; N uni2D02 ; G 3136
+U 11523 ; WX 658 ; N uni2D03 ; G 3137
+U 11524 ; WX 631 ; N uni2D04 ; G 3138
+U 11525 ; WX 962 ; N uni2D05 ; G 3139
+U 11526 ; WX 756 ; N uni2D06 ; G 3140
+U 11527 ; WX 960 ; N uni2D07 ; G 3141
+U 11528 ; WX 617 ; N uni2D08 ; G 3142
+U 11529 ; WX 646 ; N uni2D09 ; G 3143
+U 11530 ; WX 962 ; N uni2D0A ; G 3144
+U 11531 ; WX 632 ; N uni2D0B ; G 3145
+U 11532 ; WX 646 ; N uni2D0C ; G 3146
+U 11533 ; WX 962 ; N uni2D0D ; G 3147
+U 11534 ; WX 645 ; N uni2D0E ; G 3148
+U 11535 ; WX 866 ; N uni2D0F ; G 3149
+U 11536 ; WX 961 ; N uni2D10 ; G 3150
+U 11537 ; WX 645 ; N uni2D11 ; G 3151
+U 11538 ; WX 645 ; N uni2D12 ; G 3152
+U 11539 ; WX 959 ; N uni2D13 ; G 3153
+U 11540 ; WX 945 ; N uni2D14 ; G 3154
+U 11541 ; WX 863 ; N uni2D15 ; G 3155
+U 11542 ; WX 644 ; N uni2D16 ; G 3156
+U 11543 ; WX 646 ; N uni2D17 ; G 3157
+U 11544 ; WX 645 ; N uni2D18 ; G 3158
+U 11545 ; WX 649 ; N uni2D19 ; G 3159
+U 11546 ; WX 688 ; N uni2D1A ; G 3160
+U 11547 ; WX 634 ; N uni2D1B ; G 3161
+U 11548 ; WX 982 ; N uni2D1C ; G 3162
+U 11549 ; WX 681 ; N uni2D1D ; G 3163
+U 11550 ; WX 676 ; N uni2D1E ; G 3164
+U 11551 ; WX 852 ; N uni2D1F ; G 3165
+U 11552 ; WX 957 ; N uni2D20 ; G 3166
+U 11553 ; WX 632 ; N uni2D21 ; G 3167
+U 11554 ; WX 645 ; N uni2D22 ; G 3168
+U 11555 ; WX 646 ; N uni2D23 ; G 3169
+U 11556 ; WX 749 ; N uni2D24 ; G 3170
+U 11557 ; WX 914 ; N uni2D25 ; G 3171
+U 11800 ; WX 536 ; N uni2E18 ; G 3172
+U 11807 ; WX 838 ; N uni2E1F ; G 3173
+U 11810 ; WX 390 ; N uni2E22 ; G 3174
+U 11811 ; WX 390 ; N uni2E23 ; G 3175
+U 11812 ; WX 390 ; N uni2E24 ; G 3176
+U 11813 ; WX 390 ; N uni2E25 ; G 3177
+U 11822 ; WX 536 ; N uni2E2E ; G 3178
+U 42564 ; WX 685 ; N uniA644 ; G 3179
+U 42565 ; WX 513 ; N uniA645 ; G 3180
+U 42566 ; WX 395 ; N uniA646 ; G 3181
+U 42567 ; WX 392 ; N uniA647 ; G 3182
+U 42576 ; WX 1104 ; N uniA650 ; G 3183
+U 42577 ; WX 888 ; N uniA651 ; G 3184
+U 42580 ; WX 1193 ; N uniA654 ; G 3185
+U 42581 ; WX 871 ; N uniA655 ; G 3186
+U 42582 ; WX 1140 ; N uniA656 ; G 3187
+U 42583 ; WX 899 ; N uniA657 ; G 3188
+U 42648 ; WX 1416 ; N uniA698 ; G 3189
+U 42649 ; WX 999 ; N uniA699 ; G 3190
+U 42760 ; WX 493 ; N uniA708 ; G 3191
+U 42761 ; WX 493 ; N uniA709 ; G 3192
+U 42762 ; WX 493 ; N uniA70A ; G 3193
+U 42763 ; WX 493 ; N uniA70B ; G 3194
+U 42764 ; WX 493 ; N uniA70C ; G 3195
+U 42765 ; WX 493 ; N uniA70D ; G 3196
+U 42766 ; WX 493 ; N uniA70E ; G 3197
+U 42767 ; WX 493 ; N uniA70F ; G 3198
+U 42768 ; WX 493 ; N uniA710 ; G 3199
+U 42769 ; WX 493 ; N uniA711 ; G 3200
+U 42770 ; WX 493 ; N uniA712 ; G 3201
+U 42771 ; WX 493 ; N uniA713 ; G 3202
+U 42772 ; WX 493 ; N uniA714 ; G 3203
+U 42773 ; WX 493 ; N uniA715 ; G 3204
+U 42774 ; WX 493 ; N uniA716 ; G 3205
+U 42779 ; WX 369 ; N uniA71B ; G 3206
+U 42780 ; WX 369 ; N uniA71C ; G 3207
+U 42781 ; WX 253 ; N uniA71D ; G 3208
+U 42782 ; WX 253 ; N uniA71E ; G 3209
+U 42783 ; WX 253 ; N uniA71F ; G 3210
+U 42790 ; WX 872 ; N uniA726 ; G 3211
+U 42791 ; WX 634 ; N uniA727 ; G 3212
+U 42792 ; WX 843 ; N uniA728 ; G 3213
+U 42793 ; WX 754 ; N uniA729 ; G 3214
+U 42794 ; WX 612 ; N uniA72A ; G 3215
+U 42795 ; WX 560 ; N uniA72B ; G 3216
+U 42796 ; WX 548 ; N uniA72C ; G 3217
+U 42797 ; WX 531 ; N uniA72D ; G 3218
+U 42798 ; WX 629 ; N uniA72E ; G 3219
+U 42799 ; WX 610 ; N uniA72F ; G 3220
+U 42800 ; WX 514 ; N uniA730 ; G 3221
+U 42801 ; WX 513 ; N uniA731 ; G 3222
+U 42802 ; WX 1195 ; N uniA732 ; G 3223
+U 42803 ; WX 944 ; N uniA733 ; G 3224
+U 42804 ; WX 1226 ; N uniA734 ; G 3225
+U 42805 ; WX 950 ; N uniA735 ; G 3226
+U 42806 ; WX 1149 ; N uniA736 ; G 3227
+U 42807 ; WX 934 ; N uniA737 ; G 3228
+U 42808 ; WX 968 ; N uniA738 ; G 3229
+U 42809 ; WX 784 ; N uniA739 ; G 3230
+U 42810 ; WX 968 ; N uniA73A ; G 3231
+U 42811 ; WX 784 ; N uniA73B ; G 3232
+U 42812 ; WX 962 ; N uniA73C ; G 3233
+U 42813 ; WX 824 ; N uniA73D ; G 3234
+U 42814 ; WX 765 ; N uniA73E ; G 3235
+U 42815 ; WX 560 ; N uniA73F ; G 3236
+U 42816 ; WX 774 ; N uniA740 ; G 3237
+U 42817 ; WX 625 ; N uniA741 ; G 3238
+U 42822 ; WX 787 ; N uniA746 ; G 3239
+U 42823 ; WX 434 ; N uniA747 ; G 3240
+U 42826 ; WX 932 ; N uniA74A ; G 3241
+U 42827 ; WX 711 ; N uniA74B ; G 3242
+U 42830 ; WX 1416 ; N uniA74E ; G 3243
+U 42831 ; WX 999 ; N uniA74F ; G 3244
+U 42856 ; WX 707 ; N uniA768 ; G 3245
+U 42857 ; WX 610 ; N uniA769 ; G 3246
+U 42875 ; WX 612 ; N uniA77B ; G 3247
+U 42876 ; WX 478 ; N uniA77C ; G 3248
+U 42880 ; WX 664 ; N uniA780 ; G 3249
+U 42881 ; WX 320 ; N uniA781 ; G 3250
+U 42882 ; WX 843 ; N uniA782 ; G 3251
+U 42883 ; WX 644 ; N uniA783 ; G 3252
+U 42884 ; WX 612 ; N uniA784 ; G 3253
+U 42885 ; WX 478 ; N uniA785 ; G 3254
+U 42886 ; WX 765 ; N uniA786 ; G 3255
+U 42887 ; WX 560 ; N uniA787 ; G 3256
+U 42891 ; WX 402 ; N uniA78B ; G 3257
+U 42892 ; WX 275 ; N uniA78C ; G 3258
+U 42893 ; WX 773 ; N uniA78D ; G 3259
+U 42896 ; WX 875 ; N uniA790 ; G 3260
+U 42897 ; WX 644 ; N uniA791 ; G 3261
+U 42922 ; WX 872 ; N uniA7AA ; G 3262
+U 43000 ; WX 549 ; N uniA7F8 ; G 3263
+U 43001 ; WX 623 ; N uniA7F9 ; G 3264
+U 43002 ; WX 957 ; N uniA7FA ; G 3265
+U 43003 ; WX 694 ; N uniA7FB ; G 3266
+U 43004 ; WX 673 ; N uniA7FC ; G 3267
+U 43005 ; WX 1024 ; N uniA7FD ; G 3268
+U 43006 ; WX 395 ; N uniA7FE ; G 3269
+U 43007 ; WX 1201 ; N uniA7FF ; G 3270
+U 62464 ; WX 654 ; N uniF400 ; G 3271
+U 62465 ; WX 665 ; N uniF401 ; G 3272
+U 62466 ; WX 714 ; N uniF402 ; G 3273
+U 62467 ; WX 947 ; N uniF403 ; G 3274
+U 62468 ; WX 665 ; N uniF404 ; G 3275
+U 62469 ; WX 659 ; N uniF405 ; G 3276
+U 62470 ; WX 725 ; N uniF406 ; G 3277
+U 62471 ; WX 986 ; N uniF407 ; G 3278
+U 62472 ; WX 665 ; N uniF408 ; G 3279
+U 62473 ; WX 665 ; N uniF409 ; G 3280
+U 62474 ; WX 1257 ; N uniF40A ; G 3281
+U 62475 ; WX 683 ; N uniF40B ; G 3282
+U 62476 ; WX 682 ; N uniF40C ; G 3283
+U 62477 ; WX 953 ; N uniF40D ; G 3284
+U 62478 ; WX 665 ; N uniF40E ; G 3285
+U 62479 ; WX 682 ; N uniF40F ; G 3286
+U 62480 ; WX 999 ; N uniF410 ; G 3287
+U 62481 ; WX 746 ; N uniF411 ; G 3288
+U 62482 ; WX 798 ; N uniF412 ; G 3289
+U 62483 ; WX 748 ; N uniF413 ; G 3290
+U 62484 ; WX 944 ; N uniF414 ; G 3291
+U 62485 ; WX 681 ; N uniF415 ; G 3292
+U 62486 ; WX 936 ; N uniF416 ; G 3293
+U 62487 ; WX 680 ; N uniF417 ; G 3294
+U 62488 ; WX 688 ; N uniF418 ; G 3295
+U 62489 ; WX 682 ; N uniF419 ; G 3296
+U 62490 ; WX 729 ; N uniF41A ; G 3297
+U 62491 ; WX 682 ; N uniF41B ; G 3298
+U 62492 ; WX 688 ; N uniF41C ; G 3299
+U 62493 ; WX 666 ; N uniF41D ; G 3300
+U 62494 ; WX 729 ; N uniF41E ; G 3301
+U 62495 ; WX 884 ; N uniF41F ; G 3302
+U 62496 ; WX 665 ; N uniF420 ; G 3303
+U 62497 ; WX 706 ; N uniF421 ; G 3304
+U 62498 ; WX 666 ; N uniF422 ; G 3305
+U 62499 ; WX 665 ; N uniF423 ; G 3306
+U 62500 ; WX 665 ; N uniF424 ; G 3307
+U 62501 ; WX 722 ; N uniF425 ; G 3308
+U 62502 ; WX 961 ; N uniF426 ; G 3309
+U 62504 ; WX 904 ; N uniF428 ; G 3310
+U 63173 ; WX 602 ; N uniF6C5 ; G 3311
+U 63185 ; WX 500 ; N cyrBreve ; G 3312
+U 63188 ; WX 500 ; N cyrbreve ; G 3313
+U 64256 ; WX 710 ; N uniFB00 ; G 3314
+U 64257 ; WX 667 ; N fi ; G 3315
+U 64258 ; WX 667 ; N fl ; G 3316
+U 64259 ; WX 1028 ; N uniFB03 ; G 3317
+U 64260 ; WX 1030 ; N uniFB04 ; G 3318
+U 64261 ; WX 771 ; N uniFB05 ; G 3319
+U 64262 ; WX 933 ; N uniFB06 ; G 3320
+U 65024 ; WX 0 ; N uniFE00 ; G 3321
+U 65025 ; WX 0 ; N uniFE01 ; G 3322
+U 65026 ; WX 0 ; N uniFE02 ; G 3323
+U 65027 ; WX 0 ; N uniFE03 ; G 3324
+U 65028 ; WX 0 ; N uniFE04 ; G 3325
+U 65029 ; WX 0 ; N uniFE05 ; G 3326
+U 65030 ; WX 0 ; N uniFE06 ; G 3327
+U 65031 ; WX 0 ; N uniFE07 ; G 3328
+U 65032 ; WX 0 ; N uniFE08 ; G 3329
+U 65033 ; WX 0 ; N uniFE09 ; G 3330
+U 65034 ; WX 0 ; N uniFE0A ; G 3331
+U 65035 ; WX 0 ; N uniFE0B ; G 3332
+U 65036 ; WX 0 ; N uniFE0C ; G 3333
+U 65037 ; WX 0 ; N uniFE0D ; G 3334
+U 65038 ; WX 0 ; N uniFE0E ; G 3335
+U 65039 ; WX 0 ; N uniFE0F ; G 3336
+U 65529 ; WX 0 ; N uniFFF9 ; G 3337
+U 65530 ; WX 0 ; N uniFFFA ; G 3338
+U 65531 ; WX 0 ; N uniFFFB ; G 3339
+U 65532 ; WX 0 ; N uniFFFC ; G 3340
+U 65533 ; WX 1025 ; N uniFFFD ; G 3341
+EndCharMetrics
+StartKernData
+StartKernPairs 1367
+
+KPX dollar seven -112
+KPX dollar nine -102
+KPX dollar colon -83
+KPX dollar less -83
+KPX dollar I -36
+KPX dollar W -36
+KPX dollar Y -83
+KPX dollar Z -92
+KPX dollar backslash -83
+KPX dollar questiondown -83
+KPX dollar Aacute -83
+KPX dollar Hcircumflex -112
+KPX dollar hcircumflex -36
+KPX dollar Hbar -112
+KPX dollar hbar -36
+KPX dollar Kcommaaccent -83
+KPX dollar kcommaaccent -92
+KPX dollar kgreenlandic -83
+KPX dollar Lacute -83
+KPX dollar lacute -83
+KPX dollar uni01DC -112
+KPX dollar uni01DD -36
+KPX dollar uni01F4 -83
+
+KPX percent ampersand 38
+KPX percent asterisk 38
+KPX percent two 38
+KPX percent less -36
+KPX percent Egrave 38
+KPX percent Ecircumflex 38
+KPX percent Igrave 38
+KPX percent Icircumflex 38
+KPX percent Thorn 38
+KPX percent agrave 38
+KPX percent acircumflex 38
+KPX percent adieresis 38
+KPX percent Dcaron 38
+KPX percent Dcroat 38
+KPX percent Emacron 38
+KPX percent Ebreve 38
+KPX percent kgreenlandic -36
+KPX percent lacute -36
+KPX percent uni01AC 38
+KPX percent uni01AE 38
+KPX percent uni01F0 38
+KPX percent uni01F4 -36
+
+
+KPX quotesingle nine -36
+
+
+KPX parenright dollar -178
+KPX parenright D -139
+KPX parenright H -112
+KPX parenright R -112
+KPX parenright cent -139
+KPX parenright sterling -139
+KPX parenright currency -139
+KPX parenright yen -139
+KPX parenright brokenbar -139
+KPX parenright section -139
+KPX parenright dieresis -139
+KPX parenright ordfeminine -112
+KPX parenright guillemotleft -112
+KPX parenright logicalnot -112
+KPX parenright sfthyphen -112
+KPX parenright acute -112
+KPX parenright mu -112
+KPX parenright paragraph -112
+KPX parenright periodcentered -112
+KPX parenright cedilla -112
+KPX parenright ordmasculine -112
+KPX parenright Acircumflex -178
+KPX parenright Atilde -139
+KPX parenright Adieresis -178
+KPX parenright Aring -139
+KPX parenright AE -178
+KPX parenright Ccedilla -139
+KPX parenright Otilde -112
+KPX parenright multiply -112
+KPX parenright Ugrave -112
+KPX parenright Ucircumflex -112
+KPX parenright Yacute -112
+KPX parenright dcaron -112
+KPX parenright dmacron -112
+KPX parenright emacron -112
+KPX parenright ebreve -112
+KPX parenright uni01A5 -139
+KPX parenright uni01AD -112
+KPX parenright Uhorn -112
+KPX parenright uni01F1 -112
+
+KPX asterisk less -36
+KPX asterisk lacute -36
+
+
+KPX period dollar -83
+KPX period ampersand -55
+KPX period two -55
+KPX period eight -73
+KPX period colon -73
+KPX period less -55
+KPX period H -55
+KPX period R -55
+KPX period X -45
+KPX period backslash -131
+KPX period ordfeminine -55
+KPX period guillemotleft -55
+KPX period logicalnot -55
+KPX period sfthyphen -55
+KPX period acute -55
+KPX period mu -55
+KPX period paragraph -55
+KPX period periodcentered -55
+KPX period cedilla -55
+KPX period ordmasculine -36
+KPX period guillemotright -45
+KPX period onequarter -45
+KPX period onehalf -45
+KPX period threequarters -45
+KPX period questiondown -131
+KPX period Aacute -131
+KPX period Egrave -55
+KPX period Icircumflex -55
+KPX period Yacute -55
+KPX period Ebreve -55
+KPX period ebreve -55
+KPX period Idot -73
+KPX period dotlessi -45
+KPX period lacute -55
+
+KPX slash seven -167
+KPX slash eight -112
+KPX slash nine -243
+KPX slash colon -178
+KPX slash less -131
+KPX slash backslash -36
+KPX slash questiondown -36
+KPX slash Aacute -36
+KPX slash Hbar -167
+KPX slash Idot -112
+KPX slash lacute -131
+
+
+KPX two nine -36
+KPX two semicolon -36
+
+KPX three dollar -188
+KPX three eight -36
+KPX three D -92
+KPX three H -92
+KPX three R -83
+KPX three V -55
+KPX three cent -92
+KPX three sterling -92
+KPX three currency -92
+KPX three yen -92
+KPX three brokenbar -92
+KPX three section -92
+KPX three dieresis -92
+KPX three ordfeminine -92
+KPX three guillemotleft -92
+KPX three logicalnot -92
+KPX three sfthyphen -92
+KPX three acute -83
+KPX three mu -83
+KPX three paragraph -83
+KPX three periodcentered -83
+KPX three cedilla -83
+KPX three ordmasculine -83
+KPX three Yacute -92
+KPX three ebreve -83
+KPX three gdotaccent -55
+KPX three gcommaaccent -55
+KPX three Idot -36
+
+
+KPX five seven -36
+KPX five nine -73
+KPX five colon -45
+KPX five less -63
+KPX five D 47
+KPX five backslash -36
+KPX five cent 47
+KPX five sterling 47
+KPX five currency 47
+KPX five yen 47
+KPX five brokenbar 47
+KPX five section 47
+KPX five dieresis 47
+KPX five ordmasculine 38
+KPX five questiondown -36
+KPX five Aacute -36
+KPX five Hbar -36
+KPX five lacute -63
+
+KPX six six -36
+KPX six Gdotaccent -36
+KPX six Gcommaaccent -36
+
+KPX seven dollar -112
+KPX seven seven 38
+KPX seven D -159
+KPX seven F -159
+KPX seven H -159
+KPX seven R -159
+KPX seven V -149
+KPX seven Z -73
+KPX seven cent -59
+KPX seven sterling -159
+KPX seven currency -59
+KPX seven yen -59
+KPX seven brokenbar -59
+KPX seven section -59
+KPX seven dieresis -159
+KPX seven copyright -159
+KPX seven ordfeminine -99
+KPX seven guillemotleft -159
+KPX seven logicalnot -99
+KPX seven sfthyphen -99
+KPX seven acute -79
+KPX seven mu -159
+KPX seven paragraph -79
+KPX seven periodcentered -79
+KPX seven cedilla -79
+KPX seven ordmasculine -159
+KPX seven Eacute -159
+KPX seven Idieresis -159
+KPX seven Yacute -159
+KPX seven ebreve -159
+KPX seven gdotaccent -149
+KPX seven gcommaaccent -149
+KPX seven Hbar 38
+
+KPX eight dollar -63
+KPX eight hyphen -55
+
+KPX nine dollar -139
+KPX nine two -36
+KPX nine D -188
+KPX nine H -188
+KPX nine L -36
+KPX nine R -188
+KPX nine X -131
+KPX nine backslash -83
+KPX nine cent -188
+KPX nine sterling -188
+KPX nine currency -188
+KPX nine yen -188
+KPX nine brokenbar -188
+KPX nine section -188
+KPX nine dieresis -188
+KPX nine ordfeminine -188
+KPX nine guillemotleft -188
+KPX nine logicalnot -188
+KPX nine sfthyphen -188
+KPX nine acute -188
+KPX nine mu -188
+KPX nine paragraph -188
+KPX nine periodcentered -188
+KPX nine cedilla -188
+KPX nine ordmasculine -188
+KPX nine guillemotright -131
+KPX nine onequarter -131
+KPX nine onehalf -131
+KPX nine threequarters -131
+KPX nine questiondown -83
+KPX nine Aacute -83
+KPX nine Yacute -188
+KPX nine Ebreve -36
+KPX nine ebreve -188
+KPX nine dotlessi -131
+
+KPX colon dollar -102
+KPX colon D -178
+KPX colon H -167
+KPX colon L -36
+KPX colon R -139
+KPX colon U -92
+KPX colon X -83
+KPX colon backslash -45
+KPX colon cent -178
+KPX colon sterling -178
+KPX colon currency -178
+KPX colon yen -178
+KPX colon brokenbar -178
+KPX colon section -178
+KPX colon dieresis -139
+KPX colon ordfeminine -167
+KPX colon guillemotleft -167
+KPX colon logicalnot -167
+KPX colon sfthyphen -167
+KPX colon acute -139
+KPX colon mu -139
+KPX colon paragraph -139
+KPX colon periodcentered -139
+KPX colon cedilla -139
+KPX colon ordmasculine -139
+KPX colon guillemotright -83
+KPX colon onequarter -83
+KPX colon onehalf -83
+KPX colon threequarters -83
+KPX colon questiondown -45
+KPX colon Aacute -45
+KPX colon Yacute -167
+KPX colon ebreve -139
+KPX colon edotaccent -92
+KPX colon ecaron -92
+KPX colon dotlessi -83
+
+KPX semicolon dollar -73
+KPX semicolon ampersand -36
+KPX semicolon two -36
+KPX semicolon Egrave -36
+KPX semicolon Icircumflex -36
+KPX semicolon Ebreve -36
+
+KPX less dollar -159
+KPX less ampersand -36
+KPX less D -159
+KPX less H -178
+KPX less L -36
+KPX less R -178
+KPX less X -178
+KPX less cent -159
+KPX less sterling -159
+KPX less currency -159
+KPX less yen -159
+KPX less brokenbar -159
+KPX less section -159
+KPX less dieresis -196
+KPX less ordfeminine -178
+KPX less guillemotleft -178
+KPX less logicalnot -178
+KPX less sfthyphen -178
+KPX less acute -178
+KPX less mu -178
+KPX less paragraph -178
+KPX less periodcentered -178
+KPX less cedilla -178
+KPX less ordmasculine -178
+KPX less guillemotright -178
+KPX less onequarter -178
+KPX less onehalf -178
+KPX less threequarters -178
+KPX less Egrave -36
+KPX less Icircumflex -36
+KPX less Yacute -178
+KPX less ebreve -215
+KPX less dotlessi -178
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+KPX Acircumflex seven -112
+KPX Acircumflex nine -102
+KPX Acircumflex colon -83
+KPX Acircumflex less -83
+KPX Acircumflex I -36
+KPX Acircumflex W -36
+KPX Acircumflex Y -83
+KPX Acircumflex Z -92
+KPX Acircumflex backslash -83
+KPX Acircumflex questiondown -83
+KPX Acircumflex Aacute -83
+KPX Acircumflex Hcircumflex -112
+KPX Acircumflex hcircumflex -36
+KPX Acircumflex Hbar -112
+KPX Acircumflex hbar -36
+KPX Acircumflex Kcommaaccent -83
+KPX Acircumflex kcommaaccent -92
+KPX Acircumflex kgreenlandic -83
+KPX Acircumflex Lacute -83
+KPX Acircumflex lacute -83
+KPX Acircumflex uni01DC -112
+KPX Acircumflex uni01DD -36
+KPX Acircumflex uni01F4 -83
+
+KPX Adieresis seven -112
+KPX Adieresis nine -102
+KPX Adieresis colon -83
+KPX Adieresis less -83
+KPX Adieresis I -36
+KPX Adieresis W -36
+KPX Adieresis Y -83
+KPX Adieresis Z -92
+KPX Adieresis backslash -83
+KPX Adieresis questiondown -83
+KPX Adieresis Aacute -83
+KPX Adieresis Hcircumflex -112
+KPX Adieresis hcircumflex -36
+KPX Adieresis Hbar -112
+KPX Adieresis hbar -36
+KPX Adieresis Kcommaaccent -83
+KPX Adieresis kcommaaccent -92
+KPX Adieresis kgreenlandic -83
+KPX Adieresis Lacute -83
+KPX Adieresis lacute -83
+KPX Adieresis uni01DC -112
+KPX Adieresis uni01DD -36
+KPX Adieresis uni01F4 -83
+
+KPX AE seven -112
+KPX AE nine -102
+KPX AE colon -83
+KPX AE less -83
+KPX AE I -36
+KPX AE W -36
+KPX AE Y -83
+KPX AE Z -92
+KPX AE backslash -83
+KPX AE questiondown -83
+KPX AE Aacute -83
+KPX AE Hcircumflex -112
+KPX AE hcircumflex -36
+KPX AE Hbar -112
+KPX AE hbar -36
+KPX AE Kcommaaccent -83
+KPX AE kcommaaccent -92
+KPX AE kgreenlandic -83
+KPX AE Lacute -83
+KPX AE lacute -83
+KPX AE uni01DC -112
+KPX AE uni01DD -36
+KPX AE uni01F4 -83
+
+
+
+
+
+KPX Eth nine -36
+
+KPX Ograve nine -36
+
+
+KPX agrave less -36
+KPX agrave lacute -36
+
+KPX ucircumflex seven -167
+KPX ucircumflex eight -112
+KPX ucircumflex nine -243
+KPX ucircumflex colon -178
+KPX ucircumflex less -131
+KPX ucircumflex backslash -36
+KPX ucircumflex questiondown -36
+KPX ucircumflex Aacute -36
+KPX ucircumflex Hbar -167
+KPX ucircumflex Idot -112
+KPX ucircumflex lacute -131
+
+KPX ydieresis seven -167
+KPX ydieresis eight -112
+KPX ydieresis nine -243
+KPX ydieresis colon -178
+KPX ydieresis less -131
+KPX ydieresis backslash -36
+KPX ydieresis questiondown -36
+KPX ydieresis Aacute -36
+KPX ydieresis Hbar -167
+KPX ydieresis Idot -112
+KPX ydieresis lacute -131
+
+KPX Abreve O -227
+
+KPX abreve seven -167
+KPX abreve eight -36
+KPX abreve nine -243
+KPX abreve colon -178
+KPX abreve less -206
+KPX abreve backslash -36
+KPX abreve questiondown -36
+KPX abreve Aacute -36
+KPX abreve Hbar -167
+KPX abreve Idot -36
+KPX abreve lacute -206
+
+
+
+KPX Edotaccent seven -36
+KPX Edotaccent nine -73
+KPX Edotaccent colon -45
+KPX Edotaccent less -63
+KPX Edotaccent D 47
+KPX Edotaccent backslash -36
+KPX Edotaccent cent 47
+KPX Edotaccent sterling 47
+KPX Edotaccent currency 47
+KPX Edotaccent yen 47
+KPX Edotaccent brokenbar 47
+KPX Edotaccent section 47
+KPX Edotaccent dieresis 47
+KPX Edotaccent ordmasculine 38
+KPX Edotaccent questiondown -36
+KPX Edotaccent Aacute -36
+KPX Edotaccent Hbar -36
+KPX Edotaccent lacute -63
+
+
+KPX Ecaron seven -36
+KPX Ecaron nine -73
+KPX Ecaron colon -45
+KPX Ecaron less -63
+KPX Ecaron D 47
+KPX Ecaron backslash -36
+KPX Ecaron cent 47
+KPX Ecaron sterling 47
+KPX Ecaron currency 47
+KPX Ecaron yen 47
+KPX Ecaron brokenbar 47
+KPX Ecaron section 47
+KPX Ecaron dieresis 47
+KPX Ecaron ordmasculine 38
+KPX Ecaron questiondown -36
+KPX Ecaron Aacute -36
+KPX Ecaron Hbar -36
+KPX Ecaron lacute -63
+
+
+KPX Gdotaccent six -36
+KPX Gdotaccent Gdotaccent -36
+KPX Gdotaccent Gcommaaccent -36
+
+KPX Gcommaaccent six -36
+KPX Gcommaaccent Gdotaccent -36
+KPX Gcommaaccent Gcommaaccent -36
+
+KPX Hbar dollar -112
+KPX Hbar seven 38
+KPX Hbar D -159
+KPX Hbar F -159
+KPX Hbar H -159
+KPX Hbar R -159
+KPX Hbar V -149
+KPX Hbar Z -73
+KPX Hbar cent -159
+KPX Hbar sterling -159
+KPX Hbar currency -159
+KPX Hbar yen -159
+KPX Hbar brokenbar -159
+KPX Hbar section -159
+KPX Hbar dieresis -159
+KPX Hbar copyright -159
+KPX Hbar ordfeminine -159
+KPX Hbar guillemotleft -159
+KPX Hbar logicalnot -159
+KPX Hbar sfthyphen -159
+KPX Hbar acute -159
+KPX Hbar mu -159
+KPX Hbar paragraph -159
+KPX Hbar periodcentered -159
+KPX Hbar cedilla -159
+KPX Hbar ordmasculine -159
+KPX Hbar Eacute -159
+KPX Hbar Idieresis -159
+KPX Hbar Yacute -159
+KPX Hbar ebreve -159
+KPX Hbar gdotaccent -149
+KPX Hbar gcommaaccent -149
+KPX Hbar Hbar 38
+
+KPX Idot dollar -63
+KPX Idot hyphen -55
+
+KPX kcommaaccent D 110
+KPX kcommaaccent F 85
+KPX kcommaaccent G 97
+KPX kcommaaccent H 86
+KPX kcommaaccent I 220
+KPX kcommaaccent J 97
+KPX kcommaaccent L 220
+KPX kcommaaccent M 218
+KPX kcommaaccent P 125
+KPX kcommaaccent Q 125
+KPX kcommaaccent R 85
+KPX kcommaaccent S 140
+KPX kcommaaccent T 97
+KPX kcommaaccent U 125
+KPX kcommaaccent V 155
+KPX kcommaaccent W 235
+KPX kcommaaccent X 144
+KPX kcommaaccent Y 205
+KPX kcommaaccent Z 166
+KPX kcommaaccent bracketleft 174
+KPX kcommaaccent backslash 205
+KPX kcommaaccent bracketright 179
+KPX kcommaaccent kcommaaccent 261
+
+KPX lacute dollar -159
+KPX lacute ampersand -36
+KPX lacute D -159
+KPX lacute H -178
+KPX lacute L -36
+KPX lacute R -178
+KPX lacute X -178
+KPX lacute cent -159
+KPX lacute sterling -159
+KPX lacute currency -159
+KPX lacute yen -159
+KPX lacute brokenbar -159
+KPX lacute section -159
+KPX lacute dieresis -196
+KPX lacute ordfeminine -178
+KPX lacute guillemotleft -178
+KPX lacute logicalnot -178
+KPX lacute sfthyphen -178
+KPX lacute acute -178
+KPX lacute mu -178
+KPX lacute paragraph -178
+KPX lacute periodcentered -178
+KPX lacute cedilla -178
+KPX lacute ordmasculine -178
+KPX lacute guillemotright -178
+KPX lacute onequarter -178
+KPX lacute onehalf -178
+KPX lacute threequarters -178
+KPX lacute Egrave -36
+KPX lacute Icircumflex -36
+KPX lacute Yacute -178
+KPX lacute ebreve -215
+KPX lacute dotlessi -178
+
+
+KPX uni027D dollar -264
+KPX uni027D hyphen 47
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Bold.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Bold.afm
new file mode 100644
index 0000000..92fa6aa
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Bold.afm
@@ -0,0 +1,2829 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:43:52 1997
+Comment UniqueID 43052
+Comment VMusage 37169 48194
+FontName Helvetica-Bold
+FullName Helvetica Bold
+FamilyName Helvetica
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -170 -228 1003 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StdHW 118
+StdVW 140
+StartCharMetrics 317
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 90 0 244 718 ;
+C 34 ; WX 474 ; N quotedbl ; B 98 447 376 718 ;
+C 35 ; WX 556 ; N numbersign ; B 18 0 538 698 ;
+C 36 ; WX 556 ; N dollar ; B 30 -115 523 775 ;
+C 37 ; WX 889 ; N percent ; B 28 -19 861 710 ;
+C 38 ; WX 722 ; N ampersand ; B 54 -19 701 718 ;
+C 146 ; WX 278 ; N quoteright ; B 69 445 209 718 ;
+C 40 ; WX 333 ; N parenleft ; B 35 -208 314 734 ;
+C 41 ; WX 333 ; N parenright ; B 19 -208 298 734 ;
+C 42 ; WX 389 ; N asterisk ; B 27 387 362 718 ;
+C 43 ; WX 584 ; N plus ; B 40 0 544 506 ;
+C 44 ; WX 278 ; N comma ; B 64 -168 214 146 ;
+C 45 ; WX 333 ; N hyphen ; B 27 215 306 345 ;
+C 173 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 46 ; WX 278 ; N period ; B 64 0 214 146 ;
+C 47 ; WX 278 ; N slash ; B -33 -19 311 737 ;
+C 48 ; WX 556 ; N zero ; B 32 -19 524 710 ;
+C 49 ; WX 556 ; N one ; B 69 0 378 710 ;
+C 50 ; WX 556 ; N two ; B 26 0 511 710 ;
+C 51 ; WX 556 ; N three ; B 27 -19 516 710 ;
+C 52 ; WX 556 ; N four ; B 27 0 526 710 ;
+C 53 ; WX 556 ; N five ; B 27 -19 516 698 ;
+C 54 ; WX 556 ; N six ; B 31 -19 520 710 ;
+C 55 ; WX 556 ; N seven ; B 25 0 528 698 ;
+C 56 ; WX 556 ; N eight ; B 32 -19 524 710 ;
+C 57 ; WX 556 ; N nine ; B 30 -19 522 710 ;
+C 58 ; WX 333 ; N colon ; B 92 0 242 512 ;
+C 59 ; WX 333 ; N semicolon ; B 92 -168 242 512 ;
+C 60 ; WX 584 ; N less ; B 38 -8 546 514 ;
+C 61 ; WX 584 ; N equal ; B 40 87 544 419 ;
+C 62 ; WX 584 ; N greater ; B 38 -8 546 514 ;
+C 63 ; WX 611 ; N question ; B 60 0 556 727 ;
+C 64 ; WX 975 ; N at ; B 118 -19 856 737 ;
+C 65 ; WX 722 ; N A ; B 20 0 702 718 ;
+C 66 ; WX 722 ; N B ; B 76 0 669 718 ;
+C 67 ; WX 722 ; N C ; B 44 -19 684 737 ;
+C 68 ; WX 722 ; N D ; B 76 0 685 718 ;
+C 69 ; WX 667 ; N E ; B 76 0 621 718 ;
+C 70 ; WX 611 ; N F ; B 76 0 587 718 ;
+C 71 ; WX 778 ; N G ; B 44 -19 713 737 ;
+C 72 ; WX 722 ; N H ; B 71 0 651 718 ;
+C 73 ; WX 278 ; N I ; B 64 0 214 718 ;
+C 74 ; WX 556 ; N J ; B 22 -18 484 718 ;
+C 75 ; WX 722 ; N K ; B 87 0 722 718 ;
+C 76 ; WX 611 ; N L ; B 76 0 583 718 ;
+C 77 ; WX 833 ; N M ; B 69 0 765 718 ;
+C 78 ; WX 722 ; N N ; B 69 0 654 718 ;
+C 79 ; WX 778 ; N O ; B 44 -19 734 737 ;
+C 80 ; WX 667 ; N P ; B 76 0 627 718 ;
+C 81 ; WX 778 ; N Q ; B 44 -52 737 737 ;
+C 82 ; WX 722 ; N R ; B 76 0 677 718 ;
+C 83 ; WX 667 ; N S ; B 39 -19 629 737 ;
+C 84 ; WX 611 ; N T ; B 14 0 598 718 ;
+C 85 ; WX 722 ; N U ; B 72 -19 651 718 ;
+C 86 ; WX 667 ; N V ; B 19 0 648 718 ;
+C 87 ; WX 944 ; N W ; B 16 0 929 718 ;
+C 88 ; WX 667 ; N X ; B 14 0 653 718 ;
+C 89 ; WX 667 ; N Y ; B 15 0 653 718 ;
+C 90 ; WX 611 ; N Z ; B 25 0 586 718 ;
+C 91 ; WX 333 ; N bracketleft ; B 63 -196 309 722 ;
+C 92 ; WX 278 ; N backslash ; B -33 -19 311 737 ;
+C 93 ; WX 333 ; N bracketright ; B 24 -196 270 722 ;
+C 94 ; WX 584 ; N asciicircum ; B 62 323 522 698 ;
+C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
+C 145 ; WX 278 ; N quoteleft ; B 69 454 209 727 ;
+C 97 ; WX 556 ; N a ; B 29 -14 527 546 ;
+C 98 ; WX 611 ; N b ; B 61 -14 578 718 ;
+C 99 ; WX 556 ; N c ; B 34 -14 524 546 ;
+C 100 ; WX 611 ; N d ; B 34 -14 551 718 ;
+C 101 ; WX 556 ; N e ; B 23 -14 528 546 ;
+C 102 ; WX 333 ; N f ; B 10 0 318 727 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 40 -217 553 546 ;
+C 104 ; WX 611 ; N h ; B 65 0 546 718 ;
+C 105 ; WX 278 ; N i ; B 69 0 209 725 ;
+C 106 ; WX 278 ; N j ; B 3 -214 209 725 ;
+C 107 ; WX 556 ; N k ; B 69 0 562 718 ;
+C 108 ; WX 278 ; N l ; B 69 0 209 718 ;
+C 109 ; WX 889 ; N m ; B 64 0 826 546 ;
+C 110 ; WX 611 ; N n ; B 65 0 546 546 ;
+C 111 ; WX 611 ; N o ; B 34 -14 578 546 ;
+C 112 ; WX 611 ; N p ; B 62 -207 578 546 ;
+C 113 ; WX 611 ; N q ; B 34 -207 552 546 ;
+C 114 ; WX 389 ; N r ; B 64 0 373 546 ;
+C 115 ; WX 556 ; N s ; B 30 -14 519 546 ;
+C 116 ; WX 333 ; N t ; B 10 -6 309 676 ;
+C 117 ; WX 611 ; N u ; B 66 -14 545 532 ;
+C 118 ; WX 556 ; N v ; B 13 0 543 532 ;
+C 119 ; WX 778 ; N w ; B 10 0 769 532 ;
+C 120 ; WX 556 ; N x ; B 15 0 541 532 ;
+C 121 ; WX 556 ; N y ; B 10 -214 539 532 ;
+C 122 ; WX 500 ; N z ; B 20 0 480 532 ;
+C 123 ; WX 389 ; N braceleft ; B 48 -196 365 722 ;
+C 124 ; WX 280 ; N bar ; B 84 -225 196 775 ;
+C 125 ; WX 389 ; N braceright ; B 24 -196 341 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 61 163 523 343 ;
+C 161 ; WX 333 ; N exclamdown ; B 90 -186 244 532 ;
+C 162 ; WX 556 ; N cent ; B 34 -118 524 628 ;
+C 163 ; WX 556 ; N sterling ; B 28 -16 541 718 ;
+C -1 ; WX 167 ; N fraction ; B -170 -19 336 710 ;
+C 165 ; WX 556 ; N yen ; B -9 0 565 698 ;
+C 131 ; WX 556 ; N florin ; B -10 -210 516 737 ;
+C 167 ; WX 556 ; N section ; B 34 -184 522 727 ;
+C 164 ; WX 556 ; N currency ; B -3 76 559 636 ;
+C 39 ; WX 238 ; N quotesingle ; B 70 447 168 718 ;
+C 147 ; WX 500 ; N quotedblleft ; B 64 454 436 727 ;
+C 171 ; WX 556 ; N guillemotleft ; B 88 76 468 484 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 83 76 250 484 ;
+C 155 ; WX 333 ; N guilsinglright ; B 83 76 250 484 ;
+C -1 ; WX 611 ; N fi ; B 10 0 542 727 ;
+C -1 ; WX 611 ; N fl ; B 10 0 542 727 ;
+C 150 ; WX 556 ; N endash ; B 0 227 556 333 ;
+C 134 ; WX 556 ; N dagger ; B 36 -171 520 718 ;
+C 135 ; WX 556 ; N daggerdbl ; B 36 -171 520 718 ;
+C 183 ; WX 278 ; N periodcentered ; B 58 172 220 334 ;
+C 182 ; WX 556 ; N paragraph ; B -8 -191 539 700 ;
+C 149 ; WX 350 ; N bullet ; B 10 194 340 524 ;
+C 130 ; WX 278 ; N quotesinglbase ; B 69 -146 209 127 ;
+C 132 ; WX 500 ; N quotedblbase ; B 64 -146 436 127 ;
+C 148 ; WX 500 ; N quotedblright ; B 64 445 436 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 88 76 468 484 ;
+C 133 ; WX 1000 ; N ellipsis ; B 92 0 908 146 ;
+C 137 ; WX 1000 ; N perthousand ; B -3 -19 1003 710 ;
+C 191 ; WX 611 ; N questiondown ; B 55 -195 551 532 ;
+C 96 ; WX 333 ; N grave ; B -23 604 225 750 ;
+C 180 ; WX 333 ; N acute ; B 108 604 356 750 ;
+C 136 ; WX 333 ; N circumflex ; B -10 604 343 750 ;
+C 152 ; WX 333 ; N tilde ; B -17 610 350 737 ;
+C 175 ; WX 333 ; N macron ; B -6 604 339 678 ;
+C -1 ; WX 333 ; N breve ; B -2 604 335 750 ;
+C -1 ; WX 333 ; N dotaccent ; B 104 614 230 729 ;
+C 168 ; WX 333 ; N dieresis ; B 6 614 327 729 ;
+C -1 ; WX 333 ; N ring ; B 59 568 275 776 ;
+C 184 ; WX 333 ; N cedilla ; B 6 -228 245 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B 9 604 486 750 ;
+C -1 ; WX 333 ; N ogonek ; B 71 -228 304 0 ;
+C -1 ; WX 333 ; N caron ; B -10 604 343 750 ;
+C 151 ; WX 1000 ; N emdash ; B 0 227 1000 333 ;
+C 198 ; WX 1000 ; N AE ; B 5 0 954 718 ;
+C 170 ; WX 370 ; N ordfeminine ; B 22 401 347 737 ;
+C -1 ; WX 611 ; N Lslash ; B -20 0 583 718 ;
+C 216 ; WX 778 ; N Oslash ; B 33 -27 744 745 ;
+C 140 ; WX 1000 ; N OE ; B 37 -19 961 737 ;
+C 186 ; WX 365 ; N ordmasculine ; B 6 401 360 737 ;
+C 230 ; WX 889 ; N ae ; B 29 -14 858 546 ;
+C -1 ; WX 278 ; N dotlessi ; B 69 0 209 532 ;
+C -1 ; WX 278 ; N lslash ; B -18 0 296 718 ;
+C 248 ; WX 611 ; N oslash ; B 22 -29 589 560 ;
+C 156 ; WX 944 ; N oe ; B 34 -14 912 546 ;
+C 223 ; WX 611 ; N germandbls ; B 69 -14 579 731 ;
+C 207 ; WX 278 ; N Idieresis ; B -21 0 300 915 ;
+C 233 ; WX 556 ; N eacute ; B 23 -14 528 750 ;
+C -1 ; WX 556 ; N abreve ; B 29 -14 527 750 ;
+C -1 ; WX 611 ; N uhungarumlaut ; B 66 -14 625 750 ;
+C -1 ; WX 556 ; N ecaron ; B 23 -14 528 750 ;
+C 159 ; WX 667 ; N Ydieresis ; B 15 0 653 915 ;
+C 247 ; WX 584 ; N divide ; B 40 -42 544 548 ;
+C 221 ; WX 667 ; N Yacute ; B 15 0 653 936 ;
+C 194 ; WX 722 ; N Acircumflex ; B 20 0 702 936 ;
+C 225 ; WX 556 ; N aacute ; B 29 -14 527 750 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 72 -19 651 936 ;
+C 253 ; WX 556 ; N yacute ; B 10 -214 539 750 ;
+C -1 ; WX 556 ; N scommaaccent ; B 30 -228 519 546 ;
+C 234 ; WX 556 ; N ecircumflex ; B 23 -14 528 750 ;
+C -1 ; WX 722 ; N Uring ; B 72 -19 651 962 ;
+C 220 ; WX 722 ; N Udieresis ; B 72 -19 651 915 ;
+C -1 ; WX 556 ; N aogonek ; B 29 -224 545 546 ;
+C 218 ; WX 722 ; N Uacute ; B 72 -19 651 936 ;
+C -1 ; WX 611 ; N uogonek ; B 66 -228 545 532 ;
+C 203 ; WX 667 ; N Edieresis ; B 76 0 621 915 ;
+C -1 ; WX 722 ; N Dcroat ; B -5 0 685 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 64 -228 199 -50 ;
+C 169 ; WX 737 ; N copyright ; B -11 -19 749 737 ;
+C -1 ; WX 667 ; N Emacron ; B 76 0 621 864 ;
+C -1 ; WX 556 ; N ccaron ; B 34 -14 524 750 ;
+C 229 ; WX 556 ; N aring ; B 29 -14 527 776 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 69 -228 654 718 ;
+C -1 ; WX 278 ; N lacute ; B 69 0 329 936 ;
+C 224 ; WX 556 ; N agrave ; B 29 -14 527 750 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 14 -228 598 718 ;
+C -1 ; WX 722 ; N Cacute ; B 44 -19 684 936 ;
+C 227 ; WX 556 ; N atilde ; B 29 -14 527 737 ;
+C -1 ; WX 667 ; N Edotaccent ; B 76 0 621 915 ;
+C 154 ; WX 556 ; N scaron ; B 30 -14 519 750 ;
+C -1 ; WX 556 ; N scedilla ; B 30 -228 519 546 ;
+C 237 ; WX 278 ; N iacute ; B 69 0 329 750 ;
+C -1 ; WX 494 ; N lozenge ; B 10 0 484 745 ;
+C -1 ; WX 722 ; N Rcaron ; B 76 0 677 936 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 44 -228 713 737 ;
+C 251 ; WX 611 ; N ucircumflex ; B 66 -14 545 750 ;
+C 226 ; WX 556 ; N acircumflex ; B 29 -14 527 750 ;
+C -1 ; WX 722 ; N Amacron ; B 20 0 702 864 ;
+C -1 ; WX 389 ; N rcaron ; B 18 0 373 750 ;
+C 231 ; WX 556 ; N ccedilla ; B 34 -228 524 546 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 25 0 586 915 ;
+C 222 ; WX 667 ; N Thorn ; B 76 0 627 718 ;
+C -1 ; WX 778 ; N Omacron ; B 44 -19 734 864 ;
+C -1 ; WX 722 ; N Racute ; B 76 0 677 936 ;
+C -1 ; WX 667 ; N Sacute ; B 39 -19 629 936 ;
+C -1 ; WX 743 ; N dcaron ; B 34 -14 750 718 ;
+C -1 ; WX 722 ; N Umacron ; B 72 -19 651 864 ;
+C -1 ; WX 611 ; N uring ; B 66 -14 545 776 ;
+C 179 ; WX 333 ; N threesuperior ; B 8 271 326 710 ;
+C 210 ; WX 778 ; N Ograve ; B 44 -19 734 936 ;
+C 192 ; WX 722 ; N Agrave ; B 20 0 702 936 ;
+C -1 ; WX 722 ; N Abreve ; B 20 0 702 936 ;
+C 215 ; WX 584 ; N multiply ; B 40 1 545 505 ;
+C 250 ; WX 611 ; N uacute ; B 66 -14 545 750 ;
+C -1 ; WX 611 ; N Tcaron ; B 14 0 598 936 ;
+C -1 ; WX 494 ; N partialdiff ; B 11 -21 494 750 ;
+C 255 ; WX 556 ; N ydieresis ; B 10 -214 539 729 ;
+C -1 ; WX 722 ; N Nacute ; B 69 0 654 936 ;
+C 238 ; WX 278 ; N icircumflex ; B -37 0 316 750 ;
+C 202 ; WX 667 ; N Ecircumflex ; B 76 0 621 936 ;
+C 228 ; WX 556 ; N adieresis ; B 29 -14 527 729 ;
+C 235 ; WX 556 ; N edieresis ; B 23 -14 528 729 ;
+C -1 ; WX 556 ; N cacute ; B 34 -14 524 750 ;
+C -1 ; WX 611 ; N nacute ; B 65 0 546 750 ;
+C -1 ; WX 611 ; N umacron ; B 66 -14 545 678 ;
+C -1 ; WX 722 ; N Ncaron ; B 69 0 654 936 ;
+C 205 ; WX 278 ; N Iacute ; B 64 0 329 936 ;
+C 177 ; WX 584 ; N plusminus ; B 40 0 544 506 ;
+C 166 ; WX 280 ; N brokenbar ; B 84 -150 196 700 ;
+C 174 ; WX 737 ; N registered ; B -11 -19 748 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 44 -19 713 936 ;
+C -1 ; WX 278 ; N Idotaccent ; B 64 0 214 915 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 585 706 ;
+C 200 ; WX 667 ; N Egrave ; B 76 0 621 936 ;
+C -1 ; WX 389 ; N racute ; B 64 0 384 750 ;
+C -1 ; WX 611 ; N omacron ; B 34 -14 578 678 ;
+C -1 ; WX 611 ; N Zacute ; B 25 0 586 936 ;
+C 142 ; WX 611 ; N Zcaron ; B 25 0 586 936 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 704 ;
+C 208 ; WX 722 ; N Eth ; B -5 0 685 718 ;
+C 199 ; WX 722 ; N Ccedilla ; B 44 -228 684 737 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 69 -228 213 718 ;
+C -1 ; WX 389 ; N tcaron ; B 10 -6 421 878 ;
+C -1 ; WX 556 ; N eogonek ; B 23 -228 528 546 ;
+C -1 ; WX 722 ; N Uogonek ; B 72 -228 651 718 ;
+C 193 ; WX 722 ; N Aacute ; B 20 0 702 936 ;
+C 196 ; WX 722 ; N Adieresis ; B 20 0 702 915 ;
+C 232 ; WX 556 ; N egrave ; B 23 -14 528 750 ;
+C -1 ; WX 500 ; N zacute ; B 20 0 480 750 ;
+C -1 ; WX 278 ; N iogonek ; B 16 -224 249 725 ;
+C 211 ; WX 778 ; N Oacute ; B 44 -19 734 936 ;
+C 243 ; WX 611 ; N oacute ; B 34 -14 578 750 ;
+C -1 ; WX 556 ; N amacron ; B 29 -14 527 678 ;
+C -1 ; WX 556 ; N sacute ; B 30 -14 519 750 ;
+C 239 ; WX 278 ; N idieresis ; B -21 0 300 729 ;
+C 212 ; WX 778 ; N Ocircumflex ; B 44 -19 734 936 ;
+C 217 ; WX 722 ; N Ugrave ; B 72 -19 651 936 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 611 ; N thorn ; B 62 -208 578 718 ;
+C 178 ; WX 333 ; N twosuperior ; B 9 283 324 710 ;
+C 214 ; WX 778 ; N Odieresis ; B 44 -19 734 915 ;
+C 181 ; WX 611 ; N mu ; B 66 -207 545 532 ;
+C 236 ; WX 278 ; N igrave ; B -50 0 209 750 ;
+C -1 ; WX 611 ; N ohungarumlaut ; B 34 -14 625 750 ;
+C -1 ; WX 667 ; N Eogonek ; B 76 -224 639 718 ;
+C -1 ; WX 611 ; N dcroat ; B 34 -14 650 718 ;
+C 190 ; WX 834 ; N threequarters ; B 16 -19 799 710 ;
+C -1 ; WX 667 ; N Scedilla ; B 39 -228 629 737 ;
+C -1 ; WX 400 ; N lcaron ; B 69 0 408 718 ;
+C -1 ; WX 722 ; N Kcommaaccent ; B 87 -228 722 718 ;
+C -1 ; WX 611 ; N Lacute ; B 76 0 583 936 ;
+C 153 ; WX 1000 ; N trademark ; B 44 306 956 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 23 -14 528 729 ;
+C 204 ; WX 278 ; N Igrave ; B -50 0 214 936 ;
+C -1 ; WX 278 ; N Imacron ; B -33 0 312 864 ;
+C -1 ; WX 611 ; N Lcaron ; B 76 0 583 718 ;
+C 189 ; WX 834 ; N onehalf ; B 26 -19 794 710 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 526 704 ;
+C 244 ; WX 611 ; N ocircumflex ; B 34 -14 578 750 ;
+C 241 ; WX 611 ; N ntilde ; B 65 0 546 737 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 72 -19 681 936 ;
+C 201 ; WX 667 ; N Eacute ; B 76 0 621 936 ;
+C -1 ; WX 556 ; N emacron ; B 23 -14 528 678 ;
+C -1 ; WX 611 ; N gbreve ; B 40 -217 553 750 ;
+C 188 ; WX 834 ; N onequarter ; B 26 -19 766 710 ;
+C 138 ; WX 667 ; N Scaron ; B 39 -19 629 936 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 39 -228 629 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 44 -19 734 936 ;
+C 176 ; WX 400 ; N degree ; B 57 426 343 712 ;
+C 242 ; WX 611 ; N ograve ; B 34 -14 578 750 ;
+C -1 ; WX 722 ; N Ccaron ; B 44 -19 684 936 ;
+C 249 ; WX 611 ; N ugrave ; B 66 -14 545 750 ;
+C -1 ; WX 549 ; N radical ; B 10 -46 512 850 ;
+C -1 ; WX 722 ; N Dcaron ; B 76 0 685 936 ;
+C -1 ; WX 389 ; N rcommaaccent ; B 64 -228 373 546 ;
+C 209 ; WX 722 ; N Ntilde ; B 69 0 654 923 ;
+C 245 ; WX 611 ; N otilde ; B 34 -14 578 737 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 76 -228 677 718 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B 76 -228 583 718 ;
+C 195 ; WX 722 ; N Atilde ; B 20 0 702 923 ;
+C -1 ; WX 722 ; N Aogonek ; B 20 -224 742 718 ;
+C 197 ; WX 722 ; N Aring ; B 20 0 702 962 ;
+C 213 ; WX 778 ; N Otilde ; B 44 -19 734 923 ;
+C -1 ; WX 500 ; N zdotaccent ; B 20 0 480 729 ;
+C -1 ; WX 667 ; N Ecaron ; B 76 0 621 936 ;
+C -1 ; WX 278 ; N Iogonek ; B -11 -228 222 718 ;
+C -1 ; WX 556 ; N kcommaaccent ; B 69 -228 562 718 ;
+C -1 ; WX 584 ; N minus ; B 40 197 544 309 ;
+C 206 ; WX 278 ; N Icircumflex ; B -37 0 316 936 ;
+C -1 ; WX 611 ; N ncaron ; B 65 0 546 750 ;
+C -1 ; WX 333 ; N tcommaaccent ; B 10 -228 309 676 ;
+C 172 ; WX 584 ; N logicalnot ; B 40 108 544 419 ;
+C 246 ; WX 611 ; N odieresis ; B 34 -14 578 729 ;
+C 252 ; WX 611 ; N udieresis ; B 66 -14 545 729 ;
+C -1 ; WX 549 ; N notequal ; B 15 -49 540 570 ;
+C -1 ; WX 611 ; N gcommaaccent ; B 40 -217 553 850 ;
+C 240 ; WX 611 ; N eth ; B 34 -14 578 737 ;
+C 158 ; WX 500 ; N zcaron ; B 20 0 480 750 ;
+C -1 ; WX 611 ; N ncommaaccent ; B 65 -228 546 546 ;
+C 185 ; WX 333 ; N onesuperior ; B 26 283 237 710 ;
+C -1 ; WX 278 ; N imacron ; B -8 0 285 678 ;
+C 128 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2481
+KPX A C -40
+KPX A Cacute -40
+KPX A Ccaron -40
+KPX A Ccedilla -40
+KPX A G -50
+KPX A Gbreve -50
+KPX A Gcommaaccent -50
+KPX A O -40
+KPX A Oacute -40
+KPX A Ocircumflex -40
+KPX A Odieresis -40
+KPX A Ograve -40
+KPX A Ohungarumlaut -40
+KPX A Omacron -40
+KPX A Oslash -40
+KPX A Otilde -40
+KPX A Q -40
+KPX A T -90
+KPX A Tcaron -90
+KPX A Tcommaaccent -90
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -80
+KPX A W -60
+KPX A Y -110
+KPX A Yacute -110
+KPX A Ydieresis -110
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -30
+KPX A y -30
+KPX A yacute -30
+KPX A ydieresis -30
+KPX Aacute C -40
+KPX Aacute Cacute -40
+KPX Aacute Ccaron -40
+KPX Aacute Ccedilla -40
+KPX Aacute G -50
+KPX Aacute Gbreve -50
+KPX Aacute Gcommaaccent -50
+KPX Aacute O -40
+KPX Aacute Oacute -40
+KPX Aacute Ocircumflex -40
+KPX Aacute Odieresis -40
+KPX Aacute Ograve -40
+KPX Aacute Ohungarumlaut -40
+KPX Aacute Omacron -40
+KPX Aacute Oslash -40
+KPX Aacute Otilde -40
+KPX Aacute Q -40
+KPX Aacute T -90
+KPX Aacute Tcaron -90
+KPX Aacute Tcommaaccent -90
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -80
+KPX Aacute W -60
+KPX Aacute Y -110
+KPX Aacute Yacute -110
+KPX Aacute Ydieresis -110
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -30
+KPX Aacute y -30
+KPX Aacute yacute -30
+KPX Aacute ydieresis -30
+KPX Abreve C -40
+KPX Abreve Cacute -40
+KPX Abreve Ccaron -40
+KPX Abreve Ccedilla -40
+KPX Abreve G -50
+KPX Abreve Gbreve -50
+KPX Abreve Gcommaaccent -50
+KPX Abreve O -40
+KPX Abreve Oacute -40
+KPX Abreve Ocircumflex -40
+KPX Abreve Odieresis -40
+KPX Abreve Ograve -40
+KPX Abreve Ohungarumlaut -40
+KPX Abreve Omacron -40
+KPX Abreve Oslash -40
+KPX Abreve Otilde -40
+KPX Abreve Q -40
+KPX Abreve T -90
+KPX Abreve Tcaron -90
+KPX Abreve Tcommaaccent -90
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -80
+KPX Abreve W -60
+KPX Abreve Y -110
+KPX Abreve Yacute -110
+KPX Abreve Ydieresis -110
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -30
+KPX Abreve y -30
+KPX Abreve yacute -30
+KPX Abreve ydieresis -30
+KPX Acircumflex C -40
+KPX Acircumflex Cacute -40
+KPX Acircumflex Ccaron -40
+KPX Acircumflex Ccedilla -40
+KPX Acircumflex G -50
+KPX Acircumflex Gbreve -50
+KPX Acircumflex Gcommaaccent -50
+KPX Acircumflex O -40
+KPX Acircumflex Oacute -40
+KPX Acircumflex Ocircumflex -40
+KPX Acircumflex Odieresis -40
+KPX Acircumflex Ograve -40
+KPX Acircumflex Ohungarumlaut -40
+KPX Acircumflex Omacron -40
+KPX Acircumflex Oslash -40
+KPX Acircumflex Otilde -40
+KPX Acircumflex Q -40
+KPX Acircumflex T -90
+KPX Acircumflex Tcaron -90
+KPX Acircumflex Tcommaaccent -90
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -80
+KPX Acircumflex W -60
+KPX Acircumflex Y -110
+KPX Acircumflex Yacute -110
+KPX Acircumflex Ydieresis -110
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -30
+KPX Acircumflex y -30
+KPX Acircumflex yacute -30
+KPX Acircumflex ydieresis -30
+KPX Adieresis C -40
+KPX Adieresis Cacute -40
+KPX Adieresis Ccaron -40
+KPX Adieresis Ccedilla -40
+KPX Adieresis G -50
+KPX Adieresis Gbreve -50
+KPX Adieresis Gcommaaccent -50
+KPX Adieresis O -40
+KPX Adieresis Oacute -40
+KPX Adieresis Ocircumflex -40
+KPX Adieresis Odieresis -40
+KPX Adieresis Ograve -40
+KPX Adieresis Ohungarumlaut -40
+KPX Adieresis Omacron -40
+KPX Adieresis Oslash -40
+KPX Adieresis Otilde -40
+KPX Adieresis Q -40
+KPX Adieresis T -90
+KPX Adieresis Tcaron -90
+KPX Adieresis Tcommaaccent -90
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -80
+KPX Adieresis W -60
+KPX Adieresis Y -110
+KPX Adieresis Yacute -110
+KPX Adieresis Ydieresis -110
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -30
+KPX Adieresis y -30
+KPX Adieresis yacute -30
+KPX Adieresis ydieresis -30
+KPX Agrave C -40
+KPX Agrave Cacute -40
+KPX Agrave Ccaron -40
+KPX Agrave Ccedilla -40
+KPX Agrave G -50
+KPX Agrave Gbreve -50
+KPX Agrave Gcommaaccent -50
+KPX Agrave O -40
+KPX Agrave Oacute -40
+KPX Agrave Ocircumflex -40
+KPX Agrave Odieresis -40
+KPX Agrave Ograve -40
+KPX Agrave Ohungarumlaut -40
+KPX Agrave Omacron -40
+KPX Agrave Oslash -40
+KPX Agrave Otilde -40
+KPX Agrave Q -40
+KPX Agrave T -90
+KPX Agrave Tcaron -90
+KPX Agrave Tcommaaccent -90
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -80
+KPX Agrave W -60
+KPX Agrave Y -110
+KPX Agrave Yacute -110
+KPX Agrave Ydieresis -110
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -30
+KPX Agrave y -30
+KPX Agrave yacute -30
+KPX Agrave ydieresis -30
+KPX Amacron C -40
+KPX Amacron Cacute -40
+KPX Amacron Ccaron -40
+KPX Amacron Ccedilla -40
+KPX Amacron G -50
+KPX Amacron Gbreve -50
+KPX Amacron Gcommaaccent -50
+KPX Amacron O -40
+KPX Amacron Oacute -40
+KPX Amacron Ocircumflex -40
+KPX Amacron Odieresis -40
+KPX Amacron Ograve -40
+KPX Amacron Ohungarumlaut -40
+KPX Amacron Omacron -40
+KPX Amacron Oslash -40
+KPX Amacron Otilde -40
+KPX Amacron Q -40
+KPX Amacron T -90
+KPX Amacron Tcaron -90
+KPX Amacron Tcommaaccent -90
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -80
+KPX Amacron W -60
+KPX Amacron Y -110
+KPX Amacron Yacute -110
+KPX Amacron Ydieresis -110
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -30
+KPX Amacron y -30
+KPX Amacron yacute -30
+KPX Amacron ydieresis -30
+KPX Aogonek C -40
+KPX Aogonek Cacute -40
+KPX Aogonek Ccaron -40
+KPX Aogonek Ccedilla -40
+KPX Aogonek G -50
+KPX Aogonek Gbreve -50
+KPX Aogonek Gcommaaccent -50
+KPX Aogonek O -40
+KPX Aogonek Oacute -40
+KPX Aogonek Ocircumflex -40
+KPX Aogonek Odieresis -40
+KPX Aogonek Ograve -40
+KPX Aogonek Ohungarumlaut -40
+KPX Aogonek Omacron -40
+KPX Aogonek Oslash -40
+KPX Aogonek Otilde -40
+KPX Aogonek Q -40
+KPX Aogonek T -90
+KPX Aogonek Tcaron -90
+KPX Aogonek Tcommaaccent -90
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -80
+KPX Aogonek W -60
+KPX Aogonek Y -110
+KPX Aogonek Yacute -110
+KPX Aogonek Ydieresis -110
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -30
+KPX Aogonek y -30
+KPX Aogonek yacute -30
+KPX Aogonek ydieresis -30
+KPX Aring C -40
+KPX Aring Cacute -40
+KPX Aring Ccaron -40
+KPX Aring Ccedilla -40
+KPX Aring G -50
+KPX Aring Gbreve -50
+KPX Aring Gcommaaccent -50
+KPX Aring O -40
+KPX Aring Oacute -40
+KPX Aring Ocircumflex -40
+KPX Aring Odieresis -40
+KPX Aring Ograve -40
+KPX Aring Ohungarumlaut -40
+KPX Aring Omacron -40
+KPX Aring Oslash -40
+KPX Aring Otilde -40
+KPX Aring Q -40
+KPX Aring T -90
+KPX Aring Tcaron -90
+KPX Aring Tcommaaccent -90
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -80
+KPX Aring W -60
+KPX Aring Y -110
+KPX Aring Yacute -110
+KPX Aring Ydieresis -110
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -30
+KPX Aring y -30
+KPX Aring yacute -30
+KPX Aring ydieresis -30
+KPX Atilde C -40
+KPX Atilde Cacute -40
+KPX Atilde Ccaron -40
+KPX Atilde Ccedilla -40
+KPX Atilde G -50
+KPX Atilde Gbreve -50
+KPX Atilde Gcommaaccent -50
+KPX Atilde O -40
+KPX Atilde Oacute -40
+KPX Atilde Ocircumflex -40
+KPX Atilde Odieresis -40
+KPX Atilde Ograve -40
+KPX Atilde Ohungarumlaut -40
+KPX Atilde Omacron -40
+KPX Atilde Oslash -40
+KPX Atilde Otilde -40
+KPX Atilde Q -40
+KPX Atilde T -90
+KPX Atilde Tcaron -90
+KPX Atilde Tcommaaccent -90
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -80
+KPX Atilde W -60
+KPX Atilde Y -110
+KPX Atilde Yacute -110
+KPX Atilde Ydieresis -110
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -30
+KPX Atilde y -30
+KPX Atilde yacute -30
+KPX Atilde ydieresis -30
+KPX B A -30
+KPX B Aacute -30
+KPX B Abreve -30
+KPX B Acircumflex -30
+KPX B Adieresis -30
+KPX B Agrave -30
+KPX B Amacron -30
+KPX B Aogonek -30
+KPX B Aring -30
+KPX B Atilde -30
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -40
+KPX D W -40
+KPX D Y -70
+KPX D Yacute -70
+KPX D Ydieresis -70
+KPX D comma -30
+KPX D period -30
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -70
+KPX Dcaron Yacute -70
+KPX Dcaron Ydieresis -70
+KPX Dcaron comma -30
+KPX Dcaron period -30
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -70
+KPX Dcroat Yacute -70
+KPX Dcroat Ydieresis -70
+KPX Dcroat comma -30
+KPX Dcroat period -30
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -20
+KPX F aacute -20
+KPX F abreve -20
+KPX F acircumflex -20
+KPX F adieresis -20
+KPX F agrave -20
+KPX F amacron -20
+KPX F aogonek -20
+KPX F aring -20
+KPX F atilde -20
+KPX F comma -100
+KPX F period -100
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J comma -20
+KPX J period -20
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -15
+KPX K eacute -15
+KPX K ecaron -15
+KPX K ecircumflex -15
+KPX K edieresis -15
+KPX K edotaccent -15
+KPX K egrave -15
+KPX K emacron -15
+KPX K eogonek -15
+KPX K o -35
+KPX K oacute -35
+KPX K ocircumflex -35
+KPX K odieresis -35
+KPX K ograve -35
+KPX K ohungarumlaut -35
+KPX K omacron -35
+KPX K oslash -35
+KPX K otilde -35
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -40
+KPX K yacute -40
+KPX K ydieresis -40
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -15
+KPX Kcommaaccent eacute -15
+KPX Kcommaaccent ecaron -15
+KPX Kcommaaccent ecircumflex -15
+KPX Kcommaaccent edieresis -15
+KPX Kcommaaccent edotaccent -15
+KPX Kcommaaccent egrave -15
+KPX Kcommaaccent emacron -15
+KPX Kcommaaccent eogonek -15
+KPX Kcommaaccent o -35
+KPX Kcommaaccent oacute -35
+KPX Kcommaaccent ocircumflex -35
+KPX Kcommaaccent odieresis -35
+KPX Kcommaaccent ograve -35
+KPX Kcommaaccent ohungarumlaut -35
+KPX Kcommaaccent omacron -35
+KPX Kcommaaccent oslash -35
+KPX Kcommaaccent otilde -35
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -40
+KPX Kcommaaccent yacute -40
+KPX Kcommaaccent ydieresis -40
+KPX L T -90
+KPX L Tcaron -90
+KPX L Tcommaaccent -90
+KPX L V -110
+KPX L W -80
+KPX L Y -120
+KPX L Yacute -120
+KPX L Ydieresis -120
+KPX L quotedblright -140
+KPX L quoteright -140
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -90
+KPX Lacute Tcaron -90
+KPX Lacute Tcommaaccent -90
+KPX Lacute V -110
+KPX Lacute W -80
+KPX Lacute Y -120
+KPX Lacute Yacute -120
+KPX Lacute Ydieresis -120
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -140
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcommaaccent T -90
+KPX Lcommaaccent Tcaron -90
+KPX Lcommaaccent Tcommaaccent -90
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -80
+KPX Lcommaaccent Y -120
+KPX Lcommaaccent Yacute -120
+KPX Lcommaaccent Ydieresis -120
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -140
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -90
+KPX Lslash Tcaron -90
+KPX Lslash Tcommaaccent -90
+KPX Lslash V -110
+KPX Lslash W -80
+KPX Lslash Y -120
+KPX Lslash Yacute -120
+KPX Lslash Ydieresis -120
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -140
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -50
+KPX O Aacute -50
+KPX O Abreve -50
+KPX O Acircumflex -50
+KPX O Adieresis -50
+KPX O Agrave -50
+KPX O Amacron -50
+KPX O Aogonek -50
+KPX O Aring -50
+KPX O Atilde -50
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -50
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -50
+KPX Oacute Aacute -50
+KPX Oacute Abreve -50
+KPX Oacute Acircumflex -50
+KPX Oacute Adieresis -50
+KPX Oacute Agrave -50
+KPX Oacute Amacron -50
+KPX Oacute Aogonek -50
+KPX Oacute Aring -50
+KPX Oacute Atilde -50
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -50
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -50
+KPX Ocircumflex Aacute -50
+KPX Ocircumflex Abreve -50
+KPX Ocircumflex Acircumflex -50
+KPX Ocircumflex Adieresis -50
+KPX Ocircumflex Agrave -50
+KPX Ocircumflex Amacron -50
+KPX Ocircumflex Aogonek -50
+KPX Ocircumflex Aring -50
+KPX Ocircumflex Atilde -50
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -50
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -50
+KPX Odieresis Aacute -50
+KPX Odieresis Abreve -50
+KPX Odieresis Acircumflex -50
+KPX Odieresis Adieresis -50
+KPX Odieresis Agrave -50
+KPX Odieresis Amacron -50
+KPX Odieresis Aogonek -50
+KPX Odieresis Aring -50
+KPX Odieresis Atilde -50
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -50
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -50
+KPX Ograve Aacute -50
+KPX Ograve Abreve -50
+KPX Ograve Acircumflex -50
+KPX Ograve Adieresis -50
+KPX Ograve Agrave -50
+KPX Ograve Amacron -50
+KPX Ograve Aogonek -50
+KPX Ograve Aring -50
+KPX Ograve Atilde -50
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -50
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -50
+KPX Ohungarumlaut Aacute -50
+KPX Ohungarumlaut Abreve -50
+KPX Ohungarumlaut Acircumflex -50
+KPX Ohungarumlaut Adieresis -50
+KPX Ohungarumlaut Agrave -50
+KPX Ohungarumlaut Amacron -50
+KPX Ohungarumlaut Aogonek -50
+KPX Ohungarumlaut Aring -50
+KPX Ohungarumlaut Atilde -50
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -50
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -50
+KPX Omacron Aacute -50
+KPX Omacron Abreve -50
+KPX Omacron Acircumflex -50
+KPX Omacron Adieresis -50
+KPX Omacron Agrave -50
+KPX Omacron Amacron -50
+KPX Omacron Aogonek -50
+KPX Omacron Aring -50
+KPX Omacron Atilde -50
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -50
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -50
+KPX Oslash Aacute -50
+KPX Oslash Abreve -50
+KPX Oslash Acircumflex -50
+KPX Oslash Adieresis -50
+KPX Oslash Agrave -50
+KPX Oslash Amacron -50
+KPX Oslash Aogonek -50
+KPX Oslash Aring -50
+KPX Oslash Atilde -50
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -50
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -50
+KPX Otilde Aacute -50
+KPX Otilde Abreve -50
+KPX Otilde Acircumflex -50
+KPX Otilde Adieresis -50
+KPX Otilde Agrave -50
+KPX Otilde Amacron -50
+KPX Otilde Aogonek -50
+KPX Otilde Aring -50
+KPX Otilde Atilde -50
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -50
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -100
+KPX P Aacute -100
+KPX P Abreve -100
+KPX P Acircumflex -100
+KPX P Adieresis -100
+KPX P Agrave -100
+KPX P Amacron -100
+KPX P Aogonek -100
+KPX P Aring -100
+KPX P Atilde -100
+KPX P a -30
+KPX P aacute -30
+KPX P abreve -30
+KPX P acircumflex -30
+KPX P adieresis -30
+KPX P agrave -30
+KPX P amacron -30
+KPX P aogonek -30
+KPX P aring -30
+KPX P atilde -30
+KPX P comma -120
+KPX P e -30
+KPX P eacute -30
+KPX P ecaron -30
+KPX P ecircumflex -30
+KPX P edieresis -30
+KPX P edotaccent -30
+KPX P egrave -30
+KPX P emacron -30
+KPX P eogonek -30
+KPX P o -40
+KPX P oacute -40
+KPX P ocircumflex -40
+KPX P odieresis -40
+KPX P ograve -40
+KPX P ohungarumlaut -40
+KPX P omacron -40
+KPX P oslash -40
+KPX P otilde -40
+KPX P period -120
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX Q comma 20
+KPX Q period 20
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -20
+KPX R Tcaron -20
+KPX R Tcommaaccent -20
+KPX R U -20
+KPX R Uacute -20
+KPX R Ucircumflex -20
+KPX R Udieresis -20
+KPX R Ugrave -20
+KPX R Uhungarumlaut -20
+KPX R Umacron -20
+KPX R Uogonek -20
+KPX R Uring -20
+KPX R V -50
+KPX R W -40
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -20
+KPX Racute Tcaron -20
+KPX Racute Tcommaaccent -20
+KPX Racute U -20
+KPX Racute Uacute -20
+KPX Racute Ucircumflex -20
+KPX Racute Udieresis -20
+KPX Racute Ugrave -20
+KPX Racute Uhungarumlaut -20
+KPX Racute Umacron -20
+KPX Racute Uogonek -20
+KPX Racute Uring -20
+KPX Racute V -50
+KPX Racute W -40
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -20
+KPX Rcaron Tcaron -20
+KPX Rcaron Tcommaaccent -20
+KPX Rcaron U -20
+KPX Rcaron Uacute -20
+KPX Rcaron Ucircumflex -20
+KPX Rcaron Udieresis -20
+KPX Rcaron Ugrave -20
+KPX Rcaron Uhungarumlaut -20
+KPX Rcaron Umacron -20
+KPX Rcaron Uogonek -20
+KPX Rcaron Uring -20
+KPX Rcaron V -50
+KPX Rcaron W -40
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -20
+KPX Rcommaaccent Tcaron -20
+KPX Rcommaaccent Tcommaaccent -20
+KPX Rcommaaccent U -20
+KPX Rcommaaccent Uacute -20
+KPX Rcommaaccent Ucircumflex -20
+KPX Rcommaaccent Udieresis -20
+KPX Rcommaaccent Ugrave -20
+KPX Rcommaaccent Uhungarumlaut -20
+KPX Rcommaaccent Umacron -20
+KPX Rcommaaccent Uogonek -20
+KPX Rcommaaccent Uring -20
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -40
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX T A -90
+KPX T Aacute -90
+KPX T Abreve -90
+KPX T Acircumflex -90
+KPX T Adieresis -90
+KPX T Agrave -90
+KPX T Amacron -90
+KPX T Aogonek -90
+KPX T Aring -90
+KPX T Atilde -90
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -80
+KPX T aacute -80
+KPX T abreve -80
+KPX T acircumflex -80
+KPX T adieresis -80
+KPX T agrave -80
+KPX T amacron -80
+KPX T aogonek -80
+KPX T aring -80
+KPX T atilde -80
+KPX T colon -40
+KPX T comma -80
+KPX T e -60
+KPX T eacute -60
+KPX T ecaron -60
+KPX T ecircumflex -60
+KPX T edieresis -60
+KPX T edotaccent -60
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -60
+KPX T hyphen -120
+KPX T o -80
+KPX T oacute -80
+KPX T ocircumflex -80
+KPX T odieresis -80
+KPX T ograve -80
+KPX T ohungarumlaut -80
+KPX T omacron -80
+KPX T oslash -80
+KPX T otilde -80
+KPX T period -80
+KPX T r -80
+KPX T racute -80
+KPX T rcommaaccent -80
+KPX T semicolon -40
+KPX T u -90
+KPX T uacute -90
+KPX T ucircumflex -90
+KPX T udieresis -90
+KPX T ugrave -90
+KPX T uhungarumlaut -90
+KPX T umacron -90
+KPX T uogonek -90
+KPX T uring -90
+KPX T w -60
+KPX T y -60
+KPX T yacute -60
+KPX T ydieresis -60
+KPX Tcaron A -90
+KPX Tcaron Aacute -90
+KPX Tcaron Abreve -90
+KPX Tcaron Acircumflex -90
+KPX Tcaron Adieresis -90
+KPX Tcaron Agrave -90
+KPX Tcaron Amacron -90
+KPX Tcaron Aogonek -90
+KPX Tcaron Aring -90
+KPX Tcaron Atilde -90
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -80
+KPX Tcaron aacute -80
+KPX Tcaron abreve -80
+KPX Tcaron acircumflex -80
+KPX Tcaron adieresis -80
+KPX Tcaron agrave -80
+KPX Tcaron amacron -80
+KPX Tcaron aogonek -80
+KPX Tcaron aring -80
+KPX Tcaron atilde -80
+KPX Tcaron colon -40
+KPX Tcaron comma -80
+KPX Tcaron e -60
+KPX Tcaron eacute -60
+KPX Tcaron ecaron -60
+KPX Tcaron ecircumflex -60
+KPX Tcaron edieresis -60
+KPX Tcaron edotaccent -60
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -60
+KPX Tcaron hyphen -120
+KPX Tcaron o -80
+KPX Tcaron oacute -80
+KPX Tcaron ocircumflex -80
+KPX Tcaron odieresis -80
+KPX Tcaron ograve -80
+KPX Tcaron ohungarumlaut -80
+KPX Tcaron omacron -80
+KPX Tcaron oslash -80
+KPX Tcaron otilde -80
+KPX Tcaron period -80
+KPX Tcaron r -80
+KPX Tcaron racute -80
+KPX Tcaron rcommaaccent -80
+KPX Tcaron semicolon -40
+KPX Tcaron u -90
+KPX Tcaron uacute -90
+KPX Tcaron ucircumflex -90
+KPX Tcaron udieresis -90
+KPX Tcaron ugrave -90
+KPX Tcaron uhungarumlaut -90
+KPX Tcaron umacron -90
+KPX Tcaron uogonek -90
+KPX Tcaron uring -90
+KPX Tcaron w -60
+KPX Tcaron y -60
+KPX Tcaron yacute -60
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -90
+KPX Tcommaaccent Aacute -90
+KPX Tcommaaccent Abreve -90
+KPX Tcommaaccent Acircumflex -90
+KPX Tcommaaccent Adieresis -90
+KPX Tcommaaccent Agrave -90
+KPX Tcommaaccent Amacron -90
+KPX Tcommaaccent Aogonek -90
+KPX Tcommaaccent Aring -90
+KPX Tcommaaccent Atilde -90
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -80
+KPX Tcommaaccent aacute -80
+KPX Tcommaaccent abreve -80
+KPX Tcommaaccent acircumflex -80
+KPX Tcommaaccent adieresis -80
+KPX Tcommaaccent agrave -80
+KPX Tcommaaccent amacron -80
+KPX Tcommaaccent aogonek -80
+KPX Tcommaaccent aring -80
+KPX Tcommaaccent atilde -80
+KPX Tcommaaccent colon -40
+KPX Tcommaaccent comma -80
+KPX Tcommaaccent e -60
+KPX Tcommaaccent eacute -60
+KPX Tcommaaccent ecaron -60
+KPX Tcommaaccent ecircumflex -60
+KPX Tcommaaccent edieresis -60
+KPX Tcommaaccent edotaccent -60
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -60
+KPX Tcommaaccent hyphen -120
+KPX Tcommaaccent o -80
+KPX Tcommaaccent oacute -80
+KPX Tcommaaccent ocircumflex -80
+KPX Tcommaaccent odieresis -80
+KPX Tcommaaccent ograve -80
+KPX Tcommaaccent ohungarumlaut -80
+KPX Tcommaaccent omacron -80
+KPX Tcommaaccent oslash -80
+KPX Tcommaaccent otilde -80
+KPX Tcommaaccent period -80
+KPX Tcommaaccent r -80
+KPX Tcommaaccent racute -80
+KPX Tcommaaccent rcommaaccent -80
+KPX Tcommaaccent semicolon -40
+KPX Tcommaaccent u -90
+KPX Tcommaaccent uacute -90
+KPX Tcommaaccent ucircumflex -90
+KPX Tcommaaccent udieresis -90
+KPX Tcommaaccent ugrave -90
+KPX Tcommaaccent uhungarumlaut -90
+KPX Tcommaaccent umacron -90
+KPX Tcommaaccent uogonek -90
+KPX Tcommaaccent uring -90
+KPX Tcommaaccent w -60
+KPX Tcommaaccent y -60
+KPX Tcommaaccent yacute -60
+KPX Tcommaaccent ydieresis -60
+KPX U A -50
+KPX U Aacute -50
+KPX U Abreve -50
+KPX U Acircumflex -50
+KPX U Adieresis -50
+KPX U Agrave -50
+KPX U Amacron -50
+KPX U Aogonek -50
+KPX U Aring -50
+KPX U Atilde -50
+KPX U comma -30
+KPX U period -30
+KPX Uacute A -50
+KPX Uacute Aacute -50
+KPX Uacute Abreve -50
+KPX Uacute Acircumflex -50
+KPX Uacute Adieresis -50
+KPX Uacute Agrave -50
+KPX Uacute Amacron -50
+KPX Uacute Aogonek -50
+KPX Uacute Aring -50
+KPX Uacute Atilde -50
+KPX Uacute comma -30
+KPX Uacute period -30
+KPX Ucircumflex A -50
+KPX Ucircumflex Aacute -50
+KPX Ucircumflex Abreve -50
+KPX Ucircumflex Acircumflex -50
+KPX Ucircumflex Adieresis -50
+KPX Ucircumflex Agrave -50
+KPX Ucircumflex Amacron -50
+KPX Ucircumflex Aogonek -50
+KPX Ucircumflex Aring -50
+KPX Ucircumflex Atilde -50
+KPX Ucircumflex comma -30
+KPX Ucircumflex period -30
+KPX Udieresis A -50
+KPX Udieresis Aacute -50
+KPX Udieresis Abreve -50
+KPX Udieresis Acircumflex -50
+KPX Udieresis Adieresis -50
+KPX Udieresis Agrave -50
+KPX Udieresis Amacron -50
+KPX Udieresis Aogonek -50
+KPX Udieresis Aring -50
+KPX Udieresis Atilde -50
+KPX Udieresis comma -30
+KPX Udieresis period -30
+KPX Ugrave A -50
+KPX Ugrave Aacute -50
+KPX Ugrave Abreve -50
+KPX Ugrave Acircumflex -50
+KPX Ugrave Adieresis -50
+KPX Ugrave Agrave -50
+KPX Ugrave Amacron -50
+KPX Ugrave Aogonek -50
+KPX Ugrave Aring -50
+KPX Ugrave Atilde -50
+KPX Ugrave comma -30
+KPX Ugrave period -30
+KPX Uhungarumlaut A -50
+KPX Uhungarumlaut Aacute -50
+KPX Uhungarumlaut Abreve -50
+KPX Uhungarumlaut Acircumflex -50
+KPX Uhungarumlaut Adieresis -50
+KPX Uhungarumlaut Agrave -50
+KPX Uhungarumlaut Amacron -50
+KPX Uhungarumlaut Aogonek -50
+KPX Uhungarumlaut Aring -50
+KPX Uhungarumlaut Atilde -50
+KPX Uhungarumlaut comma -30
+KPX Uhungarumlaut period -30
+KPX Umacron A -50
+KPX Umacron Aacute -50
+KPX Umacron Abreve -50
+KPX Umacron Acircumflex -50
+KPX Umacron Adieresis -50
+KPX Umacron Agrave -50
+KPX Umacron Amacron -50
+KPX Umacron Aogonek -50
+KPX Umacron Aring -50
+KPX Umacron Atilde -50
+KPX Umacron comma -30
+KPX Umacron period -30
+KPX Uogonek A -50
+KPX Uogonek Aacute -50
+KPX Uogonek Abreve -50
+KPX Uogonek Acircumflex -50
+KPX Uogonek Adieresis -50
+KPX Uogonek Agrave -50
+KPX Uogonek Amacron -50
+KPX Uogonek Aogonek -50
+KPX Uogonek Aring -50
+KPX Uogonek Atilde -50
+KPX Uogonek comma -30
+KPX Uogonek period -30
+KPX Uring A -50
+KPX Uring Aacute -50
+KPX Uring Abreve -50
+KPX Uring Acircumflex -50
+KPX Uring Adieresis -50
+KPX Uring Agrave -50
+KPX Uring Amacron -50
+KPX Uring Aogonek -50
+KPX Uring Aring -50
+KPX Uring Atilde -50
+KPX Uring comma -30
+KPX Uring period -30
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -50
+KPX V Gbreve -50
+KPX V Gcommaaccent -50
+KPX V O -50
+KPX V Oacute -50
+KPX V Ocircumflex -50
+KPX V Odieresis -50
+KPX V Ograve -50
+KPX V Ohungarumlaut -50
+KPX V Omacron -50
+KPX V Oslash -50
+KPX V Otilde -50
+KPX V a -60
+KPX V aacute -60
+KPX V abreve -60
+KPX V acircumflex -60
+KPX V adieresis -60
+KPX V agrave -60
+KPX V amacron -60
+KPX V aogonek -60
+KPX V aring -60
+KPX V atilde -60
+KPX V colon -40
+KPX V comma -120
+KPX V e -50
+KPX V eacute -50
+KPX V ecaron -50
+KPX V ecircumflex -50
+KPX V edieresis -50
+KPX V edotaccent -50
+KPX V egrave -50
+KPX V emacron -50
+KPX V eogonek -50
+KPX V hyphen -80
+KPX V o -90
+KPX V oacute -90
+KPX V ocircumflex -90
+KPX V odieresis -90
+KPX V ograve -90
+KPX V ohungarumlaut -90
+KPX V omacron -90
+KPX V oslash -90
+KPX V otilde -90
+KPX V period -120
+KPX V semicolon -40
+KPX V u -60
+KPX V uacute -60
+KPX V ucircumflex -60
+KPX V udieresis -60
+KPX V ugrave -60
+KPX V uhungarumlaut -60
+KPX V umacron -60
+KPX V uogonek -60
+KPX V uring -60
+KPX W A -60
+KPX W Aacute -60
+KPX W Abreve -60
+KPX W Acircumflex -60
+KPX W Adieresis -60
+KPX W Agrave -60
+KPX W Amacron -60
+KPX W Aogonek -60
+KPX W Aring -60
+KPX W Atilde -60
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W colon -10
+KPX W comma -80
+KPX W e -35
+KPX W eacute -35
+KPX W ecaron -35
+KPX W ecircumflex -35
+KPX W edieresis -35
+KPX W edotaccent -35
+KPX W egrave -35
+KPX W emacron -35
+KPX W eogonek -35
+KPX W hyphen -40
+KPX W o -60
+KPX W oacute -60
+KPX W ocircumflex -60
+KPX W odieresis -60
+KPX W ograve -60
+KPX W ohungarumlaut -60
+KPX W omacron -60
+KPX W oslash -60
+KPX W otilde -60
+KPX W period -80
+KPX W semicolon -10
+KPX W u -45
+KPX W uacute -45
+KPX W ucircumflex -45
+KPX W udieresis -45
+KPX W ugrave -45
+KPX W uhungarumlaut -45
+KPX W umacron -45
+KPX W uogonek -45
+KPX W uring -45
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -70
+KPX Y Oacute -70
+KPX Y Ocircumflex -70
+KPX Y Odieresis -70
+KPX Y Ograve -70
+KPX Y Ohungarumlaut -70
+KPX Y Omacron -70
+KPX Y Oslash -70
+KPX Y Otilde -70
+KPX Y a -90
+KPX Y aacute -90
+KPX Y abreve -90
+KPX Y acircumflex -90
+KPX Y adieresis -90
+KPX Y agrave -90
+KPX Y amacron -90
+KPX Y aogonek -90
+KPX Y aring -90
+KPX Y atilde -90
+KPX Y colon -50
+KPX Y comma -100
+KPX Y e -80
+KPX Y eacute -80
+KPX Y ecaron -80
+KPX Y ecircumflex -80
+KPX Y edieresis -80
+KPX Y edotaccent -80
+KPX Y egrave -80
+KPX Y emacron -80
+KPX Y eogonek -80
+KPX Y o -100
+KPX Y oacute -100
+KPX Y ocircumflex -100
+KPX Y odieresis -100
+KPX Y ograve -100
+KPX Y ohungarumlaut -100
+KPX Y omacron -100
+KPX Y oslash -100
+KPX Y otilde -100
+KPX Y period -100
+KPX Y semicolon -50
+KPX Y u -100
+KPX Y uacute -100
+KPX Y ucircumflex -100
+KPX Y udieresis -100
+KPX Y ugrave -100
+KPX Y uhungarumlaut -100
+KPX Y umacron -100
+KPX Y uogonek -100
+KPX Y uring -100
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -70
+KPX Yacute Oacute -70
+KPX Yacute Ocircumflex -70
+KPX Yacute Odieresis -70
+KPX Yacute Ograve -70
+KPX Yacute Ohungarumlaut -70
+KPX Yacute Omacron -70
+KPX Yacute Oslash -70
+KPX Yacute Otilde -70
+KPX Yacute a -90
+KPX Yacute aacute -90
+KPX Yacute abreve -90
+KPX Yacute acircumflex -90
+KPX Yacute adieresis -90
+KPX Yacute agrave -90
+KPX Yacute amacron -90
+KPX Yacute aogonek -90
+KPX Yacute aring -90
+KPX Yacute atilde -90
+KPX Yacute colon -50
+KPX Yacute comma -100
+KPX Yacute e -80
+KPX Yacute eacute -80
+KPX Yacute ecaron -80
+KPX Yacute ecircumflex -80
+KPX Yacute edieresis -80
+KPX Yacute edotaccent -80
+KPX Yacute egrave -80
+KPX Yacute emacron -80
+KPX Yacute eogonek -80
+KPX Yacute o -100
+KPX Yacute oacute -100
+KPX Yacute ocircumflex -100
+KPX Yacute odieresis -100
+KPX Yacute ograve -100
+KPX Yacute ohungarumlaut -100
+KPX Yacute omacron -100
+KPX Yacute oslash -100
+KPX Yacute otilde -100
+KPX Yacute period -100
+KPX Yacute semicolon -50
+KPX Yacute u -100
+KPX Yacute uacute -100
+KPX Yacute ucircumflex -100
+KPX Yacute udieresis -100
+KPX Yacute ugrave -100
+KPX Yacute uhungarumlaut -100
+KPX Yacute umacron -100
+KPX Yacute uogonek -100
+KPX Yacute uring -100
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -70
+KPX Ydieresis Oacute -70
+KPX Ydieresis Ocircumflex -70
+KPX Ydieresis Odieresis -70
+KPX Ydieresis Ograve -70
+KPX Ydieresis Ohungarumlaut -70
+KPX Ydieresis Omacron -70
+KPX Ydieresis Oslash -70
+KPX Ydieresis Otilde -70
+KPX Ydieresis a -90
+KPX Ydieresis aacute -90
+KPX Ydieresis abreve -90
+KPX Ydieresis acircumflex -90
+KPX Ydieresis adieresis -90
+KPX Ydieresis agrave -90
+KPX Ydieresis amacron -90
+KPX Ydieresis aogonek -90
+KPX Ydieresis aring -90
+KPX Ydieresis atilde -90
+KPX Ydieresis colon -50
+KPX Ydieresis comma -100
+KPX Ydieresis e -80
+KPX Ydieresis eacute -80
+KPX Ydieresis ecaron -80
+KPX Ydieresis ecircumflex -80
+KPX Ydieresis edieresis -80
+KPX Ydieresis edotaccent -80
+KPX Ydieresis egrave -80
+KPX Ydieresis emacron -80
+KPX Ydieresis eogonek -80
+KPX Ydieresis o -100
+KPX Ydieresis oacute -100
+KPX Ydieresis ocircumflex -100
+KPX Ydieresis odieresis -100
+KPX Ydieresis ograve -100
+KPX Ydieresis ohungarumlaut -100
+KPX Ydieresis omacron -100
+KPX Ydieresis oslash -100
+KPX Ydieresis otilde -100
+KPX Ydieresis period -100
+KPX Ydieresis semicolon -50
+KPX Ydieresis u -100
+KPX Ydieresis uacute -100
+KPX Ydieresis ucircumflex -100
+KPX Ydieresis udieresis -100
+KPX Ydieresis ugrave -100
+KPX Ydieresis uhungarumlaut -100
+KPX Ydieresis umacron -100
+KPX Ydieresis uogonek -100
+KPX Ydieresis uring -100
+KPX a g -10
+KPX a gbreve -10
+KPX a gcommaaccent -10
+KPX a v -15
+KPX a w -15
+KPX a y -20
+KPX a yacute -20
+KPX a ydieresis -20
+KPX aacute g -10
+KPX aacute gbreve -10
+KPX aacute gcommaaccent -10
+KPX aacute v -15
+KPX aacute w -15
+KPX aacute y -20
+KPX aacute yacute -20
+KPX aacute ydieresis -20
+KPX abreve g -10
+KPX abreve gbreve -10
+KPX abreve gcommaaccent -10
+KPX abreve v -15
+KPX abreve w -15
+KPX abreve y -20
+KPX abreve yacute -20
+KPX abreve ydieresis -20
+KPX acircumflex g -10
+KPX acircumflex gbreve -10
+KPX acircumflex gcommaaccent -10
+KPX acircumflex v -15
+KPX acircumflex w -15
+KPX acircumflex y -20
+KPX acircumflex yacute -20
+KPX acircumflex ydieresis -20
+KPX adieresis g -10
+KPX adieresis gbreve -10
+KPX adieresis gcommaaccent -10
+KPX adieresis v -15
+KPX adieresis w -15
+KPX adieresis y -20
+KPX adieresis yacute -20
+KPX adieresis ydieresis -20
+KPX agrave g -10
+KPX agrave gbreve -10
+KPX agrave gcommaaccent -10
+KPX agrave v -15
+KPX agrave w -15
+KPX agrave y -20
+KPX agrave yacute -20
+KPX agrave ydieresis -20
+KPX amacron g -10
+KPX amacron gbreve -10
+KPX amacron gcommaaccent -10
+KPX amacron v -15
+KPX amacron w -15
+KPX amacron y -20
+KPX amacron yacute -20
+KPX amacron ydieresis -20
+KPX aogonek g -10
+KPX aogonek gbreve -10
+KPX aogonek gcommaaccent -10
+KPX aogonek v -15
+KPX aogonek w -15
+KPX aogonek y -20
+KPX aogonek yacute -20
+KPX aogonek ydieresis -20
+KPX aring g -10
+KPX aring gbreve -10
+KPX aring gcommaaccent -10
+KPX aring v -15
+KPX aring w -15
+KPX aring y -20
+KPX aring yacute -20
+KPX aring ydieresis -20
+KPX atilde g -10
+KPX atilde gbreve -10
+KPX atilde gcommaaccent -10
+KPX atilde v -15
+KPX atilde w -15
+KPX atilde y -20
+KPX atilde yacute -20
+KPX atilde ydieresis -20
+KPX b l -10
+KPX b lacute -10
+KPX b lcommaaccent -10
+KPX b lslash -10
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c h -10
+KPX c k -20
+KPX c kcommaaccent -20
+KPX c l -20
+KPX c lacute -20
+KPX c lcommaaccent -20
+KPX c lslash -20
+KPX c y -10
+KPX c yacute -10
+KPX c ydieresis -10
+KPX cacute h -10
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX cacute l -20
+KPX cacute lacute -20
+KPX cacute lcommaaccent -20
+KPX cacute lslash -20
+KPX cacute y -10
+KPX cacute yacute -10
+KPX cacute ydieresis -10
+KPX ccaron h -10
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccaron l -20
+KPX ccaron lacute -20
+KPX ccaron lcommaaccent -20
+KPX ccaron lslash -20
+KPX ccaron y -10
+KPX ccaron yacute -10
+KPX ccaron ydieresis -10
+KPX ccedilla h -10
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX ccedilla l -20
+KPX ccedilla lacute -20
+KPX ccedilla lcommaaccent -20
+KPX ccedilla lslash -20
+KPX ccedilla y -10
+KPX ccedilla yacute -10
+KPX ccedilla ydieresis -10
+KPX colon space -40
+KPX comma quotedblright -120
+KPX comma quoteright -120
+KPX comma space -40
+KPX d d -10
+KPX d dcroat -10
+KPX d v -15
+KPX d w -15
+KPX d y -15
+KPX d yacute -15
+KPX d ydieresis -15
+KPX dcroat d -10
+KPX dcroat dcroat -10
+KPX dcroat v -15
+KPX dcroat w -15
+KPX dcroat y -15
+KPX dcroat yacute -15
+KPX dcroat ydieresis -15
+KPX e comma 10
+KPX e period 20
+KPX e v -15
+KPX e w -15
+KPX e x -15
+KPX e y -15
+KPX e yacute -15
+KPX e ydieresis -15
+KPX eacute comma 10
+KPX eacute period 20
+KPX eacute v -15
+KPX eacute w -15
+KPX eacute x -15
+KPX eacute y -15
+KPX eacute yacute -15
+KPX eacute ydieresis -15
+KPX ecaron comma 10
+KPX ecaron period 20
+KPX ecaron v -15
+KPX ecaron w -15
+KPX ecaron x -15
+KPX ecaron y -15
+KPX ecaron yacute -15
+KPX ecaron ydieresis -15
+KPX ecircumflex comma 10
+KPX ecircumflex period 20
+KPX ecircumflex v -15
+KPX ecircumflex w -15
+KPX ecircumflex x -15
+KPX ecircumflex y -15
+KPX ecircumflex yacute -15
+KPX ecircumflex ydieresis -15
+KPX edieresis comma 10
+KPX edieresis period 20
+KPX edieresis v -15
+KPX edieresis w -15
+KPX edieresis x -15
+KPX edieresis y -15
+KPX edieresis yacute -15
+KPX edieresis ydieresis -15
+KPX edotaccent comma 10
+KPX edotaccent period 20
+KPX edotaccent v -15
+KPX edotaccent w -15
+KPX edotaccent x -15
+KPX edotaccent y -15
+KPX edotaccent yacute -15
+KPX edotaccent ydieresis -15
+KPX egrave comma 10
+KPX egrave period 20
+KPX egrave v -15
+KPX egrave w -15
+KPX egrave x -15
+KPX egrave y -15
+KPX egrave yacute -15
+KPX egrave ydieresis -15
+KPX emacron comma 10
+KPX emacron period 20
+KPX emacron v -15
+KPX emacron w -15
+KPX emacron x -15
+KPX emacron y -15
+KPX emacron yacute -15
+KPX emacron ydieresis -15
+KPX eogonek comma 10
+KPX eogonek period 20
+KPX eogonek v -15
+KPX eogonek w -15
+KPX eogonek x -15
+KPX eogonek y -15
+KPX eogonek yacute -15
+KPX eogonek ydieresis -15
+KPX f comma -10
+KPX f e -10
+KPX f eacute -10
+KPX f ecaron -10
+KPX f ecircumflex -10
+KPX f edieresis -10
+KPX f edotaccent -10
+KPX f egrave -10
+KPX f emacron -10
+KPX f eogonek -10
+KPX f o -20
+KPX f oacute -20
+KPX f ocircumflex -20
+KPX f odieresis -20
+KPX f ograve -20
+KPX f ohungarumlaut -20
+KPX f omacron -20
+KPX f oslash -20
+KPX f otilde -20
+KPX f period -10
+KPX f quotedblright 30
+KPX f quoteright 30
+KPX g e 10
+KPX g eacute 10
+KPX g ecaron 10
+KPX g ecircumflex 10
+KPX g edieresis 10
+KPX g edotaccent 10
+KPX g egrave 10
+KPX g emacron 10
+KPX g eogonek 10
+KPX g g -10
+KPX g gbreve -10
+KPX g gcommaaccent -10
+KPX gbreve e 10
+KPX gbreve eacute 10
+KPX gbreve ecaron 10
+KPX gbreve ecircumflex 10
+KPX gbreve edieresis 10
+KPX gbreve edotaccent 10
+KPX gbreve egrave 10
+KPX gbreve emacron 10
+KPX gbreve eogonek 10
+KPX gbreve g -10
+KPX gbreve gbreve -10
+KPX gbreve gcommaaccent -10
+KPX gcommaaccent e 10
+KPX gcommaaccent eacute 10
+KPX gcommaaccent ecaron 10
+KPX gcommaaccent ecircumflex 10
+KPX gcommaaccent edieresis 10
+KPX gcommaaccent edotaccent 10
+KPX gcommaaccent egrave 10
+KPX gcommaaccent emacron 10
+KPX gcommaaccent eogonek 10
+KPX gcommaaccent g -10
+KPX gcommaaccent gbreve -10
+KPX gcommaaccent gcommaaccent -10
+KPX h y -20
+KPX h yacute -20
+KPX h ydieresis -20
+KPX k o -15
+KPX k oacute -15
+KPX k ocircumflex -15
+KPX k odieresis -15
+KPX k ograve -15
+KPX k ohungarumlaut -15
+KPX k omacron -15
+KPX k oslash -15
+KPX k otilde -15
+KPX kcommaaccent o -15
+KPX kcommaaccent oacute -15
+KPX kcommaaccent ocircumflex -15
+KPX kcommaaccent odieresis -15
+KPX kcommaaccent ograve -15
+KPX kcommaaccent ohungarumlaut -15
+KPX kcommaaccent omacron -15
+KPX kcommaaccent oslash -15
+KPX kcommaaccent otilde -15
+KPX l w -15
+KPX l y -15
+KPX l yacute -15
+KPX l ydieresis -15
+KPX lacute w -15
+KPX lacute y -15
+KPX lacute yacute -15
+KPX lacute ydieresis -15
+KPX lcommaaccent w -15
+KPX lcommaaccent y -15
+KPX lcommaaccent yacute -15
+KPX lcommaaccent ydieresis -15
+KPX lslash w -15
+KPX lslash y -15
+KPX lslash yacute -15
+KPX lslash ydieresis -15
+KPX m u -20
+KPX m uacute -20
+KPX m ucircumflex -20
+KPX m udieresis -20
+KPX m ugrave -20
+KPX m uhungarumlaut -20
+KPX m umacron -20
+KPX m uogonek -20
+KPX m uring -20
+KPX m y -30
+KPX m yacute -30
+KPX m ydieresis -30
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -40
+KPX n y -20
+KPX n yacute -20
+KPX n ydieresis -20
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -40
+KPX nacute y -20
+KPX nacute yacute -20
+KPX nacute ydieresis -20
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -40
+KPX ncaron y -20
+KPX ncaron yacute -20
+KPX ncaron ydieresis -20
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -40
+KPX ncommaaccent y -20
+KPX ncommaaccent yacute -20
+KPX ncommaaccent ydieresis -20
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -40
+KPX ntilde y -20
+KPX ntilde yacute -20
+KPX ntilde ydieresis -20
+KPX o v -20
+KPX o w -15
+KPX o x -30
+KPX o y -20
+KPX o yacute -20
+KPX o ydieresis -20
+KPX oacute v -20
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -20
+KPX oacute yacute -20
+KPX oacute ydieresis -20
+KPX ocircumflex v -20
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -20
+KPX ocircumflex yacute -20
+KPX ocircumflex ydieresis -20
+KPX odieresis v -20
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -20
+KPX odieresis yacute -20
+KPX odieresis ydieresis -20
+KPX ograve v -20
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -20
+KPX ograve yacute -20
+KPX ograve ydieresis -20
+KPX ohungarumlaut v -20
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -20
+KPX ohungarumlaut yacute -20
+KPX ohungarumlaut ydieresis -20
+KPX omacron v -20
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -20
+KPX omacron yacute -20
+KPX omacron ydieresis -20
+KPX oslash v -20
+KPX oslash w -15
+KPX oslash x -30
+KPX oslash y -20
+KPX oslash yacute -20
+KPX oslash ydieresis -20
+KPX otilde v -20
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -20
+KPX otilde yacute -20
+KPX otilde ydieresis -20
+KPX p y -15
+KPX p yacute -15
+KPX p ydieresis -15
+KPX period quotedblright -120
+KPX period quoteright -120
+KPX period space -40
+KPX quotedblright space -80
+KPX quoteleft quoteleft -46
+KPX quoteright d -80
+KPX quoteright dcroat -80
+KPX quoteright l -20
+KPX quoteright lacute -20
+KPX quoteright lcommaaccent -20
+KPX quoteright lslash -20
+KPX quoteright quoteright -46
+KPX quoteright r -40
+KPX quoteright racute -40
+KPX quoteright rcaron -40
+KPX quoteright rcommaaccent -40
+KPX quoteright s -60
+KPX quoteright sacute -60
+KPX quoteright scaron -60
+KPX quoteright scedilla -60
+KPX quoteright scommaaccent -60
+KPX quoteright space -80
+KPX quoteright v -20
+KPX r c -20
+KPX r cacute -20
+KPX r ccaron -20
+KPX r ccedilla -20
+KPX r comma -60
+KPX r d -20
+KPX r dcroat -20
+KPX r g -15
+KPX r gbreve -15
+KPX r gcommaaccent -15
+KPX r hyphen -20
+KPX r o -20
+KPX r oacute -20
+KPX r ocircumflex -20
+KPX r odieresis -20
+KPX r ograve -20
+KPX r ohungarumlaut -20
+KPX r omacron -20
+KPX r oslash -20
+KPX r otilde -20
+KPX r period -60
+KPX r q -20
+KPX r s -15
+KPX r sacute -15
+KPX r scaron -15
+KPX r scedilla -15
+KPX r scommaaccent -15
+KPX r t 20
+KPX r tcommaaccent 20
+KPX r v 10
+KPX r y 10
+KPX r yacute 10
+KPX r ydieresis 10
+KPX racute c -20
+KPX racute cacute -20
+KPX racute ccaron -20
+KPX racute ccedilla -20
+KPX racute comma -60
+KPX racute d -20
+KPX racute dcroat -20
+KPX racute g -15
+KPX racute gbreve -15
+KPX racute gcommaaccent -15
+KPX racute hyphen -20
+KPX racute o -20
+KPX racute oacute -20
+KPX racute ocircumflex -20
+KPX racute odieresis -20
+KPX racute ograve -20
+KPX racute ohungarumlaut -20
+KPX racute omacron -20
+KPX racute oslash -20
+KPX racute otilde -20
+KPX racute period -60
+KPX racute q -20
+KPX racute s -15
+KPX racute sacute -15
+KPX racute scaron -15
+KPX racute scedilla -15
+KPX racute scommaaccent -15
+KPX racute t 20
+KPX racute tcommaaccent 20
+KPX racute v 10
+KPX racute y 10
+KPX racute yacute 10
+KPX racute ydieresis 10
+KPX rcaron c -20
+KPX rcaron cacute -20
+KPX rcaron ccaron -20
+KPX rcaron ccedilla -20
+KPX rcaron comma -60
+KPX rcaron d -20
+KPX rcaron dcroat -20
+KPX rcaron g -15
+KPX rcaron gbreve -15
+KPX rcaron gcommaaccent -15
+KPX rcaron hyphen -20
+KPX rcaron o -20
+KPX rcaron oacute -20
+KPX rcaron ocircumflex -20
+KPX rcaron odieresis -20
+KPX rcaron ograve -20
+KPX rcaron ohungarumlaut -20
+KPX rcaron omacron -20
+KPX rcaron oslash -20
+KPX rcaron otilde -20
+KPX rcaron period -60
+KPX rcaron q -20
+KPX rcaron s -15
+KPX rcaron sacute -15
+KPX rcaron scaron -15
+KPX rcaron scedilla -15
+KPX rcaron scommaaccent -15
+KPX rcaron t 20
+KPX rcaron tcommaaccent 20
+KPX rcaron v 10
+KPX rcaron y 10
+KPX rcaron yacute 10
+KPX rcaron ydieresis 10
+KPX rcommaaccent c -20
+KPX rcommaaccent cacute -20
+KPX rcommaaccent ccaron -20
+KPX rcommaaccent ccedilla -20
+KPX rcommaaccent comma -60
+KPX rcommaaccent d -20
+KPX rcommaaccent dcroat -20
+KPX rcommaaccent g -15
+KPX rcommaaccent gbreve -15
+KPX rcommaaccent gcommaaccent -15
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent o -20
+KPX rcommaaccent oacute -20
+KPX rcommaaccent ocircumflex -20
+KPX rcommaaccent odieresis -20
+KPX rcommaaccent ograve -20
+KPX rcommaaccent ohungarumlaut -20
+KPX rcommaaccent omacron -20
+KPX rcommaaccent oslash -20
+KPX rcommaaccent otilde -20
+KPX rcommaaccent period -60
+KPX rcommaaccent q -20
+KPX rcommaaccent s -15
+KPX rcommaaccent sacute -15
+KPX rcommaaccent scaron -15
+KPX rcommaaccent scedilla -15
+KPX rcommaaccent scommaaccent -15
+KPX rcommaaccent t 20
+KPX rcommaaccent tcommaaccent 20
+KPX rcommaaccent v 10
+KPX rcommaaccent y 10
+KPX rcommaaccent yacute 10
+KPX rcommaaccent ydieresis 10
+KPX s w -15
+KPX sacute w -15
+KPX scaron w -15
+KPX scedilla w -15
+KPX scommaaccent w -15
+KPX semicolon space -40
+KPX space T -100
+KPX space Tcaron -100
+KPX space Tcommaaccent -100
+KPX space V -80
+KPX space W -80
+KPX space Y -120
+KPX space Yacute -120
+KPX space Ydieresis -120
+KPX space quotedblleft -80
+KPX space quoteleft -60
+KPX v a -20
+KPX v aacute -20
+KPX v abreve -20
+KPX v acircumflex -20
+KPX v adieresis -20
+KPX v agrave -20
+KPX v amacron -20
+KPX v aogonek -20
+KPX v aring -20
+KPX v atilde -20
+KPX v comma -80
+KPX v o -30
+KPX v oacute -30
+KPX v ocircumflex -30
+KPX v odieresis -30
+KPX v ograve -30
+KPX v ohungarumlaut -30
+KPX v omacron -30
+KPX v oslash -30
+KPX v otilde -30
+KPX v period -80
+KPX w comma -40
+KPX w o -20
+KPX w oacute -20
+KPX w ocircumflex -20
+KPX w odieresis -20
+KPX w ograve -20
+KPX w ohungarumlaut -20
+KPX w omacron -20
+KPX w oslash -20
+KPX w otilde -20
+KPX w period -40
+KPX x e -10
+KPX x eacute -10
+KPX x ecaron -10
+KPX x ecircumflex -10
+KPX x edieresis -10
+KPX x edotaccent -10
+KPX x egrave -10
+KPX x emacron -10
+KPX x eogonek -10
+KPX y a -30
+KPX y aacute -30
+KPX y abreve -30
+KPX y acircumflex -30
+KPX y adieresis -30
+KPX y agrave -30
+KPX y amacron -30
+KPX y aogonek -30
+KPX y aring -30
+KPX y atilde -30
+KPX y comma -80
+KPX y e -10
+KPX y eacute -10
+KPX y ecaron -10
+KPX y ecircumflex -10
+KPX y edieresis -10
+KPX y edotaccent -10
+KPX y egrave -10
+KPX y emacron -10
+KPX y eogonek -10
+KPX y o -25
+KPX y oacute -25
+KPX y ocircumflex -25
+KPX y odieresis -25
+KPX y ograve -25
+KPX y ohungarumlaut -25
+KPX y omacron -25
+KPX y oslash -25
+KPX y otilde -25
+KPX y period -80
+KPX yacute a -30
+KPX yacute aacute -30
+KPX yacute abreve -30
+KPX yacute acircumflex -30
+KPX yacute adieresis -30
+KPX yacute agrave -30
+KPX yacute amacron -30
+KPX yacute aogonek -30
+KPX yacute aring -30
+KPX yacute atilde -30
+KPX yacute comma -80
+KPX yacute e -10
+KPX yacute eacute -10
+KPX yacute ecaron -10
+KPX yacute ecircumflex -10
+KPX yacute edieresis -10
+KPX yacute edotaccent -10
+KPX yacute egrave -10
+KPX yacute emacron -10
+KPX yacute eogonek -10
+KPX yacute o -25
+KPX yacute oacute -25
+KPX yacute ocircumflex -25
+KPX yacute odieresis -25
+KPX yacute ograve -25
+KPX yacute ohungarumlaut -25
+KPX yacute omacron -25
+KPX yacute oslash -25
+KPX yacute otilde -25
+KPX yacute period -80
+KPX ydieresis a -30
+KPX ydieresis aacute -30
+KPX ydieresis abreve -30
+KPX ydieresis acircumflex -30
+KPX ydieresis adieresis -30
+KPX ydieresis agrave -30
+KPX ydieresis amacron -30
+KPX ydieresis aogonek -30
+KPX ydieresis aring -30
+KPX ydieresis atilde -30
+KPX ydieresis comma -80
+KPX ydieresis e -10
+KPX ydieresis eacute -10
+KPX ydieresis ecaron -10
+KPX ydieresis ecircumflex -10
+KPX ydieresis edieresis -10
+KPX ydieresis edotaccent -10
+KPX ydieresis egrave -10
+KPX ydieresis emacron -10
+KPX ydieresis eogonek -10
+KPX ydieresis o -25
+KPX ydieresis oacute -25
+KPX ydieresis ocircumflex -25
+KPX ydieresis odieresis -25
+KPX ydieresis ograve -25
+KPX ydieresis ohungarumlaut -25
+KPX ydieresis omacron -25
+KPX ydieresis oslash -25
+KPX ydieresis otilde -25
+KPX ydieresis period -80
+KPX z e 10
+KPX z eacute 10
+KPX z ecaron 10
+KPX z ecircumflex 10
+KPX z edieresis 10
+KPX z edotaccent 10
+KPX z egrave 10
+KPX z emacron 10
+KPX z eogonek 10
+KPX zacute e 10
+KPX zacute eacute 10
+KPX zacute ecaron 10
+KPX zacute ecircumflex 10
+KPX zacute edieresis 10
+KPX zacute edotaccent 10
+KPX zacute egrave 10
+KPX zacute emacron 10
+KPX zacute eogonek 10
+KPX zcaron e 10
+KPX zcaron eacute 10
+KPX zcaron ecaron 10
+KPX zcaron ecircumflex 10
+KPX zcaron edieresis 10
+KPX zcaron edotaccent 10
+KPX zcaron egrave 10
+KPX zcaron emacron 10
+KPX zcaron eogonek 10
+KPX zdotaccent e 10
+KPX zdotaccent eacute 10
+KPX zdotaccent ecaron 10
+KPX zdotaccent ecircumflex 10
+KPX zdotaccent edieresis 10
+KPX zdotaccent edotaccent 10
+KPX zdotaccent egrave 10
+KPX zdotaccent emacron 10
+KPX zdotaccent eogonek 10
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-BoldOblique.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-BoldOblique.afm
new file mode 100644
index 0000000..e98a0bb
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-BoldOblique.afm
@@ -0,0 +1,2829 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:45:12 1997
+Comment UniqueID 43053
+Comment VMusage 14482 68586
+FontName Helvetica-BoldOblique
+FullName Helvetica Bold Oblique
+FamilyName Helvetica
+Weight Bold
+ItalicAngle -12
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -174 -228 1114 962
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 718
+XHeight 532
+Ascender 718
+Descender -207
+StdHW 118
+StdVW 140
+StartCharMetrics 317
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 94 0 397 718 ;
+C 34 ; WX 474 ; N quotedbl ; B 193 447 529 718 ;
+C 35 ; WX 556 ; N numbersign ; B 60 0 644 698 ;
+C 36 ; WX 556 ; N dollar ; B 67 -115 622 775 ;
+C 37 ; WX 889 ; N percent ; B 136 -19 901 710 ;
+C 38 ; WX 722 ; N ampersand ; B 89 -19 732 718 ;
+C 146 ; WX 278 ; N quoteright ; B 167 445 362 718 ;
+C 40 ; WX 333 ; N parenleft ; B 76 -208 470 734 ;
+C 41 ; WX 333 ; N parenright ; B -25 -208 369 734 ;
+C 42 ; WX 389 ; N asterisk ; B 146 387 481 718 ;
+C 43 ; WX 584 ; N plus ; B 82 0 610 506 ;
+C 44 ; WX 278 ; N comma ; B 28 -168 245 146 ;
+C 45 ; WX 333 ; N hyphen ; B 73 215 379 345 ;
+C 173 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 46 ; WX 278 ; N period ; B 64 0 245 146 ;
+C 47 ; WX 278 ; N slash ; B -37 -19 468 737 ;
+C 48 ; WX 556 ; N zero ; B 86 -19 617 710 ;
+C 49 ; WX 556 ; N one ; B 173 0 529 710 ;
+C 50 ; WX 556 ; N two ; B 26 0 619 710 ;
+C 51 ; WX 556 ; N three ; B 65 -19 608 710 ;
+C 52 ; WX 556 ; N four ; B 60 0 598 710 ;
+C 53 ; WX 556 ; N five ; B 64 -19 636 698 ;
+C 54 ; WX 556 ; N six ; B 85 -19 619 710 ;
+C 55 ; WX 556 ; N seven ; B 125 0 676 698 ;
+C 56 ; WX 556 ; N eight ; B 69 -19 616 710 ;
+C 57 ; WX 556 ; N nine ; B 78 -19 615 710 ;
+C 58 ; WX 333 ; N colon ; B 92 0 351 512 ;
+C 59 ; WX 333 ; N semicolon ; B 56 -168 351 512 ;
+C 60 ; WX 584 ; N less ; B 82 -8 655 514 ;
+C 61 ; WX 584 ; N equal ; B 58 87 633 419 ;
+C 62 ; WX 584 ; N greater ; B 36 -8 609 514 ;
+C 63 ; WX 611 ; N question ; B 165 0 671 727 ;
+C 64 ; WX 975 ; N at ; B 186 -19 954 737 ;
+C 65 ; WX 722 ; N A ; B 20 0 702 718 ;
+C 66 ; WX 722 ; N B ; B 76 0 764 718 ;
+C 67 ; WX 722 ; N C ; B 107 -19 789 737 ;
+C 68 ; WX 722 ; N D ; B 76 0 777 718 ;
+C 69 ; WX 667 ; N E ; B 76 0 757 718 ;
+C 70 ; WX 611 ; N F ; B 76 0 740 718 ;
+C 71 ; WX 778 ; N G ; B 108 -19 817 737 ;
+C 72 ; WX 722 ; N H ; B 71 0 804 718 ;
+C 73 ; WX 278 ; N I ; B 64 0 367 718 ;
+C 74 ; WX 556 ; N J ; B 60 -18 637 718 ;
+C 75 ; WX 722 ; N K ; B 87 0 858 718 ;
+C 76 ; WX 611 ; N L ; B 76 0 611 718 ;
+C 77 ; WX 833 ; N M ; B 69 0 918 718 ;
+C 78 ; WX 722 ; N N ; B 69 0 807 718 ;
+C 79 ; WX 778 ; N O ; B 107 -19 823 737 ;
+C 80 ; WX 667 ; N P ; B 76 0 738 718 ;
+C 81 ; WX 778 ; N Q ; B 107 -52 823 737 ;
+C 82 ; WX 722 ; N R ; B 76 0 778 718 ;
+C 83 ; WX 667 ; N S ; B 81 -19 718 737 ;
+C 84 ; WX 611 ; N T ; B 140 0 751 718 ;
+C 85 ; WX 722 ; N U ; B 116 -19 804 718 ;
+C 86 ; WX 667 ; N V ; B 172 0 801 718 ;
+C 87 ; WX 944 ; N W ; B 169 0 1082 718 ;
+C 88 ; WX 667 ; N X ; B 14 0 791 718 ;
+C 89 ; WX 667 ; N Y ; B 168 0 806 718 ;
+C 90 ; WX 611 ; N Z ; B 25 0 737 718 ;
+C 91 ; WX 333 ; N bracketleft ; B 21 -196 462 722 ;
+C 92 ; WX 278 ; N backslash ; B 124 -19 307 737 ;
+C 93 ; WX 333 ; N bracketright ; B -18 -196 423 722 ;
+C 94 ; WX 584 ; N asciicircum ; B 131 323 591 698 ;
+C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ;
+C 145 ; WX 278 ; N quoteleft ; B 165 454 361 727 ;
+C 97 ; WX 556 ; N a ; B 55 -14 583 546 ;
+C 98 ; WX 611 ; N b ; B 61 -14 645 718 ;
+C 99 ; WX 556 ; N c ; B 79 -14 599 546 ;
+C 100 ; WX 611 ; N d ; B 82 -14 704 718 ;
+C 101 ; WX 556 ; N e ; B 70 -14 593 546 ;
+C 102 ; WX 333 ; N f ; B 87 0 469 727 ; L i fi ; L l fl ;
+C 103 ; WX 611 ; N g ; B 38 -217 666 546 ;
+C 104 ; WX 611 ; N h ; B 65 0 629 718 ;
+C 105 ; WX 278 ; N i ; B 69 0 363 725 ;
+C 106 ; WX 278 ; N j ; B -42 -214 363 725 ;
+C 107 ; WX 556 ; N k ; B 69 0 670 718 ;
+C 108 ; WX 278 ; N l ; B 69 0 362 718 ;
+C 109 ; WX 889 ; N m ; B 64 0 909 546 ;
+C 110 ; WX 611 ; N n ; B 65 0 629 546 ;
+C 111 ; WX 611 ; N o ; B 82 -14 643 546 ;
+C 112 ; WX 611 ; N p ; B 18 -207 645 546 ;
+C 113 ; WX 611 ; N q ; B 80 -207 665 546 ;
+C 114 ; WX 389 ; N r ; B 64 0 489 546 ;
+C 115 ; WX 556 ; N s ; B 63 -14 584 546 ;
+C 116 ; WX 333 ; N t ; B 100 -6 422 676 ;
+C 117 ; WX 611 ; N u ; B 98 -14 658 532 ;
+C 118 ; WX 556 ; N v ; B 126 0 656 532 ;
+C 119 ; WX 778 ; N w ; B 123 0 882 532 ;
+C 120 ; WX 556 ; N x ; B 15 0 648 532 ;
+C 121 ; WX 556 ; N y ; B 42 -214 652 532 ;
+C 122 ; WX 500 ; N z ; B 20 0 583 532 ;
+C 123 ; WX 389 ; N braceleft ; B 94 -196 518 722 ;
+C 124 ; WX 280 ; N bar ; B 36 -225 361 775 ;
+C 125 ; WX 389 ; N braceright ; B -18 -196 407 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 115 163 577 343 ;
+C 161 ; WX 333 ; N exclamdown ; B 50 -186 353 532 ;
+C 162 ; WX 556 ; N cent ; B 79 -118 599 628 ;
+C 163 ; WX 556 ; N sterling ; B 50 -16 635 718 ;
+C -1 ; WX 167 ; N fraction ; B -174 -19 487 710 ;
+C 165 ; WX 556 ; N yen ; B 60 0 713 698 ;
+C 131 ; WX 556 ; N florin ; B -50 -210 669 737 ;
+C 167 ; WX 556 ; N section ; B 61 -184 598 727 ;
+C 164 ; WX 556 ; N currency ; B 27 76 680 636 ;
+C 39 ; WX 238 ; N quotesingle ; B 165 447 321 718 ;
+C 147 ; WX 500 ; N quotedblleft ; B 160 454 588 727 ;
+C 171 ; WX 556 ; N guillemotleft ; B 135 76 571 484 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 130 76 353 484 ;
+C 155 ; WX 333 ; N guilsinglright ; B 99 76 322 484 ;
+C -1 ; WX 611 ; N fi ; B 87 0 696 727 ;
+C -1 ; WX 611 ; N fl ; B 87 0 695 727 ;
+C 150 ; WX 556 ; N endash ; B 48 227 627 333 ;
+C 134 ; WX 556 ; N dagger ; B 118 -171 626 718 ;
+C 135 ; WX 556 ; N daggerdbl ; B 46 -171 628 718 ;
+C 183 ; WX 278 ; N periodcentered ; B 110 172 276 334 ;
+C 182 ; WX 556 ; N paragraph ; B 98 -191 688 700 ;
+C 149 ; WX 350 ; N bullet ; B 83 194 420 524 ;
+C 130 ; WX 278 ; N quotesinglbase ; B 41 -146 236 127 ;
+C 132 ; WX 500 ; N quotedblbase ; B 36 -146 463 127 ;
+C 148 ; WX 500 ; N quotedblright ; B 162 445 589 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 104 76 540 484 ;
+C 133 ; WX 1000 ; N ellipsis ; B 92 0 939 146 ;
+C 137 ; WX 1000 ; N perthousand ; B 76 -19 1038 710 ;
+C 191 ; WX 611 ; N questiondown ; B 53 -195 559 532 ;
+C 96 ; WX 333 ; N grave ; B 136 604 353 750 ;
+C 180 ; WX 333 ; N acute ; B 236 604 515 750 ;
+C 136 ; WX 333 ; N circumflex ; B 118 604 471 750 ;
+C 152 ; WX 333 ; N tilde ; B 113 610 507 737 ;
+C 175 ; WX 333 ; N macron ; B 122 604 483 678 ;
+C -1 ; WX 333 ; N breve ; B 156 604 494 750 ;
+C -1 ; WX 333 ; N dotaccent ; B 235 614 385 729 ;
+C 168 ; WX 333 ; N dieresis ; B 137 614 482 729 ;
+C -1 ; WX 333 ; N ring ; B 200 568 420 776 ;
+C 184 ; WX 333 ; N cedilla ; B -37 -228 220 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B 137 604 645 750 ;
+C -1 ; WX 333 ; N ogonek ; B 41 -228 264 0 ;
+C -1 ; WX 333 ; N caron ; B 149 604 502 750 ;
+C 151 ; WX 1000 ; N emdash ; B 48 227 1071 333 ;
+C 198 ; WX 1000 ; N AE ; B 5 0 1100 718 ;
+C 170 ; WX 370 ; N ordfeminine ; B 125 401 465 737 ;
+C -1 ; WX 611 ; N Lslash ; B 34 0 611 718 ;
+C 216 ; WX 778 ; N Oslash ; B 35 -27 894 745 ;
+C 140 ; WX 1000 ; N OE ; B 99 -19 1114 737 ;
+C 186 ; WX 365 ; N ordmasculine ; B 123 401 485 737 ;
+C 230 ; WX 889 ; N ae ; B 56 -14 923 546 ;
+C -1 ; WX 278 ; N dotlessi ; B 69 0 322 532 ;
+C -1 ; WX 278 ; N lslash ; B 40 0 407 718 ;
+C 248 ; WX 611 ; N oslash ; B 22 -29 701 560 ;
+C 156 ; WX 944 ; N oe ; B 82 -14 977 546 ;
+C 223 ; WX 611 ; N germandbls ; B 69 -14 657 731 ;
+C 207 ; WX 278 ; N Idieresis ; B 64 0 494 915 ;
+C 233 ; WX 556 ; N eacute ; B 70 -14 627 750 ;
+C -1 ; WX 556 ; N abreve ; B 55 -14 606 750 ;
+C -1 ; WX 611 ; N uhungarumlaut ; B 98 -14 784 750 ;
+C -1 ; WX 556 ; N ecaron ; B 70 -14 614 750 ;
+C 159 ; WX 667 ; N Ydieresis ; B 168 0 806 915 ;
+C 247 ; WX 584 ; N divide ; B 82 -42 610 548 ;
+C 221 ; WX 667 ; N Yacute ; B 168 0 806 936 ;
+C 194 ; WX 722 ; N Acircumflex ; B 20 0 706 936 ;
+C 225 ; WX 556 ; N aacute ; B 55 -14 627 750 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 116 -19 804 936 ;
+C 253 ; WX 556 ; N yacute ; B 42 -214 652 750 ;
+C -1 ; WX 556 ; N scommaaccent ; B 63 -228 584 546 ;
+C 234 ; WX 556 ; N ecircumflex ; B 70 -14 593 750 ;
+C -1 ; WX 722 ; N Uring ; B 116 -19 804 962 ;
+C 220 ; WX 722 ; N Udieresis ; B 116 -19 804 915 ;
+C -1 ; WX 556 ; N aogonek ; B 55 -224 583 546 ;
+C 218 ; WX 722 ; N Uacute ; B 116 -19 804 936 ;
+C -1 ; WX 611 ; N uogonek ; B 98 -228 658 532 ;
+C 203 ; WX 667 ; N Edieresis ; B 76 0 757 915 ;
+C -1 ; WX 722 ; N Dcroat ; B 62 0 777 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 16 -228 188 -50 ;
+C 169 ; WX 737 ; N copyright ; B 56 -19 835 737 ;
+C -1 ; WX 667 ; N Emacron ; B 76 0 757 864 ;
+C -1 ; WX 556 ; N ccaron ; B 79 -14 614 750 ;
+C 229 ; WX 556 ; N aring ; B 55 -14 583 776 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 69 -228 807 718 ;
+C -1 ; WX 278 ; N lacute ; B 69 0 528 936 ;
+C 224 ; WX 556 ; N agrave ; B 55 -14 583 750 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 140 -228 751 718 ;
+C -1 ; WX 722 ; N Cacute ; B 107 -19 789 936 ;
+C 227 ; WX 556 ; N atilde ; B 55 -14 619 737 ;
+C -1 ; WX 667 ; N Edotaccent ; B 76 0 757 915 ;
+C 154 ; WX 556 ; N scaron ; B 63 -14 614 750 ;
+C -1 ; WX 556 ; N scedilla ; B 63 -228 584 546 ;
+C 237 ; WX 278 ; N iacute ; B 69 0 488 750 ;
+C -1 ; WX 494 ; N lozenge ; B 90 0 564 745 ;
+C -1 ; WX 722 ; N Rcaron ; B 76 0 778 936 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 108 -228 817 737 ;
+C 251 ; WX 611 ; N ucircumflex ; B 98 -14 658 750 ;
+C 226 ; WX 556 ; N acircumflex ; B 55 -14 583 750 ;
+C -1 ; WX 722 ; N Amacron ; B 20 0 718 864 ;
+C -1 ; WX 389 ; N rcaron ; B 64 0 530 750 ;
+C 231 ; WX 556 ; N ccedilla ; B 79 -228 599 546 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 25 0 737 915 ;
+C 222 ; WX 667 ; N Thorn ; B 76 0 716 718 ;
+C -1 ; WX 778 ; N Omacron ; B 107 -19 823 864 ;
+C -1 ; WX 722 ; N Racute ; B 76 0 778 936 ;
+C -1 ; WX 667 ; N Sacute ; B 81 -19 722 936 ;
+C -1 ; WX 743 ; N dcaron ; B 82 -14 903 718 ;
+C -1 ; WX 722 ; N Umacron ; B 116 -19 804 864 ;
+C -1 ; WX 611 ; N uring ; B 98 -14 658 776 ;
+C 179 ; WX 333 ; N threesuperior ; B 91 271 441 710 ;
+C 210 ; WX 778 ; N Ograve ; B 107 -19 823 936 ;
+C 192 ; WX 722 ; N Agrave ; B 20 0 702 936 ;
+C -1 ; WX 722 ; N Abreve ; B 20 0 729 936 ;
+C 215 ; WX 584 ; N multiply ; B 57 1 635 505 ;
+C 250 ; WX 611 ; N uacute ; B 98 -14 658 750 ;
+C -1 ; WX 611 ; N Tcaron ; B 140 0 751 936 ;
+C -1 ; WX 494 ; N partialdiff ; B 43 -21 585 750 ;
+C 255 ; WX 556 ; N ydieresis ; B 42 -214 652 729 ;
+C -1 ; WX 722 ; N Nacute ; B 69 0 807 936 ;
+C 238 ; WX 278 ; N icircumflex ; B 69 0 444 750 ;
+C 202 ; WX 667 ; N Ecircumflex ; B 76 0 757 936 ;
+C 228 ; WX 556 ; N adieresis ; B 55 -14 594 729 ;
+C 235 ; WX 556 ; N edieresis ; B 70 -14 594 729 ;
+C -1 ; WX 556 ; N cacute ; B 79 -14 627 750 ;
+C -1 ; WX 611 ; N nacute ; B 65 0 654 750 ;
+C -1 ; WX 611 ; N umacron ; B 98 -14 658 678 ;
+C -1 ; WX 722 ; N Ncaron ; B 69 0 807 936 ;
+C 205 ; WX 278 ; N Iacute ; B 64 0 528 936 ;
+C 177 ; WX 584 ; N plusminus ; B 40 0 625 506 ;
+C 166 ; WX 280 ; N brokenbar ; B 52 -150 345 700 ;
+C 174 ; WX 737 ; N registered ; B 55 -19 834 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 108 -19 817 936 ;
+C -1 ; WX 278 ; N Idotaccent ; B 64 0 397 915 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 670 706 ;
+C 200 ; WX 667 ; N Egrave ; B 76 0 757 936 ;
+C -1 ; WX 389 ; N racute ; B 64 0 543 750 ;
+C -1 ; WX 611 ; N omacron ; B 82 -14 643 678 ;
+C -1 ; WX 611 ; N Zacute ; B 25 0 737 936 ;
+C 142 ; WX 611 ; N Zcaron ; B 25 0 737 936 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 629 704 ;
+C 208 ; WX 722 ; N Eth ; B 62 0 777 718 ;
+C 199 ; WX 722 ; N Ccedilla ; B 107 -228 789 737 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 30 -228 362 718 ;
+C -1 ; WX 389 ; N tcaron ; B 100 -6 608 878 ;
+C -1 ; WX 556 ; N eogonek ; B 70 -228 593 546 ;
+C -1 ; WX 722 ; N Uogonek ; B 116 -228 804 718 ;
+C 193 ; WX 722 ; N Aacute ; B 20 0 750 936 ;
+C 196 ; WX 722 ; N Adieresis ; B 20 0 716 915 ;
+C 232 ; WX 556 ; N egrave ; B 70 -14 593 750 ;
+C -1 ; WX 500 ; N zacute ; B 20 0 599 750 ;
+C -1 ; WX 278 ; N iogonek ; B -14 -224 363 725 ;
+C 211 ; WX 778 ; N Oacute ; B 107 -19 823 936 ;
+C 243 ; WX 611 ; N oacute ; B 82 -14 654 750 ;
+C -1 ; WX 556 ; N amacron ; B 55 -14 595 678 ;
+C -1 ; WX 556 ; N sacute ; B 63 -14 627 750 ;
+C 239 ; WX 278 ; N idieresis ; B 69 0 455 729 ;
+C 212 ; WX 778 ; N Ocircumflex ; B 107 -19 823 936 ;
+C 217 ; WX 722 ; N Ugrave ; B 116 -19 804 936 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 611 ; N thorn ; B 18 -208 645 718 ;
+C 178 ; WX 333 ; N twosuperior ; B 69 283 449 710 ;
+C 214 ; WX 778 ; N Odieresis ; B 107 -19 823 915 ;
+C 181 ; WX 611 ; N mu ; B 22 -207 658 532 ;
+C 236 ; WX 278 ; N igrave ; B 69 0 326 750 ;
+C -1 ; WX 611 ; N ohungarumlaut ; B 82 -14 784 750 ;
+C -1 ; WX 667 ; N Eogonek ; B 76 -224 757 718 ;
+C -1 ; WX 611 ; N dcroat ; B 82 -14 789 718 ;
+C 190 ; WX 834 ; N threequarters ; B 99 -19 839 710 ;
+C -1 ; WX 667 ; N Scedilla ; B 81 -228 718 737 ;
+C -1 ; WX 400 ; N lcaron ; B 69 0 561 718 ;
+C -1 ; WX 722 ; N Kcommaaccent ; B 87 -228 858 718 ;
+C -1 ; WX 611 ; N Lacute ; B 76 0 611 936 ;
+C 153 ; WX 1000 ; N trademark ; B 179 306 1109 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 70 -14 593 729 ;
+C 204 ; WX 278 ; N Igrave ; B 64 0 367 936 ;
+C -1 ; WX 278 ; N Imacron ; B 64 0 496 864 ;
+C -1 ; WX 611 ; N Lcaron ; B 76 0 643 718 ;
+C 189 ; WX 834 ; N onehalf ; B 132 -19 858 710 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 676 704 ;
+C 244 ; WX 611 ; N ocircumflex ; B 82 -14 643 750 ;
+C 241 ; WX 611 ; N ntilde ; B 65 0 646 737 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 116 -19 880 936 ;
+C 201 ; WX 667 ; N Eacute ; B 76 0 757 936 ;
+C -1 ; WX 556 ; N emacron ; B 70 -14 595 678 ;
+C -1 ; WX 611 ; N gbreve ; B 38 -217 666 750 ;
+C 188 ; WX 834 ; N onequarter ; B 132 -19 806 710 ;
+C 138 ; WX 667 ; N Scaron ; B 81 -19 718 936 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 81 -228 718 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 107 -19 908 936 ;
+C 176 ; WX 400 ; N degree ; B 175 426 467 712 ;
+C 242 ; WX 611 ; N ograve ; B 82 -14 643 750 ;
+C -1 ; WX 722 ; N Ccaron ; B 107 -19 789 936 ;
+C 249 ; WX 611 ; N ugrave ; B 98 -14 658 750 ;
+C -1 ; WX 549 ; N radical ; B 112 -46 689 850 ;
+C -1 ; WX 722 ; N Dcaron ; B 76 0 777 936 ;
+C -1 ; WX 389 ; N rcommaaccent ; B 26 -228 489 546 ;
+C 209 ; WX 722 ; N Ntilde ; B 69 0 807 923 ;
+C 245 ; WX 611 ; N otilde ; B 82 -14 646 737 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 76 -228 778 718 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B 76 -228 611 718 ;
+C 195 ; WX 722 ; N Atilde ; B 20 0 741 923 ;
+C -1 ; WX 722 ; N Aogonek ; B 20 -224 702 718 ;
+C 197 ; WX 722 ; N Aring ; B 20 0 702 962 ;
+C 213 ; WX 778 ; N Otilde ; B 107 -19 823 923 ;
+C -1 ; WX 500 ; N zdotaccent ; B 20 0 583 729 ;
+C -1 ; WX 667 ; N Ecaron ; B 76 0 757 936 ;
+C -1 ; WX 278 ; N Iogonek ; B -41 -228 367 718 ;
+C -1 ; WX 556 ; N kcommaaccent ; B 69 -228 670 718 ;
+C -1 ; WX 584 ; N minus ; B 82 197 610 309 ;
+C 206 ; WX 278 ; N Icircumflex ; B 64 0 484 936 ;
+C -1 ; WX 611 ; N ncaron ; B 65 0 641 750 ;
+C -1 ; WX 333 ; N tcommaaccent ; B 58 -228 422 676 ;
+C 172 ; WX 584 ; N logicalnot ; B 105 108 633 419 ;
+C 246 ; WX 611 ; N odieresis ; B 82 -14 643 729 ;
+C 252 ; WX 611 ; N udieresis ; B 98 -14 658 729 ;
+C -1 ; WX 549 ; N notequal ; B 32 -49 630 570 ;
+C -1 ; WX 611 ; N gcommaaccent ; B 38 -217 666 850 ;
+C 240 ; WX 611 ; N eth ; B 82 -14 670 737 ;
+C 158 ; WX 500 ; N zcaron ; B 20 0 586 750 ;
+C -1 ; WX 611 ; N ncommaaccent ; B 65 -228 629 546 ;
+C 185 ; WX 333 ; N onesuperior ; B 148 283 388 710 ;
+C -1 ; WX 278 ; N imacron ; B 69 0 429 678 ;
+C 128 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2481
+KPX A C -40
+KPX A Cacute -40
+KPX A Ccaron -40
+KPX A Ccedilla -40
+KPX A G -50
+KPX A Gbreve -50
+KPX A Gcommaaccent -50
+KPX A O -40
+KPX A Oacute -40
+KPX A Ocircumflex -40
+KPX A Odieresis -40
+KPX A Ograve -40
+KPX A Ohungarumlaut -40
+KPX A Omacron -40
+KPX A Oslash -40
+KPX A Otilde -40
+KPX A Q -40
+KPX A T -90
+KPX A Tcaron -90
+KPX A Tcommaaccent -90
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -80
+KPX A W -60
+KPX A Y -110
+KPX A Yacute -110
+KPX A Ydieresis -110
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -30
+KPX A y -30
+KPX A yacute -30
+KPX A ydieresis -30
+KPX Aacute C -40
+KPX Aacute Cacute -40
+KPX Aacute Ccaron -40
+KPX Aacute Ccedilla -40
+KPX Aacute G -50
+KPX Aacute Gbreve -50
+KPX Aacute Gcommaaccent -50
+KPX Aacute O -40
+KPX Aacute Oacute -40
+KPX Aacute Ocircumflex -40
+KPX Aacute Odieresis -40
+KPX Aacute Ograve -40
+KPX Aacute Ohungarumlaut -40
+KPX Aacute Omacron -40
+KPX Aacute Oslash -40
+KPX Aacute Otilde -40
+KPX Aacute Q -40
+KPX Aacute T -90
+KPX Aacute Tcaron -90
+KPX Aacute Tcommaaccent -90
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -80
+KPX Aacute W -60
+KPX Aacute Y -110
+KPX Aacute Yacute -110
+KPX Aacute Ydieresis -110
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -30
+KPX Aacute y -30
+KPX Aacute yacute -30
+KPX Aacute ydieresis -30
+KPX Abreve C -40
+KPX Abreve Cacute -40
+KPX Abreve Ccaron -40
+KPX Abreve Ccedilla -40
+KPX Abreve G -50
+KPX Abreve Gbreve -50
+KPX Abreve Gcommaaccent -50
+KPX Abreve O -40
+KPX Abreve Oacute -40
+KPX Abreve Ocircumflex -40
+KPX Abreve Odieresis -40
+KPX Abreve Ograve -40
+KPX Abreve Ohungarumlaut -40
+KPX Abreve Omacron -40
+KPX Abreve Oslash -40
+KPX Abreve Otilde -40
+KPX Abreve Q -40
+KPX Abreve T -90
+KPX Abreve Tcaron -90
+KPX Abreve Tcommaaccent -90
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -80
+KPX Abreve W -60
+KPX Abreve Y -110
+KPX Abreve Yacute -110
+KPX Abreve Ydieresis -110
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -30
+KPX Abreve y -30
+KPX Abreve yacute -30
+KPX Abreve ydieresis -30
+KPX Acircumflex C -40
+KPX Acircumflex Cacute -40
+KPX Acircumflex Ccaron -40
+KPX Acircumflex Ccedilla -40
+KPX Acircumflex G -50
+KPX Acircumflex Gbreve -50
+KPX Acircumflex Gcommaaccent -50
+KPX Acircumflex O -40
+KPX Acircumflex Oacute -40
+KPX Acircumflex Ocircumflex -40
+KPX Acircumflex Odieresis -40
+KPX Acircumflex Ograve -40
+KPX Acircumflex Ohungarumlaut -40
+KPX Acircumflex Omacron -40
+KPX Acircumflex Oslash -40
+KPX Acircumflex Otilde -40
+KPX Acircumflex Q -40
+KPX Acircumflex T -90
+KPX Acircumflex Tcaron -90
+KPX Acircumflex Tcommaaccent -90
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -80
+KPX Acircumflex W -60
+KPX Acircumflex Y -110
+KPX Acircumflex Yacute -110
+KPX Acircumflex Ydieresis -110
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -30
+KPX Acircumflex y -30
+KPX Acircumflex yacute -30
+KPX Acircumflex ydieresis -30
+KPX Adieresis C -40
+KPX Adieresis Cacute -40
+KPX Adieresis Ccaron -40
+KPX Adieresis Ccedilla -40
+KPX Adieresis G -50
+KPX Adieresis Gbreve -50
+KPX Adieresis Gcommaaccent -50
+KPX Adieresis O -40
+KPX Adieresis Oacute -40
+KPX Adieresis Ocircumflex -40
+KPX Adieresis Odieresis -40
+KPX Adieresis Ograve -40
+KPX Adieresis Ohungarumlaut -40
+KPX Adieresis Omacron -40
+KPX Adieresis Oslash -40
+KPX Adieresis Otilde -40
+KPX Adieresis Q -40
+KPX Adieresis T -90
+KPX Adieresis Tcaron -90
+KPX Adieresis Tcommaaccent -90
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -80
+KPX Adieresis W -60
+KPX Adieresis Y -110
+KPX Adieresis Yacute -110
+KPX Adieresis Ydieresis -110
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -30
+KPX Adieresis y -30
+KPX Adieresis yacute -30
+KPX Adieresis ydieresis -30
+KPX Agrave C -40
+KPX Agrave Cacute -40
+KPX Agrave Ccaron -40
+KPX Agrave Ccedilla -40
+KPX Agrave G -50
+KPX Agrave Gbreve -50
+KPX Agrave Gcommaaccent -50
+KPX Agrave O -40
+KPX Agrave Oacute -40
+KPX Agrave Ocircumflex -40
+KPX Agrave Odieresis -40
+KPX Agrave Ograve -40
+KPX Agrave Ohungarumlaut -40
+KPX Agrave Omacron -40
+KPX Agrave Oslash -40
+KPX Agrave Otilde -40
+KPX Agrave Q -40
+KPX Agrave T -90
+KPX Agrave Tcaron -90
+KPX Agrave Tcommaaccent -90
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -80
+KPX Agrave W -60
+KPX Agrave Y -110
+KPX Agrave Yacute -110
+KPX Agrave Ydieresis -110
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -30
+KPX Agrave y -30
+KPX Agrave yacute -30
+KPX Agrave ydieresis -30
+KPX Amacron C -40
+KPX Amacron Cacute -40
+KPX Amacron Ccaron -40
+KPX Amacron Ccedilla -40
+KPX Amacron G -50
+KPX Amacron Gbreve -50
+KPX Amacron Gcommaaccent -50
+KPX Amacron O -40
+KPX Amacron Oacute -40
+KPX Amacron Ocircumflex -40
+KPX Amacron Odieresis -40
+KPX Amacron Ograve -40
+KPX Amacron Ohungarumlaut -40
+KPX Amacron Omacron -40
+KPX Amacron Oslash -40
+KPX Amacron Otilde -40
+KPX Amacron Q -40
+KPX Amacron T -90
+KPX Amacron Tcaron -90
+KPX Amacron Tcommaaccent -90
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -80
+KPX Amacron W -60
+KPX Amacron Y -110
+KPX Amacron Yacute -110
+KPX Amacron Ydieresis -110
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -30
+KPX Amacron y -30
+KPX Amacron yacute -30
+KPX Amacron ydieresis -30
+KPX Aogonek C -40
+KPX Aogonek Cacute -40
+KPX Aogonek Ccaron -40
+KPX Aogonek Ccedilla -40
+KPX Aogonek G -50
+KPX Aogonek Gbreve -50
+KPX Aogonek Gcommaaccent -50
+KPX Aogonek O -40
+KPX Aogonek Oacute -40
+KPX Aogonek Ocircumflex -40
+KPX Aogonek Odieresis -40
+KPX Aogonek Ograve -40
+KPX Aogonek Ohungarumlaut -40
+KPX Aogonek Omacron -40
+KPX Aogonek Oslash -40
+KPX Aogonek Otilde -40
+KPX Aogonek Q -40
+KPX Aogonek T -90
+KPX Aogonek Tcaron -90
+KPX Aogonek Tcommaaccent -90
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -80
+KPX Aogonek W -60
+KPX Aogonek Y -110
+KPX Aogonek Yacute -110
+KPX Aogonek Ydieresis -110
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -30
+KPX Aogonek y -30
+KPX Aogonek yacute -30
+KPX Aogonek ydieresis -30
+KPX Aring C -40
+KPX Aring Cacute -40
+KPX Aring Ccaron -40
+KPX Aring Ccedilla -40
+KPX Aring G -50
+KPX Aring Gbreve -50
+KPX Aring Gcommaaccent -50
+KPX Aring O -40
+KPX Aring Oacute -40
+KPX Aring Ocircumflex -40
+KPX Aring Odieresis -40
+KPX Aring Ograve -40
+KPX Aring Ohungarumlaut -40
+KPX Aring Omacron -40
+KPX Aring Oslash -40
+KPX Aring Otilde -40
+KPX Aring Q -40
+KPX Aring T -90
+KPX Aring Tcaron -90
+KPX Aring Tcommaaccent -90
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -80
+KPX Aring W -60
+KPX Aring Y -110
+KPX Aring Yacute -110
+KPX Aring Ydieresis -110
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -30
+KPX Aring y -30
+KPX Aring yacute -30
+KPX Aring ydieresis -30
+KPX Atilde C -40
+KPX Atilde Cacute -40
+KPX Atilde Ccaron -40
+KPX Atilde Ccedilla -40
+KPX Atilde G -50
+KPX Atilde Gbreve -50
+KPX Atilde Gcommaaccent -50
+KPX Atilde O -40
+KPX Atilde Oacute -40
+KPX Atilde Ocircumflex -40
+KPX Atilde Odieresis -40
+KPX Atilde Ograve -40
+KPX Atilde Ohungarumlaut -40
+KPX Atilde Omacron -40
+KPX Atilde Oslash -40
+KPX Atilde Otilde -40
+KPX Atilde Q -40
+KPX Atilde T -90
+KPX Atilde Tcaron -90
+KPX Atilde Tcommaaccent -90
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -80
+KPX Atilde W -60
+KPX Atilde Y -110
+KPX Atilde Yacute -110
+KPX Atilde Ydieresis -110
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -30
+KPX Atilde y -30
+KPX Atilde yacute -30
+KPX Atilde ydieresis -30
+KPX B A -30
+KPX B Aacute -30
+KPX B Abreve -30
+KPX B Acircumflex -30
+KPX B Adieresis -30
+KPX B Agrave -30
+KPX B Amacron -30
+KPX B Aogonek -30
+KPX B Aring -30
+KPX B Atilde -30
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -40
+KPX D W -40
+KPX D Y -70
+KPX D Yacute -70
+KPX D Ydieresis -70
+KPX D comma -30
+KPX D period -30
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -70
+KPX Dcaron Yacute -70
+KPX Dcaron Ydieresis -70
+KPX Dcaron comma -30
+KPX Dcaron period -30
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -70
+KPX Dcroat Yacute -70
+KPX Dcroat Ydieresis -70
+KPX Dcroat comma -30
+KPX Dcroat period -30
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -20
+KPX F aacute -20
+KPX F abreve -20
+KPX F acircumflex -20
+KPX F adieresis -20
+KPX F agrave -20
+KPX F amacron -20
+KPX F aogonek -20
+KPX F aring -20
+KPX F atilde -20
+KPX F comma -100
+KPX F period -100
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J comma -20
+KPX J period -20
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -15
+KPX K eacute -15
+KPX K ecaron -15
+KPX K ecircumflex -15
+KPX K edieresis -15
+KPX K edotaccent -15
+KPX K egrave -15
+KPX K emacron -15
+KPX K eogonek -15
+KPX K o -35
+KPX K oacute -35
+KPX K ocircumflex -35
+KPX K odieresis -35
+KPX K ograve -35
+KPX K ohungarumlaut -35
+KPX K omacron -35
+KPX K oslash -35
+KPX K otilde -35
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -40
+KPX K yacute -40
+KPX K ydieresis -40
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -15
+KPX Kcommaaccent eacute -15
+KPX Kcommaaccent ecaron -15
+KPX Kcommaaccent ecircumflex -15
+KPX Kcommaaccent edieresis -15
+KPX Kcommaaccent edotaccent -15
+KPX Kcommaaccent egrave -15
+KPX Kcommaaccent emacron -15
+KPX Kcommaaccent eogonek -15
+KPX Kcommaaccent o -35
+KPX Kcommaaccent oacute -35
+KPX Kcommaaccent ocircumflex -35
+KPX Kcommaaccent odieresis -35
+KPX Kcommaaccent ograve -35
+KPX Kcommaaccent ohungarumlaut -35
+KPX Kcommaaccent omacron -35
+KPX Kcommaaccent oslash -35
+KPX Kcommaaccent otilde -35
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -40
+KPX Kcommaaccent yacute -40
+KPX Kcommaaccent ydieresis -40
+KPX L T -90
+KPX L Tcaron -90
+KPX L Tcommaaccent -90
+KPX L V -110
+KPX L W -80
+KPX L Y -120
+KPX L Yacute -120
+KPX L Ydieresis -120
+KPX L quotedblright -140
+KPX L quoteright -140
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -90
+KPX Lacute Tcaron -90
+KPX Lacute Tcommaaccent -90
+KPX Lacute V -110
+KPX Lacute W -80
+KPX Lacute Y -120
+KPX Lacute Yacute -120
+KPX Lacute Ydieresis -120
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -140
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcommaaccent T -90
+KPX Lcommaaccent Tcaron -90
+KPX Lcommaaccent Tcommaaccent -90
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -80
+KPX Lcommaaccent Y -120
+KPX Lcommaaccent Yacute -120
+KPX Lcommaaccent Ydieresis -120
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -140
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -90
+KPX Lslash Tcaron -90
+KPX Lslash Tcommaaccent -90
+KPX Lslash V -110
+KPX Lslash W -80
+KPX Lslash Y -120
+KPX Lslash Yacute -120
+KPX Lslash Ydieresis -120
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -140
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -50
+KPX O Aacute -50
+KPX O Abreve -50
+KPX O Acircumflex -50
+KPX O Adieresis -50
+KPX O Agrave -50
+KPX O Amacron -50
+KPX O Aogonek -50
+KPX O Aring -50
+KPX O Atilde -50
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -50
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -50
+KPX Oacute Aacute -50
+KPX Oacute Abreve -50
+KPX Oacute Acircumflex -50
+KPX Oacute Adieresis -50
+KPX Oacute Agrave -50
+KPX Oacute Amacron -50
+KPX Oacute Aogonek -50
+KPX Oacute Aring -50
+KPX Oacute Atilde -50
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -50
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -50
+KPX Ocircumflex Aacute -50
+KPX Ocircumflex Abreve -50
+KPX Ocircumflex Acircumflex -50
+KPX Ocircumflex Adieresis -50
+KPX Ocircumflex Agrave -50
+KPX Ocircumflex Amacron -50
+KPX Ocircumflex Aogonek -50
+KPX Ocircumflex Aring -50
+KPX Ocircumflex Atilde -50
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -50
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -50
+KPX Odieresis Aacute -50
+KPX Odieresis Abreve -50
+KPX Odieresis Acircumflex -50
+KPX Odieresis Adieresis -50
+KPX Odieresis Agrave -50
+KPX Odieresis Amacron -50
+KPX Odieresis Aogonek -50
+KPX Odieresis Aring -50
+KPX Odieresis Atilde -50
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -50
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -50
+KPX Ograve Aacute -50
+KPX Ograve Abreve -50
+KPX Ograve Acircumflex -50
+KPX Ograve Adieresis -50
+KPX Ograve Agrave -50
+KPX Ograve Amacron -50
+KPX Ograve Aogonek -50
+KPX Ograve Aring -50
+KPX Ograve Atilde -50
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -50
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -50
+KPX Ohungarumlaut Aacute -50
+KPX Ohungarumlaut Abreve -50
+KPX Ohungarumlaut Acircumflex -50
+KPX Ohungarumlaut Adieresis -50
+KPX Ohungarumlaut Agrave -50
+KPX Ohungarumlaut Amacron -50
+KPX Ohungarumlaut Aogonek -50
+KPX Ohungarumlaut Aring -50
+KPX Ohungarumlaut Atilde -50
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -50
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -50
+KPX Omacron Aacute -50
+KPX Omacron Abreve -50
+KPX Omacron Acircumflex -50
+KPX Omacron Adieresis -50
+KPX Omacron Agrave -50
+KPX Omacron Amacron -50
+KPX Omacron Aogonek -50
+KPX Omacron Aring -50
+KPX Omacron Atilde -50
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -50
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -50
+KPX Oslash Aacute -50
+KPX Oslash Abreve -50
+KPX Oslash Acircumflex -50
+KPX Oslash Adieresis -50
+KPX Oslash Agrave -50
+KPX Oslash Amacron -50
+KPX Oslash Aogonek -50
+KPX Oslash Aring -50
+KPX Oslash Atilde -50
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -50
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -50
+KPX Otilde Aacute -50
+KPX Otilde Abreve -50
+KPX Otilde Acircumflex -50
+KPX Otilde Adieresis -50
+KPX Otilde Agrave -50
+KPX Otilde Amacron -50
+KPX Otilde Aogonek -50
+KPX Otilde Aring -50
+KPX Otilde Atilde -50
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -50
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -100
+KPX P Aacute -100
+KPX P Abreve -100
+KPX P Acircumflex -100
+KPX P Adieresis -100
+KPX P Agrave -100
+KPX P Amacron -100
+KPX P Aogonek -100
+KPX P Aring -100
+KPX P Atilde -100
+KPX P a -30
+KPX P aacute -30
+KPX P abreve -30
+KPX P acircumflex -30
+KPX P adieresis -30
+KPX P agrave -30
+KPX P amacron -30
+KPX P aogonek -30
+KPX P aring -30
+KPX P atilde -30
+KPX P comma -120
+KPX P e -30
+KPX P eacute -30
+KPX P ecaron -30
+KPX P ecircumflex -30
+KPX P edieresis -30
+KPX P edotaccent -30
+KPX P egrave -30
+KPX P emacron -30
+KPX P eogonek -30
+KPX P o -40
+KPX P oacute -40
+KPX P ocircumflex -40
+KPX P odieresis -40
+KPX P ograve -40
+KPX P ohungarumlaut -40
+KPX P omacron -40
+KPX P oslash -40
+KPX P otilde -40
+KPX P period -120
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX Q comma 20
+KPX Q period 20
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -20
+KPX R Tcaron -20
+KPX R Tcommaaccent -20
+KPX R U -20
+KPX R Uacute -20
+KPX R Ucircumflex -20
+KPX R Udieresis -20
+KPX R Ugrave -20
+KPX R Uhungarumlaut -20
+KPX R Umacron -20
+KPX R Uogonek -20
+KPX R Uring -20
+KPX R V -50
+KPX R W -40
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -20
+KPX Racute Tcaron -20
+KPX Racute Tcommaaccent -20
+KPX Racute U -20
+KPX Racute Uacute -20
+KPX Racute Ucircumflex -20
+KPX Racute Udieresis -20
+KPX Racute Ugrave -20
+KPX Racute Uhungarumlaut -20
+KPX Racute Umacron -20
+KPX Racute Uogonek -20
+KPX Racute Uring -20
+KPX Racute V -50
+KPX Racute W -40
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -20
+KPX Rcaron Tcaron -20
+KPX Rcaron Tcommaaccent -20
+KPX Rcaron U -20
+KPX Rcaron Uacute -20
+KPX Rcaron Ucircumflex -20
+KPX Rcaron Udieresis -20
+KPX Rcaron Ugrave -20
+KPX Rcaron Uhungarumlaut -20
+KPX Rcaron Umacron -20
+KPX Rcaron Uogonek -20
+KPX Rcaron Uring -20
+KPX Rcaron V -50
+KPX Rcaron W -40
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -20
+KPX Rcommaaccent Tcaron -20
+KPX Rcommaaccent Tcommaaccent -20
+KPX Rcommaaccent U -20
+KPX Rcommaaccent Uacute -20
+KPX Rcommaaccent Ucircumflex -20
+KPX Rcommaaccent Udieresis -20
+KPX Rcommaaccent Ugrave -20
+KPX Rcommaaccent Uhungarumlaut -20
+KPX Rcommaaccent Umacron -20
+KPX Rcommaaccent Uogonek -20
+KPX Rcommaaccent Uring -20
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -40
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX T A -90
+KPX T Aacute -90
+KPX T Abreve -90
+KPX T Acircumflex -90
+KPX T Adieresis -90
+KPX T Agrave -90
+KPX T Amacron -90
+KPX T Aogonek -90
+KPX T Aring -90
+KPX T Atilde -90
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -80
+KPX T aacute -80
+KPX T abreve -80
+KPX T acircumflex -80
+KPX T adieresis -80
+KPX T agrave -80
+KPX T amacron -80
+KPX T aogonek -80
+KPX T aring -80
+KPX T atilde -80
+KPX T colon -40
+KPX T comma -80
+KPX T e -60
+KPX T eacute -60
+KPX T ecaron -60
+KPX T ecircumflex -60
+KPX T edieresis -60
+KPX T edotaccent -60
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -60
+KPX T hyphen -120
+KPX T o -80
+KPX T oacute -80
+KPX T ocircumflex -80
+KPX T odieresis -80
+KPX T ograve -80
+KPX T ohungarumlaut -80
+KPX T omacron -80
+KPX T oslash -80
+KPX T otilde -80
+KPX T period -80
+KPX T r -80
+KPX T racute -80
+KPX T rcommaaccent -80
+KPX T semicolon -40
+KPX T u -90
+KPX T uacute -90
+KPX T ucircumflex -90
+KPX T udieresis -90
+KPX T ugrave -90
+KPX T uhungarumlaut -90
+KPX T umacron -90
+KPX T uogonek -90
+KPX T uring -90
+KPX T w -60
+KPX T y -60
+KPX T yacute -60
+KPX T ydieresis -60
+KPX Tcaron A -90
+KPX Tcaron Aacute -90
+KPX Tcaron Abreve -90
+KPX Tcaron Acircumflex -90
+KPX Tcaron Adieresis -90
+KPX Tcaron Agrave -90
+KPX Tcaron Amacron -90
+KPX Tcaron Aogonek -90
+KPX Tcaron Aring -90
+KPX Tcaron Atilde -90
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -80
+KPX Tcaron aacute -80
+KPX Tcaron abreve -80
+KPX Tcaron acircumflex -80
+KPX Tcaron adieresis -80
+KPX Tcaron agrave -80
+KPX Tcaron amacron -80
+KPX Tcaron aogonek -80
+KPX Tcaron aring -80
+KPX Tcaron atilde -80
+KPX Tcaron colon -40
+KPX Tcaron comma -80
+KPX Tcaron e -60
+KPX Tcaron eacute -60
+KPX Tcaron ecaron -60
+KPX Tcaron ecircumflex -60
+KPX Tcaron edieresis -60
+KPX Tcaron edotaccent -60
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -60
+KPX Tcaron hyphen -120
+KPX Tcaron o -80
+KPX Tcaron oacute -80
+KPX Tcaron ocircumflex -80
+KPX Tcaron odieresis -80
+KPX Tcaron ograve -80
+KPX Tcaron ohungarumlaut -80
+KPX Tcaron omacron -80
+KPX Tcaron oslash -80
+KPX Tcaron otilde -80
+KPX Tcaron period -80
+KPX Tcaron r -80
+KPX Tcaron racute -80
+KPX Tcaron rcommaaccent -80
+KPX Tcaron semicolon -40
+KPX Tcaron u -90
+KPX Tcaron uacute -90
+KPX Tcaron ucircumflex -90
+KPX Tcaron udieresis -90
+KPX Tcaron ugrave -90
+KPX Tcaron uhungarumlaut -90
+KPX Tcaron umacron -90
+KPX Tcaron uogonek -90
+KPX Tcaron uring -90
+KPX Tcaron w -60
+KPX Tcaron y -60
+KPX Tcaron yacute -60
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -90
+KPX Tcommaaccent Aacute -90
+KPX Tcommaaccent Abreve -90
+KPX Tcommaaccent Acircumflex -90
+KPX Tcommaaccent Adieresis -90
+KPX Tcommaaccent Agrave -90
+KPX Tcommaaccent Amacron -90
+KPX Tcommaaccent Aogonek -90
+KPX Tcommaaccent Aring -90
+KPX Tcommaaccent Atilde -90
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -80
+KPX Tcommaaccent aacute -80
+KPX Tcommaaccent abreve -80
+KPX Tcommaaccent acircumflex -80
+KPX Tcommaaccent adieresis -80
+KPX Tcommaaccent agrave -80
+KPX Tcommaaccent amacron -80
+KPX Tcommaaccent aogonek -80
+KPX Tcommaaccent aring -80
+KPX Tcommaaccent atilde -80
+KPX Tcommaaccent colon -40
+KPX Tcommaaccent comma -80
+KPX Tcommaaccent e -60
+KPX Tcommaaccent eacute -60
+KPX Tcommaaccent ecaron -60
+KPX Tcommaaccent ecircumflex -60
+KPX Tcommaaccent edieresis -60
+KPX Tcommaaccent edotaccent -60
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -60
+KPX Tcommaaccent hyphen -120
+KPX Tcommaaccent o -80
+KPX Tcommaaccent oacute -80
+KPX Tcommaaccent ocircumflex -80
+KPX Tcommaaccent odieresis -80
+KPX Tcommaaccent ograve -80
+KPX Tcommaaccent ohungarumlaut -80
+KPX Tcommaaccent omacron -80
+KPX Tcommaaccent oslash -80
+KPX Tcommaaccent otilde -80
+KPX Tcommaaccent period -80
+KPX Tcommaaccent r -80
+KPX Tcommaaccent racute -80
+KPX Tcommaaccent rcommaaccent -80
+KPX Tcommaaccent semicolon -40
+KPX Tcommaaccent u -90
+KPX Tcommaaccent uacute -90
+KPX Tcommaaccent ucircumflex -90
+KPX Tcommaaccent udieresis -90
+KPX Tcommaaccent ugrave -90
+KPX Tcommaaccent uhungarumlaut -90
+KPX Tcommaaccent umacron -90
+KPX Tcommaaccent uogonek -90
+KPX Tcommaaccent uring -90
+KPX Tcommaaccent w -60
+KPX Tcommaaccent y -60
+KPX Tcommaaccent yacute -60
+KPX Tcommaaccent ydieresis -60
+KPX U A -50
+KPX U Aacute -50
+KPX U Abreve -50
+KPX U Acircumflex -50
+KPX U Adieresis -50
+KPX U Agrave -50
+KPX U Amacron -50
+KPX U Aogonek -50
+KPX U Aring -50
+KPX U Atilde -50
+KPX U comma -30
+KPX U period -30
+KPX Uacute A -50
+KPX Uacute Aacute -50
+KPX Uacute Abreve -50
+KPX Uacute Acircumflex -50
+KPX Uacute Adieresis -50
+KPX Uacute Agrave -50
+KPX Uacute Amacron -50
+KPX Uacute Aogonek -50
+KPX Uacute Aring -50
+KPX Uacute Atilde -50
+KPX Uacute comma -30
+KPX Uacute period -30
+KPX Ucircumflex A -50
+KPX Ucircumflex Aacute -50
+KPX Ucircumflex Abreve -50
+KPX Ucircumflex Acircumflex -50
+KPX Ucircumflex Adieresis -50
+KPX Ucircumflex Agrave -50
+KPX Ucircumflex Amacron -50
+KPX Ucircumflex Aogonek -50
+KPX Ucircumflex Aring -50
+KPX Ucircumflex Atilde -50
+KPX Ucircumflex comma -30
+KPX Ucircumflex period -30
+KPX Udieresis A -50
+KPX Udieresis Aacute -50
+KPX Udieresis Abreve -50
+KPX Udieresis Acircumflex -50
+KPX Udieresis Adieresis -50
+KPX Udieresis Agrave -50
+KPX Udieresis Amacron -50
+KPX Udieresis Aogonek -50
+KPX Udieresis Aring -50
+KPX Udieresis Atilde -50
+KPX Udieresis comma -30
+KPX Udieresis period -30
+KPX Ugrave A -50
+KPX Ugrave Aacute -50
+KPX Ugrave Abreve -50
+KPX Ugrave Acircumflex -50
+KPX Ugrave Adieresis -50
+KPX Ugrave Agrave -50
+KPX Ugrave Amacron -50
+KPX Ugrave Aogonek -50
+KPX Ugrave Aring -50
+KPX Ugrave Atilde -50
+KPX Ugrave comma -30
+KPX Ugrave period -30
+KPX Uhungarumlaut A -50
+KPX Uhungarumlaut Aacute -50
+KPX Uhungarumlaut Abreve -50
+KPX Uhungarumlaut Acircumflex -50
+KPX Uhungarumlaut Adieresis -50
+KPX Uhungarumlaut Agrave -50
+KPX Uhungarumlaut Amacron -50
+KPX Uhungarumlaut Aogonek -50
+KPX Uhungarumlaut Aring -50
+KPX Uhungarumlaut Atilde -50
+KPX Uhungarumlaut comma -30
+KPX Uhungarumlaut period -30
+KPX Umacron A -50
+KPX Umacron Aacute -50
+KPX Umacron Abreve -50
+KPX Umacron Acircumflex -50
+KPX Umacron Adieresis -50
+KPX Umacron Agrave -50
+KPX Umacron Amacron -50
+KPX Umacron Aogonek -50
+KPX Umacron Aring -50
+KPX Umacron Atilde -50
+KPX Umacron comma -30
+KPX Umacron period -30
+KPX Uogonek A -50
+KPX Uogonek Aacute -50
+KPX Uogonek Abreve -50
+KPX Uogonek Acircumflex -50
+KPX Uogonek Adieresis -50
+KPX Uogonek Agrave -50
+KPX Uogonek Amacron -50
+KPX Uogonek Aogonek -50
+KPX Uogonek Aring -50
+KPX Uogonek Atilde -50
+KPX Uogonek comma -30
+KPX Uogonek period -30
+KPX Uring A -50
+KPX Uring Aacute -50
+KPX Uring Abreve -50
+KPX Uring Acircumflex -50
+KPX Uring Adieresis -50
+KPX Uring Agrave -50
+KPX Uring Amacron -50
+KPX Uring Aogonek -50
+KPX Uring Aring -50
+KPX Uring Atilde -50
+KPX Uring comma -30
+KPX Uring period -30
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -50
+KPX V Gbreve -50
+KPX V Gcommaaccent -50
+KPX V O -50
+KPX V Oacute -50
+KPX V Ocircumflex -50
+KPX V Odieresis -50
+KPX V Ograve -50
+KPX V Ohungarumlaut -50
+KPX V Omacron -50
+KPX V Oslash -50
+KPX V Otilde -50
+KPX V a -60
+KPX V aacute -60
+KPX V abreve -60
+KPX V acircumflex -60
+KPX V adieresis -60
+KPX V agrave -60
+KPX V amacron -60
+KPX V aogonek -60
+KPX V aring -60
+KPX V atilde -60
+KPX V colon -40
+KPX V comma -120
+KPX V e -50
+KPX V eacute -50
+KPX V ecaron -50
+KPX V ecircumflex -50
+KPX V edieresis -50
+KPX V edotaccent -50
+KPX V egrave -50
+KPX V emacron -50
+KPX V eogonek -50
+KPX V hyphen -80
+KPX V o -90
+KPX V oacute -90
+KPX V ocircumflex -90
+KPX V odieresis -90
+KPX V ograve -90
+KPX V ohungarumlaut -90
+KPX V omacron -90
+KPX V oslash -90
+KPX V otilde -90
+KPX V period -120
+KPX V semicolon -40
+KPX V u -60
+KPX V uacute -60
+KPX V ucircumflex -60
+KPX V udieresis -60
+KPX V ugrave -60
+KPX V uhungarumlaut -60
+KPX V umacron -60
+KPX V uogonek -60
+KPX V uring -60
+KPX W A -60
+KPX W Aacute -60
+KPX W Abreve -60
+KPX W Acircumflex -60
+KPX W Adieresis -60
+KPX W Agrave -60
+KPX W Amacron -60
+KPX W Aogonek -60
+KPX W Aring -60
+KPX W Atilde -60
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W colon -10
+KPX W comma -80
+KPX W e -35
+KPX W eacute -35
+KPX W ecaron -35
+KPX W ecircumflex -35
+KPX W edieresis -35
+KPX W edotaccent -35
+KPX W egrave -35
+KPX W emacron -35
+KPX W eogonek -35
+KPX W hyphen -40
+KPX W o -60
+KPX W oacute -60
+KPX W ocircumflex -60
+KPX W odieresis -60
+KPX W ograve -60
+KPX W ohungarumlaut -60
+KPX W omacron -60
+KPX W oslash -60
+KPX W otilde -60
+KPX W period -80
+KPX W semicolon -10
+KPX W u -45
+KPX W uacute -45
+KPX W ucircumflex -45
+KPX W udieresis -45
+KPX W ugrave -45
+KPX W uhungarumlaut -45
+KPX W umacron -45
+KPX W uogonek -45
+KPX W uring -45
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -70
+KPX Y Oacute -70
+KPX Y Ocircumflex -70
+KPX Y Odieresis -70
+KPX Y Ograve -70
+KPX Y Ohungarumlaut -70
+KPX Y Omacron -70
+KPX Y Oslash -70
+KPX Y Otilde -70
+KPX Y a -90
+KPX Y aacute -90
+KPX Y abreve -90
+KPX Y acircumflex -90
+KPX Y adieresis -90
+KPX Y agrave -90
+KPX Y amacron -90
+KPX Y aogonek -90
+KPX Y aring -90
+KPX Y atilde -90
+KPX Y colon -50
+KPX Y comma -100
+KPX Y e -80
+KPX Y eacute -80
+KPX Y ecaron -80
+KPX Y ecircumflex -80
+KPX Y edieresis -80
+KPX Y edotaccent -80
+KPX Y egrave -80
+KPX Y emacron -80
+KPX Y eogonek -80
+KPX Y o -100
+KPX Y oacute -100
+KPX Y ocircumflex -100
+KPX Y odieresis -100
+KPX Y ograve -100
+KPX Y ohungarumlaut -100
+KPX Y omacron -100
+KPX Y oslash -100
+KPX Y otilde -100
+KPX Y period -100
+KPX Y semicolon -50
+KPX Y u -100
+KPX Y uacute -100
+KPX Y ucircumflex -100
+KPX Y udieresis -100
+KPX Y ugrave -100
+KPX Y uhungarumlaut -100
+KPX Y umacron -100
+KPX Y uogonek -100
+KPX Y uring -100
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -70
+KPX Yacute Oacute -70
+KPX Yacute Ocircumflex -70
+KPX Yacute Odieresis -70
+KPX Yacute Ograve -70
+KPX Yacute Ohungarumlaut -70
+KPX Yacute Omacron -70
+KPX Yacute Oslash -70
+KPX Yacute Otilde -70
+KPX Yacute a -90
+KPX Yacute aacute -90
+KPX Yacute abreve -90
+KPX Yacute acircumflex -90
+KPX Yacute adieresis -90
+KPX Yacute agrave -90
+KPX Yacute amacron -90
+KPX Yacute aogonek -90
+KPX Yacute aring -90
+KPX Yacute atilde -90
+KPX Yacute colon -50
+KPX Yacute comma -100
+KPX Yacute e -80
+KPX Yacute eacute -80
+KPX Yacute ecaron -80
+KPX Yacute ecircumflex -80
+KPX Yacute edieresis -80
+KPX Yacute edotaccent -80
+KPX Yacute egrave -80
+KPX Yacute emacron -80
+KPX Yacute eogonek -80
+KPX Yacute o -100
+KPX Yacute oacute -100
+KPX Yacute ocircumflex -100
+KPX Yacute odieresis -100
+KPX Yacute ograve -100
+KPX Yacute ohungarumlaut -100
+KPX Yacute omacron -100
+KPX Yacute oslash -100
+KPX Yacute otilde -100
+KPX Yacute period -100
+KPX Yacute semicolon -50
+KPX Yacute u -100
+KPX Yacute uacute -100
+KPX Yacute ucircumflex -100
+KPX Yacute udieresis -100
+KPX Yacute ugrave -100
+KPX Yacute uhungarumlaut -100
+KPX Yacute umacron -100
+KPX Yacute uogonek -100
+KPX Yacute uring -100
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -70
+KPX Ydieresis Oacute -70
+KPX Ydieresis Ocircumflex -70
+KPX Ydieresis Odieresis -70
+KPX Ydieresis Ograve -70
+KPX Ydieresis Ohungarumlaut -70
+KPX Ydieresis Omacron -70
+KPX Ydieresis Oslash -70
+KPX Ydieresis Otilde -70
+KPX Ydieresis a -90
+KPX Ydieresis aacute -90
+KPX Ydieresis abreve -90
+KPX Ydieresis acircumflex -90
+KPX Ydieresis adieresis -90
+KPX Ydieresis agrave -90
+KPX Ydieresis amacron -90
+KPX Ydieresis aogonek -90
+KPX Ydieresis aring -90
+KPX Ydieresis atilde -90
+KPX Ydieresis colon -50
+KPX Ydieresis comma -100
+KPX Ydieresis e -80
+KPX Ydieresis eacute -80
+KPX Ydieresis ecaron -80
+KPX Ydieresis ecircumflex -80
+KPX Ydieresis edieresis -80
+KPX Ydieresis edotaccent -80
+KPX Ydieresis egrave -80
+KPX Ydieresis emacron -80
+KPX Ydieresis eogonek -80
+KPX Ydieresis o -100
+KPX Ydieresis oacute -100
+KPX Ydieresis ocircumflex -100
+KPX Ydieresis odieresis -100
+KPX Ydieresis ograve -100
+KPX Ydieresis ohungarumlaut -100
+KPX Ydieresis omacron -100
+KPX Ydieresis oslash -100
+KPX Ydieresis otilde -100
+KPX Ydieresis period -100
+KPX Ydieresis semicolon -50
+KPX Ydieresis u -100
+KPX Ydieresis uacute -100
+KPX Ydieresis ucircumflex -100
+KPX Ydieresis udieresis -100
+KPX Ydieresis ugrave -100
+KPX Ydieresis uhungarumlaut -100
+KPX Ydieresis umacron -100
+KPX Ydieresis uogonek -100
+KPX Ydieresis uring -100
+KPX a g -10
+KPX a gbreve -10
+KPX a gcommaaccent -10
+KPX a v -15
+KPX a w -15
+KPX a y -20
+KPX a yacute -20
+KPX a ydieresis -20
+KPX aacute g -10
+KPX aacute gbreve -10
+KPX aacute gcommaaccent -10
+KPX aacute v -15
+KPX aacute w -15
+KPX aacute y -20
+KPX aacute yacute -20
+KPX aacute ydieresis -20
+KPX abreve g -10
+KPX abreve gbreve -10
+KPX abreve gcommaaccent -10
+KPX abreve v -15
+KPX abreve w -15
+KPX abreve y -20
+KPX abreve yacute -20
+KPX abreve ydieresis -20
+KPX acircumflex g -10
+KPX acircumflex gbreve -10
+KPX acircumflex gcommaaccent -10
+KPX acircumflex v -15
+KPX acircumflex w -15
+KPX acircumflex y -20
+KPX acircumflex yacute -20
+KPX acircumflex ydieresis -20
+KPX adieresis g -10
+KPX adieresis gbreve -10
+KPX adieresis gcommaaccent -10
+KPX adieresis v -15
+KPX adieresis w -15
+KPX adieresis y -20
+KPX adieresis yacute -20
+KPX adieresis ydieresis -20
+KPX agrave g -10
+KPX agrave gbreve -10
+KPX agrave gcommaaccent -10
+KPX agrave v -15
+KPX agrave w -15
+KPX agrave y -20
+KPX agrave yacute -20
+KPX agrave ydieresis -20
+KPX amacron g -10
+KPX amacron gbreve -10
+KPX amacron gcommaaccent -10
+KPX amacron v -15
+KPX amacron w -15
+KPX amacron y -20
+KPX amacron yacute -20
+KPX amacron ydieresis -20
+KPX aogonek g -10
+KPX aogonek gbreve -10
+KPX aogonek gcommaaccent -10
+KPX aogonek v -15
+KPX aogonek w -15
+KPX aogonek y -20
+KPX aogonek yacute -20
+KPX aogonek ydieresis -20
+KPX aring g -10
+KPX aring gbreve -10
+KPX aring gcommaaccent -10
+KPX aring v -15
+KPX aring w -15
+KPX aring y -20
+KPX aring yacute -20
+KPX aring ydieresis -20
+KPX atilde g -10
+KPX atilde gbreve -10
+KPX atilde gcommaaccent -10
+KPX atilde v -15
+KPX atilde w -15
+KPX atilde y -20
+KPX atilde yacute -20
+KPX atilde ydieresis -20
+KPX b l -10
+KPX b lacute -10
+KPX b lcommaaccent -10
+KPX b lslash -10
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c h -10
+KPX c k -20
+KPX c kcommaaccent -20
+KPX c l -20
+KPX c lacute -20
+KPX c lcommaaccent -20
+KPX c lslash -20
+KPX c y -10
+KPX c yacute -10
+KPX c ydieresis -10
+KPX cacute h -10
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX cacute l -20
+KPX cacute lacute -20
+KPX cacute lcommaaccent -20
+KPX cacute lslash -20
+KPX cacute y -10
+KPX cacute yacute -10
+KPX cacute ydieresis -10
+KPX ccaron h -10
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccaron l -20
+KPX ccaron lacute -20
+KPX ccaron lcommaaccent -20
+KPX ccaron lslash -20
+KPX ccaron y -10
+KPX ccaron yacute -10
+KPX ccaron ydieresis -10
+KPX ccedilla h -10
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX ccedilla l -20
+KPX ccedilla lacute -20
+KPX ccedilla lcommaaccent -20
+KPX ccedilla lslash -20
+KPX ccedilla y -10
+KPX ccedilla yacute -10
+KPX ccedilla ydieresis -10
+KPX colon space -40
+KPX comma quotedblright -120
+KPX comma quoteright -120
+KPX comma space -40
+KPX d d -10
+KPX d dcroat -10
+KPX d v -15
+KPX d w -15
+KPX d y -15
+KPX d yacute -15
+KPX d ydieresis -15
+KPX dcroat d -10
+KPX dcroat dcroat -10
+KPX dcroat v -15
+KPX dcroat w -15
+KPX dcroat y -15
+KPX dcroat yacute -15
+KPX dcroat ydieresis -15
+KPX e comma 10
+KPX e period 20
+KPX e v -15
+KPX e w -15
+KPX e x -15
+KPX e y -15
+KPX e yacute -15
+KPX e ydieresis -15
+KPX eacute comma 10
+KPX eacute period 20
+KPX eacute v -15
+KPX eacute w -15
+KPX eacute x -15
+KPX eacute y -15
+KPX eacute yacute -15
+KPX eacute ydieresis -15
+KPX ecaron comma 10
+KPX ecaron period 20
+KPX ecaron v -15
+KPX ecaron w -15
+KPX ecaron x -15
+KPX ecaron y -15
+KPX ecaron yacute -15
+KPX ecaron ydieresis -15
+KPX ecircumflex comma 10
+KPX ecircumflex period 20
+KPX ecircumflex v -15
+KPX ecircumflex w -15
+KPX ecircumflex x -15
+KPX ecircumflex y -15
+KPX ecircumflex yacute -15
+KPX ecircumflex ydieresis -15
+KPX edieresis comma 10
+KPX edieresis period 20
+KPX edieresis v -15
+KPX edieresis w -15
+KPX edieresis x -15
+KPX edieresis y -15
+KPX edieresis yacute -15
+KPX edieresis ydieresis -15
+KPX edotaccent comma 10
+KPX edotaccent period 20
+KPX edotaccent v -15
+KPX edotaccent w -15
+KPX edotaccent x -15
+KPX edotaccent y -15
+KPX edotaccent yacute -15
+KPX edotaccent ydieresis -15
+KPX egrave comma 10
+KPX egrave period 20
+KPX egrave v -15
+KPX egrave w -15
+KPX egrave x -15
+KPX egrave y -15
+KPX egrave yacute -15
+KPX egrave ydieresis -15
+KPX emacron comma 10
+KPX emacron period 20
+KPX emacron v -15
+KPX emacron w -15
+KPX emacron x -15
+KPX emacron y -15
+KPX emacron yacute -15
+KPX emacron ydieresis -15
+KPX eogonek comma 10
+KPX eogonek period 20
+KPX eogonek v -15
+KPX eogonek w -15
+KPX eogonek x -15
+KPX eogonek y -15
+KPX eogonek yacute -15
+KPX eogonek ydieresis -15
+KPX f comma -10
+KPX f e -10
+KPX f eacute -10
+KPX f ecaron -10
+KPX f ecircumflex -10
+KPX f edieresis -10
+KPX f edotaccent -10
+KPX f egrave -10
+KPX f emacron -10
+KPX f eogonek -10
+KPX f o -20
+KPX f oacute -20
+KPX f ocircumflex -20
+KPX f odieresis -20
+KPX f ograve -20
+KPX f ohungarumlaut -20
+KPX f omacron -20
+KPX f oslash -20
+KPX f otilde -20
+KPX f period -10
+KPX f quotedblright 30
+KPX f quoteright 30
+KPX g e 10
+KPX g eacute 10
+KPX g ecaron 10
+KPX g ecircumflex 10
+KPX g edieresis 10
+KPX g edotaccent 10
+KPX g egrave 10
+KPX g emacron 10
+KPX g eogonek 10
+KPX g g -10
+KPX g gbreve -10
+KPX g gcommaaccent -10
+KPX gbreve e 10
+KPX gbreve eacute 10
+KPX gbreve ecaron 10
+KPX gbreve ecircumflex 10
+KPX gbreve edieresis 10
+KPX gbreve edotaccent 10
+KPX gbreve egrave 10
+KPX gbreve emacron 10
+KPX gbreve eogonek 10
+KPX gbreve g -10
+KPX gbreve gbreve -10
+KPX gbreve gcommaaccent -10
+KPX gcommaaccent e 10
+KPX gcommaaccent eacute 10
+KPX gcommaaccent ecaron 10
+KPX gcommaaccent ecircumflex 10
+KPX gcommaaccent edieresis 10
+KPX gcommaaccent edotaccent 10
+KPX gcommaaccent egrave 10
+KPX gcommaaccent emacron 10
+KPX gcommaaccent eogonek 10
+KPX gcommaaccent g -10
+KPX gcommaaccent gbreve -10
+KPX gcommaaccent gcommaaccent -10
+KPX h y -20
+KPX h yacute -20
+KPX h ydieresis -20
+KPX k o -15
+KPX k oacute -15
+KPX k ocircumflex -15
+KPX k odieresis -15
+KPX k ograve -15
+KPX k ohungarumlaut -15
+KPX k omacron -15
+KPX k oslash -15
+KPX k otilde -15
+KPX kcommaaccent o -15
+KPX kcommaaccent oacute -15
+KPX kcommaaccent ocircumflex -15
+KPX kcommaaccent odieresis -15
+KPX kcommaaccent ograve -15
+KPX kcommaaccent ohungarumlaut -15
+KPX kcommaaccent omacron -15
+KPX kcommaaccent oslash -15
+KPX kcommaaccent otilde -15
+KPX l w -15
+KPX l y -15
+KPX l yacute -15
+KPX l ydieresis -15
+KPX lacute w -15
+KPX lacute y -15
+KPX lacute yacute -15
+KPX lacute ydieresis -15
+KPX lcommaaccent w -15
+KPX lcommaaccent y -15
+KPX lcommaaccent yacute -15
+KPX lcommaaccent ydieresis -15
+KPX lslash w -15
+KPX lslash y -15
+KPX lslash yacute -15
+KPX lslash ydieresis -15
+KPX m u -20
+KPX m uacute -20
+KPX m ucircumflex -20
+KPX m udieresis -20
+KPX m ugrave -20
+KPX m uhungarumlaut -20
+KPX m umacron -20
+KPX m uogonek -20
+KPX m uring -20
+KPX m y -30
+KPX m yacute -30
+KPX m ydieresis -30
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -40
+KPX n y -20
+KPX n yacute -20
+KPX n ydieresis -20
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -40
+KPX nacute y -20
+KPX nacute yacute -20
+KPX nacute ydieresis -20
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -40
+KPX ncaron y -20
+KPX ncaron yacute -20
+KPX ncaron ydieresis -20
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -40
+KPX ncommaaccent y -20
+KPX ncommaaccent yacute -20
+KPX ncommaaccent ydieresis -20
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -40
+KPX ntilde y -20
+KPX ntilde yacute -20
+KPX ntilde ydieresis -20
+KPX o v -20
+KPX o w -15
+KPX o x -30
+KPX o y -20
+KPX o yacute -20
+KPX o ydieresis -20
+KPX oacute v -20
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -20
+KPX oacute yacute -20
+KPX oacute ydieresis -20
+KPX ocircumflex v -20
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -20
+KPX ocircumflex yacute -20
+KPX ocircumflex ydieresis -20
+KPX odieresis v -20
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -20
+KPX odieresis yacute -20
+KPX odieresis ydieresis -20
+KPX ograve v -20
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -20
+KPX ograve yacute -20
+KPX ograve ydieresis -20
+KPX ohungarumlaut v -20
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -20
+KPX ohungarumlaut yacute -20
+KPX ohungarumlaut ydieresis -20
+KPX omacron v -20
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -20
+KPX omacron yacute -20
+KPX omacron ydieresis -20
+KPX oslash v -20
+KPX oslash w -15
+KPX oslash x -30
+KPX oslash y -20
+KPX oslash yacute -20
+KPX oslash ydieresis -20
+KPX otilde v -20
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -20
+KPX otilde yacute -20
+KPX otilde ydieresis -20
+KPX p y -15
+KPX p yacute -15
+KPX p ydieresis -15
+KPX period quotedblright -120
+KPX period quoteright -120
+KPX period space -40
+KPX quotedblright space -80
+KPX quoteleft quoteleft -46
+KPX quoteright d -80
+KPX quoteright dcroat -80
+KPX quoteright l -20
+KPX quoteright lacute -20
+KPX quoteright lcommaaccent -20
+KPX quoteright lslash -20
+KPX quoteright quoteright -46
+KPX quoteright r -40
+KPX quoteright racute -40
+KPX quoteright rcaron -40
+KPX quoteright rcommaaccent -40
+KPX quoteright s -60
+KPX quoteright sacute -60
+KPX quoteright scaron -60
+KPX quoteright scedilla -60
+KPX quoteright scommaaccent -60
+KPX quoteright space -80
+KPX quoteright v -20
+KPX r c -20
+KPX r cacute -20
+KPX r ccaron -20
+KPX r ccedilla -20
+KPX r comma -60
+KPX r d -20
+KPX r dcroat -20
+KPX r g -15
+KPX r gbreve -15
+KPX r gcommaaccent -15
+KPX r hyphen -20
+KPX r o -20
+KPX r oacute -20
+KPX r ocircumflex -20
+KPX r odieresis -20
+KPX r ograve -20
+KPX r ohungarumlaut -20
+KPX r omacron -20
+KPX r oslash -20
+KPX r otilde -20
+KPX r period -60
+KPX r q -20
+KPX r s -15
+KPX r sacute -15
+KPX r scaron -15
+KPX r scedilla -15
+KPX r scommaaccent -15
+KPX r t 20
+KPX r tcommaaccent 20
+KPX r v 10
+KPX r y 10
+KPX r yacute 10
+KPX r ydieresis 10
+KPX racute c -20
+KPX racute cacute -20
+KPX racute ccaron -20
+KPX racute ccedilla -20
+KPX racute comma -60
+KPX racute d -20
+KPX racute dcroat -20
+KPX racute g -15
+KPX racute gbreve -15
+KPX racute gcommaaccent -15
+KPX racute hyphen -20
+KPX racute o -20
+KPX racute oacute -20
+KPX racute ocircumflex -20
+KPX racute odieresis -20
+KPX racute ograve -20
+KPX racute ohungarumlaut -20
+KPX racute omacron -20
+KPX racute oslash -20
+KPX racute otilde -20
+KPX racute period -60
+KPX racute q -20
+KPX racute s -15
+KPX racute sacute -15
+KPX racute scaron -15
+KPX racute scedilla -15
+KPX racute scommaaccent -15
+KPX racute t 20
+KPX racute tcommaaccent 20
+KPX racute v 10
+KPX racute y 10
+KPX racute yacute 10
+KPX racute ydieresis 10
+KPX rcaron c -20
+KPX rcaron cacute -20
+KPX rcaron ccaron -20
+KPX rcaron ccedilla -20
+KPX rcaron comma -60
+KPX rcaron d -20
+KPX rcaron dcroat -20
+KPX rcaron g -15
+KPX rcaron gbreve -15
+KPX rcaron gcommaaccent -15
+KPX rcaron hyphen -20
+KPX rcaron o -20
+KPX rcaron oacute -20
+KPX rcaron ocircumflex -20
+KPX rcaron odieresis -20
+KPX rcaron ograve -20
+KPX rcaron ohungarumlaut -20
+KPX rcaron omacron -20
+KPX rcaron oslash -20
+KPX rcaron otilde -20
+KPX rcaron period -60
+KPX rcaron q -20
+KPX rcaron s -15
+KPX rcaron sacute -15
+KPX rcaron scaron -15
+KPX rcaron scedilla -15
+KPX rcaron scommaaccent -15
+KPX rcaron t 20
+KPX rcaron tcommaaccent 20
+KPX rcaron v 10
+KPX rcaron y 10
+KPX rcaron yacute 10
+KPX rcaron ydieresis 10
+KPX rcommaaccent c -20
+KPX rcommaaccent cacute -20
+KPX rcommaaccent ccaron -20
+KPX rcommaaccent ccedilla -20
+KPX rcommaaccent comma -60
+KPX rcommaaccent d -20
+KPX rcommaaccent dcroat -20
+KPX rcommaaccent g -15
+KPX rcommaaccent gbreve -15
+KPX rcommaaccent gcommaaccent -15
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent o -20
+KPX rcommaaccent oacute -20
+KPX rcommaaccent ocircumflex -20
+KPX rcommaaccent odieresis -20
+KPX rcommaaccent ograve -20
+KPX rcommaaccent ohungarumlaut -20
+KPX rcommaaccent omacron -20
+KPX rcommaaccent oslash -20
+KPX rcommaaccent otilde -20
+KPX rcommaaccent period -60
+KPX rcommaaccent q -20
+KPX rcommaaccent s -15
+KPX rcommaaccent sacute -15
+KPX rcommaaccent scaron -15
+KPX rcommaaccent scedilla -15
+KPX rcommaaccent scommaaccent -15
+KPX rcommaaccent t 20
+KPX rcommaaccent tcommaaccent 20
+KPX rcommaaccent v 10
+KPX rcommaaccent y 10
+KPX rcommaaccent yacute 10
+KPX rcommaaccent ydieresis 10
+KPX s w -15
+KPX sacute w -15
+KPX scaron w -15
+KPX scedilla w -15
+KPX scommaaccent w -15
+KPX semicolon space -40
+KPX space T -100
+KPX space Tcaron -100
+KPX space Tcommaaccent -100
+KPX space V -80
+KPX space W -80
+KPX space Y -120
+KPX space Yacute -120
+KPX space Ydieresis -120
+KPX space quotedblleft -80
+KPX space quoteleft -60
+KPX v a -20
+KPX v aacute -20
+KPX v abreve -20
+KPX v acircumflex -20
+KPX v adieresis -20
+KPX v agrave -20
+KPX v amacron -20
+KPX v aogonek -20
+KPX v aring -20
+KPX v atilde -20
+KPX v comma -80
+KPX v o -30
+KPX v oacute -30
+KPX v ocircumflex -30
+KPX v odieresis -30
+KPX v ograve -30
+KPX v ohungarumlaut -30
+KPX v omacron -30
+KPX v oslash -30
+KPX v otilde -30
+KPX v period -80
+KPX w comma -40
+KPX w o -20
+KPX w oacute -20
+KPX w ocircumflex -20
+KPX w odieresis -20
+KPX w ograve -20
+KPX w ohungarumlaut -20
+KPX w omacron -20
+KPX w oslash -20
+KPX w otilde -20
+KPX w period -40
+KPX x e -10
+KPX x eacute -10
+KPX x ecaron -10
+KPX x ecircumflex -10
+KPX x edieresis -10
+KPX x edotaccent -10
+KPX x egrave -10
+KPX x emacron -10
+KPX x eogonek -10
+KPX y a -30
+KPX y aacute -30
+KPX y abreve -30
+KPX y acircumflex -30
+KPX y adieresis -30
+KPX y agrave -30
+KPX y amacron -30
+KPX y aogonek -30
+KPX y aring -30
+KPX y atilde -30
+KPX y comma -80
+KPX y e -10
+KPX y eacute -10
+KPX y ecaron -10
+KPX y ecircumflex -10
+KPX y edieresis -10
+KPX y edotaccent -10
+KPX y egrave -10
+KPX y emacron -10
+KPX y eogonek -10
+KPX y o -25
+KPX y oacute -25
+KPX y ocircumflex -25
+KPX y odieresis -25
+KPX y ograve -25
+KPX y ohungarumlaut -25
+KPX y omacron -25
+KPX y oslash -25
+KPX y otilde -25
+KPX y period -80
+KPX yacute a -30
+KPX yacute aacute -30
+KPX yacute abreve -30
+KPX yacute acircumflex -30
+KPX yacute adieresis -30
+KPX yacute agrave -30
+KPX yacute amacron -30
+KPX yacute aogonek -30
+KPX yacute aring -30
+KPX yacute atilde -30
+KPX yacute comma -80
+KPX yacute e -10
+KPX yacute eacute -10
+KPX yacute ecaron -10
+KPX yacute ecircumflex -10
+KPX yacute edieresis -10
+KPX yacute edotaccent -10
+KPX yacute egrave -10
+KPX yacute emacron -10
+KPX yacute eogonek -10
+KPX yacute o -25
+KPX yacute oacute -25
+KPX yacute ocircumflex -25
+KPX yacute odieresis -25
+KPX yacute ograve -25
+KPX yacute ohungarumlaut -25
+KPX yacute omacron -25
+KPX yacute oslash -25
+KPX yacute otilde -25
+KPX yacute period -80
+KPX ydieresis a -30
+KPX ydieresis aacute -30
+KPX ydieresis abreve -30
+KPX ydieresis acircumflex -30
+KPX ydieresis adieresis -30
+KPX ydieresis agrave -30
+KPX ydieresis amacron -30
+KPX ydieresis aogonek -30
+KPX ydieresis aring -30
+KPX ydieresis atilde -30
+KPX ydieresis comma -80
+KPX ydieresis e -10
+KPX ydieresis eacute -10
+KPX ydieresis ecaron -10
+KPX ydieresis ecircumflex -10
+KPX ydieresis edieresis -10
+KPX ydieresis edotaccent -10
+KPX ydieresis egrave -10
+KPX ydieresis emacron -10
+KPX ydieresis eogonek -10
+KPX ydieresis o -25
+KPX ydieresis oacute -25
+KPX ydieresis ocircumflex -25
+KPX ydieresis odieresis -25
+KPX ydieresis ograve -25
+KPX ydieresis ohungarumlaut -25
+KPX ydieresis omacron -25
+KPX ydieresis oslash -25
+KPX ydieresis otilde -25
+KPX ydieresis period -80
+KPX z e 10
+KPX z eacute 10
+KPX z ecaron 10
+KPX z ecircumflex 10
+KPX z edieresis 10
+KPX z edotaccent 10
+KPX z egrave 10
+KPX z emacron 10
+KPX z eogonek 10
+KPX zacute e 10
+KPX zacute eacute 10
+KPX zacute ecaron 10
+KPX zacute ecircumflex 10
+KPX zacute edieresis 10
+KPX zacute edotaccent 10
+KPX zacute egrave 10
+KPX zacute emacron 10
+KPX zacute eogonek 10
+KPX zcaron e 10
+KPX zcaron eacute 10
+KPX zcaron ecaron 10
+KPX zcaron ecircumflex 10
+KPX zcaron edieresis 10
+KPX zcaron edotaccent 10
+KPX zcaron egrave 10
+KPX zcaron emacron 10
+KPX zcaron eogonek 10
+KPX zdotaccent e 10
+KPX zdotaccent eacute 10
+KPX zdotaccent ecaron 10
+KPX zdotaccent ecircumflex 10
+KPX zdotaccent edieresis 10
+KPX zdotaccent edotaccent 10
+KPX zdotaccent egrave 10
+KPX zdotaccent emacron 10
+KPX zdotaccent eogonek 10
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Oblique.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Oblique.afm
new file mode 100644
index 0000000..e7beaf0
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica-Oblique.afm
@@ -0,0 +1,3053 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:44:31 1997
+Comment UniqueID 43055
+Comment VMusage 14960 69346
+FontName Helvetica-Oblique
+FullName Helvetica Oblique
+FamilyName Helvetica
+Weight Medium
+ItalicAngle -12
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -170 -225 1116 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StdHW 76
+StdVW 88
+StartCharMetrics 317
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 90 0 340 718 ;
+C 34 ; WX 355 ; N quotedbl ; B 168 463 438 718 ;
+C 35 ; WX 556 ; N numbersign ; B 73 0 631 688 ;
+C 36 ; WX 556 ; N dollar ; B 69 -115 617 775 ;
+C 37 ; WX 889 ; N percent ; B 147 -19 889 703 ;
+C 38 ; WX 667 ; N ampersand ; B 77 -15 647 718 ;
+C 146 ; WX 222 ; N quoteright ; B 151 463 310 718 ;
+C 40 ; WX 333 ; N parenleft ; B 108 -207 454 733 ;
+C 41 ; WX 333 ; N parenright ; B -9 -207 337 733 ;
+C 42 ; WX 389 ; N asterisk ; B 165 431 475 718 ;
+C 43 ; WX 584 ; N plus ; B 85 0 606 505 ;
+C 44 ; WX 278 ; N comma ; B 56 -147 214 106 ;
+C 45 ; WX 333 ; N hyphen ; B 93 232 357 322 ;
+C 173 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 46 ; WX 278 ; N period ; B 87 0 214 106 ;
+C 47 ; WX 278 ; N slash ; B -21 -19 452 737 ;
+C 48 ; WX 556 ; N zero ; B 93 -19 608 703 ;
+C 49 ; WX 556 ; N one ; B 207 0 508 703 ;
+C 50 ; WX 556 ; N two ; B 26 0 617 703 ;
+C 51 ; WX 556 ; N three ; B 75 -19 610 703 ;
+C 52 ; WX 556 ; N four ; B 61 0 576 703 ;
+C 53 ; WX 556 ; N five ; B 68 -19 621 688 ;
+C 54 ; WX 556 ; N six ; B 91 -19 615 703 ;
+C 55 ; WX 556 ; N seven ; B 137 0 669 688 ;
+C 56 ; WX 556 ; N eight ; B 74 -19 607 703 ;
+C 57 ; WX 556 ; N nine ; B 82 -19 609 703 ;
+C 58 ; WX 278 ; N colon ; B 87 0 301 516 ;
+C 59 ; WX 278 ; N semicolon ; B 56 -147 301 516 ;
+C 60 ; WX 584 ; N less ; B 94 11 641 495 ;
+C 61 ; WX 584 ; N equal ; B 63 115 628 390 ;
+C 62 ; WX 584 ; N greater ; B 50 11 597 495 ;
+C 63 ; WX 556 ; N question ; B 161 0 610 727 ;
+C 64 ; WX 1015 ; N at ; B 215 -19 965 737 ;
+C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
+C 66 ; WX 667 ; N B ; B 74 0 712 718 ;
+C 67 ; WX 722 ; N C ; B 108 -19 782 737 ;
+C 68 ; WX 722 ; N D ; B 81 0 764 718 ;
+C 69 ; WX 667 ; N E ; B 86 0 762 718 ;
+C 70 ; WX 611 ; N F ; B 86 0 736 718 ;
+C 71 ; WX 778 ; N G ; B 111 -19 799 737 ;
+C 72 ; WX 722 ; N H ; B 77 0 799 718 ;
+C 73 ; WX 278 ; N I ; B 91 0 341 718 ;
+C 74 ; WX 500 ; N J ; B 47 -19 581 718 ;
+C 75 ; WX 667 ; N K ; B 76 0 808 718 ;
+C 76 ; WX 556 ; N L ; B 76 0 555 718 ;
+C 77 ; WX 833 ; N M ; B 73 0 914 718 ;
+C 78 ; WX 722 ; N N ; B 76 0 799 718 ;
+C 79 ; WX 778 ; N O ; B 105 -19 826 737 ;
+C 80 ; WX 667 ; N P ; B 86 0 737 718 ;
+C 81 ; WX 778 ; N Q ; B 105 -56 826 737 ;
+C 82 ; WX 722 ; N R ; B 88 0 773 718 ;
+C 83 ; WX 667 ; N S ; B 90 -19 713 737 ;
+C 84 ; WX 611 ; N T ; B 148 0 750 718 ;
+C 85 ; WX 722 ; N U ; B 123 -19 797 718 ;
+C 86 ; WX 667 ; N V ; B 173 0 800 718 ;
+C 87 ; WX 944 ; N W ; B 169 0 1081 718 ;
+C 88 ; WX 667 ; N X ; B 19 0 790 718 ;
+C 89 ; WX 667 ; N Y ; B 167 0 806 718 ;
+C 90 ; WX 611 ; N Z ; B 23 0 741 718 ;
+C 91 ; WX 278 ; N bracketleft ; B 21 -196 403 722 ;
+C 92 ; WX 278 ; N backslash ; B 140 -19 291 737 ;
+C 93 ; WX 278 ; N bracketright ; B -14 -196 368 722 ;
+C 94 ; WX 469 ; N asciicircum ; B 42 264 539 688 ;
+C 95 ; WX 556 ; N underscore ; B -27 -125 540 -75 ;
+C 145 ; WX 222 ; N quoteleft ; B 165 470 323 725 ;
+C 97 ; WX 556 ; N a ; B 61 -15 559 538 ;
+C 98 ; WX 556 ; N b ; B 58 -15 584 718 ;
+C 99 ; WX 500 ; N c ; B 74 -15 553 538 ;
+C 100 ; WX 556 ; N d ; B 84 -15 652 718 ;
+C 101 ; WX 556 ; N e ; B 84 -15 578 538 ;
+C 102 ; WX 278 ; N f ; B 86 0 416 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 42 -220 610 538 ;
+C 104 ; WX 556 ; N h ; B 65 0 573 718 ;
+C 105 ; WX 222 ; N i ; B 67 0 308 718 ;
+C 106 ; WX 222 ; N j ; B -60 -210 308 718 ;
+C 107 ; WX 500 ; N k ; B 67 0 600 718 ;
+C 108 ; WX 222 ; N l ; B 67 0 308 718 ;
+C 109 ; WX 833 ; N m ; B 65 0 852 538 ;
+C 110 ; WX 556 ; N n ; B 65 0 573 538 ;
+C 111 ; WX 556 ; N o ; B 83 -14 585 538 ;
+C 112 ; WX 556 ; N p ; B 14 -207 584 538 ;
+C 113 ; WX 556 ; N q ; B 84 -207 605 538 ;
+C 114 ; WX 333 ; N r ; B 77 0 446 538 ;
+C 115 ; WX 500 ; N s ; B 63 -15 529 538 ;
+C 116 ; WX 278 ; N t ; B 102 -7 368 669 ;
+C 117 ; WX 556 ; N u ; B 94 -15 600 523 ;
+C 118 ; WX 500 ; N v ; B 119 0 603 523 ;
+C 119 ; WX 722 ; N w ; B 125 0 820 523 ;
+C 120 ; WX 500 ; N x ; B 11 0 594 523 ;
+C 121 ; WX 500 ; N y ; B 15 -214 600 523 ;
+C 122 ; WX 500 ; N z ; B 31 0 571 523 ;
+C 123 ; WX 334 ; N braceleft ; B 92 -196 445 722 ;
+C 124 ; WX 260 ; N bar ; B 46 -225 332 775 ;
+C 125 ; WX 334 ; N braceright ; B 0 -196 354 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 111 180 580 326 ;
+C 161 ; WX 333 ; N exclamdown ; B 77 -195 326 523 ;
+C 162 ; WX 556 ; N cent ; B 95 -115 584 623 ;
+C 163 ; WX 556 ; N sterling ; B 49 -16 634 718 ;
+C -1 ; WX 167 ; N fraction ; B -170 -19 482 703 ;
+C 165 ; WX 556 ; N yen ; B 81 0 699 688 ;
+C 131 ; WX 556 ; N florin ; B -52 -207 654 737 ;
+C 167 ; WX 556 ; N section ; B 76 -191 584 737 ;
+C 164 ; WX 556 ; N currency ; B 60 99 646 603 ;
+C 39 ; WX 191 ; N quotesingle ; B 157 463 285 718 ;
+C 147 ; WX 333 ; N quotedblleft ; B 138 470 461 725 ;
+C 171 ; WX 556 ; N guillemotleft ; B 146 108 554 446 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 137 108 340 446 ;
+C 155 ; WX 333 ; N guilsinglright ; B 111 108 314 446 ;
+C -1 ; WX 500 ; N fi ; B 86 0 587 728 ;
+C -1 ; WX 500 ; N fl ; B 86 0 585 728 ;
+C 150 ; WX 556 ; N endash ; B 51 240 623 313 ;
+C 134 ; WX 556 ; N dagger ; B 135 -159 622 718 ;
+C 135 ; WX 556 ; N daggerdbl ; B 52 -159 623 718 ;
+C 183 ; WX 278 ; N periodcentered ; B 129 190 257 315 ;
+C 182 ; WX 537 ; N paragraph ; B 126 -173 650 718 ;
+C 149 ; WX 350 ; N bullet ; B 91 202 413 517 ;
+C 130 ; WX 222 ; N quotesinglbase ; B 21 -149 180 106 ;
+C 132 ; WX 333 ; N quotedblbase ; B -6 -149 318 106 ;
+C 148 ; WX 333 ; N quotedblright ; B 124 463 448 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 120 108 528 446 ;
+C 133 ; WX 1000 ; N ellipsis ; B 115 0 908 106 ;
+C 137 ; WX 1000 ; N perthousand ; B 88 -19 1029 703 ;
+C 191 ; WX 611 ; N questiondown ; B 85 -201 534 525 ;
+C 96 ; WX 333 ; N grave ; B 170 593 337 734 ;
+C 180 ; WX 333 ; N acute ; B 248 593 475 734 ;
+C 136 ; WX 333 ; N circumflex ; B 147 593 438 734 ;
+C 152 ; WX 333 ; N tilde ; B 125 606 490 722 ;
+C 175 ; WX 333 ; N macron ; B 143 627 468 684 ;
+C -1 ; WX 333 ; N breve ; B 167 595 476 731 ;
+C -1 ; WX 333 ; N dotaccent ; B 249 604 362 706 ;
+C 168 ; WX 333 ; N dieresis ; B 168 604 443 706 ;
+C -1 ; WX 333 ; N ring ; B 214 572 402 756 ;
+C 184 ; WX 333 ; N cedilla ; B 2 -225 232 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B 157 593 565 734 ;
+C -1 ; WX 333 ; N ogonek ; B 43 -225 249 0 ;
+C -1 ; WX 333 ; N caron ; B 177 593 468 734 ;
+C 151 ; WX 1000 ; N emdash ; B 51 240 1067 313 ;
+C 198 ; WX 1000 ; N AE ; B 8 0 1097 718 ;
+C 170 ; WX 370 ; N ordfeminine ; B 127 405 449 737 ;
+C -1 ; WX 556 ; N Lslash ; B 41 0 555 718 ;
+C 216 ; WX 778 ; N Oslash ; B 43 -19 890 737 ;
+C 140 ; WX 1000 ; N OE ; B 98 -19 1116 737 ;
+C 186 ; WX 365 ; N ordmasculine ; B 141 405 468 737 ;
+C 230 ; WX 889 ; N ae ; B 61 -15 909 538 ;
+C -1 ; WX 278 ; N dotlessi ; B 95 0 294 523 ;
+C -1 ; WX 222 ; N lslash ; B 41 0 347 718 ;
+C 248 ; WX 611 ; N oslash ; B 29 -22 647 545 ;
+C 156 ; WX 944 ; N oe ; B 83 -15 964 538 ;
+C 223 ; WX 611 ; N germandbls ; B 67 -15 658 728 ;
+C 207 ; WX 278 ; N Idieresis ; B 91 0 458 901 ;
+C 233 ; WX 556 ; N eacute ; B 84 -15 587 734 ;
+C -1 ; WX 556 ; N abreve ; B 61 -15 578 731 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 94 -15 677 734 ;
+C -1 ; WX 556 ; N ecaron ; B 84 -15 580 734 ;
+C 159 ; WX 667 ; N Ydieresis ; B 167 0 806 901 ;
+C 247 ; WX 584 ; N divide ; B 85 -19 606 524 ;
+C 221 ; WX 667 ; N Yacute ; B 167 0 806 929 ;
+C 194 ; WX 667 ; N Acircumflex ; B 14 0 654 929 ;
+C 225 ; WX 556 ; N aacute ; B 61 -15 587 734 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 123 -19 797 929 ;
+C 253 ; WX 500 ; N yacute ; B 15 -214 600 734 ;
+C -1 ; WX 500 ; N scommaaccent ; B 63 -225 529 538 ;
+C 234 ; WX 556 ; N ecircumflex ; B 84 -15 578 734 ;
+C -1 ; WX 722 ; N Uring ; B 123 -19 797 931 ;
+C 220 ; WX 722 ; N Udieresis ; B 123 -19 797 901 ;
+C -1 ; WX 556 ; N aogonek ; B 61 -220 559 538 ;
+C 218 ; WX 722 ; N Uacute ; B 123 -19 797 929 ;
+C -1 ; WX 556 ; N uogonek ; B 94 -225 600 523 ;
+C 203 ; WX 667 ; N Edieresis ; B 86 0 762 901 ;
+C -1 ; WX 722 ; N Dcroat ; B 69 0 764 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 39 -225 172 -40 ;
+C 169 ; WX 737 ; N copyright ; B 54 -19 837 737 ;
+C -1 ; WX 667 ; N Emacron ; B 86 0 762 879 ;
+C -1 ; WX 500 ; N ccaron ; B 74 -15 553 734 ;
+C 229 ; WX 556 ; N aring ; B 61 -15 559 756 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 76 -225 799 718 ;
+C -1 ; WX 222 ; N lacute ; B 67 0 461 929 ;
+C 224 ; WX 556 ; N agrave ; B 61 -15 559 734 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 148 -225 750 718 ;
+C -1 ; WX 722 ; N Cacute ; B 108 -19 782 929 ;
+C 227 ; WX 556 ; N atilde ; B 61 -15 592 722 ;
+C -1 ; WX 667 ; N Edotaccent ; B 86 0 762 901 ;
+C 154 ; WX 500 ; N scaron ; B 63 -15 552 734 ;
+C -1 ; WX 500 ; N scedilla ; B 63 -225 529 538 ;
+C 237 ; WX 278 ; N iacute ; B 95 0 448 734 ;
+C -1 ; WX 471 ; N lozenge ; B 88 0 540 728 ;
+C -1 ; WX 722 ; N Rcaron ; B 88 0 773 929 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 111 -225 799 737 ;
+C 251 ; WX 556 ; N ucircumflex ; B 94 -15 600 734 ;
+C 226 ; WX 556 ; N acircumflex ; B 61 -15 559 734 ;
+C -1 ; WX 667 ; N Amacron ; B 14 0 677 879 ;
+C -1 ; WX 333 ; N rcaron ; B 77 0 508 734 ;
+C 231 ; WX 500 ; N ccedilla ; B 74 -225 553 538 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 23 0 741 901 ;
+C 222 ; WX 667 ; N Thorn ; B 86 0 712 718 ;
+C -1 ; WX 778 ; N Omacron ; B 105 -19 826 879 ;
+C -1 ; WX 722 ; N Racute ; B 88 0 773 929 ;
+C -1 ; WX 667 ; N Sacute ; B 90 -19 713 929 ;
+C -1 ; WX 643 ; N dcaron ; B 84 -15 808 718 ;
+C -1 ; WX 722 ; N Umacron ; B 123 -19 797 879 ;
+C -1 ; WX 556 ; N uring ; B 94 -15 600 756 ;
+C 179 ; WX 333 ; N threesuperior ; B 90 270 436 703 ;
+C 210 ; WX 778 ; N Ograve ; B 105 -19 826 929 ;
+C 192 ; WX 667 ; N Agrave ; B 14 0 654 929 ;
+C -1 ; WX 667 ; N Abreve ; B 14 0 685 926 ;
+C 215 ; WX 584 ; N multiply ; B 50 0 642 506 ;
+C 250 ; WX 556 ; N uacute ; B 94 -15 600 734 ;
+C -1 ; WX 611 ; N Tcaron ; B 148 0 750 929 ;
+C -1 ; WX 476 ; N partialdiff ; B 41 -38 550 714 ;
+C 255 ; WX 500 ; N ydieresis ; B 15 -214 600 706 ;
+C -1 ; WX 722 ; N Nacute ; B 76 0 799 929 ;
+C 238 ; WX 278 ; N icircumflex ; B 95 0 411 734 ;
+C 202 ; WX 667 ; N Ecircumflex ; B 86 0 762 929 ;
+C 228 ; WX 556 ; N adieresis ; B 61 -15 559 706 ;
+C 235 ; WX 556 ; N edieresis ; B 84 -15 578 706 ;
+C -1 ; WX 500 ; N cacute ; B 74 -15 559 734 ;
+C -1 ; WX 556 ; N nacute ; B 65 0 587 734 ;
+C -1 ; WX 556 ; N umacron ; B 94 -15 600 684 ;
+C -1 ; WX 722 ; N Ncaron ; B 76 0 799 929 ;
+C 205 ; WX 278 ; N Iacute ; B 91 0 489 929 ;
+C 177 ; WX 584 ; N plusminus ; B 39 0 618 506 ;
+C 166 ; WX 260 ; N brokenbar ; B 62 -150 316 700 ;
+C 174 ; WX 737 ; N registered ; B 54 -19 837 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 111 -19 799 926 ;
+C -1 ; WX 278 ; N Idotaccent ; B 91 0 377 901 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 671 706 ;
+C 200 ; WX 667 ; N Egrave ; B 86 0 762 929 ;
+C -1 ; WX 333 ; N racute ; B 77 0 475 734 ;
+C -1 ; WX 556 ; N omacron ; B 83 -14 585 684 ;
+C -1 ; WX 611 ; N Zacute ; B 23 0 741 929 ;
+C 142 ; WX 611 ; N Zcaron ; B 23 0 741 929 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 620 674 ;
+C 208 ; WX 722 ; N Eth ; B 69 0 764 718 ;
+C 199 ; WX 722 ; N Ccedilla ; B 108 -225 782 737 ;
+C -1 ; WX 222 ; N lcommaaccent ; B 25 -225 308 718 ;
+C -1 ; WX 317 ; N tcaron ; B 102 -7 501 808 ;
+C -1 ; WX 556 ; N eogonek ; B 84 -225 578 538 ;
+C -1 ; WX 722 ; N Uogonek ; B 123 -225 797 718 ;
+C 193 ; WX 667 ; N Aacute ; B 14 0 683 929 ;
+C 196 ; WX 667 ; N Adieresis ; B 14 0 654 901 ;
+C 232 ; WX 556 ; N egrave ; B 84 -15 578 734 ;
+C -1 ; WX 500 ; N zacute ; B 31 0 571 734 ;
+C -1 ; WX 222 ; N iogonek ; B -61 -225 308 718 ;
+C 211 ; WX 778 ; N Oacute ; B 105 -19 826 929 ;
+C 243 ; WX 556 ; N oacute ; B 83 -14 587 734 ;
+C -1 ; WX 556 ; N amacron ; B 61 -15 580 684 ;
+C -1 ; WX 500 ; N sacute ; B 63 -15 559 734 ;
+C 239 ; WX 278 ; N idieresis ; B 95 0 416 706 ;
+C 212 ; WX 778 ; N Ocircumflex ; B 105 -19 826 929 ;
+C 217 ; WX 722 ; N Ugrave ; B 123 -19 797 929 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 556 ; N thorn ; B 14 -207 584 718 ;
+C 178 ; WX 333 ; N twosuperior ; B 64 281 449 703 ;
+C 214 ; WX 778 ; N Odieresis ; B 105 -19 826 901 ;
+C 181 ; WX 556 ; N mu ; B 24 -207 600 523 ;
+C 236 ; WX 278 ; N igrave ; B 95 0 310 734 ;
+C -1 ; WX 556 ; N ohungarumlaut ; B 83 -14 677 734 ;
+C -1 ; WX 667 ; N Eogonek ; B 86 -220 762 718 ;
+C -1 ; WX 556 ; N dcroat ; B 84 -15 689 718 ;
+C 190 ; WX 834 ; N threequarters ; B 130 -19 861 703 ;
+C -1 ; WX 667 ; N Scedilla ; B 90 -225 713 737 ;
+C -1 ; WX 299 ; N lcaron ; B 67 0 464 718 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B 76 -225 808 718 ;
+C -1 ; WX 556 ; N Lacute ; B 76 0 555 929 ;
+C 153 ; WX 1000 ; N trademark ; B 186 306 1056 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 84 -15 578 706 ;
+C 204 ; WX 278 ; N Igrave ; B 91 0 351 929 ;
+C -1 ; WX 278 ; N Imacron ; B 91 0 483 879 ;
+C -1 ; WX 556 ; N Lcaron ; B 76 0 570 718 ;
+C 189 ; WX 834 ; N onehalf ; B 114 -19 839 703 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 666 674 ;
+C 244 ; WX 556 ; N ocircumflex ; B 83 -14 585 734 ;
+C 241 ; WX 556 ; N ntilde ; B 65 0 592 722 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 123 -19 801 929 ;
+C 201 ; WX 667 ; N Eacute ; B 86 0 762 929 ;
+C -1 ; WX 556 ; N emacron ; B 84 -15 580 684 ;
+C -1 ; WX 556 ; N gbreve ; B 42 -220 610 731 ;
+C 188 ; WX 834 ; N onequarter ; B 150 -19 802 703 ;
+C 138 ; WX 667 ; N Scaron ; B 90 -19 713 929 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 90 -225 713 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 105 -19 829 929 ;
+C 176 ; WX 400 ; N degree ; B 169 411 468 703 ;
+C 242 ; WX 556 ; N ograve ; B 83 -14 585 734 ;
+C -1 ; WX 722 ; N Ccaron ; B 108 -19 782 929 ;
+C 249 ; WX 556 ; N ugrave ; B 94 -15 600 734 ;
+C -1 ; WX 453 ; N radical ; B 79 -80 617 762 ;
+C -1 ; WX 722 ; N Dcaron ; B 81 0 764 929 ;
+C -1 ; WX 333 ; N rcommaaccent ; B 30 -225 446 538 ;
+C 209 ; WX 722 ; N Ntilde ; B 76 0 799 917 ;
+C 245 ; WX 556 ; N otilde ; B 83 -14 602 722 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 88 -225 773 718 ;
+C -1 ; WX 556 ; N Lcommaaccent ; B 76 -225 555 718 ;
+C 195 ; WX 667 ; N Atilde ; B 14 0 699 917 ;
+C -1 ; WX 667 ; N Aogonek ; B 14 -225 654 718 ;
+C 197 ; WX 667 ; N Aring ; B 14 0 654 931 ;
+C 213 ; WX 778 ; N Otilde ; B 105 -19 826 917 ;
+C -1 ; WX 500 ; N zdotaccent ; B 31 0 571 706 ;
+C -1 ; WX 667 ; N Ecaron ; B 86 0 762 929 ;
+C -1 ; WX 278 ; N Iogonek ; B -33 -225 341 718 ;
+C -1 ; WX 500 ; N kcommaaccent ; B 67 -225 600 718 ;
+C -1 ; WX 584 ; N minus ; B 85 216 606 289 ;
+C 206 ; WX 278 ; N Icircumflex ; B 91 0 452 929 ;
+C -1 ; WX 556 ; N ncaron ; B 65 0 580 734 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 63 -225 368 669 ;
+C 172 ; WX 584 ; N logicalnot ; B 106 108 628 390 ;
+C 246 ; WX 556 ; N odieresis ; B 83 -14 585 706 ;
+C 252 ; WX 556 ; N udieresis ; B 94 -15 600 706 ;
+C -1 ; WX 549 ; N notequal ; B 34 -35 623 551 ;
+C -1 ; WX 556 ; N gcommaaccent ; B 42 -220 610 822 ;
+C 240 ; WX 556 ; N eth ; B 81 -15 617 737 ;
+C 158 ; WX 500 ; N zcaron ; B 31 0 571 734 ;
+C -1 ; WX 556 ; N ncommaaccent ; B 65 -225 573 538 ;
+C 185 ; WX 333 ; N onesuperior ; B 166 281 371 703 ;
+C -1 ; WX 278 ; N imacron ; B 95 0 417 684 ;
+C 128 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2705
+KPX A C -30
+KPX A Cacute -30
+KPX A Ccaron -30
+KPX A Ccedilla -30
+KPX A G -30
+KPX A Gbreve -30
+KPX A Gcommaaccent -30
+KPX A O -30
+KPX A Oacute -30
+KPX A Ocircumflex -30
+KPX A Odieresis -30
+KPX A Ograve -30
+KPX A Ohungarumlaut -30
+KPX A Omacron -30
+KPX A Oslash -30
+KPX A Otilde -30
+KPX A Q -30
+KPX A T -120
+KPX A Tcaron -120
+KPX A Tcommaaccent -120
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -70
+KPX A W -50
+KPX A Y -100
+KPX A Yacute -100
+KPX A Ydieresis -100
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -40
+KPX A y -40
+KPX A yacute -40
+KPX A ydieresis -40
+KPX Aacute C -30
+KPX Aacute Cacute -30
+KPX Aacute Ccaron -30
+KPX Aacute Ccedilla -30
+KPX Aacute G -30
+KPX Aacute Gbreve -30
+KPX Aacute Gcommaaccent -30
+KPX Aacute O -30
+KPX Aacute Oacute -30
+KPX Aacute Ocircumflex -30
+KPX Aacute Odieresis -30
+KPX Aacute Ograve -30
+KPX Aacute Ohungarumlaut -30
+KPX Aacute Omacron -30
+KPX Aacute Oslash -30
+KPX Aacute Otilde -30
+KPX Aacute Q -30
+KPX Aacute T -120
+KPX Aacute Tcaron -120
+KPX Aacute Tcommaaccent -120
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -70
+KPX Aacute W -50
+KPX Aacute Y -100
+KPX Aacute Yacute -100
+KPX Aacute Ydieresis -100
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -40
+KPX Aacute y -40
+KPX Aacute yacute -40
+KPX Aacute ydieresis -40
+KPX Abreve C -30
+KPX Abreve Cacute -30
+KPX Abreve Ccaron -30
+KPX Abreve Ccedilla -30
+KPX Abreve G -30
+KPX Abreve Gbreve -30
+KPX Abreve Gcommaaccent -30
+KPX Abreve O -30
+KPX Abreve Oacute -30
+KPX Abreve Ocircumflex -30
+KPX Abreve Odieresis -30
+KPX Abreve Ograve -30
+KPX Abreve Ohungarumlaut -30
+KPX Abreve Omacron -30
+KPX Abreve Oslash -30
+KPX Abreve Otilde -30
+KPX Abreve Q -30
+KPX Abreve T -120
+KPX Abreve Tcaron -120
+KPX Abreve Tcommaaccent -120
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -70
+KPX Abreve W -50
+KPX Abreve Y -100
+KPX Abreve Yacute -100
+KPX Abreve Ydieresis -100
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -40
+KPX Abreve y -40
+KPX Abreve yacute -40
+KPX Abreve ydieresis -40
+KPX Acircumflex C -30
+KPX Acircumflex Cacute -30
+KPX Acircumflex Ccaron -30
+KPX Acircumflex Ccedilla -30
+KPX Acircumflex G -30
+KPX Acircumflex Gbreve -30
+KPX Acircumflex Gcommaaccent -30
+KPX Acircumflex O -30
+KPX Acircumflex Oacute -30
+KPX Acircumflex Ocircumflex -30
+KPX Acircumflex Odieresis -30
+KPX Acircumflex Ograve -30
+KPX Acircumflex Ohungarumlaut -30
+KPX Acircumflex Omacron -30
+KPX Acircumflex Oslash -30
+KPX Acircumflex Otilde -30
+KPX Acircumflex Q -30
+KPX Acircumflex T -120
+KPX Acircumflex Tcaron -120
+KPX Acircumflex Tcommaaccent -120
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -70
+KPX Acircumflex W -50
+KPX Acircumflex Y -100
+KPX Acircumflex Yacute -100
+KPX Acircumflex Ydieresis -100
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -40
+KPX Acircumflex y -40
+KPX Acircumflex yacute -40
+KPX Acircumflex ydieresis -40
+KPX Adieresis C -30
+KPX Adieresis Cacute -30
+KPX Adieresis Ccaron -30
+KPX Adieresis Ccedilla -30
+KPX Adieresis G -30
+KPX Adieresis Gbreve -30
+KPX Adieresis Gcommaaccent -30
+KPX Adieresis O -30
+KPX Adieresis Oacute -30
+KPX Adieresis Ocircumflex -30
+KPX Adieresis Odieresis -30
+KPX Adieresis Ograve -30
+KPX Adieresis Ohungarumlaut -30
+KPX Adieresis Omacron -30
+KPX Adieresis Oslash -30
+KPX Adieresis Otilde -30
+KPX Adieresis Q -30
+KPX Adieresis T -120
+KPX Adieresis Tcaron -120
+KPX Adieresis Tcommaaccent -120
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -70
+KPX Adieresis W -50
+KPX Adieresis Y -100
+KPX Adieresis Yacute -100
+KPX Adieresis Ydieresis -100
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -40
+KPX Adieresis y -40
+KPX Adieresis yacute -40
+KPX Adieresis ydieresis -40
+KPX Agrave C -30
+KPX Agrave Cacute -30
+KPX Agrave Ccaron -30
+KPX Agrave Ccedilla -30
+KPX Agrave G -30
+KPX Agrave Gbreve -30
+KPX Agrave Gcommaaccent -30
+KPX Agrave O -30
+KPX Agrave Oacute -30
+KPX Agrave Ocircumflex -30
+KPX Agrave Odieresis -30
+KPX Agrave Ograve -30
+KPX Agrave Ohungarumlaut -30
+KPX Agrave Omacron -30
+KPX Agrave Oslash -30
+KPX Agrave Otilde -30
+KPX Agrave Q -30
+KPX Agrave T -120
+KPX Agrave Tcaron -120
+KPX Agrave Tcommaaccent -120
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -70
+KPX Agrave W -50
+KPX Agrave Y -100
+KPX Agrave Yacute -100
+KPX Agrave Ydieresis -100
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -40
+KPX Agrave y -40
+KPX Agrave yacute -40
+KPX Agrave ydieresis -40
+KPX Amacron C -30
+KPX Amacron Cacute -30
+KPX Amacron Ccaron -30
+KPX Amacron Ccedilla -30
+KPX Amacron G -30
+KPX Amacron Gbreve -30
+KPX Amacron Gcommaaccent -30
+KPX Amacron O -30
+KPX Amacron Oacute -30
+KPX Amacron Ocircumflex -30
+KPX Amacron Odieresis -30
+KPX Amacron Ograve -30
+KPX Amacron Ohungarumlaut -30
+KPX Amacron Omacron -30
+KPX Amacron Oslash -30
+KPX Amacron Otilde -30
+KPX Amacron Q -30
+KPX Amacron T -120
+KPX Amacron Tcaron -120
+KPX Amacron Tcommaaccent -120
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -70
+KPX Amacron W -50
+KPX Amacron Y -100
+KPX Amacron Yacute -100
+KPX Amacron Ydieresis -100
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -40
+KPX Amacron y -40
+KPX Amacron yacute -40
+KPX Amacron ydieresis -40
+KPX Aogonek C -30
+KPX Aogonek Cacute -30
+KPX Aogonek Ccaron -30
+KPX Aogonek Ccedilla -30
+KPX Aogonek G -30
+KPX Aogonek Gbreve -30
+KPX Aogonek Gcommaaccent -30
+KPX Aogonek O -30
+KPX Aogonek Oacute -30
+KPX Aogonek Ocircumflex -30
+KPX Aogonek Odieresis -30
+KPX Aogonek Ograve -30
+KPX Aogonek Ohungarumlaut -30
+KPX Aogonek Omacron -30
+KPX Aogonek Oslash -30
+KPX Aogonek Otilde -30
+KPX Aogonek Q -30
+KPX Aogonek T -120
+KPX Aogonek Tcaron -120
+KPX Aogonek Tcommaaccent -120
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -70
+KPX Aogonek W -50
+KPX Aogonek Y -100
+KPX Aogonek Yacute -100
+KPX Aogonek Ydieresis -100
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -40
+KPX Aogonek y -40
+KPX Aogonek yacute -40
+KPX Aogonek ydieresis -40
+KPX Aring C -30
+KPX Aring Cacute -30
+KPX Aring Ccaron -30
+KPX Aring Ccedilla -30
+KPX Aring G -30
+KPX Aring Gbreve -30
+KPX Aring Gcommaaccent -30
+KPX Aring O -30
+KPX Aring Oacute -30
+KPX Aring Ocircumflex -30
+KPX Aring Odieresis -30
+KPX Aring Ograve -30
+KPX Aring Ohungarumlaut -30
+KPX Aring Omacron -30
+KPX Aring Oslash -30
+KPX Aring Otilde -30
+KPX Aring Q -30
+KPX Aring T -120
+KPX Aring Tcaron -120
+KPX Aring Tcommaaccent -120
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -70
+KPX Aring W -50
+KPX Aring Y -100
+KPX Aring Yacute -100
+KPX Aring Ydieresis -100
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -40
+KPX Aring y -40
+KPX Aring yacute -40
+KPX Aring ydieresis -40
+KPX Atilde C -30
+KPX Atilde Cacute -30
+KPX Atilde Ccaron -30
+KPX Atilde Ccedilla -30
+KPX Atilde G -30
+KPX Atilde Gbreve -30
+KPX Atilde Gcommaaccent -30
+KPX Atilde O -30
+KPX Atilde Oacute -30
+KPX Atilde Ocircumflex -30
+KPX Atilde Odieresis -30
+KPX Atilde Ograve -30
+KPX Atilde Ohungarumlaut -30
+KPX Atilde Omacron -30
+KPX Atilde Oslash -30
+KPX Atilde Otilde -30
+KPX Atilde Q -30
+KPX Atilde T -120
+KPX Atilde Tcaron -120
+KPX Atilde Tcommaaccent -120
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -70
+KPX Atilde W -50
+KPX Atilde Y -100
+KPX Atilde Yacute -100
+KPX Atilde Ydieresis -100
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -40
+KPX Atilde y -40
+KPX Atilde yacute -40
+KPX Atilde ydieresis -40
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX B comma -20
+KPX B period -20
+KPX C comma -30
+KPX C period -30
+KPX Cacute comma -30
+KPX Cacute period -30
+KPX Ccaron comma -30
+KPX Ccaron period -30
+KPX Ccedilla comma -30
+KPX Ccedilla period -30
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -70
+KPX D W -40
+KPX D Y -90
+KPX D Yacute -90
+KPX D Ydieresis -90
+KPX D comma -70
+KPX D period -70
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -70
+KPX Dcaron W -40
+KPX Dcaron Y -90
+KPX Dcaron Yacute -90
+KPX Dcaron Ydieresis -90
+KPX Dcaron comma -70
+KPX Dcaron period -70
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -70
+KPX Dcroat W -40
+KPX Dcroat Y -90
+KPX Dcroat Yacute -90
+KPX Dcroat Ydieresis -90
+KPX Dcroat comma -70
+KPX Dcroat period -70
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -50
+KPX F aacute -50
+KPX F abreve -50
+KPX F acircumflex -50
+KPX F adieresis -50
+KPX F agrave -50
+KPX F amacron -50
+KPX F aogonek -50
+KPX F aring -50
+KPX F atilde -50
+KPX F comma -150
+KPX F e -30
+KPX F eacute -30
+KPX F ecaron -30
+KPX F ecircumflex -30
+KPX F edieresis -30
+KPX F edotaccent -30
+KPX F egrave -30
+KPX F emacron -30
+KPX F eogonek -30
+KPX F o -30
+KPX F oacute -30
+KPX F ocircumflex -30
+KPX F odieresis -30
+KPX F ograve -30
+KPX F ohungarumlaut -30
+KPX F omacron -30
+KPX F oslash -30
+KPX F otilde -30
+KPX F period -150
+KPX F r -45
+KPX F racute -45
+KPX F rcaron -45
+KPX F rcommaaccent -45
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J a -20
+KPX J aacute -20
+KPX J abreve -20
+KPX J acircumflex -20
+KPX J adieresis -20
+KPX J agrave -20
+KPX J amacron -20
+KPX J aogonek -20
+KPX J aring -20
+KPX J atilde -20
+KPX J comma -30
+KPX J period -30
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -50
+KPX K Oacute -50
+KPX K Ocircumflex -50
+KPX K Odieresis -50
+KPX K Ograve -50
+KPX K Ohungarumlaut -50
+KPX K Omacron -50
+KPX K Oslash -50
+KPX K Otilde -50
+KPX K e -40
+KPX K eacute -40
+KPX K ecaron -40
+KPX K ecircumflex -40
+KPX K edieresis -40
+KPX K edotaccent -40
+KPX K egrave -40
+KPX K emacron -40
+KPX K eogonek -40
+KPX K o -40
+KPX K oacute -40
+KPX K ocircumflex -40
+KPX K odieresis -40
+KPX K ograve -40
+KPX K ohungarumlaut -40
+KPX K omacron -40
+KPX K oslash -40
+KPX K otilde -40
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -50
+KPX K yacute -50
+KPX K ydieresis -50
+KPX Kcommaaccent O -50
+KPX Kcommaaccent Oacute -50
+KPX Kcommaaccent Ocircumflex -50
+KPX Kcommaaccent Odieresis -50
+KPX Kcommaaccent Ograve -50
+KPX Kcommaaccent Ohungarumlaut -50
+KPX Kcommaaccent Omacron -50
+KPX Kcommaaccent Oslash -50
+KPX Kcommaaccent Otilde -50
+KPX Kcommaaccent e -40
+KPX Kcommaaccent eacute -40
+KPX Kcommaaccent ecaron -40
+KPX Kcommaaccent ecircumflex -40
+KPX Kcommaaccent edieresis -40
+KPX Kcommaaccent edotaccent -40
+KPX Kcommaaccent egrave -40
+KPX Kcommaaccent emacron -40
+KPX Kcommaaccent eogonek -40
+KPX Kcommaaccent o -40
+KPX Kcommaaccent oacute -40
+KPX Kcommaaccent ocircumflex -40
+KPX Kcommaaccent odieresis -40
+KPX Kcommaaccent ograve -40
+KPX Kcommaaccent ohungarumlaut -40
+KPX Kcommaaccent omacron -40
+KPX Kcommaaccent oslash -40
+KPX Kcommaaccent otilde -40
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -50
+KPX Kcommaaccent yacute -50
+KPX Kcommaaccent ydieresis -50
+KPX L T -110
+KPX L Tcaron -110
+KPX L Tcommaaccent -110
+KPX L V -110
+KPX L W -70
+KPX L Y -140
+KPX L Yacute -140
+KPX L Ydieresis -140
+KPX L quotedblright -140
+KPX L quoteright -160
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -110
+KPX Lacute Tcaron -110
+KPX Lacute Tcommaaccent -110
+KPX Lacute V -110
+KPX Lacute W -70
+KPX Lacute Y -140
+KPX Lacute Yacute -140
+KPX Lacute Ydieresis -140
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -160
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcaron T -110
+KPX Lcaron Tcaron -110
+KPX Lcaron Tcommaaccent -110
+KPX Lcaron V -110
+KPX Lcaron W -70
+KPX Lcaron Y -140
+KPX Lcaron Yacute -140
+KPX Lcaron Ydieresis -140
+KPX Lcaron quotedblright -140
+KPX Lcaron quoteright -160
+KPX Lcaron y -30
+KPX Lcaron yacute -30
+KPX Lcaron ydieresis -30
+KPX Lcommaaccent T -110
+KPX Lcommaaccent Tcaron -110
+KPX Lcommaaccent Tcommaaccent -110
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -70
+KPX Lcommaaccent Y -140
+KPX Lcommaaccent Yacute -140
+KPX Lcommaaccent Ydieresis -140
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -160
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -110
+KPX Lslash Tcaron -110
+KPX Lslash Tcommaaccent -110
+KPX Lslash V -110
+KPX Lslash W -70
+KPX Lslash Y -140
+KPX Lslash Yacute -140
+KPX Lslash Ydieresis -140
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -160
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -20
+KPX O Aacute -20
+KPX O Abreve -20
+KPX O Acircumflex -20
+KPX O Adieresis -20
+KPX O Agrave -20
+KPX O Amacron -20
+KPX O Aogonek -20
+KPX O Aring -20
+KPX O Atilde -20
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -30
+KPX O X -60
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -20
+KPX Oacute Aacute -20
+KPX Oacute Abreve -20
+KPX Oacute Acircumflex -20
+KPX Oacute Adieresis -20
+KPX Oacute Agrave -20
+KPX Oacute Amacron -20
+KPX Oacute Aogonek -20
+KPX Oacute Aring -20
+KPX Oacute Atilde -20
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -30
+KPX Oacute X -60
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -20
+KPX Ocircumflex Aacute -20
+KPX Ocircumflex Abreve -20
+KPX Ocircumflex Acircumflex -20
+KPX Ocircumflex Adieresis -20
+KPX Ocircumflex Agrave -20
+KPX Ocircumflex Amacron -20
+KPX Ocircumflex Aogonek -20
+KPX Ocircumflex Aring -20
+KPX Ocircumflex Atilde -20
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -30
+KPX Ocircumflex X -60
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -20
+KPX Odieresis Aacute -20
+KPX Odieresis Abreve -20
+KPX Odieresis Acircumflex -20
+KPX Odieresis Adieresis -20
+KPX Odieresis Agrave -20
+KPX Odieresis Amacron -20
+KPX Odieresis Aogonek -20
+KPX Odieresis Aring -20
+KPX Odieresis Atilde -20
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -30
+KPX Odieresis X -60
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -20
+KPX Ograve Aacute -20
+KPX Ograve Abreve -20
+KPX Ograve Acircumflex -20
+KPX Ograve Adieresis -20
+KPX Ograve Agrave -20
+KPX Ograve Amacron -20
+KPX Ograve Aogonek -20
+KPX Ograve Aring -20
+KPX Ograve Atilde -20
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -30
+KPX Ograve X -60
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -20
+KPX Ohungarumlaut Aacute -20
+KPX Ohungarumlaut Abreve -20
+KPX Ohungarumlaut Acircumflex -20
+KPX Ohungarumlaut Adieresis -20
+KPX Ohungarumlaut Agrave -20
+KPX Ohungarumlaut Amacron -20
+KPX Ohungarumlaut Aogonek -20
+KPX Ohungarumlaut Aring -20
+KPX Ohungarumlaut Atilde -20
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -30
+KPX Ohungarumlaut X -60
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -20
+KPX Omacron Aacute -20
+KPX Omacron Abreve -20
+KPX Omacron Acircumflex -20
+KPX Omacron Adieresis -20
+KPX Omacron Agrave -20
+KPX Omacron Amacron -20
+KPX Omacron Aogonek -20
+KPX Omacron Aring -20
+KPX Omacron Atilde -20
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -30
+KPX Omacron X -60
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -20
+KPX Oslash Aacute -20
+KPX Oslash Abreve -20
+KPX Oslash Acircumflex -20
+KPX Oslash Adieresis -20
+KPX Oslash Agrave -20
+KPX Oslash Amacron -20
+KPX Oslash Aogonek -20
+KPX Oslash Aring -20
+KPX Oslash Atilde -20
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -30
+KPX Oslash X -60
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -20
+KPX Otilde Aacute -20
+KPX Otilde Abreve -20
+KPX Otilde Acircumflex -20
+KPX Otilde Adieresis -20
+KPX Otilde Agrave -20
+KPX Otilde Amacron -20
+KPX Otilde Aogonek -20
+KPX Otilde Aring -20
+KPX Otilde Atilde -20
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -30
+KPX Otilde X -60
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -120
+KPX P Aacute -120
+KPX P Abreve -120
+KPX P Acircumflex -120
+KPX P Adieresis -120
+KPX P Agrave -120
+KPX P Amacron -120
+KPX P Aogonek -120
+KPX P Aring -120
+KPX P Atilde -120
+KPX P a -40
+KPX P aacute -40
+KPX P abreve -40
+KPX P acircumflex -40
+KPX P adieresis -40
+KPX P agrave -40
+KPX P amacron -40
+KPX P aogonek -40
+KPX P aring -40
+KPX P atilde -40
+KPX P comma -180
+KPX P e -50
+KPX P eacute -50
+KPX P ecaron -50
+KPX P ecircumflex -50
+KPX P edieresis -50
+KPX P edotaccent -50
+KPX P egrave -50
+KPX P emacron -50
+KPX P eogonek -50
+KPX P o -50
+KPX P oacute -50
+KPX P ocircumflex -50
+KPX P odieresis -50
+KPX P ograve -50
+KPX P ohungarumlaut -50
+KPX P omacron -50
+KPX P oslash -50
+KPX P otilde -50
+KPX P period -180
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -30
+KPX R Tcaron -30
+KPX R Tcommaaccent -30
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -50
+KPX R W -30
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -30
+KPX Racute Tcaron -30
+KPX Racute Tcommaaccent -30
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -50
+KPX Racute W -30
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -30
+KPX Rcaron Tcaron -30
+KPX Rcaron Tcommaaccent -30
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -50
+KPX Rcaron W -30
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -30
+KPX Rcommaaccent Tcaron -30
+KPX Rcommaaccent Tcommaaccent -30
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -30
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX S comma -20
+KPX S period -20
+KPX Sacute comma -20
+KPX Sacute period -20
+KPX Scaron comma -20
+KPX Scaron period -20
+KPX Scedilla comma -20
+KPX Scedilla period -20
+KPX Scommaaccent comma -20
+KPX Scommaaccent period -20
+KPX T A -120
+KPX T Aacute -120
+KPX T Abreve -120
+KPX T Acircumflex -120
+KPX T Adieresis -120
+KPX T Agrave -120
+KPX T Amacron -120
+KPX T Aogonek -120
+KPX T Aring -120
+KPX T Atilde -120
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -120
+KPX T aacute -120
+KPX T abreve -60
+KPX T acircumflex -120
+KPX T adieresis -120
+KPX T agrave -120
+KPX T amacron -60
+KPX T aogonek -120
+KPX T aring -120
+KPX T atilde -60
+KPX T colon -20
+KPX T comma -120
+KPX T e -120
+KPX T eacute -120
+KPX T ecaron -120
+KPX T ecircumflex -120
+KPX T edieresis -120
+KPX T edotaccent -120
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -120
+KPX T hyphen -140
+KPX T o -120
+KPX T oacute -120
+KPX T ocircumflex -120
+KPX T odieresis -120
+KPX T ograve -120
+KPX T ohungarumlaut -120
+KPX T omacron -60
+KPX T oslash -120
+KPX T otilde -60
+KPX T period -120
+KPX T r -120
+KPX T racute -120
+KPX T rcaron -120
+KPX T rcommaaccent -120
+KPX T semicolon -20
+KPX T u -120
+KPX T uacute -120
+KPX T ucircumflex -120
+KPX T udieresis -120
+KPX T ugrave -120
+KPX T uhungarumlaut -120
+KPX T umacron -60
+KPX T uogonek -120
+KPX T uring -120
+KPX T w -120
+KPX T y -120
+KPX T yacute -120
+KPX T ydieresis -60
+KPX Tcaron A -120
+KPX Tcaron Aacute -120
+KPX Tcaron Abreve -120
+KPX Tcaron Acircumflex -120
+KPX Tcaron Adieresis -120
+KPX Tcaron Agrave -120
+KPX Tcaron Amacron -120
+KPX Tcaron Aogonek -120
+KPX Tcaron Aring -120
+KPX Tcaron Atilde -120
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -120
+KPX Tcaron aacute -120
+KPX Tcaron abreve -60
+KPX Tcaron acircumflex -120
+KPX Tcaron adieresis -120
+KPX Tcaron agrave -120
+KPX Tcaron amacron -60
+KPX Tcaron aogonek -120
+KPX Tcaron aring -120
+KPX Tcaron atilde -60
+KPX Tcaron colon -20
+KPX Tcaron comma -120
+KPX Tcaron e -120
+KPX Tcaron eacute -120
+KPX Tcaron ecaron -120
+KPX Tcaron ecircumflex -120
+KPX Tcaron edieresis -120
+KPX Tcaron edotaccent -120
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -120
+KPX Tcaron hyphen -140
+KPX Tcaron o -120
+KPX Tcaron oacute -120
+KPX Tcaron ocircumflex -120
+KPX Tcaron odieresis -120
+KPX Tcaron ograve -120
+KPX Tcaron ohungarumlaut -120
+KPX Tcaron omacron -60
+KPX Tcaron oslash -120
+KPX Tcaron otilde -60
+KPX Tcaron period -120
+KPX Tcaron r -120
+KPX Tcaron racute -120
+KPX Tcaron rcaron -120
+KPX Tcaron rcommaaccent -120
+KPX Tcaron semicolon -20
+KPX Tcaron u -120
+KPX Tcaron uacute -120
+KPX Tcaron ucircumflex -120
+KPX Tcaron udieresis -120
+KPX Tcaron ugrave -120
+KPX Tcaron uhungarumlaut -120
+KPX Tcaron umacron -60
+KPX Tcaron uogonek -120
+KPX Tcaron uring -120
+KPX Tcaron w -120
+KPX Tcaron y -120
+KPX Tcaron yacute -120
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -120
+KPX Tcommaaccent Aacute -120
+KPX Tcommaaccent Abreve -120
+KPX Tcommaaccent Acircumflex -120
+KPX Tcommaaccent Adieresis -120
+KPX Tcommaaccent Agrave -120
+KPX Tcommaaccent Amacron -120
+KPX Tcommaaccent Aogonek -120
+KPX Tcommaaccent Aring -120
+KPX Tcommaaccent Atilde -120
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -120
+KPX Tcommaaccent aacute -120
+KPX Tcommaaccent abreve -60
+KPX Tcommaaccent acircumflex -120
+KPX Tcommaaccent adieresis -120
+KPX Tcommaaccent agrave -120
+KPX Tcommaaccent amacron -60
+KPX Tcommaaccent aogonek -120
+KPX Tcommaaccent aring -120
+KPX Tcommaaccent atilde -60
+KPX Tcommaaccent colon -20
+KPX Tcommaaccent comma -120
+KPX Tcommaaccent e -120
+KPX Tcommaaccent eacute -120
+KPX Tcommaaccent ecaron -120
+KPX Tcommaaccent ecircumflex -120
+KPX Tcommaaccent edieresis -120
+KPX Tcommaaccent edotaccent -120
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -120
+KPX Tcommaaccent hyphen -140
+KPX Tcommaaccent o -120
+KPX Tcommaaccent oacute -120
+KPX Tcommaaccent ocircumflex -120
+KPX Tcommaaccent odieresis -120
+KPX Tcommaaccent ograve -120
+KPX Tcommaaccent ohungarumlaut -120
+KPX Tcommaaccent omacron -60
+KPX Tcommaaccent oslash -120
+KPX Tcommaaccent otilde -60
+KPX Tcommaaccent period -120
+KPX Tcommaaccent r -120
+KPX Tcommaaccent racute -120
+KPX Tcommaaccent rcaron -120
+KPX Tcommaaccent rcommaaccent -120
+KPX Tcommaaccent semicolon -20
+KPX Tcommaaccent u -120
+KPX Tcommaaccent uacute -120
+KPX Tcommaaccent ucircumflex -120
+KPX Tcommaaccent udieresis -120
+KPX Tcommaaccent ugrave -120
+KPX Tcommaaccent uhungarumlaut -120
+KPX Tcommaaccent umacron -60
+KPX Tcommaaccent uogonek -120
+KPX Tcommaaccent uring -120
+KPX Tcommaaccent w -120
+KPX Tcommaaccent y -120
+KPX Tcommaaccent yacute -120
+KPX Tcommaaccent ydieresis -60
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX U comma -40
+KPX U period -40
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Uacute comma -40
+KPX Uacute period -40
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Ucircumflex comma -40
+KPX Ucircumflex period -40
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Udieresis comma -40
+KPX Udieresis period -40
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Ugrave comma -40
+KPX Ugrave period -40
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Uhungarumlaut comma -40
+KPX Uhungarumlaut period -40
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Umacron comma -40
+KPX Umacron period -40
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uogonek comma -40
+KPX Uogonek period -40
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX Uring comma -40
+KPX Uring period -40
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -40
+KPX V Gbreve -40
+KPX V Gcommaaccent -40
+KPX V O -40
+KPX V Oacute -40
+KPX V Ocircumflex -40
+KPX V Odieresis -40
+KPX V Ograve -40
+KPX V Ohungarumlaut -40
+KPX V Omacron -40
+KPX V Oslash -40
+KPX V Otilde -40
+KPX V a -70
+KPX V aacute -70
+KPX V abreve -70
+KPX V acircumflex -70
+KPX V adieresis -70
+KPX V agrave -70
+KPX V amacron -70
+KPX V aogonek -70
+KPX V aring -70
+KPX V atilde -70
+KPX V colon -40
+KPX V comma -125
+KPX V e -80
+KPX V eacute -80
+KPX V ecaron -80
+KPX V ecircumflex -80
+KPX V edieresis -80
+KPX V edotaccent -80
+KPX V egrave -80
+KPX V emacron -80
+KPX V eogonek -80
+KPX V hyphen -80
+KPX V o -80
+KPX V oacute -80
+KPX V ocircumflex -80
+KPX V odieresis -80
+KPX V ograve -80
+KPX V ohungarumlaut -80
+KPX V omacron -80
+KPX V oslash -80
+KPX V otilde -80
+KPX V period -125
+KPX V semicolon -40
+KPX V u -70
+KPX V uacute -70
+KPX V ucircumflex -70
+KPX V udieresis -70
+KPX V ugrave -70
+KPX V uhungarumlaut -70
+KPX V umacron -70
+KPX V uogonek -70
+KPX V uring -70
+KPX W A -50
+KPX W Aacute -50
+KPX W Abreve -50
+KPX W Acircumflex -50
+KPX W Adieresis -50
+KPX W Agrave -50
+KPX W Amacron -50
+KPX W Aogonek -50
+KPX W Aring -50
+KPX W Atilde -50
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W comma -80
+KPX W e -30
+KPX W eacute -30
+KPX W ecaron -30
+KPX W ecircumflex -30
+KPX W edieresis -30
+KPX W edotaccent -30
+KPX W egrave -30
+KPX W emacron -30
+KPX W eogonek -30
+KPX W hyphen -40
+KPX W o -30
+KPX W oacute -30
+KPX W ocircumflex -30
+KPX W odieresis -30
+KPX W ograve -30
+KPX W ohungarumlaut -30
+KPX W omacron -30
+KPX W oslash -30
+KPX W otilde -30
+KPX W period -80
+KPX W u -30
+KPX W uacute -30
+KPX W ucircumflex -30
+KPX W udieresis -30
+KPX W ugrave -30
+KPX W uhungarumlaut -30
+KPX W umacron -30
+KPX W uogonek -30
+KPX W uring -30
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -85
+KPX Y Oacute -85
+KPX Y Ocircumflex -85
+KPX Y Odieresis -85
+KPX Y Ograve -85
+KPX Y Ohungarumlaut -85
+KPX Y Omacron -85
+KPX Y Oslash -85
+KPX Y Otilde -85
+KPX Y a -140
+KPX Y aacute -140
+KPX Y abreve -70
+KPX Y acircumflex -140
+KPX Y adieresis -140
+KPX Y agrave -140
+KPX Y amacron -70
+KPX Y aogonek -140
+KPX Y aring -140
+KPX Y atilde -140
+KPX Y colon -60
+KPX Y comma -140
+KPX Y e -140
+KPX Y eacute -140
+KPX Y ecaron -140
+KPX Y ecircumflex -140
+KPX Y edieresis -140
+KPX Y edotaccent -140
+KPX Y egrave -140
+KPX Y emacron -70
+KPX Y eogonek -140
+KPX Y hyphen -140
+KPX Y i -20
+KPX Y iacute -20
+KPX Y iogonek -20
+KPX Y o -140
+KPX Y oacute -140
+KPX Y ocircumflex -140
+KPX Y odieresis -140
+KPX Y ograve -140
+KPX Y ohungarumlaut -140
+KPX Y omacron -140
+KPX Y oslash -140
+KPX Y otilde -140
+KPX Y period -140
+KPX Y semicolon -60
+KPX Y u -110
+KPX Y uacute -110
+KPX Y ucircumflex -110
+KPX Y udieresis -110
+KPX Y ugrave -110
+KPX Y uhungarumlaut -110
+KPX Y umacron -110
+KPX Y uogonek -110
+KPX Y uring -110
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -85
+KPX Yacute Oacute -85
+KPX Yacute Ocircumflex -85
+KPX Yacute Odieresis -85
+KPX Yacute Ograve -85
+KPX Yacute Ohungarumlaut -85
+KPX Yacute Omacron -85
+KPX Yacute Oslash -85
+KPX Yacute Otilde -85
+KPX Yacute a -140
+KPX Yacute aacute -140
+KPX Yacute abreve -70
+KPX Yacute acircumflex -140
+KPX Yacute adieresis -140
+KPX Yacute agrave -140
+KPX Yacute amacron -70
+KPX Yacute aogonek -140
+KPX Yacute aring -140
+KPX Yacute atilde -70
+KPX Yacute colon -60
+KPX Yacute comma -140
+KPX Yacute e -140
+KPX Yacute eacute -140
+KPX Yacute ecaron -140
+KPX Yacute ecircumflex -140
+KPX Yacute edieresis -140
+KPX Yacute edotaccent -140
+KPX Yacute egrave -140
+KPX Yacute emacron -70
+KPX Yacute eogonek -140
+KPX Yacute hyphen -140
+KPX Yacute i -20
+KPX Yacute iacute -20
+KPX Yacute iogonek -20
+KPX Yacute o -140
+KPX Yacute oacute -140
+KPX Yacute ocircumflex -140
+KPX Yacute odieresis -140
+KPX Yacute ograve -140
+KPX Yacute ohungarumlaut -140
+KPX Yacute omacron -70
+KPX Yacute oslash -140
+KPX Yacute otilde -140
+KPX Yacute period -140
+KPX Yacute semicolon -60
+KPX Yacute u -110
+KPX Yacute uacute -110
+KPX Yacute ucircumflex -110
+KPX Yacute udieresis -110
+KPX Yacute ugrave -110
+KPX Yacute uhungarumlaut -110
+KPX Yacute umacron -110
+KPX Yacute uogonek -110
+KPX Yacute uring -110
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -85
+KPX Ydieresis Oacute -85
+KPX Ydieresis Ocircumflex -85
+KPX Ydieresis Odieresis -85
+KPX Ydieresis Ograve -85
+KPX Ydieresis Ohungarumlaut -85
+KPX Ydieresis Omacron -85
+KPX Ydieresis Oslash -85
+KPX Ydieresis Otilde -85
+KPX Ydieresis a -140
+KPX Ydieresis aacute -140
+KPX Ydieresis abreve -70
+KPX Ydieresis acircumflex -140
+KPX Ydieresis adieresis -140
+KPX Ydieresis agrave -140
+KPX Ydieresis amacron -70
+KPX Ydieresis aogonek -140
+KPX Ydieresis aring -140
+KPX Ydieresis atilde -70
+KPX Ydieresis colon -60
+KPX Ydieresis comma -140
+KPX Ydieresis e -140
+KPX Ydieresis eacute -140
+KPX Ydieresis ecaron -140
+KPX Ydieresis ecircumflex -140
+KPX Ydieresis edieresis -140
+KPX Ydieresis edotaccent -140
+KPX Ydieresis egrave -140
+KPX Ydieresis emacron -70
+KPX Ydieresis eogonek -140
+KPX Ydieresis hyphen -140
+KPX Ydieresis i -20
+KPX Ydieresis iacute -20
+KPX Ydieresis iogonek -20
+KPX Ydieresis o -140
+KPX Ydieresis oacute -140
+KPX Ydieresis ocircumflex -140
+KPX Ydieresis odieresis -140
+KPX Ydieresis ograve -140
+KPX Ydieresis ohungarumlaut -140
+KPX Ydieresis omacron -140
+KPX Ydieresis oslash -140
+KPX Ydieresis otilde -140
+KPX Ydieresis period -140
+KPX Ydieresis semicolon -60
+KPX Ydieresis u -110
+KPX Ydieresis uacute -110
+KPX Ydieresis ucircumflex -110
+KPX Ydieresis udieresis -110
+KPX Ydieresis ugrave -110
+KPX Ydieresis uhungarumlaut -110
+KPX Ydieresis umacron -110
+KPX Ydieresis uogonek -110
+KPX Ydieresis uring -110
+KPX a v -20
+KPX a w -20
+KPX a y -30
+KPX a yacute -30
+KPX a ydieresis -30
+KPX aacute v -20
+KPX aacute w -20
+KPX aacute y -30
+KPX aacute yacute -30
+KPX aacute ydieresis -30
+KPX abreve v -20
+KPX abreve w -20
+KPX abreve y -30
+KPX abreve yacute -30
+KPX abreve ydieresis -30
+KPX acircumflex v -20
+KPX acircumflex w -20
+KPX acircumflex y -30
+KPX acircumflex yacute -30
+KPX acircumflex ydieresis -30
+KPX adieresis v -20
+KPX adieresis w -20
+KPX adieresis y -30
+KPX adieresis yacute -30
+KPX adieresis ydieresis -30
+KPX agrave v -20
+KPX agrave w -20
+KPX agrave y -30
+KPX agrave yacute -30
+KPX agrave ydieresis -30
+KPX amacron v -20
+KPX amacron w -20
+KPX amacron y -30
+KPX amacron yacute -30
+KPX amacron ydieresis -30
+KPX aogonek v -20
+KPX aogonek w -20
+KPX aogonek y -30
+KPX aogonek yacute -30
+KPX aogonek ydieresis -30
+KPX aring v -20
+KPX aring w -20
+KPX aring y -30
+KPX aring yacute -30
+KPX aring ydieresis -30
+KPX atilde v -20
+KPX atilde w -20
+KPX atilde y -30
+KPX atilde yacute -30
+KPX atilde ydieresis -30
+KPX b b -10
+KPX b comma -40
+KPX b l -20
+KPX b lacute -20
+KPX b lcommaaccent -20
+KPX b lslash -20
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c comma -15
+KPX c k -20
+KPX c kcommaaccent -20
+KPX cacute comma -15
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX ccaron comma -15
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccedilla comma -15
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX colon space -50
+KPX comma quotedblright -100
+KPX comma quoteright -100
+KPX e comma -15
+KPX e period -15
+KPX e v -30
+KPX e w -20
+KPX e x -30
+KPX e y -20
+KPX e yacute -20
+KPX e ydieresis -20
+KPX eacute comma -15
+KPX eacute period -15
+KPX eacute v -30
+KPX eacute w -20
+KPX eacute x -30
+KPX eacute y -20
+KPX eacute yacute -20
+KPX eacute ydieresis -20
+KPX ecaron comma -15
+KPX ecaron period -15
+KPX ecaron v -30
+KPX ecaron w -20
+KPX ecaron x -30
+KPX ecaron y -20
+KPX ecaron yacute -20
+KPX ecaron ydieresis -20
+KPX ecircumflex comma -15
+KPX ecircumflex period -15
+KPX ecircumflex v -30
+KPX ecircumflex w -20
+KPX ecircumflex x -30
+KPX ecircumflex y -20
+KPX ecircumflex yacute -20
+KPX ecircumflex ydieresis -20
+KPX edieresis comma -15
+KPX edieresis period -15
+KPX edieresis v -30
+KPX edieresis w -20
+KPX edieresis x -30
+KPX edieresis y -20
+KPX edieresis yacute -20
+KPX edieresis ydieresis -20
+KPX edotaccent comma -15
+KPX edotaccent period -15
+KPX edotaccent v -30
+KPX edotaccent w -20
+KPX edotaccent x -30
+KPX edotaccent y -20
+KPX edotaccent yacute -20
+KPX edotaccent ydieresis -20
+KPX egrave comma -15
+KPX egrave period -15
+KPX egrave v -30
+KPX egrave w -20
+KPX egrave x -30
+KPX egrave y -20
+KPX egrave yacute -20
+KPX egrave ydieresis -20
+KPX emacron comma -15
+KPX emacron period -15
+KPX emacron v -30
+KPX emacron w -20
+KPX emacron x -30
+KPX emacron y -20
+KPX emacron yacute -20
+KPX emacron ydieresis -20
+KPX eogonek comma -15
+KPX eogonek period -15
+KPX eogonek v -30
+KPX eogonek w -20
+KPX eogonek x -30
+KPX eogonek y -20
+KPX eogonek yacute -20
+KPX eogonek ydieresis -20
+KPX f a -30
+KPX f aacute -30
+KPX f abreve -30
+KPX f acircumflex -30
+KPX f adieresis -30
+KPX f agrave -30
+KPX f amacron -30
+KPX f aogonek -30
+KPX f aring -30
+KPX f atilde -30
+KPX f comma -30
+KPX f dotlessi -28
+KPX f e -30
+KPX f eacute -30
+KPX f ecaron -30
+KPX f ecircumflex -30
+KPX f edieresis -30
+KPX f edotaccent -30
+KPX f egrave -30
+KPX f emacron -30
+KPX f eogonek -30
+KPX f o -30
+KPX f oacute -30
+KPX f ocircumflex -30
+KPX f odieresis -30
+KPX f ograve -30
+KPX f ohungarumlaut -30
+KPX f omacron -30
+KPX f oslash -30
+KPX f otilde -30
+KPX f period -30
+KPX f quotedblright 60
+KPX f quoteright 50
+KPX g r -10
+KPX g racute -10
+KPX g rcaron -10
+KPX g rcommaaccent -10
+KPX gbreve r -10
+KPX gbreve racute -10
+KPX gbreve rcaron -10
+KPX gbreve rcommaaccent -10
+KPX gcommaaccent r -10
+KPX gcommaaccent racute -10
+KPX gcommaaccent rcaron -10
+KPX gcommaaccent rcommaaccent -10
+KPX h y -30
+KPX h yacute -30
+KPX h ydieresis -30
+KPX k e -20
+KPX k eacute -20
+KPX k ecaron -20
+KPX k ecircumflex -20
+KPX k edieresis -20
+KPX k edotaccent -20
+KPX k egrave -20
+KPX k emacron -20
+KPX k eogonek -20
+KPX k o -20
+KPX k oacute -20
+KPX k ocircumflex -20
+KPX k odieresis -20
+KPX k ograve -20
+KPX k ohungarumlaut -20
+KPX k omacron -20
+KPX k oslash -20
+KPX k otilde -20
+KPX kcommaaccent e -20
+KPX kcommaaccent eacute -20
+KPX kcommaaccent ecaron -20
+KPX kcommaaccent ecircumflex -20
+KPX kcommaaccent edieresis -20
+KPX kcommaaccent edotaccent -20
+KPX kcommaaccent egrave -20
+KPX kcommaaccent emacron -20
+KPX kcommaaccent eogonek -20
+KPX kcommaaccent o -20
+KPX kcommaaccent oacute -20
+KPX kcommaaccent ocircumflex -20
+KPX kcommaaccent odieresis -20
+KPX kcommaaccent ograve -20
+KPX kcommaaccent ohungarumlaut -20
+KPX kcommaaccent omacron -20
+KPX kcommaaccent oslash -20
+KPX kcommaaccent otilde -20
+KPX m u -10
+KPX m uacute -10
+KPX m ucircumflex -10
+KPX m udieresis -10
+KPX m ugrave -10
+KPX m uhungarumlaut -10
+KPX m umacron -10
+KPX m uogonek -10
+KPX m uring -10
+KPX m y -15
+KPX m yacute -15
+KPX m ydieresis -15
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -20
+KPX n y -15
+KPX n yacute -15
+KPX n ydieresis -15
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -20
+KPX nacute y -15
+KPX nacute yacute -15
+KPX nacute ydieresis -15
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -20
+KPX ncaron y -15
+KPX ncaron yacute -15
+KPX ncaron ydieresis -15
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -20
+KPX ncommaaccent y -15
+KPX ncommaaccent yacute -15
+KPX ncommaaccent ydieresis -15
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -20
+KPX ntilde y -15
+KPX ntilde yacute -15
+KPX ntilde ydieresis -15
+KPX o comma -40
+KPX o period -40
+KPX o v -15
+KPX o w -15
+KPX o x -30
+KPX o y -30
+KPX o yacute -30
+KPX o ydieresis -30
+KPX oacute comma -40
+KPX oacute period -40
+KPX oacute v -15
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -30
+KPX oacute yacute -30
+KPX oacute ydieresis -30
+KPX ocircumflex comma -40
+KPX ocircumflex period -40
+KPX ocircumflex v -15
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -30
+KPX ocircumflex yacute -30
+KPX ocircumflex ydieresis -30
+KPX odieresis comma -40
+KPX odieresis period -40
+KPX odieresis v -15
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -30
+KPX odieresis yacute -30
+KPX odieresis ydieresis -30
+KPX ograve comma -40
+KPX ograve period -40
+KPX ograve v -15
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -30
+KPX ograve yacute -30
+KPX ograve ydieresis -30
+KPX ohungarumlaut comma -40
+KPX ohungarumlaut period -40
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -30
+KPX ohungarumlaut yacute -30
+KPX ohungarumlaut ydieresis -30
+KPX omacron comma -40
+KPX omacron period -40
+KPX omacron v -15
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -30
+KPX omacron yacute -30
+KPX omacron ydieresis -30
+KPX oslash a -55
+KPX oslash aacute -55
+KPX oslash abreve -55
+KPX oslash acircumflex -55
+KPX oslash adieresis -55
+KPX oslash agrave -55
+KPX oslash amacron -55
+KPX oslash aogonek -55
+KPX oslash aring -55
+KPX oslash atilde -55
+KPX oslash b -55
+KPX oslash c -55
+KPX oslash cacute -55
+KPX oslash ccaron -55
+KPX oslash ccedilla -55
+KPX oslash comma -95
+KPX oslash d -55
+KPX oslash dcroat -55
+KPX oslash e -55
+KPX oslash eacute -55
+KPX oslash ecaron -55
+KPX oslash ecircumflex -55
+KPX oslash edieresis -55
+KPX oslash edotaccent -55
+KPX oslash egrave -55
+KPX oslash emacron -55
+KPX oslash eogonek -55
+KPX oslash f -55
+KPX oslash g -55
+KPX oslash gbreve -55
+KPX oslash gcommaaccent -55
+KPX oslash h -55
+KPX oslash i -55
+KPX oslash iacute -55
+KPX oslash icircumflex -55
+KPX oslash idieresis -55
+KPX oslash igrave -55
+KPX oslash imacron -55
+KPX oslash iogonek -55
+KPX oslash j -55
+KPX oslash k -55
+KPX oslash kcommaaccent -55
+KPX oslash l -55
+KPX oslash lacute -55
+KPX oslash lcommaaccent -55
+KPX oslash lslash -55
+KPX oslash m -55
+KPX oslash n -55
+KPX oslash nacute -55
+KPX oslash ncaron -55
+KPX oslash ncommaaccent -55
+KPX oslash ntilde -55
+KPX oslash o -55
+KPX oslash oacute -55
+KPX oslash ocircumflex -55
+KPX oslash odieresis -55
+KPX oslash ograve -55
+KPX oslash ohungarumlaut -55
+KPX oslash omacron -55
+KPX oslash oslash -55
+KPX oslash otilde -55
+KPX oslash p -55
+KPX oslash period -95
+KPX oslash q -55
+KPX oslash r -55
+KPX oslash racute -55
+KPX oslash rcaron -55
+KPX oslash rcommaaccent -55
+KPX oslash s -55
+KPX oslash sacute -55
+KPX oslash scaron -55
+KPX oslash scedilla -55
+KPX oslash scommaaccent -55
+KPX oslash t -55
+KPX oslash tcommaaccent -55
+KPX oslash u -55
+KPX oslash uacute -55
+KPX oslash ucircumflex -55
+KPX oslash udieresis -55
+KPX oslash ugrave -55
+KPX oslash uhungarumlaut -55
+KPX oslash umacron -55
+KPX oslash uogonek -55
+KPX oslash uring -55
+KPX oslash v -70
+KPX oslash w -70
+KPX oslash x -85
+KPX oslash y -70
+KPX oslash yacute -70
+KPX oslash ydieresis -70
+KPX oslash z -55
+KPX oslash zacute -55
+KPX oslash zcaron -55
+KPX oslash zdotaccent -55
+KPX otilde comma -40
+KPX otilde period -40
+KPX otilde v -15
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -30
+KPX otilde yacute -30
+KPX otilde ydieresis -30
+KPX p comma -35
+KPX p period -35
+KPX p y -30
+KPX p yacute -30
+KPX p ydieresis -30
+KPX period quotedblright -100
+KPX period quoteright -100
+KPX period space -60
+KPX quotedblright space -40
+KPX quoteleft quoteleft -57
+KPX quoteright d -50
+KPX quoteright dcroat -50
+KPX quoteright quoteright -57
+KPX quoteright r -50
+KPX quoteright racute -50
+KPX quoteright rcaron -50
+KPX quoteright rcommaaccent -50
+KPX quoteright s -50
+KPX quoteright sacute -50
+KPX quoteright scaron -50
+KPX quoteright scedilla -50
+KPX quoteright scommaaccent -50
+KPX quoteright space -70
+KPX r a -10
+KPX r aacute -10
+KPX r abreve -10
+KPX r acircumflex -10
+KPX r adieresis -10
+KPX r agrave -10
+KPX r amacron -10
+KPX r aogonek -10
+KPX r aring -10
+KPX r atilde -10
+KPX r colon 30
+KPX r comma -50
+KPX r i 15
+KPX r iacute 15
+KPX r icircumflex 15
+KPX r idieresis 15
+KPX r igrave 15
+KPX r imacron 15
+KPX r iogonek 15
+KPX r k 15
+KPX r kcommaaccent 15
+KPX r l 15
+KPX r lacute 15
+KPX r lcommaaccent 15
+KPX r lslash 15
+KPX r m 25
+KPX r n 25
+KPX r nacute 25
+KPX r ncaron 25
+KPX r ncommaaccent 25
+KPX r ntilde 25
+KPX r p 30
+KPX r period -50
+KPX r semicolon 30
+KPX r t 40
+KPX r tcommaaccent 40
+KPX r u 15
+KPX r uacute 15
+KPX r ucircumflex 15
+KPX r udieresis 15
+KPX r ugrave 15
+KPX r uhungarumlaut 15
+KPX r umacron 15
+KPX r uogonek 15
+KPX r uring 15
+KPX r v 30
+KPX r y 30
+KPX r yacute 30
+KPX r ydieresis 30
+KPX racute a -10
+KPX racute aacute -10
+KPX racute abreve -10
+KPX racute acircumflex -10
+KPX racute adieresis -10
+KPX racute agrave -10
+KPX racute amacron -10
+KPX racute aogonek -10
+KPX racute aring -10
+KPX racute atilde -10
+KPX racute colon 30
+KPX racute comma -50
+KPX racute i 15
+KPX racute iacute 15
+KPX racute icircumflex 15
+KPX racute idieresis 15
+KPX racute igrave 15
+KPX racute imacron 15
+KPX racute iogonek 15
+KPX racute k 15
+KPX racute kcommaaccent 15
+KPX racute l 15
+KPX racute lacute 15
+KPX racute lcommaaccent 15
+KPX racute lslash 15
+KPX racute m 25
+KPX racute n 25
+KPX racute nacute 25
+KPX racute ncaron 25
+KPX racute ncommaaccent 25
+KPX racute ntilde 25
+KPX racute p 30
+KPX racute period -50
+KPX racute semicolon 30
+KPX racute t 40
+KPX racute tcommaaccent 40
+KPX racute u 15
+KPX racute uacute 15
+KPX racute ucircumflex 15
+KPX racute udieresis 15
+KPX racute ugrave 15
+KPX racute uhungarumlaut 15
+KPX racute umacron 15
+KPX racute uogonek 15
+KPX racute uring 15
+KPX racute v 30
+KPX racute y 30
+KPX racute yacute 30
+KPX racute ydieresis 30
+KPX rcaron a -10
+KPX rcaron aacute -10
+KPX rcaron abreve -10
+KPX rcaron acircumflex -10
+KPX rcaron adieresis -10
+KPX rcaron agrave -10
+KPX rcaron amacron -10
+KPX rcaron aogonek -10
+KPX rcaron aring -10
+KPX rcaron atilde -10
+KPX rcaron colon 30
+KPX rcaron comma -50
+KPX rcaron i 15
+KPX rcaron iacute 15
+KPX rcaron icircumflex 15
+KPX rcaron idieresis 15
+KPX rcaron igrave 15
+KPX rcaron imacron 15
+KPX rcaron iogonek 15
+KPX rcaron k 15
+KPX rcaron kcommaaccent 15
+KPX rcaron l 15
+KPX rcaron lacute 15
+KPX rcaron lcommaaccent 15
+KPX rcaron lslash 15
+KPX rcaron m 25
+KPX rcaron n 25
+KPX rcaron nacute 25
+KPX rcaron ncaron 25
+KPX rcaron ncommaaccent 25
+KPX rcaron ntilde 25
+KPX rcaron p 30
+KPX rcaron period -50
+KPX rcaron semicolon 30
+KPX rcaron t 40
+KPX rcaron tcommaaccent 40
+KPX rcaron u 15
+KPX rcaron uacute 15
+KPX rcaron ucircumflex 15
+KPX rcaron udieresis 15
+KPX rcaron ugrave 15
+KPX rcaron uhungarumlaut 15
+KPX rcaron umacron 15
+KPX rcaron uogonek 15
+KPX rcaron uring 15
+KPX rcaron v 30
+KPX rcaron y 30
+KPX rcaron yacute 30
+KPX rcaron ydieresis 30
+KPX rcommaaccent a -10
+KPX rcommaaccent aacute -10
+KPX rcommaaccent abreve -10
+KPX rcommaaccent acircumflex -10
+KPX rcommaaccent adieresis -10
+KPX rcommaaccent agrave -10
+KPX rcommaaccent amacron -10
+KPX rcommaaccent aogonek -10
+KPX rcommaaccent aring -10
+KPX rcommaaccent atilde -10
+KPX rcommaaccent colon 30
+KPX rcommaaccent comma -50
+KPX rcommaaccent i 15
+KPX rcommaaccent iacute 15
+KPX rcommaaccent icircumflex 15
+KPX rcommaaccent idieresis 15
+KPX rcommaaccent igrave 15
+KPX rcommaaccent imacron 15
+KPX rcommaaccent iogonek 15
+KPX rcommaaccent k 15
+KPX rcommaaccent kcommaaccent 15
+KPX rcommaaccent l 15
+KPX rcommaaccent lacute 15
+KPX rcommaaccent lcommaaccent 15
+KPX rcommaaccent lslash 15
+KPX rcommaaccent m 25
+KPX rcommaaccent n 25
+KPX rcommaaccent nacute 25
+KPX rcommaaccent ncaron 25
+KPX rcommaaccent ncommaaccent 25
+KPX rcommaaccent ntilde 25
+KPX rcommaaccent p 30
+KPX rcommaaccent period -50
+KPX rcommaaccent semicolon 30
+KPX rcommaaccent t 40
+KPX rcommaaccent tcommaaccent 40
+KPX rcommaaccent u 15
+KPX rcommaaccent uacute 15
+KPX rcommaaccent ucircumflex 15
+KPX rcommaaccent udieresis 15
+KPX rcommaaccent ugrave 15
+KPX rcommaaccent uhungarumlaut 15
+KPX rcommaaccent umacron 15
+KPX rcommaaccent uogonek 15
+KPX rcommaaccent uring 15
+KPX rcommaaccent v 30
+KPX rcommaaccent y 30
+KPX rcommaaccent yacute 30
+KPX rcommaaccent ydieresis 30
+KPX s comma -15
+KPX s period -15
+KPX s w -30
+KPX sacute comma -15
+KPX sacute period -15
+KPX sacute w -30
+KPX scaron comma -15
+KPX scaron period -15
+KPX scaron w -30
+KPX scedilla comma -15
+KPX scedilla period -15
+KPX scedilla w -30
+KPX scommaaccent comma -15
+KPX scommaaccent period -15
+KPX scommaaccent w -30
+KPX semicolon space -50
+KPX space T -50
+KPX space Tcaron -50
+KPX space Tcommaaccent -50
+KPX space V -50
+KPX space W -40
+KPX space Y -90
+KPX space Yacute -90
+KPX space Ydieresis -90
+KPX space quotedblleft -30
+KPX space quoteleft -60
+KPX v a -25
+KPX v aacute -25
+KPX v abreve -25
+KPX v acircumflex -25
+KPX v adieresis -25
+KPX v agrave -25
+KPX v amacron -25
+KPX v aogonek -25
+KPX v aring -25
+KPX v atilde -25
+KPX v comma -80
+KPX v e -25
+KPX v eacute -25
+KPX v ecaron -25
+KPX v ecircumflex -25
+KPX v edieresis -25
+KPX v edotaccent -25
+KPX v egrave -25
+KPX v emacron -25
+KPX v eogonek -25
+KPX v o -25
+KPX v oacute -25
+KPX v ocircumflex -25
+KPX v odieresis -25
+KPX v ograve -25
+KPX v ohungarumlaut -25
+KPX v omacron -25
+KPX v oslash -25
+KPX v otilde -25
+KPX v period -80
+KPX w a -15
+KPX w aacute -15
+KPX w abreve -15
+KPX w acircumflex -15
+KPX w adieresis -15
+KPX w agrave -15
+KPX w amacron -15
+KPX w aogonek -15
+KPX w aring -15
+KPX w atilde -15
+KPX w comma -60
+KPX w e -10
+KPX w eacute -10
+KPX w ecaron -10
+KPX w ecircumflex -10
+KPX w edieresis -10
+KPX w edotaccent -10
+KPX w egrave -10
+KPX w emacron -10
+KPX w eogonek -10
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -60
+KPX x e -30
+KPX x eacute -30
+KPX x ecaron -30
+KPX x ecircumflex -30
+KPX x edieresis -30
+KPX x edotaccent -30
+KPX x egrave -30
+KPX x emacron -30
+KPX x eogonek -30
+KPX y a -20
+KPX y aacute -20
+KPX y abreve -20
+KPX y acircumflex -20
+KPX y adieresis -20
+KPX y agrave -20
+KPX y amacron -20
+KPX y aogonek -20
+KPX y aring -20
+KPX y atilde -20
+KPX y comma -100
+KPX y e -20
+KPX y eacute -20
+KPX y ecaron -20
+KPX y ecircumflex -20
+KPX y edieresis -20
+KPX y edotaccent -20
+KPX y egrave -20
+KPX y emacron -20
+KPX y eogonek -20
+KPX y o -20
+KPX y oacute -20
+KPX y ocircumflex -20
+KPX y odieresis -20
+KPX y ograve -20
+KPX y ohungarumlaut -20
+KPX y omacron -20
+KPX y oslash -20
+KPX y otilde -20
+KPX y period -100
+KPX yacute a -20
+KPX yacute aacute -20
+KPX yacute abreve -20
+KPX yacute acircumflex -20
+KPX yacute adieresis -20
+KPX yacute agrave -20
+KPX yacute amacron -20
+KPX yacute aogonek -20
+KPX yacute aring -20
+KPX yacute atilde -20
+KPX yacute comma -100
+KPX yacute e -20
+KPX yacute eacute -20
+KPX yacute ecaron -20
+KPX yacute ecircumflex -20
+KPX yacute edieresis -20
+KPX yacute edotaccent -20
+KPX yacute egrave -20
+KPX yacute emacron -20
+KPX yacute eogonek -20
+KPX yacute o -20
+KPX yacute oacute -20
+KPX yacute ocircumflex -20
+KPX yacute odieresis -20
+KPX yacute ograve -20
+KPX yacute ohungarumlaut -20
+KPX yacute omacron -20
+KPX yacute oslash -20
+KPX yacute otilde -20
+KPX yacute period -100
+KPX ydieresis a -20
+KPX ydieresis aacute -20
+KPX ydieresis abreve -20
+KPX ydieresis acircumflex -20
+KPX ydieresis adieresis -20
+KPX ydieresis agrave -20
+KPX ydieresis amacron -20
+KPX ydieresis aogonek -20
+KPX ydieresis aring -20
+KPX ydieresis atilde -20
+KPX ydieresis comma -100
+KPX ydieresis e -20
+KPX ydieresis eacute -20
+KPX ydieresis ecaron -20
+KPX ydieresis ecircumflex -20
+KPX ydieresis edieresis -20
+KPX ydieresis edotaccent -20
+KPX ydieresis egrave -20
+KPX ydieresis emacron -20
+KPX ydieresis eogonek -20
+KPX ydieresis o -20
+KPX ydieresis oacute -20
+KPX ydieresis ocircumflex -20
+KPX ydieresis odieresis -20
+KPX ydieresis ograve -20
+KPX ydieresis ohungarumlaut -20
+KPX ydieresis omacron -20
+KPX ydieresis oslash -20
+KPX ydieresis otilde -20
+KPX ydieresis period -100
+KPX z e -15
+KPX z eacute -15
+KPX z ecaron -15
+KPX z ecircumflex -15
+KPX z edieresis -15
+KPX z edotaccent -15
+KPX z egrave -15
+KPX z emacron -15
+KPX z eogonek -15
+KPX z o -15
+KPX z oacute -15
+KPX z ocircumflex -15
+KPX z odieresis -15
+KPX z ograve -15
+KPX z ohungarumlaut -15
+KPX z omacron -15
+KPX z oslash -15
+KPX z otilde -15
+KPX zacute e -15
+KPX zacute eacute -15
+KPX zacute ecaron -15
+KPX zacute ecircumflex -15
+KPX zacute edieresis -15
+KPX zacute edotaccent -15
+KPX zacute egrave -15
+KPX zacute emacron -15
+KPX zacute eogonek -15
+KPX zacute o -15
+KPX zacute oacute -15
+KPX zacute ocircumflex -15
+KPX zacute odieresis -15
+KPX zacute ograve -15
+KPX zacute ohungarumlaut -15
+KPX zacute omacron -15
+KPX zacute oslash -15
+KPX zacute otilde -15
+KPX zcaron e -15
+KPX zcaron eacute -15
+KPX zcaron ecaron -15
+KPX zcaron ecircumflex -15
+KPX zcaron edieresis -15
+KPX zcaron edotaccent -15
+KPX zcaron egrave -15
+KPX zcaron emacron -15
+KPX zcaron eogonek -15
+KPX zcaron o -15
+KPX zcaron oacute -15
+KPX zcaron ocircumflex -15
+KPX zcaron odieresis -15
+KPX zcaron ograve -15
+KPX zcaron ohungarumlaut -15
+KPX zcaron omacron -15
+KPX zcaron oslash -15
+KPX zcaron otilde -15
+KPX zdotaccent e -15
+KPX zdotaccent eacute -15
+KPX zdotaccent ecaron -15
+KPX zdotaccent ecircumflex -15
+KPX zdotaccent edieresis -15
+KPX zdotaccent edotaccent -15
+KPX zdotaccent egrave -15
+KPX zdotaccent emacron -15
+KPX zdotaccent eogonek -15
+KPX zdotaccent o -15
+KPX zdotaccent oacute -15
+KPX zdotaccent ocircumflex -15
+KPX zdotaccent odieresis -15
+KPX zdotaccent ograve -15
+KPX zdotaccent ohungarumlaut -15
+KPX zdotaccent omacron -15
+KPX zdotaccent oslash -15
+KPX zdotaccent otilde -15
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica.afm
new file mode 100644
index 0000000..35a15b1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Helvetica.afm
@@ -0,0 +1,3053 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:38:23 1997
+Comment UniqueID 43054
+Comment VMusage 37069 48094
+FontName Helvetica
+FullName Helvetica
+FamilyName Helvetica
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -166 -225 1000 931
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All Rights Reserved.Helvetica is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 718
+XHeight 523
+Ascender 718
+Descender -207
+StdHW 76
+StdVW 88
+StartCharMetrics 317
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 278 ; N exclam ; B 90 0 187 718 ;
+C 34 ; WX 355 ; N quotedbl ; B 70 463 285 718 ;
+C 35 ; WX 556 ; N numbersign ; B 28 0 529 688 ;
+C 36 ; WX 556 ; N dollar ; B 32 -115 520 775 ;
+C 37 ; WX 889 ; N percent ; B 39 -19 850 703 ;
+C 38 ; WX 667 ; N ampersand ; B 44 -15 645 718 ;
+C 146 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
+C 40 ; WX 333 ; N parenleft ; B 68 -207 299 733 ;
+C 41 ; WX 333 ; N parenright ; B 34 -207 265 733 ;
+C 42 ; WX 389 ; N asterisk ; B 39 431 349 718 ;
+C 43 ; WX 584 ; N plus ; B 39 0 545 505 ;
+C 44 ; WX 278 ; N comma ; B 87 -147 191 106 ;
+C 45 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 173 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
+C 46 ; WX 278 ; N period ; B 87 0 191 106 ;
+C 47 ; WX 278 ; N slash ; B -17 -19 295 737 ;
+C 48 ; WX 556 ; N zero ; B 37 -19 519 703 ;
+C 49 ; WX 556 ; N one ; B 101 0 359 703 ;
+C 50 ; WX 556 ; N two ; B 26 0 507 703 ;
+C 51 ; WX 556 ; N three ; B 34 -19 522 703 ;
+C 52 ; WX 556 ; N four ; B 25 0 523 703 ;
+C 53 ; WX 556 ; N five ; B 32 -19 514 688 ;
+C 54 ; WX 556 ; N six ; B 38 -19 518 703 ;
+C 55 ; WX 556 ; N seven ; B 37 0 523 688 ;
+C 56 ; WX 556 ; N eight ; B 38 -19 517 703 ;
+C 57 ; WX 556 ; N nine ; B 42 -19 514 703 ;
+C 58 ; WX 278 ; N colon ; B 87 0 191 516 ;
+C 59 ; WX 278 ; N semicolon ; B 87 -147 191 516 ;
+C 60 ; WX 584 ; N less ; B 48 11 536 495 ;
+C 61 ; WX 584 ; N equal ; B 39 115 545 390 ;
+C 62 ; WX 584 ; N greater ; B 48 11 536 495 ;
+C 63 ; WX 556 ; N question ; B 56 0 492 727 ;
+C 64 ; WX 1015 ; N at ; B 147 -19 868 737 ;
+C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
+C 66 ; WX 667 ; N B ; B 74 0 627 718 ;
+C 67 ; WX 722 ; N C ; B 44 -19 681 737 ;
+C 68 ; WX 722 ; N D ; B 81 0 674 718 ;
+C 69 ; WX 667 ; N E ; B 86 0 616 718 ;
+C 70 ; WX 611 ; N F ; B 86 0 583 718 ;
+C 71 ; WX 778 ; N G ; B 48 -19 704 737 ;
+C 72 ; WX 722 ; N H ; B 77 0 646 718 ;
+C 73 ; WX 278 ; N I ; B 91 0 188 718 ;
+C 74 ; WX 500 ; N J ; B 17 -19 428 718 ;
+C 75 ; WX 667 ; N K ; B 76 0 663 718 ;
+C 76 ; WX 556 ; N L ; B 76 0 537 718 ;
+C 77 ; WX 833 ; N M ; B 73 0 761 718 ;
+C 78 ; WX 722 ; N N ; B 76 0 646 718 ;
+C 79 ; WX 778 ; N O ; B 39 -19 739 737 ;
+C 80 ; WX 667 ; N P ; B 86 0 622 718 ;
+C 81 ; WX 778 ; N Q ; B 39 -56 739 737 ;
+C 82 ; WX 722 ; N R ; B 88 0 684 718 ;
+C 83 ; WX 667 ; N S ; B 49 -19 620 737 ;
+C 84 ; WX 611 ; N T ; B 14 0 597 718 ;
+C 85 ; WX 722 ; N U ; B 79 -19 644 718 ;
+C 86 ; WX 667 ; N V ; B 20 0 647 718 ;
+C 87 ; WX 944 ; N W ; B 16 0 928 718 ;
+C 88 ; WX 667 ; N X ; B 19 0 648 718 ;
+C 89 ; WX 667 ; N Y ; B 14 0 653 718 ;
+C 90 ; WX 611 ; N Z ; B 23 0 588 718 ;
+C 91 ; WX 278 ; N bracketleft ; B 63 -196 250 722 ;
+C 92 ; WX 278 ; N backslash ; B -17 -19 295 737 ;
+C 93 ; WX 278 ; N bracketright ; B 28 -196 215 722 ;
+C 94 ; WX 469 ; N asciicircum ; B -14 264 483 688 ;
+C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
+C 145 ; WX 222 ; N quoteleft ; B 65 470 169 725 ;
+C 97 ; WX 556 ; N a ; B 36 -15 530 538 ;
+C 98 ; WX 556 ; N b ; B 58 -15 517 718 ;
+C 99 ; WX 500 ; N c ; B 30 -15 477 538 ;
+C 100 ; WX 556 ; N d ; B 35 -15 499 718 ;
+C 101 ; WX 556 ; N e ; B 40 -15 516 538 ;
+C 102 ; WX 278 ; N f ; B 14 0 262 728 ; L i fi ; L l fl ;
+C 103 ; WX 556 ; N g ; B 40 -220 499 538 ;
+C 104 ; WX 556 ; N h ; B 65 0 491 718 ;
+C 105 ; WX 222 ; N i ; B 67 0 155 718 ;
+C 106 ; WX 222 ; N j ; B -16 -210 155 718 ;
+C 107 ; WX 500 ; N k ; B 67 0 501 718 ;
+C 108 ; WX 222 ; N l ; B 67 0 155 718 ;
+C 109 ; WX 833 ; N m ; B 65 0 769 538 ;
+C 110 ; WX 556 ; N n ; B 65 0 491 538 ;
+C 111 ; WX 556 ; N o ; B 35 -14 521 538 ;
+C 112 ; WX 556 ; N p ; B 58 -207 517 538 ;
+C 113 ; WX 556 ; N q ; B 35 -207 494 538 ;
+C 114 ; WX 333 ; N r ; B 77 0 332 538 ;
+C 115 ; WX 500 ; N s ; B 32 -15 464 538 ;
+C 116 ; WX 278 ; N t ; B 14 -7 257 669 ;
+C 117 ; WX 556 ; N u ; B 68 -15 489 523 ;
+C 118 ; WX 500 ; N v ; B 8 0 492 523 ;
+C 119 ; WX 722 ; N w ; B 14 0 709 523 ;
+C 120 ; WX 500 ; N x ; B 11 0 490 523 ;
+C 121 ; WX 500 ; N y ; B 11 -214 489 523 ;
+C 122 ; WX 500 ; N z ; B 31 0 469 523 ;
+C 123 ; WX 334 ; N braceleft ; B 42 -196 292 722 ;
+C 124 ; WX 260 ; N bar ; B 94 -225 167 775 ;
+C 125 ; WX 334 ; N braceright ; B 42 -196 292 722 ;
+C 126 ; WX 584 ; N asciitilde ; B 61 180 523 326 ;
+C 161 ; WX 333 ; N exclamdown ; B 118 -195 215 523 ;
+C 162 ; WX 556 ; N cent ; B 51 -115 513 623 ;
+C 163 ; WX 556 ; N sterling ; B 33 -16 539 718 ;
+C -1 ; WX 167 ; N fraction ; B -166 -19 333 703 ;
+C 165 ; WX 556 ; N yen ; B 3 0 553 688 ;
+C 131 ; WX 556 ; N florin ; B -11 -207 501 737 ;
+C 167 ; WX 556 ; N section ; B 43 -191 512 737 ;
+C 164 ; WX 556 ; N currency ; B 28 99 528 603 ;
+C 39 ; WX 191 ; N quotesingle ; B 59 463 132 718 ;
+C 147 ; WX 333 ; N quotedblleft ; B 38 470 307 725 ;
+C 171 ; WX 556 ; N guillemotleft ; B 97 108 459 446 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 88 108 245 446 ;
+C 155 ; WX 333 ; N guilsinglright ; B 88 108 245 446 ;
+C -1 ; WX 500 ; N fi ; B 14 0 434 728 ;
+C -1 ; WX 500 ; N fl ; B 14 0 432 728 ;
+C 150 ; WX 556 ; N endash ; B 0 240 556 313 ;
+C 134 ; WX 556 ; N dagger ; B 43 -159 514 718 ;
+C 135 ; WX 556 ; N daggerdbl ; B 43 -159 514 718 ;
+C 183 ; WX 278 ; N periodcentered ; B 77 190 202 315 ;
+C 182 ; WX 537 ; N paragraph ; B 18 -173 497 718 ;
+C 149 ; WX 350 ; N bullet ; B 18 202 333 517 ;
+C 130 ; WX 222 ; N quotesinglbase ; B 53 -149 157 106 ;
+C 132 ; WX 333 ; N quotedblbase ; B 26 -149 295 106 ;
+C 148 ; WX 333 ; N quotedblright ; B 26 463 295 718 ;
+C 187 ; WX 556 ; N guillemotright ; B 97 108 459 446 ;
+C 133 ; WX 1000 ; N ellipsis ; B 115 0 885 106 ;
+C 137 ; WX 1000 ; N perthousand ; B 7 -19 994 703 ;
+C 191 ; WX 611 ; N questiondown ; B 91 -201 527 525 ;
+C 96 ; WX 333 ; N grave ; B 14 593 211 734 ;
+C 180 ; WX 333 ; N acute ; B 122 593 319 734 ;
+C 136 ; WX 333 ; N circumflex ; B 21 593 312 734 ;
+C 152 ; WX 333 ; N tilde ; B -4 606 337 722 ;
+C 175 ; WX 333 ; N macron ; B 10 627 323 684 ;
+C -1 ; WX 333 ; N breve ; B 13 595 321 731 ;
+C -1 ; WX 333 ; N dotaccent ; B 121 604 212 706 ;
+C 168 ; WX 333 ; N dieresis ; B 40 604 293 706 ;
+C -1 ; WX 333 ; N ring ; B 75 572 259 756 ;
+C 184 ; WX 333 ; N cedilla ; B 45 -225 259 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B 31 593 409 734 ;
+C -1 ; WX 333 ; N ogonek ; B 73 -225 287 0 ;
+C -1 ; WX 333 ; N caron ; B 21 593 312 734 ;
+C 151 ; WX 1000 ; N emdash ; B 0 240 1000 313 ;
+C 198 ; WX 1000 ; N AE ; B 8 0 951 718 ;
+C 170 ; WX 370 ; N ordfeminine ; B 24 405 346 737 ;
+C -1 ; WX 556 ; N Lslash ; B -20 0 537 718 ;
+C 216 ; WX 778 ; N Oslash ; B 39 -19 740 737 ;
+C 140 ; WX 1000 ; N OE ; B 36 -19 965 737 ;
+C 186 ; WX 365 ; N ordmasculine ; B 25 405 341 737 ;
+C 230 ; WX 889 ; N ae ; B 36 -15 847 538 ;
+C -1 ; WX 278 ; N dotlessi ; B 95 0 183 523 ;
+C -1 ; WX 222 ; N lslash ; B -20 0 242 718 ;
+C 248 ; WX 611 ; N oslash ; B 28 -22 537 545 ;
+C 156 ; WX 944 ; N oe ; B 35 -15 902 538 ;
+C 223 ; WX 611 ; N germandbls ; B 67 -15 571 728 ;
+C 207 ; WX 278 ; N Idieresis ; B 13 0 266 901 ;
+C 233 ; WX 556 ; N eacute ; B 40 -15 516 734 ;
+C -1 ; WX 556 ; N abreve ; B 36 -15 530 731 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 68 -15 521 734 ;
+C -1 ; WX 556 ; N ecaron ; B 40 -15 516 734 ;
+C 159 ; WX 667 ; N Ydieresis ; B 14 0 653 901 ;
+C 247 ; WX 584 ; N divide ; B 39 -19 545 524 ;
+C 221 ; WX 667 ; N Yacute ; B 14 0 653 929 ;
+C 194 ; WX 667 ; N Acircumflex ; B 14 0 654 929 ;
+C 225 ; WX 556 ; N aacute ; B 36 -15 530 734 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 79 -19 644 929 ;
+C 253 ; WX 500 ; N yacute ; B 11 -214 489 734 ;
+C -1 ; WX 500 ; N scommaaccent ; B 32 -225 464 538 ;
+C 234 ; WX 556 ; N ecircumflex ; B 40 -15 516 734 ;
+C -1 ; WX 722 ; N Uring ; B 79 -19 644 931 ;
+C 220 ; WX 722 ; N Udieresis ; B 79 -19 644 901 ;
+C -1 ; WX 556 ; N aogonek ; B 36 -220 547 538 ;
+C 218 ; WX 722 ; N Uacute ; B 79 -19 644 929 ;
+C -1 ; WX 556 ; N uogonek ; B 68 -225 519 523 ;
+C 203 ; WX 667 ; N Edieresis ; B 86 0 616 901 ;
+C -1 ; WX 722 ; N Dcroat ; B 0 0 674 718 ;
+C -1 ; WX 250 ; N commaaccent ; B 87 -225 181 -40 ;
+C 169 ; WX 737 ; N copyright ; B -14 -19 752 737 ;
+C -1 ; WX 667 ; N Emacron ; B 86 0 616 879 ;
+C -1 ; WX 500 ; N ccaron ; B 30 -15 477 734 ;
+C 229 ; WX 556 ; N aring ; B 36 -15 530 756 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 76 -225 646 718 ;
+C -1 ; WX 222 ; N lacute ; B 67 0 264 929 ;
+C 224 ; WX 556 ; N agrave ; B 36 -15 530 734 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 14 -225 597 718 ;
+C -1 ; WX 722 ; N Cacute ; B 44 -19 681 929 ;
+C 227 ; WX 556 ; N atilde ; B 36 -15 530 722 ;
+C -1 ; WX 667 ; N Edotaccent ; B 86 0 616 901 ;
+C 154 ; WX 500 ; N scaron ; B 32 -15 464 734 ;
+C -1 ; WX 500 ; N scedilla ; B 32 -225 464 538 ;
+C 237 ; WX 278 ; N iacute ; B 95 0 292 734 ;
+C -1 ; WX 471 ; N lozenge ; B 10 0 462 728 ;
+C -1 ; WX 722 ; N Rcaron ; B 88 0 684 929 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 48 -225 704 737 ;
+C 251 ; WX 556 ; N ucircumflex ; B 68 -15 489 734 ;
+C 226 ; WX 556 ; N acircumflex ; B 36 -15 530 734 ;
+C -1 ; WX 667 ; N Amacron ; B 14 0 654 879 ;
+C -1 ; WX 333 ; N rcaron ; B 61 0 352 734 ;
+C 231 ; WX 500 ; N ccedilla ; B 30 -225 477 538 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 23 0 588 901 ;
+C 222 ; WX 667 ; N Thorn ; B 86 0 622 718 ;
+C -1 ; WX 778 ; N Omacron ; B 39 -19 739 879 ;
+C -1 ; WX 722 ; N Racute ; B 88 0 684 929 ;
+C -1 ; WX 667 ; N Sacute ; B 49 -19 620 929 ;
+C -1 ; WX 643 ; N dcaron ; B 35 -15 655 718 ;
+C -1 ; WX 722 ; N Umacron ; B 79 -19 644 879 ;
+C -1 ; WX 556 ; N uring ; B 68 -15 489 756 ;
+C 179 ; WX 333 ; N threesuperior ; B 5 270 325 703 ;
+C 210 ; WX 778 ; N Ograve ; B 39 -19 739 929 ;
+C 192 ; WX 667 ; N Agrave ; B 14 0 654 929 ;
+C -1 ; WX 667 ; N Abreve ; B 14 0 654 926 ;
+C 215 ; WX 584 ; N multiply ; B 39 0 545 506 ;
+C 250 ; WX 556 ; N uacute ; B 68 -15 489 734 ;
+C -1 ; WX 611 ; N Tcaron ; B 14 0 597 929 ;
+C -1 ; WX 476 ; N partialdiff ; B 13 -38 463 714 ;
+C 255 ; WX 500 ; N ydieresis ; B 11 -214 489 706 ;
+C -1 ; WX 722 ; N Nacute ; B 76 0 646 929 ;
+C 238 ; WX 278 ; N icircumflex ; B -6 0 285 734 ;
+C 202 ; WX 667 ; N Ecircumflex ; B 86 0 616 929 ;
+C 228 ; WX 556 ; N adieresis ; B 36 -15 530 706 ;
+C 235 ; WX 556 ; N edieresis ; B 40 -15 516 706 ;
+C -1 ; WX 500 ; N cacute ; B 30 -15 477 734 ;
+C -1 ; WX 556 ; N nacute ; B 65 0 491 734 ;
+C -1 ; WX 556 ; N umacron ; B 68 -15 489 684 ;
+C -1 ; WX 722 ; N Ncaron ; B 76 0 646 929 ;
+C 205 ; WX 278 ; N Iacute ; B 91 0 292 929 ;
+C 177 ; WX 584 ; N plusminus ; B 39 0 545 506 ;
+C 166 ; WX 260 ; N brokenbar ; B 94 -150 167 700 ;
+C 174 ; WX 737 ; N registered ; B -14 -19 752 737 ;
+C -1 ; WX 778 ; N Gbreve ; B 48 -19 704 926 ;
+C -1 ; WX 278 ; N Idotaccent ; B 91 0 188 901 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 586 706 ;
+C 200 ; WX 667 ; N Egrave ; B 86 0 616 929 ;
+C -1 ; WX 333 ; N racute ; B 77 0 332 734 ;
+C -1 ; WX 556 ; N omacron ; B 35 -14 521 684 ;
+C -1 ; WX 611 ; N Zacute ; B 23 0 588 929 ;
+C 142 ; WX 611 ; N Zcaron ; B 23 0 588 929 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 674 ;
+C 208 ; WX 722 ; N Eth ; B 0 0 674 718 ;
+C 199 ; WX 722 ; N Ccedilla ; B 44 -225 681 737 ;
+C -1 ; WX 222 ; N lcommaaccent ; B 67 -225 167 718 ;
+C -1 ; WX 317 ; N tcaron ; B 14 -7 329 808 ;
+C -1 ; WX 556 ; N eogonek ; B 40 -225 516 538 ;
+C -1 ; WX 722 ; N Uogonek ; B 79 -225 644 718 ;
+C 193 ; WX 667 ; N Aacute ; B 14 0 654 929 ;
+C 196 ; WX 667 ; N Adieresis ; B 14 0 654 901 ;
+C 232 ; WX 556 ; N egrave ; B 40 -15 516 734 ;
+C -1 ; WX 500 ; N zacute ; B 31 0 469 734 ;
+C -1 ; WX 222 ; N iogonek ; B -31 -225 183 718 ;
+C 211 ; WX 778 ; N Oacute ; B 39 -19 739 929 ;
+C 243 ; WX 556 ; N oacute ; B 35 -14 521 734 ;
+C -1 ; WX 556 ; N amacron ; B 36 -15 530 684 ;
+C -1 ; WX 500 ; N sacute ; B 32 -15 464 734 ;
+C 239 ; WX 278 ; N idieresis ; B 13 0 266 706 ;
+C 212 ; WX 778 ; N Ocircumflex ; B 39 -19 739 929 ;
+C 217 ; WX 722 ; N Ugrave ; B 79 -19 644 929 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 556 ; N thorn ; B 58 -207 517 718 ;
+C 178 ; WX 333 ; N twosuperior ; B 4 281 323 703 ;
+C 214 ; WX 778 ; N Odieresis ; B 39 -19 739 901 ;
+C 181 ; WX 556 ; N mu ; B 68 -207 489 523 ;
+C 236 ; WX 278 ; N igrave ; B -13 0 184 734 ;
+C -1 ; WX 556 ; N ohungarumlaut ; B 35 -14 521 734 ;
+C -1 ; WX 667 ; N Eogonek ; B 86 -220 633 718 ;
+C -1 ; WX 556 ; N dcroat ; B 35 -15 550 718 ;
+C 190 ; WX 834 ; N threequarters ; B 45 -19 810 703 ;
+C -1 ; WX 667 ; N Scedilla ; B 49 -225 620 737 ;
+C -1 ; WX 299 ; N lcaron ; B 67 0 311 718 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B 76 -225 663 718 ;
+C -1 ; WX 556 ; N Lacute ; B 76 0 537 929 ;
+C 153 ; WX 1000 ; N trademark ; B 46 306 903 718 ;
+C -1 ; WX 556 ; N edotaccent ; B 40 -15 516 706 ;
+C 204 ; WX 278 ; N Igrave ; B -13 0 188 929 ;
+C -1 ; WX 278 ; N Imacron ; B -17 0 296 879 ;
+C -1 ; WX 556 ; N Lcaron ; B 76 0 537 718 ;
+C 189 ; WX 834 ; N onehalf ; B 43 -19 773 703 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 523 674 ;
+C 244 ; WX 556 ; N ocircumflex ; B 35 -14 521 734 ;
+C 241 ; WX 556 ; N ntilde ; B 65 0 491 722 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 79 -19 644 929 ;
+C 201 ; WX 667 ; N Eacute ; B 86 0 616 929 ;
+C -1 ; WX 556 ; N emacron ; B 40 -15 516 684 ;
+C -1 ; WX 556 ; N gbreve ; B 40 -220 499 731 ;
+C 188 ; WX 834 ; N onequarter ; B 73 -19 756 703 ;
+C 138 ; WX 667 ; N Scaron ; B 49 -19 620 929 ;
+C -1 ; WX 667 ; N Scommaaccent ; B 49 -225 620 737 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 39 -19 739 929 ;
+C 176 ; WX 400 ; N degree ; B 54 411 346 703 ;
+C 242 ; WX 556 ; N ograve ; B 35 -14 521 734 ;
+C -1 ; WX 722 ; N Ccaron ; B 44 -19 681 929 ;
+C 249 ; WX 556 ; N ugrave ; B 68 -15 489 734 ;
+C -1 ; WX 453 ; N radical ; B -4 -80 458 762 ;
+C -1 ; WX 722 ; N Dcaron ; B 81 0 674 929 ;
+C -1 ; WX 333 ; N rcommaaccent ; B 77 -225 332 538 ;
+C 209 ; WX 722 ; N Ntilde ; B 76 0 646 917 ;
+C 245 ; WX 556 ; N otilde ; B 35 -14 521 722 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 88 -225 684 718 ;
+C -1 ; WX 556 ; N Lcommaaccent ; B 76 -225 537 718 ;
+C 195 ; WX 667 ; N Atilde ; B 14 0 654 917 ;
+C -1 ; WX 667 ; N Aogonek ; B 14 -225 654 718 ;
+C 197 ; WX 667 ; N Aring ; B 14 0 654 931 ;
+C 213 ; WX 778 ; N Otilde ; B 39 -19 739 917 ;
+C -1 ; WX 500 ; N zdotaccent ; B 31 0 469 706 ;
+C -1 ; WX 667 ; N Ecaron ; B 86 0 616 929 ;
+C -1 ; WX 278 ; N Iogonek ; B -3 -225 211 718 ;
+C -1 ; WX 500 ; N kcommaaccent ; B 67 -225 501 718 ;
+C -1 ; WX 584 ; N minus ; B 39 216 545 289 ;
+C 206 ; WX 278 ; N Icircumflex ; B -6 0 285 929 ;
+C -1 ; WX 556 ; N ncaron ; B 65 0 491 734 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 14 -225 257 669 ;
+C 172 ; WX 584 ; N logicalnot ; B 39 108 545 390 ;
+C 246 ; WX 556 ; N odieresis ; B 35 -14 521 706 ;
+C 252 ; WX 556 ; N udieresis ; B 68 -15 489 706 ;
+C -1 ; WX 549 ; N notequal ; B 12 -35 537 551 ;
+C -1 ; WX 556 ; N gcommaaccent ; B 40 -220 499 822 ;
+C 240 ; WX 556 ; N eth ; B 35 -15 522 737 ;
+C 158 ; WX 500 ; N zcaron ; B 31 0 469 734 ;
+C -1 ; WX 556 ; N ncommaaccent ; B 65 -225 491 538 ;
+C 185 ; WX 333 ; N onesuperior ; B 43 281 222 703 ;
+C -1 ; WX 278 ; N imacron ; B 5 0 272 684 ;
+C 128 ; WX 556 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2705
+KPX A C -30
+KPX A Cacute -30
+KPX A Ccaron -30
+KPX A Ccedilla -30
+KPX A G -30
+KPX A Gbreve -30
+KPX A Gcommaaccent -30
+KPX A O -30
+KPX A Oacute -30
+KPX A Ocircumflex -30
+KPX A Odieresis -30
+KPX A Ograve -30
+KPX A Ohungarumlaut -30
+KPX A Omacron -30
+KPX A Oslash -30
+KPX A Otilde -30
+KPX A Q -30
+KPX A T -120
+KPX A Tcaron -120
+KPX A Tcommaaccent -120
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -70
+KPX A W -50
+KPX A Y -100
+KPX A Yacute -100
+KPX A Ydieresis -100
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -40
+KPX A w -40
+KPX A y -40
+KPX A yacute -40
+KPX A ydieresis -40
+KPX Aacute C -30
+KPX Aacute Cacute -30
+KPX Aacute Ccaron -30
+KPX Aacute Ccedilla -30
+KPX Aacute G -30
+KPX Aacute Gbreve -30
+KPX Aacute Gcommaaccent -30
+KPX Aacute O -30
+KPX Aacute Oacute -30
+KPX Aacute Ocircumflex -30
+KPX Aacute Odieresis -30
+KPX Aacute Ograve -30
+KPX Aacute Ohungarumlaut -30
+KPX Aacute Omacron -30
+KPX Aacute Oslash -30
+KPX Aacute Otilde -30
+KPX Aacute Q -30
+KPX Aacute T -120
+KPX Aacute Tcaron -120
+KPX Aacute Tcommaaccent -120
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -70
+KPX Aacute W -50
+KPX Aacute Y -100
+KPX Aacute Yacute -100
+KPX Aacute Ydieresis -100
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -40
+KPX Aacute w -40
+KPX Aacute y -40
+KPX Aacute yacute -40
+KPX Aacute ydieresis -40
+KPX Abreve C -30
+KPX Abreve Cacute -30
+KPX Abreve Ccaron -30
+KPX Abreve Ccedilla -30
+KPX Abreve G -30
+KPX Abreve Gbreve -30
+KPX Abreve Gcommaaccent -30
+KPX Abreve O -30
+KPX Abreve Oacute -30
+KPX Abreve Ocircumflex -30
+KPX Abreve Odieresis -30
+KPX Abreve Ograve -30
+KPX Abreve Ohungarumlaut -30
+KPX Abreve Omacron -30
+KPX Abreve Oslash -30
+KPX Abreve Otilde -30
+KPX Abreve Q -30
+KPX Abreve T -120
+KPX Abreve Tcaron -120
+KPX Abreve Tcommaaccent -120
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -70
+KPX Abreve W -50
+KPX Abreve Y -100
+KPX Abreve Yacute -100
+KPX Abreve Ydieresis -100
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -40
+KPX Abreve w -40
+KPX Abreve y -40
+KPX Abreve yacute -40
+KPX Abreve ydieresis -40
+KPX Acircumflex C -30
+KPX Acircumflex Cacute -30
+KPX Acircumflex Ccaron -30
+KPX Acircumflex Ccedilla -30
+KPX Acircumflex G -30
+KPX Acircumflex Gbreve -30
+KPX Acircumflex Gcommaaccent -30
+KPX Acircumflex O -30
+KPX Acircumflex Oacute -30
+KPX Acircumflex Ocircumflex -30
+KPX Acircumflex Odieresis -30
+KPX Acircumflex Ograve -30
+KPX Acircumflex Ohungarumlaut -30
+KPX Acircumflex Omacron -30
+KPX Acircumflex Oslash -30
+KPX Acircumflex Otilde -30
+KPX Acircumflex Q -30
+KPX Acircumflex T -120
+KPX Acircumflex Tcaron -120
+KPX Acircumflex Tcommaaccent -120
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -70
+KPX Acircumflex W -50
+KPX Acircumflex Y -100
+KPX Acircumflex Yacute -100
+KPX Acircumflex Ydieresis -100
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -40
+KPX Acircumflex w -40
+KPX Acircumflex y -40
+KPX Acircumflex yacute -40
+KPX Acircumflex ydieresis -40
+KPX Adieresis C -30
+KPX Adieresis Cacute -30
+KPX Adieresis Ccaron -30
+KPX Adieresis Ccedilla -30
+KPX Adieresis G -30
+KPX Adieresis Gbreve -30
+KPX Adieresis Gcommaaccent -30
+KPX Adieresis O -30
+KPX Adieresis Oacute -30
+KPX Adieresis Ocircumflex -30
+KPX Adieresis Odieresis -30
+KPX Adieresis Ograve -30
+KPX Adieresis Ohungarumlaut -30
+KPX Adieresis Omacron -30
+KPX Adieresis Oslash -30
+KPX Adieresis Otilde -30
+KPX Adieresis Q -30
+KPX Adieresis T -120
+KPX Adieresis Tcaron -120
+KPX Adieresis Tcommaaccent -120
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -70
+KPX Adieresis W -50
+KPX Adieresis Y -100
+KPX Adieresis Yacute -100
+KPX Adieresis Ydieresis -100
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -40
+KPX Adieresis w -40
+KPX Adieresis y -40
+KPX Adieresis yacute -40
+KPX Adieresis ydieresis -40
+KPX Agrave C -30
+KPX Agrave Cacute -30
+KPX Agrave Ccaron -30
+KPX Agrave Ccedilla -30
+KPX Agrave G -30
+KPX Agrave Gbreve -30
+KPX Agrave Gcommaaccent -30
+KPX Agrave O -30
+KPX Agrave Oacute -30
+KPX Agrave Ocircumflex -30
+KPX Agrave Odieresis -30
+KPX Agrave Ograve -30
+KPX Agrave Ohungarumlaut -30
+KPX Agrave Omacron -30
+KPX Agrave Oslash -30
+KPX Agrave Otilde -30
+KPX Agrave Q -30
+KPX Agrave T -120
+KPX Agrave Tcaron -120
+KPX Agrave Tcommaaccent -120
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -70
+KPX Agrave W -50
+KPX Agrave Y -100
+KPX Agrave Yacute -100
+KPX Agrave Ydieresis -100
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -40
+KPX Agrave w -40
+KPX Agrave y -40
+KPX Agrave yacute -40
+KPX Agrave ydieresis -40
+KPX Amacron C -30
+KPX Amacron Cacute -30
+KPX Amacron Ccaron -30
+KPX Amacron Ccedilla -30
+KPX Amacron G -30
+KPX Amacron Gbreve -30
+KPX Amacron Gcommaaccent -30
+KPX Amacron O -30
+KPX Amacron Oacute -30
+KPX Amacron Ocircumflex -30
+KPX Amacron Odieresis -30
+KPX Amacron Ograve -30
+KPX Amacron Ohungarumlaut -30
+KPX Amacron Omacron -30
+KPX Amacron Oslash -30
+KPX Amacron Otilde -30
+KPX Amacron Q -30
+KPX Amacron T -120
+KPX Amacron Tcaron -120
+KPX Amacron Tcommaaccent -120
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -70
+KPX Amacron W -50
+KPX Amacron Y -100
+KPX Amacron Yacute -100
+KPX Amacron Ydieresis -100
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -40
+KPX Amacron w -40
+KPX Amacron y -40
+KPX Amacron yacute -40
+KPX Amacron ydieresis -40
+KPX Aogonek C -30
+KPX Aogonek Cacute -30
+KPX Aogonek Ccaron -30
+KPX Aogonek Ccedilla -30
+KPX Aogonek G -30
+KPX Aogonek Gbreve -30
+KPX Aogonek Gcommaaccent -30
+KPX Aogonek O -30
+KPX Aogonek Oacute -30
+KPX Aogonek Ocircumflex -30
+KPX Aogonek Odieresis -30
+KPX Aogonek Ograve -30
+KPX Aogonek Ohungarumlaut -30
+KPX Aogonek Omacron -30
+KPX Aogonek Oslash -30
+KPX Aogonek Otilde -30
+KPX Aogonek Q -30
+KPX Aogonek T -120
+KPX Aogonek Tcaron -120
+KPX Aogonek Tcommaaccent -120
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -70
+KPX Aogonek W -50
+KPX Aogonek Y -100
+KPX Aogonek Yacute -100
+KPX Aogonek Ydieresis -100
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -40
+KPX Aogonek w -40
+KPX Aogonek y -40
+KPX Aogonek yacute -40
+KPX Aogonek ydieresis -40
+KPX Aring C -30
+KPX Aring Cacute -30
+KPX Aring Ccaron -30
+KPX Aring Ccedilla -30
+KPX Aring G -30
+KPX Aring Gbreve -30
+KPX Aring Gcommaaccent -30
+KPX Aring O -30
+KPX Aring Oacute -30
+KPX Aring Ocircumflex -30
+KPX Aring Odieresis -30
+KPX Aring Ograve -30
+KPX Aring Ohungarumlaut -30
+KPX Aring Omacron -30
+KPX Aring Oslash -30
+KPX Aring Otilde -30
+KPX Aring Q -30
+KPX Aring T -120
+KPX Aring Tcaron -120
+KPX Aring Tcommaaccent -120
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -70
+KPX Aring W -50
+KPX Aring Y -100
+KPX Aring Yacute -100
+KPX Aring Ydieresis -100
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -40
+KPX Aring w -40
+KPX Aring y -40
+KPX Aring yacute -40
+KPX Aring ydieresis -40
+KPX Atilde C -30
+KPX Atilde Cacute -30
+KPX Atilde Ccaron -30
+KPX Atilde Ccedilla -30
+KPX Atilde G -30
+KPX Atilde Gbreve -30
+KPX Atilde Gcommaaccent -30
+KPX Atilde O -30
+KPX Atilde Oacute -30
+KPX Atilde Ocircumflex -30
+KPX Atilde Odieresis -30
+KPX Atilde Ograve -30
+KPX Atilde Ohungarumlaut -30
+KPX Atilde Omacron -30
+KPX Atilde Oslash -30
+KPX Atilde Otilde -30
+KPX Atilde Q -30
+KPX Atilde T -120
+KPX Atilde Tcaron -120
+KPX Atilde Tcommaaccent -120
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -70
+KPX Atilde W -50
+KPX Atilde Y -100
+KPX Atilde Yacute -100
+KPX Atilde Ydieresis -100
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -40
+KPX Atilde w -40
+KPX Atilde y -40
+KPX Atilde yacute -40
+KPX Atilde ydieresis -40
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX B comma -20
+KPX B period -20
+KPX C comma -30
+KPX C period -30
+KPX Cacute comma -30
+KPX Cacute period -30
+KPX Ccaron comma -30
+KPX Ccaron period -30
+KPX Ccedilla comma -30
+KPX Ccedilla period -30
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -70
+KPX D W -40
+KPX D Y -90
+KPX D Yacute -90
+KPX D Ydieresis -90
+KPX D comma -70
+KPX D period -70
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -70
+KPX Dcaron W -40
+KPX Dcaron Y -90
+KPX Dcaron Yacute -90
+KPX Dcaron Ydieresis -90
+KPX Dcaron comma -70
+KPX Dcaron period -70
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -70
+KPX Dcroat W -40
+KPX Dcroat Y -90
+KPX Dcroat Yacute -90
+KPX Dcroat Ydieresis -90
+KPX Dcroat comma -70
+KPX Dcroat period -70
+KPX F A -80
+KPX F Aacute -80
+KPX F Abreve -80
+KPX F Acircumflex -80
+KPX F Adieresis -80
+KPX F Agrave -80
+KPX F Amacron -80
+KPX F Aogonek -80
+KPX F Aring -80
+KPX F Atilde -80
+KPX F a -50
+KPX F aacute -50
+KPX F abreve -50
+KPX F acircumflex -50
+KPX F adieresis -50
+KPX F agrave -50
+KPX F amacron -50
+KPX F aogonek -50
+KPX F aring -50
+KPX F atilde -50
+KPX F comma -150
+KPX F e -30
+KPX F eacute -30
+KPX F ecaron -30
+KPX F ecircumflex -30
+KPX F edieresis -30
+KPX F edotaccent -30
+KPX F egrave -30
+KPX F emacron -30
+KPX F eogonek -30
+KPX F o -30
+KPX F oacute -30
+KPX F ocircumflex -30
+KPX F odieresis -30
+KPX F ograve -30
+KPX F ohungarumlaut -30
+KPX F omacron -30
+KPX F oslash -30
+KPX F otilde -30
+KPX F period -150
+KPX F r -45
+KPX F racute -45
+KPX F rcaron -45
+KPX F rcommaaccent -45
+KPX J A -20
+KPX J Aacute -20
+KPX J Abreve -20
+KPX J Acircumflex -20
+KPX J Adieresis -20
+KPX J Agrave -20
+KPX J Amacron -20
+KPX J Aogonek -20
+KPX J Aring -20
+KPX J Atilde -20
+KPX J a -20
+KPX J aacute -20
+KPX J abreve -20
+KPX J acircumflex -20
+KPX J adieresis -20
+KPX J agrave -20
+KPX J amacron -20
+KPX J aogonek -20
+KPX J aring -20
+KPX J atilde -20
+KPX J comma -30
+KPX J period -30
+KPX J u -20
+KPX J uacute -20
+KPX J ucircumflex -20
+KPX J udieresis -20
+KPX J ugrave -20
+KPX J uhungarumlaut -20
+KPX J umacron -20
+KPX J uogonek -20
+KPX J uring -20
+KPX K O -50
+KPX K Oacute -50
+KPX K Ocircumflex -50
+KPX K Odieresis -50
+KPX K Ograve -50
+KPX K Ohungarumlaut -50
+KPX K Omacron -50
+KPX K Oslash -50
+KPX K Otilde -50
+KPX K e -40
+KPX K eacute -40
+KPX K ecaron -40
+KPX K ecircumflex -40
+KPX K edieresis -40
+KPX K edotaccent -40
+KPX K egrave -40
+KPX K emacron -40
+KPX K eogonek -40
+KPX K o -40
+KPX K oacute -40
+KPX K ocircumflex -40
+KPX K odieresis -40
+KPX K ograve -40
+KPX K ohungarumlaut -40
+KPX K omacron -40
+KPX K oslash -40
+KPX K otilde -40
+KPX K u -30
+KPX K uacute -30
+KPX K ucircumflex -30
+KPX K udieresis -30
+KPX K ugrave -30
+KPX K uhungarumlaut -30
+KPX K umacron -30
+KPX K uogonek -30
+KPX K uring -30
+KPX K y -50
+KPX K yacute -50
+KPX K ydieresis -50
+KPX Kcommaaccent O -50
+KPX Kcommaaccent Oacute -50
+KPX Kcommaaccent Ocircumflex -50
+KPX Kcommaaccent Odieresis -50
+KPX Kcommaaccent Ograve -50
+KPX Kcommaaccent Ohungarumlaut -50
+KPX Kcommaaccent Omacron -50
+KPX Kcommaaccent Oslash -50
+KPX Kcommaaccent Otilde -50
+KPX Kcommaaccent e -40
+KPX Kcommaaccent eacute -40
+KPX Kcommaaccent ecaron -40
+KPX Kcommaaccent ecircumflex -40
+KPX Kcommaaccent edieresis -40
+KPX Kcommaaccent edotaccent -40
+KPX Kcommaaccent egrave -40
+KPX Kcommaaccent emacron -40
+KPX Kcommaaccent eogonek -40
+KPX Kcommaaccent o -40
+KPX Kcommaaccent oacute -40
+KPX Kcommaaccent ocircumflex -40
+KPX Kcommaaccent odieresis -40
+KPX Kcommaaccent ograve -40
+KPX Kcommaaccent ohungarumlaut -40
+KPX Kcommaaccent omacron -40
+KPX Kcommaaccent oslash -40
+KPX Kcommaaccent otilde -40
+KPX Kcommaaccent u -30
+KPX Kcommaaccent uacute -30
+KPX Kcommaaccent ucircumflex -30
+KPX Kcommaaccent udieresis -30
+KPX Kcommaaccent ugrave -30
+KPX Kcommaaccent uhungarumlaut -30
+KPX Kcommaaccent umacron -30
+KPX Kcommaaccent uogonek -30
+KPX Kcommaaccent uring -30
+KPX Kcommaaccent y -50
+KPX Kcommaaccent yacute -50
+KPX Kcommaaccent ydieresis -50
+KPX L T -110
+KPX L Tcaron -110
+KPX L Tcommaaccent -110
+KPX L V -110
+KPX L W -70
+KPX L Y -140
+KPX L Yacute -140
+KPX L Ydieresis -140
+KPX L quotedblright -140
+KPX L quoteright -160
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -110
+KPX Lacute Tcaron -110
+KPX Lacute Tcommaaccent -110
+KPX Lacute V -110
+KPX Lacute W -70
+KPX Lacute Y -140
+KPX Lacute Yacute -140
+KPX Lacute Ydieresis -140
+KPX Lacute quotedblright -140
+KPX Lacute quoteright -160
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcaron T -110
+KPX Lcaron Tcaron -110
+KPX Lcaron Tcommaaccent -110
+KPX Lcaron V -110
+KPX Lcaron W -70
+KPX Lcaron Y -140
+KPX Lcaron Yacute -140
+KPX Lcaron Ydieresis -140
+KPX Lcaron quotedblright -140
+KPX Lcaron quoteright -160
+KPX Lcaron y -30
+KPX Lcaron yacute -30
+KPX Lcaron ydieresis -30
+KPX Lcommaaccent T -110
+KPX Lcommaaccent Tcaron -110
+KPX Lcommaaccent Tcommaaccent -110
+KPX Lcommaaccent V -110
+KPX Lcommaaccent W -70
+KPX Lcommaaccent Y -140
+KPX Lcommaaccent Yacute -140
+KPX Lcommaaccent Ydieresis -140
+KPX Lcommaaccent quotedblright -140
+KPX Lcommaaccent quoteright -160
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -110
+KPX Lslash Tcaron -110
+KPX Lslash Tcommaaccent -110
+KPX Lslash V -110
+KPX Lslash W -70
+KPX Lslash Y -140
+KPX Lslash Yacute -140
+KPX Lslash Ydieresis -140
+KPX Lslash quotedblright -140
+KPX Lslash quoteright -160
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX O A -20
+KPX O Aacute -20
+KPX O Abreve -20
+KPX O Acircumflex -20
+KPX O Adieresis -20
+KPX O Agrave -20
+KPX O Amacron -20
+KPX O Aogonek -20
+KPX O Aring -20
+KPX O Atilde -20
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -30
+KPX O X -60
+KPX O Y -70
+KPX O Yacute -70
+KPX O Ydieresis -70
+KPX O comma -40
+KPX O period -40
+KPX Oacute A -20
+KPX Oacute Aacute -20
+KPX Oacute Abreve -20
+KPX Oacute Acircumflex -20
+KPX Oacute Adieresis -20
+KPX Oacute Agrave -20
+KPX Oacute Amacron -20
+KPX Oacute Aogonek -20
+KPX Oacute Aring -20
+KPX Oacute Atilde -20
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -30
+KPX Oacute X -60
+KPX Oacute Y -70
+KPX Oacute Yacute -70
+KPX Oacute Ydieresis -70
+KPX Oacute comma -40
+KPX Oacute period -40
+KPX Ocircumflex A -20
+KPX Ocircumflex Aacute -20
+KPX Ocircumflex Abreve -20
+KPX Ocircumflex Acircumflex -20
+KPX Ocircumflex Adieresis -20
+KPX Ocircumflex Agrave -20
+KPX Ocircumflex Amacron -20
+KPX Ocircumflex Aogonek -20
+KPX Ocircumflex Aring -20
+KPX Ocircumflex Atilde -20
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -30
+KPX Ocircumflex X -60
+KPX Ocircumflex Y -70
+KPX Ocircumflex Yacute -70
+KPX Ocircumflex Ydieresis -70
+KPX Ocircumflex comma -40
+KPX Ocircumflex period -40
+KPX Odieresis A -20
+KPX Odieresis Aacute -20
+KPX Odieresis Abreve -20
+KPX Odieresis Acircumflex -20
+KPX Odieresis Adieresis -20
+KPX Odieresis Agrave -20
+KPX Odieresis Amacron -20
+KPX Odieresis Aogonek -20
+KPX Odieresis Aring -20
+KPX Odieresis Atilde -20
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -30
+KPX Odieresis X -60
+KPX Odieresis Y -70
+KPX Odieresis Yacute -70
+KPX Odieresis Ydieresis -70
+KPX Odieresis comma -40
+KPX Odieresis period -40
+KPX Ograve A -20
+KPX Ograve Aacute -20
+KPX Ograve Abreve -20
+KPX Ograve Acircumflex -20
+KPX Ograve Adieresis -20
+KPX Ograve Agrave -20
+KPX Ograve Amacron -20
+KPX Ograve Aogonek -20
+KPX Ograve Aring -20
+KPX Ograve Atilde -20
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -30
+KPX Ograve X -60
+KPX Ograve Y -70
+KPX Ograve Yacute -70
+KPX Ograve Ydieresis -70
+KPX Ograve comma -40
+KPX Ograve period -40
+KPX Ohungarumlaut A -20
+KPX Ohungarumlaut Aacute -20
+KPX Ohungarumlaut Abreve -20
+KPX Ohungarumlaut Acircumflex -20
+KPX Ohungarumlaut Adieresis -20
+KPX Ohungarumlaut Agrave -20
+KPX Ohungarumlaut Amacron -20
+KPX Ohungarumlaut Aogonek -20
+KPX Ohungarumlaut Aring -20
+KPX Ohungarumlaut Atilde -20
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -30
+KPX Ohungarumlaut X -60
+KPX Ohungarumlaut Y -70
+KPX Ohungarumlaut Yacute -70
+KPX Ohungarumlaut Ydieresis -70
+KPX Ohungarumlaut comma -40
+KPX Ohungarumlaut period -40
+KPX Omacron A -20
+KPX Omacron Aacute -20
+KPX Omacron Abreve -20
+KPX Omacron Acircumflex -20
+KPX Omacron Adieresis -20
+KPX Omacron Agrave -20
+KPX Omacron Amacron -20
+KPX Omacron Aogonek -20
+KPX Omacron Aring -20
+KPX Omacron Atilde -20
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -30
+KPX Omacron X -60
+KPX Omacron Y -70
+KPX Omacron Yacute -70
+KPX Omacron Ydieresis -70
+KPX Omacron comma -40
+KPX Omacron period -40
+KPX Oslash A -20
+KPX Oslash Aacute -20
+KPX Oslash Abreve -20
+KPX Oslash Acircumflex -20
+KPX Oslash Adieresis -20
+KPX Oslash Agrave -20
+KPX Oslash Amacron -20
+KPX Oslash Aogonek -20
+KPX Oslash Aring -20
+KPX Oslash Atilde -20
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -30
+KPX Oslash X -60
+KPX Oslash Y -70
+KPX Oslash Yacute -70
+KPX Oslash Ydieresis -70
+KPX Oslash comma -40
+KPX Oslash period -40
+KPX Otilde A -20
+KPX Otilde Aacute -20
+KPX Otilde Abreve -20
+KPX Otilde Acircumflex -20
+KPX Otilde Adieresis -20
+KPX Otilde Agrave -20
+KPX Otilde Amacron -20
+KPX Otilde Aogonek -20
+KPX Otilde Aring -20
+KPX Otilde Atilde -20
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -30
+KPX Otilde X -60
+KPX Otilde Y -70
+KPX Otilde Yacute -70
+KPX Otilde Ydieresis -70
+KPX Otilde comma -40
+KPX Otilde period -40
+KPX P A -120
+KPX P Aacute -120
+KPX P Abreve -120
+KPX P Acircumflex -120
+KPX P Adieresis -120
+KPX P Agrave -120
+KPX P Amacron -120
+KPX P Aogonek -120
+KPX P Aring -120
+KPX P Atilde -120
+KPX P a -40
+KPX P aacute -40
+KPX P abreve -40
+KPX P acircumflex -40
+KPX P adieresis -40
+KPX P agrave -40
+KPX P amacron -40
+KPX P aogonek -40
+KPX P aring -40
+KPX P atilde -40
+KPX P comma -180
+KPX P e -50
+KPX P eacute -50
+KPX P ecaron -50
+KPX P ecircumflex -50
+KPX P edieresis -50
+KPX P edotaccent -50
+KPX P egrave -50
+KPX P emacron -50
+KPX P eogonek -50
+KPX P o -50
+KPX P oacute -50
+KPX P ocircumflex -50
+KPX P odieresis -50
+KPX P ograve -50
+KPX P ohungarumlaut -50
+KPX P omacron -50
+KPX P oslash -50
+KPX P otilde -50
+KPX P period -180
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -20
+KPX R Oacute -20
+KPX R Ocircumflex -20
+KPX R Odieresis -20
+KPX R Ograve -20
+KPX R Ohungarumlaut -20
+KPX R Omacron -20
+KPX R Oslash -20
+KPX R Otilde -20
+KPX R T -30
+KPX R Tcaron -30
+KPX R Tcommaaccent -30
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -50
+KPX R W -30
+KPX R Y -50
+KPX R Yacute -50
+KPX R Ydieresis -50
+KPX Racute O -20
+KPX Racute Oacute -20
+KPX Racute Ocircumflex -20
+KPX Racute Odieresis -20
+KPX Racute Ograve -20
+KPX Racute Ohungarumlaut -20
+KPX Racute Omacron -20
+KPX Racute Oslash -20
+KPX Racute Otilde -20
+KPX Racute T -30
+KPX Racute Tcaron -30
+KPX Racute Tcommaaccent -30
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -50
+KPX Racute W -30
+KPX Racute Y -50
+KPX Racute Yacute -50
+KPX Racute Ydieresis -50
+KPX Rcaron O -20
+KPX Rcaron Oacute -20
+KPX Rcaron Ocircumflex -20
+KPX Rcaron Odieresis -20
+KPX Rcaron Ograve -20
+KPX Rcaron Ohungarumlaut -20
+KPX Rcaron Omacron -20
+KPX Rcaron Oslash -20
+KPX Rcaron Otilde -20
+KPX Rcaron T -30
+KPX Rcaron Tcaron -30
+KPX Rcaron Tcommaaccent -30
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -50
+KPX Rcaron W -30
+KPX Rcaron Y -50
+KPX Rcaron Yacute -50
+KPX Rcaron Ydieresis -50
+KPX Rcommaaccent O -20
+KPX Rcommaaccent Oacute -20
+KPX Rcommaaccent Ocircumflex -20
+KPX Rcommaaccent Odieresis -20
+KPX Rcommaaccent Ograve -20
+KPX Rcommaaccent Ohungarumlaut -20
+KPX Rcommaaccent Omacron -20
+KPX Rcommaaccent Oslash -20
+KPX Rcommaaccent Otilde -20
+KPX Rcommaaccent T -30
+KPX Rcommaaccent Tcaron -30
+KPX Rcommaaccent Tcommaaccent -30
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -50
+KPX Rcommaaccent W -30
+KPX Rcommaaccent Y -50
+KPX Rcommaaccent Yacute -50
+KPX Rcommaaccent Ydieresis -50
+KPX S comma -20
+KPX S period -20
+KPX Sacute comma -20
+KPX Sacute period -20
+KPX Scaron comma -20
+KPX Scaron period -20
+KPX Scedilla comma -20
+KPX Scedilla period -20
+KPX Scommaaccent comma -20
+KPX Scommaaccent period -20
+KPX T A -120
+KPX T Aacute -120
+KPX T Abreve -120
+KPX T Acircumflex -120
+KPX T Adieresis -120
+KPX T Agrave -120
+KPX T Amacron -120
+KPX T Aogonek -120
+KPX T Aring -120
+KPX T Atilde -120
+KPX T O -40
+KPX T Oacute -40
+KPX T Ocircumflex -40
+KPX T Odieresis -40
+KPX T Ograve -40
+KPX T Ohungarumlaut -40
+KPX T Omacron -40
+KPX T Oslash -40
+KPX T Otilde -40
+KPX T a -120
+KPX T aacute -120
+KPX T abreve -60
+KPX T acircumflex -120
+KPX T adieresis -120
+KPX T agrave -120
+KPX T amacron -60
+KPX T aogonek -120
+KPX T aring -120
+KPX T atilde -60
+KPX T colon -20
+KPX T comma -120
+KPX T e -120
+KPX T eacute -120
+KPX T ecaron -120
+KPX T ecircumflex -120
+KPX T edieresis -120
+KPX T edotaccent -120
+KPX T egrave -60
+KPX T emacron -60
+KPX T eogonek -120
+KPX T hyphen -140
+KPX T o -120
+KPX T oacute -120
+KPX T ocircumflex -120
+KPX T odieresis -120
+KPX T ograve -120
+KPX T ohungarumlaut -120
+KPX T omacron -60
+KPX T oslash -120
+KPX T otilde -60
+KPX T period -120
+KPX T r -120
+KPX T racute -120
+KPX T rcaron -120
+KPX T rcommaaccent -120
+KPX T semicolon -20
+KPX T u -120
+KPX T uacute -120
+KPX T ucircumflex -120
+KPX T udieresis -120
+KPX T ugrave -120
+KPX T uhungarumlaut -120
+KPX T umacron -60
+KPX T uogonek -120
+KPX T uring -120
+KPX T w -120
+KPX T y -120
+KPX T yacute -120
+KPX T ydieresis -60
+KPX Tcaron A -120
+KPX Tcaron Aacute -120
+KPX Tcaron Abreve -120
+KPX Tcaron Acircumflex -120
+KPX Tcaron Adieresis -120
+KPX Tcaron Agrave -120
+KPX Tcaron Amacron -120
+KPX Tcaron Aogonek -120
+KPX Tcaron Aring -120
+KPX Tcaron Atilde -120
+KPX Tcaron O -40
+KPX Tcaron Oacute -40
+KPX Tcaron Ocircumflex -40
+KPX Tcaron Odieresis -40
+KPX Tcaron Ograve -40
+KPX Tcaron Ohungarumlaut -40
+KPX Tcaron Omacron -40
+KPX Tcaron Oslash -40
+KPX Tcaron Otilde -40
+KPX Tcaron a -120
+KPX Tcaron aacute -120
+KPX Tcaron abreve -60
+KPX Tcaron acircumflex -120
+KPX Tcaron adieresis -120
+KPX Tcaron agrave -120
+KPX Tcaron amacron -60
+KPX Tcaron aogonek -120
+KPX Tcaron aring -120
+KPX Tcaron atilde -60
+KPX Tcaron colon -20
+KPX Tcaron comma -120
+KPX Tcaron e -120
+KPX Tcaron eacute -120
+KPX Tcaron ecaron -120
+KPX Tcaron ecircumflex -120
+KPX Tcaron edieresis -120
+KPX Tcaron edotaccent -120
+KPX Tcaron egrave -60
+KPX Tcaron emacron -60
+KPX Tcaron eogonek -120
+KPX Tcaron hyphen -140
+KPX Tcaron o -120
+KPX Tcaron oacute -120
+KPX Tcaron ocircumflex -120
+KPX Tcaron odieresis -120
+KPX Tcaron ograve -120
+KPX Tcaron ohungarumlaut -120
+KPX Tcaron omacron -60
+KPX Tcaron oslash -120
+KPX Tcaron otilde -60
+KPX Tcaron period -120
+KPX Tcaron r -120
+KPX Tcaron racute -120
+KPX Tcaron rcaron -120
+KPX Tcaron rcommaaccent -120
+KPX Tcaron semicolon -20
+KPX Tcaron u -120
+KPX Tcaron uacute -120
+KPX Tcaron ucircumflex -120
+KPX Tcaron udieresis -120
+KPX Tcaron ugrave -120
+KPX Tcaron uhungarumlaut -120
+KPX Tcaron umacron -60
+KPX Tcaron uogonek -120
+KPX Tcaron uring -120
+KPX Tcaron w -120
+KPX Tcaron y -120
+KPX Tcaron yacute -120
+KPX Tcaron ydieresis -60
+KPX Tcommaaccent A -120
+KPX Tcommaaccent Aacute -120
+KPX Tcommaaccent Abreve -120
+KPX Tcommaaccent Acircumflex -120
+KPX Tcommaaccent Adieresis -120
+KPX Tcommaaccent Agrave -120
+KPX Tcommaaccent Amacron -120
+KPX Tcommaaccent Aogonek -120
+KPX Tcommaaccent Aring -120
+KPX Tcommaaccent Atilde -120
+KPX Tcommaaccent O -40
+KPX Tcommaaccent Oacute -40
+KPX Tcommaaccent Ocircumflex -40
+KPX Tcommaaccent Odieresis -40
+KPX Tcommaaccent Ograve -40
+KPX Tcommaaccent Ohungarumlaut -40
+KPX Tcommaaccent Omacron -40
+KPX Tcommaaccent Oslash -40
+KPX Tcommaaccent Otilde -40
+KPX Tcommaaccent a -120
+KPX Tcommaaccent aacute -120
+KPX Tcommaaccent abreve -60
+KPX Tcommaaccent acircumflex -120
+KPX Tcommaaccent adieresis -120
+KPX Tcommaaccent agrave -120
+KPX Tcommaaccent amacron -60
+KPX Tcommaaccent aogonek -120
+KPX Tcommaaccent aring -120
+KPX Tcommaaccent atilde -60
+KPX Tcommaaccent colon -20
+KPX Tcommaaccent comma -120
+KPX Tcommaaccent e -120
+KPX Tcommaaccent eacute -120
+KPX Tcommaaccent ecaron -120
+KPX Tcommaaccent ecircumflex -120
+KPX Tcommaaccent edieresis -120
+KPX Tcommaaccent edotaccent -120
+KPX Tcommaaccent egrave -60
+KPX Tcommaaccent emacron -60
+KPX Tcommaaccent eogonek -120
+KPX Tcommaaccent hyphen -140
+KPX Tcommaaccent o -120
+KPX Tcommaaccent oacute -120
+KPX Tcommaaccent ocircumflex -120
+KPX Tcommaaccent odieresis -120
+KPX Tcommaaccent ograve -120
+KPX Tcommaaccent ohungarumlaut -120
+KPX Tcommaaccent omacron -60
+KPX Tcommaaccent oslash -120
+KPX Tcommaaccent otilde -60
+KPX Tcommaaccent period -120
+KPX Tcommaaccent r -120
+KPX Tcommaaccent racute -120
+KPX Tcommaaccent rcaron -120
+KPX Tcommaaccent rcommaaccent -120
+KPX Tcommaaccent semicolon -20
+KPX Tcommaaccent u -120
+KPX Tcommaaccent uacute -120
+KPX Tcommaaccent ucircumflex -120
+KPX Tcommaaccent udieresis -120
+KPX Tcommaaccent ugrave -120
+KPX Tcommaaccent uhungarumlaut -120
+KPX Tcommaaccent umacron -60
+KPX Tcommaaccent uogonek -120
+KPX Tcommaaccent uring -120
+KPX Tcommaaccent w -120
+KPX Tcommaaccent y -120
+KPX Tcommaaccent yacute -120
+KPX Tcommaaccent ydieresis -60
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX U comma -40
+KPX U period -40
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Uacute comma -40
+KPX Uacute period -40
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Ucircumflex comma -40
+KPX Ucircumflex period -40
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Udieresis comma -40
+KPX Udieresis period -40
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Ugrave comma -40
+KPX Ugrave period -40
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Uhungarumlaut comma -40
+KPX Uhungarumlaut period -40
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Umacron comma -40
+KPX Umacron period -40
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uogonek comma -40
+KPX Uogonek period -40
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX Uring comma -40
+KPX Uring period -40
+KPX V A -80
+KPX V Aacute -80
+KPX V Abreve -80
+KPX V Acircumflex -80
+KPX V Adieresis -80
+KPX V Agrave -80
+KPX V Amacron -80
+KPX V Aogonek -80
+KPX V Aring -80
+KPX V Atilde -80
+KPX V G -40
+KPX V Gbreve -40
+KPX V Gcommaaccent -40
+KPX V O -40
+KPX V Oacute -40
+KPX V Ocircumflex -40
+KPX V Odieresis -40
+KPX V Ograve -40
+KPX V Ohungarumlaut -40
+KPX V Omacron -40
+KPX V Oslash -40
+KPX V Otilde -40
+KPX V a -70
+KPX V aacute -70
+KPX V abreve -70
+KPX V acircumflex -70
+KPX V adieresis -70
+KPX V agrave -70
+KPX V amacron -70
+KPX V aogonek -70
+KPX V aring -70
+KPX V atilde -70
+KPX V colon -40
+KPX V comma -125
+KPX V e -80
+KPX V eacute -80
+KPX V ecaron -80
+KPX V ecircumflex -80
+KPX V edieresis -80
+KPX V edotaccent -80
+KPX V egrave -80
+KPX V emacron -80
+KPX V eogonek -80
+KPX V hyphen -80
+KPX V o -80
+KPX V oacute -80
+KPX V ocircumflex -80
+KPX V odieresis -80
+KPX V ograve -80
+KPX V ohungarumlaut -80
+KPX V omacron -80
+KPX V oslash -80
+KPX V otilde -80
+KPX V period -125
+KPX V semicolon -40
+KPX V u -70
+KPX V uacute -70
+KPX V ucircumflex -70
+KPX V udieresis -70
+KPX V ugrave -70
+KPX V uhungarumlaut -70
+KPX V umacron -70
+KPX V uogonek -70
+KPX V uring -70
+KPX W A -50
+KPX W Aacute -50
+KPX W Abreve -50
+KPX W Acircumflex -50
+KPX W Adieresis -50
+KPX W Agrave -50
+KPX W Amacron -50
+KPX W Aogonek -50
+KPX W Aring -50
+KPX W Atilde -50
+KPX W O -20
+KPX W Oacute -20
+KPX W Ocircumflex -20
+KPX W Odieresis -20
+KPX W Ograve -20
+KPX W Ohungarumlaut -20
+KPX W Omacron -20
+KPX W Oslash -20
+KPX W Otilde -20
+KPX W a -40
+KPX W aacute -40
+KPX W abreve -40
+KPX W acircumflex -40
+KPX W adieresis -40
+KPX W agrave -40
+KPX W amacron -40
+KPX W aogonek -40
+KPX W aring -40
+KPX W atilde -40
+KPX W comma -80
+KPX W e -30
+KPX W eacute -30
+KPX W ecaron -30
+KPX W ecircumflex -30
+KPX W edieresis -30
+KPX W edotaccent -30
+KPX W egrave -30
+KPX W emacron -30
+KPX W eogonek -30
+KPX W hyphen -40
+KPX W o -30
+KPX W oacute -30
+KPX W ocircumflex -30
+KPX W odieresis -30
+KPX W ograve -30
+KPX W ohungarumlaut -30
+KPX W omacron -30
+KPX W oslash -30
+KPX W otilde -30
+KPX W period -80
+KPX W u -30
+KPX W uacute -30
+KPX W ucircumflex -30
+KPX W udieresis -30
+KPX W ugrave -30
+KPX W uhungarumlaut -30
+KPX W umacron -30
+KPX W uogonek -30
+KPX W uring -30
+KPX W y -20
+KPX W yacute -20
+KPX W ydieresis -20
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -85
+KPX Y Oacute -85
+KPX Y Ocircumflex -85
+KPX Y Odieresis -85
+KPX Y Ograve -85
+KPX Y Ohungarumlaut -85
+KPX Y Omacron -85
+KPX Y Oslash -85
+KPX Y Otilde -85
+KPX Y a -140
+KPX Y aacute -140
+KPX Y abreve -70
+KPX Y acircumflex -140
+KPX Y adieresis -140
+KPX Y agrave -140
+KPX Y amacron -70
+KPX Y aogonek -140
+KPX Y aring -140
+KPX Y atilde -140
+KPX Y colon -60
+KPX Y comma -140
+KPX Y e -140
+KPX Y eacute -140
+KPX Y ecaron -140
+KPX Y ecircumflex -140
+KPX Y edieresis -140
+KPX Y edotaccent -140
+KPX Y egrave -140
+KPX Y emacron -70
+KPX Y eogonek -140
+KPX Y hyphen -140
+KPX Y i -20
+KPX Y iacute -20
+KPX Y iogonek -20
+KPX Y o -140
+KPX Y oacute -140
+KPX Y ocircumflex -140
+KPX Y odieresis -140
+KPX Y ograve -140
+KPX Y ohungarumlaut -140
+KPX Y omacron -140
+KPX Y oslash -140
+KPX Y otilde -140
+KPX Y period -140
+KPX Y semicolon -60
+KPX Y u -110
+KPX Y uacute -110
+KPX Y ucircumflex -110
+KPX Y udieresis -110
+KPX Y ugrave -110
+KPX Y uhungarumlaut -110
+KPX Y umacron -110
+KPX Y uogonek -110
+KPX Y uring -110
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -85
+KPX Yacute Oacute -85
+KPX Yacute Ocircumflex -85
+KPX Yacute Odieresis -85
+KPX Yacute Ograve -85
+KPX Yacute Ohungarumlaut -85
+KPX Yacute Omacron -85
+KPX Yacute Oslash -85
+KPX Yacute Otilde -85
+KPX Yacute a -140
+KPX Yacute aacute -140
+KPX Yacute abreve -70
+KPX Yacute acircumflex -140
+KPX Yacute adieresis -140
+KPX Yacute agrave -140
+KPX Yacute amacron -70
+KPX Yacute aogonek -140
+KPX Yacute aring -140
+KPX Yacute atilde -70
+KPX Yacute colon -60
+KPX Yacute comma -140
+KPX Yacute e -140
+KPX Yacute eacute -140
+KPX Yacute ecaron -140
+KPX Yacute ecircumflex -140
+KPX Yacute edieresis -140
+KPX Yacute edotaccent -140
+KPX Yacute egrave -140
+KPX Yacute emacron -70
+KPX Yacute eogonek -140
+KPX Yacute hyphen -140
+KPX Yacute i -20
+KPX Yacute iacute -20
+KPX Yacute iogonek -20
+KPX Yacute o -140
+KPX Yacute oacute -140
+KPX Yacute ocircumflex -140
+KPX Yacute odieresis -140
+KPX Yacute ograve -140
+KPX Yacute ohungarumlaut -140
+KPX Yacute omacron -70
+KPX Yacute oslash -140
+KPX Yacute otilde -140
+KPX Yacute period -140
+KPX Yacute semicolon -60
+KPX Yacute u -110
+KPX Yacute uacute -110
+KPX Yacute ucircumflex -110
+KPX Yacute udieresis -110
+KPX Yacute ugrave -110
+KPX Yacute uhungarumlaut -110
+KPX Yacute umacron -110
+KPX Yacute uogonek -110
+KPX Yacute uring -110
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -85
+KPX Ydieresis Oacute -85
+KPX Ydieresis Ocircumflex -85
+KPX Ydieresis Odieresis -85
+KPX Ydieresis Ograve -85
+KPX Ydieresis Ohungarumlaut -85
+KPX Ydieresis Omacron -85
+KPX Ydieresis Oslash -85
+KPX Ydieresis Otilde -85
+KPX Ydieresis a -140
+KPX Ydieresis aacute -140
+KPX Ydieresis abreve -70
+KPX Ydieresis acircumflex -140
+KPX Ydieresis adieresis -140
+KPX Ydieresis agrave -140
+KPX Ydieresis amacron -70
+KPX Ydieresis aogonek -140
+KPX Ydieresis aring -140
+KPX Ydieresis atilde -70
+KPX Ydieresis colon -60
+KPX Ydieresis comma -140
+KPX Ydieresis e -140
+KPX Ydieresis eacute -140
+KPX Ydieresis ecaron -140
+KPX Ydieresis ecircumflex -140
+KPX Ydieresis edieresis -140
+KPX Ydieresis edotaccent -140
+KPX Ydieresis egrave -140
+KPX Ydieresis emacron -70
+KPX Ydieresis eogonek -140
+KPX Ydieresis hyphen -140
+KPX Ydieresis i -20
+KPX Ydieresis iacute -20
+KPX Ydieresis iogonek -20
+KPX Ydieresis o -140
+KPX Ydieresis oacute -140
+KPX Ydieresis ocircumflex -140
+KPX Ydieresis odieresis -140
+KPX Ydieresis ograve -140
+KPX Ydieresis ohungarumlaut -140
+KPX Ydieresis omacron -140
+KPX Ydieresis oslash -140
+KPX Ydieresis otilde -140
+KPX Ydieresis period -140
+KPX Ydieresis semicolon -60
+KPX Ydieresis u -110
+KPX Ydieresis uacute -110
+KPX Ydieresis ucircumflex -110
+KPX Ydieresis udieresis -110
+KPX Ydieresis ugrave -110
+KPX Ydieresis uhungarumlaut -110
+KPX Ydieresis umacron -110
+KPX Ydieresis uogonek -110
+KPX Ydieresis uring -110
+KPX a v -20
+KPX a w -20
+KPX a y -30
+KPX a yacute -30
+KPX a ydieresis -30
+KPX aacute v -20
+KPX aacute w -20
+KPX aacute y -30
+KPX aacute yacute -30
+KPX aacute ydieresis -30
+KPX abreve v -20
+KPX abreve w -20
+KPX abreve y -30
+KPX abreve yacute -30
+KPX abreve ydieresis -30
+KPX acircumflex v -20
+KPX acircumflex w -20
+KPX acircumflex y -30
+KPX acircumflex yacute -30
+KPX acircumflex ydieresis -30
+KPX adieresis v -20
+KPX adieresis w -20
+KPX adieresis y -30
+KPX adieresis yacute -30
+KPX adieresis ydieresis -30
+KPX agrave v -20
+KPX agrave w -20
+KPX agrave y -30
+KPX agrave yacute -30
+KPX agrave ydieresis -30
+KPX amacron v -20
+KPX amacron w -20
+KPX amacron y -30
+KPX amacron yacute -30
+KPX amacron ydieresis -30
+KPX aogonek v -20
+KPX aogonek w -20
+KPX aogonek y -30
+KPX aogonek yacute -30
+KPX aogonek ydieresis -30
+KPX aring v -20
+KPX aring w -20
+KPX aring y -30
+KPX aring yacute -30
+KPX aring ydieresis -30
+KPX atilde v -20
+KPX atilde w -20
+KPX atilde y -30
+KPX atilde yacute -30
+KPX atilde ydieresis -30
+KPX b b -10
+KPX b comma -40
+KPX b l -20
+KPX b lacute -20
+KPX b lcommaaccent -20
+KPX b lslash -20
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -20
+KPX b y -20
+KPX b yacute -20
+KPX b ydieresis -20
+KPX c comma -15
+KPX c k -20
+KPX c kcommaaccent -20
+KPX cacute comma -15
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX ccaron comma -15
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccedilla comma -15
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX colon space -50
+KPX comma quotedblright -100
+KPX comma quoteright -100
+KPX e comma -15
+KPX e period -15
+KPX e v -30
+KPX e w -20
+KPX e x -30
+KPX e y -20
+KPX e yacute -20
+KPX e ydieresis -20
+KPX eacute comma -15
+KPX eacute period -15
+KPX eacute v -30
+KPX eacute w -20
+KPX eacute x -30
+KPX eacute y -20
+KPX eacute yacute -20
+KPX eacute ydieresis -20
+KPX ecaron comma -15
+KPX ecaron period -15
+KPX ecaron v -30
+KPX ecaron w -20
+KPX ecaron x -30
+KPX ecaron y -20
+KPX ecaron yacute -20
+KPX ecaron ydieresis -20
+KPX ecircumflex comma -15
+KPX ecircumflex period -15
+KPX ecircumflex v -30
+KPX ecircumflex w -20
+KPX ecircumflex x -30
+KPX ecircumflex y -20
+KPX ecircumflex yacute -20
+KPX ecircumflex ydieresis -20
+KPX edieresis comma -15
+KPX edieresis period -15
+KPX edieresis v -30
+KPX edieresis w -20
+KPX edieresis x -30
+KPX edieresis y -20
+KPX edieresis yacute -20
+KPX edieresis ydieresis -20
+KPX edotaccent comma -15
+KPX edotaccent period -15
+KPX edotaccent v -30
+KPX edotaccent w -20
+KPX edotaccent x -30
+KPX edotaccent y -20
+KPX edotaccent yacute -20
+KPX edotaccent ydieresis -20
+KPX egrave comma -15
+KPX egrave period -15
+KPX egrave v -30
+KPX egrave w -20
+KPX egrave x -30
+KPX egrave y -20
+KPX egrave yacute -20
+KPX egrave ydieresis -20
+KPX emacron comma -15
+KPX emacron period -15
+KPX emacron v -30
+KPX emacron w -20
+KPX emacron x -30
+KPX emacron y -20
+KPX emacron yacute -20
+KPX emacron ydieresis -20
+KPX eogonek comma -15
+KPX eogonek period -15
+KPX eogonek v -30
+KPX eogonek w -20
+KPX eogonek x -30
+KPX eogonek y -20
+KPX eogonek yacute -20
+KPX eogonek ydieresis -20
+KPX f a -30
+KPX f aacute -30
+KPX f abreve -30
+KPX f acircumflex -30
+KPX f adieresis -30
+KPX f agrave -30
+KPX f amacron -30
+KPX f aogonek -30
+KPX f aring -30
+KPX f atilde -30
+KPX f comma -30
+KPX f dotlessi -28
+KPX f e -30
+KPX f eacute -30
+KPX f ecaron -30
+KPX f ecircumflex -30
+KPX f edieresis -30
+KPX f edotaccent -30
+KPX f egrave -30
+KPX f emacron -30
+KPX f eogonek -30
+KPX f o -30
+KPX f oacute -30
+KPX f ocircumflex -30
+KPX f odieresis -30
+KPX f ograve -30
+KPX f ohungarumlaut -30
+KPX f omacron -30
+KPX f oslash -30
+KPX f otilde -30
+KPX f period -30
+KPX f quotedblright 60
+KPX f quoteright 50
+KPX g r -10
+KPX g racute -10
+KPX g rcaron -10
+KPX g rcommaaccent -10
+KPX gbreve r -10
+KPX gbreve racute -10
+KPX gbreve rcaron -10
+KPX gbreve rcommaaccent -10
+KPX gcommaaccent r -10
+KPX gcommaaccent racute -10
+KPX gcommaaccent rcaron -10
+KPX gcommaaccent rcommaaccent -10
+KPX h y -30
+KPX h yacute -30
+KPX h ydieresis -30
+KPX k e -20
+KPX k eacute -20
+KPX k ecaron -20
+KPX k ecircumflex -20
+KPX k edieresis -20
+KPX k edotaccent -20
+KPX k egrave -20
+KPX k emacron -20
+KPX k eogonek -20
+KPX k o -20
+KPX k oacute -20
+KPX k ocircumflex -20
+KPX k odieresis -20
+KPX k ograve -20
+KPX k ohungarumlaut -20
+KPX k omacron -20
+KPX k oslash -20
+KPX k otilde -20
+KPX kcommaaccent e -20
+KPX kcommaaccent eacute -20
+KPX kcommaaccent ecaron -20
+KPX kcommaaccent ecircumflex -20
+KPX kcommaaccent edieresis -20
+KPX kcommaaccent edotaccent -20
+KPX kcommaaccent egrave -20
+KPX kcommaaccent emacron -20
+KPX kcommaaccent eogonek -20
+KPX kcommaaccent o -20
+KPX kcommaaccent oacute -20
+KPX kcommaaccent ocircumflex -20
+KPX kcommaaccent odieresis -20
+KPX kcommaaccent ograve -20
+KPX kcommaaccent ohungarumlaut -20
+KPX kcommaaccent omacron -20
+KPX kcommaaccent oslash -20
+KPX kcommaaccent otilde -20
+KPX m u -10
+KPX m uacute -10
+KPX m ucircumflex -10
+KPX m udieresis -10
+KPX m ugrave -10
+KPX m uhungarumlaut -10
+KPX m umacron -10
+KPX m uogonek -10
+KPX m uring -10
+KPX m y -15
+KPX m yacute -15
+KPX m ydieresis -15
+KPX n u -10
+KPX n uacute -10
+KPX n ucircumflex -10
+KPX n udieresis -10
+KPX n ugrave -10
+KPX n uhungarumlaut -10
+KPX n umacron -10
+KPX n uogonek -10
+KPX n uring -10
+KPX n v -20
+KPX n y -15
+KPX n yacute -15
+KPX n ydieresis -15
+KPX nacute u -10
+KPX nacute uacute -10
+KPX nacute ucircumflex -10
+KPX nacute udieresis -10
+KPX nacute ugrave -10
+KPX nacute uhungarumlaut -10
+KPX nacute umacron -10
+KPX nacute uogonek -10
+KPX nacute uring -10
+KPX nacute v -20
+KPX nacute y -15
+KPX nacute yacute -15
+KPX nacute ydieresis -15
+KPX ncaron u -10
+KPX ncaron uacute -10
+KPX ncaron ucircumflex -10
+KPX ncaron udieresis -10
+KPX ncaron ugrave -10
+KPX ncaron uhungarumlaut -10
+KPX ncaron umacron -10
+KPX ncaron uogonek -10
+KPX ncaron uring -10
+KPX ncaron v -20
+KPX ncaron y -15
+KPX ncaron yacute -15
+KPX ncaron ydieresis -15
+KPX ncommaaccent u -10
+KPX ncommaaccent uacute -10
+KPX ncommaaccent ucircumflex -10
+KPX ncommaaccent udieresis -10
+KPX ncommaaccent ugrave -10
+KPX ncommaaccent uhungarumlaut -10
+KPX ncommaaccent umacron -10
+KPX ncommaaccent uogonek -10
+KPX ncommaaccent uring -10
+KPX ncommaaccent v -20
+KPX ncommaaccent y -15
+KPX ncommaaccent yacute -15
+KPX ncommaaccent ydieresis -15
+KPX ntilde u -10
+KPX ntilde uacute -10
+KPX ntilde ucircumflex -10
+KPX ntilde udieresis -10
+KPX ntilde ugrave -10
+KPX ntilde uhungarumlaut -10
+KPX ntilde umacron -10
+KPX ntilde uogonek -10
+KPX ntilde uring -10
+KPX ntilde v -20
+KPX ntilde y -15
+KPX ntilde yacute -15
+KPX ntilde ydieresis -15
+KPX o comma -40
+KPX o period -40
+KPX o v -15
+KPX o w -15
+KPX o x -30
+KPX o y -30
+KPX o yacute -30
+KPX o ydieresis -30
+KPX oacute comma -40
+KPX oacute period -40
+KPX oacute v -15
+KPX oacute w -15
+KPX oacute x -30
+KPX oacute y -30
+KPX oacute yacute -30
+KPX oacute ydieresis -30
+KPX ocircumflex comma -40
+KPX ocircumflex period -40
+KPX ocircumflex v -15
+KPX ocircumflex w -15
+KPX ocircumflex x -30
+KPX ocircumflex y -30
+KPX ocircumflex yacute -30
+KPX ocircumflex ydieresis -30
+KPX odieresis comma -40
+KPX odieresis period -40
+KPX odieresis v -15
+KPX odieresis w -15
+KPX odieresis x -30
+KPX odieresis y -30
+KPX odieresis yacute -30
+KPX odieresis ydieresis -30
+KPX ograve comma -40
+KPX ograve period -40
+KPX ograve v -15
+KPX ograve w -15
+KPX ograve x -30
+KPX ograve y -30
+KPX ograve yacute -30
+KPX ograve ydieresis -30
+KPX ohungarumlaut comma -40
+KPX ohungarumlaut period -40
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -15
+KPX ohungarumlaut x -30
+KPX ohungarumlaut y -30
+KPX ohungarumlaut yacute -30
+KPX ohungarumlaut ydieresis -30
+KPX omacron comma -40
+KPX omacron period -40
+KPX omacron v -15
+KPX omacron w -15
+KPX omacron x -30
+KPX omacron y -30
+KPX omacron yacute -30
+KPX omacron ydieresis -30
+KPX oslash a -55
+KPX oslash aacute -55
+KPX oslash abreve -55
+KPX oslash acircumflex -55
+KPX oslash adieresis -55
+KPX oslash agrave -55
+KPX oslash amacron -55
+KPX oslash aogonek -55
+KPX oslash aring -55
+KPX oslash atilde -55
+KPX oslash b -55
+KPX oslash c -55
+KPX oslash cacute -55
+KPX oslash ccaron -55
+KPX oslash ccedilla -55
+KPX oslash comma -95
+KPX oslash d -55
+KPX oslash dcroat -55
+KPX oslash e -55
+KPX oslash eacute -55
+KPX oslash ecaron -55
+KPX oslash ecircumflex -55
+KPX oslash edieresis -55
+KPX oslash edotaccent -55
+KPX oslash egrave -55
+KPX oslash emacron -55
+KPX oslash eogonek -55
+KPX oslash f -55
+KPX oslash g -55
+KPX oslash gbreve -55
+KPX oslash gcommaaccent -55
+KPX oslash h -55
+KPX oslash i -55
+KPX oslash iacute -55
+KPX oslash icircumflex -55
+KPX oslash idieresis -55
+KPX oslash igrave -55
+KPX oslash imacron -55
+KPX oslash iogonek -55
+KPX oslash j -55
+KPX oslash k -55
+KPX oslash kcommaaccent -55
+KPX oslash l -55
+KPX oslash lacute -55
+KPX oslash lcommaaccent -55
+KPX oslash lslash -55
+KPX oslash m -55
+KPX oslash n -55
+KPX oslash nacute -55
+KPX oslash ncaron -55
+KPX oslash ncommaaccent -55
+KPX oslash ntilde -55
+KPX oslash o -55
+KPX oslash oacute -55
+KPX oslash ocircumflex -55
+KPX oslash odieresis -55
+KPX oslash ograve -55
+KPX oslash ohungarumlaut -55
+KPX oslash omacron -55
+KPX oslash oslash -55
+KPX oslash otilde -55
+KPX oslash p -55
+KPX oslash period -95
+KPX oslash q -55
+KPX oslash r -55
+KPX oslash racute -55
+KPX oslash rcaron -55
+KPX oslash rcommaaccent -55
+KPX oslash s -55
+KPX oslash sacute -55
+KPX oslash scaron -55
+KPX oslash scedilla -55
+KPX oslash scommaaccent -55
+KPX oslash t -55
+KPX oslash tcommaaccent -55
+KPX oslash u -55
+KPX oslash uacute -55
+KPX oslash ucircumflex -55
+KPX oslash udieresis -55
+KPX oslash ugrave -55
+KPX oslash uhungarumlaut -55
+KPX oslash umacron -55
+KPX oslash uogonek -55
+KPX oslash uring -55
+KPX oslash v -70
+KPX oslash w -70
+KPX oslash x -85
+KPX oslash y -70
+KPX oslash yacute -70
+KPX oslash ydieresis -70
+KPX oslash z -55
+KPX oslash zacute -55
+KPX oslash zcaron -55
+KPX oslash zdotaccent -55
+KPX otilde comma -40
+KPX otilde period -40
+KPX otilde v -15
+KPX otilde w -15
+KPX otilde x -30
+KPX otilde y -30
+KPX otilde yacute -30
+KPX otilde ydieresis -30
+KPX p comma -35
+KPX p period -35
+KPX p y -30
+KPX p yacute -30
+KPX p ydieresis -30
+KPX period quotedblright -100
+KPX period quoteright -100
+KPX period space -60
+KPX quotedblright space -40
+KPX quoteleft quoteleft -57
+KPX quoteright d -50
+KPX quoteright dcroat -50
+KPX quoteright quoteright -57
+KPX quoteright r -50
+KPX quoteright racute -50
+KPX quoteright rcaron -50
+KPX quoteright rcommaaccent -50
+KPX quoteright s -50
+KPX quoteright sacute -50
+KPX quoteright scaron -50
+KPX quoteright scedilla -50
+KPX quoteright scommaaccent -50
+KPX quoteright space -70
+KPX r a -10
+KPX r aacute -10
+KPX r abreve -10
+KPX r acircumflex -10
+KPX r adieresis -10
+KPX r agrave -10
+KPX r amacron -10
+KPX r aogonek -10
+KPX r aring -10
+KPX r atilde -10
+KPX r colon 30
+KPX r comma -50
+KPX r i 15
+KPX r iacute 15
+KPX r icircumflex 15
+KPX r idieresis 15
+KPX r igrave 15
+KPX r imacron 15
+KPX r iogonek 15
+KPX r k 15
+KPX r kcommaaccent 15
+KPX r l 15
+KPX r lacute 15
+KPX r lcommaaccent 15
+KPX r lslash 15
+KPX r m 25
+KPX r n 25
+KPX r nacute 25
+KPX r ncaron 25
+KPX r ncommaaccent 25
+KPX r ntilde 25
+KPX r p 30
+KPX r period -50
+KPX r semicolon 30
+KPX r t 40
+KPX r tcommaaccent 40
+KPX r u 15
+KPX r uacute 15
+KPX r ucircumflex 15
+KPX r udieresis 15
+KPX r ugrave 15
+KPX r uhungarumlaut 15
+KPX r umacron 15
+KPX r uogonek 15
+KPX r uring 15
+KPX r v 30
+KPX r y 30
+KPX r yacute 30
+KPX r ydieresis 30
+KPX racute a -10
+KPX racute aacute -10
+KPX racute abreve -10
+KPX racute acircumflex -10
+KPX racute adieresis -10
+KPX racute agrave -10
+KPX racute amacron -10
+KPX racute aogonek -10
+KPX racute aring -10
+KPX racute atilde -10
+KPX racute colon 30
+KPX racute comma -50
+KPX racute i 15
+KPX racute iacute 15
+KPX racute icircumflex 15
+KPX racute idieresis 15
+KPX racute igrave 15
+KPX racute imacron 15
+KPX racute iogonek 15
+KPX racute k 15
+KPX racute kcommaaccent 15
+KPX racute l 15
+KPX racute lacute 15
+KPX racute lcommaaccent 15
+KPX racute lslash 15
+KPX racute m 25
+KPX racute n 25
+KPX racute nacute 25
+KPX racute ncaron 25
+KPX racute ncommaaccent 25
+KPX racute ntilde 25
+KPX racute p 30
+KPX racute period -50
+KPX racute semicolon 30
+KPX racute t 40
+KPX racute tcommaaccent 40
+KPX racute u 15
+KPX racute uacute 15
+KPX racute ucircumflex 15
+KPX racute udieresis 15
+KPX racute ugrave 15
+KPX racute uhungarumlaut 15
+KPX racute umacron 15
+KPX racute uogonek 15
+KPX racute uring 15
+KPX racute v 30
+KPX racute y 30
+KPX racute yacute 30
+KPX racute ydieresis 30
+KPX rcaron a -10
+KPX rcaron aacute -10
+KPX rcaron abreve -10
+KPX rcaron acircumflex -10
+KPX rcaron adieresis -10
+KPX rcaron agrave -10
+KPX rcaron amacron -10
+KPX rcaron aogonek -10
+KPX rcaron aring -10
+KPX rcaron atilde -10
+KPX rcaron colon 30
+KPX rcaron comma -50
+KPX rcaron i 15
+KPX rcaron iacute 15
+KPX rcaron icircumflex 15
+KPX rcaron idieresis 15
+KPX rcaron igrave 15
+KPX rcaron imacron 15
+KPX rcaron iogonek 15
+KPX rcaron k 15
+KPX rcaron kcommaaccent 15
+KPX rcaron l 15
+KPX rcaron lacute 15
+KPX rcaron lcommaaccent 15
+KPX rcaron lslash 15
+KPX rcaron m 25
+KPX rcaron n 25
+KPX rcaron nacute 25
+KPX rcaron ncaron 25
+KPX rcaron ncommaaccent 25
+KPX rcaron ntilde 25
+KPX rcaron p 30
+KPX rcaron period -50
+KPX rcaron semicolon 30
+KPX rcaron t 40
+KPX rcaron tcommaaccent 40
+KPX rcaron u 15
+KPX rcaron uacute 15
+KPX rcaron ucircumflex 15
+KPX rcaron udieresis 15
+KPX rcaron ugrave 15
+KPX rcaron uhungarumlaut 15
+KPX rcaron umacron 15
+KPX rcaron uogonek 15
+KPX rcaron uring 15
+KPX rcaron v 30
+KPX rcaron y 30
+KPX rcaron yacute 30
+KPX rcaron ydieresis 30
+KPX rcommaaccent a -10
+KPX rcommaaccent aacute -10
+KPX rcommaaccent abreve -10
+KPX rcommaaccent acircumflex -10
+KPX rcommaaccent adieresis -10
+KPX rcommaaccent agrave -10
+KPX rcommaaccent amacron -10
+KPX rcommaaccent aogonek -10
+KPX rcommaaccent aring -10
+KPX rcommaaccent atilde -10
+KPX rcommaaccent colon 30
+KPX rcommaaccent comma -50
+KPX rcommaaccent i 15
+KPX rcommaaccent iacute 15
+KPX rcommaaccent icircumflex 15
+KPX rcommaaccent idieresis 15
+KPX rcommaaccent igrave 15
+KPX rcommaaccent imacron 15
+KPX rcommaaccent iogonek 15
+KPX rcommaaccent k 15
+KPX rcommaaccent kcommaaccent 15
+KPX rcommaaccent l 15
+KPX rcommaaccent lacute 15
+KPX rcommaaccent lcommaaccent 15
+KPX rcommaaccent lslash 15
+KPX rcommaaccent m 25
+KPX rcommaaccent n 25
+KPX rcommaaccent nacute 25
+KPX rcommaaccent ncaron 25
+KPX rcommaaccent ncommaaccent 25
+KPX rcommaaccent ntilde 25
+KPX rcommaaccent p 30
+KPX rcommaaccent period -50
+KPX rcommaaccent semicolon 30
+KPX rcommaaccent t 40
+KPX rcommaaccent tcommaaccent 40
+KPX rcommaaccent u 15
+KPX rcommaaccent uacute 15
+KPX rcommaaccent ucircumflex 15
+KPX rcommaaccent udieresis 15
+KPX rcommaaccent ugrave 15
+KPX rcommaaccent uhungarumlaut 15
+KPX rcommaaccent umacron 15
+KPX rcommaaccent uogonek 15
+KPX rcommaaccent uring 15
+KPX rcommaaccent v 30
+KPX rcommaaccent y 30
+KPX rcommaaccent yacute 30
+KPX rcommaaccent ydieresis 30
+KPX s comma -15
+KPX s period -15
+KPX s w -30
+KPX sacute comma -15
+KPX sacute period -15
+KPX sacute w -30
+KPX scaron comma -15
+KPX scaron period -15
+KPX scaron w -30
+KPX scedilla comma -15
+KPX scedilla period -15
+KPX scedilla w -30
+KPX scommaaccent comma -15
+KPX scommaaccent period -15
+KPX scommaaccent w -30
+KPX semicolon space -50
+KPX space T -50
+KPX space Tcaron -50
+KPX space Tcommaaccent -50
+KPX space V -50
+KPX space W -40
+KPX space Y -90
+KPX space Yacute -90
+KPX space Ydieresis -90
+KPX space quotedblleft -30
+KPX space quoteleft -60
+KPX v a -25
+KPX v aacute -25
+KPX v abreve -25
+KPX v acircumflex -25
+KPX v adieresis -25
+KPX v agrave -25
+KPX v amacron -25
+KPX v aogonek -25
+KPX v aring -25
+KPX v atilde -25
+KPX v comma -80
+KPX v e -25
+KPX v eacute -25
+KPX v ecaron -25
+KPX v ecircumflex -25
+KPX v edieresis -25
+KPX v edotaccent -25
+KPX v egrave -25
+KPX v emacron -25
+KPX v eogonek -25
+KPX v o -25
+KPX v oacute -25
+KPX v ocircumflex -25
+KPX v odieresis -25
+KPX v ograve -25
+KPX v ohungarumlaut -25
+KPX v omacron -25
+KPX v oslash -25
+KPX v otilde -25
+KPX v period -80
+KPX w a -15
+KPX w aacute -15
+KPX w abreve -15
+KPX w acircumflex -15
+KPX w adieresis -15
+KPX w agrave -15
+KPX w amacron -15
+KPX w aogonek -15
+KPX w aring -15
+KPX w atilde -15
+KPX w comma -60
+KPX w e -10
+KPX w eacute -10
+KPX w ecaron -10
+KPX w ecircumflex -10
+KPX w edieresis -10
+KPX w edotaccent -10
+KPX w egrave -10
+KPX w emacron -10
+KPX w eogonek -10
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -60
+KPX x e -30
+KPX x eacute -30
+KPX x ecaron -30
+KPX x ecircumflex -30
+KPX x edieresis -30
+KPX x edotaccent -30
+KPX x egrave -30
+KPX x emacron -30
+KPX x eogonek -30
+KPX y a -20
+KPX y aacute -20
+KPX y abreve -20
+KPX y acircumflex -20
+KPX y adieresis -20
+KPX y agrave -20
+KPX y amacron -20
+KPX y aogonek -20
+KPX y aring -20
+KPX y atilde -20
+KPX y comma -100
+KPX y e -20
+KPX y eacute -20
+KPX y ecaron -20
+KPX y ecircumflex -20
+KPX y edieresis -20
+KPX y edotaccent -20
+KPX y egrave -20
+KPX y emacron -20
+KPX y eogonek -20
+KPX y o -20
+KPX y oacute -20
+KPX y ocircumflex -20
+KPX y odieresis -20
+KPX y ograve -20
+KPX y ohungarumlaut -20
+KPX y omacron -20
+KPX y oslash -20
+KPX y otilde -20
+KPX y period -100
+KPX yacute a -20
+KPX yacute aacute -20
+KPX yacute abreve -20
+KPX yacute acircumflex -20
+KPX yacute adieresis -20
+KPX yacute agrave -20
+KPX yacute amacron -20
+KPX yacute aogonek -20
+KPX yacute aring -20
+KPX yacute atilde -20
+KPX yacute comma -100
+KPX yacute e -20
+KPX yacute eacute -20
+KPX yacute ecaron -20
+KPX yacute ecircumflex -20
+KPX yacute edieresis -20
+KPX yacute edotaccent -20
+KPX yacute egrave -20
+KPX yacute emacron -20
+KPX yacute eogonek -20
+KPX yacute o -20
+KPX yacute oacute -20
+KPX yacute ocircumflex -20
+KPX yacute odieresis -20
+KPX yacute ograve -20
+KPX yacute ohungarumlaut -20
+KPX yacute omacron -20
+KPX yacute oslash -20
+KPX yacute otilde -20
+KPX yacute period -100
+KPX ydieresis a -20
+KPX ydieresis aacute -20
+KPX ydieresis abreve -20
+KPX ydieresis acircumflex -20
+KPX ydieresis adieresis -20
+KPX ydieresis agrave -20
+KPX ydieresis amacron -20
+KPX ydieresis aogonek -20
+KPX ydieresis aring -20
+KPX ydieresis atilde -20
+KPX ydieresis comma -100
+KPX ydieresis e -20
+KPX ydieresis eacute -20
+KPX ydieresis ecaron -20
+KPX ydieresis ecircumflex -20
+KPX ydieresis edieresis -20
+KPX ydieresis edotaccent -20
+KPX ydieresis egrave -20
+KPX ydieresis emacron -20
+KPX ydieresis eogonek -20
+KPX ydieresis o -20
+KPX ydieresis oacute -20
+KPX ydieresis ocircumflex -20
+KPX ydieresis odieresis -20
+KPX ydieresis ograve -20
+KPX ydieresis ohungarumlaut -20
+KPX ydieresis omacron -20
+KPX ydieresis oslash -20
+KPX ydieresis otilde -20
+KPX ydieresis period -100
+KPX z e -15
+KPX z eacute -15
+KPX z ecaron -15
+KPX z ecircumflex -15
+KPX z edieresis -15
+KPX z edotaccent -15
+KPX z egrave -15
+KPX z emacron -15
+KPX z eogonek -15
+KPX z o -15
+KPX z oacute -15
+KPX z ocircumflex -15
+KPX z odieresis -15
+KPX z ograve -15
+KPX z ohungarumlaut -15
+KPX z omacron -15
+KPX z oslash -15
+KPX z otilde -15
+KPX zacute e -15
+KPX zacute eacute -15
+KPX zacute ecaron -15
+KPX zacute ecircumflex -15
+KPX zacute edieresis -15
+KPX zacute edotaccent -15
+KPX zacute egrave -15
+KPX zacute emacron -15
+KPX zacute eogonek -15
+KPX zacute o -15
+KPX zacute oacute -15
+KPX zacute ocircumflex -15
+KPX zacute odieresis -15
+KPX zacute ograve -15
+KPX zacute ohungarumlaut -15
+KPX zacute omacron -15
+KPX zacute oslash -15
+KPX zacute otilde -15
+KPX zcaron e -15
+KPX zcaron eacute -15
+KPX zcaron ecaron -15
+KPX zcaron ecircumflex -15
+KPX zcaron edieresis -15
+KPX zcaron edotaccent -15
+KPX zcaron egrave -15
+KPX zcaron emacron -15
+KPX zcaron eogonek -15
+KPX zcaron o -15
+KPX zcaron oacute -15
+KPX zcaron ocircumflex -15
+KPX zcaron odieresis -15
+KPX zcaron ograve -15
+KPX zcaron ohungarumlaut -15
+KPX zcaron omacron -15
+KPX zcaron oslash -15
+KPX zcaron otilde -15
+KPX zdotaccent e -15
+KPX zdotaccent eacute -15
+KPX zdotaccent ecaron -15
+KPX zdotaccent ecircumflex -15
+KPX zdotaccent edieresis -15
+KPX zdotaccent edotaccent -15
+KPX zdotaccent egrave -15
+KPX zdotaccent emacron -15
+KPX zdotaccent eogonek -15
+KPX zdotaccent o -15
+KPX zdotaccent oacute -15
+KPX zdotaccent ocircumflex -15
+KPX zdotaccent odieresis -15
+KPX zdotaccent ograve -15
+KPX zdotaccent ohungarumlaut -15
+KPX zdotaccent omacron -15
+KPX zdotaccent oslash -15
+KPX zdotaccent otilde -15
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Symbol.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Symbol.afm
new file mode 100644
index 0000000..524cfb6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Symbol.afm
@@ -0,0 +1,213 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
+Comment Creation Date: Thu May 1 15:12:25 1997
+Comment UniqueID 43064
+Comment VMusage 30820 39997
+FontName Symbol
+FullName Symbol
+FamilyName Symbol
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet Special
+FontBBox -180 -293 1090 1010
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.008
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1997 Adobe Systems Incorporated. All rights reserved.
+EncodingScheme FontSpecific
+StdHW 92
+StdVW 85
+StartCharMetrics 190
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 128 -17 240 672 ;
+C 34 ; WX 713 ; N universal ; B 31 0 681 705 ;
+C 35 ; WX 500 ; N numbersign ; B 20 -16 481 673 ;
+C 36 ; WX 549 ; N existential ; B 25 0 478 707 ;
+C 37 ; WX 833 ; N percent ; B 63 -36 771 655 ;
+C 38 ; WX 778 ; N ampersand ; B 41 -18 750 661 ;
+C 39 ; WX 439 ; N suchthat ; B 48 -17 414 500 ;
+C 40 ; WX 333 ; N parenleft ; B 53 -191 300 673 ;
+C 41 ; WX 333 ; N parenright ; B 30 -191 277 673 ;
+C 42 ; WX 500 ; N asteriskmath ; B 65 134 427 551 ;
+C 43 ; WX 549 ; N plus ; B 10 0 539 533 ;
+C 44 ; WX 250 ; N comma ; B 56 -152 194 104 ;
+C 45 ; WX 549 ; N minus ; B 11 233 535 288 ;
+C 46 ; WX 250 ; N period ; B 69 -17 181 95 ;
+C 47 ; WX 278 ; N slash ; B 0 -18 254 646 ;
+C 48 ; WX 500 ; N zero ; B 24 -14 476 685 ;
+C 49 ; WX 500 ; N one ; B 117 0 390 673 ;
+C 50 ; WX 500 ; N two ; B 25 0 475 685 ;
+C 51 ; WX 500 ; N three ; B 43 -14 435 685 ;
+C 52 ; WX 500 ; N four ; B 15 0 469 685 ;
+C 53 ; WX 500 ; N five ; B 32 -14 445 690 ;
+C 54 ; WX 500 ; N six ; B 34 -14 468 685 ;
+C 55 ; WX 500 ; N seven ; B 24 -16 448 673 ;
+C 56 ; WX 500 ; N eight ; B 56 -14 445 685 ;
+C 57 ; WX 500 ; N nine ; B 30 -18 459 685 ;
+C 58 ; WX 278 ; N colon ; B 81 -17 193 460 ;
+C 59 ; WX 278 ; N semicolon ; B 83 -152 221 460 ;
+C 60 ; WX 549 ; N less ; B 26 0 523 522 ;
+C 61 ; WX 549 ; N equal ; B 11 141 537 390 ;
+C 62 ; WX 549 ; N greater ; B 26 0 523 522 ;
+C 63 ; WX 444 ; N question ; B 70 -17 412 686 ;
+C 64 ; WX 549 ; N congruent ; B 11 0 537 475 ;
+C 65 ; WX 722 ; N Alpha ; B 4 0 684 673 ;
+C 66 ; WX 667 ; N Beta ; B 29 0 592 673 ;
+C 67 ; WX 722 ; N Chi ; B -9 0 704 673 ;
+C 68 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 69 ; WX 611 ; N Epsilon ; B 32 0 617 673 ;
+C 70 ; WX 763 ; N Phi ; B 26 0 741 673 ;
+C 71 ; WX 603 ; N Gamma ; B 24 0 609 673 ;
+C 72 ; WX 722 ; N Eta ; B 39 0 729 673 ;
+C 73 ; WX 333 ; N Iota ; B 32 0 316 673 ;
+C 74 ; WX 631 ; N theta1 ; B 18 -18 623 689 ;
+C 75 ; WX 722 ; N Kappa ; B 35 0 722 673 ;
+C 76 ; WX 686 ; N Lambda ; B 6 0 680 688 ;
+C 77 ; WX 889 ; N Mu ; B 28 0 887 673 ;
+C 78 ; WX 722 ; N Nu ; B 29 -8 720 673 ;
+C 79 ; WX 722 ; N Omicron ; B 41 -17 715 685 ;
+C 80 ; WX 768 ; N Pi ; B 25 0 745 673 ;
+C 81 ; WX 741 ; N Theta ; B 41 -17 715 685 ;
+C 82 ; WX 556 ; N Rho ; B 28 0 563 673 ;
+C 83 ; WX 592 ; N Sigma ; B 5 0 589 673 ;
+C 84 ; WX 611 ; N Tau ; B 33 0 607 673 ;
+C 85 ; WX 690 ; N Upsilon ; B -8 0 694 673 ;
+C 86 ; WX 439 ; N sigma1 ; B 40 -233 436 500 ;
+C 87 ; WX 768 ; N Omega ; B 34 0 736 688 ;
+C 88 ; WX 645 ; N Xi ; B 40 0 599 673 ;
+C 89 ; WX 795 ; N Psi ; B 15 0 781 684 ;
+C 90 ; WX 611 ; N Zeta ; B 44 0 636 673 ;
+C 91 ; WX 333 ; N bracketleft ; B 86 -155 299 674 ;
+C 92 ; WX 863 ; N therefore ; B 163 0 701 487 ;
+C 93 ; WX 333 ; N bracketright ; B 33 -155 246 674 ;
+C 94 ; WX 658 ; N perpendicular ; B 15 0 652 674 ;
+C 95 ; WX 500 ; N underscore ; B -2 -125 502 -75 ;
+C 96 ; WX 500 ; N radicalex ; B 480 881 1090 917 ;
+C 97 ; WX 631 ; N alpha ; B 41 -18 622 500 ;
+C 98 ; WX 549 ; N beta ; B 61 -223 515 741 ;
+C 99 ; WX 549 ; N chi ; B 12 -231 522 499 ;
+C 100 ; WX 494 ; N delta ; B 40 -19 481 740 ;
+C 101 ; WX 439 ; N epsilon ; B 22 -19 427 502 ;
+C 102 ; WX 521 ; N phi ; B 28 -224 492 673 ;
+C 103 ; WX 411 ; N gamma ; B 5 -225 484 499 ;
+C 104 ; WX 603 ; N eta ; B 0 -202 527 514 ;
+C 105 ; WX 329 ; N iota ; B 0 -17 301 503 ;
+C 106 ; WX 603 ; N phi1 ; B 36 -224 587 499 ;
+C 107 ; WX 549 ; N kappa ; B 33 0 558 501 ;
+C 108 ; WX 549 ; N lambda ; B 24 -17 548 739 ;
+C 109 ; WX 576 ; N mu ; B 33 -223 567 500 ;
+C 110 ; WX 521 ; N nu ; B -9 -16 475 507 ;
+C 111 ; WX 549 ; N omicron ; B 35 -19 501 499 ;
+C 112 ; WX 549 ; N pi ; B 10 -19 530 487 ;
+C 113 ; WX 521 ; N theta ; B 43 -17 485 690 ;
+C 114 ; WX 549 ; N rho ; B 50 -230 490 499 ;
+C 115 ; WX 603 ; N sigma ; B 30 -21 588 500 ;
+C 116 ; WX 439 ; N tau ; B 10 -19 418 500 ;
+C 117 ; WX 576 ; N upsilon ; B 7 -18 535 507 ;
+C 118 ; WX 713 ; N omega1 ; B 12 -18 671 583 ;
+C 119 ; WX 686 ; N omega ; B 42 -17 684 500 ;
+C 120 ; WX 493 ; N xi ; B 27 -224 469 766 ;
+C 121 ; WX 686 ; N psi ; B 12 -228 701 500 ;
+C 122 ; WX 494 ; N zeta ; B 60 -225 467 756 ;
+C 123 ; WX 480 ; N braceleft ; B 58 -183 397 673 ;
+C 124 ; WX 200 ; N bar ; B 65 -293 135 707 ;
+C 125 ; WX 480 ; N braceright ; B 79 -183 418 673 ;
+C 126 ; WX 549 ; N similar ; B 17 203 529 307 ;
+C 160 ; WX 750 ; N Euro ; B 20 -12 714 685 ;
+C 161 ; WX 620 ; N Upsilon1 ; B -2 0 610 685 ;
+C 162 ; WX 247 ; N minute ; B 27 459 228 735 ;
+C 163 ; WX 549 ; N lessequal ; B 29 0 526 639 ;
+C 164 ; WX 167 ; N fraction ; B -180 -12 340 677 ;
+C 165 ; WX 713 ; N infinity ; B 26 124 688 404 ;
+C 166 ; WX 500 ; N florin ; B 2 -193 494 686 ;
+C 167 ; WX 753 ; N club ; B 86 -26 660 533 ;
+C 168 ; WX 753 ; N diamond ; B 142 -36 600 550 ;
+C 169 ; WX 753 ; N heart ; B 117 -33 631 532 ;
+C 170 ; WX 753 ; N spade ; B 113 -36 629 548 ;
+C 171 ; WX 1042 ; N arrowboth ; B 24 -15 1024 511 ;
+C 172 ; WX 987 ; N arrowleft ; B 32 -15 942 511 ;
+C 173 ; WX 603 ; N arrowup ; B 45 0 571 910 ;
+C 174 ; WX 987 ; N arrowright ; B 49 -15 959 511 ;
+C 175 ; WX 603 ; N arrowdown ; B 45 -22 571 888 ;
+C 176 ; WX 400 ; N degree ; B 50 385 350 685 ;
+C 177 ; WX 549 ; N plusminus ; B 10 0 539 645 ;
+C 178 ; WX 411 ; N second ; B 20 459 413 737 ;
+C 179 ; WX 549 ; N greaterequal ; B 29 0 526 639 ;
+C 180 ; WX 549 ; N multiply ; B 17 8 533 524 ;
+C 181 ; WX 713 ; N proportional ; B 27 123 639 404 ;
+C 182 ; WX 494 ; N partialdiff ; B 26 -20 462 746 ;
+C 183 ; WX 460 ; N bullet ; B 50 113 410 473 ;
+C 184 ; WX 549 ; N divide ; B 10 71 536 456 ;
+C 185 ; WX 549 ; N notequal ; B 15 -25 540 549 ;
+C 186 ; WX 549 ; N equivalence ; B 14 82 538 443 ;
+C 187 ; WX 549 ; N approxequal ; B 14 135 527 394 ;
+C 188 ; WX 1000 ; N ellipsis ; B 111 -17 889 95 ;
+C 189 ; WX 603 ; N arrowvertex ; B 280 -120 336 1010 ;
+C 190 ; WX 1000 ; N arrowhorizex ; B -60 220 1050 276 ;
+C 191 ; WX 658 ; N carriagereturn ; B 15 -16 602 629 ;
+C 192 ; WX 823 ; N aleph ; B 175 -18 661 658 ;
+C 193 ; WX 686 ; N Ifraktur ; B 10 -53 578 740 ;
+C 194 ; WX 795 ; N Rfraktur ; B 26 -15 759 734 ;
+C 195 ; WX 987 ; N weierstrass ; B 159 -211 870 573 ;
+C 196 ; WX 768 ; N circlemultiply ; B 43 -17 733 673 ;
+C 197 ; WX 768 ; N circleplus ; B 43 -15 733 675 ;
+C 198 ; WX 823 ; N emptyset ; B 39 -24 781 719 ;
+C 199 ; WX 768 ; N intersection ; B 40 0 732 509 ;
+C 200 ; WX 768 ; N union ; B 40 -17 732 492 ;
+C 201 ; WX 713 ; N propersuperset ; B 20 0 673 470 ;
+C 202 ; WX 713 ; N reflexsuperset ; B 20 -125 673 470 ;
+C 203 ; WX 713 ; N notsubset ; B 36 -70 690 540 ;
+C 204 ; WX 713 ; N propersubset ; B 37 0 690 470 ;
+C 205 ; WX 713 ; N reflexsubset ; B 37 -125 690 470 ;
+C 206 ; WX 713 ; N element ; B 45 0 505 468 ;
+C 207 ; WX 713 ; N notelement ; B 45 -58 505 555 ;
+C 208 ; WX 768 ; N angle ; B 26 0 738 673 ;
+C 209 ; WX 713 ; N gradient ; B 36 -19 681 718 ;
+C 210 ; WX 790 ; N registerserif ; B 50 -17 740 673 ;
+C 211 ; WX 790 ; N copyrightserif ; B 51 -15 741 675 ;
+C 212 ; WX 890 ; N trademarkserif ; B 18 293 855 673 ;
+C 213 ; WX 823 ; N product ; B 25 -101 803 751 ;
+C 214 ; WX 549 ; N radical ; B 10 -38 515 917 ;
+C 215 ; WX 250 ; N dotmath ; B 69 210 169 310 ;
+C 216 ; WX 713 ; N logicalnot ; B 15 0 680 288 ;
+C 217 ; WX 603 ; N logicaland ; B 23 0 583 454 ;
+C 218 ; WX 603 ; N logicalor ; B 30 0 578 477 ;
+C 219 ; WX 1042 ; N arrowdblboth ; B 27 -20 1023 510 ;
+C 220 ; WX 987 ; N arrowdblleft ; B 30 -15 939 513 ;
+C 221 ; WX 603 ; N arrowdblup ; B 39 2 567 911 ;
+C 222 ; WX 987 ; N arrowdblright ; B 45 -20 954 508 ;
+C 223 ; WX 603 ; N arrowdbldown ; B 44 -19 572 890 ;
+C 224 ; WX 494 ; N lozenge ; B 18 0 466 745 ;
+C 225 ; WX 329 ; N angleleft ; B 25 -198 306 746 ;
+C 226 ; WX 790 ; N registersans ; B 50 -20 740 670 ;
+C 227 ; WX 790 ; N copyrightsans ; B 49 -15 739 675 ;
+C 228 ; WX 786 ; N trademarksans ; B 5 293 725 673 ;
+C 229 ; WX 713 ; N summation ; B 14 -108 695 752 ;
+C 230 ; WX 384 ; N parenlefttp ; B 24 -293 436 926 ;
+C 231 ; WX 384 ; N parenleftex ; B 24 -85 108 925 ;
+C 232 ; WX 384 ; N parenleftbt ; B 24 -293 436 926 ;
+C 233 ; WX 384 ; N bracketlefttp ; B 0 -80 349 926 ;
+C 234 ; WX 384 ; N bracketleftex ; B 0 -79 77 925 ;
+C 235 ; WX 384 ; N bracketleftbt ; B 0 -80 349 926 ;
+C 236 ; WX 494 ; N bracelefttp ; B 209 -85 445 925 ;
+C 237 ; WX 494 ; N braceleftmid ; B 20 -85 284 935 ;
+C 238 ; WX 494 ; N braceleftbt ; B 209 -75 445 935 ;
+C 239 ; WX 494 ; N braceex ; B 209 -85 284 935 ;
+C 241 ; WX 329 ; N angleright ; B 21 -198 302 746 ;
+C 242 ; WX 274 ; N integral ; B 2 -107 291 916 ;
+C 243 ; WX 686 ; N integraltp ; B 308 -88 675 920 ;
+C 244 ; WX 686 ; N integralex ; B 308 -88 378 975 ;
+C 245 ; WX 686 ; N integralbt ; B 11 -87 378 921 ;
+C 246 ; WX 384 ; N parenrighttp ; B 54 -293 466 926 ;
+C 247 ; WX 384 ; N parenrightex ; B 382 -85 466 925 ;
+C 248 ; WX 384 ; N parenrightbt ; B 54 -293 466 926 ;
+C 249 ; WX 384 ; N bracketrighttp ; B 22 -80 371 926 ;
+C 250 ; WX 384 ; N bracketrightex ; B 294 -79 371 925 ;
+C 251 ; WX 384 ; N bracketrightbt ; B 22 -80 371 926 ;
+C 252 ; WX 494 ; N bracerighttp ; B 48 -85 284 925 ;
+C 253 ; WX 494 ; N bracerightmid ; B 209 -85 473 935 ;
+C 254 ; WX 494 ; N bracerightbt ; B 48 -75 284 935 ;
+C -1 ; WX 790 ; N apple ; B 56 -3 733 808 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Bold.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Bold.afm
new file mode 100644
index 0000000..86a3421
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Bold.afm
@@ -0,0 +1,2590 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:52:56 1997
+Comment UniqueID 43065
+Comment VMusage 41636 52661
+FontName Times-Bold
+FullName Times Bold
+FamilyName Times
+Weight Bold
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -168 -218 1000 935
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 676
+XHeight 461
+Ascender 683
+Descender -217
+StdHW 44
+StdVW 139
+StartCharMetrics 317
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 81 -13 251 691 ;
+C 34 ; WX 555 ; N quotedbl ; B 83 404 472 691 ;
+C 35 ; WX 500 ; N numbersign ; B 4 0 496 700 ;
+C 36 ; WX 500 ; N dollar ; B 29 -99 472 750 ;
+C 37 ; WX 1000 ; N percent ; B 124 -14 877 692 ;
+C 38 ; WX 833 ; N ampersand ; B 62 -16 787 691 ;
+C 146 ; WX 333 ; N quoteright ; B 79 356 263 691 ;
+C 40 ; WX 333 ; N parenleft ; B 46 -168 306 694 ;
+C 41 ; WX 333 ; N parenright ; B 27 -168 287 694 ;
+C 42 ; WX 500 ; N asterisk ; B 56 255 447 691 ;
+C 43 ; WX 570 ; N plus ; B 33 0 537 506 ;
+C 44 ; WX 250 ; N comma ; B 39 -180 223 155 ;
+C 45 ; WX 333 ; N hyphen ; B 44 171 287 287 ;
+C 173 ; WX 333 ; N hyphen ; B 44 171 287 287 ;
+C 46 ; WX 250 ; N period ; B 41 -13 210 156 ;
+C 47 ; WX 278 ; N slash ; B -24 -19 302 691 ;
+C 48 ; WX 500 ; N zero ; B 24 -13 476 688 ;
+C 49 ; WX 500 ; N one ; B 65 0 442 688 ;
+C 50 ; WX 500 ; N two ; B 17 0 478 688 ;
+C 51 ; WX 500 ; N three ; B 16 -14 468 688 ;
+C 52 ; WX 500 ; N four ; B 19 0 475 688 ;
+C 53 ; WX 500 ; N five ; B 22 -8 470 676 ;
+C 54 ; WX 500 ; N six ; B 28 -13 475 688 ;
+C 55 ; WX 500 ; N seven ; B 17 0 477 676 ;
+C 56 ; WX 500 ; N eight ; B 28 -13 472 688 ;
+C 57 ; WX 500 ; N nine ; B 26 -13 473 688 ;
+C 58 ; WX 333 ; N colon ; B 82 -13 251 472 ;
+C 59 ; WX 333 ; N semicolon ; B 82 -180 266 472 ;
+C 60 ; WX 570 ; N less ; B 31 -8 539 514 ;
+C 61 ; WX 570 ; N equal ; B 33 107 537 399 ;
+C 62 ; WX 570 ; N greater ; B 31 -8 539 514 ;
+C 63 ; WX 500 ; N question ; B 57 -13 445 689 ;
+C 64 ; WX 930 ; N at ; B 108 -19 822 691 ;
+C 65 ; WX 722 ; N A ; B 9 0 689 690 ;
+C 66 ; WX 667 ; N B ; B 16 0 619 676 ;
+C 67 ; WX 722 ; N C ; B 49 -19 687 691 ;
+C 68 ; WX 722 ; N D ; B 14 0 690 676 ;
+C 69 ; WX 667 ; N E ; B 16 0 641 676 ;
+C 70 ; WX 611 ; N F ; B 16 0 583 676 ;
+C 71 ; WX 778 ; N G ; B 37 -19 755 691 ;
+C 72 ; WX 778 ; N H ; B 21 0 759 676 ;
+C 73 ; WX 389 ; N I ; B 20 0 370 676 ;
+C 74 ; WX 500 ; N J ; B 3 -96 479 676 ;
+C 75 ; WX 778 ; N K ; B 30 0 769 676 ;
+C 76 ; WX 667 ; N L ; B 19 0 638 676 ;
+C 77 ; WX 944 ; N M ; B 14 0 921 676 ;
+C 78 ; WX 722 ; N N ; B 16 -18 701 676 ;
+C 79 ; WX 778 ; N O ; B 35 -19 743 691 ;
+C 80 ; WX 611 ; N P ; B 16 0 600 676 ;
+C 81 ; WX 778 ; N Q ; B 35 -176 743 691 ;
+C 82 ; WX 722 ; N R ; B 26 0 715 676 ;
+C 83 ; WX 556 ; N S ; B 35 -19 513 692 ;
+C 84 ; WX 667 ; N T ; B 31 0 636 676 ;
+C 85 ; WX 722 ; N U ; B 16 -19 701 676 ;
+C 86 ; WX 722 ; N V ; B 16 -18 701 676 ;
+C 87 ; WX 1000 ; N W ; B 19 -15 981 676 ;
+C 88 ; WX 722 ; N X ; B 16 0 699 676 ;
+C 89 ; WX 722 ; N Y ; B 15 0 699 676 ;
+C 90 ; WX 667 ; N Z ; B 28 0 634 676 ;
+C 91 ; WX 333 ; N bracketleft ; B 67 -149 301 678 ;
+C 92 ; WX 278 ; N backslash ; B -25 -19 303 691 ;
+C 93 ; WX 333 ; N bracketright ; B 32 -149 266 678 ;
+C 94 ; WX 581 ; N asciicircum ; B 73 311 509 676 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 145 ; WX 333 ; N quoteleft ; B 70 356 254 691 ;
+C 97 ; WX 500 ; N a ; B 25 -14 488 473 ;
+C 98 ; WX 556 ; N b ; B 17 -14 521 676 ;
+C 99 ; WX 444 ; N c ; B 25 -14 430 473 ;
+C 100 ; WX 556 ; N d ; B 25 -14 534 676 ;
+C 101 ; WX 444 ; N e ; B 25 -14 426 473 ;
+C 102 ; WX 333 ; N f ; B 14 0 389 691 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -206 483 473 ;
+C 104 ; WX 556 ; N h ; B 16 0 534 676 ;
+C 105 ; WX 278 ; N i ; B 16 0 255 691 ;
+C 106 ; WX 333 ; N j ; B -57 -203 263 691 ;
+C 107 ; WX 556 ; N k ; B 22 0 543 676 ;
+C 108 ; WX 278 ; N l ; B 16 0 255 676 ;
+C 109 ; WX 833 ; N m ; B 16 0 814 473 ;
+C 110 ; WX 556 ; N n ; B 21 0 539 473 ;
+C 111 ; WX 500 ; N o ; B 25 -14 476 473 ;
+C 112 ; WX 556 ; N p ; B 19 -205 524 473 ;
+C 113 ; WX 556 ; N q ; B 34 -205 536 473 ;
+C 114 ; WX 444 ; N r ; B 29 0 434 473 ;
+C 115 ; WX 389 ; N s ; B 25 -14 361 473 ;
+C 116 ; WX 333 ; N t ; B 20 -12 332 630 ;
+C 117 ; WX 556 ; N u ; B 16 -14 537 461 ;
+C 118 ; WX 500 ; N v ; B 21 -14 485 461 ;
+C 119 ; WX 722 ; N w ; B 23 -14 707 461 ;
+C 120 ; WX 500 ; N x ; B 12 0 484 461 ;
+C 121 ; WX 500 ; N y ; B 16 -205 480 461 ;
+C 122 ; WX 444 ; N z ; B 21 0 420 461 ;
+C 123 ; WX 394 ; N braceleft ; B 22 -175 340 698 ;
+C 124 ; WX 220 ; N bar ; B 66 -218 154 782 ;
+C 125 ; WX 394 ; N braceright ; B 54 -175 372 698 ;
+C 126 ; WX 520 ; N asciitilde ; B 29 173 491 333 ;
+C 161 ; WX 333 ; N exclamdown ; B 82 -203 252 501 ;
+C 162 ; WX 500 ; N cent ; B 53 -140 458 588 ;
+C 163 ; WX 500 ; N sterling ; B 21 -14 477 684 ;
+C -1 ; WX 167 ; N fraction ; B -168 -12 329 688 ;
+C 165 ; WX 500 ; N yen ; B -64 0 547 676 ;
+C 131 ; WX 500 ; N florin ; B 0 -155 498 706 ;
+C 167 ; WX 500 ; N section ; B 57 -132 443 691 ;
+C 164 ; WX 500 ; N currency ; B -26 61 526 613 ;
+C 39 ; WX 278 ; N quotesingle ; B 75 404 204 691 ;
+C 147 ; WX 500 ; N quotedblleft ; B 32 356 486 691 ;
+C 171 ; WX 500 ; N guillemotleft ; B 23 36 473 415 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 51 36 305 415 ;
+C 155 ; WX 333 ; N guilsinglright ; B 28 36 282 415 ;
+C -1 ; WX 556 ; N fi ; B 14 0 536 691 ;
+C -1 ; WX 556 ; N fl ; B 14 0 536 691 ;
+C 150 ; WX 500 ; N endash ; B 0 181 500 271 ;
+C 134 ; WX 500 ; N dagger ; B 47 -134 453 691 ;
+C 135 ; WX 500 ; N daggerdbl ; B 45 -132 456 691 ;
+C 183 ; WX 250 ; N periodcentered ; B 41 248 210 417 ;
+C 182 ; WX 540 ; N paragraph ; B 0 -186 519 676 ;
+C 149 ; WX 350 ; N bullet ; B 35 198 315 478 ;
+C 130 ; WX 333 ; N quotesinglbase ; B 79 -180 263 155 ;
+C 132 ; WX 500 ; N quotedblbase ; B 14 -180 468 155 ;
+C 148 ; WX 500 ; N quotedblright ; B 14 356 468 691 ;
+C 187 ; WX 500 ; N guillemotright ; B 27 36 477 415 ;
+C 133 ; WX 1000 ; N ellipsis ; B 82 -13 917 156 ;
+C 137 ; WX 1000 ; N perthousand ; B 7 -29 995 706 ;
+C 191 ; WX 500 ; N questiondown ; B 55 -201 443 501 ;
+C 96 ; WX 333 ; N grave ; B 8 528 246 713 ;
+C 180 ; WX 333 ; N acute ; B 86 528 324 713 ;
+C 136 ; WX 333 ; N circumflex ; B -2 528 335 704 ;
+C 152 ; WX 333 ; N tilde ; B -16 547 349 674 ;
+C 175 ; WX 333 ; N macron ; B 1 565 331 637 ;
+C -1 ; WX 333 ; N breve ; B 15 528 318 691 ;
+C -1 ; WX 333 ; N dotaccent ; B 103 536 258 691 ;
+C 168 ; WX 333 ; N dieresis ; B -2 537 335 667 ;
+C -1 ; WX 333 ; N ring ; B 60 527 273 740 ;
+C 184 ; WX 333 ; N cedilla ; B 68 -218 294 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B -13 528 425 713 ;
+C -1 ; WX 333 ; N ogonek ; B 90 -193 319 24 ;
+C -1 ; WX 333 ; N caron ; B -2 528 335 704 ;
+C 151 ; WX 1000 ; N emdash ; B 0 181 1000 271 ;
+C 198 ; WX 1000 ; N AE ; B 4 0 951 676 ;
+C 170 ; WX 300 ; N ordfeminine ; B -1 397 301 688 ;
+C -1 ; WX 667 ; N Lslash ; B 19 0 638 676 ;
+C 216 ; WX 778 ; N Oslash ; B 35 -74 743 737 ;
+C 140 ; WX 1000 ; N OE ; B 22 -5 981 684 ;
+C 186 ; WX 330 ; N ordmasculine ; B 18 397 312 688 ;
+C 230 ; WX 722 ; N ae ; B 33 -14 693 473 ;
+C -1 ; WX 278 ; N dotlessi ; B 16 0 255 461 ;
+C -1 ; WX 278 ; N lslash ; B -22 0 303 676 ;
+C 248 ; WX 500 ; N oslash ; B 25 -92 476 549 ;
+C 156 ; WX 722 ; N oe ; B 22 -14 696 473 ;
+C 223 ; WX 556 ; N germandbls ; B 19 -12 517 691 ;
+C 207 ; WX 389 ; N Idieresis ; B 20 0 370 877 ;
+C 233 ; WX 444 ; N eacute ; B 25 -14 426 713 ;
+C -1 ; WX 500 ; N abreve ; B 25 -14 488 691 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 16 -14 557 713 ;
+C -1 ; WX 444 ; N ecaron ; B 25 -14 426 704 ;
+C 159 ; WX 722 ; N Ydieresis ; B 15 0 699 877 ;
+C 247 ; WX 570 ; N divide ; B 33 -31 537 537 ;
+C 221 ; WX 722 ; N Yacute ; B 15 0 699 923 ;
+C 194 ; WX 722 ; N Acircumflex ; B 9 0 689 914 ;
+C 225 ; WX 500 ; N aacute ; B 25 -14 488 713 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 16 -19 701 914 ;
+C 253 ; WX 500 ; N yacute ; B 16 -205 480 713 ;
+C -1 ; WX 389 ; N scommaaccent ; B 25 -218 361 473 ;
+C 234 ; WX 444 ; N ecircumflex ; B 25 -14 426 704 ;
+C -1 ; WX 722 ; N Uring ; B 16 -19 701 935 ;
+C 220 ; WX 722 ; N Udieresis ; B 16 -19 701 877 ;
+C -1 ; WX 500 ; N aogonek ; B 25 -193 504 473 ;
+C 218 ; WX 722 ; N Uacute ; B 16 -19 701 923 ;
+C -1 ; WX 556 ; N uogonek ; B 16 -193 539 461 ;
+C 203 ; WX 667 ; N Edieresis ; B 16 0 641 877 ;
+C -1 ; WX 722 ; N Dcroat ; B 6 0 690 676 ;
+C -1 ; WX 250 ; N commaaccent ; B 47 -218 203 -50 ;
+C 169 ; WX 747 ; N copyright ; B 26 -19 721 691 ;
+C -1 ; WX 667 ; N Emacron ; B 16 0 641 847 ;
+C -1 ; WX 444 ; N ccaron ; B 25 -14 430 704 ;
+C 229 ; WX 500 ; N aring ; B 25 -14 488 740 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 16 -188 701 676 ;
+C -1 ; WX 278 ; N lacute ; B 16 0 297 923 ;
+C 224 ; WX 500 ; N agrave ; B 25 -14 488 713 ;
+C -1 ; WX 667 ; N Tcommaaccent ; B 31 -218 636 676 ;
+C -1 ; WX 722 ; N Cacute ; B 49 -19 687 923 ;
+C 227 ; WX 500 ; N atilde ; B 25 -14 488 674 ;
+C -1 ; WX 667 ; N Edotaccent ; B 16 0 641 901 ;
+C 154 ; WX 389 ; N scaron ; B 25 -14 363 704 ;
+C -1 ; WX 389 ; N scedilla ; B 25 -218 361 473 ;
+C 237 ; WX 278 ; N iacute ; B 16 0 289 713 ;
+C -1 ; WX 494 ; N lozenge ; B 10 0 484 745 ;
+C -1 ; WX 722 ; N Rcaron ; B 26 0 715 914 ;
+C -1 ; WX 778 ; N Gcommaaccent ; B 37 -218 755 691 ;
+C 251 ; WX 556 ; N ucircumflex ; B 16 -14 537 704 ;
+C 226 ; WX 500 ; N acircumflex ; B 25 -14 488 704 ;
+C -1 ; WX 722 ; N Amacron ; B 9 0 689 847 ;
+C -1 ; WX 444 ; N rcaron ; B 29 0 434 704 ;
+C 231 ; WX 444 ; N ccedilla ; B 25 -218 430 473 ;
+C -1 ; WX 667 ; N Zdotaccent ; B 28 0 634 901 ;
+C 222 ; WX 611 ; N Thorn ; B 16 0 600 676 ;
+C -1 ; WX 778 ; N Omacron ; B 35 -19 743 847 ;
+C -1 ; WX 722 ; N Racute ; B 26 0 715 923 ;
+C -1 ; WX 556 ; N Sacute ; B 35 -19 513 923 ;
+C -1 ; WX 672 ; N dcaron ; B 25 -14 681 682 ;
+C -1 ; WX 722 ; N Umacron ; B 16 -19 701 847 ;
+C -1 ; WX 556 ; N uring ; B 16 -14 537 740 ;
+C 179 ; WX 300 ; N threesuperior ; B 3 268 297 688 ;
+C 210 ; WX 778 ; N Ograve ; B 35 -19 743 923 ;
+C 192 ; WX 722 ; N Agrave ; B 9 0 689 923 ;
+C -1 ; WX 722 ; N Abreve ; B 9 0 689 901 ;
+C 215 ; WX 570 ; N multiply ; B 48 16 522 490 ;
+C 250 ; WX 556 ; N uacute ; B 16 -14 537 713 ;
+C -1 ; WX 667 ; N Tcaron ; B 31 0 636 914 ;
+C -1 ; WX 494 ; N partialdiff ; B 11 -21 494 750 ;
+C 255 ; WX 500 ; N ydieresis ; B 16 -205 480 667 ;
+C -1 ; WX 722 ; N Nacute ; B 16 -18 701 923 ;
+C 238 ; WX 278 ; N icircumflex ; B -37 0 300 704 ;
+C 202 ; WX 667 ; N Ecircumflex ; B 16 0 641 914 ;
+C 228 ; WX 500 ; N adieresis ; B 25 -14 488 667 ;
+C 235 ; WX 444 ; N edieresis ; B 25 -14 426 667 ;
+C -1 ; WX 444 ; N cacute ; B 25 -14 430 713 ;
+C -1 ; WX 556 ; N nacute ; B 21 0 539 713 ;
+C -1 ; WX 556 ; N umacron ; B 16 -14 537 637 ;
+C -1 ; WX 722 ; N Ncaron ; B 16 -18 701 914 ;
+C 205 ; WX 389 ; N Iacute ; B 20 0 370 923 ;
+C 177 ; WX 570 ; N plusminus ; B 33 0 537 506 ;
+C 166 ; WX 220 ; N brokenbar ; B 66 -143 154 707 ;
+C 174 ; WX 747 ; N registered ; B 26 -19 721 691 ;
+C -1 ; WX 778 ; N Gbreve ; B 37 -19 755 901 ;
+C -1 ; WX 389 ; N Idotaccent ; B 20 0 370 901 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 585 706 ;
+C 200 ; WX 667 ; N Egrave ; B 16 0 641 923 ;
+C -1 ; WX 444 ; N racute ; B 29 0 434 713 ;
+C -1 ; WX 500 ; N omacron ; B 25 -14 476 637 ;
+C -1 ; WX 667 ; N Zacute ; B 28 0 634 923 ;
+C 142 ; WX 667 ; N Zcaron ; B 28 0 634 914 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 704 ;
+C 208 ; WX 722 ; N Eth ; B 6 0 690 676 ;
+C 199 ; WX 722 ; N Ccedilla ; B 49 -218 687 691 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 16 -218 255 676 ;
+C -1 ; WX 416 ; N tcaron ; B 20 -12 425 815 ;
+C -1 ; WX 444 ; N eogonek ; B 25 -193 426 473 ;
+C -1 ; WX 722 ; N Uogonek ; B 16 -193 701 676 ;
+C 193 ; WX 722 ; N Aacute ; B 9 0 689 923 ;
+C 196 ; WX 722 ; N Adieresis ; B 9 0 689 877 ;
+C 232 ; WX 444 ; N egrave ; B 25 -14 426 713 ;
+C -1 ; WX 444 ; N zacute ; B 21 0 420 713 ;
+C -1 ; WX 278 ; N iogonek ; B 16 -193 274 691 ;
+C 211 ; WX 778 ; N Oacute ; B 35 -19 743 923 ;
+C 243 ; WX 500 ; N oacute ; B 25 -14 476 713 ;
+C -1 ; WX 500 ; N amacron ; B 25 -14 488 637 ;
+C -1 ; WX 389 ; N sacute ; B 25 -14 361 713 ;
+C 239 ; WX 278 ; N idieresis ; B -37 0 300 667 ;
+C 212 ; WX 778 ; N Ocircumflex ; B 35 -19 743 914 ;
+C 217 ; WX 722 ; N Ugrave ; B 16 -19 701 923 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 556 ; N thorn ; B 19 -205 524 676 ;
+C 178 ; WX 300 ; N twosuperior ; B 0 275 300 688 ;
+C 214 ; WX 778 ; N Odieresis ; B 35 -19 743 877 ;
+C 181 ; WX 556 ; N mu ; B 33 -206 536 461 ;
+C 236 ; WX 278 ; N igrave ; B -27 0 255 713 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B 25 -14 529 713 ;
+C -1 ; WX 667 ; N Eogonek ; B 16 -193 644 676 ;
+C -1 ; WX 556 ; N dcroat ; B 25 -14 534 676 ;
+C 190 ; WX 750 ; N threequarters ; B 23 -12 733 688 ;
+C -1 ; WX 556 ; N Scedilla ; B 35 -218 513 692 ;
+C -1 ; WX 394 ; N lcaron ; B 16 0 412 682 ;
+C -1 ; WX 778 ; N Kcommaaccent ; B 30 -218 769 676 ;
+C -1 ; WX 667 ; N Lacute ; B 19 0 638 923 ;
+C 153 ; WX 1000 ; N trademark ; B 24 271 977 676 ;
+C -1 ; WX 444 ; N edotaccent ; B 25 -14 426 691 ;
+C 204 ; WX 389 ; N Igrave ; B 20 0 370 923 ;
+C -1 ; WX 389 ; N Imacron ; B 20 0 370 847 ;
+C -1 ; WX 667 ; N Lcaron ; B 19 0 652 682 ;
+C 189 ; WX 750 ; N onehalf ; B -7 -12 775 688 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 526 704 ;
+C 244 ; WX 500 ; N ocircumflex ; B 25 -14 476 704 ;
+C 241 ; WX 556 ; N ntilde ; B 21 0 539 674 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 16 -19 701 923 ;
+C 201 ; WX 667 ; N Eacute ; B 16 0 641 923 ;
+C -1 ; WX 444 ; N emacron ; B 25 -14 426 637 ;
+C -1 ; WX 500 ; N gbreve ; B 28 -206 483 691 ;
+C 188 ; WX 750 ; N onequarter ; B 28 -12 743 688 ;
+C 138 ; WX 556 ; N Scaron ; B 35 -19 513 914 ;
+C -1 ; WX 556 ; N Scommaaccent ; B 35 -218 513 692 ;
+C -1 ; WX 778 ; N Ohungarumlaut ; B 35 -19 743 923 ;
+C 176 ; WX 400 ; N degree ; B 57 402 343 688 ;
+C 242 ; WX 500 ; N ograve ; B 25 -14 476 713 ;
+C -1 ; WX 722 ; N Ccaron ; B 49 -19 687 914 ;
+C 249 ; WX 556 ; N ugrave ; B 16 -14 537 713 ;
+C -1 ; WX 549 ; N radical ; B 10 -46 512 850 ;
+C -1 ; WX 722 ; N Dcaron ; B 14 0 690 914 ;
+C -1 ; WX 444 ; N rcommaaccent ; B 29 -218 434 473 ;
+C 209 ; WX 722 ; N Ntilde ; B 16 -18 701 884 ;
+C 245 ; WX 500 ; N otilde ; B 25 -14 476 674 ;
+C -1 ; WX 722 ; N Rcommaaccent ; B 26 -218 715 676 ;
+C -1 ; WX 667 ; N Lcommaaccent ; B 19 -218 638 676 ;
+C 195 ; WX 722 ; N Atilde ; B 9 0 689 884 ;
+C -1 ; WX 722 ; N Aogonek ; B 9 -193 699 690 ;
+C 197 ; WX 722 ; N Aring ; B 9 0 689 935 ;
+C 213 ; WX 778 ; N Otilde ; B 35 -19 743 884 ;
+C -1 ; WX 444 ; N zdotaccent ; B 21 0 420 691 ;
+C -1 ; WX 667 ; N Ecaron ; B 16 0 641 914 ;
+C -1 ; WX 389 ; N Iogonek ; B 20 -193 370 676 ;
+C -1 ; WX 556 ; N kcommaaccent ; B 22 -218 543 676 ;
+C -1 ; WX 570 ; N minus ; B 33 209 537 297 ;
+C 206 ; WX 389 ; N Icircumflex ; B 20 0 370 914 ;
+C -1 ; WX 556 ; N ncaron ; B 21 0 539 704 ;
+C -1 ; WX 333 ; N tcommaaccent ; B 20 -218 332 630 ;
+C 172 ; WX 570 ; N logicalnot ; B 33 108 537 399 ;
+C 246 ; WX 500 ; N odieresis ; B 25 -14 476 667 ;
+C 252 ; WX 556 ; N udieresis ; B 16 -14 537 667 ;
+C -1 ; WX 549 ; N notequal ; B 15 -49 540 570 ;
+C -1 ; WX 500 ; N gcommaaccent ; B 28 -206 483 829 ;
+C 240 ; WX 500 ; N eth ; B 25 -14 476 691 ;
+C 158 ; WX 444 ; N zcaron ; B 21 0 420 704 ;
+C -1 ; WX 556 ; N ncommaaccent ; B 21 -218 539 473 ;
+C 185 ; WX 300 ; N onesuperior ; B 28 275 273 688 ;
+C -1 ; WX 278 ; N imacron ; B -8 0 272 637 ;
+C 128 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2242
+KPX A C -55
+KPX A Cacute -55
+KPX A Ccaron -55
+KPX A Ccedilla -55
+KPX A G -55
+KPX A Gbreve -55
+KPX A Gcommaaccent -55
+KPX A O -45
+KPX A Oacute -45
+KPX A Ocircumflex -45
+KPX A Odieresis -45
+KPX A Ograve -45
+KPX A Ohungarumlaut -45
+KPX A Omacron -45
+KPX A Oslash -45
+KPX A Otilde -45
+KPX A Q -45
+KPX A T -95
+KPX A Tcaron -95
+KPX A Tcommaaccent -95
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -145
+KPX A W -130
+KPX A Y -100
+KPX A Yacute -100
+KPX A Ydieresis -100
+KPX A p -25
+KPX A quoteright -74
+KPX A u -50
+KPX A uacute -50
+KPX A ucircumflex -50
+KPX A udieresis -50
+KPX A ugrave -50
+KPX A uhungarumlaut -50
+KPX A umacron -50
+KPX A uogonek -50
+KPX A uring -50
+KPX A v -100
+KPX A w -90
+KPX A y -74
+KPX A yacute -74
+KPX A ydieresis -74
+KPX Aacute C -55
+KPX Aacute Cacute -55
+KPX Aacute Ccaron -55
+KPX Aacute Ccedilla -55
+KPX Aacute G -55
+KPX Aacute Gbreve -55
+KPX Aacute Gcommaaccent -55
+KPX Aacute O -45
+KPX Aacute Oacute -45
+KPX Aacute Ocircumflex -45
+KPX Aacute Odieresis -45
+KPX Aacute Ograve -45
+KPX Aacute Ohungarumlaut -45
+KPX Aacute Omacron -45
+KPX Aacute Oslash -45
+KPX Aacute Otilde -45
+KPX Aacute Q -45
+KPX Aacute T -95
+KPX Aacute Tcaron -95
+KPX Aacute Tcommaaccent -95
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -145
+KPX Aacute W -130
+KPX Aacute Y -100
+KPX Aacute Yacute -100
+KPX Aacute Ydieresis -100
+KPX Aacute p -25
+KPX Aacute quoteright -74
+KPX Aacute u -50
+KPX Aacute uacute -50
+KPX Aacute ucircumflex -50
+KPX Aacute udieresis -50
+KPX Aacute ugrave -50
+KPX Aacute uhungarumlaut -50
+KPX Aacute umacron -50
+KPX Aacute uogonek -50
+KPX Aacute uring -50
+KPX Aacute v -100
+KPX Aacute w -90
+KPX Aacute y -74
+KPX Aacute yacute -74
+KPX Aacute ydieresis -74
+KPX Abreve C -55
+KPX Abreve Cacute -55
+KPX Abreve Ccaron -55
+KPX Abreve Ccedilla -55
+KPX Abreve G -55
+KPX Abreve Gbreve -55
+KPX Abreve Gcommaaccent -55
+KPX Abreve O -45
+KPX Abreve Oacute -45
+KPX Abreve Ocircumflex -45
+KPX Abreve Odieresis -45
+KPX Abreve Ograve -45
+KPX Abreve Ohungarumlaut -45
+KPX Abreve Omacron -45
+KPX Abreve Oslash -45
+KPX Abreve Otilde -45
+KPX Abreve Q -45
+KPX Abreve T -95
+KPX Abreve Tcaron -95
+KPX Abreve Tcommaaccent -95
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -145
+KPX Abreve W -130
+KPX Abreve Y -100
+KPX Abreve Yacute -100
+KPX Abreve Ydieresis -100
+KPX Abreve p -25
+KPX Abreve quoteright -74
+KPX Abreve u -50
+KPX Abreve uacute -50
+KPX Abreve ucircumflex -50
+KPX Abreve udieresis -50
+KPX Abreve ugrave -50
+KPX Abreve uhungarumlaut -50
+KPX Abreve umacron -50
+KPX Abreve uogonek -50
+KPX Abreve uring -50
+KPX Abreve v -100
+KPX Abreve w -90
+KPX Abreve y -74
+KPX Abreve yacute -74
+KPX Abreve ydieresis -74
+KPX Acircumflex C -55
+KPX Acircumflex Cacute -55
+KPX Acircumflex Ccaron -55
+KPX Acircumflex Ccedilla -55
+KPX Acircumflex G -55
+KPX Acircumflex Gbreve -55
+KPX Acircumflex Gcommaaccent -55
+KPX Acircumflex O -45
+KPX Acircumflex Oacute -45
+KPX Acircumflex Ocircumflex -45
+KPX Acircumflex Odieresis -45
+KPX Acircumflex Ograve -45
+KPX Acircumflex Ohungarumlaut -45
+KPX Acircumflex Omacron -45
+KPX Acircumflex Oslash -45
+KPX Acircumflex Otilde -45
+KPX Acircumflex Q -45
+KPX Acircumflex T -95
+KPX Acircumflex Tcaron -95
+KPX Acircumflex Tcommaaccent -95
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -145
+KPX Acircumflex W -130
+KPX Acircumflex Y -100
+KPX Acircumflex Yacute -100
+KPX Acircumflex Ydieresis -100
+KPX Acircumflex p -25
+KPX Acircumflex quoteright -74
+KPX Acircumflex u -50
+KPX Acircumflex uacute -50
+KPX Acircumflex ucircumflex -50
+KPX Acircumflex udieresis -50
+KPX Acircumflex ugrave -50
+KPX Acircumflex uhungarumlaut -50
+KPX Acircumflex umacron -50
+KPX Acircumflex uogonek -50
+KPX Acircumflex uring -50
+KPX Acircumflex v -100
+KPX Acircumflex w -90
+KPX Acircumflex y -74
+KPX Acircumflex yacute -74
+KPX Acircumflex ydieresis -74
+KPX Adieresis C -55
+KPX Adieresis Cacute -55
+KPX Adieresis Ccaron -55
+KPX Adieresis Ccedilla -55
+KPX Adieresis G -55
+KPX Adieresis Gbreve -55
+KPX Adieresis Gcommaaccent -55
+KPX Adieresis O -45
+KPX Adieresis Oacute -45
+KPX Adieresis Ocircumflex -45
+KPX Adieresis Odieresis -45
+KPX Adieresis Ograve -45
+KPX Adieresis Ohungarumlaut -45
+KPX Adieresis Omacron -45
+KPX Adieresis Oslash -45
+KPX Adieresis Otilde -45
+KPX Adieresis Q -45
+KPX Adieresis T -95
+KPX Adieresis Tcaron -95
+KPX Adieresis Tcommaaccent -95
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -145
+KPX Adieresis W -130
+KPX Adieresis Y -100
+KPX Adieresis Yacute -100
+KPX Adieresis Ydieresis -100
+KPX Adieresis p -25
+KPX Adieresis quoteright -74
+KPX Adieresis u -50
+KPX Adieresis uacute -50
+KPX Adieresis ucircumflex -50
+KPX Adieresis udieresis -50
+KPX Adieresis ugrave -50
+KPX Adieresis uhungarumlaut -50
+KPX Adieresis umacron -50
+KPX Adieresis uogonek -50
+KPX Adieresis uring -50
+KPX Adieresis v -100
+KPX Adieresis w -90
+KPX Adieresis y -74
+KPX Adieresis yacute -74
+KPX Adieresis ydieresis -74
+KPX Agrave C -55
+KPX Agrave Cacute -55
+KPX Agrave Ccaron -55
+KPX Agrave Ccedilla -55
+KPX Agrave G -55
+KPX Agrave Gbreve -55
+KPX Agrave Gcommaaccent -55
+KPX Agrave O -45
+KPX Agrave Oacute -45
+KPX Agrave Ocircumflex -45
+KPX Agrave Odieresis -45
+KPX Agrave Ograve -45
+KPX Agrave Ohungarumlaut -45
+KPX Agrave Omacron -45
+KPX Agrave Oslash -45
+KPX Agrave Otilde -45
+KPX Agrave Q -45
+KPX Agrave T -95
+KPX Agrave Tcaron -95
+KPX Agrave Tcommaaccent -95
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -145
+KPX Agrave W -130
+KPX Agrave Y -100
+KPX Agrave Yacute -100
+KPX Agrave Ydieresis -100
+KPX Agrave p -25
+KPX Agrave quoteright -74
+KPX Agrave u -50
+KPX Agrave uacute -50
+KPX Agrave ucircumflex -50
+KPX Agrave udieresis -50
+KPX Agrave ugrave -50
+KPX Agrave uhungarumlaut -50
+KPX Agrave umacron -50
+KPX Agrave uogonek -50
+KPX Agrave uring -50
+KPX Agrave v -100
+KPX Agrave w -90
+KPX Agrave y -74
+KPX Agrave yacute -74
+KPX Agrave ydieresis -74
+KPX Amacron C -55
+KPX Amacron Cacute -55
+KPX Amacron Ccaron -55
+KPX Amacron Ccedilla -55
+KPX Amacron G -55
+KPX Amacron Gbreve -55
+KPX Amacron Gcommaaccent -55
+KPX Amacron O -45
+KPX Amacron Oacute -45
+KPX Amacron Ocircumflex -45
+KPX Amacron Odieresis -45
+KPX Amacron Ograve -45
+KPX Amacron Ohungarumlaut -45
+KPX Amacron Omacron -45
+KPX Amacron Oslash -45
+KPX Amacron Otilde -45
+KPX Amacron Q -45
+KPX Amacron T -95
+KPX Amacron Tcaron -95
+KPX Amacron Tcommaaccent -95
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -145
+KPX Amacron W -130
+KPX Amacron Y -100
+KPX Amacron Yacute -100
+KPX Amacron Ydieresis -100
+KPX Amacron p -25
+KPX Amacron quoteright -74
+KPX Amacron u -50
+KPX Amacron uacute -50
+KPX Amacron ucircumflex -50
+KPX Amacron udieresis -50
+KPX Amacron ugrave -50
+KPX Amacron uhungarumlaut -50
+KPX Amacron umacron -50
+KPX Amacron uogonek -50
+KPX Amacron uring -50
+KPX Amacron v -100
+KPX Amacron w -90
+KPX Amacron y -74
+KPX Amacron yacute -74
+KPX Amacron ydieresis -74
+KPX Aogonek C -55
+KPX Aogonek Cacute -55
+KPX Aogonek Ccaron -55
+KPX Aogonek Ccedilla -55
+KPX Aogonek G -55
+KPX Aogonek Gbreve -55
+KPX Aogonek Gcommaaccent -55
+KPX Aogonek O -45
+KPX Aogonek Oacute -45
+KPX Aogonek Ocircumflex -45
+KPX Aogonek Odieresis -45
+KPX Aogonek Ograve -45
+KPX Aogonek Ohungarumlaut -45
+KPX Aogonek Omacron -45
+KPX Aogonek Oslash -45
+KPX Aogonek Otilde -45
+KPX Aogonek Q -45
+KPX Aogonek T -95
+KPX Aogonek Tcaron -95
+KPX Aogonek Tcommaaccent -95
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -145
+KPX Aogonek W -130
+KPX Aogonek Y -100
+KPX Aogonek Yacute -100
+KPX Aogonek Ydieresis -100
+KPX Aogonek p -25
+KPX Aogonek quoteright -74
+KPX Aogonek u -50
+KPX Aogonek uacute -50
+KPX Aogonek ucircumflex -50
+KPX Aogonek udieresis -50
+KPX Aogonek ugrave -50
+KPX Aogonek uhungarumlaut -50
+KPX Aogonek umacron -50
+KPX Aogonek uogonek -50
+KPX Aogonek uring -50
+KPX Aogonek v -100
+KPX Aogonek w -90
+KPX Aogonek y -34
+KPX Aogonek yacute -34
+KPX Aogonek ydieresis -34
+KPX Aring C -55
+KPX Aring Cacute -55
+KPX Aring Ccaron -55
+KPX Aring Ccedilla -55
+KPX Aring G -55
+KPX Aring Gbreve -55
+KPX Aring Gcommaaccent -55
+KPX Aring O -45
+KPX Aring Oacute -45
+KPX Aring Ocircumflex -45
+KPX Aring Odieresis -45
+KPX Aring Ograve -45
+KPX Aring Ohungarumlaut -45
+KPX Aring Omacron -45
+KPX Aring Oslash -45
+KPX Aring Otilde -45
+KPX Aring Q -45
+KPX Aring T -95
+KPX Aring Tcaron -95
+KPX Aring Tcommaaccent -95
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -145
+KPX Aring W -130
+KPX Aring Y -100
+KPX Aring Yacute -100
+KPX Aring Ydieresis -100
+KPX Aring p -25
+KPX Aring quoteright -74
+KPX Aring u -50
+KPX Aring uacute -50
+KPX Aring ucircumflex -50
+KPX Aring udieresis -50
+KPX Aring ugrave -50
+KPX Aring uhungarumlaut -50
+KPX Aring umacron -50
+KPX Aring uogonek -50
+KPX Aring uring -50
+KPX Aring v -100
+KPX Aring w -90
+KPX Aring y -74
+KPX Aring yacute -74
+KPX Aring ydieresis -74
+KPX Atilde C -55
+KPX Atilde Cacute -55
+KPX Atilde Ccaron -55
+KPX Atilde Ccedilla -55
+KPX Atilde G -55
+KPX Atilde Gbreve -55
+KPX Atilde Gcommaaccent -55
+KPX Atilde O -45
+KPX Atilde Oacute -45
+KPX Atilde Ocircumflex -45
+KPX Atilde Odieresis -45
+KPX Atilde Ograve -45
+KPX Atilde Ohungarumlaut -45
+KPX Atilde Omacron -45
+KPX Atilde Oslash -45
+KPX Atilde Otilde -45
+KPX Atilde Q -45
+KPX Atilde T -95
+KPX Atilde Tcaron -95
+KPX Atilde Tcommaaccent -95
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -145
+KPX Atilde W -130
+KPX Atilde Y -100
+KPX Atilde Yacute -100
+KPX Atilde Ydieresis -100
+KPX Atilde p -25
+KPX Atilde quoteright -74
+KPX Atilde u -50
+KPX Atilde uacute -50
+KPX Atilde ucircumflex -50
+KPX Atilde udieresis -50
+KPX Atilde ugrave -50
+KPX Atilde uhungarumlaut -50
+KPX Atilde umacron -50
+KPX Atilde uogonek -50
+KPX Atilde uring -50
+KPX Atilde v -100
+KPX Atilde w -90
+KPX Atilde y -74
+KPX Atilde yacute -74
+KPX Atilde ydieresis -74
+KPX B A -30
+KPX B Aacute -30
+KPX B Abreve -30
+KPX B Acircumflex -30
+KPX B Adieresis -30
+KPX B Agrave -30
+KPX B Amacron -30
+KPX B Aogonek -30
+KPX B Aring -30
+KPX B Atilde -30
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -35
+KPX D Aacute -35
+KPX D Abreve -35
+KPX D Acircumflex -35
+KPX D Adieresis -35
+KPX D Agrave -35
+KPX D Amacron -35
+KPX D Aogonek -35
+KPX D Aring -35
+KPX D Atilde -35
+KPX D V -40
+KPX D W -40
+KPX D Y -40
+KPX D Yacute -40
+KPX D Ydieresis -40
+KPX D period -20
+KPX Dcaron A -35
+KPX Dcaron Aacute -35
+KPX Dcaron Abreve -35
+KPX Dcaron Acircumflex -35
+KPX Dcaron Adieresis -35
+KPX Dcaron Agrave -35
+KPX Dcaron Amacron -35
+KPX Dcaron Aogonek -35
+KPX Dcaron Aring -35
+KPX Dcaron Atilde -35
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -40
+KPX Dcaron Yacute -40
+KPX Dcaron Ydieresis -40
+KPX Dcaron period -20
+KPX Dcroat A -35
+KPX Dcroat Aacute -35
+KPX Dcroat Abreve -35
+KPX Dcroat Acircumflex -35
+KPX Dcroat Adieresis -35
+KPX Dcroat Agrave -35
+KPX Dcroat Amacron -35
+KPX Dcroat Aogonek -35
+KPX Dcroat Aring -35
+KPX Dcroat Atilde -35
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -40
+KPX Dcroat Yacute -40
+KPX Dcroat Ydieresis -40
+KPX Dcroat period -20
+KPX F A -90
+KPX F Aacute -90
+KPX F Abreve -90
+KPX F Acircumflex -90
+KPX F Adieresis -90
+KPX F Agrave -90
+KPX F Amacron -90
+KPX F Aogonek -90
+KPX F Aring -90
+KPX F Atilde -90
+KPX F a -25
+KPX F aacute -25
+KPX F abreve -25
+KPX F acircumflex -25
+KPX F adieresis -25
+KPX F agrave -25
+KPX F amacron -25
+KPX F aogonek -25
+KPX F aring -25
+KPX F atilde -25
+KPX F comma -92
+KPX F e -25
+KPX F eacute -25
+KPX F ecaron -25
+KPX F ecircumflex -25
+KPX F edieresis -25
+KPX F edotaccent -25
+KPX F egrave -25
+KPX F emacron -25
+KPX F eogonek -25
+KPX F o -25
+KPX F oacute -25
+KPX F ocircumflex -25
+KPX F odieresis -25
+KPX F ograve -25
+KPX F ohungarumlaut -25
+KPX F omacron -25
+KPX F oslash -25
+KPX F otilde -25
+KPX F period -110
+KPX J A -30
+KPX J Aacute -30
+KPX J Abreve -30
+KPX J Acircumflex -30
+KPX J Adieresis -30
+KPX J Agrave -30
+KPX J Amacron -30
+KPX J Aogonek -30
+KPX J Aring -30
+KPX J Atilde -30
+KPX J a -15
+KPX J aacute -15
+KPX J abreve -15
+KPX J acircumflex -15
+KPX J adieresis -15
+KPX J agrave -15
+KPX J amacron -15
+KPX J aogonek -15
+KPX J aring -15
+KPX J atilde -15
+KPX J e -15
+KPX J eacute -15
+KPX J ecaron -15
+KPX J ecircumflex -15
+KPX J edieresis -15
+KPX J edotaccent -15
+KPX J egrave -15
+KPX J emacron -15
+KPX J eogonek -15
+KPX J o -15
+KPX J oacute -15
+KPX J ocircumflex -15
+KPX J odieresis -15
+KPX J ograve -15
+KPX J ohungarumlaut -15
+KPX J omacron -15
+KPX J oslash -15
+KPX J otilde -15
+KPX J period -20
+KPX J u -15
+KPX J uacute -15
+KPX J ucircumflex -15
+KPX J udieresis -15
+KPX J ugrave -15
+KPX J uhungarumlaut -15
+KPX J umacron -15
+KPX J uogonek -15
+KPX J uring -15
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -25
+KPX K eacute -25
+KPX K ecaron -25
+KPX K ecircumflex -25
+KPX K edieresis -25
+KPX K edotaccent -25
+KPX K egrave -25
+KPX K emacron -25
+KPX K eogonek -25
+KPX K o -25
+KPX K oacute -25
+KPX K ocircumflex -25
+KPX K odieresis -25
+KPX K ograve -25
+KPX K ohungarumlaut -25
+KPX K omacron -25
+KPX K oslash -25
+KPX K otilde -25
+KPX K u -15
+KPX K uacute -15
+KPX K ucircumflex -15
+KPX K udieresis -15
+KPX K ugrave -15
+KPX K uhungarumlaut -15
+KPX K umacron -15
+KPX K uogonek -15
+KPX K uring -15
+KPX K y -45
+KPX K yacute -45
+KPX K ydieresis -45
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -25
+KPX Kcommaaccent eacute -25
+KPX Kcommaaccent ecaron -25
+KPX Kcommaaccent ecircumflex -25
+KPX Kcommaaccent edieresis -25
+KPX Kcommaaccent edotaccent -25
+KPX Kcommaaccent egrave -25
+KPX Kcommaaccent emacron -25
+KPX Kcommaaccent eogonek -25
+KPX Kcommaaccent o -25
+KPX Kcommaaccent oacute -25
+KPX Kcommaaccent ocircumflex -25
+KPX Kcommaaccent odieresis -25
+KPX Kcommaaccent ograve -25
+KPX Kcommaaccent ohungarumlaut -25
+KPX Kcommaaccent omacron -25
+KPX Kcommaaccent oslash -25
+KPX Kcommaaccent otilde -25
+KPX Kcommaaccent u -15
+KPX Kcommaaccent uacute -15
+KPX Kcommaaccent ucircumflex -15
+KPX Kcommaaccent udieresis -15
+KPX Kcommaaccent ugrave -15
+KPX Kcommaaccent uhungarumlaut -15
+KPX Kcommaaccent umacron -15
+KPX Kcommaaccent uogonek -15
+KPX Kcommaaccent uring -15
+KPX Kcommaaccent y -45
+KPX Kcommaaccent yacute -45
+KPX Kcommaaccent ydieresis -45
+KPX L T -92
+KPX L Tcaron -92
+KPX L Tcommaaccent -92
+KPX L V -92
+KPX L W -92
+KPX L Y -92
+KPX L Yacute -92
+KPX L Ydieresis -92
+KPX L quotedblright -20
+KPX L quoteright -110
+KPX L y -55
+KPX L yacute -55
+KPX L ydieresis -55
+KPX Lacute T -92
+KPX Lacute Tcaron -92
+KPX Lacute Tcommaaccent -92
+KPX Lacute V -92
+KPX Lacute W -92
+KPX Lacute Y -92
+KPX Lacute Yacute -92
+KPX Lacute Ydieresis -92
+KPX Lacute quotedblright -20
+KPX Lacute quoteright -110
+KPX Lacute y -55
+KPX Lacute yacute -55
+KPX Lacute ydieresis -55
+KPX Lcommaaccent T -92
+KPX Lcommaaccent Tcaron -92
+KPX Lcommaaccent Tcommaaccent -92
+KPX Lcommaaccent V -92
+KPX Lcommaaccent W -92
+KPX Lcommaaccent Y -92
+KPX Lcommaaccent Yacute -92
+KPX Lcommaaccent Ydieresis -92
+KPX Lcommaaccent quotedblright -20
+KPX Lcommaaccent quoteright -110
+KPX Lcommaaccent y -55
+KPX Lcommaaccent yacute -55
+KPX Lcommaaccent ydieresis -55
+KPX Lslash T -92
+KPX Lslash Tcaron -92
+KPX Lslash Tcommaaccent -92
+KPX Lslash V -92
+KPX Lslash W -92
+KPX Lslash Y -92
+KPX Lslash Yacute -92
+KPX Lslash Ydieresis -92
+KPX Lslash quotedblright -20
+KPX Lslash quoteright -110
+KPX Lslash y -55
+KPX Lslash yacute -55
+KPX Lslash ydieresis -55
+KPX N A -20
+KPX N Aacute -20
+KPX N Abreve -20
+KPX N Acircumflex -20
+KPX N Adieresis -20
+KPX N Agrave -20
+KPX N Amacron -20
+KPX N Aogonek -20
+KPX N Aring -20
+KPX N Atilde -20
+KPX Nacute A -20
+KPX Nacute Aacute -20
+KPX Nacute Abreve -20
+KPX Nacute Acircumflex -20
+KPX Nacute Adieresis -20
+KPX Nacute Agrave -20
+KPX Nacute Amacron -20
+KPX Nacute Aogonek -20
+KPX Nacute Aring -20
+KPX Nacute Atilde -20
+KPX Ncaron A -20
+KPX Ncaron Aacute -20
+KPX Ncaron Abreve -20
+KPX Ncaron Acircumflex -20
+KPX Ncaron Adieresis -20
+KPX Ncaron Agrave -20
+KPX Ncaron Amacron -20
+KPX Ncaron Aogonek -20
+KPX Ncaron Aring -20
+KPX Ncaron Atilde -20
+KPX Ncommaaccent A -20
+KPX Ncommaaccent Aacute -20
+KPX Ncommaaccent Abreve -20
+KPX Ncommaaccent Acircumflex -20
+KPX Ncommaaccent Adieresis -20
+KPX Ncommaaccent Agrave -20
+KPX Ncommaaccent Amacron -20
+KPX Ncommaaccent Aogonek -20
+KPX Ncommaaccent Aring -20
+KPX Ncommaaccent Atilde -20
+KPX Ntilde A -20
+KPX Ntilde Aacute -20
+KPX Ntilde Abreve -20
+KPX Ntilde Acircumflex -20
+KPX Ntilde Adieresis -20
+KPX Ntilde Agrave -20
+KPX Ntilde Amacron -20
+KPX Ntilde Aogonek -20
+KPX Ntilde Aring -20
+KPX Ntilde Atilde -20
+KPX O A -40
+KPX O Aacute -40
+KPX O Abreve -40
+KPX O Acircumflex -40
+KPX O Adieresis -40
+KPX O Agrave -40
+KPX O Amacron -40
+KPX O Aogonek -40
+KPX O Aring -40
+KPX O Atilde -40
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -40
+KPX Oacute Aacute -40
+KPX Oacute Abreve -40
+KPX Oacute Acircumflex -40
+KPX Oacute Adieresis -40
+KPX Oacute Agrave -40
+KPX Oacute Amacron -40
+KPX Oacute Aogonek -40
+KPX Oacute Aring -40
+KPX Oacute Atilde -40
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -40
+KPX Ocircumflex Aacute -40
+KPX Ocircumflex Abreve -40
+KPX Ocircumflex Acircumflex -40
+KPX Ocircumflex Adieresis -40
+KPX Ocircumflex Agrave -40
+KPX Ocircumflex Amacron -40
+KPX Ocircumflex Aogonek -40
+KPX Ocircumflex Aring -40
+KPX Ocircumflex Atilde -40
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -40
+KPX Odieresis Aacute -40
+KPX Odieresis Abreve -40
+KPX Odieresis Acircumflex -40
+KPX Odieresis Adieresis -40
+KPX Odieresis Agrave -40
+KPX Odieresis Amacron -40
+KPX Odieresis Aogonek -40
+KPX Odieresis Aring -40
+KPX Odieresis Atilde -40
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -40
+KPX Ograve Aacute -40
+KPX Ograve Abreve -40
+KPX Ograve Acircumflex -40
+KPX Ograve Adieresis -40
+KPX Ograve Agrave -40
+KPX Ograve Amacron -40
+KPX Ograve Aogonek -40
+KPX Ograve Aring -40
+KPX Ograve Atilde -40
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -40
+KPX Ohungarumlaut Aacute -40
+KPX Ohungarumlaut Abreve -40
+KPX Ohungarumlaut Acircumflex -40
+KPX Ohungarumlaut Adieresis -40
+KPX Ohungarumlaut Agrave -40
+KPX Ohungarumlaut Amacron -40
+KPX Ohungarumlaut Aogonek -40
+KPX Ohungarumlaut Aring -40
+KPX Ohungarumlaut Atilde -40
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -40
+KPX Omacron Aacute -40
+KPX Omacron Abreve -40
+KPX Omacron Acircumflex -40
+KPX Omacron Adieresis -40
+KPX Omacron Agrave -40
+KPX Omacron Amacron -40
+KPX Omacron Aogonek -40
+KPX Omacron Aring -40
+KPX Omacron Atilde -40
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -40
+KPX Oslash Aacute -40
+KPX Oslash Abreve -40
+KPX Oslash Acircumflex -40
+KPX Oslash Adieresis -40
+KPX Oslash Agrave -40
+KPX Oslash Amacron -40
+KPX Oslash Aogonek -40
+KPX Oslash Aring -40
+KPX Oslash Atilde -40
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -40
+KPX Otilde Aacute -40
+KPX Otilde Abreve -40
+KPX Otilde Acircumflex -40
+KPX Otilde Adieresis -40
+KPX Otilde Agrave -40
+KPX Otilde Amacron -40
+KPX Otilde Aogonek -40
+KPX Otilde Aring -40
+KPX Otilde Atilde -40
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -74
+KPX P Aacute -74
+KPX P Abreve -74
+KPX P Acircumflex -74
+KPX P Adieresis -74
+KPX P Agrave -74
+KPX P Amacron -74
+KPX P Aogonek -74
+KPX P Aring -74
+KPX P Atilde -74
+KPX P a -10
+KPX P aacute -10
+KPX P abreve -10
+KPX P acircumflex -10
+KPX P adieresis -10
+KPX P agrave -10
+KPX P amacron -10
+KPX P aogonek -10
+KPX P aring -10
+KPX P atilde -10
+KPX P comma -92
+KPX P e -20
+KPX P eacute -20
+KPX P ecaron -20
+KPX P ecircumflex -20
+KPX P edieresis -20
+KPX P edotaccent -20
+KPX P egrave -20
+KPX P emacron -20
+KPX P eogonek -20
+KPX P o -20
+KPX P oacute -20
+KPX P ocircumflex -20
+KPX P odieresis -20
+KPX P ograve -20
+KPX P ohungarumlaut -20
+KPX P omacron -20
+KPX P oslash -20
+KPX P otilde -20
+KPX P period -110
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX Q period -20
+KPX R O -30
+KPX R Oacute -30
+KPX R Ocircumflex -30
+KPX R Odieresis -30
+KPX R Ograve -30
+KPX R Ohungarumlaut -30
+KPX R Omacron -30
+KPX R Oslash -30
+KPX R Otilde -30
+KPX R T -40
+KPX R Tcaron -40
+KPX R Tcommaaccent -40
+KPX R U -30
+KPX R Uacute -30
+KPX R Ucircumflex -30
+KPX R Udieresis -30
+KPX R Ugrave -30
+KPX R Uhungarumlaut -30
+KPX R Umacron -30
+KPX R Uogonek -30
+KPX R Uring -30
+KPX R V -55
+KPX R W -35
+KPX R Y -35
+KPX R Yacute -35
+KPX R Ydieresis -35
+KPX Racute O -30
+KPX Racute Oacute -30
+KPX Racute Ocircumflex -30
+KPX Racute Odieresis -30
+KPX Racute Ograve -30
+KPX Racute Ohungarumlaut -30
+KPX Racute Omacron -30
+KPX Racute Oslash -30
+KPX Racute Otilde -30
+KPX Racute T -40
+KPX Racute Tcaron -40
+KPX Racute Tcommaaccent -40
+KPX Racute U -30
+KPX Racute Uacute -30
+KPX Racute Ucircumflex -30
+KPX Racute Udieresis -30
+KPX Racute Ugrave -30
+KPX Racute Uhungarumlaut -30
+KPX Racute Umacron -30
+KPX Racute Uogonek -30
+KPX Racute Uring -30
+KPX Racute V -55
+KPX Racute W -35
+KPX Racute Y -35
+KPX Racute Yacute -35
+KPX Racute Ydieresis -35
+KPX Rcaron O -30
+KPX Rcaron Oacute -30
+KPX Rcaron Ocircumflex -30
+KPX Rcaron Odieresis -30
+KPX Rcaron Ograve -30
+KPX Rcaron Ohungarumlaut -30
+KPX Rcaron Omacron -30
+KPX Rcaron Oslash -30
+KPX Rcaron Otilde -30
+KPX Rcaron T -40
+KPX Rcaron Tcaron -40
+KPX Rcaron Tcommaaccent -40
+KPX Rcaron U -30
+KPX Rcaron Uacute -30
+KPX Rcaron Ucircumflex -30
+KPX Rcaron Udieresis -30
+KPX Rcaron Ugrave -30
+KPX Rcaron Uhungarumlaut -30
+KPX Rcaron Umacron -30
+KPX Rcaron Uogonek -30
+KPX Rcaron Uring -30
+KPX Rcaron V -55
+KPX Rcaron W -35
+KPX Rcaron Y -35
+KPX Rcaron Yacute -35
+KPX Rcaron Ydieresis -35
+KPX Rcommaaccent O -30
+KPX Rcommaaccent Oacute -30
+KPX Rcommaaccent Ocircumflex -30
+KPX Rcommaaccent Odieresis -30
+KPX Rcommaaccent Ograve -30
+KPX Rcommaaccent Ohungarumlaut -30
+KPX Rcommaaccent Omacron -30
+KPX Rcommaaccent Oslash -30
+KPX Rcommaaccent Otilde -30
+KPX Rcommaaccent T -40
+KPX Rcommaaccent Tcaron -40
+KPX Rcommaaccent Tcommaaccent -40
+KPX Rcommaaccent U -30
+KPX Rcommaaccent Uacute -30
+KPX Rcommaaccent Ucircumflex -30
+KPX Rcommaaccent Udieresis -30
+KPX Rcommaaccent Ugrave -30
+KPX Rcommaaccent Uhungarumlaut -30
+KPX Rcommaaccent Umacron -30
+KPX Rcommaaccent Uogonek -30
+KPX Rcommaaccent Uring -30
+KPX Rcommaaccent V -55
+KPX Rcommaaccent W -35
+KPX Rcommaaccent Y -35
+KPX Rcommaaccent Yacute -35
+KPX Rcommaaccent Ydieresis -35
+KPX T A -90
+KPX T Aacute -90
+KPX T Abreve -90
+KPX T Acircumflex -90
+KPX T Adieresis -90
+KPX T Agrave -90
+KPX T Amacron -90
+KPX T Aogonek -90
+KPX T Aring -90
+KPX T Atilde -90
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -92
+KPX T aacute -92
+KPX T abreve -52
+KPX T acircumflex -52
+KPX T adieresis -52
+KPX T agrave -52
+KPX T amacron -52
+KPX T aogonek -92
+KPX T aring -92
+KPX T atilde -52
+KPX T colon -74
+KPX T comma -74
+KPX T e -92
+KPX T eacute -92
+KPX T ecaron -92
+KPX T ecircumflex -92
+KPX T edieresis -52
+KPX T edotaccent -92
+KPX T egrave -52
+KPX T emacron -52
+KPX T eogonek -92
+KPX T hyphen -92
+KPX T i -18
+KPX T iacute -18
+KPX T iogonek -18
+KPX T o -92
+KPX T oacute -92
+KPX T ocircumflex -92
+KPX T odieresis -92
+KPX T ograve -92
+KPX T ohungarumlaut -92
+KPX T omacron -92
+KPX T oslash -92
+KPX T otilde -92
+KPX T period -90
+KPX T r -74
+KPX T racute -74
+KPX T rcaron -74
+KPX T rcommaaccent -74
+KPX T semicolon -74
+KPX T u -92
+KPX T uacute -92
+KPX T ucircumflex -92
+KPX T udieresis -92
+KPX T ugrave -92
+KPX T uhungarumlaut -92
+KPX T umacron -92
+KPX T uogonek -92
+KPX T uring -92
+KPX T w -74
+KPX T y -34
+KPX T yacute -34
+KPX T ydieresis -34
+KPX Tcaron A -90
+KPX Tcaron Aacute -90
+KPX Tcaron Abreve -90
+KPX Tcaron Acircumflex -90
+KPX Tcaron Adieresis -90
+KPX Tcaron Agrave -90
+KPX Tcaron Amacron -90
+KPX Tcaron Aogonek -90
+KPX Tcaron Aring -90
+KPX Tcaron Atilde -90
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -92
+KPX Tcaron aacute -92
+KPX Tcaron abreve -52
+KPX Tcaron acircumflex -52
+KPX Tcaron adieresis -52
+KPX Tcaron agrave -52
+KPX Tcaron amacron -52
+KPX Tcaron aogonek -92
+KPX Tcaron aring -92
+KPX Tcaron atilde -52
+KPX Tcaron colon -74
+KPX Tcaron comma -74
+KPX Tcaron e -92
+KPX Tcaron eacute -92
+KPX Tcaron ecaron -92
+KPX Tcaron ecircumflex -92
+KPX Tcaron edieresis -52
+KPX Tcaron edotaccent -92
+KPX Tcaron egrave -52
+KPX Tcaron emacron -52
+KPX Tcaron eogonek -92
+KPX Tcaron hyphen -92
+KPX Tcaron i -18
+KPX Tcaron iacute -18
+KPX Tcaron iogonek -18
+KPX Tcaron o -92
+KPX Tcaron oacute -92
+KPX Tcaron ocircumflex -92
+KPX Tcaron odieresis -92
+KPX Tcaron ograve -92
+KPX Tcaron ohungarumlaut -92
+KPX Tcaron omacron -92
+KPX Tcaron oslash -92
+KPX Tcaron otilde -92
+KPX Tcaron period -90
+KPX Tcaron r -74
+KPX Tcaron racute -74
+KPX Tcaron rcaron -74
+KPX Tcaron rcommaaccent -74
+KPX Tcaron semicolon -74
+KPX Tcaron u -92
+KPX Tcaron uacute -92
+KPX Tcaron ucircumflex -92
+KPX Tcaron udieresis -92
+KPX Tcaron ugrave -92
+KPX Tcaron uhungarumlaut -92
+KPX Tcaron umacron -92
+KPX Tcaron uogonek -92
+KPX Tcaron uring -92
+KPX Tcaron w -74
+KPX Tcaron y -34
+KPX Tcaron yacute -34
+KPX Tcaron ydieresis -34
+KPX Tcommaaccent A -90
+KPX Tcommaaccent Aacute -90
+KPX Tcommaaccent Abreve -90
+KPX Tcommaaccent Acircumflex -90
+KPX Tcommaaccent Adieresis -90
+KPX Tcommaaccent Agrave -90
+KPX Tcommaaccent Amacron -90
+KPX Tcommaaccent Aogonek -90
+KPX Tcommaaccent Aring -90
+KPX Tcommaaccent Atilde -90
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -92
+KPX Tcommaaccent aacute -92
+KPX Tcommaaccent abreve -52
+KPX Tcommaaccent acircumflex -52
+KPX Tcommaaccent adieresis -52
+KPX Tcommaaccent agrave -52
+KPX Tcommaaccent amacron -52
+KPX Tcommaaccent aogonek -92
+KPX Tcommaaccent aring -92
+KPX Tcommaaccent atilde -52
+KPX Tcommaaccent colon -74
+KPX Tcommaaccent comma -74
+KPX Tcommaaccent e -92
+KPX Tcommaaccent eacute -92
+KPX Tcommaaccent ecaron -92
+KPX Tcommaaccent ecircumflex -92
+KPX Tcommaaccent edieresis -52
+KPX Tcommaaccent edotaccent -92
+KPX Tcommaaccent egrave -52
+KPX Tcommaaccent emacron -52
+KPX Tcommaaccent eogonek -92
+KPX Tcommaaccent hyphen -92
+KPX Tcommaaccent i -18
+KPX Tcommaaccent iacute -18
+KPX Tcommaaccent iogonek -18
+KPX Tcommaaccent o -92
+KPX Tcommaaccent oacute -92
+KPX Tcommaaccent ocircumflex -92
+KPX Tcommaaccent odieresis -92
+KPX Tcommaaccent ograve -92
+KPX Tcommaaccent ohungarumlaut -92
+KPX Tcommaaccent omacron -92
+KPX Tcommaaccent oslash -92
+KPX Tcommaaccent otilde -92
+KPX Tcommaaccent period -90
+KPX Tcommaaccent r -74
+KPX Tcommaaccent racute -74
+KPX Tcommaaccent rcaron -74
+KPX Tcommaaccent rcommaaccent -74
+KPX Tcommaaccent semicolon -74
+KPX Tcommaaccent u -92
+KPX Tcommaaccent uacute -92
+KPX Tcommaaccent ucircumflex -92
+KPX Tcommaaccent udieresis -92
+KPX Tcommaaccent ugrave -92
+KPX Tcommaaccent uhungarumlaut -92
+KPX Tcommaaccent umacron -92
+KPX Tcommaaccent uogonek -92
+KPX Tcommaaccent uring -92
+KPX Tcommaaccent w -74
+KPX Tcommaaccent y -34
+KPX Tcommaaccent yacute -34
+KPX Tcommaaccent ydieresis -34
+KPX U A -60
+KPX U Aacute -60
+KPX U Abreve -60
+KPX U Acircumflex -60
+KPX U Adieresis -60
+KPX U Agrave -60
+KPX U Amacron -60
+KPX U Aogonek -60
+KPX U Aring -60
+KPX U Atilde -60
+KPX U comma -50
+KPX U period -50
+KPX Uacute A -60
+KPX Uacute Aacute -60
+KPX Uacute Abreve -60
+KPX Uacute Acircumflex -60
+KPX Uacute Adieresis -60
+KPX Uacute Agrave -60
+KPX Uacute Amacron -60
+KPX Uacute Aogonek -60
+KPX Uacute Aring -60
+KPX Uacute Atilde -60
+KPX Uacute comma -50
+KPX Uacute period -50
+KPX Ucircumflex A -60
+KPX Ucircumflex Aacute -60
+KPX Ucircumflex Abreve -60
+KPX Ucircumflex Acircumflex -60
+KPX Ucircumflex Adieresis -60
+KPX Ucircumflex Agrave -60
+KPX Ucircumflex Amacron -60
+KPX Ucircumflex Aogonek -60
+KPX Ucircumflex Aring -60
+KPX Ucircumflex Atilde -60
+KPX Ucircumflex comma -50
+KPX Ucircumflex period -50
+KPX Udieresis A -60
+KPX Udieresis Aacute -60
+KPX Udieresis Abreve -60
+KPX Udieresis Acircumflex -60
+KPX Udieresis Adieresis -60
+KPX Udieresis Agrave -60
+KPX Udieresis Amacron -60
+KPX Udieresis Aogonek -60
+KPX Udieresis Aring -60
+KPX Udieresis Atilde -60
+KPX Udieresis comma -50
+KPX Udieresis period -50
+KPX Ugrave A -60
+KPX Ugrave Aacute -60
+KPX Ugrave Abreve -60
+KPX Ugrave Acircumflex -60
+KPX Ugrave Adieresis -60
+KPX Ugrave Agrave -60
+KPX Ugrave Amacron -60
+KPX Ugrave Aogonek -60
+KPX Ugrave Aring -60
+KPX Ugrave Atilde -60
+KPX Ugrave comma -50
+KPX Ugrave period -50
+KPX Uhungarumlaut A -60
+KPX Uhungarumlaut Aacute -60
+KPX Uhungarumlaut Abreve -60
+KPX Uhungarumlaut Acircumflex -60
+KPX Uhungarumlaut Adieresis -60
+KPX Uhungarumlaut Agrave -60
+KPX Uhungarumlaut Amacron -60
+KPX Uhungarumlaut Aogonek -60
+KPX Uhungarumlaut Aring -60
+KPX Uhungarumlaut Atilde -60
+KPX Uhungarumlaut comma -50
+KPX Uhungarumlaut period -50
+KPX Umacron A -60
+KPX Umacron Aacute -60
+KPX Umacron Abreve -60
+KPX Umacron Acircumflex -60
+KPX Umacron Adieresis -60
+KPX Umacron Agrave -60
+KPX Umacron Amacron -60
+KPX Umacron Aogonek -60
+KPX Umacron Aring -60
+KPX Umacron Atilde -60
+KPX Umacron comma -50
+KPX Umacron period -50
+KPX Uogonek A -60
+KPX Uogonek Aacute -60
+KPX Uogonek Abreve -60
+KPX Uogonek Acircumflex -60
+KPX Uogonek Adieresis -60
+KPX Uogonek Agrave -60
+KPX Uogonek Amacron -60
+KPX Uogonek Aogonek -60
+KPX Uogonek Aring -60
+KPX Uogonek Atilde -60
+KPX Uogonek comma -50
+KPX Uogonek period -50
+KPX Uring A -60
+KPX Uring Aacute -60
+KPX Uring Abreve -60
+KPX Uring Acircumflex -60
+KPX Uring Adieresis -60
+KPX Uring Agrave -60
+KPX Uring Amacron -60
+KPX Uring Aogonek -60
+KPX Uring Aring -60
+KPX Uring Atilde -60
+KPX Uring comma -50
+KPX Uring period -50
+KPX V A -135
+KPX V Aacute -135
+KPX V Abreve -135
+KPX V Acircumflex -135
+KPX V Adieresis -135
+KPX V Agrave -135
+KPX V Amacron -135
+KPX V Aogonek -135
+KPX V Aring -135
+KPX V Atilde -135
+KPX V G -30
+KPX V Gbreve -30
+KPX V Gcommaaccent -30
+KPX V O -45
+KPX V Oacute -45
+KPX V Ocircumflex -45
+KPX V Odieresis -45
+KPX V Ograve -45
+KPX V Ohungarumlaut -45
+KPX V Omacron -45
+KPX V Oslash -45
+KPX V Otilde -45
+KPX V a -92
+KPX V aacute -92
+KPX V abreve -92
+KPX V acircumflex -92
+KPX V adieresis -92
+KPX V agrave -92
+KPX V amacron -92
+KPX V aogonek -92
+KPX V aring -92
+KPX V atilde -92
+KPX V colon -92
+KPX V comma -129
+KPX V e -100
+KPX V eacute -100
+KPX V ecaron -100
+KPX V ecircumflex -100
+KPX V edieresis -100
+KPX V edotaccent -100
+KPX V egrave -100
+KPX V emacron -100
+KPX V eogonek -100
+KPX V hyphen -74
+KPX V i -37
+KPX V iacute -37
+KPX V icircumflex -37
+KPX V idieresis -37
+KPX V igrave -37
+KPX V imacron -37
+KPX V iogonek -37
+KPX V o -100
+KPX V oacute -100
+KPX V ocircumflex -100
+KPX V odieresis -100
+KPX V ograve -100
+KPX V ohungarumlaut -100
+KPX V omacron -100
+KPX V oslash -100
+KPX V otilde -100
+KPX V period -145
+KPX V semicolon -92
+KPX V u -92
+KPX V uacute -92
+KPX V ucircumflex -92
+KPX V udieresis -92
+KPX V ugrave -92
+KPX V uhungarumlaut -92
+KPX V umacron -92
+KPX V uogonek -92
+KPX V uring -92
+KPX W A -120
+KPX W Aacute -120
+KPX W Abreve -120
+KPX W Acircumflex -120
+KPX W Adieresis -120
+KPX W Agrave -120
+KPX W Amacron -120
+KPX W Aogonek -120
+KPX W Aring -120
+KPX W Atilde -120
+KPX W O -10
+KPX W Oacute -10
+KPX W Ocircumflex -10
+KPX W Odieresis -10
+KPX W Ograve -10
+KPX W Ohungarumlaut -10
+KPX W Omacron -10
+KPX W Oslash -10
+KPX W Otilde -10
+KPX W a -65
+KPX W aacute -65
+KPX W abreve -65
+KPX W acircumflex -65
+KPX W adieresis -65
+KPX W agrave -65
+KPX W amacron -65
+KPX W aogonek -65
+KPX W aring -65
+KPX W atilde -65
+KPX W colon -55
+KPX W comma -92
+KPX W e -65
+KPX W eacute -65
+KPX W ecaron -65
+KPX W ecircumflex -65
+KPX W edieresis -65
+KPX W edotaccent -65
+KPX W egrave -65
+KPX W emacron -65
+KPX W eogonek -65
+KPX W hyphen -37
+KPX W i -18
+KPX W iacute -18
+KPX W iogonek -18
+KPX W o -75
+KPX W oacute -75
+KPX W ocircumflex -75
+KPX W odieresis -75
+KPX W ograve -75
+KPX W ohungarumlaut -75
+KPX W omacron -75
+KPX W oslash -75
+KPX W otilde -75
+KPX W period -92
+KPX W semicolon -55
+KPX W u -50
+KPX W uacute -50
+KPX W ucircumflex -50
+KPX W udieresis -50
+KPX W ugrave -50
+KPX W uhungarumlaut -50
+KPX W umacron -50
+KPX W uogonek -50
+KPX W uring -50
+KPX W y -60
+KPX W yacute -60
+KPX W ydieresis -60
+KPX Y A -110
+KPX Y Aacute -110
+KPX Y Abreve -110
+KPX Y Acircumflex -110
+KPX Y Adieresis -110
+KPX Y Agrave -110
+KPX Y Amacron -110
+KPX Y Aogonek -110
+KPX Y Aring -110
+KPX Y Atilde -110
+KPX Y O -35
+KPX Y Oacute -35
+KPX Y Ocircumflex -35
+KPX Y Odieresis -35
+KPX Y Ograve -35
+KPX Y Ohungarumlaut -35
+KPX Y Omacron -35
+KPX Y Oslash -35
+KPX Y Otilde -35
+KPX Y a -85
+KPX Y aacute -85
+KPX Y abreve -85
+KPX Y acircumflex -85
+KPX Y adieresis -85
+KPX Y agrave -85
+KPX Y amacron -85
+KPX Y aogonek -85
+KPX Y aring -85
+KPX Y atilde -85
+KPX Y colon -92
+KPX Y comma -92
+KPX Y e -111
+KPX Y eacute -111
+KPX Y ecaron -111
+KPX Y ecircumflex -111
+KPX Y edieresis -71
+KPX Y edotaccent -111
+KPX Y egrave -71
+KPX Y emacron -71
+KPX Y eogonek -111
+KPX Y hyphen -92
+KPX Y i -37
+KPX Y iacute -37
+KPX Y iogonek -37
+KPX Y o -111
+KPX Y oacute -111
+KPX Y ocircumflex -111
+KPX Y odieresis -111
+KPX Y ograve -111
+KPX Y ohungarumlaut -111
+KPX Y omacron -111
+KPX Y oslash -111
+KPX Y otilde -111
+KPX Y period -92
+KPX Y semicolon -92
+KPX Y u -92
+KPX Y uacute -92
+KPX Y ucircumflex -92
+KPX Y udieresis -92
+KPX Y ugrave -92
+KPX Y uhungarumlaut -92
+KPX Y umacron -92
+KPX Y uogonek -92
+KPX Y uring -92
+KPX Yacute A -110
+KPX Yacute Aacute -110
+KPX Yacute Abreve -110
+KPX Yacute Acircumflex -110
+KPX Yacute Adieresis -110
+KPX Yacute Agrave -110
+KPX Yacute Amacron -110
+KPX Yacute Aogonek -110
+KPX Yacute Aring -110
+KPX Yacute Atilde -110
+KPX Yacute O -35
+KPX Yacute Oacute -35
+KPX Yacute Ocircumflex -35
+KPX Yacute Odieresis -35
+KPX Yacute Ograve -35
+KPX Yacute Ohungarumlaut -35
+KPX Yacute Omacron -35
+KPX Yacute Oslash -35
+KPX Yacute Otilde -35
+KPX Yacute a -85
+KPX Yacute aacute -85
+KPX Yacute abreve -85
+KPX Yacute acircumflex -85
+KPX Yacute adieresis -85
+KPX Yacute agrave -85
+KPX Yacute amacron -85
+KPX Yacute aogonek -85
+KPX Yacute aring -85
+KPX Yacute atilde -85
+KPX Yacute colon -92
+KPX Yacute comma -92
+KPX Yacute e -111
+KPX Yacute eacute -111
+KPX Yacute ecaron -111
+KPX Yacute ecircumflex -111
+KPX Yacute edieresis -71
+KPX Yacute edotaccent -111
+KPX Yacute egrave -71
+KPX Yacute emacron -71
+KPX Yacute eogonek -111
+KPX Yacute hyphen -92
+KPX Yacute i -37
+KPX Yacute iacute -37
+KPX Yacute iogonek -37
+KPX Yacute o -111
+KPX Yacute oacute -111
+KPX Yacute ocircumflex -111
+KPX Yacute odieresis -111
+KPX Yacute ograve -111
+KPX Yacute ohungarumlaut -111
+KPX Yacute omacron -111
+KPX Yacute oslash -111
+KPX Yacute otilde -111
+KPX Yacute period -92
+KPX Yacute semicolon -92
+KPX Yacute u -92
+KPX Yacute uacute -92
+KPX Yacute ucircumflex -92
+KPX Yacute udieresis -92
+KPX Yacute ugrave -92
+KPX Yacute uhungarumlaut -92
+KPX Yacute umacron -92
+KPX Yacute uogonek -92
+KPX Yacute uring -92
+KPX Ydieresis A -110
+KPX Ydieresis Aacute -110
+KPX Ydieresis Abreve -110
+KPX Ydieresis Acircumflex -110
+KPX Ydieresis Adieresis -110
+KPX Ydieresis Agrave -110
+KPX Ydieresis Amacron -110
+KPX Ydieresis Aogonek -110
+KPX Ydieresis Aring -110
+KPX Ydieresis Atilde -110
+KPX Ydieresis O -35
+KPX Ydieresis Oacute -35
+KPX Ydieresis Ocircumflex -35
+KPX Ydieresis Odieresis -35
+KPX Ydieresis Ograve -35
+KPX Ydieresis Ohungarumlaut -35
+KPX Ydieresis Omacron -35
+KPX Ydieresis Oslash -35
+KPX Ydieresis Otilde -35
+KPX Ydieresis a -85
+KPX Ydieresis aacute -85
+KPX Ydieresis abreve -85
+KPX Ydieresis acircumflex -85
+KPX Ydieresis adieresis -85
+KPX Ydieresis agrave -85
+KPX Ydieresis amacron -85
+KPX Ydieresis aogonek -85
+KPX Ydieresis aring -85
+KPX Ydieresis atilde -85
+KPX Ydieresis colon -92
+KPX Ydieresis comma -92
+KPX Ydieresis e -111
+KPX Ydieresis eacute -111
+KPX Ydieresis ecaron -111
+KPX Ydieresis ecircumflex -111
+KPX Ydieresis edieresis -71
+KPX Ydieresis edotaccent -111
+KPX Ydieresis egrave -71
+KPX Ydieresis emacron -71
+KPX Ydieresis eogonek -111
+KPX Ydieresis hyphen -92
+KPX Ydieresis i -37
+KPX Ydieresis iacute -37
+KPX Ydieresis iogonek -37
+KPX Ydieresis o -111
+KPX Ydieresis oacute -111
+KPX Ydieresis ocircumflex -111
+KPX Ydieresis odieresis -111
+KPX Ydieresis ograve -111
+KPX Ydieresis ohungarumlaut -111
+KPX Ydieresis omacron -111
+KPX Ydieresis oslash -111
+KPX Ydieresis otilde -111
+KPX Ydieresis period -92
+KPX Ydieresis semicolon -92
+KPX Ydieresis u -92
+KPX Ydieresis uacute -92
+KPX Ydieresis ucircumflex -92
+KPX Ydieresis udieresis -92
+KPX Ydieresis ugrave -92
+KPX Ydieresis uhungarumlaut -92
+KPX Ydieresis umacron -92
+KPX Ydieresis uogonek -92
+KPX Ydieresis uring -92
+KPX a v -25
+KPX aacute v -25
+KPX abreve v -25
+KPX acircumflex v -25
+KPX adieresis v -25
+KPX agrave v -25
+KPX amacron v -25
+KPX aogonek v -25
+KPX aring v -25
+KPX atilde v -25
+KPX b b -10
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -15
+KPX comma quotedblright -45
+KPX comma quoteright -55
+KPX d w -15
+KPX dcroat w -15
+KPX e v -15
+KPX eacute v -15
+KPX ecaron v -15
+KPX ecircumflex v -15
+KPX edieresis v -15
+KPX edotaccent v -15
+KPX egrave v -15
+KPX emacron v -15
+KPX eogonek v -15
+KPX f comma -15
+KPX f dotlessi -35
+KPX f i -25
+KPX f o -25
+KPX f oacute -25
+KPX f ocircumflex -25
+KPX f odieresis -25
+KPX f ograve -25
+KPX f ohungarumlaut -25
+KPX f omacron -25
+KPX f oslash -25
+KPX f otilde -25
+KPX f period -15
+KPX f quotedblright 50
+KPX f quoteright 55
+KPX g period -15
+KPX gbreve period -15
+KPX gcommaaccent period -15
+KPX h y -15
+KPX h yacute -15
+KPX h ydieresis -15
+KPX i v -10
+KPX iacute v -10
+KPX icircumflex v -10
+KPX idieresis v -10
+KPX igrave v -10
+KPX imacron v -10
+KPX iogonek v -10
+KPX k e -10
+KPX k eacute -10
+KPX k ecaron -10
+KPX k ecircumflex -10
+KPX k edieresis -10
+KPX k edotaccent -10
+KPX k egrave -10
+KPX k emacron -10
+KPX k eogonek -10
+KPX k o -15
+KPX k oacute -15
+KPX k ocircumflex -15
+KPX k odieresis -15
+KPX k ograve -15
+KPX k ohungarumlaut -15
+KPX k omacron -15
+KPX k oslash -15
+KPX k otilde -15
+KPX k y -15
+KPX k yacute -15
+KPX k ydieresis -15
+KPX kcommaaccent e -10
+KPX kcommaaccent eacute -10
+KPX kcommaaccent ecaron -10
+KPX kcommaaccent ecircumflex -10
+KPX kcommaaccent edieresis -10
+KPX kcommaaccent edotaccent -10
+KPX kcommaaccent egrave -10
+KPX kcommaaccent emacron -10
+KPX kcommaaccent eogonek -10
+KPX kcommaaccent o -15
+KPX kcommaaccent oacute -15
+KPX kcommaaccent ocircumflex -15
+KPX kcommaaccent odieresis -15
+KPX kcommaaccent ograve -15
+KPX kcommaaccent ohungarumlaut -15
+KPX kcommaaccent omacron -15
+KPX kcommaaccent oslash -15
+KPX kcommaaccent otilde -15
+KPX kcommaaccent y -15
+KPX kcommaaccent yacute -15
+KPX kcommaaccent ydieresis -15
+KPX n v -40
+KPX nacute v -40
+KPX ncaron v -40
+KPX ncommaaccent v -40
+KPX ntilde v -40
+KPX o v -10
+KPX o w -10
+KPX oacute v -10
+KPX oacute w -10
+KPX ocircumflex v -10
+KPX ocircumflex w -10
+KPX odieresis v -10
+KPX odieresis w -10
+KPX ograve v -10
+KPX ograve w -10
+KPX ohungarumlaut v -10
+KPX ohungarumlaut w -10
+KPX omacron v -10
+KPX omacron w -10
+KPX oslash v -10
+KPX oslash w -10
+KPX otilde v -10
+KPX otilde w -10
+KPX period quotedblright -55
+KPX period quoteright -55
+KPX quotedblleft A -10
+KPX quotedblleft Aacute -10
+KPX quotedblleft Abreve -10
+KPX quotedblleft Acircumflex -10
+KPX quotedblleft Adieresis -10
+KPX quotedblleft Agrave -10
+KPX quotedblleft Amacron -10
+KPX quotedblleft Aogonek -10
+KPX quotedblleft Aring -10
+KPX quotedblleft Atilde -10
+KPX quoteleft A -10
+KPX quoteleft Aacute -10
+KPX quoteleft Abreve -10
+KPX quoteleft Acircumflex -10
+KPX quoteleft Adieresis -10
+KPX quoteleft Agrave -10
+KPX quoteleft Amacron -10
+KPX quoteleft Aogonek -10
+KPX quoteleft Aring -10
+KPX quoteleft Atilde -10
+KPX quoteleft quoteleft -63
+KPX quoteright d -20
+KPX quoteright dcroat -20
+KPX quoteright quoteright -63
+KPX quoteright r -20
+KPX quoteright racute -20
+KPX quoteright rcaron -20
+KPX quoteright rcommaaccent -20
+KPX quoteright s -37
+KPX quoteright sacute -37
+KPX quoteright scaron -37
+KPX quoteright scedilla -37
+KPX quoteright scommaaccent -37
+KPX quoteright space -74
+KPX quoteright v -20
+KPX r c -18
+KPX r cacute -18
+KPX r ccaron -18
+KPX r ccedilla -18
+KPX r comma -92
+KPX r e -18
+KPX r eacute -18
+KPX r ecaron -18
+KPX r ecircumflex -18
+KPX r edieresis -18
+KPX r edotaccent -18
+KPX r egrave -18
+KPX r emacron -18
+KPX r eogonek -18
+KPX r g -10
+KPX r gbreve -10
+KPX r gcommaaccent -10
+KPX r hyphen -37
+KPX r n -15
+KPX r nacute -15
+KPX r ncaron -15
+KPX r ncommaaccent -15
+KPX r ntilde -15
+KPX r o -18
+KPX r oacute -18
+KPX r ocircumflex -18
+KPX r odieresis -18
+KPX r ograve -18
+KPX r ohungarumlaut -18
+KPX r omacron -18
+KPX r oslash -18
+KPX r otilde -18
+KPX r p -10
+KPX r period -100
+KPX r q -18
+KPX r v -10
+KPX racute c -18
+KPX racute cacute -18
+KPX racute ccaron -18
+KPX racute ccedilla -18
+KPX racute comma -92
+KPX racute e -18
+KPX racute eacute -18
+KPX racute ecaron -18
+KPX racute ecircumflex -18
+KPX racute edieresis -18
+KPX racute edotaccent -18
+KPX racute egrave -18
+KPX racute emacron -18
+KPX racute eogonek -18
+KPX racute g -10
+KPX racute gbreve -10
+KPX racute gcommaaccent -10
+KPX racute hyphen -37
+KPX racute n -15
+KPX racute nacute -15
+KPX racute ncaron -15
+KPX racute ncommaaccent -15
+KPX racute ntilde -15
+KPX racute o -18
+KPX racute oacute -18
+KPX racute ocircumflex -18
+KPX racute odieresis -18
+KPX racute ograve -18
+KPX racute ohungarumlaut -18
+KPX racute omacron -18
+KPX racute oslash -18
+KPX racute otilde -18
+KPX racute p -10
+KPX racute period -100
+KPX racute q -18
+KPX racute v -10
+KPX rcaron c -18
+KPX rcaron cacute -18
+KPX rcaron ccaron -18
+KPX rcaron ccedilla -18
+KPX rcaron comma -92
+KPX rcaron e -18
+KPX rcaron eacute -18
+KPX rcaron ecaron -18
+KPX rcaron ecircumflex -18
+KPX rcaron edieresis -18
+KPX rcaron edotaccent -18
+KPX rcaron egrave -18
+KPX rcaron emacron -18
+KPX rcaron eogonek -18
+KPX rcaron g -10
+KPX rcaron gbreve -10
+KPX rcaron gcommaaccent -10
+KPX rcaron hyphen -37
+KPX rcaron n -15
+KPX rcaron nacute -15
+KPX rcaron ncaron -15
+KPX rcaron ncommaaccent -15
+KPX rcaron ntilde -15
+KPX rcaron o -18
+KPX rcaron oacute -18
+KPX rcaron ocircumflex -18
+KPX rcaron odieresis -18
+KPX rcaron ograve -18
+KPX rcaron ohungarumlaut -18
+KPX rcaron omacron -18
+KPX rcaron oslash -18
+KPX rcaron otilde -18
+KPX rcaron p -10
+KPX rcaron period -100
+KPX rcaron q -18
+KPX rcaron v -10
+KPX rcommaaccent c -18
+KPX rcommaaccent cacute -18
+KPX rcommaaccent ccaron -18
+KPX rcommaaccent ccedilla -18
+KPX rcommaaccent comma -92
+KPX rcommaaccent e -18
+KPX rcommaaccent eacute -18
+KPX rcommaaccent ecaron -18
+KPX rcommaaccent ecircumflex -18
+KPX rcommaaccent edieresis -18
+KPX rcommaaccent edotaccent -18
+KPX rcommaaccent egrave -18
+KPX rcommaaccent emacron -18
+KPX rcommaaccent eogonek -18
+KPX rcommaaccent g -10
+KPX rcommaaccent gbreve -10
+KPX rcommaaccent gcommaaccent -10
+KPX rcommaaccent hyphen -37
+KPX rcommaaccent n -15
+KPX rcommaaccent nacute -15
+KPX rcommaaccent ncaron -15
+KPX rcommaaccent ncommaaccent -15
+KPX rcommaaccent ntilde -15
+KPX rcommaaccent o -18
+KPX rcommaaccent oacute -18
+KPX rcommaaccent ocircumflex -18
+KPX rcommaaccent odieresis -18
+KPX rcommaaccent ograve -18
+KPX rcommaaccent ohungarumlaut -18
+KPX rcommaaccent omacron -18
+KPX rcommaaccent oslash -18
+KPX rcommaaccent otilde -18
+KPX rcommaaccent p -10
+KPX rcommaaccent period -100
+KPX rcommaaccent q -18
+KPX rcommaaccent v -10
+KPX space A -55
+KPX space Aacute -55
+KPX space Abreve -55
+KPX space Acircumflex -55
+KPX space Adieresis -55
+KPX space Agrave -55
+KPX space Amacron -55
+KPX space Aogonek -55
+KPX space Aring -55
+KPX space Atilde -55
+KPX space T -30
+KPX space Tcaron -30
+KPX space Tcommaaccent -30
+KPX space V -45
+KPX space W -30
+KPX space Y -55
+KPX space Yacute -55
+KPX space Ydieresis -55
+KPX v a -10
+KPX v aacute -10
+KPX v abreve -10
+KPX v acircumflex -10
+KPX v adieresis -10
+KPX v agrave -10
+KPX v amacron -10
+KPX v aogonek -10
+KPX v aring -10
+KPX v atilde -10
+KPX v comma -55
+KPX v e -10
+KPX v eacute -10
+KPX v ecaron -10
+KPX v ecircumflex -10
+KPX v edieresis -10
+KPX v edotaccent -10
+KPX v egrave -10
+KPX v emacron -10
+KPX v eogonek -10
+KPX v o -10
+KPX v oacute -10
+KPX v ocircumflex -10
+KPX v odieresis -10
+KPX v ograve -10
+KPX v ohungarumlaut -10
+KPX v omacron -10
+KPX v oslash -10
+KPX v otilde -10
+KPX v period -70
+KPX w comma -55
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -70
+KPX y comma -55
+KPX y e -10
+KPX y eacute -10
+KPX y ecaron -10
+KPX y ecircumflex -10
+KPX y edieresis -10
+KPX y edotaccent -10
+KPX y egrave -10
+KPX y emacron -10
+KPX y eogonek -10
+KPX y o -25
+KPX y oacute -25
+KPX y ocircumflex -25
+KPX y odieresis -25
+KPX y ograve -25
+KPX y ohungarumlaut -25
+KPX y omacron -25
+KPX y oslash -25
+KPX y otilde -25
+KPX y period -70
+KPX yacute comma -55
+KPX yacute e -10
+KPX yacute eacute -10
+KPX yacute ecaron -10
+KPX yacute ecircumflex -10
+KPX yacute edieresis -10
+KPX yacute edotaccent -10
+KPX yacute egrave -10
+KPX yacute emacron -10
+KPX yacute eogonek -10
+KPX yacute o -25
+KPX yacute oacute -25
+KPX yacute ocircumflex -25
+KPX yacute odieresis -25
+KPX yacute ograve -25
+KPX yacute ohungarumlaut -25
+KPX yacute omacron -25
+KPX yacute oslash -25
+KPX yacute otilde -25
+KPX yacute period -70
+KPX ydieresis comma -55
+KPX ydieresis e -10
+KPX ydieresis eacute -10
+KPX ydieresis ecaron -10
+KPX ydieresis ecircumflex -10
+KPX ydieresis edieresis -10
+KPX ydieresis edotaccent -10
+KPX ydieresis egrave -10
+KPX ydieresis emacron -10
+KPX ydieresis eogonek -10
+KPX ydieresis o -25
+KPX ydieresis oacute -25
+KPX ydieresis ocircumflex -25
+KPX ydieresis odieresis -25
+KPX ydieresis ograve -25
+KPX ydieresis ohungarumlaut -25
+KPX ydieresis omacron -25
+KPX ydieresis oslash -25
+KPX ydieresis otilde -25
+KPX ydieresis period -70
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-BoldItalic.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-BoldItalic.afm
new file mode 100644
index 0000000..6bc0683
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-BoldItalic.afm
@@ -0,0 +1,2386 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 13:04:06 1997
+Comment UniqueID 43066
+Comment VMusage 45874 56899
+FontName Times-BoldItalic
+FullName Times Bold Italic
+FamilyName Times
+Weight Bold
+ItalicAngle -15
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -200 -218 996 921
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 669
+XHeight 462
+Ascender 683
+Descender -217
+StdHW 42
+StdVW 121
+StartCharMetrics 317
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 389 ; N exclam ; B 67 -13 370 684 ;
+C 34 ; WX 555 ; N quotedbl ; B 136 398 536 685 ;
+C 35 ; WX 500 ; N numbersign ; B -33 0 533 700 ;
+C 36 ; WX 500 ; N dollar ; B -20 -100 497 733 ;
+C 37 ; WX 833 ; N percent ; B 39 -10 793 692 ;
+C 38 ; WX 778 ; N ampersand ; B 5 -19 699 682 ;
+C 146 ; WX 333 ; N quoteright ; B 98 369 302 685 ;
+C 40 ; WX 333 ; N parenleft ; B 28 -179 344 685 ;
+C 41 ; WX 333 ; N parenright ; B -44 -179 271 685 ;
+C 42 ; WX 500 ; N asterisk ; B 65 249 456 685 ;
+C 43 ; WX 570 ; N plus ; B 33 0 537 506 ;
+C 44 ; WX 250 ; N comma ; B -60 -182 144 134 ;
+C 45 ; WX 333 ; N hyphen ; B 2 166 271 282 ;
+C 173 ; WX 333 ; N hyphen ; B 2 166 271 282 ;
+C 46 ; WX 250 ; N period ; B -9 -13 139 135 ;
+C 47 ; WX 278 ; N slash ; B -64 -18 342 685 ;
+C 48 ; WX 500 ; N zero ; B 17 -14 477 683 ;
+C 49 ; WX 500 ; N one ; B 5 0 419 683 ;
+C 50 ; WX 500 ; N two ; B -27 0 446 683 ;
+C 51 ; WX 500 ; N three ; B -15 -13 450 683 ;
+C 52 ; WX 500 ; N four ; B -15 0 503 683 ;
+C 53 ; WX 500 ; N five ; B -11 -13 487 669 ;
+C 54 ; WX 500 ; N six ; B 23 -15 509 679 ;
+C 55 ; WX 500 ; N seven ; B 52 0 525 669 ;
+C 56 ; WX 500 ; N eight ; B 3 -13 476 683 ;
+C 57 ; WX 500 ; N nine ; B -12 -10 475 683 ;
+C 58 ; WX 333 ; N colon ; B 23 -13 264 459 ;
+C 59 ; WX 333 ; N semicolon ; B -25 -183 264 459 ;
+C 60 ; WX 570 ; N less ; B 31 -8 539 514 ;
+C 61 ; WX 570 ; N equal ; B 33 107 537 399 ;
+C 62 ; WX 570 ; N greater ; B 31 -8 539 514 ;
+C 63 ; WX 500 ; N question ; B 79 -13 470 684 ;
+C 64 ; WX 832 ; N at ; B 63 -18 770 685 ;
+C 65 ; WX 667 ; N A ; B -67 0 593 683 ;
+C 66 ; WX 667 ; N B ; B -24 0 624 669 ;
+C 67 ; WX 667 ; N C ; B 32 -18 677 685 ;
+C 68 ; WX 722 ; N D ; B -46 0 685 669 ;
+C 69 ; WX 667 ; N E ; B -27 0 653 669 ;
+C 70 ; WX 667 ; N F ; B -13 0 660 669 ;
+C 71 ; WX 722 ; N G ; B 21 -18 706 685 ;
+C 72 ; WX 778 ; N H ; B -24 0 799 669 ;
+C 73 ; WX 389 ; N I ; B -32 0 406 669 ;
+C 74 ; WX 500 ; N J ; B -46 -99 524 669 ;
+C 75 ; WX 667 ; N K ; B -21 0 702 669 ;
+C 76 ; WX 611 ; N L ; B -22 0 590 669 ;
+C 77 ; WX 889 ; N M ; B -29 -12 917 669 ;
+C 78 ; WX 722 ; N N ; B -27 -15 748 669 ;
+C 79 ; WX 722 ; N O ; B 27 -18 691 685 ;
+C 80 ; WX 611 ; N P ; B -27 0 613 669 ;
+C 81 ; WX 722 ; N Q ; B 27 -208 691 685 ;
+C 82 ; WX 667 ; N R ; B -29 0 623 669 ;
+C 83 ; WX 556 ; N S ; B 2 -18 526 685 ;
+C 84 ; WX 611 ; N T ; B 50 0 650 669 ;
+C 85 ; WX 722 ; N U ; B 67 -18 744 669 ;
+C 86 ; WX 667 ; N V ; B 65 -18 715 669 ;
+C 87 ; WX 889 ; N W ; B 65 -18 940 669 ;
+C 88 ; WX 667 ; N X ; B -24 0 694 669 ;
+C 89 ; WX 611 ; N Y ; B 73 0 659 669 ;
+C 90 ; WX 611 ; N Z ; B -11 0 590 669 ;
+C 91 ; WX 333 ; N bracketleft ; B -37 -159 362 674 ;
+C 92 ; WX 278 ; N backslash ; B -1 -18 279 685 ;
+C 93 ; WX 333 ; N bracketright ; B -56 -157 343 674 ;
+C 94 ; WX 570 ; N asciicircum ; B 67 304 503 669 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 145 ; WX 333 ; N quoteleft ; B 128 369 332 685 ;
+C 97 ; WX 500 ; N a ; B -21 -14 455 462 ;
+C 98 ; WX 500 ; N b ; B -14 -13 444 699 ;
+C 99 ; WX 444 ; N c ; B -5 -13 392 462 ;
+C 100 ; WX 500 ; N d ; B -21 -13 517 699 ;
+C 101 ; WX 444 ; N e ; B 5 -13 398 462 ;
+C 102 ; WX 333 ; N f ; B -169 -205 446 698 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B -52 -203 478 462 ;
+C 104 ; WX 556 ; N h ; B -13 -9 498 699 ;
+C 105 ; WX 278 ; N i ; B 2 -9 263 684 ;
+C 106 ; WX 278 ; N j ; B -189 -207 279 684 ;
+C 107 ; WX 500 ; N k ; B -23 -8 483 699 ;
+C 108 ; WX 278 ; N l ; B 2 -9 290 699 ;
+C 109 ; WX 778 ; N m ; B -14 -9 722 462 ;
+C 110 ; WX 556 ; N n ; B -6 -9 493 462 ;
+C 111 ; WX 500 ; N o ; B -3 -13 441 462 ;
+C 112 ; WX 500 ; N p ; B -120 -205 446 462 ;
+C 113 ; WX 500 ; N q ; B 1 -205 471 462 ;
+C 114 ; WX 389 ; N r ; B -21 0 389 462 ;
+C 115 ; WX 389 ; N s ; B -19 -13 333 462 ;
+C 116 ; WX 278 ; N t ; B -11 -9 281 594 ;
+C 117 ; WX 556 ; N u ; B 15 -9 492 462 ;
+C 118 ; WX 444 ; N v ; B 16 -13 401 462 ;
+C 119 ; WX 667 ; N w ; B 16 -13 614 462 ;
+C 120 ; WX 500 ; N x ; B -46 -13 469 462 ;
+C 121 ; WX 444 ; N y ; B -94 -205 392 462 ;
+C 122 ; WX 389 ; N z ; B -43 -78 368 449 ;
+C 123 ; WX 348 ; N braceleft ; B 5 -187 436 686 ;
+C 124 ; WX 220 ; N bar ; B 66 -218 154 782 ;
+C 125 ; WX 348 ; N braceright ; B -129 -187 302 686 ;
+C 126 ; WX 570 ; N asciitilde ; B 54 173 516 333 ;
+C 161 ; WX 389 ; N exclamdown ; B 19 -205 322 492 ;
+C 162 ; WX 500 ; N cent ; B 42 -143 439 576 ;
+C 163 ; WX 500 ; N sterling ; B -32 -12 510 683 ;
+C -1 ; WX 167 ; N fraction ; B -169 -14 324 683 ;
+C 165 ; WX 500 ; N yen ; B 33 0 628 669 ;
+C 131 ; WX 500 ; N florin ; B -87 -156 537 707 ;
+C 167 ; WX 500 ; N section ; B 36 -143 459 685 ;
+C 164 ; WX 500 ; N currency ; B -26 34 526 586 ;
+C 39 ; WX 278 ; N quotesingle ; B 128 398 268 685 ;
+C 147 ; WX 500 ; N quotedblleft ; B 53 369 513 685 ;
+C 171 ; WX 500 ; N guillemotleft ; B 12 32 468 415 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 32 32 303 415 ;
+C 155 ; WX 333 ; N guilsinglright ; B 10 32 281 415 ;
+C -1 ; WX 556 ; N fi ; B -188 -205 514 703 ;
+C -1 ; WX 556 ; N fl ; B -186 -205 553 704 ;
+C 150 ; WX 500 ; N endash ; B -40 178 477 269 ;
+C 134 ; WX 500 ; N dagger ; B 91 -145 494 685 ;
+C 135 ; WX 500 ; N daggerdbl ; B 10 -139 493 685 ;
+C 183 ; WX 250 ; N periodcentered ; B 51 257 199 405 ;
+C 182 ; WX 500 ; N paragraph ; B -57 -193 562 669 ;
+C 149 ; WX 350 ; N bullet ; B 0 175 350 525 ;
+C 130 ; WX 333 ; N quotesinglbase ; B -5 -182 199 134 ;
+C 132 ; WX 500 ; N quotedblbase ; B -57 -182 403 134 ;
+C 148 ; WX 500 ; N quotedblright ; B 53 369 513 685 ;
+C 187 ; WX 500 ; N guillemotright ; B 12 32 468 415 ;
+C 133 ; WX 1000 ; N ellipsis ; B 40 -13 852 135 ;
+C 137 ; WX 1000 ; N perthousand ; B 7 -29 996 706 ;
+C 191 ; WX 500 ; N questiondown ; B 30 -205 421 492 ;
+C 96 ; WX 333 ; N grave ; B 85 516 297 697 ;
+C 180 ; WX 333 ; N acute ; B 139 516 379 697 ;
+C 136 ; WX 333 ; N circumflex ; B 40 516 367 690 ;
+C 152 ; WX 333 ; N tilde ; B 48 536 407 655 ;
+C 175 ; WX 333 ; N macron ; B 51 553 393 623 ;
+C -1 ; WX 333 ; N breve ; B 71 516 387 678 ;
+C -1 ; WX 333 ; N dotaccent ; B 163 550 298 684 ;
+C 168 ; WX 333 ; N dieresis ; B 55 550 402 684 ;
+C -1 ; WX 333 ; N ring ; B 127 516 340 729 ;
+C 184 ; WX 333 ; N cedilla ; B -80 -218 156 5 ;
+C -1 ; WX 333 ; N hungarumlaut ; B 69 516 498 697 ;
+C -1 ; WX 333 ; N ogonek ; B 15 -183 244 34 ;
+C -1 ; WX 333 ; N caron ; B 79 516 411 690 ;
+C 151 ; WX 1000 ; N emdash ; B -40 178 977 269 ;
+C 198 ; WX 944 ; N AE ; B -64 0 918 669 ;
+C 170 ; WX 266 ; N ordfeminine ; B 16 399 330 685 ;
+C -1 ; WX 611 ; N Lslash ; B -22 0 590 669 ;
+C 216 ; WX 722 ; N Oslash ; B 27 -125 691 764 ;
+C 140 ; WX 944 ; N OE ; B 23 -8 946 677 ;
+C 186 ; WX 300 ; N ordmasculine ; B 56 400 347 685 ;
+C 230 ; WX 722 ; N ae ; B -5 -13 673 462 ;
+C -1 ; WX 278 ; N dotlessi ; B 2 -9 238 462 ;
+C -1 ; WX 278 ; N lslash ; B -7 -9 307 699 ;
+C 248 ; WX 500 ; N oslash ; B -3 -119 441 560 ;
+C 156 ; WX 722 ; N oe ; B 6 -13 674 462 ;
+C 223 ; WX 500 ; N germandbls ; B -200 -200 473 705 ;
+C 207 ; WX 389 ; N Idieresis ; B -32 0 450 862 ;
+C 233 ; WX 444 ; N eacute ; B 5 -13 435 697 ;
+C -1 ; WX 500 ; N abreve ; B -21 -14 471 678 ;
+C -1 ; WX 556 ; N uhungarumlaut ; B 15 -9 610 697 ;
+C -1 ; WX 444 ; N ecaron ; B 5 -13 467 690 ;
+C 159 ; WX 611 ; N Ydieresis ; B 73 0 659 862 ;
+C 247 ; WX 570 ; N divide ; B 33 -29 537 535 ;
+C 221 ; WX 611 ; N Yacute ; B 73 0 659 904 ;
+C 194 ; WX 667 ; N Acircumflex ; B -67 0 593 897 ;
+C 225 ; WX 500 ; N aacute ; B -21 -14 463 697 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 67 -18 744 897 ;
+C 253 ; WX 444 ; N yacute ; B -94 -205 435 697 ;
+C -1 ; WX 389 ; N scommaaccent ; B -19 -218 333 462 ;
+C 234 ; WX 444 ; N ecircumflex ; B 5 -13 423 690 ;
+C -1 ; WX 722 ; N Uring ; B 67 -18 744 921 ;
+C 220 ; WX 722 ; N Udieresis ; B 67 -18 744 862 ;
+C -1 ; WX 500 ; N aogonek ; B -21 -183 455 462 ;
+C 218 ; WX 722 ; N Uacute ; B 67 -18 744 904 ;
+C -1 ; WX 556 ; N uogonek ; B 15 -183 492 462 ;
+C 203 ; WX 667 ; N Edieresis ; B -27 0 653 862 ;
+C -1 ; WX 722 ; N Dcroat ; B -31 0 700 669 ;
+C -1 ; WX 250 ; N commaaccent ; B -36 -218 131 -50 ;
+C 169 ; WX 747 ; N copyright ; B 30 -18 718 685 ;
+C -1 ; WX 667 ; N Emacron ; B -27 0 653 830 ;
+C -1 ; WX 444 ; N ccaron ; B -5 -13 467 690 ;
+C 229 ; WX 500 ; N aring ; B -21 -14 455 729 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B -27 -218 748 669 ;
+C -1 ; WX 278 ; N lacute ; B 2 -9 392 904 ;
+C 224 ; WX 500 ; N agrave ; B -21 -14 455 697 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 50 -218 650 669 ;
+C -1 ; WX 667 ; N Cacute ; B 32 -18 677 904 ;
+C 227 ; WX 500 ; N atilde ; B -21 -14 491 655 ;
+C -1 ; WX 667 ; N Edotaccent ; B -27 0 653 862 ;
+C 154 ; WX 389 ; N scaron ; B -19 -13 424 690 ;
+C -1 ; WX 389 ; N scedilla ; B -19 -218 333 462 ;
+C 237 ; WX 278 ; N iacute ; B 2 -9 352 697 ;
+C -1 ; WX 494 ; N lozenge ; B 10 0 484 745 ;
+C -1 ; WX 667 ; N Rcaron ; B -29 0 623 897 ;
+C -1 ; WX 722 ; N Gcommaaccent ; B 21 -218 706 685 ;
+C 251 ; WX 556 ; N ucircumflex ; B 15 -9 492 690 ;
+C 226 ; WX 500 ; N acircumflex ; B -21 -14 455 690 ;
+C -1 ; WX 667 ; N Amacron ; B -67 0 593 830 ;
+C -1 ; WX 389 ; N rcaron ; B -21 0 424 690 ;
+C 231 ; WX 444 ; N ccedilla ; B -5 -218 392 462 ;
+C -1 ; WX 611 ; N Zdotaccent ; B -11 0 590 862 ;
+C 222 ; WX 611 ; N Thorn ; B -27 0 573 669 ;
+C -1 ; WX 722 ; N Omacron ; B 27 -18 691 830 ;
+C -1 ; WX 667 ; N Racute ; B -29 0 623 904 ;
+C -1 ; WX 556 ; N Sacute ; B 2 -18 531 904 ;
+C -1 ; WX 608 ; N dcaron ; B -21 -13 675 708 ;
+C -1 ; WX 722 ; N Umacron ; B 67 -18 744 830 ;
+C -1 ; WX 556 ; N uring ; B 15 -9 492 729 ;
+C 179 ; WX 300 ; N threesuperior ; B 17 265 321 683 ;
+C 210 ; WX 722 ; N Ograve ; B 27 -18 691 904 ;
+C 192 ; WX 667 ; N Agrave ; B -67 0 593 904 ;
+C -1 ; WX 667 ; N Abreve ; B -67 0 593 885 ;
+C 215 ; WX 570 ; N multiply ; B 48 16 522 490 ;
+C 250 ; WX 556 ; N uacute ; B 15 -9 492 697 ;
+C -1 ; WX 611 ; N Tcaron ; B 50 0 650 897 ;
+C -1 ; WX 494 ; N partialdiff ; B 11 -21 494 750 ;
+C 255 ; WX 444 ; N ydieresis ; B -94 -205 443 655 ;
+C -1 ; WX 722 ; N Nacute ; B -27 -15 748 904 ;
+C 238 ; WX 278 ; N icircumflex ; B -3 -9 324 690 ;
+C 202 ; WX 667 ; N Ecircumflex ; B -27 0 653 897 ;
+C 228 ; WX 500 ; N adieresis ; B -21 -14 476 655 ;
+C 235 ; WX 444 ; N edieresis ; B 5 -13 448 655 ;
+C -1 ; WX 444 ; N cacute ; B -5 -13 435 697 ;
+C -1 ; WX 556 ; N nacute ; B -6 -9 493 697 ;
+C -1 ; WX 556 ; N umacron ; B 15 -9 492 623 ;
+C -1 ; WX 722 ; N Ncaron ; B -27 -15 748 897 ;
+C 205 ; WX 389 ; N Iacute ; B -32 0 432 904 ;
+C 177 ; WX 570 ; N plusminus ; B 33 0 537 506 ;
+C 166 ; WX 220 ; N brokenbar ; B 66 -143 154 707 ;
+C 174 ; WX 747 ; N registered ; B 30 -18 718 685 ;
+C -1 ; WX 722 ; N Gbreve ; B 21 -18 706 885 ;
+C -1 ; WX 389 ; N Idotaccent ; B -32 0 406 862 ;
+C -1 ; WX 600 ; N summation ; B 14 -10 585 706 ;
+C 200 ; WX 667 ; N Egrave ; B -27 0 653 904 ;
+C -1 ; WX 389 ; N racute ; B -21 0 407 697 ;
+C -1 ; WX 500 ; N omacron ; B -3 -13 462 623 ;
+C -1 ; WX 611 ; N Zacute ; B -11 0 590 904 ;
+C 142 ; WX 611 ; N Zcaron ; B -11 0 590 897 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 704 ;
+C 208 ; WX 722 ; N Eth ; B -31 0 700 669 ;
+C 199 ; WX 667 ; N Ccedilla ; B 32 -218 677 685 ;
+C -1 ; WX 278 ; N lcommaaccent ; B -42 -218 290 699 ;
+C -1 ; WX 366 ; N tcaron ; B -11 -9 434 754 ;
+C -1 ; WX 444 ; N eogonek ; B 5 -183 398 462 ;
+C -1 ; WX 722 ; N Uogonek ; B 67 -183 744 669 ;
+C 193 ; WX 667 ; N Aacute ; B -67 0 593 904 ;
+C 196 ; WX 667 ; N Adieresis ; B -67 0 593 862 ;
+C 232 ; WX 444 ; N egrave ; B 5 -13 398 697 ;
+C -1 ; WX 389 ; N zacute ; B -43 -78 407 697 ;
+C -1 ; WX 278 ; N iogonek ; B -20 -183 263 684 ;
+C 211 ; WX 722 ; N Oacute ; B 27 -18 691 904 ;
+C 243 ; WX 500 ; N oacute ; B -3 -13 463 697 ;
+C -1 ; WX 500 ; N amacron ; B -21 -14 467 623 ;
+C -1 ; WX 389 ; N sacute ; B -19 -13 407 697 ;
+C 239 ; WX 278 ; N idieresis ; B 2 -9 364 655 ;
+C 212 ; WX 722 ; N Ocircumflex ; B 27 -18 691 897 ;
+C 217 ; WX 722 ; N Ugrave ; B 67 -18 744 904 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 500 ; N thorn ; B -120 -205 446 699 ;
+C 178 ; WX 300 ; N twosuperior ; B 2 274 313 683 ;
+C 214 ; WX 722 ; N Odieresis ; B 27 -18 691 862 ;
+C 181 ; WX 576 ; N mu ; B -60 -207 516 449 ;
+C 236 ; WX 278 ; N igrave ; B 2 -9 259 697 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B -3 -13 582 697 ;
+C -1 ; WX 667 ; N Eogonek ; B -27 -183 653 669 ;
+C -1 ; WX 500 ; N dcroat ; B -21 -13 552 699 ;
+C 190 ; WX 750 ; N threequarters ; B 7 -14 726 683 ;
+C -1 ; WX 556 ; N Scedilla ; B 2 -218 526 685 ;
+C -1 ; WX 382 ; N lcaron ; B 2 -9 448 708 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B -21 -218 702 669 ;
+C -1 ; WX 611 ; N Lacute ; B -22 0 590 904 ;
+C 153 ; WX 1000 ; N trademark ; B 32 263 968 669 ;
+C -1 ; WX 444 ; N edotaccent ; B 5 -13 398 655 ;
+C 204 ; WX 389 ; N Igrave ; B -32 0 406 904 ;
+C -1 ; WX 389 ; N Imacron ; B -32 0 461 830 ;
+C -1 ; WX 611 ; N Lcaron ; B -22 0 671 718 ;
+C 189 ; WX 750 ; N onehalf ; B -9 -14 723 683 ;
+C -1 ; WX 549 ; N lessequal ; B 29 0 526 704 ;
+C 244 ; WX 500 ; N ocircumflex ; B -3 -13 451 690 ;
+C 241 ; WX 556 ; N ntilde ; B -6 -9 504 655 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 67 -18 744 904 ;
+C 201 ; WX 667 ; N Eacute ; B -27 0 653 904 ;
+C -1 ; WX 444 ; N emacron ; B 5 -13 439 623 ;
+C -1 ; WX 500 ; N gbreve ; B -52 -203 478 678 ;
+C 188 ; WX 750 ; N onequarter ; B 7 -14 721 683 ;
+C 138 ; WX 556 ; N Scaron ; B 2 -18 553 897 ;
+C -1 ; WX 556 ; N Scommaaccent ; B 2 -218 526 685 ;
+C -1 ; WX 722 ; N Ohungarumlaut ; B 27 -18 723 904 ;
+C 176 ; WX 400 ; N degree ; B 83 397 369 683 ;
+C 242 ; WX 500 ; N ograve ; B -3 -13 441 697 ;
+C -1 ; WX 667 ; N Ccaron ; B 32 -18 677 897 ;
+C 249 ; WX 556 ; N ugrave ; B 15 -9 492 697 ;
+C -1 ; WX 549 ; N radical ; B 10 -46 512 850 ;
+C -1 ; WX 722 ; N Dcaron ; B -46 0 685 897 ;
+C -1 ; WX 389 ; N rcommaaccent ; B -67 -218 389 462 ;
+C 209 ; WX 722 ; N Ntilde ; B -27 -15 748 862 ;
+C 245 ; WX 500 ; N otilde ; B -3 -13 491 655 ;
+C -1 ; WX 667 ; N Rcommaaccent ; B -29 -218 623 669 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B -22 -218 590 669 ;
+C 195 ; WX 667 ; N Atilde ; B -67 0 593 862 ;
+C -1 ; WX 667 ; N Aogonek ; B -67 -183 604 683 ;
+C 197 ; WX 667 ; N Aring ; B -67 0 593 921 ;
+C 213 ; WX 722 ; N Otilde ; B 27 -18 691 862 ;
+C -1 ; WX 389 ; N zdotaccent ; B -43 -78 368 655 ;
+C -1 ; WX 667 ; N Ecaron ; B -27 0 653 897 ;
+C -1 ; WX 389 ; N Iogonek ; B -32 -183 406 669 ;
+C -1 ; WX 500 ; N kcommaaccent ; B -23 -218 483 699 ;
+C -1 ; WX 606 ; N minus ; B 51 209 555 297 ;
+C 206 ; WX 389 ; N Icircumflex ; B -32 0 450 897 ;
+C -1 ; WX 556 ; N ncaron ; B -6 -9 523 690 ;
+C -1 ; WX 278 ; N tcommaaccent ; B -62 -218 281 594 ;
+C 172 ; WX 606 ; N logicalnot ; B 51 108 555 399 ;
+C 246 ; WX 500 ; N odieresis ; B -3 -13 471 655 ;
+C 252 ; WX 556 ; N udieresis ; B 15 -9 499 655 ;
+C -1 ; WX 549 ; N notequal ; B 15 -49 540 570 ;
+C -1 ; WX 500 ; N gcommaaccent ; B -52 -203 478 767 ;
+C 240 ; WX 500 ; N eth ; B -3 -13 454 699 ;
+C 158 ; WX 389 ; N zcaron ; B -43 -78 424 690 ;
+C -1 ; WX 556 ; N ncommaaccent ; B -6 -218 493 462 ;
+C 185 ; WX 300 ; N onesuperior ; B 30 274 301 683 ;
+C -1 ; WX 278 ; N imacron ; B 2 -9 294 623 ;
+C 128 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2038
+KPX A C -65
+KPX A Cacute -65
+KPX A Ccaron -65
+KPX A Ccedilla -65
+KPX A G -60
+KPX A Gbreve -60
+KPX A Gcommaaccent -60
+KPX A O -50
+KPX A Oacute -50
+KPX A Ocircumflex -50
+KPX A Odieresis -50
+KPX A Ograve -50
+KPX A Ohungarumlaut -50
+KPX A Omacron -50
+KPX A Oslash -50
+KPX A Otilde -50
+KPX A Q -55
+KPX A T -55
+KPX A Tcaron -55
+KPX A Tcommaaccent -55
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -95
+KPX A W -100
+KPX A Y -70
+KPX A Yacute -70
+KPX A Ydieresis -70
+KPX A quoteright -74
+KPX A u -30
+KPX A uacute -30
+KPX A ucircumflex -30
+KPX A udieresis -30
+KPX A ugrave -30
+KPX A uhungarumlaut -30
+KPX A umacron -30
+KPX A uogonek -30
+KPX A uring -30
+KPX A v -74
+KPX A w -74
+KPX A y -74
+KPX A yacute -74
+KPX A ydieresis -74
+KPX Aacute C -65
+KPX Aacute Cacute -65
+KPX Aacute Ccaron -65
+KPX Aacute Ccedilla -65
+KPX Aacute G -60
+KPX Aacute Gbreve -60
+KPX Aacute Gcommaaccent -60
+KPX Aacute O -50
+KPX Aacute Oacute -50
+KPX Aacute Ocircumflex -50
+KPX Aacute Odieresis -50
+KPX Aacute Ograve -50
+KPX Aacute Ohungarumlaut -50
+KPX Aacute Omacron -50
+KPX Aacute Oslash -50
+KPX Aacute Otilde -50
+KPX Aacute Q -55
+KPX Aacute T -55
+KPX Aacute Tcaron -55
+KPX Aacute Tcommaaccent -55
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -95
+KPX Aacute W -100
+KPX Aacute Y -70
+KPX Aacute Yacute -70
+KPX Aacute Ydieresis -70
+KPX Aacute quoteright -74
+KPX Aacute u -30
+KPX Aacute uacute -30
+KPX Aacute ucircumflex -30
+KPX Aacute udieresis -30
+KPX Aacute ugrave -30
+KPX Aacute uhungarumlaut -30
+KPX Aacute umacron -30
+KPX Aacute uogonek -30
+KPX Aacute uring -30
+KPX Aacute v -74
+KPX Aacute w -74
+KPX Aacute y -74
+KPX Aacute yacute -74
+KPX Aacute ydieresis -74
+KPX Abreve C -65
+KPX Abreve Cacute -65
+KPX Abreve Ccaron -65
+KPX Abreve Ccedilla -65
+KPX Abreve G -60
+KPX Abreve Gbreve -60
+KPX Abreve Gcommaaccent -60
+KPX Abreve O -50
+KPX Abreve Oacute -50
+KPX Abreve Ocircumflex -50
+KPX Abreve Odieresis -50
+KPX Abreve Ograve -50
+KPX Abreve Ohungarumlaut -50
+KPX Abreve Omacron -50
+KPX Abreve Oslash -50
+KPX Abreve Otilde -50
+KPX Abreve Q -55
+KPX Abreve T -55
+KPX Abreve Tcaron -55
+KPX Abreve Tcommaaccent -55
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -95
+KPX Abreve W -100
+KPX Abreve Y -70
+KPX Abreve Yacute -70
+KPX Abreve Ydieresis -70
+KPX Abreve quoteright -74
+KPX Abreve u -30
+KPX Abreve uacute -30
+KPX Abreve ucircumflex -30
+KPX Abreve udieresis -30
+KPX Abreve ugrave -30
+KPX Abreve uhungarumlaut -30
+KPX Abreve umacron -30
+KPX Abreve uogonek -30
+KPX Abreve uring -30
+KPX Abreve v -74
+KPX Abreve w -74
+KPX Abreve y -74
+KPX Abreve yacute -74
+KPX Abreve ydieresis -74
+KPX Acircumflex C -65
+KPX Acircumflex Cacute -65
+KPX Acircumflex Ccaron -65
+KPX Acircumflex Ccedilla -65
+KPX Acircumflex G -60
+KPX Acircumflex Gbreve -60
+KPX Acircumflex Gcommaaccent -60
+KPX Acircumflex O -50
+KPX Acircumflex Oacute -50
+KPX Acircumflex Ocircumflex -50
+KPX Acircumflex Odieresis -50
+KPX Acircumflex Ograve -50
+KPX Acircumflex Ohungarumlaut -50
+KPX Acircumflex Omacron -50
+KPX Acircumflex Oslash -50
+KPX Acircumflex Otilde -50
+KPX Acircumflex Q -55
+KPX Acircumflex T -55
+KPX Acircumflex Tcaron -55
+KPX Acircumflex Tcommaaccent -55
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -95
+KPX Acircumflex W -100
+KPX Acircumflex Y -70
+KPX Acircumflex Yacute -70
+KPX Acircumflex Ydieresis -70
+KPX Acircumflex quoteright -74
+KPX Acircumflex u -30
+KPX Acircumflex uacute -30
+KPX Acircumflex ucircumflex -30
+KPX Acircumflex udieresis -30
+KPX Acircumflex ugrave -30
+KPX Acircumflex uhungarumlaut -30
+KPX Acircumflex umacron -30
+KPX Acircumflex uogonek -30
+KPX Acircumflex uring -30
+KPX Acircumflex v -74
+KPX Acircumflex w -74
+KPX Acircumflex y -74
+KPX Acircumflex yacute -74
+KPX Acircumflex ydieresis -74
+KPX Adieresis C -65
+KPX Adieresis Cacute -65
+KPX Adieresis Ccaron -65
+KPX Adieresis Ccedilla -65
+KPX Adieresis G -60
+KPX Adieresis Gbreve -60
+KPX Adieresis Gcommaaccent -60
+KPX Adieresis O -50
+KPX Adieresis Oacute -50
+KPX Adieresis Ocircumflex -50
+KPX Adieresis Odieresis -50
+KPX Adieresis Ograve -50
+KPX Adieresis Ohungarumlaut -50
+KPX Adieresis Omacron -50
+KPX Adieresis Oslash -50
+KPX Adieresis Otilde -50
+KPX Adieresis Q -55
+KPX Adieresis T -55
+KPX Adieresis Tcaron -55
+KPX Adieresis Tcommaaccent -55
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -95
+KPX Adieresis W -100
+KPX Adieresis Y -70
+KPX Adieresis Yacute -70
+KPX Adieresis Ydieresis -70
+KPX Adieresis quoteright -74
+KPX Adieresis u -30
+KPX Adieresis uacute -30
+KPX Adieresis ucircumflex -30
+KPX Adieresis udieresis -30
+KPX Adieresis ugrave -30
+KPX Adieresis uhungarumlaut -30
+KPX Adieresis umacron -30
+KPX Adieresis uogonek -30
+KPX Adieresis uring -30
+KPX Adieresis v -74
+KPX Adieresis w -74
+KPX Adieresis y -74
+KPX Adieresis yacute -74
+KPX Adieresis ydieresis -74
+KPX Agrave C -65
+KPX Agrave Cacute -65
+KPX Agrave Ccaron -65
+KPX Agrave Ccedilla -65
+KPX Agrave G -60
+KPX Agrave Gbreve -60
+KPX Agrave Gcommaaccent -60
+KPX Agrave O -50
+KPX Agrave Oacute -50
+KPX Agrave Ocircumflex -50
+KPX Agrave Odieresis -50
+KPX Agrave Ograve -50
+KPX Agrave Ohungarumlaut -50
+KPX Agrave Omacron -50
+KPX Agrave Oslash -50
+KPX Agrave Otilde -50
+KPX Agrave Q -55
+KPX Agrave T -55
+KPX Agrave Tcaron -55
+KPX Agrave Tcommaaccent -55
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -95
+KPX Agrave W -100
+KPX Agrave Y -70
+KPX Agrave Yacute -70
+KPX Agrave Ydieresis -70
+KPX Agrave quoteright -74
+KPX Agrave u -30
+KPX Agrave uacute -30
+KPX Agrave ucircumflex -30
+KPX Agrave udieresis -30
+KPX Agrave ugrave -30
+KPX Agrave uhungarumlaut -30
+KPX Agrave umacron -30
+KPX Agrave uogonek -30
+KPX Agrave uring -30
+KPX Agrave v -74
+KPX Agrave w -74
+KPX Agrave y -74
+KPX Agrave yacute -74
+KPX Agrave ydieresis -74
+KPX Amacron C -65
+KPX Amacron Cacute -65
+KPX Amacron Ccaron -65
+KPX Amacron Ccedilla -65
+KPX Amacron G -60
+KPX Amacron Gbreve -60
+KPX Amacron Gcommaaccent -60
+KPX Amacron O -50
+KPX Amacron Oacute -50
+KPX Amacron Ocircumflex -50
+KPX Amacron Odieresis -50
+KPX Amacron Ograve -50
+KPX Amacron Ohungarumlaut -50
+KPX Amacron Omacron -50
+KPX Amacron Oslash -50
+KPX Amacron Otilde -50
+KPX Amacron Q -55
+KPX Amacron T -55
+KPX Amacron Tcaron -55
+KPX Amacron Tcommaaccent -55
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -95
+KPX Amacron W -100
+KPX Amacron Y -70
+KPX Amacron Yacute -70
+KPX Amacron Ydieresis -70
+KPX Amacron quoteright -74
+KPX Amacron u -30
+KPX Amacron uacute -30
+KPX Amacron ucircumflex -30
+KPX Amacron udieresis -30
+KPX Amacron ugrave -30
+KPX Amacron uhungarumlaut -30
+KPX Amacron umacron -30
+KPX Amacron uogonek -30
+KPX Amacron uring -30
+KPX Amacron v -74
+KPX Amacron w -74
+KPX Amacron y -74
+KPX Amacron yacute -74
+KPX Amacron ydieresis -74
+KPX Aogonek C -65
+KPX Aogonek Cacute -65
+KPX Aogonek Ccaron -65
+KPX Aogonek Ccedilla -65
+KPX Aogonek G -60
+KPX Aogonek Gbreve -60
+KPX Aogonek Gcommaaccent -60
+KPX Aogonek O -50
+KPX Aogonek Oacute -50
+KPX Aogonek Ocircumflex -50
+KPX Aogonek Odieresis -50
+KPX Aogonek Ograve -50
+KPX Aogonek Ohungarumlaut -50
+KPX Aogonek Omacron -50
+KPX Aogonek Oslash -50
+KPX Aogonek Otilde -50
+KPX Aogonek Q -55
+KPX Aogonek T -55
+KPX Aogonek Tcaron -55
+KPX Aogonek Tcommaaccent -55
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -95
+KPX Aogonek W -100
+KPX Aogonek Y -70
+KPX Aogonek Yacute -70
+KPX Aogonek Ydieresis -70
+KPX Aogonek quoteright -74
+KPX Aogonek u -30
+KPX Aogonek uacute -30
+KPX Aogonek ucircumflex -30
+KPX Aogonek udieresis -30
+KPX Aogonek ugrave -30
+KPX Aogonek uhungarumlaut -30
+KPX Aogonek umacron -30
+KPX Aogonek uogonek -30
+KPX Aogonek uring -30
+KPX Aogonek v -74
+KPX Aogonek w -74
+KPX Aogonek y -34
+KPX Aogonek yacute -34
+KPX Aogonek ydieresis -34
+KPX Aring C -65
+KPX Aring Cacute -65
+KPX Aring Ccaron -65
+KPX Aring Ccedilla -65
+KPX Aring G -60
+KPX Aring Gbreve -60
+KPX Aring Gcommaaccent -60
+KPX Aring O -50
+KPX Aring Oacute -50
+KPX Aring Ocircumflex -50
+KPX Aring Odieresis -50
+KPX Aring Ograve -50
+KPX Aring Ohungarumlaut -50
+KPX Aring Omacron -50
+KPX Aring Oslash -50
+KPX Aring Otilde -50
+KPX Aring Q -55
+KPX Aring T -55
+KPX Aring Tcaron -55
+KPX Aring Tcommaaccent -55
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -95
+KPX Aring W -100
+KPX Aring Y -70
+KPX Aring Yacute -70
+KPX Aring Ydieresis -70
+KPX Aring quoteright -74
+KPX Aring u -30
+KPX Aring uacute -30
+KPX Aring ucircumflex -30
+KPX Aring udieresis -30
+KPX Aring ugrave -30
+KPX Aring uhungarumlaut -30
+KPX Aring umacron -30
+KPX Aring uogonek -30
+KPX Aring uring -30
+KPX Aring v -74
+KPX Aring w -74
+KPX Aring y -74
+KPX Aring yacute -74
+KPX Aring ydieresis -74
+KPX Atilde C -65
+KPX Atilde Cacute -65
+KPX Atilde Ccaron -65
+KPX Atilde Ccedilla -65
+KPX Atilde G -60
+KPX Atilde Gbreve -60
+KPX Atilde Gcommaaccent -60
+KPX Atilde O -50
+KPX Atilde Oacute -50
+KPX Atilde Ocircumflex -50
+KPX Atilde Odieresis -50
+KPX Atilde Ograve -50
+KPX Atilde Ohungarumlaut -50
+KPX Atilde Omacron -50
+KPX Atilde Oslash -50
+KPX Atilde Otilde -50
+KPX Atilde Q -55
+KPX Atilde T -55
+KPX Atilde Tcaron -55
+KPX Atilde Tcommaaccent -55
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -95
+KPX Atilde W -100
+KPX Atilde Y -70
+KPX Atilde Yacute -70
+KPX Atilde Ydieresis -70
+KPX Atilde quoteright -74
+KPX Atilde u -30
+KPX Atilde uacute -30
+KPX Atilde ucircumflex -30
+KPX Atilde udieresis -30
+KPX Atilde ugrave -30
+KPX Atilde uhungarumlaut -30
+KPX Atilde umacron -30
+KPX Atilde uogonek -30
+KPX Atilde uring -30
+KPX Atilde v -74
+KPX Atilde w -74
+KPX Atilde y -74
+KPX Atilde yacute -74
+KPX Atilde ydieresis -74
+KPX B A -25
+KPX B Aacute -25
+KPX B Abreve -25
+KPX B Acircumflex -25
+KPX B Adieresis -25
+KPX B Agrave -25
+KPX B Amacron -25
+KPX B Aogonek -25
+KPX B Aring -25
+KPX B Atilde -25
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -25
+KPX D Aacute -25
+KPX D Abreve -25
+KPX D Acircumflex -25
+KPX D Adieresis -25
+KPX D Agrave -25
+KPX D Amacron -25
+KPX D Aogonek -25
+KPX D Aring -25
+KPX D Atilde -25
+KPX D V -50
+KPX D W -40
+KPX D Y -50
+KPX D Yacute -50
+KPX D Ydieresis -50
+KPX Dcaron A -25
+KPX Dcaron Aacute -25
+KPX Dcaron Abreve -25
+KPX Dcaron Acircumflex -25
+KPX Dcaron Adieresis -25
+KPX Dcaron Agrave -25
+KPX Dcaron Amacron -25
+KPX Dcaron Aogonek -25
+KPX Dcaron Aring -25
+KPX Dcaron Atilde -25
+KPX Dcaron V -50
+KPX Dcaron W -40
+KPX Dcaron Y -50
+KPX Dcaron Yacute -50
+KPX Dcaron Ydieresis -50
+KPX Dcroat A -25
+KPX Dcroat Aacute -25
+KPX Dcroat Abreve -25
+KPX Dcroat Acircumflex -25
+KPX Dcroat Adieresis -25
+KPX Dcroat Agrave -25
+KPX Dcroat Amacron -25
+KPX Dcroat Aogonek -25
+KPX Dcroat Aring -25
+KPX Dcroat Atilde -25
+KPX Dcroat V -50
+KPX Dcroat W -40
+KPX Dcroat Y -50
+KPX Dcroat Yacute -50
+KPX Dcroat Ydieresis -50
+KPX F A -100
+KPX F Aacute -100
+KPX F Abreve -100
+KPX F Acircumflex -100
+KPX F Adieresis -100
+KPX F Agrave -100
+KPX F Amacron -100
+KPX F Aogonek -100
+KPX F Aring -100
+KPX F Atilde -100
+KPX F a -95
+KPX F aacute -95
+KPX F abreve -95
+KPX F acircumflex -95
+KPX F adieresis -95
+KPX F agrave -95
+KPX F amacron -95
+KPX F aogonek -95
+KPX F aring -95
+KPX F atilde -95
+KPX F comma -129
+KPX F e -100
+KPX F eacute -100
+KPX F ecaron -100
+KPX F ecircumflex -100
+KPX F edieresis -100
+KPX F edotaccent -100
+KPX F egrave -100
+KPX F emacron -100
+KPX F eogonek -100
+KPX F i -40
+KPX F iacute -40
+KPX F icircumflex -40
+KPX F idieresis -40
+KPX F igrave -40
+KPX F imacron -40
+KPX F iogonek -40
+KPX F o -70
+KPX F oacute -70
+KPX F ocircumflex -70
+KPX F odieresis -70
+KPX F ograve -70
+KPX F ohungarumlaut -70
+KPX F omacron -70
+KPX F oslash -70
+KPX F otilde -70
+KPX F period -129
+KPX F r -50
+KPX F racute -50
+KPX F rcaron -50
+KPX F rcommaaccent -50
+KPX J A -25
+KPX J Aacute -25
+KPX J Abreve -25
+KPX J Acircumflex -25
+KPX J Adieresis -25
+KPX J Agrave -25
+KPX J Amacron -25
+KPX J Aogonek -25
+KPX J Aring -25
+KPX J Atilde -25
+KPX J a -40
+KPX J aacute -40
+KPX J abreve -40
+KPX J acircumflex -40
+KPX J adieresis -40
+KPX J agrave -40
+KPX J amacron -40
+KPX J aogonek -40
+KPX J aring -40
+KPX J atilde -40
+KPX J comma -10
+KPX J e -40
+KPX J eacute -40
+KPX J ecaron -40
+KPX J ecircumflex -40
+KPX J edieresis -40
+KPX J edotaccent -40
+KPX J egrave -40
+KPX J emacron -40
+KPX J eogonek -40
+KPX J o -40
+KPX J oacute -40
+KPX J ocircumflex -40
+KPX J odieresis -40
+KPX J ograve -40
+KPX J ohungarumlaut -40
+KPX J omacron -40
+KPX J oslash -40
+KPX J otilde -40
+KPX J period -10
+KPX J u -40
+KPX J uacute -40
+KPX J ucircumflex -40
+KPX J udieresis -40
+KPX J ugrave -40
+KPX J uhungarumlaut -40
+KPX J umacron -40
+KPX J uogonek -40
+KPX J uring -40
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -25
+KPX K eacute -25
+KPX K ecaron -25
+KPX K ecircumflex -25
+KPX K edieresis -25
+KPX K edotaccent -25
+KPX K egrave -25
+KPX K emacron -25
+KPX K eogonek -25
+KPX K o -25
+KPX K oacute -25
+KPX K ocircumflex -25
+KPX K odieresis -25
+KPX K ograve -25
+KPX K ohungarumlaut -25
+KPX K omacron -25
+KPX K oslash -25
+KPX K otilde -25
+KPX K u -20
+KPX K uacute -20
+KPX K ucircumflex -20
+KPX K udieresis -20
+KPX K ugrave -20
+KPX K uhungarumlaut -20
+KPX K umacron -20
+KPX K uogonek -20
+KPX K uring -20
+KPX K y -20
+KPX K yacute -20
+KPX K ydieresis -20
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -25
+KPX Kcommaaccent eacute -25
+KPX Kcommaaccent ecaron -25
+KPX Kcommaaccent ecircumflex -25
+KPX Kcommaaccent edieresis -25
+KPX Kcommaaccent edotaccent -25
+KPX Kcommaaccent egrave -25
+KPX Kcommaaccent emacron -25
+KPX Kcommaaccent eogonek -25
+KPX Kcommaaccent o -25
+KPX Kcommaaccent oacute -25
+KPX Kcommaaccent ocircumflex -25
+KPX Kcommaaccent odieresis -25
+KPX Kcommaaccent ograve -25
+KPX Kcommaaccent ohungarumlaut -25
+KPX Kcommaaccent omacron -25
+KPX Kcommaaccent oslash -25
+KPX Kcommaaccent otilde -25
+KPX Kcommaaccent u -20
+KPX Kcommaaccent uacute -20
+KPX Kcommaaccent ucircumflex -20
+KPX Kcommaaccent udieresis -20
+KPX Kcommaaccent ugrave -20
+KPX Kcommaaccent uhungarumlaut -20
+KPX Kcommaaccent umacron -20
+KPX Kcommaaccent uogonek -20
+KPX Kcommaaccent uring -20
+KPX Kcommaaccent y -20
+KPX Kcommaaccent yacute -20
+KPX Kcommaaccent ydieresis -20
+KPX L T -18
+KPX L Tcaron -18
+KPX L Tcommaaccent -18
+KPX L V -37
+KPX L W -37
+KPX L Y -37
+KPX L Yacute -37
+KPX L Ydieresis -37
+KPX L quoteright -55
+KPX L y -37
+KPX L yacute -37
+KPX L ydieresis -37
+KPX Lacute T -18
+KPX Lacute Tcaron -18
+KPX Lacute Tcommaaccent -18
+KPX Lacute V -37
+KPX Lacute W -37
+KPX Lacute Y -37
+KPX Lacute Yacute -37
+KPX Lacute Ydieresis -37
+KPX Lacute quoteright -55
+KPX Lacute y -37
+KPX Lacute yacute -37
+KPX Lacute ydieresis -37
+KPX Lcommaaccent T -18
+KPX Lcommaaccent Tcaron -18
+KPX Lcommaaccent Tcommaaccent -18
+KPX Lcommaaccent V -37
+KPX Lcommaaccent W -37
+KPX Lcommaaccent Y -37
+KPX Lcommaaccent Yacute -37
+KPX Lcommaaccent Ydieresis -37
+KPX Lcommaaccent quoteright -55
+KPX Lcommaaccent y -37
+KPX Lcommaaccent yacute -37
+KPX Lcommaaccent ydieresis -37
+KPX Lslash T -18
+KPX Lslash Tcaron -18
+KPX Lslash Tcommaaccent -18
+KPX Lslash V -37
+KPX Lslash W -37
+KPX Lslash Y -37
+KPX Lslash Yacute -37
+KPX Lslash Ydieresis -37
+KPX Lslash quoteright -55
+KPX Lslash y -37
+KPX Lslash yacute -37
+KPX Lslash ydieresis -37
+KPX N A -30
+KPX N Aacute -30
+KPX N Abreve -30
+KPX N Acircumflex -30
+KPX N Adieresis -30
+KPX N Agrave -30
+KPX N Amacron -30
+KPX N Aogonek -30
+KPX N Aring -30
+KPX N Atilde -30
+KPX Nacute A -30
+KPX Nacute Aacute -30
+KPX Nacute Abreve -30
+KPX Nacute Acircumflex -30
+KPX Nacute Adieresis -30
+KPX Nacute Agrave -30
+KPX Nacute Amacron -30
+KPX Nacute Aogonek -30
+KPX Nacute Aring -30
+KPX Nacute Atilde -30
+KPX Ncaron A -30
+KPX Ncaron Aacute -30
+KPX Ncaron Abreve -30
+KPX Ncaron Acircumflex -30
+KPX Ncaron Adieresis -30
+KPX Ncaron Agrave -30
+KPX Ncaron Amacron -30
+KPX Ncaron Aogonek -30
+KPX Ncaron Aring -30
+KPX Ncaron Atilde -30
+KPX Ncommaaccent A -30
+KPX Ncommaaccent Aacute -30
+KPX Ncommaaccent Abreve -30
+KPX Ncommaaccent Acircumflex -30
+KPX Ncommaaccent Adieresis -30
+KPX Ncommaaccent Agrave -30
+KPX Ncommaaccent Amacron -30
+KPX Ncommaaccent Aogonek -30
+KPX Ncommaaccent Aring -30
+KPX Ncommaaccent Atilde -30
+KPX Ntilde A -30
+KPX Ntilde Aacute -30
+KPX Ntilde Abreve -30
+KPX Ntilde Acircumflex -30
+KPX Ntilde Adieresis -30
+KPX Ntilde Agrave -30
+KPX Ntilde Amacron -30
+KPX Ntilde Aogonek -30
+KPX Ntilde Aring -30
+KPX Ntilde Atilde -30
+KPX O A -40
+KPX O Aacute -40
+KPX O Abreve -40
+KPX O Acircumflex -40
+KPX O Adieresis -40
+KPX O Agrave -40
+KPX O Amacron -40
+KPX O Aogonek -40
+KPX O Aring -40
+KPX O Atilde -40
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -40
+KPX Oacute Aacute -40
+KPX Oacute Abreve -40
+KPX Oacute Acircumflex -40
+KPX Oacute Adieresis -40
+KPX Oacute Agrave -40
+KPX Oacute Amacron -40
+KPX Oacute Aogonek -40
+KPX Oacute Aring -40
+KPX Oacute Atilde -40
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -40
+KPX Ocircumflex Aacute -40
+KPX Ocircumflex Abreve -40
+KPX Ocircumflex Acircumflex -40
+KPX Ocircumflex Adieresis -40
+KPX Ocircumflex Agrave -40
+KPX Ocircumflex Amacron -40
+KPX Ocircumflex Aogonek -40
+KPX Ocircumflex Aring -40
+KPX Ocircumflex Atilde -40
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -40
+KPX Odieresis Aacute -40
+KPX Odieresis Abreve -40
+KPX Odieresis Acircumflex -40
+KPX Odieresis Adieresis -40
+KPX Odieresis Agrave -40
+KPX Odieresis Amacron -40
+KPX Odieresis Aogonek -40
+KPX Odieresis Aring -40
+KPX Odieresis Atilde -40
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -40
+KPX Ograve Aacute -40
+KPX Ograve Abreve -40
+KPX Ograve Acircumflex -40
+KPX Ograve Adieresis -40
+KPX Ograve Agrave -40
+KPX Ograve Amacron -40
+KPX Ograve Aogonek -40
+KPX Ograve Aring -40
+KPX Ograve Atilde -40
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -40
+KPX Ohungarumlaut Aacute -40
+KPX Ohungarumlaut Abreve -40
+KPX Ohungarumlaut Acircumflex -40
+KPX Ohungarumlaut Adieresis -40
+KPX Ohungarumlaut Agrave -40
+KPX Ohungarumlaut Amacron -40
+KPX Ohungarumlaut Aogonek -40
+KPX Ohungarumlaut Aring -40
+KPX Ohungarumlaut Atilde -40
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -40
+KPX Omacron Aacute -40
+KPX Omacron Abreve -40
+KPX Omacron Acircumflex -40
+KPX Omacron Adieresis -40
+KPX Omacron Agrave -40
+KPX Omacron Amacron -40
+KPX Omacron Aogonek -40
+KPX Omacron Aring -40
+KPX Omacron Atilde -40
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -40
+KPX Oslash Aacute -40
+KPX Oslash Abreve -40
+KPX Oslash Acircumflex -40
+KPX Oslash Adieresis -40
+KPX Oslash Agrave -40
+KPX Oslash Amacron -40
+KPX Oslash Aogonek -40
+KPX Oslash Aring -40
+KPX Oslash Atilde -40
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -40
+KPX Otilde Aacute -40
+KPX Otilde Abreve -40
+KPX Otilde Acircumflex -40
+KPX Otilde Adieresis -40
+KPX Otilde Agrave -40
+KPX Otilde Amacron -40
+KPX Otilde Aogonek -40
+KPX Otilde Aring -40
+KPX Otilde Atilde -40
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -85
+KPX P Aacute -85
+KPX P Abreve -85
+KPX P Acircumflex -85
+KPX P Adieresis -85
+KPX P Agrave -85
+KPX P Amacron -85
+KPX P Aogonek -85
+KPX P Aring -85
+KPX P Atilde -85
+KPX P a -40
+KPX P aacute -40
+KPX P abreve -40
+KPX P acircumflex -40
+KPX P adieresis -40
+KPX P agrave -40
+KPX P amacron -40
+KPX P aogonek -40
+KPX P aring -40
+KPX P atilde -40
+KPX P comma -129
+KPX P e -50
+KPX P eacute -50
+KPX P ecaron -50
+KPX P ecircumflex -50
+KPX P edieresis -50
+KPX P edotaccent -50
+KPX P egrave -50
+KPX P emacron -50
+KPX P eogonek -50
+KPX P o -55
+KPX P oacute -55
+KPX P ocircumflex -55
+KPX P odieresis -55
+KPX P ograve -55
+KPX P ohungarumlaut -55
+KPX P omacron -55
+KPX P oslash -55
+KPX P otilde -55
+KPX P period -129
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -40
+KPX R Oacute -40
+KPX R Ocircumflex -40
+KPX R Odieresis -40
+KPX R Ograve -40
+KPX R Ohungarumlaut -40
+KPX R Omacron -40
+KPX R Oslash -40
+KPX R Otilde -40
+KPX R T -30
+KPX R Tcaron -30
+KPX R Tcommaaccent -30
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -18
+KPX R W -18
+KPX R Y -18
+KPX R Yacute -18
+KPX R Ydieresis -18
+KPX Racute O -40
+KPX Racute Oacute -40
+KPX Racute Ocircumflex -40
+KPX Racute Odieresis -40
+KPX Racute Ograve -40
+KPX Racute Ohungarumlaut -40
+KPX Racute Omacron -40
+KPX Racute Oslash -40
+KPX Racute Otilde -40
+KPX Racute T -30
+KPX Racute Tcaron -30
+KPX Racute Tcommaaccent -30
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -18
+KPX Racute W -18
+KPX Racute Y -18
+KPX Racute Yacute -18
+KPX Racute Ydieresis -18
+KPX Rcaron O -40
+KPX Rcaron Oacute -40
+KPX Rcaron Ocircumflex -40
+KPX Rcaron Odieresis -40
+KPX Rcaron Ograve -40
+KPX Rcaron Ohungarumlaut -40
+KPX Rcaron Omacron -40
+KPX Rcaron Oslash -40
+KPX Rcaron Otilde -40
+KPX Rcaron T -30
+KPX Rcaron Tcaron -30
+KPX Rcaron Tcommaaccent -30
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -18
+KPX Rcaron W -18
+KPX Rcaron Y -18
+KPX Rcaron Yacute -18
+KPX Rcaron Ydieresis -18
+KPX Rcommaaccent O -40
+KPX Rcommaaccent Oacute -40
+KPX Rcommaaccent Ocircumflex -40
+KPX Rcommaaccent Odieresis -40
+KPX Rcommaaccent Ograve -40
+KPX Rcommaaccent Ohungarumlaut -40
+KPX Rcommaaccent Omacron -40
+KPX Rcommaaccent Oslash -40
+KPX Rcommaaccent Otilde -40
+KPX Rcommaaccent T -30
+KPX Rcommaaccent Tcaron -30
+KPX Rcommaaccent Tcommaaccent -30
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -18
+KPX Rcommaaccent W -18
+KPX Rcommaaccent Y -18
+KPX Rcommaaccent Yacute -18
+KPX Rcommaaccent Ydieresis -18
+KPX T A -55
+KPX T Aacute -55
+KPX T Abreve -55
+KPX T Acircumflex -55
+KPX T Adieresis -55
+KPX T Agrave -55
+KPX T Amacron -55
+KPX T Aogonek -55
+KPX T Aring -55
+KPX T Atilde -55
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -92
+KPX T aacute -92
+KPX T abreve -92
+KPX T acircumflex -92
+KPX T adieresis -92
+KPX T agrave -92
+KPX T amacron -92
+KPX T aogonek -92
+KPX T aring -92
+KPX T atilde -92
+KPX T colon -74
+KPX T comma -92
+KPX T e -92
+KPX T eacute -92
+KPX T ecaron -92
+KPX T ecircumflex -92
+KPX T edieresis -52
+KPX T edotaccent -92
+KPX T egrave -52
+KPX T emacron -52
+KPX T eogonek -92
+KPX T hyphen -92
+KPX T i -37
+KPX T iacute -37
+KPX T iogonek -37
+KPX T o -95
+KPX T oacute -95
+KPX T ocircumflex -95
+KPX T odieresis -95
+KPX T ograve -95
+KPX T ohungarumlaut -95
+KPX T omacron -95
+KPX T oslash -95
+KPX T otilde -95
+KPX T period -92
+KPX T r -37
+KPX T racute -37
+KPX T rcaron -37
+KPX T rcommaaccent -37
+KPX T semicolon -74
+KPX T u -37
+KPX T uacute -37
+KPX T ucircumflex -37
+KPX T udieresis -37
+KPX T ugrave -37
+KPX T uhungarumlaut -37
+KPX T umacron -37
+KPX T uogonek -37
+KPX T uring -37
+KPX T w -37
+KPX T y -37
+KPX T yacute -37
+KPX T ydieresis -37
+KPX Tcaron A -55
+KPX Tcaron Aacute -55
+KPX Tcaron Abreve -55
+KPX Tcaron Acircumflex -55
+KPX Tcaron Adieresis -55
+KPX Tcaron Agrave -55
+KPX Tcaron Amacron -55
+KPX Tcaron Aogonek -55
+KPX Tcaron Aring -55
+KPX Tcaron Atilde -55
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -92
+KPX Tcaron aacute -92
+KPX Tcaron abreve -92
+KPX Tcaron acircumflex -92
+KPX Tcaron adieresis -92
+KPX Tcaron agrave -92
+KPX Tcaron amacron -92
+KPX Tcaron aogonek -92
+KPX Tcaron aring -92
+KPX Tcaron atilde -92
+KPX Tcaron colon -74
+KPX Tcaron comma -92
+KPX Tcaron e -92
+KPX Tcaron eacute -92
+KPX Tcaron ecaron -92
+KPX Tcaron ecircumflex -92
+KPX Tcaron edieresis -52
+KPX Tcaron edotaccent -92
+KPX Tcaron egrave -52
+KPX Tcaron emacron -52
+KPX Tcaron eogonek -92
+KPX Tcaron hyphen -92
+KPX Tcaron i -37
+KPX Tcaron iacute -37
+KPX Tcaron iogonek -37
+KPX Tcaron o -95
+KPX Tcaron oacute -95
+KPX Tcaron ocircumflex -95
+KPX Tcaron odieresis -95
+KPX Tcaron ograve -95
+KPX Tcaron ohungarumlaut -95
+KPX Tcaron omacron -95
+KPX Tcaron oslash -95
+KPX Tcaron otilde -95
+KPX Tcaron period -92
+KPX Tcaron r -37
+KPX Tcaron racute -37
+KPX Tcaron rcaron -37
+KPX Tcaron rcommaaccent -37
+KPX Tcaron semicolon -74
+KPX Tcaron u -37
+KPX Tcaron uacute -37
+KPX Tcaron ucircumflex -37
+KPX Tcaron udieresis -37
+KPX Tcaron ugrave -37
+KPX Tcaron uhungarumlaut -37
+KPX Tcaron umacron -37
+KPX Tcaron uogonek -37
+KPX Tcaron uring -37
+KPX Tcaron w -37
+KPX Tcaron y -37
+KPX Tcaron yacute -37
+KPX Tcaron ydieresis -37
+KPX Tcommaaccent A -55
+KPX Tcommaaccent Aacute -55
+KPX Tcommaaccent Abreve -55
+KPX Tcommaaccent Acircumflex -55
+KPX Tcommaaccent Adieresis -55
+KPX Tcommaaccent Agrave -55
+KPX Tcommaaccent Amacron -55
+KPX Tcommaaccent Aogonek -55
+KPX Tcommaaccent Aring -55
+KPX Tcommaaccent Atilde -55
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -92
+KPX Tcommaaccent aacute -92
+KPX Tcommaaccent abreve -92
+KPX Tcommaaccent acircumflex -92
+KPX Tcommaaccent adieresis -92
+KPX Tcommaaccent agrave -92
+KPX Tcommaaccent amacron -92
+KPX Tcommaaccent aogonek -92
+KPX Tcommaaccent aring -92
+KPX Tcommaaccent atilde -92
+KPX Tcommaaccent colon -74
+KPX Tcommaaccent comma -92
+KPX Tcommaaccent e -92
+KPX Tcommaaccent eacute -92
+KPX Tcommaaccent ecaron -92
+KPX Tcommaaccent ecircumflex -92
+KPX Tcommaaccent edieresis -52
+KPX Tcommaaccent edotaccent -92
+KPX Tcommaaccent egrave -52
+KPX Tcommaaccent emacron -52
+KPX Tcommaaccent eogonek -92
+KPX Tcommaaccent hyphen -92
+KPX Tcommaaccent i -37
+KPX Tcommaaccent iacute -37
+KPX Tcommaaccent iogonek -37
+KPX Tcommaaccent o -95
+KPX Tcommaaccent oacute -95
+KPX Tcommaaccent ocircumflex -95
+KPX Tcommaaccent odieresis -95
+KPX Tcommaaccent ograve -95
+KPX Tcommaaccent ohungarumlaut -95
+KPX Tcommaaccent omacron -95
+KPX Tcommaaccent oslash -95
+KPX Tcommaaccent otilde -95
+KPX Tcommaaccent period -92
+KPX Tcommaaccent r -37
+KPX Tcommaaccent racute -37
+KPX Tcommaaccent rcaron -37
+KPX Tcommaaccent rcommaaccent -37
+KPX Tcommaaccent semicolon -74
+KPX Tcommaaccent u -37
+KPX Tcommaaccent uacute -37
+KPX Tcommaaccent ucircumflex -37
+KPX Tcommaaccent udieresis -37
+KPX Tcommaaccent ugrave -37
+KPX Tcommaaccent uhungarumlaut -37
+KPX Tcommaaccent umacron -37
+KPX Tcommaaccent uogonek -37
+KPX Tcommaaccent uring -37
+KPX Tcommaaccent w -37
+KPX Tcommaaccent y -37
+KPX Tcommaaccent yacute -37
+KPX Tcommaaccent ydieresis -37
+KPX U A -45
+KPX U Aacute -45
+KPX U Abreve -45
+KPX U Acircumflex -45
+KPX U Adieresis -45
+KPX U Agrave -45
+KPX U Amacron -45
+KPX U Aogonek -45
+KPX U Aring -45
+KPX U Atilde -45
+KPX Uacute A -45
+KPX Uacute Aacute -45
+KPX Uacute Abreve -45
+KPX Uacute Acircumflex -45
+KPX Uacute Adieresis -45
+KPX Uacute Agrave -45
+KPX Uacute Amacron -45
+KPX Uacute Aogonek -45
+KPX Uacute Aring -45
+KPX Uacute Atilde -45
+KPX Ucircumflex A -45
+KPX Ucircumflex Aacute -45
+KPX Ucircumflex Abreve -45
+KPX Ucircumflex Acircumflex -45
+KPX Ucircumflex Adieresis -45
+KPX Ucircumflex Agrave -45
+KPX Ucircumflex Amacron -45
+KPX Ucircumflex Aogonek -45
+KPX Ucircumflex Aring -45
+KPX Ucircumflex Atilde -45
+KPX Udieresis A -45
+KPX Udieresis Aacute -45
+KPX Udieresis Abreve -45
+KPX Udieresis Acircumflex -45
+KPX Udieresis Adieresis -45
+KPX Udieresis Agrave -45
+KPX Udieresis Amacron -45
+KPX Udieresis Aogonek -45
+KPX Udieresis Aring -45
+KPX Udieresis Atilde -45
+KPX Ugrave A -45
+KPX Ugrave Aacute -45
+KPX Ugrave Abreve -45
+KPX Ugrave Acircumflex -45
+KPX Ugrave Adieresis -45
+KPX Ugrave Agrave -45
+KPX Ugrave Amacron -45
+KPX Ugrave Aogonek -45
+KPX Ugrave Aring -45
+KPX Ugrave Atilde -45
+KPX Uhungarumlaut A -45
+KPX Uhungarumlaut Aacute -45
+KPX Uhungarumlaut Abreve -45
+KPX Uhungarumlaut Acircumflex -45
+KPX Uhungarumlaut Adieresis -45
+KPX Uhungarumlaut Agrave -45
+KPX Uhungarumlaut Amacron -45
+KPX Uhungarumlaut Aogonek -45
+KPX Uhungarumlaut Aring -45
+KPX Uhungarumlaut Atilde -45
+KPX Umacron A -45
+KPX Umacron Aacute -45
+KPX Umacron Abreve -45
+KPX Umacron Acircumflex -45
+KPX Umacron Adieresis -45
+KPX Umacron Agrave -45
+KPX Umacron Amacron -45
+KPX Umacron Aogonek -45
+KPX Umacron Aring -45
+KPX Umacron Atilde -45
+KPX Uogonek A -45
+KPX Uogonek Aacute -45
+KPX Uogonek Abreve -45
+KPX Uogonek Acircumflex -45
+KPX Uogonek Adieresis -45
+KPX Uogonek Agrave -45
+KPX Uogonek Amacron -45
+KPX Uogonek Aogonek -45
+KPX Uogonek Aring -45
+KPX Uogonek Atilde -45
+KPX Uring A -45
+KPX Uring Aacute -45
+KPX Uring Abreve -45
+KPX Uring Acircumflex -45
+KPX Uring Adieresis -45
+KPX Uring Agrave -45
+KPX Uring Amacron -45
+KPX Uring Aogonek -45
+KPX Uring Aring -45
+KPX Uring Atilde -45
+KPX V A -85
+KPX V Aacute -85
+KPX V Abreve -85
+KPX V Acircumflex -85
+KPX V Adieresis -85
+KPX V Agrave -85
+KPX V Amacron -85
+KPX V Aogonek -85
+KPX V Aring -85
+KPX V Atilde -85
+KPX V G -10
+KPX V Gbreve -10
+KPX V Gcommaaccent -10
+KPX V O -30
+KPX V Oacute -30
+KPX V Ocircumflex -30
+KPX V Odieresis -30
+KPX V Ograve -30
+KPX V Ohungarumlaut -30
+KPX V Omacron -30
+KPX V Oslash -30
+KPX V Otilde -30
+KPX V a -111
+KPX V aacute -111
+KPX V abreve -111
+KPX V acircumflex -111
+KPX V adieresis -111
+KPX V agrave -111
+KPX V amacron -111
+KPX V aogonek -111
+KPX V aring -111
+KPX V atilde -111
+KPX V colon -74
+KPX V comma -129
+KPX V e -111
+KPX V eacute -111
+KPX V ecaron -111
+KPX V ecircumflex -111
+KPX V edieresis -71
+KPX V edotaccent -111
+KPX V egrave -71
+KPX V emacron -71
+KPX V eogonek -111
+KPX V hyphen -70
+KPX V i -55
+KPX V iacute -55
+KPX V iogonek -55
+KPX V o -111
+KPX V oacute -111
+KPX V ocircumflex -111
+KPX V odieresis -111
+KPX V ograve -111
+KPX V ohungarumlaut -111
+KPX V omacron -111
+KPX V oslash -111
+KPX V otilde -111
+KPX V period -129
+KPX V semicolon -74
+KPX V u -55
+KPX V uacute -55
+KPX V ucircumflex -55
+KPX V udieresis -55
+KPX V ugrave -55
+KPX V uhungarumlaut -55
+KPX V umacron -55
+KPX V uogonek -55
+KPX V uring -55
+KPX W A -74
+KPX W Aacute -74
+KPX W Abreve -74
+KPX W Acircumflex -74
+KPX W Adieresis -74
+KPX W Agrave -74
+KPX W Amacron -74
+KPX W Aogonek -74
+KPX W Aring -74
+KPX W Atilde -74
+KPX W O -15
+KPX W Oacute -15
+KPX W Ocircumflex -15
+KPX W Odieresis -15
+KPX W Ograve -15
+KPX W Ohungarumlaut -15
+KPX W Omacron -15
+KPX W Oslash -15
+KPX W Otilde -15
+KPX W a -85
+KPX W aacute -85
+KPX W abreve -85
+KPX W acircumflex -85
+KPX W adieresis -85
+KPX W agrave -85
+KPX W amacron -85
+KPX W aogonek -85
+KPX W aring -85
+KPX W atilde -85
+KPX W colon -55
+KPX W comma -74
+KPX W e -90
+KPX W eacute -90
+KPX W ecaron -90
+KPX W ecircumflex -90
+KPX W edieresis -50
+KPX W edotaccent -90
+KPX W egrave -50
+KPX W emacron -50
+KPX W eogonek -90
+KPX W hyphen -50
+KPX W i -37
+KPX W iacute -37
+KPX W iogonek -37
+KPX W o -80
+KPX W oacute -80
+KPX W ocircumflex -80
+KPX W odieresis -80
+KPX W ograve -80
+KPX W ohungarumlaut -80
+KPX W omacron -80
+KPX W oslash -80
+KPX W otilde -80
+KPX W period -74
+KPX W semicolon -55
+KPX W u -55
+KPX W uacute -55
+KPX W ucircumflex -55
+KPX W udieresis -55
+KPX W ugrave -55
+KPX W uhungarumlaut -55
+KPX W umacron -55
+KPX W uogonek -55
+KPX W uring -55
+KPX W y -55
+KPX W yacute -55
+KPX W ydieresis -55
+KPX Y A -74
+KPX Y Aacute -74
+KPX Y Abreve -74
+KPX Y Acircumflex -74
+KPX Y Adieresis -74
+KPX Y Agrave -74
+KPX Y Amacron -74
+KPX Y Aogonek -74
+KPX Y Aring -74
+KPX Y Atilde -74
+KPX Y O -25
+KPX Y Oacute -25
+KPX Y Ocircumflex -25
+KPX Y Odieresis -25
+KPX Y Ograve -25
+KPX Y Ohungarumlaut -25
+KPX Y Omacron -25
+KPX Y Oslash -25
+KPX Y Otilde -25
+KPX Y a -92
+KPX Y aacute -92
+KPX Y abreve -92
+KPX Y acircumflex -92
+KPX Y adieresis -92
+KPX Y agrave -92
+KPX Y amacron -92
+KPX Y aogonek -92
+KPX Y aring -92
+KPX Y atilde -92
+KPX Y colon -92
+KPX Y comma -92
+KPX Y e -111
+KPX Y eacute -111
+KPX Y ecaron -111
+KPX Y ecircumflex -71
+KPX Y edieresis -71
+KPX Y edotaccent -111
+KPX Y egrave -71
+KPX Y emacron -71
+KPX Y eogonek -111
+KPX Y hyphen -92
+KPX Y i -55
+KPX Y iacute -55
+KPX Y iogonek -55
+KPX Y o -111
+KPX Y oacute -111
+KPX Y ocircumflex -111
+KPX Y odieresis -111
+KPX Y ograve -111
+KPX Y ohungarumlaut -111
+KPX Y omacron -111
+KPX Y oslash -111
+KPX Y otilde -111
+KPX Y period -74
+KPX Y semicolon -92
+KPX Y u -92
+KPX Y uacute -92
+KPX Y ucircumflex -92
+KPX Y udieresis -92
+KPX Y ugrave -92
+KPX Y uhungarumlaut -92
+KPX Y umacron -92
+KPX Y uogonek -92
+KPX Y uring -92
+KPX Yacute A -74
+KPX Yacute Aacute -74
+KPX Yacute Abreve -74
+KPX Yacute Acircumflex -74
+KPX Yacute Adieresis -74
+KPX Yacute Agrave -74
+KPX Yacute Amacron -74
+KPX Yacute Aogonek -74
+KPX Yacute Aring -74
+KPX Yacute Atilde -74
+KPX Yacute O -25
+KPX Yacute Oacute -25
+KPX Yacute Ocircumflex -25
+KPX Yacute Odieresis -25
+KPX Yacute Ograve -25
+KPX Yacute Ohungarumlaut -25
+KPX Yacute Omacron -25
+KPX Yacute Oslash -25
+KPX Yacute Otilde -25
+KPX Yacute a -92
+KPX Yacute aacute -92
+KPX Yacute abreve -92
+KPX Yacute acircumflex -92
+KPX Yacute adieresis -92
+KPX Yacute agrave -92
+KPX Yacute amacron -92
+KPX Yacute aogonek -92
+KPX Yacute aring -92
+KPX Yacute atilde -92
+KPX Yacute colon -92
+KPX Yacute comma -92
+KPX Yacute e -111
+KPX Yacute eacute -111
+KPX Yacute ecaron -111
+KPX Yacute ecircumflex -71
+KPX Yacute edieresis -71
+KPX Yacute edotaccent -111
+KPX Yacute egrave -71
+KPX Yacute emacron -71
+KPX Yacute eogonek -111
+KPX Yacute hyphen -92
+KPX Yacute i -55
+KPX Yacute iacute -55
+KPX Yacute iogonek -55
+KPX Yacute o -111
+KPX Yacute oacute -111
+KPX Yacute ocircumflex -111
+KPX Yacute odieresis -111
+KPX Yacute ograve -111
+KPX Yacute ohungarumlaut -111
+KPX Yacute omacron -111
+KPX Yacute oslash -111
+KPX Yacute otilde -111
+KPX Yacute period -74
+KPX Yacute semicolon -92
+KPX Yacute u -92
+KPX Yacute uacute -92
+KPX Yacute ucircumflex -92
+KPX Yacute udieresis -92
+KPX Yacute ugrave -92
+KPX Yacute uhungarumlaut -92
+KPX Yacute umacron -92
+KPX Yacute uogonek -92
+KPX Yacute uring -92
+KPX Ydieresis A -74
+KPX Ydieresis Aacute -74
+KPX Ydieresis Abreve -74
+KPX Ydieresis Acircumflex -74
+KPX Ydieresis Adieresis -74
+KPX Ydieresis Agrave -74
+KPX Ydieresis Amacron -74
+KPX Ydieresis Aogonek -74
+KPX Ydieresis Aring -74
+KPX Ydieresis Atilde -74
+KPX Ydieresis O -25
+KPX Ydieresis Oacute -25
+KPX Ydieresis Ocircumflex -25
+KPX Ydieresis Odieresis -25
+KPX Ydieresis Ograve -25
+KPX Ydieresis Ohungarumlaut -25
+KPX Ydieresis Omacron -25
+KPX Ydieresis Oslash -25
+KPX Ydieresis Otilde -25
+KPX Ydieresis a -92
+KPX Ydieresis aacute -92
+KPX Ydieresis abreve -92
+KPX Ydieresis acircumflex -92
+KPX Ydieresis adieresis -92
+KPX Ydieresis agrave -92
+KPX Ydieresis amacron -92
+KPX Ydieresis aogonek -92
+KPX Ydieresis aring -92
+KPX Ydieresis atilde -92
+KPX Ydieresis colon -92
+KPX Ydieresis comma -92
+KPX Ydieresis e -111
+KPX Ydieresis eacute -111
+KPX Ydieresis ecaron -111
+KPX Ydieresis ecircumflex -71
+KPX Ydieresis edieresis -71
+KPX Ydieresis edotaccent -111
+KPX Ydieresis egrave -71
+KPX Ydieresis emacron -71
+KPX Ydieresis eogonek -111
+KPX Ydieresis hyphen -92
+KPX Ydieresis i -55
+KPX Ydieresis iacute -55
+KPX Ydieresis iogonek -55
+KPX Ydieresis o -111
+KPX Ydieresis oacute -111
+KPX Ydieresis ocircumflex -111
+KPX Ydieresis odieresis -111
+KPX Ydieresis ograve -111
+KPX Ydieresis ohungarumlaut -111
+KPX Ydieresis omacron -111
+KPX Ydieresis oslash -111
+KPX Ydieresis otilde -111
+KPX Ydieresis period -74
+KPX Ydieresis semicolon -92
+KPX Ydieresis u -92
+KPX Ydieresis uacute -92
+KPX Ydieresis ucircumflex -92
+KPX Ydieresis udieresis -92
+KPX Ydieresis ugrave -92
+KPX Ydieresis uhungarumlaut -92
+KPX Ydieresis umacron -92
+KPX Ydieresis uogonek -92
+KPX Ydieresis uring -92
+KPX b b -10
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX c h -10
+KPX c k -10
+KPX c kcommaaccent -10
+KPX cacute h -10
+KPX cacute k -10
+KPX cacute kcommaaccent -10
+KPX ccaron h -10
+KPX ccaron k -10
+KPX ccaron kcommaaccent -10
+KPX ccedilla h -10
+KPX ccedilla k -10
+KPX ccedilla kcommaaccent -10
+KPX comma quotedblright -95
+KPX comma quoteright -95
+KPX e b -10
+KPX eacute b -10
+KPX ecaron b -10
+KPX ecircumflex b -10
+KPX edieresis b -10
+KPX edotaccent b -10
+KPX egrave b -10
+KPX emacron b -10
+KPX eogonek b -10
+KPX f comma -10
+KPX f dotlessi -30
+KPX f e -10
+KPX f eacute -10
+KPX f edotaccent -10
+KPX f eogonek -10
+KPX f f -18
+KPX f o -10
+KPX f oacute -10
+KPX f ocircumflex -10
+KPX f ograve -10
+KPX f ohungarumlaut -10
+KPX f oslash -10
+KPX f otilde -10
+KPX f period -10
+KPX f quoteright 55
+KPX k e -30
+KPX k eacute -30
+KPX k ecaron -30
+KPX k ecircumflex -30
+KPX k edieresis -30
+KPX k edotaccent -30
+KPX k egrave -30
+KPX k emacron -30
+KPX k eogonek -30
+KPX k o -10
+KPX k oacute -10
+KPX k ocircumflex -10
+KPX k odieresis -10
+KPX k ograve -10
+KPX k ohungarumlaut -10
+KPX k omacron -10
+KPX k oslash -10
+KPX k otilde -10
+KPX kcommaaccent e -30
+KPX kcommaaccent eacute -30
+KPX kcommaaccent ecaron -30
+KPX kcommaaccent ecircumflex -30
+KPX kcommaaccent edieresis -30
+KPX kcommaaccent edotaccent -30
+KPX kcommaaccent egrave -30
+KPX kcommaaccent emacron -30
+KPX kcommaaccent eogonek -30
+KPX kcommaaccent o -10
+KPX kcommaaccent oacute -10
+KPX kcommaaccent ocircumflex -10
+KPX kcommaaccent odieresis -10
+KPX kcommaaccent ograve -10
+KPX kcommaaccent ohungarumlaut -10
+KPX kcommaaccent omacron -10
+KPX kcommaaccent oslash -10
+KPX kcommaaccent otilde -10
+KPX n v -40
+KPX nacute v -40
+KPX ncaron v -40
+KPX ncommaaccent v -40
+KPX ntilde v -40
+KPX o v -15
+KPX o w -25
+KPX o x -10
+KPX o y -10
+KPX o yacute -10
+KPX o ydieresis -10
+KPX oacute v -15
+KPX oacute w -25
+KPX oacute x -10
+KPX oacute y -10
+KPX oacute yacute -10
+KPX oacute ydieresis -10
+KPX ocircumflex v -15
+KPX ocircumflex w -25
+KPX ocircumflex x -10
+KPX ocircumflex y -10
+KPX ocircumflex yacute -10
+KPX ocircumflex ydieresis -10
+KPX odieresis v -15
+KPX odieresis w -25
+KPX odieresis x -10
+KPX odieresis y -10
+KPX odieresis yacute -10
+KPX odieresis ydieresis -10
+KPX ograve v -15
+KPX ograve w -25
+KPX ograve x -10
+KPX ograve y -10
+KPX ograve yacute -10
+KPX ograve ydieresis -10
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -25
+KPX ohungarumlaut x -10
+KPX ohungarumlaut y -10
+KPX ohungarumlaut yacute -10
+KPX ohungarumlaut ydieresis -10
+KPX omacron v -15
+KPX omacron w -25
+KPX omacron x -10
+KPX omacron y -10
+KPX omacron yacute -10
+KPX omacron ydieresis -10
+KPX oslash v -15
+KPX oslash w -25
+KPX oslash x -10
+KPX oslash y -10
+KPX oslash yacute -10
+KPX oslash ydieresis -10
+KPX otilde v -15
+KPX otilde w -25
+KPX otilde x -10
+KPX otilde y -10
+KPX otilde yacute -10
+KPX otilde ydieresis -10
+KPX period quotedblright -95
+KPX period quoteright -95
+KPX quoteleft quoteleft -74
+KPX quoteright d -15
+KPX quoteright dcroat -15
+KPX quoteright quoteright -74
+KPX quoteright r -15
+KPX quoteright racute -15
+KPX quoteright rcaron -15
+KPX quoteright rcommaaccent -15
+KPX quoteright s -74
+KPX quoteright sacute -74
+KPX quoteright scaron -74
+KPX quoteright scedilla -74
+KPX quoteright scommaaccent -74
+KPX quoteright space -74
+KPX quoteright t -37
+KPX quoteright tcommaaccent -37
+KPX quoteright v -15
+KPX r comma -65
+KPX r period -65
+KPX racute comma -65
+KPX racute period -65
+KPX rcaron comma -65
+KPX rcaron period -65
+KPX rcommaaccent comma -65
+KPX rcommaaccent period -65
+KPX space A -37
+KPX space Aacute -37
+KPX space Abreve -37
+KPX space Acircumflex -37
+KPX space Adieresis -37
+KPX space Agrave -37
+KPX space Amacron -37
+KPX space Aogonek -37
+KPX space Aring -37
+KPX space Atilde -37
+KPX space V -70
+KPX space W -70
+KPX space Y -70
+KPX space Yacute -70
+KPX space Ydieresis -70
+KPX v comma -37
+KPX v e -15
+KPX v eacute -15
+KPX v ecaron -15
+KPX v ecircumflex -15
+KPX v edieresis -15
+KPX v edotaccent -15
+KPX v egrave -15
+KPX v emacron -15
+KPX v eogonek -15
+KPX v o -15
+KPX v oacute -15
+KPX v ocircumflex -15
+KPX v odieresis -15
+KPX v ograve -15
+KPX v ohungarumlaut -15
+KPX v omacron -15
+KPX v oslash -15
+KPX v otilde -15
+KPX v period -37
+KPX w a -10
+KPX w aacute -10
+KPX w abreve -10
+KPX w acircumflex -10
+KPX w adieresis -10
+KPX w agrave -10
+KPX w amacron -10
+KPX w aogonek -10
+KPX w aring -10
+KPX w atilde -10
+KPX w comma -37
+KPX w e -10
+KPX w eacute -10
+KPX w ecaron -10
+KPX w ecircumflex -10
+KPX w edieresis -10
+KPX w edotaccent -10
+KPX w egrave -10
+KPX w emacron -10
+KPX w eogonek -10
+KPX w o -15
+KPX w oacute -15
+KPX w ocircumflex -15
+KPX w odieresis -15
+KPX w ograve -15
+KPX w ohungarumlaut -15
+KPX w omacron -15
+KPX w oslash -15
+KPX w otilde -15
+KPX w period -37
+KPX x e -10
+KPX x eacute -10
+KPX x ecaron -10
+KPX x ecircumflex -10
+KPX x edieresis -10
+KPX x edotaccent -10
+KPX x egrave -10
+KPX x emacron -10
+KPX x eogonek -10
+KPX y comma -37
+KPX y period -37
+KPX yacute comma -37
+KPX yacute period -37
+KPX ydieresis comma -37
+KPX ydieresis period -37
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Italic.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Italic.afm
new file mode 100644
index 0000000..3c25d77
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Italic.afm
@@ -0,0 +1,2669 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:56:55 1997
+Comment UniqueID 43067
+Comment VMusage 47727 58752
+FontName Times-Italic
+FullName Times Italic
+FamilyName Times
+Weight Medium
+ItalicAngle -15.5
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -169 -217 1010 883
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 653
+XHeight 441
+Ascender 683
+Descender -217
+StdHW 32
+StdVW 76
+StartCharMetrics 317
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 39 -11 302 667 ;
+C 34 ; WX 420 ; N quotedbl ; B 144 421 432 666 ;
+C 35 ; WX 500 ; N numbersign ; B 2 0 540 676 ;
+C 36 ; WX 500 ; N dollar ; B 31 -89 497 731 ;
+C 37 ; WX 833 ; N percent ; B 79 -13 790 676 ;
+C 38 ; WX 778 ; N ampersand ; B 76 -18 723 666 ;
+C 146 ; WX 333 ; N quoteright ; B 151 436 290 666 ;
+C 40 ; WX 333 ; N parenleft ; B 42 -181 315 669 ;
+C 41 ; WX 333 ; N parenright ; B 16 -180 289 669 ;
+C 42 ; WX 500 ; N asterisk ; B 128 255 492 666 ;
+C 43 ; WX 675 ; N plus ; B 86 0 590 506 ;
+C 44 ; WX 250 ; N comma ; B -4 -129 135 101 ;
+C 45 ; WX 333 ; N hyphen ; B 49 192 282 255 ;
+C 173 ; WX 333 ; N hyphen ; B 49 192 282 255 ;
+C 46 ; WX 250 ; N period ; B 27 -11 138 100 ;
+C 47 ; WX 278 ; N slash ; B -65 -18 386 666 ;
+C 48 ; WX 500 ; N zero ; B 32 -7 497 676 ;
+C 49 ; WX 500 ; N one ; B 49 0 409 676 ;
+C 50 ; WX 500 ; N two ; B 12 0 452 676 ;
+C 51 ; WX 500 ; N three ; B 15 -7 465 676 ;
+C 52 ; WX 500 ; N four ; B 1 0 479 676 ;
+C 53 ; WX 500 ; N five ; B 15 -7 491 666 ;
+C 54 ; WX 500 ; N six ; B 30 -7 521 686 ;
+C 55 ; WX 500 ; N seven ; B 75 -8 537 666 ;
+C 56 ; WX 500 ; N eight ; B 30 -7 493 676 ;
+C 57 ; WX 500 ; N nine ; B 23 -17 492 676 ;
+C 58 ; WX 333 ; N colon ; B 50 -11 261 441 ;
+C 59 ; WX 333 ; N semicolon ; B 27 -129 261 441 ;
+C 60 ; WX 675 ; N less ; B 84 -8 592 514 ;
+C 61 ; WX 675 ; N equal ; B 86 120 590 386 ;
+C 62 ; WX 675 ; N greater ; B 84 -8 592 514 ;
+C 63 ; WX 500 ; N question ; B 132 -12 472 664 ;
+C 64 ; WX 920 ; N at ; B 118 -18 806 666 ;
+C 65 ; WX 611 ; N A ; B -51 0 564 668 ;
+C 66 ; WX 611 ; N B ; B -8 0 588 653 ;
+C 67 ; WX 667 ; N C ; B 66 -18 689 666 ;
+C 68 ; WX 722 ; N D ; B -8 0 700 653 ;
+C 69 ; WX 611 ; N E ; B -1 0 634 653 ;
+C 70 ; WX 611 ; N F ; B 8 0 645 653 ;
+C 71 ; WX 722 ; N G ; B 52 -18 722 666 ;
+C 72 ; WX 722 ; N H ; B -8 0 767 653 ;
+C 73 ; WX 333 ; N I ; B -8 0 384 653 ;
+C 74 ; WX 444 ; N J ; B -6 -18 491 653 ;
+C 75 ; WX 667 ; N K ; B 7 0 722 653 ;
+C 76 ; WX 556 ; N L ; B -8 0 559 653 ;
+C 77 ; WX 833 ; N M ; B -18 0 873 653 ;
+C 78 ; WX 667 ; N N ; B -20 -15 727 653 ;
+C 79 ; WX 722 ; N O ; B 60 -18 699 666 ;
+C 80 ; WX 611 ; N P ; B 0 0 605 653 ;
+C 81 ; WX 722 ; N Q ; B 59 -182 699 666 ;
+C 82 ; WX 611 ; N R ; B -13 0 588 653 ;
+C 83 ; WX 500 ; N S ; B 17 -18 508 667 ;
+C 84 ; WX 556 ; N T ; B 59 0 633 653 ;
+C 85 ; WX 722 ; N U ; B 102 -18 765 653 ;
+C 86 ; WX 611 ; N V ; B 76 -18 688 653 ;
+C 87 ; WX 833 ; N W ; B 71 -18 906 653 ;
+C 88 ; WX 611 ; N X ; B -29 0 655 653 ;
+C 89 ; WX 556 ; N Y ; B 78 0 633 653 ;
+C 90 ; WX 556 ; N Z ; B -6 0 606 653 ;
+C 91 ; WX 389 ; N bracketleft ; B 21 -153 391 663 ;
+C 92 ; WX 278 ; N backslash ; B -41 -18 319 666 ;
+C 93 ; WX 389 ; N bracketright ; B 12 -153 382 663 ;
+C 94 ; WX 422 ; N asciicircum ; B 0 301 422 666 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 145 ; WX 333 ; N quoteleft ; B 171 436 310 666 ;
+C 97 ; WX 500 ; N a ; B 17 -11 476 441 ;
+C 98 ; WX 500 ; N b ; B 23 -11 473 683 ;
+C 99 ; WX 444 ; N c ; B 30 -11 425 441 ;
+C 100 ; WX 500 ; N d ; B 15 -13 527 683 ;
+C 101 ; WX 444 ; N e ; B 31 -11 412 441 ;
+C 102 ; WX 278 ; N f ; B -147 -207 424 678 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 8 -206 472 441 ;
+C 104 ; WX 500 ; N h ; B 19 -9 478 683 ;
+C 105 ; WX 278 ; N i ; B 49 -11 264 654 ;
+C 106 ; WX 278 ; N j ; B -124 -207 276 654 ;
+C 107 ; WX 444 ; N k ; B 14 -11 461 683 ;
+C 108 ; WX 278 ; N l ; B 41 -11 279 683 ;
+C 109 ; WX 722 ; N m ; B 12 -9 704 441 ;
+C 110 ; WX 500 ; N n ; B 14 -9 474 441 ;
+C 111 ; WX 500 ; N o ; B 27 -11 468 441 ;
+C 112 ; WX 500 ; N p ; B -75 -205 469 441 ;
+C 113 ; WX 500 ; N q ; B 25 -209 483 441 ;
+C 114 ; WX 389 ; N r ; B 45 0 412 441 ;
+C 115 ; WX 389 ; N s ; B 16 -13 366 442 ;
+C 116 ; WX 278 ; N t ; B 37 -11 296 546 ;
+C 117 ; WX 500 ; N u ; B 42 -11 475 441 ;
+C 118 ; WX 444 ; N v ; B 21 -18 426 441 ;
+C 119 ; WX 667 ; N w ; B 16 -18 648 441 ;
+C 120 ; WX 444 ; N x ; B -27 -11 447 441 ;
+C 121 ; WX 444 ; N y ; B -24 -206 426 441 ;
+C 122 ; WX 389 ; N z ; B -2 -81 380 428 ;
+C 123 ; WX 400 ; N braceleft ; B 51 -177 407 687 ;
+C 124 ; WX 275 ; N bar ; B 105 -217 171 783 ;
+C 125 ; WX 400 ; N braceright ; B -7 -177 349 687 ;
+C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;
+C 161 ; WX 389 ; N exclamdown ; B 59 -205 322 473 ;
+C 162 ; WX 500 ; N cent ; B 77 -143 472 560 ;
+C 163 ; WX 500 ; N sterling ; B 10 -6 517 670 ;
+C -1 ; WX 167 ; N fraction ; B -169 -10 337 676 ;
+C 165 ; WX 500 ; N yen ; B 27 0 603 653 ;
+C 131 ; WX 500 ; N florin ; B 25 -182 507 682 ;
+C 167 ; WX 500 ; N section ; B 53 -162 461 666 ;
+C 164 ; WX 500 ; N currency ; B -22 53 522 597 ;
+C 39 ; WX 214 ; N quotesingle ; B 132 421 241 666 ;
+C 147 ; WX 556 ; N quotedblleft ; B 166 436 514 666 ;
+C 171 ; WX 500 ; N guillemotleft ; B 53 37 445 403 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 51 37 281 403 ;
+C 155 ; WX 333 ; N guilsinglright ; B 52 37 282 403 ;
+C -1 ; WX 500 ; N fi ; B -141 -207 481 681 ;
+C -1 ; WX 500 ; N fl ; B -141 -204 518 682 ;
+C 150 ; WX 500 ; N endash ; B -6 197 505 243 ;
+C 134 ; WX 500 ; N dagger ; B 101 -159 488 666 ;
+C 135 ; WX 500 ; N daggerdbl ; B 22 -143 491 666 ;
+C 183 ; WX 250 ; N periodcentered ; B 70 199 181 310 ;
+C 182 ; WX 523 ; N paragraph ; B 55 -123 616 653 ;
+C 149 ; WX 350 ; N bullet ; B 40 191 310 461 ;
+C 130 ; WX 333 ; N quotesinglbase ; B 44 -129 183 101 ;
+C 132 ; WX 556 ; N quotedblbase ; B 57 -129 405 101 ;
+C 148 ; WX 556 ; N quotedblright ; B 151 436 499 666 ;
+C 187 ; WX 500 ; N guillemotright ; B 55 37 447 403 ;
+C 133 ; WX 889 ; N ellipsis ; B 57 -11 762 100 ;
+C 137 ; WX 1000 ; N perthousand ; B 25 -19 1010 706 ;
+C 191 ; WX 500 ; N questiondown ; B 28 -205 368 471 ;
+C 96 ; WX 333 ; N grave ; B 121 492 311 664 ;
+C 180 ; WX 333 ; N acute ; B 180 494 403 664 ;
+C 136 ; WX 333 ; N circumflex ; B 91 492 385 661 ;
+C 152 ; WX 333 ; N tilde ; B 100 517 427 624 ;
+C 175 ; WX 333 ; N macron ; B 99 532 411 583 ;
+C -1 ; WX 333 ; N breve ; B 117 492 418 650 ;
+C -1 ; WX 333 ; N dotaccent ; B 207 548 305 646 ;
+C 168 ; WX 333 ; N dieresis ; B 107 548 405 646 ;
+C -1 ; WX 333 ; N ring ; B 155 492 355 691 ;
+C 184 ; WX 333 ; N cedilla ; B -30 -217 182 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B 93 494 486 664 ;
+C -1 ; WX 333 ; N ogonek ; B 20 -169 203 40 ;
+C -1 ; WX 333 ; N caron ; B 121 492 426 661 ;
+C 151 ; WX 889 ; N emdash ; B -6 197 894 243 ;
+C 198 ; WX 889 ; N AE ; B -27 0 911 653 ;
+C 170 ; WX 276 ; N ordfeminine ; B 42 406 352 676 ;
+C -1 ; WX 556 ; N Lslash ; B -8 0 559 653 ;
+C 216 ; WX 722 ; N Oslash ; B 60 -105 699 722 ;
+C 140 ; WX 944 ; N OE ; B 49 -8 964 666 ;
+C 186 ; WX 310 ; N ordmasculine ; B 67 406 362 676 ;
+C 230 ; WX 667 ; N ae ; B 23 -11 640 441 ;
+C -1 ; WX 278 ; N dotlessi ; B 49 -11 235 441 ;
+C -1 ; WX 278 ; N lslash ; B 41 -11 312 683 ;
+C 248 ; WX 500 ; N oslash ; B 28 -135 469 554 ;
+C 156 ; WX 667 ; N oe ; B 20 -12 646 441 ;
+C 223 ; WX 500 ; N germandbls ; B -168 -207 493 679 ;
+C 207 ; WX 333 ; N Idieresis ; B -8 0 435 818 ;
+C 233 ; WX 444 ; N eacute ; B 31 -11 459 664 ;
+C -1 ; WX 500 ; N abreve ; B 17 -11 502 650 ;
+C -1 ; WX 500 ; N uhungarumlaut ; B 42 -11 580 664 ;
+C -1 ; WX 444 ; N ecaron ; B 31 -11 482 661 ;
+C 159 ; WX 556 ; N Ydieresis ; B 78 0 633 818 ;
+C 247 ; WX 675 ; N divide ; B 86 -11 590 517 ;
+C 221 ; WX 556 ; N Yacute ; B 78 0 633 876 ;
+C 194 ; WX 611 ; N Acircumflex ; B -51 0 564 873 ;
+C 225 ; WX 500 ; N aacute ; B 17 -11 487 664 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 102 -18 765 873 ;
+C 253 ; WX 444 ; N yacute ; B -24 -206 459 664 ;
+C -1 ; WX 389 ; N scommaaccent ; B 16 -217 366 442 ;
+C 234 ; WX 444 ; N ecircumflex ; B 31 -11 441 661 ;
+C -1 ; WX 722 ; N Uring ; B 102 -18 765 883 ;
+C 220 ; WX 722 ; N Udieresis ; B 102 -18 765 818 ;
+C -1 ; WX 500 ; N aogonek ; B 17 -169 476 441 ;
+C 218 ; WX 722 ; N Uacute ; B 102 -18 765 876 ;
+C -1 ; WX 500 ; N uogonek ; B 42 -169 477 441 ;
+C 203 ; WX 611 ; N Edieresis ; B -1 0 634 818 ;
+C -1 ; WX 722 ; N Dcroat ; B -8 0 700 653 ;
+C -1 ; WX 250 ; N commaaccent ; B 8 -217 133 -50 ;
+C 169 ; WX 760 ; N copyright ; B 41 -18 719 666 ;
+C -1 ; WX 611 ; N Emacron ; B -1 0 634 795 ;
+C -1 ; WX 444 ; N ccaron ; B 30 -11 482 661 ;
+C 229 ; WX 500 ; N aring ; B 17 -11 476 691 ;
+C -1 ; WX 667 ; N Ncommaaccent ; B -20 -187 727 653 ;
+C -1 ; WX 278 ; N lacute ; B 41 -11 395 876 ;
+C 224 ; WX 500 ; N agrave ; B 17 -11 476 664 ;
+C -1 ; WX 556 ; N Tcommaaccent ; B 59 -217 633 653 ;
+C -1 ; WX 667 ; N Cacute ; B 66 -18 690 876 ;
+C 227 ; WX 500 ; N atilde ; B 17 -11 511 624 ;
+C -1 ; WX 611 ; N Edotaccent ; B -1 0 634 818 ;
+C 154 ; WX 389 ; N scaron ; B 16 -13 454 661 ;
+C -1 ; WX 389 ; N scedilla ; B 16 -217 366 442 ;
+C 237 ; WX 278 ; N iacute ; B 49 -11 355 664 ;
+C -1 ; WX 471 ; N lozenge ; B 13 0 459 724 ;
+C -1 ; WX 611 ; N Rcaron ; B -13 0 588 873 ;
+C -1 ; WX 722 ; N Gcommaaccent ; B 52 -217 722 666 ;
+C 251 ; WX 500 ; N ucircumflex ; B 42 -11 475 661 ;
+C 226 ; WX 500 ; N acircumflex ; B 17 -11 476 661 ;
+C -1 ; WX 611 ; N Amacron ; B -51 0 564 795 ;
+C -1 ; WX 389 ; N rcaron ; B 45 0 434 661 ;
+C 231 ; WX 444 ; N ccedilla ; B 30 -217 425 441 ;
+C -1 ; WX 556 ; N Zdotaccent ; B -6 0 606 818 ;
+C 222 ; WX 611 ; N Thorn ; B 0 0 569 653 ;
+C -1 ; WX 722 ; N Omacron ; B 60 -18 699 795 ;
+C -1 ; WX 611 ; N Racute ; B -13 0 588 876 ;
+C -1 ; WX 500 ; N Sacute ; B 17 -18 508 876 ;
+C -1 ; WX 544 ; N dcaron ; B 15 -13 658 683 ;
+C -1 ; WX 722 ; N Umacron ; B 102 -18 765 795 ;
+C -1 ; WX 500 ; N uring ; B 42 -11 475 691 ;
+C 179 ; WX 300 ; N threesuperior ; B 43 268 339 676 ;
+C 210 ; WX 722 ; N Ograve ; B 60 -18 699 876 ;
+C 192 ; WX 611 ; N Agrave ; B -51 0 564 876 ;
+C -1 ; WX 611 ; N Abreve ; B -51 0 564 862 ;
+C 215 ; WX 675 ; N multiply ; B 93 8 582 497 ;
+C 250 ; WX 500 ; N uacute ; B 42 -11 477 664 ;
+C -1 ; WX 556 ; N Tcaron ; B 59 0 633 873 ;
+C -1 ; WX 476 ; N partialdiff ; B 17 -38 459 710 ;
+C 255 ; WX 444 ; N ydieresis ; B -24 -206 441 606 ;
+C -1 ; WX 667 ; N Nacute ; B -20 -15 727 876 ;
+C 238 ; WX 278 ; N icircumflex ; B 33 -11 327 661 ;
+C 202 ; WX 611 ; N Ecircumflex ; B -1 0 634 873 ;
+C 228 ; WX 500 ; N adieresis ; B 17 -11 489 606 ;
+C 235 ; WX 444 ; N edieresis ; B 31 -11 451 606 ;
+C -1 ; WX 444 ; N cacute ; B 30 -11 459 664 ;
+C -1 ; WX 500 ; N nacute ; B 14 -9 477 664 ;
+C -1 ; WX 500 ; N umacron ; B 42 -11 485 583 ;
+C -1 ; WX 667 ; N Ncaron ; B -20 -15 727 873 ;
+C 205 ; WX 333 ; N Iacute ; B -8 0 433 876 ;
+C 177 ; WX 675 ; N plusminus ; B 86 0 590 506 ;
+C 166 ; WX 275 ; N brokenbar ; B 105 -142 171 708 ;
+C 174 ; WX 760 ; N registered ; B 41 -18 719 666 ;
+C -1 ; WX 722 ; N Gbreve ; B 52 -18 722 862 ;
+C -1 ; WX 333 ; N Idotaccent ; B -8 0 384 818 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
+C 200 ; WX 611 ; N Egrave ; B -1 0 634 876 ;
+C -1 ; WX 389 ; N racute ; B 45 0 431 664 ;
+C -1 ; WX 500 ; N omacron ; B 27 -11 495 583 ;
+C -1 ; WX 556 ; N Zacute ; B -6 0 606 876 ;
+C 142 ; WX 556 ; N Zcaron ; B -6 0 606 873 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 658 ;
+C 208 ; WX 722 ; N Eth ; B -8 0 700 653 ;
+C 199 ; WX 667 ; N Ccedilla ; B 66 -217 689 666 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 22 -217 279 683 ;
+C -1 ; WX 300 ; N tcaron ; B 37 -11 407 681 ;
+C -1 ; WX 444 ; N eogonek ; B 31 -169 412 441 ;
+C -1 ; WX 722 ; N Uogonek ; B 102 -184 765 653 ;
+C 193 ; WX 611 ; N Aacute ; B -51 0 564 876 ;
+C 196 ; WX 611 ; N Adieresis ; B -51 0 564 818 ;
+C 232 ; WX 444 ; N egrave ; B 31 -11 412 664 ;
+C -1 ; WX 389 ; N zacute ; B -2 -81 431 664 ;
+C -1 ; WX 278 ; N iogonek ; B 49 -169 264 654 ;
+C 211 ; WX 722 ; N Oacute ; B 60 -18 699 876 ;
+C 243 ; WX 500 ; N oacute ; B 27 -11 487 664 ;
+C -1 ; WX 500 ; N amacron ; B 17 -11 495 583 ;
+C -1 ; WX 389 ; N sacute ; B 16 -13 431 664 ;
+C 239 ; WX 278 ; N idieresis ; B 49 -11 352 606 ;
+C 212 ; WX 722 ; N Ocircumflex ; B 60 -18 699 873 ;
+C 217 ; WX 722 ; N Ugrave ; B 102 -18 765 876 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 500 ; N thorn ; B -75 -205 469 683 ;
+C 178 ; WX 300 ; N twosuperior ; B 33 271 324 676 ;
+C 214 ; WX 722 ; N Odieresis ; B 60 -18 699 818 ;
+C 181 ; WX 500 ; N mu ; B -30 -209 497 428 ;
+C 236 ; WX 278 ; N igrave ; B 49 -11 284 664 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B 27 -11 590 664 ;
+C -1 ; WX 611 ; N Eogonek ; B -1 -169 634 653 ;
+C -1 ; WX 500 ; N dcroat ; B 15 -13 572 683 ;
+C 190 ; WX 750 ; N threequarters ; B 23 -10 736 676 ;
+C -1 ; WX 500 ; N Scedilla ; B 17 -217 508 667 ;
+C -1 ; WX 300 ; N lcaron ; B 41 -11 407 683 ;
+C -1 ; WX 667 ; N Kcommaaccent ; B 7 -217 722 653 ;
+C -1 ; WX 556 ; N Lacute ; B -8 0 559 876 ;
+C 153 ; WX 980 ; N trademark ; B 30 247 957 653 ;
+C -1 ; WX 444 ; N edotaccent ; B 31 -11 412 606 ;
+C 204 ; WX 333 ; N Igrave ; B -8 0 384 876 ;
+C -1 ; WX 333 ; N Imacron ; B -8 0 441 795 ;
+C -1 ; WX 611 ; N Lcaron ; B -8 0 586 653 ;
+C 189 ; WX 750 ; N onehalf ; B 34 -10 749 676 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 523 658 ;
+C 244 ; WX 500 ; N ocircumflex ; B 27 -11 468 661 ;
+C 241 ; WX 500 ; N ntilde ; B 14 -9 476 624 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 102 -18 765 876 ;
+C 201 ; WX 611 ; N Eacute ; B -1 0 634 876 ;
+C -1 ; WX 444 ; N emacron ; B 31 -11 457 583 ;
+C -1 ; WX 500 ; N gbreve ; B 8 -206 487 650 ;
+C 188 ; WX 750 ; N onequarter ; B 33 -10 736 676 ;
+C 138 ; WX 500 ; N Scaron ; B 17 -18 520 873 ;
+C -1 ; WX 500 ; N Scommaaccent ; B 17 -217 508 667 ;
+C -1 ; WX 722 ; N Ohungarumlaut ; B 60 -18 699 876 ;
+C 176 ; WX 400 ; N degree ; B 101 390 387 676 ;
+C 242 ; WX 500 ; N ograve ; B 27 -11 468 664 ;
+C -1 ; WX 667 ; N Ccaron ; B 66 -18 689 873 ;
+C 249 ; WX 500 ; N ugrave ; B 42 -11 475 664 ;
+C -1 ; WX 453 ; N radical ; B 2 -60 452 768 ;
+C -1 ; WX 722 ; N Dcaron ; B -8 0 700 873 ;
+C -1 ; WX 389 ; N rcommaaccent ; B -3 -217 412 441 ;
+C 209 ; WX 667 ; N Ntilde ; B -20 -15 727 836 ;
+C 245 ; WX 500 ; N otilde ; B 27 -11 496 624 ;
+C -1 ; WX 611 ; N Rcommaaccent ; B -13 -187 588 653 ;
+C -1 ; WX 556 ; N Lcommaaccent ; B -8 -217 559 653 ;
+C 195 ; WX 611 ; N Atilde ; B -51 0 566 836 ;
+C -1 ; WX 611 ; N Aogonek ; B -51 -169 566 668 ;
+C 197 ; WX 611 ; N Aring ; B -51 0 564 883 ;
+C 213 ; WX 722 ; N Otilde ; B 60 -18 699 836 ;
+C -1 ; WX 389 ; N zdotaccent ; B -2 -81 380 606 ;
+C -1 ; WX 611 ; N Ecaron ; B -1 0 634 873 ;
+C -1 ; WX 333 ; N Iogonek ; B -8 -169 384 653 ;
+C -1 ; WX 444 ; N kcommaaccent ; B 14 -187 461 683 ;
+C -1 ; WX 675 ; N minus ; B 86 220 590 286 ;
+C 206 ; WX 333 ; N Icircumflex ; B -8 0 425 873 ;
+C -1 ; WX 500 ; N ncaron ; B 14 -9 510 661 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 2 -217 296 546 ;
+C 172 ; WX 675 ; N logicalnot ; B 86 108 590 386 ;
+C 246 ; WX 500 ; N odieresis ; B 27 -11 489 606 ;
+C 252 ; WX 500 ; N udieresis ; B 42 -11 479 606 ;
+C -1 ; WX 549 ; N notequal ; B 12 -29 537 541 ;
+C -1 ; WX 500 ; N gcommaaccent ; B 8 -206 472 706 ;
+C 240 ; WX 500 ; N eth ; B 27 -11 482 683 ;
+C 158 ; WX 389 ; N zcaron ; B -2 -81 434 661 ;
+C -1 ; WX 500 ; N ncommaaccent ; B 14 -187 474 441 ;
+C 185 ; WX 300 ; N onesuperior ; B 43 271 284 676 ;
+C -1 ; WX 278 ; N imacron ; B 46 -11 311 583 ;
+C 128 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2321
+KPX A C -30
+KPX A Cacute -30
+KPX A Ccaron -30
+KPX A Ccedilla -30
+KPX A G -35
+KPX A Gbreve -35
+KPX A Gcommaaccent -35
+KPX A O -40
+KPX A Oacute -40
+KPX A Ocircumflex -40
+KPX A Odieresis -40
+KPX A Ograve -40
+KPX A Ohungarumlaut -40
+KPX A Omacron -40
+KPX A Oslash -40
+KPX A Otilde -40
+KPX A Q -40
+KPX A T -37
+KPX A Tcaron -37
+KPX A Tcommaaccent -37
+KPX A U -50
+KPX A Uacute -50
+KPX A Ucircumflex -50
+KPX A Udieresis -50
+KPX A Ugrave -50
+KPX A Uhungarumlaut -50
+KPX A Umacron -50
+KPX A Uogonek -50
+KPX A Uring -50
+KPX A V -105
+KPX A W -95
+KPX A Y -55
+KPX A Yacute -55
+KPX A Ydieresis -55
+KPX A quoteright -37
+KPX A u -20
+KPX A uacute -20
+KPX A ucircumflex -20
+KPX A udieresis -20
+KPX A ugrave -20
+KPX A uhungarumlaut -20
+KPX A umacron -20
+KPX A uogonek -20
+KPX A uring -20
+KPX A v -55
+KPX A w -55
+KPX A y -55
+KPX A yacute -55
+KPX A ydieresis -55
+KPX Aacute C -30
+KPX Aacute Cacute -30
+KPX Aacute Ccaron -30
+KPX Aacute Ccedilla -30
+KPX Aacute G -35
+KPX Aacute Gbreve -35
+KPX Aacute Gcommaaccent -35
+KPX Aacute O -40
+KPX Aacute Oacute -40
+KPX Aacute Ocircumflex -40
+KPX Aacute Odieresis -40
+KPX Aacute Ograve -40
+KPX Aacute Ohungarumlaut -40
+KPX Aacute Omacron -40
+KPX Aacute Oslash -40
+KPX Aacute Otilde -40
+KPX Aacute Q -40
+KPX Aacute T -37
+KPX Aacute Tcaron -37
+KPX Aacute Tcommaaccent -37
+KPX Aacute U -50
+KPX Aacute Uacute -50
+KPX Aacute Ucircumflex -50
+KPX Aacute Udieresis -50
+KPX Aacute Ugrave -50
+KPX Aacute Uhungarumlaut -50
+KPX Aacute Umacron -50
+KPX Aacute Uogonek -50
+KPX Aacute Uring -50
+KPX Aacute V -105
+KPX Aacute W -95
+KPX Aacute Y -55
+KPX Aacute Yacute -55
+KPX Aacute Ydieresis -55
+KPX Aacute quoteright -37
+KPX Aacute u -20
+KPX Aacute uacute -20
+KPX Aacute ucircumflex -20
+KPX Aacute udieresis -20
+KPX Aacute ugrave -20
+KPX Aacute uhungarumlaut -20
+KPX Aacute umacron -20
+KPX Aacute uogonek -20
+KPX Aacute uring -20
+KPX Aacute v -55
+KPX Aacute w -55
+KPX Aacute y -55
+KPX Aacute yacute -55
+KPX Aacute ydieresis -55
+KPX Abreve C -30
+KPX Abreve Cacute -30
+KPX Abreve Ccaron -30
+KPX Abreve Ccedilla -30
+KPX Abreve G -35
+KPX Abreve Gbreve -35
+KPX Abreve Gcommaaccent -35
+KPX Abreve O -40
+KPX Abreve Oacute -40
+KPX Abreve Ocircumflex -40
+KPX Abreve Odieresis -40
+KPX Abreve Ograve -40
+KPX Abreve Ohungarumlaut -40
+KPX Abreve Omacron -40
+KPX Abreve Oslash -40
+KPX Abreve Otilde -40
+KPX Abreve Q -40
+KPX Abreve T -37
+KPX Abreve Tcaron -37
+KPX Abreve Tcommaaccent -37
+KPX Abreve U -50
+KPX Abreve Uacute -50
+KPX Abreve Ucircumflex -50
+KPX Abreve Udieresis -50
+KPX Abreve Ugrave -50
+KPX Abreve Uhungarumlaut -50
+KPX Abreve Umacron -50
+KPX Abreve Uogonek -50
+KPX Abreve Uring -50
+KPX Abreve V -105
+KPX Abreve W -95
+KPX Abreve Y -55
+KPX Abreve Yacute -55
+KPX Abreve Ydieresis -55
+KPX Abreve quoteright -37
+KPX Abreve u -20
+KPX Abreve uacute -20
+KPX Abreve ucircumflex -20
+KPX Abreve udieresis -20
+KPX Abreve ugrave -20
+KPX Abreve uhungarumlaut -20
+KPX Abreve umacron -20
+KPX Abreve uogonek -20
+KPX Abreve uring -20
+KPX Abreve v -55
+KPX Abreve w -55
+KPX Abreve y -55
+KPX Abreve yacute -55
+KPX Abreve ydieresis -55
+KPX Acircumflex C -30
+KPX Acircumflex Cacute -30
+KPX Acircumflex Ccaron -30
+KPX Acircumflex Ccedilla -30
+KPX Acircumflex G -35
+KPX Acircumflex Gbreve -35
+KPX Acircumflex Gcommaaccent -35
+KPX Acircumflex O -40
+KPX Acircumflex Oacute -40
+KPX Acircumflex Ocircumflex -40
+KPX Acircumflex Odieresis -40
+KPX Acircumflex Ograve -40
+KPX Acircumflex Ohungarumlaut -40
+KPX Acircumflex Omacron -40
+KPX Acircumflex Oslash -40
+KPX Acircumflex Otilde -40
+KPX Acircumflex Q -40
+KPX Acircumflex T -37
+KPX Acircumflex Tcaron -37
+KPX Acircumflex Tcommaaccent -37
+KPX Acircumflex U -50
+KPX Acircumflex Uacute -50
+KPX Acircumflex Ucircumflex -50
+KPX Acircumflex Udieresis -50
+KPX Acircumflex Ugrave -50
+KPX Acircumflex Uhungarumlaut -50
+KPX Acircumflex Umacron -50
+KPX Acircumflex Uogonek -50
+KPX Acircumflex Uring -50
+KPX Acircumflex V -105
+KPX Acircumflex W -95
+KPX Acircumflex Y -55
+KPX Acircumflex Yacute -55
+KPX Acircumflex Ydieresis -55
+KPX Acircumflex quoteright -37
+KPX Acircumflex u -20
+KPX Acircumflex uacute -20
+KPX Acircumflex ucircumflex -20
+KPX Acircumflex udieresis -20
+KPX Acircumflex ugrave -20
+KPX Acircumflex uhungarumlaut -20
+KPX Acircumflex umacron -20
+KPX Acircumflex uogonek -20
+KPX Acircumflex uring -20
+KPX Acircumflex v -55
+KPX Acircumflex w -55
+KPX Acircumflex y -55
+KPX Acircumflex yacute -55
+KPX Acircumflex ydieresis -55
+KPX Adieresis C -30
+KPX Adieresis Cacute -30
+KPX Adieresis Ccaron -30
+KPX Adieresis Ccedilla -30
+KPX Adieresis G -35
+KPX Adieresis Gbreve -35
+KPX Adieresis Gcommaaccent -35
+KPX Adieresis O -40
+KPX Adieresis Oacute -40
+KPX Adieresis Ocircumflex -40
+KPX Adieresis Odieresis -40
+KPX Adieresis Ograve -40
+KPX Adieresis Ohungarumlaut -40
+KPX Adieresis Omacron -40
+KPX Adieresis Oslash -40
+KPX Adieresis Otilde -40
+KPX Adieresis Q -40
+KPX Adieresis T -37
+KPX Adieresis Tcaron -37
+KPX Adieresis Tcommaaccent -37
+KPX Adieresis U -50
+KPX Adieresis Uacute -50
+KPX Adieresis Ucircumflex -50
+KPX Adieresis Udieresis -50
+KPX Adieresis Ugrave -50
+KPX Adieresis Uhungarumlaut -50
+KPX Adieresis Umacron -50
+KPX Adieresis Uogonek -50
+KPX Adieresis Uring -50
+KPX Adieresis V -105
+KPX Adieresis W -95
+KPX Adieresis Y -55
+KPX Adieresis Yacute -55
+KPX Adieresis Ydieresis -55
+KPX Adieresis quoteright -37
+KPX Adieresis u -20
+KPX Adieresis uacute -20
+KPX Adieresis ucircumflex -20
+KPX Adieresis udieresis -20
+KPX Adieresis ugrave -20
+KPX Adieresis uhungarumlaut -20
+KPX Adieresis umacron -20
+KPX Adieresis uogonek -20
+KPX Adieresis uring -20
+KPX Adieresis v -55
+KPX Adieresis w -55
+KPX Adieresis y -55
+KPX Adieresis yacute -55
+KPX Adieresis ydieresis -55
+KPX Agrave C -30
+KPX Agrave Cacute -30
+KPX Agrave Ccaron -30
+KPX Agrave Ccedilla -30
+KPX Agrave G -35
+KPX Agrave Gbreve -35
+KPX Agrave Gcommaaccent -35
+KPX Agrave O -40
+KPX Agrave Oacute -40
+KPX Agrave Ocircumflex -40
+KPX Agrave Odieresis -40
+KPX Agrave Ograve -40
+KPX Agrave Ohungarumlaut -40
+KPX Agrave Omacron -40
+KPX Agrave Oslash -40
+KPX Agrave Otilde -40
+KPX Agrave Q -40
+KPX Agrave T -37
+KPX Agrave Tcaron -37
+KPX Agrave Tcommaaccent -37
+KPX Agrave U -50
+KPX Agrave Uacute -50
+KPX Agrave Ucircumflex -50
+KPX Agrave Udieresis -50
+KPX Agrave Ugrave -50
+KPX Agrave Uhungarumlaut -50
+KPX Agrave Umacron -50
+KPX Agrave Uogonek -50
+KPX Agrave Uring -50
+KPX Agrave V -105
+KPX Agrave W -95
+KPX Agrave Y -55
+KPX Agrave Yacute -55
+KPX Agrave Ydieresis -55
+KPX Agrave quoteright -37
+KPX Agrave u -20
+KPX Agrave uacute -20
+KPX Agrave ucircumflex -20
+KPX Agrave udieresis -20
+KPX Agrave ugrave -20
+KPX Agrave uhungarumlaut -20
+KPX Agrave umacron -20
+KPX Agrave uogonek -20
+KPX Agrave uring -20
+KPX Agrave v -55
+KPX Agrave w -55
+KPX Agrave y -55
+KPX Agrave yacute -55
+KPX Agrave ydieresis -55
+KPX Amacron C -30
+KPX Amacron Cacute -30
+KPX Amacron Ccaron -30
+KPX Amacron Ccedilla -30
+KPX Amacron G -35
+KPX Amacron Gbreve -35
+KPX Amacron Gcommaaccent -35
+KPX Amacron O -40
+KPX Amacron Oacute -40
+KPX Amacron Ocircumflex -40
+KPX Amacron Odieresis -40
+KPX Amacron Ograve -40
+KPX Amacron Ohungarumlaut -40
+KPX Amacron Omacron -40
+KPX Amacron Oslash -40
+KPX Amacron Otilde -40
+KPX Amacron Q -40
+KPX Amacron T -37
+KPX Amacron Tcaron -37
+KPX Amacron Tcommaaccent -37
+KPX Amacron U -50
+KPX Amacron Uacute -50
+KPX Amacron Ucircumflex -50
+KPX Amacron Udieresis -50
+KPX Amacron Ugrave -50
+KPX Amacron Uhungarumlaut -50
+KPX Amacron Umacron -50
+KPX Amacron Uogonek -50
+KPX Amacron Uring -50
+KPX Amacron V -105
+KPX Amacron W -95
+KPX Amacron Y -55
+KPX Amacron Yacute -55
+KPX Amacron Ydieresis -55
+KPX Amacron quoteright -37
+KPX Amacron u -20
+KPX Amacron uacute -20
+KPX Amacron ucircumflex -20
+KPX Amacron udieresis -20
+KPX Amacron ugrave -20
+KPX Amacron uhungarumlaut -20
+KPX Amacron umacron -20
+KPX Amacron uogonek -20
+KPX Amacron uring -20
+KPX Amacron v -55
+KPX Amacron w -55
+KPX Amacron y -55
+KPX Amacron yacute -55
+KPX Amacron ydieresis -55
+KPX Aogonek C -30
+KPX Aogonek Cacute -30
+KPX Aogonek Ccaron -30
+KPX Aogonek Ccedilla -30
+KPX Aogonek G -35
+KPX Aogonek Gbreve -35
+KPX Aogonek Gcommaaccent -35
+KPX Aogonek O -40
+KPX Aogonek Oacute -40
+KPX Aogonek Ocircumflex -40
+KPX Aogonek Odieresis -40
+KPX Aogonek Ograve -40
+KPX Aogonek Ohungarumlaut -40
+KPX Aogonek Omacron -40
+KPX Aogonek Oslash -40
+KPX Aogonek Otilde -40
+KPX Aogonek Q -40
+KPX Aogonek T -37
+KPX Aogonek Tcaron -37
+KPX Aogonek Tcommaaccent -37
+KPX Aogonek U -50
+KPX Aogonek Uacute -50
+KPX Aogonek Ucircumflex -50
+KPX Aogonek Udieresis -50
+KPX Aogonek Ugrave -50
+KPX Aogonek Uhungarumlaut -50
+KPX Aogonek Umacron -50
+KPX Aogonek Uogonek -50
+KPX Aogonek Uring -50
+KPX Aogonek V -105
+KPX Aogonek W -95
+KPX Aogonek Y -55
+KPX Aogonek Yacute -55
+KPX Aogonek Ydieresis -55
+KPX Aogonek quoteright -37
+KPX Aogonek u -20
+KPX Aogonek uacute -20
+KPX Aogonek ucircumflex -20
+KPX Aogonek udieresis -20
+KPX Aogonek ugrave -20
+KPX Aogonek uhungarumlaut -20
+KPX Aogonek umacron -20
+KPX Aogonek uogonek -20
+KPX Aogonek uring -20
+KPX Aogonek v -55
+KPX Aogonek w -55
+KPX Aogonek y -55
+KPX Aogonek yacute -55
+KPX Aogonek ydieresis -55
+KPX Aring C -30
+KPX Aring Cacute -30
+KPX Aring Ccaron -30
+KPX Aring Ccedilla -30
+KPX Aring G -35
+KPX Aring Gbreve -35
+KPX Aring Gcommaaccent -35
+KPX Aring O -40
+KPX Aring Oacute -40
+KPX Aring Ocircumflex -40
+KPX Aring Odieresis -40
+KPX Aring Ograve -40
+KPX Aring Ohungarumlaut -40
+KPX Aring Omacron -40
+KPX Aring Oslash -40
+KPX Aring Otilde -40
+KPX Aring Q -40
+KPX Aring T -37
+KPX Aring Tcaron -37
+KPX Aring Tcommaaccent -37
+KPX Aring U -50
+KPX Aring Uacute -50
+KPX Aring Ucircumflex -50
+KPX Aring Udieresis -50
+KPX Aring Ugrave -50
+KPX Aring Uhungarumlaut -50
+KPX Aring Umacron -50
+KPX Aring Uogonek -50
+KPX Aring Uring -50
+KPX Aring V -105
+KPX Aring W -95
+KPX Aring Y -55
+KPX Aring Yacute -55
+KPX Aring Ydieresis -55
+KPX Aring quoteright -37
+KPX Aring u -20
+KPX Aring uacute -20
+KPX Aring ucircumflex -20
+KPX Aring udieresis -20
+KPX Aring ugrave -20
+KPX Aring uhungarumlaut -20
+KPX Aring umacron -20
+KPX Aring uogonek -20
+KPX Aring uring -20
+KPX Aring v -55
+KPX Aring w -55
+KPX Aring y -55
+KPX Aring yacute -55
+KPX Aring ydieresis -55
+KPX Atilde C -30
+KPX Atilde Cacute -30
+KPX Atilde Ccaron -30
+KPX Atilde Ccedilla -30
+KPX Atilde G -35
+KPX Atilde Gbreve -35
+KPX Atilde Gcommaaccent -35
+KPX Atilde O -40
+KPX Atilde Oacute -40
+KPX Atilde Ocircumflex -40
+KPX Atilde Odieresis -40
+KPX Atilde Ograve -40
+KPX Atilde Ohungarumlaut -40
+KPX Atilde Omacron -40
+KPX Atilde Oslash -40
+KPX Atilde Otilde -40
+KPX Atilde Q -40
+KPX Atilde T -37
+KPX Atilde Tcaron -37
+KPX Atilde Tcommaaccent -37
+KPX Atilde U -50
+KPX Atilde Uacute -50
+KPX Atilde Ucircumflex -50
+KPX Atilde Udieresis -50
+KPX Atilde Ugrave -50
+KPX Atilde Uhungarumlaut -50
+KPX Atilde Umacron -50
+KPX Atilde Uogonek -50
+KPX Atilde Uring -50
+KPX Atilde V -105
+KPX Atilde W -95
+KPX Atilde Y -55
+KPX Atilde Yacute -55
+KPX Atilde Ydieresis -55
+KPX Atilde quoteright -37
+KPX Atilde u -20
+KPX Atilde uacute -20
+KPX Atilde ucircumflex -20
+KPX Atilde udieresis -20
+KPX Atilde ugrave -20
+KPX Atilde uhungarumlaut -20
+KPX Atilde umacron -20
+KPX Atilde uogonek -20
+KPX Atilde uring -20
+KPX Atilde v -55
+KPX Atilde w -55
+KPX Atilde y -55
+KPX Atilde yacute -55
+KPX Atilde ydieresis -55
+KPX B A -25
+KPX B Aacute -25
+KPX B Abreve -25
+KPX B Acircumflex -25
+KPX B Adieresis -25
+KPX B Agrave -25
+KPX B Amacron -25
+KPX B Aogonek -25
+KPX B Aring -25
+KPX B Atilde -25
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -35
+KPX D Aacute -35
+KPX D Abreve -35
+KPX D Acircumflex -35
+KPX D Adieresis -35
+KPX D Agrave -35
+KPX D Amacron -35
+KPX D Aogonek -35
+KPX D Aring -35
+KPX D Atilde -35
+KPX D V -40
+KPX D W -40
+KPX D Y -40
+KPX D Yacute -40
+KPX D Ydieresis -40
+KPX Dcaron A -35
+KPX Dcaron Aacute -35
+KPX Dcaron Abreve -35
+KPX Dcaron Acircumflex -35
+KPX Dcaron Adieresis -35
+KPX Dcaron Agrave -35
+KPX Dcaron Amacron -35
+KPX Dcaron Aogonek -35
+KPX Dcaron Aring -35
+KPX Dcaron Atilde -35
+KPX Dcaron V -40
+KPX Dcaron W -40
+KPX Dcaron Y -40
+KPX Dcaron Yacute -40
+KPX Dcaron Ydieresis -40
+KPX Dcroat A -35
+KPX Dcroat Aacute -35
+KPX Dcroat Abreve -35
+KPX Dcroat Acircumflex -35
+KPX Dcroat Adieresis -35
+KPX Dcroat Agrave -35
+KPX Dcroat Amacron -35
+KPX Dcroat Aogonek -35
+KPX Dcroat Aring -35
+KPX Dcroat Atilde -35
+KPX Dcroat V -40
+KPX Dcroat W -40
+KPX Dcroat Y -40
+KPX Dcroat Yacute -40
+KPX Dcroat Ydieresis -40
+KPX F A -115
+KPX F Aacute -115
+KPX F Abreve -115
+KPX F Acircumflex -115
+KPX F Adieresis -115
+KPX F Agrave -115
+KPX F Amacron -115
+KPX F Aogonek -115
+KPX F Aring -115
+KPX F Atilde -115
+KPX F a -75
+KPX F aacute -75
+KPX F abreve -75
+KPX F acircumflex -75
+KPX F adieresis -75
+KPX F agrave -75
+KPX F amacron -75
+KPX F aogonek -75
+KPX F aring -75
+KPX F atilde -75
+KPX F comma -135
+KPX F e -75
+KPX F eacute -75
+KPX F ecaron -75
+KPX F ecircumflex -75
+KPX F edieresis -75
+KPX F edotaccent -75
+KPX F egrave -75
+KPX F emacron -75
+KPX F eogonek -75
+KPX F i -45
+KPX F iacute -45
+KPX F icircumflex -45
+KPX F idieresis -45
+KPX F igrave -45
+KPX F imacron -45
+KPX F iogonek -45
+KPX F o -105
+KPX F oacute -105
+KPX F ocircumflex -105
+KPX F odieresis -105
+KPX F ograve -105
+KPX F ohungarumlaut -105
+KPX F omacron -105
+KPX F oslash -105
+KPX F otilde -105
+KPX F period -135
+KPX F r -55
+KPX F racute -55
+KPX F rcaron -55
+KPX F rcommaaccent -55
+KPX J A -40
+KPX J Aacute -40
+KPX J Abreve -40
+KPX J Acircumflex -40
+KPX J Adieresis -40
+KPX J Agrave -40
+KPX J Amacron -40
+KPX J Aogonek -40
+KPX J Aring -40
+KPX J Atilde -40
+KPX J a -35
+KPX J aacute -35
+KPX J abreve -35
+KPX J acircumflex -35
+KPX J adieresis -35
+KPX J agrave -35
+KPX J amacron -35
+KPX J aogonek -35
+KPX J aring -35
+KPX J atilde -35
+KPX J comma -25
+KPX J e -25
+KPX J eacute -25
+KPX J ecaron -25
+KPX J ecircumflex -25
+KPX J edieresis -25
+KPX J edotaccent -25
+KPX J egrave -25
+KPX J emacron -25
+KPX J eogonek -25
+KPX J o -25
+KPX J oacute -25
+KPX J ocircumflex -25
+KPX J odieresis -25
+KPX J ograve -25
+KPX J ohungarumlaut -25
+KPX J omacron -25
+KPX J oslash -25
+KPX J otilde -25
+KPX J period -25
+KPX J u -35
+KPX J uacute -35
+KPX J ucircumflex -35
+KPX J udieresis -35
+KPX J ugrave -35
+KPX J uhungarumlaut -35
+KPX J umacron -35
+KPX J uogonek -35
+KPX J uring -35
+KPX K O -50
+KPX K Oacute -50
+KPX K Ocircumflex -50
+KPX K Odieresis -50
+KPX K Ograve -50
+KPX K Ohungarumlaut -50
+KPX K Omacron -50
+KPX K Oslash -50
+KPX K Otilde -50
+KPX K e -35
+KPX K eacute -35
+KPX K ecaron -35
+KPX K ecircumflex -35
+KPX K edieresis -35
+KPX K edotaccent -35
+KPX K egrave -35
+KPX K emacron -35
+KPX K eogonek -35
+KPX K o -40
+KPX K oacute -40
+KPX K ocircumflex -40
+KPX K odieresis -40
+KPX K ograve -40
+KPX K ohungarumlaut -40
+KPX K omacron -40
+KPX K oslash -40
+KPX K otilde -40
+KPX K u -40
+KPX K uacute -40
+KPX K ucircumflex -40
+KPX K udieresis -40
+KPX K ugrave -40
+KPX K uhungarumlaut -40
+KPX K umacron -40
+KPX K uogonek -40
+KPX K uring -40
+KPX K y -40
+KPX K yacute -40
+KPX K ydieresis -40
+KPX Kcommaaccent O -50
+KPX Kcommaaccent Oacute -50
+KPX Kcommaaccent Ocircumflex -50
+KPX Kcommaaccent Odieresis -50
+KPX Kcommaaccent Ograve -50
+KPX Kcommaaccent Ohungarumlaut -50
+KPX Kcommaaccent Omacron -50
+KPX Kcommaaccent Oslash -50
+KPX Kcommaaccent Otilde -50
+KPX Kcommaaccent e -35
+KPX Kcommaaccent eacute -35
+KPX Kcommaaccent ecaron -35
+KPX Kcommaaccent ecircumflex -35
+KPX Kcommaaccent edieresis -35
+KPX Kcommaaccent edotaccent -35
+KPX Kcommaaccent egrave -35
+KPX Kcommaaccent emacron -35
+KPX Kcommaaccent eogonek -35
+KPX Kcommaaccent o -40
+KPX Kcommaaccent oacute -40
+KPX Kcommaaccent ocircumflex -40
+KPX Kcommaaccent odieresis -40
+KPX Kcommaaccent ograve -40
+KPX Kcommaaccent ohungarumlaut -40
+KPX Kcommaaccent omacron -40
+KPX Kcommaaccent oslash -40
+KPX Kcommaaccent otilde -40
+KPX Kcommaaccent u -40
+KPX Kcommaaccent uacute -40
+KPX Kcommaaccent ucircumflex -40
+KPX Kcommaaccent udieresis -40
+KPX Kcommaaccent ugrave -40
+KPX Kcommaaccent uhungarumlaut -40
+KPX Kcommaaccent umacron -40
+KPX Kcommaaccent uogonek -40
+KPX Kcommaaccent uring -40
+KPX Kcommaaccent y -40
+KPX Kcommaaccent yacute -40
+KPX Kcommaaccent ydieresis -40
+KPX L T -20
+KPX L Tcaron -20
+KPX L Tcommaaccent -20
+KPX L V -55
+KPX L W -55
+KPX L Y -20
+KPX L Yacute -20
+KPX L Ydieresis -20
+KPX L quoteright -37
+KPX L y -30
+KPX L yacute -30
+KPX L ydieresis -30
+KPX Lacute T -20
+KPX Lacute Tcaron -20
+KPX Lacute Tcommaaccent -20
+KPX Lacute V -55
+KPX Lacute W -55
+KPX Lacute Y -20
+KPX Lacute Yacute -20
+KPX Lacute Ydieresis -20
+KPX Lacute quoteright -37
+KPX Lacute y -30
+KPX Lacute yacute -30
+KPX Lacute ydieresis -30
+KPX Lcommaaccent T -20
+KPX Lcommaaccent Tcaron -20
+KPX Lcommaaccent Tcommaaccent -20
+KPX Lcommaaccent V -55
+KPX Lcommaaccent W -55
+KPX Lcommaaccent Y -20
+KPX Lcommaaccent Yacute -20
+KPX Lcommaaccent Ydieresis -20
+KPX Lcommaaccent quoteright -37
+KPX Lcommaaccent y -30
+KPX Lcommaaccent yacute -30
+KPX Lcommaaccent ydieresis -30
+KPX Lslash T -20
+KPX Lslash Tcaron -20
+KPX Lslash Tcommaaccent -20
+KPX Lslash V -55
+KPX Lslash W -55
+KPX Lslash Y -20
+KPX Lslash Yacute -20
+KPX Lslash Ydieresis -20
+KPX Lslash quoteright -37
+KPX Lslash y -30
+KPX Lslash yacute -30
+KPX Lslash ydieresis -30
+KPX N A -27
+KPX N Aacute -27
+KPX N Abreve -27
+KPX N Acircumflex -27
+KPX N Adieresis -27
+KPX N Agrave -27
+KPX N Amacron -27
+KPX N Aogonek -27
+KPX N Aring -27
+KPX N Atilde -27
+KPX Nacute A -27
+KPX Nacute Aacute -27
+KPX Nacute Abreve -27
+KPX Nacute Acircumflex -27
+KPX Nacute Adieresis -27
+KPX Nacute Agrave -27
+KPX Nacute Amacron -27
+KPX Nacute Aogonek -27
+KPX Nacute Aring -27
+KPX Nacute Atilde -27
+KPX Ncaron A -27
+KPX Ncaron Aacute -27
+KPX Ncaron Abreve -27
+KPX Ncaron Acircumflex -27
+KPX Ncaron Adieresis -27
+KPX Ncaron Agrave -27
+KPX Ncaron Amacron -27
+KPX Ncaron Aogonek -27
+KPX Ncaron Aring -27
+KPX Ncaron Atilde -27
+KPX Ncommaaccent A -27
+KPX Ncommaaccent Aacute -27
+KPX Ncommaaccent Abreve -27
+KPX Ncommaaccent Acircumflex -27
+KPX Ncommaaccent Adieresis -27
+KPX Ncommaaccent Agrave -27
+KPX Ncommaaccent Amacron -27
+KPX Ncommaaccent Aogonek -27
+KPX Ncommaaccent Aring -27
+KPX Ncommaaccent Atilde -27
+KPX Ntilde A -27
+KPX Ntilde Aacute -27
+KPX Ntilde Abreve -27
+KPX Ntilde Acircumflex -27
+KPX Ntilde Adieresis -27
+KPX Ntilde Agrave -27
+KPX Ntilde Amacron -27
+KPX Ntilde Aogonek -27
+KPX Ntilde Aring -27
+KPX Ntilde Atilde -27
+KPX O A -55
+KPX O Aacute -55
+KPX O Abreve -55
+KPX O Acircumflex -55
+KPX O Adieresis -55
+KPX O Agrave -55
+KPX O Amacron -55
+KPX O Aogonek -55
+KPX O Aring -55
+KPX O Atilde -55
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -50
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -55
+KPX Oacute Aacute -55
+KPX Oacute Abreve -55
+KPX Oacute Acircumflex -55
+KPX Oacute Adieresis -55
+KPX Oacute Agrave -55
+KPX Oacute Amacron -55
+KPX Oacute Aogonek -55
+KPX Oacute Aring -55
+KPX Oacute Atilde -55
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -50
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -55
+KPX Ocircumflex Aacute -55
+KPX Ocircumflex Abreve -55
+KPX Ocircumflex Acircumflex -55
+KPX Ocircumflex Adieresis -55
+KPX Ocircumflex Agrave -55
+KPX Ocircumflex Amacron -55
+KPX Ocircumflex Aogonek -55
+KPX Ocircumflex Aring -55
+KPX Ocircumflex Atilde -55
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -50
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -55
+KPX Odieresis Aacute -55
+KPX Odieresis Abreve -55
+KPX Odieresis Acircumflex -55
+KPX Odieresis Adieresis -55
+KPX Odieresis Agrave -55
+KPX Odieresis Amacron -55
+KPX Odieresis Aogonek -55
+KPX Odieresis Aring -55
+KPX Odieresis Atilde -55
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -50
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -55
+KPX Ograve Aacute -55
+KPX Ograve Abreve -55
+KPX Ograve Acircumflex -55
+KPX Ograve Adieresis -55
+KPX Ograve Agrave -55
+KPX Ograve Amacron -55
+KPX Ograve Aogonek -55
+KPX Ograve Aring -55
+KPX Ograve Atilde -55
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -50
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -55
+KPX Ohungarumlaut Aacute -55
+KPX Ohungarumlaut Abreve -55
+KPX Ohungarumlaut Acircumflex -55
+KPX Ohungarumlaut Adieresis -55
+KPX Ohungarumlaut Agrave -55
+KPX Ohungarumlaut Amacron -55
+KPX Ohungarumlaut Aogonek -55
+KPX Ohungarumlaut Aring -55
+KPX Ohungarumlaut Atilde -55
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -50
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -55
+KPX Omacron Aacute -55
+KPX Omacron Abreve -55
+KPX Omacron Acircumflex -55
+KPX Omacron Adieresis -55
+KPX Omacron Agrave -55
+KPX Omacron Amacron -55
+KPX Omacron Aogonek -55
+KPX Omacron Aring -55
+KPX Omacron Atilde -55
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -50
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -55
+KPX Oslash Aacute -55
+KPX Oslash Abreve -55
+KPX Oslash Acircumflex -55
+KPX Oslash Adieresis -55
+KPX Oslash Agrave -55
+KPX Oslash Amacron -55
+KPX Oslash Aogonek -55
+KPX Oslash Aring -55
+KPX Oslash Atilde -55
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -50
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -55
+KPX Otilde Aacute -55
+KPX Otilde Abreve -55
+KPX Otilde Acircumflex -55
+KPX Otilde Adieresis -55
+KPX Otilde Agrave -55
+KPX Otilde Amacron -55
+KPX Otilde Aogonek -55
+KPX Otilde Aring -55
+KPX Otilde Atilde -55
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -50
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -90
+KPX P Aacute -90
+KPX P Abreve -90
+KPX P Acircumflex -90
+KPX P Adieresis -90
+KPX P Agrave -90
+KPX P Amacron -90
+KPX P Aogonek -90
+KPX P Aring -90
+KPX P Atilde -90
+KPX P a -80
+KPX P aacute -80
+KPX P abreve -80
+KPX P acircumflex -80
+KPX P adieresis -80
+KPX P agrave -80
+KPX P amacron -80
+KPX P aogonek -80
+KPX P aring -80
+KPX P atilde -80
+KPX P comma -135
+KPX P e -80
+KPX P eacute -80
+KPX P ecaron -80
+KPX P ecircumflex -80
+KPX P edieresis -80
+KPX P edotaccent -80
+KPX P egrave -80
+KPX P emacron -80
+KPX P eogonek -80
+KPX P o -80
+KPX P oacute -80
+KPX P ocircumflex -80
+KPX P odieresis -80
+KPX P ograve -80
+KPX P ohungarumlaut -80
+KPX P omacron -80
+KPX P oslash -80
+KPX P otilde -80
+KPX P period -135
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -40
+KPX R Oacute -40
+KPX R Ocircumflex -40
+KPX R Odieresis -40
+KPX R Ograve -40
+KPX R Ohungarumlaut -40
+KPX R Omacron -40
+KPX R Oslash -40
+KPX R Otilde -40
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -18
+KPX R W -18
+KPX R Y -18
+KPX R Yacute -18
+KPX R Ydieresis -18
+KPX Racute O -40
+KPX Racute Oacute -40
+KPX Racute Ocircumflex -40
+KPX Racute Odieresis -40
+KPX Racute Ograve -40
+KPX Racute Ohungarumlaut -40
+KPX Racute Omacron -40
+KPX Racute Oslash -40
+KPX Racute Otilde -40
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -18
+KPX Racute W -18
+KPX Racute Y -18
+KPX Racute Yacute -18
+KPX Racute Ydieresis -18
+KPX Rcaron O -40
+KPX Rcaron Oacute -40
+KPX Rcaron Ocircumflex -40
+KPX Rcaron Odieresis -40
+KPX Rcaron Ograve -40
+KPX Rcaron Ohungarumlaut -40
+KPX Rcaron Omacron -40
+KPX Rcaron Oslash -40
+KPX Rcaron Otilde -40
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -18
+KPX Rcaron W -18
+KPX Rcaron Y -18
+KPX Rcaron Yacute -18
+KPX Rcaron Ydieresis -18
+KPX Rcommaaccent O -40
+KPX Rcommaaccent Oacute -40
+KPX Rcommaaccent Ocircumflex -40
+KPX Rcommaaccent Odieresis -40
+KPX Rcommaaccent Ograve -40
+KPX Rcommaaccent Ohungarumlaut -40
+KPX Rcommaaccent Omacron -40
+KPX Rcommaaccent Oslash -40
+KPX Rcommaaccent Otilde -40
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -18
+KPX Rcommaaccent W -18
+KPX Rcommaaccent Y -18
+KPX Rcommaaccent Yacute -18
+KPX Rcommaaccent Ydieresis -18
+KPX T A -50
+KPX T Aacute -50
+KPX T Abreve -50
+KPX T Acircumflex -50
+KPX T Adieresis -50
+KPX T Agrave -50
+KPX T Amacron -50
+KPX T Aogonek -50
+KPX T Aring -50
+KPX T Atilde -50
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -92
+KPX T aacute -92
+KPX T abreve -92
+KPX T acircumflex -92
+KPX T adieresis -92
+KPX T agrave -92
+KPX T amacron -92
+KPX T aogonek -92
+KPX T aring -92
+KPX T atilde -92
+KPX T colon -55
+KPX T comma -74
+KPX T e -92
+KPX T eacute -92
+KPX T ecaron -92
+KPX T ecircumflex -52
+KPX T edieresis -52
+KPX T edotaccent -92
+KPX T egrave -52
+KPX T emacron -52
+KPX T eogonek -92
+KPX T hyphen -74
+KPX T i -55
+KPX T iacute -55
+KPX T iogonek -55
+KPX T o -92
+KPX T oacute -92
+KPX T ocircumflex -92
+KPX T odieresis -92
+KPX T ograve -92
+KPX T ohungarumlaut -92
+KPX T omacron -92
+KPX T oslash -92
+KPX T otilde -92
+KPX T period -74
+KPX T r -55
+KPX T racute -55
+KPX T rcaron -55
+KPX T rcommaaccent -55
+KPX T semicolon -65
+KPX T u -55
+KPX T uacute -55
+KPX T ucircumflex -55
+KPX T udieresis -55
+KPX T ugrave -55
+KPX T uhungarumlaut -55
+KPX T umacron -55
+KPX T uogonek -55
+KPX T uring -55
+KPX T w -74
+KPX T y -74
+KPX T yacute -74
+KPX T ydieresis -34
+KPX Tcaron A -50
+KPX Tcaron Aacute -50
+KPX Tcaron Abreve -50
+KPX Tcaron Acircumflex -50
+KPX Tcaron Adieresis -50
+KPX Tcaron Agrave -50
+KPX Tcaron Amacron -50
+KPX Tcaron Aogonek -50
+KPX Tcaron Aring -50
+KPX Tcaron Atilde -50
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -92
+KPX Tcaron aacute -92
+KPX Tcaron abreve -92
+KPX Tcaron acircumflex -92
+KPX Tcaron adieresis -92
+KPX Tcaron agrave -92
+KPX Tcaron amacron -92
+KPX Tcaron aogonek -92
+KPX Tcaron aring -92
+KPX Tcaron atilde -92
+KPX Tcaron colon -55
+KPX Tcaron comma -74
+KPX Tcaron e -92
+KPX Tcaron eacute -92
+KPX Tcaron ecaron -92
+KPX Tcaron ecircumflex -52
+KPX Tcaron edieresis -52
+KPX Tcaron edotaccent -92
+KPX Tcaron egrave -52
+KPX Tcaron emacron -52
+KPX Tcaron eogonek -92
+KPX Tcaron hyphen -74
+KPX Tcaron i -55
+KPX Tcaron iacute -55
+KPX Tcaron iogonek -55
+KPX Tcaron o -92
+KPX Tcaron oacute -92
+KPX Tcaron ocircumflex -92
+KPX Tcaron odieresis -92
+KPX Tcaron ograve -92
+KPX Tcaron ohungarumlaut -92
+KPX Tcaron omacron -92
+KPX Tcaron oslash -92
+KPX Tcaron otilde -92
+KPX Tcaron period -74
+KPX Tcaron r -55
+KPX Tcaron racute -55
+KPX Tcaron rcaron -55
+KPX Tcaron rcommaaccent -55
+KPX Tcaron semicolon -65
+KPX Tcaron u -55
+KPX Tcaron uacute -55
+KPX Tcaron ucircumflex -55
+KPX Tcaron udieresis -55
+KPX Tcaron ugrave -55
+KPX Tcaron uhungarumlaut -55
+KPX Tcaron umacron -55
+KPX Tcaron uogonek -55
+KPX Tcaron uring -55
+KPX Tcaron w -74
+KPX Tcaron y -74
+KPX Tcaron yacute -74
+KPX Tcaron ydieresis -34
+KPX Tcommaaccent A -50
+KPX Tcommaaccent Aacute -50
+KPX Tcommaaccent Abreve -50
+KPX Tcommaaccent Acircumflex -50
+KPX Tcommaaccent Adieresis -50
+KPX Tcommaaccent Agrave -50
+KPX Tcommaaccent Amacron -50
+KPX Tcommaaccent Aogonek -50
+KPX Tcommaaccent Aring -50
+KPX Tcommaaccent Atilde -50
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -92
+KPX Tcommaaccent aacute -92
+KPX Tcommaaccent abreve -92
+KPX Tcommaaccent acircumflex -92
+KPX Tcommaaccent adieresis -92
+KPX Tcommaaccent agrave -92
+KPX Tcommaaccent amacron -92
+KPX Tcommaaccent aogonek -92
+KPX Tcommaaccent aring -92
+KPX Tcommaaccent atilde -92
+KPX Tcommaaccent colon -55
+KPX Tcommaaccent comma -74
+KPX Tcommaaccent e -92
+KPX Tcommaaccent eacute -92
+KPX Tcommaaccent ecaron -92
+KPX Tcommaaccent ecircumflex -52
+KPX Tcommaaccent edieresis -52
+KPX Tcommaaccent edotaccent -92
+KPX Tcommaaccent egrave -52
+KPX Tcommaaccent emacron -52
+KPX Tcommaaccent eogonek -92
+KPX Tcommaaccent hyphen -74
+KPX Tcommaaccent i -55
+KPX Tcommaaccent iacute -55
+KPX Tcommaaccent iogonek -55
+KPX Tcommaaccent o -92
+KPX Tcommaaccent oacute -92
+KPX Tcommaaccent ocircumflex -92
+KPX Tcommaaccent odieresis -92
+KPX Tcommaaccent ograve -92
+KPX Tcommaaccent ohungarumlaut -92
+KPX Tcommaaccent omacron -92
+KPX Tcommaaccent oslash -92
+KPX Tcommaaccent otilde -92
+KPX Tcommaaccent period -74
+KPX Tcommaaccent r -55
+KPX Tcommaaccent racute -55
+KPX Tcommaaccent rcaron -55
+KPX Tcommaaccent rcommaaccent -55
+KPX Tcommaaccent semicolon -65
+KPX Tcommaaccent u -55
+KPX Tcommaaccent uacute -55
+KPX Tcommaaccent ucircumflex -55
+KPX Tcommaaccent udieresis -55
+KPX Tcommaaccent ugrave -55
+KPX Tcommaaccent uhungarumlaut -55
+KPX Tcommaaccent umacron -55
+KPX Tcommaaccent uogonek -55
+KPX Tcommaaccent uring -55
+KPX Tcommaaccent w -74
+KPX Tcommaaccent y -74
+KPX Tcommaaccent yacute -74
+KPX Tcommaaccent ydieresis -34
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX U comma -25
+KPX U period -25
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Uacute comma -25
+KPX Uacute period -25
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Ucircumflex comma -25
+KPX Ucircumflex period -25
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Udieresis comma -25
+KPX Udieresis period -25
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Ugrave comma -25
+KPX Ugrave period -25
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Uhungarumlaut comma -25
+KPX Uhungarumlaut period -25
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Umacron comma -25
+KPX Umacron period -25
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uogonek comma -25
+KPX Uogonek period -25
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX Uring comma -25
+KPX Uring period -25
+KPX V A -60
+KPX V Aacute -60
+KPX V Abreve -60
+KPX V Acircumflex -60
+KPX V Adieresis -60
+KPX V Agrave -60
+KPX V Amacron -60
+KPX V Aogonek -60
+KPX V Aring -60
+KPX V Atilde -60
+KPX V O -30
+KPX V Oacute -30
+KPX V Ocircumflex -30
+KPX V Odieresis -30
+KPX V Ograve -30
+KPX V Ohungarumlaut -30
+KPX V Omacron -30
+KPX V Oslash -30
+KPX V Otilde -30
+KPX V a -111
+KPX V aacute -111
+KPX V abreve -111
+KPX V acircumflex -111
+KPX V adieresis -111
+KPX V agrave -111
+KPX V amacron -111
+KPX V aogonek -111
+KPX V aring -111
+KPX V atilde -111
+KPX V colon -65
+KPX V comma -129
+KPX V e -111
+KPX V eacute -111
+KPX V ecaron -111
+KPX V ecircumflex -111
+KPX V edieresis -71
+KPX V edotaccent -111
+KPX V egrave -71
+KPX V emacron -71
+KPX V eogonek -111
+KPX V hyphen -55
+KPX V i -74
+KPX V iacute -74
+KPX V icircumflex -34
+KPX V idieresis -34
+KPX V igrave -34
+KPX V imacron -34
+KPX V iogonek -74
+KPX V o -111
+KPX V oacute -111
+KPX V ocircumflex -111
+KPX V odieresis -111
+KPX V ograve -111
+KPX V ohungarumlaut -111
+KPX V omacron -111
+KPX V oslash -111
+KPX V otilde -111
+KPX V period -129
+KPX V semicolon -74
+KPX V u -74
+KPX V uacute -74
+KPX V ucircumflex -74
+KPX V udieresis -74
+KPX V ugrave -74
+KPX V uhungarumlaut -74
+KPX V umacron -74
+KPX V uogonek -74
+KPX V uring -74
+KPX W A -60
+KPX W Aacute -60
+KPX W Abreve -60
+KPX W Acircumflex -60
+KPX W Adieresis -60
+KPX W Agrave -60
+KPX W Amacron -60
+KPX W Aogonek -60
+KPX W Aring -60
+KPX W Atilde -60
+KPX W O -25
+KPX W Oacute -25
+KPX W Ocircumflex -25
+KPX W Odieresis -25
+KPX W Ograve -25
+KPX W Ohungarumlaut -25
+KPX W Omacron -25
+KPX W Oslash -25
+KPX W Otilde -25
+KPX W a -92
+KPX W aacute -92
+KPX W abreve -92
+KPX W acircumflex -92
+KPX W adieresis -92
+KPX W agrave -92
+KPX W amacron -92
+KPX W aogonek -92
+KPX W aring -92
+KPX W atilde -92
+KPX W colon -65
+KPX W comma -92
+KPX W e -92
+KPX W eacute -92
+KPX W ecaron -92
+KPX W ecircumflex -92
+KPX W edieresis -52
+KPX W edotaccent -92
+KPX W egrave -52
+KPX W emacron -52
+KPX W eogonek -92
+KPX W hyphen -37
+KPX W i -55
+KPX W iacute -55
+KPX W iogonek -55
+KPX W o -92
+KPX W oacute -92
+KPX W ocircumflex -92
+KPX W odieresis -92
+KPX W ograve -92
+KPX W ohungarumlaut -92
+KPX W omacron -92
+KPX W oslash -92
+KPX W otilde -92
+KPX W period -92
+KPX W semicolon -65
+KPX W u -55
+KPX W uacute -55
+KPX W ucircumflex -55
+KPX W udieresis -55
+KPX W ugrave -55
+KPX W uhungarumlaut -55
+KPX W umacron -55
+KPX W uogonek -55
+KPX W uring -55
+KPX W y -70
+KPX W yacute -70
+KPX W ydieresis -70
+KPX Y A -50
+KPX Y Aacute -50
+KPX Y Abreve -50
+KPX Y Acircumflex -50
+KPX Y Adieresis -50
+KPX Y Agrave -50
+KPX Y Amacron -50
+KPX Y Aogonek -50
+KPX Y Aring -50
+KPX Y Atilde -50
+KPX Y O -15
+KPX Y Oacute -15
+KPX Y Ocircumflex -15
+KPX Y Odieresis -15
+KPX Y Ograve -15
+KPX Y Ohungarumlaut -15
+KPX Y Omacron -15
+KPX Y Oslash -15
+KPX Y Otilde -15
+KPX Y a -92
+KPX Y aacute -92
+KPX Y abreve -92
+KPX Y acircumflex -92
+KPX Y adieresis -92
+KPX Y agrave -92
+KPX Y amacron -92
+KPX Y aogonek -92
+KPX Y aring -92
+KPX Y atilde -92
+KPX Y colon -65
+KPX Y comma -92
+KPX Y e -92
+KPX Y eacute -92
+KPX Y ecaron -92
+KPX Y ecircumflex -92
+KPX Y edieresis -52
+KPX Y edotaccent -92
+KPX Y egrave -52
+KPX Y emacron -52
+KPX Y eogonek -92
+KPX Y hyphen -74
+KPX Y i -74
+KPX Y iacute -74
+KPX Y icircumflex -34
+KPX Y idieresis -34
+KPX Y igrave -34
+KPX Y imacron -34
+KPX Y iogonek -74
+KPX Y o -92
+KPX Y oacute -92
+KPX Y ocircumflex -92
+KPX Y odieresis -92
+KPX Y ograve -92
+KPX Y ohungarumlaut -92
+KPX Y omacron -92
+KPX Y oslash -92
+KPX Y otilde -92
+KPX Y period -92
+KPX Y semicolon -65
+KPX Y u -92
+KPX Y uacute -92
+KPX Y ucircumflex -92
+KPX Y udieresis -92
+KPX Y ugrave -92
+KPX Y uhungarumlaut -92
+KPX Y umacron -92
+KPX Y uogonek -92
+KPX Y uring -92
+KPX Yacute A -50
+KPX Yacute Aacute -50
+KPX Yacute Abreve -50
+KPX Yacute Acircumflex -50
+KPX Yacute Adieresis -50
+KPX Yacute Agrave -50
+KPX Yacute Amacron -50
+KPX Yacute Aogonek -50
+KPX Yacute Aring -50
+KPX Yacute Atilde -50
+KPX Yacute O -15
+KPX Yacute Oacute -15
+KPX Yacute Ocircumflex -15
+KPX Yacute Odieresis -15
+KPX Yacute Ograve -15
+KPX Yacute Ohungarumlaut -15
+KPX Yacute Omacron -15
+KPX Yacute Oslash -15
+KPX Yacute Otilde -15
+KPX Yacute a -92
+KPX Yacute aacute -92
+KPX Yacute abreve -92
+KPX Yacute acircumflex -92
+KPX Yacute adieresis -92
+KPX Yacute agrave -92
+KPX Yacute amacron -92
+KPX Yacute aogonek -92
+KPX Yacute aring -92
+KPX Yacute atilde -92
+KPX Yacute colon -65
+KPX Yacute comma -92
+KPX Yacute e -92
+KPX Yacute eacute -92
+KPX Yacute ecaron -92
+KPX Yacute ecircumflex -92
+KPX Yacute edieresis -52
+KPX Yacute edotaccent -92
+KPX Yacute egrave -52
+KPX Yacute emacron -52
+KPX Yacute eogonek -92
+KPX Yacute hyphen -74
+KPX Yacute i -74
+KPX Yacute iacute -74
+KPX Yacute icircumflex -34
+KPX Yacute idieresis -34
+KPX Yacute igrave -34
+KPX Yacute imacron -34
+KPX Yacute iogonek -74
+KPX Yacute o -92
+KPX Yacute oacute -92
+KPX Yacute ocircumflex -92
+KPX Yacute odieresis -92
+KPX Yacute ograve -92
+KPX Yacute ohungarumlaut -92
+KPX Yacute omacron -92
+KPX Yacute oslash -92
+KPX Yacute otilde -92
+KPX Yacute period -92
+KPX Yacute semicolon -65
+KPX Yacute u -92
+KPX Yacute uacute -92
+KPX Yacute ucircumflex -92
+KPX Yacute udieresis -92
+KPX Yacute ugrave -92
+KPX Yacute uhungarumlaut -92
+KPX Yacute umacron -92
+KPX Yacute uogonek -92
+KPX Yacute uring -92
+KPX Ydieresis A -50
+KPX Ydieresis Aacute -50
+KPX Ydieresis Abreve -50
+KPX Ydieresis Acircumflex -50
+KPX Ydieresis Adieresis -50
+KPX Ydieresis Agrave -50
+KPX Ydieresis Amacron -50
+KPX Ydieresis Aogonek -50
+KPX Ydieresis Aring -50
+KPX Ydieresis Atilde -50
+KPX Ydieresis O -15
+KPX Ydieresis Oacute -15
+KPX Ydieresis Ocircumflex -15
+KPX Ydieresis Odieresis -15
+KPX Ydieresis Ograve -15
+KPX Ydieresis Ohungarumlaut -15
+KPX Ydieresis Omacron -15
+KPX Ydieresis Oslash -15
+KPX Ydieresis Otilde -15
+KPX Ydieresis a -92
+KPX Ydieresis aacute -92
+KPX Ydieresis abreve -92
+KPX Ydieresis acircumflex -92
+KPX Ydieresis adieresis -92
+KPX Ydieresis agrave -92
+KPX Ydieresis amacron -92
+KPX Ydieresis aogonek -92
+KPX Ydieresis aring -92
+KPX Ydieresis atilde -92
+KPX Ydieresis colon -65
+KPX Ydieresis comma -92
+KPX Ydieresis e -92
+KPX Ydieresis eacute -92
+KPX Ydieresis ecaron -92
+KPX Ydieresis ecircumflex -92
+KPX Ydieresis edieresis -52
+KPX Ydieresis edotaccent -92
+KPX Ydieresis egrave -52
+KPX Ydieresis emacron -52
+KPX Ydieresis eogonek -92
+KPX Ydieresis hyphen -74
+KPX Ydieresis i -74
+KPX Ydieresis iacute -74
+KPX Ydieresis icircumflex -34
+KPX Ydieresis idieresis -34
+KPX Ydieresis igrave -34
+KPX Ydieresis imacron -34
+KPX Ydieresis iogonek -74
+KPX Ydieresis o -92
+KPX Ydieresis oacute -92
+KPX Ydieresis ocircumflex -92
+KPX Ydieresis odieresis -92
+KPX Ydieresis ograve -92
+KPX Ydieresis ohungarumlaut -92
+KPX Ydieresis omacron -92
+KPX Ydieresis oslash -92
+KPX Ydieresis otilde -92
+KPX Ydieresis period -92
+KPX Ydieresis semicolon -65
+KPX Ydieresis u -92
+KPX Ydieresis uacute -92
+KPX Ydieresis ucircumflex -92
+KPX Ydieresis udieresis -92
+KPX Ydieresis ugrave -92
+KPX Ydieresis uhungarumlaut -92
+KPX Ydieresis umacron -92
+KPX Ydieresis uogonek -92
+KPX Ydieresis uring -92
+KPX a g -10
+KPX a gbreve -10
+KPX a gcommaaccent -10
+KPX aacute g -10
+KPX aacute gbreve -10
+KPX aacute gcommaaccent -10
+KPX abreve g -10
+KPX abreve gbreve -10
+KPX abreve gcommaaccent -10
+KPX acircumflex g -10
+KPX acircumflex gbreve -10
+KPX acircumflex gcommaaccent -10
+KPX adieresis g -10
+KPX adieresis gbreve -10
+KPX adieresis gcommaaccent -10
+KPX agrave g -10
+KPX agrave gbreve -10
+KPX agrave gcommaaccent -10
+KPX amacron g -10
+KPX amacron gbreve -10
+KPX amacron gcommaaccent -10
+KPX aogonek g -10
+KPX aogonek gbreve -10
+KPX aogonek gcommaaccent -10
+KPX aring g -10
+KPX aring gbreve -10
+KPX aring gcommaaccent -10
+KPX atilde g -10
+KPX atilde gbreve -10
+KPX atilde gcommaaccent -10
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX c h -15
+KPX c k -20
+KPX c kcommaaccent -20
+KPX cacute h -15
+KPX cacute k -20
+KPX cacute kcommaaccent -20
+KPX ccaron h -15
+KPX ccaron k -20
+KPX ccaron kcommaaccent -20
+KPX ccedilla h -15
+KPX ccedilla k -20
+KPX ccedilla kcommaaccent -20
+KPX comma quotedblright -140
+KPX comma quoteright -140
+KPX e comma -10
+KPX e g -40
+KPX e gbreve -40
+KPX e gcommaaccent -40
+KPX e period -15
+KPX e v -15
+KPX e w -15
+KPX e x -20
+KPX e y -30
+KPX e yacute -30
+KPX e ydieresis -30
+KPX eacute comma -10
+KPX eacute g -40
+KPX eacute gbreve -40
+KPX eacute gcommaaccent -40
+KPX eacute period -15
+KPX eacute v -15
+KPX eacute w -15
+KPX eacute x -20
+KPX eacute y -30
+KPX eacute yacute -30
+KPX eacute ydieresis -30
+KPX ecaron comma -10
+KPX ecaron g -40
+KPX ecaron gbreve -40
+KPX ecaron gcommaaccent -40
+KPX ecaron period -15
+KPX ecaron v -15
+KPX ecaron w -15
+KPX ecaron x -20
+KPX ecaron y -30
+KPX ecaron yacute -30
+KPX ecaron ydieresis -30
+KPX ecircumflex comma -10
+KPX ecircumflex g -40
+KPX ecircumflex gbreve -40
+KPX ecircumflex gcommaaccent -40
+KPX ecircumflex period -15
+KPX ecircumflex v -15
+KPX ecircumflex w -15
+KPX ecircumflex x -20
+KPX ecircumflex y -30
+KPX ecircumflex yacute -30
+KPX ecircumflex ydieresis -30
+KPX edieresis comma -10
+KPX edieresis g -40
+KPX edieresis gbreve -40
+KPX edieresis gcommaaccent -40
+KPX edieresis period -15
+KPX edieresis v -15
+KPX edieresis w -15
+KPX edieresis x -20
+KPX edieresis y -30
+KPX edieresis yacute -30
+KPX edieresis ydieresis -30
+KPX edotaccent comma -10
+KPX edotaccent g -40
+KPX edotaccent gbreve -40
+KPX edotaccent gcommaaccent -40
+KPX edotaccent period -15
+KPX edotaccent v -15
+KPX edotaccent w -15
+KPX edotaccent x -20
+KPX edotaccent y -30
+KPX edotaccent yacute -30
+KPX edotaccent ydieresis -30
+KPX egrave comma -10
+KPX egrave g -40
+KPX egrave gbreve -40
+KPX egrave gcommaaccent -40
+KPX egrave period -15
+KPX egrave v -15
+KPX egrave w -15
+KPX egrave x -20
+KPX egrave y -30
+KPX egrave yacute -30
+KPX egrave ydieresis -30
+KPX emacron comma -10
+KPX emacron g -40
+KPX emacron gbreve -40
+KPX emacron gcommaaccent -40
+KPX emacron period -15
+KPX emacron v -15
+KPX emacron w -15
+KPX emacron x -20
+KPX emacron y -30
+KPX emacron yacute -30
+KPX emacron ydieresis -30
+KPX eogonek comma -10
+KPX eogonek g -40
+KPX eogonek gbreve -40
+KPX eogonek gcommaaccent -40
+KPX eogonek period -15
+KPX eogonek v -15
+KPX eogonek w -15
+KPX eogonek x -20
+KPX eogonek y -30
+KPX eogonek yacute -30
+KPX eogonek ydieresis -30
+KPX f comma -10
+KPX f dotlessi -60
+KPX f f -18
+KPX f i -20
+KPX f iogonek -20
+KPX f period -15
+KPX f quoteright 92
+KPX g comma -10
+KPX g e -10
+KPX g eacute -10
+KPX g ecaron -10
+KPX g ecircumflex -10
+KPX g edieresis -10
+KPX g edotaccent -10
+KPX g egrave -10
+KPX g emacron -10
+KPX g eogonek -10
+KPX g g -10
+KPX g gbreve -10
+KPX g gcommaaccent -10
+KPX g period -15
+KPX gbreve comma -10
+KPX gbreve e -10
+KPX gbreve eacute -10
+KPX gbreve ecaron -10
+KPX gbreve ecircumflex -10
+KPX gbreve edieresis -10
+KPX gbreve edotaccent -10
+KPX gbreve egrave -10
+KPX gbreve emacron -10
+KPX gbreve eogonek -10
+KPX gbreve g -10
+KPX gbreve gbreve -10
+KPX gbreve gcommaaccent -10
+KPX gbreve period -15
+KPX gcommaaccent comma -10
+KPX gcommaaccent e -10
+KPX gcommaaccent eacute -10
+KPX gcommaaccent ecaron -10
+KPX gcommaaccent ecircumflex -10
+KPX gcommaaccent edieresis -10
+KPX gcommaaccent edotaccent -10
+KPX gcommaaccent egrave -10
+KPX gcommaaccent emacron -10
+KPX gcommaaccent eogonek -10
+KPX gcommaaccent g -10
+KPX gcommaaccent gbreve -10
+KPX gcommaaccent gcommaaccent -10
+KPX gcommaaccent period -15
+KPX k e -10
+KPX k eacute -10
+KPX k ecaron -10
+KPX k ecircumflex -10
+KPX k edieresis -10
+KPX k edotaccent -10
+KPX k egrave -10
+KPX k emacron -10
+KPX k eogonek -10
+KPX k o -10
+KPX k oacute -10
+KPX k ocircumflex -10
+KPX k odieresis -10
+KPX k ograve -10
+KPX k ohungarumlaut -10
+KPX k omacron -10
+KPX k oslash -10
+KPX k otilde -10
+KPX k y -10
+KPX k yacute -10
+KPX k ydieresis -10
+KPX kcommaaccent e -10
+KPX kcommaaccent eacute -10
+KPX kcommaaccent ecaron -10
+KPX kcommaaccent ecircumflex -10
+KPX kcommaaccent edieresis -10
+KPX kcommaaccent edotaccent -10
+KPX kcommaaccent egrave -10
+KPX kcommaaccent emacron -10
+KPX kcommaaccent eogonek -10
+KPX kcommaaccent o -10
+KPX kcommaaccent oacute -10
+KPX kcommaaccent ocircumflex -10
+KPX kcommaaccent odieresis -10
+KPX kcommaaccent ograve -10
+KPX kcommaaccent ohungarumlaut -10
+KPX kcommaaccent omacron -10
+KPX kcommaaccent oslash -10
+KPX kcommaaccent otilde -10
+KPX kcommaaccent y -10
+KPX kcommaaccent yacute -10
+KPX kcommaaccent ydieresis -10
+KPX n v -40
+KPX nacute v -40
+KPX ncaron v -40
+KPX ncommaaccent v -40
+KPX ntilde v -40
+KPX o g -10
+KPX o gbreve -10
+KPX o gcommaaccent -10
+KPX o v -10
+KPX oacute g -10
+KPX oacute gbreve -10
+KPX oacute gcommaaccent -10
+KPX oacute v -10
+KPX ocircumflex g -10
+KPX ocircumflex gbreve -10
+KPX ocircumflex gcommaaccent -10
+KPX ocircumflex v -10
+KPX odieresis g -10
+KPX odieresis gbreve -10
+KPX odieresis gcommaaccent -10
+KPX odieresis v -10
+KPX ograve g -10
+KPX ograve gbreve -10
+KPX ograve gcommaaccent -10
+KPX ograve v -10
+KPX ohungarumlaut g -10
+KPX ohungarumlaut gbreve -10
+KPX ohungarumlaut gcommaaccent -10
+KPX ohungarumlaut v -10
+KPX omacron g -10
+KPX omacron gbreve -10
+KPX omacron gcommaaccent -10
+KPX omacron v -10
+KPX oslash g -10
+KPX oslash gbreve -10
+KPX oslash gcommaaccent -10
+KPX oslash v -10
+KPX otilde g -10
+KPX otilde gbreve -10
+KPX otilde gcommaaccent -10
+KPX otilde v -10
+KPX period quotedblright -140
+KPX period quoteright -140
+KPX quoteleft quoteleft -111
+KPX quoteright d -25
+KPX quoteright dcroat -25
+KPX quoteright quoteright -111
+KPX quoteright r -25
+KPX quoteright racute -25
+KPX quoteright rcaron -25
+KPX quoteright rcommaaccent -25
+KPX quoteright s -40
+KPX quoteright sacute -40
+KPX quoteright scaron -40
+KPX quoteright scedilla -40
+KPX quoteright scommaaccent -40
+KPX quoteright space -111
+KPX quoteright t -30
+KPX quoteright tcommaaccent -30
+KPX quoteright v -10
+KPX r a -15
+KPX r aacute -15
+KPX r abreve -15
+KPX r acircumflex -15
+KPX r adieresis -15
+KPX r agrave -15
+KPX r amacron -15
+KPX r aogonek -15
+KPX r aring -15
+KPX r atilde -15
+KPX r c -37
+KPX r cacute -37
+KPX r ccaron -37
+KPX r ccedilla -37
+KPX r comma -111
+KPX r d -37
+KPX r dcroat -37
+KPX r e -37
+KPX r eacute -37
+KPX r ecaron -37
+KPX r ecircumflex -37
+KPX r edieresis -37
+KPX r edotaccent -37
+KPX r egrave -37
+KPX r emacron -37
+KPX r eogonek -37
+KPX r g -37
+KPX r gbreve -37
+KPX r gcommaaccent -37
+KPX r hyphen -20
+KPX r o -45
+KPX r oacute -45
+KPX r ocircumflex -45
+KPX r odieresis -45
+KPX r ograve -45
+KPX r ohungarumlaut -45
+KPX r omacron -45
+KPX r oslash -45
+KPX r otilde -45
+KPX r period -111
+KPX r q -37
+KPX r s -10
+KPX r sacute -10
+KPX r scaron -10
+KPX r scedilla -10
+KPX r scommaaccent -10
+KPX racute a -15
+KPX racute aacute -15
+KPX racute abreve -15
+KPX racute acircumflex -15
+KPX racute adieresis -15
+KPX racute agrave -15
+KPX racute amacron -15
+KPX racute aogonek -15
+KPX racute aring -15
+KPX racute atilde -15
+KPX racute c -37
+KPX racute cacute -37
+KPX racute ccaron -37
+KPX racute ccedilla -37
+KPX racute comma -111
+KPX racute d -37
+KPX racute dcroat -37
+KPX racute e -37
+KPX racute eacute -37
+KPX racute ecaron -37
+KPX racute ecircumflex -37
+KPX racute edieresis -37
+KPX racute edotaccent -37
+KPX racute egrave -37
+KPX racute emacron -37
+KPX racute eogonek -37
+KPX racute g -37
+KPX racute gbreve -37
+KPX racute gcommaaccent -37
+KPX racute hyphen -20
+KPX racute o -45
+KPX racute oacute -45
+KPX racute ocircumflex -45
+KPX racute odieresis -45
+KPX racute ograve -45
+KPX racute ohungarumlaut -45
+KPX racute omacron -45
+KPX racute oslash -45
+KPX racute otilde -45
+KPX racute period -111
+KPX racute q -37
+KPX racute s -10
+KPX racute sacute -10
+KPX racute scaron -10
+KPX racute scedilla -10
+KPX racute scommaaccent -10
+KPX rcaron a -15
+KPX rcaron aacute -15
+KPX rcaron abreve -15
+KPX rcaron acircumflex -15
+KPX rcaron adieresis -15
+KPX rcaron agrave -15
+KPX rcaron amacron -15
+KPX rcaron aogonek -15
+KPX rcaron aring -15
+KPX rcaron atilde -15
+KPX rcaron c -37
+KPX rcaron cacute -37
+KPX rcaron ccaron -37
+KPX rcaron ccedilla -37
+KPX rcaron comma -111
+KPX rcaron d -37
+KPX rcaron dcroat -37
+KPX rcaron e -37
+KPX rcaron eacute -37
+KPX rcaron ecaron -37
+KPX rcaron ecircumflex -37
+KPX rcaron edieresis -37
+KPX rcaron edotaccent -37
+KPX rcaron egrave -37
+KPX rcaron emacron -37
+KPX rcaron eogonek -37
+KPX rcaron g -37
+KPX rcaron gbreve -37
+KPX rcaron gcommaaccent -37
+KPX rcaron hyphen -20
+KPX rcaron o -45
+KPX rcaron oacute -45
+KPX rcaron ocircumflex -45
+KPX rcaron odieresis -45
+KPX rcaron ograve -45
+KPX rcaron ohungarumlaut -45
+KPX rcaron omacron -45
+KPX rcaron oslash -45
+KPX rcaron otilde -45
+KPX rcaron period -111
+KPX rcaron q -37
+KPX rcaron s -10
+KPX rcaron sacute -10
+KPX rcaron scaron -10
+KPX rcaron scedilla -10
+KPX rcaron scommaaccent -10
+KPX rcommaaccent a -15
+KPX rcommaaccent aacute -15
+KPX rcommaaccent abreve -15
+KPX rcommaaccent acircumflex -15
+KPX rcommaaccent adieresis -15
+KPX rcommaaccent agrave -15
+KPX rcommaaccent amacron -15
+KPX rcommaaccent aogonek -15
+KPX rcommaaccent aring -15
+KPX rcommaaccent atilde -15
+KPX rcommaaccent c -37
+KPX rcommaaccent cacute -37
+KPX rcommaaccent ccaron -37
+KPX rcommaaccent ccedilla -37
+KPX rcommaaccent comma -111
+KPX rcommaaccent d -37
+KPX rcommaaccent dcroat -37
+KPX rcommaaccent e -37
+KPX rcommaaccent eacute -37
+KPX rcommaaccent ecaron -37
+KPX rcommaaccent ecircumflex -37
+KPX rcommaaccent edieresis -37
+KPX rcommaaccent edotaccent -37
+KPX rcommaaccent egrave -37
+KPX rcommaaccent emacron -37
+KPX rcommaaccent eogonek -37
+KPX rcommaaccent g -37
+KPX rcommaaccent gbreve -37
+KPX rcommaaccent gcommaaccent -37
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent o -45
+KPX rcommaaccent oacute -45
+KPX rcommaaccent ocircumflex -45
+KPX rcommaaccent odieresis -45
+KPX rcommaaccent ograve -45
+KPX rcommaaccent ohungarumlaut -45
+KPX rcommaaccent omacron -45
+KPX rcommaaccent oslash -45
+KPX rcommaaccent otilde -45
+KPX rcommaaccent period -111
+KPX rcommaaccent q -37
+KPX rcommaaccent s -10
+KPX rcommaaccent sacute -10
+KPX rcommaaccent scaron -10
+KPX rcommaaccent scedilla -10
+KPX rcommaaccent scommaaccent -10
+KPX space A -18
+KPX space Aacute -18
+KPX space Abreve -18
+KPX space Acircumflex -18
+KPX space Adieresis -18
+KPX space Agrave -18
+KPX space Amacron -18
+KPX space Aogonek -18
+KPX space Aring -18
+KPX space Atilde -18
+KPX space T -18
+KPX space Tcaron -18
+KPX space Tcommaaccent -18
+KPX space V -35
+KPX space W -40
+KPX space Y -75
+KPX space Yacute -75
+KPX space Ydieresis -75
+KPX v comma -74
+KPX v period -74
+KPX w comma -74
+KPX w period -74
+KPX y comma -55
+KPX y period -55
+KPX yacute comma -55
+KPX yacute period -55
+KPX ydieresis comma -55
+KPX ydieresis period -55
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Roman.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Roman.afm
new file mode 100644
index 0000000..0fdf332
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/Times-Roman.afm
@@ -0,0 +1,2421 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 12:49:17 1997
+Comment UniqueID 43068
+Comment VMusage 43909 54934
+FontName Times-Roman
+FullName Times Roman
+FamilyName Times
+Weight Roman
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet ExtendedRoman
+FontBBox -168 -218 1000 898
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.00
+Notice Copyright (c) 1985, 1987, 1989, 1990, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved.Times is a trademark of Linotype-Hell AG and/or its subsidiaries.
+EncodingScheme WinAnsiEncoding
+CapHeight 662
+XHeight 450
+Ascender 683
+Descender -217
+StdHW 28
+StdVW 84
+StartCharMetrics 317
+C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 160 ; WX 250 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 333 ; N exclam ; B 130 -9 238 676 ;
+C 34 ; WX 408 ; N quotedbl ; B 77 431 331 676 ;
+C 35 ; WX 500 ; N numbersign ; B 5 0 496 662 ;
+C 36 ; WX 500 ; N dollar ; B 44 -87 457 727 ;
+C 37 ; WX 833 ; N percent ; B 61 -13 772 676 ;
+C 38 ; WX 778 ; N ampersand ; B 42 -13 750 676 ;
+C 146 ; WX 333 ; N quoteright ; B 79 433 218 676 ;
+C 40 ; WX 333 ; N parenleft ; B 48 -177 304 676 ;
+C 41 ; WX 333 ; N parenright ; B 29 -177 285 676 ;
+C 42 ; WX 500 ; N asterisk ; B 69 265 432 676 ;
+C 43 ; WX 564 ; N plus ; B 30 0 534 506 ;
+C 44 ; WX 250 ; N comma ; B 56 -141 195 102 ;
+C 45 ; WX 333 ; N hyphen ; B 39 194 285 257 ;
+C 173 ; WX 333 ; N hyphen ; B 39 194 285 257 ;
+C 46 ; WX 250 ; N period ; B 70 -11 181 100 ;
+C 47 ; WX 278 ; N slash ; B -9 -14 287 676 ;
+C 48 ; WX 500 ; N zero ; B 24 -14 476 676 ;
+C 49 ; WX 500 ; N one ; B 111 0 394 676 ;
+C 50 ; WX 500 ; N two ; B 30 0 475 676 ;
+C 51 ; WX 500 ; N three ; B 43 -14 431 676 ;
+C 52 ; WX 500 ; N four ; B 12 0 472 676 ;
+C 53 ; WX 500 ; N five ; B 32 -14 438 688 ;
+C 54 ; WX 500 ; N six ; B 34 -14 468 684 ;
+C 55 ; WX 500 ; N seven ; B 20 -8 449 662 ;
+C 56 ; WX 500 ; N eight ; B 56 -14 445 676 ;
+C 57 ; WX 500 ; N nine ; B 30 -22 459 676 ;
+C 58 ; WX 278 ; N colon ; B 81 -11 192 459 ;
+C 59 ; WX 278 ; N semicolon ; B 80 -141 219 459 ;
+C 60 ; WX 564 ; N less ; B 28 -8 536 514 ;
+C 61 ; WX 564 ; N equal ; B 30 120 534 386 ;
+C 62 ; WX 564 ; N greater ; B 28 -8 536 514 ;
+C 63 ; WX 444 ; N question ; B 68 -8 414 676 ;
+C 64 ; WX 921 ; N at ; B 116 -14 809 676 ;
+C 65 ; WX 722 ; N A ; B 15 0 706 674 ;
+C 66 ; WX 667 ; N B ; B 17 0 593 662 ;
+C 67 ; WX 667 ; N C ; B 28 -14 633 676 ;
+C 68 ; WX 722 ; N D ; B 16 0 685 662 ;
+C 69 ; WX 611 ; N E ; B 12 0 597 662 ;
+C 70 ; WX 556 ; N F ; B 12 0 546 662 ;
+C 71 ; WX 722 ; N G ; B 32 -14 709 676 ;
+C 72 ; WX 722 ; N H ; B 19 0 702 662 ;
+C 73 ; WX 333 ; N I ; B 18 0 315 662 ;
+C 74 ; WX 389 ; N J ; B 10 -14 370 662 ;
+C 75 ; WX 722 ; N K ; B 34 0 723 662 ;
+C 76 ; WX 611 ; N L ; B 12 0 598 662 ;
+C 77 ; WX 889 ; N M ; B 12 0 863 662 ;
+C 78 ; WX 722 ; N N ; B 12 -11 707 662 ;
+C 79 ; WX 722 ; N O ; B 34 -14 688 676 ;
+C 80 ; WX 556 ; N P ; B 16 0 542 662 ;
+C 81 ; WX 722 ; N Q ; B 34 -178 701 676 ;
+C 82 ; WX 667 ; N R ; B 17 0 659 662 ;
+C 83 ; WX 556 ; N S ; B 42 -14 491 676 ;
+C 84 ; WX 611 ; N T ; B 17 0 593 662 ;
+C 85 ; WX 722 ; N U ; B 14 -14 705 662 ;
+C 86 ; WX 722 ; N V ; B 16 -11 697 662 ;
+C 87 ; WX 944 ; N W ; B 5 -11 932 662 ;
+C 88 ; WX 722 ; N X ; B 10 0 704 662 ;
+C 89 ; WX 722 ; N Y ; B 22 0 703 662 ;
+C 90 ; WX 611 ; N Z ; B 9 0 597 662 ;
+C 91 ; WX 333 ; N bracketleft ; B 88 -156 299 662 ;
+C 92 ; WX 278 ; N backslash ; B -9 -14 287 676 ;
+C 93 ; WX 333 ; N bracketright ; B 34 -156 245 662 ;
+C 94 ; WX 469 ; N asciicircum ; B 24 297 446 662 ;
+C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
+C 145 ; WX 333 ; N quoteleft ; B 115 433 254 676 ;
+C 97 ; WX 444 ; N a ; B 37 -10 442 460 ;
+C 98 ; WX 500 ; N b ; B 3 -10 468 683 ;
+C 99 ; WX 444 ; N c ; B 25 -10 412 460 ;
+C 100 ; WX 500 ; N d ; B 27 -10 491 683 ;
+C 101 ; WX 444 ; N e ; B 25 -10 424 460 ;
+C 102 ; WX 333 ; N f ; B 20 0 383 683 ; L i fi ; L l fl ;
+C 103 ; WX 500 ; N g ; B 28 -218 470 460 ;
+C 104 ; WX 500 ; N h ; B 9 0 487 683 ;
+C 105 ; WX 278 ; N i ; B 16 0 253 683 ;
+C 106 ; WX 278 ; N j ; B -70 -218 194 683 ;
+C 107 ; WX 500 ; N k ; B 7 0 505 683 ;
+C 108 ; WX 278 ; N l ; B 19 0 257 683 ;
+C 109 ; WX 778 ; N m ; B 16 0 775 460 ;
+C 110 ; WX 500 ; N n ; B 16 0 485 460 ;
+C 111 ; WX 500 ; N o ; B 29 -10 470 460 ;
+C 112 ; WX 500 ; N p ; B 5 -217 470 460 ;
+C 113 ; WX 500 ; N q ; B 24 -217 488 460 ;
+C 114 ; WX 333 ; N r ; B 5 0 335 460 ;
+C 115 ; WX 389 ; N s ; B 51 -10 348 460 ;
+C 116 ; WX 278 ; N t ; B 13 -10 279 579 ;
+C 117 ; WX 500 ; N u ; B 9 -10 479 450 ;
+C 118 ; WX 500 ; N v ; B 19 -14 477 450 ;
+C 119 ; WX 722 ; N w ; B 21 -14 694 450 ;
+C 120 ; WX 500 ; N x ; B 17 0 479 450 ;
+C 121 ; WX 500 ; N y ; B 14 -218 475 450 ;
+C 122 ; WX 444 ; N z ; B 27 0 418 450 ;
+C 123 ; WX 480 ; N braceleft ; B 100 -181 350 680 ;
+C 124 ; WX 200 ; N bar ; B 67 -218 133 782 ;
+C 125 ; WX 480 ; N braceright ; B 130 -181 380 680 ;
+C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;
+C 161 ; WX 333 ; N exclamdown ; B 97 -218 205 467 ;
+C 162 ; WX 500 ; N cent ; B 53 -138 448 579 ;
+C 163 ; WX 500 ; N sterling ; B 12 -8 490 676 ;
+C -1 ; WX 167 ; N fraction ; B -168 -14 331 676 ;
+C 165 ; WX 500 ; N yen ; B -53 0 512 662 ;
+C 131 ; WX 500 ; N florin ; B 7 -189 490 676 ;
+C 167 ; WX 500 ; N section ; B 70 -148 426 676 ;
+C 164 ; WX 500 ; N currency ; B -22 58 522 602 ;
+C 39 ; WX 180 ; N quotesingle ; B 48 431 133 676 ;
+C 147 ; WX 444 ; N quotedblleft ; B 43 433 414 676 ;
+C 171 ; WX 500 ; N guillemotleft ; B 42 33 456 416 ;
+C 139 ; WX 333 ; N guilsinglleft ; B 63 33 285 416 ;
+C 155 ; WX 333 ; N guilsinglright ; B 48 33 270 416 ;
+C -1 ; WX 556 ; N fi ; B 31 0 521 683 ;
+C -1 ; WX 556 ; N fl ; B 32 0 521 683 ;
+C 150 ; WX 500 ; N endash ; B 0 201 500 250 ;
+C 134 ; WX 500 ; N dagger ; B 59 -149 442 676 ;
+C 135 ; WX 500 ; N daggerdbl ; B 58 -153 442 676 ;
+C 183 ; WX 250 ; N periodcentered ; B 70 199 181 310 ;
+C 182 ; WX 453 ; N paragraph ; B -22 -154 450 662 ;
+C 149 ; WX 350 ; N bullet ; B 40 196 310 466 ;
+C 130 ; WX 333 ; N quotesinglbase ; B 79 -141 218 102 ;
+C 132 ; WX 444 ; N quotedblbase ; B 45 -141 416 102 ;
+C 148 ; WX 444 ; N quotedblright ; B 30 433 401 676 ;
+C 187 ; WX 500 ; N guillemotright ; B 44 33 458 416 ;
+C 133 ; WX 1000 ; N ellipsis ; B 111 -11 888 100 ;
+C 137 ; WX 1000 ; N perthousand ; B 7 -19 994 706 ;
+C 191 ; WX 444 ; N questiondown ; B 30 -218 376 466 ;
+C 96 ; WX 333 ; N grave ; B 19 507 242 678 ;
+C 180 ; WX 333 ; N acute ; B 93 507 317 678 ;
+C 136 ; WX 333 ; N circumflex ; B 11 507 322 674 ;
+C 152 ; WX 333 ; N tilde ; B 1 532 331 638 ;
+C 175 ; WX 333 ; N macron ; B 11 547 322 601 ;
+C -1 ; WX 333 ; N breve ; B 26 507 307 664 ;
+C -1 ; WX 333 ; N dotaccent ; B 118 581 216 681 ;
+C 168 ; WX 333 ; N dieresis ; B 18 581 315 681 ;
+C -1 ; WX 333 ; N ring ; B 67 512 266 711 ;
+C 184 ; WX 333 ; N cedilla ; B 52 -215 261 0 ;
+C -1 ; WX 333 ; N hungarumlaut ; B -3 507 377 678 ;
+C -1 ; WX 333 ; N ogonek ; B 62 -165 243 0 ;
+C -1 ; WX 333 ; N caron ; B 11 507 322 674 ;
+C 151 ; WX 1000 ; N emdash ; B 0 201 1000 250 ;
+C 198 ; WX 889 ; N AE ; B 0 0 863 662 ;
+C 170 ; WX 276 ; N ordfeminine ; B 4 394 270 676 ;
+C -1 ; WX 611 ; N Lslash ; B 12 0 598 662 ;
+C 216 ; WX 722 ; N Oslash ; B 34 -80 688 734 ;
+C 140 ; WX 889 ; N OE ; B 30 -6 885 668 ;
+C 186 ; WX 310 ; N ordmasculine ; B 6 394 304 676 ;
+C 230 ; WX 667 ; N ae ; B 38 -10 632 460 ;
+C -1 ; WX 278 ; N dotlessi ; B 16 0 253 460 ;
+C -1 ; WX 278 ; N lslash ; B 19 0 259 683 ;
+C 248 ; WX 500 ; N oslash ; B 29 -112 470 551 ;
+C 156 ; WX 722 ; N oe ; B 30 -10 690 460 ;
+C 223 ; WX 500 ; N germandbls ; B 12 -9 468 683 ;
+C 207 ; WX 333 ; N Idieresis ; B 18 0 315 835 ;
+C 233 ; WX 444 ; N eacute ; B 25 -10 424 678 ;
+C -1 ; WX 444 ; N abreve ; B 37 -10 442 664 ;
+C -1 ; WX 500 ; N uhungarumlaut ; B 9 -10 501 678 ;
+C -1 ; WX 444 ; N ecaron ; B 25 -10 424 674 ;
+C 159 ; WX 722 ; N Ydieresis ; B 22 0 703 835 ;
+C 247 ; WX 564 ; N divide ; B 30 -10 534 516 ;
+C 221 ; WX 722 ; N Yacute ; B 22 0 703 890 ;
+C 194 ; WX 722 ; N Acircumflex ; B 15 0 706 886 ;
+C 225 ; WX 444 ; N aacute ; B 37 -10 442 678 ;
+C 219 ; WX 722 ; N Ucircumflex ; B 14 -14 705 886 ;
+C 253 ; WX 500 ; N yacute ; B 14 -218 475 678 ;
+C -1 ; WX 389 ; N scommaaccent ; B 51 -218 348 460 ;
+C 234 ; WX 444 ; N ecircumflex ; B 25 -10 424 674 ;
+C -1 ; WX 722 ; N Uring ; B 14 -14 705 898 ;
+C 220 ; WX 722 ; N Udieresis ; B 14 -14 705 835 ;
+C -1 ; WX 444 ; N aogonek ; B 37 -165 469 460 ;
+C 218 ; WX 722 ; N Uacute ; B 14 -14 705 890 ;
+C -1 ; WX 500 ; N uogonek ; B 9 -155 487 450 ;
+C 203 ; WX 611 ; N Edieresis ; B 12 0 597 835 ;
+C -1 ; WX 722 ; N Dcroat ; B 16 0 685 662 ;
+C -1 ; WX 250 ; N commaaccent ; B 59 -218 184 -50 ;
+C 169 ; WX 760 ; N copyright ; B 38 -14 722 676 ;
+C -1 ; WX 611 ; N Emacron ; B 12 0 597 813 ;
+C -1 ; WX 444 ; N ccaron ; B 25 -10 412 674 ;
+C 229 ; WX 444 ; N aring ; B 37 -10 442 711 ;
+C -1 ; WX 722 ; N Ncommaaccent ; B 12 -198 707 662 ;
+C -1 ; WX 278 ; N lacute ; B 19 0 290 890 ;
+C 224 ; WX 444 ; N agrave ; B 37 -10 442 678 ;
+C -1 ; WX 611 ; N Tcommaaccent ; B 17 -218 593 662 ;
+C -1 ; WX 667 ; N Cacute ; B 28 -14 633 890 ;
+C 227 ; WX 444 ; N atilde ; B 37 -10 442 638 ;
+C -1 ; WX 611 ; N Edotaccent ; B 12 0 597 835 ;
+C 154 ; WX 389 ; N scaron ; B 39 -10 350 674 ;
+C -1 ; WX 389 ; N scedilla ; B 51 -215 348 460 ;
+C 237 ; WX 278 ; N iacute ; B 16 0 290 678 ;
+C -1 ; WX 471 ; N lozenge ; B 13 0 459 724 ;
+C -1 ; WX 667 ; N Rcaron ; B 17 0 659 886 ;
+C -1 ; WX 722 ; N Gcommaaccent ; B 32 -218 709 676 ;
+C 251 ; WX 500 ; N ucircumflex ; B 9 -10 479 674 ;
+C 226 ; WX 444 ; N acircumflex ; B 37 -10 442 674 ;
+C -1 ; WX 722 ; N Amacron ; B 15 0 706 813 ;
+C -1 ; WX 333 ; N rcaron ; B 5 0 335 674 ;
+C 231 ; WX 444 ; N ccedilla ; B 25 -215 412 460 ;
+C -1 ; WX 611 ; N Zdotaccent ; B 9 0 597 835 ;
+C 222 ; WX 556 ; N Thorn ; B 16 0 542 662 ;
+C -1 ; WX 722 ; N Omacron ; B 34 -14 688 813 ;
+C -1 ; WX 667 ; N Racute ; B 17 0 659 890 ;
+C -1 ; WX 556 ; N Sacute ; B 42 -14 491 890 ;
+C -1 ; WX 588 ; N dcaron ; B 27 -10 589 695 ;
+C -1 ; WX 722 ; N Umacron ; B 14 -14 705 813 ;
+C -1 ; WX 500 ; N uring ; B 9 -10 479 711 ;
+C 179 ; WX 300 ; N threesuperior ; B 15 262 291 676 ;
+C 210 ; WX 722 ; N Ograve ; B 34 -14 688 890 ;
+C 192 ; WX 722 ; N Agrave ; B 15 0 706 890 ;
+C -1 ; WX 722 ; N Abreve ; B 15 0 706 876 ;
+C 215 ; WX 564 ; N multiply ; B 38 8 527 497 ;
+C 250 ; WX 500 ; N uacute ; B 9 -10 479 678 ;
+C -1 ; WX 611 ; N Tcaron ; B 17 0 593 886 ;
+C -1 ; WX 476 ; N partialdiff ; B 17 -38 459 710 ;
+C 255 ; WX 500 ; N ydieresis ; B 14 -218 475 623 ;
+C -1 ; WX 722 ; N Nacute ; B 12 -11 707 890 ;
+C 238 ; WX 278 ; N icircumflex ; B -16 0 295 674 ;
+C 202 ; WX 611 ; N Ecircumflex ; B 12 0 597 886 ;
+C 228 ; WX 444 ; N adieresis ; B 37 -10 442 623 ;
+C 235 ; WX 444 ; N edieresis ; B 25 -10 424 623 ;
+C -1 ; WX 444 ; N cacute ; B 25 -10 413 678 ;
+C -1 ; WX 500 ; N nacute ; B 16 0 485 678 ;
+C -1 ; WX 500 ; N umacron ; B 9 -10 479 601 ;
+C -1 ; WX 722 ; N Ncaron ; B 12 -11 707 886 ;
+C 205 ; WX 333 ; N Iacute ; B 18 0 317 890 ;
+C 177 ; WX 564 ; N plusminus ; B 30 0 534 506 ;
+C 166 ; WX 200 ; N brokenbar ; B 67 -143 133 707 ;
+C 174 ; WX 760 ; N registered ; B 38 -14 722 676 ;
+C -1 ; WX 722 ; N Gbreve ; B 32 -14 709 876 ;
+C -1 ; WX 333 ; N Idotaccent ; B 18 0 315 835 ;
+C -1 ; WX 600 ; N summation ; B 15 -10 585 706 ;
+C 200 ; WX 611 ; N Egrave ; B 12 0 597 890 ;
+C -1 ; WX 333 ; N racute ; B 5 0 335 678 ;
+C -1 ; WX 500 ; N omacron ; B 29 -10 470 601 ;
+C -1 ; WX 611 ; N Zacute ; B 9 0 597 890 ;
+C 142 ; WX 611 ; N Zcaron ; B 9 0 597 886 ;
+C -1 ; WX 549 ; N greaterequal ; B 26 0 523 666 ;
+C 208 ; WX 722 ; N Eth ; B 16 0 685 662 ;
+C 199 ; WX 667 ; N Ccedilla ; B 28 -215 633 676 ;
+C -1 ; WX 278 ; N lcommaaccent ; B 19 -218 257 683 ;
+C -1 ; WX 326 ; N tcaron ; B 13 -10 318 722 ;
+C -1 ; WX 444 ; N eogonek ; B 25 -165 424 460 ;
+C -1 ; WX 722 ; N Uogonek ; B 14 -165 705 662 ;
+C 193 ; WX 722 ; N Aacute ; B 15 0 706 890 ;
+C 196 ; WX 722 ; N Adieresis ; B 15 0 706 835 ;
+C 232 ; WX 444 ; N egrave ; B 25 -10 424 678 ;
+C -1 ; WX 444 ; N zacute ; B 27 0 418 678 ;
+C -1 ; WX 278 ; N iogonek ; B 16 -165 265 683 ;
+C 211 ; WX 722 ; N Oacute ; B 34 -14 688 890 ;
+C 243 ; WX 500 ; N oacute ; B 29 -10 470 678 ;
+C -1 ; WX 444 ; N amacron ; B 37 -10 442 601 ;
+C -1 ; WX 389 ; N sacute ; B 51 -10 348 678 ;
+C 239 ; WX 278 ; N idieresis ; B -9 0 288 623 ;
+C 212 ; WX 722 ; N Ocircumflex ; B 34 -14 688 886 ;
+C 217 ; WX 722 ; N Ugrave ; B 14 -14 705 890 ;
+C -1 ; WX 612 ; N Delta ; B 6 0 608 688 ;
+C 254 ; WX 500 ; N thorn ; B 5 -217 470 683 ;
+C 178 ; WX 300 ; N twosuperior ; B 1 270 296 676 ;
+C 214 ; WX 722 ; N Odieresis ; B 34 -14 688 835 ;
+C 181 ; WX 500 ; N mu ; B 36 -218 512 450 ;
+C 236 ; WX 278 ; N igrave ; B -8 0 253 678 ;
+C -1 ; WX 500 ; N ohungarumlaut ; B 29 -10 491 678 ;
+C -1 ; WX 611 ; N Eogonek ; B 12 -165 597 662 ;
+C -1 ; WX 500 ; N dcroat ; B 27 -10 500 683 ;
+C 190 ; WX 750 ; N threequarters ; B 15 -14 718 676 ;
+C -1 ; WX 556 ; N Scedilla ; B 42 -215 491 676 ;
+C -1 ; WX 344 ; N lcaron ; B 19 0 347 695 ;
+C -1 ; WX 722 ; N Kcommaaccent ; B 34 -198 723 662 ;
+C -1 ; WX 611 ; N Lacute ; B 12 0 598 890 ;
+C 153 ; WX 980 ; N trademark ; B 30 256 957 662 ;
+C -1 ; WX 444 ; N edotaccent ; B 25 -10 424 623 ;
+C 204 ; WX 333 ; N Igrave ; B 18 0 315 890 ;
+C -1 ; WX 333 ; N Imacron ; B 11 0 322 813 ;
+C -1 ; WX 611 ; N Lcaron ; B 12 0 598 676 ;
+C 189 ; WX 750 ; N onehalf ; B 31 -14 746 676 ;
+C -1 ; WX 549 ; N lessequal ; B 26 0 523 666 ;
+C 244 ; WX 500 ; N ocircumflex ; B 29 -10 470 674 ;
+C 241 ; WX 500 ; N ntilde ; B 16 0 485 638 ;
+C -1 ; WX 722 ; N Uhungarumlaut ; B 14 -14 705 890 ;
+C 201 ; WX 611 ; N Eacute ; B 12 0 597 890 ;
+C -1 ; WX 444 ; N emacron ; B 25 -10 424 601 ;
+C -1 ; WX 500 ; N gbreve ; B 28 -218 470 664 ;
+C 188 ; WX 750 ; N onequarter ; B 37 -14 718 676 ;
+C 138 ; WX 556 ; N Scaron ; B 42 -14 491 886 ;
+C -1 ; WX 556 ; N Scommaaccent ; B 42 -218 491 676 ;
+C -1 ; WX 722 ; N Ohungarumlaut ; B 34 -14 688 890 ;
+C 176 ; WX 400 ; N degree ; B 57 390 343 676 ;
+C 242 ; WX 500 ; N ograve ; B 29 -10 470 678 ;
+C -1 ; WX 667 ; N Ccaron ; B 28 -14 633 886 ;
+C 249 ; WX 500 ; N ugrave ; B 9 -10 479 678 ;
+C -1 ; WX 453 ; N radical ; B 2 -60 452 768 ;
+C -1 ; WX 722 ; N Dcaron ; B 16 0 685 886 ;
+C -1 ; WX 333 ; N rcommaaccent ; B 5 -218 335 460 ;
+C 209 ; WX 722 ; N Ntilde ; B 12 -11 707 850 ;
+C 245 ; WX 500 ; N otilde ; B 29 -10 470 638 ;
+C -1 ; WX 667 ; N Rcommaaccent ; B 17 -198 659 662 ;
+C -1 ; WX 611 ; N Lcommaaccent ; B 12 -218 598 662 ;
+C 195 ; WX 722 ; N Atilde ; B 15 0 706 850 ;
+C -1 ; WX 722 ; N Aogonek ; B 15 -165 738 674 ;
+C 197 ; WX 722 ; N Aring ; B 15 0 706 898 ;
+C 213 ; WX 722 ; N Otilde ; B 34 -14 688 850 ;
+C -1 ; WX 444 ; N zdotaccent ; B 27 0 418 623 ;
+C -1 ; WX 611 ; N Ecaron ; B 12 0 597 886 ;
+C -1 ; WX 333 ; N Iogonek ; B 18 -165 315 662 ;
+C -1 ; WX 500 ; N kcommaaccent ; B 7 -218 505 683 ;
+C -1 ; WX 564 ; N minus ; B 30 220 534 286 ;
+C 206 ; WX 333 ; N Icircumflex ; B 11 0 322 886 ;
+C -1 ; WX 500 ; N ncaron ; B 16 0 485 674 ;
+C -1 ; WX 278 ; N tcommaaccent ; B 13 -218 279 579 ;
+C 172 ; WX 564 ; N logicalnot ; B 30 108 534 386 ;
+C 246 ; WX 500 ; N odieresis ; B 29 -10 470 623 ;
+C 252 ; WX 500 ; N udieresis ; B 9 -10 479 623 ;
+C -1 ; WX 549 ; N notequal ; B 12 -31 537 547 ;
+C -1 ; WX 500 ; N gcommaaccent ; B 28 -218 470 749 ;
+C 240 ; WX 500 ; N eth ; B 29 -10 471 686 ;
+C 158 ; WX 444 ; N zcaron ; B 27 0 418 674 ;
+C -1 ; WX 500 ; N ncommaaccent ; B 16 -218 485 460 ;
+C 185 ; WX 300 ; N onesuperior ; B 57 270 248 676 ;
+C -1 ; WX 278 ; N imacron ; B 6 0 271 601 ;
+C 128 ; WX 500 ; N Euro ; B 0 0 0 0 ;
+EndCharMetrics
+StartKernData
+StartKernPairs 2073
+KPX A C -40
+KPX A Cacute -40
+KPX A Ccaron -40
+KPX A Ccedilla -40
+KPX A G -40
+KPX A Gbreve -40
+KPX A Gcommaaccent -40
+KPX A O -55
+KPX A Oacute -55
+KPX A Ocircumflex -55
+KPX A Odieresis -55
+KPX A Ograve -55
+KPX A Ohungarumlaut -55
+KPX A Omacron -55
+KPX A Oslash -55
+KPX A Otilde -55
+KPX A Q -55
+KPX A T -111
+KPX A Tcaron -111
+KPX A Tcommaaccent -111
+KPX A U -55
+KPX A Uacute -55
+KPX A Ucircumflex -55
+KPX A Udieresis -55
+KPX A Ugrave -55
+KPX A Uhungarumlaut -55
+KPX A Umacron -55
+KPX A Uogonek -55
+KPX A Uring -55
+KPX A V -135
+KPX A W -90
+KPX A Y -105
+KPX A Yacute -105
+KPX A Ydieresis -105
+KPX A quoteright -111
+KPX A v -74
+KPX A w -92
+KPX A y -92
+KPX A yacute -92
+KPX A ydieresis -92
+KPX Aacute C -40
+KPX Aacute Cacute -40
+KPX Aacute Ccaron -40
+KPX Aacute Ccedilla -40
+KPX Aacute G -40
+KPX Aacute Gbreve -40
+KPX Aacute Gcommaaccent -40
+KPX Aacute O -55
+KPX Aacute Oacute -55
+KPX Aacute Ocircumflex -55
+KPX Aacute Odieresis -55
+KPX Aacute Ograve -55
+KPX Aacute Ohungarumlaut -55
+KPX Aacute Omacron -55
+KPX Aacute Oslash -55
+KPX Aacute Otilde -55
+KPX Aacute Q -55
+KPX Aacute T -111
+KPX Aacute Tcaron -111
+KPX Aacute Tcommaaccent -111
+KPX Aacute U -55
+KPX Aacute Uacute -55
+KPX Aacute Ucircumflex -55
+KPX Aacute Udieresis -55
+KPX Aacute Ugrave -55
+KPX Aacute Uhungarumlaut -55
+KPX Aacute Umacron -55
+KPX Aacute Uogonek -55
+KPX Aacute Uring -55
+KPX Aacute V -135
+KPX Aacute W -90
+KPX Aacute Y -105
+KPX Aacute Yacute -105
+KPX Aacute Ydieresis -105
+KPX Aacute quoteright -111
+KPX Aacute v -74
+KPX Aacute w -92
+KPX Aacute y -92
+KPX Aacute yacute -92
+KPX Aacute ydieresis -92
+KPX Abreve C -40
+KPX Abreve Cacute -40
+KPX Abreve Ccaron -40
+KPX Abreve Ccedilla -40
+KPX Abreve G -40
+KPX Abreve Gbreve -40
+KPX Abreve Gcommaaccent -40
+KPX Abreve O -55
+KPX Abreve Oacute -55
+KPX Abreve Ocircumflex -55
+KPX Abreve Odieresis -55
+KPX Abreve Ograve -55
+KPX Abreve Ohungarumlaut -55
+KPX Abreve Omacron -55
+KPX Abreve Oslash -55
+KPX Abreve Otilde -55
+KPX Abreve Q -55
+KPX Abreve T -111
+KPX Abreve Tcaron -111
+KPX Abreve Tcommaaccent -111
+KPX Abreve U -55
+KPX Abreve Uacute -55
+KPX Abreve Ucircumflex -55
+KPX Abreve Udieresis -55
+KPX Abreve Ugrave -55
+KPX Abreve Uhungarumlaut -55
+KPX Abreve Umacron -55
+KPX Abreve Uogonek -55
+KPX Abreve Uring -55
+KPX Abreve V -135
+KPX Abreve W -90
+KPX Abreve Y -105
+KPX Abreve Yacute -105
+KPX Abreve Ydieresis -105
+KPX Abreve quoteright -111
+KPX Abreve v -74
+KPX Abreve w -92
+KPX Abreve y -92
+KPX Abreve yacute -92
+KPX Abreve ydieresis -92
+KPX Acircumflex C -40
+KPX Acircumflex Cacute -40
+KPX Acircumflex Ccaron -40
+KPX Acircumflex Ccedilla -40
+KPX Acircumflex G -40
+KPX Acircumflex Gbreve -40
+KPX Acircumflex Gcommaaccent -40
+KPX Acircumflex O -55
+KPX Acircumflex Oacute -55
+KPX Acircumflex Ocircumflex -55
+KPX Acircumflex Odieresis -55
+KPX Acircumflex Ograve -55
+KPX Acircumflex Ohungarumlaut -55
+KPX Acircumflex Omacron -55
+KPX Acircumflex Oslash -55
+KPX Acircumflex Otilde -55
+KPX Acircumflex Q -55
+KPX Acircumflex T -111
+KPX Acircumflex Tcaron -111
+KPX Acircumflex Tcommaaccent -111
+KPX Acircumflex U -55
+KPX Acircumflex Uacute -55
+KPX Acircumflex Ucircumflex -55
+KPX Acircumflex Udieresis -55
+KPX Acircumflex Ugrave -55
+KPX Acircumflex Uhungarumlaut -55
+KPX Acircumflex Umacron -55
+KPX Acircumflex Uogonek -55
+KPX Acircumflex Uring -55
+KPX Acircumflex V -135
+KPX Acircumflex W -90
+KPX Acircumflex Y -105
+KPX Acircumflex Yacute -105
+KPX Acircumflex Ydieresis -105
+KPX Acircumflex quoteright -111
+KPX Acircumflex v -74
+KPX Acircumflex w -92
+KPX Acircumflex y -92
+KPX Acircumflex yacute -92
+KPX Acircumflex ydieresis -92
+KPX Adieresis C -40
+KPX Adieresis Cacute -40
+KPX Adieresis Ccaron -40
+KPX Adieresis Ccedilla -40
+KPX Adieresis G -40
+KPX Adieresis Gbreve -40
+KPX Adieresis Gcommaaccent -40
+KPX Adieresis O -55
+KPX Adieresis Oacute -55
+KPX Adieresis Ocircumflex -55
+KPX Adieresis Odieresis -55
+KPX Adieresis Ograve -55
+KPX Adieresis Ohungarumlaut -55
+KPX Adieresis Omacron -55
+KPX Adieresis Oslash -55
+KPX Adieresis Otilde -55
+KPX Adieresis Q -55
+KPX Adieresis T -111
+KPX Adieresis Tcaron -111
+KPX Adieresis Tcommaaccent -111
+KPX Adieresis U -55
+KPX Adieresis Uacute -55
+KPX Adieresis Ucircumflex -55
+KPX Adieresis Udieresis -55
+KPX Adieresis Ugrave -55
+KPX Adieresis Uhungarumlaut -55
+KPX Adieresis Umacron -55
+KPX Adieresis Uogonek -55
+KPX Adieresis Uring -55
+KPX Adieresis V -135
+KPX Adieresis W -90
+KPX Adieresis Y -105
+KPX Adieresis Yacute -105
+KPX Adieresis Ydieresis -105
+KPX Adieresis quoteright -111
+KPX Adieresis v -74
+KPX Adieresis w -92
+KPX Adieresis y -92
+KPX Adieresis yacute -92
+KPX Adieresis ydieresis -92
+KPX Agrave C -40
+KPX Agrave Cacute -40
+KPX Agrave Ccaron -40
+KPX Agrave Ccedilla -40
+KPX Agrave G -40
+KPX Agrave Gbreve -40
+KPX Agrave Gcommaaccent -40
+KPX Agrave O -55
+KPX Agrave Oacute -55
+KPX Agrave Ocircumflex -55
+KPX Agrave Odieresis -55
+KPX Agrave Ograve -55
+KPX Agrave Ohungarumlaut -55
+KPX Agrave Omacron -55
+KPX Agrave Oslash -55
+KPX Agrave Otilde -55
+KPX Agrave Q -55
+KPX Agrave T -111
+KPX Agrave Tcaron -111
+KPX Agrave Tcommaaccent -111
+KPX Agrave U -55
+KPX Agrave Uacute -55
+KPX Agrave Ucircumflex -55
+KPX Agrave Udieresis -55
+KPX Agrave Ugrave -55
+KPX Agrave Uhungarumlaut -55
+KPX Agrave Umacron -55
+KPX Agrave Uogonek -55
+KPX Agrave Uring -55
+KPX Agrave V -135
+KPX Agrave W -90
+KPX Agrave Y -105
+KPX Agrave Yacute -105
+KPX Agrave Ydieresis -105
+KPX Agrave quoteright -111
+KPX Agrave v -74
+KPX Agrave w -92
+KPX Agrave y -92
+KPX Agrave yacute -92
+KPX Agrave ydieresis -92
+KPX Amacron C -40
+KPX Amacron Cacute -40
+KPX Amacron Ccaron -40
+KPX Amacron Ccedilla -40
+KPX Amacron G -40
+KPX Amacron Gbreve -40
+KPX Amacron Gcommaaccent -40
+KPX Amacron O -55
+KPX Amacron Oacute -55
+KPX Amacron Ocircumflex -55
+KPX Amacron Odieresis -55
+KPX Amacron Ograve -55
+KPX Amacron Ohungarumlaut -55
+KPX Amacron Omacron -55
+KPX Amacron Oslash -55
+KPX Amacron Otilde -55
+KPX Amacron Q -55
+KPX Amacron T -111
+KPX Amacron Tcaron -111
+KPX Amacron Tcommaaccent -111
+KPX Amacron U -55
+KPX Amacron Uacute -55
+KPX Amacron Ucircumflex -55
+KPX Amacron Udieresis -55
+KPX Amacron Ugrave -55
+KPX Amacron Uhungarumlaut -55
+KPX Amacron Umacron -55
+KPX Amacron Uogonek -55
+KPX Amacron Uring -55
+KPX Amacron V -135
+KPX Amacron W -90
+KPX Amacron Y -105
+KPX Amacron Yacute -105
+KPX Amacron Ydieresis -105
+KPX Amacron quoteright -111
+KPX Amacron v -74
+KPX Amacron w -92
+KPX Amacron y -92
+KPX Amacron yacute -92
+KPX Amacron ydieresis -92
+KPX Aogonek C -40
+KPX Aogonek Cacute -40
+KPX Aogonek Ccaron -40
+KPX Aogonek Ccedilla -40
+KPX Aogonek G -40
+KPX Aogonek Gbreve -40
+KPX Aogonek Gcommaaccent -40
+KPX Aogonek O -55
+KPX Aogonek Oacute -55
+KPX Aogonek Ocircumflex -55
+KPX Aogonek Odieresis -55
+KPX Aogonek Ograve -55
+KPX Aogonek Ohungarumlaut -55
+KPX Aogonek Omacron -55
+KPX Aogonek Oslash -55
+KPX Aogonek Otilde -55
+KPX Aogonek Q -55
+KPX Aogonek T -111
+KPX Aogonek Tcaron -111
+KPX Aogonek Tcommaaccent -111
+KPX Aogonek U -55
+KPX Aogonek Uacute -55
+KPX Aogonek Ucircumflex -55
+KPX Aogonek Udieresis -55
+KPX Aogonek Ugrave -55
+KPX Aogonek Uhungarumlaut -55
+KPX Aogonek Umacron -55
+KPX Aogonek Uogonek -55
+KPX Aogonek Uring -55
+KPX Aogonek V -135
+KPX Aogonek W -90
+KPX Aogonek Y -105
+KPX Aogonek Yacute -105
+KPX Aogonek Ydieresis -105
+KPX Aogonek quoteright -111
+KPX Aogonek v -74
+KPX Aogonek w -52
+KPX Aogonek y -52
+KPX Aogonek yacute -52
+KPX Aogonek ydieresis -52
+KPX Aring C -40
+KPX Aring Cacute -40
+KPX Aring Ccaron -40
+KPX Aring Ccedilla -40
+KPX Aring G -40
+KPX Aring Gbreve -40
+KPX Aring Gcommaaccent -40
+KPX Aring O -55
+KPX Aring Oacute -55
+KPX Aring Ocircumflex -55
+KPX Aring Odieresis -55
+KPX Aring Ograve -55
+KPX Aring Ohungarumlaut -55
+KPX Aring Omacron -55
+KPX Aring Oslash -55
+KPX Aring Otilde -55
+KPX Aring Q -55
+KPX Aring T -111
+KPX Aring Tcaron -111
+KPX Aring Tcommaaccent -111
+KPX Aring U -55
+KPX Aring Uacute -55
+KPX Aring Ucircumflex -55
+KPX Aring Udieresis -55
+KPX Aring Ugrave -55
+KPX Aring Uhungarumlaut -55
+KPX Aring Umacron -55
+KPX Aring Uogonek -55
+KPX Aring Uring -55
+KPX Aring V -135
+KPX Aring W -90
+KPX Aring Y -105
+KPX Aring Yacute -105
+KPX Aring Ydieresis -105
+KPX Aring quoteright -111
+KPX Aring v -74
+KPX Aring w -92
+KPX Aring y -92
+KPX Aring yacute -92
+KPX Aring ydieresis -92
+KPX Atilde C -40
+KPX Atilde Cacute -40
+KPX Atilde Ccaron -40
+KPX Atilde Ccedilla -40
+KPX Atilde G -40
+KPX Atilde Gbreve -40
+KPX Atilde Gcommaaccent -40
+KPX Atilde O -55
+KPX Atilde Oacute -55
+KPX Atilde Ocircumflex -55
+KPX Atilde Odieresis -55
+KPX Atilde Ograve -55
+KPX Atilde Ohungarumlaut -55
+KPX Atilde Omacron -55
+KPX Atilde Oslash -55
+KPX Atilde Otilde -55
+KPX Atilde Q -55
+KPX Atilde T -111
+KPX Atilde Tcaron -111
+KPX Atilde Tcommaaccent -111
+KPX Atilde U -55
+KPX Atilde Uacute -55
+KPX Atilde Ucircumflex -55
+KPX Atilde Udieresis -55
+KPX Atilde Ugrave -55
+KPX Atilde Uhungarumlaut -55
+KPX Atilde Umacron -55
+KPX Atilde Uogonek -55
+KPX Atilde Uring -55
+KPX Atilde V -135
+KPX Atilde W -90
+KPX Atilde Y -105
+KPX Atilde Yacute -105
+KPX Atilde Ydieresis -105
+KPX Atilde quoteright -111
+KPX Atilde v -74
+KPX Atilde w -92
+KPX Atilde y -92
+KPX Atilde yacute -92
+KPX Atilde ydieresis -92
+KPX B A -35
+KPX B Aacute -35
+KPX B Abreve -35
+KPX B Acircumflex -35
+KPX B Adieresis -35
+KPX B Agrave -35
+KPX B Amacron -35
+KPX B Aogonek -35
+KPX B Aring -35
+KPX B Atilde -35
+KPX B U -10
+KPX B Uacute -10
+KPX B Ucircumflex -10
+KPX B Udieresis -10
+KPX B Ugrave -10
+KPX B Uhungarumlaut -10
+KPX B Umacron -10
+KPX B Uogonek -10
+KPX B Uring -10
+KPX D A -40
+KPX D Aacute -40
+KPX D Abreve -40
+KPX D Acircumflex -40
+KPX D Adieresis -40
+KPX D Agrave -40
+KPX D Amacron -40
+KPX D Aogonek -40
+KPX D Aring -40
+KPX D Atilde -40
+KPX D V -40
+KPX D W -30
+KPX D Y -55
+KPX D Yacute -55
+KPX D Ydieresis -55
+KPX Dcaron A -40
+KPX Dcaron Aacute -40
+KPX Dcaron Abreve -40
+KPX Dcaron Acircumflex -40
+KPX Dcaron Adieresis -40
+KPX Dcaron Agrave -40
+KPX Dcaron Amacron -40
+KPX Dcaron Aogonek -40
+KPX Dcaron Aring -40
+KPX Dcaron Atilde -40
+KPX Dcaron V -40
+KPX Dcaron W -30
+KPX Dcaron Y -55
+KPX Dcaron Yacute -55
+KPX Dcaron Ydieresis -55
+KPX Dcroat A -40
+KPX Dcroat Aacute -40
+KPX Dcroat Abreve -40
+KPX Dcroat Acircumflex -40
+KPX Dcroat Adieresis -40
+KPX Dcroat Agrave -40
+KPX Dcroat Amacron -40
+KPX Dcroat Aogonek -40
+KPX Dcroat Aring -40
+KPX Dcroat Atilde -40
+KPX Dcroat V -40
+KPX Dcroat W -30
+KPX Dcroat Y -55
+KPX Dcroat Yacute -55
+KPX Dcroat Ydieresis -55
+KPX F A -74
+KPX F Aacute -74
+KPX F Abreve -74
+KPX F Acircumflex -74
+KPX F Adieresis -74
+KPX F Agrave -74
+KPX F Amacron -74
+KPX F Aogonek -74
+KPX F Aring -74
+KPX F Atilde -74
+KPX F a -15
+KPX F aacute -15
+KPX F abreve -15
+KPX F acircumflex -15
+KPX F adieresis -15
+KPX F agrave -15
+KPX F amacron -15
+KPX F aogonek -15
+KPX F aring -15
+KPX F atilde -15
+KPX F comma -80
+KPX F o -15
+KPX F oacute -15
+KPX F ocircumflex -15
+KPX F odieresis -15
+KPX F ograve -15
+KPX F ohungarumlaut -15
+KPX F omacron -15
+KPX F oslash -15
+KPX F otilde -15
+KPX F period -80
+KPX J A -60
+KPX J Aacute -60
+KPX J Abreve -60
+KPX J Acircumflex -60
+KPX J Adieresis -60
+KPX J Agrave -60
+KPX J Amacron -60
+KPX J Aogonek -60
+KPX J Aring -60
+KPX J Atilde -60
+KPX K O -30
+KPX K Oacute -30
+KPX K Ocircumflex -30
+KPX K Odieresis -30
+KPX K Ograve -30
+KPX K Ohungarumlaut -30
+KPX K Omacron -30
+KPX K Oslash -30
+KPX K Otilde -30
+KPX K e -25
+KPX K eacute -25
+KPX K ecaron -25
+KPX K ecircumflex -25
+KPX K edieresis -25
+KPX K edotaccent -25
+KPX K egrave -25
+KPX K emacron -25
+KPX K eogonek -25
+KPX K o -35
+KPX K oacute -35
+KPX K ocircumflex -35
+KPX K odieresis -35
+KPX K ograve -35
+KPX K ohungarumlaut -35
+KPX K omacron -35
+KPX K oslash -35
+KPX K otilde -35
+KPX K u -15
+KPX K uacute -15
+KPX K ucircumflex -15
+KPX K udieresis -15
+KPX K ugrave -15
+KPX K uhungarumlaut -15
+KPX K umacron -15
+KPX K uogonek -15
+KPX K uring -15
+KPX K y -25
+KPX K yacute -25
+KPX K ydieresis -25
+KPX Kcommaaccent O -30
+KPX Kcommaaccent Oacute -30
+KPX Kcommaaccent Ocircumflex -30
+KPX Kcommaaccent Odieresis -30
+KPX Kcommaaccent Ograve -30
+KPX Kcommaaccent Ohungarumlaut -30
+KPX Kcommaaccent Omacron -30
+KPX Kcommaaccent Oslash -30
+KPX Kcommaaccent Otilde -30
+KPX Kcommaaccent e -25
+KPX Kcommaaccent eacute -25
+KPX Kcommaaccent ecaron -25
+KPX Kcommaaccent ecircumflex -25
+KPX Kcommaaccent edieresis -25
+KPX Kcommaaccent edotaccent -25
+KPX Kcommaaccent egrave -25
+KPX Kcommaaccent emacron -25
+KPX Kcommaaccent eogonek -25
+KPX Kcommaaccent o -35
+KPX Kcommaaccent oacute -35
+KPX Kcommaaccent ocircumflex -35
+KPX Kcommaaccent odieresis -35
+KPX Kcommaaccent ograve -35
+KPX Kcommaaccent ohungarumlaut -35
+KPX Kcommaaccent omacron -35
+KPX Kcommaaccent oslash -35
+KPX Kcommaaccent otilde -35
+KPX Kcommaaccent u -15
+KPX Kcommaaccent uacute -15
+KPX Kcommaaccent ucircumflex -15
+KPX Kcommaaccent udieresis -15
+KPX Kcommaaccent ugrave -15
+KPX Kcommaaccent uhungarumlaut -15
+KPX Kcommaaccent umacron -15
+KPX Kcommaaccent uogonek -15
+KPX Kcommaaccent uring -15
+KPX Kcommaaccent y -25
+KPX Kcommaaccent yacute -25
+KPX Kcommaaccent ydieresis -25
+KPX L T -92
+KPX L Tcaron -92
+KPX L Tcommaaccent -92
+KPX L V -100
+KPX L W -74
+KPX L Y -100
+KPX L Yacute -100
+KPX L Ydieresis -100
+KPX L quoteright -92
+KPX L y -55
+KPX L yacute -55
+KPX L ydieresis -55
+KPX Lacute T -92
+KPX Lacute Tcaron -92
+KPX Lacute Tcommaaccent -92
+KPX Lacute V -100
+KPX Lacute W -74
+KPX Lacute Y -100
+KPX Lacute Yacute -100
+KPX Lacute Ydieresis -100
+KPX Lacute quoteright -92
+KPX Lacute y -55
+KPX Lacute yacute -55
+KPX Lacute ydieresis -55
+KPX Lcaron quoteright -92
+KPX Lcaron y -55
+KPX Lcaron yacute -55
+KPX Lcaron ydieresis -55
+KPX Lcommaaccent T -92
+KPX Lcommaaccent Tcaron -92
+KPX Lcommaaccent Tcommaaccent -92
+KPX Lcommaaccent V -100
+KPX Lcommaaccent W -74
+KPX Lcommaaccent Y -100
+KPX Lcommaaccent Yacute -100
+KPX Lcommaaccent Ydieresis -100
+KPX Lcommaaccent quoteright -92
+KPX Lcommaaccent y -55
+KPX Lcommaaccent yacute -55
+KPX Lcommaaccent ydieresis -55
+KPX Lslash T -92
+KPX Lslash Tcaron -92
+KPX Lslash Tcommaaccent -92
+KPX Lslash V -100
+KPX Lslash W -74
+KPX Lslash Y -100
+KPX Lslash Yacute -100
+KPX Lslash Ydieresis -100
+KPX Lslash quoteright -92
+KPX Lslash y -55
+KPX Lslash yacute -55
+KPX Lslash ydieresis -55
+KPX N A -35
+KPX N Aacute -35
+KPX N Abreve -35
+KPX N Acircumflex -35
+KPX N Adieresis -35
+KPX N Agrave -35
+KPX N Amacron -35
+KPX N Aogonek -35
+KPX N Aring -35
+KPX N Atilde -35
+KPX Nacute A -35
+KPX Nacute Aacute -35
+KPX Nacute Abreve -35
+KPX Nacute Acircumflex -35
+KPX Nacute Adieresis -35
+KPX Nacute Agrave -35
+KPX Nacute Amacron -35
+KPX Nacute Aogonek -35
+KPX Nacute Aring -35
+KPX Nacute Atilde -35
+KPX Ncaron A -35
+KPX Ncaron Aacute -35
+KPX Ncaron Abreve -35
+KPX Ncaron Acircumflex -35
+KPX Ncaron Adieresis -35
+KPX Ncaron Agrave -35
+KPX Ncaron Amacron -35
+KPX Ncaron Aogonek -35
+KPX Ncaron Aring -35
+KPX Ncaron Atilde -35
+KPX Ncommaaccent A -35
+KPX Ncommaaccent Aacute -35
+KPX Ncommaaccent Abreve -35
+KPX Ncommaaccent Acircumflex -35
+KPX Ncommaaccent Adieresis -35
+KPX Ncommaaccent Agrave -35
+KPX Ncommaaccent Amacron -35
+KPX Ncommaaccent Aogonek -35
+KPX Ncommaaccent Aring -35
+KPX Ncommaaccent Atilde -35
+KPX Ntilde A -35
+KPX Ntilde Aacute -35
+KPX Ntilde Abreve -35
+KPX Ntilde Acircumflex -35
+KPX Ntilde Adieresis -35
+KPX Ntilde Agrave -35
+KPX Ntilde Amacron -35
+KPX Ntilde Aogonek -35
+KPX Ntilde Aring -35
+KPX Ntilde Atilde -35
+KPX O A -35
+KPX O Aacute -35
+KPX O Abreve -35
+KPX O Acircumflex -35
+KPX O Adieresis -35
+KPX O Agrave -35
+KPX O Amacron -35
+KPX O Aogonek -35
+KPX O Aring -35
+KPX O Atilde -35
+KPX O T -40
+KPX O Tcaron -40
+KPX O Tcommaaccent -40
+KPX O V -50
+KPX O W -35
+KPX O X -40
+KPX O Y -50
+KPX O Yacute -50
+KPX O Ydieresis -50
+KPX Oacute A -35
+KPX Oacute Aacute -35
+KPX Oacute Abreve -35
+KPX Oacute Acircumflex -35
+KPX Oacute Adieresis -35
+KPX Oacute Agrave -35
+KPX Oacute Amacron -35
+KPX Oacute Aogonek -35
+KPX Oacute Aring -35
+KPX Oacute Atilde -35
+KPX Oacute T -40
+KPX Oacute Tcaron -40
+KPX Oacute Tcommaaccent -40
+KPX Oacute V -50
+KPX Oacute W -35
+KPX Oacute X -40
+KPX Oacute Y -50
+KPX Oacute Yacute -50
+KPX Oacute Ydieresis -50
+KPX Ocircumflex A -35
+KPX Ocircumflex Aacute -35
+KPX Ocircumflex Abreve -35
+KPX Ocircumflex Acircumflex -35
+KPX Ocircumflex Adieresis -35
+KPX Ocircumflex Agrave -35
+KPX Ocircumflex Amacron -35
+KPX Ocircumflex Aogonek -35
+KPX Ocircumflex Aring -35
+KPX Ocircumflex Atilde -35
+KPX Ocircumflex T -40
+KPX Ocircumflex Tcaron -40
+KPX Ocircumflex Tcommaaccent -40
+KPX Ocircumflex V -50
+KPX Ocircumflex W -35
+KPX Ocircumflex X -40
+KPX Ocircumflex Y -50
+KPX Ocircumflex Yacute -50
+KPX Ocircumflex Ydieresis -50
+KPX Odieresis A -35
+KPX Odieresis Aacute -35
+KPX Odieresis Abreve -35
+KPX Odieresis Acircumflex -35
+KPX Odieresis Adieresis -35
+KPX Odieresis Agrave -35
+KPX Odieresis Amacron -35
+KPX Odieresis Aogonek -35
+KPX Odieresis Aring -35
+KPX Odieresis Atilde -35
+KPX Odieresis T -40
+KPX Odieresis Tcaron -40
+KPX Odieresis Tcommaaccent -40
+KPX Odieresis V -50
+KPX Odieresis W -35
+KPX Odieresis X -40
+KPX Odieresis Y -50
+KPX Odieresis Yacute -50
+KPX Odieresis Ydieresis -50
+KPX Ograve A -35
+KPX Ograve Aacute -35
+KPX Ograve Abreve -35
+KPX Ograve Acircumflex -35
+KPX Ograve Adieresis -35
+KPX Ograve Agrave -35
+KPX Ograve Amacron -35
+KPX Ograve Aogonek -35
+KPX Ograve Aring -35
+KPX Ograve Atilde -35
+KPX Ograve T -40
+KPX Ograve Tcaron -40
+KPX Ograve Tcommaaccent -40
+KPX Ograve V -50
+KPX Ograve W -35
+KPX Ograve X -40
+KPX Ograve Y -50
+KPX Ograve Yacute -50
+KPX Ograve Ydieresis -50
+KPX Ohungarumlaut A -35
+KPX Ohungarumlaut Aacute -35
+KPX Ohungarumlaut Abreve -35
+KPX Ohungarumlaut Acircumflex -35
+KPX Ohungarumlaut Adieresis -35
+KPX Ohungarumlaut Agrave -35
+KPX Ohungarumlaut Amacron -35
+KPX Ohungarumlaut Aogonek -35
+KPX Ohungarumlaut Aring -35
+KPX Ohungarumlaut Atilde -35
+KPX Ohungarumlaut T -40
+KPX Ohungarumlaut Tcaron -40
+KPX Ohungarumlaut Tcommaaccent -40
+KPX Ohungarumlaut V -50
+KPX Ohungarumlaut W -35
+KPX Ohungarumlaut X -40
+KPX Ohungarumlaut Y -50
+KPX Ohungarumlaut Yacute -50
+KPX Ohungarumlaut Ydieresis -50
+KPX Omacron A -35
+KPX Omacron Aacute -35
+KPX Omacron Abreve -35
+KPX Omacron Acircumflex -35
+KPX Omacron Adieresis -35
+KPX Omacron Agrave -35
+KPX Omacron Amacron -35
+KPX Omacron Aogonek -35
+KPX Omacron Aring -35
+KPX Omacron Atilde -35
+KPX Omacron T -40
+KPX Omacron Tcaron -40
+KPX Omacron Tcommaaccent -40
+KPX Omacron V -50
+KPX Omacron W -35
+KPX Omacron X -40
+KPX Omacron Y -50
+KPX Omacron Yacute -50
+KPX Omacron Ydieresis -50
+KPX Oslash A -35
+KPX Oslash Aacute -35
+KPX Oslash Abreve -35
+KPX Oslash Acircumflex -35
+KPX Oslash Adieresis -35
+KPX Oslash Agrave -35
+KPX Oslash Amacron -35
+KPX Oslash Aogonek -35
+KPX Oslash Aring -35
+KPX Oslash Atilde -35
+KPX Oslash T -40
+KPX Oslash Tcaron -40
+KPX Oslash Tcommaaccent -40
+KPX Oslash V -50
+KPX Oslash W -35
+KPX Oslash X -40
+KPX Oslash Y -50
+KPX Oslash Yacute -50
+KPX Oslash Ydieresis -50
+KPX Otilde A -35
+KPX Otilde Aacute -35
+KPX Otilde Abreve -35
+KPX Otilde Acircumflex -35
+KPX Otilde Adieresis -35
+KPX Otilde Agrave -35
+KPX Otilde Amacron -35
+KPX Otilde Aogonek -35
+KPX Otilde Aring -35
+KPX Otilde Atilde -35
+KPX Otilde T -40
+KPX Otilde Tcaron -40
+KPX Otilde Tcommaaccent -40
+KPX Otilde V -50
+KPX Otilde W -35
+KPX Otilde X -40
+KPX Otilde Y -50
+KPX Otilde Yacute -50
+KPX Otilde Ydieresis -50
+KPX P A -92
+KPX P Aacute -92
+KPX P Abreve -92
+KPX P Acircumflex -92
+KPX P Adieresis -92
+KPX P Agrave -92
+KPX P Amacron -92
+KPX P Aogonek -92
+KPX P Aring -92
+KPX P Atilde -92
+KPX P a -15
+KPX P aacute -15
+KPX P abreve -15
+KPX P acircumflex -15
+KPX P adieresis -15
+KPX P agrave -15
+KPX P amacron -15
+KPX P aogonek -15
+KPX P aring -15
+KPX P atilde -15
+KPX P comma -111
+KPX P period -111
+KPX Q U -10
+KPX Q Uacute -10
+KPX Q Ucircumflex -10
+KPX Q Udieresis -10
+KPX Q Ugrave -10
+KPX Q Uhungarumlaut -10
+KPX Q Umacron -10
+KPX Q Uogonek -10
+KPX Q Uring -10
+KPX R O -40
+KPX R Oacute -40
+KPX R Ocircumflex -40
+KPX R Odieresis -40
+KPX R Ograve -40
+KPX R Ohungarumlaut -40
+KPX R Omacron -40
+KPX R Oslash -40
+KPX R Otilde -40
+KPX R T -60
+KPX R Tcaron -60
+KPX R Tcommaaccent -60
+KPX R U -40
+KPX R Uacute -40
+KPX R Ucircumflex -40
+KPX R Udieresis -40
+KPX R Ugrave -40
+KPX R Uhungarumlaut -40
+KPX R Umacron -40
+KPX R Uogonek -40
+KPX R Uring -40
+KPX R V -80
+KPX R W -55
+KPX R Y -65
+KPX R Yacute -65
+KPX R Ydieresis -65
+KPX Racute O -40
+KPX Racute Oacute -40
+KPX Racute Ocircumflex -40
+KPX Racute Odieresis -40
+KPX Racute Ograve -40
+KPX Racute Ohungarumlaut -40
+KPX Racute Omacron -40
+KPX Racute Oslash -40
+KPX Racute Otilde -40
+KPX Racute T -60
+KPX Racute Tcaron -60
+KPX Racute Tcommaaccent -60
+KPX Racute U -40
+KPX Racute Uacute -40
+KPX Racute Ucircumflex -40
+KPX Racute Udieresis -40
+KPX Racute Ugrave -40
+KPX Racute Uhungarumlaut -40
+KPX Racute Umacron -40
+KPX Racute Uogonek -40
+KPX Racute Uring -40
+KPX Racute V -80
+KPX Racute W -55
+KPX Racute Y -65
+KPX Racute Yacute -65
+KPX Racute Ydieresis -65
+KPX Rcaron O -40
+KPX Rcaron Oacute -40
+KPX Rcaron Ocircumflex -40
+KPX Rcaron Odieresis -40
+KPX Rcaron Ograve -40
+KPX Rcaron Ohungarumlaut -40
+KPX Rcaron Omacron -40
+KPX Rcaron Oslash -40
+KPX Rcaron Otilde -40
+KPX Rcaron T -60
+KPX Rcaron Tcaron -60
+KPX Rcaron Tcommaaccent -60
+KPX Rcaron U -40
+KPX Rcaron Uacute -40
+KPX Rcaron Ucircumflex -40
+KPX Rcaron Udieresis -40
+KPX Rcaron Ugrave -40
+KPX Rcaron Uhungarumlaut -40
+KPX Rcaron Umacron -40
+KPX Rcaron Uogonek -40
+KPX Rcaron Uring -40
+KPX Rcaron V -80
+KPX Rcaron W -55
+KPX Rcaron Y -65
+KPX Rcaron Yacute -65
+KPX Rcaron Ydieresis -65
+KPX Rcommaaccent O -40
+KPX Rcommaaccent Oacute -40
+KPX Rcommaaccent Ocircumflex -40
+KPX Rcommaaccent Odieresis -40
+KPX Rcommaaccent Ograve -40
+KPX Rcommaaccent Ohungarumlaut -40
+KPX Rcommaaccent Omacron -40
+KPX Rcommaaccent Oslash -40
+KPX Rcommaaccent Otilde -40
+KPX Rcommaaccent T -60
+KPX Rcommaaccent Tcaron -60
+KPX Rcommaaccent Tcommaaccent -60
+KPX Rcommaaccent U -40
+KPX Rcommaaccent Uacute -40
+KPX Rcommaaccent Ucircumflex -40
+KPX Rcommaaccent Udieresis -40
+KPX Rcommaaccent Ugrave -40
+KPX Rcommaaccent Uhungarumlaut -40
+KPX Rcommaaccent Umacron -40
+KPX Rcommaaccent Uogonek -40
+KPX Rcommaaccent Uring -40
+KPX Rcommaaccent V -80
+KPX Rcommaaccent W -55
+KPX Rcommaaccent Y -65
+KPX Rcommaaccent Yacute -65
+KPX Rcommaaccent Ydieresis -65
+KPX T A -93
+KPX T Aacute -93
+KPX T Abreve -93
+KPX T Acircumflex -93
+KPX T Adieresis -93
+KPX T Agrave -93
+KPX T Amacron -93
+KPX T Aogonek -93
+KPX T Aring -93
+KPX T Atilde -93
+KPX T O -18
+KPX T Oacute -18
+KPX T Ocircumflex -18
+KPX T Odieresis -18
+KPX T Ograve -18
+KPX T Ohungarumlaut -18
+KPX T Omacron -18
+KPX T Oslash -18
+KPX T Otilde -18
+KPX T a -80
+KPX T aacute -80
+KPX T abreve -80
+KPX T acircumflex -80
+KPX T adieresis -40
+KPX T agrave -40
+KPX T amacron -40
+KPX T aogonek -80
+KPX T aring -80
+KPX T atilde -40
+KPX T colon -50
+KPX T comma -74
+KPX T e -70
+KPX T eacute -70
+KPX T ecaron -70
+KPX T ecircumflex -70
+KPX T edieresis -30
+KPX T edotaccent -70
+KPX T egrave -70
+KPX T emacron -30
+KPX T eogonek -70
+KPX T hyphen -92
+KPX T i -35
+KPX T iacute -35
+KPX T iogonek -35
+KPX T o -80
+KPX T oacute -80
+KPX T ocircumflex -80
+KPX T odieresis -80
+KPX T ograve -80
+KPX T ohungarumlaut -80
+KPX T omacron -80
+KPX T oslash -80
+KPX T otilde -80
+KPX T period -74
+KPX T r -35
+KPX T racute -35
+KPX T rcaron -35
+KPX T rcommaaccent -35
+KPX T semicolon -55
+KPX T u -45
+KPX T uacute -45
+KPX T ucircumflex -45
+KPX T udieresis -45
+KPX T ugrave -45
+KPX T uhungarumlaut -45
+KPX T umacron -45
+KPX T uogonek -45
+KPX T uring -45
+KPX T w -80
+KPX T y -80
+KPX T yacute -80
+KPX T ydieresis -80
+KPX Tcaron A -93
+KPX Tcaron Aacute -93
+KPX Tcaron Abreve -93
+KPX Tcaron Acircumflex -93
+KPX Tcaron Adieresis -93
+KPX Tcaron Agrave -93
+KPX Tcaron Amacron -93
+KPX Tcaron Aogonek -93
+KPX Tcaron Aring -93
+KPX Tcaron Atilde -93
+KPX Tcaron O -18
+KPX Tcaron Oacute -18
+KPX Tcaron Ocircumflex -18
+KPX Tcaron Odieresis -18
+KPX Tcaron Ograve -18
+KPX Tcaron Ohungarumlaut -18
+KPX Tcaron Omacron -18
+KPX Tcaron Oslash -18
+KPX Tcaron Otilde -18
+KPX Tcaron a -80
+KPX Tcaron aacute -80
+KPX Tcaron abreve -80
+KPX Tcaron acircumflex -80
+KPX Tcaron adieresis -40
+KPX Tcaron agrave -40
+KPX Tcaron amacron -40
+KPX Tcaron aogonek -80
+KPX Tcaron aring -80
+KPX Tcaron atilde -40
+KPX Tcaron colon -50
+KPX Tcaron comma -74
+KPX Tcaron e -70
+KPX Tcaron eacute -70
+KPX Tcaron ecaron -70
+KPX Tcaron ecircumflex -30
+KPX Tcaron edieresis -30
+KPX Tcaron edotaccent -70
+KPX Tcaron egrave -70
+KPX Tcaron emacron -30
+KPX Tcaron eogonek -70
+KPX Tcaron hyphen -92
+KPX Tcaron i -35
+KPX Tcaron iacute -35
+KPX Tcaron iogonek -35
+KPX Tcaron o -80
+KPX Tcaron oacute -80
+KPX Tcaron ocircumflex -80
+KPX Tcaron odieresis -80
+KPX Tcaron ograve -80
+KPX Tcaron ohungarumlaut -80
+KPX Tcaron omacron -80
+KPX Tcaron oslash -80
+KPX Tcaron otilde -80
+KPX Tcaron period -74
+KPX Tcaron r -35
+KPX Tcaron racute -35
+KPX Tcaron rcaron -35
+KPX Tcaron rcommaaccent -35
+KPX Tcaron semicolon -55
+KPX Tcaron u -45
+KPX Tcaron uacute -45
+KPX Tcaron ucircumflex -45
+KPX Tcaron udieresis -45
+KPX Tcaron ugrave -45
+KPX Tcaron uhungarumlaut -45
+KPX Tcaron umacron -45
+KPX Tcaron uogonek -45
+KPX Tcaron uring -45
+KPX Tcaron w -80
+KPX Tcaron y -80
+KPX Tcaron yacute -80
+KPX Tcaron ydieresis -80
+KPX Tcommaaccent A -93
+KPX Tcommaaccent Aacute -93
+KPX Tcommaaccent Abreve -93
+KPX Tcommaaccent Acircumflex -93
+KPX Tcommaaccent Adieresis -93
+KPX Tcommaaccent Agrave -93
+KPX Tcommaaccent Amacron -93
+KPX Tcommaaccent Aogonek -93
+KPX Tcommaaccent Aring -93
+KPX Tcommaaccent Atilde -93
+KPX Tcommaaccent O -18
+KPX Tcommaaccent Oacute -18
+KPX Tcommaaccent Ocircumflex -18
+KPX Tcommaaccent Odieresis -18
+KPX Tcommaaccent Ograve -18
+KPX Tcommaaccent Ohungarumlaut -18
+KPX Tcommaaccent Omacron -18
+KPX Tcommaaccent Oslash -18
+KPX Tcommaaccent Otilde -18
+KPX Tcommaaccent a -80
+KPX Tcommaaccent aacute -80
+KPX Tcommaaccent abreve -80
+KPX Tcommaaccent acircumflex -80
+KPX Tcommaaccent adieresis -40
+KPX Tcommaaccent agrave -40
+KPX Tcommaaccent amacron -40
+KPX Tcommaaccent aogonek -80
+KPX Tcommaaccent aring -80
+KPX Tcommaaccent atilde -40
+KPX Tcommaaccent colon -50
+KPX Tcommaaccent comma -74
+KPX Tcommaaccent e -70
+KPX Tcommaaccent eacute -70
+KPX Tcommaaccent ecaron -70
+KPX Tcommaaccent ecircumflex -30
+KPX Tcommaaccent edieresis -30
+KPX Tcommaaccent edotaccent -70
+KPX Tcommaaccent egrave -30
+KPX Tcommaaccent emacron -70
+KPX Tcommaaccent eogonek -70
+KPX Tcommaaccent hyphen -92
+KPX Tcommaaccent i -35
+KPX Tcommaaccent iacute -35
+KPX Tcommaaccent iogonek -35
+KPX Tcommaaccent o -80
+KPX Tcommaaccent oacute -80
+KPX Tcommaaccent ocircumflex -80
+KPX Tcommaaccent odieresis -80
+KPX Tcommaaccent ograve -80
+KPX Tcommaaccent ohungarumlaut -80
+KPX Tcommaaccent omacron -80
+KPX Tcommaaccent oslash -80
+KPX Tcommaaccent otilde -80
+KPX Tcommaaccent period -74
+KPX Tcommaaccent r -35
+KPX Tcommaaccent racute -35
+KPX Tcommaaccent rcaron -35
+KPX Tcommaaccent rcommaaccent -35
+KPX Tcommaaccent semicolon -55
+KPX Tcommaaccent u -45
+KPX Tcommaaccent uacute -45
+KPX Tcommaaccent ucircumflex -45
+KPX Tcommaaccent udieresis -45
+KPX Tcommaaccent ugrave -45
+KPX Tcommaaccent uhungarumlaut -45
+KPX Tcommaaccent umacron -45
+KPX Tcommaaccent uogonek -45
+KPX Tcommaaccent uring -45
+KPX Tcommaaccent w -80
+KPX Tcommaaccent y -80
+KPX Tcommaaccent yacute -80
+KPX Tcommaaccent ydieresis -80
+KPX U A -40
+KPX U Aacute -40
+KPX U Abreve -40
+KPX U Acircumflex -40
+KPX U Adieresis -40
+KPX U Agrave -40
+KPX U Amacron -40
+KPX U Aogonek -40
+KPX U Aring -40
+KPX U Atilde -40
+KPX Uacute A -40
+KPX Uacute Aacute -40
+KPX Uacute Abreve -40
+KPX Uacute Acircumflex -40
+KPX Uacute Adieresis -40
+KPX Uacute Agrave -40
+KPX Uacute Amacron -40
+KPX Uacute Aogonek -40
+KPX Uacute Aring -40
+KPX Uacute Atilde -40
+KPX Ucircumflex A -40
+KPX Ucircumflex Aacute -40
+KPX Ucircumflex Abreve -40
+KPX Ucircumflex Acircumflex -40
+KPX Ucircumflex Adieresis -40
+KPX Ucircumflex Agrave -40
+KPX Ucircumflex Amacron -40
+KPX Ucircumflex Aogonek -40
+KPX Ucircumflex Aring -40
+KPX Ucircumflex Atilde -40
+KPX Udieresis A -40
+KPX Udieresis Aacute -40
+KPX Udieresis Abreve -40
+KPX Udieresis Acircumflex -40
+KPX Udieresis Adieresis -40
+KPX Udieresis Agrave -40
+KPX Udieresis Amacron -40
+KPX Udieresis Aogonek -40
+KPX Udieresis Aring -40
+KPX Udieresis Atilde -40
+KPX Ugrave A -40
+KPX Ugrave Aacute -40
+KPX Ugrave Abreve -40
+KPX Ugrave Acircumflex -40
+KPX Ugrave Adieresis -40
+KPX Ugrave Agrave -40
+KPX Ugrave Amacron -40
+KPX Ugrave Aogonek -40
+KPX Ugrave Aring -40
+KPX Ugrave Atilde -40
+KPX Uhungarumlaut A -40
+KPX Uhungarumlaut Aacute -40
+KPX Uhungarumlaut Abreve -40
+KPX Uhungarumlaut Acircumflex -40
+KPX Uhungarumlaut Adieresis -40
+KPX Uhungarumlaut Agrave -40
+KPX Uhungarumlaut Amacron -40
+KPX Uhungarumlaut Aogonek -40
+KPX Uhungarumlaut Aring -40
+KPX Uhungarumlaut Atilde -40
+KPX Umacron A -40
+KPX Umacron Aacute -40
+KPX Umacron Abreve -40
+KPX Umacron Acircumflex -40
+KPX Umacron Adieresis -40
+KPX Umacron Agrave -40
+KPX Umacron Amacron -40
+KPX Umacron Aogonek -40
+KPX Umacron Aring -40
+KPX Umacron Atilde -40
+KPX Uogonek A -40
+KPX Uogonek Aacute -40
+KPX Uogonek Abreve -40
+KPX Uogonek Acircumflex -40
+KPX Uogonek Adieresis -40
+KPX Uogonek Agrave -40
+KPX Uogonek Amacron -40
+KPX Uogonek Aogonek -40
+KPX Uogonek Aring -40
+KPX Uogonek Atilde -40
+KPX Uring A -40
+KPX Uring Aacute -40
+KPX Uring Abreve -40
+KPX Uring Acircumflex -40
+KPX Uring Adieresis -40
+KPX Uring Agrave -40
+KPX Uring Amacron -40
+KPX Uring Aogonek -40
+KPX Uring Aring -40
+KPX Uring Atilde -40
+KPX V A -135
+KPX V Aacute -135
+KPX V Abreve -135
+KPX V Acircumflex -135
+KPX V Adieresis -135
+KPX V Agrave -135
+KPX V Amacron -135
+KPX V Aogonek -135
+KPX V Aring -135
+KPX V Atilde -135
+KPX V G -15
+KPX V Gbreve -15
+KPX V Gcommaaccent -15
+KPX V O -40
+KPX V Oacute -40
+KPX V Ocircumflex -40
+KPX V Odieresis -40
+KPX V Ograve -40
+KPX V Ohungarumlaut -40
+KPX V Omacron -40
+KPX V Oslash -40
+KPX V Otilde -40
+KPX V a -111
+KPX V aacute -111
+KPX V abreve -111
+KPX V acircumflex -71
+KPX V adieresis -71
+KPX V agrave -71
+KPX V amacron -71
+KPX V aogonek -111
+KPX V aring -111
+KPX V atilde -71
+KPX V colon -74
+KPX V comma -129
+KPX V e -111
+KPX V eacute -111
+KPX V ecaron -71
+KPX V ecircumflex -71
+KPX V edieresis -71
+KPX V edotaccent -111
+KPX V egrave -71
+KPX V emacron -71
+KPX V eogonek -111
+KPX V hyphen -100
+KPX V i -60
+KPX V iacute -60
+KPX V icircumflex -20
+KPX V idieresis -20
+KPX V igrave -20
+KPX V imacron -20
+KPX V iogonek -60
+KPX V o -129
+KPX V oacute -129
+KPX V ocircumflex -129
+KPX V odieresis -89
+KPX V ograve -89
+KPX V ohungarumlaut -129
+KPX V omacron -89
+KPX V oslash -129
+KPX V otilde -89
+KPX V period -129
+KPX V semicolon -74
+KPX V u -75
+KPX V uacute -75
+KPX V ucircumflex -75
+KPX V udieresis -75
+KPX V ugrave -75
+KPX V uhungarumlaut -75
+KPX V umacron -75
+KPX V uogonek -75
+KPX V uring -75
+KPX W A -120
+KPX W Aacute -120
+KPX W Abreve -120
+KPX W Acircumflex -120
+KPX W Adieresis -120
+KPX W Agrave -120
+KPX W Amacron -120
+KPX W Aogonek -120
+KPX W Aring -120
+KPX W Atilde -120
+KPX W O -10
+KPX W Oacute -10
+KPX W Ocircumflex -10
+KPX W Odieresis -10
+KPX W Ograve -10
+KPX W Ohungarumlaut -10
+KPX W Omacron -10
+KPX W Oslash -10
+KPX W Otilde -10
+KPX W a -80
+KPX W aacute -80
+KPX W abreve -80
+KPX W acircumflex -80
+KPX W adieresis -80
+KPX W agrave -80
+KPX W amacron -80
+KPX W aogonek -80
+KPX W aring -80
+KPX W atilde -80
+KPX W colon -37
+KPX W comma -92
+KPX W e -80
+KPX W eacute -80
+KPX W ecaron -80
+KPX W ecircumflex -80
+KPX W edieresis -40
+KPX W edotaccent -80
+KPX W egrave -40
+KPX W emacron -40
+KPX W eogonek -80
+KPX W hyphen -65
+KPX W i -40
+KPX W iacute -40
+KPX W iogonek -40
+KPX W o -80
+KPX W oacute -80
+KPX W ocircumflex -80
+KPX W odieresis -80
+KPX W ograve -80
+KPX W ohungarumlaut -80
+KPX W omacron -80
+KPX W oslash -80
+KPX W otilde -80
+KPX W period -92
+KPX W semicolon -37
+KPX W u -50
+KPX W uacute -50
+KPX W ucircumflex -50
+KPX W udieresis -50
+KPX W ugrave -50
+KPX W uhungarumlaut -50
+KPX W umacron -50
+KPX W uogonek -50
+KPX W uring -50
+KPX W y -73
+KPX W yacute -73
+KPX W ydieresis -73
+KPX Y A -120
+KPX Y Aacute -120
+KPX Y Abreve -120
+KPX Y Acircumflex -120
+KPX Y Adieresis -120
+KPX Y Agrave -120
+KPX Y Amacron -120
+KPX Y Aogonek -120
+KPX Y Aring -120
+KPX Y Atilde -120
+KPX Y O -30
+KPX Y Oacute -30
+KPX Y Ocircumflex -30
+KPX Y Odieresis -30
+KPX Y Ograve -30
+KPX Y Ohungarumlaut -30
+KPX Y Omacron -30
+KPX Y Oslash -30
+KPX Y Otilde -30
+KPX Y a -100
+KPX Y aacute -100
+KPX Y abreve -100
+KPX Y acircumflex -100
+KPX Y adieresis -60
+KPX Y agrave -60
+KPX Y amacron -60
+KPX Y aogonek -100
+KPX Y aring -100
+KPX Y atilde -60
+KPX Y colon -92
+KPX Y comma -129
+KPX Y e -100
+KPX Y eacute -100
+KPX Y ecaron -100
+KPX Y ecircumflex -100
+KPX Y edieresis -60
+KPX Y edotaccent -100
+KPX Y egrave -60
+KPX Y emacron -60
+KPX Y eogonek -100
+KPX Y hyphen -111
+KPX Y i -55
+KPX Y iacute -55
+KPX Y iogonek -55
+KPX Y o -110
+KPX Y oacute -110
+KPX Y ocircumflex -110
+KPX Y odieresis -70
+KPX Y ograve -70
+KPX Y ohungarumlaut -110
+KPX Y omacron -70
+KPX Y oslash -110
+KPX Y otilde -70
+KPX Y period -129
+KPX Y semicolon -92
+KPX Y u -111
+KPX Y uacute -111
+KPX Y ucircumflex -111
+KPX Y udieresis -71
+KPX Y ugrave -71
+KPX Y uhungarumlaut -111
+KPX Y umacron -71
+KPX Y uogonek -111
+KPX Y uring -111
+KPX Yacute A -120
+KPX Yacute Aacute -120
+KPX Yacute Abreve -120
+KPX Yacute Acircumflex -120
+KPX Yacute Adieresis -120
+KPX Yacute Agrave -120
+KPX Yacute Amacron -120
+KPX Yacute Aogonek -120
+KPX Yacute Aring -120
+KPX Yacute Atilde -120
+KPX Yacute O -30
+KPX Yacute Oacute -30
+KPX Yacute Ocircumflex -30
+KPX Yacute Odieresis -30
+KPX Yacute Ograve -30
+KPX Yacute Ohungarumlaut -30
+KPX Yacute Omacron -30
+KPX Yacute Oslash -30
+KPX Yacute Otilde -30
+KPX Yacute a -100
+KPX Yacute aacute -100
+KPX Yacute abreve -100
+KPX Yacute acircumflex -100
+KPX Yacute adieresis -60
+KPX Yacute agrave -60
+KPX Yacute amacron -60
+KPX Yacute aogonek -100
+KPX Yacute aring -100
+KPX Yacute atilde -60
+KPX Yacute colon -92
+KPX Yacute comma -129
+KPX Yacute e -100
+KPX Yacute eacute -100
+KPX Yacute ecaron -100
+KPX Yacute ecircumflex -100
+KPX Yacute edieresis -60
+KPX Yacute edotaccent -100
+KPX Yacute egrave -60
+KPX Yacute emacron -60
+KPX Yacute eogonek -100
+KPX Yacute hyphen -111
+KPX Yacute i -55
+KPX Yacute iacute -55
+KPX Yacute iogonek -55
+KPX Yacute o -110
+KPX Yacute oacute -110
+KPX Yacute ocircumflex -110
+KPX Yacute odieresis -70
+KPX Yacute ograve -70
+KPX Yacute ohungarumlaut -110
+KPX Yacute omacron -70
+KPX Yacute oslash -110
+KPX Yacute otilde -70
+KPX Yacute period -129
+KPX Yacute semicolon -92
+KPX Yacute u -111
+KPX Yacute uacute -111
+KPX Yacute ucircumflex -111
+KPX Yacute udieresis -71
+KPX Yacute ugrave -71
+KPX Yacute uhungarumlaut -111
+KPX Yacute umacron -71
+KPX Yacute uogonek -111
+KPX Yacute uring -111
+KPX Ydieresis A -120
+KPX Ydieresis Aacute -120
+KPX Ydieresis Abreve -120
+KPX Ydieresis Acircumflex -120
+KPX Ydieresis Adieresis -120
+KPX Ydieresis Agrave -120
+KPX Ydieresis Amacron -120
+KPX Ydieresis Aogonek -120
+KPX Ydieresis Aring -120
+KPX Ydieresis Atilde -120
+KPX Ydieresis O -30
+KPX Ydieresis Oacute -30
+KPX Ydieresis Ocircumflex -30
+KPX Ydieresis Odieresis -30
+KPX Ydieresis Ograve -30
+KPX Ydieresis Ohungarumlaut -30
+KPX Ydieresis Omacron -30
+KPX Ydieresis Oslash -30
+KPX Ydieresis Otilde -30
+KPX Ydieresis a -100
+KPX Ydieresis aacute -100
+KPX Ydieresis abreve -100
+KPX Ydieresis acircumflex -100
+KPX Ydieresis adieresis -60
+KPX Ydieresis agrave -60
+KPX Ydieresis amacron -60
+KPX Ydieresis aogonek -100
+KPX Ydieresis aring -100
+KPX Ydieresis atilde -100
+KPX Ydieresis colon -92
+KPX Ydieresis comma -129
+KPX Ydieresis e -100
+KPX Ydieresis eacute -100
+KPX Ydieresis ecaron -100
+KPX Ydieresis ecircumflex -100
+KPX Ydieresis edieresis -60
+KPX Ydieresis edotaccent -100
+KPX Ydieresis egrave -60
+KPX Ydieresis emacron -60
+KPX Ydieresis eogonek -100
+KPX Ydieresis hyphen -111
+KPX Ydieresis i -55
+KPX Ydieresis iacute -55
+KPX Ydieresis iogonek -55
+KPX Ydieresis o -110
+KPX Ydieresis oacute -110
+KPX Ydieresis ocircumflex -110
+KPX Ydieresis odieresis -70
+KPX Ydieresis ograve -70
+KPX Ydieresis ohungarumlaut -110
+KPX Ydieresis omacron -70
+KPX Ydieresis oslash -110
+KPX Ydieresis otilde -70
+KPX Ydieresis period -129
+KPX Ydieresis semicolon -92
+KPX Ydieresis u -111
+KPX Ydieresis uacute -111
+KPX Ydieresis ucircumflex -111
+KPX Ydieresis udieresis -71
+KPX Ydieresis ugrave -71
+KPX Ydieresis uhungarumlaut -111
+KPX Ydieresis umacron -71
+KPX Ydieresis uogonek -111
+KPX Ydieresis uring -111
+KPX a v -20
+KPX a w -15
+KPX aacute v -20
+KPX aacute w -15
+KPX abreve v -20
+KPX abreve w -15
+KPX acircumflex v -20
+KPX acircumflex w -15
+KPX adieresis v -20
+KPX adieresis w -15
+KPX agrave v -20
+KPX agrave w -15
+KPX amacron v -20
+KPX amacron w -15
+KPX aogonek v -20
+KPX aogonek w -15
+KPX aring v -20
+KPX aring w -15
+KPX atilde v -20
+KPX atilde w -15
+KPX b period -40
+KPX b u -20
+KPX b uacute -20
+KPX b ucircumflex -20
+KPX b udieresis -20
+KPX b ugrave -20
+KPX b uhungarumlaut -20
+KPX b umacron -20
+KPX b uogonek -20
+KPX b uring -20
+KPX b v -15
+KPX c y -15
+KPX c yacute -15
+KPX c ydieresis -15
+KPX cacute y -15
+KPX cacute yacute -15
+KPX cacute ydieresis -15
+KPX ccaron y -15
+KPX ccaron yacute -15
+KPX ccaron ydieresis -15
+KPX ccedilla y -15
+KPX ccedilla yacute -15
+KPX ccedilla ydieresis -15
+KPX comma quotedblright -70
+KPX comma quoteright -70
+KPX e g -15
+KPX e gbreve -15
+KPX e gcommaaccent -15
+KPX e v -25
+KPX e w -25
+KPX e x -15
+KPX e y -15
+KPX e yacute -15
+KPX e ydieresis -15
+KPX eacute g -15
+KPX eacute gbreve -15
+KPX eacute gcommaaccent -15
+KPX eacute v -25
+KPX eacute w -25
+KPX eacute x -15
+KPX eacute y -15
+KPX eacute yacute -15
+KPX eacute ydieresis -15
+KPX ecaron g -15
+KPX ecaron gbreve -15
+KPX ecaron gcommaaccent -15
+KPX ecaron v -25
+KPX ecaron w -25
+KPX ecaron x -15
+KPX ecaron y -15
+KPX ecaron yacute -15
+KPX ecaron ydieresis -15
+KPX ecircumflex g -15
+KPX ecircumflex gbreve -15
+KPX ecircumflex gcommaaccent -15
+KPX ecircumflex v -25
+KPX ecircumflex w -25
+KPX ecircumflex x -15
+KPX ecircumflex y -15
+KPX ecircumflex yacute -15
+KPX ecircumflex ydieresis -15
+KPX edieresis g -15
+KPX edieresis gbreve -15
+KPX edieresis gcommaaccent -15
+KPX edieresis v -25
+KPX edieresis w -25
+KPX edieresis x -15
+KPX edieresis y -15
+KPX edieresis yacute -15
+KPX edieresis ydieresis -15
+KPX edotaccent g -15
+KPX edotaccent gbreve -15
+KPX edotaccent gcommaaccent -15
+KPX edotaccent v -25
+KPX edotaccent w -25
+KPX edotaccent x -15
+KPX edotaccent y -15
+KPX edotaccent yacute -15
+KPX edotaccent ydieresis -15
+KPX egrave g -15
+KPX egrave gbreve -15
+KPX egrave gcommaaccent -15
+KPX egrave v -25
+KPX egrave w -25
+KPX egrave x -15
+KPX egrave y -15
+KPX egrave yacute -15
+KPX egrave ydieresis -15
+KPX emacron g -15
+KPX emacron gbreve -15
+KPX emacron gcommaaccent -15
+KPX emacron v -25
+KPX emacron w -25
+KPX emacron x -15
+KPX emacron y -15
+KPX emacron yacute -15
+KPX emacron ydieresis -15
+KPX eogonek g -15
+KPX eogonek gbreve -15
+KPX eogonek gcommaaccent -15
+KPX eogonek v -25
+KPX eogonek w -25
+KPX eogonek x -15
+KPX eogonek y -15
+KPX eogonek yacute -15
+KPX eogonek ydieresis -15
+KPX f a -10
+KPX f aacute -10
+KPX f abreve -10
+KPX f acircumflex -10
+KPX f adieresis -10
+KPX f agrave -10
+KPX f amacron -10
+KPX f aogonek -10
+KPX f aring -10
+KPX f atilde -10
+KPX f dotlessi -50
+KPX f f -25
+KPX f i -20
+KPX f iacute -20
+KPX f quoteright 55
+KPX g a -5
+KPX g aacute -5
+KPX g abreve -5
+KPX g acircumflex -5
+KPX g adieresis -5
+KPX g agrave -5
+KPX g amacron -5
+KPX g aogonek -5
+KPX g aring -5
+KPX g atilde -5
+KPX gbreve a -5
+KPX gbreve aacute -5
+KPX gbreve abreve -5
+KPX gbreve acircumflex -5
+KPX gbreve adieresis -5
+KPX gbreve agrave -5
+KPX gbreve amacron -5
+KPX gbreve aogonek -5
+KPX gbreve aring -5
+KPX gbreve atilde -5
+KPX gcommaaccent a -5
+KPX gcommaaccent aacute -5
+KPX gcommaaccent abreve -5
+KPX gcommaaccent acircumflex -5
+KPX gcommaaccent adieresis -5
+KPX gcommaaccent agrave -5
+KPX gcommaaccent amacron -5
+KPX gcommaaccent aogonek -5
+KPX gcommaaccent aring -5
+KPX gcommaaccent atilde -5
+KPX h y -5
+KPX h yacute -5
+KPX h ydieresis -5
+KPX i v -25
+KPX iacute v -25
+KPX icircumflex v -25
+KPX idieresis v -25
+KPX igrave v -25
+KPX imacron v -25
+KPX iogonek v -25
+KPX k e -10
+KPX k eacute -10
+KPX k ecaron -10
+KPX k ecircumflex -10
+KPX k edieresis -10
+KPX k edotaccent -10
+KPX k egrave -10
+KPX k emacron -10
+KPX k eogonek -10
+KPX k o -10
+KPX k oacute -10
+KPX k ocircumflex -10
+KPX k odieresis -10
+KPX k ograve -10
+KPX k ohungarumlaut -10
+KPX k omacron -10
+KPX k oslash -10
+KPX k otilde -10
+KPX k y -15
+KPX k yacute -15
+KPX k ydieresis -15
+KPX kcommaaccent e -10
+KPX kcommaaccent eacute -10
+KPX kcommaaccent ecaron -10
+KPX kcommaaccent ecircumflex -10
+KPX kcommaaccent edieresis -10
+KPX kcommaaccent edotaccent -10
+KPX kcommaaccent egrave -10
+KPX kcommaaccent emacron -10
+KPX kcommaaccent eogonek -10
+KPX kcommaaccent o -10
+KPX kcommaaccent oacute -10
+KPX kcommaaccent ocircumflex -10
+KPX kcommaaccent odieresis -10
+KPX kcommaaccent ograve -10
+KPX kcommaaccent ohungarumlaut -10
+KPX kcommaaccent omacron -10
+KPX kcommaaccent oslash -10
+KPX kcommaaccent otilde -10
+KPX kcommaaccent y -15
+KPX kcommaaccent yacute -15
+KPX kcommaaccent ydieresis -15
+KPX l w -10
+KPX lacute w -10
+KPX lcommaaccent w -10
+KPX lslash w -10
+KPX n v -40
+KPX n y -15
+KPX n yacute -15
+KPX n ydieresis -15
+KPX nacute v -40
+KPX nacute y -15
+KPX nacute yacute -15
+KPX nacute ydieresis -15
+KPX ncaron v -40
+KPX ncaron y -15
+KPX ncaron yacute -15
+KPX ncaron ydieresis -15
+KPX ncommaaccent v -40
+KPX ncommaaccent y -15
+KPX ncommaaccent yacute -15
+KPX ncommaaccent ydieresis -15
+KPX ntilde v -40
+KPX ntilde y -15
+KPX ntilde yacute -15
+KPX ntilde ydieresis -15
+KPX o v -15
+KPX o w -25
+KPX o y -10
+KPX o yacute -10
+KPX o ydieresis -10
+KPX oacute v -15
+KPX oacute w -25
+KPX oacute y -10
+KPX oacute yacute -10
+KPX oacute ydieresis -10
+KPX ocircumflex v -15
+KPX ocircumflex w -25
+KPX ocircumflex y -10
+KPX ocircumflex yacute -10
+KPX ocircumflex ydieresis -10
+KPX odieresis v -15
+KPX odieresis w -25
+KPX odieresis y -10
+KPX odieresis yacute -10
+KPX odieresis ydieresis -10
+KPX ograve v -15
+KPX ograve w -25
+KPX ograve y -10
+KPX ograve yacute -10
+KPX ograve ydieresis -10
+KPX ohungarumlaut v -15
+KPX ohungarumlaut w -25
+KPX ohungarumlaut y -10
+KPX ohungarumlaut yacute -10
+KPX ohungarumlaut ydieresis -10
+KPX omacron v -15
+KPX omacron w -25
+KPX omacron y -10
+KPX omacron yacute -10
+KPX omacron ydieresis -10
+KPX oslash v -15
+KPX oslash w -25
+KPX oslash y -10
+KPX oslash yacute -10
+KPX oslash ydieresis -10
+KPX otilde v -15
+KPX otilde w -25
+KPX otilde y -10
+KPX otilde yacute -10
+KPX otilde ydieresis -10
+KPX p y -10
+KPX p yacute -10
+KPX p ydieresis -10
+KPX period quotedblright -70
+KPX period quoteright -70
+KPX quotedblleft A -80
+KPX quotedblleft Aacute -80
+KPX quotedblleft Abreve -80
+KPX quotedblleft Acircumflex -80
+KPX quotedblleft Adieresis -80
+KPX quotedblleft Agrave -80
+KPX quotedblleft Amacron -80
+KPX quotedblleft Aogonek -80
+KPX quotedblleft Aring -80
+KPX quotedblleft Atilde -80
+KPX quoteleft A -80
+KPX quoteleft Aacute -80
+KPX quoteleft Abreve -80
+KPX quoteleft Acircumflex -80
+KPX quoteleft Adieresis -80
+KPX quoteleft Agrave -80
+KPX quoteleft Amacron -80
+KPX quoteleft Aogonek -80
+KPX quoteleft Aring -80
+KPX quoteleft Atilde -80
+KPX quoteleft quoteleft -74
+KPX quoteright d -50
+KPX quoteright dcroat -50
+KPX quoteright l -10
+KPX quoteright lacute -10
+KPX quoteright lcommaaccent -10
+KPX quoteright lslash -10
+KPX quoteright quoteright -74
+KPX quoteright r -50
+KPX quoteright racute -50
+KPX quoteright rcaron -50
+KPX quoteright rcommaaccent -50
+KPX quoteright s -55
+KPX quoteright sacute -55
+KPX quoteright scaron -55
+KPX quoteright scedilla -55
+KPX quoteright scommaaccent -55
+KPX quoteright space -74
+KPX quoteright t -18
+KPX quoteright tcommaaccent -18
+KPX quoteright v -50
+KPX r comma -40
+KPX r g -18
+KPX r gbreve -18
+KPX r gcommaaccent -18
+KPX r hyphen -20
+KPX r period -55
+KPX racute comma -40
+KPX racute g -18
+KPX racute gbreve -18
+KPX racute gcommaaccent -18
+KPX racute hyphen -20
+KPX racute period -55
+KPX rcaron comma -40
+KPX rcaron g -18
+KPX rcaron gbreve -18
+KPX rcaron gcommaaccent -18
+KPX rcaron hyphen -20
+KPX rcaron period -55
+KPX rcommaaccent comma -40
+KPX rcommaaccent g -18
+KPX rcommaaccent gbreve -18
+KPX rcommaaccent gcommaaccent -18
+KPX rcommaaccent hyphen -20
+KPX rcommaaccent period -55
+KPX space A -55
+KPX space Aacute -55
+KPX space Abreve -55
+KPX space Acircumflex -55
+KPX space Adieresis -55
+KPX space Agrave -55
+KPX space Amacron -55
+KPX space Aogonek -55
+KPX space Aring -55
+KPX space Atilde -55
+KPX space T -18
+KPX space Tcaron -18
+KPX space Tcommaaccent -18
+KPX space V -50
+KPX space W -30
+KPX space Y -90
+KPX space Yacute -90
+KPX space Ydieresis -90
+KPX v a -25
+KPX v aacute -25
+KPX v abreve -25
+KPX v acircumflex -25
+KPX v adieresis -25
+KPX v agrave -25
+KPX v amacron -25
+KPX v aogonek -25
+KPX v aring -25
+KPX v atilde -25
+KPX v comma -65
+KPX v e -15
+KPX v eacute -15
+KPX v ecaron -15
+KPX v ecircumflex -15
+KPX v edieresis -15
+KPX v edotaccent -15
+KPX v egrave -15
+KPX v emacron -15
+KPX v eogonek -15
+KPX v o -20
+KPX v oacute -20
+KPX v ocircumflex -20
+KPX v odieresis -20
+KPX v ograve -20
+KPX v ohungarumlaut -20
+KPX v omacron -20
+KPX v oslash -20
+KPX v otilde -20
+KPX v period -65
+KPX w a -10
+KPX w aacute -10
+KPX w abreve -10
+KPX w acircumflex -10
+KPX w adieresis -10
+KPX w agrave -10
+KPX w amacron -10
+KPX w aogonek -10
+KPX w aring -10
+KPX w atilde -10
+KPX w comma -65
+KPX w o -10
+KPX w oacute -10
+KPX w ocircumflex -10
+KPX w odieresis -10
+KPX w ograve -10
+KPX w ohungarumlaut -10
+KPX w omacron -10
+KPX w oslash -10
+KPX w otilde -10
+KPX w period -65
+KPX x e -15
+KPX x eacute -15
+KPX x ecaron -15
+KPX x ecircumflex -15
+KPX x edieresis -15
+KPX x edotaccent -15
+KPX x egrave -15
+KPX x emacron -15
+KPX x eogonek -15
+KPX y comma -65
+KPX y period -65
+KPX yacute comma -65
+KPX yacute period -65
+KPX ydieresis comma -65
+KPX ydieresis period -65
+EndKernPairs
+EndKernData
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/ZapfDingbats.afm b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/ZapfDingbats.afm
new file mode 100644
index 0000000..dc5662e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/ZapfDingbats.afm
@@ -0,0 +1,225 @@
+StartFontMetrics 4.1
+Comment Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.
+Comment Creation Date: Thu May 1 15:14:13 1997
+Comment UniqueID 43082
+Comment VMusage 45775 55535
+FontName ZapfDingbats
+FullName ITC Zapf Dingbats
+FamilyName ZapfDingbats
+Weight Medium
+ItalicAngle 0
+IsFixedPitch false
+CharacterSet Special
+FontBBox -1 -143 981 820
+UnderlinePosition -100
+UnderlineThickness 50
+Version 002.000
+Notice Copyright (c) 1985, 1987, 1988, 1989, 1997 Adobe Systems Incorporated. All Rights Reserved.ITC Zapf Dingbats is a registered trademark of International Typeface Corporation.
+EncodingScheme FontSpecific
+StdHW 28
+StdVW 90
+StartCharMetrics 202
+C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
+C 33 ; WX 974 ; N a1 ; B 35 72 939 621 ;
+C 34 ; WX 961 ; N a2 ; B 35 81 927 611 ;
+C 35 ; WX 974 ; N a202 ; B 35 72 939 621 ;
+C 36 ; WX 980 ; N a3 ; B 35 0 945 692 ;
+C 37 ; WX 719 ; N a4 ; B 34 139 685 566 ;
+C 38 ; WX 789 ; N a5 ; B 35 -14 755 705 ;
+C 39 ; WX 790 ; N a119 ; B 35 -14 755 705 ;
+C 40 ; WX 791 ; N a118 ; B 35 -13 761 705 ;
+C 41 ; WX 690 ; N a117 ; B 34 138 655 553 ;
+C 42 ; WX 960 ; N a11 ; B 35 123 925 568 ;
+C 43 ; WX 939 ; N a12 ; B 35 134 904 559 ;
+C 44 ; WX 549 ; N a13 ; B 29 -11 516 705 ;
+C 45 ; WX 855 ; N a14 ; B 34 59 820 632 ;
+C 46 ; WX 911 ; N a15 ; B 35 50 876 642 ;
+C 47 ; WX 933 ; N a16 ; B 35 139 899 550 ;
+C 48 ; WX 911 ; N a105 ; B 35 50 876 642 ;
+C 49 ; WX 945 ; N a17 ; B 35 139 909 553 ;
+C 50 ; WX 974 ; N a18 ; B 35 104 938 587 ;
+C 51 ; WX 755 ; N a19 ; B 34 -13 721 705 ;
+C 52 ; WX 846 ; N a20 ; B 36 -14 811 705 ;
+C 53 ; WX 762 ; N a21 ; B 35 0 727 692 ;
+C 54 ; WX 761 ; N a22 ; B 35 0 727 692 ;
+C 55 ; WX 571 ; N a23 ; B -1 -68 571 661 ;
+C 56 ; WX 677 ; N a24 ; B 36 -13 642 705 ;
+C 57 ; WX 763 ; N a25 ; B 35 0 728 692 ;
+C 58 ; WX 760 ; N a26 ; B 35 0 726 692 ;
+C 59 ; WX 759 ; N a27 ; B 35 0 725 692 ;
+C 60 ; WX 754 ; N a28 ; B 35 0 720 692 ;
+C 61 ; WX 494 ; N a6 ; B 35 0 460 692 ;
+C 62 ; WX 552 ; N a7 ; B 35 0 517 692 ;
+C 63 ; WX 537 ; N a8 ; B 35 0 503 692 ;
+C 64 ; WX 577 ; N a9 ; B 35 96 542 596 ;
+C 65 ; WX 692 ; N a10 ; B 35 -14 657 705 ;
+C 66 ; WX 786 ; N a29 ; B 35 -14 751 705 ;
+C 67 ; WX 788 ; N a30 ; B 35 -14 752 705 ;
+C 68 ; WX 788 ; N a31 ; B 35 -14 753 705 ;
+C 69 ; WX 790 ; N a32 ; B 35 -14 756 705 ;
+C 70 ; WX 793 ; N a33 ; B 35 -13 759 705 ;
+C 71 ; WX 794 ; N a34 ; B 35 -13 759 705 ;
+C 72 ; WX 816 ; N a35 ; B 35 -14 782 705 ;
+C 73 ; WX 823 ; N a36 ; B 35 -14 787 705 ;
+C 74 ; WX 789 ; N a37 ; B 35 -14 754 705 ;
+C 75 ; WX 841 ; N a38 ; B 35 -14 807 705 ;
+C 76 ; WX 823 ; N a39 ; B 35 -14 789 705 ;
+C 77 ; WX 833 ; N a40 ; B 35 -14 798 705 ;
+C 78 ; WX 816 ; N a41 ; B 35 -13 782 705 ;
+C 79 ; WX 831 ; N a42 ; B 35 -14 796 705 ;
+C 80 ; WX 923 ; N a43 ; B 35 -14 888 705 ;
+C 81 ; WX 744 ; N a44 ; B 35 0 710 692 ;
+C 82 ; WX 723 ; N a45 ; B 35 0 688 692 ;
+C 83 ; WX 749 ; N a46 ; B 35 0 714 692 ;
+C 84 ; WX 790 ; N a47 ; B 34 -14 756 705 ;
+C 85 ; WX 792 ; N a48 ; B 35 -14 758 705 ;
+C 86 ; WX 695 ; N a49 ; B 35 -14 661 706 ;
+C 87 ; WX 776 ; N a50 ; B 35 -6 741 699 ;
+C 88 ; WX 768 ; N a51 ; B 35 -7 734 699 ;
+C 89 ; WX 792 ; N a52 ; B 35 -14 757 705 ;
+C 90 ; WX 759 ; N a53 ; B 35 0 725 692 ;
+C 91 ; WX 707 ; N a54 ; B 35 -13 672 704 ;
+C 92 ; WX 708 ; N a55 ; B 35 -14 672 705 ;
+C 93 ; WX 682 ; N a56 ; B 35 -14 647 705 ;
+C 94 ; WX 701 ; N a57 ; B 35 -14 666 705 ;
+C 95 ; WX 826 ; N a58 ; B 35 -14 791 705 ;
+C 96 ; WX 815 ; N a59 ; B 35 -14 780 705 ;
+C 97 ; WX 789 ; N a60 ; B 35 -14 754 705 ;
+C 98 ; WX 789 ; N a61 ; B 35 -14 754 705 ;
+C 99 ; WX 707 ; N a62 ; B 34 -14 673 705 ;
+C 100 ; WX 687 ; N a63 ; B 36 0 651 692 ;
+C 101 ; WX 696 ; N a64 ; B 35 0 661 691 ;
+C 102 ; WX 689 ; N a65 ; B 35 0 655 692 ;
+C 103 ; WX 786 ; N a66 ; B 34 -14 751 705 ;
+C 104 ; WX 787 ; N a67 ; B 35 -14 752 705 ;
+C 105 ; WX 713 ; N a68 ; B 35 -14 678 705 ;
+C 106 ; WX 791 ; N a69 ; B 35 -14 756 705 ;
+C 107 ; WX 785 ; N a70 ; B 36 -14 751 705 ;
+C 108 ; WX 791 ; N a71 ; B 35 -14 757 705 ;
+C 109 ; WX 873 ; N a72 ; B 35 -14 838 705 ;
+C 110 ; WX 761 ; N a73 ; B 35 0 726 692 ;
+C 111 ; WX 762 ; N a74 ; B 35 0 727 692 ;
+C 112 ; WX 762 ; N a203 ; B 35 0 727 692 ;
+C 113 ; WX 759 ; N a75 ; B 35 0 725 692 ;
+C 114 ; WX 759 ; N a204 ; B 35 0 725 692 ;
+C 115 ; WX 892 ; N a76 ; B 35 0 858 705 ;
+C 116 ; WX 892 ; N a77 ; B 35 -14 858 692 ;
+C 117 ; WX 788 ; N a78 ; B 35 -14 754 705 ;
+C 118 ; WX 784 ; N a79 ; B 35 -14 749 705 ;
+C 119 ; WX 438 ; N a81 ; B 35 -14 403 705 ;
+C 120 ; WX 138 ; N a82 ; B 35 0 104 692 ;
+C 121 ; WX 277 ; N a83 ; B 35 0 242 692 ;
+C 122 ; WX 415 ; N a84 ; B 35 0 380 692 ;
+C 123 ; WX 392 ; N a97 ; B 35 263 357 705 ;
+C 124 ; WX 392 ; N a98 ; B 34 263 357 705 ;
+C 125 ; WX 668 ; N a99 ; B 35 263 633 705 ;
+C 126 ; WX 668 ; N a100 ; B 36 263 634 705 ;
+C 128 ; WX 390 ; N a89 ; B 35 -14 356 705 ;
+C 129 ; WX 390 ; N a90 ; B 35 -14 355 705 ;
+C 130 ; WX 317 ; N a93 ; B 35 0 283 692 ;
+C 131 ; WX 317 ; N a94 ; B 35 0 283 692 ;
+C 132 ; WX 276 ; N a91 ; B 35 0 242 692 ;
+C 133 ; WX 276 ; N a92 ; B 35 0 242 692 ;
+C 134 ; WX 509 ; N a205 ; B 35 0 475 692 ;
+C 135 ; WX 509 ; N a85 ; B 35 0 475 692 ;
+C 136 ; WX 410 ; N a206 ; B 35 0 375 692 ;
+C 137 ; WX 410 ; N a86 ; B 35 0 375 692 ;
+C 138 ; WX 234 ; N a87 ; B 35 -14 199 705 ;
+C 139 ; WX 234 ; N a88 ; B 35 -14 199 705 ;
+C 140 ; WX 334 ; N a95 ; B 35 0 299 692 ;
+C 141 ; WX 334 ; N a96 ; B 35 0 299 692 ;
+C 161 ; WX 732 ; N a101 ; B 35 -143 697 806 ;
+C 162 ; WX 544 ; N a102 ; B 56 -14 488 706 ;
+C 163 ; WX 544 ; N a103 ; B 34 -14 508 705 ;
+C 164 ; WX 910 ; N a104 ; B 35 40 875 651 ;
+C 165 ; WX 667 ; N a106 ; B 35 -14 633 705 ;
+C 166 ; WX 760 ; N a107 ; B 35 -14 726 705 ;
+C 167 ; WX 760 ; N a108 ; B 0 121 758 569 ;
+C 168 ; WX 776 ; N a112 ; B 35 0 741 705 ;
+C 169 ; WX 595 ; N a111 ; B 34 -14 560 705 ;
+C 170 ; WX 694 ; N a110 ; B 35 -14 659 705 ;
+C 171 ; WX 626 ; N a109 ; B 34 0 591 705 ;
+C 172 ; WX 788 ; N a120 ; B 35 -14 754 705 ;
+C 173 ; WX 788 ; N a121 ; B 35 -14 754 705 ;
+C 174 ; WX 788 ; N a122 ; B 35 -14 754 705 ;
+C 175 ; WX 788 ; N a123 ; B 35 -14 754 705 ;
+C 176 ; WX 788 ; N a124 ; B 35 -14 754 705 ;
+C 177 ; WX 788 ; N a125 ; B 35 -14 754 705 ;
+C 178 ; WX 788 ; N a126 ; B 35 -14 754 705 ;
+C 179 ; WX 788 ; N a127 ; B 35 -14 754 705 ;
+C 180 ; WX 788 ; N a128 ; B 35 -14 754 705 ;
+C 181 ; WX 788 ; N a129 ; B 35 -14 754 705 ;
+C 182 ; WX 788 ; N a130 ; B 35 -14 754 705 ;
+C 183 ; WX 788 ; N a131 ; B 35 -14 754 705 ;
+C 184 ; WX 788 ; N a132 ; B 35 -14 754 705 ;
+C 185 ; WX 788 ; N a133 ; B 35 -14 754 705 ;
+C 186 ; WX 788 ; N a134 ; B 35 -14 754 705 ;
+C 187 ; WX 788 ; N a135 ; B 35 -14 754 705 ;
+C 188 ; WX 788 ; N a136 ; B 35 -14 754 705 ;
+C 189 ; WX 788 ; N a137 ; B 35 -14 754 705 ;
+C 190 ; WX 788 ; N a138 ; B 35 -14 754 705 ;
+C 191 ; WX 788 ; N a139 ; B 35 -14 754 705 ;
+C 192 ; WX 788 ; N a140 ; B 35 -14 754 705 ;
+C 193 ; WX 788 ; N a141 ; B 35 -14 754 705 ;
+C 194 ; WX 788 ; N a142 ; B 35 -14 754 705 ;
+C 195 ; WX 788 ; N a143 ; B 35 -14 754 705 ;
+C 196 ; WX 788 ; N a144 ; B 35 -14 754 705 ;
+C 197 ; WX 788 ; N a145 ; B 35 -14 754 705 ;
+C 198 ; WX 788 ; N a146 ; B 35 -14 754 705 ;
+C 199 ; WX 788 ; N a147 ; B 35 -14 754 705 ;
+C 200 ; WX 788 ; N a148 ; B 35 -14 754 705 ;
+C 201 ; WX 788 ; N a149 ; B 35 -14 754 705 ;
+C 202 ; WX 788 ; N a150 ; B 35 -14 754 705 ;
+C 203 ; WX 788 ; N a151 ; B 35 -14 754 705 ;
+C 204 ; WX 788 ; N a152 ; B 35 -14 754 705 ;
+C 205 ; WX 788 ; N a153 ; B 35 -14 754 705 ;
+C 206 ; WX 788 ; N a154 ; B 35 -14 754 705 ;
+C 207 ; WX 788 ; N a155 ; B 35 -14 754 705 ;
+C 208 ; WX 788 ; N a156 ; B 35 -14 754 705 ;
+C 209 ; WX 788 ; N a157 ; B 35 -14 754 705 ;
+C 210 ; WX 788 ; N a158 ; B 35 -14 754 705 ;
+C 211 ; WX 788 ; N a159 ; B 35 -14 754 705 ;
+C 212 ; WX 894 ; N a160 ; B 35 58 860 634 ;
+C 213 ; WX 838 ; N a161 ; B 35 152 803 540 ;
+C 214 ; WX 1016 ; N a163 ; B 34 152 981 540 ;
+C 215 ; WX 458 ; N a164 ; B 35 -127 422 820 ;
+C 216 ; WX 748 ; N a196 ; B 35 94 698 597 ;
+C 217 ; WX 924 ; N a165 ; B 35 140 890 552 ;
+C 218 ; WX 748 ; N a192 ; B 35 94 698 597 ;
+C 219 ; WX 918 ; N a166 ; B 35 166 884 526 ;
+C 220 ; WX 927 ; N a167 ; B 35 32 892 660 ;
+C 221 ; WX 928 ; N a168 ; B 35 129 891 562 ;
+C 222 ; WX 928 ; N a169 ; B 35 128 893 563 ;
+C 223 ; WX 834 ; N a170 ; B 35 155 799 537 ;
+C 224 ; WX 873 ; N a171 ; B 35 93 838 599 ;
+C 225 ; WX 828 ; N a172 ; B 35 104 791 588 ;
+C 226 ; WX 924 ; N a173 ; B 35 98 889 594 ;
+C 227 ; WX 924 ; N a162 ; B 35 98 889 594 ;
+C 228 ; WX 917 ; N a174 ; B 35 0 882 692 ;
+C 229 ; WX 930 ; N a175 ; B 35 84 896 608 ;
+C 230 ; WX 931 ; N a176 ; B 35 84 896 608 ;
+C 231 ; WX 463 ; N a177 ; B 35 -99 429 791 ;
+C 232 ; WX 883 ; N a178 ; B 35 71 848 623 ;
+C 233 ; WX 836 ; N a179 ; B 35 44 802 648 ;
+C 234 ; WX 836 ; N a193 ; B 35 44 802 648 ;
+C 235 ; WX 867 ; N a180 ; B 35 101 832 591 ;
+C 236 ; WX 867 ; N a199 ; B 35 101 832 591 ;
+C 237 ; WX 696 ; N a181 ; B 35 44 661 648 ;
+C 238 ; WX 696 ; N a200 ; B 35 44 661 648 ;
+C 239 ; WX 874 ; N a182 ; B 35 77 840 619 ;
+C 241 ; WX 874 ; N a201 ; B 35 73 840 615 ;
+C 242 ; WX 760 ; N a183 ; B 35 0 725 692 ;
+C 243 ; WX 946 ; N a184 ; B 35 160 911 533 ;
+C 244 ; WX 771 ; N a197 ; B 34 37 736 655 ;
+C 245 ; WX 865 ; N a185 ; B 35 207 830 481 ;
+C 246 ; WX 771 ; N a194 ; B 34 37 736 655 ;
+C 247 ; WX 888 ; N a198 ; B 34 -19 853 712 ;
+C 248 ; WX 967 ; N a186 ; B 35 124 932 568 ;
+C 249 ; WX 888 ; N a195 ; B 34 -19 853 712 ;
+C 250 ; WX 831 ; N a187 ; B 35 113 796 579 ;
+C 251 ; WX 873 ; N a188 ; B 36 118 838 578 ;
+C 252 ; WX 927 ; N a189 ; B 35 150 891 542 ;
+C 253 ; WX 970 ; N a190 ; B 35 76 931 616 ;
+C 254 ; WX 918 ; N a191 ; B 34 99 884 593 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/installed-fonts.dist.json b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/installed-fonts.dist.json
new file mode 100644
index 0000000..c6abf15
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/installed-fonts.dist.json
@@ -0,0 +1,80 @@
+{
+ "sans-serif": {
+ "normal": "Helvetica",
+ "bold": "Helvetica-Bold",
+ "italic": "Helvetica-Oblique",
+ "bold_italic": "Helvetica-BoldOblique"
+ },
+ "times": {
+ "normal": "Times-Roman",
+ "bold": "Times-Bold",
+ "italic": "Times-Italic",
+ "bold_italic": "Times-BoldItalic"
+ },
+ "times-roman": {
+ "normal": "Times-Roman",
+ "bold": "Times-Bold",
+ "italic": "Times-Italic",
+ "bold_italic": "Times-BoldItalic"
+ },
+ "courier": {
+ "normal": "Courier",
+ "bold": "Courier-Bold",
+ "italic": "Courier-Oblique",
+ "bold_italic": "Courier-BoldOblique"
+ },
+ "helvetica": {
+ "normal": "Helvetica",
+ "bold": "Helvetica-Bold",
+ "italic": "Helvetica-Oblique",
+ "bold_italic": "Helvetica-BoldOblique"
+ },
+ "zapfdingbats": {
+ "normal": "ZapfDingbats",
+ "bold": "ZapfDingbats",
+ "italic": "ZapfDingbats",
+ "bold_italic": "ZapfDingbats"
+ },
+ "symbol": {
+ "normal": "Symbol",
+ "bold": "Symbol",
+ "italic": "Symbol",
+ "bold_italic": "Symbol"
+ },
+ "serif": {
+ "normal": "Times-Roman",
+ "bold": "Times-Bold",
+ "italic": "Times-Italic",
+ "bold_italic": "Times-BoldItalic"
+ },
+ "monospace": {
+ "normal": "Courier",
+ "bold": "Courier-Bold",
+ "italic": "Courier-Oblique",
+ "bold_italic": "Courier-BoldOblique"
+ },
+ "fixed": {
+ "normal": "Courier",
+ "bold": "Courier-Bold",
+ "italic": "Courier-Oblique",
+ "bold_italic": "Courier-BoldOblique"
+ },
+ "dejavu sans": {
+ "bold": "DejaVuSans-Bold",
+ "bold_italic": "DejaVuSans-BoldOblique",
+ "italic": "DejaVuSans-Oblique",
+ "normal": "DejaVuSans"
+ },
+ "dejavu sans mono": {
+ "bold": "DejaVuSansMono-Bold",
+ "bold_italic": "DejaVuSansMono-BoldOblique",
+ "italic": "DejaVuSansMono-Oblique",
+ "normal": "DejaVuSansMono"
+ },
+ "dejavu serif": {
+ "bold": "DejaVuSerif-Bold",
+ "bold_italic": "DejaVuSerif-BoldItalic",
+ "italic": "DejaVuSerif-Italic",
+ "normal": "DejaVuSerif"
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/mustRead.html b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/mustRead.html
new file mode 100644
index 0000000..b9f4ba2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/fonts/mustRead.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html;charset=iso-8859-1">
+ <meta name="generator" content="Adobe GoLive 4">
+ <title>Core 14 AFM Files - ReadMe</title>
+ </head>
+ <body bgcolor="white">
+ <font color="white">or</font>
+ <table border="0" cellpadding="0" cellspacing="2">
+ <tr>
+ <td width="40"></td>
+ <td width="300">This file and the 14 PostScript(R) AFM files it accompanies may be used, copied, and distributed for any purpose and without charge, with or without modification, provided that all copyright notices are retained; that the AFM files are not distributed without this file; that all modifications to this file or any of the AFM files are prominently noted in the modified file(s); and that this paragraph is not modified. Adobe Systems has no responsibility or obligation to support the use of the AFM files. <font color="white">Col</font></td>
+ </tr>
+ </table>
+ <p>Source <a href="http://www.adobe.com/devnet/font/#pcfi">http://www.adobe.com/devnet/font/#pcfi</a></p>
+</body>
+</html> \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.png b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.png
new file mode 100644
index 0000000..771a1a3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.png
Binary files differ
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.svg b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.svg
new file mode 100644
index 0000000..83ba7e7
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/broken_image.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
+ <g>
+ <rect stroke="#666666" id="svg_1" height="60.499994" width="60.166667" y="1.666669" x="1.999998" stroke-width="1.5" fill="none"/>
+ <line stroke-linecap="butt" stroke-linejoin="miter" id="svg_3" y2="59.333253" x2="59.749916" y1="4.333415" x1="4.250079" stroke-width="1.5" stroke="#999999" fill="none"/>
+ <line stroke-linecap="butt" stroke-linejoin="miter" id="svg_4" y2="59.999665" x2="4.062838" y1="3.750342" x1="60.062164" stroke-width="1.5" stroke="#999999" fill="none"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/html.css b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/html.css
new file mode 100644
index 0000000..89dcde6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/lib/res/html.css
@@ -0,0 +1,518 @@
+/**
+ * dompdf default stylesheet.
+ *
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ *
+ * Portions from Mozilla
+ * @link https://dxr.mozilla.org/mozilla-central/source/layout/style/res/html.css
+ * @license http://mozilla.org/MPL/2.0/ Mozilla Public License, v. 2.0
+ *
+ * Portions from W3C
+ * @link https://drafts.csswg.org/css-ui-3/#default-style-sheet
+ *
+ */
+
+@page {
+ margin: 1.2cm;
+}
+
+html {
+ display: -dompdf-page !important;
+ counter-reset: page;
+}
+
+/* blocks */
+
+article,
+aside,
+details,
+div,
+dt,
+figcaption,
+footer,
+form,
+header,
+hgroup,
+main,
+nav,
+noscript,
+section,
+summary {
+ display: block;
+}
+
+body {
+ page-break-before: avoid;
+ display: block !important;
+ counter-increment: page;
+}
+
+p, dl, multicol {
+ display: block;
+ margin: 1em 0;
+}
+
+dd {
+ display: block;
+ margin-left: 40px;
+}
+
+blockquote, figure {
+ display: block;
+ margin: 1em 40px;
+}
+
+address {
+ display: block;
+ font-style: italic;
+}
+
+center {
+ display: block;
+ text-align: center;
+}
+
+blockquote[type=cite] {
+ display: block;
+ margin: 1em 0;
+ padding-left: 1em;
+ border-left: solid;
+ border-color: blue;
+ border-width: thin;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ display: block;
+ font-weight: bold;
+}
+
+h1 {
+ font-size: 2em;
+ margin: .67em 0;
+}
+
+h2 {
+ font-size: 1.5em;
+ margin: .83em 0;
+}
+
+h3 {
+ font-size: 1.17em;
+ margin: 1em 0;
+}
+
+h4 {
+ margin: 1.33em 0;
+}
+
+h5 {
+ font-size: 0.83em;
+ margin: 1.67em 0;
+}
+
+h6 {
+ font-size: 0.67em;
+ margin: 2.33em 0;
+}
+
+listing {
+ display: block;
+ font-family: fixed;
+ font-size: medium;
+ white-space: pre;
+ margin: 1em 0;
+}
+
+plaintext, pre, xmp {
+ display: block;
+ font-family: fixed;
+ white-space: pre;
+ margin: 1em 0;
+}
+
+/* tables */
+
+table {
+ display: table;
+ border-spacing: 2px;
+ border-collapse: separate;
+ margin-top: 0;
+ margin-bottom: 0;
+ text-indent: 0;
+ text-align: left; /* quirk */
+}
+
+table[border] {
+ border: outset gray;
+}
+
+table[border] td,
+table[border] th {
+ border: 1px inset gray;
+}
+
+table[border="0"] td,
+table[border="0"] th {
+ border-width: 0;
+}
+
+/* make sure backgrounds are inherited in tables -- see bug 4510 */
+td, th, tr {
+ background: inherit;
+}
+
+/* caption inherits from table not table-outer */
+caption {
+ display: table-caption;
+ text-align: center;
+}
+
+tr {
+ display: table-row;
+ vertical-align: inherit;
+}
+
+col {
+ display: table-column;
+}
+
+colgroup {
+ display: table-column-group;
+}
+
+tbody {
+ display: table-row-group;
+ vertical-align: middle;
+}
+
+thead {
+ display: table-header-group;
+ vertical-align: middle;
+}
+
+tfoot {
+ display: table-footer-group;
+ vertical-align: middle;
+}
+
+/* To simulate tbody auto-insertion */
+table > tr {
+ vertical-align: middle;
+}
+
+td {
+ display: table-cell;
+ vertical-align: inherit;
+ text-align: inherit;
+ padding: 1px;
+}
+
+th {
+ display: table-cell;
+ vertical-align: inherit;
+ text-align: center;
+ font-weight: bold;
+ padding: 1px;
+}
+
+/* inlines */
+
+q:before {
+ content: open-quote;
+}
+
+q:after {
+ content: close-quote;
+}
+
+:link {
+ color: #00c;
+ text-decoration: underline;
+}
+
+b, strong {
+ font-weight: bolder;
+}
+
+i, cite, em, var, dfn {
+ font-style: italic;
+}
+
+tt, code, kbd, samp {
+ font-family: fixed;
+}
+
+u, ins {
+ text-decoration: underline;
+}
+
+s, strike, del {
+ text-decoration: line-through;
+}
+
+big {
+ font-size: larger;
+}
+
+small {
+ font-size: smaller;
+}
+
+sub {
+ vertical-align: sub;
+ font-size: smaller;
+ line-height: normal;
+}
+
+sup {
+ vertical-align: super;
+ font-size: smaller;
+ line-height: normal;
+}
+
+nobr {
+ white-space: nowrap;
+}
+
+mark {
+ background: yellow;
+ color: black;
+}
+
+/* titles */
+
+abbr[title], acronym[title] {
+ text-decoration: dotted underline;
+}
+
+/* lists */
+
+ul, menu, dir {
+ display: block;
+ list-style-type: disc;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+ol {
+ display: block;
+ list-style-type: decimal;
+ margin: 1em 0;
+ padding-left: 40px;
+}
+
+li {
+ display: list-item;
+}
+
+/*li:before {
+ display: -dompdf-list-bullet !important;
+ content: counter(-dompdf-default-counter) ". ";
+ padding-right: 0.5em;
+}*/
+
+/* nested lists have no top/bottom margins */
+:matches(ul, ol, dir, menu, dl) ul,
+:matches(ul, ol, dir, menu, dl) ol,
+:matches(ul, ol, dir, menu, dl) dir,
+:matches(ul, ol, dir, menu, dl) menu,
+:matches(ul, ol, dir, menu, dl) dl {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+/* 2 deep unordered lists use a circle */
+:matches(ul, ol, dir, menu) ul,
+:matches(ul, ol, dir, menu) ul,
+:matches(ul, ol, dir, menu) ul,
+:matches(ul, ol, dir, menu) ul {
+ list-style-type: circle;
+}
+
+/* 3 deep (or more) unordered lists use a square */
+:matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) ul,
+:matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) menu,
+:matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) dir {
+ list-style-type: square;
+}
+
+/* forms */
+/* From https://drafts.csswg.org/css-ui-3/#default-style-sheet */
+form {
+ display: block;
+}
+
+input, button, select {
+ display: inline-block;
+ font-family: sans-serif;
+}
+
+input[type=text],
+input[type=password],
+select {
+ width: 12em;
+}
+
+input[type=text],
+input[type=password],
+input[type=button],
+input[type=submit],
+input[type=reset],
+input[type=file],
+button,
+textarea,
+select {
+ background: #FFF;
+ border: 1px solid #999;
+ padding: 2px;
+ margin: 2px;
+}
+
+input[type=button],
+input[type=submit],
+input[type=reset],
+input[type=file],
+button {
+ background: #CCC;
+ text-align: center;
+}
+
+input[type=file] {
+ width: 8em;
+}
+
+input[type=text]:before,
+input[type=button]:before,
+input[type=submit]:before,
+input[type=reset]:before {
+ content: attr(value);
+}
+
+input[type=file]:before {
+ content: "Choose a file";
+}
+
+input[type=password][value]:before {
+ font-family: "DejaVu Sans" !important;
+ content: "\2022\2022\2022\2022\2022\2022\2022\2022";
+ line-height: 1em;
+}
+
+input[type=checkbox],
+input[type=radio],
+select:after {
+ font-family: "DejaVu Sans" !important;
+ font-size: 18px;
+ line-height: 1;
+}
+
+input[type=checkbox]:before {
+ content: "\2610";
+}
+
+input[type=checkbox][checked]:before {
+ content: "\2611";
+}
+
+input[type=radio]:before {
+ content: "\25CB";
+}
+
+input[type=radio][checked]:before {
+ content: "\25C9";
+}
+
+textarea {
+ display: block;
+ height: 3em;
+ overflow: hidden;
+ font-family: monospace;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+select {
+ position: relative!important;
+ overflow: hidden!important;
+}
+
+select:after {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 5em;
+ width: 1.4em;
+ text-align: center;
+ background: #CCC;
+ content: "\25BE";
+}
+
+select option {
+ display: none;
+}
+
+select option[selected] {
+ display: inline;
+}
+
+fieldset {
+ display: block;
+ margin: 0.6em 2px 2px;
+ padding: 0.75em;
+ border: 1pt groove #666;
+ position: relative;
+}
+
+fieldset > legend {
+ position: absolute;
+ top: -0.6em;
+ left: 0.75em;
+ padding: 0 0.3em;
+ background: white;
+}
+
+legend {
+ display: inline-block;
+}
+
+/* leafs */
+
+hr {
+ display: block;
+ height: 0;
+ border: 1px inset;
+ margin: 0.5em auto 0.5em auto;
+}
+
+hr[size="1"] {
+ border-style: solid none none none;
+}
+
+iframe {
+ border: 2px inset;
+}
+
+noframes {
+ display: block;
+}
+
+br {
+ display: -dompdf-br;
+}
+
+img, img_generated {
+ display: -dompdf-image !important;
+}
+
+dompdf_generated {
+ display: inline;
+}
+
+/* hidden elements */
+area, base, basefont, head, meta, script, style, title,
+noembed, param {
+ display: none;
+ -dompdf-keep: yes;
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/CPDF.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/CPDF.php
new file mode 100644
index 0000000..e8fc6ae
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/CPDF.php
@@ -0,0 +1,944 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+// FIXME: Need to sanity check inputs to this class
+namespace Dompdf\Adapter;
+
+use Dompdf\Canvas;
+use Dompdf\Dompdf;
+use Dompdf\Exception;
+use Dompdf\FontMetrics;
+use Dompdf\Helpers;
+use Dompdf\Image\Cache;
+use FontLib\Exception\FontNotFoundException;
+
+/**
+ * PDF rendering interface
+ *
+ * Dompdf\Adapter\CPDF provides a simple stateless interface to the stateful one
+ * provided by the Cpdf class.
+ *
+ * Unless otherwise mentioned, all dimensions are in points (1/72 in). The
+ * coordinate origin is in the top left corner, and y values increase
+ * downwards.
+ *
+ * See {@link http://www.ros.co.nz/pdf/} for more complete documentation
+ * on the underlying {@link Cpdf} class.
+ *
+ * @package dompdf
+ */
+class CPDF implements Canvas
+{
+
+ /**
+ * Dimensions of paper sizes in points
+ *
+ * @var array
+ */
+ static $PAPER_SIZES = [
+ "4a0" => [0.0, 0.0, 4767.87, 6740.79],
+ "2a0" => [0.0, 0.0, 3370.39, 4767.87],
+ "a0" => [0.0, 0.0, 2383.94, 3370.39],
+ "a1" => [0.0, 0.0, 1683.78, 2383.94],
+ "a2" => [0.0, 0.0, 1190.55, 1683.78],
+ "a3" => [0.0, 0.0, 841.89, 1190.55],
+ "a4" => [0.0, 0.0, 595.28, 841.89],
+ "a5" => [0.0, 0.0, 419.53, 595.28],
+ "a6" => [0.0, 0.0, 297.64, 419.53],
+ "a7" => [0.0, 0.0, 209.76, 297.64],
+ "a8" => [0.0, 0.0, 147.40, 209.76],
+ "a9" => [0.0, 0.0, 104.88, 147.40],
+ "a10" => [0.0, 0.0, 73.70, 104.88],
+ "b0" => [0.0, 0.0, 2834.65, 4008.19],
+ "b1" => [0.0, 0.0, 2004.09, 2834.65],
+ "b2" => [0.0, 0.0, 1417.32, 2004.09],
+ "b3" => [0.0, 0.0, 1000.63, 1417.32],
+ "b4" => [0.0, 0.0, 708.66, 1000.63],
+ "b5" => [0.0, 0.0, 498.90, 708.66],
+ "b6" => [0.0, 0.0, 354.33, 498.90],
+ "b7" => [0.0, 0.0, 249.45, 354.33],
+ "b8" => [0.0, 0.0, 175.75, 249.45],
+ "b9" => [0.0, 0.0, 124.72, 175.75],
+ "b10" => [0.0, 0.0, 87.87, 124.72],
+ "c0" => [0.0, 0.0, 2599.37, 3676.54],
+ "c1" => [0.0, 0.0, 1836.85, 2599.37],
+ "c2" => [0.0, 0.0, 1298.27, 1836.85],
+ "c3" => [0.0, 0.0, 918.43, 1298.27],
+ "c4" => [0.0, 0.0, 649.13, 918.43],
+ "c5" => [0.0, 0.0, 459.21, 649.13],
+ "c6" => [0.0, 0.0, 323.15, 459.21],
+ "c7" => [0.0, 0.0, 229.61, 323.15],
+ "c8" => [0.0, 0.0, 161.57, 229.61],
+ "c9" => [0.0, 0.0, 113.39, 161.57],
+ "c10" => [0.0, 0.0, 79.37, 113.39],
+ "ra0" => [0.0, 0.0, 2437.80, 3458.27],
+ "ra1" => [0.0, 0.0, 1729.13, 2437.80],
+ "ra2" => [0.0, 0.0, 1218.90, 1729.13],
+ "ra3" => [0.0, 0.0, 864.57, 1218.90],
+ "ra4" => [0.0, 0.0, 609.45, 864.57],
+ "sra0" => [0.0, 0.0, 2551.18, 3628.35],
+ "sra1" => [0.0, 0.0, 1814.17, 2551.18],
+ "sra2" => [0.0, 0.0, 1275.59, 1814.17],
+ "sra3" => [0.0, 0.0, 907.09, 1275.59],
+ "sra4" => [0.0, 0.0, 637.80, 907.09],
+ "letter" => [0.0, 0.0, 612.00, 792.00],
+ "half-letter" => [0.0, 0.0, 396.00, 612.00],
+ "legal" => [0.0, 0.0, 612.00, 1008.00],
+ "ledger" => [0.0, 0.0, 1224.00, 792.00],
+ "tabloid" => [0.0, 0.0, 792.00, 1224.00],
+ "executive" => [0.0, 0.0, 521.86, 756.00],
+ "folio" => [0.0, 0.0, 612.00, 936.00],
+ "commercial #10 envelope" => [0.0, 0.0, 684.00, 297.00],
+ "catalog #10 1/2 envelope" => [0.0, 0.0, 648.00, 864.00],
+ "8.5x11" => [0.0, 0.0, 612.00, 792.00],
+ "8.5x14" => [0.0, 0.0, 612.00, 1008.00],
+ "11x17" => [0.0, 0.0, 792.00, 1224.00],
+ ];
+
+ /**
+ * The Dompdf object
+ *
+ * @var Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * Instance of Cpdf class
+ *
+ * @var \Dompdf\Cpdf
+ */
+ protected $_pdf;
+
+ /**
+ * PDF width, in points
+ *
+ * @var float
+ */
+ protected $_width;
+
+ /**
+ * PDF height, in points
+ *
+ * @var float
+ */
+ protected $_height;
+
+ /**
+ * Current page number
+ *
+ * @var int
+ */
+ protected $_page_number;
+
+ /**
+ * Total number of pages
+ *
+ * @var int
+ */
+ protected $_page_count;
+
+ /**
+ * Array of pages for accessing after rendering is initially complete
+ *
+ * @var array
+ */
+ protected $_pages;
+
+ /**
+ * Currently-applied opacity level (0 - 1)
+ *
+ * @var float
+ */
+ protected $_current_opacity = 1;
+
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null)
+ {
+ if (is_array($paper)) {
+ $size = array_map("floatval", $paper);
+ } else {
+ $paper = strtolower($paper);
+ $size = self::$PAPER_SIZES[$paper] ?? self::$PAPER_SIZES["letter"];
+ }
+
+ if (strtolower($orientation) === "landscape") {
+ [$size[2], $size[3]] = [$size[3], $size[2]];
+ }
+
+ if ($dompdf === null) {
+ $this->_dompdf = new Dompdf();
+ } else {
+ $this->_dompdf = $dompdf;
+ }
+
+ $this->_pdf = new \Dompdf\Cpdf(
+ $size,
+ true,
+ $this->_dompdf->getOptions()->getFontCache(),
+ $this->_dompdf->getOptions()->getTempDir()
+ );
+
+ $this->_pdf->addInfo("Producer", sprintf("%s + CPDF", $this->_dompdf->version));
+ $time = substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
+ $this->_pdf->addInfo("CreationDate", "D:$time");
+ $this->_pdf->addInfo("ModDate", "D:$time");
+
+ $this->_width = $size[2] - $size[0];
+ $this->_height = $size[3] - $size[1];
+
+ $this->_page_number = $this->_page_count = 1;
+
+ $this->_pages = [$this->_pdf->getFirstPageId()];
+ }
+
+ public function get_dompdf()
+ {
+ return $this->_dompdf;
+ }
+
+ /**
+ * Returns the Cpdf instance
+ *
+ * @return \Dompdf\Cpdf
+ */
+ public function get_cpdf()
+ {
+ return $this->_pdf;
+ }
+
+ public function add_info(string $label, string $value): void
+ {
+ $this->_pdf->addInfo($label, $value);
+ }
+
+ /**
+ * Opens a new 'object'
+ *
+ * While an object is open, all drawing actions are recorded in the object,
+ * as opposed to being drawn on the current page. Objects can be added
+ * later to a specific page or to several pages.
+ *
+ * The return value is an integer ID for the new object.
+ *
+ * @see CPDF::close_object()
+ * @see CPDF::add_object()
+ *
+ * @return int
+ */
+ public function open_object()
+ {
+ $ret = $this->_pdf->openObject();
+ $this->_pdf->saveState();
+ return $ret;
+ }
+
+ /**
+ * Reopens an existing 'object'
+ *
+ * @see CPDF::open_object()
+ * @param int $object the ID of a previously opened object
+ */
+ public function reopen_object($object)
+ {
+ $this->_pdf->reopenObject($object);
+ $this->_pdf->saveState();
+ }
+
+ /**
+ * Closes the current 'object'
+ *
+ * @see CPDF::open_object()
+ */
+ public function close_object()
+ {
+ $this->_pdf->restoreState();
+ $this->_pdf->closeObject();
+ }
+
+ /**
+ * Adds a specified 'object' to the document
+ *
+ * $object int specifying an object created with {@link
+ * CPDF::open_object()}. $where can be one of:
+ * - 'add' add to current page only
+ * - 'all' add to every page from the current one onwards
+ * - 'odd' add to all odd numbered pages from now on
+ * - 'even' add to all even numbered pages from now on
+ * - 'next' add the object to the next page only
+ * - 'nextodd' add to all odd numbered pages from the next one
+ * - 'nexteven' add to all even numbered pages from the next one
+ *
+ * @see Cpdf::addObject()
+ *
+ * @param int $object
+ * @param string $where
+ */
+ public function add_object($object, $where = 'all')
+ {
+ $this->_pdf->addObject($object, $where);
+ }
+
+ /**
+ * Stops the specified 'object' from appearing in the document.
+ *
+ * The object will stop being displayed on the page following the current
+ * one.
+ *
+ * @param int $object
+ */
+ public function stop_object($object)
+ {
+ $this->_pdf->stopObject($object);
+ }
+
+ /**
+ * Serialize the pdf object's current state for retrieval later
+ */
+ public function serialize_object($id)
+ {
+ return $this->_pdf->serializeObject($id);
+ }
+
+ public function reopen_serialized_object($obj)
+ {
+ return $this->_pdf->restoreSerializedObject($obj);
+ }
+
+ //........................................................................
+
+ public function get_width()
+ {
+ return $this->_width;
+ }
+
+ public function get_height()
+ {
+ return $this->_height;
+ }
+
+ public function get_page_number()
+ {
+ return $this->_page_number;
+ }
+
+ public function get_page_count()
+ {
+ return $this->_page_count;
+ }
+
+ /**
+ * Sets the current page number
+ *
+ * @param int $num
+ */
+ public function set_page_number($num)
+ {
+ $this->_page_number = $num;
+ }
+
+ public function set_page_count($count)
+ {
+ $this->_page_count = $count;
+ }
+
+ /**
+ * Sets the stroke color
+ *
+ * See {@link Style::set_color()} for the format of the color array.
+ *
+ * @param array $color
+ */
+ protected function _set_stroke_color($color)
+ {
+ $this->_pdf->setStrokeColor($color);
+ $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
+ $alpha *= $this->_current_opacity;
+ $this->_set_line_transparency("Normal", $alpha);
+ }
+
+ /**
+ * Sets the fill colour
+ *
+ * See {@link Style::set_color()} for the format of the colour array.
+ *
+ * @param array $color
+ */
+ protected function _set_fill_color($color)
+ {
+ $this->_pdf->setColor($color);
+ $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
+ $alpha *= $this->_current_opacity;
+ $this->_set_fill_transparency("Normal", $alpha);
+ }
+
+ /**
+ * Sets line transparency
+ * @see Cpdf::setLineTransparency()
+ *
+ * Valid blend modes are (case-sensitive):
+ *
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
+ * ColorDodge, ColorBurn, HardLight, SoftLight, Difference,
+ * Exclusion
+ *
+ * @param string $mode the blending mode to use
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
+ */
+ protected function _set_line_transparency($mode, $opacity)
+ {
+ $this->_pdf->setLineTransparency($mode, $opacity);
+ }
+
+ /**
+ * Sets fill transparency
+ * @see Cpdf::setFillTransparency()
+ *
+ * Valid blend modes are (case-sensitive):
+ *
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
+ * Exclusion
+ *
+ * @param string $mode the blending mode to use
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
+ */
+ protected function _set_fill_transparency($mode, $opacity)
+ {
+ $this->_pdf->setFillTransparency($mode, $opacity);
+ }
+
+ /**
+ * Sets the line style
+ *
+ * @see Cpdf::setLineStyle()
+ *
+ * @param float $width
+ * @param string $cap
+ * @param string $join
+ * @param array $dash
+ */
+ protected function _set_line_style($width, $cap, $join, $dash)
+ {
+ $this->_pdf->setLineStyle($width, $cap, $join, $dash);
+ }
+
+ public function set_opacity(float $opacity, string $mode = "Normal"): void
+ {
+ $this->_set_line_transparency($mode, $opacity);
+ $this->_set_fill_transparency($mode, $opacity);
+ $this->_current_opacity = $opacity;
+ }
+
+ public function set_default_view($view, $options = [])
+ {
+ array_unshift($options, $view);
+ call_user_func_array([$this->_pdf, "openHere"], $options);
+ }
+
+ /**
+ * Remaps y coords from 4th to 1st quadrant
+ *
+ * @param float $y
+ * @return float
+ */
+ protected function y($y)
+ {
+ return $this->_height - $y;
+ }
+
+ public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
+ {
+ $this->_set_stroke_color($color);
+ $this->_set_line_style($width, $cap, "", $style);
+
+ $this->_pdf->line($x1, $this->y($y1),
+ $x2, $this->y($y2));
+ $this->_set_line_transparency("Normal", $this->_current_opacity);
+ }
+
+ public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
+ {
+ $this->_set_stroke_color($color);
+ $this->_set_line_style($width, $cap, "", $style);
+
+ $this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false);
+ $this->_set_line_transparency("Normal", $this->_current_opacity);
+ }
+
+ public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
+ {
+ $this->_set_stroke_color($color);
+ $this->_set_line_style($width, $cap, "", $style);
+ $this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h);
+ $this->_set_line_transparency("Normal", $this->_current_opacity);
+ }
+
+ public function filled_rectangle($x1, $y1, $w, $h, $color)
+ {
+ $this->_set_fill_color($color);
+ $this->_pdf->filledRectangle($x1, $this->y($y1) - $h, $w, $h);
+ $this->_set_fill_transparency("Normal", $this->_current_opacity);
+ }
+
+ public function clipping_rectangle($x1, $y1, $w, $h)
+ {
+ $this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h);
+ }
+
+ public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
+ {
+ $this->_pdf->clippingRectangleRounded($x1, $this->y($y1) - $h, $w, $h, $rTL, $rTR, $rBR, $rBL);
+ }
+
+ public function clipping_polygon(array $points): void
+ {
+ // Adjust y values
+ for ($i = 1; $i < count($points); $i += 2) {
+ $points[$i] = $this->y($points[$i]);
+ }
+
+ $this->_pdf->clippingPolygon($points);
+ }
+
+ public function clipping_end()
+ {
+ $this->_pdf->clippingEnd();
+ }
+
+ public function save()
+ {
+ $this->_pdf->saveState();
+ }
+
+ public function restore()
+ {
+ $this->_pdf->restoreState();
+ }
+
+ public function rotate($angle, $x, $y)
+ {
+ $this->_pdf->rotate($angle, $x, $y);
+ }
+
+ public function skew($angle_x, $angle_y, $x, $y)
+ {
+ $this->_pdf->skew($angle_x, $angle_y, $x, $y);
+ }
+
+ public function scale($s_x, $s_y, $x, $y)
+ {
+ $this->_pdf->scale($s_x, $s_y, $x, $y);
+ }
+
+ public function translate($t_x, $t_y)
+ {
+ $this->_pdf->translate($t_x, $t_y);
+ }
+
+ public function transform($a, $b, $c, $d, $e, $f)
+ {
+ $this->_pdf->transform([$a, $b, $c, $d, $e, $f]);
+ }
+
+ public function polygon($points, $color, $width = null, $style = [], $fill = false)
+ {
+ $this->_set_fill_color($color);
+ $this->_set_stroke_color($color);
+
+ if (!$fill && isset($width)) {
+ $this->_set_line_style($width, "square", "miter", $style);
+ }
+
+ // Adjust y values
+ for ($i = 1; $i < count($points); $i += 2) {
+ $points[$i] = $this->y($points[$i]);
+ }
+
+ $this->_pdf->polygon($points, $fill);
+
+ $this->_set_fill_transparency("Normal", $this->_current_opacity);
+ $this->_set_line_transparency("Normal", $this->_current_opacity);
+ }
+
+ public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
+ {
+ $this->_set_fill_color($color);
+ $this->_set_stroke_color($color);
+
+ if (!$fill && isset($width)) {
+ $this->_set_line_style($width, "round", "round", $style);
+ }
+
+ $this->_pdf->ellipse($x, $this->y($y), $r, 0, 0, 8, 0, 360, 1, $fill);
+
+ $this->_set_fill_transparency("Normal", $this->_current_opacity);
+ $this->_set_line_transparency("Normal", $this->_current_opacity);
+ }
+
+ /**
+ * Convert image to a PNG image
+ *
+ * @param string $image_url
+ * @param string $type
+ *
+ * @return string|null The url of the newly converted image
+ */
+ protected function _convert_to_png($image_url, $type)
+ {
+ $filename = Cache::getTempImage($image_url);
+
+ if ($filename !== null && file_exists($filename)) {
+ return $filename;
+ }
+
+ $func_name = "imagecreatefrom$type";
+
+ set_error_handler([Helpers::class, "record_warnings"]);
+
+ if (!function_exists($func_name)) {
+ if (!method_exists(Helpers::class, $func_name)) {
+ throw new Exception("Function $func_name() not found. Cannot convert $type image: $image_url. Please install the image PHP extension.");
+ }
+ $func_name = [Helpers::class, $func_name];
+ }
+
+ try {
+ $im = call_user_func($func_name, $image_url);
+
+ if ($im) {
+ imageinterlace($im, false);
+
+ $tmp_dir = $this->_dompdf->getOptions()->getTempDir();
+ $tmp_name = @tempnam($tmp_dir, "{$type}_dompdf_img_");
+ @unlink($tmp_name);
+ $filename = "$tmp_name.png";
+
+ imagepng($im, $filename);
+ imagedestroy($im);
+ } else {
+ $filename = null;
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ if ($filename !== null) {
+ Cache::addTempImage($image_url, $filename);
+ }
+
+ return $filename;
+ }
+
+ public function image($img, $x, $y, $w, $h, $resolution = "normal")
+ {
+ [$width, $height, $type] = Helpers::dompdf_getimagesize($img, $this->get_dompdf()->getHttpContext());
+
+ $debug_png = $this->_dompdf->getOptions()->getDebugPng();
+
+ if ($debug_png) {
+ print "[image:$img|$width|$height|$type]";
+ }
+
+ switch ($type) {
+ case "jpeg":
+ if ($debug_png) {
+ print '!!!jpg!!!';
+ }
+ $this->_pdf->addJpegFromFile($img, $x, $this->y($y) - $h, $w, $h);
+ break;
+
+ case "webp":
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case "gif":
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case "bmp":
+ if ($debug_png) print "!!!{$type}!!!";
+ $img = $this->_convert_to_png($img, $type);
+ if ($img === null) {
+ if ($debug_png) print '!!!conversion to PDF failed!!!';
+ $this->image(Cache::$broken_image, $x, $y, $w, $h, $resolution);
+ break;
+ }
+
+ case "png":
+ if ($debug_png) print '!!!png!!!';
+
+ $this->_pdf->addPngFromFile($img, $x, $this->y($y) - $h, $w, $h);
+ break;
+
+ case "svg":
+ if ($debug_png) print '!!!SVG!!!';
+
+ $this->_pdf->addSvgFromFile($img, $x, $this->y($y) - $h, $w, $h);
+ break;
+
+ default:
+ if ($debug_png) print '!!!unknown!!!';
+ }
+ }
+
+ public function select($x, $y, $w, $h, $font, $size, $color = [0, 0, 0], $opts = [])
+ {
+ $pdf = $this->_pdf;
+
+ $font .= ".afm";
+ $pdf->selectFont($font);
+
+ if (!isset($pdf->acroFormId)) {
+ $pdf->addForm();
+ }
+
+ $ft = \Dompdf\Cpdf::ACROFORM_FIELD_CHOICE;
+ $ff = \Dompdf\Cpdf::ACROFORM_FIELD_CHOICE_COMBO;
+
+ $id = $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color);
+ $pdf->setFormFieldOpt($id, $opts);
+ }
+
+ public function textarea($x, $y, $w, $h, $font, $size, $color = [0, 0, 0])
+ {
+ $pdf = $this->_pdf;
+
+ $font .= ".afm";
+ $pdf->selectFont($font);
+
+ if (!isset($pdf->acroFormId)) {
+ $pdf->addForm();
+ }
+
+ $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT;
+ $ff = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT_MULTILINE;
+
+ $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color);
+ }
+
+ public function input($x, $y, $w, $h, $type, $font, $size, $color = [0, 0, 0])
+ {
+ $pdf = $this->_pdf;
+
+ $font .= ".afm";
+ $pdf->selectFont($font);
+
+ if (!isset($pdf->acroFormId)) {
+ $pdf->addForm();
+ }
+
+ $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT;
+ $ff = 0;
+
+ switch ($type) {
+ case 'text':
+ $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT;
+ break;
+ case 'password':
+ $ft = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT;
+ $ff = \Dompdf\Cpdf::ACROFORM_FIELD_TEXT_PASSWORD;
+ break;
+ case 'submit':
+ $ft = \Dompdf\Cpdf::ACROFORM_FIELD_BUTTON;
+ break;
+ }
+
+ $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color);
+ }
+
+ public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
+ {
+ $pdf = $this->_pdf;
+
+ $this->_set_fill_color($color);
+
+ $is_font_subsetting = $this->_dompdf->getOptions()->getIsFontSubsettingEnabled();
+ $pdf->selectFont($font . '.afm', '', true, $is_font_subsetting);
+
+ $pdf->addText($x, $this->y($y) - $pdf->getFontHeight($size), $size, $text, $angle, $word_space, $char_space);
+
+ $this->_set_fill_transparency("Normal", $this->_current_opacity);
+ }
+
+ public function javascript($code)
+ {
+ $this->_pdf->addJavascript($code);
+ }
+
+ //........................................................................
+
+ public function add_named_dest($anchorname)
+ {
+ $this->_pdf->addDestination($anchorname, "Fit");
+ }
+
+ public function add_link($url, $x, $y, $width, $height)
+ {
+ $y = $this->y($y) - $height;
+
+ if (strpos($url, '#') === 0) {
+ // Local link
+ $name = substr($url, 1);
+ if ($name) {
+ $this->_pdf->addInternalLink($name, $x, $y, $x + $width, $y + $height);
+ }
+ } else {
+ $this->_pdf->addLink($url, $x, $y, $x + $width, $y + $height);
+ }
+ }
+
+ /**
+ * @throws FontNotFoundException
+ */
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
+ {
+ $this->_pdf->selectFont($font, '', true, $this->_dompdf->getOptions()->getIsFontSubsettingEnabled());
+ return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing);
+ }
+
+ /**
+ * @throws FontNotFoundException
+ */
+ public function get_font_height($font, $size)
+ {
+ $options = $this->_dompdf->getOptions();
+ $this->_pdf->selectFont($font, '', true, $options->getIsFontSubsettingEnabled());
+
+ return $this->_pdf->getFontHeight($size) * $options->getFontHeightRatio();
+ }
+
+ /*function get_font_x_height($font, $size) {
+ $this->_pdf->selectFont($font);
+ $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
+ return $this->_pdf->getFontXHeight($size) * $ratio;
+ }*/
+
+ /**
+ * @throws FontNotFoundException
+ */
+ public function get_font_baseline($font, $size)
+ {
+ $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
+ return $this->get_font_height($font, $size) / $ratio;
+ }
+
+ /**
+ * Processes a callback or script on every page.
+ *
+ * The callback function receives the four parameters `int $pageNumber`,
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in
+ * that order. If a script is passed as string, the variables `$PAGE_NUM`,
+ * `$PAGE_COUNT`, `$pdf`, and `$fontMetrics` are available instead. Passing
+ * a script as string is deprecated and will be removed in a future version.
+ *
+ * This function can be used to add page numbers to all pages after the
+ * first one, for example.
+ *
+ * @param callable|string $callback The callback function or PHP script to process on every page
+ */
+ public function page_script($callback): void
+ {
+ if (is_string($callback)) {
+ $this->processPageScript(function (
+ int $PAGE_NUM,
+ int $PAGE_COUNT,
+ self $pdf,
+ FontMetrics $fontMetrics
+ ) use ($callback) {
+ eval($callback);
+ });
+ return;
+ }
+
+ $this->processPageScript($callback);
+ }
+
+ public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
+ {
+ $this->processPageScript(function (int $pageNumber, int $pageCount) use ($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle) {
+ $text = str_replace(
+ ["{PAGE_NUM}", "{PAGE_COUNT}"],
+ [$pageNumber, $pageCount],
+ $text
+ );
+ $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle);
+ });
+ }
+
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
+ {
+ $this->processPageScript(function () use ($x1, $y1, $x2, $y2, $color, $width, $style) {
+ $this->line($x1, $y1, $x2, $y2, $color, $width, $style);
+ });
+ }
+
+ /**
+ * @return int
+ */
+ public function new_page()
+ {
+ $this->_page_number++;
+ $this->_page_count++;
+
+ $ret = $this->_pdf->newPage();
+ $this->_pages[] = $ret;
+ return $ret;
+ }
+
+ protected function processPageScript(callable $callback): void
+ {
+ $pageNumber = 1;
+
+ foreach ($this->_pages as $pid) {
+ $this->reopen_object($pid);
+
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $callback($pageNumber, $this->_page_count, $this, $fontMetrics);
+
+ $this->close_object();
+ $pageNumber++;
+ }
+ }
+
+ public function stream($filename = "document.pdf", $options = [])
+ {
+ if (headers_sent()) {
+ die("Unable to stream pdf: headers already sent");
+ }
+
+ if (!isset($options["compress"])) $options["compress"] = true;
+ if (!isset($options["Attachment"])) $options["Attachment"] = true;
+
+ $debug = !$options['compress'];
+ $tmp = ltrim($this->_pdf->output($debug));
+
+ header("Cache-Control: private");
+ header("Content-Type: application/pdf");
+ header("Content-Length: " . mb_strlen($tmp, "8bit"));
+
+ $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
+ header(Helpers::buildContentDispositionHeader($attachment, $filename));
+
+ echo $tmp;
+ flush();
+ }
+
+ public function output($options = [])
+ {
+ if (!isset($options["compress"])) $options["compress"] = true;
+
+ $debug = !$options['compress'];
+
+ return $this->_pdf->output($debug);
+ }
+
+ /**
+ * Returns logging messages generated by the Cpdf class
+ *
+ * @return string
+ */
+ public function get_messages()
+ {
+ return $this->_pdf->messages;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/GD.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/GD.php
new file mode 100644
index 0000000..8c10e47
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/GD.php
@@ -0,0 +1,929 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Adapter;
+
+use Dompdf\Canvas;
+use Dompdf\Dompdf;
+use Dompdf\Helpers;
+use Dompdf\Image\Cache;
+
+/**
+ * Image rendering interface
+ *
+ * Renders to an image format supported by GD (jpeg, gif, png, xpm).
+ * Not super-useful day-to-day but handy nonetheless
+ *
+ * @package dompdf
+ */
+class GD implements Canvas
+{
+ /**
+ * @var Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * Resource handle for the image
+ *
+ * @var \GdImage|resource
+ */
+ protected $_img;
+
+ /**
+ * Resource handle for the image
+ *
+ * @var \GdImage[]|resource[]
+ */
+ protected $_imgs;
+
+ /**
+ * Apparent canvas width in pixels
+ *
+ * @var int
+ */
+ protected $_width;
+
+ /**
+ * Apparent canvas height in pixels
+ *
+ * @var int
+ */
+ protected $_height;
+
+ /**
+ * Actual image width in pixels
+ *
+ * @var int
+ */
+ protected $_actual_width;
+
+ /**
+ * Actual image height in pixels
+ *
+ * @var int
+ */
+ protected $_actual_height;
+
+ /**
+ * Current page number
+ *
+ * @var int
+ */
+ protected $_page_number;
+
+ /**
+ * Total number of pages
+ *
+ * @var int
+ */
+ protected $_page_count;
+
+ /**
+ * Image antialias factor
+ *
+ * @var float
+ */
+ protected $_aa_factor;
+
+ /**
+ * Allocated colors
+ *
+ * @var array
+ */
+ protected $_colors;
+
+ /**
+ * Background color
+ *
+ * @var int
+ */
+ protected $_bg_color;
+
+ /**
+ * Background color array
+ *
+ * @var int
+ */
+ protected $_bg_color_array;
+
+ /**
+ * Actual DPI
+ *
+ * @var int
+ */
+ protected $dpi;
+
+ /**
+ * Amount to scale font sizes
+ *
+ * Font sizes are 72 DPI, GD internally uses 96. Scale them proportionally.
+ * 72 / 96 = 0.75.
+ *
+ * @var float
+ */
+ const FONT_SCALE = 0.75;
+
+ /**
+ * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link CPDF::$PAPER_SIZES}) or
+ * an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`).
+ * @param string $orientation The paper orientation, either `portrait` or `landscape`.
+ * @param Dompdf $dompdf The Dompdf instance.
+ * @param float $aa_factor Anti-aliasing factor, 1 for no AA
+ * @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1
+ */
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null, $aa_factor = 1.0, $bg_color = [1, 1, 1, 0])
+ {
+ if (is_array($paper)) {
+ $size = array_map("floatval", $paper);
+ } else {
+ $paper = strtolower($paper);
+ $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
+ }
+
+ if (strtolower($orientation) === "landscape") {
+ [$size[2], $size[3]] = [$size[3], $size[2]];
+ }
+
+ if ($dompdf === null) {
+ $this->_dompdf = new Dompdf();
+ } else {
+ $this->_dompdf = $dompdf;
+ }
+
+ $this->dpi = $this->get_dompdf()->getOptions()->getDpi();
+
+ if ($aa_factor < 1) {
+ $aa_factor = 1;
+ }
+
+ $this->_aa_factor = $aa_factor;
+
+ $size[2] *= $aa_factor;
+ $size[3] *= $aa_factor;
+
+ $this->_width = $size[2] - $size[0];
+ $this->_height = $size[3] - $size[1];
+
+ $this->_actual_width = $this->_upscale($this->_width);
+ $this->_actual_height = $this->_upscale($this->_height);
+
+ $this->_page_number = $this->_page_count = 0;
+
+ if (is_null($bg_color) || !is_array($bg_color)) {
+ // Pure white bg
+ $bg_color = [1, 1, 1, 0];
+ }
+
+ $this->_bg_color_array = $bg_color;
+
+ $this->new_page();
+ }
+
+ public function get_dompdf()
+ {
+ return $this->_dompdf;
+ }
+
+ /**
+ * Return the GD image resource
+ *
+ * @return \GdImage|resource
+ */
+ public function get_image()
+ {
+ return $this->_img;
+ }
+
+ /**
+ * Return the image's width in pixels
+ *
+ * @return int
+ */
+ public function get_width()
+ {
+ return round($this->_width / $this->_aa_factor);
+ }
+
+ /**
+ * Return the image's height in pixels
+ *
+ * @return int
+ */
+ public function get_height()
+ {
+ return round($this->_height / $this->_aa_factor);
+ }
+
+ public function get_page_number()
+ {
+ return $this->_page_number;
+ }
+
+ public function get_page_count()
+ {
+ return $this->_page_count;
+ }
+
+ /**
+ * Sets the current page number
+ *
+ * @param int $num
+ */
+ public function set_page_number($num)
+ {
+ $this->_page_number = $num;
+ }
+
+ public function set_page_count($count)
+ {
+ $this->_page_count = $count;
+ }
+
+ public function set_opacity(float $opacity, string $mode = "Normal"): void
+ {
+ // FIXME
+ }
+
+ /**
+ * Allocate a new color. Allocate with GD as needed and store
+ * previously allocated colors in $this->_colors.
+ *
+ * @param array $color The new current color
+ * @return int The allocated color
+ */
+ protected function _allocate_color($color)
+ {
+ $a = isset($color["alpha"]) ? $color["alpha"] : 1;
+
+ if (isset($color["c"])) {
+ $color = Helpers::cmyk_to_rgb($color);
+ }
+
+ list($r, $g, $b) = $color;
+
+ $r = round($r * 255);
+ $g = round($g * 255);
+ $b = round($b * 255);
+ $a = round(127 - ($a * 127));
+
+ // Clip values
+ $r = $r > 255 ? 255 : $r;
+ $g = $g > 255 ? 255 : $g;
+ $b = $b > 255 ? 255 : $b;
+ $a = $a > 127 ? 127 : $a;
+
+ $r = $r < 0 ? 0 : $r;
+ $g = $g < 0 ? 0 : $g;
+ $b = $b < 0 ? 0 : $b;
+ $a = $a < 0 ? 0 : $a;
+
+ $key = sprintf("#%02X%02X%02X%02X", $r, $g, $b, $a);
+
+ if (isset($this->_colors[$key])) {
+ return $this->_colors[$key];
+ }
+
+ if ($a != 0) {
+ $this->_colors[$key] = imagecolorallocatealpha($this->get_image(), $r, $g, $b, $a);
+ } else {
+ $this->_colors[$key] = imagecolorallocate($this->get_image(), $r, $g, $b);
+ }
+
+ return $this->_colors[$key];
+ }
+
+ /**
+ * Scales value up to the current canvas DPI from 72 DPI
+ *
+ * @param float $length
+ * @return int
+ */
+ protected function _upscale($length)
+ {
+ return round(($length * $this->dpi) / 72 * $this->_aa_factor);
+ }
+
+ /**
+ * Scales value down from the current canvas DPI to 72 DPI
+ *
+ * @param float $length
+ * @return float
+ */
+ protected function _downscale($length)
+ {
+ return round(($length / $this->dpi * 72) / $this->_aa_factor);
+ }
+
+ protected function convertStyle(array $style, int $color, int $width): array
+ {
+ $gdStyle = [];
+
+ if (count($style) === 1) {
+ $style[] = $style[0];
+ }
+
+ foreach ($style as $index => $s) {
+ $d = $this->_upscale($s);
+
+ for ($i = 0; $i < $d; $i++) {
+ for ($j = 0; $j < $width; $j++) {
+ $gdStyle[] = $index % 2 === 0
+ ? $color
+ : IMG_COLOR_TRANSPARENT;
+ }
+ }
+ }
+
+ return $gdStyle;
+ }
+
+ public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
+ {
+ // Account for the fact that round and square caps are expected to
+ // extend outwards
+ if ($cap === "round" || $cap === "square") {
+ // Shift line by half width
+ $w = $width / 2;
+ $a = $x2 - $x1;
+ $b = $y2 - $y1;
+ $c = sqrt($a ** 2 + $b ** 2);
+ $dx = $a * $w / $c;
+ $dy = $b * $w / $c;
+
+ $x1 -= $dx;
+ $x2 -= $dx;
+ $y1 -= $dy;
+ $y2 -= $dy;
+
+ // Adapt dash pattern
+ if (is_array($style)) {
+ foreach ($style as $index => &$s) {
+ $s = $index % 2 === 0 ? $s + $width : $s - $width;
+ }
+ }
+ }
+
+ // Scale by the AA factor and DPI
+ $x1 = $this->_upscale($x1);
+ $y1 = $this->_upscale($y1);
+ $x2 = $this->_upscale($x2);
+ $y2 = $this->_upscale($y2);
+ $width = $this->_upscale($width);
+
+ $c = $this->_allocate_color($color);
+
+ // Convert the style array if required
+ if (is_array($style) && count($style) > 0) {
+ $gd_style = $this->convertStyle($style, $c, $width);
+
+ if (!empty($gd_style)) {
+ imagesetstyle($this->get_image(), $gd_style);
+ $c = IMG_COLOR_STYLED;
+ }
+ }
+
+ imagesetthickness($this->get_image(), $width);
+
+ imageline($this->get_image(), $x1, $y1, $x2, $y2, $c);
+ }
+
+ public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
+ {
+ // Account for the fact that round and square caps are expected to
+ // extend outwards
+ if ($cap === "round" || $cap === "square") {
+ // Adapt dash pattern
+ if (is_array($style)) {
+ foreach ($style as $index => &$s) {
+ $s = $index % 2 === 0 ? $s + $width : $s - $width;
+ }
+ }
+ }
+
+ // Scale by the AA factor and DPI
+ $x = $this->_upscale($x);
+ $y = $this->_upscale($y);
+ $w = $this->_upscale($r1 * 2);
+ $h = $this->_upscale($r2 * 2);
+ $width = $this->_upscale($width);
+
+ // Adapt angles as imagearc counts clockwise
+ $start = 360 - $aend;
+ $end = 360 - $astart;
+
+ $c = $this->_allocate_color($color);
+
+ // Convert the style array if required
+ if (is_array($style) && count($style) > 0) {
+ $gd_style = $this->convertStyle($style, $c, $width);
+
+ if (!empty($gd_style)) {
+ imagesetstyle($this->get_image(), $gd_style);
+ $c = IMG_COLOR_STYLED;
+ }
+ }
+
+ imagesetthickness($this->get_image(), $width);
+
+ imagearc($this->get_image(), $x, $y, $w, $h, $start, $end, $c);
+ }
+
+ public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
+ {
+ // Account for the fact that round and square caps are expected to
+ // extend outwards
+ if ($cap === "round" || $cap === "square") {
+ // Adapt dash pattern
+ if (is_array($style)) {
+ foreach ($style as $index => &$s) {
+ $s = $index % 2 === 0 ? $s + $width : $s - $width;
+ }
+ }
+ }
+
+ // Scale by the AA factor and DPI
+ $x1 = $this->_upscale($x1);
+ $y1 = $this->_upscale($y1);
+ $w = $this->_upscale($w);
+ $h = $this->_upscale($h);
+ $width = $this->_upscale($width);
+
+ $c = $this->_allocate_color($color);
+
+ // Convert the style array if required
+ if (is_array($style) && count($style) > 0) {
+ $gd_style = $this->convertStyle($style, $c, $width);
+
+ if (!empty($gd_style)) {
+ imagesetstyle($this->get_image(), $gd_style);
+ $c = IMG_COLOR_STYLED;
+ }
+ }
+
+ imagesetthickness($this->get_image(), $width);
+
+ if ($c === IMG_COLOR_STYLED) {
+ imagepolygon($this->get_image(), [
+ $x1, $y1,
+ $x1 + $w, $y1,
+ $x1 + $w, $y1 + $h,
+ $x1, $y1 + $h
+ ], $c);
+ } else {
+ imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
+ }
+ }
+
+ public function filled_rectangle($x1, $y1, $w, $h, $color)
+ {
+ // Scale by the AA factor and DPI
+ $x1 = $this->_upscale($x1);
+ $y1 = $this->_upscale($y1);
+ $w = $this->_upscale($w);
+ $h = $this->_upscale($h);
+
+ $c = $this->_allocate_color($color);
+
+ imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
+ }
+
+ public function clipping_rectangle($x1, $y1, $w, $h)
+ {
+ // @todo
+ }
+
+ public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
+ {
+ // @todo
+ }
+
+ public function clipping_polygon(array $points): void
+ {
+ // @todo
+ }
+
+ public function clipping_end()
+ {
+ // @todo
+ }
+
+ public function save()
+ {
+ $this->get_dompdf()->getOptions()->setDpi(72);
+ }
+
+ public function restore()
+ {
+ $this->get_dompdf()->getOptions()->setDpi($this->dpi);
+ }
+
+ public function rotate($angle, $x, $y)
+ {
+ // @todo
+ }
+
+ public function skew($angle_x, $angle_y, $x, $y)
+ {
+ // @todo
+ }
+
+ public function scale($s_x, $s_y, $x, $y)
+ {
+ // @todo
+ }
+
+ public function translate($t_x, $t_y)
+ {
+ // @todo
+ }
+
+ public function transform($a, $b, $c, $d, $e, $f)
+ {
+ // @todo
+ }
+
+ public function polygon($points, $color, $width = null, $style = [], $fill = false)
+ {
+ // Scale each point by the AA factor and DPI
+ foreach (array_keys($points) as $i) {
+ $points[$i] = $this->_upscale($points[$i]);
+ }
+
+ $width = isset($width) ? $this->_upscale($width) : null;
+
+ $c = $this->_allocate_color($color);
+
+ // Convert the style array if required
+ if (is_array($style) && count($style) > 0 && isset($width) && !$fill) {
+ $gd_style = $this->convertStyle($style, $c, $width);
+
+ if (!empty($gd_style)) {
+ imagesetstyle($this->get_image(), $gd_style);
+ $c = IMG_COLOR_STYLED;
+ }
+ }
+
+ imagesetthickness($this->get_image(), isset($width) ? $width : 0);
+
+ if ($fill) {
+ imagefilledpolygon($this->get_image(), $points, $c);
+ } else {
+ imagepolygon($this->get_image(), $points, $c);
+ }
+ }
+
+ public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
+ {
+ // Scale by the AA factor and DPI
+ $x = $this->_upscale($x);
+ $y = $this->_upscale($y);
+ $d = $this->_upscale(2 * $r);
+ $width = isset($width) ? $this->_upscale($width) : null;
+
+ $c = $this->_allocate_color($color);
+
+ // Convert the style array if required
+ if (is_array($style) && count($style) > 0 && isset($width) && !$fill) {
+ $gd_style = $this->convertStyle($style, $c, $width);
+
+ if (!empty($gd_style)) {
+ imagesetstyle($this->get_image(), $gd_style);
+ $c = IMG_COLOR_STYLED;
+ }
+ }
+
+ imagesetthickness($this->get_image(), isset($width) ? $width : 0);
+
+ if ($fill) {
+ imagefilledellipse($this->get_image(), $x, $y, $d, $d, $c);
+ } else {
+ imageellipse($this->get_image(), $x, $y, $d, $d, $c);
+ }
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function image($img, $x, $y, $w, $h, $resolution = "normal")
+ {
+ $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext());
+
+ if (!$img_type) {
+ return;
+ }
+
+ $func_name = "imagecreatefrom$img_type";
+ if (!function_exists($func_name)) {
+ if (!method_exists(Helpers::class, $func_name)) {
+ throw new \Exception("Function $func_name() not found. Cannot convert $img_type image: $img. Please install the image PHP extension.");
+ }
+ $func_name = [Helpers::class, $func_name];
+ }
+ $src = @call_user_func($func_name, $img);
+
+ if (!$src) {
+ return; // Probably should add to $_dompdf_errors or whatever here
+ }
+
+ // Scale by the AA factor and DPI
+ $x = $this->_upscale($x);
+ $y = $this->_upscale($y);
+
+ $w = $this->_upscale($w);
+ $h = $this->_upscale($h);
+
+ $img_w = imagesx($src);
+ $img_h = imagesy($src);
+
+ imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h);
+ }
+
+ public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0)
+ {
+ // Scale by the AA factor and DPI
+ $x = $this->_upscale($x);
+ $y = $this->_upscale($y);
+ $size = $this->_upscale($size) * self::FONT_SCALE;
+
+ $h = round($this->get_font_height_actual($font, $size));
+ $c = $this->_allocate_color($color);
+
+ // imagettftext() converts numeric entities to their respective
+ // character. Preserve any originally double encoded entities to be
+ // represented as is.
+ // eg: &amp;#160; will render &#160; rather than its character.
+ $text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&#38;\1', $text);
+
+ $text = mb_encode_numericentity($text, [0x0080, 0xff, 0, 0xff], 'UTF-8');
+
+ $font = $this->get_ttf_file($font);
+
+ // FIXME: word spacing
+ imagettftext($this->get_image(), $size, $angle, $x, $y + $h, $c, $font, $text);
+ }
+
+ public function javascript($code)
+ {
+ // Not implemented
+ }
+
+ public function add_named_dest($anchorname)
+ {
+ // Not implemented
+ }
+
+ public function add_link($url, $x, $y, $width, $height)
+ {
+ // Not implemented
+ }
+
+ public function add_info(string $label, string $value): void
+ {
+ // N/A
+ }
+
+ public function set_default_view($view, $options = [])
+ {
+ // N/A
+ }
+
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
+ {
+ $font = $this->get_ttf_file($font);
+ $size = $this->_upscale($size) * self::FONT_SCALE;
+
+ // imagettfbbox() converts numeric entities to their respective
+ // character. Preserve any originally double encoded entities to be
+ // represented as is.
+ // eg: &amp;#160; will render &#160; rather than its character.
+ $text = preg_replace('/&(#(?:x[a-fA-F0-9]+|[0-9]+);)/', '&#38;\1', $text);
+
+ $text = mb_encode_numericentity($text, [0x0080, 0xffff, 0, 0xffff], 'UTF-8');
+
+ // FIXME: word spacing
+ list($x1, , $x2) = imagettfbbox($size, 0, $font, $text);
+
+ // Add additional 1pt to prevent text overflow issues
+ return $this->_downscale($x2 - $x1) + 1;
+ }
+
+ /**
+ * @param string|null $font
+ * @return string
+ */
+ public function get_ttf_file($font)
+ {
+ if ($font === null) {
+ $font = "";
+ }
+
+ if ( stripos($font, ".ttf") === false ) {
+ $font .= ".ttf";
+ }
+
+ if (!file_exists($font)) {
+ $font_metrics = $this->_dompdf->getFontMetrics();
+ $font = $font_metrics->getFont($this->_dompdf->getOptions()->getDefaultFont()) . ".ttf";
+ if (!file_exists($font)) {
+ if (strpos($font, "mono")) {
+ $font = $font_metrics->getFont("DejaVu Mono") . ".ttf";
+ } elseif (strpos($font, "sans") !== false) {
+ $font = $font_metrics->getFont("DejaVu Sans") . ".ttf";
+ } elseif (strpos($font, "serif")) {
+ $font = $font_metrics->getFont("DejaVu Serif") . ".ttf";
+ } else {
+ $font = $font_metrics->getFont("DejaVu Sans") . ".ttf";
+ }
+ }
+ }
+
+ return $font;
+ }
+
+ public function get_font_height($font, $size)
+ {
+ $size = $this->_upscale($size) * self::FONT_SCALE;
+
+ $height = $this->get_font_height_actual($font, $size);
+
+ return $this->_downscale($height);
+ }
+
+ /**
+ * @param string $font
+ * @param float $size
+ *
+ * @return float
+ */
+ protected function get_font_height_actual($font, $size)
+ {
+ $font = $this->get_ttf_file($font);
+ $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
+
+ // FIXME: word spacing
+ list(, $y2, , , , $y1) = imagettfbbox($size, 0, $font, "MXjpqytfhl"); // Test string with ascenders, descenders and caps
+ return ($y2 - $y1) * $ratio;
+ }
+
+ public function get_font_baseline($font, $size)
+ {
+ $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
+ return $this->get_font_height($font, $size) / $ratio;
+ }
+
+ public function new_page()
+ {
+ $this->_page_number++;
+ $this->_page_count++;
+
+ $this->_img = imagecreatetruecolor($this->_actual_width, $this->_actual_height);
+
+ $this->_bg_color = $this->_allocate_color($this->_bg_color_array);
+ imagealphablending($this->_img, true);
+ imagesavealpha($this->_img, true);
+ imagefill($this->_img, 0, 0, $this->_bg_color);
+
+ $this->_imgs[] = $this->_img;
+ }
+
+ public function open_object()
+ {
+ // N/A
+ }
+
+ public function close_object()
+ {
+ // N/A
+ }
+
+ public function add_object()
+ {
+ // N/A
+ }
+
+ public function page_script($callback): void
+ {
+ // N/A
+ }
+
+ public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
+ {
+ // N/A
+ }
+
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
+ {
+ // N/A
+ }
+
+ /**
+ * Streams the image to the client.
+ *
+ * @param string $filename The filename to present to the client.
+ * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
+ * 'page' => Number of the page to output (defaults to the first); 'Attachment': 1 or 0 (default 1).
+ */
+ public function stream($filename, $options = [])
+ {
+ if (headers_sent()) {
+ die("Unable to stream image: headers already sent");
+ }
+
+ if (!isset($options["type"])) $options["type"] = "png";
+ if (!isset($options["Attachment"])) $options["Attachment"] = true;
+ $type = strtolower($options["type"]);
+
+ switch ($type) {
+ case "jpg":
+ case "jpeg":
+ $contentType = "image/jpeg";
+ $extension = ".jpg";
+ break;
+ case "png":
+ default:
+ $contentType = "image/png";
+ $extension = ".png";
+ break;
+ }
+
+ header("Cache-Control: private");
+ header("Content-Type: $contentType");
+
+ $filename = str_replace(["\n", "'"], "", basename($filename, ".$type")) . $extension;
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
+ header(Helpers::buildContentDispositionHeader($attachment, $filename));
+
+ $this->_output($options);
+ flush();
+ }
+
+ /**
+ * Returns the image as a string.
+ *
+ * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
+ * 'page' => Number of the page to output (defaults to the first).
+ * @return string
+ */
+ public function output($options = [])
+ {
+ ob_start();
+
+ $this->_output($options);
+
+ return ob_get_clean();
+ }
+
+ /**
+ * Outputs the image stream directly.
+ *
+ * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
+ * 'page' => Number of the page to output (defaults to the first).
+ */
+ protected function _output($options = [])
+ {
+ if (!isset($options["type"])) $options["type"] = "png";
+ if (!isset($options["page"])) $options["page"] = 1;
+ $type = strtolower($options["type"]);
+
+ if (isset($this->_imgs[$options["page"] - 1])) {
+ $img = $this->_imgs[$options["page"] - 1];
+ } else {
+ $img = $this->_imgs[0];
+ }
+
+ // Perform any antialiasing
+ if ($this->_aa_factor != 1) {
+ $dst_w = round($this->_actual_width / $this->_aa_factor);
+ $dst_h = round($this->_actual_height / $this->_aa_factor);
+ $dst = imagecreatetruecolor($dst_w, $dst_h);
+ imagecopyresampled($dst, $img, 0, 0, 0, 0,
+ $dst_w, $dst_h,
+ $this->_actual_width, $this->_actual_height);
+ } else {
+ $dst = $img;
+ }
+
+ switch ($type) {
+ case "jpg":
+ case "jpeg":
+ if (!isset($options["quality"])) {
+ $options["quality"] = 75;
+ }
+
+ imagejpeg($dst, null, $options["quality"]);
+ break;
+ case "png":
+ default:
+ imagepng($dst);
+ break;
+ }
+
+ if ($this->_aa_factor != 1) {
+ imagedestroy($dst);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/PDFLib.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/PDFLib.php
new file mode 100644
index 0000000..ee5ae8d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Adapter/PDFLib.php
@@ -0,0 +1,1450 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Adapter;
+
+use Dompdf\Canvas;
+use Dompdf\Dompdf;
+use Dompdf\Exception;
+use Dompdf\FontMetrics;
+use Dompdf\Helpers;
+use Dompdf\Image\Cache;
+
+/**
+ * PDF rendering interface
+ *
+ * Dompdf\Adapter\PDFLib provides a simple, stateless interface to the one
+ * provided by PDFLib.
+ *
+ * Unless otherwise mentioned, all dimensions are in points (1/72 in).
+ * The coordinate origin is in the top left corner and y values
+ * increase downwards.
+ *
+ * See {@link http://www.pdflib.com/} for more complete documentation
+ * on the underlying PDFlib functions.
+ *
+ * @package dompdf
+ */
+class PDFLib implements Canvas
+{
+
+ /**
+ * Dimensions of paper sizes in points
+ *
+ * @var array
+ */
+ public static $PAPER_SIZES = []; // Set to Dompdf\Adapter\CPDF::$PAPER_SIZES below.
+
+ /**
+ * Whether to create PDFs in memory or on disk
+ *
+ * @var bool
+ */
+ static $IN_MEMORY = true;
+
+ /**
+ * Saves the major version of PDFLib for compatibility requests
+ *
+ * @var null|int
+ */
+ protected static $MAJOR_VERSION = null;
+
+
+ /**
+ * Transforms the list of native fonts into PDFLib compatible names (casesensitive)
+ *
+ * @var array
+ */
+ public static $nativeFontsTpPDFLib = [
+ "courier" => "Courier",
+ "courier-bold" => "Courier-Bold",
+ "courier-oblique" => "Courier-Oblique",
+ "courier-boldoblique" => "Courier-BoldOblique",
+ "helvetica" => "Helvetica",
+ "helvetica-bold" => "Helvetica-Bold",
+ "helvetica-oblique" => "Helvetica-Oblique",
+ "helvetica-boldoblique" => "Helvetica-BoldOblique",
+ "times" => "Times-Roman",
+ "times-roman" => "Times-Roman",
+ "times-bold" => "Times-Bold",
+ "times-italic" => "Times-Italic",
+ "times-bolditalic" => "Times-BoldItalic",
+ "symbol" => "Symbol",
+ "zapfdinbats" => "ZapfDingbats",
+ "zapfdingbats" => "ZapfDingbats",
+ ];
+
+ /**
+ * @var \Dompdf\Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * Instance of PDFLib class
+ *
+ * @var \PDFLib
+ */
+ protected $_pdf;
+
+ /**
+ * Name of temporary file used for PDFs created on disk
+ *
+ * @var string
+ */
+ protected $_file;
+
+ /**
+ * PDF width, in points
+ *
+ * @var float
+ */
+ protected $_width;
+
+ /**
+ * PDF height, in points
+ *
+ * @var float
+ */
+ protected $_height;
+
+ /**
+ * Last fill color used
+ *
+ * @var array
+ */
+ protected $_last_fill_color;
+
+ /**
+ * Last stroke color used
+ *
+ * @var array
+ */
+ protected $_last_stroke_color;
+
+ /**
+ * The current opacity level
+ *
+ * @var float|null
+ */
+ protected $_current_opacity;
+
+ /**
+ * Cache of image handles
+ *
+ * @var array
+ */
+ protected $_imgs;
+
+ /**
+ * Cache of font handles
+ *
+ * @var array
+ */
+ protected $_fonts;
+
+ /**
+ * Cache of fontFile checks
+ *
+ * @var array
+ */
+ protected $_fontsFiles;
+
+ /**
+ * List of objects (templates) to add to multiple pages
+ *
+ * @var array
+ */
+ protected $_objs;
+
+ /**
+ * List of gstate objects created for this PDF (for reuse)
+ *
+ * @var array
+ */
+ protected $_gstates = [];
+
+ /**
+ * Current page number
+ *
+ * @var int
+ */
+ protected $_page_number;
+
+ /**
+ * Total number of pages
+ *
+ * @var int
+ */
+ protected $_page_count;
+
+ /**
+ * Array of pages for accessing after rendering is initially complete
+ *
+ * @var array
+ */
+ protected $_pages;
+
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null)
+ {
+ if (is_array($paper)) {
+ $size = array_map("floatval", $paper);
+ } else {
+ $paper = strtolower($paper);
+ $size = self::$PAPER_SIZES[$paper] ?? self::$PAPER_SIZES["letter"];
+ }
+
+ if (strtolower($orientation) === "landscape") {
+ [$size[2], $size[3]] = [$size[3], $size[2]];
+ }
+
+ $this->_width = $size[2] - $size[0];
+ $this->_height = $size[3] - $size[1];
+
+ if ($dompdf === null) {
+ $this->_dompdf = new Dompdf();
+ } else {
+ $this->_dompdf = $dompdf;
+ }
+
+ $this->_pdf = new \PDFLib();
+
+ $license = $dompdf->getOptions()->getPdflibLicense();
+ if (strlen($license) > 0) {
+ $this->setPDFLibParameter("license", $license);
+ }
+
+ if ($this->getPDFLibMajorVersion() < 10) {
+ $this->setPDFLibParameter("textformat", "utf8");
+ }
+ if ($this->getPDFLibMajorVersion() >= 7) {
+ $this->setPDFLibParameter("errorpolicy", "return");
+ // $this->_pdf->set_option('logging={filename=' . \APP_PATH . '/logs/pdflib.log classes={api=1 warning=2}}');
+ // $this->_pdf->set_option('errorpolicy=exception');
+ } else {
+ $this->setPDFLibParameter("fontwarning", "false");
+ }
+
+ $searchPath = $this->_dompdf->getOptions()->getFontDir();
+ if (empty($searchPath) === false) {
+ $this->_pdf->set_option('searchpath={' . $searchPath . '}');
+ }
+
+ // fetch PDFLib version information for the producer field
+ $this->_pdf->set_info("Producer Addendum", sprintf("%s + PDFLib %s", $dompdf->version, $this->getPDFLibMajorVersion()));
+
+ // Silence pedantic warnings about missing TZ settings
+ $tz = @date_default_timezone_get();
+ date_default_timezone_set("UTC");
+ $this->_pdf->set_info("Date", date("Y-m-d"));
+ date_default_timezone_set($tz);
+
+ if (self::$IN_MEMORY) {
+ $this->_pdf->begin_document("", "");
+ } else {
+ $tmp_dir = $this->_dompdf->getOptions()->getTempDir();
+ $tmp_name = @tempnam($tmp_dir, "libdompdf_pdf_");
+ @unlink($tmp_name);
+ $this->_file = "$tmp_name.pdf";
+ $this->_pdf->begin_document($this->_file, "");
+ }
+
+ $this->_pdf->begin_page_ext($this->_width, $this->_height, "");
+
+ $this->_page_number = $this->_page_count = 1;
+
+ $this->_imgs = [];
+ $this->_fonts = [];
+ $this->_objs = [];
+ }
+
+ function get_dompdf()
+ {
+ return $this->_dompdf;
+ }
+
+ /**
+ * Close the pdf
+ */
+ protected function _close()
+ {
+ $this->_place_objects();
+
+ // Close all pages
+ $this->_pdf->suspend_page("");
+ for ($p = 1; $p <= $this->_page_count; $p++) {
+ $this->_pdf->resume_page("pagenumber=$p");
+ $this->_pdf->end_page_ext("");
+ }
+
+ $this->_pdf->end_document("");
+ }
+
+
+ /**
+ * Returns the PDFLib instance
+ *
+ * @return PDFLib
+ */
+ public function get_pdflib()
+ {
+ return $this->_pdf;
+ }
+
+ public function add_info(string $label, string $value): void
+ {
+ $this->_pdf->set_info($label, $value);
+ }
+
+ /**
+ * Opens a new 'object' (template in PDFLib-speak)
+ *
+ * While an object is open, all drawing actions are recorded to the
+ * object instead of being drawn on the current page. Objects can
+ * be added later to a specific page or to several pages.
+ *
+ * The return value is an integer ID for the new object.
+ *
+ * @see PDFLib::close_object()
+ * @see PDFLib::add_object()
+ *
+ * @return int
+ */
+ public function open_object()
+ {
+ $this->_pdf->suspend_page("");
+ if ($this->getPDFLibMajorVersion() >= 7) {
+ $ret = $this->_pdf->begin_template_ext($this->_width, $this->_height, null);
+ } else {
+ $ret = $this->_pdf->begin_template($this->_width, $this->_height);
+ }
+ $this->_pdf->save();
+ $this->_objs[$ret] = ["start_page" => $this->_page_number];
+
+ return $ret;
+ }
+
+ /**
+ * Reopen an existing object (NOT IMPLEMENTED)
+ * PDFLib does not seem to support reopening templates.
+ *
+ * @param int $object the ID of a previously opened object
+ *
+ * @throws Exception
+ * @return void
+ */
+ public function reopen_object($object)
+ {
+ throw new Exception("PDFLib does not support reopening objects.");
+ }
+
+ /**
+ * Close the current template
+ *
+ * @see PDFLib::open_object()
+ */
+ public function close_object()
+ {
+ $this->_pdf->restore();
+ if ($this->getPDFLibMajorVersion() >= 7) {
+ $this->_pdf->end_template_ext($this->_width, $this->_height);
+ } else {
+ $this->_pdf->end_template();
+ }
+ $this->_pdf->resume_page("pagenumber=" . $this->_page_number);
+ }
+
+ /**
+ * Adds the specified object to the document
+ *
+ * $where can be one of:
+ * - 'add' add to current page only
+ * - 'all' add to every page from the current one onwards
+ * - 'odd' add to all odd numbered pages from now on
+ * - 'even' add to all even numbered pages from now on
+ * - 'next' add the object to the next page only
+ * - 'nextodd' add to all odd numbered pages from the next one
+ * - 'nexteven' add to all even numbered pages from the next one
+ *
+ * @param int $object the object handle returned by open_object()
+ * @param string $where
+ */
+ public function add_object($object, $where = 'all')
+ {
+
+ if (mb_strpos($where, "next") !== false) {
+ $this->_objs[$object]["start_page"]++;
+ $where = str_replace("next", "", $where);
+ if ($where == "") {
+ $where = "add";
+ }
+ }
+
+ $this->_objs[$object]["where"] = $where;
+ }
+
+ /**
+ * Stops the specified template from appearing in the document.
+ *
+ * The object will stop being displayed on the page following the
+ * current one.
+ *
+ * @param int $object
+ */
+ public function stop_object($object)
+ {
+
+ if (!isset($this->_objs[$object])) {
+ return;
+ }
+
+ $start = $this->_objs[$object]["start_page"];
+ $where = $this->_objs[$object]["where"];
+
+ // Place the object on this page if required
+ if ($this->_page_number >= $start &&
+ (($this->_page_number % 2 == 0 && $where === "even") ||
+ ($this->_page_number % 2 == 1 && $where === "odd") ||
+ ($where === "all"))
+ ) {
+ $this->_pdf->fit_image($object, 0, 0, "");
+ }
+
+ $this->_objs[$object] = null;
+ unset($this->_objs[$object]);
+ }
+
+ /**
+ * Add all active objects to the current page
+ */
+ protected function _place_objects()
+ {
+
+ foreach ($this->_objs as $obj => $props) {
+ $start = $props["start_page"];
+ $where = $props["where"];
+
+ // Place the object on this page if required
+ if ($this->_page_number >= $start &&
+ (($this->_page_number % 2 == 0 && $where === "even") ||
+ ($this->_page_number % 2 == 1 && $where === "odd") ||
+ ($where === "all"))
+ ) {
+ $this->_pdf->fit_image($obj, 0, 0, "");
+ }
+ }
+ }
+
+ public function get_width()
+ {
+ return $this->_width;
+ }
+
+ public function get_height()
+ {
+ return $this->_height;
+ }
+
+ public function get_page_number()
+ {
+ return $this->_page_number;
+ }
+
+ public function get_page_count()
+ {
+ return $this->_page_count;
+ }
+
+ /**
+ * @param $num
+ */
+ public function set_page_number($num)
+ {
+ $this->_page_number = (int)$num;
+ }
+
+ public function set_page_count($count)
+ {
+ $this->_page_count = (int)$count;
+ }
+
+ /**
+ * Sets the line style
+ *
+ * @param float $width
+ * @param string $cap
+ * @param string $join
+ * @param array $dash
+ */
+ protected function _set_line_style($width, $cap, $join, $dash)
+ {
+ if (!is_array($dash)) {
+ $dash = [];
+ }
+
+ // Work around PDFLib limitation with 0 dash length:
+ // Value 0 for option 'dasharray' is too small (minimum 1.5e-05)
+ foreach ($dash as &$d) {
+ if ($d == 0) {
+ $d = 1.5e-5;
+ }
+ }
+
+ if (count($dash) === 1) {
+ $dash[] = $dash[0];
+ }
+
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ if (count($dash) > 1) {
+ $this->_pdf->set_graphics_option("dasharray={" . implode(" ", $dash) . "}");
+ } else {
+ $this->_pdf->set_graphics_option("dasharray=none");
+ }
+ } else {
+ if (count($dash) > 1) {
+ $this->_pdf->setdashpattern("dasharray={" . implode(" ", $dash) . "}");
+ } else {
+ $this->_pdf->setdash(0, 0);
+ }
+ }
+
+ switch ($join) {
+ case "miter":
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->_pdf->set_graphics_option('linejoin=0');
+ } else {
+ $this->_pdf->setlinejoin(0);
+ }
+ break;
+
+ case "round":
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->_pdf->set_graphics_option('linejoin=1');
+ } else {
+ $this->_pdf->setlinejoin(1);
+ }
+ break;
+
+ case "bevel":
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->_pdf->set_graphics_option('linejoin=2');
+ } else {
+ $this->_pdf->setlinejoin(2);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ switch ($cap) {
+ case "butt":
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->_pdf->set_graphics_option('linecap=0');
+ } else {
+ $this->_pdf->setlinecap(0);
+ }
+ break;
+
+ case "round":
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->_pdf->set_graphics_option('linecap=1');
+ } else {
+ $this->_pdf->setlinecap(1);
+ }
+ break;
+
+ case "square":
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->_pdf->set_graphics_option('linecap=2');
+ } else {
+ $this->_pdf->setlinecap(2);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ $this->_pdf->setlinewidth($width);
+ }
+
+ /**
+ * Sets the line color
+ *
+ * @param array $color array(r,g,b)
+ */
+ protected function _set_stroke_color($color)
+ {
+ // TODO: we should check the current PDF stroke color
+ // instead of the cached value
+ if ($this->_last_stroke_color == $color) {
+ // FIXME: do nothing, this optimization is broken by the
+ // stroke being set as a side effect of other operations
+ //return;
+ }
+
+ $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
+ if (isset($this->_current_opacity)) {
+ $alpha *= $this->_current_opacity;
+ }
+
+ $this->_last_stroke_color = $color;
+
+ if (isset($color[3])) {
+ $type = "cmyk";
+ list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], $color[3]];
+ } elseif (isset($color[2])) {
+ $type = "rgb";
+ list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], null];
+ } else {
+ $type = "gray";
+ list($c1, $c2, $c3, $c4) = [$color[0], $color[1], null, null];
+ }
+
+ $this->_set_stroke_opacity($alpha, "Normal");
+ $this->_pdf->setcolor("stroke", $type, $c1, $c2, $c3, $c4);
+ }
+
+ /**
+ * Sets the fill color
+ *
+ * @param array $color array(r,g,b)
+ */
+ protected function _set_fill_color($color)
+ {
+ // TODO: we should check the current PDF fill color
+ // instead of the cached value
+ if ($this->_last_fill_color == $color) {
+ // FIXME: do nothing, this optimization is broken by the
+ // fill being set as a side effect of other operations
+ //return;
+ }
+
+ $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
+ if (isset($this->_current_opacity)) {
+ $alpha *= $this->_current_opacity;
+ }
+
+ $this->_last_fill_color = $color;
+
+ if (isset($color[3])) {
+ $type = "cmyk";
+ list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], $color[3]];
+ } elseif (isset($color[2])) {
+ $type = "rgb";
+ list($c1, $c2, $c3, $c4) = [$color[0], $color[1], $color[2], null];
+ } else {
+ $type = "gray";
+ list($c1, $c2, $c3, $c4) = [$color[0], $color[1], null, null];
+ }
+
+ $this->_set_fill_opacity($alpha, "Normal");
+ $this->_pdf->setcolor("fill", $type, $c1, $c2, $c3, $c4);
+ }
+
+ /**
+ * Sets the fill opacity
+ *
+ * @param float $opacity
+ * @param string $mode
+ */
+ public function _set_fill_opacity($opacity, $mode = "Normal")
+ {
+ if ($mode === "Normal" && isset($opacity)) {
+ $this->_set_gstate("opacityfill=$opacity");
+ }
+ }
+
+ /**
+ * Sets the stroke opacity
+ *
+ * @param float $opacity
+ * @param string $mode
+ */
+ public function _set_stroke_opacity($opacity, $mode = "Normal")
+ {
+ if ($mode === "Normal" && isset($opacity)) {
+ $this->_set_gstate("opacitystroke=$opacity");
+ }
+ }
+
+ public function set_opacity(float $opacity, string $mode = "Normal"): void
+ {
+ if ($mode === "Normal") {
+ $this->_set_gstate("opacityfill=$opacity opacitystroke=$opacity");
+ $this->_current_opacity = $opacity;
+ }
+ }
+
+ /**
+ * Sets the gstate
+ *
+ * @param $gstate_options
+ * @return int
+ */
+ public function _set_gstate($gstate_options)
+ {
+ if (($gstate = array_search($gstate_options, $this->_gstates)) === false) {
+ $gstate = $this->_pdf->create_gstate($gstate_options);
+ $this->_gstates[$gstate] = $gstate_options;
+ }
+
+ return $this->_pdf->set_gstate($gstate);
+ }
+
+ public function set_default_view($view, $options = [])
+ {
+ // TODO
+ // http://www.pdflib.com/fileadmin/pdflib/pdf/manuals/PDFlib-8.0.2-API-reference.pdf
+ /**
+ * fitheight Fit the page height to the window, with the x coordinate left at the left edge of the window.
+ * fitrect Fit the rectangle specified by left, bottom, right, and top to the window.
+ * fitvisible Fit the visible contents of the page (the ArtBox) to the window.
+ * fitvisibleheight Fit the visible contents of the page to the window with the x coordinate left at the left edge of the window.
+ * fitvisiblewidth Fit the visible contents of the page to the window with the y coordinate top at the top edge of the window.
+ * fitwidth Fit the page width to the window, with the y coordinate top at the top edge of the window.
+ * fitwindow Fit the complete page to the window.
+ * fixed
+ */
+ //$this->setPDFLibParameter("openaction", $view);
+ }
+
+ /**
+ * Loads a specific font and stores the corresponding descriptor.
+ *
+ * @param string $font
+ * @param string $encoding
+ * @param string $options
+ *
+ * @return int the font descriptor for the font
+ */
+ protected function _load_font($font, $encoding = null, $options = "")
+ {
+ // Fix for PDFLibs case-sensitive font names
+ $baseFont = basename($font);
+ $isNativeFont = false;
+ if (isset(self::$nativeFontsTpPDFLib[$baseFont])) {
+ $font = self::$nativeFontsTpPDFLib[$baseFont];
+ $isNativeFont = true;
+ }
+
+ // Check if the font is a native PDF font
+ // Embed non-native fonts
+ $test = strtolower($baseFont);
+ if (in_array($test, DOMPDF::$nativeFonts)) {
+ $font = basename($font);
+ } else {
+ // Embed non-native fonts
+ $options .= " embedding=true";
+ }
+
+ $options .= " autosubsetting=" . ($this->_dompdf->getOptions()->getIsFontSubsettingEnabled() === false ? "false" : "true");
+
+ if (is_null($encoding)) {
+ // Unicode encoding is only available for the commerical
+ // version of PDFlib and not PDFlib-Lite
+ if (strlen($this->_dompdf->getOptions()->getPdflibLicense()) > 0) {
+ $encoding = "unicode";
+ } else {
+ $encoding = "auto";
+ }
+ }
+
+ $key = "$font:$encoding:$options";
+ if (isset($this->_fonts[$key])) {
+ return $this->_fonts[$key];
+ }
+
+ // Native fonts are build in, just load it
+ if ($isNativeFont) {
+ $this->_fonts[$key] = $this->_pdf->load_font($font, $encoding, $options);
+
+ return $this->_fonts[$key];
+ }
+
+ $fontOutline = $this->getPDFLibParameter("FontOutline", 1);
+ if ($fontOutline === "" || $fontOutline <= 0) {
+ $families = $this->_dompdf->getFontMetrics()->getFontFamilies();
+ foreach ($families as $files) {
+ foreach ($files as $file) {
+ $face = basename($file);
+ $afm = null;
+
+ if (isset($this->_fontsFiles[$face])) {
+ continue;
+ }
+
+ // Prefer ttfs to afms
+ if (file_exists("$file.ttf")) {
+ $outline = "$file.ttf";
+ } elseif (file_exists("$file.TTF")) {
+ $outline = "$file.TTF";
+ } elseif (file_exists("$file.pfb")) {
+ $outline = "$file.pfb";
+ if (file_exists("$file.afm")) {
+ $afm = "$file.afm";
+ }
+ } elseif (file_exists("$file.PFB")) {
+ $outline = "$file.PFB";
+ if (file_exists("$file.AFM")) {
+ $afm = "$file.AFM";
+ }
+ } else {
+ continue;
+ }
+
+ $this->_fontsFiles[$face] = true;
+
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->setPDFLibParameter("FontOutline", '{' . "$face=$outline" . '}');
+ } else {
+ $this->setPDFLibParameter("FontOutline", "\{$face\}=\{$outline\}");
+ }
+
+ if (is_null($afm)) {
+ continue;
+ }
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ $this->setPDFLibParameter("FontAFM", '{' . "$face=$afm" . '}');
+ } else {
+ $this->setPDFLibParameter("FontAFM", "\{$face\}=\{$afm\}");
+ }
+ }
+ }
+ }
+
+ $this->_fonts[$key] = $this->_pdf->load_font($font, $encoding, $options);
+
+ return $this->_fonts[$key];
+ }
+
+ /**
+ * Remaps y coords from 4th to 1st quadrant
+ *
+ * @param float $y
+ * @return float
+ */
+ protected function y($y)
+ {
+ return $this->_height - $y;
+ }
+
+ public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
+ {
+ $this->_set_line_style($width, $cap, "", $style);
+ $this->_set_stroke_color($color);
+
+ $y1 = $this->y($y1);
+ $y2 = $this->y($y2);
+
+ $this->_pdf->moveto($x1, $y1);
+ $this->_pdf->lineto($x2, $y2);
+ $this->_pdf->stroke();
+
+ $this->_set_stroke_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
+ {
+ $this->_set_line_style($width, $cap, "", $style);
+ $this->_set_stroke_color($color);
+
+ $y = $this->y($y);
+
+ $this->_pdf->arc($x, $y, $r1, $astart, $aend);
+ $this->_pdf->stroke();
+
+ $this->_set_stroke_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
+ {
+ $this->_set_stroke_color($color);
+ $this->_set_line_style($width, $cap, "", $style);
+
+ $y1 = $this->y($y1) - $h;
+
+ $this->_pdf->rect($x1, $y1, $w, $h);
+ $this->_pdf->stroke();
+
+ $this->_set_stroke_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function filled_rectangle($x1, $y1, $w, $h, $color)
+ {
+ $this->_set_fill_color($color);
+
+ $y1 = $this->y($y1) - $h;
+
+ $this->_pdf->rect(floatval($x1), floatval($y1), floatval($w), floatval($h));
+ $this->_pdf->fill();
+
+ $this->_set_fill_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function clipping_rectangle($x1, $y1, $w, $h)
+ {
+ $this->_pdf->save();
+
+ $y1 = $this->y($y1) - $h;
+
+ $this->_pdf->rect(floatval($x1), floatval($y1), floatval($w), floatval($h));
+ $this->_pdf->clip();
+ }
+
+ public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
+ {
+ if ($this->getPDFLibMajorVersion() < 9) {
+ //TODO: add PDFLib7 support
+ $this->clipping_rectangle($x1, $y1, $w, $h);
+ return;
+ }
+
+ $this->_pdf->save();
+
+ // we use 0,0 for the base coordinates for the path points
+ // since we're drawing the path at the $x1,$y1 coordinates
+
+ $path = 0;
+ //start: left edge, top end
+ $path = $this->_pdf->add_path_point($path, 0, 0 - $rTL + $h, "move", "");
+ // line: left edge, bottom end
+ $path = $this->_pdf->add_path_point($path, 0, 0 + $rBL, "line", "");
+ // curve: bottom-left corner
+ if ($rBL > 0) {
+ $path = $this->_pdf->add_path_point($path, 0 + $rBL, 0, "elliptical", "radius=$rBL clockwise=false");
+ }
+ // line: bottom edge, left end
+ $path = $this->_pdf->add_path_point($path, 0 - $rBR + $w, 0, "line", "");
+ // curve: bottom-right corner
+ if ($rBR > 0) {
+ $path = $this->_pdf->add_path_point($path, 0 + $w, 0 + $rBR, "elliptical", "radius=$rBR clockwise=false");
+ }
+ // line: right edge, top end
+ $path = $this->_pdf->add_path_point($path, 0 + $w, 0 - $rTR + $h, "line", "");
+ // curve: top-right corner
+ if ($rTR > 0) {
+ $path = $this->_pdf->add_path_point($path, 0 - $rTR + $w, 0 + $h, "elliptical", "radius=$rTR clockwise=false");
+ }
+ // line: top edge, left end
+ $path = $this->_pdf->add_path_point($path, 0 + $rTL, 0 + $h, "line", "");
+ // curve: top-left corner
+ if ($rTL > 0) {
+ $path = $this->_pdf->add_path_point($path, 0, 0 - $rTL + $h, "elliptical", "radius=$rTL clockwise=false");
+ }
+ $this->_pdf->draw_path($path, $x1, $this->_height-$y1-$h, "clip=true");
+ }
+
+ public function clipping_polygon(array $points): void
+ {
+ $this->_pdf->save();
+
+ $y = $this->y(array_pop($points));
+ $x = array_pop($points);
+ $this->_pdf->moveto($x, $y);
+
+ while (count($points) > 1) {
+ $y = $this->y(array_pop($points));
+ $x = array_pop($points);
+ $this->_pdf->lineto($x, $y);
+ }
+
+ $this->_pdf->closepath();
+ $this->_pdf->clip();
+ }
+
+ public function clipping_end()
+ {
+ $this->_pdf->restore();
+ }
+
+ public function save()
+ {
+ $this->_pdf->save();
+ }
+
+ function restore()
+ {
+ $this->_pdf->restore();
+ }
+
+ public function rotate($angle, $x, $y)
+ {
+ $pdf = $this->_pdf;
+ $pdf->translate($x, $this->_height - $y);
+ $pdf->rotate(-$angle);
+ $pdf->translate(-$x, -$this->_height + $y);
+ }
+
+ public function skew($angle_x, $angle_y, $x, $y)
+ {
+ $pdf = $this->_pdf;
+ $pdf->translate($x, $this->_height - $y);
+ $pdf->skew($angle_y, $angle_x); // Needs to be inverted
+ $pdf->translate(-$x, -$this->_height + $y);
+ }
+
+ public function scale($s_x, $s_y, $x, $y)
+ {
+ $pdf = $this->_pdf;
+ $pdf->translate($x, $this->_height - $y);
+ $pdf->scale($s_x, $s_y);
+ $pdf->translate(-$x, -$this->_height + $y);
+ }
+
+ public function translate($t_x, $t_y)
+ {
+ $this->_pdf->translate($t_x, -$t_y);
+ }
+
+ public function transform($a, $b, $c, $d, $e, $f)
+ {
+ $this->_pdf->concat($a, $b, $c, $d, $e, $f);
+ }
+
+ public function polygon($points, $color, $width = null, $style = [], $fill = false)
+ {
+ $this->_set_fill_color($color);
+ $this->_set_stroke_color($color);
+
+ if (!$fill && isset($width)) {
+ $this->_set_line_style($width, "square", "miter", $style);
+ }
+
+ $y = $this->y(array_pop($points));
+ $x = array_pop($points);
+ $this->_pdf->moveto($x, $y);
+
+ while (count($points) > 1) {
+ $y = $this->y(array_pop($points));
+ $x = array_pop($points);
+ $this->_pdf->lineto($x, $y);
+ }
+
+ if ($fill) {
+ $this->_pdf->fill();
+ } else {
+ $this->_pdf->closepath_stroke();
+ }
+
+ $this->_set_fill_opacity($this->_current_opacity, "Normal");
+ $this->_set_stroke_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
+ {
+ $this->_set_fill_color($color);
+ $this->_set_stroke_color($color);
+
+ if (!$fill && isset($width)) {
+ $this->_set_line_style($width, "round", "round", $style);
+ }
+
+ $y = $this->y($y);
+
+ $this->_pdf->circle($x, $y, $r);
+
+ if ($fill) {
+ $this->_pdf->fill();
+ } else {
+ $this->_pdf->stroke();
+ }
+
+ $this->_set_fill_opacity($this->_current_opacity, "Normal");
+ $this->_set_stroke_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function image($img, $x, $y, $w, $h, $resolution = "normal")
+ {
+ $w = (int)$w;
+ $h = (int)$h;
+
+ $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext());
+
+ // Strip file:// prefix
+ if (substr($img, 0, 7) === "file://") {
+ $img = substr($img, 7);
+ }
+
+ if (!isset($this->_imgs[$img])) {
+ if (strtolower($img_type) === "svg") {
+ //FIXME: PDFLib loads SVG but returns error message "Function must not be called in 'page' scope"
+ $image_load_response = $this->_pdf->load_graphics($img_type, $img, "");
+ } else {
+ $image_load_response = $this->_pdf->load_image($img_type, $img, "");
+ }
+ if ($image_load_response === 0) {
+ //TODO: should do something with the error message
+ $error = $this->_pdf->get_errmsg();
+ return;
+ }
+ $this->_imgs[$img] = $image_load_response;
+ }
+
+ $img = $this->_imgs[$img];
+
+ $y = $this->y($y) - $h;
+ if (strtolower($img_type) === "svg") {
+ $this->_pdf->fit_graphics($img, $x, $y, 'boxsize={' . "$w $h" . '} fitmethod=entire');
+ } else {
+ $this->_pdf->fit_image($img, $x, $y, 'boxsize={' . "$w $h" . '} fitmethod=entire');
+ }
+ }
+
+ public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0, $char_spacing = 0, $angle = 0)
+ {
+ if ($size == 0) {
+ return;
+ }
+
+ $fh = $this->_load_font($font);
+
+ $this->_pdf->setfont($fh, $size);
+ $this->_set_fill_color($color);
+
+ $y = $this->y($y) - $this->get_font_height($font, $size);
+
+ $word_spacing = (float)$word_spacing;
+ $char_spacing = (float)$char_spacing;
+ $angle = -(float)$angle;
+
+ $this->_pdf->fit_textline($text, $x, $y, "rotate=$angle wordspacing=$word_spacing charspacing=$char_spacing ");
+
+ $this->_set_fill_opacity($this->_current_opacity, "Normal");
+ }
+
+ public function javascript($code)
+ {
+ if (strlen($this->_dompdf->getOptions()->getPdflibLicense()) > 0) {
+ $this->_pdf->create_action("JavaScript", $code);
+ }
+ }
+
+ public function add_named_dest($anchorname)
+ {
+ $this->_pdf->add_nameddest($anchorname, "");
+ }
+
+ public function add_link($url, $x, $y, $width, $height)
+ {
+ $y = $this->y($y) - $height;
+ if (strpos($url, '#') === 0) {
+ // Local link
+ $name = substr($url, 1);
+ if ($name) {
+ $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link',
+ "contents={$url} destname=" . substr($url, 1) . " linewidth=0");
+ }
+ } else {
+ //TODO: PDFLib::create_action does not permit non-HTTP links for URI actions
+ $action = $this->_pdf->create_action("URI", "url={{$url}}");
+ // add the annotation only if the action was created
+ if ($action !== 0) {
+ $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link', "contents={{$url}} action={activate=$action} linewidth=0");
+ }
+ }
+ }
+
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $letter_spacing = 0.0)
+ {
+ if ($size == 0) {
+ return 0.0;
+ }
+
+ $fh = $this->_load_font($font);
+
+ // Determine the additional width due to extra spacing
+ $num_spaces = mb_substr_count($text, " ");
+ $delta = $word_spacing * $num_spaces;
+
+ if ($letter_spacing) {
+ $num_chars = mb_strlen($text);
+ $delta += $num_chars * $letter_spacing;
+ }
+
+ return $this->_pdf->stringwidth($text, $fh, $size) + $delta;
+ }
+
+ public function get_font_height($font, $size)
+ {
+ if ($size == 0) {
+ return 0.0;
+ }
+
+ $fh = $this->_load_font($font);
+
+ $this->_pdf->setfont($fh, $size);
+
+ $asc = $this->_pdf->info_font($fh, "ascender", "fontsize=$size");
+ $desc = $this->_pdf->info_font($fh, "descender", "fontsize=$size");
+
+ // $desc is usually < 0,
+ $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
+
+ return (abs($asc) + abs($desc)) * $ratio;
+ }
+
+ public function get_font_baseline($font, $size)
+ {
+ $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
+
+ return $this->get_font_height($font, $size) / $ratio * 1.1;
+ }
+
+ /**
+ * Processes a callback or script on every page.
+ *
+ * The callback function receives the four parameters `int $pageNumber`,
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in
+ * that order. If a script is passed as string, the variables `$PAGE_NUM`,
+ * `$PAGE_COUNT`, `$pdf`, and `$fontMetrics` are available instead. Passing
+ * a script as string is deprecated and will be removed in a future version.
+ *
+ * This function can be used to add page numbers to all pages after the
+ * first one, for example.
+ *
+ * @param callable|string $callback The callback function or PHP script to process on every page
+ */
+ public function page_script($callback): void
+ {
+ if (is_string($callback)) {
+ $this->processPageScript(function (
+ int $PAGE_NUM,
+ int $PAGE_COUNT,
+ self $pdf,
+ FontMetrics $fontMetrics
+ ) use ($callback) {
+ eval($callback);
+ });
+ return;
+ }
+
+ $this->processPageScript($callback);
+ }
+
+ public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
+ {
+ $this->processPageScript(function (int $pageNumber, int $pageCount) use ($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle) {
+ $text = str_replace(
+ ["{PAGE_NUM}", "{PAGE_COUNT}"],
+ [$pageNumber, $pageCount],
+ $text
+ );
+ $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle);
+ });
+ }
+
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
+ {
+ $this->processPageScript(function () use ($x1, $y1, $x2, $y2, $color, $width, $style) {
+ $this->line($x1, $y1, $x2, $y2, $color, $width, $style);
+ });
+ }
+
+ public function new_page()
+ {
+ // Add objects to the current page
+ $this->_place_objects();
+
+ $this->_pdf->suspend_page("");
+ $this->_pdf->begin_page_ext($this->_width, $this->_height, "");
+ $this->_page_number = ++$this->_page_count;
+ }
+
+ protected function processPageScript(callable $callback): void
+ {
+ $this->_pdf->suspend_page("");
+
+ for ($p = 1; $p <= $this->_page_count; $p++) {
+ $this->_pdf->resume_page("pagenumber=$p");
+
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $callback($p, $this->_page_count, $this, $fontMetrics);
+
+ $this->_pdf->suspend_page("");
+ }
+
+ $this->_pdf->resume_page("pagenumber=" . $this->_page_number);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function stream($filename = "document.pdf", $options = [])
+ {
+ if (headers_sent()) {
+ die("Unable to stream pdf: headers already sent");
+ }
+
+ if (!isset($options["compress"])) {
+ $options["compress"] = true;
+ }
+ if (!isset($options["Attachment"])) {
+ $options["Attachment"] = true;
+ }
+
+ if ($options["compress"]) {
+ $this->setPDFLibValue("compress", 6);
+ } else {
+ $this->setPDFLibValue("compress", 0);
+ }
+
+ $this->_close();
+
+ $data = "";
+
+ if (self::$IN_MEMORY) {
+ $data = $this->_pdf->get_buffer();
+ $size = mb_strlen($data, "8bit");
+ } else {
+ $size = filesize($this->_file);
+ }
+
+ header("Cache-Control: private");
+ header("Content-Type: application/pdf");
+ header("Content-Length: " . $size);
+
+ $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
+ header(Helpers::buildContentDispositionHeader($attachment, $filename));
+
+ if (self::$IN_MEMORY) {
+ echo $data;
+ } else {
+ // Chunked readfile()
+ $chunk = (1 << 21); // 2 MB
+ $fh = fopen($this->_file, "rb");
+ if (!$fh) {
+ throw new Exception("Unable to load temporary PDF file: " . $this->_file);
+ }
+
+ while (!feof($fh)) {
+ echo fread($fh, $chunk);
+ }
+ fclose($fh);
+
+ //debugpng
+ if ($this->_dompdf->getOptions()->getDebugPng()) {
+ print '[pdflib stream unlink ' . $this->_file . ']';
+ }
+ if (!$this->_dompdf->getOptions()->getDebugKeepTemp()) {
+ unlink($this->_file);
+ }
+ $this->_file = null;
+ unset($this->_file);
+ }
+
+ flush();
+ }
+
+ public function output($options = [])
+ {
+ if (!isset($options["compress"])) {
+ $options["compress"] = true;
+ }
+
+ if ($options["compress"]) {
+ $this->setPDFLibValue("compress", 6);
+ } else {
+ $this->setPDFLibValue("compress", 0);
+ }
+
+ $this->_close();
+
+ if (self::$IN_MEMORY) {
+ $data = $this->_pdf->get_buffer();
+ } else {
+ $data = file_get_contents($this->_file);
+
+ //debugpng
+ if ($this->_dompdf->getOptions()->getDebugPng()) {
+ print '[pdflib output unlink ' . $this->_file . ']';
+ }
+ if (!$this->_dompdf->getOptions()->getDebugKeepTemp()) {
+ unlink($this->_file);
+ }
+ $this->_file = null;
+ unset($this->_file);
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param string $keyword
+ * @param string $optlist
+ * @return mixed
+ */
+ protected function getPDFLibParameter($keyword, $optlist = "")
+ {
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ return $this->_pdf->get_option($keyword, "");
+ }
+
+ return $this->_pdf->get_parameter($keyword, $optlist);
+ }
+
+ /**
+ * @param string $keyword
+ * @param string $value
+ * @return mixed
+ */
+ protected function setPDFLibParameter($keyword, $value)
+ {
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ return $this->_pdf->set_option($keyword . "=" . $value);
+ }
+
+ return $this->_pdf->set_parameter($keyword, $value);
+ }
+
+ /**
+ * @param string $keyword
+ * @param string $optlist
+ * @return mixed
+ */
+ protected function getPDFLibValue($keyword, $optlist = "")
+ {
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ return $this->getPDFLibParameter($keyword, $optlist);
+ }
+
+ return $this->_pdf->get_value($keyword);
+ }
+
+ /**
+ * @param string $keyword
+ * @param string $value
+ * @return mixed
+ */
+ protected function setPDFLibValue($keyword, $value)
+ {
+ if ($this->getPDFLibMajorVersion() >= 9) {
+ return $this->setPDFLibParameter($keyword, $value);
+ }
+
+ return $this->_pdf->set_value($keyword, $value);
+ }
+
+ /**
+ * @return int
+ */
+ protected function getPDFLibMajorVersion()
+ {
+ if (is_null(self::$MAJOR_VERSION)) {
+ if (method_exists($this->_pdf, "get_option")) {
+ self::$MAJOR_VERSION = abs(intval($this->_pdf->get_option("major", "")));
+ } else {
+ self::$MAJOR_VERSION = abs(intval($this->_pdf->get_value("major", "")));
+ }
+ }
+
+ return self::$MAJOR_VERSION;
+ }
+}
+
+// Workaround for idiotic limitation on statics...
+PDFLib::$PAPER_SIZES = CPDF::$PAPER_SIZES;
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Canvas.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Canvas.php
new file mode 100644
index 0000000..1812def
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Canvas.php
@@ -0,0 +1,477 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+/**
+ * Main rendering interface
+ *
+ * Currently {@link Dompdf\Adapter\CPDF}, {@link Dompdf\Adapter\PDFLib}, and {@link Dompdf\Adapter\GD}
+ * implement this interface.
+ *
+ * Implementations should measure x and y increasing to the left and down,
+ * respectively, with the origin in the top left corner. Implementations
+ * are free to use a unit other than points for length, but I can't
+ * guarantee that the results will look any good.
+ *
+ * @package dompdf
+ */
+interface Canvas
+{
+ /**
+ * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link Dompdf\Adapter\CPDF::$PAPER_SIZES})
+ * or an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`).
+ * @param string $orientation The paper orientation, either `portrait` or `landscape`.
+ * @param Dompdf $dompdf The Dompdf instance.
+ */
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null);
+
+ /**
+ * @return Dompdf
+ */
+ function get_dompdf();
+
+ /**
+ * Returns the current page number
+ *
+ * @return int
+ */
+ function get_page_number();
+
+ /**
+ * Returns the total number of pages in the document
+ *
+ * @return int
+ */
+ function get_page_count();
+
+ /**
+ * Sets the total number of pages
+ *
+ * @param int $count
+ */
+ function set_page_count($count);
+
+ /**
+ * Draws a line from x1,y1 to x2,y2
+ *
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
+ * $style and $cap parameters (aka dash and cap).
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $x2
+ * @param float $y2
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $width
+ * @param array $style
+ * @param string $cap `butt`, `round`, or `square`
+ */
+ function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt");
+
+ /**
+ * Draws an arc
+ *
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
+ * $style and $cap parameters (aka dash and cap).
+ *
+ * @param float $x X coordinate of the arc
+ * @param float $y Y coordinate of the arc
+ * @param float $r1 Radius 1
+ * @param float $r2 Radius 2
+ * @param float $astart Start angle in degrees
+ * @param float $aend End angle in degrees
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $width
+ * @param array $style
+ * @param string $cap `butt`, `round`, or `square`
+ */
+ function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt");
+
+ /**
+ * Draws a rectangle at x1,y1 with width w and height h
+ *
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
+ * $style and $cap parameters (aka dash and cap).
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $w
+ * @param float $h
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $width
+ * @param array $style
+ * @param string $cap `butt`, `round`, or `square`
+ */
+ function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt");
+
+ /**
+ * Draws a filled rectangle at x1,y1 with width w and height h
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $w
+ * @param float $h
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ */
+ function filled_rectangle($x1, $y1, $w, $h, $color);
+
+ /**
+ * Starts a clipping rectangle at x1,y1 with width w and height h
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $w
+ * @param float $h
+ */
+ function clipping_rectangle($x1, $y1, $w, $h);
+
+ /**
+ * Starts a rounded clipping rectangle at x1,y1 with width w and height h
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $w
+ * @param float $h
+ * @param float $tl
+ * @param float $tr
+ * @param float $br
+ * @param float $bl
+ */
+ function clipping_roundrectangle($x1, $y1, $w, $h, $tl, $tr, $br, $bl);
+
+ /**
+ * Starts a clipping polygon
+ *
+ * @param float[] $points
+ */
+ public function clipping_polygon(array $points): void;
+
+ /**
+ * Ends the last clipping shape
+ */
+ function clipping_end();
+
+ /**
+ * Processes a callback on every page.
+ *
+ * The callback function receives the four parameters `int $pageNumber`,
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in
+ * that order.
+ *
+ * This function can be used to add page numbers to all pages after the
+ * first one, for example.
+ *
+ * @param callable $callback The callback function to process on every page
+ */
+ public function page_script($callback): void;
+
+ /**
+ * Writes text at the specified x and y coordinates on every page.
+ *
+ * The strings '{PAGE_NUM}' and '{PAGE_COUNT}' are automatically replaced
+ * with their current values.
+ *
+ * @param float $x
+ * @param float $y
+ * @param string $text The text to write
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $word_space Word spacing adjustment
+ * @param float $char_space Char spacing adjustment
+ * @param float $angle Angle to write the text at, measured clockwise starting from the x-axis
+ */
+ public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
+
+ /**
+ * Draws a line at the specified coordinates on every page.
+ *
+ * @param float $x1
+ * @param float $y1
+ * @param float $x2
+ * @param float $y2
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $width
+ * @param array $style
+ */
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []);
+
+ /**
+ * Save current state
+ */
+ function save();
+
+ /**
+ * Restore last state
+ */
+ function restore();
+
+ /**
+ * Rotate
+ *
+ * @param float $angle angle in degrees for counter-clockwise rotation
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function rotate($angle, $x, $y);
+
+ /**
+ * Skew
+ *
+ * @param float $angle_x
+ * @param float $angle_y
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function skew($angle_x, $angle_y, $x, $y);
+
+ /**
+ * Scale
+ *
+ * @param float $s_x scaling factor for width as percent
+ * @param float $s_y scaling factor for height as percent
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function scale($s_x, $s_y, $x, $y);
+
+ /**
+ * Translate
+ *
+ * @param float $t_x movement to the right
+ * @param float $t_y movement to the bottom
+ */
+ function translate($t_x, $t_y);
+
+ /**
+ * Transform
+ *
+ * @param float $a
+ * @param float $b
+ * @param float $c
+ * @param float $d
+ * @param float $e
+ * @param float $f
+ */
+ function transform($a, $b, $c, $d, $e, $f);
+
+ /**
+ * Draws a polygon
+ *
+ * The polygon is formed by joining all the points stored in the $points
+ * array. $points has the following structure:
+ * ```
+ * array(0 => x1,
+ * 1 => y1,
+ * 2 => x2,
+ * 3 => y2,
+ * ...
+ * );
+ * ```
+ *
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
+ * $style parameter (aka dash).
+ *
+ * @param array $points
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $width
+ * @param array $style
+ * @param bool $fill Fills the polygon if true
+ */
+ function polygon($points, $color, $width = null, $style = [], $fill = false);
+
+ /**
+ * Draws a circle at $x,$y with radius $r
+ *
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
+ * $style parameter (aka dash).
+ *
+ * @param float $x
+ * @param float $y
+ * @param float $r
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $width
+ * @param array $style
+ * @param bool $fill Fills the circle if true
+ */
+ function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false);
+
+ /**
+ * Add an image to the pdf.
+ *
+ * The image is placed at the specified x and y coordinates with the
+ * given width and height.
+ *
+ * @param string $img The path to the image
+ * @param float $x X position
+ * @param float $y Y position
+ * @param float $w Width
+ * @param float $h Height
+ * @param string $resolution The resolution of the image
+ */
+ function image($img, $x, $y, $w, $h, $resolution = "normal");
+
+ /**
+ * Writes text at the specified x and y coordinates
+ *
+ * @param float $x
+ * @param float $y
+ * @param string $text The text to write
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
+ * where r, g, b, and alpha are float values between 0 and 1
+ * @param float $word_space Word spacing adjustment
+ * @param float $char_space Char spacing adjustment
+ * @param float $angle Angle to write the text at, measured clockwise starting from the x-axis
+ */
+ function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
+
+ /**
+ * Add a named destination (similar to <a name="foo">...</a> in html)
+ *
+ * @param string $anchorname The name of the named destination
+ */
+ function add_named_dest($anchorname);
+
+ /**
+ * Add a link to the pdf
+ *
+ * @param string $url The url to link to
+ * @param float $x The x position of the link
+ * @param float $y The y position of the link
+ * @param float $width The width of the link
+ * @param float $height The height of the link
+ */
+ function add_link($url, $x, $y, $width, $height);
+
+ /**
+ * Add meta information to the PDF.
+ *
+ * @param string $label Label of the value (Creator, Producer, etc.)
+ * @param string $value The text to set
+ */
+ public function add_info(string $label, string $value): void;
+
+ /**
+ * Calculates text size, in points
+ *
+ * @param string $text The text to be sized
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ * @param float $word_spacing Word spacing, if any
+ * @param float $char_spacing Char spacing, if any
+ *
+ * @return float
+ */
+ function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0);
+
+ /**
+ * Calculates font height, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ function get_font_height($font, $size);
+
+ /**
+ * Returns the font x-height, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ //function get_font_x_height($font, $size);
+
+ /**
+ * Calculates font baseline, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ function get_font_baseline($font, $size);
+
+ /**
+ * Returns the PDF's width in points
+ *
+ * @return float
+ */
+ function get_width();
+
+ /**
+ * Returns the PDF's height in points
+ *
+ * @return float
+ */
+ function get_height();
+
+ /**
+ * Sets the opacity
+ *
+ * @param float $opacity
+ * @param string $mode
+ */
+ public function set_opacity(float $opacity, string $mode = "Normal"): void;
+
+ /**
+ * Sets the default view
+ *
+ * @param string $view
+ * 'XYZ' left, top, zoom
+ * 'Fit'
+ * 'FitH' top
+ * 'FitV' left
+ * 'FitR' left,bottom,right
+ * 'FitB'
+ * 'FitBH' top
+ * 'FitBV' left
+ * @param array $options
+ */
+ function set_default_view($view, $options = []);
+
+ /**
+ * @param string $code
+ */
+ function javascript($code);
+
+ /**
+ * Starts a new page
+ *
+ * Subsequent drawing operations will appear on the new page.
+ */
+ function new_page();
+
+ /**
+ * Streams the PDF to the client.
+ *
+ * @param string $filename The filename to present to the client.
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
+ */
+ function stream($filename, $options = []);
+
+ /**
+ * Returns the PDF as a string.
+ *
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
+ *
+ * @return string
+ */
+ function output($options = []);
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/CanvasFactory.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/CanvasFactory.php
new file mode 100644
index 0000000..86352e1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/CanvasFactory.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+/**
+ * Create canvas instances
+ *
+ * The canvas factory creates canvas instances based on the
+ * availability of rendering backends and config options.
+ *
+ * @package dompdf
+ */
+class CanvasFactory
+{
+ /**
+ * Constructor is private: this is a static class
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * @param Dompdf $dompdf
+ * @param string|array $paper
+ * @param string $orientation
+ * @param string $class
+ *
+ * @return Canvas
+ */
+ static function get_instance(Dompdf $dompdf, $paper = null, $orientation = null, $class = null)
+ {
+ $backend = strtolower($dompdf->getOptions()->getPdfBackend());
+
+ if (isset($class) && class_exists($class, false)) {
+ $class .= "_Adapter";
+ } else {
+ if (($backend === "auto" || $backend === "pdflib") &&
+ class_exists("PDFLib", false)
+ ) {
+ $class = "Dompdf\\Adapter\\PDFLib";
+ }
+
+ else {
+ if ($backend === "gd" && extension_loaded('gd')) {
+ $class = "Dompdf\\Adapter\\GD";
+ } else {
+ $class = "Dompdf\\Adapter\\CPDF";
+ }
+ }
+ }
+
+ return new $class($paper, $orientation, $dompdf);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Cellmap.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Cellmap.php
new file mode 100644
index 0000000..e6c1c68
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Cellmap.php
@@ -0,0 +1,999 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\Table as TableFrameDecorator;
+use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
+
+/**
+ * Maps table cells to the table grid.
+ *
+ * This class resolves borders in tables with collapsed borders and helps
+ * place row & column spanned table cells.
+ *
+ * @package dompdf
+ */
+class Cellmap
+{
+ /**
+ * Border style weight lookup for collapsed border resolution.
+ */
+ protected const BORDER_STYLE_SCORE = [
+ "double" => 8,
+ "solid" => 7,
+ "dashed" => 6,
+ "dotted" => 5,
+ "ridge" => 4,
+ "outset" => 3,
+ "groove" => 2,
+ "inset" => 1,
+ "none" => 0
+ ];
+
+ /**
+ * The table object this cellmap is attached to.
+ *
+ * @var TableFrameDecorator
+ */
+ protected $_table;
+
+ /**
+ * The total number of rows in the table
+ *
+ * @var int
+ */
+ protected $_num_rows;
+
+ /**
+ * The total number of columns in the table
+ *
+ * @var int
+ */
+ protected $_num_cols;
+
+ /**
+ * 2D array mapping <row,column> to frames
+ *
+ * @var Frame[][]
+ */
+ protected $_cells;
+
+ /**
+ * 1D array of column dimensions
+ *
+ * @var array
+ */
+ protected $_columns;
+
+ /**
+ * 1D array of row dimensions
+ *
+ * @var array
+ */
+ protected $_rows;
+
+ /**
+ * 2D array of border specs
+ *
+ * @var array
+ */
+ protected $_borders;
+
+ /**
+ * 1D Array mapping frames to (multiple) <row, col> pairs, keyed on frame_id.
+ *
+ * @var array[]
+ */
+ protected $_frames;
+
+ /**
+ * Current column when adding cells, 0-based
+ *
+ * @var int
+ */
+ private $__col;
+
+ /**
+ * Current row when adding cells, 0-based
+ *
+ * @var int
+ */
+ private $__row;
+
+ /**
+ * Tells whether the columns' width can be modified
+ *
+ * @var bool
+ */
+ private $_columns_locked = false;
+
+ /**
+ * Tells whether the table has table-layout:fixed
+ *
+ * @var bool
+ */
+ private $_fixed_layout = false;
+
+ /**
+ * @param TableFrameDecorator $table
+ */
+ public function __construct(TableFrameDecorator $table)
+ {
+ $this->_table = $table;
+ $this->reset();
+ }
+
+ public function reset(): void
+ {
+ $this->_num_rows = 0;
+ $this->_num_cols = 0;
+
+ $this->_cells = [];
+ $this->_frames = [];
+
+ if (!$this->_columns_locked) {
+ $this->_columns = [];
+ }
+
+ $this->_rows = [];
+
+ $this->_borders = [];
+
+ $this->__col = $this->__row = 0;
+ }
+
+ public function lock_columns(): void
+ {
+ $this->_columns_locked = true;
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_columns_locked()
+ {
+ return $this->_columns_locked;
+ }
+
+ /**
+ * @param bool $fixed
+ */
+ public function set_layout_fixed(bool $fixed)
+ {
+ $this->_fixed_layout = $fixed;
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_layout_fixed()
+ {
+ return $this->_fixed_layout;
+ }
+
+ /**
+ * @return int
+ */
+ public function get_num_rows()
+ {
+ return $this->_num_rows;
+ }
+
+ /**
+ * @return int
+ */
+ public function get_num_cols()
+ {
+ return $this->_num_cols;
+ }
+
+ /**
+ * @return array
+ */
+ public function &get_columns()
+ {
+ return $this->_columns;
+ }
+
+ /**
+ * @param $columns
+ */
+ public function set_columns($columns)
+ {
+ $this->_columns = $columns;
+ }
+
+ /**
+ * @param int $i
+ *
+ * @return mixed
+ */
+ public function &get_column($i)
+ {
+ if (!isset($this->_columns[$i])) {
+ $this->_columns[$i] = [
+ "x" => 0,
+ "min-width" => 0,
+ "max-width" => 0,
+ "used-width" => null,
+ "absolute" => 0,
+ "percent" => 0,
+ "auto" => true,
+ ];
+ }
+
+ return $this->_columns[$i];
+ }
+
+ /**
+ * @return array
+ */
+ public function &get_rows()
+ {
+ return $this->_rows;
+ }
+
+ /**
+ * @param int $j
+ *
+ * @return mixed
+ */
+ public function &get_row($j)
+ {
+ if (!isset($this->_rows[$j])) {
+ $this->_rows[$j] = [
+ "y" => 0,
+ "first-column" => 0,
+ "height" => null,
+ ];
+ }
+
+ return $this->_rows[$j];
+ }
+
+ /**
+ * @param int $i
+ * @param int $j
+ * @param mixed $h_v
+ * @param null|mixed $prop
+ *
+ * @return mixed
+ */
+ public function get_border($i, $j, $h_v, $prop = null)
+ {
+ if (!isset($this->_borders[$i][$j][$h_v])) {
+ $this->_borders[$i][$j][$h_v] = [
+ "width" => 0,
+ "style" => "solid",
+ "color" => "black",
+ ];
+ }
+
+ if (isset($prop)) {
+ return $this->_borders[$i][$j][$h_v][$prop];
+ }
+
+ return $this->_borders[$i][$j][$h_v];
+ }
+
+ /**
+ * @param int $i
+ * @param int $j
+ *
+ * @return array
+ */
+ public function get_border_properties($i, $j)
+ {
+ return [
+ "top" => $this->get_border($i, $j, "horizontal"),
+ "right" => $this->get_border($i, $j + 1, "vertical"),
+ "bottom" => $this->get_border($i + 1, $j, "horizontal"),
+ "left" => $this->get_border($i, $j, "vertical"),
+ ];
+ }
+
+ /**
+ * @param Frame $frame
+ *
+ * @return array|null
+ */
+ public function get_spanned_cells(Frame $frame)
+ {
+ $key = $frame->get_id();
+
+ if (isset($this->_frames[$key])) {
+ return $this->_frames[$key];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param Frame $frame
+ *
+ * @return bool
+ */
+ public function frame_exists_in_cellmap(Frame $frame)
+ {
+ $key = $frame->get_id();
+
+ return isset($this->_frames[$key]);
+ }
+
+ /**
+ * @param Frame $frame
+ *
+ * @return array
+ * @throws Exception
+ */
+ public function get_frame_position(Frame $frame)
+ {
+ global $_dompdf_warnings;
+
+ $key = $frame->get_id();
+
+ if (!isset($this->_frames[$key])) {
+ throw new Exception("Frame not found in cellmap");
+ }
+
+ // Positions are stored relative to the table position
+ [$table_x, $table_y] = $this->_table->get_position();
+ $col = $this->_frames[$key]["columns"][0];
+ $row = $this->_frames[$key]["rows"][0];
+
+ if (!isset($this->_columns[$col])) {
+ $_dompdf_warnings[] = "Frame not found in columns array. Check your table layout for missing or extra TDs.";
+ $x = $table_x;
+ } else {
+ $x = $table_x + $this->_columns[$col]["x"];
+ }
+
+ if (!isset($this->_rows[$row])) {
+ $_dompdf_warnings[] = "Frame not found in row array. Check your table layout for missing or extra TDs.";
+ $y = $table_y;
+ } else {
+ $y = $table_y + $this->_rows[$row]["y"];
+ }
+
+ return [$x, $y, "x" => $x, "y" => $y];
+ }
+
+ /**
+ * @param Frame $frame
+ *
+ * @return int
+ * @throws Exception
+ */
+ public function get_frame_width(Frame $frame)
+ {
+ $key = $frame->get_id();
+
+ if (!isset($this->_frames[$key])) {
+ throw new Exception("Frame not found in cellmap");
+ }
+
+ $cols = $this->_frames[$key]["columns"];
+ $w = 0;
+ foreach ($cols as $i) {
+ $w += $this->_columns[$i]["used-width"];
+ }
+
+ return $w;
+ }
+
+ /**
+ * @param Frame $frame
+ *
+ * @return int
+ * @throws Exception
+ * @throws Exception
+ */
+ public function get_frame_height(Frame $frame)
+ {
+ $key = $frame->get_id();
+
+ if (!isset($this->_frames[$key])) {
+ throw new Exception("Frame not found in cellmap");
+ }
+
+ $rows = $this->_frames[$key]["rows"];
+ $h = 0;
+ foreach ($rows as $i) {
+ if (!isset($this->_rows[$i])) {
+ throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code");
+ }
+
+ $h += $this->_rows[$i]["height"];
+ }
+
+ return $h;
+ }
+
+ /**
+ * @param int $j
+ * @param mixed $width
+ */
+ public function set_column_width($j, $width)
+ {
+ if ($this->_columns_locked) {
+ return;
+ }
+
+ $col =& $this->get_column($j);
+ $col["used-width"] = $width;
+ $next_col =& $this->get_column($j + 1);
+ $next_col["x"] = $col["x"] + $width;
+ }
+
+ /**
+ * @param int $i
+ * @param long $height
+ */
+ public function set_row_height($i, $height)
+ {
+ $row =& $this->get_row($i);
+ if ($height > $row["height"]) {
+ $row["height"] = $height;
+ }
+ $next_row =& $this->get_row($i + 1);
+ $next_row["y"] = $row["y"] + $row["height"];
+ }
+
+ /**
+ * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
+ *
+ * @param int $i
+ * @param int $j
+ * @param string $h_v `horizontal` or `vertical`
+ * @param array $border_spec
+ */
+ protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void
+ {
+ if (!isset($this->_borders[$i][$j][$h_v])) {
+ $this->_borders[$i][$j][$h_v] = $border_spec;
+ return;
+ }
+
+ $border = $this->_borders[$i][$j][$h_v];
+
+ $n_width = $border_spec["width"];
+ $n_style = $border_spec["style"];
+ $o_width = $border["width"];
+ $o_style = $border["style"];
+
+ if ($o_style === "hidden") {
+ return;
+ }
+
+ // A style of `none` has lowest priority independent of its specified
+ // width here, as its resolved width is always 0
+ if ($n_style === "hidden" || $n_width > $o_width
+ || ($o_width == $n_width
+ && isset(self::BORDER_STYLE_SCORE[$n_style])
+ && isset(self::BORDER_STYLE_SCORE[$o_style])
+ && self::BORDER_STYLE_SCORE[$n_style] > self::BORDER_STYLE_SCORE[$o_style])
+ ) {
+ $this->_borders[$i][$j][$h_v] = $border_spec;
+ }
+ }
+
+ /**
+ * Get the resolved border properties for the given frame.
+ *
+ * @param AbstractFrameDecorator $frame
+ *
+ * @return array[]
+ */
+ protected function get_resolved_border(AbstractFrameDecorator $frame): array
+ {
+ $key = $frame->get_id();
+ $columns = $this->_frames[$key]["columns"];
+ $rows = $this->_frames[$key]["rows"];
+
+ $first_col = $columns[0];
+ $last_col = $columns[count($columns) - 1];
+ $first_row = $rows[0];
+ $last_row = $rows[count($rows) - 1];
+
+ $max_top = null;
+ $max_bottom = null;
+ $max_left = null;
+ $max_right = null;
+
+ foreach ($columns as $col) {
+ $top = $this->_borders[$first_row][$col]["horizontal"];
+ $bottom = $this->_borders[$last_row + 1][$col]["horizontal"];
+
+ if ($max_top === null || $top["width"] > $max_top["width"]) {
+ $max_top = $top;
+ }
+ if ($max_bottom === null || $bottom["width"] > $max_bottom["width"]) {
+ $max_bottom = $bottom;
+ }
+ }
+
+ foreach ($rows as $row) {
+ $left = $this->_borders[$row][$first_col]["vertical"];
+ $right = $this->_borders[$row][$last_col + 1]["vertical"];
+
+ if ($max_left === null || $left["width"] > $max_left["width"]) {
+ $max_left = $left;
+ }
+ if ($max_right === null || $right["width"] > $max_right["width"]) {
+ $max_right = $right;
+ }
+ }
+
+ return [$max_top, $max_right, $max_bottom, $max_left];
+ }
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ public function add_frame(Frame $frame): void
+ {
+ $style = $frame->get_style();
+ $display = $style->display;
+
+ $collapse = $this->_table->get_style()->border_collapse === "collapse";
+
+ // Recursively add the frames within the table, its row groups and rows
+ if ($frame === $this->_table
+ || $display === "table-row"
+ || in_array($display, TableFrameDecorator::ROW_GROUPS, true)
+ ) {
+ $start_row = $this->__row;
+
+ foreach ($frame->get_children() as $child) {
+ $this->add_frame($child);
+ }
+
+ if ($display === "table-row") {
+ $this->add_row();
+ }
+
+ $num_rows = $this->__row - $start_row - 1;
+ $key = $frame->get_id();
+
+ // Row groups always span across the entire table
+ $this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols - 1));
+ $this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1));
+ $this->_frames[$key]["frame"] = $frame;
+
+ if ($collapse) {
+ $bp = $style->get_border_properties();
+
+ // Resolve vertical borders
+ for ($i = 0; $i < $num_rows + 1; $i++) {
+ $this->resolve_border($start_row + $i, 0, "vertical", $bp["left"]);
+ $this->resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]);
+ }
+
+ // Resolve horizontal borders
+ for ($j = 0; $j < $this->_num_cols; $j++) {
+ $this->resolve_border($start_row, $j, "horizontal", $bp["top"]);
+ $this->resolve_border($this->__row, $j, "horizontal", $bp["bottom"]);
+ }
+
+ if ($frame === $this->_table) {
+ // Clear borders because the cells are now using them. The
+ // border width still needs to be set to half the resolved
+ // width so that the table is positioned properly
+ [$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
+
+ $style->set_used("border_top_width", $top["width"] / 2);
+ $style->set_used("border_right_width", $right["width"] / 2);
+ $style->set_used("border_bottom_width", $bottom["width"] / 2);
+ $style->set_used("border_left_width", $left["width"] / 2);
+ $style->set_used("border_style", "none");
+ } else {
+ // Clear borders for rows and row groups
+ $style->set_used("border_width", 0);
+ $style->set_used("border_style", "none");
+ }
+ }
+
+ if ($frame === $this->_table) {
+ // Apply resolved borders to table cells and calculate column
+ // widths after all frames have been added
+ $this->calculate_column_widths();
+ }
+ return;
+ }
+
+ // Add the frame to the cellmap
+ $key = $frame->get_id();
+ $node = $frame->get_node();
+ $bp = $style->get_border_properties();
+
+ // Determine where this cell is going
+ $colspan = max((int) $node->getAttribute("colspan"), 1);
+ $rowspan = max((int) $node->getAttribute("rowspan"), 1);
+
+ // Find the next available column (fix by Ciro Mondueri)
+ $ac = $this->__col;
+ while (isset($this->_cells[$this->__row][$ac])) {
+ $ac++;
+ }
+
+ $this->__col = $ac;
+
+ // Rows:
+ for ($i = 0; $i < $rowspan; $i++) {
+ $row = $this->__row + $i;
+
+ $this->_frames[$key]["rows"][] = $row;
+
+ for ($j = 0; $j < $colspan; $j++) {
+ $this->_cells[$row][$this->__col + $j] = $frame;
+ }
+
+ if ($collapse) {
+ // Resolve vertical borders
+ $this->resolve_border($row, $this->__col, "vertical", $bp["left"]);
+ $this->resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]);
+ }
+ }
+
+ // Columns:
+ for ($j = 0; $j < $colspan; $j++) {
+ $col = $this->__col + $j;
+ $this->_frames[$key]["columns"][] = $col;
+
+ if ($collapse) {
+ // Resolve horizontal borders
+ $this->resolve_border($this->__row, $col, "horizontal", $bp["top"]);
+ $this->resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]);
+ }
+ }
+
+ $this->_frames[$key]["frame"] = $frame;
+
+ $this->__col += $colspan;
+ if ($this->__col > $this->_num_cols) {
+ $this->_num_cols = $this->__col;
+ }
+ }
+
+ /**
+ * Apply resolved borders to table cells and calculate column widths.
+ */
+ protected function calculate_column_widths(): void
+ {
+ $table = $this->_table;
+ $table_style = $table->get_style();
+ $collapse = $table_style->border_collapse === "collapse";
+
+ if ($collapse) {
+ $v_spacing = 0;
+ $h_spacing = 0;
+ } else {
+ // The additional 1/2 width gets added to the table proper
+ [$h, $v] = $table_style->border_spacing;
+ $v_spacing = $v / 2;
+ $h_spacing = $h / 2;
+ }
+
+ foreach ($this->_frames as $frame_info) {
+ /** @var TableCellFrameDecorator */
+ $frame = $frame_info["frame"];
+ $style = $frame->get_style();
+ $display = $style->display;
+
+ if ($display !== "table-cell") {
+ continue;
+ }
+
+ if ($collapse) {
+ // Set the resolved border at half width
+ [$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
+
+ $style->set_used("border_top_width", $top["width"] / 2);
+ $style->set_used("border_top_style", $top["style"]);
+ $style->set_used("border_top_color", $top["color"]);
+ $style->set_used("border_right_width", $right["width"] / 2);
+ $style->set_used("border_right_style", $right["style"]);
+ $style->set_used("border_right_color", $right["color"]);
+ $style->set_used("border_bottom_width", $bottom["width"] / 2);
+ $style->set_used("border_bottom_style", $bottom["style"]);
+ $style->set_used("border_bottom_color", $bottom["color"]);
+ $style->set_used("border_left_width", $left["width"] / 2);
+ $style->set_used("border_left_style", $left["style"]);
+ $style->set_used("border_left_color", $left["color"]);
+ $style->set_used("margin", 0);
+ } else {
+ // Border spacing is effectively a margin between cells
+ $style->set_used("margin_top", $v_spacing);
+ $style->set_used("margin_bottom", $v_spacing);
+ $style->set_used("margin_left", $h_spacing);
+ $style->set_used("margin_right", $h_spacing);
+ }
+
+ if ($this->_columns_locked) {
+ continue;
+ }
+
+ $node = $frame->get_node();
+ $colspan = max((int) $node->getAttribute("colspan"), 1);
+ $first_col = $frame_info["columns"][0];
+
+ // Resolve the frame's width
+ if ($this->_fixed_layout) {
+ list($frame_min, $frame_max) = [0, 10e-10];
+ } else {
+ list($frame_min, $frame_max) = $frame->get_min_max_width();
+ }
+
+ $width = $style->width;
+
+ $val = null;
+ if (Helpers::is_percent($width) && $colspan === 1) {
+ $var = "percent";
+ $val = (float)rtrim($width, "% ");
+ } elseif ($width !== "auto" && $colspan === 1) {
+ $var = "absolute";
+ $val = $frame_min;
+ }
+
+ $min = 0;
+ $max = 0;
+ for ($cs = 0; $cs < $colspan; $cs++) {
+
+ // Resolve the frame's width(s) with other cells
+ $col =& $this->get_column($first_col + $cs);
+
+ // Note: $var is either 'percent' or 'absolute'. We compare the
+ // requested percentage or absolute values with the existing widths
+ // and adjust accordingly.
+ if (isset($var) && $val > $col[$var]) {
+ $col[$var] = $val;
+ $col["auto"] = false;
+ }
+
+ $min += $col["min-width"];
+ $max += $col["max-width"];
+ }
+
+ if ($frame_min > $min && $colspan === 1) {
+ // The frame needs more space. Expand each sub-column
+ // FIXME try to avoid putting this dummy value when table-layout:fixed
+ $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min));
+ for ($c = 0; $c < $colspan; $c++) {
+ $col =& $this->get_column($first_col + $c);
+ $col["min-width"] += $inc;
+ }
+ }
+
+ if ($frame_max > $max) {
+ // FIXME try to avoid putting this dummy value when table-layout:fixed
+ $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan);
+ for ($c = 0; $c < $colspan; $c++) {
+ $col =& $this->get_column($first_col + $c);
+ $col["max-width"] += $inc;
+ }
+ }
+ }
+
+ // Adjust absolute columns so that the absolute (and max) width is the
+ // largest minimum width of all cells. This accounts for cells without
+ // absolute width within an absolute column
+ foreach ($this->_columns as &$col) {
+ if ($col["absolute"] > 0) {
+ $col["absolute"] = $col["min-width"];
+ $col["max-width"] = $col["min-width"];
+ }
+ }
+ }
+
+ protected function add_row(): void
+ {
+ $this->__row++;
+ $this->_num_rows++;
+
+ // Find the next available column
+ $i = 0;
+ while (isset($this->_cells[$this->__row][$i])) {
+ $i++;
+ }
+
+ $this->__col = $i;
+ }
+
+ /**
+ * Remove a row from the cellmap.
+ *
+ * @param Frame
+ */
+ public function remove_row(Frame $row)
+ {
+ $key = $row->get_id();
+ if (!isset($this->_frames[$key])) {
+ return; // Presumably this row has already been removed
+ }
+
+ $this->__row = $this->_num_rows--;
+
+ $rows = $this->_frames[$key]["rows"];
+ $columns = $this->_frames[$key]["columns"];
+
+ // Remove all frames from this row
+ foreach ($rows as $r) {
+ foreach ($columns as $c) {
+ if (isset($this->_cells[$r][$c])) {
+ $id = $this->_cells[$r][$c]->get_id();
+
+ $this->_cells[$r][$c] = null;
+ unset($this->_cells[$r][$c]);
+
+ // has multiple rows?
+ if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) {
+ // remove just the desired row, but leave the frame
+ if (($row_key = array_search($r, $this->_frames[$id]["rows"])) !== false) {
+ unset($this->_frames[$id]["rows"][$row_key]);
+ }
+ continue;
+ }
+
+ $this->_frames[$id] = null;
+ unset($this->_frames[$id]);
+ }
+ }
+
+ $this->_rows[$r] = null;
+ unset($this->_rows[$r]);
+ }
+
+ $this->_frames[$key] = null;
+ unset($this->_frames[$key]);
+ }
+
+ /**
+ * Remove a row group from the cellmap.
+ *
+ * @param Frame $group The group to remove
+ */
+ public function remove_row_group(Frame $group)
+ {
+ $key = $group->get_id();
+ if (!isset($this->_frames[$key])) {
+ return; // Presumably this row has already been removed
+ }
+
+ $iter = $group->get_first_child();
+ while ($iter) {
+ $this->remove_row($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ $this->_frames[$key] = null;
+ unset($this->_frames[$key]);
+ }
+
+ /**
+ * Update a row group after rows have been removed
+ *
+ * @param Frame $group The group to update
+ * @param Frame $last_row The last row in the row group
+ */
+ public function update_row_group(Frame $group, Frame $last_row)
+ {
+ $g_key = $group->get_id();
+
+ $first_index = $this->_frames[$g_key]["rows"][0];
+ $last_index = $first_index;
+ $row = $last_row;
+ while ($row = $row->get_prev_sibling()) {
+ $last_index++;
+ }
+
+ $this->_frames[$g_key]["rows"] = range($first_index, $last_index);
+ }
+
+ public function assign_x_positions(): void
+ {
+ // Pre-condition: widths must be resolved and assigned to columns and
+ // column[0]["x"] must be set.
+
+ if ($this->_columns_locked) {
+ return;
+ }
+
+ $x = $this->_columns[0]["x"];
+ foreach (array_keys($this->_columns) as $j) {
+ $this->_columns[$j]["x"] = $x;
+ $x += $this->_columns[$j]["used-width"];
+ }
+ }
+
+ public function assign_frame_heights(): void
+ {
+ // Pre-condition: widths and heights of each column & row must be
+ // calcluated
+ foreach ($this->_frames as $arr) {
+ $frame = $arr["frame"];
+
+ $h = 0.0;
+ foreach ($arr["rows"] as $row) {
+ if (!isset($this->_rows[$row])) {
+ // The row has been removed because of a page split, so skip it.
+ continue;
+ }
+
+ $h += $this->_rows[$row]["height"];
+ }
+
+ if ($frame instanceof TableCellFrameDecorator) {
+ $frame->set_cell_height($h);
+ } else {
+ $frame->get_style()->set_used("height", $h);
+ }
+ }
+ }
+
+ /**
+ * Re-adjust frame height if the table height is larger than its content
+ */
+ public function set_frame_heights(float $table_height, float $content_height): void
+ {
+ // Distribute the increased height proportionally amongst each row
+ foreach ($this->_frames as $arr) {
+ $frame = $arr["frame"];
+
+ $h = 0.0;
+ foreach ($arr["rows"] as $row) {
+ if (!isset($this->_rows[$row])) {
+ continue;
+ }
+
+ $h += $this->_rows[$row]["height"];
+ }
+
+ if ($content_height > 0) {
+ $new_height = ($h / $content_height) * $table_height;
+ } else {
+ $new_height = 0.0;
+ }
+
+ if ($frame instanceof TableCellFrameDecorator) {
+ $frame->set_cell_height($new_height);
+ } else {
+ $frame->get_style()->set_used("height", $new_height);
+ }
+ }
+ }
+
+ /**
+ * Used for debugging:
+ *
+ * @return string
+ */
+ public function __toString(): string
+ {
+ $str = "";
+ $str .= "Columns:<br/>";
+ $str .= Helpers::pre_r($this->_columns, true);
+ $str .= "Rows:<br/>";
+ $str .= Helpers::pre_r($this->_rows, true);
+
+ $str .= "Frames:<br/>";
+ $arr = [];
+ foreach ($this->_frames as $key => $val) {
+ $arr[$key] = ["columns" => $val["columns"], "rows" => $val["rows"]];
+ }
+
+ $str .= Helpers::pre_r($arr, true);
+
+ if (php_sapi_name() == "cli") {
+ $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
+ ["\n", chr(27) . "[01;33m", chr(27) . "[0m"],
+ $str));
+ }
+
+ return $str;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/AttributeTranslator.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/AttributeTranslator.php
new file mode 100644
index 0000000..b2013e1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/AttributeTranslator.php
@@ -0,0 +1,652 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Css;
+
+use Dompdf\Frame;
+use Dompdf\Helpers;
+
+/**
+ * Translates HTML 4.0 attributes into CSS rules
+ *
+ * @package dompdf
+ */
+class AttributeTranslator
+{
+ static $_style_attr = "_html_style_attribute";
+
+ // Munged data originally from
+ // http://www.w3.org/TR/REC-html40/index/attributes.html
+ // http://www.cs.tut.fi/~jkorpela/html2css.html
+ private static $__ATTRIBUTE_LOOKUP = [
+ //'caption' => array ( 'align' => '', ),
+ 'img' => [
+ 'align' => [
+ 'bottom' => 'vertical-align: baseline;',
+ 'middle' => 'vertical-align: middle;',
+ 'top' => 'vertical-align: top;',
+ 'left' => 'float: left;',
+ 'right' => 'float: right;'
+ ],
+ 'border' => 'border: %0.2Fpx solid;',
+ 'height' => '_set_px_height',
+ 'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;',
+ 'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;',
+ 'width' => '_set_px_width',
+ ],
+ 'table' => [
+ 'align' => [
+ 'left' => 'margin-left: 0; margin-right: auto;',
+ 'center' => 'margin-left: auto; margin-right: auto;',
+ 'right' => 'margin-left: auto; margin-right: 0;'
+ ],
+ 'bgcolor' => 'background-color: %s;',
+ 'border' => '_set_table_border',
+ 'cellpadding' => '_set_table_cellpadding', //'border-spacing: %0.2F; border-collapse: separate;',
+ 'cellspacing' => '_set_table_cellspacing',
+ 'frame' => [
+ 'void' => 'border-style: none;',
+ 'above' => 'border-top-style: solid;',
+ 'below' => 'border-bottom-style: solid;',
+ 'hsides' => 'border-left-style: solid; border-right-style: solid;',
+ 'vsides' => 'border-top-style: solid; border-bottom-style: solid;',
+ 'lhs' => 'border-left-style: solid;',
+ 'rhs' => 'border-right-style: solid;',
+ 'box' => 'border-style: solid;',
+ 'border' => 'border-style: solid;'
+ ],
+ 'rules' => '_set_table_rules',
+ 'width' => 'width: %s;',
+ ],
+ 'hr' => [
+ 'align' => '_set_hr_align', // Need to grab width to set 'left' & 'right' correctly
+ 'noshade' => 'border-style: solid;',
+ 'size' => '_set_hr_size', //'border-width: %0.2F px;',
+ 'width' => 'width: %s;',
+ ],
+ 'div' => [
+ 'align' => 'text-align: %s;',
+ ],
+ 'h1' => [
+ 'align' => 'text-align: %s;',
+ ],
+ 'h2' => [
+ 'align' => 'text-align: %s;',
+ ],
+ 'h3' => [
+ 'align' => 'text-align: %s;',
+ ],
+ 'h4' => [
+ 'align' => 'text-align: %s;',
+ ],
+ 'h5' => [
+ 'align' => 'text-align: %s;',
+ ],
+ 'h6' => [
+ 'align' => 'text-align: %s;',
+ ],
+ //TODO: translate more form element attributes
+ 'input' => [
+ 'size' => '_set_input_width'
+ ],
+ 'p' => [
+ 'align' => 'text-align: %s;',
+ ],
+// 'col' => array(
+// 'align' => '',
+// 'valign' => '',
+// ),
+// 'colgroup' => array(
+// 'align' => '',
+// 'valign' => '',
+// ),
+ 'tbody' => [
+ 'align' => '_set_table_row_align',
+ 'valign' => '_set_table_row_valign',
+ ],
+ 'td' => [
+ 'align' => 'text-align: %s;',
+ 'bgcolor' => '_set_background_color',
+ 'height' => 'height: %s;',
+ 'nowrap' => 'white-space: nowrap;',
+ 'valign' => 'vertical-align: %s;',
+ 'width' => 'width: %s;',
+ ],
+ 'tfoot' => [
+ 'align' => '_set_table_row_align',
+ 'valign' => '_set_table_row_valign',
+ ],
+ 'th' => [
+ 'align' => 'text-align: %s;',
+ 'bgcolor' => '_set_background_color',
+ 'height' => 'height: %s;',
+ 'nowrap' => 'white-space: nowrap;',
+ 'valign' => 'vertical-align: %s;',
+ 'width' => 'width: %s;',
+ ],
+ 'thead' => [
+ 'align' => '_set_table_row_align',
+ 'valign' => '_set_table_row_valign',
+ ],
+ 'tr' => [
+ 'align' => '_set_table_row_align',
+ 'bgcolor' => '_set_table_row_bgcolor',
+ 'valign' => '_set_table_row_valign',
+ ],
+ 'body' => [
+ 'background' => 'background-image: url(%s);',
+ 'bgcolor' => '_set_background_color',
+ 'link' => '_set_body_link',
+ 'text' => '_set_color',
+ ],
+ 'br' => [
+ 'clear' => 'clear: %s;',
+ ],
+ 'basefont' => [
+ 'color' => '_set_color',
+ 'face' => 'font-family: %s;',
+ 'size' => '_set_basefont_size',
+ ],
+ 'font' => [
+ 'color' => '_set_color',
+ 'face' => 'font-family: %s;',
+ 'size' => '_set_font_size',
+ ],
+ 'dir' => [
+ 'compact' => 'margin: 0.5em 0;',
+ ],
+ 'dl' => [
+ 'compact' => 'margin: 0.5em 0;',
+ ],
+ 'menu' => [
+ 'compact' => 'margin: 0.5em 0;',
+ ],
+ 'ol' => [
+ 'compact' => 'margin: 0.5em 0;',
+ 'start' => 'counter-reset: -dompdf-default-counter %d;',
+ 'type' => 'list-style-type: %s;',
+ ],
+ 'ul' => [
+ 'compact' => 'margin: 0.5em 0;',
+ 'type' => 'list-style-type: %s;',
+ ],
+ 'li' => [
+ 'type' => 'list-style-type: %s;',
+ 'value' => 'counter-reset: -dompdf-default-counter %d;',
+ ],
+ 'pre' => [
+ 'width' => 'width: %s;',
+ ],
+ ];
+
+ protected static $_last_basefont_size = 3;
+ protected static $_font_size_lookup = [
+ // For basefont support
+ -3 => "4pt",
+ -2 => "5pt",
+ -1 => "6pt",
+ 0 => "7pt",
+
+ 1 => "8pt",
+ 2 => "10pt",
+ 3 => "12pt",
+ 4 => "14pt",
+ 5 => "18pt",
+ 6 => "24pt",
+ 7 => "34pt",
+
+ // For basefont support
+ 8 => "48pt",
+ 9 => "44pt",
+ 10 => "52pt",
+ 11 => "60pt",
+ ];
+
+ /**
+ * @param Frame $frame
+ */
+ static function translate_attributes(Frame $frame)
+ {
+ $node = $frame->get_node();
+ $tag = $node->nodeName;
+
+ if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) {
+ return;
+ }
+
+ $valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag];
+ $attrs = $node->attributes;
+ $style = rtrim($node->getAttribute(self::$_style_attr), "; ");
+ if ($style != "") {
+ $style .= ";";
+ }
+
+ foreach ($attrs as $attr => $attr_node) {
+ if (!isset($valid_attrs[$attr])) {
+ continue;
+ }
+
+ $value = $attr_node->value;
+
+ $target = $valid_attrs[$attr];
+
+ // Look up $value in $target, if $target is an array:
+ if (is_array($target)) {
+ if (isset($target[$value])) {
+ $style .= " " . self::_resolve_target($node, $target[$value], $value);
+ }
+ } else {
+ // otherwise use target directly
+ $style .= " " . self::_resolve_target($node, $target, $value);
+ }
+ }
+
+ if (!is_null($style)) {
+ $style = ltrim($style);
+ $node->setAttribute(self::$_style_attr, $style);
+ }
+ }
+
+ /**
+ * @param \DOMNode $node
+ * @param string $target
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _resolve_target(\DOMNode $node, $target, $value)
+ {
+ if ($target[0] === "_") {
+ return self::$target($node, $value);
+ }
+
+ return $value ? sprintf($target, $value) : "";
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $new_style
+ */
+ static function append_style(\DOMElement $node, $new_style)
+ {
+ $style = rtrim($node->getAttribute(self::$_style_attr), ";");
+ $style .= $new_style;
+ $style = ltrim($style, ";");
+ $node->setAttribute(self::$_style_attr, $style);
+ }
+
+ /**
+ * @param \DOMNode $node
+ *
+ * @return \DOMNodeList|\DOMElement[]
+ */
+ protected static function get_cell_list(\DOMNode $node)
+ {
+ $xpath = new \DOMXpath($node->ownerDocument);
+
+ switch ($node->nodeName) {
+ default:
+ case "table":
+ $query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th";
+ break;
+
+ case "tbody":
+ case "tfoot":
+ case "thead":
+ $query = "tr/td | tr/th";
+ break;
+
+ case "tr":
+ $query = "td | th";
+ break;
+ }
+
+ return $xpath->query($query, $node);
+ }
+
+ /**
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _get_valid_color($value)
+ {
+ if (preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches)) {
+ $value = "#$matches[1]";
+ }
+
+ return $value;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _set_color(\DOMElement $node, $value)
+ {
+ $value = self::_get_valid_color($value);
+
+ return "color: $value;";
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _set_background_color(\DOMElement $node, $value)
+ {
+ $value = self::_get_valid_color($value);
+
+ return "background-color: $value;";
+ }
+
+ protected static function _set_px_width(\DOMElement $node, string $value): string
+ {
+ $v = trim($value);
+
+ if (Helpers::is_percent($v)) {
+ return sprintf("width: %s;", $v);
+ }
+
+ if (is_numeric(mb_substr($v, 0, 1))) {
+ return sprintf("width: %spx;", (float) $v);
+ }
+
+ return "";
+ }
+
+ protected static function _set_px_height(\DOMElement $node, string $value): string
+ {
+ $v = trim($value);
+
+ if (Helpers::is_percent($v)) {
+ return sprintf("height: %s;", $v);
+ }
+
+ if (is_numeric(mb_substr($v, 0, 1))) {
+ return sprintf("height: %spx;", (float) $v);
+ }
+
+ return "";
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null
+ */
+ protected static function _set_table_cellpadding(\DOMElement $node, $value)
+ {
+ $cell_list = self::get_cell_list($node);
+
+ foreach ($cell_list as $cell) {
+ self::append_style($cell, "; padding: {$value}px;");
+ }
+
+ return null;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _set_table_border(\DOMElement $node, $value)
+ {
+ return "border-width: $value" . "px;";
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _set_table_cellspacing(\DOMElement $node, $value)
+ {
+ $style = rtrim($node->getAttribute(self::$_style_attr), ";");
+
+ if ($value == 0) {
+ $style .= "; border-collapse: collapse;";
+ } else {
+ $style .= "; border-spacing: {$value}px; border-collapse: separate;";
+ }
+
+ return ltrim($style, ";");
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null|string
+ */
+ protected static function _set_table_rules(\DOMElement $node, $value)
+ {
+ $new_style = "; border-collapse: collapse;";
+
+ switch ($value) {
+ case "none":
+ $new_style .= "border-style: none;";
+ break;
+
+ case "groups":
+ // FIXME: unsupported
+ return null;
+
+ case "rows":
+ $new_style .= "border-style: solid none solid none; border-width: 1px; ";
+ break;
+
+ case "cols":
+ $new_style .= "border-style: none solid none solid; border-width: 1px; ";
+ break;
+
+ case "all":
+ $new_style .= "border-style: solid; border-width: 1px; ";
+ break;
+
+ default:
+ // Invalid value
+ return null;
+ }
+
+ $cell_list = self::get_cell_list($node);
+
+ foreach ($cell_list as $cell) {
+ $style = $cell->getAttribute(self::$_style_attr);
+ $style .= $new_style;
+ $cell->setAttribute(self::$_style_attr, $style);
+ }
+
+ $style = rtrim($node->getAttribute(self::$_style_attr), ";");
+ $style .= "; border-collapse: collapse; ";
+
+ return ltrim($style, "; ");
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _set_hr_size(\DOMElement $node, $value)
+ {
+ $style = rtrim($node->getAttribute(self::$_style_attr), ";");
+ $style .= "; border-width: " . max(0, $value - 2) . "; ";
+
+ return ltrim($style, "; ");
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null|string
+ */
+ protected static function _set_hr_align(\DOMElement $node, $value)
+ {
+ $style = rtrim($node->getAttribute(self::$_style_attr), ";");
+ $width = $node->getAttribute("width");
+
+ if ($width == "") {
+ $width = "100%";
+ }
+
+ $remainder = 100 - (double)rtrim($width, "% ");
+
+ switch ($value) {
+ case "left":
+ $style .= "; margin-right: $remainder %;";
+ break;
+
+ case "right":
+ $style .= "; margin-left: $remainder %;";
+ break;
+
+ case "center":
+ $style .= "; margin-left: auto; margin-right: auto;";
+ break;
+
+ default:
+ return null;
+ }
+
+ return ltrim($style, "; ");
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null|string
+ */
+ protected static function _set_input_width(\DOMElement $node, $value)
+ {
+ if (empty($value)) { return null; }
+
+ if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), ["text","password"])) {
+ return sprintf("width: %Fem", (((int)$value * .65)+2));
+ } else {
+ return sprintf("width: %upx;", (int)$value);
+ }
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null
+ */
+ protected static function _set_table_row_align(\DOMElement $node, $value)
+ {
+ $cell_list = self::get_cell_list($node);
+
+ foreach ($cell_list as $cell) {
+ self::append_style($cell, "; text-align: $value;");
+ }
+
+ return null;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null
+ */
+ protected static function _set_table_row_valign(\DOMElement $node, $value)
+ {
+ $cell_list = self::get_cell_list($node);
+
+ foreach ($cell_list as $cell) {
+ self::append_style($cell, "; vertical-align: $value;");
+ }
+
+ return null;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null
+ */
+ protected static function _set_table_row_bgcolor(\DOMElement $node, $value)
+ {
+ $cell_list = self::get_cell_list($node);
+ $value = self::_get_valid_color($value);
+
+ foreach ($cell_list as $cell) {
+ self::append_style($cell, "; background-color: $value;");
+ }
+
+ return null;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null
+ */
+ protected static function _set_body_link(\DOMElement $node, $value)
+ {
+ $a_list = $node->getElementsByTagName("a");
+ $value = self::_get_valid_color($value);
+
+ foreach ($a_list as $a) {
+ self::append_style($a, "; color: $value;");
+ }
+
+ return null;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return null
+ */
+ protected static function _set_basefont_size(\DOMElement $node, $value)
+ {
+ // FIXME: ? we don't actually set the font size of anything here, just
+ // the base size for later modification by <font> tags.
+ self::$_last_basefont_size = $value;
+
+ return null;
+ }
+
+ /**
+ * @param \DOMElement $node
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function _set_font_size(\DOMElement $node, $value)
+ {
+ $style = $node->getAttribute(self::$_style_attr);
+
+ if ($value[0] === "-" || $value[0] === "+") {
+ $value = self::$_last_basefont_size + (int)$value;
+ }
+
+ if (isset(self::$_font_size_lookup[$value])) {
+ $style .= "; font-size: " . self::$_font_size_lookup[$value] . ";";
+ } else {
+ $style .= "; font-size: $value;";
+ }
+
+ return ltrim($style, "; ");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Color.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Color.php
new file mode 100644
index 0000000..28a9f56
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Color.php
@@ -0,0 +1,339 @@
+<?php
+
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Css;
+
+use Dompdf\Helpers;
+
+class Color
+{
+ static $cssColorNames = [
+ "aliceblue" => "F0F8FF",
+ "antiquewhite" => "FAEBD7",
+ "aqua" => "00FFFF",
+ "aquamarine" => "7FFFD4",
+ "azure" => "F0FFFF",
+ "beige" => "F5F5DC",
+ "bisque" => "FFE4C4",
+ "black" => "000000",
+ "blanchedalmond" => "FFEBCD",
+ "blue" => "0000FF",
+ "blueviolet" => "8A2BE2",
+ "brown" => "A52A2A",
+ "burlywood" => "DEB887",
+ "cadetblue" => "5F9EA0",
+ "chartreuse" => "7FFF00",
+ "chocolate" => "D2691E",
+ "coral" => "FF7F50",
+ "cornflowerblue" => "6495ED",
+ "cornsilk" => "FFF8DC",
+ "crimson" => "DC143C",
+ "cyan" => "00FFFF",
+ "darkblue" => "00008B",
+ "darkcyan" => "008B8B",
+ "darkgoldenrod" => "B8860B",
+ "darkgray" => "A9A9A9",
+ "darkgreen" => "006400",
+ "darkgrey" => "A9A9A9",
+ "darkkhaki" => "BDB76B",
+ "darkmagenta" => "8B008B",
+ "darkolivegreen" => "556B2F",
+ "darkorange" => "FF8C00",
+ "darkorchid" => "9932CC",
+ "darkred" => "8B0000",
+ "darksalmon" => "E9967A",
+ "darkseagreen" => "8FBC8F",
+ "darkslateblue" => "483D8B",
+ "darkslategray" => "2F4F4F",
+ "darkslategrey" => "2F4F4F",
+ "darkturquoise" => "00CED1",
+ "darkviolet" => "9400D3",
+ "deeppink" => "FF1493",
+ "deepskyblue" => "00BFFF",
+ "dimgray" => "696969",
+ "dimgrey" => "696969",
+ "dodgerblue" => "1E90FF",
+ "firebrick" => "B22222",
+ "floralwhite" => "FFFAF0",
+ "forestgreen" => "228B22",
+ "fuchsia" => "FF00FF",
+ "gainsboro" => "DCDCDC",
+ "ghostwhite" => "F8F8FF",
+ "gold" => "FFD700",
+ "goldenrod" => "DAA520",
+ "gray" => "808080",
+ "green" => "008000",
+ "greenyellow" => "ADFF2F",
+ "grey" => "808080",
+ "honeydew" => "F0FFF0",
+ "hotpink" => "FF69B4",
+ "indianred" => "CD5C5C",
+ "indigo" => "4B0082",
+ "ivory" => "FFFFF0",
+ "khaki" => "F0E68C",
+ "lavender" => "E6E6FA",
+ "lavenderblush" => "FFF0F5",
+ "lawngreen" => "7CFC00",
+ "lemonchiffon" => "FFFACD",
+ "lightblue" => "ADD8E6",
+ "lightcoral" => "F08080",
+ "lightcyan" => "E0FFFF",
+ "lightgoldenrodyellow" => "FAFAD2",
+ "lightgray" => "D3D3D3",
+ "lightgreen" => "90EE90",
+ "lightgrey" => "D3D3D3",
+ "lightpink" => "FFB6C1",
+ "lightsalmon" => "FFA07A",
+ "lightseagreen" => "20B2AA",
+ "lightskyblue" => "87CEFA",
+ "lightslategray" => "778899",
+ "lightslategrey" => "778899",
+ "lightsteelblue" => "B0C4DE",
+ "lightyellow" => "FFFFE0",
+ "lime" => "00FF00",
+ "limegreen" => "32CD32",
+ "linen" => "FAF0E6",
+ "magenta" => "FF00FF",
+ "maroon" => "800000",
+ "mediumaquamarine" => "66CDAA",
+ "mediumblue" => "0000CD",
+ "mediumorchid" => "BA55D3",
+ "mediumpurple" => "9370DB",
+ "mediumseagreen" => "3CB371",
+ "mediumslateblue" => "7B68EE",
+ "mediumspringgreen" => "00FA9A",
+ "mediumturquoise" => "48D1CC",
+ "mediumvioletred" => "C71585",
+ "midnightblue" => "191970",
+ "mintcream" => "F5FFFA",
+ "mistyrose" => "FFE4E1",
+ "moccasin" => "FFE4B5",
+ "navajowhite" => "FFDEAD",
+ "navy" => "000080",
+ "oldlace" => "FDF5E6",
+ "olive" => "808000",
+ "olivedrab" => "6B8E23",
+ "orange" => "FFA500",
+ "orangered" => "FF4500",
+ "orchid" => "DA70D6",
+ "palegoldenrod" => "EEE8AA",
+ "palegreen" => "98FB98",
+ "paleturquoise" => "AFEEEE",
+ "palevioletred" => "DB7093",
+ "papayawhip" => "FFEFD5",
+ "peachpuff" => "FFDAB9",
+ "peru" => "CD853F",
+ "pink" => "FFC0CB",
+ "plum" => "DDA0DD",
+ "powderblue" => "B0E0E6",
+ "purple" => "800080",
+ "red" => "FF0000",
+ "rosybrown" => "BC8F8F",
+ "royalblue" => "4169E1",
+ "saddlebrown" => "8B4513",
+ "salmon" => "FA8072",
+ "sandybrown" => "F4A460",
+ "seagreen" => "2E8B57",
+ "seashell" => "FFF5EE",
+ "sienna" => "A0522D",
+ "silver" => "C0C0C0",
+ "skyblue" => "87CEEB",
+ "slateblue" => "6A5ACD",
+ "slategray" => "708090",
+ "slategrey" => "708090",
+ "snow" => "FFFAFA",
+ "springgreen" => "00FF7F",
+ "steelblue" => "4682B4",
+ "tan" => "D2B48C",
+ "teal" => "008080",
+ "thistle" => "D8BFD8",
+ "tomato" => "FF6347",
+ "turquoise" => "40E0D0",
+ "violet" => "EE82EE",
+ "wheat" => "F5DEB3",
+ "white" => "FFFFFF",
+ "whitesmoke" => "F5F5F5",
+ "yellow" => "FFFF00",
+ "yellowgreen" => "9ACD32",
+ ];
+
+ /**
+ * @param array|string|null $color
+ * @return array|string|null
+ */
+ static function parse($color)
+ {
+ if ($color === null) {
+ return null;
+ }
+
+ if (is_array($color)) {
+ // Assume the array has the right format...
+ // FIXME: should/could verify this.
+ return $color;
+ }
+
+ static $cache = [];
+
+ $color = strtolower($color);
+
+ if (isset($cache[$color])) {
+ return $cache[$color];
+ }
+
+ if ($color === "transparent") {
+ return $cache[$color] = $color;
+ }
+
+ if (isset(self::$cssColorNames[$color])) {
+ return $cache[$color] = self::getArray(self::$cssColorNames[$color]);
+ }
+
+ // https://www.w3.org/TR/css-color-4/#hex-notation
+ if (mb_substr($color, 0, 1) === "#") {
+ $length = mb_strlen($color);
+ $alpha = 1.0;
+
+ // #rgb format
+ if ($length === 4) {
+ return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]);
+ }
+
+ // #rgba format
+ if ($length === 5) {
+ if (ctype_xdigit($color[4])) {
+ $alpha = round(hexdec($color[4] . $color[4])/255, 2);
+ }
+ return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha);
+ }
+
+ // #rrggbb format
+ if ($length === 7) {
+ return $cache[$color] = self::getArray(mb_substr($color, 1, 6));
+ }
+
+ // #rrggbbaa format
+ if ($length === 9) {
+ if (ctype_xdigit(mb_substr($color, 7, 2))) {
+ $alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2);
+ }
+ return $cache[$color] = self::getArray(mb_substr($color, 1, 6), $alpha);
+ }
+
+ return null;
+ }
+
+ // rgb( r g b [/α] ) / rgb( r,g,b[,α] ) format and alias rgba()
+ // https://www.w3.org/TR/css-color-4/#rgb-functions
+ if (mb_substr($color, 0, 4) === "rgb(" || mb_substr($color, 0, 5) === "rgba(") {
+ $i = mb_strpos($color, "(");
+ $j = mb_strpos($color, ")");
+
+ // Bad color value
+ if ($i === false || $j === false) {
+ return null;
+ }
+
+ $value_decl = trim(mb_substr($color, $i + 1, $j - $i - 1));
+
+ if (mb_strpos($value_decl, ",") === false) {
+ // Space-separated values syntax `r g b` or `r g b / α`
+ $parts = preg_split("/\s*\/\s*/", $value_decl);
+ $triplet = preg_split("/\s+/", $parts[0]);
+ $alpha = $parts[1] ?? 1.0;
+ } else {
+ // Comma-separated values syntax `r, g, b` or `r, g, b, α`
+ $parts = preg_split("/\s*,\s*/", $value_decl);
+ $triplet = array_slice($parts, 0, 3);
+ $alpha = $parts[3] ?? 1.0;
+ }
+
+ if (count($triplet) !== 3) {
+ return null;
+ }
+
+ // Parse alpha value
+ if (Helpers::is_percent($alpha)) {
+ $alpha = (float) $alpha / 100;
+ } else {
+ $alpha = (float) $alpha;
+ }
+
+ $alpha = max(0.0, min($alpha, 1.0));
+
+ foreach ($triplet as &$c) {
+ if (Helpers::is_percent($c)) {
+ $c = round((float) $c * 2.55);
+ }
+ }
+
+ return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet), $alpha);
+ }
+
+ // cmyk( c,m,y,k ) format
+ // http://www.w3.org/TR/css3-gcpm/#cmyk-colors
+ if (mb_substr($color, 0, 5) === "cmyk(") {
+ $i = mb_strpos($color, "(");
+ $j = mb_strpos($color, ")");
+
+ // Bad color value
+ if ($i === false || $j === false) {
+ return null;
+ }
+
+ $values = explode(",", mb_substr($color, $i + 1, $j - $i - 1));
+
+ if (count($values) != 4) {
+ return null;
+ }
+
+ $values = array_map(function ($c) {
+ return min(1.0, max(0.0, floatval(trim($c))));
+ }, $values);
+
+ return $cache[$color] = self::getArray($values);
+ }
+
+ // Invalid or unsupported color format
+ return null;
+ }
+
+ /**
+ * @param array|string $color
+ * @param float $alpha
+ * @return array
+ */
+ static function getArray($color, $alpha = 1.0)
+ {
+ $c = [null, null, null, null, "alpha" => $alpha, "hex" => null];
+
+ if (is_array($color)) {
+ $c = $color;
+ $c["c"] = $c[0];
+ $c["m"] = $c[1];
+ $c["y"] = $c[2];
+ $c["k"] = $c[3];
+ $c["alpha"] = $alpha;
+ $c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])";
+ } else {
+ if (ctype_xdigit($color) === false || mb_strlen($color) !== 6) {
+ // invalid color value ... expected 6-character hex
+ return $c;
+ }
+ $c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff;
+ $c[1] = hexdec(mb_substr($color, 2, 2)) / 0xff;
+ $c[2] = hexdec(mb_substr($color, 4, 2)) / 0xff;
+ $c["r"] = $c[0];
+ $c["g"] = $c[1];
+ $c["b"] = $c[2];
+ $c["alpha"] = $alpha;
+ $c["hex"] = sprintf("#%s%02X", $color, round($alpha * 255));
+ }
+
+ return $c;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Style.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Style.php
new file mode 100644
index 0000000..2d4c9d7
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Style.php
@@ -0,0 +1,3743 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Css;
+
+use Dompdf\Adapter\CPDF;
+use Dompdf\Exception;
+use Dompdf\FontMetrics;
+use Dompdf\Frame;
+
+/**
+ * Represents CSS properties.
+ *
+ * The Style class is responsible for handling and storing CSS properties.
+ * It includes methods to resolve colors and lengths, as well as getters &
+ * setters for many CSS properties.
+ *
+ * Access to the different CSS properties is provided by the methods
+ * {@link Style::set_prop()} and {@link Style::get_specified()}, and the
+ * property overload methods {@link Style::__set()} and {@link Style::__get()},
+ * as well as {@link Style::set_used()}. The latter methods operate on used
+ * values and permit access to any (CSS) property using the following syntax:
+ *
+ * ```
+ * $style->margin_top = 10.0;
+ * echo $style->margin_top; // Returns `10.0`
+ * ```
+ *
+ * To declare a property from a string, use {@link Style::set_prop()}:
+ *
+ * ```
+ * $style->set_prop("margin_top", "1em");
+ * echo $style->get_specified("margin_top"); // Returns `1em`
+ * echo $style->margin_top; // Returns `12.0`, assuming the default font size
+ * ```
+ *
+ * Actual CSS parsing is performed in the {@link Stylesheet} class.
+ *
+ * @property string $azimuth
+ * @property string $background_attachment
+ * @property array|string $background_color
+ * @property string $background_image Image URL or `none`
+ * @property string $background_image_resolution
+ * @property array $background_position
+ * @property string $background_repeat
+ * @property array|string $background_size `cover`, `contain`, or `[width, height]`, each being a length, percentage, or `auto`
+ * @property string $border_collapse
+ * @property string $border_color Only use for setting all sides to the same color
+ * @property float[] $border_spacing Pair of `[horizontal, vertical]` spacing
+ * @property string $border_style Only use for setting all sides to the same style
+ * @property array|string $border_top_color
+ * @property array|string $border_right_color
+ * @property array|string $border_bottom_color
+ * @property array|string $border_left_color
+ * @property string $border_top_style Valid border style
+ * @property string $border_right_style Valid border style
+ * @property string $border_bottom_style Valid border style
+ * @property string $border_left_style Valid border style
+ * @property float $border_top_width Length in pt
+ * @property float $border_right_width Length in pt
+ * @property float $border_bottom_width Length in pt
+ * @property float $border_left_width Length in pt
+ * @property string $border_width Only use for setting all sides to the same width
+ * @property float|string $border_bottom_left_radius Radius in pt or a percentage value
+ * @property float|string $border_bottom_right_radius Radius in pt or a percentage value
+ * @property float|string $border_top_left_radius Radius in pt or a percentage value
+ * @property float|string $border_top_right_radius Radius in pt or a percentage value
+ * @property string $border_radius Only use for setting all corners to the same radius
+ * @property float|string $bottom Length in pt, a percentage value, or `auto`
+ * @property string $caption_side
+ * @property string $clear
+ * @property string $clip
+ * @property array|string $color
+ * @property string[]|string $content List of content components, `normal`, or `none`
+ * @property array|string $counter_increment Array defining the counters to increment or `none`
+ * @property array|string $counter_reset Array defining the counters to reset or `none`
+ * @property string $cue_after
+ * @property string $cue_before
+ * @property string $cue
+ * @property string $cursor
+ * @property string $direction
+ * @property string $display
+ * @property string $elevation
+ * @property string $empty_cells
+ * @property string $float
+ * @property string $font_family
+ * @property float $font_size Length in pt
+ * @property string $font_style
+ * @property string $font_variant
+ * @property string $font_weight
+ * @property float|string $height Length in pt, a percentage value, or `auto`
+ * @property string $image_resolution
+ * @property string $inset Only use for setting all box insets to the same length
+ * @property float|string $left Length in pt, a percentage value, or `auto`
+ * @property float $letter_spacing Length in pt
+ * @property float $line_height Length in pt
+ * @property string $list_style_image Image URL or `none`
+ * @property string $list_style_position
+ * @property string $list_style_type
+ * @property float|string $margin_right Length in pt, a percentage value, or `auto`
+ * @property float|string $margin_left Length in pt, a percentage value, or `auto`
+ * @property float|string $margin_top Length in pt, a percentage value, or `auto`
+ * @property float|string $margin_bottom Length in pt, a percentage value, or `auto`
+ * @property string $margin Only use for setting all sides to the same length
+ * @property float|string $max_height Length in pt, a percentage value, or `none`
+ * @property float|string $max_width Length in pt, a percentage value, or `none`
+ * @property float|string $min_height Length in pt, a percentage value, or `auto`
+ * @property float|string $min_width Length in pt, a percentage value, or `auto`
+ * @property float $opacity Number in the range [0, 1]
+ * @property int $orphans
+ * @property array|string $outline_color
+ * @property string $outline_style Valid border style, except for `hidden`
+ * @property float $outline_width Length in pt
+ * @property float $outline_offset Length in pt
+ * @property string $overflow
+ * @property string $overflow_wrap
+ * @property float|string $padding_top Length in pt or a percentage value
+ * @property float|string $padding_right Length in pt or a percentage value
+ * @property float|string $padding_bottom Length in pt or a percentage value
+ * @property float|string $padding_left Length in pt or a percentage value
+ * @property string $padding Only use for setting all sides to the same length
+ * @property string $page_break_after
+ * @property string $page_break_before
+ * @property string $page_break_inside
+ * @property string $pause_after
+ * @property string $pause_before
+ * @property string $pause
+ * @property string $pitch_range
+ * @property string $pitch
+ * @property string $play_during
+ * @property string $position
+ * @property string $quotes
+ * @property string $richness
+ * @property float|string $right Length in pt, a percentage value, or `auto`
+ * @property float[]|string $size Pair of `[width, height]` or `auto`
+ * @property string $speak_header
+ * @property string $speak_numeral
+ * @property string $speak_punctuation
+ * @property string $speak
+ * @property string $speech_rate
+ * @property string $src
+ * @property string $stress
+ * @property string $table_layout
+ * @property string $text_align
+ * @property string $text_decoration
+ * @property float|string $text_indent Length in pt or a percentage value
+ * @property string $text_transform
+ * @property float|string $top Length in pt, a percentage value, or `auto`
+ * @property array $transform List of transforms
+ * @property array $transform_origin
+ * @property string $unicode_bidi
+ * @property string $unicode_range
+ * @property string $vertical_align
+ * @property string $visibility
+ * @property string $voice_family
+ * @property string $volume
+ * @property string $white_space
+ * @property int $widows
+ * @property float|string $width Length in pt, a percentage value, or `auto`
+ * @property string $word_break
+ * @property float $word_spacing Length in pt
+ * @property int|string $z_index Integer value or `auto`
+ * @property string $_dompdf_keep
+ *
+ * @package dompdf
+ */
+class Style
+{
+ protected const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*";
+ protected const CSS_INTEGER = "[+-]?\d+";
+ protected const CSS_NUMBER = "[+-]?\d*\.?\d+(?:[eE][+-]?\d+)?";
+
+ /**
+ * Default font size, in points.
+ *
+ * @var float
+ */
+ public static $default_font_size = 12;
+
+ /**
+ * Default line height, as a fraction of the font size.
+ *
+ * @var float
+ */
+ public static $default_line_height = 1.2;
+
+ /**
+ * Default "absolute" font sizes relative to the default font-size
+ * https://www.w3.org/TR/css-fonts-3/#absolute-size-value
+ *
+ * @var array<float>
+ */
+ public static $font_size_keywords = [
+ "xx-small" => 0.6, // 3/5
+ "x-small" => 0.75, // 3/4
+ "small" => 0.889, // 8/9
+ "medium" => 1, // 1
+ "large" => 1.2, // 6/5
+ "x-large" => 1.5, // 3/2
+ "xx-large" => 2.0, // 2/1
+ ];
+
+ /**
+ * List of valid text-align keywords.
+ */
+ public const TEXT_ALIGN_KEYWORDS = ["left", "right", "center", "justify"];
+
+ /**
+ * List of valid vertical-align keywords.
+ */
+ public const VERTICAL_ALIGN_KEYWORDS = ["baseline", "bottom", "middle",
+ "sub", "super", "text-bottom", "text-top", "top"];
+
+ /**
+ * List of all block-level (outer) display types.
+ * * https://www.w3.org/TR/css-display-3/#display-type
+ * * https://www.w3.org/TR/css-display-3/#block-level
+ */
+ public const BLOCK_LEVEL_TYPES = [
+ "block",
+ // "flow-root",
+ "list-item",
+ // "flex",
+ // "grid",
+ "table"
+ ];
+
+ /**
+ * List of all inline-level (outer) display types.
+ * * https://www.w3.org/TR/css-display-3/#display-type
+ * * https://www.w3.org/TR/css-display-3/#inline-level
+ */
+ public const INLINE_LEVEL_TYPES = [
+ "inline",
+ "inline-block",
+ // "inline-flex",
+ // "inline-grid",
+ "inline-table"
+ ];
+
+ /**
+ * List of all table-internal (outer) display types.
+ * * https://www.w3.org/TR/css-display-3/#layout-specific-display
+ */
+ public const TABLE_INTERNAL_TYPES = [
+ "table-row-group",
+ "table-header-group",
+ "table-footer-group",
+ "table-row",
+ "table-cell",
+ "table-column-group",
+ "table-column",
+ "table-caption"
+ ];
+
+ /**
+ * List of all inline (inner) display types.
+ */
+ public const INLINE_TYPES = ["inline"];
+
+ /**
+ * List of all block (inner) display types.
+ */
+ public const BLOCK_TYPES = ["block", "inline-block", "table-cell", "list-item"];
+
+ /**
+ * List of all table (inner) display types.
+ */
+ public const TABLE_TYPES = ["table", "inline-table"];
+
+ /**
+ * Lookup table for valid display types. Initially computed from the
+ * different constants.
+ *
+ * @var array
+ */
+ protected static $valid_display_types = [];
+
+ /**
+ * List of all positioned types.
+ */
+ public const POSITIONED_TYPES = ["relative", "absolute", "fixed"];
+
+ /**
+ * List of valid border styles.
+ */
+ public const BORDER_STYLES = [
+ "none", "hidden",
+ "dotted", "dashed", "solid",
+ "double", "groove", "ridge", "inset", "outset"
+ ];
+
+ /**
+ * List of valid outline-style values.
+ * Same as the border styles, except `auto` is allowed, `hidden` is not.
+ *
+ * @link https://www.w3.org/TR/css-ui-4/#typedef-outline-line-style
+ */
+ protected const OUTLINE_STYLES = [
+ "auto", "none",
+ "dotted", "dashed", "solid",
+ "double", "groove", "ridge", "inset", "outset"
+ ];
+
+ /**
+ * Map of CSS shorthand properties and their corresponding sub-properties.
+ * The order of the sub-properties is relevant for the fallback getter,
+ * which is used in case no specific getter method is defined.
+ *
+ * @var array
+ */
+ protected static $_props_shorthand = [
+ "background" => [
+ "background_image",
+ "background_position",
+ "background_size",
+ "background_repeat",
+ // "background_origin",
+ // "background_clip",
+ "background_attachment",
+ "background_color"
+ ],
+ "border" => [
+ "border_top_width",
+ "border_right_width",
+ "border_bottom_width",
+ "border_left_width",
+ "border_top_style",
+ "border_right_style",
+ "border_bottom_style",
+ "border_left_style",
+ "border_top_color",
+ "border_right_color",
+ "border_bottom_color",
+ "border_left_color"
+ ],
+ "border_top" => [
+ "border_top_width",
+ "border_top_style",
+ "border_top_color"
+ ],
+ "border_right" => [
+ "border_right_width",
+ "border_right_style",
+ "border_right_color"
+ ],
+ "border_bottom" => [
+ "border_bottom_width",
+ "border_bottom_style",
+ "border_bottom_color"
+ ],
+ "border_left" => [
+ "border_left_width",
+ "border_left_style",
+ "border_left_color"
+ ],
+ "border_width" => [
+ "border_top_width",
+ "border_right_width",
+ "border_bottom_width",
+ "border_left_width"
+ ],
+ "border_style" => [
+ "border_top_style",
+ "border_right_style",
+ "border_bottom_style",
+ "border_left_style"
+ ],
+ "border_color" => [
+ "border_top_color",
+ "border_right_color",
+ "border_bottom_color",
+ "border_left_color"
+ ],
+ "border_radius" => [
+ "border_top_left_radius",
+ "border_top_right_radius",
+ "border_bottom_right_radius",
+ "border_bottom_left_radius"
+ ],
+ "font" => [
+ "font_family",
+ "font_size",
+ // "font_stretch",
+ "font_style",
+ "font_variant",
+ "font_weight",
+ "line_height"
+ ],
+ "inset" => [
+ "top",
+ "right",
+ "bottom",
+ "left"
+ ],
+ "list_style" => [
+ "list_style_image",
+ "list_style_position",
+ "list_style_type"
+ ],
+ "margin" => [
+ "margin_top",
+ "margin_right",
+ "margin_bottom",
+ "margin_left"
+ ],
+ "padding" => [
+ "padding_top",
+ "padding_right",
+ "padding_bottom",
+ "padding_left"
+ ],
+ "outline" => [
+ "outline_width",
+ "outline_style",
+ "outline_color"
+ ]
+ ];
+
+ /**
+ * Maps legacy property names to actual property names.
+ *
+ * @var array
+ */
+ protected static $_props_alias = [
+ "word_wrap" => "overflow_wrap",
+ "_dompdf_background_image_resolution" => "background_image_resolution",
+ "_dompdf_image_resolution" => "image_resolution",
+ "_webkit_transform" => "transform",
+ "_webkit_transform_origin" => "transform_origin"
+ ];
+
+ /**
+ * Default style values.
+ *
+ * @link https://www.w3.org/TR/CSS21/propidx.html
+ *
+ * @var array
+ */
+ protected static $_defaults = null;
+
+ /**
+ * List of inherited properties
+ *
+ * @link https://www.w3.org/TR/CSS21/propidx.html
+ *
+ * @var array
+ */
+ protected static $_inherited = null;
+
+ /**
+ * Caches method_exists result
+ *
+ * @var array<bool>
+ */
+ protected static $_methods_cache = [];
+
+ /**
+ * The stylesheet this style belongs to
+ *
+ * @var Stylesheet
+ */
+ protected $_stylesheet;
+
+ /**
+ * Media queries attached to the style
+ *
+ * @var array
+ */
+ protected $_media_queries;
+
+ /**
+ * Properties set by an `!important` declaration.
+ *
+ * @var array
+ */
+ protected $_important_props = [];
+
+ /**
+ * Specified (or declared) values of the CSS properties.
+ *
+ * https://www.w3.org/TR/css-cascade-3/#value-stages
+ *
+ * @var array
+ */
+ protected $_props = [];
+
+ /**
+ * Computed values of the CSS properties.
+ *
+ * @var array
+ */
+ protected $_props_computed = [];
+
+ /**
+ * Used values of the CSS properties.
+ *
+ * @var array
+ */
+ protected $_props_used = [];
+
+ /**
+ * Marks properties with non-final used values that should be cleared on
+ * style reset.
+ *
+ * @var array
+ */
+ protected $non_final_used = [];
+
+ protected static $_dependency_map = [
+ "border_top_style" => [
+ "border_top_width"
+ ],
+ "border_bottom_style" => [
+ "border_bottom_width"
+ ],
+ "border_left_style" => [
+ "border_left_width"
+ ],
+ "border_right_style" => [
+ "border_right_width"
+ ],
+ "direction" => [
+ "text_align"
+ ],
+ "font_size" => [
+ "background_position",
+ "background_size",
+ "border_top_width",
+ "border_right_width",
+ "border_bottom_width",
+ "border_left_width",
+ "border_top_left_radius",
+ "border_top_right_radius",
+ "border_bottom_right_radius",
+ "border_bottom_left_radius",
+ "letter_spacing",
+ "line_height",
+ "margin_top",
+ "margin_right",
+ "margin_bottom",
+ "margin_left",
+ "outline_width",
+ "outline_offset",
+ "padding_top",
+ "padding_right",
+ "padding_bottom",
+ "padding_left",
+ "word_spacing",
+ "width",
+ "height",
+ "min-width",
+ "min-height",
+ "max-width",
+ "max-height"
+ ],
+ "float" => [
+ "display"
+ ],
+ "position" => [
+ "display"
+ ],
+ "outline_style" => [
+ "outline_width"
+ ]
+ ];
+
+ /**
+ * Lookup table for dependent properties. Initially computed from the
+ * dependency map.
+ *
+ * @var array
+ */
+ protected static $_dependent_props = [];
+
+ /**
+ * Style of the parent element in document tree.
+ *
+ * @var Style
+ */
+ protected $parent_style;
+
+ /**
+ * @var Frame|null
+ */
+ protected $_frame;
+
+ /**
+ * The origin of the style
+ *
+ * @var int
+ */
+ protected $_origin = Stylesheet::ORIG_AUTHOR;
+
+ /**
+ * The computed bottom spacing
+ *
+ * @var float|string|null
+ */
+ private $_computed_bottom_spacing = null;
+
+ /**
+ * @var bool|null
+ */
+ private $has_border_radius_cache = null;
+
+ /**
+ * @var array|null
+ */
+ private $resolved_border_radius = null;
+
+ /**
+ * @var FontMetrics
+ */
+ private $fontMetrics;
+
+ /**
+ * @param Stylesheet $stylesheet The stylesheet the style is associated with.
+ * @param int $origin
+ */
+ public function __construct(Stylesheet $stylesheet, int $origin = Stylesheet::ORIG_AUTHOR)
+ {
+ $this->fontMetrics = $stylesheet->getFontMetrics();
+
+ $this->_stylesheet = $stylesheet;
+ $this->_media_queries = [];
+ $this->_origin = $origin;
+ $this->parent_style = null;
+
+ if (!isset(self::$_defaults)) {
+
+ // Shorthand
+ $d =& self::$_defaults;
+
+ // All CSS 2.1 properties, and their default values
+ // Some properties are specified with their computed value for
+ // efficiency; this only works if the computed value is not
+ // dependent on another property
+ $d["azimuth"] = "center";
+ $d["background_attachment"] = "scroll";
+ $d["background_color"] = "transparent";
+ $d["background_image"] = "none";
+ $d["background_image_resolution"] = "normal";
+ $d["background_position"] = ["0%", "0%"];
+ $d["background_repeat"] = "repeat";
+ $d["background"] = "";
+ $d["border_collapse"] = "separate";
+ $d["border_color"] = "";
+ $d["border_spacing"] = [0.0, 0.0];
+ $d["border_style"] = "";
+ $d["border_top"] = "";
+ $d["border_right"] = "";
+ $d["border_bottom"] = "";
+ $d["border_left"] = "";
+ $d["border_top_color"] = "currentcolor";
+ $d["border_right_color"] = "currentcolor";
+ $d["border_bottom_color"] = "currentcolor";
+ $d["border_left_color"] = "currentcolor";
+ $d["border_top_style"] = "none";
+ $d["border_right_style"] = "none";
+ $d["border_bottom_style"] = "none";
+ $d["border_left_style"] = "none";
+ $d["border_top_width"] = "medium";
+ $d["border_right_width"] = "medium";
+ $d["border_bottom_width"] = "medium";
+ $d["border_left_width"] = "medium";
+ $d["border_width"] = "";
+ $d["border_bottom_left_radius"] = 0.0;
+ $d["border_bottom_right_radius"] = 0.0;
+ $d["border_top_left_radius"] = 0.0;
+ $d["border_top_right_radius"] = 0.0;
+ $d["border_radius"] = "";
+ $d["border"] = "";
+ $d["bottom"] = "auto";
+ $d["caption_side"] = "top";
+ $d["clear"] = "none";
+ $d["clip"] = "auto";
+ $d["color"] = "#000000";
+ $d["content"] = "normal";
+ $d["counter_increment"] = "none";
+ $d["counter_reset"] = "none";
+ $d["cue_after"] = "none";
+ $d["cue_before"] = "none";
+ $d["cue"] = "";
+ $d["cursor"] = "auto";
+ $d["direction"] = "ltr";
+ $d["display"] = "inline";
+ $d["elevation"] = "level";
+ $d["empty_cells"] = "show";
+ $d["float"] = "none";
+ $d["font_family"] = $stylesheet->get_dompdf()->getOptions()->getDefaultFont();
+ $d["font_size"] = "medium";
+ $d["font_style"] = "normal";
+ $d["font_variant"] = "normal";
+ $d["font_weight"] = "normal";
+ $d["font"] = "";
+ $d["height"] = "auto";
+ $d["image_resolution"] = "normal";
+ $d["inset"] = "";
+ $d["left"] = "auto";
+ $d["letter_spacing"] = "normal";
+ $d["line_height"] = "normal";
+ $d["list_style_image"] = "none";
+ $d["list_style_position"] = "outside";
+ $d["list_style_type"] = "disc";
+ $d["list_style"] = "";
+ $d["margin_right"] = 0.0;
+ $d["margin_left"] = 0.0;
+ $d["margin_top"] = 0.0;
+ $d["margin_bottom"] = 0.0;
+ $d["margin"] = "";
+ $d["max_height"] = "none";
+ $d["max_width"] = "none";
+ $d["min_height"] = "auto";
+ $d["min_width"] = "auto";
+ $d["orphans"] = 2;
+ $d["outline_color"] = "currentcolor"; // "invert" special color is not supported
+ $d["outline_style"] = "none";
+ $d["outline_width"] = "medium";
+ $d["outline_offset"] = 0.0;
+ $d["outline"] = "";
+ $d["overflow"] = "visible";
+ $d["overflow_wrap"] = "normal";
+ $d["padding_top"] = 0.0;
+ $d["padding_right"] = 0.0;
+ $d["padding_bottom"] = 0.0;
+ $d["padding_left"] = 0.0;
+ $d["padding"] = "";
+ $d["page_break_after"] = "auto";
+ $d["page_break_before"] = "auto";
+ $d["page_break_inside"] = "auto";
+ $d["pause_after"] = "0";
+ $d["pause_before"] = "0";
+ $d["pause"] = "";
+ $d["pitch_range"] = "50";
+ $d["pitch"] = "medium";
+ $d["play_during"] = "auto";
+ $d["position"] = "static";
+ $d["quotes"] = "auto";
+ $d["richness"] = "50";
+ $d["right"] = "auto";
+ $d["size"] = "auto"; // @page
+ $d["speak_header"] = "once";
+ $d["speak_numeral"] = "continuous";
+ $d["speak_punctuation"] = "none";
+ $d["speak"] = "normal";
+ $d["speech_rate"] = "medium";
+ $d["stress"] = "50";
+ $d["table_layout"] = "auto";
+ $d["text_align"] = "";
+ $d["text_decoration"] = "none";
+ $d["text_indent"] = 0.0;
+ $d["text_transform"] = "none";
+ $d["top"] = "auto";
+ $d["unicode_bidi"] = "normal";
+ $d["vertical_align"] = "baseline";
+ $d["visibility"] = "visible";
+ $d["voice_family"] = "";
+ $d["volume"] = "medium";
+ $d["white_space"] = "normal";
+ $d["widows"] = 2;
+ $d["width"] = "auto";
+ $d["word_break"] = "normal";
+ $d["word_spacing"] = "normal";
+ $d["z_index"] = "auto";
+
+ // CSS3
+ $d["opacity"] = 1.0;
+ $d["background_size"] = ["auto", "auto"];
+ $d["transform"] = "none";
+ $d["transform_origin"] = "50% 50%";
+
+ // for @font-face
+ $d["src"] = "";
+ $d["unicode_range"] = "";
+
+ // vendor-prefixed properties
+ $d["_dompdf_keep"] = "";
+
+ // Properties that inherit by default
+ self::$_inherited = [
+ "azimuth",
+ "background_image_resolution",
+ "border_collapse",
+ "border_spacing",
+ "caption_side",
+ "color",
+ "cursor",
+ "direction",
+ "elevation",
+ "empty_cells",
+ "font_family",
+ "font_size",
+ "font_style",
+ "font_variant",
+ "font_weight",
+ "font",
+ "image_resolution",
+ "letter_spacing",
+ "line_height",
+ "list_style_image",
+ "list_style_position",
+ "list_style_type",
+ "list_style",
+ "orphans",
+ "overflow_wrap",
+ "pitch_range",
+ "pitch",
+ "quotes",
+ "richness",
+ "speak_header",
+ "speak_numeral",
+ "speak_punctuation",
+ "speak",
+ "speech_rate",
+ "stress",
+ "text_align",
+ "text_indent",
+ "text_transform",
+ "visibility",
+ "voice_family",
+ "volume",
+ "white_space",
+ "widows",
+ "word_break",
+ "word_spacing",
+ ];
+
+ // Compute dependent props from dependency map
+ foreach (self::$_dependency_map as $props) {
+ foreach ($props as $prop) {
+ self::$_dependent_props[$prop] = true;
+ }
+ }
+
+ // Compute valid display-type lookup table
+ self::$valid_display_types = [
+ "none" => true,
+ "-dompdf-br" => true,
+ "-dompdf-image" => true,
+ "-dompdf-list-bullet" => true,
+ "-dompdf-page" => true
+ ];
+ foreach (self::BLOCK_LEVEL_TYPES as $val) {
+ self::$valid_display_types[$val] = true;
+ }
+ foreach (self::INLINE_LEVEL_TYPES as $val) {
+ self::$valid_display_types[$val] = true;
+ }
+ foreach (self::TABLE_INTERNAL_TYPES as $val) {
+ self::$valid_display_types[$val] = true;
+ }
+ }
+ }
+
+ /**
+ * Clear all non-final used values.
+ *
+ * @return void
+ */
+ public function reset(): void
+ {
+ foreach (array_keys($this->non_final_used) as $prop) {
+ unset($this->_props_used[$prop]);
+ }
+
+ $this->non_final_used = [];
+ }
+
+ /**
+ * @param array $media_queries
+ */
+ public function set_media_queries(array $media_queries): void
+ {
+ $this->_media_queries = $media_queries;
+ }
+
+ /**
+ * @return array
+ */
+ public function get_media_queries(): array
+ {
+ return $this->_media_queries;
+ }
+
+ /**
+ * @param Frame $frame
+ */
+ public function set_frame(Frame $frame): void
+ {
+ $this->_frame = $frame;
+ }
+
+ /**
+ * @return Frame|null
+ */
+ public function get_frame(): ?Frame
+ {
+ return $this->_frame;
+ }
+
+ /**
+ * @param int $origin
+ */
+ public function set_origin(int $origin): void
+ {
+ $this->_origin = $origin;
+ }
+
+ /**
+ * @return int
+ */
+ public function get_origin(): int
+ {
+ return $this->_origin;
+ }
+
+ /**
+ * Returns the {@link Stylesheet} the style is associated with.
+ *
+ * @return Stylesheet
+ */
+ public function get_stylesheet(): Stylesheet
+ {
+ return $this->_stylesheet;
+ }
+
+ public function is_absolute(): bool
+ {
+ $position = $this->__get("position");
+ return $position === "absolute" || $position === "fixed";
+ }
+
+ public function is_in_flow(): bool
+ {
+ $float = $this->__get("float");
+ return $float === "none" && !$this->is_absolute();
+ }
+
+ /**
+ * Converts any CSS length value into an absolute length in points.
+ *
+ * length_in_pt() takes a single length (e.g. '1em') or an array of
+ * lengths and returns an absolute length. If an array is passed, then
+ * the return value is the sum of all elements. If any of the lengths
+ * provided are "auto" or "none" then that value is returned.
+ *
+ * If a reference size is not provided, the current font size is used.
+ *
+ * @param float|string|array $length The numeric length (or string measurement) or array of lengths to resolve.
+ * @param float|null $ref_size An absolute reference size to resolve percentage lengths.
+ *
+ * @return float|string
+ */
+ public function length_in_pt($length, ?float $ref_size = null)
+ {
+ $font_size = $this->__get("font_size");
+ $ref_size = $ref_size ?? $font_size;
+
+ if (!\is_array($length)) {
+ $length = [$length];
+ }
+
+ $ret = 0.0;
+
+ foreach ($length as $l) {
+ if ($l === "auto" || $l === "none") {
+ return $l;
+ }
+
+ // Assume numeric values are already in points
+ if (is_numeric($l)) {
+ $ret += (float) $l;
+ continue;
+ }
+
+ $val = $this->single_length_in_pt((string) $l, $ref_size, $font_size);
+ $ret += $val ?? 0;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Convert a length declaration to pt.
+ *
+ * @param string $l The length declaration.
+ * @param float $ref_size Reference size for percentage declarations.
+ * @param float|null $font_size Font size for resolving font-size relative units.
+ *
+ * @return float|null The length in pt, or `null` for invalid declarations.
+ */
+ protected function single_length_in_pt(string $l, float $ref_size = 0, ?float $font_size = null): ?float
+ {
+ static $cache = [];
+
+ $font_size = $font_size ?? $this->__get("font_size");
+
+ $key = "$l/$ref_size/$font_size";
+
+ if (\array_key_exists($key, $cache)) {
+ return $cache[$key];
+ }
+
+ $number = self::CSS_NUMBER;
+ $pattern = "/^($number)(.*)?$/";
+
+ if (!preg_match($pattern, $l, $matches)) {
+ return null;
+ }
+
+ $v = (float) $matches[1];
+ $unit = mb_strtolower($matches[2]);
+
+ if ($unit === "") {
+ // Legacy support for unitless values, not covered by spec. Might
+ // want to restrict this to unitless `0` in the future
+ $value = $v;
+ }
+
+ elseif ($unit === "%") {
+ $value = $v / 100 * $ref_size;
+ }
+
+ elseif ($unit === "px") {
+ $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
+ $value = ($v * 72) / $dpi;
+ }
+
+ elseif ($unit === "pt") {
+ $value = $v;
+ }
+
+ elseif ($unit === "rem") {
+ $tree = $this->_stylesheet->get_dompdf()->getTree();
+ $root_style = $tree !== null ? $tree->get_root()->get_style() : null;
+ $root_font_size = $root_style === null || $root_style === $this
+ ? $font_size
+ : $root_style->__get("font_size");
+ $value = $v * $root_font_size;
+
+ // Skip caching if the root style is not available yet, as to avoid
+ // incorrectly cached values if the root font size is different from
+ // the default
+ if ($root_style === null) {
+ return $value;
+ }
+ }
+
+ elseif ($unit === "em") {
+ $value = $v * $font_size;
+ }
+
+ elseif ($unit === "cm") {
+ $value = $v * 72 / 2.54;
+ }
+
+ elseif ($unit === "mm") {
+ $value = $v * 72 / 25.4;
+ }
+
+ elseif ($unit === "ex") {
+ // FIXME: em:ex ratio?
+ $value = $v * $font_size / 2;
+ }
+
+ elseif ($unit === "in") {
+ $value = $v * 72;
+ }
+
+ elseif ($unit === "pc") {
+ $value = $v * 12;
+ }
+
+ else {
+ // Invalid or unsupported declaration
+ $value = null;
+ }
+
+ return $cache[$key] = $value;
+ }
+
+ /**
+ * Resolve inherited property values using the provided parent style or the
+ * default values, in case no parent style exists.
+ *
+ * https://www.w3.org/TR/css-cascade-3/#inheriting
+ *
+ * @param Style|null $parent
+ */
+ public function inherit(?Style $parent = null): void
+ {
+ $this->parent_style = $parent;
+
+ // Clear the computed font size, as it might depend on the parent
+ // font size
+ unset($this->_props_computed["font_size"]);
+ unset($this->_props_used["font_size"]);
+
+ if ($parent) {
+ foreach (self::$_inherited as $prop) {
+ // For properties that inherit by default: When the cascade did
+ // not result in a value, inherit the parent value. Inheritance
+ // is handled via the specific sub-properties for shorthands
+ if (isset($this->_props[$prop]) || isset(self::$_props_shorthand[$prop])) {
+ continue;
+ }
+
+ if (isset($parent->_props[$prop])) {
+ $parent_val = $parent->computed($prop);
+
+ $this->_props[$prop] = $parent_val;
+ $this->_props_computed[$prop] = $parent_val;
+ $this->_props_used[$prop] = null;
+ }
+ }
+ }
+
+ foreach ($this->_props as $prop => $val) {
+ if ($val === "inherit") {
+ if ($parent && isset($parent->_props[$prop])) {
+ $parent_val = $parent->computed($prop);
+
+ $this->_props[$prop] = $parent_val;
+ $this->_props_computed[$prop] = $parent_val;
+ $this->_props_used[$prop] = null;
+ } else {
+ // Parent prop not set, use default
+ $this->_props[$prop] = self::$_defaults[$prop];
+ unset($this->_props_computed[$prop]);
+ unset($this->_props_used[$prop]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Override properties in this style with those in $style
+ *
+ * @param Style $style
+ */
+ public function merge(Style $style): void
+ {
+ foreach ($style->_props as $prop => $val) {
+ $important = isset($style->_important_props[$prop]);
+
+ // `!important` declarations take precedence over normal ones
+ if (!$important && isset($this->_important_props[$prop])) {
+ continue;
+ }
+
+ if ($important) {
+ $this->_important_props[$prop] = true;
+ }
+
+ $this->_props[$prop] = $val;
+
+ // Copy an existing computed value only for non-dependent
+ // properties; otherwise it may be invalid for the current style
+ if (!isset(self::$_dependent_props[$prop])
+ && \array_key_exists($prop, $style->_props_computed)
+ ) {
+ $this->_props_computed[$prop] = $style->_props_computed[$prop];
+ $this->_props_used[$prop] = null;
+ } else {
+ unset($this->_props_computed[$prop]);
+ unset($this->_props_used[$prop]);
+ }
+ }
+ }
+
+ /**
+ * Clear information about important declarations after the style has been
+ * finalized during stylesheet loading.
+ */
+ public function clear_important(): void
+ {
+ $this->_important_props = [];
+ }
+
+ /**
+ * Clear border-radius and bottom-spacing cache as necessary when a given
+ * property is set.
+ *
+ * @param string $prop The property that is set.
+ */
+ protected function clear_cache(string $prop): void
+ {
+ // Clear border-radius cache on setting any border-radius
+ // property
+ if ($prop === "border_top_left_radius"
+ || $prop === "border_top_right_radius"
+ || $prop === "border_bottom_left_radius"
+ || $prop === "border_bottom_right_radius"
+ ) {
+ $this->has_border_radius_cache = null;
+ $this->resolved_border_radius = null;
+ }
+
+ // Clear bottom-spacing cache if necessary. Border style can
+ // disable/enable border calculations
+ if ($prop === "margin_bottom"
+ || $prop === "padding_bottom"
+ || $prop === "border_bottom_width"
+ || $prop === "border_bottom_style"
+ ) {
+ $this->_computed_bottom_spacing = null;
+ }
+ }
+
+ /**
+ * Set a style property from a value declaration.
+ *
+ * Setting `$clear_dependencies` to `false` is useful for saving a bit of
+ * unnecessary work while loading stylesheets.
+ *
+ * @param string $prop The property to set.
+ * @param mixed $val The value declaration or computed value.
+ * @param bool $important Whether the declaration is important.
+ * @param bool $clear_dependencies Whether to clear computed values of dependent properties.
+ */
+ public function set_prop(string $prop, $val, bool $important = false, bool $clear_dependencies = true): void
+ {
+ $prop = str_replace("-", "_", $prop);
+
+ // Legacy property aliases
+ if (isset(self::$_props_alias[$prop])) {
+ $prop = self::$_props_alias[$prop];
+ }
+
+ if (!isset(self::$_defaults[$prop])) {
+ global $_dompdf_warnings;
+ $_dompdf_warnings[] = "'$prop' is not a recognized CSS property.";
+ return;
+ }
+
+ if ($prop !== "content" && \is_string($val) && mb_strpos($val, "url") === false && mb_strlen($val) > 1) {
+ $val = mb_strtolower(trim(str_replace(["\n", "\t"], [" "], $val)));
+ }
+
+ if (isset(self::$_props_shorthand[$prop])) {
+ // Shorthand properties directly set their respective sub-properties
+ // https://www.w3.org/TR/css-cascade-3/#shorthand
+ if ($val === "initial" || $val === "inherit" || $val === "unset") {
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
+ $this->set_prop($sub_prop, $val, $important, $clear_dependencies);
+ }
+ } else {
+ $method = "_set_$prop";
+
+ if (!isset(self::$_methods_cache[$method])) {
+ self::$_methods_cache[$method] = method_exists($this, $method);
+ }
+
+ if (self::$_methods_cache[$method]) {
+ $values = $this->$method($val);
+
+ if ($values === []) {
+ return;
+ }
+
+ // Each missing sub-property is assigned its initial value
+ // https://www.w3.org/TR/css-cascade-3/#shorthand
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
+ $sub_val = $values[$sub_prop] ?? self::$_defaults[$sub_prop];
+ $this->set_prop($sub_prop, $sub_val, $important, $clear_dependencies);
+ }
+ }
+ }
+ } else {
+ // Legacy support for `word-break: break-word`
+ // https://www.w3.org/TR/css-text-3/#valdef-word-break-break-word
+ if ($prop === "word_break" && $val === "break-word") {
+ $val = "normal";
+ $this->set_prop("overflow_wrap", "anywhere", $important, $clear_dependencies);
+ }
+
+ // `!important` declarations take precedence over normal ones
+ if (!$important && isset($this->_important_props[$prop])) {
+ return;
+ }
+
+ if ($important) {
+ $this->_important_props[$prop] = true;
+ }
+
+ // https://www.w3.org/TR/css-cascade-3/#inherit-initial
+ if ($val === "unset") {
+ $val = \in_array($prop, self::$_inherited, true)
+ ? "inherit"
+ : "initial";
+ }
+
+ // https://www.w3.org/TR/css-cascade-3/#valdef-all-initial
+ if ($val === "initial") {
+ $val = self::$_defaults[$prop];
+ }
+
+ $computed = $this->compute_prop($prop, $val);
+
+ // Skip invalid declarations
+ if ($computed === null) {
+ return;
+ }
+
+ $this->_props[$prop] = $val;
+ $this->_props_computed[$prop] = $computed;
+ $this->_props_used[$prop] = null;
+
+ if ($clear_dependencies) {
+ // Clear the computed values of any dependent properties, so
+ // they can be re-computed
+ if (isset(self::$_dependency_map[$prop])) {
+ foreach (self::$_dependency_map[$prop] as $dependent) {
+ unset($this->_props_computed[$dependent]);
+ unset($this->_props_used[$dependent]);
+ }
+ }
+
+ $this->clear_cache($prop);
+ }
+ }
+ }
+
+ /**
+ * Get the specified value of a style property.
+ *
+ * @param string $prop
+ *
+ * @return mixed
+ * @throws Exception
+ */
+ public function get_specified(string $prop)
+ {
+ // Legacy property aliases
+ if (isset(self::$_props_alias[$prop])) {
+ $prop = self::$_props_alias[$prop];
+ }
+
+ if (!isset(self::$_defaults[$prop])) {
+ throw new Exception("'$prop' is not a recognized CSS property.");
+ }
+
+ return $this->_props[$prop] ?? self::$_defaults[$prop];
+ }
+
+ /**
+ * Set a style property to its final value.
+ *
+ * This sets the specified and used value of the style property to the given
+ * value, meaning the value is not parsed and thus should have a type
+ * compatible with the property.
+ *
+ * If a shorthand property is specified, all of its sub-properties are set
+ * to the given value.
+ *
+ * @param string $prop The property to set.
+ * @param mixed $val The final value of the property.
+ *
+ * @throws Exception
+ */
+ public function __set(string $prop, $val)
+ {
+ // Legacy property aliases
+ if (isset(self::$_props_alias[$prop])) {
+ $prop = self::$_props_alias[$prop];
+ }
+
+ if (!isset(self::$_defaults[$prop])) {
+ throw new Exception("'$prop' is not a recognized CSS property.");
+ }
+
+ if (isset(self::$_props_shorthand[$prop])) {
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
+ $this->__set($sub_prop, $val);
+ }
+ } else {
+ $this->_props[$prop] = $val;
+ $this->_props_computed[$prop] = $val;
+ $this->_props_used[$prop] = $val;
+
+ $this->clear_cache($prop);
+ }
+ }
+
+ /**
+ * Set the used value of a style property.
+ *
+ * Used values are cleared on style reset.
+ *
+ * If a shorthand property is specified, all of its sub-properties are set
+ * to the given value.
+ *
+ * @param string $prop The property to set.
+ * @param mixed $val The used value of the property.
+ *
+ * @throws Exception
+ */
+ public function set_used(string $prop, $val): void
+ {
+ // Legacy property aliases
+ if (isset(self::$_props_alias[$prop])) {
+ $prop = self::$_props_alias[$prop];
+ }
+
+ if (!isset(self::$_defaults[$prop])) {
+ throw new Exception("'$prop' is not a recognized CSS property.");
+ }
+
+ if (isset(self::$_props_shorthand[$prop])) {
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
+ $this->set_used($sub_prop, $val);
+ }
+ } else {
+ $this->_props_used[$prop] = $val;
+ $this->non_final_used[$prop] = true;
+ }
+ }
+
+ /**
+ * Get the used or computed value of a style property, depending on whether
+ * the used value has been determined yet.
+ *
+ * @param string $prop
+ *
+ * @return mixed
+ * @throws Exception
+ */
+ public function __get(string $prop)
+ {
+ // Legacy property aliases
+ if (isset(self::$_props_alias[$prop])) {
+ $prop = self::$_props_alias[$prop];
+ }
+
+ if (!isset(self::$_defaults[$prop])) {
+ throw new Exception("'$prop' is not a recognized CSS property.");
+ }
+
+ if (isset($this->_props_used[$prop])) {
+ return $this->_props_used[$prop];
+ }
+
+ $method = "_get_$prop";
+
+ if (!isset(self::$_methods_cache[$method])) {
+ self::$_methods_cache[$method] = method_exists($this, $method);
+ }
+
+ if (isset(self::$_props_shorthand[$prop])) {
+ // Don't cache shorthand values, always use getter. If no dedicated
+ // getter exists, use a simple fallback getter concatenating all
+ // sub-property values
+ if (self::$_methods_cache[$method]) {
+ return $this->$method();
+ } else {
+ return implode(" ", array_map(function ($sub_prop) {
+ $val = $this->__get($sub_prop);
+ return \is_array($val) ? implode(" ", $val) : $val;
+ }, self::$_props_shorthand[$prop]));
+ }
+ } else {
+ $computed = $this->computed($prop);
+ $used = self::$_methods_cache[$method]
+ ? $this->$method($computed)
+ : $computed;
+
+ $this->_props_used[$prop] = $used;
+ return $used;
+ }
+ }
+
+ /**
+ * @param string $prop The property to compute.
+ * @param mixed $val The value to compute. Non-string values are treated as already computed.
+ *
+ * @return mixed The computed value.
+ */
+ protected function compute_prop(string $prop, $val)
+ {
+ // During style merge, the parent style is not available yet, so
+ // temporarily use the initial value for `inherit` properties. The
+ // keyword is properly resolved during inheritance
+ if ($val === "inherit") {
+ $val = self::$_defaults[$prop];
+ }
+
+ // Check for values which are already computed
+ if (!\is_string($val)) {
+ return $val;
+ }
+
+ $method = "_compute_$prop";
+
+ if (!isset(self::$_methods_cache[$method])) {
+ self::$_methods_cache[$method] = method_exists($this, $method);
+ }
+
+ if (self::$_methods_cache[$method]) {
+ return $this->$method($val);
+ } elseif ($val !== "") {
+ return $val;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the computed value for the given property.
+ *
+ * @param string $prop The property to get the computed value of.
+ *
+ * @return mixed The computed value.
+ */
+ protected function computed(string $prop)
+ {
+ if (!\array_key_exists($prop, $this->_props_computed)) {
+ $val = $this->_props[$prop] ?? self::$_defaults[$prop];
+ $computed = $this->compute_prop($prop, $val);
+
+ $this->_props_computed[$prop] = $computed;
+ }
+
+ return $this->_props_computed[$prop];
+ }
+
+ /**
+ * @param float $cbw The width of the containing block.
+ * @return float|string|null
+ */
+ public function computed_bottom_spacing(float $cbw)
+ {
+ // Caching the bottom spacing independently of the given width is a bit
+ // iffy, but should be okay, as the containing block should only
+ // potentially change after a page break, and the style is reset in that
+ // case
+ if ($this->_computed_bottom_spacing !== null) {
+ return $this->_computed_bottom_spacing;
+ }
+ return $this->_computed_bottom_spacing = $this->length_in_pt(
+ [
+ $this->margin_bottom,
+ $this->padding_bottom,
+ $this->border_bottom_width
+ ],
+ $cbw
+ );
+ }
+
+ /**
+ * Returns an `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
+ * based on the provided CSS color value.
+ *
+ * @param string|null $color
+ * @return array|string|null
+ */
+ public function munge_color($color)
+ {
+ return Color::parse($color);
+ }
+
+ /**
+ * @return string
+ */
+ public function get_font_family_raw(): string
+ {
+ return trim($this->_props["font_family"], " \t\n\r\x0B\"'");
+ }
+
+ /**
+ * Getter for the `font-family` CSS property.
+ *
+ * Uses the {@link FontMetrics} class to resolve the font family into an
+ * actual font file.
+ *
+ * @param string $computed
+ * @return string
+ * @throws Exception
+ *
+ * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
+ */
+ protected function _get_font_family($computed): string
+ {
+ //TODO: we should be using the calculated prop rather than perform the entire family parsing operation again
+
+ $fontMetrics = $this->getFontMetrics();
+ $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
+
+ // Select the appropriate font. First determine the subtype, then check
+ // the specified font-families for a candidate.
+
+ // Resolve font-weight
+ $weight = $this->__get("font_weight");
+ if ($weight === 'bold') {
+ $weight = 700;
+ } elseif (preg_match('/^[0-9]+$/', $weight, $match)) {
+ $weight = (int)$match[0];
+ } else {
+ $weight = 400;
+ }
+
+ // Resolve font-style
+ $font_style = $this->__get("font_style");
+ $subtype = $fontMetrics->getType($weight . ' ' . $font_style);
+
+ $families = preg_split("/\s*,\s*/", $computed);
+
+ $font = null;
+ foreach ($families as $family) {
+ //remove leading and trailing string delimiters, e.g. on font names with spaces;
+ //remove leading and trailing whitespace
+ $family = trim($family, " \t\n\r\x0B\"'");
+ if ($DEBUGCSS) {
+ print '(' . $family . ')';
+ }
+ $font = $fontMetrics->getFont($family, $subtype);
+
+ if ($font) {
+ if ($DEBUGCSS) {
+ print "<pre>[get_font_family:";
+ print '(' . $computed . '.' . $font_style . '.' . $weight . '.' . $subtype . ')';
+ print '(' . $font . ")get_font_family]\n</pre>";
+ }
+ return $font;
+ }
+ }
+
+ $family = null;
+ if ($DEBUGCSS) {
+ print '(default)';
+ }
+ $font = $fontMetrics->getFont($family, $subtype);
+
+ if ($font) {
+ if ($DEBUGCSS) {
+ print '(' . $font . ")get_font_family]\n</pre>";
+ }
+ return $font;
+ }
+
+ throw new Exception("Unable to find a suitable font replacement for: '" . $computed . "'");
+ }
+
+ /**
+ * @param float|string $computed
+ * @return float
+ *
+ * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
+ */
+ protected function _get_word_spacing($computed)
+ {
+ if (\is_float($computed)) {
+ return $computed;
+ }
+
+ // Resolve percentage values
+ $font_size = $this->__get("font_size");
+ return $this->single_length_in_pt($computed, $font_size);
+ }
+
+ /**
+ * @param float|string $computed
+ * @return float
+ *
+ * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
+ */
+ protected function _get_letter_spacing($computed)
+ {
+ if (\is_float($computed)) {
+ return $computed;
+ }
+
+ // Resolve percentage values
+ $font_size = $this->__get("font_size");
+ return $this->single_length_in_pt($computed, $font_size);
+ }
+
+ /**
+ * @param float|string $computed
+ * @return float
+ *
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
+ */
+ protected function _get_line_height($computed)
+ {
+ // Lengths have been computed to float, number values to string
+ if (\is_float($computed)) {
+ return $computed;
+ }
+
+ $font_size = $this->__get("font_size");
+ $factor = $computed === "normal"
+ ? self::$default_line_height
+ : (float) $computed;
+
+ return $factor * $font_size;
+ }
+
+ /**
+ * @param string $computed
+ * @param bool $current_is_parent
+ *
+ * @return array|string
+ */
+ protected function get_color_value($computed, bool $current_is_parent = false)
+ {
+ if ($computed === "currentcolor") {
+ // https://www.w3.org/TR/css-color-4/#resolving-other-colors
+ if ($current_is_parent) {
+ // Use the `color` value from the parent for the `color`
+ // property itself
+ return isset($this->parent_style)
+ ? $this->parent_style->__get("color")
+ : $this->munge_color(self::$_defaults["color"]);
+ }
+
+ return $this->__get("color");
+ }
+
+ return $this->munge_color($computed) ?? "transparent";
+ }
+
+ /**
+ * Returns the color as an array
+ *
+ * The array has the following format:
+ * `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
+ *
+ * @param string $computed
+ * @return array|string
+ *
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
+ */
+ protected function _get_color($computed)
+ {
+ return $this->get_color_value($computed, true);
+ }
+
+ /**
+ * Returns the background color as an array
+ *
+ * See {@link Style::_get_color()} for format of the color array.
+ *
+ * @param string $computed
+ * @return array|string
+ *
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
+ */
+ protected function _get_background_color($computed)
+ {
+ return $this->get_color_value($computed);
+ }
+
+ /**
+ * Returns the background image URI, or "none"
+ *
+ * @param string $computed
+ * @return string
+ *
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
+ */
+ protected function _get_background_image($computed): string
+ {
+ return $this->_stylesheet->resolve_url($computed);
+ }
+
+ /**
+ * Returns the border color as an array
+ *
+ * See {@link Style::_get_color()} for format of the color array.
+ *
+ * @param string $computed
+ * @return array|string
+ *
+ * @link https://www.w3.org/TR/CSS21/box.html#border-color-properties
+ */
+ protected function _get_border_top_color($computed)
+ {
+ return $this->get_color_value($computed);
+ }
+
+ /**
+ * @param string $computed
+ * @return array|string
+ */
+ protected function _get_border_right_color($computed)
+ {
+ return $this->get_color_value($computed);
+ }
+
+ /**
+ * @param string $computed
+ * @return array|string
+ */
+ protected function _get_border_bottom_color($computed)
+ {
+ return $this->get_color_value($computed);
+ }
+
+ /**
+ * @param string $computed
+ * @return array|string
+ */
+ protected function _get_border_left_color($computed)
+ {
+ return $this->get_color_value($computed);
+ }
+
+ /**
+ * Return an array of all border properties.
+ *
+ * The returned array has the following structure:
+ *
+ * ```
+ * array("top" => array("width" => [border-width],
+ * "style" => [border-style],
+ * "color" => [border-color (array)]),
+ * "bottom" ... )
+ * ```
+ *
+ * @return array
+ */
+ public function get_border_properties(): array
+ {
+ return [
+ "top" => [
+ "width" => $this->__get("border_top_width"),
+ "style" => $this->__get("border_top_style"),
+ "color" => $this->__get("border_top_color"),
+ ],
+ "bottom" => [
+ "width" => $this->__get("border_bottom_width"),
+ "style" => $this->__get("border_bottom_style"),
+ "color" => $this->__get("border_bottom_color"),
+ ],
+ "right" => [
+ "width" => $this->__get("border_right_width"),
+ "style" => $this->__get("border_right_style"),
+ "color" => $this->__get("border_right_color"),
+ ],
+ "left" => [
+ "width" => $this->__get("border_left_width"),
+ "style" => $this->__get("border_left_style"),
+ "color" => $this->__get("border_left_color"),
+ ],
+ ];
+ }
+
+ /**
+ * Return a single border-side property
+ *
+ * @param string $side
+ * @return string
+ */
+ protected function get_border_side(string $side): string
+ {
+ $color = $this->__get("border_{$side}_color");
+
+ return $this->__get("border_{$side}_width") . " " .
+ $this->__get("border_{$side}_style") . " " .
+ (\is_array($color) ? $color["hex"] : $color);
+ }
+
+ /**
+ * Return full border properties as a string
+ *
+ * Border properties are returned just as specified in CSS:
+ * `[width] [style] [color]`
+ * e.g. "1px solid blue"
+ *
+ * @return string
+ *
+ * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
+ */
+ protected function _get_border_top(): string
+ {
+ return $this->get_border_side("top");
+ }
+
+ /**
+ * @return string
+ */
+ protected function _get_border_right(): string
+ {
+ return $this->get_border_side("right");
+ }
+
+ /**
+ * @return string
+ */
+ protected function _get_border_bottom(): string
+ {
+ return $this->get_border_side("bottom");
+ }
+
+ /**
+ * @return string
+ */
+ protected function _get_border_left(): string
+ {
+ return $this->get_border_side("left");
+ }
+
+ public function has_border_radius(): bool
+ {
+ if (isset($this->has_border_radius_cache)) {
+ return $this->has_border_radius_cache;
+ }
+
+ // Use a fixed ref size here. We don't know the border-box width here
+ // and font size might be 0. Since we are only interested in whether
+ // there is any border radius at all, this should do
+ $tl = (float) $this->length_in_pt($this->border_top_left_radius, 12);
+ $tr = (float) $this->length_in_pt($this->border_top_right_radius, 12);
+ $br = (float) $this->length_in_pt($this->border_bottom_right_radius, 12);
+ $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, 12);
+
+ $this->has_border_radius_cache = $tl + $tr + $br + $bl > 0;
+ return $this->has_border_radius_cache;
+ }
+
+ /**
+ * Get the final border-radius values to use.
+ *
+ * Percentage values are resolved relative to the width of the border box.
+ * The border radius is additionally scaled for the given render box, and
+ * constrained by its width and height.
+ *
+ * @param float[] $border_box The border box of the frame.
+ * @param float[]|null $render_box The box to resolve the border radius for.
+ *
+ * @return float[] A 4-tuple of top-left, top-right, bottom-right, and bottom-left radius.
+ */
+ public function resolve_border_radius(
+ array $border_box,
+ ?array $render_box = null
+ ): array {
+ $render_box = $render_box ?? $border_box;
+ $use_cache = $render_box === $border_box;
+
+ if ($use_cache && isset($this->resolved_border_radius)) {
+ return $this->resolved_border_radius;
+ }
+
+ [$x, $y, $w, $h] = $border_box;
+
+ // Resolve percentages relative to width, as long as we have no support
+ // for per-axis radii
+ $tl = (float) $this->length_in_pt($this->border_top_left_radius, $w);
+ $tr = (float) $this->length_in_pt($this->border_top_right_radius, $w);
+ $br = (float) $this->length_in_pt($this->border_bottom_right_radius, $w);
+ $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, $w);
+
+ if ($tl + $tr + $br + $bl > 0) {
+ [$rx, $ry, $rw, $rh] = $render_box;
+
+ $t_offset = $y - $ry;
+ $r_offset = $rx + $rw - $x - $w;
+ $b_offset = $ry + $rh - $y - $h;
+ $l_offset = $x - $rx;
+
+ if ($tl > 0) {
+ $tl = max($tl + ($t_offset + $l_offset) / 2, 0);
+ }
+ if ($tr > 0) {
+ $tr = max($tr + ($t_offset + $r_offset) / 2, 0);
+ }
+ if ($br > 0) {
+ $br = max($br + ($b_offset + $r_offset) / 2, 0);
+ }
+ if ($bl > 0) {
+ $bl = max($bl + ($b_offset + $l_offset) / 2, 0);
+ }
+
+ if ($tl + $bl > $rh) {
+ $f = $rh / ($tl + $bl);
+ $tl = $f * $tl;
+ $bl = $f * $bl;
+ }
+ if ($tr + $br > $rh) {
+ $f = $rh / ($tr + $br);
+ $tr = $f * $tr;
+ $br = $f * $br;
+ }
+ if ($tl + $tr > $rw) {
+ $f = $rw / ($tl + $tr);
+ $tl = $f * $tl;
+ $tr = $f * $tr;
+ }
+ if ($bl + $br > $rw) {
+ $f = $rw / ($bl + $br);
+ $bl = $f * $bl;
+ $br = $f * $br;
+ }
+ }
+
+ $values = [$tl, $tr, $br, $bl];
+
+ if ($use_cache) {
+ $this->resolved_border_radius = $values;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns the outline color as an array
+ *
+ * See {@link Style::_get_color()} for format of the color array.
+ *
+ * @param string $computed
+ * @return array|string
+ *
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-color
+ */
+ protected function _get_outline_color($computed)
+ {
+ return $this->get_color_value($computed);
+ }
+
+ /**
+ * @param string $computed
+ * @return string
+ *
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-style
+ */
+ protected function _get_outline_style($computed): string
+ {
+ return $computed === "auto" ? "solid" : $computed;
+ }
+
+ /**
+ * Return full outline properties as a string
+ *
+ * Outline properties are returned just as specified in CSS:
+ * `[width] [style] [color]`
+ * e.g. "1px solid blue"
+ *
+ * @return string
+ *
+ * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
+ */
+ protected function _get_outline(): string
+ {
+ $color = $this->__get("outline_color");
+
+ return $this->__get("outline_width") . " " .
+ $this->__get("outline_style") . " " .
+ (\is_array($color) ? $color["hex"] : $color);
+ }
+
+ /**
+ * Returns the list style image URI, or "none"
+ *
+ * @param string $computed
+ * @return string
+ *
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
+ */
+ protected function _get_list_style_image($computed): string
+ {
+ return $this->_stylesheet->resolve_url($computed);
+ }
+
+ /**
+ * @param string $value
+ * @param int $default
+ *
+ * @return array|string
+ */
+ protected function parse_counter_prop(string $value, int $default)
+ {
+ $ident = self::CSS_IDENTIFIER;
+ $integer = self::CSS_INTEGER;
+ $pattern = "/($ident)(?:\s+($integer))?/";
+
+ if (!preg_match_all($pattern, $value, $matches, PREG_SET_ORDER)) {
+ return "none";
+ }
+
+ $counters = [];
+
+ foreach ($matches as $match) {
+ $counter = $match[1];
+ $value = isset($match[2]) ? (int) $match[2] : $default;
+ $counters[$counter] = $value;
+ }
+
+ return $counters;
+ }
+
+ /**
+ * @param string $computed
+ * @return array|string
+ *
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment
+ */
+ protected function _get_counter_increment($computed)
+ {
+ if ($computed === "none") {
+ return $computed;
+ }
+
+ return $this->parse_counter_prop($computed, 1);
+ }
+
+ /**
+ * @param string $computed
+ * @return array|string
+ *
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-reset
+ */
+ protected function _get_counter_reset($computed)
+ {
+ if ($computed === "none") {
+ return $computed;
+ }
+
+ return $this->parse_counter_prop($computed, 0);
+ }
+
+ /**
+ * @param string $computed
+ * @return string[]|string
+ *
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-content
+ */
+ protected function _get_content($computed)
+ {
+ if ($computed === "normal" || $computed === "none") {
+ return $computed;
+ }
+
+ return $this->parse_property_value($computed);
+ }
+
+ /*==============================*/
+
+ /**
+ * Parse a property value into its components.
+ *
+ * @param string $value
+ *
+ * @return string[]
+ */
+ protected function parse_property_value(string $value): array
+ {
+ $ident = self::CSS_IDENTIFIER;
+ $number = self::CSS_NUMBER;
+
+ $pattern = "/\n" .
+ "\s* \" ( (?:[^\"]|\\\\[\"])* ) (?<!\\\\)\" |\n" . // String ""
+ "\s* ' ( (?:[^']|\\\\['])* ) (?<!\\\\)' |\n" . // String ''
+ "\s* ($ident \\([^)]*\\) ) |\n" . // Functional
+ "\s* ($ident) |\n" . // Keyword
+ "\s* (\#[0-9a-fA-F]*) |\n" . // Hex value
+ "\s* ($number [a-zA-Z%]*) |\n" . // Number (+ unit/percentage)
+ "\s* ([\/,;]) \n" . // Delimiter
+ "/Sx";
+
+ if (!preg_match_all($pattern, $value, $matches)) {
+ return [];
+ }
+
+ return array_map("trim", $matches[0]);
+ }
+
+ protected function is_color_value(string $val): bool
+ {
+ return $val === "currentcolor"
+ || $val === "transparent"
+ || isset(Color::$cssColorNames[$val])
+ || preg_match("/^#|rgb\(|rgba\(|cmyk\(/", $val);
+ }
+
+ /**
+ * @param string $val
+ * @return string|null
+ */
+ protected function compute_color_value(string $val): ?string
+ {
+ // https://www.w3.org/TR/css-color-4/#resolving-other-colors
+ $munged_color = $val !== "currentcolor"
+ ? $this->munge_color($val)
+ : $val;
+
+ if ($munged_color === null) {
+ return null;
+ }
+
+ return \is_array($munged_color) ? $munged_color["hex"] : $munged_color;
+ }
+
+ /**
+ * @param string $val
+ * @return int|null
+ */
+ protected function compute_integer(string $val): ?int
+ {
+ $integer = self::CSS_INTEGER;
+ return preg_match("/^$integer$/", $val)
+ ? (int) $val
+ : null;
+ }
+
+ /**
+ * @param string $val
+ * @return float|null
+ */
+ protected function compute_length(string $val): ?float
+ {
+ return mb_strpos($val, "%") === false
+ ? $this->single_length_in_pt($val)
+ : null;
+ }
+
+ /**
+ * @param string $val
+ * @return float|null
+ */
+ protected function compute_length_positive(string $val): ?float
+ {
+ $computed = $this->compute_length($val);
+ return $computed !== null && $computed >= 0 ? $computed : null;
+ }
+
+ /**
+ * @param string $val
+ * @return float|string|null
+ */
+ protected function compute_length_percentage(string $val)
+ {
+ // Compute with a fixed ref size to decide whether percentage values
+ // are valid
+ $computed = $this->single_length_in_pt($val, 12);
+
+ if ($computed === null) {
+ return null;
+ }
+
+ // Retain valid percentage declarations
+ return mb_strpos($val, "%") === false ? $computed : $val;
+ }
+
+ /**
+ * @param string $val
+ * @return float|string|null
+ */
+ protected function compute_length_percentage_positive(string $val)
+ {
+ // Compute with a fixed ref size to decide whether percentage values
+ // are valid
+ $computed = $this->single_length_in_pt($val, 12);
+
+ if ($computed === null || $computed < 0) {
+ return null;
+ }
+
+ // Retain valid percentage declarations
+ return mb_strpos($val, "%") === false ? $computed : $val;
+ }
+
+ /**
+ * @param string $val
+ * @param string $style_prop The corresponding border-/outline-style property.
+ *
+ * @return float|null
+ *
+ * @link https://www.w3.org/TR/css-backgrounds-3/#typedef-line-width
+ */
+ protected function compute_line_width(string $val, string $style_prop): ?float
+ {
+ // Border-width keywords
+ if ($val === "thin") {
+ $computed = 0.5;
+ } elseif ($val === "medium") {
+ $computed = 1.5;
+ } elseif ($val === "thick") {
+ $computed = 2.5;
+ } else {
+ $computed = $this->compute_length_positive($val);
+ }
+
+ if ($computed === null) {
+ return null;
+ }
+
+ // Computed width is 0 if the line style is `none` or `hidden`
+ // https://www.w3.org/TR/css-backgrounds-3/#border-width
+ // https://www.w3.org/TR/css-ui-4/#outline-width
+ $lineStyle = $this->__get($style_prop);
+ $hasLineStyle = $lineStyle !== "none" && $lineStyle !== "hidden";
+
+ return $hasLineStyle ? $computed : 0.0;
+ }
+
+ /**
+ * @param string $val
+ * @return string|null
+ */
+ protected function compute_border_style(string $val): ?string
+ {
+ return \in_array($val, self::BORDER_STYLES, true) ? $val : null;
+ }
+
+ /**
+ * Parse a property value with 1 to 4 components into 4 values, as required
+ * by shorthand properties such as `margin`, `padding`, and `border-radius`.
+ *
+ * @param string $prop The shorthand property with exactly 4 sub-properties to handle.
+ * @param string $value The property value to parse.
+ *
+ * @return string[]
+ */
+ protected function set_quad_shorthand(string $prop, string $value): array
+ {
+ $v = $this->parse_property_value($value);
+
+ switch (\count($v)) {
+ case 1:
+ $values = [$v[0], $v[0], $v[0], $v[0]];
+ break;
+ case 2:
+ $values = [$v[0], $v[1], $v[0], $v[1]];
+ break;
+ case 3:
+ $values = [$v[0], $v[1], $v[2], $v[1]];
+ break;
+ case 4:
+ $values = [$v[0], $v[1], $v[2], $v[3]];
+ break;
+ default:
+ return [];
+ }
+
+ return array_combine(self::$_props_shorthand[$prop], $values);
+ }
+
+ /*======================*/
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visuren.html#display-prop
+ */
+ protected function _compute_display(string $val)
+ {
+ // Make sure that common valid, but unsupported display types have an
+ // appropriate fallback display type
+ switch ($val) {
+ case "flow-root":
+ case "flex":
+ case "grid":
+ case "table-caption":
+ $val = "block";
+ break;
+ case "inline-flex":
+ case "inline-grid":
+ $val = "inline-block";
+ break;
+ }
+
+ if (!isset(self::$valid_display_types[$val])) {
+ return null;
+ }
+
+ // https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
+ if ($this->is_in_flow()) {
+ return $val;
+ } else {
+ switch ($val) {
+ case "inline":
+ case "inline-block":
+ // case "table-row-group":
+ // case "table-header-group":
+ // case "table-footer-group":
+ // case "table-row":
+ // case "table-cell":
+ // case "table-column-group":
+ // case "table-column":
+ // case "table-caption":
+ return "block";
+ case "inline-table":
+ return "table";
+ default:
+ return $val;
+ }
+ }
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
+ */
+ protected function _compute_color(string $color)
+ {
+ return $this->compute_color_value($color);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
+ */
+ protected function _compute_background_color(string $color)
+ {
+ return $this->compute_color_value($color);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
+ */
+ protected function _compute_background_image(string $val)
+ {
+ $parsed_val = $this->_stylesheet->resolve_url($val);
+
+ if ($parsed_val === "none") {
+ return "none";
+ } else {
+ return "url($parsed_val)";
+ }
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
+ */
+ protected function _compute_background_repeat(string $val)
+ {
+ $keywords = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
+ return \in_array($val, $keywords, true) ? $val : null;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
+ */
+ protected function _compute_background_attachment(string $val)
+ {
+ $keywords = ["scroll", "fixed"];
+ return \in_array($val, $keywords, true) ? $val : null;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-position
+ */
+ protected function _compute_background_position(string $val)
+ {
+ $parts = preg_split("/\s+/", $val);
+
+ if (\count($parts) > 2) {
+ return null;
+ }
+
+ switch ($parts[0]) {
+ case "left":
+ $x = "0%";
+ break;
+
+ case "right":
+ $x = "100%";
+ break;
+
+ case "top":
+ $y = "0%";
+ break;
+
+ case "bottom":
+ $y = "100%";
+ break;
+
+ case "center":
+ $x = "50%";
+ $y = "50%";
+ break;
+
+ default:
+ $x = $parts[0];
+ break;
+ }
+
+ if (isset($parts[1])) {
+ switch ($parts[1]) {
+ case "left":
+ $x = "0%";
+ break;
+
+ case "right":
+ $x = "100%";
+ break;
+
+ case "top":
+ $y = "0%";
+ break;
+
+ case "bottom":
+ $y = "100%";
+ break;
+
+ case "center":
+ if ($parts[0] === "left" || $parts[0] === "right" || $parts[0] === "center") {
+ $y = "50%";
+ } else {
+ $x = "50%";
+ }
+ break;
+
+ default:
+ $y = $parts[1];
+ break;
+ }
+ } else {
+ $y = "50%";
+ }
+
+ if (!isset($x)) {
+ $x = "0%";
+ }
+
+ if (!isset($y)) {
+ $y = "0%";
+ }
+
+ return [$x, $y];
+ }
+
+ /**
+ * Compute `background-size`.
+ *
+ * Computes to one of the following values:
+ * * `cover`
+ * * `contain`
+ * * `[width, height]`, each being a length, percentage, or `auto`
+ *
+ * @link https://www.w3.org/TR/css-backgrounds-3/#background-size
+ */
+ protected function _compute_background_size(string $val)
+ {
+ if ($val === "cover" || $val === "contain") {
+ return $val;
+ }
+
+ $parts = preg_split("/\s+/", $val);
+
+ if (\count($parts) > 2) {
+ return null;
+ }
+
+ $width = $parts[0];
+ if ($width !== "auto") {
+ $width = $this->compute_length_percentage_positive($width);
+ }
+
+ $height = $parts[1] ?? "auto";
+ if ($height !== "auto") {
+ $height = $this->compute_length_percentage_positive($height);
+ }
+
+ if ($width === null || $height === null) {
+ return null;
+ }
+
+ return [$width, $height];
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-background
+ */
+ protected function _set_background(string $value): array
+ {
+ $components = $this->parse_property_value($value);
+ $props = [];
+ $pos_size = [];
+
+ foreach ($components as $val) {
+ if ($val === "none" || mb_substr($val, 0, 4) === "url(") {
+ $props["background_image"] = $val;
+ } elseif ($val === "scroll" || $val === "fixed") {
+ $props["background_attachment"] = $val;
+ } elseif ($val === "repeat" || $val === "repeat-x" || $val === "repeat-y" || $val === "no-repeat") {
+ $props["background_repeat"] = $val;
+ } elseif ($this->is_color_value($val)) {
+ $props["background_color"] = $val;
+ } else {
+ $pos_size[] = $val;
+ }
+ }
+
+ if (\count($pos_size)) {
+ // Split value list at "/"
+ $index = array_search("/", $pos_size, true);
+
+ if ($index !== false) {
+ $pos = \array_slice($pos_size, 0, $index);
+ $size = \array_slice($pos_size, $index + 1);
+ } else {
+ $pos = $pos_size;
+ $size = [];
+ }
+
+ $props["background_position"] = implode(" ", $pos);
+
+ if (\count($size)) {
+ $props["background_size"] = implode(" ", $size);
+ }
+ }
+
+ return $props;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
+ */
+ protected function _compute_font_size(string $size)
+ {
+ $parent_font_size = isset($this->parent_style)
+ ? $this->parent_style->__get("font_size")
+ : self::$default_font_size;
+
+ switch ($size) {
+ case "xx-small":
+ case "x-small":
+ case "small":
+ case "medium":
+ case "large":
+ case "x-large":
+ case "xx-large":
+ $fs = self::$default_font_size * self::$font_size_keywords[$size];
+ break;
+
+ case "smaller":
+ $fs = 8 / 9 * $parent_font_size;
+ break;
+
+ case "larger":
+ $fs = 6 / 5 * $parent_font_size;
+ break;
+
+ default:
+ $fs = $this->single_length_in_pt($size, $parent_font_size, $parent_font_size);
+ break;
+ }
+
+ return $fs;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/fonts.html#font-boldness
+ */
+ protected function _compute_font_weight(string $weight)
+ {
+ $computed_weight = $weight;
+
+ if ($weight === "bolder") {
+ //TODO: One font weight heavier than the parent element (among the available weights of the font).
+ $computed_weight = "bold";
+ } elseif ($weight === "lighter") {
+ //TODO: One font weight lighter than the parent element (among the available weights of the font).
+ $computed_weight = "normal";
+ }
+
+ return $computed_weight;
+ }
+
+ /**
+ * Handle the `font` shorthand property.
+ *
+ * `[ font-style || font-variant || font-weight ] font-size [ / line-height ] font-family`
+ *
+ * @link https://www.w3.org/TR/CSS21/fonts.html#font-shorthand
+ */
+ protected function _set_font(string $value): array
+ {
+ $components = $this->parse_property_value($value);
+ $props = [];
+
+ $number = self::CSS_NUMBER;
+ $unit = "pt|px|pc|rem|em|ex|in|cm|mm|%";
+ $sizePattern = "/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|$number(?:$unit))$/";
+ $sizeIndex = null;
+
+ // Find index of font-size to split the component list
+ foreach ($components as $i => $val) {
+ if (preg_match($sizePattern, $val)) {
+ $sizeIndex = $i;
+ $props["font_size"] = $val;
+ break;
+ }
+ }
+
+ // `font-size` is mandatory
+ if ($sizeIndex === null) {
+ return [];
+ }
+
+ // `font-style`, `font-variant`, `font-weight` in any order
+ $styleVariantWeight = \array_slice($components, 0, $sizeIndex);
+ $stylePattern = "/^(italic|oblique)$/";
+ $variantPattern = "/^(small-caps)$/";
+ $weightPattern = "/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900)$/";
+
+ if (\count($styleVariantWeight) > 3) {
+ return [];
+ }
+
+ foreach ($styleVariantWeight as $val) {
+ if ($val === "normal") {
+ // Ignore any `normal` value, as it is valid and the initial
+ // value for all three properties
+ } elseif (!isset($props["font_style"]) && preg_match($stylePattern, $val)) {
+ $props["font_style"] = $val;
+ } elseif (!isset($props["font_variant"]) && preg_match($variantPattern, $val)) {
+ $props["font_variant"] = $val;
+ } elseif (!isset($props["font_weight"]) && preg_match($weightPattern, $val)) {
+ $props["font_weight"] = $val;
+ } else {
+ // Duplicates and other values disallowed here
+ return [];
+ }
+ }
+
+ // Optional slash + `line-height` followed by mandatory `font-family`
+ $lineFamily = \array_slice($components, $sizeIndex + 1);
+ $hasLineHeight = $lineFamily !== [] && $lineFamily[0] === "/";
+ $lineHeight = $hasLineHeight ? \array_slice($lineFamily, 1, 1) : [];
+ $fontFamily = $hasLineHeight ? \array_slice($lineFamily, 2) : $lineFamily;
+ $lineHeightPattern = "/^(normal|$number(?:$unit)?)$/";
+
+ // Missing `font-family` or `line-height` after slash
+ if ($fontFamily === []
+ || ($hasLineHeight && !preg_match($lineHeightPattern, $lineHeight[0]))
+ ) {
+ return [];
+ }
+
+ if ($hasLineHeight) {
+ $props["line_height"] = $lineHeight[0];
+ }
+
+ $props["font_family"] = implode("", $fontFamily);
+
+ return $props;
+ }
+
+ /**
+ * Compute `text-align`.
+ *
+ * If no alignment is set on the element and the direction is rtl then
+ * the property is set to "right", otherwise it is set to "left".
+ *
+ * @link https://www.w3.org/TR/CSS21/text.html#propdef-text-align
+ */
+ protected function _compute_text_align(string $val)
+ {
+ $alignment = $val;
+ if ($alignment === "") {
+ $alignment = "left";
+ if ($this->__get("direction") === "rtl") {
+ $alignment = "right";
+ }
+ }
+
+ if (!\in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) {
+ return null;
+ }
+
+ return $alignment;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
+ */
+ protected function _compute_word_spacing(string $val)
+ {
+ if ($val === "normal") {
+ return 0.0;
+ }
+
+ return $this->compute_length_percentage($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
+ */
+ protected function _compute_letter_spacing(string $val)
+ {
+ if ($val === "normal") {
+ return 0.0;
+ }
+
+ return $this->compute_length_percentage($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
+ */
+ protected function _compute_line_height(string $val)
+ {
+ if ($val === "normal") {
+ return $val;
+ }
+
+ // Compute number values to string and lengths to float (in pt)
+ if (is_numeric($val)) {
+ return (string) $val;
+ }
+
+ $font_size = $this->__get("font_size");
+ $computed = $this->single_length_in_pt($val, $font_size);
+ return $computed !== null && $computed >= 0 ? $computed : null;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-text-3/#text-indent-property
+ */
+ protected function _compute_text_indent(string $val)
+ {
+ return $this->compute_length_percentage($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-before
+ */
+ protected function _compute_page_break_before(string $break)
+ {
+ if ($break === "left" || $break === "right") {
+ $break = "always";
+ }
+
+ return $break;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-after
+ */
+ protected function _compute_page_break_after(string $break)
+ {
+ if ($break === "left" || $break === "right") {
+ $break = "always";
+ }
+
+ return $break;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-width
+ */
+ protected function _compute_width(string $val)
+ {
+ if ($val === "auto") {
+ return $val;
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-height
+ */
+ protected function _compute_height(string $val)
+ {
+ if ($val === "auto") {
+ return $val;
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-width
+ */
+ protected function _compute_min_width(string $val)
+ {
+ // Legacy support for `none`, not covered by spec
+ if ($val === "auto" || $val === "none") {
+ return "auto";
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-height
+ */
+ protected function _compute_min_height(string $val)
+ {
+ // Legacy support for `none`, not covered by spec
+ if ($val === "auto" || $val === "none") {
+ return "auto";
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-width
+ */
+ protected function _compute_max_width(string $val)
+ {
+ // Legacy support for `auto`, not covered by spec
+ if ($val === "none" || $val === "auto") {
+ return "none";
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-height
+ */
+ protected function _compute_max_height(string $val)
+ {
+ // Legacy support for `auto`, not covered by spec
+ if ($val === "none" || $val === "auto") {
+ return "none";
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-position-3/#inset-properties
+ * @link https://www.w3.org/TR/css-position-3/#propdef-inset
+ */
+ protected function _set_inset(string $val): array
+ {
+ return $this->set_quad_shorthand("inset", $val);
+ }
+
+ /**
+ * @param string $val
+ * @return float|string|null
+ */
+ protected function compute_box_inset(string $val)
+ {
+ if ($val === "auto") {
+ return $val;
+ }
+
+ return $this->compute_length_percentage($val);
+ }
+
+ protected function _compute_top(string $val)
+ {
+ return $this->compute_box_inset($val);
+ }
+
+ protected function _compute_right(string $val)
+ {
+ return $this->compute_box_inset($val);
+ }
+
+ protected function _compute_bottom(string $val)
+ {
+ return $this->compute_box_inset($val);
+ }
+
+ protected function _compute_left(string $val)
+ {
+ return $this->compute_box_inset($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/box.html#margin-properties
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-margin
+ */
+ protected function _set_margin(string $val): array
+ {
+ return $this->set_quad_shorthand("margin", $val);
+ }
+
+ /**
+ * @param string $val
+ * @return float|string|null
+ */
+ protected function compute_margin(string $val)
+ {
+ // Legacy support for `none` keyword, not covered by spec
+ if ($val === "none") {
+ return 0.0;
+ }
+
+ if ($val === "auto") {
+ return $val;
+ }
+
+ return $this->compute_length_percentage($val);
+ }
+
+ protected function _compute_margin_top(string $val)
+ {
+ return $this->compute_margin($val);
+ }
+
+ protected function _compute_margin_right(string $val)
+ {
+ return $this->compute_margin($val);
+ }
+
+ protected function _compute_margin_bottom(string $val)
+ {
+ return $this->compute_margin($val);
+ }
+
+ protected function _compute_margin_left(string $val)
+ {
+ return $this->compute_margin($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/box.html#padding-properties
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-padding
+ */
+ protected function _set_padding(string $val): array
+ {
+ return $this->set_quad_shorthand("padding", $val);
+ }
+
+ /**
+ * @param string $val
+ * @return float|string|null
+ */
+ protected function compute_padding(string $val)
+ {
+ // Legacy support for `none` keyword, not covered by spec
+ if ($val === "none") {
+ return 0.0;
+ }
+
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ protected function _compute_padding_top(string $val)
+ {
+ return $this->compute_padding($val);
+ }
+
+ protected function _compute_padding_right(string $val)
+ {
+ return $this->compute_padding($val);
+ }
+
+ protected function _compute_padding_bottom(string $val)
+ {
+ return $this->compute_padding($val);
+ }
+
+ protected function _compute_padding_left(string $val)
+ {
+ return $this->compute_padding($val);
+ }
+
+ /**
+ * @param string $value `width || style || color`
+ * @param string[] $styles The list of border styles to accept.
+ *
+ * @return array Array of `[width, style, color]`, or `null` if the declaration is invalid.
+ */
+ protected function parse_border_side(string $value, array $styles = self::BORDER_STYLES): ?array
+ {
+ $components = $this->parse_property_value($value);
+ $width = null;
+ $style = null;
+ $color = null;
+
+ foreach ($components as $val) {
+ if ($style === null && \in_array($val, $styles, true)) {
+ $style = $val;
+ } elseif ($color === null && $this->is_color_value($val)) {
+ $color = $val;
+ } elseif ($width === null) {
+ // Assume width
+ $width = $val;
+ } else {
+ // Duplicates are not allowed
+ return null;
+ }
+ }
+
+ return [$width, $style, $color];
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/box.html#border-properties
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border
+ */
+ protected function _set_border(string $value): array
+ {
+ $values = $this->parse_border_side($value);
+
+ if ($values === null) {
+ return [];
+ }
+
+ return array_merge(
+ array_combine(self::$_props_shorthand["border_top"], $values),
+ array_combine(self::$_props_shorthand["border_right"], $values),
+ array_combine(self::$_props_shorthand["border_bottom"], $values),
+ array_combine(self::$_props_shorthand["border_left"], $values)
+ );
+ }
+
+ /**
+ * @param string $prop
+ * @param string $value
+ * @return array
+ */
+ protected function set_border_side(string $prop, string $value): array
+ {
+ $values = $this->parse_border_side($value);
+
+ if ($values === null) {
+ return [];
+ }
+
+ return array_combine(self::$_props_shorthand[$prop], $values);
+ }
+
+ protected function _set_border_top(string $val): array
+ {
+ return $this->set_border_side("border_top", $val);
+ }
+
+ protected function _set_border_right(string $val): array
+ {
+ return $this->set_border_side("border_right", $val);
+ }
+
+ protected function _set_border_bottom(string $val): array
+ {
+ return $this->set_border_side("border_bottom", $val);
+ }
+
+ protected function _set_border_left(string $val): array
+ {
+ return $this->set_border_side("border_left", $val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-color
+ */
+ protected function _set_border_color(string $val): array
+ {
+ return $this->set_quad_shorthand("border_color", $val);
+ }
+
+ protected function _compute_border_top_color(string $val)
+ {
+ return $this->compute_color_value($val);
+ }
+
+ protected function _compute_border_right_color(string $val)
+ {
+ return $this->compute_color_value($val);
+ }
+
+ protected function _compute_border_bottom_color(string $val)
+ {
+ return $this->compute_color_value($val);
+ }
+
+ protected function _compute_border_left_color(string $val)
+ {
+ return $this->compute_color_value($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-style
+ */
+ protected function _set_border_style(string $val): array
+ {
+ return $this->set_quad_shorthand("border_style", $val);
+ }
+
+ protected function _compute_border_top_style(string $val)
+ {
+ return $this->compute_border_style($val);
+ }
+
+ protected function _compute_border_right_style(string $val)
+ {
+ return $this->compute_border_style($val);
+ }
+
+ protected function _compute_border_bottom_style(string $val)
+ {
+ return $this->compute_border_style($val);
+ }
+
+ protected function _compute_border_left_style(string $val)
+ {
+ return $this->compute_border_style($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-width
+ */
+ protected function _set_border_width(string $val): array
+ {
+ return $this->set_quad_shorthand("border_width", $val);
+ }
+
+ protected function _compute_border_top_width(string $val)
+ {
+ return $this->compute_line_width($val, "border_top_style");
+ }
+
+ protected function _compute_border_right_width(string $val)
+ {
+ return $this->compute_line_width($val, "border_right_style");
+ }
+
+ protected function _compute_border_bottom_width(string $val)
+ {
+ return $this->compute_line_width($val, "border_bottom_style");
+ }
+
+ protected function _compute_border_left_width(string $val)
+ {
+ return $this->compute_line_width($val, "border_left_style");
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-backgrounds-3/#corners
+ * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-border-radius
+ */
+ protected function _set_border_radius(string $val): array
+ {
+ return $this->set_quad_shorthand("border_radius", $val);
+ }
+
+ protected function _compute_border_top_left_radius(string $val)
+ {
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ protected function _compute_border_top_right_radius(string $val)
+ {
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ protected function _compute_border_bottom_right_radius(string $val)
+ {
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ protected function _compute_border_bottom_left_radius(string $val)
+ {
+ return $this->compute_length_percentage_positive($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-ui-4/#outline-props
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline
+ */
+ protected function _set_outline(string $value): array
+ {
+ $values = $this->parse_border_side($value, self::OUTLINE_STYLES);
+
+ if ($values === null) {
+ return [];
+ }
+
+ return array_combine(self::$_props_shorthand["outline"], $values);
+ }
+
+ protected function _compute_outline_color(string $val)
+ {
+ return $this->compute_color_value($val);
+ }
+
+ protected function _compute_outline_style(string $val)
+ {
+ return \in_array($val, self::OUTLINE_STYLES, true) ? $val : null;
+ }
+
+ protected function _compute_outline_width(string $val)
+ {
+ return $this->compute_line_width($val, "outline_style");
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-offset
+ */
+ protected function _compute_outline_offset(string $val)
+ {
+ return $this->compute_length($val);
+ }
+
+ /**
+ * Compute `border-spacing` to two lengths of the form
+ * `[horizontal, vertical]`.
+ *
+ * @link https://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
+ */
+ protected function _compute_border_spacing(string $val)
+ {
+ $parts = preg_split("/\s+/", $val);
+
+ if (\count($parts) > 2) {
+ return null;
+ }
+
+ $h = $this->compute_length_positive($parts[0]);
+ $v = isset($parts[1])
+ ? $this->compute_length_positive($parts[1])
+ : $h;
+
+ if ($h === null || $v === null) {
+ return null;
+ }
+
+ return [$h, $v];
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
+ */
+ protected function _compute_list_style_image(string $val)
+ {
+ $parsed_val = $this->_stylesheet->resolve_url($val);
+
+ if ($parsed_val === "none") {
+ return "none";
+ } else {
+ return "url($parsed_val)";
+ }
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style
+ */
+ protected function _set_list_style(string $value): array
+ {
+ static $positions = ["inside", "outside"];
+ static $types = [
+ "disc", "circle", "square",
+ "decimal-leading-zero", "decimal", "1",
+ "lower-roman", "upper-roman", "a", "A",
+ "lower-greek",
+ "lower-latin", "upper-latin",
+ "lower-alpha", "upper-alpha",
+ "armenian", "georgian", "hebrew",
+ "cjk-ideographic", "hiragana", "katakana",
+ "hiragana-iroha", "katakana-iroha", "none"
+ ];
+
+ $components = $this->parse_property_value($value);
+ $props = [];
+
+ foreach ($components as $val) {
+ /* https://www.w3.org/TR/CSS21/generate.html#list-style
+ * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
+ */
+ if ($val === "none") {
+ $props["list_style_type"] = $val;
+ $props["list_style_image"] = $val;
+ continue;
+ }
+
+ //On setting or merging or inheriting list_style_image as well as list_style_type,
+ //and url exists, then url has precedence, otherwise fall back to list_style_type
+ //Firefox is wrong here (list_style_image gets overwritten on explicit list_style_type)
+ //Internet Explorer 7/8 and dompdf is right.
+
+ if (mb_substr($val, 0, 4) === "url(") {
+ $props["list_style_image"] = $val;
+ continue;
+ }
+
+ if (\in_array($val, $types, true)) {
+ $props["list_style_type"] = $val;
+ } elseif (\in_array($val, $positions, true)) {
+ $props["list_style_position"] = $val;
+ }
+ }
+
+ return $props;
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-page-3/#page-size-prop
+ */
+ protected function _compute_size(string $val)
+ {
+ if ($val === "auto") {
+ return $val;
+ }
+
+ $parts = $this->parse_property_value($val);
+ $count = \count($parts);
+
+ if ($count === 0 || $count > 3) {
+ return null;
+ }
+
+ $size = null;
+ $orientation = null;
+ $lengths = [];
+
+ foreach ($parts as $part) {
+ if ($size === null && isset(CPDF::$PAPER_SIZES[$part])) {
+ $size = $part;
+ } elseif ($orientation === null && ($part === "portrait" || $part === "landscape")) {
+ $orientation = $part;
+ } else {
+ $lengths[] = $part;
+ }
+ }
+
+ if ($size !== null && $lengths !== []) {
+ return null;
+ }
+
+ if ($size !== null) {
+ // Standard paper size
+ [$l1, $l2] = \array_slice(CPDF::$PAPER_SIZES[$size], 2, 2);
+ } elseif ($lengths === []) {
+ // Orientation only, use default paper size
+ $dims = $this->_stylesheet->get_dompdf()->getPaperSize();
+ [$l1, $l2] = \array_slice($dims, 2, 2);
+ } else {
+ // Custom paper size
+ $l1 = $this->compute_length_positive($lengths[0]);
+ $l2 = isset($lengths[1]) ? $this->compute_length_positive($lengths[1]) : $l1;
+
+ if ($l1 === null || $l2 === null) {
+ return null;
+ }
+ }
+
+ if (($orientation === "portrait" && $l1 > $l2)
+ || ($orientation === "landscape" && $l2 > $l1)
+ ) {
+ return [$l2, $l1];
+ }
+
+ return [$l1, $l2];
+ }
+
+ /**
+ * @param string $computed
+ * @return array
+ *
+ * @link https://www.w3.org/TR/css-transforms-1/#transform-property
+ */
+ protected function _get_transform($computed)
+ {
+ //TODO: should be handled in setter (lengths set to absolute)
+
+ $number = "\s*([^,\s]+)\s*";
+ $tr_value = "\s*([^,\s]+)\s*";
+ $angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
+
+ if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) {
+ return [];
+ }
+
+ $functions = [
+ //"matrix" => "\($number,$number,$number,$number,$number,$number\)",
+
+ "translate" => "\($tr_value(?:,$tr_value)?\)",
+ "translateX" => "\($tr_value\)",
+ "translateY" => "\($tr_value\)",
+
+ "scale" => "\($number(?:,$number)?\)",
+ "scaleX" => "\($number\)",
+ "scaleY" => "\($number\)",
+
+ "rotate" => "\($angle\)",
+
+ "skew" => "\($angle(?:,$angle)?\)",
+ "skewX" => "\($angle\)",
+ "skewY" => "\($angle\)",
+ ];
+
+ $transforms = [];
+
+ foreach ($parts as $part) {
+ $t = $part[0];
+
+ foreach ($functions as $name => $pattern) {
+ if (preg_match("/$name\s*$pattern/i", $t, $matches)) {
+ $values = \array_slice($matches, 1);
+
+ switch ($name) {
+ // <angle> units
+ case "rotate":
+ case "skew":
+ case "skewX":
+ case "skewY":
+
+ foreach ($values as $i => $value) {
+ if (strpos($value, "rad")) {
+ $values[$i] = rad2deg((float) $value);
+ } else {
+ $values[$i] = (float) $value;
+ }
+ }
+
+ switch ($name) {
+ case "skew":
+ if (!isset($values[1])) {
+ $values[1] = 0;
+ }
+ break;
+ case "skewX":
+ $name = "skew";
+ $values = [$values[0], 0];
+ break;
+ case "skewY":
+ $name = "skew";
+ $values = [0, $values[0]];
+ break;
+ }
+ break;
+
+ // <translation-value> units
+ case "translate":
+ $values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width));
+
+ if (isset($values[1])) {
+ $values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height));
+ } else {
+ $values[1] = 0;
+ }
+ break;
+
+ case "translateX":
+ $name = "translate";
+ $values = [$this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0];
+ break;
+
+ case "translateY":
+ $name = "translate";
+ $values = [0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height))];
+ break;
+
+ // <number> units
+ case "scale":
+ if (!isset($values[1])) {
+ $values[1] = $values[0];
+ }
+ break;
+
+ case "scaleX":
+ $name = "scale";
+ $values = [$values[0], 1.0];
+ break;
+
+ case "scaleY":
+ $name = "scale";
+ $values = [1.0, $values[0]];
+ break;
+ }
+
+ $transforms[] = [
+ $name,
+ $values,
+ ];
+ }
+ }
+ }
+
+ return $transforms;
+ }
+
+ /**
+ * @param string $computed
+ * @return array
+ *
+ * @link https://www.w3.org/TR/css-transforms-1/#transform-origin-property
+ */
+ protected function _get_transform_origin($computed)
+ {
+ //TODO: should be handled in setter
+
+ $values = preg_split("/\s+/", $computed);
+
+ $values = array_map(function ($value) {
+ if (\in_array($value, ["top", "left"], true)) {
+ return 0;
+ } elseif (\in_array($value, ["bottom", "right"], true)) {
+ return "100%";
+ } else {
+ return $value;
+ }
+ }, $values);
+
+ if (!isset($values[1])) {
+ $values[1] = $values[0];
+ }
+
+ return $values;
+ }
+
+ /**
+ * @param string $val
+ * @return string|null
+ */
+ protected function parse_image_resolution(string $val): ?string
+ {
+ // If exif data could be get:
+ // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/';
+
+ $re = '/^\s*(\d+|normal|auto)\s*$/';
+
+ if (!preg_match($re, $val, $matches)) {
+ return null;
+ }
+
+ return $matches[1];
+ }
+
+ /**
+ * auto | normal | dpi
+ */
+ protected function _compute_background_image_resolution(string $val)
+ {
+ return $this->parse_image_resolution($val);
+ }
+
+ /**
+ * auto | normal | dpi
+ */
+ protected function _compute_image_resolution(string $val)
+ {
+ return $this->parse_image_resolution($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-break-3/#propdef-orphans
+ */
+ protected function _compute_orphans(string $val)
+ {
+ return $this->compute_integer($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-break-3/#propdef-widows
+ */
+ protected function _compute_widows(string $val)
+ {
+ return $this->compute_integer($val);
+ }
+
+ /**
+ * @link https://www.w3.org/TR/css-color-4/#propdef-opacity
+ */
+ protected function _compute_opacity(string $val)
+ {
+ $number = self::CSS_NUMBER;
+ $pattern = "/^($number)(%?)$/";
+
+ if (!preg_match($pattern, $val, $matches)) {
+ return null;
+ }
+
+ $v = (float) $matches[1];
+ $percent = $matches[2] === "%";
+ $opacity = $percent ? ($v / 100) : $v;
+
+ return max(0.0, min($opacity, 1.0));
+ }
+
+ /**
+ * @link https://www.w3.org/TR/CSS21//visuren.html#propdef-z-index
+ */
+ protected function _compute_z_index(string $val)
+ {
+ if ($val === "auto") {
+ return $val;
+ }
+
+ return $this->compute_integer($val);
+ }
+
+ /**
+ * @param FontMetrics $fontMetrics
+ * @return $this
+ */
+ public function setFontMetrics(FontMetrics $fontMetrics)
+ {
+ $this->fontMetrics = $fontMetrics;
+ return $this;
+ }
+
+ /**
+ * @return FontMetrics
+ */
+ public function getFontMetrics()
+ {
+ return $this->fontMetrics;
+ }
+
+ /**
+ * Generate a string representation of the Style
+ *
+ * This dumps the entire property array into a string via print_r. Useful
+ * for debugging.
+ *
+ * @return string
+ */
+ /*DEBUGCSS print: see below additional debugging util*/
+ public function __toString(): string
+ {
+ $parent_font_size = $this->parent_style
+ ? $this->parent_style->font_size
+ : self::$default_font_size;
+
+ return print_r(array_merge(["parent_font_size" => $parent_font_size],
+ $this->_props), true);
+ }
+
+ /*DEBUGCSS*/
+ public function debug_print(): void
+ {
+ $parent_font_size = $this->parent_style
+ ? $this->parent_style->font_size
+ : self::$default_font_size;
+
+ print " parent_font_size:" . $parent_font_size . ";\n";
+ print " Props [\n";
+ print " specified [\n";
+ foreach ($this->_props as $prop => $val) {
+ print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
+ if (isset($this->_important_props[$prop])) {
+ print ' !important';
+ }
+ print ";\n";
+ }
+ print " ]\n";
+ print " computed [\n";
+ foreach ($this->_props_computed as $prop => $val) {
+ print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
+ print ";\n";
+ }
+ print " ]\n";
+ print " cached [\n";
+ foreach ($this->_props_used as $prop => $val) {
+ print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
+ print ";\n";
+ }
+ print " ]\n";
+ print " ]\n";
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Stylesheet.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Stylesheet.php
new file mode 100644
index 0000000..20e019a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Css/Stylesheet.php
@@ -0,0 +1,1689 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Css;
+
+use DOMElement;
+use DOMXPath;
+use Dompdf\Dompdf;
+use Dompdf\Helpers;
+use Dompdf\Exception;
+use Dompdf\FontMetrics;
+use Dompdf\Frame\FrameTree;
+
+/**
+ * The master stylesheet class
+ *
+ * The Stylesheet class is responsible for parsing stylesheets and style
+ * tags/attributes. It also acts as a registry of the individual Style
+ * objects generated by the current set of loaded CSS files and style
+ * elements.
+ *
+ * @see Style
+ * @package dompdf
+ */
+class Stylesheet
+{
+ /**
+ * The location of the default built-in CSS file.
+ */
+ const DEFAULT_STYLESHEET = "/lib/res/html.css";
+
+ /**
+ * User agent stylesheet origin
+ *
+ * @var int
+ */
+ const ORIG_UA = 1;
+
+ /**
+ * User normal stylesheet origin
+ *
+ * @var int
+ */
+ const ORIG_USER = 2;
+
+ /**
+ * Author normal stylesheet origin
+ *
+ * @var int
+ */
+ const ORIG_AUTHOR = 3;
+
+ /*
+ * The highest possible specificity is 0x01000000 (and that is only for author
+ * stylesheets, as it is for inline styles). Origin precedence can be achieved by
+ * adding multiples of 0x10000000 to the actual specificity. Important
+ * declarations are handled in Style; though technically they should be handled
+ * here so that user important declarations can be made to take precedence over
+ * user important declarations, this doesn't matter in practice as Dompdf does
+ * not support user stylesheets, and user agent stylesheets can not include
+ * important declarations.
+ */
+ private static $_stylesheet_origins = [
+ self::ORIG_UA => 0x00000000, // user agent declarations
+ self::ORIG_USER => 0x10000000, // user normal declarations
+ self::ORIG_AUTHOR => 0x30000000, // author normal declarations
+ ];
+
+ /**
+ * Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added
+ * to the beginning of an author stylesheet, i.e. anything in author stylesheets
+ * should override them.
+ */
+ const SPEC_NON_CSS = 0x20000000;
+
+ /**
+ * Current dompdf instance
+ *
+ * @var Dompdf
+ */
+ private $_dompdf;
+
+ /**
+ * Array of currently defined styles
+ *
+ * @var Style[]
+ */
+ private $_styles;
+
+ /**
+ * Base protocol of the document being parsed
+ * Used to handle relative urls.
+ *
+ * @var string
+ */
+ private $_protocol = "";
+
+ /**
+ * Base hostname of the document being parsed
+ * Used to handle relative urls.
+ *
+ * @var string
+ */
+ private $_base_host = "";
+
+ /**
+ * Base path of the document being parsed
+ * Used to handle relative urls.
+ *
+ * @var string
+ */
+ private $_base_path = "";
+
+ /**
+ * The styles defined by @page rules
+ *
+ * @var array<Style>
+ */
+ private $_page_styles;
+
+ /**
+ * List of loaded files, used to prevent recursion
+ *
+ * @var array
+ */
+ private $_loaded_files;
+
+ /**
+ * Current stylesheet origin
+ *
+ * @var int
+ */
+ private $_current_origin = self::ORIG_UA;
+
+ /**
+ * Accepted CSS media types
+ * List of types and parsing rules for future extensions:
+ * http://www.w3.org/TR/REC-html40/types.html
+ * screen, tty, tv, projection, handheld, print, braille, aural, all
+ * The following are non standard extensions for undocumented specific environments.
+ * static, visual, bitmap, paged, dompdf
+ * Note, even though the generated pdf file is intended for print output,
+ * the desired content might be different (e.g. screen or projection view of html file).
+ * Therefore allow specification of content by dompdf setting Options::defaultMediaType.
+ * If given, replace media "print" by Options::defaultMediaType.
+ * (Previous version $ACCEPTED_MEDIA_TYPES = $ACCEPTED_GENERIC_MEDIA_TYPES + $ACCEPTED_DEFAULT_MEDIA_TYPE)
+ */
+ static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print";
+ static $ACCEPTED_GENERIC_MEDIA_TYPES = ["all", "static", "visual", "bitmap", "paged", "dompdf"];
+ static $VALID_MEDIA_TYPES = ["all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual"];
+
+ /**
+ * @var FontMetrics
+ */
+ private $fontMetrics;
+
+ /**
+ * The class constructor.
+ *
+ * The base protocol, host & path are initialized to those of
+ * the current script.
+ */
+ function __construct(Dompdf $dompdf)
+ {
+ $this->_dompdf = $dompdf;
+ $this->setFontMetrics($dompdf->getFontMetrics());
+ $this->_styles = [];
+ $this->_loaded_files = [];
+ $script = __FILE__;
+ if (isset($_SERVER["SCRIPT_FILENAME"])) {
+ $script = $_SERVER["SCRIPT_FILENAME"];
+ }
+ list($this->_protocol, $this->_base_host, $this->_base_path) = Helpers::explode_url($script);
+ $this->_page_styles = ["base" => new Style($this)];
+ }
+
+ /**
+ * Set the base protocol
+ *
+ * @param string $protocol
+ */
+ function set_protocol(string $protocol)
+ {
+ $this->_protocol = $protocol;
+ }
+
+ /**
+ * Set the base host
+ *
+ * @param string $host
+ */
+ function set_host(string $host)
+ {
+ $this->_base_host = $host;
+ }
+
+ /**
+ * Set the base path
+ *
+ * @param string $path
+ */
+ function set_base_path(string $path)
+ {
+ $this->_base_path = $path;
+ }
+
+ /**
+ * Return the Dompdf object
+ *
+ * @return Dompdf
+ */
+ function get_dompdf()
+ {
+ return $this->_dompdf;
+ }
+
+ /**
+ * Return the base protocol for this stylesheet
+ *
+ * @return string
+ */
+ function get_protocol()
+ {
+ return $this->_protocol;
+ }
+
+ /**
+ * Return the base host for this stylesheet
+ *
+ * @return string
+ */
+ function get_host()
+ {
+ return $this->_base_host;
+ }
+
+ /**
+ * Return the base path for this stylesheet
+ *
+ * @return string
+ */
+ function get_base_path()
+ {
+ return $this->_base_path;
+ }
+
+ /**
+ * Return the array of page styles
+ *
+ * @return Style[]
+ */
+ function get_page_styles()
+ {
+ return $this->_page_styles;
+ }
+
+ /**
+ * Create a new Style object associated with this stylesheet
+ *
+ * @return Style
+ */
+ function create_style(): Style
+ {
+ return new Style($this, $this->_current_origin);
+ }
+
+ /**
+ * Add a new Style object to the stylesheet
+ *
+ * The style's origin is changed to the current origin of the stylesheet.
+ *
+ * @param string $key the Style's selector
+ * @param Style $style the Style to be added
+ */
+ function add_style(string $key, Style $style): void
+ {
+ if (!isset($this->_styles[$key])) {
+ $this->_styles[$key] = [];
+ }
+
+ $style->set_origin($this->_current_origin);
+ $this->_styles[$key][] = $style;
+ }
+
+ /**
+ * load and parse a CSS string
+ *
+ * @param string $css
+ * @param int $origin
+ */
+ function load_css(&$css, $origin = self::ORIG_AUTHOR)
+ {
+ if ($origin) {
+ $this->_current_origin = $origin;
+ }
+ $this->_parse_css($css);
+ }
+
+ /**
+ * load and parse a CSS file
+ *
+ * @param string $file
+ * @param int $origin
+ */
+ function load_css_file($file, $origin = self::ORIG_AUTHOR)
+ {
+ if ($origin) {
+ $this->_current_origin = $origin;
+ }
+
+ // Prevent circular references
+ if (isset($this->_loaded_files[$file])) {
+ return;
+ }
+
+ $this->_loaded_files[$file] = true;
+
+ if (strpos($file, "data:") === 0) {
+ $parsed = Helpers::parse_data_uri($file);
+ $css = $parsed["data"];
+ } else {
+ $options = $this->_dompdf->getOptions();
+
+ $parsed_url = Helpers::explode_url($file);
+ $protocol = $parsed_url["protocol"];
+
+ if ($file !== $this->getDefaultStylesheet()) {
+ $allowed_protocols = $options->getAllowedProtocols();
+ if (!array_key_exists($protocol, $allowed_protocols)) {
+ Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The communication protocol is not supported.", __FILE__, __LINE__);
+ return;
+ }
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
+ [$result, $message] = $rule($file);
+ if (!$result) {
+ Helpers::record_warnings(E_USER_WARNING, "Error loading $file: $message", __FILE__, __LINE__);
+ return;
+ }
+ }
+ }
+
+ [$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
+
+ $good_mime_type = true;
+
+ // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
+ if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) {
+ foreach ($http_response_header as $_header) {
+ if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) &&
+ ($matches[1] !== "text/css")
+ ) {
+ $good_mime_type = false;
+ }
+ }
+ }
+ if (!$good_mime_type || $css === null) {
+ Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);
+ return;
+ }
+
+ [$this->_protocol, $this->_base_host, $this->_base_path] = $parsed_url;
+ }
+
+ $this->_parse_css($css);
+ }
+
+ /**
+ * @link http://www.w3.org/TR/CSS21/cascade.html#specificity
+ *
+ * @param string $selector
+ * @param int $origin :
+ * - Stylesheet::ORIG_UA: user agent style sheet
+ * - Stylesheet::ORIG_USER: user style sheet
+ * - Stylesheet::ORIG_AUTHOR: author style sheet
+ *
+ * @return int
+ */
+ private function _specificity($selector, $origin = self::ORIG_AUTHOR)
+ {
+ // http://www.w3.org/TR/CSS21/cascade.html#specificity
+ // ignoring the ":" pseudoclass modifiers
+ // also ignored in _css_selector_to_xpath
+
+ $a = ($selector === "!attr") ? 1 : 0;
+
+ $b = min(mb_substr_count($selector, "#"), 255);
+
+ $c = min(mb_substr_count($selector, ".") +
+ mb_substr_count($selector, "["), 255);
+
+ $d = min(mb_substr_count($selector, " ") +
+ mb_substr_count($selector, ">") +
+ mb_substr_count($selector, "+") +
+ mb_substr_count($selector, "~") -
+ mb_substr_count($selector, "~="), 255);
+
+ //If a normal element name is at the beginning of the string,
+ //a leading whitespace might have been removed on whitespace collapsing and removal
+ //therefore there might be one whitespace less as selected element names
+ //this can lead to a too small specificity
+ //see _css_selector_to_xpath
+
+ if (!in_array($selector[0], [" ", ">", ".", "#", "+", "~", ":", "["]) && $selector !== "*") {
+ $d++;
+ }
+
+ if ($this->_dompdf->getOptions()->getDebugCss()) {
+ /*DEBUGCSS*/
+ print "<pre>\n";
+ /*DEBUGCSS*/
+ printf("_specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector);
+ /*DEBUGCSS*/
+ print "</pre>";
+ }
+
+ return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d));
+ }
+
+ /**
+ * Converts a CSS selector to an XPath query.
+ *
+ * @param string $selector
+ * @param bool $first_pass
+ *
+ * @throws Exception
+ * @return array
+ */
+ private function _css_selector_to_xpath(string $selector, bool $first_pass = false): array
+ {
+ // Collapse white space and strip whitespace around delimiters
+ //$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/");
+ //$replace = array(" ", "\\1");
+ //$selector = preg_replace($search, $replace, trim($selector));
+
+ // Initial query (non-absolute)
+ $query = "//";
+
+ // Will contain :before and :after
+ $pseudo_elements = [];
+
+ // Will contain :link, etc
+ $pseudo_classes = [];
+
+ // Parse the selector
+ //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE);
+
+ $delimiters = [" ", ">", ".", "#", "+", "~", ":", "[", "("];
+
+ // Add an implicit * at the beginning of the selector
+ // if it begins with an attribute selector
+ if ($selector[0] === "[") {
+ $selector = "*$selector";
+ }
+
+ // Add an implicit space at the beginning of the selector if there is no
+ // delimiter there already.
+ if (!in_array($selector[0], $delimiters)) {
+ $selector = " $selector";
+ }
+
+ $tok = "";
+ $len = mb_strlen($selector);
+ $i = 0;
+
+ while ($i < $len) {
+
+ $s = $selector[$i];
+ $i++;
+
+ // Eat characters up to the next delimiter
+ $tok = "";
+ $in_attr = false;
+ $in_func = false;
+
+ while ($i < $len) {
+ $c = $selector[$i];
+ $c_prev = $selector[$i - 1];
+
+ if (!$in_func && !$in_attr && in_array($c, $delimiters) && !(($c == $c_prev) == ":")) {
+ break;
+ }
+
+ if ($c_prev === "[") {
+ $in_attr = true;
+ }
+ if ($c_prev === "(") {
+ $in_func = true;
+ }
+
+ $tok .= $selector[$i++];
+
+ if ($in_attr && $c === "]") {
+ $in_attr = false;
+ break;
+ }
+ if ($in_func && $c === ")") {
+ $in_func = false;
+ break;
+ }
+ }
+
+ switch ($s) {
+
+ case " ":
+ case ">":
+ // All elements matching the next token that are direct children of
+ // the current token
+ $expr = $s === " " ? "descendant" : "child";
+
+ if (mb_substr($query, -1, 1) !== "/") {
+ $query .= "/";
+ }
+
+ // Tag names are case-insensitive
+ $tok = strtolower($tok);
+
+ if (!$tok) {
+ $tok = "*";
+ }
+
+ $query .= "$expr::$tok";
+ $tok = "";
+ break;
+
+ case ".":
+ case "#":
+ // All elements matching the current token with a class/id equal to
+ // the _next_ token.
+
+ $attr = $s === "." ? "class" : "id";
+
+ // empty class/id == *
+ if (mb_substr($query, -1, 1) === "/") {
+ $query .= "*";
+ }
+
+ // Match multiple classes: $tok contains the current selected
+ // class. Search for class attributes with class="$tok",
+ // class=".* $tok .*" and class=".* $tok"
+
+ // This doesn't work because libxml only supports XPath 1.0...
+ //$query .= "[matches(@$attr,\"^{$tok}\$|^{$tok}[ ]+|[ ]+{$tok}\$|[ ]+{$tok}[ ]+\")]";
+
+ $query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$tok', ' '))]";
+ $tok = "";
+ break;
+
+ case "+":
+ case "~":
+ // Next-sibling combinator
+ // Subsequent-sibling combinator
+ // https://www.w3.org/TR/selectors-3/#sibling-combinators
+ if (mb_substr($query, -1, 1) !== "/") {
+ $query .= "/";
+ }
+
+ // Tag names are case-insensitive
+ $tok = strtolower($tok);
+
+ if (!$tok) {
+ $tok = "*";
+ }
+
+ $query .= "following-sibling::$tok";
+
+ if ($s === "+") {
+ $query .= "[1]";
+ }
+
+ $tok = "";
+ break;
+
+ case ":":
+ $i2 = $i - strlen($tok) - 2; // the char before ":"
+ if (($i2 < 0 || !isset($selector[$i2]) || (in_array($selector[$i2], $delimiters) && $selector[$i2] != ":")) && substr($query, -1) != "*") {
+ $query .= "*";
+ }
+
+ $last = false;
+
+ // Pseudo-classes
+ switch ($tok) {
+
+ case "first-child":
+ $query .= "[not(preceding-sibling::*)]";
+ $tok = "";
+ break;
+
+ case "last-child":
+ $query .= "[not(following-sibling::*)]";
+ $tok = "";
+ break;
+
+ case "first-of-type":
+ $query .= "[position() = 1]";
+ $tok = "";
+ break;
+
+ case "last-of-type":
+ $query .= "[position() = last()]";
+ $tok = "";
+ break;
+
+ // an+b, n, odd, and even
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case "nth-last-of-type":
+ $last = true;
+ case "nth-of-type":
+ //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
+ $descendant_delimeter = strrpos($query, "::");
+ $isChild = substr($query, $descendant_delimeter-5, 5) == "child";
+ $el = substr($query, $descendant_delimeter+2);
+ $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . $el;
+
+ $pseudo_classes[$tok] = true;
+ $p = $i + 1;
+ $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
+ $position = $last ? "(last()-position()+1)" : "position()";
+
+ // 1
+ if (preg_match("/^\d+$/", $nth)) {
+ $condition = "$position = $nth";
+ } // odd
+ elseif ($nth === "odd") {
+ $condition = "($position mod 2) = 1";
+ } // even
+ elseif ($nth === "even") {
+ $condition = "($position mod 2) = 0";
+ } // an+b
+ else {
+ $condition = $this->_selector_an_plus_b($nth, $last);
+ }
+
+ $query .= "[$condition]";
+ $tok = "";
+ break;
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case "nth-last-child":
+ $last = true;
+ case "nth-child":
+ //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
+ $descendant_delimeter = strrpos($query, "::");
+ $isChild = substr($query, $descendant_delimeter-5, 5) == "child";
+ $el = substr($query, $descendant_delimeter+2);
+ $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . "*";
+
+ $pseudo_classes[$tok] = true;
+ $p = $i + 1;
+ $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
+ $position = $last ? "(last()-position()+1)" : "position()";
+
+ // 1
+ if (preg_match("/^\d+$/", $nth)) {
+ $condition = "$position = $nth";
+ } // odd
+ elseif ($nth === "odd") {
+ $condition = "($position mod 2) = 1";
+ } // even
+ elseif ($nth === "even") {
+ $condition = "($position mod 2) = 0";
+ } // an+b
+ else {
+ $condition = $this->_selector_an_plus_b($nth, $last);
+ }
+
+ $query .= "[$condition]";
+ if ($el != "*") {
+ $query .= "[name() = '$el']";
+ }
+ $tok = "";
+ break;
+
+ //TODO: bit of a hack attempt at matches support, currently only matches against elements
+ case "matches":
+ $pseudo_classes[$tok] = true;
+ $p = $i + 1;
+ $matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
+
+ // Tag names are case-insensitive
+ $elements = array_map("trim", explode(",", strtolower($matchList)));
+ foreach ($elements as &$element) {
+ $element = "name() = '$element'";
+ }
+
+ $query .= "[" . implode(" or ", $elements) . "]";
+ $tok = "";
+ break;
+
+ case "link":
+ $query .= "[@href]";
+ $tok = "";
+ break;
+
+ case "first-line":
+ case ":first-line":
+ case "first-letter":
+ case ":first-letter":
+ // TODO
+ $el = trim($tok, ":");
+ $pseudo_elements[$el] = true;
+ break;
+
+ // N/A
+ case "focus":
+ case "active":
+ case "hover":
+ case "visited":
+ $query .= "[false()]";
+ $tok = "";
+ break;
+
+ /* Pseudo-elements */
+ case "before":
+ case ":before":
+ case "after":
+ case ":after":
+ $pos = trim($tok, ":");
+ $pseudo_elements[$pos] = true;
+ if (!$first_pass) {
+ $query .= "/*[@$pos]";
+ }
+
+ $tok = "";
+ break;
+
+ case "empty":
+ $query .= "[not(*) and not(normalize-space())]";
+ $tok = "";
+ break;
+
+ case "disabled":
+ case "checked":
+ $query .= "[@$tok]";
+ $tok = "";
+ break;
+
+ case "enabled":
+ $query .= "[not(@disabled)]";
+ $tok = "";
+ break;
+
+ // the selector is not handled, until we support all possible selectors force an empty set (silent failure)
+ default:
+ $query = "/../.."; // go up two levels because generated content starts on the body element
+ $tok = "";
+ break;
+ }
+
+ break;
+
+ case "[":
+ // Attribute selectors. All with an attribute matching the following token(s)
+ // https://www.w3.org/TR/selectors-3/#attribute-selectors
+ $attr_delimiters = ["=", "]", "~", "|", "$", "^", "*"];
+ $tok_len = mb_strlen($tok);
+ $j = 0;
+
+ $attr = "";
+ $op = "";
+ $value = "";
+
+ while ($j < $tok_len) {
+ if (in_array($tok[$j], $attr_delimiters)) {
+ break;
+ }
+ $attr .= $tok[$j++];
+ }
+
+ switch ($tok[$j]) {
+
+ case "~":
+ case "|":
+ case "$":
+ case "^":
+ case "*":
+ $op .= $tok[$j++];
+
+ if ($tok[$j] !== "=") {
+ throw new Exception("Invalid CSS selector syntax: invalid attribute selector: $selector");
+ }
+
+ $op .= $tok[$j];
+ break;
+
+ case "=":
+ $op = "=";
+ break;
+
+ }
+
+ // Read the attribute value, if required
+ if ($op != "") {
+ $j++;
+ while ($j < $tok_len) {
+ if ($tok[$j] === "]") {
+ break;
+ }
+ $value .= $tok[$j++];
+ }
+ }
+
+ if ($attr == "") {
+ throw new Exception("Invalid CSS selector syntax: missing attribute name");
+ }
+
+ $value = trim($value, "\"'");
+
+ switch ($op) {
+
+ case "":
+ $query .= "[@$attr]";
+ break;
+
+ case "=":
+ $query .= "[@$attr=\"$value\"]";
+ break;
+
+ case "~=":
+ // FIXME: this will break if $value contains quoted strings
+ // (e.g. [type~="a b c" "d e f"])
+ // FIXME: Don't match anything if value contains
+ // whitespace or is the empty string
+ $query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$value', ' '))]";
+ break;
+
+ case "|=":
+ $values = explode("-", $value);
+ $query .= "[";
+
+ foreach ($values as $val) {
+ $query .= "starts-with(@$attr, \"$val\") or ";
+ }
+
+ $query = rtrim($query, " or ") . "]";
+ break;
+
+ case "$=":
+ $query .= "[substring(@$attr, string-length(@$attr)-" . (strlen($value) - 1) . ")=\"$value\"]";
+ break;
+
+ case "^=":
+ $query .= "[starts-with(@$attr,\"$value\")]";
+ break;
+
+ case "*=":
+ $query .= "[contains(@$attr,\"$value\")]";
+ break;
+ }
+
+ break;
+ }
+ }
+ $i++;
+
+// case ":":
+// // Pseudo selectors: ignore for now. Partially handled directly
+// // below.
+
+// // Skip until the next special character, leaving the token as-is
+// while ( $i < $len ) {
+// if ( in_array($selector[$i], $delimiters) )
+// break;
+// $i++;
+// }
+// break;
+
+// default:
+// // Add the character to the token
+// $tok .= $selector[$i++];
+// break;
+// }
+
+// }
+
+
+ // Trim the trailing '/' from the query
+ if (mb_strlen($query) > 2) {
+ $query = rtrim($query, "/");
+ }
+
+ return ['query' => $query, 'pseudo_elements' => $pseudo_elements];
+ }
+
+ /**
+ * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
+ *
+ * @param string $expr
+ * @param bool $last
+ *
+ * @return string
+ */
+ protected function _selector_an_plus_b(string $expr, bool $last = false): string
+ {
+ $expr = preg_replace("/\s/", "", $expr);
+ if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) {
+ return "false()";
+ }
+
+ $a = (isset($matches["a"]) && $matches["a"] !== "") ? ($matches["a"] !== "-" ? intval($matches["a"]) : -1) : 1;
+ $b = (isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0;
+
+ $position = $last ? "(last()-position()+1)" : "position()";
+
+ if ($b == 0) {
+ return "($position mod $a) = 0";
+ } else {
+ $compare = ($a < 0) ? "<=" : ">=";
+ $b2 = -$b;
+ if ($b2 >= 0) {
+ $b2 = "+$b2";
+ }
+ return "($position $compare $b) and ((($position $b2) mod " . abs($a) . ") = 0)";
+ }
+ }
+
+ /**
+ * applies all current styles to a particular document tree
+ *
+ * apply_styles() applies all currently loaded styles to the provided
+ * {@link FrameTree}. Aside from parsing CSS, this is the main purpose
+ * of this class.
+ *
+ * @param \Dompdf\Frame\FrameTree $tree
+ */
+ function apply_styles(FrameTree $tree)
+ {
+ // Use XPath to select nodes. This would be easier if we could attach
+ // Frame objects directly to DOMNodes using the setUserData() method, but
+ // we can't do that just yet. Instead, we set a _node attribute_ in
+ // Frame->set_id() and use that as a handle on the Frame object via
+ // FrameTree::$_registry.
+
+ // We create a scratch array of styles indexed by frame id. Once all
+ // styles have been assigned, we order the cached styles by specificity
+ // and create a final style object to assign to the frame.
+
+ // FIXME: this is not particularly robust...
+
+ $styles = [];
+ $xp = new DOMXPath($tree->get_dom());
+ $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
+
+ // Add generated content
+ foreach ($this->_styles as $selector => $selector_styles) {
+ /** @var Style $style */
+ foreach ($selector_styles as $style) {
+ if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) {
+ continue;
+ }
+
+ $query = $this->_css_selector_to_xpath($selector, true);
+
+ // Retrieve the nodes, limit to body for generated content
+ //TODO: If we use a context node can we remove the leading dot?
+ $nodes = @$xp->query('.' . $query["query"]);
+ if ($nodes === false) {
+ Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
+ continue;
+ }
+
+ /** @var \DOMElement $node */
+ foreach ($nodes as $node) {
+ // Only DOMElements get styles
+ if ($node->nodeType != XML_ELEMENT_NODE) {
+ continue;
+ }
+
+ foreach (array_keys($query["pseudo_elements"], true, true) as $pos) {
+ // Do not add a new pseudo element if another one already matched
+ if ($node->hasAttribute("dompdf_{$pos}_frame_id")) {
+ continue;
+ }
+
+ $content = $style->get_specified("content");
+
+ // Do not create non-displayed before/after pseudo elements
+ // https://www.w3.org/TR/CSS21/generate.html#content
+ // https://www.w3.org/TR/CSS21/generate.html#undisplayed-counters
+ if ($content === "normal" || $content === "none") {
+ continue;
+ }
+
+ if (($src = $this->resolve_url($content)) !== "none") {
+ $new_node = $node->ownerDocument->createElement("img_generated");
+ $new_node->setAttribute("src", $src);
+ } else {
+ $new_node = $node->ownerDocument->createElement("dompdf_generated");
+ }
+
+ $new_node->setAttribute($pos, $pos);
+ $new_frame_id = $tree->insert_node($node, $new_node, $pos);
+ $node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id);
+ }
+ }
+ }
+ }
+
+ // Apply all styles in stylesheet
+ foreach ($this->_styles as $selector => $selector_styles) {
+ /** @var Style $style */
+ foreach ($selector_styles as $style) {
+ $query = $this->_css_selector_to_xpath($selector);
+
+ // Retrieve the nodes
+ $nodes = @$xp->query($query["query"]);
+ if ($nodes === false) {
+ Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
+ continue;
+ }
+
+ $spec = $this->_specificity($selector, $style->get_origin());
+
+ foreach ($nodes as $node) {
+ // Retrieve the node id
+ // Only DOMElements get styles
+ if ($node->nodeType != XML_ELEMENT_NODE) {
+ continue;
+ }
+
+ $id = $node->getAttribute("frame_id");
+
+ // Assign the current style to the scratch array
+ $styles[$id][$spec][] = $style;
+ }
+ }
+ }
+
+ // Set the page width, height, and orientation based on the canvas paper size
+ $canvas = $this->_dompdf->getCanvas();
+ $paper_width = $canvas->get_width();
+ $paper_height = $canvas->get_height();
+ $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
+
+ if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) {
+ $paper_width = $this->_page_styles['base']->size[0];
+ $paper_height = $this->_page_styles['base']->size[1];
+ $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
+ }
+
+ // Now create the styles and assign them to the appropriate frames. (We
+ // iterate over the tree using an implicit FrameTree iterator.)
+ $root_flg = false;
+ foreach ($tree as $frame) {
+ // Helpers::pre_r($frame->get_node()->nodeName . ":");
+ if (!$root_flg && $this->_page_styles["base"]) {
+ $style = $this->_page_styles["base"];
+ } else {
+ $style = $this->create_style();
+ }
+
+ // Find nearest DOMElement parent
+ $p = $frame;
+ while ($p = $p->get_parent()) {
+ if ($p->get_node()->nodeType === XML_ELEMENT_NODE) {
+ break;
+ }
+ }
+
+ // Styles can only be applied directly to DOMElements; anonymous
+ // frames inherit from their parent
+ if ($frame->get_node()->nodeType !== XML_ELEMENT_NODE) {
+ $style->inherit($p ? $p->get_style() : null);
+ $frame->set_style($style);
+ continue;
+ }
+
+ $id = $frame->get_id();
+
+ // Handle HTML 4.0 attributes
+ AttributeTranslator::translate_attributes($frame);
+ if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") {
+ $styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str);
+ }
+
+ // Locate any additional style attributes
+ if (($str = $frame->get_node()->getAttribute("style")) !== "") {
+ // Destroy CSS comments
+ $str = preg_replace("'/\*.*?\*/'si", "", $str);
+
+ $spec = $this->_specificity("!attr", self::ORIG_AUTHOR);
+ $styles[$id][$spec][] = $this->_parse_properties($str);
+ }
+
+ // Grab the applicable styles
+ if (isset($styles[$id])) {
+
+ /** @var array[][] $applied_styles */
+ $applied_styles = $styles[$id];
+
+ // Sort by specificity
+ ksort($applied_styles);
+
+ if ($DEBUGCSS) {
+ $debug_nodename = $frame->get_node()->nodeName;
+ print "<pre>\n$debug_nodename [\n";
+ foreach ($applied_styles as $spec => $arr) {
+ printf(" specificity 0x%08x\n", $spec);
+ /** @var Style $s */
+ foreach ($arr as $s) {
+ print " [\n";
+ $s->debug_print();
+ print " ]\n";
+ }
+ }
+ }
+
+ // Merge the new styles with the inherited styles
+ $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
+ $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
+ foreach ($applied_styles as $arr) {
+ /** @var Style $s */
+ foreach ($arr as $s) {
+ $media_queries = $s->get_media_queries();
+ foreach ($media_queries as $media_query) {
+ list($media_query_feature, $media_query_value) = $media_query;
+ // if any of the Style's media queries fail then do not apply the style
+ //TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling
+ if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) {
+ if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) {
+ continue (3);
+ }
+ } else {
+ switch ($media_query_feature) {
+ case "height":
+ if ($paper_height !== (float)$style->length_in_pt($media_query_value)) {
+ continue (3);
+ }
+ break;
+ case "min-height":
+ if ($paper_height < (float)$style->length_in_pt($media_query_value)) {
+ continue (3);
+ }
+ break;
+ case "max-height":
+ if ($paper_height > (float)$style->length_in_pt($media_query_value)) {
+ continue (3);
+ }
+ break;
+ case "width":
+ if ($paper_width !== (float)$style->length_in_pt($media_query_value)) {
+ continue (3);
+ }
+ break;
+ case "min-width":
+ //if (min($paper_width, $media_query_width) === $paper_width) {
+ if ($paper_width < (float)$style->length_in_pt($media_query_value)) {
+ continue (3);
+ }
+ break;
+ case "max-width":
+ //if (max($paper_width, $media_query_width) === $paper_width) {
+ if ($paper_width > (float)$style->length_in_pt($media_query_value)) {
+ continue (3);
+ }
+ break;
+ case "orientation":
+ if ($paper_orientation !== $media_query_value) {
+ continue (3);
+ }
+ break;
+ default:
+ Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__);
+ break;
+ }
+ }
+ }
+
+ $style->merge($s);
+ }
+ }
+ }
+
+ // Handle inheritance
+ if ($p && $DEBUGCSS) {
+ print " inherit [\n";
+ $p->get_style()->debug_print();
+ print " ]\n";
+ }
+
+ $style->inherit($p ? $p->get_style() : null);
+
+ if ($DEBUGCSS) {
+ print " DomElementStyle [\n";
+ $style->debug_print();
+ print " ]\n";
+ print "]\n</pre>";
+ }
+
+ $style->clear_important();
+ $frame->set_style($style);
+
+ if (!$root_flg && $this->_page_styles["base"]) {
+ $root_flg = true;
+
+ // set the page width, height, and orientation based on the parsed page style
+ if ($style->size !== "auto") {
+ list($paper_width, $paper_height) = $style->size;
+ }
+ $paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right);
+ $paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom);
+ $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
+ }
+ }
+
+ // We're done! Clean out the registry of all styles since we
+ // won't be needing this later.
+ foreach (array_keys($this->_styles) as $key) {
+ $this->_styles[$key] = null;
+ unset($this->_styles[$key]);
+ }
+ }
+
+ /**
+ * parse a CSS string using a regex parser
+ * Called by {@link Stylesheet::parse_css()}
+ *
+ * @param string $str
+ *
+ * @throws Exception
+ */
+ private function _parse_css($str)
+ {
+ $str = trim($str);
+
+ // Destroy comments and remove HTML comments
+ $css = preg_replace([
+ "'/\*.*?\*/'si",
+ "/^<!--/",
+ "/-->$/"
+ ], "", $str);
+
+ // FIXME: handle '{' within strings, e.g. [attr="string {}"]
+
+ // Something more legible:
+ $re =
+ "/\s* # Skip leading whitespace \n" .
+ "( @([^\s{]+)\s*([^{;]*) (?:;|({)) )? # Match @rules followed by ';' or '{' \n" .
+ "(?(1) # Only parse sub-sections if we're in an @rule... \n" .
+ " (?(4) # ...and if there was a leading '{' \n" .
+ " \s*( (?:(?>[^{}]+) ({)? # Parse rulesets and individual @page rules \n" .
+ " (?(6) (?>[^}]*) }) \s*)+? \n" .
+ " ) \n" .
+ " }) # Balancing '}' \n" .
+ "| # Branch to match regular rules (not preceded by '@') \n" .
+ "([^{]*{[^}]*})) # Parse normal rulesets \n" .
+ "/xs";
+
+ if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) {
+ // An error occurred
+ throw new Exception("Error parsing css file: preg_match_all() failed.");
+ }
+
+ // After matching, the array indices are set as follows:
+ //
+ // [0] => complete text of match
+ // [1] => contains '@import ...;' or '@media {' if applicable
+ // [2] => text following @ for cases where [1] is set
+ // [3] => media types or full text following '@import ...;'
+ // [4] => '{', if present
+ // [5] => rulesets within media rules
+ // [6] => '{', within media rules
+ // [7] => individual rules, outside of media rules
+ //
+
+ $media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx";
+
+ //Helpers::pre_r($matches);
+ foreach ($matches as $match) {
+ $match[2] = trim($match[2]);
+
+ if ($match[2] !== "") {
+ // Handle @rules
+ switch ($match[2]) {
+
+ case "import":
+ $this->_parse_import($match[3]);
+ break;
+
+ case "media":
+ $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
+ $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
+
+ $media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3])));
+ foreach ($media_queries as $media_query) {
+ if (in_array($media_query, $acceptedmedia)) {
+ //if we have a media type match go ahead and parse the stylesheet
+ $this->_parse_sections($match[5]);
+ break;
+ } elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) {
+ // otherwise conditionally parse the stylesheet assuming there are parseable media queries
+ if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) {
+ $mq = [];
+ foreach ($media_query_matches as $media_query_match) {
+ if (empty($media_query_match[1]) === false) {
+ $media_query_feature = strtolower($media_query_match[3]);
+ $media_query_value = strtolower($media_query_match[2]);
+ $mq[] = [$media_query_feature, $media_query_value];
+ } elseif (empty($media_query_match[4]) === false) {
+ $media_query_feature = strtolower($media_query_match[5]);
+ $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
+ $mq[] = [$media_query_feature, $media_query_value];
+ }
+ }
+ $this->_parse_sections($match[5], $mq);
+ break;
+ }
+ }
+ }
+ break;
+
+ case "page":
+ //This handles @page to be applied to page oriented media
+ //Note: This has a reduced syntax:
+ //@page { margin:1cm; color:blue; }
+ //Not a sequence of styles like a full.css, but only the properties
+ //of a single style, which is applied to the very first "root" frame before
+ //processing other styles of the frame.
+ //Working properties:
+ // margin (for margin around edge of paper)
+ // font-family (default font of pages)
+ // color (default text color of pages)
+ //Non working properties:
+ // border
+ // padding
+ // background-color
+ //Todo:Reason is unknown
+ //Other properties (like further font or border attributes) not tested.
+ //If a border or background color around each paper sheet is desired,
+ //assign it to the <body> tag, possibly only for the css of the correct media type.
+
+ // If the page has a name, skip the style.
+ $page_selector = trim($match[3]);
+
+ $key = null;
+ switch ($page_selector) {
+ case "":
+ $key = "base";
+ break;
+
+ case ":left":
+ case ":right":
+ case ":odd":
+ case ":even":
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case ":first":
+ $key = $page_selector;
+ break;
+
+ default:
+ break 2;
+ }
+
+ // Store the style for later...
+ if (empty($this->_page_styles[$key])) {
+ $this->_page_styles[$key] = $this->_parse_properties($match[5]);
+ } else {
+ $this->_page_styles[$key]->merge($this->_parse_properties($match[5]));
+ }
+ break;
+
+ case "font-face":
+ $this->_parse_font_face($match[5]);
+ break;
+
+ default:
+ // ignore everything else
+ break;
+ }
+
+ continue;
+ }
+
+ if ($match[7] !== "") {
+ $this->_parse_sections($match[7]);
+ }
+ }
+ }
+
+ /**
+ * Resolve the given `url()` declaration to an absolute URL.
+ *
+ * @param string|null $val The declaration to resolve in the context of the stylesheet.
+ * @return string The resolved URL, or `none`, if the value is `none`,
+ * invalid, or points to a non-existent local file.
+ */
+ public function resolve_url($val): string
+ {
+ $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
+ $parsed_url = "none";
+
+ if (empty($val) || $val === "none") {
+ $path = "none";
+ } elseif (mb_strpos($val, "url") === false) {
+ $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
+ } else {
+ $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
+
+ // Resolve the url now in the context of the current stylesheet
+ $path = Helpers::build_url($this->_protocol,
+ $this->_base_host,
+ $this->_base_path,
+ $val);
+ if ($path === null) {
+ $path = "none";
+ }
+ }
+ if ($DEBUGCSS) {
+ $parsed_url = Helpers::explode_url($path);
+ print "<pre>[_image\n";
+ print_r($parsed_url);
+ print $this->_protocol . "\n" . $this->_base_path . "\n" . $path . "\n";
+ print "_image]</pre>";
+ }
+ return $path;
+ }
+
+ /**
+ * parse @import{} sections
+ *
+ * @param string $url the url of the imported CSS file
+ */
+ private function _parse_import($url)
+ {
+ $arr = preg_split("/[\s\n,]/", $url, -1, PREG_SPLIT_NO_EMPTY);
+ $url = array_shift($arr);
+ $accept = false;
+
+ if (count($arr) > 0) {
+ $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
+ $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
+
+ // @import url media_type [media_type...]
+ foreach ($arr as $type) {
+ if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
+ $accept = true;
+ break;
+ }
+ }
+
+ } else {
+ // unconditional import
+ $accept = true;
+ }
+
+ if ($accept) {
+ // Store our current base url properties in case the new url is elsewhere
+ $protocol = $this->_protocol;
+ $host = $this->_base_host;
+ $path = $this->_base_path;
+
+ // $url = str_replace(array('"',"url", "(", ")"), "", $url);
+ // If the protocol is php, assume that we will import using file://
+ // $url = Helpers::build_url($protocol === "php://" ? "file://" : $protocol, $host, $path, $url);
+ // Above does not work for subfolders and absolute urls.
+ // Todo: As above, do we need to replace php or file to an empty protocol for local files?
+
+ if (($url = $this->resolve_url($url)) !== "none") {
+ $this->load_css_file($url);
+ }
+
+ // Restore the current base url
+ $this->_protocol = $protocol;
+ $this->_base_host = $host;
+ $this->_base_path = $path;
+ }
+ }
+
+ /**
+ * parse @font-face{} sections
+ * http://www.w3.org/TR/css3-fonts/#the-font-face-rule
+ *
+ * @param string $str CSS @font-face rules
+ */
+ private function _parse_font_face($str)
+ {
+ $descriptors = $this->_parse_properties($str);
+
+ preg_match_all("/(url|local)\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\)\s*(format\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\))?/i", $descriptors->src, $src);
+
+ $valid_sources = [];
+ foreach ($src[0] as $i => $value) {
+ $source = [
+ "local" => strtolower($src[1][$i]) === "local",
+ "uri" => $src[2][$i],
+ "format" => strtolower($src[4][$i]),
+ "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
+ ];
+
+ if (!$source["local"] && in_array($source["format"], ["", "truetype"]) && $source["path"] !== null) {
+ $valid_sources[] = $source;
+ }
+ }
+
+ // No valid sources
+ if (empty($valid_sources)) {
+ return;
+ }
+
+ $style = [
+ "family" => $descriptors->get_font_family_raw(),
+ "weight" => $descriptors->font_weight,
+ "style" => $descriptors->font_style,
+ ];
+
+ $this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext());
+ }
+
+ /**
+ * parse regular CSS blocks
+ *
+ * _parse_properties() creates a new Style object based on the provided
+ * CSS rules.
+ *
+ * @param string $str CSS rules
+ * @return Style
+ */
+ private function _parse_properties($str)
+ {
+ $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str);
+ $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
+
+ if ($DEBUGCSS) {
+ print '[_parse_properties';
+ }
+
+ // Create the style
+ $style = new Style($this, Stylesheet::ORIG_AUTHOR);
+
+ foreach ($properties as $prop) {
+ // If the $prop contains an url, the regex may be wrong
+ // @todo: fix the regex so that it works every time
+ /*if (strpos($prop, "url(") === false) {
+ if (preg_match("/([a-z-]+)\s*:\s*[^:]+$/i", $prop, $m))
+ $prop = $m[0];
+ }*/
+
+ //A css property can have " ! important" appended (whitespace optional)
+ //strip this off to decode core of the property correctly.
+
+ /* Instead of short code, prefer the typical case with fast code
+ $important = preg_match("/(.*?)!\s*important/",$prop,$match);
+ if ( $important ) {
+ $prop = $match[1];
+ }
+ $prop = trim($prop);
+ */
+ if ($DEBUGCSS) print '(';
+
+ $important = false;
+ $prop = trim($prop);
+
+ if (substr($prop, -9) === 'important') {
+ $prop_tmp = rtrim(substr($prop, 0, -9));
+
+ if (substr($prop_tmp, -1) === '!') {
+ $prop = rtrim(substr($prop_tmp, 0, -1));
+ $important = true;
+ }
+ }
+
+ if ($prop === "") {
+ if ($DEBUGCSS) print 'empty)';
+ continue;
+ }
+
+ $i = mb_strpos($prop, ":");
+ if ($i === false) {
+ if ($DEBUGCSS) print 'novalue' . $prop . ')';
+ continue;
+ }
+
+ $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i)));
+ $value = ltrim(mb_substr($prop, $i + 1));
+
+ if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')';
+
+ $style->set_prop($prop_name, $value, $important, false);
+ }
+ if ($DEBUGCSS) print '_parse_properties]';
+
+ return $style;
+ }
+
+ /**
+ * parse selector + rulesets
+ *
+ * @param string $str CSS selectors and rulesets
+ * @param array $media_queries
+ */
+ private function _parse_sections($str, $media_queries = [])
+ {
+ // Pre-process selectors: collapse all whitespace and strip whitespace
+ // around '>', '.', ':', '+', '~', '#'
+ $patterns = ["/\s+/", "/\s+([>.:+~#])\s+/"];
+ $replacements = [" ", "\\1"];
+ $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
+
+ $sections = explode("}", $str);
+ if ($DEBUGCSS) print '[_parse_sections';
+ foreach ($sections as $sect) {
+ $i = mb_strpos($sect, "{");
+ if ($i === false) { continue; }
+
+ if ($DEBUGCSS) print '[section';
+
+ $selector_str = preg_replace($patterns, $replacements, mb_substr($sect, 0, $i));
+ $selectors = preg_split("/,(?![^\(]*\))/", $selector_str, 0, PREG_SPLIT_NO_EMPTY);
+ $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1)));
+
+ // Assign it to the selected elements
+ foreach ($selectors as $selector) {
+ $selector = trim($selector);
+
+ if ($selector == "") {
+ if ($DEBUGCSS) print '#empty#';
+ continue;
+ }
+ if ($DEBUGCSS) print '#' . $selector . '#';
+ //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; }
+
+ //FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here
+ if (count($media_queries) > 0) {
+ $style->set_media_queries($media_queries);
+ }
+ $this->add_style($selector, $style);
+ }
+
+ if ($DEBUGCSS) {
+ print 'section]';
+ }
+ }
+
+ if ($DEBUGCSS) {
+ print "_parse_sections]\n";
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getDefaultStylesheet()
+ {
+ $options = $this->_dompdf->getOptions();
+ $rootDir = realpath($options->getRootDir());
+ return Helpers::build_url("file://", "", $rootDir, $rootDir . self::DEFAULT_STYLESHEET);
+ }
+
+ /**
+ * @param FontMetrics $fontMetrics
+ * @return $this
+ */
+ public function setFontMetrics(FontMetrics $fontMetrics)
+ {
+ $this->fontMetrics = $fontMetrics;
+ return $this;
+ }
+
+ /**
+ * @return FontMetrics
+ */
+ public function getFontMetrics()
+ {
+ return $this->fontMetrics;
+ }
+
+ /**
+ * dumps the entire stylesheet as a string
+ *
+ * Generates a string of each selector and associated style in the
+ * Stylesheet. Useful for debugging.
+ *
+ * @return string
+ */
+ function __toString()
+ {
+ $str = "";
+ foreach ($this->_styles as $selector => $selector_styles) {
+ /** @var Style $style */
+ foreach ($selector_styles as $style) {
+ $str .= "$selector => " . $style->__toString() . "\n";
+ }
+ }
+
+ return $str;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Dompdf.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Dompdf.php
new file mode 100644
index 0000000..6feec59
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Dompdf.php
@@ -0,0 +1,1470 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+use DOMDocument;
+use DOMNode;
+use Dompdf\Adapter\CPDF;
+use DOMXPath;
+use Dompdf\Frame\Factory;
+use Dompdf\Frame\FrameTree;
+use Dompdf\Image\Cache;
+use Dompdf\Css\Stylesheet;
+use Dompdf\Helpers;
+use Masterminds\HTML5;
+
+/**
+ * Dompdf - PHP5 HTML to PDF renderer
+ *
+ * Dompdf loads HTML and does its best to render it as a PDF. It gets its
+ * name from the new DomDocument PHP5 extension. Source HTML is first
+ * parsed by a DomDocument object. Dompdf takes the resulting DOM tree and
+ * attaches a {@link Frame} object to each node. {@link Frame} objects store
+ * positioning and layout information and each has a reference to a {@link
+ * Style} object.
+ *
+ * Style information is loaded and parsed (see {@link Stylesheet}) and is
+ * applied to the frames in the tree by using XPath. CSS selectors are
+ * converted into XPath queries, and the computed {@link Style} objects are
+ * applied to the {@link Frame}s.
+ *
+ * {@link Frame}s are then decorated (in the design pattern sense of the
+ * word) based on their CSS display property ({@link
+ * http://www.w3.org/TR/CSS21/visuren.html#propdef-display}).
+ * Frame_Decorators augment the basic {@link Frame} class by adding
+ * additional properties and methods specific to the particular type of
+ * {@link Frame}. For example, in the CSS layout model, block frames
+ * (display: block;) contain line boxes that are usually filled with text or
+ * other inline frames. The Block therefore adds a $lines
+ * property as well as methods to add {@link Frame}s to lines and to add
+ * additional lines. {@link Frame}s also are attached to specific
+ * AbstractPositioner and {@link AbstractFrameReflower} objects that contain the
+ * positioining and layout algorithm for a specific type of frame,
+ * respectively. This is an application of the Strategy pattern.
+ *
+ * Layout, or reflow, proceeds recursively (post-order) starting at the root
+ * of the document. Space constraints (containing block width & height) are
+ * pushed down, and resolved positions and sizes bubble up. Thus, every
+ * {@link Frame} in the document tree is traversed once (except for tables
+ * which use a two-pass layout algorithm). If you are interested in the
+ * details, see the reflow() method of the Reflower classes.
+ *
+ * Rendering is relatively straightforward once layout is complete. {@link
+ * Frame}s are rendered using an adapted {@link Cpdf} class, originally
+ * written by Wayne Munro, http://www.ros.co.nz/pdf/. (Some performance
+ * related changes have been made to the original {@link Cpdf} class, and
+ * the {@link Dompdf\Adapter\CPDF} class provides a simple, stateless interface to
+ * PDF generation.) PDFLib support has now also been added, via the {@link
+ * Dompdf\Adapter\PDFLib}.
+ *
+ *
+ * @package dompdf
+ */
+class Dompdf
+{
+ /**
+ * Version string for dompdf
+ *
+ * @var string
+ */
+ private $version = 'dompdf';
+
+ /**
+ * DomDocument representing the HTML document
+ *
+ * @var DOMDocument
+ */
+ private $dom;
+
+ /**
+ * FrameTree derived from the DOM tree
+ *
+ * @var FrameTree
+ */
+ private $tree;
+
+ /**
+ * Stylesheet for the document
+ *
+ * @var Stylesheet
+ */
+ private $css;
+
+ /**
+ * Actual PDF renderer
+ *
+ * @var Canvas
+ */
+ private $canvas;
+
+ /**
+ * Desired paper size ('letter', 'legal', 'A4', etc.)
+ *
+ * @var string|array
+ */
+ private $paperSize;
+
+ /**
+ * Paper orientation ('portrait' or 'landscape')
+ *
+ * @var string
+ */
+ private $paperOrientation = "portrait";
+
+ /**
+ * Callbacks on new page and new element
+ *
+ * @var array
+ */
+ private $callbacks = [];
+
+ /**
+ * Experimental caching capability
+ *
+ * @var string
+ */
+ private $cacheId;
+
+ /**
+ * Base hostname
+ *
+ * Used for relative paths/urls
+ * @var string
+ */
+ private $baseHost = "";
+
+ /**
+ * Absolute base path
+ *
+ * Used for relative paths/urls
+ * @var string
+ */
+ private $basePath = "";
+
+ /**
+ * Protocol used to request file (file://, http://, etc)
+ *
+ * @var string
+ */
+ private $protocol = "";
+
+ /**
+ * The system's locale
+ *
+ * @var string
+ */
+ private $systemLocale = null;
+
+ /**
+ * The system's mbstring internal encoding
+ *
+ * @var string
+ */
+ private $mbstringEncoding = null;
+
+ /**
+ * The system's PCRE JIT configuration
+ *
+ * @var string
+ */
+ private $pcreJit = null;
+
+ /**
+ * The default view of the PDF in the viewer
+ *
+ * @var string
+ */
+ private $defaultView = "Fit";
+
+ /**
+ * The default view options of the PDF in the viewer
+ *
+ * @var array
+ */
+ private $defaultViewOptions = [];
+
+ /**
+ * Tells whether the DOM document is in quirksmode (experimental)
+ *
+ * @var bool
+ */
+ private $quirksmode = false;
+
+ /**
+ * Local file extension whitelist
+ *
+ * File extensions supported by dompdf for local files.
+ *
+ * @var array
+ */
+ private $allowedLocalFileExtensions = ["htm", "html"];
+
+ /**
+ * @var array
+ */
+ private $messages = [];
+
+ /**
+ * @var Options
+ */
+ private $options;
+
+ /**
+ * @var FontMetrics
+ */
+ private $fontMetrics;
+
+ /**
+ * The list of built-in fonts
+ *
+ * @var array
+ * @deprecated
+ */
+ public static $native_fonts = [
+ "courier", "courier-bold", "courier-oblique", "courier-boldoblique",
+ "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
+ "times-roman", "times-bold", "times-italic", "times-bolditalic",
+ "symbol", "zapfdinbats"
+ ];
+
+ /**
+ * The list of built-in fonts
+ *
+ * @var array
+ */
+ public static $nativeFonts = [
+ "courier", "courier-bold", "courier-oblique", "courier-boldoblique",
+ "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
+ "times-roman", "times-bold", "times-italic", "times-bolditalic",
+ "symbol", "zapfdinbats"
+ ];
+
+ /**
+ * Class constructor
+ *
+ * @param Options|array|null $options
+ */
+ public function __construct($options = null)
+ {
+ if (isset($options) && $options instanceof Options) {
+ $this->setOptions($options);
+ } elseif (is_array($options)) {
+ $this->setOptions(new Options($options));
+ } else {
+ $this->setOptions(new Options());
+ }
+
+ $versionFile = realpath(__DIR__ . '/../VERSION');
+ if (($version = file_get_contents($versionFile)) !== false) {
+ $version = trim($version);
+ if ($version !== '$Format:<%h>$') {
+ $this->version = sprintf('dompdf %s', $version);
+ }
+ }
+
+ $this->setPhpConfig();
+
+ $this->paperSize = $this->options->getDefaultPaperSize();
+ $this->paperOrientation = $this->options->getDefaultPaperOrientation();
+
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
+ $this->fontMetrics = new FontMetrics($this->canvas, $this->options);
+ $this->css = new Stylesheet($this);
+
+ $this->restorePhpConfig();
+ }
+
+ /**
+ * Save the system's existing locale, PCRE JIT, and MBString encoding
+ * configuration and configure the system for Dompdf processing
+ */
+ private function setPhpConfig()
+ {
+ if (sprintf('%.1f', 1.0) !== '1.0') {
+ $this->systemLocale = setlocale(LC_NUMERIC, "0");
+ setlocale(LC_NUMERIC, "C");
+ }
+
+ $this->pcreJit = @ini_get('pcre.jit');
+ @ini_set('pcre.jit', '0');
+
+ $this->mbstringEncoding = mb_internal_encoding();
+ mb_internal_encoding('UTF-8');
+ }
+
+ /**
+ * Restore the system's locale configuration
+ */
+ private function restorePhpConfig()
+ {
+ if ($this->systemLocale !== null) {
+ setlocale(LC_NUMERIC, $this->systemLocale);
+ $this->systemLocale = null;
+ }
+
+ if ($this->pcreJit !== null) {
+ @ini_set('pcre.jit', $this->pcreJit);
+ $this->pcreJit = null;
+ }
+
+ if ($this->mbstringEncoding !== null) {
+ mb_internal_encoding($this->mbstringEncoding);
+ $this->mbstringEncoding = null;
+ }
+ }
+
+ /**
+ * @param $file
+ * @deprecated
+ */
+ public function load_html_file($file)
+ {
+ $this->loadHtmlFile($file);
+ }
+
+ /**
+ * Loads an HTML file
+ * Parse errors are stored in the global array _dompdf_warnings.
+ *
+ * @param string $file a filename or url to load
+ * @param string $encoding Encoding of $file
+ *
+ * @throws Exception
+ */
+ public function loadHtmlFile($file, $encoding = null)
+ {
+ $this->setPhpConfig();
+
+ if (!$this->protocol && !$this->baseHost && !$this->basePath) {
+ [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
+ }
+ $protocol = strtolower($this->protocol);
+ $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
+
+ $allowed_protocols = $this->options->getAllowedProtocols();
+ if (!array_key_exists($protocol, $allowed_protocols)) {
+ throw new Exception("Permission denied on $file. The communication protocol is not supported.");
+ }
+
+ if ($protocol === "file://") {
+ $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
+ if (!in_array($ext, $this->allowedLocalFileExtensions)) {
+ throw new Exception("Permission denied on $file: The file extension is forbidden.");
+ }
+ }
+
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
+ [$result, $message] = $rule($uri);
+ if (!$result) {
+ throw new Exception("Error loading $file: $message");
+ }
+ }
+
+ [$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext());
+ if ($contents === null) {
+ throw new Exception("File '$file' not found.");
+ }
+
+ // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
+ if (isset($http_response_header)) {
+ foreach ($http_response_header as $_header) {
+ if (preg_match("@Content-Type:\s*[\w/]+;\s*?charset=([^\s]+)@i", $_header, $matches)) {
+ $encoding = strtoupper($matches[1]);
+ break;
+ }
+ }
+ }
+
+ $this->restorePhpConfig();
+
+ $this->loadHtml($contents, $encoding);
+ }
+
+ /**
+ * @param string $str
+ * @param string $encoding
+ * @deprecated
+ */
+ public function load_html($str, $encoding = null)
+ {
+ $this->loadHtml($str, $encoding);
+ }
+
+ public function loadDOM($doc, $quirksmode = false) {
+ // Remove #text children nodes in nodes that shouldn't have
+ $tag_names = ["html", "head", "table", "tbody", "thead", "tfoot", "tr"];
+ foreach ($tag_names as $tag_name) {
+ $nodes = $doc->getElementsByTagName($tag_name);
+
+ foreach ($nodes as $node) {
+ self::removeTextNodes($node);
+ }
+ }
+
+ $this->dom = $doc;
+ $this->quirksmode = $quirksmode;
+ $this->tree = new FrameTree($this->dom);
+ }
+
+ /**
+ * Loads an HTML string
+ * Parse errors are stored in the global array _dompdf_warnings.
+ *
+ * @param string $str HTML text to load
+ * @param string $encoding Encoding of $str
+ */
+ public function loadHtml($str, $encoding = null)
+ {
+ $this->setPhpConfig();
+
+ // Determine character encoding when $encoding parameter not used
+ if ($encoding === null) {
+ mb_detect_order('auto');
+ if (($encoding = mb_detect_encoding($str, null, true)) === false) {
+
+ //"auto" is expanded to "ASCII,JIS,UTF-8,EUC-JP,SJIS"
+ $encoding = "auto";
+ }
+ }
+
+ if (in_array(strtoupper($encoding), array('UTF-8','UTF8')) === false) {
+ $str = mb_convert_encoding($str, 'UTF-8', $encoding);
+
+ //Update encoding after converting
+ $encoding = 'UTF-8';
+ }
+
+ $metatags = [
+ '@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i',
+ '@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i',
+ '@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i',
+ ];
+ foreach ($metatags as $metatag) {
+ if (preg_match($metatag, $str, $matches)) {
+ if (isset($matches[1]) && in_array($matches[1], mb_list_encodings())) {
+ $document_encoding = $matches[1];
+ break;
+ }
+ }
+ }
+ if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8','UTF8']) === false) {
+ $str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
+ } elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) {
+ $str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
+ } elseif (isset($document_encoding) === false) {
+ $str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str;
+ }
+
+ // remove BOM mark from UTF-8, it's treated as document text by DOMDocument
+ // FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)?
+ if (substr($str, 0, 3) == chr(0xEF) . chr(0xBB) . chr(0xBF)) {
+ $str = substr($str, 3);
+ }
+
+ // Store parsing warnings as messages
+ set_error_handler([Helpers::class, 'record_warnings']);
+
+ try {
+ // @todo Take the quirksmode into account
+ // https://quirks.spec.whatwg.org/
+ // http://hsivonen.iki.fi/doctype/
+ $quirksmode = false;
+
+ $html5 = new HTML5(['encoding' => $encoding, 'disable_html_ns' => true]);
+ $dom = $html5->loadHTML($str);
+
+ // extra step to normalize the HTML document structure
+ // see Masterminds/html5-php#166
+ $doc = new DOMDocument("1.0", $encoding);
+ $doc->preserveWhiteSpace = true;
+ $doc->loadHTML($html5->saveHTML($dom), LIBXML_NOWARNING | LIBXML_NOERROR);
+
+ $this->loadDOM($doc, $quirksmode);
+ } finally {
+ restore_error_handler();
+ $this->restorePhpConfig();
+ }
+ }
+
+ /**
+ * @param DOMNode $node
+ * @deprecated
+ */
+ public static function remove_text_nodes(DOMNode $node)
+ {
+ self::removeTextNodes($node);
+ }
+
+ /**
+ * @param DOMNode $node
+ */
+ public static function removeTextNodes(DOMNode $node)
+ {
+ $children = [];
+ for ($i = 0; $i < $node->childNodes->length; $i++) {
+ $child = $node->childNodes->item($i);
+ if ($child->nodeName === "#text") {
+ $children[] = $child;
+ }
+ }
+
+ foreach ($children as $child) {
+ $node->removeChild($child);
+ }
+ }
+
+ /**
+ * Builds the {@link FrameTree}, loads any CSS and applies the styles to
+ * the {@link FrameTree}
+ */
+ private function processHtml()
+ {
+ $this->tree->build_tree();
+
+ $this->css->load_css_file($this->css->getDefaultStylesheet(), Stylesheet::ORIG_UA);
+
+ $acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES;
+ $acceptedmedia[] = $this->options->getDefaultMediaType();
+
+ // <base href="" />
+ /** @var \DOMElement|null */
+ $baseNode = $this->dom->getElementsByTagName("base")->item(0);
+ $baseHref = $baseNode ? $baseNode->getAttribute("href") : "";
+ if ($baseHref !== "") {
+ [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($baseHref);
+ }
+
+ // Set the base path of the Stylesheet to that of the file being processed
+ $this->css->set_protocol($this->protocol);
+ $this->css->set_host($this->baseHost);
+ $this->css->set_base_path($this->basePath);
+
+ // Get all the stylesheets so that they are processed in document order
+ $xpath = new DOMXPath($this->dom);
+ $stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']");
+
+ /** @var \DOMElement $tag */
+ foreach ($stylesheets as $tag) {
+ switch (strtolower($tag->nodeName)) {
+ // load <link rel="STYLESHEET" ... /> tags
+ case "link":
+ if (mb_strtolower(stripos($tag->getAttribute("rel"), "stylesheet") !== false) || // may be "appendix stylesheet"
+ mb_strtolower($tag->getAttribute("type")) === "text/css"
+ ) {
+ //Check if the css file is for an accepted media type
+ //media not given then always valid
+ $formedialist = preg_split("/[\s\n,]/", $tag->getAttribute("media"), -1, PREG_SPLIT_NO_EMPTY);
+ if (count($formedialist) > 0) {
+ $accept = false;
+ foreach ($formedialist as $type) {
+ if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
+ $accept = true;
+ break;
+ }
+ }
+
+ if (!$accept) {
+ //found at least one mediatype, but none of the accepted ones
+ //Skip this css file.
+ break;
+ }
+ }
+
+ $url = $tag->getAttribute("href");
+ $url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url);
+
+ if ($url !== null) {
+ $this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR);
+ }
+ }
+ break;
+
+ // load <style> tags
+ case "style":
+ // Accept all <style> tags by default (note this is contrary to W3C
+ // HTML 4.0 spec:
+ // http://www.w3.org/TR/REC-html40/present/styles.html#adef-media
+ // which states that the default media type is 'screen'
+ if ($tag->hasAttributes() &&
+ ($media = $tag->getAttribute("media")) &&
+ !in_array($media, $acceptedmedia)
+ ) {
+ break;
+ }
+
+ $css = "";
+ if ($tag->hasChildNodes()) {
+ $child = $tag->firstChild;
+ while ($child) {
+ $css .= $child->nodeValue; // Handle <style><!-- blah --></style>
+ $child = $child->nextSibling;
+ }
+ } else {
+ $css = $tag->nodeValue;
+ }
+
+ // Set the base path of the Stylesheet to that of the file being processed
+ $this->css->set_protocol($this->protocol);
+ $this->css->set_host($this->baseHost);
+ $this->css->set_base_path($this->basePath);
+
+ $this->css->load_css($css, Stylesheet::ORIG_AUTHOR);
+ break;
+ }
+
+ // Set the base path of the Stylesheet to that of the file being processed
+ $this->css->set_protocol($this->protocol);
+ $this->css->set_host($this->baseHost);
+ $this->css->set_base_path($this->basePath);
+ }
+ }
+
+ /**
+ * @param string $cacheId
+ * @deprecated
+ */
+ public function enable_caching($cacheId)
+ {
+ $this->enableCaching($cacheId);
+ }
+
+ /**
+ * Enable experimental caching capability
+ *
+ * @param string $cacheId
+ */
+ public function enableCaching($cacheId)
+ {
+ $this->cacheId = $cacheId;
+ }
+
+ /**
+ * @param string $value
+ * @return bool
+ * @deprecated
+ */
+ public function parse_default_view($value)
+ {
+ return $this->parseDefaultView($value);
+ }
+
+ /**
+ * @param string $value
+ * @return bool
+ */
+ public function parseDefaultView($value)
+ {
+ $valid = ["XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"];
+
+ $options = preg_split("/\s*,\s*/", trim($value));
+ $defaultView = array_shift($options);
+
+ if (!in_array($defaultView, $valid)) {
+ return false;
+ }
+
+ $this->setDefaultView($defaultView, $options);
+ return true;
+ }
+
+ /**
+ * Renders the HTML to PDF
+ */
+ public function render()
+ {
+ $this->setPhpConfig();
+
+ $logOutputFile = $this->options->getLogOutputFile();
+ if ($logOutputFile) {
+ if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
+ touch($logOutputFile);
+ }
+
+ $startTime = microtime(true);
+ if (is_writable($logOutputFile)) {
+ ob_start();
+ }
+ }
+
+ $this->processHtml();
+
+ $this->css->apply_styles($this->tree);
+
+ // @page style rules : size, margins
+ $pageStyles = $this->css->get_page_styles();
+ $basePageStyle = $pageStyles["base"];
+ unset($pageStyles["base"]);
+
+ foreach ($pageStyles as $pageStyle) {
+ $pageStyle->inherit($basePageStyle);
+ }
+
+ // Set paper size if defined via CSS
+ if (is_array($basePageStyle->size)) {
+ [$width, $height] = $basePageStyle->size;
+ $this->setPaper([0, 0, $width, $height]);
+ }
+
+ // Create a new canvas instance if the current one does not match the
+ // desired paper size
+ $canvasWidth = $this->canvas->get_width();
+ $canvasHeight = $this->canvas->get_height();
+ $size = $this->getPaperSize();
+
+ if ($canvasWidth !== $size[2] || $canvasHeight !== $size[3]) {
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
+ $this->fontMetrics->setCanvas($this->canvas);
+ }
+
+ $canvas = $this->canvas;
+
+ $root_frame = $this->tree->get_root();
+ $root = Factory::decorate_root($root_frame, $this);
+ foreach ($this->tree as $frame) {
+ if ($frame === $root_frame) {
+ continue;
+ }
+ Factory::decorate_frame($frame, $this, $root);
+ }
+
+ // Add meta information
+ $title = $this->dom->getElementsByTagName("title");
+ if ($title->length) {
+ $canvas->add_info("Title", trim($title->item(0)->nodeValue));
+ }
+
+ $metas = $this->dom->getElementsByTagName("meta");
+ $labels = [
+ "author" => "Author",
+ "keywords" => "Keywords",
+ "description" => "Subject",
+ ];
+ /** @var \DOMElement $meta */
+ foreach ($metas as $meta) {
+ $name = mb_strtolower($meta->getAttribute("name"));
+ $value = trim($meta->getAttribute("content"));
+
+ if (isset($labels[$name])) {
+ $canvas->add_info($labels[$name], $value);
+ continue;
+ }
+
+ if ($name === "dompdf.view" && $this->parseDefaultView($value)) {
+ $canvas->set_default_view($this->defaultView, $this->defaultViewOptions);
+ }
+ }
+
+ $root->set_containing_block(0, 0, $canvas->get_width(), $canvas->get_height());
+ $root->set_renderer(new Renderer($this));
+
+ // This is where the magic happens:
+ $root->reflow();
+
+ if (isset($this->callbacks["end_document"])) {
+ $fs = $this->callbacks["end_document"];
+
+ foreach ($fs as $f) {
+ $canvas->page_script($f);
+ }
+ }
+
+ // Clean up cached images
+ if (!$this->options->getDebugKeepTemp()) {
+ Cache::clear($this->options->getDebugPng());
+ }
+
+ global $_dompdf_warnings, $_dompdf_show_warnings;
+ if ($_dompdf_show_warnings && isset($_dompdf_warnings)) {
+ echo '<b>Dompdf Warnings</b><br><pre>';
+ foreach ($_dompdf_warnings as $msg) {
+ echo $msg . "\n";
+ }
+
+ if ($canvas instanceof CPDF) {
+ echo $canvas->get_cpdf()->messages;
+ }
+ echo '</pre>';
+ flush();
+ }
+
+ if ($logOutputFile && is_writable($logOutputFile)) {
+ $this->writeLog($logOutputFile, $startTime);
+ ob_end_clean();
+ }
+
+ $this->restorePhpConfig();
+ }
+
+ /**
+ * Writes the output buffer in the log file
+ *
+ * @param string $logOutputFile
+ * @param float $startTime
+ */
+ private function writeLog(string $logOutputFile, float $startTime): void
+ {
+ $frames = Frame::$ID_COUNTER;
+ $memory = memory_get_peak_usage(true) / 1024;
+ $time = (microtime(true) - $startTime) * 1000;
+
+ $out = sprintf(
+ "<span style='color: #000' title='Frames'>%6d</span>" .
+ "<span style='color: #009' title='Memory'>%10.2f KB</span>" .
+ "<span style='color: #900' title='Time'>%10.2f ms</span>" .
+ "<span title='Quirksmode'> " .
+ ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") .
+ "</span><br />", $frames, $memory, $time);
+
+ $out .= ob_get_contents();
+ ob_clean();
+
+ file_put_contents($logOutputFile, $out);
+ }
+
+ /**
+ * Add meta information to the PDF after rendering.
+ *
+ * @deprecated
+ */
+ public function add_info($label, $value)
+ {
+ $this->addInfo($label, $value);
+ }
+
+ /**
+ * Add meta information to the PDF after rendering.
+ *
+ * @param string $label Label of the value (Creator, Producer, etc.)
+ * @param string $value The text to set
+ */
+ public function addInfo(string $label, string $value): void
+ {
+ $this->canvas->add_info($label, $value);
+ }
+
+ /**
+ * Streams the PDF to the client.
+ *
+ * The file will open a download dialog by default. The options
+ * parameter controls the output. Accepted options (array keys) are:
+ *
+ * 'compress' = > 1 (=default) or 0:
+ * Apply content stream compression
+ *
+ * 'Attachment' => 1 (=default) or 0:
+ * Set the 'Content-Disposition:' HTTP header to 'attachment'
+ * (thereby causing the browser to open a download dialog)
+ *
+ * @param string $filename the name of the streamed file
+ * @param array $options header options (see above)
+ */
+ public function stream($filename = "document.pdf", $options = [])
+ {
+ $this->setPhpConfig();
+
+ $this->canvas->stream($filename, $options);
+
+ $this->restorePhpConfig();
+ }
+
+ /**
+ * Returns the PDF as a string.
+ *
+ * The options parameter controls the output. Accepted options are:
+ *
+ * 'compress' = > 1 or 0 - apply content stream compression, this is
+ * on (1) by default
+ *
+ * @param array $options options (see above)
+ *
+ * @return string|null
+ */
+ public function output($options = [])
+ {
+ $this->setPhpConfig();
+
+ $output = $this->canvas->output($options);
+
+ $this->restorePhpConfig();
+
+ return $output;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function output_html()
+ {
+ return $this->outputHtml();
+ }
+
+ /**
+ * Returns the underlying HTML document as a string
+ *
+ * @return string
+ */
+ public function outputHtml()
+ {
+ return $this->dom->saveHTML();
+ }
+
+ /**
+ * Get the dompdf option value
+ *
+ * @param string $key
+ * @return mixed
+ * @deprecated
+ */
+ public function get_option($key)
+ {
+ return $this->options->get($key);
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @return $this
+ * @deprecated
+ */
+ public function set_option($key, $value)
+ {
+ $this->options->set($key, $value);
+ return $this;
+ }
+
+ /**
+ * @param array $options
+ * @return $this
+ * @deprecated
+ */
+ public function set_options(array $options)
+ {
+ $this->options->set($options);
+ return $this;
+ }
+
+ /**
+ * @param string $size
+ * @param string $orientation
+ * @deprecated
+ */
+ public function set_paper($size, $orientation = "portrait")
+ {
+ $this->setPaper($size, $orientation);
+ }
+
+ /**
+ * Sets the paper size & orientation
+ *
+ * @param string|float[] $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
+ * @param string $orientation 'portrait' or 'landscape'
+ * @return $this
+ */
+ public function setPaper($size, $orientation = "portrait")
+ {
+ $this->paperSize = $size;
+ $this->paperOrientation = $orientation;
+ return $this;
+ }
+
+ /**
+ * Gets the paper size
+ *
+ * @return float[] A four-element float array
+ */
+ public function getPaperSize()
+ {
+ $paper = $this->paperSize;
+ $orientation = $this->paperOrientation;
+
+ if (is_array($paper)) {
+ $size = array_map("floatval", $paper);
+ } else {
+ $paper = strtolower($paper);
+ $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
+ }
+
+ if (strtolower($orientation) === "landscape") {
+ [$size[2], $size[3]] = [$size[3], $size[2]];
+ }
+
+ return $size;
+ }
+
+ /**
+ * Gets the paper orientation
+ *
+ * @return string Either "portrait" or "landscape"
+ */
+ public function getPaperOrientation()
+ {
+ return $this->paperOrientation;
+ }
+
+ /**
+ * @param FrameTree $tree
+ * @return $this
+ */
+ public function setTree(FrameTree $tree)
+ {
+ $this->tree = $tree;
+ return $this;
+ }
+
+ /**
+ * @return FrameTree
+ * @deprecated
+ */
+ public function get_tree()
+ {
+ return $this->getTree();
+ }
+
+ /**
+ * Returns the underlying {@link FrameTree} object
+ *
+ * @return FrameTree
+ */
+ public function getTree()
+ {
+ return $this->tree;
+ }
+
+ /**
+ * @param string $protocol
+ * @return $this
+ * @deprecated
+ */
+ public function set_protocol($protocol)
+ {
+ return $this->setProtocol($protocol);
+ }
+
+ /**
+ * Sets the protocol to use
+ * FIXME validate these
+ *
+ * @param string $protocol
+ * @return $this
+ */
+ public function setProtocol(string $protocol)
+ {
+ $this->protocol = $protocol;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function get_protocol()
+ {
+ return $this->getProtocol();
+ }
+
+ /**
+ * Returns the protocol in use
+ *
+ * @return string
+ */
+ public function getProtocol()
+ {
+ return $this->protocol;
+ }
+
+ /**
+ * @param string $host
+ * @deprecated
+ */
+ public function set_host($host)
+ {
+ $this->setBaseHost($host);
+ }
+
+ /**
+ * Sets the base hostname
+ *
+ * @param string $baseHost
+ * @return $this
+ */
+ public function setBaseHost(string $baseHost)
+ {
+ $this->baseHost = $baseHost;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function get_host()
+ {
+ return $this->getBaseHost();
+ }
+
+ /**
+ * Returns the base hostname
+ *
+ * @return string
+ */
+ public function getBaseHost()
+ {
+ return $this->baseHost;
+ }
+
+ /**
+ * Sets the base path
+ *
+ * @param string $path
+ * @deprecated
+ */
+ public function set_base_path($path)
+ {
+ $this->setBasePath($path);
+ }
+
+ /**
+ * Sets the base path
+ *
+ * @param string $basePath
+ * @return $this
+ */
+ public function setBasePath(string $basePath)
+ {
+ $this->basePath = $basePath;
+ return $this;
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function get_base_path()
+ {
+ return $this->getBasePath();
+ }
+
+ /**
+ * Returns the base path
+ *
+ * @return string
+ */
+ public function getBasePath()
+ {
+ return $this->basePath;
+ }
+
+ /**
+ * @param string $default_view The default document view
+ * @param array $options The view's options
+ * @return $this
+ * @deprecated
+ */
+ public function set_default_view($default_view, $options)
+ {
+ return $this->setDefaultView($default_view, $options);
+ }
+
+ /**
+ * Sets the default view
+ *
+ * @param string $defaultView The default document view
+ * @param array $options The view's options
+ * @return $this
+ */
+ public function setDefaultView($defaultView, $options)
+ {
+ $this->defaultView = $defaultView;
+ $this->defaultViewOptions = $options;
+ return $this;
+ }
+
+ /**
+ * @param resource $http_context
+ * @return $this
+ * @deprecated
+ */
+ public function set_http_context($http_context)
+ {
+ return $this->setHttpContext($http_context);
+ }
+
+ /**
+ * Sets the HTTP context
+ *
+ * @param resource|array $httpContext
+ * @return $this
+ */
+ public function setHttpContext($httpContext)
+ {
+ $this->options->setHttpContext($httpContext);
+ return $this;
+ }
+
+ /**
+ * @return resource
+ * @deprecated
+ */
+ public function get_http_context()
+ {
+ return $this->getHttpContext();
+ }
+
+ /**
+ * Returns the HTTP context
+ *
+ * @return resource
+ */
+ public function getHttpContext()
+ {
+ return $this->options->getHttpContext();
+ }
+
+ /**
+ * Set a custom `Canvas` instance to render the document to.
+ *
+ * Be aware that the instance will be replaced on render if the document
+ * defines a paper size different from the canvas.
+ *
+ * @param Canvas $canvas
+ * @return $this
+ */
+ public function setCanvas(Canvas $canvas)
+ {
+ $this->canvas = $canvas;
+ return $this;
+ }
+
+ /**
+ * @return Canvas
+ * @deprecated
+ */
+ public function get_canvas()
+ {
+ return $this->getCanvas();
+ }
+
+ /**
+ * Return the underlying Canvas instance (e.g. Dompdf\Adapter\CPDF, Dompdf\Adapter\GD)
+ *
+ * @return Canvas
+ */
+ public function getCanvas()
+ {
+ return $this->canvas;
+ }
+
+ /**
+ * @param Stylesheet $css
+ * @return $this
+ */
+ public function setCss(Stylesheet $css)
+ {
+ $this->css = $css;
+ return $this;
+ }
+
+ /**
+ * @return Stylesheet
+ * @deprecated
+ */
+ public function get_css()
+ {
+ return $this->getCss();
+ }
+
+ /**
+ * Returns the stylesheet
+ *
+ * @return Stylesheet
+ */
+ public function getCss()
+ {
+ return $this->css;
+ }
+
+ /**
+ * @param DOMDocument $dom
+ * @return $this
+ */
+ public function setDom(DOMDocument $dom)
+ {
+ $this->dom = $dom;
+ return $this;
+ }
+
+ /**
+ * @return DOMDocument
+ * @deprecated
+ */
+ public function get_dom()
+ {
+ return $this->getDom();
+ }
+
+ /**
+ * @return DOMDocument
+ */
+ public function getDom()
+ {
+ return $this->dom;
+ }
+
+ /**
+ * @param Options $options
+ * @return $this
+ */
+ public function setOptions(Options $options)
+ {
+ // For backwards compatibility
+ if ($this->options && $this->options->getHttpContext() && !$options->getHttpContext()) {
+ $options->setHttpContext($this->options->getHttpContext());
+ }
+
+ $this->options = $options;
+ $fontMetrics = $this->fontMetrics;
+ if (isset($fontMetrics)) {
+ $fontMetrics->setOptions($options);
+ }
+ return $this;
+ }
+
+ /**
+ * @return Options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @return array
+ * @deprecated
+ */
+ public function get_callbacks()
+ {
+ return $this->getCallbacks();
+ }
+
+ /**
+ * Returns the callbacks array
+ *
+ * @return array
+ */
+ public function getCallbacks()
+ {
+ return $this->callbacks;
+ }
+
+ /**
+ * @param array $callbacks the set of callbacks to set
+ * @return $this
+ * @deprecated
+ */
+ public function set_callbacks($callbacks)
+ {
+ return $this->setCallbacks($callbacks);
+ }
+
+ /**
+ * Define callbacks that allow modifying the document during render.
+ *
+ * The callbacks array should contain arrays with `event` set to a callback
+ * event name and `f` set to a function or any other callable.
+ *
+ * The available callback events are:
+ * * `begin_page_reflow`: called before page reflow
+ * * `begin_frame`: called before a frame is rendered
+ * * `end_frame`: called after frame rendering is complete
+ * * `begin_page_render`: called before a page is rendered
+ * * `end_page_render`: called after page rendering is complete
+ * * `end_document`: called for every page after rendering is complete
+ *
+ * The function `f` receives three arguments `Frame $frame`, `Canvas $canvas`,
+ * and `FontMetrics $fontMetrics` for all events but `end_document`. For
+ * `end_document`, the function receives four arguments `int $pageNumber`,
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics` instead.
+ *
+ * @param array $callbacks The set of callbacks to set.
+ * @return $this
+ */
+ public function setCallbacks(array $callbacks): self
+ {
+ $this->callbacks = [];
+
+ foreach ($callbacks as $c) {
+ if (is_array($c) && isset($c["event"]) && isset($c["f"])) {
+ $event = $c["event"];
+ $f = $c["f"];
+ if (is_string($event) && is_callable($f)) {
+ $this->callbacks[$event][] = $f;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ * @deprecated
+ */
+ public function get_quirksmode()
+ {
+ return $this->getQuirksmode();
+ }
+
+ /**
+ * Get the quirks mode
+ *
+ * @return boolean true if quirks mode is active
+ */
+ public function getQuirksmode()
+ {
+ return $this->quirksmode;
+ }
+
+ /**
+ * @param FontMetrics $fontMetrics
+ * @return $this
+ */
+ public function setFontMetrics(FontMetrics $fontMetrics)
+ {
+ $this->fontMetrics = $fontMetrics;
+ return $this;
+ }
+
+ /**
+ * @return FontMetrics
+ */
+ public function getFontMetrics()
+ {
+ return $this->fontMetrics;
+ }
+
+ /**
+ * PHP5 overloaded getter
+ * Along with {@link Dompdf::__set()} __get() provides access to all
+ * properties directly. Typically __get() is not called directly outside
+ * of this class.
+ *
+ * @param string $prop
+ *
+ * @throws Exception
+ * @return mixed
+ */
+ function __get($prop)
+ {
+ switch ($prop) {
+ case 'version':
+ return $this->version;
+ default:
+ throw new Exception('Invalid property: ' . $prop);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception.php
new file mode 100644
index 0000000..3a90e47
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+/**
+ * Standard exception thrown by DOMPDF classes
+ *
+ * @package dompdf
+ */
+class Exception extends \Exception
+{
+
+ /**
+ * Class constructor
+ *
+ * @param string $message Error message
+ * @param int $code Error code
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ parent::__construct($message, $code);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception/ImageException.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception/ImageException.php
new file mode 100644
index 0000000..ea1dfe4
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Exception/ImageException.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Exception;
+
+use Dompdf\Exception;
+
+/**
+ * Image exception thrown by DOMPDF
+ *
+ * @package dompdf
+ */
+class ImageException extends Exception
+{
+
+ /**
+ * Class constructor
+ *
+ * @param string $message Error message
+ * @param int $code Error code
+ */
+ function __construct($message = null, $code = 0)
+ {
+ parent::__construct($message, $code);
+ }
+
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FontMetrics.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FontMetrics.php
new file mode 100644
index 0000000..5698c88
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FontMetrics.php
@@ -0,0 +1,635 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+use FontLib\Font;
+
+/**
+ * The font metrics class
+ *
+ * This class provides information about fonts and text. It can resolve
+ * font names into actual installed font files, as well as determine the
+ * size of text in a particular font and size.
+ *
+ * @static
+ * @package dompdf
+ */
+class FontMetrics
+{
+ /**
+ * Name of the user font families file
+ *
+ * This file must be writable by the webserver process only to update it
+ * with save_font_families() after adding the .afm file references of a new font family
+ * with FontMetrics::saveFontFamilies().
+ * This is typically done only from command line with load_font.php on converting
+ * ttf fonts to ufm with php-font-lib.
+ */
+ const USER_FONTS_FILE = "installed-fonts.json";
+
+
+ /**
+ * Underlying {@link Canvas} object to perform text size calculations
+ *
+ * @var Canvas
+ */
+ protected $canvas;
+
+ /**
+ * Array of bundled font family names to variants
+ *
+ * @var array
+ */
+ protected $bundledFonts = [];
+
+ /**
+ * Array of user defined font family names to variants
+ *
+ * @var array
+ */
+ protected $userFonts = [];
+
+ /**
+ * combined list of all font families with absolute paths
+ *
+ * @var array
+ */
+ protected $fontFamilies;
+
+ /**
+ * @var Options
+ */
+ private $options;
+
+ /**
+ * Class initialization
+ */
+ public function __construct(Canvas $canvas, Options $options)
+ {
+ $this->setCanvas($canvas);
+ $this->setOptions($options);
+ $this->loadFontFamilies();
+ }
+
+ /**
+ * @deprecated
+ */
+ public function save_font_families()
+ {
+ $this->saveFontFamilies();
+ }
+
+ /**
+ * Saves the stored font family cache
+ *
+ * The name and location of the cache file are determined by {@link
+ * FontMetrics::USER_FONTS_FILE}. This file should be writable by the
+ * webserver process.
+ *
+ * @see FontMetrics::loadFontFamilies()
+ */
+ public function saveFontFamilies()
+ {
+ file_put_contents($this->getUserFontsFilePath(), json_encode($this->userFonts, JSON_PRETTY_PRINT));
+ }
+
+ /**
+ * @deprecated
+ */
+ public function load_font_families()
+ {
+ $this->loadFontFamilies();
+ }
+
+ /**
+ * Loads the stored font family cache
+ *
+ * @see FontMetrics::saveFontFamilies()
+ */
+ public function loadFontFamilies()
+ {
+ $file = $this->options->getRootDir() . "/lib/fonts/installed-fonts.dist.json";
+ $this->bundledFonts = json_decode(file_get_contents($file), true);
+
+ if (is_readable($this->getUserFontsFilePath())) {
+ $this->userFonts = json_decode(file_get_contents($this->getUserFontsFilePath()), true);
+ } else {
+ $this->loadFontFamiliesLegacy();
+ }
+ }
+
+ private function loadFontFamiliesLegacy()
+ {
+ $legacyCacheFile = $this->options->getFontDir() . '/dompdf_font_family_cache.php';
+ if (is_readable($legacyCacheFile)) {
+ $fontDir = $this->options->getFontDir();
+ $rootDir = $this->options->getRootDir();
+
+ if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); }
+ if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); }
+
+ $cacheDataClosure = require $legacyCacheFile;
+ $cacheData = is_array($cacheDataClosure) ? $cacheDataClosure : $cacheDataClosure($fontDir, $rootDir);
+ if (is_array($cacheData)) {
+ foreach ($cacheData as $family => $variants) {
+ if (!isset($this->bundledFonts[$family]) && is_array($variants)) {
+ foreach ($variants as $variant => $variantPath) {
+ $variantName = basename($variantPath);
+ $variantDir = dirname($variantPath);
+ if ($variantDir == $fontDir) {
+ $this->userFonts[$family][$variant] = $variantName;
+ } else {
+ $this->userFonts[$family][$variant] = $variantPath;
+ }
+ }
+ }
+ }
+ $this->saveFontFamilies();
+ }
+ }
+ }
+
+ /**
+ * @param array $style
+ * @param string $remote_file
+ * @param resource $context
+ * @return bool
+ * @deprecated
+ */
+ public function register_font($style, $remote_file, $context = null)
+ {
+ return $this->registerFont($style, $remote_file);
+ }
+
+ /**
+ * @param array $style
+ * @param string $remoteFile
+ * @param resource $context
+ * @return bool
+ */
+ public function registerFont($style, $remoteFile, $context = null)
+ {
+ $fontname = mb_strtolower($style["family"]);
+ $families = $this->getFontFamilies();
+
+ $entry = [];
+ if (isset($families[$fontname])) {
+ $entry = $families[$fontname];
+ }
+
+ $styleString = $this->getType("{$style['weight']} {$style['style']}");
+
+ $remoteHash = md5($remoteFile);
+
+ $prefix = $fontname . "_" . $styleString;
+ $prefix = trim($prefix, "-");
+ if (function_exists('iconv')) {
+ $prefix = @iconv('utf-8', 'us-ascii//TRANSLIT', $prefix);
+ }
+ $prefix_encoding = mb_detect_encoding($prefix, mb_detect_order(), true);
+ $substchar = mb_substitute_character();
+ mb_substitute_character(0x005F);
+ $prefix = mb_convert_encoding($prefix, "ISO-8859-1", $prefix_encoding);
+ mb_substitute_character($substchar);
+ $prefix = preg_replace("[\W]", "_", $prefix);
+ $prefix = preg_replace("/[^-_\w]+/", "", $prefix);
+
+ $localFile = $prefix . "_" . $remoteHash;
+ $localFilePath = $this->getOptions()->getFontDir() . "/" . $localFile;
+
+ if (isset($entry[$styleString]) && $localFilePath == $entry[$styleString]) {
+ return true;
+ }
+
+
+ $entry[$styleString] = $localFile;
+
+ // Download the remote file
+ [$protocol] = Helpers::explode_url($remoteFile);
+ $allowed_protocols = $this->options->getAllowedProtocols();
+ if (!array_key_exists($protocol, $allowed_protocols)) {
+ Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__);
+ return false;
+ }
+
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
+ [$result, $message] = $rule($remoteFile);
+ if ($result !== true) {
+ Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__);
+ return false;
+ }
+ }
+
+ list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
+ if ($remoteFileContent === null) {
+ return false;
+ }
+
+ $localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-");
+ file_put_contents($localTempFile, $remoteFileContent);
+
+ $font = Font::load($localTempFile);
+
+ if (!$font) {
+ unlink($localTempFile);
+ return false;
+ }
+
+ $font->parse();
+ $font->saveAdobeFontMetrics("$localFilePath.ufm");
+ $font->close();
+
+ unlink($localTempFile);
+
+ if ( !file_exists("$localFilePath.ufm") ) {
+ return false;
+ }
+
+ $fontExtension = ".ttf";
+ switch ($font->getFontType()) {
+ case "TrueType":
+ default:
+ $fontExtension = ".ttf";
+ break;
+ }
+
+ // Save the changes
+ file_put_contents($localFilePath.$fontExtension, $remoteFileContent);
+
+ if ( !file_exists($localFilePath.$fontExtension) ) {
+ unlink("$localFilePath.ufm");
+ return false;
+ }
+
+ $this->setFontFamily($fontname, $entry);
+
+ return true;
+ }
+
+ /**
+ * @param $text
+ * @param $font
+ * @param $size
+ * @param float $word_spacing
+ * @param float $char_spacing
+ * @return float
+ * @deprecated
+ */
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
+ {
+ //return self::$_pdf->get_text_width($text, $font, $size, $word_spacing, $char_spacing);
+ return $this->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
+ }
+
+ /**
+ * Calculates text size, in points
+ *
+ * @param string $text The text to be sized
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ * @param float $wordSpacing Word spacing, if any
+ * @param float $charSpacing Char spacing, if any
+ *
+ * @return float
+ */
+ public function getTextWidth(string $text, $font, float $size, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
+ {
+ // @todo Make sure this cache is efficient before enabling it
+ static $cache = [];
+
+ if ($text === "") {
+ return 0;
+ }
+
+ // Don't cache long strings
+ $useCache = !isset($text[50]); // Faster than strlen
+
+ // Text-size calculations depend on the canvas used. Make sure to not
+ // return wrong values when switching canvas backends
+ $canvasClass = get_class($this->canvas);
+ $key = "$canvasClass/$font/$size/$wordSpacing/$charSpacing";
+
+ if ($useCache && isset($cache[$key][$text])) {
+ return $cache[$key][$text];
+ }
+
+ $width = $this->canvas->get_text_width($text, $font, $size, $wordSpacing, $charSpacing);
+
+ if ($useCache) {
+ $cache[$key][$text] = $width;
+ }
+
+ return $width;
+ }
+
+ /**
+ * @param $font
+ * @param $size
+ * @return float
+ * @deprecated
+ */
+ public function get_font_height($font, $size)
+ {
+ return $this->getFontHeight($font, $size);
+ }
+
+ /**
+ * Calculates font height, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ public function getFontHeight($font, float $size): float
+ {
+ return $this->canvas->get_font_height($font, $size);
+ }
+
+ /**
+ * Calculates font baseline, in points
+ *
+ * @param string $font The font file to use
+ * @param float $size The font size, in points
+ *
+ * @return float
+ */
+ public function getFontBaseline($font, float $size): float
+ {
+ return $this->canvas->get_font_baseline($font, $size);
+ }
+
+ /**
+ * @param $family_raw
+ * @param string $subtype_raw
+ * @return string
+ * @deprecated
+ */
+ public function get_font($family_raw, $subtype_raw = "normal")
+ {
+ return $this->getFont($family_raw, $subtype_raw);
+ }
+
+ /**
+ * Resolves a font family & subtype into an actual font file
+ * Subtype can be one of 'normal', 'bold', 'italic' or 'bold_italic'. If
+ * the particular font family has no suitable font file, the default font
+ * ({@link Options::defaultFont}) is used. The font file returned
+ * is the absolute pathname to the font file on the system.
+ *
+ * @param string|null $familyRaw
+ * @param string $subtypeRaw
+ *
+ * @return string|null
+ */
+ public function getFont($familyRaw, $subtypeRaw = "normal")
+ {
+ static $cache = [];
+
+ if (isset($cache[$familyRaw][$subtypeRaw])) {
+ return $cache[$familyRaw][$subtypeRaw];
+ }
+
+ /* Allow calling for various fonts in search path. Therefore not immediately
+ * return replacement on non match.
+ * Only when called with NULL try replacement.
+ * When this is also missing there is really trouble.
+ * If only the subtype fails, nevertheless return failure.
+ * Only on checking the fallback font, check various subtypes on same font.
+ */
+
+ $subtype = strtolower($subtypeRaw);
+
+ $families = $this->getFontFamilies();
+ if ($familyRaw) {
+ $family = str_replace(["'", '"'], "", strtolower($familyRaw));
+
+ if (isset($families[$family][$subtype])) {
+ return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
+ }
+
+ return null;
+ }
+
+ $fallback_families = [strtolower($this->options->getDefaultFont()), "serif"];
+ foreach ($fallback_families as $family) {
+ if (isset($families[$family][$subtype])) {
+ return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
+ }
+
+ if (!isset($families[$family])) {
+ continue;
+ }
+
+ $family = $families[$family];
+
+ foreach ($family as $sub => $font) {
+ if (strpos($subtype, $sub) !== false) {
+ return $cache[$familyRaw][$subtypeRaw] = $font;
+ }
+ }
+
+ if ($subtype !== "normal") {
+ foreach ($family as $sub => $font) {
+ if ($sub !== "normal") {
+ return $cache[$familyRaw][$subtypeRaw] = $font;
+ }
+ }
+ }
+
+ $subtype = "normal";
+
+ if (isset($family[$subtype])) {
+ return $cache[$familyRaw][$subtypeRaw] = $family[$subtype];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $family
+ * @return null|string
+ * @deprecated
+ */
+ public function get_family($family)
+ {
+ return $this->getFamily($family);
+ }
+
+ /**
+ * @param string $family
+ * @return null|string
+ */
+ public function getFamily($family)
+ {
+ $family = str_replace(["'", '"'], "", mb_strtolower($family));
+ $families = $this->getFontFamilies();
+
+ if (isset($families[$family])) {
+ return $families[$family];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $type
+ * @return string
+ * @deprecated
+ */
+ public function get_type($type)
+ {
+ return $this->getType($type);
+ }
+
+ /**
+ * @param string $type
+ * @return string
+ */
+ public function getType($type)
+ {
+ if (preg_match('/bold/i', $type)) {
+ $weight = 700;
+ } elseif (preg_match('/([1-9]00)/', $type, $match)) {
+ $weight = (int)$match[0];
+ } else {
+ $weight = 400;
+ }
+ $weight = $weight === 400 ? 'normal' : $weight;
+ $weight = $weight === 700 ? 'bold' : $weight;
+
+ $style = preg_match('/italic|oblique/i', $type) ? 'italic' : null;
+
+ if ($weight === 'normal' && $style !== null) {
+ return $style;
+ }
+
+ return $style === null
+ ? $weight
+ : $weight.'_'.$style;
+ }
+
+ /**
+ * @return array
+ * @deprecated
+ */
+ public function get_font_families()
+ {
+ return $this->getFontFamilies();
+ }
+
+ /**
+ * Returns the current font lookup table
+ *
+ * @return array
+ */
+ public function getFontFamilies()
+ {
+ if (!isset($this->fontFamilies)) {
+ $this->setFontFamilies();
+ }
+ return $this->fontFamilies;
+ }
+
+ /**
+ * Convert loaded fonts to font lookup table
+ *
+ * @return array
+ */
+ public function setFontFamilies()
+ {
+ $fontFamilies = [];
+ if (isset($this->bundledFonts) && is_array($this->bundledFonts)) {
+ foreach ($this->bundledFonts as $family => $variants) {
+ if (!isset($fontFamilies[$family])) {
+ $fontFamilies[$family] = array_map(function ($variant) {
+ return $this->getOptions()->getRootDir() . '/lib/fonts/' . $variant;
+ }, $variants);
+ }
+ }
+ }
+ if (isset($this->userFonts) && is_array($this->userFonts)) {
+ foreach ($this->userFonts as $family => $variants) {
+ $fontFamilies[$family] = array_map(function ($variant) {
+ $variantName = basename($variant);
+ if ($variantName === $variant) {
+ return $this->getOptions()->getFontDir() . '/' . $variant;
+ }
+ return $variant;
+ }, $variants);
+ }
+ }
+ $this->fontFamilies = $fontFamilies;
+ }
+
+ /**
+ * @param string $fontname
+ * @param mixed $entry
+ * @deprecated
+ */
+ public function set_font_family($fontname, $entry)
+ {
+ $this->setFontFamily($fontname, $entry);
+ }
+
+ /**
+ * @param string $fontname
+ * @param mixed $entry
+ */
+ public function setFontFamily($fontname, $entry)
+ {
+ $this->userFonts[mb_strtolower($fontname)] = $entry;
+ $this->saveFontFamilies();
+ unset($this->fontFamilies);
+ }
+
+ /**
+ * @return string
+ */
+ public function getUserFontsFilePath()
+ {
+ return $this->options->getFontDir() . '/' . self::USER_FONTS_FILE;
+ }
+
+ /**
+ * @param Options $options
+ * @return $this
+ */
+ public function setOptions(Options $options)
+ {
+ $this->options = $options;
+ unset($this->fontFamilies);
+ return $this;
+ }
+
+ /**
+ * @return Options
+ */
+ public function getOptions()
+ {
+ return $this->options;
+ }
+
+ /**
+ * @param Canvas $canvas
+ * @return $this
+ */
+ public function setCanvas(Canvas $canvas)
+ {
+ $this->canvas = $canvas;
+ return $this;
+ }
+
+ /**
+ * @return Canvas
+ */
+ public function getCanvas()
+ {
+ return $this->canvas;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame.php
new file mode 100644
index 0000000..55136b2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame.php
@@ -0,0 +1,1217 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+use Dompdf\Css\Style;
+use Dompdf\Frame\FrameListIterator;
+
+/**
+ * The main Frame class
+ *
+ * This class represents a single HTML element. This class stores
+ * positioning information as well as containing block location and
+ * dimensions. Style information for the element is stored in a {@link
+ * Style} object. Tree structure is maintained via the parent & children
+ * links.
+ *
+ * @package dompdf
+ */
+class Frame
+{
+ const WS_TEXT = 1;
+ const WS_SPACE = 2;
+
+ /**
+ * The DOMElement or DOMText object this frame represents
+ *
+ * @var \DOMElement|\DOMText
+ */
+ protected $_node;
+
+ /**
+ * Unique identifier for this frame. Used to reference this frame
+ * via the node.
+ *
+ * @var int
+ */
+ protected $_id;
+
+ /**
+ * Unique id counter
+ *
+ * @var int
+ */
+ public static $ID_COUNTER = 0; /*protected*/
+
+ /**
+ * This frame's calculated style
+ *
+ * @var Style
+ */
+ protected $_style;
+
+ /**
+ * This frame's parent in the document tree.
+ *
+ * @var Frame
+ */
+ protected $_parent;
+
+ /**
+ * This frame's first child. All children are handled as a
+ * doubly-linked list.
+ *
+ * @var Frame
+ */
+ protected $_first_child;
+
+ /**
+ * This frame's last child.
+ *
+ * @var Frame
+ */
+ protected $_last_child;
+
+ /**
+ * This frame's previous sibling in the document tree.
+ *
+ * @var Frame
+ */
+ protected $_prev_sibling;
+
+ /**
+ * This frame's next sibling in the document tree.
+ *
+ * @var Frame
+ */
+ protected $_next_sibling;
+
+ /**
+ * This frame's containing block (used in layout): array(x, y, w, h)
+ *
+ * @var float[]
+ */
+ protected $_containing_block;
+
+ /**
+ * Position on the page of the top-left corner of the margin box of
+ * this frame: array(x,y)
+ *
+ * @var float[]
+ */
+ protected $_position;
+
+ /**
+ * Absolute opacity of this frame
+ *
+ * @var float
+ */
+ protected $_opacity;
+
+ /**
+ * This frame's decorator
+ *
+ * @var FrameDecorator\AbstractFrameDecorator
+ */
+ protected $_decorator;
+
+ /**
+ * This frame's containing line box
+ *
+ * @var LineBox|null
+ */
+ protected $_containing_line;
+
+ /**
+ * @var array
+ */
+ protected $_is_cache = [];
+
+ /**
+ * Tells whether the frame was already pushed to the next page
+ *
+ * @var bool
+ */
+ public $_already_pushed = false;
+
+ /**
+ * @var bool
+ */
+ public $_float_next_line = false;
+
+ /**
+ * @var int
+ */
+ public static $_ws_state = self::WS_SPACE;
+
+ /**
+ * Class constructor
+ *
+ * @param \DOMNode $node the DOMNode this frame represents
+ */
+ public function __construct(\DOMNode $node)
+ {
+ $this->_node = $node;
+
+ $this->_parent = null;
+ $this->_first_child = null;
+ $this->_last_child = null;
+ $this->_prev_sibling = $this->_next_sibling = null;
+
+ $this->_style = null;
+
+ $this->_containing_block = [
+ "x" => null,
+ "y" => null,
+ "w" => null,
+ "h" => null,
+ ];
+
+ $this->_containing_block[0] =& $this->_containing_block["x"];
+ $this->_containing_block[1] =& $this->_containing_block["y"];
+ $this->_containing_block[2] =& $this->_containing_block["w"];
+ $this->_containing_block[3] =& $this->_containing_block["h"];
+
+ $this->_position = [
+ "x" => null,
+ "y" => null,
+ ];
+
+ $this->_position[0] =& $this->_position["x"];
+ $this->_position[1] =& $this->_position["y"];
+
+ $this->_opacity = 1.0;
+ $this->_decorator = null;
+
+ $this->set_id(self::$ID_COUNTER++);
+ }
+
+ /**
+ * WIP : preprocessing to remove all the unused whitespace
+ */
+ protected function ws_trim()
+ {
+ if ($this->ws_keep()) {
+ return;
+ }
+
+ if (self::$_ws_state === self::WS_SPACE) {
+ $node = $this->_node;
+
+ if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
+ $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue));
+ self::$_ws_state = self::WS_TEXT;
+ }
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ protected function ws_keep()
+ {
+ $whitespace = $this->get_style()->white_space;
+
+ return in_array($whitespace, ["pre", "pre-wrap", "pre-line"]);
+ }
+
+ /**
+ * @return bool
+ */
+ protected function ws_is_text()
+ {
+ $node = $this->get_node();
+
+ if ($node->nodeName === "img") {
+ return true;
+ }
+
+ if (!$this->is_in_flow()) {
+ return false;
+ }
+
+ if ($this->is_text_node()) {
+ return trim($node->nodeValue) !== "";
+ }
+
+ return true;
+ }
+
+ /**
+ * "Destructor": forcibly free all references held by this frame
+ *
+ * @param bool $recursive if true, call dispose on all children
+ */
+ public function dispose($recursive = false)
+ {
+ if ($recursive) {
+ while ($child = $this->_first_child) {
+ $child->dispose(true);
+ }
+ }
+
+ // Remove this frame from the tree
+ if ($this->_prev_sibling) {
+ $this->_prev_sibling->_next_sibling = $this->_next_sibling;
+ }
+
+ if ($this->_next_sibling) {
+ $this->_next_sibling->_prev_sibling = $this->_prev_sibling;
+ }
+
+ if ($this->_parent && $this->_parent->_first_child === $this) {
+ $this->_parent->_first_child = $this->_next_sibling;
+ }
+
+ if ($this->_parent && $this->_parent->_last_child === $this) {
+ $this->_parent->_last_child = $this->_prev_sibling;
+ }
+
+ if ($this->_parent) {
+ $this->_parent->get_node()->removeChild($this->_node);
+ }
+
+ $this->_style = null;
+ unset($this->_style);
+ }
+
+ /**
+ * Re-initialize the frame
+ */
+ public function reset()
+ {
+ $this->_position["x"] = null;
+ $this->_position["y"] = null;
+
+ $this->_containing_block["x"] = null;
+ $this->_containing_block["y"] = null;
+ $this->_containing_block["w"] = null;
+ $this->_containing_block["h"] = null;
+
+ $this->_style->reset();
+ }
+
+ /**
+ * @return \DOMElement|\DOMText
+ */
+ public function get_node()
+ {
+ return $this->_node;
+ }
+
+ /**
+ * @return int
+ */
+ public function get_id()
+ {
+ return $this->_id;
+ }
+
+ /**
+ * @return Style
+ */
+ public function get_style()
+ {
+ return $this->_style;
+ }
+
+ /**
+ * @deprecated
+ * @return Style
+ */
+ public function get_original_style()
+ {
+ return $this->_style;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_parent()
+ {
+ return $this->_parent;
+ }
+
+ /**
+ * @return FrameDecorator\AbstractFrameDecorator
+ */
+ public function get_decorator()
+ {
+ return $this->_decorator;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_first_child()
+ {
+ return $this->_first_child;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_last_child()
+ {
+ return $this->_last_child;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_prev_sibling()
+ {
+ return $this->_prev_sibling;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function get_next_sibling()
+ {
+ return $this->_next_sibling;
+ }
+
+ /**
+ * @return FrameListIterator
+ */
+ public function get_children(): FrameListIterator
+ {
+ return new FrameListIterator($this);
+ }
+
+ // Layout property accessors
+
+ /**
+ * Containing block dimensions
+ *
+ * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h)
+ *
+ * @return float[]|float
+ */
+ public function get_containing_block($i = null)
+ {
+ if (isset($i)) {
+ return $this->_containing_block[$i];
+ }
+
+ return $this->_containing_block;
+ }
+
+ /**
+ * Block position
+ *
+ * @param string|null $i The key of the wanted position value (x, y)
+ *
+ * @return float[]|float
+ */
+ public function get_position($i = null)
+ {
+ if (isset($i)) {
+ return $this->_position[$i];
+ }
+
+ return $this->_position;
+ }
+
+ //........................................................................
+
+ /**
+ * Return the width of the margin box of the frame, in pt. Meaningless
+ * unless the width has been calculated properly.
+ *
+ * @return float
+ */
+ public function get_margin_width(): float
+ {
+ $style = $this->_style;
+
+ return (float)$style->length_in_pt([
+ $style->width,
+ $style->margin_left,
+ $style->margin_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right
+ ], $this->_containing_block["w"]);
+ }
+
+ /**
+ * Return the height of the margin box of the frame, in pt. Meaningless
+ * unless the height has been calculated properly.
+ *
+ * @return float
+ */
+ public function get_margin_height(): float
+ {
+ $style = $this->_style;
+
+ return (float)$style->length_in_pt(
+ [
+ $style->height,
+ (float)$style->length_in_pt(
+ [
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->margin_top,
+ $style->margin_bottom,
+ $style->padding_top,
+ $style->padding_bottom
+ ], $this->_containing_block["w"]
+ )
+ ],
+ $this->_containing_block["h"]
+ );
+ }
+
+ /**
+ * Return the content box (x,y,w,h) of the frame.
+ *
+ * Width and height might be reported as 0 if they have not been resolved
+ * yet.
+ *
+ * @return float[]
+ */
+ public function get_content_box(): array
+ {
+ $style = $this->_style;
+ $cb = $this->_containing_block;
+
+ $x = $this->_position["x"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_left,
+ $style->border_left_width,
+ $style->padding_left
+ ],
+ $cb["w"]
+ );
+
+ $y = $this->_position["y"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_top,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cb["w"]
+ );
+
+ $w = (float)$style->length_in_pt($style->width, $cb["w"]);
+
+ $h = (float)$style->length_in_pt($style->height, $cb["h"]);
+
+ return [0 => $x, "x" => $x,
+ 1 => $y, "y" => $y,
+ 2 => $w, "w" => $w,
+ 3 => $h, "h" => $h];
+ }
+
+ /**
+ * Return the padding box (x,y,w,h) of the frame.
+ *
+ * Width and height might be reported as 0 if they have not been resolved
+ * yet.
+ *
+ * @return float[]
+ */
+ public function get_padding_box(): array
+ {
+ $style = $this->_style;
+ $cb = $this->_containing_block;
+
+ $x = $this->_position["x"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_left,
+ $style->border_left_width
+ ],
+ $cb["w"]
+ );
+
+ $y = $this->_position["y"] +
+ (float)$style->length_in_pt(
+ [
+ $style->margin_top,
+ $style->border_top_width
+ ],
+ $cb["h"]
+ );
+
+ $w = (float)$style->length_in_pt(
+ [
+ $style->padding_left,
+ $style->width,
+ $style->padding_right
+ ],
+ $cb["w"]
+ );
+
+ $h = (float)$style->length_in_pt(
+ [
+ $style->padding_top,
+ $style->padding_bottom,
+ $style->length_in_pt($style->height, $cb["h"])
+ ],
+ $cb["w"]
+ );
+
+ return [0 => $x, "x" => $x,
+ 1 => $y, "y" => $y,
+ 2 => $w, "w" => $w,
+ 3 => $h, "h" => $h];
+ }
+
+ /**
+ * Return the border box of the frame.
+ *
+ * Width and height might be reported as 0 if they have not been resolved
+ * yet.
+ *
+ * @return float[]
+ */
+ public function get_border_box(): array
+ {
+ $style = $this->_style;
+ $cb = $this->_containing_block;
+
+ $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
+
+ $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
+
+ $w = (float)$style->length_in_pt(
+ [
+ $style->border_left_width,
+ $style->padding_left,
+ $style->width,
+ $style->padding_right,
+ $style->border_right_width
+ ],
+ $cb["w"]
+ );
+
+ $h = (float)$style->length_in_pt(
+ [
+ $style->border_top_width,
+ $style->padding_top,
+ $style->padding_bottom,
+ $style->border_bottom_width,
+ $style->length_in_pt($style->height, $cb["h"])
+ ],
+ $cb["w"]
+ );
+
+ return [0 => $x, "x" => $x,
+ 1 => $y, "y" => $y,
+ 2 => $w, "w" => $w,
+ 3 => $h, "h" => $h];
+ }
+
+ /**
+ * @param float|null $opacity
+ *
+ * @return float
+ */
+ public function get_opacity(?float $opacity = null): float
+ {
+ if ($opacity !== null) {
+ $this->set_opacity($opacity);
+ }
+
+ return $this->_opacity;
+ }
+
+ /**
+ * @return LineBox|null
+ */
+ public function &get_containing_line()
+ {
+ return $this->_containing_line;
+ }
+
+ //........................................................................
+ // Set methods
+
+ /**
+ * @param int $id
+ */
+ public function set_id($id)
+ {
+ $this->_id = $id;
+
+ // We can only set attributes of DOMElement objects (nodeType == 1).
+ // Since these are the only objects that we can assign CSS rules to,
+ // this shortcoming is okay.
+ if ($this->_node->nodeType == XML_ELEMENT_NODE) {
+ $this->_node->setAttribute("frame_id", $id);
+ }
+ }
+
+ /**
+ * @param Style $style
+ */
+ public function set_style(Style $style): void
+ {
+ // $style->set_frame($this);
+ $this->_style = $style;
+ }
+
+ /**
+ * @param FrameDecorator\AbstractFrameDecorator $decorator
+ */
+ public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
+ {
+ $this->_decorator = $decorator;
+ }
+
+ /**
+ * @param float|float[]|null $x
+ * @param float|null $y
+ * @param float|null $w
+ * @param float|null $h
+ */
+ public function set_containing_block($x = null, $y = null, $w = null, $h = null)
+ {
+ if (is_array($x)) {
+ foreach ($x as $key => $val) {
+ $$key = $val;
+ }
+ }
+
+ if (is_numeric($x)) {
+ $this->_containing_block["x"] = $x;
+ }
+
+ if (is_numeric($y)) {
+ $this->_containing_block["y"] = $y;
+ }
+
+ if (is_numeric($w)) {
+ $this->_containing_block["w"] = $w;
+ }
+
+ if (is_numeric($h)) {
+ $this->_containing_block["h"] = $h;
+ }
+ }
+
+ /**
+ * @param float|float[]|null $x
+ * @param float|null $y
+ */
+ public function set_position($x = null, $y = null)
+ {
+ if (is_array($x)) {
+ list($x, $y) = [$x["x"], $x["y"]];
+ }
+
+ if (is_numeric($x)) {
+ $this->_position["x"] = $x;
+ }
+
+ if (is_numeric($y)) {
+ $this->_position["y"] = $y;
+ }
+ }
+
+ /**
+ * @param float $opacity
+ */
+ public function set_opacity(float $opacity): void
+ {
+ $parent = $this->get_parent();
+ $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
+ $this->_opacity = $base_opacity * $opacity;
+ }
+
+ /**
+ * @param LineBox $line
+ */
+ public function set_containing_line(LineBox $line)
+ {
+ $this->_containing_line = $line;
+ }
+
+ /**
+ * Indicates if the margin height is auto sized
+ *
+ * @return bool
+ */
+ public function is_auto_height()
+ {
+ $style = $this->_style;
+
+ return in_array(
+ "auto",
+ [
+ $style->height,
+ $style->margin_top,
+ $style->margin_bottom,
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->padding_top,
+ $style->padding_bottom,
+ $this->_containing_block["h"]
+ ],
+ true
+ );
+ }
+
+ /**
+ * Indicates if the margin width is auto sized
+ *
+ * @return bool
+ */
+ public function is_auto_width()
+ {
+ $style = $this->_style;
+
+ return in_array(
+ "auto",
+ [
+ $style->width,
+ $style->margin_left,
+ $style->margin_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right,
+ $this->_containing_block["w"]
+ ],
+ true
+ );
+ }
+
+ /**
+ * Tells if the frame is a text node
+ *
+ * @return bool
+ */
+ public function is_text_node(): bool
+ {
+ if (isset($this->_is_cache["text_node"])) {
+ return $this->_is_cache["text_node"];
+ }
+
+ return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text");
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_positioned(): bool
+ {
+ if (isset($this->_is_cache["positioned"])) {
+ return $this->_is_cache["positioned"];
+ }
+
+ $position = $this->get_style()->position;
+
+ return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_absolute(): bool
+ {
+ if (isset($this->_is_cache["absolute"])) {
+ return $this->_is_cache["absolute"];
+ }
+
+ return $this->_is_cache["absolute"] = $this->get_style()->is_absolute();
+ }
+
+ /**
+ * Whether the frame is a block container.
+ *
+ * @return bool
+ */
+ public function is_block(): bool
+ {
+ if (isset($this->_is_cache["block"])) {
+ return $this->_is_cache["block"];
+ }
+
+ return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true);
+ }
+
+ /**
+ * Whether the frame has a block-level display type.
+ *
+ * @return bool
+ */
+ public function is_block_level(): bool
+ {
+ if (isset($this->_is_cache["block_level"])) {
+ return $this->_is_cache["block_level"];
+ }
+
+ $display = $this->get_style()->display;
+
+ return $this->_is_cache["block_level"] = in_array($display, Style::BLOCK_LEVEL_TYPES, true);
+ }
+
+ /**
+ * Whether the frame has an inline-level display type.
+ *
+ * @return bool
+ */
+ public function is_inline_level(): bool
+ {
+ if (isset($this->_is_cache["inline_level"])) {
+ return $this->_is_cache["inline_level"];
+ }
+
+ $display = $this->get_style()->display;
+
+ return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_in_flow(): bool
+ {
+ if (isset($this->_is_cache["in_flow"])) {
+ return $this->_is_cache["in_flow"];
+ }
+
+ return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow();
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_pre(): bool
+ {
+ if (isset($this->_is_cache["pre"])) {
+ return $this->_is_cache["pre"];
+ }
+
+ $white_space = $this->get_style()->white_space;
+
+ return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"], true);
+ }
+
+ /**
+ * @return bool
+ */
+ public function is_table(): bool
+ {
+ if (isset($this->_is_cache["table"])) {
+ return $this->_is_cache["table"];
+ }
+
+ $display = $this->get_style()->display;
+
+ return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true);
+ }
+
+
+ /**
+ * Inserts a new child at the beginning of the Frame
+ *
+ * @param Frame $child The new Frame to insert
+ * @param bool $update_node Whether or not to update the DOM
+ */
+ public function prepend_child(Frame $child, $update_node = true)
+ {
+ if ($update_node) {
+ $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
+ }
+
+ // Remove the child from its parent
+ if ($child->_parent) {
+ $child->_parent->remove_child($child, false);
+ }
+
+ $child->_parent = $this;
+ $child->_prev_sibling = null;
+
+ // Handle the first child
+ if (!$this->_first_child) {
+ $this->_first_child = $child;
+ $this->_last_child = $child;
+ $child->_next_sibling = null;
+ } else {
+ $this->_first_child->_prev_sibling = $child;
+ $child->_next_sibling = $this->_first_child;
+ $this->_first_child = $child;
+ }
+ }
+
+ /**
+ * Inserts a new child at the end of the Frame
+ *
+ * @param Frame $child The new Frame to insert
+ * @param bool $update_node Whether or not to update the DOM
+ */
+ public function append_child(Frame $child, $update_node = true)
+ {
+ if ($update_node) {
+ $this->_node->appendChild($child->_node);
+ }
+
+ // Remove the child from its parent
+ if ($child->_parent) {
+ $child->_parent->remove_child($child, false);
+ }
+
+ $child->_parent = $this;
+ $decorator = $child->get_decorator();
+ // force an update to the cached parent
+ if ($decorator !== null) {
+ $decorator->get_parent(false);
+ }
+ $child->_next_sibling = null;
+
+ // Handle the first child
+ if (!$this->_last_child) {
+ $this->_first_child = $child;
+ $this->_last_child = $child;
+ $child->_prev_sibling = null;
+ } else {
+ $this->_last_child->_next_sibling = $child;
+ $child->_prev_sibling = $this->_last_child;
+ $this->_last_child = $child;
+ }
+ }
+
+ /**
+ * Inserts a new child immediately before the specified frame
+ *
+ * @param Frame $new_child The new Frame to insert
+ * @param Frame $ref The Frame after the new Frame
+ * @param bool $update_node Whether or not to update the DOM
+ *
+ * @throws Exception
+ */
+ public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ if ($ref === $this->_first_child) {
+ $this->prepend_child($new_child, $update_node);
+
+ return;
+ }
+
+ if (is_null($ref)) {
+ $this->append_child($new_child, $update_node);
+
+ return;
+ }
+
+ if ($ref->_parent !== $this) {
+ throw new Exception("Reference child is not a child of this node.");
+ }
+
+ // Update the node
+ if ($update_node) {
+ $this->_node->insertBefore($new_child->_node, $ref->_node);
+ }
+
+ // Remove the child from its parent
+ if ($new_child->_parent) {
+ $new_child->_parent->remove_child($new_child, false);
+ }
+
+ $new_child->_parent = $this;
+ $new_child->_next_sibling = $ref;
+ $new_child->_prev_sibling = $ref->_prev_sibling;
+
+ if ($ref->_prev_sibling) {
+ $ref->_prev_sibling->_next_sibling = $new_child;
+ }
+
+ $ref->_prev_sibling = $new_child;
+ }
+
+ /**
+ * Inserts a new child immediately after the specified frame
+ *
+ * @param Frame $new_child The new Frame to insert
+ * @param Frame $ref The Frame before the new Frame
+ * @param bool $update_node Whether or not to update the DOM
+ *
+ * @throws Exception
+ */
+ public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ if ($ref === $this->_last_child) {
+ $this->append_child($new_child, $update_node);
+
+ return;
+ }
+
+ if (is_null($ref)) {
+ $this->prepend_child($new_child, $update_node);
+
+ return;
+ }
+
+ if ($ref->_parent !== $this) {
+ throw new Exception("Reference child is not a child of this node.");
+ }
+
+ // Update the node
+ if ($update_node) {
+ if ($ref->_next_sibling) {
+ $next_node = $ref->_next_sibling->_node;
+ $this->_node->insertBefore($new_child->_node, $next_node);
+ } else {
+ $new_child->_node = $this->_node->appendChild($new_child->_node);
+ }
+ }
+
+ // Remove the child from its parent
+ if ($new_child->_parent) {
+ $new_child->_parent->remove_child($new_child, false);
+ }
+
+ $new_child->_parent = $this;
+ $new_child->_prev_sibling = $ref;
+ $new_child->_next_sibling = $ref->_next_sibling;
+
+ if ($ref->_next_sibling) {
+ $ref->_next_sibling->_prev_sibling = $new_child;
+ }
+
+ $ref->_next_sibling = $new_child;
+ }
+
+ /**
+ * Remove a child frame
+ *
+ * @param Frame $child
+ * @param bool $update_node Whether or not to remove the DOM node
+ *
+ * @throws Exception
+ * @return Frame The removed child frame
+ */
+ public function remove_child(Frame $child, $update_node = true)
+ {
+ if ($child->_parent !== $this) {
+ throw new Exception("Child not found in this frame");
+ }
+
+ if ($update_node) {
+ $this->_node->removeChild($child->_node);
+ }
+
+ if ($child === $this->_first_child) {
+ $this->_first_child = $child->_next_sibling;
+ }
+
+ if ($child === $this->_last_child) {
+ $this->_last_child = $child->_prev_sibling;
+ }
+
+ if ($child->_prev_sibling) {
+ $child->_prev_sibling->_next_sibling = $child->_next_sibling;
+ }
+
+ if ($child->_next_sibling) {
+ $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
+ }
+
+ $child->_next_sibling = null;
+ $child->_prev_sibling = null;
+ $child->_parent = null;
+
+ return $child;
+ }
+
+ //........................................................................
+
+ // Debugging function:
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ // Skip empty text frames
+// if ( $this->is_text_node() &&
+// preg_replace("/\s/", "", $this->_node->data) === "" )
+// return "";
+
+
+ $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
+ //$str .= spl_object_hash($this->_node) . "<br/>";
+ $str .= "Id: " . $this->get_id() . "<br/>";
+ $str .= "Class: " . get_class($this) . "<br/>";
+
+ if ($this->is_text_node()) {
+ $tmp = htmlspecialchars($this->_node->nodeValue);
+ $str .= "<pre>'" . mb_substr($tmp, 0, 70) .
+ (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
+ } elseif ($css_class = $this->_node->getAttribute("class")) {
+ $str .= "CSS class: '$css_class'<br/>";
+ }
+
+ if ($this->_parent) {
+ $str .= "\nParent:" . $this->_parent->_node->nodeName .
+ " (" . spl_object_hash($this->_parent->_node) . ") " .
+ "<br/>";
+ }
+
+ if ($this->_prev_sibling) {
+ $str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
+ " (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
+ "<br/>";
+ }
+
+ if ($this->_next_sibling) {
+ $str .= "Next: " . $this->_next_sibling->_node->nodeName .
+ " (" . spl_object_hash($this->_next_sibling->_node) . ") " .
+ "<br/>";
+ }
+
+ $d = $this->get_decorator();
+ while ($d && $d != $d->get_decorator()) {
+ $str .= "Decorator: " . get_class($d) . "<br/>";
+ $d = $d->get_decorator();
+ }
+
+ $str .= "Position: " . Helpers::pre_r($this->_position, true);
+ $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
+ $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
+ $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
+
+ $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
+
+ if ($this->_decorator instanceof FrameDecorator\Block) {
+ $str .= "Lines:<pre>";
+ foreach ($this->_decorator->get_line_boxes() as $line) {
+ foreach ($line->get_frames() as $frame) {
+ if ($frame instanceof FrameDecorator\Text) {
+ $str .= "\ntext: ";
+ $str .= "'" . htmlspecialchars($frame->get_text()) . "'";
+ } else {
+ $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
+ }
+ }
+
+ $str .=
+ "\ny => " . $line->y . "\n" .
+ "w => " . $line->w . "\n" .
+ "h => " . $line->h . "\n" .
+ "left => " . $line->left . "\n" .
+ "right => " . $line->right . "\n";
+ }
+ $str .= "</pre>";
+ }
+
+ $str .= "\n";
+ if (php_sapi_name() === "cli") {
+ $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
+ ["\n", "", ""],
+ $str));
+ }
+
+ return $str;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php
new file mode 100644
index 0000000..b4bab88
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/Factory.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Frame;
+
+use Dompdf\Dompdf;
+use Dompdf\Exception;
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use DOMXPath;
+use Dompdf\FrameDecorator\Page as PageFrameDecorator;
+use Dompdf\FrameReflower\Page as PageFrameReflower;
+use Dompdf\Positioner\AbstractPositioner;
+
+/**
+ * Contains frame decorating logic
+ *
+ * This class is responsible for assigning the correct {@link AbstractFrameDecorator},
+ * {@link AbstractPositioner}, and {@link AbstractFrameReflower} objects to {@link Frame}
+ * objects. This is determined primarily by the Frame's display type, but
+ * also by the Frame's node's type (e.g. DomElement vs. #text)
+ *
+ * @package dompdf
+ */
+class Factory
+{
+
+ /**
+ * Array of positioners for specific frame types
+ *
+ * @var AbstractPositioner[]
+ */
+ protected static $_positioners;
+
+ /**
+ * Decorate the root Frame
+ *
+ * @param $root Frame The frame to decorate
+ * @param $dompdf Dompdf The dompdf instance
+ *
+ * @return PageFrameDecorator
+ */
+ static function decorate_root(Frame $root, Dompdf $dompdf)
+ {
+ $frame = new PageFrameDecorator($root, $dompdf);
+ $frame->set_reflower(new PageFrameReflower($frame));
+ $root->set_decorator($frame);
+
+ return $frame;
+ }
+
+ /**
+ * Decorate a Frame
+ *
+ * @param Frame $frame The frame to decorate
+ * @param Dompdf $dompdf The dompdf instance
+ * @param Frame $root The root of the frame
+ *
+ * @throws Exception
+ * @return AbstractFrameDecorator
+ * FIXME: this is admittedly a little smelly...
+ */
+ static function decorate_frame(Frame $frame, Dompdf $dompdf, Frame $root = null)
+ {
+ $style = $frame->get_style();
+ $display = $style->display;
+
+ switch ($display) {
+
+ case "block":
+ $positioner = "Block";
+ $decorator = "Block";
+ $reflower = "Block";
+ break;
+
+ case "inline-block":
+ $positioner = "Inline";
+ $decorator = "Block";
+ $reflower = "Block";
+ break;
+
+ case "inline":
+ $positioner = "Inline";
+ if ($frame->is_text_node()) {
+ $decorator = "Text";
+ $reflower = "Text";
+ } else {
+ $decorator = "Inline";
+ $reflower = "Inline";
+ }
+ break;
+
+ case "table":
+ $positioner = "Block";
+ $decorator = "Table";
+ $reflower = "Table";
+ break;
+
+ case "inline-table":
+ $positioner = "Inline";
+ $decorator = "Table";
+ $reflower = "Table";
+ break;
+
+ case "table-row-group":
+ case "table-header-group":
+ case "table-footer-group":
+ $positioner = "NullPositioner";
+ $decorator = "TableRowGroup";
+ $reflower = "TableRowGroup";
+ break;
+
+ case "table-row":
+ $positioner = "NullPositioner";
+ $decorator = "TableRow";
+ $reflower = "TableRow";
+ break;
+
+ case "table-cell":
+ $positioner = "TableCell";
+ $decorator = "TableCell";
+ $reflower = "TableCell";
+ break;
+
+ case "list-item":
+ $positioner = "Block";
+ $decorator = "Block";
+ $reflower = "Block";
+ break;
+
+ case "-dompdf-list-bullet":
+ if ($style->list_style_position === "inside") {
+ $positioner = "Inline";
+ } else {
+ $positioner = "ListBullet";
+ }
+
+ if ($style->list_style_image !== "none") {
+ $decorator = "ListBulletImage";
+ } else {
+ $decorator = "ListBullet";
+ }
+
+ $reflower = "ListBullet";
+ break;
+
+ case "-dompdf-image":
+ $positioner = "Inline";
+ $decorator = "Image";
+ $reflower = "Image";
+ break;
+
+ case "-dompdf-br":
+ $positioner = "Inline";
+ $decorator = "Inline";
+ $reflower = "Inline";
+ break;
+
+ default:
+ case "none":
+ if ($style->_dompdf_keep !== "yes") {
+ // Remove the node and the frame
+ $frame->get_parent()->remove_child($frame);
+ return;
+ }
+
+ $positioner = "NullPositioner";
+ $decorator = "NullFrameDecorator";
+ $reflower = "NullFrameReflower";
+ break;
+ }
+
+ // Handle CSS position
+ $position = $style->position;
+
+ if ($position === "absolute") {
+ $positioner = "Absolute";
+ } else {
+ if ($position === "fixed") {
+ $positioner = "Fixed";
+ }
+ }
+
+ $node = $frame->get_node();
+
+ // Handle nodeName
+ if ($node->nodeName === "img") {
+ $style->set_prop("display", "-dompdf-image");
+ $decorator = "Image";
+ $reflower = "Image";
+ }
+
+ $decorator = "Dompdf\\FrameDecorator\\$decorator";
+ $reflower = "Dompdf\\FrameReflower\\$reflower";
+
+ /** @var AbstractFrameDecorator $deco */
+ $deco = new $decorator($frame, $dompdf);
+
+ $deco->set_positioner(self::getPositionerInstance($positioner));
+ $deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics()));
+
+ if ($root) {
+ $deco->set_root($root);
+ }
+
+ if ($display === "list-item") {
+ // Insert a list-bullet frame
+ $xml = $dompdf->getDom();
+ $bullet_node = $xml->createElement("bullet"); // arbitrary choice
+ $b_f = new Frame($bullet_node);
+
+ $node = $frame->get_node();
+ $parent_node = $node->parentNode;
+ if ($parent_node && $parent_node instanceof \DOMElement) {
+ if (!$parent_node->hasAttribute("dompdf-children-count")) {
+ $xpath = new DOMXPath($xml);
+ $count = $xpath->query("li", $parent_node)->length;
+ $parent_node->setAttribute("dompdf-children-count", $count);
+ }
+
+ if (is_numeric($node->getAttribute("value"))) {
+ $index = intval($node->getAttribute("value"));
+ } else {
+ if (!$parent_node->hasAttribute("dompdf-counter")) {
+ $index = ($parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1);
+ } else {
+ $index = (int)$parent_node->getAttribute("dompdf-counter") + 1;
+ }
+ }
+
+ $parent_node->setAttribute("dompdf-counter", $index);
+ $bullet_node->setAttribute("dompdf-counter", $index);
+ }
+
+ $new_style = $dompdf->getCss()->create_style();
+ $new_style->set_prop("display", "-dompdf-list-bullet");
+ $new_style->inherit($style);
+ $b_f->set_style($new_style);
+
+ $deco->prepend_child(Factory::decorate_frame($b_f, $dompdf, $root));
+ }
+
+ return $deco;
+ }
+
+ /**
+ * Creates Positioners
+ *
+ * @param string $type type of positioner to use
+ * @return AbstractPositioner
+ */
+ protected static function getPositionerInstance($type)
+ {
+ if (!isset(self::$_positioners[$type])) {
+ $class = '\\Dompdf\\Positioner\\'.$type;
+ self::$_positioners[$type] = new $class();
+ }
+ return self::$_positioners[$type];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php
new file mode 100644
index 0000000..0157550
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameListIterator.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Frame;
+
+use Iterator;
+use Dompdf\Frame;
+
+/**
+ * Linked-list Iterator
+ *
+ * Returns children in order and allows for the list to change during iteration,
+ * provided the changes occur to or after the current element.
+ *
+ * @package dompdf
+ */
+class FrameListIterator implements Iterator
+{
+ /**
+ * @var Frame
+ */
+ protected $parent;
+
+ /**
+ * @var Frame|null
+ */
+ protected $cur;
+
+ /**
+ * @var Frame|null
+ */
+ protected $prev;
+
+ /**
+ * @var int
+ */
+ protected $num;
+
+ /**
+ * @param Frame $frame
+ */
+ public function __construct(Frame $frame)
+ {
+ $this->parent = $frame;
+ $this->rewind();
+ }
+
+ public function rewind(): void
+ {
+ $this->cur = $this->parent->get_first_child();
+ $this->prev = null;
+ $this->num = 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return $this->cur !== null;
+ }
+
+ /**
+ * @return int
+ */
+ public function key(): int
+ {
+ return $this->num;
+ }
+
+ /**
+ * @return Frame|null
+ */
+ public function current(): ?Frame
+ {
+ return $this->cur;
+ }
+
+ public function next(): void
+ {
+ if ($this->cur === null) {
+ return;
+ }
+
+ if ($this->cur->get_parent() === $this->parent) {
+ $this->prev = $this->cur;
+ $this->cur = $this->cur->get_next_sibling();
+ $this->num++;
+ } else {
+ // Continue from the previous child if the current frame has been
+ // moved to another parent
+ $this->cur = $this->prev !== null
+ ? $this->prev->get_next_sibling()
+ : $this->parent->get_first_child();
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php
new file mode 100644
index 0000000..6d012d8
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTree.php
@@ -0,0 +1,324 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Frame;
+
+use DOMDocument;
+use DOMNode;
+use DOMElement;
+use DOMXPath;
+
+use Dompdf\Exception;
+use Dompdf\Frame;
+use IteratorAggregate;
+
+/**
+ * Represents an entire document as a tree of frames
+ *
+ * The FrameTree consists of {@link Frame} objects each tied to specific
+ * DOMNode objects in a specific DomDocument. The FrameTree has the same
+ * structure as the DomDocument, but adds additional capabilities for
+ * styling and layout.
+ *
+ * @package dompdf
+ */
+class FrameTree implements IteratorAggregate
+{
+ /**
+ * Tags to ignore while parsing the tree
+ *
+ * @var array
+ */
+ protected static $HIDDEN_TAGS = [
+ "area",
+ "base",
+ "basefont",
+ "head",
+ "style",
+ "meta",
+ "title",
+ "colgroup",
+ "noembed",
+ "param",
+ "#comment"
+ ];
+
+ /**
+ * The main DomDocument
+ *
+ * @see http://ca2.php.net/manual/en/ref.dom.php
+ * @var DOMDocument
+ */
+ protected $_dom;
+
+ /**
+ * The root node of the FrameTree.
+ *
+ * @var Frame
+ */
+ protected $_root;
+
+ /**
+ * Subtrees of absolutely positioned elements
+ *
+ * @var array of Frames
+ */
+ protected $_absolute_frames;
+
+ /**
+ * A mapping of {@link Frame} objects to DOMNode objects
+ *
+ * @var array
+ */
+ protected $_registry;
+
+ /**
+ * Class constructor
+ *
+ * @param DOMDocument $dom the main DomDocument object representing the current html document
+ */
+ public function __construct(DomDocument $dom)
+ {
+ $this->_dom = $dom;
+ $this->_root = null;
+ $this->_registry = [];
+ }
+
+ /**
+ * Returns the DOMDocument object representing the current html document
+ *
+ * @return DOMDocument
+ */
+ public function get_dom()
+ {
+ return $this->_dom;
+ }
+
+ /**
+ * Returns the root frame of the tree
+ *
+ * @return Frame
+ */
+ public function get_root()
+ {
+ return $this->_root;
+ }
+
+ /**
+ * Returns a specific frame given its id
+ *
+ * @param string $id
+ *
+ * @return Frame|null
+ */
+ public function get_frame($id)
+ {
+ return isset($this->_registry[$id]) ? $this->_registry[$id] : null;
+ }
+
+ /**
+ * Returns a post-order iterator for all frames in the tree
+ *
+ * @deprecated Iterate the tree directly instead
+ * @return FrameTreeIterator
+ */
+ public function get_frames(): FrameTreeIterator
+ {
+ return new FrameTreeIterator($this->_root);
+ }
+
+ /**
+ * Returns a post-order iterator for all frames in the tree
+ *
+ * @return FrameTreeIterator
+ */
+ public function getIterator(): FrameTreeIterator
+ {
+ return new FrameTreeIterator($this->_root);
+ }
+
+ /**
+ * Builds the tree
+ */
+ public function build_tree()
+ {
+ $html = $this->_dom->getElementsByTagName("html")->item(0);
+ if (is_null($html)) {
+ $html = $this->_dom->firstChild;
+ }
+
+ if (is_null($html)) {
+ throw new Exception("Requested HTML document contains no data.");
+ }
+
+ $this->fix_tables();
+
+ $this->_root = $this->_build_tree_r($html);
+ }
+
+ /**
+ * Adds missing TBODYs around TR
+ */
+ protected function fix_tables()
+ {
+ $xp = new DOMXPath($this->_dom);
+
+ // Move table caption before the table
+ // FIXME find a better way to deal with it...
+ $captions = $xp->query('//table/caption');
+ foreach ($captions as $caption) {
+ $table = $caption->parentNode;
+ $table->parentNode->insertBefore($caption, $table);
+ }
+
+ $firstRows = $xp->query('//table/tr[1]');
+ /** @var DOMElement $tableChild */
+ foreach ($firstRows as $tableChild) {
+ $tbody = $this->_dom->createElement('tbody');
+ $tableNode = $tableChild->parentNode;
+ do {
+ if ($tableChild->nodeName === 'tr') {
+ $tmpNode = $tableChild;
+ $tableChild = $tableChild->nextSibling;
+ $tableNode->removeChild($tmpNode);
+ $tbody->appendChild($tmpNode);
+ } else {
+ if ($tbody->hasChildNodes() === true) {
+ $tableNode->insertBefore($tbody, $tableChild);
+ $tbody = $this->_dom->createElement('tbody');
+ }
+ $tableChild = $tableChild->nextSibling;
+ }
+ } while ($tableChild);
+ if ($tbody->hasChildNodes() === true) {
+ $tableNode->appendChild($tbody);
+ }
+ }
+ }
+
+ // FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes
+ /**
+ * Remove a child from a node
+ *
+ * Remove a child from a node. If the removed node results in two
+ * adjacent #text nodes then combine them.
+ *
+ * @param DOMNode $node the current DOMNode being considered
+ * @param array $children an array of nodes that are the children of $node
+ * @param int $index index from the $children array of the node to remove
+ */
+ protected function _remove_node(DOMNode $node, array &$children, $index)
+ {
+ $child = $children[$index];
+ $previousChild = $child->previousSibling;
+ $nextChild = $child->nextSibling;
+ $node->removeChild($child);
+ if (isset($previousChild, $nextChild)) {
+ if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") {
+ $previousChild->nodeValue .= $nextChild->nodeValue;
+ $this->_remove_node($node, $children, $index+1);
+ }
+ }
+ array_splice($children, $index, 1);
+ }
+
+ /**
+ * Recursively adds {@link Frame} objects to the tree
+ *
+ * Recursively build a tree of Frame objects based on a dom tree.
+ * No layout information is calculated at this time, although the
+ * tree may be adjusted (i.e. nodes and frames for generated content
+ * and images may be created).
+ *
+ * @param DOMNode $node the current DOMNode being considered
+ *
+ * @return Frame
+ */
+ protected function _build_tree_r(DOMNode $node)
+ {
+ $frame = new Frame($node);
+ $id = $frame->get_id();
+ $this->_registry[$id] = $frame;
+
+ if (!$node->hasChildNodes()) {
+ return $frame;
+ }
+
+ // Store the children in an array so that the tree can be modified
+ $children = [];
+ $length = $node->childNodes->length;
+ for ($i = 0; $i < $length; $i++) {
+ $children[] = $node->childNodes->item($i);
+ }
+ $index = 0;
+ // INFO: We don't advance $index if a node is removed to avoid skipping nodes
+ while ($index < count($children)) {
+ $child = $children[$index];
+ $nodeName = strtolower($child->nodeName);
+
+ // Skip non-displaying nodes
+ if (in_array($nodeName, self::$HIDDEN_TAGS)) {
+ if ($nodeName !== "head" && $nodeName !== "style") {
+ $this->_remove_node($node, $children, $index);
+ } else {
+ $index++;
+ }
+ continue;
+ }
+ // Skip empty text nodes
+ if ($nodeName === "#text" && $child->nodeValue === "") {
+ $this->_remove_node($node, $children, $index);
+ continue;
+ }
+ // Skip empty image nodes
+ if ($nodeName === "img" && $child->getAttribute("src") === "") {
+ $this->_remove_node($node, $children, $index);
+ continue;
+ }
+
+ if (is_object($child)) {
+ $frame->append_child($this->_build_tree_r($child), false);
+ }
+ $index++;
+ }
+
+ return $frame;
+ }
+
+ /**
+ * @param DOMElement $node
+ * @param DOMElement $new_node
+ * @param string $pos
+ *
+ * @return mixed
+ */
+ public function insert_node(DOMElement $node, DOMElement $new_node, $pos)
+ {
+ if ($pos === "after" || !$node->firstChild) {
+ $node->appendChild($new_node);
+ } else {
+ $node->insertBefore($new_node, $node->firstChild);
+ }
+
+ $this->_build_tree_r($new_node);
+
+ $frame_id = $new_node->getAttribute("frame_id");
+ $frame = $this->get_frame($frame_id);
+
+ $parent_id = $node->getAttribute("frame_id");
+ $parent = $this->get_frame($parent_id);
+
+ if ($parent) {
+ if ($pos === "before") {
+ $parent->prepend_child($frame, false);
+ } else {
+ $parent->append_child($frame, false);
+ }
+ }
+
+ return $frame_id;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php
new file mode 100644
index 0000000..4da8da1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Frame;
+
+use Iterator;
+use Dompdf\Frame;
+
+/**
+ * Pre-order Iterator
+ *
+ * Returns frames in preorder traversal order (parent then children)
+ *
+ * @package dompdf
+ */
+class FrameTreeIterator implements Iterator
+{
+ /**
+ * @var Frame
+ */
+ protected $_root;
+
+ /**
+ * @var Frame[]
+ */
+ protected $_stack = [];
+
+ /**
+ * @var int
+ */
+ protected $_num;
+
+ /**
+ * @param Frame $root
+ */
+ public function __construct(Frame $root)
+ {
+ $this->_stack[] = $this->_root = $root;
+ $this->_num = 0;
+ }
+
+ public function rewind(): void
+ {
+ $this->_stack = [$this->_root];
+ $this->_num = 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return count($this->_stack) > 0;
+ }
+
+ /**
+ * @return int
+ */
+ public function key(): int
+ {
+ return $this->_num;
+ }
+
+ /**
+ * @return Frame
+ */
+ public function current(): Frame
+ {
+ return end($this->_stack);
+ }
+
+ public function next(): void
+ {
+ $b = array_pop($this->_stack);
+ $this->_num++;
+
+ // Push all children onto the stack in reverse order
+ if ($c = $b->get_last_child()) {
+ $this->_stack[] = $c;
+ while ($c = $c->get_prev_sibling()) {
+ $this->_stack[] = $c;
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php
new file mode 100644
index 0000000..2c1fcc5
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php
@@ -0,0 +1,923 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use DOMElement;
+use DOMNode;
+use Dompdf\Helpers;
+use Dompdf\Dompdf;
+use Dompdf\Exception;
+use Dompdf\Frame;
+use Dompdf\Frame\Factory;
+use Dompdf\Frame\FrameListIterator;
+use Dompdf\Frame\FrameTreeIterator;
+use Dompdf\FrameReflower\AbstractFrameReflower;
+use Dompdf\Css\Style;
+use Dompdf\Positioner\AbstractPositioner;
+
+/**
+ * Base AbstractFrameDecorator class
+ *
+ * @package dompdf
+ */
+abstract class AbstractFrameDecorator extends Frame
+{
+ const DEFAULT_COUNTER = "-dompdf-default-counter";
+
+ /**
+ * array([id] => counter_value) (for generated content)
+ *
+ * @var array
+ */
+ public $_counters = [];
+
+ /**
+ * The root node of the DOM tree
+ *
+ * @var Frame
+ */
+ protected $_root;
+
+ /**
+ * The decorated frame
+ *
+ * @var Frame
+ */
+ protected $_frame;
+
+ /**
+ * AbstractPositioner object used to position this frame (Strategy pattern)
+ *
+ * @var AbstractPositioner
+ */
+ protected $_positioner;
+
+ /**
+ * Reflower object used to calculate frame dimensions (Strategy pattern)
+ *
+ * @var AbstractFrameReflower
+ */
+ protected $_reflower;
+
+ /**
+ * Reference to the current dompdf instance
+ *
+ * @var Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * First block parent
+ *
+ * @var Block
+ */
+ private $_block_parent;
+
+ /**
+ * First positioned parent (position: relative | absolute | fixed)
+ *
+ * @var AbstractFrameDecorator
+ */
+ private $_positioned_parent;
+
+ /**
+ * Cache for the get_parent while loop results
+ *
+ * @var Frame
+ */
+ private $_cached_parent;
+
+ /**
+ * Whether generated content and counters have been set.
+ *
+ * @var bool
+ */
+ public $content_set = false;
+
+ /**
+ * Whether the frame has been split
+ *
+ * @var bool
+ */
+ public $is_split = false;
+
+ /**
+ * Whether the frame is a split-off frame
+ *
+ * @var bool
+ */
+ public $is_split_off = false;
+
+ /**
+ * Class constructor
+ *
+ * @param Frame $frame The decoration target
+ * @param Dompdf $dompdf The Dompdf object
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ $this->_frame = $frame;
+ $this->_root = null;
+ $this->_dompdf = $dompdf;
+ $frame->set_decorator($this);
+ }
+
+ /**
+ * "Destructor": forcibly free all references held by this object
+ *
+ * @param bool $recursive if true, call dispose on all children
+ */
+ function dispose($recursive = false)
+ {
+ if ($recursive) {
+ while ($child = $this->get_first_child()) {
+ $child->dispose(true);
+ }
+ }
+
+ $this->_root = null;
+ unset($this->_root);
+
+ $this->_frame->dispose(true);
+ $this->_frame = null;
+ unset($this->_frame);
+
+ $this->_positioner = null;
+ unset($this->_positioner);
+
+ $this->_reflower = null;
+ unset($this->_reflower);
+ }
+
+ /**
+ * Return a copy of this frame with $node as its node
+ *
+ * @param DOMNode $node
+ *
+ * @return AbstractFrameDecorator
+ */
+ function copy(DOMNode $node)
+ {
+ $frame = new Frame($node);
+ $style = clone $this->_frame->get_style();
+
+ $style->reset();
+ $frame->set_style($style);
+
+ if ($node instanceof DOMElement && $node->hasAttribute("id")) {
+ $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
+ $node->removeAttribute("id");
+ }
+
+ return Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
+ }
+
+ /**
+ * Create a deep copy: copy this node and all children
+ *
+ * @return AbstractFrameDecorator
+ */
+ function deep_copy()
+ {
+ $node = $this->_frame->get_node()->cloneNode();
+ $frame = new Frame($node);
+ $style = clone $this->_frame->get_style();
+
+ $style->reset();
+ $frame->set_style($style);
+
+ if ($node instanceof DOMElement && $node->hasAttribute("id")) {
+ $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
+ $node->removeAttribute("id");
+ }
+
+ $deco = Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
+
+ foreach ($this->get_children() as $child) {
+ $deco->append_child($child->deep_copy());
+ }
+
+ return $deco;
+ }
+
+ /**
+ * Create an anonymous child frame, inheriting styles from this frame.
+ *
+ * @param string $node_name
+ * @param string $display
+ *
+ * @return AbstractFrameDecorator
+ */
+ public function create_anonymous_child(string $node_name, string $display): AbstractFrameDecorator
+ {
+ $style = $this->get_style();
+ $child_style = $style->get_stylesheet()->create_style();
+ $child_style->set_prop("display", $display);
+ $child_style->inherit($style);
+
+ $node = $this->get_node()->ownerDocument->createElement($node_name);
+ $frame = new Frame($node);
+ $frame->set_style($child_style);
+
+ return Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
+ }
+
+ function reset()
+ {
+ $this->_frame->reset();
+ $this->_reflower->reset();
+ $this->reset_generated_content();
+ $this->revert_counter_increment();
+
+ $this->content_set = false;
+ $this->_counters = [];
+
+ // clear parent lookup caches
+ $this->_cached_parent = null;
+ $this->_block_parent = null;
+ $this->_positioned_parent = null;
+
+ // Reset all children
+ foreach ($this->get_children() as $child) {
+ $child->reset();
+ }
+ }
+
+ /**
+ * If this represents a generated node then child nodes represent generated
+ * content. Remove the children since the content will be generated next
+ * time this frame is reflowed.
+ */
+ protected function reset_generated_content(): void
+ {
+ if ($this->content_set
+ && $this->get_node()->nodeName === "dompdf_generated"
+ ) {
+ $content = $this->get_style()->content;
+
+ if ($content !== "normal" && $content !== "none") {
+ foreach ($this->get_children() as $child) {
+ $this->remove_child($child);
+ }
+ }
+ }
+ }
+
+ /**
+ * Decrement any counters that were incremented on the current node, unless
+ * that node is the body.
+ */
+ protected function revert_counter_increment(): void
+ {
+ if ($this->content_set
+ && $this->get_node()->nodeName !== "body"
+ && ($decrement = $this->get_style()->counter_increment) !== "none"
+ ) {
+ $this->decrement_counters($decrement);
+ }
+ }
+
+ // Getters -----------
+
+ function get_id()
+ {
+ return $this->_frame->get_id();
+ }
+
+ /**
+ * @return Frame
+ */
+ function get_frame()
+ {
+ return $this->_frame;
+ }
+
+ function get_node()
+ {
+ return $this->_frame->get_node();
+ }
+
+ function get_style()
+ {
+ return $this->_frame->get_style();
+ }
+
+ /**
+ * @deprecated
+ */
+ function get_original_style()
+ {
+ return $this->_frame->get_style();
+ }
+
+ function get_containing_block($i = null)
+ {
+ return $this->_frame->get_containing_block($i);
+ }
+
+ function get_position($i = null)
+ {
+ return $this->_frame->get_position($i);
+ }
+
+ /**
+ * @return Dompdf
+ */
+ function get_dompdf()
+ {
+ return $this->_dompdf;
+ }
+
+ public function get_margin_width(): float
+ {
+ return $this->_frame->get_margin_width();
+ }
+
+ public function get_margin_height(): float
+ {
+ return $this->_frame->get_margin_height();
+ }
+
+ public function get_content_box(): array
+ {
+ return $this->_frame->get_content_box();
+ }
+
+ public function get_padding_box(): array
+ {
+ return $this->_frame->get_padding_box();
+ }
+
+ public function get_border_box(): array
+ {
+ return $this->_frame->get_border_box();
+ }
+
+ function set_id($id)
+ {
+ $this->_frame->set_id($id);
+ }
+
+ public function set_style(Style $style): void
+ {
+ $this->_frame->set_style($style);
+ }
+
+ function set_containing_block($x = null, $y = null, $w = null, $h = null)
+ {
+ $this->_frame->set_containing_block($x, $y, $w, $h);
+ }
+
+ function set_position($x = null, $y = null)
+ {
+ $this->_frame->set_position($x, $y);
+ }
+
+ function is_auto_height()
+ {
+ return $this->_frame->is_auto_height();
+ }
+
+ function is_auto_width()
+ {
+ return $this->_frame->is_auto_width();
+ }
+
+ function __toString()
+ {
+ return $this->_frame->__toString();
+ }
+
+ function prepend_child(Frame $child, $update_node = true)
+ {
+ while ($child instanceof AbstractFrameDecorator) {
+ $child = $child->_frame;
+ }
+
+ $this->_frame->prepend_child($child, $update_node);
+ }
+
+ function append_child(Frame $child, $update_node = true)
+ {
+ while ($child instanceof AbstractFrameDecorator) {
+ $child = $child->_frame;
+ }
+
+ $this->_frame->append_child($child, $update_node);
+ }
+
+ function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ while ($new_child instanceof AbstractFrameDecorator) {
+ $new_child = $new_child->_frame;
+ }
+
+ if ($ref instanceof AbstractFrameDecorator) {
+ $ref = $ref->_frame;
+ }
+
+ $this->_frame->insert_child_before($new_child, $ref, $update_node);
+ }
+
+ function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
+ {
+ $insert_frame = $new_child;
+ while ($insert_frame instanceof AbstractFrameDecorator) {
+ $insert_frame = $insert_frame->_frame;
+ }
+
+ $reference_frame = $ref;
+ while ($reference_frame instanceof AbstractFrameDecorator) {
+ $reference_frame = $reference_frame->_frame;
+ }
+
+ $this->_frame->insert_child_after($insert_frame, $reference_frame, $update_node);
+ }
+
+ function remove_child(Frame $child, $update_node = true)
+ {
+ while ($child instanceof AbstractFrameDecorator) {
+ $child = $child->_frame;
+ }
+
+ return $this->_frame->remove_child($child, $update_node);
+ }
+
+ /**
+ * @param bool $use_cache
+ * @return AbstractFrameDecorator
+ */
+ function get_parent($use_cache = true)
+ {
+ if ($use_cache && $this->_cached_parent) {
+ return $this->_cached_parent;
+ }
+ $p = $this->_frame->get_parent();
+ if ($p && $deco = $p->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $this->_cached_parent = $deco;
+ } else {
+ return $this->_cached_parent = $p;
+ }
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_first_child()
+ {
+ $c = $this->_frame->get_first_child();
+ if ($c && $deco = $c->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($c) {
+ return $c;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_last_child()
+ {
+ $c = $this->_frame->get_last_child();
+ if ($c && $deco = $c->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($c) {
+ return $c;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_prev_sibling()
+ {
+ $s = $this->_frame->get_prev_sibling();
+ if ($s && $deco = $s->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($s) {
+ return $s;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function get_next_sibling()
+ {
+ $s = $this->_frame->get_next_sibling();
+ if ($s && $deco = $s->get_decorator()) {
+ while ($tmp = $deco->get_decorator()) {
+ $deco = $tmp;
+ }
+
+ return $deco;
+ } else {
+ if ($s) {
+ return $s;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return FrameListIterator<AbstractFrameDecorator>
+ */
+ public function get_children(): FrameListIterator
+ {
+ return new FrameListIterator($this);
+ }
+
+ /**
+ * @return FrameTreeIterator<AbstractFrameDecorator>
+ */
+ function get_subtree(): FrameTreeIterator
+ {
+ return new FrameTreeIterator($this);
+ }
+
+ function set_positioner(AbstractPositioner $posn)
+ {
+ $this->_positioner = $posn;
+ if ($this->_frame instanceof AbstractFrameDecorator) {
+ $this->_frame->set_positioner($posn);
+ }
+ }
+
+ function set_reflower(AbstractFrameReflower $reflower)
+ {
+ $this->_reflower = $reflower;
+ if ($this->_frame instanceof AbstractFrameDecorator) {
+ $this->_frame->set_reflower($reflower);
+ }
+ }
+
+ /**
+ * @return AbstractPositioner
+ */
+ function get_positioner()
+ {
+ return $this->_positioner;
+ }
+
+ /**
+ * @return AbstractFrameReflower
+ */
+ function get_reflower()
+ {
+ return $this->_reflower;
+ }
+
+ /**
+ * @param Frame $root
+ */
+ function set_root(Frame $root)
+ {
+ $this->_root = $root;
+
+ if ($this->_frame instanceof AbstractFrameDecorator) {
+ $this->_frame->set_root($root);
+ }
+ }
+
+ /**
+ * @return Page
+ */
+ function get_root()
+ {
+ return $this->_root;
+ }
+
+ /**
+ * @return Block
+ */
+ function find_block_parent()
+ {
+ // Find our nearest block level parent
+ if (isset($this->_block_parent)) {
+ return $this->_block_parent;
+ }
+
+ $p = $this->get_parent();
+
+ while ($p) {
+ if ($p->is_block()) {
+ break;
+ }
+
+ $p = $p->get_parent();
+ }
+
+ return $this->_block_parent = $p;
+ }
+
+ /**
+ * @return AbstractFrameDecorator
+ */
+ function find_positioned_parent()
+ {
+ // Find our nearest relative positioned parent
+ if (isset($this->_positioned_parent)) {
+ return $this->_positioned_parent;
+ }
+
+ $p = $this->get_parent();
+ while ($p) {
+ if ($p->is_positioned()) {
+ break;
+ }
+
+ $p = $p->get_parent();
+ }
+
+ if (!$p) {
+ $p = $this->_root;
+ }
+
+ return $this->_positioned_parent = $p;
+ }
+
+ /**
+ * Split this frame at $child.
+ * The current frame is cloned and $child and all children following
+ * $child are added to the clone. The clone is then passed to the
+ * current frame's parent->split() method.
+ *
+ * @param Frame|null $child
+ * @param bool $page_break
+ * @param bool $forced Whether the page break is forced.
+ *
+ * @throws Exception
+ */
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ $this->get_parent()->split($this, $page_break, $forced);
+ return;
+ }
+
+ if ($child->get_parent() !== $this) {
+ throw new Exception("Unable to split: frame is not a child of this one.");
+ }
+
+ $this->revert_counter_increment();
+
+ $node = $this->_frame->get_node();
+ $split = $this->copy($node->cloneNode());
+
+ $style = $this->_frame->get_style();
+ $split_style = $split->get_style();
+
+ // Truncate the box decoration at the split, except for the body
+ if ($node->nodeName !== "body") {
+ // Clear bottom decoration of original frame
+ $style->margin_bottom = 0.0;
+ $style->padding_bottom = 0.0;
+ $style->border_bottom_width = 0.0;
+ $style->border_bottom_left_radius = 0.0;
+ $style->border_bottom_right_radius = 0.0;
+
+ // Clear top decoration of split frame
+ $split_style->margin_top = 0.0;
+ $split_style->padding_top = 0.0;
+ $split_style->border_top_width = 0.0;
+ $split_style->border_top_left_radius = 0.0;
+ $split_style->border_top_right_radius = 0.0;
+ $split_style->page_break_before = "auto";
+ }
+
+ $split_style->text_indent = 0.0;
+ $split_style->counter_reset = "none";
+
+ $this->is_split = true;
+ $split->is_split_off = true;
+ $split->_already_pushed = true;
+
+ $this->get_parent()->insert_child_after($split, $this);
+
+ if ($this instanceof Block) {
+ // Remove the frames that will be moved to the new split node from
+ // the line boxes
+ $this->remove_frames_from_line($child);
+
+ // recalculate the float offsets after paging
+ foreach ($this->get_line_boxes() as $line_box) {
+ $line_box->get_float_offsets();
+ }
+ }
+
+ if (!$forced) {
+ // Reset top margin in case of an unforced page break
+ // https://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
+ $child->get_style()->margin_top = 0.0;
+ }
+
+ // Add $child and all following siblings to the new split node
+ $iter = $child;
+ while ($iter) {
+ $frame = $iter;
+ $iter = $iter->get_next_sibling();
+ $frame->reset();
+ $split->append_child($frame);
+ }
+
+ $this->get_parent()->split($split, $page_break, $forced);
+
+ // Preserve the current counter values. This must be done after the
+ // parent split, as counters get reset on frame reset
+ $split->_counters = $this->_counters;
+ }
+
+ /**
+ * @param array $counters
+ */
+ public function reset_counters(array $counters): void
+ {
+ foreach ($counters as $id => $value) {
+ $this->reset_counter($id, $value);
+ }
+ }
+
+ /**
+ * @param string $id
+ * @param int $value
+ */
+ public function reset_counter(string $id = self::DEFAULT_COUNTER, int $value = 0): void
+ {
+ $this->get_parent()->_counters[$id] = $value;
+ }
+
+ /**
+ * @param array $counters
+ */
+ public function decrement_counters(array $counters): void
+ {
+ foreach ($counters as $id => $increment) {
+ $this->increment_counter($id, $increment * -1);
+ }
+ }
+
+ /**
+ * @param array $counters
+ */
+ public function increment_counters(array $counters): void
+ {
+ foreach ($counters as $id => $increment) {
+ $this->increment_counter($id, $increment);
+ }
+ }
+
+ /**
+ * @param string $id
+ * @param int $increment
+ */
+ public function increment_counter(string $id = self::DEFAULT_COUNTER, int $increment = 1): void
+ {
+ $counter_frame = $this->lookup_counter_frame($id);
+
+ if ($counter_frame) {
+ if (!isset($counter_frame->_counters[$id])) {
+ $counter_frame->_counters[$id] = 0;
+ }
+
+ $counter_frame->_counters[$id] += $increment;
+ }
+ }
+
+ /**
+ * @param string $id
+ * @return AbstractFrameDecorator|null
+ */
+ function lookup_counter_frame($id = self::DEFAULT_COUNTER)
+ {
+ $f = $this->get_parent();
+
+ while ($f) {
+ if (isset($f->_counters[$id])) {
+ return $f;
+ }
+ $fp = $f->get_parent();
+
+ if (!$fp) {
+ return $f;
+ }
+
+ $f = $fp;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $id
+ * @param string $type
+ * @return bool|string
+ *
+ * TODO: What version is the best : this one or the one in ListBullet ?
+ */
+ function counter_value(string $id = self::DEFAULT_COUNTER, string $type = "decimal")
+ {
+ $type = mb_strtolower($type);
+
+ if (!isset($this->_counters[$id])) {
+ $this->_counters[$id] = 0;
+ }
+
+ $value = $this->_counters[$id];
+
+ switch ($type) {
+ default:
+ case "decimal":
+ return $value;
+
+ case "decimal-leading-zero":
+ return str_pad($value, 2, "0", STR_PAD_LEFT);
+
+ case "lower-roman":
+ return Helpers::dec2roman($value);
+
+ case "upper-roman":
+ return mb_strtoupper(Helpers::dec2roman($value));
+
+ case "lower-latin":
+ case "lower-alpha":
+ return chr((($value - 1) % 26) + ord('a'));
+
+ case "upper-latin":
+ case "upper-alpha":
+ return chr((($value - 1) % 26) + ord('A'));
+
+ case "lower-greek":
+ return Helpers::unichr($value + 944);
+
+ case "upper-greek":
+ return Helpers::unichr($value + 912);
+ }
+ }
+
+ final function position()
+ {
+ $this->_positioner->position($this);
+ }
+
+ /**
+ * @param float $offset_x
+ * @param float $offset_y
+ * @param bool $ignore_self
+ */
+ final function move(float $offset_x, float $offset_y, bool $ignore_self = false): void
+ {
+ $this->_positioner->move($this, $offset_x, $offset_y, $ignore_self);
+ }
+
+ /**
+ * @param Block|null $block
+ */
+ final function reflow(Block $block = null)
+ {
+ // Uncomment this to see the frames before they're laid out, instead of
+ // during rendering.
+ //echo $this->_frame; flush();
+ $this->_reflower->reflow($block);
+ }
+
+ /**
+ * @return array
+ */
+ final public function get_min_max_width(): array
+ {
+ return $this->_reflower->get_min_max_width();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php
new file mode 100644
index 0000000..1fcf134
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Block.php
@@ -0,0 +1,256 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+use Dompdf\LineBox;
+
+/**
+ * Decorates frames for block layout
+ *
+ * @package dompdf
+ */
+class Block extends AbstractFrameDecorator
+{
+ /**
+ * Current line index
+ *
+ * @var int
+ */
+ protected $_cl;
+
+ /**
+ * The block's line boxes
+ *
+ * @var LineBox[]
+ */
+ protected $_line_boxes;
+
+ /**
+ * List of markers that have not found their line box to vertically align
+ * with yet. Markers are collected by nested block containers until an
+ * inline line box is found at the start of the block.
+ *
+ * @var ListBullet[]
+ */
+ protected $dangling_markers;
+
+ /**
+ * Block constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+
+ $this->_line_boxes = [new LineBox($this)];
+ $this->_cl = 0;
+ $this->dangling_markers = [];
+ }
+
+ function reset()
+ {
+ parent::reset();
+
+ $this->_line_boxes = [new LineBox($this)];
+ $this->_cl = 0;
+ $this->dangling_markers = [];
+ }
+
+ /**
+ * @return LineBox
+ */
+ function get_current_line_box()
+ {
+ return $this->_line_boxes[$this->_cl];
+ }
+
+ /**
+ * @return int
+ */
+ function get_current_line_number()
+ {
+ return $this->_cl;
+ }
+
+ /**
+ * @return LineBox[]
+ */
+ function get_line_boxes()
+ {
+ return $this->_line_boxes;
+ }
+
+ /**
+ * @param int $line_number
+ * @return int
+ */
+ function set_current_line_number($line_number)
+ {
+ $line_boxes_count = count($this->_line_boxes);
+ $cl = max(min($line_number, $line_boxes_count), 0);
+ return ($this->_cl = $cl);
+ }
+
+ /**
+ * @param int $i
+ */
+ function clear_line($i)
+ {
+ if (isset($this->_line_boxes[$i])) {
+ unset($this->_line_boxes[$i]);
+ }
+ }
+
+ /**
+ * @param Frame $frame
+ * @return LineBox|null
+ */
+ public function add_frame_to_line(Frame $frame): ?LineBox
+ {
+ $current_line = $this->_line_boxes[$this->_cl];
+ $frame->set_containing_line($current_line);
+
+ // Inline frames are currently treated as wrappers, and are not actually
+ // added to the line
+ if ($frame instanceof Inline) {
+ return null;
+ }
+
+ $current_line->add_frame($frame);
+
+ $this->increase_line_width($frame->get_margin_width());
+ $this->maximize_line_height($frame->get_margin_height(), $frame);
+
+ // Add any dangling list markers to the first line box if it is inline
+ if ($this->_cl === 0 && $current_line->inline
+ && $this->dangling_markers !== []
+ ) {
+ foreach ($this->dangling_markers as $marker) {
+ $current_line->add_list_marker($marker);
+ $this->maximize_line_height($marker->get_margin_height(), $marker);
+ }
+
+ $this->dangling_markers = [];
+ }
+
+ return $current_line;
+ }
+
+ /**
+ * Remove the given frame and all following frames and lines from the block.
+ *
+ * @param Frame $frame
+ */
+ public function remove_frames_from_line(Frame $frame): void
+ {
+ // Inline frames are not added to line boxes themselves, only their
+ // text frame children
+ $actualFrame = $frame;
+ while ($actualFrame !== null && $actualFrame instanceof Inline) {
+ $actualFrame = $actualFrame->get_first_child();
+ }
+
+ if ($actualFrame === null) {
+ return;
+ }
+
+ // Search backwards through the lines for $frame
+ $frame = $actualFrame;
+ $i = $this->_cl;
+ $j = null;
+
+ while ($i > 0) {
+ $line = $this->_line_boxes[$i];
+ foreach ($line->get_frames() as $index => $f) {
+ if ($frame === $f) {
+ $j = $index;
+ break 2;
+ }
+ }
+ $i--;
+ }
+
+ if ($j === null) {
+ return;
+ }
+
+ // Remove all lines that follow
+ for ($k = $this->_cl; $k > $i; $k--) {
+ unset($this->_line_boxes[$k]);
+ }
+
+ // Remove the line, if it is empty
+ if ($j > 0) {
+ $line->remove_frames($j);
+ } else {
+ unset($this->_line_boxes[$i]);
+ }
+
+ // Reset array indices
+ $this->_line_boxes = array_values($this->_line_boxes);
+ $this->_cl = count($this->_line_boxes) - 1;
+ }
+
+ /**
+ * @param float $w
+ */
+ public function increase_line_width(float $w): void
+ {
+ $this->_line_boxes[$this->_cl]->w += $w;
+ }
+
+ /**
+ * @param float $val
+ * @param Frame $frame
+ */
+ public function maximize_line_height(float $val, Frame $frame): void
+ {
+ if ($val > $this->_line_boxes[$this->_cl]->h) {
+ $this->_line_boxes[$this->_cl]->tallest_frame = $frame;
+ $this->_line_boxes[$this->_cl]->h = $val;
+ }
+ }
+
+ /**
+ * @param bool $br
+ */
+ public function add_line(bool $br = false): void
+ {
+ $line = $this->_line_boxes[$this->_cl];
+
+ $line->br = $br;
+ $y = $line->y + $line->h;
+
+ $new_line = new LineBox($this, $y);
+
+ $this->_line_boxes[++$this->_cl] = $new_line;
+ }
+
+ /**
+ * @param ListBullet $marker
+ */
+ public function add_dangling_marker(ListBullet $marker): void
+ {
+ $this->dangling_markers[] = $marker;
+ }
+
+ /**
+ * Inherit any dangling markers from the parent block.
+ *
+ * @param Block $block
+ */
+ public function inherit_dangling_markers(self $block): void
+ {
+ if ($block->dangling_markers !== []) {
+ $this->dangling_markers = $block->dangling_markers;
+ $block->dangling_markers = [];
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php
new file mode 100644
index 0000000..92ac491
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Image.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+use Dompdf\Helpers;
+use Dompdf\Image\Cache;
+
+/**
+ * Decorates frames for image layout and rendering
+ *
+ * @package dompdf
+ */
+class Image extends AbstractFrameDecorator
+{
+
+ /**
+ * The path to the image file (note that remote images are
+ * downloaded locally to Options:tempDir).
+ *
+ * @var string
+ */
+ protected $_image_url;
+
+ /**
+ * The image's file error message
+ *
+ * @var string
+ */
+ protected $_image_msg;
+
+ /**
+ * Class constructor
+ *
+ * @param Frame $frame the frame to decorate
+ * @param DOMPDF $dompdf the document's dompdf object (required to resolve relative & remote urls)
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ $url = $frame->get_node()->getAttribute("src");
+
+ $debug_png = $dompdf->getOptions()->getDebugPng();
+ if ($debug_png) {
+ print '[__construct ' . $url . ']';
+ }
+
+ list($this->_image_url, /*$type*/, $this->_image_msg) = Cache::resolve_url(
+ $url,
+ $dompdf->getProtocol(),
+ $dompdf->getBaseHost(),
+ $dompdf->getBasePath(),
+ $dompdf->getOptions()
+ );
+
+ if (Cache::is_broken($this->_image_url) &&
+ $alt = $frame->get_node()->getAttribute("alt")
+ ) {
+ $fontMetrics = $dompdf->getFontMetrics();
+ $style = $frame->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ $style->width = (4 / 3) * $fontMetrics->getTextWidth($alt, $font, $size, $word_spacing, $letter_spacing);
+ $style->height = $fontMetrics->getFontHeight($font, $size);
+ }
+ }
+
+ /**
+ * Get the intrinsic pixel dimensions of the image.
+ *
+ * @return array Width and height as `float|int`.
+ */
+ public function get_intrinsic_dimensions(): array
+ {
+ [$width, $height] = Helpers::dompdf_getimagesize($this->_image_url, $this->_dompdf->getHttpContext());
+
+ return [$width, $height];
+ }
+
+ /**
+ * Resample the given pixel length according to dpi.
+ *
+ * @param float|int $length
+ * @return float
+ */
+ public function resample($length): float
+ {
+ $dpi = $this->_dompdf->getOptions()->getDpi();
+ return ($length * 72) / $dpi;
+ }
+
+ /**
+ * Return the image's url
+ *
+ * @return string The url of this image
+ */
+ function get_image_url()
+ {
+ return $this->_image_url;
+ }
+
+ /**
+ * Return the image's error message
+ *
+ * @return string The image's error message
+ */
+ function get_image_msg()
+ {
+ return $this->_image_msg;
+ }
+
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php
new file mode 100644
index 0000000..668d795
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Inline.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+use Dompdf\Exception;
+
+/**
+ * Decorates frames for inline layout
+ *
+ * @package dompdf
+ */
+class Inline extends AbstractFrameDecorator
+{
+
+ /**
+ * Inline constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ }
+
+ /**
+ * Vertical padding, border, and margin do not apply when determining the
+ * height for inline frames.
+ *
+ * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
+ *
+ * The vertical padding, border and margin of an inline, non-replaced box
+ * start at the top and bottom of the content area, not the
+ * 'line-height'. But only the 'line-height' is used to calculate the
+ * height of the line box.
+ *
+ * @return float
+ */
+ public function get_margin_height(): float
+ {
+ $style = $this->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
+
+ return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight;
+ }
+
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ $this->get_parent()->split($this, $page_break, $forced);
+ return;
+ }
+
+ if ($child->get_parent() !== $this) {
+ throw new Exception("Unable to split: frame is not a child of this one.");
+ }
+
+ $this->revert_counter_increment();
+ $node = $this->_frame->get_node();
+ $split = $this->copy($node->cloneNode());
+
+ $style = $this->_frame->get_style();
+ $split_style = $split->get_style();
+
+ // Unset the current node's right style properties
+ $style->margin_right = 0.0;
+ $style->padding_right = 0.0;
+ $style->border_right_width = 0.0;
+ $style->border_top_right_radius = 0.0;
+ $style->border_bottom_right_radius = 0.0;
+
+ // Unset the split node's left style properties since we don't want them
+ // to propagate
+ $split_style->margin_left = 0.0;
+ $split_style->padding_left = 0.0;
+ $split_style->border_left_width = 0.0;
+ $split_style->border_top_left_radius = 0.0;
+ $split_style->border_bottom_left_radius = 0.0;
+
+ // If this is a generated node don't propagate the content style
+ if ($split->get_node()->nodeName == "dompdf_generated") {
+ $split_style->content = "normal";
+ }
+
+ //On continuation of inline element on next line,
+ //don't repeat non-horizontally repeatable background images
+ //See e.g. in testcase image_variants, long descriptions
+ if (($url = $style->background_image) && $url !== "none"
+ && ($repeat = $style->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-x"
+ ) {
+ $split_style->background_image = "none";
+ }
+
+ $this->get_parent()->insert_child_after($split, $this);
+
+ // Add $child and all following siblings to the new split node
+ $iter = $child;
+ while ($iter) {
+ $frame = $iter;
+ $iter = $iter->get_next_sibling();
+ $frame->reset();
+ $split->append_child($frame);
+ }
+
+ $parent = $this->get_parent();
+
+ if ($page_break) {
+ $parent->split($split, $page_break, $forced);
+ } elseif ($parent instanceof Inline) {
+ $parent->split($split);
+ }
+ }
+
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php
new file mode 100644
index 0000000..703f467
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+
+/**
+ * Decorates frames for list bullet rendering
+ *
+ * @package dompdf
+ */
+class ListBullet extends AbstractFrameDecorator
+{
+ /**
+ * Bullet diameter as fraction of font size.
+ */
+ public const BULLET_SIZE = 0.35;
+
+ /**
+ * Bullet offset from font baseline as fraction of font size.
+ */
+ public const BULLET_OFFSET = 0.1;
+
+ /**
+ * Thickness of bullet outline as fraction of font size.
+ * See also `DECO_THICKNESS`. Screen: 0.08, print: better less, e.g. 0.04.
+ */
+ public const BULLET_THICKNESS = 0.04;
+
+ /**
+ * Indentation from the start of the line as fraction of font size.
+ */
+ public const MARKER_INDENT = 0.52;
+
+ /**
+ * ListBullet constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ }
+
+ /**
+ * Get the width of the bullet symbol.
+ *
+ * @return float
+ */
+ public function get_width(): float
+ {
+ $style = $this->_frame->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ return $style->font_size * self::BULLET_SIZE;
+ }
+
+ /**
+ * Get the height of the bullet symbol.
+ *
+ * @return float
+ */
+ public function get_height(): float
+ {
+ $style = $this->_frame->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ return $style->font_size * self::BULLET_SIZE;
+ }
+
+ /**
+ * Get the width of the bullet, including indentation.
+ */
+ public function get_margin_width(): float
+ {
+ $style = $this->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ return $style->font_size * (self::BULLET_SIZE + self::MARKER_INDENT);
+ }
+
+ /**
+ * Get the line height for the bullet.
+ *
+ * This increases the height of the corresponding line box when necessary.
+ */
+ public function get_margin_height(): float
+ {
+ $style = $this->get_style();
+
+ if ($style->list_style_type === "none") {
+ return 0.0;
+ }
+
+ // TODO: This is a copy of `FrameDecorator\Text::get_margin_height()`
+ // Would be nice to properly refactor that at some point
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
+
+ return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php
new file mode 100644
index 0000000..d921929
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/ListBulletImage.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+use Dompdf\Helpers;
+use Dompdf\Image\Cache;
+
+/**
+ * Decorates frames for list bullets with custom images
+ *
+ * @package dompdf
+ */
+class ListBulletImage extends ListBullet
+{
+
+ /**
+ * The underlying image frame
+ *
+ * @var Image
+ */
+ protected $_img;
+
+ /**
+ * The image's width in pixels
+ *
+ * @var float
+ */
+ protected $_width;
+
+ /**
+ * The image's height in pixels
+ *
+ * @var float
+ */
+ protected $_height;
+
+ /**
+ * ListBulletImage constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ $style = $frame->get_style();
+ $url = $style->list_style_image;
+ $frame->get_node()->setAttribute("src", $url);
+ $this->_img = new Image($frame, $dompdf);
+ parent::__construct($this->_img, $dompdf);
+
+ $url = $this->_img->get_image_url();
+
+ if (Cache::is_broken($url)) {
+ $this->_width = parent::get_width();
+ $this->_height = parent::get_height();
+ } else {
+ // Resample the bullet image to be consistent with 'auto' sized images
+ [$width, $height] = $this->_img->get_intrinsic_dimensions();
+ $this->_width = $this->_img->resample($width);
+ $this->_height = $this->_img->resample($height);
+ }
+ }
+
+ public function get_width(): float
+ {
+ return $this->_width;
+ }
+
+ public function get_height(): float
+ {
+ return $this->_height;
+ }
+
+ public function get_margin_width(): float
+ {
+ $style = $this->get_style();
+ return $this->_width + $style->font_size * self::MARKER_INDENT;
+ }
+
+ public function get_margin_height(): float
+ {
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $style = $this->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $fontMetrics->getFontHeight($font, $size);
+ $baseline = $fontMetrics->getFontBaseline($font, $size);
+
+ // This is the same factor as used in
+ // `FrameDecorator\Text::get_margin_height()`
+ $f = $style->line_height / ($size > 0 ? $size : 1);
+
+ // FIXME: Tries to approximate replacing the space above the font
+ // baseline with the image
+ return $f * ($fontHeight - $baseline) + $this->_height;
+ }
+
+ /**
+ * Return image url
+ *
+ * @return string
+ */
+ function get_image_url()
+ {
+ return $this->_img->get_image_url();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php
new file mode 100644
index 0000000..f083816
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/NullFrameDecorator.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+
+/**
+ * Dummy decorator
+ *
+ * @package dompdf
+ */
+class NullFrameDecorator extends AbstractFrameDecorator
+{
+ /**
+ * NullFrameDecorator constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ $style = $this->_frame->get_style();
+ $style->width = 0;
+ $style->height = 0;
+ $style->margin = 0;
+ $style->padding = 0;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php
new file mode 100644
index 0000000..25ef240
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Page.php
@@ -0,0 +1,753 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Helpers;
+use Dompdf\Frame;
+use Dompdf\Renderer;
+
+/**
+ * Decorates frames for page layout
+ *
+ * @package dompdf
+ */
+class Page extends AbstractFrameDecorator
+{
+ /**
+ * The y value of the bottom edge of the page area.
+ *
+ * https://www.w3.org/TR/CSS21/page.html#page-margins
+ *
+ * @var float
+ */
+ protected $bottom_page_edge;
+
+ /**
+ * Flag indicating page is full.
+ *
+ * @var bool
+ */
+ protected $_page_full;
+
+ /**
+ * Number of tables currently being reflowed
+ *
+ * @var int
+ */
+ protected $_in_table;
+
+ /**
+ * The pdf renderer
+ *
+ * @var Renderer
+ */
+ protected $_renderer;
+
+ /**
+ * This page's floating frames
+ *
+ * @var array
+ */
+ protected $_floating_frames = [];
+
+ //........................................................................
+
+ /**
+ * Class constructor
+ *
+ * @param Frame $frame the frame to decorate
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ $this->_page_full = false;
+ $this->_in_table = 0;
+ $this->bottom_page_edge = null;
+ }
+
+ /**
+ * Set the renderer used for this pdf
+ *
+ * @param Renderer $renderer the renderer to use
+ */
+ function set_renderer($renderer)
+ {
+ $this->_renderer = $renderer;
+ }
+
+ /**
+ * Return the renderer used for this pdf
+ *
+ * @return Renderer
+ */
+ function get_renderer()
+ {
+ return $this->_renderer;
+ }
+
+ /**
+ * Calculate the bottom edge of the page area after margins have been
+ * applied for the current page.
+ */
+ public function calculate_bottom_page_edge(): void
+ {
+ [, , , $cbh] = $this->get_containing_block();
+ $style = $this->get_style();
+ $margin_bottom = (float) $style->length_in_pt($style->margin_bottom, $cbh);
+
+ $this->bottom_page_edge = $cbh - $margin_bottom;
+ }
+
+ /**
+ * Returns true if the page is full and is no longer accepting frames.
+ *
+ * @return bool
+ */
+ function is_full()
+ {
+ return $this->_page_full;
+ }
+
+ /**
+ * Start a new page by resetting the full flag.
+ */
+ function next_page()
+ {
+ $this->_floating_frames = [];
+ $this->_renderer->new_page();
+ $this->_page_full = false;
+ }
+
+ /**
+ * Indicate to the page that a table is currently being reflowed.
+ */
+ function table_reflow_start()
+ {
+ $this->_in_table++;
+ }
+
+ /**
+ * Indicate to the page that table reflow is finished.
+ */
+ function table_reflow_end()
+ {
+ $this->_in_table--;
+ }
+
+ /**
+ * Return whether we are currently in a nested table or not
+ *
+ * @return bool
+ */
+ function in_nested_table()
+ {
+ return $this->_in_table > 1;
+ }
+
+ /**
+ * Check if a forced page break is required before $frame. This uses the
+ * frame's page_break_before property as well as the preceding frame's
+ * page_break_after property.
+ *
+ * @link http://www.w3.org/TR/CSS21/page.html#forced
+ *
+ * @param AbstractFrameDecorator $frame the frame to check
+ *
+ * @return bool true if a page break occurred
+ */
+ function check_forced_page_break(Frame $frame)
+ {
+ // Skip check if page is already split and for the body
+ if ($this->_page_full || $frame->get_node()->nodeName === "body") {
+ return false;
+ }
+
+ $page_breaks = ["always", "left", "right"];
+ $style = $frame->get_style();
+
+ if (($frame->is_block_level() || $style->display === "table-row")
+ && in_array($style->page_break_before, $page_breaks, true)
+ ) {
+ // Prevent cascading splits
+ $frame->split(null, true, true);
+ $style->page_break_before = "auto";
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ // Find the preceding block-level sibling (or table row). Inline
+ // elements are treated as if wrapped in an anonymous block container
+ // here. See https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level
+ $prev = $frame->get_prev_sibling();
+ while ($prev && (($prev->is_text_node() && $prev->get_node()->nodeValue === "")
+ || $prev->get_node()->nodeName === "bullet")
+ ) {
+ $prev = $prev->get_prev_sibling();
+ }
+
+ if ($prev && ($prev->is_block_level() || $prev->get_style()->display === "table-row")) {
+ if (in_array($prev->get_style()->page_break_after, $page_breaks, true)) {
+ // Prevent cascading splits
+ $frame->split(null, true, true);
+ $prev->get_style()->page_break_after = "auto";
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ $prev_last_child = $prev->get_last_child();
+ while ($prev_last_child && (($prev_last_child->is_text_node() && $prev_last_child->get_node()->nodeValue === "")
+ || $prev_last_child->get_node()->nodeName === "bullet")
+ ) {
+ $prev_last_child = $prev_last_child->get_prev_sibling();
+ }
+
+ if ($prev_last_child
+ && $prev_last_child->is_block_level()
+ && in_array($prev_last_child->get_style()->page_break_after, $page_breaks, true)
+ ) {
+ $frame->split(null, true, true);
+ $prev_last_child->get_style()->page_break_after = "auto";
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for a gap between the top content edge of a frame and its child
+ * content.
+ *
+ * Additionally, the top margin, border, and padding of the frame must fit
+ * on the current page.
+ *
+ * @param float $childPos The top margin or line-box edge of the child content.
+ * @param Frame $frame The parent frame to check.
+ * @return bool
+ */
+ protected function hasGap(float $childPos, Frame $frame): bool
+ {
+ $style = $frame->get_style();
+ $cbw = $frame->get_containing_block("w");
+ $contentEdge = $frame->get_position("y") + (float) $style->length_in_pt([
+ $style->margin_top,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cbw);
+
+ return Helpers::lengthGreater($childPos, $contentEdge)
+ && Helpers::lengthLessOrEqual($contentEdge, $this->bottom_page_edge);
+ }
+
+ /**
+ * Determine if a page break is allowed before $frame
+ * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
+ *
+ * In the normal flow, page breaks can occur at the following places:
+ *
+ * 1. In the vertical margin between block boxes. When an
+ * unforced page break occurs here, the used values of the
+ * relevant 'margin-top' and 'margin-bottom' properties are set
+ * to '0'. When a forced page break occurs here, the used value
+ * of the relevant 'margin-bottom' property is set to '0'; the
+ * relevant 'margin-top' used value may either be set to '0' or
+ * retained.
+ * 2. Between line boxes inside a block container box.
+ * 3. Between the content edge of a block container box and the
+ * outer edges of its child content (margin edges of block-level
+ * children or line box edges for inline-level children) if there
+ * is a (non-zero) gap between them.
+ *
+ * These breaks are subject to the following rules:
+ *
+ * * Rule A: Breaking at (1) is allowed only if the
+ * 'page-break-after' and 'page-break-before' properties of all
+ * the elements generating boxes that meet at this margin allow
+ * it, which is when at least one of them has the value
+ * 'always', 'left', or 'right', or when all of them are 'auto'.
+ *
+ * * Rule B: However, if all of them are 'auto' and a common
+ * ancestor of all the elements has a 'page-break-inside' value
+ * of 'avoid', then breaking here is not allowed.
+ *
+ * * Rule C: Breaking at (2) is allowed only if the number of line
+ * boxes between the break and the start of the enclosing block
+ * box is the value of 'orphans' or more, and the number of line
+ * boxes between the break and the end of the box is the value
+ * of 'widows' or more.
+ *
+ * * Rule D: In addition, breaking at (2) or (3) is allowed only
+ * if the 'page-break-inside' property of the element and all
+ * its ancestors is 'auto'.
+ *
+ * If the above does not provide enough break points to keep content
+ * from overflowing the page boxes, then rules A, B and D are
+ * dropped in order to find additional breakpoints.
+ *
+ * If that still does not lead to sufficient break points, rule C is
+ * dropped as well, to find still more break points.
+ *
+ * We also allow breaks between table rows.
+ *
+ * @param AbstractFrameDecorator $frame the frame to check
+ *
+ * @return bool true if a break is allowed, false otherwise
+ */
+ protected function _page_break_allowed(Frame $frame)
+ {
+ Helpers::dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")");
+ $display = $frame->get_style()->display;
+
+ // Block Frames (1):
+ if ($frame->is_block_level() || $display === "-dompdf-image") {
+
+ // Avoid breaks within table-cells
+ if ($this->_in_table > ($display === "table" ? 1 : 0)) {
+ Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table);
+
+ return false;
+ }
+
+ // Rule A
+ if ($frame->get_style()->page_break_before === "avoid") {
+ Helpers::dompdf_debug("page-break", "before: avoid");
+
+ return false;
+ }
+
+ // Find the preceding block-level sibling. Inline elements are
+ // treated as if wrapped in an anonymous block container here. See
+ // https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level
+ $prev = $frame->get_prev_sibling();
+ while ($prev && (($prev->is_text_node() && $prev->get_node()->nodeValue === "")
+ || $prev->get_node()->nodeName === "bullet")
+ ) {
+ $prev = $prev->get_prev_sibling();
+ }
+
+ // Does the previous element allow a page break after?
+ if ($prev && ($prev->is_block_level() || $prev->get_style()->display === "-dompdf-image")
+ && $prev->get_style()->page_break_after === "avoid"
+ ) {
+ Helpers::dompdf_debug("page-break", "after: avoid");
+
+ return false;
+ }
+
+ // Rules B & D
+ $parent = $frame->get_parent();
+ $p = $parent;
+ while ($p) {
+ if ($p->get_style()->page_break_inside === "avoid") {
+ Helpers::dompdf_debug("page-break", "parent->inside: avoid");
+
+ return false;
+ }
+ $p = $p->find_block_parent();
+ }
+
+ // To prevent cascading page breaks when a top-level element has
+ // page-break-inside: avoid, ensure that at least one frame is
+ // on the page before splitting.
+ if ($parent->get_node()->nodeName === "body" && !$prev) {
+ // We are the body's first child
+ Helpers::dompdf_debug("page-break", "Body's first child.");
+
+ return false;
+ }
+
+ // Check for a possible type (3) break
+ if (!$prev && $parent && !$this->hasGap($frame->get_position("y"), $parent)) {
+ Helpers::dompdf_debug("page-break", "First block-level frame, no gap");
+
+ return false;
+ }
+
+ Helpers::dompdf_debug("page-break", "block: break allowed");
+
+ return true;
+
+ } // Inline frames (2):
+ else {
+ if ($frame->is_inline_level()) {
+
+ // Avoid breaks within table-cells
+ if ($this->_in_table) {
+ Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table);
+
+ return false;
+ }
+
+ // Rule C
+ $block_parent = $frame->find_block_parent();
+ $parent_style = $block_parent->get_style();
+ $line = $block_parent->get_current_line_box();
+ $line_count = count($block_parent->get_line_boxes());
+ $line_number = $frame->get_containing_line() && empty($line->get_frames())
+ ? $line_count - 1
+ : $line_count;
+
+ // The line number of the frame can be less than the current
+ // number of line boxes, in case we are backtracking. As long as
+ // we are not checking for widows yet, just checking against the
+ // number of line boxes is sufficient in most cases, though.
+ if ($line_number <= $parent_style->orphans) {
+ Helpers::dompdf_debug("page-break", "orphans");
+
+ return false;
+ }
+
+ // FIXME: Checking widows is tricky without having laid out the
+ // remaining line boxes. Just ignore it for now...
+
+ // Rule D
+ $p = $block_parent;
+ while ($p) {
+ if ($p->get_style()->page_break_inside === "avoid") {
+ Helpers::dompdf_debug("page-break", "parent->inside: avoid");
+
+ return false;
+ }
+ $p = $p->find_block_parent();
+ }
+
+ // To prevent cascading page breaks when a top-level element has
+ // page-break-inside: avoid, ensure that at least one frame with
+ // some content is on the page before splitting.
+ $prev = $frame->get_prev_sibling();
+ while ($prev && ($prev->is_text_node() && trim($prev->get_node()->nodeValue) == "")) {
+ $prev = $prev->get_prev_sibling();
+ }
+
+ if ($block_parent->get_node()->nodeName === "body" && !$prev) {
+ // We are the body's first child
+ Helpers::dompdf_debug("page-break", "Body's first child.");
+
+ return false;
+ }
+
+ Helpers::dompdf_debug("page-break", "inline: break allowed");
+
+ return true;
+
+ // Table-rows
+ } else {
+ if ($display === "table-row") {
+
+ // If this is a nested table, prevent the page from breaking
+ if ($this->_in_table > 1) {
+ Helpers::dompdf_debug("page-break", "table: nested table");
+
+ return false;
+ }
+
+ // Rule A (table row)
+ if ($frame->get_style()->page_break_before === "avoid") {
+ Helpers::dompdf_debug("page-break", "before: avoid");
+
+ return false;
+ }
+
+ // Find the preceding row
+ $prev = $frame->get_prev_sibling();
+
+ if (!$prev) {
+ $prev_group = $frame->get_parent()->get_prev_sibling();
+
+ if ($prev_group
+ && in_array($prev_group->get_style()->display, Table::ROW_GROUPS, true)
+ ) {
+ $prev = $prev_group->get_last_child();
+ }
+ }
+
+ // Check if a page break is allowed after the preceding row
+ if ($prev && $prev->get_style()->page_break_after === "avoid") {
+ Helpers::dompdf_debug("page-break", "after: avoid");
+
+ return false;
+ }
+
+ // Avoid breaking before the first row of a table
+ if (!$prev) {
+ Helpers::dompdf_debug("page-break", "table: first-row");
+
+ return false;
+ }
+
+ // Rule B (table row)
+ // Check if the page_break_inside property is not 'avoid'
+ // for the parent table or any of its ancestors
+ $table = Table::find_parent_table($frame);
+
+ $p = $table;
+ while ($p) {
+ if ($p->get_style()->page_break_inside === "avoid") {
+ Helpers::dompdf_debug("page-break", "parent->inside: avoid");
+
+ return false;
+ }
+ $p = $p->find_block_parent();
+ }
+
+ Helpers::dompdf_debug("page-break", "table-row: break allowed");
+
+ return true;
+ } else {
+ if (in_array($display, Table::ROW_GROUPS, true)) {
+
+ // Disallow breaks at row-groups: only split at row boundaries
+ return false;
+
+ } else {
+ Helpers::dompdf_debug("page-break", "? " . $display);
+
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if $frame will fit on the page. If the frame does not fit,
+ * the frame tree is modified so that a page break occurs in the
+ * correct location.
+ *
+ * @param AbstractFrameDecorator $frame the frame to check
+ *
+ * @return bool
+ */
+ function check_page_break(Frame $frame)
+ {
+ if ($this->_page_full || $frame->_already_pushed
+ // Never check for breaks on empty text nodes
+ || ($frame->is_text_node() && $frame->get_node()->nodeValue === "")
+ ) {
+ return false;
+ }
+
+ $p = $frame;
+ do {
+ $display = $p->get_style()->display;
+ if ($display == "table-row") {
+ if ($p->_already_pushed) { return false; }
+ }
+ } while ($p = $p->get_parent());
+
+ // If the frame is absolute or fixed it shouldn't break
+ $p = $frame;
+ do {
+ if ($p->is_absolute()) {
+ return false;
+ }
+ } while ($p = $p->get_parent());
+
+ $margin_height = $frame->get_margin_height();
+
+ // Determine the frame's maximum y value
+ $max_y = (float)$frame->get_position("y") + $margin_height;
+
+ // If a split is to occur here, then the bottom margins & paddings of all
+ // parents of $frame must fit on the page as well:
+ $p = $frame->get_parent();
+ while ($p && $p !== $this) {
+ $cbw = $p->get_containing_block("w");
+ $max_y += (float) $p->get_style()->computed_bottom_spacing($cbw);
+ $p = $p->get_parent();
+ }
+
+ // Check if $frame flows off the page
+ if (Helpers::lengthLessOrEqual($max_y, $this->bottom_page_edge)) {
+ // no: do nothing
+ return false;
+ }
+
+ Helpers::dompdf_debug("page-break", "check_page_break");
+ Helpers::dompdf_debug("page-break", "in_table: " . $this->_in_table);
+
+ // yes: determine page break location
+ $iter = $frame;
+ $flg = false;
+ $pushed_flg = false;
+
+ $in_table = $this->_in_table;
+
+ Helpers::dompdf_debug("page-break", "Starting search");
+ while ($iter) {
+ // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). "";
+ if ($iter === $this) {
+ Helpers::dompdf_debug("page-break", "reached root.");
+ // We've reached the root in our search. Just split at $frame.
+ break;
+ }
+
+ if ($iter->_already_pushed) {
+ $pushed_flg = true;
+ } elseif ($this->_page_break_allowed($iter)) {
+ Helpers::dompdf_debug("page-break", "break allowed, splitting.");
+ $iter->split(null, true);
+ $this->_page_full = true;
+ $this->_in_table = $in_table;
+ $iter->_already_pushed = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ if (!$flg && $next = $iter->get_last_child()) {
+ Helpers::dompdf_debug("page-break", "following last child.");
+
+ if ($next->is_table()) {
+ $this->_in_table++;
+ }
+
+ $iter = $next;
+ $pushed_flg = false;
+ continue;
+ }
+
+ if ($pushed_flg) {
+ // The frame was already pushed, avoid breaking on a previous page
+ break;
+ }
+
+ $next = $iter->get_prev_sibling();
+ // Skip empty text nodes
+ while ($next && $next->is_text_node() && $next->get_node()->nodeValue === "") {
+ $next = $next->get_prev_sibling();
+ }
+
+ if ($next) {
+ Helpers::dompdf_debug("page-break", "following prev sibling.");
+
+ if ($next->is_table() && !$iter->is_table()) {
+ $this->_in_table++;
+ } elseif (!$next->is_table() && $iter->is_table()) {
+ $this->_in_table--;
+ }
+
+ $iter = $next;
+ $flg = false;
+ continue;
+ }
+
+ if ($next = $iter->get_parent()) {
+ Helpers::dompdf_debug("page-break", "following parent.");
+
+ if ($iter->is_table()) {
+ $this->_in_table--;
+ }
+
+ $iter = $next;
+ $flg = true;
+ continue;
+ }
+
+ break;
+ }
+
+ $this->_in_table = $in_table;
+
+ // No valid page break found. Just break at $frame.
+ Helpers::dompdf_debug("page-break", "no valid break found, just splitting.");
+
+ // If we are in a table, backtrack to the nearest top-level table row
+ if ($this->_in_table) {
+ $iter = $frame;
+ while ($iter && $iter->get_style()->display !== "table-row" && $iter->get_style()->display !== 'table-row-group' && $iter->_already_pushed === false) {
+ $iter = $iter->get_parent();
+ }
+
+ if ($iter) {
+ $iter->split(null, true);
+ $iter->_already_pushed = true;
+ } else {
+ return false;
+ }
+ } else {
+ $frame->split(null, true);
+ }
+
+ $this->_page_full = true;
+ $frame->_already_pushed = true;
+
+ return true;
+ }
+
+ //........................................................................
+
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ // Do nothing
+ }
+
+ /**
+ * Add a floating frame
+ *
+ * @param Frame $frame
+ *
+ * @return void
+ */
+ function add_floating_frame(Frame $frame)
+ {
+ array_unshift($this->_floating_frames, $frame);
+ }
+
+ /**
+ * @return Frame[]
+ */
+ function get_floating_frames()
+ {
+ return $this->_floating_frames;
+ }
+
+ /**
+ * @param $key
+ */
+ public function remove_floating_frame($key)
+ {
+ unset($this->_floating_frames[$key]);
+ }
+
+ /**
+ * @param Frame $child
+ * @return int|mixed
+ */
+ public function get_lowest_float_offset(Frame $child)
+ {
+ $style = $child->get_style();
+ $side = $style->clear;
+ $float = $style->float;
+
+ $y = 0;
+
+ if ($float === "none") {
+ foreach ($this->_floating_frames as $key => $frame) {
+ if ($side === "both" || $frame->get_style()->float === $side) {
+ $y = max($y, $frame->get_position("y") + $frame->get_margin_height());
+ }
+ $this->remove_floating_frame($key);
+ }
+ }
+
+ if ($y > 0) {
+ $y++; // add 1px buffer from float
+ }
+
+ return $y;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php
new file mode 100644
index 0000000..2770cbe
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Table.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Cellmap;
+use DOMNode;
+use Dompdf\Css\Style;
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+
+/**
+ * Decorates Frames for table layout
+ *
+ * @package dompdf
+ */
+class Table extends AbstractFrameDecorator
+{
+ public const VALID_CHILDREN = Style::TABLE_INTERNAL_TYPES;
+
+ /**
+ * List of all row-group display types.
+ */
+ public const ROW_GROUPS = [
+ "table-row-group",
+ "table-header-group",
+ "table-footer-group"
+ ];
+
+ /**
+ * The Cellmap object for this table. The cellmap maps table cells
+ * to rows and columns, and aids in calculating column widths.
+ *
+ * @var Cellmap
+ */
+ protected $_cellmap;
+
+ /**
+ * Table header rows. Each table header is duplicated when a table
+ * spans pages.
+ *
+ * @var TableRowGroup[]
+ */
+ protected $_headers;
+
+ /**
+ * Table footer rows. Each table footer is duplicated when a table
+ * spans pages.
+ *
+ * @var TableRowGroup[]
+ */
+ protected $_footers;
+
+ /**
+ * Class constructor
+ *
+ * @param Frame $frame the frame to decorate
+ * @param Dompdf $dompdf
+ */
+ public function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ $this->_cellmap = new Cellmap($this);
+
+ if ($frame->get_style()->table_layout === "fixed") {
+ $this->_cellmap->set_layout_fixed(true);
+ }
+
+ $this->_headers = [];
+ $this->_footers = [];
+ }
+
+ public function reset()
+ {
+ parent::reset();
+ $this->_cellmap->reset();
+ $this->_headers = [];
+ $this->_footers = [];
+ $this->_reflower->reset();
+ }
+
+ //........................................................................
+
+ /**
+ * Split the table at $row. $row and all subsequent rows will be
+ * added to the clone. This method is overridden in order to remove
+ * frames from the cellmap properly.
+ */
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ parent::split($child, $page_break, $forced);
+ return;
+ }
+
+ // If $child is a header or if it is the first non-header row, do
+ // not duplicate headers, simply move the table to the next page.
+ if (count($this->_headers)
+ && !in_array($child, $this->_headers, true)
+ && !in_array($child->get_prev_sibling(), $this->_headers, true)
+ ) {
+ $first_header = null;
+
+ // Insert copies of the table headers before $child
+ foreach ($this->_headers as $header) {
+
+ $new_header = $header->deep_copy();
+
+ if (is_null($first_header)) {
+ $first_header = $new_header;
+ }
+
+ $this->insert_child_before($new_header, $child);
+ }
+
+ parent::split($first_header, $page_break, $forced);
+
+ } elseif (in_array($child->get_style()->display, self::ROW_GROUPS, true)) {
+
+ // Individual rows should have already been handled
+ parent::split($child, $page_break, $forced);
+
+ } else {
+
+ $iter = $child;
+
+ while ($iter) {
+ $this->_cellmap->remove_row($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ parent::split($child, $page_break, $forced);
+ }
+ }
+
+ public function copy(DOMNode $node)
+ {
+ $deco = parent::copy($node);
+
+ // In order to keep columns' widths through pages
+ $deco->_cellmap->set_columns($this->_cellmap->get_columns());
+ $deco->_cellmap->lock_columns();
+
+ return $deco;
+ }
+
+ /**
+ * Static function to locate the parent table of a frame
+ *
+ * @param Frame $frame
+ *
+ * @return Table the table that is an ancestor of $frame
+ */
+ public static function find_parent_table(Frame $frame)
+ {
+ while ($frame = $frame->get_parent()) {
+ if ($frame->is_table()) {
+ break;
+ }
+ }
+
+ return $frame;
+ }
+
+ /**
+ * Return this table's Cellmap
+ *
+ * @return Cellmap
+ */
+ public function get_cellmap()
+ {
+ return $this->_cellmap;
+ }
+
+ //........................................................................
+
+ /**
+ * Check for text nodes between valid table children that only contain white
+ * space, except if white space is to be preserved.
+ *
+ * @param AbstractFrameDecorator $frame
+ *
+ * @return bool
+ */
+ private function isEmptyTextNode(AbstractFrameDecorator $frame): bool
+ {
+ // This is based on the white-space pattern in `FrameReflower\Text`,
+ // i.e. only match on collapsible white space
+ $wsPattern = '/^[^\S\xA0\x{202F}\x{2007}]*$/u';
+ $validChildOrNull = function ($frame) {
+ return $frame === null
+ || in_array($frame->get_style()->display, self::VALID_CHILDREN, true);
+ };
+
+ return $frame instanceof Text
+ && !$frame->is_pre()
+ && preg_match($wsPattern, $frame->get_text())
+ && $validChildOrNull($frame->get_prev_sibling())
+ && $validChildOrNull($frame->get_next_sibling());
+ }
+
+ /**
+ * Restructure tree so that the table has the correct structure. Misplaced
+ * children are appropriately wrapped in anonymous row groups, rows, and
+ * cells.
+ *
+ * https://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
+ */
+ public function normalize(): void
+ {
+ $column_caption = ["table-column-group", "table-column", "table-caption"];
+ $children = iterator_to_array($this->get_children());
+ $tbody = null;
+
+ foreach ($children as $child) {
+ $display = $child->get_style()->display;
+
+ if (in_array($display, self::ROW_GROUPS, true)) {
+ // Reset anonymous tbody
+ $tbody = null;
+
+ // Add headers and footers
+ if ($display === "table-header-group") {
+ $this->_headers[] = $child;
+ } elseif ($display === "table-footer-group") {
+ $this->_footers[] = $child;
+ }
+ continue;
+ }
+
+ if (in_array($display, $column_caption, true)) {
+ continue;
+ }
+
+ // Remove empty text nodes between valid children
+ if ($this->isEmptyTextNode($child)) {
+ $this->remove_child($child);
+ continue;
+ }
+
+ // Catch consecutive misplaced frames within a single anonymous group
+ if ($tbody === null) {
+ $tbody = $this->create_anonymous_child("tbody", "table-row-group");
+ $this->insert_child_before($tbody, $child);
+ }
+
+ $tbody->append_child($child);
+ }
+
+ // Handle empty table: Make sure there is at least one row group
+ if (!$this->get_first_child()) {
+ $tbody = $this->create_anonymous_child("tbody", "table-row-group");
+ $this->append_child($tbody);
+ }
+
+ foreach ($this->get_children() as $child) {
+ $display = $child->get_style()->display;
+
+ if (in_array($display, self::ROW_GROUPS, true)) {
+ $this->normalizeRowGroup($child);
+ }
+ }
+ }
+
+ private function normalizeRowGroup(AbstractFrameDecorator $frame): void
+ {
+ $children = iterator_to_array($frame->get_children());
+ $tr = null;
+
+ foreach ($children as $child) {
+ $display = $child->get_style()->display;
+
+ if ($display === "table-row") {
+ // Reset anonymous tr
+ $tr = null;
+ continue;
+ }
+
+ // Remove empty text nodes between valid children
+ if ($this->isEmptyTextNode($child)) {
+ $frame->remove_child($child);
+ continue;
+ }
+
+ // Catch consecutive misplaced frames within a single anonymous row
+ if ($tr === null) {
+ $tr = $frame->create_anonymous_child("tr", "table-row");
+ $frame->insert_child_before($tr, $child);
+ }
+
+ $tr->append_child($child);
+ }
+
+ // Handle empty row group: Make sure there is at least one row
+ if (!$frame->get_first_child()) {
+ $tr = $frame->create_anonymous_child("tr", "table-row");
+ $frame->append_child($tr);
+ }
+
+ foreach ($frame->get_children() as $child) {
+ $this->normalizeRow($child);
+ }
+ }
+
+ private function normalizeRow(AbstractFrameDecorator $frame): void
+ {
+ $children = iterator_to_array($frame->get_children());
+ $td = null;
+
+ foreach ($children as $child) {
+ $display = $child->get_style()->display;
+
+ if ($display === "table-cell") {
+ // Reset anonymous td
+ $td = null;
+ continue;
+ }
+
+ // Remove empty text nodes between valid children
+ if ($this->isEmptyTextNode($child)) {
+ $frame->remove_child($child);
+ continue;
+ }
+
+ // Catch consecutive misplaced frames within a single anonymous cell
+ if ($td === null) {
+ $td = $frame->create_anonymous_child("td", "table-cell");
+ $frame->insert_child_before($td, $child);
+ }
+
+ $td->append_child($child);
+ }
+
+ // Handle empty row: Make sure there is at least one cell
+ if (!$frame->get_first_child()) {
+ $td = $frame->create_anonymous_child("td", "table-cell");
+ $frame->append_child($td);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php
new file mode 100644
index 0000000..d382164
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+
+/**
+ * Decorates table cells for layout
+ *
+ * @package dompdf
+ */
+class TableCell extends BlockFrameDecorator
+{
+
+ protected $_resolved_borders;
+ protected $_content_height;
+
+ //........................................................................
+
+ /**
+ * TableCell constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ $this->_resolved_borders = [];
+ $this->_content_height = 0;
+ }
+
+ //........................................................................
+
+ function reset()
+ {
+ parent::reset();
+ $this->_resolved_borders = [];
+ $this->_content_height = 0;
+ $this->_frame->reset();
+ }
+
+ /**
+ * @return int
+ */
+ function get_content_height()
+ {
+ return $this->_content_height;
+ }
+
+ /**
+ * @param $height
+ */
+ function set_content_height($height)
+ {
+ $this->_content_height = $height;
+ }
+
+ /**
+ * @param $height
+ */
+ function set_cell_height($height)
+ {
+ $style = $this->get_style();
+ $v_space = (float)$style->length_in_pt(
+ [
+ $style->margin_top,
+ $style->padding_top,
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->padding_bottom,
+ $style->margin_bottom
+ ],
+ (float)$style->length_in_pt($style->height)
+ );
+
+ $new_height = $height - $v_space;
+ $style->set_used("height", $new_height);
+
+ if ($new_height > $this->_content_height) {
+ $y_offset = 0;
+
+ // Adjust our vertical alignment
+ switch ($style->vertical_align) {
+ default:
+ case "baseline":
+ // FIXME: this isn't right
+
+ case "top":
+ // Don't need to do anything
+ return;
+
+ case "middle":
+ $y_offset = ($new_height - $this->_content_height) / 2;
+ break;
+
+ case "bottom":
+ $y_offset = $new_height - $this->_content_height;
+ break;
+ }
+
+ if ($y_offset) {
+ // Move our children
+ foreach ($this->get_line_boxes() as $line) {
+ foreach ($line->get_frames() as $frame) {
+ $frame->move(0, $y_offset);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @param $side
+ * @param $border_spec
+ */
+ function set_resolved_border($side, $border_spec)
+ {
+ $this->_resolved_borders[$side] = $border_spec;
+ }
+
+ /**
+ * @param $side
+ * @return mixed
+ */
+ function get_resolved_border($side)
+ {
+ return $this->_resolved_borders[$side];
+ }
+
+ /**
+ * @return array
+ */
+ function get_resolved_borders()
+ {
+ return $this->_resolved_borders;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php
new file mode 100644
index 0000000..ba985c9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+
+/**
+ * Decorates Frames for table row layout
+ *
+ * @package dompdf
+ */
+class TableRow extends AbstractFrameDecorator
+{
+ /**
+ * TableRow constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRowGroup.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRowGroup.php
new file mode 100644
index 0000000..00fe43a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/TableRowGroup.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+
+/**
+ * Table row group decorator
+ *
+ * Overrides split() method for tbody, thead & tfoot elements
+ *
+ * @package dompdf
+ */
+class TableRowGroup extends AbstractFrameDecorator
+{
+
+ /**
+ * Class constructor
+ *
+ * @param Frame $frame Frame to decorate
+ * @param Dompdf $dompdf Current dompdf instance
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ parent::__construct($frame, $dompdf);
+ }
+
+ /**
+ * Split the row group at the given child and remove all subsequent child
+ * rows and all subsequent row groups from the cellmap.
+ */
+ public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
+ {
+ if (is_null($child)) {
+ parent::split($child, $page_break, $forced);
+ return;
+ }
+
+ // Remove child & all subsequent rows from the cellmap
+ /** @var Table $parent */
+ $parent = $this->get_parent();
+ $cellmap = $parent->get_cellmap();
+ $iter = $child;
+
+ while ($iter) {
+ $cellmap->remove_row($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ // Remove all subsequent row groups from the cellmap
+ $iter = $this->get_next_sibling();
+
+ while ($iter) {
+ $cellmap->remove_row_group($iter);
+ $iter = $iter->get_next_sibling();
+ }
+
+ // If we are splitting at the first child remove the
+ // table-row-group from the cellmap as well
+ if ($child === $this->get_first_child()) {
+ $cellmap->remove_row_group($this);
+ parent::split(null, $page_break, $forced);
+ return;
+ }
+
+ $cellmap->update_row_group($this, $child->get_prev_sibling());
+ parent::split($child, $page_break, $forced);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php
new file mode 100644
index 0000000..9d66934
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameDecorator/Text.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameDecorator;
+
+use Dompdf\Dompdf;
+use Dompdf\Frame;
+use Dompdf\Exception;
+
+/**
+ * Decorates Frame objects for text layout
+ *
+ * @package dompdf
+ */
+class Text extends AbstractFrameDecorator
+{
+ /**
+ * @var float
+ */
+ protected $text_spacing;
+
+ /**
+ * Text constructor.
+ * @param Frame $frame
+ * @param Dompdf $dompdf
+ * @throws Exception
+ */
+ function __construct(Frame $frame, Dompdf $dompdf)
+ {
+ if (!$frame->is_text_node()) {
+ throw new Exception("Text_Decorator can only be applied to #text nodes.");
+ }
+
+ parent::__construct($frame, $dompdf);
+ $this->text_spacing = 0.0;
+ }
+
+ function reset()
+ {
+ parent::reset();
+ $this->text_spacing = 0.0;
+ }
+
+ // Accessor methods
+
+ /**
+ * @return float
+ */
+ public function get_text_spacing(): float
+ {
+ return $this->text_spacing;
+ }
+
+ /**
+ * @return string
+ */
+ function get_text()
+ {
+ // FIXME: this should be in a child class (and is incorrect)
+// if ( $this->_frame->get_style()->content !== "normal" ) {
+// $this->_frame->get_node()->data = $this->_frame->get_style()->content;
+// $this->_frame->get_style()->content = "normal";
+// }
+
+// Helpers::pre_r("---");
+// $style = $this->_frame->get_style();
+// var_dump($text = $this->_frame->get_node()->data);
+// var_dump($asc = utf8_decode($text));
+// for ($i = 0; $i < strlen($asc); $i++)
+// Helpers::pre_r("$i: " . $asc[$i] . " - " . ord($asc[$i]));
+// Helpers::pre_r("width: " . $this->_dompdf->getFontMetrics()->getTextWidth($text, $style->font_family, $style->font_size));
+
+ return $this->_frame->get_node()->data;
+ }
+
+ //........................................................................
+
+ /**
+ * Vertical padding, border, and margin do not apply when determining the
+ * height for inline frames.
+ *
+ * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
+ *
+ * The vertical padding, border and margin of an inline, non-replaced box
+ * start at the top and bottom of the content area, not the
+ * 'line-height'. But only the 'line-height' is used to calculate the
+ * height of the line box.
+ *
+ * @return float
+ */
+ public function get_margin_height(): float
+ {
+ // This function is also called in add_frame_to_line() and is used to
+ // determine the line height
+ $style = $this->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $fontHeight = $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
+
+ return ($style->line_height / ($size > 0 ? $size : 1)) * $fontHeight;
+ }
+
+ public function get_padding_box(): array
+ {
+ $style = $this->_frame->get_style();
+ $pb = $this->_frame->get_padding_box();
+ $pb[3] = $pb["h"] = (float) $style->length_in_pt($style->height);
+ return $pb;
+ }
+
+ /**
+ * @param float $spacing
+ */
+ public function set_text_spacing(float $spacing): void
+ {
+ $this->text_spacing = $spacing;
+ $this->recalculate_width();
+ }
+
+ /**
+ * Recalculate the text width
+ *
+ * @return float
+ */
+ public function recalculate_width(): float
+ {
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+ $style = $this->get_style();
+ $text = $this->get_text();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $this->text_spacing + $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+ $text_width = $fontMetrics->getTextWidth($text, $font, $size, $word_spacing, $letter_spacing);
+
+ $style->set_used("width", $text_width);
+ return $text_width;
+ }
+
+ // Text manipulation methods
+
+ /**
+ * Split the text in this frame at the offset specified. The remaining
+ * text is added as a sibling frame following this one and is returned.
+ *
+ * @param int $offset
+ * @return Frame|null
+ */
+ function split_text($offset)
+ {
+ if ($offset == 0) {
+ return null;
+ }
+
+ $split = $this->_frame->get_node()->splitText($offset);
+ if ($split === false) {
+ return null;
+ }
+
+ $deco = $this->copy($split);
+
+ $p = $this->get_parent();
+ $p->insert_child_after($deco, $this, false);
+
+ if ($p instanceof Inline) {
+ $p->split($deco);
+ }
+
+ return $deco;
+ }
+
+ /**
+ * @param int $offset
+ * @param int $count
+ */
+ function delete_text($offset, $count)
+ {
+ $this->_frame->get_node()->deleteData($offset, $count);
+ }
+
+ /**
+ * @param string $text
+ */
+ function set_text($text)
+ {
+ $this->_frame->get_node()->data = $text;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php
new file mode 100644
index 0000000..fa0cc51
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php
@@ -0,0 +1,705 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\Dompdf;
+use Dompdf\Helpers;
+use Dompdf\Frame;
+use Dompdf\Frame\Factory;
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\Block;
+
+/**
+ * Base reflower class
+ *
+ * Reflower objects are responsible for determining the width and height of
+ * individual frames. They also create line and page breaks as necessary.
+ *
+ * @package dompdf
+ */
+abstract class AbstractFrameReflower
+{
+
+ /**
+ * Frame for this reflower
+ *
+ * @var AbstractFrameDecorator
+ */
+ protected $_frame;
+
+ /**
+ * Cached min/max child size
+ *
+ * @var array
+ */
+ protected $_min_max_child_cache;
+
+ /**
+ * Cached min/max size
+ *
+ * @var array
+ */
+ protected $_min_max_cache;
+
+ /**
+ * AbstractFrameReflower constructor.
+ * @param AbstractFrameDecorator $frame
+ */
+ function __construct(AbstractFrameDecorator $frame)
+ {
+ $this->_frame = $frame;
+ $this->_min_max_child_cache = null;
+ $this->_min_max_cache = null;
+ }
+
+ /**
+ * @return Dompdf
+ */
+ function get_dompdf()
+ {
+ return $this->_frame->get_dompdf();
+ }
+
+ public function reset(): void
+ {
+ $this->_min_max_child_cache = null;
+ $this->_min_max_cache = null;
+ }
+
+ /**
+ * Determine the actual containing block for absolute and fixed position.
+ *
+ * https://www.w3.org/TR/CSS21/visudet.html#containing-block-details
+ */
+ protected function determine_absolute_containing_block(): void
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ switch ($style->position) {
+ case "absolute":
+ $parent = $frame->find_positioned_parent();
+ if ($parent !== $frame->get_root()) {
+ $parent_style = $parent->get_style();
+ $parent_padding_box = $parent->get_padding_box();
+ //FIXME: an accurate measure of the positioned parent height
+ // is not possible until reflow has completed;
+ // we'll fall back to the parent's containing block,
+ // which is wrong for auto-height parents
+ if ($parent_style->height === "auto") {
+ $parent_containing_block = $parent->get_containing_block();
+ $containing_block_height = $parent_containing_block["h"] -
+ (float)$parent_style->length_in_pt([
+ $parent_style->margin_top,
+ $parent_style->margin_bottom,
+ $parent_style->border_top_width,
+ $parent_style->border_bottom_width
+ ], $parent_containing_block["w"]);
+ } else {
+ $containing_block_height = $parent_padding_box["h"];
+ }
+ $frame->set_containing_block($parent_padding_box["x"], $parent_padding_box["y"], $parent_padding_box["w"], $containing_block_height);
+ break;
+ }
+ case "fixed":
+ $initial_cb = $frame->get_root()->get_first_child()->get_containing_block();
+ $frame->set_containing_block($initial_cb["x"], $initial_cb["y"], $initial_cb["w"], $initial_cb["h"]);
+ break;
+ default:
+ // Nothing to do, containing block already set via parent
+ break;
+ }
+ }
+
+ /**
+ * Collapse frames margins
+ * http://www.w3.org/TR/CSS21/box.html#collapsing-margins
+ */
+ protected function _collapse_margins(): void
+ {
+ $frame = $this->_frame;
+
+ // Margins of float/absolutely positioned/inline-level elements do not collapse
+ if (!$frame->is_in_flow() || $frame->is_inline_level()
+ || $frame->get_root() === $frame || $frame->get_parent() === $frame->get_root()
+ ) {
+ return;
+ }
+
+ $cb = $frame->get_containing_block();
+ $style = $frame->get_style();
+
+ $t = $style->length_in_pt($style->margin_top, $cb["w"]);
+ $b = $style->length_in_pt($style->margin_bottom, $cb["w"]);
+
+ // Handle 'auto' values
+ if ($t === "auto") {
+ $style->set_used("margin_top", 0.0);
+ $t = 0.0;
+ }
+
+ if ($b === "auto") {
+ $style->set_used("margin_bottom", 0.0);
+ $b = 0.0;
+ }
+
+ // Collapse vertical margins:
+ $n = $frame->get_next_sibling();
+ if ( $n && !($n->is_block_level() && $n->is_in_flow()) ) {
+ while ($n = $n->get_next_sibling()) {
+ if ($n->is_block_level() && $n->is_in_flow()) {
+ break;
+ }
+
+ if (!$n->get_first_child()) {
+ $n = null;
+ break;
+ }
+ }
+ }
+
+ if ($n) {
+ $n_style = $n->get_style();
+ $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["w"]);
+
+ $b = $this->get_collapsed_margin_length($b, $n_t);
+ $style->set_used("margin_bottom", $b);
+ $n_style->set_used("margin_top", 0.0);
+ }
+
+ // Collapse our first child's margin, if there is no border or padding
+ if ($style->border_top_width == 0 && $style->length_in_pt($style->padding_top) == 0) {
+ $f = $this->_frame->get_first_child();
+ if ( $f && !($f->is_block_level() && $f->is_in_flow()) ) {
+ while ($f = $f->get_next_sibling()) {
+ if ($f->is_block_level() && $f->is_in_flow()) {
+ break;
+ }
+
+ if (!$f->get_first_child()) {
+ $f = null;
+ break;
+ }
+ }
+ }
+
+ // Margins are collapsed only between block-level boxes
+ if ($f) {
+ $f_style = $f->get_style();
+ $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["w"]);
+
+ $t = $this->get_collapsed_margin_length($t, $f_t);
+ $style->set_used("margin_top", $t);
+ $f_style->set_used("margin_top", 0.0);
+ }
+ }
+
+ // Collapse our last child's margin, if there is no border or padding
+ if ($style->border_bottom_width == 0 && $style->length_in_pt($style->padding_bottom) == 0) {
+ $l = $this->_frame->get_last_child();
+ if ( $l && !($l->is_block_level() && $l->is_in_flow()) ) {
+ while ($l = $l->get_prev_sibling()) {
+ if ($l->is_block_level() && $l->is_in_flow()) {
+ break;
+ }
+
+ if (!$l->get_last_child()) {
+ $l = null;
+ break;
+ }
+ }
+ }
+
+ // Margins are collapsed only between block-level boxes
+ if ($l) {
+ $l_style = $l->get_style();
+ $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["w"]);
+
+ $b = $this->get_collapsed_margin_length($b, $l_b);
+ $style->set_used("margin_bottom", $b);
+ $l_style->set_used("margin_bottom", 0.0);
+ }
+ }
+ }
+
+ /**
+ * Get the combined (collapsed) length of two adjoining margins.
+ *
+ * See http://www.w3.org/TR/CSS21/box.html#collapsing-margins.
+ *
+ * @param float $l1
+ * @param float $l2
+ *
+ * @return float
+ */
+ private function get_collapsed_margin_length(float $l1, float $l2): float
+ {
+ if ($l1 < 0 && $l2 < 0) {
+ return min($l1, $l2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
+ }
+
+ if ($l1 < 0 || $l2 < 0) {
+ return $l1 + $l2; // x + y = x - abs(y), if y < 0
+ }
+
+ return max($l1, $l2);
+ }
+
+ /**
+ * Handle relative positioning according to
+ * https://www.w3.org/TR/CSS21/visuren.html#relative-positioning.
+ *
+ * @param AbstractFrameDecorator $frame The frame to handle.
+ */
+ protected function position_relative(AbstractFrameDecorator $frame): void
+ {
+ $style = $frame->get_style();
+
+ if ($style->position === "relative") {
+ $cb = $frame->get_containing_block();
+ $top = $style->length_in_pt($style->top, $cb["h"]);
+ $right = $style->length_in_pt($style->right, $cb["w"]);
+ $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
+ $left = $style->length_in_pt($style->left, $cb["w"]);
+
+ // FIXME RTL case:
+ // if ($left !== "auto" && $right !== "auto") $left = -$right;
+ if ($left === "auto" && $right === "auto") {
+ $left = 0;
+ } elseif ($left === "auto") {
+ $left = -$right;
+ }
+
+ if ($top === "auto" && $bottom === "auto") {
+ $top = 0;
+ } elseif ($top === "auto") {
+ $top = -$bottom;
+ }
+
+ $frame->move($left, $top);
+ }
+ }
+
+ /**
+ * @param Block|null $block
+ */
+ abstract function reflow(Block $block = null);
+
+ /**
+ * Resolve the `min-width` property.
+ *
+ * Resolves to 0 if not set or if a percentage and the containing-block
+ * width is not defined.
+ *
+ * @param float|null $cbw Width of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_min_width(?float $cbw): float
+ {
+ $style = $this->_frame->get_style();
+ $min_width = $style->min_width;
+
+ return $min_width !== "auto"
+ ? $style->length_in_pt($min_width, $cbw ?? 0)
+ : 0.0;
+ }
+
+ /**
+ * Resolve the `max-width` property.
+ *
+ * Resolves to `INF` if not set or if a percentage and the containing-block
+ * width is not defined.
+ *
+ * @param float|null $cbw Width of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_max_width(?float $cbw): float
+ {
+ $style = $this->_frame->get_style();
+ $max_width = $style->max_width;
+
+ return $max_width !== "none"
+ ? $style->length_in_pt($max_width, $cbw ?? INF)
+ : INF;
+ }
+
+ /**
+ * Resolve the `min-height` property.
+ *
+ * Resolves to 0 if not set or if a percentage and the containing-block
+ * height is not defined.
+ *
+ * @param float|null $cbh Height of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_min_height(?float $cbh): float
+ {
+ $style = $this->_frame->get_style();
+ $min_height = $style->min_height;
+
+ return $min_height !== "auto"
+ ? $style->length_in_pt($min_height, $cbh ?? 0)
+ : 0.0;
+ }
+
+ /**
+ * Resolve the `max-height` property.
+ *
+ * Resolves to `INF` if not set or if a percentage and the containing-block
+ * height is not defined.
+ *
+ * @param float|null $cbh Height of the containing block.
+ *
+ * @return float
+ */
+ protected function resolve_max_height(?float $cbh): float
+ {
+ $style = $this->_frame->get_style();
+ $max_height = $style->max_height;
+
+ return $max_height !== "none"
+ ? $style->length_in_pt($style->max_height, $cbh ?? INF)
+ : INF;
+ }
+
+ /**
+ * Get the minimum and maximum preferred width of the contents of the frame,
+ * as requested by its children.
+ *
+ * @return array A two-element array of min and max width.
+ */
+ public function get_min_max_child_width(): array
+ {
+ if (!is_null($this->_min_max_child_cache)) {
+ return $this->_min_max_child_cache;
+ }
+
+ $low = [];
+ $high = [];
+
+ for ($iter = $this->_frame->get_children(); $iter->valid(); $iter->next()) {
+ $inline_min = 0;
+ $inline_max = 0;
+
+ // Add all adjacent inline widths together to calculate max width
+ while ($iter->valid() && ($iter->current()->is_inline_level() || $iter->current()->get_style()->display === "-dompdf-image")) {
+ /** @var AbstractFrameDecorator */
+ $child = $iter->current();
+ $child->get_reflower()->_set_content();
+ $minmax = $child->get_min_max_width();
+
+ if (in_array($child->get_style()->white_space, ["pre", "nowrap"], true)) {
+ $inline_min += $minmax["min"];
+ } else {
+ $low[] = $minmax["min"];
+ }
+
+ $inline_max += $minmax["max"];
+ $iter->next();
+ }
+
+ if ($inline_min > 0) {
+ $low[] = $inline_min;
+ }
+ if ($inline_max > 0) {
+ $high[] = $inline_max;
+ }
+
+ // Skip children with absolute position
+ if ($iter->valid() && !$iter->current()->is_absolute()) {
+ /** @var AbstractFrameDecorator */
+ $child = $iter->current();
+ $child->get_reflower()->_set_content();
+ list($low[], $high[]) = $child->get_min_max_width();
+ }
+ }
+
+ $min = count($low) ? max($low) : 0;
+ $max = count($high) ? max($high) : 0;
+
+ return $this->_min_max_child_cache = [$min, $max];
+ }
+
+ /**
+ * Get the minimum and maximum preferred content-box width of the frame.
+ *
+ * @return array A two-element array of min and max width.
+ */
+ public function get_min_max_content_width(): array
+ {
+ return $this->get_min_max_child_width();
+ }
+
+ /**
+ * Get the minimum and maximum preferred border-box width of the frame.
+ *
+ * Required for shrink-to-fit width calculation, as used in automatic table
+ * layout, absolute positioning, float and inline-block. This provides a
+ * basic implementation. Child classes should override this or
+ * `get_min_max_content_width` as necessary.
+ *
+ * @return array An array `[0 => min, 1 => max, "min" => min, "max" => max]`
+ * of min and max width.
+ */
+ public function get_min_max_width(): array
+ {
+ if (!is_null($this->_min_max_cache)) {
+ return $this->_min_max_cache;
+ }
+
+ $style = $this->_frame->get_style();
+ [$min, $max] = $this->get_min_max_content_width();
+
+ // Account for margins, borders, and padding
+ $dims = [
+ $style->padding_left,
+ $style->padding_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->margin_left,
+ $style->margin_right
+ ];
+
+ // The containing block is not defined yet, treat percentages as 0
+ $delta = (float) $style->length_in_pt($dims, 0);
+ $min += $delta;
+ $max += $delta;
+
+ return $this->_min_max_cache = [$min, $max, "min" => $min, "max" => $max];
+ }
+
+ /**
+ * Parses a CSS string containing quotes and escaped hex characters
+ *
+ * @param $string string The CSS string to parse
+ * @param $single_trim
+ * @return string
+ */
+ protected function _parse_string($string, $single_trim = false)
+ {
+ if ($single_trim) {
+ $string = preg_replace('/^[\"\']/', "", $string);
+ $string = preg_replace('/[\"\']$/', "", $string);
+ } else {
+ $string = trim($string, "'\"");
+ }
+
+ $string = str_replace(["\\\n", '\\"', "\\'"],
+ ["", '"', "'"], $string);
+
+ // Convert escaped hex characters into ascii characters (e.g. \A => newline)
+ $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/",
+ function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); },
+ $string);
+ return $string;
+ }
+
+ /**
+ * Parses a CSS "quotes" property
+ *
+ * https://www.w3.org/TR/css-content-3/#quotes
+ *
+ * @return array An array of pairs of quotes
+ */
+ protected function _parse_quotes(): array
+ {
+ $quotes = $this->_frame->get_style()->quotes;
+
+ if ($quotes === "none") {
+ return [];
+ }
+
+ if ($quotes === "auto") {
+ // TODO: Use typographically appropriate quotes for the current
+ // language here
+ return [['"', '"'], ["'", "'"]];
+ }
+
+ // Matches quote types
+ $re = '/(\'[^\']*\')|(\"[^\"]*\")/';
+
+ // Split on spaces, except within quotes
+ if (!preg_match_all($re, $quotes, $matches, PREG_SET_ORDER)) {
+ return [];
+ }
+
+ $quotes_array = [];
+ foreach ($matches as $_quote) {
+ $quotes_array[] = $this->_parse_string($_quote[0], true);
+ }
+
+ return array_chunk($quotes_array, 2);
+ }
+
+ /**
+ * Parses the CSS "content" property
+ *
+ * https://www.w3.org/TR/CSS21/generate.html#content
+ *
+ * @return string The resulting string
+ */
+ protected function _parse_content(): string
+ {
+ $style = $this->_frame->get_style();
+ $content = $style->content;
+
+ if ($content === "normal" || $content === "none") {
+ return "";
+ }
+
+ $quotes = $this->_parse_quotes();
+ $text = "";
+
+ foreach ($content as $val) {
+ // String
+ if (in_array(mb_substr($val, 0, 1), ['"', "'"], true)) {
+ $text .= $this->_parse_string($val);
+ continue;
+ }
+
+ $val = mb_strtolower($val);
+
+ // Keywords
+ if ($val === "open-quote") {
+ // FIXME: Take quotation depth into account
+ if (isset($quotes[0][0])) {
+ $text .= $quotes[0][0];
+ }
+ continue;
+ } elseif ($val === "close-quote") {
+ // FIXME: Take quotation depth into account
+ if (isset($quotes[0][1])) {
+ $text .= $quotes[0][1];
+ }
+ continue;
+ } elseif ($val === "no-open-quote") {
+ // FIXME: Increment quotation depth
+ continue;
+ } elseif ($val === "no-close-quote") {
+ // FIXME: Decrement quotation depth
+ continue;
+ }
+
+ // attr()
+ if (mb_substr($val, 0, 5) === "attr(") {
+ $i = mb_strpos($val, ")");
+ if ($i === false) {
+ continue;
+ }
+
+ $attr = trim(mb_substr($val, 5, $i - 5));
+ if ($attr === "") {
+ continue;
+ }
+
+ $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr);
+ continue;
+ }
+
+ // counter()/counters()
+ if (mb_substr($val, 0, 7) === "counter") {
+ // Handle counter() references:
+ // http://www.w3.org/TR/CSS21/generate.html#content
+
+ $i = mb_strpos($val, ")");
+ if ($i === false) {
+ continue;
+ }
+
+ preg_match('/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]*)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i', $val, $args);
+ $counter_id = $args[3];
+
+ if (strtolower($args[1]) === "counter") {
+ // counter(name [,style])
+ if (isset($args[5])) {
+ $type = trim($args[5]);
+ } else {
+ $type = "decimal";
+ }
+ $p = $this->_frame->lookup_counter_frame($counter_id);
+
+ $text .= $p->counter_value($counter_id, $type);
+ } elseif (strtolower($args[1]) === "counters") {
+ // counters(name, string [,style])
+ if (isset($args[5])) {
+ $string = $this->_parse_string($args[5]);
+ } else {
+ $string = "";
+ }
+
+ if (isset($args[7])) {
+ $type = trim($args[7]);
+ } else {
+ $type = "decimal";
+ }
+
+ $p = $this->_frame->lookup_counter_frame($counter_id);
+ $tmp = [];
+ while ($p) {
+ // We only want to use the counter values when they actually increment the counter
+ if (array_key_exists($counter_id, $p->_counters)) {
+ array_unshift($tmp, $p->counter_value($counter_id, $type));
+ }
+ $p = $p->lookup_counter_frame($counter_id);
+ }
+ $text .= implode($string, $tmp);
+ } else {
+ // countertops?
+ }
+
+ continue;
+ }
+ }
+
+ return $text;
+ }
+
+ /**
+ * Handle counters and set generated content if the frame is a
+ * generated-content frame.
+ */
+ protected function _set_content(): void
+ {
+ $frame = $this->_frame;
+
+ if ($frame->content_set) {
+ return;
+ }
+
+ $style = $frame->get_style();
+
+ if (($reset = $style->counter_reset) !== "none") {
+ $frame->reset_counters($reset);
+ }
+
+ if (($increment = $style->counter_increment) !== "none") {
+ $frame->increment_counters($increment);
+ }
+
+ if ($frame->get_node()->nodeName === "dompdf_generated") {
+ $content = $this->_parse_content();
+
+ if ($content !== "") {
+ $node = $frame->get_node()->ownerDocument->createTextNode($content);
+
+ $new_style = $style->get_stylesheet()->create_style();
+ $new_style->inherit($style);
+
+ $new_frame = new Frame($node);
+ $new_frame->set_style($new_style);
+
+ Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root());
+ $frame->append_child($new_frame);
+ }
+ }
+
+ $frame->content_set = true;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php
new file mode 100644
index 0000000..f9e50a6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Block.php
@@ -0,0 +1,949 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
+use Dompdf\FrameDecorator\Text as TextFrameDecorator;
+use Dompdf\Exception;
+use Dompdf\Css\Style;
+use Dompdf\Helpers;
+
+/**
+ * Reflows block frames
+ *
+ * @package dompdf
+ */
+class Block extends AbstractFrameReflower
+{
+ // Minimum line width to justify, as fraction of available width
+ const MIN_JUSTIFY_WIDTH = 0.80;
+
+ /**
+ * Frame for this reflower
+ *
+ * @var BlockFrameDecorator
+ */
+ protected $_frame;
+
+ function __construct(BlockFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * Calculate the ideal used value for the width property as per:
+ * http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins
+ *
+ * @param float $width
+ *
+ * @return array
+ */
+ protected function _calculate_width($width)
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $absolute = $frame->is_absolute();
+
+ $cb = $frame->get_containing_block();
+ $w = $cb["w"];
+
+ $rm = $style->length_in_pt($style->margin_right, $w);
+ $lm = $style->length_in_pt($style->margin_left, $w);
+
+ $left = $style->length_in_pt($style->left, $w);
+ $right = $style->length_in_pt($style->right, $w);
+
+ // Handle 'auto' values
+ $dims = [$style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right,
+ $width !== "auto" ? $width : 0,
+ $rm !== "auto" ? $rm : 0,
+ $lm !== "auto" ? $lm : 0];
+
+ // absolutely positioned boxes take the 'left' and 'right' properties into account
+ if ($absolute) {
+ $dims[] = $left !== "auto" ? $left : 0;
+ $dims[] = $right !== "auto" ? $right : 0;
+ }
+
+ $sum = (float)$style->length_in_pt($dims, $w);
+
+ // Compare to the containing block
+ $diff = $w - $sum;
+
+ if ($absolute) {
+ // Absolutely positioned
+ // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
+
+ if ($width === "auto" || $left === "auto" || $right === "auto") {
+ // "all of the three are 'auto'" logic + otherwise case
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+
+ $block_parent = $frame->find_block_parent();
+ $parent_content = $block_parent->get_content_box();
+ $line = $block_parent->get_current_line_box();
+
+ // TODO: This is the in-flow inline position. Use the in-flow
+ // block position if the original display type is block-level
+ $inflow_x = $parent_content["x"] - $cb["x"] + $line->left + $line->w;
+
+ if ($width === "auto" && $left === "auto" && $right === "auto") {
+ // rule 3, per instruction preceding rule set
+ // shrink-to-fit width
+ $left = $inflow_x;
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff - $left), $max);
+ $right = $diff - $left - $width;
+ } elseif ($width === "auto" && $left === "auto") {
+ // rule 1
+ // shrink-to-fit width
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff), $max);
+ $left = $diff - $width;
+ } elseif ($width === "auto" && $right === "auto") {
+ // rule 3
+ // shrink-to-fit width
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff), $max);
+ $right = $diff - $width;
+ } elseif ($left === "auto" && $right === "auto") {
+ // rule 2
+ $left = $inflow_x;
+ $right = $diff - $left;
+ } elseif ($left === "auto") {
+ // rule 4
+ $left = $diff;
+ } elseif ($width === "auto") {
+ // rule 5
+ $width = max($diff, 0);
+ } else {
+ // $right === "auto"
+ // rule 6
+ $right = $diff;
+ }
+ } else {
+ // "none of the three are 'auto'" logic described in paragraph preceding the rules
+ if ($diff >= 0) {
+ if ($lm === "auto" && $rm === "auto") {
+ $lm = $rm = $diff / 2;
+ } elseif ($lm === "auto") {
+ $lm = $diff;
+ } elseif ($rm === "auto") {
+ $rm = $diff;
+ }
+ } else {
+ // over-constrained, solve for right
+ $right = $right + $diff;
+
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+ }
+ }
+ } elseif ($style->float !== "none" || $style->display === "inline-block") {
+ // Shrink-to-fit width for float and inline block
+ // https://www.w3.org/TR/CSS21/visudet.html#float-width
+ // https://www.w3.org/TR/CSS21/visudet.html#inlineblock-width
+
+ if ($width === "auto") {
+ [$min, $max] = $this->get_min_max_child_width();
+ $width = min(max($min, $diff), $max);
+ }
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+ } else {
+ // Block-level, normal flow
+ // https://www.w3.org/TR/CSS21/visudet.html#blockwidth
+
+ if ($diff >= 0) {
+ // Find auto properties and get them to take up the slack
+ if ($width === "auto") {
+ $width = $diff;
+
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ if ($rm === "auto") {
+ $rm = 0;
+ }
+ } elseif ($lm === "auto" && $rm === "auto") {
+ $lm = $rm = $diff / 2;
+ } elseif ($lm === "auto") {
+ $lm = $diff;
+ } elseif ($rm === "auto") {
+ $rm = $diff;
+ }
+ } else {
+ // We are over constrained--set margin-right to the difference
+ $rm = (float) $rm + $diff;
+
+ if ($width === "auto") {
+ $width = 0;
+ }
+ if ($lm === "auto") {
+ $lm = 0;
+ }
+ }
+ }
+
+ return [
+ "width" => $width,
+ "margin_left" => $lm,
+ "margin_right" => $rm,
+ "left" => $left,
+ "right" => $right,
+ ];
+ }
+
+ /**
+ * Call the above function, but resolve max/min widths
+ *
+ * @throws Exception
+ * @return array
+ */
+ protected function _calculate_restricted_width()
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ if (!isset($cb["w"])) {
+ throw new Exception("Box property calculation requires containing block width");
+ }
+
+ $width = $style->length_in_pt($style->width, $cb["w"]);
+
+ $values = $this->_calculate_width($width);
+ $margin_left = $values["margin_left"];
+ $margin_right = $values["margin_right"];
+ $width = $values["width"];
+ $left = $values["left"];
+ $right = $values["right"];
+
+ // Handle min/max width
+ // https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ $min_width = $this->resolve_min_width($cb["w"]);
+ $max_width = $this->resolve_max_width($cb["w"]);
+
+ if ($width > $max_width) {
+ $values = $this->_calculate_width($max_width);
+ $margin_left = $values["margin_left"];
+ $margin_right = $values["margin_right"];
+ $width = $values["width"];
+ $left = $values["left"];
+ $right = $values["right"];
+ }
+
+ if ($width < $min_width) {
+ $values = $this->_calculate_width($min_width);
+ $margin_left = $values["margin_left"];
+ $margin_right = $values["margin_right"];
+ $width = $values["width"];
+ $left = $values["left"];
+ $right = $values["right"];
+ }
+
+ return [$width, $margin_left, $margin_right, $left, $right];
+ }
+
+ /**
+ * Determine the unrestricted height of content within the block
+ * not by adding each line's height, but by getting the last line's position.
+ * This because lines could have been pushed lower by a clearing element.
+ *
+ * @return float
+ */
+ protected function _calculate_content_height()
+ {
+ $height = 0;
+ $lines = $this->_frame->get_line_boxes();
+ if (count($lines) > 0) {
+ $last_line = end($lines);
+ $content_box = $this->_frame->get_content_box();
+ $height = $last_line->y + $last_line->h - $content_box["y"];
+ }
+ return $height;
+ }
+
+ /**
+ * Determine the frame's restricted height
+ *
+ * @return array
+ */
+ protected function _calculate_restricted_height()
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $content_height = $this->_calculate_content_height();
+ $cb = $frame->get_containing_block();
+
+ $height = $style->length_in_pt($style->height, $cb["h"]);
+ $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
+ $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
+
+ $top = $style->length_in_pt($style->top, $cb["h"]);
+ $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
+
+ if ($frame->is_absolute()) {
+ // Absolutely positioned
+ // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height
+
+ $h_dims = [
+ $top !== "auto" ? $top : 0,
+ $height !== "auto" ? $height : 0,
+ $bottom !== "auto" ? $bottom : 0
+ ];
+ $w_dims = [
+ $style->margin_top !== "auto" ? $style->margin_top : 0,
+ $style->padding_top,
+ $style->border_top_width,
+ $style->border_bottom_width,
+ $style->padding_bottom,
+ $style->margin_bottom !== "auto" ? $style->margin_bottom : 0
+ ];
+
+ $sum = (float)$style->length_in_pt($h_dims, $cb["h"])
+ + (float)$style->length_in_pt($w_dims, $cb["w"]);
+
+ $diff = $cb["h"] - $sum;
+
+ if ($height === "auto" || $top === "auto" || $bottom === "auto") {
+ // "all of the three are 'auto'" logic + otherwise case
+ if ($margin_top === "auto") {
+ $margin_top = 0;
+ }
+ if ($margin_bottom === "auto") {
+ $margin_bottom = 0;
+ }
+
+ $block_parent = $frame->find_block_parent();
+ $current_line = $block_parent->get_current_line_box();
+
+ // TODO: This is the in-flow inline position. Use the in-flow
+ // block position if the original display type is block-level
+ $inflow_y = $current_line->y - $cb["y"];
+
+ if ($height === "auto" && $top === "auto" && $bottom === "auto") {
+ // rule 3, per instruction preceding rule set
+ $top = $inflow_y;
+ $height = $content_height;
+ $bottom = $diff - $top - $height;
+ } elseif ($height === "auto" && $top === "auto") {
+ // rule 1
+ $height = $content_height;
+ $top = $diff - $height;
+ } elseif ($height === "auto" && $bottom === "auto") {
+ // rule 3
+ $height = $content_height;
+ $bottom = $diff - $height;
+ } elseif ($top === "auto" && $bottom === "auto") {
+ // rule 2
+ $top = $inflow_y;
+ $bottom = $diff - $top;
+ } elseif ($top === "auto") {
+ // rule 4
+ $top = $diff;
+ } elseif ($height === "auto") {
+ // rule 5
+ $height = max($diff, 0);
+ } else {
+ // $bottom === "auto"
+ // rule 6
+ $bottom = $diff;
+ }
+ } else {
+ // "none of the three are 'auto'" logic described in paragraph preceding the rules
+ if ($diff >= 0) {
+ if ($margin_top === "auto" && $margin_bottom === "auto") {
+ $margin_top = $margin_bottom = $diff / 2;
+ } elseif ($margin_top === "auto") {
+ $margin_top = $diff;
+ } elseif ($margin_bottom === "auto") {
+ $margin_bottom = $diff;
+ }
+ } else {
+ // over-constrained, solve for bottom
+ $bottom = $bottom + $diff;
+
+ if ($margin_top === "auto") {
+ $margin_top = 0;
+ }
+ if ($margin_bottom === "auto") {
+ $margin_bottom = 0;
+ }
+ }
+ }
+ } else {
+ // https://www.w3.org/TR/CSS21/visudet.html#normal-block
+ // https://www.w3.org/TR/CSS21/visudet.html#block-root-margin
+
+ if ($height === "auto") {
+ $height = $content_height;
+ }
+ if ($margin_top === "auto") {
+ $margin_top = 0;
+ }
+ if ($margin_bottom === "auto") {
+ $margin_bottom = 0;
+ }
+
+ // Handle min/max height
+ // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ $min_height = $this->resolve_min_height($cb["h"]);
+ $max_height = $this->resolve_max_height($cb["h"]);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+ }
+
+ // TODO: Need to also take min/max height into account for absolute
+ // positioning, using similar logic to the `_calculate_width`/
+ // `calculate_restricted_width` split above. The non-absolute case
+ // can simply clamp height within min/max, as margins and offsets are
+ // not affected
+
+ return [$height, $margin_top, $margin_bottom, $top, $bottom];
+ }
+
+ /**
+ * Adjust the justification of each of our lines.
+ * http://www.w3.org/TR/CSS21/text.html#propdef-text-align
+ */
+ protected function _text_align()
+ {
+ $style = $this->_frame->get_style();
+ $w = $this->_frame->get_containing_block("w");
+ $width = (float)$style->length_in_pt($style->width, $w);
+ $text_indent = (float)$style->length_in_pt($style->text_indent, $w);
+
+ switch ($style->text_align) {
+ default:
+ case "left":
+ foreach ($this->_frame->get_line_boxes() as $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ if ($line->left) {
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($line->left, 0);
+ }
+ }
+ }
+ break;
+
+ case "right":
+ foreach ($this->_frame->get_line_boxes() as $i => $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ $indent = $i === 0 ? $text_indent : 0;
+ $dx = $width - $line->w - $line->right - $indent;
+
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($dx, 0);
+ }
+ }
+ break;
+
+ case "justify":
+ // We justify all lines except the last one, unless the frame
+ // has been split, in which case the actual last line is part of
+ // the split-off frame
+ $lines = $this->_frame->get_line_boxes();
+ $last_line_index = $this->_frame->is_split ? null : count($lines) - 1;
+
+ foreach ($lines as $i => $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ if ($line->left) {
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($line->left, 0);
+ }
+ }
+
+ if ($line->br || $i === $last_line_index) {
+ continue;
+ }
+
+ $frames = $line->get_frames();
+ $other_frame_count = 0;
+
+ foreach ($frames as $frame) {
+ if (!($frame instanceof TextFrameDecorator)) {
+ $other_frame_count++;
+ }
+ }
+
+ $word_count = $line->wc + $other_frame_count;
+
+ // Set the spacing for each child
+ if ($word_count > 1) {
+ $indent = $i === 0 ? $text_indent : 0;
+ $spacing = ($width - $line->get_width() - $indent) / ($word_count - 1);
+ } else {
+ $spacing = 0;
+ }
+
+ $dx = 0;
+ foreach ($frames as $frame) {
+ if ($frame instanceof TextFrameDecorator) {
+ $text = $frame->get_text();
+ $spaces = mb_substr_count($text, " ");
+
+ $frame->move($dx, 0);
+ $frame->set_text_spacing($spacing);
+
+ $dx += $spaces * $spacing;
+ } else {
+ $frame->move($dx, 0);
+ }
+ }
+
+ // The line (should) now occupy the entire width
+ $line->w = $width;
+ }
+ break;
+
+ case "center":
+ case "centre":
+ foreach ($this->_frame->get_line_boxes() as $i => $line) {
+ if (!$line->inline) {
+ continue;
+ }
+
+ $line->trim_trailing_ws();
+
+ $indent = $i === 0 ? $text_indent : 0;
+ $dx = ($width + $line->left - $line->w - $line->right - $indent) / 2;
+
+ foreach ($line->frames_to_align() as $frame) {
+ $frame->move($dx, 0);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * Align inline children vertically.
+ * Aligns each child vertically after each line is reflowed
+ */
+ function vertical_align()
+ {
+ $fontMetrics = $this->get_dompdf()->getFontMetrics();
+
+ foreach ($this->_frame->get_line_boxes() as $line) {
+ $height = $line->h;
+
+ // Move all markers to the top of the line box
+ foreach ($line->get_list_markers() as $marker) {
+ $x = $marker->get_position("x");
+ $marker->set_position($x, $line->y);
+ }
+
+ foreach ($line->frames_to_align() as $frame) {
+ $style = $frame->get_style();
+ $isInlineBlock = $style->display !== "inline"
+ && $style->display !== "-dompdf-list-bullet";
+
+ $baseline = $fontMetrics->getFontBaseline($style->font_family, $style->font_size);
+ $y_offset = 0;
+
+ //FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?)
+ if ($isInlineBlock) {
+ // Workaround: Skip vertical alignment if the frame is the
+ // only one one the line, excluding empty text frames, which
+ // may be the result of trailing white space
+ // FIXME: This special case should be removed once vertical
+ // alignment is properly fixed
+ $skip = true;
+
+ foreach ($line->get_frames() as $other) {
+ if ($other !== $frame
+ && !($other->is_text_node() && $other->get_node()->nodeValue === "")
+ ) {
+ $skip = false;
+ break;
+ }
+ }
+
+ if ($skip) {
+ continue;
+ }
+
+ $marginHeight = $frame->get_margin_height();
+ $imageHeightDiff = $height * 0.8 - $marginHeight;
+
+ $align = $frame->get_style()->vertical_align;
+ if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
+ switch ($align) {
+ case "middle":
+ $y_offset = $imageHeightDiff / 2;
+ break;
+
+ case "sub":
+ $y_offset = 0.3 * $height + $imageHeightDiff;
+ break;
+
+ case "super":
+ $y_offset = -0.2 * $height + $imageHeightDiff;
+ break;
+
+ case "text-top": // FIXME: this should be the height of the frame minus the height of the text
+ $y_offset = $height - $style->line_height;
+ break;
+
+ case "top":
+ break;
+
+ case "text-bottom": // FIXME: align bottom of image with the descender?
+ case "bottom":
+ $y_offset = 0.3 * $height + $imageHeightDiff;
+ break;
+
+ case "baseline":
+ default:
+ $y_offset = $imageHeightDiff;
+ break;
+ }
+ } else {
+ $y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - $marginHeight;
+ }
+ } else {
+ $parent = $frame->get_parent();
+ if ($parent instanceof TableCellFrameDecorator) {
+ $align = "baseline";
+ } else {
+ $align = $parent->get_style()->vertical_align;
+ }
+ if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
+ switch ($align) {
+ case "middle":
+ $y_offset = ($height * 0.8 - $baseline) / 2;
+ break;
+
+ case "sub":
+ $y_offset = $height * 0.8 - $baseline * 0.5;
+ break;
+
+ case "super":
+ $y_offset = $height * 0.8 - $baseline * 1.4;
+ break;
+
+ case "text-top":
+ case "top": // Not strictly accurate, but good enough for now
+ break;
+
+ case "text-bottom":
+ case "bottom":
+ $y_offset = $height * 0.8 - $baseline;
+ break;
+
+ case "baseline":
+ default:
+ $y_offset = $height * 0.8 - $baseline;
+ break;
+ }
+ } else {
+ $y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size);
+ }
+ }
+
+ if ($y_offset !== 0) {
+ $frame->move(0, $y_offset);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param AbstractFrameDecorator $child
+ */
+ function process_clear(AbstractFrameDecorator $child)
+ {
+ $child_style = $child->get_style();
+ $root = $this->_frame->get_root();
+
+ // Handle "clear"
+ if ($child_style->clear !== "none") {
+ //TODO: this is a WIP for handling clear/float frames that are in between inline frames
+ if ($child->get_prev_sibling() !== null) {
+ $this->_frame->add_line();
+ }
+ if ($child_style->float !== "none" && $child->get_next_sibling()) {
+ $this->_frame->set_current_line_number($this->_frame->get_current_line_number() - 1);
+ }
+
+ $lowest_y = $root->get_lowest_float_offset($child);
+
+ // If a float is still applying, we handle it
+ if ($lowest_y) {
+ if ($child->is_in_flow()) {
+ $line_box = $this->_frame->get_current_line_box();
+ $line_box->y = $lowest_y + $child->get_margin_height();
+ $line_box->left = 0;
+ $line_box->right = 0;
+ }
+
+ $child->move(0, $lowest_y - $child->get_position("y"));
+ }
+ }
+ }
+
+ /**
+ * @param AbstractFrameDecorator $child
+ * @param float $cb_x
+ * @param float $cb_w
+ */
+ function process_float(AbstractFrameDecorator $child, $cb_x, $cb_w)
+ {
+ $child_style = $child->get_style();
+ $root = $this->_frame->get_root();
+
+ // Handle "float"
+ if ($child_style->float !== "none") {
+ $root->add_floating_frame($child);
+
+ // Remove next frame's beginning whitespace
+ $next = $child->get_next_sibling();
+ if ($next && $next instanceof TextFrameDecorator) {
+ $next->set_text(ltrim($next->get_text()));
+ }
+
+ $line_box = $this->_frame->get_current_line_box();
+ list($old_x, $old_y) = $child->get_position();
+
+ $float_x = $cb_x;
+ $float_y = $old_y;
+ $float_w = $child->get_margin_width();
+
+ if ($child_style->clear === "none") {
+ switch ($child_style->float) {
+ case "left":
+ $float_x += $line_box->left;
+ break;
+ case "right":
+ $float_x += ($cb_w - $line_box->right - $float_w);
+ break;
+ }
+ } else {
+ if ($child_style->float === "right") {
+ $float_x += ($cb_w - $float_w);
+ }
+ }
+
+ if ($cb_w < $float_x + $float_w - $old_x) {
+ // TODO handle when floating elements don't fit
+ }
+
+ $line_box->get_float_offsets();
+
+ if ($child->_float_next_line) {
+ $float_y += $line_box->h;
+ }
+
+ $child->set_position($float_x, $float_y);
+ $child->move($float_x - $old_x, $float_y - $old_y, true);
+ }
+ }
+
+ /**
+ * @param BlockFrameDecorator $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+
+ // Check if a page break is forced
+ $page = $this->_frame->get_root();
+ $page->check_forced_page_break($this->_frame);
+
+ // Bail if the page is full
+ if ($page->is_full()) {
+ return;
+ }
+
+ $this->determine_absolute_containing_block();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ // Inherit any dangling list markers
+ if ($block && $this->_frame->is_in_flow()) {
+ $this->_frame->inherit_dangling_markers($block);
+ }
+
+ // Collapse margins if required
+ $this->_collapse_margins();
+
+ $style = $this->_frame->get_style();
+ $cb = $this->_frame->get_containing_block();
+
+ // Determine the constraints imposed by this frame: calculate the width
+ // of the content area:
+ [$width, $margin_left, $margin_right, $left, $right] = $this->_calculate_restricted_width();
+
+ // Store the calculated properties
+ $style->set_used("width", $width);
+ $style->set_used("margin_left", $margin_left);
+ $style->set_used("margin_right", $margin_right);
+ $style->set_used("left", $left);
+ $style->set_used("right", $right);
+
+ $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
+ $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
+
+ $auto_top = $style->top === "auto";
+ $auto_margin_top = $margin_top === "auto";
+
+ // Update the position
+ $this->_frame->position();
+ [$x, $y] = $this->_frame->get_position();
+
+ // Adjust the first line based on the text-indent property
+ $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
+ $this->_frame->increase_line_width($indent);
+
+ // Determine the content edge
+ $top = (float)$style->length_in_pt([
+ $margin_top !== "auto" ? $margin_top : 0,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cb["w"]);
+ $bottom = (float)$style->length_in_pt([
+ $margin_bottom !== "auto" ? $margin_bottom : 0,
+ $style->border_bottom_width,
+ $style->padding_bottom
+ ], $cb["w"]);
+
+ $cb_x = $x + (float)$margin_left + (float)$style->length_in_pt([$style->border_left_width,
+ $style->padding_left], $cb["w"]);
+
+ $cb_y = $y + $top;
+
+ $height = $style->length_in_pt($style->height, $cb["h"]);
+ if ($height === "auto") {
+ $height = ($cb["h"] + $cb["y"]) - $bottom - $cb_y;
+ }
+
+ // Set the y position of the first line in this block
+ $line_box = $this->_frame->get_current_line_box();
+ $line_box->y = $cb_y;
+ $line_box->get_float_offsets();
+
+ // Set the containing blocks and reflow each child
+ foreach ($this->_frame->get_children() as $child) {
+ $child->set_containing_block($cb_x, $cb_y, $width, $height);
+ $this->process_clear($child);
+ $child->reflow($this->_frame);
+
+ // Check for a page break before the child
+ $page->check_page_break($child);
+
+ // Don't add the child to the line if a page break has occurred
+ // before it (possibly via a descendant), in which case it has been
+ // reset, including its position
+ if ($page->is_full() && $child->get_position("x") === null) {
+ break;
+ }
+
+ $this->process_float($child, $cb_x, $width);
+ }
+
+ // Stop reflow if a page break has occurred before the frame, in which
+ // case it has been reset, including its position
+ if ($page->is_full() && $this->_frame->get_position("x") === null) {
+ return;
+ }
+
+ // Determine our height
+ [$height, $margin_top, $margin_bottom, $top, $bottom] = $this->_calculate_restricted_height();
+
+ $style->set_used("height", $height);
+ $style->set_used("margin_top", $margin_top);
+ $style->set_used("margin_bottom", $margin_bottom);
+ $style->set_used("top", $top);
+ $style->set_used("bottom", $bottom);
+
+ if ($this->_frame->is_absolute()) {
+ if ($auto_top) {
+ $this->_frame->move(0, $top);
+ }
+ if ($auto_margin_top) {
+ $this->_frame->move(0, $margin_top, true);
+ }
+ }
+
+ $this->_text_align();
+ $this->vertical_align();
+
+ // Handle relative positioning
+ foreach ($this->_frame->get_children() as $child) {
+ $this->position_relative($child);
+ }
+
+ if ($block && $this->_frame->is_in_flow()) {
+ $block->add_frame_to_line($this->_frame);
+
+ if ($this->_frame->is_block_level()) {
+ $block->add_line();
+ }
+ }
+ }
+
+ public function get_min_max_content_width(): array
+ {
+ // TODO: While the containing block is not set yet on the frame, it can
+ // already be determined in some cases due to fixed dimensions on the
+ // ancestor forming the containing block. In such cases, percentage
+ // values could be resolved here
+ $style = $this->_frame->get_style();
+ $width = $style->width;
+ $fixed_width = $width !== "auto" && !Helpers::is_percent($width);
+
+ // If the frame has a specified width, then we don't need to check
+ // its children
+ if ($fixed_width) {
+ $min = (float) $style->length_in_pt($width, 0);
+ $max = $min;
+ } else {
+ [$min, $max] = $this->get_min_max_child_width();
+ }
+
+ // Handle min/max width style properties
+ $min_width = $this->resolve_min_width(null);
+ $max_width = $this->resolve_max_width(null);
+ $min = Helpers::clamp($min, $min_width, $max_width);
+ $max = Helpers::clamp($max, $min_width, $max_width);
+
+ return [$min, $max];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php
new file mode 100644
index 0000000..eae3408
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Image.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\Helpers;
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Image as ImageFrameDecorator;
+
+/**
+ * Image reflower class
+ *
+ * @package dompdf
+ */
+class Image extends AbstractFrameReflower
+{
+
+ /**
+ * Image constructor.
+ * @param ImageFrameDecorator $frame
+ */
+ function __construct(ImageFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ $this->determine_absolute_containing_block();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ //FLOAT
+ //$frame = $this->_frame;
+ //$page = $frame->get_root();
+
+ //if ($frame->get_style()->float !== "none" ) {
+ // $page->add_floating_frame($this);
+ //}
+
+ $this->resolve_dimensions();
+ $this->resolve_margins();
+
+ $frame = $this->_frame;
+ $frame->position();
+
+ if ($block && $frame->is_in_flow()) {
+ $block->add_frame_to_line($frame);
+ }
+ }
+
+ public function get_min_max_content_width(): array
+ {
+ // TODO: While the containing block is not set yet on the frame, it can
+ // already be determined in some cases due to fixed dimensions on the
+ // ancestor forming the containing block. In such cases, percentage
+ // values could be resolved here
+ $style = $this->_frame->get_style();
+
+ [$width] = $this->calculate_size(null, null);
+ $min_width = $this->resolve_min_width(null);
+ $percent_width = Helpers::is_percent($style->width)
+ || Helpers::is_percent($style->max_width)
+ || ($style->width === "auto"
+ && (Helpers::is_percent($style->height) || Helpers::is_percent($style->max_height)));
+
+ // Use the specified min width as minimum when width or max width depend
+ // on the containing block and cannot be resolved yet. This mimics
+ // browser behavior
+ $min = $percent_width ? $min_width : $width;
+ $max = $width;
+
+ return [$min, $max];
+ }
+
+ /**
+ * Calculate width and height, accounting for min/max constraints.
+ *
+ * * https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
+ * * https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
+ * * https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ * * https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ *
+ * @param float|null $cbw Width of the containing block.
+ * @param float|null $cbh Height of the containing block.
+ *
+ * @return float[]
+ */
+ protected function calculate_size(?float $cbw, ?float $cbh): array
+ {
+ /** @var ImageFrameDecorator */
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ $computed_width = $style->width;
+ $computed_height = $style->height;
+
+ $width = $cbw === null && Helpers::is_percent($computed_width)
+ ? "auto"
+ : $style->length_in_pt($computed_width, $cbw ?? 0);
+ $height = $cbh === null && Helpers::is_percent($computed_height)
+ ? "auto"
+ : $style->length_in_pt($computed_height, $cbh ?? 0);
+ $min_width = $this->resolve_min_width($cbw);
+ $max_width = $this->resolve_max_width($cbw);
+ $min_height = $this->resolve_min_height($cbh);
+ $max_height = $this->resolve_max_height($cbh);
+
+ if ($width === "auto" && $height === "auto") {
+ // Use intrinsic dimensions, resampled to pt
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ $w = $frame->resample($img_width);
+ $h = $frame->resample($img_height);
+
+ // Resolve min/max constraints according to the constraint-violation
+ // table in https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
+ $max_width = max($min_width, $max_width);
+ $max_height = max($min_height, $max_height);
+
+ if (($w > $max_width && $h <= $max_height)
+ || ($w > $max_width && $h > $max_height && $max_width / $w <= $max_height / $h)
+ || ($w < $min_width && $h > $min_height)
+ || ($w < $min_width && $h < $min_height && $min_width / $w > $min_height / $h)
+ ) {
+ $width = Helpers::clamp($w, $min_width, $max_width);
+ $height = $width * ($img_height / $img_width);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+ } else {
+ $height = Helpers::clamp($h, $min_height, $max_height);
+ $width = $height * ($img_width / $img_height);
+ $width = Helpers::clamp($width, $min_width, $max_width);
+ }
+ } elseif ($height === "auto") {
+ // Width is fixed, scale height according to aspect ratio
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ $width = Helpers::clamp((float) $width, $min_width, $max_width);
+ $height = $width * ($img_height / $img_width);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+ } elseif ($width === "auto") {
+ // Height is fixed, scale width according to aspect ratio
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ $height = Helpers::clamp((float) $height, $min_height, $max_height);
+ $width = $height * ($img_width / $img_height);
+ $width = Helpers::clamp($width, $min_width, $max_width);
+ } else {
+ // Width and height are fixed
+ $width = Helpers::clamp((float) $width, $min_width, $max_width);
+ $height = Helpers::clamp((float) $height, $min_height, $max_height);
+ }
+
+ return [$width, $height];
+ }
+
+ protected function resolve_dimensions(): void
+ {
+ /** @var ImageFrameDecorator */
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ $debug_png = $this->get_dompdf()->getOptions()->getDebugPng();
+
+ if ($debug_png) {
+ [$img_width, $img_height] = $frame->get_intrinsic_dimensions();
+ print "resolve_dimensions() " .
+ $frame->get_style()->width . " " .
+ $frame->get_style()->height . ";" .
+ $frame->get_parent()->get_style()->width . " " .
+ $frame->get_parent()->get_style()->height . ";" .
+ $frame->get_parent()->get_parent()->get_style()->width . " " .
+ $frame->get_parent()->get_parent()->get_style()->height . ";" .
+ $img_width . " " .
+ $img_height . "|";
+ }
+
+ [, , $cbw, $cbh] = $frame->get_containing_block();
+ [$width, $height] = $this->calculate_size($cbw, $cbh);
+
+ if ($debug_png) {
+ print $width . " " . $height . ";";
+ }
+
+ $style->set_used("width", $width);
+ $style->set_used("height", $height);
+ }
+
+ protected function resolve_margins(): void
+ {
+ // Only handle the inline case for now
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
+ $style = $this->_frame->get_style();
+
+ if ($style->margin_left === "auto") {
+ $style->set_used("margin_left", 0.0);
+ }
+ if ($style->margin_right === "auto") {
+ $style->set_used("margin_right", 0.0);
+ }
+ if ($style->margin_top === "auto") {
+ $style->set_used("margin_top", 0.0);
+ }
+ if ($style->margin_bottom === "auto") {
+ $style->set_used("margin_bottom", 0.0);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php
new file mode 100644
index 0000000..34a6686
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Inline.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Inline as InlineFrameDecorator;
+use Dompdf\FrameDecorator\Text as TextFrameDecorator;
+
+/**
+ * Reflows inline frames
+ *
+ * @package dompdf
+ */
+class Inline extends AbstractFrameReflower
+{
+ /**
+ * Inline constructor.
+ * @param InlineFrameDecorator $frame
+ */
+ function __construct(InlineFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * Handle reflow of empty inline frames.
+ *
+ * Regular inline frames are positioned together with their text (or inline)
+ * children after child reflow. Empty inline frames have no children that
+ * could determine the positioning, so they need to be handled separately.
+ *
+ * @param BlockFrameDecorator $block
+ */
+ protected function reflow_empty(BlockFrameDecorator $block): void
+ {
+ /** @var InlineFrameDecorator */
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ // Resolve width, so the margin width can be checked
+ $style->set_used("width", 0.0);
+
+ $cb = $frame->get_containing_block();
+ $line = $block->get_current_line_box();
+ $width = $frame->get_margin_width();
+
+ if ($width > ($cb["w"] - $line->left - $line->w - $line->right)) {
+ $block->add_line();
+
+ // Find the appropriate inline ancestor to split
+ $child = $frame;
+ $p = $child->get_parent();
+ while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if ($p instanceof InlineFrameDecorator) {
+ // Split parent and stop current reflow. Reflow continues
+ // via child-reflow loop of split parent
+ $p->split($child);
+ return;
+ }
+ }
+
+ $frame->position();
+ $block->add_frame_to_line($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ /** @var InlineFrameDecorator */
+ $frame = $this->_frame;
+
+ // Check if a page break is forced
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $style = $frame->get_style();
+
+ // Resolve auto margins
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-width
+ // https://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
+ if ($style->margin_left === "auto") {
+ $style->set_used("margin_left", 0.0);
+ }
+ if ($style->margin_right === "auto") {
+ $style->set_used("margin_right", 0.0);
+ }
+ if ($style->margin_top === "auto") {
+ $style->set_used("margin_top", 0.0);
+ }
+ if ($style->margin_bottom === "auto") {
+ $style->set_used("margin_bottom", 0.0);
+ }
+
+ // Handle line breaks
+ if ($frame->get_node()->nodeName === "br") {
+ if ($block) {
+ $line = $block->get_current_line_box();
+ $frame->set_containing_line($line);
+ $block->maximize_line_height($frame->get_margin_height(), $frame);
+ $block->add_line(true);
+
+ $next = $frame->get_next_sibling();
+ $p = $frame->get_parent();
+
+ if ($next && $p instanceof InlineFrameDecorator) {
+ $p->split($next);
+ }
+ }
+ return;
+ }
+
+ // Handle empty inline frames
+ if (!$frame->get_first_child()) {
+ if ($block) {
+ $this->reflow_empty($block);
+ }
+ return;
+ }
+
+ // Add our margin, padding & border to the first and last children
+ if (($f = $frame->get_first_child()) && $f instanceof TextFrameDecorator) {
+ $f_style = $f->get_style();
+ $f_style->margin_left = $style->margin_left;
+ $f_style->padding_left = $style->padding_left;
+ $f_style->border_left_width = $style->border_left_width;
+ $f_style->border_left_style = $style->border_left_style;
+ $f_style->border_left_color = $style->border_left_color;
+ }
+
+ if (($l = $frame->get_last_child()) && $l instanceof TextFrameDecorator) {
+ $l_style = $l->get_style();
+ $l_style->margin_right = $style->margin_right;
+ $l_style->padding_right = $style->padding_right;
+ $l_style->border_right_width = $style->border_right_width;
+ $l_style->border_right_style = $style->border_right_style;
+ $l_style->border_right_color = $style->border_right_color;
+ }
+
+ $cb = $frame->get_containing_block();
+
+ // Set the containing blocks and reflow each child. The containing
+ // block is not changed by line boxes.
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($cb);
+ $child->reflow($block);
+
+ // Stop reflow if the frame has been reset by a line or page break
+ // due to child reflow
+ if (!$frame->content_set) {
+ return;
+ }
+ }
+
+ if (!$frame->get_first_child()) {
+ return;
+ }
+
+ // Assume the position of the first child
+ [$x, $y] = $frame->get_first_child()->get_position();
+ $frame->set_position($x, $y);
+
+ // Handle relative positioning
+ foreach ($frame->get_children() as $child) {
+ $this->position_relative($child);
+ }
+
+ if ($block) {
+ $block->add_frame_to_line($frame);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php
new file mode 100644
index 0000000..f735bae
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator;
+
+/**
+ * Reflows list bullets
+ *
+ * @package dompdf
+ */
+class ListBullet extends AbstractFrameReflower
+{
+
+ /**
+ * ListBullet constructor.
+ * @param ListBulletFrameDecorator $frame
+ */
+ function __construct(ListBulletFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ /** @var ListBulletFrameDecorator */
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+
+ $style->set_used("width", $frame->get_width());
+ $frame->position();
+
+ if ($style->list_style_position === "inside") {
+ $block->add_frame_to_line($frame);
+ } else {
+ $block->add_dangling_marker($frame);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php
new file mode 100644
index 0000000..8d7e558
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/NullFrameReflower.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+
+/**
+ * Dummy reflower
+ *
+ * @package dompdf
+ */
+class NullFrameReflower extends AbstractFrameReflower
+{
+
+ /**
+ * NullFrameReflower constructor.
+ * @param Frame $frame
+ */
+ function __construct(Frame $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ return;
+ }
+
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Page.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Page.php
new file mode 100644
index 0000000..923865b
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Page.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Page as PageFrameDecorator;
+
+/**
+ * Reflows pages
+ *
+ * @package dompdf
+ */
+class Page extends AbstractFrameReflower
+{
+
+ /**
+ * Cache of the callbacks array
+ *
+ * @var array
+ */
+ private $_callbacks;
+
+ /**
+ * Cache of the canvas
+ *
+ * @var \Dompdf\Canvas
+ */
+ private $_canvas;
+
+ /**
+ * Page constructor.
+ * @param PageFrameDecorator $frame
+ */
+ function __construct(PageFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param PageFrameDecorator $frame
+ * @param int $page_number
+ */
+ function apply_page_style(Frame $frame, $page_number)
+ {
+ $style = $frame->get_style();
+ $page_styles = $style->get_stylesheet()->get_page_styles();
+
+ // http://www.w3.org/TR/CSS21/page.html#page-selectors
+ if (count($page_styles) > 1) {
+ $odd = $page_number % 2 == 1;
+ $first = $page_number == 1;
+
+ $style = clone $page_styles["base"];
+
+ // FIXME RTL
+ if ($odd && isset($page_styles[":right"])) {
+ $style->merge($page_styles[":right"]);
+ }
+
+ if ($odd && isset($page_styles[":odd"])) {
+ $style->merge($page_styles[":odd"]);
+ }
+
+ // FIXME RTL
+ if (!$odd && isset($page_styles[":left"])) {
+ $style->merge($page_styles[":left"]);
+ }
+
+ if (!$odd && isset($page_styles[":even"])) {
+ $style->merge($page_styles[":even"]);
+ }
+
+ if ($first && isset($page_styles[":first"])) {
+ $style->merge($page_styles[":first"]);
+ }
+
+ $frame->set_style($style);
+ }
+
+ $frame->calculate_bottom_page_edge();
+ }
+
+ /**
+ * Paged layout:
+ * http://www.w3.org/TR/CSS21/page.html
+ *
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ /** @var PageFrameDecorator $frame */
+ $frame = $this->_frame;
+ $child = $frame->get_first_child();
+ $fixed_children = [];
+ $prev_child = null;
+ $current_page = 0;
+
+ while ($child) {
+ $this->apply_page_style($frame, $current_page + 1);
+
+ $style = $frame->get_style();
+
+ // Pages are only concerned with margins
+ $cb = $frame->get_containing_block();
+ $left = (float)$style->length_in_pt($style->margin_left, $cb["w"]);
+ $right = (float)$style->length_in_pt($style->margin_right, $cb["w"]);
+ $top = (float)$style->length_in_pt($style->margin_top, $cb["h"]);
+ $bottom = (float)$style->length_in_pt($style->margin_bottom, $cb["h"]);
+
+ $content_x = $cb["x"] + $left;
+ $content_y = $cb["y"] + $top;
+ $content_width = $cb["w"] - $left - $right;
+ $content_height = $cb["h"] - $top - $bottom;
+
+ // Only if it's the first page, we save the nodes with a fixed position
+ if ($current_page == 0) {
+ foreach ($child->get_children() as $onechild) {
+ if ($onechild->get_style()->position === "fixed") {
+ $fixed_children[] = $onechild->deep_copy();
+ }
+ }
+ $fixed_children = array_reverse($fixed_children);
+ }
+
+ $child->set_containing_block($content_x, $content_y, $content_width, $content_height);
+
+ // Check for begin reflow callback
+ $this->_check_callbacks("begin_page_reflow", $child);
+
+ //Insert a copy of each node which have a fixed position
+ if ($current_page >= 1) {
+ foreach ($fixed_children as $fixed_child) {
+ $child->insert_child_before($fixed_child->deep_copy(), $child->get_first_child());
+ }
+ }
+
+ $child->reflow();
+ $next_child = $child->get_next_sibling();
+
+ // Check for begin render callback
+ $this->_check_callbacks("begin_page_render", $child);
+
+ // Render the page
+ $frame->get_renderer()->render($child);
+
+ // Check for end render callback
+ $this->_check_callbacks("end_page_render", $child);
+
+ if ($next_child) {
+ $frame->next_page();
+ }
+
+ // Wait to dispose of all frames on the previous page
+ // so callback will have access to them
+ if ($prev_child) {
+ $prev_child->dispose(true);
+ }
+ $prev_child = $child;
+ $child = $next_child;
+ $current_page++;
+ }
+
+ // Dispose of previous page if it still exists
+ if ($prev_child) {
+ $prev_child->dispose(true);
+ }
+ }
+
+ /**
+ * Check for callbacks that need to be performed when a given event
+ * gets triggered on a page
+ *
+ * @param string $event The type of event
+ * @param Frame $frame The frame that event is triggered on
+ */
+ protected function _check_callbacks(string $event, Frame $frame): void
+ {
+ if (!isset($this->_callbacks)) {
+ $dompdf = $this->get_dompdf();
+ $this->_callbacks = $dompdf->getCallbacks();
+ $this->_canvas = $dompdf->getCanvas();
+ }
+
+ if (isset($this->_callbacks[$event])) {
+ $fs = $this->_callbacks[$event];
+ $canvas = $this->_canvas;
+ $fontMetrics = $this->get_dompdf()->getFontMetrics();
+
+ foreach ($fs as $f) {
+ $f($frame, $canvas, $fontMetrics);
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php
new file mode 100644
index 0000000..5173738
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Table.php
@@ -0,0 +1,523 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Table as TableFrameDecorator;
+use Dompdf\Helpers;
+
+/**
+ * Reflows tables
+ *
+ * @package dompdf
+ */
+class Table extends AbstractFrameReflower
+{
+ /**
+ * Frame for this reflower
+ *
+ * @var TableFrameDecorator
+ */
+ protected $_frame;
+
+ /**
+ * Cache of results between call to get_min_max_width and assign_widths
+ *
+ * @var array
+ */
+ protected $_state;
+
+ /**
+ * Table constructor.
+ * @param TableFrameDecorator $frame
+ */
+ function __construct(TableFrameDecorator $frame)
+ {
+ $this->_state = null;
+ parent::__construct($frame);
+ }
+
+ /**
+ * State is held here so it needs to be reset along with the decorator
+ */
+ public function reset(): void
+ {
+ parent::reset();
+ $this->_state = null;
+ }
+
+ protected function _assign_widths()
+ {
+ $style = $this->_frame->get_style();
+
+ // Find the min/max width of the table and sort the columns into
+ // absolute/percent/auto arrays
+ $delta = $this->_state["width_delta"];
+ $min_width = $this->_state["min_width"];
+ $max_width = $this->_state["max_width"];
+ $percent_used = $this->_state["percent_used"];
+ $absolute_used = $this->_state["absolute_used"];
+ $auto_min = $this->_state["auto_min"];
+
+ $absolute =& $this->_state["absolute"];
+ $percent =& $this->_state["percent"];
+ $auto =& $this->_state["auto"];
+
+ // Determine the actual width of the table (excluding borders and
+ // padding)
+ $cb = $this->_frame->get_containing_block();
+ $columns =& $this->_frame->get_cellmap()->get_columns();
+
+ $width = $style->width;
+ $min_table_width = $this->resolve_min_width($cb["w"]) - $delta;
+
+ if ($width !== "auto") {
+ $preferred_width = (float) $style->length_in_pt($width, $cb["w"]) - $delta;
+
+ if ($preferred_width < $min_table_width) {
+ $preferred_width = $min_table_width;
+ }
+
+ if ($preferred_width > $min_width) {
+ $width = $preferred_width;
+ } else {
+ $width = $min_width;
+ }
+
+ } else {
+ if ($max_width + $delta < $cb["w"]) {
+ $width = $max_width;
+ } elseif ($cb["w"] - $delta > $min_width) {
+ $width = $cb["w"] - $delta;
+ } else {
+ $width = $min_width;
+ }
+
+ if ($width < $min_table_width) {
+ $width = $min_table_width;
+ }
+
+ }
+
+ // Store our resolved width
+ $style->set_used("width", $width);
+
+ $cellmap = $this->_frame->get_cellmap();
+
+ if ($cellmap->is_columns_locked()) {
+ return;
+ }
+
+ // If the whole table fits on the page, then assign each column it's max width
+ if ($width == $max_width) {
+ foreach ($columns as $i => $col) {
+ $cellmap->set_column_width($i, $col["max-width"]);
+ }
+
+ return;
+ }
+
+ // Determine leftover and assign it evenly to all columns
+ if ($width > $min_width) {
+ // We have three cases to deal with:
+ //
+ // 1. All columns are auto or absolute width. In this case we
+ // distribute extra space across all auto columns weighted by the
+ // difference between their max and min width, or by max width only
+ // if the width of the table is larger than the max width for all
+ // columns.
+ //
+ // 2. Only absolute widths have been specified, no auto columns. In
+ // this case we distribute extra space across all columns weighted
+ // by their absolute width.
+ //
+ // 3. Percentage widths have been specified. In this case we normalize
+ // the percentage values and try to assign widths as fractions of
+ // the table width. Absolute column widths are fully satisfied and
+ // any remaining space is evenly distributed among all auto columns.
+
+ // Case 1:
+ if ($percent_used == 0 && count($auto)) {
+ foreach ($absolute as $i) {
+ $w = $columns[$i]["min-width"];
+ $cellmap->set_column_width($i, $w);
+ }
+
+ if ($width < $max_width) {
+ $increment = $width - $min_width;
+ $table_delta = $max_width - $min_width;
+
+ foreach ($auto as $i) {
+ $min = $columns[$i]["min-width"];
+ $max = $columns[$i]["max-width"];
+ $col_delta = $max - $min;
+ $w = $min + $increment * ($col_delta / $table_delta);
+ $cellmap->set_column_width($i, $w);
+ }
+ } else {
+ $increment = $width - $max_width;
+ $auto_max = $max_width - $absolute_used;
+
+ foreach ($auto as $i) {
+ $max = $columns[$i]["max-width"];
+ $f = $auto_max > 0 ? $max / $auto_max : 1 / count($auto);
+ $w = $max + $increment * $f;
+ $cellmap->set_column_width($i, $w);
+ }
+ }
+ return;
+ }
+
+ // Case 2:
+ if ($percent_used == 0 && !count($auto)) {
+ $increment = $width - $absolute_used;
+
+ foreach ($absolute as $i) {
+ $abs = $columns[$i]["min-width"];
+ $f = $absolute_used > 0 ? $abs / $absolute_used : 1 / count($absolute);
+ $w = $abs + $increment * $f;
+ $cellmap->set_column_width($i, $w);
+ }
+ return;
+ }
+
+ // Case 3:
+ if ($percent_used > 0) {
+ // Scale percent values if the total percentage is > 100 or
+ // there are no auto values to take up slack
+ if ($percent_used > 100 || count($auto) == 0) {
+ $scale = 100 / $percent_used;
+ } else {
+ $scale = 1;
+ }
+
+ // Account for the minimum space used by the unassigned auto
+ // columns, by the columns with absolute widths, and the
+ // percentage columns following the current one
+ $used_width = $auto_min + $absolute_used;
+
+ foreach ($absolute as $i) {
+ $w = $columns[$i]["min-width"];
+ $cellmap->set_column_width($i, $w);
+ }
+
+ $percent_min = 0;
+
+ foreach ($percent as $i) {
+ $percent_min += $columns[$i]["min-width"];
+ }
+
+ // First-come, first served
+ foreach ($percent as $i) {
+ $min = $columns[$i]["min-width"];
+ $percent_min -= $min;
+ $slack = $width - $used_width - $percent_min;
+
+ $columns[$i]["percent"] *= $scale;
+ $w = min($columns[$i]["percent"] * $width / 100, $slack);
+
+ if ($w < $min) {
+ $w = $min;
+ }
+
+ $cellmap->set_column_width($i, $w);
+ $used_width += $w;
+ }
+
+ // This works because $used_width includes the min-width of each
+ // unassigned column
+ if (count($auto) > 0) {
+ $increment = ($width - $used_width) / count($auto);
+
+ foreach ($auto as $i) {
+ $w = $columns[$i]["min-width"] + $increment;
+ $cellmap->set_column_width($i, $w);
+ }
+ }
+ return;
+ }
+ } else {
+ // We are over-constrained:
+ // Each column gets its minimum width
+ foreach ($columns as $i => $col) {
+ $cellmap->set_column_width($i, $col["min-width"]);
+ }
+ }
+ }
+
+ /**
+ * Determine the frame's height based on min/max height
+ *
+ * @return float
+ */
+ protected function _calculate_height()
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ $height = $style->length_in_pt($style->height, $cb["h"]);
+
+ $cellmap = $frame->get_cellmap();
+ $cellmap->assign_frame_heights();
+ $rows = $cellmap->get_rows();
+
+ // Determine our content height
+ $content_height = 0.0;
+ foreach ($rows as $r) {
+ $content_height += $r["height"];
+ }
+
+ if ($height === "auto") {
+ $height = $content_height;
+ }
+
+ // Handle min/max height
+ // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
+ $min_height = $this->resolve_min_height($cb["h"]);
+ $max_height = $this->resolve_max_height($cb["h"]);
+ $height = Helpers::clamp($height, $min_height, $max_height);
+
+ // Use the content height or the height value, whichever is greater
+ if ($height <= $content_height) {
+ $height = $content_height;
+ } else {
+ // FIXME: Borders and row positions are not properly updated by this
+ // $cellmap->set_frame_heights($height, $content_height);
+ }
+
+ return $height;
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ /** @var TableFrameDecorator */
+ $frame = $this->_frame;
+
+ // Check if a page break is forced
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ // Bail if the page is full
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Let the page know that we're reflowing a table so that splits
+ // are suppressed (simply setting page-break-inside: avoid won't
+ // work because we may have an arbitrary number of block elements
+ // inside tds.)
+ $page->table_reflow_start();
+
+ $this->determine_absolute_containing_block();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ // Collapse vertical margins, if required
+ $this->_collapse_margins();
+
+ // Table layout algorithm:
+ // http://www.w3.org/TR/CSS21/tables.html#auto-table-layout
+
+ if (is_null($this->_state)) {
+ $this->get_min_max_width();
+ }
+
+ $cb = $frame->get_containing_block();
+ $style = $frame->get_style();
+
+ // This is slightly inexact, but should be okay. Add half the
+ // border-spacing to the table as padding. The other half is added to
+ // the cells themselves.
+ if ($style->border_collapse === "separate") {
+ [$h, $v] = $style->border_spacing;
+ $v = $v / 2;
+ $h = $h / 2;
+
+ $style->set_used("padding_left", (float)$style->length_in_pt($style->padding_left, $cb["w"]) + $h);
+ $style->set_used("padding_right", (float)$style->length_in_pt($style->padding_right, $cb["w"]) + $h);
+ $style->set_used("padding_top", (float)$style->length_in_pt($style->padding_top, $cb["w"]) + $v);
+ $style->set_used("padding_bottom", (float)$style->length_in_pt($style->padding_bottom, $cb["w"]) + $v);
+ }
+
+ $this->_assign_widths();
+
+ // Adjust left & right margins, if they are auto
+ $delta = $this->_state["width_delta"];
+ $width = $style->width;
+ $left = $style->length_in_pt($style->margin_left, $cb["w"]);
+ $right = $style->length_in_pt($style->margin_right, $cb["w"]);
+
+ $diff = (float) $cb["w"] - (float) $width - $delta;
+
+ if ($left === "auto" && $right === "auto") {
+ if ($diff < 0) {
+ $left = 0;
+ $right = $diff;
+ } else {
+ $left = $right = $diff / 2;
+ }
+ } else {
+ if ($left === "auto") {
+ $left = max($diff - $right, 0);
+ }
+ if ($right === "auto") {
+ $right = max($diff - $left, 0);
+ }
+ }
+
+ $style->set_used("margin_left", $left);
+ $style->set_used("margin_right", $right);
+
+ $frame->position();
+ [$x, $y] = $frame->get_position();
+
+ // Determine the content edge
+ $offset_x = (float)$left + (float)$style->length_in_pt([
+ $style->padding_left,
+ $style->border_left_width
+ ], $cb["w"]);
+ $offset_y = (float)$style->length_in_pt([
+ $style->margin_top,
+ $style->border_top_width,
+ $style->padding_top
+ ], $cb["w"]);
+ $content_x = $x + $offset_x;
+ $content_y = $y + $offset_y;
+
+ if (isset($cb["h"])) {
+ $h = $cb["h"];
+ } else {
+ $h = null;
+ }
+
+ $cellmap = $frame->get_cellmap();
+ $col =& $cellmap->get_column(0);
+ $col["x"] = $offset_x;
+
+ $row =& $cellmap->get_row(0);
+ $row["y"] = $offset_y;
+
+ $cellmap->assign_x_positions();
+
+ // Set the containing block of each child & reflow
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($content_x, $content_y, $width, $h);
+ $child->reflow();
+
+ if (!$page->in_nested_table()) {
+ // Check if a split has occurred
+ $page->check_page_break($child);
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+ }
+
+ // Stop reflow if a page break has occurred before the frame, in which
+ // case it has been reset, including its position
+ if ($page->is_full() && $frame->get_position("x") === null) {
+ $page->table_reflow_end();
+ return;
+ }
+
+ // Assign heights to our cells:
+ $style->set_used("height", $this->_calculate_height());
+
+ $page->table_reflow_end();
+
+ if ($block && $frame->is_in_flow()) {
+ $block->add_frame_to_line($frame);
+
+ if ($frame->is_block_level()) {
+ $block->add_line();
+ }
+ }
+ }
+
+ public function get_min_max_width(): array
+ {
+ if (!is_null($this->_min_max_cache)) {
+ return $this->_min_max_cache;
+ }
+
+ $style = $this->_frame->get_style();
+ $cellmap = $this->_frame->get_cellmap();
+
+ $this->_frame->normalize();
+
+ // Add the cells to the cellmap (this will calculate column widths as
+ // frames are added)
+ $cellmap->add_frame($this->_frame);
+
+ // Find the min/max width of the table and sort the columns into
+ // absolute/percent/auto arrays
+ $this->_state = [];
+ $this->_state["min_width"] = 0;
+ $this->_state["max_width"] = 0;
+
+ $this->_state["percent_used"] = 0;
+ $this->_state["absolute_used"] = 0;
+ $this->_state["auto_min"] = 0;
+
+ $this->_state["absolute"] = [];
+ $this->_state["percent"] = [];
+ $this->_state["auto"] = [];
+
+ $columns =& $cellmap->get_columns();
+ foreach ($columns as $i => $col) {
+ $this->_state["min_width"] += $col["min-width"];
+ $this->_state["max_width"] += $col["max-width"];
+
+ if ($col["absolute"] > 0) {
+ $this->_state["absolute"][] = $i;
+ $this->_state["absolute_used"] += $col["min-width"];
+ } elseif ($col["percent"] > 0) {
+ $this->_state["percent"][] = $i;
+ $this->_state["percent_used"] += $col["percent"];
+ } else {
+ $this->_state["auto"][] = $i;
+ $this->_state["auto_min"] += $col["min-width"];
+ }
+ }
+
+ // Account for margins, borders, padding, and border spacing
+ $cb_w = $this->_frame->get_containing_block("w");
+ $lm = (float) $style->length_in_pt($style->margin_left, $cb_w);
+ $rm = (float) $style->length_in_pt($style->margin_right, $cb_w);
+
+ $dims = [
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->padding_left,
+ $style->padding_right
+ ];
+
+ if ($style->border_collapse !== "collapse") {
+ list($dims[]) = $style->border_spacing;
+ }
+
+ $delta = (float) $style->length_in_pt($dims, $cb_w);
+
+ $this->_state["width_delta"] = $delta;
+
+ $min_width = $this->_state["min_width"] + $delta + $lm + $rm;
+ $max_width = $this->_state["max_width"] + $delta + $lm + $rm;
+
+ return $this->_min_max_cache = [
+ $min_width,
+ $max_width,
+ "min" => $min_width,
+ "max" => $max_width
+ ];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php
new file mode 100644
index 0000000..f5ce35d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableCell.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Table as TableFrameDecorator;
+use Dompdf\Helpers;
+
+/**
+ * Reflows table cells
+ *
+ * @package dompdf
+ */
+class TableCell extends Block
+{
+ /**
+ * TableCell constructor.
+ * @param BlockFrameDecorator $frame
+ */
+ function __construct(BlockFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ // Counters and generated content
+ $this->_set_content();
+
+ $style = $this->_frame->get_style();
+
+ $table = TableFrameDecorator::find_parent_table($this->_frame);
+ $cellmap = $table->get_cellmap();
+
+ list($x, $y) = $cellmap->get_frame_position($this->_frame);
+ $this->_frame->set_position($x, $y);
+
+ $cells = $cellmap->get_spanned_cells($this->_frame);
+
+ $w = 0;
+ foreach ($cells["columns"] as $i) {
+ $col = $cellmap->get_column($i);
+ $w += $col["used-width"];
+ }
+
+ //FIXME?
+ $h = $this->_frame->get_containing_block("h");
+
+ $left_space = (float)$style->length_in_pt([$style->margin_left,
+ $style->padding_left,
+ $style->border_left_width],
+ $w);
+
+ $right_space = (float)$style->length_in_pt([$style->padding_right,
+ $style->margin_right,
+ $style->border_right_width],
+ $w);
+
+ $top_space = (float)$style->length_in_pt([$style->margin_top,
+ $style->padding_top,
+ $style->border_top_width],
+ $h);
+ $bottom_space = (float)$style->length_in_pt([$style->margin_bottom,
+ $style->padding_bottom,
+ $style->border_bottom_width],
+ $h);
+
+ $cb_w = $w - $left_space - $right_space;
+ $style->set_used("width", $cb_w);
+
+ $content_x = $x + $left_space;
+ $content_y = $line_y = $y + $top_space;
+
+ // Adjust the first line based on the text-indent property
+ $indent = (float)$style->length_in_pt($style->text_indent, $w);
+ $this->_frame->increase_line_width($indent);
+
+ $page = $this->_frame->get_root();
+
+ // Set the y position of the first line in the cell
+ $line_box = $this->_frame->get_current_line_box();
+ $line_box->y = $line_y;
+
+ // Set the containing blocks and reflow each child
+ foreach ($this->_frame->get_children() as $child) {
+ $child->set_containing_block($content_x, $content_y, $cb_w, $h);
+ $this->process_clear($child);
+ $child->reflow($this->_frame);
+ $this->process_float($child, $content_x, $cb_w);
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+
+ // Determine our height
+ $style_height = (float)$style->length_in_pt($style->height, $h);
+
+ /** @var FrameDecorator\TableCell */
+ $frame = $this->_frame;
+
+ $frame->set_content_height($this->_calculate_content_height());
+
+ $height = max($style_height, (float)$frame->get_content_height());
+
+ // Let the cellmap know our height
+ $cell_height = $height / count($cells["rows"]);
+
+ if ($style_height <= $height) {
+ $cell_height += $top_space + $bottom_space;
+ }
+
+ foreach ($cells["rows"] as $i) {
+ $cellmap->set_row_height($i, $cell_height);
+ }
+
+ $style->set_used("height", $height);
+
+ $this->_text_align();
+ $this->vertical_align();
+
+ // Handle relative positioning
+ foreach ($this->_frame->get_children() as $child) {
+ $this->position_relative($child);
+ }
+ }
+
+ public function get_min_max_content_width(): array
+ {
+ // Ignore percentage values for a specified width here, as they are
+ // relative to the table width, which is not determined yet
+ $style = $this->_frame->get_style();
+ $width = $style->width;
+ $fixed_width = $width !== "auto" && !Helpers::is_percent($width);
+
+ [$min, $max] = $this->get_min_max_child_width();
+
+ // For table cells: Use specified width if it is greater than the
+ // minimum defined by the content
+ if ($fixed_width) {
+ $width = (float) $style->length_in_pt($width, 0);
+ $min = max($width, $min);
+ $max = $min;
+ }
+
+ // Handle min/max width style properties
+ $min_width = $this->resolve_min_width(null);
+ $max_width = $this->resolve_max_width(null);
+ $min = Helpers::clamp($min, $min_width, $max_width);
+ $max = Helpers::clamp($max, $min_width, $max_width);
+
+ return [$min, $max];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php
new file mode 100644
index 0000000..f84c125
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRow.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Table as TableFrameDecorator;
+use Dompdf\FrameDecorator\TableRow as TableRowFrameDecorator;
+use Dompdf\Exception;
+
+/**
+ * Reflows table rows
+ *
+ * @package dompdf
+ */
+class TableRow extends AbstractFrameReflower
+{
+ /**
+ * TableRow constructor.
+ * @param TableRowFrameDecorator $frame
+ */
+ function __construct(TableRowFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ /** @var TableRowFrameDecorator */
+ $frame = $this->_frame;
+
+ // Check if a page break is forced
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ // Bail if the page is full
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $this->_frame->position();
+ $style = $this->_frame->get_style();
+ $cb = $this->_frame->get_containing_block();
+
+ foreach ($this->_frame->get_children() as $child) {
+ $child->set_containing_block($cb);
+ $child->reflow();
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+
+ if ($page->is_full()) {
+ return;
+ }
+
+ $table = TableFrameDecorator::find_parent_table($this->_frame);
+ $cellmap = $table->get_cellmap();
+ $style->set_used("width", $cellmap->get_frame_width($this->_frame));
+ $style->set_used("height", $cellmap->get_frame_height($this->_frame));
+
+ $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function get_min_max_width(): array
+ {
+ throw new Exception("Min/max width is undefined for table rows");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php
new file mode 100644
index 0000000..c8b19aa
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Table as TableFrameDecorator;
+use Dompdf\FrameDecorator\TableRowGroup as TableRowGroupFrameDecorator;
+
+/**
+ * Reflows table row groups (e.g. tbody tags)
+ *
+ * @package dompdf
+ */
+class TableRowGroup extends AbstractFrameReflower
+{
+
+ /**
+ * TableRowGroup constructor.
+ * @param TableRowGroupFrameDecorator $frame
+ */
+ function __construct(TableRowGroupFrameDecorator $frame)
+ {
+ parent::__construct($frame);
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ /** @var TableRowGroupFrameDecorator */
+ $frame = $this->_frame;
+ $page = $frame->get_root();
+
+ // Counters and generated content
+ $this->_set_content();
+
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+
+ foreach ($frame->get_children() as $child) {
+ $child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]);
+ $child->reflow();
+
+ // Check if a split has occurred
+ $page->check_page_break($child);
+
+ if ($page->is_full()) {
+ break;
+ }
+ }
+
+ $table = TableFrameDecorator::find_parent_table($frame);
+ $cellmap = $table->get_cellmap();
+
+ // Stop reflow if a page break has occurred before the frame, in which
+ // case it is not part of its parent table's cell map yet
+ if ($page->is_full() && !$cellmap->frame_exists_in_cellmap($frame)) {
+ return;
+ }
+
+ $style->set_used("width", $cellmap->get_frame_width($frame));
+ $style->set_used("height", $cellmap->get_frame_height($frame));
+
+ $frame->set_position($cellmap->get_frame_position($frame));
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php
new file mode 100644
index 0000000..57439ae
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/Text.php
@@ -0,0 +1,605 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\FrameReflower;
+
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\FrameDecorator\Inline as InlineFrameDecorator;
+use Dompdf\FrameDecorator\Text as TextFrameDecorator;
+use Dompdf\FontMetrics;
+use Dompdf\Helpers;
+
+/**
+ * Reflows text frames.
+ *
+ * @package dompdf
+ */
+class Text extends AbstractFrameReflower
+{
+ /**
+ * PHP string representation of HTML entity <shy>
+ */
+ const SOFT_HYPHEN = "\xC2\xAD";
+
+ /**
+ * The regex splits on everything that's a separator (^\S double negative),
+ * excluding the following non-breaking space characters:
+ * * nbsp (\xA0)
+ * * narrow nbsp (\x{202F})
+ * * figure space (\x{2007})
+ */
+ public static $_whitespace_pattern = '/([^\S\xA0\x{202F}\x{2007}]+)/u';
+
+ /**
+ * The regex splits on everything that's a separator (^\S double negative)
+ * plus dashes, excluding the following non-breaking space characters:
+ * * nbsp (\xA0)
+ * * narrow nbsp (\x{202F})
+ * * figure space (\x{2007})
+ */
+ public static $_wordbreak_pattern = '/([^\S\xA0\x{202F}\x{2007}\n]+|\R|\-+|\xAD+)/u';
+
+ /**
+ * Frame for this reflower
+ *
+ * @var TextFrameDecorator
+ */
+ protected $_frame;
+
+ /**
+ * Saves trailing whitespace trimmed after a line break, so it can be
+ * restored when needed.
+ *
+ * @var string|null
+ */
+ protected $trailingWs = null;
+
+ /**
+ * @var FontMetrics
+ */
+ private $fontMetrics;
+
+ /**
+ * @param TextFrameDecorator $frame
+ * @param FontMetrics $fontMetrics
+ */
+ public function __construct(TextFrameDecorator $frame, FontMetrics $fontMetrics)
+ {
+ parent::__construct($frame);
+ $this->setFontMetrics($fontMetrics);
+ }
+
+ /**
+ * Apply text transform and white-space collapse according to style.
+ *
+ * * http://www.w3.org/TR/CSS21/text.html#propdef-text-transform
+ * * http://www.w3.org/TR/CSS21/text.html#propdef-white-space
+ *
+ * @param string $text
+ * @return string
+ */
+ protected function pre_process_text(string $text): string
+ {
+ $style = $this->_frame->get_style();
+
+ // Handle text transform
+ switch ($style->text_transform) {
+ case "capitalize":
+ $text = Helpers::mb_ucwords($text);
+ break;
+ case "uppercase":
+ $text = mb_convert_case($text, MB_CASE_UPPER);
+ break;
+ case "lowercase":
+ $text = mb_convert_case($text, MB_CASE_LOWER);
+ break;
+ default:
+ break;
+ }
+
+ // Handle white-space collapse
+ switch ($style->white_space) {
+ default:
+ case "normal":
+ case "nowrap":
+ $text = preg_replace(self::$_whitespace_pattern, " ", $text) ?? "";
+ break;
+
+ case "pre-line":
+ // Collapse white space except for line breaks
+ $text = preg_replace('/([^\S\xA0\x{202F}\x{2007}\n]+)/u', " ", $text) ?? "";
+ break;
+
+ case "pre":
+ case "pre-wrap":
+ break;
+
+ }
+
+ return $text;
+ }
+
+ /**
+ * @param string $text
+ * @param BlockFrameDecorator $block
+ * @param bool $nowrap
+ *
+ * @return bool|int
+ */
+ protected function line_break(string $text, BlockFrameDecorator $block, bool $nowrap = false)
+ {
+ $fontMetrics = $this->getFontMetrics();
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ // Determine the available width
+ $current_line = $block->get_current_line_box();
+ $line_width = $frame->get_containing_block("w");
+ $current_line_width = $current_line->left + $current_line->w + $current_line->right;
+ $available_width = $line_width - $current_line_width;
+
+ // Determine the frame width including margin, padding & border
+ $visible_text = preg_replace('/\xAD/u', "", $text);
+ $text_width = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
+ $mbp_width = (float) $style->length_in_pt([
+ $style->margin_left,
+ $style->border_left_width,
+ $style->padding_left,
+ $style->padding_right,
+ $style->border_right_width,
+ $style->margin_right
+ ], $line_width);
+ $frame_width = $text_width + $mbp_width;
+
+ if (Helpers::lengthLessOrEqual($frame_width, $available_width)) {
+ return false;
+ }
+
+ if ($nowrap) {
+ return $current_line_width == 0 ? false : 0;
+ }
+
+ // Split the text into words
+ $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $wc = count($words);
+
+ // Determine the split point
+ $width = 0.0;
+ $str = "";
+
+ $space_width = $fontMetrics->getTextWidth(" ", $font, $size, $word_spacing, $letter_spacing);
+ $shy_width = $fontMetrics->getTextWidth(self::SOFT_HYPHEN, $font, $size);
+
+ // @todo support <wbr>
+ for ($i = 0; $i < $wc; $i += 2) {
+ // Allow trailing white space to overflow. White space is always
+ // collapsed to the standard space character currently, so only
+ // handle that for now
+ $sep = $words[$i + 1] ?? "";
+ $word = $sep === " " ? $words[$i] : $words[$i] . $sep;
+ $word_width = $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
+ $used_width = $width + $word_width + $mbp_width;
+
+ if (Helpers::lengthGreater($used_width, $available_width)) {
+ // If the previous split happened by soft hyphen, we have to
+ // append its width again because the last hyphen of a line
+ // won't be removed
+ if (isset($words[$i - 1]) && self::SOFT_HYPHEN === $words[$i - 1]) {
+ $width += $shy_width;
+ }
+ break;
+ }
+
+ // If the word is splitted by soft hyphen, but no line break is needed
+ // we have to reduce the width. But the str is not modified, otherwise
+ // the wrong offset is calculated at the end of this method.
+ if ($sep === self::SOFT_HYPHEN) {
+ $width += $word_width - $shy_width;
+ $str .= $word;
+ } elseif ($sep === " ") {
+ $width += $word_width + $space_width;
+ $str .= $word . $sep;
+ } else {
+ $width += $word_width;
+ $str .= $word;
+ }
+ }
+
+ // The first word has overflowed. Force it onto the line, or as many
+ // characters as fit if breaking words is allowed
+ if ($current_line_width == 0 && $width === 0.0) {
+ if ($sep === " ") {
+ $word .= $sep;
+ }
+
+ // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
+ $wrap = $style->overflow_wrap;
+ $break_word = $wrap === "anywhere" || $wrap === "break-word";
+
+ if ($break_word) {
+ $s = "";
+
+ for ($j = 0; $j < mb_strlen($word); $j++) {
+ $c = mb_substr($word, $j, 1);
+ $w = $fontMetrics->getTextWidth($s . $c, $font, $size, $word_spacing, $letter_spacing);
+
+ if (Helpers::lengthGreater($w, $available_width)) {
+ break;
+ }
+
+ $s .= $c;
+ }
+
+ // Always force the first character onto the line
+ $str = $j === 0 ? $s . $c : $s;
+ } else {
+ $str = $word;
+ }
+ }
+
+ $offset = mb_strlen($str);
+ return $offset;
+ }
+
+ /**
+ * @param string $text
+ * @return bool|int
+ */
+ protected function newline_break(string $text)
+ {
+ if (($i = mb_strpos($text, "\n")) === false) {
+ return false;
+ }
+
+ return $i + 1;
+ }
+
+ /**
+ * @param BlockFrameDecorator $block
+ * @return bool|null Whether to add a new line at the end. `null` if reflow
+ * should be stopped.
+ */
+ protected function layout_line(BlockFrameDecorator $block): ?bool
+ {
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $current_line = $block->get_current_line_box();
+ $text = $frame->get_text();
+
+ // Trim leading white space if this is the first text on the line
+ if ($current_line->w === 0.0 && !$frame->is_pre()) {
+ $text = ltrim($text, " ");
+ }
+
+ // Exclude wrapped white space. This handles white space between block
+ // elements in case white space is collapsed
+ if ($text === "") {
+ $frame->set_text("");
+ $style->set_used("width", 0.0);
+ return null;
+ }
+
+ // Determine the next line break
+ // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
+ $white_space = $style->white_space;
+ $nowrap = $white_space === "nowrap" || $white_space === "pre";
+
+ switch ($white_space) {
+ default:
+ case "normal":
+ case "nowrap":
+ $split = $this->line_break($text, $block, $nowrap);
+ $add_line = false;
+ break;
+
+ case "pre":
+ case "pre-line":
+ case "pre-wrap":
+ $hard_split = $this->newline_break($text);
+ $first_line = $hard_split !== false
+ ? mb_substr($text, 0, $hard_split)
+ : $text;
+ $soft_split = $this->line_break($first_line, $block, $nowrap);
+
+ $split = $soft_split !== false ? $soft_split : $hard_split;
+ $add_line = $hard_split !== false;
+ break;
+ }
+
+ if ($split === 0) {
+ // Make sure to move text when floating frames leave no space to
+ // place anything onto the line
+ // TODO: Would probably be better to move just below the current
+ // floating frame instead of trying to place text in line-height
+ // increments
+ if ($current_line->h === 0.0) {
+ // Line height might be 0
+ $h = max($frame->get_margin_height(), 1.0);
+ $block->maximize_line_height($h, $frame);
+ }
+
+ // Break line and repeat layout
+ $block->add_line();
+
+ // Find the appropriate inline ancestor to split
+ $child = $frame;
+ $p = $child->get_parent();
+ while ($p instanceof InlineFrameDecorator && !$child->get_prev_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if ($p instanceof InlineFrameDecorator) {
+ // Split parent and stop current reflow. Reflow continues
+ // via child-reflow loop of split parent
+ $p->split($child);
+ return null;
+ }
+
+ return $this->layout_line($block);
+ }
+
+ // Final split point is determined
+ if ($split !== false && $split < mb_strlen($text)) {
+ // Split the line
+ $frame->set_text($text);
+ $frame->split_text($split);
+ $add_line = true;
+
+ // Remove inner soft hyphens
+ $t = $frame->get_text();
+ $shyPosition = mb_strpos($t, self::SOFT_HYPHEN);
+ if (false !== $shyPosition && $shyPosition < mb_strlen($t) - 1) {
+ $t = str_replace(self::SOFT_HYPHEN, "", mb_substr($t, 0, -1)) . mb_substr($t, -1);
+ $frame->set_text($t);
+ }
+ } else {
+ // No split required
+ // Remove soft hyphens
+ $text = str_replace(self::SOFT_HYPHEN, "", $text);
+ $frame->set_text($text);
+ }
+
+ // Set our new width
+ $frame->recalculate_width();
+
+ return $add_line;
+ }
+
+ /**
+ * @param BlockFrameDecorator|null $block
+ */
+ function reflow(BlockFrameDecorator $block = null)
+ {
+ $frame = $this->_frame;
+ $page = $frame->get_root();
+ $page->check_forced_page_break($frame);
+
+ if ($page->is_full()) {
+ return;
+ }
+
+ // Determine the text height
+ $style = $frame->get_style();
+ $size = $style->font_size;
+ $font = $style->font_family;
+ $font_height = $this->getFontMetrics()->getFontHeight($font, $size);
+ $style->set_used("height", $font_height);
+
+ // Handle text transform and white space
+ $text = $this->pre_process_text($frame->get_text());
+ $frame->set_text($text);
+
+ $add_line = $this->layout_line($block);
+
+ if ($add_line === null) {
+ return;
+ }
+
+ $frame->position();
+
+ if ($block) {
+ $line = $block->add_frame_to_line($frame);
+ $trimmed = trim($frame->get_text());
+
+ // Split the text into words (used to determine spacing between
+ // words on justified lines)
+ if ($trimmed !== "") {
+ $words = preg_split(self::$_whitespace_pattern, $trimmed);
+ $line->wc += count($words);
+ }
+
+ if ($add_line) {
+ $block->add_line();
+ }
+ }
+ }
+
+ /**
+ * Trim trailing white space from the frame text.
+ */
+ public function trim_trailing_ws(): void
+ {
+ $frame = $this->_frame;
+ $text = $frame->get_text();
+ $trailing = mb_substr($text, -1);
+
+ // White space is always collapsed to the standard space character
+ // currently, so only handle that for now
+ if ($trailing === " ") {
+ $this->trailingWs = $trailing;
+ $frame->set_text(mb_substr($text, 0, -1));
+ $frame->recalculate_width();
+ }
+ }
+
+ public function reset(): void
+ {
+ parent::reset();
+
+ // Restore trimmed trailing white space, as the frame will go through
+ // another reflow and line breaks might be different after a split
+ if ($this->trailingWs !== null) {
+ $text = $this->_frame->get_text();
+ $this->_frame->set_text($text . $this->trailingWs);
+ $this->trailingWs = null;
+ }
+ }
+
+ //........................................................................
+
+ public function get_min_max_width(): array
+ {
+ $fontMetrics = $this->getFontMetrics();
+ $frame = $this->_frame;
+ $style = $frame->get_style();
+ $text = $frame->get_text();
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ // Handle text transform and white space
+ $text = $this->pre_process_text($frame->get_text());
+
+ if (!$frame->is_pre()) {
+ // Determine whether the frame is at the start of its parent block.
+ // Trim leading white space in that case
+ $child = $frame;
+ $p = $frame->get_parent();
+ while (!$p->is_block() && !$child->get_prev_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if (!$child->get_prev_sibling()) {
+ $text = ltrim($text, " ");
+ }
+
+ // Determine whether the frame is at the end of its parent block.
+ // Trim trailing white space in that case
+ $child = $frame;
+ $p = $frame->get_parent();
+ while (!$p->is_block() && !$child->get_next_sibling()) {
+ $child = $p;
+ $p = $p->get_parent();
+ }
+
+ if (!$child->get_next_sibling()) {
+ $text = rtrim($text, " ");
+ }
+ }
+
+ // Strip soft hyphens for max-line-width calculations
+ $visible_text = preg_replace('/\xAD/u', "", $text);
+
+ // Determine minimum text width
+ switch ($style->white_space) {
+ default:
+ case "normal":
+ case "pre-line":
+ case "pre-wrap":
+ // The min width is the longest word or, if breaking words is
+ // allowed with the `anywhere` keyword, the widest character.
+ // For performance reasons, we only check the first character in
+ // the latter case.
+ // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
+ if ($style->overflow_wrap === "anywhere") {
+ $char = mb_substr($visible_text, 0, 1);
+ $min = $fontMetrics->getTextWidth($char, $font, $size, $word_spacing, $letter_spacing);
+ } else {
+ // Find the longest word
+ $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $lengths = array_map(function ($chunk) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
+ // Allow trailing white space to overflow. As in actual
+ // layout above, only handle a single space for now
+ $sep = $chunk[1] ?? "";
+ $word = $sep === " " ? $chunk[0] : $chunk[0] . $sep;
+ return $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
+ }, array_chunk($words, 2));
+ $min = max($lengths);
+ }
+ break;
+
+ case "pre":
+ // Find the longest line
+ $lines = array_flip(preg_split("/\R/u", $visible_text));
+ array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
+ $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
+ });
+ arsort($lines);
+ $min = reset($lines);
+ break;
+
+ case "nowrap":
+ $min = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
+ break;
+ }
+
+ // Determine maximum text width
+ switch ($style->white_space) {
+ default:
+ case "normal":
+ $max = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
+ break;
+
+ case "pre-line":
+ case "pre-wrap":
+ // Find the longest line
+ $lines = array_flip(preg_split("/\R/u", $visible_text));
+ array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
+ $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
+ });
+ arsort($lines);
+ $max = reset($lines);
+ break;
+
+ case "pre":
+ case "nowrap":
+ $max = $min;
+ break;
+ }
+
+ // Account for margins, borders, and padding
+ $dims = [
+ $style->padding_left,
+ $style->padding_right,
+ $style->border_left_width,
+ $style->border_right_width,
+ $style->margin_left,
+ $style->margin_right
+ ];
+
+ // The containing block is not defined yet, treat percentages as 0
+ $delta = (float) $style->length_in_pt($dims, 0);
+ $min += $delta;
+ $max += $delta;
+
+ return [$min, $max, "min" => $min, "max" => $max];
+ }
+
+ /**
+ * @param FontMetrics $fontMetrics
+ * @return $this
+ */
+ public function setFontMetrics(FontMetrics $fontMetrics)
+ {
+ $this->fontMetrics = $fontMetrics;
+ return $this;
+ }
+
+ /**
+ * @return FontMetrics
+ */
+ public function getFontMetrics()
+ {
+ return $this->fontMetrics;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Helpers.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Helpers.php
new file mode 100644
index 0000000..ba78515
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Helpers.php
@@ -0,0 +1,1093 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+class Helpers
+{
+ /**
+ * print_r wrapper for html/cli output
+ *
+ * Wraps print_r() output in < pre > tags if the current sapi is not 'cli'.
+ * Returns the output string instead of displaying it if $return is true.
+ *
+ * @param mixed $mixed variable or expression to display
+ * @param bool $return
+ *
+ * @return string|null
+ */
+ public static function pre_r($mixed, $return = false)
+ {
+ if ($return) {
+ return "<pre>" . print_r($mixed, true) . "</pre>";
+ }
+
+ if (php_sapi_name() !== "cli") {
+ echo "<pre>";
+ }
+
+ print_r($mixed);
+
+ if (php_sapi_name() !== "cli") {
+ echo "</pre>";
+ } else {
+ echo "\n";
+ }
+
+ flush();
+
+ return null;
+ }
+
+ /**
+ * builds a full url given a protocol, hostname, base path and url
+ *
+ * @param string $protocol
+ * @param string $host
+ * @param string $base_path
+ * @param string $url
+ * @return string
+ *
+ * Initially the trailing slash of $base_path was optional, and conditionally appended.
+ * However on dynamically created sites, where the page is given as url parameter,
+ * the base path might not end with an url.
+ * Therefore do not append a slash, and **require** the $base_url to ending in a slash
+ * when needed.
+ * Vice versa, on using the local file system path of a file, make sure that the slash
+ * is appended (o.k. also for Windows)
+ */
+ public static function build_url($protocol, $host, $base_path, $url)
+ {
+ $protocol = mb_strtolower($protocol);
+ if (empty($protocol)) {
+ $protocol = "file://";
+ }
+ if ($url === "") {
+ return null;
+ }
+
+ $url_lc = mb_strtolower($url);
+
+ // Is the url already fully qualified, a Data URI, or a reference to a named anchor?
+ // File-protocol URLs may require additional processing (e.g. for URLs with a relative path)
+ if (
+ (
+ mb_strpos($url_lc, "://") !== false
+ && !in_array(substr($url_lc, 0, 7), ["file://", "phar://"], true)
+ )
+ || mb_substr($url_lc, 0, 1) === "#"
+ || mb_strpos($url_lc, "data:") === 0
+ || mb_strpos($url_lc, "mailto:") === 0
+ || mb_strpos($url_lc, "tel:") === 0
+ ) {
+ return $url;
+ }
+
+ $res = "";
+ if (strpos($url_lc, "file://") === 0) {
+ $url = substr($url, 7);
+ $protocol = "file://";
+ } elseif (strpos($url_lc, "phar://") === 0) {
+ $res = substr($url, strpos($url_lc, ".phar")+5);
+ $url = substr($url, 7, strpos($url_lc, ".phar")-2);
+ $protocol = "phar://";
+ }
+
+ $ret = "";
+
+ $is_local_path = in_array($protocol, ["file://", "phar://"], true);
+
+ if ($is_local_path) {
+ //On Windows local file, an abs path can begin also with a '\' or a drive letter and colon
+ //drive: followed by a relative path would be a drive specific default folder.
+ //not known in php app code, treat as abs path
+ //($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/'))
+ if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || (mb_strlen($url) > 1 && $url[0] !== '\\' && $url[1] !== ':'))) {
+ // For rel path and local access we ignore the host, and run the path through realpath()
+ $ret .= realpath($base_path) . '/';
+ }
+ $ret .= $url;
+ $ret = preg_replace('/\?(.*)$/', "", $ret);
+
+ $filepath = realpath($ret);
+ if ($filepath === false) {
+ return null;
+ }
+
+ $ret = "$protocol$filepath$res";
+
+ return $ret;
+ }
+
+ $ret = $protocol;
+ // Protocol relative urls (e.g. "//example.org/style.css")
+ if (strpos($url, '//') === 0) {
+ $ret .= substr($url, 2);
+ //remote urls with backslash in html/css are not really correct, but lets be genereous
+ } elseif ($url[0] === '/' || $url[0] === '\\') {
+ // Absolute path
+ $ret .= $host . $url;
+ } else {
+ // Relative path
+ //$base_path = $base_path !== "" ? rtrim($base_path, "/\\") . "/" : "";
+ $ret .= $host . $base_path . $url;
+ }
+
+ // URL should now be complete, final cleanup
+ $parsed_url = parse_url($ret);
+
+ // reproduced from https://www.php.net/manual/en/function.parse-url.php#106731
+ $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
+ $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
+ $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
+ $user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
+ $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
+ $pass = ($user || $pass) ? "$pass@" : '';
+ $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
+ $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
+ $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
+
+ // partially reproduced from https://stackoverflow.com/a/1243431/264628
+ /* replace '//' or '/./' or '/foo/../' with '/' */
+ $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
+ for ($n=1; $n>0; $path=preg_replace($re, '/', $path, -1, $n)) {}
+
+ $ret = "$scheme$user$pass$host$port$path$query$fragment";
+
+ return $ret;
+ }
+
+ /**
+ * Builds a HTTP Content-Disposition header string using `$dispositionType`
+ * and `$filename`.
+ *
+ * If the filename contains any characters not in the ISO-8859-1 character
+ * set, a fallback filename will be included for clients not supporting the
+ * `filename*` parameter.
+ *
+ * @param string $dispositionType
+ * @param string $filename
+ * @return string
+ */
+ public static function buildContentDispositionHeader($dispositionType, $filename)
+ {
+ $encoding = mb_detect_encoding($filename);
+ $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
+ $fallbackfilename = str_replace("\"", "", $fallbackfilename);
+ $encodedfilename = rawurlencode($filename);
+
+ $contentDisposition = "Content-Disposition: $dispositionType; filename=\"$fallbackfilename\"";
+ if ($fallbackfilename !== $filename) {
+ $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
+ }
+
+ return $contentDisposition;
+ }
+
+ /**
+ * Converts decimal numbers to roman numerals.
+ *
+ * As numbers larger than 3999 (and smaller than 1) cannot be represented in
+ * the standard form of roman numerals, those are left in decimal form.
+ *
+ * See https://en.wikipedia.org/wiki/Roman_numerals#Standard_form
+ *
+ * @param int|string $num
+ *
+ * @throws Exception
+ * @return string
+ */
+ public static function dec2roman($num): string
+ {
+
+ static $ones = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
+ static $tens = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
+ static $hund = ["", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"];
+ static $thou = ["", "m", "mm", "mmm"];
+
+ if (!is_numeric($num)) {
+ throw new Exception("dec2roman() requires a numeric argument.");
+ }
+
+ if ($num >= 4000 || $num <= 0) {
+ return (string) $num;
+ }
+
+ $num = strrev((string)$num);
+
+ $ret = "";
+ switch (mb_strlen($num)) {
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 4:
+ $ret .= $thou[$num[3]];
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 3:
+ $ret .= $hund[$num[2]];
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 2:
+ $ret .= $tens[$num[1]];
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 1:
+ $ret .= $ones[$num[0]];
+ default:
+ break;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Restrict a length to the given range.
+ *
+ * If min > max, the result is min.
+ *
+ * @param float $length
+ * @param float $min
+ * @param float $max
+ *
+ * @return float
+ */
+ public static function clamp(float $length, float $min, float $max): float
+ {
+ return max($min, min($length, $max));
+ }
+
+ /**
+ * Determines whether $value is a percentage or not
+ *
+ * @param string|float|int $value
+ *
+ * @return bool
+ */
+ public static function is_percent($value): bool
+ {
+ return is_string($value) && false !== mb_strpos($value, "%");
+ }
+
+ /**
+ * Parses a data URI scheme
+ * http://en.wikipedia.org/wiki/Data_URI_scheme
+ *
+ * @param string $data_uri The data URI to parse
+ *
+ * @return array|bool The result with charset, mime type and decoded data
+ */
+ public static function parse_data_uri($data_uri)
+ {
+ if (!preg_match('/^data:(?P<mime>[a-z0-9\/+-.]+)(;charset=(?P<charset>[a-z0-9-])+)?(?P<base64>;base64)?\,(?P<data>.*)?/is', $data_uri, $match)) {
+ return false;
+ }
+
+ $match['data'] = rawurldecode($match['data']);
+ $result = [
+ 'charset' => $match['charset'] ? $match['charset'] : 'US-ASCII',
+ 'mime' => $match['mime'] ? $match['mime'] : 'text/plain',
+ 'data' => $match['base64'] ? base64_decode($match['data']) : $match['data'],
+ ];
+
+ return $result;
+ }
+
+ /**
+ * Encodes a Uniform Resource Identifier (URI) by replacing non-alphanumeric
+ * characters with a percent (%) sign followed by two hex digits, excepting
+ * characters in the URI reserved character set.
+ *
+ * Assumes that the URI is a complete URI, so does not encode reserved
+ * characters that have special meaning in the URI.
+ *
+ * Simulates the encodeURI function available in JavaScript
+ * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
+ *
+ * Source: http://stackoverflow.com/q/4929584/264628
+ *
+ * @param string $uri The URI to encode
+ * @return string The original URL with special characters encoded
+ */
+ public static function encodeURI($uri) {
+ $unescaped = [
+ '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~',
+ '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')'
+ ];
+ $reserved = [
+ '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':',
+ '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$'
+ ];
+ $score = [
+ '%23'=>'#'
+ ];
+ return strtr(rawurlencode(rawurldecode($uri)), array_merge($reserved, $unescaped, $score));
+ }
+
+ /**
+ * Decoder for RLE8 compression in windows bitmaps
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
+ *
+ * @param string $str Data to decode
+ * @param int $width Image width
+ *
+ * @return string
+ */
+ public static function rle8_decode($str, $width)
+ {
+ $lineWidth = $width + (3 - ($width - 1) % 4);
+ $out = '';
+ $cnt = strlen($str);
+
+ for ($i = 0; $i < $cnt; $i++) {
+ $o = ord($str[$i]);
+ switch ($o) {
+ case 0: # ESCAPE
+ $i++;
+ switch (ord($str[$i])) {
+ case 0: # NEW LINE
+ $padCnt = $lineWidth - strlen($out) % $lineWidth;
+ if ($padCnt < $lineWidth) {
+ $out .= str_repeat(chr(0), $padCnt); # pad line
+ }
+ break;
+ case 1: # END OF FILE
+ $padCnt = $lineWidth - strlen($out) % $lineWidth;
+ if ($padCnt < $lineWidth) {
+ $out .= str_repeat(chr(0), $padCnt); # pad line
+ }
+ break 3;
+ case 2: # DELTA
+ $i += 2;
+ break;
+ default: # ABSOLUTE MODE
+ $num = ord($str[$i]);
+ for ($j = 0; $j < $num; $j++) {
+ $out .= $str[++$i];
+ }
+ if ($num % 2) {
+ $i++;
+ }
+ }
+ break;
+ default:
+ $out .= str_repeat($str[++$i], $o);
+ }
+ }
+ return $out;
+ }
+
+ /**
+ * Decoder for RLE4 compression in windows bitmaps
+ * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
+ *
+ * @param string $str Data to decode
+ * @param int $width Image width
+ *
+ * @return string
+ */
+ public static function rle4_decode($str, $width)
+ {
+ $w = floor($width / 2) + ($width % 2);
+ $lineWidth = $w + (3 - (($width - 1) / 2) % 4);
+ $pixels = [];
+ $cnt = strlen($str);
+ $c = 0;
+
+ for ($i = 0; $i < $cnt; $i++) {
+ $o = ord($str[$i]);
+ switch ($o) {
+ case 0: # ESCAPE
+ $i++;
+ switch (ord($str[$i])) {
+ case 0: # NEW LINE
+ while (count($pixels) % $lineWidth != 0) {
+ $pixels[] = 0;
+ }
+ break;
+ case 1: # END OF FILE
+ while (count($pixels) % $lineWidth != 0) {
+ $pixels[] = 0;
+ }
+ break 3;
+ case 2: # DELTA
+ $i += 2;
+ break;
+ default: # ABSOLUTE MODE
+ $num = ord($str[$i]);
+ for ($j = 0; $j < $num; $j++) {
+ if ($j % 2 == 0) {
+ $c = ord($str[++$i]);
+ $pixels[] = ($c & 240) >> 4;
+ } else {
+ $pixels[] = $c & 15;
+ }
+ }
+
+ if ($num % 2 == 0) {
+ $i++;
+ }
+ }
+ break;
+ default:
+ $c = ord($str[++$i]);
+ for ($j = 0; $j < $o; $j++) {
+ $pixels[] = ($j % 2 == 0 ? ($c & 240) >> 4 : $c & 15);
+ }
+ }
+ }
+
+ $out = '';
+ if (count($pixels) % 2) {
+ $pixels[] = 0;
+ }
+
+ $cnt = count($pixels) / 2;
+
+ for ($i = 0; $i < $cnt; $i++) {
+ $out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]);
+ }
+
+ return $out;
+ }
+
+ /**
+ * parse a full url or pathname and return an array(protocol, host, path,
+ * file + query + fragment)
+ *
+ * @param string $url
+ * @return array
+ */
+ public static function explode_url($url)
+ {
+ $protocol = "";
+ $host = "";
+ $path = "";
+ $file = "";
+ $res = "";
+
+ $arr = parse_url($url);
+ if ( isset($arr["scheme"]) ) {
+ $arr["scheme"] = mb_strtolower($arr["scheme"]);
+ }
+
+ if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && $arr["scheme"] !== "phar" && strlen($arr["scheme"]) > 1) {
+ $protocol = $arr["scheme"] . "://";
+
+ if (isset($arr["user"])) {
+ $host .= $arr["user"];
+
+ if (isset($arr["pass"])) {
+ $host .= ":" . $arr["pass"];
+ }
+
+ $host .= "@";
+ }
+
+ if (isset($arr["host"])) {
+ $host .= $arr["host"];
+ }
+
+ if (isset($arr["port"])) {
+ $host .= ":" . $arr["port"];
+ }
+
+ if (isset($arr["path"]) && $arr["path"] !== "") {
+ // Do we have a trailing slash?
+ if ($arr["path"][mb_strlen($arr["path"]) - 1] === "/") {
+ $path = $arr["path"];
+ $file = "";
+ } else {
+ $path = rtrim(dirname($arr["path"]), '/\\') . "/";
+ $file = basename($arr["path"]);
+ }
+ }
+
+ if (isset($arr["query"])) {
+ $file .= "?" . $arr["query"];
+ }
+
+ if (isset($arr["fragment"])) {
+ $file .= "#" . $arr["fragment"];
+ }
+
+ } else {
+
+ $protocol = "";
+ $host = ""; // localhost, really
+
+ $i = mb_stripos($url, "://");
+ if ($i !== false) {
+ $protocol = mb_strtolower(mb_substr($url, 0, $i + 3));
+ $url = mb_substr($url, $i + 3);
+ } else {
+ $protocol = "file://";
+ }
+
+ if ($protocol === "phar://") {
+ $res = substr($url, stripos($url, ".phar")+5);
+ $url = substr($url, 7, stripos($url, ".phar")-2);
+ }
+
+ $file = basename($url);
+ $path = dirname($url) . "/";
+ }
+
+ $ret = [$protocol, $host, $path, $file,
+ "protocol" => $protocol,
+ "host" => $host,
+ "path" => $path,
+ "file" => $file,
+ "resource" => $res];
+ return $ret;
+ }
+
+ /**
+ * Print debug messages
+ *
+ * @param string $type The type of debug messages to print
+ * @param string $msg The message to show
+ */
+ public static function dompdf_debug($type, $msg)
+ {
+ global $_DOMPDF_DEBUG_TYPES, $_dompdf_show_warnings, $_dompdf_debug;
+ if (isset($_DOMPDF_DEBUG_TYPES[$type]) && ($_dompdf_show_warnings || $_dompdf_debug)) {
+ $arr = debug_backtrace();
+
+ echo basename($arr[0]["file"]) . " (" . $arr[0]["line"] . "): " . $arr[1]["function"] . ": ";
+ Helpers::pre_r($msg);
+ }
+ }
+
+ /**
+ * Stores warnings in an array for display later
+ * This function allows warnings generated by the DomDocument parser
+ * and CSS loader ({@link Stylesheet}) to be captured and displayed
+ * later. Without this function, errors are displayed immediately and
+ * PDF streaming is impossible.
+ * @see http://www.php.net/manual/en/function.set-error_handler.php
+ *
+ * @param int $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param string $errline
+ *
+ * @throws Exception
+ */
+ public static function record_warnings($errno, $errstr, $errfile, $errline)
+ {
+ // Not a warning or notice
+ if (!($errno & (E_WARNING | E_NOTICE | E_USER_NOTICE | E_USER_WARNING | E_STRICT | E_DEPRECATED | E_USER_DEPRECATED))) {
+ throw new Exception($errstr . " $errno");
+ }
+
+ global $_dompdf_warnings;
+ global $_dompdf_show_warnings;
+
+ if ($_dompdf_show_warnings) {
+ echo $errstr . "\n";
+ }
+
+ $_dompdf_warnings[] = $errstr;
+ }
+
+ /**
+ * @param $c
+ * @return bool|string
+ */
+ public static function unichr($c)
+ {
+ if ($c <= 0x7F) {
+ return chr($c);
+ } elseif ($c <= 0x7FF) {
+ return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
+ } elseif ($c <= 0xFFFF) {
+ return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ } elseif ($c <= 0x10FFFF) {
+ return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
+ . chr(0x80 | $c >> 6 & 0x3F)
+ . chr(0x80 | $c & 0x3F);
+ }
+ return false;
+ }
+
+ /**
+ * Converts a CMYK color to RGB
+ *
+ * @param float|float[] $c
+ * @param float $m
+ * @param float $y
+ * @param float $k
+ *
+ * @return float[]
+ */
+ public static function cmyk_to_rgb($c, $m = null, $y = null, $k = null)
+ {
+ if (is_array($c)) {
+ [$c, $m, $y, $k] = $c;
+ }
+
+ $c *= 255;
+ $m *= 255;
+ $y *= 255;
+ $k *= 255;
+
+ $r = (1 - round(2.55 * ($c + $k)));
+ $g = (1 - round(2.55 * ($m + $k)));
+ $b = (1 - round(2.55 * ($y + $k)));
+
+ if ($r < 0) {
+ $r = 0;
+ }
+ if ($g < 0) {
+ $g = 0;
+ }
+ if ($b < 0) {
+ $b = 0;
+ }
+
+ return [
+ $r, $g, $b,
+ "r" => $r, "g" => $g, "b" => $b
+ ];
+ }
+
+ /**
+ * getimagesize doesn't give a good size for 32bit BMP image v5
+ *
+ * @param string $filename
+ * @param resource $context
+ * @return array An array of three elements: width and height as
+ * `float|int`, and image type as `string|null`.
+ */
+ public static function dompdf_getimagesize($filename, $context = null)
+ {
+ static $cache = [];
+
+ if (isset($cache[$filename])) {
+ return $cache[$filename];
+ }
+
+ [$width, $height, $type] = getimagesize($filename);
+
+ // Custom types
+ $types = [
+ IMAGETYPE_JPEG => "jpeg",
+ IMAGETYPE_GIF => "gif",
+ IMAGETYPE_BMP => "bmp",
+ IMAGETYPE_PNG => "png",
+ IMAGETYPE_WEBP => "webp",
+ ];
+
+ $type = $types[$type] ?? null;
+
+ if ($width == null || $height == null) {
+ [$data] = Helpers::getFileContent($filename, $context);
+
+ if ($data !== null) {
+ if (substr($data, 0, 2) === "BM") {
+ $meta = unpack("vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight", $data);
+ $width = (int) $meta["width"];
+ $height = (int) $meta["height"];
+ $type = "bmp";
+ } elseif (strpos($data, "<svg") !== false) {
+ $doc = new \Svg\Document();
+ $doc->loadFile($filename);
+
+ [$width, $height] = $doc->getDimensions();
+ $width = (float) $width;
+ $height = (float) $height;
+ $type = "svg";
+ }
+ }
+ }
+
+ return $cache[$filename] = [$width ?? 0, $height ?? 0, $type];
+ }
+
+ /**
+ * Credit goes to mgutt
+ * http://www.programmierer-forum.de/function-imagecreatefrombmp-welche-variante-laeuft-t143137.htm
+ * Modified by Fabien Menager to support RGB555 BMP format
+ */
+ public static function imagecreatefrombmp($filename, $context = null)
+ {
+ if (!function_exists("imagecreatetruecolor")) {
+ trigger_error("The PHP GD extension is required, but is not installed.", E_ERROR);
+ return false;
+ }
+
+ // version 1.00
+ if (!($fh = fopen($filename, 'rb'))) {
+ trigger_error('imagecreatefrombmp: Can not open ' . $filename, E_USER_WARNING);
+ return false;
+ }
+
+ $bytes_read = 0;
+
+ // read file header
+ $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
+
+ // check for bitmap
+ if ($meta['type'] != 19778) {
+ trigger_error('imagecreatefrombmp: ' . $filename . ' is not a bitmap!', E_USER_WARNING);
+ return false;
+ }
+
+ // read image header
+ $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
+ $bytes_read += 40;
+
+ // read additional bitfield header
+ if ($meta['compression'] == 3) {
+ $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
+ $bytes_read += 12;
+ }
+
+ // set bytes and padding
+ $meta['bytes'] = $meta['bits'] / 8;
+ $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
+ if ($meta['decal'] == 4) {
+ $meta['decal'] = 0;
+ }
+
+ // obtain imagesize
+ if ($meta['imagesize'] < 1) {
+ $meta['imagesize'] = $meta['filesize'] - $meta['offset'];
+ // in rare cases filesize is equal to offset so we need to read physical size
+ if ($meta['imagesize'] < 1) {
+ $meta['imagesize'] = @filesize($filename) - $meta['offset'];
+ if ($meta['imagesize'] < 1) {
+ trigger_error('imagecreatefrombmp: Can not obtain filesize of ' . $filename . '!', E_USER_WARNING);
+ return false;
+ }
+ }
+ }
+
+ // calculate colors
+ $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
+
+ // read color palette
+ $palette = [];
+ if ($meta['bits'] < 16) {
+ $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
+ // in rare cases the color value is signed
+ if ($palette[1] < 0) {
+ foreach ($palette as $i => $color) {
+ $palette[$i] = $color + 16777216;
+ }
+ }
+ }
+
+ // ignore extra bitmap headers
+ if ($meta['headersize'] > $bytes_read) {
+ fread($fh, $meta['headersize'] - $bytes_read);
+ }
+
+ // create gd image
+ $im = imagecreatetruecolor($meta['width'], $meta['height']);
+ $data = fread($fh, $meta['imagesize']);
+
+ // uncompress data
+ switch ($meta['compression']) {
+ case 1:
+ $data = Helpers::rle8_decode($data, $meta['width']);
+ break;
+ case 2:
+ $data = Helpers::rle4_decode($data, $meta['width']);
+ break;
+ }
+
+ $p = 0;
+ $vide = chr(0);
+ $y = $meta['height'] - 1;
+ $error = 'imagecreatefrombmp: ' . $filename . ' has not enough data!';
+
+ // loop through the image data beginning with the lower left corner
+ while ($y >= 0) {
+ $x = 0;
+ while ($x < $meta['width']) {
+ switch ($meta['bits']) {
+ case 32:
+ case 24:
+ if (!($part = substr($data, $p, 3 /*$meta['bytes']*/))) {
+ trigger_error($error, E_USER_WARNING);
+ return $im;
+ }
+ $color = unpack('V', $part . $vide);
+ break;
+ case 16:
+ if (!($part = substr($data, $p, 2 /*$meta['bytes']*/))) {
+ trigger_error($error, E_USER_WARNING);
+ return $im;
+ }
+ $color = unpack('v', $part);
+
+ if (empty($meta['rMask']) || $meta['rMask'] != 0xf800) {
+ $color[1] = (($color[1] & 0x7c00) >> 7) * 65536 + (($color[1] & 0x03e0) >> 2) * 256 + (($color[1] & 0x001f) << 3); // 555
+ } else {
+ $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); // 565
+ }
+ break;
+ case 8:
+ $color = unpack('n', $vide . substr($data, $p, 1));
+ $color[1] = $palette[$color[1] + 1];
+ break;
+ case 4:
+ $color = unpack('n', $vide . substr($data, floor($p), 1));
+ $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
+ $color[1] = $palette[$color[1] + 1];
+ break;
+ case 1:
+ $color = unpack('n', $vide . substr($data, floor($p), 1));
+ switch (($p * 8) % 8) {
+ case 0:
+ $color[1] = $color[1] >> 7;
+ break;
+ case 1:
+ $color[1] = ($color[1] & 0x40) >> 6;
+ break;
+ case 2:
+ $color[1] = ($color[1] & 0x20) >> 5;
+ break;
+ case 3:
+ $color[1] = ($color[1] & 0x10) >> 4;
+ break;
+ case 4:
+ $color[1] = ($color[1] & 0x8) >> 3;
+ break;
+ case 5:
+ $color[1] = ($color[1] & 0x4) >> 2;
+ break;
+ case 6:
+ $color[1] = ($color[1] & 0x2) >> 1;
+ break;
+ case 7:
+ $color[1] = ($color[1] & 0x1);
+ break;
+ }
+ $color[1] = $palette[$color[1] + 1];
+ break;
+ default:
+ trigger_error('imagecreatefrombmp: ' . $filename . ' has ' . $meta['bits'] . ' bits and this is not supported!', E_USER_WARNING);
+ return false;
+ }
+ imagesetpixel($im, $x, $y, $color[1]);
+ $x++;
+ $p += $meta['bytes'];
+ }
+ $y--;
+ $p += $meta['decal'];
+ }
+ fclose($fh);
+ return $im;
+ }
+
+ /**
+ * Gets the content of the file at the specified path using one of
+ * the following methods, in preferential order:
+ * - file_get_contents: if allow_url_fopen is true or the file is local
+ * - curl: if allow_url_fopen is false and curl is available
+ *
+ * @param string $uri
+ * @param resource $context
+ * @param int $offset
+ * @param int $maxlen
+ * @return string[]
+ */
+ public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null)
+ {
+ $content = null;
+ $headers = null;
+ [$protocol] = Helpers::explode_url($uri);
+ $is_local_path = in_array(strtolower($protocol), ["", "file://", "phar://"], true);
+ $can_use_curl = in_array(strtolower($protocol), ["http://", "https://"], true);
+
+ set_error_handler([self::class, 'record_warnings']);
+
+ try {
+ if ($is_local_path || ini_get('allow_url_fopen') || !$can_use_curl) {
+ if ($is_local_path === false) {
+ $uri = Helpers::encodeURI($uri);
+ }
+ if (isset($maxlen)) {
+ $result = file_get_contents($uri, false, $context, $offset, $maxlen);
+ } else {
+ $result = file_get_contents($uri, false, $context, $offset);
+ }
+ if ($result !== false) {
+ $content = $result;
+ }
+ if (isset($http_response_header)) {
+ $headers = $http_response_header;
+ }
+
+ } elseif ($can_use_curl && function_exists('curl_exec')) {
+ $curl = curl_init($uri);
+
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curl, CURLOPT_HEADER, true);
+ if ($offset > 0) {
+ curl_setopt($curl, CURLOPT_RESUME_FROM, $offset);
+ }
+
+ if ($maxlen > 0) {
+ curl_setopt($curl, CURLOPT_BUFFERSIZE, 128);
+ curl_setopt($curl, CURLOPT_NOPROGRESS, false);
+ curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, function ($res, $download_size_total, $download_size, $upload_size_total, $upload_size) use ($maxlen) {
+ return ($download_size > $maxlen) ? 1 : 0;
+ });
+ }
+
+ $context_options = [];
+ if (!is_null($context)) {
+ $context_options = stream_context_get_options($context);
+ }
+ foreach ($context_options as $stream => $options) {
+ foreach ($options as $option => $value) {
+ $key = strtolower($stream) . ":" . strtolower($option);
+ switch ($key) {
+ case "curl:curl_verify_ssl_host":
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, !$value ? 0 : 2);
+ break;
+ case "curl:max_redirects":
+ curl_setopt($curl, CURLOPT_MAXREDIRS, $value);
+ break;
+ case "http:follow_location":
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, $value);
+ break;
+ case "http:header":
+ if (is_string($value)) {
+ curl_setopt($curl, CURLOPT_HTTPHEADER, [$value]);
+ } else {
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $value);
+ }
+ break;
+ case "http:timeout":
+ curl_setopt($curl, CURLOPT_TIMEOUT, $value);
+ break;
+ case "http:user_agent":
+ curl_setopt($curl, CURLOPT_USERAGENT, $value);
+ break;
+ case "curl:curl_verify_ssl_peer":
+ case "ssl:verify_peer":
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $value);
+ break;
+ }
+ }
+ }
+
+ $data = curl_exec($curl);
+
+ if ($data !== false && !curl_errno($curl)) {
+ switch ($http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE)) {
+ case 200:
+ $raw_headers = substr($data, 0, curl_getinfo($curl, CURLINFO_HEADER_SIZE));
+ $headers = preg_split("/[\n\r]+/", trim($raw_headers));
+ $content = substr($data, curl_getinfo($curl, CURLINFO_HEADER_SIZE));
+ break;
+ }
+ }
+ curl_close($curl);
+ }
+ } finally {
+ restore_error_handler();
+ }
+
+ return [$content, $headers];
+ }
+
+ /**
+ * @param string $str
+ * @return string
+ */
+ public static function mb_ucwords(string $str): string
+ {
+ $max_len = mb_strlen($str);
+ if ($max_len === 1) {
+ return mb_strtoupper($str);
+ }
+
+ $str = mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1);
+
+ foreach ([' ', '.', ',', '!', '?', '-', '+'] as $s) {
+ $pos = 0;
+ while (($pos = mb_strpos($str, $s, $pos)) !== false) {
+ $pos++;
+ // Nothing to do if the separator is the last char of the string
+ if ($pos !== false && $pos < $max_len) {
+ // If the char we want to upper is the last char there is nothing to append behind
+ if ($pos + 1 < $max_len) {
+ $str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)) . mb_substr($str, $pos + 1);
+ } else {
+ $str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1));
+ }
+ }
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * Check whether two lengths should be considered equal, accounting for
+ * inaccuracies in float computation.
+ *
+ * The implementation relies on the fact that we are neither dealing with
+ * very large, nor with very small numbers in layout. Adapted from
+ * https://floating-point-gui.de/errors/comparison/.
+ *
+ * @param float $a
+ * @param float $b
+ *
+ * @return bool
+ */
+ public static function lengthEqual(float $a, float $b): bool
+ {
+ // The epsilon results in a precision of at least:
+ // * 7 decimal digits at around 1
+ // * 4 decimal digits at around 1000 (around the size of common paper formats)
+ // * 2 decimal digits at around 100,000 (100,000pt ~ 35.28m)
+ static $epsilon = 1e-8;
+ static $almostZero = 1e-12;
+
+ $diff = abs($a - $b);
+
+ if ($a === $b || $diff < $almostZero) {
+ return true;
+ }
+
+ return $diff < $epsilon * max(abs($a), abs($b));
+ }
+
+ /**
+ * Check `$a < $b`, accounting for inaccuracies in float computation.
+ */
+ public static function lengthLess(float $a, float $b): bool
+ {
+ return $a < $b && !self::lengthEqual($a, $b);
+ }
+
+ /**
+ * Check `$a <= $b`, accounting for inaccuracies in float computation.
+ */
+ public static function lengthLessOrEqual(float $a, float $b): bool
+ {
+ return $a <= $b || self::lengthEqual($a, $b);
+ }
+
+ /**
+ * Check `$a > $b`, accounting for inaccuracies in float computation.
+ */
+ public static function lengthGreater(float $a, float $b): bool
+ {
+ return $a > $b && !self::lengthEqual($a, $b);
+ }
+
+ /**
+ * Check `$a >= $b`, accounting for inaccuracies in float computation.
+ */
+ public static function lengthGreaterOrEqual(float $a, float $b): bool
+ {
+ return $a >= $b || self::lengthEqual($a, $b);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Image/Cache.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Image/Cache.php
new file mode 100644
index 0000000..f337bbb
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Image/Cache.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Image;
+
+use Dompdf\Options;
+use Dompdf\Helpers;
+use Dompdf\Exception\ImageException;
+
+/**
+ * Static class that resolves image urls and downloads and caches
+ * remote images if required.
+ *
+ * @package dompdf
+ */
+class Cache
+{
+ /**
+ * Array of downloaded images. Cached so that identical images are
+ * not needlessly downloaded.
+ *
+ * @var array
+ */
+ protected static $_cache = [];
+
+ /**
+ * @var array
+ */
+ protected static $tempImages = [];
+
+ /**
+ * The url to the "broken image" used when images can't be loaded
+ *
+ * @var string
+ */
+ public static $broken_image = "data:image/svg+xml;charset=utf8,%3C?xml version='1.0'?%3E%3Csvg width='64' height='64' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Crect stroke='%23666666' id='svg_1' height='60.499994' width='60.166667' y='1.666669' x='1.999998' stroke-width='1.5' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_3' y2='59.333253' x2='59.749916' y1='4.333415' x1='4.250079' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_4' y2='59.999665' x2='4.062838' y1='3.750342' x1='60.062164' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3C/g%3E%3C/svg%3E";
+
+ public static $error_message = "Image not found or type unknown";
+
+ /**
+ * Resolve and fetch an image for use.
+ *
+ * @param string $url The url of the image
+ * @param string $protocol Default protocol if none specified in $url
+ * @param string $host Default host if none specified in $url
+ * @param string $base_path Default path if none specified in $url
+ * @param Options $options An instance of Dompdf\Options
+ *
+ * @return array An array with three elements: The local path to the image, the image
+ * extension, and an error message if the image could not be cached
+ */
+ static function resolve_url($url, $protocol, $host, $base_path, Options $options)
+ {
+ $tempfile = null;
+ $resolved_url = null;
+ $type = null;
+ $message = null;
+
+ try {
+ $full_url = Helpers::build_url($protocol, $host, $base_path, $url);
+
+ if ($full_url === null) {
+ throw new ImageException("Unable to parse image URL $url.", E_WARNING);
+ }
+
+ $parsed_url = Helpers::explode_url($full_url);
+ $protocol = strtolower($parsed_url["protocol"]);
+ $is_data_uri = strpos($protocol, "data:") === 0;
+
+ if (!$is_data_uri) {
+ $allowed_protocols = $options->getAllowedProtocols();
+ if (!array_key_exists($protocol, $allowed_protocols)) {
+ throw new ImageException("Permission denied on $url. The communication protocol is not supported.", E_WARNING);
+ }
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
+ [$result, $message] = $rule($full_url);
+ if (!$result) {
+ throw new ImageException("Error loading $url: $message", E_WARNING);
+ }
+ }
+ }
+
+ if ($protocol === "file://") {
+ $resolved_url = $full_url;
+ } elseif (isset(self::$_cache[$full_url])) {
+ $resolved_url = self::$_cache[$full_url];
+ } else {
+ $tmp_dir = $options->getTempDir();
+ if (($resolved_url = @tempnam($tmp_dir, "ca_dompdf_img_")) === false) {
+ throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
+ }
+ $tempfile = $resolved_url;
+
+ $image = null;
+ if ($is_data_uri) {
+ if (($parsed_data_uri = Helpers::parse_data_uri($url)) !== false) {
+ $image = $parsed_data_uri["data"];
+ }
+ } else {
+ list($image, $http_response_header) = Helpers::getFileContent($full_url, $options->getHttpContext());
+ }
+
+ // Image not found or invalid
+ if ($image === null) {
+ $msg = ($is_data_uri ? "Data-URI could not be parsed" : "Image not found");
+ throw new ImageException($msg, E_WARNING);
+ }
+
+ if (@file_put_contents($resolved_url, $image) === false) {
+ throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
+ }
+
+ self::$_cache[$full_url] = $resolved_url;
+ }
+
+ // Check if the local file is readable
+ if (!is_readable($resolved_url) || !filesize($resolved_url)) {
+ throw new ImageException("Image not readable or empty", E_WARNING);
+ }
+
+ list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
+
+ if (($width && $height && in_array($type, ["gif", "png", "jpeg", "bmp", "svg","webp"], true)) === false) {
+ throw new ImageException("Image type unknown", E_WARNING);
+ }
+
+ if ($type === "svg") {
+ $parser = xml_parser_create("utf-8");
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
+ xml_set_element_handler(
+ $parser,
+ function ($parser, $name, $attributes) use ($options, $parsed_url, $full_url) {
+ if ($name === "image") {
+ $attributes = array_change_key_case($attributes, CASE_LOWER);
+ $url = $attributes["xlink:href"] ?? $attributes["href"];
+ if (!empty($url)) {
+ $inner_full_url = Helpers::build_url($parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $url);
+ if ($inner_full_url === $full_url) {
+ throw new ImageException("SVG self-reference is not allowed", E_WARNING);
+ }
+ [$resolved_url, $type, $message] = self::resolve_url($url, $parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $options);
+ if (!empty($message)) {
+ throw new ImageException("This SVG document references a restricted resource. $message", E_WARNING);
+ }
+ }
+ }
+ },
+ false
+ );
+
+ if (($fp = fopen($resolved_url, "r")) !== false) {
+ while ($line = fread($fp, 8192)) {
+ xml_parse($parser, $line, false);
+ }
+ fclose($fp);
+ }
+ xml_parser_free($parser);
+ }
+ } catch (ImageException $e) {
+ if ($tempfile) {
+ unlink($tempfile);
+ }
+ $resolved_url = self::$broken_image;
+ list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
+ $message = self::$error_message;
+ Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
+ self::$_cache[$full_url] = $resolved_url;
+ }
+
+ return [$resolved_url, $type, $message];
+ }
+
+ /**
+ * Register a temp file for the given original image file.
+ *
+ * @param string $filePath The path of the original image.
+ * @param string $tempPath The path of the temp file to register.
+ * @param string $key An optional key to register the temp file at.
+ */
+ static function addTempImage(string $filePath, string $tempPath, string $key = "default"): void
+ {
+ if (!isset(self::$tempImages[$filePath])) {
+ self::$tempImages[$filePath] = [];
+ }
+
+ self::$tempImages[$filePath][$key] = $tempPath;
+ }
+
+ /**
+ * Get the path of a temp file registered for the given original image file.
+ *
+ * @param string $filePath The path of the original image.
+ * @param string $key The key the temp file is registered at.
+ */
+ static function getTempImage(string $filePath, string $key = "default"): ?string
+ {
+ return self::$tempImages[$filePath][$key] ?? null;
+ }
+
+ /**
+ * Unlink all cached images (i.e. temporary images either downloaded
+ * or converted) except for the bundled "broken image"
+ */
+ static function clear(bool $debugPng = false)
+ {
+ foreach (self::$_cache as $file) {
+ if ($file === self::$broken_image) {
+ continue;
+ }
+ if ($debugPng) {
+ print "[clear unlink $file]";
+ }
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+
+ foreach (self::$tempImages as $versions) {
+ foreach ($versions as $file) {
+ if ($file === self::$broken_image) {
+ continue;
+ }
+ if ($debugPng) {
+ print "[unlink temp image $file]";
+ }
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ }
+
+ self::$_cache = [];
+ self::$tempImages = [];
+ }
+
+ static function detect_type($file, $context = null)
+ {
+ list(, , $type) = Helpers::dompdf_getimagesize($file, $context);
+
+ return $type;
+ }
+
+ static function is_broken($url)
+ {
+ return $url === self::$broken_image;
+ }
+}
+
+if (file_exists(realpath(__DIR__ . "/../../lib/res/broken_image.svg"))) {
+ Cache::$broken_image = realpath(__DIR__ . "/../../lib/res/broken_image.svg");
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/JavascriptEmbedder.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/JavascriptEmbedder.php
new file mode 100644
index 0000000..f4b9bc2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/JavascriptEmbedder.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+/**
+ * Embeds Javascript into the PDF document
+ *
+ * @package dompdf
+ */
+class JavascriptEmbedder
+{
+
+ /**
+ * @var Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * JavascriptEmbedder constructor.
+ *
+ * @param Dompdf $dompdf
+ */
+ public function __construct(Dompdf $dompdf)
+ {
+ $this->_dompdf = $dompdf;
+ }
+
+ /**
+ * @param $script
+ */
+ public function insert($script)
+ {
+ $this->_dompdf->getCanvas()->javascript($script);
+ }
+
+ /**
+ * @param Frame $frame
+ */
+ public function render(Frame $frame)
+ {
+ if (!$this->_dompdf->getOptions()->getIsJavascriptEnabled()) {
+ return;
+ }
+
+ $this->insert($frame->get_node()->nodeValue);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/LineBox.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/LineBox.php
new file mode 100644
index 0000000..11b83c1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/LineBox.php
@@ -0,0 +1,412 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\Block;
+use Dompdf\FrameDecorator\ListBullet;
+use Dompdf\FrameDecorator\Page;
+use Dompdf\FrameReflower\Text as TextFrameReflower;
+use Dompdf\Positioner\Inline as InlinePositioner;
+
+/**
+ * The line box class
+ *
+ * This class represents a line box
+ * http://www.w3.org/TR/CSS2/visuren.html#line-box
+ *
+ * @package dompdf
+ */
+class LineBox
+{
+
+ /**
+ * @var Block
+ */
+ protected $_block_frame;
+
+ /**
+ * @var AbstractFrameDecorator[]
+ */
+ protected $_frames = [];
+
+ /**
+ * @var ListBullet[]
+ */
+ protected $list_markers = [];
+
+ /**
+ * @var int
+ */
+ public $wc = 0;
+
+ /**
+ * @var float
+ */
+ public $y = null;
+
+ /**
+ * @var float
+ */
+ public $w = 0.0;
+
+ /**
+ * @var float
+ */
+ public $h = 0.0;
+
+ /**
+ * @var float
+ */
+ public $left = 0.0;
+
+ /**
+ * @var float
+ */
+ public $right = 0.0;
+
+ /**
+ * @var AbstractFrameDecorator
+ */
+ public $tallest_frame = null;
+
+ /**
+ * @var bool[]
+ */
+ public $floating_blocks = [];
+
+ /**
+ * @var bool
+ */
+ public $br = false;
+
+ /**
+ * Whether the line box contains any inline-positioned frames.
+ *
+ * @var bool
+ */
+ public $inline = false;
+
+ /**
+ * Class constructor
+ *
+ * @param Block $frame the Block containing this line
+ * @param int $y
+ */
+ public function __construct(Block $frame, $y = 0)
+ {
+ $this->_block_frame = $frame;
+ $this->_frames = [];
+ $this->y = $y;
+
+ $this->get_float_offsets();
+ }
+
+ /**
+ * Returns the floating elements inside the first floating parent
+ *
+ * @param Page $root
+ *
+ * @return Frame[]
+ */
+ public function get_floats_inside(Page $root)
+ {
+ $floating_frames = $root->get_floating_frames();
+
+ if (count($floating_frames) == 0) {
+ return $floating_frames;
+ }
+
+ // Find nearest floating element
+ $p = $this->_block_frame;
+ while ($p->get_style()->float === "none") {
+ $parent = $p->get_parent();
+
+ if (!$parent) {
+ break;
+ }
+
+ $p = $parent;
+ }
+
+ if ($p == $root) {
+ return $floating_frames;
+ }
+
+ $parent = $p;
+
+ $childs = [];
+
+ foreach ($floating_frames as $_floating) {
+ $p = $_floating->get_parent();
+
+ while (($p = $p->get_parent()) && $p !== $parent);
+
+ if ($p) {
+ $childs[] = $p;
+ }
+ }
+
+ return $childs;
+ }
+
+ public function get_float_offsets()
+ {
+ static $anti_infinite_loop = 10000; // FIXME smelly hack
+
+ $reflower = $this->_block_frame->get_reflower();
+
+ if (!$reflower) {
+ return;
+ }
+
+ $cb_w = null;
+
+ $block = $this->_block_frame;
+ $root = $block->get_root();
+
+ if (!$root) {
+ return;
+ }
+
+ $style = $this->_block_frame->get_style();
+ $floating_frames = $this->get_floats_inside($root);
+ $inside_left_floating_width = 0;
+ $inside_right_floating_width = 0;
+ $outside_left_floating_width = 0;
+ $outside_right_floating_width = 0;
+
+ foreach ($floating_frames as $child_key => $floating_frame) {
+ $floating_frame_parent = $floating_frame->get_parent();
+ $id = $floating_frame->get_id();
+
+ if (isset($this->floating_blocks[$id])) {
+ continue;
+ }
+
+ $float = $floating_frame->get_style()->float;
+ $floating_width = $floating_frame->get_margin_width();
+
+ if (!$cb_w) {
+ $cb_w = $floating_frame->get_containing_block("w");
+ }
+
+ $line_w = $this->get_width();
+
+ if (!$floating_frame->_float_next_line && ($cb_w <= $line_w + $floating_width) && ($cb_w > $line_w)) {
+ $floating_frame->_float_next_line = true;
+ continue;
+ }
+
+ // If the child is still shifted by the floating element
+ if ($anti_infinite_loop-- > 0 &&
+ $floating_frame->get_position("y") + $floating_frame->get_margin_height() >= $this->y &&
+ $block->get_position("x") + $block->get_margin_width() >= $floating_frame->get_position("x")
+ ) {
+ if ($float === "left") {
+ if ($floating_frame_parent === $this->_block_frame) {
+ $inside_left_floating_width += $floating_width;
+ } else {
+ $outside_left_floating_width += $floating_width;
+ }
+ } elseif ($float === "right") {
+ if ($floating_frame_parent === $this->_block_frame) {
+ $inside_right_floating_width += $floating_width;
+ } else {
+ $outside_right_floating_width += $floating_width;
+ }
+ }
+
+ $this->floating_blocks[$id] = true;
+ } // else, the floating element won't shift anymore
+ else {
+ $root->remove_floating_frame($child_key);
+ }
+ }
+
+ $this->left += $inside_left_floating_width;
+ if ($outside_left_floating_width > 0 && $outside_left_floating_width > ((float)$style->length_in_pt($style->margin_left) + (float)$style->length_in_pt($style->padding_left))) {
+ $this->left += $outside_left_floating_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->padding_left);
+ }
+ $this->right += $inside_right_floating_width;
+ if ($outside_right_floating_width > 0 && $outside_right_floating_width > ((float)$style->length_in_pt($style->margin_left) + (float)$style->length_in_pt($style->padding_right))) {
+ $this->right += $outside_right_floating_width - (float)$style->length_in_pt($style->margin_right) - (float)$style->length_in_pt($style->padding_right);
+ }
+ }
+
+ /**
+ * @return float
+ */
+ public function get_width()
+ {
+ return $this->left + $this->w + $this->right;
+ }
+
+ /**
+ * @return Block
+ */
+ public function get_block_frame()
+ {
+ return $this->_block_frame;
+ }
+
+ /**
+ * @return AbstractFrameDecorator[]
+ */
+ function &get_frames()
+ {
+ return $this->_frames;
+ }
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ public function add_frame(Frame $frame): void
+ {
+ $this->_frames[] = $frame;
+
+ if ($frame->get_positioner() instanceof InlinePositioner) {
+ $this->inline = true;
+ }
+ }
+
+ /**
+ * Remove the frame at the given index and all following frames from the
+ * line.
+ *
+ * @param int $index
+ */
+ public function remove_frames(int $index): void
+ {
+ $lastIndex = count($this->_frames) - 1;
+
+ if ($index < 0 || $index > $lastIndex) {
+ return;
+ }
+
+ for ($i = $lastIndex; $i >= $index; $i--) {
+ $f = $this->_frames[$i];
+ unset($this->_frames[$i]);
+ $this->w -= $f->get_margin_width();
+ }
+
+ // Reset array indices
+ $this->_frames = array_values($this->_frames);
+
+ // Recalculate the height of the line
+ $h = 0.0;
+ $this->inline = false;
+
+ foreach ($this->_frames as $f) {
+ $h = max($h, $f->get_margin_height());
+
+ if ($f->get_positioner() instanceof InlinePositioner) {
+ $this->inline = true;
+ }
+ }
+
+ $this->h = $h;
+ }
+
+ /**
+ * Get the `outside` positioned list markers to be vertically aligned with
+ * the line box.
+ *
+ * @return ListBullet[]
+ */
+ public function get_list_markers(): array
+ {
+ return $this->list_markers;
+ }
+
+ /**
+ * Add a list marker to the line box.
+ *
+ * The list marker is only added for the purpose of vertical alignment, it
+ * is not actually added to the list of frames of the line box.
+ */
+ public function add_list_marker(ListBullet $marker): void
+ {
+ $this->list_markers[] = $marker;
+ }
+
+ /**
+ * An iterator of all list markers and inline positioned frames of the line
+ * box.
+ *
+ * @return \Iterator<AbstractFrameDecorator>
+ */
+ public function frames_to_align(): \Iterator
+ {
+ yield from $this->list_markers;
+
+ foreach ($this->_frames as $frame) {
+ if ($frame->get_positioner() instanceof InlinePositioner) {
+ yield $frame;
+ }
+ }
+ }
+
+ /**
+ * Trim trailing whitespace from the line.
+ */
+ public function trim_trailing_ws(): void
+ {
+ $lastIndex = count($this->_frames) - 1;
+
+ if ($lastIndex < 0) {
+ return;
+ }
+
+ $lastFrame = $this->_frames[$lastIndex];
+ $reflower = $lastFrame->get_reflower();
+
+ if ($reflower instanceof TextFrameReflower && !$lastFrame->is_pre()) {
+ $reflower->trim_trailing_ws();
+ $this->recalculate_width();
+ }
+ }
+
+ /**
+ * Recalculate LineBox width based on the contained frames total width.
+ *
+ * @return float
+ */
+ public function recalculate_width(): float
+ {
+ $width = 0.0;
+
+ foreach ($this->_frames as $frame) {
+ $width += $frame->get_margin_width();
+ }
+
+ return $this->w = $width;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString(): string
+ {
+ $props = ["wc", "y", "w", "h", "left", "right", "br"];
+ $s = "";
+ foreach ($props as $prop) {
+ $s .= "$prop: " . $this->$prop . "\n";
+ }
+ $s .= count($this->_frames) . " frames\n";
+
+ return $s;
+ }
+}
+
+/*
+class LineBoxList implements Iterator {
+ private $_p = 0;
+ private $_lines = array();
+
+}
+*/
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Options.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Options.php
new file mode 100644
index 0000000..de127d2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Options.php
@@ -0,0 +1,1159 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+class Options
+{
+ /**
+ * The root of your DOMPDF installation
+ *
+ * @var string
+ */
+ private $rootDir;
+
+ /**
+ * The location of a temporary directory.
+ *
+ * The directory specified must be writable by the executing process.
+ * The temporary directory is required to download remote images and when
+ * using the PFDLib back end.
+ *
+ * @var string
+ */
+ private $tempDir;
+
+ /**
+ * The location of the DOMPDF font directory
+ *
+ * The location of the directory where DOMPDF will store fonts and font metrics
+ * Note: This directory must exist and be writable by the executing process.
+ *
+ * @var string
+ */
+ private $fontDir;
+
+ /**
+ * The location of the DOMPDF font cache directory
+ *
+ * This directory contains the cached font metrics for the fonts used by DOMPDF.
+ * This directory can be the same as $fontDir
+ *
+ * Note: This directory must exist and be writable by the executing process.
+ *
+ * @var string
+ */
+ private $fontCache;
+
+ /**
+ * dompdf's "chroot"
+ *
+ * Utilized by Dompdf's default file:// protocol URI validation rule.
+ * All local files opened by dompdf must be in a subdirectory of the directory
+ * or directories specified by this option.
+ * DO NOT set this value to '/' since this could allow an attacker to use dompdf to
+ * read any files on the server. This should be an absolute path.
+ *
+ * ==== IMPORTANT ====
+ * This setting may increase the risk of system exploit. Do not change
+ * this settings without understanding the consequences. Additional
+ * documentation is available on the dompdf wiki at:
+ * https://github.com/dompdf/dompdf/wiki
+ *
+ * @var array
+ */
+ private $chroot;
+
+ /**
+ * Protocol whitelist
+ *
+ * Protocols and PHP wrappers allowed in URIs, and the validation rules
+ * that determine if a resouce may be loaded. Full support is not guaranteed
+ * for the protocols/wrappers specified
+ * by this array.
+ *
+ * @var array
+ */
+ private $allowedProtocols = [
+ "file://" => ["rules" => []],
+ "http://" => ["rules" => []],
+ "https://" => ["rules" => []]
+ ];
+
+ /**
+ * @var string
+ */
+ private $logOutputFile;
+
+ /**
+ * Styles targeted to this media type are applied to the document.
+ * This is on top of the media types that are always applied:
+ * all, static, visual, bitmap, paged, dompdf
+ *
+ * @var string
+ */
+ private $defaultMediaType = "screen";
+
+ /**
+ * The default paper size.
+ *
+ * North America standard is "letter"; other countries generally "a4"
+ * @see \Dompdf\Adapter\CPDF::PAPER_SIZES for valid sizes
+ *
+ * @var string
+ */
+ private $defaultPaperSize = "letter";
+
+ /**
+ * The default paper orientation.
+ *
+ * The orientation of the page (portrait or landscape).
+ *
+ * @var string
+ */
+ private $defaultPaperOrientation = "portrait";
+
+ /**
+ * The default font family
+ *
+ * Used if no suitable fonts can be found. This must exist in the font folder.
+ *
+ * @var string
+ */
+ private $defaultFont = "serif";
+
+ /**
+ * Image DPI setting
+ *
+ * This setting determines the default DPI setting for images and fonts. The
+ * DPI may be overridden for inline images by explicitly setting the
+ * image's width & height style attributes (i.e. if the image's native
+ * width is 600 pixels and you specify the image's width as 72 points,
+ * the image will have a DPI of 600 in the rendered PDF. The DPI of
+ * background images can not be overridden and is controlled entirely
+ * via this parameter.
+ *
+ * For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI).
+ * If a size in html is given as px (or without unit as image size),
+ * this tells the corresponding size in pt at 72 DPI.
+ * This adjusts the relative sizes to be similar to the rendering of the
+ * html page in a reference browser.
+ *
+ * In pdf, always 1 pt = 1/72 inch
+ *
+ * @var int
+ */
+ private $dpi = 96;
+
+ /**
+ * A ratio applied to the fonts height to be more like browsers' line height
+ *
+ * @var float
+ */
+ private $fontHeightRatio = 1.1;
+
+ /**
+ * Enable embedded PHP
+ *
+ * If this setting is set to true then DOMPDF will automatically evaluate
+ * embedded PHP contained within <script type="text/php"> ... </script> tags.
+ *
+ * ==== IMPORTANT ====
+ * Enabling this for documents you do not trust (e.g. arbitrary remote html
+ * pages) is a security risk. Embedded scripts are run with the same level of
+ * system access available to dompdf. Set this option to false (recommended)
+ * if you wish to process untrusted documents.
+ *
+ * This setting may increase the risk of system exploit. Do not change
+ * this settings without understanding the consequences. Additional
+ * documentation is available on the dompdf wiki at:
+ * https://github.com/dompdf/dompdf/wiki
+ *
+ * @var bool
+ */
+ private $isPhpEnabled = false;
+
+ /**
+ * Enable remote file access
+ *
+ * If this setting is set to true, DOMPDF will access remote sites for
+ * images and CSS files as required.
+ *
+ * ==== IMPORTANT ====
+ * This can be a security risk, in particular in combination with isPhpEnabled and
+ * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...);
+ * This allows anonymous users to download legally doubtful internet content which on
+ * tracing back appears to being downloaded by your server, or allows malicious php code
+ * in remote html pages to be executed by your server with your account privileges.
+ *
+ * This setting may increase the risk of system exploit. Do not change
+ * this settings without understanding the consequences. Additional
+ * documentation is available on the dompdf wiki at:
+ * https://github.com/dompdf/dompdf/wiki
+ *
+ * @var bool
+ */
+ private $isRemoteEnabled = false;
+
+ /**
+ * Enable inline JavaScript
+ *
+ * If this setting is set to true then DOMPDF will automatically insert
+ * JavaScript code contained within <script type="text/javascript"> ... </script>
+ * tags as written into the PDF.
+ *
+ * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer,
+ * not browser-based JavaScript executed by Dompdf.
+ *
+ * @var bool
+ */
+ private $isJavascriptEnabled = true;
+
+ /**
+ * Use the HTML5 Lib parser
+ *
+ * @deprecated
+ * @var bool
+ */
+ private $isHtml5ParserEnabled = true;
+
+ /**
+ * Whether to enable font subsetting or not.
+ *
+ * @var bool
+ */
+ private $isFontSubsettingEnabled = true;
+
+ /**
+ * @var bool
+ */
+ private $debugPng = false;
+
+ /**
+ * @var bool
+ */
+ private $debugKeepTemp = false;
+
+ /**
+ * @var bool
+ */
+ private $debugCss = false;
+
+ /**
+ * @var bool
+ */
+ private $debugLayout = false;
+
+ /**
+ * @var bool
+ */
+ private $debugLayoutLines = true;
+
+ /**
+ * @var bool
+ */
+ private $debugLayoutBlocks = true;
+
+ /**
+ * @var bool
+ */
+ private $debugLayoutInline = true;
+
+ /**
+ * @var bool
+ */
+ private $debugLayoutPaddingBox = true;
+
+ /**
+ * The PDF rendering backend to use
+ *
+ * Valid settings are 'PDFLib', 'CPDF', 'GD', and 'auto'. 'auto' will
+ * look for PDFLib and use it if found, or if not it will fall back on
+ * CPDF. 'GD' renders PDFs to graphic files. {@link Dompdf\CanvasFactory}
+ * ultimately determines which rendering class to instantiate
+ * based on this setting.
+ *
+ * @var string
+ */
+ private $pdfBackend = "CPDF";
+
+ /**
+ * PDFlib license key
+ *
+ * If you are using a licensed, commercial version of PDFlib, specify
+ * your license key here. If you are using PDFlib-Lite or are evaluating
+ * the commercial version of PDFlib, comment out this setting.
+ *
+ * @link http://www.pdflib.com
+ *
+ * If pdflib present in web server and auto or selected explicitly above,
+ * a real license code must exist!
+ *
+ * @var string
+ */
+ private $pdflibLicense = "";
+
+ /**
+ * HTTP context created with stream_context_create()
+ * Will be used for file_get_contents
+ *
+ * @link https://www.php.net/manual/context.php
+ *
+ * @var resource
+ */
+ private $httpContext;
+
+ /**
+ * @param array $attributes
+ */
+ public function __construct(array $attributes = null)
+ {
+ $rootDir = realpath(__DIR__ . "/../");
+ $this->setChroot(array($rootDir));
+ $this->setRootDir($rootDir);
+ $this->setTempDir(sys_get_temp_dir());
+ $this->setFontDir($rootDir . "/lib/fonts");
+ $this->setFontCache($this->getFontDir());
+
+ $ver = "";
+ $versionFile = realpath(__DIR__ . '/../VERSION');
+ if (($version = file_get_contents($versionFile)) !== false) {
+ $version = trim($version);
+ if ($version !== '$Format:<%h>$') {
+ $ver = "/$version";
+ }
+ }
+ $this->setHttpContext([
+ "http" => [
+ "follow_location" => false,
+ "user_agent" => "Dompdf$ver https://github.com/dompdf/dompdf"
+ ]
+ ]);
+
+ $this->setAllowedProtocols(["file://", "http://", "https://"]);
+
+ if (null !== $attributes) {
+ $this->set($attributes);
+ }
+ }
+
+ /**
+ * @param array|string $attributes
+ * @param null|mixed $value
+ * @return $this
+ */
+ public function set($attributes, $value = null)
+ {
+ if (!is_array($attributes)) {
+ $attributes = [$attributes => $value];
+ }
+ foreach ($attributes as $key => $value) {
+ if ($key === 'tempDir' || $key === 'temp_dir') {
+ $this->setTempDir($value);
+ } elseif ($key === 'fontDir' || $key === 'font_dir') {
+ $this->setFontDir($value);
+ } elseif ($key === 'fontCache' || $key === 'font_cache') {
+ $this->setFontCache($value);
+ } elseif ($key === 'chroot') {
+ $this->setChroot($value);
+ } elseif ($key === 'allowedProtocols') {
+ $this->setAllowedProtocols($value);
+ } elseif ($key === 'logOutputFile' || $key === 'log_output_file') {
+ $this->setLogOutputFile($value);
+ } elseif ($key === 'defaultMediaType' || $key === 'default_media_type') {
+ $this->setDefaultMediaType($value);
+ } elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') {
+ $this->setDefaultPaperSize($value);
+ } elseif ($key === 'defaultPaperOrientation' || $key === 'default_paper_orientation') {
+ $this->setDefaultPaperOrientation($value);
+ } elseif ($key === 'defaultFont' || $key === 'default_font') {
+ $this->setDefaultFont($value);
+ } elseif ($key === 'dpi') {
+ $this->setDpi($value);
+ } elseif ($key === 'fontHeightRatio' || $key === 'font_height_ratio') {
+ $this->setFontHeightRatio($value);
+ } elseif ($key === 'isPhpEnabled' || $key === 'is_php_enabled' || $key === 'enable_php') {
+ $this->setIsPhpEnabled($value);
+ } elseif ($key === 'isRemoteEnabled' || $key === 'is_remote_enabled' || $key === 'enable_remote') {
+ $this->setIsRemoteEnabled($value);
+ } elseif ($key === 'isJavascriptEnabled' || $key === 'is_javascript_enabled' || $key === 'enable_javascript') {
+ $this->setIsJavascriptEnabled($value);
+ } elseif ($key === 'isHtml5ParserEnabled' || $key === 'is_html5_parser_enabled' || $key === 'enable_html5_parser') {
+ $this->setIsHtml5ParserEnabled($value);
+ } elseif ($key === 'isFontSubsettingEnabled' || $key === 'is_font_subsetting_enabled' || $key === 'enable_font_subsetting') {
+ $this->setIsFontSubsettingEnabled($value);
+ } elseif ($key === 'debugPng' || $key === 'debug_png') {
+ $this->setDebugPng($value);
+ } elseif ($key === 'debugKeepTemp' || $key === 'debug_keep_temp') {
+ $this->setDebugKeepTemp($value);
+ } elseif ($key === 'debugCss' || $key === 'debug_css') {
+ $this->setDebugCss($value);
+ } elseif ($key === 'debugLayout' || $key === 'debug_layout') {
+ $this->setDebugLayout($value);
+ } elseif ($key === 'debugLayoutLines' || $key === 'debug_layout_lines') {
+ $this->setDebugLayoutLines($value);
+ } elseif ($key === 'debugLayoutBlocks' || $key === 'debug_layout_blocks') {
+ $this->setDebugLayoutBlocks($value);
+ } elseif ($key === 'debugLayoutInline' || $key === 'debug_layout_inline') {
+ $this->setDebugLayoutInline($value);
+ } elseif ($key === 'debugLayoutPaddingBox' || $key === 'debug_layout_padding_box') {
+ $this->setDebugLayoutPaddingBox($value);
+ } elseif ($key === 'pdfBackend' || $key === 'pdf_backend') {
+ $this->setPdfBackend($value);
+ } elseif ($key === 'pdflibLicense' || $key === 'pdflib_license') {
+ $this->setPdflibLicense($value);
+ } elseif ($key === 'httpContext' || $key === 'http_context') {
+ $this->setHttpContext($value);
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * @param string $key
+ * @return mixed
+ */
+ public function get($key)
+ {
+ if ($key === 'tempDir' || $key === 'temp_dir') {
+ return $this->getTempDir();
+ } elseif ($key === 'fontDir' || $key === 'font_dir') {
+ return $this->getFontDir();
+ } elseif ($key === 'fontCache' || $key === 'font_cache') {
+ return $this->getFontCache();
+ } elseif ($key === 'chroot') {
+ return $this->getChroot();
+ } elseif ($key === 'allowedProtocols') {
+ return $this->getAllowedProtocols();
+ } elseif ($key === 'logOutputFile' || $key === 'log_output_file') {
+ return $this->getLogOutputFile();
+ } elseif ($key === 'defaultMediaType' || $key === 'default_media_type') {
+ return $this->getDefaultMediaType();
+ } elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') {
+ return $this->getDefaultPaperSize();
+ } elseif ($key === 'defaultPaperOrientation' || $key === 'default_paper_orientation') {
+ return $this->getDefaultPaperOrientation();
+ } elseif ($key === 'defaultFont' || $key === 'default_font') {
+ return $this->getDefaultFont();
+ } elseif ($key === 'dpi') {
+ return $this->getDpi();
+ } elseif ($key === 'fontHeightRatio' || $key === 'font_height_ratio') {
+ return $this->getFontHeightRatio();
+ } elseif ($key === 'isPhpEnabled' || $key === 'is_php_enabled' || $key === 'enable_php') {
+ return $this->getIsPhpEnabled();
+ } elseif ($key === 'isRemoteEnabled' || $key === 'is_remote_enabled' || $key === 'enable_remote') {
+ return $this->getIsRemoteEnabled();
+ } elseif ($key === 'isJavascriptEnabled' || $key === 'is_javascript_enabled' || $key === 'enable_javascript') {
+ return $this->getIsJavascriptEnabled();
+ } elseif ($key === 'isHtml5ParserEnabled' || $key === 'is_html5_parser_enabled' || $key === 'enable_html5_parser') {
+ return $this->getIsHtml5ParserEnabled();
+ } elseif ($key === 'isFontSubsettingEnabled' || $key === 'is_font_subsetting_enabled' || $key === 'enable_font_subsetting') {
+ return $this->getIsFontSubsettingEnabled();
+ } elseif ($key === 'debugPng' || $key === 'debug_png') {
+ return $this->getDebugPng();
+ } elseif ($key === 'debugKeepTemp' || $key === 'debug_keep_temp') {
+ return $this->getDebugKeepTemp();
+ } elseif ($key === 'debugCss' || $key === 'debug_css') {
+ return $this->getDebugCss();
+ } elseif ($key === 'debugLayout' || $key === 'debug_layout') {
+ return $this->getDebugLayout();
+ } elseif ($key === 'debugLayoutLines' || $key === 'debug_layout_lines') {
+ return $this->getDebugLayoutLines();
+ } elseif ($key === 'debugLayoutBlocks' || $key === 'debug_layout_blocks') {
+ return $this->getDebugLayoutBlocks();
+ } elseif ($key === 'debugLayoutInline' || $key === 'debug_layout_inline') {
+ return $this->getDebugLayoutInline();
+ } elseif ($key === 'debugLayoutPaddingBox' || $key === 'debug_layout_padding_box') {
+ return $this->getDebugLayoutPaddingBox();
+ } elseif ($key === 'pdfBackend' || $key === 'pdf_backend') {
+ return $this->getPdfBackend();
+ } elseif ($key === 'pdflibLicense' || $key === 'pdflib_license') {
+ return $this->getPdflibLicense();
+ } elseif ($key === 'httpContext' || $key === 'http_context') {
+ return $this->getHttpContext();
+ }
+ return null;
+ }
+
+ /**
+ * @param string $pdfBackend
+ * @return $this
+ */
+ public function setPdfBackend($pdfBackend)
+ {
+ $this->pdfBackend = $pdfBackend;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPdfBackend()
+ {
+ return $this->pdfBackend;
+ }
+
+ /**
+ * @param string $pdflibLicense
+ * @return $this
+ */
+ public function setPdflibLicense($pdflibLicense)
+ {
+ $this->pdflibLicense = $pdflibLicense;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPdflibLicense()
+ {
+ return $this->pdflibLicense;
+ }
+
+ /**
+ * @param array|string $chroot
+ * @return $this
+ */
+ public function setChroot($chroot, $delimiter = ',')
+ {
+ if (is_string($chroot)) {
+ $this->chroot = explode($delimiter, $chroot);
+ } elseif (is_array($chroot)) {
+ $this->chroot = $chroot;
+ }
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getAllowedProtocols()
+ {
+ return $this->allowedProtocols;
+ }
+
+ /**
+ * @param array $allowedProtocols The protocols to allow, as an array
+ * formatted as ["protocol://" => ["rules" => [callable]], ...]
+ * or ["protocol://", ...]
+ *
+ * @return $this
+ */
+ public function setAllowedProtocols(array $allowedProtocols)
+ {
+ $protocols = [];
+ foreach ($allowedProtocols as $protocol => $config) {
+ if (is_string($protocol)) {
+ $protocols[$protocol] = [];
+ if (is_array($config)) {
+ $protocols[$protocol] = $config;
+ }
+ } elseif (is_string($config)) {
+ $protocols[$config] = [];
+ }
+ }
+ $this->allowedProtocols = [];
+ foreach ($protocols as $protocol => $config) {
+ $this->addAllowedProtocol($protocol, ...($config["rules"] ?? []));
+ }
+ return $this;
+ }
+
+ /**
+ * Adds a new protocol to the allowed protocols collection
+ *
+ * @param string $protocol The scheme to add (e.g. "http://")
+ * @param callable $rule A callable that validates the protocol
+ * @return $this
+ */
+ public function addAllowedProtocol(string $protocol, callable ...$rules)
+ {
+ $protocol = strtolower($protocol);
+ if (empty($rules)) {
+ $rules = [];
+ switch ($protocol) {
+ case "file://":
+ $rules[] = [$this, "validateLocalUri"];
+ break;
+ case "http://":
+ case "https://":
+ $rules[] = [$this, "validateRemoteUri"];
+ break;
+ case "phar://":
+ $rules[] = [$this, "validatePharUri"];
+ break;
+ }
+ }
+ $this->allowedProtocols[$protocol] = ["rules" => $rules];
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getChroot()
+ {
+ $chroot = [];
+ if (is_array($this->chroot)) {
+ $chroot = $this->chroot;
+ }
+ return $chroot;
+ }
+
+ /**
+ * @param boolean $debugCss
+ * @return $this
+ */
+ public function setDebugCss($debugCss)
+ {
+ $this->debugCss = $debugCss;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugCss()
+ {
+ return $this->debugCss;
+ }
+
+ /**
+ * @param boolean $debugKeepTemp
+ * @return $this
+ */
+ public function setDebugKeepTemp($debugKeepTemp)
+ {
+ $this->debugKeepTemp = $debugKeepTemp;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugKeepTemp()
+ {
+ return $this->debugKeepTemp;
+ }
+
+ /**
+ * @param boolean $debugLayout
+ * @return $this
+ */
+ public function setDebugLayout($debugLayout)
+ {
+ $this->debugLayout = $debugLayout;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugLayout()
+ {
+ return $this->debugLayout;
+ }
+
+ /**
+ * @param boolean $debugLayoutBlocks
+ * @return $this
+ */
+ public function setDebugLayoutBlocks($debugLayoutBlocks)
+ {
+ $this->debugLayoutBlocks = $debugLayoutBlocks;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugLayoutBlocks()
+ {
+ return $this->debugLayoutBlocks;
+ }
+
+ /**
+ * @param boolean $debugLayoutInline
+ * @return $this
+ */
+ public function setDebugLayoutInline($debugLayoutInline)
+ {
+ $this->debugLayoutInline = $debugLayoutInline;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugLayoutInline()
+ {
+ return $this->debugLayoutInline;
+ }
+
+ /**
+ * @param boolean $debugLayoutLines
+ * @return $this
+ */
+ public function setDebugLayoutLines($debugLayoutLines)
+ {
+ $this->debugLayoutLines = $debugLayoutLines;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugLayoutLines()
+ {
+ return $this->debugLayoutLines;
+ }
+
+ /**
+ * @param boolean $debugLayoutPaddingBox
+ * @return $this
+ */
+ public function setDebugLayoutPaddingBox($debugLayoutPaddingBox)
+ {
+ $this->debugLayoutPaddingBox = $debugLayoutPaddingBox;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugLayoutPaddingBox()
+ {
+ return $this->debugLayoutPaddingBox;
+ }
+
+ /**
+ * @param boolean $debugPng
+ * @return $this
+ */
+ public function setDebugPng($debugPng)
+ {
+ $this->debugPng = $debugPng;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getDebugPng()
+ {
+ return $this->debugPng;
+ }
+
+ /**
+ * @param string $defaultFont
+ * @return $this
+ */
+ public function setDefaultFont($defaultFont)
+ {
+ if (!($defaultFont === null || trim($defaultFont) === "")) {
+ $this->defaultFont = $defaultFont;
+ } else {
+ $this->defaultFont = "serif";
+ }
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDefaultFont()
+ {
+ return $this->defaultFont;
+ }
+
+ /**
+ * @param string $defaultMediaType
+ * @return $this
+ */
+ public function setDefaultMediaType($defaultMediaType)
+ {
+ $this->defaultMediaType = $defaultMediaType;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDefaultMediaType()
+ {
+ return $this->defaultMediaType;
+ }
+
+ /**
+ * @param string $defaultPaperSize
+ * @return $this
+ */
+ public function setDefaultPaperSize($defaultPaperSize)
+ {
+ $this->defaultPaperSize = $defaultPaperSize;
+ return $this;
+ }
+
+ /**
+ * @param string $defaultPaperOrientation
+ * @return $this
+ */
+ public function setDefaultPaperOrientation($defaultPaperOrientation)
+ {
+ $this->defaultPaperOrientation = $defaultPaperOrientation;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDefaultPaperSize()
+ {
+ return $this->defaultPaperSize;
+ }
+
+ /**
+ * @return string
+ */
+ public function getDefaultPaperOrientation()
+ {
+ return $this->defaultPaperOrientation;
+ }
+
+ /**
+ * @param int $dpi
+ * @return $this
+ */
+ public function setDpi($dpi)
+ {
+ $this->dpi = $dpi;
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getDpi()
+ {
+ return $this->dpi;
+ }
+
+ /**
+ * @param string $fontCache
+ * @return $this
+ */
+ public function setFontCache($fontCache)
+ {
+ $this->fontCache = $fontCache;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFontCache()
+ {
+ return $this->fontCache;
+ }
+
+ /**
+ * @param string $fontDir
+ * @return $this
+ */
+ public function setFontDir($fontDir)
+ {
+ $this->fontDir = $fontDir;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getFontDir()
+ {
+ return $this->fontDir;
+ }
+
+ /**
+ * @param float $fontHeightRatio
+ * @return $this
+ */
+ public function setFontHeightRatio($fontHeightRatio)
+ {
+ $this->fontHeightRatio = $fontHeightRatio;
+ return $this;
+ }
+
+ /**
+ * @return float
+ */
+ public function getFontHeightRatio()
+ {
+ return $this->fontHeightRatio;
+ }
+
+ /**
+ * @param boolean $isFontSubsettingEnabled
+ * @return $this
+ */
+ public function setIsFontSubsettingEnabled($isFontSubsettingEnabled)
+ {
+ $this->isFontSubsettingEnabled = $isFontSubsettingEnabled;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getIsFontSubsettingEnabled()
+ {
+ return $this->isFontSubsettingEnabled;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isFontSubsettingEnabled()
+ {
+ return $this->getIsFontSubsettingEnabled();
+ }
+
+ /**
+ * @deprecated
+ * @param boolean $isHtml5ParserEnabled
+ * @return $this
+ */
+ public function setIsHtml5ParserEnabled($isHtml5ParserEnabled)
+ {
+ $this->isHtml5ParserEnabled = $isHtml5ParserEnabled;
+ return $this;
+ }
+
+ /**
+ * @deprecated
+ * @return boolean
+ */
+ public function getIsHtml5ParserEnabled()
+ {
+ return $this->isHtml5ParserEnabled;
+ }
+
+ /**
+ * @deprecated
+ * @return boolean
+ */
+ public function isHtml5ParserEnabled()
+ {
+ return $this->getIsHtml5ParserEnabled();
+ }
+
+ /**
+ * @param boolean $isJavascriptEnabled
+ * @return $this
+ */
+ public function setIsJavascriptEnabled($isJavascriptEnabled)
+ {
+ $this->isJavascriptEnabled = $isJavascriptEnabled;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getIsJavascriptEnabled()
+ {
+ return $this->isJavascriptEnabled;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isJavascriptEnabled()
+ {
+ return $this->getIsJavascriptEnabled();
+ }
+
+ /**
+ * @param boolean $isPhpEnabled
+ * @return $this
+ */
+ public function setIsPhpEnabled($isPhpEnabled)
+ {
+ $this->isPhpEnabled = $isPhpEnabled;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getIsPhpEnabled()
+ {
+ return $this->isPhpEnabled;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isPhpEnabled()
+ {
+ return $this->getIsPhpEnabled();
+ }
+
+ /**
+ * @param boolean $isRemoteEnabled
+ * @return $this
+ */
+ public function setIsRemoteEnabled($isRemoteEnabled)
+ {
+ $this->isRemoteEnabled = $isRemoteEnabled;
+ return $this;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getIsRemoteEnabled()
+ {
+ return $this->isRemoteEnabled;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isRemoteEnabled()
+ {
+ return $this->getIsRemoteEnabled();
+ }
+
+ /**
+ * @param string $logOutputFile
+ * @return $this
+ */
+ public function setLogOutputFile($logOutputFile)
+ {
+ $this->logOutputFile = $logOutputFile;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLogOutputFile()
+ {
+ return $this->logOutputFile;
+ }
+
+ /**
+ * @param string $tempDir
+ * @return $this
+ */
+ public function setTempDir($tempDir)
+ {
+ $this->tempDir = $tempDir;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTempDir()
+ {
+ return $this->tempDir;
+ }
+
+ /**
+ * @param string $rootDir
+ * @return $this
+ */
+ public function setRootDir($rootDir)
+ {
+ $this->rootDir = $rootDir;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRootDir()
+ {
+ return $this->rootDir;
+ }
+
+ /**
+ * Sets the HTTP context
+ *
+ * @param resource|array $httpContext
+ * @return $this
+ */
+ public function setHttpContext($httpContext)
+ {
+ $this->httpContext = is_array($httpContext) ? stream_context_create($httpContext) : $httpContext;
+ return $this;
+ }
+
+ /**
+ * Returns the HTTP context
+ *
+ * @return resource
+ */
+ public function getHttpContext()
+ {
+ return $this->httpContext;
+ }
+
+ public function validateLocalUri(string $uri)
+ {
+ if ($uri === null || strlen($uri) === 0) {
+ return [false, "The URI must not be empty."];
+ }
+
+ $realfile = realpath(str_replace("file://", "", $uri));
+
+ $dirs = $this->chroot;
+ $dirs[] = $this->rootDir;
+ $chrootValid = false;
+ foreach ($dirs as $chrootPath) {
+ $chrootPath = realpath($chrootPath);
+ if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
+ $chrootValid = true;
+ break;
+ }
+ }
+ if ($chrootValid !== true) {
+ return [false, "Permission denied. The file could not be found under the paths specified by Options::chroot."];
+ }
+
+ if (!$realfile) {
+ return [false, "File not found."];
+ }
+
+ return [true, null];
+ }
+
+ public function validatePharUri(string $uri)
+ {
+ if ($uri === null || strlen($uri) === 0) {
+ return [false, "The URI must not be empty."];
+ }
+
+ $file = substr(substr($uri, 0, strpos($uri, ".phar") + 5), 7);
+ return $this->validateLocalUri($file);
+ }
+
+ public function validateRemoteUri(string $uri)
+ {
+ if ($uri === null || strlen($uri) === 0) {
+ return [false, "The URI must not be empty."];
+ }
+
+ if (!$this->isRemoteEnabled) {
+ return [false, "Remote file requested, but remote file download is disabled."];
+ }
+
+ return [true, null];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/PhpEvaluator.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/PhpEvaluator.php
new file mode 100644
index 0000000..4a46555
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/PhpEvaluator.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+/**
+ * Executes inline PHP code during the rendering process
+ *
+ * @package dompdf
+ */
+class PhpEvaluator
+{
+
+ /**
+ * @var Canvas
+ */
+ protected $_canvas;
+
+ /**
+ * PhpEvaluator constructor.
+ * @param Canvas $canvas
+ */
+ public function __construct(Canvas $canvas)
+ {
+ $this->_canvas = $canvas;
+ }
+
+ /**
+ * @param $code
+ * @param array $vars
+ */
+ public function evaluate($code, $vars = [])
+ {
+ if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
+ return;
+ }
+
+ // Set up some variables for the inline code
+ $pdf = $this->_canvas;
+ $fontMetrics = $pdf->get_dompdf()->getFontMetrics();
+ $PAGE_NUM = $pdf->get_page_number();
+ $PAGE_COUNT = $pdf->get_page_count();
+
+ // Override those variables if passed in
+ foreach ($vars as $k => $v) {
+ $$k = $v;
+ }
+
+ eval($code);
+ }
+
+ /**
+ * @param Frame $frame
+ */
+ public function render(Frame $frame)
+ {
+ $this->evaluate($frame->get_node()->nodeValue);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Absolute.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Absolute.php
new file mode 100644
index 0000000..2df9a74
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Absolute.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameReflower\Block;
+
+/**
+ * Positions absolutely positioned frames
+ */
+class Absolute extends AbstractPositioner
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ if ($frame->get_reflower() instanceof Block) {
+ $style = $frame->get_style();
+ [$cbx, $cby, $cbw, $cbh] = $frame->get_containing_block();
+
+ // If the `top` value is `auto`, the frame will be repositioned
+ // after its height has been resolved
+ $left = (float) $style->length_in_pt($style->left, $cbw);
+ $top = (float) $style->length_in_pt($style->top, $cbh);
+
+ $frame->set_position($cbx + $left, $cby + $top);
+ } else {
+ // Legacy positioning logic for image and table frames
+ // TODO: Resolve dimensions, margins, and offsets similar to the
+ // block case in the reflowers and use the simplified logic above
+ $style = $frame->get_style();
+ $block_parent = $frame->find_block_parent();
+ $current_line = $block_parent->get_current_line_box();
+
+ list($x, $y, $w, $h) = $frame->get_containing_block();
+ $inflow_x = $block_parent->get_content_box()["x"] + $current_line->left + $current_line->w;
+ $inflow_y = $current_line->y;
+
+ $top = $style->length_in_pt($style->top, $h);
+ $right = $style->length_in_pt($style->right, $w);
+ $bottom = $style->length_in_pt($style->bottom, $h);
+ $left = $style->length_in_pt($style->left, $w);
+
+ list($width, $height) = [$frame->get_margin_width(), $frame->get_margin_height()];
+
+ $orig_width = $style->get_specified("width");
+ $orig_height = $style->get_specified("height");
+
+ /****************************
+ *
+ * Width auto:
+ * ____________| left=auto | left=fixed |
+ * right=auto | A | B |
+ * right=fixed | C | D |
+ *
+ * Width fixed:
+ * ____________| left=auto | left=fixed |
+ * right=auto | E | F |
+ * right=fixed | G | H |
+ *****************************/
+
+ if ($left === "auto") {
+ if ($right === "auto") {
+ // A or E - Keep the frame at the same position
+ $x = $inflow_x;
+ } else {
+ if ($orig_width === "auto") {
+ // C
+ $x += $w - $width - $right;
+ } else {
+ // G
+ $x += $w - $width - $right;
+ }
+ }
+ } else {
+ if ($right === "auto") {
+ // B or F
+ $x += (float)$left;
+ } else {
+ if ($orig_width === "auto") {
+ // D - TODO change width
+ $x += (float)$left;
+ } else {
+ // H - Everything is fixed: left + width win
+ $x += (float)$left;
+ }
+ }
+ }
+
+ // The same vertically
+ if ($top === "auto") {
+ if ($bottom === "auto") {
+ // A or E - Keep the frame at the same position
+ $y = $inflow_y;
+ } else {
+ if ($orig_height === "auto") {
+ // C
+ $y += (float)$h - $height - (float)$bottom;
+ } else {
+ // G
+ $y += (float)$h - $height - (float)$bottom;
+ }
+ }
+ } else {
+ if ($bottom === "auto") {
+ // B or F
+ $y += (float)$top;
+ } else {
+ if ($orig_height === "auto") {
+ // D - TODO change height
+ $y += (float)$top;
+ } else {
+ // H - Everything is fixed: top + height win
+ $y += (float)$top;
+ }
+ }
+ }
+
+ $frame->set_position($x, $y);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/AbstractPositioner.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/AbstractPositioner.php
new file mode 100644
index 0000000..a75c09f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/AbstractPositioner.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+
+/**
+ * Base AbstractPositioner class
+ *
+ * Defines positioner interface
+ *
+ * @package dompdf
+ */
+abstract class AbstractPositioner
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ abstract function position(AbstractFrameDecorator $frame): void;
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ * @param float $offset_x
+ * @param float $offset_y
+ * @param bool $ignore_self
+ */
+ function move(
+ AbstractFrameDecorator $frame,
+ float $offset_x,
+ float $offset_y,
+ bool $ignore_self = false
+ ): void {
+ [$x, $y] = $frame->get_position();
+
+ if (!$ignore_self) {
+ $frame->set_position($x + $offset_x, $y + $offset_y);
+ }
+
+ foreach ($frame->get_children() as $child) {
+ $child->move($offset_x, $offset_y);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Block.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Block.php
new file mode 100644
index 0000000..e6f65ea
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Block.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+
+/**
+ * Positions block frames
+ *
+ * @package dompdf
+ */
+class Block extends AbstractPositioner
+{
+
+ function position(AbstractFrameDecorator $frame): void
+ {
+ $style = $frame->get_style();
+ $cb = $frame->get_containing_block();
+ $p = $frame->find_block_parent();
+
+ if ($p) {
+ $float = $style->float;
+
+ if (!$float || $float === "none") {
+ $p->add_line(true);
+ }
+ $y = $p->get_current_line_box()->y;
+ } else {
+ $y = $cb["y"];
+ }
+
+ $x = $cb["x"];
+
+ $frame->set_position($x, $y);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Fixed.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Fixed.php
new file mode 100644
index 0000000..13eb9e9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Fixed.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameReflower\Block;
+
+/**
+ * Positions fixely positioned frames
+ */
+class Fixed extends Absolute
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ if ($frame->get_reflower() instanceof Block) {
+ parent::position($frame);
+ } else {
+ // Legacy positioning logic for image and table frames
+ // TODO: Resolve dimensions, margins, and offsets similar to the
+ // block case in the reflowers and use the simplified logic above
+ $style = $frame->get_style();
+ $root = $frame->get_root();
+ $initialcb = $root->get_containing_block();
+ $initialcb_style = $root->get_style();
+
+ $p = $frame->find_block_parent();
+ if ($p) {
+ $p->add_line();
+ }
+ // Compute the margins of the @page style
+ $margin_top = (float)$initialcb_style->length_in_pt($initialcb_style->margin_top, $initialcb["h"]);
+ $margin_right = (float)$initialcb_style->length_in_pt($initialcb_style->margin_right, $initialcb["w"]);
+ $margin_bottom = (float)$initialcb_style->length_in_pt($initialcb_style->margin_bottom, $initialcb["h"]);
+ $margin_left = (float)$initialcb_style->length_in_pt($initialcb_style->margin_left, $initialcb["w"]);
+
+ // The needed computed style of the element
+ $height = (float)$style->length_in_pt($style->get_specified("height"), $initialcb["h"]);
+ $width = (float)$style->length_in_pt($style->get_specified("width"), $initialcb["w"]);
+
+ $top = $style->length_in_pt($style->get_specified("top"), $initialcb["h"]);
+ $right = $style->length_in_pt($style->get_specified("right"), $initialcb["w"]);
+ $bottom = $style->length_in_pt($style->get_specified("bottom"), $initialcb["h"]);
+ $left = $style->length_in_pt($style->get_specified("left"), $initialcb["w"]);
+
+ $y = $margin_top;
+ if (isset($top)) {
+ $y = (float)$top + $margin_top;
+ if ($top === "auto") {
+ $y = $margin_top;
+ if (isset($bottom) && $bottom !== "auto") {
+ $y = $initialcb["h"] - $bottom - $margin_bottom;
+ if ($frame->is_auto_height()) {
+ $y -= $height;
+ } else {
+ $y -= $frame->get_margin_height();
+ }
+ }
+ }
+ }
+
+ $x = $margin_left;
+ if (isset($left)) {
+ $x = (float)$left + $margin_left;
+ if ($left === "auto") {
+ $x = $margin_left;
+ if (isset($right) && $right !== "auto") {
+ $x = $initialcb["w"] - $right - $margin_right;
+ if ($frame->is_auto_width()) {
+ $x -= $width;
+ } else {
+ $x -= $frame->get_margin_width();
+ }
+ }
+ }
+ }
+
+ $frame->set_position($x, $y);
+
+ foreach ($frame->get_children() as $child) {
+ $child->set_position($x, $y);
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Inline.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Inline.php
new file mode 100644
index 0000000..c0bdaf2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/Inline.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\Inline as InlineFrameDecorator;
+use Dompdf\Exception;
+use Dompdf\Helpers;
+
+/**
+ * Positions inline frames
+ *
+ * @package dompdf
+ */
+class Inline extends AbstractPositioner
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ * @throws Exception
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ // Find our nearest block level parent and access its lines property
+ $block = $frame->find_block_parent();
+
+ if (!$block) {
+ throw new Exception("No block-level parent found. Not good.");
+ }
+
+ $cb = $frame->get_containing_block();
+ $line = $block->get_current_line_box();
+
+ if (!$frame->is_text_node() && !($frame instanceof InlineFrameDecorator)) {
+ // Atomic inline boxes and replaced inline elements
+ // (inline-block, inline-table, img etc.)
+ $width = $frame->get_margin_width();
+ $available_width = $cb["w"] - $line->left - $line->w - $line->right;
+
+ if (Helpers::lengthGreater($width, $available_width)) {
+ $block->add_line();
+ $line = $block->get_current_line_box();
+ }
+ }
+
+ $frame->set_position($cb["x"] + $line->w, $line->y);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/ListBullet.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/ListBullet.php
new file mode 100644
index 0000000..081d594
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/ListBullet.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator;
+
+/**
+ * Positions list bullets
+ *
+ * @package dompdf
+ */
+class ListBullet extends AbstractPositioner
+{
+ /**
+ * @param ListBulletFrameDecorator $frame
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ // List markers are positioned to the left of the border edge of their
+ // parent element (FIXME: right for RTL)
+ $parent = $frame->get_parent();
+ $style = $parent->get_style();
+ $cbw = $parent->get_containing_block("w");
+ $margin_left = (float) $style->length_in_pt($style->margin_left, $cbw);
+ $border_edge = $parent->get_position("x") + $margin_left;
+
+ // This includes the marker indentation
+ $x = $border_edge - $frame->get_margin_width();
+
+ // The marker is later vertically aligned with the corresponding line
+ // box and its vertical position is fine-tuned in the renderer
+ $p = $frame->find_block_parent();
+ $y = $p->get_current_line_box()->y;
+
+ $frame->set_position($x, $y);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/NullPositioner.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/NullPositioner.php
new file mode 100644
index 0000000..6ad425c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/NullPositioner.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+
+/**
+ * Dummy positioner
+ *
+ * @package dompdf
+ */
+class NullPositioner extends AbstractPositioner
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ return;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableCell.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableCell.php
new file mode 100644
index 0000000..1a6ac62
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableCell.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+use Dompdf\FrameDecorator\Table;
+
+/**
+ * Positions table cells
+ *
+ * @package dompdf
+ */
+class TableCell extends AbstractPositioner
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ $table = Table::find_parent_table($frame);
+ $cellmap = $table->get_cellmap();
+ $frame->set_position($cellmap->get_frame_position($frame));
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableRow.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableRow.php
new file mode 100644
index 0000000..79c0fcf
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Positioner/TableRow.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Positioner;
+
+use Dompdf\FrameDecorator\AbstractFrameDecorator;
+
+/**
+ * Positions table rows
+ *
+ * @package dompdf
+ */
+class TableRow extends AbstractPositioner
+{
+
+ /**
+ * @param AbstractFrameDecorator $frame
+ */
+ function position(AbstractFrameDecorator $frame): void
+ {
+ $cb = $frame->get_containing_block();
+ $p = $frame->get_prev_sibling();
+
+ if ($p) {
+ $y = $p->get_position("y") + $p->get_margin_height();
+ } else {
+ $y = $cb["y"];
+ }
+ $frame->set_position($cb["x"], $y);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer.php
new file mode 100644
index 0000000..e3cacc1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer.php
@@ -0,0 +1,291 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf;
+
+use Dompdf\Renderer\AbstractRenderer;
+use Dompdf\Renderer\Block;
+use Dompdf\Renderer\Image;
+use Dompdf\Renderer\ListBullet;
+use Dompdf\Renderer\TableCell;
+use Dompdf\Renderer\TableRowGroup;
+use Dompdf\Renderer\Text;
+
+/**
+ * Concrete renderer
+ *
+ * Instantiates several specific renderers in order to render any given frame.
+ *
+ * @package dompdf
+ */
+class Renderer extends AbstractRenderer
+{
+
+ /**
+ * Array of renderers for specific frame types
+ *
+ * @var AbstractRenderer[]
+ */
+ protected $_renderers;
+
+ /**
+ * Cache of the callbacks array
+ *
+ * @var array
+ */
+ private $_callbacks;
+
+ /**
+ * Advance the canvas to the next page
+ */
+ function new_page()
+ {
+ $this->_canvas->new_page();
+ }
+
+ /**
+ * Render frames recursively
+ *
+ * @param Frame $frame the frame to render
+ */
+ public function render(Frame $frame)
+ {
+ global $_dompdf_debug;
+
+ $this->_check_callbacks("begin_frame", $frame);
+
+ if ($_dompdf_debug) {
+ echo $frame;
+ flush();
+ }
+
+ $style = $frame->get_style();
+
+ if (in_array($style->visibility, ["hidden", "collapse"], true)) {
+ return;
+ }
+
+ $display = $style->display;
+ $transformList = $style->transform;
+ $hasTransform = $transformList !== [];
+
+ // Starts the CSS transformation
+ if ($hasTransform) {
+ $this->_canvas->save();
+ list($x, $y) = $frame->get_padding_box();
+ $origin = $style->transform_origin;
+
+ foreach ($transformList as $transform) {
+ list($function, $values) = $transform;
+ if ($function === "matrix") {
+ $function = "transform";
+ }
+
+ $values = array_map("floatval", $values);
+ $values[] = $x + (float)$style->length_in_pt($origin[0], (float)$style->length_in_pt($style->width));
+ $values[] = $y + (float)$style->length_in_pt($origin[1], (float)$style->length_in_pt($style->height));
+
+ call_user_func_array([$this->_canvas, $function], $values);
+ }
+ }
+
+ switch ($display) {
+
+ case "block":
+ case "list-item":
+ case "inline-block":
+ case "table":
+ case "inline-table":
+ $this->_render_frame("block", $frame);
+ break;
+
+ case "inline":
+ if ($frame->is_text_node()) {
+ $this->_render_frame("text", $frame);
+ } else {
+ $this->_render_frame("inline", $frame);
+ }
+ break;
+
+ case "table-cell":
+ $this->_render_frame("table-cell", $frame);
+ break;
+
+ case "table-row-group":
+ case "table-header-group":
+ case "table-footer-group":
+ $this->_render_frame("table-row-group", $frame);
+ break;
+
+ case "-dompdf-list-bullet":
+ $this->_render_frame("list-bullet", $frame);
+ break;
+
+ case "-dompdf-image":
+ $this->_render_frame("image", $frame);
+ break;
+
+ case "none":
+ $node = $frame->get_node();
+
+ if ($node->nodeName === "script") {
+ if ($node->getAttribute("type") === "text/php" ||
+ $node->getAttribute("language") === "php"
+ ) {
+ // Evaluate embedded php scripts
+ $this->_render_frame("php", $frame);
+ } elseif ($node->getAttribute("type") === "text/javascript" ||
+ $node->getAttribute("language") === "javascript"
+ ) {
+ // Insert JavaScript
+ $this->_render_frame("javascript", $frame);
+ }
+ }
+
+ // Don't render children, so skip to next iter
+ return;
+
+ default:
+ break;
+
+ }
+
+ // Starts the overflow: hidden box
+ if ($style->overflow === "hidden") {
+ $padding_box = $frame->get_padding_box();
+ [$x, $y, $w, $h] = $padding_box;
+ $style = $frame->get_style();
+
+ if ($style->has_border_radius()) {
+ $border_box = $frame->get_border_box();
+ [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $padding_box);
+ $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
+ } else {
+ $this->_canvas->clipping_rectangle($x, $y, $w, $h);
+ }
+ }
+
+ $stack = [];
+
+ foreach ($frame->get_children() as $child) {
+ // < 0 : negative z-index
+ // = 0 : no z-index, no stacking context
+ // = 1 : stacking context without z-index
+ // > 1 : z-index
+ $child_style = $child->get_style();
+ $child_z_index = $child_style->z_index;
+ $z_index = 0;
+
+ if ($child_z_index !== "auto") {
+ $z_index = $child_z_index + 1;
+ } elseif ($child_style->float !== "none" || $child->is_positioned()) {
+ $z_index = 1;
+ }
+
+ $stack[$z_index][] = $child;
+ }
+
+ ksort($stack);
+
+ foreach ($stack as $by_index) {
+ foreach ($by_index as $child) {
+ $this->render($child);
+ }
+ }
+
+ // Ends the overflow: hidden box
+ if ($style->overflow === "hidden") {
+ $this->_canvas->clipping_end();
+ }
+
+ if ($hasTransform) {
+ $this->_canvas->restore();
+ }
+
+ // Check for end frame callback
+ $this->_check_callbacks("end_frame", $frame);
+ }
+
+ /**
+ * Check for callbacks that need to be performed when a given event
+ * gets triggered on a frame
+ *
+ * @param string $event The type of event
+ * @param Frame $frame The frame that event is triggered on
+ */
+ protected function _check_callbacks(string $event, Frame $frame): void
+ {
+ if (!isset($this->_callbacks)) {
+ $this->_callbacks = $this->_dompdf->getCallbacks();
+ }
+
+ if (isset($this->_callbacks[$event])) {
+ $fs = $this->_callbacks[$event];
+ $canvas = $this->_canvas;
+ $fontMetrics = $this->_dompdf->getFontMetrics();
+
+ foreach ($fs as $f) {
+ $f($frame, $canvas, $fontMetrics);
+ }
+ }
+ }
+
+ /**
+ * Render a single frame
+ *
+ * Creates Renderer objects on demand
+ *
+ * @param string $type type of renderer to use
+ * @param Frame $frame the frame to render
+ */
+ protected function _render_frame($type, $frame)
+ {
+
+ if (!isset($this->_renderers[$type])) {
+
+ switch ($type) {
+ case "block":
+ $this->_renderers[$type] = new Block($this->_dompdf);
+ break;
+
+ case "inline":
+ $this->_renderers[$type] = new Renderer\Inline($this->_dompdf);
+ break;
+
+ case "text":
+ $this->_renderers[$type] = new Text($this->_dompdf);
+ break;
+
+ case "image":
+ $this->_renderers[$type] = new Image($this->_dompdf);
+ break;
+
+ case "table-cell":
+ $this->_renderers[$type] = new TableCell($this->_dompdf);
+ break;
+
+ case "table-row-group":
+ $this->_renderers[$type] = new TableRowGroup($this->_dompdf);
+ break;
+
+ case "list-bullet":
+ $this->_renderers[$type] = new ListBullet($this->_dompdf);
+ break;
+
+ case "php":
+ $this->_renderers[$type] = new PhpEvaluator($this->_canvas);
+ break;
+
+ case "javascript":
+ $this->_renderers[$type] = new JavascriptEmbedder($this->_dompdf);
+ break;
+
+ }
+ }
+
+ $this->_renderers[$type]->render($frame);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/AbstractRenderer.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/AbstractRenderer.php
new file mode 100644
index 0000000..8b01ef8
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/AbstractRenderer.php
@@ -0,0 +1,1244 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Adapter\CPDF;
+use Dompdf\Css\Color;
+use Dompdf\Css\Style;
+use Dompdf\Dompdf;
+use Dompdf\Helpers;
+use Dompdf\Frame;
+use Dompdf\Image\Cache;
+
+/**
+ * Base renderer class
+ *
+ * @package dompdf
+ */
+abstract class AbstractRenderer
+{
+
+ /**
+ * Rendering backend
+ *
+ * @var \Dompdf\Canvas
+ */
+ protected $_canvas;
+
+ /**
+ * Current dompdf instance
+ *
+ * @var Dompdf
+ */
+ protected $_dompdf;
+
+ /**
+ * Class constructor
+ *
+ * @param Dompdf $dompdf The current dompdf instance
+ */
+ function __construct(Dompdf $dompdf)
+ {
+ $this->_dompdf = $dompdf;
+ $this->_canvas = $dompdf->getCanvas();
+ }
+
+ /**
+ * Render a frame.
+ *
+ * Specialized in child classes
+ *
+ * @param Frame $frame The frame to render
+ */
+ abstract function render(Frame $frame);
+
+ /**
+ * @param Frame $frame
+ * @param float[] $border_box
+ */
+ protected function _render_background(Frame $frame, array $border_box): void
+ {
+ $style = $frame->get_style();
+ $color = $style->background_color;
+ $image = $style->background_image;
+ [$x, $y, $w, $h] = $border_box;
+
+ if ($color === "transparent" && $image === "none") {
+ return;
+ }
+
+ if ($style->has_border_radius()) {
+ [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box);
+ $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
+ }
+
+ if ($color !== "transparent") {
+ $this->_canvas->filled_rectangle($x, $y, $w, $h, $color);
+ }
+
+ if ($image !== "none") {
+ $this->_background_image($image, $x, $y, $w, $h, $style);
+ }
+
+ if ($style->has_border_radius()) {
+ $this->_canvas->clipping_end();
+ }
+ }
+
+ /**
+ * @param Frame $frame
+ * @param float[] $border_box
+ * @param string $corner_style
+ */
+ protected function _render_border(Frame $frame, array $border_box, string $corner_style = "bevel"): void
+ {
+ $style = $frame->get_style();
+ $bp = $style->get_border_properties();
+ [$x, $y, $w, $h] = $border_box;
+ [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box);
+
+ // Short-cut: If all the borders are "solid" with the same color and
+ // style, and no radius, we'd better draw a rectangle
+ if ($bp["top"]["style"] === "solid" &&
+ $bp["top"] === $bp["right"] &&
+ $bp["right"] === $bp["bottom"] &&
+ $bp["bottom"] === $bp["left"] &&
+ !$style->has_border_radius()
+ ) {
+ $props = $bp["top"];
+ if ($props["color"] === "transparent" || $props["width"] <= 0) {
+ return;
+ }
+
+ $width = (float)$style->length_in_pt($props["width"]);
+ $this->_canvas->rectangle($x + $width / 2, $y + $width / 2, $w - $width, $h - $width, $props["color"], $width);
+ return;
+ }
+
+ // Do it the long way
+ $widths = [
+ (float)$style->length_in_pt($bp["top"]["width"]),
+ (float)$style->length_in_pt($bp["right"]["width"]),
+ (float)$style->length_in_pt($bp["bottom"]["width"]),
+ (float)$style->length_in_pt($bp["left"]["width"])
+ ];
+
+ foreach ($bp as $side => $props) {
+ if ($props["style"] === "none" ||
+ $props["style"] === "hidden" ||
+ $props["color"] === "transparent" ||
+ $props["width"] <= 0
+ ) {
+ continue;
+ }
+
+ [$x, $y, $w, $h] = $border_box;
+ $method = "_border_" . $props["style"];
+
+ switch ($side) {
+ case "top":
+ $length = $w;
+ $r1 = $tl;
+ $r2 = $tr;
+ break;
+
+ case "bottom":
+ $length = $w;
+ $y += $h;
+ $r1 = $bl;
+ $r2 = $br;
+ break;
+
+ case "left":
+ $length = $h;
+ $r1 = $tl;
+ $r2 = $bl;
+ break;
+
+ case "right":
+ $length = $h;
+ $x += $w;
+ $r1 = $tr;
+ $r2 = $br;
+ break;
+
+ default:
+ break;
+ }
+
+ // draw rounded corners
+ $this->$method($x, $y, $length, $props["color"], $widths, $side, $corner_style, $r1, $r2);
+ }
+ }
+
+ /**
+ * @param Frame $frame
+ * @param float[] $border_box
+ * @param string $corner_style
+ */
+ protected function _render_outline(Frame $frame, array $border_box, string $corner_style = "bevel"): void
+ {
+ $style = $frame->get_style();
+
+ $width = $style->outline_width;
+ $outline_style = $style->outline_style;
+ $color = $style->outline_color;
+
+ if ($outline_style === "none" || $color === "transparent" || $width <= 0) {
+ return;
+ }
+
+ $offset = $style->outline_offset;
+
+ [$x, $y, $w, $h] = $border_box;
+ $d = $width + $offset;
+ $outline_box = [$x - $d, $y - $d, $w + $d * 2, $h + $d * 2];
+ [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $outline_box);
+
+ $x -= $offset;
+ $y -= $offset;
+ $w += $offset * 2;
+ $h += $offset * 2;
+
+ // For a simple outline, we can draw a rectangle
+ if ($outline_style === "solid" && !$style->has_border_radius()) {
+ $x -= $width / 2;
+ $y -= $width / 2;
+ $w += $width;
+ $h += $width;
+
+ $this->_canvas->rectangle($x, $y, $w, $h, $color, $width);
+ return;
+ }
+
+ $x -= $width;
+ $y -= $width;
+ $w += $width * 2;
+ $h += $width * 2;
+
+ $method = "_border_" . $outline_style;
+ $widths = array_fill(0, 4, $width);
+ $sides = ["top", "right", "left", "bottom"];
+
+ foreach ($sides as $side) {
+ switch ($side) {
+ case "top":
+ $length = $w;
+ $side_x = $x;
+ $side_y = $y;
+ $r1 = $tl;
+ $r2 = $tr;
+ break;
+
+ case "bottom":
+ $length = $w;
+ $side_x = $x;
+ $side_y = $y + $h;
+ $r1 = $bl;
+ $r2 = $br;
+ break;
+
+ case "left":
+ $length = $h;
+ $side_x = $x;
+ $side_y = $y;
+ $r1 = $tl;
+ $r2 = $bl;
+ break;
+
+ case "right":
+ $length = $h;
+ $side_x = $x + $w;
+ $side_y = $y;
+ $r1 = $tr;
+ $r2 = $br;
+ break;
+
+ default:
+ break;
+ }
+
+ $this->$method($side_x, $side_y, $length, $color, $widths, $side, $corner_style, $r1, $r2);
+ }
+ }
+
+ /**
+ * Render a background image over a rectangular area
+ *
+ * @param string $url The background image to load
+ * @param float $x The left edge of the rectangular area
+ * @param float $y The top edge of the rectangular area
+ * @param float $width The width of the rectangular area
+ * @param float $height The height of the rectangular area
+ * @param Style $style The associated Style object
+ *
+ * @throws \Exception
+ */
+ protected function _background_image($url, $x, $y, $width, $height, $style)
+ {
+ if (!function_exists("imagecreatetruecolor")) {
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
+ }
+
+ $sheet = $style->get_stylesheet();
+
+ // Skip degenerate cases
+ if ($width == 0 || $height == 0) {
+ return;
+ }
+
+ $box_width = $width;
+ $box_height = $height;
+
+ //debugpng
+ if ($this->_dompdf->getOptions()->getDebugPng()) {
+ print '[_background_image ' . $url . ']';
+ }
+
+ list($img, $type, /*$msg*/) = Cache::resolve_url(
+ $url,
+ $sheet->get_protocol(),
+ $sheet->get_host(),
+ $sheet->get_base_path(),
+ $this->_dompdf->getOptions()
+ );
+
+ // Bail if the image is no good
+ if (Cache::is_broken($img)) {
+ return;
+ }
+
+ //Try to optimize away reading and composing of same background multiple times
+ //Postponing read with imagecreatefrom ...()
+ //final composition parameters and name not known yet
+ //Therefore read dimension directly from file, instead of creating gd object first.
+ //$img_w = imagesx($src); $img_h = imagesy($src);
+
+ list($img_w, $img_h) = Helpers::dompdf_getimagesize($img, $this->_dompdf->getHttpContext());
+ if ($img_w == 0 || $img_h == 0) {
+ return;
+ }
+
+ // save for later check if file needs to be resized.
+ $org_img_w = $img_w;
+ $org_img_h = $img_h;
+
+ $repeat = $style->background_repeat;
+ $dpi = $this->_dompdf->getOptions()->getDpi();
+
+ //Increase background resolution and dependent box size according to image resolution to be placed in
+ //Then image can be copied in without resize
+ $bg_width = round((float)($width * $dpi) / 72);
+ $bg_height = round((float)($height * $dpi) / 72);
+
+ list($img_w, $img_h) = $this->_resize_background_image(
+ $img_w,
+ $img_h,
+ $bg_width,
+ $bg_height,
+ $style->background_size,
+ $dpi
+ );
+ //Need %bg_x, $bg_y as background pos, where img starts, converted to pixel
+
+ list($bg_x, $bg_y) = $style->background_position;
+
+ if (Helpers::is_percent($bg_x)) {
+ // The point $bg_x % from the left edge of the image is placed
+ // $bg_x % from the left edge of the background rectangle
+ $p = ((float)$bg_x) / 100.0;
+ $x1 = $p * $img_w;
+ $x2 = $p * $bg_width;
+
+ $bg_x = $x2 - $x1;
+ } else {
+ $bg_x = (float)($style->length_in_pt($bg_x) * $dpi) / 72;
+ }
+
+ $bg_x = round($bg_x + (float)$style->length_in_pt($style->border_left_width) * $dpi / 72);
+
+ if (Helpers::is_percent($bg_y)) {
+ // The point $bg_y % from the left edge of the image is placed
+ // $bg_y % from the left edge of the background rectangle
+ $p = ((float)$bg_y) / 100.0;
+ $y1 = $p * $img_h;
+ $y2 = $p * $bg_height;
+
+ $bg_y = $y2 - $y1;
+ } else {
+ $bg_y = (float)($style->length_in_pt($bg_y) * $dpi) / 72;
+ }
+
+ $bg_y = round($bg_y + (float)$style->length_in_pt($style->border_top_width) * $dpi / 72);
+
+ //clip background to the image area on partial repeat. Nothing to do if img off area
+ //On repeat, normalize start position to the tile at immediate left/top or 0/0 of area
+ //On no repeat with positive offset: move size/start to have offset==0
+ //Handle x/y Dimensions separately
+
+ if ($repeat !== "repeat" && $repeat !== "repeat-x") {
+ //No repeat x
+ if ($bg_x < 0) {
+ $bg_width = $img_w + $bg_x;
+ } else {
+ $x += ($bg_x * 72) / $dpi;
+ $bg_width = $bg_width - $bg_x;
+ if ($bg_width > $img_w) {
+ $bg_width = $img_w;
+ }
+ $bg_x = 0;
+ }
+
+ if ($bg_width <= 0) {
+ return;
+ }
+
+ $width = (float)($bg_width * 72) / $dpi;
+ } else {
+ //repeat x
+ if ($bg_x < 0) {
+ $bg_x = -((-$bg_x) % $img_w);
+ } else {
+ $bg_x = $bg_x % $img_w;
+ if ($bg_x > 0) {
+ $bg_x -= $img_w;
+ }
+ }
+ }
+
+ if ($repeat !== "repeat" && $repeat !== "repeat-y") {
+ //no repeat y
+ if ($bg_y < 0) {
+ $bg_height = $img_h + $bg_y;
+ } else {
+ $y += ($bg_y * 72) / $dpi;
+ $bg_height = $bg_height - $bg_y;
+ if ($bg_height > $img_h) {
+ $bg_height = $img_h;
+ }
+ $bg_y = 0;
+ }
+ if ($bg_height <= 0) {
+ return;
+ }
+ $height = (float)($bg_height * 72) / $dpi;
+ } else {
+ //repeat y
+ if ($bg_y < 0) {
+ $bg_y = -((-$bg_y) % $img_h);
+ } else {
+ $bg_y = $bg_y % $img_h;
+ if ($bg_y > 0) {
+ $bg_y -= $img_h;
+ }
+ }
+ }
+
+ //Optimization, if repeat has no effect
+ if ($repeat === "repeat" && $bg_y <= 0 && $img_h + $bg_y >= $bg_height) {
+ $repeat = "repeat-x";
+ }
+
+ if ($repeat === "repeat" && $bg_x <= 0 && $img_w + $bg_x >= $bg_width) {
+ $repeat = "repeat-y";
+ }
+
+ if (($repeat === "repeat-x" && $bg_x <= 0 && $img_w + $bg_x >= $bg_width) ||
+ ($repeat === "repeat-y" && $bg_y <= 0 && $img_h + $bg_y >= $bg_height)
+ ) {
+ $repeat = "no-repeat";
+ }
+
+ // Avoid rendering identical background-image variants multiple times
+ // This is not dependent of background color of box! .'_'.(is_array($bg_color) ? $bg_color["hex"] : $bg_color)
+ // Note: Here, bg_* are the start values, not end values after going through the tile loops!
+
+ $key = implode("_", [$bg_width, $bg_height, $img_w, $img_h, $bg_x, $bg_y, $repeat]);
+ // FIXME: This will fail when a file with that exact name exists in the
+ // same directory, included in the document as regular image
+ $cpdfKey = $img . "_" . $key;
+ $tmpFile = Cache::getTempImage($img, $key);
+ $cached = ($this->_canvas instanceof CPDF && $this->_canvas->get_cpdf()->image_iscached($cpdfKey))
+ || ($tmpFile !== null && file_exists($tmpFile));
+
+ if (!$cached) {
+ // img: image url string
+ // img_w, img_h: original image size in px
+ // width, height: box size in pt
+ // bg_width, bg_height: box size in px
+ // x, y: left/top edge of box on page in pt
+ // start_x, start_y: placement of image relative to pattern
+ // $repeat: repeat mode
+ // $bg: GD object of result image
+ // $src: GD object of original image
+
+ // Create a new image to fit over the background rectangle
+ $bg = imagecreatetruecolor($bg_width, $bg_height);
+ $cpdfFromGd = true;
+
+ switch (strtolower($type)) {
+ case "png":
+ $cpdfFromGd = false;
+ imagesavealpha($bg, true);
+ imagealphablending($bg, false);
+ $src = @imagecreatefrompng($img);
+ break;
+
+ case "jpeg":
+ $src = @imagecreatefromjpeg($img);
+ break;
+
+ case "webp":
+ $src = @imagecreatefromwebp($img);
+ break;
+
+ case "gif":
+ $src = @imagecreatefromgif($img);
+ break;
+
+ case "bmp":
+ $src = @Helpers::imagecreatefrombmp($img);
+ break;
+
+ default:
+ return; // Unsupported image type
+ }
+
+ if ($src == null) {
+ return;
+ }
+
+ if ($img_w != $org_img_w || $img_h != $org_img_h) {
+ $newSrc = imagescale($src, $img_w, $img_h);
+ imagedestroy($src);
+ $src = $newSrc;
+ }
+
+ if ($src == null) {
+ return;
+ }
+
+ //Background color if box is not relevant here
+ //Non transparent image: box clipped to real size. Background non relevant.
+ //Transparent image: The image controls the transparency and lets shine through whatever background.
+ //However on transparent image preset the composed image with the transparency color,
+ //to keep the transparency when copying over the non transparent parts of the tiles.
+ $ti = imagecolortransparent($src);
+ $palletsize = imagecolorstotal($src);
+
+ if ($ti >= 0 && $ti < $palletsize) {
+ $tc = imagecolorsforindex($src, $ti);
+ $ti = imagecolorallocate($bg, $tc['red'], $tc['green'], $tc['blue']);
+ imagefill($bg, 0, 0, $ti);
+ imagecolortransparent($bg, $ti);
+ }
+
+ //This has only an effect for the non repeatable dimension.
+ //compute start of src and dest coordinates of the single copy
+ if ($bg_x < 0) {
+ $dst_x = 0;
+ $src_x = -$bg_x;
+ } else {
+ $src_x = 0;
+ $dst_x = $bg_x;
+ }
+
+ if ($bg_y < 0) {
+ $dst_y = 0;
+ $src_y = -$bg_y;
+ } else {
+ $src_y = 0;
+ $dst_y = $bg_y;
+ }
+
+ //For historical reasons exchange meanings of variables:
+ //start_* will be the start values, while bg_* will be the temporary start values in the loops
+ $start_x = $bg_x;
+ $start_y = $bg_y;
+
+ // Copy regions from the source image to the background
+ if ($repeat === "no-repeat") {
+ // Simply place the image on the background
+ imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $img_h);
+
+ } elseif ($repeat === "repeat-x") {
+ for ($bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w) {
+ if ($bg_x < 0) {
+ $dst_x = 0;
+ $src_x = -$bg_x;
+ $w = $img_w + $bg_x;
+ } else {
+ $dst_x = $bg_x;
+ $src_x = 0;
+ $w = $img_w;
+ }
+ imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $img_h);
+ }
+ } elseif ($repeat === "repeat-y") {
+
+ for ($bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h) {
+ if ($bg_y < 0) {
+ $dst_y = 0;
+ $src_y = -$bg_y;
+ $h = $img_h + $bg_y;
+ } else {
+ $dst_y = $bg_y;
+ $src_y = 0;
+ $h = $img_h;
+ }
+ imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $h);
+ }
+ } elseif ($repeat === "repeat") {
+ for ($bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h) {
+ for ($bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w) {
+ if ($bg_x < 0) {
+ $dst_x = 0;
+ $src_x = -$bg_x;
+ $w = $img_w + $bg_x;
+ } else {
+ $dst_x = $bg_x;
+ $src_x = 0;
+ $w = $img_w;
+ }
+
+ if ($bg_y < 0) {
+ $dst_y = 0;
+ $src_y = -$bg_y;
+ $h = $img_h + $bg_y;
+ } else {
+ $dst_y = $bg_y;
+ $src_y = 0;
+ $h = $img_h;
+ }
+ imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $h);
+ }
+ }
+ } else {
+ print 'Unknown repeat!';
+ }
+
+ imagedestroy($src);
+
+ if ($cpdfFromGd && $this->_canvas instanceof CPDF) {
+ // Skip writing temp file as the GD object is added directly
+ } else {
+ $tmpDir = $this->_dompdf->getOptions()->getTempDir();
+ $tmpName = @tempnam($tmpDir, "bg_dompdf_img_");
+ @unlink($tmpName);
+ $tmpFile = "$tmpName.png";
+
+ imagepng($bg, $tmpFile);
+ imagedestroy($bg);
+
+ Cache::addTempImage($img, $tmpFile, $key);
+ }
+ } else {
+ $bg = null;
+ $cpdfFromGd = $tmpFile === null;
+ }
+
+ if ($this->_dompdf->getOptions()->getDebugPng()) {
+ print '[_background_image ' . $tmpFile . ']';
+ }
+
+ $this->_canvas->clipping_rectangle($x, $y, $box_width, $box_height);
+
+ // When using cpdf and optimization to direct png creation from gd object is available,
+ // don't create temp file, but place gd object directly into the pdf
+ if ($cpdfFromGd && $this->_canvas instanceof CPDF) {
+ // Note: CPDF_Adapter image converts y position
+ $this->_canvas->get_cpdf()->addImagePng($bg, $cpdfKey, $x, $this->_canvas->get_height() - $y - $height, $width, $height);
+
+ if (isset($bg)) {
+ imagedestroy($bg);
+ }
+ } else {
+ $this->_canvas->image($tmpFile, $x, $y, $width, $height);
+ }
+
+ $this->_canvas->clipping_end();
+ }
+
+ // Border rendering functions
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_dotted($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dotted", $r1, $r2);
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_dashed($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dashed", $r1, $r2);
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_solid($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "solid", $r1, $r2);
+ }
+
+ /**
+ * @param string $side
+ * @param float $ratio
+ * @param float $top
+ * @param float $right
+ * @param float $bottom
+ * @param float $left
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _apply_ratio($side, $ratio, $top, $right, $bottom, $left, &$x, &$y, &$length, &$r1, &$r2)
+ {
+ switch ($side) {
+ case "top":
+ $r1 -= $left * $ratio;
+ $r2 -= $right * $ratio;
+ $x += $left * $ratio;
+ $y += $top * $ratio;
+ $length -= $left * $ratio + $right * $ratio;
+ break;
+
+ case "bottom":
+ $r1 -= $right * $ratio;
+ $r2 -= $left * $ratio;
+ $x += $left * $ratio;
+ $y -= $bottom * $ratio;
+ $length -= $left * $ratio + $right * $ratio;
+ break;
+
+ case "left":
+ $r1 -= $top * $ratio;
+ $r2 -= $bottom * $ratio;
+ $x += $left * $ratio;
+ $y += $top * $ratio;
+ $length -= $top * $ratio + $bottom * $ratio;
+ break;
+
+ case "right":
+ $r1 -= $bottom * $ratio;
+ $r2 -= $top * $ratio;
+ $x -= $right * $ratio;
+ $y += $top * $ratio;
+ $length -= $top * $ratio + $bottom * $ratio;
+ break;
+
+ default:
+ return;
+ }
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_double($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ list($top, $right, $bottom, $left) = $widths;
+
+ $third_widths = [$top / 3, $right / 3, $bottom / 3, $left / 3];
+
+ // draw the outer border
+ $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2);
+
+ $this->_apply_ratio($side, 2 / 3, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
+
+ $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2);
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_groove($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ list($top, $right, $bottom, $left) = $widths;
+
+ $half_widths = [$top / 2, $right / 2, $bottom / 2, $left / 2];
+
+ $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
+
+ $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
+
+ $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_ridge($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ list($top, $right, $bottom, $left) = $widths;
+
+ $half_widths = [$top / 2, $right / 2, $bottom / 2, $left / 2];
+
+ $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
+
+ $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
+
+ $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
+ }
+
+ /**
+ * @param $c
+ * @return mixed
+ */
+ protected function _tint($c)
+ {
+ if (!is_numeric($c)) {
+ return $c;
+ }
+
+ return min(1, $c + 0.16);
+ }
+
+ /**
+ * @param $c
+ * @return mixed
+ */
+ protected function _shade($c)
+ {
+ if (!is_numeric($c)) {
+ return $c;
+ }
+
+ return max(0, $c - 0.33);
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_inset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ switch ($side) {
+ case "top":
+ case "left":
+ $shade = array_map([$this, "_shade"], $color);
+ $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2);
+ break;
+
+ case "bottom":
+ case "right":
+ $tint = array_map([$this, "_tint"], $color);
+ $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2);
+ break;
+
+ default:
+ return;
+ }
+ }
+
+ /**
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_outset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0)
+ {
+ switch ($side) {
+ case "top":
+ case "left":
+ $tint = array_map([$this, "_tint"], $color);
+ $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2);
+ break;
+
+ case "bottom":
+ case "right":
+ $shade = array_map([$this, "_shade"], $color);
+ $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2);
+ break;
+
+ default:
+ return;
+ }
+ }
+
+ /**
+ * Get the dash pattern and cap style for the given border style, width, and
+ * line length.
+ *
+ * The base pattern is adjusted so that it fits the given line length
+ * symmetrically.
+ *
+ * @param string $style
+ * @param float $width
+ * @param float $length
+ *
+ * @return array
+ */
+ protected function dashPattern(string $style, float $width, float $length): array
+ {
+ if ($style === "dashed") {
+ $w = 3 * $width;
+
+ if ($length < $w) {
+ $s = $w;
+ } else {
+ // Scale dashes and gaps
+ $r = round($length / $w);
+ $r = $r % 2 === 0 ? $r + 1 : $r;
+ $s = $length / $r;
+ }
+
+ return [[$s], "butt"];
+ }
+
+ if ($style === "dotted") {
+ // Draw circles along the line
+ // Round caps extend outwards by half line width, so a zero dash
+ // width results in a circle
+ $gap = $width <= 1 ? 2 : 1;
+ $w = ($gap + 1) * $width;
+
+ if ($length < $w) {
+ $s = $w;
+ } else {
+ // Only scale gaps
+ $l = $length - $width;
+ $r = max(round($l / $w), 1);
+ $s = $l / $r;
+ }
+
+ return [[0, $s], "round"];
+ }
+
+ return [[], "butt"];
+ }
+
+ /**
+ * Draws a solid, dotted, or dashed line, observing the border radius
+ *
+ * @param float $x
+ * @param float $y
+ * @param float $length
+ * @param array $color
+ * @param float[] $widths
+ * @param string $side
+ * @param string $corner_style
+ * @param string $pattern_name
+ * @param float $r1
+ * @param float $r2
+ */
+ protected function _border_line($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $pattern_name = "none", $r1 = 0, $r2 = 0)
+ {
+ /** used by $$side */
+ [$top, $right, $bottom, $left] = $widths;
+ $width = $$side;
+
+ // No need to clip corners if border radius is large enough
+ $cornerClip = $corner_style === "bevel" && ($r1 < $width || $r2 < $width);
+ $lineLength = $length - $r1 - $r2;
+ [$pattern, $cap] = $this->dashPattern($pattern_name, $width, $lineLength);
+
+ // Determine arc border radius for corner arcs
+ $halfWidth = $width / 2;
+ $ar1 = max($r1 - $halfWidth, 0);
+ $ar2 = max($r2 - $halfWidth, 0);
+
+ // Small angle adjustments to prevent the background from shining through
+ $adj1 = $ar1 / 80;
+ $adj2 = $ar2 / 80;
+
+ // Adjust line width and corner angles to account for the fact that
+ // round caps extend outwards. The line is actually only shifted below,
+ // not shortened, as otherwise the end dash (circle) will vanish
+ // occasionally
+ $dl = $cap === "round" ? $halfWidth : 0;
+
+ if ($cap === "round" && $ar1 > 0) {
+ $adj1 -= rad2deg(asin($halfWidth / $ar1));
+ }
+ if ($cap === "round" && $ar2 > 0) {
+ $adj2 -= rad2deg(asin($halfWidth / $ar2));
+ }
+
+ switch ($side) {
+ case "top":
+ if ($cornerClip) {
+ $points = [
+ $x, $y,
+ $x, $y - 1, // Extend outwards to avoid gaps
+ $x + $length, $y - 1, // Extend outwards to avoid gaps
+ $x + $length, $y,
+ $x + $length - max($right, $r2), $y + max($width, $r2),
+ $x + max($left, $r1), $y + max($width, $r1)
+ ];
+ $this->_canvas->clipping_polygon($points);
+ }
+
+ $y += $halfWidth;
+
+ if ($ar1 > 0 && $adj1 > -22.5) {
+ $this->_canvas->arc($x + $r1, $y + $ar1, $ar1, $ar1, 90 - $adj1, 135 + $adj1, $color, $width, $pattern, $cap);
+ }
+
+ if ($lineLength > 0) {
+ $this->_canvas->line($x + $dl + $r1, $y, $x + $dl + $length - $r2, $y, $color, $width, $pattern, $cap);
+ }
+
+ if ($ar2 > 0 && $adj2 > -22.5) {
+ $this->_canvas->arc($x + $length - $r2, $y + $ar2, $ar2, $ar2, 45 - $adj2, 90 + $adj2, $color, $width, $pattern, $cap);
+ }
+ break;
+
+ case "bottom":
+ if ($cornerClip) {
+ $points = [
+ $x, $y,
+ $x, $y + 1, // Extend outwards to avoid gaps
+ $x + $length, $y + 1, // Extend outwards to avoid gaps
+ $x + $length, $y,
+ $x + $length - max($right, $r2), $y - max($width, $r2),
+ $x + max($left, $r1), $y - max($width, $r1)
+ ];
+ $this->_canvas->clipping_polygon($points);
+ }
+
+ $y -= $halfWidth;
+
+ if ($ar1 > 0 && $adj1 > -22.5) {
+ $this->_canvas->arc($x + $r1, $y - $ar1, $ar1, $ar1, 225 - $adj1, 270 + $adj1, $color, $width, $pattern, $cap);
+ }
+
+ if ($lineLength > 0) {
+ $this->_canvas->line($x + $dl + $r1, $y, $x + $dl + $length - $r2, $y, $color, $width, $pattern, $cap);
+ }
+
+ if ($ar2 > 0 && $adj2 > -22.5) {
+ $this->_canvas->arc($x + $length - $r2, $y - $ar2, $ar2, $ar2, 270 - $adj2, 315 + $adj2, $color, $width, $pattern, $cap);
+ }
+ break;
+
+ case "left":
+ if ($cornerClip) {
+ $points = [
+ $x, $y,
+ $x - 1, $y, // Extend outwards to avoid gaps
+ $x - 1, $y + $length, // Extend outwards to avoid gaps
+ $x, $y + $length,
+ $x + max($width, $r2), $y + $length - max($bottom, $r2),
+ $x + max($width, $r1), $y + max($top, $r1)
+ ];
+ $this->_canvas->clipping_polygon($points);
+ }
+
+ $x += $halfWidth;
+
+ if ($ar1 > 0 && $adj1 > -22.5) {
+ $this->_canvas->arc($x + $ar1, $y + $r1, $ar1, $ar1, 135 - $adj1, 180 + $adj1, $color, $width, $pattern, $cap);
+ }
+
+ if ($lineLength > 0) {
+ $this->_canvas->line($x, $y + $dl + $r1, $x, $y + $dl + $length - $r2, $color, $width, $pattern, $cap);
+ }
+
+ if ($ar2 > 0 && $adj2 > -22.5) {
+ $this->_canvas->arc($x + $ar2, $y + $length - $r2, $ar2, $ar2, 180 - $adj2, 225 + $adj2, $color, $width, $pattern, $cap);
+ }
+ break;
+
+ case "right":
+ if ($cornerClip) {
+ $points = [
+ $x, $y,
+ $x + 1, $y, // Extend outwards to avoid gaps
+ $x + 1, $y + $length, // Extend outwards to avoid gaps
+ $x, $y + $length,
+ $x - max($width, $r2), $y + $length - max($bottom, $r2),
+ $x - max($width, $r1), $y + max($top, $r1)
+ ];
+ $this->_canvas->clipping_polygon($points);
+ }
+
+ $x -= $halfWidth;
+
+ if ($ar1 > 0 && $adj1 > -22.5) {
+ $this->_canvas->arc($x - $ar1, $y + $r1, $ar1, $ar1, 0 - $adj1, 45 + $adj1, $color, $width, $pattern, $cap);
+ }
+
+ if ($lineLength > 0) {
+ $this->_canvas->line($x, $y + $dl + $r1, $x, $y + $dl + $length - $r2, $color, $width, $pattern, $cap);
+ }
+
+ if ($ar2 > 0 && $adj2 > -22.5) {
+ $this->_canvas->arc($x - $ar2, $y + $length - $r2, $ar2, $ar2, 315 - $adj2, 360 + $adj2, $color, $width, $pattern, $cap);
+ }
+ break;
+ }
+
+ if ($cornerClip) {
+ $this->_canvas->clipping_end();
+ }
+ }
+
+ /**
+ * @param float $opacity
+ */
+ protected function _set_opacity(float $opacity): void
+ {
+ if ($opacity >= 0.0 && $opacity <= 1.0) {
+ $this->_canvas->set_opacity($opacity);
+ }
+ }
+
+ /**
+ * @param float[] $box
+ * @param string $color
+ * @param array $style
+ */
+ protected function _debug_layout($box, $color = "red", $style = [])
+ {
+ $this->_canvas->rectangle($box[0], $box[1], $box[2], $box[3], Color::parse($color), 0.1, $style);
+ }
+
+ /**
+ * @param float $img_width
+ * @param float $img_height
+ * @param float $container_width
+ * @param float $container_height
+ * @param array|string $bg_resize
+ * @param int $dpi
+ *
+ * @return array
+ */
+ protected function _resize_background_image(
+ $img_width,
+ $img_height,
+ $container_width,
+ $container_height,
+ $bg_resize,
+ $dpi
+ ) {
+ // We got two some specific numbers and/or auto definitions
+ if (is_array($bg_resize)) {
+ $is_auto_width = $bg_resize[0] === 'auto';
+ if ($is_auto_width) {
+ $new_img_width = $img_width;
+ } else {
+ $new_img_width = $bg_resize[0];
+ if (Helpers::is_percent($new_img_width)) {
+ $new_img_width = round(($container_width / 100) * (float)$new_img_width);
+ } else {
+ $new_img_width = round($new_img_width * $dpi / 72);
+ }
+ }
+
+ $is_auto_height = $bg_resize[1] === 'auto';
+ if ($is_auto_height) {
+ $new_img_height = $img_height;
+ } else {
+ $new_img_height = $bg_resize[1];
+ if (Helpers::is_percent($new_img_height)) {
+ $new_img_height = round(($container_height / 100) * (float)$new_img_height);
+ } else {
+ $new_img_height = round($new_img_height * $dpi / 72);
+ }
+ }
+
+ // if one of both was set to auto the other one needs to scale proportionally
+ if ($is_auto_width !== $is_auto_height) {
+ if ($is_auto_height) {
+ $new_img_height = round($new_img_width * ($img_height / $img_width));
+ } else {
+ $new_img_width = round($new_img_height * ($img_width / $img_height));
+ }
+ }
+ } else {
+ $container_ratio = $container_height / $container_width;
+
+ if ($bg_resize === 'cover' || $bg_resize === 'contain') {
+ $img_ratio = $img_height / $img_width;
+
+ if (
+ ($bg_resize === 'cover' && $container_ratio > $img_ratio) ||
+ ($bg_resize === 'contain' && $container_ratio < $img_ratio)
+ ) {
+ $new_img_height = $container_height;
+ $new_img_width = round($container_height / $img_ratio);
+ } else {
+ $new_img_width = $container_width;
+ $new_img_height = round($container_width * $img_ratio);
+ }
+ } else {
+ $new_img_width = $img_width;
+ $new_img_height = $img_height;
+ }
+ }
+
+ return [$new_img_width, $new_img_height];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Block.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Block.php
new file mode 100644
index 0000000..99db192
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Block.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
+use Dompdf\Helpers;
+
+/**
+ * Renders block frames
+ *
+ * @package dompdf
+ */
+class Block extends AbstractRenderer
+{
+
+ /**
+ * @param Frame $frame
+ */
+ function render(Frame $frame)
+ {
+ $style = $frame->get_style();
+ $node = $frame->get_node();
+ $dompdf = $this->_dompdf;
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ [$x, $y, $w, $h] = $frame->get_border_box();
+
+ if ($node->nodeName === "body") {
+ // Margins should be fully resolved at this point
+ $mt = $style->margin_top;
+ $mb = $style->margin_bottom;
+ $h = $frame->get_containing_block("h") - $mt - $mb;
+ }
+
+ $border_box = [$x, $y, $w, $h];
+
+ // Draw our background, border and content
+ $this->_render_background($frame, $border_box);
+ $this->_render_border($frame, $border_box);
+ $this->_render_outline($frame, $border_box);
+
+ // Handle anchors & links
+ if ($node->nodeName === "a" && $href = $node->getAttribute("href")) {
+ $href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href) ?? $href;
+ $this->_canvas->add_link($href, $x, $y, $w, $h);
+ }
+
+ $id = $frame->get_node()->getAttribute("id");
+ if (strlen($id) > 0) {
+ $this->_canvas->add_named_dest($id);
+ }
+
+ $this->debugBlockLayout($frame, "red", false);
+ }
+
+ protected function debugBlockLayout(Frame $frame, ?string $color, bool $lines = false): void
+ {
+ $options = $this->_dompdf->getOptions();
+ $debugLayout = $options->getDebugLayout();
+
+ if (!$debugLayout) {
+ return;
+ }
+
+ if ($color && $options->getDebugLayoutBlocks()) {
+ $this->_debug_layout($frame->get_border_box(), $color);
+
+ if ($options->getDebugLayoutPaddingBox()) {
+ $this->_debug_layout($frame->get_padding_box(), $color, [0.5, 0.5]);
+ }
+ }
+
+ if ($lines && $options->getDebugLayoutLines() && $frame instanceof BlockFrameDecorator) {
+ [$cx, , $cw] = $frame->get_content_box();
+
+ foreach ($frame->get_line_boxes() as $line) {
+ $lw = $cw - $line->left - $line->right;
+ $this->_debug_layout([$cx + $line->left, $line->y, $lw, $line->h], "orange");
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Image.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Image.php
new file mode 100644
index 0000000..61f684f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Image.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\Image as ImageFrameDecorator;
+use Dompdf\Image\Cache;
+
+/**
+ * Image renderer
+ *
+ * @package dompdf
+ */
+class Image extends Block
+{
+ /**
+ * @param ImageFrameDecorator $frame
+ */
+ function render(Frame $frame)
+ {
+ $style = $frame->get_style();
+ $border_box = $frame->get_border_box();
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ // Render background & borders
+ $this->_render_background($frame, $border_box);
+ $this->_render_border($frame, $border_box);
+ $this->_render_outline($frame, $border_box);
+
+ $content_box = $frame->get_content_box();
+ [$x, $y, $w, $h] = $content_box;
+
+ $src = $frame->get_image_url();
+ $alt = null;
+
+ if (Cache::is_broken($src) &&
+ $alt = $frame->get_node()->getAttribute("alt")
+ ) {
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+
+ $this->_canvas->text(
+ $x,
+ $y,
+ $alt,
+ $font,
+ $size,
+ $style->color,
+ $word_spacing,
+ $letter_spacing
+ );
+ } elseif ($w > 0 && $h > 0) {
+ if ($style->has_border_radius()) {
+ [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $content_box);
+ $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
+ }
+
+ $this->_canvas->image($src, $x, $y, $w, $h, $style->image_resolution);
+
+ if ($style->has_border_radius()) {
+ $this->_canvas->clipping_end();
+ }
+ }
+
+ if ($msg = $frame->get_image_msg()) {
+ $parts = preg_split("/\s*\n\s*/", $msg);
+ $font = $style->font_family;
+ $height = 10;
+ $_y = $alt ? $y + $h - count($parts) * $height : $y;
+
+ foreach ($parts as $i => $_part) {
+ $this->_canvas->text($x, $_y + $i * $height, $_part, $font, $height * 0.8, [0.5, 0.5, 0.5]);
+ }
+ }
+
+ $id = $frame->get_node()->getAttribute("id");
+ if (strlen($id) > 0) {
+ $this->_canvas->add_named_dest($id);
+ }
+
+ $this->debugBlockLayout($frame, "blue");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Inline.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Inline.php
new file mode 100644
index 0000000..ad35464
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Inline.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Frame;
+use Dompdf\Helpers;
+
+/**
+ * Renders inline frames
+ *
+ * @package dompdf
+ */
+class Inline extends AbstractRenderer
+{
+ function render(Frame $frame)
+ {
+ if (!$frame->get_first_child()) {
+ return; // No children, no service
+ }
+
+ $style = $frame->get_style();
+ $dompdf = $this->_dompdf;
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ $do_debug_layout_line = $dompdf->getOptions()->getDebugLayout()
+ && $dompdf->getOptions()->getDebugLayoutInline();
+
+ // Draw the background & border behind each child. To do this we need
+ // to figure out just how much space each child takes:
+ [$x, $y] = $frame->get_first_child()->get_position();
+ [$w, $h] = $this->get_child_size($frame, $do_debug_layout_line);
+
+ [, , $cbw] = $frame->get_containing_block();
+ $margin_left = $style->length_in_pt($style->margin_left, $cbw);
+ $pt = $style->length_in_pt($style->padding_top, $cbw);
+ $pb = $style->length_in_pt($style->padding_bottom, $cbw);
+
+ // Make sure that border and background start inside the left margin
+ // Extend the drawn box by border and padding in vertical direction, as
+ // these do not affect layout
+ // FIXME: Using a small vertical offset of a fraction of the height here
+ // to work around the vertical position being slightly off in general
+ $x += $margin_left;
+ $y -= $style->border_top_width + $pt - ($h * 0.1);
+ $w += $style->border_left_width + $style->border_right_width;
+ $h += $style->border_top_width + $pt + $style->border_bottom_width + $pb;
+
+ $border_box = [$x, $y, $w, $h];
+ $this->_render_background($frame, $border_box);
+ $this->_render_border($frame, $border_box);
+ $this->_render_outline($frame, $border_box);
+
+ $node = $frame->get_node();
+ $id = $node->getAttribute("id");
+ if (strlen($id) > 0) {
+ $this->_canvas->add_named_dest($id);
+ }
+
+ // Only two levels of links frames
+ $is_link_node = $node->nodeName === "a";
+ if ($is_link_node) {
+ if (($name = $node->getAttribute("name"))) {
+ $this->_canvas->add_named_dest($name);
+ }
+ }
+
+ if ($frame->get_parent() && $frame->get_parent()->get_node()->nodeName === "a") {
+ $link_node = $frame->get_parent()->get_node();
+ }
+
+ // Handle anchors & links
+ if ($is_link_node) {
+ if ($href = $node->getAttribute("href")) {
+ $href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href) ?? $href;
+ $this->_canvas->add_link($href, $x, $y, $w, $h);
+ }
+ }
+ }
+
+ protected function get_child_size(Frame $frame, bool $do_debug_layout_line): array
+ {
+ $w = 0.0;
+ $h = 0.0;
+
+ foreach ($frame->get_children() as $child) {
+ if ($child->get_node()->nodeValue === " " && $child->get_prev_sibling() && !$child->get_next_sibling()) {
+ break;
+ }
+
+ $style = $child->get_style();
+ $auto_width = $style->width === "auto";
+ $auto_height = $style->height === "auto";
+ [, , $child_w, $child_h] = $child->get_padding_box();
+
+ if ($auto_width || $auto_height) {
+ [$child_w2, $child_h2] = $this->get_child_size($child, $do_debug_layout_line);
+
+ if ($auto_width) {
+ $child_w = $child_w2;
+ }
+
+ if ($auto_height) {
+ $child_h = $child_h2;
+ }
+ }
+
+ $w += $child_w;
+ $h = max($h, $child_h);
+
+ if ($do_debug_layout_line) {
+ $this->_debug_layout($child->get_border_box(), "blue");
+
+ if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) {
+ $this->_debug_layout($child->get_padding_box(), "blue", [0.5, 0.5]);
+ }
+ }
+ }
+
+ return [$w, $h];
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/ListBullet.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/ListBullet.php
new file mode 100644
index 0000000..2df6696
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/ListBullet.php
@@ -0,0 +1,235 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Helpers;
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator;
+use Dompdf\FrameDecorator\ListBulletImage;
+use Dompdf\Image\Cache;
+
+/**
+ * Renders list bullets
+ *
+ * @package dompdf
+ */
+class ListBullet extends AbstractRenderer
+{
+ /**
+ * @param $type
+ * @return mixed|string
+ */
+ static function get_counter_chars($type)
+ {
+ static $cache = [];
+
+ if (isset($cache[$type])) {
+ return $cache[$type];
+ }
+
+ $uppercase = false;
+ $text = "";
+
+ switch ($type) {
+ case "decimal-leading-zero":
+ case "decimal":
+ case "1":
+ return "0123456789";
+
+ case "upper-alpha":
+ case "upper-latin":
+ case "A":
+ $uppercase = true;
+ case "lower-alpha":
+ case "lower-latin":
+ case "a":
+ $text = "abcdefghijklmnopqrstuvwxyz";
+ break;
+
+ case "upper-roman":
+ case "I":
+ $uppercase = true;
+ case "lower-roman":
+ case "i":
+ $text = "ivxlcdm";
+ break;
+
+ case "lower-greek":
+ for ($i = 0; $i < 24; $i++) {
+ $text .= Helpers::unichr($i + 944);
+ }
+ break;
+ }
+
+ if ($uppercase) {
+ $text = strtoupper($text);
+ }
+
+ return $cache[$type] = "$text.";
+ }
+
+ /**
+ * @param int $n
+ * @param string $type
+ * @param int|null $pad
+ *
+ * @return string
+ */
+ private function make_counter($n, $type, $pad = null)
+ {
+ $n = intval($n);
+ $text = "";
+ $uppercase = false;
+
+ switch ($type) {
+ case "decimal-leading-zero":
+ case "decimal":
+ case "1":
+ if ($pad) {
+ $text = str_pad($n, $pad, "0", STR_PAD_LEFT);
+ } else {
+ $text = $n;
+ }
+ break;
+
+ case "upper-alpha":
+ case "upper-latin":
+ case "A":
+ $uppercase = true;
+ case "lower-alpha":
+ case "lower-latin":
+ case "a":
+ $text = chr((($n - 1) % 26) + ord('a'));
+ break;
+
+ case "upper-roman":
+ case "I":
+ $uppercase = true;
+ case "lower-roman":
+ case "i":
+ $text = Helpers::dec2roman($n);
+ break;
+
+ case "lower-greek":
+ $text = Helpers::unichr($n + 944);
+ break;
+ }
+
+ if ($uppercase) {
+ $text = strtoupper($text);
+ }
+
+ return "$text.";
+ }
+
+ /**
+ * @param ListBulletFrameDecorator $frame
+ */
+ function render(Frame $frame)
+ {
+ $li = $frame->get_parent();
+ $style = $frame->get_style();
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ // Don't render bullets twice if the list item was split
+ if ($li->is_split_off) {
+ return;
+ }
+
+ $font_family = $style->font_family;
+ $font_size = $style->font_size;
+ $baseline = $this->_canvas->get_font_baseline($font_family, $font_size);
+
+ // Handle list-style-image
+ // If list style image is requested but missing, fall back to predefined types
+ if ($frame instanceof ListBulletImage && !Cache::is_broken($img = $frame->get_image_url())) {
+ [$x, $y] = $frame->get_position();
+ $w = $frame->get_width();
+ $h = $frame->get_height();
+ $y += $baseline - $h;
+
+ $this->_canvas->image($img, $x, $y, $w, $h);
+ } else {
+ $bullet_style = $style->list_style_type;
+
+ switch ($bullet_style) {
+ default:
+ case "disc":
+ case "circle":
+ [$x, $y] = $frame->get_position();
+ $offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET;
+ $r = ($font_size * ListBulletFrameDecorator::BULLET_SIZE) / 2;
+ $x += $r;
+ $y += $baseline - $r - $offset;
+ $o = $font_size * ListBulletFrameDecorator::BULLET_THICKNESS;
+ $this->_canvas->circle($x, $y, $r, $style->color, $o, null, $bullet_style !== "circle");
+ break;
+
+ case "square":
+ [$x, $y] = $frame->get_position();
+ $offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET;
+ $w = $font_size * ListBulletFrameDecorator::BULLET_SIZE;
+ $y += $baseline - $w - $offset;
+ $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color);
+ break;
+
+ case "decimal-leading-zero":
+ case "decimal":
+ case "lower-alpha":
+ case "lower-latin":
+ case "lower-roman":
+ case "lower-greek":
+ case "upper-alpha":
+ case "upper-latin":
+ case "upper-roman":
+ case "1": // HTML 4.0 compatibility
+ case "a":
+ case "i":
+ case "A":
+ case "I":
+ $pad = null;
+ if ($bullet_style === "decimal-leading-zero") {
+ $pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count"));
+ }
+
+ $node = $frame->get_node();
+
+ if (!$node->hasAttribute("dompdf-counter")) {
+ return;
+ }
+
+ $index = $node->getAttribute("dompdf-counter");
+ $text = $this->make_counter($index, $bullet_style, $pad);
+
+ if (trim($text) === "") {
+ return;
+ }
+
+ $word_spacing = $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+ $text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font_family, $font_size, $word_spacing, $letter_spacing);
+
+ [$x, $y] = $frame->get_position();
+ // Correct for static frame width applied by positioner
+ $x += $frame->get_width() - $text_width;
+
+ $this->_canvas->text($x, $y, $text,
+ $font_family, $font_size,
+ $style->color, $word_spacing, $letter_spacing);
+
+ case "none":
+ break;
+ }
+ }
+
+ $id = $frame->get_node()->getAttribute("id");
+ if (strlen($id) > 0) {
+ $this->_canvas->add_named_dest($id);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableCell.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableCell.php
new file mode 100644
index 0000000..cbbffd3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableCell.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Frame;
+use Dompdf\FrameDecorator\Table;
+
+/**
+ * Renders table cells
+ *
+ * @package dompdf
+ */
+class TableCell extends Block
+{
+
+ /**
+ * @param Frame $frame
+ */
+ function render(Frame $frame)
+ {
+ $style = $frame->get_style();
+
+ if (trim($frame->get_node()->nodeValue) === "" && $style->empty_cells === "hide") {
+ return;
+ }
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ $border_box = $frame->get_border_box();
+ $table = Table::find_parent_table($frame);
+
+ if ($table->get_style()->border_collapse !== "collapse") {
+ $this->_render_background($frame, $border_box);
+ $this->_render_border($frame, $border_box);
+ $this->_render_outline($frame, $border_box);
+ } else {
+ // The collapsed case is slightly complicated...
+
+ $cells = $table->get_cellmap()->get_spanned_cells($frame);
+
+ if (is_null($cells)) {
+ return;
+ }
+
+ // Render the background to the padding box, as the cells are
+ // rendered individually one after another, and we don't want the
+ // background to overlap an adjacent border
+ $padding_box = $frame->get_padding_box();
+
+ $this->_render_background($frame, $padding_box);
+ $this->_render_collapsed_border($frame, $table);
+
+ // FIXME: Outline should be drawn over other cells
+ $this->_render_outline($frame, $border_box);
+ }
+
+ $id = $frame->get_node()->getAttribute("id");
+ if (strlen($id) > 0) {
+ $this->_canvas->add_named_dest($id);
+ }
+
+ // $this->debugBlockLayout($frame, "red", false);
+ }
+
+ /**
+ * @param Frame $frame
+ * @param Table $table
+ */
+ protected function _render_collapsed_border(Frame $frame, Table $table): void
+ {
+ $cellmap = $table->get_cellmap();
+ $cells = $cellmap->get_spanned_cells($frame);
+ $num_rows = $cellmap->get_num_rows();
+ $num_cols = $cellmap->get_num_cols();
+
+ [$table_x, $table_y] = $table->get_position();
+
+ // Determine the top row spanned by this cell
+ $i = $cells["rows"][0];
+ $top_row = $cellmap->get_row($i);
+
+ // Determine if this cell borders on the bottom of the table. If so,
+ // then we draw its bottom border. Otherwise the next row down will
+ // draw its top border instead.
+ if (in_array($num_rows - 1, $cells["rows"])) {
+ $draw_bottom = true;
+ $bottom_row = $cellmap->get_row($num_rows - 1);
+ } else {
+ $draw_bottom = false;
+ }
+
+ // Draw the horizontal borders
+ foreach ($cells["columns"] as $j) {
+ $bp = $cellmap->get_border_properties($i, $j);
+ $col = $cellmap->get_column($j);
+
+ $x = $table_x + $col["x"] - $bp["left"]["width"] / 2;
+ $y = $table_y + $top_row["y"] - $bp["top"]["width"] / 2;
+ $w = $col["used-width"] + ($bp["left"]["width"] + $bp["right"]["width"]) / 2;
+
+ if ($bp["top"]["width"] > 0) {
+ $widths = [
+ (float)$bp["top"]["width"],
+ (float)$bp["right"]["width"],
+ (float)$bp["bottom"]["width"],
+ (float)$bp["left"]["width"]
+ ];
+
+ $method = "_border_" . $bp["top"]["style"];
+ $this->$method($x, $y, $w, $bp["top"]["color"], $widths, "top", "square");
+ }
+
+ if ($draw_bottom) {
+ $bp = $cellmap->get_border_properties($num_rows - 1, $j);
+ if ($bp["bottom"]["width"] <= 0) {
+ continue;
+ }
+
+ $widths = [
+ (float)$bp["top"]["width"],
+ (float)$bp["right"]["width"],
+ (float)$bp["bottom"]["width"],
+ (float)$bp["left"]["width"]
+ ];
+
+ $y = $table_y + $bottom_row["y"] + $bottom_row["height"] + $bp["bottom"]["width"] / 2;
+
+ $method = "_border_" . $bp["bottom"]["style"];
+ $this->$method($x, $y, $w, $bp["bottom"]["color"], $widths, "bottom", "square");
+ }
+ }
+
+ $j = $cells["columns"][0];
+ $left_col = $cellmap->get_column($j);
+
+ if (in_array($num_cols - 1, $cells["columns"])) {
+ $draw_right = true;
+ $right_col = $cellmap->get_column($num_cols - 1);
+ } else {
+ $draw_right = false;
+ }
+
+ // Draw the vertical borders
+ foreach ($cells["rows"] as $i) {
+ $bp = $cellmap->get_border_properties($i, $j);
+ $row = $cellmap->get_row($i);
+
+ $x = $table_x + $left_col["x"] - $bp["left"]["width"] / 2;
+ $y = $table_y + $row["y"] - $bp["top"]["width"] / 2;
+ $h = $row["height"] + ($bp["top"]["width"] + $bp["bottom"]["width"]) / 2;
+
+ if ($bp["left"]["width"] > 0) {
+ $widths = [
+ (float)$bp["top"]["width"],
+ (float)$bp["right"]["width"],
+ (float)$bp["bottom"]["width"],
+ (float)$bp["left"]["width"]
+ ];
+
+ $method = "_border_" . $bp["left"]["style"];
+ $this->$method($x, $y, $h, $bp["left"]["color"], $widths, "left", "square");
+ }
+
+ if ($draw_right) {
+ $bp = $cellmap->get_border_properties($i, $num_cols - 1);
+ if ($bp["right"]["width"] <= 0) {
+ continue;
+ }
+
+ $widths = [
+ (float)$bp["top"]["width"],
+ (float)$bp["right"]["width"],
+ (float)$bp["bottom"]["width"],
+ (float)$bp["left"]["width"]
+ ];
+
+ $x = $table_x + $right_col["x"] + $right_col["used-width"] + $bp["right"]["width"] / 2;
+
+ $method = "_border_" . $bp["right"]["style"];
+ $this->$method($x, $y, $h, $bp["right"]["color"], $widths, "right", "square");
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableRowGroup.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableRowGroup.php
new file mode 100644
index 0000000..295ccde
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/TableRowGroup.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Frame;
+
+/**
+ * Renders block frames
+ *
+ * @package dompdf
+ */
+class TableRowGroup extends Block
+{
+
+ /**
+ * @param Frame $frame
+ */
+ function render(Frame $frame)
+ {
+ $style = $frame->get_style();
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ $border_box = $frame->get_border_box();
+
+ $this->_render_border($frame, $border_box);
+ $this->_render_outline($frame, $border_box);
+
+ $id = $frame->get_node()->getAttribute("id");
+ if (strlen($id) > 0) {
+ $this->_canvas->add_named_dest($id);
+ }
+
+ $this->debugBlockLayout($frame, "red");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Text.php b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Text.php
new file mode 100644
index 0000000..e7baa0a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/dompdf/dompdf/src/Renderer/Text.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * @package dompdf
+ * @link https://github.com/dompdf/dompdf
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace Dompdf\Renderer;
+
+use Dompdf\Adapter\CPDF;
+use Dompdf\Frame;
+
+/**
+ * Renders text frames
+ *
+ * @package dompdf
+ */
+class Text extends AbstractRenderer
+{
+ /** Thickness of underline. Screen: 0.08, print: better less, e.g. 0.04 */
+ const DECO_THICKNESS = 0.02;
+
+ //Tweaking if $base and $descent are not accurate.
+ //Check method_exists( $this->_canvas, "get_cpdf" )
+ //- For cpdf these can and must stay 0, because font metrics are used directly.
+ //- For other renderers, if different values are wanted, separate the parameter sets.
+ // But $size and $size-$height seem to be accurate enough
+
+ /** Relative to bottom of text, as fraction of height */
+ const UNDERLINE_OFFSET = 0.0;
+
+ /** Relative to top of text */
+ const OVERLINE_OFFSET = 0.0;
+
+ /** Relative to centre of text. */
+ const LINETHROUGH_OFFSET = 0.0;
+
+ /** How far to extend lines past either end, in pt */
+ const DECO_EXTENSION = 0.0;
+
+ /**
+ * @param \Dompdf\FrameDecorator\Text $frame
+ */
+ function render(Frame $frame)
+ {
+ $style = $frame->get_style();
+ $text = $frame->get_text();
+
+ if (trim($text) === "") {
+ return;
+ }
+
+ $this->_set_opacity($frame->get_opacity($style->opacity));
+
+ list($x, $y) = $frame->get_position();
+ $cb = $frame->get_containing_block();
+
+ $ml = $style->margin_left;
+ $pl = $style->padding_left;
+ $bl = $style->border_left_width;
+ $x += (float) $style->length_in_pt([$ml, $pl, $bl], $cb["w"]);
+
+ $font = $style->font_family;
+ $size = $style->font_size;
+ $frame_font_size = $frame->get_dompdf()->getFontMetrics()->getFontHeight($font, $size);
+ $word_spacing = $frame->get_text_spacing() + $style->word_spacing;
+ $letter_spacing = $style->letter_spacing;
+ $width = (float) $style->width;
+
+ /*$text = str_replace(
+ array("{PAGE_NUM}"),
+ array($this->_canvas->get_page_number()),
+ $text
+ );*/
+
+ $this->_canvas->text($x, $y, $text,
+ $font, $size,
+ $style->color, $word_spacing, $letter_spacing);
+
+ $line = $frame->get_containing_line();
+
+ // FIXME Instead of using the tallest frame to position,
+ // the decoration, the text should be well placed
+ if (false && $line->tallest_frame) {
+ $base_frame = $line->tallest_frame;
+ $style = $base_frame->get_style();
+ $size = $style->font_size;
+ }
+
+ $line_thickness = $size * self::DECO_THICKNESS;
+ $underline_offset = $size * self::UNDERLINE_OFFSET;
+ $overline_offset = $size * self::OVERLINE_OFFSET;
+ $linethrough_offset = $size * self::LINETHROUGH_OFFSET;
+ $underline_position = -0.08;
+
+ if ($this->_canvas instanceof CPDF) {
+ $cpdf_font = $this->_canvas->get_cpdf()->fonts[$style->font_family];
+
+ if (isset($cpdf_font["UnderlinePosition"])) {
+ $underline_position = $cpdf_font["UnderlinePosition"] / 1000;
+ }
+
+ if (isset($cpdf_font["UnderlineThickness"])) {
+ $line_thickness = $size * ($cpdf_font["UnderlineThickness"] / 1000);
+ }
+ }
+
+ $descent = $size * $underline_position;
+ $base = $frame_font_size;
+
+ // Handle text decoration:
+ // http://www.w3.org/TR/CSS21/text.html#propdef-text-decoration
+
+ // Draw all applicable text-decorations. Start with the root and work our way down.
+ $p = $frame;
+ $stack = [];
+ while ($p = $p->get_parent()) {
+ $stack[] = $p;
+ }
+
+ while (isset($stack[0])) {
+ $f = array_pop($stack);
+
+ if (($text_deco = $f->get_style()->text_decoration) === "none") {
+ continue;
+ }
+
+ $deco_y = $y; //$line->y;
+ $color = $f->get_style()->color;
+
+ switch ($text_deco) {
+ default:
+ continue 2;
+
+ case "underline":
+ $deco_y += $base - $descent + $underline_offset + $line_thickness / 2;
+ break;
+
+ case "overline":
+ $deco_y += $overline_offset + $line_thickness / 2;
+ break;
+
+ case "line-through":
+ $deco_y += $base * 0.7 + $linethrough_offset;
+ break;
+ }
+
+ $dx = 0;
+ $x1 = $x - self::DECO_EXTENSION;
+ $x2 = $x + $width + $dx + self::DECO_EXTENSION;
+ $this->_canvas->line($x1, $deco_y, $x2, $deco_y, $color, $line_thickness);
+ }
+
+ if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutLines()) {
+ $text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $letter_spacing);
+ $this->_debug_layout([$x, $y, $text_width, $frame_font_size], "orange", [0.5, 0.5]);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/CREDITS b/library/vendor/dompdf/vendor/masterminds/html5/CREDITS
new file mode 100644
index 0000000..c2dbc4b
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/CREDITS
@@ -0,0 +1,11 @@
+Matt Butcher [technosophos] <technosophos@gmail.com> (lead)
+Matt Farina [mattfarina] <matt@mattfarina.com> (lead)
+Asmir Mustafic [goetas] <goetas@lignano.it> (contributor)
+Edward Z. Yang [ezyang] <ezyang@mit.edu> (contributor)
+Geoffrey Sneddon [gsnedders] <geoffers@gmail.com> (contributor)
+Kukhar Vasily [ngreduce] <ngreduce@gmail.com> (contributor)
+Rune Christensen [MrElectronic] <mrelectronic@example.com> (contributor)
+Mišo Belica [miso-belica] <miso-belica@example.com> (contributor)
+Asmir Mustafic [goetas] <goetas@example.com> (contributor)
+KITAITI Makoto [KitaitiMakoto] <KitaitiMakoto@example.com> (contributor)
+Jacob Floyd [cognifloyd] <cognifloyd@gmail.com> (contributor)
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/LICENSE.txt b/library/vendor/dompdf/vendor/masterminds/html5/LICENSE.txt
new file mode 100644
index 0000000..3c275b5
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/LICENSE.txt
@@ -0,0 +1,66 @@
+## HTML5-PHP License
+
+Copyright (c) 2013 The Authors of HTML5-PHP
+
+Matt Butcher - mattbutcher@google.com
+Matt Farina - matt@mattfarina.com
+Asmir Mustafic - goetas@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.
+
+## HTML5Lib License
+
+Portions of this are based on html5lib's PHP version, which was a
+sub-project of html5lib. The following is the list of contributors from
+html5lib:
+
+html5lib:
+
+Copyright (c) 2006-2009 The Authors
+
+Contributors:
+James Graham - jg307@cam.ac.uk
+Anne van Kesteren - annevankesteren@gmail.com
+Lachlan Hunt - lachlan.hunt@lachy.id.au
+Matt McDonald - kanashii@kanashii.ca
+Sam Ruby - rubys@intertwingly.net
+Ian Hickson (Google) - ian@hixie.ch
+Thomas Broyer - t.broyer@ltgt.net
+Jacques Distler - distler@golem.ph.utexas.edu
+Henri Sivonen - hsivonen@iki.fi
+Adam Barth - abarth@webkit.org
+Eric Seidel - eric@webkit.org
+The Mozilla Foundation (contributions from Henri Sivonen since 2008)
+David Flanagan (Mozilla) - dflanagan@mozilla.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/library/vendor/dompdf/vendor/masterminds/html5/README.md b/library/vendor/dompdf/vendor/masterminds/html5/README.md
new file mode 100644
index 0000000..b1ca1e3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/README.md
@@ -0,0 +1,270 @@
+> # UKRAINE NEEDS YOUR HELP NOW!
+>
+> On 24 February 2022, Russian [President Vladimir Putin ordered an invasion of Ukraine by Russian Armed Forces](https://www.bbc.com/news/world-europe-60504334).
+>
+> Your support is urgently needed.
+>
+> - Donate to the volunteers. Here is the volunteer fund helping the Ukrainian army to provide all the necessary equipment:
+> https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi or https://savelife.in.ua/en/donate/
+> - Triple-check social media sources. Russian disinformation is attempting to coverup and distort the reality in Ukraine.
+> - Help Ukrainian refugees who are fleeing Russian attacks and shellings: https://www.globalcitizen.org/en/content/ways-to-help-ukraine-conflict/
+> - Put pressure on your political representatives to provide help to Ukraine.
+> - Believe in the Ukrainian people, they will not surrender, they don't have another Ukraine.
+>
+> THANK YOU!
+----
+
+# HTML5-PHP
+
+HTML5 is a standards-compliant HTML5 parser and writer written entirely in PHP.
+It is stable and used in many production websites, and has
+well over [five million downloads](https://packagist.org/packages/masterminds/html5).
+
+HTML5 provides the following features.
+
+- An HTML5 serializer
+- Support for PHP namespaces
+- Composer support
+- Event-based (SAX-like) parser
+- A DOM tree builder
+- Interoperability with [QueryPath](https://github.com/technosophos/querypath)
+- Runs on **PHP** 5.3.0 or newer
+
+[![Build Status](https://travis-ci.org/Masterminds/html5-php.png?branch=master)](https://travis-ci.org/Masterminds/html5-php)
+[![Latest Stable Version](https://poser.pugx.org/masterminds/html5/v/stable.png)](https://packagist.org/packages/masterminds/html5)
+[![Code Coverage](https://scrutinizer-ci.com/g/Masterminds/html5-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Masterminds/html5-php/?branch=master)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Masterminds/html5-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Masterminds/html5-php/?branch=master)
+[![Stability: Sustained](https://masterminds.github.io/stability/sustained.svg)](https://masterminds.github.io/stability/sustained.html)
+
+## Installation
+
+Install HTML5-PHP using [composer](http://getcomposer.org/).
+
+By adding the `masterminds/html5` dependency to your `composer.json` file:
+
+```json
+{
+ "require" : {
+ "masterminds/html5": "^2.0"
+ },
+}
+```
+
+By invoking require command via composer executable:
+
+```bash
+composer require masterminds/html5
+```
+
+## Basic Usage
+
+HTML5-PHP has a high-level API and a low-level API.
+
+Here is how you use the high-level `HTML5` library API:
+
+```php
+<?php
+// Assuming you installed from Composer:
+require "vendor/autoload.php";
+
+use Masterminds\HTML5;
+
+// An example HTML document:
+$html = <<< 'HERE'
+ <html>
+ <head>
+ <title>TEST</title>
+ </head>
+ <body id='foo'>
+ <h1>Hello World</h1>
+ <p>This is a test of the HTML5 parser.</p>
+ </body>
+ </html>
+HERE;
+
+// Parse the document. $dom is a DOMDocument.
+$html5 = new HTML5();
+$dom = $html5->loadHTML($html);
+
+// Render it as HTML5:
+print $html5->saveHTML($dom);
+
+// Or save it to a file:
+$html5->save($dom, 'out.html');
+```
+
+The `$dom` created by the parser is a full `DOMDocument` object. And the
+`save()` and `saveHTML()` methods will take any DOMDocument.
+
+### Options
+
+It is possible to pass in an array of configuration options when loading
+an HTML5 document.
+
+```php
+// An associative array of options
+$options = array(
+ 'option_name' => 'option_value',
+);
+
+// Provide the options to the constructor
+$html5 = new HTML5($options);
+
+$dom = $html5->loadHTML($html);
+```
+
+The following options are supported:
+
+* `encode_entities` (boolean): Indicates that the serializer should aggressively
+ encode characters as entities. Without this, it only encodes the bare
+ minimum.
+* `disable_html_ns` (boolean): Prevents the parser from automatically
+ assigning the HTML5 namespace to the DOM document. This is for
+ non-namespace aware DOM tools.
+* `target_document` (\DOMDocument): A DOM document that will be used as the
+ destination for the parsed nodes.
+* `implicit_namespaces` (array): An assoc array of namespaces that should be
+ used by the parser. Name is tag prefix, value is NS URI.
+
+## The Low-Level API
+
+This library provides the following low-level APIs that you can use to
+create more customized HTML5 tools:
+
+- A SAX-like event-based parser that you can hook into for special kinds
+of parsing.
+- A flexible error-reporting mechanism that can be tuned to document
+syntax checking.
+- A DOM implementation that uses PHP's built-in DOM library.
+
+The unit tests exercise each piece of the API, and every public function
+is well-documented.
+
+### Parser Design
+
+The parser is designed as follows:
+
+- The `Scanner` handles scanning on behalf of the parser.
+- The `Tokenizer` requests data off of the scanner, parses it, clasifies
+it, and sends it to an `EventHandler`. It is a *recursive descent parser.*
+- The `EventHandler` receives notifications and data for each specific
+semantic event that occurs during tokenization.
+- The `DOMBuilder` is an `EventHandler` that listens for tokenizing
+events and builds a document tree (`DOMDocument`) based on the events.
+
+### Serializer Design
+
+The serializer takes a data structure (the `DOMDocument`) and transforms
+it into a character representation -- an HTML5 document.
+
+The serializer is broken into three parts:
+
+- The `OutputRules` contain the rules to turn DOM elements into strings. The
+rules are an implementation of the interface `RulesInterface` allowing for
+different rule sets to be used.
+- The `Traverser`, which is a special-purpose tree walker. It visits
+each node node in the tree and uses the `OutputRules` to transform the node
+into a string.
+- `HTML5` manages the `Traverser` and stores the resultant data
+in the correct place.
+
+The serializer (`save()`, `saveHTML()`) follows the
+[section 8.9 of the HTML 5.0 spec](http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#serializing-html-fragments).
+So tags are serialized according to these rules:
+
+- A tag with children: &lt;foo&gt;CHILDREN&lt;/foo&gt;
+- A tag that cannot have content: &lt;foo&gt; (no closing tag)
+- A tag that could have content, but doesn't: &lt;foo&gt;&lt;/foo&gt;
+
+## Known Issues (Or, Things We Designed Against the Spec)
+
+Please check the issue queue for a full list, but the following are
+issues known issues that are not presently on the roadmap:
+
+- Namespaces: HTML5 only [supports a selected list of namespaces](http://www.w3.org/TR/html5/infrastructure.html#namespaces)
+ and they do not operate in the same way as XML namespaces. A `:` has no special
+ meaning.
+ By default the parser does not support XML style namespaces via `:`;
+ to enable the XML namespaces see the [XML Namespaces section](#xml-namespaces)
+- Scripts: This parser does not contain a JavaScript or a CSS
+ interpreter. While one may be supplied, not all features will be
+ supported.
+- Rentrance: The current parser is not re-entrant. (Thus you can't pause
+ the parser to modify the HTML string mid-parse.)
+- Validation: The current tree builder is **not** a validating parser.
+ While it will correct some HTML, it does not check that the HTML
+ conforms to the standard. (Should you wish, you can build a validating
+ parser by extending DOMTree or building your own EventHandler
+ implementation.)
+ * There is limited support for insertion modes.
+ * Some autocorrection is done automatically.
+ * Per the spec, many legacy tags are admitted and correctly handled,
+ even though they are technically not part of HTML5.
+- Attribute names and values: Due to the implementation details of the
+ PHP implementation of DOM, attribute names that do not follow the
+ XML 1.0 standard are not inserted into the DOM. (Effectively, they
+ are ignored.) If you've got a clever fix for this, jump in!
+- Processor Instructions: The HTML5 spec does not allow processor
+ instructions. We do. Since this is a server-side library, we think
+ this is useful. And that means, dear reader, that in some cases you
+ can parse the HTML from a mixed PHP/HTML document. This, however,
+ is an incidental feature, not a core feature.
+- HTML manifests: Unsupported.
+- PLAINTEXT: Unsupported.
+- Adoption Agency Algorithm: Not yet implemented. (8.2.5.4.7)
+
+## XML Namespaces
+
+To use XML style namespaces you have to configure well the main `HTML5` instance.
+
+```php
+use Masterminds\HTML5;
+$html = new HTML5(array(
+ "xmlNamespaces" => true
+));
+
+$dom = $html->loadHTML('<t:tag xmlns:t="http://www.example.com"/>');
+
+$dom->documentElement->namespaceURI; // http://www.example.com
+
+```
+
+You can also add some default prefixes that will not require the namespace declaration,
+but its elements will be namespaced.
+
+```php
+use Masterminds\HTML5;
+$html = new HTML5(array(
+ "implicitNamespaces"=>array(
+ "t"=>"http://www.example.com"
+ )
+));
+
+$dom = $html->loadHTML('<t:tag/>');
+
+$dom->documentElement->namespaceURI; // http://www.example.com
+
+```
+
+## Thanks to...
+
+The dedicated (and patient) contributors of patches small and large,
+who have already made this library better.See the CREDITS file for
+a list of contributors.
+
+We owe a huge debt of gratitude to the original authors of html5lib.
+
+While not much of the original parser remains, we learned a lot from
+reading the html5lib library. And some pieces remain here. In
+particular, much of the UTF-8 and Unicode handling is derived from the
+html5lib project.
+
+## License
+
+This software is released under the MIT license. The original html5lib
+library was also released under the MIT license.
+
+See LICENSE.txt
+
+Certain files contain copyright assertions by specific individuals
+involved with html5lib. Those have been retained where appropriate.
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/UPGRADING.md b/library/vendor/dompdf/vendor/masterminds/html5/UPGRADING.md
new file mode 100644
index 0000000..76e3a19
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/UPGRADING.md
@@ -0,0 +1,21 @@
+From 1.x to 2.x
+=================
+
+- All classes uses `Masterminds` namespace.
+- All public static methods has been removed from `HTML5` class and the general API to access the HTML5 functionalities has changed.
+
+ Before:
+
+ $dom = \HTML5::loadHTML('<html>....');
+ \HTML5::saveHTML($dom);
+
+ After:
+
+ use Masterminds\HTML5;
+
+ $html5 = new HTML5();
+
+ $dom = $html5->loadHTML('<html>....');
+ echo $html5->saveHTML($dom);
+
+
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/bin/entities.php b/library/vendor/dompdf/vendor/masterminds/html5/bin/entities.php
new file mode 100644
index 0000000..56323a3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/bin/entities.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Fetch the entities.json file and convert to PHP datastructure.
+ */
+
+// The URL to the official entities JSON file.
+$ENTITIES_URL = 'http://www.w3.org/TR/2012/CR-html5-20121217/entities.json';
+
+$payload = file_get_contents($ENTITIES_URL);
+$json = json_decode($payload);
+
+$table = array();
+foreach ($json as $name => $obj) {
+ $sname = substr($name, 1, -1);
+ $table[$sname] = $obj->characters;
+}
+
+echo '<?php
+namespace Masterminds\\HTML5;
+/** Entity lookup tables. This class is automatically generated. */
+class Entities {
+ public static $byName = ';
+var_export($table);
+echo ';
+}' . PHP_EOL;
+//print serialize($table);
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/composer.json b/library/vendor/dompdf/vendor/masterminds/html5/composer.json
new file mode 100644
index 0000000..fb7674e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/composer.json
@@ -0,0 +1,42 @@
+{
+ "name": "masterminds/html5",
+ "description": "An HTML5 parser and serializer.",
+ "type": "library",
+ "homepage": "http://masterminds.github.io/html5-php",
+ "license": "MIT",
+ "keywords": ["xml", "html", "html5", "dom", "parser", "serializer", "querypath"],
+ "authors": [
+ {
+ "name": "Matt Butcher",
+ "email": "technosophos@gmail.com"
+ },
+ {
+ "name": "Matt Farina",
+ "email": "matt@mattfarina.com"
+ },
+ {
+ "name": "Asmir Mustafic",
+ "email": "goetas@gmail.com"
+ }
+ ],
+ "require" : {
+ "ext-ctype": "*",
+ "ext-dom": "*",
+ "ext-libxml" : "*",
+ "php" : ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit" : "^4.8.35 || ^5.7.21 || ^6 || ^7"
+ },
+ "autoload": {
+ "psr-4": {"Masterminds\\": "src"}
+ },
+ "autoload-dev": {
+ "psr-4": {"Masterminds\\HTML5\\Tests\\": "test/HTML5"}
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5.php
new file mode 100644
index 0000000..c857145
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5.php
@@ -0,0 +1,246 @@
+<?php
+
+namespace Masterminds;
+
+use Masterminds\HTML5\Parser\DOMTreeBuilder;
+use Masterminds\HTML5\Parser\Scanner;
+use Masterminds\HTML5\Parser\Tokenizer;
+use Masterminds\HTML5\Serializer\OutputRules;
+use Masterminds\HTML5\Serializer\Traverser;
+
+/**
+ * This class offers convenience methods for parsing and serializing HTML5.
+ * It is roughly designed to mirror the \DOMDocument native class.
+ */
+class HTML5
+{
+ /**
+ * Global options for the parser and serializer.
+ *
+ * @var array
+ */
+ private $defaultOptions = array(
+ // Whether the serializer should aggressively encode all characters as entities.
+ 'encode_entities' => false,
+
+ // Prevents the parser from automatically assigning the HTML5 namespace to the DOM document.
+ 'disable_html_ns' => false,
+ );
+
+ protected $errors = array();
+
+ public function __construct(array $defaultOptions = array())
+ {
+ $this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions);
+ }
+
+ /**
+ * Get the current default options.
+ *
+ * @return array
+ */
+ public function getOptions()
+ {
+ return $this->defaultOptions;
+ }
+
+ /**
+ * Load and parse an HTML file.
+ *
+ * This will apply the HTML5 parser, which is tolerant of many
+ * varieties of HTML, including XHTML 1, HTML 4, and well-formed HTML
+ * 3. Note that in these cases, not all of the old data will be
+ * preserved. For example, XHTML's XML declaration will be removed.
+ *
+ * The rules governing parsing are set out in the HTML 5 spec.
+ *
+ * @param string|resource $file The path to the file to parse. If this is a resource, it is
+ * assumed to be an open stream whose pointer is set to the first
+ * byte of input.
+ * @param array $options Configuration options when parsing the HTML.
+ *
+ * @return \DOMDocument A DOM document. These object type is defined by the libxml
+ * library, and should have been included with your version of PHP.
+ */
+ public function load($file, array $options = array())
+ {
+ // Handle the case where file is a resource.
+ if (is_resource($file)) {
+ return $this->parse(stream_get_contents($file), $options);
+ }
+
+ return $this->parse(file_get_contents($file), $options);
+ }
+
+ /**
+ * Parse a HTML Document from a string.
+ *
+ * Take a string of HTML 5 (or earlier) and parse it into a
+ * DOMDocument.
+ *
+ * @param string $string A html5 document as a string.
+ * @param array $options Configuration options when parsing the HTML.
+ *
+ * @return \DOMDocument A DOM document. DOM is part of libxml, which is included with
+ * almost all distribtions of PHP.
+ */
+ public function loadHTML($string, array $options = array())
+ {
+ return $this->parse($string, $options);
+ }
+
+ /**
+ * Convenience function to load an HTML file.
+ *
+ * This is here to provide backwards compatibility with the
+ * PHP DOM implementation. It simply calls load().
+ *
+ * @param string $file The path to the file to parse. If this is a resource, it is
+ * assumed to be an open stream whose pointer is set to the first
+ * byte of input.
+ * @param array $options Configuration options when parsing the HTML.
+ *
+ * @return \DOMDocument A DOM document. These object type is defined by the libxml
+ * library, and should have been included with your version of PHP.
+ */
+ public function loadHTMLFile($file, array $options = array())
+ {
+ return $this->load($file, $options);
+ }
+
+ /**
+ * Parse a HTML fragment from a string.
+ *
+ * @param string $string the HTML5 fragment as a string
+ * @param array $options Configuration options when parsing the HTML
+ *
+ * @return \DOMDocumentFragment A DOM fragment. The DOM is part of libxml, which is included with
+ * almost all distributions of PHP.
+ */
+ public function loadHTMLFragment($string, array $options = array())
+ {
+ return $this->parseFragment($string, $options);
+ }
+
+ /**
+ * Return all errors encountered into parsing phase.
+ *
+ * @return array
+ */
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ /**
+ * Return true it some errors were encountered into parsing phase.
+ *
+ * @return bool
+ */
+ public function hasErrors()
+ {
+ return count($this->errors) > 0;
+ }
+
+ /**
+ * Parse an input string.
+ *
+ * @param string $input
+ * @param array $options
+ *
+ * @return \DOMDocument
+ */
+ public function parse($input, array $options = array())
+ {
+ $this->errors = array();
+ $options = array_merge($this->defaultOptions, $options);
+ $events = new DOMTreeBuilder(false, $options);
+ $scanner = new Scanner($input, !empty($options['encoding']) ? $options['encoding'] : 'UTF-8');
+ $parser = new Tokenizer($scanner, $events, !empty($options['xmlNamespaces']) ? Tokenizer::CONFORMANT_XML : Tokenizer::CONFORMANT_HTML);
+
+ $parser->parse();
+ $this->errors = $events->getErrors();
+
+ return $events->document();
+ }
+
+ /**
+ * Parse an input stream where the stream is a fragment.
+ *
+ * Lower-level loading function. This requires an input stream instead
+ * of a string, file, or resource.
+ *
+ * @param string $input The input data to parse in the form of a string.
+ * @param array $options An array of options.
+ *
+ * @return \DOMDocumentFragment
+ */
+ public function parseFragment($input, array $options = array())
+ {
+ $options = array_merge($this->defaultOptions, $options);
+ $events = new DOMTreeBuilder(true, $options);
+ $scanner = new Scanner($input, !empty($options['encoding']) ? $options['encoding'] : 'UTF-8');
+ $parser = new Tokenizer($scanner, $events, !empty($options['xmlNamespaces']) ? Tokenizer::CONFORMANT_XML : Tokenizer::CONFORMANT_HTML);
+
+ $parser->parse();
+ $this->errors = $events->getErrors();
+
+ return $events->fragment();
+ }
+
+ /**
+ * Save a DOM into a given file as HTML5.
+ *
+ * @param mixed $dom The DOM to be serialized.
+ * @param string|resource $file The filename to be written or resource to write to.
+ * @param array $options Configuration options when serializing the DOM. These include:
+ * - encode_entities: Text written to the output is escaped by default and not all
+ * entities are encoded. If this is set to true all entities will be encoded.
+ * Defaults to false.
+ */
+ public function save($dom, $file, $options = array())
+ {
+ $close = true;
+ if (is_resource($file)) {
+ $stream = $file;
+ $close = false;
+ } else {
+ $stream = fopen($file, 'wb');
+ }
+ $options = array_merge($this->defaultOptions, $options);
+ $rules = new OutputRules($stream, $options);
+ $trav = new Traverser($dom, $stream, $rules, $options);
+
+ $trav->walk();
+ /*
+ * release the traverser to avoid cyclic references and allow PHP to free memory without waiting for gc_collect_cycles
+ */
+ $rules->unsetTraverser();
+ if ($close) {
+ fclose($stream);
+ }
+ }
+
+ /**
+ * Convert a DOM into an HTML5 string.
+ *
+ * @param mixed $dom The DOM to be serialized.
+ * @param array $options Configuration options when serializing the DOM. These include:
+ * - encode_entities: Text written to the output is escaped by default and not all
+ * entities are encoded. If this is set to true all entities will be encoded.
+ * Defaults to false.
+ *
+ * @return string A HTML5 documented generated from the DOM.
+ */
+ public function saveHTML($dom, $options = array())
+ {
+ $stream = fopen('php://temp', 'wb');
+ $this->save($dom, $stream, array_merge($this->defaultOptions, $options));
+
+ $html = stream_get_contents($stream, -1, 0);
+
+ fclose($stream);
+
+ return $html;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Elements.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Elements.php
new file mode 100644
index 0000000..8fe7987
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Elements.php
@@ -0,0 +1,619 @@
+<?php
+/**
+ * Provide general element functions.
+ */
+
+namespace Masterminds\HTML5;
+
+/**
+ * This class provides general information about HTML5 elements,
+ * including syntactic and semantic issues.
+ * Parsers and serializers can
+ * use this class as a reference point for information about the rules
+ * of various HTML5 elements.
+ *
+ * @todo consider using a bitmask table lookup. There is enough overlap in
+ * naming that this could significantly shrink the size and maybe make it
+ * faster. See the Go teams implementation at https://code.google.com/p/go/source/browse/html/atom.
+ */
+class Elements
+{
+ /**
+ * Indicates an element is described in the specification.
+ */
+ const KNOWN_ELEMENT = 1;
+
+ // From section 8.1.2: "script", "style"
+ // From 8.2.5.4.7 ("in body" insertion mode): "noembed"
+ // From 8.4 "style", "xmp", "iframe", "noembed", "noframes"
+ /**
+ * Indicates the contained text should be processed as raw text.
+ */
+ const TEXT_RAW = 2;
+
+ // From section 8.1.2: "textarea", "title"
+ /**
+ * Indicates the contained text should be processed as RCDATA.
+ */
+ const TEXT_RCDATA = 4;
+
+ /**
+ * Indicates the tag cannot have content.
+ */
+ const VOID_TAG = 8;
+
+ // "address", "article", "aside", "blockquote", "center", "details", "dialog", "dir", "div", "dl",
+ // "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu",
+ // "nav", "ol", "p", "section", "summary", "ul"
+ // "h1", "h2", "h3", "h4", "h5", "h6"
+ // "pre", "listing"
+ // "form"
+ // "plaintext"
+ /**
+ * Indicates that if a previous event is for a P tag, that element
+ * should be considered closed.
+ */
+ const AUTOCLOSE_P = 16;
+
+ /**
+ * Indicates that the text inside is plaintext (pre).
+ */
+ const TEXT_PLAINTEXT = 32;
+
+ // See https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements
+ /**
+ * Indicates that the tag is a block.
+ */
+ const BLOCK_TAG = 64;
+
+ /**
+ * Indicates that the tag allows only inline elements as child nodes.
+ */
+ const BLOCK_ONLY_INLINE = 128;
+
+ /**
+ * The HTML5 elements as defined in http://dev.w3.org/html5/markup/elements.html.
+ *
+ * @var array
+ */
+ public static $html5 = array(
+ 'a' => 1,
+ 'abbr' => 1,
+ 'address' => 65, // NORMAL | BLOCK_TAG
+ 'area' => 9, // NORMAL | VOID_TAG
+ 'article' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'aside' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'audio' => 1, // NORMAL
+ 'b' => 1,
+ 'base' => 9, // NORMAL | VOID_TAG
+ 'bdi' => 1,
+ 'bdo' => 1,
+ 'blockquote' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'body' => 1,
+ 'br' => 9, // NORMAL | VOID_TAG
+ 'button' => 1,
+ 'canvas' => 65, // NORMAL | BLOCK_TAG
+ 'caption' => 1,
+ 'cite' => 1,
+ 'code' => 1,
+ 'col' => 9, // NORMAL | VOID_TAG
+ 'colgroup' => 1,
+ 'command' => 9, // NORMAL | VOID_TAG
+ // "data" => 1, // This is highly experimental and only part of the whatwg spec (not w3c). See https://developer.mozilla.org/en-US/docs/HTML/Element/data
+ 'datalist' => 1,
+ 'dd' => 65, // NORMAL | BLOCK_TAG
+ 'del' => 1,
+ 'details' => 17, // NORMAL | AUTOCLOSE_P,
+ 'dfn' => 1,
+ 'dialog' => 17, // NORMAL | AUTOCLOSE_P,
+ 'div' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'dl' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'dt' => 1,
+ 'em' => 1,
+ 'embed' => 9, // NORMAL | VOID_TAG
+ 'fieldset' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'figcaption' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'figure' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'footer' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'form' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'h1' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'h2' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'h3' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'h4' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'h5' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'h6' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'head' => 1,
+ 'header' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'hgroup' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'hr' => 73, // NORMAL | VOID_TAG
+ 'html' => 1,
+ 'i' => 1,
+ 'iframe' => 3, // NORMAL | TEXT_RAW
+ 'img' => 9, // NORMAL | VOID_TAG
+ 'input' => 9, // NORMAL | VOID_TAG
+ 'kbd' => 1,
+ 'ins' => 1,
+ 'keygen' => 9, // NORMAL | VOID_TAG
+ 'label' => 1,
+ 'legend' => 1,
+ 'li' => 1,
+ 'link' => 9, // NORMAL | VOID_TAG
+ 'map' => 1,
+ 'mark' => 1,
+ 'menu' => 17, // NORMAL | AUTOCLOSE_P,
+ 'meta' => 9, // NORMAL | VOID_TAG
+ 'meter' => 1,
+ 'nav' => 17, // NORMAL | AUTOCLOSE_P,
+ 'noscript' => 65, // NORMAL | BLOCK_TAG
+ 'object' => 1,
+ 'ol' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'optgroup' => 1,
+ 'option' => 1,
+ 'output' => 65, // NORMAL | BLOCK_TAG
+ 'p' => 209, // NORMAL | AUTOCLOSE_P | BLOCK_TAG | BLOCK_ONLY_INLINE
+ 'param' => 9, // NORMAL | VOID_TAG
+ 'pre' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'progress' => 1,
+ 'q' => 1,
+ 'rp' => 1,
+ 'rt' => 1,
+ 'ruby' => 1,
+ 's' => 1,
+ 'samp' => 1,
+ 'script' => 3, // NORMAL | TEXT_RAW
+ 'section' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'select' => 1,
+ 'small' => 1,
+ 'source' => 9, // NORMAL | VOID_TAG
+ 'span' => 1,
+ 'strong' => 1,
+ 'style' => 3, // NORMAL | TEXT_RAW
+ 'sub' => 1,
+ 'summary' => 17, // NORMAL | AUTOCLOSE_P,
+ 'sup' => 1,
+ 'table' => 65, // NORMAL | BLOCK_TAG
+ 'tbody' => 1,
+ 'td' => 1,
+ 'textarea' => 5, // NORMAL | TEXT_RCDATA
+ 'tfoot' => 65, // NORMAL | BLOCK_TAG
+ 'th' => 1,
+ 'thead' => 1,
+ 'time' => 1,
+ 'title' => 5, // NORMAL | TEXT_RCDATA
+ 'tr' => 1,
+ 'track' => 9, // NORMAL | VOID_TAG
+ 'u' => 1,
+ 'ul' => 81, // NORMAL | AUTOCLOSE_P | BLOCK_TAG
+ 'var' => 1,
+ 'video' => 65, // NORMAL | BLOCK_TAG
+ 'wbr' => 9, // NORMAL | VOID_TAG
+
+ // Legacy?
+ 'basefont' => 8, // VOID_TAG
+ 'bgsound' => 8, // VOID_TAG
+ 'noframes' => 2, // RAW_TEXT
+ 'frame' => 9, // NORMAL | VOID_TAG
+ 'frameset' => 1,
+ 'center' => 16,
+ 'dir' => 16,
+ 'listing' => 16, // AUTOCLOSE_P
+ 'plaintext' => 48, // AUTOCLOSE_P | TEXT_PLAINTEXT
+ 'applet' => 0,
+ 'marquee' => 0,
+ 'isindex' => 8, // VOID_TAG
+ 'xmp' => 20, // AUTOCLOSE_P | VOID_TAG | RAW_TEXT
+ 'noembed' => 2, // RAW_TEXT
+ );
+
+ /**
+ * The MathML elements.
+ * See http://www.w3.org/wiki/MathML/Elements.
+ *
+ * In our case we are only concerned with presentation MathML and not content
+ * MathML. There is a nice list of this subset at https://developer.mozilla.org/en-US/docs/MathML/Element.
+ *
+ * @var array
+ */
+ public static $mathml = array(
+ 'maction' => 1,
+ 'maligngroup' => 1,
+ 'malignmark' => 1,
+ 'math' => 1,
+ 'menclose' => 1,
+ 'merror' => 1,
+ 'mfenced' => 1,
+ 'mfrac' => 1,
+ 'mglyph' => 1,
+ 'mi' => 1,
+ 'mlabeledtr' => 1,
+ 'mlongdiv' => 1,
+ 'mmultiscripts' => 1,
+ 'mn' => 1,
+ 'mo' => 1,
+ 'mover' => 1,
+ 'mpadded' => 1,
+ 'mphantom' => 1,
+ 'mroot' => 1,
+ 'mrow' => 1,
+ 'ms' => 1,
+ 'mscarries' => 1,
+ 'mscarry' => 1,
+ 'msgroup' => 1,
+ 'msline' => 1,
+ 'mspace' => 1,
+ 'msqrt' => 1,
+ 'msrow' => 1,
+ 'mstack' => 1,
+ 'mstyle' => 1,
+ 'msub' => 1,
+ 'msup' => 1,
+ 'msubsup' => 1,
+ 'mtable' => 1,
+ 'mtd' => 1,
+ 'mtext' => 1,
+ 'mtr' => 1,
+ 'munder' => 1,
+ 'munderover' => 1,
+ );
+
+ /**
+ * The svg elements.
+ *
+ * The Mozilla documentation has a good list at https://developer.mozilla.org/en-US/docs/SVG/Element.
+ * The w3c list appears to be lacking in some areas like filter effect elements.
+ * That list can be found at http://www.w3.org/wiki/SVG/Elements.
+ *
+ * Note, FireFox appears to do a better job rendering filter effects than chrome.
+ * While they are in the spec I'm not sure how widely implemented they are.
+ *
+ * @var array
+ */
+ public static $svg = array(
+ 'a' => 1,
+ 'altGlyph' => 1,
+ 'altGlyphDef' => 1,
+ 'altGlyphItem' => 1,
+ 'animate' => 1,
+ 'animateColor' => 1,
+ 'animateMotion' => 1,
+ 'animateTransform' => 1,
+ 'circle' => 1,
+ 'clipPath' => 1,
+ 'color-profile' => 1,
+ 'cursor' => 1,
+ 'defs' => 1,
+ 'desc' => 1,
+ 'ellipse' => 1,
+ 'feBlend' => 1,
+ 'feColorMatrix' => 1,
+ 'feComponentTransfer' => 1,
+ 'feComposite' => 1,
+ 'feConvolveMatrix' => 1,
+ 'feDiffuseLighting' => 1,
+ 'feDisplacementMap' => 1,
+ 'feDistantLight' => 1,
+ 'feFlood' => 1,
+ 'feFuncA' => 1,
+ 'feFuncB' => 1,
+ 'feFuncG' => 1,
+ 'feFuncR' => 1,
+ 'feGaussianBlur' => 1,
+ 'feImage' => 1,
+ 'feMerge' => 1,
+ 'feMergeNode' => 1,
+ 'feMorphology' => 1,
+ 'feOffset' => 1,
+ 'fePointLight' => 1,
+ 'feSpecularLighting' => 1,
+ 'feSpotLight' => 1,
+ 'feTile' => 1,
+ 'feTurbulence' => 1,
+ 'filter' => 1,
+ 'font' => 1,
+ 'font-face' => 1,
+ 'font-face-format' => 1,
+ 'font-face-name' => 1,
+ 'font-face-src' => 1,
+ 'font-face-uri' => 1,
+ 'foreignObject' => 1,
+ 'g' => 1,
+ 'glyph' => 1,
+ 'glyphRef' => 1,
+ 'hkern' => 1,
+ 'image' => 1,
+ 'line' => 1,
+ 'linearGradient' => 1,
+ 'marker' => 1,
+ 'mask' => 1,
+ 'metadata' => 1,
+ 'missing-glyph' => 1,
+ 'mpath' => 1,
+ 'path' => 1,
+ 'pattern' => 1,
+ 'polygon' => 1,
+ 'polyline' => 1,
+ 'radialGradient' => 1,
+ 'rect' => 1,
+ 'script' => 3, // NORMAL | RAW_TEXT
+ 'set' => 1,
+ 'stop' => 1,
+ 'style' => 3, // NORMAL | RAW_TEXT
+ 'svg' => 1,
+ 'switch' => 1,
+ 'symbol' => 1,
+ 'text' => 1,
+ 'textPath' => 1,
+ 'title' => 1,
+ 'tref' => 1,
+ 'tspan' => 1,
+ 'use' => 1,
+ 'view' => 1,
+ 'vkern' => 1,
+ );
+
+ /**
+ * Some attributes in SVG are case sensitive.
+ *
+ * This map contains key/value pairs with the key as the lowercase attribute
+ * name and the value with the correct casing.
+ */
+ public static $svgCaseSensitiveAttributeMap = array(
+ 'attributename' => 'attributeName',
+ 'attributetype' => 'attributeType',
+ 'basefrequency' => 'baseFrequency',
+ 'baseprofile' => 'baseProfile',
+ 'calcmode' => 'calcMode',
+ 'clippathunits' => 'clipPathUnits',
+ 'contentscripttype' => 'contentScriptType',
+ 'contentstyletype' => 'contentStyleType',
+ 'diffuseconstant' => 'diffuseConstant',
+ 'edgemode' => 'edgeMode',
+ 'externalresourcesrequired' => 'externalResourcesRequired',
+ 'filterres' => 'filterRes',
+ 'filterunits' => 'filterUnits',
+ 'glyphref' => 'glyphRef',
+ 'gradienttransform' => 'gradientTransform',
+ 'gradientunits' => 'gradientUnits',
+ 'kernelmatrix' => 'kernelMatrix',
+ 'kernelunitlength' => 'kernelUnitLength',
+ 'keypoints' => 'keyPoints',
+ 'keysplines' => 'keySplines',
+ 'keytimes' => 'keyTimes',
+ 'lengthadjust' => 'lengthAdjust',
+ 'limitingconeangle' => 'limitingConeAngle',
+ 'markerheight' => 'markerHeight',
+ 'markerunits' => 'markerUnits',
+ 'markerwidth' => 'markerWidth',
+ 'maskcontentunits' => 'maskContentUnits',
+ 'maskunits' => 'maskUnits',
+ 'numoctaves' => 'numOctaves',
+ 'pathlength' => 'pathLength',
+ 'patterncontentunits' => 'patternContentUnits',
+ 'patterntransform' => 'patternTransform',
+ 'patternunits' => 'patternUnits',
+ 'pointsatx' => 'pointsAtX',
+ 'pointsaty' => 'pointsAtY',
+ 'pointsatz' => 'pointsAtZ',
+ 'preservealpha' => 'preserveAlpha',
+ 'preserveaspectratio' => 'preserveAspectRatio',
+ 'primitiveunits' => 'primitiveUnits',
+ 'refx' => 'refX',
+ 'refy' => 'refY',
+ 'repeatcount' => 'repeatCount',
+ 'repeatdur' => 'repeatDur',
+ 'requiredextensions' => 'requiredExtensions',
+ 'requiredfeatures' => 'requiredFeatures',
+ 'specularconstant' => 'specularConstant',
+ 'specularexponent' => 'specularExponent',
+ 'spreadmethod' => 'spreadMethod',
+ 'startoffset' => 'startOffset',
+ 'stddeviation' => 'stdDeviation',
+ 'stitchtiles' => 'stitchTiles',
+ 'surfacescale' => 'surfaceScale',
+ 'systemlanguage' => 'systemLanguage',
+ 'tablevalues' => 'tableValues',
+ 'targetx' => 'targetX',
+ 'targety' => 'targetY',
+ 'textlength' => 'textLength',
+ 'viewbox' => 'viewBox',
+ 'viewtarget' => 'viewTarget',
+ 'xchannelselector' => 'xChannelSelector',
+ 'ychannelselector' => 'yChannelSelector',
+ 'zoomandpan' => 'zoomAndPan',
+ );
+
+ /**
+ * Some SVG elements are case sensitive.
+ * This map contains these.
+ *
+ * The map contains key/value store of the name is lowercase as the keys and
+ * the correct casing as the value.
+ */
+ public static $svgCaseSensitiveElementMap = array(
+ 'altglyph' => 'altGlyph',
+ 'altglyphdef' => 'altGlyphDef',
+ 'altglyphitem' => 'altGlyphItem',
+ 'animatecolor' => 'animateColor',
+ 'animatemotion' => 'animateMotion',
+ 'animatetransform' => 'animateTransform',
+ 'clippath' => 'clipPath',
+ 'feblend' => 'feBlend',
+ 'fecolormatrix' => 'feColorMatrix',
+ 'fecomponenttransfer' => 'feComponentTransfer',
+ 'fecomposite' => 'feComposite',
+ 'feconvolvematrix' => 'feConvolveMatrix',
+ 'fediffuselighting' => 'feDiffuseLighting',
+ 'fedisplacementmap' => 'feDisplacementMap',
+ 'fedistantlight' => 'feDistantLight',
+ 'feflood' => 'feFlood',
+ 'fefunca' => 'feFuncA',
+ 'fefuncb' => 'feFuncB',
+ 'fefuncg' => 'feFuncG',
+ 'fefuncr' => 'feFuncR',
+ 'fegaussianblur' => 'feGaussianBlur',
+ 'feimage' => 'feImage',
+ 'femerge' => 'feMerge',
+ 'femergenode' => 'feMergeNode',
+ 'femorphology' => 'feMorphology',
+ 'feoffset' => 'feOffset',
+ 'fepointlight' => 'fePointLight',
+ 'fespecularlighting' => 'feSpecularLighting',
+ 'fespotlight' => 'feSpotLight',
+ 'fetile' => 'feTile',
+ 'feturbulence' => 'feTurbulence',
+ 'foreignobject' => 'foreignObject',
+ 'glyphref' => 'glyphRef',
+ 'lineargradient' => 'linearGradient',
+ 'radialgradient' => 'radialGradient',
+ 'textpath' => 'textPath',
+ );
+
+ /**
+ * Check whether the given element meets the given criterion.
+ *
+ * Example:
+ *
+ * Elements::isA('script', Elements::TEXT_RAW); // Returns true.
+ *
+ * Elements::isA('script', Elements::TEXT_RCDATA); // Returns false.
+ *
+ * @param string $name The element name.
+ * @param int $mask One of the constants on this class.
+ *
+ * @return bool true if the element matches the mask, false otherwise.
+ */
+ public static function isA($name, $mask)
+ {
+ return (static::element($name) & $mask) === $mask;
+ }
+
+ /**
+ * Test if an element is a valid html5 element.
+ *
+ * @param string $name The name of the element.
+ *
+ * @return bool true if a html5 element and false otherwise.
+ */
+ public static function isHtml5Element($name)
+ {
+ // html5 element names are case insensitive. Forcing lowercase for the check.
+ // Do we need this check or will all data passed here already be lowercase?
+ return isset(static::$html5[strtolower($name)]);
+ }
+
+ /**
+ * Test if an element name is a valid MathML presentation element.
+ *
+ * @param string $name The name of the element.
+ *
+ * @return bool true if a MathML name and false otherwise.
+ */
+ public static function isMathMLElement($name)
+ {
+ // MathML is case-sensitive unlike html5 elements.
+ return isset(static::$mathml[$name]);
+ }
+
+ /**
+ * Test if an element is a valid SVG element.
+ *
+ * @param string $name The name of the element.
+ *
+ * @return bool true if a SVG element and false otherise.
+ */
+ public static function isSvgElement($name)
+ {
+ // SVG is case-sensitive unlike html5 elements.
+ return isset(static::$svg[$name]);
+ }
+
+ /**
+ * Is an element name valid in an html5 document.
+ * This includes html5 elements along with other allowed embedded content
+ * such as svg and mathml.
+ *
+ * @param string $name The name of the element.
+ *
+ * @return bool true if valid and false otherwise.
+ */
+ public static function isElement($name)
+ {
+ return static::isHtml5Element($name) || static::isMathMLElement($name) || static::isSvgElement($name);
+ }
+
+ /**
+ * Get the element mask for the given element name.
+ *
+ * @param string $name The name of the element.
+ *
+ * @return int the element mask.
+ */
+ public static function element($name)
+ {
+ if (isset(static::$html5[$name])) {
+ return static::$html5[$name];
+ }
+ if (isset(static::$svg[$name])) {
+ return static::$svg[$name];
+ }
+ if (isset(static::$mathml[$name])) {
+ return static::$mathml[$name];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Normalize a SVG element name to its proper case and form.
+ *
+ * @param string $name The name of the element.
+ *
+ * @return string the normalized form of the element name.
+ */
+ public static function normalizeSvgElement($name)
+ {
+ $name = strtolower($name);
+ if (isset(static::$svgCaseSensitiveElementMap[$name])) {
+ $name = static::$svgCaseSensitiveElementMap[$name];
+ }
+
+ return $name;
+ }
+
+ /**
+ * Normalize a SVG attribute name to its proper case and form.
+ *
+ * @param string $name The name of the attribute.
+ *
+ * @return string The normalized form of the attribute name.
+ */
+ public static function normalizeSvgAttribute($name)
+ {
+ $name = strtolower($name);
+ if (isset(static::$svgCaseSensitiveAttributeMap[$name])) {
+ $name = static::$svgCaseSensitiveAttributeMap[$name];
+ }
+
+ return $name;
+ }
+
+ /**
+ * Normalize a MathML attribute name to its proper case and form.
+ * Note, all MathML element names are lowercase.
+ *
+ * @param string $name The name of the attribute.
+ *
+ * @return string The normalized form of the attribute name.
+ */
+ public static function normalizeMathMlAttribute($name)
+ {
+ $name = strtolower($name);
+
+ // Only one attribute has a mixed case form for MathML.
+ if ('definitionurl' === $name) {
+ $name = 'definitionURL';
+ }
+
+ return $name;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Entities.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Entities.php
new file mode 100644
index 0000000..0e7227d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Entities.php
@@ -0,0 +1,2236 @@
+<?php
+
+namespace Masterminds\HTML5;
+
+/**
+ * Entity lookup tables.
+ * This class is automatically generated.
+ */
+class Entities
+{
+ public static $byName = array(
+ 'Aacute' => 'Ã',
+ 'Aacut' => 'Ã',
+ 'aacute' => 'á',
+ 'aacut' => 'á',
+ 'Abreve' => 'Ä‚',
+ 'abreve' => 'ă',
+ 'ac' => '∾',
+ 'acd' => '∿',
+ 'acE' => '∾̳',
+ 'Acirc' => 'Â',
+ 'Acir' => 'Â',
+ 'acirc' => 'â',
+ 'acir' => 'â',
+ 'acute' => '´',
+ 'acut' => '´',
+ 'Acy' => 'Ð',
+ 'acy' => 'а',
+ 'AElig' => 'Æ',
+ 'AEli' => 'Æ',
+ 'aelig' => 'æ',
+ 'aeli' => 'æ',
+ 'af' => 'â¡',
+ 'Afr' => 'ð”„',
+ 'afr' => 'ð”ž',
+ 'Agrave' => 'À',
+ 'Agrav' => 'À',
+ 'agrave' => 'à',
+ 'agrav' => 'à',
+ 'alefsym' => 'ℵ',
+ 'aleph' => 'ℵ',
+ 'Alpha' => 'Α',
+ 'alpha' => 'α',
+ 'Amacr' => 'Ä€',
+ 'amacr' => 'Ä',
+ 'amalg' => '⨿',
+ 'AMP' => '&',
+ 'AM' => '&',
+ 'amp' => '&',
+ 'am' => '&',
+ 'And' => 'â©“',
+ 'and' => '∧',
+ 'andand' => 'â©•',
+ 'andd' => '⩜',
+ 'andslope' => '⩘',
+ 'andv' => 'â©š',
+ 'ang' => '∠',
+ 'ange' => '⦤',
+ 'angle' => '∠',
+ 'angmsd' => '∡',
+ 'angmsdaa' => '⦨',
+ 'angmsdab' => '⦩',
+ 'angmsdac' => '⦪',
+ 'angmsdad' => '⦫',
+ 'angmsdae' => '⦬',
+ 'angmsdaf' => '⦭',
+ 'angmsdag' => '⦮',
+ 'angmsdah' => '⦯',
+ 'angrt' => '∟',
+ 'angrtvb' => '⊾',
+ 'angrtvbd' => 'â¦',
+ 'angsph' => '∢',
+ 'angst' => 'Ã…',
+ 'angzarr' => 'â¼',
+ 'Aogon' => 'Ä„',
+ 'aogon' => 'Ä…',
+ 'Aopf' => 'ð”¸',
+ 'aopf' => 'ð•’',
+ 'ap' => '≈',
+ 'apacir' => '⩯',
+ 'apE' => 'â©°',
+ 'ape' => '≊',
+ 'apid' => '≋',
+ 'apos' => '\'',
+ 'ApplyFunction' => 'â¡',
+ 'approx' => '≈',
+ 'approxeq' => '≊',
+ 'Aring' => 'Ã…',
+ 'Arin' => 'Ã…',
+ 'aring' => 'Ã¥',
+ 'arin' => 'Ã¥',
+ 'Ascr' => 'ð’œ',
+ 'ascr' => 'ð’¶',
+ 'Assign' => '≔',
+ 'ast' => '*',
+ 'asymp' => '≈',
+ 'asympeq' => 'â‰',
+ 'Atilde' => 'Ã',
+ 'Atild' => 'Ã',
+ 'atilde' => 'ã',
+ 'atild' => 'ã',
+ 'Auml' => 'Ä',
+ 'Aum' => 'Ä',
+ 'auml' => 'ä',
+ 'aum' => 'ä',
+ 'awconint' => '∳',
+ 'awint' => '⨑',
+ 'backcong' => '≌',
+ 'backepsilon' => '϶',
+ 'backprime' => '‵',
+ 'backsim' => '∽',
+ 'backsimeq' => 'â‹',
+ 'Backslash' => '∖',
+ 'Barv' => '⫧',
+ 'barvee' => '⊽',
+ 'Barwed' => '⌆',
+ 'barwed' => '⌅',
+ 'barwedge' => '⌅',
+ 'bbrk' => '⎵',
+ 'bbrktbrk' => '⎶',
+ 'bcong' => '≌',
+ 'Bcy' => 'Б',
+ 'bcy' => 'б',
+ 'bdquo' => '„',
+ 'becaus' => '∵',
+ 'Because' => '∵',
+ 'because' => '∵',
+ 'bemptyv' => '⦰',
+ 'bepsi' => '϶',
+ 'bernou' => 'ℬ',
+ 'Bernoullis' => 'ℬ',
+ 'Beta' => 'Î’',
+ 'beta' => 'β',
+ 'beth' => 'ℶ',
+ 'between' => '≬',
+ 'Bfr' => 'ð”…',
+ 'bfr' => 'ð”Ÿ',
+ 'bigcap' => 'â‹‚',
+ 'bigcirc' => 'â—¯',
+ 'bigcup' => '⋃',
+ 'bigodot' => '⨀',
+ 'bigoplus' => 'â¨',
+ 'bigotimes' => '⨂',
+ 'bigsqcup' => '⨆',
+ 'bigstar' => '★',
+ 'bigtriangledown' => 'â–½',
+ 'bigtriangleup' => 'â–³',
+ 'biguplus' => '⨄',
+ 'bigvee' => 'â‹',
+ 'bigwedge' => 'â‹€',
+ 'bkarow' => 'â¤',
+ 'blacklozenge' => '⧫',
+ 'blacksquare' => 'â–ª',
+ 'blacktriangle' => 'â–´',
+ 'blacktriangledown' => 'â–¾',
+ 'blacktriangleleft' => 'â—‚',
+ 'blacktriangleright' => 'â–¸',
+ 'blank' => 'â£',
+ 'blk12' => 'â–’',
+ 'blk14' => 'â–‘',
+ 'blk34' => 'â–“',
+ 'block' => 'â–ˆ',
+ 'bne' => '=⃥',
+ 'bnequiv' => '≡⃥',
+ 'bNot' => 'â«­',
+ 'bnot' => 'âŒ',
+ 'Bopf' => 'ð”¹',
+ 'bopf' => 'ð•“',
+ 'bot' => '⊥',
+ 'bottom' => '⊥',
+ 'bowtie' => '⋈',
+ 'boxbox' => '⧉',
+ 'boxDL' => 'â•—',
+ 'boxDl' => 'â•–',
+ 'boxdL' => 'â••',
+ 'boxdl' => 'â”',
+ 'boxDR' => 'â•”',
+ 'boxDr' => 'â•“',
+ 'boxdR' => 'â•’',
+ 'boxdr' => '┌',
+ 'boxH' => 'â•',
+ 'boxh' => '─',
+ 'boxHD' => '╦',
+ 'boxHd' => '╤',
+ 'boxhD' => 'â•¥',
+ 'boxhd' => '┬',
+ 'boxHU' => 'â•©',
+ 'boxHu' => '╧',
+ 'boxhU' => '╨',
+ 'boxhu' => 'â”´',
+ 'boxminus' => '⊟',
+ 'boxplus' => '⊞',
+ 'boxtimes' => '⊠',
+ 'boxUL' => 'â•',
+ 'boxUl' => '╜',
+ 'boxuL' => 'â•›',
+ 'boxul' => '┘',
+ 'boxUR' => 'â•š',
+ 'boxUr' => 'â•™',
+ 'boxuR' => '╘',
+ 'boxur' => 'â””',
+ 'boxV' => 'â•‘',
+ 'boxv' => '│',
+ 'boxVH' => '╬',
+ 'boxVh' => 'â•«',
+ 'boxvH' => '╪',
+ 'boxvh' => '┼',
+ 'boxVL' => 'â•£',
+ 'boxVl' => 'â•¢',
+ 'boxvL' => 'â•¡',
+ 'boxvl' => '┤',
+ 'boxVR' => 'â• ',
+ 'boxVr' => 'â•Ÿ',
+ 'boxvR' => 'â•ž',
+ 'boxvr' => '├',
+ 'bprime' => '‵',
+ 'Breve' => '˘',
+ 'breve' => '˘',
+ 'brvbar' => '¦',
+ 'brvba' => '¦',
+ 'Bscr' => 'ℬ',
+ 'bscr' => 'ð’·',
+ 'bsemi' => 'â',
+ 'bsim' => '∽',
+ 'bsime' => 'â‹',
+ 'bsol' => '\\',
+ 'bsolb' => '⧅',
+ 'bsolhsub' => '⟈',
+ 'bull' => '•',
+ 'bullet' => '•',
+ 'bump' => '≎',
+ 'bumpE' => '⪮',
+ 'bumpe' => 'â‰',
+ 'Bumpeq' => '≎',
+ 'bumpeq' => 'â‰',
+ 'Cacute' => 'Ć',
+ 'cacute' => 'ć',
+ 'Cap' => 'â‹’',
+ 'cap' => '∩',
+ 'capand' => 'â©„',
+ 'capbrcup' => '⩉',
+ 'capcap' => 'â©‹',
+ 'capcup' => '⩇',
+ 'capdot' => 'â©€',
+ 'CapitalDifferentialD' => 'â……',
+ 'caps' => '∩︀',
+ 'caret' => 'â',
+ 'caron' => 'ˇ',
+ 'Cayleys' => 'â„­',
+ 'ccaps' => 'â©',
+ 'Ccaron' => 'Č',
+ 'ccaron' => 'Ä',
+ 'Ccedil' => 'Ç',
+ 'Ccedi' => 'Ç',
+ 'ccedil' => 'ç',
+ 'ccedi' => 'ç',
+ 'Ccirc' => 'Ĉ',
+ 'ccirc' => 'ĉ',
+ 'Cconint' => '∰',
+ 'ccups' => '⩌',
+ 'ccupssm' => 'â©',
+ 'Cdot' => 'ÄŠ',
+ 'cdot' => 'Ä‹',
+ 'cedil' => '¸',
+ 'cedi' => '¸',
+ 'Cedilla' => '¸',
+ 'cemptyv' => '⦲',
+ 'cent' => '¢',
+ 'cen' => '¢',
+ 'CenterDot' => '·',
+ 'centerdot' => '·',
+ 'Cfr' => 'â„­',
+ 'cfr' => 'ð” ',
+ 'CHcy' => 'Ч',
+ 'chcy' => 'ч',
+ 'check' => '✓',
+ 'checkmark' => '✓',
+ 'Chi' => 'Χ',
+ 'chi' => 'χ',
+ 'cir' => 'â—‹',
+ 'circ' => 'ˆ',
+ 'circeq' => '≗',
+ 'circlearrowleft' => '↺',
+ 'circlearrowright' => '↻',
+ 'circledast' => '⊛',
+ 'circledcirc' => '⊚',
+ 'circleddash' => 'âŠ',
+ 'CircleDot' => '⊙',
+ 'circledR' => '®',
+ 'circledS' => 'Ⓢ',
+ 'CircleMinus' => '⊖',
+ 'CirclePlus' => '⊕',
+ 'CircleTimes' => '⊗',
+ 'cirE' => '⧃',
+ 'cire' => '≗',
+ 'cirfnint' => 'â¨',
+ 'cirmid' => '⫯',
+ 'cirscir' => '⧂',
+ 'ClockwiseContourIntegral' => '∲',
+ 'CloseCurlyDoubleQuote' => 'â€',
+ 'CloseCurlyQuote' => '’',
+ 'clubs' => '♣',
+ 'clubsuit' => '♣',
+ 'Colon' => '∷',
+ 'colon' => ':',
+ 'Colone' => 'â©´',
+ 'colone' => '≔',
+ 'coloneq' => '≔',
+ 'comma' => ',',
+ 'commat' => '@',
+ 'comp' => 'âˆ',
+ 'compfn' => '∘',
+ 'complement' => 'âˆ',
+ 'complexes' => 'â„‚',
+ 'cong' => '≅',
+ 'congdot' => 'â©­',
+ 'Congruent' => '≡',
+ 'Conint' => '∯',
+ 'conint' => '∮',
+ 'ContourIntegral' => '∮',
+ 'Copf' => 'â„‚',
+ 'copf' => 'ð•”',
+ 'coprod' => 'âˆ',
+ 'Coproduct' => 'âˆ',
+ 'COPY' => '©',
+ 'COP' => '©',
+ 'copy' => '©',
+ 'cop' => '©',
+ 'copysr' => 'â„—',
+ 'CounterClockwiseContourIntegral' => '∳',
+ 'crarr' => '↵',
+ 'Cross' => '⨯',
+ 'cross' => '✗',
+ 'Cscr' => 'ð’ž',
+ 'cscr' => 'ð’¸',
+ 'csub' => 'â«',
+ 'csube' => 'â«‘',
+ 'csup' => 'â«',
+ 'csupe' => 'â«’',
+ 'ctdot' => '⋯',
+ 'cudarrl' => '⤸',
+ 'cudarrr' => '⤵',
+ 'cuepr' => 'â‹ž',
+ 'cuesc' => 'â‹Ÿ',
+ 'cularr' => '↶',
+ 'cularrp' => '⤽',
+ 'Cup' => 'â‹“',
+ 'cup' => '∪',
+ 'cupbrcap' => '⩈',
+ 'CupCap' => 'â‰',
+ 'cupcap' => '⩆',
+ 'cupcup' => 'â©Š',
+ 'cupdot' => 'âŠ',
+ 'cupor' => 'â©…',
+ 'cups' => '∪︀',
+ 'curarr' => '↷',
+ 'curarrm' => '⤼',
+ 'curlyeqprec' => 'â‹ž',
+ 'curlyeqsucc' => 'â‹Ÿ',
+ 'curlyvee' => 'â‹Ž',
+ 'curlywedge' => 'â‹',
+ 'curren' => '¤',
+ 'curre' => '¤',
+ 'curvearrowleft' => '↶',
+ 'curvearrowright' => '↷',
+ 'cuvee' => 'â‹Ž',
+ 'cuwed' => 'â‹',
+ 'cwconint' => '∲',
+ 'cwint' => '∱',
+ 'cylcty' => '⌭',
+ 'Dagger' => '‡',
+ 'dagger' => '†',
+ 'daleth' => 'ℸ',
+ 'Darr' => '↡',
+ 'dArr' => '⇓',
+ 'darr' => '↓',
+ 'dash' => 'â€',
+ 'Dashv' => '⫤',
+ 'dashv' => '⊣',
+ 'dbkarow' => 'â¤',
+ 'dblac' => 'Ë',
+ 'Dcaron' => 'ÄŽ',
+ 'dcaron' => 'Ä',
+ 'Dcy' => 'Д',
+ 'dcy' => 'д',
+ 'DD' => 'â……',
+ 'dd' => 'â…†',
+ 'ddagger' => '‡',
+ 'ddarr' => '⇊',
+ 'DDotrahd' => '⤑',
+ 'ddotseq' => 'â©·',
+ 'deg' => '°',
+ 'de' => '°',
+ 'Del' => '∇',
+ 'Delta' => 'Δ',
+ 'delta' => 'δ',
+ 'demptyv' => '⦱',
+ 'dfisht' => '⥿',
+ 'Dfr' => 'ð”‡',
+ 'dfr' => 'ð”¡',
+ 'dHar' => '⥥',
+ 'dharl' => '⇃',
+ 'dharr' => '⇂',
+ 'DiacriticalAcute' => '´',
+ 'DiacriticalDot' => 'Ë™',
+ 'DiacriticalDoubleAcute' => 'Ë',
+ 'DiacriticalGrave' => '`',
+ 'DiacriticalTilde' => '˜',
+ 'diam' => 'â‹„',
+ 'Diamond' => 'â‹„',
+ 'diamond' => 'â‹„',
+ 'diamondsuit' => '♦',
+ 'diams' => '♦',
+ 'die' => '¨',
+ 'DifferentialD' => 'â…†',
+ 'digamma' => 'Ï',
+ 'disin' => '⋲',
+ 'div' => '÷',
+ 'divide' => '÷',
+ 'divid' => '÷',
+ 'divideontimes' => '⋇',
+ 'divonx' => '⋇',
+ 'DJcy' => 'Ђ',
+ 'djcy' => 'Ñ’',
+ 'dlcorn' => '⌞',
+ 'dlcrop' => 'âŒ',
+ 'dollar' => '$',
+ 'Dopf' => 'ð”»',
+ 'dopf' => 'ð••',
+ 'Dot' => '¨',
+ 'dot' => 'Ë™',
+ 'DotDot' => '⃜',
+ 'doteq' => 'â‰',
+ 'doteqdot' => '≑',
+ 'DotEqual' => 'â‰',
+ 'dotminus' => '∸',
+ 'dotplus' => '∔',
+ 'dotsquare' => '⊡',
+ 'doublebarwedge' => '⌆',
+ 'DoubleContourIntegral' => '∯',
+ 'DoubleDot' => '¨',
+ 'DoubleDownArrow' => '⇓',
+ 'DoubleLeftArrow' => 'â‡',
+ 'DoubleLeftRightArrow' => '⇔',
+ 'DoubleLeftTee' => '⫤',
+ 'DoubleLongLeftArrow' => '⟸',
+ 'DoubleLongLeftRightArrow' => '⟺',
+ 'DoubleLongRightArrow' => '⟹',
+ 'DoubleRightArrow' => '⇒',
+ 'DoubleRightTee' => '⊨',
+ 'DoubleUpArrow' => '⇑',
+ 'DoubleUpDownArrow' => '⇕',
+ 'DoubleVerticalBar' => '∥',
+ 'DownArrow' => '↓',
+ 'Downarrow' => '⇓',
+ 'downarrow' => '↓',
+ 'DownArrowBar' => '⤓',
+ 'DownArrowUpArrow' => '⇵',
+ 'DownBreve' => 'Ì‘',
+ 'downdownarrows' => '⇊',
+ 'downharpoonleft' => '⇃',
+ 'downharpoonright' => '⇂',
+ 'DownLeftRightVector' => 'â¥',
+ 'DownLeftTeeVector' => '⥞',
+ 'DownLeftVector' => '↽',
+ 'DownLeftVectorBar' => '⥖',
+ 'DownRightTeeVector' => '⥟',
+ 'DownRightVector' => 'â‡',
+ 'DownRightVectorBar' => '⥗',
+ 'DownTee' => '⊤',
+ 'DownTeeArrow' => '↧',
+ 'drbkarow' => 'â¤',
+ 'drcorn' => '⌟',
+ 'drcrop' => '⌌',
+ 'Dscr' => 'ð’Ÿ',
+ 'dscr' => 'ð’¹',
+ 'DScy' => 'Ð…',
+ 'dscy' => 'Ñ•',
+ 'dsol' => '⧶',
+ 'Dstrok' => 'Ä',
+ 'dstrok' => 'Ä‘',
+ 'dtdot' => '⋱',
+ 'dtri' => 'â–¿',
+ 'dtrif' => 'â–¾',
+ 'duarr' => '⇵',
+ 'duhar' => '⥯',
+ 'dwangle' => '⦦',
+ 'DZcy' => 'Ð',
+ 'dzcy' => 'ÑŸ',
+ 'dzigrarr' => '⟿',
+ 'Eacute' => 'É',
+ 'Eacut' => 'É',
+ 'eacute' => 'é',
+ 'eacut' => 'é',
+ 'easter' => 'â©®',
+ 'Ecaron' => 'Äš',
+ 'ecaron' => 'Ä›',
+ 'ecir' => 'ê',
+ 'Ecirc' => 'Ê',
+ 'Ecir' => 'Ê',
+ 'ecirc' => 'ê',
+ 'ecolon' => '≕',
+ 'Ecy' => 'Э',
+ 'ecy' => 'Ñ',
+ 'eDDot' => 'â©·',
+ 'Edot' => 'Ä–',
+ 'eDot' => '≑',
+ 'edot' => 'Ä—',
+ 'ee' => 'â…‡',
+ 'efDot' => '≒',
+ 'Efr' => 'ð”ˆ',
+ 'efr' => 'ð”¢',
+ 'eg' => '⪚',
+ 'Egrave' => 'È',
+ 'Egrav' => 'È',
+ 'egrave' => 'è',
+ 'egrav' => 'è',
+ 'egs' => '⪖',
+ 'egsdot' => '⪘',
+ 'el' => '⪙',
+ 'Element' => '∈',
+ 'elinters' => 'â§',
+ 'ell' => 'â„“',
+ 'els' => '⪕',
+ 'elsdot' => '⪗',
+ 'Emacr' => 'Ä’',
+ 'emacr' => 'Ä“',
+ 'empty' => '∅',
+ 'emptyset' => '∅',
+ 'EmptySmallSquare' => 'â—»',
+ 'emptyv' => '∅',
+ 'EmptyVerySmallSquare' => 'â–«',
+ 'emsp' => ' ',
+ 'emsp13' => ' ',
+ 'emsp14' => ' ',
+ 'ENG' => 'ÅŠ',
+ 'eng' => 'Å‹',
+ 'ensp' => ' ',
+ 'Eogon' => 'Ę',
+ 'eogon' => 'Ä™',
+ 'Eopf' => 'ð”¼',
+ 'eopf' => 'ð•–',
+ 'epar' => 'â‹•',
+ 'eparsl' => '⧣',
+ 'eplus' => '⩱',
+ 'epsi' => 'ε',
+ 'Epsilon' => 'Ε',
+ 'epsilon' => 'ε',
+ 'epsiv' => 'ϵ',
+ 'eqcirc' => '≖',
+ 'eqcolon' => '≕',
+ 'eqsim' => '≂',
+ 'eqslantgtr' => '⪖',
+ 'eqslantless' => '⪕',
+ 'Equal' => '⩵',
+ 'equals' => '=',
+ 'EqualTilde' => '≂',
+ 'equest' => '≟',
+ 'Equilibrium' => '⇌',
+ 'equiv' => '≡',
+ 'equivDD' => '⩸',
+ 'eqvparsl' => '⧥',
+ 'erarr' => '⥱',
+ 'erDot' => '≓',
+ 'Escr' => 'â„°',
+ 'escr' => 'ℯ',
+ 'esdot' => 'â‰',
+ 'Esim' => '⩳',
+ 'esim' => '≂',
+ 'Eta' => 'Η',
+ 'eta' => 'η',
+ 'ETH' => 'Ã',
+ 'ET' => 'Ã',
+ 'eth' => 'ð',
+ 'et' => 'ð',
+ 'Euml' => 'Ë',
+ 'Eum' => 'Ë',
+ 'euml' => 'ë',
+ 'eum' => 'ë',
+ 'euro' => '€',
+ 'excl' => '!',
+ 'exist' => '∃',
+ 'Exists' => '∃',
+ 'expectation' => 'â„°',
+ 'ExponentialE' => 'â…‡',
+ 'exponentiale' => 'â…‡',
+ 'fallingdotseq' => '≒',
+ 'Fcy' => 'Ф',
+ 'fcy' => 'Ñ„',
+ 'female' => '♀',
+ 'ffilig' => 'ffi',
+ 'fflig' => 'ff',
+ 'ffllig' => 'ffl',
+ 'Ffr' => 'ð”‰',
+ 'ffr' => 'ð”£',
+ 'filig' => 'ï¬',
+ 'FilledSmallSquare' => 'â—¼',
+ 'FilledVerySmallSquare' => 'â–ª',
+ 'fjlig' => 'fj',
+ 'flat' => 'â™­',
+ 'fllig' => 'fl',
+ 'fltns' => 'â–±',
+ 'fnof' => 'Æ’',
+ 'Fopf' => 'ð”½',
+ 'fopf' => 'ð•—',
+ 'ForAll' => '∀',
+ 'forall' => '∀',
+ 'fork' => 'â‹”',
+ 'forkv' => 'â«™',
+ 'Fouriertrf' => 'ℱ',
+ 'fpartint' => 'â¨',
+ 'frac12' => '½',
+ 'frac1' => '¼',
+ 'frac13' => 'â…“',
+ 'frac14' => '¼',
+ 'frac15' => 'â…•',
+ 'frac16' => 'â…™',
+ 'frac18' => 'â…›',
+ 'frac23' => 'â…”',
+ 'frac25' => 'â…–',
+ 'frac34' => '¾',
+ 'frac3' => '¾',
+ 'frac35' => 'â…—',
+ 'frac38' => '⅜',
+ 'frac45' => 'â…˜',
+ 'frac56' => 'â…š',
+ 'frac58' => 'â…',
+ 'frac78' => 'â…ž',
+ 'frasl' => 'â„',
+ 'frown' => '⌢',
+ 'Fscr' => 'ℱ',
+ 'fscr' => 'ð’»',
+ 'gacute' => 'ǵ',
+ 'Gamma' => 'Γ',
+ 'gamma' => 'γ',
+ 'Gammad' => 'Ϝ',
+ 'gammad' => 'Ï',
+ 'gap' => '⪆',
+ 'Gbreve' => 'Äž',
+ 'gbreve' => 'ÄŸ',
+ 'Gcedil' => 'Ä¢',
+ 'Gcirc' => 'Ĝ',
+ 'gcirc' => 'Ä',
+ 'Gcy' => 'Г',
+ 'gcy' => 'г',
+ 'Gdot' => 'Ä ',
+ 'gdot' => 'Ä¡',
+ 'gE' => '≧',
+ 'ge' => '≥',
+ 'gEl' => '⪌',
+ 'gel' => 'â‹›',
+ 'geq' => '≥',
+ 'geqq' => '≧',
+ 'geqslant' => '⩾',
+ 'ges' => '⩾',
+ 'gescc' => '⪩',
+ 'gesdot' => '⪀',
+ 'gesdoto' => '⪂',
+ 'gesdotol' => '⪄',
+ 'gesl' => '⋛︀',
+ 'gesles' => '⪔',
+ 'Gfr' => 'ð”Š',
+ 'gfr' => 'ð”¤',
+ 'Gg' => 'â‹™',
+ 'gg' => '≫',
+ 'ggg' => 'â‹™',
+ 'gimel' => 'â„·',
+ 'GJcy' => 'Ѓ',
+ 'gjcy' => 'Ñ“',
+ 'gl' => '≷',
+ 'gla' => '⪥',
+ 'glE' => '⪒',
+ 'glj' => '⪤',
+ 'gnap' => '⪊',
+ 'gnapprox' => '⪊',
+ 'gnE' => '≩',
+ 'gne' => '⪈',
+ 'gneq' => '⪈',
+ 'gneqq' => '≩',
+ 'gnsim' => '⋧',
+ 'Gopf' => 'ð”¾',
+ 'gopf' => 'ð•˜',
+ 'grave' => '`',
+ 'GreaterEqual' => '≥',
+ 'GreaterEqualLess' => 'â‹›',
+ 'GreaterFullEqual' => '≧',
+ 'GreaterGreater' => '⪢',
+ 'GreaterLess' => '≷',
+ 'GreaterSlantEqual' => '⩾',
+ 'GreaterTilde' => '≳',
+ 'Gscr' => 'ð’¢',
+ 'gscr' => 'â„Š',
+ 'gsim' => '≳',
+ 'gsime' => '⪎',
+ 'gsiml' => 'âª',
+ 'GT' => '>',
+ 'G' => '>',
+ 'Gt' => '≫',
+ 'gt' => '>',
+ 'g' => '>',
+ 'gtcc' => '⪧',
+ 'gtcir' => '⩺',
+ 'gtdot' => 'â‹—',
+ 'gtlPar' => '⦕',
+ 'gtquest' => '⩼',
+ 'gtrapprox' => '⪆',
+ 'gtrarr' => '⥸',
+ 'gtrdot' => 'â‹—',
+ 'gtreqless' => 'â‹›',
+ 'gtreqqless' => '⪌',
+ 'gtrless' => '≷',
+ 'gtrsim' => '≳',
+ 'gvertneqq' => '≩︀',
+ 'gvnE' => '≩︀',
+ 'Hacek' => 'ˇ',
+ 'hairsp' => ' ',
+ 'half' => '½',
+ 'hamilt' => 'â„‹',
+ 'HARDcy' => 'Ъ',
+ 'hardcy' => 'ÑŠ',
+ 'hArr' => '⇔',
+ 'harr' => '↔',
+ 'harrcir' => '⥈',
+ 'harrw' => '↭',
+ 'Hat' => '^',
+ 'hbar' => 'â„',
+ 'Hcirc' => 'Ĥ',
+ 'hcirc' => 'Ä¥',
+ 'hearts' => '♥',
+ 'heartsuit' => '♥',
+ 'hellip' => '…',
+ 'hercon' => '⊹',
+ 'Hfr' => 'ℌ',
+ 'hfr' => 'ð”¥',
+ 'HilbertSpace' => 'â„‹',
+ 'hksearow' => '⤥',
+ 'hkswarow' => '⤦',
+ 'hoarr' => '⇿',
+ 'homtht' => '∻',
+ 'hookleftarrow' => '↩',
+ 'hookrightarrow' => '↪',
+ 'Hopf' => 'â„',
+ 'hopf' => 'ð•™',
+ 'horbar' => '―',
+ 'HorizontalLine' => '─',
+ 'Hscr' => 'â„‹',
+ 'hscr' => 'ð’½',
+ 'hslash' => 'â„',
+ 'Hstrok' => 'Ħ',
+ 'hstrok' => 'ħ',
+ 'HumpDownHump' => '≎',
+ 'HumpEqual' => 'â‰',
+ 'hybull' => 'âƒ',
+ 'hyphen' => 'â€',
+ 'Iacute' => 'Ã',
+ 'Iacut' => 'Ã',
+ 'iacute' => 'í',
+ 'iacut' => 'í',
+ 'ic' => 'â£',
+ 'Icirc' => 'ÃŽ',
+ 'Icir' => 'ÃŽ',
+ 'icirc' => 'î',
+ 'icir' => 'î',
+ 'Icy' => 'И',
+ 'icy' => 'и',
+ 'Idot' => 'Ä°',
+ 'IEcy' => 'Е',
+ 'iecy' => 'е',
+ 'iexcl' => '¡',
+ 'iexc' => '¡',
+ 'iff' => '⇔',
+ 'Ifr' => 'â„‘',
+ 'ifr' => 'ð”¦',
+ 'Igrave' => 'Ì',
+ 'Igrav' => 'Ì',
+ 'igrave' => 'ì',
+ 'igrav' => 'ì',
+ 'ii' => 'â…ˆ',
+ 'iiiint' => '⨌',
+ 'iiint' => '∭',
+ 'iinfin' => '⧜',
+ 'iiota' => 'â„©',
+ 'IJlig' => 'IJ',
+ 'ijlig' => 'ij',
+ 'Im' => 'â„‘',
+ 'Imacr' => 'Ī',
+ 'imacr' => 'Ä«',
+ 'image' => 'â„‘',
+ 'ImaginaryI' => 'â…ˆ',
+ 'imagline' => 'â„',
+ 'imagpart' => 'â„‘',
+ 'imath' => 'ı',
+ 'imof' => '⊷',
+ 'imped' => 'Ƶ',
+ 'Implies' => '⇒',
+ 'in' => '∈',
+ 'incare' => 'â„…',
+ 'infin' => '∞',
+ 'infintie' => 'â§',
+ 'inodot' => 'ı',
+ 'Int' => '∬',
+ 'int' => '∫',
+ 'intcal' => '⊺',
+ 'integers' => 'ℤ',
+ 'Integral' => '∫',
+ 'intercal' => '⊺',
+ 'Intersection' => 'â‹‚',
+ 'intlarhk' => '⨗',
+ 'intprod' => '⨼',
+ 'InvisibleComma' => 'â£',
+ 'InvisibleTimes' => 'â¢',
+ 'IOcy' => 'Ð',
+ 'iocy' => 'Ñ‘',
+ 'Iogon' => 'Ä®',
+ 'iogon' => 'į',
+ 'Iopf' => 'ð•€',
+ 'iopf' => 'ð•š',
+ 'Iota' => 'Ι',
+ 'iota' => 'ι',
+ 'iprod' => '⨼',
+ 'iquest' => '¿',
+ 'iques' => '¿',
+ 'Iscr' => 'â„',
+ 'iscr' => 'ð’¾',
+ 'isin' => '∈',
+ 'isindot' => '⋵',
+ 'isinE' => '⋹',
+ 'isins' => 'â‹´',
+ 'isinsv' => '⋳',
+ 'isinv' => '∈',
+ 'it' => 'â¢',
+ 'Itilde' => 'Ĩ',
+ 'itilde' => 'Ä©',
+ 'Iukcy' => 'І',
+ 'iukcy' => 'Ñ–',
+ 'Iuml' => 'Ã',
+ 'Ium' => 'Ã',
+ 'iuml' => 'ï',
+ 'ium' => 'ï',
+ 'Jcirc' => 'Ä´',
+ 'jcirc' => 'ĵ',
+ 'Jcy' => 'Й',
+ 'jcy' => 'й',
+ 'Jfr' => 'ð”',
+ 'jfr' => 'ð”§',
+ 'jmath' => 'È·',
+ 'Jopf' => 'ð•',
+ 'jopf' => 'ð•›',
+ 'Jscr' => 'ð’¥',
+ 'jscr' => 'ð’¿',
+ 'Jsercy' => 'Ј',
+ 'jsercy' => 'ј',
+ 'Jukcy' => 'Є',
+ 'jukcy' => 'Ñ”',
+ 'Kappa' => 'Κ',
+ 'kappa' => 'κ',
+ 'kappav' => 'Ï°',
+ 'Kcedil' => 'Ķ',
+ 'kcedil' => 'Ä·',
+ 'Kcy' => 'К',
+ 'kcy' => 'к',
+ 'Kfr' => 'ð”Ž',
+ 'kfr' => 'ð”¨',
+ 'kgreen' => 'ĸ',
+ 'KHcy' => 'Ð¥',
+ 'khcy' => 'Ñ…',
+ 'KJcy' => 'Ќ',
+ 'kjcy' => 'ќ',
+ 'Kopf' => 'ð•‚',
+ 'kopf' => 'ð•œ',
+ 'Kscr' => 'ð’¦',
+ 'kscr' => 'ð“€',
+ 'lAarr' => '⇚',
+ 'Lacute' => 'Ĺ',
+ 'lacute' => 'ĺ',
+ 'laemptyv' => '⦴',
+ 'lagran' => 'â„’',
+ 'Lambda' => 'Λ',
+ 'lambda' => 'λ',
+ 'Lang' => '⟪',
+ 'lang' => '⟨',
+ 'langd' => '⦑',
+ 'langle' => '⟨',
+ 'lap' => '⪅',
+ 'Laplacetrf' => 'â„’',
+ 'laquo' => '«',
+ 'laqu' => '«',
+ 'Larr' => '↞',
+ 'lArr' => 'â‡',
+ 'larr' => 'â†',
+ 'larrb' => '⇤',
+ 'larrbfs' => '⤟',
+ 'larrfs' => 'â¤',
+ 'larrhk' => '↩',
+ 'larrlp' => '↫',
+ 'larrpl' => '⤹',
+ 'larrsim' => '⥳',
+ 'larrtl' => '↢',
+ 'lat' => '⪫',
+ 'lAtail' => '⤛',
+ 'latail' => '⤙',
+ 'late' => '⪭',
+ 'lates' => '⪭︀',
+ 'lBarr' => '⤎',
+ 'lbarr' => '⤌',
+ 'lbbrk' => 'â²',
+ 'lbrace' => '{',
+ 'lbrack' => '[',
+ 'lbrke' => '⦋',
+ 'lbrksld' => 'â¦',
+ 'lbrkslu' => 'â¦',
+ 'Lcaron' => 'Ľ',
+ 'lcaron' => 'ľ',
+ 'Lcedil' => 'Ä»',
+ 'lcedil' => 'ļ',
+ 'lceil' => '⌈',
+ 'lcub' => '{',
+ 'Lcy' => 'Л',
+ 'lcy' => 'л',
+ 'ldca' => '⤶',
+ 'ldquo' => '“',
+ 'ldquor' => '„',
+ 'ldrdhar' => '⥧',
+ 'ldrushar' => '⥋',
+ 'ldsh' => '↲',
+ 'lE' => '≦',
+ 'le' => '≤',
+ 'LeftAngleBracket' => '⟨',
+ 'LeftArrow' => 'â†',
+ 'Leftarrow' => 'â‡',
+ 'leftarrow' => 'â†',
+ 'LeftArrowBar' => '⇤',
+ 'LeftArrowRightArrow' => '⇆',
+ 'leftarrowtail' => '↢',
+ 'LeftCeiling' => '⌈',
+ 'LeftDoubleBracket' => '⟦',
+ 'LeftDownTeeVector' => '⥡',
+ 'LeftDownVector' => '⇃',
+ 'LeftDownVectorBar' => '⥙',
+ 'LeftFloor' => '⌊',
+ 'leftharpoondown' => '↽',
+ 'leftharpoonup' => '↼',
+ 'leftleftarrows' => '⇇',
+ 'LeftRightArrow' => '↔',
+ 'Leftrightarrow' => '⇔',
+ 'leftrightarrow' => '↔',
+ 'leftrightarrows' => '⇆',
+ 'leftrightharpoons' => '⇋',
+ 'leftrightsquigarrow' => '↭',
+ 'LeftRightVector' => '⥎',
+ 'LeftTee' => '⊣',
+ 'LeftTeeArrow' => '↤',
+ 'LeftTeeVector' => '⥚',
+ 'leftthreetimes' => 'â‹‹',
+ 'LeftTriangle' => '⊲',
+ 'LeftTriangleBar' => 'â§',
+ 'LeftTriangleEqual' => '⊴',
+ 'LeftUpDownVector' => '⥑',
+ 'LeftUpTeeVector' => '⥠',
+ 'LeftUpVector' => '↿',
+ 'LeftUpVectorBar' => '⥘',
+ 'LeftVector' => '↼',
+ 'LeftVectorBar' => '⥒',
+ 'lEg' => '⪋',
+ 'leg' => 'â‹š',
+ 'leq' => '≤',
+ 'leqq' => '≦',
+ 'leqslant' => '⩽',
+ 'les' => '⩽',
+ 'lescc' => '⪨',
+ 'lesdot' => 'â©¿',
+ 'lesdoto' => 'âª',
+ 'lesdotor' => '⪃',
+ 'lesg' => '⋚︀',
+ 'lesges' => '⪓',
+ 'lessapprox' => '⪅',
+ 'lessdot' => 'â‹–',
+ 'lesseqgtr' => 'â‹š',
+ 'lesseqqgtr' => '⪋',
+ 'LessEqualGreater' => 'â‹š',
+ 'LessFullEqual' => '≦',
+ 'LessGreater' => '≶',
+ 'lessgtr' => '≶',
+ 'LessLess' => '⪡',
+ 'lesssim' => '≲',
+ 'LessSlantEqual' => '⩽',
+ 'LessTilde' => '≲',
+ 'lfisht' => '⥼',
+ 'lfloor' => '⌊',
+ 'Lfr' => 'ð”',
+ 'lfr' => 'ð”©',
+ 'lg' => '≶',
+ 'lgE' => '⪑',
+ 'lHar' => '⥢',
+ 'lhard' => '↽',
+ 'lharu' => '↼',
+ 'lharul' => '⥪',
+ 'lhblk' => 'â–„',
+ 'LJcy' => 'Љ',
+ 'ljcy' => 'Ñ™',
+ 'Ll' => '⋘',
+ 'll' => '≪',
+ 'llarr' => '⇇',
+ 'llcorner' => '⌞',
+ 'Lleftarrow' => '⇚',
+ 'llhard' => '⥫',
+ 'lltri' => 'â—º',
+ 'Lmidot' => 'Ä¿',
+ 'lmidot' => 'Å€',
+ 'lmoust' => '⎰',
+ 'lmoustache' => '⎰',
+ 'lnap' => '⪉',
+ 'lnapprox' => '⪉',
+ 'lnE' => '≨',
+ 'lne' => '⪇',
+ 'lneq' => '⪇',
+ 'lneqq' => '≨',
+ 'lnsim' => '⋦',
+ 'loang' => '⟬',
+ 'loarr' => '⇽',
+ 'lobrk' => '⟦',
+ 'LongLeftArrow' => '⟵',
+ 'Longleftarrow' => '⟸',
+ 'longleftarrow' => '⟵',
+ 'LongLeftRightArrow' => '⟷',
+ 'Longleftrightarrow' => '⟺',
+ 'longleftrightarrow' => '⟷',
+ 'longmapsto' => '⟼',
+ 'LongRightArrow' => '⟶',
+ 'Longrightarrow' => '⟹',
+ 'longrightarrow' => '⟶',
+ 'looparrowleft' => '↫',
+ 'looparrowright' => '↬',
+ 'lopar' => '⦅',
+ 'Lopf' => 'ð•ƒ',
+ 'lopf' => 'ð•',
+ 'loplus' => '⨭',
+ 'lotimes' => '⨴',
+ 'lowast' => '∗',
+ 'lowbar' => '_',
+ 'LowerLeftArrow' => '↙',
+ 'LowerRightArrow' => '↘',
+ 'loz' => 'â—Š',
+ 'lozenge' => 'â—Š',
+ 'lozf' => '⧫',
+ 'lpar' => '(',
+ 'lparlt' => '⦓',
+ 'lrarr' => '⇆',
+ 'lrcorner' => '⌟',
+ 'lrhar' => '⇋',
+ 'lrhard' => '⥭',
+ 'lrm' => '‎',
+ 'lrtri' => '⊿',
+ 'lsaquo' => '‹',
+ 'Lscr' => 'â„’',
+ 'lscr' => 'ð“',
+ 'Lsh' => '↰',
+ 'lsh' => '↰',
+ 'lsim' => '≲',
+ 'lsime' => 'âª',
+ 'lsimg' => 'âª',
+ 'lsqb' => '[',
+ 'lsquo' => '‘',
+ 'lsquor' => '‚',
+ 'Lstrok' => 'Å',
+ 'lstrok' => 'Å‚',
+ 'LT' => '<',
+ 'L' => '<',
+ 'Lt' => '≪',
+ 'lt' => '<',
+ 'l' => '<',
+ 'ltcc' => '⪦',
+ 'ltcir' => '⩹',
+ 'ltdot' => 'â‹–',
+ 'lthree' => 'â‹‹',
+ 'ltimes' => '⋉',
+ 'ltlarr' => '⥶',
+ 'ltquest' => 'â©»',
+ 'ltri' => 'â—ƒ',
+ 'ltrie' => '⊴',
+ 'ltrif' => 'â—‚',
+ 'ltrPar' => '⦖',
+ 'lurdshar' => '⥊',
+ 'luruhar' => '⥦',
+ 'lvertneqq' => '≨︀',
+ 'lvnE' => '≨︀',
+ 'macr' => '¯',
+ 'mac' => '¯',
+ 'male' => '♂',
+ 'malt' => '✠',
+ 'maltese' => '✠',
+ 'Map' => '⤅',
+ 'map' => '↦',
+ 'mapsto' => '↦',
+ 'mapstodown' => '↧',
+ 'mapstoleft' => '↤',
+ 'mapstoup' => '↥',
+ 'marker' => 'â–®',
+ 'mcomma' => '⨩',
+ 'Mcy' => 'М',
+ 'mcy' => 'м',
+ 'mdash' => '—',
+ 'mDDot' => '∺',
+ 'measuredangle' => '∡',
+ 'MediumSpace' => 'âŸ',
+ 'Mellintrf' => 'ℳ',
+ 'Mfr' => 'ð”',
+ 'mfr' => 'ð”ª',
+ 'mho' => '℧',
+ 'micro' => 'µ',
+ 'micr' => 'µ',
+ 'mid' => '∣',
+ 'midast' => '*',
+ 'midcir' => 'â«°',
+ 'middot' => '·',
+ 'middo' => '·',
+ 'minus' => '−',
+ 'minusb' => '⊟',
+ 'minusd' => '∸',
+ 'minusdu' => '⨪',
+ 'MinusPlus' => '∓',
+ 'mlcp' => 'â«›',
+ 'mldr' => '…',
+ 'mnplus' => '∓',
+ 'models' => '⊧',
+ 'Mopf' => 'ð•„',
+ 'mopf' => 'ð•ž',
+ 'mp' => '∓',
+ 'Mscr' => 'ℳ',
+ 'mscr' => 'ð“‚',
+ 'mstpos' => '∾',
+ 'Mu' => 'Μ',
+ 'mu' => 'μ',
+ 'multimap' => '⊸',
+ 'mumap' => '⊸',
+ 'nabla' => '∇',
+ 'Nacute' => 'Ń',
+ 'nacute' => 'Å„',
+ 'nang' => '∠⃒',
+ 'nap' => '≉',
+ 'napE' => '⩰̸',
+ 'napid' => '≋̸',
+ 'napos' => 'ʼn',
+ 'napprox' => '≉',
+ 'natur' => 'â™®',
+ 'natural' => 'â™®',
+ 'naturals' => 'â„•',
+ 'nbsp' => ' ',
+ 'nbs' => ' ',
+ 'nbump' => '≎̸',
+ 'nbumpe' => 'â‰Ì¸',
+ 'ncap' => '⩃',
+ 'Ncaron' => 'Ň',
+ 'ncaron' => 'ň',
+ 'Ncedil' => 'Å…',
+ 'ncedil' => 'ņ',
+ 'ncong' => '≇',
+ 'ncongdot' => '⩭̸',
+ 'ncup' => 'â©‚',
+ 'Ncy' => 'Ð',
+ 'ncy' => 'н',
+ 'ndash' => '–',
+ 'ne' => '≠',
+ 'nearhk' => '⤤',
+ 'neArr' => '⇗',
+ 'nearr' => '↗',
+ 'nearrow' => '↗',
+ 'nedot' => 'â‰Ì¸',
+ 'NegativeMediumSpace' => '​',
+ 'NegativeThickSpace' => '​',
+ 'NegativeThinSpace' => '​',
+ 'NegativeVeryThinSpace' => '​',
+ 'nequiv' => '≢',
+ 'nesear' => '⤨',
+ 'nesim' => '≂̸',
+ 'NestedGreaterGreater' => '≫',
+ 'NestedLessLess' => '≪',
+ 'NewLine' => '
+',
+ 'nexist' => '∄',
+ 'nexists' => '∄',
+ 'Nfr' => 'ð”‘',
+ 'nfr' => 'ð”«',
+ 'ngE' => '≧̸',
+ 'nge' => '≱',
+ 'ngeq' => '≱',
+ 'ngeqq' => '≧̸',
+ 'ngeqslant' => '⩾̸',
+ 'nges' => '⩾̸',
+ 'nGg' => '⋙̸',
+ 'ngsim' => '≵',
+ 'nGt' => '≫⃒',
+ 'ngt' => '≯',
+ 'ngtr' => '≯',
+ 'nGtv' => '≫̸',
+ 'nhArr' => '⇎',
+ 'nharr' => '↮',
+ 'nhpar' => '⫲',
+ 'ni' => '∋',
+ 'nis' => '⋼',
+ 'nisd' => '⋺',
+ 'niv' => '∋',
+ 'NJcy' => 'Њ',
+ 'njcy' => 'Ñš',
+ 'nlArr' => 'â‡',
+ 'nlarr' => '↚',
+ 'nldr' => '‥',
+ 'nlE' => '≦̸',
+ 'nle' => '≰',
+ 'nLeftarrow' => 'â‡',
+ 'nleftarrow' => '↚',
+ 'nLeftrightarrow' => '⇎',
+ 'nleftrightarrow' => '↮',
+ 'nleq' => '≰',
+ 'nleqq' => '≦̸',
+ 'nleqslant' => '⩽̸',
+ 'nles' => '⩽̸',
+ 'nless' => '≮',
+ 'nLl' => '⋘̸',
+ 'nlsim' => '≴',
+ 'nLt' => '≪⃒',
+ 'nlt' => '≮',
+ 'nltri' => '⋪',
+ 'nltrie' => '⋬',
+ 'nLtv' => '≪̸',
+ 'nmid' => '∤',
+ 'NoBreak' => 'â ',
+ 'NonBreakingSpace' => ' ',
+ 'Nopf' => 'â„•',
+ 'nopf' => 'ð•Ÿ',
+ 'Not' => '⫬',
+ 'not' => '¬',
+ 'no' => '¬',
+ 'NotCongruent' => '≢',
+ 'NotCupCap' => '≭',
+ 'NotDoubleVerticalBar' => '∦',
+ 'NotElement' => '∉',
+ 'NotEqual' => '≠',
+ 'NotEqualTilde' => '≂̸',
+ 'NotExists' => '∄',
+ 'NotGreater' => '≯',
+ 'NotGreaterEqual' => '≱',
+ 'NotGreaterFullEqual' => '≧̸',
+ 'NotGreaterGreater' => '≫̸',
+ 'NotGreaterLess' => '≹',
+ 'NotGreaterSlantEqual' => '⩾̸',
+ 'NotGreaterTilde' => '≵',
+ 'NotHumpDownHump' => '≎̸',
+ 'NotHumpEqual' => 'â‰Ì¸',
+ 'notin' => '∉',
+ 'notindot' => '⋵̸',
+ 'notinE' => '⋹̸',
+ 'notinva' => '∉',
+ 'notinvb' => 'â‹·',
+ 'notinvc' => '⋶',
+ 'NotLeftTriangle' => '⋪',
+ 'NotLeftTriangleBar' => 'â§Ì¸',
+ 'NotLeftTriangleEqual' => '⋬',
+ 'NotLess' => '≮',
+ 'NotLessEqual' => '≰',
+ 'NotLessGreater' => '≸',
+ 'NotLessLess' => '≪̸',
+ 'NotLessSlantEqual' => '⩽̸',
+ 'NotLessTilde' => '≴',
+ 'NotNestedGreaterGreater' => '⪢̸',
+ 'NotNestedLessLess' => '⪡̸',
+ 'notni' => '∌',
+ 'notniva' => '∌',
+ 'notnivb' => '⋾',
+ 'notnivc' => '⋽',
+ 'NotPrecedes' => '⊀',
+ 'NotPrecedesEqual' => '⪯̸',
+ 'NotPrecedesSlantEqual' => 'â‹ ',
+ 'NotReverseElement' => '∌',
+ 'NotRightTriangle' => 'â‹«',
+ 'NotRightTriangleBar' => 'â§Ì¸',
+ 'NotRightTriangleEqual' => 'â‹­',
+ 'NotSquareSubset' => 'âŠÌ¸',
+ 'NotSquareSubsetEqual' => 'â‹¢',
+ 'NotSquareSuperset' => 'âŠÌ¸',
+ 'NotSquareSupersetEqual' => 'â‹£',
+ 'NotSubset' => '⊂⃒',
+ 'NotSubsetEqual' => '⊈',
+ 'NotSucceeds' => 'âŠ',
+ 'NotSucceedsEqual' => '⪰̸',
+ 'NotSucceedsSlantEqual' => 'â‹¡',
+ 'NotSucceedsTilde' => '≿̸',
+ 'NotSuperset' => '⊃⃒',
+ 'NotSupersetEqual' => '⊉',
+ 'NotTilde' => 'â‰',
+ 'NotTildeEqual' => '≄',
+ 'NotTildeFullEqual' => '≇',
+ 'NotTildeTilde' => '≉',
+ 'NotVerticalBar' => '∤',
+ 'npar' => '∦',
+ 'nparallel' => '∦',
+ 'nparsl' => '⫽⃥',
+ 'npart' => '∂̸',
+ 'npolint' => '⨔',
+ 'npr' => '⊀',
+ 'nprcue' => 'â‹ ',
+ 'npre' => '⪯̸',
+ 'nprec' => '⊀',
+ 'npreceq' => '⪯̸',
+ 'nrArr' => 'â‡',
+ 'nrarr' => '↛',
+ 'nrarrc' => '⤳̸',
+ 'nrarrw' => 'â†Ì¸',
+ 'nRightarrow' => 'â‡',
+ 'nrightarrow' => '↛',
+ 'nrtri' => 'â‹«',
+ 'nrtrie' => 'â‹­',
+ 'nsc' => 'âŠ',
+ 'nsccue' => 'â‹¡',
+ 'nsce' => '⪰̸',
+ 'Nscr' => 'ð’©',
+ 'nscr' => 'ð“ƒ',
+ 'nshortmid' => '∤',
+ 'nshortparallel' => '∦',
+ 'nsim' => 'â‰',
+ 'nsime' => '≄',
+ 'nsimeq' => '≄',
+ 'nsmid' => '∤',
+ 'nspar' => '∦',
+ 'nsqsube' => 'â‹¢',
+ 'nsqsupe' => 'â‹£',
+ 'nsub' => '⊄',
+ 'nsubE' => '⫅̸',
+ 'nsube' => '⊈',
+ 'nsubset' => '⊂⃒',
+ 'nsubseteq' => '⊈',
+ 'nsubseteqq' => '⫅̸',
+ 'nsucc' => 'âŠ',
+ 'nsucceq' => '⪰̸',
+ 'nsup' => '⊅',
+ 'nsupE' => '⫆̸',
+ 'nsupe' => '⊉',
+ 'nsupset' => '⊃⃒',
+ 'nsupseteq' => '⊉',
+ 'nsupseteqq' => '⫆̸',
+ 'ntgl' => '≹',
+ 'Ntilde' => 'Ñ',
+ 'Ntild' => 'Ñ',
+ 'ntilde' => 'ñ',
+ 'ntild' => 'ñ',
+ 'ntlg' => '≸',
+ 'ntriangleleft' => '⋪',
+ 'ntrianglelefteq' => '⋬',
+ 'ntriangleright' => 'â‹«',
+ 'ntrianglerighteq' => 'â‹­',
+ 'Nu' => 'Î',
+ 'nu' => 'ν',
+ 'num' => '#',
+ 'numero' => 'â„–',
+ 'numsp' => ' ',
+ 'nvap' => 'â‰âƒ’',
+ 'nVDash' => '⊯',
+ 'nVdash' => '⊮',
+ 'nvDash' => '⊭',
+ 'nvdash' => '⊬',
+ 'nvge' => '≥⃒',
+ 'nvgt' => '>⃒',
+ 'nvHarr' => '⤄',
+ 'nvinfin' => '⧞',
+ 'nvlArr' => '⤂',
+ 'nvle' => '≤⃒',
+ 'nvlt' => '<⃒',
+ 'nvltrie' => '⊴⃒',
+ 'nvrArr' => '⤃',
+ 'nvrtrie' => '⊵⃒',
+ 'nvsim' => '∼⃒',
+ 'nwarhk' => '⤣',
+ 'nwArr' => '⇖',
+ 'nwarr' => '↖',
+ 'nwarrow' => '↖',
+ 'nwnear' => '⤧',
+ 'Oacute' => 'Ó',
+ 'Oacut' => 'Ó',
+ 'oacute' => 'ó',
+ 'oacut' => 'ó',
+ 'oast' => '⊛',
+ 'ocir' => 'ô',
+ 'Ocirc' => 'Ô',
+ 'Ocir' => 'Ô',
+ 'ocirc' => 'ô',
+ 'Ocy' => 'О',
+ 'ocy' => 'о',
+ 'odash' => 'âŠ',
+ 'Odblac' => 'Å',
+ 'odblac' => 'Å‘',
+ 'odiv' => '⨸',
+ 'odot' => '⊙',
+ 'odsold' => '⦼',
+ 'OElig' => 'Å’',
+ 'oelig' => 'Å“',
+ 'ofcir' => '⦿',
+ 'Ofr' => 'ð”’',
+ 'ofr' => 'ð”¬',
+ 'ogon' => 'Ë›',
+ 'Ograve' => 'Ã’',
+ 'Ograv' => 'Ã’',
+ 'ograve' => 'ò',
+ 'ograv' => 'ò',
+ 'ogt' => 'â§',
+ 'ohbar' => '⦵',
+ 'ohm' => 'Ω',
+ 'oint' => '∮',
+ 'olarr' => '↺',
+ 'olcir' => '⦾',
+ 'olcross' => '⦻',
+ 'oline' => '‾',
+ 'olt' => '⧀',
+ 'Omacr' => 'Ō',
+ 'omacr' => 'Å',
+ 'Omega' => 'Ω',
+ 'omega' => 'ω',
+ 'Omicron' => 'Ο',
+ 'omicron' => 'ο',
+ 'omid' => '⦶',
+ 'ominus' => '⊖',
+ 'Oopf' => 'ð•†',
+ 'oopf' => 'ð• ',
+ 'opar' => '⦷',
+ 'OpenCurlyDoubleQuote' => '“',
+ 'OpenCurlyQuote' => '‘',
+ 'operp' => '⦹',
+ 'oplus' => '⊕',
+ 'Or' => 'â©”',
+ 'or' => '∨',
+ 'orarr' => '↻',
+ 'ord' => 'º',
+ 'order' => 'â„´',
+ 'orderof' => 'â„´',
+ 'ordf' => 'ª',
+ 'ordm' => 'º',
+ 'origof' => '⊶',
+ 'oror' => 'â©–',
+ 'orslope' => 'â©—',
+ 'orv' => 'â©›',
+ 'oS' => 'Ⓢ',
+ 'Oscr' => 'ð’ª',
+ 'oscr' => 'â„´',
+ 'Oslash' => 'Ø',
+ 'Oslas' => 'Ø',
+ 'oslash' => 'ø',
+ 'oslas' => 'ø',
+ 'osol' => '⊘',
+ 'Otilde' => 'Õ',
+ 'Otild' => 'Õ',
+ 'otilde' => 'õ',
+ 'otild' => 'õ',
+ 'Otimes' => '⨷',
+ 'otimes' => '⊗',
+ 'otimesas' => '⨶',
+ 'Ouml' => 'Ö',
+ 'Oum' => 'Ö',
+ 'ouml' => 'ö',
+ 'oum' => 'ö',
+ 'ovbar' => '⌽',
+ 'OverBar' => '‾',
+ 'OverBrace' => 'âž',
+ 'OverBracket' => '⎴',
+ 'OverParenthesis' => 'âœ',
+ 'par' => '¶',
+ 'para' => '¶',
+ 'parallel' => '∥',
+ 'parsim' => '⫳',
+ 'parsl' => '⫽',
+ 'part' => '∂',
+ 'PartialD' => '∂',
+ 'Pcy' => 'П',
+ 'pcy' => 'п',
+ 'percnt' => '%',
+ 'period' => '.',
+ 'permil' => '‰',
+ 'perp' => '⊥',
+ 'pertenk' => '‱',
+ 'Pfr' => 'ð”“',
+ 'pfr' => 'ð”­',
+ 'Phi' => 'Φ',
+ 'phi' => 'φ',
+ 'phiv' => 'Ï•',
+ 'phmmat' => 'ℳ',
+ 'phone' => '☎',
+ 'Pi' => 'Π',
+ 'pi' => 'Ï€',
+ 'pitchfork' => 'â‹”',
+ 'piv' => 'Ï–',
+ 'planck' => 'â„',
+ 'planckh' => 'â„Ž',
+ 'plankv' => 'â„',
+ 'plus' => '+',
+ 'plusacir' => '⨣',
+ 'plusb' => '⊞',
+ 'pluscir' => '⨢',
+ 'plusdo' => '∔',
+ 'plusdu' => '⨥',
+ 'pluse' => '⩲',
+ 'PlusMinus' => '±',
+ 'plusmn' => '±',
+ 'plusm' => '±',
+ 'plussim' => '⨦',
+ 'plustwo' => '⨧',
+ 'pm' => '±',
+ 'Poincareplane' => 'ℌ',
+ 'pointint' => '⨕',
+ 'Popf' => 'â„™',
+ 'popf' => 'ð•¡',
+ 'pound' => '£',
+ 'poun' => '£',
+ 'Pr' => '⪻',
+ 'pr' => '≺',
+ 'prap' => '⪷',
+ 'prcue' => '≼',
+ 'prE' => '⪳',
+ 'pre' => '⪯',
+ 'prec' => '≺',
+ 'precapprox' => '⪷',
+ 'preccurlyeq' => '≼',
+ 'Precedes' => '≺',
+ 'PrecedesEqual' => '⪯',
+ 'PrecedesSlantEqual' => '≼',
+ 'PrecedesTilde' => '≾',
+ 'preceq' => '⪯',
+ 'precnapprox' => '⪹',
+ 'precneqq' => '⪵',
+ 'precnsim' => '⋨',
+ 'precsim' => '≾',
+ 'Prime' => '″',
+ 'prime' => '′',
+ 'primes' => 'â„™',
+ 'prnap' => '⪹',
+ 'prnE' => '⪵',
+ 'prnsim' => '⋨',
+ 'prod' => 'âˆ',
+ 'Product' => 'âˆ',
+ 'profalar' => '⌮',
+ 'profline' => '⌒',
+ 'profsurf' => '⌓',
+ 'prop' => 'âˆ',
+ 'Proportion' => '∷',
+ 'Proportional' => 'âˆ',
+ 'propto' => 'âˆ',
+ 'prsim' => '≾',
+ 'prurel' => '⊰',
+ 'Pscr' => 'ð’«',
+ 'pscr' => 'ð“…',
+ 'Psi' => 'Ψ',
+ 'psi' => 'ψ',
+ 'puncsp' => ' ',
+ 'Qfr' => 'ð””',
+ 'qfr' => 'ð”®',
+ 'qint' => '⨌',
+ 'Qopf' => 'â„š',
+ 'qopf' => 'ð•¢',
+ 'qprime' => 'â—',
+ 'Qscr' => 'ð’¬',
+ 'qscr' => 'ð“†',
+ 'quaternions' => 'â„',
+ 'quatint' => '⨖',
+ 'quest' => '?',
+ 'questeq' => '≟',
+ 'QUOT' => '"',
+ 'QUO' => '"',
+ 'quot' => '"',
+ 'quo' => '"',
+ 'rAarr' => '⇛',
+ 'race' => '∽̱',
+ 'Racute' => 'Å”',
+ 'racute' => 'Å•',
+ 'radic' => '√',
+ 'raemptyv' => '⦳',
+ 'Rang' => '⟫',
+ 'rang' => '⟩',
+ 'rangd' => '⦒',
+ 'range' => '⦥',
+ 'rangle' => '⟩',
+ 'raquo' => '»',
+ 'raqu' => '»',
+ 'Rarr' => '↠',
+ 'rArr' => '⇒',
+ 'rarr' => '→',
+ 'rarrap' => '⥵',
+ 'rarrb' => '⇥',
+ 'rarrbfs' => '⤠',
+ 'rarrc' => '⤳',
+ 'rarrfs' => '⤞',
+ 'rarrhk' => '↪',
+ 'rarrlp' => '↬',
+ 'rarrpl' => '⥅',
+ 'rarrsim' => '⥴',
+ 'Rarrtl' => '⤖',
+ 'rarrtl' => '↣',
+ 'rarrw' => 'â†',
+ 'rAtail' => '⤜',
+ 'ratail' => '⤚',
+ 'ratio' => '∶',
+ 'rationals' => 'â„š',
+ 'RBarr' => 'â¤',
+ 'rBarr' => 'â¤',
+ 'rbarr' => 'â¤',
+ 'rbbrk' => 'â³',
+ 'rbrace' => '}',
+ 'rbrack' => ']',
+ 'rbrke' => '⦌',
+ 'rbrksld' => '⦎',
+ 'rbrkslu' => 'â¦',
+ 'Rcaron' => 'Ř',
+ 'rcaron' => 'Å™',
+ 'Rcedil' => 'Å–',
+ 'rcedil' => 'Å—',
+ 'rceil' => '⌉',
+ 'rcub' => '}',
+ 'Rcy' => 'Р',
+ 'rcy' => 'Ñ€',
+ 'rdca' => '⤷',
+ 'rdldhar' => '⥩',
+ 'rdquo' => 'â€',
+ 'rdquor' => 'â€',
+ 'rdsh' => '↳',
+ 'Re' => 'ℜ',
+ 'real' => 'ℜ',
+ 'realine' => 'â„›',
+ 'realpart' => 'ℜ',
+ 'reals' => 'â„',
+ 'rect' => 'â–­',
+ 'REG' => '®',
+ 'RE' => '®',
+ 'reg' => '®',
+ 're' => '®',
+ 'ReverseElement' => '∋',
+ 'ReverseEquilibrium' => '⇋',
+ 'ReverseUpEquilibrium' => '⥯',
+ 'rfisht' => '⥽',
+ 'rfloor' => '⌋',
+ 'Rfr' => 'ℜ',
+ 'rfr' => 'ð”¯',
+ 'rHar' => '⥤',
+ 'rhard' => 'â‡',
+ 'rharu' => '⇀',
+ 'rharul' => '⥬',
+ 'Rho' => 'Ρ',
+ 'rho' => 'Ï',
+ 'rhov' => 'ϱ',
+ 'RightAngleBracket' => '⟩',
+ 'RightArrow' => '→',
+ 'Rightarrow' => '⇒',
+ 'rightarrow' => '→',
+ 'RightArrowBar' => '⇥',
+ 'RightArrowLeftArrow' => '⇄',
+ 'rightarrowtail' => '↣',
+ 'RightCeiling' => '⌉',
+ 'RightDoubleBracket' => '⟧',
+ 'RightDownTeeVector' => 'â¥',
+ 'RightDownVector' => '⇂',
+ 'RightDownVectorBar' => '⥕',
+ 'RightFloor' => '⌋',
+ 'rightharpoondown' => 'â‡',
+ 'rightharpoonup' => '⇀',
+ 'rightleftarrows' => '⇄',
+ 'rightleftharpoons' => '⇌',
+ 'rightrightarrows' => '⇉',
+ 'rightsquigarrow' => 'â†',
+ 'RightTee' => '⊢',
+ 'RightTeeArrow' => '↦',
+ 'RightTeeVector' => '⥛',
+ 'rightthreetimes' => '⋌',
+ 'RightTriangle' => '⊳',
+ 'RightTriangleBar' => 'â§',
+ 'RightTriangleEqual' => '⊵',
+ 'RightUpDownVector' => 'â¥',
+ 'RightUpTeeVector' => '⥜',
+ 'RightUpVector' => '↾',
+ 'RightUpVectorBar' => '⥔',
+ 'RightVector' => '⇀',
+ 'RightVectorBar' => '⥓',
+ 'ring' => 'Ëš',
+ 'risingdotseq' => '≓',
+ 'rlarr' => '⇄',
+ 'rlhar' => '⇌',
+ 'rlm' => 'â€',
+ 'rmoust' => '⎱',
+ 'rmoustache' => '⎱',
+ 'rnmid' => 'â«®',
+ 'roang' => '⟭',
+ 'roarr' => '⇾',
+ 'robrk' => '⟧',
+ 'ropar' => '⦆',
+ 'Ropf' => 'â„',
+ 'ropf' => 'ð•£',
+ 'roplus' => '⨮',
+ 'rotimes' => '⨵',
+ 'RoundImplies' => '⥰',
+ 'rpar' => ')',
+ 'rpargt' => '⦔',
+ 'rppolint' => '⨒',
+ 'rrarr' => '⇉',
+ 'Rrightarrow' => '⇛',
+ 'rsaquo' => '›',
+ 'Rscr' => 'â„›',
+ 'rscr' => 'ð“‡',
+ 'Rsh' => '↱',
+ 'rsh' => '↱',
+ 'rsqb' => ']',
+ 'rsquo' => '’',
+ 'rsquor' => '’',
+ 'rthree' => '⋌',
+ 'rtimes' => 'â‹Š',
+ 'rtri' => 'â–¹',
+ 'rtrie' => '⊵',
+ 'rtrif' => 'â–¸',
+ 'rtriltri' => '⧎',
+ 'RuleDelayed' => '⧴',
+ 'ruluhar' => '⥨',
+ 'rx' => 'â„ž',
+ 'Sacute' => 'Åš',
+ 'sacute' => 'Å›',
+ 'sbquo' => '‚',
+ 'Sc' => '⪼',
+ 'sc' => '≻',
+ 'scap' => '⪸',
+ 'Scaron' => 'Å ',
+ 'scaron' => 'Å¡',
+ 'sccue' => '≽',
+ 'scE' => '⪴',
+ 'sce' => '⪰',
+ 'Scedil' => 'Åž',
+ 'scedil' => 'ÅŸ',
+ 'Scirc' => 'Ŝ',
+ 'scirc' => 'Å',
+ 'scnap' => '⪺',
+ 'scnE' => '⪶',
+ 'scnsim' => 'â‹©',
+ 'scpolint' => '⨓',
+ 'scsim' => '≿',
+ 'Scy' => 'С',
+ 'scy' => 'Ñ',
+ 'sdot' => 'â‹…',
+ 'sdotb' => '⊡',
+ 'sdote' => '⩦',
+ 'searhk' => '⤥',
+ 'seArr' => '⇘',
+ 'searr' => '↘',
+ 'searrow' => '↘',
+ 'sect' => '§',
+ 'sec' => '§',
+ 'semi' => ';',
+ 'seswar' => '⤩',
+ 'setminus' => '∖',
+ 'setmn' => '∖',
+ 'sext' => '✶',
+ 'Sfr' => 'ð”–',
+ 'sfr' => 'ð”°',
+ 'sfrown' => '⌢',
+ 'sharp' => '♯',
+ 'SHCHcy' => 'Щ',
+ 'shchcy' => 'щ',
+ 'SHcy' => 'Ш',
+ 'shcy' => 'ш',
+ 'ShortDownArrow' => '↓',
+ 'ShortLeftArrow' => 'â†',
+ 'shortmid' => '∣',
+ 'shortparallel' => '∥',
+ 'ShortRightArrow' => '→',
+ 'ShortUpArrow' => '↑',
+ 'shy' => '­',
+ 'sh' => '­',
+ 'Sigma' => 'Σ',
+ 'sigma' => 'σ',
+ 'sigmaf' => 'Ï‚',
+ 'sigmav' => 'Ï‚',
+ 'sim' => '∼',
+ 'simdot' => '⩪',
+ 'sime' => '≃',
+ 'simeq' => '≃',
+ 'simg' => '⪞',
+ 'simgE' => '⪠',
+ 'siml' => 'âª',
+ 'simlE' => '⪟',
+ 'simne' => '≆',
+ 'simplus' => '⨤',
+ 'simrarr' => '⥲',
+ 'slarr' => 'â†',
+ 'SmallCircle' => '∘',
+ 'smallsetminus' => '∖',
+ 'smashp' => '⨳',
+ 'smeparsl' => '⧤',
+ 'smid' => '∣',
+ 'smile' => '⌣',
+ 'smt' => '⪪',
+ 'smte' => '⪬',
+ 'smtes' => '⪬︀',
+ 'SOFTcy' => 'Ь',
+ 'softcy' => 'ь',
+ 'sol' => '/',
+ 'solb' => '⧄',
+ 'solbar' => '⌿',
+ 'Sopf' => 'ð•Š',
+ 'sopf' => 'ð•¤',
+ 'spades' => 'â™ ',
+ 'spadesuit' => 'â™ ',
+ 'spar' => '∥',
+ 'sqcap' => '⊓',
+ 'sqcaps' => '⊓︀',
+ 'sqcup' => '⊔',
+ 'sqcups' => '⊔︀',
+ 'Sqrt' => '√',
+ 'sqsub' => 'âŠ',
+ 'sqsube' => '⊑',
+ 'sqsubset' => 'âŠ',
+ 'sqsubseteq' => '⊑',
+ 'sqsup' => 'âŠ',
+ 'sqsupe' => '⊒',
+ 'sqsupset' => 'âŠ',
+ 'sqsupseteq' => '⊒',
+ 'squ' => 'â–¡',
+ 'Square' => 'â–¡',
+ 'square' => 'â–¡',
+ 'SquareIntersection' => '⊓',
+ 'SquareSubset' => 'âŠ',
+ 'SquareSubsetEqual' => '⊑',
+ 'SquareSuperset' => 'âŠ',
+ 'SquareSupersetEqual' => '⊒',
+ 'SquareUnion' => '⊔',
+ 'squarf' => 'â–ª',
+ 'squf' => 'â–ª',
+ 'srarr' => '→',
+ 'Sscr' => 'ð’®',
+ 'sscr' => 'ð“ˆ',
+ 'ssetmn' => '∖',
+ 'ssmile' => '⌣',
+ 'sstarf' => '⋆',
+ 'Star' => '⋆',
+ 'star' => '☆',
+ 'starf' => '★',
+ 'straightepsilon' => 'ϵ',
+ 'straightphi' => 'Ï•',
+ 'strns' => '¯',
+ 'Sub' => 'â‹',
+ 'sub' => '⊂',
+ 'subdot' => '⪽',
+ 'subE' => 'â«…',
+ 'sube' => '⊆',
+ 'subedot' => '⫃',
+ 'submult' => 'â«',
+ 'subnE' => 'â«‹',
+ 'subne' => '⊊',
+ 'subplus' => '⪿',
+ 'subrarr' => '⥹',
+ 'Subset' => 'â‹',
+ 'subset' => '⊂',
+ 'subseteq' => '⊆',
+ 'subseteqq' => 'â«…',
+ 'SubsetEqual' => '⊆',
+ 'subsetneq' => '⊊',
+ 'subsetneqq' => 'â«‹',
+ 'subsim' => '⫇',
+ 'subsub' => 'â«•',
+ 'subsup' => 'â«“',
+ 'succ' => '≻',
+ 'succapprox' => '⪸',
+ 'succcurlyeq' => '≽',
+ 'Succeeds' => '≻',
+ 'SucceedsEqual' => '⪰',
+ 'SucceedsSlantEqual' => '≽',
+ 'SucceedsTilde' => '≿',
+ 'succeq' => '⪰',
+ 'succnapprox' => '⪺',
+ 'succneqq' => '⪶',
+ 'succnsim' => 'â‹©',
+ 'succsim' => '≿',
+ 'SuchThat' => '∋',
+ 'Sum' => '∑',
+ 'sum' => '∑',
+ 'sung' => '♪',
+ 'Sup' => 'â‹‘',
+ 'sup' => '³',
+ 'sup1' => '¹',
+ 'sup2' => '²',
+ 'sup3' => '³',
+ 'supdot' => '⪾',
+ 'supdsub' => '⫘',
+ 'supE' => '⫆',
+ 'supe' => '⊇',
+ 'supedot' => 'â«„',
+ 'Superset' => '⊃',
+ 'SupersetEqual' => '⊇',
+ 'suphsol' => '⟉',
+ 'suphsub' => 'â«—',
+ 'suplarr' => '⥻',
+ 'supmult' => 'â«‚',
+ 'supnE' => '⫌',
+ 'supne' => '⊋',
+ 'supplus' => 'â«€',
+ 'Supset' => 'â‹‘',
+ 'supset' => '⊃',
+ 'supseteq' => '⊇',
+ 'supseteqq' => '⫆',
+ 'supsetneq' => '⊋',
+ 'supsetneqq' => '⫌',
+ 'supsim' => '⫈',
+ 'supsub' => 'â«”',
+ 'supsup' => 'â«–',
+ 'swarhk' => '⤦',
+ 'swArr' => '⇙',
+ 'swarr' => '↙',
+ 'swarrow' => '↙',
+ 'swnwar' => '⤪',
+ 'szlig' => 'ß',
+ 'szli' => 'ß',
+ 'Tab' => ' ',
+ 'target' => '⌖',
+ 'Tau' => 'Τ',
+ 'tau' => 'Ï„',
+ 'tbrk' => '⎴',
+ 'Tcaron' => 'Ť',
+ 'tcaron' => 'Å¥',
+ 'Tcedil' => 'Å¢',
+ 'tcedil' => 'Å£',
+ 'Tcy' => 'Т',
+ 'tcy' => 'Ñ‚',
+ 'tdot' => '⃛',
+ 'telrec' => '⌕',
+ 'Tfr' => 'ð”—',
+ 'tfr' => 'ð”±',
+ 'there4' => '∴',
+ 'Therefore' => '∴',
+ 'therefore' => '∴',
+ 'Theta' => 'Θ',
+ 'theta' => 'θ',
+ 'thetasym' => 'Ï‘',
+ 'thetav' => 'Ï‘',
+ 'thickapprox' => '≈',
+ 'thicksim' => '∼',
+ 'ThickSpace' => 'âŸâ€Š',
+ 'thinsp' => ' ',
+ 'ThinSpace' => ' ',
+ 'thkap' => '≈',
+ 'thksim' => '∼',
+ 'THORN' => 'Þ',
+ 'THOR' => 'Þ',
+ 'thorn' => 'þ',
+ 'thor' => 'þ',
+ 'Tilde' => '∼',
+ 'tilde' => '˜',
+ 'TildeEqual' => '≃',
+ 'TildeFullEqual' => '≅',
+ 'TildeTilde' => '≈',
+ 'times' => '×',
+ 'time' => '×',
+ 'timesb' => '⊠',
+ 'timesbar' => '⨱',
+ 'timesd' => '⨰',
+ 'tint' => '∭',
+ 'toea' => '⤨',
+ 'top' => '⊤',
+ 'topbot' => '⌶',
+ 'topcir' => '⫱',
+ 'Topf' => 'ð•‹',
+ 'topf' => 'ð•¥',
+ 'topfork' => 'â«š',
+ 'tosa' => '⤩',
+ 'tprime' => '‴',
+ 'TRADE' => 'â„¢',
+ 'trade' => 'â„¢',
+ 'triangle' => 'â–µ',
+ 'triangledown' => 'â–¿',
+ 'triangleleft' => 'â—ƒ',
+ 'trianglelefteq' => '⊴',
+ 'triangleq' => '≜',
+ 'triangleright' => 'â–¹',
+ 'trianglerighteq' => '⊵',
+ 'tridot' => 'â—¬',
+ 'trie' => '≜',
+ 'triminus' => '⨺',
+ 'TripleDot' => '⃛',
+ 'triplus' => '⨹',
+ 'trisb' => 'â§',
+ 'tritime' => '⨻',
+ 'trpezium' => 'â¢',
+ 'Tscr' => 'ð’¯',
+ 'tscr' => 'ð“‰',
+ 'TScy' => 'Ц',
+ 'tscy' => 'ц',
+ 'TSHcy' => 'Ћ',
+ 'tshcy' => 'Ñ›',
+ 'Tstrok' => 'Ŧ',
+ 'tstrok' => 'ŧ',
+ 'twixt' => '≬',
+ 'twoheadleftarrow' => '↞',
+ 'twoheadrightarrow' => '↠',
+ 'Uacute' => 'Ú',
+ 'Uacut' => 'Ú',
+ 'uacute' => 'ú',
+ 'uacut' => 'ú',
+ 'Uarr' => '↟',
+ 'uArr' => '⇑',
+ 'uarr' => '↑',
+ 'Uarrocir' => '⥉',
+ 'Ubrcy' => 'ÐŽ',
+ 'ubrcy' => 'Ñž',
+ 'Ubreve' => 'Ŭ',
+ 'ubreve' => 'Å­',
+ 'Ucirc' => 'Û',
+ 'Ucir' => 'Û',
+ 'ucirc' => 'û',
+ 'ucir' => 'û',
+ 'Ucy' => 'У',
+ 'ucy' => 'у',
+ 'udarr' => '⇅',
+ 'Udblac' => 'Å°',
+ 'udblac' => 'ű',
+ 'udhar' => '⥮',
+ 'ufisht' => '⥾',
+ 'Ufr' => 'ð”˜',
+ 'ufr' => 'ð”²',
+ 'Ugrave' => 'Ù',
+ 'Ugrav' => 'Ù',
+ 'ugrave' => 'ù',
+ 'ugrav' => 'ù',
+ 'uHar' => '⥣',
+ 'uharl' => '↿',
+ 'uharr' => '↾',
+ 'uhblk' => 'â–€',
+ 'ulcorn' => '⌜',
+ 'ulcorner' => '⌜',
+ 'ulcrop' => 'âŒ',
+ 'ultri' => 'â—¸',
+ 'Umacr' => 'Ū',
+ 'umacr' => 'Å«',
+ 'uml' => '¨',
+ 'um' => '¨',
+ 'UnderBar' => '_',
+ 'UnderBrace' => 'âŸ',
+ 'UnderBracket' => '⎵',
+ 'UnderParenthesis' => 'â',
+ 'Union' => '⋃',
+ 'UnionPlus' => '⊎',
+ 'Uogon' => 'Ų',
+ 'uogon' => 'ų',
+ 'Uopf' => 'ð•Œ',
+ 'uopf' => 'ð•¦',
+ 'UpArrow' => '↑',
+ 'Uparrow' => '⇑',
+ 'uparrow' => '↑',
+ 'UpArrowBar' => '⤒',
+ 'UpArrowDownArrow' => '⇅',
+ 'UpDownArrow' => '↕',
+ 'Updownarrow' => '⇕',
+ 'updownarrow' => '↕',
+ 'UpEquilibrium' => '⥮',
+ 'upharpoonleft' => '↿',
+ 'upharpoonright' => '↾',
+ 'uplus' => '⊎',
+ 'UpperLeftArrow' => '↖',
+ 'UpperRightArrow' => '↗',
+ 'Upsi' => 'Ï’',
+ 'upsi' => 'Ï…',
+ 'upsih' => 'Ï’',
+ 'Upsilon' => 'Î¥',
+ 'upsilon' => 'Ï…',
+ 'UpTee' => '⊥',
+ 'UpTeeArrow' => '↥',
+ 'upuparrows' => '⇈',
+ 'urcorn' => 'âŒ',
+ 'urcorner' => 'âŒ',
+ 'urcrop' => '⌎',
+ 'Uring' => 'Å®',
+ 'uring' => 'ů',
+ 'urtri' => 'â—¹',
+ 'Uscr' => 'ð’°',
+ 'uscr' => 'ð“Š',
+ 'utdot' => 'â‹°',
+ 'Utilde' => 'Ũ',
+ 'utilde' => 'Å©',
+ 'utri' => 'â–µ',
+ 'utrif' => 'â–´',
+ 'uuarr' => '⇈',
+ 'Uuml' => 'Ü',
+ 'Uum' => 'Ü',
+ 'uuml' => 'ü',
+ 'uum' => 'ü',
+ 'uwangle' => '⦧',
+ 'vangrt' => '⦜',
+ 'varepsilon' => 'ϵ',
+ 'varkappa' => 'Ï°',
+ 'varnothing' => '∅',
+ 'varphi' => 'Ï•',
+ 'varpi' => 'Ï–',
+ 'varpropto' => 'âˆ',
+ 'vArr' => '⇕',
+ 'varr' => '↕',
+ 'varrho' => 'ϱ',
+ 'varsigma' => 'Ï‚',
+ 'varsubsetneq' => '⊊︀',
+ 'varsubsetneqq' => '⫋︀',
+ 'varsupsetneq' => '⊋︀',
+ 'varsupsetneqq' => '⫌︀',
+ 'vartheta' => 'Ï‘',
+ 'vartriangleleft' => '⊲',
+ 'vartriangleright' => '⊳',
+ 'Vbar' => 'â««',
+ 'vBar' => '⫨',
+ 'vBarv' => 'â«©',
+ 'Vcy' => 'Ð’',
+ 'vcy' => 'в',
+ 'VDash' => '⊫',
+ 'Vdash' => '⊩',
+ 'vDash' => '⊨',
+ 'vdash' => '⊢',
+ 'Vdashl' => '⫦',
+ 'Vee' => 'â‹',
+ 'vee' => '∨',
+ 'veebar' => '⊻',
+ 'veeeq' => '≚',
+ 'vellip' => 'â‹®',
+ 'Verbar' => '‖',
+ 'verbar' => '|',
+ 'Vert' => '‖',
+ 'vert' => '|',
+ 'VerticalBar' => '∣',
+ 'VerticalLine' => '|',
+ 'VerticalSeparator' => 'â˜',
+ 'VerticalTilde' => '≀',
+ 'VeryThinSpace' => ' ',
+ 'Vfr' => 'ð”™',
+ 'vfr' => 'ð”³',
+ 'vltri' => '⊲',
+ 'vnsub' => '⊂⃒',
+ 'vnsup' => '⊃⃒',
+ 'Vopf' => 'ð•',
+ 'vopf' => 'ð•§',
+ 'vprop' => 'âˆ',
+ 'vrtri' => '⊳',
+ 'Vscr' => 'ð’±',
+ 'vscr' => 'ð“‹',
+ 'vsubnE' => '⫋︀',
+ 'vsubne' => '⊊︀',
+ 'vsupnE' => '⫌︀',
+ 'vsupne' => '⊋︀',
+ 'Vvdash' => '⊪',
+ 'vzigzag' => '⦚',
+ 'Wcirc' => 'Å´',
+ 'wcirc' => 'ŵ',
+ 'wedbar' => 'â©Ÿ',
+ 'Wedge' => 'â‹€',
+ 'wedge' => '∧',
+ 'wedgeq' => '≙',
+ 'weierp' => '℘',
+ 'Wfr' => 'ð”š',
+ 'wfr' => 'ð”´',
+ 'Wopf' => 'ð•Ž',
+ 'wopf' => 'ð•¨',
+ 'wp' => '℘',
+ 'wr' => '≀',
+ 'wreath' => '≀',
+ 'Wscr' => 'ð’²',
+ 'wscr' => 'ð“Œ',
+ 'xcap' => 'â‹‚',
+ 'xcirc' => 'â—¯',
+ 'xcup' => '⋃',
+ 'xdtri' => 'â–½',
+ 'Xfr' => 'ð”›',
+ 'xfr' => 'ð”µ',
+ 'xhArr' => '⟺',
+ 'xharr' => '⟷',
+ 'Xi' => 'Ξ',
+ 'xi' => 'ξ',
+ 'xlArr' => '⟸',
+ 'xlarr' => '⟵',
+ 'xmap' => '⟼',
+ 'xnis' => 'â‹»',
+ 'xodot' => '⨀',
+ 'Xopf' => 'ð•',
+ 'xopf' => 'ð•©',
+ 'xoplus' => 'â¨',
+ 'xotime' => '⨂',
+ 'xrArr' => '⟹',
+ 'xrarr' => '⟶',
+ 'Xscr' => 'ð’³',
+ 'xscr' => 'ð“',
+ 'xsqcup' => '⨆',
+ 'xuplus' => '⨄',
+ 'xutri' => 'â–³',
+ 'xvee' => 'â‹',
+ 'xwedge' => 'â‹€',
+ 'Yacute' => 'Ã',
+ 'Yacut' => 'Ã',
+ 'yacute' => 'ý',
+ 'yacut' => 'ý',
+ 'YAcy' => 'Я',
+ 'yacy' => 'Ñ',
+ 'Ycirc' => 'Ŷ',
+ 'ycirc' => 'Å·',
+ 'Ycy' => 'Ы',
+ 'ycy' => 'Ñ‹',
+ 'yen' => 'Â¥',
+ 'ye' => 'Â¥',
+ 'Yfr' => 'ð”œ',
+ 'yfr' => 'ð”¶',
+ 'YIcy' => 'Ї',
+ 'yicy' => 'Ñ—',
+ 'Yopf' => 'ð•',
+ 'yopf' => 'ð•ª',
+ 'Yscr' => 'ð’´',
+ 'yscr' => 'ð“Ž',
+ 'YUcy' => 'Ю',
+ 'yucy' => 'ÑŽ',
+ 'Yuml' => 'Ÿ',
+ 'yuml' => 'ÿ',
+ 'yum' => 'ÿ',
+ 'Zacute' => 'Ź',
+ 'zacute' => 'ź',
+ 'Zcaron' => 'Ž',
+ 'zcaron' => 'ž',
+ 'Zcy' => 'З',
+ 'zcy' => 'з',
+ 'Zdot' => 'Å»',
+ 'zdot' => 'ż',
+ 'zeetrf' => 'ℨ',
+ 'ZeroWidthSpace' => '​',
+ 'Zeta' => 'Ζ',
+ 'zeta' => 'ζ',
+ 'Zfr' => 'ℨ',
+ 'zfr' => 'ð”·',
+ 'ZHcy' => 'Ж',
+ 'zhcy' => 'ж',
+ 'zigrarr' => 'â‡',
+ 'Zopf' => 'ℤ',
+ 'zopf' => 'ð•«',
+ 'Zscr' => 'ð’µ',
+ 'zscr' => 'ð“',
+ 'zwj' => 'â€',
+ 'zwnj' => '‌',
+ );
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Exception.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Exception.php
new file mode 100644
index 0000000..64e97e6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Exception.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Masterminds\HTML5;
+
+/**
+ * The base exception for the HTML5 project.
+ */
+class Exception extends \Exception
+{
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/InstructionProcessor.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/InstructionProcessor.php
new file mode 100644
index 0000000..33a73fc
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/InstructionProcessor.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * A handler for processor instructions.
+ */
+
+namespace Masterminds\HTML5;
+
+/**
+ * Provide an processor to handle embedded instructions.
+ *
+ * XML defines a mechanism for inserting instructions (like PHP) into a
+ * document. These are called "Processor Instructions." The HTML5 parser
+ * provides an opportunity to handle these processor instructions during
+ * the tree-building phase (before the DOM is constructed), which makes
+ * it possible to alter the document as it is being created.
+ *
+ * One could, for example, use this mechanism to execute well-formed PHP
+ * code embedded inside of an HTML5 document.
+ */
+interface InstructionProcessor
+{
+ /**
+ * Process an individual processing instruction.
+ *
+ * The process() function is responsible for doing the following:
+ * - Determining whether $name is an instruction type it can handle.
+ * - Determining what to do with the data passed in.
+ * - Making any subsequent modifications to the DOM by modifying the
+ * DOMElement or its attached DOM tree.
+ *
+ * @param \DOMElement $element The parent element for the current processing instruction.
+ * @param string $name The instruction's name. E.g. `&lt;?php` has the name `php`.
+ * @param string $data All of the data between the opening and closing PI marks.
+ *
+ * @return \DOMElement The element that should be considered "Current". This may just be
+ * the element passed in, but if the processor added more elements,
+ * it may choose to reset the current element to one of the elements
+ * it created. (When in doubt, return the element passed in.)
+ */
+ public function process(\DOMElement $element, $name, $data);
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php
new file mode 100644
index 0000000..490b548
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+use Masterminds\HTML5\Entities;
+
+/**
+ * Manage entity references.
+ *
+ * This is a simple resolver for HTML5 character reference entitites. See Entities for the list of supported entities.
+ */
+class CharacterReference
+{
+ protected static $numeric_mask = array(
+ 0x0,
+ 0x2FFFF,
+ 0,
+ 0xFFFF,
+ );
+
+ /**
+ * Given a name (e.g. 'amp'), lookup the UTF-8 character ('&').
+ *
+ * @param string $name The name to look up.
+ *
+ * @return string The character sequence. In UTF-8 this may be more than one byte.
+ */
+ public static function lookupName($name)
+ {
+ // Do we really want to return NULL here? or FFFD
+ return isset(Entities::$byName[$name]) ? Entities::$byName[$name] : null;
+ }
+
+ /**
+ * Given a decimal number, return the UTF-8 character.
+ *
+ * @param $int
+ *
+ * @return false|string|string[]|null
+ */
+ public static function lookupDecimal($int)
+ {
+ $entity = '&#' . $int . ';';
+
+ // UNTESTED: This may fail on some planes. Couldn't find full documentation
+ // on the value of the mask array.
+ return mb_decode_numericentity($entity, static::$numeric_mask, 'utf-8');
+ }
+
+ /**
+ * Given a hexidecimal number, return the UTF-8 character.
+ *
+ * @param $hexdec
+ *
+ * @return false|string|string[]|null
+ */
+ public static function lookupHex($hexdec)
+ {
+ return static::lookupDecimal(hexdec($hexdec));
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php
new file mode 100644
index 0000000..293d83e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/DOMTreeBuilder.php
@@ -0,0 +1,705 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+use Masterminds\HTML5\Elements;
+use Masterminds\HTML5\InstructionProcessor;
+
+/**
+ * Create an HTML5 DOM tree from events.
+ *
+ * This attempts to create a DOM from events emitted by a parser. This
+ * attempts (but does not guarantee) to up-convert older HTML documents
+ * to HTML5. It does this by applying HTML5's rules, but it will not
+ * change the architecture of the document itself.
+ *
+ * Many of the error correction and quirks features suggested in the specification
+ * are implemented herein; however, not all of them are. Since we do not
+ * assume a graphical user agent, no presentation-specific logic is conducted
+ * during tree building.
+ *
+ * FIXME: The present tree builder does not exactly follow the state machine rules
+ * for insert modes as outlined in the HTML5 spec. The processor needs to be
+ * re-written to accomodate this. See, for example, the Go language HTML5
+ * parser.
+ */
+class DOMTreeBuilder implements EventHandler
+{
+ /**
+ * Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.
+ */
+ const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';
+
+ const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
+
+ const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';
+
+ const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
+
+ const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';
+
+ const NAMESPACE_XMLNS = 'http://www.w3.org/2000/xmlns/';
+
+ const OPT_DISABLE_HTML_NS = 'disable_html_ns';
+
+ const OPT_TARGET_DOC = 'target_document';
+
+ const OPT_IMPLICIT_NS = 'implicit_namespaces';
+
+ /**
+ * Holds the HTML5 element names that causes a namespace switch.
+ *
+ * @var array
+ */
+ protected $nsRoots = array(
+ 'html' => self::NAMESPACE_HTML,
+ 'svg' => self::NAMESPACE_SVG,
+ 'math' => self::NAMESPACE_MATHML,
+ );
+
+ /**
+ * Holds the always available namespaces (which does not require the XMLNS declaration).
+ *
+ * @var array
+ */
+ protected $implicitNamespaces = array(
+ 'xml' => self::NAMESPACE_XML,
+ 'xmlns' => self::NAMESPACE_XMLNS,
+ 'xlink' => self::NAMESPACE_XLINK,
+ );
+
+ /**
+ * Holds a stack of currently active namespaces.
+ *
+ * @var array
+ */
+ protected $nsStack = array();
+
+ /**
+ * Holds the number of namespaces declared by a node.
+ *
+ * @var array
+ */
+ protected $pushes = array();
+
+ /**
+ * Defined in 8.2.5.
+ */
+ const IM_INITIAL = 0;
+
+ const IM_BEFORE_HTML = 1;
+
+ const IM_BEFORE_HEAD = 2;
+
+ const IM_IN_HEAD = 3;
+
+ const IM_IN_HEAD_NOSCRIPT = 4;
+
+ const IM_AFTER_HEAD = 5;
+
+ const IM_IN_BODY = 6;
+
+ const IM_TEXT = 7;
+
+ const IM_IN_TABLE = 8;
+
+ const IM_IN_TABLE_TEXT = 9;
+
+ const IM_IN_CAPTION = 10;
+
+ const IM_IN_COLUMN_GROUP = 11;
+
+ const IM_IN_TABLE_BODY = 12;
+
+ const IM_IN_ROW = 13;
+
+ const IM_IN_CELL = 14;
+
+ const IM_IN_SELECT = 15;
+
+ const IM_IN_SELECT_IN_TABLE = 16;
+
+ const IM_AFTER_BODY = 17;
+
+ const IM_IN_FRAMESET = 18;
+
+ const IM_AFTER_FRAMESET = 19;
+
+ const IM_AFTER_AFTER_BODY = 20;
+
+ const IM_AFTER_AFTER_FRAMESET = 21;
+
+ const IM_IN_SVG = 22;
+
+ const IM_IN_MATHML = 23;
+
+ protected $options = array();
+
+ protected $stack = array();
+
+ protected $current; // Pointer in the tag hierarchy.
+ protected $rules;
+ protected $doc;
+
+ protected $frag;
+
+ protected $processor;
+
+ protected $insertMode = 0;
+
+ /**
+ * Track if we are in an element that allows only inline child nodes.
+ *
+ * @var string|null
+ */
+ protected $onlyInline;
+
+ /**
+ * Quirks mode is enabled by default.
+ * Any document that is missing the DT will be considered to be in quirks mode.
+ */
+ protected $quirks = true;
+
+ protected $errors = array();
+
+ public function __construct($isFragment = false, array $options = array())
+ {
+ $this->options = $options;
+
+ if (isset($options[self::OPT_TARGET_DOC])) {
+ $this->doc = $options[self::OPT_TARGET_DOC];
+ } else {
+ $impl = new \DOMImplementation();
+ // XXX:
+ // Create the doctype. For now, we are always creating HTML5
+ // documents, and attempting to up-convert any older DTDs to HTML5.
+ $dt = $impl->createDocumentType('html');
+ // $this->doc = \DOMImplementation::createDocument(NULL, 'html', $dt);
+ $this->doc = $impl->createDocument(null, '', $dt);
+ $this->doc->encoding = !empty($options['encoding']) ? $options['encoding'] : 'UTF-8';
+ }
+
+ $this->errors = array();
+
+ $this->current = $this->doc; // ->documentElement;
+
+ // Create a rules engine for tags.
+ $this->rules = new TreeBuildingRules();
+
+ $implicitNS = array();
+ if (isset($this->options[self::OPT_IMPLICIT_NS])) {
+ $implicitNS = $this->options[self::OPT_IMPLICIT_NS];
+ } elseif (isset($this->options['implicitNamespaces'])) {
+ $implicitNS = $this->options['implicitNamespaces'];
+ }
+
+ // Fill $nsStack with the defalut HTML5 namespaces, plus the "implicitNamespaces" array taken form $options
+ array_unshift($this->nsStack, $implicitNS + array('' => self::NAMESPACE_HTML) + $this->implicitNamespaces);
+
+ if ($isFragment) {
+ $this->insertMode = static::IM_IN_BODY;
+ $this->frag = $this->doc->createDocumentFragment();
+ $this->current = $this->frag;
+ }
+ }
+
+ /**
+ * Get the document.
+ */
+ public function document()
+ {
+ return $this->doc;
+ }
+
+ /**
+ * Get the DOM fragment for the body.
+ *
+ * This returns a DOMNodeList because a fragment may have zero or more
+ * DOMNodes at its root.
+ *
+ * @see http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#concept-frag-parse-context
+ *
+ * @return \DOMDocumentFragment
+ */
+ public function fragment()
+ {
+ return $this->frag;
+ }
+
+ /**
+ * Provide an instruction processor.
+ *
+ * This is used for handling Processor Instructions as they are
+ * inserted. If omitted, PI's are inserted directly into the DOM tree.
+ *
+ * @param InstructionProcessor $proc
+ */
+ public function setInstructionProcessor(InstructionProcessor $proc)
+ {
+ $this->processor = $proc;
+ }
+
+ public function doctype($name, $idType = 0, $id = null, $quirks = false)
+ {
+ // This is used solely for setting quirks mode. Currently we don't
+ // try to preserve the inbound DT. We convert it to HTML5.
+ $this->quirks = $quirks;
+
+ if ($this->insertMode > static::IM_INITIAL) {
+ $this->parseError('Illegal placement of DOCTYPE tag. Ignoring: ' . $name);
+
+ return;
+ }
+
+ $this->insertMode = static::IM_BEFORE_HTML;
+ }
+
+ /**
+ * Process the start tag.
+ *
+ * @todo - XMLNS namespace handling (we need to parse, even if it's not valid)
+ * - XLink, MathML and SVG namespace handling
+ * - Omission rules: 8.1.2.4 Optional tags
+ *
+ * @param string $name
+ * @param array $attributes
+ * @param bool $selfClosing
+ *
+ * @return int
+ */
+ public function startTag($name, $attributes = array(), $selfClosing = false)
+ {
+ $lname = $this->normalizeTagName($name);
+
+ // Make sure we have an html element.
+ if (!$this->doc->documentElement && 'html' !== $name && !$this->frag) {
+ $this->startTag('html');
+ }
+
+ // Set quirks mode if we're at IM_INITIAL with no doctype.
+ if ($this->insertMode === static::IM_INITIAL) {
+ $this->quirks = true;
+ $this->parseError('No DOCTYPE specified.');
+ }
+
+ // SPECIAL TAG HANDLING:
+ // Spec says do this, and "don't ask."
+ // find the spec where this is defined... looks problematic
+ if ('image' === $name && !($this->insertMode === static::IM_IN_SVG || $this->insertMode === static::IM_IN_MATHML)) {
+ $name = 'img';
+ }
+
+ // Autoclose p tags where appropriate.
+ if ($this->insertMode >= static::IM_IN_BODY && Elements::isA($name, Elements::AUTOCLOSE_P)) {
+ $this->autoclose('p');
+ }
+
+ // Set insert mode:
+ switch ($name) {
+ case 'html':
+ $this->insertMode = static::IM_BEFORE_HEAD;
+ break;
+ case 'head':
+ if ($this->insertMode > static::IM_BEFORE_HEAD) {
+ $this->parseError('Unexpected head tag outside of head context.');
+ } else {
+ $this->insertMode = static::IM_IN_HEAD;
+ }
+ break;
+ case 'body':
+ $this->insertMode = static::IM_IN_BODY;
+ break;
+ case 'svg':
+ $this->insertMode = static::IM_IN_SVG;
+ break;
+ case 'math':
+ $this->insertMode = static::IM_IN_MATHML;
+ break;
+ case 'noscript':
+ if ($this->insertMode === static::IM_IN_HEAD) {
+ $this->insertMode = static::IM_IN_HEAD_NOSCRIPT;
+ }
+ break;
+ }
+
+ // Special case handling for SVG.
+ if ($this->insertMode === static::IM_IN_SVG) {
+ $lname = Elements::normalizeSvgElement($lname);
+ }
+
+ $pushes = 0;
+ // when we found a tag thats appears inside $nsRoots, we have to switch the defalut namespace
+ if (isset($this->nsRoots[$lname]) && $this->nsStack[0][''] !== $this->nsRoots[$lname]) {
+ array_unshift($this->nsStack, array(
+ '' => $this->nsRoots[$lname],
+ ) + $this->nsStack[0]);
+ ++$pushes;
+ }
+ $needsWorkaround = false;
+ if (isset($this->options['xmlNamespaces']) && $this->options['xmlNamespaces']) {
+ // when xmlNamespaces is true a and we found a 'xmlns' or 'xmlns:*' attribute, we should add a new item to the $nsStack
+ foreach ($attributes as $aName => $aVal) {
+ if ('xmlns' === $aName) {
+ $needsWorkaround = $aVal;
+ array_unshift($this->nsStack, array(
+ '' => $aVal,
+ ) + $this->nsStack[0]);
+ ++$pushes;
+ } elseif ('xmlns' === (($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : '')) {
+ array_unshift($this->nsStack, array(
+ substr($aName, $pos + 1) => $aVal,
+ ) + $this->nsStack[0]);
+ ++$pushes;
+ }
+ }
+ }
+
+ if ($this->onlyInline && Elements::isA($lname, Elements::BLOCK_TAG)) {
+ $this->autoclose($this->onlyInline);
+ $this->onlyInline = null;
+ }
+
+ try {
+ $prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : '';
+
+ if (false !== $needsWorkaround) {
+ $xml = "<$lname xmlns=\"$needsWorkaround\" " . (strlen($prefix) && isset($this->nsStack[0][$prefix]) ? ("xmlns:$prefix=\"" . $this->nsStack[0][$prefix] . '"') : '') . '/>';
+
+ $frag = new \DOMDocument('1.0', 'UTF-8');
+ $frag->loadXML($xml);
+
+ $ele = $this->doc->importNode($frag->documentElement, true);
+ } else {
+ if (!isset($this->nsStack[0][$prefix]) || ('' === $prefix && isset($this->options[self::OPT_DISABLE_HTML_NS]) && $this->options[self::OPT_DISABLE_HTML_NS])) {
+ $ele = $this->doc->createElement($lname);
+ } else {
+ $ele = $this->doc->createElementNS($this->nsStack[0][$prefix], $lname);
+ }
+ }
+ } catch (\DOMException $e) {
+ $this->parseError("Illegal tag name: <$lname>. Replaced with <invalid>.");
+ $ele = $this->doc->createElement('invalid');
+ }
+
+ if (Elements::isA($lname, Elements::BLOCK_ONLY_INLINE)) {
+ $this->onlyInline = $lname;
+ }
+
+ // When we add some namespacess, we have to track them. Later, when "endElement" is invoked, we have to remove them.
+ // When we are on a void tag, we do not need to care about namesapce nesting.
+ if ($pushes > 0 && !Elements::isA($name, Elements::VOID_TAG)) {
+ // PHP tends to free the memory used by DOM,
+ // to avoid spl_object_hash collisions whe have to avoid garbage collection of $ele storing it into $pushes
+ // see https://bugs.php.net/bug.php?id=67459
+ $this->pushes[spl_object_hash($ele)] = array($pushes, $ele);
+ }
+
+ foreach ($attributes as $aName => $aVal) {
+ // xmlns attributes can't be set
+ if ('xmlns' === $aName) {
+ continue;
+ }
+
+ if ($this->insertMode === static::IM_IN_SVG) {
+ $aName = Elements::normalizeSvgAttribute($aName);
+ } elseif ($this->insertMode === static::IM_IN_MATHML) {
+ $aName = Elements::normalizeMathMlAttribute($aName);
+ }
+
+ $aVal = (string) $aVal;
+
+ try {
+ $prefix = ($pos = strpos($aName, ':')) ? substr($aName, 0, $pos) : false;
+
+ if ('xmlns' === $prefix) {
+ $ele->setAttributeNS(self::NAMESPACE_XMLNS, $aName, $aVal);
+ } elseif (false !== $prefix && isset($this->nsStack[0][$prefix])) {
+ $ele->setAttributeNS($this->nsStack[0][$prefix], $aName, $aVal);
+ } else {
+ $ele->setAttribute($aName, $aVal);
+ }
+ } catch (\DOMException $e) {
+ $this->parseError("Illegal attribute name for tag $name. Ignoring: $aName");
+ continue;
+ }
+
+ // This is necessary on a non-DTD schema, like HTML5.
+ if ('id' === $aName) {
+ $ele->setIdAttribute('id', true);
+ }
+ }
+
+ if ($this->frag !== $this->current && $this->rules->hasRules($name)) {
+ // Some elements have special processing rules. Handle those separately.
+ $this->current = $this->rules->evaluate($ele, $this->current);
+ } else {
+ // Otherwise, it's a standard element.
+ $this->current->appendChild($ele);
+
+ if (!Elements::isA($name, Elements::VOID_TAG)) {
+ $this->current = $ele;
+ }
+
+ // Self-closing tags should only be respected on foreign elements
+ // (and are implied on void elements)
+ // See: https://www.w3.org/TR/html5/syntax.html#start-tags
+ if (Elements::isHtml5Element($name)) {
+ $selfClosing = false;
+ }
+ }
+
+ // This is sort of a last-ditch attempt to correct for cases where no head/body
+ // elements are provided.
+ if ($this->insertMode <= static::IM_BEFORE_HEAD && 'head' !== $name && 'html' !== $name) {
+ $this->insertMode = static::IM_IN_BODY;
+ }
+
+ // When we are on a void tag, we do not need to care about namesapce nesting,
+ // but we have to remove the namespaces pushed to $nsStack.
+ if ($pushes > 0 && Elements::isA($name, Elements::VOID_TAG)) {
+ // remove the namespaced definded by current node
+ for ($i = 0; $i < $pushes; ++$i) {
+ array_shift($this->nsStack);
+ }
+ }
+
+ if ($selfClosing) {
+ $this->endTag($name);
+ }
+
+ // Return the element mask, which the tokenizer can then use to set
+ // various processing rules.
+ return Elements::element($name);
+ }
+
+ public function endTag($name)
+ {
+ $lname = $this->normalizeTagName($name);
+
+ // Special case within 12.2.6.4.7: An end tag whose tag name is "br" should be treated as an opening tag
+ if ('br' === $name) {
+ $this->parseError('Closing tag encountered for void element br.');
+
+ $this->startTag('br');
+ }
+ // Ignore closing tags for other unary elements.
+ elseif (Elements::isA($name, Elements::VOID_TAG)) {
+ return;
+ }
+
+ if ($this->insertMode <= static::IM_BEFORE_HTML) {
+ // 8.2.5.4.2
+ if (in_array($name, array(
+ 'html',
+ 'br',
+ 'head',
+ 'title',
+ ))) {
+ $this->startTag('html');
+ $this->endTag($name);
+ $this->insertMode = static::IM_BEFORE_HEAD;
+
+ return;
+ }
+
+ // Ignore the tag.
+ $this->parseError('Illegal closing tag at global scope.');
+
+ return;
+ }
+
+ // Special case handling for SVG.
+ if ($this->insertMode === static::IM_IN_SVG) {
+ $lname = Elements::normalizeSvgElement($lname);
+ }
+
+ $cid = spl_object_hash($this->current);
+
+ // XXX: HTML has no parent. What do we do, though,
+ // if this element appears in the wrong place?
+ if ('html' === $lname) {
+ return;
+ }
+
+ // remove the namespaced definded by current node
+ if (isset($this->pushes[$cid])) {
+ for ($i = 0; $i < $this->pushes[$cid][0]; ++$i) {
+ array_shift($this->nsStack);
+ }
+ unset($this->pushes[$cid]);
+ }
+
+ if (!$this->autoclose($lname)) {
+ $this->parseError('Could not find closing tag for ' . $lname);
+ }
+
+ switch ($lname) {
+ case 'head':
+ $this->insertMode = static::IM_AFTER_HEAD;
+ break;
+ case 'body':
+ $this->insertMode = static::IM_AFTER_BODY;
+ break;
+ case 'svg':
+ case 'mathml':
+ $this->insertMode = static::IM_IN_BODY;
+ break;
+ }
+ }
+
+ public function comment($cdata)
+ {
+ // TODO: Need to handle case where comment appears outside of the HTML tag.
+ $node = $this->doc->createComment($cdata);
+ $this->current->appendChild($node);
+ }
+
+ public function text($data)
+ {
+ // XXX: Hmmm.... should we really be this strict?
+ if ($this->insertMode < static::IM_IN_HEAD) {
+ // Per '8.2.5.4.3 The "before head" insertion mode' the characters
+ // " \t\n\r\f" should be ignored but no mention of a parse error. This is
+ // practical as most documents contain these characters. Other text is not
+ // expected here so recording a parse error is necessary.
+ $dataTmp = trim($data, " \t\n\r\f");
+ if (!empty($dataTmp)) {
+ // fprintf(STDOUT, "Unexpected insert mode: %d", $this->insertMode);
+ $this->parseError('Unexpected text. Ignoring: ' . $dataTmp);
+ }
+
+ return;
+ }
+ // fprintf(STDOUT, "Appending text %s.", $data);
+ $node = $this->doc->createTextNode($data);
+ $this->current->appendChild($node);
+ }
+
+ public function eof()
+ {
+ // If the $current isn't the $root, do we need to do anything?
+ }
+
+ public function parseError($msg, $line = 0, $col = 0)
+ {
+ $this->errors[] = sprintf('Line %d, Col %d: %s', $line, $col, $msg);
+ }
+
+ public function getErrors()
+ {
+ return $this->errors;
+ }
+
+ public function cdata($data)
+ {
+ $node = $this->doc->createCDATASection($data);
+ $this->current->appendChild($node);
+ }
+
+ public function processingInstruction($name, $data = null)
+ {
+ // XXX: Ignore initial XML declaration, per the spec.
+ if ($this->insertMode === static::IM_INITIAL && 'xml' === strtolower($name)) {
+ return;
+ }
+
+ // Important: The processor may modify the current DOM tree however it sees fit.
+ if ($this->processor instanceof InstructionProcessor) {
+ $res = $this->processor->process($this->current, $name, $data);
+ if (!empty($res)) {
+ $this->current = $res;
+ }
+
+ return;
+ }
+
+ // Otherwise, this is just a dumb PI element.
+ $node = $this->doc->createProcessingInstruction($name, $data);
+
+ $this->current->appendChild($node);
+ }
+
+ // ==========================================================================
+ // UTILITIES
+ // ==========================================================================
+
+ /**
+ * Apply normalization rules to a tag name.
+ * See sections 2.9 and 8.1.2.
+ *
+ * @param string $tagName
+ *
+ * @return string The normalized tag name.
+ */
+ protected function normalizeTagName($tagName)
+ {
+ /*
+ * Section 2.9 suggests that we should not do this. if (strpos($name, ':') !== false) { // We know from the grammar that there must be at least one other // char besides :, since : is not a legal tag start. $parts = explode(':', $name); return array_pop($parts); }
+ */
+ return $tagName;
+ }
+
+ protected function quirksTreeResolver($name)
+ {
+ throw new \Exception('Not implemented.');
+ }
+
+ /**
+ * Automatically climb the tree and close the closest node with the matching $tag.
+ *
+ * @param string $tagName
+ *
+ * @return bool
+ */
+ protected function autoclose($tagName)
+ {
+ $working = $this->current;
+ do {
+ if (XML_ELEMENT_NODE !== $working->nodeType) {
+ return false;
+ }
+ if ($working->tagName === $tagName) {
+ $this->current = $working->parentNode;
+
+ return true;
+ }
+ } while ($working = $working->parentNode);
+
+ return false;
+ }
+
+ /**
+ * Checks if the given tagname is an ancestor of the present candidate.
+ *
+ * If $this->current or anything above $this->current matches the given tag
+ * name, this returns true.
+ *
+ * @param string $tagName
+ *
+ * @return bool
+ */
+ protected function isAncestor($tagName)
+ {
+ $candidate = $this->current;
+ while (XML_ELEMENT_NODE === $candidate->nodeType) {
+ if ($candidate->tagName === $tagName) {
+ return true;
+ }
+ $candidate = $candidate->parentNode;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the immediate parent element is of the given tagname.
+ *
+ * @param string $tagName
+ *
+ * @return bool
+ */
+ protected function isParent($tagName)
+ {
+ return $this->current->tagName === $tagName;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php
new file mode 100644
index 0000000..9893a71
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+/**
+ * Standard events for HTML5.
+ *
+ * This is roughly analogous to a SAX2 or expat-style interface.
+ * However, it is tuned specifically for HTML5, according to section 8
+ * of the HTML5 specification.
+ *
+ * An event handler receives parser events. For a concrete
+ * implementation, see DOMTreeBuilder.
+ *
+ * Quirks support in the parser is limited to close-in syntax (malformed
+ * tags or attributes). Higher order syntax and semantic issues with a
+ * document (e.g. mismatched tags, illegal nesting, etc.) are the
+ * responsibility of the event handler implementation.
+ *
+ * See HTML5 spec section 8.2.4
+ */
+interface EventHandler
+{
+ const DOCTYPE_NONE = 0;
+
+ const DOCTYPE_PUBLIC = 1;
+
+ const DOCTYPE_SYSTEM = 2;
+
+ /**
+ * A doctype declaration.
+ *
+ * @param string $name The name of the root element.
+ * @param int $idType One of DOCTYPE_NONE, DOCTYPE_PUBLIC, or DOCTYPE_SYSTEM
+ * @param string $id The identifier. For DOCTYPE_PUBLIC, this is the public ID. If DOCTYPE_SYSTEM,
+ * then this is a system ID.
+ * @param bool $quirks Indicates whether the builder should enter quirks mode.
+ */
+ public function doctype($name, $idType = 0, $id = null, $quirks = false);
+
+ /**
+ * A start tag.
+ *
+ * IMPORTANT: The parser watches the return value of this event. If this returns
+ * an integer, the parser will switch TEXTMODE patters according to the int.
+ *
+ * This is how the Tree Builder can tell the Tokenizer when a certain tag should
+ * cause the parser to go into RAW text mode.
+ *
+ * The HTML5 standard requires that the builder is the one that initiates this
+ * step, and this is the only way short of a circular reference that we can
+ * do that.
+ *
+ * Example: if a startTag even for a `script` name is fired, and the startTag()
+ * implementation returns Tokenizer::TEXTMODE_RAW, then the tokenizer will
+ * switch into RAW text mode and consume data until it reaches a closing
+ * `script` tag.
+ *
+ * The textmode is automatically reset to Tokenizer::TEXTMODE_NORMAL when the
+ * closing tag is encounter. **This behavior may change.**
+ *
+ * @param string $name The tag name.
+ * @param array $attributes An array with all of the tag's attributes.
+ * @param bool $selfClosing An indicator of whether or not this tag is self-closing (<foo/>).
+ *
+ * @return int one of the Tokenizer::TEXTMODE_* constants
+ */
+ public function startTag($name, $attributes = array(), $selfClosing = false);
+
+ /**
+ * An end-tag.
+ */
+ public function endTag($name);
+
+ /**
+ * A comment section (unparsed character data).
+ */
+ public function comment($cdata);
+
+ /**
+ * A unit of parsed character data.
+ *
+ * Entities in this text are *already decoded*.
+ */
+ public function text($cdata);
+
+ /**
+ * Indicates that the document has been entirely processed.
+ */
+ public function eof();
+
+ /**
+ * Emitted when the parser encounters an error condition.
+ */
+ public function parseError($msg, $line, $col);
+
+ /**
+ * A CDATA section.
+ *
+ * @param string $data
+ * The unparsed character data
+ */
+ public function cdata($data);
+
+ /**
+ * This is a holdover from the XML spec.
+ *
+ * While user agents don't get PIs, server-side does.
+ *
+ * @param string $name The name of the processor (e.g. 'php').
+ * @param string $data The unparsed data.
+ */
+ public function processingInstruction($name, $data = null);
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php
new file mode 100644
index 0000000..b081ed9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+/**
+ * The FileInputStream loads a file to be parsed.
+ *
+ * So right now we read files into strings and then process the
+ * string. We chose to do this largely for the sake of expediency of
+ * development, and also because we could optimize toward processing
+ * arbitrarily large chunks of the input. But in the future, we'd
+ * really like to rewrite this class to efficiently handle lower level
+ * stream reads (and thus efficiently handle large documents).
+ *
+ * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
+ */
+class FileInputStream extends StringInputStream implements InputStream
+{
+ /**
+ * Load a file input stream.
+ *
+ * @param string $data The file or url path to load.
+ * @param string $encoding The encoding to use for the data.
+ * @param string $debug A fprintf format to use to echo the data on stdout.
+ */
+ public function __construct($data, $encoding = 'UTF-8', $debug = '')
+ {
+ // Get the contents of the file.
+ $content = file_get_contents($data);
+
+ parent::__construct($content, $encoding, $debug);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/InputStream.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/InputStream.php
new file mode 100644
index 0000000..ff4ef22
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/InputStream.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+/**
+ * Interface for stream readers.
+ *
+ * The parser only reads from streams. Various input sources can write
+ * an adapater to this InputStream.
+ *
+ * Currently provided InputStream implementations include
+ * FileInputStream and StringInputStream.
+ *
+ * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
+ */
+interface InputStream extends \Iterator
+{
+ /**
+ * Returns the current line that is being consumed.
+ *
+ * TODO: Move this to the scanner.
+ */
+ public function currentLine();
+
+ /**
+ * Returns the current column of the current line that the tokenizer is at.
+ *
+ * Newlines are column 0. The first char after a newline is column 1.
+ *
+ * @TODO Move this to the scanner.
+ *
+ * @return int The column number.
+ */
+ public function columnOffset();
+
+ /**
+ * Get all characters until EOF.
+ *
+ * This consumes characters until the EOF.
+ */
+ public function remainingChars();
+
+ /**
+ * Read to a particular match (or until $max bytes are consumed).
+ *
+ * This operates on byte sequences, not characters.
+ *
+ * Matches as far as possible until we reach a certain set of bytes
+ * and returns the matched substring.
+ *
+ * @see strcspn
+ *
+ * @param string $bytes Bytes to match.
+ * @param int $max Maximum number of bytes to scan.
+ *
+ * @return mixed Index or false if no match is found. You should use strong
+ * equality when checking the result, since index could be 0.
+ */
+ public function charsUntil($bytes, $max = null);
+
+ /**
+ * Returns the string so long as $bytes matches.
+ *
+ * Matches as far as possible with a certain set of bytes
+ * and returns the matched substring.
+ *
+ * @see strspn
+ *
+ * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
+ * current char, the pointer advances and the char is part of the
+ * substring.
+ * @param int $max The max number of chars to read.
+ */
+ public function charsWhile($bytes, $max = null);
+
+ /**
+ * Unconsume one character.
+ *
+ * @param int $howMany The number of characters to move the pointer back.
+ */
+ public function unconsume($howMany = 1);
+
+ /**
+ * Retrieve the next character without advancing the pointer.
+ */
+ public function peek();
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/ParseError.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/ParseError.php
new file mode 100644
index 0000000..640e516
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/ParseError.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+/**
+ * Emit when the parser has an error.
+ */
+class ParseError extends \Exception
+{
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/README.md b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/README.md
new file mode 100644
index 0000000..9f92957
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/README.md
@@ -0,0 +1,53 @@
+# The Parser Model
+
+The parser model here follows the model in section
+[8.2.1](http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#parsing)
+of the HTML5 specification, though we do not assume a networking layer.
+
+ [ InputStream ] // Generic support for reading input.
+ ||
+ [ Scanner ] // Breaks down the stream into characters.
+ ||
+ [ Tokenizer ] // Groups characters into syntactic
+ ||
+ [ Tree Builder ] // Organizes units into a tree of objects
+ ||
+ [ DOM Document ] // The final state of the parsed document.
+
+
+## InputStream
+
+This is an interface with at least two concrete implementations:
+
+- StringInputStream: Reads an HTML5 string.
+- FileInputStream: Reads an HTML5 file.
+
+## Scanner
+
+This is a mechanical piece of the parser.
+
+## Tokenizer
+
+This follows section 8.4 of the HTML5 spec. It is (roughly) a recursive
+descent parser. (Though there are plenty of optimizations that are less
+than purely functional.
+
+## EventHandler and DOMTree
+
+EventHandler is the interface for tree builders. Since not all
+implementations will necessarily build trees, we've chosen a more
+generic name.
+
+The event handler emits tokens during tokenization.
+
+The DOMTree is an event handler that builds a DOM tree. The output of
+the DOMTree builder is a DOMDocument.
+
+## DOMDocument
+
+PHP has a DOMDocument class built-in (technically, it's part of libxml.)
+We use that, thus rendering the output of this process compatible with
+SimpleXML, QueryPath, and many other XML/HTML processing tools.
+
+For cases where the HTML5 is a fragment of a HTML5 document a
+DOMDocumentFragment is returned instead. This is another built-in class.
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Scanner.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Scanner.php
new file mode 100644
index 0000000..1b25888
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Scanner.php
@@ -0,0 +1,416 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+use Masterminds\HTML5\Exception;
+
+/**
+ * The scanner scans over a given data input to react appropriately to characters.
+ */
+class Scanner
+{
+ const CHARS_HEX = 'abcdefABCDEF01234567890';
+ const CHARS_ALNUM = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
+ const CHARS_ALPHA = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ /**
+ * The string data we're parsing.
+ */
+ private $data;
+
+ /**
+ * The current integer byte position we are in $data.
+ */
+ private $char;
+
+ /**
+ * Length of $data; when $char === $data, we are at the end-of-file.
+ */
+ private $EOF;
+
+ /**
+ * Parse errors.
+ */
+ public $errors = array();
+
+ /**
+ * Create a new Scanner.
+ *
+ * @param string $data Data to parse.
+ * @param string $encoding The encoding to use for the data.
+ *
+ * @throws Exception If the given data cannot be encoded to UTF-8.
+ */
+ public function __construct($data, $encoding = 'UTF-8')
+ {
+ if ($data instanceof InputStream) {
+ @trigger_error('InputStream objects are deprecated since version 2.4 and will be removed in 3.0. Use strings instead.', E_USER_DEPRECATED);
+ $data = (string) $data;
+ }
+
+ $data = UTF8Utils::convertToUTF8($data, $encoding);
+
+ // There is good reason to question whether it makes sense to
+ // do this here, since most of these checks are done during
+ // parsing, and since this check doesn't actually *do* anything.
+ $this->errors = UTF8Utils::checkForIllegalCodepoints($data);
+
+ $data = $this->replaceLinefeeds($data);
+
+ $this->data = $data;
+ $this->char = 0;
+ $this->EOF = strlen($data);
+ }
+
+ /**
+ * Check if upcomming chars match the given sequence.
+ *
+ * This will read the stream for the $sequence. If it's
+ * found, this will return true. If not, return false.
+ * Since this unconsumes any chars it reads, the caller
+ * will still need to read the next sequence, even if
+ * this returns true.
+ *
+ * Example: $this->scanner->sequenceMatches('</script>') will
+ * see if the input stream is at the start of a
+ * '</script>' string.
+ *
+ * @param string $sequence
+ * @param bool $caseSensitive
+ *
+ * @return bool
+ */
+ public function sequenceMatches($sequence, $caseSensitive = true)
+ {
+ $portion = substr($this->data, $this->char, strlen($sequence));
+
+ return $caseSensitive ? $portion === $sequence : 0 === strcasecmp($portion, $sequence);
+ }
+
+ /**
+ * Get the current position.
+ *
+ * @return int The current intiger byte position.
+ */
+ public function position()
+ {
+ return $this->char;
+ }
+
+ /**
+ * Take a peek at the next character in the data.
+ *
+ * @return string The next character.
+ */
+ public function peek()
+ {
+ if (($this->char + 1) < $this->EOF) {
+ return $this->data[$this->char + 1];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the next character.
+ * Note: This advances the pointer.
+ *
+ * @return string The next character.
+ */
+ public function next()
+ {
+ ++$this->char;
+
+ if ($this->char < $this->EOF) {
+ return $this->data[$this->char];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the current character.
+ * Note, this does not advance the pointer.
+ *
+ * @return string The current character.
+ */
+ public function current()
+ {
+ if ($this->char < $this->EOF) {
+ return $this->data[$this->char];
+ }
+
+ return false;
+ }
+
+ /**
+ * Silently consume N chars.
+ *
+ * @param int $count
+ */
+ public function consume($count = 1)
+ {
+ $this->char += $count;
+ }
+
+ /**
+ * Unconsume some of the data.
+ * This moves the data pointer backwards.
+ *
+ * @param int $howMany The number of characters to move the pointer back.
+ */
+ public function unconsume($howMany = 1)
+ {
+ if (($this->char - $howMany) >= 0) {
+ $this->char -= $howMany;
+ }
+ }
+
+ /**
+ * Get the next group of that contains hex characters.
+ * Note, along with getting the characters the pointer in the data will be
+ * moved as well.
+ *
+ * @return string The next group that is hex characters.
+ */
+ public function getHex()
+ {
+ return $this->doCharsWhile(static::CHARS_HEX);
+ }
+
+ /**
+ * Get the next group of characters that are ASCII Alpha characters.
+ * Note, along with getting the characters the pointer in the data will be
+ * moved as well.
+ *
+ * @return string The next group of ASCII alpha characters.
+ */
+ public function getAsciiAlpha()
+ {
+ return $this->doCharsWhile(static::CHARS_ALPHA);
+ }
+
+ /**
+ * Get the next group of characters that are ASCII Alpha characters and numbers.
+ * Note, along with getting the characters the pointer in the data will be
+ * moved as well.
+ *
+ * @return string The next group of ASCII alpha characters and numbers.
+ */
+ public function getAsciiAlphaNum()
+ {
+ return $this->doCharsWhile(static::CHARS_ALNUM);
+ }
+
+ /**
+ * Get the next group of numbers.
+ * Note, along with getting the characters the pointer in the data will be
+ * moved as well.
+ *
+ * @return string The next group of numbers.
+ */
+ public function getNumeric()
+ {
+ return $this->doCharsWhile('0123456789');
+ }
+
+ /**
+ * Consume whitespace.
+ * Whitespace in HTML5 is: formfeed, tab, newline, space.
+ *
+ * @return int The length of the matched whitespaces.
+ */
+ public function whitespace()
+ {
+ if ($this->char >= $this->EOF) {
+ return false;
+ }
+
+ $len = strspn($this->data, "\n\t\f ", $this->char);
+
+ $this->char += $len;
+
+ return $len;
+ }
+
+ /**
+ * Returns the current line that is being consumed.
+ *
+ * @return int The current line number.
+ */
+ public function currentLine()
+ {
+ if (empty($this->EOF) || 0 === $this->char) {
+ return 1;
+ }
+
+ // Add one to $this->char because we want the number for the next
+ // byte to be processed.
+ return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
+ }
+
+ /**
+ * Read chars until something in the mask is encountered.
+ *
+ * @param string $mask
+ *
+ * @return mixed
+ */
+ public function charsUntil($mask)
+ {
+ return $this->doCharsUntil($mask);
+ }
+
+ /**
+ * Read chars as long as the mask matches.
+ *
+ * @param string $mask
+ *
+ * @return int
+ */
+ public function charsWhile($mask)
+ {
+ return $this->doCharsWhile($mask);
+ }
+
+ /**
+ * Returns the current column of the current line that the tokenizer is at.
+ *
+ * Newlines are column 0. The first char after a newline is column 1.
+ *
+ * @return int The column number.
+ */
+ public function columnOffset()
+ {
+ // Short circuit for the first char.
+ if (0 === $this->char) {
+ return 0;
+ }
+
+ // strrpos is weird, and the offset needs to be negative for what we
+ // want (i.e., the last \n before $this->char). This needs to not have
+ // one (to make it point to the next character, the one we want the
+ // position of) added to it because strrpos's behaviour includes the
+ // final offset byte.
+ $backwardFrom = $this->char - 1 - strlen($this->data);
+ $lastLine = strrpos($this->data, "\n", $backwardFrom);
+
+ // However, for here we want the length up until the next byte to be
+ // processed, so add one to the current byte ($this->char).
+ if (false !== $lastLine) {
+ $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
+ } else {
+ // After a newline.
+ $findLengthOf = substr($this->data, 0, $this->char);
+ }
+
+ return UTF8Utils::countChars($findLengthOf);
+ }
+
+ /**
+ * Get all characters until EOF.
+ *
+ * This consumes characters until the EOF.
+ *
+ * @return int The number of characters remaining.
+ */
+ public function remainingChars()
+ {
+ if ($this->char < $this->EOF) {
+ $data = substr($this->data, $this->char);
+ $this->char = $this->EOF;
+
+ return $data;
+ }
+
+ return ''; // false;
+ }
+
+ /**
+ * Replace linefeed characters according to the spec.
+ *
+ * @param $data
+ *
+ * @return string
+ */
+ private function replaceLinefeeds($data)
+ {
+ /*
+ * U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially.
+ * Any CR characters that are followed by LF characters must be removed, and any CR characters not
+ * followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are
+ * represented by LF characters, and there are never any CR characters in the input to the tokenization
+ * stage.
+ */
+ $crlfTable = array(
+ "\0" => "\xEF\xBF\xBD",
+ "\r\n" => "\n",
+ "\r" => "\n",
+ );
+
+ return strtr($data, $crlfTable);
+ }
+
+ /**
+ * Read to a particular match (or until $max bytes are consumed).
+ *
+ * This operates on byte sequences, not characters.
+ *
+ * Matches as far as possible until we reach a certain set of bytes
+ * and returns the matched substring.
+ *
+ * @param string $bytes Bytes to match.
+ * @param int $max Maximum number of bytes to scan.
+ *
+ * @return mixed Index or false if no match is found. You should use strong
+ * equality when checking the result, since index could be 0.
+ */
+ private function doCharsUntil($bytes, $max = null)
+ {
+ if ($this->char >= $this->EOF) {
+ return false;
+ }
+
+ if (0 === $max || $max) {
+ $len = strcspn($this->data, $bytes, $this->char, $max);
+ } else {
+ $len = strcspn($this->data, $bytes, $this->char);
+ }
+
+ $string = (string) substr($this->data, $this->char, $len);
+ $this->char += $len;
+
+ return $string;
+ }
+
+ /**
+ * Returns the string so long as $bytes matches.
+ *
+ * Matches as far as possible with a certain set of bytes
+ * and returns the matched substring.
+ *
+ * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
+ * current char, the pointer advances and the char is part of the
+ * substring.
+ * @param int $max The max number of chars to read.
+ *
+ * @return string
+ */
+ private function doCharsWhile($bytes, $max = null)
+ {
+ if ($this->char >= $this->EOF) {
+ return false;
+ }
+
+ if (0 === $max || $max) {
+ $len = strspn($this->data, $bytes, $this->char, $max);
+ } else {
+ $len = strspn($this->data, $bytes, $this->char);
+ }
+
+ $string = (string) substr($this->data, $this->char, $len);
+ $this->char += $len;
+
+ return $string;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/StringInputStream.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/StringInputStream.php
new file mode 100644
index 0000000..0c213fe
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/StringInputStream.php
@@ -0,0 +1,331 @@
+<?php
+/**
+ * Loads a string to be parsed.
+ */
+
+namespace Masterminds\HTML5\Parser;
+
+/*
+ *
+* Based on code from html5lib:
+
+Copyright 2009 Geoffrey Sneddon <http://gsnedders.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.
+
+*/
+
+// Some conventions:
+// - /* */ indicates verbatim text from the HTML 5 specification
+// MPB: Not sure which version of the spec. Moving from HTML5lib to
+// HTML5-PHP, I have been using this version:
+// http://www.w3.org/TR/2012/CR-html5-20121217/Overview.html#contents
+//
+// - // indicates regular comments
+
+/**
+ * @deprecated since 2.4, to remove in 3.0. Use a string in the scanner instead.
+ */
+class StringInputStream implements InputStream
+{
+ /**
+ * The string data we're parsing.
+ */
+ private $data;
+
+ /**
+ * The current integer byte position we are in $data.
+ */
+ private $char;
+
+ /**
+ * Length of $data; when $char === $data, we are at the end-of-file.
+ */
+ private $EOF;
+
+ /**
+ * Parse errors.
+ */
+ public $errors = array();
+
+ /**
+ * Create a new InputStream wrapper.
+ *
+ * @param string $data Data to parse.
+ * @param string $encoding The encoding to use for the data.
+ * @param string $debug A fprintf format to use to echo the data on stdout.
+ */
+ public function __construct($data, $encoding = 'UTF-8', $debug = '')
+ {
+ $data = UTF8Utils::convertToUTF8($data, $encoding);
+ if ($debug) {
+ fprintf(STDOUT, $debug, $data, strlen($data));
+ }
+
+ // There is good reason to question whether it makes sense to
+ // do this here, since most of these checks are done during
+ // parsing, and since this check doesn't actually *do* anything.
+ $this->errors = UTF8Utils::checkForIllegalCodepoints($data);
+
+ $data = $this->replaceLinefeeds($data);
+
+ $this->data = $data;
+ $this->char = 0;
+ $this->EOF = strlen($data);
+ }
+
+ public function __toString()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Replace linefeed characters according to the spec.
+ */
+ protected function replaceLinefeeds($data)
+ {
+ /*
+ * U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED (LF) characters are treated specially.
+ * Any CR characters that are followed by LF characters must be removed, and any CR characters not
+ * followed by LF characters must be converted to LF characters. Thus, newlines in HTML DOMs are
+ * represented by LF characters, and there are never any CR characters in the input to the tokenization
+ * stage.
+ */
+ $crlfTable = array(
+ "\0" => "\xEF\xBF\xBD",
+ "\r\n" => "\n",
+ "\r" => "\n",
+ );
+
+ return strtr($data, $crlfTable);
+ }
+
+ /**
+ * Returns the current line that the tokenizer is at.
+ */
+ public function currentLine()
+ {
+ if (empty($this->EOF) || 0 === $this->char) {
+ return 1;
+ }
+ // Add one to $this->char because we want the number for the next
+ // byte to be processed.
+ return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
+ }
+
+ /**
+ * @deprecated
+ */
+ public function getCurrentLine()
+ {
+ return $this->currentLine();
+ }
+
+ /**
+ * Returns the current column of the current line that the tokenizer is at.
+ * Newlines are column 0. The first char after a newline is column 1.
+ *
+ * @return int The column number.
+ */
+ public function columnOffset()
+ {
+ // Short circuit for the first char.
+ if (0 === $this->char) {
+ return 0;
+ }
+ // strrpos is weird, and the offset needs to be negative for what we
+ // want (i.e., the last \n before $this->char). This needs to not have
+ // one (to make it point to the next character, the one we want the
+ // position of) added to it because strrpos's behaviour includes the
+ // final offset byte.
+ $backwardFrom = $this->char - 1 - strlen($this->data);
+ $lastLine = strrpos($this->data, "\n", $backwardFrom);
+
+ // However, for here we want the length up until the next byte to be
+ // processed, so add one to the current byte ($this->char).
+ if (false !== $lastLine) {
+ $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
+ } else {
+ // After a newline.
+ $findLengthOf = substr($this->data, 0, $this->char);
+ }
+
+ return UTF8Utils::countChars($findLengthOf);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function getColumnOffset()
+ {
+ return $this->columnOffset();
+ }
+
+ /**
+ * Get the current character.
+ *
+ * @return string The current character.
+ */
+ public function current()
+ {
+ return $this->data[$this->char];
+ }
+
+ /**
+ * Advance the pointer.
+ * This is part of the Iterator interface.
+ */
+ public function next()
+ {
+ ++$this->char;
+ }
+
+ /**
+ * Rewind to the start of the string.
+ */
+ public function rewind()
+ {
+ $this->char = 0;
+ }
+
+ /**
+ * Is the current pointer location valid.
+ *
+ * @return bool Whether the current pointer location is valid.
+ */
+ public function valid()
+ {
+ return $this->char < $this->EOF;
+ }
+
+ /**
+ * Get all characters until EOF.
+ *
+ * This reads to the end of the file, and sets the read marker at the
+ * end of the file.
+ *
+ * Note this performs bounds checking.
+ *
+ * @return string Returns the remaining text. If called when the InputStream is
+ * already exhausted, it returns an empty string.
+ */
+ public function remainingChars()
+ {
+ if ($this->char < $this->EOF) {
+ $data = substr($this->data, $this->char);
+ $this->char = $this->EOF;
+
+ return $data;
+ }
+
+ return ''; // false;
+ }
+
+ /**
+ * Read to a particular match (or until $max bytes are consumed).
+ *
+ * This operates on byte sequences, not characters.
+ *
+ * Matches as far as possible until we reach a certain set of bytes
+ * and returns the matched substring.
+ *
+ * @param string $bytes Bytes to match.
+ * @param int $max Maximum number of bytes to scan.
+ *
+ * @return mixed Index or false if no match is found. You should use strong
+ * equality when checking the result, since index could be 0.
+ */
+ public function charsUntil($bytes, $max = null)
+ {
+ if ($this->char >= $this->EOF) {
+ return false;
+ }
+
+ if (0 === $max || $max) {
+ $len = strcspn($this->data, $bytes, $this->char, $max);
+ } else {
+ $len = strcspn($this->data, $bytes, $this->char);
+ }
+
+ $string = (string) substr($this->data, $this->char, $len);
+ $this->char += $len;
+
+ return $string;
+ }
+
+ /**
+ * Returns the string so long as $bytes matches.
+ *
+ * Matches as far as possible with a certain set of bytes
+ * and returns the matched substring.
+ *
+ * @param string $bytes A mask of bytes to match. If ANY byte in this mask matches the
+ * current char, the pointer advances and the char is part of the
+ * substring.
+ * @param int $max The max number of chars to read.
+ *
+ * @return string
+ */
+ public function charsWhile($bytes, $max = null)
+ {
+ if ($this->char >= $this->EOF) {
+ return false;
+ }
+
+ if (0 === $max || $max) {
+ $len = strspn($this->data, $bytes, $this->char, $max);
+ } else {
+ $len = strspn($this->data, $bytes, $this->char);
+ }
+ $string = (string) substr($this->data, $this->char, $len);
+ $this->char += $len;
+
+ return $string;
+ }
+
+ /**
+ * Unconsume characters.
+ *
+ * @param int $howMany The number of characters to unconsume.
+ */
+ public function unconsume($howMany = 1)
+ {
+ if (($this->char - $howMany) >= 0) {
+ $this->char -= $howMany;
+ }
+ }
+
+ /**
+ * Look ahead without moving cursor.
+ */
+ public function peek()
+ {
+ if (($this->char + 1) <= $this->EOF) {
+ return $this->data[$this->char + 1];
+ }
+
+ return false;
+ }
+
+ public function key()
+ {
+ return $this->char;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php
new file mode 100644
index 0000000..016919a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php
@@ -0,0 +1,1197 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+use Masterminds\HTML5\Elements;
+
+/**
+ * The HTML5 tokenizer.
+ *
+ * The tokenizer's role is reading data from the scanner and gathering it into
+ * semantic units. From the tokenizer, data is emitted to an event handler,
+ * which may (for example) create a DOM tree.
+ *
+ * The HTML5 specification has a detailed explanation of tokenizing HTML5. We
+ * follow that specification to the maximum extent that we can. If you find
+ * a discrepancy that is not documented, please file a bug and/or submit a
+ * patch.
+ *
+ * This tokenizer is implemented as a recursive descent parser.
+ *
+ * Within the API documentation, you may see references to the specific section
+ * of the HTML5 spec that the code attempts to reproduce. Example: 8.2.4.1.
+ * This refers to section 8.2.4.1 of the HTML5 CR specification.
+ *
+ * @see http://www.w3.org/TR/2012/CR-html5-20121217/
+ */
+class Tokenizer
+{
+ protected $scanner;
+
+ protected $events;
+
+ protected $tok;
+
+ /**
+ * Buffer for text.
+ */
+ protected $text = '';
+
+ // When this goes to false, the parser stops.
+ protected $carryOn = true;
+
+ protected $textMode = 0; // TEXTMODE_NORMAL;
+ protected $untilTag = null;
+
+ const CONFORMANT_XML = 'xml';
+ const CONFORMANT_HTML = 'html';
+ protected $mode = self::CONFORMANT_HTML;
+
+ /**
+ * Create a new tokenizer.
+ *
+ * Typically, parsing a document involves creating a new tokenizer, giving
+ * it a scanner (input) and an event handler (output), and then calling
+ * the Tokenizer::parse() method.`
+ *
+ * @param Scanner $scanner A scanner initialized with an input stream.
+ * @param EventHandler $eventHandler An event handler, initialized and ready to receive events.
+ * @param string $mode
+ */
+ public function __construct($scanner, $eventHandler, $mode = self::CONFORMANT_HTML)
+ {
+ $this->scanner = $scanner;
+ $this->events = $eventHandler;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Begin parsing.
+ *
+ * This will begin scanning the document, tokenizing as it goes.
+ * Tokens are emitted into the event handler.
+ *
+ * Tokenizing will continue until the document is completely
+ * read. Errors are emitted into the event handler, but
+ * the parser will attempt to continue parsing until the
+ * entire input stream is read.
+ */
+ public function parse()
+ {
+ do {
+ $this->consumeData();
+ // FIXME: Add infinite loop protection.
+ } while ($this->carryOn);
+ }
+
+ /**
+ * Set the text mode for the character data reader.
+ *
+ * HTML5 defines three different modes for reading text:
+ * - Normal: Read until a tag is encountered.
+ * - RCDATA: Read until a tag is encountered, but skip a few otherwise-
+ * special characters.
+ * - Raw: Read until a special closing tag is encountered (viz. pre, script)
+ *
+ * This allows those modes to be set.
+ *
+ * Normally, setting is done by the event handler via a special return code on
+ * startTag(), but it can also be set manually using this function.
+ *
+ * @param int $textmode One of Elements::TEXT_*.
+ * @param string $untilTag The tag that should stop RAW or RCDATA mode. Normal mode does not
+ * use this indicator.
+ */
+ public function setTextMode($textmode, $untilTag = null)
+ {
+ $this->textMode = $textmode & (Elements::TEXT_RAW | Elements::TEXT_RCDATA);
+ $this->untilTag = $untilTag;
+ }
+
+ /**
+ * Consume a character and make a move.
+ * HTML5 8.2.4.1.
+ */
+ protected function consumeData()
+ {
+ $tok = $this->scanner->current();
+
+ if ('&' === $tok) {
+ // Character reference
+ $ref = $this->decodeCharacterReference();
+ $this->buffer($ref);
+
+ $tok = $this->scanner->current();
+ }
+
+ // Parse tag
+ if ('<' === $tok) {
+ // Any buffered text data can go out now.
+ $this->flushBuffer();
+
+ $tok = $this->scanner->next();
+
+ if ('!' === $tok) {
+ $this->markupDeclaration();
+ } elseif ('/' === $tok) {
+ $this->endTag();
+ } elseif ('?' === $tok) {
+ $this->processingInstruction();
+ } elseif (ctype_alpha($tok)) {
+ $this->tagName();
+ } else {
+ $this->parseError('Illegal tag opening');
+ // TODO is this necessary ?
+ $this->characterData();
+ }
+
+ $tok = $this->scanner->current();
+ }
+
+ if (false === $tok) {
+ // Handle end of document
+ $this->eof();
+ } else {
+ // Parse character
+ switch ($this->textMode) {
+ case Elements::TEXT_RAW:
+ $this->rawText($tok);
+ break;
+
+ case Elements::TEXT_RCDATA:
+ $this->rcdata($tok);
+ break;
+
+ default:
+ if ('<' === $tok || '&' === $tok) {
+ break;
+ }
+
+ // NULL character
+ if ("\00" === $tok) {
+ $this->parseError('Received null character.');
+
+ $this->text .= $tok;
+ $this->scanner->consume();
+
+ break;
+ }
+
+ $this->text .= $this->scanner->charsUntil("<&\0");
+ }
+ }
+
+ return $this->carryOn;
+ }
+
+ /**
+ * Parse anything that looks like character data.
+ *
+ * Different rules apply based on the current text mode.
+ *
+ * @see Elements::TEXT_RAW Elements::TEXT_RCDATA.
+ */
+ protected function characterData()
+ {
+ $tok = $this->scanner->current();
+ if (false === $tok) {
+ return false;
+ }
+ switch ($this->textMode) {
+ case Elements::TEXT_RAW:
+ return $this->rawText($tok);
+ case Elements::TEXT_RCDATA:
+ return $this->rcdata($tok);
+ default:
+ if ('<' === $tok || '&' === $tok) {
+ return false;
+ }
+
+ return $this->text($tok);
+ }
+ }
+
+ /**
+ * This buffers the current token as character data.
+ *
+ * @param string $tok The current token.
+ *
+ * @return bool
+ */
+ protected function text($tok)
+ {
+ // This should never happen...
+ if (false === $tok) {
+ return false;
+ }
+
+ // NULL character
+ if ("\00" === $tok) {
+ $this->parseError('Received null character.');
+ }
+
+ $this->buffer($tok);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ /**
+ * Read text in RAW mode.
+ *
+ * @param string $tok The current token.
+ *
+ * @return bool
+ */
+ protected function rawText($tok)
+ {
+ if (is_null($this->untilTag)) {
+ return $this->text($tok);
+ }
+
+ $sequence = '</' . $this->untilTag . '>';
+ $txt = $this->readUntilSequence($sequence);
+ $this->events->text($txt);
+ $this->setTextMode(0);
+
+ return $this->endTag();
+ }
+
+ /**
+ * Read text in RCDATA mode.
+ *
+ * @param string $tok The current token.
+ *
+ * @return bool
+ */
+ protected function rcdata($tok)
+ {
+ if (is_null($this->untilTag)) {
+ return $this->text($tok);
+ }
+
+ $sequence = '</' . $this->untilTag;
+ $txt = '';
+
+ $caseSensitive = !Elements::isHtml5Element($this->untilTag);
+ while (false !== $tok && !('<' == $tok && ($this->scanner->sequenceMatches($sequence, $caseSensitive)))) {
+ if ('&' == $tok) {
+ $txt .= $this->decodeCharacterReference();
+ $tok = $this->scanner->current();
+ } else {
+ $txt .= $tok;
+ $tok = $this->scanner->next();
+ }
+ }
+ $len = strlen($sequence);
+ $this->scanner->consume($len);
+ $len += $this->scanner->whitespace();
+ if ('>' !== $this->scanner->current()) {
+ $this->parseError('Unclosed RCDATA end tag');
+ }
+
+ $this->scanner->unconsume($len);
+ $this->events->text($txt);
+ $this->setTextMode(0);
+
+ return $this->endTag();
+ }
+
+ /**
+ * If the document is read, emit an EOF event.
+ */
+ protected function eof()
+ {
+ // fprintf(STDOUT, "EOF");
+ $this->flushBuffer();
+ $this->events->eof();
+ $this->carryOn = false;
+ }
+
+ /**
+ * Look for markup.
+ */
+ protected function markupDeclaration()
+ {
+ $tok = $this->scanner->next();
+
+ // Comment:
+ if ('-' == $tok && '-' == $this->scanner->peek()) {
+ $this->scanner->consume(2);
+
+ return $this->comment();
+ } elseif ('D' == $tok || 'd' == $tok) { // Doctype
+ return $this->doctype();
+ } elseif ('[' == $tok) { // CDATA section
+ return $this->cdataSection();
+ }
+
+ // FINISH
+ $this->parseError('Expected <!--, <![CDATA[, or <!DOCTYPE. Got <!%s', $tok);
+ $this->bogusComment('<!');
+
+ return true;
+ }
+
+ /**
+ * Consume an end tag. See section 8.2.4.9.
+ */
+ protected function endTag()
+ {
+ if ('/' != $this->scanner->current()) {
+ return false;
+ }
+ $tok = $this->scanner->next();
+
+ // a-zA-Z -> tagname
+ // > -> parse error
+ // EOF -> parse error
+ // -> parse error
+ if (!ctype_alpha($tok)) {
+ $this->parseError("Expected tag name, got '%s'", $tok);
+ if ("\0" == $tok || false === $tok) {
+ return false;
+ }
+
+ return $this->bogusComment('</');
+ }
+
+ $name = $this->scanner->charsUntil("\n\f \t>");
+ $name = self::CONFORMANT_XML === $this->mode ? $name : strtolower($name);
+ // Trash whitespace.
+ $this->scanner->whitespace();
+
+ $tok = $this->scanner->current();
+ if ('>' != $tok) {
+ $this->parseError("Expected >, got '%s'", $tok);
+ // We just trash stuff until we get to the next tag close.
+ $this->scanner->charsUntil('>');
+ }
+
+ $this->events->endTag($name);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ /**
+ * Consume a tag name and body. See section 8.2.4.10.
+ */
+ protected function tagName()
+ {
+ // We know this is at least one char.
+ $name = $this->scanner->charsWhile(':_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
+ $name = self::CONFORMANT_XML === $this->mode ? $name : strtolower($name);
+ $attributes = array();
+ $selfClose = false;
+
+ // Handle attribute parse exceptions here so that we can
+ // react by trying to build a sensible parse tree.
+ try {
+ do {
+ $this->scanner->whitespace();
+ $this->attribute($attributes);
+ } while (!$this->isTagEnd($selfClose));
+ } catch (ParseError $e) {
+ $selfClose = false;
+ }
+
+ $mode = $this->events->startTag($name, $attributes, $selfClose);
+
+ if (is_int($mode)) {
+ $this->setTextMode($mode, $name);
+ }
+
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ /**
+ * Check if the scanner has reached the end of a tag.
+ */
+ protected function isTagEnd(&$selfClose)
+ {
+ $tok = $this->scanner->current();
+ if ('/' == $tok) {
+ $this->scanner->consume();
+ $this->scanner->whitespace();
+ $tok = $this->scanner->current();
+
+ if ('>' == $tok) {
+ $selfClose = true;
+
+ return true;
+ }
+ if (false === $tok) {
+ $this->parseError('Unexpected EOF inside of tag.');
+
+ return true;
+ }
+ // Basically, we skip the / token and go on.
+ // See 8.2.4.43.
+ $this->parseError("Unexpected '%s' inside of a tag.", $tok);
+
+ return false;
+ }
+
+ if ('>' == $tok) {
+ return true;
+ }
+ if (false === $tok) {
+ $this->parseError('Unexpected EOF inside of tag.');
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parse attributes from inside of a tag.
+ *
+ * @param string[] $attributes
+ *
+ * @return bool
+ *
+ * @throws ParseError
+ */
+ protected function attribute(&$attributes)
+ {
+ $tok = $this->scanner->current();
+ if ('/' == $tok || '>' == $tok || false === $tok) {
+ return false;
+ }
+
+ if ('<' == $tok) {
+ $this->parseError("Unexpected '<' inside of attributes list.");
+ // Push the < back onto the stack.
+ $this->scanner->unconsume();
+ // Let the caller figure out how to handle this.
+ throw new ParseError('Start tag inside of attribute.');
+ }
+
+ $name = strtolower($this->scanner->charsUntil("/>=\n\f\t "));
+
+ if (0 == strlen($name)) {
+ $tok = $this->scanner->current();
+ $this->parseError('Expected an attribute name, got %s.', $tok);
+ // Really, only '=' can be the char here. Everything else gets absorbed
+ // under one rule or another.
+ $name = $tok;
+ $this->scanner->consume();
+ }
+
+ $isValidAttribute = true;
+ // Attribute names can contain most Unicode characters for HTML5.
+ // But method "DOMElement::setAttribute" is throwing exception
+ // because of it's own internal restriction so these have to be filtered.
+ // see issue #23: https://github.com/Masterminds/html5-php/issues/23
+ // and http://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#syntax-attribute-name
+ if (preg_match("/[\x1-\x2C\\/\x3B-\x40\x5B-\x5E\x60\x7B-\x7F]/u", $name)) {
+ $this->parseError('Unexpected characters in attribute name: %s', $name);
+ $isValidAttribute = false;
+ } // There is no limitation for 1st character in HTML5.
+ // But method "DOMElement::setAttribute" is throwing exception for the
+ // characters below so they have to be filtered.
+ // see issue #23: https://github.com/Masterminds/html5-php/issues/23
+ // and http://www.w3.org/TR/2011/WD-html5-20110525/syntax.html#syntax-attribute-name
+ elseif (preg_match('/^[0-9.-]/u', $name)) {
+ $this->parseError('Unexpected character at the begining of attribute name: %s', $name);
+ $isValidAttribute = false;
+ }
+ // 8.1.2.3
+ $this->scanner->whitespace();
+
+ $val = $this->attributeValue();
+ if ($isValidAttribute) {
+ $attributes[$name] = $val;
+ }
+
+ return true;
+ }
+
+ /**
+ * Consume an attribute value. See section 8.2.4.37 and after.
+ *
+ * @return string|null
+ */
+ protected function attributeValue()
+ {
+ if ('=' != $this->scanner->current()) {
+ return null;
+ }
+ $this->scanner->consume();
+ // 8.1.2.3
+ $this->scanner->whitespace();
+
+ $tok = $this->scanner->current();
+ switch ($tok) {
+ case "\n":
+ case "\f":
+ case ' ':
+ case "\t":
+ // Whitespace here indicates an empty value.
+ return null;
+ case '"':
+ case "'":
+ $this->scanner->consume();
+
+ return $this->quotedAttributeValue($tok);
+ case '>':
+ // case '/': // 8.2.4.37 seems to allow foo=/ as a valid attr.
+ $this->parseError('Expected attribute value, got tag end.');
+
+ return null;
+ case '=':
+ case '`':
+ $this->parseError('Expecting quotes, got %s.', $tok);
+
+ return $this->unquotedAttributeValue();
+ default:
+ return $this->unquotedAttributeValue();
+ }
+ }
+
+ /**
+ * Get an attribute value string.
+ *
+ * @param string $quote IMPORTANT: This is a series of chars! Any one of which will be considered
+ * termination of an attribute's value. E.g. "\"'" will stop at either
+ * ' or ".
+ *
+ * @return string The attribute value.
+ */
+ protected function quotedAttributeValue($quote)
+ {
+ $stoplist = "\f" . $quote;
+ $val = '';
+
+ while (true) {
+ $tokens = $this->scanner->charsUntil($stoplist . '&');
+ if (false !== $tokens) {
+ $val .= $tokens;
+ } else {
+ break;
+ }
+
+ $tok = $this->scanner->current();
+ if ('&' == $tok) {
+ $val .= $this->decodeCharacterReference(true);
+ continue;
+ }
+ break;
+ }
+ $this->scanner->consume();
+
+ return $val;
+ }
+
+ protected function unquotedAttributeValue()
+ {
+ $val = '';
+ $tok = $this->scanner->current();
+ while (false !== $tok) {
+ switch ($tok) {
+ case "\n":
+ case "\f":
+ case ' ':
+ case "\t":
+ case '>':
+ break 2;
+
+ case '&':
+ $val .= $this->decodeCharacterReference(true);
+ $tok = $this->scanner->current();
+
+ break;
+
+ case "'":
+ case '"':
+ case '<':
+ case '=':
+ case '`':
+ $this->parseError('Unexpected chars in unquoted attribute value %s', $tok);
+ $val .= $tok;
+ $tok = $this->scanner->next();
+ break;
+
+ default:
+ $val .= $this->scanner->charsUntil("\t\n\f >&\"'<=`");
+
+ $tok = $this->scanner->current();
+ }
+ }
+
+ return $val;
+ }
+
+ /**
+ * Consume malformed markup as if it were a comment.
+ * 8.2.4.44.
+ *
+ * The spec requires that the ENTIRE tag-like thing be enclosed inside of
+ * the comment. So this will generate comments like:
+ *
+ * &lt;!--&lt/+foo&gt;--&gt;
+ *
+ * @param string $leading Prepend any leading characters. This essentially
+ * negates the need to backtrack, but it's sort of a hack.
+ *
+ * @return bool
+ */
+ protected function bogusComment($leading = '')
+ {
+ $comment = $leading;
+ $tokens = $this->scanner->charsUntil('>');
+ if (false !== $tokens) {
+ $comment .= $tokens;
+ }
+ $tok = $this->scanner->current();
+ if (false !== $tok) {
+ $comment .= $tok;
+ }
+
+ $this->flushBuffer();
+ $this->events->comment($comment);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ /**
+ * Read a comment.
+ * Expects the first tok to be inside of the comment.
+ *
+ * @return bool
+ */
+ protected function comment()
+ {
+ $tok = $this->scanner->current();
+ $comment = '';
+
+ // <!-->. Emit an empty comment because 8.2.4.46 says to.
+ if ('>' == $tok) {
+ // Parse error. Emit the comment token.
+ $this->parseError("Expected comment data, got '>'");
+ $this->events->comment('');
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ // Replace NULL with the replacement char.
+ if ("\0" == $tok) {
+ $tok = UTF8Utils::FFFD;
+ }
+ while (!$this->isCommentEnd()) {
+ $comment .= $tok;
+ $tok = $this->scanner->next();
+ }
+
+ $this->events->comment($comment);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ /**
+ * Check if the scanner has reached the end of a comment.
+ *
+ * @return bool
+ */
+ protected function isCommentEnd()
+ {
+ $tok = $this->scanner->current();
+
+ // EOF
+ if (false === $tok) {
+ // Hit the end.
+ $this->parseError('Unexpected EOF in a comment.');
+
+ return true;
+ }
+
+ // If next two tokens are not '--', not the end.
+ if ('-' != $tok || '-' != $this->scanner->peek()) {
+ return false;
+ }
+
+ $this->scanner->consume(2); // Consume '-' and one of '!' or '>'
+
+ // Test for '>'
+ if ('>' == $this->scanner->current()) {
+ return true;
+ }
+ // Test for '!>'
+ if ('!' == $this->scanner->current() && '>' == $this->scanner->peek()) {
+ $this->scanner->consume(); // Consume the last '>'
+ return true;
+ }
+ // Unread '-' and one of '!' or '>';
+ $this->scanner->unconsume(2);
+
+ return false;
+ }
+
+ /**
+ * Parse a DOCTYPE.
+ *
+ * Parse a DOCTYPE declaration. This method has strong bearing on whether or
+ * not Quirksmode is enabled on the event handler.
+ *
+ * @todo This method is a little long. Should probably refactor.
+ *
+ * @return bool
+ */
+ protected function doctype()
+ {
+ // Check that string is DOCTYPE.
+ if ($this->scanner->sequenceMatches('DOCTYPE', false)) {
+ $this->scanner->consume(7);
+ } else {
+ $chars = $this->scanner->charsWhile('DOCTYPEdoctype');
+ $this->parseError('Expected DOCTYPE, got %s', $chars);
+
+ return $this->bogusComment('<!' . $chars);
+ }
+
+ $this->scanner->whitespace();
+ $tok = $this->scanner->current();
+
+ // EOF: die.
+ if (false === $tok) {
+ $this->events->doctype('html5', EventHandler::DOCTYPE_NONE, '', true);
+ $this->eof();
+
+ return true;
+ }
+
+ // NULL char: convert.
+ if ("\0" === $tok) {
+ $this->parseError('Unexpected null character in DOCTYPE.');
+ }
+
+ $stop = " \n\f>";
+ $doctypeName = $this->scanner->charsUntil($stop);
+ // Lowercase ASCII, replace \0 with FFFD
+ $doctypeName = strtolower(strtr($doctypeName, "\0", UTF8Utils::FFFD));
+
+ $tok = $this->scanner->current();
+
+ // If false, emit a parse error, DOCTYPE, and return.
+ if (false === $tok) {
+ $this->parseError('Unexpected EOF in DOCTYPE declaration.');
+ $this->events->doctype($doctypeName, EventHandler::DOCTYPE_NONE, null, true);
+
+ return true;
+ }
+
+ // Short DOCTYPE, like <!DOCTYPE html>
+ if ('>' == $tok) {
+ // DOCTYPE without a name.
+ if (0 == strlen($doctypeName)) {
+ $this->parseError('Expected a DOCTYPE name. Got nothing.');
+ $this->events->doctype($doctypeName, 0, null, true);
+ $this->scanner->consume();
+
+ return true;
+ }
+ $this->events->doctype($doctypeName);
+ $this->scanner->consume();
+
+ return true;
+ }
+ $this->scanner->whitespace();
+
+ $pub = strtoupper($this->scanner->getAsciiAlpha());
+ $white = $this->scanner->whitespace();
+
+ // Get ID, and flag it as pub or system.
+ if (('PUBLIC' == $pub || 'SYSTEM' == $pub) && $white > 0) {
+ // Get the sys ID.
+ $type = 'PUBLIC' == $pub ? EventHandler::DOCTYPE_PUBLIC : EventHandler::DOCTYPE_SYSTEM;
+ $id = $this->quotedString("\0>");
+ if (false === $id) {
+ $this->events->doctype($doctypeName, $type, $pub, false);
+
+ return true;
+ }
+
+ // Premature EOF.
+ if (false === $this->scanner->current()) {
+ $this->parseError('Unexpected EOF in DOCTYPE');
+ $this->events->doctype($doctypeName, $type, $id, true);
+
+ return true;
+ }
+
+ // Well-formed complete DOCTYPE.
+ $this->scanner->whitespace();
+ if ('>' == $this->scanner->current()) {
+ $this->events->doctype($doctypeName, $type, $id, false);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ // If we get here, we have <!DOCTYPE foo PUBLIC "bar" SOME_JUNK
+ // Throw away the junk, parse error, quirks mode, return true.
+ $this->scanner->charsUntil('>');
+ $this->parseError('Malformed DOCTYPE.');
+ $this->events->doctype($doctypeName, $type, $id, true);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ // Else it's a bogus DOCTYPE.
+ // Consume to > and trash.
+ $this->scanner->charsUntil('>');
+
+ $this->parseError('Expected PUBLIC or SYSTEM. Got %s.', $pub);
+ $this->events->doctype($doctypeName, 0, null, true);
+ $this->scanner->consume();
+
+ return true;
+ }
+
+ /**
+ * Utility for reading a quoted string.
+ *
+ * @param string $stopchars Characters (in addition to a close-quote) that should stop the string.
+ * E.g. sometimes '>' is higher precedence than '"' or "'".
+ *
+ * @return mixed String if one is found (quotations omitted).
+ */
+ protected function quotedString($stopchars)
+ {
+ $tok = $this->scanner->current();
+ if ('"' == $tok || "'" == $tok) {
+ $this->scanner->consume();
+ $ret = $this->scanner->charsUntil($tok . $stopchars);
+ if ($this->scanner->current() == $tok) {
+ $this->scanner->consume();
+ } else {
+ // Parse error because no close quote.
+ $this->parseError('Expected %s, got %s', $tok, $this->scanner->current());
+ }
+
+ return $ret;
+ }
+
+ return false;
+ }
+
+ /**
+ * Handle a CDATA section.
+ *
+ * @return bool
+ */
+ protected function cdataSection()
+ {
+ $cdata = '';
+ $this->scanner->consume();
+
+ $chars = $this->scanner->charsWhile('CDAT');
+ if ('CDATA' != $chars || '[' != $this->scanner->current()) {
+ $this->parseError('Expected [CDATA[, got %s', $chars);
+
+ return $this->bogusComment('<![' . $chars);
+ }
+
+ $tok = $this->scanner->next();
+ do {
+ if (false === $tok) {
+ $this->parseError('Unexpected EOF inside CDATA.');
+ $this->bogusComment('<![CDATA[' . $cdata);
+
+ return true;
+ }
+ $cdata .= $tok;
+ $tok = $this->scanner->next();
+ } while (!$this->scanner->sequenceMatches(']]>'));
+
+ // Consume ]]>
+ $this->scanner->consume(3);
+
+ $this->events->cdata($cdata);
+
+ return true;
+ }
+
+ // ================================================================
+ // Non-HTML5
+ // ================================================================
+
+ /**
+ * Handle a processing instruction.
+ *
+ * XML processing instructions are supposed to be ignored in HTML5,
+ * treated as "bogus comments". However, since we're not a user
+ * agent, we allow them. We consume until ?> and then issue a
+ * EventListener::processingInstruction() event.
+ *
+ * @return bool
+ */
+ protected function processingInstruction()
+ {
+ if ('?' != $this->scanner->current()) {
+ return false;
+ }
+
+ $tok = $this->scanner->next();
+ $procName = $this->scanner->getAsciiAlpha();
+ $white = $this->scanner->whitespace();
+
+ // If not a PI, send to bogusComment.
+ if (0 == strlen($procName) || 0 == $white || false == $this->scanner->current()) {
+ $this->parseError("Expected processing instruction name, got $tok");
+ $this->bogusComment('<?' . $tok . $procName);
+
+ return true;
+ }
+
+ $data = '';
+ // As long as it's not the case that the next two chars are ? and >.
+ while (!('?' == $this->scanner->current() && '>' == $this->scanner->peek())) {
+ $data .= $this->scanner->current();
+
+ $tok = $this->scanner->next();
+ if (false === $tok) {
+ $this->parseError('Unexpected EOF in processing instruction.');
+ $this->events->processingInstruction($procName, $data);
+
+ return true;
+ }
+ }
+
+ $this->scanner->consume(2); // Consume the closing tag
+ $this->events->processingInstruction($procName, $data);
+
+ return true;
+ }
+
+ // ================================================================
+ // UTILITY FUNCTIONS
+ // ================================================================
+
+ /**
+ * Read from the input stream until we get to the desired sequene
+ * or hit the end of the input stream.
+ *
+ * @param string $sequence
+ *
+ * @return string
+ */
+ protected function readUntilSequence($sequence)
+ {
+ $buffer = '';
+
+ // Optimization for reading larger blocks faster.
+ $first = substr($sequence, 0, 1);
+ while (false !== $this->scanner->current()) {
+ $buffer .= $this->scanner->charsUntil($first);
+
+ // Stop as soon as we hit the stopping condition.
+ if ($this->scanner->sequenceMatches($sequence, false)) {
+ return $buffer;
+ }
+ $buffer .= $this->scanner->current();
+ $this->scanner->consume();
+ }
+
+ // If we get here, we hit the EOF.
+ $this->parseError('Unexpected EOF during text read.');
+
+ return $buffer;
+ }
+
+ /**
+ * Check if upcomming chars match the given sequence.
+ *
+ * This will read the stream for the $sequence. If it's
+ * found, this will return true. If not, return false.
+ * Since this unconsumes any chars it reads, the caller
+ * will still need to read the next sequence, even if
+ * this returns true.
+ *
+ * Example: $this->scanner->sequenceMatches('</script>') will
+ * see if the input stream is at the start of a
+ * '</script>' string.
+ *
+ * @param string $sequence
+ * @param bool $caseSensitive
+ *
+ * @return bool
+ */
+ protected function sequenceMatches($sequence, $caseSensitive = true)
+ {
+ @trigger_error(__METHOD__ . ' method is deprecated since version 2.4 and will be removed in 3.0. Use Scanner::sequenceMatches() instead.', E_USER_DEPRECATED);
+
+ return $this->scanner->sequenceMatches($sequence, $caseSensitive);
+ }
+
+ /**
+ * Send a TEXT event with the contents of the text buffer.
+ *
+ * This emits an EventHandler::text() event with the current contents of the
+ * temporary text buffer. (The buffer is used to group as much PCDATA
+ * as we can instead of emitting lots and lots of TEXT events.)
+ */
+ protected function flushBuffer()
+ {
+ if ('' === $this->text) {
+ return;
+ }
+ $this->events->text($this->text);
+ $this->text = '';
+ }
+
+ /**
+ * Add text to the temporary buffer.
+ *
+ * @see flushBuffer()
+ *
+ * @param string $str
+ */
+ protected function buffer($str)
+ {
+ $this->text .= $str;
+ }
+
+ /**
+ * Emit a parse error.
+ *
+ * A parse error always returns false because it never consumes any
+ * characters.
+ *
+ * @param string $msg
+ *
+ * @return string
+ */
+ protected function parseError($msg)
+ {
+ $args = func_get_args();
+
+ if (count($args) > 1) {
+ array_shift($args);
+ $msg = vsprintf($msg, $args);
+ }
+
+ $line = $this->scanner->currentLine();
+ $col = $this->scanner->columnOffset();
+ $this->events->parseError($msg, $line, $col);
+
+ return false;
+ }
+
+ /**
+ * Decode a character reference and return the string.
+ *
+ * If $inAttribute is set to true, a bare & will be returned as-is.
+ *
+ * @param bool $inAttribute Set to true if the text is inside of an attribute value.
+ * false otherwise.
+ *
+ * @return string
+ */
+ protected function decodeCharacterReference($inAttribute = false)
+ {
+ // Next char after &.
+ $tok = $this->scanner->next();
+ $start = $this->scanner->position();
+
+ if (false === $tok) {
+ return '&';
+ }
+
+ // These indicate not an entity. We return just
+ // the &.
+ if ("\t" === $tok || "\n" === $tok || "\f" === $tok || ' ' === $tok || '&' === $tok || '<' === $tok) {
+ // $this->scanner->next();
+ return '&';
+ }
+
+ // Numeric entity
+ if ('#' === $tok) {
+ $tok = $this->scanner->next();
+
+ if (false === $tok) {
+ $this->parseError('Expected &#DEC; &#HEX;, got EOF');
+ $this->scanner->unconsume(1);
+
+ return '&';
+ }
+
+ // Hexidecimal encoding.
+ // X[0-9a-fA-F]+;
+ // x[0-9a-fA-F]+;
+ if ('x' === $tok || 'X' === $tok) {
+ $tok = $this->scanner->next(); // Consume x
+
+ // Convert from hex code to char.
+ $hex = $this->scanner->getHex();
+ if (empty($hex)) {
+ $this->parseError('Expected &#xHEX;, got &#x%s', $tok);
+ // We unconsume because we don't know what parser rules might
+ // be in effect for the remaining chars. For example. '&#>'
+ // might result in a specific parsing rule inside of tag
+ // contexts, while not inside of pcdata context.
+ $this->scanner->unconsume(2);
+
+ return '&';
+ }
+ $entity = CharacterReference::lookupHex($hex);
+ } // Decimal encoding.
+ // [0-9]+;
+ else {
+ // Convert from decimal to char.
+ $numeric = $this->scanner->getNumeric();
+ if (false === $numeric) {
+ $this->parseError('Expected &#DIGITS;, got &#%s', $tok);
+ $this->scanner->unconsume(2);
+
+ return '&';
+ }
+ $entity = CharacterReference::lookupDecimal($numeric);
+ }
+ } elseif ('=' === $tok && $inAttribute) {
+ return '&';
+ } else { // String entity.
+ // Attempt to consume a string up to a ';'.
+ // [a-zA-Z0-9]+;
+ $cname = $this->scanner->getAsciiAlphaNum();
+ $entity = CharacterReference::lookupName($cname);
+
+ // When no entity is found provide the name of the unmatched string
+ // and continue on as the & is not part of an entity. The & will
+ // be converted to &amp; elsewhere.
+ if (null === $entity) {
+ if (!$inAttribute || '' === $cname) {
+ $this->parseError("No match in entity table for '%s'", $cname);
+ }
+ $this->scanner->unconsume($this->scanner->position() - $start);
+
+ return '&';
+ }
+ }
+
+ // The scanner has advanced the cursor for us.
+ $tok = $this->scanner->current();
+
+ // We have an entity. We're done here.
+ if (';' === $tok) {
+ $this->scanner->consume();
+
+ return $entity;
+ }
+
+ // Failing to match ; means unconsume the entire string.
+ $this->scanner->unconsume($this->scanner->position() - $start);
+
+ $this->parseError('Expected &ENTITY;, got &ENTITY%s (no trailing ;) ', $tok);
+
+ return '&';
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php
new file mode 100644
index 0000000..00d3951
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+/**
+ * Handles special-case rules for the DOM tree builder.
+ *
+ * Many tags have special rules that need to be accomodated on an
+ * individual basis. This class handles those rules.
+ *
+ * See section 8.1.2.4 of the spec.
+ *
+ * @todo - colgroup and col special behaviors
+ * - body and head special behaviors
+ */
+class TreeBuildingRules
+{
+ protected static $tags = array(
+ 'li' => 1,
+ 'dd' => 1,
+ 'dt' => 1,
+ 'rt' => 1,
+ 'rp' => 1,
+ 'tr' => 1,
+ 'th' => 1,
+ 'td' => 1,
+ 'thead' => 1,
+ 'tfoot' => 1,
+ 'tbody' => 1,
+ 'table' => 1,
+ 'optgroup' => 1,
+ 'option' => 1,
+ );
+
+ /**
+ * Returns true if the given tagname has special processing rules.
+ */
+ public function hasRules($tagname)
+ {
+ return isset(static::$tags[$tagname]);
+ }
+
+ /**
+ * Evaluate the rule for the current tag name.
+ *
+ * This may modify the existing DOM.
+ *
+ * @return \DOMElement The new Current DOM element.
+ */
+ public function evaluate($new, $current)
+ {
+ switch ($new->tagName) {
+ case 'li':
+ return $this->handleLI($new, $current);
+ case 'dt':
+ case 'dd':
+ return $this->handleDT($new, $current);
+ case 'rt':
+ case 'rp':
+ return $this->handleRT($new, $current);
+ case 'optgroup':
+ return $this->closeIfCurrentMatches($new, $current, array(
+ 'optgroup',
+ ));
+ case 'option':
+ return $this->closeIfCurrentMatches($new, $current, array(
+ 'option',
+ ));
+ case 'tr':
+ return $this->closeIfCurrentMatches($new, $current, array(
+ 'tr',
+ ));
+ case 'td':
+ case 'th':
+ return $this->closeIfCurrentMatches($new, $current, array(
+ 'th',
+ 'td',
+ ));
+ case 'tbody':
+ case 'thead':
+ case 'tfoot':
+ case 'table': // Spec isn't explicit about this, but it's necessary.
+
+ return $this->closeIfCurrentMatches($new, $current, array(
+ 'thead',
+ 'tfoot',
+ 'tbody',
+ ));
+ }
+
+ return $current;
+ }
+
+ protected function handleLI($ele, $current)
+ {
+ return $this->closeIfCurrentMatches($ele, $current, array(
+ 'li',
+ ));
+ }
+
+ protected function handleDT($ele, $current)
+ {
+ return $this->closeIfCurrentMatches($ele, $current, array(
+ 'dt',
+ 'dd',
+ ));
+ }
+
+ protected function handleRT($ele, $current)
+ {
+ return $this->closeIfCurrentMatches($ele, $current, array(
+ 'rt',
+ 'rp',
+ ));
+ }
+
+ protected function closeIfCurrentMatches($ele, $current, $match)
+ {
+ if (in_array($current->tagName, $match, true)) {
+ $current->parentNode->appendChild($ele);
+ } else {
+ $current->appendChild($ele);
+ }
+
+ return $ele;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/UTF8Utils.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/UTF8Utils.php
new file mode 100644
index 0000000..f6a70bf
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Parser/UTF8Utils.php
@@ -0,0 +1,183 @@
+<?php
+
+namespace Masterminds\HTML5\Parser;
+
+/*
+Portions based on code from html5lib files with the following copyright:
+
+Copyright 2009 Geoffrey Sneddon <http://gsnedders.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.
+*/
+
+use Masterminds\HTML5\Exception;
+
+class UTF8Utils
+{
+ /**
+ * The Unicode replacement character.
+ */
+ const FFFD = "\xEF\xBF\xBD";
+
+ /**
+ * Count the number of characters in a string.
+ * UTF-8 aware. This will try (in order) iconv, MB, libxml, and finally a custom counter.
+ *
+ * @param string $string
+ *
+ * @return int
+ */
+ public static function countChars($string)
+ {
+ // Get the length for the string we need.
+ if (function_exists('mb_strlen')) {
+ return mb_strlen($string, 'utf-8');
+ }
+
+ if (function_exists('iconv_strlen')) {
+ return iconv_strlen($string, 'utf-8');
+ }
+
+ if (function_exists('utf8_decode')) {
+ // MPB: Will this work? Won't certain decodes lead to two chars
+ // extrapolated out of 2-byte chars?
+ return strlen(utf8_decode($string));
+ }
+
+ $count = count_chars($string);
+
+ // 0x80 = 0x7F - 0 + 1 (one added to get inclusive range)
+ // 0x33 = 0xF4 - 0x2C + 1 (one added to get inclusive range)
+ return array_sum(array_slice($count, 0, 0x80)) + array_sum(array_slice($count, 0xC2, 0x33));
+ }
+
+ /**
+ * Convert data from the given encoding to UTF-8.
+ *
+ * This has not yet been tested with charactersets other than UTF-8.
+ * It should work with ISO-8859-1/-13 and standard Latin Win charsets.
+ *
+ * @param string $data The data to convert
+ * @param string $encoding A valid encoding. Examples: http://www.php.net/manual/en/mbstring.supported-encodings.php
+ *
+ * @return string
+ */
+ public static function convertToUTF8($data, $encoding = 'UTF-8')
+ {
+ /*
+ * From the HTML5 spec: Given an encoding, the bytes in the input stream must be converted
+ * to Unicode characters for the tokeniser, as described by the rules for that encoding,
+ * except that the leading U+FEFF BYTE ORDER MARK character, if any, must not be stripped
+ * by the encoding layer (it is stripped by the rule below). Bytes or sequences of bytes
+ * in the original byte stream that could not be converted to Unicode characters must be
+ * converted to U+FFFD REPLACEMENT CHARACTER code points.
+ */
+
+ // mb_convert_encoding is chosen over iconv because of a bug. The best
+ // details for the bug are on http://us1.php.net/manual/en/function.iconv.php#108643
+ // which contains links to the actual but reports as well as work around
+ // details.
+ if (function_exists('mb_convert_encoding')) {
+ // mb library has the following behaviors:
+ // - UTF-16 surrogates result in false.
+ // - Overlongs and outside Plane 16 result in empty strings.
+
+ // Before we run mb_convert_encoding we need to tell it what to do with
+ // characters it does not know. This could be different than the parent
+ // application executing this library so we store the value, change it
+ // to our needs, and then change it back when we are done. This feels
+ // a little excessive and it would be great if there was a better way.
+ $save = mb_substitute_character();
+ mb_substitute_character('none');
+ $data = mb_convert_encoding($data, 'UTF-8', $encoding);
+ mb_substitute_character($save);
+ }
+ // @todo Get iconv running in at least some environments if that is possible.
+ elseif (function_exists('iconv') && 'auto' !== $encoding) {
+ // fprintf(STDOUT, "iconv found\n");
+ // iconv has the following behaviors:
+ // - Overlong representations are ignored.
+ // - Beyond Plane 16 is replaced with a lower char.
+ // - Incomplete sequences generate a warning.
+ $data = @iconv($encoding, 'UTF-8//IGNORE', $data);
+ } else {
+ throw new Exception('Not implemented, please install mbstring or iconv');
+ }
+
+ /*
+ * One leading U+FEFF BYTE ORDER MARK character must be ignored if any are present.
+ */
+ if ("\xEF\xBB\xBF" === substr($data, 0, 3)) {
+ $data = substr($data, 3);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Checks for Unicode code points that are not valid in a document.
+ *
+ * @param string $data A string to analyze
+ *
+ * @return array An array of (string) error messages produced by the scanning
+ */
+ public static function checkForIllegalCodepoints($data)
+ {
+ // Vestigal error handling.
+ $errors = array();
+
+ /*
+ * All U+0000 null characters in the input must be replaced by U+FFFD REPLACEMENT CHARACTERs.
+ * Any occurrences of such characters is a parse error.
+ */
+ for ($i = 0, $count = substr_count($data, "\0"); $i < $count; ++$i) {
+ $errors[] = 'null-character';
+ }
+
+ /*
+ * Any occurrences of any characters in the ranges U+0001 to U+0008, U+000B, U+000E to U+001F, U+007F
+ * to U+009F, U+D800 to U+DFFF , U+FDD0 to U+FDEF, and characters U+FFFE, U+FFFF, U+1FFFE, U+1FFFF,
+ * U+2FFFE, U+2FFFF, U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE, U+6FFFF, U+7FFFE,
+ * U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF, U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF,
+ * U+DFFFE, U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, and U+10FFFF are parse errors.
+ * (These are all control characters or permanently undefined Unicode characters.)
+ */
+ // Check PCRE is loaded.
+ $count = preg_match_all(
+ '/(?:
+ [\x01-\x08\x0B\x0E-\x1F\x7F] # U+0001 to U+0008, U+000B, U+000E to U+001F and U+007F
+ |
+ \xC2[\x80-\x9F] # U+0080 to U+009F
+ |
+ \xED(?:\xA0[\x80-\xFF]|[\xA1-\xBE][\x00-\xFF]|\xBF[\x00-\xBF]) # U+D800 to U+DFFFF
+ |
+ \xEF\xB7[\x90-\xAF] # U+FDD0 to U+FDEF
+ |
+ \xEF\xBF[\xBE\xBF] # U+FFFE and U+FFFF
+ |
+ [\xF0-\xF4][\x8F-\xBF]\xBF[\xBE\xBF] # U+nFFFE and U+nFFFF (1 <= n <= 10_{16})
+ )/x', $data, $matches);
+ for ($i = 0; $i < $count; ++$i) {
+ $errors[] = 'invalid-codepoint';
+ }
+
+ return $errors;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php
new file mode 100644
index 0000000..e9421a1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/HTML5Entities.php
@@ -0,0 +1,1533 @@
+<?php
+/**
+ * @file
+ * This contains HTML5 entities to use with serializing.
+ *
+ * The list here is mildly different from the list at Entities because
+ * that list was generated from the w3c. It contains some entities that are
+ * not entirely proper such as &am; which maps to &. This list is meant to be
+ * a fallback for PHP versions prior to PHP 5.4 when dealing with encoding.
+ */
+
+namespace Masterminds\HTML5\Serializer;
+
+/**
+ * A mapping of entities to their html5 representation.
+ * Used for older PHP
+ * versions that don't have the mapping.
+ */
+class HTML5Entities
+{
+ public static $map = array(
+ ' ' => '&Tab;',
+ "\n" => '&NewLine;',
+ '!' => '&excl;',
+ '"' => '&quot;',
+ '#' => '&num;',
+ '$' => '&dollar;',
+ '%' => '&percnt;',
+ '&' => '&amp;',
+ '\'' => '&apos;',
+ '(' => '&lpar;',
+ ')' => '&rpar;',
+ '*' => '&ast;',
+ '+' => '&plus;',
+ ',' => '&comma;',
+ '.' => '&period;',
+ '/' => '&sol;',
+ ':' => '&colon;',
+ ';' => '&semi;',
+ '<' => '&lt;',
+ '<⃒' => '&nvlt',
+ '=' => '&equals;',
+ '=⃥' => '&bne',
+ '>' => '&gt;',
+ '>⃒' => '&nvgt',
+ '?' => '&quest;',
+ '@' => '&commat;',
+ '[' => '&lbrack;',
+ '\\' => '&bsol;',
+ ']' => '&rsqb;',
+ '^' => '&Hat;',
+ '_' => '&lowbar;',
+ '`' => '&grave;',
+ 'fj' => '&fjlig',
+ '{' => '&lbrace;',
+ '|' => '&vert;',
+ '}' => '&rcub;',
+ ' ' => '&nbsp;',
+ '¡' => '&iexcl;',
+ '¢' => '&cent;',
+ '£' => '&pound;',
+ '¤' => '&curren;',
+ 'Â¥' => '&yen;',
+ '¦' => '&brvbar;',
+ '§' => '&sect;',
+ '¨' => '&DoubleDot;',
+ '©' => '&copy;',
+ 'ª' => '&ordf;',
+ '«' => '&laquo;',
+ '¬' => '&not;',
+ '­' => '&shy;',
+ '®' => '&reg;',
+ '¯' => '&macr;',
+ '°' => '&deg;',
+ '±' => '&plusmn;',
+ '²' => '&sup2;',
+ '³' => '&sup3;',
+ '´' => '&DiacriticalAcute;',
+ 'µ' => '&micro;',
+ '¶' => '&para;',
+ '·' => '&CenterDot;',
+ '¸' => '&Cedilla;',
+ '¹' => '&sup1;',
+ 'º' => '&ordm;',
+ '»' => '&raquo;',
+ '¼' => '&frac14;',
+ '½' => '&half;',
+ '¾' => '&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;',
+ 'Ä€' => '&Amacr;',
+ 'Ä' => '&amacr;',
+ 'Ä‚' => '&Abreve;',
+ 'ă' => '&abreve;',
+ 'Ä„' => '&Aogon;',
+ 'Ä…' => '&aogon;',
+ 'Ć' => '&Cacute;',
+ 'ć' => '&cacute;',
+ 'Ĉ' => '&Ccirc;',
+ 'ĉ' => '&ccirc;',
+ 'ÄŠ' => '&Cdot;',
+ 'Ä‹' => '&cdot;',
+ 'Č' => '&Ccaron;',
+ 'Ä' => '&ccaron;',
+ 'ÄŽ' => '&Dcaron;',
+ 'Ä' => '&dcaron;',
+ 'Ä' => '&Dstrok;',
+ 'Ä‘' => '&dstrok;',
+ 'Ä’' => '&Emacr;',
+ 'Ä“' => '&emacr;',
+ 'Ä–' => '&Edot;',
+ 'Ä—' => '&edot;',
+ 'Ę' => '&Eogon;',
+ 'Ä™' => '&eogon;',
+ 'Äš' => '&Ecaron;',
+ 'Ä›' => '&ecaron;',
+ 'Ĝ' => '&Gcirc;',
+ 'Ä' => '&gcirc;',
+ 'Äž' => '&Gbreve;',
+ 'ÄŸ' => '&gbreve;',
+ 'Ä ' => '&Gdot;',
+ 'Ä¡' => '&gdot;',
+ 'Ä¢' => '&Gcedil;',
+ 'Ĥ' => '&Hcirc;',
+ 'Ä¥' => '&hcirc;',
+ 'Ħ' => '&Hstrok;',
+ 'ħ' => '&hstrok;',
+ 'Ĩ' => '&Itilde;',
+ 'Ä©' => '&itilde;',
+ 'Ī' => '&Imacr;',
+ 'Ä«' => '&imacr;',
+ 'Ä®' => '&Iogon;',
+ 'į' => '&iogon;',
+ 'Ä°' => '&Idot;',
+ 'ı' => '&inodot;',
+ 'IJ' => '&IJlig;',
+ 'ij' => '&ijlig;',
+ 'Ä´' => '&Jcirc;',
+ 'ĵ' => '&jcirc;',
+ 'Ķ' => '&Kcedil;',
+ 'Ä·' => '&kcedil;',
+ 'ĸ' => '&kgreen;',
+ 'Ĺ' => '&Lacute;',
+ 'ĺ' => '&lacute;',
+ 'Ä»' => '&Lcedil;',
+ 'ļ' => '&lcedil;',
+ 'Ľ' => '&Lcaron;',
+ 'ľ' => '&lcaron;',
+ 'Ä¿' => '&Lmidot;',
+ 'Å€' => '&lmidot;',
+ 'Å' => '&Lstrok;',
+ 'Å‚' => '&lstrok;',
+ 'Ń' => '&Nacute;',
+ 'Å„' => '&nacute;',
+ 'Å…' => '&Ncedil;',
+ 'ņ' => '&ncedil;',
+ 'Ň' => '&Ncaron;',
+ 'ň' => '&ncaron;',
+ 'ʼn' => '&napos;',
+ 'ÅŠ' => '&ENG;',
+ 'Å‹' => '&eng;',
+ 'Ō' => '&Omacr;',
+ 'Å' => '&omacr;',
+ 'Å' => '&Odblac;',
+ 'Å‘' => '&odblac;',
+ 'Å’' => '&OElig;',
+ 'Å“' => '&oelig;',
+ 'Å”' => '&Racute;',
+ 'Å•' => '&racute;',
+ 'Å–' => '&Rcedil;',
+ 'Å—' => '&rcedil;',
+ 'Ř' => '&Rcaron;',
+ 'Å™' => '&rcaron;',
+ 'Åš' => '&Sacute;',
+ 'Å›' => '&sacute;',
+ 'Ŝ' => '&Scirc;',
+ 'Å' => '&scirc;',
+ 'Åž' => '&Scedil;',
+ 'ÅŸ' => '&scedil;',
+ 'Å ' => '&Scaron;',
+ 'Å¡' => '&scaron;',
+ 'Å¢' => '&Tcedil;',
+ 'Å£' => '&tcedil;',
+ 'Ť' => '&Tcaron;',
+ 'Å¥' => '&tcaron;',
+ 'Ŧ' => '&Tstrok;',
+ 'ŧ' => '&tstrok;',
+ 'Ũ' => '&Utilde;',
+ 'Å©' => '&utilde;',
+ 'Ū' => '&Umacr;',
+ 'Å«' => '&umacr;',
+ 'Ŭ' => '&Ubreve;',
+ 'Å­' => '&ubreve;',
+ 'Å®' => '&Uring;',
+ 'ů' => '&uring;',
+ 'Å°' => '&Udblac;',
+ 'ű' => '&udblac;',
+ 'Ų' => '&Uogon;',
+ 'ų' => '&uogon;',
+ 'Å´' => '&Wcirc;',
+ 'ŵ' => '&wcirc;',
+ 'Ŷ' => '&Ycirc;',
+ 'Å·' => '&ycirc;',
+ 'Ÿ' => '&Yuml;',
+ 'Ź' => '&Zacute;',
+ 'ź' => '&zacute;',
+ 'Å»' => '&Zdot;',
+ 'ż' => '&zdot;',
+ 'Ž' => '&Zcaron;',
+ 'ž' => '&zcaron;',
+ 'Æ’' => '&fnof;',
+ 'Ƶ' => '&imped;',
+ 'ǵ' => '&gacute;',
+ 'È·' => '&jmath;',
+ 'ˆ' => '&circ;',
+ 'ˇ' => '&Hacek;',
+ '˘' => '&Breve;',
+ 'Ë™' => '&dot;',
+ 'Ëš' => '&ring;',
+ 'Ë›' => '&ogon;',
+ '˜' => '&DiacriticalTilde;',
+ 'Ë' => '&DiacriticalDoubleAcute;',
+ 'Ì‘' => '&DownBreve;',
+ 'Α' => '&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;',
+ 'ε' => '&epsi;',
+ 'ζ' => '&zeta;',
+ 'η' => '&eta;',
+ 'θ' => '&theta;',
+ 'ι' => '&iota;',
+ 'κ' => '&kappa;',
+ 'λ' => '&lambda;',
+ 'μ' => '&mu;',
+ 'ν' => '&nu;',
+ 'ξ' => '&xi;',
+ 'ο' => '&omicron;',
+ 'Ï€' => '&pi;',
+ 'Ï' => '&rho;',
+ 'Ï‚' => '&sigmav;',
+ 'σ' => '&sigma;',
+ 'Ï„' => '&tau;',
+ 'Ï…' => '&upsi;',
+ 'φ' => '&phi;',
+ 'χ' => '&chi;',
+ 'ψ' => '&psi;',
+ 'ω' => '&omega;',
+ 'Ï‘' => '&thetasym;',
+ 'Ï’' => '&upsih;',
+ 'Ï•' => '&straightphi;',
+ 'Ï–' => '&piv;',
+ 'Ϝ' => '&Gammad;',
+ 'Ï' => '&gammad;',
+ 'Ï°' => '&varkappa;',
+ 'ϱ' => '&rhov;',
+ 'ϵ' => '&straightepsilon;',
+ '϶' => '&backepsilon;',
+ 'Ð' => '&IOcy;',
+ 'Ђ' => '&DJcy;',
+ 'Ѓ' => '&GJcy;',
+ 'Є' => '&Jukcy;',
+ 'Ð…' => '&DScy;',
+ 'І' => '&Iukcy;',
+ 'Ї' => '&YIcy;',
+ 'Ј' => '&Jsercy;',
+ 'Љ' => '&LJcy;',
+ 'Њ' => '&NJcy;',
+ 'Ћ' => '&TSHcy;',
+ 'Ќ' => '&KJcy;',
+ 'ÐŽ' => '&Ubrcy;',
+ 'Ð' => '&DZcy;',
+ 'Ð' => '&Acy;',
+ 'Б' => '&Bcy;',
+ 'Ð’' => '&Vcy;',
+ 'Г' => '&Gcy;',
+ 'Д' => '&Dcy;',
+ 'Е' => '&IEcy;',
+ 'Ж' => '&ZHcy;',
+ 'З' => '&Zcy;',
+ 'И' => '&Icy;',
+ 'Й' => '&Jcy;',
+ 'К' => '&Kcy;',
+ 'Л' => '&Lcy;',
+ 'М' => '&Mcy;',
+ 'Ð' => '&Ncy;',
+ 'О' => '&Ocy;',
+ 'П' => '&Pcy;',
+ 'Р' => '&Rcy;',
+ 'С' => '&Scy;',
+ 'Т' => '&Tcy;',
+ 'У' => '&Ucy;',
+ 'Ф' => '&Fcy;',
+ 'Ð¥' => '&KHcy;',
+ 'Ц' => '&TScy;',
+ 'Ч' => '&CHcy;',
+ 'Ш' => '&SHcy;',
+ 'Щ' => '&SHCHcy;',
+ 'Ъ' => '&HARDcy;',
+ 'Ы' => '&Ycy;',
+ 'Ь' => '&SOFTcy;',
+ 'Э' => '&Ecy;',
+ 'Ю' => '&YUcy;',
+ 'Я' => '&YAcy;',
+ 'а' => '&acy;',
+ 'б' => '&bcy;',
+ 'в' => '&vcy;',
+ 'г' => '&gcy;',
+ 'д' => '&dcy;',
+ 'е' => '&iecy;',
+ 'ж' => '&zhcy;',
+ 'з' => '&zcy;',
+ 'и' => '&icy;',
+ 'й' => '&jcy;',
+ 'к' => '&kcy;',
+ 'л' => '&lcy;',
+ 'м' => '&mcy;',
+ 'н' => '&ncy;',
+ 'о' => '&ocy;',
+ 'п' => '&pcy;',
+ 'Ñ€' => '&rcy;',
+ 'Ñ' => '&scy;',
+ 'Ñ‚' => '&tcy;',
+ 'у' => '&ucy;',
+ 'Ñ„' => '&fcy;',
+ 'Ñ…' => '&khcy;',
+ 'ц' => '&tscy;',
+ 'ч' => '&chcy;',
+ 'ш' => '&shcy;',
+ 'щ' => '&shchcy;',
+ 'ÑŠ' => '&hardcy;',
+ 'Ñ‹' => '&ycy;',
+ 'ь' => '&softcy;',
+ 'Ñ' => '&ecy;',
+ 'ÑŽ' => '&yucy;',
+ 'Ñ' => '&yacy;',
+ 'Ñ‘' => '&iocy;',
+ 'Ñ’' => '&djcy;',
+ 'Ñ“' => '&gjcy;',
+ 'Ñ”' => '&jukcy;',
+ 'Ñ•' => '&dscy;',
+ 'Ñ–' => '&iukcy;',
+ 'Ñ—' => '&yicy;',
+ 'ј' => '&jsercy;',
+ 'Ñ™' => '&ljcy;',
+ 'Ñš' => '&njcy;',
+ 'Ñ›' => '&tshcy;',
+ 'ќ' => '&kjcy;',
+ 'Ñž' => '&ubrcy;',
+ 'ÑŸ' => '&dzcy;',
+ ' ' => '&ensp;',
+ ' ' => '&emsp;',
+ ' ' => '&emsp13;',
+ ' ' => '&emsp14;',
+ ' ' => '&numsp;',
+ ' ' => '&puncsp;',
+ ' ' => '&ThinSpace;',
+ ' ' => '&hairsp;',
+ '​' => '&ZeroWidthSpace;',
+ '‌' => '&zwnj;',
+ 'â€' => '&zwj;',
+ '‎' => '&lrm;',
+ 'â€' => '&rlm;',
+ 'â€' => '&hyphen;',
+ '–' => '&ndash;',
+ '—' => '&mdash;',
+ '―' => '&horbar;',
+ '‖' => '&Verbar;',
+ '‘' => '&OpenCurlyQuote;',
+ '’' => '&rsquo;',
+ '‚' => '&sbquo;',
+ '“' => '&OpenCurlyDoubleQuote;',
+ 'â€' => '&rdquo;',
+ '„' => '&bdquo;',
+ '†' => '&dagger;',
+ '‡' => '&Dagger;',
+ '•' => '&bull;',
+ '‥' => '&nldr;',
+ '…' => '&hellip;',
+ '‰' => '&permil;',
+ '‱' => '&pertenk;',
+ '′' => '&prime;',
+ '″' => '&Prime;',
+ '‴' => '&tprime;',
+ '‵' => '&backprime;',
+ '‹' => '&lsaquo;',
+ '›' => '&rsaquo;',
+ '‾' => '&oline;',
+ 'â' => '&caret;',
+ 'âƒ' => '&hybull;',
+ 'â„' => '&frasl;',
+ 'â' => '&bsemi;',
+ 'â—' => '&qprime;',
+ 'âŸ' => '&MediumSpace;',
+ 'âŸâ€Š' => '&ThickSpace',
+ 'â ' => '&NoBreak;',
+ 'â¡' => '&af;',
+ 'â¢' => '&InvisibleTimes;',
+ 'â£' => '&ic;',
+ '€' => '&euro;',
+ '⃛' => '&TripleDot;',
+ '⃜' => '&DotDot;',
+ 'â„‚' => '&complexes;',
+ 'â„…' => '&incare;',
+ 'â„Š' => '&gscr;',
+ 'â„‹' => '&HilbertSpace;',
+ 'ℌ' => '&Hfr;',
+ 'â„' => '&Hopf;',
+ 'â„Ž' => '&planckh;',
+ 'â„' => '&planck;',
+ 'â„' => '&imagline;',
+ 'â„‘' => '&Ifr;',
+ 'â„’' => '&lagran;',
+ 'â„“' => '&ell;',
+ 'â„•' => '&naturals;',
+ 'â„–' => '&numero;',
+ 'â„—' => '&copysr;',
+ '℘' => '&wp;',
+ 'â„™' => '&primes;',
+ 'â„š' => '&rationals;',
+ 'â„›' => '&realine;',
+ 'ℜ' => '&Rfr;',
+ 'â„' => '&Ropf;',
+ 'â„ž' => '&rx;',
+ 'â„¢' => '&trade;',
+ 'ℤ' => '&Zopf;',
+ '℧' => '&mho;',
+ 'ℨ' => '&Zfr;',
+ 'â„©' => '&iiota;',
+ 'ℬ' => '&Bscr;',
+ 'â„­' => '&Cfr;',
+ 'ℯ' => '&escr;',
+ 'â„°' => '&expectation;',
+ 'ℱ' => '&Fouriertrf;',
+ 'ℳ' => '&Mellintrf;',
+ 'â„´' => '&orderof;',
+ 'ℵ' => '&aleph;',
+ 'ℶ' => '&beth;',
+ 'â„·' => '&gimel;',
+ 'ℸ' => '&daleth;',
+ 'â……' => '&CapitalDifferentialD;',
+ 'â…†' => '&DifferentialD;',
+ 'â…‡' => '&exponentiale;',
+ 'â…ˆ' => '&ImaginaryI;',
+ 'â…“' => '&frac13;',
+ 'â…”' => '&frac23;',
+ 'â…•' => '&frac15;',
+ 'â…–' => '&frac25;',
+ 'â…—' => '&frac35;',
+ 'â…˜' => '&frac45;',
+ 'â…™' => '&frac16;',
+ 'â…š' => '&frac56;',
+ 'â…›' => '&frac18;',
+ '⅜' => '&frac38;',
+ 'â…' => '&frac58;',
+ 'â…ž' => '&frac78;',
+ 'â†' => '&larr;',
+ '↑' => '&uarr;',
+ '→' => '&srarr;',
+ '↓' => '&darr;',
+ '↔' => '&harr;',
+ '↕' => '&UpDownArrow;',
+ '↖' => '&nwarrow;',
+ '↗' => '&UpperRightArrow;',
+ '↘' => '&LowerRightArrow;',
+ '↙' => '&swarr;',
+ '↚' => '&nleftarrow;',
+ '↛' => '&nrarr;',
+ 'â†' => '&rarrw;',
+ 'â†Ì¸' => '&nrarrw',
+ '↞' => '&Larr;',
+ '↟' => '&Uarr;',
+ '↠' => '&twoheadrightarrow;',
+ '↡' => '&Darr;',
+ '↢' => '&larrtl;',
+ '↣' => '&rarrtl;',
+ '↤' => '&LeftTeeArrow;',
+ '↥' => '&UpTeeArrow;',
+ '↦' => '&map;',
+ '↧' => '&DownTeeArrow;',
+ '↩' => '&larrhk;',
+ '↪' => '&rarrhk;',
+ '↫' => '&larrlp;',
+ '↬' => '&looparrowright;',
+ '↭' => '&harrw;',
+ '↮' => '&nleftrightarrow;',
+ '↰' => '&Lsh;',
+ '↱' => '&rsh;',
+ '↲' => '&ldsh;',
+ '↳' => '&rdsh;',
+ '↵' => '&crarr;',
+ '↶' => '&curvearrowleft;',
+ '↷' => '&curarr;',
+ '↺' => '&olarr;',
+ '↻' => '&orarr;',
+ '↼' => '&leftharpoonup;',
+ '↽' => '&leftharpoondown;',
+ '↾' => '&RightUpVector;',
+ '↿' => '&uharl;',
+ '⇀' => '&rharu;',
+ 'â‡' => '&rhard;',
+ '⇂' => '&RightDownVector;',
+ '⇃' => '&dharl;',
+ '⇄' => '&rightleftarrows;',
+ '⇅' => '&udarr;',
+ '⇆' => '&lrarr;',
+ '⇇' => '&llarr;',
+ '⇈' => '&upuparrows;',
+ '⇉' => '&rrarr;',
+ '⇊' => '&downdownarrows;',
+ '⇋' => '&leftrightharpoons;',
+ '⇌' => '&rightleftharpoons;',
+ 'â‡' => '&nLeftarrow;',
+ '⇎' => '&nhArr;',
+ 'â‡' => '&nrArr;',
+ 'â‡' => '&DoubleLeftArrow;',
+ '⇑' => '&DoubleUpArrow;',
+ '⇒' => '&Implies;',
+ '⇓' => '&Downarrow;',
+ '⇔' => '&hArr;',
+ '⇕' => '&Updownarrow;',
+ '⇖' => '&nwArr;',
+ '⇗' => '&neArr;',
+ '⇘' => '&seArr;',
+ '⇙' => '&swArr;',
+ '⇚' => '&lAarr;',
+ '⇛' => '&rAarr;',
+ 'â‡' => '&zigrarr;',
+ '⇤' => '&LeftArrowBar;',
+ '⇥' => '&RightArrowBar;',
+ '⇵' => '&DownArrowUpArrow;',
+ '⇽' => '&loarr;',
+ '⇾' => '&roarr;',
+ '⇿' => '&hoarr;',
+ '∀' => '&forall;',
+ 'âˆ' => '&comp;',
+ '∂' => '&part;',
+ '∂̸' => '&npart',
+ '∃' => '&Exists;',
+ '∄' => '&nexist;',
+ '∅' => '&empty;',
+ '∇' => '&nabla;',
+ '∈' => '&isinv;',
+ '∉' => '&notin;',
+ '∋' => '&ReverseElement;',
+ '∌' => '&notniva;',
+ 'âˆ' => '&prod;',
+ 'âˆ' => '&Coproduct;',
+ '∑' => '&sum;',
+ '−' => '&minus;',
+ '∓' => '&MinusPlus;',
+ '∔' => '&plusdo;',
+ '∖' => '&ssetmn;',
+ '∗' => '&lowast;',
+ '∘' => '&compfn;',
+ '√' => '&Sqrt;',
+ 'âˆ' => '&prop;',
+ '∞' => '&infin;',
+ '∟' => '&angrt;',
+ '∠' => '&angle;',
+ '∠⃒' => '&nang',
+ '∡' => '&angmsd;',
+ '∢' => '&angsph;',
+ '∣' => '&mid;',
+ '∤' => '&nshortmid;',
+ '∥' => '&shortparallel;',
+ '∦' => '&nparallel;',
+ '∧' => '&and;',
+ '∨' => '&or;',
+ '∩' => '&cap;',
+ '∩︀' => '&caps',
+ '∪' => '&cup;',
+ '∪︀' => '&cups',
+ '∫' => '&Integral;',
+ '∬' => '&Int;',
+ '∭' => '&tint;',
+ '∮' => '&ContourIntegral;',
+ '∯' => '&DoubleContourIntegral;',
+ '∰' => '&Cconint;',
+ '∱' => '&cwint;',
+ '∲' => '&cwconint;',
+ '∳' => '&awconint;',
+ '∴' => '&there4;',
+ '∵' => '&Because;',
+ '∶' => '&ratio;',
+ '∷' => '&Colon;',
+ '∸' => '&minusd;',
+ '∺' => '&mDDot;',
+ '∻' => '&homtht;',
+ '∼' => '&sim;',
+ '∼⃒' => '&nvsim',
+ '∽' => '&bsim;',
+ '∽̱' => '&race',
+ '∾' => '&ac;',
+ '∾̳' => '&acE',
+ '∿' => '&acd;',
+ '≀' => '&wr;',
+ 'â‰' => '&NotTilde;',
+ '≂' => '&esim;',
+ '≂̸' => '&nesim',
+ '≃' => '&simeq;',
+ '≄' => '&nsime;',
+ '≅' => '&TildeFullEqual;',
+ '≆' => '&simne;',
+ '≇' => '&ncong;',
+ '≈' => '&approx;',
+ '≉' => '&napprox;',
+ '≊' => '&ape;',
+ '≋' => '&apid;',
+ '≋̸' => '&napid',
+ '≌' => '&bcong;',
+ 'â‰' => '&CupCap;',
+ 'â‰âƒ’' => '&nvap',
+ '≎' => '&bump;',
+ '≎̸' => '&nbump',
+ 'â‰' => '&HumpEqual;',
+ 'â‰Ì¸' => '&nbumpe',
+ 'â‰' => '&esdot;',
+ 'â‰Ì¸' => '&nedot',
+ '≑' => '&doteqdot;',
+ '≒' => '&fallingdotseq;',
+ '≓' => '&risingdotseq;',
+ '≔' => '&coloneq;',
+ '≕' => '&eqcolon;',
+ '≖' => '&ecir;',
+ '≗' => '&circeq;',
+ '≙' => '&wedgeq;',
+ '≚' => '&veeeq;',
+ '≜' => '&triangleq;',
+ '≟' => '&equest;',
+ '≠' => '&NotEqual;',
+ '≡' => '&Congruent;',
+ '≡⃥' => '&bnequiv',
+ '≢' => '&NotCongruent;',
+ '≤' => '&leq;',
+ '≤⃒' => '&nvle',
+ '≥' => '&ge;',
+ '≥⃒' => '&nvge',
+ '≦' => '&lE;',
+ '≦̸' => '&nlE',
+ '≧' => '&geqq;',
+ '≧̸' => '&NotGreaterFullEqual',
+ '≨' => '&lneqq;',
+ '≨︀' => '&lvertneqq',
+ '≩' => '&gneqq;',
+ '≩︀' => '&gvertneqq',
+ '≪' => '&ll;',
+ '≪̸' => '&nLtv',
+ '≪⃒' => '&nLt',
+ '≫' => '&gg;',
+ '≫̸' => '&NotGreaterGreater',
+ '≫⃒' => '&nGt',
+ '≬' => '&between;',
+ '≭' => '&NotCupCap;',
+ '≮' => '&NotLess;',
+ '≯' => '&ngtr;',
+ '≰' => '&NotLessEqual;',
+ '≱' => '&ngeq;',
+ '≲' => '&LessTilde;',
+ '≳' => '&GreaterTilde;',
+ '≴' => '&nlsim;',
+ '≵' => '&ngsim;',
+ '≶' => '&lessgtr;',
+ '≷' => '&gl;',
+ '≸' => '&ntlg;',
+ '≹' => '&NotGreaterLess;',
+ '≺' => '&prec;',
+ '≻' => '&succ;',
+ '≼' => '&PrecedesSlantEqual;',
+ '≽' => '&succcurlyeq;',
+ '≾' => '&precsim;',
+ '≿' => '&SucceedsTilde;',
+ '≿̸' => '&NotSucceedsTilde',
+ '⊀' => '&npr;',
+ 'âŠ' => '&NotSucceeds;',
+ '⊂' => '&sub;',
+ '⊂⃒' => '&vnsub',
+ '⊃' => '&sup;',
+ '⊃⃒' => '&nsupset',
+ '⊄' => '&nsub;',
+ '⊅' => '&nsup;',
+ '⊆' => '&SubsetEqual;',
+ '⊇' => '&supe;',
+ '⊈' => '&NotSubsetEqual;',
+ '⊉' => '&NotSupersetEqual;',
+ '⊊' => '&subsetneq;',
+ '⊊︀' => '&vsubne',
+ '⊋' => '&supsetneq;',
+ '⊋︀' => '&vsupne',
+ 'âŠ' => '&cupdot;',
+ '⊎' => '&UnionPlus;',
+ 'âŠ' => '&sqsub;',
+ 'âŠÌ¸' => '&NotSquareSubset',
+ 'âŠ' => '&sqsupset;',
+ 'âŠÌ¸' => '&NotSquareSuperset',
+ '⊑' => '&SquareSubsetEqual;',
+ '⊒' => '&SquareSupersetEqual;',
+ '⊓' => '&sqcap;',
+ '⊓︀' => '&sqcaps',
+ '⊔' => '&sqcup;',
+ '⊔︀' => '&sqcups',
+ '⊕' => '&CirclePlus;',
+ '⊖' => '&ominus;',
+ '⊗' => '&CircleTimes;',
+ '⊘' => '&osol;',
+ '⊙' => '&CircleDot;',
+ '⊚' => '&ocir;',
+ '⊛' => '&oast;',
+ 'âŠ' => '&odash;',
+ '⊞' => '&boxplus;',
+ '⊟' => '&boxminus;',
+ '⊠' => '&timesb;',
+ '⊡' => '&sdotb;',
+ '⊢' => '&vdash;',
+ '⊣' => '&dashv;',
+ '⊤' => '&DownTee;',
+ '⊥' => '&perp;',
+ '⊧' => '&models;',
+ '⊨' => '&DoubleRightTee;',
+ '⊩' => '&Vdash;',
+ '⊪' => '&Vvdash;',
+ '⊫' => '&VDash;',
+ '⊬' => '&nvdash;',
+ '⊭' => '&nvDash;',
+ '⊮' => '&nVdash;',
+ '⊯' => '&nVDash;',
+ '⊰' => '&prurel;',
+ '⊲' => '&vartriangleleft;',
+ '⊳' => '&vrtri;',
+ '⊴' => '&LeftTriangleEqual;',
+ '⊴⃒' => '&nvltrie',
+ '⊵' => '&RightTriangleEqual;',
+ '⊵⃒' => '&nvrtrie',
+ '⊶' => '&origof;',
+ '⊷' => '&imof;',
+ '⊸' => '&mumap;',
+ '⊹' => '&hercon;',
+ '⊺' => '&intcal;',
+ '⊻' => '&veebar;',
+ '⊽' => '&barvee;',
+ '⊾' => '&angrtvb;',
+ '⊿' => '&lrtri;',
+ 'â‹€' => '&xwedge;',
+ 'â‹' => '&xvee;',
+ 'â‹‚' => '&bigcap;',
+ '⋃' => '&bigcup;',
+ 'â‹„' => '&diamond;',
+ 'â‹…' => '&sdot;',
+ '⋆' => '&Star;',
+ '⋇' => '&divonx;',
+ '⋈' => '&bowtie;',
+ '⋉' => '&ltimes;',
+ 'â‹Š' => '&rtimes;',
+ 'â‹‹' => '&lthree;',
+ '⋌' => '&rthree;',
+ 'â‹' => '&backsimeq;',
+ 'â‹Ž' => '&curlyvee;',
+ 'â‹' => '&curlywedge;',
+ 'â‹' => '&Sub;',
+ 'â‹‘' => '&Supset;',
+ 'â‹’' => '&Cap;',
+ 'â‹“' => '&Cup;',
+ 'â‹”' => '&pitchfork;',
+ 'â‹•' => '&epar;',
+ 'â‹–' => '&lessdot;',
+ 'â‹—' => '&gtrdot;',
+ '⋘' => '&Ll;',
+ '⋘̸' => '&nLl',
+ 'â‹™' => '&Gg;',
+ '⋙̸' => '&nGg',
+ 'â‹š' => '&lesseqgtr;',
+ '⋚︀' => '&lesg',
+ 'â‹›' => '&gtreqless;',
+ '⋛︀' => '&gesl',
+ 'â‹ž' => '&curlyeqprec;',
+ 'â‹Ÿ' => '&cuesc;',
+ 'â‹ ' => '&NotPrecedesSlantEqual;',
+ 'â‹¡' => '&NotSucceedsSlantEqual;',
+ 'â‹¢' => '&NotSquareSubsetEqual;',
+ 'â‹£' => '&NotSquareSupersetEqual;',
+ '⋦' => '&lnsim;',
+ '⋧' => '&gnsim;',
+ '⋨' => '&precnsim;',
+ 'â‹©' => '&scnsim;',
+ '⋪' => '&nltri;',
+ 'â‹«' => '&ntriangleright;',
+ '⋬' => '&nltrie;',
+ 'â‹­' => '&NotRightTriangleEqual;',
+ 'â‹®' => '&vellip;',
+ '⋯' => '&ctdot;',
+ 'â‹°' => '&utdot;',
+ '⋱' => '&dtdot;',
+ '⋲' => '&disin;',
+ '⋳' => '&isinsv;',
+ 'â‹´' => '&isins;',
+ '⋵' => '&isindot;',
+ '⋵̸' => '&notindot',
+ '⋶' => '&notinvc;',
+ 'â‹·' => '&notinvb;',
+ '⋹' => '&isinE;',
+ '⋹̸' => '&notinE',
+ '⋺' => '&nisd;',
+ 'â‹»' => '&xnis;',
+ '⋼' => '&nis;',
+ '⋽' => '&notnivc;',
+ '⋾' => '&notnivb;',
+ '⌅' => '&barwed;',
+ '⌆' => '&doublebarwedge;',
+ '⌈' => '&lceil;',
+ '⌉' => '&RightCeiling;',
+ '⌊' => '&LeftFloor;',
+ '⌋' => '&RightFloor;',
+ '⌌' => '&drcrop;',
+ 'âŒ' => '&dlcrop;',
+ '⌎' => '&urcrop;',
+ 'âŒ' => '&ulcrop;',
+ 'âŒ' => '&bnot;',
+ '⌒' => '&profline;',
+ '⌓' => '&profsurf;',
+ '⌕' => '&telrec;',
+ '⌖' => '&target;',
+ '⌜' => '&ulcorner;',
+ 'âŒ' => '&urcorner;',
+ '⌞' => '&llcorner;',
+ '⌟' => '&drcorn;',
+ '⌢' => '&frown;',
+ '⌣' => '&smile;',
+ '⌭' => '&cylcty;',
+ '⌮' => '&profalar;',
+ '⌶' => '&topbot;',
+ '⌽' => '&ovbar;',
+ '⌿' => '&solbar;',
+ 'â¼' => '&angzarr;',
+ '⎰' => '&lmoust;',
+ '⎱' => '&rmoust;',
+ '⎴' => '&OverBracket;',
+ '⎵' => '&bbrk;',
+ '⎶' => '&bbrktbrk;',
+ 'âœ' => '&OverParenthesis;',
+ 'â' => '&UnderParenthesis;',
+ 'âž' => '&OverBrace;',
+ 'âŸ' => '&UnderBrace;',
+ 'â¢' => '&trpezium;',
+ 'â§' => '&elinters;',
+ 'â£' => '&blank;',
+ 'Ⓢ' => '&oS;',
+ '─' => '&HorizontalLine;',
+ '│' => '&boxv;',
+ '┌' => '&boxdr;',
+ 'â”' => '&boxdl;',
+ 'â””' => '&boxur;',
+ '┘' => '&boxul;',
+ '├' => '&boxvr;',
+ '┤' => '&boxvl;',
+ '┬' => '&boxhd;',
+ 'â”´' => '&boxhu;',
+ '┼' => '&boxvh;',
+ 'â•' => '&boxH;',
+ 'â•‘' => '&boxV;',
+ 'â•’' => '&boxdR;',
+ 'â•“' => '&boxDr;',
+ 'â•”' => '&boxDR;',
+ 'â••' => '&boxdL;',
+ 'â•–' => '&boxDl;',
+ 'â•—' => '&boxDL;',
+ '╘' => '&boxuR;',
+ 'â•™' => '&boxUr;',
+ 'â•š' => '&boxUR;',
+ 'â•›' => '&boxuL;',
+ '╜' => '&boxUl;',
+ 'â•' => '&boxUL;',
+ 'â•ž' => '&boxvR;',
+ 'â•Ÿ' => '&boxVr;',
+ 'â• ' => '&boxVR;',
+ 'â•¡' => '&boxvL;',
+ 'â•¢' => '&boxVl;',
+ 'â•£' => '&boxVL;',
+ '╤' => '&boxHd;',
+ 'â•¥' => '&boxhD;',
+ '╦' => '&boxHD;',
+ '╧' => '&boxHu;',
+ '╨' => '&boxhU;',
+ 'â•©' => '&boxHU;',
+ '╪' => '&boxvH;',
+ 'â•«' => '&boxVh;',
+ '╬' => '&boxVH;',
+ 'â–€' => '&uhblk;',
+ 'â–„' => '&lhblk;',
+ 'â–ˆ' => '&block;',
+ 'â–‘' => '&blk14;',
+ 'â–’' => '&blk12;',
+ 'â–“' => '&blk34;',
+ 'â–¡' => '&Square;',
+ 'â–ª' => '&squarf;',
+ 'â–«' => '&EmptyVerySmallSquare;',
+ 'â–­' => '&rect;',
+ 'â–®' => '&marker;',
+ 'â–±' => '&fltns;',
+ 'â–³' => '&bigtriangleup;',
+ 'â–´' => '&blacktriangle;',
+ 'â–µ' => '&triangle;',
+ 'â–¸' => '&blacktriangleright;',
+ 'â–¹' => '&rtri;',
+ 'â–½' => '&bigtriangledown;',
+ 'â–¾' => '&blacktriangledown;',
+ 'â–¿' => '&triangledown;',
+ 'â—‚' => '&blacktriangleleft;',
+ 'â—ƒ' => '&ltri;',
+ 'â—Š' => '&lozenge;',
+ 'â—‹' => '&cir;',
+ 'â—¬' => '&tridot;',
+ 'â—¯' => '&bigcirc;',
+ 'â—¸' => '&ultri;',
+ 'â—¹' => '&urtri;',
+ 'â—º' => '&lltri;',
+ 'â—»' => '&EmptySmallSquare;',
+ 'â—¼' => '&FilledSmallSquare;',
+ '★' => '&starf;',
+ '☆' => '&star;',
+ '☎' => '&phone;',
+ '♀' => '&female;',
+ '♂' => '&male;',
+ 'â™ ' => '&spadesuit;',
+ '♣' => '&clubs;',
+ '♥' => '&hearts;',
+ '♦' => '&diamondsuit;',
+ '♪' => '&sung;',
+ 'â™­' => '&flat;',
+ 'â™®' => '&natur;',
+ '♯' => '&sharp;',
+ '✓' => '&check;',
+ '✗' => '&cross;',
+ '✠' => '&maltese;',
+ '✶' => '&sext;',
+ 'â˜' => '&VerticalSeparator;',
+ 'â²' => '&lbbrk;',
+ 'â³' => '&rbbrk;',
+ '⟈' => '&bsolhsub;',
+ '⟉' => '&suphsol;',
+ '⟦' => '&LeftDoubleBracket;',
+ '⟧' => '&RightDoubleBracket;',
+ '⟨' => '&langle;',
+ '⟩' => '&RightAngleBracket;',
+ '⟪' => '&Lang;',
+ '⟫' => '&Rang;',
+ '⟬' => '&loang;',
+ '⟭' => '&roang;',
+ '⟵' => '&longleftarrow;',
+ '⟶' => '&LongRightArrow;',
+ '⟷' => '&LongLeftRightArrow;',
+ '⟸' => '&xlArr;',
+ '⟹' => '&DoubleLongRightArrow;',
+ '⟺' => '&xhArr;',
+ '⟼' => '&xmap;',
+ '⟿' => '&dzigrarr;',
+ '⤂' => '&nvlArr;',
+ '⤃' => '&nvrArr;',
+ '⤄' => '&nvHarr;',
+ '⤅' => '&Map;',
+ '⤌' => '&lbarr;',
+ 'â¤' => '&bkarow;',
+ '⤎' => '&lBarr;',
+ 'â¤' => '&dbkarow;',
+ 'â¤' => '&drbkarow;',
+ '⤑' => '&DDotrahd;',
+ '⤒' => '&UpArrowBar;',
+ '⤓' => '&DownArrowBar;',
+ '⤖' => '&Rarrtl;',
+ '⤙' => '&latail;',
+ '⤚' => '&ratail;',
+ '⤛' => '&lAtail;',
+ '⤜' => '&rAtail;',
+ 'â¤' => '&larrfs;',
+ '⤞' => '&rarrfs;',
+ '⤟' => '&larrbfs;',
+ '⤠' => '&rarrbfs;',
+ '⤣' => '&nwarhk;',
+ '⤤' => '&nearhk;',
+ '⤥' => '&searhk;',
+ '⤦' => '&swarhk;',
+ '⤧' => '&nwnear;',
+ '⤨' => '&toea;',
+ '⤩' => '&seswar;',
+ '⤪' => '&swnwar;',
+ '⤳' => '&rarrc;',
+ '⤳̸' => '&nrarrc',
+ '⤵' => '&cudarrr;',
+ '⤶' => '&ldca;',
+ '⤷' => '&rdca;',
+ '⤸' => '&cudarrl;',
+ '⤹' => '&larrpl;',
+ '⤼' => '&curarrm;',
+ '⤽' => '&cularrp;',
+ '⥅' => '&rarrpl;',
+ '⥈' => '&harrcir;',
+ '⥉' => '&Uarrocir;',
+ '⥊' => '&lurdshar;',
+ '⥋' => '&ldrushar;',
+ '⥎' => '&LeftRightVector;',
+ 'â¥' => '&RightUpDownVector;',
+ 'â¥' => '&DownLeftRightVector;',
+ '⥑' => '&LeftUpDownVector;',
+ '⥒' => '&LeftVectorBar;',
+ '⥓' => '&RightVectorBar;',
+ '⥔' => '&RightUpVectorBar;',
+ '⥕' => '&RightDownVectorBar;',
+ '⥖' => '&DownLeftVectorBar;',
+ '⥗' => '&DownRightVectorBar;',
+ '⥘' => '&LeftUpVectorBar;',
+ '⥙' => '&LeftDownVectorBar;',
+ '⥚' => '&LeftTeeVector;',
+ '⥛' => '&RightTeeVector;',
+ '⥜' => '&RightUpTeeVector;',
+ 'â¥' => '&RightDownTeeVector;',
+ '⥞' => '&DownLeftTeeVector;',
+ '⥟' => '&DownRightTeeVector;',
+ '⥠' => '&LeftUpTeeVector;',
+ '⥡' => '&LeftDownTeeVector;',
+ '⥢' => '&lHar;',
+ '⥣' => '&uHar;',
+ '⥤' => '&rHar;',
+ '⥥' => '&dHar;',
+ '⥦' => '&luruhar;',
+ '⥧' => '&ldrdhar;',
+ '⥨' => '&ruluhar;',
+ '⥩' => '&rdldhar;',
+ '⥪' => '&lharul;',
+ '⥫' => '&llhard;',
+ '⥬' => '&rharul;',
+ '⥭' => '&lrhard;',
+ '⥮' => '&udhar;',
+ '⥯' => '&ReverseUpEquilibrium;',
+ '⥰' => '&RoundImplies;',
+ '⥱' => '&erarr;',
+ '⥲' => '&simrarr;',
+ '⥳' => '&larrsim;',
+ '⥴' => '&rarrsim;',
+ '⥵' => '&rarrap;',
+ '⥶' => '&ltlarr;',
+ '⥸' => '&gtrarr;',
+ '⥹' => '&subrarr;',
+ '⥻' => '&suplarr;',
+ '⥼' => '&lfisht;',
+ '⥽' => '&rfisht;',
+ '⥾' => '&ufisht;',
+ '⥿' => '&dfisht;',
+ '⦅' => '&lopar;',
+ '⦆' => '&ropar;',
+ '⦋' => '&lbrke;',
+ '⦌' => '&rbrke;',
+ 'â¦' => '&lbrkslu;',
+ '⦎' => '&rbrksld;',
+ 'â¦' => '&lbrksld;',
+ 'â¦' => '&rbrkslu;',
+ '⦑' => '&langd;',
+ '⦒' => '&rangd;',
+ '⦓' => '&lparlt;',
+ '⦔' => '&rpargt;',
+ '⦕' => '&gtlPar;',
+ '⦖' => '&ltrPar;',
+ '⦚' => '&vzigzag;',
+ '⦜' => '&vangrt;',
+ 'â¦' => '&angrtvbd;',
+ '⦤' => '&ange;',
+ '⦥' => '&range;',
+ '⦦' => '&dwangle;',
+ '⦧' => '&uwangle;',
+ '⦨' => '&angmsdaa;',
+ '⦩' => '&angmsdab;',
+ '⦪' => '&angmsdac;',
+ '⦫' => '&angmsdad;',
+ '⦬' => '&angmsdae;',
+ '⦭' => '&angmsdaf;',
+ '⦮' => '&angmsdag;',
+ '⦯' => '&angmsdah;',
+ '⦰' => '&bemptyv;',
+ '⦱' => '&demptyv;',
+ '⦲' => '&cemptyv;',
+ '⦳' => '&raemptyv;',
+ '⦴' => '&laemptyv;',
+ '⦵' => '&ohbar;',
+ '⦶' => '&omid;',
+ '⦷' => '&opar;',
+ '⦹' => '&operp;',
+ '⦻' => '&olcross;',
+ '⦼' => '&odsold;',
+ '⦾' => '&olcir;',
+ '⦿' => '&ofcir;',
+ '⧀' => '&olt;',
+ 'â§' => '&ogt;',
+ '⧂' => '&cirscir;',
+ '⧃' => '&cirE;',
+ '⧄' => '&solb;',
+ '⧅' => '&bsolb;',
+ '⧉' => '&boxbox;',
+ 'â§' => '&trisb;',
+ '⧎' => '&rtriltri;',
+ 'â§' => '&LeftTriangleBar;',
+ 'â§Ì¸' => '&NotLeftTriangleBar',
+ 'â§' => '&RightTriangleBar;',
+ 'â§Ì¸' => '&NotRightTriangleBar',
+ '⧜' => '&iinfin;',
+ 'â§' => '&infintie;',
+ '⧞' => '&nvinfin;',
+ '⧣' => '&eparsl;',
+ '⧤' => '&smeparsl;',
+ '⧥' => '&eqvparsl;',
+ '⧫' => '&lozf;',
+ '⧴' => '&RuleDelayed;',
+ '⧶' => '&dsol;',
+ '⨀' => '&xodot;',
+ 'â¨' => '&bigoplus;',
+ '⨂' => '&bigotimes;',
+ '⨄' => '&biguplus;',
+ '⨆' => '&bigsqcup;',
+ '⨌' => '&iiiint;',
+ 'â¨' => '&fpartint;',
+ 'â¨' => '&cirfnint;',
+ '⨑' => '&awint;',
+ '⨒' => '&rppolint;',
+ '⨓' => '&scpolint;',
+ '⨔' => '&npolint;',
+ '⨕' => '&pointint;',
+ '⨖' => '&quatint;',
+ '⨗' => '&intlarhk;',
+ '⨢' => '&pluscir;',
+ '⨣' => '&plusacir;',
+ '⨤' => '&simplus;',
+ '⨥' => '&plusdu;',
+ '⨦' => '&plussim;',
+ '⨧' => '&plustwo;',
+ '⨩' => '&mcomma;',
+ '⨪' => '&minusdu;',
+ '⨭' => '&loplus;',
+ '⨮' => '&roplus;',
+ '⨯' => '&Cross;',
+ '⨰' => '&timesd;',
+ '⨱' => '&timesbar;',
+ '⨳' => '&smashp;',
+ '⨴' => '&lotimes;',
+ '⨵' => '&rotimes;',
+ '⨶' => '&otimesas;',
+ '⨷' => '&Otimes;',
+ '⨸' => '&odiv;',
+ '⨹' => '&triplus;',
+ '⨺' => '&triminus;',
+ '⨻' => '&tritime;',
+ '⨼' => '&iprod;',
+ '⨿' => '&amalg;',
+ 'â©€' => '&capdot;',
+ 'â©‚' => '&ncup;',
+ '⩃' => '&ncap;',
+ 'â©„' => '&capand;',
+ 'â©…' => '&cupor;',
+ '⩆' => '&cupcap;',
+ '⩇' => '&capcup;',
+ '⩈' => '&cupbrcap;',
+ '⩉' => '&capbrcup;',
+ 'â©Š' => '&cupcup;',
+ 'â©‹' => '&capcap;',
+ '⩌' => '&ccups;',
+ 'â©' => '&ccaps;',
+ 'â©' => '&ccupssm;',
+ 'â©“' => '&And;',
+ 'â©”' => '&Or;',
+ 'â©•' => '&andand;',
+ 'â©–' => '&oror;',
+ 'â©—' => '&orslope;',
+ '⩘' => '&andslope;',
+ 'â©š' => '&andv;',
+ 'â©›' => '&orv;',
+ '⩜' => '&andd;',
+ 'â©' => '&ord;',
+ 'â©Ÿ' => '&wedbar;',
+ '⩦' => '&sdote;',
+ '⩪' => '&simdot;',
+ 'â©­' => '&congdot;',
+ '⩭̸' => '&ncongdot',
+ 'â©®' => '&easter;',
+ '⩯' => '&apacir;',
+ 'â©°' => '&apE;',
+ '⩰̸' => '&napE',
+ '⩱' => '&eplus;',
+ '⩲' => '&pluse;',
+ '⩳' => '&Esim;',
+ 'â©´' => '&Colone;',
+ '⩵' => '&Equal;',
+ 'â©·' => '&ddotseq;',
+ '⩸' => '&equivDD;',
+ '⩹' => '&ltcir;',
+ '⩺' => '&gtcir;',
+ 'â©»' => '&ltquest;',
+ '⩼' => '&gtquest;',
+ '⩽' => '&les;',
+ '⩽̸' => '&nles',
+ '⩾' => '&ges;',
+ '⩾̸' => '&nges',
+ 'â©¿' => '&lesdot;',
+ '⪀' => '&gesdot;',
+ 'âª' => '&lesdoto;',
+ '⪂' => '&gesdoto;',
+ '⪃' => '&lesdotor;',
+ '⪄' => '&gesdotol;',
+ '⪅' => '&lap;',
+ '⪆' => '&gap;',
+ '⪇' => '&lne;',
+ '⪈' => '&gne;',
+ '⪉' => '&lnap;',
+ '⪊' => '&gnap;',
+ '⪋' => '&lesseqqgtr;',
+ '⪌' => '&gEl;',
+ 'âª' => '&lsime;',
+ '⪎' => '&gsime;',
+ 'âª' => '&lsimg;',
+ 'âª' => '&gsiml;',
+ '⪑' => '&lgE;',
+ '⪒' => '&glE;',
+ '⪓' => '&lesges;',
+ '⪔' => '&gesles;',
+ '⪕' => '&els;',
+ '⪖' => '&egs;',
+ '⪗' => '&elsdot;',
+ '⪘' => '&egsdot;',
+ '⪙' => '&el;',
+ '⪚' => '&eg;',
+ 'âª' => '&siml;',
+ '⪞' => '&simg;',
+ '⪟' => '&simlE;',
+ '⪠' => '&simgE;',
+ '⪡' => '&LessLess;',
+ '⪡̸' => '&NotNestedLessLess',
+ '⪢' => '&GreaterGreater;',
+ '⪢̸' => '&NotNestedGreaterGreater',
+ '⪤' => '&glj;',
+ '⪥' => '&gla;',
+ '⪦' => '&ltcc;',
+ '⪧' => '&gtcc;',
+ '⪨' => '&lescc;',
+ '⪩' => '&gescc;',
+ '⪪' => '&smt;',
+ '⪫' => '&lat;',
+ '⪬' => '&smte;',
+ '⪬︀' => '&smtes',
+ '⪭' => '&late;',
+ '⪭︀' => '&lates',
+ '⪮' => '&bumpE;',
+ '⪯' => '&preceq;',
+ '⪯̸' => '&NotPrecedesEqual',
+ '⪰' => '&SucceedsEqual;',
+ '⪰̸' => '&NotSucceedsEqual',
+ '⪳' => '&prE;',
+ '⪴' => '&scE;',
+ '⪵' => '&precneqq;',
+ '⪶' => '&scnE;',
+ '⪷' => '&precapprox;',
+ '⪸' => '&succapprox;',
+ '⪹' => '&precnapprox;',
+ '⪺' => '&succnapprox;',
+ '⪻' => '&Pr;',
+ '⪼' => '&Sc;',
+ '⪽' => '&subdot;',
+ '⪾' => '&supdot;',
+ '⪿' => '&subplus;',
+ 'â«€' => '&supplus;',
+ 'â«' => '&submult;',
+ 'â«‚' => '&supmult;',
+ '⫃' => '&subedot;',
+ 'â«„' => '&supedot;',
+ 'â«…' => '&subE;',
+ '⫅̸' => '&nsubE',
+ '⫆' => '&supseteqq;',
+ '⫆̸' => '&nsupseteqq',
+ '⫇' => '&subsim;',
+ '⫈' => '&supsim;',
+ 'â«‹' => '&subsetneqq;',
+ '⫋︀' => '&vsubnE',
+ '⫌' => '&supnE;',
+ '⫌︀' => '&varsupsetneqq',
+ 'â«' => '&csub;',
+ 'â«' => '&csup;',
+ 'â«‘' => '&csube;',
+ 'â«’' => '&csupe;',
+ 'â«“' => '&subsup;',
+ 'â«”' => '&supsub;',
+ 'â«•' => '&subsub;',
+ 'â«–' => '&supsup;',
+ 'â«—' => '&suphsub;',
+ '⫘' => '&supdsub;',
+ 'â«™' => '&forkv;',
+ 'â«š' => '&topfork;',
+ 'â«›' => '&mlcp;',
+ '⫤' => '&Dashv;',
+ '⫦' => '&Vdashl;',
+ '⫧' => '&Barv;',
+ '⫨' => '&vBar;',
+ 'â«©' => '&vBarv;',
+ 'â««' => '&Vbar;',
+ '⫬' => '&Not;',
+ 'â«­' => '&bNot;',
+ 'â«®' => '&rnmid;',
+ '⫯' => '&cirmid;',
+ 'â«°' => '&midcir;',
+ '⫱' => '&topcir;',
+ '⫲' => '&nhpar;',
+ '⫳' => '&parsim;',
+ '⫽︀' => '&varsupsetneqq',
+ 'ff' => '&fflig;',
+ 'ï¬' => '&filig;',
+ 'fl' => '&fllig;',
+ 'ffi' => '&ffilig;',
+ 'ffl' => '&ffllig;',
+ 'ð’œ' => '&Ascr;',
+ 'ð’ž' => '&Cscr;',
+ 'ð’Ÿ' => '&Dscr;',
+ 'ð’¢' => '&Gscr;',
+ 'ð’¥' => '&Jscr;',
+ 'ð’¦' => '&Kscr;',
+ 'ð’©' => '&Nscr;',
+ 'ð’ª' => '&Oscr;',
+ 'ð’«' => '&Pscr;',
+ 'ð’¬' => '&Qscr;',
+ 'ð’®' => '&Sscr;',
+ 'ð’¯' => '&Tscr;',
+ 'ð’°' => '&Uscr;',
+ 'ð’±' => '&Vscr;',
+ 'ð’²' => '&Wscr;',
+ 'ð’³' => '&Xscr;',
+ 'ð’´' => '&Yscr;',
+ 'ð’µ' => '&Zscr;',
+ 'ð’¶' => '&ascr;',
+ 'ð’·' => '&bscr;',
+ 'ð’¸' => '&cscr;',
+ 'ð’¹' => '&dscr;',
+ 'ð’»' => '&fscr;',
+ 'ð’½' => '&hscr;',
+ 'ð’¾' => '&iscr;',
+ 'ð’¿' => '&jscr;',
+ 'ð“€' => '&kscr;',
+ 'ð“' => '&lscr;',
+ 'ð“‚' => '&mscr;',
+ 'ð“ƒ' => '&nscr;',
+ 'ð“…' => '&pscr;',
+ 'ð“†' => '&qscr;',
+ 'ð“‡' => '&rscr;',
+ 'ð“ˆ' => '&sscr;',
+ 'ð“‰' => '&tscr;',
+ 'ð“Š' => '&uscr;',
+ 'ð“‹' => '&vscr;',
+ 'ð“Œ' => '&wscr;',
+ 'ð“' => '&xscr;',
+ 'ð“Ž' => '&yscr;',
+ 'ð“' => '&zscr;',
+ 'ð”„' => '&Afr;',
+ 'ð”…' => '&Bfr;',
+ 'ð”‡' => '&Dfr;',
+ 'ð”ˆ' => '&Efr;',
+ 'ð”‰' => '&Ffr;',
+ 'ð”Š' => '&Gfr;',
+ 'ð”' => '&Jfr;',
+ 'ð”Ž' => '&Kfr;',
+ 'ð”' => '&Lfr;',
+ 'ð”' => '&Mfr;',
+ 'ð”‘' => '&Nfr;',
+ 'ð”’' => '&Ofr;',
+ 'ð”“' => '&Pfr;',
+ 'ð””' => '&Qfr;',
+ 'ð”–' => '&Sfr;',
+ 'ð”—' => '&Tfr;',
+ 'ð”˜' => '&Ufr;',
+ 'ð”™' => '&Vfr;',
+ 'ð”š' => '&Wfr;',
+ 'ð”›' => '&Xfr;',
+ 'ð”œ' => '&Yfr;',
+ 'ð”ž' => '&afr;',
+ 'ð”Ÿ' => '&bfr;',
+ 'ð” ' => '&cfr;',
+ 'ð”¡' => '&dfr;',
+ 'ð”¢' => '&efr;',
+ 'ð”£' => '&ffr;',
+ 'ð”¤' => '&gfr;',
+ 'ð”¥' => '&hfr;',
+ 'ð”¦' => '&ifr;',
+ 'ð”§' => '&jfr;',
+ 'ð”¨' => '&kfr;',
+ 'ð”©' => '&lfr;',
+ 'ð”ª' => '&mfr;',
+ 'ð”«' => '&nfr;',
+ 'ð”¬' => '&ofr;',
+ 'ð”­' => '&pfr;',
+ 'ð”®' => '&qfr;',
+ 'ð”¯' => '&rfr;',
+ 'ð”°' => '&sfr;',
+ 'ð”±' => '&tfr;',
+ 'ð”²' => '&ufr;',
+ 'ð”³' => '&vfr;',
+ 'ð”´' => '&wfr;',
+ 'ð”µ' => '&xfr;',
+ 'ð”¶' => '&yfr;',
+ 'ð”·' => '&zfr;',
+ 'ð”¸' => '&Aopf;',
+ 'ð”¹' => '&Bopf;',
+ 'ð”»' => '&Dopf;',
+ 'ð”¼' => '&Eopf;',
+ 'ð”½' => '&Fopf;',
+ 'ð”¾' => '&Gopf;',
+ 'ð•€' => '&Iopf;',
+ 'ð•' => '&Jopf;',
+ 'ð•‚' => '&Kopf;',
+ 'ð•ƒ' => '&Lopf;',
+ 'ð•„' => '&Mopf;',
+ 'ð•†' => '&Oopf;',
+ 'ð•Š' => '&Sopf;',
+ 'ð•‹' => '&Topf;',
+ 'ð•Œ' => '&Uopf;',
+ 'ð•' => '&Vopf;',
+ 'ð•Ž' => '&Wopf;',
+ 'ð•' => '&Xopf;',
+ 'ð•' => '&Yopf;',
+ 'ð•’' => '&aopf;',
+ 'ð•“' => '&bopf;',
+ 'ð•”' => '&copf;',
+ 'ð••' => '&dopf;',
+ 'ð•–' => '&eopf;',
+ 'ð•—' => '&fopf;',
+ 'ð•˜' => '&gopf;',
+ 'ð•™' => '&hopf;',
+ 'ð•š' => '&iopf;',
+ 'ð•›' => '&jopf;',
+ 'ð•œ' => '&kopf;',
+ 'ð•' => '&lopf;',
+ 'ð•ž' => '&mopf;',
+ 'ð•Ÿ' => '&nopf;',
+ 'ð• ' => '&oopf;',
+ 'ð•¡' => '&popf;',
+ 'ð•¢' => '&qopf;',
+ 'ð•£' => '&ropf;',
+ 'ð•¤' => '&sopf;',
+ 'ð•¥' => '&topf;',
+ 'ð•¦' => '&uopf;',
+ 'ð•§' => '&vopf;',
+ 'ð•¨' => '&wopf;',
+ 'ð•©' => '&xopf;',
+ 'ð•ª' => '&yopf;',
+ 'ð•«' => '&zopf;',
+ );
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/OutputRules.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/OutputRules.php
new file mode 100644
index 0000000..ec467f2
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/OutputRules.php
@@ -0,0 +1,553 @@
+<?php
+/**
+ * @file
+ * The rules for generating output in the serializer.
+ *
+ * These output rules are likely to generate output similar to the document that
+ * was parsed. It is not intended to output exactly the document that was parsed.
+ */
+
+namespace Masterminds\HTML5\Serializer;
+
+use Masterminds\HTML5\Elements;
+
+/**
+ * Generate the output html5 based on element rules.
+ */
+class OutputRules implements RulesInterface
+{
+ /**
+ * Defined in http://www.w3.org/TR/html51/infrastructure.html#html-namespace-0.
+ */
+ const NAMESPACE_HTML = 'http://www.w3.org/1999/xhtml';
+
+ const NAMESPACE_MATHML = 'http://www.w3.org/1998/Math/MathML';
+
+ const NAMESPACE_SVG = 'http://www.w3.org/2000/svg';
+
+ const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
+
+ const NAMESPACE_XML = 'http://www.w3.org/XML/1998/namespace';
+
+ const NAMESPACE_XMLNS = 'http://www.w3.org/2000/xmlns/';
+
+ /**
+ * Holds the HTML5 element names that causes a namespace switch.
+ *
+ * @var array
+ */
+ protected $implicitNamespaces = array(
+ self::NAMESPACE_HTML,
+ self::NAMESPACE_SVG,
+ self::NAMESPACE_MATHML,
+ self::NAMESPACE_XML,
+ self::NAMESPACE_XMLNS,
+ );
+
+ const IM_IN_HTML = 1;
+
+ const IM_IN_SVG = 2;
+
+ const IM_IN_MATHML = 3;
+
+ /**
+ * Used as cache to detect if is available ENT_HTML5.
+ *
+ * @var bool
+ */
+ private $hasHTML5 = false;
+
+ protected $traverser;
+
+ protected $encode = false;
+
+ protected $out;
+
+ protected $outputMode;
+
+ private $xpath;
+
+ protected $nonBooleanAttributes = array(
+ /*
+ array(
+ 'nodeNamespace'=>'http://www.w3.org/1999/xhtml',
+ 'attrNamespace'=>'http://www.w3.org/1999/xhtml',
+
+ 'nodeName'=>'img', 'nodeName'=>array('img', 'a'),
+ 'attrName'=>'alt', 'attrName'=>array('title', 'alt'),
+ ),
+ */
+ array(
+ 'nodeNamespace' => 'http://www.w3.org/1999/xhtml',
+ 'attrName' => array('href',
+ 'hreflang',
+ 'http-equiv',
+ 'icon',
+ 'id',
+ 'keytype',
+ 'kind',
+ 'label',
+ 'lang',
+ 'language',
+ 'list',
+ 'maxlength',
+ 'media',
+ 'method',
+ 'name',
+ 'placeholder',
+ 'rel',
+ 'rows',
+ 'rowspan',
+ 'sandbox',
+ 'spellcheck',
+ 'scope',
+ 'seamless',
+ 'shape',
+ 'size',
+ 'sizes',
+ 'span',
+ 'src',
+ 'srcdoc',
+ 'srclang',
+ 'srcset',
+ 'start',
+ 'step',
+ 'style',
+ 'summary',
+ 'tabindex',
+ 'target',
+ 'title',
+ 'type',
+ 'value',
+ 'width',
+ 'border',
+ 'charset',
+ 'cite',
+ 'class',
+ 'code',
+ 'codebase',
+ 'color',
+ 'cols',
+ 'colspan',
+ 'content',
+ 'coords',
+ 'data',
+ 'datetime',
+ 'default',
+ 'dir',
+ 'dirname',
+ 'enctype',
+ 'for',
+ 'form',
+ 'formaction',
+ 'headers',
+ 'height',
+ 'accept',
+ 'accept-charset',
+ 'accesskey',
+ 'action',
+ 'align',
+ 'alt',
+ 'bgcolor',
+ ),
+ ),
+ array(
+ 'nodeNamespace' => 'http://www.w3.org/1999/xhtml',
+ 'xpath' => 'starts-with(local-name(), \'data-\')',
+ ),
+ );
+
+ const DOCTYPE = '<!DOCTYPE html>';
+
+ public function __construct($output, $options = array())
+ {
+ if (isset($options['encode_entities'])) {
+ $this->encode = $options['encode_entities'];
+ }
+
+ $this->outputMode = static::IM_IN_HTML;
+ $this->out = $output;
+ $this->hasHTML5 = defined('ENT_HTML5');
+ }
+
+ public function addRule(array $rule)
+ {
+ $this->nonBooleanAttributes[] = $rule;
+ }
+
+ public function setTraverser(Traverser $traverser)
+ {
+ $this->traverser = $traverser;
+
+ return $this;
+ }
+
+ public function unsetTraverser()
+ {
+ $this->traverser = null;
+
+ return $this;
+ }
+
+ public function document($dom)
+ {
+ $this->doctype();
+ if ($dom->documentElement) {
+ foreach ($dom->childNodes as $node) {
+ $this->traverser->node($node);
+ }
+ $this->nl();
+ }
+ }
+
+ protected function doctype()
+ {
+ $this->wr(static::DOCTYPE);
+ $this->nl();
+ }
+
+ public function element($ele)
+ {
+ $name = $ele->tagName;
+
+ // Per spec:
+ // If the element has a declared namespace in the HTML, MathML or
+ // SVG namespaces, we use the lname instead of the tagName.
+ if ($this->traverser->isLocalElement($ele)) {
+ $name = $ele->localName;
+ }
+
+ // If we are in SVG or MathML there is special handling.
+ // Using if/elseif instead of switch because it's faster in PHP.
+ if ('svg' == $name) {
+ $this->outputMode = static::IM_IN_SVG;
+ $name = Elements::normalizeSvgElement($name);
+ } elseif ('math' == $name) {
+ $this->outputMode = static::IM_IN_MATHML;
+ }
+
+ $this->openTag($ele);
+ if (Elements::isA($name, Elements::TEXT_RAW)) {
+ foreach ($ele->childNodes as $child) {
+ if ($child instanceof \DOMCharacterData) {
+ $this->wr($child->data);
+ } elseif ($child instanceof \DOMElement) {
+ $this->element($child);
+ }
+ }
+ } else {
+ // Handle children.
+ if ($ele->hasChildNodes()) {
+ $this->traverser->children($ele->childNodes);
+ }
+
+ // Close out the SVG or MathML special handling.
+ if ('svg' == $name || 'math' == $name) {
+ $this->outputMode = static::IM_IN_HTML;
+ }
+ }
+
+ // If not unary, add a closing tag.
+ if (!Elements::isA($name, Elements::VOID_TAG)) {
+ $this->closeTag($ele);
+ }
+ }
+
+ /**
+ * Write a text node.
+ *
+ * @param \DOMText $ele The text node to write.
+ */
+ public function text($ele)
+ {
+ if (isset($ele->parentNode) && isset($ele->parentNode->tagName) && Elements::isA($ele->parentNode->localName, Elements::TEXT_RAW)) {
+ $this->wr($ele->data);
+
+ return;
+ }
+
+ // FIXME: This probably needs some flags set.
+ $this->wr($this->enc($ele->data));
+ }
+
+ public function cdata($ele)
+ {
+ // This encodes CDATA.
+ $this->wr($ele->ownerDocument->saveXML($ele));
+ }
+
+ public function comment($ele)
+ {
+ // These produce identical output.
+ // $this->wr('<!--')->wr($ele->data)->wr('-->');
+ $this->wr($ele->ownerDocument->saveXML($ele));
+ }
+
+ public function processorInstruction($ele)
+ {
+ $this->wr('<?')
+ ->wr($ele->target)
+ ->wr(' ')
+ ->wr($ele->data)
+ ->wr('?>');
+ }
+
+ /**
+ * Write the namespace attributes.
+ *
+ * @param \DOMNode $ele The element being written.
+ */
+ protected function namespaceAttrs($ele)
+ {
+ if (!$this->xpath || $this->xpath->document !== $ele->ownerDocument) {
+ $this->xpath = new \DOMXPath($ele->ownerDocument);
+ }
+
+ foreach ($this->xpath->query('namespace::*[not(.=../../namespace::*)]', $ele) as $nsNode) {
+ if (!in_array($nsNode->nodeValue, $this->implicitNamespaces)) {
+ $this->wr(' ')->wr($nsNode->nodeName)->wr('="')->wr($nsNode->nodeValue)->wr('"');
+ }
+ }
+ }
+
+ /**
+ * Write the opening tag.
+ *
+ * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the
+ * qualified name (8.3).
+ *
+ * @param \DOMNode $ele The element being written.
+ */
+ protected function openTag($ele)
+ {
+ $this->wr('<')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName);
+
+ $this->attrs($ele);
+ $this->namespaceAttrs($ele);
+
+ if ($this->outputMode == static::IM_IN_HTML) {
+ $this->wr('>');
+ } // If we are not in html mode we are in SVG, MathML, or XML embedded content.
+ else {
+ if ($ele->hasChildNodes()) {
+ $this->wr('>');
+ } // If there are no children this is self closing.
+ else {
+ $this->wr(' />');
+ }
+ }
+ }
+
+ protected function attrs($ele)
+ {
+ // FIXME: Needs support for xml, xmlns, xlink, and namespaced elements.
+ if (!$ele->hasAttributes()) {
+ return $this;
+ }
+
+ // TODO: Currently, this always writes name="value", and does not do
+ // value-less attributes.
+ $map = $ele->attributes;
+ $len = $map->length;
+ for ($i = 0; $i < $len; ++$i) {
+ $node = $map->item($i);
+ $val = $this->enc($node->value, true);
+
+ // XXX: The spec says that we need to ensure that anything in
+ // the XML, XMLNS, or XLink NS's should use the canonical
+ // prefix. It seems that DOM does this for us already, but there
+ // may be exceptions.
+ $name = $node->nodeName;
+
+ // Special handling for attributes in SVG and MathML.
+ // Using if/elseif instead of switch because it's faster in PHP.
+ if ($this->outputMode == static::IM_IN_SVG) {
+ $name = Elements::normalizeSvgAttribute($name);
+ } elseif ($this->outputMode == static::IM_IN_MATHML) {
+ $name = Elements::normalizeMathMlAttribute($name);
+ }
+
+ $this->wr(' ')->wr($name);
+
+ if ((isset($val) && '' !== $val) || $this->nonBooleanAttribute($node)) {
+ $this->wr('="')->wr($val)->wr('"');
+ }
+ }
+ }
+
+ protected function nonBooleanAttribute(\DOMAttr $attr)
+ {
+ $ele = $attr->ownerElement;
+ foreach ($this->nonBooleanAttributes as $rule) {
+ if (isset($rule['nodeNamespace']) && $rule['nodeNamespace'] !== $ele->namespaceURI) {
+ continue;
+ }
+ if (isset($rule['attNamespace']) && $rule['attNamespace'] !== $attr->namespaceURI) {
+ continue;
+ }
+ if (isset($rule['nodeName']) && !is_array($rule['nodeName']) && $rule['nodeName'] !== $ele->localName) {
+ continue;
+ }
+ if (isset($rule['nodeName']) && is_array($rule['nodeName']) && !in_array($ele->localName, $rule['nodeName'], true)) {
+ continue;
+ }
+ if (isset($rule['attrName']) && !is_array($rule['attrName']) && $rule['attrName'] !== $attr->localName) {
+ continue;
+ }
+ if (isset($rule['attrName']) && is_array($rule['attrName']) && !in_array($attr->localName, $rule['attrName'], true)) {
+ continue;
+ }
+ if (isset($rule['xpath'])) {
+ $xp = $this->getXPath($attr);
+ if (isset($rule['prefixes'])) {
+ foreach ($rule['prefixes'] as $nsPrefix => $ns) {
+ $xp->registerNamespace($nsPrefix, $ns);
+ }
+ }
+ if (!$xp->evaluate($rule['xpath'], $attr)) {
+ continue;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private function getXPath(\DOMNode $node)
+ {
+ if (!$this->xpath) {
+ $this->xpath = new \DOMXPath($node->ownerDocument);
+ }
+
+ return $this->xpath;
+ }
+
+ /**
+ * Write the closing tag.
+ *
+ * Tags for HTML, MathML, and SVG are in the local name. Otherwise, use the
+ * qualified name (8.3).
+ *
+ * @param \DOMNode $ele The element being written.
+ */
+ protected function closeTag($ele)
+ {
+ if ($this->outputMode == static::IM_IN_HTML || $ele->hasChildNodes()) {
+ $this->wr('</')->wr($this->traverser->isLocalElement($ele) ? $ele->localName : $ele->tagName)->wr('>');
+ }
+ }
+
+ /**
+ * Write to the output.
+ *
+ * @param string $text The string to put into the output
+ *
+ * @return $this
+ */
+ protected function wr($text)
+ {
+ fwrite($this->out, $text);
+
+ return $this;
+ }
+
+ /**
+ * Write a new line character.
+ *
+ * @return $this
+ */
+ protected function nl()
+ {
+ fwrite($this->out, PHP_EOL);
+
+ return $this;
+ }
+
+ /**
+ * Encode text.
+ *
+ * When encode is set to false, the default value, the text passed in is
+ * escaped per section 8.3 of the html5 spec. For details on how text is
+ * escaped see the escape() method.
+ *
+ * When encoding is set to true the text is converted to named character
+ * references where appropriate. Section 8.1.4 Character references of the
+ * html5 spec refers to using named character references. This is useful for
+ * characters that can't otherwise legally be used in the text.
+ *
+ * The named character references are listed in section 8.5.
+ *
+ * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#named-character-references True encoding will turn all named character references into their entities.
+ * This includes such characters as +.# and many other common ones. By default
+ * encoding here will just escape &'<>".
+ *
+ * Note, PHP 5.4+ has better html5 encoding.
+ *
+ * @todo Use the Entities class in php 5.3 to have html5 entities.
+ *
+ * @param string $text Text to encode.
+ * @param bool $attribute True if we are encoding an attrubute, false otherwise.
+ *
+ * @return string The encoded text.
+ */
+ protected function enc($text, $attribute = false)
+ {
+ // Escape the text rather than convert to named character references.
+ if (!$this->encode) {
+ return $this->escape($text, $attribute);
+ }
+
+ // If we are in PHP 5.4+ we can use the native html5 entity functionality to
+ // convert the named character references.
+
+ if ($this->hasHTML5) {
+ return htmlentities($text, ENT_HTML5 | ENT_SUBSTITUTE | ENT_QUOTES, 'UTF-8', false);
+ } // If a version earlier than 5.4 html5 entities are not entirely handled.
+ // This manually handles them.
+ else {
+ return strtr($text, HTML5Entities::$map);
+ }
+ }
+
+ /**
+ * Escape test.
+ *
+ * According to the html5 spec section 8.3 Serializing HTML fragments, text
+ * within tags that are not style, script, xmp, iframe, noembed, and noframes
+ * need to be properly escaped.
+ *
+ * The & should be converted to &amp;, no breaking space unicode characters
+ * converted to &nbsp;, when in attribute mode the " should be converted to
+ * &quot;, and when not in attribute mode the < and > should be converted to
+ * &lt; and &gt;.
+ *
+ * @see http://www.w3.org/TR/2013/CR-html5-20130806/syntax.html#escapingString
+ *
+ * @param string $text Text to escape.
+ * @param bool $attribute True if we are escaping an attrubute, false otherwise.
+ */
+ protected function escape($text, $attribute = false)
+ {
+ // Not using htmlspecialchars because, while it does escaping, it doesn't
+ // match the requirements of section 8.5. For example, it doesn't handle
+ // non-breaking spaces.
+ if ($attribute) {
+ $replace = array(
+ '"' => '&quot;',
+ '&' => '&amp;',
+ "\xc2\xa0" => '&nbsp;',
+ );
+ } else {
+ $replace = array(
+ '<' => '&lt;',
+ '>' => '&gt;',
+ '&' => '&amp;',
+ "\xc2\xa0" => '&nbsp;',
+ );
+ }
+
+ return strtr($text, $replace);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/README.md b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/README.md
new file mode 100644
index 0000000..849a47f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/README.md
@@ -0,0 +1,33 @@
+# The Serializer (Writer) Model
+
+The serializer roughly follows sections _8.1 Writing HTML documents_ and section
+_8.3 Serializing HTML fragments_ by converting DOMDocument, DOMDocumentFragment,
+and DOMNodeList into HTML5.
+
+ [ HTML5 ] // Interface for saving.
+ ||
+ [ Traverser ] // Walk the DOM
+ ||
+ [ Rules ] // Convert DOM elements into strings.
+ ||
+ [ HTML5 ] // HTML5 document or fragment in text.
+
+
+## HTML5 Class
+
+Provides the top level interface for saving.
+
+## The Traverser
+
+Walks the DOM finding each element and passing it off to the output rules to
+convert to HTML5.
+
+## Output Rules
+
+The output rules are defined in the RulesInterface which can have multiple
+implementations. Currently, the OutputRules is the default implementation that
+converts a DOM as is into HTML5.
+
+## HTML5 String
+
+The output of the process it HTML5 as a string or saved to a file. \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/RulesInterface.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/RulesInterface.php
new file mode 100644
index 0000000..69a6ecd
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/RulesInterface.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * @file
+ * The interface definition for Rules to generate output.
+ */
+
+namespace Masterminds\HTML5\Serializer;
+
+/**
+ * To create a new rule set for writing output the RulesInterface needs to be implemented.
+ * The resulting class can be specified in the options with the key of rules.
+ *
+ * For an example implementation see Serializer\OutputRules.
+ */
+interface RulesInterface
+{
+ /**
+ * The class constructor.
+ *
+ * Note, before the rules can be used a traverser must be registered.
+ *
+ * @param mixed $output The output stream to write output to.
+ * @param array $options An array of options.
+ */
+ public function __construct($output, $options = array());
+
+ /**
+ * Register the traverser used in but the rules.
+ *
+ * Note, only one traverser can be used by the rules.
+ *
+ * @param Traverser $traverser The traverser used in the rules.
+ *
+ * @return RulesInterface $this for the current object.
+ */
+ public function setTraverser(Traverser $traverser);
+
+ /**
+ * Write a document element (\DOMDocument).
+ *
+ * Instead of returning the result write it to the output stream ($output)
+ * that was passed into the constructor.
+ *
+ * @param \DOMDocument $dom
+ */
+ public function document($dom);
+
+ /**
+ * Write an element.
+ *
+ * Instead of returning the result write it to the output stream ($output)
+ * that was passed into the constructor.
+ *
+ * @param mixed $ele
+ */
+ public function element($ele);
+
+ /**
+ * Write a text node.
+ *
+ * Instead of returning the result write it to the output stream ($output)
+ * that was passed into the constructor.
+ *
+ * @param mixed $ele
+ */
+ public function text($ele);
+
+ /**
+ * Write a CDATA node.
+ *
+ * Instead of returning the result write it to the output stream ($output)
+ * that was passed into the constructor.
+ *
+ * @param mixed $ele
+ */
+ public function cdata($ele);
+
+ /**
+ * Write a comment node.
+ *
+ * Instead of returning the result write it to the output stream ($output)
+ * that was passed into the constructor.
+ *
+ * @param mixed $ele
+ */
+ public function comment($ele);
+
+ /**
+ * Write a processor instruction.
+ *
+ * To learn about processor instructions see InstructionProcessor
+ *
+ * Instead of returning the result write it to the output stream ($output)
+ * that was passed into the constructor.
+ *
+ * @param mixed $ele
+ */
+ public function processorInstruction($ele);
+}
diff --git a/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/Traverser.php b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/Traverser.php
new file mode 100644
index 0000000..1e8d792
--- /dev/null
+++ b/library/vendor/dompdf/vendor/masterminds/html5/src/HTML5/Serializer/Traverser.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Masterminds\HTML5\Serializer;
+
+/**
+ * Traverser for walking a DOM tree.
+ *
+ * This is a concrete traverser designed to convert a DOM tree into an
+ * HTML5 document. It is not intended to be a generic DOMTreeWalker
+ * implementation.
+ *
+ * @see http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#serializing-html-fragments
+ */
+class Traverser
+{
+ /**
+ * Namespaces that should be treated as "local" to HTML5.
+ */
+ protected static $local_ns = array(
+ 'http://www.w3.org/1999/xhtml' => 'html',
+ 'http://www.w3.org/1998/Math/MathML' => 'math',
+ 'http://www.w3.org/2000/svg' => 'svg',
+ );
+
+ protected $dom;
+
+ protected $options;
+
+ protected $encode = false;
+
+ protected $rules;
+
+ protected $out;
+
+ /**
+ * Create a traverser.
+ *
+ * @param \DOMNode|\DOMNodeList $dom The document or node to traverse.
+ * @param resource $out A stream that allows writing. The traverser will output into this
+ * stream.
+ * @param array $options An array of options for the traverser as key/value pairs. These include:
+ * - encode_entities: A bool to specify if full encding should happen for all named
+ * charachter references. Defaults to false which escapes &'<>".
+ * - output_rules: The path to the class handling the output rules.
+ */
+ public function __construct($dom, $out, RulesInterface $rules, $options = array())
+ {
+ $this->dom = $dom;
+ $this->out = $out;
+ $this->rules = $rules;
+ $this->options = $options;
+
+ $this->rules->setTraverser($this);
+ }
+
+ /**
+ * Tell the traverser to walk the DOM.
+ *
+ * @return resource $out Returns the output stream.
+ */
+ public function walk()
+ {
+ if ($this->dom instanceof \DOMDocument) {
+ $this->rules->document($this->dom);
+ } elseif ($this->dom instanceof \DOMDocumentFragment) {
+ // Document fragments are a special case. Only the children need to
+ // be serialized.
+ if ($this->dom->hasChildNodes()) {
+ $this->children($this->dom->childNodes);
+ }
+ } // If NodeList, loop
+ elseif ($this->dom instanceof \DOMNodeList) {
+ // If this is a NodeList of DOMDocuments this will not work.
+ $this->children($this->dom);
+ } // Else assume this is a DOMNode-like datastructure.
+ else {
+ $this->node($this->dom);
+ }
+
+ return $this->out;
+ }
+
+ /**
+ * Process a node in the DOM.
+ *
+ * @param mixed $node A node implementing \DOMNode.
+ */
+ public function node($node)
+ {
+ // A listing of types is at http://php.net/manual/en/dom.constants.php
+ switch ($node->nodeType) {
+ case XML_ELEMENT_NODE:
+ $this->rules->element($node);
+ break;
+ case XML_TEXT_NODE:
+ $this->rules->text($node);
+ break;
+ case XML_CDATA_SECTION_NODE:
+ $this->rules->cdata($node);
+ break;
+ case XML_PI_NODE:
+ $this->rules->processorInstruction($node);
+ break;
+ case XML_COMMENT_NODE:
+ $this->rules->comment($node);
+ break;
+ // Currently we don't support embedding DTDs.
+ default:
+ //print '<!-- Skipped -->';
+ break;
+ }
+ }
+
+ /**
+ * Walk through all the nodes on a node list.
+ *
+ * @param \DOMNodeList $nl A list of child elements to walk through.
+ */
+ public function children($nl)
+ {
+ foreach ($nl as $node) {
+ $this->node($node);
+ }
+ }
+
+ /**
+ * Is an element local?
+ *
+ * @param mixed $ele An element that implement \DOMNode.
+ *
+ * @return bool true if local and false otherwise.
+ */
+ public function isLocalElement($ele)
+ {
+ $uri = $ele->namespaceURI;
+ if (empty($uri)) {
+ return false;
+ }
+
+ return isset(static::$local_ns[$uri]);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/LICENSE b/library/vendor/dompdf/vendor/phenx/php-font-lib/LICENSE
new file mode 100644
index 0000000..bca992d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/LICENSE
@@ -0,0 +1,456 @@
+ 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. \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/README.md b/library/vendor/dompdf/vendor/phenx/php-font-lib/README.md
new file mode 100644
index 0000000..0fa24ee
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/README.md
@@ -0,0 +1,28 @@
+[![PHPUnit tests](https://github.com/dompdf/php-font-lib/actions/workflows/phpunit.yml/badge.svg)](https://github.com/dompdf/php-font-lib/actions/workflows/phpunit.yml)
+
+# PHP Font Lib
+
+This library can be used to:
+ * Read TrueType, OpenType (with TrueType glyphs), WOFF font files
+ * Extract basic info (name, style, etc)
+ * Extract advanced info (horizontal metrics, glyph names, glyph shapes, etc)
+ * Make an Adobe Font Metrics (AFM) file from a font
+
+You can find a demo GUI [here](http://pxd.me/php-font-lib/www/font_explorer.html).
+
+This project was initiated by the need to read font files in the [DOMPDF project](https://github.com/dompdf/dompdf).
+
+Usage Example
+-------------
+
+```
+$font = \FontLib\Font::load('../../fontfile.ttf');
+$font->parse(); // for getFontWeight() to work this call must be done first!
+echo $font->getFontName() .'<br>';
+echo $font->getFontSubfamily() .'<br>';
+echo $font->getFontSubfamilyID() .'<br>';
+echo $font->getFontFullName() .'<br>';
+echo $font->getFontVersion() .'<br>';
+echo $font->getFontWeight() .'<br>';
+echo $font->getFontPostscriptName() .'<br>';
+```
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/bower.json b/library/vendor/dompdf/vendor/phenx/php-font-lib/bower.json
new file mode 100644
index 0000000..0a4a45b
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/bower.json
@@ -0,0 +1,23 @@
+{
+ "name": "php-font-lib",
+ "version": "0.3.1",
+ "license": "LGPL-3.0",
+ "keywords": [
+ "font",
+ "parse",
+ "export",
+ "truetype",
+ "opentype",
+ "woff"
+ ],
+ "homepage": "https://github.com/PhenX/php-font-lib",
+ "_release": "0.3.1",
+ "_resolution": {
+ "type": "version",
+ "tag": "v0.3.1",
+ "commit": "d13682b7e27d14a6323c441426f3dde1cd86c751"
+ },
+ "_source": "https://github.com/PhenX/php-font-lib.git",
+ "_target": "*",
+ "_originalSource": "https://github.com/PhenX/php-font-lib.git"
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/composer.json b/library/vendor/dompdf/vendor/phenx/php-font-lib/composer.json
new file mode 100644
index 0000000..29b0653
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/composer.json
@@ -0,0 +1,32 @@
+{
+ "name": "phenx/php-font-lib",
+ "type": "library",
+ "description": "A library to read, parse, export and make subsets of different types of font files.",
+ "homepage": "https://github.com/PhenX/php-font-lib",
+ "license": "LGPL-3.0",
+ "authors": [
+ {
+ "name": "Fabien Ménager",
+ "email": "fabien.menager@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "FontLib\\": "src/FontLib"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "FontLib\\Tests\\": "tests/FontLib"
+ }
+ },
+ "config": {
+ "bin-dir": "bin"
+ },
+ "require": {
+ "ext-mbstring": "*"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge" : "^3 || ^4 || ^5"
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/index.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/index.php
new file mode 100644
index 0000000..7ed173a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/index.php
@@ -0,0 +1 @@
+<?php header("Location: www/"); ?> \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/adobe-standard-encoding.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/adobe-standard-encoding.map
new file mode 100644
index 0000000..230d4a1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/adobe-standard-encoding.map
@@ -0,0 +1,231 @@
+// Adobe Standard Encoding table for ttf2pt1
+// Thomas Henlich <Thomas.Henlich@mailbox.tu-dresden.de>
+
+=20 U+0020 SPACE
+=21 U+0021 EXCLAMATION MARK
+=22 U+0022 QUOTATION MARK
+=23 U+0023 NUMBER SIGN
+=24 U+0024 DOLLAR SIGN
+=25 U+0025 PERCENT SIGN
+=26 U+0026 AMPERSAND
+=27 U+2019 RIGHT SINGLE QUOTATION MARK
+=28 U+0028 LEFT PARENTHESIS
+=29 U+0029 RIGHT PARENTHESIS
+=2A U+002A ASTERISK
+=2B U+002B PLUS SIGN
+=2C U+002C COMMA
+=2D U+002D HYPHEN-MINUS
+=2E U+002E FULL STOP
+=2F U+002F SOLIDUS
+=30 U+0030 DIGIT ZERO
+=31 U+0031 DIGIT ONE
+=32 U+0032 DIGIT TWO
+=33 U+0033 DIGIT THREE
+=34 U+0034 DIGIT FOUR
+=35 U+0035 DIGIT FIVE
+=36 U+0036 DIGIT SIX
+=37 U+0037 DIGIT SEVEN
+=38 U+0038 DIGIT EIGHT
+=39 U+0039 DIGIT NINE
+=3A U+003A COLON
+=3B U+003B SEMICOLON
+=3C U+003C LESS-THAN SIGN
+=3D U+003D EQUALS SIGN
+=3E U+003E GREATER-THAN SIGN
+=3F U+003F QUESTION MARK
+=40 U+0040 COMMERCIAL AT
+=41 U+0041 LATIN CAPITAL LETTER A
+=42 U+0042 LATIN CAPITAL LETTER B
+=43 U+0043 LATIN CAPITAL LETTER C
+=44 U+0044 LATIN CAPITAL LETTER D
+=45 U+0045 LATIN CAPITAL LETTER E
+=46 U+0046 LATIN CAPITAL LETTER F
+=47 U+0047 LATIN CAPITAL LETTER G
+=48 U+0048 LATIN CAPITAL LETTER H
+=49 U+0049 LATIN CAPITAL LETTER I
+=4A U+004A LATIN CAPITAL LETTER J
+=4B U+004B LATIN CAPITAL LETTER K
+=4C U+004C LATIN CAPITAL LETTER L
+=4D U+004D LATIN CAPITAL LETTER M
+=4E U+004E LATIN CAPITAL LETTER N
+=4F U+004F LATIN CAPITAL LETTER O
+=50 U+0050 LATIN CAPITAL LETTER P
+=51 U+0051 LATIN CAPITAL LETTER Q
+=52 U+0052 LATIN CAPITAL LETTER R
+=53 U+0053 LATIN CAPITAL LETTER S
+=54 U+0054 LATIN CAPITAL LETTER T
+=55 U+0055 LATIN CAPITAL LETTER U
+=56 U+0056 LATIN CAPITAL LETTER V
+=57 U+0057 LATIN CAPITAL LETTER W
+=58 U+0058 LATIN CAPITAL LETTER X
+=59 U+0059 LATIN CAPITAL LETTER Y
+=5A U+005A LATIN CAPITAL LETTER Z
+=5B U+005B LEFT SQUARE BRACKET
+=5C U+005C REVERSE SOLIDUS
+=5D U+005D RIGHT SQUARE BRACKET
+=5E U+005E CIRCUMFLEX ACCENT
+=5F U+005F LOW LINE
+=60 U+2018 LEFT SINGLE QUOTATION MARK
+=61 U+0061 LATIN SMALL LETTER A
+=62 U+0062 LATIN SMALL LETTER B
+=63 U+0063 LATIN SMALL LETTER C
+=64 U+0064 LATIN SMALL LETTER D
+=65 U+0065 LATIN SMALL LETTER E
+=66 U+0066 LATIN SMALL LETTER F
+=67 U+0067 LATIN SMALL LETTER G
+=68 U+0068 LATIN SMALL LETTER H
+=69 U+0069 LATIN SMALL LETTER I
+=6A U+006A LATIN SMALL LETTER J
+=6B U+006B LATIN SMALL LETTER K
+=6C U+006C LATIN SMALL LETTER L
+=6D U+006D LATIN SMALL LETTER M
+=6E U+006E LATIN SMALL LETTER N
+=6F U+006F LATIN SMALL LETTER O
+=70 U+0070 LATIN SMALL LETTER P
+=71 U+0071 LATIN SMALL LETTER Q
+=72 U+0072 LATIN SMALL LETTER R
+=73 U+0073 LATIN SMALL LETTER S
+=74 U+0074 LATIN SMALL LETTER T
+=75 U+0075 LATIN SMALL LETTER U
+=76 U+0076 LATIN SMALL LETTER V
+=77 U+0077 LATIN SMALL LETTER W
+=78 U+0078 LATIN SMALL LETTER X
+=79 U+0079 LATIN SMALL LETTER Y
+=7A U+007A LATIN SMALL LETTER Z
+=7B U+007B LEFT CURLY BRACKET
+=7C U+007C VERTICAL LINE
+=7D U+007D RIGHT CURLY BRACKET
+=7E U+007E TILDE
+=A1 U+00A1 INVERTED EXCLAMATION MARK
+=A2 U+00A2 CENT SIGN
+=A3 U+00A3 POUND SIGN
+=A4 U+2044 FRACTION SLASH
+=A5 U+00A5 YEN SIGN
+=A6 U+0192 LATIN SMALL LETTER F WITH HOOK
+=A7 U+00A7 SECTION SIGN
+=A8 U+00A4 CURRENCY SIGN
+=A9 U+0027 APOSTROPHE
+=AA U+201C LEFT DOUBLE QUOTATION MARK
+=AB U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+=AC U+2039 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+=AD U+203A SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+=AE U+FB01 LATIN SMALL LIGATURE FI
+=AF U+FB02 LATIN SMALL LIGATURE FL
+=B1 U+2013 EN DASH
+=B2 U+2020 DAGGER
+=B3 U+2021 DOUBLE DAGGER
+=B4 U+00B7 MIDDLE DOT
+=B6 U+00B6 PILCROW SIGN
+=B7 U+2022 BULLET
+=B8 U+201A SINGLE LOW-9 QUOTATION MARK
+=B9 U+201E DOUBLE LOW-9 QUOTATION MARK
+=BA U+201D RIGHT DOUBLE QUOTATION MARK
+=BB U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+=BC U+2026 HORIZONTAL ELLIPSIS
+=BD U+2030 PER MILLE SIGN
+=BF U+00BF INVERTED QUESTION MARK
+=C1 U+0060 GRAVE ACCENT
+=C2 U+00B4 ACUTE ACCENT
+=C3 U+02C6 MODIFIER LETTER CIRCUMFLEX ACCENT
+=C4 U+02DC SMALL TILDE
+=C5 U+00AF MACRON
+=C6 U+02D8 BREVE
+=C7 U+02D9 DOT ABOVE
+=C8 U+00A8 DIAERESIS
+=CA U+02DA RING ABOVE
+=CB U+00B8 CEDILLA
+=CD U+02DD DOUBLE ACUTE ACCENT
+=CE U+02DB OGONEK
+=CF U+02C7 CARON
+=D0 U+2014 EM DASH
+=E1 U+00C6 LATIN CAPITAL LETTER AE
+=E3 U+00AA FEMININE ORDINAL INDICATOR
+=E8 U+0141 LATIN CAPITAL LETTER L WITH STROKE
+=E9 U+00D8 LATIN CAPITAL LETTER O WITH STROKE
+=EA U+0152 LATIN CAPITAL LIGATURE OE
+=EB U+00BA MASCULINE ORDINAL INDICATOR
+=F1 U+00E6 LATIN SMALL LETTER AE
+=F5 U+0131 LATIN SMALL LETTER DOTLESS I
+=F8 U+0142 LATIN SMALL LETTER L WITH STROKE
+=F9 U+00F8 LATIN SMALL LETTER O WITH STROKE
+=FA U+0153 LATIN SMALL LIGATURE OE
+=FB U+00DF LATIN SMALL LETTER SHARP S
+
+// unencoded characters:
+=100 U+00E7 LATIN SMALL LETTER C WITH CEDILLA
+=101 U+00FF LATIN SMALL LETTER Y WITH DIAERESIS
+=102 U+00E3 LATIN SMALL LETTER A WITH TILDE
+=103 U+00EE LATIN SMALL LETTER I WITH CIRCUMFLEX
+=104 U+00B3 SUPERSCRIPT THREE
+=105 U+00EA LATIN SMALL LETTER E WITH CIRCUMFLEX
+=106 U+00FE LATIN SMALL LETTER THORN
+=107 U+00E8 LATIN SMALL LETTER E WITH GRAVE
+=108 U+00B2 SUPERSCRIPT TWO
+=109 U+00E9 LATIN SMALL LETTER E WITH ACUTE
+=10A U+00F5 LATIN SMALL LETTER O WITH TILDE
+=10B U+00C1 LATIN CAPITAL LETTER A WITH ACUTE
+=10C U+00F4 LATIN SMALL LETTER O WITH CIRCUMFLEX
+=10D U+00FD LATIN SMALL LETTER Y WITH ACUTE
+=10E U+00FC LATIN SMALL LETTER U WITH DIAERESIS
+=10F U+00BE VULGAR FRACTION THREE QUARTERS
+=110 U+00E2 LATIN SMALL LETTER A WITH CIRCUMFLEX
+=111 U+00D0 LATIN CAPITAL LETTER ETH
+=112 U+00EB LATIN SMALL LETTER E WITH DIAERESIS
+=113 U+00F9 LATIN SMALL LETTER U WITH GRAVE
+=114 U+2122 TRADE MARK SIGN
+=115 U+00F2 LATIN SMALL LETTER O WITH GRAVE
+=116 U+0161 LATIN SMALL LETTER S WITH CARON
+=117 U+00CF LATIN CAPITAL LETTER I WITH DIAERESIS
+=118 U+00FA LATIN SMALL LETTER U WITH ACUTE
+=119 U+00E0 LATIN SMALL LETTER A WITH GRAVE
+=11A U+00F1 LATIN SMALL LETTER N WITH TILDE
+=11B U+00E5 LATIN SMALL LETTER A WITH RING ABOVE
+=11C U+017E LATIN SMALL LETTER Z WITH CARON
+=11D U+00CE LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+=11E U+00D1 LATIN CAPITAL LETTER N WITH TILDE
+=11F U+00FB LATIN SMALL LETTER U WITH CIRCUMFLEX
+=120 U+00CA LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+=121 U+00CD LATIN CAPITAL LETTER I WITH ACUTE
+=122 U+00C7 LATIN CAPITAL LETTER C WITH CEDILLA
+=123 U+00D6 LATIN CAPITAL LETTER O WITH DIAERESIS
+=124 U+0160 LATIN CAPITAL LETTER S WITH CARON
+=125 U+00CC LATIN CAPITAL LETTER I WITH GRAVE
+=126 U+00E4 LATIN SMALL LETTER A WITH DIAERESIS
+=127 U+00D2 LATIN CAPITAL LETTER O WITH GRAVE
+=128 U+00C8 LATIN CAPITAL LETTER E WITH GRAVE
+=129 U+0178 LATIN CAPITAL LETTER Y WITH DIAERESIS
+=12A U+00AE REGISTERED SIGN
+=12B U+00D5 LATIN CAPITAL LETTER O WITH TILDE
+=12C U+00BC VULGAR FRACTION ONE QUARTER
+=12D U+00D9 LATIN CAPITAL LETTER U WITH GRAVE
+=12E U+00DB LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+=12F U+00DE LATIN CAPITAL LETTER THORN
+=130 U+00F7 DIVISION SIGN
+=131 U+00C3 LATIN CAPITAL LETTER A WITH TILDE
+=132 U+00DA LATIN CAPITAL LETTER U WITH ACUTE
+=133 U+00D4 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+=134 U+00AC NOT SIGN
+=135 U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE
+=136 U+00EF LATIN SMALL LETTER I WITH DIAERESIS
+=137 U+00ED LATIN SMALL LETTER I WITH ACUTE
+=138 U+00E1 LATIN SMALL LETTER A WITH ACUTE
+=139 U+00B1 PLUS-MINUS SIGN
+=13A U+00D7 MULTIPLICATION SIGN
+=13B U+00DC LATIN CAPITAL LETTER U WITH DIAERESIS
+=13C U+2212 MINUS SIGN
+=13D U+00B9 SUPERSCRIPT ONE
+=13E U+00C9 LATIN CAPITAL LETTER E WITH ACUTE
+=13F U+00C2 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+=140 U+00A9 COPYRIGHT SIGN
+=141 U+00C0 LATIN CAPITAL LETTER A WITH GRAVE
+=142 U+00F6 LATIN SMALL LETTER O WITH DIAERESIS
+=143 U+00F3 LATIN SMALL LETTER O WITH ACUTE
+=144 U+00B0 DEGREE SIGN
+=145 U+00EC LATIN SMALL LETTER I WITH GRAVE
+=146 U+00B5 MICRO SIGN
+=147 U+00D3 LATIN CAPITAL LETTER O WITH ACUTE
+=148 U+00F0 LATIN SMALL LETTER ETH
+=149 U+00C4 LATIN CAPITAL LETTER A WITH DIAERESIS
+=14A U+00DD LATIN CAPITAL LETTER Y WITH ACUTE
+=14B U+00A6 BROKEN BAR
+=14C U+00BD VULGAR FRACTION ONE HALF
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1250.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1250.map
new file mode 100644
index 0000000..33a555e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1250.map
@@ -0,0 +1,251 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!89 U+2030 perthousand
+!8A U+0160 Scaron
+!8B U+2039 guilsinglleft
+!8C U+015A Sacute
+!8D U+0164 Tcaron
+!8E U+017D Zcaron
+!8F U+0179 Zacute
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!99 U+2122 trademark
+!9A U+0161 scaron
+!9B U+203A guilsinglright
+!9C U+015B sacute
+!9D U+0165 tcaron
+!9E U+017E zcaron
+!9F U+017A zacute
+!A0 U+00A0 space
+!A1 U+02C7 caron
+!A2 U+02D8 breve
+!A3 U+0141 Lslash
+!A4 U+00A4 currency
+!A5 U+0104 Aogonek
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+015E Scedilla
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+017B Zdotaccent
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+02DB ogonek
+!B3 U+0142 lslash
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+0105 aogonek
+!BA U+015F scedilla
+!BB U+00BB guillemotright
+!BC U+013D Lcaron
+!BD U+02DD hungarumlaut
+!BE U+013E lcaron
+!BF U+017C zdotaccent
+!C0 U+0154 Racute
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+0102 Abreve
+!C4 U+00C4 Adieresis
+!C5 U+0139 Lacute
+!C6 U+0106 Cacute
+!C7 U+00C7 Ccedilla
+!C8 U+010C Ccaron
+!C9 U+00C9 Eacute
+!CA U+0118 Eogonek
+!CB U+00CB Edieresis
+!CC U+011A Ecaron
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+010E Dcaron
+!D0 U+0110 Dcroat
+!D1 U+0143 Nacute
+!D2 U+0147 Ncaron
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+0150 Ohungarumlaut
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+0158 Rcaron
+!D9 U+016E Uring
+!DA U+00DA Uacute
+!DB U+0170 Uhungarumlaut
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+0162 Tcommaaccent
+!DF U+00DF germandbls
+!E0 U+0155 racute
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+0103 abreve
+!E4 U+00E4 adieresis
+!E5 U+013A lacute
+!E6 U+0107 cacute
+!E7 U+00E7 ccedilla
+!E8 U+010D ccaron
+!E9 U+00E9 eacute
+!EA U+0119 eogonek
+!EB U+00EB edieresis
+!EC U+011B ecaron
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+010F dcaron
+!F0 U+0111 dcroat
+!F1 U+0144 nacute
+!F2 U+0148 ncaron
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+0151 ohungarumlaut
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+0159 rcaron
+!F9 U+016F uring
+!FA U+00FA uacute
+!FB U+0171 uhungarumlaut
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+0163 tcommaaccent
+!FF U+02D9 dotaccent
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1251.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1251.map
new file mode 100644
index 0000000..b5960fe
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1251.map
@@ -0,0 +1,255 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0402 afii10051
+!81 U+0403 afii10052
+!82 U+201A quotesinglbase
+!83 U+0453 afii10100
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!88 U+20AC Euro
+!89 U+2030 perthousand
+!8A U+0409 afii10058
+!8B U+2039 guilsinglleft
+!8C U+040A afii10059
+!8D U+040C afii10061
+!8E U+040B afii10060
+!8F U+040F afii10145
+!90 U+0452 afii10099
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!99 U+2122 trademark
+!9A U+0459 afii10106
+!9B U+203A guilsinglright
+!9C U+045A afii10107
+!9D U+045C afii10109
+!9E U+045B afii10108
+!9F U+045F afii10193
+!A0 U+00A0 space
+!A1 U+040E afii10062
+!A2 U+045E afii10110
+!A3 U+0408 afii10057
+!A4 U+00A4 currency
+!A5 U+0490 afii10050
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+0401 afii10023
+!A9 U+00A9 copyright
+!AA U+0404 afii10053
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+0407 afii10056
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+0406 afii10055
+!B3 U+0456 afii10103
+!B4 U+0491 afii10098
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+0451 afii10071
+!B9 U+2116 afii61352
+!BA U+0454 afii10101
+!BB U+00BB guillemotright
+!BC U+0458 afii10105
+!BD U+0405 afii10054
+!BE U+0455 afii10102
+!BF U+0457 afii10104
+!C0 U+0410 afii10017
+!C1 U+0411 afii10018
+!C2 U+0412 afii10019
+!C3 U+0413 afii10020
+!C4 U+0414 afii10021
+!C5 U+0415 afii10022
+!C6 U+0416 afii10024
+!C7 U+0417 afii10025
+!C8 U+0418 afii10026
+!C9 U+0419 afii10027
+!CA U+041A afii10028
+!CB U+041B afii10029
+!CC U+041C afii10030
+!CD U+041D afii10031
+!CE U+041E afii10032
+!CF U+041F afii10033
+!D0 U+0420 afii10034
+!D1 U+0421 afii10035
+!D2 U+0422 afii10036
+!D3 U+0423 afii10037
+!D4 U+0424 afii10038
+!D5 U+0425 afii10039
+!D6 U+0426 afii10040
+!D7 U+0427 afii10041
+!D8 U+0428 afii10042
+!D9 U+0429 afii10043
+!DA U+042A afii10044
+!DB U+042B afii10045
+!DC U+042C afii10046
+!DD U+042D afii10047
+!DE U+042E afii10048
+!DF U+042F afii10049
+!E0 U+0430 afii10065
+!E1 U+0431 afii10066
+!E2 U+0432 afii10067
+!E3 U+0433 afii10068
+!E4 U+0434 afii10069
+!E5 U+0435 afii10070
+!E6 U+0436 afii10072
+!E7 U+0437 afii10073
+!E8 U+0438 afii10074
+!E9 U+0439 afii10075
+!EA U+043A afii10076
+!EB U+043B afii10077
+!EC U+043C afii10078
+!ED U+043D afii10079
+!EE U+043E afii10080
+!EF U+043F afii10081
+!F0 U+0440 afii10082
+!F1 U+0441 afii10083
+!F2 U+0442 afii10084
+!F3 U+0443 afii10085
+!F4 U+0444 afii10086
+!F5 U+0445 afii10087
+!F6 U+0446 afii10088
+!F7 U+0447 afii10089
+!F8 U+0448 afii10090
+!F9 U+0449 afii10091
+!FA U+044A afii10092
+!FB U+044B afii10093
+!FC U+044C afii10094
+!FD U+044D afii10095
+!FE U+044E afii10096
+!FF U+044F afii10097
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1252.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1252.map
new file mode 100644
index 0000000..b79015c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1252.map
@@ -0,0 +1,251 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!83 U+0192 florin
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!88 U+02C6 circumflex
+!89 U+2030 perthousand
+!8A U+0160 Scaron
+!8B U+2039 guilsinglleft
+!8C U+0152 OE
+!8E U+017D Zcaron
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!98 U+02DC tilde
+!99 U+2122 trademark
+!9A U+0161 scaron
+!9B U+203A guilsinglright
+!9C U+0153 oe
+!9E U+017E zcaron
+!9F U+0178 Ydieresis
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+00D0 Eth
+!D1 U+00D1 Ntilde
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+00DE Thorn
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+00F0 eth
+!F1 U+00F1 ntilde
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+00FE thorn
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1253.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1253.map
new file mode 100644
index 0000000..b5d843c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1253.map
@@ -0,0 +1,239 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!83 U+0192 florin
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!89 U+2030 perthousand
+!8B U+2039 guilsinglleft
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!99 U+2122 trademark
+!9B U+203A guilsinglright
+!A0 U+00A0 space
+!A1 U+0385 dieresistonos
+!A2 U+0386 Alphatonos
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+2015 afii00208
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+0384 tonos
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+0388 Epsilontonos
+!B9 U+0389 Etatonos
+!BA U+038A Iotatonos
+!BB U+00BB guillemotright
+!BC U+038C Omicrontonos
+!BD U+00BD onehalf
+!BE U+038E Upsilontonos
+!BF U+038F Omegatonos
+!C0 U+0390 iotadieresistonos
+!C1 U+0391 Alpha
+!C2 U+0392 Beta
+!C3 U+0393 Gamma
+!C4 U+0394 Delta
+!C5 U+0395 Epsilon
+!C6 U+0396 Zeta
+!C7 U+0397 Eta
+!C8 U+0398 Theta
+!C9 U+0399 Iota
+!CA U+039A Kappa
+!CB U+039B Lambda
+!CC U+039C Mu
+!CD U+039D Nu
+!CE U+039E Xi
+!CF U+039F Omicron
+!D0 U+03A0 Pi
+!D1 U+03A1 Rho
+!D3 U+03A3 Sigma
+!D4 U+03A4 Tau
+!D5 U+03A5 Upsilon
+!D6 U+03A6 Phi
+!D7 U+03A7 Chi
+!D8 U+03A8 Psi
+!D9 U+03A9 Omega
+!DA U+03AA Iotadieresis
+!DB U+03AB Upsilondieresis
+!DC U+03AC alphatonos
+!DD U+03AD epsilontonos
+!DE U+03AE etatonos
+!DF U+03AF iotatonos
+!E0 U+03B0 upsilondieresistonos
+!E1 U+03B1 alpha
+!E2 U+03B2 beta
+!E3 U+03B3 gamma
+!E4 U+03B4 delta
+!E5 U+03B5 epsilon
+!E6 U+03B6 zeta
+!E7 U+03B7 eta
+!E8 U+03B8 theta
+!E9 U+03B9 iota
+!EA U+03BA kappa
+!EB U+03BB lambda
+!EC U+03BC mu
+!ED U+03BD nu
+!EE U+03BE xi
+!EF U+03BF omicron
+!F0 U+03C0 pi
+!F1 U+03C1 rho
+!F2 U+03C2 sigma1
+!F3 U+03C3 sigma
+!F4 U+03C4 tau
+!F5 U+03C5 upsilon
+!F6 U+03C6 phi
+!F7 U+03C7 chi
+!F8 U+03C8 psi
+!F9 U+03C9 omega
+!FA U+03CA iotadieresis
+!FB U+03CB upsilondieresis
+!FC U+03CC omicrontonos
+!FD U+03CD upsilontonos
+!FE U+03CE omegatonos
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1254.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1254.map
new file mode 100644
index 0000000..3cc8c78
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1254.map
@@ -0,0 +1,249 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!83 U+0192 florin
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!88 U+02C6 circumflex
+!89 U+2030 perthousand
+!8A U+0160 Scaron
+!8B U+2039 guilsinglleft
+!8C U+0152 OE
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!98 U+02DC tilde
+!99 U+2122 trademark
+!9A U+0161 scaron
+!9B U+203A guilsinglright
+!9C U+0153 oe
+!9F U+0178 Ydieresis
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+011E Gbreve
+!D1 U+00D1 Ntilde
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+0130 Idotaccent
+!DE U+015E Scedilla
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+011F gbreve
+!F1 U+00F1 ntilde
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+0131 dotlessi
+!FE U+015F scedilla
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1255.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1255.map
new file mode 100644
index 0000000..fa13530
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1255.map
@@ -0,0 +1,233 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!83 U+0192 florin
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!88 U+02C6 circumflex
+!89 U+2030 perthousand
+!8B U+2039 guilsinglleft
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!98 U+02DC tilde
+!99 U+2122 trademark
+!9B U+203A guilsinglright
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+20AA afii57636
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00D7 multiply
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD sfthyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 middot
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00F7 divide
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+05B0 afii57799
+!C1 U+05B1 afii57801
+!C2 U+05B2 afii57800
+!C3 U+05B3 afii57802
+!C4 U+05B4 afii57793
+!C5 U+05B5 afii57794
+!C6 U+05B6 afii57795
+!C7 U+05B7 afii57798
+!C8 U+05B8 afii57797
+!C9 U+05B9 afii57806
+!CB U+05BB afii57796
+!CC U+05BC afii57807
+!CD U+05BD afii57839
+!CE U+05BE afii57645
+!CF U+05BF afii57841
+!D0 U+05C0 afii57842
+!D1 U+05C1 afii57804
+!D2 U+05C2 afii57803
+!D3 U+05C3 afii57658
+!D4 U+05F0 afii57716
+!D5 U+05F1 afii57717
+!D6 U+05F2 afii57718
+!D7 U+05F3 gereshhebrew
+!D8 U+05F4 gershayimhebrew
+!E0 U+05D0 afii57664
+!E1 U+05D1 afii57665
+!E2 U+05D2 afii57666
+!E3 U+05D3 afii57667
+!E4 U+05D4 afii57668
+!E5 U+05D5 afii57669
+!E6 U+05D6 afii57670
+!E7 U+05D7 afii57671
+!E8 U+05D8 afii57672
+!E9 U+05D9 afii57673
+!EA U+05DA afii57674
+!EB U+05DB afii57675
+!EC U+05DC afii57676
+!ED U+05DD afii57677
+!EE U+05DE afii57678
+!EF U+05DF afii57679
+!F0 U+05E0 afii57680
+!F1 U+05E1 afii57681
+!F2 U+05E2 afii57682
+!F3 U+05E3 afii57683
+!F4 U+05E4 afii57684
+!F5 U+05E5 afii57685
+!F6 U+05E6 afii57686
+!F7 U+05E7 afii57687
+!F8 U+05E8 afii57688
+!F9 U+05E9 afii57689
+!FA U+05EA afii57690
+!FD U+200E afii299
+!FE U+200F afii300
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1257.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1257.map
new file mode 100644
index 0000000..077fdc3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1257.map
@@ -0,0 +1,244 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!89 U+2030 perthousand
+!8B U+2039 guilsinglleft
+!8D U+00A8 dieresis
+!8E U+02C7 caron
+!8F U+00B8 cedilla
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!99 U+2122 trademark
+!9B U+203A guilsinglright
+!9D U+00AF macron
+!9E U+02DB ogonek
+!A0 U+00A0 space
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00D8 Oslash
+!A9 U+00A9 copyright
+!AA U+0156 Rcommaaccent
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00C6 AE
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00F8 oslash
+!B9 U+00B9 onesuperior
+!BA U+0157 rcommaaccent
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00E6 ae
+!C0 U+0104 Aogonek
+!C1 U+012E Iogonek
+!C2 U+0100 Amacron
+!C3 U+0106 Cacute
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+0118 Eogonek
+!C7 U+0112 Emacron
+!C8 U+010C Ccaron
+!C9 U+00C9 Eacute
+!CA U+0179 Zacute
+!CB U+0116 Edotaccent
+!CC U+0122 Gcommaaccent
+!CD U+0136 Kcommaaccent
+!CE U+012A Imacron
+!CF U+013B Lcommaaccent
+!D0 U+0160 Scaron
+!D1 U+0143 Nacute
+!D2 U+0145 Ncommaaccent
+!D3 U+00D3 Oacute
+!D4 U+014C Omacron
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+0172 Uogonek
+!D9 U+0141 Lslash
+!DA U+015A Sacute
+!DB U+016A Umacron
+!DC U+00DC Udieresis
+!DD U+017B Zdotaccent
+!DE U+017D Zcaron
+!DF U+00DF germandbls
+!E0 U+0105 aogonek
+!E1 U+012F iogonek
+!E2 U+0101 amacron
+!E3 U+0107 cacute
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+0119 eogonek
+!E7 U+0113 emacron
+!E8 U+010D ccaron
+!E9 U+00E9 eacute
+!EA U+017A zacute
+!EB U+0117 edotaccent
+!EC U+0123 gcommaaccent
+!ED U+0137 kcommaaccent
+!EE U+012B imacron
+!EF U+013C lcommaaccent
+!F0 U+0161 scaron
+!F1 U+0144 nacute
+!F2 U+0146 ncommaaccent
+!F3 U+00F3 oacute
+!F4 U+014D omacron
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+0173 uogonek
+!F9 U+0142 lslash
+!FA U+015B sacute
+!FB U+016B umacron
+!FC U+00FC udieresis
+!FD U+017C zdotaccent
+!FE U+017E zcaron
+!FF U+02D9 dotaccent
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1258.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1258.map
new file mode 100644
index 0000000..7032c35
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp1258.map
@@ -0,0 +1,247 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!83 U+0192 florin
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!88 U+02C6 circumflex
+!89 U+2030 perthousand
+!8B U+2039 guilsinglleft
+!8C U+0152 OE
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!98 U+02DC tilde
+!99 U+2122 trademark
+!9B U+203A guilsinglright
+!9C U+0153 oe
+!9F U+0178 Ydieresis
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+0102 Abreve
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+0300 gravecomb
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+0110 Dcroat
+!D1 U+00D1 Ntilde
+!D2 U+0309 hookabovecomb
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+01A0 Ohorn
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+01AF Uhorn
+!DE U+0303 tildecomb
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+0103 abreve
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+0301 acutecomb
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+0111 dcroat
+!F1 U+00F1 ntilde
+!F2 U+0323 dotbelowcomb
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+01A1 ohorn
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+01B0 uhorn
+!FE U+20AB dong
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp874.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp874.map
new file mode 100644
index 0000000..16330d0
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/cp874.map
@@ -0,0 +1,225 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!85 U+2026 ellipsis
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!A0 U+00A0 space
+!A1 U+0E01 kokaithai
+!A2 U+0E02 khokhaithai
+!A3 U+0E03 khokhuatthai
+!A4 U+0E04 khokhwaithai
+!A5 U+0E05 khokhonthai
+!A6 U+0E06 khorakhangthai
+!A7 U+0E07 ngonguthai
+!A8 U+0E08 chochanthai
+!A9 U+0E09 chochingthai
+!AA U+0E0A chochangthai
+!AB U+0E0B sosothai
+!AC U+0E0C chochoethai
+!AD U+0E0D yoyingthai
+!AE U+0E0E dochadathai
+!AF U+0E0F topatakthai
+!B0 U+0E10 thothanthai
+!B1 U+0E11 thonangmonthothai
+!B2 U+0E12 thophuthaothai
+!B3 U+0E13 nonenthai
+!B4 U+0E14 dodekthai
+!B5 U+0E15 totaothai
+!B6 U+0E16 thothungthai
+!B7 U+0E17 thothahanthai
+!B8 U+0E18 thothongthai
+!B9 U+0E19 nonuthai
+!BA U+0E1A bobaimaithai
+!BB U+0E1B poplathai
+!BC U+0E1C phophungthai
+!BD U+0E1D fofathai
+!BE U+0E1E phophanthai
+!BF U+0E1F fofanthai
+!C0 U+0E20 phosamphaothai
+!C1 U+0E21 momathai
+!C2 U+0E22 yoyakthai
+!C3 U+0E23 roruathai
+!C4 U+0E24 ruthai
+!C5 U+0E25 lolingthai
+!C6 U+0E26 luthai
+!C7 U+0E27 wowaenthai
+!C8 U+0E28 sosalathai
+!C9 U+0E29 sorusithai
+!CA U+0E2A sosuathai
+!CB U+0E2B hohipthai
+!CC U+0E2C lochulathai
+!CD U+0E2D oangthai
+!CE U+0E2E honokhukthai
+!CF U+0E2F paiyannoithai
+!D0 U+0E30 saraathai
+!D1 U+0E31 maihanakatthai
+!D2 U+0E32 saraaathai
+!D3 U+0E33 saraamthai
+!D4 U+0E34 saraithai
+!D5 U+0E35 saraiithai
+!D6 U+0E36 sarauethai
+!D7 U+0E37 saraueethai
+!D8 U+0E38 sarauthai
+!D9 U+0E39 sarauuthai
+!DA U+0E3A phinthuthai
+!DF U+0E3F bahtthai
+!E0 U+0E40 saraethai
+!E1 U+0E41 saraaethai
+!E2 U+0E42 saraothai
+!E3 U+0E43 saraaimaimuanthai
+!E4 U+0E44 saraaimaimalaithai
+!E5 U+0E45 lakkhangyaothai
+!E6 U+0E46 maiyamokthai
+!E7 U+0E47 maitaikhuthai
+!E8 U+0E48 maiekthai
+!E9 U+0E49 maithothai
+!EA U+0E4A maitrithai
+!EB U+0E4B maichattawathai
+!EC U+0E4C thanthakhatthai
+!ED U+0E4D nikhahitthai
+!EE U+0E4E yamakkanthai
+!EF U+0E4F fongmanthai
+!F0 U+0E50 zerothai
+!F1 U+0E51 onethai
+!F2 U+0E52 twothai
+!F3 U+0E53 threethai
+!F4 U+0E54 fourthai
+!F5 U+0E55 fivethai
+!F6 U+0E56 sixthai
+!F7 U+0E57 seventhai
+!F8 U+0E58 eightthai
+!F9 U+0E59 ninethai
+!FA U+0E5A angkhankhuthai
+!FB U+0E5B khomutthai
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-1.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-1.map
new file mode 100644
index 0000000..74fb2fe
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-1.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+00D0 Eth
+!D1 U+00D1 Ntilde
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+00DE Thorn
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+00F0 eth
+!F1 U+00F1 ntilde
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+00FE thorn
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-11.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-11.map
new file mode 100644
index 0000000..8cf6673
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-11.map
@@ -0,0 +1,248 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+0E01 kokaithai
+!A2 U+0E02 khokhaithai
+!A3 U+0E03 khokhuatthai
+!A4 U+0E04 khokhwaithai
+!A5 U+0E05 khokhonthai
+!A6 U+0E06 khorakhangthai
+!A7 U+0E07 ngonguthai
+!A8 U+0E08 chochanthai
+!A9 U+0E09 chochingthai
+!AA U+0E0A chochangthai
+!AB U+0E0B sosothai
+!AC U+0E0C chochoethai
+!AD U+0E0D yoyingthai
+!AE U+0E0E dochadathai
+!AF U+0E0F topatakthai
+!B0 U+0E10 thothanthai
+!B1 U+0E11 thonangmonthothai
+!B2 U+0E12 thophuthaothai
+!B3 U+0E13 nonenthai
+!B4 U+0E14 dodekthai
+!B5 U+0E15 totaothai
+!B6 U+0E16 thothungthai
+!B7 U+0E17 thothahanthai
+!B8 U+0E18 thothongthai
+!B9 U+0E19 nonuthai
+!BA U+0E1A bobaimaithai
+!BB U+0E1B poplathai
+!BC U+0E1C phophungthai
+!BD U+0E1D fofathai
+!BE U+0E1E phophanthai
+!BF U+0E1F fofanthai
+!C0 U+0E20 phosamphaothai
+!C1 U+0E21 momathai
+!C2 U+0E22 yoyakthai
+!C3 U+0E23 roruathai
+!C4 U+0E24 ruthai
+!C5 U+0E25 lolingthai
+!C6 U+0E26 luthai
+!C7 U+0E27 wowaenthai
+!C8 U+0E28 sosalathai
+!C9 U+0E29 sorusithai
+!CA U+0E2A sosuathai
+!CB U+0E2B hohipthai
+!CC U+0E2C lochulathai
+!CD U+0E2D oangthai
+!CE U+0E2E honokhukthai
+!CF U+0E2F paiyannoithai
+!D0 U+0E30 saraathai
+!D1 U+0E31 maihanakatthai
+!D2 U+0E32 saraaathai
+!D3 U+0E33 saraamthai
+!D4 U+0E34 saraithai
+!D5 U+0E35 saraiithai
+!D6 U+0E36 sarauethai
+!D7 U+0E37 saraueethai
+!D8 U+0E38 sarauthai
+!D9 U+0E39 sarauuthai
+!DA U+0E3A phinthuthai
+!DF U+0E3F bahtthai
+!E0 U+0E40 saraethai
+!E1 U+0E41 saraaethai
+!E2 U+0E42 saraothai
+!E3 U+0E43 saraaimaimuanthai
+!E4 U+0E44 saraaimaimalaithai
+!E5 U+0E45 lakkhangyaothai
+!E6 U+0E46 maiyamokthai
+!E7 U+0E47 maitaikhuthai
+!E8 U+0E48 maiekthai
+!E9 U+0E49 maithothai
+!EA U+0E4A maitrithai
+!EB U+0E4B maichattawathai
+!EC U+0E4C thanthakhatthai
+!ED U+0E4D nikhahitthai
+!EE U+0E4E yamakkanthai
+!EF U+0E4F fongmanthai
+!F0 U+0E50 zerothai
+!F1 U+0E51 onethai
+!F2 U+0E52 twothai
+!F3 U+0E53 threethai
+!F4 U+0E54 fourthai
+!F5 U+0E55 fivethai
+!F6 U+0E56 sixthai
+!F7 U+0E57 seventhai
+!F8 U+0E58 eightthai
+!F9 U+0E59 ninethai
+!FA U+0E5A angkhankhuthai
+!FB U+0E5B khomutthai
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-15.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-15.map
new file mode 100644
index 0000000..2689703
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-15.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+20AC Euro
+!A5 U+00A5 yen
+!A6 U+0160 Scaron
+!A7 U+00A7 section
+!A8 U+0161 scaron
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+017D Zcaron
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+017E zcaron
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+0152 OE
+!BD U+0153 oe
+!BE U+0178 Ydieresis
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+00D0 Eth
+!D1 U+00D1 Ntilde
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+00DE Thorn
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+00F0 eth
+!F1 U+00F1 ntilde
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+00FE thorn
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-16.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-16.map
new file mode 100644
index 0000000..89b802a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-16.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+0104 Aogonek
+!A2 U+0105 aogonek
+!A3 U+0141 Lslash
+!A4 U+20AC Euro
+!A5 U+201E quotedblbase
+!A6 U+0160 Scaron
+!A7 U+00A7 section
+!A8 U+0161 scaron
+!A9 U+00A9 copyright
+!AA U+0218 Scommaaccent
+!AB U+00AB guillemotleft
+!AC U+0179 Zacute
+!AD U+00AD hyphen
+!AE U+017A zacute
+!AF U+017B Zdotaccent
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+010C Ccaron
+!B3 U+0142 lslash
+!B4 U+017D Zcaron
+!B5 U+201D quotedblright
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+017E zcaron
+!B9 U+010D ccaron
+!BA U+0219 scommaaccent
+!BB U+00BB guillemotright
+!BC U+0152 OE
+!BD U+0153 oe
+!BE U+0178 Ydieresis
+!BF U+017C zdotaccent
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+0102 Abreve
+!C4 U+00C4 Adieresis
+!C5 U+0106 Cacute
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+0110 Dcroat
+!D1 U+0143 Nacute
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+0150 Ohungarumlaut
+!D6 U+00D6 Odieresis
+!D7 U+015A Sacute
+!D8 U+0170 Uhungarumlaut
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+0118 Eogonek
+!DE U+021A Tcommaaccent
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+0103 abreve
+!E4 U+00E4 adieresis
+!E5 U+0107 cacute
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+0111 dcroat
+!F1 U+0144 nacute
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+0151 ohungarumlaut
+!F6 U+00F6 odieresis
+!F7 U+015B sacute
+!F8 U+0171 uhungarumlaut
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+0119 eogonek
+!FE U+021B tcommaaccent
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-2.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-2.map
new file mode 100644
index 0000000..af9588d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-2.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+0104 Aogonek
+!A2 U+02D8 breve
+!A3 U+0141 Lslash
+!A4 U+00A4 currency
+!A5 U+013D Lcaron
+!A6 U+015A Sacute
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+0160 Scaron
+!AA U+015E Scedilla
+!AB U+0164 Tcaron
+!AC U+0179 Zacute
+!AD U+00AD hyphen
+!AE U+017D Zcaron
+!AF U+017B Zdotaccent
+!B0 U+00B0 degree
+!B1 U+0105 aogonek
+!B2 U+02DB ogonek
+!B3 U+0142 lslash
+!B4 U+00B4 acute
+!B5 U+013E lcaron
+!B6 U+015B sacute
+!B7 U+02C7 caron
+!B8 U+00B8 cedilla
+!B9 U+0161 scaron
+!BA U+015F scedilla
+!BB U+0165 tcaron
+!BC U+017A zacute
+!BD U+02DD hungarumlaut
+!BE U+017E zcaron
+!BF U+017C zdotaccent
+!C0 U+0154 Racute
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+0102 Abreve
+!C4 U+00C4 Adieresis
+!C5 U+0139 Lacute
+!C6 U+0106 Cacute
+!C7 U+00C7 Ccedilla
+!C8 U+010C Ccaron
+!C9 U+00C9 Eacute
+!CA U+0118 Eogonek
+!CB U+00CB Edieresis
+!CC U+011A Ecaron
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+010E Dcaron
+!D0 U+0110 Dcroat
+!D1 U+0143 Nacute
+!D2 U+0147 Ncaron
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+0150 Ohungarumlaut
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+0158 Rcaron
+!D9 U+016E Uring
+!DA U+00DA Uacute
+!DB U+0170 Uhungarumlaut
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+0162 Tcommaaccent
+!DF U+00DF germandbls
+!E0 U+0155 racute
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+0103 abreve
+!E4 U+00E4 adieresis
+!E5 U+013A lacute
+!E6 U+0107 cacute
+!E7 U+00E7 ccedilla
+!E8 U+010D ccaron
+!E9 U+00E9 eacute
+!EA U+0119 eogonek
+!EB U+00EB edieresis
+!EC U+011B ecaron
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+010F dcaron
+!F0 U+0111 dcroat
+!F1 U+0144 nacute
+!F2 U+0148 ncaron
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+0151 ohungarumlaut
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+0159 rcaron
+!F9 U+016F uring
+!FA U+00FA uacute
+!FB U+0171 uhungarumlaut
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+0163 tcommaaccent
+!FF U+02D9 dotaccent
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-4.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-4.map
new file mode 100644
index 0000000..e81dd7f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-4.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+0104 Aogonek
+!A2 U+0138 kgreenlandic
+!A3 U+0156 Rcommaaccent
+!A4 U+00A4 currency
+!A5 U+0128 Itilde
+!A6 U+013B Lcommaaccent
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+0160 Scaron
+!AA U+0112 Emacron
+!AB U+0122 Gcommaaccent
+!AC U+0166 Tbar
+!AD U+00AD hyphen
+!AE U+017D Zcaron
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+0105 aogonek
+!B2 U+02DB ogonek
+!B3 U+0157 rcommaaccent
+!B4 U+00B4 acute
+!B5 U+0129 itilde
+!B6 U+013C lcommaaccent
+!B7 U+02C7 caron
+!B8 U+00B8 cedilla
+!B9 U+0161 scaron
+!BA U+0113 emacron
+!BB U+0123 gcommaaccent
+!BC U+0167 tbar
+!BD U+014A Eng
+!BE U+017E zcaron
+!BF U+014B eng
+!C0 U+0100 Amacron
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+012E Iogonek
+!C8 U+010C Ccaron
+!C9 U+00C9 Eacute
+!CA U+0118 Eogonek
+!CB U+00CB Edieresis
+!CC U+0116 Edotaccent
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+012A Imacron
+!D0 U+0110 Dcroat
+!D1 U+0145 Ncommaaccent
+!D2 U+014C Omacron
+!D3 U+0136 Kcommaaccent
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+0172 Uogonek
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+0168 Utilde
+!DE U+016A Umacron
+!DF U+00DF germandbls
+!E0 U+0101 amacron
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+012F iogonek
+!E8 U+010D ccaron
+!E9 U+00E9 eacute
+!EA U+0119 eogonek
+!EB U+00EB edieresis
+!EC U+0117 edotaccent
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+012B imacron
+!F0 U+0111 dcroat
+!F1 U+0146 ncommaaccent
+!F2 U+014D omacron
+!F3 U+0137 kcommaaccent
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+0173 uogonek
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+0169 utilde
+!FE U+016B umacron
+!FF U+02D9 dotaccent
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-5.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-5.map
new file mode 100644
index 0000000..8030fd5
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-5.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+0401 afii10023
+!A2 U+0402 afii10051
+!A3 U+0403 afii10052
+!A4 U+0404 afii10053
+!A5 U+0405 afii10054
+!A6 U+0406 afii10055
+!A7 U+0407 afii10056
+!A8 U+0408 afii10057
+!A9 U+0409 afii10058
+!AA U+040A afii10059
+!AB U+040B afii10060
+!AC U+040C afii10061
+!AD U+00AD hyphen
+!AE U+040E afii10062
+!AF U+040F afii10145
+!B0 U+0410 afii10017
+!B1 U+0411 afii10018
+!B2 U+0412 afii10019
+!B3 U+0413 afii10020
+!B4 U+0414 afii10021
+!B5 U+0415 afii10022
+!B6 U+0416 afii10024
+!B7 U+0417 afii10025
+!B8 U+0418 afii10026
+!B9 U+0419 afii10027
+!BA U+041A afii10028
+!BB U+041B afii10029
+!BC U+041C afii10030
+!BD U+041D afii10031
+!BE U+041E afii10032
+!BF U+041F afii10033
+!C0 U+0420 afii10034
+!C1 U+0421 afii10035
+!C2 U+0422 afii10036
+!C3 U+0423 afii10037
+!C4 U+0424 afii10038
+!C5 U+0425 afii10039
+!C6 U+0426 afii10040
+!C7 U+0427 afii10041
+!C8 U+0428 afii10042
+!C9 U+0429 afii10043
+!CA U+042A afii10044
+!CB U+042B afii10045
+!CC U+042C afii10046
+!CD U+042D afii10047
+!CE U+042E afii10048
+!CF U+042F afii10049
+!D0 U+0430 afii10065
+!D1 U+0431 afii10066
+!D2 U+0432 afii10067
+!D3 U+0433 afii10068
+!D4 U+0434 afii10069
+!D5 U+0435 afii10070
+!D6 U+0436 afii10072
+!D7 U+0437 afii10073
+!D8 U+0438 afii10074
+!D9 U+0439 afii10075
+!DA U+043A afii10076
+!DB U+043B afii10077
+!DC U+043C afii10078
+!DD U+043D afii10079
+!DE U+043E afii10080
+!DF U+043F afii10081
+!E0 U+0440 afii10082
+!E1 U+0441 afii10083
+!E2 U+0442 afii10084
+!E3 U+0443 afii10085
+!E4 U+0444 afii10086
+!E5 U+0445 afii10087
+!E6 U+0446 afii10088
+!E7 U+0447 afii10089
+!E8 U+0448 afii10090
+!E9 U+0449 afii10091
+!EA U+044A afii10092
+!EB U+044B afii10093
+!EC U+044C afii10094
+!ED U+044D afii10095
+!EE U+044E afii10096
+!EF U+044F afii10097
+!F0 U+2116 afii61352
+!F1 U+0451 afii10071
+!F2 U+0452 afii10099
+!F3 U+0453 afii10100
+!F4 U+0454 afii10101
+!F5 U+0455 afii10102
+!F6 U+0456 afii10103
+!F7 U+0457 afii10104
+!F8 U+0458 afii10105
+!F9 U+0459 afii10106
+!FA U+045A afii10107
+!FB U+045B afii10108
+!FC U+045C afii10109
+!FD U+00A7 section
+!FE U+045E afii10110
+!FF U+045F afii10193
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-7.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-7.map
new file mode 100644
index 0000000..be8698a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-7.map
@@ -0,0 +1,250 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+2018 quoteleft
+!A2 U+2019 quoteright
+!A3 U+00A3 sterling
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AF U+2015 afii00208
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+0384 tonos
+!B5 U+0385 dieresistonos
+!B6 U+0386 Alphatonos
+!B7 U+00B7 periodcentered
+!B8 U+0388 Epsilontonos
+!B9 U+0389 Etatonos
+!BA U+038A Iotatonos
+!BB U+00BB guillemotright
+!BC U+038C Omicrontonos
+!BD U+00BD onehalf
+!BE U+038E Upsilontonos
+!BF U+038F Omegatonos
+!C0 U+0390 iotadieresistonos
+!C1 U+0391 Alpha
+!C2 U+0392 Beta
+!C3 U+0393 Gamma
+!C4 U+0394 Delta
+!C5 U+0395 Epsilon
+!C6 U+0396 Zeta
+!C7 U+0397 Eta
+!C8 U+0398 Theta
+!C9 U+0399 Iota
+!CA U+039A Kappa
+!CB U+039B Lambda
+!CC U+039C Mu
+!CD U+039D Nu
+!CE U+039E Xi
+!CF U+039F Omicron
+!D0 U+03A0 Pi
+!D1 U+03A1 Rho
+!D3 U+03A3 Sigma
+!D4 U+03A4 Tau
+!D5 U+03A5 Upsilon
+!D6 U+03A6 Phi
+!D7 U+03A7 Chi
+!D8 U+03A8 Psi
+!D9 U+03A9 Omega
+!DA U+03AA Iotadieresis
+!DB U+03AB Upsilondieresis
+!DC U+03AC alphatonos
+!DD U+03AD epsilontonos
+!DE U+03AE etatonos
+!DF U+03AF iotatonos
+!E0 U+03B0 upsilondieresistonos
+!E1 U+03B1 alpha
+!E2 U+03B2 beta
+!E3 U+03B3 gamma
+!E4 U+03B4 delta
+!E5 U+03B5 epsilon
+!E6 U+03B6 zeta
+!E7 U+03B7 eta
+!E8 U+03B8 theta
+!E9 U+03B9 iota
+!EA U+03BA kappa
+!EB U+03BB lambda
+!EC U+03BC mu
+!ED U+03BD nu
+!EE U+03BE xi
+!EF U+03BF omicron
+!F0 U+03C0 pi
+!F1 U+03C1 rho
+!F2 U+03C2 sigma1
+!F3 U+03C3 sigma
+!F4 U+03C4 tau
+!F5 U+03C5 upsilon
+!F6 U+03C6 phi
+!F7 U+03C7 chi
+!F8 U+03C8 psi
+!F9 U+03C9 omega
+!FA U+03CA iotadieresis
+!FB U+03CB upsilondieresis
+!FC U+03CC omicrontonos
+!FD U+03CD upsilontonos
+!FE U+03CE omegatonos
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-9.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-9.map
new file mode 100644
index 0000000..a60bb19
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/iso-8859-9.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+0080 .notdef
+!81 U+0081 .notdef
+!82 U+0082 .notdef
+!83 U+0083 .notdef
+!84 U+0084 .notdef
+!85 U+0085 .notdef
+!86 U+0086 .notdef
+!87 U+0087 .notdef
+!88 U+0088 .notdef
+!89 U+0089 .notdef
+!8A U+008A .notdef
+!8B U+008B .notdef
+!8C U+008C .notdef
+!8D U+008D .notdef
+!8E U+008E .notdef
+!8F U+008F .notdef
+!90 U+0090 .notdef
+!91 U+0091 .notdef
+!92 U+0092 .notdef
+!93 U+0093 .notdef
+!94 U+0094 .notdef
+!95 U+0095 .notdef
+!96 U+0096 .notdef
+!97 U+0097 .notdef
+!98 U+0098 .notdef
+!99 U+0099 .notdef
+!9A U+009A .notdef
+!9B U+009B .notdef
+!9C U+009C .notdef
+!9D U+009D .notdef
+!9E U+009E .notdef
+!9F U+009F .notdef
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+011E Gbreve
+!D1 U+00D1 Ntilde
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+0130 Idotaccent
+!DE U+015E Scedilla
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+011F gbreve
+!F1 U+00F1 ntilde
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+0131 dotlessi
+!FE U+015F scedilla
+!FF U+00FF ydieresis
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-r.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-r.map
new file mode 100644
index 0000000..026880d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-r.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+2500 SF100000
+!81 U+2502 SF110000
+!82 U+250C SF010000
+!83 U+2510 SF030000
+!84 U+2514 SF020000
+!85 U+2518 SF040000
+!86 U+251C SF080000
+!87 U+2524 SF090000
+!88 U+252C SF060000
+!89 U+2534 SF070000
+!8A U+253C SF050000
+!8B U+2580 upblock
+!8C U+2584 dnblock
+!8D U+2588 block
+!8E U+258C lfblock
+!8F U+2590 rtblock
+!90 U+2591 ltshade
+!91 U+2592 shade
+!92 U+2593 dkshade
+!93 U+2320 integraltp
+!94 U+25A0 filledbox
+!95 U+2219 periodcentered
+!96 U+221A radical
+!97 U+2248 approxequal
+!98 U+2264 lessequal
+!99 U+2265 greaterequal
+!9A U+00A0 space
+!9B U+2321 integralbt
+!9C U+00B0 degree
+!9D U+00B2 twosuperior
+!9E U+00B7 periodcentered
+!9F U+00F7 divide
+!A0 U+2550 SF430000
+!A1 U+2551 SF240000
+!A2 U+2552 SF510000
+!A3 U+0451 afii10071
+!A4 U+2553 SF520000
+!A5 U+2554 SF390000
+!A6 U+2555 SF220000
+!A7 U+2556 SF210000
+!A8 U+2557 SF250000
+!A9 U+2558 SF500000
+!AA U+2559 SF490000
+!AB U+255A SF380000
+!AC U+255B SF280000
+!AD U+255C SF270000
+!AE U+255D SF260000
+!AF U+255E SF360000
+!B0 U+255F SF370000
+!B1 U+2560 SF420000
+!B2 U+2561 SF190000
+!B3 U+0401 afii10023
+!B4 U+2562 SF200000
+!B5 U+2563 SF230000
+!B6 U+2564 SF470000
+!B7 U+2565 SF480000
+!B8 U+2566 SF410000
+!B9 U+2567 SF450000
+!BA U+2568 SF460000
+!BB U+2569 SF400000
+!BC U+256A SF540000
+!BD U+256B SF530000
+!BE U+256C SF440000
+!BF U+00A9 copyright
+!C0 U+044E afii10096
+!C1 U+0430 afii10065
+!C2 U+0431 afii10066
+!C3 U+0446 afii10088
+!C4 U+0434 afii10069
+!C5 U+0435 afii10070
+!C6 U+0444 afii10086
+!C7 U+0433 afii10068
+!C8 U+0445 afii10087
+!C9 U+0438 afii10074
+!CA U+0439 afii10075
+!CB U+043A afii10076
+!CC U+043B afii10077
+!CD U+043C afii10078
+!CE U+043D afii10079
+!CF U+043E afii10080
+!D0 U+043F afii10081
+!D1 U+044F afii10097
+!D2 U+0440 afii10082
+!D3 U+0441 afii10083
+!D4 U+0442 afii10084
+!D5 U+0443 afii10085
+!D6 U+0436 afii10072
+!D7 U+0432 afii10067
+!D8 U+044C afii10094
+!D9 U+044B afii10093
+!DA U+0437 afii10073
+!DB U+0448 afii10090
+!DC U+044D afii10095
+!DD U+0449 afii10091
+!DE U+0447 afii10089
+!DF U+044A afii10092
+!E0 U+042E afii10048
+!E1 U+0410 afii10017
+!E2 U+0411 afii10018
+!E3 U+0426 afii10040
+!E4 U+0414 afii10021
+!E5 U+0415 afii10022
+!E6 U+0424 afii10038
+!E7 U+0413 afii10020
+!E8 U+0425 afii10039
+!E9 U+0418 afii10026
+!EA U+0419 afii10027
+!EB U+041A afii10028
+!EC U+041B afii10029
+!ED U+041C afii10030
+!EE U+041D afii10031
+!EF U+041E afii10032
+!F0 U+041F afii10033
+!F1 U+042F afii10049
+!F2 U+0420 afii10034
+!F3 U+0421 afii10035
+!F4 U+0422 afii10036
+!F5 U+0423 afii10037
+!F6 U+0416 afii10024
+!F7 U+0412 afii10019
+!F8 U+042C afii10046
+!F9 U+042B afii10045
+!FA U+0417 afii10025
+!FB U+0428 afii10042
+!FC U+042D afii10047
+!FD U+0429 afii10043
+!FE U+0427 afii10041
+!FF U+042A afii10044
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-u.map b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-u.map
new file mode 100644
index 0000000..97d9d03
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/maps/koi8-u.map
@@ -0,0 +1,256 @@
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+2500 SF100000
+!81 U+2502 SF110000
+!82 U+250C SF010000
+!83 U+2510 SF030000
+!84 U+2514 SF020000
+!85 U+2518 SF040000
+!86 U+251C SF080000
+!87 U+2524 SF090000
+!88 U+252C SF060000
+!89 U+2534 SF070000
+!8A U+253C SF050000
+!8B U+2580 upblock
+!8C U+2584 dnblock
+!8D U+2588 block
+!8E U+258C lfblock
+!8F U+2590 rtblock
+!90 U+2591 ltshade
+!91 U+2592 shade
+!92 U+2593 dkshade
+!93 U+2320 integraltp
+!94 U+25A0 filledbox
+!95 U+2022 bullet
+!96 U+221A radical
+!97 U+2248 approxequal
+!98 U+2264 lessequal
+!99 U+2265 greaterequal
+!9A U+00A0 space
+!9B U+2321 integralbt
+!9C U+00B0 degree
+!9D U+00B2 twosuperior
+!9E U+00B7 periodcentered
+!9F U+00F7 divide
+!A0 U+2550 SF430000
+!A1 U+2551 SF240000
+!A2 U+2552 SF510000
+!A3 U+0451 afii10071
+!A4 U+0454 afii10101
+!A5 U+2554 SF390000
+!A6 U+0456 afii10103
+!A7 U+0457 afii10104
+!A8 U+2557 SF250000
+!A9 U+2558 SF500000
+!AA U+2559 SF490000
+!AB U+255A SF380000
+!AC U+255B SF280000
+!AD U+0491 afii10098
+!AE U+255D SF260000
+!AF U+255E SF360000
+!B0 U+255F SF370000
+!B1 U+2560 SF420000
+!B2 U+2561 SF190000
+!B3 U+0401 afii10023
+!B4 U+0404 afii10053
+!B5 U+2563 SF230000
+!B6 U+0406 afii10055
+!B7 U+0407 afii10056
+!B8 U+2566 SF410000
+!B9 U+2567 SF450000
+!BA U+2568 SF460000
+!BB U+2569 SF400000
+!BC U+256A SF540000
+!BD U+0490 afii10050
+!BE U+256C SF440000
+!BF U+00A9 copyright
+!C0 U+044E afii10096
+!C1 U+0430 afii10065
+!C2 U+0431 afii10066
+!C3 U+0446 afii10088
+!C4 U+0434 afii10069
+!C5 U+0435 afii10070
+!C6 U+0444 afii10086
+!C7 U+0433 afii10068
+!C8 U+0445 afii10087
+!C9 U+0438 afii10074
+!CA U+0439 afii10075
+!CB U+043A afii10076
+!CC U+043B afii10077
+!CD U+043C afii10078
+!CE U+043D afii10079
+!CF U+043E afii10080
+!D0 U+043F afii10081
+!D1 U+044F afii10097
+!D2 U+0440 afii10082
+!D3 U+0441 afii10083
+!D4 U+0442 afii10084
+!D5 U+0443 afii10085
+!D6 U+0436 afii10072
+!D7 U+0432 afii10067
+!D8 U+044C afii10094
+!D9 U+044B afii10093
+!DA U+0437 afii10073
+!DB U+0448 afii10090
+!DC U+044D afii10095
+!DD U+0449 afii10091
+!DE U+0447 afii10089
+!DF U+044A afii10092
+!E0 U+042E afii10048
+!E1 U+0410 afii10017
+!E2 U+0411 afii10018
+!E3 U+0426 afii10040
+!E4 U+0414 afii10021
+!E5 U+0415 afii10022
+!E6 U+0424 afii10038
+!E7 U+0413 afii10020
+!E8 U+0425 afii10039
+!E9 U+0418 afii10026
+!EA U+0419 afii10027
+!EB U+041A afii10028
+!EC U+041B afii10029
+!ED U+041C afii10030
+!EE U+041D afii10031
+!EF U+041E afii10032
+!F0 U+041F afii10033
+!F1 U+042F afii10049
+!F2 U+0420 afii10034
+!F3 U+0421 afii10035
+!F4 U+0422 afii10036
+!F5 U+0423 afii10037
+!F6 U+0416 afii10024
+!F7 U+0412 afii10019
+!F8 U+042C afii10046
+!F9 U+042B afii10045
+!FA U+0417 afii10025
+!FB U+0428 afii10042
+!FC U+042D afii10047
+!FD U+0429 afii10043
+!FE U+0427 afii10041
+!FF U+042A afii10044
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/AdobeFontMetrics.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/AdobeFontMetrics.php
new file mode 100644
index 0000000..e75385f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/AdobeFontMetrics.php
@@ -0,0 +1,217 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib;
+
+use FontLib\Table\Type\name;
+use FontLib\TrueType\File;
+
+/**
+ * Adobe Font Metrics file creation utility class.
+ *
+ * @package php-font-lib
+ */
+class AdobeFontMetrics {
+ private $f;
+
+ /**
+ * @var File
+ */
+ private $font;
+
+ function __construct(File $font) {
+ $this->font = $font;
+ }
+
+ function write($file, $encoding = null) {
+ $map_data = array();
+
+ if ($encoding) {
+ $encoding = preg_replace("/[^a-z0-9-_]/", "", $encoding);
+ $map_file = dirname(__FILE__) . "/../maps/$encoding.map";
+ if (!file_exists($map_file)) {
+ throw new \Exception("Unknown encoding ($encoding)");
+ }
+
+ $map = new EncodingMap($map_file);
+ $map_data = $map->parse();
+ }
+
+ $this->f = fopen($file, "w+");
+
+ $font = $this->font;
+
+ $this->startSection("FontMetrics", 4.1);
+ $this->addPair("Notice", "Converted by PHP-font-lib");
+ $this->addPair("Comment", "https://github.com/PhenX/php-font-lib");
+
+ $encoding_scheme = ($encoding ? $encoding : "FontSpecific");
+ $this->addPair("EncodingScheme", $encoding_scheme);
+
+ $records = $font->getData("name", "records");
+ foreach ($records as $id => $record) {
+ if (!isset(name::$nameIdCodes[$id]) || preg_match("/[\r\n]/", $record->string)) {
+ continue;
+ }
+
+ $this->addPair(name::$nameIdCodes[$id], $record->string);
+ }
+
+ $os2 = $font->getData("OS/2");
+ $this->addPair("Weight", ($os2["usWeightClass"] > 400 ? "Bold" : "Medium"));
+
+ $post = $font->getData("post");
+ $this->addPair("ItalicAngle", $post["italicAngle"]);
+ $this->addPair("IsFixedPitch", ($post["isFixedPitch"] ? "true" : "false"));
+ $this->addPair("UnderlineThickness", $font->normalizeFUnit($post["underlineThickness"]));
+ $this->addPair("UnderlinePosition", $font->normalizeFUnit($post["underlinePosition"]));
+
+ $hhea = $font->getData("hhea");
+
+ if (isset($hhea["ascent"])) {
+ $this->addPair("FontHeightOffset", $font->normalizeFUnit($hhea["lineGap"]));
+ $this->addPair("Ascender", $font->normalizeFUnit($hhea["ascent"]));
+ $this->addPair("Descender", $font->normalizeFUnit($hhea["descent"]));
+ }
+ else {
+ $this->addPair("FontHeightOffset", $font->normalizeFUnit($os2["typoLineGap"]));
+ $this->addPair("Ascender", $font->normalizeFUnit($os2["typoAscender"]));
+ $this->addPair("Descender", -abs($font->normalizeFUnit($os2["typoDescender"])));
+ }
+
+ $head = $font->getData("head");
+ $this->addArray("FontBBox", array(
+ $font->normalizeFUnit($head["xMin"]),
+ $font->normalizeFUnit($head["yMin"]),
+ $font->normalizeFUnit($head["xMax"]),
+ $font->normalizeFUnit($head["yMax"]),
+ ));
+
+ $glyphIndexArray = $font->getUnicodeCharMap();
+
+ if ($glyphIndexArray) {
+ $hmtx = $font->getData("hmtx");
+ $names = $font->getData("post", "names");
+
+ $this->startSection("CharMetrics", count($hmtx));
+
+ if ($encoding) {
+ foreach ($map_data as $code => $value) {
+ list($c, $name) = $value;
+
+ if (!isset($glyphIndexArray[$c])) {
+ continue;
+ }
+
+ $g = $glyphIndexArray[$c];
+
+ if (!isset($hmtx[$g])) {
+ $hmtx[$g] = $hmtx[0];
+ }
+
+ $this->addMetric(array(
+ "C" => ($code > 255 ? -1 : $code),
+ "WX" => $font->normalizeFUnit($hmtx[$g][0]),
+ "N" => $name,
+ ));
+ }
+ }
+ else {
+ foreach ($glyphIndexArray as $c => $g) {
+ if (!isset($hmtx[$g])) {
+ $hmtx[$g] = $hmtx[0];
+ }
+
+ $this->addMetric(array(
+ "U" => $c,
+ "WX" => $font->normalizeFUnit($hmtx[$g][0]),
+ "N" => (isset($names[$g]) ? $names[$g] : sprintf("uni%04x", $c)),
+ "G" => $g,
+ ));
+ }
+ }
+
+ $this->endSection("CharMetrics");
+
+ $kern = $font->getData("kern", "subtable");
+ $tree = is_array($kern) ? $kern["tree"] : null;
+
+ if (!$encoding && is_array($tree)) {
+ $this->startSection("KernData");
+ $this->startSection("KernPairs", count($tree, COUNT_RECURSIVE) - count($tree));
+
+ foreach ($tree as $left => $values) {
+ if (!is_array($values)) {
+ continue;
+ }
+ if (!isset($glyphIndexArray[$left])) {
+ continue;
+ }
+
+ $left_gid = $glyphIndexArray[$left];
+
+ if (!isset($names[$left_gid])) {
+ continue;
+ }
+
+ $left_name = $names[$left_gid];
+
+ $this->addLine("");
+
+ foreach ($values as $right => $value) {
+ if (!isset($glyphIndexArray[$right])) {
+ continue;
+ }
+
+ $right_gid = $glyphIndexArray[$right];
+
+ if (!isset($names[$right_gid])) {
+ continue;
+ }
+
+ $right_name = $names[$right_gid];
+ $this->addPair("KPX", "$left_name $right_name $value");
+ }
+ }
+
+ $this->endSection("KernPairs");
+ $this->endSection("KernData");
+ }
+ }
+
+ $this->endSection("FontMetrics");
+ }
+
+ function addLine($line) {
+ fwrite($this->f, "$line\n");
+ }
+
+ function addPair($key, $value) {
+ $this->addLine("$key $value");
+ }
+
+ function addArray($key, $array) {
+ $this->addLine("$key " . implode(" ", $array));
+ }
+
+ function addMetric($data) {
+ $array = array();
+ foreach ($data as $key => $value) {
+ $array[] = "$key $value";
+ }
+ $this->addLine(implode(" ; ", $array));
+ }
+
+ function startSection($name, $value = "") {
+ $this->addLine("Start$name $value");
+ }
+
+ function endSection($name) {
+ $this->addLine("End$name");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Autoloader.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Autoloader.php
new file mode 100644
index 0000000..cd30545
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Autoloader.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib;
+
+/**
+ * Autoloads FontLib classes
+ *
+ * @package php-font-lib
+ */
+class Autoloader {
+ const PREFIX = 'FontLib';
+
+ /**
+ * Register the autoloader
+ */
+ public static function register() {
+ spl_autoload_register(array(new self, 'autoload'));
+ }
+
+ /**
+ * Autoloader
+ *
+ * @param string
+ */
+ public static function autoload($class) {
+ $prefixLength = strlen(self::PREFIX);
+ if (0 === strncmp(self::PREFIX, $class, $prefixLength)) {
+ $file = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, $prefixLength));
+ $file = realpath(__DIR__ . (empty($file) ? '' : DIRECTORY_SEPARATOR) . $file . '.php');
+ if (file_exists($file)) {
+ require_once $file;
+ }
+ }
+ }
+}
+
+Autoloader::register(); \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/BinaryStream.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/BinaryStream.php
new file mode 100644
index 0000000..c7eb52f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/BinaryStream.php
@@ -0,0 +1,449 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib;
+
+/**
+ * Generic font file binary stream.
+ *
+ * @package php-font-lib
+ */
+class BinaryStream {
+ /**
+ * @var resource The file pointer
+ */
+ protected $f;
+
+ const uint8 = 1;
+ const int8 = 2;
+ const uint16 = 3;
+ const int16 = 4;
+ const uint32 = 5;
+ const int32 = 6;
+ const shortFrac = 7;
+ const Fixed = 8;
+ const FWord = 9;
+ const uFWord = 10;
+ const F2Dot14 = 11;
+ const longDateTime = 12;
+ const char = 13;
+
+ const modeRead = "rb";
+ const modeWrite = "wb";
+ const modeReadWrite = "rb+";
+
+ static function backtrace() {
+ var_dump(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
+ }
+
+ /**
+ * Open a font file in read mode
+ *
+ * @param string $filename The file name of the font to open
+ *
+ * @return bool
+ */
+ public function load($filename) {
+ return $this->open($filename, self::modeRead);
+ }
+
+ /**
+ * Open a font file in a chosen mode
+ *
+ * @param string $filename The file name of the font to open
+ * @param string $mode The opening mode
+ *
+ * @throws \Exception
+ * @return bool
+ */
+ public function open($filename, $mode = self::modeRead) {
+ if (!in_array($mode, array(self::modeRead, self::modeWrite, self::modeReadWrite))) {
+ throw new \Exception("Unknown file open mode");
+ }
+
+ $this->f = fopen($filename, $mode);
+
+ return $this->f != false;
+ }
+
+ /**
+ * Close the internal file pointer
+ */
+ public function close() {
+ return fclose($this->f) != false;
+ }
+
+ /**
+ * Change the internal file pointer
+ *
+ * @param resource $fp
+ *
+ * @throws \Exception
+ */
+ public function setFile($fp) {
+ if (!is_resource($fp)) {
+ throw new \Exception('$fp is not a valid resource');
+ }
+
+ $this->f = $fp;
+ }
+
+ /**
+ * Create a temporary file in write mode
+ *
+ * @param bool $allow_memory Allow in-memory files
+ *
+ * @return resource the temporary file pointer resource
+ */
+ public static function getTempFile($allow_memory = true) {
+ $f = null;
+
+ if ($allow_memory) {
+ $f = fopen("php://temp", "rb+");
+ }
+ else {
+ $f = fopen(tempnam(sys_get_temp_dir(), "fnt"), "rb+");
+ }
+
+ return $f;
+ }
+
+ /**
+ * Move the internal file pinter to $offset bytes
+ *
+ * @param int $offset
+ *
+ * @return bool True if the $offset position exists in the file
+ */
+ public function seek($offset) {
+ return fseek($this->f, $offset, SEEK_SET) == 0;
+ }
+
+ /**
+ * Gives the current position in the file
+ *
+ * @return int The current position
+ */
+ public function pos() {
+ return ftell($this->f);
+ }
+
+ public function skip($n) {
+ fseek($this->f, $n, SEEK_CUR);
+ }
+
+ /**
+ * @param int $n The number of bytes to read
+ *
+ * @return string
+ */
+ public function read($n) {
+ if ($n < 1) {
+ return "";
+ }
+
+ return (string) fread($this->f, $n);
+ }
+
+ public function write($data, $length = null) {
+ if ($data === null || $data === "" || $data === false) {
+ return 0;
+ }
+
+ return fwrite($this->f, $data, $length);
+ }
+
+ public function readUInt8() {
+ return ord($this->read(1));
+ }
+
+ public function readUInt8Many($count) {
+ return array_values(unpack("C*", $this->read($count)));
+ }
+
+ public function writeUInt8($data) {
+ return $this->write(chr($data), 1);
+ }
+
+ public function readInt8() {
+ $v = $this->readUInt8();
+
+ if ($v >= 0x80) {
+ $v -= 0x100;
+ }
+
+ return $v;
+ }
+
+ public function readInt8Many($count) {
+ return array_values(unpack("c*", $this->read($count)));
+ }
+
+ public function writeInt8($data) {
+ if ($data < 0) {
+ $data += 0x100;
+ }
+
+ return $this->writeUInt8($data);
+ }
+
+ public function readUInt16() {
+ $a = unpack("nn", $this->read(2));
+
+ return $a["n"];
+ }
+
+ public function readUInt16Many($count) {
+ return array_values(unpack("n*", $this->read($count * 2)));
+ }
+
+ public function readUFWord() {
+ return $this->readUInt16();
+ }
+
+ public function writeUInt16($data) {
+ return $this->write(pack("n", $data), 2);
+ }
+
+ public function writeUFWord($data) {
+ return $this->writeUInt16($data);
+ }
+
+ public function readInt16() {
+ $a = unpack("nn", $this->read(2));
+ $v = $a["n"];
+
+ if ($v >= 0x8000) {
+ $v -= 0x10000;
+ }
+
+ return $v;
+ }
+
+ public function readInt16Many($count) {
+ $vals = array_values(unpack("n*", $this->read($count * 2)));
+ foreach ($vals as &$v) {
+ if ($v >= 0x8000) {
+ $v -= 0x10000;
+ }
+ }
+
+ return $vals;
+ }
+
+ public function readFWord() {
+ return $this->readInt16();
+ }
+
+ public function writeInt16($data) {
+ if ($data < 0) {
+ $data += 0x10000;
+ }
+
+ return $this->writeUInt16($data);
+ }
+
+ public function writeFWord($data) {
+ return $this->writeInt16($data);
+ }
+
+ public function readUInt32() {
+ $a = unpack("NN", $this->read(4));
+
+ return $a["N"];
+ }
+
+ public function writeUInt32($data) {
+ return $this->write(pack("N", $data), 4);
+ }
+
+ public function readFixed() {
+ $d = $this->readInt16();
+ $d2 = $this->readUInt16();
+
+ return round($d + $d2 / 0x10000, 4);
+ }
+
+ public function writeFixed($data) {
+ $left = floor($data);
+ $right = ($data - $left) * 0x10000;
+
+ return $this->writeInt16($left) + $this->writeUInt16($right);
+ }
+
+ public function readLongDateTime() {
+ $this->readUInt32(); // ignored
+ $date = $this->readUInt32() - 2082844800;
+
+ # PHP_INT_MIN isn't defined in PHP < 7.0
+ $php_int_min = defined("PHP_INT_MIN") ? PHP_INT_MIN : ~PHP_INT_MAX;
+
+ if (is_string($date) || $date > PHP_INT_MAX || $date < $php_int_min) {
+ $date = 0;
+ }
+
+ return date("Y-m-d H:i:s", $date);
+ }
+
+ public function writeLongDateTime($data) {
+ $date = strtotime($data);
+ $date += 2082844800;
+
+ return $this->writeUInt32(0) + $this->writeUInt32($date);
+ }
+
+ public function unpack($def) {
+ $d = array();
+ foreach ($def as $name => $type) {
+ $d[$name] = $this->r($type);
+ }
+
+ return $d;
+ }
+
+ public function pack($def, $data) {
+ $bytes = 0;
+ foreach ($def as $name => $type) {
+ $bytes += $this->w($type, $data[$name]);
+ }
+
+ return $bytes;
+ }
+
+ /**
+ * Read a data of type $type in the file from the current position
+ *
+ * @param mixed $type The data type to read
+ *
+ * @return mixed The data that was read
+ */
+ public function r($type) {
+ switch ($type) {
+ case self::uint8:
+ return $this->readUInt8();
+ case self::int8:
+ return $this->readInt8();
+ case self::uint16:
+ return $this->readUInt16();
+ case self::int16:
+ return $this->readInt16();
+ case self::uint32:
+ return $this->readUInt32();
+ case self::int32:
+ return $this->readUInt32();
+ case self::shortFrac:
+ return $this->readFixed();
+ case self::Fixed:
+ return $this->readFixed();
+ case self::FWord:
+ return $this->readInt16();
+ case self::uFWord:
+ return $this->readUInt16();
+ case self::F2Dot14:
+ return $this->readInt16();
+ case self::longDateTime:
+ return $this->readLongDateTime();
+ case self::char:
+ return $this->read(1);
+ default:
+ if (is_array($type)) {
+ if ($type[0] == self::char) {
+ return $this->read($type[1]);
+ }
+ if ($type[0] == self::uint16) {
+ return $this->readUInt16Many($type[1]);
+ }
+ if ($type[0] == self::int16) {
+ return $this->readInt16Many($type[1]);
+ }
+ if ($type[0] == self::uint8) {
+ return $this->readUInt8Many($type[1]);
+ }
+ if ($type[0] == self::int8) {
+ return $this->readInt8Many($type[1]);
+ }
+
+ $ret = array();
+ for ($i = 0; $i < $type[1]; $i++) {
+ $ret[] = $this->r($type[0]);
+ }
+
+ return $ret;
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Write $data of type $type in the file from the current position
+ *
+ * @param mixed $type The data type to write
+ * @param mixed $data The data to write
+ *
+ * @return int The number of bytes read
+ */
+ public function w($type, $data) {
+ switch ($type) {
+ case self::uint8:
+ return $this->writeUInt8($data);
+ case self::int8:
+ return $this->writeInt8($data);
+ case self::uint16:
+ return $this->writeUInt16($data);
+ case self::int16:
+ return $this->writeInt16($data);
+ case self::uint32:
+ return $this->writeUInt32($data);
+ case self::int32:
+ return $this->writeUInt32($data);
+ case self::shortFrac:
+ return $this->writeFixed($data);
+ case self::Fixed:
+ return $this->writeFixed($data);
+ case self::FWord:
+ return $this->writeInt16($data);
+ case self::uFWord:
+ return $this->writeUInt16($data);
+ case self::F2Dot14:
+ return $this->writeInt16($data);
+ case self::longDateTime:
+ return $this->writeLongDateTime($data);
+ case self::char:
+ return $this->write($data, 1);
+ default:
+ if (is_array($type)) {
+ if ($type[0] == self::char) {
+ return $this->write($data, $type[1]);
+ }
+
+ $ret = 0;
+ for ($i = 0; $i < $type[1]; $i++) {
+ if (isset($data[$i])) {
+ $ret += $this->w($type[0], $data[$i]);
+ }
+ }
+
+ return $ret;
+ }
+
+ return null;
+ }
+ }
+
+ /**
+ * Converts a Uint32 value to string
+ *
+ * @param int $uint32
+ *
+ * @return string The string
+ */
+ public function convertUInt32ToStr($uint32) {
+ return chr(($uint32 >> 24) & 0xFF) . chr(($uint32 >> 16) & 0xFF) . chr(($uint32 >> 8) & 0xFF) . chr($uint32 & 0xFF);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/File.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/File.php
new file mode 100644
index 0000000..f51d876
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/File.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\EOT;
+
+/**
+ * EOT font file.
+ *
+ * @package php-font-lib
+ */
+class File extends \FontLib\TrueType\File {
+ const TTEMBED_SUBSET = 0x00000001;
+ const TTEMBED_TTCOMPRESSED = 0x00000004;
+ const TTEMBED_FAILIFVARIATIONSIMULATED = 0x00000010;
+ const TTMBED_EMBEDEUDC = 0x00000020;
+ const TTEMBED_VALIDATIONTESTS = 0x00000040; // Deprecated
+ const TTEMBED_WEBOBJECT = 0x00000080;
+ const TTEMBED_XORENCRYPTDATA = 0x10000000;
+
+ /**
+ * @var Header
+ */
+ public $header;
+
+ function parseHeader() {
+ if (!empty($this->header)) {
+ return;
+ }
+
+ $this->header = new Header($this);
+ $this->header->parse();
+ }
+
+ function parse() {
+ $this->parseHeader();
+
+ $flags = $this->header->data["Flags"];
+
+ if ($flags & self::TTEMBED_TTCOMPRESSED) {
+ $mtx_version = $this->readUInt8();
+ $mtx_copy_limit = $this->readUInt8() << 16 | $this->readUInt8() << 8 | $this->readUInt8();
+ $mtx_offset_1 = $this->readUInt8() << 16 | $this->readUInt8() << 8 | $this->readUInt8();
+ $mtx_offset_2 = $this->readUInt8() << 16 | $this->readUInt8() << 8 | $this->readUInt8();
+ /*
+ var_dump("$mtx_version $mtx_copy_limit $mtx_offset_1 $mtx_offset_2");
+
+ $pos = $this->pos();
+ $size = $mtx_offset_1 - $pos;
+ var_dump("pos: $pos");
+ var_dump("size: $size");*/
+ }
+
+ if ($flags & self::TTEMBED_XORENCRYPTDATA) {
+ // Process XOR
+ }
+ // TODO Read font data ...
+ }
+
+ /**
+ * Little endian version of the read method
+ *
+ * @param int $n The number of bytes to read
+ *
+ * @return string
+ */
+ public function read($n) {
+ if ($n < 1) {
+ return "";
+ }
+
+ $string = (string) fread($this->f, $n);
+ $chunks = mb_str_split($string, 2, '8bit');
+ $chunks = array_map("strrev", $chunks);
+ return implode("", $chunks);
+ }
+
+ public function readUInt32() {
+ $uint32 = parent::readUInt32();
+
+ return $uint32 >> 16 & 0x0000FFFF | $uint32 << 16 & 0xFFFF0000;
+ }
+
+ /**
+ * Get font copyright
+ *
+ * @return string|null
+ */
+ function getFontCopyright() {
+ return null;
+ }
+
+ /**
+ * Get font name
+ *
+ * @return string|null
+ */
+ function getFontName() {
+ return $this->header->data["FamilyName"];
+ }
+
+ /**
+ * Get font subfamily
+ *
+ * @return string|null
+ */
+ function getFontSubfamily() {
+ return $this->header->data["StyleName"];
+ }
+
+ /**
+ * Get font subfamily ID
+ *
+ * @return string|null
+ */
+ function getFontSubfamilyID() {
+ return $this->header->data["StyleName"];
+ }
+
+ /**
+ * Get font full name
+ *
+ * @return string|null
+ */
+ function getFontFullName() {
+ return $this->header->data["FullName"];
+ }
+
+ /**
+ * Get font version
+ *
+ * @return string|null
+ */
+ function getFontVersion() {
+ return $this->header->data["VersionName"];
+ }
+
+ /**
+ * Get font weight
+ *
+ * @return string|null
+ */
+ function getFontWeight() {
+ return $this->header->data["Weight"];
+ }
+
+ /**
+ * Get font Postscript name
+ *
+ * @return string|null
+ */
+ function getFontPostscriptName() {
+ return null;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/Header.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/Header.php
new file mode 100644
index 0000000..960e36a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EOT/Header.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\EOT;
+
+use Exception;
+use FontLib\Font;
+
+/**
+ * TrueType font file header.
+ *
+ * @package php-font-lib
+ *
+ * @property File $font
+ */
+class Header extends \FontLib\Header {
+ protected $def = array(
+ "format" => self::uint32,
+ "numTables" => self::uint16,
+ "searchRange" => self::uint16,
+ "entrySelector" => self::uint16,
+ "rangeShift" => self::uint16,
+ );
+
+ public function parse() {
+ $font = $this->font;
+
+ $this->data = $font->unpack(array(
+ "EOTSize" => self::uint32,
+ "FontDataSize" => self::uint32,
+ "Version" => self::uint32,
+ "Flags" => self::uint32,
+ "FontPANOSE" => array(self::uint8, 10),
+ "Charset" => self::uint8,
+ "Italic" => self::uint8,
+ "Weight" => self::uint32,
+ "fsType" => self::uint16,
+ "MagicNumber" => self::uint16,
+ "UnicodeRange1" => self::uint32,
+ "UnicodeRange2" => self::uint32,
+ "UnicodeRange3" => self::uint32,
+ "UnicodeRange4" => self::uint32,
+ "CodePageRange1" => self::uint32,
+ "CodePageRange2" => self::uint32,
+ "CheckSumAdjustment" => self::uint32,
+ "Reserved1" => self::uint32,
+ "Reserved2" => self::uint32,
+ "Reserved3" => self::uint32,
+ "Reserved4" => self::uint32,
+ ));
+
+ $this->data["Padding1"] = $font->readUInt16();
+ $this->readString("FamilyName");
+
+ $this->data["Padding2"] = $font->readUInt16();
+ $this->readString("StyleName");
+
+ $this->data["Padding3"] = $font->readUInt16();
+ $this->readString("VersionName");
+
+ $this->data["Padding4"] = $font->readUInt16();
+ $this->readString("FullName");
+
+ switch ($this->data["Version"]) {
+ default:
+ throw new Exception("Unknown EOT version " . $this->data["Version"]);
+
+ case 0x00010000:
+ // Nothing to do more
+ break;
+
+ case 0x00020001:
+ $this->data["Padding5"] = $font->readUInt16();
+ $this->readString("RootString");
+ break;
+
+ case 0x00020002:
+ $this->data["Padding5"] = $font->readUInt16();
+ $this->readString("RootString");
+
+ $this->data["RootStringCheckSum"] = $font->readUInt32();
+ $this->data["EUDCCodePage"] = $font->readUInt32();
+
+ $this->data["Padding6"] = $font->readUInt16();
+ $this->readString("Signature");
+
+ $this->data["EUDCFlags"] = $font->readUInt32();
+ $this->data["EUDCFontSize"] = $font->readUInt32();
+ break;
+ }
+
+ if (!empty($this->data["RootString"])) {
+ $this->data["RootString"] = explode("\0", $this->data["RootString"]);
+ }
+ }
+
+ private function readString($name) {
+ $font = $this->font;
+ $size = $font->readUInt16();
+
+ $this->data["{$name}Size"] = $size;
+ $this->data[$name] = Font::UTF16ToUTF8($font->read($size));
+ }
+
+ public function encode() {
+ //return $this->font->pack($this->def, $this->data);
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EncodingMap.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EncodingMap.php
new file mode 100644
index 0000000..2acdebc
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/EncodingMap.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib;
+
+/**
+ * Encoding map used to map a code point to a Unicode char.
+ *
+ * @package php-font-lib
+ */
+class EncodingMap {
+ private $f;
+
+ function __construct($file) {
+ $this->f = fopen($file, "r");
+ }
+
+ function parse() {
+ $map = array();
+
+ while ($line = fgets($this->f)) {
+ if (preg_match('/^[\!\=]([0-9A-F]{2,})\s+U\+([0-9A-F]{2})([0-9A-F]{2})\s+([^\s]+)/', $line, $matches)) {
+ $unicode = (hexdec($matches[2]) << 8) + hexdec($matches[3]);
+ $map[hexdec($matches[1])] = array($unicode, $matches[4]);
+ }
+ }
+
+ ksort($map);
+
+ return $map;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Exception/FontNotFoundException.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Exception/FontNotFoundException.php
new file mode 100644
index 0000000..d97f252
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Exception/FontNotFoundException.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace FontLib\Exception;
+
+class FontNotFoundException extends \Exception
+{
+ public function __construct($fontPath)
+ {
+ $this->message = 'Font not found in: ' . $fontPath;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Font.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Font.php
new file mode 100644
index 0000000..e13a653
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Font.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib;
+
+use FontLib\Exception\FontNotFoundException;
+
+/**
+ * Generic font file.
+ *
+ * @package php-font-lib
+ */
+class Font {
+ static $debug = false;
+
+ /**
+ * @param string $file The font file
+ *
+ * @return TrueType\File|null $file
+ */
+ public static function load($file) {
+ if(!file_exists($file)){
+ throw new FontNotFoundException($file);
+ }
+
+ $header = file_get_contents($file, false, null, 0, 4);
+ $class = null;
+
+ switch ($header) {
+ case "\x00\x01\x00\x00":
+ case "true":
+ case "typ1":
+ $class = "TrueType\\File";
+ break;
+
+ case "OTTO":
+ $class = "OpenType\\File";
+ break;
+
+ case "wOFF":
+ $class = "WOFF\\File";
+ break;
+
+ case "ttcf":
+ $class = "TrueType\\Collection";
+ break;
+
+ // Unknown type or EOT
+ default:
+ $magicNumber = file_get_contents($file, false, null, 34, 2);
+
+ if ($magicNumber === "LP") {
+ $class = "EOT\\File";
+ }
+ }
+
+ if ($class) {
+ $class = "FontLib\\$class";
+
+ /** @var TrueType\File $obj */
+ $obj = new $class;
+ $obj->load($file);
+
+ return $obj;
+ }
+
+ return null;
+ }
+
+ static function d($str) {
+ if (!self::$debug) {
+ return;
+ }
+ echo "$str\n";
+ }
+
+ static function UTF16ToUTF8($str) {
+ return mb_convert_encoding($str, "utf-8", "utf-16");
+ }
+
+ static function UTF8ToUTF16($str) {
+ return mb_convert_encoding($str, "utf-16", "utf-8");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/Outline.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/Outline.php
new file mode 100644
index 0000000..639ff60
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/Outline.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $
+ */
+namespace FontLib\Glyph;
+
+use FontLib\Table\Type\glyf;
+use FontLib\TrueType\File;
+use FontLib\BinaryStream;
+
+/**
+ * `glyf` font table.
+ *
+ * @package php-font-lib
+ */
+class Outline extends BinaryStream {
+ /**
+ * @var \FontLib\Table\Type\glyf
+ */
+ protected $table;
+
+ protected $offset;
+ protected $size;
+
+ // Data
+ public $numberOfContours;
+ public $xMin;
+ public $yMin;
+ public $xMax;
+ public $yMax;
+
+ /**
+ * @var string|null
+ */
+ public $raw;
+
+ /**
+ * @param glyf $table
+ * @param $offset
+ * @param $size
+ *
+ * @return Outline
+ */
+ static function init(glyf $table, $offset, $size, BinaryStream $font) {
+ $font->seek($offset);
+
+ if ($font->readInt16() > -1) {
+ /** @var OutlineSimple $glyph */
+ $glyph = new OutlineSimple($table, $offset, $size);
+ }
+ else {
+ /** @var OutlineComposite $glyph */
+ $glyph = new OutlineComposite($table, $offset, $size);
+ }
+
+ $glyph->parse($font);
+
+ return $glyph;
+ }
+
+ /**
+ * @return File
+ */
+ function getFont() {
+ return $this->table->getFont();
+ }
+
+ function __construct(glyf $table, $offset = null, $size = null) {
+ $this->table = $table;
+ $this->offset = $offset;
+ $this->size = $size;
+ }
+
+ function parse(BinaryStream $font) {
+ $font->seek($this->offset);
+
+ $this->raw = $font->read($this->size);
+ }
+
+ function parseData() {
+ $font = $this->getFont();
+ $font->seek($this->offset);
+
+ $this->numberOfContours = $font->readInt16();
+ $this->xMin = $font->readFWord();
+ $this->yMin = $font->readFWord();
+ $this->xMax = $font->readFWord();
+ $this->yMax = $font->readFWord();
+ }
+
+ function encode() {
+ $font = $this->getFont();
+
+ return $font->write($this->raw, mb_strlen((string) $this->raw, '8bit'));
+ }
+
+ function getSVGContours() {
+ // Inherit
+ }
+
+ function getGlyphIDs() {
+ return array();
+ }
+}
+
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComponent.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComponent.php
new file mode 100644
index 0000000..9cafaf4
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComponent.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $
+ */
+
+namespace FontLib\Glyph;
+/**
+ * Glyph outline component
+ *
+ * @package php-font-lib
+ */
+class OutlineComponent {
+ public $flags;
+ public $glyphIndex;
+ public $a, $b, $c, $d, $e, $f;
+ public $point_compound;
+ public $point_component;
+ public $instructions;
+
+ function getMatrix() {
+ return array(
+ $this->a, $this->b,
+ $this->c, $this->d,
+ $this->e, $this->f,
+ );
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComposite.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComposite.php
new file mode 100644
index 0000000..8ab0d2c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineComposite.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $
+ */
+
+namespace FontLib\Glyph;
+
+/**
+ * Composite glyph outline
+ *
+ * @package php-font-lib
+ */
+class OutlineComposite extends Outline {
+ const ARG_1_AND_2_ARE_WORDS = 0x0001;
+ const ARGS_ARE_XY_VALUES = 0x0002;
+ const ROUND_XY_TO_GRID = 0x0004;
+ const WE_HAVE_A_SCALE = 0x0008;
+ const MORE_COMPONENTS = 0x0020;
+ const WE_HAVE_AN_X_AND_Y_SCALE = 0x0040;
+ const WE_HAVE_A_TWO_BY_TWO = 0x0080;
+ const WE_HAVE_INSTRUCTIONS = 0x0100;
+ const USE_MY_METRICS = 0x0200;
+ const OVERLAP_COMPOUND = 0x0400;
+
+ /**
+ * @var OutlineComponent[]
+ */
+ public $components = array();
+
+ function getGlyphIDs() {
+ if (empty($this->components)) {
+ $this->parseData();
+ }
+
+ $glyphIDs = array();
+ foreach ($this->components as $_component) {
+ $glyphIDs[] = $_component->glyphIndex;
+
+ $_glyph = $this->table->data[$_component->glyphIndex];
+
+ if ($_glyph !== $this) {
+ $glyphIDs = array_merge($glyphIDs, $_glyph->getGlyphIDs());
+ }
+ }
+
+ return $glyphIDs;
+ }
+
+ /*function parse() {
+ //$this->parseData();
+ }*/
+
+ function parseData() {
+ parent::parseData();
+
+ $font = $this->getFont();
+
+ do {
+ $flags = $font->readUInt16();
+ $glyphIndex = $font->readUInt16();
+
+ $a = 1.0;
+ $b = 0.0;
+ $c = 0.0;
+ $d = 1.0;
+ $e = 0.0;
+ $f = 0.0;
+
+ $point_compound = null;
+ $point_component = null;
+
+ $instructions = null;
+
+ if ($flags & self::ARG_1_AND_2_ARE_WORDS) {
+ if ($flags & self::ARGS_ARE_XY_VALUES) {
+ $e = $font->readInt16();
+ $f = $font->readInt16();
+ }
+ else {
+ $point_compound = $font->readUInt16();
+ $point_component = $font->readUInt16();
+ }
+ }
+ else {
+ if ($flags & self::ARGS_ARE_XY_VALUES) {
+ $e = $font->readInt8();
+ $f = $font->readInt8();
+ }
+ else {
+ $point_compound = $font->readUInt8();
+ $point_component = $font->readUInt8();
+ }
+ }
+
+ if ($flags & self::WE_HAVE_A_SCALE) {
+ $a = $d = $font->readInt16();
+ }
+ elseif ($flags & self::WE_HAVE_AN_X_AND_Y_SCALE) {
+ $a = $font->readInt16();
+ $d = $font->readInt16();
+ }
+ elseif ($flags & self::WE_HAVE_A_TWO_BY_TWO) {
+ $a = $font->readInt16();
+ $b = $font->readInt16();
+ $c = $font->readInt16();
+ $d = $font->readInt16();
+ }
+
+ //if ($flags & self::WE_HAVE_INSTRUCTIONS) {
+ //
+ //}
+
+ $component = new OutlineComponent();
+ $component->flags = $flags;
+ $component->glyphIndex = $glyphIndex;
+ $component->a = $a;
+ $component->b = $b;
+ $component->c = $c;
+ $component->d = $d;
+ $component->e = $e;
+ $component->f = $f;
+ $component->point_compound = $point_compound;
+ $component->point_component = $point_component;
+ $component->instructions = $instructions;
+
+ $this->components[] = $component;
+ } while ($flags & self::MORE_COMPONENTS);
+ }
+
+ function encode() {
+ $font = $this->getFont();
+
+ $gids = $font->getSubset();
+
+ $size = $font->writeInt16(-1);
+ $size += $font->writeFWord($this->xMin);
+ $size += $font->writeFWord($this->yMin);
+ $size += $font->writeFWord($this->xMax);
+ $size += $font->writeFWord($this->yMax);
+
+ foreach ($this->components as $_i => $_component) {
+ $flags = 0;
+ if ($_component->point_component === null && $_component->point_compound === null) {
+ $flags |= self::ARGS_ARE_XY_VALUES;
+
+ if (abs($_component->e) > 0x7F || abs($_component->f) > 0x7F) {
+ $flags |= self::ARG_1_AND_2_ARE_WORDS;
+ }
+ }
+ elseif ($_component->point_component > 0xFF || $_component->point_compound > 0xFF) {
+ $flags |= self::ARG_1_AND_2_ARE_WORDS;
+ }
+
+ if ($_component->b == 0 && $_component->c == 0) {
+ if ($_component->a == $_component->d) {
+ if ($_component->a != 1.0) {
+ $flags |= self::WE_HAVE_A_SCALE;
+ }
+ }
+ else {
+ $flags |= self::WE_HAVE_AN_X_AND_Y_SCALE;
+ }
+ }
+ else {
+ $flags |= self::WE_HAVE_A_TWO_BY_TWO;
+ }
+
+ if ($_i < count($this->components) - 1) {
+ $flags |= self::MORE_COMPONENTS;
+ }
+
+ $size += $font->writeUInt16($flags);
+
+ $new_gid = array_search($_component->glyphIndex, $gids);
+ $size += $font->writeUInt16($new_gid);
+
+ if ($flags & self::ARG_1_AND_2_ARE_WORDS) {
+ if ($flags & self::ARGS_ARE_XY_VALUES) {
+ $size += $font->writeInt16($_component->e);
+ $size += $font->writeInt16($_component->f);
+ }
+ else {
+ $size += $font->writeUInt16($_component->point_compound);
+ $size += $font->writeUInt16($_component->point_component);
+ }
+ }
+ else {
+ if ($flags & self::ARGS_ARE_XY_VALUES) {
+ $size += $font->writeInt8($_component->e);
+ $size += $font->writeInt8($_component->f);
+ }
+ else {
+ $size += $font->writeUInt8($_component->point_compound);
+ $size += $font->writeUInt8($_component->point_component);
+ }
+ }
+
+ if ($flags & self::WE_HAVE_A_SCALE) {
+ $size += $font->writeInt16($_component->a);
+ }
+ elseif ($flags & self::WE_HAVE_AN_X_AND_Y_SCALE) {
+ $size += $font->writeInt16($_component->a);
+ $size += $font->writeInt16($_component->d);
+ }
+ elseif ($flags & self::WE_HAVE_A_TWO_BY_TWO) {
+ $size += $font->writeInt16($_component->a);
+ $size += $font->writeInt16($_component->b);
+ $size += $font->writeInt16($_component->c);
+ $size += $font->writeInt16($_component->d);
+ }
+ }
+
+ return $size;
+ }
+
+ public function getSVGContours() {
+ $contours = array();
+
+ /** @var \FontLib\Table\Type\glyf $glyph_data */
+ $glyph_data = $this->getFont()->getTableObject("glyf");
+
+ /** @var Outline[] $glyphs */
+ $glyphs = $glyph_data->data;
+
+ foreach ($this->components as $component) {
+ $_glyph = $glyphs[$component->glyphIndex];
+
+ if ($_glyph !== $this) {
+ $contours[] = array(
+ "contours" => $_glyph->getSVGContours(),
+ "transform" => $component->getMatrix(),
+ );
+ }
+ }
+
+ return $contours;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineSimple.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineSimple.php
new file mode 100644
index 0000000..56b2fb4
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Glyph/OutlineSimple.php
@@ -0,0 +1,335 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ * @version $Id: Font_Table_glyf.php 46 2012-04-02 20:22:38Z fabien.menager $
+ */
+
+namespace FontLib\Glyph;
+
+/**
+ * `glyf` font table.
+ *
+ * @package php-font-lib
+ */
+class OutlineSimple extends Outline {
+ const ON_CURVE = 0x01;
+ const X_SHORT_VECTOR = 0x02;
+ const Y_SHORT_VECTOR = 0x04;
+ const REPEAT = 0x08;
+ const THIS_X_IS_SAME = 0x10;
+ const THIS_Y_IS_SAME = 0x20;
+
+ public $instructions;
+ public $points;
+
+ function parseData() {
+ parent::parseData();
+
+ if (!$this->size) {
+ return;
+ }
+
+ $font = $this->getFont();
+
+ $noc = $this->numberOfContours;
+
+ if ($noc == 0) {
+ return;
+ }
+
+ $endPtsOfContours = $font->r(array(self::uint16, $noc));
+
+ $instructionLength = $font->readUInt16();
+ $this->instructions = $font->r(array(self::uint8, $instructionLength));
+
+ $count = $endPtsOfContours[$noc - 1] + 1;
+
+ // Flags
+ $flags = array();
+ for ($index = 0; $index < $count; $index++) {
+ $flags[$index] = $font->readUInt8();
+
+ if ($flags[$index] & self::REPEAT) {
+ $repeats = $font->readUInt8();
+
+ for ($i = 1; $i <= $repeats; $i++) {
+ $flags[$index + $i] = $flags[$index];
+ }
+
+ $index += $repeats;
+ }
+ }
+
+ $points = array();
+ foreach ($flags as $i => $flag) {
+ $points[$i]["onCurve"] = $flag & self::ON_CURVE;
+ $points[$i]["endOfContour"] = in_array($i, $endPtsOfContours);
+ }
+
+ // X Coords
+ $x = 0;
+ for ($i = 0; $i < $count; $i++) {
+ $flag = $flags[$i];
+
+ if ($flag & self::THIS_X_IS_SAME) {
+ if ($flag & self::X_SHORT_VECTOR) {
+ $x += $font->readUInt8();
+ }
+ }
+ else {
+ if ($flag & self::X_SHORT_VECTOR) {
+ $x -= $font->readUInt8();
+ }
+ else {
+ $x += $font->readInt16();
+ }
+ }
+
+ $points[$i]["x"] = $x;
+ }
+
+ // Y Coords
+ $y = 0;
+ for ($i = 0; $i < $count; $i++) {
+ $flag = $flags[$i];
+
+ if ($flag & self::THIS_Y_IS_SAME) {
+ if ($flag & self::Y_SHORT_VECTOR) {
+ $y += $font->readUInt8();
+ }
+ }
+ else {
+ if ($flag & self::Y_SHORT_VECTOR) {
+ $y -= $font->readUInt8();
+ }
+ else {
+ $y += $font->readInt16();
+ }
+ }
+
+ $points[$i]["y"] = $y;
+ }
+
+ $this->points = $points;
+ }
+
+ public function splitSVGPath($path) {
+ preg_match_all('/([a-z])|(-?\d+(?:\.\d+)?)/i', $path, $matches, PREG_PATTERN_ORDER);
+
+ return $matches[0];
+ }
+
+ public function makePoints($path) {
+ $path = $this->splitSVGPath($path);
+ $l = count($path);
+ $i = 0;
+
+ $points = array();
+
+ while ($i < $l) {
+ switch ($path[$i]) {
+ // moveTo
+ case "M":
+ $points[] = array(
+ "onCurve" => true,
+ "x" => $path[++$i],
+ "y" => $path[++$i],
+ "endOfContour" => false,
+ );
+ break;
+
+ // lineTo
+ case "L":
+ $points[] = array(
+ "onCurve" => true,
+ "x" => $path[++$i],
+ "y" => $path[++$i],
+ "endOfContour" => false,
+ );
+ break;
+
+ // quadraticCurveTo
+ case "Q":
+ $points[] = array(
+ "onCurve" => false,
+ "x" => $path[++$i],
+ "y" => $path[++$i],
+ "endOfContour" => false,
+ );
+ $points[] = array(
+ "onCurve" => true,
+ "x" => $path[++$i],
+ "y" => $path[++$i],
+ "endOfContour" => false,
+ );
+ break;
+
+ // closePath
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case "z":
+ $points[count($points) - 1]["endOfContour"] = true;
+
+ default:
+ $i++;
+ break;
+ }
+ }
+
+ return $points;
+ }
+
+ function encode() {
+ if (empty($this->points)) {
+ return parent::encode();
+ }
+
+ return $this->size = $this->encodePoints($this->points);
+ }
+
+ public function encodePoints($points) {
+ $endPtsOfContours = array();
+ $flags = array();
+ $coords_x = array();
+ $coords_y = array();
+
+ $last_x = 0;
+ $last_y = 0;
+ $xMin = $yMin = 0xFFFF;
+ $xMax = $yMax = -0xFFFF;
+ foreach ($points as $i => $point) {
+ $flag = 0;
+ if ($point["onCurve"]) {
+ $flag |= self::ON_CURVE;
+ }
+
+ if ($point["endOfContour"]) {
+ $endPtsOfContours[] = $i;
+ }
+
+ // Simplified, we could do some optimizations
+ if ($point["x"] == $last_x) {
+ $flag |= self::THIS_X_IS_SAME;
+ }
+ else {
+ $x = intval($point["x"]);
+ $xMin = min($x, $xMin);
+ $xMax = max($x, $xMax);
+ $coords_x[] = $x - $last_x; // int16
+ }
+
+ // Simplified, we could do some optimizations
+ if ($point["y"] == $last_y) {
+ $flag |= self::THIS_Y_IS_SAME;
+ }
+ else {
+ $y = intval($point["y"]);
+ $yMin = min($y, $yMin);
+ $yMax = max($y, $yMax);
+ $coords_y[] = $y - $last_y; // int16
+ }
+
+ $flags[] = $flag;
+ $last_x = $point["x"];
+ $last_y = $point["y"];
+ }
+
+ $font = $this->getFont();
+
+ $l = 0;
+ $l += $font->writeInt16(count($endPtsOfContours)); // endPtsOfContours
+ $l += $font->writeFWord(isset($this->xMin) ? $this->xMin : $xMin); // xMin
+ $l += $font->writeFWord(isset($this->yMin) ? $this->yMin : $yMin); // yMin
+ $l += $font->writeFWord(isset($this->xMax) ? $this->xMax : $xMax); // xMax
+ $l += $font->writeFWord(isset($this->yMax) ? $this->yMax : $yMax); // yMax
+
+ // Simple glyf
+ $l += $font->w(array(self::uint16, count($endPtsOfContours)), $endPtsOfContours); // endPtsOfContours
+ $l += $font->writeUInt16(0); // instructionLength
+ $l += $font->w(array(self::uint8, count($flags)), $flags); // flags
+ $l += $font->w(array(self::int16, count($coords_x)), $coords_x); // xCoordinates
+ $l += $font->w(array(self::int16, count($coords_y)), $coords_y); // yCoordinates
+ return $l;
+ }
+
+ public function getSVGContours($points = null) {
+ $path = "";
+
+ if (!$points) {
+ if (empty($this->points)) {
+ $this->parseData();
+ }
+
+ $points = $this->points;
+ }
+
+ $length = (empty($points) ? 0 : count($points));
+ $firstIndex = 0;
+ $count = 0;
+
+ for ($i = 0; $i < $length; $i++) {
+ $count++;
+
+ if ($points[$i]["endOfContour"]) {
+ $path .= $this->getSVGPath($points, $firstIndex, $count);
+ $firstIndex = $i + 1;
+ $count = 0;
+ }
+ }
+
+ return $path;
+ }
+
+ protected function getSVGPath($points, $startIndex, $count) {
+ $offset = 0;
+ $path = "";
+
+ while ($offset < $count) {
+ $point = $points[$startIndex + $offset % $count];
+ $point_p1 = $points[$startIndex + ($offset + 1) % $count];
+
+ if ($offset == 0) {
+ $path .= "M{$point['x']},{$point['y']} ";
+ }
+
+ if ($point["onCurve"]) {
+ if ($point_p1["onCurve"]) {
+ $path .= "L{$point_p1['x']},{$point_p1['y']} ";
+ $offset++;
+ }
+ else {
+ $point_p2 = $points[$startIndex + ($offset + 2) % $count];
+
+ if ($point_p2["onCurve"]) {
+ $path .= "Q{$point_p1['x']},{$point_p1['y']},{$point_p2['x']},{$point_p2['y']} ";
+ }
+ else {
+ $path .= "Q{$point_p1['x']},{$point_p1['y']}," . $this->midValue($point_p1['x'], $point_p2['x']) . "," . $this->midValue($point_p1['y'], $point_p2['y']) . " ";
+ }
+
+ $offset += 2;
+ }
+ }
+ else {
+ if ($point_p1["onCurve"]) {
+ $path .= "Q{$point['x']},{$point['y']},{$point_p1['x']},{$point_p1['y']} ";
+ }
+ else {
+ $path .= "Q{$point['x']},{$point['y']}," . $this->midValue($point['x'], $point_p1['x']) . "," . $this->midValue($point['y'], $point_p1['y']) . " ";
+ }
+
+ $offset++;
+ }
+ }
+
+ $path .= "z ";
+
+ return $path;
+ }
+
+ function midValue($a, $b) {
+ return $a + ($b - $a) / 2;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Header.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Header.php
new file mode 100644
index 0000000..cbf137e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Header.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace FontLib;
+
+use FontLib\TrueType\File;
+
+/**
+ * Font header container.
+ *
+ * @package php-font-lib
+ */
+abstract class Header extends BinaryStream {
+ /**
+ * @var File
+ */
+ protected $font;
+ protected $def = array();
+
+ public $data;
+
+ public function __construct(File $font) {
+ $this->font = $font;
+ }
+
+ public function encode() {
+ return $this->font->pack($this->def, $this->data);
+ }
+
+ public function parse() {
+ $this->data = $this->font->unpack($this->def);
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/File.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/File.php
new file mode 100644
index 0000000..9c6df96
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/File.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\OpenType;
+
+/**
+ * Open Type font, the same as a TrueType one.
+ *
+ * @package php-font-lib
+ */
+class File extends \FontLib\TrueType\File {
+ //
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/TableDirectoryEntry.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/TableDirectoryEntry.php
new file mode 100644
index 0000000..dd75a3e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/OpenType/TableDirectoryEntry.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\OpenType;
+
+/**
+ * Open Type Table directory entry, the same as a TrueType one.
+ *
+ * @package php-font-lib
+ */
+class TableDirectoryEntry extends \FontLib\TrueType\TableDirectoryEntry {
+
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/DirectoryEntry.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/DirectoryEntry.php
new file mode 100644
index 0000000..54a67af
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/DirectoryEntry.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace FontLib\Table;
+
+use FontLib\TrueType\File;
+use FontLib\Font;
+use FontLib\BinaryStream;
+
+/**
+ * Generic Font table directory entry.
+ *
+ * @package php-font-lib
+ */
+class DirectoryEntry extends BinaryStream {
+ /**
+ * @var File
+ */
+ protected $font;
+
+ /**
+ * @var Table
+ */
+ protected $font_table;
+
+ public $entryLength = 4;
+
+ public $tag;
+ public $checksum;
+ public $offset;
+ public $length;
+
+ protected $origF;
+
+ /**
+ * @param string $data
+ *
+ * @return int
+ */
+ static function computeChecksum($data) {
+ $len = mb_strlen($data, '8bit');
+ $mod = $len % 4;
+
+ if ($mod) {
+ $data = str_pad($data, $len + (4 - $mod), "\0");
+ }
+
+ $len = mb_strlen($data, '8bit');
+
+ $hi = 0x0000;
+ $lo = 0x0000;
+
+ for ($i = 0; $i < $len; $i += 4) {
+ $hi += (ord($data[$i]) << 8) + ord($data[$i + 1]);
+ $lo += (ord($data[$i + 2]) << 8) + ord($data[$i + 3]);
+ $hi += $lo >> 16;
+ $lo = $lo & 0xFFFF;
+ $hi = $hi & 0xFFFF;
+ }
+
+ return ($hi << 8) + $lo;
+ }
+
+ function __construct(File $font) {
+ $this->font = $font;
+ $this->f = $font->f;
+ }
+
+ function parse() {
+ $this->tag = $this->font->read(4);
+ }
+
+ function open($filename, $mode = self::modeRead) {
+ // void
+ }
+
+ function setTable(Table $font_table) {
+ $this->font_table = $font_table;
+ }
+
+ function encode($entry_offset) {
+ Font::d("\n==== $this->tag ====");
+ //Font::d("Entry offset = $entry_offset");
+
+ $data = $this->font_table;
+ $font = $this->font;
+
+ $table_offset = $font->pos();
+ $this->offset = $table_offset;
+ $table_length = $data->encode();
+
+ $font->seek($table_offset);
+ $table_data = $font->read($table_length);
+
+ $font->seek($entry_offset);
+
+ $font->write($this->tag, 4);
+ $font->writeUInt32(self::computeChecksum($table_data));
+ $font->writeUInt32($table_offset);
+ $font->writeUInt32($table_length);
+
+ Font::d("Bytes written = $table_length");
+
+ $font->seek($table_offset + $table_length);
+ }
+
+ /**
+ * @return File
+ */
+ function getFont() {
+ return $this->font;
+ }
+
+ function startRead() {
+ $this->font->seek($this->offset);
+ }
+
+ function endRead() {
+ //
+ }
+
+ function startWrite() {
+ $this->font->seek($this->offset);
+ }
+
+ function endWrite() {
+ //
+ }
+}
+
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Table.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Table.php
new file mode 100644
index 0000000..b127112
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Table.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace FontLib\Table;
+
+use FontLib\TrueType\File;
+use FontLib\Font;
+use FontLib\BinaryStream;
+
+/**
+ * Generic font table.
+ *
+ * @package php-font-lib
+ */
+class Table extends BinaryStream {
+ /**
+ * @var DirectoryEntry
+ */
+ protected $entry;
+ protected $def = array();
+
+ public $data;
+
+ final public function __construct(DirectoryEntry $entry) {
+ $this->entry = $entry;
+ $entry->setTable($this);
+ }
+
+ /**
+ * @return File
+ */
+ public function getFont() {
+ return $this->entry->getFont();
+ }
+
+ protected function _encode() {
+ if (empty($this->data)) {
+ Font::d(" >> Table is empty");
+
+ return 0;
+ }
+
+ return $this->getFont()->pack($this->def, $this->data);
+ }
+
+ protected function _parse() {
+ $this->data = $this->getFont()->unpack($this->def);
+ }
+
+ protected function _parseRaw() {
+ $this->data = $this->getFont()->read($this->entry->length);
+ }
+
+ protected function _encodeRaw() {
+ return $this->getFont()->write($this->data, $this->entry->length);
+ }
+
+ public function toHTML() {
+ return "<pre>" . var_export($this->data, true) . "</pre>";
+ }
+
+ final public function encode() {
+ $this->entry->startWrite();
+
+ if (false && empty($this->def)) {
+ $length = $this->_encodeRaw();
+ }
+ else {
+ $length = $this->_encode();
+ }
+
+ $this->entry->endWrite();
+
+ return $length;
+ }
+
+ final public function parse() {
+ $this->entry->startRead();
+
+ if (false && empty($this->def)) {
+ $this->_parseRaw();
+ }
+ else {
+ $this->_parse();
+ }
+
+ $this->entry->endRead();
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/cmap.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/cmap.php
new file mode 100644
index 0000000..7db77e1
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/cmap.php
@@ -0,0 +1,298 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `cmap` font table.
+ *
+ * @package php-font-lib
+ */
+class cmap extends Table {
+ private static $header_format = array(
+ "version" => self::uint16,
+ "numberSubtables" => self::uint16,
+ );
+
+ private static $subtable_header_format = array(
+ "platformID" => self::uint16,
+ "platformSpecificID" => self::uint16,
+ "offset" => self::uint32,
+ );
+
+ private static $subtable_v4_format = array(
+ "length" => self::uint16,
+ "language" => self::uint16,
+ "segCountX2" => self::uint16,
+ "searchRange" => self::uint16,
+ "entrySelector" => self::uint16,
+ "rangeShift" => self::uint16,
+ );
+
+ private static $subtable_v12_format = array(
+ "length" => self::uint32,
+ "language" => self::uint32,
+ "ngroups" => self::uint32
+ );
+
+ protected function _parse() {
+ $font = $this->getFont();
+
+ $cmap_offset = $font->pos();
+
+ $data = $font->unpack(self::$header_format);
+
+ $subtables = array();
+ for ($i = 0; $i < $data["numberSubtables"]; $i++) {
+ $subtables[] = $font->unpack(self::$subtable_header_format);
+ }
+
+ $data["subtables"] = $subtables;
+
+ foreach ($data["subtables"] as $i => &$subtable) {
+ $font->seek($cmap_offset + $subtable["offset"]);
+
+ $subtable["format"] = $font->readUInt16();
+
+ // @todo Only CMAP version 4 and 12
+ if (($subtable["format"] != 4) && ($subtable["format"] != 12)) {
+ unset($data["subtables"][$i]);
+ $data["numberSubtables"]--;
+ continue;
+ }
+
+ if ($subtable["format"] == 12) {
+
+ $font->readUInt16();
+
+ $subtable += $font->unpack(self::$subtable_v12_format);
+
+ $glyphIndexArray = array();
+ $endCodes = array();
+ $startCodes = array();
+
+ for ($p = 0; $p < $subtable['ngroups']; $p++) {
+
+ $startCode = $startCodes[] = $font->readUInt32();
+ $endCode = $endCodes[] = $font->readUInt32();
+ $startGlyphCode = $font->readUInt32();
+
+ for ($c = $startCode; $c <= $endCode; $c++) {
+ $glyphIndexArray[$c] = $startGlyphCode;
+ $startGlyphCode++;
+ }
+ }
+
+ $subtable += array(
+ "startCode" => $startCodes,
+ "endCode" => $endCodes,
+ "glyphIndexArray" => $glyphIndexArray,
+ );
+
+ }
+ else if ($subtable["format"] == 4) {
+
+ $subtable += $font->unpack(self::$subtable_v4_format);
+
+ $segCount = $subtable["segCountX2"] / 2;
+ $subtable["segCount"] = $segCount;
+
+ $endCode = $font->readUInt16Many($segCount);
+
+ $font->readUInt16(); // reservedPad
+
+ $startCode = $font->readUInt16Many($segCount);
+ $idDelta = $font->readInt16Many($segCount);
+
+ $ro_start = $font->pos();
+ $idRangeOffset = $font->readUInt16Many($segCount);
+
+ $glyphIndexArray = array();
+ for ($i = 0; $i < $segCount; $i++) {
+ $c1 = $startCode[$i];
+ $c2 = $endCode[$i];
+ $d = $idDelta[$i];
+ $ro = $idRangeOffset[$i];
+
+ if ($ro > 0) {
+ $font->seek($subtable["offset"] + 2 * $i + $ro);
+ }
+
+ for ($c = $c1; $c <= $c2; $c++) {
+ if ($ro == 0) {
+ $gid = ($c + $d) & 0xFFFF;
+ }
+ else {
+ $offset = ($c - $c1) * 2 + $ro;
+ $offset = $ro_start + 2 * $i + $offset;
+
+ $font->seek($offset);
+ $gid = $font->readUInt16();
+
+ if ($gid != 0) {
+ $gid = ($gid + $d) & 0xFFFF;
+ }
+ }
+
+ if ($gid > 0) {
+ $glyphIndexArray[$c] = $gid;
+ }
+ }
+ }
+
+ $subtable += array(
+ "endCode" => $endCode,
+ "startCode" => $startCode,
+ "idDelta" => $idDelta,
+ "idRangeOffset" => $idRangeOffset,
+ "glyphIndexArray" => $glyphIndexArray,
+ );
+ }
+ }
+
+ $this->data = $data;
+ }
+
+ function _encode() {
+ $font = $this->getFont();
+
+ $subset = $font->getSubset();
+ $glyphIndexArray = $font->getUnicodeCharMap();
+
+ $newGlyphIndexArray = array();
+ foreach ($glyphIndexArray as $code => $gid) {
+ $new_gid = array_search($gid, $subset);
+ if ($new_gid !== false) {
+ $newGlyphIndexArray[$code] = $new_gid;
+ }
+ }
+
+ ksort($newGlyphIndexArray); // Sort by char code
+
+ $segments = array();
+
+ $i = -1;
+ $prevCode = 0xFFFF;
+ $prevGid = 0xFFFF;
+
+ foreach ($newGlyphIndexArray as $code => $gid) {
+ if (
+ $prevCode + 1 != $code ||
+ $prevGid + 1 != $gid
+ ) {
+ $i++;
+ $segments[$i] = array();
+ }
+
+ $segments[$i][] = array($code, $gid);
+
+ $prevCode = $code;
+ $prevGid = $gid;
+ }
+
+ $segments[][] = array(0xFFFF, 0xFFFF);
+
+ $startCode = array();
+ $endCode = array();
+ $idDelta = array();
+
+ foreach ($segments as $codes) {
+ $start = reset($codes);
+ $end = end($codes);
+
+ $startCode[] = $start[0];
+ $endCode[] = $end[0];
+ $idDelta[] = $start[1] - $start[0];
+ }
+
+ $segCount = count($startCode);
+ $idRangeOffset = array_fill(0, $segCount, 0);
+
+ $searchRange = 1;
+ $entrySelector = 0;
+ while ($searchRange * 2 <= $segCount) {
+ $searchRange *= 2;
+ $entrySelector++;
+ }
+ $searchRange *= 2;
+ $rangeShift = $segCount * 2 - $searchRange;
+
+ $subtables = array(
+ array(
+ // header
+ "platformID" => 3, // Unicode
+ "platformSpecificID" => 1,
+ "offset" => null,
+
+ // subtable
+ "format" => 4,
+ "length" => null,
+ "language" => 0,
+ "segCount" => $segCount,
+ "segCountX2" => $segCount * 2,
+ "searchRange" => $searchRange,
+ "entrySelector" => $entrySelector,
+ "rangeShift" => $rangeShift,
+ "startCode" => $startCode,
+ "endCode" => $endCode,
+ "idDelta" => $idDelta,
+ "idRangeOffset" => $idRangeOffset,
+ "glyphIndexArray" => $newGlyphIndexArray,
+ )
+ );
+
+ $data = array(
+ "version" => 0,
+ "numberSubtables" => count($subtables),
+ "subtables" => $subtables,
+ );
+
+ $length = $font->pack(self::$header_format, $data);
+
+ $subtable_headers_size = $data["numberSubtables"] * 8; // size of self::$subtable_header_format
+ $subtable_headers_offset = $font->pos();
+
+ $length += $font->write(str_repeat("\0", $subtable_headers_size), $subtable_headers_size);
+
+ // write subtables data
+ foreach ($data["subtables"] as $i => $subtable) {
+ $length_before = $length;
+ $data["subtables"][$i]["offset"] = $length;
+
+ $length += $font->writeUInt16($subtable["format"]);
+
+ $before_subheader = $font->pos();
+ $length += $font->pack(self::$subtable_v4_format, $subtable);
+
+ $segCount = $subtable["segCount"];
+ $length += $font->w(array(self::uint16, $segCount), $subtable["endCode"]);
+ $length += $font->writeUInt16(0); // reservedPad
+ $length += $font->w(array(self::uint16, $segCount), $subtable["startCode"]);
+ $length += $font->w(array(self::int16, $segCount), $subtable["idDelta"]);
+ $length += $font->w(array(self::uint16, $segCount), $subtable["idRangeOffset"]);
+ $length += $font->w(array(self::uint16, $segCount), array_values($subtable["glyphIndexArray"]));
+
+ $after_subtable = $font->pos();
+
+ $subtable["length"] = $length - $length_before;
+ $font->seek($before_subheader);
+ $length += $font->pack(self::$subtable_v4_format, $subtable);
+
+ $font->seek($after_subtable);
+ }
+
+ // write subtables headers
+ $font->seek($subtable_headers_offset);
+ foreach ($data["subtables"] as $subtable) {
+ $font->pack(self::$subtable_header_format, $subtable);
+ }
+
+ return $length;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/glyf.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/glyf.php
new file mode 100644
index 0000000..1fbec3f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/glyf.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+
+use FontLib\Table\Table;
+use FontLib\Glyph\Outline;
+use FontLib\Glyph\OutlineSimple;
+
+/**
+ * `glyf` font table.
+ *
+ * @package php-font-lib
+ * @property Outline[] $data
+ */
+class glyf extends Table {
+ protected function _parse() {
+ $font = $this->getFont();
+ $offset = $font->pos();
+
+ $loca = $font->getData("loca");
+ $real_loca = array_slice($loca, 0, -1); // Not the last dummy loca entry
+
+ $data = array();
+
+ foreach ($real_loca as $gid => $location) {
+ $_offset = $offset + $loca[$gid];
+ $_size = $loca[$gid + 1] - $loca[$gid];
+ $data[$gid] = Outline::init($this, $_offset, $_size, $font);
+ }
+
+ $this->data = $data;
+ }
+
+ public function getGlyphIDs($gids = array()) {
+ $glyphIDs = array();
+
+ foreach ($gids as $_gid) {
+ $_glyph = $this->data[$_gid];
+ $glyphIDs = array_merge($glyphIDs, $_glyph->getGlyphIDs());
+ }
+
+ return array_unique(array_merge($gids, $glyphIDs));
+ }
+
+ public function toHTML() {
+ $max = 160;
+ $font = $this->getFont();
+
+ $head = $font->getData("head");
+ $head_json = json_encode($head);
+
+ $os2 = $font->getData("OS/2");
+ $os2_json = json_encode($os2);
+
+ $hmtx = $font->getData("hmtx");
+ $hmtx_json = json_encode($hmtx);
+
+ $names = $font->getData("post", "names");
+ $glyphIndexArray = array_flip($font->getUnicodeCharMap());
+
+ $width = (abs($head["xMin"]) + $head["xMax"]);
+ $height = (abs($head["yMin"]) + $head["yMax"]);
+
+ $ratio = 1;
+ if ($width > $max || $height > $max) {
+ $ratio = max($width, $height) / $max;
+ $width = round($width / $ratio);
+ $height = round($height / $ratio);
+ }
+
+ $n = 500;
+
+ $s = "<h3>" . "Only the first $n simple glyphs are shown (" . count($this->data) . " total)
+ <div class='glyph-view simple'>Simple glyph</div>
+ <div class='glyph-view composite'>Composite glyph</div>
+ Zoom: <input type='range' value='100' max='400' onchange='Glyph.resize(this.value)' />
+ </h3>
+ <script>
+ Glyph.ratio = $ratio;
+ Glyph.head = $head_json;
+ Glyph.os2 = $os2_json;
+ Glyph.hmtx = $hmtx_json;
+ Glyph.width = $width;
+ Glyph.height = $height;
+ </script>";
+
+ foreach ($this->data as $g => $glyph) {
+ if ($n-- <= 0) {
+ break;
+ }
+
+ $glyph->parseData();
+
+ $shape = array(
+ "SVGContours" => $glyph->getSVGContours(),
+ "xMin" => $glyph->xMin,
+ "yMin" => $glyph->yMin,
+ "xMax" => $glyph->xMax,
+ "yMax" => $glyph->yMax,
+ );
+ $shape_json = json_encode($shape);
+
+ $type = ($glyph instanceof OutlineSimple ? "simple" : "composite");
+ $char = isset($glyphIndexArray[$g]) ? $glyphIndexArray[$g] : 0;
+ $name = isset($names[$g]) ? $names[$g] : sprintf("uni%04x", $char);
+ $char = $char ? "&#{$glyphIndexArray[$g]};" : "";
+
+ $s .= "<div class='glyph-view $type' id='glyph-$g'>
+ <span class='glyph-id'>$g</span>
+ <span class='char'>$char</span>
+ <span class='char-name'>$name</span>
+ ";
+
+ if ($type == "composite") {
+ foreach ($glyph->getGlyphIDs() as $_id) {
+ $s .= "<a href='#glyph-$_id' class='glyph-component-id'>$_id</a> ";
+ }
+ }
+
+ $s .= "<br />
+ <canvas width='$width' height='$height' id='glyph-canvas-$g'></canvas>
+ </div>
+ <script>Glyph.glyphs.push([$g,$shape_json]);</script>";
+ }
+
+ return $s;
+ }
+
+
+ protected function _encode() {
+ $font = $this->getFont();
+ $subset = $font->getSubset();
+ $data = $this->data;
+
+ $loca = array();
+
+ $length = 0;
+ foreach ($subset as $gid) {
+ $loca[] = $length;
+ $length += $data[$gid]->encode();
+ }
+
+ $loca[] = $length; // dummy loca
+ $font->getTableObject("loca")->data = $loca;
+
+ return $length;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/head.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/head.php
new file mode 100644
index 0000000..6349f14
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/head.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+use Exception;
+
+/**
+ * `head` font table.
+ *
+ * @package php-font-lib
+ */
+class head extends Table {
+ protected $def = array(
+ "tableVersion" => self::Fixed,
+ "fontRevision" => self::Fixed,
+ "checkSumAdjustment" => self::uint32,
+ "magicNumber" => self::uint32,
+ "flags" => self::uint16,
+ "unitsPerEm" => self::uint16,
+ "created" => self::longDateTime,
+ "modified" => self::longDateTime,
+ "xMin" => self::FWord,
+ "yMin" => self::FWord,
+ "xMax" => self::FWord,
+ "yMax" => self::FWord,
+ "macStyle" => self::uint16,
+ "lowestRecPPEM" => self::uint16,
+ "fontDirectionHint" => self::int16,
+ "indexToLocFormat" => self::int16,
+ "glyphDataFormat" => self::int16,
+ );
+
+ protected function _parse() {
+ parent::_parse();
+
+ if ($this->data["magicNumber"] != 0x5F0F3CF5) {
+ throw new Exception("Incorrect magic number (" . dechex($this->data["magicNumber"]) . ")");
+ }
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hhea.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hhea.php
new file mode 100644
index 0000000..dc60a14
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hhea.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `hhea` font table.
+ *
+ * @package php-font-lib
+ */
+class hhea extends Table {
+ protected $def = array(
+ "version" => self::Fixed,
+ "ascent" => self::FWord,
+ "descent" => self::FWord,
+ "lineGap" => self::FWord,
+ "advanceWidthMax" => self::uFWord,
+ "minLeftSideBearing" => self::FWord,
+ "minRightSideBearing" => self::FWord,
+ "xMaxExtent" => self::FWord,
+ "caretSlopeRise" => self::int16,
+ "caretSlopeRun" => self::int16,
+ "caretOffset" => self::FWord,
+ self::int16,
+ self::int16,
+ self::int16,
+ self::int16,
+ "metricDataFormat" => self::int16,
+ "numOfLongHorMetrics" => self::uint16,
+ );
+
+ function _encode() {
+ $font = $this->getFont();
+ $this->data["numOfLongHorMetrics"] = count($font->getSubset());
+
+ return parent::_encode();
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hmtx.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hmtx.php
new file mode 100644
index 0000000..76e3307
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/hmtx.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `hmtx` font table.
+ *
+ * @package php-font-lib
+ */
+class hmtx extends Table {
+ protected function _parse() {
+ $font = $this->getFont();
+ $offset = $font->pos();
+
+ $numOfLongHorMetrics = $font->getData("hhea", "numOfLongHorMetrics");
+ $numGlyphs = $font->getData("maxp", "numGlyphs");
+
+ $font->seek($offset);
+
+ $data = array();
+ $metrics = $font->readUInt16Many($numOfLongHorMetrics * 2);
+ for ($gid = 0, $mid = 0; $gid < $numOfLongHorMetrics; $gid++) {
+ $advanceWidth = isset($metrics[$mid]) ? $metrics[$mid] : 0;
+ $mid += 1;
+ $leftSideBearing = isset($metrics[$mid]) ? $metrics[$mid] : 0;
+ $mid += 1;
+ $data[$gid] = array($advanceWidth, $leftSideBearing);
+ }
+
+ if ($numOfLongHorMetrics < $numGlyphs) {
+ $lastWidth = end($data);
+ $data = array_pad($data, $numGlyphs, $lastWidth);
+ }
+
+ $this->data = $data;
+ }
+
+ protected function _encode() {
+ $font = $this->getFont();
+ $subset = $font->getSubset();
+ $data = $this->data;
+
+ $length = 0;
+
+ foreach ($subset as $gid) {
+ $length += $font->writeUInt16($data[$gid][0]);
+ $length += $font->writeUInt16($data[$gid][1]);
+ }
+
+ return $length;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/kern.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/kern.php
new file mode 100644
index 0000000..9875946
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/kern.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `kern` font table.
+ *
+ * @package php-font-lib
+ */
+class kern extends Table {
+ protected function _parse() {
+ $font = $this->getFont();
+
+ $data = $font->unpack(array(
+ "version" => self::uint16,
+ "nTables" => self::uint16,
+
+ // only the first subtable will be parsed
+ "subtableVersion" => self::uint16,
+ "length" => self::uint16,
+ "coverage" => self::uint16,
+ ));
+
+ $data["format"] = ($data["coverage"] >> 8);
+
+ $subtable = array();
+
+ switch ($data["format"]) {
+ case 0:
+ $subtable = $font->unpack(array(
+ "nPairs" => self::uint16,
+ "searchRange" => self::uint16,
+ "entrySelector" => self::uint16,
+ "rangeShift" => self::uint16,
+ ));
+
+ $pairs = array();
+ $tree = array();
+
+ $values = $font->readUInt16Many($subtable["nPairs"] * 3);
+ for ($i = 0, $idx = 0; $i < $subtable["nPairs"]; $i++) {
+ $left = $values[$idx++];
+ $right = $values[$idx++];
+ $value = $values[$idx++];
+
+ if ($value >= 0x8000) {
+ $value -= 0x10000;
+ }
+
+ $pairs[] = array(
+ "left" => $left,
+ "right" => $right,
+ "value" => $value,
+ );
+
+ $tree[$left][$right] = $value;
+ }
+
+ //$subtable["pairs"] = $pairs;
+ $subtable["tree"] = $tree;
+ break;
+
+ case 1:
+ case 2:
+ case 3:
+ break;
+ }
+
+ $data["subtable"] = $subtable;
+
+ $this->data = $data;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/loca.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/loca.php
new file mode 100644
index 0000000..cbc2a20
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/loca.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `loca` font table.
+ *
+ * @package php-font-lib
+ */
+class loca extends Table {
+ protected function _parse() {
+ $font = $this->getFont();
+ $offset = $font->pos();
+
+ $indexToLocFormat = $font->getData("head", "indexToLocFormat");
+ $numGlyphs = $font->getData("maxp", "numGlyphs");
+
+ $font->seek($offset);
+
+ $data = array();
+
+ // 2 bytes
+ if ($indexToLocFormat == 0) {
+ $d = $font->read(($numGlyphs + 1) * 2);
+ $loc = unpack("n*", $d);
+
+ for ($i = 0; $i <= $numGlyphs; $i++) {
+ $data[] = isset($loc[$i + 1]) ? $loc[$i + 1] * 2 : 0;
+ }
+ }
+
+ // 4 bytes
+ else {
+ if ($indexToLocFormat == 1) {
+ $d = $font->read(($numGlyphs + 1) * 4);
+ $loc = unpack("N*", $d);
+
+ for ($i = 0; $i <= $numGlyphs; $i++) {
+ $data[] = isset($loc[$i + 1]) ? $loc[$i + 1] : 0;
+ }
+ }
+ }
+
+ $this->data = $data;
+ }
+
+ function _encode() {
+ $font = $this->getFont();
+ $data = $this->data;
+
+ $indexToLocFormat = $font->getData("head", "indexToLocFormat");
+ $numGlyphs = $font->getData("maxp", "numGlyphs");
+ $length = 0;
+
+ // 2 bytes
+ if ($indexToLocFormat == 0) {
+ for ($i = 0; $i <= $numGlyphs; $i++) {
+ $length += $font->writeUInt16($data[$i] / 2);
+ }
+ }
+
+ // 4 bytes
+ else {
+ if ($indexToLocFormat == 1) {
+ for ($i = 0; $i <= $numGlyphs; $i++) {
+ $length += $font->writeUInt32($data[$i]);
+ }
+ }
+ }
+
+ return $length;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/maxp.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/maxp.php
new file mode 100644
index 0000000..b4ebae0
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/maxp.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `maxp` font table.
+ *
+ * @package php-font-lib
+ */
+class maxp extends Table {
+ protected $def = array(
+ "version" => self::Fixed,
+ "numGlyphs" => self::uint16,
+ "maxPoints" => self::uint16,
+ "maxContours" => self::uint16,
+ "maxComponentPoints" => self::uint16,
+ "maxComponentContours" => self::uint16,
+ "maxZones" => self::uint16,
+ "maxTwilightPoints" => self::uint16,
+ "maxStorage" => self::uint16,
+ "maxFunctionDefs" => self::uint16,
+ "maxInstructionDefs" => self::uint16,
+ "maxStackElements" => self::uint16,
+ "maxSizeOfInstructions" => self::uint16,
+ "maxComponentElements" => self::uint16,
+ "maxComponentDepth" => self::uint16,
+ );
+
+ function _encode() {
+ $font = $this->getFont();
+ $this->data["numGlyphs"] = count($font->getSubset());
+
+ return parent::_encode();
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/name.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/name.php
new file mode 100644
index 0000000..794824d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/name.php
@@ -0,0 +1,193 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+
+use FontLib\Table\Table;
+use FontLib\Font;
+
+/**
+ * `name` font table.
+ *
+ * @package php-font-lib
+ */
+class name extends Table {
+ private static $header_format = array(
+ "format" => self::uint16,
+ "count" => self::uint16,
+ "stringOffset" => self::uint16,
+ );
+
+ const NAME_COPYRIGHT = 0;
+ const NAME_NAME = 1;
+ const NAME_SUBFAMILY = 2;
+ const NAME_SUBFAMILY_ID = 3;
+ const NAME_FULL_NAME = 4;
+ const NAME_VERSION = 5;
+ const NAME_POSTSCRIPT_NAME = 6;
+ const NAME_TRADEMARK = 7;
+ const NAME_MANUFACTURER = 8;
+ const NAME_DESIGNER = 9;
+ const NAME_DESCRIPTION = 10;
+ const NAME_VENDOR_URL = 11;
+ const NAME_DESIGNER_URL = 12;
+ const NAME_LICENSE = 13;
+ const NAME_LICENSE_URL = 14;
+ const NAME_PREFERRE_FAMILY = 16;
+ const NAME_PREFERRE_SUBFAMILY = 17;
+ const NAME_COMPAT_FULL_NAME = 18;
+ const NAME_SAMPLE_TEXT = 19;
+
+ static $nameIdCodes = array(
+ 0 => "Copyright",
+ 1 => "FontName",
+ 2 => "FontSubfamily",
+ 3 => "UniqueID",
+ 4 => "FullName",
+ 5 => "Version",
+ 6 => "PostScriptName",
+ 7 => "Trademark",
+ 8 => "Manufacturer",
+ 9 => "Designer",
+ 10 => "Description",
+ 11 => "FontVendorURL",
+ 12 => "FontDesignerURL",
+ 13 => "LicenseDescription",
+ 14 => "LicenseURL",
+ // 15
+ 16 => "PreferredFamily",
+ 17 => "PreferredSubfamily",
+ 18 => "CompatibleFullName",
+ 19 => "SampleText",
+ );
+
+ static $platforms = array(
+ 0 => "Unicode",
+ 1 => "Macintosh",
+ // 2 => Reserved
+ 3 => "Microsoft",
+ );
+
+ static $platformSpecific = array(
+ // Unicode
+ 0 => array(
+ 0 => "Default semantics",
+ 1 => "Version 1.1 semantics",
+ 2 => "ISO 10646 1993 semantics (deprecated)",
+ 3 => "Unicode 2.0 or later semantics",
+ ),
+
+ // Macintosh
+ 1 => array(
+ 0 => "Roman",
+ 1 => "Japanese",
+ 2 => "Traditional Chinese",
+ 3 => "Korean",
+ 4 => "Arabic",
+ 5 => "Hebrew",
+ 6 => "Greek",
+ 7 => "Russian",
+ 8 => "RSymbol",
+ 9 => "Devanagari",
+ 10 => "Gurmukhi",
+ 11 => "Gujarati",
+ 12 => "Oriya",
+ 13 => "Bengali",
+ 14 => "Tamil",
+ 15 => "Telugu",
+ 16 => "Kannada",
+ 17 => "Malayalam",
+ 18 => "Sinhalese",
+ 19 => "Burmese",
+ 20 => "Khmer",
+ 21 => "Thai",
+ 22 => "Laotian",
+ 23 => "Georgian",
+ 24 => "Armenian",
+ 25 => "Simplified Chinese",
+ 26 => "Tibetan",
+ 27 => "Mongolian",
+ 28 => "Geez",
+ 29 => "Slavic",
+ 30 => "Vietnamese",
+ 31 => "Sindhi",
+ ),
+
+ // Microsoft
+ 3 => array(
+ 0 => "Symbol",
+ 1 => "Unicode BMP (UCS-2)",
+ 2 => "ShiftJIS",
+ 3 => "PRC",
+ 4 => "Big5",
+ 5 => "Wansung",
+ 6 => "Johab",
+ // 7 => Reserved
+ // 8 => Reserved
+ // 9 => Reserved
+ 10 => "Unicode UCS-4",
+ ),
+ );
+
+ protected function _parse() {
+ $font = $this->getFont();
+
+ $tableOffset = $font->pos();
+
+ $data = $font->unpack(self::$header_format);
+
+ $records = array();
+ for ($i = 0; $i < $data["count"]; $i++) {
+ $record = new nameRecord();
+ $record_data = $font->unpack(nameRecord::$format);
+ $record->map($record_data);
+
+ $records[] = $record;
+ }
+
+ $names = array();
+ foreach ($records as $record) {
+ $font->seek($tableOffset + $data["stringOffset"] + $record->offset);
+ $s = $font->read($record->length);
+ $record->string = Font::UTF16ToUTF8($s);
+ $names[$record->nameID] = $record;
+ }
+
+ $data["records"] = $names;
+
+ $this->data = $data;
+ }
+
+ protected function _encode() {
+ $font = $this->getFont();
+
+ /** @var nameRecord[] $records */
+ $records = $this->data["records"];
+ $count_records = count($records);
+
+ $this->data["count"] = $count_records;
+ $this->data["stringOffset"] = 6 + $count_records * 12; // 6 => uint16 * 3, 12 => sizeof self::$record_format
+
+ $length = $font->pack(self::$header_format, $this->data);
+
+ $offset = 0;
+ foreach ($records as $record) {
+ $record->length = mb_strlen($record->getUTF16(), "8bit");
+ $record->offset = $offset;
+ $offset += $record->length;
+ $length += $font->pack(nameRecord::$format, (array)$record);
+ }
+
+ foreach ($records as $record) {
+ $str = $record->getUTF16();
+ $length += $font->write($str, mb_strlen($str, "8bit"));
+ }
+
+ return $length;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/nameRecord.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/nameRecord.php
new file mode 100644
index 0000000..2073c20
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/nameRecord.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+namespace FontLib\Table\Type;
+
+use FontLib\Font;
+use FontLib\BinaryStream;
+
+/**
+ * Font table name record.
+ *
+ * @package php-font-lib
+ */
+class nameRecord extends BinaryStream {
+ public $platformID;
+ public $platformSpecificID;
+ public $languageID;
+ public $nameID;
+ public $length;
+ public $offset;
+ public $string;
+
+ public static $format = array(
+ "platformID" => self::uint16,
+ "platformSpecificID" => self::uint16,
+ "languageID" => self::uint16,
+ "nameID" => self::uint16,
+ "length" => self::uint16,
+ "offset" => self::uint16,
+ );
+
+ public function map($data) {
+ foreach ($data as $key => $value) {
+ $this->$key = $value;
+ }
+ }
+
+ public function getUTF8() {
+ return $this->string;
+ }
+
+ public function getUTF16() {
+ return Font::UTF8ToUTF16($this->string);
+ }
+
+ function __toString() {
+ return $this->string;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/os2.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/os2.php
new file mode 100644
index 0000000..19a3e21
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/os2.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+
+/**
+ * `OS/2` font table.
+ *
+ * @package php-font-lib
+ */
+class os2 extends Table {
+ protected $def = array(
+ "version" => self::uint16,
+ "xAvgCharWidth" => self::int16,
+ "usWeightClass" => self::uint16,
+ "usWidthClass" => self::uint16,
+ "fsType" => self::int16,
+ "ySubscriptXSize" => self::int16,
+ "ySubscriptYSize" => self::int16,
+ "ySubscriptXOffset" => self::int16,
+ "ySubscriptYOffset" => self::int16,
+ "ySuperscriptXSize" => self::int16,
+ "ySuperscriptYSize" => self::int16,
+ "ySuperscriptXOffset" => self::int16,
+ "ySuperscriptYOffset" => self::int16,
+ "yStrikeoutSize" => self::int16,
+ "yStrikeoutPosition" => self::int16,
+ "sFamilyClass" => self::int16,
+ "panose" => array(self::uint8, 10),
+ "ulCharRange" => array(self::uint32, 4),
+ "achVendID" => array(self::char, 4),
+ "fsSelection" => self::uint16,
+ "fsFirstCharIndex" => self::uint16,
+ "fsLastCharIndex" => self::uint16,
+ "typoAscender" => self::int16,
+ "typoDescender" => self::int16,
+ "typoLineGap" => self::int16,
+ "winAscent" => self::int16,
+ "winDescent" => self::int16,
+ );
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/post.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/post.php
new file mode 100644
index 0000000..030a942
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/Table/Type/post.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\Table\Type;
+use FontLib\Table\Table;
+use FontLib\TrueType\File;
+
+/**
+ * `post` font table.
+ *
+ * @package php-font-lib
+ */
+class post extends Table {
+ protected $def = array(
+ "format" => self::Fixed,
+ "italicAngle" => self::Fixed,
+ "underlinePosition" => self::FWord,
+ "underlineThickness" => self::FWord,
+ "isFixedPitch" => self::uint32,
+ "minMemType42" => self::uint32,
+ "maxMemType42" => self::uint32,
+ "minMemType1" => self::uint32,
+ "maxMemType1" => self::uint32,
+ );
+
+ protected function _parse() {
+ $font = $this->getFont();
+ $data = $font->unpack($this->def);
+
+ $names = array();
+
+ switch ($data["format"]) {
+ case 1:
+ $names = File::$macCharNames;
+ break;
+
+ case 2:
+ $data["numberOfGlyphs"] = $font->readUInt16();
+
+ $glyphNameIndex = $font->readUInt16Many($data["numberOfGlyphs"]);
+
+ $data["glyphNameIndex"] = $glyphNameIndex;
+
+ $namesPascal = array();
+ for ($i = 0; $i < $data["numberOfGlyphs"]; $i++) {
+ $len = $font->readUInt8();
+ $namesPascal[] = $font->read($len);
+ }
+
+ foreach ($glyphNameIndex as $g => $index) {
+ if ($index < 258) {
+ $names[$g] = File::$macCharNames[$index];
+ }
+ else {
+ if (array_key_exists($index - 258, $namesPascal)) {
+ $names[$g] = $namesPascal[$index - 258];
+ }
+ }
+ }
+
+ break;
+
+ case 2.5:
+ // TODO
+ break;
+
+ case 3:
+ // nothing
+ break;
+
+ case 4:
+ // TODO
+ break;
+ }
+
+ $data["names"] = $names;
+
+ $this->data = $data;
+ }
+
+ function _encode() {
+ $font = $this->getFont();
+ $data = $this->data;
+ $data["format"] = 3;
+
+ $length = $font->pack($this->def, $data);
+
+ return $length;
+ /*
+ $subset = $font->getSubset();
+
+ switch($data["format"]) {
+ case 1:
+ // nothing to do
+ break;
+
+ case 2:
+ $old_names = $data["names"];
+
+ $glyphNameIndex = range(0, count($subset));
+
+ $names = array();
+ foreach($subset as $gid) {
+ $names[] = $data["names"][$data["glyphNameIndex"][$gid]];
+ }
+
+ $numberOfGlyphs = count($names);
+ $length += $font->writeUInt16($numberOfGlyphs);
+
+ foreach($glyphNameIndex as $gni) {
+ $length += $font->writeUInt16($gni);
+ }
+
+ //$names = array_slice($names, 257);
+ foreach($names as $name) {
+ $len = strlen($name);
+ $length += $font->writeUInt8($len);
+ $length += $font->write($name, $len);
+ }
+
+ break;
+
+ case 2.5:
+ // TODO
+ break;
+
+ case 3:
+ // nothing
+ break;
+
+ case 4:
+ // TODO
+ break;
+ }
+
+ return $length;*/
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Collection.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Collection.php
new file mode 100644
index 0000000..460ef4d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Collection.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\TrueType;
+
+use Countable;
+use FontLib\BinaryStream;
+use Iterator;
+use OutOfBoundsException;
+
+/**
+ * TrueType collection font file.
+ *
+ * @package php-font-lib
+ */
+class Collection extends BinaryStream implements Iterator, Countable {
+ /**
+ * Current iterator position.
+ *
+ * @var integer
+ */
+ private $position = 0;
+
+ protected $collectionOffsets = array();
+ protected $collection = array();
+ protected $version;
+ protected $numFonts;
+
+ function parse() {
+ if (isset($this->numFonts)) {
+ return;
+ }
+
+ $this->read(4); // tag name
+
+ $this->version = $this->readFixed();
+ $this->numFonts = $this->readUInt32();
+
+ for ($i = 0; $i < $this->numFonts; $i++) {
+ $this->collectionOffsets[] = $this->readUInt32();
+ }
+ }
+
+ /**
+ * @param int $fontId
+ *
+ * @throws OutOfBoundsException
+ * @return File
+ */
+ function getFont($fontId) {
+ $this->parse();
+
+ if (!isset($this->collectionOffsets[$fontId])) {
+ throw new OutOfBoundsException();
+ }
+
+ if (isset($this->collection[$fontId])) {
+ return $this->collection[$fontId];
+ }
+
+ $font = new File();
+ $font->f = $this->f;
+ $font->setTableOffset($this->collectionOffsets[$fontId]);
+
+ return $this->collection[$fontId] = $font;
+ }
+
+ function current() {
+ return $this->getFont($this->position);
+ }
+
+ function key() {
+ return $this->position;
+ }
+
+ function next() {
+ return ++$this->position;
+ }
+
+ function rewind() {
+ $this->position = 0;
+ }
+
+ function valid() {
+ $this->parse();
+
+ return isset($this->collectionOffsets[$this->position]);
+ }
+
+ function count() {
+ $this->parse();
+
+ return $this->numFonts;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/File.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/File.php
new file mode 100644
index 0000000..3594479
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/File.php
@@ -0,0 +1,471 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\TrueType;
+
+use FontLib\AdobeFontMetrics;
+use FontLib\Font;
+use FontLib\BinaryStream;
+use FontLib\Table\Table;
+use FontLib\Table\DirectoryEntry;
+use FontLib\Table\Type\glyf;
+use FontLib\Table\Type\name;
+use FontLib\Table\Type\nameRecord;
+
+/**
+ * TrueType font file.
+ *
+ * @package php-font-lib
+ */
+class File extends BinaryStream {
+ /**
+ * @var Header
+ */
+ public $header = array();
+
+ private $tableOffset = 0; // Used for TTC
+
+ private static $raw = false;
+
+ protected $directory = array();
+ protected $data = array();
+
+ protected $glyph_subset = array();
+
+ public $glyph_all = array();
+
+ static $macCharNames = array(
+ ".notdef", ".null", "CR",
+ "space", "exclam", "quotedbl", "numbersign",
+ "dollar", "percent", "ampersand", "quotesingle",
+ "parenleft", "parenright", "asterisk", "plus",
+ "comma", "hyphen", "period", "slash",
+ "zero", "one", "two", "three",
+ "four", "five", "six", "seven",
+ "eight", "nine", "colon", "semicolon",
+ "less", "equal", "greater", "question",
+ "at", "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", "bracketleft",
+ "backslash", "bracketright", "asciicircum", "underscore",
+ "grave", "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", "braceleft",
+ "bar", "braceright", "asciitilde", "Adieresis",
+ "Aring", "Ccedilla", "Eacute", "Ntilde",
+ "Odieresis", "Udieresis", "aacute", "agrave",
+ "acircumflex", "adieresis", "atilde", "aring",
+ "ccedilla", "eacute", "egrave", "ecircumflex",
+ "edieresis", "iacute", "igrave", "icircumflex",
+ "idieresis", "ntilde", "oacute", "ograve",
+ "ocircumflex", "odieresis", "otilde", "uacute",
+ "ugrave", "ucircumflex", "udieresis", "dagger",
+ "degree", "cent", "sterling", "section",
+ "bullet", "paragraph", "germandbls", "registered",
+ "copyright", "trademark", "acute", "dieresis",
+ "notequal", "AE", "Oslash", "infinity",
+ "plusminus", "lessequal", "greaterequal", "yen",
+ "mu", "partialdiff", "summation", "product",
+ "pi", "integral", "ordfeminine", "ordmasculine",
+ "Omega", "ae", "oslash", "questiondown",
+ "exclamdown", "logicalnot", "radical", "florin",
+ "approxequal", "increment", "guillemotleft", "guillemotright",
+ "ellipsis", "nbspace", "Agrave", "Atilde",
+ "Otilde", "OE", "oe", "endash",
+ "emdash", "quotedblleft", "quotedblright", "quoteleft",
+ "quoteright", "divide", "lozenge", "ydieresis",
+ "Ydieresis", "fraction", "currency", "guilsinglleft",
+ "guilsinglright", "fi", "fl", "daggerdbl",
+ "periodcentered", "quotesinglbase", "quotedblbase", "perthousand",
+ "Acircumflex", "Ecircumflex", "Aacute", "Edieresis",
+ "Egrave", "Iacute", "Icircumflex", "Idieresis",
+ "Igrave", "Oacute", "Ocircumflex", "applelogo",
+ "Ograve", "Uacute", "Ucircumflex", "Ugrave",
+ "dotlessi", "circumflex", "tilde", "macron",
+ "breve", "dotaccent", "ring", "cedilla",
+ "hungarumlaut", "ogonek", "caron", "Lslash",
+ "lslash", "Scaron", "scaron", "Zcaron",
+ "zcaron", "brokenbar", "Eth", "eth",
+ "Yacute", "yacute", "Thorn", "thorn",
+ "minus", "multiply", "onesuperior", "twosuperior",
+ "threesuperior", "onehalf", "onequarter", "threequarters",
+ "franc", "Gbreve", "gbreve", "Idot",
+ "Scedilla", "scedilla", "Cacute", "cacute",
+ "Ccaron", "ccaron", "dmacron"
+ );
+
+ function getTable() {
+ $this->parseTableEntries();
+
+ return $this->directory;
+ }
+
+ function setTableOffset($offset) {
+ $this->tableOffset = $offset;
+ }
+
+ function parse() {
+ $this->parseTableEntries();
+
+ $this->data = array();
+
+ foreach ($this->directory as $tag => $table) {
+ if (empty($this->data[$tag])) {
+ $this->readTable($tag);
+ }
+ }
+ }
+
+ function utf8toUnicode($str) {
+ $len = mb_strlen($str, '8bit');
+ $out = array();
+
+ for ($i = 0; $i < $len; $i++) {
+ $uni = -1;
+ $h = ord($str[$i]);
+
+ if ($h <= 0x7F) {
+ $uni = $h;
+ }
+ elseif ($h >= 0xC2) {
+ if (($h <= 0xDF) && ($i < $len - 1)) {
+ $uni = ($h & 0x1F) << 6 | (ord($str[++$i]) & 0x3F);
+ }
+ elseif (($h <= 0xEF) && ($i < $len - 2)) {
+ $uni = ($h & 0x0F) << 12 | (ord($str[++$i]) & 0x3F) << 6 | (ord($str[++$i]) & 0x3F);
+ }
+ elseif (($h <= 0xF4) && ($i < $len - 3)) {
+ $uni = ($h & 0x0F) << 18 | (ord($str[++$i]) & 0x3F) << 12 | (ord($str[++$i]) & 0x3F) << 6 | (ord($str[++$i]) & 0x3F);
+ }
+ }
+
+ if ($uni >= 0) {
+ $out[] = $uni;
+ }
+ }
+
+ return $out;
+ }
+
+ function getUnicodeCharMap() {
+ $subtable = null;
+ foreach ($this->getData("cmap", "subtables") as $_subtable) {
+ if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
+ $subtable = $_subtable;
+ break;
+ }
+ }
+
+ if ($subtable) {
+ return $subtable["glyphIndexArray"];
+ }
+
+ return null;
+ }
+
+ function setSubset($subset) {
+ if (!is_array($subset)) {
+ $subset = $this->utf8toUnicode($subset);
+ }
+
+ $subset = array_unique($subset);
+
+ $glyphIndexArray = $this->getUnicodeCharMap();
+
+ if (!$glyphIndexArray) {
+ return;
+ }
+
+ $gids = array(
+ 0, // .notdef
+ 1, // .null
+ );
+
+ foreach ($subset as $code) {
+ if (!isset($glyphIndexArray[$code])) {
+ continue;
+ }
+
+ $gid = $glyphIndexArray[$code];
+ $gids[$gid] = $gid;
+ }
+
+ /** @var glyf $glyf */
+ $glyf = $this->getTableObject("glyf");
+ $gids = $glyf->getGlyphIDs($gids);
+
+ sort($gids);
+
+ $this->glyph_subset = $gids;
+ $this->glyph_all = array_values($glyphIndexArray); // FIXME
+ }
+
+ function getSubset() {
+ if (empty($this->glyph_subset)) {
+ return $this->glyph_all;
+ }
+
+ return $this->glyph_subset;
+ }
+
+ function encode($tags = array()) {
+ if (!self::$raw) {
+ $tags = array_merge(array("head", "hhea", "cmap", "hmtx", "maxp", "glyf", "loca", "name", "post"), $tags);
+ }
+ else {
+ $tags = array_keys($this->directory);
+ }
+
+ $num_tables = count($tags);
+ $n = 16; // @todo
+
+ Font::d("Tables : " . implode(", ", $tags));
+
+ /** @var DirectoryEntry[] $entries */
+ $entries = array();
+ foreach ($tags as $tag) {
+ if (!isset($this->directory[$tag])) {
+ Font::d(" >> '$tag' table doesn't exist");
+ continue;
+ }
+
+ $entries[$tag] = $this->directory[$tag];
+ }
+
+ $this->header->data["numTables"] = $num_tables;
+ $this->header->encode();
+
+ $directory_offset = $this->pos();
+ $offset = $directory_offset + $num_tables * $n;
+ $this->seek($offset);
+
+ $i = 0;
+ foreach ($entries as $entry) {
+ $entry->encode($directory_offset + $i * $n);
+ $i++;
+ }
+ }
+
+ function parseHeader() {
+ if (!empty($this->header)) {
+ return;
+ }
+
+ $this->seek($this->tableOffset);
+
+ $this->header = new Header($this);
+ $this->header->parse();
+ }
+
+ function getFontType(){
+ $class_parts = explode("\\", get_class($this));
+ return $class_parts[1];
+ }
+
+ function parseTableEntries() {
+ $this->parseHeader();
+
+ if (!empty($this->directory)) {
+ return;
+ }
+
+ if (empty($this->header->data["numTables"])) {
+ return;
+ }
+
+
+ $type = $this->getFontType();
+ $class = "FontLib\\$type\\TableDirectoryEntry";
+
+ for ($i = 0; $i < $this->header->data["numTables"]; $i++) {
+ /** @var TableDirectoryEntry $entry */
+ $entry = new $class($this);
+ $entry->parse();
+
+ $this->directory[$entry->tag] = $entry;
+ }
+ }
+
+ function normalizeFUnit($value, $base = 1000) {
+ return round($value * ($base / $this->getData("head", "unitsPerEm")));
+ }
+
+ protected function readTable($tag) {
+ $this->parseTableEntries();
+
+ if (!self::$raw) {
+ $name_canon = preg_replace("/[^a-z0-9]/", "", strtolower($tag));
+
+ $class = "FontLib\\Table\\Type\\$name_canon";
+
+ if (!isset($this->directory[$tag]) || !@class_exists($class)) {
+ return;
+ }
+ }
+ else {
+ $class = "FontLib\\Table\\Table";
+ }
+
+ /** @var Table $table */
+ $table = new $class($this->directory[$tag]);
+ $table->parse();
+
+ $this->data[$tag] = $table;
+ }
+
+ /**
+ * @param $name
+ *
+ * @return Table
+ */
+ public function getTableObject($name) {
+ return $this->data[$name];
+ }
+
+ public function setTableObject($name, Table $data) {
+ $this->data[$name] = $data;
+ }
+
+ public function getData($name, $key = null) {
+ $this->parseTableEntries();
+
+ if (empty($this->data[$name])) {
+ $this->readTable($name);
+ }
+
+ if (!isset($this->data[$name])) {
+ return null;
+ }
+
+ if (!$key) {
+ return $this->data[$name]->data;
+ }
+ else {
+ return $this->data[$name]->data[$key];
+ }
+ }
+
+ function addDirectoryEntry(DirectoryEntry $entry) {
+ $this->directory[$entry->tag] = $entry;
+ }
+
+ function saveAdobeFontMetrics($file, $encoding = null) {
+ $afm = new AdobeFontMetrics($this);
+ $afm->write($file, $encoding);
+ }
+
+ /**
+ * Get a specific name table string value from its ID
+ *
+ * @param int $nameID The name ID
+ *
+ * @return string|null
+ */
+ function getNameTableString($nameID) {
+ /** @var nameRecord[] $records */
+ $records = $this->getData("name", "records");
+
+ if (!isset($records[$nameID])) {
+ return null;
+ }
+
+ return $records[$nameID]->string;
+ }
+
+ /**
+ * Get font copyright
+ *
+ * @return string|null
+ */
+ function getFontCopyright() {
+ return $this->getNameTableString(name::NAME_COPYRIGHT);
+ }
+
+ /**
+ * Get font name
+ *
+ * @return string|null
+ */
+ function getFontName() {
+ return $this->getNameTableString(name::NAME_NAME);
+ }
+
+ /**
+ * Get font subfamily
+ *
+ * @return string|null
+ */
+ function getFontSubfamily() {
+ return $this->getNameTableString(name::NAME_SUBFAMILY);
+ }
+
+ /**
+ * Get font subfamily ID
+ *
+ * @return string|null
+ */
+ function getFontSubfamilyID() {
+ return $this->getNameTableString(name::NAME_SUBFAMILY_ID);
+ }
+
+ /**
+ * Get font full name
+ *
+ * @return string|null
+ */
+ function getFontFullName() {
+ return $this->getNameTableString(name::NAME_FULL_NAME);
+ }
+
+ /**
+ * Get font version
+ *
+ * @return string|null
+ */
+ function getFontVersion() {
+ return $this->getNameTableString(name::NAME_VERSION);
+ }
+
+ /**
+ * Get font weight
+ *
+ * @return string|null
+ */
+ function getFontWeight() {
+ return $this->getTableObject("OS/2")->data["usWeightClass"];
+ }
+
+ /**
+ * Get font Postscript name
+ *
+ * @return string|null
+ */
+ function getFontPostscriptName() {
+ return $this->getNameTableString(name::NAME_POSTSCRIPT_NAME);
+ }
+
+ function reduce() {
+ $names_to_keep = array(
+ name::NAME_COPYRIGHT,
+ name::NAME_NAME,
+ name::NAME_SUBFAMILY,
+ name::NAME_SUBFAMILY_ID,
+ name::NAME_FULL_NAME,
+ name::NAME_VERSION,
+ name::NAME_POSTSCRIPT_NAME,
+ );
+
+ foreach ($this->data["name"]->data["records"] as $id => $rec) {
+ if (!in_array($id, $names_to_keep)) {
+ unset($this->data["name"]->data["records"][$id]);
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Header.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Header.php
new file mode 100644
index 0000000..7ff79cc
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/Header.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\TrueType;
+
+/**
+ * TrueType font file header.
+ *
+ * @package php-font-lib
+ */
+class Header extends \FontLib\Header {
+ protected $def = array(
+ "format" => self::uint32,
+ "numTables" => self::uint16,
+ "searchRange" => self::uint16,
+ "entrySelector" => self::uint16,
+ "rangeShift" => self::uint16,
+ );
+
+ public function parse() {
+ parent::parse();
+
+ $format = $this->data["format"];
+ $this->data["formatText"] = $this->convertUInt32ToStr($format);
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/TableDirectoryEntry.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/TableDirectoryEntry.php
new file mode 100644
index 0000000..fc4fe55
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/TrueType/TableDirectoryEntry.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\TrueType;
+
+use FontLib\Table\DirectoryEntry;
+
+/**
+ * TrueType table directory entry.
+ *
+ * @package php-font-lib
+ */
+class TableDirectoryEntry extends DirectoryEntry {
+ function __construct(File $font) {
+ parent::__construct($font);
+ }
+
+ function parse() {
+ parent::parse();
+
+ $font = $this->font;
+ $this->checksum = $font->readUInt32();
+ $this->offset = $font->readUInt32();
+ $this->length = $font->readUInt32();
+ $this->entryLength += 12;
+ }
+}
+
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/File.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/File.php
new file mode 100644
index 0000000..4668c23
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/File.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\WOFF;
+
+use FontLib\Table\DirectoryEntry;
+
+/**
+ * WOFF font file.
+ *
+ * @package php-font-lib
+ *
+ * @property TableDirectoryEntry[] $directory
+ */
+class File extends \FontLib\TrueType\File {
+ function parseHeader() {
+ if (!empty($this->header)) {
+ return;
+ }
+
+ $this->header = new Header($this);
+ $this->header->parse();
+ }
+
+ public function load($file) {
+ parent::load($file);
+
+ $this->parseTableEntries();
+ $dataOffset = $this->pos() + count($this->directory) * 20;
+
+ $fw = $this->getTempFile(false);
+ $fr = $this->f;
+
+ $this->f = $fw;
+ $offset = $this->header->encode();
+
+ foreach ($this->directory as $entry) {
+ // Read ...
+ $this->f = $fr;
+ $this->seek($entry->offset);
+ $data = $this->read($entry->length);
+
+ if ($entry->length < $entry->origLength) {
+ $data = (string) gzuncompress($data);
+ }
+
+ // Prepare data ...
+ $length = mb_strlen($data, '8bit');
+ $entry->length = $entry->origLength = $length;
+ $entry->offset = $dataOffset;
+
+ // Write ...
+ $this->f = $fw;
+
+ // Woff Entry
+ $this->seek($offset);
+ $offset += $this->write($entry->tag, 4); // tag
+ $offset += $this->writeUInt32($dataOffset); // offset
+ $offset += $this->writeUInt32($length); // length
+ $offset += $this->writeUInt32($length); // origLength
+ $offset += $this->writeUInt32(DirectoryEntry::computeChecksum($data)); // checksum
+
+ // Data
+ $this->seek($dataOffset);
+ $dataOffset += $this->write($data, $length);
+ }
+
+ $this->f = $fw;
+ $this->seek(0);
+
+ // Need to re-parse this, don't know why
+ $this->header = null;
+ $this->directory = array();
+ $this->parseTableEntries();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/Header.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/Header.php
new file mode 100644
index 0000000..65a6f14
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/Header.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\WOFF;
+
+/**
+ * WOFF font file header.
+ *
+ * @package php-font-lib
+ */
+class Header extends \FontLib\TrueType\Header {
+ protected $def = array(
+ "format" => self::uint32,
+ "flavor" => self::uint32,
+ "length" => self::uint32,
+ "numTables" => self::uint16,
+ self::uint16,
+ "totalSfntSize" => self::uint32,
+ "majorVersion" => self::uint16,
+ "minorVersion" => self::uint16,
+ "metaOffset" => self::uint32,
+ "metaLength" => self::uint32,
+ "metaOrigLength" => self::uint32,
+ "privOffset" => self::uint32,
+ "privLength" => self::uint32,
+ );
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/TableDirectoryEntry.php b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/TableDirectoryEntry.php
new file mode 100644
index 0000000..eb67c9c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-font-lib/src/FontLib/WOFF/TableDirectoryEntry.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @package php-font-lib
+ * @link https://github.com/PhenX/php-font-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
+ */
+
+namespace FontLib\WOFF;
+
+use FontLib\Table\DirectoryEntry;
+
+/**
+ * WOFF font file table directory entry.
+ *
+ * @package php-font-lib
+ */
+class TableDirectoryEntry extends DirectoryEntry {
+ public $origLength;
+
+ function __construct(File $font) {
+ parent::__construct($font);
+ }
+
+ function parse() {
+ parent::parse();
+
+ $font = $this->font;
+ $this->offset = $font->readUInt32();
+ $this->length = $font->readUInt32();
+ $this->origLength = $font->readUInt32();
+ $this->checksum = $font->readUInt32();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/LICENSE b/library/vendor/dompdf/vendor/phenx/php-svg-lib/LICENSE
new file mode 100644
index 0000000..0a04128
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ 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 that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU 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 as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/README.md b/library/vendor/dompdf/vendor/phenx/php-svg-lib/README.md
new file mode 100644
index 0000000..2b8e6f6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/README.md
@@ -0,0 +1,13 @@
+# SVG file parsing / rendering library
+
+[![Build Status](https://github.com/phenx/php-svg-lib/workflows/test/badge.svg)](https://github.com/phenx/php-svg-lib/actions)
+
+
+[![Latest Stable Version](https://poser.pugx.org/phenx/php-svg-lib/v/stable)](https://packagist.org/packages/phenx/php-svg-lib)
+[![Total Downloads](https://poser.pugx.org/phenx/php-svg-lib/downloads)](https://packagist.org/packages/phenx/php-svg-lib)
+[![Latest Unstable Version](https://poser.pugx.org/phenx/php-svg-lib/v/unstable)](https://packagist.org/packages/phenx/php-svg-lib)
+[![License](https://poser.pugx.org/phenx/php-svg-lib/license)](https://packagist.org/packages/phenx/php-svg-lib)
+
+The main purpose of this lib is to rasterize SVG to a surface which can be an image or a PDF for example, through a `\Svg\Surface` PHP interface.
+
+This project was initialized by the need to render SVG documents inside PDF files for the [DomPdf](http://dompdf.github.io) project.
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/composer.json b/library/vendor/dompdf/vendor/phenx/php-svg-lib/composer.json
new file mode 100644
index 0000000..a6ed9c5
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "phenx/php-svg-lib",
+ "type": "library",
+ "description": "A library to read, parse and export to PDF SVG files.",
+ "homepage": "https://github.com/PhenX/php-svg-lib",
+ "license": "LGPL-3.0",
+ "authors": [
+ {
+ "name": "Fabien Ménager",
+ "email": "fabien.menager@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Svg\\": "src/Svg"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Svg\\Tests\\": "tests/Svg"
+ }
+ },
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "ext-mbstring": "*",
+ "sabberworm/php-css-parser": "^8.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/CssLength.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/CssLength.php
new file mode 100644
index 0000000..88eda8c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/CssLength.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Svg;
+
+class CssLength
+{
+ /**
+ * Array of valid css length units.
+ * Should be pre-sorted by unit text length so no earlier length can be
+ * contained within a latter (eg. 'in' within 'vmin').
+ *
+ * @var string[]
+ */
+ protected static $units = [
+ 'vmax',
+ 'vmin',
+ 'rem',
+ 'px',
+ 'pt',
+ 'cm',
+ 'mm',
+ 'in',
+ 'pc',
+ 'em',
+ 'ex',
+ 'ch',
+ 'vw',
+ 'vh',
+ '%',
+ 'q',
+ ];
+
+ /**
+ * A list of units that are inch-relative, and their unit division within an inch.
+ *
+ * @var array<string, float>
+ */
+ protected static $inchDivisions = [
+ 'in' => 1,
+ 'cm' => 2.54,
+ 'mm' => 25.4,
+ 'q' => 101.6,
+ 'pc' => 6,
+ 'pt' => 72,
+ ];
+
+ /**
+ * The CSS length unit indicator.
+ * Will be lower-case and one of the units listed in the '$units' array or empty.
+ *
+ * @var string
+ */
+ protected $unit = '';
+
+ /**
+ * The numeric value of the given length.
+ *
+ * @var float
+ */
+ protected $value = 0;
+
+ /**
+ * The original unparsed length provided.
+ *
+ * @var string
+ */
+ protected $unparsed;
+
+ public function __construct(string $length)
+ {
+ $this->unparsed = $length;
+ $this->parseLengthComponents($length);
+ }
+
+ /**
+ * Parse out the unit and value components from the given string length.
+ */
+ protected function parseLengthComponents(string $length): void
+ {
+ $length = strtolower($length);
+
+ foreach (self::$units as $unit) {
+ $pos = strpos($length, $unit);
+ if ($pos) {
+ $this->value = floatval(substr($length, 0, $pos));
+ $this->unit = $unit;
+ return;
+ }
+ }
+
+ $this->unit = '';
+ $this->value = floatval($length);
+ }
+
+ /**
+ * Get the unit type of this css length.
+ * Units are standardised to be lower-cased.
+ *
+ * @return string
+ */
+ public function getUnit(): string
+ {
+ return $this->unit;
+ }
+
+ /**
+ * Get this CSS length in the equivalent pixel count size.
+ *
+ * @param float $referenceSize
+ * @param float $dpi
+ *
+ * @return float
+ */
+ public function toPixels(float $referenceSize = 11.0, float $dpi = 96.0): float
+ {
+ // Standard relative units
+ if (in_array($this->unit, ['em', 'rem', 'ex', 'ch'])) {
+ return $this->value * $referenceSize;
+ }
+
+ // Percentage relative units
+ if (in_array($this->unit, ['%', 'vw', 'vh', 'vmin', 'vmax'])) {
+ return $this->value * ($referenceSize / 100);
+ }
+
+ // Inch relative units
+ if (in_array($this->unit, array_keys(static::$inchDivisions))) {
+ $inchValue = $this->value * $dpi;
+ $division = static::$inchDivisions[$this->unit];
+ return $inchValue / $division;
+ }
+
+ return $this->value;
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/DefaultStyle.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/DefaultStyle.php
new file mode 100644
index 0000000..4e73d29
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/DefaultStyle.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg;
+
+class DefaultStyle extends Style
+{
+ public $color = [0, 0, 0, 1];
+ public $opacity = 1.0;
+ public $display = 'inline';
+
+ public $fill = [0, 0, 0, 1];
+ public $fillOpacity = 1.0;
+ public $fillRule = 'nonzero';
+
+ public $stroke = 'none';
+ public $strokeOpacity = 1.0;
+ public $strokeLinecap = 'butt';
+ public $strokeLinejoin = 'miter';
+ public $strokeMiterlimit = 4;
+ public $strokeWidth = 1.0;
+ public $strokeDasharray = 0;
+ public $strokeDashoffset = 0;
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Document.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Document.php
new file mode 100644
index 0000000..4de226e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Document.php
@@ -0,0 +1,406 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg;
+
+use Svg\Surface\SurfaceInterface;
+use Svg\Tag\AbstractTag;
+use Svg\Tag\Anchor;
+use Svg\Tag\Circle;
+use Svg\Tag\Ellipse;
+use Svg\Tag\Group;
+use Svg\Tag\ClipPath;
+use Svg\Tag\Image;
+use Svg\Tag\Line;
+use Svg\Tag\LinearGradient;
+use Svg\Tag\Path;
+use Svg\Tag\Polygon;
+use Svg\Tag\Polyline;
+use Svg\Tag\Rect;
+use Svg\Tag\Stop;
+use Svg\Tag\Text;
+use Svg\Tag\StyleTag;
+use Svg\Tag\UseTag;
+
+class Document extends AbstractTag
+{
+ protected $filename;
+ public $inDefs = false;
+
+ protected $x;
+ protected $y;
+ protected $width;
+ protected $height;
+
+ protected $subPathInit;
+ protected $pathBBox;
+ protected $viewBox;
+
+ /** @var SurfaceInterface */
+ protected $surface;
+
+ /** @var AbstractTag[] */
+ protected $stack = array();
+
+ /** @var AbstractTag[] */
+ protected $defs = array();
+
+ /** @var \Sabberworm\CSS\CSSList\Document[] */
+ protected $styleSheets = array();
+
+ public function loadFile($filename)
+ {
+ $this->filename = $filename;
+ }
+
+ protected function initParser() {
+ $parser = xml_parser_create("utf-8");
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
+ xml_set_element_handler(
+ $parser,
+ array($this, "_tagStart"),
+ array($this, "_tagEnd")
+ );
+ xml_set_character_data_handler(
+ $parser,
+ array($this, "_charData")
+ );
+
+ return $parser;
+ }
+
+ public function __construct() {
+
+ }
+
+ /**
+ * @return SurfaceInterface
+ */
+ public function getSurface()
+ {
+ return $this->surface;
+ }
+
+ public function getStack()
+ {
+ return $this->stack;
+ }
+
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ public function getDiagonal()
+ {
+ return sqrt(($this->width)**2 + ($this->height)**2) / sqrt(2);
+ }
+
+ public function getDimensions() {
+ $rootAttributes = null;
+
+ $parser = xml_parser_create("utf-8");
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
+ xml_set_element_handler(
+ $parser,
+ function ($parser, $name, $attributes) use (&$rootAttributes) {
+ if ($name === "svg" && $rootAttributes === null) {
+ $attributes = array_change_key_case($attributes, CASE_LOWER);
+
+ $rootAttributes = $attributes;
+ }
+ },
+ function ($parser, $name) {}
+ );
+
+ $fp = fopen($this->filename, "r");
+ while ($line = fread($fp, 8192)) {
+ xml_parse($parser, $line, false);
+
+ if ($rootAttributes !== null) {
+ break;
+ }
+ }
+
+ xml_parser_free($parser);
+
+ return $this->handleSizeAttributes($rootAttributes);
+ }
+
+ public function handleSizeAttributes($attributes){
+ if ($this->width === null) {
+ if (isset($attributes["width"])) {
+ $width = $this->convertSize($attributes["width"], 400);
+ $this->width = $width;
+ }
+
+ if (isset($attributes["height"])) {
+ $height = $this->convertSize($attributes["height"], 300);
+ $this->height = $height;
+ }
+
+ if (isset($attributes['viewbox'])) {
+ $viewBox = preg_split('/[\s,]+/is', trim($attributes['viewbox']));
+ if (count($viewBox) == 4) {
+ $this->x = $viewBox[0];
+ $this->y = $viewBox[1];
+
+ if (!$this->width) {
+ $this->width = $viewBox[2];
+ }
+ if (!$this->height) {
+ $this->height = $viewBox[3];
+ }
+ }
+ }
+ }
+
+ return array(
+ 0 => $this->width,
+ 1 => $this->height,
+
+ "width" => $this->width,
+ "height" => $this->height,
+ );
+ }
+
+ public function getDocument(){
+ return $this;
+ }
+
+ /**
+ * Append a style sheet
+ *
+ * @param \Sabberworm\CSS\CSSList\Document $stylesheet
+ */
+ public function appendStyleSheet($stylesheet) {
+ $this->styleSheets[] = $stylesheet;
+ }
+
+ /**
+ * Get the document style sheets
+ *
+ * @return \Sabberworm\CSS\CSSList\Document[]
+ */
+ public function getStyleSheets() {
+ return $this->styleSheets;
+ }
+
+ protected function before($attributes)
+ {
+ $surface = $this->getSurface();
+
+ $style = new DefaultStyle();
+ $style->inherit($this);
+ $style->fromAttributes($attributes);
+
+ $this->setStyle($style);
+
+ $surface->setStyle($style);
+ }
+
+ public function render(SurfaceInterface $surface)
+ {
+ $this->inDefs = false;
+ $this->surface = $surface;
+
+ $parser = $this->initParser();
+
+ if ($this->x || $this->y) {
+ $surface->translate(-$this->x, -$this->y);
+ }
+
+ $fp = fopen($this->filename, "r");
+ while ($line = fread($fp, 8192)) {
+ xml_parse($parser, $line, false);
+ }
+
+ xml_parse($parser, "", true);
+
+ xml_parser_free($parser);
+ }
+
+ protected function svgOffset($attributes)
+ {
+ $this->attributes = $attributes;
+
+ $this->handleSizeAttributes($attributes);
+ }
+
+ public function getDef($id) {
+ $id = ltrim($id, "#");
+
+ return isset($this->defs[$id]) ? $this->defs[$id] : null;
+ }
+
+ private function _tagStart($parser, $name, $attributes)
+ {
+ $this->x = 0;
+ $this->y = 0;
+
+ $tag = null;
+
+ $attributes = array_change_key_case($attributes, CASE_LOWER);
+
+ switch (strtolower($name)) {
+ case 'defs':
+ $this->inDefs = true;
+ return;
+
+ case 'svg':
+ if (count($this->attributes)) {
+ $tag = new Group($this, $name);
+ }
+ else {
+ $tag = $this;
+ $this->svgOffset($attributes);
+ }
+ break;
+
+ case 'path':
+ $tag = new Path($this, $name);
+ break;
+
+ case 'rect':
+ $tag = new Rect($this, $name);
+ break;
+
+ case 'circle':
+ $tag = new Circle($this, $name);
+ break;
+
+ case 'ellipse':
+ $tag = new Ellipse($this, $name);
+ break;
+
+ case 'image':
+ $tag = new Image($this, $name);
+ break;
+
+ case 'line':
+ $tag = new Line($this, $name);
+ break;
+
+ case 'polyline':
+ $tag = new Polyline($this, $name);
+ break;
+
+ case 'polygon':
+ $tag = new Polygon($this, $name);
+ break;
+
+ case 'lineargradient':
+ $tag = new LinearGradient($this, $name);
+ break;
+
+ case 'radialgradient':
+ $tag = new LinearGradient($this, $name);
+ break;
+
+ case 'stop':
+ $tag = new Stop($this, $name);
+ break;
+
+ case 'style':
+ $tag = new StyleTag($this, $name);
+ break;
+
+ case 'a':
+ $tag = new Anchor($this, $name);
+ break;
+
+ case 'g':
+ case 'symbol':
+ $tag = new Group($this, $name);
+ break;
+
+ case 'clippath':
+ $tag = new ClipPath($this, $name);
+ break;
+
+ case 'use':
+ $tag = new UseTag($this, $name);
+ break;
+
+ case 'text':
+ $tag = new Text($this, $name);
+ break;
+
+ case 'desc':
+ return;
+ }
+
+ if ($tag) {
+ if (isset($attributes["id"])) {
+ $this->defs[$attributes["id"]] = $tag;
+ }
+ else {
+ /** @var AbstractTag $top */
+ $top = end($this->stack);
+ if ($top && $top != $tag) {
+ $top->children[] = $tag;
+ }
+ }
+
+ $this->stack[] = $tag;
+
+ $tag->handle($attributes);
+ }
+ }
+
+ function _charData($parser, $data)
+ {
+ $stack_top = end($this->stack);
+
+ if ($stack_top instanceof Text || $stack_top instanceof StyleTag) {
+ $stack_top->appendText($data);
+ }
+ }
+
+ function _tagEnd($parser, $name)
+ {
+ /** @var AbstractTag $tag */
+ $tag = null;
+ switch (strtolower($name)) {
+ case 'defs':
+ $this->inDefs = false;
+ return;
+
+ case 'svg':
+ case 'path':
+ case 'rect':
+ case 'circle':
+ case 'ellipse':
+ case 'image':
+ case 'line':
+ case 'polyline':
+ case 'polygon':
+ case 'radialgradient':
+ case 'lineargradient':
+ case 'stop':
+ case 'style':
+ case 'text':
+ case 'g':
+ case 'symbol':
+ case 'clippath':
+ case 'use':
+ case 'a':
+ $tag = array_pop($this->stack);
+ break;
+ }
+
+ if (!$this->inDefs && $tag) {
+ $tag->handleEnd();
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Gradient/Stop.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Gradient/Stop.php
new file mode 100644
index 0000000..a37fb97
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Gradient/Stop.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Gradient;
+
+class Stop
+{
+ public $offset;
+ public $color;
+ public $opacity = 1.0;
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Style.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Style.php
new file mode 100644
index 0000000..14b11e9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Style.php
@@ -0,0 +1,541 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg;
+
+use Svg\Tag\AbstractTag;
+
+class Style
+{
+ const TYPE_COLOR = 1;
+ const TYPE_LENGTH = 2;
+ const TYPE_NAME = 3;
+ const TYPE_ANGLE = 4;
+ const TYPE_NUMBER = 5;
+
+ private $_parentStyle;
+
+ public $color;
+ public $opacity;
+ public $display;
+
+ public $fill;
+ public $fillOpacity;
+ public $fillRule;
+
+ public $stroke;
+ public $strokeOpacity;
+ public $strokeLinecap;
+ public $strokeLinejoin;
+ public $strokeMiterlimit;
+ public $strokeWidth;
+ public $strokeDasharray;
+ public $strokeDashoffset;
+
+ public $fontFamily = 'serif';
+ public $fontSize = 12;
+ public $fontWeight = 'normal';
+ public $fontStyle = 'normal';
+ public $textAnchor = 'start';
+
+ protected function getStyleMap()
+ {
+ return array(
+ 'color' => array('color', self::TYPE_COLOR),
+ 'opacity' => array('opacity', self::TYPE_NUMBER),
+ 'display' => array('display', self::TYPE_NAME),
+
+ 'fill' => array('fill', self::TYPE_COLOR),
+ 'fill-opacity' => array('fillOpacity', self::TYPE_NUMBER),
+ 'fill-rule' => array('fillRule', self::TYPE_NAME),
+
+ 'stroke' => array('stroke', self::TYPE_COLOR),
+ 'stroke-dasharray' => array('strokeDasharray', self::TYPE_NAME),
+ 'stroke-dashoffset' => array('strokeDashoffset', self::TYPE_NUMBER),
+ 'stroke-linecap' => array('strokeLinecap', self::TYPE_NAME),
+ 'stroke-linejoin' => array('strokeLinejoin', self::TYPE_NAME),
+ 'stroke-miterlimit' => array('strokeMiterlimit', self::TYPE_NUMBER),
+ 'stroke-opacity' => array('strokeOpacity', self::TYPE_NUMBER),
+ 'stroke-width' => array('strokeWidth', self::TYPE_NUMBER),
+
+ 'font-family' => array('fontFamily', self::TYPE_NAME),
+ 'font-size' => array('fontSize', self::TYPE_NUMBER),
+ 'font-weight' => array('fontWeight', self::TYPE_NAME),
+ 'font-style' => array('fontStyle', self::TYPE_NAME),
+ 'text-anchor' => array('textAnchor', self::TYPE_NAME),
+ );
+ }
+
+ /**
+ * @param $attributes
+ *
+ * @return Style
+ */
+ public function fromAttributes($attributes)
+ {
+ $this->fillStyles($attributes);
+
+ if (isset($attributes["style"])) {
+ $styles = self::parseCssStyle($attributes["style"]);
+ $this->fillStyles($styles);
+ }
+ }
+
+ public function inherit(AbstractTag $tag) {
+ $group = $tag->getParentGroup();
+ if ($group) {
+ $parent_style = $group->getStyle();
+ $this->_parentStyle = $parent_style;
+ foreach ($parent_style as $_key => $_value) {
+ if ($_value !== null) {
+ $this->$_key = $_value;
+ }
+ }
+ }
+ }
+
+ public function fromStyleSheets(AbstractTag $tag, $attributes) {
+ $class = isset($attributes["class"]) ? preg_split('/\s+/', trim($attributes["class"])) : null;
+
+ $stylesheets = $tag->getDocument()->getStyleSheets();
+
+ $styles = array();
+
+ foreach ($stylesheets as $_sc) {
+
+ /** @var \Sabberworm\CSS\RuleSet\DeclarationBlock $_decl */
+ foreach ($_sc->getAllDeclarationBlocks() as $_decl) {
+
+ /** @var \Sabberworm\CSS\Property\Selector $_selector */
+ foreach ($_decl->getSelectors() as $_selector) {
+ $_selector = $_selector->getSelector();
+
+ // Match class name
+ if ($class !== null) {
+ foreach ($class as $_class) {
+ if ($_selector === ".$_class") {
+ /** @var \Sabberworm\CSS\Rule\Rule $_rule */
+ foreach ($_decl->getRules() as $_rule) {
+ $styles[$_rule->getRule()] = $_rule->getValue() . "";
+ }
+
+ break 2;
+ }
+ }
+ }
+
+ // Match tag name
+ if ($_selector === $tag->tagName) {
+ /** @var \Sabberworm\CSS\Rule\Rule $_rule */
+ foreach ($_decl->getRules() as $_rule) {
+ $styles[$_rule->getRule()] = $_rule->getValue() . "";
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ $this->fillStyles($styles);
+ }
+
+ protected function fillStyles($styles)
+ {
+ $style_map = $this->getStyleMap();
+ foreach ($style_map as $from => $spec) {
+ if (isset($styles[$from])) {
+ list($to, $type) = $spec;
+ $value = null;
+ switch ($type) {
+ case self::TYPE_COLOR:
+ $value = self::parseColor($styles[$from]);
+ if ($value === "currentcolor") {
+ if ($type === "color") {
+ $value = $this->_parentStyle->color;
+ } else {
+ $value = $this->color;
+ }
+ }
+ if ($value !== null && $value[3] !== 1 && array_key_exists("{$from}-opacity", $style_map) === true) {
+ $styles["{$from}-opacity"] = $value[3];
+ }
+ break;
+
+ case self::TYPE_NUMBER:
+ $value = ($styles[$from] === null) ? null : (float)$styles[$from];
+ break;
+
+ default:
+ $value = $styles[$from];
+ }
+
+ if ($value !== null) {
+ $this->$to = $value;
+ }
+ }
+ }
+ }
+
+ static function parseColor($color)
+ {
+ $color = strtolower(trim($color));
+
+ $parts = preg_split('/[^,]\s+/', $color, 2);
+
+ if (count($parts) == 2) {
+ $color = $parts[1];
+ } else {
+ $color = $parts[0];
+ }
+
+ if ($color === "none") {
+ return "none";
+ }
+
+ if ($color === "currentcolor") {
+ return "currentcolor";
+ }
+
+ // SVG color name
+ if (isset(self::$colorNames[$color])) {
+ return self::parseHexColor(self::$colorNames[$color]);
+ }
+
+ // Hex color
+ if ($color[0] === "#") {
+ return self::parseHexColor($color);
+ }
+
+ // RGB color
+ if (strpos($color, "rgb") !== false) {
+ return self::getQuad($color);
+ }
+
+ // RGB color
+ if (strpos($color, "hsl") !== false) {
+ $quad = self::getQuad($color, true);
+
+ if ($quad == null) {
+ return null;
+ }
+
+ list($h, $s, $l, $a) = $quad;
+
+ $r = $l;
+ $g = $l;
+ $b = $l;
+ $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s);
+ if ($v > 0) {
+ $m = $l + $l - $v;
+ $sv = ($v - $m) / $v;
+ $h *= 6.0;
+ $sextant = floor($h);
+ $fract = $h - $sextant;
+ $vsf = $v * $sv * $fract;
+ $mid1 = $m + $vsf;
+ $mid2 = $v - $vsf;
+
+ switch ($sextant) {
+ case 0:
+ $r = $v;
+ $g = $mid1;
+ $b = $m;
+ break;
+ case 1:
+ $r = $mid2;
+ $g = $v;
+ $b = $m;
+ break;
+ case 2:
+ $r = $m;
+ $g = $v;
+ $b = $mid1;
+ break;
+ case 3:
+ $r = $m;
+ $g = $mid2;
+ $b = $v;
+ break;
+ case 4:
+ $r = $mid1;
+ $g = $m;
+ $b = $v;
+ break;
+ case 5:
+ $r = $v;
+ $g = $m;
+ $b = $mid2;
+ break;
+ }
+ }
+ $a = $a * 255;
+
+ return array(
+ $r * 255.0,
+ $g * 255.0,
+ $b * 255.0,
+ $a
+ );
+ }
+
+ // Gradient
+ if (strpos($color, "url(#") !== false) {
+ $i = strpos($color, "(");
+ $j = strpos($color, ")");
+
+ // Bad url format
+ if ($i === false || $j === false) {
+ return null;
+ }
+
+ return trim(substr($color, $i + 1, $j - $i - 1));
+ }
+
+ return null;
+ }
+
+ static function getQuad($color, $percent = false) {
+ $i = strpos($color, "(");
+ $j = strpos($color, ")");
+
+ // Bad color value
+ if ($i === false || $j === false) {
+ return null;
+ }
+
+ $quad = preg_split("/\\s*[,\\/]\\s*/", trim(substr($color, $i + 1, $j - $i - 1)));
+ if (!isset($quad[3])) {
+ $quad[3] = 1;
+ }
+
+ if (count($quad) != 3 && count($quad) != 4) {
+ return null;
+ }
+
+ foreach (array_keys($quad) as $c) {
+ $quad[$c] = trim($quad[$c]);
+
+ if ($percent) {
+ if ($quad[$c][strlen($quad[$c]) - 1] === "%") {
+ $quad[$c] = floatval($quad[$c]) / 100;
+ } else {
+ $quad[$c] = $quad[$c] / 255;
+ }
+ } else {
+ if ($quad[$c][strlen($quad[$c]) - 1] === "%") {
+ $quad[$c] = round(floatval($quad[$c]) * 2.55);
+ }
+ }
+ }
+
+ return $quad;
+ }
+
+ static function parseHexColor($hex)
+ {
+ $c = array(0, 0, 0, 1);
+
+ // #FFFFFF
+ if (isset($hex[6])) {
+ $c[0] = hexdec(substr($hex, 1, 2));
+ $c[1] = hexdec(substr($hex, 3, 2));
+ $c[2] = hexdec(substr($hex, 5, 2));
+
+ if (isset($hex[7])) {
+ $alpha = substr($hex, 7, 2);
+ if (ctype_xdigit($alpha)) {
+ $c[3] = round(hexdec($alpha)/255, 2);
+ }
+ }
+ } else {
+ $c[0] = hexdec($hex[1] . $hex[1]);
+ $c[1] = hexdec($hex[2] . $hex[2]);
+ $c[2] = hexdec($hex[3] . $hex[3]);
+
+ if (isset($hex[4])) {
+ if (ctype_xdigit($hex[4])) {
+ $c[3] = round(hexdec($hex[4] . $hex[4])/255, 2);
+ }
+ }
+ }
+
+ return $c;
+ }
+
+ /**
+ * Simple CSS parser
+ *
+ * @param $style
+ *
+ * @return array
+ */
+ static function parseCssStyle($style)
+ {
+ $matches = array();
+ preg_match_all("/([a-z-]+)\\s*:\\s*([^;$]+)/si", $style, $matches, PREG_SET_ORDER);
+
+ $styles = array();
+ foreach ($matches as $match) {
+ $styles[$match[1]] = $match[2];
+ }
+
+ return $styles;
+ }
+
+ static $colorNames = array(
+ 'antiquewhite' => '#FAEBD7',
+ 'aqua' => '#00FFFF',
+ 'aquamarine' => '#7FFFD4',
+ 'beige' => '#F5F5DC',
+ 'black' => '#000000',
+ 'blue' => '#0000FF',
+ 'brown' => '#A52A2A',
+ 'cadetblue' => '#5F9EA0',
+ 'chocolate' => '#D2691E',
+ 'cornflowerblue' => '#6495ED',
+ 'crimson' => '#DC143C',
+ 'darkblue' => '#00008B',
+ 'darkgoldenrod' => '#B8860B',
+ 'darkgreen' => '#006400',
+ 'darkmagenta' => '#8B008B',
+ 'darkorange' => '#FF8C00',
+ 'darkred' => '#8B0000',
+ 'darkseagreen' => '#8FBC8F',
+ 'darkslategray' => '#2F4F4F',
+ 'darkviolet' => '#9400D3',
+ 'deepskyblue' => '#00BFFF',
+ 'dodgerblue' => '#1E90FF',
+ 'firebrick' => '#B22222',
+ 'forestgreen' => '#228B22',
+ 'fuchsia' => '#FF00FF',
+ 'gainsboro' => '#DCDCDC',
+ 'gold' => '#FFD700',
+ 'gray' => '#808080',
+ 'green' => '#008000',
+ 'greenyellow' => '#ADFF2F',
+ 'hotpink' => '#FF69B4',
+ 'indigo' => '#4B0082',
+ 'khaki' => '#F0E68C',
+ 'lavenderblush' => '#FFF0F5',
+ 'lemonchiffon' => '#FFFACD',
+ 'lightcoral' => '#F08080',
+ 'lightgoldenrodyellow' => '#FAFAD2',
+ 'lightgreen' => '#90EE90',
+ 'lightsalmon' => '#FFA07A',
+ 'lightskyblue' => '#87CEFA',
+ 'lightslategray' => '#778899',
+ 'lightyellow' => '#FFFFE0',
+ 'lime' => '#00FF00',
+ 'limegreen' => '#32CD32',
+ 'magenta' => '#FF00FF',
+ 'maroon' => '#800000',
+ 'mediumaquamarine' => '#66CDAA',
+ 'mediumorchid' => '#BA55D3',
+ 'mediumseagreen' => '#3CB371',
+ 'mediumspringgreen' => '#00FA9A',
+ 'mediumvioletred' => '#C71585',
+ 'midnightblue' => '#191970',
+ 'mintcream' => '#F5FFFA',
+ 'moccasin' => '#FFE4B5',
+ 'navy' => '#000080',
+ 'olive' => '#808000',
+ 'orange' => '#FFA500',
+ 'orchid' => '#DA70D6',
+ 'palegreen' => '#98FB98',
+ 'palevioletred' => '#D87093',
+ 'peachpuff' => '#FFDAB9',
+ 'pink' => '#FFC0CB',
+ 'powderblue' => '#B0E0E6',
+ 'purple' => '#800080',
+ 'red' => '#FF0000',
+ 'royalblue' => '#4169E1',
+ 'salmon' => '#FA8072',
+ 'seagreen' => '#2E8B57',
+ 'sienna' => '#A0522D',
+ 'silver' => '#C0C0C0',
+ 'skyblue' => '#87CEEB',
+ 'slategray' => '#708090',
+ 'springgreen' => '#00FF7F',
+ 'steelblue' => '#4682B4',
+ 'tan' => '#D2B48C',
+ 'teal' => '#008080',
+ 'thistle' => '#D8BFD8',
+ 'turquoise' => '#40E0D0',
+ 'violetred' => '#D02090',
+ 'white' => '#FFFFFF',
+ 'yellow' => '#FFFF00',
+ 'aliceblue' => '#f0f8ff',
+ 'azure' => '#f0ffff',
+ 'bisque' => '#ffe4c4',
+ 'blanchedalmond' => '#ffebcd',
+ 'blueviolet' => '#8a2be2',
+ 'burlywood' => '#deb887',
+ 'chartreuse' => '#7fff00',
+ 'coral' => '#ff7f50',
+ 'cornsilk' => '#fff8dc',
+ 'cyan' => '#00ffff',
+ 'darkcyan' => '#008b8b',
+ 'darkgray' => '#a9a9a9',
+ 'darkgrey' => '#a9a9a9',
+ 'darkkhaki' => '#bdb76b',
+ 'darkolivegreen' => '#556b2f',
+ 'darkorchid' => '#9932cc',
+ 'darksalmon' => '#e9967a',
+ 'darkslateblue' => '#483d8b',
+ 'darkslategrey' => '#2f4f4f',
+ 'darkturquoise' => '#00ced1',
+ 'deeppink' => '#ff1493',
+ 'dimgray' => '#696969',
+ 'dimgrey' => '#696969',
+ 'floralwhite' => '#fffaf0',
+ 'ghostwhite' => '#f8f8ff',
+ 'goldenrod' => '#daa520',
+ 'grey' => '#808080',
+ 'honeydew' => '#f0fff0',
+ 'indianred' => '#cd5c5c',
+ 'ivory' => '#fffff0',
+ 'lavender' => '#e6e6fa',
+ 'lawngreen' => '#7cfc00',
+ 'lightblue' => '#add8e6',
+ 'lightcyan' => '#e0ffff',
+ 'lightgray' => '#d3d3d3',
+ 'lightgrey' => '#d3d3d3',
+ 'lightpink' => '#ffb6c1',
+ 'lightseagreen' => '#20b2aa',
+ 'lightslategrey' => '#778899',
+ 'lightsteelblue' => '#b0c4de',
+ 'linen' => '#faf0e6',
+ 'mediumblue' => '#0000cd',
+ 'mediumpurple' => '#9370db',
+ 'mediumslateblue' => '#7b68ee',
+ 'mediumturquoise' => '#48d1cc',
+ 'mistyrose' => '#ffe4e1',
+ 'navajowhite' => '#ffdead',
+ 'oldlace' => '#fdf5e6',
+ 'olivedrab' => '#6b8e23',
+ 'orangered' => '#ff4500',
+ 'palegoldenrod' => '#eee8aa',
+ 'paleturquoise' => '#afeeee',
+ 'papayawhip' => '#ffefd5',
+ 'peru' => '#cd853f',
+ 'plum' => '#dda0dd',
+ 'rosybrown' => '#bc8f8f',
+ 'saddlebrown' => '#8b4513',
+ 'sandybrown' => '#f4a460',
+ 'seashell' => '#fff5ee',
+ 'slateblue' => '#6a5acd',
+ 'slategrey' => '#708090',
+ 'snow' => '#fffafa',
+ 'tomato' => '#ff6347',
+ 'violet' => '#ee82ee',
+ 'wheat' => '#f5deb3',
+ 'whitesmoke' => '#f5f5f5',
+ 'yellowgreen' => '#9acd32',
+ );
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php
new file mode 100644
index 0000000..caa28a8
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php
@@ -0,0 +1,6418 @@
+<?php
+/**
+ * A PHP class to provide the basic functionality to create a pdf document without
+ * any requirement for additional modules.
+ *
+ * Extended by Orion Richardson to support Unicode / UTF-8 characters using
+ * TCPDF and others as a guide.
+ *
+ * @author Wayne Munro <pdf@ros.co.nz>
+ * @author Orion Richardson <orionr@yahoo.com>
+ * @author Helmut Tischer <htischer@weihenstephan.org>
+ * @author Ryan H. Masten <ryan.masten@gmail.com>
+ * @author Brian Sweeney <eclecticgeek@gmail.com>
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license Public Domain http://creativecommons.org/licenses/publicdomain/
+ * @package Cpdf
+ */
+
+namespace Svg\Surface;
+
+class CPdf
+{
+ const PDF_VERSION = '1.7';
+
+ const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
+ const ACROFORM_SIG_APPENDONLY = 0x0002;
+
+ const ACROFORM_FIELD_BUTTON = 'Btn';
+ const ACROFORM_FIELD_TEXT = 'Tx';
+ const ACROFORM_FIELD_CHOICE = 'Ch';
+ const ACROFORM_FIELD_SIG = 'Sig';
+
+ const ACROFORM_FIELD_READONLY = 0x0001;
+ const ACROFORM_FIELD_REQUIRED = 0x0002;
+
+ const ACROFORM_FIELD_TEXT_MULTILINE = 0x1000;
+ const ACROFORM_FIELD_TEXT_PASSWORD = 0x2000;
+ const ACROFORM_FIELD_TEXT_RICHTEXT = 0x10000;
+
+ const ACROFORM_FIELD_CHOICE_COMBO = 0x20000;
+ const ACROFORM_FIELD_CHOICE_EDIT = 0x40000;
+ const ACROFORM_FIELD_CHOICE_SORT = 0x80000;
+ const ACROFORM_FIELD_CHOICE_MULTISELECT = 0x200000;
+
+ const XOBJECT_SUBTYPE_FORM = 'Form';
+
+ /**
+ * @var integer The current number of pdf objects in the document
+ */
+ public $numObj = 0;
+
+ /**
+ * @var array This array contains all of the pdf objects, ready for final assembly
+ */
+ public $objects = [];
+
+ /**
+ * @var integer The objectId (number within the objects array) of the document catalog
+ */
+ public $catalogId;
+
+ /**
+ * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
+ */
+ protected $indirectReferenceId = 0;
+
+ /**
+ * @var integer The objectId (number within the objects array)
+ */
+ protected $embeddedFilesId = 0;
+
+ /**
+ * AcroForm objectId
+ *
+ * @var integer
+ */
+ public $acroFormId;
+
+ /**
+ * @var int
+ */
+ public $signatureMaxLen = 5000;
+
+ /**
+ * @var array Array carrying information about the fonts that the system currently knows about
+ * Used to ensure that a font is not loaded twice, among other things
+ */
+ public $fonts = [];
+
+ /**
+ * @var string The default font metrics file to use if no other font has been loaded.
+ * The path to the directory containing the font metrics should be included
+ */
+ public $defaultFont = './fonts/Helvetica.afm';
+
+ /**
+ * @string A record of the current font
+ */
+ public $currentFont = '';
+
+ /**
+ * @var string The current base font
+ */
+ public $currentBaseFont = '';
+
+ /**
+ * @var integer The number of the current font within the font array
+ */
+ public $currentFontNum = 0;
+
+ /**
+ * @var integer
+ */
+ public $currentNode;
+
+ /**
+ * @var integer Object number of the current page
+ */
+ public $currentPage;
+
+ /**
+ * @var integer Object number of the currently active contents block
+ */
+ public $currentContents;
+
+ /**
+ * @var integer Number of fonts within the system
+ */
+ public $numFonts = 0;
+
+ /**
+ * @var integer Number of graphic state resources used
+ */
+ private $numStates = 0;
+
+ /**
+ * @var array Number of graphic state resources used
+ */
+ private $gstates = [];
+
+ /**
+ * @var array Current color for fill operations, defaults to inactive value,
+ * all three components should be between 0 and 1 inclusive when active
+ */
+ public $currentColor = null;
+
+ /**
+ * @var array Current color for stroke operations (lines etc.)
+ */
+ public $currentStrokeColor = null;
+
+ /**
+ * @var string Fill rule (nonzero or evenodd)
+ */
+ public $fillRule = "nonzero";
+
+ /**
+ * @var string Current style that lines are drawn in
+ */
+ public $currentLineStyle = '';
+
+ /**
+ * @var array Current line transparency (partial graphics state)
+ */
+ public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
+
+ /**
+ * array Current fill transparency (partial graphics state)
+ */
+ public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
+
+ /**
+ * @var array An array which is used to save the state of the document, mainly the colors and styles
+ * it is used to temporarily change to another state, then change back to what it was before
+ */
+ public $stateStack = [];
+
+ /**
+ * @var integer Number of elements within the state stack
+ */
+ public $nStateStack = 0;
+
+ /**
+ * @var integer Number of page objects within the document
+ */
+ public $numPages = 0;
+
+ /**
+ * @var array Object Id storage stack
+ */
+ public $stack = [];
+
+ /**
+ * @var integer Number of elements within the object Id storage stack
+ */
+ public $nStack = 0;
+
+ /**
+ * an array which contains information about the objects which are not firmly attached to pages
+ * these have been added with the addObject function
+ */
+ public $looseObjects = [];
+
+ /**
+ * array contains information about how the loose objects are to be added to the document
+ */
+ public $addLooseObjects = [];
+
+ /**
+ * @var integer The objectId of the information object for the document
+ * this contains authorship, title etc.
+ */
+ public $infoObject = 0;
+
+ /**
+ * @var integer Number of images being tracked within the document
+ */
+ public $numImages = 0;
+
+ /**
+ * @var array An array containing options about the document
+ * it defaults to turning on the compression of the objects
+ */
+ public $options = ['compression' => true];
+
+ /**
+ * @var integer The objectId of the first page of the document
+ */
+ public $firstPageId;
+
+ /**
+ * @var integer The object Id of the procset object
+ */
+ public $procsetObjectId;
+
+ /**
+ * @var array Store the information about the relationship between font families
+ * this used so that the code knows which font is the bold version of another font, etc.
+ * the value of this array is initialised in the constructor function.
+ */
+ public $fontFamilies = [];
+
+ /**
+ * @var string Folder for php serialized formats of font metrics files.
+ * If empty string, use same folder as original metrics files.
+ * This can be passed in from class creator.
+ * If this folder does not exist or is not writable, Cpdf will be **much** slower.
+ * Because of potential trouble with php safe mode, folder cannot be created at runtime.
+ */
+ public $fontcache = '';
+
+ /**
+ * @var integer The version of the font metrics cache file.
+ * This value must be manually incremented whenever the internal font data structure is modified.
+ */
+ public $fontcacheVersion = 6;
+
+ /**
+ * @var string Temporary folder.
+ * If empty string, will attempt system tmp folder.
+ * This can be passed in from class creator.
+ */
+ public $tmp = '';
+
+ /**
+ * @var string Track if the current font is bolded or italicised
+ */
+ public $currentTextState = '';
+
+ /**
+ * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
+ */
+ public $messages = '';
+
+ /**
+ * @var string The encryption array for the document encryption is stored here
+ */
+ public $arc4 = '';
+
+ /**
+ * @var integer The object Id of the encryption information
+ */
+ public $arc4_objnum = 0;
+
+ /**
+ * @var string The file identifier, used to uniquely identify a pdf document
+ */
+ public $fileIdentifier = '';
+
+ /**
+ * @var boolean A flag to say if a document is to be encrypted or not
+ */
+ public $encrypted = false;
+
+ /**
+ * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
+ */
+ public $encryptionKey = '';
+
+ /**
+ * @var array Array which forms a stack to keep track of nested callback functions
+ */
+ public $callback = [];
+
+ /**
+ * @var integer The number of callback functions in the callback array
+ */
+ public $nCallback = 0;
+
+ /**
+ * @var array Store label->id pairs for named destinations, these will be used to replace internal links
+ * done this way so that destinations can be defined after the location that links to them
+ */
+ public $destinations = [];
+
+ /**
+ * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
+ * publiciables within the class, so that the user can rollback at will (from each 'start' command)
+ * note that this includes the objects array, so these can be large.
+ */
+ public $checkpoint = '';
+
+ /**
+ * @var array Table of Image origin filenames and image labels which were already added with o_image().
+ * Allows to merge identical images
+ */
+ public $imagelist = [];
+
+ /**
+ * @var array Table of already added alpha and plain image files for transparent PNG images.
+ */
+ protected $imageAlphaList = [];
+
+ /**
+ * @var array List of temporary image files to be deleted after processing.
+ */
+ protected $imageCache = [];
+
+ /**
+ * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
+ */
+ public $isUnicode = false;
+
+ /**
+ * @var string the JavaScript code of the document
+ */
+ public $javascript = '';
+
+ /**
+ * @var boolean whether the compression is possible
+ */
+ protected $compressionReady = false;
+
+ /**
+ * @var array Current page size
+ */
+ protected $currentPageSize = ["width" => 0, "height" => 0];
+
+ /**
+ * @var array All the chars that will be required in the font subsets
+ */
+ protected $stringSubsets = [];
+
+ /**
+ * @var string The target internal encoding
+ */
+ protected static $targetEncoding = 'Windows-1252';
+
+ /**
+ * @var array
+ */
+ protected $byteRange = array();
+
+ /**
+ * @var array The list of the core fonts
+ */
+ protected static $coreFonts = [
+ 'courier',
+ 'courier-bold',
+ 'courier-oblique',
+ 'courier-boldoblique',
+ 'helvetica',
+ 'helvetica-bold',
+ 'helvetica-oblique',
+ 'helvetica-boldoblique',
+ 'times-roman',
+ 'times-bold',
+ 'times-italic',
+ 'times-bolditalic',
+ 'symbol',
+ 'zapfdingbats'
+ ];
+
+ /**
+ * Class constructor
+ * This will start a new document
+ *
+ * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
+ * @param boolean $isUnicode Whether text will be treated as Unicode or not.
+ * @param string $fontcache The font cache folder
+ * @param string $tmp The temporary folder
+ */
+ function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
+ {
+ $this->isUnicode = $isUnicode;
+ $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
+ $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
+ $this->newDocument($pageSize);
+
+ $this->compressionReady = function_exists('gzcompress');
+
+ if (in_array('Windows-1252', mb_list_encodings())) {
+ self::$targetEncoding = 'Windows-1252';
+ }
+
+ // also initialize the font families that are known about already
+ $this->setFontFamily('init');
+ }
+
+ public function __destruct()
+ {
+ foreach ($this->imageCache as $file) {
+ if (file_exists($file)) {
+ unlink($file);
+ }
+ }
+ }
+
+ /**
+ * Document object methods (internal use only)
+ *
+ * There is about one object method for each type of object in the pdf document
+ * Each function has the same call list ($id,$action,$options).
+ * $id = the object ID of the object, or what it is to be if it is being created
+ * $action = a string specifying the action to be performed, though ALL must support:
+ * 'new' - create the object with the id $id
+ * 'out' - produce the output for the pdf object
+ * $options = optional, a string or array containing the various parameters for the object
+ *
+ * These, in conjunction with the output function are the ONLY way for output to be produced
+ * within the pdf 'file'.
+ */
+
+ /**
+ * Destination object, used to specify the location for the user to jump to, presently on opening
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_destination($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'destination', 'info' => []];
+ $tmp = '';
+ switch ($options['type']) {
+ case 'XYZ':
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 'FitR':
+ $tmp = ' ' . $options['p3'] . $tmp;
+ case 'FitH':
+ case 'FitV':
+ case 'FitBH':
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 'FitBV':
+ $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
+ case 'Fit':
+ case 'FitB':
+ $tmp = $options['type'] . $tmp;
+ $this->objects[$id]['info']['string'] = $tmp;
+ $this->objects[$id]['info']['page'] = $options['page'];
+ }
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+
+ $tmp = $o['info'];
+ $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * set the viewer preferences
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return string|null
+ */
+ protected function o_viewerPreferences($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
+ break;
+
+ case 'add':
+ $o = &$this->objects[$id];
+
+ foreach ($options as $k => $v) {
+ switch ($k) {
+ // Boolean keys
+ case 'HideToolbar':
+ case 'HideMenubar':
+ case 'HideWindowUI':
+ case 'FitWindow':
+ case 'CenterWindow':
+ case 'DisplayDocTitle':
+ case 'PickTrayByPDFSize':
+ $o['info'][$k] = (bool)$v;
+ break;
+
+ // Integer keys
+ case 'NumCopies':
+ $o['info'][$k] = (int)$v;
+ break;
+
+ // Name keys
+ case 'ViewArea':
+ case 'ViewClip':
+ case 'PrintClip':
+ case 'PrintArea':
+ $o['info'][$k] = (string)$v;
+ break;
+
+ // Named with limited valid values
+ case 'NonFullScreenPageMode':
+ if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ case 'Direction':
+ if (!in_array($v, ['L2R', 'R2L'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ case 'PrintScaling':
+ if (!in_array($v, ['None', 'AppDefault'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ case 'Duplex':
+ if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
+ break;
+ }
+ $o['info'][$k] = $v;
+ break;
+
+ // Integer array
+ case 'PrintPageRange':
+ // Cast to integer array
+ foreach ($v as $vK => $vV) {
+ $v[$vK] = (int)$vV;
+ }
+ $o['info'][$k] = array_values($v);
+ break;
+ }
+ }
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< ";
+
+ foreach ($o['info'] as $k => $v) {
+ if (is_string($v)) {
+ $v = '/' . $v;
+ } elseif (is_int($v)) {
+ $v = (string) $v;
+ } elseif (is_bool($v)) {
+ $v = ($v ? 'true' : 'false');
+ } elseif (is_array($v)) {
+ $v = '[' . implode(' ', $v) . ']';
+ }
+ $res .= "\n/$k $v";
+ }
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * define the document catalog, the overall controller for the document
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return string|null
+ */
+ protected function o_catalog($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'catalog', 'info' => []];
+ $this->catalogId = $id;
+ break;
+
+ case 'acroform':
+ case 'outlines':
+ case 'pages':
+ case 'openHere':
+ case 'names':
+ $o['info'][$action] = $options;
+ break;
+
+ case 'viewerPreferences':
+ if (!isset($o['info']['viewerPreferences'])) {
+ $this->numObj++;
+ $this->o_viewerPreferences($this->numObj, 'new');
+ $o['info']['viewerPreferences'] = $this->numObj;
+ }
+
+ $vp = $o['info']['viewerPreferences'];
+ $this->o_viewerPreferences($vp, 'add', $options);
+
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Catalog";
+
+ foreach ($o['info'] as $k => $v) {
+ switch ($k) {
+ case 'outlines':
+ $res .= "\n/Outlines $v 0 R";
+ break;
+
+ case 'pages':
+ $res .= "\n/Pages $v 0 R";
+ break;
+
+ case 'viewerPreferences':
+ $res .= "\n/ViewerPreferences $v 0 R";
+ break;
+
+ case 'openHere':
+ $res .= "\n/OpenAction $v 0 R";
+ break;
+
+ case 'names':
+ $res .= "\n/Names $v 0 R";
+ break;
+
+ case 'acroform':
+ $res .= "\n/AcroForm $v 0 R";
+ break;
+ }
+ }
+
+ $res .= " >>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * object which is a parent to the pages in the document
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_pages($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'pages', 'info' => []];
+ $this->o_catalog($this->catalogId, 'pages', $id);
+ break;
+
+ case 'page':
+ if (!is_array($options)) {
+ // then it will just be the id of the new page
+ $o['info']['pages'][] = $options;
+ } else {
+ // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
+ // and pos is either 'before' or 'after', saying where this page will fit.
+ if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
+ $i = array_search($options['rid'], $o['info']['pages']);
+ if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
+
+ // then there is a match
+ // make a space
+ switch ($options['pos']) {
+ case 'before':
+ $k = $i;
+ break;
+
+ case 'after':
+ $k = $i + 1;
+ break;
+
+ default:
+ $k = -1;
+ break;
+ }
+
+ if ($k >= 0) {
+ for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
+ $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
+ }
+
+ $o['info']['pages'][$k] = $options['id'];
+ }
+ }
+ }
+ }
+ break;
+
+ case 'procset':
+ $o['info']['procset'] = $options;
+ break;
+
+ case 'mediaBox':
+ $o['info']['mediaBox'] = $options;
+ // which should be an array of 4 numbers
+ $this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
+ break;
+
+ case 'font':
+ $o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
+ break;
+
+ case 'extGState':
+ $o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
+ break;
+
+ case 'xObject':
+ $o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
+ break;
+
+ case 'out':
+ if (count($o['info']['pages'])) {
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
+ foreach ($o['info']['pages'] as $v) {
+ $res .= "$v 0 R\n";
+ }
+
+ $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
+
+ if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
+ isset($o['info']['procset']) ||
+ (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
+ ) {
+ $res .= "\n/Resources <<";
+
+ if (isset($o['info']['procset'])) {
+ $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
+ }
+
+ if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
+ $res .= "\n/Font << ";
+ foreach ($o['info']['fonts'] as $finfo) {
+ $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+
+ if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
+ $res .= "\n/XObject << ";
+ foreach ($o['info']['xObjects'] as $finfo) {
+ $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+
+ if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
+ $res .= "\n/ExtGState << ";
+ foreach ($o['info']['extGStates'] as $gstate) {
+ $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+
+ $res .= "\n>>";
+ if (isset($o['info']['mediaBox'])) {
+ $tmp = $o['info']['mediaBox'];
+ $res .= "\n/MediaBox [" . sprintf(
+ '%.3F %.3F %.3F %.3F',
+ $tmp[0],
+ $tmp[1],
+ $tmp[2],
+ $tmp[3]
+ ) . ']';
+ }
+ }
+
+ $res .= "\n >>\nendobj";
+ } else {
+ $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
+ }
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * define the outlines in the doc, empty for now
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_outlines($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
+ $this->o_catalog($this->catalogId, 'outlines', $id);
+ break;
+
+ case 'outline':
+ $o['info']['outlines'][] = $options;
+ break;
+
+ case 'out':
+ if (count($o['info']['outlines'])) {
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
+ foreach ($o['info']['outlines'] as $v) {
+ $res .= "$v 0 R ";
+ }
+
+ $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
+ } else {
+ $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
+ }
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an object to hold the font description
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return string|null
+ * @throws FontNotFoundException
+ */
+ protected function o_font($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'font',
+ 'info' => [
+ 'name' => $options['name'],
+ 'fontFileName' => $options['fontFileName'],
+ 'SubType' => 'Type1',
+ 'isSubsetting' => $options['isSubsetting']
+ ]
+ ];
+ $fontNum = $this->numFonts;
+ $this->objects[$id]['info']['fontNum'] = $fontNum;
+
+ // deal with the encoding and the differences
+ if (isset($options['differences'])) {
+ // then we'll need an encoding dictionary
+ $this->numObj++;
+ $this->o_fontEncoding($this->numObj, 'new', $options);
+ $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
+ } else {
+ if (isset($options['encoding'])) {
+ // we can specify encoding here
+ switch ($options['encoding']) {
+ case 'WinAnsiEncoding':
+ case 'MacRomanEncoding':
+ case 'MacExpertEncoding':
+ $this->objects[$id]['info']['encoding'] = $options['encoding'];
+ break;
+
+ case 'none':
+ break;
+
+ default:
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
+ break;
+ }
+ } else {
+ $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
+ }
+ }
+
+ if ($this->fonts[$options['fontFileName']]['isUnicode']) {
+ // For Unicode fonts, we need to incorporate font data into
+ // sub-sections that are linked from the primary font section.
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
+ // for more information.
+ //
+ // All of this code is adapted from the excellent changes made to
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
+
+ $toUnicodeId = ++$this->numObj;
+ $this->o_toUnicode($toUnicodeId, 'new');
+ $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
+
+ $cidFontId = ++$this->numObj;
+ $this->o_fontDescendentCID($cidFontId, 'new', $options);
+ $this->objects[$id]['info']['cidFont'] = $cidFontId;
+ }
+
+ // also tell the pages node about the new font
+ $this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
+ break;
+
+ case 'add':
+ $font_options = $this->processFont($id, $o['info']);
+
+ if ($font_options !== false) {
+ foreach ($font_options as $k => $v) {
+ switch ($k) {
+ case 'BaseFont':
+ $o['info']['name'] = $v;
+ break;
+ case 'FirstChar':
+ case 'LastChar':
+ case 'Widths':
+ case 'FontDescriptor':
+ case 'SubType':
+ $this->addMessage('o_font ' . $k . " : " . $v);
+ $o['info'][$k] = $v;
+ break;
+ }
+ }
+
+ // pass values down to descendent font
+ if (isset($o['info']['cidFont'])) {
+ $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
+ }
+ }
+ break;
+
+ case 'out':
+ if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
+ // For Unicode fonts, we need to incorporate font data into
+ // sub-sections that are linked from the primary font section.
+ // Look at o_fontGIDtoCID and o_fontDescendentCID functions
+ // for more information.
+ //
+ // All of this code is adapted from the excellent changes made to
+ // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
+
+ $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
+
+ // The horizontal identity mapping for 2-byte CIDs; may be used
+ // with CIDFonts using any Registry, Ordering, and Supplement values.
+ $res .= "/Encoding /Identity-H\n";
+ $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
+ $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
+ $res .= ">>\n";
+ $res .= "endobj";
+ } else {
+ $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
+ $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
+
+ if (isset($o['info']['encodingDictionary'])) {
+ // then place a reference to the dictionary
+ $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
+ } else {
+ if (isset($o['info']['encoding'])) {
+ // use the specified encoding
+ $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
+ }
+ }
+
+ if (isset($o['info']['FirstChar'])) {
+ $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
+ }
+
+ if (isset($o['info']['LastChar'])) {
+ $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
+ }
+
+ if (isset($o['info']['Widths'])) {
+ $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
+ }
+
+ if (isset($o['info']['FontDescriptor'])) {
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
+ }
+
+ $res .= ">>\n";
+ $res .= "endobj";
+ }
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function getFontSubsettingTag(array $font): string
+ {
+ // convert font num to hexavigesimal numeral system letters A - Z only
+ $base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
+ for ($i = 0; $i < strlen($base_26); $i++) {
+ $char = $base_26[$i];
+ if ($char <= "9") {
+ $base_26[$i] = chr(65 + intval($char));
+ } else {
+ $base_26[$i] = chr(ord($char) + 10);
+ }
+ }
+
+ return 'SUB' . str_pad($base_26, 3 , 'A', STR_PAD_LEFT);
+ }
+
+ /**
+ * @param int $fontObjId
+ * @param array $object_info
+ * @return array|false
+ * @throws FontNotFoundException
+ */
+ private function processFont(int $fontObjId, array $object_info)
+ {
+ $fontFileName = $object_info['fontFileName'];
+ if (!isset($this->fonts[$fontFileName])) {
+ return false;
+ }
+
+ $font = &$this->fonts[$fontFileName];
+
+ $fileSuffix = $font['fileSuffix'];
+ $fileSuffixLower = strtolower($font['fileSuffix']);
+ $fbfile = "$fontFileName.$fileSuffix";
+ $isTtfFont = $fileSuffixLower === 'ttf';
+ $isPfbFont = $fileSuffixLower === 'pfb';
+
+ $this->addMessage('selectFont: checking for - ' . $fbfile);
+
+ if (!$fileSuffix) {
+ $this->addMessage(
+ 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
+ );
+
+ return false;
+ } else {
+ $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
+ // $fontObj = $this->numObj;
+ $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
+
+ // find the array of font widths, and put that into an object.
+ $firstChar = -1;
+ $lastChar = 0;
+ $widths = [];
+ $cid_widths = [];
+
+ foreach ($font['C'] as $num => $d) {
+ if (intval($num) > 0 || $num == '0') {
+ if (!$font['isUnicode']) {
+ // With Unicode, widths array isn't used
+ if ($lastChar > 0 && $num > $lastChar + 1) {
+ for ($i = $lastChar + 1; $i < $num; $i++) {
+ $widths[] = 0;
+ }
+ }
+ }
+
+ $widths[] = $d;
+
+ if ($font['isUnicode']) {
+ $cid_widths[$num] = $d;
+ }
+
+ if ($firstChar == -1) {
+ $firstChar = $num;
+ }
+
+ $lastChar = $num;
+ }
+ }
+
+ // also need to adjust the widths for the differences array
+ if (isset($object['differences'])) {
+ foreach ($object['differences'] as $charNum => $charName) {
+ if ($charNum > $lastChar) {
+ if (!$object['isUnicode']) {
+ // With Unicode, widths array isn't used
+ for ($i = $lastChar + 1; $i <= $charNum; $i++) {
+ $widths[] = 0;
+ }
+ }
+
+ $lastChar = $charNum;
+ }
+
+ if (isset($font['C'][$charName])) {
+ $widths[$charNum - $firstChar] = $font['C'][$charName];
+ if ($font['isUnicode']) {
+ $cid_widths[$charName] = $font['C'][$charName];
+ }
+ }
+ }
+ }
+
+ if ($font['isUnicode']) {
+ $font['CIDWidths'] = $cid_widths;
+ }
+
+ $this->addMessage('selectFont: FirstChar = ' . $firstChar);
+ $this->addMessage('selectFont: LastChar = ' . $lastChar);
+
+ $widthid = -1;
+
+ if (!$font['isUnicode']) {
+ // With Unicode, widths array isn't used
+
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new', 'raw');
+ $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
+ $widthid = $this->numObj;
+ }
+
+ $missing_width = 500;
+ $stemV = 70;
+
+ if (isset($font['MissingWidth'])) {
+ $missing_width = $font['MissingWidth'];
+ }
+ if (isset($font['StdVW'])) {
+ $stemV = $font['StdVW'];
+ } else {
+ if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
+ $stemV = 120;
+ }
+ }
+
+ // load the pfb file, and put that into an object too.
+ // note that pdf supports only binary format type 1 font files, though there is a
+ // simple utility to convert them from pfa to pfb.
+ $data = file_get_contents($fbfile);
+
+ // create the font descriptor
+ $this->numObj++;
+ $fontDescriptorId = $this->numObj;
+
+ $this->numObj++;
+ $pfbid = $this->numObj;
+
+ // determine flags (more than a little flakey, hopefully will not matter much)
+ $flags = 0;
+
+ if ($font['ItalicAngle'] != 0) {
+ $flags += pow(2, 6);
+ }
+
+ if ($font['IsFixedPitch'] === 'true') {
+ $flags += 1;
+ }
+
+ $flags += pow(2, 5); // assume non-sybolic
+ $list = [
+ 'Ascent' => 'Ascender',
+ 'CapHeight' => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
+ 'MissingWidth' => 'MissingWidth',
+ 'Descent' => 'Descender',
+ 'FontBBox' => 'FontBBox',
+ 'ItalicAngle' => 'ItalicAngle'
+ ];
+ $fdopt = [
+ 'Flags' => $flags,
+ 'FontName' => $adobeFontName,
+ 'StemV' => $stemV
+ ];
+
+ foreach ($list as $k => $v) {
+ if (isset($font[$v])) {
+ $fdopt[$k] = $font[$v];
+ }
+ }
+
+ if ($isPfbFont) {
+ $fdopt['FontFile'] = $pfbid;
+ } elseif ($isTtfFont) {
+ $fdopt['FontFile2'] = $pfbid;
+ }
+
+ $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
+
+ // embed the font program
+ $this->o_contents($this->numObj, 'new');
+ $this->objects[$pfbid]['c'] .= $data;
+
+ // determine the cruicial lengths within this file
+ if ($isPfbFont) {
+ $l1 = strpos($data, 'eexec') + 6;
+ $l2 = strpos($data, '00000000') - $l1;
+ $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
+ $this->o_contents(
+ $this->numObj,
+ 'add',
+ ['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
+ );
+ } elseif ($isTtfFont) {
+ $l1 = mb_strlen($data, '8bit');
+ $this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
+ }
+
+ // tell the font object about all this new stuff
+ $options = [
+ 'BaseFont' => $adobeFontName,
+ 'MissingWidth' => $missing_width,
+ 'Widths' => $widthid,
+ 'FirstChar' => $firstChar,
+ 'LastChar' => $lastChar,
+ 'FontDescriptor' => $fontDescriptorId
+ ];
+
+ if ($isTtfFont) {
+ $options['SubType'] = 'TrueType';
+ }
+
+ $this->addMessage("adding extra info to font.($fontObjId)");
+
+ foreach ($options as $fk => $fv) {
+ $this->addMessage("$fk : $fv");
+ }
+ }
+
+ return $options;
+ }
+
+ /**
+ * A toUnicode section, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @return null|string
+ */
+ protected function o_toUnicode($id, $action)
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'toUnicode'
+ ];
+ break;
+ case 'add':
+ break;
+ case 'out':
+ $ordering = 'UCS';
+ $registry = 'Adobe';
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $ordering = $this->ARC4($ordering);
+ $registry = $this->filterText($this->ARC4($registry), false, false);
+ }
+
+ $stream = <<<EOT
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<</Registry ($registry)
+/Ordering ($ordering)
+/Supplement 0
+>> def
+/CMapName /Adobe-Identity-UCS def
+/CMapType 2 def
+1 begincodespacerange
+<0000> <FFFF>
+endcodespacerange
+1 beginbfrange
+<0000> <FFFF> <0000>
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+EOT;
+
+ $res = "\n$id 0 obj\n";
+ $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
+ $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a font descriptor, needed for including additional fonts
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_fontDescriptor($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
+ foreach ($o['info'] as $label => $value) {
+ switch ($label) {
+ case 'Ascent':
+ case 'CapHeight':
+ case 'Descent':
+ case 'Flags':
+ case 'ItalicAngle':
+ case 'StemV':
+ case 'AvgWidth':
+ case 'Leading':
+ case 'MaxWidth':
+ case 'MissingWidth':
+ case 'StemH':
+ case 'XHeight':
+ case 'CharSet':
+ if (mb_strlen($value, '8bit')) {
+ $res .= "/$label $value\n";
+ }
+
+ break;
+ case 'FontFile':
+ case 'FontFile2':
+ case 'FontFile3':
+ $res .= "/$label $value 0 R\n";
+ break;
+
+ case 'FontBBox':
+ $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
+ break;
+
+ case 'FontName':
+ $res .= "/$label /$value\n";
+ break;
+ }
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * the font encoding
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_fontEncoding($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ // the options array should contain 'differences' and maybe 'encoding'
+ $this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Encoding\n";
+ if (!isset($o['info']['encoding'])) {
+ $o['info']['encoding'] = 'WinAnsiEncoding';
+ }
+
+ if ($o['info']['encoding'] !== 'none') {
+ $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
+ }
+
+ $res .= "/Differences \n[";
+
+ $onum = -100;
+
+ foreach ($o['info']['differences'] as $num => $label) {
+ if ($num != $onum + 1) {
+ // we cannot make use of consecutive numbering
+ $res .= "\n$num /$label";
+ } else {
+ $res .= " /$label";
+ }
+
+ $onum = $num;
+ }
+
+ $res .= "\n]\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a descendent cid font, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return null|string
+ */
+ protected function o_fontDescendentCID($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
+
+ // we need a CID system info section
+ $cidSystemInfoId = ++$this->numObj;
+ $this->o_cidSystemInfo($cidSystemInfoId, 'new');
+ $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
+
+ // and a CID to GID map
+ $cidToGidMapId = ++$this->numObj;
+ $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
+ $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
+ break;
+
+ case 'add':
+ foreach ($options as $k => $v) {
+ switch ($k) {
+ case 'BaseFont':
+ $o['info']['name'] = $v;
+ break;
+
+ case 'FirstChar':
+ case 'LastChar':
+ case 'MissingWidth':
+ case 'FontDescriptor':
+ case 'SubType':
+ $this->addMessage("o_fontDescendentCID $k : $v");
+ $o['info'][$k] = $v;
+ break;
+ }
+ }
+
+ // pass values down to cid to gid map
+ $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n";
+ $res .= "<</Type /Font\n";
+ $res .= "/Subtype /CIDFontType2\n";
+ $res .= "/BaseFont /" . $o['info']['name'] . "\n";
+ $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
+ // if (isset($o['info']['FirstChar'])) {
+ // $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
+ // }
+
+ // if (isset($o['info']['LastChar'])) {
+ // $res.= "/LastChar ".$o['info']['LastChar']."\n";
+ // }
+ if (isset($o['info']['FontDescriptor'])) {
+ $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
+ }
+
+ if (isset($o['info']['MissingWidth'])) {
+ $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
+ }
+
+ if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
+ $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
+ $w = '';
+ foreach ($cid_widths as $cid => $width) {
+ $w .= "$cid [$width] ";
+ }
+ $res .= "/W [$w]\n";
+ }
+
+ $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
+ $res .= ">>\n";
+ $res .= "endobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * CID system info section, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @return null|string
+ */
+ protected function o_cidSystemInfo($id, $action)
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'cidSystemInfo'
+ ];
+ break;
+ case 'add':
+ break;
+ case 'out':
+ $ordering = 'UCS';
+ $registry = 'Adobe';
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $ordering = $this->ARC4($ordering);
+ $registry = $this->ARC4($registry);
+ }
+
+
+ $res = "\n$id 0 obj\n";
+
+ $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
+ $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
+ $res .= "/Supplement 0\n"; // The supplement number of the character collection.
+ $res .= ">>";
+
+ $res .= "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a font glyph to character map, needed for unicode fonts
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_fontGIDtoCIDMap($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n";
+ $fontFileName = $o['info']['fontFileName'];
+ $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
+
+ $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
+ $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
+
+ if (!$compressed && isset($o['raw'])) {
+ $res .= $tmp;
+ } else {
+ $res .= "<<";
+
+ if (!$compressed && $this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on this content stream
+ $compressed = true;
+ $tmp = gzcompress($tmp, 6);
+ }
+ if ($compressed) {
+ $res .= "\n/Filter /FlateDecode";
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
+ }
+
+ $res .= "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * the document procset, solves some problems with printing to old PS printers
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_procset($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
+ $this->o_pages($this->currentNode, 'procset', $id);
+ $this->procsetObjectId = $id;
+ break;
+
+ case 'add':
+ // this is to add new items to the procset list, despite the fact that this is considered
+ // obsolete, the items are required for printing to some postscript printers
+ switch ($options) {
+ case 'ImageB':
+ case 'ImageC':
+ case 'ImageI':
+ $o['info'][$options] = 1;
+ break;
+ }
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n[";
+ foreach ($o['info'] as $label => $val) {
+ $res .= "/$label ";
+ }
+ $res .= "]\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * define the document information
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_info($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->infoObject = $id;
+ $date = 'D:' . @date('Ymd');
+ $this->objects[$id] = [
+ 't' => 'info',
+ 'info' => [
+ 'Producer' => 'CPDF (dompdf)',
+ 'CreationDate' => $date
+ ]
+ ];
+ break;
+ case 'Title':
+ case 'Author':
+ case 'Subject':
+ case 'Keywords':
+ case 'Creator':
+ case 'Producer':
+ case 'CreationDate':
+ case 'ModDate':
+ case 'Trapped':
+ $this->objects[$id]['info'][$action] = $options;
+ break;
+
+ case 'out':
+ $encrypted = $this->encrypted;
+ if ($encrypted) {
+ $this->encryptInit($id);
+ }
+
+ $res = "\n$id 0 obj\n<<\n";
+ $o = &$this->objects[$id];
+ foreach ($o['info'] as $k => $v) {
+ $res .= "/$k (";
+
+ // dates must be outputted as-is, without Unicode transformations
+ if ($k !== 'CreationDate' && $k !== 'ModDate') {
+ $v = $this->filterText($v, true, false);
+ }
+
+ if ($encrypted) {
+ $v = $this->ARC4($v);
+ }
+
+ $res .= $v;
+ $res .= ")\n";
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an action object, used to link to URLS initially
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_action($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ if (is_array($options)) {
+ $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
+ } else {
+ // then assume a URI action
+ $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
+ }
+ break;
+
+ case 'out':
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ }
+
+ $res = "\n$id 0 obj\n<< /Type /Action";
+ switch ($o['type']) {
+ case 'ilink':
+ if (!isset($this->destinations[(string)$o['info']['label']])) {
+ break;
+ }
+
+ // there will be an 'label' setting, this is the name of the destination
+ $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
+ break;
+
+ case 'URI':
+ $res .= "\n/S /URI\n/URI (";
+ if ($this->encrypted) {
+ $res .= $this->filterText($this->ARC4($o['info']), false, false);
+ } else {
+ $res .= $this->filterText($o['info'], false, false);
+ }
+
+ $res .= ")";
+ break;
+ }
+
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an annotation object, this will add an annotation to the current page.
+ * initially will support just link annotations
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_annotation($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ // add the annotation to the current page
+ $pageId = $this->currentPage;
+ $this->o_page($pageId, 'annot', $id);
+
+ // and add the action object which is going to be required
+ switch ($options['type']) {
+ case 'link':
+ $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
+ $this->numObj++;
+ $this->o_action($this->numObj, 'new', $options['url']);
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
+ break;
+
+ case 'ilink':
+ // this is to a named internal link
+ $label = $options['label'];
+ $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
+ $this->numObj++;
+ $this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
+ $this->objects[$id]['info']['actionId'] = $this->numObj;
+ break;
+ }
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Annot";
+ switch ($o['info']['type']) {
+ case 'link':
+ case 'ilink':
+ $res .= "\n/Subtype /Link";
+ break;
+ }
+ $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
+ $res .= "\n/Border [0 0 0]";
+ $res .= "\n/H /I";
+ $res .= "\n/Rect [ ";
+
+ foreach ($o['info']['rect'] as $v) {
+ $res .= sprintf("%.4F ", $v);
+ }
+
+ $res .= "]";
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * a page object, it also creates a contents object to hold its contents
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_page($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->numPages++;
+ $this->objects[$id] = [
+ 't' => 'page',
+ 'info' => [
+ 'parent' => $this->currentNode,
+ 'pageNum' => $this->numPages,
+ 'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
+ ]
+ ];
+
+ if (is_array($options)) {
+ // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
+ $options['id'] = $id;
+ $this->o_pages($this->currentNode, 'page', $options);
+ } else {
+ $this->o_pages($this->currentNode, 'page', $id);
+ }
+
+ $this->currentPage = $id;
+ //make a contents object to go with this page
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new', $id);
+ $this->currentContents = $this->numObj;
+ $this->objects[$id]['info']['contents'] = [];
+ $this->objects[$id]['info']['contents'][] = $this->numObj;
+
+ $match = ($this->numPages % 2 ? 'odd' : 'even');
+ foreach ($this->addLooseObjects as $oId => $target) {
+ if ($target === 'all' || $match === $target) {
+ $this->objects[$id]['info']['contents'][] = $oId;
+ }
+ }
+ break;
+
+ case 'content':
+ $o['info']['contents'][] = $options;
+ break;
+
+ case 'annot':
+ // add an annotation to this page
+ if (!isset($o['info']['annot'])) {
+ $o['info']['annot'] = [];
+ }
+
+ // $options should contain the id of the annotation dictionary
+ $o['info']['annot'][] = $options;
+ break;
+
+ case 'out':
+ $res = "\n$id 0 obj\n<< /Type /Page";
+ if (isset($o['info']['mediaBox'])) {
+ $tmp = $o['info']['mediaBox'];
+ $res .= "\n/MediaBox [" . sprintf(
+ '%.3F %.3F %.3F %.3F',
+ $tmp[0],
+ $tmp[1],
+ $tmp[2],
+ $tmp[3]
+ ) . ']';
+ }
+ $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
+
+ if (isset($o['info']['annot'])) {
+ $res .= "\n/Annots [";
+ foreach ($o['info']['annot'] as $aId) {
+ $res .= " $aId 0 R";
+ }
+ $res .= " ]";
+ }
+
+ $count = count($o['info']['contents']);
+ if ($count == 1) {
+ $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
+ } else {
+ if ($count > 1) {
+ $res .= "\n/Contents [\n";
+
+ // reverse the page contents so added objects are below normal content
+ //foreach (array_reverse($o['info']['contents']) as $cId) {
+ // Back to normal now that I've got transparency working --Benj
+ foreach ($o['info']['contents'] as $cId) {
+ $res .= "$cId 0 R\n";
+ }
+ $res .= "]";
+ }
+ }
+
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * the contents objects hold all of the content which appears on pages
+ *
+ * @param $id
+ * @param $action
+ * @param string|array $options
+ * @return null|string
+ */
+ protected function o_contents($id, $action, $options = '')
+ {
+ if ($action !== 'new') {
+ $o = &$this->objects[$id];
+ }
+
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
+ if (mb_strlen($options, '8bit') && intval($options)) {
+ // then this contents is the primary for a page
+ $this->objects[$id]['onPage'] = $options;
+ } else {
+ if ($options === 'raw') {
+ // then this page contains some other type of system object
+ $this->objects[$id]['raw'] = 1;
+ }
+ }
+ break;
+
+ case 'add':
+ // add more options to the declaration
+ foreach ($options as $k => $v) {
+ $o['info'][$k] = $v;
+ }
+
+ case 'out':
+ $tmp = $o['c'];
+ $res = "\n$id 0 obj\n";
+
+ if (isset($this->objects[$id]['raw'])) {
+ $res .= $tmp;
+ } else {
+ $res .= "<<";
+ if ($this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on this content stream
+ $res .= " /Filter /FlateDecode";
+ $tmp = gzcompress($tmp, 6);
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
+ }
+
+ $res .= "\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @return string|null
+ */
+ protected function o_embedjs($id, $action)
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'embedjs',
+ 'info' => [
+ 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
+ ]
+ ];
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< ";
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @param string $code
+ * @return null|string
+ */
+ protected function o_javascript($id, $action, $code = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = [
+ 't' => 'javascript',
+ 'info' => [
+ 'S' => '/JavaScript',
+ 'JS' => '(' . $this->filterText($code, true, false) . ')',
+ ]
+ ];
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< ";
+
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * an image object, will be an XObject in the document, includes description and data
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_image($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ // make the new object
+ $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
+
+ $info =& $this->objects[$id]['info'];
+
+ $info['Type'] = '/XObject';
+ $info['Subtype'] = '/Image';
+ $info['Width'] = $options['iw'];
+ $info['Height'] = $options['ih'];
+
+ if (isset($options['masked']) && $options['masked']) {
+ $info['SMask'] = ($this->numObj - 1) . ' 0 R';
+ }
+
+ if (!isset($options['type']) || $options['type'] === 'jpg') {
+ if (!isset($options['channels'])) {
+ $options['channels'] = 3;
+ }
+
+ switch ($options['channels']) {
+ case 1:
+ $info['ColorSpace'] = '/DeviceGray';
+ break;
+ case 4:
+ $info['ColorSpace'] = '/DeviceCMYK';
+ break;
+ default:
+ $info['ColorSpace'] = '/DeviceRGB';
+ break;
+ }
+
+ if ($info['ColorSpace'] === '/DeviceCMYK') {
+ $info['Decode'] = '[1 0 1 0 1 0 1 0]';
+ }
+
+ $info['Filter'] = '/DCTDecode';
+ $info['BitsPerComponent'] = 8;
+ } else {
+ if ($options['type'] === 'png') {
+ $info['Filter'] = '/FlateDecode';
+ $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
+
+ if ($options['isMask']) {
+ $info['ColorSpace'] = '/DeviceGray';
+ } else {
+ if (mb_strlen($options['pdata'], '8bit')) {
+ $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new');
+ $this->objects[$this->numObj]['c'] = $options['pdata'];
+ $tmp .= $this->numObj . ' 0 R';
+ $tmp .= ' ]';
+ $info['ColorSpace'] = $tmp;
+
+ if (isset($options['transparency'])) {
+ $transparency = $options['transparency'];
+ switch ($transparency['type']) {
+ case 'indexed':
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
+ $info['Mask'] = $tmp;
+ break;
+
+ case 'color-key':
+ $tmp = ' [ ' .
+ $transparency['r'] . ' ' . $transparency['r'] .
+ $transparency['g'] . ' ' . $transparency['g'] .
+ $transparency['b'] . ' ' . $transparency['b'] .
+ ' ] ';
+ $info['Mask'] = $tmp;
+ break;
+ }
+ }
+ } else {
+ if (isset($options['transparency'])) {
+ $transparency = $options['transparency'];
+
+ switch ($transparency['type']) {
+ case 'indexed':
+ $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
+ $info['Mask'] = $tmp;
+ break;
+
+ case 'color-key':
+ $tmp = ' [ ' .
+ $transparency['r'] . ' ' . $transparency['r'] . ' ' .
+ $transparency['g'] . ' ' . $transparency['g'] . ' ' .
+ $transparency['b'] . ' ' . $transparency['b'] .
+ ' ] ';
+ $info['Mask'] = $tmp;
+ break;
+ }
+ }
+ $info['ColorSpace'] = '/' . $options['color'];
+ }
+ }
+
+ $info['BitsPerComponent'] = $options['bitsPerComponent'];
+ }
+ }
+
+ // assign it a place in the named resource dictionary as an external object, according to
+ // the label passed in with it.
+ $this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
+
+ // also make sure that we have the right procset object for it.
+ $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $tmp = &$o['data'];
+ $res = "\n$id 0 obj\n<<";
+
+ foreach ($o['info'] as $k => $v) {
+ $res .= "\n/$k $v";
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * graphics state object
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_extGState($id, $action, $options = "")
+ {
+ static $valid_params = [
+ "LW",
+ "LC",
+ "LC",
+ "LJ",
+ "ML",
+ "D",
+ "RI",
+ "OP",
+ "op",
+ "OPM",
+ "Font",
+ "BG",
+ "BG2",
+ "UCR",
+ "TR",
+ "TR2",
+ "HT",
+ "FL",
+ "SM",
+ "SA",
+ "BM",
+ "SMask",
+ "CA",
+ "ca",
+ "AIS",
+ "TK"
+ ];
+
+ switch ($action) {
+ case "new":
+ $this->objects[$id] = ['t' => 'extGState', 'info' => $options];
+
+ // Tell the pages about the new resource
+ $this->numStates++;
+ $this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
+ break;
+
+ case "out":
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
+
+ foreach ($o["info"] as $k => $v) {
+ if (!in_array($k, $valid_params)) {
+ continue;
+ }
+ $res .= "/$k $v\n";
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param integer $id
+ * @param string $action
+ * @param mixed $options
+ * @return string
+ */
+ protected function o_xobject($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
+ break;
+
+ case 'procset':
+ $this->objects[$id]['procset'] = $options;
+ break;
+
+ case 'font':
+ $this->objects[$id]['fonts'][$options['fontNum']] = [
+ 'objNum' => $options['objNum'],
+ 'fontNum' => $options['fontNum']
+ ];
+ break;
+
+ case 'xObject':
+ $this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< /Type /XObject\n";
+
+ foreach ($o["info"] as $k => $v) {
+ switch($k)
+ {
+ case 'Subtype':
+ $res .= "/Subtype /$v\n";
+ break;
+ case 'bbox':
+ $res .= "/BBox [";
+ foreach ($v as $value) {
+ $res .= sprintf("%.4F ", $value);
+ }
+ $res .= "]\n";
+ break;
+ default:
+ $res .= "/$k $v\n";
+ break;
+ }
+ }
+ $res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
+
+ $res .= "/Resources <<";
+ if (isset($o['procset'])) {
+ $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
+ } else {
+ $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
+ }
+ if (isset($o['fonts']) && count($o['fonts'])) {
+ $res .= "\n/Font << ";
+ foreach ($o['fonts'] as $finfo) {
+ $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+ if (isset($o['xObjects']) && count($o['xObjects'])) {
+ $res .= "\n/XObject << ";
+ foreach ($o['xObjects'] as $finfo) {
+ $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
+ }
+ $res .= "\n>>";
+ }
+ $res .= "\n>>\n";
+
+ $tmp = $o["c"];
+ if ($this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on this content stream
+ $res .= " /Filter /FlateDecode\n";
+ $tmp = gzcompress($tmp, 6);
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $tmp = $this->ARC4($tmp);
+ }
+
+ $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
+ $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";;
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_acroform($id, $action, $options = '')
+ {
+ switch ($action) {
+ case "new":
+ $this->o_catalog($this->catalogId, 'acroform', $id);
+ $this->objects[$id] = array('t' => 'acroform', 'info' => $options);
+ break;
+
+ case 'addfield':
+ $this->objects[$id]['info']['Fields'][] = $options;
+ break;
+
+ case 'font':
+ $this->objects[$id]['fonts'][$options['fontNum']] = [
+ 'objNum' => $options['objNum'],
+ 'fontNum' => $options['fontNum']
+ ];
+ break;
+
+ case "out":
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<<";
+
+ foreach ($o["info"] as $k => $v) {
+ switch($k) {
+ case 'Fields':
+ $res .= " /Fields [";
+ foreach ($v as $i) {
+ $res .= "$i 0 R ";
+ }
+ $res .= "]\n";
+ break;
+ default:
+ $res .= "/$k $v\n";
+ }
+ }
+
+ $res .= "/DR <<\n";
+ if (isset($o['fonts']) && count($o['fonts'])) {
+ $res .= "/Font << \n";
+ foreach ($o['fonts'] as $finfo) {
+ $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
+ }
+ $res .= ">>\n";
+ }
+ $res .= ">>\n";
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * @param $id
+ * @param $action
+ * @param mixed $options
+ * @return null|string
+ */
+ protected function o_field($id, $action, $options = '')
+ {
+ switch ($action) {
+ case "new":
+ $this->o_page($options['pageid'], 'annot', $id);
+ $this->o_acroform($this->acroFormId, 'addfield', $id);
+ $this->objects[$id] = ['t' => 'field', 'info' => $options];
+ break;
+
+ case 'set':
+ $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
+ break;
+
+ case "out":
+ $o = &$this->objects[$id];
+ $res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
+
+ $encrypted = $this->encrypted;
+ if ($encrypted) {
+ $this->encryptInit($id);
+ }
+
+ foreach ($o["info"] as $k => $v) {
+ switch ($k) {
+ case 'pageid':
+ $res .= "/P $v 0 R\n";
+ break;
+ case 'value':
+ if ($encrypted) {
+ $v = $this->filterText($this->ARC4($v), false, false);
+ }
+ $res .= "/V ($v)\n";
+ break;
+ case 'refvalue':
+ $res .= "/V $v 0 R\n";
+ break;
+ case 'da':
+ if ($encrypted) {
+ $v = $this->filterText($this->ARC4($v), false, false);
+ }
+ $res .= "/DA ($v)\n";
+ break;
+ case 'options':
+ $res .= "/Opt [\n";
+ foreach ($v as $opt) {
+ if ($encrypted) {
+ $opt = $this->filterText($this->ARC4($opt), false, false);
+ }
+ $res .= "($opt)\n";
+ }
+ $res .= "]\n";
+ break;
+ case 'rect':
+ $res .= "/Rect [";
+ foreach ($v as $value) {
+ $res .= sprintf("%.4F ", $value);
+ }
+ $res .= "]\n";
+ break;
+ case 'appearance':
+ $res .= "/AP << ";
+ foreach ($v as $a => $ref) {
+ $res .= "/$a $ref 0 R ";
+ }
+ $res .= ">>\n";
+ break;
+ case 'T':
+ if($encrypted) {
+ $v = $this->filterText($this->ARC4($v), false, false);
+ }
+ $res .= "/T ($v)\n";
+ break;
+ default:
+ $res .= "/$k $v\n";
+ }
+
+ }
+
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return null|string
+ */
+ protected function o_sig($id, $action, $options = '')
+ {
+ $sign_maxlen = $this->signatureMaxLen;
+
+ switch ($action) {
+ case "new":
+ $this->objects[$id] = array('t' => 'sig', 'info' => $options);
+ $this->byteRange[$id] = ['t' => 'sig'];
+ break;
+
+ case 'byterange':
+ $o = &$this->objects[$id];
+ $content =& $options['content'];
+ $content_len = strlen($content);
+ $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
+ $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
+ $rangeStartPos = $pos + $len + 1 + 10; // before '<'
+ $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos ), $len, ' ', STR_PAD_RIGHT), $pos, $len);
+
+ $fuid = uniqid();
+ $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
+ $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
+
+ if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
+ throw new \Exception("Unable to write temporary file for signing.");
+ }
+ if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
+ FILE_APPEND) === false) {
+ throw new \Exception("Unable to write temporary file for signing.");
+ }
+
+ if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
+ $o['info']['SignCert'],
+ array($o['info']['PrivKey'], $o['info']['Password']),
+ array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
+ throw new \Exception("Failed to prepare signature.");
+ }
+
+ $signature = file_get_contents($tmpOutput);
+
+ unlink($tmpInput);
+ unlink($tmpOutput);
+
+ $sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
+ list($head, $signature) = explode("\n\n", $sign);
+
+ $signature = base64_decode(trim($signature));
+
+ $signature = current(unpack('H*', $signature));
+ $signature = str_pad($signature, $sign_maxlen, '0');
+ $siglen = strlen($signature);
+ if (strlen($signature) > $sign_maxlen) {
+ throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
+ }
+
+ $content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
+ break;
+
+ case "out":
+ $res = "\n$id 0 obj\n<<\n";
+
+ $encrypted = $this->encrypted;
+ if ($encrypted) {
+ $this->encryptInit($id);
+ }
+
+ $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
+ $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
+ $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
+ $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
+
+ $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
+ if ($encrypted) {
+ $date = $this->ARC4($date);
+ }
+
+ $res .= "/M ($date)\n";
+ $res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
+
+ $o = &$this->objects[$id];
+ foreach ($o['info'] as $k => $v) {
+ switch($k) {
+ case 'Name':
+ case 'Location':
+ case 'Reason':
+ case 'ContactInfo':
+ if ($v !== null && $v !== '') {
+ $res .= "/$k (" .
+ ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
+ }
+ break;
+ }
+ }
+ $res .= ">>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * encryption object.
+ *
+ * @param $id
+ * @param $action
+ * @param string $options
+ * @return string|null
+ */
+ protected function o_encryption($id, $action, $options = '')
+ {
+ switch ($action) {
+ case 'new':
+ // make the new object
+ $this->objects[$id] = ['t' => 'encryption', 'info' => $options];
+ $this->arc4_objnum = $id;
+ break;
+
+ case 'keys':
+ // figure out the additional parameters required
+ $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
+ . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
+ . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
+ . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
+
+ $info = $this->objects[$id]['info'];
+
+ $len = mb_strlen($info['owner'], '8bit');
+
+ if ($len > 32) {
+ $owner = substr($info['owner'], 0, 32);
+ } else {
+ if ($len < 32) {
+ $owner = $info['owner'] . substr($pad, 0, 32 - $len);
+ } else {
+ $owner = $info['owner'];
+ }
+ }
+
+ $len = mb_strlen($info['user'], '8bit');
+ if ($len > 32) {
+ $user = substr($info['user'], 0, 32);
+ } else {
+ if ($len < 32) {
+ $user = $info['user'] . substr($pad, 0, 32 - $len);
+ } else {
+ $user = $info['user'];
+ }
+ }
+
+ $tmp = $this->md5_16($owner);
+ $okey = substr($tmp, 0, 5);
+ $this->ARC4_init($okey);
+ $ovalue = $this->ARC4($user);
+ $this->objects[$id]['info']['O'] = $ovalue;
+
+ // now make the u value, phew.
+ $tmp = $this->md5_16(
+ $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
+ );
+
+ $ukey = substr($tmp, 0, 5);
+ $this->ARC4_init($ukey);
+ $this->encryptionKey = $ukey;
+ $this->encrypted = true;
+ $uvalue = $this->ARC4($pad);
+ $this->objects[$id]['info']['U'] = $uvalue;
+ // initialize the arc4 array
+ break;
+
+ case 'out':
+ $o = &$this->objects[$id];
+
+ $res = "\n$id 0 obj\n<<";
+ $res .= "\n/Filter /Standard";
+ $res .= "\n/V 1";
+ $res .= "\n/R 2";
+ $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
+ $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
+ // and the p-value needs to be converted to account for the twos-complement approach
+ $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
+ $res .= "\n/P " . ($o['info']['p']);
+ $res .= "\n>>\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_indirect_references($id, $action, $options = null)
+ {
+ switch ($action) {
+ case 'new':
+ case 'add':
+ if ($id === 0) {
+ $id = ++$this->numObj;
+ $this->o_catalog($this->catalogId, 'names', $id);
+ $this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
+ $this->indirectReferenceId = $id;
+ } else {
+ $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
+ }
+ break;
+ case 'out':
+ $res = "\n$id 0 obj << ";
+
+ foreach($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
+ $res .= "/$referenceObjName $referenceObjId 0 R ";
+ }
+
+ $res .= ">> endobj";
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_names($id, $action, $options = null)
+ {
+ switch ($action) {
+ case 'new':
+ case 'add':
+ if ($id === 0) {
+ $id = ++$this->numObj;
+ $this->objects[$id] = ['t' => 'names', 'info' => [$options]];
+ $this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
+ $this->embeddedFilesId = $id;
+ } else {
+ $this->objects[$id]['info'][] = $options;
+ }
+ break;
+ case 'out':
+ $info = &$this->objects[$id]['info'];
+ $res = '';
+ if (count($info) > 0) {
+ $res = "\n$id 0 obj << /Names [ ";
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ }
+
+ foreach ($info as $entry) {
+ if ($this->encrypted) {
+ $filename = $this->ARC4($entry['filename']);
+ } else {
+ $filename = $entry['filename'];
+ }
+
+ $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
+ }
+
+ $res .= "] >> endobj";
+ }
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_embedded_file_dictionary($id, $action, $options = null)
+ {
+ switch ($action) {
+ case 'new':
+ $embeddedFileId = ++$this->numObj;
+ $options['embedded_reference'] = $embeddedFileId;
+ $this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
+ $this->o_embedded_file($embeddedFileId, 'new', $options);
+ $options['dict_reference'] = $id;
+ $this->o_names($this->embeddedFilesId, 'add', $options);
+ break;
+ case 'out':
+ $info = &$this->objects[$id]['info'];
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $filename = $this->ARC4($info['filename']);
+ $description = $this->ARC4($info['description']);
+ } else {
+ $filename = $info['filename'];
+ $description = $info['description'];
+ }
+
+ $res = "\n$id 0 obj <</Type /Filespec /EF";
+ $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
+ $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
+ $res .= " >> endobj";
+ return $res;
+ }
+
+ return null;
+ }
+
+ protected function o_embedded_file($id, $action, $options = null): ?string
+ {
+ switch ($action) {
+ case 'new':
+ $this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
+ break;
+ case 'out':
+ $info = &$this->objects[$id]['info'];
+
+ if ($this->compressionReady) {
+ $filepath = $info['filepath'];
+ $checksum = md5_file($filepath);
+ $f = fopen($filepath, "rb");
+
+ $file_content_compressed = '';
+ $deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
+ while (($block = fread($f, 8192))) {
+ $file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
+ }
+ $file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
+ $file_size_uncompressed = ftell($f);
+ fclose($f);
+ } else {
+ $file_content = file_get_contents($info['filepath']);
+ $file_size_uncompressed = mb_strlen($file_content, '8bit');
+ $checksum = md5($file_content);
+ }
+
+ if ($this->encrypted) {
+ $this->encryptInit($id);
+ $checksum = $this->ARC4($checksum);
+ $file_content_compressed = $this->ARC4($file_content_compressed);
+ }
+ $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
+
+ $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
+ " /Type/EmbeddedFile /Filter/FlateDecode" .
+ " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
+
+ return $res;
+ }
+
+ return null;
+ }
+
+ /**
+ * ARC4 functions
+ * A series of function to implement ARC4 encoding in PHP
+ */
+
+ /**
+ * calculate the 16 byte version of the 128 bit md5 digest of the string
+ *
+ * @param $string
+ * @return string
+ */
+ function md5_16($string)
+ {
+ $tmp = md5($string);
+ $out = '';
+ for ($i = 0; $i <= 30; $i = $i + 2) {
+ $out .= chr(hexdec(substr($tmp, $i, 2)));
+ }
+
+ return $out;
+ }
+
+ /**
+ * initialize the encryption for processing a particular object
+ *
+ * @param $id
+ */
+ function encryptInit($id)
+ {
+ $tmp = $this->encryptionKey;
+ $hex = dechex($id);
+ if (mb_strlen($hex, '8bit') < 6) {
+ $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
+ }
+ $tmp .= chr(hexdec(substr($hex, 4, 2)))
+ . chr(hexdec(substr($hex, 2, 2)))
+ . chr(hexdec(substr($hex, 0, 2)))
+ . chr(0)
+ . chr(0)
+ ;
+ $key = $this->md5_16($tmp);
+ $this->ARC4_init(substr($key, 0, 10));
+ }
+
+ /**
+ * initialize the ARC4 encryption
+ *
+ * @param string $key
+ */
+ function ARC4_init($key = '')
+ {
+ $this->arc4 = '';
+
+ // setup the control array
+ if (mb_strlen($key, '8bit') == 0) {
+ return;
+ }
+
+ $k = '';
+ while (mb_strlen($k, '8bit') < 256) {
+ $k .= $key;
+ }
+
+ $k = substr($k, 0, 256);
+ for ($i = 0; $i < 256; $i++) {
+ $this->arc4 .= chr($i);
+ }
+
+ $j = 0;
+
+ for ($i = 0; $i < 256; $i++) {
+ $t = $this->arc4[$i];
+ $j = ($j + ord($t) + ord($k[$i])) % 256;
+ $this->arc4[$i] = $this->arc4[$j];
+ $this->arc4[$j] = $t;
+ }
+ }
+
+ /**
+ * ARC4 encrypt a text string
+ *
+ * @param $text
+ * @return string
+ */
+ function ARC4($text)
+ {
+ $len = mb_strlen($text, '8bit');
+ $a = 0;
+ $b = 0;
+ $c = $this->arc4;
+ $out = '';
+ for ($i = 0; $i < $len; $i++) {
+ $a = ($a + 1) % 256;
+ $t = $c[$a];
+ $b = ($b + ord($t)) % 256;
+ $c[$a] = $c[$b];
+ $c[$b] = $t;
+ $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
+ $out .= chr(ord($text[$i]) ^ $k);
+ }
+
+ return $out;
+ }
+
+ /**
+ * functions which can be called to adjust or add to the document
+ */
+
+ /**
+ * add a link in the document to an external URL
+ *
+ * @param $url
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ */
+ function addLink($url, $x0, $y0, $x1, $y1)
+ {
+ $this->numObj++;
+ $info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
+ $this->o_annotation($this->numObj, 'new', $info);
+ }
+
+ /**
+ * add a link in the document to an internal destination (ie. within the document)
+ *
+ * @param $label
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ */
+ function addInternalLink($label, $x0, $y0, $x1, $y1)
+ {
+ $this->numObj++;
+ $info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
+ $this->o_annotation($this->numObj, 'new', $info);
+ }
+
+ /**
+ * set the encryption of the document
+ * can be used to turn it on and/or set the passwords which it will have.
+ * also the functions that the user will have are set here, such as print, modify, add
+ *
+ * @param string $userPass
+ * @param string $ownerPass
+ * @param array $pc
+ */
+ function setEncryption($userPass = '', $ownerPass = '', $pc = [])
+ {
+ $p = bindec("11000000");
+
+ $options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
+
+ foreach ($pc as $k => $v) {
+ if ($v && isset($options[$k])) {
+ $p += $options[$k];
+ } else {
+ if (isset($options[$v])) {
+ $p += $options[$v];
+ }
+ }
+ }
+
+ // implement encryption on the document
+ if ($this->arc4_objnum == 0) {
+ // then the block does not exist already, add it.
+ $this->numObj++;
+ if (mb_strlen($ownerPass) == 0) {
+ $ownerPass = $userPass;
+ }
+
+ $this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
+ }
+ }
+
+ /**
+ * should be used for internal checks, not implemented as yet
+ */
+ function checkAllHere()
+ {
+ }
+
+ /**
+ * return the pdf stream as a string returned from the function
+ *
+ * @param bool $debug
+ * @return string
+ */
+ function output($debug = false)
+ {
+ if ($debug) {
+ // turn compression off
+ $this->options['compression'] = false;
+ }
+
+ if ($this->javascript) {
+ $this->numObj++;
+
+ $js_id = $this->numObj;
+ $this->o_embedjs($js_id, 'new');
+ $this->o_javascript(++$this->numObj, 'new', $this->javascript);
+
+ $id = $this->catalogId;
+
+ $this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
+ }
+
+ if ($this->fileIdentifier === '') {
+ $tmp = implode('', $this->objects[$this->infoObject]['info']);
+ $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
+ }
+
+ if ($this->arc4_objnum) {
+ $this->o_encryption($this->arc4_objnum, 'keys');
+ $this->ARC4_init($this->encryptionKey);
+ }
+
+ $this->checkAllHere();
+
+ $xref = [];
+ $content = '%PDF-' . self::PDF_VERSION;
+ $pos = mb_strlen($content, '8bit');
+
+ // pre-process o_font objects before output of all objects
+ foreach ($this->objects as $k => $v) {
+ if ($v['t'] === 'font') {
+ $this->o_font($k, 'add');
+ }
+ }
+
+ foreach ($this->objects as $k => $v) {
+ $tmp = 'o_' . $v['t'];
+ $cont = $this->$tmp($k, 'out');
+ $content .= $cont;
+ $xref[] = $pos + 1; //+1 to account for \n at the start of each object
+ $pos += mb_strlen($cont, '8bit');
+ }
+
+ $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
+
+ foreach ($xref as $p) {
+ $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
+ }
+
+ $content .= "trailer\n<<\n" .
+ '/Size ' . (count($xref) + 1) . "\n" .
+ '/Root 1 0 R' . "\n" .
+ '/Info ' . $this->infoObject . " 0 R\n"
+ ;
+
+ // if encryption has been applied to this document then add the marker for this dictionary
+ if ($this->arc4_objnum > 0) {
+ $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
+ }
+
+ $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
+
+ // account for \n added at start of xref table
+ $pos++;
+
+ $content .= ">>\nstartxref\n$pos\n%%EOF\n";
+
+ if (count($this->byteRange) > 0) {
+ foreach ($this->byteRange as $k => $v) {
+ $tmp = 'o_' . $v['t'];
+ $this->$tmp($k, 'byterange', ['content' => &$content]);
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * initialize a new document
+ * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
+ * this function is called automatically by the constructor function
+ *
+ * @param array $pageSize
+ */
+ private function newDocument($pageSize = [0, 0, 612, 792])
+ {
+ $this->numObj = 0;
+ $this->objects = [];
+
+ $this->numObj++;
+ $this->o_catalog($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_outlines($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_pages($this->numObj, 'new');
+
+ $this->o_pages($this->numObj, 'mediaBox', $pageSize);
+ $this->currentNode = 3;
+
+ $this->numObj++;
+ $this->o_procset($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_info($this->numObj, 'new');
+
+ $this->numObj++;
+ $this->o_page($this->numObj, 'new');
+
+ // need to store the first page id as there is no way to get it to the user during
+ // startup
+ $this->firstPageId = $this->currentContents;
+ }
+
+ /**
+ * open the font file and return a php structure containing it.
+ * first check if this one has been done before and saved in a form more suited to php
+ * note that if a php serialized version does not exist it will try and make one, but will
+ * require write access to the directory to do it... it is MUCH faster to have these serialized
+ * files.
+ *
+ * @param $font
+ */
+ private function openFont($font)
+ {
+ // assume that $font contains the path and file but not the extension
+ $name = basename($font);
+ $dir = dirname($font) . '/';
+
+ $fontcache = $this->fontcache;
+ if ($fontcache == '') {
+ $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
+ }
+
+ //$name filename without folder and extension of font metrics
+ //$dir folder of font metrics
+ //$fontcache folder of runtime created php serialized version of font metrics.
+ // If this is not given, the same folder as the font metrics will be used.
+ // Storing and reusing serialized versions improves speed much
+
+ $this->addMessage("openFont: $font - $name");
+
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
+ $metrics_name = "$name.afm";
+ } else {
+ $metrics_name = "$name.ufm";
+ }
+
+ $cache_name = "$metrics_name.php";
+ $this->addMessage("metrics: $metrics_name, cache: $cache_name");
+
+ if (file_exists($fontcache . '/' . $cache_name)) {
+ $this->addMessage("openFont: php file exists $fontcache/$cache_name");
+ $this->fonts[$font] = require($fontcache . '/' . $cache_name);
+
+ if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
+ // if the font file is old, then clear it out and prepare for re-creation
+ $this->addMessage('openFont: clear out, make way for new version.');
+ $this->fonts[$font] = null;
+ unset($this->fonts[$font]);
+ }
+ } else {
+ $old_cache_name = "php_$metrics_name";
+ if (file_exists($fontcache . '/' . $old_cache_name)) {
+ $this->addMessage(
+ "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
+ );
+ $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
+ file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
+
+ $this->openFont($font);
+ return;
+ }
+ }
+
+ if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
+ // then rebuild the php_<font>.afm file from the <font>.afm file
+ $this->addMessage("openFont: build php file from $dir$metrics_name");
+ $data = [];
+
+ // 20 => 'space'
+ $data['codeToName'] = [];
+
+ // Since we're not going to enable Unicode for the core fonts we need to use a font-based
+ // setting for Unicode support rather than a global setting.
+ $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
+
+ $cidtogid = '';
+ if ($data['isUnicode']) {
+ $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
+ }
+
+ $file = file($dir . $metrics_name);
+
+ foreach ($file as $rowA) {
+ $row = trim($rowA);
+ $pos = strpos($row, ' ');
+
+ if ($pos) {
+ // then there must be some keyword
+ $key = substr($row, 0, $pos);
+ switch ($key) {
+ case 'FontName':
+ case 'FullName':
+ case 'FamilyName':
+ case 'PostScriptName':
+ case 'Weight':
+ case 'ItalicAngle':
+ case 'IsFixedPitch':
+ case 'CharacterSet':
+ case 'UnderlinePosition':
+ case 'UnderlineThickness':
+ case 'Version':
+ case 'EncodingScheme':
+ case 'CapHeight':
+ case 'XHeight':
+ case 'Ascender':
+ case 'Descender':
+ case 'StdHW':
+ case 'StdVW':
+ case 'StartCharMetrics':
+ case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big.
+ $data[$key] = trim(substr($row, $pos));
+ break;
+
+ case 'FontBBox':
+ $data[$key] = explode(' ', trim(substr($row, $pos)));
+ break;
+
+ //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
+ case 'C': // Found in AFM files
+ $bits = explode(';', trim($row));
+ $dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
+
+ foreach ($bits as $bit) {
+ $bits2 = explode(' ', trim($bit));
+ if (mb_strlen($bits2[0], '8bit') == 0) {
+ continue;
+ }
+
+ if (count($bits2) > 2) {
+ $dtmp[$bits2[0]] = [];
+ for ($i = 1; $i < count($bits2); $i++) {
+ $dtmp[$bits2[0]][] = $bits2[$i];
+ }
+ } else {
+ if (count($bits2) == 2) {
+ $dtmp[$bits2[0]] = $bits2[1];
+ }
+ }
+ }
+
+ $c = (int)$dtmp['C'];
+ $n = $dtmp['N'];
+ $width = floatval($dtmp['WX']);
+
+ if ($c >= 0) {
+ if (!ctype_xdigit($n) || $c != hexdec($n)) {
+ $data['codeToName'][$c] = $n;
+ }
+ $data['C'][$c] = $width;
+ } elseif (isset($n)) {
+ $data['C'][$n] = $width;
+ }
+
+ if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
+ $data['MissingWidth'] = $width;
+ }
+
+ break;
+
+ // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
+ case 'U': // Found in UFM files
+ if (!$data['isUnicode']) {
+ break;
+ }
+
+ $bits = explode(';', trim($row));
+ $dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
+
+ foreach ($bits as $bit) {
+ $bits2 = explode(' ', trim($bit));
+ if (mb_strlen($bits2[0], '8bit') === 0) {
+ continue;
+ }
+
+ if (count($bits2) > 2) {
+ $dtmp[$bits2[0]] = [];
+ for ($i = 1; $i < count($bits2); $i++) {
+ $dtmp[$bits2[0]][] = $bits2[$i];
+ }
+ } else {
+ if (count($bits2) == 2) {
+ $dtmp[$bits2[0]] = $bits2[1];
+ }
+ }
+ }
+
+ $c = (int)$dtmp['U'];
+ $n = $dtmp['N'];
+ $glyph = $dtmp['G'];
+ $width = floatval($dtmp['WX']);
+
+ if ($c >= 0) {
+ // Set values in CID to GID map
+ if ($c >= 0 && $c < 0xFFFF && $glyph) {
+ $cidtogid[$c * 2] = chr($glyph >> 8);
+ $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
+ }
+
+ if (!ctype_xdigit($n) || $c != hexdec($n)) {
+ $data['codeToName'][$c] = $n;
+ }
+ $data['C'][$c] = $width;
+ } elseif (isset($n)) {
+ $data['C'][$n] = $width;
+ }
+
+ if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
+ $data['MissingWidth'] = $width;
+ }
+
+ break;
+
+ case 'KPX':
+ break; // don't include them as they are not used yet
+ //KPX Adieresis yacute -40
+ /*$bits = explode(' ', trim($row));
+ $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
+ break;*/
+ }
+ }
+ }
+
+ if ($this->compressionReady && $this->options['compression']) {
+ // then implement ZLIB based compression on CIDtoGID string
+ $data['CIDtoGID_Compressed'] = true;
+ $cidtogid = gzcompress($cidtogid, 6);
+ }
+ $data['CIDtoGID'] = base64_encode($cidtogid);
+ $data['_version_'] = $this->fontcacheVersion;
+ $this->fonts[$font] = $data;
+
+ //Because of potential trouble with php safe mode, expect that the folder already exists.
+ //If not existing, this will hit performance because of missing cached results.
+ if (is_dir($fontcache) && is_writable($fontcache)) {
+ file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
+ }
+ $data = null;
+ }
+
+ if (!isset($this->fonts[$font])) {
+ $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
+ }
+
+ //pre_r($this->messages);
+ }
+
+ /**
+ * if the font is not loaded then load it and make the required object
+ * else just make it the current font
+ * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
+ * note that encoding='none' will need to be used for symbolic fonts
+ * and 'differences' => an array of mappings between numbers 0->255 and character names.
+ *
+ * @param $fontName
+ * @param string $encoding
+ * @param bool $set
+ * @param bool $isSubsetting
+ * @return int
+ * @throws FontNotFoundException
+ */
+ function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
+ {
+ if ($fontName === null || $fontName === '') {
+ return $this->currentFontNum;
+ }
+
+ $ext = substr($fontName, -4);
+ if ($ext === '.afm' || $ext === '.ufm') {
+ $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
+ }
+
+ if (!isset($this->fonts[$fontName])) {
+ $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
+
+ // load the file
+ $this->openFont($fontName);
+
+ if (isset($this->fonts[$fontName])) {
+ $this->numObj++;
+ $this->numFonts++;
+
+ $font = &$this->fonts[$fontName];
+
+ $name = basename($fontName);
+ $options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
+
+ if (is_array($encoding)) {
+ // then encoding and differences might be set
+ if (isset($encoding['encoding'])) {
+ $options['encoding'] = $encoding['encoding'];
+ }
+
+ if (isset($encoding['differences'])) {
+ $options['differences'] = $encoding['differences'];
+ }
+ } else {
+ if (mb_strlen($encoding, '8bit')) {
+ // then perhaps only the encoding has been set
+ $options['encoding'] = $encoding;
+ }
+ }
+
+ $this->o_font($this->numObj, 'new', $options);
+
+ if (file_exists("$fontName.ttf")) {
+ $fileSuffix = 'ttf';
+ } elseif (file_exists("$fontName.TTF")) {
+ $fileSuffix = 'TTF';
+ } elseif (file_exists("$fontName.pfb")) {
+ $fileSuffix = 'pfb';
+ } elseif (file_exists("$fontName.PFB")) {
+ $fileSuffix = 'PFB';
+ } else {
+ $fileSuffix = '';
+ }
+
+ $font['fileSuffix'] = $fileSuffix;
+
+ $font['fontNum'] = $this->numFonts;
+ $font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
+
+ // also set the differences here, note that this means that these will take effect only the
+ //first time that a font is selected, else they are ignored
+ if (isset($options['differences'])) {
+ $font['differences'] = $options['differences'];
+ }
+ }
+ }
+
+ if ($set && isset($this->fonts[$fontName])) {
+ // so if for some reason the font was not set in the last one then it will not be selected
+ $this->currentBaseFont = $fontName;
+
+ // the next lines mean that if a new font is selected, then the current text state will be
+ // applied to it as well.
+ $this->currentFont = $this->currentBaseFont;
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
+ }
+
+ return $this->currentFontNum;
+ }
+
+ /**
+ * sets up the current font, based on the font families, and the current text state
+ * note that this system is quite flexible, a bold-italic font can be completely different to a
+ * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
+ * This function is to be called whenever the currentTextState is changed, it will update
+ * the currentFont setting to whatever the appropriate family one is.
+ * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
+ * This function will change the currentFont to whatever it should be, but will not change the
+ * currentBaseFont.
+ */
+ private function setCurrentFont()
+ {
+ // if (strlen($this->currentBaseFont) == 0){
+ // // then assume an initial font
+ // $this->selectFont($this->defaultFont);
+ // }
+ // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
+ // if (strlen($this->currentTextState)
+ // && isset($this->fontFamilies[$cf])
+ // && isset($this->fontFamilies[$cf][$this->currentTextState])){
+ // // then we are in some state or another
+ // // and this font has a family, and the current setting exists within it
+ // // select the font, then return it
+ // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
+ // $this->selectFont($nf,'',0);
+ // $this->currentFont = $nf;
+ // $this->currentFontNum = $this->fonts[$nf]['fontNum'];
+ // } else {
+ // // the this font must not have the right family member for the current state
+ // // simply assume the base font
+ $this->currentFont = $this->currentBaseFont;
+ $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
+ // }
+ }
+
+ /**
+ * function for the user to find out what the ID is of the first page that was created during
+ * startup - useful if they wish to add something to it later.
+ *
+ * @return int
+ */
+ function getFirstPageId()
+ {
+ return $this->firstPageId;
+ }
+
+ /**
+ * add content to the currently active object
+ *
+ * @param $content
+ */
+ private function addContent($content)
+ {
+ $this->objects[$this->currentContents]['c'] .= $content;
+ }
+
+ /**
+ * sets the color for fill operations
+ *
+ * @param $color
+ * @param bool $force
+ */
+ function setColor($color, $force = false)
+ {
+ $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
+
+ if (!$force && $this->currentColor == $new_color) {
+ return;
+ }
+
+ if (isset($new_color[3])) {
+ $this->currentColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
+ } else {
+ if (isset($new_color[2])) {
+ $this->currentColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
+ }
+ }
+ }
+
+ /**
+ * sets the color for fill operations
+ *
+ * @param $fillRule
+ */
+ function setFillRule($fillRule)
+ {
+ if (!in_array($fillRule, ["nonzero", "evenodd"])) {
+ return;
+ }
+
+ $this->fillRule = $fillRule;
+ }
+
+ /**
+ * sets the color for stroke operations
+ *
+ * @param $color
+ * @param bool $force
+ */
+ function setStrokeColor($color, $force = false)
+ {
+ $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
+
+ if (!$force && $this->currentStrokeColor == $new_color) {
+ return;
+ }
+
+ if (isset($new_color[3])) {
+ $this->currentStrokeColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
+ } else {
+ if (isset($new_color[2])) {
+ $this->currentStrokeColor = $new_color;
+ $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
+ }
+ }
+ }
+
+ /**
+ * Set the graphics state for compositions
+ *
+ * @param $parameters
+ */
+ function setGraphicsState($parameters)
+ {
+ // Create a new graphics state object if necessary
+ if (($gstate = array_search($parameters, $this->gstates)) === false) {
+ $this->numObj++;
+ $this->o_extGState($this->numObj, 'new', $parameters);
+ $gstate = $this->numStates;
+ $this->gstates[$gstate] = $parameters;
+ }
+ $this->addContent("\n/GS$gstate gs");
+ }
+
+ /**
+ * Set current blend mode & opacity for lines.
+ *
+ * Valid blend modes are:
+ *
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
+ * Exclusion
+ *
+ * @param string $mode the blend mode to use
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
+ */
+ function setLineTransparency($mode, $opacity)
+ {
+ static $blend_modes = [
+ "Normal",
+ "Multiply",
+ "Screen",
+ "Overlay",
+ "Darken",
+ "Lighten",
+ "ColorDogde",
+ "ColorBurn",
+ "HardLight",
+ "SoftLight",
+ "Difference",
+ "Exclusion"
+ ];
+
+ if (!in_array($mode, $blend_modes)) {
+ $mode = "Normal";
+ }
+
+ if (is_null($this->currentLineTransparency)) {
+ $this->currentLineTransparency = [];
+ }
+
+ if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
+ $this->currentLineTransparency['mode'] : '') &&
+ $opacity === (key_exists('opacity', $this->currentLineTransparency) ?
+ $this->currentLineTransparency["opacity"] : '')) {
+ return;
+ }
+
+ $this->currentLineTransparency["mode"] = $mode;
+ $this->currentLineTransparency["opacity"] = $opacity;
+
+ $options = [
+ "BM" => "/$mode",
+ "CA" => (float)$opacity
+ ];
+
+ $this->setGraphicsState($options);
+ }
+
+ /**
+ * Set current blend mode & opacity for filled objects.
+ *
+ * Valid blend modes are:
+ *
+ * Normal, Multiply, Screen, Overlay, Darken, Lighten,
+ * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
+ * Exclusion
+ *
+ * @param string $mode the blend mode to use
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
+ */
+ function setFillTransparency($mode, $opacity)
+ {
+ static $blend_modes = [
+ "Normal",
+ "Multiply",
+ "Screen",
+ "Overlay",
+ "Darken",
+ "Lighten",
+ "ColorDogde",
+ "ColorBurn",
+ "HardLight",
+ "SoftLight",
+ "Difference",
+ "Exclusion"
+ ];
+
+ if (!in_array($mode, $blend_modes)) {
+ $mode = "Normal";
+ }
+
+ if (is_null($this->currentFillTransparency)) {
+ $this->currentFillTransparency = [];
+ }
+
+ if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
+ $this->currentFillTransparency['mode'] : '') &&
+ $opacity === (key_exists('opacity', $this->currentFillTransparency) ?
+ $this->currentFillTransparency["opacity"] : '')) {
+ return;
+ }
+
+ $this->currentFillTransparency["mode"] = $mode;
+ $this->currentFillTransparency["opacity"] = $opacity;
+
+ $options = [
+ "BM" => "/$mode",
+ "ca" => (float)$opacity,
+ ];
+
+ $this->setGraphicsState($options);
+ }
+
+ /**
+ * draw a line from one set of coordinates to another
+ *
+ * @param $x1
+ * @param $y1
+ * @param $x2
+ * @param $y2
+ * @param bool $stroke
+ */
+ function line($x1, $y1, $x2, $y2, $stroke = true)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
+
+ if ($stroke) {
+ $this->addContent(' S');
+ }
+ }
+
+ /**
+ * draw a bezier curve based on 4 control points
+ *
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ * @param $x2
+ * @param $y2
+ * @param $x3
+ * @param $y3
+ */
+ function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+ {
+ // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
+ // as the control points for the curve.
+ $this->addContent(
+ sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+ );
+ }
+
+ /**
+ * draw a part of an ellipse
+ *
+ * @param $x0
+ * @param $y0
+ * @param $astart
+ * @param $afinish
+ * @param $r1
+ * @param int $r2
+ * @param int $angle
+ * @param int $nSeg
+ */
+ function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
+ {
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
+ }
+
+ /**
+ * draw a filled ellipse
+ *
+ * @param $x0
+ * @param $y0
+ * @param $r1
+ * @param int $r2
+ * @param int $angle
+ * @param int $nSeg
+ * @param int $astart
+ * @param int $afinish
+ */
+ function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
+ {
+ $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
+ }
+
+ /**
+ * @param $x
+ * @param $y
+ */
+ function lineTo($x, $y)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
+ }
+
+ /**
+ * @param $x
+ * @param $y
+ */
+ function moveTo($x, $y)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
+ }
+
+ /**
+ * draw a bezier curve based on 4 control points
+ *
+ * @param $x1
+ * @param $y1
+ * @param $x2
+ * @param $y2
+ * @param $x3
+ * @param $y3
+ */
+ function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
+ }
+
+ /**
+ * draw a bezier curve based on 4 control points
+ */
+ function quadTo($cpx, $cpy, $x, $y)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
+ }
+
+ function closePath()
+ {
+ $this->addContent(' h');
+ }
+
+ function endPath()
+ {
+ $this->addContent(' n');
+ }
+
+ /**
+ * draw an ellipse
+ * note that the part and filled ellipse are just special cases of this function
+ *
+ * draws an ellipse in the current line style
+ * centered at $x0,$y0, radii $r1,$r2
+ * if $r2 is not set, then a circle is drawn
+ * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
+ * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
+ * pretty crappy shape at 2, as we are approximating with bezier curves.
+ *
+ * @param $x0
+ * @param $y0
+ * @param $r1
+ * @param int $r2
+ * @param int $angle
+ * @param int $nSeg
+ * @param int $astart
+ * @param int $afinish
+ * @param bool $close
+ * @param bool $fill
+ * @param bool $stroke
+ * @param bool $incomplete
+ */
+ function ellipse(
+ $x0,
+ $y0,
+ $r1,
+ $r2 = 0,
+ $angle = 0,
+ $nSeg = 8,
+ $astart = 0,
+ $afinish = 360,
+ $close = true,
+ $fill = false,
+ $stroke = true,
+ $incomplete = false
+ ) {
+ if ($r1 == 0) {
+ return;
+ }
+
+ if ($r2 == 0) {
+ $r2 = $r1;
+ }
+
+ if ($nSeg < 2) {
+ $nSeg = 2;
+ }
+
+ $astart = deg2rad((float)$astart);
+ $afinish = deg2rad((float)$afinish);
+ $totalAngle = $afinish - $astart;
+
+ $dt = $totalAngle / $nSeg;
+ $dtm = $dt / 3;
+
+ if ($angle != 0) {
+ $a = -1 * deg2rad((float)$angle);
+
+ $this->addContent(
+ sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
+ );
+
+ $x0 = 0;
+ $y0 = 0;
+ }
+
+ $t1 = $astart;
+ $a0 = $x0 + $r1 * cos($t1);
+ $b0 = $y0 + $r2 * sin($t1);
+ $c0 = -$r1 * sin($t1);
+ $d0 = $r2 * cos($t1);
+
+ if (!$incomplete) {
+ $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
+ }
+
+ for ($i = 1; $i <= $nSeg; $i++) {
+ // draw this bit of the total curve
+ $t1 = $i * $dt + $astart;
+ $a1 = $x0 + $r1 * cos($t1);
+ $b1 = $y0 + $r2 * sin($t1);
+ $c1 = -$r1 * sin($t1);
+ $d1 = $r2 * cos($t1);
+
+ $this->addContent(
+ sprintf(
+ "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
+ ($a0 + $c0 * $dtm),
+ ($b0 + $d0 * $dtm),
+ ($a1 - $c1 * $dtm),
+ ($b1 - $d1 * $dtm),
+ $a1,
+ $b1
+ )
+ );
+
+ $a0 = $a1;
+ $b0 = $b1;
+ $c0 = $c1;
+ $d0 = $d1;
+ }
+
+ if (!$incomplete) {
+ if ($fill) {
+ $this->addContent(' f');
+ }
+
+ if ($stroke) {
+ if ($close) {
+ $this->addContent(' s'); // small 's' signifies closing the path as well
+ } else {
+ $this->addContent(' S');
+ }
+ }
+ }
+
+ if ($angle != 0) {
+ $this->addContent(' Q');
+ }
+ }
+
+ /**
+ * this sets the line drawing style.
+ * width, is the thickness of the line in user units
+ * cap is the type of cap to put on the line, values can be 'butt','round','square'
+ * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
+ * end of the line.
+ * join can be 'miter', 'round', 'bevel'
+ * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
+ * on and off dashes.
+ * (2) represents 2 on, 2 off, 2 on , 2 off ...
+ * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
+ * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
+ *
+ * @param int $width
+ * @param string $cap
+ * @param string $join
+ * @param string $dash
+ * @param int $phase
+ */
+ function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
+ {
+ // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
+ $string = '';
+
+ if ($width > 0) {
+ $string .= "$width w";
+ }
+
+ $ca = ['butt' => 0, 'round' => 1, 'square' => 2];
+
+ if (isset($ca[$cap])) {
+ $string .= " $ca[$cap] J";
+ }
+
+ $ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
+
+ if (isset($ja[$join])) {
+ $string .= " $ja[$join] j";
+ }
+
+ if (is_array($dash)) {
+ $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
+ }
+
+ $this->currentLineStyle = $string;
+ $this->addContent("\n$string");
+ }
+
+ /**
+ * draw a polygon, the syntax for this is similar to the GD polygon command
+ *
+ * @param $p
+ * @param $np
+ * @param bool $f
+ */
+ function polygon($p, $np, $f = false)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
+
+ for ($i = 2; $i < $np * 2; $i = $i + 2) {
+ $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
+ }
+
+ if ($f) {
+ $this->addContent(' f');
+ } else {
+ $this->addContent(' S');
+ }
+ }
+
+ /**
+ * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
+ * the coordinates of the upper-right corner
+ *
+ * @param $x1
+ * @param $y1
+ * @param $width
+ * @param $height
+ */
+ function filledRectangle($x1, $y1, $width, $height)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
+ }
+
+ /**
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
+ * the coordinates of the upper-right corner
+ *
+ * @param $x1
+ * @param $y1
+ * @param $width
+ * @param $height
+ */
+ function rectangle($x1, $y1, $width, $height)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
+ }
+
+ /**
+ * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
+ * the coordinates of the upper-right corner
+ *
+ * @param $x1
+ * @param $y1
+ * @param $width
+ * @param $height
+ */
+ function rect($x1, $y1, $width, $height)
+ {
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
+ }
+
+ function stroke(bool $close = false)
+ {
+ $this->addContent("\n" . ($close ? "s" : "S"));
+ }
+
+ function fill()
+ {
+ $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
+ }
+
+ function fillStroke(bool $close = false)
+ {
+ $this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
+ }
+
+ /**
+ * @param string $subtype
+ * @param integer $x
+ * @param integer $y
+ * @param integer $w
+ * @param integer $h
+ * @return int
+ */
+ function addXObject($subtype, $x, $y, $w, $h)
+ {
+ $id = ++$this->numObj;
+ $this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
+ return $id;
+ }
+
+ /**
+ * @param integer $numXObject
+ * @param string $type
+ * @param array $options
+ */
+ function setXObjectResource($numXObject, $type, $options)
+ {
+ if (in_array($type, ['procset', 'font', 'xObject'])) {
+ $this->o_xobject($numXObject, $type, $options);
+ }
+ }
+
+ /**
+ * add signature
+ *
+ * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
+ *
+ * $signatureId = $cpdf->addSignature([
+ * 'signcert' => file_get_contents('dompdf.crt'),
+ * 'privkey' => file_get_contents('dompdf.key'),
+ * 'password' => 'password',
+ * 'name' => 'DomPDF DEMO',
+ * 'location' => 'Home',
+ * 'reason' => 'First Form',
+ * 'contactinfo' => 'info'
+ * ]);
+ * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
+ *
+ * @param string $signcert
+ * @param string $privkey
+ * @param string $password
+ * @param string|null $name
+ * @param string|null $location
+ * @param string|null $reason
+ * @param string|null $contactinfo
+ * @return int
+ */
+ function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
+ $sigId = ++$this->numObj;
+ $this->o_sig($sigId, 'new', [
+ 'SignCert' => $signcert,
+ 'PrivKey' => $privkey,
+ 'Password' => $password,
+ 'Name' => $name,
+ 'Location' => $location,
+ 'Reason' => $reason,
+ 'ContactInfo' => $contactinfo
+ ]);
+
+ return $sigId;
+ }
+
+ /**
+ * add field to form
+ *
+ * @param string $type ACROFORM_FIELD_*
+ * @param string $name
+ * @param $x0
+ * @param $y0
+ * @param $x1
+ * @param $y1
+ * @param integer $ff Field Flag ACROFORM_FIELD_*_*
+ * @param float $size
+ * @param array $color
+ * @return int
+ */
+ public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $color = implode(' ', $color) . ' rg';
+
+ $currentFontNum = $this->currentFontNum;
+ $font = array_filter($this->objects[$this->currentNode]['info']['fonts'],
+ function($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; });
+
+ $this->o_acroform($this->acroFormId, 'font',
+ ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
+
+ $fieldId = ++$this->numObj;
+ $this->o_field($fieldId, 'new', [
+ 'rect' => [$x0, $y0, $x1, $y1],
+ 'F' => 4,
+ 'FT' => "/$type",
+ 'T' => $name,
+ 'Ff' => $ff,
+ 'pageid' => $this->currentPage,
+ 'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
+ ]);
+
+ return $fieldId;
+ }
+
+ /**
+ * set Field value
+ *
+ * @param integer $numFieldObj
+ * @param string $value
+ */
+ public function setFormFieldValue($numFieldObj, $value)
+ {
+ $this->o_field($numFieldObj, 'set', ['value' => $value]);
+ }
+
+ /**
+ * set Field value (reference)
+ *
+ * @param integer $numFieldObj
+ * @param integer $numObj Object number
+ */
+ public function setFormFieldRefValue($numFieldObj, $numObj)
+ {
+ $this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
+ }
+
+ /**
+ * set Field Appearanc (reference)
+ *
+ * @param integer $numFieldObj
+ * @param integer $normalNumObj
+ * @param integer|null $rolloverNumObj
+ * @param integer|null $downNumObj
+ */
+ public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
+ {
+ $appearance['N'] = $normalNumObj;
+
+ if ($rolloverNumObj !== null) {
+ $appearance['R'] = $rolloverNumObj;
+ }
+
+ if ($downNumObj !== null) {
+ $appearance['D'] = $downNumObj;
+ }
+
+ $this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
+ }
+
+ /**
+ * set Choice Field option values
+ *
+ * @param integer $numFieldObj
+ * @param array $value
+ */
+ public function setFormFieldOpt($numFieldObj, $value)
+ {
+ $this->o_field($numFieldObj, 'set', ['options' => $value]);
+ }
+
+ /**
+ * add form to document
+ *
+ * @param integer $sigFlags
+ * @param boolean $needAppearances
+ */
+ public function addForm($sigFlags = 0, $needAppearances = false)
+ {
+ $this->acroFormId = ++$this->numObj;
+ $this->o_acroform($this->acroFormId, 'new', [
+ 'NeedAppearances' => $needAppearances ? 'true' : 'false',
+ 'SigFlags' => $sigFlags
+ ]);
+ }
+
+ /**
+ * save the current graphic state
+ */
+ function save()
+ {
+ // we must reset the color cache or it will keep bad colors after clipping
+ $this->currentColor = null;
+ $this->currentStrokeColor = null;
+ $this->addContent("\nq");
+ }
+
+ /**
+ * restore the last graphic state
+ */
+ function restore()
+ {
+ // we must reset the color cache or it will keep bad colors after clipping
+ $this->currentColor = null;
+ $this->currentStrokeColor = null;
+ $this->addContent("\nQ");
+ }
+
+ /**
+ * draw a clipping rectangle, all the elements added after this will be clipped
+ *
+ * @param $x1
+ * @param $y1
+ * @param $width
+ * @param $height
+ */
+ function clippingRectangle($x1, $y1, $width, $height)
+ {
+ $this->save();
+ $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
+ }
+
+ /**
+ * draw a clipping rounded rectangle, all the elements added after this will be clipped
+ *
+ * @param $x1
+ * @param $y1
+ * @param $w
+ * @param $h
+ * @param $rTL
+ * @param $rTR
+ * @param $rBR
+ * @param $rBL
+ */
+ function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
+ {
+ $this->save();
+
+ // start: top edge, left end
+ $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
+
+ // line: bottom edge, left end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
+
+ // curve: bottom-left corner
+ $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
+
+ // line: right edge, bottom end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
+
+ // curve: bottom-right corner
+ $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
+
+ // line: right edge, top end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
+
+ // curve: bottom-right corner
+ $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
+
+ // line: bottom edge, right end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
+
+ // curve: top-right corner
+ $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
+
+ // line: top edge, left end
+ $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
+
+ // Close & clip
+ $this->addContent(" W n");
+ }
+
+ /**
+ * ends the last clipping shape
+ */
+ function clippingEnd()
+ {
+ $this->restore();
+ }
+
+ /**
+ * scale
+ *
+ * @param float $s_x scaling factor for width as percent
+ * @param float $s_y scaling factor for height as percent
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function scale($s_x, $s_y, $x, $y)
+ {
+ $y = $this->currentPageSize["height"] - $y;
+
+ $tm = [
+ $s_x,
+ 0,
+ 0,
+ $s_y,
+ $x * (1 - $s_x),
+ $y * (1 - $s_y)
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * translate
+ *
+ * @param float $t_x movement to the right
+ * @param float $t_y movement to the bottom
+ */
+ function translate($t_x, $t_y)
+ {
+ $tm = [
+ 1,
+ 0,
+ 0,
+ 1,
+ $t_x,
+ -$t_y
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * rotate
+ *
+ * @param float $angle angle in degrees for counter-clockwise rotation
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function rotate($angle, $x, $y)
+ {
+ $y = $this->currentPageSize["height"] - $y;
+
+ $a = deg2rad($angle);
+ $cos_a = cos($a);
+ $sin_a = sin($a);
+
+ $tm = [
+ $cos_a,
+ -$sin_a,
+ $sin_a,
+ $cos_a,
+ $x - $sin_a * $y - $cos_a * $x,
+ $y - $cos_a * $y + $sin_a * $x,
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * skew
+ *
+ * @param float $angle_x
+ * @param float $angle_y
+ * @param float $x Origin abscissa
+ * @param float $y Origin ordinate
+ */
+ function skew($angle_x, $angle_y, $x, $y)
+ {
+ $y = $this->currentPageSize["height"] - $y;
+
+ $tan_x = tan(deg2rad($angle_x));
+ $tan_y = tan(deg2rad($angle_y));
+
+ $tm = [
+ 1,
+ -$tan_y,
+ -$tan_x,
+ 1,
+ $tan_x * $y,
+ $tan_y * $x,
+ ];
+
+ $this->transform($tm);
+ }
+
+ /**
+ * apply graphic transformations
+ *
+ * @param array $tm transformation matrix
+ */
+ function transform($tm)
+ {
+ $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
+ }
+
+ /**
+ * add a new page to the document
+ * this also makes the new page the current active object
+ *
+ * @param int $insert
+ * @param int $id
+ * @param string $pos
+ * @return int
+ */
+ function newPage($insert = 0, $id = 0, $pos = 'after')
+ {
+ // if there is a state saved, then go up the stack closing them
+ // then on the new page, re-open them with the right setings
+
+ if ($this->nStateStack) {
+ for ($i = $this->nStateStack; $i >= 1; $i--) {
+ $this->restoreState($i);
+ }
+ }
+
+ $this->numObj++;
+
+ if ($insert) {
+ // the id from the ezPdf class is the id of the contents of the page, not the page object itself
+ // query that object to find the parent
+ $rid = $this->objects[$id]['onPage'];
+ $opt = ['rid' => $rid, 'pos' => $pos];
+ $this->o_page($this->numObj, 'new', $opt);
+ } else {
+ $this->o_page($this->numObj, 'new');
+ }
+
+ // if there is a stack saved, then put that onto the page
+ if ($this->nStateStack) {
+ for ($i = 1; $i <= $this->nStateStack; $i++) {
+ $this->saveState($i);
+ }
+ }
+
+ // and if there has been a stroke or fill color set, then transfer them
+ if (isset($this->currentColor)) {
+ $this->setColor($this->currentColor, true);
+ }
+
+ if (isset($this->currentStrokeColor)) {
+ $this->setStrokeColor($this->currentStrokeColor, true);
+ }
+
+ // if there is a line style set, then put this in too
+ if (mb_strlen($this->currentLineStyle, '8bit')) {
+ $this->addContent("\n$this->currentLineStyle");
+ }
+
+ // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
+ return $this->currentContents;
+ }
+
+ /**
+ * Streams the PDF to the client.
+ *
+ * @param string $filename The filename to present to the client.
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
+ */
+ function stream($filename = "document.pdf", $options = [])
+ {
+ if (headers_sent()) {
+ die("Unable to stream pdf: headers already sent");
+ }
+
+ if (!isset($options["compress"])) $options["compress"] = true;
+ if (!isset($options["Attachment"])) $options["Attachment"] = true;
+
+ $debug = !$options['compress'];
+ $tmp = ltrim($this->output($debug));
+
+ header("Cache-Control: private");
+ header("Content-Type: application/pdf");
+ header("Content-Length: " . mb_strlen($tmp, "8bit"));
+
+ $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
+ $attachment = $options["Attachment"] ? "attachment" : "inline";
+
+ $encoding = mb_detect_encoding($filename);
+ $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
+ $fallbackfilename = str_replace("\"", "", $fallbackfilename);
+ $encodedfilename = rawurlencode($filename);
+
+ $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
+ if ($fallbackfilename !== $filename) {
+ $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
+ }
+ header($contentDisposition);
+
+ echo $tmp;
+ flush();
+ }
+
+ /**
+ * return the height in units of the current font in the given size
+ *
+ * @param $size
+ * @return float|int
+ */
+ function getFontHeight($size)
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $font = $this->fonts[$this->currentFont];
+
+ // for the current font, and the given size, what is the height of the font in user units
+ if (isset($font['Ascender']) && isset($font['Descender'])) {
+ $h = $font['Ascender'] - $font['Descender'];
+ } else {
+ $h = $font['FontBBox'][3] - $font['FontBBox'][1];
+ }
+
+ // have to adjust by a font offset for Windows fonts. unfortunately it looks like
+ // the bounding box calculations are wrong and I don't know why.
+ if (isset($font['FontHeightOffset'])) {
+ // For CourierNew from Windows this needs to be -646 to match the
+ // Adobe native Courier font.
+ //
+ // For FreeMono from GNU this needs to be -337 to match the
+ // Courier font.
+ //
+ // Both have been added manually to the .afm and .ufm files.
+ $h += (int)$font['FontHeightOffset'];
+ }
+
+ return $size * $h / 1000;
+ }
+
+ /**
+ * @param $size
+ * @return float|int
+ */
+ function getFontXHeight($size)
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $font = $this->fonts[$this->currentFont];
+
+ // for the current font, and the given size, what is the height of the font in user units
+ if (isset($font['XHeight'])) {
+ $xh = $font['Ascender'] - $font['Descender'];
+ } else {
+ $xh = $this->getFontHeight($size) / 2;
+ }
+
+ return $size * $xh / 1000;
+ }
+
+ /**
+ * return the font descender, this will normally return a negative number
+ * if you add this number to the baseline, you get the level of the bottom of the font
+ * it is in the pdf user units
+ *
+ * @param $size
+ * @return float|int
+ */
+ function getFontDescender($size)
+ {
+ // note that this will most likely return a negative value
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
+ $h = $this->fonts[$this->currentFont]['Descender'];
+
+ return $size * $h / 1000;
+ }
+
+ /**
+ * filter the text, this is applied to all text just before being inserted into the pdf document
+ * it escapes the various things that need to be escaped, and so on
+ *
+ * @access private
+ *
+ * @param $text
+ * @param bool $bom
+ * @param bool $convert_encoding
+ * @return string
+ */
+ function filterText($text, $bom = true, $convert_encoding = true)
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ if ($convert_encoding) {
+ $cf = $this->currentFont;
+ if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
+ $text = $this->utf8toUtf16BE($text, $bom);
+ } else {
+ //$text = html_entity_decode($text, ENT_QUOTES);
+ $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
+ }
+ } else if ($bom) {
+ $text = $this->utf8toUtf16BE($text, $bom);
+ }
+
+ // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
+ return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
+ }
+
+ /**
+ * return array containing codepoints (UTF-8 character values) for the
+ * string passed in.
+ *
+ * based on the excellent TCPDF code by Nicola Asuni and the
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
+ *
+ * @access private
+ * @author Orion Richardson
+ * @since January 5, 2008
+ *
+ * @param string $text UTF-8 string to process
+ *
+ * @return array UTF-8 codepoints array for the string
+ */
+ function utf8toCodePointsArray(&$text)
+ {
+ $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
+ $unicode = []; // array containing unicode values
+ $bytes = []; // array containing single character byte sequences
+ $numbytes = 1; // number of octets needed to represent the UTF-8 character
+
+ for ($i = 0; $i < $length; $i++) {
+ $c = ord($text[$i]); // get one string character at time
+ if (count($bytes) === 0) { // get starting octect
+ if ($c <= 0x7F) {
+ $unicode[] = $c; // use the character "as is" because is ASCII
+ $numbytes = 1;
+ } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
+ $bytes[] = ($c - 0xC0) << 0x06;
+ $numbytes = 2;
+ } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
+ $bytes[] = ($c - 0xE0) << 0x0C;
+ $numbytes = 3;
+ } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
+ $bytes[] = ($c - 0xF0) << 0x12;
+ $numbytes = 4;
+ } else {
+ // use replacement character for other invalid sequences
+ $unicode[] = 0xFFFD;
+ $bytes = [];
+ $numbytes = 1;
+ }
+ } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
+ $bytes[] = $c - 0x80;
+ if (count($bytes) === $numbytes) {
+ // compose UTF-8 bytes to a single unicode value
+ $c = $bytes[0];
+ for ($j = 1; $j < $numbytes; $j++) {
+ $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
+ }
+ if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
+ // The definition of UTF-8 prohibits encoding character numbers between
+ // U+D800 and U+DFFF, which are reserved for use with the UTF-16
+ // encoding form (as surrogate pairs) and do not directly represent
+ // characters.
+ $unicode[] = 0xFFFD; // use replacement character
+ } else {
+ $unicode[] = $c; // add char to array
+ }
+ // reset data for next char
+ $bytes = [];
+ $numbytes = 1;
+ }
+ } else {
+ // use replacement character for other invalid sequences
+ $unicode[] = 0xFFFD;
+ $bytes = [];
+ $numbytes = 1;
+ }
+ }
+
+ return $unicode;
+ }
+
+ /**
+ * convert UTF-8 to UTF-16 with an additional byte order marker
+ * at the front if required.
+ *
+ * based on the excellent TCPDF code by Nicola Asuni and the
+ * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
+ *
+ * @access private
+ * @author Orion Richardson
+ * @since January 5, 2008
+ *
+ * @param string $text UTF-8 string to process
+ * @param boolean $bom whether to add the byte order marker
+ *
+ * @return string UTF-16 result string
+ */
+ function utf8toUtf16BE(&$text, $bom = true)
+ {
+ $out = $bom ? "\xFE\xFF" : '';
+
+ $unicode = $this->utf8toCodePointsArray($text);
+ foreach ($unicode as $c) {
+ if ($c === 0xFFFD) {
+ $out .= "\xFF\xFD"; // replacement character
+ } elseif ($c < 0x10000) {
+ $out .= chr($c >> 0x08) . chr($c & 0xFF);
+ } else {
+ $c -= 0x10000;
+ $w1 = 0xD800 | ($c >> 0x10);
+ $w2 = 0xDC00 | ($c & 0x3FF);
+ $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * given a start position and information about how text is to be laid out, calculate where
+ * on the page the text will end
+ *
+ * @param $x
+ * @param $y
+ * @param $angle
+ * @param $size
+ * @param $wa
+ * @param $text
+ * @return array
+ */
+ private function getTextPosition($x, $y, $angle, $size, $wa, $text)
+ {
+ // given this information return an array containing x and y for the end position as elements 0 and 1
+ $w = $this->getTextWidth($size, $text);
+
+ // need to adjust for the number of spaces in this text
+ $words = explode(' ', $text);
+ $nspaces = count($words) - 1;
+ $w += $wa * $nspaces;
+ $a = deg2rad((float)$angle);
+
+ return [cos($a) * $w + $x, -sin($a) * $w + $y];
+ }
+
+ /**
+ * Callback method used by smallCaps
+ *
+ * @param array $matches
+ *
+ * @return string
+ */
+ function toUpper($matches)
+ {
+ return mb_strtoupper($matches[0]);
+ }
+
+ function concatMatches($matches)
+ {
+ $str = "";
+ foreach ($matches as $match) {
+ $str .= $match[0];
+ }
+
+ return $str;
+ }
+
+ /**
+ * register text for font subsetting
+ *
+ * @param $font
+ * @param $text
+ */
+ function registerText($font, $text)
+ {
+ if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
+ return;
+ }
+
+ if (!isset($this->stringSubsets[$font])) {
+ $this->stringSubsets[$font] = [];
+ }
+
+ $this->stringSubsets[$font] = array_unique(
+ array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
+ );
+ }
+
+ /**
+ * add text to the document, at a specified location, size and angle on the page
+ *
+ * @param $x
+ * @param $y
+ * @param $size
+ * @param $text
+ * @param int $angle
+ * @param int $wordSpaceAdjust
+ * @param int $charSpaceAdjust
+ * @param bool $smallCaps
+ */
+ function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
+ {
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $text = str_replace(["\r", "\n"], "", $text);
+
+ // if ($smallCaps) {
+ // preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
+ // $lower = $this->concatMatches($matches);
+ // d($lower);
+
+ // preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
+ // $other = $this->concatMatches($matches);
+ // d($other);
+
+ // $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
+ // }
+
+ // if there are any open callbacks, then they should be called, to show the start of the line
+ if ($this->nCallback > 0) {
+ for ($i = $this->nCallback; $i > 0; $i--) {
+ // call each function
+ $info = [
+ 'x' => $x,
+ 'y' => $y,
+ 'angle' => $angle,
+ 'status' => 'sol',
+ 'p' => $this->callback[$i]['p'],
+ 'nCallback' => $this->callback[$i]['nCallback'],
+ 'height' => $this->callback[$i]['height'],
+ 'descender' => $this->callback[$i]['descender']
+ ];
+
+ $func = $this->callback[$i]['f'];
+ $this->$func($info);
+ }
+ }
+
+ if ($angle == 0) {
+ $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
+ } else {
+ $a = deg2rad((float)$angle);
+ $this->addContent(
+ sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
+ );
+ }
+
+ if ($wordSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
+ }
+
+ if ($charSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
+ }
+
+ $len = mb_strlen($text);
+ $start = 0;
+
+ if ($start < $len) {
+ $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start);
+ $place_text = $this->filterText($part, false);
+ // modify unicode text so that extra word spacing is manually implemented (bug #)
+ if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
+ $space_scale = 1000 / $size;
+ $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
+ }
+ $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
+ $this->addContent(" [($place_text)] TJ");
+ }
+
+ if ($wordSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tw", 0));
+ }
+
+ if ($charSpaceAdjust != 0) {
+ $this->addContent(sprintf(" %.3F Tc", 0));
+ }
+
+ $this->addContent(' ET');
+
+ // if there are any open callbacks, then they should be called, to show the end of the line
+ if ($this->nCallback > 0) {
+ for ($i = $this->nCallback; $i > 0; $i--) {
+ // call each function
+ $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
+ $info = [
+ 'x' => $tmp[0],
+ 'y' => $tmp[1],
+ 'angle' => $angle,
+ 'status' => 'eol',
+ 'p' => $this->callback[$i]['p'],
+ 'nCallback' => $this->callback[$i]['nCallback'],
+ 'height' => $this->callback[$i]['height'],
+ 'descender' => $this->callback[$i]['descender']
+ ];
+ $func = $this->callback[$i]['f'];
+ $this->$func($info);
+ }
+ }
+
+ if ($this->fonts[$this->currentFont]['isSubsetting']) {
+ $this->registerText($this->currentFont, $text);
+ }
+ }
+
+ /**
+ * calculate how wide a given text string will be on a page, at a given size.
+ * this can be called externally, but is also used by the other class functions
+ *
+ * @param float $size
+ * @param string $text
+ * @param float $word_spacing
+ * @param float $char_spacing
+ * @return float
+ */
+ function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
+ {
+ static $ord_cache = [];
+
+ // this function should not change any of the settings, though it will need to
+ // track any directives which change during calculation, so copy them at the start
+ // and put them back at the end.
+ $store_currentTextState = $this->currentTextState;
+
+ if (!$this->numFonts) {
+ $this->selectFont($this->defaultFont);
+ }
+
+ $text = str_replace(["\r", "\n"], "", $text);
+
+ // converts a number or a float to a string so it can get the width
+ $text = "$text";
+
+ // hmm, this is where it all starts to get tricky - use the font information to
+ // calculate the width of each character, add them up and convert to user units
+ $w = 0;
+ $cf = $this->currentFont;
+ $current_font = $this->fonts[$cf];
+ $space_scale = 1000 / ($size > 0 ? $size : 1);
+
+ if ($current_font['isUnicode']) {
+ // for Unicode, use the code points array to calculate width rather
+ // than just the string itself
+ $unicode = $this->utf8toCodePointsArray($text);
+
+ foreach ($unicode as $char) {
+ // check if we have to replace character
+ if (isset($current_font['differences'][$char])) {
+ $char = $current_font['differences'][$char];
+ }
+
+ if (isset($current_font['C'][$char])) {
+ $char_width = $current_font['C'][$char];
+
+ // add the character width
+ $w += $char_width;
+
+ // add additional padding for space
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
+ $w += $word_spacing * $space_scale;
+ }
+ }
+ }
+
+ // add additional char spacing
+ if ($char_spacing != 0) {
+ $w += $char_spacing * $space_scale * count($unicode);
+ }
+
+ } else {
+ // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
+ if ($this->isUnicode) {
+ $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
+ }
+
+ $len = mb_strlen($text, 'Windows-1252');
+
+ for ($i = 0; $i < $len; $i++) {
+ $c = $text[$i];
+ $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
+
+ // check if we have to replace character
+ if (isset($current_font['differences'][$char])) {
+ $char = $current_font['differences'][$char];
+ }
+
+ if (isset($current_font['C'][$char])) {
+ $char_width = $current_font['C'][$char];
+
+ // add the character width
+ $w += $char_width;
+
+ // add additional padding for space
+ if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
+ $w += $word_spacing * $space_scale;
+ }
+ }
+ }
+
+ // add additional char spacing
+ if ($char_spacing != 0) {
+ $w += $char_spacing * $space_scale * $len;
+ }
+ }
+
+ $this->currentTextState = $store_currentTextState;
+ $this->setCurrentFont();
+
+ return $w * $size / 1000;
+ }
+
+ /**
+ * this will be called at a new page to return the state to what it was on the
+ * end of the previous page, before the stack was closed down
+ * This is to get around not being able to have open 'q' across pages
+ *
+ * @param int $pageEnd
+ */
+ function saveState($pageEnd = 0)
+ {
+ if ($pageEnd) {
+ // this will be called at a new page to return the state to what it was on the
+ // end of the previous page, before the stack was closed down
+ // This is to get around not being able to have open 'q' across pages
+ $opt = $this->stateStack[$pageEnd];
+ // ok to use this as stack starts numbering at 1
+ $this->setColor($opt['col'], true);
+ $this->setStrokeColor($opt['str'], true);
+ $this->addContent("\n" . $opt['lin']);
+ // $this->currentLineStyle = $opt['lin'];
+ } else {
+ $this->nStateStack++;
+ $this->stateStack[$this->nStateStack] = [
+ 'col' => $this->currentColor,
+ 'str' => $this->currentStrokeColor,
+ 'lin' => $this->currentLineStyle
+ ];
+ }
+
+ $this->save();
+ }
+
+ /**
+ * restore a previously saved state
+ *
+ * @param int $pageEnd
+ */
+ function restoreState($pageEnd = 0)
+ {
+ if (!$pageEnd) {
+ $n = $this->nStateStack;
+ $this->currentColor = $this->stateStack[$n]['col'];
+ $this->currentStrokeColor = $this->stateStack[$n]['str'];
+ $this->addContent("\n" . $this->stateStack[$n]['lin']);
+ $this->currentLineStyle = $this->stateStack[$n]['lin'];
+ $this->stateStack[$n] = null;
+ unset($this->stateStack[$n]);
+ $this->nStateStack--;
+ }
+
+ $this->restore();
+ }
+
+ /**
+ * make a loose object, the output will go into this object, until it is closed, then will revert to
+ * the current one.
+ * this object will not appear until it is included within a page.
+ * the function will return the object number
+ *
+ * @return int
+ */
+ function openObject()
+ {
+ $this->nStack++;
+ $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
+ // add a new object of the content type, to hold the data flow
+ $this->numObj++;
+ $this->o_contents($this->numObj, 'new');
+ $this->currentContents = $this->numObj;
+ $this->looseObjects[$this->numObj] = 1;
+
+ return $this->numObj;
+ }
+
+ /**
+ * open an existing object for editing
+ *
+ * @param $id
+ */
+ function reopenObject($id)
+ {
+ $this->nStack++;
+ $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
+ $this->currentContents = $id;
+
+ // also if this object is the primary contents for a page, then set the current page to its parent
+ if (isset($this->objects[$id]['onPage'])) {
+ $this->currentPage = $this->objects[$id]['onPage'];
+ }
+ }
+
+ /**
+ * close an object
+ */
+ function closeObject()
+ {
+ // close the object, as long as there was one open in the first place, which will be indicated by
+ // an objectId on the stack.
+ if ($this->nStack > 0) {
+ $this->currentContents = $this->stack[$this->nStack]['c'];
+ $this->currentPage = $this->stack[$this->nStack]['p'];
+ $this->nStack--;
+ // easier to probably not worry about removing the old entries, they will be overwritten
+ // if there are new ones.
+ }
+ }
+
+ /**
+ * stop an object from appearing on pages from this point on
+ *
+ * @param $id
+ */
+ function stopObject($id)
+ {
+ // if an object has been appearing on pages up to now, then stop it, this page will
+ // be the last one that could contain it.
+ if (isset($this->addLooseObjects[$id])) {
+ $this->addLooseObjects[$id] = '';
+ }
+ }
+
+ /**
+ * after an object has been created, it wil only show if it has been added, using this function.
+ *
+ * @param $id
+ * @param string $options
+ */
+ function addObject($id, $options = 'add')
+ {
+ // add the specified object to the page
+ if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
+ // then it is a valid object, and it is not being added to itself
+ switch ($options) {
+ case 'all':
+ // then this object is to be added to this page (done in the next block) and
+ // all future new pages.
+ $this->addLooseObjects[$id] = 'all';
+
+ case 'add':
+ if (isset($this->objects[$this->currentContents]['onPage'])) {
+ // then the destination contents is the primary for the page
+ // (though this object is actually added to that page)
+ $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
+ }
+ break;
+
+ case 'even':
+ $this->addLooseObjects[$id] = 'even';
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
+ $this->addObject($id);
+ // hacky huh :)
+ }
+ break;
+
+ case 'odd':
+ $this->addLooseObjects[$id] = 'odd';
+ $pageObjectId = $this->objects[$this->currentContents]['onPage'];
+ if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
+ $this->addObject($id);
+ // hacky huh :)
+ }
+ break;
+
+ case 'next':
+ $this->addLooseObjects[$id] = 'all';
+ break;
+
+ case 'nexteven':
+ $this->addLooseObjects[$id] = 'even';
+ break;
+
+ case 'nextodd':
+ $this->addLooseObjects[$id] = 'odd';
+ break;
+ }
+ }
+ }
+
+ /**
+ * return a storable representation of a specific object
+ *
+ * @param $id
+ * @return string|null
+ */
+ function serializeObject($id)
+ {
+ if (array_key_exists($id, $this->objects)) {
+ return serialize($this->objects[$id]);
+ }
+
+ return null;
+ }
+
+ /**
+ * restore an object from its stored representation. Returns its new object id.
+ *
+ * @param $obj
+ * @return int
+ */
+ function restoreSerializedObject($obj)
+ {
+ $obj_id = $this->openObject();
+ $this->objects[$obj_id] = unserialize($obj);
+ $this->closeObject();
+
+ return $obj_id;
+ }
+
+ /**
+ * Embeds a file inside the PDF
+ *
+ * @param string $filepath path to the file to store inside the PDF
+ * @param string $embeddedFilename the filename displayed in the list of embedded files
+ * @param string $description a description in the list of embedded files
+ */
+ public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
+ {
+ $this->numObj++;
+ $this->o_embedded_file_dictionary(
+ $this->numObj,
+ 'new',
+ [
+ 'filepath' => $filepath,
+ 'filename' => $embeddedFilename,
+ 'description' => $description
+ ]
+ );
+ }
+
+ /**
+ * add content to the documents info object
+ *
+ * @param $label
+ * @param int $value
+ */
+ function addInfo($label, $value = 0)
+ {
+ // this will only work if the label is one of the valid ones.
+ // modify this so that arrays can be passed as well.
+ // if $label is an array then assume that it is key => value pairs
+ // else assume that they are both scalar, anything else will probably error
+ if (is_array($label)) {
+ foreach ($label as $l => $v) {
+ $this->o_info($this->infoObject, $l, $v);
+ }
+ } else {
+ $this->o_info($this->infoObject, $label, $value);
+ }
+ }
+
+ /**
+ * set the viewer preferences of the document, it is up to the browser to obey these.
+ *
+ * @param $label
+ * @param int $value
+ */
+ function setPreferences($label, $value = 0)
+ {
+ // this will only work if the label is one of the valid ones.
+ if (is_array($label)) {
+ foreach ($label as $l => $v) {
+ $this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
+ }
+ } else {
+ $this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
+ }
+ }
+
+ /**
+ * extract an integer from a position in a byte stream
+ *
+ * @param $data
+ * @param $pos
+ * @param $num
+ * @return int
+ */
+ private function getBytes(&$data, $pos, $num)
+ {
+ // return the integer represented by $num bytes from $pos within $data
+ $ret = 0;
+ for ($i = 0; $i < $num; $i++) {
+ $ret *= 256;
+ $ret += ord($data[$pos + $i]);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Check if image already added to pdf image directory.
+ * If yes, need not to create again (pass empty data)
+ *
+ * @param string $imgname
+ * @return bool
+ */
+ function image_iscached($imgname)
+ {
+ return isset($this->imagelist[$imgname]);
+ }
+
+ /**
+ * add a PNG image into the document, from a GD object
+ * this should work with remote files
+ *
+ * @param \GdImage|resource $img A GD resource
+ * @param string $file The PNG file
+ * @param float $x X position
+ * @param float $y Y position
+ * @param float $w Width
+ * @param float $h Height
+ * @param bool $is_mask true if the image is a mask
+ * @param bool $mask true if the image is masked
+ * @throws Exception
+ */
+ function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
+ {
+ if (!function_exists("imagepng")) {
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
+ }
+
+ //if already cached, need not to read again
+ if (isset($this->imagelist[$file])) {
+ $data = null;
+ } else {
+ // Example for transparency handling on new image. Retain for current image
+ // $tIndex = imagecolortransparent($img);
+ // if ($tIndex > 0) {
+ // $tColor = imagecolorsforindex($img, $tIndex);
+ // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
+ // imagefill($new_img, 0, 0, $new_tIndex);
+ // imagecolortransparent($new_img, $new_tIndex);
+ // }
+ // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
+ //imagealphablending($img, true);
+
+ //default, but explicitely set to ensure pdf compatibility
+ imagesavealpha($img, false/*!$is_mask && !$mask*/);
+
+ $error = 0;
+ //DEBUG_IMG_TEMP
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addImagePng ' . $file . ']';
+ }
+
+ ob_start();
+ @imagepng($img);
+ $data = ob_get_clean();
+
+ if ($data == '') {
+ $error = 1;
+ $errormsg = 'trouble writing file from GD';
+ //DEBUG_IMG_TEMP
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print 'trouble writing file from GD';
+ }
+ }
+
+ if ($error) {
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
+
+ return;
+ }
+ } //End isset($this->imagelist[$file]) (png Duplicate removal)
+
+ $this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
+ }
+
+ /**
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param $w
+ * @param $h
+ * @param $byte
+ */
+ protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
+ {
+ // generate images
+ $img = imagecreatefrompng($file);
+
+ if ($img === false) {
+ return;
+ }
+
+ // FIXME The pixel transformation doesn't work well with 8bit PNGs
+ $eight_bit = ($byte & 4) !== 4;
+
+ $wpx = imagesx($img);
+ $hpx = imagesy($img);
+
+ imagesavealpha($img, false);
+
+ // create temp alpha file
+ $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
+ @unlink($tempfile_alpha);
+ $tempfile_alpha = "$tempfile_alpha.png";
+
+ // create temp plain file
+ $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
+ @unlink($tempfile_plain);
+ $tempfile_plain = "$tempfile_plain.png";
+
+ $imgalpha = imagecreate($wpx, $hpx);
+ imagesavealpha($imgalpha, false);
+
+ // generate gray scale palette (0 -> 255)
+ for ($c = 0; $c < 256; ++$c) {
+ imagecolorallocate($imgalpha, $c, $c, $c);
+ }
+
+ // Use PECL gmagick + Graphics Magic to process transparent PNG images
+ if (extension_loaded("gmagick")) {
+ $gmagick = new \Gmagick($file);
+ $gmagick->setimageformat('png');
+
+ // Get opacity channel (negative of alpha channel)
+ $alpha_channel_neg = clone $gmagick;
+ $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
+
+ // Negate opacity channel
+ $alpha_channel = new \Gmagick();
+ $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
+ $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
+ $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
+ $alpha_channel->writeimage($tempfile_alpha);
+
+ // Cast to 8bit+palette
+ $imgalpha_ = imagecreatefrompng($tempfile_alpha);
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
+ imagedestroy($imgalpha_);
+ imagepng($imgalpha, $tempfile_alpha);
+
+ // Make opaque image
+ $color_channels = new \Gmagick();
+ $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
+ $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
+ $color_channels->writeimage($tempfile_plain);
+
+ $imgplain = imagecreatefrompng($tempfile_plain);
+ }
+ // Use PECL imagick + ImageMagic to process transparent PNG images
+ elseif (extension_loaded("imagick")) {
+ // Native cloning was added to pecl-imagick in svn commit 263814
+ // the first version containing it was 3.0.1RC1
+ static $imagickClonable = null;
+ if ($imagickClonable === null) {
+ $imagickClonable = true;
+ if (defined('Imagick::IMAGICK_EXTVER')) {
+ $imagickVersion = \Imagick::IMAGICK_EXTVER;
+ } else {
+ $imagickVersion = '0';
+ }
+ if (version_compare($imagickVersion, '0.0.1', '>=')) {
+ $imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
+ }
+ }
+
+ $imagick = new \Imagick($file);
+ $imagick->setFormat('png');
+
+ // Get opacity channel (negative of alpha channel)
+ if ($imagick->getImageAlphaChannel() !== 0) {
+ $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
+ $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
+ // Since ImageMagick7 negate invert transparency as default
+ if (\Imagick::getVersion()['versionNumber'] < 1800) {
+ $alpha_channel->negateImage(true);
+ }
+ $alpha_channel->writeImage($tempfile_alpha);
+
+ // Cast to 8bit+palette
+ $imgalpha_ = imagecreatefrompng($tempfile_alpha);
+ imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
+ imagedestroy($imgalpha_);
+ imagepng($imgalpha, $tempfile_alpha);
+ } else {
+ $tempfile_alpha = null;
+ }
+
+ // Make opaque image
+ $color_channels = new \Imagick();
+ $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
+ $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
+ $color_channels->writeImage($tempfile_plain);
+
+ $imgplain = imagecreatefrompng($tempfile_plain);
+ } else {
+ // allocated colors cache
+ $allocated_colors = [];
+
+ // extract alpha channel
+ for ($xpx = 0; $xpx < $wpx; ++$xpx) {
+ for ($ypx = 0; $ypx < $hpx; ++$ypx) {
+ $color = imagecolorat($img, $xpx, $ypx);
+ $col = imagecolorsforindex($img, $color);
+ $alpha = $col['alpha'];
+
+ if ($eight_bit) {
+ // with gamma correction
+ $gammacorr = 2.2;
+ $pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
+ } else {
+ // without gamma correction
+ $pixel = (127 - $alpha) * 2;
+
+ $key = $col['red'] . $col['green'] . $col['blue'];
+
+ if (!isset($allocated_colors[$key])) {
+ $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
+ $allocated_colors[$key] = $pixel_img;
+ } else {
+ $pixel_img = $allocated_colors[$key];
+ }
+
+ imagesetpixel($img, $xpx, $ypx, $pixel_img);
+ }
+
+ imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
+ }
+ }
+
+ // extract image without alpha channel
+ $imgplain = imagecreatetruecolor($wpx, $hpx);
+ imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
+ imagedestroy($img);
+
+ imagepng($imgalpha, $tempfile_alpha);
+ imagepng($imgplain, $tempfile_plain);
+ }
+
+ $this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
+
+ // embed mask image
+ if ($tempfile_alpha) {
+ $this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
+ imagedestroy($imgalpha);
+ $this->imageCache[] = $tempfile_alpha;
+ }
+
+ // embed image, masked with previously embedded mask
+ $this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
+ imagedestroy($imgplain);
+ $this->imageCache[] = $tempfile_plain;
+ }
+
+ /**
+ * add a PNG image into the document, from a file
+ * this should work with remote files
+ *
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ * @throws Exception
+ */
+ function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
+ {
+ if (!function_exists("imagecreatefrompng")) {
+ throw new \Exception("The PHP GD extension is required, but is not installed.");
+ }
+
+ if (isset($this->imageAlphaList[$file])) {
+ [$alphaFile, $plainFile] = $this->imageAlphaList[$file];
+
+ if ($alphaFile) {
+ $img = null;
+ $this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
+ }
+
+ $img = null;
+ $this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
+ return;
+ }
+
+ //if already cached, need not to read again
+ if (isset($this->imagelist[$file])) {
+ $img = null;
+ } else {
+ $info = file_get_contents($file, false, null, 24, 5);
+ $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
+ $bit_depth = $meta["bitDepth"];
+ $color_type = $meta["colorType"];
+
+ // http://www.w3.org/TR/PNG/#11IHDR
+ // 3 => indexed
+ // 4 => greyscale with alpha
+ // 6 => fullcolor with alpha
+ $is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
+
+ if ($is_alpha) { // exclude grayscale alpha
+ $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
+ return;
+ }
+
+ //png files typically contain an alpha channel.
+ //pdf file format or class.pdf does not support alpha blending.
+ //on alpha blended images, more transparent areas have a color near black.
+ //This appears in the result on not storing the alpha channel.
+ //Correct would be the box background image or its parent when transparent.
+ //But this would make the image dependent on the background.
+ //Therefore create an image with white background and copy in
+ //A more natural background than black is white.
+ //Therefore create an empty image with white background and merge the
+ //image in with alpha blending.
+ $imgtmp = @imagecreatefrompng($file);
+ if (!$imgtmp) {
+ return;
+ }
+ $sx = imagesx($imgtmp);
+ $sy = imagesy($imgtmp);
+ $img = imagecreatetruecolor($sx, $sy);
+ imagealphablending($img, true);
+
+ // @todo is it still needed ??
+ $ti = imagecolortransparent($imgtmp);
+ if ($ti >= 0) {
+ $tc = imagecolorsforindex($imgtmp, $ti);
+ $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
+ imagefill($img, 0, 0, $ti);
+ imagecolortransparent($img, $ti);
+ } else {
+ imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
+ }
+
+ imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
+ imagedestroy($imgtmp);
+ }
+ $this->addImagePng($img, $file, $x, $y, $w, $h);
+
+ if ($img) {
+ imagedestroy($img);
+ }
+ }
+
+ /**
+ * add a PNG image into the document, from a memory buffer of the file
+ *
+ * @param $data
+ * @param $file
+ * @param $x
+ * @param $y
+ * @param float $w
+ * @param float $h
+ * @param bool $is_mask
+ * @param null $mask
+ */
+ function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
+ {
+ if (isset($this->imagelist[$file])) {
+ $data = null;
+ $info['width'] = $this->imagelist[$file]['w'];
+ $info['height'] = $this->imagelist[$file]['h'];
+ $label = $this->imagelist[$file]['label'];
+ } else {
+ if ($data == null) {
+ $this->addMessage('addPngFromBuf error - data not present!');
+
+ return;
+ }
+
+ $error = 0;
+
+ if (!$error) {
+ $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
+
+ if (mb_substr($data, 0, 8, '8bit') != $header) {
+ $error = 1;
+
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile this file does not have a valid header ' . $file . ']';
+ }
+
+ $errormsg = 'this file does not have a valid header';
+ }
+ }
+
+ if (!$error) {
+ // set pointer
+ $p = 8;
+ $len = mb_strlen($data, '8bit');
+
+ // cycle through the file, identifying chunks
+ $haveHeader = 0;
+ $info = [];
+ $idata = '';
+ $pdata = '';
+
+ while ($p < $len) {
+ $chunkLen = $this->getBytes($data, $p, 4);
+ $chunkType = mb_substr($data, $p + 4, 4, '8bit');
+
+ switch ($chunkType) {
+ case 'IHDR':
+ // this is where all the file information comes from
+ $info['width'] = $this->getBytes($data, $p + 8, 4);
+ $info['height'] = $this->getBytes($data, $p + 12, 4);
+ $info['bitDepth'] = ord($data[$p + 16]);
+ $info['colorType'] = ord($data[$p + 17]);
+ $info['compressionMethod'] = ord($data[$p + 18]);
+ $info['filterMethod'] = ord($data[$p + 19]);
+ $info['interlaceMethod'] = ord($data[$p + 20]);
+
+ //print_r($info);
+ $haveHeader = 1;
+ if ($info['compressionMethod'] != 0) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile unsupported compression method ' . $file . ']';
+ }
+
+ $errormsg = 'unsupported compression method';
+ }
+
+ if ($info['filterMethod'] != 0) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile unsupported filter method ' . $file . ']';
+ }
+
+ $errormsg = 'unsupported filter method';
+ }
+ break;
+
+ case 'PLTE':
+ $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
+ break;
+
+ case 'IDAT':
+ $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
+ break;
+
+ case 'tRNS':
+ //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
+ //print "tRNS found, color type = ".$info['colorType']."\n";
+ $transparency = [];
+
+ switch ($info['colorType']) {
+ // indexed color, rbg
+ case 3:
+ /* corresponding to entries in the plte chunk
+ Alpha for palette index 0: 1 byte
+ Alpha for palette index 1: 1 byte
+ ...etc...
+ */
+ // there will be one entry for each palette entry. up until the last non-opaque entry.
+ // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
+ $transparency['type'] = 'indexed';
+ $trans = 0;
+
+ for ($i = $chunkLen; $i >= 0; $i--) {
+ if (ord($data[$p + 8 + $i]) == 0) {
+ $trans = $i;
+ }
+ }
+
+ $transparency['data'] = $trans;
+ break;
+
+ // grayscale
+ case 0:
+ /* corresponding to entries in the plte chunk
+ Gray: 2 bytes, range 0 .. (2^bitdepth)-1
+ */
+ // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
+ $transparency['type'] = 'indexed';
+ $transparency['data'] = ord($data[$p + 8 + 1]);
+ break;
+
+ // truecolor
+ case 2:
+ /* corresponding to entries in the plte chunk
+ Red: 2 bytes, range 0 .. (2^bitdepth)-1
+ Green: 2 bytes, range 0 .. (2^bitdepth)-1
+ Blue: 2 bytes, range 0 .. (2^bitdepth)-1
+ */
+ $transparency['r'] = $this->getBytes($data, $p + 8, 2);
+ // r from truecolor
+ $transparency['g'] = $this->getBytes($data, $p + 10, 2);
+ // g from truecolor
+ $transparency['b'] = $this->getBytes($data, $p + 12, 2);
+ // b from truecolor
+
+ $transparency['type'] = 'color-key';
+ break;
+
+ //unsupported transparency type
+ default:
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile unsupported transparency type ' . $file . ']';
+ }
+ break;
+ }
+
+ // KS End new code
+ break;
+
+ default:
+ break;
+ }
+
+ $p += $chunkLen + 12;
+ }
+
+ if (!$haveHeader) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile information header is missing ' . $file . ']';
+ }
+
+ $errormsg = 'information header is missing';
+ }
+
+ if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
+ }
+
+ $errormsg = 'There appears to be no support for interlaced images in pdf.';
+ }
+ }
+
+ if (!$error && $info['bitDepth'] > 8) {
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
+ }
+
+ $errormsg = 'only bit depth of 8 or less is supported';
+ }
+
+ if (!$error) {
+ switch ($info['colorType']) {
+ case 3:
+ $color = 'DeviceRGB';
+ $ncolor = 1;
+ break;
+
+ case 2:
+ $color = 'DeviceRGB';
+ $ncolor = 3;
+ break;
+
+ case 0:
+ $color = 'DeviceGray';
+ $ncolor = 1;
+ break;
+
+ default:
+ $error = 1;
+
+ //debugpng
+ if (defined("DEBUGPNG") && DEBUGPNG) {
+ print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
+ }
+
+ $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
+ }
+ }
+
+ if ($error) {
+ $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
+
+ return;
+ }
+
+ //print_r($info);
+ // so this image is ok... add it in.
+ $this->numImages++;
+ $im = $this->numImages;
+ $label = "I$im";
+ $this->numObj++;
+
+ // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
+ $options = [
+ 'label' => $label,
+ 'data' => $idata,
+ 'bitsPerComponent' => $info['bitDepth'],
+ 'pdata' => $pdata,
+ 'iw' => $info['width'],
+ 'ih' => $info['height'],
+ 'type' => 'png',
+ 'color' => $color,
+ 'ncolor' => $ncolor,
+ 'masked' => $mask,
+ 'isMask' => $is_mask
+ ];
+
+ if (isset($transparency)) {
+ $options['transparency'] = $transparency;
+ }
+
+ $this->o_image($this->numObj, 'new', $options);
+ $this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
+ }
+
+ if ($is_mask) {
+ return;
+ }
+
+ if ($w <= 0 && $h <= 0) {
+ $w = $info['width'];
+ $h = $info['height'];
+ }
+
+ if ($w <= 0) {
+ $w = $h / $info['height'] * $info['width'];
+ }
+
+ if ($h <= 0) {
+ $h = $w * $info['height'] / $info['width'];
+ }
+
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
+ }
+
+ /**
+ * add a JPEG image into the document, from a file
+ *
+ * @param $img
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ */
+ function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
+ {
+ // attempt to add a jpeg image straight from a file, using no GD commands
+ // note that this function is unable to operate on a remote file.
+
+ if (!file_exists($img)) {
+ return;
+ }
+
+ if ($this->image_iscached($img)) {
+ $data = null;
+ $imageWidth = $this->imagelist[$img]['w'];
+ $imageHeight = $this->imagelist[$img]['h'];
+ $channels = $this->imagelist[$img]['c'];
+ } else {
+ $tmp = getimagesize($img);
+ $imageWidth = $tmp[0];
+ $imageHeight = $tmp[1];
+
+ if (isset($tmp['channels'])) {
+ $channels = $tmp['channels'];
+ } else {
+ $channels = 3;
+ }
+
+ $data = file_get_contents($img);
+ }
+
+ if ($w <= 0 && $h <= 0) {
+ $w = $imageWidth;
+ }
+
+ if ($w == 0) {
+ $w = $h / $imageHeight * $imageWidth;
+ }
+
+ if ($h == 0) {
+ $h = $w * $imageHeight / $imageWidth;
+ }
+
+ $this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
+ }
+
+ /**
+ * common code used by the two JPEG adding functions
+ * @param $data
+ * @param $imgname
+ * @param $imageWidth
+ * @param $imageHeight
+ * @param $x
+ * @param $y
+ * @param int $w
+ * @param int $h
+ * @param int $channels
+ */
+ private function addJpegImage_common(
+ &$data,
+ $imgname,
+ $imageWidth,
+ $imageHeight,
+ $x,
+ $y,
+ $w = 0,
+ $h = 0,
+ $channels = 3
+ ) {
+ if ($this->image_iscached($imgname)) {
+ $label = $this->imagelist[$imgname]['label'];
+ //debugpng
+ //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
+
+ } else {
+ if ($data == null) {
+ $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
+
+ return;
+ }
+
+ // note that this function is not to be called externally
+ // it is just the common code between the GD and the file options
+ $this->numImages++;
+ $im = $this->numImages;
+ $label = "I$im";
+ $this->numObj++;
+
+ $this->o_image(
+ $this->numObj,
+ 'new',
+ [
+ 'label' => $label,
+ 'data' => &$data,
+ 'iw' => $imageWidth,
+ 'ih' => $imageHeight,
+ 'channels' => $channels
+ ]
+ );
+
+ $this->imagelist[$imgname] = [
+ 'label' => $label,
+ 'w' => $imageWidth,
+ 'h' => $imageHeight,
+ 'c' => $channels
+ ];
+ }
+
+ $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
+ }
+
+ /**
+ * specify where the document should open when it first starts
+ *
+ * @param $style
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ */
+ function openHere($style, $a = 0, $b = 0, $c = 0)
+ {
+ // this function will open the document at a specified page, in a specified style
+ // the values for style, and the required parameters are:
+ // 'XYZ' left, top, zoom
+ // 'Fit'
+ // 'FitH' top
+ // 'FitV' left
+ // 'FitR' left,bottom,right
+ // 'FitB'
+ // 'FitBH' top
+ // 'FitBV' left
+ $this->numObj++;
+ $this->o_destination(
+ $this->numObj,
+ 'new',
+ ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
+ );
+ $id = $this->catalogId;
+ $this->o_catalog($id, 'openHere', $this->numObj);
+ }
+
+ /**
+ * Add JavaScript code to the PDF document
+ *
+ * @param string $code
+ */
+ function addJavascript($code)
+ {
+ $this->javascript .= $code;
+ }
+
+ /**
+ * create a labelled destination within the document
+ *
+ * @param $label
+ * @param $style
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ */
+ function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
+ {
+ // associates the given label with the destination, it is done this way so that a destination can be specified after
+ // it has been linked to
+ // styles are the same as the 'openHere' function
+ $this->numObj++;
+ $this->o_destination(
+ $this->numObj,
+ 'new',
+ ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
+ );
+ $id = $this->numObj;
+
+ // store the label->idf relationship, note that this means that labels can be used only once
+ $this->destinations["$label"] = $id;
+ }
+
+ /**
+ * define font families, this is used to initialize the font families for the default fonts
+ * and for the user to add new ones for their fonts. The default bahavious can be overridden should
+ * that be desired.
+ *
+ * @param $family
+ * @param string $options
+ */
+ function setFontFamily($family, $options = '')
+ {
+ if (!is_array($options)) {
+ if ($family === 'init') {
+ // set the known family groups
+ // these font families will be used to enable bold and italic markers to be included
+ // within text streams. html forms will be used... <b></b> <i></i>
+ $this->fontFamilies['Helvetica.afm'] =
+ [
+ 'b' => 'Helvetica-Bold.afm',
+ 'i' => 'Helvetica-Oblique.afm',
+ 'bi' => 'Helvetica-BoldOblique.afm',
+ 'ib' => 'Helvetica-BoldOblique.afm'
+ ];
+
+ $this->fontFamilies['Courier.afm'] =
+ [
+ 'b' => 'Courier-Bold.afm',
+ 'i' => 'Courier-Oblique.afm',
+ 'bi' => 'Courier-BoldOblique.afm',
+ 'ib' => 'Courier-BoldOblique.afm'
+ ];
+
+ $this->fontFamilies['Times-Roman.afm'] =
+ [
+ 'b' => 'Times-Bold.afm',
+ 'i' => 'Times-Italic.afm',
+ 'bi' => 'Times-BoldItalic.afm',
+ 'ib' => 'Times-BoldItalic.afm'
+ ];
+ }
+ } else {
+
+ // the user is trying to set a font family
+ // note that this can also be used to set the base ones to something else
+ if (mb_strlen($family)) {
+ $this->fontFamilies[$family] = $options;
+ }
+ }
+ }
+
+ /**
+ * used to add messages for use in debugging
+ *
+ * @param $message
+ */
+ function addMessage($message)
+ {
+ $this->messages .= $message . "\n";
+ }
+
+ /**
+ * a few functions which should allow the document to be treated transactionally.
+ *
+ * @param $action
+ */
+ function transaction($action)
+ {
+ switch ($action) {
+ case 'start':
+ // store all the data away into the checkpoint variable
+ $data = get_object_vars($this);
+ $this->checkpoint = $data;
+ unset($data);
+ break;
+
+ case 'commit':
+ if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
+ $tmp = $this->checkpoint['checkpoint'];
+ $this->checkpoint = $tmp;
+ unset($tmp);
+ } else {
+ $this->checkpoint = '';
+ }
+ break;
+
+ case 'rewind':
+ // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
+ if (is_array($this->checkpoint)) {
+ // can only abort if were inside a checkpoint
+ $tmp = $this->checkpoint;
+
+ foreach ($tmp as $k => $v) {
+ if ($k !== 'checkpoint') {
+ $this->$k = $v;
+ }
+ }
+ unset($tmp);
+ }
+ break;
+
+ case 'abort':
+ if (is_array($this->checkpoint)) {
+ // can only abort if were inside a checkpoint
+ $tmp = $this->checkpoint;
+ foreach ($tmp as $k => $v) {
+ $this->$k = $v;
+ }
+ unset($tmp);
+ }
+ break;
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php
new file mode 100644
index 0000000..62cc74a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php
@@ -0,0 +1,495 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Surface;
+
+use Svg\Document;
+use Svg\Style;
+
+class SurfaceCpdf implements SurfaceInterface
+{
+ const DEBUG = false;
+
+ /** @var \Svg\Surface\CPdf */
+ private $canvas;
+
+ private $width;
+ private $height;
+
+ /** @var Style */
+ private $style;
+
+ public function __construct(Document $doc, $canvas = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $dimensions = $doc->getDimensions();
+ $w = $dimensions["width"];
+ $h = $dimensions["height"];
+
+ if (!$canvas) {
+ $canvas = new \Svg\Surface\CPdf(array(0, 0, $w, $h));
+ $refl = new \ReflectionClass($canvas);
+ $canvas->fontcache = realpath(dirname($refl->getFileName()) . "/../../fonts/")."/";
+ }
+
+ // Flip PDF coordinate system so that the origin is in
+ // the top left rather than the bottom left
+ $canvas->transform(array(
+ 1, 0,
+ 0, -1,
+ 0, $h
+ ));
+
+ $this->width = $w;
+ $this->height = $h;
+
+ $this->canvas = $canvas;
+ }
+
+ function out()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ return $this->canvas->output();
+ }
+
+ public function save()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->save();
+ }
+
+ public function restore()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->restore();
+ }
+
+ public function scale($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $this->transform($x, 0, 0, $y, 0, 0);
+ }
+
+ public function rotate($angle)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $a = deg2rad($angle);
+ $cos_a = cos($a);
+ $sin_a = sin($a);
+
+ $this->transform(
+ $cos_a, $sin_a,
+ -$sin_a, $cos_a,
+ 0, 0
+ );
+ }
+
+ public function translate($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $this->transform(
+ 1, 0,
+ 0, 1,
+ $x, $y
+ );
+ }
+
+ public function transform($a, $b, $c, $d, $e, $f)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $this->canvas->transform(array($a, $b, $c, $d, $e, $f));
+ }
+
+ public function beginPath()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ // TODO: Implement beginPath() method.
+ }
+
+ public function closePath()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->closePath();
+ }
+
+ public function fillStroke(bool $close = false)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->fillStroke($close);
+ }
+
+ public function clip()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->clip();
+ }
+
+ public function fillText($text, $x, $y, $maxWidth = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->addText($x, $y, $this->style->fontSize, $text);
+ }
+
+ public function strokeText($text, $x, $y, $maxWidth = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->addText($x, $y, $this->style->fontSize, $text);
+ }
+
+ public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ if (strpos($image, "data:") === 0) {
+ $parts = explode(',', $image, 2);
+
+ $data = $parts[1];
+ $base64 = false;
+
+ $token = strtok($parts[0], ';');
+ while ($token !== false) {
+ if ($token == 'base64') {
+ $base64 = true;
+ }
+
+ $token = strtok(';');
+ }
+
+ if ($base64) {
+ $data = base64_decode($data);
+ }
+ }
+ else {
+ $data = file_get_contents($image);
+ }
+
+ $image = tempnam(sys_get_temp_dir(), "svg");
+ file_put_contents($image, $data);
+
+ $img = $this->image($image, $sx, $sy, $sw, $sh, "normal");
+
+
+ unlink($image);
+ }
+
+ public static function getimagesize($filename)
+ {
+ static $cache = array();
+
+ if (isset($cache[$filename])) {
+ return $cache[$filename];
+ }
+
+ list($width, $height, $type) = getimagesize($filename);
+
+ if ($width == null || $height == null) {
+ $data = file_get_contents($filename, null, null, 0, 26);
+
+ if (substr($data, 0, 2) === "BM") {
+ $meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data);
+ $width = (int)$meta['width'];
+ $height = (int)$meta['height'];
+ $type = IMAGETYPE_BMP;
+ }
+ }
+
+ return $cache[$filename] = array($width, $height, $type);
+ }
+
+ function image($img, $x, $y, $w, $h, $resolution = "normal")
+ {
+ list($width, $height, $type) = $this->getimagesize($img);
+
+ switch ($type) {
+ case IMAGETYPE_JPEG:
+ $this->canvas->addJpegFromFile($img, $x, $y - $h, $w, $h);
+ break;
+
+ case IMAGETYPE_GIF:
+ case IMAGETYPE_BMP:
+ // @todo use cache for BMP and GIF
+ $img = $this->_convert_gif_bmp_to_png($img, $type);
+
+ case IMAGETYPE_PNG:
+ $this->canvas->addPngFromFile($img, $x, $y - $h, $w, $h);
+ break;
+
+ default:
+ }
+ }
+
+ public function lineTo($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->lineTo($x, $y);
+ }
+
+ public function moveTo($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->moveTo($x, $y);
+ }
+
+ public function quadraticCurveTo($cpx, $cpy, $x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ // FIXME not accurate
+ $this->canvas->quadTo($cpx, $cpy, $x, $y);
+ }
+
+ public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->curveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
+ }
+
+ public function arcTo($x1, $y1, $x2, $y2, $radius)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ }
+
+ public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, $startAngle, $endAngle, false, false, false, true);
+ }
+
+ public function circle($x, $y, $radius)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, 0, 360, true, false, false, false);
+ }
+
+ public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->ellipse($x, $y, $radiusX, $radiusY, 0, 8, 0, 360, false, false, false, false);
+ }
+
+ public function fillRect($x, $y, $w, $h)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->rect($x, $y, $w, $h);
+ $this->fill();
+ }
+
+ public function rect($x, $y, $w, $h, $rx = 0, $ry = 0)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $canvas = $this->canvas;
+
+ if ($rx <= 0.000001/* && $ry <= 0.000001*/) {
+ $canvas->rect($x, $y, $w, $h);
+
+ return;
+ }
+
+ $rx = min($rx, $w / 2);
+ $rx = min($rx, $h / 2);
+
+ /* Define a path for a rectangle with corners rounded by a given radius.
+ * Start from the lower left corner and proceed counterclockwise.
+ */
+ $this->moveTo($x + $rx, $y);
+
+ /* Start of the arc segment in the lower right corner */
+ $this->lineTo($x + $w - $rx, $y);
+
+ /* Arc segment in the lower right corner */
+ $this->arc($x + $w - $rx, $y + $rx, $rx, 270, 360);
+
+ /* Start of the arc segment in the upper right corner */
+ $this->lineTo($x + $w, $y + $h - $rx );
+
+ /* Arc segment in the upper right corner */
+ $this->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90);
+
+ /* Start of the arc segment in the upper left corner */
+ $this->lineTo($x + $rx, $y + $h);
+
+ /* Arc segment in the upper left corner */
+ $this->arc($x + $rx, $y + $h - $rx, $rx, 90, 180);
+
+ /* Start of the arc segment in the lower left corner */
+ $this->lineTo($x , $y + $rx);
+
+ /* Arc segment in the lower left corner */
+ $this->arc($x + $rx, $y + $rx, $rx, 180, 270);
+ }
+
+ public function fill()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->fill();
+ }
+
+ public function strokeRect($x, $y, $w, $h)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->rect($x, $y, $w, $h);
+ $this->stroke();
+ }
+
+ public function stroke(bool $close = false)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->stroke($close);
+ }
+
+ public function endPath()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->endPath();
+ }
+
+ public function measureText($text)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $style = $this->getStyle();
+ $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
+
+ return $this->canvas->getTextWidth($this->getStyle()->fontSize, $text);
+ }
+
+ public function getStyle()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ return $this->style;
+ }
+
+ public function setStyle(Style $style)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $this->style = $style;
+ $canvas = $this->canvas;
+
+ if (is_array($style->stroke) && $stroke = $style->stroke) {
+ $canvas->setStrokeColor(array((float)$stroke[0]/255, (float)$stroke[1]/255, (float)$stroke[2]/255), true);
+ }
+
+ if (is_array($style->fill) && $fill = $style->fill) {
+ $canvas->setColor(array((float)$fill[0]/255, (float)$fill[1]/255, (float)$fill[2]/255), true);
+ }
+
+ if ($fillRule = strtolower($style->fillRule)) {
+ $canvas->setFillRule($fillRule);
+ }
+
+ $opacity = $style->opacity;
+ if ($opacity !== null && $opacity < 1.0) {
+ $canvas->setLineTransparency("Normal", $opacity);
+ $canvas->currentLineTransparency = null;
+
+ $canvas->setFillTransparency("Normal", $opacity);
+ $canvas->currentFillTransparency = null;
+ }
+ else {
+ $fillOpacity = $style->fillOpacity;
+ if ($fillOpacity !== null && $fillOpacity < 1.0) {
+ $canvas->setFillTransparency("Normal", $fillOpacity);
+ $canvas->currentFillTransparency = null;
+ }
+
+ $strokeOpacity = $style->strokeOpacity;
+ if ($strokeOpacity !== null && $strokeOpacity < 1.0) {
+ $canvas->setLineTransparency("Normal", $strokeOpacity);
+ $canvas->currentLineTransparency = null;
+ }
+ }
+
+ $dashArray = null;
+ if ($style->strokeDasharray) {
+ $dashArray = preg_split('/\s*,\s*/', $style->strokeDasharray);
+ }
+
+
+ $phase=0;
+ if ($style->strokeDashoffset) {
+ $phase = $style->strokeDashoffset;
+ }
+
+
+ $canvas->setLineStyle(
+ $style->strokeWidth,
+ $style->strokeLinecap,
+ $style->strokeLinejoin,
+ $dashArray,
+ $phase
+ );
+
+ $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
+ }
+
+ public function setFont($family, $style, $weight)
+ {
+ $map = [
+ "serif" => "times",
+ "sans-serif" => "helvetica",
+ "fantasy" => "symbol",
+ "cursive" => "times",
+ "monospace" => "courier"
+ ];
+
+ $styleMap = [
+ "courier" => [
+ "" => "Courier",
+ "b" => "Courier-Bold",
+ "i" => "Courier-Oblique",
+ "bi" => "Courier-BoldOblique",
+ ],
+ "helvetica" => [
+ "" => "Helvetica",
+ "b" => "Helvetica-Bold",
+ "i" => "Helvetica-Oblique",
+ "bi" => "Helvetica-BoldOblique",
+ ],
+ "symbol" => [
+ "" => "Symbol"
+ ],
+ "times" => [
+ "" => "Times-Roman",
+ "b" => "Times-Bold",
+ "i" => "Times-Italic",
+ "bi" => "Times-BoldItalic",
+ ],
+ ];
+
+ $family_lc = strtolower($family);
+ if (isset($map[$family_lc])) {
+ $family = $map[$family_lc];
+ }
+
+ if (isset($styleMap[$family])) {
+ $key = "";
+
+ $weight = strtolower($weight);
+ if ($weight === "bold" || $weight === "bolder" || (is_numeric($weight) && $weight >= 600)) {
+ $key .= "b";
+ }
+
+ $style = strtolower($style);
+ if ($style === "italic" || $style === "oblique") {
+ $key .= "i";
+ }
+
+ if (isset($styleMap[$family][$key])) {
+ $family = $styleMap[$family][$key];
+ }
+ }
+
+ $this->canvas->selectFont("$family.afm");
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceInterface.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceInterface.php
new file mode 100644
index 0000000..25b3001
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceInterface.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Surface;
+
+use Svg\Style;
+
+/**
+ * Interface Surface, like CanvasRenderingContext2D
+ *
+ * @package Svg
+ */
+interface SurfaceInterface
+{
+ public function save();
+
+ public function restore();
+
+ // transformations (default transform is the identity matrix)
+ public function scale($x, $y);
+
+ public function rotate($angle);
+
+ public function translate($x, $y);
+
+ public function transform($a, $b, $c, $d, $e, $f);
+
+ // path ends
+ public function beginPath();
+
+ public function closePath();
+
+ public function fill();
+
+ public function stroke(bool $close = false);
+
+ public function endPath();
+
+ public function fillStroke(bool $close = false);
+
+ public function clip();
+
+ // text (see also the CanvasDrawingStyles interface)
+ public function fillText($text, $x, $y, $maxWidth = null);
+
+ public function strokeText($text, $x, $y, $maxWidth = null);
+
+ public function measureText($text);
+
+ // drawing images
+ public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null);
+
+ // paths
+ public function lineTo($x, $y);
+
+ public function moveTo($x, $y);
+
+ public function quadraticCurveTo($cpx, $cpy, $x, $y);
+
+ public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
+
+ public function arcTo($x1, $y1, $x2, $y2, $radius);
+
+ public function circle($x, $y, $radius);
+
+ public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false);
+
+ public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise);
+
+ // Rectangle
+ public function rect($x, $y, $w, $h, $rx = 0, $ry = 0);
+
+ public function fillRect($x, $y, $w, $h);
+
+ public function strokeRect($x, $y, $w, $h);
+
+ public function setStyle(Style $style);
+
+ /**
+ * @return Style
+ */
+ public function getStyle();
+
+ public function setFont($family, $style, $weight);
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfacePDFLib.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfacePDFLib.php
new file mode 100644
index 0000000..3d25aef
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Surface/SurfacePDFLib.php
@@ -0,0 +1,430 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Surface;
+
+use Svg\Style;
+use Svg\Document;
+
+class SurfacePDFLib implements SurfaceInterface
+{
+ const DEBUG = false;
+
+ private $canvas;
+
+ private $width;
+ private $height;
+
+ /** @var Style */
+ private $style;
+
+ public function __construct(Document $doc, $canvas = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $dimensions = $doc->getDimensions();
+ $w = $dimensions["width"];
+ $h = $dimensions["height"];
+
+ if (!$canvas) {
+ $canvas = new \PDFlib();
+
+ /* all strings are expected as utf8 */
+ $canvas->set_option("stringformat=utf8");
+ $canvas->set_option("errorpolicy=return");
+
+ /* open new PDF file; insert a file name to create the PDF on disk */
+ if ($canvas->begin_document("", "") == 0) {
+ die("Error: " . $canvas->get_errmsg());
+ }
+ $canvas->set_info("Creator", "PDFlib starter sample");
+ $canvas->set_info("Title", "starter_graphics");
+
+ $canvas->begin_page_ext($w, $h, "");
+ }
+
+ // Flip PDF coordinate system so that the origin is in
+ // the top left rather than the bottom left
+ $canvas->setmatrix(
+ 1, 0,
+ 0, -1,
+ 0, $h
+ );
+
+ $this->width = $w;
+ $this->height = $h;
+
+ $this->canvas = $canvas;
+ }
+
+ function out()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $this->canvas->end_page_ext("");
+ $this->canvas->end_document("");
+
+ return $this->canvas->get_buffer();
+ }
+
+ public function save()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->save();
+ }
+
+ public function restore()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->restore();
+ }
+
+ public function scale($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->scale($x, $y);
+ }
+
+ public function rotate($angle)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->rotate($angle);
+ }
+
+ public function translate($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->translate($x, $y);
+ }
+
+ public function transform($a, $b, $c, $d, $e, $f)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->concat($a, $b, $c, $d, $e, $f);
+ }
+
+ public function beginPath()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ // TODO: Implement beginPath() method.
+ }
+
+ public function closePath()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->closepath();
+ }
+
+ public function fillStroke(bool $close = false)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ if ($close) {
+ $this->canvas->closepath_fill_stroke();
+ } else {
+ $this->canvas->fill_stroke();
+ }
+ }
+
+ public function clip()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->clip();
+ }
+
+ public function fillText($text, $x, $y, $maxWidth = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->set_text_pos($x, $y);
+ $this->canvas->show($text);
+ }
+
+ public function strokeText($text, $x, $y, $maxWidth = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ // TODO: Implement drawImage() method.
+ }
+
+ public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ if (strpos($image, "data:") === 0) {
+ $data = substr($image, strpos($image, ";") + 1);
+ if (strpos($data, "base64") === 0) {
+ $data = base64_decode(substr($data, 7));
+ }
+ }
+ else {
+ $data = file_get_contents($image);
+ }
+
+ $image = tempnam(sys_get_temp_dir(), "svg");
+ file_put_contents($image, $data);
+
+ $img = $this->canvas->load_image("auto", $image, "");
+
+ $sy = $sy - $sh;
+ $this->canvas->fit_image($img, $sx, $sy, 'boxsize={' . "$sw $sh" . '} fitmethod=entire');
+
+ unlink($image);
+ }
+
+ public function lineTo($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->lineto($x, $y);
+ }
+
+ public function moveTo($x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->moveto($x, $y);
+ }
+
+ public function quadraticCurveTo($cpx, $cpy, $x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ // FIXME not accurate
+ $this->canvas->curveTo($cpx, $cpy, $cpx, $cpy, $x, $y);
+ }
+
+ public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->curveto($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
+ }
+
+ public function arcTo($x1, $y1, $x2, $y2, $radius)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ }
+
+ public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->arc($x, $y, $radius, $startAngle, $endAngle);
+ }
+
+ public function circle($x, $y, $radius)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->circle($x, $y, $radius);
+ }
+
+ public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->ellipse($x, $y, $radiusX, $radiusY);
+ }
+
+ public function fillRect($x, $y, $w, $h)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->rect($x, $y, $w, $h);
+ $this->fill();
+ }
+
+ public function rect($x, $y, $w, $h, $rx = 0, $ry = 0)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $canvas = $this->canvas;
+
+ if ($rx <= 0.000001/* && $ry <= 0.000001*/) {
+ $canvas->rect($x, $y, $w, $h);
+
+ return;
+ }
+
+ /* Define a path for a rectangle with corners rounded by a given radius.
+ * Start from the lower left corner and proceed counterclockwise.
+ */
+ $canvas->moveto($x + $rx, $y);
+
+ /* Start of the arc segment in the lower right corner */
+ $canvas->lineto($x + $w - $rx, $y);
+
+ /* Arc segment in the lower right corner */
+ $canvas->arc($x + $w - $rx, $y + $rx, $rx, 270, 360);
+
+ /* Start of the arc segment in the upper right corner */
+ $canvas->lineto($x + $w, $y + $h - $rx );
+
+ /* Arc segment in the upper right corner */
+ $canvas->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90);
+
+ /* Start of the arc segment in the upper left corner */
+ $canvas->lineto($x + $rx, $y + $h);
+
+ /* Arc segment in the upper left corner */
+ $canvas->arc($x + $rx, $y + $h - $rx, $rx, 90, 180);
+
+ /* Start of the arc segment in the lower left corner */
+ $canvas->lineto($x , $y + $rx);
+
+ /* Arc segment in the lower left corner */
+ $canvas->arc($x + $rx, $y + $rx, $rx, 180, 270);
+ }
+
+ public function fill()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->fill();
+ }
+
+ public function strokeRect($x, $y, $w, $h)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->rect($x, $y, $w, $h);
+ $this->stroke();
+ }
+
+ public function stroke(bool $close = false)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ if ($close) {
+ $this->canvas->closepath_stroke();
+ } else {
+ $this->canvas->stroke();
+ }
+ }
+
+ public function endPath()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $this->canvas->endPath();
+ }
+
+ public function measureText($text)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ $style = $this->getStyle();
+ $font = $this->getFont($style->fontFamily, $style->fontStyle);
+
+ return $this->canvas->stringwidth($text, $font, $this->getStyle()->fontSize);
+ }
+
+ public function getStyle()
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+ return $this->style;
+ }
+
+ public function setStyle(Style $style)
+ {
+ if (self::DEBUG) echo __FUNCTION__ . "\n";
+
+ $this->style = $style;
+ $canvas = $this->canvas;
+
+ if (is_array($style->stroke) && $stroke = $style->stroke) {
+ $canvas->setcolor(
+ "stroke",
+ "rgb",
+ $stroke[0] / 255,
+ $stroke[1] / 255,
+ $stroke[2] / 255,
+ null
+ );
+ }
+
+ if (is_array($style->fill) && $fill = $style->fill) {
+ $canvas->setcolor(
+ "fill",
+ "rgb",
+ $fill[0] / 255,
+ $fill[1] / 255,
+ $fill[2] / 255,
+ null
+ );
+ }
+
+ if ($fillRule = strtolower($style->fillRule)) {
+ $map = array(
+ "nonzero" => "winding",
+ "evenodd" => "evenodd",
+ );
+
+ if (isset($map[$fillRule])) {
+ $fillRule = $map[$fillRule];
+
+ $canvas->set_parameter("fillrule", $fillRule);
+ }
+ }
+
+ $opts = array();
+ if ($style->strokeWidth > 0.000001) {
+ $opts[] = "linewidth=$style->strokeWidth";
+ }
+
+ if (in_array($style->strokeLinecap, array("butt", "round", "projecting"))) {
+ $opts[] = "linecap=$style->strokeLinecap";
+ }
+
+ if (in_array($style->strokeLinejoin, array("miter", "round", "bevel"))) {
+ $opts[] = "linejoin=$style->strokeLinejoin";
+ }
+
+ $canvas->set_graphics_option(implode(" ", $opts));
+
+ $opts = array();
+ $opacity = $style->opacity;
+ if ($opacity !== null && $opacity < 1.0) {
+ $opts[] = "opacityfill=$opacity";
+ $opts[] = "opacitystroke=$opacity";
+ }
+ else {
+ $fillOpacity = $style->fillOpacity;
+ if ($fillOpacity !== null && $fillOpacity < 1.0) {
+ $opts[] = "opacityfill=$fillOpacity";
+ }
+
+ $strokeOpacity = $style->strokeOpacity;
+ if ($strokeOpacity !== null && $strokeOpacity < 1.0) {
+ $opts[] = "opacitystroke=$strokeOpacity";
+ }
+ }
+
+ if (count($opts)) {
+ $gs = $canvas->create_gstate(implode(" ", $opts));
+ $canvas->set_gstate($gs);
+ }
+
+ $font = $this->getFont($style->fontFamily, $style->fontStyle);
+ if ($font) {
+ $canvas->setfont($font, $style->fontSize);
+ }
+ }
+
+ private function getFont($family, $style)
+ {
+ $map = array(
+ "serif" => "Times",
+ "sans-serif" => "Helvetica",
+ "fantasy" => "Symbol",
+ "cursive" => "Times",
+ "monospace" => "Courier",
+
+ "arial" => "Helvetica",
+ "verdana" => "Helvetica",
+ );
+
+ $family = strtolower($family);
+ if (isset($map[$family])) {
+ $family = $map[$family];
+ }
+
+ return $this->canvas->load_font($family, "unicode", "fontstyle=$style");
+ }
+
+ public function setFont($family, $style, $weight)
+ {
+ // TODO: Implement setFont() method.
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/AbstractTag.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/AbstractTag.php
new file mode 100644
index 0000000..9fa6793
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/AbstractTag.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\CssLength;
+use Svg\Document;
+use Svg\Style;
+
+abstract class AbstractTag
+{
+ /** @var Document */
+ protected $document;
+
+ public $tagName;
+
+ /** @var Style */
+ protected $style;
+
+ protected $attributes = array();
+
+ protected $hasShape = true;
+
+ /** @var self[] */
+ protected $children = array();
+
+ public function __construct(Document $document, $tagName)
+ {
+ $this->document = $document;
+ $this->tagName = $tagName;
+ }
+
+ public function getDocument(){
+ return $this->document;
+ }
+
+ /**
+ * @return Group|null
+ */
+ public function getParentGroup() {
+ $stack = $this->getDocument()->getStack();
+ for ($i = count($stack)-2; $i >= 0; $i--) {
+ $tag = $stack[$i];
+
+ if ($tag instanceof Group || $tag instanceof Document) {
+ return $tag;
+ }
+ }
+
+ return null;
+ }
+
+ public function handle($attributes)
+ {
+ $this->attributes = $attributes;
+
+ if (!$this->getDocument()->inDefs) {
+ $this->before($attributes);
+ $this->start($attributes);
+ }
+ }
+
+ public function handleEnd()
+ {
+ if (!$this->getDocument()->inDefs) {
+ $this->end();
+ $this->after();
+ }
+ }
+
+ protected function before($attributes)
+ {
+ }
+
+ protected function start($attributes)
+ {
+ }
+
+ protected function end()
+ {
+ }
+
+ protected function after()
+ {
+ }
+
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ protected function setStyle(Style $style)
+ {
+ $this->style = $style;
+
+ if ($style->display === "none") {
+ $this->hasShape = false;
+ }
+ }
+
+ /**
+ * @return Style
+ */
+ public function getStyle()
+ {
+ return $this->style;
+ }
+
+ /**
+ * Make a style object from the tag and its attributes
+ *
+ * @param array $attributes
+ *
+ * @return Style
+ */
+ protected function makeStyle($attributes) {
+ $style = new Style();
+ $style->inherit($this);
+ $style->fromStyleSheets($this, $attributes);
+ $style->fromAttributes($attributes);
+
+ return $style;
+ }
+
+ protected function applyTransform($attributes)
+ {
+
+ if (isset($attributes["transform"])) {
+ $surface = $this->document->getSurface();
+
+ $transform = $attributes["transform"];
+
+ $matches = array();
+ preg_match_all(
+ '/(matrix|translate|scale|rotate|skew|skewX|skewY)\((.*?)\)/is',
+ $transform,
+ $matches,
+ PREG_SET_ORDER
+ );
+
+ $transformations = array();
+ foreach ($matches as $match) {
+ $arguments = preg_split('/[ ,]+/', $match[2]);
+ array_unshift($arguments, $match[1]);
+ $transformations[] = $arguments;
+ }
+
+ foreach ($transformations as $t) {
+ switch ($t[0]) {
+ case "matrix":
+ $surface->transform($t[1], $t[2], $t[3], $t[4], $t[5], $t[6]);
+ break;
+
+ case "translate":
+ $surface->translate($t[1], isset($t[2]) ? $t[2] : 0);
+ break;
+
+ case "scale":
+ $surface->scale($t[1], isset($t[2]) ? $t[2] : $t[1]);
+ break;
+
+ case "rotate":
+ if (isset($t[2])) {
+ $t[3] = isset($t[3]) ? $t[3] : 0;
+ $surface->translate($t[2], $t[3]);
+ $surface->rotate($t[1]);
+ $surface->translate(-$t[2], -$t[3]);
+ } else {
+ $surface->rotate($t[1]);
+ }
+ break;
+
+ case "skewX":
+ $tan_x = tan(deg2rad($t[1]));
+ $surface->transform(1, 0, $tan_x, 1, 0, 0);
+ break;
+
+ case "skewY":
+ $tan_y = tan(deg2rad($t[1]));
+ $surface->transform(1, $tan_y, 0, 1, 0, 0);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert the given size for the context of this current tag.
+ * Takes a pixel-based reference, which is usually specific to the context of the size,
+ * but the actual reference size will be decided based upon the unit used.
+ *
+ * @param string $size
+ * @param float $pxReference
+ *
+ * @return float
+ */
+ protected function convertSize(string $size, float $pxReference): float
+ {
+ $length = new CssLength($size);
+ $reference = $pxReference;
+ $defaultFontSize = 12;
+
+ switch ($length->getUnit()) {
+ case "em":
+ $reference = $this->style->fontSize ?? $defaultFontSize;
+ break;
+ case "rem":
+ $reference = $this->document->style->fontSize ?? $defaultFontSize;
+ break;
+ case "ex":
+ case "ch":
+ $emRef = $this->style->fontSize ?? $defaultFontSize;
+ $reference = $emRef * 0.5;
+ break;
+ case "vw":
+ $reference = $this->getDocument()->getWidth();
+ break;
+ case "vh":
+ $reference = $this->getDocument()->getHeight();
+ break;
+ case "vmin":
+ $reference = min($this->getDocument()->getHeight(), $this->getDocument()->getWidth());
+ break;
+ case "vmax":
+ $reference = max($this->getDocument()->getHeight(), $this->getDocument()->getWidth());
+ break;
+ }
+
+ return (new CssLength($size))->toPixels($reference);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Anchor.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Anchor.php
new file mode 100644
index 0000000..6979495
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Anchor.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+class Anchor extends Group
+{
+
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Circle.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Circle.php
new file mode 100644
index 0000000..e504ffe
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Circle.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Circle extends Shape
+{
+ protected $cx = 0;
+ protected $cy = 0;
+ protected $r;
+
+ public function start($attributes)
+ {
+ if (isset($attributes['cx'])) {
+ $width = $this->document->getWidth();
+ $this->cx = $this->convertSize($attributes['cx'], $width);
+ }
+ if (isset($attributes['cy'])) {
+ $height = $this->document->getHeight();
+ $this->cy = $this->convertSize($attributes['cy'], $height);
+ }
+ if (isset($attributes['r'])) {
+ $diagonal = $this->document->getDiagonal();
+ $this->r = $this->convertSize($attributes['r'], $diagonal);
+ }
+
+ $this->document->getSurface()->circle($this->cx, $this->cy, $this->r);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/ClipPath.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/ClipPath.php
new file mode 100644
index 0000000..46722f9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/ClipPath.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class ClipPath extends AbstractTag
+{
+ protected function before($attributes)
+ {
+ $surface = $this->document->getSurface();
+
+ $surface->save();
+
+ $style = $this->makeStyle($attributes);
+
+ $this->setStyle($style);
+ $surface->setStyle($style);
+
+ $this->applyTransform($attributes);
+ }
+
+ protected function after()
+ {
+ $this->document->getSurface()->restore();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Ellipse.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Ellipse.php
new file mode 100644
index 0000000..42891e0
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Ellipse.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Ellipse extends Shape
+{
+ protected $cx = 0;
+ protected $cy = 0;
+ protected $rx = 0;
+ protected $ry = 0;
+
+ public function start($attributes)
+ {
+ parent::start($attributes);
+
+ $width = $this->document->getWidth();
+ $height = $this->document->getHeight();
+
+ if (isset($attributes['cx'])) {
+ $this->cx = $this->convertSize($attributes['cx'], $width);
+ }
+ if (isset($attributes['cy'])) {
+ $this->cy = $this->convertSize($attributes['cy'], $height);
+ }
+ if (isset($attributes['rx'])) {
+ $this->rx = $this->convertSize($attributes['rx'], $width);
+ }
+ if (isset($attributes['ry'])) {
+ $this->ry = $this->convertSize($attributes['ry'], $height);
+ }
+
+ $this->document->getSurface()->ellipse($this->cx, $this->cy, $this->rx, $this->ry, 0, 0, 360, false);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Group.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Group.php
new file mode 100644
index 0000000..bacb385
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Group.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Group extends AbstractTag
+{
+ protected function before($attributes)
+ {
+ $surface = $this->document->getSurface();
+
+ $surface->save();
+
+ $style = $this->makeStyle($attributes);
+
+ $this->setStyle($style);
+ $surface->setStyle($style);
+
+ $this->applyTransform($attributes);
+ }
+
+ protected function after()
+ {
+ $this->document->getSurface()->restore();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Image.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Image.php
new file mode 100644
index 0000000..bda17ea
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Image.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Image extends AbstractTag
+{
+ protected $x = 0;
+ protected $y = 0;
+ protected $width = 0;
+ protected $height = 0;
+ protected $href = null;
+
+ protected function before($attributes)
+ {
+ parent::before($attributes);
+
+ $surface = $this->document->getSurface();
+ $surface->save();
+
+ $this->applyTransform($attributes);
+ }
+
+ public function start($attributes)
+ {
+ $height = $this->document->getHeight();
+ $width = $this->document->getWidth();
+ $this->y = $height;
+
+ if (isset($attributes['x'])) {
+ $this->x = $this->convertSize($attributes['x'], $width);
+ }
+ if (isset($attributes['y'])) {
+ $this->y = $height - $this->convertSize($attributes['y'], $height);
+ }
+
+ if (isset($attributes['width'])) {
+ $this->width = $this->convertSize($attributes['width'], $width);
+ }
+ if (isset($attributes['height'])) {
+ $this->height = $this->convertSize($attributes['height'], $height);
+ }
+
+ if (isset($attributes['xlink:href'])) {
+ $this->href = $attributes['xlink:href'];
+ }
+
+ if (isset($attributes['href'])) {
+ $this->href = $attributes['href'];
+ }
+
+ $this->document->getSurface()->transform(1, 0, 0, -1, 0, $height);
+
+ $this->document->getSurface()->drawImage($this->href, $this->x, $this->y, $this->width, $this->height);
+ }
+
+ protected function after()
+ {
+ $this->document->getSurface()->restore();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Line.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Line.php
new file mode 100644
index 0000000..fb3b64c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Line.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Line extends Shape
+{
+ protected $x1 = 0;
+ protected $y1 = 0;
+
+ protected $x2 = 0;
+ protected $y2 = 0;
+
+ public function start($attributes)
+ {
+ $height = $this->document->getHeight();
+ $width = $this->document->getWidth();
+
+ if (isset($attributes['x1'])) {
+ $this->x1 = $this->convertSize($attributes['x1'], $width);
+ }
+ if (isset($attributes['y1'])) {
+ $this->y1 = $this->convertSize($attributes['y1'], $height);
+ }
+ if (isset($attributes['x2'])) {
+ $this->x2 = $this->convertSize($attributes['x2'], $width);
+ }
+ if (isset($attributes['y2'])) {
+ $this->y2 = $this->convertSize($attributes['y2'], $height);
+ }
+
+ $surface = $this->document->getSurface();
+ $surface->moveTo($this->x1, $this->y1);
+ $surface->lineTo($this->x2, $this->y2);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/LinearGradient.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/LinearGradient.php
new file mode 100644
index 0000000..c5e6397
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/LinearGradient.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+
+use Svg\Gradient;
+use Svg\Style;
+
+class LinearGradient extends AbstractTag
+{
+ protected $x1;
+ protected $y1;
+ protected $x2;
+ protected $y2;
+
+ /** @var Gradient\Stop[] */
+ protected $stops = array();
+
+ public function start($attributes)
+ {
+ parent::start($attributes);
+
+ if (isset($attributes['x1'])) {
+ $this->x1 = $attributes['x1'];
+ }
+ if (isset($attributes['y1'])) {
+ $this->y1 = $attributes['y1'];
+ }
+ if (isset($attributes['x2'])) {
+ $this->x2 = $attributes['x2'];
+ }
+ if (isset($attributes['y2'])) {
+ $this->y2 = $attributes['y2'];
+ }
+ }
+
+ public function getStops() {
+ if (empty($this->stops)) {
+ foreach ($this->children as $_child) {
+ if ($_child->tagName != "stop") {
+ continue;
+ }
+
+ $_stop = new Gradient\Stop();
+ $_attributes = $_child->attributes;
+
+ // Style
+ if (isset($_attributes["style"])) {
+ $_style = Style::parseCssStyle($_attributes["style"]);
+
+ if (isset($_style["stop-color"])) {
+ $_stop->color = Style::parseColor($_style["stop-color"]);
+ }
+
+ if (isset($_style["stop-opacity"])) {
+ $_stop->opacity = max(0, min(1.0, $_style["stop-opacity"]));
+ }
+ }
+
+ // Attributes
+ if (isset($_attributes["offset"])) {
+ $_stop->offset = $_attributes["offset"];
+ }
+ if (isset($_attributes["stop-color"])) {
+ $_stop->color = Style::parseColor($_attributes["stop-color"]);
+ }
+ if (isset($_attributes["stop-opacity"])) {
+ $_stop->opacity = max(0, min(1.0, $_attributes["stop-opacity"]));
+ }
+
+ $this->stops[] = $_stop;
+ }
+ }
+
+ return $this->stops;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php
new file mode 100644
index 0000000..3dce7a6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Path.php
@@ -0,0 +1,576 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Surface\SurfaceInterface;
+
+class Path extends Shape
+{
+ // kindly borrowed from fabric.util.parsePath.
+ /* @see https://github.com/fabricjs/fabric.js/blob/master/src/util/path.js#L664 */
+ const NUMBER_PATTERN = '([-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?)\s*';
+ const COMMA_PATTERN = '(?:\s+,?\s*|,\s*)?';
+ const FLAG_PATTERN = '([01])';
+ const ARC_REGEXP = '/'
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::FLAG_PATTERN
+ . self::COMMA_PATTERN
+ . self::FLAG_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . self::COMMA_PATTERN
+ . self::NUMBER_PATTERN
+ . '/';
+
+ static $commandLengths = array(
+ 'm' => 2,
+ 'l' => 2,
+ 'h' => 1,
+ 'v' => 1,
+ 'c' => 6,
+ 's' => 4,
+ 'q' => 4,
+ 't' => 2,
+ 'a' => 7,
+ );
+
+ static $repeatedCommands = array(
+ 'm' => 'l',
+ 'M' => 'L',
+ );
+
+ public static function parse(string $commandSequence): array
+ {
+ $commands = array();
+ preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $commandSequence, $commands, PREG_SET_ORDER);
+
+ $path = array();
+ foreach ($commands as $c) {
+ if (count($c) == 3) {
+ $commandLower = strtolower($c[1]);
+
+ // arcs have special flags that apparently don't require spaces.
+ if ($commandLower === 'a' && preg_match_all(static::ARC_REGEXP, $c[2], $matches, PREG_PATTERN_ORDER)) {
+ $numberOfMatches = count($matches[0]);
+ for ($k = 0; $k < $numberOfMatches; ++$k) {
+ $path[] = [
+ $c[1],
+ $matches[1][$k],
+ $matches[2][$k],
+ $matches[3][$k],
+ $matches[4][$k],
+ $matches[5][$k],
+ $matches[6][$k],
+ $matches[7][$k],
+ ];
+ }
+ continue;
+ }
+
+ $arguments = array();
+ preg_match_all('/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/i', $c[2], $arguments, PREG_PATTERN_ORDER);
+ $item = $arguments[0];
+
+ if (
+ isset(self::$commandLengths[$commandLower]) &&
+ ($commandLength = self::$commandLengths[$commandLower]) &&
+ count($item) > $commandLength
+ ) {
+ $repeatedCommand = isset(self::$repeatedCommands[$c[1]]) ? self::$repeatedCommands[$c[1]] : $c[1];
+ $command = $c[1];
+
+ for ($k = 0, $klen = count($item); $k < $klen; $k += $commandLength) {
+ $_item = array_slice($item, $k, $k + $commandLength);
+ array_unshift($_item, $command);
+ $path[] = $_item;
+
+ $command = $repeatedCommand;
+ }
+ } else {
+ array_unshift($item, $c[1]);
+ $path[] = $item;
+ }
+
+ } else {
+ $item = array($c[1]);
+
+ $path[] = $item;
+ }
+ }
+
+ return $path;
+ }
+
+ public function start($attributes)
+ {
+ if (!isset($attributes['d'])) {
+ $this->hasShape = false;
+
+ return;
+ }
+
+ $path = static::parse($attributes['d']);
+ $surface = $this->document->getSurface();
+
+ // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js
+ $current = null; // current instruction
+ $previous = null;
+ $subpathStartX = 0;
+ $subpathStartY = 0;
+ $x = 0; // current x
+ $y = 0; // current y
+ $controlX = 0; // current control point x
+ $controlY = 0; // current control point y
+ $tempX = null;
+ $tempY = null;
+ $tempControlX = null;
+ $tempControlY = null;
+ $l = 0; //-((this.width / 2) + $this.pathOffset.x),
+ $t = 0; //-((this.height / 2) + $this.pathOffset.y),
+
+ foreach ($path as $current) {
+ switch ($current[0]) { // first letter
+ case 'l': // lineto, relative
+ $x += $current[1];
+ $y += $current[2];
+ $surface->lineTo($x + $l, $y + $t);
+ break;
+
+ case 'L': // lineto, absolute
+ $x = $current[1];
+ $y = $current[2];
+ $surface->lineTo($x + $l, $y + $t);
+ break;
+
+ case 'h': // horizontal lineto, relative
+ $x += $current[1];
+ $surface->lineTo($x + $l, $y + $t);
+ break;
+
+ case 'H': // horizontal lineto, absolute
+ $x = $current[1];
+ $surface->lineTo($x + $l, $y + $t);
+ break;
+
+ case 'v': // vertical lineto, relative
+ $y += $current[1];
+ $surface->lineTo($x + $l, $y + $t);
+ break;
+
+ case 'V': // verical lineto, absolute
+ $y = $current[1];
+ $surface->lineTo($x + $l, $y + $t);
+ break;
+
+ case 'm': // moveTo, relative
+ $x += $current[1];
+ $y += $current[2];
+ $subpathStartX = $x;
+ $subpathStartY = $y;
+ $surface->moveTo($x + $l, $y + $t);
+ break;
+
+ case 'M': // moveTo, absolute
+ $x = $current[1];
+ $y = $current[2];
+ $subpathStartX = $x;
+ $subpathStartY = $y;
+ $surface->moveTo($x + $l, $y + $t);
+ break;
+
+ case 'c': // bezierCurveTo, relative
+ $tempX = $x + $current[5];
+ $tempY = $y + $current[6];
+ $controlX = $x + $current[3];
+ $controlY = $y + $current[4];
+ $surface->bezierCurveTo(
+ $x + $current[1] + $l, // x1
+ $y + $current[2] + $t, // y1
+ $controlX + $l, // x2
+ $controlY + $t, // y2
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'C': // bezierCurveTo, absolute
+ $x = $current[5];
+ $y = $current[6];
+ $controlX = $current[3];
+ $controlY = $current[4];
+ $surface->bezierCurveTo(
+ $current[1] + $l,
+ $current[2] + $t,
+ $controlX + $l,
+ $controlY + $t,
+ $x + $l,
+ $y + $t
+ );
+ break;
+
+ case 's': // shorthand cubic bezierCurveTo, relative
+
+ // transform to absolute x,y
+ $tempX = $x + $current[3];
+ $tempY = $y + $current[4];
+
+ if (!preg_match('/[CcSs]/', $previous[0])) {
+ // If there is no previous command or if the previous command was not a C, c, S, or s,
+ // the control point is coincident with the current point
+ $controlX = $x;
+ $controlY = $y;
+ } else {
+ // calculate reflection of previous control points
+ $controlX = 2 * $x - $controlX;
+ $controlY = 2 * $y - $controlY;
+ }
+
+ $surface->bezierCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $x + $current[1] + $l,
+ $y + $current[2] + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ // set control point to 2nd one of this command
+ // "... the first control point is assumed to be
+ // the reflection of the second control point on
+ // the previous command relative to the current point."
+ $controlX = $x + $current[1];
+ $controlY = $y + $current[2];
+
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'S': // shorthand cubic bezierCurveTo, absolute
+ $tempX = $current[3];
+ $tempY = $current[4];
+
+ if (!preg_match('/[CcSs]/', $previous[0])) {
+ // If there is no previous command or if the previous command was not a C, c, S, or s,
+ // the control point is coincident with the current point
+ $controlX = $x;
+ $controlY = $y;
+ } else {
+ // calculate reflection of previous control points
+ $controlX = 2 * $x - $controlX;
+ $controlY = 2 * $y - $controlY;
+ }
+
+ $surface->bezierCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $current[1] + $l,
+ $current[2] + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+
+ // set control point to 2nd one of this command
+ // "... the first control point is assumed to be
+ // the reflection of the second control point on
+ // the previous command relative to the current point."
+ $controlX = $current[1];
+ $controlY = $current[2];
+
+ break;
+
+ case 'q': // quadraticCurveTo, relative
+ // transform to absolute x,y
+ $tempX = $x + $current[3];
+ $tempY = $y + $current[4];
+
+ $controlX = $x + $current[1];
+ $controlY = $y + $current[2];
+
+ $surface->quadraticCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'Q': // quadraticCurveTo, absolute
+ $tempX = $current[3];
+ $tempY = $current[4];
+
+ $surface->quadraticCurveTo(
+ $current[1] + $l,
+ $current[2] + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ $controlX = $current[1];
+ $controlY = $current[2];
+ break;
+
+ case 't': // shorthand quadraticCurveTo, relative
+
+ // transform to absolute x,y
+ $tempX = $x + $current[1];
+ $tempY = $y + $current[2];
+
+ // calculate reflection of previous control points
+ if (preg_match('/[QqT]/', $previous[0])) {
+ $controlX = 2 * $x - $controlX;
+ $controlY = 2 * $y - $controlY;
+ } elseif ($previous[0] === 't') {
+ $controlX = 2 * $x - $tempControlX;
+ $controlY = 2 * $y - $tempControlY;
+ } else {
+ $controlX = $x;
+ $controlY = $y;
+ }
+
+ $tempControlX = $controlX;
+ $tempControlY = $controlY;
+
+ $surface->quadraticCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'T':
+ $tempX = $current[1];
+ $tempY = $current[2];
+
+ // calculate reflection of previous control points
+ if (preg_match('/[QqTt]/', $previous[0])) {
+ $controlX = 2 * $x - $controlX;
+ $controlY = 2 * $y - $controlY;
+ } else {
+ $controlX = $x;
+ $controlY = $y;
+ }
+
+ $surface->quadraticCurveTo(
+ $controlX + $l,
+ $controlY + $t,
+ $tempX + $l,
+ $tempY + $t
+ );
+ $x = $tempX;
+ $y = $tempY;
+ break;
+
+ case 'a':
+ $this->drawArc(
+ $surface,
+ $x + $l,
+ $y + $t,
+ array(
+ $current[1],
+ $current[2],
+ $current[3],
+ $current[4],
+ $current[5],
+ $current[6] + $x + $l,
+ $current[7] + $y + $t
+ )
+ );
+ $x += $current[6];
+ $y += $current[7];
+ break;
+
+ case 'A':
+ // TODO: optimize this
+ $this->drawArc(
+ $surface,
+ $x + $l,
+ $y + $t,
+ array(
+ $current[1],
+ $current[2],
+ $current[3],
+ $current[4],
+ $current[5],
+ $current[6] + $l,
+ $current[7] + $t
+ )
+ );
+ $x = $current[6];
+ $y = $current[7];
+ break;
+
+ case 'z':
+ case 'Z':
+ $x = $subpathStartX;
+ $y = $subpathStartY;
+ $surface->closePath();
+ break;
+ }
+ $previous = $current;
+ }
+ }
+
+ function drawArc(SurfaceInterface $surface, $fx, $fy, $coords)
+ {
+ $rx = $coords[0];
+ $ry = $coords[1];
+ $rot = $coords[2];
+ $large = $coords[3];
+ $sweep = $coords[4];
+ $tx = $coords[5];
+ $ty = $coords[6];
+ $segs = array(
+ array(),
+ array(),
+ array(),
+ array(),
+ );
+
+ $toX = $tx - $fx;
+ $toY = $ty - $fy;
+
+ if ($toX + $toY === 0) {
+ return;
+ }
+
+ $segsNorm = $this->arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rot);
+
+ for ($i = 0, $len = count($segsNorm); $i < $len; $i++) {
+ $segs[$i][0] = $segsNorm[$i][0] + $fx;
+ $segs[$i][1] = $segsNorm[$i][1] + $fy;
+ $segs[$i][2] = $segsNorm[$i][2] + $fx;
+ $segs[$i][3] = $segsNorm[$i][3] + $fy;
+ $segs[$i][4] = $segsNorm[$i][4] + $fx;
+ $segs[$i][5] = $segsNorm[$i][5] + $fy;
+
+ call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]);
+ }
+ }
+
+ function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX)
+ {
+ $th = $rotateX * M_PI / 180;
+ $sinTh = sin($th);
+ $cosTh = cos($th);
+ $fromX = 0;
+ $fromY = 0;
+
+ $rx = abs($rx);
+ $ry = abs($ry);
+
+ $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5;
+ $py = -$cosTh * $toY * 0.5 + $sinTh * $toX * 0.5;
+ $rx2 = $rx * $rx;
+ $ry2 = $ry * $ry;
+ $py2 = $py * $py;
+ $px2 = $px * $px;
+ $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2;
+ $root = 0;
+
+ if ($pl < 0) {
+ $s = sqrt(1 - $pl / ($rx2 * $ry2));
+ $rx *= $s;
+ $ry *= $s;
+ } else {
+ $root = ($large == $sweep ? -1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 + $ry2 * $px2));
+ }
+
+ $cx = $root * $rx * $py / $ry;
+ $cy = -$root * $ry * $px / $rx;
+ $cx1 = $cosTh * $cx - $sinTh * $cy + $toX * 0.5;
+ $cy1 = $sinTh * $cx + $cosTh * $cy + $toY * 0.5;
+ $mTheta = $this->calcVectorAngle(1, 0, ($px - $cx) / $rx, ($py - $cy) / $ry);
+ $dtheta = $this->calcVectorAngle(($px - $cx) / $rx, ($py - $cy) / $ry, (-$px - $cx) / $rx, (-$py - $cy) / $ry);
+
+ if ($sweep == 0 && $dtheta > 0) {
+ $dtheta -= 2 * M_PI;
+ } else {
+ if ($sweep == 1 && $dtheta < 0) {
+ $dtheta += 2 * M_PI;
+ }
+ }
+
+ // $Convert $into $cubic $bezier $segments <= 90deg
+ $segments = ceil(abs($dtheta / M_PI * 2));
+ $result = array();
+ $mDelta = $dtheta / $segments;
+ $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2);
+ $th3 = $mTheta + $mDelta;
+
+ for ($i = 0; $i < $segments; $i++) {
+ $result[$i] = $this->segmentToBezier(
+ $mTheta,
+ $th3,
+ $cosTh,
+ $sinTh,
+ $rx,
+ $ry,
+ $cx1,
+ $cy1,
+ $mT,
+ $fromX,
+ $fromY
+ );
+ $fromX = $result[$i][4];
+ $fromY = $result[$i][5];
+ $mTheta = $th3;
+ $th3 += $mDelta;
+ }
+
+ return $result;
+ }
+
+ function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY)
+ {
+ $costh2 = cos($th2);
+ $sinth2 = sin($th2);
+ $costh3 = cos($th3);
+ $sinth3 = sin($th3);
+ $toX = $cosTh * $rx * $costh3 - $sinTh * $ry * $sinth3 + $cx1;
+ $toY = $sinTh * $rx * $costh3 + $cosTh * $ry * $sinth3 + $cy1;
+ $cp1X = $fromX + $mT * (-$cosTh * $rx * $sinth2 - $sinTh * $ry * $costh2);
+ $cp1Y = $fromY + $mT * (-$sinTh * $rx * $sinth2 + $cosTh * $ry * $costh2);
+ $cp2X = $toX + $mT * ($cosTh * $rx * $sinth3 + $sinTh * $ry * $costh3);
+ $cp2Y = $toY + $mT * ($sinTh * $rx * $sinth3 - $cosTh * $ry * $costh3);
+
+ return array(
+ $cp1X,
+ $cp1Y,
+ $cp2X,
+ $cp2Y,
+ $toX,
+ $toY
+ );
+ }
+
+ function calcVectorAngle($ux, $uy, $vx, $vy)
+ {
+ $ta = atan2($uy, $ux);
+ $tb = atan2($vy, $vx);
+ if ($tb >= $ta) {
+ return $tb - $ta;
+ } else {
+ return 2 * M_PI - ($ta - $tb);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polygon.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polygon.php
new file mode 100644
index 0000000..e7ca92a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polygon.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+class Polygon extends Shape
+{
+ public function start($attributes)
+ {
+ $tmp = array();
+ preg_match_all('/([\-]*[0-9\.]+)/', $attributes['points'], $tmp, PREG_PATTERN_ORDER);
+
+ $points = $tmp[0];
+ $count = count($points);
+
+ if ($count < 4) {
+ // nothing to draw
+ return;
+ }
+
+ $surface = $this->document->getSurface();
+ list($x, $y) = $points;
+ $surface->moveTo($x, $y);
+
+ for ($i = 2; $i < $count; $i += 2) {
+ if ($i + 1 === $count) {
+ // invalid trailing point
+ continue;
+ }
+ $x = $points[$i];
+ $y = $points[$i + 1];
+ $surface->lineTo($x, $y);
+ }
+
+ $surface->closePath();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polyline.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polyline.php
new file mode 100644
index 0000000..45e2131
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Polyline.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+class Polyline extends Shape
+{
+ public function start($attributes)
+ {
+ $tmp = array();
+ preg_match_all('/([\-]*[0-9\.]+)/', $attributes['points'], $tmp, PREG_PATTERN_ORDER);
+
+ $points = $tmp[0];
+ $count = count($points);
+
+ if ($count < 4) {
+ // nothing to draw
+ return;
+ }
+
+ $surface = $this->document->getSurface();
+ list($x, $y) = $points;
+ $surface->moveTo($x, $y);
+
+ for ($i = 2; $i < $count; $i += 2) {
+ if ($i + 1 === $count) {
+ // invalid trailing point
+ continue;
+ }
+ $x = $points[$i];
+ $y = $points[$i + 1];
+ $surface->lineTo($x, $y);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/RadialGradient.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/RadialGradient.php
new file mode 100644
index 0000000..a9de62f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/RadialGradient.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+class RadialGradient extends AbstractTag
+{
+ public function start($attributes)
+ {
+
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Rect.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Rect.php
new file mode 100644
index 0000000..b5f3f77
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Rect.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Rect extends Shape
+{
+ protected $x = 0;
+ protected $y = 0;
+ protected $width = 0;
+ protected $height = 0;
+ protected $rx = 0;
+ protected $ry = 0;
+
+ public function start($attributes)
+ {
+ $width = $this->document->getWidth();
+ $height = $this->document->getHeight();
+
+ if (isset($attributes['x'])) {
+ $this->x = $this->convertSize($attributes['x'], $width);
+ }
+ if (isset($attributes['y'])) {
+ $this->y = $this->convertSize($attributes['y'], $height);
+ }
+
+ if (isset($attributes['width'])) {
+ $this->width = $this->convertSize($attributes['width'], $width);
+ }
+ if (isset($attributes['height'])) {
+ $this->height = $this->convertSize($attributes['height'], $height);
+ }
+
+ if (isset($attributes['rx'])) {
+ $this->rx = $attributes['rx'];
+ }
+ if (isset($attributes['ry'])) {
+ $this->ry = $attributes['ry'];
+ }
+
+ $this->document->getSurface()->rect($this->x, $this->y, $this->width, $this->height, $this->rx, $this->ry);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Shape.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Shape.php
new file mode 100644
index 0000000..767e81d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Shape.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Shape extends AbstractTag
+{
+ protected function before($attributes)
+ {
+ $surface = $this->document->getSurface();
+
+ $surface->save();
+
+ $style = $this->makeStyle($attributes);
+
+ $this->setStyle($style);
+ $surface->setStyle($style);
+
+ $this->applyTransform($attributes);
+ }
+
+ protected function after()
+ {
+ $surface = $this->document->getSurface();
+
+ if ($this->hasShape) {
+ $style = $surface->getStyle();
+
+ $fill = $style->fill && is_array($style->fill);
+ $stroke = $style->stroke && is_array($style->stroke);
+
+ if ($fill) {
+ if ($stroke) {
+ $surface->fillStroke(false);
+ } else {
+// if (is_string($style->fill)) {
+// /** @var LinearGradient|RadialGradient $gradient */
+// $gradient = $this->getDocument()->getDef($style->fill);
+//
+// var_dump($gradient->getStops());
+// }
+
+ $surface->fill();
+ }
+ }
+ elseif ($stroke) {
+ $surface->stroke(false);
+ }
+ else {
+ $surface->endPath();
+ }
+ }
+
+ $surface->restore();
+ }
+} \ No newline at end of file
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Stop.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Stop.php
new file mode 100644
index 0000000..22c9a98
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Stop.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+class Stop extends AbstractTag
+{
+ public function start($attributes)
+ {
+
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/StyleTag.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/StyleTag.php
new file mode 100644
index 0000000..309de01
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/StyleTag.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Sabberworm\CSS;
+
+class StyleTag extends AbstractTag
+{
+ protected $text = "";
+
+ public function end()
+ {
+ $parser = new CSS\Parser($this->text);
+ $this->document->appendStyleSheet($parser->parse());
+ }
+
+ public function appendText($text)
+ {
+ $this->text .= $text;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Text.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Text.php
new file mode 100644
index 0000000..80e08a6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/Text.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+use Svg\Style;
+
+class Text extends Shape
+{
+ protected $x = 0;
+ protected $y = 0;
+ protected $text = "";
+
+ public function start($attributes)
+ {
+ $height = $this->document->getHeight();
+ $this->y = $height;
+
+ if (isset($attributes['x'])) {
+ $width = $this->document->getWidth();
+ $this->x = $this->convertSize($attributes['x'], $width);
+ }
+ if (isset($attributes['y'])) {
+ $this->y = $height - $this->convertSize($attributes['y'], $height);
+ }
+
+ $this->document->getSurface()->transform(1, 0, 0, -1, 0, $height);
+ }
+
+ public function end()
+ {
+ $surface = $this->document->getSurface();
+ $x = $this->x;
+ $y = $this->y;
+ $style = $surface->getStyle();
+ $surface->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
+
+ switch ($style->textAnchor) {
+ case "middle":
+ $width = $surface->measureText($this->text);
+ $x -= $width / 2;
+ break;
+
+ case "end":
+ $width = $surface->measureText($this->text);
+ $x -= $width;
+ break;
+ }
+
+ $surface->fillText($this->getText(), $x, $y);
+ }
+
+ protected function after()
+ {
+ $this->document->getSurface()->restore();
+ }
+
+ public function appendText($text)
+ {
+ $this->text .= $text;
+ }
+
+ public function getText()
+ {
+ return trim($this->text);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/UseTag.php b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/UseTag.php
new file mode 100644
index 0000000..c5f00ea
--- /dev/null
+++ b/library/vendor/dompdf/vendor/phenx/php-svg-lib/src/Svg/Tag/UseTag.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * @package php-svg-lib
+ * @link http://github.com/PhenX/php-svg-lib
+ * @author Fabien Ménager <fabien.menager@gmail.com>
+ * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
+ */
+
+namespace Svg\Tag;
+
+class UseTag extends AbstractTag
+{
+ protected $x = 0;
+ protected $y = 0;
+ protected $width;
+ protected $height;
+
+ /** @var AbstractTag */
+ protected $reference;
+
+ protected function before($attributes)
+ {
+ if (isset($attributes['x'])) {
+ $this->x = $attributes['x'];
+ }
+ if (isset($attributes['y'])) {
+ $this->y = $attributes['y'];
+ }
+
+ if (isset($attributes['width'])) {
+ $this->width = $attributes['width'];
+ }
+ if (isset($attributes['height'])) {
+ $this->height = $attributes['height'];
+ }
+
+ parent::before($attributes);
+
+ $document = $this->getDocument();
+
+ $link = $attributes["href"] ?? $attributes["xlink:href"];
+ $this->reference = $document->getDef($link);
+
+ if ($this->reference) {
+ $this->reference->before($attributes);
+ }
+
+ $surface = $document->getSurface();
+ $surface->save();
+
+ $surface->translate($this->x, $this->y);
+ }
+
+ protected function after() {
+ parent::after();
+
+ if ($this->reference) {
+ $this->reference->after();
+ }
+
+ $this->getDocument()->getSurface()->restore();
+ }
+
+ public function handle($attributes)
+ {
+ parent::handle($attributes);
+
+ if (!$this->reference) {
+ return;
+ }
+
+ $mergedAttributes = $this->reference->attributes;
+ $attributesToNotMerge = ['x', 'y', 'width', 'height'];
+ foreach ($attributes as $attrKey => $attrVal) {
+ if (!in_array($attrKey, $attributesToNotMerge) && !isset($mergedAttributes[$attrKey])) {
+ $mergedAttributes[$attrKey] = $attrVal;
+ }
+ }
+
+ $this->reference->handle($mergedAttributes);
+
+ foreach ($this->reference->children as $_child) {
+ $_attributes = array_merge($_child->attributes, $mergedAttributes);
+ $_child->handle($_attributes);
+ }
+ }
+
+ public function handleEnd()
+ {
+ parent::handleEnd();
+
+ if (!$this->reference) {
+ return;
+ }
+
+ $this->reference->handleEnd();
+
+ foreach ($this->reference->children as $_child) {
+ $_child->handleEnd();
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/CHANGELOG.md b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/CHANGELOG.md
new file mode 100644
index 0000000..a23fd0e
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/CHANGELOG.md
@@ -0,0 +1,241 @@
+# Revision History
+
+## 8.4.0
+
+### Features
+
+* Support for PHP 8.x
+* PHPDoc annotations
+* Allow usage of CSS variables inside color functions (by parsing them as regular functions)
+* Use PSR-12 code style
+* *No deprecations*
+
+### Bugfixes
+
+* Improved handling of whitespace in `calc()`
+* Fix parsing units whose prefix is also a valid unit, like `vmin`
+* Allow passing an object to `CSSList#replace`
+* Fix PHP 7.3 warnings
+* Correctly parse keyframes with `%`
+* Don’t convert large numbers to scientific notation
+* Allow a file to end after an `@import`
+* Preserve case of CSS variables as specced
+* Allow identifiers to use escapes the same way as strings
+* No longer use `eval` for the comparison in `getSelectorsBySpecificity`, in case it gets passed untrusted input (CVE-2020-13756). Also fixed in 8.3.1, 8.2.1, 8.1.1, 8.0.1, 7.0.4, 6.0.2, 5.2.1, 5.1.3, 5.0.9, 4.0.1, 3.0.1, 2.0.1, 1.0.1.
+* Prevent an infinite loop when parsing invalid grid line names
+* Remove invalid unit `vm`
+* Retain rule order after expanding shorthands
+
+### Backwards-incompatible changes
+
+* PHP ≥ 5.6 is now required
+* HHVM compatibility target dropped
+
+## 8.3.0 (2019-02-22)
+
+* Refactor parsing logic to mostly reside in the class files whose data structure is to be parsed (this should eventually allow us to unit-test specific parts of the parsing logic individually).
+* Fix error in parsing `calc` expessions when the first operand is a negative number, thanks to @raxbg.
+* Support parsing CSS4 colors in hex notation with alpha values, thanks to @raxbg.
+* Swallow more errors in lenient mode, thanks to @raxbg.
+* Allow specifying arbitrary strings to output before and after declaration blocks, thanks to @westonruter.
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 8.2.0 (2018-07-13)
+
+* Support parsing `calc()`, thanks to @raxbg.
+* Support parsing grid-lines, again thanks to @raxbg.
+* Support parsing legacy IE filters (`progid:`) in lenient mode, thanks to @FMCorz
+* Performance improvements parsing large files, again thanks to @FMCorz
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 8.1.0 (2016-07-19)
+
+* Comments are no longer silently ignored but stored with the object with which they appear (no render support, though). Thanks to @FMCorz.
+* The IE hacks using `\0` and `\9` can now be parsed (and rendered) in lenient mode. Thanks (again) to @FMCorz.
+* Media queries with or without spaces before the query are parsed. Still no *real* parsing support, though. Sorry…
+* PHPUnit is now listed as a dev-dependency in composer.json.
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 8.0.0 (2016-06-30)
+
+* Store source CSS line numbers in tokens and parsing exceptions.
+* *No deprecations*
+
+### Backwards-incompatible changes
+
+* Unrecoverable parser errors throw an exception of type `Sabberworm\CSS\Parsing\SourceException` instead of `\Exception`.
+
+## 7.0.3 (2016-04-27)
+
+* Fixed parsing empty CSS when multibyte is off
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 7.0.2 (2016-02-11)
+
+* 150 time performance boost thanks to @[ossinkine](https://github.com/ossinkine)
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 7.0.1 (2015-12-25)
+
+* No more suppressed `E_NOTICE`
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 7.0.0 (2015-08-24)
+
+* Compatibility with PHP 7. Well timed, eh?
+* *No deprecations*
+
+### Backwards-incompatible changes
+
+* The `Sabberworm\CSS\Value\String` class has been renamed to `Sabberworm\CSS\Value\CSSString`.
+
+## 6.0.1 (2015-08-24)
+
+* Remove some declarations in interfaces incompatible with PHP 5.3 (< 5.3.9)
+* *No deprecations*
+
+## 6.0.0 (2014-07-03)
+
+* Format output using Sabberworm\CSS\OutputFormat
+* *No backwards-incompatible changes*
+
+### Deprecations
+
+* The parse() method replaces __toString with an optional argument (instance of the OutputFormat class)
+
+## 5.2.0 (2014-06-30)
+
+* Support removing a selector from a declaration block using `$oBlock->removeSelector($mSelector)`
+* Introduce a specialized exception (Sabberworm\CSS\Parsing\OuputException) for exceptions during output rendering
+
+* *No deprecations*
+
+#### Backwards-incompatible changes
+
+* Outputting a declaration block that has no selectors throws an OuputException instead of outputting an invalid ` {…}` into the CSS document.
+
+## 5.1.2 (2013-10-30)
+
+* Remove the use of consumeUntil in comment parsing. This makes it possible to parse comments such as `/** Perfectly valid **/`
+* Add fr relative size unit
+* Fix some issues with HHVM
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.1.1 (2013-10-28)
+
+* Updated CHANGELOG.md to reflect changes since 5.0.4
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.1.0 (2013-10-24)
+
+* Performance enhancements by Michael M Slusarz
+* More rescue entry points for lenient parsing (unexpected tokens between declaration blocks and unclosed comments)
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.8 (2013-08-15)
+
+* Make default settings’ multibyte parsing option dependent on whether or not the mbstring extension is actually installed.
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.7 (2013-08-04)
+
+* Fix broken decimal point output optimization
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.6 (2013-05-31)
+
+* Fix broken unit test
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.5 (2013-04-17)
+
+* Initial support for lenient parsing (setting this parser option will catch some exceptions internally and recover the parser’s state as neatly as possible).
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.4 (2013-03-21)
+
+* Don’t output floats with locale-aware separator chars
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.3 (2013-03-21)
+
+* More size units recognized
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.2 (2013-03-21)
+
+* CHANGELOG.md file added to distribution
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.1 (2013-03-20)
+
+* Internal cleanup
+* *No backwards-incompatible changes*
+* *No deprecations*
+
+## 5.0.0 (2013-03-20)
+
+* Correctly parse all known CSS 3 units (including Hz and kHz).
+* Output RGB colors in short (#aaa or #ababab) notation
+* Be case-insensitive when parsing identifiers.
+* *No deprecations*
+
+### Backwards-incompatible changes
+
+* `Sabberworm\CSS\Value\Color`’s `__toString` method overrides `CSSList`’s to maybe return something other than `type(value, …)` (see above).
+
+## 4.0.0 (2013-03-19)
+
+* Support for more @-rules
+* Generic interface `Sabberworm\CSS\Property\AtRule`, implemented by all @-rule classes
+* *No deprecations*
+
+### Backwards-incompatible changes
+
+* `Sabberworm\CSS\RuleSet\AtRule` renamed to `Sabberworm\CSS\RuleSet\AtRuleSet`
+* `Sabberworm\CSS\CSSList\MediaQuery` renamed to `Sabberworm\CSS\RuleSet\CSSList\AtRuleBlockList` with differing semantics and API (which also works for other block-list-based @-rules like `@supports`).
+
+## 3.0.0 (2013-03-06)
+
+* Support for lenient parsing (on by default)
+* *No deprecations*
+
+### Backwards-incompatible changes
+
+* All properties (like whether or not to use `mb_`-functions, which default charset to use and – new – whether or not to be forgiving when parsing) are now encapsulated in an instance of `Sabberworm\CSS\Settings` which can be passed as the second argument to `Sabberworm\CSS\Parser->__construct()`.
+* Specifying a charset as the second argument to `Sabberworm\CSS\Parser->__construct()` is no longer supported. Use `Sabberworm\CSS\Settings::create()->withDefaultCharset('some-charset')` instead.
+* Setting `Sabberworm\CSS\Parser->bUseMbFunctions` has no effect. Use `Sabberworm\CSS\Settings::create()->withMultibyteSupport(true/false)` instead.
+* `Sabberworm\CSS\Parser->parse()` may throw a `Sabberworm\CSS\Parsing\UnexpectedTokenException` when in strict parsing mode.
+
+## 2.0.0 (2013-01-29)
+
+* Allow multiple rules of the same type per rule set
+
+### Backwards-incompatible changes
+
+* `Sabberworm\CSS\RuleSet->getRules()` returns an index-based array instead of an associative array. Use `Sabberworm\CSS\RuleSet->getRulesAssoc()` (which eliminates duplicate rules and lets the later rule of the same name win).
+* `Sabberworm\CSS\RuleSet->removeRule()` works as it did before except when passed an instance of `Sabberworm\CSS\Rule\Rule`, in which case it would only remove the exact rule given instead of all the rules of the same type. To get the old behaviour, use `Sabberworm\CSS\RuleSet->removeRule($oRule->getRule()`;
+
+## 1.0
+
+Initial release of a stable public API.
+
+## 0.9
+
+Last version not to use PSR-0 project organization semantics.
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/LICENSE b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/LICENSE
new file mode 100644
index 0000000..686a4e3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2011 Raphael Schweikert, https://www.sabberworm.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/library/vendor/dompdf/vendor/sabberworm/php-css-parser/README.md b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/README.md
new file mode 100644
index 0000000..66fb1c6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/README.md
@@ -0,0 +1,632 @@
+# PHP CSS Parser
+
+[![Build Status](https://github.com/sabberworm/PHP-CSS-Parser/workflows/CI/badge.svg?branch=master)](https://github.com/sabberworm/PHP-CSS-Parser/actions/)
+
+A Parser for CSS Files written in PHP. Allows extraction of CSS files into a data structure, manipulation of said structure and output as (optimized) CSS.
+
+## Usage
+
+### Installation using Composer
+
+```bash
+composer require sabberworm/php-css-parser
+```
+
+### Extraction
+
+To use the CSS Parser, create a new instance. The constructor takes the following form:
+
+```php
+new \Sabberworm\CSS\Parser($css);
+```
+
+To read a file, for example, you’d do the following:
+
+```php
+$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css'));
+$cssDocument = $parser->parse();
+```
+
+The resulting CSS document structure can be manipulated prior to being output.
+
+### Options
+
+#### Charset
+
+The charset option is used only if no `@charset` declaration is found in the CSS file. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that.
+
+```php
+$settings = \Sabberworm\CSS\Settings::create()
+ ->withDefaultCharset('windows-1252');
+$parser = new \Sabberworm\CSS\Parser($css, $settings);
+```
+
+#### Strict parsing
+
+To have the parser choke on invalid rules, supply a thusly configured `\Sabberworm\CSS\Settings` object:
+
+```php
+$parser = new \Sabberworm\CSS\Parser(
+ file_get_contents('somefile.css'),
+ \Sabberworm\CSS\Settings::create()->beStrict()
+);
+```
+
+#### Disable multibyte functions
+
+To achieve faster parsing, you can choose to have PHP-CSS-Parser use regular string functions instead of `mb_*` functions. This should work fine in most cases, even for UTF-8 files, as all the multibyte characters are in string literals. Still it’s not recommended using this with input you have no control over as it’s not thoroughly covered by test cases.
+
+```php
+$settings = \Sabberworm\CSS\Settings::create()->withMultibyteSupport(false);
+$parser = new \Sabberworm\CSS\Parser($css, $settings);
+```
+
+### Manipulation
+
+The resulting data structure consists mainly of five basic types: `CSSList`, `RuleSet`, `Rule`, `Selector` and `Value`. There are two additional types used: `Import` and `Charset`, which you won’t use often.
+
+#### CSSList
+
+`CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector), but it may also contain at-rules, charset declarations, etc. `CSSList` has the following concrete subtypes:
+
+* `Document` – representing the root of a CSS file.
+* `MediaQuery` – represents a subsection of a `CSSList` that only applies to an output device matching the contained media query.
+
+To access the items stored in a `CSSList` – like the document you got back when calling `$parser->parse()` –, use `getContents()`, then iterate over that collection and use instanceof to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`.
+
+To append a new item (selector, media query, etc.) to an existing `CSSList`, construct it using the constructor for this class and use the `append($oItem)` method.
+
+#### RuleSet
+
+`RuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist:
+
+* `AtRuleSet` – for generic at-rules which do not match the ones specifically mentioned like `@import`, `@charset` or `@media`. A common example for this is `@font-face`.
+* `DeclarationBlock` – a `RuleSet` constrained by a `Selector`; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements.
+
+Note: A `CSSList` can contain other `CSSList`s (and `Import`s as well as a `Charset`), while a `RuleSet` can only contain `Rule`s.
+
+If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)` (which accepts either a `Rule` instance or a rule name; optionally suffixed by a dash to remove all related rules).
+
+#### Rule
+
+`Rule`s just have a key (the rule) and a value. These values are all instances of a `Value`.
+
+#### Value
+
+`Value` is an abstract class that only defines the `render` method. The concrete subclasses for atomic value types are:
+
+* `Size` – consists of a numeric `size` value and a unit.
+* `Color` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
+* `CSSString` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes.
+* `URL` – URLs in CSS; always output in URL("") notation.
+
+There is another abstract subclass of `Value`, `ValueList`. A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`). There are two types of `ValueList`s:
+
+* `RuleValueList` – The default type, used to represent all multi-valued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list).
+* `CSSFunction` – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`.
+
+#### Convenience methods
+
+There are a few convenience methods on Document to ease finding, manipulating and deleting rules:
+
+* `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested your selectors are. Aliased as `getAllSelectors()`.
+* `getAllRuleSets()` – does what it says; no matter how deeply nested your rule sets are.
+* `getAllValues()` – finds all `Value` objects inside `Rule`s.
+
+## To-Do
+
+* More convenience methods (like `selectorsWithElement($sId/Class/TagName)`, `attributesOfType($type)`, `removeAttributesOfType($type)`)
+* Real multibyte support. Currently, only multibyte charsets whose first 255 code points take up only one byte and are identical with ASCII are supported (yes, UTF-8 fits this description).
+* Named color support (using `Color` instead of an anonymous string literal)
+
+## Use cases
+
+### Use `Parser` to prepend an ID to all selectors
+
+```php
+$myId = "#my_id";
+$parser = new \Sabberworm\CSS\Parser($css);
+$cssDocument = $parser->parse();
+foreach ($cssDocument->getAllDeclarationBlocks() as $block) {
+ foreach ($block->getSelectors() as $selector) {
+ // Loop over all selector parts (the comma-separated strings in a
+ // selector) and prepend the ID.
+ $selector->setSelector($myId.' '.$selector->getSelector());
+ }
+}
+```
+
+### Shrink all absolute sizes to half
+
+```php
+$parser = new \Sabberworm\CSS\Parser($css);
+$cssDocument = $parser->parse();
+foreach ($cssDocument->getAllValues() as $value) {
+ if ($value instanceof CSSSize && !$value->isRelative()) {
+ $value->setSize($value->getSize() / 2);
+ }
+}
+```
+
+### Remove unwanted rules
+
+```php
+$parser = new \Sabberworm\CSS\Parser($css);
+$cssDocument = $parser->parse();
+foreach($cssDocument->getAllRuleSets() as $oRuleSet) {
+ // Note that the added dash will make this remove all rules starting with
+ // `font-` (like `font-size`, `font-weight`, etc.) as well as a potential
+ // `font-rule`.
+ $oRuleSet->removeRule('font-');
+ $oRuleSet->removeRule('cursor');
+}
+```
+
+### Output
+
+To output the entire CSS document into a variable, just use `->render()`:
+
+```php
+$parser = new \Sabberworm\CSS\Parser(file_get_contents('somefile.css'));
+$cssDocument = $parser->parse();
+print $cssDocument->render();
+```
+
+If you want to format the output, pass an instance of type `\Sabberworm\CSS\OutputFormat`:
+
+```php
+$format = \Sabberworm\CSS\OutputFormat::create()
+ ->indentWithSpaces(4)->setSpaceBetweenRules("\n");
+print $cssDocument->render($format);
+```
+
+Or use one of the predefined formats:
+
+```php
+print $cssDocument->render(Sabberworm\CSS\OutputFormat::createPretty());
+print $cssDocument->render(Sabberworm\CSS\OutputFormat::createCompact());
+```
+
+To see what you can do with output formatting, look at the tests in `tests/OutputFormatTest.php`.
+
+## Examples
+
+### Example 1 (At-Rules)
+
+#### Input
+
+```css
+@charset "utf-8";
+
+@font-face {
+ font-family: "CrassRoots";
+ src: url("../media/cr.ttf");
+}
+
+html, body {
+ font-size: 1.6em;
+}
+
+@keyframes mymove {
+ from { top: 0px; }
+ to { top: 200px; }
+}
+
+```
+
+#### Structure (`var_dump()`)
+
+```php
+class Sabberworm\CSS\CSSList\Document#4 (2) {
+ protected $aContents =>
+ array(4) {
+ [0] =>
+ class Sabberworm\CSS\Property\Charset#6 (2) {
+ private $sCharset =>
+ class Sabberworm\CSS\Value\CSSString#5 (2) {
+ private $sString =>
+ string(5) "utf-8"
+ protected $iLineNo =>
+ int(1)
+ }
+ protected $iLineNo =>
+ int(1)
+ }
+ [1] =>
+ class Sabberworm\CSS\RuleSet\AtRuleSet#7 (4) {
+ private $sType =>
+ string(9) "font-face"
+ private $sArgs =>
+ string(0) ""
+ private $aRules =>
+ array(2) {
+ 'font-family' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#8 (4) {
+ private $sRule =>
+ string(11) "font-family"
+ private $mValue =>
+ class Sabberworm\CSS\Value\CSSString#9 (2) {
+ private $sString =>
+ string(10) "CrassRoots"
+ protected $iLineNo =>
+ int(4)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(4)
+ }
+ }
+ 'src' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#10 (4) {
+ private $sRule =>
+ string(3) "src"
+ private $mValue =>
+ class Sabberworm\CSS\Value\URL#11 (2) {
+ private $oURL =>
+ class Sabberworm\CSS\Value\CSSString#12 (2) {
+ private $sString =>
+ string(15) "../media/cr.ttf"
+ protected $iLineNo =>
+ int(5)
+ }
+ protected $iLineNo =>
+ int(5)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(5)
+ }
+ }
+ }
+ protected $iLineNo =>
+ int(3)
+ }
+ [2] =>
+ class Sabberworm\CSS\RuleSet\DeclarationBlock#13 (3) {
+ private $aSelectors =>
+ array(2) {
+ [0] =>
+ class Sabberworm\CSS\Property\Selector#14 (2) {
+ private $sSelector =>
+ string(4) "html"
+ private $iSpecificity =>
+ NULL
+ }
+ [1] =>
+ class Sabberworm\CSS\Property\Selector#15 (2) {
+ private $sSelector =>
+ string(4) "body"
+ private $iSpecificity =>
+ NULL
+ }
+ }
+ private $aRules =>
+ array(1) {
+ 'font-size' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#16 (4) {
+ private $sRule =>
+ string(9) "font-size"
+ private $mValue =>
+ class Sabberworm\CSS\Value\Size#17 (4) {
+ private $fSize =>
+ double(1.6)
+ private $sUnit =>
+ string(2) "em"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(9)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(9)
+ }
+ }
+ }
+ protected $iLineNo =>
+ int(8)
+ }
+ [3] =>
+ class Sabberworm\CSS\CSSList\KeyFrame#18 (4) {
+ private $vendorKeyFrame =>
+ string(9) "keyframes"
+ private $animationName =>
+ string(6) "mymove"
+ protected $aContents =>
+ array(2) {
+ [0] =>
+ class Sabberworm\CSS\RuleSet\DeclarationBlock#19 (3) {
+ private $aSelectors =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Property\Selector#20 (2) {
+ private $sSelector =>
+ string(4) "from"
+ private $iSpecificity =>
+ NULL
+ }
+ }
+ private $aRules =>
+ array(1) {
+ 'top' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#21 (4) {
+ private $sRule =>
+ string(3) "top"
+ private $mValue =>
+ class Sabberworm\CSS\Value\Size#22 (4) {
+ private $fSize =>
+ double(0)
+ private $sUnit =>
+ string(2) "px"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(13)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(13)
+ }
+ }
+ }
+ protected $iLineNo =>
+ int(13)
+ }
+ [1] =>
+ class Sabberworm\CSS\RuleSet\DeclarationBlock#23 (3) {
+ private $aSelectors =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Property\Selector#24 (2) {
+ private $sSelector =>
+ string(2) "to"
+ private $iSpecificity =>
+ NULL
+ }
+ }
+ private $aRules =>
+ array(1) {
+ 'top' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#25 (4) {
+ private $sRule =>
+ string(3) "top"
+ private $mValue =>
+ class Sabberworm\CSS\Value\Size#26 (4) {
+ private $fSize =>
+ double(200)
+ private $sUnit =>
+ string(2) "px"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(14)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(14)
+ }
+ }
+ }
+ protected $iLineNo =>
+ int(14)
+ }
+ }
+ protected $iLineNo =>
+ int(12)
+ }
+ }
+ protected $iLineNo =>
+ int(1)
+}
+
+```
+
+#### Output (`render()`)
+
+```css
+@charset "utf-8";
+@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}
+html, body {font-size: 1.6em;}
+@keyframes mymove {from {top: 0px;} to {top: 200px;}}
+```
+
+### Example 2 (Values)
+
+#### Input
+
+```css
+#header {
+ margin: 10px 2em 1cm 2%;
+ font-family: Verdana, Helvetica, "Gill Sans", sans-serif;
+ color: red !important;
+}
+
+```
+
+#### Structure (`var_dump()`)
+
+```php
+class Sabberworm\CSS\CSSList\Document#4 (2) {
+ protected $aContents =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\RuleSet\DeclarationBlock#5 (3) {
+ private $aSelectors =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Property\Selector#6 (2) {
+ private $sSelector =>
+ string(7) "#header"
+ private $iSpecificity =>
+ NULL
+ }
+ }
+ private $aRules =>
+ array(3) {
+ 'margin' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#7 (4) {
+ private $sRule =>
+ string(6) "margin"
+ private $mValue =>
+ class Sabberworm\CSS\Value\RuleValueList#12 (3) {
+ protected $aComponents =>
+ array(4) {
+ [0] =>
+ class Sabberworm\CSS\Value\Size#8 (4) {
+ private $fSize =>
+ double(10)
+ private $sUnit =>
+ string(2) "px"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(2)
+ }
+ [1] =>
+ class Sabberworm\CSS\Value\Size#9 (4) {
+ private $fSize =>
+ double(2)
+ private $sUnit =>
+ string(2) "em"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(2)
+ }
+ [2] =>
+ class Sabberworm\CSS\Value\Size#10 (4) {
+ private $fSize =>
+ double(1)
+ private $sUnit =>
+ string(2) "cm"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(2)
+ }
+ [3] =>
+ class Sabberworm\CSS\Value\Size#11 (4) {
+ private $fSize =>
+ double(2)
+ private $sUnit =>
+ string(1) "%"
+ private $bIsColorComponent =>
+ bool(false)
+ protected $iLineNo =>
+ int(2)
+ }
+ }
+ protected $sSeparator =>
+ string(1) " "
+ protected $iLineNo =>
+ int(2)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(2)
+ }
+ }
+ 'font-family' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#13 (4) {
+ private $sRule =>
+ string(11) "font-family"
+ private $mValue =>
+ class Sabberworm\CSS\Value\RuleValueList#15 (3) {
+ protected $aComponents =>
+ array(4) {
+ [0] =>
+ string(7) "Verdana"
+ [1] =>
+ string(9) "Helvetica"
+ [2] =>
+ class Sabberworm\CSS\Value\CSSString#14 (2) {
+ private $sString =>
+ string(9) "Gill Sans"
+ protected $iLineNo =>
+ int(3)
+ }
+ [3] =>
+ string(10) "sans-serif"
+ }
+ protected $sSeparator =>
+ string(1) ","
+ protected $iLineNo =>
+ int(3)
+ }
+ private $bIsImportant =>
+ bool(false)
+ protected $iLineNo =>
+ int(3)
+ }
+ }
+ 'color' =>
+ array(1) {
+ [0] =>
+ class Sabberworm\CSS\Rule\Rule#16 (4) {
+ private $sRule =>
+ string(5) "color"
+ private $mValue =>
+ string(3) "red"
+ private $bIsImportant =>
+ bool(true)
+ protected $iLineNo =>
+ int(4)
+ }
+ }
+ }
+ protected $iLineNo =>
+ int(1)
+ }
+ }
+ protected $iLineNo =>
+ int(1)
+}
+
+```
+
+#### Output (`render()`)
+
+```css
+#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;}
+```
+
+## Contributors/Thanks to
+
+* [oliverklee](https://github.com/oliverklee) for lots of refactorings, code modernizations and CI integrations
+* [raxbg](https://github.com/raxbg) for contributions to parse `calc`, grid lines, and various bugfixes.
+* [westonruter](https://github.com/westonruter) for bugfixes and improvements.
+* [FMCorz](https://github.com/FMCorz) for many patches and suggestions, for being able to parse comments and IE hacks (in lenient mode).
+* [Lullabot](https://github.com/Lullabot) for a patch that allows to know the line number for each parsed token.
+* [ju1ius](https://github.com/ju1ius) for the specificity parsing code and the ability to expand/compact shorthand properties.
+* [ossinkine](https://github.com/ossinkine) for a 150 time performance boost.
+* [GaryJones](https://github.com/GaryJones) for lots of input and [https://css-specificity.info/](https://css-specificity.info/).
+* [docteurklein](https://github.com/docteurklein) for output formatting and `CSSList->remove()` inspiration.
+* [nicolopignatelli](https://github.com/nicolopignatelli) for PSR-0 compatibility.
+* [diegoembarcadero](https://github.com/diegoembarcadero) for keyframe at-rule parsing.
+* [goetas](https://github.com/goetas) for @namespace at-rule support.
+* [View full list](https://github.com/sabberworm/PHP-CSS-Parser/contributors)
+
+## Misc
+
+* Legacy Support: The latest pre-PSR-0 version of this project can be checked with the `0.9.0` tag.
+* Running Tests: To run all unit tests for this project, run `composer install` to install phpunit and use `./vendor/bin/phpunit`.
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/composer.json b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/composer.json
new file mode 100644
index 0000000..e192dd5
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/composer.json
@@ -0,0 +1,69 @@
+{
+ "name": "sabberworm/php-css-parser",
+ "type": "library",
+ "description": "Parser for CSS Files written in PHP",
+ "keywords": [
+ "parser",
+ "css",
+ "stylesheet"
+ ],
+ "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Raphael Schweikert"
+ }
+ ],
+ "require": {
+ "php": ">=5.6.20",
+ "ext-iconv": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.36",
+ "codacy/coverage": "^1.4"
+ },
+ "suggest": {
+ "ext-mbstring": "for parsing UTF-8 CSS"
+ },
+ "autoload": {
+ "psr-4": {
+ "Sabberworm\\CSS\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Sabberworm\\CSS\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "ci": [
+ "@ci:static"
+ ],
+ "ci:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix --dry-run -v --show-progress=dots bin src tests",
+ "ci:php:sniffer": "@php ./.phive/phpcs.phar --standard=config/phpcs.xml bin src tests",
+ "ci:php:stan": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon",
+ "ci:static": [
+ "@ci:php:fixer",
+ "@ci:php:sniffer",
+ "@ci:php:stan"
+ ],
+ "fix:php": [
+ "@fix:php:fixer",
+ "@fix:php:sniffer"
+ ],
+ "fix:php:fixer": "@php ./.phive/php-cs-fixer.phar --config=config/php-cs-fixer.php fix bin src tests",
+ "fix:php:sniffer": "@php ./.phive/phpcbf.phar --standard=config/phpcs.xml bin src tests",
+ "phpstan:baseline": "@php ./.phive/phpstan.phar --configuration=config/phpstan.neon --generate-baseline=config/phpstan-baseline.neon"
+ },
+ "scripts-descriptions": {
+ "ci": "Runs all dynamic and static code checks (i.e. currently, only the static checks).",
+ "ci:php:fixer": "Checks the code style with PHP CS Fixer.",
+ "ci:php:sniffer": "Checks the code style with PHP_CodeSniffer.",
+ "ci:php:stan": "Checks the types with PHPStan.",
+ "ci:static": "Runs all static code analysis checks for the code.",
+ "fix:php": "Autofixes all autofixable issues in the PHP code.",
+ "fix:php:fixer": "Fixes autofixable issues found by PHP CS Fixer.",
+ "fix:php:sniffer": "Fixes autofixable issues found by PHP_CodeSniffer.",
+ "phpstand:baseline": "Updates the PHPStan baseline file to match the code."
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php
new file mode 100644
index 0000000..218adb9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/AtRuleBlockList.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Sabberworm\CSS\CSSList;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Property\AtRule;
+
+/**
+ * A `BlockList` constructed by an unknown at-rule. `@media` rules are rendered into `AtRuleBlockList` objects.
+ */
+class AtRuleBlockList extends CSSBlockList implements AtRule
+{
+ /**
+ * @var string
+ */
+ private $sType;
+
+ /**
+ * @var string
+ */
+ private $sArgs;
+
+ /**
+ * @param string $sType
+ * @param string $sArgs
+ * @param int $iLineNo
+ */
+ public function __construct($sType, $sArgs = '', $iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ $this->sType = $sType;
+ $this->sArgs = $sArgs;
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleName()
+ {
+ return $this->sType;
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleArgs()
+ {
+ return $this->sArgs;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sArgs = $this->sArgs;
+ if ($sArgs) {
+ $sArgs = ' ' . $sArgs;
+ }
+ $sResult = $oOutputFormat->sBeforeAtRuleBlock;
+ $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{";
+ $sResult .= parent::render($oOutputFormat);
+ $sResult .= '}';
+ $sResult .= $oOutputFormat->sAfterAtRuleBlock;
+ return $sResult;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRootList()
+ {
+ return false;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php
new file mode 100644
index 0000000..fce7913
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSBlockList.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Sabberworm\CSS\CSSList;
+
+use Sabberworm\CSS\Property\Selector;
+use Sabberworm\CSS\Rule\Rule;
+use Sabberworm\CSS\RuleSet\DeclarationBlock;
+use Sabberworm\CSS\RuleSet\RuleSet;
+use Sabberworm\CSS\Value\CSSFunction;
+use Sabberworm\CSS\Value\Value;
+use Sabberworm\CSS\Value\ValueList;
+
+/**
+ * A `CSSBlockList` is a `CSSList` whose `DeclarationBlock`s are guaranteed to contain valid declaration blocks or
+ * at-rules.
+ *
+ * Most `CSSList`s conform to this category but some at-rules (such as `@keyframes`) do not.
+ */
+abstract class CSSBlockList extends CSSList
+{
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ }
+
+ /**
+ * @param array<int, DeclarationBlock> $aResult
+ *
+ * @return void
+ */
+ protected function allDeclarationBlocks(array &$aResult)
+ {
+ foreach ($this->aContents as $mContent) {
+ if ($mContent instanceof DeclarationBlock) {
+ $aResult[] = $mContent;
+ } elseif ($mContent instanceof CSSBlockList) {
+ $mContent->allDeclarationBlocks($aResult);
+ }
+ }
+ }
+
+ /**
+ * @param array<int, RuleSet> $aResult
+ *
+ * @return void
+ */
+ protected function allRuleSets(array &$aResult)
+ {
+ foreach ($this->aContents as $mContent) {
+ if ($mContent instanceof RuleSet) {
+ $aResult[] = $mContent;
+ } elseif ($mContent instanceof CSSBlockList) {
+ $mContent->allRuleSets($aResult);
+ }
+ }
+ }
+
+ /**
+ * @param CSSList|Rule|RuleSet|Value $oElement
+ * @param array<int, Value> $aResult
+ * @param string|null $sSearchString
+ * @param bool $bSearchInFunctionArguments
+ *
+ * @return void
+ */
+ protected function allValues($oElement, array &$aResult, $sSearchString = null, $bSearchInFunctionArguments = false)
+ {
+ if ($oElement instanceof CSSBlockList) {
+ foreach ($oElement->getContents() as $oContent) {
+ $this->allValues($oContent, $aResult, $sSearchString, $bSearchInFunctionArguments);
+ }
+ } elseif ($oElement instanceof RuleSet) {
+ foreach ($oElement->getRules($sSearchString) as $oRule) {
+ $this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments);
+ }
+ } elseif ($oElement instanceof Rule) {
+ $this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments);
+ } elseif ($oElement instanceof ValueList) {
+ if ($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) {
+ foreach ($oElement->getListComponents() as $mComponent) {
+ $this->allValues($mComponent, $aResult, $sSearchString, $bSearchInFunctionArguments);
+ }
+ }
+ } else {
+ // Non-List `Value` or `CSSString` (CSS identifier)
+ $aResult[] = $oElement;
+ }
+ }
+
+ /**
+ * @param array<int, Selector> $aResult
+ * @param string|null $sSpecificitySearch
+ *
+ * @return void
+ */
+ protected function allSelectors(array &$aResult, $sSpecificitySearch = null)
+ {
+ /** @var array<int, DeclarationBlock> $aDeclarationBlocks */
+ $aDeclarationBlocks = [];
+ $this->allDeclarationBlocks($aDeclarationBlocks);
+ foreach ($aDeclarationBlocks as $oBlock) {
+ foreach ($oBlock->getSelectors() as $oSelector) {
+ if ($sSpecificitySearch === null) {
+ $aResult[] = $oSelector;
+ } else {
+ $sComparator = '===';
+ $aSpecificitySearch = explode(' ', $sSpecificitySearch);
+ $iTargetSpecificity = $aSpecificitySearch[0];
+ if (count($aSpecificitySearch) > 1) {
+ $sComparator = $aSpecificitySearch[0];
+ $iTargetSpecificity = $aSpecificitySearch[1];
+ }
+ $iTargetSpecificity = (int)$iTargetSpecificity;
+ $iSelectorSpecificity = $oSelector->getSpecificity();
+ $bMatches = false;
+ switch ($sComparator) {
+ case '<=':
+ $bMatches = $iSelectorSpecificity <= $iTargetSpecificity;
+ break;
+ case '<':
+ $bMatches = $iSelectorSpecificity < $iTargetSpecificity;
+ break;
+ case '>=':
+ $bMatches = $iSelectorSpecificity >= $iTargetSpecificity;
+ break;
+ case '>':
+ $bMatches = $iSelectorSpecificity > $iTargetSpecificity;
+ break;
+ default:
+ $bMatches = $iSelectorSpecificity === $iTargetSpecificity;
+ break;
+ }
+ if ($bMatches) {
+ $aResult[] = $oSelector;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php
new file mode 100644
index 0000000..946740a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/CSSList.php
@@ -0,0 +1,479 @@
+<?php
+
+namespace Sabberworm\CSS\CSSList;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\Comment\Commentable;
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\SourceException;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+use Sabberworm\CSS\Property\AtRule;
+use Sabberworm\CSS\Property\Charset;
+use Sabberworm\CSS\Property\CSSNamespace;
+use Sabberworm\CSS\Property\Import;
+use Sabberworm\CSS\Property\Selector;
+use Sabberworm\CSS\Renderable;
+use Sabberworm\CSS\RuleSet\AtRuleSet;
+use Sabberworm\CSS\RuleSet\DeclarationBlock;
+use Sabberworm\CSS\RuleSet\RuleSet;
+use Sabberworm\CSS\Settings;
+use Sabberworm\CSS\Value\CSSString;
+use Sabberworm\CSS\Value\URL;
+use Sabberworm\CSS\Value\Value;
+
+/**
+ * A `CSSList` is the most generic container available. Its contents include `RuleSet` as well as other `CSSList`
+ * objects.
+ *
+ * Also, it may contain `Import` and `Charset` objects stemming from at-rules.
+ */
+abstract class CSSList implements Renderable, Commentable
+{
+ /**
+ * @var array<array-key, Comment>
+ */
+ protected $aComments;
+
+ /**
+ * @var array<int, RuleSet|CSSList|Import|Charset>
+ */
+ protected $aContents;
+
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ $this->aComments = [];
+ $this->aContents = [];
+ $this->iLineNo = $iLineNo;
+ }
+
+ /**
+ * @return void
+ *
+ * @throws UnexpectedTokenException
+ * @throws SourceException
+ */
+ public static function parseList(ParserState $oParserState, CSSList $oList)
+ {
+ $bIsRoot = $oList instanceof Document;
+ if (is_string($oParserState)) {
+ $oParserState = new ParserState($oParserState, Settings::create());
+ }
+ $bLenientParsing = $oParserState->getSettings()->bLenientParsing;
+ while (!$oParserState->isEnd()) {
+ $comments = $oParserState->consumeWhiteSpace();
+ $oListItem = null;
+ if ($bLenientParsing) {
+ try {
+ $oListItem = self::parseListItem($oParserState, $oList);
+ } catch (UnexpectedTokenException $e) {
+ $oListItem = false;
+ }
+ } else {
+ $oListItem = self::parseListItem($oParserState, $oList);
+ }
+ if ($oListItem === null) {
+ // List parsing finished
+ return;
+ }
+ if ($oListItem) {
+ $oListItem->setComments($comments);
+ $oList->append($oListItem);
+ }
+ $oParserState->consumeWhiteSpace();
+ }
+ if (!$bIsRoot && !$bLenientParsing) {
+ throw new SourceException("Unexpected end of document", $oParserState->currentLine());
+ }
+ }
+
+ /**
+ * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|DeclarationBlock|null|false
+ *
+ * @throws SourceException
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ private static function parseListItem(ParserState $oParserState, CSSList $oList)
+ {
+ $bIsRoot = $oList instanceof Document;
+ if ($oParserState->comes('@')) {
+ $oAtRule = self::parseAtRule($oParserState);
+ if ($oAtRule instanceof Charset) {
+ if (!$bIsRoot) {
+ throw new UnexpectedTokenException(
+ '@charset may only occur in root document',
+ '',
+ 'custom',
+ $oParserState->currentLine()
+ );
+ }
+ if (count($oList->getContents()) > 0) {
+ throw new UnexpectedTokenException(
+ '@charset must be the first parseable token in a document',
+ '',
+ 'custom',
+ $oParserState->currentLine()
+ );
+ }
+ $oParserState->setCharset($oAtRule->getCharset()->getString());
+ }
+ return $oAtRule;
+ } elseif ($oParserState->comes('}')) {
+ if (!$oParserState->getSettings()->bLenientParsing) {
+ throw new UnexpectedTokenException('CSS selector', '}', 'identifier', $oParserState->currentLine());
+ } else {
+ if ($bIsRoot) {
+ if ($oParserState->getSettings()->bLenientParsing) {
+ return DeclarationBlock::parse($oParserState);
+ } else {
+ throw new SourceException("Unopened {", $oParserState->currentLine());
+ }
+ } else {
+ return null;
+ }
+ }
+ } else {
+ return DeclarationBlock::parse($oParserState, $oList);
+ }
+ }
+
+ /**
+ * @param ParserState $oParserState
+ *
+ * @return AtRuleBlockList|KeyFrame|Charset|CSSNamespace|Import|AtRuleSet|null
+ *
+ * @throws SourceException
+ * @throws UnexpectedTokenException
+ * @throws UnexpectedEOFException
+ */
+ private static function parseAtRule(ParserState $oParserState)
+ {
+ $oParserState->consume('@');
+ $sIdentifier = $oParserState->parseIdentifier();
+ $iIdentifierLineNum = $oParserState->currentLine();
+ $oParserState->consumeWhiteSpace();
+ if ($sIdentifier === 'import') {
+ $oLocation = URL::parse($oParserState);
+ $oParserState->consumeWhiteSpace();
+ $sMediaQuery = null;
+ if (!$oParserState->comes(';')) {
+ $sMediaQuery = trim($oParserState->consumeUntil([';', ParserState::EOF]));
+ }
+ $oParserState->consumeUntil([';', ParserState::EOF], true, true);
+ return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum);
+ } elseif ($sIdentifier === 'charset') {
+ $sCharset = CSSString::parse($oParserState);
+ $oParserState->consumeWhiteSpace();
+ $oParserState->consumeUntil([';', ParserState::EOF], true, true);
+ return new Charset($sCharset, $iIdentifierLineNum);
+ } elseif (self::identifierIs($sIdentifier, 'keyframes')) {
+ $oResult = new KeyFrame($iIdentifierLineNum);
+ $oResult->setVendorKeyFrame($sIdentifier);
+ $oResult->setAnimationName(trim($oParserState->consumeUntil('{', false, true)));
+ CSSList::parseList($oParserState, $oResult);
+ if ($oParserState->comes('}')) {
+ $oParserState->consume('}');
+ }
+ return $oResult;
+ } elseif ($sIdentifier === 'namespace') {
+ $sPrefix = null;
+ $mUrl = Value::parsePrimitiveValue($oParserState);
+ if (!$oParserState->comes(';')) {
+ $sPrefix = $mUrl;
+ $mUrl = Value::parsePrimitiveValue($oParserState);
+ }
+ $oParserState->consumeUntil([';', ParserState::EOF], true, true);
+ if ($sPrefix !== null && !is_string($sPrefix)) {
+ throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum);
+ }
+ if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) {
+ throw new UnexpectedTokenException(
+ 'Wrong namespace url of invalid type',
+ $mUrl,
+ 'custom',
+ $iIdentifierLineNum
+ );
+ }
+ return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum);
+ } else {
+ // Unknown other at rule (font-face or such)
+ $sArgs = trim($oParserState->consumeUntil('{', false, true));
+ if (substr_count($sArgs, "(") != substr_count($sArgs, ")")) {
+ if ($oParserState->getSettings()->bLenientParsing) {
+ return null;
+ } else {
+ throw new SourceException("Unmatched brace count in media query", $oParserState->currentLine());
+ }
+ }
+ $bUseRuleSet = true;
+ foreach (explode('/', AtRule::BLOCK_RULES) as $sBlockRuleName) {
+ if (self::identifierIs($sIdentifier, $sBlockRuleName)) {
+ $bUseRuleSet = false;
+ break;
+ }
+ }
+ if ($bUseRuleSet) {
+ $oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum);
+ RuleSet::parseRuleSet($oParserState, $oAtRule);
+ } else {
+ $oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum);
+ CSSList::parseList($oParserState, $oAtRule);
+ if ($oParserState->comes('}')) {
+ $oParserState->consume('}');
+ }
+ }
+ return $oAtRule;
+ }
+ }
+
+ /**
+ * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed.
+ * We need to check for these versions too.
+ *
+ * @param string $sIdentifier
+ * @param string $sMatch
+ *
+ * @return bool
+ */
+ private static function identifierIs($sIdentifier, $sMatch)
+ {
+ return (strcasecmp($sIdentifier, $sMatch) === 0)
+ ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1;
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * Prepends an item to the list of contents.
+ *
+ * @param RuleSet|CSSList|Import|Charset $oItem
+ *
+ * @return void
+ */
+ public function prepend($oItem)
+ {
+ array_unshift($this->aContents, $oItem);
+ }
+
+ /**
+ * Appends an item to tje list of contents.
+ *
+ * @param RuleSet|CSSList|Import|Charset $oItem
+ *
+ * @return void
+ */
+ public function append($oItem)
+ {
+ $this->aContents[] = $oItem;
+ }
+
+ /**
+ * Splices the list of contents.
+ *
+ * @param int $iOffset
+ * @param int $iLength
+ * @param array<int, RuleSet|CSSList|Import|Charset> $mReplacement
+ *
+ * @return void
+ */
+ public function splice($iOffset, $iLength = null, $mReplacement = null)
+ {
+ array_splice($this->aContents, $iOffset, $iLength, $mReplacement);
+ }
+
+ /**
+ * Removes an item from the CSS list.
+ *
+ * @param RuleSet|Import|Charset|CSSList $oItemToRemove
+ * May be a RuleSet (most likely a DeclarationBlock), a Import,
+ * a Charset or another CSSList (most likely a MediaQuery)
+ *
+ * @return bool whether the item was removed
+ */
+ public function remove($oItemToRemove)
+ {
+ $iKey = array_search($oItemToRemove, $this->aContents, true);
+ if ($iKey !== false) {
+ unset($this->aContents[$iKey]);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Replaces an item from the CSS list.
+ *
+ * @param RuleSet|Import|Charset|CSSList $oOldItem
+ * May be a `RuleSet` (most likely a `DeclarationBlock`), an `Import`, a `Charset`
+ * or another `CSSList` (most likely a `MediaQuery`)
+ *
+ * @return bool
+ */
+ public function replace($oOldItem, $mNewItem)
+ {
+ $iKey = array_search($oOldItem, $this->aContents, true);
+ if ($iKey !== false) {
+ if (is_array($mNewItem)) {
+ array_splice($this->aContents, $iKey, 1, $mNewItem);
+ } else {
+ array_splice($this->aContents, $iKey, 1, [$mNewItem]);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param array<int, RuleSet|Import|Charset|CSSList> $aContents
+ */
+ public function setContents(array $aContents)
+ {
+ $this->aContents = [];
+ foreach ($aContents as $content) {
+ $this->append($content);
+ }
+ }
+
+ /**
+ * Removes a declaration block from the CSS list if it matches all given selectors.
+ *
+ * @param DeclarationBlock|array<array-key, Selector>|string $mSelector the selectors to match
+ * @param bool $bRemoveAll whether to stop at the first declaration block found or remove all blocks
+ *
+ * @return void
+ */
+ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false)
+ {
+ if ($mSelector instanceof DeclarationBlock) {
+ $mSelector = $mSelector->getSelectors();
+ }
+ if (!is_array($mSelector)) {
+ $mSelector = explode(',', $mSelector);
+ }
+ foreach ($mSelector as $iKey => &$mSel) {
+ if (!($mSel instanceof Selector)) {
+ if (!Selector::isValid($mSel)) {
+ throw new UnexpectedTokenException(
+ "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
+ $mSel,
+ "custom"
+ );
+ }
+ $mSel = new Selector($mSel);
+ }
+ }
+ foreach ($this->aContents as $iKey => $mItem) {
+ if (!($mItem instanceof DeclarationBlock)) {
+ continue;
+ }
+ if ($mItem->getSelectors() == $mSelector) {
+ unset($this->aContents[$iKey]);
+ if (!$bRemoveAll) {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sResult = '';
+ $bIsFirst = true;
+ $oNextLevel = $oOutputFormat;
+ if (!$this->isRootList()) {
+ $oNextLevel = $oOutputFormat->nextLevel();
+ }
+ foreach ($this->aContents as $oContent) {
+ $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) {
+ return $oContent->render($oNextLevel);
+ });
+ if ($sRendered === null) {
+ continue;
+ }
+ if ($bIsFirst) {
+ $bIsFirst = false;
+ $sResult .= $oNextLevel->spaceBeforeBlocks();
+ } else {
+ $sResult .= $oNextLevel->spaceBetweenBlocks();
+ }
+ $sResult .= $sRendered;
+ }
+
+ if (!$bIsFirst) {
+ // Had some output
+ $sResult .= $oOutputFormat->spaceAfterBlocks();
+ }
+
+ return $sResult;
+ }
+
+ /**
+ * Return true if the list can not be further outdented. Only important when rendering.
+ *
+ * @return bool
+ */
+ abstract public function isRootList();
+
+ /**
+ * @return array<int, RuleSet|Import|Charset|CSSList>
+ */
+ public function getContents()
+ {
+ return $this->aContents;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments)
+ {
+ $this->aComments = array_merge($this->aComments, $aComments);
+ }
+
+ /**
+ * @return array<array-key, Comment>
+ */
+ public function getComments()
+ {
+ return $this->aComments;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments)
+ {
+ $this->aComments = $aComments;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/Document.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/Document.php
new file mode 100644
index 0000000..91ab2c6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/Document.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Sabberworm\CSS\CSSList;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\SourceException;
+use Sabberworm\CSS\Property\Selector;
+use Sabberworm\CSS\RuleSet\DeclarationBlock;
+use Sabberworm\CSS\RuleSet\RuleSet;
+use Sabberworm\CSS\Value\Value;
+
+/**
+ * The root `CSSList` of a parsed file. Contains all top-level CSS contents, mostly declaration blocks,
+ * but also any at-rules encountered.
+ */
+class Document extends CSSBlockList
+{
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ }
+
+ /**
+ * @return Document
+ *
+ * @throws SourceException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $oDocument = new Document($oParserState->currentLine());
+ CSSList::parseList($oParserState, $oDocument);
+ return $oDocument;
+ }
+
+ /**
+ * Gets all `DeclarationBlock` objects recursively.
+ *
+ * @return array<int, DeclarationBlock>
+ */
+ public function getAllDeclarationBlocks()
+ {
+ /** @var array<int, DeclarationBlock> $aResult */
+ $aResult = [];
+ $this->allDeclarationBlocks($aResult);
+ return $aResult;
+ }
+
+ /**
+ * Gets all `DeclarationBlock` objects recursively.
+ *
+ * @return array<int, DeclarationBlock>
+ *
+ * @deprecated will be removed in version 9.0; use `getAllDeclarationBlocks()` instead
+ */
+ public function getAllSelectors()
+ {
+ return $this->getAllDeclarationBlocks();
+ }
+
+ /**
+ * Returns all `RuleSet` objects found recursively in the tree.
+ *
+ * @return array<int, RuleSet>
+ */
+ public function getAllRuleSets()
+ {
+ /** @var array<int, RuleSet> $aResult */
+ $aResult = [];
+ $this->allRuleSets($aResult);
+ return $aResult;
+ }
+
+ /**
+ * Returns all `Value` objects found recursively in the tree.
+ *
+ * @param CSSList|RuleSet|string $mElement
+ * the `CSSList` or `RuleSet` to start the search from (defaults to the whole document).
+ * If a string is given, it is used as rule name filter.
+ * @param bool $bSearchInFunctionArguments whether to also return Value objects used as Function arguments.
+ *
+ * @return array<int, Value>
+ *
+ * @see RuleSet->getRules()
+ */
+ public function getAllValues($mElement = null, $bSearchInFunctionArguments = false)
+ {
+ $sSearchString = null;
+ if ($mElement === null) {
+ $mElement = $this;
+ } elseif (is_string($mElement)) {
+ $sSearchString = $mElement;
+ $mElement = $this;
+ }
+ /** @var array<int, Value> $aResult */
+ $aResult = [];
+ $this->allValues($mElement, $aResult, $sSearchString, $bSearchInFunctionArguments);
+ return $aResult;
+ }
+
+ /**
+ * Returns all `Selector` objects found recursively in the tree.
+ *
+ * Note that this does not yield the full `DeclarationBlock` that the selector belongs to
+ * (and, currently, there is no way to get to that).
+ *
+ * @param string|null $sSpecificitySearch
+ * An optional filter by specificity.
+ * May contain a comparison operator and a number or just a number (defaults to "==").
+ *
+ * @return array<int, Selector>
+ * @example `getSelectorsBySpecificity('>= 100')`
+ *
+ */
+ public function getSelectorsBySpecificity($sSpecificitySearch = null)
+ {
+ /** @var array<int, Selector> $aResult */
+ $aResult = [];
+ $this->allSelectors($aResult, $sSpecificitySearch);
+ return $aResult;
+ }
+
+ /**
+ * Expands all shorthand properties to their long value.
+ *
+ * @return void
+ */
+ public function expandShorthands()
+ {
+ foreach ($this->getAllDeclarationBlocks() as $oDeclaration) {
+ $oDeclaration->expandShorthands();
+ }
+ }
+
+ /**
+ * Create shorthands properties whenever possible.
+ *
+ * @return void
+ */
+ public function createShorthands()
+ {
+ foreach ($this->getAllDeclarationBlocks() as $oDeclaration) {
+ $oDeclaration->createShorthands();
+ }
+ }
+
+ /**
+ * Overrides `render()` to make format argument optional.
+ *
+ * @param OutputFormat|null $oOutputFormat
+ *
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat = null)
+ {
+ if ($oOutputFormat === null) {
+ $oOutputFormat = new OutputFormat();
+ }
+ return parent::render($oOutputFormat);
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRootList()
+ {
+ return true;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/KeyFrame.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/KeyFrame.php
new file mode 100644
index 0000000..d9420e9
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/CSSList/KeyFrame.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Sabberworm\CSS\CSSList;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Property\AtRule;
+
+class KeyFrame extends CSSList implements AtRule
+{
+ /**
+ * @var string|null
+ */
+ private $vendorKeyFrame;
+
+ /**
+ * @var string|null
+ */
+ private $animationName;
+
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ $this->vendorKeyFrame = null;
+ $this->animationName = null;
+ }
+
+ /**
+ * @param string $vendorKeyFrame
+ */
+ public function setVendorKeyFrame($vendorKeyFrame)
+ {
+ $this->vendorKeyFrame = $vendorKeyFrame;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getVendorKeyFrame()
+ {
+ return $this->vendorKeyFrame;
+ }
+
+ /**
+ * @param string $animationName
+ */
+ public function setAnimationName($animationName)
+ {
+ $this->animationName = $animationName;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getAnimationName()
+ {
+ return $this->animationName;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sResult = "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{";
+ $sResult .= parent::render($oOutputFormat);
+ $sResult .= '}';
+ return $sResult;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRootList()
+ {
+ return false;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function atRuleName()
+ {
+ return $this->vendorKeyFrame;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function atRuleArgs()
+ {
+ return $this->animationName;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Comment.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Comment.php
new file mode 100644
index 0000000..6128d74
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Comment.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Sabberworm\CSS\Comment;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Renderable;
+
+class Comment implements Renderable
+{
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @var string
+ */
+ protected $sComment;
+
+ /**
+ * @param string $sComment
+ * @param int $iLineNo
+ */
+ public function __construct($sComment = '', $iLineNo = 0)
+ {
+ $this->sComment = $sComment;
+ $this->iLineNo = $iLineNo;
+ }
+
+ /**
+ * @return string
+ */
+ public function getComment()
+ {
+ return $this->sComment;
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @param string $sComment
+ *
+ * @return void
+ */
+ public function setComment($sComment)
+ {
+ $this->sComment = $sComment;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return '/*' . $this->sComment . '*/';
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Commentable.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Commentable.php
new file mode 100644
index 0000000..5e450bf
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Comment/Commentable.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Sabberworm\CSS\Comment;
+
+interface Commentable
+{
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments);
+
+ /**
+ * @return array<array-key, Comment>
+ */
+ public function getComments();
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments);
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormat.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormat.php
new file mode 100644
index 0000000..595d306
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormat.php
@@ -0,0 +1,334 @@
+<?php
+
+namespace Sabberworm\CSS;
+
+/**
+ * Class OutputFormat
+ *
+ * @method OutputFormat setSemicolonAfterLastRule(bool $bSemicolonAfterLastRule) Set whether semicolons are added after
+ * last rule.
+ */
+class OutputFormat
+{
+ /**
+ * Value format: `"` means double-quote, `'` means single-quote
+ *
+ * @var string
+ */
+ public $sStringQuotingType = '"';
+
+ /**
+ * Output RGB colors in hash notation if possible
+ *
+ * @var string
+ */
+ public $bRGBHashNotation = true;
+
+ /**
+ * Declaration format
+ *
+ * Semicolon after the last rule of a declaration block can be omitted. To do that, set this false.
+ *
+ * @var bool
+ */
+ public $bSemicolonAfterLastRule = true;
+
+ /**
+ * Spacing
+ * Note that these strings are not sanity-checked: the value should only consist of whitespace
+ * Any newline character will be indented according to the current level.
+ * The triples (After, Before, Between) can be set using a wildcard (e.g. `$oFormat->set('Space*Rules', "\n");`)
+ */
+ public $sSpaceAfterRuleName = ' ';
+
+ /**
+ * @var string
+ */
+ public $sSpaceBeforeRules = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceAfterRules = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceBetweenRules = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceBeforeBlocks = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceAfterBlocks = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceBetweenBlocks = "\n";
+
+ /**
+ * Content injected in and around at-rule blocks.
+ *
+ * @var string
+ */
+ public $sBeforeAtRuleBlock = '';
+
+ /**
+ * @var string
+ */
+ public $sAfterAtRuleBlock = '';
+
+ /**
+ * This is what’s printed before and after the comma if a declaration block contains multiple selectors.
+ *
+ * @var string
+ */
+ public $sSpaceBeforeSelectorSeparator = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceAfterSelectorSeparator = ' ';
+
+ /**
+ * This is what’s printed after the comma of value lists
+ *
+ * @var string
+ */
+ public $sSpaceBeforeListArgumentSeparator = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceAfterListArgumentSeparator = '';
+
+ /**
+ * @var string
+ */
+ public $sSpaceBeforeOpeningBrace = ' ';
+
+ /**
+ * Content injected in and around declaration blocks.
+ *
+ * @var string
+ */
+ public $sBeforeDeclarationBlock = '';
+
+ /**
+ * @var string
+ */
+ public $sAfterDeclarationBlockSelectors = '';
+
+ /**
+ * @var string
+ */
+ public $sAfterDeclarationBlock = '';
+
+ /**
+ * Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings.
+ *
+ * @var string
+ */
+ public $sIndentation = "\t";
+
+ /**
+ * Output exceptions.
+ *
+ * @var bool
+ */
+ public $bIgnoreExceptions = false;
+
+ /**
+ * @var OutputFormatter|null
+ */
+ private $oFormatter = null;
+
+ /**
+ * @var OutputFormat|null
+ */
+ private $oNextLevelFormat = null;
+
+ /**
+ * @var int
+ */
+ private $iIndentationLevel = 0;
+
+ public function __construct()
+ {
+ }
+
+ /**
+ * @param string $sName
+ *
+ * @return string|null
+ */
+ public function get($sName)
+ {
+ $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i'];
+ foreach ($aVarPrefixes as $sPrefix) {
+ $sFieldName = $sPrefix . ucfirst($sName);
+ if (isset($this->$sFieldName)) {
+ return $this->$sFieldName;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param array<array-key, string>|string $aNames
+ * @param mixed $mValue
+ *
+ * @return self|false
+ */
+ public function set($aNames, $mValue)
+ {
+ $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i'];
+ if (is_string($aNames) && strpos($aNames, '*') !== false) {
+ $aNames =
+ [
+ str_replace('*', 'Before', $aNames),
+ str_replace('*', 'Between', $aNames),
+ str_replace('*', 'After', $aNames),
+ ];
+ } elseif (!is_array($aNames)) {
+ $aNames = [$aNames];
+ }
+ foreach ($aVarPrefixes as $sPrefix) {
+ $bDidReplace = false;
+ foreach ($aNames as $sName) {
+ $sFieldName = $sPrefix . ucfirst($sName);
+ if (isset($this->$sFieldName)) {
+ $this->$sFieldName = $mValue;
+ $bDidReplace = true;
+ }
+ }
+ if ($bDidReplace) {
+ return $this;
+ }
+ }
+ // Break the chain so the user knows this option is invalid
+ return false;
+ }
+
+ /**
+ * @param string $sMethodName
+ * @param array<array-key, mixed> $aArguments
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function __call($sMethodName, array $aArguments)
+ {
+ if (strpos($sMethodName, 'set') === 0) {
+ return $this->set(substr($sMethodName, 3), $aArguments[0]);
+ } elseif (strpos($sMethodName, 'get') === 0) {
+ return $this->get(substr($sMethodName, 3));
+ } elseif (method_exists(OutputFormatter::class, $sMethodName)) {
+ return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments);
+ } else {
+ throw new \Exception('Unknown OutputFormat method called: ' . $sMethodName);
+ }
+ }
+
+ /**
+ * @param int $iNumber
+ *
+ * @return self
+ */
+ public function indentWithTabs($iNumber = 1)
+ {
+ return $this->setIndentation(str_repeat("\t", $iNumber));
+ }
+
+ /**
+ * @param int $iNumber
+ *
+ * @return self
+ */
+ public function indentWithSpaces($iNumber = 2)
+ {
+ return $this->setIndentation(str_repeat(" ", $iNumber));
+ }
+
+ /**
+ * @return OutputFormat
+ */
+ public function nextLevel()
+ {
+ if ($this->oNextLevelFormat === null) {
+ $this->oNextLevelFormat = clone $this;
+ $this->oNextLevelFormat->iIndentationLevel++;
+ $this->oNextLevelFormat->oFormatter = null;
+ }
+ return $this->oNextLevelFormat;
+ }
+
+ /**
+ * @return void
+ */
+ public function beLenient()
+ {
+ $this->bIgnoreExceptions = true;
+ }
+
+ /**
+ * @return OutputFormatter
+ */
+ public function getFormatter()
+ {
+ if ($this->oFormatter === null) {
+ $this->oFormatter = new OutputFormatter($this);
+ }
+ return $this->oFormatter;
+ }
+
+ /**
+ * @return int
+ */
+ public function level()
+ {
+ return $this->iIndentationLevel;
+ }
+
+ /**
+ * Creates an instance of this class without any particular formatting settings.
+ *
+ * @return self
+ */
+ public static function create()
+ {
+ return new OutputFormat();
+ }
+
+ /**
+ * Creates an instance of this class with a preset for compact formatting.
+ *
+ * @return self
+ */
+ public static function createCompact()
+ {
+ $format = self::create();
+ $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')
+ ->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
+ return $format;
+ }
+
+ /**
+ * Creates an instance of this class with a preset for pretty formatting.
+ *
+ * @return self
+ */
+ public static function createPretty()
+ {
+ $format = self::create();
+ $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")
+ ->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']);
+ return $format;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormatter.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormatter.php
new file mode 100644
index 0000000..535feca
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/OutputFormatter.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace Sabberworm\CSS;
+
+use Sabberworm\CSS\Parsing\OutputException;
+
+class OutputFormatter
+{
+ /**
+ * @var OutputFormat
+ */
+ private $oFormat;
+
+ public function __construct(OutputFormat $oFormat)
+ {
+ $this->oFormat = $oFormat;
+ }
+
+ /**
+ * @param string $sName
+ * @param string|null $sType
+ *
+ * @return string
+ */
+ public function space($sName, $sType = null)
+ {
+ $sSpaceString = $this->oFormat->get("Space$sName");
+ // If $sSpaceString is an array, we have multiple values configured
+ // depending on the type of object the space applies to
+ if (is_array($sSpaceString)) {
+ if ($sType !== null && isset($sSpaceString[$sType])) {
+ $sSpaceString = $sSpaceString[$sType];
+ } else {
+ $sSpaceString = reset($sSpaceString);
+ }
+ }
+ return $this->prepareSpace($sSpaceString);
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceAfterRuleName()
+ {
+ return $this->space('AfterRuleName');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceBeforeRules()
+ {
+ return $this->space('BeforeRules');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceAfterRules()
+ {
+ return $this->space('AfterRules');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceBetweenRules()
+ {
+ return $this->space('BetweenRules');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceBeforeBlocks()
+ {
+ return $this->space('BeforeBlocks');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceAfterBlocks()
+ {
+ return $this->space('AfterBlocks');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceBetweenBlocks()
+ {
+ return $this->space('BetweenBlocks');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceBeforeSelectorSeparator()
+ {
+ return $this->space('BeforeSelectorSeparator');
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceAfterSelectorSeparator()
+ {
+ return $this->space('AfterSelectorSeparator');
+ }
+
+ /**
+ * @param string $sSeparator
+ *
+ * @return string
+ */
+ public function spaceBeforeListArgumentSeparator($sSeparator)
+ {
+ return $this->space('BeforeListArgumentSeparator', $sSeparator);
+ }
+
+ /**
+ * @param string $sSeparator
+ *
+ * @return string
+ */
+ public function spaceAfterListArgumentSeparator($sSeparator)
+ {
+ return $this->space('AfterListArgumentSeparator', $sSeparator);
+ }
+
+ /**
+ * @return string
+ */
+ public function spaceBeforeOpeningBrace()
+ {
+ return $this->space('BeforeOpeningBrace');
+ }
+
+ /**
+ * Runs the given code, either swallowing or passing exceptions, depending on the `bIgnoreExceptions` setting.
+ *
+ * @param string $cCode the name of the function to call
+ *
+ * @return string|null
+ */
+ public function safely($cCode)
+ {
+ if ($this->oFormat->get('IgnoreExceptions')) {
+ // If output exceptions are ignored, run the code with exception guards
+ try {
+ return $cCode();
+ } catch (OutputException $e) {
+ return null;
+ } // Do nothing
+ } else {
+ // Run the code as-is
+ return $cCode();
+ }
+ }
+
+ /**
+ * Clone of the `implode` function, but calls `render` with the current output format instead of `__toString()`.
+ *
+ * @param string $sSeparator
+ * @param array<array-key, Renderable|string> $aValues
+ * @param bool $bIncreaseLevel
+ *
+ * @return string
+ */
+ public function implode($sSeparator, array $aValues, $bIncreaseLevel = false)
+ {
+ $sResult = '';
+ $oFormat = $this->oFormat;
+ if ($bIncreaseLevel) {
+ $oFormat = $oFormat->nextLevel();
+ }
+ $bIsFirst = true;
+ foreach ($aValues as $mValue) {
+ if ($bIsFirst) {
+ $bIsFirst = false;
+ } else {
+ $sResult .= $sSeparator;
+ }
+ if ($mValue instanceof Renderable) {
+ $sResult .= $mValue->render($oFormat);
+ } else {
+ $sResult .= $mValue;
+ }
+ }
+ return $sResult;
+ }
+
+ /**
+ * @param string $sString
+ *
+ * @return string
+ */
+ public function removeLastSemicolon($sString)
+ {
+ if ($this->oFormat->get('SemicolonAfterLastRule')) {
+ return $sString;
+ }
+ $sString = explode(';', $sString);
+ if (count($sString) < 2) {
+ return $sString[0];
+ }
+ $sLast = array_pop($sString);
+ $sNextToLast = array_pop($sString);
+ array_push($sString, $sNextToLast . $sLast);
+ return implode(';', $sString);
+ }
+
+ /**
+ * @param string $sSpaceString
+ *
+ * @return string
+ */
+ private function prepareSpace($sSpaceString)
+ {
+ return str_replace("\n", "\n" . $this->indent(), $sSpaceString);
+ }
+
+ /**
+ * @return string
+ */
+ private function indent()
+ {
+ return str_repeat($this->oFormat->sIndentation, $this->oFormat->level());
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parser.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parser.php
new file mode 100644
index 0000000..f3b0493
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parser.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Sabberworm\CSS;
+
+use Sabberworm\CSS\CSSList\Document;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\SourceException;
+
+/**
+ * This class parses CSS from text into a data structure.
+ */
+class Parser
+{
+ /**
+ * @var ParserState
+ */
+ private $oParserState;
+
+ /**
+ * @param string $sText
+ * @param Settings|null $oParserSettings
+ * @param int $iLineNo the line number (starting from 1, not from 0)
+ */
+ public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1)
+ {
+ if ($oParserSettings === null) {
+ $oParserSettings = Settings::create();
+ }
+ $this->oParserState = new ParserState($sText, $oParserSettings, $iLineNo);
+ }
+
+ /**
+ * @param string $sCharset
+ *
+ * @return void
+ */
+ public function setCharset($sCharset)
+ {
+ $this->oParserState->setCharset($sCharset);
+ }
+
+ /**
+ * @return void
+ */
+ public function getCharset()
+ {
+ // Note: The `return` statement is missing here. This is a bug that needs to be fixed.
+ $this->oParserState->getCharset();
+ }
+
+ /**
+ * @return Document
+ *
+ * @throws SourceException
+ */
+ public function parse()
+ {
+ return Document::parse($this->oParserState);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/OutputException.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/OutputException.php
new file mode 100644
index 0000000..9bfbc75
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/OutputException.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Sabberworm\CSS\Parsing;
+
+/**
+ * Thrown if the CSS parser attempts to print something invalid.
+ */
+class OutputException extends SourceException
+{
+ /**
+ * @param string $sMessage
+ * @param int $iLineNo
+ */
+ public function __construct($sMessage, $iLineNo = 0)
+ {
+ parent::__construct($sMessage, $iLineNo);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/ParserState.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/ParserState.php
new file mode 100644
index 0000000..e7d85ee
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/ParserState.php
@@ -0,0 +1,516 @@
+<?php
+
+namespace Sabberworm\CSS\Parsing;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\Settings;
+
+class ParserState
+{
+ /**
+ * @var null
+ */
+ const EOF = null;
+
+ /**
+ * @var Settings
+ */
+ private $oParserSettings;
+
+ /**
+ * @var string
+ */
+ private $sText;
+
+ /**
+ * @var array<int, string>
+ */
+ private $aText;
+
+ /**
+ * @var int
+ */
+ private $iCurrentPosition;
+
+ /**
+ * @var string
+ */
+ private $sCharset;
+
+ /**
+ * @var int
+ */
+ private $iLength;
+
+ /**
+ * @var int
+ */
+ private $iLineNo;
+
+ /**
+ * @param string $sText
+ * @param int $iLineNo
+ */
+ public function __construct($sText, Settings $oParserSettings, $iLineNo = 1)
+ {
+ $this->oParserSettings = $oParserSettings;
+ $this->sText = $sText;
+ $this->iCurrentPosition = 0;
+ $this->iLineNo = $iLineNo;
+ $this->setCharset($this->oParserSettings->sDefaultCharset);
+ }
+
+ /**
+ * @param string $sCharset
+ *
+ * @return void
+ */
+ public function setCharset($sCharset)
+ {
+ $this->sCharset = $sCharset;
+ $this->aText = $this->strsplit($this->sText);
+ if (is_array($this->aText)) {
+ $this->iLength = count($this->aText);
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->sCharset;
+ }
+
+ /**
+ * @return int
+ */
+ public function currentLine()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @return int
+ */
+ public function currentColumn()
+ {
+ return $this->iCurrentPosition;
+ }
+
+ /**
+ * @return Settings
+ */
+ public function getSettings()
+ {
+ return $this->oParserSettings;
+ }
+
+ /**
+ * @param bool $bIgnoreCase
+ *
+ * @return string
+ *
+ * @throws UnexpectedTokenException
+ */
+ public function parseIdentifier($bIgnoreCase = true)
+ {
+ $sResult = $this->parseCharacter(true);
+ if ($sResult === null) {
+ throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
+ }
+ $sCharacter = null;
+ while (($sCharacter = $this->parseCharacter(true)) !== null) {
+ if (preg_match('/[a-zA-Z0-9\x{00A0}-\x{FFFF}_-]/Sux', $sCharacter)) {
+ $sResult .= $sCharacter;
+ } else {
+ $sResult .= '\\' . $sCharacter;
+ }
+ }
+ if ($bIgnoreCase) {
+ $sResult = $this->strtolower($sResult);
+ }
+ return $sResult;
+ }
+
+ /**
+ * @param bool $bIsForIdentifier
+ *
+ * @return string|null
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public function parseCharacter($bIsForIdentifier)
+ {
+ if ($this->peek() === '\\') {
+ if (
+ $bIsForIdentifier && $this->oParserSettings->bLenientParsing
+ && ($this->comes('\0') || $this->comes('\9'))
+ ) {
+ // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing.
+ return null;
+ }
+ $this->consume('\\');
+ if ($this->comes('\n') || $this->comes('\r')) {
+ return '';
+ }
+ if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) {
+ return $this->consume(1);
+ }
+ $sUnicode = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u', 6);
+ if ($this->strlen($sUnicode) < 6) {
+ // Consume whitespace after incomplete unicode escape
+ if (preg_match('/\\s/isSu', $this->peek())) {
+ if ($this->comes('\r\n')) {
+ $this->consume(2);
+ } else {
+ $this->consume(1);
+ }
+ }
+ }
+ $iUnicode = intval($sUnicode, 16);
+ $sUtf32 = "";
+ for ($i = 0; $i < 4; ++$i) {
+ $sUtf32 .= chr($iUnicode & 0xff);
+ $iUnicode = $iUnicode >> 8;
+ }
+ return iconv('utf-32le', $this->sCharset, $sUtf32);
+ }
+ if ($bIsForIdentifier) {
+ $peek = ord($this->peek());
+ // Ranges: a-z A-Z 0-9 - _
+ if (
+ ($peek >= 97 && $peek <= 122)
+ || ($peek >= 65 && $peek <= 90)
+ || ($peek >= 48 && $peek <= 57)
+ || ($peek === 45)
+ || ($peek === 95)
+ || ($peek > 0xa1)
+ ) {
+ return $this->consume(1);
+ }
+ } else {
+ return $this->consume(1);
+ }
+ return null;
+ }
+
+ /**
+ * @return array<int, Comment>|void
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public function consumeWhiteSpace()
+ {
+ $comments = [];
+ do {
+ while (preg_match('/\\s/isSu', $this->peek()) === 1) {
+ $this->consume(1);
+ }
+ if ($this->oParserSettings->bLenientParsing) {
+ try {
+ $oComment = $this->consumeComment();
+ } catch (UnexpectedEOFException $e) {
+ $this->iCurrentPosition = $this->iLength;
+ return;
+ }
+ } else {
+ $oComment = $this->consumeComment();
+ }
+ if ($oComment !== false) {
+ $comments[] = $oComment;
+ }
+ } while ($oComment !== false);
+ return $comments;
+ }
+
+ /**
+ * @param string $sString
+ * @param bool $bCaseInsensitive
+ *
+ * @return bool
+ */
+ public function comes($sString, $bCaseInsensitive = false)
+ {
+ $sPeek = $this->peek(strlen($sString));
+ return ($sPeek == '')
+ ? false
+ : $this->streql($sPeek, $sString, $bCaseInsensitive);
+ }
+
+ /**
+ * @param int $iLength
+ * @param int $iOffset
+ *
+ * @return string
+ */
+ public function peek($iLength = 1, $iOffset = 0)
+ {
+ $iOffset += $this->iCurrentPosition;
+ if ($iOffset >= $this->iLength) {
+ return '';
+ }
+ return $this->substr($iOffset, $iLength);
+ }
+
+ /**
+ * @param int $mValue
+ *
+ * @return string
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public function consume($mValue = 1)
+ {
+ if (is_string($mValue)) {
+ $iLineCount = substr_count($mValue, "\n");
+ $iLength = $this->strlen($mValue);
+ if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
+ throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
+ }
+ $this->iLineNo += $iLineCount;
+ $this->iCurrentPosition += $this->strlen($mValue);
+ return $mValue;
+ } else {
+ if ($this->iCurrentPosition + $mValue > $this->iLength) {
+ throw new UnexpectedEOFException($mValue, $this->peek(5), 'count', $this->iLineNo);
+ }
+ $sResult = $this->substr($this->iCurrentPosition, $mValue);
+ $iLineCount = substr_count($sResult, "\n");
+ $this->iLineNo += $iLineCount;
+ $this->iCurrentPosition += $mValue;
+ return $sResult;
+ }
+ }
+
+ /**
+ * @param string $mExpression
+ * @param int|null $iMaxLength
+ *
+ * @return string
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public function consumeExpression($mExpression, $iMaxLength = null)
+ {
+ $aMatches = null;
+ $sInput = $iMaxLength !== null ? $this->peek($iMaxLength) : $this->inputLeft();
+ if (preg_match($mExpression, $sInput, $aMatches, PREG_OFFSET_CAPTURE) === 1) {
+ return $this->consume($aMatches[0][0]);
+ }
+ throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
+ }
+
+ /**
+ * @return Comment|false
+ */
+ public function consumeComment()
+ {
+ $mComment = false;
+ if ($this->comes('/*')) {
+ $iLineNo = $this->iLineNo;
+ $this->consume(1);
+ $mComment = '';
+ while (($char = $this->consume(1)) !== '') {
+ $mComment .= $char;
+ if ($this->comes('*/')) {
+ $this->consume(2);
+ break;
+ }
+ }
+ }
+
+ if ($mComment !== false) {
+ // We skip the * which was included in the comment.
+ return new Comment(substr($mComment, 1), $iLineNo);
+ }
+
+ return $mComment;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEnd()
+ {
+ return $this->iCurrentPosition >= $this->iLength;
+ }
+
+ /**
+ * @param array<array-key, string>|string $aEnd
+ * @param string $bIncludeEnd
+ * @param string $consumeEnd
+ * @param array<int, Comment> $comments
+ *
+ * @return string
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false, array &$comments = [])
+ {
+ $aEnd = is_array($aEnd) ? $aEnd : [$aEnd];
+ $out = '';
+ $start = $this->iCurrentPosition;
+
+ while (!$this->isEnd()) {
+ $char = $this->consume(1);
+ if (in_array($char, $aEnd)) {
+ if ($bIncludeEnd) {
+ $out .= $char;
+ } elseif (!$consumeEnd) {
+ $this->iCurrentPosition -= $this->strlen($char);
+ }
+ return $out;
+ }
+ $out .= $char;
+ if ($comment = $this->consumeComment()) {
+ $comments[] = $comment;
+ }
+ }
+
+ if (in_array(self::EOF, $aEnd)) {
+ return $out;
+ }
+
+ $this->iCurrentPosition = $start;
+ throw new UnexpectedEOFException(
+ 'One of ("' . implode('","', $aEnd) . '")',
+ $this->peek(5),
+ 'search',
+ $this->iLineNo
+ );
+ }
+
+ /**
+ * @return string
+ */
+ private function inputLeft()
+ {
+ return $this->substr($this->iCurrentPosition, -1);
+ }
+
+ /**
+ * @param string $sString1
+ * @param string $sString2
+ * @param bool $bCaseInsensitive
+ *
+ * @return bool
+ */
+ public function streql($sString1, $sString2, $bCaseInsensitive = true)
+ {
+ if ($bCaseInsensitive) {
+ return $this->strtolower($sString1) === $this->strtolower($sString2);
+ } else {
+ return $sString1 === $sString2;
+ }
+ }
+
+ /**
+ * @param int $iAmount
+ *
+ * @return void
+ */
+ public function backtrack($iAmount)
+ {
+ $this->iCurrentPosition -= $iAmount;
+ }
+
+ /**
+ * @param string $sString
+ *
+ * @return int
+ */
+ public function strlen($sString)
+ {
+ if ($this->oParserSettings->bMultibyteSupport) {
+ return mb_strlen($sString, $this->sCharset);
+ } else {
+ return strlen($sString);
+ }
+ }
+
+ /**
+ * @param int $iStart
+ * @param int $iLength
+ *
+ * @return string
+ */
+ private function substr($iStart, $iLength)
+ {
+ if ($iLength < 0) {
+ $iLength = $this->iLength - $iStart + $iLength;
+ }
+ if ($iStart + $iLength > $this->iLength) {
+ $iLength = $this->iLength - $iStart;
+ }
+ $sResult = '';
+ while ($iLength > 0) {
+ $sResult .= $this->aText[$iStart];
+ $iStart++;
+ $iLength--;
+ }
+ return $sResult;
+ }
+
+ /**
+ * @param string $sString
+ *
+ * @return string
+ */
+ private function strtolower($sString)
+ {
+ if ($this->oParserSettings->bMultibyteSupport) {
+ return mb_strtolower($sString, $this->sCharset);
+ } else {
+ return strtolower($sString);
+ }
+ }
+
+ /**
+ * @param string $sString
+ *
+ * @return array<int, string>
+ */
+ private function strsplit($sString)
+ {
+ if ($this->oParserSettings->bMultibyteSupport) {
+ if ($this->streql($this->sCharset, 'utf-8')) {
+ return preg_split('//u', $sString, -1, PREG_SPLIT_NO_EMPTY);
+ } else {
+ $iLength = mb_strlen($sString, $this->sCharset);
+ $aResult = [];
+ for ($i = 0; $i < $iLength; ++$i) {
+ $aResult[] = mb_substr($sString, $i, 1, $this->sCharset);
+ }
+ return $aResult;
+ }
+ } else {
+ if ($sString === '') {
+ return [];
+ } else {
+ return str_split($sString);
+ }
+ }
+ }
+
+ /**
+ * @param string $sString
+ * @param string $sNeedle
+ * @param int $iOffset
+ *
+ * @return int|false
+ */
+ private function strpos($sString, $sNeedle, $iOffset)
+ {
+ if ($this->oParserSettings->bMultibyteSupport) {
+ return mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset);
+ } else {
+ return strpos($sString, $sNeedle, $iOffset);
+ }
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/SourceException.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/SourceException.php
new file mode 100644
index 0000000..1ca668a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/SourceException.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Sabberworm\CSS\Parsing;
+
+class SourceException extends \Exception
+{
+ /**
+ * @var int
+ */
+ private $iLineNo;
+
+ /**
+ * @param string $sMessage
+ * @param int $iLineNo
+ */
+ public function __construct($sMessage, $iLineNo = 0)
+ {
+ $this->iLineNo = $iLineNo;
+ if (!empty($iLineNo)) {
+ $sMessage .= " [line no: $iLineNo]";
+ }
+ parent::__construct($sMessage);
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php
new file mode 100644
index 0000000..368ec70
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedEOFException.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Sabberworm\CSS\Parsing;
+
+/**
+ * Thrown if the CSS parser encounters end of file it did not expect.
+ *
+ * Extends `UnexpectedTokenException` in order to preserve backwards compatibility.
+ */
+class UnexpectedEOFException extends UnexpectedTokenException
+{
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php
new file mode 100644
index 0000000..7d5b1a6
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Parsing/UnexpectedTokenException.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Sabberworm\CSS\Parsing;
+
+/**
+ * Thrown if the CSS parser encounters a token it did not expect.
+ */
+class UnexpectedTokenException extends SourceException
+{
+ /**
+ * @var string
+ */
+ private $sExpected;
+
+ /**
+ * @var string
+ */
+ private $sFound;
+
+ /**
+ * Possible values: literal, identifier, count, expression, search
+ *
+ * @var string
+ */
+ private $sMatchType;
+
+ /**
+ * @param string $sExpected
+ * @param string $sFound
+ * @param string $sMatchType
+ * @param int $iLineNo
+ */
+ public function __construct($sExpected, $sFound, $sMatchType = 'literal', $iLineNo = 0)
+ {
+ $this->sExpected = $sExpected;
+ $this->sFound = $sFound;
+ $this->sMatchType = $sMatchType;
+ $sMessage = "Token “{$sExpected}†({$sMatchType}) not found. Got “{$sFound}â€.";
+ if ($this->sMatchType === 'search') {
+ $sMessage = "Search for “{$sExpected}†returned no results. Context: “{$sFound}â€.";
+ } elseif ($this->sMatchType === 'count') {
+ $sMessage = "Next token was expected to have {$sExpected} chars. Context: “{$sFound}â€.";
+ } elseif ($this->sMatchType === 'identifier') {
+ $sMessage = "Identifier expected. Got “{$sFound}â€";
+ } elseif ($this->sMatchType === 'custom') {
+ $sMessage = trim("$sExpected $sFound");
+ }
+
+ parent::__construct($sMessage, $iLineNo);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/AtRule.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/AtRule.php
new file mode 100644
index 0000000..9536ff5
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/AtRule.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Sabberworm\CSS\Property;
+
+use Sabberworm\CSS\Comment\Commentable;
+use Sabberworm\CSS\Renderable;
+
+interface AtRule extends Renderable, Commentable
+{
+ /**
+ * Since there are more set rules than block rules,
+ * we’re whitelisting the block rules and have anything else be treated as a set rule.
+ *
+ * @var string
+ */
+ const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values';
+
+ /**
+ * … and more font-specific ones (to be used inside font-feature-values)
+ *
+ * @var string
+ */
+ const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation';
+
+ /**
+ * @return string|null
+ */
+ public function atRuleName();
+
+ /**
+ * @return string|null
+ */
+ public function atRuleArgs();
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/CSSNamespace.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/CSSNamespace.php
new file mode 100644
index 0000000..0d7eb49
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/CSSNamespace.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Sabberworm\CSS\Property;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\OutputFormat;
+
+/**
+ * `CSSNamespace` represents an `@namespace` rule.
+ */
+class CSSNamespace implements AtRule
+{
+ /**
+ * @var string
+ */
+ private $mUrl;
+
+ /**
+ * @var string
+ */
+ private $sPrefix;
+
+ /**
+ * @var int
+ */
+ private $iLineNo;
+
+ /**
+ * @var array<array-key, Comment>
+ */
+ protected $aComments;
+
+ /**
+ * @param string $mUrl
+ * @param string|null $sPrefix
+ * @param int $iLineNo
+ */
+ public function __construct($mUrl, $sPrefix = null, $iLineNo = 0)
+ {
+ $this->mUrl = $mUrl;
+ $this->sPrefix = $sPrefix;
+ $this->iLineNo = $iLineNo;
+ $this->aComments = [];
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return '@namespace ' . ($this->sPrefix === null ? '' : $this->sPrefix . ' ')
+ . $this->mUrl->render($oOutputFormat) . ';';
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->mUrl;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPrefix()
+ {
+ return $this->sPrefix;
+ }
+
+ /**
+ * @param string $mUrl
+ *
+ * @return void
+ */
+ public function setUrl($mUrl)
+ {
+ $this->mUrl = $mUrl;
+ }
+
+ /**
+ * @param string $sPrefix
+ *
+ * @return void
+ */
+ public function setPrefix($sPrefix)
+ {
+ $this->sPrefix = $sPrefix;
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleName()
+ {
+ return 'namespace';
+ }
+
+ /**
+ * @return array<int, string>
+ */
+ public function atRuleArgs()
+ {
+ $aResult = [$this->mUrl];
+ if ($this->sPrefix) {
+ array_unshift($aResult, $this->sPrefix);
+ }
+ return $aResult;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments)
+ {
+ $this->aComments = array_merge($this->aComments, $aComments);
+ }
+
+ /**
+ * @return array<array-key, Comment>
+ */
+ public function getComments()
+ {
+ return $this->aComments;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments)
+ {
+ $this->aComments = $aComments;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Charset.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Charset.php
new file mode 100644
index 0000000..3ee0c3d
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Charset.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace Sabberworm\CSS\Property;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\OutputFormat;
+
+/**
+ * Class representing an `@charset` rule.
+ *
+ * The following restrictions apply:
+ * - May not be found in any CSSList other than the Document.
+ * - May only appear at the very top of a Document’s contents.
+ * - Must not appear more than once.
+ */
+class Charset implements AtRule
+{
+ /**
+ * @var string
+ */
+ private $sCharset;
+
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @var array<array-key, Comment>
+ */
+ protected $aComments;
+
+ /**
+ * @param string $sCharset
+ * @param int $iLineNo
+ */
+ public function __construct($sCharset, $iLineNo = 0)
+ {
+ $this->sCharset = $sCharset;
+ $this->iLineNo = $iLineNo;
+ $this->aComments = [];
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @param string $sCharset
+ *
+ * @return void
+ */
+ public function setCharset($sCharset)
+ {
+ $this->sCharset = $sCharset;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->sCharset;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return "@charset {$this->sCharset->render($oOutputFormat)};";
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleName()
+ {
+ return 'charset';
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleArgs()
+ {
+ return $this->sCharset;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments)
+ {
+ $this->aComments = array_merge($this->aComments, $aComments);
+ }
+
+ /**
+ * @return array<array-key, Comment>
+ */
+ public function getComments()
+ {
+ return $this->aComments;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments)
+ {
+ $this->aComments = $aComments;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Import.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Import.php
new file mode 100644
index 0000000..a225301
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Import.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Sabberworm\CSS\Property;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Value\URL;
+
+/**
+ * Class representing an `@import` rule.
+ */
+class Import implements AtRule
+{
+ /**
+ * @var URL
+ */
+ private $oLocation;
+
+ /**
+ * @var string
+ */
+ private $sMediaQuery;
+
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @var array<array-key, Comment>
+ */
+ protected $aComments;
+
+ /**
+ * @param URL $oLocation
+ * @param string $sMediaQuery
+ * @param int $iLineNo
+ */
+ public function __construct(URL $oLocation, $sMediaQuery, $iLineNo = 0)
+ {
+ $this->oLocation = $oLocation;
+ $this->sMediaQuery = $sMediaQuery;
+ $this->iLineNo = $iLineNo;
+ $this->aComments = [];
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @param URL $oLocation
+ *
+ * @return void
+ */
+ public function setLocation($oLocation)
+ {
+ $this->oLocation = $oLocation;
+ }
+
+ /**
+ * @return URL
+ */
+ public function getLocation()
+ {
+ return $this->oLocation;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return "@import " . $this->oLocation->render($oOutputFormat)
+ . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';';
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleName()
+ {
+ return 'import';
+ }
+
+ /**
+ * @return array<int, URL|string>
+ */
+ public function atRuleArgs()
+ {
+ $aResult = [$this->oLocation];
+ if ($this->sMediaQuery) {
+ array_push($aResult, $this->sMediaQuery);
+ }
+ return $aResult;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments)
+ {
+ $this->aComments = array_merge($this->aComments, $aComments);
+ }
+
+ /**
+ * @return array<array-key, Comment>
+ */
+ public function getComments()
+ {
+ return $this->aComments;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments)
+ {
+ $this->aComments = $aComments;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/KeyframeSelector.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/KeyframeSelector.php
new file mode 100644
index 0000000..14ea5eb
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/KeyframeSelector.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Sabberworm\CSS\Property;
+
+class KeyframeSelector extends Selector
+{
+ /**
+ * regexp for specificity calculations
+ *
+ * @var string
+ */
+ const SELECTOR_VALIDATION_RX = '/
+ ^(
+ (?:
+ [a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
+ (?:\\\\.)? # a single escaped character
+ (?:([\'"]).*?(?<!\\\\)\2)? # a quoted text like [id="example"]
+ )*
+ )|
+ (\d+%) # keyframe animation progress percentage (e.g. 50%)
+ $
+ /ux';
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Selector.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Selector.php
new file mode 100644
index 0000000..70c9b2f
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Property/Selector.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Sabberworm\CSS\Property;
+
+/**
+ * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
+ * class.
+ */
+class Selector
+{
+ /**
+ * regexp for specificity calculations
+ *
+ * @var string
+ */
+ const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
+ (\.[\w]+) # classes
+ |
+ \[(\w+) # attributes
+ |
+ (\:( # pseudo classes
+ link|visited|active
+ |hover|focus
+ |lang
+ |target
+ |enabled|disabled|checked|indeterminate
+ |root
+ |nth-child|nth-last-child|nth-of-type|nth-last-of-type
+ |first-child|last-child|first-of-type|last-of-type
+ |only-child|only-of-type
+ |empty|contains
+ ))
+ /ix';
+
+ /**
+ * regexp for specificity calculations
+ *
+ * @var string
+ */
+ const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
+ ((^|[\s\+\>\~]+)[\w]+ # elements
+ |
+ \:{1,2}( # pseudo-elements
+ after|before|first-letter|first-line|selection
+ ))
+ /ix';
+
+ /**
+ * regexp for specificity calculations
+ *
+ * @var string
+ */
+ const SELECTOR_VALIDATION_RX = '/
+ ^(
+ (?:
+ [a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
+ (?:\\\\.)? # a single escaped character
+ (?:([\'"]).*?(?<!\\\\)\2)? # a quoted text like [id="example"]
+ )*
+ )$
+ /ux';
+
+ /**
+ * @var string
+ */
+ private $sSelector;
+
+ /**
+ * @var int|null
+ */
+ private $iSpecificity;
+
+ /**
+ * @param string $sSelector
+ *
+ * @return bool
+ */
+ public static function isValid($sSelector)
+ {
+ return preg_match(static::SELECTOR_VALIDATION_RX, $sSelector);
+ }
+
+ /**
+ * @param string $sSelector
+ * @param bool $bCalculateSpecificity
+ */
+ public function __construct($sSelector, $bCalculateSpecificity = false)
+ {
+ $this->setSelector($sSelector);
+ if ($bCalculateSpecificity) {
+ $this->getSpecificity();
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getSelector()
+ {
+ return $this->sSelector;
+ }
+
+ /**
+ * @param string $sSelector
+ *
+ * @return void
+ */
+ public function setSelector($sSelector)
+ {
+ $this->sSelector = trim($sSelector);
+ $this->iSpecificity = null;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getSelector();
+ }
+
+ /**
+ * @return int
+ */
+ public function getSpecificity()
+ {
+ if ($this->iSpecificity === null) {
+ $a = 0;
+ /// @todo should exclude \# as well as "#"
+ $aMatches = null;
+ $b = substr_count($this->sSelector, '#');
+ $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches);
+ $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches);
+ $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
+ }
+ return $this->iSpecificity;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Renderable.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Renderable.php
new file mode 100644
index 0000000..dc1bff3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Renderable.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Sabberworm\CSS;
+
+interface Renderable
+{
+ /**
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat);
+
+ /**
+ * @return int
+ */
+ public function getLineNo();
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Rule/Rule.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Rule/Rule.php
new file mode 100644
index 0000000..c1ea6df
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Rule/Rule.php
@@ -0,0 +1,392 @@
+<?php
+
+namespace Sabberworm\CSS\Rule;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\Comment\Commentable;
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+use Sabberworm\CSS\Renderable;
+use Sabberworm\CSS\Value\RuleValueList;
+use Sabberworm\CSS\Value\Value;
+
+/**
+ * RuleSets contains Rule objects which always have a key and a value.
+ * In CSS, Rules are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];â€
+ */
+class Rule implements Renderable, Commentable
+{
+ /**
+ * @var string
+ */
+ private $sRule;
+
+ /**
+ * @var RuleValueList|null
+ */
+ private $mValue;
+
+ /**
+ * @var bool
+ */
+ private $bIsImportant;
+
+ /**
+ * @var array<int, int>
+ */
+ private $aIeHack;
+
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @var int
+ */
+ protected $iColNo;
+
+ /**
+ * @var array<array-key, Comment>
+ */
+ protected $aComments;
+
+ /**
+ * @param string $sRule
+ * @param int $iLineNo
+ * @param int $iColNo
+ */
+ public function __construct($sRule, $iLineNo = 0, $iColNo = 0)
+ {
+ $this->sRule = $sRule;
+ $this->mValue = null;
+ $this->bIsImportant = false;
+ $this->aIeHack = [];
+ $this->iLineNo = $iLineNo;
+ $this->iColNo = $iColNo;
+ $this->aComments = [];
+ }
+
+ /**
+ * @return Rule
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $aComments = $oParserState->consumeWhiteSpace();
+ $oRule = new Rule(
+ $oParserState->parseIdentifier(!$oParserState->comes("--")),
+ $oParserState->currentLine(),
+ $oParserState->currentColumn()
+ );
+ $oRule->setComments($aComments);
+ $oRule->addComments($oParserState->consumeWhiteSpace());
+ $oParserState->consume(':');
+ $oValue = Value::parseValue($oParserState, self::listDelimiterForRule($oRule->getRule()));
+ $oRule->setValue($oValue);
+ if ($oParserState->getSettings()->bLenientParsing) {
+ while ($oParserState->comes('\\')) {
+ $oParserState->consume('\\');
+ $oRule->addIeHack($oParserState->consume());
+ $oParserState->consumeWhiteSpace();
+ }
+ }
+ $oParserState->consumeWhiteSpace();
+ if ($oParserState->comes('!')) {
+ $oParserState->consume('!');
+ $oParserState->consumeWhiteSpace();
+ $oParserState->consume('important');
+ $oRule->setIsImportant(true);
+ }
+ $oParserState->consumeWhiteSpace();
+ while ($oParserState->comes(';')) {
+ $oParserState->consume(';');
+ }
+ $oParserState->consumeWhiteSpace();
+
+ return $oRule;
+ }
+
+ /**
+ * @param string $sRule
+ *
+ * @return array<int, string>
+ */
+ private static function listDelimiterForRule($sRule)
+ {
+ if (preg_match('/^font($|-)/', $sRule)) {
+ return [',', '/', ' '];
+ }
+ return [',', ' ', '/'];
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @return int
+ */
+ public function getColNo()
+ {
+ return $this->iColNo;
+ }
+
+ /**
+ * @param int $iLine
+ * @param int $iColumn
+ *
+ * @return void
+ */
+ public function setPosition($iLine, $iColumn)
+ {
+ $this->iColNo = $iColumn;
+ $this->iLineNo = $iLine;
+ }
+
+ /**
+ * @param string $sRule
+ *
+ * @return void
+ */
+ public function setRule($sRule)
+ {
+ $this->sRule = $sRule;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRule()
+ {
+ return $this->sRule;
+ }
+
+ /**
+ * @return RuleValueList|null
+ */
+ public function getValue()
+ {
+ return $this->mValue;
+ }
+
+ /**
+ * @param RuleValueList|null $mValue
+ *
+ * @return void
+ */
+ public function setValue($mValue)
+ {
+ $this->mValue = $mValue;
+ }
+
+ /**
+ * @param array<array-key, array<array-key, RuleValueList>> $aSpaceSeparatedValues
+ *
+ * @return RuleValueList
+ *
+ * @deprecated will be removed in version 9.0
+ * Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility.
+ * Use `setValue()` instead and wrap the value inside a RuleValueList if necessary.
+ */
+ public function setValues(array $aSpaceSeparatedValues)
+ {
+ $oSpaceSeparatedList = null;
+ if (count($aSpaceSeparatedValues) > 1) {
+ $oSpaceSeparatedList = new RuleValueList(' ', $this->iLineNo);
+ }
+ foreach ($aSpaceSeparatedValues as $aCommaSeparatedValues) {
+ $oCommaSeparatedList = null;
+ if (count($aCommaSeparatedValues) > 1) {
+ $oCommaSeparatedList = new RuleValueList(',', $this->iLineNo);
+ }
+ foreach ($aCommaSeparatedValues as $mValue) {
+ if (!$oSpaceSeparatedList && !$oCommaSeparatedList) {
+ $this->mValue = $mValue;
+ return $mValue;
+ }
+ if ($oCommaSeparatedList) {
+ $oCommaSeparatedList->addListComponent($mValue);
+ } else {
+ $oSpaceSeparatedList->addListComponent($mValue);
+ }
+ }
+ if (!$oSpaceSeparatedList) {
+ $this->mValue = $oCommaSeparatedList;
+ return $oCommaSeparatedList;
+ } else {
+ $oSpaceSeparatedList->addListComponent($oCommaSeparatedList);
+ }
+ }
+ $this->mValue = $oSpaceSeparatedList;
+ return $oSpaceSeparatedList;
+ }
+
+ /**
+ * @return array<int, array<int, RuleValueList>>
+ *
+ * @deprecated will be removed in version 9.0
+ * Old-Style 2-dimensional array returned. Retained for (some) backwards-compatibility.
+ * Use `getValue()` instead and check for the existence of a (nested set of) ValueList object(s).
+ */
+ public function getValues()
+ {
+ if (!$this->mValue instanceof RuleValueList) {
+ return [[$this->mValue]];
+ }
+ if ($this->mValue->getListSeparator() === ',') {
+ return [$this->mValue->getListComponents()];
+ }
+ $aResult = [];
+ foreach ($this->mValue->getListComponents() as $mValue) {
+ if (!$mValue instanceof RuleValueList || $mValue->getListSeparator() !== ',') {
+ $aResult[] = [$mValue];
+ continue;
+ }
+ if ($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) {
+ $aResult[] = [];
+ }
+ foreach ($mValue->getListComponents() as $mValue) {
+ $aResult[count($aResult) - 1][] = $mValue;
+ }
+ }
+ return $aResult;
+ }
+
+ /**
+ * Adds a value to the existing value. Value will be appended if a `RuleValueList` exists of the given type.
+ * Otherwise, the existing value will be wrapped by one.
+ *
+ * @param RuleValueList|array<int, RuleValueList> $mValue
+ * @param string $sType
+ *
+ * @return void
+ */
+ public function addValue($mValue, $sType = ' ')
+ {
+ if (!is_array($mValue)) {
+ $mValue = [$mValue];
+ }
+ if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) {
+ $mCurrentValue = $this->mValue;
+ $this->mValue = new RuleValueList($sType, $this->iLineNo);
+ if ($mCurrentValue) {
+ $this->mValue->addListComponent($mCurrentValue);
+ }
+ }
+ foreach ($mValue as $mValueItem) {
+ $this->mValue->addListComponent($mValueItem);
+ }
+ }
+
+ /**
+ * @param int $iModifier
+ *
+ * @return void
+ */
+ public function addIeHack($iModifier)
+ {
+ $this->aIeHack[] = $iModifier;
+ }
+
+ /**
+ * @param array<int, int> $aModifiers
+ *
+ * @return void
+ */
+ public function setIeHack(array $aModifiers)
+ {
+ $this->aIeHack = $aModifiers;
+ }
+
+ /**
+ * @return array<int, int>
+ */
+ public function getIeHack()
+ {
+ return $this->aIeHack;
+ }
+
+ /**
+ * @param bool $bIsImportant
+ *
+ * @return void
+ */
+ public function setIsImportant($bIsImportant)
+ {
+ $this->bIsImportant = $bIsImportant;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getIsImportant()
+ {
+ return $this->bIsImportant;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}";
+ if ($this->mValue instanceof Value) { //Can also be a ValueList
+ $sResult .= $this->mValue->render($oOutputFormat);
+ } else {
+ $sResult .= $this->mValue;
+ }
+ if (!empty($this->aIeHack)) {
+ $sResult .= ' \\' . implode('\\', $this->aIeHack);
+ }
+ if ($this->bIsImportant) {
+ $sResult .= ' !important';
+ }
+ $sResult .= ';';
+ return $sResult;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments)
+ {
+ $this->aComments = array_merge($this->aComments, $aComments);
+ }
+
+ /**
+ * @return array<array-key, Comment>
+ */
+ public function getComments()
+ {
+ return $this->aComments;
+ }
+
+ /**
+ * @param array<array-key, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments)
+ {
+ $this->aComments = $aComments;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php
new file mode 100644
index 0000000..88bc5bd
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/AtRuleSet.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Sabberworm\CSS\RuleSet;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Property\AtRule;
+
+/**
+ * A RuleSet constructed by an unknown at-rule. `@font-face` rules are rendered into AtRuleSet objects.
+ */
+class AtRuleSet extends RuleSet implements AtRule
+{
+ /**
+ * @var string
+ */
+ private $sType;
+
+ /**
+ * @var string
+ */
+ private $sArgs;
+
+ /**
+ * @param string $sType
+ * @param string $sArgs
+ * @param int $iLineNo
+ */
+ public function __construct($sType, $sArgs = '', $iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ $this->sType = $sType;
+ $this->sArgs = $sArgs;
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleName()
+ {
+ return $this->sType;
+ }
+
+ /**
+ * @return string
+ */
+ public function atRuleArgs()
+ {
+ return $this->sArgs;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sArgs = $this->sArgs;
+ if ($sArgs) {
+ $sArgs = ' ' . $sArgs;
+ }
+ $sResult = "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{";
+ $sResult .= parent::render($oOutputFormat);
+ $sResult .= '}';
+ return $sResult;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php
new file mode 100644
index 0000000..c27cdd4
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/DeclarationBlock.php
@@ -0,0 +1,831 @@
+<?php
+
+namespace Sabberworm\CSS\RuleSet;
+
+use Sabberworm\CSS\CSSList\CSSList;
+use Sabberworm\CSS\CSSList\KeyFrame;
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\OutputException;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+use Sabberworm\CSS\Property\KeyframeSelector;
+use Sabberworm\CSS\Property\Selector;
+use Sabberworm\CSS\Rule\Rule;
+use Sabberworm\CSS\Value\Color;
+use Sabberworm\CSS\Value\RuleValueList;
+use Sabberworm\CSS\Value\Size;
+use Sabberworm\CSS\Value\URL;
+use Sabberworm\CSS\Value\Value;
+
+/**
+ * Declaration blocks are the parts of a CSS file which denote the rules belonging to a selector.
+ *
+ * Declaration blocks usually appear directly inside a `Document` or another `CSSList` (mostly a `MediaQuery`).
+ */
+class DeclarationBlock extends RuleSet
+{
+ /**
+ * @var array<int, Selector|string>
+ */
+ private $aSelectors;
+
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ $this->aSelectors = [];
+ }
+
+ /**
+ * @param CSSList|null $oList
+ *
+ * @return DeclarationBlock|false
+ *
+ * @throws UnexpectedTokenException
+ * @throws UnexpectedEOFException
+ */
+ public static function parse(ParserState $oParserState, $oList = null)
+ {
+ $aComments = [];
+ $oResult = new DeclarationBlock($oParserState->currentLine());
+ try {
+ $aSelectorParts = [];
+ $sStringWrapperChar = false;
+ do {
+ $aSelectorParts[] = $oParserState->consume(1)
+ . $oParserState->consumeUntil(['{', '}', '\'', '"'], false, false, $aComments);
+ if (in_array($oParserState->peek(), ['\'', '"']) && substr(end($aSelectorParts), -1) != "\\") {
+ if ($sStringWrapperChar === false) {
+ $sStringWrapperChar = $oParserState->peek();
+ } elseif ($sStringWrapperChar == $oParserState->peek()) {
+ $sStringWrapperChar = false;
+ }
+ }
+ } while (!in_array($oParserState->peek(), ['{', '}']) || $sStringWrapperChar !== false);
+ $oResult->setSelectors(implode('', $aSelectorParts), $oList);
+ if ($oParserState->comes('{')) {
+ $oParserState->consume(1);
+ }
+ } catch (UnexpectedTokenException $e) {
+ if ($oParserState->getSettings()->bLenientParsing) {
+ if (!$oParserState->comes('}')) {
+ $oParserState->consumeUntil('}', false, true);
+ }
+ return false;
+ } else {
+ throw $e;
+ }
+ }
+ $oResult->setComments($aComments);
+ RuleSet::parseRuleSet($oParserState, $oResult);
+ return $oResult;
+ }
+
+ /**
+ * @param array<int, Selector|string>|string $mSelector
+ * @param CSSList|null $oList
+ *
+ * @throws UnexpectedTokenException
+ */
+ public function setSelectors($mSelector, $oList = null)
+ {
+ if (is_array($mSelector)) {
+ $this->aSelectors = $mSelector;
+ } else {
+ $this->aSelectors = explode(',', $mSelector);
+ }
+ foreach ($this->aSelectors as $iKey => $mSelector) {
+ if (!($mSelector instanceof Selector)) {
+ if ($oList === null || !($oList instanceof KeyFrame)) {
+ if (!Selector::isValid($mSelector)) {
+ throw new UnexpectedTokenException(
+ "Selector did not match '" . Selector::SELECTOR_VALIDATION_RX . "'.",
+ $mSelector,
+ "custom"
+ );
+ }
+ $this->aSelectors[$iKey] = new Selector($mSelector);
+ } else {
+ if (!KeyframeSelector::isValid($mSelector)) {
+ throw new UnexpectedTokenException(
+ "Selector did not match '" . KeyframeSelector::SELECTOR_VALIDATION_RX . "'.",
+ $mSelector,
+ "custom"
+ );
+ }
+ $this->aSelectors[$iKey] = new KeyframeSelector($mSelector);
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove one of the selectors of the block.
+ *
+ * @param Selector|string $mSelector
+ *
+ * @return bool
+ */
+ public function removeSelector($mSelector)
+ {
+ if ($mSelector instanceof Selector) {
+ $mSelector = $mSelector->getSelector();
+ }
+ foreach ($this->aSelectors as $iKey => $oSelector) {
+ if ($oSelector->getSelector() === $mSelector) {
+ unset($this->aSelectors[$iKey]);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return array<int, Selector|string>
+ *
+ * @deprecated will be removed in version 9.0; use `getSelectors()` instead
+ */
+ public function getSelector()
+ {
+ return $this->getSelectors();
+ }
+
+ /**
+ * @param Selector|string $mSelector
+ * @param CSSList|null $oList
+ *
+ * @return void
+ *
+ * @deprecated will be removed in version 9.0; use `setSelectors()` instead
+ */
+ public function setSelector($mSelector, $oList = null)
+ {
+ $this->setSelectors($mSelector, $oList);
+ }
+
+ /**
+ * @return array<int, Selector|string>
+ */
+ public function getSelectors()
+ {
+ return $this->aSelectors;
+ }
+
+ /**
+ * Splits shorthand declarations (e.g. `margin` or `font`) into their constituent parts.
+ *
+ * @return void
+ */
+ public function expandShorthands()
+ {
+ // border must be expanded before dimensions
+ $this->expandBorderShorthand();
+ $this->expandDimensionsShorthand();
+ $this->expandFontShorthand();
+ $this->expandBackgroundShorthand();
+ $this->expandListStyleShorthand();
+ }
+
+ /**
+ * Creates shorthand declarations (e.g. `margin` or `font`) whenever possible.
+ *
+ * @return void
+ */
+ public function createShorthands()
+ {
+ $this->createBackgroundShorthand();
+ $this->createDimensionsShorthand();
+ // border must be shortened after dimensions
+ $this->createBorderShorthand();
+ $this->createFontShorthand();
+ $this->createListStyleShorthand();
+ }
+
+ /**
+ * Splits shorthand border declarations (e.g. `border: 1px red;`).
+ *
+ * Additional splitting happens in expandDimensionsShorthand.
+ *
+ * Multiple borders are not yet supported as of 3.
+ *
+ * @return void
+ */
+ public function expandBorderShorthand()
+ {
+ $aBorderRules = [
+ 'border',
+ 'border-left',
+ 'border-right',
+ 'border-top',
+ 'border-bottom',
+ ];
+ $aBorderSizes = [
+ 'thin',
+ 'medium',
+ 'thick',
+ ];
+ $aRules = $this->getRulesAssoc();
+ foreach ($aBorderRules as $sBorderRule) {
+ if (!isset($aRules[$sBorderRule])) {
+ continue;
+ }
+ $oRule = $aRules[$sBorderRule];
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ foreach ($aValues as $mValue) {
+ if ($mValue instanceof Value) {
+ $mNewValue = clone $mValue;
+ } else {
+ $mNewValue = $mValue;
+ }
+ if ($mValue instanceof Size) {
+ $sNewRuleName = $sBorderRule . "-width";
+ } elseif ($mValue instanceof Color) {
+ $sNewRuleName = $sBorderRule . "-color";
+ } else {
+ if (in_array($mValue, $aBorderSizes)) {
+ $sNewRuleName = $sBorderRule . "-width";
+ } else {
+ $sNewRuleName = $sBorderRule . "-style";
+ }
+ }
+ $oNewRule = new Rule($sNewRuleName, $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $oNewRule->addValue([$mNewValue]);
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule($sBorderRule);
+ }
+ }
+
+ /**
+ * Splits shorthand dimensional declarations (e.g. `margin: 0px auto;`)
+ * into their constituent parts.
+ *
+ * Handles `margin`, `padding`, `border-color`, `border-style` and `border-width`.
+ *
+ * @return void
+ */
+ public function expandDimensionsShorthand()
+ {
+ $aExpansions = [
+ 'margin' => 'margin-%s',
+ 'padding' => 'padding-%s',
+ 'border-color' => 'border-%s-color',
+ 'border-style' => 'border-%s-style',
+ 'border-width' => 'border-%s-width',
+ ];
+ $aRules = $this->getRulesAssoc();
+ foreach ($aExpansions as $sProperty => $sExpanded) {
+ if (!isset($aRules[$sProperty])) {
+ continue;
+ }
+ $oRule = $aRules[$sProperty];
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ $top = $right = $bottom = $left = null;
+ switch (count($aValues)) {
+ case 1:
+ $top = $right = $bottom = $left = $aValues[0];
+ break;
+ case 2:
+ $top = $bottom = $aValues[0];
+ $left = $right = $aValues[1];
+ break;
+ case 3:
+ $top = $aValues[0];
+ $left = $right = $aValues[1];
+ $bottom = $aValues[2];
+ break;
+ case 4:
+ $top = $aValues[0];
+ $right = $aValues[1];
+ $bottom = $aValues[2];
+ $left = $aValues[3];
+ break;
+ }
+ foreach (['top', 'right', 'bottom', 'left'] as $sPosition) {
+ $oNewRule = new Rule(sprintf($sExpanded, $sPosition), $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $oNewRule->addValue(${$sPosition});
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule($sProperty);
+ }
+ }
+
+ /**
+ * Converts shorthand font declarations
+ * (e.g. `font: 300 italic 11px/14px verdana, helvetica, sans-serif;`)
+ * into their constituent parts.
+ *
+ * @return void
+ */
+ public function expandFontShorthand()
+ {
+ $aRules = $this->getRulesAssoc();
+ if (!isset($aRules['font'])) {
+ return;
+ }
+ $oRule = $aRules['font'];
+ // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand
+ $aFontProperties = [
+ 'font-style' => 'normal',
+ 'font-variant' => 'normal',
+ 'font-weight' => 'normal',
+ 'font-size' => 'normal',
+ 'line-height' => 'normal',
+ ];
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ foreach ($aValues as $mValue) {
+ if (!$mValue instanceof Value) {
+ $mValue = mb_strtolower($mValue);
+ }
+ if (in_array($mValue, ['normal', 'inherit'])) {
+ foreach (['font-style', 'font-weight', 'font-variant'] as $sProperty) {
+ if (!isset($aFontProperties[$sProperty])) {
+ $aFontProperties[$sProperty] = $mValue;
+ }
+ }
+ } elseif (in_array($mValue, ['italic', 'oblique'])) {
+ $aFontProperties['font-style'] = $mValue;
+ } elseif ($mValue == 'small-caps') {
+ $aFontProperties['font-variant'] = $mValue;
+ } elseif (
+ in_array($mValue, ['bold', 'bolder', 'lighter'])
+ || ($mValue instanceof Size
+ && in_array($mValue->getSize(), range(100, 900, 100)))
+ ) {
+ $aFontProperties['font-weight'] = $mValue;
+ } elseif ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') {
+ list($oSize, $oHeight) = $mValue->getListComponents();
+ $aFontProperties['font-size'] = $oSize;
+ $aFontProperties['line-height'] = $oHeight;
+ } elseif ($mValue instanceof Size && $mValue->getUnit() !== null) {
+ $aFontProperties['font-size'] = $mValue;
+ } else {
+ $aFontProperties['font-family'] = $mValue;
+ }
+ }
+ foreach ($aFontProperties as $sProperty => $mValue) {
+ $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->addValue($mValue);
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule('font');
+ }
+
+ /**
+ * Converts shorthand background declarations
+ * (e.g. `background: url("chess.png") gray 50% repeat fixed;`)
+ * into their constituent parts.
+ *
+ * @see http://www.w3.org/TR/21/colors.html#propdef-background
+ *
+ * @return void
+ */
+ public function expandBackgroundShorthand()
+ {
+ $aRules = $this->getRulesAssoc();
+ if (!isset($aRules['background'])) {
+ return;
+ }
+ $oRule = $aRules['background'];
+ $aBgProperties = [
+ 'background-color' => ['transparent'],
+ 'background-image' => ['none'],
+ 'background-repeat' => ['repeat'],
+ 'background-attachment' => ['scroll'],
+ 'background-position' => [
+ new Size(0, '%', null, false, $this->iLineNo),
+ new Size(0, '%', null, false, $this->iLineNo),
+ ],
+ ];
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ if (count($aValues) == 1 && $aValues[0] == 'inherit') {
+ foreach ($aBgProperties as $sProperty => $mValue) {
+ $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->addValue('inherit');
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule('background');
+ return;
+ }
+ $iNumBgPos = 0;
+ foreach ($aValues as $mValue) {
+ if (!$mValue instanceof Value) {
+ $mValue = mb_strtolower($mValue);
+ }
+ if ($mValue instanceof URL) {
+ $aBgProperties['background-image'] = $mValue;
+ } elseif ($mValue instanceof Color) {
+ $aBgProperties['background-color'] = $mValue;
+ } elseif (in_array($mValue, ['scroll', 'fixed'])) {
+ $aBgProperties['background-attachment'] = $mValue;
+ } elseif (in_array($mValue, ['repeat', 'no-repeat', 'repeat-x', 'repeat-y'])) {
+ $aBgProperties['background-repeat'] = $mValue;
+ } elseif (
+ in_array($mValue, ['left', 'center', 'right', 'top', 'bottom'])
+ || $mValue instanceof Size
+ ) {
+ if ($iNumBgPos == 0) {
+ $aBgProperties['background-position'][0] = $mValue;
+ $aBgProperties['background-position'][1] = 'center';
+ } else {
+ $aBgProperties['background-position'][$iNumBgPos] = $mValue;
+ }
+ $iNumBgPos++;
+ }
+ }
+ foreach ($aBgProperties as $sProperty => $mValue) {
+ $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $oNewRule->addValue($mValue);
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule('background');
+ }
+
+ /**
+ * @return void
+ */
+ public function expandListStyleShorthand()
+ {
+ $aListProperties = [
+ 'list-style-type' => 'disc',
+ 'list-style-position' => 'outside',
+ 'list-style-image' => 'none',
+ ];
+ $aListStyleTypes = [
+ 'none',
+ 'disc',
+ 'circle',
+ 'square',
+ 'decimal-leading-zero',
+ 'decimal',
+ 'lower-roman',
+ 'upper-roman',
+ 'lower-greek',
+ 'lower-alpha',
+ 'lower-latin',
+ 'upper-alpha',
+ 'upper-latin',
+ 'hebrew',
+ 'armenian',
+ 'georgian',
+ 'cjk-ideographic',
+ 'hiragana',
+ 'hira-gana-iroha',
+ 'katakana-iroha',
+ 'katakana',
+ ];
+ $aListStylePositions = [
+ 'inside',
+ 'outside',
+ ];
+ $aRules = $this->getRulesAssoc();
+ if (!isset($aRules['list-style'])) {
+ return;
+ }
+ $oRule = $aRules['list-style'];
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ if (count($aValues) == 1 && $aValues[0] == 'inherit') {
+ foreach ($aListProperties as $sProperty => $mValue) {
+ $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->addValue('inherit');
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule('list-style');
+ return;
+ }
+ foreach ($aValues as $mValue) {
+ if (!$mValue instanceof Value) {
+ $mValue = mb_strtolower($mValue);
+ }
+ if ($mValue instanceof Url) {
+ $aListProperties['list-style-image'] = $mValue;
+ } elseif (in_array($mValue, $aListStyleTypes)) {
+ $aListProperties['list-style-types'] = $mValue;
+ } elseif (in_array($mValue, $aListStylePositions)) {
+ $aListProperties['list-style-position'] = $mValue;
+ }
+ }
+ foreach ($aListProperties as $sProperty => $mValue) {
+ $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
+ $oNewRule->setIsImportant($oRule->getIsImportant());
+ $oNewRule->addValue($mValue);
+ $this->addRule($oNewRule);
+ }
+ $this->removeRule('list-style');
+ }
+
+ /**
+ * @param array<array-key, string> $aProperties
+ * @param string $sShorthand
+ *
+ * @return void
+ */
+ public function createShorthandProperties(array $aProperties, $sShorthand)
+ {
+ $aRules = $this->getRulesAssoc();
+ $aNewValues = [];
+ foreach ($aProperties as $sProperty) {
+ if (!isset($aRules[$sProperty])) {
+ continue;
+ }
+ $oRule = $aRules[$sProperty];
+ if (!$oRule->getIsImportant()) {
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ foreach ($aValues as $mValue) {
+ $aNewValues[] = $mValue;
+ }
+ $this->removeRule($sProperty);
+ }
+ }
+ if (count($aNewValues)) {
+ $oNewRule = new Rule($sShorthand, $oRule->getLineNo(), $oRule->getColNo());
+ foreach ($aNewValues as $mValue) {
+ $oNewRule->addValue($mValue);
+ }
+ $this->addRule($oNewRule);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function createBackgroundShorthand()
+ {
+ $aProperties = [
+ 'background-color',
+ 'background-image',
+ 'background-repeat',
+ 'background-position',
+ 'background-attachment',
+ ];
+ $this->createShorthandProperties($aProperties, 'background');
+ }
+
+ /**
+ * @return void
+ */
+ public function createListStyleShorthand()
+ {
+ $aProperties = [
+ 'list-style-type',
+ 'list-style-position',
+ 'list-style-image',
+ ];
+ $this->createShorthandProperties($aProperties, 'list-style');
+ }
+
+ /**
+ * Combines `border-color`, `border-style` and `border-width` into `border`.
+ *
+ * Should be run after `create_dimensions_shorthand`!
+ *
+ * @return void
+ */
+ public function createBorderShorthand()
+ {
+ $aProperties = [
+ 'border-width',
+ 'border-style',
+ 'border-color',
+ ];
+ $this->createShorthandProperties($aProperties, 'border');
+ }
+
+ /**
+ * Looks for long format CSS dimensional properties
+ * (margin, padding, border-color, border-style and border-width)
+ * and converts them into shorthand CSS properties.
+ *
+ * @return void
+ */
+ public function createDimensionsShorthand()
+ {
+ $aPositions = ['top', 'right', 'bottom', 'left'];
+ $aExpansions = [
+ 'margin' => 'margin-%s',
+ 'padding' => 'padding-%s',
+ 'border-color' => 'border-%s-color',
+ 'border-style' => 'border-%s-style',
+ 'border-width' => 'border-%s-width',
+ ];
+ $aRules = $this->getRulesAssoc();
+ foreach ($aExpansions as $sProperty => $sExpanded) {
+ $aFoldable = [];
+ foreach ($aRules as $sRuleName => $oRule) {
+ foreach ($aPositions as $sPosition) {
+ if ($sRuleName == sprintf($sExpanded, $sPosition)) {
+ $aFoldable[$sRuleName] = $oRule;
+ }
+ }
+ }
+ // All four dimensions must be present
+ if (count($aFoldable) == 4) {
+ $aValues = [];
+ foreach ($aPositions as $sPosition) {
+ $oRule = $aRules[sprintf($sExpanded, $sPosition)];
+ $mRuleValue = $oRule->getValue();
+ $aRuleValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aRuleValues[] = $mRuleValue;
+ } else {
+ $aRuleValues = $mRuleValue->getListComponents();
+ }
+ $aValues[$sPosition] = $aRuleValues;
+ }
+ $oNewRule = new Rule($sProperty, $oRule->getLineNo(), $oRule->getColNo());
+ if ((string)$aValues['left'][0] == (string)$aValues['right'][0]) {
+ if ((string)$aValues['top'][0] == (string)$aValues['bottom'][0]) {
+ if ((string)$aValues['top'][0] == (string)$aValues['left'][0]) {
+ // All 4 sides are equal
+ $oNewRule->addValue($aValues['top']);
+ } else {
+ // Top and bottom are equal, left and right are equal
+ $oNewRule->addValue($aValues['top']);
+ $oNewRule->addValue($aValues['left']);
+ }
+ } else {
+ // Only left and right are equal
+ $oNewRule->addValue($aValues['top']);
+ $oNewRule->addValue($aValues['left']);
+ $oNewRule->addValue($aValues['bottom']);
+ }
+ } else {
+ // No sides are equal
+ $oNewRule->addValue($aValues['top']);
+ $oNewRule->addValue($aValues['left']);
+ $oNewRule->addValue($aValues['bottom']);
+ $oNewRule->addValue($aValues['right']);
+ }
+ $this->addRule($oNewRule);
+ foreach ($aPositions as $sPosition) {
+ $this->removeRule(sprintf($sExpanded, $sPosition));
+ }
+ }
+ }
+ }
+
+ /**
+ * Looks for long format CSS font properties (e.g. `font-weight`) and
+ * tries to convert them into a shorthand CSS `font` property.
+ *
+ * At least `font-size` AND `font-family` must be present in order to create a shorthand declaration.
+ *
+ * @return void
+ */
+ public function createFontShorthand()
+ {
+ $aFontProperties = [
+ 'font-style',
+ 'font-variant',
+ 'font-weight',
+ 'font-size',
+ 'line-height',
+ 'font-family',
+ ];
+ $aRules = $this->getRulesAssoc();
+ if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) {
+ return;
+ }
+ $oOldRule = isset($aRules['font-size']) ? $aRules['font-size'] : $aRules['font-family'];
+ $oNewRule = new Rule('font', $oOldRule->getLineNo(), $oOldRule->getColNo());
+ unset($oOldRule);
+ foreach (['font-style', 'font-variant', 'font-weight'] as $sProperty) {
+ if (isset($aRules[$sProperty])) {
+ $oRule = $aRules[$sProperty];
+ $mRuleValue = $oRule->getValue();
+ $aValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aValues[] = $mRuleValue;
+ } else {
+ $aValues = $mRuleValue->getListComponents();
+ }
+ if ($aValues[0] !== 'normal') {
+ $oNewRule->addValue($aValues[0]);
+ }
+ }
+ }
+ // Get the font-size value
+ $oRule = $aRules['font-size'];
+ $mRuleValue = $oRule->getValue();
+ $aFSValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aFSValues[] = $mRuleValue;
+ } else {
+ $aFSValues = $mRuleValue->getListComponents();
+ }
+ // But wait to know if we have line-height to add it
+ if (isset($aRules['line-height'])) {
+ $oRule = $aRules['line-height'];
+ $mRuleValue = $oRule->getValue();
+ $aLHValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aLHValues[] = $mRuleValue;
+ } else {
+ $aLHValues = $mRuleValue->getListComponents();
+ }
+ if ($aLHValues[0] !== 'normal') {
+ $val = new RuleValueList('/', $this->iLineNo);
+ $val->addListComponent($aFSValues[0]);
+ $val->addListComponent($aLHValues[0]);
+ $oNewRule->addValue($val);
+ }
+ } else {
+ $oNewRule->addValue($aFSValues[0]);
+ }
+ $oRule = $aRules['font-family'];
+ $mRuleValue = $oRule->getValue();
+ $aFFValues = [];
+ if (!$mRuleValue instanceof RuleValueList) {
+ $aFFValues[] = $mRuleValue;
+ } else {
+ $aFFValues = $mRuleValue->getListComponents();
+ }
+ $oFFValue = new RuleValueList(',', $this->iLineNo);
+ $oFFValue->setListComponents($aFFValues);
+ $oNewRule->addValue($oFFValue);
+
+ $this->addRule($oNewRule);
+ foreach ($aFontProperties as $sProperty) {
+ $this->removeRule($sProperty);
+ }
+ }
+
+ /**
+ * @return string
+ *
+ * @throws OutputException
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ *
+ * @throws OutputException
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ if (count($this->aSelectors) === 0) {
+ // If all the selectors have been removed, this declaration block becomes invalid
+ throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo);
+ }
+ $sResult = $oOutputFormat->sBeforeDeclarationBlock;
+ $sResult .= $oOutputFormat->implode(
+ $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(),
+ $this->aSelectors
+ );
+ $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors;
+ $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{';
+ $sResult .= parent::render($oOutputFormat);
+ $sResult .= '}';
+ $sResult .= $oOutputFormat->sAfterDeclarationBlock;
+ return $sResult;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/RuleSet.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/RuleSet.php
new file mode 100644
index 0000000..9404bb0
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/RuleSet/RuleSet.php
@@ -0,0 +1,326 @@
+<?php
+
+namespace Sabberworm\CSS\RuleSet;
+
+use Sabberworm\CSS\Comment\Comment;
+use Sabberworm\CSS\Comment\Commentable;
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+use Sabberworm\CSS\Renderable;
+use Sabberworm\CSS\Rule\Rule;
+
+/**
+ * RuleSet is a generic superclass denoting rules. The typical example for rule sets are declaration block.
+ * However, unknown At-Rules (like `@font-face`) are also rule sets.
+ */
+abstract class RuleSet implements Renderable, Commentable
+{
+ /**
+ * @var array<string, Rule>
+ */
+ private $aRules;
+
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @var array<array-key, Comment>
+ */
+ protected $aComments;
+
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ $this->aRules = [];
+ $this->iLineNo = $iLineNo;
+ $this->aComments = [];
+ }
+
+ /**
+ * @return void
+ *
+ * @throws UnexpectedTokenException
+ * @throws UnexpectedEOFException
+ */
+ public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet)
+ {
+ while ($oParserState->comes(';')) {
+ $oParserState->consume(';');
+ }
+ while (!$oParserState->comes('}')) {
+ $oRule = null;
+ if ($oParserState->getSettings()->bLenientParsing) {
+ try {
+ $oRule = Rule::parse($oParserState);
+ } catch (UnexpectedTokenException $e) {
+ try {
+ $sConsume = $oParserState->consumeUntil(["\n", ";", '}'], true);
+ // We need to “unfind†the matches to the end of the ruleSet as this will be matched later
+ if ($oParserState->streql(substr($sConsume, -1), '}')) {
+ $oParserState->backtrack(1);
+ } else {
+ while ($oParserState->comes(';')) {
+ $oParserState->consume(';');
+ }
+ }
+ } catch (UnexpectedTokenException $e) {
+ // We’ve reached the end of the document. Just close the RuleSet.
+ return;
+ }
+ }
+ } else {
+ $oRule = Rule::parse($oParserState);
+ }
+ if ($oRule) {
+ $oRuleSet->addRule($oRule);
+ }
+ }
+ $oParserState->consume('}');
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+
+ /**
+ * @param Rule|null $oSibling
+ *
+ * @return void
+ */
+ public function addRule(Rule $oRule, Rule $oSibling = null)
+ {
+ $sRule = $oRule->getRule();
+ if (!isset($this->aRules[$sRule])) {
+ $this->aRules[$sRule] = [];
+ }
+
+ $iPosition = count($this->aRules[$sRule]);
+
+ if ($oSibling !== null) {
+ $iSiblingPos = array_search($oSibling, $this->aRules[$sRule], true);
+ if ($iSiblingPos !== false) {
+ $iPosition = $iSiblingPos;
+ $oRule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1);
+ }
+ }
+ if ($oRule->getLineNo() === 0 && $oRule->getColNo() === 0) {
+ //this node is added manually, give it the next best line
+ $rules = $this->getRules();
+ $pos = count($rules);
+ if ($pos > 0) {
+ $last = $rules[$pos - 1];
+ $oRule->setPosition($last->getLineNo() + 1, 0);
+ }
+ }
+
+ array_splice($this->aRules[$sRule], $iPosition, 0, [$oRule]);
+ }
+
+ /**
+ * Returns all rules matching the given rule name
+ *
+ * @example $oRuleSet->getRules('font') // returns array(0 => $oRule, …) or array().
+ *
+ * @example $oRuleSet->getRules('font-')
+ * //returns an array of all rules either beginning with font- or matching font.
+ *
+ * @param Rule|string|null $mRule
+ * Pattern to search for. If null, returns all rules.
+ * If the pattern ends with a dash, all rules starting with the pattern are returned
+ * as well as one matching the pattern with the dash excluded.
+ * Passing a Rule behaves like calling `getRules($mRule->getRule())`.
+ *
+ * @return array<int, Rule>
+ */
+ public function getRules($mRule = null)
+ {
+ if ($mRule instanceof Rule) {
+ $mRule = $mRule->getRule();
+ }
+ /** @var array<int, Rule> $aResult */
+ $aResult = [];
+ foreach ($this->aRules as $sName => $aRules) {
+ // Either no search rule is given or the search rule matches the found rule exactly
+ // or the search rule ends in “-†and the found rule starts with the search rule.
+ if (
+ !$mRule || $sName === $mRule
+ || (
+ strrpos($mRule, '-') === strlen($mRule) - strlen('-')
+ && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1))
+ )
+ ) {
+ $aResult = array_merge($aResult, $aRules);
+ }
+ }
+ usort($aResult, function (Rule $first, Rule $second) {
+ if ($first->getLineNo() === $second->getLineNo()) {
+ return $first->getColNo() - $second->getColNo();
+ }
+ return $first->getLineNo() - $second->getLineNo();
+ });
+ return $aResult;
+ }
+
+ /**
+ * Overrides all the rules of this set.
+ *
+ * @param array<array-key, Rule> $aRules The rules to override with.
+ *
+ * @return void
+ */
+ public function setRules(array $aRules)
+ {
+ $this->aRules = [];
+ foreach ($aRules as $rule) {
+ $this->addRule($rule);
+ }
+ }
+
+ /**
+ * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name
+ * as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
+ *
+ * Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block
+ * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array
+ * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both.
+ *
+ * @param Rule|string|null $mRule $mRule
+ * Pattern to search for. If null, returns all rules. If the pattern ends with a dash,
+ * all rules starting with the pattern are returned as well as one matching the pattern with the dash
+ * excluded. Passing a Rule behaves like calling `getRules($mRule->getRule())`.
+ *
+ * @return array<string, Rule>
+ */
+ public function getRulesAssoc($mRule = null)
+ {
+ /** @var array<string, Rule> $aResult */
+ $aResult = [];
+ foreach ($this->getRules($mRule) as $oRule) {
+ $aResult[$oRule->getRule()] = $oRule;
+ }
+ return $aResult;
+ }
+
+ /**
+ * Removes a rule from this RuleSet. This accepts all the possible values that `getRules()` accepts.
+ *
+ * If given a Rule, it will only remove this particular rule (by identity).
+ * If given a name, it will remove all rules by that name.
+ *
+ * Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would
+ * remove all rules with the same name. To get the old behaviour, use `removeRule($oRule->getRule())`.
+ *
+ * @param Rule|string|null $mRule
+ * pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash,
+ * all rules starting with the pattern are removed as well as one matching the pattern with the dash
+ * excluded. Passing a Rule behaves matches by identity.
+ *
+ * @return void
+ */
+ public function removeRule($mRule)
+ {
+ if ($mRule instanceof Rule) {
+ $sRule = $mRule->getRule();
+ if (!isset($this->aRules[$sRule])) {
+ return;
+ }
+ foreach ($this->aRules[$sRule] as $iKey => $oRule) {
+ if ($oRule === $mRule) {
+ unset($this->aRules[$sRule][$iKey]);
+ }
+ }
+ } else {
+ foreach ($this->aRules as $sName => $aRules) {
+ // Either no search rule is given or the search rule matches the found rule exactly
+ // or the search rule ends in “-†and the found rule starts with the search rule or equals it
+ // (without the trailing dash).
+ if (
+ !$mRule || $sName === $mRule
+ || (strrpos($mRule, '-') === strlen($mRule) - strlen('-')
+ && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)))
+ ) {
+ unset($this->aRules[$sName]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sResult = '';
+ $bIsFirst = true;
+ foreach ($this->aRules as $aRules) {
+ foreach ($aRules as $oRule) {
+ $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) {
+ return $oRule->render($oOutputFormat->nextLevel());
+ });
+ if ($sRendered === null) {
+ continue;
+ }
+ if ($bIsFirst) {
+ $bIsFirst = false;
+ $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules();
+ } else {
+ $sResult .= $oOutputFormat->nextLevel()->spaceBetweenRules();
+ }
+ $sResult .= $sRendered;
+ }
+ }
+
+ if (!$bIsFirst) {
+ // Had some output
+ $sResult .= $oOutputFormat->spaceAfterRules();
+ }
+
+ return $oOutputFormat->removeLastSemicolon($sResult);
+ }
+
+ /**
+ * @param array<string, Comment> $aComments
+ *
+ * @return void
+ */
+ public function addComments(array $aComments)
+ {
+ $this->aComments = array_merge($this->aComments, $aComments);
+ }
+
+ /**
+ * @return array<string, Comment>
+ */
+ public function getComments()
+ {
+ return $this->aComments;
+ }
+
+ /**
+ * @param array<string, Comment> $aComments
+ *
+ * @return void
+ */
+ public function setComments(array $aComments)
+ {
+ $this->aComments = $aComments;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Settings.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Settings.php
new file mode 100644
index 0000000..7b85809
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Settings.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Sabberworm\CSS;
+
+/**
+ * Parser settings class.
+ *
+ * Configure parser behaviour here.
+ */
+class Settings
+{
+ /**
+ * Multi-byte string support.
+ * If true (mbstring extension must be enabled), will use (slower) `mb_strlen`, `mb_convert_case`, `mb_substr`
+ * and `mb_strpos` functions. Otherwise, the normal (ASCII-Only) functions will be used.
+ *
+ * @var bool
+ */
+ public $bMultibyteSupport;
+
+ /**
+ * The default charset for the CSS if no `@charset` rule is found. Defaults to utf-8.
+ *
+ * @var string
+ */
+ public $sDefaultCharset = 'utf-8';
+
+ /**
+ * Lenient parsing. When used (which is true by default), the parser will not choke
+ * on unexpected tokens but simply ignore them.
+ *
+ * @var bool
+ */
+ public $bLenientParsing = true;
+
+ private function __construct()
+ {
+ $this->bMultibyteSupport = extension_loaded('mbstring');
+ }
+
+ /**
+ * @return self new instance
+ */
+ public static function create()
+ {
+ return new Settings();
+ }
+
+ /**
+ * @param bool $bMultibyteSupport
+ *
+ * @return self fluent interface
+ */
+ public function withMultibyteSupport($bMultibyteSupport = true)
+ {
+ $this->bMultibyteSupport = $bMultibyteSupport;
+ return $this;
+ }
+
+ /**
+ * @param string $sDefaultCharset
+ *
+ * @return self fluent interface
+ */
+ public function withDefaultCharset($sDefaultCharset)
+ {
+ $this->sDefaultCharset = $sDefaultCharset;
+ return $this;
+ }
+
+ /**
+ * @param bool $bLenientParsing
+ *
+ * @return self fluent interface
+ */
+ public function withLenientParsing($bLenientParsing = true)
+ {
+ $this->bLenientParsing = $bLenientParsing;
+ return $this;
+ }
+
+ /**
+ * @return self fluent interface
+ */
+ public function beStrict()
+ {
+ return $this->withLenientParsing(false);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSFunction.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSFunction.php
new file mode 100644
index 0000000..e6b8c11
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSFunction.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+
+class CSSFunction extends ValueList
+{
+ /**
+ * @var string
+ */
+ protected $sName;
+
+ /**
+ * @param string $sName
+ * @param RuleValueList|array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aArguments
+ * @param string $sSeparator
+ * @param int $iLineNo
+ */
+ public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0)
+ {
+ if ($aArguments instanceof RuleValueList) {
+ $sSeparator = $aArguments->getListSeparator();
+ $aArguments = $aArguments->getListComponents();
+ }
+ $this->sName = $sName;
+ $this->iLineNo = $iLineNo;
+ parent::__construct($aArguments, $sSeparator, $iLineNo);
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->sName;
+ }
+
+ /**
+ * @param string $sName
+ *
+ * @return void
+ */
+ public function setName($sName)
+ {
+ $this->sName = $sName;
+ }
+
+ /**
+ * @return array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string>
+ */
+ public function getArguments()
+ {
+ return $this->aComponents;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $aArguments = parent::render($oOutputFormat);
+ return "{$this->sName}({$aArguments})";
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSString.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSString.php
new file mode 100644
index 0000000..9fafedd
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CSSString.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\SourceException;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+
+class CSSString extends PrimitiveValue
+{
+ /**
+ * @var string
+ */
+ private $sString;
+
+ /**
+ * @param string $sString
+ * @param int $iLineNo
+ */
+ public function __construct($sString, $iLineNo = 0)
+ {
+ $this->sString = $sString;
+ parent::__construct($iLineNo);
+ }
+
+ /**
+ * @return CSSString
+ *
+ * @throws SourceException
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $sBegin = $oParserState->peek();
+ $sQuote = null;
+ if ($sBegin === "'") {
+ $sQuote = "'";
+ } elseif ($sBegin === '"') {
+ $sQuote = '"';
+ }
+ if ($sQuote !== null) {
+ $oParserState->consume($sQuote);
+ }
+ $sResult = "";
+ $sContent = null;
+ if ($sQuote === null) {
+ // Unquoted strings end in whitespace or with braces, brackets, parentheses
+ while (!preg_match('/[\\s{}()<>\\[\\]]/isu', $oParserState->peek())) {
+ $sResult .= $oParserState->parseCharacter(false);
+ }
+ } else {
+ while (!$oParserState->comes($sQuote)) {
+ $sContent = $oParserState->parseCharacter(false);
+ if ($sContent === null) {
+ throw new SourceException(
+ "Non-well-formed quoted string {$oParserState->peek(3)}",
+ $oParserState->currentLine()
+ );
+ }
+ $sResult .= $sContent;
+ }
+ $oParserState->consume($sQuote);
+ }
+ return new CSSString($sResult, $oParserState->currentLine());
+ }
+
+ /**
+ * @param string $sString
+ *
+ * @return void
+ */
+ public function setString($sString)
+ {
+ $this->sString = $sString;
+ }
+
+ /**
+ * @return string
+ */
+ public function getString()
+ {
+ return $this->sString;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $sString = addslashes($this->sString);
+ $sString = str_replace("\n", '\A', $sString);
+ return $oOutputFormat->getStringQuotingType() . $sString . $oOutputFormat->getStringQuotingType();
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcFunction.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcFunction.php
new file mode 100644
index 0000000..5c92e0c
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcFunction.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+
+class CalcFunction extends CSSFunction
+{
+ /**
+ * @var int
+ */
+ const T_OPERAND = 1;
+
+ /**
+ * @var int
+ */
+ const T_OPERATOR = 2;
+
+ /**
+ * @return CalcFunction
+ *
+ * @throws UnexpectedTokenException
+ * @throws UnexpectedEOFException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $aOperators = ['+', '-', '*', '/'];
+ $sFunction = trim($oParserState->consumeUntil('(', false, true));
+ $oCalcList = new CalcRuleValueList($oParserState->currentLine());
+ $oList = new RuleValueList(',', $oParserState->currentLine());
+ $iNestingLevel = 0;
+ $iLastComponentType = null;
+ while (!$oParserState->comes(')') || $iNestingLevel > 0) {
+ $oParserState->consumeWhiteSpace();
+ if ($oParserState->comes('(')) {
+ $iNestingLevel++;
+ $oCalcList->addListComponent($oParserState->consume(1));
+ $oParserState->consumeWhiteSpace();
+ continue;
+ } elseif ($oParserState->comes(')')) {
+ $iNestingLevel--;
+ $oCalcList->addListComponent($oParserState->consume(1));
+ $oParserState->consumeWhiteSpace();
+ continue;
+ }
+ if ($iLastComponentType != CalcFunction::T_OPERAND) {
+ $oVal = Value::parsePrimitiveValue($oParserState);
+ $oCalcList->addListComponent($oVal);
+ $iLastComponentType = CalcFunction::T_OPERAND;
+ } else {
+ if (in_array($oParserState->peek(), $aOperators)) {
+ if (($oParserState->comes('-') || $oParserState->comes('+'))) {
+ if (
+ $oParserState->peek(1, -1) != ' '
+ || !($oParserState->comes('- ')
+ || $oParserState->comes('+ '))
+ ) {
+ throw new UnexpectedTokenException(
+ " {$oParserState->peek()} ",
+ $oParserState->peek(1, -1) . $oParserState->peek(2),
+ 'literal',
+ $oParserState->currentLine()
+ );
+ }
+ }
+ $oCalcList->addListComponent($oParserState->consume(1));
+ $iLastComponentType = CalcFunction::T_OPERATOR;
+ } else {
+ throw new UnexpectedTokenException(
+ sprintf(
+ 'Next token was expected to be an operand of type %s. Instead "%s" was found.',
+ implode(', ', $aOperators),
+ $oVal
+ ),
+ '',
+ 'custom',
+ $oParserState->currentLine()
+ );
+ }
+ }
+ $oParserState->consumeWhiteSpace();
+ }
+ $oList->addListComponent($oCalcList);
+ $oParserState->consume(')');
+ return new CalcFunction($sFunction, $oList, ',', $oParserState->currentLine());
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php
new file mode 100644
index 0000000..7dbd26a
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/CalcRuleValueList.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+
+class CalcRuleValueList extends RuleValueList
+{
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ parent::__construct(',', $iLineNo);
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return $oOutputFormat->implode(' ', $this->aComponents);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Color.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Color.php
new file mode 100644
index 0000000..8dc5296
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Color.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+
+class Color extends CSSFunction
+{
+ /**
+ * @param array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aColor
+ * @param int $iLineNo
+ */
+ public function __construct(array $aColor, $iLineNo = 0)
+ {
+ parent::__construct(implode('', array_keys($aColor)), $aColor, ',', $iLineNo);
+ }
+
+ /**
+ * @return Color|CSSFunction
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $aColor = [];
+ if ($oParserState->comes('#')) {
+ $oParserState->consume('#');
+ $sValue = $oParserState->parseIdentifier(false);
+ if ($oParserState->strlen($sValue) === 3) {
+ $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2];
+ } elseif ($oParserState->strlen($sValue) === 4) {
+ $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2] . $sValue[3]
+ . $sValue[3];
+ }
+
+ if ($oParserState->strlen($sValue) === 8) {
+ $aColor = [
+ 'r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
+ 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
+ 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
+ 'a' => new Size(
+ round(self::mapRange(intval($sValue[6] . $sValue[7], 16), 0, 255, 0, 1), 2),
+ null,
+ true,
+ $oParserState->currentLine()
+ ),
+ ];
+ } else {
+ $aColor = [
+ 'r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $oParserState->currentLine()),
+ 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $oParserState->currentLine()),
+ 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $oParserState->currentLine()),
+ ];
+ }
+ } else {
+ $sColorMode = $oParserState->parseIdentifier(true);
+ $oParserState->consumeWhiteSpace();
+ $oParserState->consume('(');
+
+ $bContainsVar = false;
+ $iLength = $oParserState->strlen($sColorMode);
+ for ($i = 0; $i < $iLength; ++$i) {
+ $oParserState->consumeWhiteSpace();
+ if ($oParserState->comes('var')) {
+ $aColor[$sColorMode[$i]] = CSSFunction::parseIdentifierOrFunction($oParserState);
+ $bContainsVar = true;
+ } else {
+ $aColor[$sColorMode[$i]] = Size::parse($oParserState, true);
+ }
+
+ if ($bContainsVar && $oParserState->comes(')')) {
+ // With a var argument the function can have fewer arguments
+ break;
+ }
+
+ $oParserState->consumeWhiteSpace();
+ if ($i < ($iLength - 1)) {
+ $oParserState->consume(',');
+ }
+ }
+ $oParserState->consume(')');
+
+ if ($bContainsVar) {
+ return new CSSFunction($sColorMode, array_values($aColor), ',', $oParserState->currentLine());
+ }
+ }
+ return new Color($aColor, $oParserState->currentLine());
+ }
+
+ /**
+ * @param float $fVal
+ * @param float $fFromMin
+ * @param float $fFromMax
+ * @param float $fToMin
+ * @param float $fToMax
+ *
+ * @return float
+ */
+ private static function mapRange($fVal, $fFromMin, $fFromMax, $fToMin, $fToMax)
+ {
+ $fFromRange = $fFromMax - $fFromMin;
+ $fToRange = $fToMax - $fToMin;
+ $fMultiplier = $fToRange / $fFromRange;
+ $fNewVal = $fVal - $fFromMin;
+ $fNewVal *= $fMultiplier;
+ return $fNewVal + $fToMin;
+ }
+
+ /**
+ * @return array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string>
+ */
+ public function getColor()
+ {
+ return $this->aComponents;
+ }
+
+ /**
+ * @param array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aColor
+ *
+ * @return void
+ */
+ public function setColor(array $aColor)
+ {
+ $this->setName(implode('', array_keys($aColor)));
+ $this->aComponents = $aColor;
+ }
+
+ /**
+ * @return string
+ */
+ public function getColorDescription()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ // Shorthand RGB color values
+ if ($oOutputFormat->getRGBHashNotation() && implode('', array_keys($this->aComponents)) === 'rgb') {
+ $sResult = sprintf(
+ '%02x%02x%02x',
+ $this->aComponents['r']->getSize(),
+ $this->aComponents['g']->getSize(),
+ $this->aComponents['b']->getSize()
+ );
+ return '#' . (($sResult[0] == $sResult[1]) && ($sResult[2] == $sResult[3]) && ($sResult[4] == $sResult[5])
+ ? "$sResult[0]$sResult[2]$sResult[4]" : $sResult);
+ }
+ return parent::render($oOutputFormat);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/LineName.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/LineName.php
new file mode 100644
index 0000000..e231ce3
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/LineName.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+
+class LineName extends ValueList
+{
+ /**
+ * @param array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aComponents
+ * @param int $iLineNo
+ */
+ public function __construct(array $aComponents = [], $iLineNo = 0)
+ {
+ parent::__construct($aComponents, ' ', $iLineNo);
+ }
+
+ /**
+ * @return LineName
+ *
+ * @throws UnexpectedTokenException
+ * @throws UnexpectedEOFException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $oParserState->consume('[');
+ $oParserState->consumeWhiteSpace();
+ $aNames = [];
+ do {
+ if ($oParserState->getSettings()->bLenientParsing) {
+ try {
+ $aNames[] = $oParserState->parseIdentifier();
+ } catch (UnexpectedTokenException $e) {
+ if (!$oParserState->comes(']')) {
+ throw $e;
+ }
+ }
+ } else {
+ $aNames[] = $oParserState->parseIdentifier();
+ }
+ $oParserState->consumeWhiteSpace();
+ } while (!$oParserState->comes(']'));
+ $oParserState->consume(']');
+ return new LineName($aNames, $oParserState->currentLine());
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return '[' . parent::render(OutputFormat::createCompact()) . ']';
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/PrimitiveValue.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/PrimitiveValue.php
new file mode 100644
index 0000000..055a439
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/PrimitiveValue.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+abstract class PrimitiveValue extends Value
+{
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/RuleValueList.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/RuleValueList.php
new file mode 100644
index 0000000..5d533a7
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/RuleValueList.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+class RuleValueList extends ValueList
+{
+ /**
+ * @param string $sSeparator
+ * @param int $iLineNo
+ */
+ public function __construct($sSeparator = ',', $iLineNo = 0)
+ {
+ parent::__construct([], $sSeparator, $iLineNo);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Size.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Size.php
new file mode 100644
index 0000000..b3801dc
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Size.php
@@ -0,0 +1,209 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+
+class Size extends PrimitiveValue
+{
+ /**
+ * vh/vw/vm(ax)/vmin/rem are absolute insofar as they don’t scale to the immediate parent (only the viewport)
+ *
+ * @var array<int, string>
+ */
+ const ABSOLUTE_SIZE_UNITS = ['px', 'cm', 'mm', 'mozmm', 'in', 'pt', 'pc', 'vh', 'vw', 'vmin', 'vmax', 'rem'];
+
+ /**
+ * @var array<int, string>
+ */
+ const RELATIVE_SIZE_UNITS = ['%', 'em', 'ex', 'ch', 'fr'];
+
+ /**
+ * @var array<int, string>
+ */
+ const NON_SIZE_UNITS = ['deg', 'grad', 'rad', 's', 'ms', 'turns', 'Hz', 'kHz'];
+
+ /**
+ * @var array<int, array<string, string>>|null
+ */
+ private static $SIZE_UNITS = null;
+
+ /**
+ * @var float
+ */
+ private $fSize;
+
+ /**
+ * @var string|null
+ */
+ private $sUnit;
+
+ /**
+ * @var bool
+ */
+ private $bIsColorComponent;
+
+ /**
+ * @param float|int|string $fSize
+ * @param string|null $sUnit
+ * @param bool $bIsColorComponent
+ * @param int $iLineNo
+ */
+ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false, $iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ $this->fSize = (float)$fSize;
+ $this->sUnit = $sUnit;
+ $this->bIsColorComponent = $bIsColorComponent;
+ }
+
+ /**
+ * @param bool $bIsColorComponent
+ *
+ * @return Size
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public static function parse(ParserState $oParserState, $bIsColorComponent = false)
+ {
+ $sSize = '';
+ if ($oParserState->comes('-')) {
+ $sSize .= $oParserState->consume('-');
+ }
+ while (is_numeric($oParserState->peek()) || $oParserState->comes('.')) {
+ if ($oParserState->comes('.')) {
+ $sSize .= $oParserState->consume('.');
+ } else {
+ $sSize .= $oParserState->consume(1);
+ }
+ }
+
+ $sUnit = null;
+ $aSizeUnits = self::getSizeUnits();
+ foreach ($aSizeUnits as $iLength => &$aValues) {
+ $sKey = strtolower($oParserState->peek($iLength));
+ if (array_key_exists($sKey, $aValues)) {
+ if (($sUnit = $aValues[$sKey]) !== null) {
+ $oParserState->consume($iLength);
+ break;
+ }
+ }
+ }
+ return new Size((float)$sSize, $sUnit, $bIsColorComponent, $oParserState->currentLine());
+ }
+
+ /**
+ * @return array<int, array<string, string>>
+ */
+ private static function getSizeUnits()
+ {
+ if (!is_array(self::$SIZE_UNITS)) {
+ self::$SIZE_UNITS = [];
+ foreach (array_merge(self::ABSOLUTE_SIZE_UNITS, self::RELATIVE_SIZE_UNITS, self::NON_SIZE_UNITS) as $val) {
+ $iSize = strlen($val);
+ if (!isset(self::$SIZE_UNITS[$iSize])) {
+ self::$SIZE_UNITS[$iSize] = [];
+ }
+ self::$SIZE_UNITS[$iSize][strtolower($val)] = $val;
+ }
+
+ krsort(self::$SIZE_UNITS, SORT_NUMERIC);
+ }
+
+ return self::$SIZE_UNITS;
+ }
+
+ /**
+ * @param string $sUnit
+ *
+ * @return void
+ */
+ public function setUnit($sUnit)
+ {
+ $this->sUnit = $sUnit;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getUnit()
+ {
+ return $this->sUnit;
+ }
+
+ /**
+ * @param float|int|string $fSize
+ */
+ public function setSize($fSize)
+ {
+ $this->fSize = (float)$fSize;
+ }
+
+ /**
+ * @return float
+ */
+ public function getSize()
+ {
+ return $this->fSize;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isColorComponent()
+ {
+ return $this->bIsColorComponent;
+ }
+
+ /**
+ * Returns whether the number stored in this Size really represents a size (as in a length of something on screen).
+ *
+ * @return false if the unit an angle, a duration, a frequency or the number is a component in a Color object.
+ */
+ public function isSize()
+ {
+ if (in_array($this->sUnit, self::NON_SIZE_UNITS, true)) {
+ return false;
+ }
+ return !$this->isColorComponent();
+ }
+
+ /**
+ * @return bool
+ */
+ public function isRelative()
+ {
+ if (in_array($this->sUnit, self::RELATIVE_SIZE_UNITS, true)) {
+ return true;
+ }
+ if ($this->sUnit === null && $this->fSize != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ $l = localeconv();
+ $sPoint = preg_quote($l['decimal_point'], '/');
+ $sSize = preg_match("/[\d\.]+e[+-]?\d+/i", (string)$this->fSize)
+ ? preg_replace("/$sPoint?0+$/", "", sprintf("%f", $this->fSize)) : $this->fSize;
+ return preg_replace(["/$sPoint/", "/^(-?)0\./"], ['.', '$1.'], $sSize)
+ . ($this->sUnit === null ? '' : $this->sUnit);
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/URL.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/URL.php
new file mode 100644
index 0000000..1467d50
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/URL.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\SourceException;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+
+class URL extends PrimitiveValue
+{
+ /**
+ * @var CSSString
+ */
+ private $oURL;
+
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct(CSSString $oURL, $iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ $this->oURL = $oURL;
+ }
+
+ /**
+ * @return URL
+ *
+ * @throws SourceException
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public static function parse(ParserState $oParserState)
+ {
+ $bUseUrl = $oParserState->comes('url', true);
+ if ($bUseUrl) {
+ $oParserState->consume('url');
+ $oParserState->consumeWhiteSpace();
+ $oParserState->consume('(');
+ }
+ $oParserState->consumeWhiteSpace();
+ $oResult = new URL(CSSString::parse($oParserState), $oParserState->currentLine());
+ if ($bUseUrl) {
+ $oParserState->consumeWhiteSpace();
+ $oParserState->consume(')');
+ }
+ return $oResult;
+ }
+
+ /**
+ * @return void
+ */
+ public function setURL(CSSString $oURL)
+ {
+ $this->oURL = $oURL;
+ }
+
+ /**
+ * @return CSSString
+ */
+ public function getURL()
+ {
+ return $this->oURL;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return "url({$this->oURL->render($oOutputFormat)})";
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Value.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Value.php
new file mode 100644
index 0000000..66cb9fd
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/Value.php
@@ -0,0 +1,198 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\Parsing\ParserState;
+use Sabberworm\CSS\Parsing\SourceException;
+use Sabberworm\CSS\Parsing\UnexpectedEOFException;
+use Sabberworm\CSS\Parsing\UnexpectedTokenException;
+use Sabberworm\CSS\Renderable;
+
+abstract class Value implements Renderable
+{
+ /**
+ * @var int
+ */
+ protected $iLineNo;
+
+ /**
+ * @param int $iLineNo
+ */
+ public function __construct($iLineNo = 0)
+ {
+ $this->iLineNo = $iLineNo;
+ }
+
+ /**
+ * @param array<array-key, string> $aListDelimiters
+ *
+ * @return RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string
+ *
+ * @throws UnexpectedTokenException
+ * @throws UnexpectedEOFException
+ */
+ public static function parseValue(ParserState $oParserState, array $aListDelimiters = [])
+ {
+ /** @var array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aStack */
+ $aStack = [];
+ $oParserState->consumeWhiteSpace();
+ //Build a list of delimiters and parsed values
+ while (
+ !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!')
+ || $oParserState->comes(')')
+ || $oParserState->comes('\\'))
+ ) {
+ if (count($aStack) > 0) {
+ $bFoundDelimiter = false;
+ foreach ($aListDelimiters as $sDelimiter) {
+ if ($oParserState->comes($sDelimiter)) {
+ array_push($aStack, $oParserState->consume($sDelimiter));
+ $oParserState->consumeWhiteSpace();
+ $bFoundDelimiter = true;
+ break;
+ }
+ }
+ if (!$bFoundDelimiter) {
+ //Whitespace was the list delimiter
+ array_push($aStack, ' ');
+ }
+ }
+ array_push($aStack, self::parsePrimitiveValue($oParserState));
+ $oParserState->consumeWhiteSpace();
+ }
+ // Convert the list to list objects
+ foreach ($aListDelimiters as $sDelimiter) {
+ if (count($aStack) === 1) {
+ return $aStack[0];
+ }
+ $iStartPosition = null;
+ while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) {
+ $iLength = 2; //Number of elements to be joined
+ for ($i = $iStartPosition + 2; $i < count($aStack); $i += 2, ++$iLength) {
+ if ($sDelimiter !== $aStack[$i]) {
+ break;
+ }
+ }
+ $oList = new RuleValueList($sDelimiter, $oParserState->currentLine());
+ for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i += 2) {
+ $oList->addListComponent($aStack[$i]);
+ }
+ array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, [$oList]);
+ }
+ }
+ if (!isset($aStack[0])) {
+ throw new UnexpectedTokenException(
+ " {$oParserState->peek()} ",
+ $oParserState->peek(1, -1) . $oParserState->peek(2),
+ 'literal',
+ $oParserState->currentLine()
+ );
+ }
+ return $aStack[0];
+ }
+
+ /**
+ * @param bool $bIgnoreCase
+ *
+ * @return CSSFunction|string
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ public static function parseIdentifierOrFunction(ParserState $oParserState, $bIgnoreCase = false)
+ {
+ $sResult = $oParserState->parseIdentifier($bIgnoreCase);
+
+ if ($oParserState->comes('(')) {
+ $oParserState->consume('(');
+ $aArguments = Value::parseValue($oParserState, ['=', ' ', ',']);
+ $sResult = new CSSFunction($sResult, $aArguments, ',', $oParserState->currentLine());
+ $oParserState->consume(')');
+ }
+
+ return $sResult;
+ }
+
+ /**
+ * @return CSSFunction|CSSString|LineName|Size|URL|string
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ * @throws SourceException
+ */
+ public static function parsePrimitiveValue(ParserState $oParserState)
+ {
+ $oValue = null;
+ $oParserState->consumeWhiteSpace();
+ if (
+ is_numeric($oParserState->peek())
+ || ($oParserState->comes('-.')
+ && is_numeric($oParserState->peek(1, 2)))
+ || (($oParserState->comes('-') || $oParserState->comes('.')) && is_numeric($oParserState->peek(1, 1)))
+ ) {
+ $oValue = Size::parse($oParserState);
+ } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) {
+ $oValue = Color::parse($oParserState);
+ } elseif ($oParserState->comes('url', true)) {
+ $oValue = URL::parse($oParserState);
+ } elseif (
+ $oParserState->comes('calc', true) || $oParserState->comes('-webkit-calc', true)
+ || $oParserState->comes('-moz-calc', true)
+ ) {
+ $oValue = CalcFunction::parse($oParserState);
+ } elseif ($oParserState->comes("'") || $oParserState->comes('"')) {
+ $oValue = CSSString::parse($oParserState);
+ } elseif ($oParserState->comes("progid:") && $oParserState->getSettings()->bLenientParsing) {
+ $oValue = self::parseMicrosoftFilter($oParserState);
+ } elseif ($oParserState->comes("[")) {
+ $oValue = LineName::parse($oParserState);
+ } elseif ($oParserState->comes("U+")) {
+ $oValue = self::parseUnicodeRangeValue($oParserState);
+ } else {
+ $oValue = self::parseIdentifierOrFunction($oParserState);
+ }
+ $oParserState->consumeWhiteSpace();
+ return $oValue;
+ }
+
+ /**
+ * @return CSSFunction
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ private static function parseMicrosoftFilter(ParserState $oParserState)
+ {
+ $sFunction = $oParserState->consumeUntil('(', false, true);
+ $aArguments = Value::parseValue($oParserState, [',', '=']);
+ return new CSSFunction($sFunction, $aArguments, ',', $oParserState->currentLine());
+ }
+
+ /**
+ * @return string
+ *
+ * @throws UnexpectedEOFException
+ * @throws UnexpectedTokenException
+ */
+ private static function parseUnicodeRangeValue(ParserState $oParserState)
+ {
+ $iCodepointMaxLength = 6; // Code points outside BMP can use up to six digits
+ $sRange = "";
+ $oParserState->consume("U+");
+ do {
+ if ($oParserState->comes('-')) {
+ $iCodepointMaxLength = 13; // Max length is 2 six digit code points + the dash(-) between them
+ }
+ $sRange .= $oParserState->consume(1);
+ } while (strlen($sRange) < $iCodepointMaxLength && preg_match("/[A-Fa-f0-9\?-]/", $oParserState->peek()));
+ return "U+{$sRange}";
+ }
+
+ /**
+ * @return int
+ */
+ public function getLineNo()
+ {
+ return $this->iLineNo;
+ }
+}
diff --git a/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/ValueList.php b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/ValueList.php
new file mode 100644
index 0000000..af5348b
--- /dev/null
+++ b/library/vendor/dompdf/vendor/sabberworm/php-css-parser/src/Value/ValueList.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Sabberworm\CSS\Value;
+
+use Sabberworm\CSS\OutputFormat;
+
+abstract class ValueList extends Value
+{
+ /**
+ * @var array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string>
+ */
+ protected $aComponents;
+
+ /**
+ * @var string
+ */
+ protected $sSeparator;
+
+ /**
+ * phpcs:ignore Generic.Files.LineLength
+ * @param array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string>|RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string $aComponents
+ * @param string $sSeparator
+ * @param int $iLineNo
+ */
+ public function __construct($aComponents = [], $sSeparator = ',', $iLineNo = 0)
+ {
+ parent::__construct($iLineNo);
+ if (!is_array($aComponents)) {
+ $aComponents = [$aComponents];
+ }
+ $this->aComponents = $aComponents;
+ $this->sSeparator = $sSeparator;
+ }
+
+ /**
+ * @param RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string $mComponent
+ *
+ * @return void
+ */
+ public function addListComponent($mComponent)
+ {
+ $this->aComponents[] = $mComponent;
+ }
+
+ /**
+ * @return array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string>
+ */
+ public function getListComponents()
+ {
+ return $this->aComponents;
+ }
+
+ /**
+ * @param array<int, RuleValueList|CSSFunction|CSSString|LineName|Size|URL|string> $aComponents
+ *
+ * @return void
+ */
+ public function setListComponents(array $aComponents)
+ {
+ $this->aComponents = $aComponents;
+ }
+
+ /**
+ * @return string
+ */
+ public function getListSeparator()
+ {
+ return $this->sSeparator;
+ }
+
+ /**
+ * @param string $sSeparator
+ *
+ * @return void
+ */
+ public function setListSeparator($sSeparator)
+ {
+ $this->sSeparator = $sSeparator;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->render(new OutputFormat());
+ }
+
+ /**
+ * @return string
+ */
+ public function render(OutputFormat $oOutputFormat)
+ {
+ return $oOutputFormat->implode(
+ $oOutputFormat->spaceBeforeListArgumentSeparator($this->sSeparator) . $this->sSeparator
+ . $oOutputFormat->spaceAfterListArgumentSeparator($this->sSeparator),
+ $this->aComponents
+ );
+ }
+}
diff --git a/library/vendor/lessphp/CHANGES.md b/library/vendor/lessphp/CHANGES.md
new file mode 100644
index 0000000..77eaf5b
--- /dev/null
+++ b/library/vendor/lessphp/CHANGES.md
@@ -0,0 +1,72 @@
+## 3.1.0
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v3.0.0...v3.1.0)
+* Add PHP 8.0 support: Drop use of curly braces for sub-string eval (James D. Forrester)
+* Make `Directive::__construct` $rules arg optional (fix PHP 7.4 warning) (Sam Reed)
+* ProcessExtends: Improve performance by using a map for selectors and parents (Andrey Legayev)
+
+## 3.0.0
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v2.0.0...v3.0.0)
+* Raise PHP requirement from 7.1 to 7.2.9 (James Forrester)
+
+## 2.0.0
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.8.2...v2.0.0)
+* Relax PHP requirement down to 7.1, from 7.2.9 (Franz Liedke)
+* Reflect recent breaking changes properly with the semantic versioning (James Forrester)
+
+## 1.8.2
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.8.1...v1.8.2)
+* Require PHP 7.2.9+, up from 5.3+ (James Forrester)
+* release: Update Version.php with the current release ID (COBadger)
+* Fix access array offset on value of type null (Michele Locati)
+* Fix test suite on PHP 7.4 (Sergei Morozov)
+
+## 1.8.1
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.8.0...v1.8.1)
+* Another PHP 7.3 compatibility tweak
+
+## 1.8.0
+
+Library forked by Wikimedia, from [oyejorge/less.php](https://github.com/oyejorge/less.php).
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.7.0.13...v1.8.0)
+* Supports up to PHP 7.3
+* No longer tested against PHP 5, though it's still remains allowed in `composer.json` for HHVM compatibility
+* Switched to [semantic versioning](https://semver.org/), hence version numbers now use 3 digits
+
+## 1.7.0.13
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.7.0.12...v1.7.0.13)
+* Fix composer.json (PSR-4 was invalid)
+
+## 1.7.0.12
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.7.0.11...v1.7.0.12)
+* set bin/lessc bit executable
+* Add `gettingVariables` method to `Less_Parser`
+
+## 1.7.0.11
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.7.0.10...v1.7.0.11)
+* Fix realpath issue (windows)
+* Set Less_Tree_Call property back to public ( Fix 258 266 267 issues from oyejorge/less.php)
+
+## 1.7.0.10
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.7.0.9...v1.7.10)
+* Add indentation option
+* Add `optional` modifier for @import
+* Fix $color in Exception messages
+* take relative-url into account when building the cache filename
+* urlArgs should be string no array()
+* fix missing on NameValue type [#269](https://github.com/oyejorge/less.php/issues/269)
+
+## 1.7.0.9
+
+* [All changes](https://github.com/wikimedia/less.php/compare/v1.7.0.8...v1.7.0.9)
+* Remove space at beginning of Version.php
+* Revert require() paths in test interface
diff --git a/library/vendor/lessphp/LICENSE b/library/vendor/lessphp/LICENSE
new file mode 100644
index 0000000..82216a5
--- /dev/null
+++ b/library/vendor/lessphp/LICENSE
@@ -0,0 +1,178 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
diff --git a/library/vendor/lessphp/README.md b/library/vendor/lessphp/README.md
new file mode 100644
index 0000000..a347d18
--- /dev/null
+++ b/library/vendor/lessphp/README.md
@@ -0,0 +1,332 @@
+[![Build Status](https://github.com/wikimedia/less.php/actions/workflows/php.yml/badge.svg)](https://github.com/wikimedia/less.php/actions)
+
+Less.php
+========
+
+This is a PHP port of the [official LESS processor](https://lesscss.org).
+
+* [About](#about)
+* [Installation](#installation)
+* [Security](#%EF%B8%8F-security)
+* [Basic Use](#basic-use)
+* [Caching](#caching)
+* [Source Maps](#source-maps)
+* [Command Line](#command-line)
+* [Integration with other projects](#integration-with-other-projects)
+* [Transitioning from Leafo/lessphp](#transitioning-from-leafolessphp)
+* [Credits](#credits)
+
+About
+---
+
+The code structure of Less.php mirrors that of the official processor which helps us ensure compatibility and allows for easy maintenance.
+
+Please note, there are a few unsupported LESS features:
+
+- Evaluation of JavaScript expressions within back-ticks (for obvious reasons).
+- Definition of custom functions.
+
+Installation
+---
+
+You can install the library with Composer or manually.
+
+#### Composer
+
+1. [Install Composer](https://getcomposer.org/download/)
+2. Run `composer require wikimedia/less.php`
+
+#### Manually from release
+
+1. [Download a release](https://github.com/wikimedia/less.php/releases) and upload the PHP files to your server.
+
+2. Include the library:
+
+```php
+require_once '[path to less.php]/lib/Less/Autoloader.php';
+Less_Autoloader::register();
+```
+
+âš ï¸ Security
+---
+
+The LESS processor language is powerful and including features that can read or embed arbitrary files that the web server has access to, and features that may be computationally exensive if misused.
+
+In general you should treat LESS files as being in the same trust domain as other server-side executables, such as Node.js or PHP code. In particular, it is not recommended to allow people that use your web service to provide arbitrary LESS code for server-side processing.
+
+_See also [SECURITY](./SECURITY.md)._
+
+Basic Use
+---
+
+#### Parsing strings
+
+```php
+$parser = new Less_Parser();
+$parser->parse( '@color: #4D926F; #header { color: @color; } h2 { color: @color; }' );
+$css = $parser->getCss();
+```
+
+
+#### Parsing LESS files
+The parseFile() function takes two arguments:
+
+1. The absolute path of the .less file to be parsed
+2. The url root to prepend to any relative image or @import urls in the .less file.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', 'https://example.org/mysite/' );
+$css = $parser->getCss();
+```
+
+#### Handling invalid LESS
+
+An exception will be thrown if the compiler encounters invalid LESS.
+
+```php
+try{
+ $parser = new Less_Parser();
+ $parser->parseFile( '/var/www/mysite/bootstrap.less', 'https://example.org/mysite/' );
+ $css = $parser->getCss();
+}catch(Exception $e){
+ $error_message = $e->getMessage();
+}
+```
+
+#### Parsing multiple sources
+
+Less.php can parse multiple sources to generate a single CSS file.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$parser->parse( '@color: #4D926F; #header { color: @color; } h2 { color: @color; }' );
+$css = $parser->getCss();
+```
+
+#### Getting info about the parsed files
+
+Less.php can tell you which `.less` files were imported and parsed.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+$imported_files = $parser->allParsedFiles();
+```
+
+#### Compressing output
+
+You can tell Less.php to remove comments and whitespace to generate minimized CSS files.
+
+```php
+$options = [ 'compress' => true ];
+$parser = new Less_Parser( $options );
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+#### Getting variables
+
+You can use the `getVariables()` method to get an all variables defined and
+their value in a php associative array. Note that LESS has to be previously
+compiled.
+
+```php
+$parser = new Less_Parser;
+$parser->parseFile( '/var/www/mysite/bootstrap.less');
+$css = $parser->getCss();
+$variables = $parser->getVariables();
+
+```
+
+#### Setting variables
+
+You can use the `ModifyVars()` method to customize your CSS if you have variables stored in PHP associative arrays.
+
+```php
+$parser = new Less_Parser();
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$parser->ModifyVars( [ 'font-size-base' => '16px' ] );
+$css = $parser->getCss();
+```
+
+#### Import directories
+
+By default, Less.php will look for imported files in the directory of the file passed to `parseFile()`.
+If you're using `parse()`, or if import files reside in a different directory, you can tell Less.php where to look.
+
+```php
+$directories = [ '/var/www/mysite/bootstrap/' => '/mysite/bootstrap/' ];
+$parser = new Less_Parser();
+$parser->SetImportDirs( $directories );
+$parser->parseFile( '/var/www/mysite/theme.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+Caching
+---
+
+Compiling LESS code into CSS is a time-consuming process, caching your results is highly recommended.
+
+#### Caching CSS
+
+Use the `Less_Cache` class to save and reuse the results of compiled LESS files.
+This class will check the modified time and size of each LESS file (including imported files) and regenerate a new CSS file when changes are found.
+
+Note: When changes are found, this method will return a different file name for the new cached content.
+
+```php
+$less_files = [ '/var/www/mysite/bootstrap.less' => '/mysite/' ];
+$options = [ 'cache_dir' => '/var/www/writable_folder' ];
+$css_file_name = Less_Cache::Get( $less_files, $options );
+$compiled = file_get_contents( '/var/www/writable_folder/'.$css_file_name );
+```
+
+#### Caching CSS with variables
+
+Passing options to `Less_Cache::Get()`:
+
+```php
+$less_files = [ '/var/www/mysite/bootstrap.less' => '/mysite/' ];
+$options = [ 'cache_dir' => '/var/www/writable_folder' ];
+$variables = [ 'width' => '100px' ];
+$css_file_name = Less_Cache::Get( $less_files, $options, $variables );
+$compiled = file_get_contents( '/var/www/writable_folder/'.$css_file_name );
+```
+
+#### Parser caching
+
+Less.php will save serialized parser data for each `.less` file if a writable folder is passed to the `SetCacheDir()` method.
+
+Note: This feature only caches intermediate parsing results to improve the performance of repeated CSS generation.
+
+Your application should cache any CSS generated by Less.php.
+
+```php
+$options = [ 'cache_dir'=>'/var/www/writable_folder' ];
+$parser = new Less_Parser( $options );
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+You can specify the caching technique used by changing the `cache_method` option. Supported methods are:
+
+* `php`: Creates valid PHP files which can be included without any changes (default method).
+* `var_export`: Like "php", but using PHP's `var_export()` function without any optimizations.
+ It's recommended to use "php" instead.
+* `serialize`: Faster, but pretty memory-intense.
+* `callback`: Use custom callback functions to implement your own caching method. Give the "cache_callback_get" and
+ "cache_callback_set" options with callables (see PHP's `call_user_func()` and `is_callable()` functions). Less.php
+ will pass the parser object (class `Less_Parser`), the path to the parsed .less file ("/some/path/to/file.less") and
+ an identifier that will change every time the .less file is modified. The `get` callback must return the ruleset
+ (an array with `Less_Tree` objects) provided as fourth parameter of the `set` callback. If something goes wrong,
+ return `NULL` (cache doesn't exist) or `FALSE`.
+
+
+Source maps
+---
+
+Less.php supports v3 sourcemaps.
+
+#### Inline
+
+The sourcemap will be appended to the generated CSS file.
+
+```php
+$options = [ 'sourceMap' => true ];
+$parser = new Less_Parser($options);
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+#### Saving to map file
+
+```php
+$options = [
+ 'sourceMap' => true,
+ 'sourceMapWriteTo' => '/var/www/mysite/writable_folder/filename.map',
+ 'sourceMapURL' => '/mysite/writable_folder/filename.map',
+];
+$parser = new Less_Parser($options);
+$parser->parseFile( '/var/www/mysite/bootstrap.less', '/mysite/' );
+$css = $parser->getCss();
+```
+
+Command line
+---
+
+An additional script has been included to use the compiler from the command line.
+In the simplest invocation, you specify an input file and the compiled CSS is written to standard out:
+
+```
+$ lessc input.less > output.css
+```
+
+By using the `-w` flag you can watch a specified input file and have it compile as needed to the output file:
+
+```
+$ lessc -w input.less output.css
+```
+
+Errors from watch mode are written to standard out.
+
+For more help, run `lessc --help`
+
+
+Integration with other projects
+---
+
+#### Drupal 7
+
+This library can be used as drop-in replacement of lessphp to work with [Drupal 7 less module](https://drupal.org/project/less).
+
+How to install:
+
+1. [Download the Less.php source code](https://github.com/wikimedia/less.php/archive/main.zip) and unzip it so that 'lessc.inc.php' is located at 'sites/all/libraries/lessphp/lessc.inc.php'.
+2. Download and install [Drupal 7 less module](https://drupal.org/project/less) as usual.
+3. That's it :)
+
+#### JBST WordPress theme
+
+JBST has a built-in LESS compiler based on lessphp. Customize your WordPress theme with LESS.
+
+How to use / install:
+
+1. [Download the latest release](https://github.com/bassjobsen/jamedo-bootstrap-start-theme) copy the files to your {wordpress/}wp-content/themes folder and activate it.
+2. Find the compiler under Appearance > LESS Compiler in your WordPress dashboard
+3. Enter your LESS code in the text area and press (re)compile
+
+Use the built-in compiler to:
+- set any [Bootstrap](https://getbootstrap.com/docs/3.4/customize/) variable or use Bootstrap's mixins:
+ - `@navbar-default-color: blue;`
+ - create a custom button: `.btn-custom {
+ .button-variant(white; red; blue);
+}`
+- set any built-in LESS variable: for example `@footer_bg_color: black;` sets the background color of the footer to black
+- use built-in mixins: - add a custom font: `.include-custom-font(@family: arial,@font-path, @path: @custom-font-dir, @weight: normal, @style: normal);`
+
+The compiler can also be downloaded as [plugin](https://wordpress.org/plugins/wp-less-to-css/)
+
+#### WordPress
+
+This simple plugin will simply make the library available to other plugins and themes and can be used as a dependency using the [TGM Library](http://tgmpluginactivation.com/)
+
+How to install:
+
+1. Install the plugin from your WordPress Dashboard: https://wordpress.org/plugins/lessphp/
+2. That's it :)
+
+
+Transitioning from Leafo/lessphp
+---
+
+Projects looking for an easy transition from leafo/lessphp can use the lessc.inc.php adapter. To use, [Download the Less.php source code](https://github.com/wikimedia/less.php/archive/main.zip) and unzip the files into your project so that the new `lessc.inc.php` replaces the existing `lessc.inc.php`.
+
+Note, the `setPreserveComments` will no longer have any effect on the compiled LESS.
+
+Credits
+---
+
+Less.php was originally ported to PHP in 2011 by [Matt Agar](https://github.com/agar) and then updated by [Martin JantoÅ¡oviÄ](https://github.com/Mordred) in 2012. From 2013 to 2017, [Josh Schmidt](https://github.com/oyejorge) lead development of the library. Since 2019, the library is maintained by Wikimedia.
diff --git a/library/vendor/lessphp/SECURITY.md b/library/vendor/lessphp/SECURITY.md
new file mode 100644
index 0000000..687c735
--- /dev/null
+++ b/library/vendor/lessphp/SECURITY.md
@@ -0,0 +1,5 @@
+# Security policy
+
+Wikimedia takes security seriously. If you believe you have found a
+security issue, see <https://www.mediawiki.org/wiki/Reporting_security_bugs>
+for information on how to responsibly report it.
diff --git a/library/vendor/lessphp/bin/lessc b/library/vendor/lessphp/bin/lessc
new file mode 100755
index 0000000..fa1fb95
--- /dev/null
+++ b/library/vendor/lessphp/bin/lessc
@@ -0,0 +1,191 @@
+#!/usr/bin/env php
+<?php
+
+require_once dirname(__FILE__) . '/../lib/Less/Autoloader.php';
+Less_Autoloader::register();
+
+// Create our environment
+$env = array('compress' => false, 'relativeUrls' => false);
+$silent = false;
+$watch = false;
+$rootpath = '';
+
+// Check for arguments
+array_shift($argv);
+if (!count($argv)) {
+ $argv[] = '-h';
+}
+
+// parse arguments
+foreach ($argv as $key => $arg) {
+ if (preg_match('/^--?([a-z][0-9a-z-]*)(?:=([^\s]+))?$/i', $arg, $matches)) {
+ $option = $matches[1];
+ $value = isset($matches[2]) ? $matches[2] : false;
+ unset($argv[$key]);
+
+ switch ($option) {
+ case 'h':
+ case 'help':
+ echo <<<EOD
+Usage: lessc [options] sources [destination]
+
+ -h, --help Print help (this message) and exit.
+ -s, --silent Suppress output of error messages.
+ -v, --version Print version number and exit.
+ -x, --compress Compress output by removing some whitespaces.
+ --include-path=PATHS Set include paths. Separated by `:'. Use `;' on Windows.
+ --strict-imports Force evaluation of imports.
+ -sm=on|off Turn on or off strict math, where in strict mode, math
+ --strict-math=on|off requires brackets. This option may default to on and then
+ be removed in the future.
+ -su=on|off Allow mixed units, e.g. 1px+1em or 1px*1px which have units
+ --strict-units=on|off that cannot be represented.
+ -ru, --relative-urls re-write relative urls to the base less file.
+ -rp, --rootpath=URL Set rootpath for url rewriting in relative imports and urls.
+ Works with or without the relative-urls option.
+ -w, --watch Watch input files for changes.
+
+
+EOD;
+ exit;
+ case 's':
+ case 'silent':
+ $silent = true;
+ break;
+
+ case 'w':
+ case 'watch':
+ $watch = true;
+ break;
+
+ case 'v':
+ case 'version':
+ echo "lessc " . Less_Version::version . " (less.php)\n\n";
+ exit;
+
+ case 'rp':
+ case 'rootpath':
+ $rootpath = $value;
+ break;
+
+
+ //parser options
+ case 'compress':
+ $env['compress'] = true;
+ break;
+
+ case 'ru':
+ case 'relative-urls':
+ $env['relativeUrls'] = true;
+ break;
+
+ case 'su':
+ case 'strict-units':
+ $env['strictUnits'] = ($value === 'on');
+ break;
+
+ case 'sm':
+ case 'strict-math':
+ $env['strictMath'] = ($value === 'on');
+ break;
+
+ case 'x':
+ case 'include-path':
+ $env['import_dirs'] = preg_split('#;|\:#', $value);
+ break;
+
+ }
+ }
+}
+
+if (count($argv) > 1) {
+ $output = array_pop($argv);
+ $inputs = $argv;
+}
+else {
+ $inputs = $argv;
+ $output = false;
+}
+
+if (!count($inputs)) {
+ echo("lessc: no input files\n");
+ exit;
+}
+
+if ($watch) {
+ if (!$output) {
+ echo("lessc: you must specify the output file if --watch is given\n");
+ exit;
+ }
+
+ $lastAction = 0;
+
+ echo("lessc: watching input files\n");
+
+ while (1) {
+ clearstatcache();
+
+ $updated = false;
+ foreach ($inputs as $input) {
+ if ($input == '-') {
+ if (count($inputs) == 1) {
+ echo("lessc: during watching files is not possible to watch stdin\n");
+ exit;
+ }
+ else {
+ continue;
+ }
+ }
+
+ if (filemtime($input) > $lastAction) {
+ $updated = true;
+ break;
+ }
+ }
+
+ if ($updated) {
+ $lastAction = time();
+ $parser = new Less_Parser($env);
+ foreach ($inputs as $input) {
+ try {
+ $parser->parseFile($input, $rootpath);
+ }
+ catch (Exception $e) {
+ echo("lessc: " . $e->getMessage() . " \n");
+ continue; // Invalid processing
+ }
+ }
+
+ file_put_contents($output, $parser->getCss());
+ echo("lessc: output file recompiled\n");
+ }
+
+ sleep(1);
+ }
+}
+else {
+ $parser = new Less_Parser($env);
+ foreach ($inputs as $input) {
+ if ($input == '-') {
+ $content = file_get_contents('php://stdin');
+ $parser->parse($content);
+ }
+ else {
+ try {
+ $parser->parseFile($input);
+ }
+ catch (Exception $e) {
+ if (!$silent) {
+ echo("lessc: " . ((string)$e) . " \n");
+ }
+ }
+ }
+ }
+
+ if ($output) {
+ file_put_contents($output, $parser->getCss());
+ }
+ else {
+ echo $parser->getCss();
+ }
+}
diff --git a/library/vendor/lessphp/composer.json b/library/vendor/lessphp/composer.json
new file mode 100644
index 0000000..c349e90
--- /dev/null
+++ b/library/vendor/lessphp/composer.json
@@ -0,0 +1,52 @@
+{
+ "name": "wikimedia/less.php",
+ "description": "PHP port of the LESS processor",
+ "keywords": [ "less", "css", "php", "stylesheet", "less.js", "lesscss" ],
+ "license": "Apache-2.0",
+ "authors": [
+ {
+ "name": "Josh Schmidt",
+ "homepage": "https://github.com/oyejorge"
+ },
+ {
+ "name": "Matt Agar",
+ "homepage": "https://github.com/agar"
+ },
+ {
+ "name": "Martin JantoÅ¡oviÄ",
+ "homepage": "https://github.com/Mordred"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.9"
+ },
+ "require-dev": {
+ "mediawiki/mediawiki-codesniffer": "34.0.0",
+ "mediawiki/mediawiki-phan-config": "0.10.6",
+ "mediawiki/minus-x": "1.0.0",
+ "php-parallel-lint/php-console-highlighter": "0.5.0",
+ "php-parallel-lint/php-parallel-lint": "1.2.0",
+ "phpunit/phpunit": "^8.5"
+ },
+ "scripts": {
+ "test": [
+ "parallel-lint . --exclude vendor",
+ "phpcs -sp",
+ "phpunit",
+ "minus-x check ."
+ ],
+ "cover": "phpunit --coverage-text --coverage-html coverage/ --coverage-clover coverage/clover.xml",
+ "fix": [
+ "minus-x fix .",
+ "phpcbf"
+ ],
+ "phan": "phan --allow-polyfill-parser --no-progress-bar"
+ },
+ "autoload": {
+ "psr-0": { "Less": "lib/" },
+ "classmap": ["lessc.inc.php"]
+ },
+ "bin": [
+ "bin/lessc"
+ ]
+}
diff --git a/library/vendor/lessphp/lessc.inc.php b/library/vendor/lessphp/lessc.inc.php
new file mode 100644
index 0000000..284445f
--- /dev/null
+++ b/library/vendor/lessphp/lessc.inc.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * This file provides the part of lessphp API (https://github.com/leafo/lessphp)
+ * to be a drop-in replacement for following products:
+ * - Drupal 7, by the less module v3.0+ (https://drupal.org/project/less)
+ * - Symfony 2
+ */
+
+// Register autoloader for non-composer installations
+if ( !class_exists( 'Less_Parser' ) ) {
+ require_once __DIR__ . '/lib/Less/Autoloader.php';
+ Less_Autoloader::register();
+}
+
+class lessc {
+
+ static public $VERSION = Less_Version::less_version;
+
+ public $importDir = '';
+ protected $allParsedFiles = array();
+ protected $libFunctions = array();
+ protected $registeredVars = array();
+ private $formatterName;
+ private $options = array();
+
+ public function __construct( $lessc = null, $sourceName = null ) {
+ }
+
+ public function setImportDir( $dirs ) {
+ $this->importDir = (array)$dirs;
+ }
+
+ public function addImportDir( $dir ) {
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $dir;
+ }
+
+ public function setFormatter( $name ) {
+ $this->formatterName = $name;
+ }
+
+ public function setPreserveComments( $preserve ) {
+ }
+
+ public function registerFunction( $name, $func ) {
+ $this->libFunctions[$name] = $func;
+ }
+
+ public function unregisterFunction( $name ) {
+ unset( $this->libFunctions[$name] );
+ }
+
+ public function setVariables( $variables ) {
+ foreach ( $variables as $name => $value ) {
+ $this->setVariable( $name, $value );
+ }
+ }
+
+ public function setVariable( $name, $value ) {
+ $this->registeredVars[$name] = $value;
+ }
+
+ public function unsetVariable( $name ) {
+ unset( $this->registeredVars[$name] );
+ }
+
+ public function setOptions( $options ) {
+ foreach ( $options as $name => $value ) {
+ $this->setOption( $name, $value );
+ }
+ }
+
+ public function setOption( $name, $value ) {
+ $this->options[$name] = $value;
+ }
+
+ public function parse( $buffer, $presets = array() ) {
+ $this->setVariables( $presets );
+
+ $parser = new Less_Parser( $this->getOptions() );
+ $parser->setImportDirs( $this->getImportDirs() );
+ foreach ( $this->libFunctions as $name => $func ) {
+ $parser->registerFunction( $name, $func );
+ }
+ $parser->parse( $buffer );
+ if ( count( $this->registeredVars ) ) {
+ $parser->ModifyVars( $this->registeredVars );
+ }
+
+ return $parser->getCss();
+ }
+
+ protected function getOptions() {
+ $options = array( 'relativeUrls' => false );
+ switch ( $this->formatterName ) {
+ case 'compressed':
+ $options['compress'] = true;
+ break;
+ }
+ if ( is_array( $this->options ) ) {
+ $options = array_merge( $options, $this->options );
+ }
+ return $options;
+ }
+
+ protected function getImportDirs() {
+ $dirs_ = (array)$this->importDir;
+ $dirs = array();
+ foreach ( $dirs_ as $dir ) {
+ $dirs[$dir] = '';
+ }
+ return $dirs;
+ }
+
+ public function compile( $string, $name = null ) {
+ $oldImport = $this->importDir;
+ $this->importDir = (array)$this->importDir;
+
+ $this->allParsedFiles = array();
+
+ $parser = new Less_Parser( $this->getOptions() );
+ $parser->SetImportDirs( $this->getImportDirs() );
+ if ( count( $this->registeredVars ) ) {
+ $parser->ModifyVars( $this->registeredVars );
+ }
+ foreach ( $this->libFunctions as $name => $func ) {
+ $parser->registerFunction( $name, $func );
+ }
+ $parser->parse( $string );
+ $out = $parser->getCss();
+
+ $parsed = Less_Parser::AllParsedFiles();
+ foreach ( $parsed as $file ) {
+ $this->addParsedFile( $file );
+ }
+
+ $this->importDir = $oldImport;
+
+ return $out;
+ }
+
+ public function compileFile( $fname, $outFname = null ) {
+ if ( !is_readable( $fname ) ) {
+ throw new Exception( 'load error: failed to find '.$fname );
+ }
+
+ $pi = pathinfo( $fname );
+
+ $oldImport = $this->importDir;
+
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = Less_Parser::AbsPath( $pi['dirname'] ).'/';
+
+ $this->allParsedFiles = array();
+ $this->addParsedFile( $fname );
+
+ $parser = new Less_Parser( $this->getOptions() );
+ $parser->SetImportDirs( $this->getImportDirs() );
+ if ( count( $this->registeredVars ) ) {
+ $parser->ModifyVars( $this->registeredVars );
+ }
+ foreach ( $this->libFunctions as $name => $func ) {
+ $parser->registerFunction( $name, $func );
+ }
+ $parser->parseFile( $fname );
+ $out = $parser->getCss();
+
+ $parsed = Less_Parser::AllParsedFiles();
+ foreach ( $parsed as $file ) {
+ $this->addParsedFile( $file );
+ }
+
+ $this->importDir = $oldImport;
+
+ if ( $outFname !== null ) {
+ return file_put_contents( $outFname, $out );
+ }
+
+ return $out;
+ }
+
+ public function checkedCompile( $in, $out ) {
+ if ( !is_file( $out ) || filemtime( $in ) > filemtime( $out ) ) {
+ $this->compileFile( $in, $out );
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Execute lessphp on a .less file or a lessphp cache structure
+ *
+ * The lessphp cache structure contains information about a specific
+ * less file having been parsed. It can be used as a hint for future
+ * calls to determine whether or not a rebuild is required.
+ *
+ * The cache structure contains two important keys that may be used
+ * externally:
+ *
+ * compiled: The final compiled CSS
+ * updated: The time (in seconds) the CSS was last compiled
+ *
+ * The cache structure is a plain-ol' PHP associative array and can
+ * be serialized and unserialized without a hitch.
+ *
+ * @param mixed $in Input
+ * @param bool $force Force rebuild?
+ * @return array lessphp cache structure
+ */
+ public function cachedCompile( $in, $force = false ) {
+ // assume no root
+ $root = null;
+
+ if ( is_string( $in ) ) {
+ $root = $in;
+ } elseif ( is_array( $in ) and isset( $in['root'] ) ) {
+ if ( $force or !isset( $in['files'] ) ) {
+ // If we are forcing a recompile or if for some reason the
+ // structure does not contain any file information we should
+ // specify the root to trigger a rebuild.
+ $root = $in['root'];
+ } elseif ( isset( $in['files'] ) and is_array( $in['files'] ) ) {
+ foreach ( $in['files'] as $fname => $ftime ) {
+ if ( !file_exists( $fname ) or filemtime( $fname ) > $ftime ) {
+ // One of the files we knew about previously has changed
+ // so we should look at our incoming root again.
+ $root = $in['root'];
+ break;
+ }
+ }
+ }
+ } else {
+ // TODO: Throw an exception? We got neither a string nor something
+ // that looks like a compatible lessphp cache structure.
+ return null;
+ }
+
+ if ( $root !== null ) {
+ // If we have a root value which means we should rebuild.
+ $out = array();
+ $out['root'] = $root;
+ $out['compiled'] = $this->compileFile( $root );
+ $out['files'] = $this->allParsedFiles();
+ $out['updated'] = time();
+ return $out;
+ } else {
+ // No changes, pass back the structure
+ // we were given initially.
+ return $in;
+ }
+ }
+
+ public function ccompile( $in, $out, $less = null ) {
+ if ( $less === null ) {
+ $less = new self;
+ }
+ return $less->checkedCompile( $in, $out );
+ }
+
+ public static function cexecute( $in, $force = false, $less = null ) {
+ if ( $less === null ) {
+ $less = new self;
+ }
+ return $less->cachedCompile( $in, $force );
+ }
+
+ public function allParsedFiles() {
+ return $this->allParsedFiles;
+ }
+
+ protected function addParsedFile( $file ) {
+ $this->allParsedFiles[Less_Parser::AbsPath( $file )] = filemtime( $file );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Autoloader.php b/library/vendor/lessphp/lib/Less/Autoloader.php
new file mode 100644
index 0000000..00116f7
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Autoloader.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Autoloader
+ *
+ * @package Less
+ * @subpackage autoload
+ */
+class Less_Autoloader {
+
+ /**
+ * Registered flag
+ *
+ * @var boolean
+ */
+ protected static $registered = false;
+
+ /**
+ * Library directory
+ *
+ * @var string
+ */
+ protected static $libDir;
+
+ /**
+ * Register the autoloader in the spl autoloader
+ *
+ * @return void
+ * @throws Exception If there was an error in registration
+ */
+ public static function register() {
+ if ( self::$registered ) {
+ return;
+ }
+
+ self::$libDir = dirname( __FILE__ );
+
+ if ( false === spl_autoload_register( array( 'Less_Autoloader', 'loadClass' ) ) ) {
+ throw new Exception( 'Unable to register Less_Autoloader::loadClass as an autoloading method.' );
+ }
+
+ self::$registered = true;
+ }
+
+ /**
+ * Unregisters the autoloader
+ *
+ * @return void
+ */
+ public static function unregister() {
+ spl_autoload_unregister( array( 'Less_Autoloader', 'loadClass' ) );
+ self::$registered = false;
+ }
+
+ /**
+ * Loads the class
+ *
+ * @param string $className The class to load
+ */
+ public static function loadClass( $className ) {
+ // handle only package classes
+ if ( strpos( $className, 'Less_' ) !== 0 ) {
+ return;
+ }
+
+ $className = substr( $className, 5 );
+ $fileName = self::$libDir . DIRECTORY_SEPARATOR . str_replace( '_', DIRECTORY_SEPARATOR, $className ) . '.php';
+
+ if ( file_exists( $fileName ) ) {
+ require $fileName;
+ return true;
+ } else {
+ throw new Exception( 'file not loadable '.$fileName );
+ }
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Cache.php b/library/vendor/lessphp/lib/Less/Cache.php
new file mode 100644
index 0000000..ce09eea
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Cache.php
@@ -0,0 +1,290 @@
+<?php
+
+require_once dirname( __FILE__ ).'/Version.php';
+
+/**
+ * Utility for handling the generation and caching of css files
+ *
+ * @package Less
+ * @subpackage cache
+ */
+class Less_Cache {
+
+ // directory less.php can use for storing data
+ public static $cache_dir = false;
+
+ // prefix for the storing data
+ public static $prefix = 'lessphp_';
+
+ // prefix for the storing vars
+ public static $prefix_vars = 'lessphpvars_';
+
+ // specifies the number of seconds after which data created by less.php will be seen as 'garbage' and potentially cleaned up
+ public static $gc_lifetime = 604800;
+
+ /**
+ * Save and reuse the results of compiled less files.
+ * The first call to Get() will generate css and save it.
+ * Subsequent calls to Get() with the same arguments will return the same css filename
+ *
+ * @param array $less_files Array of .less files to compile
+ * @param array $parser_options Array of compiler options
+ * @param array $modify_vars Array of variables
+ * @return string Name of the css file
+ */
+ public static function Get( $less_files, $parser_options = array(), $modify_vars = array() ) {
+ // check $cache_dir
+ if ( isset( $parser_options['cache_dir'] ) ) {
+ Less_Cache::$cache_dir = $parser_options['cache_dir'];
+ }
+
+ if ( empty( Less_Cache::$cache_dir ) ) {
+ throw new Exception( 'cache_dir not set' );
+ }
+
+ if ( isset( $parser_options['prefix'] ) ) {
+ Less_Cache::$prefix = $parser_options['prefix'];
+ }
+
+ if ( empty( Less_Cache::$prefix ) ) {
+ throw new Exception( 'prefix not set' );
+ }
+
+ if ( isset( $parser_options['prefix_vars'] ) ) {
+ Less_Cache::$prefix_vars = $parser_options['prefix_vars'];
+ }
+
+ if ( empty( Less_Cache::$prefix_vars ) ) {
+ throw new Exception( 'prefix_vars not set' );
+ }
+
+ self::CheckCacheDir();
+ $less_files = (array)$less_files;
+
+ // create a file for variables
+ if ( !empty( $modify_vars ) ) {
+ $lessvars = Less_Parser::serializeVars( $modify_vars );
+ $vars_file = Less_Cache::$cache_dir . Less_Cache::$prefix_vars . sha1( $lessvars ) . '.less';
+
+ if ( !file_exists( $vars_file ) ) {
+ file_put_contents( $vars_file, $lessvars );
+ }
+
+ $less_files += array( $vars_file => '/' );
+ }
+
+ // generate name for compiled css file
+ $hash = md5( json_encode( $less_files ) );
+ $list_file = Less_Cache::$cache_dir . Less_Cache::$prefix . $hash . '.list';
+
+ // check cached content
+ if ( !isset( $parser_options['use_cache'] ) || $parser_options['use_cache'] === true ) {
+ if ( file_exists( $list_file ) ) {
+
+ self::ListFiles( $list_file, $list, $cached_name );
+ $compiled_name = self::CompiledName( $list, $hash );
+
+ // if $cached_name is the same as the $compiled name, don't regenerate
+ if ( !$cached_name || $cached_name === $compiled_name ) {
+
+ $output_file = self::OutputFile( $compiled_name, $parser_options );
+
+ if ( $output_file && file_exists( $output_file ) ) {
+ @touch( $list_file );
+ return basename( $output_file ); // for backwards compatibility, we just return the name of the file
+ }
+ }
+ }
+ }
+
+ $compiled = self::Cache( $less_files, $parser_options );
+ if ( !$compiled ) {
+ return false;
+ }
+
+ $compiled_name = self::CompiledName( $less_files, $hash );
+ $output_file = self::OutputFile( $compiled_name, $parser_options );
+
+ // save the file list
+ $list = $less_files;
+ $list[] = $compiled_name;
+ $cache = implode( "\n", $list );
+ file_put_contents( $list_file, $cache );
+
+ // save the css
+ file_put_contents( $output_file, $compiled );
+
+ // clean up
+ self::CleanCache();
+
+ return basename( $output_file );
+ }
+
+ /**
+ * Force the compiler to regenerate the cached css file
+ *
+ * @param array $less_files Array of .less files to compile
+ * @param array $parser_options Array of compiler options
+ * @param array $modify_vars Array of variables
+ * @return string Name of the css file
+ */
+ public static function Regen( $less_files, $parser_options = array(), $modify_vars = array() ) {
+ $parser_options['use_cache'] = false;
+ return self::Get( $less_files, $parser_options, $modify_vars );
+ }
+
+ public static function Cache( &$less_files, $parser_options = array() ) {
+ // get less.php if it exists
+ $file = dirname( __FILE__ ) . '/Less.php';
+ if ( file_exists( $file ) && !class_exists( 'Less_Parser' ) ) {
+ require_once $file;
+ }
+
+ $parser_options['cache_dir'] = Less_Cache::$cache_dir;
+ $parser = new Less_Parser( $parser_options );
+
+ // combine files
+ foreach ( $less_files as $file_path => $uri_or_less ) {
+
+ // treat as less markup if there are newline characters
+ if ( strpos( $uri_or_less, "\n" ) !== false ) {
+ $parser->Parse( $uri_or_less );
+ continue;
+ }
+
+ $parser->ParseFile( $file_path, $uri_or_less );
+ }
+
+ $compiled = $parser->getCss();
+
+ $less_files = $parser->allParsedFiles();
+
+ return $compiled;
+ }
+
+ private static function OutputFile( $compiled_name, $parser_options ) {
+ // custom output file
+ if ( !empty( $parser_options['output'] ) ) {
+
+ // relative to cache directory?
+ if ( preg_match( '#[\\\\/]#', $parser_options['output'] ) ) {
+ return $parser_options['output'];
+ }
+
+ return Less_Cache::$cache_dir.$parser_options['output'];
+ }
+
+ return Less_Cache::$cache_dir.$compiled_name;
+ }
+
+ private static function CompiledName( $files, $extrahash ) {
+ // save the file list
+ $temp = array( Less_Version::cache_version );
+ foreach ( $files as $file ) {
+ $temp[] = filemtime( $file )."\t".filesize( $file )."\t".$file;
+ }
+
+ return Less_Cache::$prefix.sha1( json_encode( $temp ).$extrahash ).'.css';
+ }
+
+ public static function SetCacheDir( $dir ) {
+ Less_Cache::$cache_dir = $dir;
+ self::CheckCacheDir();
+ }
+
+ public static function CheckCacheDir() {
+ Less_Cache::$cache_dir = str_replace( '\\', '/', Less_Cache::$cache_dir );
+ Less_Cache::$cache_dir = rtrim( Less_Cache::$cache_dir, '/' ).'/';
+
+ if ( !file_exists( Less_Cache::$cache_dir ) ) {
+ if ( !mkdir( Less_Cache::$cache_dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.Less_Cache::$cache_dir );
+ }
+
+ } elseif ( !is_dir( Less_Cache::$cache_dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.Less_Cache::$cache_dir );
+
+ } elseif ( !is_writable( Less_Cache::$cache_dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.Less_Cache::$cache_dir );
+
+ }
+
+ }
+
+ /**
+ * Delete unused less.php files
+ */
+ public static function CleanCache() {
+ static $clean = false;
+
+ if ( $clean || empty( Less_Cache::$cache_dir ) ) {
+ return;
+ }
+
+ $clean = true;
+
+ // only remove files with extensions created by less.php
+ // css files removed based on the list files
+ $remove_types = array( 'lesscache' => 1,'list' => 1,'less' => 1,'map' => 1 );
+
+ $files = scandir( Less_Cache::$cache_dir );
+ if ( !$files ) {
+ return;
+ }
+
+ $check_time = time() - self::$gc_lifetime;
+ foreach ( $files as $file ) {
+
+ // don't delete if the file wasn't created with less.php
+ if ( strpos( $file, Less_Cache::$prefix ) !== 0 ) {
+ continue;
+ }
+
+ $parts = explode( '.', $file );
+ $type = array_pop( $parts );
+
+ if ( !isset( $remove_types[$type] ) ) {
+ continue;
+ }
+
+ $full_path = Less_Cache::$cache_dir . $file;
+ $mtime = filemtime( $full_path );
+
+ // don't delete if it's a relatively new file
+ if ( $mtime > $check_time ) {
+ continue;
+ }
+
+ // delete the list file and associated css file
+ if ( $type === 'list' ) {
+ self::ListFiles( $full_path, $list, $css_file_name );
+ if ( $css_file_name ) {
+ $css_file = Less_Cache::$cache_dir . $css_file_name;
+ if ( file_exists( $css_file ) ) {
+ unlink( $css_file );
+ }
+ }
+ }
+
+ unlink( $full_path );
+ }
+
+ }
+
+ /**
+ * Get the list of less files and generated css file from a list file
+ */
+ static function ListFiles( $list_file, &$list, &$css_file_name ) {
+ $list = explode( "\n", file_get_contents( $list_file ) );
+
+ // pop the cached name that should match $compiled_name
+ $css_file_name = array_pop( $list );
+
+ if ( !preg_match( '/^' . Less_Cache::$prefix . '[a-f0-9]+\.css$/', $css_file_name ) ) {
+ $list[] = $css_file_name;
+ $css_file_name = false;
+ }
+
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Colors.php b/library/vendor/lessphp/lib/Less/Colors.php
new file mode 100644
index 0000000..9be76cb
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Colors.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * Utility for css colors
+ *
+ * @package Less
+ * @subpackage color
+ */
+class Less_Colors {
+
+ public static $colors = array(
+ 'aliceblue' => '#f0f8ff',
+ 'antiquewhite' => '#faebd7',
+ 'aqua' => '#00ffff',
+ 'aquamarine' => '#7fffd4',
+ 'azure' => '#f0ffff',
+ 'beige' => '#f5f5dc',
+ 'bisque' => '#ffe4c4',
+ 'black' => '#000000',
+ 'blanchedalmond' => '#ffebcd',
+ 'blue' => '#0000ff',
+ 'blueviolet' => '#8a2be2',
+ 'brown' => '#a52a2a',
+ 'burlywood' => '#deb887',
+ 'cadetblue' => '#5f9ea0',
+ 'chartreuse' => '#7fff00',
+ 'chocolate' => '#d2691e',
+ 'coral' => '#ff7f50',
+ 'cornflowerblue' => '#6495ed',
+ 'cornsilk' => '#fff8dc',
+ 'crimson' => '#dc143c',
+ 'cyan' => '#00ffff',
+ 'darkblue' => '#00008b',
+ 'darkcyan' => '#008b8b',
+ 'darkgoldenrod' => '#b8860b',
+ 'darkgray' => '#a9a9a9',
+ 'darkgrey' => '#a9a9a9',
+ 'darkgreen' => '#006400',
+ 'darkkhaki' => '#bdb76b',
+ 'darkmagenta' => '#8b008b',
+ 'darkolivegreen' => '#556b2f',
+ 'darkorange' => '#ff8c00',
+ 'darkorchid' => '#9932cc',
+ 'darkred' => '#8b0000',
+ 'darksalmon' => '#e9967a',
+ 'darkseagreen' => '#8fbc8f',
+ 'darkslateblue' => '#483d8b',
+ 'darkslategray' => '#2f4f4f',
+ 'darkslategrey' => '#2f4f4f',
+ 'darkturquoise' => '#00ced1',
+ 'darkviolet' => '#9400d3',
+ 'deeppink' => '#ff1493',
+ 'deepskyblue' => '#00bfff',
+ 'dimgray' => '#696969',
+ 'dimgrey' => '#696969',
+ 'dodgerblue' => '#1e90ff',
+ 'firebrick' => '#b22222',
+ 'floralwhite' => '#fffaf0',
+ 'forestgreen' => '#228b22',
+ 'fuchsia' => '#ff00ff',
+ 'gainsboro' => '#dcdcdc',
+ 'ghostwhite' => '#f8f8ff',
+ 'gold' => '#ffd700',
+ 'goldenrod' => '#daa520',
+ 'gray' => '#808080',
+ 'grey' => '#808080',
+ 'green' => '#008000',
+ 'greenyellow' => '#adff2f',
+ 'honeydew' => '#f0fff0',
+ 'hotpink' => '#ff69b4',
+ 'indianred' => '#cd5c5c',
+ 'indigo' => '#4b0082',
+ 'ivory' => '#fffff0',
+ 'khaki' => '#f0e68c',
+ 'lavender' => '#e6e6fa',
+ 'lavenderblush' => '#fff0f5',
+ 'lawngreen' => '#7cfc00',
+ 'lemonchiffon' => '#fffacd',
+ 'lightblue' => '#add8e6',
+ 'lightcoral' => '#f08080',
+ 'lightcyan' => '#e0ffff',
+ 'lightgoldenrodyellow' => '#fafad2',
+ 'lightgray' => '#d3d3d3',
+ 'lightgrey' => '#d3d3d3',
+ 'lightgreen' => '#90ee90',
+ 'lightpink' => '#ffb6c1',
+ 'lightsalmon' => '#ffa07a',
+ 'lightseagreen' => '#20b2aa',
+ 'lightskyblue' => '#87cefa',
+ 'lightslategray' => '#778899',
+ 'lightslategrey' => '#778899',
+ 'lightsteelblue' => '#b0c4de',
+ 'lightyellow' => '#ffffe0',
+ 'lime' => '#00ff00',
+ 'limegreen' => '#32cd32',
+ 'linen' => '#faf0e6',
+ 'magenta' => '#ff00ff',
+ 'maroon' => '#800000',
+ 'mediumaquamarine' => '#66cdaa',
+ 'mediumblue' => '#0000cd',
+ 'mediumorchid' => '#ba55d3',
+ 'mediumpurple' => '#9370d8',
+ 'mediumseagreen' => '#3cb371',
+ 'mediumslateblue' => '#7b68ee',
+ 'mediumspringgreen' => '#00fa9a',
+ 'mediumturquoise' => '#48d1cc',
+ 'mediumvioletred' => '#c71585',
+ 'midnightblue' => '#191970',
+ 'mintcream' => '#f5fffa',
+ 'mistyrose' => '#ffe4e1',
+ 'moccasin' => '#ffe4b5',
+ 'navajowhite' => '#ffdead',
+ 'navy' => '#000080',
+ 'oldlace' => '#fdf5e6',
+ 'olive' => '#808000',
+ 'olivedrab' => '#6b8e23',
+ 'orange' => '#ffa500',
+ 'orangered' => '#ff4500',
+ 'orchid' => '#da70d6',
+ 'palegoldenrod' => '#eee8aa',
+ 'palegreen' => '#98fb98',
+ 'paleturquoise' => '#afeeee',
+ 'palevioletred' => '#d87093',
+ 'papayawhip' => '#ffefd5',
+ 'peachpuff' => '#ffdab9',
+ 'peru' => '#cd853f',
+ 'pink' => '#ffc0cb',
+ 'plum' => '#dda0dd',
+ 'powderblue' => '#b0e0e6',
+ 'purple' => '#800080',
+ 'red' => '#ff0000',
+ 'rosybrown' => '#bc8f8f',
+ 'royalblue' => '#4169e1',
+ 'saddlebrown' => '#8b4513',
+ 'salmon' => '#fa8072',
+ 'sandybrown' => '#f4a460',
+ 'seagreen' => '#2e8b57',
+ 'seashell' => '#fff5ee',
+ 'sienna' => '#a0522d',
+ 'silver' => '#c0c0c0',
+ 'skyblue' => '#87ceeb',
+ 'slateblue' => '#6a5acd',
+ 'slategray' => '#708090',
+ 'slategrey' => '#708090',
+ 'snow' => '#fffafa',
+ 'springgreen' => '#00ff7f',
+ 'steelblue' => '#4682b4',
+ 'tan' => '#d2b48c',
+ 'teal' => '#008080',
+ 'thistle' => '#d8bfd8',
+ 'tomato' => '#ff6347',
+ 'turquoise' => '#40e0d0',
+ 'violet' => '#ee82ee',
+ 'wheat' => '#f5deb3',
+ 'white' => '#ffffff',
+ 'whitesmoke' => '#f5f5f5',
+ 'yellow' => '#ffff00',
+ 'yellowgreen' => '#9acd32'
+ );
+
+ public static function hasOwnProperty( $color ) {
+ return isset( self::$colors[$color] );
+ }
+
+ public static function color( $color ) {
+ return self::$colors[$color];
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Configurable.php b/library/vendor/lessphp/lib/Less/Configurable.php
new file mode 100644
index 0000000..f431810
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Configurable.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Configurable
+ *
+ * @package Less
+ * @subpackage Core
+ */
+abstract class Less_Configurable {
+
+ /**
+ * Array of options
+ *
+ * @var array
+ */
+ protected $options = array();
+
+ /**
+ * Array of default options
+ *
+ * @var array
+ */
+ protected $defaultOptions = array();
+
+ /**
+ * Set options
+ *
+ * If $options is an object it will be converted into an array by called
+ * it's toArray method.
+ *
+ * @throws Exception
+ * @param array|object $options
+ */
+ public function setOptions( $options ) {
+ $options = array_intersect_key( $options, $this->defaultOptions );
+ $this->options = array_merge( $this->defaultOptions, $this->options, $options );
+ }
+
+ /**
+ * Get an option value by name
+ *
+ * If the option is empty or not set a NULL value will be returned.
+ *
+ * @param string $name
+ * @param mixed $default Default value if confiuration of $name is not present
+ * @return mixed
+ */
+ public function getOption( $name, $default = null ) {
+ if ( isset( $this->options[$name] ) ) {
+ return $this->options[$name];
+ }
+ return $default;
+ }
+
+ /**
+ * Set an option
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function setOption( $name, $value ) {
+ $this->options[$name] = $value;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Environment.php b/library/vendor/lessphp/lib/Less/Environment.php
new file mode 100644
index 0000000..5eddc08
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Environment.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * Environment
+ *
+ * @package Less
+ * @subpackage environment
+ */
+class Less_Environment {
+
+ // public $paths = array(); // option - unmodified - paths to search for imports on
+ //public static $files = array(); // list of files that have been imported, used for import-once
+ //public $rootpath; // option - rootpath to append to URL's
+ //public static $strictImports = null; // option -
+ //public $insecure; // option - whether to allow imports from insecure ssl hosts
+ //public $processImports; // option - whether to process imports. if false then imports will not be imported
+ //public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true
+ //public $useFileCache; // browser only - whether to use the per file session cache
+ public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc.
+
+ public $importMultiple = false; // whether we are currently importing multiple copies
+
+ /**
+ * @var array
+ */
+ public $frames = array();
+
+ /**
+ * @var array
+ */
+ public $mediaBlocks = array();
+
+ /**
+ * @var array
+ */
+ public $mediaPath = array();
+
+ public static $parensStack = 0;
+
+ public static $tabLevel = 0;
+
+ public static $lastRule = false;
+
+ public static $_outputMap;
+
+ public static $mixin_stack = 0;
+
+ /**
+ * @var array
+ */
+ public $functions = array();
+
+ public function Init() {
+ self::$parensStack = 0;
+ self::$tabLevel = 0;
+ self::$lastRule = false;
+ self::$mixin_stack = 0;
+
+ if ( Less_Parser::$options['compress'] ) {
+
+ Less_Environment::$_outputMap = array(
+ ',' => ',',
+ ': ' => ':',
+ '' => '',
+ ' ' => ' ',
+ ':' => ' :',
+ '+' => '+',
+ '~' => '~',
+ '>' => '>',
+ '|' => '|',
+ '^' => '^',
+ '^^' => '^^'
+ );
+
+ } else {
+
+ Less_Environment::$_outputMap = array(
+ ',' => ', ',
+ ': ' => ': ',
+ '' => '',
+ ' ' => ' ',
+ ':' => ' :',
+ '+' => ' + ',
+ '~' => ' ~ ',
+ '>' => ' > ',
+ '|' => '|',
+ '^' => ' ^ ',
+ '^^' => ' ^^ '
+ );
+
+ }
+ }
+
+ public function copyEvalEnv( $frames = array() ) {
+ $new_env = new Less_Environment();
+ $new_env->frames = $frames;
+ return $new_env;
+ }
+
+ public static function isMathOn() {
+ return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack;
+ }
+
+ public static function isPathRelative( $path ) {
+ return !preg_match( '/^(?:[a-z-]+:|\/)/', $path );
+ }
+
+ /**
+ * Canonicalize a path by resolving references to '/./', '/../'
+ * Does not remove leading "../"
+ * @param string $path or url
+ * @return string Canonicalized path
+ */
+ public static function normalizePath( $path ) {
+ $segments = explode( '/', $path );
+ $segments = array_reverse( $segments );
+
+ $path = array();
+ $path_len = 0;
+
+ while ( $segments ) {
+ $segment = array_pop( $segments );
+ switch ( $segment ) {
+
+ case '.':
+ break;
+
+ case '..':
+ if ( !$path_len || ( $path[$path_len - 1] === '..' ) ) {
+ $path[] = $segment;
+ $path_len++;
+ } else {
+ array_pop( $path );
+ $path_len--;
+ }
+ break;
+
+ default:
+ $path[] = $segment;
+ $path_len++;
+ break;
+ }
+ }
+
+ return implode( '/', $path );
+ }
+
+ public function unshiftFrame( $frame ) {
+ array_unshift( $this->frames, $frame );
+ }
+
+ public function shiftFrame() {
+ return array_shift( $this->frames );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Exception/Chunk.php b/library/vendor/lessphp/lib/Less/Exception/Chunk.php
new file mode 100644
index 0000000..2458cc5
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Exception/Chunk.php
@@ -0,0 +1,200 @@
+<?php
+
+/**
+ * Chunk Exception
+ *
+ * @package Less
+ * @subpackage exception
+ */
+class Less_Exception_Chunk extends Less_Exception_Parser {
+
+ protected $parserCurrentIndex = 0;
+
+ protected $emitFrom = 0;
+
+ protected $input_len;
+
+ /**
+ * @param string $input
+ * @param Exception|null $previous Previous exception
+ * @param int|null $index The current parser index
+ * @param array|null $currentFile The file
+ * @param int $code The exception code
+ */
+ public function __construct( $input, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
+ $this->message = 'ParseError: Unexpected input'; // default message
+
+ $this->index = $index;
+
+ $this->currentFile = $currentFile;
+
+ $this->input = $input;
+ $this->input_len = strlen( $input );
+
+ $this->Chunks();
+ $this->genMessage();
+ }
+
+ /**
+ * See less.js chunks()
+ * We don't actually need the chunks
+ */
+ protected function Chunks() {
+ $level = 0;
+ $parenLevel = 0;
+ $lastMultiCommentEndBrace = null;
+ $lastOpening = null;
+ $lastMultiComment = null;
+ $lastParen = null;
+
+ for ( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+ $cc = $this->CharCode( $this->parserCurrentIndex );
+ if ( ( ( $cc >= 97 ) && ( $cc <= 122 ) ) || ( $cc < 34 ) ) {
+ // a-z or whitespace
+ continue;
+ }
+
+ switch ( $cc ) {
+
+ // (
+ case 40:
+ $parenLevel++;
+ $lastParen = $this->parserCurrentIndex;
+ break;
+
+ // )
+ case 41:
+ $parenLevel--;
+ if ( $parenLevel < 0 ) {
+ return $this->fail( "missing opening `(`" );
+ }
+ break;
+
+ // ;
+ case 59:
+ // if (!$parenLevel) { $this->emitChunk(); }
+ break;
+
+ // {
+ case 123:
+ $level++;
+ $lastOpening = $this->parserCurrentIndex;
+ break;
+
+ // }
+ case 125:
+ $level--;
+ if ( $level < 0 ) {
+ return $this->fail( "missing opening `{`" );
+
+ }
+ // if (!$level && !$parenLevel) { $this->emitChunk(); }
+ break;
+ // \
+ case 92:
+ if ( $this->parserCurrentIndex < $this->input_len - 1 ) { $this->parserCurrentIndex++; break;
+ }
+ return $this->fail( "unescaped `\\`" );
+
+ // ", ' and `
+ case 34:
+ case 39:
+ case 96:
+ $matched = 0;
+ $currentChunkStartIndex = $this->parserCurrentIndex;
+ for ( $this->parserCurrentIndex += 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+ $cc2 = $this->CharCode( $this->parserCurrentIndex );
+ if ( $cc2 > 96 ) { continue;
+ }
+ if ( $cc2 == $cc ) { $matched = 1; break;
+ }
+ if ( $cc2 == 92 ) { // \
+ if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
+ return $this->fail( "unescaped `\\`" );
+ }
+ $this->parserCurrentIndex++;
+ }
+ }
+ if ( $matched ) { break;
+ }
+ return $this->fail( "unmatched `" . chr( $cc ) . "`", $currentChunkStartIndex );
+
+ // /, check for comment
+ case 47:
+ if ( $parenLevel || ( $this->parserCurrentIndex == $this->input_len - 1 ) ) { break;
+ }
+ $cc2 = $this->CharCode( $this->parserCurrentIndex + 1 );
+ if ( $cc2 == 47 ) {
+ // //, find lnfeed
+ for ( $this->parserCurrentIndex += 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ) {
+ $cc2 = $this->CharCode( $this->parserCurrentIndex );
+ if ( ( $cc2 <= 13 ) && ( ( $cc2 == 10 ) || ( $cc2 == 13 ) ) ) { break;
+ }
+ }
+ } else if ( $cc2 == 42 ) {
+ // /*, find */
+ $lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex;
+ for ( $this->parserCurrentIndex += 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++ ) {
+ $cc2 = $this->CharCode( $this->parserCurrentIndex );
+ if ( $cc2 == 125 ) { $lastMultiCommentEndBrace = $this->parserCurrentIndex;
+ }
+ if ( $cc2 != 42 ) { continue;
+ }
+ if ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) { break;
+ }
+ }
+ if ( $this->parserCurrentIndex == $this->input_len - 1 ) {
+ return $this->fail( "missing closing `*/`", $currentChunkStartIndex );
+ }
+ }
+ break;
+
+ // *, check for unmatched */
+ case 42:
+ if ( ( $this->parserCurrentIndex < $this->input_len - 1 ) && ( $this->CharCode( $this->parserCurrentIndex + 1 ) == 47 ) ) {
+ return $this->fail( "unmatched `/*`" );
+ }
+ break;
+ }
+ }
+
+ if ( $level !== 0 ) {
+ if ( ( $lastMultiComment > $lastOpening ) && ( $lastMultiCommentEndBrace > $lastMultiComment ) ) {
+ return $this->fail( "missing closing `}` or `*/`", $lastOpening );
+ } else {
+ return $this->fail( "missing closing `}`", $lastOpening );
+ }
+ } else if ( $parenLevel !== 0 ) {
+ return $this->fail( "missing closing `)`", $lastParen );
+ }
+
+ // chunk didn't fail
+
+ //$this->emitChunk(true);
+ }
+
+ public function CharCode( $pos ) {
+ return ord( $this->input[$pos] );
+ }
+
+ public function fail( $msg, $index = null ) {
+ if ( !$index ) {
+ $this->index = $this->parserCurrentIndex;
+ } else {
+ $this->index = $index;
+ }
+ $this->message = 'ParseError: '.$msg;
+ }
+
+ /*
+ function emitChunk( $force = false ){
+ $len = $this->parserCurrentIndex - $this->emitFrom;
+ if ((($len < 512) && !$force) || !$len) {
+ return;
+ }
+ $chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom );
+ $this->emitFrom = $this->parserCurrentIndex + 1;
+ }
+ */
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Exception/Compiler.php b/library/vendor/lessphp/lib/Less/Exception/Compiler.php
new file mode 100644
index 0000000..1c3727a
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Exception/Compiler.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Compiler Exception
+ *
+ * @package Less
+ * @subpackage exception
+ */
+class Less_Exception_Compiler extends Less_Exception_Parser {
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Exception/Parser.php b/library/vendor/lessphp/lib/Less/Exception/Parser.php
new file mode 100644
index 0000000..f0e3227
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Exception/Parser.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * Parser Exception
+ *
+ * @package Less
+ * @subpackage exception
+ */
+class Less_Exception_Parser extends Exception {
+
+ /**
+ * The current file
+ *
+ * @var array
+ */
+ public $currentFile;
+
+ /**
+ * The current parser index
+ *
+ * @var integer
+ */
+ public $index;
+
+ protected $input;
+
+ protected $details = array();
+
+ /**
+ * @param string $message
+ * @param Exception|null $previous Previous exception
+ * @param int|null $index The current parser index
+ * @param array|null $currentFile The file
+ * @param int $code The exception code
+ */
+ public function __construct( $message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0 ) {
+ parent::__construct( $message, $code, $previous );
+
+ $this->currentFile = $currentFile;
+ $this->index = $index;
+
+ $this->genMessage();
+ }
+
+ protected function getInput() {
+ if ( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists( $this->currentFile['filename'] ) ) {
+ $this->input = file_get_contents( $this->currentFile['filename'] );
+ }
+ }
+
+ /**
+ * Set a message based on the exception info
+ */
+ public function genMessage() {
+ if ( $this->currentFile && $this->currentFile['filename'] ) {
+ $this->message .= ' in '.basename( $this->currentFile['filename'] );
+ }
+
+ if ( $this->index !== null ) {
+ $this->getInput();
+ if ( $this->input ) {
+ $line = self::getLineNumber();
+ $this->message .= ' on line '.$line.', column '.self::getColumn();
+
+ $lines = explode( "\n", $this->input );
+
+ $count = count( $lines );
+ $start_line = max( 0, $line - 3 );
+ $last_line = min( $count, $start_line + 6 );
+ $num_len = strlen( $last_line );
+ for ( $i = $start_line; $i < $last_line; $i++ ) {
+ $this->message .= "\n".str_pad( $i + 1, $num_len, '0', STR_PAD_LEFT ).'| '.$lines[$i];
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Returns the line number the error was encountered
+ *
+ * @return integer
+ */
+ public function getLineNumber() {
+ if ( $this->index ) {
+ // https://bugs.php.net/bug.php?id=49790
+ if ( ini_get( "mbstring.func_overload" ) ) {
+ return substr_count( substr( $this->input, 0, $this->index ), "\n" ) + 1;
+ } else {
+ return substr_count( $this->input, "\n", 0, $this->index ) + 1;
+ }
+ }
+ return 1;
+ }
+
+ /**
+ * Returns the column the error was encountered
+ *
+ * @return integer
+ */
+ public function getColumn() {
+ $part = substr( $this->input, 0, $this->index );
+ $pos = strrpos( $part, "\n" );
+ return $this->index - $pos;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Functions.php b/library/vendor/lessphp/lib/Less/Functions.php
new file mode 100644
index 0000000..3794e86
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Functions.php
@@ -0,0 +1,1186 @@
+<?php
+
+/**
+ * Builtin functions
+ *
+ * @package Less
+ * @subpackage function
+ * @see https://lesscss.org/functions/
+ */
+class Less_Functions {
+
+ public $env;
+ public $currentFileInfo;
+
+ function __construct( $env, $currentFileInfo = null ) {
+ $this->env = $env;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @param string $op
+ */
+ public static function operate( $op, $a, $b ) {
+ switch ( $op ) {
+ case '+':
+return $a + $b;
+ case '-':
+return $a - $b;
+ case '*':
+return $a * $b;
+ case '/':
+return $a / $b;
+ }
+ }
+
+ public static function clamp( $val, $max = 1 ) {
+ return min( max( $val, 0 ), $max );
+ }
+
+ public static function fround( $value ) {
+ if ( $value === 0 ) {
+ return $value;
+ }
+
+ if ( Less_Parser::$options['numPrecision'] ) {
+ $p = pow( 10, Less_Parser::$options['numPrecision'] );
+ return round( $value * $p ) / $p;
+ }
+ return $value;
+ }
+
+ public static function number( $n ) {
+ if ( $n instanceof Less_Tree_Dimension ) {
+ return floatval( $n->unit->is( '%' ) ? $n->value / 100 : $n->value );
+ } else if ( is_numeric( $n ) ) {
+ return $n;
+ } else {
+ throw new Less_Exception_Compiler( "color functions take numbers as parameters" );
+ }
+ }
+
+ public static function scaled( $n, $size = 255 ) {
+ if ( $n instanceof Less_Tree_Dimension && $n->unit->is( '%' ) ) {
+ return (float)$n->value * $size / 100;
+ } else {
+ return Less_Functions::number( $n );
+ }
+ }
+
+ public function rgb( $r = null, $g = null, $b = null ) {
+ if ( is_null( $r ) || is_null( $g ) || is_null( $b ) ) {
+ throw new Less_Exception_Compiler( "rgb expects three parameters" );
+ }
+ return $this->rgba( $r, $g, $b, 1.0 );
+ }
+
+ public function rgba( $r = null, $g = null, $b = null, $a = null ) {
+ $rgb = array( $r, $g, $b );
+ $rgb = array_map( array( 'Less_Functions','scaled' ), $rgb );
+
+ $a = self::number( $a );
+ return new Less_Tree_Color( $rgb, $a );
+ }
+
+ public function hsl( $h, $s, $l ) {
+ return $this->hsla( $h, $s, $l, 1.0 );
+ }
+
+ public function hsla( $h, $s, $l, $a ) {
+ $h = fmod( self::number( $h ), 360 ) / 360; // Classic % operator will change float to int
+ $s = self::clamp( self::number( $s ) );
+ $l = self::clamp( self::number( $l ) );
+ $a = self::clamp( self::number( $a ) );
+
+ $m2 = $l <= 0.5 ? $l * ( $s + 1 ) : $l + $s - $l * $s;
+
+ $m1 = $l * 2 - $m2;
+
+ return $this->rgba( self::hsla_hue( $h + 1 / 3, $m1, $m2 ) * 255,
+ self::hsla_hue( $h, $m1, $m2 ) * 255,
+ self::hsla_hue( $h - 1 / 3, $m1, $m2 ) * 255,
+ $a );
+ }
+
+ /**
+ * @param double $h
+ */
+ public function hsla_hue( $h, $m1, $m2 ) {
+ $h = $h < 0 ? $h + 1 : ( $h > 1 ? $h - 1 : $h );
+ if ( $h * 6 < 1 ) return $m1 + ( $m2 - $m1 ) * $h * 6; else if ( $h * 2 < 1 ) return $m2; else if ( $h * 3 < 2 ) return $m1 + ( $m2 - $m1 ) * ( 2 / 3 - $h ) * 6; else return $m1;
+ }
+
+ public function hsv( $h, $s, $v ) {
+ return $this->hsva( $h, $s, $v, 1.0 );
+ }
+
+ /**
+ * @param double $a
+ */
+ public function hsva( $h, $s, $v, $a ) {
+ $h = ( ( Less_Functions::number( $h ) % 360 ) / 360 ) * 360;
+ $s = Less_Functions::number( $s );
+ $v = Less_Functions::number( $v );
+ $a = Less_Functions::number( $a );
+
+ $i = floor( ( $h / 60 ) % 6 );
+ $f = ( $h / 60 ) - $i;
+
+ $vs = array( $v,
+ $v * ( 1 - $s ),
+ $v * ( 1 - $f * $s ),
+ $v * ( 1 - ( 1 - $f ) * $s ) );
+
+ $perm = array( array( 0, 3, 1 ),
+ array( 2, 0, 1 ),
+ array( 1, 0, 3 ),
+ array( 1, 2, 0 ),
+ array( 3, 1, 0 ),
+ array( 0, 1, 2 ) );
+
+ return $this->rgba( $vs[$perm[$i][0]] * 255,
+ $vs[$perm[$i][1]] * 255,
+ $vs[$perm[$i][2]] * 255,
+ $a );
+ }
+
+ public function hue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( Less_Parser::round( $c['h'] ) );
+ }
+
+ public function saturation( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to saturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( Less_Parser::round( $c['s'] * 100 ), '%' );
+ }
+
+ public function lightness( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to lightness must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( Less_Parser::round( $c['l'] * 100 ), '%' );
+ }
+
+ public function hsvhue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hsvhue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsv = $color->toHSV();
+ return new Less_Tree_Dimension( Less_Parser::round( $hsv['h'] ) );
+ }
+
+ public function hsvsaturation( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hsvsaturation must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsv = $color->toHSV();
+ return new Less_Tree_Dimension( Less_Parser::round( $hsv['s'] * 100 ), '%' );
+ }
+
+ public function hsvvalue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hsvvalue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsv = $color->toHSV();
+ return new Less_Tree_Dimension( Less_Parser::round( $hsv['v'] * 100 ), '%' );
+ }
+
+ public function red( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to red must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( $color->rgb[0] );
+ }
+
+ public function green( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to green must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( $color->rgb[1] );
+ }
+
+ public function blue( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to blue must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( $color->rgb[2] );
+ }
+
+ public function alpha( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to alpha must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $c = $color->toHSL();
+ return new Less_Tree_Dimension( $c['a'] );
+ }
+
+ public function luma( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to luma must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Dimension( Less_Parser::round( $color->luma() * $color->alpha * 100 ), '%' );
+ }
+
+ public function luminance( $color = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to luminance must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $luminance =
+ ( 0.2126 * $color->rgb[0] / 255 )
+ + ( 0.7152 * $color->rgb[1] / 255 )
+ + ( 0.0722 * $color->rgb[2] / 255 );
+
+ return new Less_Tree_Dimension( Less_Parser::round( $luminance * $color->alpha * 100 ), '%' );
+ }
+
+ public function saturate( $color = null, $amount = null ) {
+ // filter: saturate(3.2);
+ // should be kept as is, so check for color
+ if ( $color instanceof Less_Tree_Dimension ) {
+ return null;
+ }
+
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to saturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to saturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['s'] += $amount->value / 100;
+ $hsl['s'] = self::clamp( $hsl['s'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ /**
+ * @param Less_Tree_Dimension $amount
+ */
+ public function desaturate( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to desaturate must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to desaturate must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['s'] -= $amount->value / 100;
+ $hsl['s'] = self::clamp( $hsl['s'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function lighten( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to lighten must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to lighten must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['l'] += $amount->value / 100;
+ $hsl['l'] = self::clamp( $hsl['l'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function darken( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to darken must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to darken must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['l'] -= $amount->value / 100;
+ $hsl['l'] = self::clamp( $hsl['l'] );
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function fadein( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to fadein must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to fadein must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['a'] += $amount->value / 100;
+ $hsl['a'] = self::clamp( $hsl['a'] );
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function fadeout( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to fadeout must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to fadeout must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hsl['a'] -= $amount->value / 100;
+ $hsl['a'] = self::clamp( $hsl['a'] );
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function fade( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to fade must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to fade must be a percentage' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+
+ $hsl['a'] = $amount->value / 100;
+ $hsl['a'] = self::clamp( $hsl['a'] );
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ public function spin( $color = null, $amount = null ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to spin must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$amount instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The second argument to spin must be a number' . ( $amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $hsl = $color->toHSL();
+ $hue = fmod( $hsl['h'] + $amount->value, 360 );
+
+ $hsl['h'] = $hue < 0 ? 360 + $hue : $hue;
+
+ return $this->hsla( $hsl['h'], $hsl['s'], $hsl['l'], $hsl['a'] );
+ }
+
+ //
+ // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
+ // https://sass-lang.com/
+ //
+
+ /**
+ * @param Less_Tree_Color $color1
+ */
+ public function mix( $color1 = null, $color2 = null, $weight = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to mix must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to mix must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$weight ) {
+ $weight = new Less_Tree_Dimension( '50', '%' );
+ }
+ if ( !$weight instanceof Less_Tree_Dimension ) {
+ throw new Less_Exception_Compiler( 'The third argument to contrast must be a percentage' . ( $weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ $p = $weight->value / 100.0;
+ $w = $p * 2 - 1;
+ $hsl1 = $color1->toHSL();
+ $hsl2 = $color2->toHSL();
+ $a = $hsl1['a'] - $hsl2['a'];
+
+ $w1 = ( ( ( ( $w * $a ) == -1 ) ? $w : ( $w + $a ) / ( 1 + $w * $a ) ) + 1 ) / 2;
+ $w2 = 1 - $w1;
+
+ $rgb = array( $color1->rgb[0] * $w1 + $color2->rgb[0] * $w2,
+ $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2,
+ $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2 );
+
+ $alpha = $color1->alpha * $p + $color2->alpha * ( 1 - $p );
+
+ return new Less_Tree_Color( $rgb, $alpha );
+ }
+
+ public function greyscale( $color ) {
+ return $this->desaturate( $color, new Less_Tree_Dimension( 100, '%' ) );
+ }
+
+ public function contrast( $color, $dark = null, $light = null, $threshold = null ) {
+ // filter: contrast(3.2);
+ // should be kept as is, so check for color
+ if ( !$color instanceof Less_Tree_Color ) {
+ return null;
+ }
+ if ( !$light ) {
+ $light = $this->rgba( 255, 255, 255, 1.0 );
+ }
+ if ( !$dark ) {
+ $dark = $this->rgba( 0, 0, 0, 1.0 );
+ }
+
+ if ( !$dark instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to contrast must be a color' . ( $dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$light instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The third argument to contrast must be a color' . ( $light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ // Figure out which is actually light and dark!
+ if ( $dark->luma() > $light->luma() ) {
+ $t = $light;
+ $light = $dark;
+ $dark = $t;
+ }
+ if ( !$threshold ) {
+ $threshold = 0.43;
+ } else {
+ $threshold = Less_Functions::number( $threshold );
+ }
+
+ if ( $color->luma() < $threshold ) {
+ return $light;
+ } else {
+ return $dark;
+ }
+ }
+
+ public function e( $str ) {
+ if ( is_string( $str ) ) {
+ return new Less_Tree_Anonymous( $str );
+ }
+ return new Less_Tree_Anonymous( $str instanceof Less_Tree_JavaScript ? $str->expression : $str->value );
+ }
+
+ public function escape( $str ) {
+ $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'",'%3F' => '?','%26' => '&','%2C' => ',','%2F' => '/','%40' => '@','%2B' => '+','%24' => '$' );
+
+ return new Less_Tree_Anonymous( strtr( rawurlencode( $str->value ), $revert ) );
+ }
+
+ /**
+ * todo: This function will need some additional work to make it work the same as less.js
+ *
+ */
+ public function replace( $string, $pattern, $replacement, $flags = null ) {
+ $result = $string->value;
+
+ $expr = '/'.str_replace( '/', '\\/', $pattern->value ).'/';
+ if ( $flags && $flags->value ) {
+ $expr .= self::replace_flags( $flags->value );
+ }
+
+ $result = preg_replace( $expr, $replacement->value, $result );
+
+ if ( property_exists( $string, 'quote' ) ) {
+ return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
+ }
+ return new Less_Tree_Quoted( '', $result );
+ }
+
+ public static function replace_flags( $flags ) {
+ $flags = str_split( $flags, 1 );
+ $new_flags = '';
+
+ foreach ( $flags as $flag ) {
+ switch ( $flag ) {
+ case 'e':
+ case 'g':
+ break;
+
+ default:
+ $new_flags .= $flag;
+ break;
+ }
+ }
+
+ return $new_flags;
+ }
+
+ public function _percent() {
+ $string = func_get_arg( 0 );
+
+ $args = func_get_args();
+ array_shift( $args );
+ $result = $string->value;
+
+ foreach ( $args as $arg ) {
+ if ( preg_match( '/%[sda]/i', $result, $token ) ) {
+ $token = $token[0];
+ $value = stristr( $token, 's' ) ? $arg->value : $arg->toCSS();
+ $value = preg_match( '/[A-Z]$/', $token ) ? urlencode( $value ) : $value;
+ $result = preg_replace( '/%[sda]/i', $value, $result, 1 );
+ }
+ }
+ $result = str_replace( '%%', '%', $result );
+
+ return new Less_Tree_Quoted( $string->quote, $result, $string->escaped );
+ }
+
+ public function unit( $val, $unit = null ) {
+ if ( !( $val instanceof Less_Tree_Dimension ) ) {
+ throw new Less_Exception_Compiler( 'The first argument to unit must be a number' . ( $val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.' ) );
+ }
+
+ if ( $unit ) {
+ if ( $unit instanceof Less_Tree_Keyword ) {
+ $unit = $unit->value;
+ } else {
+ $unit = $unit->toCSS();
+ }
+ } else {
+ $unit = "";
+ }
+ return new Less_Tree_Dimension( $val->value, $unit );
+ }
+
+ public function convert( $val, $unit ) {
+ return $val->convertTo( $unit->value );
+ }
+
+ public function round( $n, $f = false ) {
+ $fraction = 0;
+ if ( $f !== false ) {
+ $fraction = $f->value;
+ }
+
+ return $this->_math( 'Less_Parser::round', null, $n, $fraction );
+ }
+
+ public function pi() {
+ return new Less_Tree_Dimension( M_PI );
+ }
+
+ public function mod( $a, $b ) {
+ return new Less_Tree_Dimension( $a->value % $b->value, $a->unit );
+ }
+
+ public function pow( $x, $y ) {
+ if ( is_numeric( $x ) && is_numeric( $y ) ) {
+ $x = new Less_Tree_Dimension( $x );
+ $y = new Less_Tree_Dimension( $y );
+ } elseif ( !( $x instanceof Less_Tree_Dimension ) || !( $y instanceof Less_Tree_Dimension ) ) {
+ throw new Less_Exception_Compiler( 'Arguments must be numbers' );
+ }
+
+ return new Less_Tree_Dimension( pow( $x->value, $y->value ), $x->unit );
+ }
+
+ // var mathFunctions = [{name:"ce ...
+ public function ceil( $n ) {
+ return $this->_math( 'ceil', null, $n );
+ }
+
+ public function floor( $n ) {
+ return $this->_math( 'floor', null, $n );
+ }
+
+ public function sqrt( $n ) {
+ return $this->_math( 'sqrt', null, $n );
+ }
+
+ public function abs( $n ) {
+ return $this->_math( 'abs', null, $n );
+ }
+
+ public function tan( $n ) {
+ return $this->_math( 'tan', '', $n );
+ }
+
+ public function sin( $n ) {
+ return $this->_math( 'sin', '', $n );
+ }
+
+ public function cos( $n ) {
+ return $this->_math( 'cos', '', $n );
+ }
+
+ public function atan( $n ) {
+ return $this->_math( 'atan', 'rad', $n );
+ }
+
+ public function asin( $n ) {
+ return $this->_math( 'asin', 'rad', $n );
+ }
+
+ public function acos( $n ) {
+ return $this->_math( 'acos', 'rad', $n );
+ }
+
+ private function _math() {
+ $args = func_get_args();
+ $fn = array_shift( $args );
+ $unit = array_shift( $args );
+
+ if ( $args[0] instanceof Less_Tree_Dimension ) {
+
+ if ( $unit === null ) {
+ $unit = $args[0]->unit;
+ } else {
+ $args[0] = $args[0]->unify();
+ }
+ $args[0] = (float)$args[0]->value;
+ return new Less_Tree_Dimension( call_user_func_array( $fn, $args ), $unit );
+ } else if ( is_numeric( $args[0] ) ) {
+ return call_user_func_array( $fn, $args );
+ } else {
+ throw new Less_Exception_Compiler( "math functions take numbers as parameters" );
+ }
+ }
+
+ /**
+ * @param boolean $isMin
+ */
+ private function _minmax( $isMin, $args ) {
+ $arg_count = count( $args );
+
+ if ( $arg_count < 1 ) {
+ throw new Less_Exception_Compiler( 'one or more arguments required' );
+ }
+
+ $j = null;
+ $unitClone = null;
+ $unitStatic = null;
+
+ $order = array(); // elems only contains original argument values.
+ $values = array(); // key is the unit.toString() for unified tree.Dimension values,
+ // value is the index into the order array.
+
+ for ( $i = 0; $i < $arg_count; $i++ ) {
+ $current = $args[$i];
+ if ( !( $current instanceof Less_Tree_Dimension ) ) {
+ if ( is_array( $args[$i]->value ) ) {
+ $args[] = $args[$i]->value;
+ }
+ continue;
+ }
+
+ if ( $current->unit->toString() === '' && !$unitClone ) {
+ $temp = new Less_Tree_Dimension( $current->value, $unitClone );
+ $currentUnified = $temp->unify();
+ } else {
+ $currentUnified = $current->unify();
+ }
+
+ if ( $currentUnified->unit->toString() === "" && !$unitStatic ) {
+ $unit = $unitStatic;
+ } else {
+ $unit = $currentUnified->unit->toString();
+ }
+
+ if ( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ) {
+ $unitStatic = $unit;
+ }
+
+ if ( $unit != '' && !$unitClone ) {
+ $unitClone = $current->unit->toString();
+ }
+
+ if ( isset( $values[''] ) && $unit !== '' && $unit === $unitStatic ) {
+ $j = $values[''];
+ } elseif ( isset( $values[$unit] ) ) {
+ $j = $values[$unit];
+ } else {
+
+ if ( $unitStatic && $unit !== $unitStatic ) {
+ throw new Less_Exception_Compiler( 'incompatible types' );
+ }
+ $values[$unit] = count( $order );
+ $order[] = $current;
+ continue;
+ }
+
+ if ( $order[$j]->unit->toString() === "" && $unitClone ) {
+ $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone );
+ $referenceUnified = $temp->unify();
+ } else {
+ $referenceUnified = $order[$j]->unify();
+ }
+ if ( ( $isMin && $currentUnified->value < $referenceUnified->value ) || ( !$isMin && $currentUnified->value > $referenceUnified->value ) ) {
+ $order[$j] = $current;
+ }
+ }
+
+ if ( count( $order ) == 1 ) {
+ return $order[0];
+ }
+ $args = array();
+ foreach ( $order as $a ) {
+ $args[] = $a->toCSS();
+ }
+ return new Less_Tree_Anonymous( ( $isMin ? 'min(' : 'max(' ) . implode( Less_Environment::$_outputMap[','], $args ).')' );
+ }
+
+ public function min() {
+ $args = func_get_args();
+ return $this->_minmax( true, $args );
+ }
+
+ public function max() {
+ $args = func_get_args();
+ return $this->_minmax( false, $args );
+ }
+
+ public function getunit( $n ) {
+ return new Less_Tree_Anonymous( $n->unit );
+ }
+
+ public function argb( $color ) {
+ if ( !$color instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to argb must be a color' . ( $color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return new Less_Tree_Anonymous( $color->toARGB() );
+ }
+
+ public function percentage( $n ) {
+ return new Less_Tree_Dimension( $n->value * 100, '%' );
+ }
+
+ public function color( $n ) {
+ if ( $n instanceof Less_Tree_Quoted ) {
+ $colorCandidate = $n->value;
+ $returnColor = Less_Tree_Color::fromKeyword( $colorCandidate );
+ if ( $returnColor ) {
+ return $returnColor;
+ }
+ if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/', $colorCandidate ) ) {
+ return new Less_Tree_Color( substr( $colorCandidate, 1 ) );
+ }
+ throw new Less_Exception_Compiler( "argument must be a color keyword or 3/6 digit hex e.g. #FFF" );
+ } else {
+ throw new Less_Exception_Compiler( "argument must be a string" );
+ }
+ }
+
+ public function iscolor( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Color' );
+ }
+
+ public function isnumber( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Dimension' );
+ }
+
+ public function isstring( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Quoted' );
+ }
+
+ public function iskeyword( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Keyword' );
+ }
+
+ public function isurl( $n ) {
+ return $this->_isa( $n, 'Less_Tree_Url' );
+ }
+
+ public function ispixel( $n ) {
+ return $this->isunit( $n, 'px' );
+ }
+
+ public function ispercentage( $n ) {
+ return $this->isunit( $n, '%' );
+ }
+
+ public function isem( $n ) {
+ return $this->isunit( $n, 'em' );
+ }
+
+ /**
+ * @param string $unit
+ */
+ public function isunit( $n, $unit ) {
+ if ( is_object( $unit ) && property_exists( $unit, 'value' ) ) {
+ $unit = $unit->value;
+ }
+
+ return ( $n instanceof Less_Tree_Dimension ) && $n->unit->is( $unit ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+ }
+
+ /**
+ * @param string $type
+ */
+ private function _isa( $n, $type ) {
+ return is_a( $n, $type ) ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+ }
+
+ public function tint( $color, $amount = null ) {
+ return $this->mix( $this->rgb( 255, 255, 255 ), $color, $amount );
+ }
+
+ public function shade( $color, $amount = null ) {
+ return $this->mix( $this->rgb( 0, 0, 0 ), $color, $amount );
+ }
+
+ public function extract( $values, $index ) {
+ $index = (int)$index->value - 1; // (1-based index)
+ // handle non-array values as an array of length 1
+ // return 'undefined' if index is invalid
+ if ( property_exists( $values, 'value' ) && is_array( $values->value ) ) {
+ if ( isset( $values->value[$index] ) ) {
+ return $values->value[$index];
+ }
+ return null;
+
+ } elseif ( (int)$index === 0 ) {
+ return $values;
+ }
+
+ return null;
+ }
+
+ public function length( $values ) {
+ $n = ( property_exists( $values, 'value' ) && is_array( $values->value ) ) ? count( $values->value ) : 1;
+ return new Less_Tree_Dimension( $n );
+ }
+
+ public function datauri( $mimetypeNode, $filePathNode = null ) {
+ $filePath = ( $filePathNode ? $filePathNode->value : null );
+ $mimetype = $mimetypeNode->value;
+
+ $args = 2;
+ if ( !$filePath ) {
+ $filePath = $mimetype;
+ $args = 1;
+ }
+
+ $filePath = str_replace( '\\', '/', $filePath );
+ if ( Less_Environment::isPathRelative( $filePath ) ) {
+
+ if ( Less_Parser::$options['relativeUrls'] ) {
+ $temp = $this->currentFileInfo['currentDirectory'];
+ } else {
+ $temp = $this->currentFileInfo['entryPath'];
+ }
+
+ if ( !empty( $temp ) ) {
+ $filePath = Less_Environment::normalizePath( rtrim( $temp, '/' ).'/'.$filePath );
+ }
+
+ }
+
+ // detect the mimetype if not given
+ if ( $args < 2 ) {
+
+ /* incomplete
+ $mime = require('mime');
+ mimetype = mime.lookup(path);
+
+ // use base 64 unless it's an ASCII or UTF-8 format
+ var charset = mime.charsets.lookup(mimetype);
+ useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0;
+ if (useBase64) mimetype += ';base64';
+ */
+
+ $mimetype = Less_Mime::lookup( $filePath );
+
+ $charset = Less_Mime::charsets_lookup( $mimetype );
+ $useBase64 = !in_array( $charset, array( 'US-ASCII', 'UTF-8' ) );
+ if ( $useBase64 ) { $mimetype .= ';base64';
+ }
+
+ } else {
+ $useBase64 = preg_match( '/;base64$/', $mimetype );
+ }
+
+ if ( file_exists( $filePath ) ) {
+ $buf = @file_get_contents( $filePath );
+ } else {
+ $buf = false;
+ }
+
+ // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded
+ // and the --ieCompat flag is enabled, return a normal url() instead.
+ $DATA_URI_MAX_KB = 32;
+ $fileSizeInKB = round( strlen( $buf ) / 1024 );
+ if ( $fileSizeInKB >= $DATA_URI_MAX_KB ) {
+ $url = new Less_Tree_Url( ( $filePathNode ?: $mimetypeNode ), $this->currentFileInfo );
+ return $url->compile( $this );
+ }
+
+ if ( $buf ) {
+ $buf = $useBase64 ? base64_encode( $buf ) : rawurlencode( $buf );
+ $filePath = '"data:' . $mimetype . ',' . $buf . '"';
+ }
+
+ return new Less_Tree_Url( new Less_Tree_Anonymous( $filePath ) );
+ }
+
+ // svg-gradient
+ public function svggradient( $direction ) {
+ $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]';
+ $arguments = func_get_args();
+
+ if ( count( $arguments ) < 3 ) {
+ throw new Less_Exception_Compiler( $throw_message );
+ }
+
+ $stops = array_slice( $arguments, 1 );
+ $gradientType = 'linear';
+ $rectangleDimension = 'x="0" y="0" width="1" height="1"';
+ $useBase64 = true;
+ $directionValue = $direction->toCSS();
+
+ switch ( $directionValue ) {
+ case "to bottom":
+ $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"';
+ break;
+ case "to right":
+ $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"';
+ break;
+ case "to bottom right":
+ $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"';
+ break;
+ case "to top right":
+ $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"';
+ break;
+ case "ellipse":
+ case "ellipse at center":
+ $gradientType = "radial";
+ $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"';
+ $rectangleDimension = 'x="-50" y="-50" width="101" height="101"';
+ break;
+ default:
+ throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" );
+ }
+
+ $returner = '<?xml version="1.0" ?>' .
+ '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">' .
+ '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>';
+
+ for ( $i = 0; $i < count( $stops ); $i++ ) {
+ if ( is_object( $stops[$i] ) && property_exists( $stops[$i], 'value' ) ) {
+ $color = $stops[$i]->value[0];
+ $position = $stops[$i]->value[1];
+ } else {
+ $color = $stops[$i];
+ $position = null;
+ }
+
+ if ( !( $color instanceof Less_Tree_Color ) || ( !( ( $i === 0 || $i + 1 === count( $stops ) ) && $position === null ) && !( $position instanceof Less_Tree_Dimension ) ) ) {
+ throw new Less_Exception_Compiler( $throw_message );
+ }
+ if ( $position ) {
+ $positionValue = $position->toCSS();
+ } elseif ( $i === 0 ) {
+ $positionValue = '0%';
+ } else {
+ $positionValue = '100%';
+ }
+ $alpha = $color->alpha;
+ $returner .= '<stop offset="' . $positionValue . '" stop-color="' . $color->toRGB() . '"' . ( $alpha < 1 ? ' stop-opacity="' . $alpha . '"' : '' ) . '/>';
+ }
+
+ $returner .= '</' . $gradientType . 'Gradient><rect ' . $rectangleDimension . ' fill="url(#gradient)" /></svg>';
+
+ if ( $useBase64 ) {
+ $returner = "'data:image/svg+xml;base64,".base64_encode( $returner )."'";
+ } else {
+ $returner = "'data:image/svg+xml,".$returner."'";
+ }
+
+ return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) );
+ }
+
+ /**
+ * Php version of javascript's `encodeURIComponent` function
+ *
+ * @param string $string The string to encode
+ * @return string The encoded string
+ */
+ public static function encodeURIComponent( $string ) {
+ $revert = array( '%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')' );
+ return strtr( rawurlencode( $string ), $revert );
+ }
+
+ // Color Blending
+ // ref: https://www.w3.org/TR/compositing-1/
+
+ public function colorBlend( $mode, $color1, $color2 ) {
+ $ab = $color1->alpha; // backdrop
+ $as = $color2->alpha; // source
+ $r = array(); // result
+
+ $ar = $as + $ab * ( 1 - $as );
+ for ( $i = 0; $i < 3; $i++ ) {
+ $cb = $color1->rgb[$i] / 255;
+ $cs = $color2->rgb[$i] / 255;
+ $cr = call_user_func( $mode, $cb, $cs );
+ if ( $ar ) {
+ $cr = ( $as * $cs + $ab * ( $cb - $as * ( $cb + $cs - $cr ) ) ) / $ar;
+ }
+ $r[$i] = $cr * 255;
+ }
+
+ return new Less_Tree_Color( $r, $ar );
+ }
+
+ public function multiply( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to multiply must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to multiply must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendMultiply' ), $color1, $color2 );
+ }
+
+ private function colorBlendMultiply( $cb, $cs ) {
+ return $cb * $cs;
+ }
+
+ public function screen( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to screen must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to screen must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendScreen' ), $color1, $color2 );
+ }
+
+ private function colorBlendScreen( $cb, $cs ) {
+ return $cb + $cs - $cb * $cs;
+ }
+
+ public function overlay( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to overlay must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to overlay must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendOverlay' ), $color1, $color2 );
+ }
+
+ private function colorBlendOverlay( $cb, $cs ) {
+ $cb *= 2;
+ return ( $cb <= 1 )
+ ? $this->colorBlendMultiply( $cb, $cs )
+ : $this->colorBlendScreen( $cb - 1, $cs );
+ }
+
+ public function softlight( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to softlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to softlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendSoftlight' ), $color1, $color2 );
+ }
+
+ private function colorBlendSoftlight( $cb, $cs ) {
+ $d = 1;
+ $e = $cb;
+ if ( $cs > 0.5 ) {
+ $e = 1;
+ $d = ( $cb > 0.25 ) ? sqrt( $cb )
+ : ( ( 16 * $cb - 12 ) * $cb + 4 ) * $cb;
+ }
+ return $cb - ( 1 - 2 * $cs ) * $e * ( $d - $cb );
+ }
+
+ public function hardlight( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to hardlight must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to hardlight must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendHardlight' ), $color1, $color2 );
+ }
+
+ private function colorBlendHardlight( $cb, $cs ) {
+ return $this->colorBlendOverlay( $cs, $cb );
+ }
+
+ public function difference( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to difference must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to difference must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendDifference' ), $color1, $color2 );
+ }
+
+ private function colorBlendDifference( $cb, $cs ) {
+ return abs( $cb - $cs );
+ }
+
+ public function exclusion( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to exclusion must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to exclusion must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendExclusion' ), $color1, $color2 );
+ }
+
+ private function colorBlendExclusion( $cb, $cs ) {
+ return $cb + $cs - 2 * $cb * $cs;
+ }
+
+ public function average( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to average must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to average must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendAverage' ), $color1, $color2 );
+ }
+
+ // non-w3c functions:
+ public function colorBlendAverage( $cb, $cs ) {
+ return ( $cb + $cs ) / 2;
+ }
+
+ public function negation( $color1 = null, $color2 = null ) {
+ if ( !$color1 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The first argument to negation must be a color' . ( $color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+ if ( !$color2 instanceof Less_Tree_Color ) {
+ throw new Less_Exception_Compiler( 'The second argument to negation must be a color' . ( $color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '' ) );
+ }
+
+ return $this->colorBlend( array( $this,'colorBlendNegation' ), $color1, $color2 );
+ }
+
+ public function colorBlendNegation( $cb, $cs ) {
+ return 1 - abs( $cb + $cs - 1 );
+ }
+
+ // ~ End of Color Blending
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Less.php.combine b/library/vendor/lessphp/lib/Less/Less.php.combine
new file mode 100644
index 0000000..d63cc78
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Less.php.combine
@@ -0,0 +1,17 @@
+
+./Parser.php
+./Colors.php
+./Environment.php
+./Functions.php
+./Mime.php
+./Tree.php
+./Output.php
+./Visitor.php
+./VisitorReplacing.php
+./Configurable.php
+./Tree
+./Visitor
+./Exception/Parser.php
+./Exception/
+./Output
+./SourceMap
diff --git a/library/vendor/lessphp/lib/Less/Mime.php b/library/vendor/lessphp/lib/Less/Mime.php
new file mode 100644
index 0000000..b4723f9
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Mime.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Mime lookup
+ *
+ * @package Less
+ * @subpackage node
+ */
+class Less_Mime {
+
+ // this map is intentionally incomplete
+ // if you want more, install 'mime' dep
+ static $_types = array(
+ '.htm' => 'text/html',
+ '.html' => 'text/html',
+ '.gif' => 'image/gif',
+ '.jpg' => 'image/jpeg',
+ '.jpeg' => 'image/jpeg',
+ '.png' => 'image/png',
+ '.ttf' => 'application/x-font-ttf',
+ '.otf' => 'application/x-font-otf',
+ '.eot' => 'application/vnd.ms-fontobject',
+ '.woff' => 'application/x-font-woff',
+ '.svg' => 'image/svg+xml',
+ );
+
+ public static function lookup( $filepath ) {
+ $parts = explode( '.', $filepath );
+ $ext = '.'.strtolower( array_pop( $parts ) );
+
+ if ( !isset( self::$_types[$ext] ) ) {
+ return null;
+ }
+ return self::$_types[$ext];
+ }
+
+ public static function charsets_lookup( $type = null ) {
+ // assumes all text types are UTF-8
+ return $type && preg_match( '/^text\//', $type ) ? 'UTF-8' : '';
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Output.php b/library/vendor/lessphp/lib/Less/Output.php
new file mode 100644
index 0000000..21a882d
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Output.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Parser output
+ *
+ * @package Less
+ * @subpackage output
+ */
+class Less_Output {
+
+ /**
+ * Output holder
+ *
+ * @var string[]
+ */
+ protected $strs = [];
+
+ /**
+ * Adds a chunk to the stack
+ *
+ * @param string $chunk The chunk to output
+ * @param array $fileInfo The file information
+ * @param integer $index The index
+ * @param mixed $mapLines
+ */
+ public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
+ $this->strs[] = $chunk;
+ }
+
+ /**
+ * Is the output empty?
+ *
+ * @return boolean
+ */
+ public function isEmpty() {
+ return count( $this->strs ) === 0;
+ }
+
+ /**
+ * Converts the output to string
+ *
+ * @return string
+ */
+ public function toString() {
+ return implode( '', $this->strs );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Output/Mapped.php b/library/vendor/lessphp/lib/Less/Output/Mapped.php
new file mode 100644
index 0000000..71d7b71
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Output/Mapped.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * Parser output with source map
+ *
+ * @package Less
+ * @subpackage Output
+ */
+class Less_Output_Mapped extends Less_Output {
+
+ /**
+ * The source map generator
+ *
+ * @var Less_SourceMap_Generator
+ */
+ protected $generator;
+
+ /**
+ * Current line
+ *
+ * @var integer
+ */
+ protected $lineNumber = 0;
+
+ /**
+ * Current column
+ *
+ * @var integer
+ */
+ protected $column = 0;
+
+ /**
+ * Array of contents map (file and its content)
+ *
+ * @var array
+ */
+ protected $contentsMap = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $contentsMap Array of filename to contents map
+ * @param Less_SourceMap_Generator $generator
+ */
+ public function __construct( array $contentsMap, $generator ) {
+ $this->contentsMap = $contentsMap;
+ $this->generator = $generator;
+ }
+
+ /**
+ * Adds a chunk to the stack
+ * The $index for less.php may be different from less.js since less.php does not chunkify inputs
+ *
+ * @param string $chunk
+ * @param array|null $fileInfo
+ * @param int $index
+ * @param mixed $mapLines
+ */
+ public function add( $chunk, $fileInfo = null, $index = 0, $mapLines = null ) {
+ // ignore adding empty strings
+ if ( $chunk === '' ) {
+ return;
+ }
+
+ $sourceLines = array();
+ $sourceColumns = ' ';
+
+ if ( $fileInfo ) {
+
+ $url = $fileInfo['currentUri'];
+
+ if ( isset( $this->contentsMap[$url] ) ) {
+ $inputSource = substr( $this->contentsMap[$url], 0, $index );
+ $sourceLines = explode( "\n", $inputSource );
+ $sourceColumns = end( $sourceLines );
+ } else {
+ throw new Exception( 'Filename '.$url.' not in contentsMap' );
+ }
+
+ }
+
+ $lines = explode( "\n", $chunk );
+ $columns = end( $lines );
+
+ if ( $fileInfo ) {
+
+ if ( !$mapLines ) {
+ $this->generator->addMapping(
+ $this->lineNumber + 1, // generated_line
+ $this->column, // generated_column
+ count( $sourceLines ), // original_line
+ strlen( $sourceColumns ), // original_column
+ $fileInfo
+ );
+ } else {
+ for ( $i = 0, $count = count( $lines ); $i < $count; $i++ ) {
+ $this->generator->addMapping(
+ $this->lineNumber + $i + 1, // generated_line
+ $i === 0 ? $this->column : 0, // generated_column
+ count( $sourceLines ) + $i, // original_line
+ $i === 0 ? strlen( $sourceColumns ) : 0, // original_column
+ $fileInfo
+ );
+ }
+ }
+ }
+
+ if ( count( $lines ) === 1 ) {
+ $this->column += strlen( $columns );
+ } else {
+ $this->lineNumber += count( $lines ) - 1;
+ $this->column = strlen( $columns );
+ }
+
+ // add only chunk
+ parent::add( $chunk );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Parser.php b/library/vendor/lessphp/lib/Less/Parser.php
new file mode 100644
index 0000000..4162e4b
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Parser.php
@@ -0,0 +1,2676 @@
+<?php
+
+require_once dirname( __FILE__ ).'/Cache.php';
+
+/**
+ * Class for parsing and compiling less files into css
+ *
+ * @package Less
+ * @subpackage parser
+ */
+class Less_Parser {
+
+ /**
+ * Default parser options
+ */
+ public static $default_options = array(
+ 'compress' => false, // option - whether to compress
+ 'strictUnits' => false, // whether units need to evaluate correctly
+ 'strictMath' => false, // whether math has to be within parenthesis
+ 'relativeUrls' => true, // option - whether to adjust URL's to be relative
+ 'urlArgs' => '', // whether to add args into url tokens
+ 'numPrecision' => 8,
+
+ 'import_dirs' => array(),
+ 'import_callback' => null,
+ 'cache_dir' => null,
+ 'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback';
+ 'cache_callback_get' => null,
+ 'cache_callback_set' => null,
+
+ 'sourceMap' => false, // whether to output a source map
+ 'sourceMapBasepath' => null,
+ 'sourceMapWriteTo' => null,
+ 'sourceMapURL' => null,
+
+ 'indentation' => ' ',
+
+ 'plugins' => array(),
+
+ );
+
+ public static $options = array();
+
+ private $input; // Less input string
+ private $input_len; // input string length
+ private $pos; // current index in `input`
+ private $saveStack = array(); // holds state for backtracking
+ private $furthest;
+ private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding
+
+ /**
+ * @var Less_Environment
+ */
+ private $env;
+
+ protected $rules = array();
+
+ private static $imports = array();
+
+ public static $has_extends = false;
+
+ public static $next_id = 0;
+
+ /**
+ * Filename to contents of all parsed the files
+ *
+ * @var array
+ */
+ public static $contentsMap = array();
+
+ /**
+ * @param Less_Environment|array|null $env
+ */
+ public function __construct( $env = null ) {
+ // Top parser on an import tree must be sure there is one "env"
+ // which will then be passed around by reference.
+ if ( $env instanceof Less_Environment ) {
+ $this->env = $env;
+ } else {
+ $this->SetOptions( Less_Parser::$default_options );
+ $this->Reset( $env );
+ }
+
+ // mbstring.func_overload > 1 bugfix
+ // The encoding value must be set for each source file,
+ // therefore, to conserve resources and improve the speed of this design is taken here
+ if ( ini_get( 'mbstring.func_overload' ) ) {
+ $this->mb_internal_encoding = ini_get( 'mbstring.internal_encoding' );
+ @ini_set( 'mbstring.internal_encoding', 'ascii' );
+ }
+
+ }
+
+ /**
+ * Reset the parser state completely
+ */
+ public function Reset( $options = null ) {
+ $this->rules = array();
+ self::$imports = array();
+ self::$has_extends = false;
+ self::$imports = array();
+ self::$contentsMap = array();
+
+ $this->env = new Less_Environment();
+
+ // set new options
+ if ( is_array( $options ) ) {
+ $this->SetOptions( Less_Parser::$default_options );
+ $this->SetOptions( $options );
+ }
+
+ $this->env->Init();
+ }
+
+ /**
+ * Set one or more compiler options
+ * options: import_dirs, cache_dir, cache_method
+ */
+ public function SetOptions( $options ) {
+ foreach ( $options as $option => $value ) {
+ $this->SetOption( $option, $value );
+ }
+ }
+
+ /**
+ * Set one compiler option
+ */
+ public function SetOption( $option, $value ) {
+ switch ( $option ) {
+
+ case 'import_dirs':
+ $this->SetImportDirs( $value );
+ return;
+
+ case 'cache_dir':
+ if ( is_string( $value ) ) {
+ Less_Cache::SetCacheDir( $value );
+ Less_Cache::CheckCacheDir();
+ }
+ return;
+ }
+
+ Less_Parser::$options[$option] = $value;
+ }
+
+ /**
+ * Registers a new custom function
+ *
+ * @param string $name function name
+ * @param callable $callback callback
+ */
+ public function registerFunction( $name, $callback ) {
+ $this->env->functions[$name] = $callback;
+ }
+
+ /**
+ * Removed an already registered function
+ *
+ * @param string $name function name
+ */
+ public function unregisterFunction( $name ) {
+ if ( isset( $this->env->functions[$name] ) )
+ unset( $this->env->functions[$name] );
+ }
+
+ /**
+ * Get the current css buffer
+ *
+ * @return string
+ */
+ public function getCss() {
+ $precision = ini_get( 'precision' );
+ @ini_set( 'precision', '16' );
+ $locale = setlocale( LC_NUMERIC, 0 );
+ setlocale( LC_NUMERIC, "C" );
+
+ try {
+ $root = new Less_Tree_Ruleset( array(), $this->rules );
+ $root->root = true;
+ $root->firstRoot = true;
+
+ $this->PreVisitors( $root );
+
+ self::$has_extends = false;
+ $evaldRoot = $root->compile( $this->env );
+
+ $this->PostVisitors( $evaldRoot );
+
+ if ( Less_Parser::$options['sourceMap'] ) {
+ $generator = new Less_SourceMap_Generator( $evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
+ // will also save file
+ // FIXME: should happen somewhere else?
+ $css = $generator->generateCSS();
+ } else {
+ $css = $evaldRoot->toCSS();
+ }
+
+ if ( Less_Parser::$options['compress'] ) {
+ $css = preg_replace( '/(^(\s)+)|((\s)+$)/', '', $css );
+ }
+
+ } catch ( Exception $exc ) {
+ // Intentional fall-through so we can reset environment
+ }
+
+ // reset php settings
+ @ini_set( 'precision', $precision );
+ setlocale( LC_NUMERIC, $locale );
+
+ // If you previously defined $this->mb_internal_encoding
+ // is required to return the encoding as it was before
+ if ( $this->mb_internal_encoding != '' ) {
+ @ini_set( "mbstring.internal_encoding", $this->mb_internal_encoding );
+ $this->mb_internal_encoding = '';
+ }
+
+ // Rethrow exception after we handled resetting the environment
+ if ( !empty( $exc ) ) {
+ throw $exc;
+ }
+
+ return $css;
+ }
+
+ public function findValueOf( $varName ) {
+ foreach ( $this->rules as $rule ) {
+ if ( isset( $rule->variable ) && ( $rule->variable == true ) && ( str_replace( "@", "", $rule->name ) == $varName ) ) {
+ return $this->getVariableValue( $rule );
+ }
+ }
+ return null;
+ }
+
+ /**
+ *
+ * this function gets the private rules variable and returns an array of the found variables
+ * it uses a helper method getVariableValue() that contains the logic ot fetch the value from the rule object
+ *
+ * @return array
+ */
+ public function getVariables() {
+ $variables = array();
+
+ $not_variable_type = array(
+ 'Comment', // this include less comments ( // ) and css comments (/* */)
+ 'Import', // do not search variables in included files @import
+ 'Ruleset', // selectors (.someclass, #someid, …)
+ 'Operation', //
+ );
+
+ // @TODO run compilation if not runned yet
+ foreach ( $this->rules as $key => $rule ) {
+ if ( in_array( $rule->type, $not_variable_type ) ) {
+ continue;
+ }
+
+ // Note: it seems rule->type is always Rule when variable = true
+ if ( $rule->type == 'Rule' && $rule->variable ) {
+ $variables[$rule->name] = $this->getVariableValue( $rule );
+ } else {
+ if ( $rule->type == 'Comment' ) {
+ $variables[] = $this->getVariableValue( $rule );
+ }
+ }
+ }
+ return $variables;
+ }
+
+ public function findVarByName( $var_name ) {
+ foreach ( $this->rules as $rule ) {
+ if ( isset( $rule->variable ) && ( $rule->variable == true ) ) {
+ if ( $rule->name == $var_name ) {
+ return $this->getVariableValue( $rule );
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method gets the value of the less variable from the rules object.
+ * Since the objects vary here we add the logic for extracting the css/less value.
+ *
+ * @param $var
+ * @return string
+ */
+ private function getVariableValue( $var ) {
+ if ( !is_a( $var, 'Less_Tree' ) ) {
+ throw new Exception( 'var is not a Less_Tree object' );
+ }
+
+ switch ( $var->type ) {
+ case 'Color':
+ return $this->rgb2html( $var->rgb );
+ case 'Unit':
+ return $var->value. $var->unit->numerator[0];
+ case 'Variable':
+ return $this->findVarByName( $var->name );
+ case 'Keyword':
+ return $var->value;
+ case 'Url':
+ // Based on Less_Tree_Url::genCSS()
+ // Recurse to serialize the Less_Tree_Quoted value
+ return 'url(' . $this->getVariableValue( $var->value ) . ')';
+ case 'Rule':
+ return $this->getVariableValue( $var->value );
+ case 'Value':
+ $value = '';
+ foreach ( $var->value as $sub_value ) {
+ $value .= $this->getVariableValue( $sub_value ).' ';
+ }
+ return $value;
+ case 'Quoted':
+ return $var->quote.$var->value.$var->quote;
+ case 'Dimension':
+ $value = $var->value;
+ if ( $var->unit && $var->unit->numerator ) {
+ $value .= $var->unit->numerator[0];
+ }
+ return $value;
+ case 'Expression':
+ $value = "";
+ foreach ( $var->value as $item ) {
+ $value .= $this->getVariableValue( $item )." ";
+ }
+ return $value;
+ case 'Operation':
+ throw new Exception( 'getVariables() require Less to be compiled. please use $parser->getCss() before calling getVariables()' );
+ case 'Comment':
+ case 'Import':
+ case 'Ruleset':
+ default:
+ throw new Exception( "type missing in switch/case getVariableValue for ".$var->type );
+ }
+ }
+
+ private function rgb2html( $r, $g = -1, $b = -1 ) {
+ if ( is_array( $r ) && sizeof( $r ) == 3 )
+ list( $r, $g, $b ) = $r;
+
+ $r = intval( $r ); $g = intval( $g );
+ $b = intval( $b );
+
+ $r = dechex( $r < 0 ? 0 : ( $r > 255 ? 255 : $r ) );
+ $g = dechex( $g < 0 ? 0 : ( $g > 255 ? 255 : $g ) );
+ $b = dechex( $b < 0 ? 0 : ( $b > 255 ? 255 : $b ) );
+
+ $color = ( strlen( $r ) < 2 ? '0' : '' ).$r;
+ $color .= ( strlen( $g ) < 2 ? '0' : '' ).$g;
+ $color .= ( strlen( $b ) < 2 ? '0' : '' ).$b;
+ return '#'.$color;
+ }
+
+ /**
+ * Run pre-compile visitors
+ */
+ private function PreVisitors( $root ) {
+ if ( Less_Parser::$options['plugins'] ) {
+ foreach ( Less_Parser::$options['plugins'] as $plugin ) {
+ if ( !empty( $plugin->isPreEvalVisitor ) ) {
+ $plugin->run( $root );
+ }
+ }
+ }
+ }
+
+ /**
+ * Run post-compile visitors
+ */
+ private function PostVisitors( $evaldRoot ) {
+ $visitors = array();
+ $visitors[] = new Less_Visitor_joinSelector();
+ if ( self::$has_extends ) {
+ $visitors[] = new Less_Visitor_processExtends();
+ }
+ $visitors[] = new Less_Visitor_toCSS();
+
+ if ( Less_Parser::$options['plugins'] ) {
+ foreach ( Less_Parser::$options['plugins'] as $plugin ) {
+ if ( property_exists( $plugin, 'isPreEvalVisitor' ) && $plugin->isPreEvalVisitor ) {
+ continue;
+ }
+
+ if ( property_exists( $plugin, 'isPreVisitor' ) && $plugin->isPreVisitor ) {
+ array_unshift( $visitors, $plugin );
+ } else {
+ $visitors[] = $plugin;
+ }
+ }
+ }
+
+ for ( $i = 0; $i < count( $visitors ); $i++ ) {
+ $visitors[$i]->run( $evaldRoot );
+ }
+
+ }
+
+ /**
+ * Parse a Less string into css
+ *
+ * @param string $str The string to convert
+ * @param string|null $file_uri The url of the file
+ * @return Less_Tree_Ruleset|Less_Parser
+ */
+ public function parse( $str, $file_uri = null ) {
+ if ( !$file_uri ) {
+ $uri_root = '';
+ $filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
+ } else {
+ $file_uri = self::WinPath( $file_uri );
+ $filename = $file_uri;
+ $uri_root = dirname( $file_uri );
+ }
+
+ $previousFileInfo = $this->env->currentFileInfo;
+ $uri_root = self::WinPath( $uri_root );
+ $this->SetFileInfo( $filename, $uri_root );
+
+ $this->input = $str;
+ $this->_parse();
+
+ if ( $previousFileInfo ) {
+ $this->env->currentFileInfo = $previousFileInfo;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Parse a Less string from a given file
+ *
+ * @throws Less_Exception_Parser
+ * @param string $filename The file to parse
+ * @param string $uri_root The url of the file
+ * @param bool $returnRoot Indicates whether the return value should be a css string a root node
+ * @return Less_Tree_Ruleset|Less_Parser
+ */
+ public function parseFile( $filename, $uri_root = '', $returnRoot = false ) {
+ if ( !file_exists( $filename ) ) {
+ $this->Error( sprintf( 'File `%s` not found.', $filename ) );
+ }
+
+ // fix uri_root?
+ // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
+ if ( !$returnRoot && !empty( $uri_root ) && basename( $uri_root ) == basename( $filename ) ) {
+ $uri_root = dirname( $uri_root );
+ }
+
+ $previousFileInfo = $this->env->currentFileInfo;
+
+ if ( $filename ) {
+ $filename = self::AbsPath( $filename, true );
+ }
+ $uri_root = self::WinPath( $uri_root );
+
+ $this->SetFileInfo( $filename, $uri_root );
+
+ self::AddParsedFile( $filename );
+
+ if ( $returnRoot ) {
+ $rules = $this->GetRules( $filename );
+ $return = new Less_Tree_Ruleset( array(), $rules );
+ } else {
+ $this->_parse( $filename );
+ $return = $this;
+ }
+
+ if ( $previousFileInfo ) {
+ $this->env->currentFileInfo = $previousFileInfo;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Allows a user to set variables values
+ * @param array $vars
+ * @return Less_Parser
+ */
+ public function ModifyVars( $vars ) {
+ $this->input = Less_Parser::serializeVars( $vars );
+ $this->_parse();
+
+ return $this;
+ }
+
+ /**
+ * @param string $filename
+ */
+ public function SetFileInfo( $filename, $uri_root = '' ) {
+ $filename = Less_Environment::normalizePath( $filename );
+ $dirname = preg_replace( '/[^\/\\\\]*$/', '', $filename );
+
+ if ( !empty( $uri_root ) ) {
+ $uri_root = rtrim( $uri_root, '/' ).'/';
+ }
+
+ $currentFileInfo = array();
+
+ // entry info
+ if ( isset( $this->env->currentFileInfo ) ) {
+ $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
+ $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
+ $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
+
+ } else {
+ $currentFileInfo['entryPath'] = $dirname;
+ $currentFileInfo['entryUri'] = $uri_root;
+ $currentFileInfo['rootpath'] = $dirname;
+ }
+
+ $currentFileInfo['currentDirectory'] = $dirname;
+ $currentFileInfo['currentUri'] = $uri_root.basename( $filename );
+ $currentFileInfo['filename'] = $filename;
+ $currentFileInfo['uri_root'] = $uri_root;
+
+ // inherit reference
+ if ( isset( $this->env->currentFileInfo['reference'] ) && $this->env->currentFileInfo['reference'] ) {
+ $currentFileInfo['reference'] = true;
+ }
+
+ $this->env->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @deprecated 1.5.1.2
+ */
+ public function SetCacheDir( $dir ) {
+ if ( !file_exists( $dir ) ) {
+ if ( mkdir( $dir ) ) {
+ return true;
+ }
+ throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.$dir );
+
+ } elseif ( !is_dir( $dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.$dir );
+
+ } elseif ( !is_writable( $dir ) ) {
+ throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.$dir );
+
+ } else {
+ $dir = self::WinPath( $dir );
+ Less_Cache::$cache_dir = rtrim( $dir, '/' ).'/';
+ return true;
+ }
+ }
+
+ /**
+ * Set a list of directories or callbacks the parser should use for determining import paths
+ *
+ * @param array $dirs
+ */
+ public function SetImportDirs( $dirs ) {
+ Less_Parser::$options['import_dirs'] = array();
+
+ foreach ( $dirs as $path => $uri_root ) {
+
+ $path = self::WinPath( $path );
+ if ( !empty( $path ) ) {
+ $path = rtrim( $path, '/' ).'/';
+ }
+
+ if ( !is_callable( $uri_root ) ) {
+ $uri_root = self::WinPath( $uri_root );
+ if ( !empty( $uri_root ) ) {
+ $uri_root = rtrim( $uri_root, '/' ).'/';
+ }
+ }
+
+ Less_Parser::$options['import_dirs'][$path] = $uri_root;
+ }
+ }
+
+ /**
+ * @param string $file_path
+ */
+ private function _parse( $file_path = null ) {
+ $this->rules = array_merge( $this->rules, $this->GetRules( $file_path ) );
+ }
+
+ /**
+ * Return the results of parsePrimary for $file_path
+ * Use cache and save cached results if possible
+ *
+ * @param string|null $file_path
+ */
+ private function GetRules( $file_path ) {
+ $this->SetInput( $file_path );
+
+ $cache_file = $this->CacheFile( $file_path );
+ if ( $cache_file ) {
+ if ( Less_Parser::$options['cache_method'] == 'callback' ) {
+ if ( is_callable( Less_Parser::$options['cache_callback_get'] ) ) {
+ $cache = call_user_func_array(
+ Less_Parser::$options['cache_callback_get'],
+ array( $this, $file_path, $cache_file )
+ );
+
+ if ( $cache ) {
+ $this->UnsetInput();
+ return $cache;
+ }
+ }
+
+ } elseif ( file_exists( $cache_file ) ) {
+ switch ( Less_Parser::$options['cache_method'] ) {
+
+ // Using serialize
+ // Faster but uses more memory
+ case 'serialize':
+ $cache = unserialize( file_get_contents( $cache_file ) );
+ if ( $cache ) {
+ touch( $cache_file );
+ $this->UnsetInput();
+ return $cache;
+ }
+ break;
+
+ // Using generated php code
+ case 'var_export':
+ case 'php':
+ $this->UnsetInput();
+ return include $cache_file;
+ }
+ }
+ }
+
+ $rules = $this->parsePrimary();
+
+ if ( $this->pos < $this->input_len ) {
+ throw new Less_Exception_Chunk( $this->input, null, $this->furthest, $this->env->currentFileInfo );
+ }
+
+ $this->UnsetInput();
+
+ // save the cache
+ if ( $cache_file ) {
+ if ( Less_Parser::$options['cache_method'] == 'callback' ) {
+ if ( is_callable( Less_Parser::$options['cache_callback_set'] ) ) {
+ call_user_func_array(
+ Less_Parser::$options['cache_callback_set'],
+ array( $this, $file_path, $cache_file, $rules )
+ );
+ }
+
+ } else {
+ // msg('write cache file');
+ switch ( Less_Parser::$options['cache_method'] ) {
+ case 'serialize':
+ file_put_contents( $cache_file, serialize( $rules ) );
+ break;
+ case 'php':
+ file_put_contents( $cache_file, '<?php return '.self::ArgString( $rules ).'; ?>' );
+ break;
+ case 'var_export':
+ // Requires __set_state()
+ file_put_contents( $cache_file, '<?php return '.var_export( $rules, true ).'; ?>' );
+ break;
+ }
+
+ Less_Cache::CleanCache();
+ }
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Set up the input buffer
+ */
+ public function SetInput( $file_path ) {
+ if ( $file_path ) {
+ $this->input = file_get_contents( $file_path );
+ }
+
+ $this->pos = $this->furthest = 0;
+
+ // Remove potential UTF Byte Order Mark
+ $this->input = preg_replace( '/\\G\xEF\xBB\xBF/', '', $this->input );
+ $this->input_len = strlen( $this->input );
+
+ if ( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ) {
+ $uri = $this->env->currentFileInfo['currentUri'];
+ Less_Parser::$contentsMap[$uri] = $this->input;
+ }
+
+ }
+
+ /**
+ * Free up some memory
+ */
+ public function UnsetInput() {
+ $this->input = $this->pos = $this->input_len = $this->furthest = null;
+ $this->saveStack = array();
+ }
+
+ public function CacheFile( $file_path ) {
+ if ( $file_path && $this->CacheEnabled() ) {
+
+ $env = get_object_vars( $this->env );
+ unset( $env['frames'] );
+
+ $parts = array();
+ $parts[] = $file_path;
+ $parts[] = filesize( $file_path );
+ $parts[] = filemtime( $file_path );
+ $parts[] = $env;
+ $parts[] = Less_Version::cache_version;
+ $parts[] = Less_Parser::$options['cache_method'];
+ return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1( json_encode( $parts ) ), 16, 36 ) . '.lesscache';
+ }
+ }
+
+ static function AddParsedFile( $file ) {
+ self::$imports[] = $file;
+ }
+
+ static function AllParsedFiles() {
+ return self::$imports;
+ }
+
+ /**
+ * @param string $file
+ */
+ static function FileParsed( $file ) {
+ return in_array( $file, self::$imports );
+ }
+
+ function save() {
+ $this->saveStack[] = $this->pos;
+ }
+
+ private function restore() {
+ $this->pos = array_pop( $this->saveStack );
+ }
+
+ private function forget() {
+ array_pop( $this->saveStack );
+ }
+
+ /**
+ * Determine if the character at the specified offset from the current position is a white space.
+ *
+ * @param int $offset
+ *
+ * @return bool
+ */
+ private function isWhitespace( $offset = 0 ) {
+ return strpos( " \t\n\r\v\f", $this->input[$this->pos + $offset] ) !== false;
+ }
+
+ /**
+ * Parse from a token, regexp or string, and move forward if match
+ *
+ * @param array $toks
+ * @return array
+ */
+ private function match( $toks ) {
+ // The match is confirmed, add the match length to `this::pos`,
+ // and consume any extra white-space characters (' ' || '\n')
+ // which come after that. The reason for this is that LeSS's
+ // grammar is mostly white-space insensitive.
+ //
+
+ foreach ( $toks as $tok ) {
+
+ $char = $tok[0];
+
+ if ( $char === '/' ) {
+ $match = $this->MatchReg( $tok );
+
+ if ( $match ) {
+ return count( $match ) === 1 ? $match[0] : $match;
+ }
+
+ } elseif ( $char === '#' ) {
+ $match = $this->MatchChar( $tok[1] );
+
+ } else {
+ // Non-terminal, match using a function call
+ $match = $this->$tok();
+
+ }
+
+ if ( $match ) {
+ return $match;
+ }
+ }
+ }
+
+ /**
+ * @param string[] $toks
+ *
+ * @return string
+ */
+ private function MatchFuncs( $toks ) {
+ if ( $this->pos < $this->input_len ) {
+ foreach ( $toks as $tok ) {
+ $match = $this->$tok();
+ if ( $match ) {
+ return $match;
+ }
+ }
+ }
+
+ }
+
+ // Match a single character in the input,
+ private function MatchChar( $tok ) {
+ if ( ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok ) ) {
+ $this->skipWhitespace( 1 );
+ return $tok;
+ }
+ }
+
+ // Match a regexp from the current start point
+ private function MatchReg( $tok ) {
+ if ( preg_match( $tok, $this->input, $match, 0, $this->pos ) ) {
+ $this->skipWhitespace( strlen( $match[0] ) );
+ return $match;
+ }
+ }
+
+ /**
+ * Same as match(), but don't change the state of the parser,
+ * just return the match.
+ *
+ * @param string $tok
+ * @return integer
+ */
+ public function PeekReg( $tok ) {
+ return preg_match( $tok, $this->input, $match, 0, $this->pos );
+ }
+
+ /**
+ * @param string $tok
+ */
+ public function PeekChar( $tok ) {
+ // return ($this->input[$this->pos] === $tok );
+ return ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok );
+ }
+
+ /**
+ * @param integer $length
+ */
+ public function skipWhitespace( $length ) {
+ $this->pos += $length;
+
+ for ( ; $this->pos < $this->input_len; $this->pos++ ) {
+ $c = $this->input[$this->pos];
+
+ if ( ( $c !== "\n" ) && ( $c !== "\r" ) && ( $c !== "\t" ) && ( $c !== ' ' ) ) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param string $tok
+ * @param string|null $msg
+ */
+ public function expect( $tok, $msg = NULL ) {
+ $result = $this->match( array( $tok ) );
+ if ( !$result ) {
+ $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * @param string $tok
+ */
+ public function expectChar( $tok, $msg = null ) {
+ $result = $this->MatchChar( $tok );
+ if ( !$result ) {
+ $msg = $msg ? $msg : "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'";
+ $this->Error( $msg );
+ } else {
+ return $result;
+ }
+ }
+
+ //
+ // Here in, the parsing rules/functions
+ //
+ // The basic structure of the syntax tree generated is as follows:
+ //
+ // Ruleset -> Rule -> Value -> Expression -> Entity
+ //
+ // Here's some LESS code:
+ //
+ // .class {
+ // color: #fff;
+ // border: 1px solid #000;
+ // width: @w + 4px;
+ // > .child {...}
+ // }
+ //
+ // And here's what the parse tree might look like:
+ //
+ // Ruleset (Selector '.class', [
+ // Rule ("color", Value ([Expression [Color #fff]]))
+ // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
+ // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
+ // Ruleset (Selector [Element '>', '.child'], [...])
+ // ])
+ //
+ // In general, most rules will try to parse a token with the `$()` function, and if the return
+ // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
+ // first, before parsing, that's when we use `peek()`.
+ //
+
+ //
+ // The `primary` rule is the *entry* and *exit* point of the parser.
+ // The rules here can appear at any level of the parse tree.
+ //
+ // The recursive nature of the grammar is an interplay between the `block`
+ // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
+ // as represented by this simplified grammar:
+ //
+ // primary → (ruleset | rule)+
+ // ruleset → selector+ block
+ // block → '{' primary '}'
+ //
+ // Only at one point is the primary rule not called from the
+ // block rule: at the root level.
+ //
+ private function parsePrimary() {
+ $root = array();
+
+ while ( true ) {
+
+ if ( $this->pos >= $this->input_len ) {
+ break;
+ }
+
+ $node = $this->parseExtend( true );
+ if ( $node ) {
+ $root = array_merge( $root, $node );
+ continue;
+ }
+
+ // $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
+ $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective' ) );
+
+ if ( $node ) {
+ $root[] = $node;
+ } elseif ( !$this->MatchReg( '/\\G[\s\n;]+/' ) ) {
+ break;
+ }
+
+ if ( $this->PeekChar( '}' ) ) {
+ break;
+ }
+ }
+
+ return $root;
+ }
+
+ // We create a Comment node for CSS comments `/* */`,
+ // but keep the LeSS comments `//` silent, by just skipping
+ // over them.
+ private function parseComment() {
+ if ( $this->input[$this->pos] !== '/' ) {
+ return;
+ }
+
+ if ( $this->input[$this->pos + 1] === '/' ) {
+ $match = $this->MatchReg( '/\\G\/\/.*/' );
+ return $this->NewObj4( 'Less_Tree_Comment', array( $match[0], true, $this->pos, $this->env->currentFileInfo ) );
+ }
+
+ // $comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
+ $comment = $this->MatchReg( '/\\G\/\*(?s).*?\*+\/\n?/' );// not the same as less.js to prevent fatal errors
+ if ( $comment ) {
+ return $this->NewObj4( 'Less_Tree_Comment', array( $comment[0], false, $this->pos, $this->env->currentFileInfo ) );
+ }
+ }
+
+ private function parseComments() {
+ $comments = array();
+
+ while ( $this->pos < $this->input_len ) {
+ $comment = $this->parseComment();
+ if ( !$comment ) {
+ break;
+ }
+
+ $comments[] = $comment;
+ }
+
+ return $comments;
+ }
+
+ //
+ // A string, which supports escaping " and '
+ //
+ // "milky way" 'he\'s the one!'
+ //
+ private function parseEntitiesQuoted() {
+ $j = $this->pos;
+ $e = false;
+ $index = $this->pos;
+
+ if ( $this->input[$this->pos] === '~' ) {
+ $j++;
+ $e = true; // Escaped strings
+ }
+
+ $char = $this->input[$j];
+ if ( $char !== '"' && $char !== "'" ) {
+ return;
+ }
+
+ if ( $e ) {
+ $this->MatchChar( '~' );
+ }
+
+ $matched = $this->MatchQuoted( $char, $j + 1 );
+ if ( $matched === false ) {
+ return;
+ }
+
+ $quoted = $char.$matched.$char;
+ return $this->NewObj5( 'Less_Tree_Quoted', array( $quoted, $matched, $e, $index, $this->env->currentFileInfo ) );
+ }
+
+ /**
+ * When PCRE JIT is enabled in php, regular expressions don't work for matching quoted strings
+ *
+ * $regex = '/\\G\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/';
+ * $regex = '/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"/';
+ *
+ */
+ private function MatchQuoted( $quote_char, $i ) {
+ $matched = '';
+ while ( $i < $this->input_len ) {
+ $c = $this->input[$i];
+
+ // escaped character
+ if ( $c === '\\' ) {
+ $matched .= $c . $this->input[$i + 1];
+ $i += 2;
+ continue;
+ }
+
+ if ( $c === $quote_char ) {
+ $this->pos = $i + 1;
+ $this->skipWhitespace( 0 );
+ return $matched;
+ }
+
+ if ( $c === "\r" || $c === "\n" ) {
+ return false;
+ }
+
+ $i++;
+ $matched .= $c;
+ }
+
+ return false;
+ }
+
+ //
+ // A catch-all word, such as:
+ //
+ // black border-collapse
+ //
+ private function parseEntitiesKeyword() {
+ // $k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
+ $k = $this->MatchReg( '/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/' );
+ if ( $k ) {
+ $k = $k[0];
+ $color = $this->fromKeyword( $k );
+ if ( $color ) {
+ return $color;
+ }
+ return $this->NewObj1( 'Less_Tree_Keyword', $k );
+ }
+ }
+
+ // duplicate of Less_Tree_Color::FromKeyword
+ private function FromKeyword( $keyword ) {
+ $keyword = strtolower( $keyword );
+
+ if ( Less_Colors::hasOwnProperty( $keyword ) ) {
+ // detect named color
+ return $this->NewObj1( 'Less_Tree_Color', substr( Less_Colors::color( $keyword ), 1 ) );
+ }
+
+ if ( $keyword === 'transparent' ) {
+ return $this->NewObj3( 'Less_Tree_Color', array( array( 0, 0, 0 ), 0, true ) );
+ }
+ }
+
+ //
+ // A function call
+ //
+ // rgb(255, 0, 255)
+ //
+ // We also try to catch IE's `alpha()`, but let the `alpha` parser
+ // deal with the details.
+ //
+ // The arguments are parsed with the `entities.arguments` parser.
+ //
+ private function parseEntitiesCall() {
+ $index = $this->pos;
+
+ if ( !preg_match( '/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name, 0, $this->pos ) ) {
+ return;
+ }
+ $name = $name[1];
+ $nameLC = strtolower( $name );
+
+ if ( $nameLC === 'url' ) {
+ return null;
+ }
+
+ $this->pos += strlen( $name );
+
+ if ( $nameLC === 'alpha' ) {
+ $alpha_ret = $this->parseAlpha();
+ if ( $alpha_ret ) {
+ return $alpha_ret;
+ }
+ }
+
+ $this->MatchChar( '(' ); // Parse the '(' and consume whitespace.
+
+ $args = $this->parseEntitiesArguments();
+
+ if ( !$this->MatchChar( ')' ) ) {
+ return;
+ }
+
+ if ( $name ) {
+ return $this->NewObj4( 'Less_Tree_Call', array( $name, $args, $index, $this->env->currentFileInfo ) );
+ }
+ }
+
+ /**
+ * Parse a list of arguments
+ *
+ * @return array
+ */
+ private function parseEntitiesArguments() {
+ $args = array();
+ while ( true ) {
+ $arg = $this->MatchFuncs( array( 'parseEntitiesAssignment','parseExpression' ) );
+ if ( !$arg ) {
+ break;
+ }
+
+ $args[] = $arg;
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ }
+ return $args;
+ }
+
+ private function parseEntitiesLiteral() {
+ return $this->MatchFuncs( array( 'parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor' ) );
+ }
+
+ // Assignments are argument entities for calls.
+ // They are present in ie filter properties as shown below.
+ //
+ // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
+ //
+ private function parseEntitiesAssignment() {
+ $key = $this->MatchReg( '/\\G\w+(?=\s?=)/' );
+ if ( !$key ) {
+ return;
+ }
+
+ if ( !$this->MatchChar( '=' ) ) {
+ return;
+ }
+
+ $value = $this->parseEntity();
+ if ( $value ) {
+ return $this->NewObj2( 'Less_Tree_Assignment', array( $key[0], $value ) );
+ }
+ }
+
+ //
+ // Parse url() tokens
+ //
+ // We use a specific rule for urls, because they don't really behave like
+ // standard function calls. The difference is that the argument doesn't have
+ // to be enclosed within a string, so it can't be parsed as an Expression.
+ //
+ private function parseEntitiesUrl() {
+ if ( $this->input[$this->pos] !== 'u' || !$this->matchReg( '/\\Gurl\(/' ) ) {
+ return;
+ }
+
+ $value = $this->match( array( 'parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/' ) );
+ if ( !$value ) {
+ $value = '';
+ }
+
+ $this->expectChar( ')' );
+
+ if ( isset( $value->value ) || $value instanceof Less_Tree_Variable ) {
+ return $this->NewObj2( 'Less_Tree_Url', array( $value, $this->env->currentFileInfo ) );
+ }
+
+ return $this->NewObj2( 'Less_Tree_Url', array( $this->NewObj1( 'Less_Tree_Anonymous', $value ), $this->env->currentFileInfo ) );
+ }
+
+ //
+ // A Variable entity, such as `@fink`, in
+ //
+ // width: @fink + 2px
+ //
+ // We use a different parser for variable definitions,
+ // see `parsers.variable`.
+ //
+ private function parseEntitiesVariable() {
+ $index = $this->pos;
+ if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G@@?[\w-]+/' ) ) ) {
+ return $this->NewObj3( 'Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo ) );
+ }
+ }
+
+ // A variable entity using the protective {} e.g. @{var}
+ private function parseEntitiesVariableCurly() {
+ $index = $this->pos;
+
+ if ( $this->input_len > ( $this->pos + 1 ) && $this->input[$this->pos] === '@' && ( $curly = $this->MatchReg( '/\\G@\{([\w-]+)\}/' ) ) ) {
+ return $this->NewObj3( 'Less_Tree_Variable', array( '@'.$curly[1], $index, $this->env->currentFileInfo ) );
+ }
+ }
+
+ //
+ // A Hexadecimal color
+ //
+ // #4F3C2F
+ //
+ // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
+ //
+ private function parseEntitiesColor() {
+ if ( $this->PeekChar( '#' ) && ( $rgb = $this->MatchReg( '/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/' ) ) ) {
+ return $this->NewObj1( 'Less_Tree_Color', $rgb[1] );
+ }
+ }
+
+ //
+ // A Dimension, that is, a number and a unit
+ //
+ // 0.5em 95%
+ //
+ private function parseEntitiesDimension() {
+ $c = @ord( $this->input[$this->pos] );
+
+ // Is the first char of the dimension 0-9, '.', '+' or '-'
+ if ( ( $c > 57 || $c < 43 ) || $c === 47 || $c == 44 ) {
+ return;
+ }
+
+ $value = $this->MatchReg( '/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/' );
+ if ( $value ) {
+
+ if ( isset( $value[2] ) ) {
+ return $this->NewObj2( 'Less_Tree_Dimension', array( $value[1],$value[2] ) );
+ }
+ return $this->NewObj1( 'Less_Tree_Dimension', $value[1] );
+ }
+ }
+
+ //
+ // A unicode descriptor, as is used in unicode-range
+ //
+ // U+0?? or U+00A1-00A9
+ //
+ function parseUnicodeDescriptor() {
+ $ud = $this->MatchReg( '/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/' );
+ if ( $ud ) {
+ return $this->NewObj1( 'Less_Tree_UnicodeDescriptor', $ud[0] );
+ }
+ }
+
+ //
+ // JavaScript code to be evaluated
+ //
+ // `window.location.href`
+ //
+ private function parseEntitiesJavascript() {
+ $e = false;
+ $j = $this->pos;
+ if ( $this->input[$j] === '~' ) {
+ $j++;
+ $e = true;
+ }
+ if ( $this->input[$j] !== '`' ) {
+ return;
+ }
+ if ( $e ) {
+ $this->MatchChar( '~' );
+ }
+ $str = $this->MatchReg( '/\\G`([^`]*)`/' );
+ if ( $str ) {
+ return $this->NewObj3( 'Less_Tree_Javascript', array( $str[1], $this->pos, $e ) );
+ }
+ }
+
+ //
+ // The variable part of a variable definition. Used in the `rule` parser
+ //
+ // @fink:
+ //
+ private function parseVariable() {
+ if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*:/' ) ) ) {
+ return $name[1];
+ }
+ }
+
+ //
+ // The variable part of a variable definition. Used in the `rule` parser
+ //
+ // @fink();
+ //
+ private function parseRulesetCall() {
+ if ( $this->input[$this->pos] === '@' && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*\(\s*\)\s*;/' ) ) ) {
+ return $this->NewObj1( 'Less_Tree_RulesetCall', $name[1] );
+ }
+ }
+
+ //
+ // extend syntax - used to extend selectors
+ //
+ function parseExtend( $isRule = false ) {
+ $index = $this->pos;
+ $extendList = array();
+
+ if ( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ) { return;
+ }
+
+ do{
+ $option = null;
+ $elements = array();
+ while ( true ) {
+ $option = $this->MatchReg( '/\\G(all)(?=\s*(\)|,))/' );
+ if ( $option ) { break;
+ }
+ $e = $this->parseElement();
+ if ( !$e ) { break;
+ }
+ $elements[] = $e;
+ }
+
+ if ( $option ) {
+ $option = $option[1];
+ }
+
+ $extendList[] = $this->NewObj3( 'Less_Tree_Extend', array( $this->NewObj1( 'Less_Tree_Selector', $elements ), $option, $index ) );
+
+ }while ( $this->MatchChar( "," ) );
+
+ $this->expect( '/\\G\)/' );
+
+ if ( $isRule ) {
+ $this->expect( '/\\G;/' );
+ }
+
+ return $extendList;
+ }
+
+ //
+ // A Mixin call, with an optional argument list
+ //
+ // #mixins > .square(#fff);
+ // .rounded(4px, black);
+ // .button;
+ //
+ // The `while` loop is there because mixins can be
+ // namespaced, but we only support the child and descendant
+ // selector for now.
+ //
+ private function parseMixinCall() {
+ $char = $this->input[$this->pos];
+ if ( $char !== '.' && $char !== '#' ) {
+ return;
+ }
+
+ $index = $this->pos;
+ $this->save(); // stop us absorbing part of an invalid selector
+
+ $elements = $this->parseMixinCallElements();
+
+ if ( $elements ) {
+
+ if ( $this->MatchChar( '(' ) ) {
+ $returned = $this->parseMixinArgs( true );
+ $args = $returned['args'];
+ $this->expectChar( ')' );
+ } else {
+ $args = array();
+ }
+
+ $important = $this->parseImportant();
+
+ if ( $this->parseEnd() ) {
+ $this->forget();
+ return $this->NewObj5( 'Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important ) );
+ }
+ }
+
+ $this->restore();
+ }
+
+ private function parseMixinCallElements() {
+ $elements = array();
+ $c = null;
+
+ while ( true ) {
+ $elemIndex = $this->pos;
+ $e = $this->MatchReg( '/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/' );
+ if ( !$e ) {
+ break;
+ }
+ $elements[] = $this->NewObj4( 'Less_Tree_Element', array( $c, $e[0], $elemIndex, $this->env->currentFileInfo ) );
+ $c = $this->MatchChar( '>' );
+ }
+
+ return $elements;
+ }
+
+ /**
+ * @param boolean $isCall
+ */
+ private function parseMixinArgs( $isCall ) {
+ $expressions = array();
+ $argsSemiColon = array();
+ $isSemiColonSeperated = null;
+ $argsComma = array();
+ $expressionContainsNamed = null;
+ $name = null;
+ $returner = array( 'args' => array(), 'variadic' => false );
+
+ $this->save();
+
+ while ( true ) {
+ if ( $isCall ) {
+ $arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
+ } else {
+ $this->parseComments();
+ if ( $this->input[ $this->pos ] === '.' && $this->MatchReg( '/\\G\.{3}/' ) ) {
+ $returner['variadic'] = true;
+ if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
+ $isSemiColonSeperated = true;
+ }
+
+ if ( $isSemiColonSeperated ) {
+ $argsSemiColon[] = array( 'variadic' => true );
+ } else {
+ $argsComma[] = array( 'variadic' => true );
+ }
+ break;
+ }
+ $arg = $this->MatchFuncs( array( 'parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword' ) );
+ }
+
+ if ( !$arg ) {
+ break;
+ }
+
+ $nameLoop = null;
+ if ( $arg instanceof Less_Tree_Expression ) {
+ $arg->throwAwayComments();
+ }
+ $value = $arg;
+ $val = null;
+
+ if ( $isCall ) {
+ // Variable
+ if ( property_exists( $arg, 'value' ) && count( $arg->value ) == 1 ) {
+ $val = $arg->value[0];
+ }
+ } else {
+ $val = $arg;
+ }
+
+ if ( $val instanceof Less_Tree_Variable ) {
+
+ if ( $this->MatchChar( ':' ) ) {
+ if ( $expressions ) {
+ if ( $isSemiColonSeperated ) {
+ $this->Error( 'Cannot mix ; and , as delimiter types' );
+ }
+ $expressionContainsNamed = true;
+ }
+
+ // we do not support setting a ruleset as a default variable - it doesn't make sense
+ // However if we do want to add it, there is nothing blocking it, just don't error
+ // and remove isCall dependency below
+ $value = null;
+ if ( $isCall ) {
+ $value = $this->parseDetachedRuleset();
+ }
+ if ( !$value ) {
+ $value = $this->parseExpression();
+ }
+
+ if ( !$value ) {
+ if ( $isCall ) {
+ $this->Error( 'could not understand value for named argument' );
+ } else {
+ $this->restore();
+ $returner['args'] = array();
+ return $returner;
+ }
+ }
+
+ $nameLoop = ( $name = $val->name );
+ } elseif ( !$isCall && $this->MatchReg( '/\\G\.{3}/' ) ) {
+ $returner['variadic'] = true;
+ if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
+ $isSemiColonSeperated = true;
+ }
+ if ( $isSemiColonSeperated ) {
+ $argsSemiColon[] = array( 'name' => $arg->name, 'variadic' => true );
+ } else {
+ $argsComma[] = array( 'name' => $arg->name, 'variadic' => true );
+ }
+ break;
+ } elseif ( !$isCall ) {
+ $name = $nameLoop = $val->name;
+ $value = null;
+ }
+ }
+
+ if ( $value ) {
+ $expressions[] = $value;
+ }
+
+ $argsComma[] = array( 'name' => $nameLoop, 'value' => $value );
+
+ if ( $this->MatchChar( ',' ) ) {
+ continue;
+ }
+
+ if ( $this->MatchChar( ';' ) || $isSemiColonSeperated ) {
+
+ if ( $expressionContainsNamed ) {
+ $this->Error( 'Cannot mix ; and , as delimiter types' );
+ }
+
+ $isSemiColonSeperated = true;
+
+ if ( count( $expressions ) > 1 ) {
+ $value = $this->NewObj1( 'Less_Tree_Value', $expressions );
+ }
+ $argsSemiColon[] = array( 'name' => $name, 'value' => $value );
+
+ $name = null;
+ $expressions = array();
+ $expressionContainsNamed = false;
+ }
+ }
+
+ $this->forget();
+ $returner['args'] = ( $isSemiColonSeperated ? $argsSemiColon : $argsComma );
+ return $returner;
+ }
+
+ //
+ // A Mixin definition, with a list of parameters
+ //
+ // .rounded (@radius: 2px, @color) {
+ // ...
+ // }
+ //
+ // Until we have a finer grained state-machine, we have to
+ // do a look-ahead, to make sure we don't have a mixin call.
+ // See the `rule` function for more information.
+ //
+ // We start by matching `.rounded (`, and then proceed on to
+ // the argument list, which has optional default values.
+ // We store the parameters in `params`, with a `value` key,
+ // if there is a value, such as in the case of `@radius`.
+ //
+ // Once we've got our params list, and a closing `)`, we parse
+ // the `{...}` block.
+ //
+ private function parseMixinDefinition() {
+ $cond = null;
+
+ $char = $this->input[$this->pos];
+ if ( ( $char !== '.' && $char !== '#' ) || ( $char === '{' && $this->PeekReg( '/\\G[^{]*\}/' ) ) ) {
+ return;
+ }
+
+ $this->save();
+
+ $match = $this->MatchReg( '/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/' );
+ if ( $match ) {
+ $name = $match[1];
+
+ $argInfo = $this->parseMixinArgs( false );
+ $params = $argInfo['args'];
+ $variadic = $argInfo['variadic'];
+
+ // .mixincall("@{a}");
+ // looks a bit like a mixin definition..
+ // also
+ // .mixincall(@a: {rule: set;});
+ // so we have to be nice and restore
+ if ( !$this->MatchChar( ')' ) ) {
+ $this->furthest = $this->pos;
+ $this->restore();
+ return;
+ }
+
+ $this->parseComments();
+
+ if ( $this->MatchReg( '/\\Gwhen/' ) ) { // Guard
+ $cond = $this->expect( 'parseConditions', 'Expected conditions' );
+ }
+
+ $ruleset = $this->parseBlock();
+
+ if ( is_array( $ruleset ) ) {
+ $this->forget();
+ return $this->NewObj5( 'Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic ) );
+ }
+
+ $this->restore();
+ } else {
+ $this->forget();
+ }
+ }
+
+ //
+ // Entities are the smallest recognized token,
+ // and can be found inside a rule's value.
+ //
+ private function parseEntity() {
+ return $this->MatchFuncs( array( 'parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment' ) );
+ }
+
+ //
+ // A Rule terminator. Note that we use `peek()` to check for '}',
+ // because the `block` rule will be expecting it, but we still need to make sure
+ // it's there, if ';' was omitted.
+ //
+ private function parseEnd() {
+ return $this->MatchChar( ';' ) || $this->PeekChar( '}' );
+ }
+
+ //
+ // IE's alpha function
+ //
+ // alpha(opacity=88)
+ //
+ private function parseAlpha() {
+ if ( !$this->MatchReg( '/\\G\(opacity=/i' ) ) {
+ return;
+ }
+
+ $value = $this->MatchReg( '/\\G[0-9]+/' );
+ if ( $value ) {
+ $value = $value[0];
+ } else {
+ $value = $this->parseEntitiesVariable();
+ if ( !$value ) {
+ return;
+ }
+ }
+
+ $this->expectChar( ')' );
+ return $this->NewObj1( 'Less_Tree_Alpha', $value );
+ }
+
+ //
+ // A Selector Element
+ //
+ // div
+ // + h1
+ // #socks
+ // input[type="text"]
+ //
+ // Elements are the building blocks for Selectors,
+ // they are made out of a `Combinator` (see combinator rule),
+ // and an element name, such as a tag a class, or `*`.
+ //
+ private function parseElement() {
+ $c = $this->parseCombinator();
+ $index = $this->pos;
+
+ $e = $this->match( array( '/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
+ '#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly' ) );
+
+ if ( is_null( $e ) ) {
+ $this->save();
+ if ( $this->MatchChar( '(' ) ) {
+ if ( ( $v = $this->parseSelector() ) && $this->MatchChar( ')' ) ) {
+ $e = $this->NewObj1( 'Less_Tree_Paren', $v );
+ $this->forget();
+ } else {
+ $this->restore();
+ }
+ } else {
+ $this->forget();
+ }
+ }
+
+ if ( !is_null( $e ) ) {
+ return $this->NewObj4( 'Less_Tree_Element', array( $c, $e, $index, $this->env->currentFileInfo ) );
+ }
+ }
+
+ //
+ // Combinators combine elements together, in a Selector.
+ //
+ // Because our parser isn't white-space sensitive, special care
+ // has to be taken, when parsing the descendant combinator, ` `,
+ // as it's an empty space. We have to check the previous character
+ // in the input, to see if it's a ` ` character.
+ //
+ private function parseCombinator() {
+ if ( $this->pos < $this->input_len ) {
+ $c = $this->input[$this->pos];
+ if ( $c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ) {
+
+ $this->pos++;
+ if ( $this->input[$this->pos] === '^' ) {
+ $c = '^^';
+ $this->pos++;
+ }
+
+ $this->skipWhitespace( 0 );
+
+ return $c;
+ }
+
+ if ( $this->pos > 0 && $this->isWhitespace( -1 ) ) {
+ return ' ';
+ }
+ }
+ }
+
+ //
+ // A CSS selector (see selector below)
+ // with less extensions e.g. the ability to extend and guard
+ //
+ private function parseLessSelector() {
+ return $this->parseSelector( true );
+ }
+
+ //
+ // A CSS Selector
+ //
+ // .class > div + h1
+ // li a:hover
+ //
+ // Selectors are made out of one or more Elements, see above.
+ //
+ private function parseSelector( $isLess = false ) {
+ $elements = array();
+ $extendList = array();
+ $condition = null;
+ $when = false;
+ $extend = false;
+ $e = null;
+ $c = null;
+ $index = $this->pos;
+
+ while ( ( $isLess && ( $extend = $this->parseExtend() ) ) || ( $isLess && ( $when = $this->MatchReg( '/\\Gwhen/' ) ) ) || ( $e = $this->parseElement() ) ) {
+ if ( $when ) {
+ $condition = $this->expect( 'parseConditions', 'expected condition' );
+ } elseif ( $condition ) {
+ // error("CSS guard can only be used at the end of selector");
+ } elseif ( $extend ) {
+ $extendList = array_merge( $extendList, $extend );
+ } else {
+ // if( count($extendList) ){
+ //error("Extend can only be used at the end of selector");
+ //}
+ if ( $this->pos < $this->input_len ) {
+ $c = $this->input[ $this->pos ];
+ }
+ $elements[] = $e;
+ $e = null;
+ }
+
+ if ( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')' ) { break;
+ }
+ }
+
+ if ( $elements ) {
+ return $this->NewObj5( 'Less_Tree_Selector', array( $elements, $extendList, $condition, $index, $this->env->currentFileInfo ) );
+ }
+ if ( $extendList ) {
+ $this->Error( 'Extend must be used to extend a selector, it cannot be used on its own' );
+ }
+ }
+
+ private function parseTag() {
+ return ( $tag = $this->MatchReg( '/\\G[A-Za-z][A-Za-z-]*[0-9]?/' ) ) ? $tag : $this->MatchChar( '*' );
+ }
+
+ private function parseAttribute() {
+ $val = null;
+
+ if ( !$this->MatchChar( '[' ) ) {
+ return;
+ }
+
+ $key = $this->parseEntitiesVariableCurly();
+ if ( !$key ) {
+ $key = $this->expect( '/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/' );
+ }
+
+ $op = $this->MatchReg( '/\\G[|~*$^]?=/' );
+ if ( $op ) {
+ $val = $this->match( array( 'parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly' ) );
+ }
+
+ $this->expectChar( ']' );
+
+ return $this->NewObj3( 'Less_Tree_Attribute', array( $key, $op === null ? null : $op[0], $val ) );
+ }
+
+ //
+ // The `block` rule is used by `ruleset` and `mixin.definition`.
+ // It's a wrapper around the `primary` rule, with added `{}`.
+ //
+ private function parseBlock() {
+ if ( $this->MatchChar( '{' ) ) {
+ $content = $this->parsePrimary();
+ if ( $this->MatchChar( '}' ) ) {
+ return $content;
+ }
+ }
+ }
+
+ private function parseBlockRuleset() {
+ $block = $this->parseBlock();
+
+ if ( $block ) {
+ $block = $this->NewObj2( 'Less_Tree_Ruleset', array( null, $block ) );
+ }
+
+ return $block;
+ }
+
+ private function parseDetachedRuleset() {
+ $blockRuleset = $this->parseBlockRuleset();
+ if ( $blockRuleset ) {
+ return $this->NewObj1( 'Less_Tree_DetachedRuleset', $blockRuleset );
+ }
+ }
+
+ //
+ // div, .class, body > p {...}
+ //
+ private function parseRuleset() {
+ $selectors = array();
+
+ $this->save();
+
+ while ( true ) {
+ $s = $this->parseLessSelector();
+ if ( !$s ) {
+ break;
+ }
+ $selectors[] = $s;
+ $this->parseComments();
+
+ if ( $s->condition && count( $selectors ) > 1 ) {
+ $this->Error( 'Guards are only currently allowed on a single selector.' );
+ }
+
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ if ( $s->condition ) {
+ $this->Error( 'Guards are only currently allowed on a single selector.' );
+ }
+ $this->parseComments();
+ }
+
+ if ( $selectors ) {
+ $rules = $this->parseBlock();
+ if ( is_array( $rules ) ) {
+ $this->forget();
+ return $this->NewObj2( 'Less_Tree_Ruleset', array( $selectors, $rules ) ); // Less_Environment::$strictImports
+ }
+ }
+
+ // Backtrack
+ $this->furthest = $this->pos;
+ $this->restore();
+ }
+
+ /**
+ * Custom less.php parse function for finding simple name-value css pairs
+ * ex: width:100px;
+ */
+ private function parseNameValue() {
+ $index = $this->pos;
+ $this->save();
+
+ // $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
+ $match = $this->MatchReg( '/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/' );
+ if ( $match ) {
+
+ if ( $match[4] == '}' ) {
+ $this->pos = $index + strlen( $match[0] ) - 1;
+ }
+
+ if ( $match[3] ) {
+ $match[2] .= ' !important';
+ }
+
+ return $this->NewObj4( 'Less_Tree_NameValue', array( $match[1], $match[2], $index, $this->env->currentFileInfo ) );
+ }
+
+ $this->restore();
+ }
+
+ private function parseRule( $tryAnonymous = null ) {
+ $merge = false;
+ $startOfRule = $this->pos;
+
+ $c = $this->input[$this->pos];
+ if ( $c === '.' || $c === '#' || $c === '&' ) {
+ return;
+ }
+
+ $this->save();
+ $name = $this->MatchFuncs( array( 'parseVariable','parseRuleProperty' ) );
+
+ if ( $name ) {
+
+ $isVariable = is_string( $name );
+
+ $value = null;
+ if ( $isVariable ) {
+ $value = $this->parseDetachedRuleset();
+ }
+
+ $important = null;
+ if ( !$value ) {
+
+ // prefer to try to parse first if its a variable or we are compressing
+ // but always fallback on the other one
+ //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
+ if ( !$tryAnonymous && ( Less_Parser::$options['compress'] || $isVariable ) ) {
+ $value = $this->MatchFuncs( array( 'parseValue','parseAnonymousValue' ) );
+ } else {
+ $value = $this->MatchFuncs( array( 'parseAnonymousValue','parseValue' ) );
+ }
+
+ $important = $this->parseImportant();
+
+ // a name returned by this.ruleProperty() is always an array of the form:
+ // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
+ // where each item is a tree.Keyword or tree.Variable
+ if ( !$isVariable && is_array( $name ) ) {
+ $nm = array_pop( $name );
+ if ( $nm->value ) {
+ $merge = $nm->value;
+ }
+ }
+ }
+
+ if ( $value && $this->parseEnd() ) {
+ $this->forget();
+ return $this->NewObj6( 'Less_Tree_Rule', array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo ) );
+ } else {
+ $this->furthest = $this->pos;
+ $this->restore();
+ if ( $value && !$tryAnonymous ) {
+ return $this->parseRule( true );
+ }
+ }
+ } else {
+ $this->forget();
+ }
+ }
+
+ function parseAnonymousValue() {
+ if ( preg_match( '/\\G([^@+\/\'"*`(;{}-]*);/', $this->input, $match, 0, $this->pos ) ) {
+ $this->pos += strlen( $match[1] );
+ return $this->NewObj1( 'Less_Tree_Anonymous', $match[1] );
+ }
+ }
+
+ //
+ // An @import directive
+ //
+ // @import "lib";
+ //
+ // Depending on our environment, importing is done differently:
+ // In the browser, it's an XHR request, in Node, it would be a
+ // file-system operation. The function used for importing is
+ // stored in `import`, which we pass to the Import constructor.
+ //
+ private function parseImport() {
+ $this->save();
+
+ $dir = $this->MatchReg( '/\\G@import?\s+/' );
+
+ if ( $dir ) {
+ $options = $this->parseImportOptions();
+ $path = $this->MatchFuncs( array( 'parseEntitiesQuoted','parseEntitiesUrl' ) );
+
+ if ( $path ) {
+ $features = $this->parseMediaFeatures();
+ if ( $this->MatchChar( ';' ) ) {
+ if ( $features ) {
+ $features = $this->NewObj1( 'Less_Tree_Value', $features );
+ }
+
+ $this->forget();
+ return $this->NewObj5( 'Less_Tree_Import', array( $path, $features, $options, $this->pos, $this->env->currentFileInfo ) );
+ }
+ }
+ }
+
+ $this->restore();
+ }
+
+ private function parseImportOptions() {
+ $options = array();
+
+ // list of options, surrounded by parens
+ if ( !$this->MatchChar( '(' ) ) {
+ return $options;
+ }
+ do{
+ $optionName = $this->parseImportOption();
+ if ( $optionName ) {
+ $value = true;
+ switch ( $optionName ) {
+ case "css":
+ $optionName = "less";
+ $value = false;
+ break;
+ case "once":
+ $optionName = "multiple";
+ $value = false;
+ break;
+ }
+ $options[$optionName] = $value;
+ if ( !$this->MatchChar( ',' ) ) { break;
+ }
+ }
+ }while ( $optionName );
+ $this->expectChar( ')' );
+ return $options;
+ }
+
+ private function parseImportOption() {
+ $opt = $this->MatchReg( '/\\G(less|css|multiple|once|inline|reference|optional)/' );
+ if ( $opt ) {
+ return $opt[1];
+ }
+ }
+
+ private function parseMediaFeature() {
+ $nodes = array();
+
+ do{
+ $e = $this->MatchFuncs( array( 'parseEntitiesKeyword','parseEntitiesVariable' ) );
+ if ( $e ) {
+ $nodes[] = $e;
+ } elseif ( $this->MatchChar( '(' ) ) {
+ $p = $this->parseProperty();
+ $e = $this->parseValue();
+ if ( $this->MatchChar( ')' ) ) {
+ if ( $p && $e ) {
+ $r = $this->NewObj7( 'Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true ) );
+ $nodes[] = $this->NewObj1( 'Less_Tree_Paren', $r );
+ } elseif ( $e ) {
+ $nodes[] = $this->NewObj1( 'Less_Tree_Paren', $e );
+ } else {
+ return null;
+ }
+ } else return null;
+ }
+ } while ( $e );
+
+ if ( $nodes ) {
+ return $this->NewObj1( 'Less_Tree_Expression', $nodes );
+ }
+ }
+
+ private function parseMediaFeatures() {
+ $features = array();
+
+ do {
+ $e = $this->parseMediaFeature();
+ if ( $e ) {
+ $features[] = $e;
+ if ( !$this->MatchChar( ',' ) ) break;
+ } else {
+ $e = $this->parseEntitiesVariable();
+ if ( $e ) {
+ $features[] = $e;
+ if ( !$this->MatchChar( ',' ) ) break;
+ }
+ }
+ } while ( $e );
+
+ return $features ?: null;
+ }
+
+ private function parseMedia() {
+ if ( $this->MatchReg( '/\\G@media/' ) ) {
+ $features = $this->parseMediaFeatures();
+ $rules = $this->parseBlock();
+
+ if ( is_array( $rules ) ) {
+ return $this->NewObj4( 'Less_Tree_Media', array( $rules, $features, $this->pos, $this->env->currentFileInfo ) );
+ }
+ }
+ }
+
+ //
+ // A CSS Directive
+ //
+ // @charset "utf-8";
+ //
+ private function parseDirective() {
+ if ( !$this->PeekChar( '@' ) ) {
+ return;
+ }
+
+ $rules = null;
+ $index = $this->pos;
+ $hasBlock = true;
+ $hasIdentifier = false;
+ $hasExpression = false;
+ $hasUnknown = false;
+
+ $value = $this->MatchFuncs( array( 'parseImport','parseMedia' ) );
+ if ( $value ) {
+ return $value;
+ }
+
+ $this->save();
+
+ $name = $this->MatchReg( '/\\G@[a-z-]+/' );
+
+ if ( !$name ) return;
+ $name = $name[0];
+
+ $nonVendorSpecificName = $name;
+ $pos = strpos( $name, '-', 2 );
+ if ( $name[1] == '-' && $pos > 0 ) {
+ $nonVendorSpecificName = "@" . substr( $name, $pos + 1 );
+ }
+
+ switch ( $nonVendorSpecificName ) {
+ /*
+ case "@font-face":
+ case "@viewport":
+ case "@top-left":
+ case "@top-left-corner":
+ case "@top-center":
+ case "@top-right":
+ case "@top-right-corner":
+ case "@bottom-left":
+ case "@bottom-left-corner":
+ case "@bottom-center":
+ case "@bottom-right":
+ case "@bottom-right-corner":
+ case "@left-top":
+ case "@left-middle":
+ case "@left-bottom":
+ case "@right-top":
+ case "@right-middle":
+ case "@right-bottom":
+ hasBlock = true;
+ break;
+ */
+ case "@charset":
+ $hasIdentifier = true;
+ $hasBlock = false;
+ break;
+ case "@namespace":
+ $hasExpression = true;
+ $hasBlock = false;
+ break;
+ case "@keyframes":
+ $hasIdentifier = true;
+ break;
+ case "@host":
+ case "@page":
+ case "@document":
+ case "@supports":
+ $hasUnknown = true;
+ break;
+ }
+
+ if ( $hasIdentifier ) {
+ $value = $this->parseEntity();
+ if ( !$value ) {
+ $this->error( "expected " . $name . " identifier" );
+ }
+ } else if ( $hasExpression ) {
+ $value = $this->parseExpression();
+ if ( !$value ) {
+ $this->error( "expected " . $name. " expression" );
+ }
+ } else if ( $hasUnknown ) {
+
+ $value = $this->MatchReg( '/\\G[^{;]+/' );
+ if ( $value ) {
+ $value = $this->NewObj1( 'Less_Tree_Anonymous', trim( $value[0] ) );
+ }
+ }
+
+ if ( $hasBlock ) {
+ $rules = $this->parseBlockRuleset();
+ }
+
+ if ( $rules || ( !$hasBlock && $value && $this->MatchChar( ';' ) ) ) {
+ $this->forget();
+ return $this->NewObj5( 'Less_Tree_Directive', array( $name, $value, $rules, $index, $this->env->currentFileInfo ) );
+ }
+
+ $this->restore();
+ }
+
+ //
+ // A Value is a comma-delimited list of Expressions
+ //
+ // font-family: Baskerville, Georgia, serif;
+ //
+ // In a Rule, a Value represents everything after the `:`,
+ // and before the `;`.
+ //
+ private function parseValue() {
+ $expressions = array();
+
+ do{
+ $e = $this->parseExpression();
+ if ( $e ) {
+ $expressions[] = $e;
+ if ( !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ }
+ }while ( $e );
+
+ if ( $expressions ) {
+ return $this->NewObj1( 'Less_Tree_Value', $expressions );
+ }
+ }
+
+ private function parseImportant() {
+ if ( $this->PeekChar( '!' ) && $this->MatchReg( '/\\G! *important/' ) ) {
+ return ' !important';
+ }
+ }
+
+ private function parseSub() {
+ if ( $this->MatchChar( '(' ) ) {
+ $a = $this->parseAddition();
+ if ( $a ) {
+ $this->expectChar( ')' );
+ return $this->NewObj2( 'Less_Tree_Expression', array( array( $a ), true ) ); // instead of $e->parens = true so the value is cached
+ }
+ }
+ }
+
+ /**
+ * Parses multiplication operation
+ *
+ * @return Less_Tree_Operation|null
+ */
+ function parseMultiplication() {
+ $return = $m = $this->parseOperand();
+ if ( $return ) {
+ while ( true ) {
+
+ $isSpaced = $this->isWhitespace( -1 );
+
+ if ( $this->PeekReg( '/\\G\/[*\/]/' ) ) {
+ break;
+ }
+
+ $op = $this->MatchChar( '/' );
+ if ( !$op ) {
+ $op = $this->MatchChar( '*' );
+ if ( !$op ) {
+ break;
+ }
+ }
+
+ $a = $this->parseOperand();
+
+ if ( !$a ) { break;
+ }
+
+ $m->parensInOp = true;
+ $a->parensInOp = true;
+ $return = $this->NewObj3( 'Less_Tree_Operation', array( $op, array( $return, $a ), $isSpaced ) );
+ }
+ }
+ return $return;
+
+ }
+
+ /**
+ * Parses an addition operation
+ *
+ * @return Less_Tree_Operation|null
+ */
+ private function parseAddition() {
+ $return = $m = $this->parseMultiplication();
+ if ( $return ) {
+ while ( true ) {
+
+ $isSpaced = $this->isWhitespace( -1 );
+
+ $op = $this->MatchReg( '/\\G[-+]\s+/' );
+ if ( $op ) {
+ $op = $op[0];
+ } else {
+ if ( !$isSpaced ) {
+ $op = $this->match( array( '#+','#-' ) );
+ }
+ if ( !$op ) {
+ break;
+ }
+ }
+
+ $a = $this->parseMultiplication();
+ if ( !$a ) {
+ break;
+ }
+
+ $m->parensInOp = true;
+ $a->parensInOp = true;
+ $return = $this->NewObj3( 'Less_Tree_Operation', array( $op, array( $return, $a ), $isSpaced ) );
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Parses the conditions
+ *
+ * @return Less_Tree_Condition|null
+ */
+ private function parseConditions() {
+ $index = $this->pos;
+ $return = $a = $this->parseCondition();
+ if ( $a ) {
+ while ( true ) {
+ if ( !$this->PeekReg( '/\\G,\s*(not\s*)?\(/' ) || !$this->MatchChar( ',' ) ) {
+ break;
+ }
+ $b = $this->parseCondition();
+ if ( !$b ) {
+ break;
+ }
+
+ $return = $this->NewObj4( 'Less_Tree_Condition', array( 'or', $return, $b, $index ) );
+ }
+ return $return;
+ }
+ }
+
+ private function parseCondition() {
+ $index = $this->pos;
+ $negate = false;
+ $c = null;
+
+ if ( $this->MatchReg( '/\\Gnot/' ) ) $negate = true;
+ $this->expectChar( '(' );
+ $a = $this->MatchFuncs( array( 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ) );
+
+ if ( $a ) {
+ $op = $this->MatchReg( '/\\G(?:>=|<=|=<|[<=>])/' );
+ if ( $op ) {
+ $b = $this->MatchFuncs( array( 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ) );
+ if ( $b ) {
+ $c = $this->NewObj5( 'Less_Tree_Condition', array( $op[0], $a, $b, $index, $negate ) );
+ } else {
+ $this->Error( 'Unexpected expression' );
+ }
+ } else {
+ $k = $this->NewObj1( 'Less_Tree_Keyword', 'true' );
+ $c = $this->NewObj5( 'Less_Tree_Condition', array( '=', $a, $k, $index, $negate ) );
+ }
+ $this->expectChar( ')' );
+ return $this->MatchReg( '/\\Gand/' ) ? $this->NewObj3( 'Less_Tree_Condition', array( 'and', $c, $this->parseCondition() ) ) : $c;
+ }
+ }
+
+ /**
+ * An operand is anything that can be part of an operation,
+ * such as a Color, or a Variable
+ */
+ private function parseOperand() {
+ $negate = false;
+ $offset = $this->pos + 1;
+ if ( $offset >= $this->input_len ) {
+ return;
+ }
+ $char = $this->input[$offset];
+ if ( $char === '@' || $char === '(' ) {
+ $negate = $this->MatchChar( '-' );
+ }
+
+ $o = $this->MatchFuncs( array( 'parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall' ) );
+
+ if ( $negate ) {
+ $o->parensInOp = true;
+ $o = $this->NewObj1( 'Less_Tree_Negative', $o );
+ }
+
+ return $o;
+ }
+
+ /**
+ * Expressions either represent mathematical operations,
+ * or white-space delimited Entities.
+ *
+ * @return Less_Tree_Expression|null
+ */
+ private function parseExpression() {
+ $entities = array();
+
+ do {
+ $e = $this->MatchFuncs( array( 'parseAddition','parseEntity' ) );
+ if ( $e ) {
+ $entities[] = $e;
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
+ if ( !$this->PeekReg( '/\\G\/[\/*]/' ) ) {
+ $delim = $this->MatchChar( '/' );
+ if ( $delim ) {
+ $entities[] = $this->NewObj1( 'Less_Tree_Anonymous', $delim );
+ }
+ }
+ }
+ } while ( $e );
+
+ if ( $entities ) {
+ return $this->NewObj1( 'Less_Tree_Expression', $entities );
+ }
+ }
+
+ /**
+ * Parse a property
+ * eg: 'min-width', 'orientation', etc
+ *
+ * @return string
+ */
+ private function parseProperty() {
+ $name = $this->MatchReg( '/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/' );
+ if ( $name ) {
+ return $name[1];
+ }
+ }
+
+ /**
+ * Parse a rule property
+ * eg: 'color', 'width', 'height', etc
+ *
+ * @return string
+ */
+ private function parseRuleProperty() {
+ $offset = $this->pos;
+ $name = array();
+ $index = array();
+ $length = 0;
+
+ $this->rulePropertyMatch( '/\\G(\*?)/', $offset, $length, $index, $name );
+ while ( $this->rulePropertyMatch( '/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name ) ); // !
+
+ if ( ( count( $name ) > 1 ) && $this->rulePropertyMatch( '/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name ) ) {
+ // at last, we have the complete match now. move forward,
+ // convert name particles to tree objects and return:
+ $this->skipWhitespace( $length );
+
+ if ( $name[0] === '' ) {
+ array_shift( $name );
+ array_shift( $index );
+ }
+ foreach ( $name as $k => $s ) {
+ if ( !$s || $s[0] !== '@' ) {
+ $name[$k] = $this->NewObj1( 'Less_Tree_Keyword', $s );
+ } else {
+ $name[$k] = $this->NewObj3( 'Less_Tree_Variable', array( '@' . substr( $s, 2, -1 ), $index[$k], $this->env->currentFileInfo ) );
+ }
+ }
+ return $name;
+ }
+
+ }
+
+ private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ) {
+ preg_match( $re, $this->input, $a, 0, $offset );
+ if ( $a ) {
+ $index[] = $this->pos + $length;
+ $length += strlen( $a[0] );
+ $offset += strlen( $a[0] );
+ $name[] = $a[1];
+ return true;
+ }
+ }
+
+ public static function serializeVars( $vars ) {
+ $s = '';
+
+ foreach ( $vars as $name => $value ) {
+ $s .= ( ( $name[0] === '@' ) ? '' : '@' ) . $name .': '. $value . ( ( substr( $value, -1 ) === ';' ) ? '' : ';' );
+ }
+
+ return $s;
+ }
+
+ /**
+ * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
+ *
+ * @param string $b
+ */
+ public static function is_method( $a, $b ) {
+ return is_object( $a ) && method_exists( $a, $b );
+ }
+
+ /**
+ * Round numbers similarly to javascript
+ * eg: 1.499999 to 1 instead of 2
+ */
+ public static function round( $input, $precision = 0 ) {
+ $precision = pow( 10, $precision );
+ $i = $input * $precision;
+
+ $ceil = ceil( $i );
+ $floor = floor( $i );
+ if ( ( $ceil - $i ) <= ( $i - $floor ) ) {
+ return $ceil / $precision;
+ } else {
+ return $floor / $precision;
+ }
+ }
+
+ /**
+ * Create Less_Tree_* objects and optionally generate a cache string
+ *
+ * @return mixed
+ */
+ public function NewObj0( $class ) {
+ $obj = new $class();
+ if ( $this->CacheEnabled() ) {
+ $obj->cache_string = ' new '.$class.'()';
+ }
+ return $obj;
+ }
+
+ public function NewObj1( $class, $arg ) {
+ $obj = new $class( $arg );
+ if ( $this->CacheEnabled() ) {
+ $obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString( $arg ).')';
+ }
+ return $obj;
+ }
+
+ public function NewObj2( $class, $args ) {
+ $obj = new $class( $args[0], $args[1] );
+ if ( $this->CacheEnabled() ) {
+ $this->ObjCache( $obj, $class, $args );
+ }
+ return $obj;
+ }
+
+ public function NewObj3( $class, $args ) {
+ $obj = new $class( $args[0], $args[1], $args[2] );
+ if ( $this->CacheEnabled() ) {
+ $this->ObjCache( $obj, $class, $args );
+ }
+ return $obj;
+ }
+
+ public function NewObj4( $class, $args ) {
+ $obj = new $class( $args[0], $args[1], $args[2], $args[3] );
+ if ( $this->CacheEnabled() ) {
+ $this->ObjCache( $obj, $class, $args );
+ }
+ return $obj;
+ }
+
+ public function NewObj5( $class, $args ) {
+ $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
+ if ( $this->CacheEnabled() ) {
+ $this->ObjCache( $obj, $class, $args );
+ }
+ return $obj;
+ }
+
+ public function NewObj6( $class, $args ) {
+ $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
+ if ( $this->CacheEnabled() ) {
+ $this->ObjCache( $obj, $class, $args );
+ }
+ return $obj;
+ }
+
+ public function NewObj7( $class, $args ) {
+ $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
+ if ( $this->CacheEnabled() ) {
+ $this->ObjCache( $obj, $class, $args );
+ }
+ return $obj;
+ }
+
+ // caching
+ public function ObjCache( $obj, $class, $args = array() ) {
+ $obj->cache_string = ' new '.$class.'('. self::ArgCache( $args ).')';
+ }
+
+ public function ArgCache( $args ) {
+ return implode( ',', array_map( array( 'Less_Parser','ArgString' ), $args ) );
+ }
+
+ /**
+ * Convert an argument to a string for use in the parser cache
+ *
+ * @return string
+ */
+ public static function ArgString( $arg ) {
+ $type = gettype( $arg );
+
+ if ( $type === 'object' ) {
+ $string = $arg->cache_string;
+ unset( $arg->cache_string );
+ return $string;
+
+ } elseif ( $type === 'array' ) {
+ $string = ' Array(';
+ foreach ( $arg as $k => $a ) {
+ $string .= var_export( $k, true ).' => '.self::ArgString( $a ).',';
+ }
+ return $string . ')';
+ }
+
+ return var_export( $arg, true );
+ }
+
+ public function Error( $msg ) {
+ throw new Less_Exception_Parser( $msg, null, $this->furthest, $this->env->currentFileInfo );
+ }
+
+ public static function WinPath( $path ) {
+ return str_replace( '\\', '/', $path );
+ }
+
+ public static function AbsPath( $path, $winPath = false ) {
+ if ( strpos( $path, '//' ) !== false && preg_match( '_^(https?:)?//\\w+(\\.\\w+)+/\\w+_i', $path ) ) {
+ return $winPath ? '' : false;
+ } else {
+ $path = realpath( $path );
+ if ( $winPath ) {
+ $path = self::WinPath( $path );
+ }
+ return $path;
+ }
+ }
+
+ public function CacheEnabled() {
+ return ( Less_Parser::$options['cache_method'] && ( Less_Cache::$cache_dir || ( Less_Parser::$options['cache_method'] == 'callback' ) ) );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/SourceMap/Base64VLQ.php b/library/vendor/lessphp/lib/Less/SourceMap/Base64VLQ.php
new file mode 100644
index 0000000..989a4ce
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/SourceMap/Base64VLQ.php
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * Encode / Decode Base64 VLQ.
+ *
+ * @package Less
+ * @subpackage SourceMap
+ */
+class Less_SourceMap_Base64VLQ {
+
+ /**
+ * Shift
+ *
+ * @var integer
+ */
+ private $shift = 5;
+
+ /**
+ * Mask
+ *
+ * @var integer
+ */
+ private $mask = 0x1F; // == (1 << shift) == 0b00011111
+
+ /**
+ * Continuation bit
+ *
+ * @var integer
+ */
+ private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
+
+ /**
+ * Char to integer map
+ *
+ * @var array
+ */
+ private $charToIntMap = array(
+ 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
+ 'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
+ 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
+ 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27,
+ 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34,
+ 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41,
+ 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48,
+ 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56,
+ 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
+ );
+
+ /**
+ * Integer to char map
+ *
+ * @var array
+ */
+ private $intToCharMap = array(
+ 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G',
+ 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N',
+ 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
+ 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b',
+ 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i',
+ 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p',
+ 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w',
+ 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
+ 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+',
+ 63 => '/',
+ );
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ // I leave it here for future reference
+ // foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
+ // {
+ // $this->charToIntMap[$char] = $i;
+ // $this->intToCharMap[$i] = $char;
+ // }
+ }
+
+ /**
+ * Convert from a two-complement value to a value where the sign bit is
+ * is placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
+ * even on a 64 bit machine.
+ * @param string $aValue
+ */
+ public function toVLQSigned( $aValue ) {
+ return 0xffffffff & ( $aValue < 0 ? ( ( -$aValue ) << 1 ) + 1 : ( $aValue << 1 ) + 0 );
+ }
+
+ /**
+ * Convert to a two-complement value from a value where the sign bit is
+ * is placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ * We assume that the value was generated with a 32 bit machine in mind.
+ * Hence
+ * 1 becomes -2147483648
+ * even on a 64 bit machine.
+ * @param integer $aValue
+ */
+ public function fromVLQSigned( $aValue ) {
+ return $aValue & 1 ? $this->zeroFill( ~$aValue + 2, 1 ) | ( -1 - 0x7fffffff ) : $this->zeroFill( $aValue, 1 );
+ }
+
+ /**
+ * Return the base 64 VLQ encoded value.
+ *
+ * @param string $aValue The value to encode
+ * @return string The encoded value
+ */
+ public function encode( $aValue ) {
+ $encoded = '';
+ $vlq = $this->toVLQSigned( $aValue );
+ do
+ {
+ $digit = $vlq & $this->mask;
+ $vlq = $this->zeroFill( $vlq, $this->shift );
+ if ( $vlq > 0 ) {
+ $digit |= $this->continuationBit;
+ }
+ $encoded .= $this->base64Encode( $digit );
+ } while ( $vlq > 0 );
+
+ return $encoded;
+ }
+
+ /**
+ * Return the value decoded from base 64 VLQ.
+ *
+ * @param string $encoded The encoded value to decode
+ * @return integer The decoded value
+ */
+ public function decode( $encoded ) {
+ $vlq = 0;
+ $i = 0;
+ do
+ {
+ $digit = $this->base64Decode( $encoded[$i] );
+ $vlq |= ( $digit & $this->mask ) << ( $i * $this->shift );
+ $i++;
+ } while ( $digit & $this->continuationBit );
+
+ return $this->fromVLQSigned( $vlq );
+ }
+
+ /**
+ * Right shift with zero fill.
+ *
+ * @param integer $a number to shift
+ * @param integer $b number of bits to shift
+ * @return integer
+ */
+ public function zeroFill( $a, $b ) {
+ return ( $a >= 0 ) ? ( $a >> $b ) : ( $a >> $b ) & ( PHP_INT_MAX >> ( $b - 1 ) );
+ }
+
+ /**
+ * Encode single 6-bit digit as base64.
+ *
+ * @param integer $number
+ * @return string
+ * @throws Exception If the number is invalid
+ */
+ public function base64Encode( $number ) {
+ if ( $number < 0 || $number > 63 ) {
+ throw new Exception( sprintf( 'Invalid number "%s" given. Must be between 0 and 63.', $number ) );
+ }
+ return $this->intToCharMap[$number];
+ }
+
+ /**
+ * Decode single 6-bit digit from base64
+ *
+ * @param string $char
+ * @return number
+ * @throws Exception If the number is invalid
+ */
+ public function base64Decode( $char ) {
+ if ( !array_key_exists( $char, $this->charToIntMap ) ) {
+ throw new Exception( sprintf( 'Invalid base 64 digit "%s" given.', $char ) );
+ }
+ return $this->charToIntMap[$char];
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/SourceMap/Generator.php b/library/vendor/lessphp/lib/Less/SourceMap/Generator.php
new file mode 100644
index 0000000..8f82bd0
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/SourceMap/Generator.php
@@ -0,0 +1,354 @@
+<?php
+
+/**
+ * Source map generator
+ *
+ * @package Less
+ * @subpackage Output
+ */
+class Less_SourceMap_Generator extends Less_Configurable {
+
+ /**
+ * What version of source map does the generator generate?
+ */
+ const VERSION = 3;
+
+ /**
+ * Array of default options
+ *
+ * @var array
+ */
+ protected $defaultOptions = array(
+ // an optional source root, useful for relocating source files
+ // on a server or removing repeated values in the 'sources' entry.
+ // This value is prepended to the individual entries in the 'source' field.
+ 'sourceRoot' => '',
+
+ // an optional name of the generated code that this source map is associated with.
+ 'sourceMapFilename' => null,
+
+ // url of the map
+ 'sourceMapURL' => null,
+
+ // absolute path to a file to write the map to
+ 'sourceMapWriteTo' => null,
+
+ // output source contents?
+ 'outputSourceFiles' => false,
+
+ // base path for filename normalization
+ 'sourceMapRootpath' => '',
+
+ // base path for filename normalization
+ 'sourceMapBasepath' => ''
+ );
+
+ /**
+ * The base64 VLQ encoder
+ *
+ * @var Less_SourceMap_Base64VLQ
+ */
+ protected $encoder;
+
+ /**
+ * Array of mappings
+ *
+ * @var array
+ */
+ protected $mappings = array();
+
+ /**
+ * The root node
+ *
+ * @var Less_Tree_Ruleset
+ */
+ protected $root;
+
+ /**
+ * Array of contents map
+ *
+ * @var array
+ */
+ protected $contentsMap = array();
+
+ /**
+ * File to content map
+ *
+ * @var array
+ */
+ protected $sources = array();
+ protected $source_keys = array();
+
+ /**
+ * Constructor
+ *
+ * @param Less_Tree_Ruleset $root The root node
+ * @param array $options Array of options
+ */
+ public function __construct( Less_Tree_Ruleset $root, $contentsMap, $options = array() ) {
+ $this->root = $root;
+ $this->contentsMap = $contentsMap;
+ $this->encoder = new Less_SourceMap_Base64VLQ();
+
+ $this->SetOptions( $options );
+
+ $this->options['sourceMapRootpath'] = $this->fixWindowsPath( $this->options['sourceMapRootpath'], true );
+ $this->options['sourceMapBasepath'] = $this->fixWindowsPath( $this->options['sourceMapBasepath'], true );
+ }
+
+ /**
+ * Generates the CSS
+ *
+ * @return string
+ */
+ public function generateCSS() {
+ $output = new Less_Output_Mapped( $this->contentsMap, $this );
+
+ // catch the output
+ $this->root->genCSS( $output );
+
+ $sourceMapUrl = $this->getOption( 'sourceMapURL' );
+ $sourceMapFilename = $this->getOption( 'sourceMapFilename' );
+ $sourceMapContent = $this->generateJson();
+ $sourceMapWriteTo = $this->getOption( 'sourceMapWriteTo' );
+
+ if ( !$sourceMapUrl && $sourceMapFilename ) {
+ $sourceMapUrl = $this->normalizeFilename( $sourceMapFilename );
+ }
+
+ // write map to a file
+ if ( $sourceMapWriteTo ) {
+ $this->saveMap( $sourceMapWriteTo, $sourceMapContent );
+ }
+
+ // inline the map
+ if ( !$sourceMapUrl ) {
+ $sourceMapUrl = sprintf( 'data:application/json,%s', Less_Functions::encodeURIComponent( $sourceMapContent ) );
+ }
+
+ if ( $sourceMapUrl ) {
+ $output->add( sprintf( '/*# sourceMappingURL=%s */', $sourceMapUrl ) );
+ }
+
+ return $output->toString();
+ }
+
+ /**
+ * Saves the source map to a file
+ *
+ * @param string $file The absolute path to a file
+ * @param string $content The content to write
+ * @throws Exception If the file could not be saved
+ */
+ protected function saveMap( $file, $content ) {
+ $dir = dirname( $file );
+ // directory does not exist
+ if ( !is_dir( $dir ) ) {
+ // FIXME: create the dir automatically?
+ throw new Exception( sprintf( 'The directory "%s" does not exist. Cannot save the source map.', $dir ) );
+ }
+ // FIXME: proper saving, with dir write check!
+ if ( file_put_contents( $file, $content ) === false ) {
+ throw new Exception( sprintf( 'Cannot save the source map to "%s"', $file ) );
+ }
+ return true;
+ }
+
+ /**
+ * Normalizes the filename
+ *
+ * @param string $filename
+ * @return string
+ */
+ protected function normalizeFilename( $filename ) {
+ $filename = $this->fixWindowsPath( $filename );
+
+ $rootpath = $this->getOption( 'sourceMapRootpath' );
+ $basePath = $this->getOption( 'sourceMapBasepath' );
+
+ // "Trim" the 'sourceMapBasepath' from the output filename.
+ if ( is_string( $basePath ) && strpos( $filename, $basePath ) === 0 ) {
+ $filename = substr( $filename, strlen( $basePath ) );
+ }
+
+ // Remove extra leading path separators.
+ if ( strpos( $filename, '\\' ) === 0 || strpos( $filename, '/' ) === 0 ) {
+ $filename = substr( $filename, 1 );
+ }
+
+ return $rootpath . $filename;
+ }
+
+ /**
+ * Adds a mapping
+ *
+ * @param int $generatedLine The line number in generated file
+ * @param int $generatedColumn The column number in generated file
+ * @param int $originalLine The line number in original file
+ * @param int $originalColumn The column number in original file
+ * @param array $fileInfo The original source file
+ */
+ public function addMapping( $generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ) {
+ $this->mappings[] = array(
+ 'generated_line' => $generatedLine,
+ 'generated_column' => $generatedColumn,
+ 'original_line' => $originalLine,
+ 'original_column' => $originalColumn,
+ 'source_file' => $fileInfo['currentUri']
+ );
+
+ $this->sources[$fileInfo['currentUri']] = $fileInfo['filename'];
+ }
+
+ /**
+ * Generates the JSON source map
+ *
+ * @return string
+ * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
+ */
+ protected function generateJson() {
+ $sourceMap = array();
+ $mappings = $this->generateMappings();
+
+ // File version (always the first entry in the object) and must be a positive integer.
+ $sourceMap['version'] = self::VERSION;
+
+ // An optional name of the generated code that this source map is associated with.
+ $file = $this->getOption( 'sourceMapFilename' );
+ if ( $file ) {
+ $sourceMap['file'] = $file;
+ }
+
+ // An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field.
+ $root = $this->getOption( 'sourceRoot' );
+ if ( $root ) {
+ $sourceMap['sourceRoot'] = $root;
+ }
+
+ // A list of original sources used by the 'mappings' entry.
+ $sourceMap['sources'] = array();
+ foreach ( $this->sources as $source_uri => $source_filename ) {
+ $sourceMap['sources'][] = $this->normalizeFilename( $source_filename );
+ }
+
+ // A list of symbol names used by the 'mappings' entry.
+ $sourceMap['names'] = array();
+
+ // A string with the encoded mapping data.
+ $sourceMap['mappings'] = $mappings;
+
+ if ( $this->getOption( 'outputSourceFiles' ) ) {
+ // An optional list of source content, useful when the 'source' can't be hosted.
+ // The contents are listed in the same order as the sources above.
+ // 'null' may be used if some original sources should be retrieved by name.
+ $sourceMap['sourcesContent'] = $this->getSourcesContent();
+ }
+
+ // less.js compat fixes
+ if ( count( $sourceMap['sources'] ) && empty( $sourceMap['sourceRoot'] ) ) {
+ unset( $sourceMap['sourceRoot'] );
+ }
+
+ return json_encode( $sourceMap );
+ }
+
+ /**
+ * Returns the sources contents
+ *
+ * @return array|null
+ */
+ protected function getSourcesContent() {
+ if ( empty( $this->sources ) ) {
+ return;
+ }
+ $content = array();
+ foreach ( $this->sources as $sourceFile ) {
+ $content[] = file_get_contents( $sourceFile );
+ }
+ return $content;
+ }
+
+ /**
+ * Generates the mappings string
+ *
+ * @return string
+ */
+ public function generateMappings() {
+ if ( !count( $this->mappings ) ) {
+ return '';
+ }
+
+ $this->source_keys = array_flip( array_keys( $this->sources ) );
+
+ // group mappings by generated line number.
+ $groupedMap = $groupedMapEncoded = array();
+ foreach ( $this->mappings as $m ) {
+ $groupedMap[$m['generated_line']][] = $m;
+ }
+ ksort( $groupedMap );
+
+ $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
+
+ foreach ( $groupedMap as $lineNumber => $line_map ) {
+ while ( ++$lastGeneratedLine < $lineNumber ) {
+ $groupedMapEncoded[] = ';';
+ }
+
+ $lineMapEncoded = array();
+ $lastGeneratedColumn = 0;
+
+ foreach ( $line_map as $m ) {
+ $mapEncoded = $this->encoder->encode( $m['generated_column'] - $lastGeneratedColumn );
+ $lastGeneratedColumn = $m['generated_column'];
+
+ // find the index
+ if ( $m['source_file'] ) {
+ $index = $this->findFileIndex( $m['source_file'] );
+ if ( $index !== false ) {
+ $mapEncoded .= $this->encoder->encode( $index - $lastOriginalIndex );
+ $lastOriginalIndex = $index;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ $mapEncoded .= $this->encoder->encode( $m['original_line'] - 1 - $lastOriginalLine );
+ $lastOriginalLine = $m['original_line'] - 1;
+
+ $mapEncoded .= $this->encoder->encode( $m['original_column'] - $lastOriginalColumn );
+ $lastOriginalColumn = $m['original_column'];
+ }
+ }
+
+ $lineMapEncoded[] = $mapEncoded;
+ }
+
+ $groupedMapEncoded[] = implode( ',', $lineMapEncoded ) . ';';
+ }
+
+ return rtrim( implode( $groupedMapEncoded ), ';' );
+ }
+
+ /**
+ * Finds the index for the filename
+ *
+ * @param string $filename
+ * @return integer|false
+ */
+ protected function findFileIndex( $filename ) {
+ return $this->source_keys[$filename];
+ }
+
+ /**
+ * fix windows paths
+ * @param string $path
+ * @return string
+ */
+ public function fixWindowsPath( $path, $addEndSlash = false ) {
+ $slash = ( $addEndSlash ) ? '/' : '';
+ if ( !empty( $path ) ) {
+ $path = str_replace( '\\', '/', $path );
+ $path = rtrim( $path, '/' ) . $slash;
+ }
+
+ return $path;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree.php b/library/vendor/lessphp/lib/Less/Tree.php
new file mode 100644
index 0000000..7b48e9a
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Tree
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree {
+
+ public $cache_string;
+
+ public function toCSS() {
+ $output = new Less_Output();
+ $this->genCSS( $output );
+ return $output->toString();
+ }
+
+ /**
+ * Generate CSS by adding it to the output object
+ *
+ * @param Less_Output $output The output
+ * @return void
+ */
+ public function genCSS( $output ) {
+ }
+
+ /**
+ * @param Less_Tree_Ruleset[] $rules
+ */
+ public static function outputRuleset( $output, $rules ) {
+ $ruleCnt = count( $rules );
+ Less_Environment::$tabLevel++;
+
+ // Compressed
+ if ( Less_Parser::$options['compress'] ) {
+ $output->add( '{' );
+ for ( $i = 0; $i < $ruleCnt; $i++ ) {
+ $rules[$i]->genCSS( $output );
+ }
+
+ $output->add( '}' );
+ Less_Environment::$tabLevel--;
+ return;
+ }
+
+ // Non-compressed
+ $tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
+ $tabRuleStr = $tabSetStr.Less_Parser::$options['indentation'];
+
+ $output->add( " {" );
+ for ( $i = 0; $i < $ruleCnt; $i++ ) {
+ $output->add( $tabRuleStr );
+ $rules[$i]->genCSS( $output );
+ }
+ Less_Environment::$tabLevel--;
+ $output->add( $tabSetStr.'}' );
+
+ }
+
+ public function accept( $visitor ) {
+ }
+
+ public static function ReferencedArray( $rules ) {
+ foreach ( $rules as $rule ) {
+ if ( method_exists( $rule, 'markReferenced' ) ) {
+ $rule->markReferenced();
+ }
+ }
+ }
+
+ /**
+ * Requires php 5.3+
+ */
+ public static function __set_state( $args ) {
+ $class = get_called_class();
+ $obj = new $class( null, null, null, null );
+ foreach ( $args as $key => $val ) {
+ $obj->$key = $val;
+ }
+ return $obj;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Alpha.php b/library/vendor/lessphp/lib/Less/Tree/Alpha.php
new file mode 100644
index 0000000..cd38c91
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Alpha.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Alpha
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Alpha extends Less_Tree {
+ public $value;
+ public $type = 'Alpha';
+
+ public function __construct( $val ) {
+ $this->value = $val;
+ }
+
+ // function accept( $visitor ){
+ // $this->value = $visitor->visit( $this->value );
+ //}
+
+ public function compile( $env ) {
+ if ( is_object( $this->value ) ) {
+ $this->value = $this->value->compile( $env );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( "alpha(opacity=" );
+
+ if ( is_string( $this->value ) ) {
+ $output->add( $this->value );
+ } else {
+ $this->value->genCSS( $output );
+ }
+
+ $output->add( ')' );
+ }
+
+ public function toCSS() {
+ return "alpha(opacity=" . ( is_string( $this->value ) ? $this->value : $this->value->toCSS() ) . ")";
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Anonymous.php b/library/vendor/lessphp/lib/Less/Tree/Anonymous.php
new file mode 100644
index 0000000..1dc9bab
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Anonymous.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Anonymous
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Anonymous extends Less_Tree {
+ public $value;
+ public $quote;
+ public $index;
+ public $mapLines;
+ public $currentFileInfo;
+ public $type = 'Anonymous';
+
+ /**
+ * @param integer $index
+ * @param boolean $mapLines
+ */
+ public function __construct( $value, $index = null, $currentFileInfo = null, $mapLines = null ) {
+ $this->value = $value;
+ $this->index = $index;
+ $this->mapLines = $mapLines;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function compile() {
+ return new Less_Tree_Anonymous( $this->value, $this->index, $this->currentFileInfo, $this->mapLines );
+ }
+
+ public function compare( $x ) {
+ if ( !is_object( $x ) ) {
+ return -1;
+ }
+
+ $left = $this->toCSS();
+ $right = $x->toCSS();
+
+ if ( $left === $right ) {
+ return 0;
+ }
+
+ return $left < $right ? -1 : 1;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines );
+ }
+
+ public function toCSS() {
+ return $this->value;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Assignment.php b/library/vendor/lessphp/lib/Less/Tree/Assignment.php
new file mode 100644
index 0000000..69c7a4e
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Assignment.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Assignment
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Assignment extends Less_Tree {
+
+ public $key;
+ public $value;
+ public $type = 'Assignment';
+
+ public function __construct( $key, $val ) {
+ $this->key = $key;
+ $this->value = $val;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Assignment( $this->key, $this->value->compile( $env ) );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->key . '=' );
+ $this->value->genCSS( $output );
+ }
+
+ public function toCss() {
+ return $this->key . '=' . $this->value->toCSS();
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Attribute.php b/library/vendor/lessphp/lib/Less/Tree/Attribute.php
new file mode 100644
index 0000000..c47abb9
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Attribute.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Attribute
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Attribute extends Less_Tree {
+
+ public $key;
+ public $op;
+ public $value;
+ public $type = 'Attribute';
+
+ public function __construct( $key, $op, $value ) {
+ $this->key = $key;
+ $this->op = $op;
+ $this->value = $value;
+ }
+
+ public function compile( $env ) {
+ $key_obj = is_object( $this->key );
+ $val_obj = is_object( $this->value );
+
+ if ( !$key_obj && !$val_obj ) {
+ return $this;
+ }
+
+ return new Less_Tree_Attribute(
+ $key_obj ? $this->key->compile( $env ) : $this->key,
+ $this->op,
+ $val_obj ? $this->value->compile( $env ) : $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->toCSS() );
+ }
+
+ public function toCSS() {
+ $value = $this->key;
+
+ if ( $this->op ) {
+ $value .= $this->op;
+ $value .= ( is_object( $this->value ) ? $this->value->toCSS() : $this->value );
+ }
+
+ return '[' . $value . ']';
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Call.php b/library/vendor/lessphp/lib/Less/Tree/Call.php
new file mode 100644
index 0000000..5341b74
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Call.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Call
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Call extends Less_Tree {
+ public $value;
+
+ public $name;
+ public $args;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'Call';
+
+ public function __construct( $name, $args, $index, $currentFileInfo = null ) {
+ $this->name = $name;
+ $this->args = $args;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function accept( $visitor ) {
+ $this->args = $visitor->visitArray( $this->args );
+ }
+
+ //
+ // When evaluating a function call,
+ // we either find the function in `tree.functions` [1],
+ // in which case we call it, passing the evaluated arguments,
+ // or we simply print it out as it appeared originally [2].
+ //
+ // The *functions.js* file contains the built-in functions.
+ //
+ // The reason why we evaluate the arguments, is in the case where
+ // we try to pass a variable to a function, like: `saturate(@color)`.
+ // The function should receive the value, not the variable.
+ //
+ public function compile( $env = null ) {
+ $args = array();
+ foreach ( $this->args as $a ) {
+ $args[] = $a->compile( $env );
+ }
+
+ $nameLC = strtolower( $this->name );
+ switch ( $nameLC ) {
+ case '%':
+ $nameLC = '_percent';
+ break;
+
+ case 'get-unit':
+ $nameLC = 'getunit';
+ break;
+
+ case 'data-uri':
+ $nameLC = 'datauri';
+ break;
+
+ case 'svg-gradient':
+ $nameLC = 'svggradient';
+ break;
+ }
+
+ $result = null;
+ if ( $nameLC === 'default' ) {
+ $result = Less_Tree_DefaultFunc::compile();
+
+ } else {
+
+ if ( method_exists( 'Less_Functions', $nameLC ) ) { // 1.
+ try {
+
+ $func = new Less_Functions( $env, $this->currentFileInfo );
+ $result = call_user_func_array( array( $func,$nameLC ), $args );
+
+ } catch ( Exception $e ) {
+ throw new Less_Exception_Compiler( 'error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index );
+ }
+ } elseif ( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) {
+ try {
+ $result = call_user_func_array( $env->functions[$nameLC], $args );
+ } catch ( Exception $e ) {
+ throw new Less_Exception_Compiler( 'error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index );
+ }
+ }
+ }
+
+ if ( $result !== null ) {
+ return $result;
+ }
+
+ return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->name . '(', $this->currentFileInfo, $this->index );
+ $args_len = count( $this->args );
+ for ( $i = 0; $i < $args_len; $i++ ) {
+ $this->args[$i]->genCSS( $output );
+ if ( $i + 1 < $args_len ) {
+ $output->add( ', ' );
+ }
+ }
+
+ $output->add( ')' );
+ }
+
+ // public function toCSS(){
+ // return $this->compile()->toCSS();
+ //}
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Color.php b/library/vendor/lessphp/lib/Less/Tree/Color.php
new file mode 100644
index 0000000..b0f79db
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Color.php
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * Color
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Color extends Less_Tree {
+ public $rgb;
+ public $alpha;
+ public $isTransparentKeyword;
+ public $type = 'Color';
+
+ public function __construct( $rgb, $a = 1, $isTransparentKeyword = null ) {
+ if ( $isTransparentKeyword ) {
+ $this->rgb = $rgb;
+ $this->alpha = $a;
+ $this->isTransparentKeyword = true;
+ return;
+ }
+
+ $this->rgb = array();
+ if ( is_array( $rgb ) ) {
+ $this->rgb = $rgb;
+ } else if ( strlen( $rgb ) == 6 ) {
+ foreach ( str_split( $rgb, 2 ) as $c ) {
+ $this->rgb[] = hexdec( $c );
+ }
+ } else {
+ foreach ( str_split( $rgb, 1 ) as $c ) {
+ $this->rgb[] = hexdec( $c.$c );
+ }
+ }
+ $this->alpha = is_numeric( $a ) ? $a : 1;
+ }
+
+ public function compile() {
+ return $this;
+ }
+
+ public function luma() {
+ $r = $this->rgb[0] / 255;
+ $g = $this->rgb[1] / 255;
+ $b = $this->rgb[2] / 255;
+
+ $r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
+ $g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
+ $b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
+
+ return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->toCSS() );
+ }
+
+ public function toCSS( $doNotCompress = false ) {
+ $compress = Less_Parser::$options['compress'] && !$doNotCompress;
+ $alpha = Less_Functions::fround( $this->alpha );
+
+ //
+ // If we have some transparency, the only way to represent it
+ // is via `rgba`. Otherwise, we use the hex representation,
+ // which has better compatibility with older browsers.
+ // Values are capped between `0` and `255`, rounded and zero-padded.
+ //
+ if ( $alpha < 1 ) {
+ if ( ( $alpha === 0 || $alpha === 0.0 ) && isset( $this->isTransparentKeyword ) && $this->isTransparentKeyword ) {
+ return 'transparent';
+ }
+
+ $values = array();
+ foreach ( $this->rgb as $c ) {
+ $values[] = Less_Functions::clamp( round( $c ), 255 );
+ }
+ $values[] = $alpha;
+
+ $glue = ( $compress ? ',' : ', ' );
+ return "rgba(" . implode( $glue, $values ) . ")";
+ } else {
+
+ $color = $this->toRGB();
+
+ if ( $compress ) {
+
+ // Convert color to short format
+ if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
+ $color = '#'.$color[1] . $color[3] . $color[5];
+ }
+ }
+
+ return $color;
+ }
+ }
+
+ //
+ // Operations have to be done per-channel, if not,
+ // channels will spill onto each other. Once we have
+ // our result, in the form of an integer triplet,
+ // we create a new Color node to hold the result.
+ //
+
+ /**
+ * @param string $op
+ */
+ public function operate( $op, $other ) {
+ $rgb = array();
+ $alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
+ for ( $c = 0; $c < 3; $c++ ) {
+ $rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c] );
+ }
+ return new Less_Tree_Color( $rgb, $alpha );
+ }
+
+ public function toRGB() {
+ return $this->toHex( $this->rgb );
+ }
+
+ public function toHSL() {
+ $r = $this->rgb[0] / 255;
+ $g = $this->rgb[1] / 255;
+ $b = $this->rgb[2] / 255;
+ $a = $this->alpha;
+
+ $max = max( $r, $g, $b );
+ $min = min( $r, $g, $b );
+ $l = ( $max + $min ) / 2;
+ $d = $max - $min;
+
+ $h = $s = 0;
+ if ( $max !== $min ) {
+ $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
+
+ switch ( $max ) {
+ case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
+break;
+ case $g: $h = ( $b - $r ) / $d + 2;
+break;
+ case $b: $h = ( $r - $g ) / $d + 4;
+break;
+ }
+ $h /= 6;
+ }
+ return array( 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
+ }
+
+ // Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
+ public function toHSV() {
+ $r = $this->rgb[0] / 255;
+ $g = $this->rgb[1] / 255;
+ $b = $this->rgb[2] / 255;
+ $a = $this->alpha;
+
+ $max = max( $r, $g, $b );
+ $min = min( $r, $g, $b );
+
+ $v = $max;
+
+ $d = $max - $min;
+ if ( $max === 0 ) {
+ $s = 0;
+ } else {
+ $s = $d / $max;
+ }
+
+ $h = 0;
+ if ( $max !== $min ) {
+ switch ( $max ) {
+ case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
+break;
+ case $g: $h = ( $b - $r ) / $d + 2;
+break;
+ case $b: $h = ( $r - $g ) / $d + 4;
+break;
+ }
+ $h /= 6;
+ }
+ return array( 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a );
+ }
+
+ public function toARGB() {
+ $argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
+ return $this->toHex( $argb );
+ }
+
+ public function compare( $x ) {
+ if ( !property_exists( $x, 'rgb' ) ) {
+ return -1;
+ }
+
+ return ( $x->rgb[0] === $this->rgb[0] &&
+ $x->rgb[1] === $this->rgb[1] &&
+ $x->rgb[2] === $this->rgb[2] &&
+ $x->alpha === $this->alpha ) ? 0 : -1;
+ }
+
+ public function toHex( $v ) {
+ $ret = '#';
+ foreach ( $v as $c ) {
+ $c = Less_Functions::clamp( Less_Parser::round( $c ), 255 );
+ if ( $c < 16 ) {
+ $ret .= '0';
+ }
+ $ret .= dechex( $c );
+ }
+
+ return $ret;
+ }
+
+ /**
+ * @param string $keyword
+ */
+ public static function fromKeyword( $keyword ) {
+ $keyword = strtolower( $keyword );
+
+ if ( Less_Colors::hasOwnProperty( $keyword ) ) {
+ // detect named color
+ return new Less_Tree_Color( substr( Less_Colors::color( $keyword ), 1 ) );
+ }
+
+ if ( $keyword === 'transparent' ) {
+ return new Less_Tree_Color( array( 0, 0, 0 ), 0, true );
+ }
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Comment.php b/library/vendor/lessphp/lib/Less/Tree/Comment.php
new file mode 100644
index 0000000..c7e6c0f
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Comment.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Comment
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Comment extends Less_Tree {
+
+ public $value;
+ public $silent;
+ public $isReferenced;
+ public $currentFileInfo;
+ public $type = 'Comment';
+
+ public function __construct( $value, $silent, $index = null, $currentFileInfo = null ) {
+ $this->value = $value;
+ $this->silent = !!$silent;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ // if( $this->debugInfo ){
+ //$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index);
+ //}
+ $output->add( trim( $this->value ) );// TODO shouldn't need to trim, we shouldn't grab the \n
+ }
+
+ public function toCSS() {
+ return Less_Parser::$options['compress'] ? '' : $this->value;
+ }
+
+ public function isSilent() {
+ $isReference = ( $this->currentFileInfo && isset( $this->currentFileInfo['reference'] ) && ( !isset( $this->isReferenced ) || !$this->isReferenced ) );
+ $isCompressed = Less_Parser::$options['compress'] && !preg_match( '/^\/\*!/', $this->value );
+ return $this->silent || $isReference || $isCompressed;
+ }
+
+ public function compile() {
+ return $this;
+ }
+
+ public function markReferenced() {
+ $this->isReferenced = true;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Condition.php b/library/vendor/lessphp/lib/Less/Tree/Condition.php
new file mode 100644
index 0000000..355de8c
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Condition.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Condition
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Condition extends Less_Tree {
+
+ public $op;
+ public $lvalue;
+ public $rvalue;
+ public $index;
+ public $negate;
+ public $type = 'Condition';
+
+ public function __construct( $op, $l, $r, $i = 0, $negate = false ) {
+ $this->op = trim( $op );
+ $this->lvalue = $l;
+ $this->rvalue = $r;
+ $this->index = $i;
+ $this->negate = $negate;
+ }
+
+ public function accept( $visitor ) {
+ $this->lvalue = $visitor->visitObj( $this->lvalue );
+ $this->rvalue = $visitor->visitObj( $this->rvalue );
+ }
+
+ public function compile( $env ) {
+ $a = $this->lvalue->compile( $env );
+ $b = $this->rvalue->compile( $env );
+
+ switch ( $this->op ) {
+ case 'and':
+ $result = $a && $b;
+ break;
+
+ case 'or':
+ $result = $a || $b;
+ break;
+
+ default:
+ if ( Less_Parser::is_method( $a, 'compare' ) ) {
+ $result = $a->compare( $b );
+ } elseif ( Less_Parser::is_method( $b, 'compare' ) ) {
+ $result = $b->compare( $a );
+ } else {
+ throw new Less_Exception_Compiler( 'Unable to perform comparison', null, $this->index );
+ }
+
+ switch ( $result ) {
+ case -1:
+ $result = $this->op === '<' || $this->op === '=<' || $this->op === '<=';
+ break;
+
+ case 0:
+ $result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<=';
+ break;
+
+ case 1:
+ $result = $this->op === '>' || $this->op === '>=';
+ break;
+ }
+ break;
+ }
+
+ return $this->negate ? !$result : $result;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/DefaultFunc.php b/library/vendor/lessphp/lib/Less/Tree/DefaultFunc.php
new file mode 100644
index 0000000..41b78b6
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/DefaultFunc.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * DefaultFunc
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_DefaultFunc {
+
+ static $error_;
+ static $value_;
+
+ public static function compile() {
+ if ( self::$error_ ) {
+ throw new Exception( self::$error_ );
+ }
+ if ( self::$value_ !== null ) {
+ return self::$value_ ? new Less_Tree_Keyword( 'true' ) : new Less_Tree_Keyword( 'false' );
+ }
+ }
+
+ public static function value( $v ) {
+ self::$value_ = $v;
+ }
+
+ public static function error( $e ) {
+ self::$error_ = $e;
+ }
+
+ public static function reset() {
+ self::$value_ = self::$error_ = null;
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/DetachedRuleset.php b/library/vendor/lessphp/lib/Less/Tree/DetachedRuleset.php
new file mode 100644
index 0000000..ea5c7f4
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/DetachedRuleset.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * DetachedRuleset
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_DetachedRuleset extends Less_Tree {
+
+ public $ruleset;
+ public $frames;
+ public $type = 'DetachedRuleset';
+
+ public function __construct( $ruleset, $frames = null ) {
+ $this->ruleset = $ruleset;
+ $this->frames = $frames;
+ }
+
+ public function accept( $visitor ) {
+ $this->ruleset = $visitor->visitObj( $this->ruleset );
+ }
+
+ public function compile( $env ) {
+ if ( $this->frames ) {
+ $frames = $this->frames;
+ } else {
+ $frames = $env->frames;
+ }
+ return new Less_Tree_DetachedRuleset( $this->ruleset, $frames );
+ }
+
+ public function callEval( $env ) {
+ if ( $this->frames ) {
+ return $this->ruleset->compile( $env->copyEvalEnv( array_merge( $this->frames, $env->frames ) ) );
+ }
+ return $this->ruleset->compile( $env );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Dimension.php b/library/vendor/lessphp/lib/Less/Tree/Dimension.php
new file mode 100644
index 0000000..70c71b4
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Dimension.php
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * Dimension
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Dimension extends Less_Tree {
+
+ public $value;
+ public $unit;
+ public $type = 'Dimension';
+
+ public function __construct( $value, $unit = null ) {
+ $this->value = floatval( $value );
+
+ if ( $unit && ( $unit instanceof Less_Tree_Unit ) ) {
+ $this->unit = $unit;
+ } elseif ( $unit ) {
+ $this->unit = new Less_Tree_Unit( array( $unit ) );
+ } else {
+ $this->unit = new Less_Tree_Unit();
+ }
+ }
+
+ public function accept( $visitor ) {
+ $this->unit = $visitor->visitObj( $this->unit );
+ }
+
+ public function compile() {
+ return $this;
+ }
+
+ public function toColor() {
+ return new Less_Tree_Color( array( $this->value, $this->value, $this->value ) );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ) {
+ throw new Less_Exception_Compiler( "Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString() );
+ }
+
+ $value = Less_Functions::fround( $this->value );
+ $strValue = (string)$value;
+
+ if ( $value !== 0 && $value < 0.000001 && $value > -0.000001 ) {
+ // would be output 1e-6 etc.
+ $strValue = number_format( $strValue, 10 );
+ $strValue = preg_replace( '/\.?0+$/', '', $strValue );
+ }
+
+ if ( Less_Parser::$options['compress'] ) {
+ // Zero values doesn't need a unit
+ if ( $value === 0 && $this->unit->isLength() ) {
+ $output->add( $strValue );
+ return $strValue;
+ }
+
+ // Float values doesn't need a leading zero
+ if ( $value > 0 && $value < 1 && $strValue[0] === '0' ) {
+ $strValue = substr( $strValue, 1 );
+ }
+ }
+
+ $output->add( $strValue );
+ $this->unit->genCSS( $output );
+ }
+
+ public function __toString() {
+ return $this->toCSS();
+ }
+
+ // In an operation between two Dimensions,
+ // we default to the first Dimension's unit,
+ // so `1px + 2em` will yield `3px`.
+
+ /**
+ * @param string $op
+ */
+ public function operate( $op, $other ) {
+ $value = Less_Functions::operate( $op, $this->value, $other->value );
+ $unit = clone $this->unit;
+
+ if ( $op === '+' || $op === '-' ) {
+
+ if ( !$unit->numerator && !$unit->denominator ) {
+ $unit->numerator = $other->unit->numerator;
+ $unit->denominator = $other->unit->denominator;
+ } elseif ( !$other->unit->numerator && !$other->unit->denominator ) {
+ // do nothing
+ } else {
+ $other = $other->convertTo( $this->unit->usedUnits() );
+
+ if ( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ) {
+ throw new Less_Exception_Compiler( "Incompatible units. Change the units or use the unit function. Bad units: '" . $unit->toString() . "' and " . $other->unit->toString() . "'." );
+ }
+
+ $value = Less_Functions::operate( $op, $this->value, $other->value );
+ }
+ } elseif ( $op === '*' ) {
+ $unit->numerator = array_merge( $unit->numerator, $other->unit->numerator );
+ $unit->denominator = array_merge( $unit->denominator, $other->unit->denominator );
+ sort( $unit->numerator );
+ sort( $unit->denominator );
+ $unit->cancel();
+ } elseif ( $op === '/' ) {
+ $unit->numerator = array_merge( $unit->numerator, $other->unit->denominator );
+ $unit->denominator = array_merge( $unit->denominator, $other->unit->numerator );
+ sort( $unit->numerator );
+ sort( $unit->denominator );
+ $unit->cancel();
+ }
+ return new Less_Tree_Dimension( $value, $unit );
+ }
+
+ public function compare( $other ) {
+ if ( $other instanceof Less_Tree_Dimension ) {
+
+ if ( $this->unit->isEmpty() || $other->unit->isEmpty() ) {
+ $a = $this;
+ $b = $other;
+ } else {
+ $a = $this->unify();
+ $b = $other->unify();
+ if ( $a->unit->compare( $b->unit ) !== 0 ) {
+ return -1;
+ }
+ }
+ $aValue = $a->value;
+ $bValue = $b->value;
+
+ if ( $bValue > $aValue ) {
+ return -1;
+ } elseif ( $bValue < $aValue ) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ }
+
+ public function unify() {
+ return $this->convertTo( array( 'length' => 'px', 'duration' => 's', 'angle' => 'rad' ) );
+ }
+
+ public function convertTo( $conversions ) {
+ $value = $this->value;
+ $unit = clone $this->unit;
+
+ if ( is_string( $conversions ) ) {
+ $derivedConversions = array();
+ foreach ( Less_Tree_UnitConversions::$groups as $i ) {
+ if ( isset( Less_Tree_UnitConversions::${$i}[$conversions] ) ) {
+ $derivedConversions = array( $i => $conversions );
+ }
+ }
+ $conversions = $derivedConversions;
+ }
+
+ foreach ( $conversions as $groupName => $targetUnit ) {
+ $group = Less_Tree_UnitConversions::${$groupName};
+
+ // numerator
+ foreach ( $unit->numerator as $i => $atomicUnit ) {
+ $atomicUnit = $unit->numerator[$i];
+ if ( !isset( $group[$atomicUnit] ) ) {
+ continue;
+ }
+
+ $value = $value * ( $group[$atomicUnit] / $group[$targetUnit] );
+
+ $unit->numerator[$i] = $targetUnit;
+ }
+
+ // denominator
+ foreach ( $unit->denominator as $i => $atomicUnit ) {
+ $atomicUnit = $unit->denominator[$i];
+ if ( !isset( $group[$atomicUnit] ) ) {
+ continue;
+ }
+
+ $value = $value / ( $group[$atomicUnit] / $group[$targetUnit] );
+
+ $unit->denominator[$i] = $targetUnit;
+ }
+ }
+
+ $unit->cancel();
+
+ return new Less_Tree_Dimension( $value, $unit );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Directive.php b/library/vendor/lessphp/lib/Less/Tree/Directive.php
new file mode 100644
index 0000000..6c6a94f
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Directive.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * Directive
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Directive extends Less_Tree {
+
+ public $name;
+ public $value;
+ public $rules;
+ public $index;
+ public $isReferenced;
+ public $currentFileInfo;
+ public $debugInfo;
+ public $type = 'Directive';
+
+ public function __construct( $name, $value = null, $rules = null, $index = null, $currentFileInfo = null, $debugInfo = null ) {
+ $this->name = $name;
+ $this->value = $value;
+ if ( $rules ) {
+ $this->rules = $rules;
+ $this->rules->allowImports = true;
+ }
+
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->debugInfo = $debugInfo;
+ }
+
+ public function accept( $visitor ) {
+ if ( $this->rules ) {
+ $this->rules = $visitor->visitObj( $this->rules );
+ }
+ if ( $this->value ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $value = $this->value;
+ $rules = $this->rules;
+ $output->add( $this->name, $this->currentFileInfo, $this->index );
+ if ( $this->value ) {
+ $output->add( ' ' );
+ $this->value->genCSS( $output );
+ }
+ if ( $this->rules ) {
+ Less_Tree::outputRuleset( $output, array( $this->rules ) );
+ } else {
+ $output->add( ';' );
+ }
+ }
+
+ public function compile( $env ) {
+ $value = $this->value;
+ $rules = $this->rules;
+ if ( $value ) {
+ $value = $value->compile( $env );
+ }
+
+ if ( $rules ) {
+ $rules = $rules->compile( $env );
+ $rules->root = true;
+ }
+
+ return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo );
+ }
+
+ public function variable( $name ) {
+ if ( $this->rules ) {
+ return $this->rules->variable( $name );
+ }
+ }
+
+ public function find( $selector ) {
+ if ( $this->rules ) {
+ return $this->rules->find( $selector, $this );
+ }
+ }
+
+ // rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); },
+
+ public function markReferenced() {
+ $this->isReferenced = true;
+ if ( $this->rules ) {
+ Less_Tree::ReferencedArray( $this->rules->rules );
+ }
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Element.php b/library/vendor/lessphp/lib/Less/Tree/Element.php
new file mode 100644
index 0000000..5e25bc0
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Element.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Element
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Element extends Less_Tree {
+
+ public $combinator = '';
+ public $value = '';
+ public $index;
+ public $currentFileInfo;
+ public $type = 'Element';
+
+ public $value_is_object = false;
+
+ public function __construct( $combinator, $value, $index = null, $currentFileInfo = null ) {
+ $this->value = $value;
+ $this->value_is_object = is_object( $value );
+
+ if ( $combinator ) {
+ $this->combinator = $combinator;
+ }
+
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function accept( $visitor ) {
+ if ( $this->value_is_object ) { // object or string
+ $this->value = $visitor->visitObj( $this->value );
+ }
+ }
+
+ public function compile( $env ) {
+ if ( Less_Environment::$mixin_stack ) {
+ return new Less_Tree_Element( $this->combinator, ( $this->value_is_object ? $this->value->compile( $env ) : $this->value ), $this->index, $this->currentFileInfo );
+ }
+
+ if ( $this->value_is_object ) {
+ $this->value = $this->value->compile( $env );
+ }
+
+ return $this;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->toCSS(), $this->currentFileInfo, $this->index );
+ }
+
+ public function toCSS() {
+ if ( $this->value_is_object ) {
+ $value = $this->value->toCSS();
+ } else {
+ $value = $this->value;
+ }
+
+ if ( $value === '' && $this->combinator && $this->combinator === '&' ) {
+ return '';
+ }
+
+ return Less_Environment::$_outputMap[$this->combinator] . $value;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Expression.php b/library/vendor/lessphp/lib/Less/Tree/Expression.php
new file mode 100644
index 0000000..cf06315
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Expression.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Expression
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Expression extends Less_Tree {
+
+ public $value = array();
+ public $parens = false;
+ public $parensInOp = false;
+ public $type = 'Expression';
+
+ public function __construct( $value, $parens = null ) {
+ $this->value = $value;
+ $this->parens = $parens;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitArray( $this->value );
+ }
+
+ public function compile( $env ) {
+ $doubleParen = false;
+
+ if ( $this->parens && !$this->parensInOp ) {
+ Less_Environment::$parensStack++;
+ }
+
+ $returnValue = null;
+ if ( $this->value ) {
+
+ $count = count( $this->value );
+
+ if ( $count > 1 ) {
+
+ $ret = array();
+ foreach ( $this->value as $e ) {
+ $ret[] = $e->compile( $env );
+ }
+ $returnValue = new Less_Tree_Expression( $ret );
+
+ } else {
+
+ if ( ( $this->value[0] instanceof Less_Tree_Expression ) && $this->value[0]->parens && !$this->value[0]->parensInOp ) {
+ $doubleParen = true;
+ }
+
+ $returnValue = $this->value[0]->compile( $env );
+ }
+
+ } else {
+ $returnValue = $this;
+ }
+
+ if ( $this->parens ) {
+ if ( !$this->parensInOp ) {
+ Less_Environment::$parensStack--;
+
+ } elseif ( !Less_Environment::isMathOn() && !$doubleParen ) {
+ $returnValue = new Less_Tree_Paren( $returnValue );
+
+ }
+ }
+ return $returnValue;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $val_len = count( $this->value );
+ for ( $i = 0; $i < $val_len; $i++ ) {
+ $this->value[$i]->genCSS( $output );
+ if ( $i + 1 < $val_len ) {
+ $output->add( ' ' );
+ }
+ }
+ }
+
+ public function throwAwayComments() {
+ if ( is_array( $this->value ) ) {
+ $new_value = array();
+ foreach ( $this->value as $v ) {
+ if ( $v instanceof Less_Tree_Comment ) {
+ continue;
+ }
+ $new_value[] = $v;
+ }
+ $this->value = $new_value;
+ }
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Extend.php b/library/vendor/lessphp/lib/Less/Tree/Extend.php
new file mode 100644
index 0000000..4dd3665
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Extend.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Extend
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Extend extends Less_Tree {
+
+ public $selector;
+ public $option;
+ public $index;
+ public $selfSelectors = array();
+ public $allowBefore;
+ public $allowAfter;
+ public $firstExtendOnThisSelectorPath;
+ public $type = 'Extend';
+ public $ruleset;
+
+ public $object_id;
+ public $parent_ids = array();
+
+ /**
+ * @param integer $index
+ */
+ public function __construct( $selector, $option, $index ) {
+ static $i = 0;
+ $this->selector = $selector;
+ $this->option = $option;
+ $this->index = $index;
+
+ switch ( $option ) {
+ case "all":
+ $this->allowBefore = true;
+ $this->allowAfter = true;
+ break;
+ default:
+ $this->allowBefore = false;
+ $this->allowAfter = false;
+ break;
+ }
+
+ // This must use a string (instead of int) so that array_merge()
+ // preserves keys on arrays that use IDs in their keys.
+ $this->object_id = 'id_' . $i++;
+
+ $this->parent_ids = array(
+ $this->object_id => true
+ );
+ }
+
+ public function accept( $visitor ) {
+ $this->selector = $visitor->visitObj( $this->selector );
+ }
+
+ public function compile( $env ) {
+ Less_Parser::$has_extends = true;
+ $this->selector = $this->selector->compile( $env );
+ return $this;
+ // return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index);
+ }
+
+ public function findSelfSelectors( $selectors ) {
+ $selfElements = array();
+
+ for ( $i = 0, $selectors_len = count( $selectors ); $i < $selectors_len; $i++ ) {
+ $selectorElements = $selectors[$i]->elements;
+ // duplicate the logic in genCSS function inside the selector node.
+ // future TODO - move both logics into the selector joiner visitor
+ if ( $i && $selectorElements && $selectorElements[0]->combinator === "" ) {
+ $selectorElements[0]->combinator = ' ';
+ }
+ $selfElements = array_merge( $selfElements, $selectors[$i]->elements );
+ }
+
+ $this->selfSelectors = array( new Less_Tree_Selector( $selfElements ) );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Import.php b/library/vendor/lessphp/lib/Less/Tree/Import.php
new file mode 100644
index 0000000..008f353
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Import.php
@@ -0,0 +1,289 @@
+<?php
+
+/**
+ * CSS @import node
+ *
+ * The general strategy here is that we don't want to wait
+ * for the parsing to be completed, before we start importing
+ * the file. That's because in the context of a browser,
+ * most of the time will be spent waiting for the server to respond.
+ *
+ * On creation, we push the import path to our import queue, though
+ * `import,push`, we also pass it a callback, which it'll call once
+ * the file has been fetched, and parsed.
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Import extends Less_Tree {
+
+ public $options;
+ public $index;
+ public $path;
+ public $features;
+ public $currentFileInfo;
+ public $css;
+ public $skip;
+ public $root;
+ public $type = 'Import';
+
+ public function __construct( $path, $features, $options, $index, $currentFileInfo = null ) {
+ $this->options = $options;
+ $this->index = $index;
+ $this->path = $path;
+ $this->features = $features;
+ $this->currentFileInfo = $currentFileInfo;
+
+ if ( is_array( $options ) ) {
+ $this->options += array( 'inline' => false );
+
+ if ( isset( $this->options['less'] ) || $this->options['inline'] ) {
+ $this->css = !isset( $this->options['less'] ) || !$this->options['less'] || $this->options['inline'];
+ } else {
+ $pathValue = $this->getPath();
+ if ( $pathValue && preg_match( '/css([\?;].*)?$/', $pathValue ) ) {
+ $this->css = true;
+ }
+ }
+ }
+ }
+
+//
+// The actual import node doesn't return anything, when converted to CSS.
+// The reason is that it's used at the evaluation stage, so that the rules
+// it imports can be treated like any other rules.
+//
+// In `eval`, we make sure all Import nodes get evaluated, recursively, so
+// we end up with a flat structure, which can easily be imported in the parent
+// ruleset.
+//
+
+ public function accept( $visitor ) {
+ if ( $this->features ) {
+ $this->features = $visitor->visitObj( $this->features );
+ }
+ $this->path = $visitor->visitObj( $this->path );
+
+ if ( !$this->options['inline'] && $this->root ) {
+ $this->root = $visitor->visit( $this->root );
+ }
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( $this->css ) {
+
+ $output->add( '@import ', $this->currentFileInfo, $this->index );
+
+ $this->path->genCSS( $output );
+ if ( $this->features ) {
+ $output->add( ' ' );
+ $this->features->genCSS( $output );
+ }
+ $output->add( ';' );
+ }
+ }
+
+ public function toCSS() {
+ $features = $this->features ? ' ' . $this->features->toCSS() : '';
+
+ if ( $this->css ) {
+ return "@import " . $this->path->toCSS() . $features . ";\n";
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getPath() {
+ if ( $this->path instanceof Less_Tree_Quoted ) {
+ $path = $this->path->value;
+ $path = ( isset( $this->css ) || preg_match( '/(\.[a-z]*$)|([\?;].*)$/', $path ) ) ? $path : $path . '.less';
+ } else if ( $this->path instanceof Less_Tree_URL ) {
+ $path = $this->path->value->value;
+ } else {
+ return null;
+ }
+
+ // remove query string and fragment
+ return preg_replace( '/[\?#][^\?]*$/', '', $path );
+ }
+
+ public function compileForImport( $env ) {
+ return new Less_Tree_Import( $this->path->compile( $env ), $this->features, $this->options, $this->index, $this->currentFileInfo );
+ }
+
+ public function compilePath( $env ) {
+ $path = $this->path->compile( $env );
+ $rootpath = '';
+ if ( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ) {
+ $rootpath = $this->currentFileInfo['rootpath'];
+ }
+
+ if ( !( $path instanceof Less_Tree_URL ) ) {
+ if ( $rootpath ) {
+ $pathValue = $path->value;
+ // Add the base path if the import is relative
+ if ( $pathValue && Less_Environment::isPathRelative( $pathValue ) ) {
+ $path->value = $this->currentFileInfo['uri_root'].$pathValue;
+ }
+ }
+ $path->value = Less_Environment::normalizePath( $path->value );
+ }
+
+ return $path;
+ }
+
+ public function compile( $env ) {
+ $evald = $this->compileForImport( $env );
+
+ // get path & uri
+ $path_and_uri = null;
+ if ( is_callable( Less_Parser::$options['import_callback'] ) ) {
+ $path_and_uri = call_user_func( Less_Parser::$options['import_callback'], $evald );
+ }
+
+ if ( !$path_and_uri ) {
+ $path_and_uri = $evald->PathAndUri();
+ }
+
+ if ( $path_and_uri ) {
+ list( $full_path, $uri ) = $path_and_uri;
+ } else {
+ $full_path = $uri = $evald->getPath();
+ }
+
+ // import once
+ if ( $evald->skip( $full_path, $env ) ) {
+ return array();
+ }
+
+ if ( $this->options['inline'] ) {
+ // todo needs to reference css file not import
+ //$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true );
+
+ Less_Parser::AddParsedFile( $full_path );
+ $contents = new Less_Tree_Anonymous( file_get_contents( $full_path ), 0, array(), true );
+
+ if ( $this->features ) {
+ return new Less_Tree_Media( array( $contents ), $this->features->value );
+ }
+
+ return array( $contents );
+ }
+
+ // optional (need to be before "CSS" to support optional CSS imports. CSS should be checked only if empty($this->currentFileInfo))
+ if ( isset( $this->options['optional'] ) && $this->options['optional'] && !file_exists( $full_path ) && ( !$evald->css || !empty( $this->currentFileInfo ) ) ) {
+ return array();
+ }
+
+ // css ?
+ if ( $evald->css ) {
+ $features = ( $evald->features ? $evald->features->compile( $env ) : null );
+ return new Less_Tree_Import( $this->compilePath( $env ), $features, $this->options, $this->index );
+ }
+
+ return $this->ParseImport( $full_path, $uri, $env );
+ }
+
+ /**
+ * Using the import directories, get the full absolute path and uri of the import
+ */
+ public function PathAndUri() {
+ $evald_path = $this->getPath();
+
+ if ( $evald_path ) {
+
+ $import_dirs = array();
+
+ if ( Less_Environment::isPathRelative( $evald_path ) ) {
+ // if the path is relative, the file should be in the current directory
+ if ( $this->currentFileInfo ) {
+ $import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root'];
+ }
+
+ } else {
+ // otherwise, the file should be relative to the server root
+ if ( $this->currentFileInfo ) {
+ $import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri'];
+ }
+ // if the user supplied entryPath isn't the actual root
+ $import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = '';
+
+ }
+
+ // always look in user supplied import directories
+ $import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] );
+
+ foreach ( $import_dirs as $rootpath => $rooturi ) {
+ if ( is_callable( $rooturi ) ) {
+ list( $path, $uri ) = call_user_func( $rooturi, $evald_path );
+ if ( is_string( $path ) ) {
+ $full_path = $path;
+ return array( $full_path, $uri );
+ }
+ } elseif ( !empty( $rootpath ) ) {
+
+ $path = rtrim( $rootpath, '/\\' ).'/'.ltrim( $evald_path, '/\\' );
+
+ if ( file_exists( $path ) ) {
+ $full_path = Less_Environment::normalizePath( $path );
+ $uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path ) );
+ return array( $full_path, $uri );
+ } elseif ( file_exists( $path.'.less' ) ) {
+ $full_path = Less_Environment::normalizePath( $path.'.less' );
+ $uri = Less_Environment::normalizePath( dirname( $rooturi.$evald_path.'.less' ) );
+ return array( $full_path, $uri );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Parse the import url and return the rules
+ *
+ * @return Less_Tree_Media|array
+ */
+ public function ParseImport( $full_path, $uri, $env ) {
+ $import_env = clone $env;
+ if ( ( isset( $this->options['reference'] ) && $this->options['reference'] ) || isset( $this->currentFileInfo['reference'] ) ) {
+ $import_env->currentFileInfo['reference'] = true;
+ }
+
+ if ( ( isset( $this->options['multiple'] ) && $this->options['multiple'] ) ) {
+ $import_env->importMultiple = true;
+ }
+
+ $parser = new Less_Parser( $import_env );
+ $root = $parser->parseFile( $full_path, $uri, true );
+
+ $ruleset = new Less_Tree_Ruleset( array(), $root->rules );
+ $ruleset->evalImports( $import_env );
+
+ return $this->features ? new Less_Tree_Media( $ruleset->rules, $this->features->value ) : $ruleset->rules;
+ }
+
+ /**
+ * Should the import be skipped?
+ *
+ * @return boolean|null
+ */
+ private function Skip( $path, $env ) {
+ $path = Less_Parser::AbsPath( $path, true );
+
+ if ( $path && Less_Parser::FileParsed( $path ) ) {
+
+ if ( isset( $this->currentFileInfo['reference'] ) ) {
+ return true;
+ }
+
+ return !isset( $this->options['multiple'] ) && !$env->importMultiple;
+ }
+
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Javascript.php b/library/vendor/lessphp/lib/Less/Tree/Javascript.php
new file mode 100644
index 0000000..179781a
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Javascript.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Javascript
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Javascript extends Less_Tree {
+
+ public $type = 'Javascript';
+ public $escaped;
+ public $expression;
+ public $index;
+
+ /**
+ * @param boolean $index
+ * @param boolean $escaped
+ */
+ public function __construct( $string, $index, $escaped ) {
+ $this->escaped = $escaped;
+ $this->expression = $string;
+ $this->index = $index;
+ }
+
+ public function compile() {
+ return new Less_Tree_Anonymous( '/* Sorry, can not do JavaScript evaluation in PHP... :( */' );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Keyword.php b/library/vendor/lessphp/lib/Less/Tree/Keyword.php
new file mode 100644
index 0000000..ed217cf
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Keyword.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Keyword
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Keyword extends Less_Tree {
+
+ public $value;
+ public $type = 'Keyword';
+
+ /**
+ * @param string $value
+ */
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ public function compile() {
+ return $this;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( $this->value === '%' ) {
+ throw new Less_Exception_Compiler( "Invalid % without number" );
+ }
+
+ $output->add( $this->value );
+ }
+
+ public function compare( $other ) {
+ if ( $other instanceof Less_Tree_Keyword ) {
+ return $other->value === $this->value ? 0 : 1;
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Media.php b/library/vendor/lessphp/lib/Less/Tree/Media.php
new file mode 100644
index 0000000..ba25a67
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Media.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Media
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Media extends Less_Tree {
+
+ public $features;
+ public $rules;
+ public $index;
+ public $currentFileInfo;
+ public $isReferenced;
+ public $type = 'Media';
+
+ public function __construct( $value = array(), $features = array(), $index = null, $currentFileInfo = null ) {
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+
+ $selectors = $this->emptySelectors();
+
+ $this->features = new Less_Tree_Value( $features );
+
+ $this->rules = array( new Less_Tree_Ruleset( $selectors, $value ) );
+ $this->rules[0]->allowImports = true;
+ }
+
+ public function accept( $visitor ) {
+ $this->features = $visitor->visitObj( $this->features );
+ $this->rules = $visitor->visitArray( $this->rules );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( '@media ', $this->currentFileInfo, $this->index );
+ $this->features->genCSS( $output );
+ Less_Tree::outputRuleset( $output, $this->rules );
+
+ }
+
+ public function compile( $env ) {
+ $media = new Less_Tree_Media( array(), array(), $this->index, $this->currentFileInfo );
+
+ $strictMathBypass = false;
+ if ( Less_Parser::$options['strictMath'] === false ) {
+ $strictMathBypass = true;
+ Less_Parser::$options['strictMath'] = true;
+ }
+
+ $media->features = $this->features->compile( $env );
+
+ if ( $strictMathBypass ) {
+ Less_Parser::$options['strictMath'] = false;
+ }
+
+ $env->mediaPath[] = $media;
+ $env->mediaBlocks[] = $media;
+
+ array_unshift( $env->frames, $this->rules[0] );
+ $media->rules = array( $this->rules[0]->compile( $env ) );
+ array_shift( $env->frames );
+
+ array_pop( $env->mediaPath );
+
+ return !$env->mediaPath ? $media->compileTop( $env ) : $media->compileNested( $env );
+ }
+
+ public function variable( $name ) {
+ return $this->rules[0]->variable( $name );
+ }
+
+ public function find( $selector ) {
+ return $this->rules[0]->find( $selector, $this );
+ }
+
+ public function emptySelectors() {
+ $el = new Less_Tree_Element( '', '&', $this->index, $this->currentFileInfo );
+ $sels = array( new Less_Tree_Selector( array( $el ), array(), null, $this->index, $this->currentFileInfo ) );
+ $sels[0]->mediaEmpty = true;
+ return $sels;
+ }
+
+ public function markReferenced() {
+ $this->rules[0]->markReferenced();
+ $this->isReferenced = true;
+ Less_Tree::ReferencedArray( $this->rules[0]->rules );
+ }
+
+ // evaltop
+ public function compileTop( $env ) {
+ $result = $this;
+
+ if ( count( $env->mediaBlocks ) > 1 ) {
+ $selectors = $this->emptySelectors();
+ $result = new Less_Tree_Ruleset( $selectors, $env->mediaBlocks );
+ $result->multiMedia = true;
+ }
+
+ $env->mediaBlocks = array();
+ $env->mediaPath = array();
+
+ return $result;
+ }
+
+ public function compileNested( $env ) {
+ $path = array_merge( $env->mediaPath, array( $this ) );
+
+ // Extract the media-query conditions separated with `,` (OR).
+ foreach ( $path as $key => $p ) {
+ $value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features;
+ $path[$key] = is_array( $value ) ? $value : array( $value );
+ }
+
+ // Trace all permutations to generate the resulting media-query.
+ //
+ // (a, b and c) with nested (d, e) ->
+ // a and d
+ // a and e
+ // b and c and d
+ // b and c and e
+
+ $permuted = $this->permute( $path );
+ $expressions = array();
+ foreach ( $permuted as $path ) {
+
+ for ( $i = 0, $len = count( $path ); $i < $len; $i++ ) {
+ $path[$i] = Less_Parser::is_method( $path[$i], 'toCSS' ) ? $path[$i] : new Less_Tree_Anonymous( $path[$i] );
+ }
+
+ for ( $i = count( $path ) - 1; $i > 0; $i-- ) {
+ array_splice( $path, $i, 0, array( new Less_Tree_Anonymous( 'and' ) ) );
+ }
+
+ $expressions[] = new Less_Tree_Expression( $path );
+ }
+ $this->features = new Less_Tree_Value( $expressions );
+
+ // Fake a tree-node that doesn't output anything.
+ return new Less_Tree_Ruleset( array(), array() );
+ }
+
+ public function permute( $arr ) {
+ if ( !$arr )
+ return array();
+
+ if ( count( $arr ) == 1 )
+ return $arr[0];
+
+ $result = array();
+ $rest = $this->permute( array_slice( $arr, 1 ) );
+ foreach ( $rest as $r ) {
+ foreach ( $arr[0] as $a ) {
+ $result[] = array_merge(
+ is_array( $a ) ? $a : array( $a ),
+ is_array( $r ) ? $r : array( $r )
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ public function bubbleSelectors( $selectors ) {
+ if ( !$selectors ) return;
+
+ $this->rules = array( new Less_Tree_Ruleset( $selectors, array( $this->rules[0] ) ) );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Mixin/Call.php b/library/vendor/lessphp/lib/Less/Tree/Mixin/Call.php
new file mode 100644
index 0000000..8e1eece
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Mixin/Call.php
@@ -0,0 +1,193 @@
+<?php
+
+class Less_Tree_Mixin_Call extends Less_Tree {
+
+ public $selector;
+ public $arguments;
+ public $index;
+ public $currentFileInfo;
+
+ public $important;
+ public $type = 'MixinCall';
+
+ /**
+ * less.js: tree.mixin.Call
+ *
+ */
+ public function __construct( $elements, $args, $index, $currentFileInfo, $important = false ) {
+ $this->selector = new Less_Tree_Selector( $elements );
+ $this->arguments = $args;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->important = $important;
+ }
+
+ // function accept($visitor){
+ // $this->selector = $visitor->visit($this->selector);
+ // $this->arguments = $visitor->visit($this->arguments);
+ //}
+
+ public function compile( $env ) {
+ $rules = array();
+ $match = false;
+ $isOneFound = false;
+ $candidates = array();
+ $defaultUsed = false;
+ $conditionResult = array();
+
+ $args = array();
+ foreach ( $this->arguments as $a ) {
+ $args[] = array( 'name' => $a['name'], 'value' => $a['value']->compile( $env ) );
+ }
+
+ foreach ( $env->frames as $frame ) {
+
+ $mixins = $frame->find( $this->selector );
+
+ if ( !$mixins ) {
+ continue;
+ }
+
+ $isOneFound = true;
+ $defNone = 0;
+ $defTrue = 1;
+ $defFalse = 2;
+
+ // To make `default()` function independent of definition order we have two "subpasses" here.
+ // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
+ // and build candidate list with corresponding flags. Then, when we know all possible matches,
+ // we make a final decision.
+
+ $mixins_len = count( $mixins );
+ for ( $m = 0; $m < $mixins_len; $m++ ) {
+ $mixin = $mixins[$m];
+
+ if ( $this->IsRecursive( $env, $mixin ) ) {
+ continue;
+ }
+
+ if ( $mixin->matchArgs( $args, $env ) ) {
+
+ $candidate = array( 'mixin' => $mixin, 'group' => $defNone );
+
+ if ( $mixin instanceof Less_Tree_Ruleset ) {
+
+ for ( $f = 0; $f < 2; $f++ ) {
+ Less_Tree_DefaultFunc::value( $f );
+ $conditionResult[$f] = $mixin->matchCondition( $args, $env );
+ }
+ if ( $conditionResult[0] || $conditionResult[1] ) {
+ if ( $conditionResult[0] != $conditionResult[1] ) {
+ $candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
+ }
+
+ $candidates[] = $candidate;
+ }
+ } else {
+ $candidates[] = $candidate;
+ }
+
+ $match = true;
+ }
+ }
+
+ Less_Tree_DefaultFunc::reset();
+
+ $count = array( 0, 0, 0 );
+ for ( $m = 0; $m < count( $candidates ); $m++ ) {
+ $count[ $candidates[$m]['group'] ]++;
+ }
+
+ if ( $count[$defNone] > 0 ) {
+ $defaultResult = $defFalse;
+ } else {
+ $defaultResult = $defTrue;
+ if ( ( $count[$defTrue] + $count[$defFalse] ) > 1 ) {
+ throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format( $args ) . '`' );
+ }
+ }
+
+ $candidates_length = count( $candidates );
+ $length_1 = ( $candidates_length == 1 );
+
+ for ( $m = 0; $m < $candidates_length; $m++ ) {
+ $candidate = $candidates[$m]['group'];
+ if ( ( $candidate === $defNone ) || ( $candidate === $defaultResult ) ) {
+ try{
+ $mixin = $candidates[$m]['mixin'];
+ if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
+ $mixin = new Less_Tree_Mixin_Definition( '', array(), $mixin->rules, null, false );
+ $mixin->originalRuleset = $mixins[$m]->originalRuleset;
+ }
+ $rules = array_merge( $rules, $mixin->evalCall( $env, $args, $this->important )->rules );
+ } catch ( Exception $e ) {
+ // throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
+ throw new Less_Exception_Compiler( $e->getMessage(), null, null, $this->currentFileInfo );
+ }
+ }
+ }
+
+ if ( $match ) {
+ if ( !$this->currentFileInfo || !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] ) {
+ Less_Tree::ReferencedArray( $rules );
+ }
+
+ return $rules;
+ }
+ }
+
+ if ( $isOneFound ) {
+ throw new Less_Exception_Compiler( 'No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo );
+
+ } else {
+ throw new Less_Exception_Compiler( trim( $this->selector->toCSS() ) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index );
+ }
+
+ }
+
+ /**
+ * Format the args for use in exception messages
+ *
+ */
+ private function Format( $args ) {
+ $message = array();
+ if ( $args ) {
+ foreach ( $args as $a ) {
+ $argValue = '';
+ if ( $a['name'] ) {
+ $argValue .= $a['name'] . ':';
+ }
+ if ( is_object( $a['value'] ) ) {
+ $argValue .= $a['value']->toCSS();
+ } else {
+ $argValue .= '???';
+ }
+ $message[] = $argValue;
+ }
+ }
+ return implode( ', ', $message );
+ }
+
+ /**
+ * Are we in a recursive mixin call?
+ *
+ * @return bool
+ */
+ private function IsRecursive( $env, $mixin ) {
+ foreach ( $env->frames as $recur_frame ) {
+ if ( !( $mixin instanceof Less_Tree_Mixin_Definition ) ) {
+
+ if ( $mixin === $recur_frame ) {
+ return true;
+ }
+
+ if ( isset( $recur_frame->originalRuleset ) && $mixin->ruleset_id === $recur_frame->originalRuleset ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Mixin/Definition.php b/library/vendor/lessphp/lib/Less/Tree/Mixin/Definition.php
new file mode 100644
index 0000000..f9f2eb4
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Mixin/Definition.php
@@ -0,0 +1,233 @@
+<?php
+
+class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset {
+ public $name;
+ public $selectors;
+ public $params;
+ public $arity = 0;
+ public $rules;
+ public $lookups = array();
+ public $required = 0;
+ public $frames = array();
+ public $condition;
+ public $variadic;
+ public $type = 'MixinDefinition';
+
+ // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
+ public function __construct( $name, $params, $rules, $condition, $variadic = false, $frames = array() ) {
+ $this->name = $name;
+ $this->selectors = array( new Less_Tree_Selector( array( new Less_Tree_Element( null, $name ) ) ) );
+
+ $this->params = $params;
+ $this->condition = $condition;
+ $this->variadic = $variadic;
+ $this->rules = $rules;
+
+ if ( $params ) {
+ $this->arity = count( $params );
+ foreach ( $params as $p ) {
+ if ( !isset( $p['name'] ) || ( $p['name'] && !isset( $p['value'] ) ) ) {
+ $this->required++;
+ }
+ }
+ }
+
+ $this->frames = $frames;
+ $this->SetRulesetIndex();
+ }
+
+ // function accept( $visitor ){
+ // $this->params = $visitor->visit($this->params);
+ // $this->rules = $visitor->visit($this->rules);
+ // $this->condition = $visitor->visit($this->condition);
+ //}
+
+ public function toCSS() {
+ return '';
+ }
+
+ // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
+ public function compileParams( $env, $mixinFrames, $args = array(), &$evaldArguments = array() ) {
+ $frame = new Less_Tree_Ruleset( null, array() );
+ $params = $this->params;
+ $mixinEnv = null;
+ $argsLength = 0;
+
+ if ( $args ) {
+ $argsLength = count( $args );
+ for ( $i = 0; $i < $argsLength; $i++ ) {
+ $arg = $args[$i];
+
+ if ( $arg && $arg['name'] ) {
+ $isNamedFound = false;
+
+ foreach ( $params as $j => $param ) {
+ if ( !isset( $evaldArguments[$j] ) && $arg['name'] === $params[$j]['name'] ) {
+ $evaldArguments[$j] = $arg['value']->compile( $env );
+ array_unshift( $frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile( $env ) ) );
+ $isNamedFound = true;
+ break;
+ }
+ }
+ if ( $isNamedFound ) {
+ array_splice( $args, $i, 1 );
+ $i--;
+ $argsLength--;
+ continue;
+ } else {
+ throw new Less_Exception_Compiler( "Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found' );
+ }
+ }
+ }
+ }
+
+ $argIndex = 0;
+ foreach ( $params as $i => $param ) {
+
+ if ( isset( $evaldArguments[$i] ) ) { continue;
+ }
+
+ $arg = null;
+ if ( isset( $args[$argIndex] ) ) {
+ $arg = $args[$argIndex];
+ }
+
+ if ( isset( $param['name'] ) && $param['name'] ) {
+
+ if ( isset( $param['variadic'] ) ) {
+ $varargs = array();
+ for ( $j = $argIndex; $j < $argsLength; $j++ ) {
+ $varargs[] = $args[$j]['value']->compile( $env );
+ }
+ $expression = new Less_Tree_Expression( $varargs );
+ array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $expression->compile( $env ) ) );
+ } else {
+ $val = ( $arg && $arg['value'] ) ? $arg['value'] : false;
+
+ if ( $val ) {
+ $val = $val->compile( $env );
+ } else if ( isset( $param['value'] ) ) {
+
+ if ( !$mixinEnv ) {
+ $mixinEnv = new Less_Environment();
+ $mixinEnv->frames = array_merge( array( $frame ), $mixinFrames );
+ }
+
+ $val = $param['value']->compile( $mixinEnv );
+ $frame->resetCache();
+ } else {
+ throw new Less_Exception_Compiler( "Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")" );
+ }
+
+ array_unshift( $frame->rules, new Less_Tree_Rule( $param['name'], $val ) );
+ $evaldArguments[$i] = $val;
+ }
+ }
+
+ if ( isset( $param['variadic'] ) && $args ) {
+ for ( $j = $argIndex; $j < $argsLength; $j++ ) {
+ $evaldArguments[$j] = $args[$j]['value']->compile( $env );
+ }
+ }
+ $argIndex++;
+ }
+
+ ksort( $evaldArguments );
+ $evaldArguments = array_values( $evaldArguments );
+
+ return $frame;
+ }
+
+ public function compile( $env ) {
+ if ( $this->frames ) {
+ return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
+ }
+ return new Less_Tree_Mixin_Definition( $this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
+ }
+
+ public function evalCall( $env, $args = NULL, $important = NULL ) {
+ Less_Environment::$mixin_stack++;
+
+ $_arguments = array();
+
+ if ( $this->frames ) {
+ $mixinFrames = array_merge( $this->frames, $env->frames );
+ } else {
+ $mixinFrames = $env->frames;
+ }
+
+ $frame = $this->compileParams( $env, $mixinFrames, $args, $_arguments );
+
+ $ex = new Less_Tree_Expression( $_arguments );
+ array_unshift( $frame->rules, new Less_Tree_Rule( '@arguments', $ex->compile( $env ) ) );
+
+ $ruleset = new Less_Tree_Ruleset( null, $this->rules );
+ $ruleset->originalRuleset = $this->ruleset_id;
+
+ $ruleSetEnv = new Less_Environment();
+ $ruleSetEnv->frames = array_merge( array( $this, $frame ), $mixinFrames );
+ $ruleset = $ruleset->compile( $ruleSetEnv );
+
+ if ( $important ) {
+ $ruleset = $ruleset->makeImportant();
+ }
+
+ Less_Environment::$mixin_stack--;
+
+ return $ruleset;
+ }
+
+ public function matchCondition( $args, $env ) {
+ if ( !$this->condition ) {
+ return true;
+ }
+
+ // set array to prevent error on array_merge
+ if ( !is_array( $this->frames ) ) {
+ $this->frames = array();
+ }
+
+ $frame = $this->compileParams( $env, array_merge( $this->frames, $env->frames ), $args );
+
+ $compile_env = new Less_Environment();
+ $compile_env->frames = array_merge(
+ array( $frame ), // the parameter variables
+ $this->frames, // the parent namespace/mixin frames
+ $env->frames // the current environment frames
+ );
+
+ $compile_env->functions = $env->functions;
+
+ return (bool)$this->condition->compile( $compile_env );
+ }
+
+ public function matchArgs( $args, $env = NULL ) {
+ $argsLength = count( $args );
+
+ if ( !$this->variadic ) {
+ if ( $argsLength < $this->required ) {
+ return false;
+ }
+ if ( $argsLength > count( $this->params ) ) {
+ return false;
+ }
+ } else {
+ if ( $argsLength < ( $this->required - 1 ) ) {
+ return false;
+ }
+ }
+
+ $len = min( $argsLength, $this->arity );
+
+ for ( $i = 0; $i < $len; $i++ ) {
+ if ( !isset( $this->params[$i]['name'] ) && !isset( $this->params[$i]['variadic'] ) ) {
+ if ( $args[$i]['value']->compile( $env )->toCSS() != $this->params[$i]['value']->compile( $env )->toCSS() ) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/NameValue.php b/library/vendor/lessphp/lib/Less/Tree/NameValue.php
new file mode 100644
index 0000000..4f9aa97
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/NameValue.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * A simple css name-value pair
+ * ex: width:100px;
+ *
+ * In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule)
+ * Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000;
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_NameValue extends Less_Tree {
+
+ public $name;
+ public $value;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'NameValue';
+ public $important = '';
+
+ public function __construct( $name, $value = null, $index = null, $currentFileInfo = null ) {
+ $this->name = $name;
+ $this->value = $value;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function genCSS( $output ) {
+ $output->add(
+ $this->name
+ . Less_Environment::$_outputMap[': ']
+ . $this->value
+ . $this->important
+ . ( ( ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ),
+ $this->currentFileInfo, $this->index );
+ }
+
+ public function compile( $env ) {
+ return $this;
+ }
+
+ public function makeImportant() {
+ $new = new Less_Tree_NameValue( $this->name, $this->value, $this->index, $this->currentFileInfo );
+ $new->important = ' !important';
+ return $new;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Negative.php b/library/vendor/lessphp/lib/Less/Tree/Negative.php
new file mode 100644
index 0000000..f0f36c8
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Negative.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Negative
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Negative extends Less_Tree {
+
+ public $value;
+ public $type = 'Negative';
+
+ public function __construct( $node ) {
+ $this->value = $node;
+ }
+
+ // function accept($visitor) {
+ // $this->value = $visitor->visit($this->value);
+ //}
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( '-' );
+ $this->value->genCSS( $output );
+ }
+
+ public function compile( $env ) {
+ if ( Less_Environment::isMathOn() ) {
+ $ret = new Less_Tree_Operation( '*', array( new Less_Tree_Dimension( -1 ), $this->value ) );
+ return $ret->compile( $env );
+ }
+ return new Less_Tree_Negative( $this->value->compile( $env ) );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Operation.php b/library/vendor/lessphp/lib/Less/Tree/Operation.php
new file mode 100644
index 0000000..d4eb9ac
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Operation.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Operation
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Operation extends Less_Tree {
+
+ public $op;
+ public $operands;
+ public $isSpaced;
+ public $type = 'Operation';
+
+ /**
+ * @param string $op
+ */
+ public function __construct( $op, $operands, $isSpaced = false ) {
+ $this->op = trim( $op );
+ $this->operands = $operands;
+ $this->isSpaced = $isSpaced;
+ }
+
+ public function accept( $visitor ) {
+ $this->operands = $visitor->visitArray( $this->operands );
+ }
+
+ public function compile( $env ) {
+ $a = $this->operands[0]->compile( $env );
+ $b = $this->operands[1]->compile( $env );
+
+ if ( Less_Environment::isMathOn() ) {
+
+ if ( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ) {
+ $a = $a->toColor();
+
+ } elseif ( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ) {
+ $b = $b->toColor();
+
+ }
+
+ if ( !method_exists( $a, 'operate' ) ) {
+ throw new Less_Exception_Compiler( "Operation on an invalid type" );
+ }
+
+ return $a->operate( $this->op, $b );
+ }
+
+ return new Less_Tree_Operation( $this->op, array( $a, $b ), $this->isSpaced );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $this->operands[0]->genCSS( $output );
+ if ( $this->isSpaced ) {
+ $output->add( " " );
+ }
+ $output->add( $this->op );
+ if ( $this->isSpaced ) {
+ $output->add( ' ' );
+ }
+ $this->operands[1]->genCSS( $output );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Paren.php b/library/vendor/lessphp/lib/Less/Tree/Paren.php
new file mode 100644
index 0000000..77ee48c
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Paren.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Paren
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Paren extends Less_Tree {
+
+ public $value;
+ public $type = 'Paren';
+
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( '(' );
+ $this->value->genCSS( $output );
+ $output->add( ')' );
+ }
+
+ public function compile( $env ) {
+ return new Less_Tree_Paren( $this->value->compile( $env ) );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Quoted.php b/library/vendor/lessphp/lib/Less/Tree/Quoted.php
new file mode 100644
index 0000000..d01598b
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Quoted.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Quoted
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Quoted extends Less_Tree {
+ public $escaped;
+ public $value;
+ public $quote;
+ public $index;
+ public $currentFileInfo;
+ public $type = 'Quoted';
+
+ /**
+ * @param string $str
+ */
+ public function __construct( $str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ) {
+ $this->escaped = $escaped;
+ $this->value = $content;
+ if ( $str ) {
+ $this->quote = $str[0];
+ }
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( !$this->escaped ) {
+ $output->add( $this->quote, $this->currentFileInfo, $this->index );
+ }
+ $output->add( $this->value );
+ if ( !$this->escaped ) {
+ $output->add( $this->quote );
+ }
+ }
+
+ public function compile( $env ) {
+ $value = $this->value;
+ if ( preg_match_all( '/`([^`]+)`/', $this->value, $matches ) ) {
+ foreach ( $matches as $i => $match ) {
+ $js = new Less_Tree_JavaScript( $matches[1], $this->index, true );
+ $js = $js->compile()->value;
+ $value = str_replace( $matches[0][$i], $js, $value );
+ }
+ }
+
+ if ( preg_match_all( '/@\{([\w-]+)\}/', $value, $matches ) ) {
+ foreach ( $matches[1] as $i => $match ) {
+ $v = new Less_Tree_Variable( '@' . $match, $this->index, $this->currentFileInfo );
+ $v = $v->compile( $env );
+ $v = ( $v instanceof Less_Tree_Quoted ) ? $v->value : $v->toCSS();
+ $value = str_replace( $matches[0][$i], $v, $value );
+ }
+ }
+
+ return new Less_Tree_Quoted( $this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo );
+ }
+
+ public function compare( $x ) {
+ if ( !Less_Parser::is_method( $x, 'toCSS' ) ) {
+ return -1;
+ }
+
+ $left = $this->toCSS();
+ $right = $x->toCSS();
+
+ if ( $left === $right ) {
+ return 0;
+ }
+
+ return $left < $right ? -1 : 1;
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Rule.php b/library/vendor/lessphp/lib/Less/Tree/Rule.php
new file mode 100644
index 0000000..5f393eb
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Rule.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * Rule
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Rule extends Less_Tree {
+
+ public $name;
+ public $value;
+ public $important;
+ public $merge;
+ public $index;
+ public $inline;
+ public $variable;
+ public $currentFileInfo;
+ public $type = 'Rule';
+
+ /**
+ * @param string $important
+ */
+ public function __construct( $name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false ) {
+ $this->name = $name;
+ $this->value = ( $value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset ) ? $value : new Less_Tree_Value( array( $value ) );
+ $this->important = $important ? ' ' . trim( $important ) : '';
+ $this->merge = $merge;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->inline = $inline;
+ $this->variable = ( is_string( $name ) && $name[0] === '@' );
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index );
+ try{
+ $this->value->genCSS( $output );
+
+ }catch ( Less_Exception_Parser $e ) {
+ $e->index = $this->index;
+ $e->currentFile = $this->currentFileInfo;
+ throw $e;
+ }
+ $output->add( $this->important . ( ( $this->inline || ( Less_Environment::$lastRule && Less_Parser::$options['compress'] ) ) ? "" : ";" ), $this->currentFileInfo, $this->index );
+ }
+
+ public function compile( $env ) {
+ $name = $this->name;
+ if ( is_array( $name ) ) {
+ // expand 'primitive' name directly to get
+ // things faster (~10% for benchmark.less):
+ if ( count( $name ) === 1 && $name[0] instanceof Less_Tree_Keyword ) {
+ $name = $name[0]->value;
+ } else {
+ $name = $this->CompileName( $env, $name );
+ }
+ }
+
+ $strictMathBypass = Less_Parser::$options['strictMath'];
+ if ( $name === "font" && !Less_Parser::$options['strictMath'] ) {
+ Less_Parser::$options['strictMath'] = true;
+ }
+
+ try {
+ $evaldValue = $this->value->compile( $env );
+
+ if ( !$this->variable && $evaldValue->type === "DetachedRuleset" ) {
+ throw new Less_Exception_Compiler( "Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo );
+ }
+
+ if ( Less_Environment::$mixin_stack ) {
+ $return = new Less_Tree_Rule( $name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline );
+ } else {
+ $this->name = $name;
+ $this->value = $evaldValue;
+ $return = $this;
+ }
+
+ }catch ( Less_Exception_Parser $e ) {
+ if ( !is_numeric( $e->index ) ) {
+ $e->index = $this->index;
+ $e->currentFile = $this->currentFileInfo;
+ }
+ throw $e;
+ }
+
+ Less_Parser::$options['strictMath'] = $strictMathBypass;
+
+ return $return;
+ }
+
+ public function CompileName( $env, $name ) {
+ $output = new Less_Output();
+ foreach ( $name as $n ) {
+ $n->compile( $env )->genCSS( $output );
+ }
+ return $output->toString();
+ }
+
+ public function makeImportant() {
+ return new Less_Tree_Rule( $this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Ruleset.php b/library/vendor/lessphp/lib/Less/Tree/Ruleset.php
new file mode 100644
index 0000000..b621263
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Ruleset.php
@@ -0,0 +1,620 @@
+<?php
+
+/**
+ * Ruleset
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Ruleset extends Less_Tree {
+
+ protected $lookups;
+ public $_variables;
+ public $_rulesets;
+
+ public $strictImports;
+
+ public $selectors;
+ public $rules;
+ public $root;
+ public $allowImports;
+ public $paths;
+ public $firstRoot;
+ public $type = 'Ruleset';
+ public $multiMedia;
+ public $allExtends;
+
+ public $ruleset_id;
+ public $originalRuleset;
+
+ public $first_oelements;
+
+ public function SetRulesetIndex() {
+ $this->ruleset_id = Less_Parser::$next_id++;
+ $this->originalRuleset = $this->ruleset_id;
+
+ if ( $this->selectors ) {
+ foreach ( $this->selectors as $sel ) {
+ if ( $sel->_oelements ) {
+ $this->first_oelements[$sel->_oelements[0]] = true;
+ }
+ }
+ }
+ }
+
+ public function __construct( $selectors, $rules, $strictImports = null ) {
+ $this->selectors = $selectors;
+ $this->rules = $rules;
+ $this->lookups = array();
+ $this->strictImports = $strictImports;
+ $this->SetRulesetIndex();
+ }
+
+ public function accept( $visitor ) {
+ if ( $this->paths ) {
+ $paths_len = count( $this->paths );
+ for ( $i = 0,$paths_len; $i < $paths_len; $i++ ) {
+ $this->paths[$i] = $visitor->visitArray( $this->paths[$i] );
+ }
+ } elseif ( $this->selectors ) {
+ $this->selectors = $visitor->visitArray( $this->selectors );
+ }
+
+ if ( $this->rules ) {
+ $this->rules = $visitor->visitArray( $this->rules );
+ }
+ }
+
+ public function compile( $env ) {
+ $ruleset = $this->PrepareRuleset( $env );
+
+ // Store the frames around mixin definitions,
+ // so they can be evaluated like closures when the time comes.
+ $rsRuleCnt = count( $ruleset->rules );
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ if ( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) {
+ $ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
+ }
+ }
+
+ $mediaBlockCount = 0;
+ if ( $env instanceof Less_Environment ) {
+ $mediaBlockCount = count( $env->mediaBlocks );
+ }
+
+ // Evaluate mixin calls.
+ $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
+
+ // Evaluate everything else
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ if ( !( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ) ) {
+ $ruleset->rules[$i] = $ruleset->rules[$i]->compile( $env );
+ }
+ }
+
+ // Evaluate everything else
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ $rule = $ruleset->rules[$i];
+
+ // for rulesets, check if it is a css guard and can be removed
+ if ( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count( $rule->selectors ) === 1 ) {
+
+ // check if it can be folded in (e.g. & where)
+ if ( $rule->selectors[0]->isJustParentSelector() ) {
+ array_splice( $ruleset->rules, $i--, 1 );
+ $rsRuleCnt--;
+
+ for ( $j = 0; $j < count( $rule->rules ); $j++ ) {
+ $subRule = $rule->rules[$j];
+ if ( !( $subRule instanceof Less_Tree_Rule ) || !$subRule->variable ) {
+ array_splice( $ruleset->rules, ++$i, 0, array( $subRule ) );
+ $rsRuleCnt++;
+ }
+ }
+
+ }
+ }
+ }
+
+ // Pop the stack
+ $env->shiftFrame();
+
+ if ( $mediaBlockCount ) {
+ $len = count( $env->mediaBlocks );
+ for ( $i = $mediaBlockCount; $i < $len; $i++ ) {
+ $env->mediaBlocks[$i]->bubbleSelectors( $ruleset->selectors );
+ }
+ }
+
+ return $ruleset;
+ }
+
+ /**
+ * Compile Less_Tree_Mixin_Call objects
+ *
+ * @param Less_Tree_Ruleset $ruleset
+ * @param integer $rsRuleCnt
+ */
+ private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ) {
+ for ( $i = 0; $i < $rsRuleCnt; $i++ ) {
+ $rule = $ruleset->rules[$i];
+
+ if ( $rule instanceof Less_Tree_Mixin_Call ) {
+ $rule = $rule->compile( $env );
+
+ $temp = array();
+ foreach ( $rule as $r ) {
+ if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
+ // do not pollute the scope if the variable is
+ // already there. consider returning false here
+ // but we need a way to "return" variable from mixins
+ if ( !$ruleset->variable( $r->name ) ) {
+ $temp[] = $r;
+ }
+ } else {
+ $temp[] = $r;
+ }
+ }
+ $temp_count = count( $temp ) - 1;
+ array_splice( $ruleset->rules, $i, 1, $temp );
+ $rsRuleCnt += $temp_count;
+ $i += $temp_count;
+ $ruleset->resetCache();
+
+ } elseif ( $rule instanceof Less_Tree_RulesetCall ) {
+
+ $rule = $rule->compile( $env );
+ $rules = array();
+ foreach ( $rule->rules as $r ) {
+ if ( ( $r instanceof Less_Tree_Rule ) && $r->variable ) {
+ continue;
+ }
+ $rules[] = $r;
+ }
+
+ array_splice( $ruleset->rules, $i, 1, $rules );
+ $temp_count = count( $rules );
+ $rsRuleCnt += $temp_count - 1;
+ $i += $temp_count - 1;
+ $ruleset->resetCache();
+ }
+
+ }
+ }
+
+ /**
+ * Compile the selectors and create a new ruleset object for the compile() method
+ */
+ private function PrepareRuleset( $env ) {
+ $hasOnePassingSelector = false;
+ $selectors = array();
+ if ( $this->selectors ) {
+ Less_Tree_DefaultFunc::error( "it is currently only allowed in parametric mixin guards," );
+
+ foreach ( $this->selectors as $s ) {
+ $selector = $s->compile( $env );
+ $selectors[] = $selector;
+ if ( $selector->evaldCondition ) {
+ $hasOnePassingSelector = true;
+ }
+ }
+
+ Less_Tree_DefaultFunc::reset();
+ } else {
+ $hasOnePassingSelector = true;
+ }
+
+ if ( $this->rules && $hasOnePassingSelector ) {
+ $rules = $this->rules;
+ } else {
+ $rules = array();
+ }
+
+ $ruleset = new Less_Tree_Ruleset( $selectors, $rules, $this->strictImports );
+
+ $ruleset->originalRuleset = $this->ruleset_id;
+
+ $ruleset->root = $this->root;
+ $ruleset->firstRoot = $this->firstRoot;
+ $ruleset->allowImports = $this->allowImports;
+
+ // push the current ruleset to the frames stack
+ $env->unshiftFrame( $ruleset );
+
+ // Evaluate imports
+ if ( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ) {
+ $ruleset->evalImports( $env );
+ }
+
+ return $ruleset;
+ }
+
+ function evalImports( $env ) {
+ $rules_len = count( $this->rules );
+ for ( $i = 0; $i < $rules_len; $i++ ) {
+ $rule = $this->rules[$i];
+
+ if ( $rule instanceof Less_Tree_Import ) {
+ $rules = $rule->compile( $env );
+ if ( is_array( $rules ) ) {
+ array_splice( $this->rules, $i, 1, $rules );
+ $temp_count = count( $rules ) - 1;
+ $i += $temp_count;
+ $rules_len += $temp_count;
+ } else {
+ array_splice( $this->rules, $i, 1, array( $rules ) );
+ }
+
+ $this->resetCache();
+ }
+ }
+ }
+
+ function makeImportant() {
+ $important_rules = array();
+ foreach ( $this->rules as $rule ) {
+ if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ) {
+ $important_rules[] = $rule->makeImportant();
+ } else {
+ $important_rules[] = $rule;
+ }
+ }
+
+ return new Less_Tree_Ruleset( $this->selectors, $important_rules, $this->strictImports );
+ }
+
+ public function matchArgs( $args ) {
+ return !$args;
+ }
+
+ // lets you call a css selector with a guard
+ public function matchCondition( $args, $env ) {
+ $lastSelector = end( $this->selectors );
+
+ if ( !$lastSelector->evaldCondition ) {
+ return false;
+ }
+ if ( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ) {
+ return false;
+ }
+ return true;
+ }
+
+ function resetCache() {
+ $this->_rulesets = null;
+ $this->_variables = null;
+ $this->lookups = array();
+ }
+
+ public function variables() {
+ $this->_variables = array();
+ foreach ( $this->rules as $r ) {
+ if ( $r instanceof Less_Tree_Rule && $r->variable === true ) {
+ $this->_variables[$r->name] = $r;
+ }
+ }
+ }
+
+ public function variable( $name ) {
+ if ( is_null( $this->_variables ) ) {
+ $this->variables();
+ }
+ return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
+ }
+
+ public function find( $selector, $self = null ) {
+ $key = implode( ' ', $selector->_oelements );
+
+ if ( !isset( $this->lookups[$key] ) ) {
+
+ if ( !$self ) {
+ $self = $this->ruleset_id;
+ }
+
+ $this->lookups[$key] = array();
+
+ $first_oelement = $selector->_oelements[0];
+
+ foreach ( $this->rules as $rule ) {
+ if ( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ) {
+
+ if ( isset( $rule->first_oelements[$first_oelement] ) ) {
+
+ foreach ( $rule->selectors as $ruleSelector ) {
+ $match = $selector->match( $ruleSelector );
+ if ( $match ) {
+ if ( $selector->elements_len > $match ) {
+ $this->lookups[$key] = array_merge( $this->lookups[$key], $rule->find( new Less_Tree_Selector( array_slice( $selector->elements, $match ) ), $self ) );
+ } else {
+ $this->lookups[$key][] = $rule;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $this->lookups[$key];
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( !$this->root ) {
+ Less_Environment::$tabLevel++;
+ }
+
+ $tabRuleStr = $tabSetStr = '';
+ if ( !Less_Parser::$options['compress'] ) {
+ if ( Less_Environment::$tabLevel ) {
+ $tabRuleStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel );
+ $tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'], Less_Environment::$tabLevel - 1 );
+ } else {
+ $tabSetStr = $tabRuleStr = "\n";
+ }
+ }
+
+ $ruleNodes = array();
+ $rulesetNodes = array();
+ foreach ( $this->rules as $rule ) {
+
+ $class = get_class( $rule );
+ if ( ( $class === 'Less_Tree_Media' ) || ( $class === 'Less_Tree_Directive' ) || ( $this->root && $class === 'Less_Tree_Comment' ) || ( $class === 'Less_Tree_Ruleset' && $rule->rules ) ) {
+ $rulesetNodes[] = $rule;
+ } else {
+ $ruleNodes[] = $rule;
+ }
+ }
+
+ // If this is the root node, we don't render
+ // a selector, or {}.
+ if ( !$this->root ) {
+
+ /*
+ debugInfo = tree.debugInfo(env, this, tabSetStr);
+
+ if (debugInfo) {
+ output.add(debugInfo);
+ output.add(tabSetStr);
+ }
+ */
+
+ $paths_len = count( $this->paths );
+ for ( $i = 0; $i < $paths_len; $i++ ) {
+ $path = $this->paths[$i];
+ $firstSelector = true;
+
+ foreach ( $path as $p ) {
+ $p->genCSS( $output, $firstSelector );
+ $firstSelector = false;
+ }
+
+ if ( $i + 1 < $paths_len ) {
+ $output->add( ',' . $tabSetStr );
+ }
+ }
+
+ $output->add( ( Less_Parser::$options['compress'] ? '{' : " {" ) . $tabRuleStr );
+ }
+
+ // Compile rules and rulesets
+ $ruleNodes_len = count( $ruleNodes );
+ $rulesetNodes_len = count( $rulesetNodes );
+ for ( $i = 0; $i < $ruleNodes_len; $i++ ) {
+ $rule = $ruleNodes[$i];
+
+ // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
+ // In this instance we do not know whether it is the last property
+ if ( $i + 1 === $ruleNodes_len && ( !$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ) {
+ Less_Environment::$lastRule = true;
+ }
+
+ $rule->genCSS( $output );
+
+ if ( !Less_Environment::$lastRule ) {
+ $output->add( $tabRuleStr );
+ } else {
+ Less_Environment::$lastRule = false;
+ }
+ }
+
+ if ( !$this->root ) {
+ $output->add( $tabSetStr . '}' );
+ Less_Environment::$tabLevel--;
+ }
+
+ $firstRuleset = true;
+ $space = ( $this->root ? $tabRuleStr : $tabSetStr );
+ for ( $i = 0; $i < $rulesetNodes_len; $i++ ) {
+
+ if ( $ruleNodes_len && $firstRuleset ) {
+ $output->add( $space );
+ } elseif ( !$firstRuleset ) {
+ $output->add( $space );
+ }
+ $firstRuleset = false;
+ $rulesetNodes[$i]->genCSS( $output );
+ }
+
+ if ( !Less_Parser::$options['compress'] && $this->firstRoot ) {
+ $output->add( "\n" );
+ }
+
+ }
+
+ function markReferenced() {
+ if ( !$this->selectors ) {
+ return;
+ }
+ foreach ( $this->selectors as $selector ) {
+ $selector->markReferenced();
+ }
+ }
+
+ public function joinSelectors( $context, $selectors ) {
+ $paths = array();
+ if ( is_array( $selectors ) ) {
+ foreach ( $selectors as $selector ) {
+ $this->joinSelector( $paths, $context, $selector );
+ }
+ }
+ return $paths;
+ }
+
+ public function joinSelector( &$paths, $context, $selector ) {
+ $hasParentSelector = false;
+
+ foreach ( $selector->elements as $el ) {
+ if ( $el->value === '&' ) {
+ $hasParentSelector = true;
+ }
+ }
+
+ if ( !$hasParentSelector ) {
+ if ( $context ) {
+ foreach ( $context as $context_el ) {
+ $paths[] = array_merge( $context_el, array( $selector ) );
+ }
+ } else {
+ $paths[] = array( $selector );
+ }
+ return;
+ }
+
+ // The paths are [[Selector]]
+ // The first list is a list of comma separated selectors
+ // The inner list is a list of inheritance separated selectors
+ // e.g.
+ // .a, .b {
+ // .c {
+ // }
+ // }
+ // == [[.a] [.c]] [[.b] [.c]]
+ //
+
+ // the elements from the current selector so far
+ $currentElements = array();
+ // the current list of new selectors to add to the path.
+ // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
+ // by the parents
+ $newSelectors = array( array() );
+
+ foreach ( $selector->elements as $el ) {
+
+ // non parent reference elements just get added
+ if ( $el->value !== '&' ) {
+ $currentElements[] = $el;
+ } else {
+ // the new list of selectors to add
+ $selectorsMultiplied = array();
+
+ // merge the current list of non parent selector elements
+ // on to the current list of selectors to add
+ if ( $currentElements ) {
+ $this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+ }
+
+ // loop through our current selectors
+ foreach ( $newSelectors as $sel ) {
+
+ // if we don't have any parent paths, the & might be in a mixin so that it can be used
+ // whether there are parents or not
+ if ( !$context ) {
+ // the combinator used on el should now be applied to the next element instead so that
+ // it is not lost
+ if ( $sel ) {
+ $sel[0]->elements = array_slice( $sel[0]->elements, 0 );
+ $sel[0]->elements[] = new Less_Tree_Element( $el->combinator, '', $el->index, $el->currentFileInfo );
+ }
+ $selectorsMultiplied[] = $sel;
+ } else {
+
+ // and the parent selectors
+ foreach ( $context as $parentSel ) {
+ // We need to put the current selectors
+ // then join the last selector's elements on to the parents selectors
+
+ // our new selector path
+ $newSelectorPath = array();
+ // selectors from the parent after the join
+ $afterParentJoin = array();
+ $newJoinedSelectorEmpty = true;
+
+ // construct the joined selector - if & is the first thing this will be empty,
+ // if not newJoinedSelector will be the last set of elements in the selector
+ if ( $sel ) {
+ $newSelectorPath = $sel;
+ $lastSelector = array_pop( $newSelectorPath );
+ $newJoinedSelector = $selector->createDerived( array_slice( $lastSelector->elements, 0 ) );
+ $newJoinedSelectorEmpty = false;
+ } else {
+ $newJoinedSelector = $selector->createDerived( array() );
+ }
+
+ // put together the parent selectors after the join
+ if ( count( $parentSel ) > 1 ) {
+ $afterParentJoin = array_merge( $afterParentJoin, array_slice( $parentSel, 1 ) );
+ }
+
+ if ( $parentSel ) {
+ $newJoinedSelectorEmpty = false;
+
+ // join the elements so far with the first part of the parent
+ $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo );
+
+ $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice( $parentSel[0]->elements, 1 ) );
+ }
+
+ if ( !$newJoinedSelectorEmpty ) {
+ // now add the joined selector
+ $newSelectorPath[] = $newJoinedSelector;
+ }
+
+ // and the rest of the parent
+ $newSelectorPath = array_merge( $newSelectorPath, $afterParentJoin );
+
+ // add that to our new set of selectors
+ $selectorsMultiplied[] = $newSelectorPath;
+ }
+ }
+ }
+
+ // our new selectors has been multiplied, so reset the state
+ $newSelectors = $selectorsMultiplied;
+ $currentElements = array();
+ }
+ }
+
+ // if we have any elements left over (e.g. .a& .b == .b)
+ // add them on to all the current selectors
+ if ( $currentElements ) {
+ $this->mergeElementsOnToSelectors( $currentElements, $newSelectors );
+ }
+ foreach ( $newSelectors as $new_sel ) {
+ if ( $new_sel ) {
+ $paths[] = $new_sel;
+ }
+ }
+ }
+
+ function mergeElementsOnToSelectors( $elements, &$selectors ) {
+ if ( !$selectors ) {
+ $selectors[] = array( new Less_Tree_Selector( $elements ) );
+ return;
+ }
+
+ foreach ( $selectors as &$sel ) {
+
+ // if the previous thing in sel is a parent this needs to join on to it
+ if ( $sel ) {
+ $last = count( $sel ) - 1;
+ $sel[$last] = $sel[$last]->createDerived( array_merge( $sel[$last]->elements, $elements ) );
+ } else {
+ $sel[] = new Less_Tree_Selector( $elements );
+ }
+ }
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/RulesetCall.php b/library/vendor/lessphp/lib/Less/Tree/RulesetCall.php
new file mode 100644
index 0000000..079ff4f
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/RulesetCall.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * RulesetCall
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_RulesetCall extends Less_Tree {
+
+ public $variable;
+ public $type = "RulesetCall";
+
+ public function __construct( $variable ) {
+ $this->variable = $variable;
+ }
+
+ public function accept( $visitor ) {
+ }
+
+ public function compile( $env ) {
+ $variable = new Less_Tree_Variable( $this->variable );
+ $detachedRuleset = $variable->compile( $env );
+ return $detachedRuleset->callEval( $env );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Selector.php b/library/vendor/lessphp/lib/Less/Tree/Selector.php
new file mode 100644
index 0000000..67b8a5c
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Selector.php
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Selector
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Selector extends Less_Tree {
+
+ public $elements;
+ public $condition;
+ public $extendList = array();
+ public $_css;
+ public $index;
+ public $evaldCondition = false;
+ public $type = 'Selector';
+ public $currentFileInfo = array();
+ public $isReferenced;
+ public $mediaEmpty;
+
+ public $elements_len = 0;
+
+ public $_oelements;
+ public $_oelements_assoc;
+ public $_oelements_len;
+ public $cacheable = true;
+
+ /**
+ * @param boolean $isReferenced
+ */
+ public function __construct( $elements, $extendList = array(), $condition = null, $index = null, $currentFileInfo = null, $isReferenced = null ) {
+ $this->elements = $elements;
+ $this->elements_len = count( $elements );
+ $this->extendList = $extendList;
+ $this->condition = $condition;
+ if ( $currentFileInfo ) {
+ $this->currentFileInfo = $currentFileInfo;
+ }
+ $this->isReferenced = $isReferenced;
+ if ( !$condition ) {
+ $this->evaldCondition = true;
+ }
+
+ $this->CacheElements();
+ }
+
+ public function accept( $visitor ) {
+ $this->elements = $visitor->visitArray( $this->elements );
+ $this->extendList = $visitor->visitArray( $this->extendList );
+ if ( $this->condition ) {
+ $this->condition = $visitor->visitObj( $this->condition );
+ }
+
+ if ( $visitor instanceof Less_Visitor_extendFinder ) {
+ $this->CacheElements();
+ }
+ }
+
+ public function createDerived( $elements, $extendList = null, $evaldCondition = null ) {
+ $newSelector = new Less_Tree_Selector(
+ $elements,
+ ( $extendList ?: $this->extendList ),
+ null,
+ $this->index,
+ $this->currentFileInfo,
+ $this->isReferenced
+ );
+ $newSelector->evaldCondition = $evaldCondition ?: $this->evaldCondition;
+ return $newSelector;
+ }
+
+ public function match( $other ) {
+ if ( !$other->_oelements || ( $this->elements_len < $other->_oelements_len ) ) {
+ return 0;
+ }
+
+ for ( $i = 0; $i < $other->_oelements_len; $i++ ) {
+ if ( $this->elements[$i]->value !== $other->_oelements[$i] ) {
+ return 0;
+ }
+ }
+
+ return $other->_oelements_len; // return number of matched elements
+ }
+
+ public function CacheElements() {
+ $this->_oelements = array();
+ $this->_oelements_assoc = array();
+
+ $css = '';
+
+ foreach ( $this->elements as $v ) {
+
+ $css .= $v->combinator;
+ if ( !$v->value_is_object ) {
+ $css .= $v->value;
+ continue;
+ }
+
+ if ( !property_exists( $v->value, 'value' ) || !is_string( $v->value->value ) ) {
+ $this->cacheable = false;
+ return;
+ }
+ $css .= $v->value->value;
+ }
+
+ $this->_oelements_len = preg_match_all( '/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches );
+ if ( $this->_oelements_len ) {
+ $this->_oelements = $matches[0];
+
+ if ( $this->_oelements[0] === '&' ) {
+ array_shift( $this->_oelements );
+ $this->_oelements_len--;
+ }
+
+ $this->_oelements_assoc = array_fill_keys( $this->_oelements, true );
+ }
+ }
+
+ public function isJustParentSelector() {
+ return !$this->mediaEmpty &&
+ count( $this->elements ) === 1 &&
+ $this->elements[0]->value === '&' &&
+ ( $this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === '' );
+ }
+
+ public function compile( $env ) {
+ $elements = array();
+ foreach ( $this->elements as $el ) {
+ $elements[] = $el->compile( $env );
+ }
+
+ $extendList = array();
+ foreach ( $this->extendList as $el ) {
+ $extendList[] = $el->compile( $el );
+ }
+
+ $evaldCondition = false;
+ if ( $this->condition ) {
+ $evaldCondition = $this->condition->compile( $env );
+ }
+
+ return $this->createDerived( $elements, $extendList, $evaldCondition );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output, $firstSelector = true ) {
+ if ( !$firstSelector && $this->elements[0]->combinator === "" ) {
+ $output->add( ' ', $this->currentFileInfo, $this->index );
+ }
+
+ foreach ( $this->elements as $element ) {
+ $element->genCSS( $output );
+ }
+ }
+
+ public function markReferenced() {
+ $this->isReferenced = true;
+ }
+
+ public function getIsReferenced() {
+ return !isset( $this->currentFileInfo['reference'] ) || !$this->currentFileInfo['reference'] || $this->isReferenced;
+ }
+
+ public function getIsOutput() {
+ return $this->evaldCondition;
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/UnicodeDescriptor.php b/library/vendor/lessphp/lib/Less/Tree/UnicodeDescriptor.php
new file mode 100644
index 0000000..38e0526
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/UnicodeDescriptor.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * UnicodeDescriptor
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_UnicodeDescriptor extends Less_Tree {
+
+ public $value;
+ public $type = 'UnicodeDescriptor';
+
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( $this->value );
+ }
+
+ public function compile() {
+ return $this;
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Unit.php b/library/vendor/lessphp/lib/Less/Tree/Unit.php
new file mode 100644
index 0000000..8ee4731
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Unit.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * Unit
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Unit extends Less_Tree {
+
+ var $numerator = array();
+ var $denominator = array();
+ public $backupUnit;
+ public $type = 'Unit';
+
+ public function __construct( $numerator = array(), $denominator = array(), $backupUnit = null ) {
+ $this->numerator = $numerator;
+ $this->denominator = $denominator;
+ $this->backupUnit = $backupUnit;
+ }
+
+ public function __clone() {
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ if ( $this->numerator ) {
+ $output->add( $this->numerator[0] );
+ } elseif ( $this->denominator ) {
+ $output->add( $this->denominator[0] );
+ } elseif ( !Less_Parser::$options['strictUnits'] && $this->backupUnit ) {
+ $output->add( $this->backupUnit );
+ return;
+ }
+ }
+
+ public function toString() {
+ $returnStr = implode( '*', $this->numerator );
+ foreach ( $this->denominator as $d ) {
+ $returnStr .= '/'.$d;
+ }
+ return $returnStr;
+ }
+
+ public function __toString() {
+ return $this->toString();
+ }
+
+ /**
+ * @param Less_Tree_Unit $other
+ */
+ public function compare( $other ) {
+ return $this->is( $other->toString() ) ? 0 : -1;
+ }
+
+ public function is( $unitString ) {
+ return $this->toString() === $unitString;
+ }
+
+ public function isLength() {
+ $css = $this->toCSS();
+ return !!preg_match( '/px|em|%|in|cm|mm|pc|pt|ex/', $css );
+ }
+
+ public function isAngle() {
+ return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] );
+ }
+
+ public function isEmpty() {
+ return !$this->numerator && !$this->denominator;
+ }
+
+ public function isSingular() {
+ return count( $this->numerator ) <= 1 && !$this->denominator;
+ }
+
+ public function usedUnits() {
+ $result = array();
+
+ foreach ( Less_Tree_UnitConversions::$groups as $groupName ) {
+ $group = Less_Tree_UnitConversions::${$groupName};
+
+ foreach ( $this->numerator as $atomicUnit ) {
+ if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
+ $result[$groupName] = $atomicUnit;
+ }
+ }
+
+ foreach ( $this->denominator as $atomicUnit ) {
+ if ( isset( $group[$atomicUnit] ) && !isset( $result[$groupName] ) ) {
+ $result[$groupName] = $atomicUnit;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ public function cancel() {
+ $counter = array();
+ $backup = null;
+
+ foreach ( $this->numerator as $atomicUnit ) {
+ if ( !$backup ) {
+ $backup = $atomicUnit;
+ }
+ $counter[$atomicUnit] = (isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1;
+ }
+
+ foreach ( $this->denominator as $atomicUnit ) {
+ if ( !$backup ) {
+ $backup = $atomicUnit;
+ }
+ $counter[$atomicUnit] = (isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1;
+ }
+
+ $this->numerator = array();
+ $this->denominator = array();
+
+ foreach ( $counter as $atomicUnit => $count ) {
+ if ( $count > 0 ) {
+ for ( $i = 0; $i < $count; $i++ ) {
+ $this->numerator[] = $atomicUnit;
+ }
+ } elseif ( $count < 0 ) {
+ for ( $i = 0; $i < -$count; $i++ ) {
+ $this->denominator[] = $atomicUnit;
+ }
+ }
+ }
+
+ if ( !$this->numerator && !$this->denominator && $backup ) {
+ $this->backupUnit = $backup;
+ }
+
+ sort( $this->numerator );
+ sort( $this->denominator );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/UnitConversions.php b/library/vendor/lessphp/lib/Less/Tree/UnitConversions.php
new file mode 100644
index 0000000..7f857fb
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/UnitConversions.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * UnitConversions
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_UnitConversions {
+
+ public static $groups = array( 'length','duration','angle' );
+
+ public static $length = array(
+ 'm' => 1,
+ 'cm' => 0.01,
+ 'mm' => 0.001,
+ 'in' => 0.0254,
+ 'px' => 0.000264583, // 0.0254 / 96,
+ 'pt' => 0.000352778, // 0.0254 / 72,
+ 'pc' => 0.004233333, // 0.0254 / 72 * 12
+ );
+
+ public static $duration = array(
+ 's' => 1,
+ 'ms' => 0.001
+ );
+
+ public static $angle = array(
+ 'rad' => 0.1591549430919, // 1/(2*M_PI),
+ 'deg' => 0.002777778, // 1/360,
+ 'grad' => 0.0025, // 1/400,
+ 'turn' => 1
+ );
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Url.php b/library/vendor/lessphp/lib/Less/Tree/Url.php
new file mode 100644
index 0000000..4e7f114
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Url.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Url
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Url extends Less_Tree {
+
+ public $attrs;
+ public $value;
+ public $currentFileInfo;
+ public $isEvald;
+ public $type = 'Url';
+
+ public function __construct( $value, $currentFileInfo = null, $isEvald = null ) {
+ $this->value = $value;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->isEvald = $isEvald;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitObj( $this->value );
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ public function genCSS( $output ) {
+ $output->add( 'url(' );
+ $this->value->genCSS( $output );
+ $output->add( ')' );
+ }
+
+ /**
+ * @param Less_Functions $ctx
+ */
+ public function compile( $ctx ) {
+ $val = $this->value->compile( $ctx );
+
+ if ( !$this->isEvald ) {
+ // Add the base path if the URL is relative
+ if ( Less_Parser::$options['relativeUrls']
+ && $this->currentFileInfo
+ && is_string( $val->value )
+ && Less_Environment::isPathRelative( $val->value )
+ ) {
+ $rootpath = $this->currentFileInfo['uri_root'];
+ if ( !$val->quote ) {
+ $rootpath = preg_replace( '/[\(\)\'"\s]/', '\\$1', $rootpath );
+ }
+ $val->value = $rootpath . $val->value;
+ }
+
+ $val->value = Less_Environment::normalizePath( $val->value );
+ }
+
+ // Add cache buster if enabled
+ if ( Less_Parser::$options['urlArgs'] ) {
+ if ( !preg_match( '/^\s*data:/', $val->value ) ) {
+ $delimiter = strpos( $val->value, '?' ) === false ? '?' : '&';
+ $urlArgs = $delimiter . Less_Parser::$options['urlArgs'];
+ $hash_pos = strpos( $val->value, '#' );
+ if ( $hash_pos !== false ) {
+ $val->value = substr_replace( $val->value, $urlArgs, $hash_pos, 0 );
+ } else {
+ $val->value .= $urlArgs;
+ }
+ }
+ }
+
+ return new Less_Tree_URL( $val, $this->currentFileInfo, true );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Value.php b/library/vendor/lessphp/lib/Less/Tree/Value.php
new file mode 100644
index 0000000..a4c0d85
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Value.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Value
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Value extends Less_Tree {
+
+ public $type = 'Value';
+ public $value;
+
+ public function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ public function accept( $visitor ) {
+ $this->value = $visitor->visitArray( $this->value );
+ }
+
+ public function compile( $env ) {
+ $ret = array();
+ $i = 0;
+ foreach ( $this->value as $i => $v ) {
+ $ret[] = $v->compile( $env );
+ }
+ if ( $i > 0 ) {
+ return new Less_Tree_Value( $ret );
+ }
+ return $ret[0];
+ }
+
+ /**
+ * @see Less_Tree::genCSS
+ */
+ function genCSS( $output ) {
+ $len = count( $this->value );
+ for ( $i = 0; $i < $len; $i++ ) {
+ $this->value[$i]->genCSS( $output );
+ if ( $i + 1 < $len ) {
+ $output->add( Less_Environment::$_outputMap[','] );
+ }
+ }
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Tree/Variable.php b/library/vendor/lessphp/lib/Less/Tree/Variable.php
new file mode 100644
index 0000000..e36d3d4
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Tree/Variable.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Variable
+ *
+ * @package Less
+ * @subpackage tree
+ */
+class Less_Tree_Variable extends Less_Tree {
+
+ public $name;
+ public $index;
+ public $currentFileInfo;
+ public $evaluating = false;
+ public $type = 'Variable';
+
+ /**
+ * @param string $name
+ */
+ public function __construct( $name, $index = null, $currentFileInfo = null ) {
+ $this->name = $name;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ }
+
+ public function compile( $env ) {
+ if ( $this->name[1] === '@' ) {
+ $v = new Less_Tree_Variable( substr( $this->name, 1 ), $this->index + 1, $this->currentFileInfo );
+ $name = '@' . $v->compile( $env )->value;
+ } else {
+ $name = $this->name;
+ }
+
+ if ( $this->evaluating ) {
+ throw new Less_Exception_Compiler( "Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo );
+ }
+
+ $this->evaluating = true;
+
+ foreach ( $env->frames as $frame ) {
+ if ( $v = $frame->variable( $name ) ) {
+ $r = $v->value->compile( $env );
+ $this->evaluating = false;
+ return $r;
+ }
+ }
+
+ throw new Less_Exception_Compiler( "variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Version.php b/library/vendor/lessphp/lib/Less/Version.php
new file mode 100644
index 0000000..d9ad50a
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Version.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Release numbers
+ *
+ * @package Less
+ * @subpackage version
+ */
+class Less_Version {
+
+ const version = '3.1.0'; // The current build number of less.php
+ const less_version = '2.5.3'; // The less.js version that this build should be compatible with
+ const cache_version = '253'; // The parser cache version
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Visitor.php b/library/vendor/lessphp/lib/Less/Visitor.php
new file mode 100644
index 0000000..19e041f
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Visitor.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor {
+
+ protected $methods = array();
+ protected $_visitFnCache = array();
+
+ public function __construct() {
+ $this->_visitFnCache = get_class_methods( get_class( $this ) );
+ $this->_visitFnCache = array_flip( $this->_visitFnCache );
+ }
+
+ public function visitObj( $node ) {
+ $funcName = 'visit'.$node->type;
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+
+ $visitDeeper = true;
+ $this->$funcName( $node, $visitDeeper );
+
+ if ( $visitDeeper ) {
+ $node->accept( $this );
+ }
+
+ $funcName .= "Out";
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+ $this->$funcName( $node );
+ }
+
+ } else {
+ $node->accept( $this );
+ }
+
+ return $node;
+ }
+
+ public function visitArray( $nodes ) {
+ array_map( array( $this,'visitObj' ), $nodes );
+ return $nodes;
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Visitor/extendFinder.php b/library/vendor/lessphp/lib/Less/Visitor/extendFinder.php
new file mode 100644
index 0000000..7d5d3fd
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Visitor/extendFinder.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * Extend Finder Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor_extendFinder extends Less_Visitor {
+
+ public $contexts = array();
+ public $allExtendsStack;
+ public $foundExtends;
+
+ public function __construct() {
+ $this->contexts = array();
+ $this->allExtendsStack = array( array() );
+ parent::__construct();
+ }
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ $root = $this->visitObj( $root );
+ $root->allExtends =& $this->allExtendsStack[0];
+ return $root;
+ }
+
+ public function visitRule( $ruleNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitRuleset( $rulesetNode ) {
+ if ( $rulesetNode->root ) {
+ return;
+ }
+
+ $allSelectorsExtendList = array();
+
+ // get &:extend(.a); rules which apply to all selectors in this ruleset
+ if ( $rulesetNode->rules ) {
+ foreach ( $rulesetNode->rules as $rule ) {
+ if ( $rule instanceof Less_Tree_Extend ) {
+ $allSelectorsExtendList[] = $rule;
+ $rulesetNode->extendOnEveryPath = true;
+ }
+ }
+ }
+
+ // now find every selector and apply the extends that apply to all extends
+ // and the ones which apply to an individual extend
+ foreach ( $rulesetNode->paths as $selectorPath ) {
+ $selector = end( $selectorPath ); // $selectorPath[ count($selectorPath)-1];
+
+ $j = 0;
+ foreach ( $selector->extendList as $extend ) {
+ $this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j );
+ }
+ foreach ( $allSelectorsExtendList as $extend ) {
+ $this->allExtendsStackPush( $rulesetNode, $selectorPath, $extend, $j );
+ }
+ }
+
+ $this->contexts[] = $rulesetNode->selectors;
+ }
+
+ public function allExtendsStackPush( $rulesetNode, $selectorPath, $extend, &$j ) {
+ $this->foundExtends = true;
+ $extend = clone $extend;
+ $extend->findSelfSelectors( $selectorPath );
+ $extend->ruleset = $rulesetNode;
+ if ( $j === 0 ) {
+ $extend->firstExtendOnThisSelectorPath = true;
+ }
+
+ $end_key = count( $this->allExtendsStack ) - 1;
+ $this->allExtendsStack[$end_key][] = $extend;
+ $j++;
+ }
+
+ public function visitRulesetOut( $rulesetNode ) {
+ if ( !is_object( $rulesetNode ) || !$rulesetNode->root ) {
+ array_pop( $this->contexts );
+ }
+ }
+
+ public function visitMedia( $mediaNode ) {
+ $mediaNode->allExtends = array();
+ $this->allExtendsStack[] =& $mediaNode->allExtends;
+ }
+
+ public function visitMediaOut() {
+ array_pop( $this->allExtendsStack );
+ }
+
+ public function visitDirective( $directiveNode ) {
+ $directiveNode->allExtends = array();
+ $this->allExtendsStack[] =& $directiveNode->allExtends;
+ }
+
+ public function visitDirectiveOut() {
+ array_pop( $this->allExtendsStack );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/Visitor/import.php b/library/vendor/lessphp/lib/Less/Visitor/import.php
new file mode 100644
index 0000000..7af96eb
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Visitor/import.php
@@ -0,0 +1,137 @@
+<?php
+
+/*
+class Less_Visitor_import extends Less_VisitorReplacing{
+
+ public $_visitor;
+ public $_importer;
+ public $importCount;
+
+ function __construct( $evalEnv ){
+ $this->env = $evalEnv;
+ $this->importCount = 0;
+ parent::__construct();
+ }
+
+
+ function run( $root ){
+ $root = $this->visitObj($root);
+ $this->isFinished = true;
+
+ //if( $this->importCount === 0) {
+ // $this->_finish();
+ //}
+ }
+
+ function visitImport($importNode, &$visitDeeper ){
+ $importVisitor = $this;
+ $inlineCSS = $importNode->options['inline'];
+
+ if( !$importNode->css || $inlineCSS ){
+ $evaldImportNode = $importNode->compileForImport($this->env);
+
+ if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){
+ $importNode = $evaldImportNode;
+ $this->importCount++;
+ $env = clone $this->env;
+
+ if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){
+ $env->importMultiple = true;
+ }
+
+ //get path & uri
+ $path_and_uri = null;
+ if( is_callable(Less_Parser::$options['import_callback']) ){
+ $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode);
+ }
+
+ if( !$path_and_uri ){
+ $path_and_uri = $importNode->PathAndUri();
+ }
+
+ if( $path_and_uri ){
+ list($full_path, $uri) = $path_and_uri;
+ }else{
+ $full_path = $uri = $importNode->getPath();
+ }
+
+
+ //import once
+ if( $importNode->skip( $full_path, $env) ){
+ return array();
+ }
+
+ if( $importNode->options['inline'] ){
+ //todo needs to reference css file not import
+ //$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true );
+
+ Less_Parser::AddParsedFile($full_path);
+ $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true );
+
+ if( $importNode->features ){
+ return new Less_Tree_Media( array($contents), $importNode->features->value );
+ }
+
+ return array( $contents );
+ }
+
+
+ // css ?
+ if( $importNode->css ){
+ $features = ( $importNode->features ? $importNode->features->compile($env) : null );
+ return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index);
+ }
+
+ return $importNode->ParseImport( $full_path, $uri, $env );
+ }
+
+ }
+
+ $visitDeeper = false;
+ return $importNode;
+ }
+
+
+ function visitRule( $ruleNode, &$visitDeeper ){
+ $visitDeeper = false;
+ return $ruleNode;
+ }
+
+ function visitDirective($directiveNode, $visitArgs){
+ array_unshift($this->env->frames,$directiveNode);
+ return $directiveNode;
+ }
+
+ function visitDirectiveOut($directiveNode) {
+ array_shift($this->env->frames);
+ }
+
+ function visitMixinDefinition($mixinDefinitionNode, $visitArgs) {
+ array_unshift($this->env->frames,$mixinDefinitionNode);
+ return $mixinDefinitionNode;
+ }
+
+ function visitMixinDefinitionOut($mixinDefinitionNode) {
+ array_shift($this->env->frames);
+ }
+
+ function visitRuleset($rulesetNode, $visitArgs) {
+ array_unshift($this->env->frames,$rulesetNode);
+ return $rulesetNode;
+ }
+
+ function visitRulesetOut($rulesetNode) {
+ array_shift($this->env->frames);
+ }
+
+ function visitMedia($mediaNode, $visitArgs) {
+ array_unshift($this->env->frames, $mediaNode->ruleset);
+ return $mediaNode;
+ }
+
+ function visitMediaOut($mediaNode) {
+ array_shift($this->env->frames);
+ }
+
+}
+*/
diff --git a/library/vendor/lessphp/lib/Less/Visitor/joinSelector.php b/library/vendor/lessphp/lib/Less/Visitor/joinSelector.php
new file mode 100644
index 0000000..bb08ece
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Visitor/joinSelector.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Join Selector Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor_joinSelector extends Less_Visitor {
+
+ public $contexts = array( array() );
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ return $this->visitObj( $root );
+ }
+
+ public function visitRule( $ruleNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ public function visitRuleset( $rulesetNode ) {
+ $paths = array();
+
+ if ( !$rulesetNode->root ) {
+ $selectors = array();
+
+ if ( $rulesetNode->selectors && $rulesetNode->selectors ) {
+ foreach ( $rulesetNode->selectors as $selector ) {
+ if ( $selector->getIsOutput() ) {
+ $selectors[] = $selector;
+ }
+ }
+ }
+
+ if ( !$selectors ) {
+ $rulesetNode->selectors = null;
+ $rulesetNode->rules = null;
+ } else {
+ $context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1];
+ $paths = $rulesetNode->joinSelectors( $context, $selectors );
+ }
+
+ $rulesetNode->paths = $paths;
+ }
+
+ $this->contexts[] = $paths; // different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths
+ }
+
+ public function visitRulesetOut() {
+ array_pop( $this->contexts );
+ }
+
+ public function visitMedia( $mediaNode ) {
+ $context = end( $this->contexts ); // $context = $this->contexts[ count($this->contexts) - 1];
+
+ if ( !count( $context ) || ( is_object( $context[0] ) && $context[0]->multiMedia ) ) {
+ $mediaNode->rules[0]->root = true;
+ }
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Visitor/processExtends.php b/library/vendor/lessphp/lib/Less/Visitor/processExtends.php
new file mode 100644
index 0000000..b626a48
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Visitor/processExtends.php
@@ -0,0 +1,441 @@
+<?php
+
+/**
+ * Process Extends Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor_processExtends extends Less_Visitor {
+
+ public $allExtendsStack;
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ $extendFinder = new Less_Visitor_extendFinder();
+ $extendFinder->run( $root );
+ if ( !$extendFinder->foundExtends ) {
+ return $root;
+ }
+
+ $root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends );
+
+ $this->allExtendsStack = array();
+ $this->allExtendsStack[] = &$root->allExtends;
+
+ return $this->visitObj( $root );
+ }
+
+ private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0 ) {
+ //
+ // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting
+ // the selector we would do normally, but we are also adding an extend with the same target selector
+ // this means this new extend can then go and alter other extends
+ //
+ // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors
+ // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if
+ // we look at each selector at a time, as is done in visitRuleset
+
+ $extendsToAdd = array();
+
+ // loop through comparing every extend with every target extend.
+ // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place
+ // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one
+ // and the second is the target.
+ // the separation into two lists allows us to process a subset of chains with a bigger set, as is the
+ // case when processing media queries
+ for ( $extendIndex = 0, $extendsList_len = count( $extendsList ); $extendIndex < $extendsList_len; $extendIndex++ ) {
+ for ( $targetExtendIndex = 0; $targetExtendIndex < count( $extendsListTarget ); $targetExtendIndex++ ) {
+
+ $extend = $extendsList[$extendIndex];
+ $targetExtend = $extendsListTarget[$targetExtendIndex];
+
+ // Optimisation: Explicit reference, <https://github.com/wikimedia/less.php/pull/14>
+ if ( \array_key_exists( $targetExtend->object_id, $extend->parent_ids ) ) {
+ // ignore circular references
+ continue;
+ }
+
+ // find a match in the target extends self selector (the bit before :extend)
+ $selectorPath = array( $targetExtend->selfSelectors[0] );
+ $matches = $this->findMatch( $extend, $selectorPath );
+
+ if ( $matches ) {
+
+ // we found a match, so for each self selector..
+ foreach ( $extend->selfSelectors as $selfSelector ) {
+
+ // process the extend as usual
+ $newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector );
+
+ // but now we create a new extend from it
+ $newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0 );
+ $newExtend->selfSelectors = $newSelector;
+
+ // add the extend onto the list of extends for that selector
+ end( $newSelector )->extendList = array( $newExtend );
+ // $newSelector[ count($newSelector)-1]->extendList = array($newExtend);
+
+ // record that we need to add it.
+ $extendsToAdd[] = $newExtend;
+ $newExtend->ruleset = $targetExtend->ruleset;
+
+ // remember its parents for circular references
+ $newExtend->parent_ids = array_merge( $newExtend->parent_ids, $targetExtend->parent_ids, $extend->parent_ids );
+
+ // only process the selector once.. if we have :extend(.a,.b) then multiple
+ // extends will look at the same selector path, so when extending
+ // we know that any others will be duplicates in terms of what is added to the css
+ if ( $targetExtend->firstExtendOnThisSelectorPath ) {
+ $newExtend->firstExtendOnThisSelectorPath = true;
+ $targetExtend->ruleset->paths[] = $newSelector;
+ }
+ }
+ }
+ }
+ }
+
+ if ( $extendsToAdd ) {
+ // try to detect circular references to stop a stack overflow.
+ // may no longer be needed. $this->extendChainCount++;
+ if ( $iterationCount > 100 ) {
+
+ try{
+ $selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS();
+ $selectorTwo = $extendsToAdd[0]->selector->toCSS();
+ }catch ( Exception $e ) {
+ $selectorOne = "{unable to calculate}";
+ $selectorTwo = "{unable to calculate}";
+ }
+
+ throw new Less_Exception_Parser( "extend circular reference detected. One of the circular extends is currently:" . $selectorOne . ":extend(" . $selectorTwo . ")" );
+ }
+
+ // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e...
+ $extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount + 1 );
+ }
+
+ return array_merge( $extendsList, $extendsToAdd );
+ }
+
+ protected function visitRule( $ruleNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ protected function visitSelector( $selectorNode, &$visitDeeper ) {
+ $visitDeeper = false;
+ }
+
+ protected function visitRuleset( $rulesetNode ) {
+ if ( $rulesetNode->root ) {
+ return;
+ }
+
+ $allExtends = end( $this->allExtendsStack );
+ $paths_len = count( $rulesetNode->paths );
+
+ // look at each selector path in the ruleset, find any extend matches and then copy, find and replace
+ foreach ( $allExtends as $allExtend ) {
+ for ( $pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ) {
+
+ // extending extends happens initially, before the main pass
+ if ( isset( $rulesetNode->extendOnEveryPath ) && $rulesetNode->extendOnEveryPath ) {
+ continue;
+ }
+
+ $selectorPath = $rulesetNode->paths[$pathIndex];
+
+ if ( end( $selectorPath )->extendList ) {
+ continue;
+ }
+
+ $this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath );
+
+ }
+ }
+ }
+
+ private function ExtendMatch( $rulesetNode, $extend, $selectorPath ) {
+ $matches = $this->findMatch( $extend, $selectorPath );
+
+ if ( $matches ) {
+ foreach ( $extend->selfSelectors as $selfSelector ) {
+ $rulesetNode->paths[] = $this->extendSelector( $matches, $selectorPath, $selfSelector );
+ }
+ }
+ }
+
+ private function findMatch( $extend, $haystackSelectorPath ) {
+ if ( !$this->HasMatches( $extend, $haystackSelectorPath ) ) {
+ return false;
+ }
+
+ //
+ // look through the haystack selector path to try and find the needle - extend.selector
+ // returns an array of selector matches that can then be replaced
+ //
+ $needleElements = $extend->selector->elements;
+ $potentialMatches = array();
+ $potentialMatches_len = 0;
+ $potentialMatch = null;
+ $matches = array();
+
+ // loop through the haystack elements
+ $haystack_path_len = count( $haystackSelectorPath );
+ for ( $haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ) {
+ $hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex];
+
+ $haystack_elements_len = count( $hackstackSelector->elements );
+ for ( $hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ) {
+
+ $haystackElement = $hackstackSelector->elements[$hackstackElementIndex];
+
+ // if we allow elements before our match we can add a potential match every time. otherwise only at the first element.
+ if ( $extend->allowBefore || ( $haystackSelectorIndex === 0 && $hackstackElementIndex === 0 ) ) {
+ $potentialMatches[] = array( 'pathIndex' => $haystackSelectorIndex, 'index' => $hackstackElementIndex, 'matched' => 0, 'initialCombinator' => $haystackElement->combinator );
+ $potentialMatches_len++;
+ }
+
+ for ( $i = 0; $i < $potentialMatches_len; $i++ ) {
+
+ $potentialMatch = &$potentialMatches[$i];
+ $potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex );
+
+ // if we are still valid and have finished, test whether we have elements after and whether these are allowed
+ if ( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ) {
+ $potentialMatch['finished'] = true;
+
+ if ( !$extend->allowAfter && ( $hackstackElementIndex + 1 < $haystack_elements_len || $haystackSelectorIndex + 1 < $haystack_path_len ) ) {
+ $potentialMatch = null;
+ }
+ }
+
+ // if null we remove, if not, we are still valid, so either push as a valid match or continue
+ if ( $potentialMatch ) {
+ if ( $potentialMatch['finished'] ) {
+ $potentialMatch['length'] = $extend->selector->elements_len;
+ $potentialMatch['endPathIndex'] = $haystackSelectorIndex;
+ $potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match
+ $potentialMatches = array(); // we don't allow matches to overlap, so start matching again
+ $potentialMatches_len = 0;
+ $matches[] = $potentialMatch;
+ }
+ continue;
+ }
+
+ array_splice( $potentialMatches, $i, 1 );
+ $potentialMatches_len--;
+ $i--;
+ }
+ }
+ }
+
+ return $matches;
+ }
+
+ // Before going through all the nested loops, lets check to see if a match is possible
+ // Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s
+ private function HasMatches( $extend, $haystackSelectorPath ) {
+ if ( !$extend->selector->cacheable ) {
+ return true;
+ }
+
+ $first_el = $extend->selector->_oelements[0];
+
+ foreach ( $haystackSelectorPath as $hackstackSelector ) {
+ if ( !$hackstackSelector->cacheable ) {
+ return true;
+ }
+
+ // Optimisation: Explicit reference, <https://github.com/wikimedia/less.php/pull/14>
+ if ( \array_key_exists( $first_el, $hackstackSelector->_oelements_assoc ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param integer $hackstackElementIndex
+ */
+ private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ) {
+ if ( $potentialMatch['matched'] > 0 ) {
+
+ // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't
+ // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out
+ // what the resulting combinator will be
+ $targetCombinator = $haystackElement->combinator;
+ if ( $targetCombinator === '' && $hackstackElementIndex === 0 ) {
+ $targetCombinator = ' ';
+ }
+
+ if ( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ) {
+ return null;
+ }
+ }
+
+ // if we don't match, null our match to indicate failure
+ if ( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value ) ) {
+ return null;
+ }
+
+ $potentialMatch['finished'] = false;
+ $potentialMatch['matched']++;
+
+ return $potentialMatch;
+ }
+
+ private function isElementValuesEqual( $elementValue1, $elementValue2 ) {
+ if ( $elementValue1 === $elementValue2 ) {
+ return true;
+ }
+
+ if ( is_string( $elementValue1 ) || is_string( $elementValue2 ) ) {
+ return false;
+ }
+
+ if ( $elementValue1 instanceof Less_Tree_Attribute ) {
+ return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 );
+ }
+
+ $elementValue1 = $elementValue1->value;
+ if ( $elementValue1 instanceof Less_Tree_Selector ) {
+ return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 );
+ }
+
+ return false;
+ }
+
+ /**
+ * @param Less_Tree_Selector $elementValue1
+ */
+ private function isSelectorValuesEqual( $elementValue1, $elementValue2 ) {
+ $elementValue2 = $elementValue2->value;
+ if ( !( $elementValue2 instanceof Less_Tree_Selector ) || $elementValue1->elements_len !== $elementValue2->elements_len ) {
+ return false;
+ }
+
+ for ( $i = 0; $i < $elementValue1->elements_len; $i++ ) {
+
+ if ( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ) {
+ if ( $i !== 0 || ( $elementValue1->elements[$i]->combinator || ' ' ) !== ( $elementValue2->elements[$i]->combinator || ' ' ) ) {
+ return false;
+ }
+ }
+
+ if ( !$this->isElementValuesEqual( $elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param Less_Tree_Attribute $elementValue1
+ */
+ private function isAttributeValuesEqual( $elementValue1, $elementValue2 ) {
+ if ( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ) {
+ return false;
+ }
+
+ if ( !$elementValue1->value || !$elementValue2->value ) {
+ if ( $elementValue1->value || $elementValue2->value ) {
+ return false;
+ }
+ return true;
+ }
+
+ $elementValue1 = ( $elementValue1->value->value ?: $elementValue1->value );
+ $elementValue2 = ( $elementValue2->value->value ?: $elementValue2->value );
+
+ return $elementValue1 === $elementValue2;
+ }
+
+ private function extendSelector( $matches, $selectorPath, $replacementSelector ) {
+ // for a set of matches, replace each match with the replacement selector
+
+ $currentSelectorPathIndex = 0;
+ $currentSelectorPathElementIndex = 0;
+ $path = array();
+ $selectorPath_len = count( $selectorPath );
+
+ for ( $matchIndex = 0, $matches_len = count( $matches ); $matchIndex < $matches_len; $matchIndex++ ) {
+
+ $match = $matches[$matchIndex];
+ $selector = $selectorPath[ $match['pathIndex'] ];
+
+ $firstElement = new Less_Tree_Element(
+ $match['initialCombinator'],
+ $replacementSelector->elements[0]->value,
+ $replacementSelector->elements[0]->index,
+ $replacementSelector->elements[0]->currentFileInfo
+ );
+
+ if ( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ) {
+ $last_path = end( $path );
+ $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
+ $currentSelectorPathElementIndex = 0;
+ $currentSelectorPathIndex++;
+ }
+
+ $newElements = array_merge(
+ array_slice( $selector->elements, $currentSelectorPathElementIndex, ( $match['index'] - $currentSelectorPathElementIndex ) ), // last parameter of array_slice is different than the last parameter of javascript's slice
+ array( $firstElement ),
+ array_slice( $replacementSelector->elements, 1 )
+ );
+
+ if ( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ) {
+ $last_key = count( $path ) - 1;
+ $path[$last_key]->elements = array_merge( $path[$last_key]->elements, $newElements );
+ } else {
+ $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] ) );
+ $path[] = new Less_Tree_Selector( $newElements );
+ }
+
+ $currentSelectorPathIndex = $match['endPathIndex'];
+ $currentSelectorPathElementIndex = $match['endPathElementIndex'];
+ if ( $currentSelectorPathElementIndex >= count( $selectorPath[$currentSelectorPathIndex]->elements ) ) {
+ $currentSelectorPathElementIndex = 0;
+ $currentSelectorPathIndex++;
+ }
+ }
+
+ if ( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ) {
+ $last_path = end( $path );
+ $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex ) );
+ $currentSelectorPathIndex++;
+ }
+
+ $slice_len = $selectorPath_len - $currentSelectorPathIndex;
+ $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $slice_len ) );
+
+ return $path;
+ }
+
+ protected function visitMedia( $mediaNode ) {
+ $newAllExtends = array_merge( $mediaNode->allExtends, end( $this->allExtendsStack ) );
+ $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $mediaNode->allExtends );
+ }
+
+ protected function visitMediaOut() {
+ array_pop( $this->allExtendsStack );
+ }
+
+ protected function visitDirective( $directiveNode ) {
+ $newAllExtends = array_merge( $directiveNode->allExtends, end( $this->allExtendsStack ) );
+ $this->allExtendsStack[] = $this->doExtendChaining( $newAllExtends, $directiveNode->allExtends );
+ }
+
+ protected function visitDirectiveOut() {
+ array_pop( $this->allExtendsStack );
+ }
+
+}
diff --git a/library/vendor/lessphp/lib/Less/Visitor/toCSS.php b/library/vendor/lessphp/lib/Less/Visitor/toCSS.php
new file mode 100644
index 0000000..e90f211
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/Visitor/toCSS.php
@@ -0,0 +1,280 @@
+<?php
+
+/**
+ * toCSS Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_Visitor_toCSS extends Less_VisitorReplacing {
+
+ private $charset;
+
+ public function __construct() {
+ parent::__construct();
+ }
+
+ /**
+ * @param Less_Tree_Ruleset $root
+ */
+ public function run( $root ) {
+ return $this->visitObj( $root );
+ }
+
+ public function visitRule( $ruleNode ) {
+ if ( $ruleNode->variable ) {
+ return array();
+ }
+ return $ruleNode;
+ }
+
+ public function visitMixinDefinition( $mixinNode ) {
+ // mixin definitions do not get eval'd - this means they keep state
+ // so we have to clear that state here so it isn't used if toCSS is called twice
+ $mixinNode->frames = array();
+ return array();
+ }
+
+ public function visitExtend() {
+ return array();
+ }
+
+ public function visitComment( $commentNode ) {
+ if ( $commentNode->isSilent() ) {
+ return array();
+ }
+ return $commentNode;
+ }
+
+ public function visitMedia( $mediaNode, &$visitDeeper ) {
+ $mediaNode->accept( $this );
+ $visitDeeper = false;
+
+ if ( !$mediaNode->rules ) {
+ return array();
+ }
+ return $mediaNode;
+ }
+
+ public function visitDirective( $directiveNode ) {
+ if ( isset( $directiveNode->currentFileInfo['reference'] ) && ( !property_exists( $directiveNode, 'isReferenced' ) || !$directiveNode->isReferenced ) ) {
+ return array();
+ }
+ if ( $directiveNode->name === '@charset' ) {
+ // Only output the debug info together with subsequent @charset definitions
+ // a comment (or @media statement) before the actual @charset directive would
+ // be considered illegal css as it has to be on the first line
+ if ( isset( $this->charset ) && $this->charset ) {
+
+ // if( $directiveNode->debugInfo ){
+ // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
+ // $comment->debugInfo = $directiveNode->debugInfo;
+ // return $this->visit($comment);
+ //}
+
+ return array();
+ }
+ $this->charset = true;
+ }
+ return $directiveNode;
+ }
+
+ public function checkPropertiesInRoot( $rulesetNode ) {
+ if ( !$rulesetNode->firstRoot ) {
+ return;
+ }
+
+ foreach ( $rulesetNode->rules as $ruleNode ) {
+ if ( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ) {
+ $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.( $ruleNode->currentFileInfo ? ( ' Filename: '.$ruleNode->currentFileInfo['filename'] ) : null );
+ throw new Less_Exception_Compiler( $msg );
+ }
+ }
+ }
+
+ public function visitRuleset( $rulesetNode, &$visitDeeper ) {
+ $visitDeeper = false;
+
+ $this->checkPropertiesInRoot( $rulesetNode );
+
+ if ( $rulesetNode->root ) {
+ return $this->visitRulesetRoot( $rulesetNode );
+ }
+
+ $rulesets = array();
+ $rulesetNode->paths = $this->visitRulesetPaths( $rulesetNode );
+
+ // Compile rules and rulesets
+ $nodeRuleCnt = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0;
+ for ( $i = 0; $i < $nodeRuleCnt; ) {
+ $rule = $rulesetNode->rules[$i];
+
+ if ( property_exists( $rule, 'rules' ) ) {
+ // visit because we are moving them out from being a child
+ $rulesets[] = $this->visitObj( $rule );
+ array_splice( $rulesetNode->rules, $i, 1 );
+ $nodeRuleCnt--;
+ continue;
+ }
+ $i++;
+ }
+
+ // accept the visitor to remove rules and refactor itself
+ // then we can decide now whether we want it or not
+ if ( $nodeRuleCnt > 0 ) {
+ $rulesetNode->accept( $this );
+
+ if ( $rulesetNode->rules ) {
+
+ if ( count( $rulesetNode->rules ) > 1 ) {
+ $this->_mergeRules( $rulesetNode->rules );
+ $this->_removeDuplicateRules( $rulesetNode->rules );
+ }
+
+ // now decide whether we keep the ruleset
+ if ( $rulesetNode->paths ) {
+ // array_unshift($rulesets, $rulesetNode);
+ array_splice( $rulesets, 0, 0, array( $rulesetNode ) );
+ }
+ }
+
+ }
+
+ if ( count( $rulesets ) === 1 ) {
+ return $rulesets[0];
+ }
+ return $rulesets;
+ }
+
+ /**
+ * Helper function for visitiRuleset
+ *
+ * return array|Less_Tree_Ruleset
+ */
+ private function visitRulesetRoot( $rulesetNode ) {
+ $rulesetNode->accept( $this );
+ if ( $rulesetNode->firstRoot || $rulesetNode->rules ) {
+ return $rulesetNode;
+ }
+ return array();
+ }
+
+ /**
+ * Helper function for visitRuleset()
+ *
+ * @return array
+ */
+ private function visitRulesetPaths( $rulesetNode ) {
+ $paths = array();
+ foreach ( $rulesetNode->paths as $p ) {
+ if ( $p[0]->elements[0]->combinator === ' ' ) {
+ $p[0]->elements[0]->combinator = '';
+ }
+
+ foreach ( $p as $pi ) {
+ if ( $pi->getIsReferenced() && $pi->getIsOutput() ) {
+ $paths[] = $p;
+ break;
+ }
+ }
+ }
+
+ return $paths;
+ }
+
+ protected function _removeDuplicateRules( &$rules ) {
+ // remove duplicates
+ $ruleCache = array();
+ for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) {
+ $rule = $rules[$i];
+ if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ) {
+
+ if ( !isset( $ruleCache[$rule->name] ) ) {
+ $ruleCache[$rule->name] = $rule;
+ } else {
+ $ruleList =& $ruleCache[$rule->name];
+
+ if ( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ) {
+ $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
+ }
+
+ $ruleCSS = $rule->toCSS();
+ if ( array_search( $ruleCSS, $ruleList ) !== false ) {
+ array_splice( $rules, $i, 1 );
+ } else {
+ $ruleList[] = $ruleCSS;
+ }
+ }
+ }
+ }
+ }
+
+ protected function _mergeRules( &$rules ) {
+ $groups = array();
+
+ // obj($rules);
+
+ $rules_len = count( $rules );
+ for ( $i = 0; $i < $rules_len; $i++ ) {
+ $rule = $rules[$i];
+
+ if ( ( $rule instanceof Less_Tree_Rule ) && $rule->merge ) {
+
+ $key = $rule->name;
+ if ( $rule->important ) {
+ $key .= ',!';
+ }
+
+ if ( !isset( $groups[$key] ) ) {
+ $groups[$key] = array();
+ } else {
+ array_splice( $rules, $i--, 1 );
+ $rules_len--;
+ }
+
+ $groups[$key][] = $rule;
+ }
+ }
+
+ foreach ( $groups as $parts ) {
+
+ if ( count( $parts ) > 1 ) {
+ $rule = $parts[0];
+ $spacedGroups = array();
+ $lastSpacedGroup = array();
+ $parts_mapped = array();
+ foreach ( $parts as $p ) {
+ if ( $p->merge === '+' ) {
+ if ( $lastSpacedGroup ) {
+ $spacedGroups[] = self::toExpression( $lastSpacedGroup );
+ }
+ $lastSpacedGroup = array();
+ }
+ $lastSpacedGroup[] = $p;
+ }
+
+ $spacedGroups[] = self::toExpression( $lastSpacedGroup );
+ $rule->value = self::toValue( $spacedGroups );
+ }
+ }
+
+ }
+
+ public static function toExpression( $values ) {
+ $mapped = array();
+ foreach ( $values as $p ) {
+ $mapped[] = $p->value;
+ }
+ return new Less_Tree_Expression( $mapped );
+ }
+
+ public static function toValue( $values ) {
+ // return new Less_Tree_Value($values); ??
+
+ $mapped = array();
+ foreach ( $values as $p ) {
+ $mapped[] = $p;
+ }
+ return new Less_Tree_Value( $mapped );
+ }
+}
diff --git a/library/vendor/lessphp/lib/Less/VisitorReplacing.php b/library/vendor/lessphp/lib/Less/VisitorReplacing.php
new file mode 100644
index 0000000..b773d46
--- /dev/null
+++ b/library/vendor/lessphp/lib/Less/VisitorReplacing.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Replacing Visitor
+ *
+ * @package Less
+ * @subpackage visitor
+ */
+class Less_VisitorReplacing extends Less_Visitor {
+
+ public function visitObj( $node ) {
+ $funcName = 'visit'.$node->type;
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+
+ $visitDeeper = true;
+ $node = $this->$funcName( $node, $visitDeeper );
+
+ if ( $node ) {
+ if ( $visitDeeper && is_object( $node ) ) {
+ $node->accept( $this );
+ }
+
+ $funcName .= "Out";
+ if ( isset( $this->_visitFnCache[$funcName] ) ) {
+ $this->$funcName( $node );
+ }
+ }
+
+ } else {
+ $node->accept( $this );
+ }
+
+ return $node;
+ }
+
+ public function visitArray( $nodes ) {
+ $newNodes = array();
+ foreach ( $nodes as $node ) {
+ $evald = $this->visitObj( $node );
+ if ( $evald ) {
+ if ( is_array( $evald ) ) {
+ self::flatten( $evald, $newNodes );
+ } else {
+ $newNodes[] = $evald;
+ }
+ }
+ }
+ return $newNodes;
+ }
+
+ public function flatten( $arr, &$out ) {
+ foreach ( $arr as $item ) {
+ if ( !is_array( $item ) ) {
+ $out[] = $item;
+ continue;
+ }
+
+ foreach ( $item as $nestedItem ) {
+ if ( is_array( $nestedItem ) ) {
+ self::flatten( $nestedItem, $out );
+ } else {
+ $out[] = $nestedItem;
+ }
+ }
+ }
+
+ return $out;
+ }
+
+}
diff --git a/modules/doc/application/controllers/IcingawebController.php b/modules/doc/application/controllers/IcingawebController.php
new file mode 100644
index 0000000..e841c41
--- /dev/null
+++ b/modules/doc/application/controllers/IcingawebController.php
@@ -0,0 +1,62 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Controllers;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Doc\DocController;
+
+class IcingawebController extends DocController
+{
+ /**
+ * Get the path to Icinga Web 2's documentation
+ *
+ * @return string
+ *
+ * @throws \Icinga\Exception\Http\HttpNotFoundException If Icinga Web 2's documentation is not available
+ */
+ protected function getPath()
+ {
+ $path = Icinga::app()->getBaseDir('doc');
+ if (is_dir($path)) {
+ return $path;
+ }
+ if (($path = $this->Config()->get('documentation', 'icingaweb2')) !== null) {
+ if (is_dir($path)) {
+ return $path;
+ }
+ }
+ $this->httpNotFound($this->translate('Documentation for Icinga Web 2 is not available'));
+ }
+
+ /**
+ * View the toc of Icinga Web 2's documentation
+ */
+ public function tocAction()
+ {
+ $this->renderToc($this->getPath(), 'Icinga Web 2', 'doc/icingaweb/chapter');
+ }
+
+ /**
+ * View a chapter of Icinga Web 2's documentation
+ *
+ * @throws \Icinga\Exception\MissingParameterException If the required parameter 'chapter' is missing
+ */
+ public function chapterAction()
+ {
+ $chapter = $this->params->getRequired('chapter');
+ $this->renderChapter(
+ $this->getPath(),
+ $chapter,
+ 'doc/icingaweb/chapter'
+ );
+ }
+
+ /**
+ * View Icinga Web 2's documentation as PDF
+ */
+ public function pdfAction()
+ {
+ $this->renderPdf($this->getPath(), 'Icinga Web 2', 'doc/icingaweb/chapter');
+ }
+}
diff --git a/modules/doc/application/controllers/IndexController.php b/modules/doc/application/controllers/IndexController.php
new file mode 100644
index 0000000..3ff5aa1
--- /dev/null
+++ b/modules/doc/application/controllers/IndexController.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Controllers;
+
+use Icinga\Module\Doc\DocController;
+use Icinga\Web\Url;
+
+/**
+ * Documentation module index
+ */
+class IndexController extends DocController
+{
+ /**
+ * Documentation module landing page
+ *
+ * Lists documentation links
+ */
+ public function indexAction()
+ {
+ $this->getTabs()->add('documentation', array(
+ 'active' => true,
+ 'title' => $this->translate('Documentation', 'Tab title'),
+ 'url' => Url::fromRequest()
+ ));
+ }
+}
diff --git a/modules/doc/application/controllers/ModuleController.php b/modules/doc/application/controllers/ModuleController.php
new file mode 100644
index 0000000..47dfb1c
--- /dev/null
+++ b/modules/doc/application/controllers/ModuleController.php
@@ -0,0 +1,206 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Controllers;
+
+use finfo;
+use SplFileInfo;
+use Icinga\Application\Icinga;
+use Icinga\Module\Doc\DocController;
+use Icinga\Module\Doc\Exception\DocException;
+use Icinga\Web\Url;
+
+class ModuleController extends DocController
+{
+ /**
+ * Get the path to a module documentation
+ *
+ * @param string $module The name of the module
+ * @param string $default The default path
+ * @param bool $suppressErrors Whether to not throw an exception if the module documentation is not available
+ *
+ * @return string|null Path to the documentation or null if the module documentation is not available
+ * and errors are suppressed
+ *
+ * @throws \Icinga\Exception\Http\HttpNotFoundException If the module documentation is not available and errors
+ * are not suppressed
+ */
+ protected function getPath($module, $default, $suppressErrors = false)
+ {
+ if (is_dir($default)) {
+ return $default;
+ }
+ if (($path = $this->Config()->get('documentation', 'modules')) !== null) {
+ $path = str_replace('{module}', $module, $path);
+ if (is_dir($path)) {
+ return $path;
+ }
+ }
+ if ($suppressErrors) {
+ return null;
+ }
+ $this->httpNotFound($this->translate('Documentation for module \'%s\' is not available'), $module);
+ }
+
+ /**
+ * List modules which are enabled and having the 'doc' directory
+ */
+ public function indexAction()
+ {
+ $moduleManager = Icinga::app()->getModuleManager();
+ $modules = array();
+ foreach ($moduleManager->listInstalledModules() as $module) {
+ $path = $this->getPath($module, $moduleManager->getModuleDir($module, '/doc'), true);
+ if ($path !== null) {
+ $modules[] = $moduleManager->getModule($module, false);
+ }
+ }
+ $this->view->modules = $modules;
+ $this->getTabs()->add('module-documentation', array(
+ 'active' => true,
+ 'title' => $this->translate('Module Documentation', 'Tab title'),
+ 'url' => Url::fromRequest()
+ ));
+ }
+
+ /**
+ * Assert that the given module is installed
+ *
+ * @param string $moduleName
+ *
+ * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
+ */
+ protected function assertModuleInstalled($moduleName)
+ {
+ $moduleManager = Icinga::app()->getModuleManager();
+ if (! $moduleManager->hasInstalled($moduleName)) {
+ $this->httpNotFound($this->translate('Module \'%s\' is not installed'), $moduleName);
+ }
+ }
+
+ /**
+ * View the toc of a module's documentation
+ *
+ * @throws \Icinga\Exception\MissingParameterException If the required parameter 'moduleName' is empty
+ * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
+ * @see assertModuleInstalled()
+ */
+ public function tocAction()
+ {
+ $module = $this->params->getRequired('moduleName');
+ $this->assertModuleInstalled($module);
+ $moduleManager = Icinga::app()->getModuleManager();
+ $name = $moduleManager->getModule($module, false)->getTitle();
+ try {
+ $this->renderToc(
+ $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
+ $name,
+ 'doc/module/chapter',
+ array('moduleName' => $module)
+ );
+ } catch (DocException $e) {
+ $this->httpNotFound($e->getMessage());
+ }
+ }
+
+ /**
+ * View a chapter of a module's documentation
+ *
+ * @throws \Icinga\Exception\MissingParameterException If one of the required parameters 'moduleName' and
+ * 'chapter' is empty
+ * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
+ * @see assertModuleInstalled()
+ */
+ public function chapterAction()
+ {
+ $module = $this->params->getRequired('moduleName');
+ $this->assertModuleInstalled($module);
+ $chapter = $this->params->getRequired('chapter');
+ $this->view->moduleName = $module;
+ try {
+ $this->renderChapter(
+ $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
+ $chapter,
+ 'doc/module/chapter',
+ 'doc/module/img',
+ array('moduleName' => $module)
+ );
+ } catch (DocException $e) {
+ $this->httpNotFound($e->getMessage());
+ }
+ }
+
+ /**
+ * Deliver images
+ */
+ public function imageAction()
+ {
+ $module = $this->params->getRequired('moduleName');
+ $image = $this->params->getRequired('image');
+ $docPath = $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc'));
+ $imagePath = realpath($docPath . '/' . $image);
+ if ($imagePath === false || substr($imagePath, 0, strlen($docPath)) !== $docPath) {
+ $this->httpNotFound('%s does not exist', $image);
+ }
+
+ $this->_helper->viewRenderer->setNoRender(true);
+ $this->_helper->layout()->disableLayout();
+
+ $imageInfo = new SplFileInfo($imagePath);
+
+ $etag = md5($imageInfo->getMTime() . $imagePath);
+ $lastModified = gmdate('D, d M Y H:i:s T', $imageInfo->getMTime());
+ $match = false;
+
+ if (isset($_SERER['HTTP_IF_NONE_MATCH'])) {
+ $ifNoneMatch = explode(', ', stripslashes($_SERVER['HTTP_IF_NONE_MATCH']));
+ foreach ($ifNoneMatch as $tag) {
+ if ($tag === $etag) {
+ $match = true;
+ break;
+ }
+ }
+ } elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
+ $lastModifiedSince = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+ if ($lastModifiedSince === $lastModified) {
+ $match = true;
+ }
+ }
+
+ $this->getResponse()
+ ->setHeader('ETag', $etag)
+ ->setHeader('Cache-Control', 'no-transform,public,max-age=3600,must-revalidate', true);
+
+ if ($match) {
+ $this->getResponse()->setHttpResponseCode(304);
+ } else {
+ $finfo = new finfo();
+ $this->getResponse()
+ ->setHeader('Content-Type', $finfo->file($imagePath, FILEINFO_MIME_TYPE))
+ ->setHeader('Content-Length', $imageInfo->getSize())
+ ->sendHeaders();
+
+ ob_end_clean();
+ readfile($imagePath);
+ }
+ }
+
+ /**
+ * View a module's documentation as PDF
+ *
+ * @throws \Icinga\Exception\MissingParameterException If the required parameter 'moduleName' is empty
+ * @throws \Icinga\Exception\Http\HttpNotFoundException If the given module is not installed
+ * @see assertModuleInstalled()
+ */
+ public function pdfAction()
+ {
+ $module = $this->params->getRequired('moduleName');
+ $this->assertModuleInstalled($module);
+ $this->renderPdf(
+ $this->getPath($module, Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')),
+ $module,
+ 'doc/module/chapter',
+ array('moduleName' => $module)
+ );
+ }
+}
diff --git a/modules/doc/application/controllers/SearchController.php b/modules/doc/application/controllers/SearchController.php
new file mode 100644
index 0000000..6ae2b14
--- /dev/null
+++ b/modules/doc/application/controllers/SearchController.php
@@ -0,0 +1,97 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Controllers;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Doc\DocController;
+use Icinga\Module\Doc\DocParser;
+use Icinga\Module\Doc\Exception\DocException;
+use Icinga\Module\Doc\Renderer\DocSearchRenderer;
+use Icinga\Module\Doc\Search\DocSearch;
+use Icinga\Module\Doc\Search\DocSearchIterator;
+
+class SearchController extends DocController
+{
+ /**
+ * Render search
+ */
+ public function indexAction()
+ {
+ $parser = new DocParser($this->getWebPath());
+ $search = new DocSearchRenderer(
+ new DocSearchIterator(
+ $parser->getDocTree()->getIterator(),
+ new DocSearch($this->params->get('q'))
+ )
+ );
+ $search->setUrl('doc/icingaweb/chapter');
+ if (strlen($this->params->get('q')) < 3) {
+ $this->view->searches = array();
+ return;
+ }
+ $searches = array(
+ 'Icinga Web 2' => $search
+ );
+ foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $module) {
+ if (($path = $this->getModulePath($module)) !== null) {
+ try {
+ $parser = new DocParser($path);
+ $search = new DocSearchRenderer(
+ new DocSearchIterator(
+ $parser->getDocTree()->getIterator(),
+ new DocSearch($this->params->get('q'))
+ )
+ );
+ } catch (DocException $e) {
+ continue;
+ }
+ $search
+ ->setUrl('doc/module/chapter')
+ ->setUrlParams(array('moduleName' => $module));
+ $searches[$module] = $search;
+ }
+ }
+ $this->view->searches = $searches;
+ }
+
+ /**
+ * Get the path to a module's documentation
+ *
+ * @param string $module
+ *
+ * @return string|null
+ */
+ protected function getModulePath($module)
+ {
+ if (is_dir(($path = Icinga::app()->getModuleManager()->getModuleDir($module, '/doc')))) {
+ return $path;
+ }
+ if (($path = $this->Config()->get('documentation', 'modules')) !== null) {
+ $path = str_replace('{module}', $module, $path);
+ if (is_dir($path)) {
+ return $path;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the path to Icinga Web 2's documentation
+ *
+ * @return string
+ */
+ protected function getWebPath()
+ {
+ $path = Icinga::app()->getBaseDir('doc');
+ if (is_dir($path)) {
+ return $path;
+ }
+ if (($path = $this->Config()->get('documentation', 'icingaweb2')) !== null) {
+ if (is_dir($path)) {
+ return $path;
+ }
+ }
+ $this->httpNotFound($this->translate('Documentation for Icinga Web 2 is not available'));
+ }
+}
diff --git a/modules/doc/application/controllers/StyleController.php b/modules/doc/application/controllers/StyleController.php
new file mode 100644
index 0000000..5890367
--- /dev/null
+++ b/modules/doc/application/controllers/StyleController.php
@@ -0,0 +1,41 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Controllers;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Controller;
+use Icinga\Web\Widget;
+
+class StyleController extends Controller
+{
+ public function guideAction()
+ {
+ $this->view->tabs = $this->tabs()->activate('guide');
+ }
+
+ public function fontAction()
+ {
+ $this->view->tabs = $this->tabs()->activate('font');
+ $confFile = Icinga::app()->getApplicationDir('fonts/fontello-ifont/config.json');
+ $this->view->font = json_decode(file_get_contents($confFile));
+ }
+
+ protected function tabs()
+ {
+ return Widget::create('tabs')->add(
+ 'guide',
+ array(
+ 'label' => $this->translate('Style Guide'),
+ 'url' => 'doc/style/guide'
+ )
+ )->add(
+ 'font',
+ array(
+ 'label' => $this->translate('Icons'),
+ 'title' => $this->translate('List all available icons'),
+ 'url' => 'doc/style/font'
+ )
+ );
+ }
+}
diff --git a/modules/doc/application/views/scripts/chapter.phtml b/modules/doc/application/views/scripts/chapter.phtml
new file mode 100644
index 0000000..8cd4f6e
--- /dev/null
+++ b/modules/doc/application/views/scripts/chapter.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= /** @var \Icinga\Web\Widget\Tabs $tabs */ $this->tabs ?>
+</div>
+<div class="content chapter">
+ <?= /** @var \Icinga\Module\Doc\Renderer\DocSectionRenderer $section */ $section ?>
+</div>
diff --git a/modules/doc/application/views/scripts/index/index.phtml b/modules/doc/application/views/scripts/index/index.phtml
new file mode 100644
index 0000000..9bf745a
--- /dev/null
+++ b/modules/doc/application/views/scripts/index/index.phtml
@@ -0,0 +1,19 @@
+<div class="controls">
+ <?= /** @var \Icinga\Web\Widget\Tabs $tabs */ $tabs ?>
+</div>
+<div class="content">
+ <ul>
+ <li><?= $this->qlink(
+ 'Icinga Web 2',
+ 'doc/icingaweb/toc',
+ null,
+ array('title' => $this->translate('Show the documentation\'s table of contents for Icinga Web 2'))
+ ) ?></li>
+ <li><?= $this->qlink(
+ $this->translate('Module documentations'),
+ 'doc/module/',
+ null,
+ array('title' => $this->translate('List all modules for which documentation is available'))
+ ) ?></li>
+ </ul>
+</div>
diff --git a/modules/doc/application/views/scripts/module/index.phtml b/modules/doc/application/views/scripts/module/index.phtml
new file mode 100644
index 0000000..f70d69a
--- /dev/null
+++ b/modules/doc/application/views/scripts/module/index.phtml
@@ -0,0 +1,19 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+
+<div class="content">
+ <ul>
+ <?php foreach ($modules as $module): /** @var \Icinga\Application\Modules\Module $module */ ?>
+ <li><?= $this->qlink(
+ $module->getTitle(),
+ 'doc/module/toc',
+ array('moduleName' => $module->getName()),
+ array('title' => sprintf(
+ $this->translate('Show the documentation\'s table of contents for the %s'),
+ $module->getTitle()
+ ))
+ ) ?></li>
+ <?php endforeach ?>
+ </ul>
+</div>
diff --git a/modules/doc/application/views/scripts/pdf.phtml b/modules/doc/application/views/scripts/pdf.phtml
new file mode 100644
index 0000000..2666efb
--- /dev/null
+++ b/modules/doc/application/views/scripts/pdf.phtml
@@ -0,0 +1,5 @@
+<div class="content">
+ <h1><?= /** @var string $title */ $title ?></h1>
+ <?= /** @var \Icinga\Module\Doc\Renderer\DocTocRenderer $toc */ $toc ?>
+ <?= /** @var \Icinga\Module\Doc\Renderer\DocSectionRenderer $section */ $section ?>
+</div>
diff --git a/modules/doc/application/views/scripts/search/index.phtml b/modules/doc/application/views/scripts/search/index.phtml
new file mode 100644
index 0000000..c613f04
--- /dev/null
+++ b/modules/doc/application/views/scripts/search/index.phtml
@@ -0,0 +1,8 @@
+<div class="content">
+ <?php foreach (/** @var \Icinga\Module\Doc\Renderer\DocSearchRenderer[] $searches */ $searches as $title => $search): ?>
+ <h2><?= $this->escape($title) ?></h2>
+ <?= $search->isEmpty()
+ ? $this->translate('No documentation found matching the filter')
+ : $search ?>
+ <?php endforeach ?>
+</div>
diff --git a/modules/doc/application/views/scripts/style/font.phtml b/modules/doc/application/views/scripts/style/font.phtml
new file mode 100644
index 0000000..c84a983
--- /dev/null
+++ b/modules/doc/application/views/scripts/style/font.phtml
@@ -0,0 +1,15 @@
+<div class="controls">
+<?= $this->tabs ?>
+<h1>Icinga Web 2 Icons</h1>
+</div>
+
+<div class="content">
+<?php foreach ($this->font->glyphs as $icon): ?>
+<!-- TODO: move CSS away //-->
+<div style="width: 33%; font-size: 1.5em; height: 2em; float: left;" class="<?=
+ $this->font->css_prefix_text . $icon->css
+?>">
+<?= $this->escape($icon->css) ?> <span style="font-size: 0.6em">(0x<?= dechex($icon->code) ?>)</span>
+</div>
+<?php endforeach ?>
+</div>
diff --git a/modules/doc/application/views/scripts/style/guide.phtml b/modules/doc/application/views/scripts/style/guide.phtml
new file mode 100644
index 0000000..f2f57d2
--- /dev/null
+++ b/modules/doc/application/views/scripts/style/guide.phtml
@@ -0,0 +1,112 @@
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+
+<div class="content styleguide">
+ <div class="section">
+ <h1>Icinga Web 2 Design Guidelines</h1>
+
+ <ul class="toc">
+ <li><a href="#headings">Headings</a></li>
+ <li><a href="#block-content">Block Content</a></li>
+ <li><a href="#tables">Tables</a></li>
+ <li><a href="#comment-list">Comment List</a></li>
+ <li><a href="#blockquote">Blockquote</a></li>
+ </ul>
+ </div>
+
+ <div class="section">
+ <h2 id="headings">Headings</h2>
+ <h1>Header h1</h1>
+ <h2>Header h2</h2>
+ <h3>Header h3</h3>
+ <h4>Header h4</h4>
+ <h5>Header h5</h5>
+ <h6>Header h6</h6>
+ </div>
+
+ <div class="section">
+ <h2 id="block-content">Block Content</h2>
+ <h3>Paragraph</h3>
+ <p>
+ This is a paragraph. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
+ invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo
+ dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+ A <a href="#">link inside a paragraph</a>.
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et
+ dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
+ Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+ </p>
+ </div>
+
+ <div class="section">
+ <h2 id="tables">Tables</h2>
+ <table class="common-table">
+ <thead>
+ <tr>
+ <th>Table Head - th in thead</th>
+ <td>td in thead<td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th>Tbody - th</th>
+ <td>Tbody - td</td>
+ </tr>
+ <tr>
+ <th>Tbody - th</th>
+ <td>Tbody - td</td>
+ </tr>
+ <tr>
+ <th>Tbody - th</th>
+ <td>Tbody - td</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div class="section">
+ <h2 id="comment-list"><?= $this->translate('Comment List') ?></h2>
+ <dl class="comment-list">
+ <dt>
+ John Doe
+ <span class="comment-time">
+ <?= $this->translate('commented') ?>
+ <span class="relative-time"><?= $this->translate('some time ago') ?></span>
+ </span>
+ <i class="remove-action icon-cancel"></i>
+ </dt>
+ <dd>
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore
+ <br>
+ et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
+ </dd>
+ <dt>
+ Richard Roe
+ <span class="comment-time">
+ <?= $this->translate('commented') ?>
+ <span class="relative-time"><?= $this->translate('some time ago') ?></span>
+ </span>
+ <i class="remove-action icon-cancel"></i>
+ </dt>
+ <dd>
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore
+ <br>
+ et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
+ </dd>
+ </dl>
+ </div>
+
+ <div class="section">
+ <h2 id="blockquote"><?= $this->translate('Blockquote') ?></h2>
+ <blockquote>
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor
+ invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
+ At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
+ no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet,
+ consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore
+ magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
+ Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+ </blockquote>
+ </div>
+</div>
diff --git a/modules/doc/application/views/scripts/toc.phtml b/modules/doc/application/views/scripts/toc.phtml
new file mode 100644
index 0000000..d08830b
--- /dev/null
+++ b/modules/doc/application/views/scripts/toc.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= /** @var \Icinga\Web\Widget\Tabs $tabs */ $tabs ?>
+</div>
+<div class="content">
+ <?= /** @var \Icinga\Module\Doc\Renderer\DocTocRenderer $toc */ $toc ?>
+</div>
diff --git a/modules/doc/configuration.php b/modules/doc/configuration.php
new file mode 100644
index 0000000..8b909ef
--- /dev/null
+++ b/modules/doc/configuration.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/** @var $this \Icinga\Application\Modules\Module */
+
+$section = $this->menuSection(N_('Documentation'), array(
+ 'title' => 'Documentation',
+ 'icon' => 'book',
+ 'url' => 'doc',
+ 'priority' => 700
+));
+
+$section->add('Icinga Web 2', array(
+ 'url' => 'doc/icingaweb/toc',
+));
+$section->add('Module documentations', array(
+ 'url' => 'doc/module',
+));
+$section->add(N_('Developer - Style'), array(
+ 'url' => 'doc/style/guide',
+ 'priority' => 790
+));
+
+$this->provideSearchUrl($this->translate('Doc'), 'doc/search', -10);
diff --git a/modules/doc/doc/01-About.md b/modules/doc/doc/01-About.md
new file mode 100644
index 0000000..02e2cbf
--- /dev/null
+++ b/modules/doc/doc/01-About.md
@@ -0,0 +1,6 @@
+# About the Doc Module <a id="doc-module-about"></a>
+
+Please read the following chapters for more insights on this module:
+
+* [Installation](02-Installation.md#doc-module-installation)
+* [Module Documentation](03-Module-Documentation.md#module-documentation)
diff --git a/modules/doc/doc/02-Installation.md b/modules/doc/doc/02-Installation.md
new file mode 100644
index 0000000..6d93d42
--- /dev/null
+++ b/modules/doc/doc/02-Installation.md
@@ -0,0 +1,15 @@
+# Doc Module Installation <a id="doc-module-installation"></a>
+
+This module is provided with the Icinga Web 2 package and does
+not need any extra installation step.
+
+## Enable the Module <a id="monitoring-module-enable"></a>
+
+Navigate to `Configuration` -> `Modules` -> `doc` and enable
+the module.
+
+You can also enable the module during the setup wizard, or on the CLI:
+
+```
+icingacli module enable doc
+```
diff --git a/modules/doc/doc/03-Module-Documentation.md b/modules/doc/doc/03-Module-Documentation.md
new file mode 100644
index 0000000..5ce4a9a
--- /dev/null
+++ b/modules/doc/doc/03-Module-Documentation.md
@@ -0,0 +1,87 @@
+# Writing Module Documentation <a id="module-documentation"></a>
+
+![Markdown](img/markdown.png)
+
+Icinga Web 2 is capable of viewing your module's documentation, if the documentation is written in
+[Markdown](http://en.wikipedia.org/wiki/Markdown). Please refer to
+[Markdown Syntax Documentation](http://daringfireball.net/projects/markdown/syntax) for Markdown's formatting syntax.
+
+## Where to Put Module Documentation? <a id="module-documentation-location"></a>
+
+By default, your module's Markdown documentation files must be placed in the `doc` directory beneath your module's root
+directory, e.g.:
+
+```
+example-module/doc
+```
+
+## Chapters <a id="module-documentation-chapters"></a>
+
+Each Markdown documentation file represents a chapter of your module's documentation. The first found heading inside
+each file is the chapter's title. The order of chapters is based on the case insensitive "Natural Order" of your files'
+names. <dfn>Natural Order</dfn> means that the file names are ordered in the way which seems natural to humans.
+It is best practice to prefix Markdown documentation file names with numbers to ensure that they appear in the correct
+order, e.g.:
+
+```
+01-About.md
+02-Installation.md
+03-Configuration.md
+```
+
+## Table Of Contents <a id="module-documentation-toc"></a>
+
+The table of contents for your module's documentation is auto-generated based on all found headings inside each
+Markdown documentation file.
+
+## Linking Between Headings <a id="module-documentation-linking"></a>
+
+For linking between headings, place an anchor **after the text** where you want to link to, e.g.:
+
+```
+# Heading <a id="heading"></a> Heading
+```
+
+Please note that anchors have to be unique across all your Markdown documentation files.
+
+Now you can reference the anchor either in the same or **in another** Markdown documentation file, e.g.:
+
+```
+This is a link to [Heading](#heading).
+```
+
+Other tools support linking between headings by giving the filename plus the anchor to link to, e.g.:
+
+```
+This is a link to [About/Heading](01-About.md#heading)
+```
+
+This syntax is also supported in Icinga Web 2.
+
+## Including Images <a id="module-documentation-images"></a>
+
+Images must placed in the `doc` directory beneath your module's root directory, e.g.:
+
+```
+/path/to/icingaweb2/modules/example-module/doc/img/example.png
+```
+
+Note that the `img` sub directory is not mandatory but good for organizing your directory structure.
+
+Module images can be accessed using the following URL:
+
+```
+{baseURL}/doc/module/{moduleName}/image/{image} e.g. icingaweb2/doc/module/example-module/image/img/example.png
+```
+
+Markdown's image syntax is very similar to Markdown's link syntax, but prefixed with an exclamation mark, e.g.:
+
+```
+![Alt text](http://path/to/img.png "Optional Title")
+```
+
+URLs to images inside your Markdown documentation files must be specified without the base URL, e.g.:
+
+```
+![Example](img/example.png)
+```
diff --git a/modules/doc/doc/img/markdown.png b/modules/doc/doc/img/markdown.png
new file mode 100644
index 0000000..93e729b
--- /dev/null
+++ b/modules/doc/doc/img/markdown.png
Binary files differ
diff --git a/modules/doc/library/Doc/DocController.php b/modules/doc/library/Doc/DocController.php
new file mode 100644
index 0000000..0caf3ad
--- /dev/null
+++ b/modules/doc/library/Doc/DocController.php
@@ -0,0 +1,116 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc;
+
+use Icinga\Module\Doc\Renderer\DocSectionRenderer;
+use Icinga\Module\Doc\Renderer\DocTocRenderer;
+use Icinga\Web\Controller;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+
+class DocController extends Controller
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function moduleInit()
+ {
+ // Our UrlParams object does not take parameters from custom routes into account which is why we have to set
+ // them explicitly
+ if ($this->hasParam('chapter')) {
+ $this->params->set('chapter', $this->getParam('chapter'));
+ }
+ if ($this->hasParam('image')) {
+ $this->params->set('image', $this->getParam('image'));
+ }
+ if ($this->hasParam('moduleName')) {
+ $this->params->set('moduleName', $this->getParam('moduleName'));
+ }
+ }
+
+ /**
+ * Render a chapter
+ *
+ * @param string $path Path to the documentation
+ * @param string $chapter ID of the chapter
+ * @param string $url URL to replace links with
+ * @param string $imageUrl URL to images
+ * @param array $urlParams Additional URL parameters
+ */
+ protected function renderChapter($path, $chapter, $url, $imageUrl = null, array $urlParams = array())
+ {
+ $parser = new DocParser($path);
+ $section = new DocSectionRenderer($parser->getDocTree(), DocSectionRenderer::decodeUrlParam($chapter));
+ $this->view->section = $section
+ ->setHighlightSearch($this->params->get('highlight-search'))
+ ->setImageUrl($imageUrl)
+ ->setUrl($url)
+ ->setUrlParams($urlParams);
+ $first = null;
+ foreach ($section as $first) {
+ break;
+ }
+ $title = $first === null ? ucfirst($chapter) : $first->getTitle();
+ $this->view->title = $title;
+ $this->getTabs()
+ ->add('toc', array(
+ 'active' => true,
+ 'title' => $title,
+ 'url' => Url::fromRequest()
+ ))
+ ->extend(new OutputFormat(array(OutputFormat::TYPE_CSV, OutputFormat::TYPE_JSON)));
+ $this->render('chapter', null, true);
+ }
+
+ /**
+ * Render a toc
+ *
+ * @param string $path Path to the documentation
+ * @param string $name Name of the documentation
+ * @param string $url URL to replace links with
+ * @param array $urlParams Additional URL parameters
+ */
+ protected function renderToc($path, $name, $url, array $urlParams = array())
+ {
+ $parser = new DocParser($path);
+ $toc = new DocTocRenderer($parser->getDocTree()->getIterator());
+ $this->view->toc = $toc
+ ->setUrl($url)
+ ->setUrlParams($urlParams);
+ $name = ucfirst($name);
+ $title = sprintf($this->translate('%s Documentation'), $name);
+ $this->getTabs()
+ ->add('toc', array(
+ 'active' => true,
+ 'title' => $title,
+ 'url' => Url::fromRequest()
+ ))
+ ->extend(new OutputFormat(array(OutputFormat::TYPE_CSV, OutputFormat::TYPE_JSON)));
+ $this->render('toc', null, true);
+ }
+
+ /**
+ * Render a pdf
+ *
+ * @param string $path Path to the documentation
+ * @param string $name Name of the documentation
+ * @param string $url
+ * @param array $urlParams
+ */
+ protected function renderPdf($path, $name, $url, array $urlParams = array())
+ {
+ $parser = new DocParser($path);
+ $toc = new DocTocRenderer($parser->getDocTree()->getIterator());
+ $this->view->toc = $toc
+ ->setUrl($url)
+ ->setUrlParams($urlParams);
+ $section = new DocSectionRenderer($parser->getDocTree());
+ $this->view->section = $section
+ ->setUrl($url)
+ ->setUrlParams($urlParams);
+ $this->view->title = sprintf($this->translate('%s Documentation'), $name);
+ $this->_request->setParam('format', 'pdf');
+ $this->_helper->viewRenderer->setRender('pdf', null, true);
+ }
+}
diff --git a/modules/doc/library/Doc/DocParser.php b/modules/doc/library/Doc/DocParser.php
new file mode 100644
index 0000000..7ddeaa9
--- /dev/null
+++ b/modules/doc/library/Doc/DocParser.php
@@ -0,0 +1,235 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc;
+
+use CachingIterator;
+use RecursiveIteratorIterator;
+use SplFileObject;
+use SplStack;
+use Icinga\Data\Tree\SimpleTree;
+use Icinga\Exception\NotReadableError;
+use Icinga\Util\DirectoryIterator;
+use Icinga\Module\Doc\Exception\DocException;
+
+/**
+ * Parser for documentation written in Markdown
+ */
+class DocParser
+{
+ /**
+ * Internal identifier for Atx-style headers
+ *
+ * @var int
+ */
+ const HEADER_ATX = 1;
+
+ /**
+ * Internal identifier for Setext-style headers
+ *
+ * @var int
+ */
+ const HEADER_SETEXT = 2;
+
+ /**
+ * Path to the documentation
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * Iterator over documentation files
+ *
+ * @var DirectoryIterator
+ */
+ protected $docIterator;
+
+ /**
+ * Create a new documentation parser for the given path
+ *
+ * @param string $path Path to the documentation
+ *
+ * @throws DocException If the documentation directory does not exist
+ * @throws NotReadableError If the documentation directory is not readable
+ */
+ public function __construct($path)
+ {
+ if (! DirectoryIterator::isReadable($path)) {
+ throw new DocException(
+ mt('doc', 'Documentation directory \'%s\' is not readable'),
+ $path
+ );
+ }
+ $this->path = $path;
+ $this->docIterator = new DirectoryIterator($path, 'md', DirectoryIterator::FILES_FIRST);
+ }
+
+ /**
+ * Extract atx- or setext-style headers from the given lines
+ *
+ * @param string $line
+ * @param string $nextLine
+ *
+ * @return array|null An array containing the header and the header level or null if there's nothing to extract
+ */
+ protected function extractHeader($line, $nextLine)
+ {
+ if (! $line) {
+ return null;
+ }
+ $header = null;
+ if ($line
+ && $line[0] === '#'
+ && preg_match('/^#+/', $line, $match) === 1
+ ) {
+ // Atx
+ $level = strlen($match[0]);
+ $header = trim(substr($line, $level));
+ if (! $header) {
+ return null;
+ }
+ $headerStyle = static::HEADER_ATX;
+ } elseif ($nextLine
+ && ($nextLine[0] === '=' || $nextLine[0] === '-')
+ && preg_match('/^[=-]+\s*$/', $nextLine, $match) === 1
+ ) {
+ // Setext
+ $header = trim($line);
+ if (! $header) {
+ return null;
+ }
+ if ($match[0][0] === '=') {
+ $level = 1;
+ } else {
+ $level = 2;
+ }
+ $headerStyle = static::HEADER_SETEXT;
+ }
+ if ($header === null) {
+ return null;
+ }
+ if (strpos($header, '<') !== false
+ && preg_match('#(?:<(?P<tag>a|span) (?:id|name)="(?P<id>.+)"></(?P=tag)>)\s*#u', $header, $match)
+ ) {
+ $header = str_replace($match[0], '', $header);
+ $id = $match['id'];
+ } else {
+ $id = null;
+ }
+ /** @noinspection PhpUndefinedVariableInspection */
+ return array($header, $id, $level, $headerStyle);
+ }
+
+ /**
+ * Generate unique section ID
+ *
+ * @param string $id
+ * @param string $filename
+ * @param SimpleTree $tree
+ *
+ * @return string
+ */
+ protected function uuid($id, $filename, SimpleTree $tree)
+ {
+ $id = str_replace(' ', '-', $id);
+ if ($tree->getNode($id) === null) {
+ return $id;
+ }
+ $id = $id . '-' . md5($filename);
+ $offset = 0;
+ while ($tree->getNode($id)) {
+ if ($offset++ === 0) {
+ $id .= '-' . $offset;
+ } else {
+ $id = substr($id, 0, -1) . $offset;
+ }
+ }
+ return $id;
+ }
+
+ /**
+ * Get the documentation tree
+ *
+ * @return SimpleTree
+ */
+ public function getDocTree()
+ {
+ $tree = new SimpleTree();
+ foreach (new RecursiveIteratorIterator($this->docIterator) as $filename) {
+ $file = new SplFileObject($filename);
+ $file->setFlags(SplFileObject::READ_AHEAD);
+ $stack = new SplStack();
+ $cachingIterator = new CachingIterator($file);
+ $insideFencedCodeBlock = false;
+
+ for ($cachingIterator->rewind(); $cachingIterator->valid(); $cachingIterator->next()) {
+ $line = $cachingIterator->current();
+ $header = null;
+
+ if (substr($line, 0, 3) === '```') {
+ $insideFencedCodeBlock = ! $insideFencedCodeBlock;
+ } elseif (! $insideFencedCodeBlock) {
+ $fileIterator = $cachingIterator->getInnerIterator();
+ $header = $this->extractHeader($line, $fileIterator->valid() ? $fileIterator->current() : null);
+ }
+
+ if ($header !== null) {
+ list($title, $id, $level, $headerStyle) = $header;
+ while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) {
+ $stack->pop();
+ }
+ if ($id === null) {
+ $path = array();
+ foreach ($stack as $section) {
+ /** @var $section DocSection */
+ $path[] = $section->getTitle();
+ }
+ $path[] = $title;
+ $id = implode('-', $path);
+ $noFollow = true;
+ } else {
+ $noFollow = false;
+ }
+
+ $id = $this->uuid($id, $filename, $tree);
+
+ $section = new DocSection();
+ $section
+ ->setId($id)
+ ->setTitle($title)
+ ->setLevel($level)
+ ->setNoFollow($noFollow);
+ if ($stack->isEmpty()) {
+ $section->setChapter($section);
+ $tree->addChild($section);
+ } else {
+ $section->setChapter($stack->bottom());
+ $tree->addChild($section, $stack->top());
+ }
+ $stack->push($section);
+ if ($headerStyle === static::HEADER_SETEXT) {
+ $cachingIterator->next();
+ continue;
+ }
+ } else {
+ if ($stack->isEmpty()) {
+ $title = ucfirst($file->getBasename('.' . pathinfo($file->getFilename(), PATHINFO_EXTENSION)));
+ $id = $this->uuid($title, $filename, $tree);
+ $section = new DocSection();
+ $section
+ ->setId($id)
+ ->setTitle($title)
+ ->setLevel(1)
+ ->setNoFollow(true);
+ $section->setChapter($section);
+ $tree->addChild($section);
+ $stack->push($section);
+ }
+ $stack->top()->appendContent($line);
+ }
+ }
+ }
+ return $tree;
+ }
+}
diff --git a/modules/doc/library/Doc/DocSection.php b/modules/doc/library/Doc/DocSection.php
new file mode 100644
index 0000000..ce5297e
--- /dev/null
+++ b/modules/doc/library/Doc/DocSection.php
@@ -0,0 +1,159 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc;
+
+use Icinga\Data\Tree\TreeNode;
+
+/**
+ * A section of a documentation
+ */
+class DocSection extends TreeNode
+{
+ /**
+ * Chapter the section belongs to
+ *
+ * @var DocSection
+ */
+ protected $chapter;
+
+ /**
+ * Content of the section
+ *
+ * @var array
+ */
+ protected $content = array();
+
+ /**
+ * Header level
+ *
+ * @var int
+ */
+ protected $level;
+
+ /**
+ * Whether to instruct search engines to not index the link to the section
+ *
+ * @var bool
+ */
+ protected $noFollow;
+
+ /**
+ * Title of the section
+ *
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * Set the chapter the section belongs to
+ *
+ * @param DocSection $section
+ *
+ * @return $this
+ */
+ public function setChapter(DocSection $section)
+ {
+ $this->chapter = $section;
+ return $this;
+ }
+
+ /**
+ * Get the chapter the section belongs to
+ *
+ * @return DocSection
+ */
+ public function getChapter()
+ {
+ return $this->chapter;
+ }
+
+ /**
+ * Append content
+ *
+ * @param string $content
+ */
+ public function appendContent($content)
+ {
+ $this->content[] = $content;
+ }
+
+ /**
+ * Get the content of the section
+ *
+ * @return array
+ */
+ public function getContent()
+ {
+ return $this->content;
+ }
+
+ /**
+ * Set the header level
+ *
+ * @param int $level Header level
+ *
+ * @return $this
+ */
+ public function setLevel($level)
+ {
+ $this->level = (int) $level;
+ return $this;
+ }
+
+ /**
+ * Get the header level
+ *
+ * @return int
+ */
+ public function getLevel()
+ {
+ return $this->level;
+ }
+
+ /**
+ * Set whether to instruct search engines to not index the link to the section
+ *
+ * @param bool $noFollow Whether to instruct search engines to not index the link to the section
+ *
+ * @return $this
+ */
+ public function setNoFollow($noFollow = true)
+ {
+ $this->noFollow = (bool) $noFollow;
+ return $this;
+ }
+
+ /**
+ * Get whether to instruct search engines to not index the link to the section
+ *
+ * @return bool
+ */
+ public function getNoFollow()
+ {
+ return $this->noFollow;
+ }
+
+ /**
+ * Set the title of the section
+ *
+ * @param string $title Title of the section
+ *
+ * @return $this
+ */
+ public function setTitle($title)
+ {
+ $this->title = (string) $title;
+ return $this;
+ }
+
+ /**
+ * Get the title of the section
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+}
diff --git a/modules/doc/library/Doc/DocSectionFilterIterator.php b/modules/doc/library/Doc/DocSectionFilterIterator.php
new file mode 100644
index 0000000..bac5a67
--- /dev/null
+++ b/modules/doc/library/Doc/DocSectionFilterIterator.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc;
+
+use Countable;
+use RecursiveFilterIterator;
+use Icinga\Data\Tree\TreeNodeIterator;
+
+/**
+ * Recursive filter iterator over sections that are part of a particular chapter
+ *
+ * @method TreeNodeIterator getInnerIterator() {
+ * {@inheritdoc}
+ * }
+ */
+class DocSectionFilterIterator extends RecursiveFilterIterator implements Countable
+{
+ /**
+ * Chapter to filter for
+ *
+ * @var string
+ */
+ protected $chapter;
+
+ /**
+ * Create a new recursive filter iterator over sections that are part of a particular chapter
+ *
+ * @param TreeNodeIterator $iterator
+ * @param string $chapter The chapter to filter for
+ */
+ public function __construct(TreeNodeIterator $iterator, $chapter)
+ {
+ parent::__construct($iterator);
+ $this->chapter = $chapter;
+ }
+
+ /**
+ * Accept sections that are part of the given chapter
+ *
+ * @return bool Whether the current element of the iterator is acceptable
+ * through this filter
+ */
+ public function accept(): bool
+ {
+ $section = $this->current();
+ /** @var \Icinga\Module\Doc\DocSection $section */
+ if ($section->getChapter()->getId() === $this->chapter) {
+ return true;
+ }
+ return false;
+ }
+
+ public function getChildren(): self
+ {
+ return new static($this->getInnerIterator()->getChildren(), $this->chapter);
+ }
+
+ public function count(): int
+ {
+ return iterator_count($this);
+ }
+
+ /**
+ * Whether the filter swallowed every section
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return $this->count() === 0;
+ }
+}
diff --git a/modules/doc/library/Doc/Exception/ChapterNotFoundException.php b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php
new file mode 100644
index 0000000..7fa7807
--- /dev/null
+++ b/modules/doc/library/Doc/Exception/ChapterNotFoundException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Exception;
+
+/**
+ * Exception thrown if a chapter was not found
+ */
+class ChapterNotFoundException extends DocException
+{
+}
diff --git a/modules/doc/library/Doc/Exception/DocException.php b/modules/doc/library/Doc/Exception/DocException.php
new file mode 100644
index 0000000..1d9e871
--- /dev/null
+++ b/modules/doc/library/Doc/Exception/DocException.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Exception;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown if an error in the documentation module's library occurs
+ */
+class DocException extends IcingaException
+{
+}
diff --git a/modules/doc/library/Doc/Renderer/DocRenderer.php b/modules/doc/library/Doc/Renderer/DocRenderer.php
new file mode 100644
index 0000000..cb1bc39
--- /dev/null
+++ b/modules/doc/library/Doc/Renderer/DocRenderer.php
@@ -0,0 +1,208 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Renderer;
+
+use Exception;
+use Icinga\Exception\IcingaException;
+use RecursiveIteratorIterator;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+
+/**
+ * Base class for toc and section renderer
+ */
+abstract class DocRenderer extends RecursiveIteratorIterator
+{
+ /**
+ * URL to images
+ *
+ * @var string
+ */
+ protected $imageUrl;
+
+ /**
+ * URL to replace links with
+ *
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * Additional URL parameters
+ *
+ * @var array
+ */
+ protected $urlParams = array();
+
+ /**
+ * View
+ *
+ * @var View|null
+ */
+ protected $view;
+
+ /**
+ * Get the URL to images
+ *
+ * @return string
+ */
+ public function getImageUrl()
+ {
+ return $this->imageUrl;
+ }
+
+ /**
+ * Set the URL to images
+ *
+ * @param string $imageUrl
+ *
+ * @return $this
+ */
+ public function setImageUrl($imageUrl)
+ {
+ $this->imageUrl = (string) $imageUrl;
+ return $this;
+ }
+ /**
+ * Get the URL to replace links with
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set the URL to replace links with
+ *
+ * @param string $url
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ $this->url = (string) $url;
+ return $this;
+ }
+
+ /**
+ * Get additional URL parameters
+ *
+ * @return array
+ */
+ public function getUrlParams()
+ {
+ return $this->urlParams;
+ }
+
+ /**
+ * Set additional URL parameters
+ *
+ * @param array $urlParams
+ *
+ * @return $this
+ */
+ public function setUrlParams(array $urlParams)
+ {
+ $this->urlParams = array_map(array($this, 'encodeUrlParam'), $urlParams);
+ return $this;
+ }
+
+ /**
+ * Get the view
+ *
+ * @return View
+ */
+ public function getView()
+ {
+ if ($this->view === null) {
+ $this->view = Icinga::app()->getViewRenderer()->view;
+ }
+ return $this->view;
+ }
+
+ /**
+ * Set the view
+ *
+ * @param View $view
+ *
+ * @return $this
+ */
+ public function setView(View $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Encode an anchor identifier
+ *
+ * @param string $anchor
+ *
+ * @return string
+ */
+ public static function encodeAnchor($anchor)
+ {
+ return rawurlencode($anchor);
+ }
+
+ /**
+ * Decode an anchor identifier
+ *
+ * @param string $anchor
+ *
+ * @return string
+ */
+ public static function decodeAnchor($anchor)
+ {
+ return rawurldecode($anchor);
+ }
+
+ /**
+ * Encode a URL parameter
+ *
+ * @param string $param
+ *
+ * @return string
+ */
+ public static function encodeUrlParam($param)
+ {
+ return str_replace(array('%2F','%5C'), array('%252F','%255C'), rawurlencode($param));
+ }
+
+ /**
+ * Decode a URL parameter
+ *
+ * @param string $param
+ *
+ * @return string
+ */
+ public static function decodeUrlParam($param)
+ {
+ return str_replace(array('%2F', '%5C'), array('/', '\\'), $param);
+ }
+
+ /**
+ * Render to HTML
+ *
+ * @return string
+ */
+ abstract public function render();
+
+ /**
+ * Render to HTML
+ *
+ * @return string
+ * @see \Icinga\Module\Doc\Renderer::render() For the render method.
+ */
+ public function __toString()
+ {
+ try {
+ return $this->render();
+ } catch (Exception $e) {
+ return $e->getMessage() . ': ' . IcingaException::getConfidentialTraceAsString($e);
+ }
+ }
+}
diff --git a/modules/doc/library/Doc/Renderer/DocSearchRenderer.php b/modules/doc/library/Doc/Renderer/DocSearchRenderer.php
new file mode 100644
index 0000000..c6e9ae2
--- /dev/null
+++ b/modules/doc/library/Doc/Renderer/DocSearchRenderer.php
@@ -0,0 +1,131 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Renderer;
+
+use RecursiveIteratorIterator;
+use Icinga\Module\Doc\Search\DocSearchIterator;
+use Icinga\Module\Doc\Search\DocSearchMatch;
+
+/**
+ * Renderer for doc searches
+ */
+class DocSearchRenderer extends DocRenderer
+{
+ /**
+ * The content to render
+ *
+ * @var array
+ */
+ protected $content = array();
+
+ /**
+ * Create a new renderer for doc searches
+ *
+ * @param DocSearchIterator $iterator
+ */
+ public function __construct(DocSearchIterator $iterator)
+ {
+ parent::__construct($iterator, RecursiveIteratorIterator::SELF_FIRST);
+ }
+
+ public function beginIteration(): void
+ {
+ $this->content[] = '<nav role="navigation"><ul class="toc">';
+ }
+
+ public function endIteration(): void
+ {
+ $this->content[] = '</ul></nav>';
+ }
+
+ public function beginChildren(): void
+ {
+ if ($this->getInnerIterator()->getMatches()) {
+ $this->content[] = '<ul class="toc">';
+ }
+ }
+
+ public function endChildren(): void
+ {
+ if ($this->getInnerIterator()->getMatches()) {
+ $this->content[] = '</ul>';
+ }
+ }
+
+ public function render()
+ {
+ foreach ($this as $section) {
+ if (($matches = $this->getInnerIterator()->getMatches()) === null) {
+ continue;
+ }
+ $title = $this->getView()->escape($section->getTitle());
+ $contentMatches = array();
+ foreach ($matches as $match) {
+ if ($match->getMatchType() === DocSearchMatch::MATCH_HEADER) {
+ $title = $match->highlight();
+ } else {
+ $contentMatches[] = sprintf(
+ '<p>%s</p>',
+ $match->highlight()
+ );
+ }
+ }
+ $path = $this->getView()->getHelper('Url')->url(
+ array_merge(
+ $this->getUrlParams(),
+ array(
+ 'chapter' => $this->encodeUrlParam($section->getChapter()->getId())
+ )
+ ),
+ $this->url,
+ false,
+ false
+ );
+ $url = $this->getView()->url(
+ $path,
+ array('highlight-search' => $this->getInnerIterator()->getSearch()->getInput())
+ );
+ /** @var \Icinga\Web\Url $url */
+ $url->setAnchor($this->encodeAnchor($section->getId()));
+ $urlAttributes = array(
+ 'data-base-target' => '_next',
+ 'title' => $section->getId() === $section->getChapter()->getId()
+ ? sprintf(
+ $this->getView()->translate(
+ 'Show all matches of "%s" in the chapter "%s"',
+ 'search.render.section.link'
+ ),
+ $this->getInnerIterator()->getSearch()->getInput(),
+ $section->getChapter()->getTitle()
+ )
+ : sprintf(
+ $this->getView()->translate(
+ 'Show all matches of "%s" in the section "%s" of the chapter "%s"',
+ 'search.render.section.link'
+ ),
+ $this->getInnerIterator()->getSearch()->getInput(),
+ $section->getTitle(),
+ $section->getChapter()->getTitle()
+ )
+ );
+ if ($section->getNoFollow()) {
+ $urlAttributes['rel'] = 'nofollow';
+ }
+ $this->content[] = '<li>' . $this->getView()->qlink(
+ $title,
+ $url->getAbsoluteUrl(),
+ null,
+ $urlAttributes,
+ false
+ );
+ if (! empty($contentMatches)) {
+ $this->content = array_merge($this->content, $contentMatches);
+ }
+ if (! $section->hasChildren()) {
+ $this->content[] = '</li>';
+ }
+ }
+ return implode("\n", $this->content);
+ }
+}
diff --git a/modules/doc/library/Doc/Renderer/DocSectionRenderer.php b/modules/doc/library/Doc/Renderer/DocSectionRenderer.php
new file mode 100644
index 0000000..de2d4d4
--- /dev/null
+++ b/modules/doc/library/Doc/Renderer/DocSectionRenderer.php
@@ -0,0 +1,346 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Renderer;
+
+require_once 'Parsedown/Parsedown.php';
+
+use DOMDocument;
+use DOMXPath;
+use Parsedown;
+use RecursiveIteratorIterator;
+use Icinga\Data\Tree\SimpleTree;
+use Icinga\Module\Doc\Exception\ChapterNotFoundException;
+use Icinga\Module\Doc\DocSectionFilterIterator;
+use Icinga\Module\Doc\Search\DocSearch;
+use Icinga\Module\Doc\Search\DocSearchMatch;
+use Icinga\Web\Dom\DomNodeIterator;
+use Icinga\Web\Url;
+use Icinga\Web\View;
+
+/**
+ * Section renderer
+ */
+class DocSectionRenderer extends DocRenderer
+{
+ /**
+ * Content to render
+ *
+ * @var array
+ */
+ protected $content = array();
+
+ /**
+ * Search criteria to highlight
+ *
+ * @var string
+ */
+ protected $highlightSearch;
+
+ /**
+ * Parsedown instance
+ *
+ * @var Parsedown
+ */
+ protected $parsedown;
+
+ /**
+ * Documentation tree
+ *
+ * @var SimpleTree
+ */
+ protected $tree;
+
+ /**
+ * Create a new section renderer
+ *
+ * @param SimpleTree $tree The documentation tree
+ * @param string|null $chapter If not null, the chapter to filter for
+ *
+ * @throws ChapterNotFoundException If the chapter to filter for was not found
+ */
+ public function __construct(SimpleTree $tree, $chapter = null)
+ {
+ if ($chapter !== null) {
+ $filter = new DocSectionFilterIterator($tree->getIterator(), $chapter);
+ if ($filter->isEmpty()) {
+ throw new ChapterNotFoundException(
+ mt('doc', 'Chapter %s not found'),
+ $chapter
+ );
+ }
+ parent::__construct(
+ $filter,
+ RecursiveIteratorIterator::SELF_FIRST
+ );
+ } else {
+ parent::__construct($tree->getIterator(), RecursiveIteratorIterator::SELF_FIRST);
+ }
+ $this->tree = $tree;
+ $this->parsedown = Parsedown::instance();
+ }
+
+ /**
+ * Set the search criteria to highlight
+ *
+ * @param string $highlightSearch
+ *
+ * @return $this
+ */
+ public function setHighlightSearch($highlightSearch)
+ {
+ $this->highlightSearch = $highlightSearch;
+ return $this;
+ }
+
+ /**
+ * Get the search criteria to highlight
+ *
+ * @return string
+ */
+ public function getHighlightSearch()
+ {
+ return $this->highlightSearch;
+ }
+
+ /**
+ * Syntax highlighting for PHP code
+ *
+ * @param array $match
+ *
+ * @return string
+ */
+ protected function highlightPhp($match)
+ {
+ return '<pre>' . highlight_string(htmlspecialchars_decode($match[1]), true) . '</pre>';
+ }
+
+ /**
+ * Highlight search criteria
+ *
+ * @param string $html
+ * @param DocSearch $search Search criteria
+ *
+ * @return string
+ */
+ protected function highlightSearch($html, DocSearch $search)
+ {
+ $doc = new DOMDocument();
+ @$doc->loadHTML($html);
+ $iter = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($iter as $node) {
+ if ($node->nodeType !== XML_TEXT_NODE
+ || ($node->parentNode->nodeType === XML_ELEMENT_NODE && $node->parentNode->tagName === 'code')
+ ) {
+ continue;
+ }
+ $text = $node->nodeValue;
+ if (($match = $search->search($text)) === null) {
+ continue;
+ }
+ $matches = $match->getMatches();
+ ksort($matches);
+ $offset = 0;
+ $fragment = $doc->createDocumentFragment();
+ foreach ($matches as $position => $match) {
+ $fragment->appendChild($doc->createTextNode(substr($text, $offset, $position - $offset)));
+ $fragment->appendChild($doc->createElement('span', $match))
+ ->setAttribute('class', DocSearchMatch::HIGHLIGHT_CSS_CLASS);
+ $offset = $position + strlen($match);
+ }
+ $fragment->appendChild($doc->createTextNode(substr($text, $offset)));
+ $node->parentNode->replaceChild($fragment, $node);
+ }
+ // Remove <!DOCTYPE
+ $doc->removeChild($doc->doctype);
+ // Remove <html><body> and </body></html>
+ return substr($doc->saveHTML(), 12, -15);
+ }
+
+ /**
+ * Markup notes
+ *
+ * @param array $match
+ *
+ * @return string
+ */
+ protected function markupNotes($match)
+ {
+ $doc = new DOMDocument();
+ $doc->loadHTML($match[0]);
+ $xpath = new DOMXPath($doc);
+ $blockquote = $xpath->query('//blockquote[1]')->item(0);
+ /** @var \DOMElement $blockquote */
+ if (strtolower(substr(trim($blockquote->nodeValue), 0, 5)) === 'note:') {
+ $blockquote->setAttribute('class', 'note');
+ }
+ return $doc->saveXML($blockquote);
+ }
+
+ /**
+ * Replace img src tags
+ *
+ * @param $match
+ *
+ * @return string
+ */
+ protected function replaceImg($match)
+ {
+ $doc = new DOMDocument();
+ $doc->loadHTML($match[0]);
+ $xpath = new DOMXPath($doc);
+ $img = $xpath->query('//img[1]')->item(0);
+ /** @var \DOMElement $img */
+ $path = $this->getView()->getHelper('Url')->url(
+ array_merge(
+ array(
+ 'image' => trim($img->getAttribute('src'))
+ ),
+ $this->urlParams
+ ),
+ $this->imageUrl,
+ false,
+ false
+ );
+ $url = $this->getView()->url($path);
+ /** @var \Icinga\Web\Url $url */
+ $img->setAttribute('src', $url->getAbsoluteUrl());
+ return substr_replace($doc->saveXML($img), '', -2, 1); // Replace '/>' with '>'
+ }
+
+ /**
+ * Replace chapter link
+ *
+ * @param array $match
+ *
+ * @return string
+ */
+ protected function replaceChapterLink($match)
+ {
+ if (($chapter = $this->tree->getNode($this->decodeAnchor($match['chapter']))) === null) {
+ return $match[0];
+ }
+ /** @var \Icinga\Module\Doc\DocSection $section */
+ $path = $this->getView()->getHelper('Url')->url(
+ array_merge(
+ $this->urlParams,
+ array(
+ 'chapter' => $this->encodeUrlParam($chapter->getChapter()->getId())
+ )
+ ),
+ $this->url,
+ false,
+ false
+ );
+ $url = $this->getView()->url($path);
+ /** @var \Icinga\Web\Url $url */
+ return sprintf(
+ '<a %s%shref="%s"',
+ strlen($match['attribs']) ? trim($match['attribs']) . ' ' : '',
+ $chapter->getNoFollow() ? 'rel="nofollow" ' : '',
+ $url->getAbsoluteUrl()
+ );
+ }
+
+ /**
+ * Replace section link
+ *
+ * @param array $match
+ *
+ * @return string
+ */
+ protected function replaceSectionLink($match)
+ {
+ if (($section = $this->tree->getNode($this->decodeAnchor($match['section']))) === null) {
+ return $match[0];
+ }
+ /** @var \Icinga\Module\Doc\DocSection $section */
+ $path = $this->getView()->getHelper('Url')->url(
+ array_merge(
+ $this->urlParams,
+ array(
+ 'chapter' => $this->encodeUrlParam($section->getChapter()->getId())
+ )
+ ),
+ $this->url,
+ false,
+ false
+ );
+ $url = $this->getView()->url($path);
+ /** @var \Icinga\Web\Url $url */
+ $url->setAnchor($this->encodeAnchor($section->getId()));
+ return sprintf(
+ '<a %s%shref="%s"',
+ strlen($match['attribs']) ? trim($match['attribs']) . ' ' : '',
+ $section->getNoFollow() ? 'rel="nofollow" ' : '',
+ $url->getAbsoluteUrl()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ $search = null;
+ if (($highlightSearch = $this->getHighlightSearch()) !== null) {
+ $search = new DocSearch($highlightSearch);
+ }
+ foreach ($this as $section) {
+ $title = $section->getTitle();
+ if ($search !== null && ($match = $search->search($title)) !== null) {
+ $title = $match->highlight();
+ } else {
+ $title = $this->getView()->escape($title);
+ }
+ $number = '';
+ for ($i = 0; $i < $this->getDepth() + 1; ++$i) {
+ if ($i > 0) {
+ $number .= '.';
+ }
+ $number .= $this->getSubIterator($i)->key() + 1;
+ }
+ $this->content[] = sprintf(
+ '<a name="%1$s"></a><h%2$d>%3$s. %4$s</h%2$d>',
+ static::encodeAnchor($section->getId()),
+ $section->getLevel(),
+ $number,
+ $title
+ );
+ $html = $this->parsedown->text(implode('', $section->getContent()));
+ if (empty($html)) {
+ continue;
+ }
+ $html = preg_replace_callback(
+ '#<pre><code class="language-php">(.*?)</code></pre>#s',
+ array($this, 'highlightPhp'),
+ $html
+ );
+ $html = preg_replace_callback(
+ '/<img[^>]+>/',
+ array($this, 'replaceImg'),
+ $html
+ );
+ $html = preg_replace_callback(
+ '#<blockquote>.+?</blockquote>#ms',
+ array($this, 'markupNotes'),
+ $html
+ );
+ $html = preg_replace_callback(
+ '/<a\s+(?P<attribs>[^>]*?\s+)?href="(?:(?!http:\/\/)[^"#]*)#(?P<section>[^"]+)"/',
+ array($this, 'replaceSectionLink'),
+ $html
+ );
+ $html = preg_replace_callback(
+ '/<a\s+(?P<attribs>[^>]*?\s+)?href="(?:\d+-)?(?P<chapter>[^\/"#]+).md"/',
+ array($this, 'replaceChapterLink'),
+ $html
+ );
+ if ($search !== null) {
+ $html = $this->highlightSearch($html, $search);
+ }
+ $this->content[] = $html;
+ }
+ return implode("\n", $this->content);
+ }
+}
diff --git a/modules/doc/library/Doc/Renderer/DocTocRenderer.php b/modules/doc/library/Doc/Renderer/DocTocRenderer.php
new file mode 100644
index 0000000..09e9a1d
--- /dev/null
+++ b/modules/doc/library/Doc/Renderer/DocTocRenderer.php
@@ -0,0 +1,117 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Renderer;
+
+use Icinga\Data\Tree\TreeNodeIterator;
+use RecursiveIteratorIterator;
+
+/**
+ * TOC renderer
+ */
+class DocTocRenderer extends DocRenderer
+{
+ /**
+ * CSS class for the HTML list element
+ *
+ * @var string
+ */
+ const CSS_CLASS = 'toc';
+
+ /**
+ * Tag for the HTML list element
+ *
+ * @var string
+ */
+ const HTML_LIST_TAG = 'ol';
+
+ /**
+ * Content to render
+ *
+ * @var array
+ */
+ protected $content = array();
+
+ /**
+ * Create a new toc renderer
+ *
+ * @param TreeNodeIterator $iterator
+ */
+ public function __construct(TreeNodeIterator $iterator)
+ {
+ parent::__construct($iterator, RecursiveIteratorIterator::SELF_FIRST);
+ }
+
+ public function beginIteration(): void
+ {
+ $this->content[] = sprintf('<nav role="navigation"><%s class="%s">', static::HTML_LIST_TAG, static::CSS_CLASS);
+ }
+
+ public function endIteration(): void
+ {
+ $this->content[] = sprintf('</%s></nav>', static::HTML_LIST_TAG);
+ }
+
+ public function beginChildren(): void
+ {
+ $this->content[] = sprintf('<%s class="%s">', static::HTML_LIST_TAG, static::CSS_CLASS);
+ }
+
+ public function endChildren(): void
+ {
+ $this->content[] = sprintf('</%s>', static::HTML_LIST_TAG);
+ }
+
+ public function render()
+ {
+ if ($this->getInnerIterator()->isEmpty()) {
+ return '<p>' . mt('doc', 'Documentation is empty.') . '</p>';
+ }
+ $view = $this->getView();
+ $zendUrlHelper = $view->getHelper('Url');
+ foreach ($this as $section) {
+ $path = $zendUrlHelper->url(
+ array_merge(
+ $this->urlParams,
+ array(
+ 'chapter' => $this->encodeUrlParam($section->getChapter()->getId())
+ )
+ ),
+ $this->url,
+ false,
+ false
+ );
+ $url = $view->url($path);
+ /** @var \Icinga\Web\Url $url */
+ if ($this->getDepth() > 0) {
+ $url->setAnchor($this->encodeAnchor($section->getId()));
+ }
+ $urlAttributes = array(
+ 'data-base-target' => '_next',
+ 'title' => $section->getId() === $section->getChapter()->getId()
+ ? sprintf(
+ $view->translate('Show the chapter "%s"', 'toc.render.section.link'),
+ $section->getChapter()->getTitle()
+ )
+ : sprintf(
+ $view->translate('Show the section "%s" of the chapter "%s"', 'toc.render.section.link'),
+ $section->getTitle(),
+ $section->getChapter()->getTitle()
+ )
+ );
+ if ($section->getNoFollow()) {
+ $urlAttributes['rel'] = 'nofollow';
+ }
+ $this->content[] = '<li>' . $this->getView()->qlink(
+ $section->getTitle(),
+ $url->getAbsoluteUrl(),
+ null,
+ $urlAttributes
+ );
+ if (! $section->hasChildren()) {
+ $this->content[] = '</li>';
+ }
+ }
+ return implode("\n", $this->content);
+ }
+}
diff --git a/modules/doc/library/Doc/Search/DocSearch.php b/modules/doc/library/Doc/Search/DocSearch.php
new file mode 100644
index 0000000..20493e4
--- /dev/null
+++ b/modules/doc/library/Doc/Search/DocSearch.php
@@ -0,0 +1,95 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Search;
+
+/**
+ * Search documentation for a given search string
+ */
+class DocSearch
+{
+ /**
+ * Search string
+ *
+ * @var string
+ */
+ protected $input;
+
+ /**
+ * Search criteria
+ *
+ * @var array
+ */
+ protected $search;
+
+ /**
+ * Create a new doc search from the given search string
+ *
+ * @param string $search
+ */
+ public function __construct($search)
+ {
+ $this->input = $search = (string) $search;
+ $criteria = array();
+ if (preg_match_all('/"(?P<search>[^"]*)"/', $search, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
+ $unquoted = array();
+ $offset = 0;
+ foreach ($matches as $match) {
+ $fullMatch = $match[0];
+ $searchMatch = $match['search'];
+ $unquoted[] = substr($search, $offset, $fullMatch[1] - $offset);
+ $offset = $fullMatch[1] + strlen($fullMatch[0]);
+ if (strlen($searchMatch[0]) > 0) {
+ $criteria[] = $searchMatch[0];
+ }
+ }
+ $unquoted[] = substr($search, $offset);
+ $search = implode(' ', $unquoted);
+ }
+ $this->search = array_map(
+ 'strtolower',
+ array_unique(array_merge($criteria, array_filter(explode(' ', trim($search)))))
+ );
+ }
+
+ /**
+ * Get the search criteria
+ *
+ * @return array
+ */
+ public function getCriteria()
+ {
+ return $this->search;
+ }
+
+ /**
+ * Get the search string
+ *
+ * @return string
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Search in the given line
+ *
+ * @param string $line
+ *
+ * @return DocSearchMatch|null
+ */
+ public function search($line)
+ {
+ $match = new DocSearchMatch();
+ $match->setLine($line);
+ foreach ($this->search as $criteria) {
+ $offset = 0;
+ while (($position = stripos($line, $criteria, $offset)) !== false) {
+ $match->appendMatch(substr($line, $position, strlen($criteria)), $position);
+ $offset = $position + 1;
+ }
+ }
+ return $match->isEmpty() ? null : $match;
+ }
+}
diff --git a/modules/doc/library/Doc/Search/DocSearchIterator.php b/modules/doc/library/Doc/Search/DocSearchIterator.php
new file mode 100644
index 0000000..fd2c903
--- /dev/null
+++ b/modules/doc/library/Doc/Search/DocSearchIterator.php
@@ -0,0 +1,113 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Search;
+
+use RecursiveFilterIterator;
+use RecursiveIteratorIterator;
+use Icinga\Data\Tree\TreeNodeIterator;
+
+/**
+ * Iterator over doc sections that match a given search criteria
+ */
+class DocSearchIterator extends RecursiveFilterIterator
+{
+ /**
+ * Search criteria
+ *
+ * @var DocSearch
+ */
+ protected $search;
+
+ /**
+ * Current search matches
+ *
+ * @var DocSearchMatch[]|null
+ */
+ protected $matches;
+
+ /**
+ * Create a new iterator over doc sections that match the given search criteria
+ *
+ * @param TreeNodeIterator $iterator
+ * @param DocSearch $search
+ */
+ public function __construct(TreeNodeIterator $iterator, DocSearch $search)
+ {
+ $this->search = $search;
+ parent::__construct($iterator);
+ }
+
+ /**
+ * Accept sections that match the search
+ *
+ * @return bool Whether the current element of the iterator is acceptable
+ * through this filter
+ */
+ public function accept(): bool
+ {
+ $section = $this->current();
+ /** @var $section \Icinga\Module\Doc\DocSection */
+ $matches = array();
+ if (($match = $this->search->search($section->getTitle())) !== null) {
+ $matches[] = $match->setMatchType(DocSearchMatch::MATCH_HEADER);
+ }
+ foreach ($section->getContent() as $lineno => $line) {
+ if (($match = $this->search->search($line)) !== null) {
+ $matches[] = $match
+ ->setMatchType(DocSearchMatch::MATCH_CONTENT)
+ ->setLineno($lineno);
+ }
+ }
+ if (! empty($matches)) {
+ $this->matches = $matches;
+ return true;
+ }
+ if ($section->hasChildren()) {
+ $this->matches = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the search criteria
+ *
+ * @return DocSearch
+ */
+ public function getSearch()
+ {
+ return $this->search;
+ }
+
+ public function getChildren(): self
+ {
+ return new static($this->getInnerIterator()->getChildren(), $this->search);
+ }
+
+ /**
+ * Whether the search did not yield any match
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ $iter = new RecursiveIteratorIterator($this, RecursiveIteratorIterator::SELF_FIRST);
+ foreach ($iter as $section) {
+ if ($iter->getInnerIterator()->getMatches() !== null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get current matches
+ *
+ * @return DocSearchMatch[]|null
+ */
+ public function getMatches()
+ {
+ return $this->matches;
+ }
+}
diff --git a/modules/doc/library/Doc/Search/DocSearchMatch.php b/modules/doc/library/Doc/Search/DocSearchMatch.php
new file mode 100644
index 0000000..0f21748
--- /dev/null
+++ b/modules/doc/library/Doc/Search/DocSearchMatch.php
@@ -0,0 +1,215 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Doc\Search;
+
+use UnexpectedValueException;
+use Icinga\Application\Icinga;
+use Icinga\Web\View;
+
+/**
+ * A doc search match
+ */
+class DocSearchMatch
+{
+ /**
+ * CSS class for highlighting matches
+ *
+ * @var string
+ */
+ const HIGHLIGHT_CSS_CLASS = 'search-highlight';
+
+ /**
+ * Header match
+ *
+ * @var int
+ */
+ const MATCH_HEADER = 1;
+
+ /**
+ * Content match
+ *
+ * @var int
+ */
+ const MATCH_CONTENT = 2;
+
+ /**
+ * Line
+ *
+ * @var string
+ */
+ protected $line;
+
+ /**
+ * Line number
+ *
+ * @var int
+ */
+ protected $lineno;
+
+ /**
+ * Type of the match
+ *
+ * @var int
+ */
+ protected $matchType;
+
+ /**
+ * Matches
+ *
+ * @var array
+ */
+ protected $matches = array();
+
+ /**
+ * View
+ *
+ * @var View|null
+ */
+ protected $view;
+
+ /**
+ * Set the line
+ *
+ * @param string $line
+ *
+ * @return $this
+ */
+ public function setLine($line)
+ {
+ $this->line = (string) $line;
+ return $this;
+ }
+
+ /**
+ * Get the line
+ *
+ * @return string
+ */
+ public function getLine()
+ {
+ return $this->line;
+ }
+
+ /**
+ * Set the line number
+ *
+ * @param int $lineno
+ *
+ * @return $this
+ */
+ public function setLineno($lineno)
+ {
+ $this->lineno = (int) $lineno;
+ return $this;
+ }
+
+ /**
+ * Set the match type
+ *
+ * @param int $matchType
+ *
+ * @return $this
+ */
+ public function setMatchType($matchType)
+ {
+ $matchType = (int) $matchType;
+ if ($matchType !== static::MATCH_HEADER && $matchType !== static::MATCH_CONTENT) {
+ throw new UnexpectedValueException();
+ }
+ $this->matchType = $matchType;
+ return $this;
+ }
+
+ /**
+ * Get the match type
+ *
+ * @return int
+ */
+ public function getMatchType()
+ {
+ return $this->matchType;
+ }
+
+ /**
+ * Append a match
+ *
+ * @param string $match
+ * @param int $position
+ *
+ * @return $this
+ */
+ public function appendMatch($match, $position)
+ {
+ $this->matches[(int) $position] = (string) $match;
+ return $this;
+ }
+
+ /**
+ * Get the matches
+ *
+ * @return array
+ */
+ public function getMatches()
+ {
+ return $this->matches;
+ }
+
+ /**
+ * Set the view
+ *
+ * @param View $view
+ *
+ * @return $this
+ */
+ public function setView(View $view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Get the view
+ *
+ * @return View
+ */
+ public function getView()
+ {
+ if ($this->view === null) {
+ $this->view = Icinga::app()->getViewRenderer()->view;
+ }
+ return $this->view;
+ }
+
+ /**
+ * Get the line having matches highlighted
+ *
+ * @return string
+ */
+ public function highlight()
+ {
+ $highlighted = '';
+ $offset = 0;
+ $matches = $this->getMatches();
+ ksort($matches);
+ foreach ($matches as $position => $match) {
+ $highlighted .= $this->getView()->escape(substr($this->line, $offset, $position - $offset))
+ . '<span class="' . static::HIGHLIGHT_CSS_CLASS .'">'
+ . $this->getView()->escape($match)
+ . '</span>';
+ $offset = $position + strlen($match);
+ }
+ $highlighted .= $this->getView()->escape(substr($this->line, $offset));
+ return $highlighted;
+ }
+
+ /**
+ * Whether the match is empty
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->matches);
+ }
+}
diff --git a/modules/doc/module.info b/modules/doc/module.info
new file mode 100644
index 0000000..ac596db
--- /dev/null
+++ b/modules/doc/module.info
@@ -0,0 +1,4 @@
+Module: doc
+Version: 2.11.4
+Description: Documentation module
+ Extracts, shows and exports documentation for Icinga Web 2 and its modules.
diff --git a/modules/doc/public/css/module.less b/modules/doc/public/css/module.less
new file mode 100644
index 0000000..007c5c5
--- /dev/null
+++ b/modules/doc/public/css/module.less
@@ -0,0 +1,109 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+// Mixins
+
+.gradient(@a: @gray-lighter; @b: @gray-lightest) {
+ background: @a;
+ background: -webkit-gradient(linear, left top, left bottom, from(@a), to(@b));
+ background: -webkit-linear-gradient(top, @a, @b);
+ background: -moz-linear-gradient(top, @a, @b);
+ background: -ms-linear-gradient(top, @a, @b);
+ background: -o-linear-gradient(top, @a, @b);
+ background: linear-gradient(to bottom, @a, @b);
+}
+
+// General styles
+
+code {
+ color: @icinga-blue;
+ font-family: @font-family-fixed;
+}
+
+pre > code {
+ color: inherit;
+}
+
+.chapter a {
+ border-bottom: 1px dotted @gray-light;
+ font-weight: @font-weight-bold;
+
+ &:hover {
+ border-bottom: 1px solid @text-color;
+ text-decoration: none;
+ }
+}
+
+.content {
+ font-size: 1.167em;
+}
+
+.search-highlight {
+ .rounded-corners();
+
+ background: @icinga-blue;
+ color: @text-color-on-icinga-blue;
+ padding: 0 0.3em 0 0.3em;
+}
+
+.toc {
+ counter-reset: li;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ counter-increment: li;
+ margin-top: 0.25em;
+
+ > .toc {
+ margin-left: 2em;
+ }
+
+ a {
+ &:before {
+ color: @icinga-blue;
+ content: counters(li,".") " ";
+ display: inline-block;
+ font-size: small;
+ font-weight: @font-weight-bold;
+ min-width: 1.5em;
+ padding: 0.25em;
+ text-align: center;
+ }
+
+ display: block;
+ }
+ }
+}
+
+// Table styles
+
+table {
+ margin-bottom: 1em;
+ width: 100%;
+}
+
+tbody > tr:nth-child(odd) {
+ .gradient()
+}
+
+tbody > tr:nth-child(even) {
+ background: @body-bg-color;
+}
+
+td, th {
+ padding: 0.5em;
+}
+
+td {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+th {
+ border-bottom: 2px solid @icinga-blue;
+ font-weight: @font-weight-bold;
+ text-align: left;
+ text-transform: uppercase;
+}
diff --git a/modules/doc/public/js/module.js b/modules/doc/public/js/module.js
new file mode 100644
index 0000000..d5571ee
--- /dev/null
+++ b/modules/doc/public/js/module.js
@@ -0,0 +1,30 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+(function(Icinga) {
+
+ var Doc = function(module) {
+ this.module = module;
+ this.initialize();
+ this.module.icinga.logger.debug('Doc module loaded');
+ };
+
+ Doc.prototype = {
+
+ initialize: function()
+ {
+ this.module.on('rendered', this.rendered);
+ this.module.icinga.logger.debug('Doc module initialized');
+ },
+
+ rendered: function(event) {
+ var $container = $(event.currentTarget);
+ if ($('> .content.styleguide', $container).length) {
+ $container.removeClass('module-doc');
+ }
+ }
+ };
+
+ Icinga.availableModules.doc = Doc;
+
+}(Icinga));
+
diff --git a/modules/doc/run.php b/modules/doc/run.php
new file mode 100644
index 0000000..df9dd09
--- /dev/null
+++ b/modules/doc/run.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+use Icinga\Application\Icinga;
+
+if (Icinga::app()->isCli()) {
+ return;
+}
+
+$docModuleChapter = new Zend_Controller_Router_Route(
+ 'doc/module/:moduleName/chapter/:chapter',
+ array(
+ 'controller' => 'module',
+ 'action' => 'chapter',
+ 'module' => 'doc'
+ )
+);
+
+$docIcingaWebChapter = new Zend_Controller_Router_Route(
+ 'doc/icingaweb/chapter/:chapter',
+ array(
+ 'controller' => 'icingaweb',
+ 'action' => 'chapter',
+ 'module' => 'doc'
+ )
+);
+
+$docModuleToc = new Zend_Controller_Router_Route(
+ 'doc/module/:moduleName/toc',
+ array(
+ 'controller' => 'module',
+ 'action' => 'toc',
+ 'module' => 'doc'
+ )
+);
+
+$docModulePdf = new Zend_Controller_Router_Route(
+ 'doc/module/:moduleName/pdf',
+ array(
+ 'controller' => 'module',
+ 'action' => 'pdf',
+ 'module' => 'doc'
+ )
+);
+
+$docModuleImg = new Zend_Controller_Router_Route_Regex(
+ 'doc/module/([^/]+)/image/(.+)',
+ array(
+ 'controller' => 'module',
+ 'action' => 'image',
+ 'module' => 'doc'
+ ),
+ array(
+ 'moduleName' => 1,
+ 'image' => 2
+ ),
+ 'doc/module/%s/image/%s'
+);
+
+$this->addRoute('doc/module/chapter', $docModuleChapter);
+$this->addRoute('doc/icingaweb/chapter', $docIcingaWebChapter);
+$this->addRoute('doc/module/toc', $docModuleToc);
+$this->addRoute('doc/module/pdf', $docModulePdf);
+$this->addRoute('doc/module/img', $docModuleImg);
diff --git a/modules/migrate/application/clicommands/ConfigCommand.php b/modules/migrate/application/clicommands/ConfigCommand.php
new file mode 100644
index 0000000..a5be144
--- /dev/null
+++ b/modules/migrate/application/clicommands/ConfigCommand.php
@@ -0,0 +1,119 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Migrate\Clicommands;
+
+use Icinga\Cli\Command;
+use Icinga\Module\Migrate\Config\UserDomainMigration;
+use Icinga\User;
+use Icinga\Util\StringHelper;
+
+class ConfigCommand extends Command
+{
+ /**
+ * Rename users and user configurations according to a given domain
+ *
+ * The following configurations are taken into account:
+ * - Announcements
+ * - Preferences
+ * - Dashboards
+ * - Custom navigation items
+ * - Role configuration
+ * - Users and group memberships in database backends, if configured
+ *
+ * USAGE:
+ *
+ * icingacli migrate config users [options]
+ *
+ * OPTIONS:
+ *
+ * --to-domain=<to-domain> The new domain for the users
+ *
+ * --from-domain=<from-domain> Migrate only the users with the given domain.
+ * Use this switch in combination with --to-domain.
+ *
+ * --user=<user> Migrate only the given user in the format <user> or <user@domain>
+ *
+ * --map-file=<mapfile> File to use for renaming users
+ *
+ * --separator=<separator> Separator for the map file
+ *
+ * EXAMPLES:
+ *
+ * icingacli migrate config users ...
+ *
+ * Add the domain "icinga.com" to all users:
+ *
+ * --to-domain icinga.com
+ *
+ * Set the domain "example.com" on all users that have the domain "icinga.com":
+ *
+ * --to-domain example.com --from-domain icinga.com
+ *
+ * Set the domain "icinga.com" on the user "icingaadmin":
+ *
+ * --to-domain icinga.com --user icingaadmin
+ *
+ * Set the domain "icinga.com" on the users "icingaadmin@icinga.com"
+ *
+ * --to-domain example.com --user icingaadmin@icinga.com
+ *
+ * Rename users according to a map file:
+ *
+ * --map-file /path/to/mapfile --separator :
+ *
+ * MAPFILE:
+ *
+ * You may rename users according to a given map file. The map file must be separated by newlines. Each line then
+ * is specified in the format <from><separator><to>. The separator is specified with the --separator switch.
+ *
+ * Example content:
+ *
+ * icingaadmin:icingaadmin@icinga.com
+ * jdoe@example.com:jdoe@icinga.com
+ * rroe@icinga:rroe@icinga.com
+ */
+ public function usersAction()
+ {
+ if ($this->params->has('map-file')) {
+ $mapFile = $this->params->get('map-file');
+ $separator = $this->params->getRequired('separator');
+
+ $source = trim(file_get_contents($mapFile));
+ $source = StringHelper::trimSplit($source, "\n");
+
+ $map = array();
+
+ array_walk($source, function ($item) use ($separator, &$map) {
+ list($from, $to) = StringHelper::trimSplit($item, $separator, 2);
+ $map[$from] = $to;
+ });
+
+ $migration = UserDomainMigration::fromMap($map);
+ } else {
+ $toDomain = $this->params->getRequired('to-domain');
+ $fromDomain = $this->params->get('from-domain');
+ $user = $this->params->get('user');
+
+ if ($user === null) {
+ $migration = UserDomainMigration::fromDomains($toDomain, $fromDomain);
+ } else {
+ if ($fromDomain !== null) {
+ $this->fail(
+ "Ambiguous arguments: Can't use --user in combination with --from-domain."
+ . " Please use the user@domain syntax for the --user switch instead."
+ );
+ }
+
+ $user = new User($user);
+
+ $migrated = clone $user;
+ $migrated->setDomain($toDomain);
+
+ $migration = UserDomainMigration::fromMap(array($user->getUsername() => $migrated->getUsername()));
+ }
+ }
+
+ $migration->migrate();
+ }
+}
diff --git a/modules/migrate/application/clicommands/NavigationCommand.php b/modules/migrate/application/clicommands/NavigationCommand.php
new file mode 100644
index 0000000..06fb2a8
--- /dev/null
+++ b/modules/migrate/application/clicommands/NavigationCommand.php
@@ -0,0 +1,195 @@
+<?php
+
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Migrate\Clicommands;
+
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Application\Logger;
+use Icinga\Cli\Command;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\NotWritableError;
+use Icinga\Module\Icingadb\Compat\UrlMigrator;
+use Icinga\Util\DirectoryIterator;
+use Icinga\Web\Request;
+use ipl\Web\Filter\QueryString;
+use ipl\Web\Url;
+
+class NavigationCommand extends Command
+{
+ /**
+ * Migrate local user monitoring navigation items to the Icinga DB Web actions
+ *
+ * USAGE
+ *
+ * icingacli migrate navigation [options]
+ *
+ * OPTIONS:
+ *
+ * --user=<username> Migrate monitoring navigation items only for
+ * the given user. (Default *)
+ *
+ * --delete Remove the legacy files after successfully
+ * migrated the navigation items.
+ */
+ public function indexAction()
+ {
+ $moduleManager = Icinga::app()->getModuleManager();
+ if (! $moduleManager->hasEnabled('icingadb')) {
+ Logger::error('Icinga DB module is not enabled. Please verify that the module is installed and enabled.');
+ return;
+ }
+
+ $preferencesPath = Config::resolvePath('preferences');
+ $sharedNavigation = Config::resolvePath('navigation');
+ if (! file_exists($preferencesPath) && ! file_exists($sharedNavigation)) {
+ Logger::info('There are no local user navigation items to migrate');
+ return;
+ }
+
+ $rc = 0;
+ $user = $this->params->get('user');
+ $directories = new DirectoryIterator($preferencesPath);
+
+ foreach ($directories as $directory) {
+ $username = $user;
+ if ($username !== null && $directories->key() !== $username) {
+ continue;
+ }
+
+ if ($username === null) {
+ $username = $directories->key();
+ }
+
+ $hostActions = $this->readFromIni($directory . '/host-actions.ini', $rc);
+ $serviceActions = $this->readFromIni($directory . '/service-actions.ini', $rc);
+
+ Logger::info('Migrating monitoring navigation items for user "%s" to the Icinga DB Web actions', $username);
+
+ if (! $hostActions->isEmpty()) {
+ $this->migrateNavigationItems($hostActions, $directory . '/icingadb-host-actions.ini', $rc);
+ }
+
+ if (! $serviceActions->isEmpty()) {
+ $this->migrateNavigationItems($serviceActions, $directory . '/icingadb-service-actions.ini', $rc);
+ }
+ }
+
+ // Start migrating shared navigation items
+ $hostActions = $this->readFromIni($sharedNavigation . '/host-actions.ini', $rc);
+ $serviceActions = $this->readFromIni($sharedNavigation . '/service-actions.ini', $rc);
+
+ Logger::info('Migrating shared monitoring navigation items to the Icinga DB Web actions');
+
+ if (! $hostActions->isEmpty()) {
+ $this->migrateNavigationItems($hostActions, $sharedNavigation . '/icingadb-host-actions.ini', $rc);
+ }
+
+ if (! $serviceActions->isEmpty()) {
+ $this->migrateNavigationItems($serviceActions, $sharedNavigation . '/icingadb-service-actions.ini', $rc);
+ }
+
+ if ($rc > 0) {
+ Logger::error('Failed to migrate some monitoring navigation items');
+ exit($rc);
+ }
+
+ Logger::info('Successfully migrated all local user monitoring navigation items');
+ }
+
+ /**
+ * Migrate the given config to the given new config path
+ *
+ * @param Config $config
+ * @param string $path
+ * @param int $rc
+ */
+ private function migrateNavigationItems($config, $path, &$rc)
+ {
+ $deleteLegacyFiles = $this->params->get('delete');
+ $newConfig = $this->readFromIni($path, $rc);
+ $counter = 1;
+
+ /** @var ConfigObject $configObject */
+ foreach ($config->getConfigObject() as $configObject) {
+ // Change the config type from "host-action" to icingadb's new action
+ if (strpos($path, 'icingadb-host-actions') !== false) {
+ $configObject->type = 'icingadb-host-action';
+ } else {
+ $configObject->type = 'icingadb-service-action';
+ }
+
+ $urlString = $configObject->get('url');
+ if ($urlString !== null) {
+ $url = Url::fromPath($urlString, [], new Request());
+
+ try {
+ $urlString = UrlMigrator::transformUrl($url)->getAbsoluteUrl();
+ $configObject->url = rawurldecode($urlString);
+ } catch (\InvalidArgumentException $err) {
+ // Do nothing
+ }
+ }
+
+ $legacyFilter = $configObject->get('filter');
+ if ($legacyFilter !== null) {
+ $filter = QueryString::parse($legacyFilter);
+ $filter = UrlMigrator::transformFilter($filter);
+ if ($filter !== false) {
+ $configObject->filter = rawurldecode(QueryString::render($filter));
+ } else {
+ unset($configObject->filter);
+ }
+ }
+
+ $section = $config->key();
+ while ($newConfig->hasSection($section)) {
+ $section = $config->key() . $counter++;
+ }
+
+ $newConfig->setSection($section, $configObject);
+ }
+
+ try {
+ if (! $newConfig->isEmpty()) {
+ $newConfig->saveIni();
+
+ // Remove the legacy file only if explicitly requested
+ if ($deleteLegacyFiles) {
+ unlink($config->getConfigFile());
+ }
+ }
+ } catch (NotWritableError $error) {
+ Logger::error('%s: %s', $error->getMessage(), $error->getPrevious()->getMessage());
+ $rc = 256;
+ }
+ }
+
+ /**
+ * Get the navigation items config from the given ini path
+ *
+ * @param string $path Absolute path of the ini file
+ * @param int $rc The return code used to exit the action
+ *
+ * @return Config
+ */
+ private function readFromIni($path, &$rc)
+ {
+ try {
+ $config = Config::fromIni($path);
+ } catch (NotReadableError $error) {
+ if ($error->getPrevious() !== null) {
+ Logger::error('%s: %s', $error->getMessage(), $error->getPrevious()->getMessage());
+ } else {
+ Logger::error($error->getMessage());
+ }
+
+ $config = new Config();
+ $rc = 128;
+ }
+
+ return $config;
+ }
+}
diff --git a/modules/migrate/application/clicommands/PreferencesCommand.php b/modules/migrate/application/clicommands/PreferencesCommand.php
new file mode 100644
index 0000000..11d1edb
--- /dev/null
+++ b/modules/migrate/application/clicommands/PreferencesCommand.php
@@ -0,0 +1,131 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Migrate\Clicommands;
+
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Cli\Command;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\NotReadableError;
+use Icinga\Exception\NotWritableError;
+use Icinga\File\Ini\IniParser;
+use Icinga\User;
+use Icinga\User\Preferences\PreferencesStore;
+use Icinga\Util\DirectoryIterator;
+
+class PreferencesCommand extends Command
+{
+ /**
+ * Migrate local INI user preferences to a database
+ *
+ * USAGE
+ *
+ * icingacli migrate preferences [options]
+ *
+ * OPTIONS:
+ *
+ * --resource=<resource-name> The resource to use, if no current database config backend is configured.
+ * --no-set-config-backend Do not set the given resource as config backend automatically
+ */
+ public function indexAction()
+ {
+ $resource = Config::app()->get('global', 'config_resource');
+ if (empty($resource)) {
+ $resource = $this->params->getRequired('resource');
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ $connection = ResourceFactory::createResource($resourceConfig);
+
+ $preferencesPath = Config::resolvePath('preferences');
+ if (! file_exists($preferencesPath)) {
+ Logger::info('There are no local user preferences to migrate');
+ return;
+ }
+
+ $rc = 0;
+
+ $preferenceDirs = new DirectoryIterator($preferencesPath);
+ foreach ($preferenceDirs as $preferenceDir) {
+ if (! is_dir($preferenceDir)) {
+ continue;
+ }
+
+ $userName = basename($preferenceDir);
+
+ Logger::info('Migrating INI preferences for user "%s" to database...', $userName);
+
+ $dbStore = new PreferencesStore(new ConfigObject(['connection' => $connection]), new User($userName));
+
+ try {
+ $dbStore->load();
+ $dbStore->save(
+ new User\Preferences(
+ $this->loadIniFile($preferencesPath, (new User($userName))->getUsername())
+ )
+ );
+ } catch (NotReadableError $e) {
+ if ($e->getPrevious() !== null) {
+ Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage());
+ } else {
+ Logger::error($e->getMessage());
+ }
+
+ $rc = 128;
+ } catch (NotWritableError $e) {
+ Logger::error('%s: %s', $e->getMessage(), $e->getPrevious()->getMessage());
+ $rc = 256;
+ }
+ }
+
+ if ($rc > 0) {
+ Logger::error('Failed to migrate some user preferences');
+ exit($rc);
+ }
+
+ if ($this->params->has('resource') && ! $this->params->has('no-set-config-backend')) {
+ $appConfig = Config::app();
+ $globalConfig = $appConfig->getSection('global');
+ $globalConfig['config_resource'] = $resource;
+
+ try {
+ $appConfig->saveIni();
+ } catch (NotWritableError $e) {
+ Logger::error('Failed to update general configuration: %s', $e->getMessage());
+ exit(256);
+ }
+ }
+
+ Logger::info('Successfully migrated all local user preferences to database');
+ }
+
+ private function loadIniFile(string $filePath, string $username): array
+ {
+ $preferences = [];
+ $preferencesFile = sprintf(
+ '%s/%s/config.ini',
+ $filePath,
+ strtolower($username)
+ );
+
+ if (file_exists($preferencesFile)) {
+ if (! is_readable($preferencesFile)) {
+ throw new NotReadableError(
+ 'Preferences INI file %s for user %s is not readable',
+ $preferencesFile,
+ $username
+ );
+ } else {
+ $preferences = IniParser::parseIniFile($preferencesFile)->toArray();
+ }
+ }
+
+ return $preferences;
+ }
+}
diff --git a/modules/migrate/library/Migrate/Config/UserDomainMigration.php b/modules/migrate/library/Migrate/Config/UserDomainMigration.php
new file mode 100644
index 0000000..855a0ab
--- /dev/null
+++ b/modules/migrate/library/Migrate/Config/UserDomainMigration.php
@@ -0,0 +1,378 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Migrate\Config;
+
+use Icinga\Application\Config;
+use Icinga\Data\Db\DbConnection;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\ResourceFactory;
+use Icinga\User;
+use Icinga\Util\DirectoryIterator;
+use Icinga\Util\StringHelper;
+use Icinga\Web\Announcement\AnnouncementIniRepository;
+
+class UserDomainMigration
+{
+ protected $toDomain;
+
+ protected $fromDomain;
+
+ protected $map;
+
+ public static function fromMap(array $map)
+ {
+ $static = new static();
+
+ $static->map = $map;
+
+ return $static;
+ }
+
+ public static function fromDomains($toDomain, $fromDomain = null)
+ {
+ $static = new static();
+
+ $static->toDomain = $toDomain;
+ $static->fromDomain = $fromDomain;
+
+ return $static;
+ }
+
+ protected function mustMigrate(User $user)
+ {
+ if ($user->getUsername() === '*') {
+ return false;
+ }
+
+ if ($this->map !== null) {
+ return isset($this->map[$user->getUsername()]);
+ }
+
+ if ($this->fromDomain !== null && $user->hasDomain() && $user->getDomain() !== $this->fromDomain) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function migrateUser(User $user)
+ {
+ $migrated = clone $user;
+
+ if ($this->map !== null) {
+ $migrated->setUsername($this->map[$user->getUsername()]);
+ } else {
+ $migrated->setDomain($this->toDomain);
+ }
+
+ return $migrated;
+ }
+
+ protected function migrateAnnounces()
+ {
+ $announces = new AnnouncementIniRepository();
+
+ $query = $announces->select(array('author'));
+
+ if ($this->map !== null) {
+ $query->where('author', array_keys($this->map));
+ }
+
+ $migratedUsers = array();
+
+ foreach ($announces->select(array('author')) as $announce) {
+ $user = new User($announce->author);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ if (isset($migratedUsers[$user->getUsername()])) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $announces->update(
+ 'announcement',
+ array('author' => $migrated->getUsername()),
+ Filter::where('author', $user->getUsername())
+ );
+
+ $migratedUsers[$user->getUsername()] = true;
+ }
+ }
+
+ protected function migrateDashboards()
+ {
+ $directory = Config::resolvePath('dashboards');
+
+ $migration = array();
+
+ if (DirectoryIterator::isReadable($directory)) {
+ foreach (new DirectoryIterator($directory) as $username => $path) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$path] = dirname($path) . '/' . $migrated->getUsername();
+ }
+
+ foreach ($migration as $from => $to) {
+ rename($from, $to);
+ }
+ }
+ }
+
+ protected function migrateNavigation()
+ {
+ $directory = Config::resolvePath('navigation');
+
+ foreach (new DirectoryIterator($directory, 'ini') as $file) {
+ $config = Config::fromIni($file);
+
+ foreach ($config as $navigation) {
+ $owner = $navigation->owner;
+
+ if (! empty($owner)) {
+ $user = new User($owner);
+
+ if ($this->mustMigrate($user)) {
+ $migrated = $this->migrateUser($user);
+
+ $navigation->owner = $migrated->getUsername();
+ }
+ }
+
+ $users = $navigation->users;
+
+ if (! empty($users)) {
+ $users = StringHelper::trimSplit($users);
+
+ foreach ($users as &$username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $username = $migrated->getUsername();
+ }
+
+ $navigation->users = implode(',', $users);
+ }
+ }
+
+ $config->saveIni();
+ }
+ }
+
+ protected function migratePreferences()
+ {
+ $config = Config::app();
+
+ $resourceConfig = ResourceFactory::getResourceConfig($config->get('global', 'config_resource'));
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ /** @var DbConnection $conn */
+ $conn = ResourceFactory::createResource($resourceConfig);
+
+ $query = $conn
+ ->select()
+ ->from('icingaweb_user_preference', array('username'))
+ ->group('username');
+
+ if ($this->map !== null) {
+ $query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map))));
+ }
+
+ $users = $query->fetchColumn();
+
+ $migration = array();
+
+ foreach ($users as $username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$username] = $migrated->getUsername();
+ }
+
+ if (! empty($migration)) {
+ $conn->getDbAdapter()->beginTransaction();
+
+ foreach ($migration as $originalUsername => $username) {
+ $conn->update(
+ 'icingaweb_user_preference',
+ array('username' => $username),
+ Filter::where('username', $originalUsername)
+ );
+ }
+
+ $conn->getDbAdapter()->commit();
+ }
+ }
+
+ protected function migrateRoles()
+ {
+ $roles = Config::app('roles');
+
+ foreach ($roles as $role) {
+ $users = $role->users;
+
+ if (empty($users)) {
+ continue;
+ }
+
+ $users = StringHelper::trimSplit($users);
+
+ foreach ($users as &$username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $username = $migrated->getUsername();
+ }
+
+ $role->users = implode(',', $users);
+ }
+
+ $roles->saveIni();
+ }
+
+ protected function migrateUsers()
+ {
+ foreach (Config::app('authentication') as $name => $config) {
+ if (strtolower($config->backend) !== 'db') {
+ continue;
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($config->resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ /** @var DbConnection $conn */
+ $conn = ResourceFactory::createResource($resourceConfig);
+
+ $query = $conn
+ ->select()
+ ->from('icingaweb_user', array('name'))
+ ->group('name');
+
+ if ($this->map !== null) {
+ $query->applyFilter(Filter::matchAny(Filter::where('name', array_keys($this->map))));
+ }
+
+ $users = $query->fetchColumn();
+
+ $migration = array();
+
+ foreach ($users as $username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$username] = $migrated->getUsername();
+ }
+
+ if (! empty($migration)) {
+ $conn->getDbAdapter()->beginTransaction();
+
+ foreach ($migration as $originalUsername => $username) {
+ $conn->update(
+ 'icingaweb_user',
+ array('name' => $username),
+ Filter::where('name', $originalUsername)
+ );
+ }
+
+ $conn->getDbAdapter()->commit();
+ }
+ }
+
+ foreach (Config::app('groups') as $name => $config) {
+ if (strtolower($config->backend) !== 'db') {
+ continue;
+ }
+
+ $resourceConfig = ResourceFactory::getResourceConfig($config->resource);
+ if ($resourceConfig->db === 'mysql') {
+ $resourceConfig->charset = 'utf8mb4';
+ }
+
+ /** @var DbConnection $conn */
+ $conn = ResourceFactory::createResource($resourceConfig);
+
+ $query = $conn
+ ->select()
+ ->from('icingaweb_group_membership', array('username'))
+ ->group('username');
+
+ if ($this->map !== null) {
+ $query->applyFilter(Filter::matchAny(Filter::where('username', array_keys($this->map))));
+ }
+
+ $users = $query->fetchColumn();
+
+ $migration = array();
+
+ foreach ($users as $username) {
+ $user = new User($username);
+
+ if (! $this->mustMigrate($user)) {
+ continue;
+ }
+
+ $migrated = $this->migrateUser($user);
+
+ $migration[$username] = $migrated->getUsername();
+ }
+
+ if (! empty($migration)) {
+ $conn->getDbAdapter()->beginTransaction();
+
+ foreach ($migration as $originalUsername => $username) {
+ $conn->update(
+ 'icingaweb_group_membership',
+ array('username' => $username),
+ Filter::where('username', $originalUsername)
+ );
+ }
+
+ $conn->getDbAdapter()->commit();
+ }
+ }
+ }
+
+ public function migrate()
+ {
+ $this->migrateAnnounces();
+ $this->migrateDashboards();
+ $this->migrateNavigation();
+ $this->migratePreferences();
+ $this->migrateRoles();
+ $this->migrateUsers();
+ }
+}
diff --git a/modules/migrate/module.info b/modules/migrate/module.info
new file mode 100644
index 0000000..6eb2911
--- /dev/null
+++ b/modules/migrate/module.info
@@ -0,0 +1,5 @@
+Module: migrate
+Version: 2.11.4
+Description: Migrate module
+ This module was introduced with the domain-aware authentication feature in version 2.5.0.
+ It helps you migrating users and user configurations according to a given domain.
diff --git a/modules/monitoring/application/clicommands/ListCommand.php b/modules/monitoring/application/clicommands/ListCommand.php
new file mode 100644
index 0000000..6dc4193
--- /dev/null
+++ b/modules/monitoring/application/clicommands/ListCommand.php
@@ -0,0 +1,400 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Clicommands;
+
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Cli\CliUtils;
+use Icinga\Date\DateFormatter;
+use Icinga\Cli\Command;
+use Icinga\File\Csv;
+use Icinga\Module\Monitoring\Plugin\PerfdataSet;
+use Exception;
+use Icinga\Util\Json;
+
+/**
+ * Icinga monitoring objects
+ *
+ * This module is your interface to the Icinga monitoring application.
+ */
+class ListCommand extends Command
+{
+ protected $backend;
+ protected $dumpSql;
+ protected $defaultActionName = 'status';
+
+ public function init()
+ {
+ $this->backend = MonitoringBackend::instance($this->params->shift('backend'));
+ $this->dumpSql = $this->params->shift('showsql');
+ }
+
+ protected function getQuery($table, $columns)
+ {
+ $limit = $this->params->shift('limit');
+ $format = $this->params->shift('format');
+ if ($format !== null) {
+ if ($this->params->has('columns')) {
+ $columnParams = preg_split(
+ '/,/',
+ $this->params->shift('columns')
+ );
+ $columns = array();
+ foreach ($columnParams as $col) {
+ if (false !== ($pos = strpos($col, '='))) {
+ $columns[substr($col, 0, $pos)] = substr($col, $pos + 1);
+ } else {
+ $columns[] = $col;
+ }
+ }
+ }
+ }
+
+ $query = $this->backend->select()->from($table, $columns);
+ if ($limit) {
+ $query->limit($limit, $this->params->shift('offset'));
+ }
+ foreach ($this->params->getParams() as $col => $filter) {
+ $query->where($col, $filter);
+ }
+ // $query->applyFilters($this->params->getParams());
+ if ($this->dumpSql) {
+ echo wordwrap($query->dump(), 72);
+ exit;
+ }
+
+ if ($format !== null) {
+ $this->showFormatted($query, $format, $columns);
+ }
+
+ return $query;
+ }
+
+ protected function showFormatted($query, $format, $columns)
+ {
+ $query = $query->getQuery();
+ switch ($format) {
+ case 'json':
+ echo Json::sanitize($query->fetchAll());
+ break;
+ case 'csv':
+ Csv::fromQuery($query)->dump();
+ break;
+ default:
+ preg_match_all('~\$([a-z0-9_-]+)\$~', $format, $m);
+ $words = array();
+ foreach ($columns as $key => $col) {
+ if (is_numeric($key)) {
+ if (in_array($col, $m[1])) {
+ $words[] = $col;
+ }
+ } else {
+ if (in_array($key, $m[1])) {
+ $words[] = $key;
+ }
+ }
+ }
+ foreach ($query->fetchAll() as $row) {
+ $output = $format;
+ foreach ($words as $word) {
+ $output = preg_replace(
+ '~\$' . $word . '\$~',
+ $row->{$word},
+ $output
+ );
+ }
+ echo $output . "\n";
+ }
+ }
+ exit;
+ }
+
+ /**
+ * List and filter hosts
+ *
+ * This command allows you to search and visualize your hosts in
+ * different ways.
+ *
+ * USAGE
+ *
+ * icingacli monitoring list hosts [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Show detailled output
+ * --showsql Dump generated SQL query (DB backend only)
+ *
+ * --format=<csv|json|<custom>>
+ * Dump columns in the given format. <custom> format allows $column$
+ * placeholders, e.g. --format='$host$: $service$'. This requires
+ * that the columns are specified within the --columns parameter.
+ *
+ * --<column>[=filter]
+ * Filter given column by optional filter. Boolean (1/0) columns are
+ * true if no filter value is given.
+ *
+ * --problems
+ * Only show unhandled problems (HARD state and not acknowledged/in downtime).
+ *
+ * --columns='<comma separated list of host/service columns>'
+ * Add a limited set of columns to the output. The following host
+ * attributes can be fetched: state, handled, output, acknowledged, in_downtime, perfdata last_state_change
+ *
+ * EXAMPLES
+ *
+ * icingacli monitoring list hosts --problems
+ * icingacli monitoring list hosts --problems --host_state_type 0
+ * icingacli monitoring list hosts --host=local*
+ * icingacli monitoring list hosts --columns 'host,host_output' \
+ * --format='$host$ ($host_output$)'
+ */
+ public function hostsAction()
+ {
+ $columns = array(
+ 'host_name',
+ 'host_state',
+ 'host_output',
+ 'host_handled',
+ 'host_acknowledged',
+ 'host_in_downtime'
+ );
+ $query = $this->getQuery('hoststatus', $columns)
+ ->order('host_name');
+ echo $this->renderStatusQuery($query);
+ }
+
+ /**
+ * List and filter services
+ *
+ * This command allows you to search and visualize your services in
+ * different ways.
+ *
+ * USAGE
+ *
+ * icingacli monitoring list services [options]
+ *
+ * OPTIONS
+ *
+ * --verbose Show detailled output
+ * --showsql Dump generated SQL query (DB backend only)
+ *
+ * --format=<csv|json|<custom>>
+ * Dump columns in the given format. <custom> format allows $column$
+ * placeholders, e.g. --format='$host$: $service$'. This requires
+ * that the columns are specified within the --columns parameter.
+ *
+ * --<column>[=filter]
+ * Filter given column by optional filter. Boolean (1/0) columns are
+ * true if no filter value is given.
+ *
+ * --problems
+ * Only show unhandled problems (HARD state and not acknowledged/in downtime).
+ *
+ * --columns='<comma separated list of host/service columns>'
+ * Add a limited set of columns to the output. The following service
+ * attributes can be fetched: state, handled, output, acknowledged, in_downtime, perfdata last_state_change
+ *
+ * EXAMPLES
+ *
+ * icingacli monitoring list services --problems
+ * icingacli monitoring list services --problems --service_state_type 0
+ * icingacli monitoring list services --host=local* --service=*disk*
+ * icingacli monitoring list services --columns 'host,service,service_output' \
+ * --format='$host$: $service$ ($service_output$)'
+ */
+ public function servicesAction()
+ {
+ $columns = array(
+ 'host_name',
+ 'host_state',
+ 'host_output',
+ 'host_handled',
+ 'host_acknowledged',
+ 'host_in_downtime',
+ 'service_description',
+ 'service_state',
+ 'service_acknowledged',
+ 'service_in_downtime',
+ 'service_handled',
+ 'service_output',
+ 'service_perfdata',
+ 'service_last_state_change'
+ );
+ $query = $this->getQuery('servicestatus', $columns)
+ ->order('host_name');
+ echo $this->renderStatusQuery($query);
+ }
+
+ protected function renderStatusQuery($query)
+ {
+ $out = '';
+ $last_host = null;
+ $screen = $this->screen;
+ $utils = new CliUtils($screen);
+ $maxCols = $screen->getColumns();
+ $query = $query->getQuery();
+ $rows = $query->fetchAll();
+ $count = $query->count();
+ $count = count($rows);
+
+ for ($i = 0; $i < $count; $i++) {
+ $row = & $rows[$i];
+
+ $utils->setHostState($row->host_state);
+ if (! array_key_exists($i + 1, $rows)
+ || $row->host_name !== $rows[$i + 1]->host_name
+ ) {
+ $lastService = true;
+ } else {
+ $lastService = false;
+ }
+
+ $hostUnhandled = ! ($row->host_state == 0 || $row->host_handled);
+
+ if ($row->host_name !== $last_host) {
+ if (isset($row->service_description)) {
+ $out .= "\n";
+ }
+
+ $hostTxt = $utils->shortHostState();
+ if ($hostUnhandled) {
+ $out .= $utils->hostStateBackground(
+ sprintf(' %s ', $utils->shortHostState())
+ );
+ } else {
+ $out .= sprintf(
+ '%s %s ',
+ $utils->hostStateBackground(' '),
+ $utils->shortHostState()
+ );
+ }
+ $out .= sprintf(
+ " %s%s: %s\n",
+ $screen->underline($row->host_name),
+ $screen->colorize($utils->objectStateFlags('host', $row), 'lightblue'),
+ $row->host_output
+ );
+
+ if (isset($row->services_ok)) {
+ $out .= sprintf(
+ "%d services, %d problems (%d unhandled), %d OK\n",
+ $row->services_cnt,
+ $row->services_problem,
+ $row->services_problem_unhandled,
+ $row->services_ok
+ );
+ }
+ }
+
+ $last_host = $row->host_name;
+ if (! isset($row->service_description)) {
+ continue;
+ }
+
+ $utils->setServiceState($row->service_state);
+ $serviceUnhandled = ! (
+ $row->service_state == 0 || $row->service_handled
+ );
+
+ if ($lastService) {
+ $straight = ' ';
+ $leaf = 'â””';
+ } else {
+ $straight = '│';
+ $leaf = '├';
+ }
+ $out .= $utils->hostStateBackground(' ');
+
+ if ($serviceUnhandled) {
+ $out .= $utils->serviceStateBackground(
+ sprintf(' %s ', $utils->shortServiceState())
+ );
+ $emptyBg = ' ';
+ $emptySpace = '';
+ } else {
+ $out .= sprintf(
+ '%s %s ',
+ $utils->serviceStateBackground(' '),
+ $utils->shortServiceState()
+ );
+ $emptyBg = ' ';
+ $emptySpace = ' ';
+ }
+
+ $emptyLine = "\n"
+ . $utils->hostStateBackground(' ')
+ . $utils->serviceStateBackground($emptyBg)
+ . $emptySpace
+ . ' ' . $straight . ' ';
+
+ $perf = '';
+ try {
+ $pset = PerfdataSet::fromString($row->service_perfdata);
+ $perfs = array();
+ foreach ($pset as $p) {
+ if ($percent = $p->getPercentage()) {
+ if ($percent < 0 || $percent > 100) {
+ continue;
+ }
+ $perfs[] = ' '
+ . $p->getLabel()
+ . ': '
+ . $this->getPercentageSign($percent)
+ . ' '
+ . number_format($percent, 2, ',', '.')
+ . '%';
+ }
+ }
+ if (! empty($perfs)) {
+ $perf = ', ' . implode($perfs);
+ }
+ // TODO: fix wordwarp, then remove this line:
+ $perf = '';
+ } catch (Exception $e) {
+ // Ignoring perfdata errors right now, we could show some hint
+ }
+
+ $wrappedOutput = wordwrap(
+ preg_replace('~\@{3,}~', '@@@', $row->service_output),
+ $maxCols - 13
+ ) . "\n";
+ $out .= sprintf(
+ " %1s─ %s%s (%s)",
+ $leaf,
+ $screen->underline($row->service_description),
+ $screen->colorize($utils->objectStateFlags('service', $row) . $perf, 'lightblue'),
+ ucfirst(DateFormatter::timeSince($row->service_last_state_change))
+ );
+ if ($this->isVerbose) {
+ $out .= $emptyLine . preg_replace(
+ '/\n/',
+ $emptyLine,
+ $wrappedOutput
+ ) . "\n";
+ } else {
+ $out .= "\n";
+ }
+ }
+
+ $out .= "\n";
+ return $out;
+ }
+
+ protected function getPercentageSign($percent)
+ {
+ $circles = array(
+ 0 => 'â—‹',
+ 15 => 'â—”',
+ 40 => 'â—‘',
+ 65 => 'â—•',
+ 90 => 'â—',
+ );
+ $last = $circles[0];
+ foreach ($circles as $cur => $circle) {
+ if ($percent < $cur) {
+ return $last;
+ }
+ $last = $circle;
+ }
+ }
+}
diff --git a/modules/monitoring/application/clicommands/NrpeCommand.php b/modules/monitoring/application/clicommands/NrpeCommand.php
new file mode 100644
index 0000000..fe82322
--- /dev/null
+++ b/modules/monitoring/application/clicommands/NrpeCommand.php
@@ -0,0 +1,58 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Clicommands;
+
+use Icinga\Protocol\Nrpe\Connection;
+use Icinga\Cli\Command;
+use Exception;
+
+/**
+ * NRPE
+ */
+class NrpeCommand extends Command
+{
+ protected $defaultActionName = 'check';
+
+ /**
+ * Execute an NRPE command
+ *
+ * This command will execute an NRPE check, fire it against the given host
+ * and also pass through all your parameters. Output will be shown, exit
+ * code respected.
+ *
+ * USAGE
+ *
+ * icingacli monitoring nrpe <host> <command> [--ssl] [nrpe options]
+ *
+ * EXAMPLE
+ *
+ * icingacli monitoring nrpe 127.0.0.1 CheckMEM --ssl --MaxWarn=80% \
+ * --MaxCrit=90% --type=physical
+ */
+ public function checkAction()
+ {
+ $host = $this->params->shift();
+ if (! $host) {
+ echo $this->showUsage();
+ exit(3);
+ }
+ $command = $this->params->shift(null, '_NRPE_CHECK');
+ $port = $this->params->shift('port', 5666);
+ try {
+ $nrpe = new Connection($host, $port);
+ if ($this->params->shift('ssl')) {
+ $nrpe->useSsl();
+ }
+ $args = array();
+ foreach ($this->params->getParams() as $k => $v) {
+ $args[] = $k . '=' . $v;
+ }
+ echo $nrpe->sendCommand($command, $args) . "\n";
+ exit($nrpe->getLastReturnCode());
+ } catch (Exception $e) {
+ echo $e->getMessage() . "\n";
+ exit(3);
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/ActionsController.php b/modules/monitoring/application/controllers/ActionsController.php
new file mode 100644
index 0000000..bc13e21
--- /dev/null
+++ b/modules/monitoring/application/controllers/ActionsController.php
@@ -0,0 +1,135 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimesCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm;
+use Icinga\Module\Monitoring\Object\HostList;
+use Icinga\Module\Monitoring\Object\ServiceList;
+
+/**
+ * Monitoring API
+ */
+class ActionsController extends Controller
+{
+ /**
+ * Get the filter from URL parameters or exit immediately if the filter is empty
+ *
+ * @return Filter
+ */
+ protected function getFilterOrExitIfEmpty()
+ {
+ $filter = Filter::fromQueryString((string) $this->params);
+ if ($filter->isEmpty()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'Filter is required and must not be empty'))
+ ->sendResponse();
+ }
+ return $filter;
+ }
+
+ /**
+ * Schedule host downtimes
+ */
+ public function scheduleHostDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $hostList = new HostList($this->backend);
+ $hostList
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $hostList->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No hosts found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new ScheduleHostDowntimeCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setBackend($this->backend)
+ ->setObjects($hostList->fetch())
+ ->handleRequest($this->getRequest());
+ }
+
+ /**
+ * Remove host downtimes
+ */
+ public function removeHostDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $downtimes = $this->backend
+ ->select()
+ ->from('downtime', array('host_name', 'id' => 'downtime_internal_id', 'name' => 'downtime_name'))
+ ->where('object_type', 'host')
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $downtimes->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No downtimes found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new DeleteDowntimesCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setDowntimes($downtimes->fetchAll())
+ ->handleRequest($this->getRequest());
+ // @TODO(el): Respond w/ the downtimes deleted instead of the notifiaction added by
+ // DeleteDowntimesCommandForm::onSuccess().
+ }
+
+ /**
+ * Schedule service downtimes
+ */
+ public function scheduleServiceDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $serviceList = new ServiceList($this->backend);
+ $serviceList
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $serviceList->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No services found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new ScheduleServiceDowntimeCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setBackend($this->backend)
+ ->setObjects($serviceList->fetch())
+ ->handleRequest($this->getRequest());
+ }
+
+ /**
+ * Remove service downtimes
+ */
+ public function removeServiceDowntimeAction()
+ {
+ $filter = $this->getFilterOrExitIfEmpty();
+ $downtimes = $this->backend
+ ->select()
+ ->from(
+ 'downtime',
+ array('host_name', 'service_description', 'id' => 'downtime_internal_id', 'name' => 'downtime_name')
+ )
+ ->where('object_type', 'service')
+ ->applyFilter($this->getRestriction('monitoring/filter/objects'))
+ ->applyFilter($filter);
+ if (! $downtimes->count()) {
+ $this->getResponse()->json()
+ ->setFailData(array('filter' => 'No downtimes found matching the filter'))
+ ->sendResponse();
+ }
+ $form = new DeleteDowntimesCommandForm();
+ $form
+ ->setIsApiTarget(true)
+ ->setDowntimes($downtimes->fetchAll())
+ ->handleRequest($this->getRequest());
+ // @TODO(el): Respond w/ the downtimes deleted instead of the notifiaction added by
+ // DeleteDowntimesCommandForm::onSuccess().
+ }
+}
diff --git a/modules/monitoring/application/controllers/CommentController.php b/modules/monitoring/application/controllers/CommentController.php
new file mode 100644
index 0000000..e50473f
--- /dev/null
+++ b/modules/monitoring/application/controllers/CommentController.php
@@ -0,0 +1,91 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Application\Hook;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Display detailed information about a comment
+ */
+class CommentController extends Controller
+{
+ /**
+ * The fetched comment
+ *
+ * @var object
+ */
+ protected $comment;
+
+ /**
+ * Fetch the first comment with the given id and add tabs
+ */
+ public function init()
+ {
+ $commentId = $this->params->getRequired('comment_id');
+
+ $query = $this->backend->select()->from('comment', array(
+ 'id' => 'comment_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'comment_data',
+ 'author' => 'comment_author_name',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type',
+ 'persistent' => 'comment_is_persistent',
+ 'expiration' => 'comment_expiration',
+ 'name' => 'comment_name',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->where('comment_internal_id', $commentId);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ if (false === $this->comment = $query->fetchRow()) {
+ $this->httpNotFound($this->translate('Comment not found'));
+ }
+
+ $this->getTabs()->add(
+ 'comment',
+ array(
+ 'icon' => 'comment-empty',
+ 'label' => $this->translate('Comment'),
+ 'title' => $this->translate('Display detailed information about a comment.'),
+ 'url' =>'monitoring/comments/show'
+ )
+ )->activate('comment')->extend(new DashboardAction())->extend(new MenuAction());
+
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ /**
+ * Display comment detail view
+ */
+ public function showAction()
+ {
+ $this->view->comment = $this->comment;
+ $this->view->title = $this->translate('Comments');
+
+ if ($this->hasPermission('monitoring/command/comment/delete')) {
+ $listUrl = Url::fromPath('monitoring/list/comments')
+ ->setQueryString('comment_type=comment|comment_type=ack');
+ $form = new DeleteCommentCommandForm();
+ $form
+ ->populate(array(
+ 'comment_id' => $this->comment->id,
+ 'comment_is_service' => isset($this->comment->service_description),
+ 'comment_name' => $this->comment->name,
+ 'redirect' => $listUrl
+ ))
+ ->handleRequest();
+ $this->view->delCommentForm = $form;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/CommentsController.php b/modules/monitoring/application/controllers/CommentsController.php
new file mode 100644
index 0000000..9de19a0
--- /dev/null
+++ b/modules/monitoring/application/controllers/CommentsController.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentsCommandForm;
+use Icinga\Web\Url;
+
+/**
+ * Display detailed information about comments
+ */
+class CommentsController extends Controller
+{
+ /**
+ * The comments view
+ *
+ * @var \Icinga\Module\Monitoring\DataView\Comment
+ */
+ protected $comments;
+
+ /**
+ * Filter from request
+ *
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * Fetch all comments matching the current filter and add tabs
+ */
+ public function init()
+ {
+ $this->filter = Filter::fromQueryString(str_replace(
+ 'comment_id',
+ 'comment_internal_id',
+ (string) $this->params
+ ));
+ $query = $this->backend->select()->from('comment', array(
+ 'id' => 'comment_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'comment_data',
+ 'author' => 'comment_author_name',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type',
+ 'persistent' => 'comment_is_persistent',
+ 'expiration' => 'comment_expiration',
+ 'name' => 'comment_name',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->addFilter($this->filter);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ $this->comments = $query;
+
+ $this->view->title = $this->translate('Comments');
+ $this->getTabs()->add(
+ 'comments',
+ array(
+ 'icon' => 'comment-empty',
+ 'label' => $this->translate('Comments') . sprintf(' (%d)', $query->count()),
+ 'title' => $this->translate(
+ 'Display detailed information about multiple comments.'
+ ),
+ 'url' =>'monitoring/comments/show'
+ )
+ )->activate('comments');
+ }
+
+ /**
+ * Display the detail view for a comment list
+ */
+ public function showAction()
+ {
+ $this->view->comments = $this->comments;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/comments')
+ ->setQueryString($this->filter->toQueryString());
+ $this->view->removeAllLink = Url::fromPath('monitoring/comments/delete-all')
+ ->setParams($this->params);
+ }
+
+ /**
+ * Display the form for removing a comment list
+ */
+ public function deleteAllAction()
+ {
+ $this->assertPermission('monitoring/command/comment/delete');
+
+ $listCommentsLink = Url::fromPath('monitoring/list/comments')
+ ->setQueryString('comment_type=(comment|ack)');
+ $delCommentForm = new DeleteCommentsCommandForm();
+ $delCommentForm->setTitle($this->view->translate('Remove all Comments'));
+ $delCommentForm->addDescription(sprintf(
+ $this->translate('Confirm removal of %d comments.'),
+ $this->comments->count()
+ ));
+ $delCommentForm->setComments($this->comments->fetchAll())
+ ->setRedirectUrl($listCommentsLink)
+ ->handleRequest();
+ $this->view->delCommentForm = $delCommentForm;
+ $this->view->comments = $this->comments;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/comments')
+ ->setQueryString($this->filter->toQueryString());
+ }
+}
diff --git a/modules/monitoring/application/controllers/ConfigController.php b/modules/monitoring/application/controllers/ConfigController.php
new file mode 100644
index 0000000..b8ca0a1
--- /dev/null
+++ b/modules/monitoring/application/controllers/ConfigController.php
@@ -0,0 +1,298 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Exception;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfirmRemovalForm;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Forms\Config\TransportReorderForm;
+use Icinga\Web\Controller;
+use Icinga\Web\Notification;
+use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm;
+use Icinga\Module\Monitoring\Forms\Config\SecurityConfigForm;
+use Icinga\Module\Monitoring\Forms\Config\TransportConfigForm;
+
+/**
+ * Configuration controller for editing monitoring resources
+ */
+class ConfigController extends Controller
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->assertPermission('config/modules');
+ $this->view->title = $this->translate('Backends');
+ $this->view->defaultTitle = 'monitoring :: ' . $this->view->defaultTitle;
+ parent::init();
+ }
+
+ /**
+ * Display a list of available backends and command transports
+ */
+ public function indexAction()
+ {
+ $this->view->commandTransportReorderForm = $form = new TransportReorderForm();
+ $form->handleRequest();
+
+ $this->view->backendsConfig = $this->Config('backends');
+ $this->view->tabs = $this->Module()->getConfigTabs()->activate('backends');
+ }
+
+ /**
+ * Edit a monitoring backend
+ */
+ public function editbackendAction()
+ {
+ $backendName = $this->params->getRequired('backend-name');
+
+ $form = new BackendConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Edit Monitoring Backend %s'), $backendName));
+ $form->setIniConfig($this->Config('backends'));
+ $form->setResourceConfig(ResourceFactory::getResourceConfigs());
+ $form->setOnSuccess(function (BackendConfigForm $form) use ($backendName) {
+ try {
+ $form->edit($backendName, array_map(
+ function ($v) {
+ return $v !== '' ? $v : null;
+ },
+ $form->getValues()
+ ));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(sprintf(t('Monitoring backend "%s" successfully updated'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($backendName);
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Monitoring backend "%s" not found'), $backendName));
+ }
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Create a new monitoring backend
+ */
+ public function createbackendAction()
+ {
+ $form = new BackendConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle($this->translate('Create New Monitoring Backend'));
+ $form->setIniConfig($this->Config('backends'));
+
+ try {
+ $form->setResourceConfig(ResourceFactory::getResourceConfigs());
+ } catch (ConfigurationError $e) {
+ if ($this->hasPermission('config/resources')) {
+ Notification::error($e->getMessage());
+ $this->redirectNow('config/createresource');
+ }
+
+ throw $e; // No permission for resource configuration, show the error
+ }
+
+ $form->setOnSuccess(function (BackendConfigForm $form) {
+ try {
+ $form->add($form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(t('Monitoring backend successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Display a confirmation form to remove the backend identified by the 'backend' parameter
+ */
+ public function removebackendAction()
+ {
+ $backendName = $this->params->getRequired('backend-name');
+
+ $backendForm = new BackendConfigForm();
+ $backendForm->setIniConfig($this->Config('backends'));
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Remove Monitoring Backend %s'), $backendName));
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($backendName, $backendForm) {
+ try {
+ $backendForm->delete($backendName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($backendForm->save()) {
+ Notification::success(sprintf(t('Monitoring backend "%s" successfully removed'), $backendName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Remove a command transport
+ */
+ public function removetransportAction()
+ {
+ $transportName = $this->params->getRequired('transport');
+
+ $transportForm = new TransportConfigForm();
+ $transportForm->setIniConfig($this->Config('commandtransports'));
+ $form = new ConfirmRemovalForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Remove Command Transport %s'), $transportName));
+ $form->info(
+ $this->translate(
+ 'If you still have any environments or views referring to this transport, '
+ . 'you won\'t be able to send commands anymore after deletion.'
+ ),
+ false
+ );
+ $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($transportName, $transportForm) {
+ try {
+ $transportForm->delete($transportName);
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($transportForm->save()) {
+ Notification::success(sprintf(t('Command transport "%s" successfully removed'), $transportName));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Edit a command transport
+ */
+ public function edittransportAction()
+ {
+ $transportName = $this->params->getRequired('transport');
+
+ $form = new TransportConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle(sprintf($this->translate('Edit Command Transport %s'), $transportName));
+ $form->setIniConfig($this->Config('commandtransports'));
+ $form->setInstanceNames(
+ MonitoringBackend::instance()->select()->from('instance', array('instance_name'))->fetchColumn()
+ );
+ $form->setOnSuccess(function (TransportConfigForm $form) use ($transportName) {
+ try {
+ $form->edit($transportName, array_map(
+ function ($v) {
+ return $v !== '' ? $v : null;
+ },
+ $form->getValues()
+ ));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(sprintf(t('Command transport "%s" successfully updated'), $transportName));
+ return true;
+ }
+
+ return false;
+ });
+
+ try {
+ $form->load($transportName);
+ $form->handleRequest();
+ } catch (NotFoundError $_) {
+ $this->httpNotFound(sprintf($this->translate('Command transport "%s" not found'), $transportName));
+ }
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Create a new command transport
+ */
+ public function createtransportAction()
+ {
+ $form = new TransportConfigForm();
+ $form->setRedirectUrl('monitoring/config');
+ $form->setTitle($this->translate('Create New Command Transport'));
+ $form->setIniConfig($this->Config('commandtransports'));
+ $form->setInstanceNames(
+ MonitoringBackend::instance()->select()->from('instance', array('instance_name'))->fetchColumn()
+ );
+ $form->setOnSuccess(function (TransportConfigForm $form) {
+ try {
+ $form->add($form::transformEmptyValuesToNull($form->getValues()));
+ } catch (Exception $e) {
+ $form->error($e->getMessage());
+ return false;
+ }
+
+ if ($form->save()) {
+ Notification::success(t('Command transport successfully created'));
+ return true;
+ }
+
+ return false;
+ });
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->render('form');
+ }
+
+ /**
+ * Display a form to adjust security relevant settings
+ */
+ public function securityAction()
+ {
+ $form = new SecurityConfigForm();
+ $form->setIniConfig($this->Config());
+ $form->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->title = $this->translate('Security');
+ $this->view->tabs = $this->Module()->getConfigTabs()->activate('security');
+ }
+}
diff --git a/modules/monitoring/application/controllers/DowntimeController.php b/modules/monitoring/application/controllers/DowntimeController.php
new file mode 100644
index 0000000..83c03dd
--- /dev/null
+++ b/modules/monitoring/application/controllers/DowntimeController.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Application\Hook;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Display detailed information about a downtime
+ */
+class DowntimeController extends Controller
+{
+ /**
+ * The fetched downtime
+ *
+ * @var object
+ */
+ protected $downtime;
+
+ /**
+ * Fetch the downtime matching the given id and add tabs
+ */
+ public function init()
+ {
+ $downtimeId = $this->params->getRequired('downtime_id');
+
+ $query = $this->backend->select()->from('downtime', array(
+ 'id' => 'downtime_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'downtime_comment',
+ 'author_name' => 'downtime_author_name',
+ 'start' => 'downtime_start',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'end' => 'downtime_end',
+ 'duration' => 'downtime_duration',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'entry_time' => 'downtime_entry_time',
+ 'name' => 'downtime_name',
+ 'host_state',
+ 'service_state',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->where('downtime_internal_id', $downtimeId);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ if (false === $this->downtime = $query->fetchRow()) {
+ $this->httpNotFound($this->translate('Downtime not found'));
+ }
+
+ $this->getTabs()->add(
+ 'downtime',
+ array(
+
+ 'icon' => 'plug',
+ 'label' => $this->translate('Downtime'),
+ 'title' => $this->translate('Display detailed information about a downtime.'),
+ 'url' =>'monitoring/downtimes/show'
+ )
+ )->activate('downtime')->extend(new DashboardAction())->extend(new MenuAction());
+
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ /**
+ * Display the detail view for a downtime
+ */
+ public function showAction()
+ {
+ $isService = isset($this->downtime->service_description);
+ $this->view->downtime = $this->downtime;
+ $this->view->isService = $isService;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes');
+ $this->view->showHostLink = Url::fromPath('monitoring/host/show')->setParam('host', $this->downtime->host_name);
+ $this->view->showServiceLink = Url::fromPath('monitoring/service/show')
+ ->setParam('host', $this->downtime->host_name)
+ ->setParam('service', $this->downtime->service_description);
+ $this->view->stateName = $isService ? Service::getStateText($this->downtime->service_state)
+ : Host::getStateText($this->downtime->host_state);
+
+ $this->view->title = $this->translate('Downtimes');
+ if ($this->hasPermission('monitoring/command/downtime/delete')) {
+ $form = new DeleteDowntimeCommandForm();
+ $form
+ ->populate(array(
+ 'downtime_id' => $this->downtime->id,
+ 'downtime_is_service' => $isService,
+ 'downtime_name' => $this->downtime->name,
+ 'redirect' => Url::fromPath('monitoring/list/downtimes'),
+ ))
+ ->handleRequest();
+ $this->view->delDowntimeForm = $form;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/DowntimesController.php b/modules/monitoring/application/controllers/DowntimesController.php
new file mode 100644
index 0000000..4891203
--- /dev/null
+++ b/modules/monitoring/application/controllers/DowntimesController.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimesCommandForm;
+use Icinga\Web\Url;
+
+/**
+ * Display detailed information about downtimes
+ */
+class DowntimesController extends Controller
+{
+ /**
+ * The downtimes view
+ *
+ * @var \Icinga\Module\Monitoring\DataView\Downtime
+ */
+ protected $downtimes;
+
+ /**
+ * Filter from request
+ *
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * Fetch all downtimes matching the current filter and add tabs
+ */
+ public function init()
+ {
+ $this->filter = Filter::fromQueryString(str_replace(
+ 'downtime_id',
+ 'downtime_internal_id',
+ (string) $this->params
+ ));
+ $query = $this->backend->select()->from('downtime', array(
+ 'id' => 'downtime_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'downtime_comment',
+ 'author_name' => 'downtime_author_name',
+ 'start' => 'downtime_start',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'end' => 'downtime_end',
+ 'duration' => 'downtime_duration',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'entry_time' => 'downtime_entry_time',
+ 'name' => 'downtime_name',
+ 'host_state',
+ 'service_state',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ))->addFilter($this->filter);
+ $this->applyRestriction('monitoring/filter/objects', $query);
+
+ $this->downtimes = $query;
+
+ $this->view->title = $this->translate('Downtimes');
+ $this->getTabs()->add(
+ 'downtimes',
+ array(
+ 'icon' => 'plug',
+ 'label' => $this->translate('Downtimes') . sprintf(' (%d)', $query->count()),
+ 'title' => $this->translate('Display detailed information about multiple downtimes.'),
+ 'url' =>'monitoring/downtimes/show'
+ )
+ )->activate('downtimes');
+ }
+
+ /**
+ * Display the detail view for a downtime list
+ */
+ public function showAction()
+ {
+ $this->view->downtimes = $this->downtimes;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes')
+ ->setQueryString($this->filter->toQueryString());
+ $this->view->removeAllLink = Url::fromPath('monitoring/downtimes/delete-all')->setParams($this->params);
+ }
+
+ /**
+ * Display the form for removing a downtime list
+ */
+ public function deleteAllAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/delete');
+ $this->view->downtimes = $this->downtimes;
+ $this->view->listAllLink = Url::fromPath('monitoring/list/downtimes')
+ ->setQueryString($this->filter->toQueryString());
+ $delDowntimeForm = new DeleteDowntimesCommandForm();
+ $delDowntimeForm->setTitle($this->view->translate('Remove all Downtimes'));
+ $delDowntimeForm->addDescription(sprintf(
+ $this->translate('Confirm removal of %d downtimes.'),
+ $this->downtimes->count()
+ ));
+ $delDowntimeForm->setRedirectUrl(Url::fromPath('monitoring/list/downtimes'));
+ $delDowntimeForm->setDowntimes($this->downtimes->fetchAll())->handleRequest();
+ $this->view->delAllDowntimeForm = $delDowntimeForm;
+ }
+}
diff --git a/modules/monitoring/application/controllers/EventController.php b/modules/monitoring/application/controllers/EventController.php
new file mode 100644
index 0000000..08ab1bc
--- /dev/null
+++ b/modules/monitoring/application/controllers/EventController.php
@@ -0,0 +1,551 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use DateTime;
+use DateTimeZone;
+use Icinga\Module\Monitoring\Hook\EventDetailsExtensionHook;
+use Icinga\Application\Hook;
+use InvalidArgumentException;
+use Icinga\Data\Queryable;
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Util\TimezoneDetect;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+
+class EventController extends Controller
+{
+ /**
+ * @var string[]
+ */
+ protected $dataViewsByType = array(
+ 'notify' => 'notificationevent',
+ 'comment' => 'commentevent',
+ 'comment_deleted' => 'commentevent',
+ 'ack' => 'commentevent',
+ 'ack_deleted' => 'commentevent',
+ 'dt_comment' => 'commentevent',
+ 'dt_comment_deleted' => 'commentevent',
+ 'flapping' => 'flappingevent',
+ 'flapping_deleted' => 'flappingevent',
+ 'hard_state' => 'statechangeevent',
+ 'soft_state' => 'statechangeevent',
+ 'dt_start' => 'downtimeevent',
+ 'dt_end' => 'downtimeevent'
+ );
+
+ public function init()
+ {
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ }
+
+ public function showAction()
+ {
+ $type = $this->params->shiftRequired('type');
+ $id = $this->params->shiftRequired('id');
+
+ if (! isset($this->dataViewsByType[$type])
+ || $this->applyRestriction(
+ 'monitoring/filter/objects',
+ $this->backend->select()->from('eventhistory', array('id'))->where('id', $id)
+ )->fetchRow() === false
+ ) {
+ $this->httpNotFound($this->translate('Event not found'));
+ }
+
+ $event = $this->query($type, $id)->fetchRow();
+
+ if ($event === false) {
+ $this->httpNotFound($this->translate('Event not found'));
+ }
+
+ $this->view->object = $object = $event->service_description === null
+ ? new Host($this->backend, $event->host_name)
+ : new Service($this->backend, $event->host_name, $event->service_description);
+ $object->fetch();
+
+ list($icon, $label) = $this->getIconAndLabel($type);
+
+ $this->view->details = array_merge(
+ array(array($this->view->escape($this->translate('Type')), $label)),
+ $this->getDetails($type, $event)
+ );
+
+ $this->view->extensionsHtml = array();
+ /** @var EventDetailsExtensionHook $hook */
+ foreach (Hook::all('Monitoring\\EventDetailsExtension') as $hook) {
+ try {
+ $html = $hook->getHtmlForEvent($event);
+ } catch (\Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+
+ $this->view->title = $this->translate('Event Overview');
+ $this->getTabs()
+ ->add('event', array(
+ 'title' => $label,
+ 'label' => $label,
+ 'url' => Url::fromRequest(),
+ 'active' => true
+ ))
+ ->extend(new OutputFormat())
+ ->extend(new DashboardAction())
+ ->extend(new MenuAction());
+ }
+
+ /**
+ * Return translated and escaped 'Yes' if the given condition is true, 'No' otherwise, 'N/A' if NULL
+ *
+ * @param bool|null $condition
+ *
+ * @return string
+ */
+ protected function yesOrNo($condition)
+ {
+ if ($condition === null) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ return $this->view->escape($condition ? $this->translate('Yes') : $this->translate('No'));
+ }
+
+ /**
+ * Render the given duration in seconds as human readable HTML or 'N/A' if NULL
+ *
+ * @param int|null $seconds
+ *
+ * @return string
+ */
+ protected function duration($seconds)
+ {
+ return $this->view->escape(
+ $seconds === null ? $this->translate('N/A') : DateFormatter::formatDuration($seconds)
+ );
+ }
+
+ /**
+ * Render the given percent number as human readable HTML or 'N/A' if NULL
+ *
+ * @param float|null $percent
+ *
+ * @return string
+ */
+ protected function percent($percent)
+ {
+ return $this->view->escape(
+ $percent === null ? $this->translate('N/A') : sprintf($this->translate('%.2f%%'), $percent)
+ );
+ }
+
+ /**
+ * Render the given comment message as HTML or 'N/A' if NULL
+ *
+ * @param string|null $message
+ *
+ * @return string
+ */
+ protected function comment($message)
+ {
+ return $this->view->nl2br($this->view->createTicketLinks($this->view->markdown($message)));
+ }
+
+ /**
+ * Render a link to the given contact or 'N/A' if NULL
+ *
+ * @param string|null $name
+ *
+ * @return string
+ */
+ protected function contact($name)
+ {
+ return $name === null
+ ? $this->view->escape($this->translate('N/A'))
+ : $this->view->qlink($name, Url::fromPath('monitoring/show/contact', array('contact_name' => $name)));
+ }
+
+ /**
+ * Render the given monitored object state as human readable HTML or 'N/A' if NULL
+ *
+ * @param bool $isService
+ * @param int|null $state
+ *
+ * @return string
+ */
+ protected function state($isService, $state)
+ {
+ if ($state === null) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ try {
+ $stateText = $isService
+ ? Service::getStateText($state, true)
+ : Host::getStateText($state, true);
+ } catch (InvalidArgumentException $e) {
+ return $this->view->escape($this->translate('N/A'));
+ }
+
+ return '<span class="badge state-' . ($isService ? Service::getStateText($state) : Host::getStateText($state))
+ . '">&nbsp;</span><span class="state-label">' . $this->view->escape($stateText) . '</span>';
+ }
+
+ /**
+ * Render the given plugin output as human readable HTML
+ *
+ * @param string $output
+ *
+ * @return string
+ */
+ protected function pluginOutput($output)
+ {
+ return $this->view->getHelper('PluginOutput')->pluginOutput($output);
+ }
+
+ /**
+ * Return the icon and the label for the given event type
+ *
+ * @param string $eventType
+ *
+ * @return string[]
+ */
+ protected function getIconAndLabel($eventType)
+ {
+ switch ($eventType) {
+ case 'notify':
+ return array('bell', $this->translate('Notification', 'tooltip'));
+ case 'comment':
+ return array('comment-empty', $this->translate('Comment', 'tooltip'));
+ case 'comment_deleted':
+ return array('cancel', $this->translate('Comment removed', 'tooltip'));
+ case 'ack':
+ return array('ok', $this->translate('Acknowledged', 'tooltip'));
+ case 'ack_deleted':
+ return array('ok', $this->translate('Acknowledgement removed', 'tooltip'));
+ case 'dt_comment':
+ return array('plug', $this->translate('Downtime scheduled', 'tooltip'));
+ case 'dt_comment_deleted':
+ return array('plug', $this->translate('Downtime removed', 'tooltip'));
+ case 'flapping':
+ return array('flapping', $this->translate('Flapping started', 'tooltip'));
+ case 'flapping_deleted':
+ return array('flapping', $this->translate('Flapping stopped', 'tooltip'));
+ case 'hard_state':
+ return array('warning-empty', $this->translate('Hard state change'));
+ case 'soft_state':
+ return array('spinner', $this->translate('Soft state change'));
+ case 'dt_start':
+ return array('plug', $this->translate('Downtime started', 'tooltip'));
+ case 'dt_end':
+ return array('plug', $this->translate('Downtime ended', 'tooltip'));
+ }
+ }
+
+ /**
+ * Return a query for the given event ID of the given type
+ *
+ * @param string $type
+ * @param int $id
+ *
+ * @return Queryable
+ */
+ protected function query($type, $id)
+ {
+ switch ($this->dataViewsByType[$type]) {
+ case 'downtimeevent':
+ return $this->backend->select()
+ ->from('downtimeevent', array(
+ 'entry_time' => 'downtimeevent_entry_time',
+ 'author_name' => 'downtimeevent_author_name',
+ 'comment_data' => 'downtimeevent_comment_data',
+ 'is_fixed' => 'downtimeevent_is_fixed',
+ 'scheduled_start_time' => 'downtimeevent_scheduled_start_time',
+ 'scheduled_end_time' => 'downtimeevent_scheduled_end_time',
+ 'was_started' => 'downtimeevent_was_started',
+ 'actual_start_time' => 'downtimeevent_actual_start_time',
+ 'actual_end_time' => 'downtimeevent_actual_end_time',
+ 'was_cancelled' => 'downtimeevent_was_cancelled',
+ 'is_in_effect' => 'downtimeevent_is_in_effect',
+ 'trigger_time' => 'downtimeevent_trigger_time',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('downtimeevent_id', $id);
+ case 'commentevent':
+ return $this->backend->select()
+ ->from('commentevent', array(
+ 'entry_type' => 'commentevent_entry_type',
+ 'comment_time' => 'commentevent_comment_time',
+ 'author_name' => 'commentevent_author_name',
+ 'comment_data' => 'commentevent_comment_data',
+ 'is_persistent' => 'commentevent_is_persistent',
+ 'comment_source' => 'commentevent_comment_source',
+ 'expires' => 'commentevent_expires',
+ 'expiration_time' => 'commentevent_expiration_time',
+ 'deletion_time' => 'commentevent_deletion_time',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('commentevent_id', $id);
+ case 'flappingevent':
+ return $this->backend->select()
+ ->from('flappingevent', array(
+ 'event_time' => 'flappingevent_event_time',
+ 'reason_type' => 'flappingevent_reason_type',
+ 'percent_state_change' => 'flappingevent_percent_state_change',
+ 'low_threshold' => 'flappingevent_low_threshold',
+ 'high_threshold' => 'flappingevent_high_threshold',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('flappingevent_id', $id)
+ ->where('flappingevent_event_type', $type);
+ case 'notificationevent':
+ return $this->backend->select()
+ ->from('notificationevent', array(
+ 'notification_reason' => 'notificationevent_reason',
+ 'start_time' => 'notificationevent_start_time',
+ 'end_time' => 'notificationevent_end_time',
+ 'state' => 'notificationevent_state',
+ 'output' => 'notificationevent_output',
+ 'long_output' => 'notificationevent_long_output',
+ 'escalated' => 'notificationevent_escalated',
+ 'contacts_notified' => 'notificationevent_contacts_notified',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('notificationevent_id', $id);
+ case 'statechangeevent':
+ return $this->backend->select()
+ ->from('statechangeevent', array(
+ 'state_time' => 'statechangeevent_state_time',
+ 'state' => 'statechangeevent_state',
+ 'current_check_attempt' => 'statechangeevent_current_check_attempt',
+ 'max_check_attempts' => 'statechangeevent_max_check_attempts',
+ 'last_state' => 'statechangeevent_last_state',
+ 'last_hard_state' => 'statechangeevent_last_hard_state',
+ 'output' => 'statechangeevent_output',
+ 'long_output' => 'statechangeevent_long_output',
+ 'check_source' => 'statechangeevent_check_source',
+ 'host_name',
+ 'service_description'
+ ))
+ ->where('statechangeevent_id', $id)
+ ->where('statechangeevent_state_change', 1)
+ ->where('statechangeevent_state_type', $type);
+ }
+ }
+
+ /**
+ * Return the given event's data prepared for a name-value table
+ *
+ * @param string $type
+ * @param \stdClass $event
+ *
+ * @return string[][]
+ */
+ protected function getDetails($type, $event)
+ {
+ switch ($type) {
+ case 'dt_start':
+ case 'dt_end':
+ $details = array(array(
+ array($this->translate('Entry time'), DateFormatter::formatDateTime($event->entry_time)),
+ array($this->translate('Is fixed'), $this->yesOrNo($event->is_fixed)),
+ array($this->translate('Is in effect'), $this->yesOrNo($event->is_in_effect)),
+ array($this->translate('Was started'), $this->yesOrNo($event->was_started))
+ ));
+
+ if ($type === 'dt_end') {
+ $details[] = array(
+ array($this->translate('Was cancelled'), $this->yesOrNo($event->was_cancelled))
+ );
+ }
+
+ $details[] = array(
+ array($this->translate('Trigger time'), DateFormatter::formatDateTime($event->trigger_time)),
+ array(
+ $this->translate('Scheduled start time'),
+ DateFormatter::formatDateTime($event->scheduled_start_time)
+ ),
+ array(
+ $this->translate('Actual start time'),
+ DateFormatter::formatDateTime($event->actual_start_time)
+ ),
+ array(
+ $this->translate('Scheduled end time'),
+ DateFormatter::formatDateTime($event->scheduled_end_time)
+ )
+ );
+
+ if ($type === 'dt_end') {
+ $details[] = array(
+ array(
+ $this->translate('Actual end time'),
+ DateFormatter::formatDateTime($event->actual_end_time)
+ )
+ );
+ }
+
+ $details[] = array(
+ array($this->translate('Author'), $this->contact($event->author_name)),
+ array($this->translate('Comment'), $this->comment($event->comment_data))
+ );
+
+ return call_user_func_array('array_merge', $details);
+ case 'comment':
+ case 'comment_deleted':
+ case 'ack':
+ case 'ack_deleted':
+ case 'dt_comment':
+ case 'dt_comment_deleted':
+ switch ($event->entry_type) {
+ case 'comment':
+ $entryType = $this->translate('User comment');
+ break;
+ case 'downtime':
+ $entryType = $this->translate('Scheduled downtime');
+ break;
+ case 'flapping':
+ $entryType = $this->translate('Flapping');
+ break;
+ case 'ack':
+ $entryType = $this->translate('Acknowledgement');
+ break;
+ default:
+ $entryType = $this->translate('N/A');
+ }
+
+ switch ($event->comment_source) {
+ case 'icinga':
+ $commentSource = $this->translate('Icinga');
+ break;
+ case 'user':
+ $commentSource = $this->translate('User');
+ break;
+ default:
+ $commentSource = $this->translate('N/A');
+ }
+
+ return array(
+ array($this->translate('Time'), DateFormatter::formatDateTime($event->comment_time)),
+ array($this->translate('Source'), $this->view->escape($commentSource)),
+ array($this->translate('Entry type'), $this->view->escape($entryType)),
+ array($this->translate('Author'), $this->contact($event->author_name)),
+ array($this->translate('Is persistent'), $this->yesOrNo($event->is_persistent)),
+ array($this->translate('Expires'), $this->yesOrNo($event->expires)),
+ array($this->translate('Expiration time'), DateFormatter::formatDateTime($event->expiration_time)),
+ array($this->translate('Deletion time'), DateFormatter::formatDateTime($event->deletion_time)),
+ array($this->translate('Message'), $this->comment($event->comment_data))
+ );
+ case 'flapping':
+ case 'flapping_deleted':
+ switch ($event->reason_type) {
+ case 'stopped':
+ $reasonType = $this->translate('Flapping stopped normally');
+ break;
+ case 'disabled':
+ $reasonType = $this->translate('Flapping was disabled');
+ break;
+ default:
+ $reasonType = $this->translate('N/A');
+ }
+
+ return array(
+ array($this->translate('Event time'), DateFormatter::formatDateTime($event->event_time)),
+ array($this->translate('Reason'), $this->view->escape($reasonType)),
+ array($this->translate('State change'), $this->percent($event->percent_state_change)),
+ array($this->translate('Low threshold'), $this->percent($event->low_threshold)),
+ array($this->translate('High threshold'), $this->percent($event->high_threshold))
+ );
+ case 'notify':
+ switch ($event->notification_reason) {
+ case 'normal_notification':
+ $notificationReason = $this->translate('Normal notification');
+ break;
+ case 'ack':
+ $notificationReason = $this->translate('Problem acknowledgement');
+ break;
+ case 'flapping_started':
+ $notificationReason = $this->translate('Flapping started');
+ break;
+ case 'flapping_stopped':
+ $notificationReason = $this->translate('Flapping stopped');
+ break;
+ case 'flapping_disabled':
+ $notificationReason = $this->translate('Flapping was disabled');
+ break;
+ case 'dt_start':
+ $notificationReason = $this->translate('Downtime started');
+ break;
+ case 'dt_end':
+ $notificationReason = $this->translate('Downtime ended');
+ break;
+ case 'dt_cancel':
+ $notificationReason = $this->translate('Downtime was cancelled');
+ break;
+ case 'custom_notification':
+ $notificationReason = $this->translate('Custom notification');
+ break;
+ default:
+ $notificationReason = $this->translate('N/A');
+ }
+
+ $details = array(
+ array($this->translate('Start time'), DateFormatter::formatDateTime($event->start_time)),
+ array($this->translate('End time'), DateFormatter::formatDateTime($event->end_time)),
+ array($this->translate('Reason'), $this->view->escape($notificationReason)),
+ array(
+ $this->translate('State'),
+ $this->state($event->service_description !== null, $event->state)
+ ),
+ array($this->translate('Escalated'), $this->yesOrNo($event->escalated)),
+ array($this->translate('Contacts notified'), (int) $event->contacts_notified),
+ array(
+ $this->translate('Output'),
+ $this->pluginOutput($event->output) . $this->pluginOutput($event->long_output)
+ )
+ );
+
+ return $details;
+ case 'hard_state':
+ case 'soft_state':
+ $isService = $event->service_description !== null;
+
+ $details = array(
+ array($this->translate('State time'), DateFormatter::formatDateTime($event->state_time)),
+ array($this->translate('State'), $this->state($isService, $event->state)),
+ array($this->translate('Check source'), $event->check_source),
+ array($this->translate('Check attempt'), $this->view->escape(sprintf(
+ $this->translate('%d of %d'),
+ (int) $event->current_check_attempt,
+ (int) $event->max_check_attempts
+ ))),
+ array($this->translate('Last state'), $this->state($isService, $event->last_state)),
+ array($this->translate('Last hard state'), $this->state($isService, $event->last_hard_state)),
+ array(
+ $this->translate('Output'),
+ $this->pluginOutput($event->output) . $this->pluginOutput($event->long_output)
+ )
+ );
+
+ return $details;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/HealthController.php b/modules/monitoring/application/controllers/HealthController.php
new file mode 100644
index 0000000..48dd580
--- /dev/null
+++ b/modules/monitoring/application/controllers/HealthController.php
@@ -0,0 +1,196 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Instance\DisableNotificationsExpireCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Instance\ToggleInstanceFeaturesCommandForm;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Display process and performance information of the monitoring host and program-wide commands
+ */
+class HealthController extends Controller
+{
+ /**
+ * Add tabs
+ *
+ * @see \Icinga\Web\Controller\ActionController::init()
+ */
+ public function init()
+ {
+ $this
+ ->getTabs()
+ ->add(
+ 'info',
+ array(
+ 'title' => $this->translate(
+ 'Show information about the current monitoring instance\'s process'
+ . ' and it\'s performance as well as available features'
+ ),
+ 'label' => $this->translate('Process Information'),
+ 'url' =>'monitoring/health/info'
+ )
+ )
+ ->add(
+ 'stats',
+ array(
+ 'title' => $this->translate(
+ 'Show statistics about the monitored objects'
+ ),
+ 'label' => $this->translate('Stats'),
+ 'url' =>'monitoring/health/stats'
+ )
+ )
+ ->extend(new DashboardAction())->extend(new MenuAction());
+ }
+
+ /**
+ * Display process information and program-wide commands
+ */
+ public function infoAction()
+ {
+ $this->view->title = $this->translate('Process Information');
+ $this->getTabs()->activate('info');
+ $this->setAutorefreshInterval(10);
+ $this->view->backendName = $this->backend->getName();
+ $programStatus = $this->backend
+ ->select()
+ ->from(
+ 'programstatus',
+ array(
+ 'is_currently_running',
+ 'process_id',
+ 'endpoint_name',
+ 'program_start_time',
+ 'status_update_time',
+ 'program_version',
+ 'last_command_check',
+ 'last_log_rotation',
+ 'global_service_event_handler',
+ 'global_host_event_handler',
+ 'notifications_enabled',
+ 'disable_notif_expire_time',
+ 'active_service_checks_enabled',
+ 'passive_service_checks_enabled',
+ 'active_host_checks_enabled',
+ 'passive_host_checks_enabled',
+ 'event_handlers_enabled',
+ 'obsess_over_services',
+ 'obsess_over_hosts',
+ 'flap_detection_enabled',
+ 'process_performance_data'
+ )
+ )
+ ->getQuery();
+ $this->handleFormatRequest($programStatus);
+ $programStatus = $programStatus->fetchRow();
+ if ($programStatus === false) {
+ return $this->render('not-running', true, null);
+ }
+ $this->view->programStatus = $programStatus;
+ $toggleFeaturesForm = new ToggleInstanceFeaturesCommandForm();
+ $toggleFeaturesForm
+ ->setBackend($this->backend)
+ ->setStatus($programStatus)
+ ->load($programStatus)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+
+ $this->view->runtimevariables = (object) $this->backend->select()
+ ->from('runtimevariables', array('varname', 'varvalue'))
+ ->getQuery()->fetchPairs();
+
+ $this->view->checkperformance = $this->backend->select()
+ ->from('runtimesummary')
+ ->getQuery()->fetchAll();
+ }
+
+ /**
+ * Display stats about current checks and monitored objects
+ */
+ public function statsAction()
+ {
+ $this->view->title = $this->translate('Stats');
+ $this->getTabs()->activate('stats');
+
+ $servicestats = $this->backend->select()->from('servicestatussummary', array(
+ 'services_critical',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $servicestats);
+ $this->view->servicestats = $servicestats->fetchRow();
+ $this->view->unhandledServiceProblems = $this->view->servicestats->services_critical_unhandled
+ + $this->view->servicestats->services_unknown_unhandled
+ + $this->view->servicestats->services_warning_unhandled;
+
+ $hoststats = $this->backend->select()->from('hoststatussummary', array(
+ 'hosts_total',
+ 'hosts_up',
+ 'hosts_down',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_unreachable',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_pending',
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $hoststats);
+ $this->view->hoststats = $hoststats->fetchRow();
+ $this->view->unhandledhostProblems = $this->view->hoststats->hosts_down_unhandled
+ + $this->view->hoststats->hosts_unreachable_unhandled;
+
+ $this->view->unhandledProblems = $this->view->unhandledhostProblems
+ + $this->view->unhandledServiceProblems;
+
+ $this->view->runtimevariables = (object) $this->backend->select()
+ ->from('runtimevariables', array('varname', 'varvalue'))
+ ->getQuery()->fetchPairs();
+
+ $this->view->checkperformance = $this->backend->select()
+ ->from('runtimesummary')
+ ->getQuery()->fetchAll();
+ }
+
+ /**
+ * Disable notifications w/ an optional expire time
+ */
+ public function disableNotificationsAction()
+ {
+ $this->assertPermission('monitoring/command/feature/instance');
+ $this->view->title = $this->translate('Disable Notifications');
+ $programStatus = $this->backend
+ ->select()
+ ->from(
+ 'programstatus',
+ array(
+ 'notifications_enabled',
+ 'disable_notif_expire_time'
+ )
+ )
+ ->getQuery()
+ ->fetchRow();
+ $this->view->programStatus = $programStatus;
+ if ((bool) $programStatus->notifications_enabled === false) {
+ return;
+ } else {
+ $form = new DisableNotificationsExpireCommandForm();
+ $form
+ ->setRedirectUrl('monitoring/health/info')
+ ->handleRequest();
+ $this->view->form = $form;
+ }
+ }
+}
diff --git a/modules/monitoring/application/controllers/HostController.php b/modules/monitoring/application/controllers/HostController.php
new file mode 100644
index 0000000..94f1a60
--- /dev/null
+++ b/modules/monitoring/application/controllers/HostController.php
@@ -0,0 +1,185 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Web\Controller\MonitoredObjectController;
+use Icinga\Web\Hook;
+use Icinga\Web\Navigation\Navigation;
+
+class HostController extends MonitoredObjectController
+{
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $commandRedirectUrl = 'monitoring/host/show';
+
+ /**
+ * Fetch the requested host from the monitoring backend
+ */
+ public function init()
+ {
+ $host = new Host($this->backend, $this->params->getRequired('host'));
+ $this->applyRestriction('monitoring/filter/objects', $host);
+ if ($host->fetch() === false) {
+ $this->httpNotFound($this->translate('Host not found'));
+ }
+ $this->object = $host;
+ $this->createTabs();
+ $this->getTabs()->activate('host');
+ $this->view->title = $host->host_display_name;
+ $this->view->defaultTitle = $this->translate('Hosts') . ' :: ' . $this->view->defaultTitle;
+ }
+
+ /**
+ * Get host actions from hook
+ *
+ * @return Navigation
+ */
+ protected function getHostActions()
+ {
+ $navigation = new Navigation();
+ foreach (Hook::all('Monitoring\\HostActions') as $hook) {
+ $navigation->merge($hook->getNavigation($this->object));
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Show a host
+ */
+ public function showAction()
+ {
+ $this->view->actions = $this->getHostActions();
+ parent::showAction();
+ }
+
+ /**
+ * List a host's services
+ */
+ public function servicesAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->getTabs()->activate('services');
+ $query = $this->backend->select()->from('servicestatus', array(
+ 'host_name',
+ 'host_display_name',
+ 'host_state',
+ 'host_state_type',
+ 'host_last_state_change',
+ 'host_address',
+ 'host_address6',
+ 'host_handled',
+ 'service_description',
+ 'service_display_name',
+ 'service_state',
+ 'service_in_downtime',
+ 'service_acknowledged',
+ 'service_handled',
+ 'service_output',
+ 'service_perfdata',
+ 'service_attempt',
+ 'service_last_state_change',
+ 'service_icon_image',
+ 'service_icon_image_alt',
+ 'service_is_flapping',
+ 'service_state_type',
+ 'service_handled',
+ 'service_severity',
+ 'service_last_check',
+ 'service_notifications_enabled',
+ 'service_action_url',
+ 'service_notes_url',
+ 'service_active_checks_enabled',
+ 'service_passive_checks_enabled',
+ 'current_check_attempt' => 'service_current_check_attempt',
+ 'max_check_attempts' => 'service_max_check_attempts',
+ 'service_check_command',
+ 'service_next_update'
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $query);
+ $this->view->services = $query->where('host_name', $this->object->getName());
+ $this->view->object = $this->object;
+ }
+
+ /**
+ * Acknowledge a host problem
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Host Problem'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Add a host comment
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Host Comment'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule a host check
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleHostCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Host Check'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule a host downtime
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleHostDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Host Downtime'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit a passive host check result
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Host Check Result'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for host
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Host Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php
new file mode 100644
index 0000000..9219df8
--- /dev/null
+++ b/modules/monitoring/application/controllers/HostsController.php
@@ -0,0 +1,260 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleHostDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm;
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Object\HostList;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class HostsController extends Controller
+{
+ /**
+ * @var HostList
+ */
+ protected $hostList;
+
+ public function init()
+ {
+ $hostList = new HostList($this->backend);
+ $this->applyRestriction('monitoring/filter/objects', $hostList);
+ $hostList->addFilter(Filter::fromQueryString((string) $this->params));
+ $this->hostList = $hostList;
+ $this->hostList->setColumns(array(
+ 'host_acknowledged',
+ 'host_active_checks_enabled',
+ 'host_display_name',
+ 'host_event_handler_enabled',
+ 'host_flap_detection_enabled',
+ 'host_handled',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_last_state_change',
+ 'host_name',
+ 'host_notifications_enabled',
+ 'host_obsessing',
+ 'host_passive_checks_enabled',
+ 'host_problem',
+ 'host_state',
+ 'instance_name'
+ ));
+ $this->view->baseFilter = $this->hostList->getFilter();
+ $this->getTabs()->add(
+ 'show',
+ array(
+ 'label' => $this->translate('Hosts') . sprintf(' (%d)', count($this->hostList)),
+ 'title' => sprintf(
+ $this->translate('Show summarized information for %u hosts'),
+ count($this->hostList)
+ ),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
+ $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts');
+ $this->view->title = $this->translate('Hosts');
+ }
+
+ protected function handleCommandForm(ObjectsCommandForm $form)
+ {
+ $form
+ ->setBackend($this->backend)
+ ->setObjects($this->hostList)
+ ->setRedirectUrl(Url::fromPath('monitoring/hosts/show')->setParams(
+ $this->params->without('host_active_checks_enabled')
+ ))
+ ->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->objects = $this->hostList;
+ $this->view->stats = $this->hostList->getStateSummary();
+ $this->_helper->viewRenderer('partials/command/objects-command-form', null, true);
+ return $form;
+ }
+
+ public function showAction()
+ {
+ $this->setAutorefreshInterval(15);
+ $activeChecksEnabled = $this->hostList->getFeatureStatus()['active_checks_enabled'] !== 0;
+ if ($this->Auth()->hasPermission('monitoring/command/schedule-check')
+ || ($this->Auth()->hasPermission('monitoring/command/schedule-check/active-only')
+ && $activeChecksEnabled
+ )
+ ) {
+ $checkNowForm = new CheckNowCommandForm();
+ $checkNowForm
+ ->setObjects($this->hostList)
+ ->handleRequest();
+ $this->view->checkNowForm = $checkNowForm;
+ }
+
+ $acknowledgedObjects = $this->hostList->getAcknowledgedObjects();
+ if (! empty($acknowledgedObjects)) {
+ $removeAckForm = new RemoveAcknowledgementCommandForm();
+ $removeAckForm
+ ->setObjects($acknowledgedObjects)
+ ->handleRequest();
+ $this->view->removeAckForm = $removeAckForm;
+ }
+
+ $featureStatus = $this->hostList->getFeatureStatus();
+ $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array(
+ 'backend' => $this->backend,
+ 'objects' => $this->hostList
+ ));
+ $toggleFeaturesForm
+ ->load((object) $featureStatus)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+
+ $hostStates = $this->hostList->getStateSummary();
+
+ if ($activeChecksEnabled) {
+ $this->view->rescheduleAllLink = Url::fromRequest()
+ ->setPath('monitoring/hosts/reschedule-check')
+ ->addParams(['host_active_checks_enabled' => true]);
+ }
+
+ $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/hosts/schedule-downtime');
+ $this->view->processCheckResultAllLink = Url::fromRequest()->setPath('monitoring/hosts/process-check-result');
+ $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/hosts/add-comment');
+ $this->view->stats = $hostStates;
+ $this->view->objects = $this->hostList;
+ $this->view->unhandledObjects = $this->hostList->getUnhandledObjects();
+ $this->view->problemObjects = $this->hostList->getProblemObjects();
+ $this->view->acknowledgeUnhandledLink = Url::fromPath('monitoring/hosts/acknowledge-problem')
+ ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString());
+ $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/hosts/schedule-downtime')
+ ->setQueryString($this->hostList->getUnhandledObjects()->objectsFilter()->toQueryString());
+ $this->view->downtimeLink = Url::fromPath('monitoring/hosts/schedule-downtime')
+ ->setQueryString($this->hostList->getProblemObjects()->objectsFilter()->toQueryString());
+ $this->view->acknowledgedObjects = $this->hostList->getAcknowledgedObjects();
+ $this->view->acknowledgeLink = Url::fromPath('monitoring/hosts/acknowledge-problem')
+ ->setQueryString($this->hostList->getUnacknowledgedObjects()->objectsFilter()->toQueryString());
+ $this->view->unacknowledgedObjects = $this->hostList->getUnacknowledgedObjects();
+ $this->view->objectsInDowntime = $this->hostList->getObjectsInDowntime();
+ $this->view->inDowntimeLink = Url::fromPath('monitoring/list/hosts')
+ ->setQueryString(
+ $this->hostList
+ ->getObjectsInDowntime()
+ ->objectsFilter()
+ ->toQueryString()
+ );
+ $this->view->showDowntimesLink = Url::fromPath('monitoring/list/downtimes')
+ ->setQueryString(
+ $this->hostList
+ ->objectsFilter()
+ ->andFilter(FilterEqual::where('object_type', 'host'))
+ ->toQueryString()
+ );
+ $this->view->commentsLink = Url::fromRequest()->setPath('monitoring/list/comments');
+ $this->view->sendCustomNotificationLink = Url::fromRequest()
+ ->setPath('monitoring/hosts/send-custom-notification');
+
+ $this->view->extensionsHtml = array();
+ foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) {
+ /** @var DetailviewExtensionHook $hook */
+ try {
+ $html = $hook->setView($this->view)->getHtmlForObjects($this->hostList);
+ } catch (Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+ }
+
+ /**
+ * Add a host comments
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Host Comments'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Acknowledge host problems
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Host Problems'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule host checks
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleHostCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Host Checks'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule host downtimes
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleHostDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Host Downtimes'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit passive host check results
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Host Check Results'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for hosts
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Host Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php
new file mode 100644
index 0000000..0ccff99
--- /dev/null
+++ b/modules/monitoring/application/controllers/ListController.php
@@ -0,0 +1,808 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Security\SecurityException;
+use Icinga\Util\GlobFilter;
+use Icinga\Web\Form;
+use Zend_Form;
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\DataView\DataView;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\StatehistoryForm;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+use Icinga\Web\Widget\Tabextension\OutputFormat;
+use Icinga\Web\Widget\Tabs;
+
+class ListController extends Controller
+{
+ /**
+ * @see ActionController::init
+ */
+ public function init()
+ {
+ parent::init();
+ $this->createTabs();
+ }
+
+ /**
+ * Overwrite the backend to use (used for testing)
+ *
+ * @param MonitoringBackend $backend The Backend that should be used for querying
+ */
+ public function setBackend($backend)
+ {
+ $this->backend = $backend;
+ }
+
+ /**
+ * List hosts
+ */
+ public function hostsAction()
+ {
+ $this->addTitleTab(
+ 'hosts',
+ $this->translate('Hosts'),
+ $this->translate('List hosts')
+ );
+
+ $this->setAutorefreshInterval(10);
+
+ // Handle soft and hard states
+ if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') {
+ $stateColumn = 'host_hard_state';
+ $stateChangeColumn = 'host_last_hard_state_change';
+ } else {
+ $stateColumn = 'host_state';
+ $stateChangeColumn = 'host_last_state_change';
+ }
+
+ $hosts = $this->backend->select()->from('hoststatus', array_merge(array(
+ 'host_icon_image',
+ 'host_icon_image_alt',
+ 'host_name',
+ 'host_display_name',
+ 'host_state' => $stateColumn,
+ 'host_acknowledged',
+ 'host_output',
+ 'host_attempt',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_state_type',
+ 'host_handled',
+ 'host_last_state_change' => $stateChangeColumn,
+ 'host_notifications_enabled',
+ 'host_active_checks_enabled',
+ 'host_passive_checks_enabled',
+ 'host_check_command',
+ 'host_next_update'
+ ), $this->addColumns()));
+
+ $this->setupPaginationControl($hosts);
+ $this->setupSortControl(array(
+ 'host_severity' => $this->translate('Severity'),
+ 'host_state' => $this->translate('Current State'),
+ 'host_display_name' => $this->translate('Hostname'),
+ 'host_address' => $this->translate('Address'),
+ 'host_last_check' => $this->translate('Last Check'),
+ 'host_last_state_change' => $this->translate('Last State Change')
+ ), $hosts);
+ $this->filterQuery($hosts);
+ $this->setupLimitControl();
+
+ $stats = $this->backend->select()->from('hoststatussummary', array(
+ 'hosts_total',
+ 'hosts_up',
+ 'hosts_down',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_unreachable',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_pending',
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $stats);
+
+ $this->view->hosts = $hosts;
+ $this->view->stats = $stats;
+ }
+
+ /**
+ * List services
+ */
+ public function servicesAction()
+ {
+ $this->addTitleTab(
+ 'services',
+ $this->translate('Services'),
+ $this->translate('List services')
+ );
+
+ // Handle soft and hard states
+ if (strtolower($this->params->shift('stateType', 'soft')) === 'hard') {
+ $stateColumn = 'service_hard_state';
+ $stateChangeColumn = 'service_last_hard_state_change';
+ } else {
+ $stateColumn = 'service_state';
+ $stateChangeColumn = 'service_last_state_change';
+ }
+
+ $this->setAutorefreshInterval(10);
+
+ $services = $this->backend->select()->from('servicestatus', array_merge(array(
+ 'host_name',
+ 'host_display_name',
+ 'host_state',
+ 'service_description',
+ 'service_display_name',
+ 'service_state' => $stateColumn,
+ 'service_in_downtime',
+ 'service_acknowledged',
+ 'service_handled',
+ 'service_output',
+ 'service_perfdata',
+ 'service_attempt',
+ 'service_last_state_change' => $stateChangeColumn,
+ 'service_icon_image',
+ 'service_icon_image_alt',
+ 'service_is_flapping',
+ 'service_state_type',
+ 'service_handled',
+ 'service_severity',
+ 'service_notifications_enabled',
+ 'service_active_checks_enabled',
+ 'service_passive_checks_enabled',
+ 'service_check_command',
+ 'service_next_update'
+ ), $this->addColumns()));
+
+ $this->setupPaginationControl($services);
+ $this->setupSortControl(array(
+ 'service_severity' => $this->translate('Service Severity'),
+ 'service_state' => $this->translate('Current Service State'),
+ 'service_display_name' => $this->translate('Service Name'),
+ 'service_last_check' => $this->translate('Last Service Check'),
+ 'service_last_state_change' => $this->translate('Last State Change'),
+ 'host_severity' => $this->translate('Host Severity'),
+ 'host_state' => $this->translate('Current Host State'),
+ 'host_display_name' => $this->translate('Hostname'),
+ 'host_address' => $this->translate('Host Address'),
+ 'host_last_check' => $this->translate('Last Host Check')
+ ), $services);
+ $this->filterQuery($services);
+ $this->setupLimitControl();
+
+ $stats = $this->backend->select()->from('servicestatussummary', array(
+ 'services_critical',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $stats);
+
+ $this->view->services = $services;
+ $this->view->stats = $stats;
+ if (strpos($this->params->get('host_name', '*'), '*') === false) {
+ $this->view->showHost = false;
+ } else {
+ $this->view->showHost = true;
+ }
+ }
+
+ /**
+ * List downtimes
+ */
+ public function downtimesAction()
+ {
+ $this->addTitleTab(
+ 'downtimes',
+ $this->translate('Downtimes'),
+ $this->translate('List downtimes')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $downtimes = $this->backend->select()->from('downtime', array(
+ 'id' => 'downtime_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'downtime_comment',
+ 'author_name' => 'downtime_author_name',
+ 'start' => 'downtime_start',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'end' => 'downtime_end',
+ 'duration' => 'downtime_duration',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'entry_time' => 'downtime_entry_time',
+ 'name' => 'downtime_name',
+ 'host_state',
+ 'service_state',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ));
+
+ $this->setupPaginationControl($downtimes);
+ $this->setupSortControl(array(
+ 'downtime_is_in_effect' => $this->translate('Is In Effect'),
+ 'host_display_name' => $this->translate('Host'),
+ 'service_display_name' => $this->translate('Service'),
+ 'downtime_entry_time' => $this->translate('Entry Time'),
+ 'downtime_author' => $this->translate('Author'),
+ 'downtime_start' => $this->translate('Start Time'),
+ 'downtime_end' => $this->translate('End Time'),
+ 'downtime_scheduled_start' => $this->translate('Scheduled Start'),
+ 'downtime_scheduled_end' => $this->translate('Scheduled End'),
+ 'downtime_duration' => $this->translate('Duration')
+ ), $downtimes);
+ $this->filterQuery($downtimes);
+ $this->setupLimitControl();
+
+ $this->view->downtimes = $downtimes;
+
+ if ($this->Auth()->hasPermission('monitoring/command/downtime/delete')) {
+ $this->view->delDowntimeForm = new DeleteDowntimeCommandForm();
+ $this->view->delDowntimeForm->handleRequest();
+ }
+ }
+
+ /**
+ * List notifications
+ */
+ public function notificationsAction()
+ {
+ $this->addTitleTab(
+ 'notifications',
+ $this->translate('Notifications'),
+ $this->translate('List notifications')
+ );
+
+ $this->setAutorefreshInterval(15);
+
+ $notifications = $this->backend->select()->from('notification', array(
+ 'id',
+ 'host_display_name',
+ 'host_name',
+ 'notification_contact_name',
+ 'notification_output',
+ 'notification_state',
+ 'notification_timestamp',
+ 'service_description',
+ 'service_display_name'
+ ));
+
+ $this->setupPaginationControl($notifications);
+ $this->setupSortControl(array(
+ 'notification_timestamp' => $this->translate('Notification Start')
+ ), $notifications);
+ $this->filterQuery($notifications);
+ $this->setupLimitControl();
+
+ $this->view->notifications = $notifications;
+ }
+
+ /**
+ * List contacts
+ */
+ public function contactsAction()
+ {
+ if (! $this->hasPermission('*') && $this->hasPermission('no-monitoring/contacts')) {
+ throw new SecurityException('No permission for %s', 'monitoring/contacts');
+ }
+
+ $this->addTitleTab(
+ 'contacts',
+ $this->translate('Contacts'),
+ $this->translate('List contacts')
+ );
+
+ $contacts = $this->backend->select()->from('contact', array(
+ 'contact_name',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ 'contact_notify_service_timeperiod',
+ 'contact_notify_host_timeperiod'
+ ));
+
+ $this->setupPaginationControl($contacts);
+ $this->setupSortControl(array(
+ 'contact_name' => $this->translate('Name'),
+ 'contact_alias' => $this->translate('Alias'),
+ 'contact_email' => $this->translate('Email'),
+ 'contact_pager' => $this->translate('Pager Address / Number')
+ ), $contacts);
+ $this->filterQuery($contacts);
+ $this->setupLimitControl();
+
+ $this->view->contacts = $contacts;
+ }
+
+ public function eventgridAction()
+ {
+ $this->addTitleTab('eventgrid', $this->translate('Event Grid'), $this->translate('Show the Event Grid'));
+
+ $form = new StatehistoryForm();
+ $form->setEnctype(Zend_Form::ENCTYPE_URLENCODED);
+ $form->setMethod('get');
+ $form->setTokenDisabled();
+ $form->setUidDisabled();
+ $form->render();
+ $this->view->form = $form;
+
+ $this->params
+ ->remove('showCompact')
+ ->remove('format');
+ $orientation = $this->params->shift('vertical', 0) ? 'vertical' : 'horizontal';
+/*
+ $orientationBox = new SelectBox(
+ 'orientation',
+ array(
+ '0' => mt('monitoring', 'Vertical'),
+ '1' => mt('monitoring', 'Horizontal')
+ ),
+ mt('monitoring', 'Orientation'),
+ 'horizontal'
+ );
+ $orientationBox->applyRequest($this->getRequest());
+*/
+ $objectType = $form->getValue('objecttype');
+ $from = $form->getValue('from');
+ $query = $this->backend->select()->from(
+ 'eventgrid' . $objectType,
+ array('day', $form->getValue('state'))
+ );
+ $this->params->remove(array('objecttype', 'from', 'to', 'state', 'btn_submit'));
+ $this->view->filter = Filter::fromQuerystring((string) $this->params);
+ $query->applyFilter($this->view->filter);
+ $query->applyFilter(Filter::fromQuerystring('timestamp>=' . $from));
+ $this->applyRestriction('monitoring/filter/objects', $query);
+ $this->view->summary = $query;
+ $this->view->column = $form->getValue('state');
+// $this->view->orientationBox = $orientationBox;
+ $this->view->orientation = $orientation;
+ }
+
+ /**
+ * List contact groups
+ */
+ public function contactgroupsAction()
+ {
+ if (! $this->hasPermission('*') && $this->hasPermission('no-monitoring/contacts')) {
+ throw new SecurityException('No permission for %s', 'monitoring/contacts');
+ }
+
+ $this->addTitleTab(
+ 'contactgroups',
+ $this->translate('Contact Groups'),
+ $this->translate('List contact groups')
+ );
+
+ $contactGroups = $this->backend->select()->from('contactgroup', array(
+ 'contactgroup_name',
+ 'contactgroup_alias',
+ 'contact_count'
+ ));
+
+ $this->setupPaginationControl($contactGroups);
+ $this->setupSortControl(array(
+ 'contactgroup_name' => $this->translate('Contactgroup Name'),
+ 'contactgroup_alias' => $this->translate('Contactgroup Alias')
+ ), $contactGroups);
+ $this->filterQuery($contactGroups);
+ $this->setupLimitControl();
+
+ $this->view->contactGroups = $contactGroups;
+ }
+
+ /**
+ * List all comments
+ */
+ public function commentsAction()
+ {
+ $this->addTitleTab(
+ 'comments',
+ $this->translate('Comments'),
+ $this->translate('List comments')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $comments = $this->backend->select()->from('comment', array(
+ 'id' => 'comment_internal_id',
+ 'objecttype' => 'object_type',
+ 'comment' => 'comment_data',
+ 'author' => 'comment_author_name',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type',
+ 'persistent' => 'comment_is_persistent',
+ 'expiration' => 'comment_expiration',
+ 'name' => 'comment_name',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ ));
+
+ $this->setupPaginationControl($comments);
+ $this->setupSortControl(
+ array(
+ 'comment_timestamp' => $this->translate('Comment Timestamp'),
+ 'host_display_name' => $this->translate('Host'),
+ 'service_display_name' => $this->translate('Service'),
+ 'comment_type' => $this->translate('Comment Type'),
+ 'comment_expiration' => $this->translate('Expiration')
+ ),
+ $comments
+ );
+ $this->filterQuery($comments);
+ $this->setupLimitControl();
+
+ $this->view->comments = $comments;
+
+ if ($this->Auth()->hasPermission('monitoring/command/comment/delete')) {
+ $this->view->delCommentForm = new DeleteCommentCommandForm();
+ $this->view->delCommentForm->handleRequest();
+ }
+ }
+
+ /**
+ * List service groups
+ */
+ public function servicegroupsAction()
+ {
+ $this->addTitleTab(
+ 'servicegroups',
+ $this->translate('Service Groups'),
+ $this->translate('List service groups')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $serviceGroups = $this->backend->select()->from('servicegroupsummary', array(
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+
+ $this->setupPaginationControl($serviceGroups);
+ $this->setupSortControl(array(
+ 'servicegroup_alias' => $this->translate('Service Group Name'),
+ 'services_severity' => $this->translate('Severity'),
+ 'services_total' => $this->translate('Total Services')
+ ), $serviceGroups);
+ $this->filterQuery($serviceGroups);
+ $this->setupLimitControl();
+
+ $this->view->serviceGroups = $serviceGroups;
+ }
+
+ /**
+ * List service groups
+ */
+ public function servicegroupGridAction()
+ {
+ $this->addTitleTab(
+ 'servicegroup-grid',
+ $this->translate('Service Group Grid'),
+ $this->translate('Show the Service Group Grid')
+ );
+
+ $this->setAutorefreshInterval(15);
+
+ $serviceGroups = $this->backend->select()->from('servicegroupsummary', array(
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+ $this->filterQuery($serviceGroups);
+
+ $this->setupSortControl(array(
+ 'servicegroup_alias' => $this->translate('Service Group Name'),
+ 'services_severity' => $this->translate('Severity'),
+ 'services_total' => $this->translate('Total Services')
+ ), $serviceGroups, ['services_severity' => 'desc']);
+
+ $this->view->serviceGroups = $serviceGroups;
+ }
+
+ /**
+ * List host groups
+ */
+ public function hostgroupsAction()
+ {
+ $this->addTitleTab(
+ 'hostgroups',
+ $this->translate('Host Groups'),
+ $this->translate('List host groups')
+ );
+
+ $this->setAutorefreshInterval(12);
+
+ $hostGroups = $this->backend->select()->from('hostgroupsummary', array(
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_pending',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_up',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ ));
+
+ $this->setupPaginationControl($hostGroups);
+ $this->setupSortControl(array(
+ 'hostgroup_alias' => $this->translate('Host Group Name'),
+ 'hosts_severity' => $this->translate('Severity'),
+ 'hosts_total' => $this->translate('Total Hosts'),
+ 'services_total' => $this->translate('Total Services')
+ ), $hostGroups);
+ $this->filterQuery($hostGroups);
+ $this->setupLimitControl();
+
+ $this->view->hostGroups = $hostGroups;
+ }
+
+ /**
+ * List host groups
+ */
+ public function hostgroupGridAction()
+ {
+ $this->addTitleTab(
+ 'hostgroup-grid',
+ $this->translate('Host Group Grid'),
+ $this->translate('Show the Host Group Grid')
+ );
+
+ $this->setAutorefreshInterval(15);
+
+ $hostGroups = $this->backend->select()->from('hostgroupsummary', [
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_pending',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_up'
+ ]);
+ $this->filterQuery($hostGroups);
+
+ $this->setupSortControl([
+ 'hosts_severity' => $this->translate('Severity'),
+ 'hostgroup_alias' => $this->translate('Host Group Name'),
+ 'hosts_total' => $this->translate('Total Hosts'),
+ 'services_total' => $this->translate('Total Services')
+ ], $hostGroups, ['hosts_severity' => 'desc']);
+
+ $this->view->hostGroups = $hostGroups;
+ }
+
+ public function eventhistoryAction()
+ {
+ $this->addTitleTab(
+ 'eventhistory',
+ $this->translate('Event Overview'),
+ $this->translate('List event records')
+ );
+
+ $query = $this->backend->select()->from('eventhistory', array(
+ 'id',
+ 'host_name',
+ 'host_display_name',
+ 'service_description',
+ 'service_display_name',
+ 'object_type',
+ 'timestamp',
+ 'state',
+ 'output',
+ 'type'
+ ));
+
+ $this->view->history = $query;
+
+ $this->setupSortControl(array(
+ 'timestamp' => $this->translate('Occurence')
+ ), $query);
+ $this->filterQuery($query);
+ $this->setupLimitControl();
+ }
+
+ public function servicegridAction()
+ {
+ if ($this->params->has('noscript_apply')) {
+ $this->redirectNow($this->getRequest()->getUrl()->without('noscript_apply'));
+ }
+
+ $this->addTitleTab('servicegrid', $this->translate('Service Grid'), $this->translate('Show the Service Grid'));
+ $this->setAutorefreshInterval(15);
+ $query = $this->backend->select()->from('servicestatus', array(
+ 'host_display_name',
+ 'host_name',
+ 'service_description',
+ 'service_display_name',
+ 'service_handled',
+ 'service_output',
+ 'service_state'
+ ));
+ $this->filterQuery($query);
+ $filter = (bool) $this->params->shift('problems', false) ? Filter::where('service_problem', 1) : null;
+
+ $this->view->problemToggle = $problemToggle = new Form(['method' => 'GET']);
+ $problemToggle->setUidDisabled();
+ $problemToggle->setTokenDisabled();
+ $problemToggle->setAttrib('class', 'filter-toggle inline icinga-controls');
+ $problemToggle->addElement('checkbox', 'problems', [
+ 'disableHidden' => true,
+ 'autosubmit' => true,
+ 'value' => $filter !== null,
+ 'label' => $this->translate('Problems Only'),
+ 'decorators' => ['ViewHelper', ['Label', ['placement' => 'APPEND']]]
+ ]);
+
+ if ($this->params->get('flipped', false)) {
+ $pivot = $query
+ ->pivot(
+ 'host_name',
+ 'service_description',
+ $filter,
+ $filter ? clone $filter : null
+ )
+ ->setYAxisHeader('service_display_name')
+ ->setXAxisHeader('host_display_name');
+ } else {
+ $pivot = $query
+ ->pivot(
+ 'service_description',
+ 'host_name',
+ $filter,
+ $filter ? clone $filter : null
+ )
+ ->setXAxisHeader('service_display_name')
+ ->setYAxisHeader('host_display_name');
+ }
+ $this->setupSortControl(array(
+ 'host_display_name' => $this->translate('Hostname'),
+ 'service_display_name' => $this->translate('Service Name')
+ ), $pivot);
+ $this->view->horizontalPaginator = $pivot->paginateXAxis();
+ $this->view->verticalPaginator = $pivot->paginateYAxis();
+ list($pivotData, $pivotHeader) = $pivot->toArray();
+ $this->view->pivotData = $pivotData;
+ $this->view->pivotHeader = $pivotHeader;
+ if ($this->params->get('flipped', false)) {
+ $this->render('servicegrid-flipped');
+ }
+ }
+
+ /**
+ * Apply filters on a DataView
+ *
+ * @param DataView $dataView The DataView to apply filters on
+ *
+ * @return DataView $dataView
+ */
+ protected function filterQuery(DataView $dataView)
+ {
+ $this->setupFilterControl($dataView, null, null, array(
+ 'format', // handleFormatRequest()
+ 'stateType', // hostsAction() and servicesAction()
+ 'addColumns', // addColumns()
+ 'problems', // servicegridAction()
+ 'flipped' // servicegridAction()
+ ));
+
+ if ($this->params->get('format') !== 'sql' || $this->hasPermission('config/authentication/roles/show')) {
+ $this->applyRestriction('monitoring/filter/objects', $dataView);
+ }
+
+ $this->handleFormatRequest($dataView);
+
+ return $dataView;
+ }
+
+ /**
+ * Get columns to be added from URL parameter 'addColumns'
+ * and assign to $this->view->addColumns (as array)
+ *
+ * @return array
+ */
+ protected function addColumns()
+ {
+ $columns = preg_split(
+ '~,~',
+ $this->params->shift('addColumns', ''),
+ -1,
+ PREG_SPLIT_NO_EMPTY
+ );
+
+ $customVars = [];
+ $additionalCols = [];
+ foreach ($columns as $column) {
+ if (preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $column, $m)) {
+ $customVars[$m[1]]['vars'][$m[2]] = null;
+ } else {
+ $additionalCols[] = $column;
+ }
+ }
+
+ if (! empty($customVars)) {
+ $blacklistedProperties = new GlobFilter(
+ $this->getRestrictions('monitoring/blacklist/properties')
+ );
+ $customVars = $blacklistedProperties->removeMatching($customVars);
+ foreach ($customVars as $type => $vars) {
+ foreach ($vars['vars'] as $var => $_) {
+ $additionalCols[] = '_' . $type . '_' . $var;
+ }
+ }
+ }
+
+ $this->view->addColumns = $additionalCols;
+ return $additionalCols;
+ }
+
+ protected function addTitleTab($action, $title, $tip)
+ {
+ $this->getTabs()->add($action, array(
+ 'title' => $tip,
+ 'label' => $title,
+ 'url' => Url::fromRequest()
+ ))->activate($action);
+ $this->view->title = $title;
+ }
+
+ /**
+ * Return all tabs for this controller
+ *
+ * @return Tabs
+ */
+ private function createTabs()
+ {
+ $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction());
+ }
+}
diff --git a/modules/monitoring/application/controllers/ServiceController.php b/modules/monitoring/application/controllers/ServiceController.php
new file mode 100644
index 0000000..d3eeb1c
--- /dev/null
+++ b/modules/monitoring/application/controllers/ServiceController.php
@@ -0,0 +1,147 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Module\Monitoring\Web\Controller\MonitoredObjectController;
+use Icinga\Web\Hook;
+use Icinga\Web\Navigation\Navigation;
+
+class ServiceController extends MonitoredObjectController
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $commandRedirectUrl = 'monitoring/service/show';
+
+ /**
+ * Fetch the requested service from the monitoring backend
+ */
+ public function init()
+ {
+ $service = new Service(
+ $this->backend,
+ $this->params->getRequired('host'),
+ $this->params->getRequired('service')
+ );
+
+ $this->applyRestriction('monitoring/filter/objects', $service);
+
+ if ($service->fetch() === false) {
+ $this->httpNotFound($this->translate('Service not found'));
+ }
+ $this->object = $service;
+ $this->createTabs();
+ $this->getTabs()->activate('service');
+ $this->view->title = $service->service_display_name;
+ $this->view->defaultTitle = join(' :: ', [
+ $service->host_display_name,
+ $this->translate('Services'),
+ $this->view->defaultTitle
+ ]);
+ }
+
+ /**
+ * Get service actions from hook
+ *
+ * @return Navigation
+ */
+ protected function getServiceActions()
+ {
+ $navigation = new Navigation();
+ foreach (Hook::all('Monitoring\\ServiceActions') as $hook) {
+ $navigation->merge($hook->getNavigation($this->object));
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Show a service
+ */
+ public function showAction()
+ {
+ $this->view->actions = $this->getServiceActions();
+ parent::showAction();
+ }
+
+
+ /**
+ * Acknowledge a service problem
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Service Problem'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Add a service comment
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Service Comment'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule a service check
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleServiceCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Service Check'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule a service downtime
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleServiceDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Service Downtime'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit a passive service check result
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Service Check Result'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for a service
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Service Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php
new file mode 100644
index 0000000..6c65592
--- /dev/null
+++ b/modules/monitoring/application/controllers/ServicesController.php
@@ -0,0 +1,262 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Exception;
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\AcknowledgeProblemCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\AddCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ProcessCheckResultCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceCheckCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ScheduleServiceDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm;
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Object\ServiceList;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class ServicesController extends Controller
+{
+ /**
+ * @var ServiceList
+ */
+ protected $serviceList;
+
+ public function init()
+ {
+ $serviceList = new ServiceList($this->backend);
+ $this->applyRestriction('monitoring/filter/objects', $serviceList);
+ $serviceList->addFilter(Filter::fromQueryString(
+ (string) $this->params->without(array('service_problem', 'service_handled', 'showCompact'))
+ ));
+ $this->serviceList = $serviceList;
+ $this->serviceList->setColumns(array(
+ 'host_display_name',
+ 'host_handled',
+ 'host_name',
+ 'host_problem',
+ 'host_state',
+ 'instance_name',
+ 'service_acknowledged',
+ 'service_active_checks_enabled',
+ 'service_description',
+ 'service_display_name',
+ 'service_event_handler_enabled',
+ 'service_flap_detection_enabled',
+ 'service_handled',
+ 'service_in_downtime',
+ 'service_is_flapping',
+ 'service_last_state_change',
+ 'service_notifications_enabled',
+ 'service_obsessing',
+ 'service_passive_checks_enabled',
+ 'service_problem',
+ 'service_state'
+ ));
+ $this->view->baseFilter = $this->serviceList->getFilter();
+ $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/services');
+ $this->getTabs()->add(
+ 'show',
+ array(
+ 'label' => $this->translate('Services') . sprintf(' (%d)', count($this->serviceList)),
+ 'title' => sprintf(
+ $this->translate('Show summarized information for %u services'),
+ count($this->serviceList)
+ ),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
+ $this->view->title = $this->translate('Services');
+ }
+
+ protected function handleCommandForm(ObjectsCommandForm $form)
+ {
+ $form
+ ->setBackend($this->backend)
+ ->setObjects($this->serviceList)
+ ->setRedirectUrl(Url::fromPath('monitoring/services/show')->setParams(
+ $this->params->without('service_active_checks_enabled')
+ ))
+ ->handleRequest();
+
+ $this->view->form = $form;
+ $this->view->objects = $this->serviceList;
+ $this->view->stats = $this->serviceList->getServiceStateSummary();
+ $this->view->serviceStates = true;
+ $this->_helper->viewRenderer('partials/command/objects-command-form', null, true);
+ return $form;
+ }
+
+ public function showAction()
+ {
+ $this->setAutorefreshInterval(15);
+ $activeChecksEnabled = $this->serviceList->getFeatureStatus()['active_checks_enabled'] !== 0;
+ if ($this->Auth()->hasPermission('monitoring/command/schedule-check')
+ || ($this->Auth()->hasPermission('monitoring/command/schedule-check/active-only')
+ && $activeChecksEnabled
+ )
+ ) {
+ $checkNowForm = new CheckNowCommandForm();
+ $checkNowForm
+ ->setObjects($this->serviceList)
+ ->handleRequest();
+ $this->view->checkNowForm = $checkNowForm;
+ }
+
+ $acknowledgedObjects = $this->serviceList->getAcknowledgedObjects();
+ if (! empty($acknowledgedObjects)) {
+ $removeAckForm = new RemoveAcknowledgementCommandForm();
+ $removeAckForm
+ ->setObjects($acknowledgedObjects)
+ ->handleRequest();
+ $this->view->removeAckForm = $removeAckForm;
+ }
+
+ $featureStatus = $this->serviceList->getFeatureStatus();
+ $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array(
+ 'backend' => $this->backend,
+ 'objects' => $this->serviceList
+ ));
+ $toggleFeaturesForm
+ ->load((object) $featureStatus)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+
+ if ($activeChecksEnabled) {
+ $this->view->rescheduleAllLink = Url::fromRequest()
+ ->setPath('monitoring/services/reschedule-check')
+ ->addParams(['service_active_checks_enabled' => true]);
+ }
+
+ $this->view->downtimeAllLink = Url::fromRequest()->setPath('monitoring/services/schedule-downtime');
+ $this->view->processCheckResultAllLink = Url::fromRequest()->setPath(
+ 'monitoring/services/process-check-result'
+ );
+ $this->view->addCommentLink = Url::fromRequest()->setPath('monitoring/services/add-comment');
+ $this->view->deleteCommentLink = Url::fromRequest()->setPath('monitoring/services/delete-comment');
+ $this->view->stats = $this->serviceList->getServiceStateSummary();
+ $this->view->objects = $this->serviceList;
+ $this->view->unhandledObjects = $this->serviceList->getUnhandledObjects();
+ $this->view->problemObjects = $this->serviceList->getProblemObjects();
+ $this->view->downtimeUnhandledLink = Url::fromPath('monitoring/services/schedule-downtime')
+ ->setQueryString($this->serviceList->getUnhandledObjects()->objectsFilter()->toQueryString());
+ $this->view->downtimeLink = Url::fromPath('monitoring/services/schedule-downtime')
+ ->setQueryString($this->serviceList->getProblemObjects()->objectsFilter()->toQueryString());
+ $this->view->acknowledgedObjects = $acknowledgedObjects;
+ $this->view->acknowledgeLink = Url::fromPath('monitoring/services/acknowledge-problem')
+ ->setQueryString($this->serviceList->getUnacknowledgedObjects()->objectsFilter()->toQueryString());
+ $this->view->unacknowledgedObjects = $this->serviceList->getUnacknowledgedObjects();
+ $this->view->objectsInDowntime = $this->serviceList->getObjectsInDowntime();
+ $this->view->inDowntimeLink = Url::fromPath('monitoring/list/services')
+ ->setQueryString($this->serviceList->getObjectsInDowntime()
+ ->objectsFilter(array('host' => 'host_name', 'service' => 'service_description'))->toQueryString());
+ $this->view->showDowntimesLink = Url::fromPath('monitoring/downtimes/show')
+ ->setQueryString(
+ $this->serviceList->getObjectsInDowntime()
+ ->objectsFilter()->andFilter(Filter::where('object_type', 'service'))->toQueryString()
+ );
+ $this->view->commentsLink = Url::fromRequest()
+ ->setPath('monitoring/list/comments');
+ $this->view->sendCustomNotificationLink = Url::fromRequest()->setPath(
+ 'monitoring/services/send-custom-notification'
+ );
+
+ $this->view->extensionsHtml = array();
+ foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) {
+ /** @var DetailviewExtensionHook $hook */
+ try {
+ $html = $hook->setView($this->view)->getHtmlForObjects($this->serviceList);
+ } catch (Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+ }
+
+ /**
+ * Add a service comment
+ */
+ public function addCommentAction()
+ {
+ $this->assertPermission('monitoring/command/comment/add');
+
+ $form = new AddCommentCommandForm();
+ $form->setTitle($this->translate('Add Service Comments'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Acknowledge service problems
+ */
+ public function acknowledgeProblemAction()
+ {
+ $this->assertPermission('monitoring/command/acknowledge-problem');
+
+ $form = new AcknowledgeProblemCommandForm();
+ $form->setTitle($this->translate('Acknowledge Service Problems'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Reschedule service checks
+ */
+ public function rescheduleCheckAction()
+ {
+ $this->assertPermission('monitoring/command/schedule-check');
+
+ $form = new ScheduleServiceCheckCommandForm();
+ $form->setTitle($this->translate('Reschedule Service Checks'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Schedule service downtimes
+ */
+ public function scheduleDowntimeAction()
+ {
+ $this->assertPermission('monitoring/command/downtime/schedule');
+
+ $form = new ScheduleServiceDowntimeCommandForm();
+ $form->setTitle($this->translate('Schedule Service Downtimes'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Submit passive service check results
+ */
+ public function processCheckResultAction()
+ {
+ $this->assertPermission('monitoring/command/process-check-result');
+
+ $form = new ProcessCheckResultCommandForm();
+ $form->setTitle($this->translate('Submit Passive Service Check Results'));
+ $this->handleCommandForm($form);
+ }
+
+ /**
+ * Send a custom notification for services
+ */
+ public function sendCustomNotificationAction()
+ {
+ $this->assertPermission('monitoring/command/send-custom-notification');
+
+ $form = new SendCustomNotificationCommandForm();
+ $form->setTitle($this->translate('Send Custom Service Notification'));
+ $this->handleCommandForm($form);
+ }
+}
diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php
new file mode 100644
index 0000000..f1da561
--- /dev/null
+++ b/modules/monitoring/application/controllers/ShowController.php
@@ -0,0 +1,101 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Data\Filter\FilterEqual;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Security\SecurityException;
+use Icinga\Web\Url;
+
+/**
+ * Class Monitoring_ShowController
+ *
+ * Actions for show context
+ */
+class ShowController extends Controller
+{
+ /**
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ public function init()
+ {
+ $this->view->defaultTitle = $this->translate('Contacts') . ' :: ' . $this->view->defaultTitle;
+
+ parent::init();
+ }
+
+ public function contactAction()
+ {
+ if (! $this->hasPermission('*') && $this->hasPermission('no-monitoring/contacts')) {
+ throw new SecurityException('No permission for %s', 'monitoring/contacts');
+ }
+
+ $contactName = $this->params->getRequired('contact_name');
+
+ $this->getTabs()->add('contact-detail', [
+ 'title' => $this->translate('Contact details'),
+ 'label' => $this->translate('Contact'),
+ 'url' => Url::fromRequest(),
+ 'active' => true
+ ]);
+
+ $query = $this->backend->select()->from('contact', array(
+ 'contact_name',
+ 'contact_id',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ 'contact_notify_service_timeperiod',
+ 'contact_notify_service_recovery',
+ 'contact_notify_service_warning',
+ 'contact_notify_service_critical',
+ 'contact_notify_service_unknown',
+ 'contact_notify_service_flapping',
+ 'contact_notify_service_downtime',
+ 'contact_notify_host_timeperiod',
+ 'contact_notify_host_recovery',
+ 'contact_notify_host_down',
+ 'contact_notify_host_unreachable',
+ 'contact_notify_host_flapping',
+ 'contact_notify_host_downtime',
+ ));
+ $this->applyRestriction('monitoring/filter/objects', $query);
+ $query->whereEx(new FilterEqual('contact_name', '=', $contactName));
+ $contact = $query->getQuery()->fetchRow();
+
+ if ($contact) {
+ $commands = $this->backend->select()->from('command', array(
+ 'command_line',
+ 'command_name'
+ ))->where('contact_id', $contact->contact_id);
+
+ $this->view->commands = $commands;
+
+ $notifications = $this->backend->select()->from('notification', array(
+ 'id',
+ 'host_name',
+ 'service_description',
+ 'notification_output',
+ 'notification_contact_name',
+ 'notification_timestamp',
+ 'notification_state',
+ 'host_display_name',
+ 'service_display_name'
+ ));
+
+ $notifications->where('notification_contact_name', $contactName);
+ $this->applyRestriction('monitoring/filter/objects', $notifications);
+ $this->view->notifications = $notifications;
+ $this->setupLimitControl();
+ $this->setupPaginationControl($this->view->notifications);
+ $this->view->title = $contact->contact_name;
+ }
+
+ $this->view->contact = $contact;
+ $this->view->contactName = $contactName;
+ }
+}
diff --git a/modules/monitoring/application/controllers/TacticalController.php b/modules/monitoring/application/controllers/TacticalController.php
new file mode 100644
index 0000000..b147d45
--- /dev/null
+++ b/modules/monitoring/application/controllers/TacticalController.php
@@ -0,0 +1,128 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use Icinga\Chart\Donut;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class TacticalController extends Controller
+{
+ public function indexAction()
+ {
+ $this->setAutorefreshInterval(15);
+
+ $this->view->title = $this->translate('Tactical Overview');
+ $this->getTabs()->add(
+ 'tactical_overview',
+ array(
+ 'title' => $this->translate(
+ 'Show an overview of all hosts and services, their current'
+ . ' states and monitoring feature utilisation'
+ ),
+ 'label' => $this->translate('Tactical Overview'),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('tactical_overview');
+
+ $stats = $this->backend->select()->from(
+ 'statussummary',
+ array(
+ 'hosts_up',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_pending',
+ 'hosts_pending_not_checked',
+ 'hosts_not_checked',
+
+ 'services_ok',
+ 'services_warning_handled',
+ 'services_warning_unhandled',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_pending',
+ 'services_pending_not_checked',
+ 'services_not_checked',
+ )
+ );
+ $this->applyRestriction('monitoring/filter/objects', $stats);
+
+ $this->setupFilterControl($stats, null, ['host', 'service'], ['format']);
+ $this->view->setHelperFunction('filteredUrl', function ($path, array $params) {
+ $filter = clone $this->view->filterEditor->getFilter();
+
+ return $this->view->url($path)->setParams($params)->addFilter($filter);
+ });
+
+ $this->handleFormatRequest($stats);
+ $summary = $stats->fetchRow();
+
+ // Correct pending counts. Done here instead of in the query for compatibility reasons.
+ $summary->hosts_pending -= $summary->hosts_pending_not_checked;
+ $summary->services_pending -= $summary->services_pending_not_checked;
+
+ $hostSummaryChart = new Donut();
+ $hostSummaryChart
+ ->addSlice($summary->hosts_up, array('class' => 'slice-state-ok'))
+ ->addSlice($summary->hosts_down_handled, array('class' => 'slice-state-critical-handled'))
+ ->addSlice($summary->hosts_down_unhandled, array('class' => 'slice-state-critical'))
+ ->addSlice($summary->hosts_unreachable_handled, array('class' => 'slice-state-unreachable-handled'))
+ ->addSlice($summary->hosts_unreachable_unhandled, array('class' => 'slice-state-unreachable'))
+ ->addSlice($summary->hosts_pending, array('class' => 'slice-state-pending'))
+ ->addSlice($summary->hosts_pending_not_checked, array('class' => 'slice-state-not-checked'))
+ ->setLabelBig($summary->hosts_down_unhandled)
+ ->setLabelBigEyeCatching($summary->hosts_down_unhandled > 0)
+ ->setLabelSmall($this->translate('Hosts Down'));
+
+ $serviceSummaryChart = new Donut();
+ $serviceSummaryChart
+ ->addSlice($summary->services_ok, array('class' => 'slice-state-ok'))
+ ->addSlice($summary->services_warning_handled, array('class' => 'slice-state-warning-handled'))
+ ->addSlice($summary->services_warning_unhandled, array('class' => 'slice-state-warning'))
+ ->addSlice($summary->services_critical_handled, array('class' => 'slice-state-critical-handled'))
+ ->addSlice($summary->services_critical_unhandled, array('class' => 'slice-state-critical'))
+ ->addSlice($summary->services_unknown_handled, array('class' => 'slice-state-unknown-handled'))
+ ->addSlice($summary->services_unknown_unhandled, array('class' => 'slice-state-unknown'))
+ ->addSlice($summary->services_pending, array('class' => 'slice-state-pending'))
+ ->addSlice($summary->services_pending_not_checked, array('class' => 'slice-state-not-checked'))
+ ->setLabelBig($summary->services_critical_unhandled ?: $summary->services_unknown_unhandled)
+ ->setLabelBigState($summary->services_critical_unhandled > 0 ? 'critical' : (
+ $summary->services_unknown_unhandled > 0 ? 'unknown' : null
+ ))
+ ->setLabelSmall($summary->services_critical_unhandled > 0 || $summary->services_unknown_unhandled < 1
+ ? $this->translate('Services Critical')
+ : $this->translate('Services Unknown'));
+
+ $this->view->hostStatusSummaryChart = $hostSummaryChart
+ ->setLabelBigUrl($this->view->filteredUrl(
+ 'monitoring/list/hosts',
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 0,
+ 'sort' => 'host_last_check',
+ 'dir' => 'asc'
+ )
+ ))
+ ->render();
+ $this->view->serviceStatusSummaryChart = $serviceSummaryChart
+ ->setLabelBigUrl($this->view->filteredUrl(
+ 'monitoring/list/services',
+ array(
+ 'service_state' => $summary->services_critical_unhandled > 0
+ || ! $summary->services_unknown_unhandled ? 2 : 3,
+ 'service_handled' => 0,
+ 'sort' => 'service_last_check',
+ 'dir' => 'asc'
+ )
+ ))
+ ->render();
+ $this->view->statusSummary = $summary;
+ }
+}
diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php
new file mode 100644
index 0000000..deeeb36
--- /dev/null
+++ b/modules/monitoring/application/controllers/TimelineController.php
@@ -0,0 +1,325 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Controllers;
+
+use DateInterval;
+use DateTime;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Timeline\TimeLine;
+use Icinga\Module\Monitoring\Timeline\TimeRange;
+use Icinga\Module\Monitoring\Web\Widget\SelectBox;
+use Icinga\Util\Format;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+class TimelineController extends Controller
+{
+ public function indexAction()
+ {
+ $this->getTabs()->add(
+ 'timeline',
+ array(
+ 'title' => $this->translate('Show the number of historical event records grouped by time and type'),
+ 'label' => $this->translate('Timeline'),
+ 'url' => Url::fromRequest()
+ )
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('timeline');
+ $this->view->title = $this->translate('Timeline');
+
+ // TODO: filter for hard_states (precedence adjustments necessary!)
+ $this->setupIntervalBox();
+ list($displayRange, $forecastRange) = $this->buildTimeRanges();
+
+ $detailUrl = Url::fromPath('monitoring/list/eventhistory');
+
+ $timeline = new TimeLine(
+ $this->applyRestriction(
+ 'monitoring/filter/objects',
+ $this->backend->select()->from(
+ 'eventhistory',
+ array(
+ 'name' => 'type',
+ 'time' => 'timestamp'
+ )
+ )
+ ),
+ array(
+ 'notification_ack' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_flapping' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_flapping_end' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_dt_start' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_dt_end' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_custom' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'notification_state' => array(
+ 'class' => 'timeline-notification',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Notifications'),
+ 'groupBy' => 'notification_*'
+ ),
+ 'hard_state' => array(
+ 'class' => 'timeline-hard-state',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Hard state changes')
+ ),
+ 'comment' => array(
+ 'class' => 'timeline-comment',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Comments')
+ ),
+ 'ack' => array(
+ 'class' => 'timeline-ack',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Acknowledgements')
+ ),
+ 'dt_start' => array(
+ 'class' => 'timeline-downtime-start',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Started downtimes')
+ ),
+ 'dt_end' => array(
+ 'class' => 'timeline-downtime-end',
+ 'detailUrl' => $detailUrl,
+ 'label' => mt('monitoring', 'Ended downtimes')
+ )
+ )
+ );
+ $timeline->setMaximumCircleWidth('6em');
+ $timeline->setMinimumCircleWidth('0.3em');
+ $timeline->setDisplayRange($displayRange);
+ $timeline->setForecastRange($forecastRange);
+ $beingExtended = $this->getRequest()->getParam('extend') == 1;
+ $timeline->setSession($this->Window()->getSessionNamespace('timeline', !$beingExtended));
+
+ $this->view->timeline = $timeline;
+ $this->view->nextRange = $forecastRange;
+ $this->view->beingExtended = $beingExtended;
+ $this->view->intervalFormat = $this->getIntervalFormat();
+ $oldBase = $timeline->getCalculationBase(false);
+ $this->view->switchedContext = $oldBase !== null && $oldBase !== $timeline->getCalculationBase(true);
+ }
+
+ /**
+ * Create a select box the user can choose the timeline interval from
+ */
+ private function setupIntervalBox()
+ {
+ $box = new SelectBox(
+ 'intervalBox',
+ array(
+ '4h' => mt('monitoring', '4 Hours'),
+ '1d' => mt('monitoring', 'One day'),
+ '1w' => mt('monitoring', 'One week'),
+ '1m' => mt('monitoring', 'One month'),
+ '1y' => mt('monitoring', 'One year')
+ ),
+ mt('monitoring', 'TimeLine interval'),
+ 'interval'
+ );
+ $box->applyRequest($this->getRequest());
+ $this->view->intervalBox = $box;
+ }
+
+ /**
+ * Return the chosen interval
+ *
+ * @return DateInterval The chosen interval
+ */
+ private function getTimelineInterval()
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ return new DateInterval('P1D');
+ case '1w':
+ return new DateInterval('P1W');
+ case '1m':
+ return new DateInterval('P1M');
+ case '1y':
+ return new DateInterval('P1Y');
+ default:
+ return new DateInterval('PT4H');
+ }
+ }
+
+ /**
+ * Get an appropriate datetime format string for the chosen interval
+ *
+ * @return string
+ */
+ private function getIntervalFormat()
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ return $this->getDateFormat();
+ case '1w':
+ return '\W\e\ek W\<b\r\>\of Y';
+ case '1m':
+ return 'F Y';
+ case '1y':
+ return 'Y';
+ default:
+ return $this->getDateFormat() . '\<b\r\>' . $this->getTimeFormat();
+ }
+ }
+
+ /**
+ * Return a preload interval based on the chosen timeline interval and the given date and time
+ *
+ * @param DateTime $dateTime The date and time to use
+ *
+ * @return DateInterval The interval to pre-load
+ */
+ private function getPreloadInterval(DateTime $dateTime)
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ return DateInterval::createFromDateString('1 week -1 second');
+ case '1w':
+ return DateInterval::createFromDateString('8 weeks -1 second');
+ case '1m':
+ $dateCopy = clone $dateTime;
+ for ($i = 0; $i < 6; $i++) {
+ $dateCopy->sub(new DateInterval('PT' . Format::secondsByMonth($dateCopy) . 'S'));
+ }
+ return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime);
+ case '1y':
+ $dateCopy = clone $dateTime;
+ for ($i = 0; $i < 4; $i++) {
+ $dateCopy->sub(new DateInterval('PT' . Format::secondsByYear($dateCopy) . 'S'));
+ }
+ return $dateCopy->add(new DateInterval('PT1S'))->diff($dateTime);
+ default:
+ return DateInterval::createFromDateString('1 day -1 second');
+ }
+ }
+
+ /**
+ * Extrapolate the given datetime based on the chosen timeline interval
+ *
+ * @param DateTime $dateTime The datetime to extrapolate
+ */
+ private function extrapolateDateTime(DateTime &$dateTime)
+ {
+ switch ($this->view->intervalBox->getInterval()) {
+ case '1d':
+ $dateTime->setTimestamp(strtotime('tomorrow', $dateTime->getTimestamp()) - 1);
+ break;
+ case '1w':
+ $dateTime->setTimestamp(strtotime('next monday', $dateTime->getTimestamp()) - 1);
+ break;
+ case '1m':
+ $dateTime->setTimestamp(
+ strtotime(
+ 'last day of this month',
+ strtotime(
+ 'tomorrow',
+ $dateTime->getTimestamp()
+ ) - 1
+ )
+ );
+ break;
+ case '1y':
+ $dateTime->setTimestamp(strtotime('1 january next year', $dateTime->getTimestamp()) - 1);
+ break;
+ default:
+ $hour = $dateTime->format('G');
+ $end = $hour < 4 ? 4 : ($hour < 8 ? 8 : ($hour < 12 ? 12 : ($hour < 16 ? 16 : ($hour < 20 ? 20 : 24))));
+ $dateTime = DateTime::createFromFormat(
+ 'd/m/y G:i:s',
+ $dateTime->format('d/m/y') . ($end - 1) . ':59:59'
+ );
+ }
+ }
+
+ /**
+ * Return a display- and forecast time range
+ *
+ * Assembles a time range each for display and forecast purposes based on the start- and
+ * end time if given in the current request otherwise based on the current time and a
+ * end time that is calculated based on the chosen timeline interval.
+ *
+ * @return array The resulting time ranges
+ */
+ private function buildTimeRanges()
+ {
+ $startTime = new DateTime();
+ $startParam = $this->_request->getParam('start');
+ $startTimestamp = is_numeric($startParam) ? intval($startParam) : strtotime($startParam ?? '');
+ if ($startTimestamp !== false) {
+ $startTime->setTimestamp($startTimestamp);
+ } else {
+ $this->extrapolateDateTime($startTime);
+ }
+
+ $endTime = clone $startTime;
+ $endParam = $this->_request->getParam('end');
+ $endTimestamp = is_numeric($endParam) ? intval($endParam) : strtotime($endParam ?? '');
+ if ($endTimestamp !== false) {
+ $endTime->setTimestamp($endTimestamp);
+ } else {
+ $endTime->sub($this->getPreloadInterval($startTime));
+ }
+
+ $forecastStart = clone $endTime;
+ $forecastStart->sub(new DateInterval('PT1S'));
+ $forecastEnd = clone $forecastStart;
+ $forecastEnd->sub($this->getPreloadInterval($forecastStart));
+
+ $timelineInterval = $this->getTimelineInterval();
+ return array(
+ new TimeRange($startTime, $endTime, $timelineInterval),
+ new TimeRange($forecastStart, $forecastEnd, $timelineInterval)
+ );
+ }
+
+ /**
+ * Get the user's preferred time format or the application's default
+ *
+ * @return string
+ */
+ private function getTimeFormat()
+ {
+ return 'H:i';
+ }
+
+ /**
+ * Get the user's preferred date format or the application's default
+ *
+ * @return string
+ */
+ private function getDateFormat()
+ {
+ return 'Y-m-d';
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/CommandForm.php b/modules/monitoring/application/forms/Command/CommandForm.php
new file mode 100644
index 0000000..34391cf
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/CommandForm.php
@@ -0,0 +1,92 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command;
+
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Form;
+use Icinga\Web\Request;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransportInterface;
+
+/**
+ * Base class for command forms
+ */
+abstract class CommandForm extends Form
+{
+ /**
+ * Monitoring backend
+ *
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * Set the monitoring backend
+ *
+ * @param MonitoringBackend $backend
+ *
+ * @return $this
+ */
+ public function setBackend(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ return $this;
+ }
+
+ /**
+ * Get the monitoring backend
+ *
+ * @return MonitoringBackend
+ */
+ public function getBackend()
+ {
+ return $this->backend;
+ }
+
+ /**
+ * Get the transport used to send commands
+ *
+ * @param Request $request
+ *
+ * @return CommandTransportInterface
+ *
+ * @throws ConfigurationError
+ */
+ public function getTransport(Request $request)
+ {
+ if (($transportName = $request->getParam('transport')) !== null) {
+ $config = CommandTransport::getConfig();
+ if ($config->hasSection($transportName)) {
+ $transport = CommandTransport::createTransport($config->getSection($transportName));
+ } else {
+ throw new ConfigurationError(sprintf(
+ mt('monitoring', 'Command transport "%s" not found.'),
+ $transportName
+ ));
+ }
+ } else {
+ $transport = new CommandTransport();
+ }
+
+ return $transport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRedirectUrl()
+ {
+ $redirectUrl = parent::getRedirectUrl();
+ // TODO(el): Forms should provide event handling. This is quite hackish
+ $formData = $this->getRequestData();
+ if ($this->wasSent($formData)
+ && (! $this->getSubmitLabel() || $this->isSubmitted())
+ && $this->isValid($formData)
+ ) {
+ $this->getResponse()->setAutoRefreshInterval(1);
+ }
+ return $redirectUrl;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php b/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php
new file mode 100644
index 0000000..ee49962
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Instance/DisableNotificationsExpireCommandForm.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Instance;
+
+use DateTime;
+use DateInterval;
+use Icinga\Module\Monitoring\Command\Instance\DisableNotificationsExpireCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for disabling host and service notifications w/ an optional expire date and time on an Icinga instance
+ */
+class DisableNotificationsExpireCommandForm extends CommandForm
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setRequiredCue(null);
+ $this->setSubmitLabel($this->translate('Disable Notifications'));
+ $this->addDescription($this->translate(
+ 'This command is used to disable host and service notifications for a specific time.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $expireTime = new DateTime();
+ $expireTime->add(new DateInterval('PT1H'));
+ $this->addElement(
+ 'dateTimePicker',
+ 'expire_time',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Expire Time'),
+ 'description' => $this->translate('Set the expire time.'),
+ 'value' => $expireTime
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $disableNotifications = new DisableNotificationsExpireCommand();
+ $disableNotifications
+ ->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp());
+ $this->getTransport($this->request)->send($disableNotifications);
+ Notification::success($this->translate('Disabling host and service notifications..'));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php
new file mode 100644
index 0000000..8b01399
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php
@@ -0,0 +1,279 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Instance;
+
+use Icinga\Module\Monitoring\Command\Instance\ToggleInstanceFeatureCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for enabling or disabling features of Icinga instances
+ */
+class ToggleInstanceFeaturesCommandForm extends CommandForm
+{
+ /**
+ * Instance status
+ *
+ * @var object
+ */
+ protected $status;
+
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setUseFormAutosubmit();
+ $this->setAttrib('class', self::DEFAULT_CLASSES . ' instance-features');
+ }
+
+ /**
+ * Set the instance status
+ *
+ * @param object $status
+ *
+ * @return $this
+ */
+ public function setStatus($status)
+ {
+ $this->status = (object) $status;
+ return $this;
+ }
+
+ /**
+ * Get the instance status
+ *
+ * @return object
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $notificationDescription = null;
+ $isIcinga2 = $this->getBackend()->isIcinga2($this->status->program_version);
+
+ if (! $isIcinga2) {
+ if ((bool) $this->status->notifications_enabled) {
+ if ($this->hasPermission('monitoring/command/feature/instance')) {
+ $notificationDescription = sprintf(
+ '<a aria-label="%1$s" class="action-link" title="%1$s"'
+ . ' href="%2$s" data-base-target="_next">%3$s</a>',
+ $this->translate('Disable notifications for a specific time on a program-wide basis'),
+ $this->getView()->href('monitoring/health/disable-notifications'),
+ $this->translate('Disable temporarily')
+ );
+ } else {
+ $notificationDescription = null;
+ }
+ } elseif ($this->status->disable_notif_expire_time) {
+ $notificationDescription = sprintf(
+ $this->translate('Notifications will be re-enabled in <strong>%s</strong>'),
+ $this->getView()->timeUntil($this->status->disable_notif_expire_time)
+ );
+ }
+ }
+
+ $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : '';
+
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS,
+ array(
+ 'label' => $this->translate('Active Host Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS,
+ array(
+ 'label' => $this->translate('Active Service Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS,
+ array(
+ 'label' => $this->translate('Event Handlers'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION,
+ array(
+ 'label' => $this->translate('Flap Detection'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS,
+ array(
+ 'label' => $this->translate('Notifications'),
+ 'autosubmit' => true,
+ 'description' => $notificationDescription,
+ 'decorators' => array(
+ array('Label', array('tag'=>'span', 'separator' => '', 'class' => 'control-label')),
+ array(
+ 'Description',
+ array('tag' => 'span', 'class' => 'description', 'escape' => false)
+ ),
+ array(array('labelWrap' => 'HtmlTag'), array('tag' => 'div', 'class' => 'control-label-group')),
+ array('ViewHelper', array('separator' => '')),
+ array('Errors', array('separator' => '')),
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group'))
+ ),
+ 'disabled' => $toggleDisabled
+ )
+ );
+
+ if (! $isIcinga2) {
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING,
+ array(
+ 'label' => $this->translate('Obsessing Over Hosts'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING,
+ array(
+ 'label' => $this->translate('Obsessing Over Services'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS,
+ array(
+ 'label' => $this->translate('Passive Host Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS,
+ array(
+ 'label' => $this->translate('Passive Service Checks'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ }
+
+ $this->addElement(
+ 'checkbox',
+ ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA,
+ array(
+ 'label' => $this->translate('Performance Data'),
+ 'autosubmit' => true,
+ 'disabled' => $toggleDisabled
+ )
+ );
+ }
+
+ /**
+ * Load feature status
+ *
+ * @param object $instanceStatus
+ *
+ * @return $this
+ */
+ public function load($instanceStatus)
+ {
+ $this->create();
+ foreach ($this->getValues() as $feature => $enabled) {
+ $this->getElement($feature)->setChecked($instanceStatus->{$feature});
+ }
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $this->assertPermission('monitoring/command/feature/instance');
+
+ $notifications = array(
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS => array(
+ $this->translate('Enabling active host checks..'),
+ $this->translate('Disabling active host checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS => array(
+ $this->translate('Enabling active service checks..'),
+ $this->translate('Disabling active service checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS => array(
+ $this->translate('Enabling event handlers..'),
+ $this->translate('Disabling event handlers..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION => array(
+ $this->translate('Enabling flap detection..'),
+ $this->translate('Disabling flap detection..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS => array(
+ $this->translate('Enabling notifications..'),
+ $this->translate('Disabling notifications..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING => array(
+ $this->translate('Enabling obsessing over hosts..'),
+ $this->translate('Disabling obsessing over hosts..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING => array(
+ $this->translate('Enabling obsessing over services..'),
+ $this->translate('Disabling obsessing over services..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS => array(
+ $this->translate('Enabling passive host checks..'),
+ $this->translate('Disabling passive host checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS => array(
+ $this->translate('Enabling passive service checks..'),
+ $this->translate('Disabling passive service checks..')
+ ),
+ ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA => array(
+ $this->translate('Enabling performance data..'),
+ $this->translate('Disabling performance data..')
+ )
+ );
+
+ foreach ($this->getValues() as $feature => $enabled) {
+ if ((bool) $this->status->{$feature} !== (bool) $enabled) {
+ $toggleFeature = new ToggleInstanceFeatureCommand();
+ $toggleFeature
+ ->setFeature($feature)
+ ->setEnabled($enabled);
+ $this->getTransport($this->request)->send($toggleFeature);
+
+ Notification::success(
+ $notifications[$feature][$enabled ? 0 : 1]
+ );
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php
new file mode 100644
index 0000000..c7caf5d
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/AcknowledgeProblemCommandForm.php
@@ -0,0 +1,172 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateTime;
+use DateInterval;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for acknowledging host or service problems
+ */
+class AcknowledgeProblemCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to acknowledge host or service problems. When a problem is acknowledged,'
+ . ' future notifications about problems are temporarily disabled until the host or service'
+ . ' recovers.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Acknowledge problem', 'Acknowledge problems', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $config = Config::module('monitoring');
+
+ $acknowledgeExpire = (bool) $config->get('settings', 'acknowledge_expire', false);
+
+ $this->addElements(array(
+ array(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ ),
+ 'attribs' => array('class' => 'autofocus')
+ )
+ ),
+ array(
+ 'checkbox',
+ 'persistent',
+ array(
+ 'label' => $this->translate('Persistent Comment'),
+ 'value' => (bool) $config->get('settings', 'acknowledge_persistent', false),
+ 'description' => $this->translate(
+ 'If you would like the comment to remain even when the acknowledgement is removed, check this'
+ . ' option.'
+ )
+ )
+ ),
+ array(
+ 'checkbox',
+ 'expire',
+ array(
+ 'label' => $this->translate('Use Expire Time'),
+ 'value' => $acknowledgeExpire,
+ 'description' => $this->translate(
+ 'If the acknowledgement should expire, check this option.'
+ ),
+ 'autosubmit' => true
+ )
+ )
+ ));
+ $expire = isset($formData['expire']) ? $formData['expire'] : $acknowledgeExpire;
+ if ($expire) {
+ $expireTime = new DateTime();
+ $expireTime->add(new DateInterval($config->get('settings', 'acknowledge_expire_time', 'PT1H')));
+ $this->addElement(
+ 'dateTimePicker',
+ 'expire_time',
+ array(
+ 'label' => $this->translate('Expire Time'),
+ 'value' => $expireTime,
+ 'description' => $this->translate(
+ 'Enter the expire date and time for this acknowledgement here. Icinga will delete the'
+ . ' acknowledgement after this time expired.'
+ )
+ )
+ );
+ $this->addDisplayGroup(
+ array('expire', 'expire_time'),
+ 'expire-expire_time',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div'))
+ )
+ )
+ );
+ }
+ $this->addElements(array(
+ array(
+ 'checkbox',
+ 'sticky',
+ array(
+ 'label' => $this->translate('Sticky Acknowledgement'),
+ 'value' => (bool) $config->get('settings', 'acknowledge_sticky', false),
+ 'description' => $this->translate(
+ 'If you want the acknowledgement to remain until the host or service recovers even if the host'
+ . ' or service changes state, check this option.'
+ )
+ )
+ ),
+ array(
+ 'checkbox',
+ 'notify',
+ array(
+ 'label' => $this->translate('Send Notification'),
+ 'value' => (bool) $config->get('settings', 'acknowledge_notify', true),
+ 'description' => $this->translate(
+ 'If you do not want an acknowledgement notification to be sent out to the appropriate contacts,'
+ . ' uncheck this option.'
+ )
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $ack = new AcknowledgeProblemCommand();
+ $ack
+ ->setObject($object)
+ ->setComment($this->getElement('comment')->getValue())
+ ->setAuthor($this->request->getUser()->getUsername())
+ ->setPersistent($this->getElement('persistent')->isChecked())
+ ->setSticky($this->getElement('sticky')->isChecked())
+ ->setNotify($this->getElement('notify')->isChecked());
+ if ($this->getElement('expire')->isChecked()) {
+ $ack->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp());
+ }
+ $this->getTransport($this->request)->send($ack);
+ }
+ Notification::success($this->translatePlural(
+ 'Acknowledging problem..',
+ 'Acknowledging problems..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php
new file mode 100644
index 0000000..72133a0
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/AddCommentCommandForm.php
@@ -0,0 +1,148 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateInterval;
+use DateTime;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\AddCommentCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for adding host or service comments
+ */
+class AddCommentCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate('This command is used to add host or service comments.'));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Add comment', 'Add comments', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElement(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ ),
+ 'attribs' => array('class' => 'autofocus')
+ )
+ );
+ if (! $this->getBackend()->isIcinga2()) {
+ $this->addElement(
+ 'checkbox',
+ 'persistent',
+ array(
+ 'label' => $this->translate('Persistent'),
+ 'value' => (bool) Config::module('monitoring')->get('settings', 'comment_persistent', true),
+ 'description' => $this->translate(
+ 'If you uncheck this option, the comment will automatically be deleted the next time Icinga is'
+ . ' restarted.'
+ )
+ )
+ );
+ }
+
+ if (version_compare($this->getBackend()->getProgramVersion(), '2.13.0', '>=')) {
+ $config = Config::module('monitoring');
+ $commentExpire = (bool) $config->get('settings', 'comment_expire', false);
+
+ $this->addElement(
+ 'checkbox',
+ 'expire',
+ [
+ 'label' => $this->translate('Use Expire Time'),
+ 'value' => $commentExpire,
+ 'description' => $this->translate('If the comment should expire, check this option.'),
+ 'autosubmit' => true
+ ]
+ );
+
+ if (isset($formData['expire']) ? $formData['expire'] : $commentExpire) {
+ $expireTime = new DateTime();
+ $expireTime->add(new DateInterval($config->get('settings', 'comment_expire_time', 'PT1H')));
+
+ $this->addElement(
+ 'dateTimePicker',
+ 'expire_time',
+ [
+ 'label' => $this->translate('Expire Time'),
+ 'value' => $expireTime,
+ 'description' => $this->translate(
+ 'Enter the expire date and time for this comment here. Icinga will delete the'
+ . ' comment after this time expired.'
+ )
+ ]
+ );
+
+ $this->addDisplayGroup(
+ ['expire', 'expire_time'],
+ 'expire-expire_time',
+ [
+ 'decorators' => [
+ 'FormElements',
+ ['HtmlTag', ['tag' => 'div']]
+ ]
+ ]
+ );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $comment = new AddCommentCommand();
+ $comment->setObject($object);
+ $comment->setComment($this->getElement('comment')->getValue());
+ $comment->setAuthor($this->request->getUser()->getUsername());
+ if (($persistent = $this->getElement('persistent')) !== null) {
+ $comment->setPersistent($persistent->isChecked());
+ }
+
+ $expire = $this->getElement('expire');
+
+ if ($expire !== null && $expire->isChecked()) {
+ $comment->setExpireTime($this->getElement('expire_time')->getValue()->getTimestamp());
+ }
+
+ $this->getTransport($this->request)->send($comment);
+ }
+ Notification::success($this->translatePlural(
+ 'Adding comment..',
+ 'Adding comments..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php b/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php
new file mode 100644
index 0000000..a586d2f
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/CheckNowCommandForm.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostCheckCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for immediately checking hosts or services
+ */
+class CheckNowCommandForm extends ObjectsCommandForm
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Zend_Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ $this->setSubmitLabel($this->translate('Check now'));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::addSubmitButton() For the method documentation.
+ */
+ public function addSubmitButton()
+ {
+ $this->addElements(array(
+ array(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('arrows-cw') . $this->translate('Check now'),
+ 'type' => 'submit',
+ 'title' => $this->translate('Schedule the next active check to run immediately'),
+ 'value' => $this->translate('Check now')
+ )
+ )
+ ));
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if (! $object->active_checks_enabled
+ && ! $this->Auth()->hasPermission('monitoring/command/schedule-check')
+ ) {
+ continue;
+ }
+
+ if ($object->getType() === $object::TYPE_HOST) {
+ $check = new ScheduleHostCheckCommand();
+ } else {
+ $check = new ScheduleServiceCheckCommand();
+ }
+ $check
+ ->setObject($object)
+ ->setForced()
+ ->setCheckTime(time());
+ $this->getTransport($this->request)->send($check);
+ }
+ Notification::success(mtp(
+ 'monitoring',
+ 'Scheduling check..',
+ 'Scheduling checks..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php
new file mode 100644
index 0000000..cd15b19
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentCommandForm.php
@@ -0,0 +1,109 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service comments
+ */
+class DeleteCommentCommandForm extends CommandForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('cancel'),
+ 'title' => $this->translate('Delete this comment'),
+ 'type' => 'submit'
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(
+ array(
+ array(
+ 'hidden',
+ 'comment_id',
+ array(
+ 'required' => true,
+ 'validators' => array('NotEmpty'),
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'comment_is_service',
+ array(
+ 'filters' => array('Boolean'),
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'comment_name',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'redirect',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $cmd = new DeleteCommentCommand();
+ $cmd
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setCommentId($this->getElement('comment_id')->getValue())
+ ->setCommentName($this->getElement('comment_name')->getValue())
+ ->setIsService($this->getElement('comment_is_service')->getValue());
+ $this->getTransport($this->request)->send($cmd);
+ $redirect = $this->getElement('redirect')->getValue();
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ }
+ Notification::success($this->translate('Deleting comment..'));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php
new file mode 100644
index 0000000..70ea7b8
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteCommentsCommandForm.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service comments
+ */
+class DeleteCommentsCommandForm extends CommandForm
+{
+ /**
+ * The comments to delete
+ *
+ * @var array
+ */
+ protected $comments;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * Set the comments to delete
+ *
+ * @param iterable $comments
+ *
+ * @return $this
+ */
+ public function setComments($comments)
+ {
+ $this->comments = $comments;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'hidden',
+ 'redirect',
+ array('decorators' => array('ViewHelper'))
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Remove', 'Remove All', count($this->comments));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->comments as $comment) {
+ $cmd = new DeleteCommentCommand();
+ $cmd
+ ->setCommentId($comment->id)
+ ->setCommentName($comment->name)
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setIsService(isset($comment->service_description));
+ $this->getTransport($this->request)->send($cmd);
+ }
+ $redirect = $this->getElement('redirect')->getValue();
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ }
+ Notification::success(
+ $this->translatePlural('Deleting comment..', 'Deleting comments..', count($this->comments))
+ );
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php
new file mode 100644
index 0000000..79700cb
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimeCommandForm.php
@@ -0,0 +1,129 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service downtimes
+ */
+class DeleteDowntimeCommandForm extends CommandForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getView()->icon('cancel'),
+ 'title' => $this->translate('Delete this downtime'),
+ 'type' => 'submit'
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(
+ array(
+ array(
+ 'hidden',
+ 'downtime_id',
+ array(
+ 'decorators' => array('ViewHelper'),
+ 'required' => true,
+ 'validators' => array('NotEmpty')
+ )
+ ),
+ array(
+ 'hidden',
+ 'downtime_is_service',
+ array(
+ 'decorators' => array('ViewHelper'),
+ 'filters' => array('Boolean')
+ )
+ ),
+ array(
+ 'hidden',
+ 'downtime_name',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ ),
+ array(
+ 'hidden',
+ 'redirect',
+ array(
+ 'decorators' => array('ViewHelper')
+ )
+ )
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ $cmd = new DeleteDowntimeCommand();
+ $cmd
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setDowntimeId($this->getElement('downtime_id')->getValue())
+ ->setDowntimeName($this->getElement('downtime_name')->getValue())
+ ->setIsService($this->getElement('downtime_is_service')->getValue());
+
+ $errorMsg = null;
+
+ try {
+ $this->getTransport($this->request)->send($cmd);
+ } catch (CommandTransportException $e) {
+ $errorMsg = $e->getMessage();
+ }
+
+ if (! $errorMsg) {
+ $redirect = $this->getElement('redirect')->getValue();
+ Notification::success($this->translate('Deleting downtime.'));
+ } else {
+ if (! $this->getIsApiTarget()) {
+ $redirect = $this->getRequest()->getUrl();
+ }
+
+ Notification::error($errorMsg);
+ }
+
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php
new file mode 100644
index 0000000..d4ee803
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/DeleteDowntimesCommandForm.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand;
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Web\Notification;
+
+/**
+ * Form for deleting host or service downtimes
+ */
+class DeleteDowntimesCommandForm extends CommandForm
+{
+ /**
+ * The downtimes to delete
+ *
+ * @var array
+ */
+ protected $downtimes;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * Set the downtimes to delete
+ *
+ * @param iterable $downtimes
+ *
+ * @return $this
+ */
+ public function setDowntimes($downtimes)
+ {
+ $this->downtimes = $downtimes;
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'hidden',
+ 'redirect',
+ array('decorators' => array('ViewHelper'))
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Remove', 'Remove All', count($this->downtimes));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->downtimes as $downtime) {
+ $delDowntime = new DeleteDowntimeCommand();
+ $delDowntime
+ ->setDowntimeId($downtime->id)
+ ->setDowntimeName($downtime->name)
+ ->setAuthor($this->Auth()->getUser()->getUsername())
+ ->setIsService(isset($downtime->service_description));
+ $this->getTransport($this->request)->send($delDowntime);
+ }
+ $redirect = $this->getElement('redirect')->getValue();
+ if (! empty($redirect)) {
+ $this->setRedirectUrl($redirect);
+ }
+ Notification::success(
+ $this->translatePlural('Deleting downtime..', 'Deleting downtimes..', count($this->downtimes))
+ );
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php b/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php
new file mode 100644
index 0000000..928c365
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ObjectsCommandForm.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Forms\Command\CommandForm;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Base class for Icinga object command forms
+ */
+abstract class ObjectsCommandForm extends CommandForm
+{
+ /**
+ * Involved Icinga objects
+ *
+ * @var array|\Traversable|\ArrayAccess
+ */
+ protected $objects;
+
+ /**
+ * Set the involved Icinga objects
+ *
+ * @param $objects MonitoredObject|array|\Traversable|\ArrayAccess
+ *
+ * @return $this
+ */
+ public function setObjects($objects)
+ {
+ if ($objects instanceof MonitoredObject) {
+ $this->objects = array($objects);
+ } else {
+ $this->objects = $objects;
+ }
+ return $this;
+ }
+
+ /**
+ * Get the involved Icinga objects
+ *
+ * @return array|\ArrayAccess|\Traversable
+ */
+ public function getObjects()
+ {
+ return $this->objects;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php b/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php
new file mode 100644
index 0000000..ab46071
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ProcessCheckResultCommandForm.php
@@ -0,0 +1,139 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Web\Notification;
+use Icinga\Module\Monitoring\Command\Object\ProcessCheckResultCommand;
+
+/**
+ * Form for submitting a passive host or service check result
+ */
+class ProcessCheckResultCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to submit passive host or service check results.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural(
+ 'Submit Passive Check Result',
+ 'Submit Passive Check Results',
+ count($this->objects)
+ );
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData)
+ {
+ foreach ($this->getObjects() as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ // Nasty, but as getObjects() returns everything but an object with a real
+ // iterator interface this is the only way to fetch just the first element
+ break;
+ }
+
+ $this->addElement(
+ 'select',
+ 'status',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Status'),
+ 'description' => $this->translate('The state this check result should report'),
+ 'multiOptions' => $object->getType() === $object::TYPE_HOST ? $this->getHostMultiOptions() : array(
+ ProcessCheckResultCommand::SERVICE_OK => $this->translate('OK', 'icinga.state'),
+ ProcessCheckResultCommand::SERVICE_WARNING => $this->translate('WARNING', 'icinga.state'),
+ ProcessCheckResultCommand::SERVICE_CRITICAL => $this->translate('CRITICAL', 'icinga.state'),
+ ProcessCheckResultCommand::SERVICE_UNKNOWN => $this->translate('UNKNOWN', 'icinga.state')
+ )
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'output',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Output'),
+ 'description' => $this->translate('The plugin output of this check result')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'perfdata',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Performance Data'),
+ 'description' => $this->translate(
+ 'The performance data of this check result. Leave empty'
+ . ' if this check result has no performance data'
+ )
+ )
+ );
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if (! $object->passive_checks_enabled) {
+ continue;
+ }
+
+ $command = new ProcessCheckResultCommand();
+ $command->setObject($object);
+ $command->setStatus($this->getValue('status'));
+ $command->setOutput($this->getValue('output'));
+
+ if ($perfdata = $this->getValue('perfdata')) {
+ $command->setPerformanceData($perfdata);
+ }
+
+ $this->getTransport($this->request)->send($command);
+ }
+
+ Notification::success($this->translatePlural(
+ 'Processing check result..',
+ 'Processing check results..',
+ count($this->objects)
+ ));
+
+ return true;
+ }
+
+ /**
+ * Returns the available host options based on the program version
+ *
+ * @return array
+ */
+ protected function getHostMultiOptions()
+ {
+ $options = array(
+ ProcessCheckResultCommand::HOST_UP => $this->translate('UP', 'icinga.state'),
+ ProcessCheckResultCommand::HOST_DOWN => $this->translate('DOWN', 'icinga.state')
+ );
+
+ if (! $this->getBackend()->isIcinga2()) {
+ $options[ProcessCheckResultCommand::HOST_UNREACHABLE] = $this->translate('UNREACHABLE', 'icinga.state');
+ }
+
+ return $options;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php b/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php
new file mode 100644
index 0000000..e45a055
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/RemoveAcknowledgementCommandForm.php
@@ -0,0 +1,122 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\RemoveAcknowledgementCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for removing host or service problem acknowledgements
+ */
+class RemoveAcknowledgementCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Whether to show the submit label next to the remove icon
+ *
+ * The submit label is disabled in detail views but should be enabled in multi-select views.
+ *
+ * @var bool
+ */
+ protected $labelEnabled = false;
+
+ /**
+ * Whether to show the submit label next to the remove icon
+ *
+ * @return bool
+ */
+ public function isLabelEnabled()
+ {
+ return $this->labelEnabled;
+ }
+
+ /**
+ * Set whether to show the submit label next to the remove icon
+ *
+ * @param bool $labelEnabled
+ *
+ * @return $this
+ */
+ public function setLabelEnabled($labelEnabled)
+ {
+ $this->labelEnabled = (bool) $labelEnabled;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setAttrib('class', 'inline');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addSubmitButton()
+ {
+ $this->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'class' => 'link-button spinner',
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ ),
+ 'escape' => false,
+ 'ignore' => true,
+ 'label' => $this->getSubmitLabel(),
+ 'title' => $this->translatePlural(
+ 'Remove acknowledgement',
+ 'Remove acknowledgements',
+ count($this->objects)
+ ),
+ 'type' => 'submit'
+ )
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ $label = $this->getView()->icon('cancel');
+ if ($this->isLabelEnabled()) {
+ $label .= $this->translatePlural(
+ 'Remove acknowledgement',
+ 'Remove acknowledgements',
+ count($this->objects)
+ );
+ }
+
+ return $label;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $removeAck = new RemoveAcknowledgementCommand();
+ $removeAck->setObject($object);
+ $removeAck->setAuthor($this->Auth()->getUser()->getUsername());
+ $this->getTransport($this->request)->send($removeAck);
+ }
+ Notification::success(mtp(
+ 'monitoring',
+ 'Removing acknowledgement..',
+ 'Removing acknowledgements..',
+ count($this->objects)
+ ));
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php
new file mode 100644
index 0000000..55b044f
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleHostCheckCommandForm.php
@@ -0,0 +1,67 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostCheckCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form for scheduling host checks
+ */
+class ScheduleHostCheckCommandForm extends ScheduleServiceCheckCommandForm
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $config = Config::module('monitoring');
+
+ parent::createElements($formData);
+ $this->addElements(array(
+ array(
+ 'checkbox',
+ 'all_services',
+ array(
+ 'label' => $this->translate('All Services'),
+ 'value' => (bool) $config->get('settings', 'hostcheck_all_services', false),
+ 'description' => $this->translate(
+ 'Schedule check for all services on the hosts and the hosts themselves.'
+ )
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ if (! $object->active_checks_enabled
+ && ! $this->Auth()->hasPermission('monitoring/command/schedule-check')
+ ) {
+ continue;
+ }
+
+ $check = new ScheduleHostCheckCommand();
+ $check
+ ->setObject($object)
+ ->setOfAllServices($this->getElement('all_services')->isChecked());
+ $this->scheduleCheck($check, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling host check..',
+ 'Scheduling host checks..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php
new file mode 100644
index 0000000..89db1ce
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleHostDowntimeCommandForm.php
@@ -0,0 +1,178 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateInterval;
+use DateTime;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\ApiScheduleHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Transport\ApiCommandTransport;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
+use Icinga\Web\Notification;
+
+/**
+ * Form for scheduling host downtimes
+ */
+class ScheduleHostDowntimeCommandForm extends ScheduleServiceDowntimeCommandForm
+{
+ /** @var bool */
+ protected $hostDowntimeAllServices;
+
+ public function init()
+ {
+ $this->start = new DateTime();
+ $config = Config::module('monitoring');
+ $this->commentText = $config->get('settings', 'hostdowntime_comment_text');
+
+ $this->hostDowntimeAllServices = (bool) $config->get('settings', 'hostdowntime_all_services', false);
+
+ $fixedEnd = clone $this->start;
+ $fixed = $config->get('settings', 'hostdowntime_end_fixed', 'PT1H');
+ $this->fixedEnd = $fixedEnd->add(new DateInterval($fixed));
+
+ $flexibleEnd = clone $this->start;
+ $flexible = $config->get('settings', 'hostdowntime_end_flexible', 'PT1H');
+ $this->flexibleEnd = $flexibleEnd->add(new DateInterval($flexible));
+
+ $flexibleDuration = $config->get('settings', 'hostdowntime_flexible_duration', 'PT2H');
+ $this->flexibleDuration = new DateInterval($flexibleDuration);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ parent::createElements($formData);
+
+ $this->addElement(
+ 'checkbox',
+ 'all_services',
+ array(
+ 'description' => $this->translate(
+ 'Schedule downtime for all services on the hosts and the hosts themselves.'
+ ),
+ 'label' => $this->translate('All Services'),
+ 'value' => $this->hostDowntimeAllServices
+ )
+ );
+
+ if (! $this->getBackend()->isIcinga2()
+ || version_compare($this->getBackend()->getProgramVersion(), '2.6.0', '>=')
+ ) {
+ $this->addElement(
+ 'select',
+ 'child_hosts',
+ array(
+ 'description' => $this->translate(
+ 'Define what should be done with the child hosts of the hosts.'
+ ),
+ 'label' => $this->translate('Child Hosts'),
+ 'multiOptions' => array(
+ 0 => $this->translate('Do nothing with child hosts'),
+ 1 => $this->translate('Schedule triggered downtime for all child hosts'),
+ 2 => $this->translate('Schedule non-triggered downtime for all child hosts')
+ ),
+ 'value' => 0
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $end = $this->getValue('end')->getTimestamp();
+ if ($end <= $this->getValue('start')->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('The end time must be greater than the start time'));
+ return false;
+ }
+
+ $now = new DateTime;
+ if ($end <= $now->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('A downtime must not be in the past'));
+ return false;
+ }
+
+ // Send all_services API parameter if Icinga is equal to or greater than 2.11.0
+ $allServicesNative = version_compare($this->getBackend()->getProgramVersion(), '2.11.0', '>=');
+ // Use ApiScheduleHostDowntimeCommand only when Icinga is equal to or greater than 2.11.0 and
+ // when an API command transport is requested or only API command transports are configured:
+ $useApiDowntime = $allServicesNative;
+ if ($useApiDowntime) {
+ $transport = $this->getTransport($this->getRequest());
+ if ($transport instanceof CommandTransport) {
+ foreach ($transport::getConfig() as $config) {
+ if (strtolower($config->transport) !== 'api') {
+ $useApiDowntime = false;
+ break;
+ }
+ }
+ } elseif (! $transport instanceof ApiCommandTransport) {
+ $useApiDowntime = false;
+ }
+ }
+
+ foreach ($this->objects as $object) {
+ if ($useApiDowntime) {
+ $hostDowntime = (new ApiScheduleHostDowntimeCommand())
+ ->setForAllServices($this->getElement('all_services')->isChecked())
+ ->setChildOptions((int) $this->getElement('child_hosts')->getValue());
+ // Code duplicated for readability and scope
+ $hostDowntime->setObject($object);
+ $this->scheduleDowntime($hostDowntime, $this->request);
+
+ continue;
+ }
+
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ if (($childHostsEl = $this->getElement('child_hosts')) !== null) {
+ $childHosts = (int) $childHostsEl->getValue();
+ } else {
+ $childHosts = 0;
+ }
+ $allServices = $this->getElement('all_services')->isChecked();
+ if ($childHosts === 0) {
+ $hostDowntime = (new ScheduleHostDowntimeCommand())
+ ->setForAllServicesNative($allServicesNative);
+ if ($allServices === true) {
+ $hostDowntime->setForAllServices();
+ };
+ } else {
+ $hostDowntime = new PropagateHostDowntimeCommand();
+ if ($childHosts === 1) {
+ $hostDowntime->setTriggered();
+ }
+ if ($allServices === true) {
+ foreach ($object->services as $service) {
+ $serviceDowntime = new ScheduleServiceDowntimeCommand();
+ $serviceDowntime->setObject($service);
+ $this->scheduleDowntime($serviceDowntime, $this->request);
+ }
+ }
+ }
+ $hostDowntime->setObject($object);
+ $this->scheduleDowntime($hostDowntime, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling host downtime..',
+ 'Scheduling host downtimes..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php
new file mode 100644
index 0000000..f65aea8
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceCheckCommandForm.php
@@ -0,0 +1,112 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateTime;
+use DateInterval;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand;
+use Icinga\Web\Notification;
+use Icinga\Web\Request;
+
+/**
+ * Form for scheduling service checks
+ */
+class ScheduleServiceCheckCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to schedule the next check of hosts or services. Icinga will re-queue the'
+ . ' hosts or services to be checked at the time you specify.'
+ ));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Schedule check', 'Schedule checks', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $checkTime = new DateTime();
+ $checkTime->add(new DateInterval('PT1H'));
+ $this->addElements(array(
+ array(
+ 'dateTimePicker',
+ 'check_time',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Check Time'),
+ 'description' => $this->translate(
+ 'Set the date and time when the check should be scheduled.'
+ ),
+ 'value' => $checkTime
+ )
+ ),
+ array(
+ 'checkbox',
+ 'force_check',
+ array(
+ 'label' => $this->translate('Force Check'),
+ 'description' => $this->translate(
+ 'If you select this option, Icinga will force a check regardless of both what time the'
+ . ' scheduled check occurs and whether or not checks are enabled.'
+ )
+ )
+ )
+ ));
+ return $this;
+ }
+
+ /**
+ * Schedule a check
+ *
+ * @param ScheduleServiceCheckCommand $check
+ * @param Request $request
+ */
+ public function scheduleCheck(ScheduleServiceCheckCommand $check, Request $request)
+ {
+ $check
+ ->setForced($this->getElement('force_check')->isChecked())
+ ->setCheckTime($this->getElement('check_time')->getValue()->getTimestamp());
+ $this->getTransport($request)->send($check);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ if (! $object->active_checks_enabled
+ && ! $this->Auth()->hasPermission('monitoring/command/schedule-check')
+ ) {
+ continue;
+ }
+
+ $check = new ScheduleServiceCheckCommand();
+ $check->setObject($object);
+ $this->scheduleCheck($check, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling service check..',
+ 'Scheduling service checks..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php
new file mode 100644
index 0000000..90d50d4
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ScheduleServiceDowntimeCommandForm.php
@@ -0,0 +1,263 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use DateTime;
+use DateInterval;
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand;
+use Icinga\Web\Notification;
+use Icinga\Web\Request;
+
+/**
+ * Form for scheduling service downtimes
+ */
+class ScheduleServiceDowntimeCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Fixed downtime
+ */
+ const FIXED = 'fixed';
+
+ /**
+ * Flexible downtime
+ */
+ const FLEXIBLE = 'flexible';
+
+ /** @var DateTime downtime start */
+ protected $start;
+
+ /** @var DateTime fixed downtime end */
+ protected $fixedEnd;
+
+ /** @var DateTime flexible downtime end */
+ protected $flexibleEnd;
+
+ /** @var DateInterval flexible downtime duration */
+ protected $flexibleDuration;
+
+ /** @var mixed Comment text */
+ protected $commentText;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->start = new DateTime();
+
+ $config = Config::module('monitoring');
+
+ $this->commentText = $config->get('settings', 'servicedowntime_comment_text');
+ $fixedEnd = clone $this->start;
+ $fixed = $config->get('settings', 'servicedowntime_end_fixed', 'PT1H');
+ $this->fixedEnd = $fixedEnd->add(new DateInterval($fixed));
+
+ $flexibleEnd = clone $this->start;
+ $flexible = $config->get('settings', 'servicedowntime_end_flexible', 'PT1H');
+ $this->flexibleEnd = $flexibleEnd->add(new DateInterval($flexible));
+
+ $flexibleDuration = $config->get('settings', 'servicedowntime_flexible_duration', 'PT2H');
+ $this->flexibleDuration = new DateInterval($flexibleDuration);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::getSubmitLabel() For the method documentation.
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Schedule downtime', 'Schedule downtimes', count($this->objects));
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addDescription($this->translate(
+ 'This command is used to schedule host and service downtimes. During the specified downtime,'
+ . ' Icinga will not send notifications out about the hosts and services. When the scheduled'
+ . ' downtime expires, Icinga will send out notifications for the hosts and services as it'
+ . ' normally would. Scheduled downtimes are preserved across program shutdowns and'
+ . ' restarts.'
+ ));
+
+ $isFlexible = (bool) isset($formData['type']) && $formData['type'] === self::FLEXIBLE;
+
+ $this->addElements(array(
+ array(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ ),
+ 'attribs' => array('class' => 'autofocus'),
+ 'value' => $this->commentText
+ )
+ ),
+ array(
+ 'dateTimePicker',
+ 'start',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Start Time'),
+ 'description' => $this->translate('Set the start date and time for the downtime.'),
+ 'value' => $this->start
+ )
+ ),
+ array(
+ 'dateTimePicker',
+ 'end',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('End Time'),
+ 'description' => $this->translate('Set the end date and time for the downtime.'),
+ 'preserveDefault' => true,
+ 'value' => $isFlexible ? $this->flexibleEnd : $this->fixedEnd
+ )
+ ),
+ array(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Type'),
+ 'description' => $this->translate(
+ 'If you select the fixed option, the downtime will be in effect between the start and end'
+ . ' times you specify whereas a flexible downtime starts when the host or service enters a'
+ . ' problem state sometime between the start and end times you specified and lasts as long'
+ . ' as the duration time you enter. The duration fields do not apply for fixed downtimes.'
+ ),
+ 'multiOptions' => array(
+ self::FIXED => $this->translate('Fixed'),
+ self::FLEXIBLE => $this->translate('Flexible')
+ ),
+ 'validators' => array(
+ array(
+ 'InArray',
+ true,
+ array(array(self::FIXED, self::FLEXIBLE))
+ )
+ )
+ )
+ )
+ ));
+ $this->addDisplayGroup(
+ array('start', 'end'),
+ 'start-end',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div'))
+ )
+ )
+ );
+ if ($isFlexible) {
+ $this->addElements(array(
+ array(
+ 'number',
+ 'hours',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Hours'),
+ 'value' => $this->flexibleDuration->h,
+ 'min' => -1
+ )
+ ),
+ array(
+ 'number',
+ 'minutes',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Minutes'),
+ 'value' => $this->flexibleDuration->m,
+ 'min' => -1
+ )
+ )
+ ));
+ $this->addDisplayGroup(
+ array('hours', 'minutes'),
+ 'duration',
+ array(
+ 'legend' => $this->translate('Flexible Duration'),
+ 'description' => $this->translate(
+ 'Enter here the duration of the downtime. The downtime will be automatically deleted after this'
+ . ' time expired.'
+ ),
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div')),
+ array(
+ 'Description',
+ array('tag' => 'span', 'class' => 'description', 'placement' => 'prepend')
+ ),
+ 'Fieldset'
+ )
+ )
+ );
+ }
+ return $this;
+ }
+
+ public function scheduleDowntime(ScheduleServiceDowntimeCommand $downtime, Request $request)
+ {
+ $downtime
+ ->setComment($this->getElement('comment')->getValue())
+ ->setAuthor($request->getUser()->getUsername())
+ ->setStart($this->getElement('start')->getValue()->getTimestamp())
+ ->setEnd($this->getElement('end')->getValue()->getTimestamp());
+ if ($this->getElement('type')->getValue() === self::FLEXIBLE) {
+ $downtime->setFixed(false);
+ $downtime->setDuration(
+ (float) $this->getElement('hours')->getValue() * 3600
+ + (float) $this->getElement('minutes')->getValue() * 60
+ );
+ }
+ $this->getTransport($request)->send($downtime);
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $end = $this->getValue('end')->getTimestamp();
+ if ($end <= $this->getValue('start')->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('The end time must be greater than the start time'));
+ return false;
+ }
+
+ $now = new DateTime;
+ if ($end <= $now->getTimestamp()) {
+ $endElement = $this->_elements['end'];
+ $endElement->setValue($endElement->getValue()->format($endElement->getFormat()));
+ $endElement->addError($this->translate('A downtime must not be in the past'));
+ return false;
+ }
+
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $downtime = new ScheduleServiceDowntimeCommand();
+ $downtime->setObject($object);
+ $this->scheduleDowntime($downtime, $this->request);
+ }
+ Notification::success($this->translatePlural(
+ 'Scheduling service downtime..',
+ 'Scheduling service downtimes..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php b/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php
new file mode 100644
index 0000000..0d1c393
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/SendCustomNotificationCommandForm.php
@@ -0,0 +1,110 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Application\Config;
+use Icinga\Module\Monitoring\Command\Object\SendCustomNotificationCommand;
+use Icinga\Web\Notification;
+
+/**
+ * Form to send custom notifications
+ */
+class SendCustomNotificationCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->addDescription(
+ $this->translate('This command is used to send custom notifications about hosts or services.')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubmitLabel()
+ {
+ return $this->translatePlural('Send custom notification', 'Send custom notifications', count($this->objects));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $config = Config::module('monitoring');
+
+ $this->addElements(array(
+ array(
+ 'textarea',
+ 'comment',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Comment'),
+ 'description' => $this->translate(
+ 'If you work with other administrators, you may find it useful to share information about'
+ . ' the host or service that is having problems. Make sure you enter a brief description of'
+ . ' what you are doing.'
+ )
+ )
+ ),
+ array(
+ 'checkbox',
+ 'forced',
+ array(
+ 'label' => $this->translate('Forced'),
+ 'value' => (bool) $config->get('settings', 'custom_notification_forced', false),
+ 'description' => $this->translate(
+ 'If you check this option, the notification is sent out regardless of time restrictions and'
+ . ' whether or not notifications are enabled.'
+ )
+ )
+ )
+ ));
+
+ if (! $this->getBackend()->isIcinga2()) {
+ $this->addElement(
+ 'checkbox',
+ 'broadcast',
+ array(
+ 'label' => $this->translate('Broadcast'),
+ 'value' => (bool) $config->get('settings', 'custom_notification_broadcast', false),
+ 'description' => $this->translate(
+ 'If you check this option, the notification is sent out to all normal and escalated contacts.'
+ )
+ )
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onSuccess()
+ {
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ $notification = new SendCustomNotificationCommand();
+ $notification
+ ->setObject($object)
+ ->setComment($this->getElement('comment')->getValue())
+ ->setAuthor($this->request->getUser()->getUsername())
+ ->setForced($this->getElement('forced')->isChecked());
+ if (($broadcast = $this->getElement('broadcast')) !== null) {
+ $notification->setBroadcast($broadcast->isChecked());
+ }
+ $this->getTransport($this->request)->send($notification);
+ }
+ Notification::success($this->translatePlural(
+ 'Sending custom notification..',
+ 'Sending custom notifications..',
+ count($this->objects)
+ ));
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php
new file mode 100644
index 0000000..e4aabb2
--- /dev/null
+++ b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php
@@ -0,0 +1,187 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Command\Object;
+
+use Icinga\Module\Monitoring\Command\Object\ToggleObjectFeatureCommand;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Web\Notification;
+
+/**
+ * Form for enabling or disabling features of Icinga objects, i.e. hosts or services
+ */
+class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm
+{
+ /**
+ * Feature to feature spec map
+ *
+ * @var string[]
+ */
+ protected $features;
+
+ /**
+ * Feature to feature status map
+ *
+ * @var int[]
+ */
+ protected $featureStatus;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setUseFormAutosubmit();
+ $this->setAttrib('class', self::DEFAULT_CLASSES . ' object-features');
+ $features = array(
+ ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array(
+ 'label' => $this->translate('Active Checks'),
+ 'permission' => 'monitoring/command/feature/object/active-checks'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => array(
+ 'label' => $this->translate('Passive Checks'),
+ 'permission' => 'monitoring/command/feature/object/passive-checks'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_OBSESSING => array(
+ 'label' => $this->translate('Obsessing'),
+ 'permission' => 'monitoring/command/feature/object/obsessing'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => array(
+ 'label' => $this->translate('Notifications'),
+ 'permission' => 'monitoring/command/feature/object/notifications'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => array(
+ 'label' => $this->translate('Event Handler'),
+ 'permission' => 'monitoring/command/feature/object/event-handler'
+ ),
+ ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => array(
+ 'label' => $this->translate('Flap Detection'),
+ 'permission' => 'monitoring/command/feature/object/flap-detection'
+ )
+ );
+ if ($this->getBackend()->isIcinga2()) {
+ unset($features[ToggleObjectFeatureCommand::FEATURE_OBSESSING]);
+ }
+ $this->features = $features;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ foreach ($this->features as $feature => $spec) {
+ $options = array(
+ 'autosubmit' => true,
+ 'disabled' => $this->hasPermission($spec['permission']) ? null : 'disabled',
+ 'label' => $spec['label']
+ );
+ if ($formData[$feature . '_changed']) {
+ $options['description'] = $this->translate('changed');
+ }
+ if ($formData[$feature] === 2) {
+ $this->addElement('select', $feature, $options + [
+ 'description' => $this->translate('Multiple Values'),
+ 'filters' => [['Null', ['type' => \Zend_Filter_Null::STRING]]],
+ 'multiOptions' => [
+ '' => $this->translate('Leave Unchanged'),
+ $this->translate('Disable All'),
+ $this->translate('Enable All')
+ ],
+ 'decorators' => array_merge(
+ array_slice(static::$defaultElementDecorators, 0, 3),
+ [['Description', ['tag' => 'span']]],
+ array_slice(static::$defaultElementDecorators, 4, 1),
+ [['HtmlTag', ['tag' => 'div', 'class' => 'control-group indeterminate']]]
+ )
+ ]);
+ } else {
+ $options['value'] = $formData[$feature];
+ $this->addElement('checkbox', $feature, $options);
+ }
+ }
+ }
+
+ /**
+ * Load feature status
+ *
+ * @param MonitoredObject|object $object
+ *
+ * @return $this
+ */
+ public function load($object)
+ {
+ $featureStatus = array();
+ foreach (array_keys($this->features) as $feature) {
+ $featureStatus[$feature] = $object->{$feature};
+ if (isset($object->{$feature . '_changed'})) {
+ $featureStatus[$feature . '_changed'] = (bool) $object->{$feature . '_changed'};
+ } else {
+ $featureStatus[$feature . '_changed'] = false;
+ }
+ }
+ $this->create($featureStatus);
+ $this->featureStatus = $featureStatus;
+
+ return $this;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Form::onSuccess() For the method documentation.
+ */
+ public function onSuccess()
+ {
+ $notifications = array(
+ ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS => array(
+ $this->translate('Enabling active checks..'),
+ $this->translate('Disabling active checks..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS => array(
+ $this->translate('Enabling passive checks..'),
+ $this->translate('Disabling passive checks..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_OBSESSING => array(
+ $this->translate('Enabling obsessing..'),
+ $this->translate('Disabling obsessing..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS => array(
+ $this->translate('Enabling notifications..'),
+ $this->translate('Disabling notifications..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER => array(
+ $this->translate('Enabling event handler..'),
+ $this->translate('Disabling event handler..')
+ ),
+ ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION => array(
+ $this->translate('Enabling flap detection..'),
+ $this->translate('Disabling flap detection..')
+ )
+ );
+
+ foreach ($this->getValues() as $feature => $enabled) {
+ if ($this->getElement($feature)->getAttrib('disabled') !== null
+ || $enabled === null
+ || (int) $enabled === (int) $this->featureStatus[$feature]
+ ) {
+ continue;
+ }
+ foreach ($this->objects as $object) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ((bool) $object->{$feature} !== (bool) $enabled) {
+ $toggleFeature = new ToggleObjectFeatureCommand();
+ $toggleFeature
+ ->setFeature($feature)
+ ->setObject($object)
+ ->setEnabled($enabled);
+ $this->getTransport($this->request)->send($toggleFeature);
+ }
+ }
+ Notification::success(
+ $notifications[$feature][$enabled ? 0 : 1]
+ );
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/BackendConfigForm.php b/modules/monitoring/application/forms/Config/BackendConfigForm.php
new file mode 100644
index 0000000..5ed42e1
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/BackendConfigForm.php
@@ -0,0 +1,367 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Exception;
+use InvalidArgumentException;
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfigForm;
+use Icinga\Web\Form;
+
+/**
+ * Form for managing monitoring backends
+ */
+class BackendConfigForm extends ConfigForm
+{
+ /**
+ * The available monitoring backend resources split by type
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * The backend to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $backendToLoad;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_monitoring_backends');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param Config $resourceConfig The resource configuration
+ *
+ * @return $this
+ *
+ * @throws ConfigurationError In case there are no valid monitoring backend resources
+ */
+ public function setResourceConfig(Config $resourceConfig)
+ {
+ $resources = array();
+ foreach ($resourceConfig as $name => $resource) {
+ if ($resource->type === 'db') {
+ $resources['ido'][$name] = $name;
+ }
+ }
+
+ if (empty($resources)) {
+ throw new ConfigurationError($this->translate(
+ 'Could not find any valid monitoring backend resources. Please configure a database resource first.'
+ ));
+ }
+
+ $this->resources = $resources;
+ return $this;
+ }
+
+ /**
+ * Populate the form with the given backend's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function load($name)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No monitoring backend called "%s" found', $name);
+ }
+
+ $this->backendToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new monitoring backend
+ *
+ * The backend to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a backend name
+ * @throws IcingaException In case a backend with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ }
+
+ $backendName = $data['name'];
+ if ($this->config->hasSection($backendName)) {
+ throw new IcingaException(
+ $this->translate('A monitoring backend with the name "%s" does already exist'),
+ $backendName
+ );
+ }
+
+ unset($data['name']);
+ $this->config->setSection($backendName, $data);
+ return $this;
+ }
+
+ /**
+ * Edit a monitoring backend
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no backend with the given name is found
+ */
+ public function edit($name, array $data)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No monitoring backend called "%s" found', $name);
+ }
+
+ $backendConfig = $this->config->getSection($name);
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $this->config->removeSection($name);
+ $name = $data['name'];
+ }
+
+ unset($data['name']);
+ }
+
+ $backendConfig->merge($data);
+ $this->config->setSection($name, $backendConfig);
+ return $this;
+ }
+
+ /**
+ * Remove a monitoring backend
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function delete($name)
+ {
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'checkbox',
+ 'disabled',
+ array(
+ 'label' => $this->translate('Disable This Backend')
+ )
+ );
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate(
+ 'The name of this monitoring backend that is used to differentiate it from others'
+ )
+ )
+ );
+
+ $resourceType = isset($formData['type']) ? $formData['type'] : null;
+
+ $resourceTypes = array();
+ if ($resourceType === 'ido' || array_key_exists('ido', $this->resources)) {
+ $resourceTypes['ido'] = 'IDO Backend';
+ }
+
+ if ($resourceType === null) {
+ $resourceType = key($resourceTypes);
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate(
+ 'The type of data source used for retrieving monitoring information'
+ ),
+ 'multiOptions' => $resourceTypes
+ )
+ );
+
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Resource'),
+ 'description' => $this->translate('The resource to use'),
+ 'multiOptions' => $this->resources[$resourceType],
+ 'value' => current($this->resources[$resourceType]),
+ 'autosubmit' => true
+ )
+ );
+ $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource');
+ $this->addElement(
+ 'note',
+ 'resource_note',
+ array(
+ 'escape' => false,
+ 'value' => sprintf(
+ '<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
+ $this->getView()->url('config/editresource', array('resource' => $resourceName)),
+ sprintf($this->translate('Show the configuration of the %s resource'), $resourceName),
+ $this->translate('Show resource configuration')
+ )
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ // In case another error occured and the checkbox was displayed before
+ $this->addSkipValidationCheckbox();
+ }
+ }
+
+ /**
+ * Populate the configuration of the backend to load
+ */
+ public function onRequest()
+ {
+ if ($this->backendToLoad) {
+ $data = $this->config->getSection($this->backendToLoad)->toArray();
+ $data['name'] = $this->backendToLoad;
+ $this->populate($data);
+ }
+ }
+
+ /**
+ * Return whether the given values are valid
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (($el = $this->getElement('skip_validation')) === null || false === $el->isChecked()) {
+ $resourceConfig = ResourceFactory::getResourceConfig($this->getValue('resource'));
+ if (! self::isValidIdoSchema($this, $resourceConfig)
+ || (! $this->getElement('disabled')->isChecked()
+ && ! self::isValidIdoInstance($this, $resourceConfig))
+ ) {
+ if ($el === null) {
+ $this->addSkipValidationCheckbox();
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the schema validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate(
+ 'Check this to not to validate the IDO schema of the chosen resource.'
+ )
+ )
+ );
+ }
+
+ /**
+ * Return whether the given resource contains a valid IDO schema
+ *
+ * @param Form $form
+ * @param ConfigObject $resourceConfig
+ *
+ * @return bool
+ */
+ public static function isValidIdoSchema(Form $form, ConfigObject $resourceConfig)
+ {
+ try {
+ $db = ResourceFactory::createResource($resourceConfig);
+ $db->select()->from('icinga_dbversion', array('version'))->fetchOne();
+ } catch (Exception $_) {
+ $form->error($form->translate(
+ 'Cannot find the IDO schema. Please verify that the given database '
+ . 'contains the schema and that the configured user has access to it.'
+ ));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether a single icinga instance is writing to the given resource
+ *
+ * @param Form $form
+ * @param ConfigObject $resourceConfig
+ *
+ * @return bool True if it's a single instance, false if none
+ * or multiple instances are writing to it
+ */
+ public static function isValidIdoInstance(Form $form, ConfigObject $resourceConfig)
+ {
+ $db = ResourceFactory::createResource($resourceConfig);
+ $rowCount = $db->select()->from('icinga_instances')->count();
+
+ if ($rowCount === 0) {
+ $form->warning($form->translate(
+ 'There is currently no icinga instance writing to the IDO. Make sure '
+ . 'that a icinga instance is configured and able to write to the IDO.'
+ ));
+ return false;
+ } elseif ($rowCount > 1) {
+ $form->warning($form->translate(
+ 'There is currently more than one icinga instance writing to the IDO. You\'ll see all objects from all'
+ . ' instances without any differentation. If this is not desired, consider setting up a separate IDO'
+ . ' for each instance.'
+ ));
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/SecurityConfigForm.php b/modules/monitoring/application/forms/Config/SecurityConfigForm.php
new file mode 100644
index 0000000..d57f985
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/SecurityConfigForm.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Icinga\Web\Notification;
+use Icinga\Forms\ConfigForm;
+
+/**
+ * Form for modifying security relevant settings
+ */
+class SecurityConfigForm extends ConfigForm
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_monitoring_security');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * @see Form::onSuccess()
+ */
+ public function onSuccess()
+ {
+ $this->config->setSection('security', $this->getValues());
+
+ if ($this->save()) {
+ Notification::success($this->translate('New security configuration has successfully been stored'));
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @see Form::onRequest()
+ */
+ public function onRequest()
+ {
+ $this->populate($this->config->getSection('security')->toArray());
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'protected_customvars',
+ array(
+ 'allowEmpty' => true,
+ 'attribs' => array('placeholder' => $this->getDefaultProtectedCustomvars()),
+ 'label' => $this->translate('Protected Custom Variables'),
+ 'description' => $this->translate(
+ 'Comma separated case insensitive list of protected custom variables.'
+ . ' Use * as a placeholder for zero or more wildcard characters.'
+ . ' Existence of those custom variables will be shown, but their values will be masked.'
+ )
+ )
+ );
+ }
+
+ /**
+ * Return the customvars to suggest to protect when none are protected
+ *
+ * @return string
+ */
+ public function getDefaultProtectedCustomvars()
+ {
+ return '*pw*,*pass*,community';
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php b/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php
new file mode 100644
index 0000000..3d501e0
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config\Transport;
+
+use Icinga\Web\Form;
+
+class ApiTransportForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transport_api');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElements(array(
+ array(
+ 'text',
+ 'host',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate(
+ 'Hostname or address of the remote Icinga instance'
+ )
+ )
+ ),
+ array(
+ 'number',
+ 'port',
+ array(
+ 'required' => true,
+ 'preserveDefault' => true,
+ 'label' => $this->translate('Port'),
+ 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'),
+ 'value' => 5665
+ )
+ ),
+ array(
+ 'text',
+ 'username',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('API Username'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ )
+ )
+ ),
+ array(
+ 'password',
+ 'password',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('API Password'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ ),
+ 'renderPassword' => true
+ )
+ )
+ ));
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php b/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php
new file mode 100644
index 0000000..15c7357
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php
@@ -0,0 +1,37 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config\Transport;
+
+use Icinga\Web\Form;
+
+class LocalTransportForm extends Form
+{
+ /**
+ * (non-PHPDoc)
+ * @see Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transport_local');
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $this->addElement(
+ 'text',
+ 'path',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Command File'),
+ 'value' => '/var/run/icinga2/cmd/icinga2.cmd',
+ 'description' => $this->translate('Path to the local Icinga command file')
+ )
+ );
+ return $this;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php b/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php
new file mode 100644
index 0000000..7beeacf
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php
@@ -0,0 +1,185 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config\Transport;
+
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Web\Form;
+
+class RemoteTransportForm extends Form
+{
+ /**
+ * The available resources split by type
+ *
+ * @var array
+ */
+ protected $resources;
+
+ /**
+ * (non-PHPDoc)
+ * @see Form::init() For the method documentation.
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transport_remote');
+ }
+
+ /**
+ * Load all available ssh identity resources
+ *
+ * @return $this
+ *
+ * @throws \Icinga\Exception\ConfigurationError
+ */
+ public function loadResources()
+ {
+ $resourceConfig = ResourceFactory::getResourceConfigs();
+
+ $resources = array();
+ foreach ($resourceConfig as $name => $resource) {
+ if ($resource->type === 'ssh') {
+ $resources['ssh'][$name] = $name;
+ }
+ }
+
+ if (empty($resources)) {
+ throw new ConfigurationError($this->translate('Could not find any valid SSH resources'));
+ }
+
+ $this->resources = $resources;
+
+ return $this;
+ }
+
+ /**
+ * Check whether ssh identity resources exists or not
+ *
+ * @return boolean
+ */
+ public function hasResources()
+ {
+ $resourceConfig = ResourceFactory::getResourceConfigs();
+
+ foreach ($resourceConfig as $name => $resource) {
+ if ($resource->type === 'ssh') {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see Form::createElements() For the method documentation.
+ */
+ public function createElements(array $formData = array())
+ {
+ $useResource = false;
+
+ if ($this->hasResources()) {
+ $useResource = isset($formData['use_resource'])
+ ? $formData['use_resource'] : $this->getValue('use_resource');
+
+ $this->addElement(
+ 'checkbox',
+ 'use_resource',
+ array(
+ 'label' => $this->translate('Use SSH Identity'),
+ 'description' => $this->translate('Make use of the ssh identity resource'),
+ 'autosubmit' => true,
+ 'ignore' => true
+ )
+ );
+ }
+
+ if ($useResource) {
+ $this->loadResources();
+
+ $decorators = static::$defaultElementDecorators;
+ array_pop($decorators); // Removes the HtmlTag decorator
+
+ $this->addElement(
+ 'select',
+ 'resource',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('SSH Identity'),
+ 'description' => $this->translate('The resource to use'),
+ 'decorators' => $decorators,
+ 'multiOptions' => $this->resources['ssh'],
+ 'value' => current($this->resources['ssh']),
+ 'autosubmit' => false
+ )
+ );
+ $resourceName = isset($formData['resource']) ? $formData['resource'] : $this->getValue('resource');
+ $this->addElement(
+ 'note',
+ 'resource_note',
+ array(
+ 'escape' => false,
+ 'decorators' => $decorators,
+ 'value' => sprintf(
+ '<a href="%1$s" data-base-target="_next" title="%2$s" aria-label="%2$s">%3$s</a>',
+ $this->getView()->url('config/editresource', array('resource' => $resourceName)),
+ sprintf($this->translate('Show the configuration of the %s resource'), $resourceName),
+ $this->translate('Show resource configuration')
+ )
+ )
+ );
+ }
+
+ $this->addElements(array(
+ array(
+ 'text',
+ 'host',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Host'),
+ 'description' => $this->translate(
+ 'Hostname or address of the remote Icinga instance'
+ )
+ )
+ ),
+ array(
+ 'number',
+ 'port',
+ array(
+ 'required' => true,
+ 'preserveDefault' => true,
+ 'label' => $this->translate('Port'),
+ 'description' => $this->translate('SSH port to connect to on the remote Icinga instance'),
+ 'value' => 22
+ )
+ )
+ ));
+
+ if (! $useResource) {
+ $this->addElement(
+ 'text',
+ 'user',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('User'),
+ 'description' => $this->translate(
+ 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be'
+ . ' possible for this user'
+ )
+ )
+ );
+ }
+
+ $this->addElement(
+ 'text',
+ 'path',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Command File'),
+ 'value' => '/var/run/icinga2/cmd/icinga2.cmd',
+ 'description' => $this->translate('Path to the Icinga command file on the remote Icinga instance')
+ )
+ );
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/TransportConfigForm.php b/modules/monitoring/application/forms/Config/TransportConfigForm.php
new file mode 100644
index 0000000..c68e63d
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/TransportConfigForm.php
@@ -0,0 +1,392 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+use InvalidArgumentException;
+use Icinga\Application\Platform;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotFoundError;
+use Icinga\Forms\ConfigForm;
+use Icinga\Module\Monitoring\Command\Transport\ApiCommandTransport;
+use Icinga\Module\Monitoring\Command\Transport\LocalCommandFile;
+use Icinga\Module\Monitoring\Command\Transport\RemoteCommandFile;
+use Icinga\Module\Monitoring\Forms\Config\Transport\ApiTransportForm;
+use Icinga\Module\Monitoring\Forms\Config\Transport\LocalTransportForm;
+use Icinga\Module\Monitoring\Forms\Config\Transport\RemoteTransportForm;
+
+/**
+ * Form for managing command transports
+ */
+class TransportConfigForm extends ConfigForm
+{
+ /**
+ * The transport to load when displaying the form for the first time
+ *
+ * @var string
+ */
+ protected $transportToLoad;
+
+ /**
+ * The names of all available Icinga instances
+ *
+ * @var array
+ */
+ protected $instanceNames;
+
+ /**
+ * @var bool
+ */
+ protected $validatePartial = true;
+
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_config_command_transports');
+ $this->setSubmitLabel($this->translate('Save Changes'));
+ }
+
+ /**
+ * Set the names of all available Icinga instances
+ *
+ * @param array $names
+ *
+ * @return $this
+ */
+ public function setInstanceNames(array $names)
+ {
+ $this->instanceNames = $names;
+ return $this;
+ }
+
+ /**
+ * Return the names of all available Icinga instances
+ *
+ * @return array
+ */
+ public function getInstanceNames()
+ {
+ return $this->instanceNames ?: array();
+ }
+
+ /**
+ * Return a form object for the given transport type
+ *
+ * @param string $type The transport type for which to return a form
+ *
+ * @return \Icinga\Web\Form
+ *
+ * @throws InvalidArgumentException In case the given transport type is invalid
+ */
+ public function getTransportForm($type)
+ {
+ switch (strtolower($type)) {
+ case LocalCommandFile::TRANSPORT:
+ return new LocalTransportForm();
+ case RemoteCommandFile::TRANSPORT:
+ return new RemoteTransportForm();
+ case ApiCommandTransport::TRANSPORT:
+ return new ApiTransportForm();
+ default:
+ throw new InvalidArgumentException(
+ sprintf($this->translate('Invalid command transport type "%s" given'), $type)
+ );
+ }
+ }
+
+ /**
+ * Populate the form with the given transport's config
+ *
+ * @param string $name
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no transport with the given name is found
+ */
+ public function load($name)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No command transport called "%s" found', $name);
+ }
+
+ $this->transportToLoad = $name;
+ return $this;
+ }
+
+ /**
+ * Add a new command transport
+ *
+ * The transport to add is identified by the array-key `name'.
+ *
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case $data does not contain a transport name
+ * @throws IcingaException In case a transport with the same name already exists
+ */
+ public function add(array $data)
+ {
+ if (! isset($data['name'])) {
+ throw new InvalidArgumentException('Key \'name\' missing');
+ }
+
+ $transportName = $data['name'];
+ if ($this->config->hasSection($transportName)) {
+ throw new IcingaException(
+ $this->translate('A command transport with the name "%s" does already exist'),
+ $transportName
+ );
+ }
+
+ unset($data['name']);
+ $this->config->setSection($transportName, $data);
+ return $this;
+ }
+
+ /**
+ * Edit an existing command transport
+ *
+ * @param string $name
+ * @param array $data
+ *
+ * @return $this
+ *
+ * @throws NotFoundError In case no transport with the given name is found
+ */
+ public function edit($name, array $data)
+ {
+ if (! $this->config->hasSection($name)) {
+ throw new NotFoundError('No command transport called "%s" found', $name);
+ }
+
+ $transportConfig = $this->config->getSection($name);
+ if (isset($data['name'])) {
+ if ($data['name'] !== $name) {
+ $this->config->removeSection($name);
+ $name = $data['name'];
+ }
+
+ unset($data['name']);
+ }
+
+ $transportConfig->merge($data);
+ $this->config->setSection($name, $transportConfig);
+ return $this;
+ }
+
+ /**
+ * Remove a command transport
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function delete($name)
+ {
+ $this->config->removeSection($name);
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $instanceNames = $this->getInstanceNames();
+ if (count($instanceNames) > 1) {
+ $options = array('none' => $this->translate('None', 'command transport instance association'));
+ $this->addElement(
+ 'select',
+ 'instance',
+ array(
+ 'label' => $this->translate('Instance Link'),
+ 'description' => $this->translate(
+ 'The name of the Icinga instance this transport should exclusively transfer commands to.'
+ ),
+ 'multiOptions' => array_merge($options, array_combine($instanceNames, $instanceNames))
+ )
+ );
+ }
+
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Transport Name'),
+ 'description' => $this->translate(
+ 'The name of this command transport that is used to differentiate it from others'
+ )
+ )
+ );
+
+ $transportTypes = array(
+ ApiCommandTransport::TRANSPORT => $this->translate('Icinga 2 API'),
+ LocalCommandFile::TRANSPORT => $this->translate('Local Command File'),
+ RemoteCommandFile::TRANSPORT => $this->translate('Remote Command File')
+ );
+ if (! Platform::extensionLoaded('curl')) {
+ unset($transportTypes[ApiCommandTransport::TRANSPORT]);
+ }
+
+ $transportType = isset($formData['transport']) ? $formData['transport'] : null;
+ if ($transportType === null) {
+ $transportType = key($transportTypes);
+ }
+
+ $this->addElements(array(
+ array(
+ 'select',
+ 'transport',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Transport Type'),
+ 'multiOptions' => $transportTypes
+ )
+ )
+ ));
+
+ $this->addSubForm($this->getTransportForm($transportType)->create($formData), 'transport_form');
+ }
+
+ /**
+ * Add a submit button to this form and one to manually validate the configuration
+ *
+ * Calls parent::addSubmitButton() to add the submit button.
+ *
+ * @return $this
+ */
+ public function addSubmitButton()
+ {
+ parent::addSubmitButton();
+
+ if ($this->getSubForm('transport_form') instanceof ApiTransportForm) {
+ $btnSubmit = $this->getElement('btn_submit');
+
+ if ($btnSubmit !== null) {
+ // In the setup wizard $this is being used as a subform which doesn't have a submit button.
+ $this->addElement(
+ 'submit',
+ 'transport_validation',
+ array(
+ 'ignore' => true,
+ 'label' => $this->translate('Validate Configuration'),
+ 'data-progress-label' => $this->translate('Validation In Progress'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->setAttrib('data-progress-element', 'transport-progress');
+ $this->addElement(
+ 'note',
+ 'transport-progress',
+ array(
+ 'decorators' => array(
+ 'ViewHelper',
+ array('Spinner', array('id' => 'transport-progress'))
+ )
+ )
+ );
+
+ $elements = array('transport_validation', 'transport-progress');
+
+ $btnSubmit->setDecorators(array('ViewHelper'));
+ array_unshift($elements, 'btn_submit');
+
+ $this->addDisplayGroup(
+ $elements,
+ 'submit_validation',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
+ )
+ )
+ );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Populate the configuration of the transport to load
+ */
+ public function onRequest()
+ {
+ if ($this->transportToLoad) {
+ $data = $this->config->getSection($this->transportToLoad)->toArray();
+ $data['name'] = $this->transportToLoad;
+ $this->populate($data);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValidPartial(array $formData)
+ {
+ $isValidPartial = parent::isValidPartial($formData);
+
+ $transportValidation = $this->getElement('transport_validation');
+ if ($transportValidation !== null && $transportValidation->isChecked() && $this->isValid($formData)) {
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ }
+
+ return $isValidPartial;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if ($this->getSubForm('transport_form') instanceof ApiTransportForm) {
+ if (! isset($formData['transport_validation'])
+ && isset($formData['force_creation']) && $formData['force_creation']
+ ) {
+ // ignore any validation result
+ return true;
+ }
+
+ try {
+ CommandTransport::createTransport(new ConfigObject($this->getValues()))->probe();
+ } catch (CommandTransportException $e) {
+ $this->error(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $e->getMessage()
+ ));
+
+ $this->addElement(
+ 'checkbox',
+ 'force_creation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'label' => $this->translate('Force Changes'),
+ 'description' => $this->translate(
+ 'Check this box to enforce changes without connectivity validation'
+ )
+ )
+ );
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Config/TransportReorderForm.php b/modules/monitoring/application/forms/Config/TransportReorderForm.php
new file mode 100644
index 0000000..f3efe4c
--- /dev/null
+++ b/modules/monitoring/application/forms/Config/TransportReorderForm.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Config;
+
+use Icinga\Application\Config;
+use Icinga\Web\Form;
+use Icinga\Web\Notification;
+
+/**
+ * Form for reordering command transports
+ */
+class TransportReorderForm extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('form_reorder_command_transports');
+ $this->setViewScript('form/reorder-command-transports.phtml');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ // This adds just a dummy element to be able to utilize Form::getValue as part of onSuccess()
+ $this->addElement(
+ 'hidden',
+ 'transport_newpos',
+ array(
+ 'required' => true,
+ 'validators' => array(
+ array(
+ 'validator' => 'regex',
+ 'options' => array(
+ 'pattern' => '/\A\d+\|/'
+ )
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Update the command transport order and save the configuration
+ */
+ public function onSuccess()
+ {
+ list($position, $transportName) = explode('|', $this->getValue('transport_newpos'), 2);
+ $config = $this->getConfig();
+ if (! $config->hasSection($transportName)) {
+ Notification::error(sprintf($this->translate('Command transport "%s" not found'), $transportName));
+ return false;
+ }
+
+ if ($config->count() > 1) {
+ $sections = $config->keys();
+ array_splice($sections, array_search($transportName, $sections, true), 1);
+ array_splice($sections, $position, 0, array($transportName));
+
+ $sectionsInNewOrder = array();
+ foreach ($sections as $section) {
+ $sectionsInNewOrder[$section] = $config->getSection($section);
+ $config->removeSection($section);
+ }
+ foreach ($sectionsInNewOrder as $name => $options) {
+ $config->setSection($name, $options);
+ }
+
+ $config->saveIni();
+ Notification::success($this->translate('Command transport order updated'));
+ }
+ }
+
+ /**
+ * Get the command transports config
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return Config::module('monitoring', 'commandtransports');
+ }
+}
diff --git a/modules/monitoring/application/forms/EventOverviewForm.php b/modules/monitoring/application/forms/EventOverviewForm.php
new file mode 100644
index 0000000..db1511c
--- /dev/null
+++ b/modules/monitoring/application/forms/EventOverviewForm.php
@@ -0,0 +1,157 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms;
+
+use Icinga\Web\Url;
+use Icinga\Web\Form;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Configure the filter for the event overview
+ */
+class EventOverviewForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_event_overview');
+ $this->setDecorators(array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'hbox')),
+ 'Form'
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $decorators = array(
+ array('Label', array('class' => 'optional')),
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'hbox-item optionbox')),
+ );
+
+ $url = Url::fromRequest()->getAbsoluteUrl();
+ $this->addElement(
+ 'checkbox',
+ 'statechange',
+ array(
+ 'label' => $this->translate('State Changes'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->stateChangeFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'downtime',
+ array(
+ 'label' => $this->translate('Downtimes'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->downtimeFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'comment',
+ array(
+ 'label' => $this->translate('Comments'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->commentFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'notification',
+ array(
+ 'label' => $this->translate('Notifications'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->notificationFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ $this->addElement(
+ 'checkbox',
+ 'flapping',
+ array(
+ 'label' => $this->translate('Flapping'),
+ 'class' => 'autosubmit',
+ 'decorators' => $decorators,
+ 'value' => strpos($url, $this->flappingFilter()->toQueryString()) === false ? 0 : 1
+ )
+ );
+ }
+
+ /**
+ * Return the corresponding filter-object
+ *
+ * @returns Filter
+ */
+ public function getFilter()
+ {
+ $filters = array();
+ if ($this->getValue('statechange', 1)) {
+ $filters[] = $this->stateChangeFilter();
+ }
+ if ($this->getValue('comment', 1)) {
+ $filters[] = $this->commentFilter();
+ }
+ if ($this->getValue('notification', 1)) {
+ $filters[] = $this->notificationFilter();
+ }
+ if ($this->getValue('downtime', 1)) {
+ $filters[] = $this->downtimeFilter();
+ }
+ if ($this->getValue('flapping', 1)) {
+ $filters[] = $this->flappingFilter();
+ }
+ return Filter::matchAny($filters);
+ }
+
+ public function stateChangeFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'hard_state'),
+ Filter::expression('type', '=', 'soft_state')
+ );
+ }
+
+ public function commentFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'comment'),
+ Filter::expression('type', '=', 'comment_deleted'),
+ Filter::expression('type', '=', 'dt_comment'),
+ Filter::expression('type', '=', 'dt_comment_deleted'),
+ Filter::expression('type', '=', 'ack')
+ );
+ }
+
+ public function notificationFilter()
+ {
+ return Filter::expression('type', '=', 'notify');
+ }
+
+ public function downtimeFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'downtime_start'),
+ Filter::expression('type', '=', 'downtime_end')
+ );
+ }
+
+ public function flappingFilter()
+ {
+ return Filter::matchAny(
+ Filter::expression('type', '=', 'flapping'),
+ Filter::expression('type', '=', 'flapping_deleted')
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/Navigation/ActionForm.php b/modules/monitoring/application/forms/Navigation/ActionForm.php
new file mode 100644
index 0000000..81d5588
--- /dev/null
+++ b/modules/monitoring/application/forms/Navigation/ActionForm.php
@@ -0,0 +1,79 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Navigation;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\QueryException;
+use Icinga\Forms\Navigation\NavigationItemForm;
+
+class ActionForm extends NavigationItemForm
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ parent::createElements($formData);
+
+ $this->addElement(
+ 'text',
+ 'filter',
+ array(
+ 'allowEmpty' => true,
+ 'label' => $this->translate('Filter'),
+ 'description' => $this->translate(
+ 'Display this action only for objects matching this filter. Leave it blank'
+ . ' if you want this action being displayed regardless of the object'
+ )
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (($filterString = $this->getValue('filter')) !== null) {
+ $filter = Filter::matchAll();
+ $filter->setAllowedFilterColumns(array(
+ 'host_name',
+ 'hostgroup_name',
+ 'instance_name',
+ 'service_description',
+ 'servicegroup_name',
+ 'contact_name',
+ 'contactgroup_name',
+ function ($c) {
+ return preg_match('/^_(?:host|service)_/', $c);
+ }
+ ));
+
+ try {
+ $filter->addFilter(Filter::fromQueryString($filterString));
+ } catch (QueryException $_) {
+ $this->getElement('filter')->addError(sprintf(
+ $this->translate('Invalid filter provided. You can only use the following columns: %s'),
+ implode(', ', array(
+ 'instance_name',
+ 'host_name',
+ 'hostgroup_name',
+ 'service_description',
+ 'servicegroup_name',
+ 'contact_name',
+ 'contactgroup_name',
+ '_(host|service)_<customvar-name>'
+ ))
+ ));
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Navigation/HostActionForm.php b/modules/monitoring/application/forms/Navigation/HostActionForm.php
new file mode 100644
index 0000000..da237d4
--- /dev/null
+++ b/modules/monitoring/application/forms/Navigation/HostActionForm.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Navigation;
+
+class HostActionForm extends ActionForm
+{
+}
diff --git a/modules/monitoring/application/forms/Navigation/ServiceActionForm.php b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php
new file mode 100644
index 0000000..68314d1
--- /dev/null
+++ b/modules/monitoring/application/forms/Navigation/ServiceActionForm.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Navigation;
+
+class ServiceActionForm extends ActionForm
+{
+}
diff --git a/modules/monitoring/application/forms/Setup/BackendPage.php b/modules/monitoring/application/forms/Setup/BackendPage.php
new file mode 100644
index 0000000..d5c7efb
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/BackendPage.php
@@ -0,0 +1,51 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+use Icinga\Application\Platform;
+
+class BackendPage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_monitoring_backend');
+ $this->setTitle($this->translate('Monitoring Backend', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please configure below how Icinga Web 2 should retrieve monitoring information.'
+ ));
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'name',
+ array(
+ 'required' => true,
+ 'value' => 'icinga',
+ 'label' => $this->translate('Backend Name'),
+ 'description' => $this->translate('The identifier of this backend')
+ )
+ );
+
+ $resourceTypes = array();
+ if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) {
+ $resourceTypes['ido'] = 'IDO';
+ }
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate(
+ 'The data source used for retrieving monitoring information'
+ ),
+ 'multiOptions' => $resourceTypes
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/IdoResourcePage.php b/modules/monitoring/application/forms/Setup/IdoResourcePage.php
new file mode 100644
index 0000000..d648579
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/IdoResourcePage.php
@@ -0,0 +1,188 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Forms\Config\ResourceConfigForm;
+use Icinga\Forms\Config\Resource\DbResourceForm;
+use Icinga\Web\Form;
+use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm;
+use Icinga\Module\Setup\Utils\DbTool;
+
+class IdoResourcePage extends Form
+{
+ /**
+ * Initialize this form
+ */
+ public function init()
+ {
+ $this->setName('setup_monitoring_ido');
+ $this->setTitle($this->translate('Monitoring IDO Resource', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please fill out the connection details below to access the IDO database of your monitoring environment.'
+ ));
+ $this->setValidatePartial(true);
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'hidden',
+ 'type',
+ array(
+ 'required' => true,
+ 'value' => 'db'
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ // In case another error occured and the checkbox was displayed before
+ $this->addSkipValidationCheckbox();
+ } else {
+ $this->addElement(
+ 'hidden',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'value' => 0
+ )
+ );
+ }
+
+ $dbResourceForm = new DbResourceForm();
+ $this->addElements($dbResourceForm->createElements($formData)->getElements());
+ $this->getElement('name')->setValue('icinga_ido');
+ }
+
+ /**
+ * Return whether the given values are valid
+ *
+ * @param array $formData The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($formData)
+ {
+ if (! parent::isValid($formData)) {
+ return false;
+ }
+
+ if (! isset($formData['skip_validation']) || !$formData['skip_validation']) {
+ if (! $this->validateConfiguration()) {
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['backend_validation']) && parent::isValid($formData)) {
+ if (! $this->validateConfiguration(true)) {
+ return false;
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (! isset($formData['backend_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether the configuration is valid
+ *
+ * @param bool $showLog Whether to show the validation log
+ *
+ * @return bool
+ */
+ protected function validateConfiguration($showLog = false)
+ {
+ $inspection = ResourceConfigForm::inspectResource($this);
+ if ($inspection !== null) {
+ if ($showLog) {
+ $join = function ($e) use (&$join) {
+ return is_string($e) ? $e : join("\n", array_map($join, $e));
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+ }
+
+ if ($inspection->hasError()) {
+ $this->error(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $configObject = new ConfigObject($this->getValues());
+ if (! BackendConfigForm::isValidIdoSchema($this, $configObject)
+ || !BackendConfigForm::isValidIdoInstance($this, $configObject)
+ ) {
+ return false;
+ }
+
+ if ($this->getValue('db') === 'pgsql') {
+ $db = new DbTool($this->getValues());
+ $version = $db->connectToDb()->getServerVersion();
+ if (version_compare($version, '9.1', '<')) {
+ $this->error(sprintf(
+ $this->translate('The server\'s version %s is too old. The minimum required version is %s.'),
+ $version,
+ '9.1'
+ ));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the configuration validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate('Check this to not to validate the configuration')
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/SecurityPage.php b/modules/monitoring/application/forms/Setup/SecurityPage.php
new file mode 100644
index 0000000..999103c
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/SecurityPage.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+use Icinga\Module\Monitoring\Forms\Config\SecurityConfigForm;
+
+class SecurityPage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_monitoring_security');
+ $this->setTitle($this->translate('Monitoring Security', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'To protect your monitoring environment against prying eyes please fill out the settings below.'
+ ));
+ }
+
+ public function createElements(array $formData)
+ {
+ $securityConfigForm = new SecurityConfigForm();
+ $securityConfigForm->createElements($formData);
+ $this->addElements($securityConfigForm->getElements());
+ $this->getElement('protected_customvars')->setValue($securityConfigForm->getDefaultProtectedCustomvars());
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/TransportPage.php b/modules/monitoring/application/forms/Setup/TransportPage.php
new file mode 100644
index 0000000..9d0760a
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/TransportPage.php
@@ -0,0 +1,55 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+use Icinga\Module\Monitoring\Forms\Config\TransportConfigForm;
+
+class TransportPage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_command_transport');
+ $this->setTitle($this->translate('Command Transport', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please define below how you want to send commands to your monitoring instance.'
+ ));
+ $this->setValidatePartial(true);
+ }
+
+ public function createElements(array $formData)
+ {
+ $transportConfigForm = new TransportConfigForm();
+ $this->addSubForm($transportConfigForm, 'transport_form');
+ $transportConfigForm->create($formData);
+ $transportConfigForm->removeElement('instance');
+ $transportConfigForm->getElement('name')->setValue('icinga2');
+ }
+
+ public function getValues($suppressArrayNotation = false)
+ {
+ return $this->getSubForm('transport_form')->getValues($suppressArrayNotation);
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'transport_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['transport_validation']) && parent::isValid($formData)) {
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (! isset($formData['transport_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+}
diff --git a/modules/monitoring/application/forms/Setup/WelcomePage.php b/modules/monitoring/application/forms/Setup/WelcomePage.php
new file mode 100644
index 0000000..aa78db5
--- /dev/null
+++ b/modules/monitoring/application/forms/Setup/WelcomePage.php
@@ -0,0 +1,63 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms\Setup;
+
+use Icinga\Web\Form;
+
+class WelcomePage extends Form
+{
+ public function init()
+ {
+ $this->setName('setup_monitoring_welcome');
+ }
+
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'note',
+ 'welcome',
+ array(
+ 'value' => $this->translate(
+ 'Welcome to the configuration of the monitoring module for Icinga Web 2!'
+ ),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'h2'))
+ )
+ )
+ );
+
+ $this->addElement(
+ 'note',
+ 'core_hint',
+ array(
+ 'value' => $this->translate('This is the core module for Icinga Web 2.'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->addElement(
+ 'note',
+ 'description',
+ array(
+ 'value' => $this->translate(
+ 'It offers various status and reporting views with powerful filter capabilities that allow'
+ . ' you to keep track of the most important events in your monitoring environment.'
+ ),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->addDisplayGroup(
+ array('core_hint', 'description'),
+ 'info',
+ array(
+ 'decorators' => array(
+ 'FormElements',
+ array('HtmlTag', array('tag' => 'div', 'class' => 'info'))
+ )
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/forms/StatehistoryForm.php b/modules/monitoring/application/forms/StatehistoryForm.php
new file mode 100644
index 0000000..3a7c10d
--- /dev/null
+++ b/modules/monitoring/application/forms/StatehistoryForm.php
@@ -0,0 +1,141 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Forms;
+
+use Icinga\Web\Form;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Configure the filter for the event grid
+ */
+class StatehistoryForm extends Form
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function init()
+ {
+ $this->setName('form_event_overview');
+ $this->setSubmitLabel($this->translate('Apply'));
+ }
+
+ /**
+ * Return the corresponding filter-object
+ *
+ * @returns Filter
+ */
+ public function getFilter()
+ {
+ $baseFilter = Filter::matchAny(
+ Filter::expression('type', '=', 'hard_state')
+ );
+
+ if ($this->getValue('objecttype', 'hosts') === 'hosts') {
+ $objectTypeFilter = Filter::expression('object_type', '=', 'host');
+ } else {
+ $objectTypeFilter = Filter::expression('object_type', '=', 'service');
+ }
+
+ $states = array(
+ 'cnt_down_hard' => Filter::expression('state', '=', '1'),
+ 'cnt_unreachable_hard' => Filter::expression('state', '=', '2'),
+ 'cnt_up' => Filter::expression('state', '=', '0'),
+ 'cnt_critical_hard' => Filter::expression('state', '=', '2'),
+ 'cnt_warning_hard' => Filter::expression('state', '=', '1'),
+ 'cnt_unknown_hard' => Filter::expression('state', '=', '3'),
+ 'cnt_ok' => Filter::expression('state', '=', '0')
+ );
+ $state = $this->getValue('state', 'cnt_critical_hard');
+ $stateFilter = $states[$state];
+ if (in_array($state, array('cnt_ok', 'cnt_up'))) {
+ return Filter::matchAll($objectTypeFilter, $stateFilter);
+ }
+ return Filter::matchAll($baseFilter, $objectTypeFilter, $stateFilter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'select',
+ 'from',
+ array(
+ 'label' => $this->translate('From'),
+ 'value' => $this->getRequest()->getParam('from', strtotime('3 months ago')),
+ 'multiOptions' => array(
+ strtotime('midnight 3 months ago') => $this->translate('3 Months'),
+ strtotime('midnight 4 months ago') => $this->translate('4 Months'),
+ strtotime('midnight 8 months ago') => $this->translate('8 Months'),
+ strtotime('midnight 12 months ago') => $this->translate('1 Year'),
+ strtotime('midnight 24 months ago') => $this->translate('2 Years')
+ )
+ )
+ );
+ $this->addElement(
+ 'select',
+ 'to',
+ array(
+ 'label' => $this->translate('To'),
+ 'value' => $this->getRequest()->getParam('to', time()),
+ 'multiOptions' => array(
+ time() => $this->translate('Today')
+ )
+ )
+ );
+
+ $objectType = $this->getRequest()->getParam('objecttype', 'services');
+ $this->addElement(
+ 'select',
+ 'objecttype',
+ array(
+ 'label' => $this->translate('Object type'),
+ 'value' => $objectType,
+ 'multiOptions' => array(
+ 'services' => $this->translate('Services'),
+ 'hosts' => $this->translate('Hosts')
+ )
+ )
+ );
+ if ($objectType === 'services') {
+ $serviceState = $this->getRequest()->getParam('state', 'cnt_critical_hard');
+ if (in_array($serviceState, array('cnt_down_hard', 'cnt_unreachable_hard', 'cnt_up'))) {
+ $serviceState = 'cnt_critical_hard';
+ }
+ $this->addElement(
+ 'select',
+ 'state',
+ array(
+ 'label' => $this->translate('State'),
+ 'value' => $serviceState,
+ 'multiOptions' => array(
+ 'cnt_critical_hard' => $this->translate('Critical'),
+ 'cnt_warning_hard' => $this->translate('Warning'),
+ 'cnt_unknown_hard' => $this->translate('Unknown'),
+ 'cnt_ok' => $this->translate('Ok')
+ )
+ )
+ );
+ } else {
+ $hostState = $this->getRequest()->getParam('state', 'cnt_down_hard');
+ if (in_array($hostState, array('cnt_ok', 'cnt_critical_hard', 'cnt_warning', 'cnt_unknown'))) {
+ $hostState = 'cnt_down_hard';
+ }
+ $this->addElement(
+ 'select',
+ 'state',
+ array(
+ 'label' => $this->translate('State'),
+ 'value' => $hostState,
+ 'multiOptions' => array(
+ 'cnt_up' => $this->translate('Up'),
+ 'cnt_down_hard' => $this->translate('Down'),
+ 'cnt_unreachable_hard' => $this->translate('Unreachable')
+ )
+ )
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/CheckPerformance.php b/modules/monitoring/application/views/helpers/CheckPerformance.php
new file mode 100644
index 0000000..feac4d8
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/CheckPerformance.php
@@ -0,0 +1,50 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Convert check summary data into a simple usable stdClass
+ */
+class Zend_View_Helper_CheckPerformance extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return $this
+ */
+ public function checkPerformance()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a condensed row of object data
+ *
+ * @param array $results Array of stdClass
+ *
+ * @return stdClass Condensed row
+ */
+ public function create(array $results)
+ {
+ $out = new stdClass();
+ $out->host_passive_count = 0;
+ $out->host_passive_latency_avg = 0;
+ $out->host_passive_execution_avg = 0;
+ $out->service_passive_count = 0;
+ $out->service_passive_latency_avg = 0;
+ $out->service_passive_execution_avg = 0;
+ $out->service_active_count = 0;
+ $out->service_active_latency_avg = 0;
+ $out->service_active_execution_avg = 0;
+ $out->host_active_count = 0;
+ $out->host_active_latency_avg = 0;
+ $out->host_active_execution_avg = 0;
+
+ foreach ($results as $row) {
+ $key = $row->object_type . '_' . $row->check_type . '_';
+ $out->{$key . 'count'} = $row->object_count;
+ $out->{$key . 'latency_avg'} = $row->latency / $row->object_count;
+ $out->{$key . 'execution_avg'} = $row->execution_time / $row->object_count;
+ }
+ return $out;
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/ContactFlags.php b/modules/monitoring/application/views/helpers/ContactFlags.php
new file mode 100644
index 0000000..858c726
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/ContactFlags.php
@@ -0,0 +1,46 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_ContactFlags extends Zend_View_Helper_Abstract
+{
+ /**
+ * Get the human readable flag name for the given contact notification option
+ *
+ * @param string $tableName The name of the option table
+ *
+ * @return string
+ */
+ public function getNotificationOptionName($tableName)
+ {
+ $exploded = explode('_', $tableName);
+ $name = end($exploded);
+ return ucfirst($name);
+ }
+
+ /**
+ * Build all active notification options to a readable string
+ *
+ * @param object $contact The contact retrieved from a backend
+ * @param string $type Whether to display the flags for 'host' or 'service'
+ * @param string $glue The symbol to use to concatenate the flag names
+ *
+ * @return string A string that contains a human readable list of active options
+ */
+ public function contactFlags($contact, $type, $glue = ', ')
+ {
+ $optionName = 'contact_' . $type . '_notification_options';
+ if (isset($contact->$optionName)) {
+ return $contact->$optionName;
+ }
+ $out = array();
+ foreach ($contact as $key => $value) {
+ if (preg_match('/^contact_notify_' . $type . '_.*/', $key) && $value == true) {
+ $option = $this->getNotificationOptionName($key);
+ if (strtolower($option) != 'timeperiod') {
+ array_push($out, $option);
+ }
+ }
+ }
+ return implode($glue, $out);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/Customvar.php b/modules/monitoring/application/views/helpers/Customvar.php
new file mode 100644
index 0000000..8bfdc3a
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/Customvar.php
@@ -0,0 +1,62 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_Customvar extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return $this
+ */
+ public function checkPerformance()
+ {
+ return $this;
+ }
+
+ public function customvar($struct)
+ {
+ if (is_scalar($struct)) {
+ return nl2br($this->view->escape(
+ is_string($struct)
+ ? $struct
+ : var_export($struct, true)
+ ), false);
+ } elseif (is_array($struct)) {
+ return $this->renderArray($struct);
+ } elseif (is_object($struct)) {
+ return $this->renderObject($struct);
+ }
+ }
+
+ protected function renderArray($array)
+ {
+ if (empty($array)) {
+ return '[]';
+ }
+ $out = "<ul>\n";
+
+ foreach ($array as $val) {
+ $out .= '<li>' . $this->customvar($val) . "</li>\n";
+ }
+
+ return $out . "</ul>\n";
+ }
+
+ protected function renderObject($object)
+ {
+ if (0 === count((array) $object)) {
+ return '{}';
+ }
+ $out = "{<ul>\n";
+
+ foreach ($object as $key => $val) {
+ $out .= '<li>'
+ . $this->view->escape($key)
+ . ' => '
+ . $this->customvar($val)
+ . "</li>\n";
+ }
+
+ return $out . "</ul>}";
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/EscapeComment.php b/modules/monitoring/application/views/helpers/EscapeComment.php
new file mode 100644
index 0000000..be85a22
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/EscapeComment.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+/**
+ * Helper for escaping comments, but preserving links
+ */
+class Zend_View_Helper_EscapeComment extends Zend_View_Helper_Abstract
+{
+ /**
+ * The purifier to use for escaping
+ *
+ * @var HTMLPurifier
+ */
+ protected static $purifier;
+
+ /**
+ * Escape any comment for being placed inside HTML, but preserve simple links (<a href="...">).
+ *
+ * @param string $comment
+ *
+ * @return string
+ */
+ public function escapeComment($comment)
+ {
+ if (self::$purifier === null) {
+ require_once 'HTMLPurifier/Bootstrap.php';
+ require_once 'HTMLPurifier.php';
+ require_once 'HTMLPurifier.autoload.php';
+
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('Core.EscapeNonASCIICharacters', true);
+ $config->set('HTML.Allowed', 'a[href]');
+ $config->set('Cache.DefinitionImpl', null);
+ self::$purifier = new HTMLPurifier($config);
+ }
+ return self::$purifier->purify($comment);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/HostFlags.php b/modules/monitoring/application/views/helpers/HostFlags.php
new file mode 100644
index 0000000..81d8ebc
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/HostFlags.php
@@ -0,0 +1,33 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_HostFlags extends Zend_View_Helper_Abstract
+{
+ public function hostFlags($host)
+ {
+ $icons = array();
+ if (! $host->host_handled && $host->host_state > 0) {
+ $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled'));
+ }
+ if ($host->host_acknowledged) {
+ $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged'));
+ }
+ if ($host->host_is_flapping) {
+ $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping'));
+ }
+ if (! $host->host_notifications_enabled) {
+ $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled'));
+ }
+ if ($host->host_in_downtime) {
+ $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime'));
+ }
+ if (! $host->host_active_checks_enabled) {
+ if (! $host->host_passive_checks_enabled) {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled'));
+ } else {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled'));
+ }
+ }
+ return implode(' ', $icons);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/IconImage.php b/modules/monitoring/application/views/helpers/IconImage.php
new file mode 100644
index 0000000..3c0ca43
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/IconImage.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+use Icinga\Module\Monitoring\Object\Macro;
+
+/**
+ * Generate icons to describe a given hosts state
+ */
+class Zend_View_Helper_IconImage extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return \Zend_View_Helper_IconImage
+ */
+ public function iconImage()
+ {
+ return $this;
+ }
+
+ /**
+ * Display the image_icon of a MonitoredObject
+ *
+ * @param MonitoredObject|stdClass $object The host or service
+ * @return string
+ */
+ public function host($object)
+ {
+ if ($object->host_icon_image && ! preg_match('/[\'"]/', $object->host_icon_image)) {
+ return $this->view->icon(
+ Macro::resolveMacros($object->host_icon_image, $object),
+ null,
+ array(
+ 'alt' => $object->host_icon_image_alt,
+ 'class' => 'host-icon-image',
+ 'title' => $object->host_icon_image_alt
+ )
+ );
+ }
+ return '';
+ }
+
+ /**
+ * Display the image_icon of a MonitoredObject
+ *
+ * @param MonitoredObject|stdClass $object The host or service
+ * @return string
+ */
+ public function service($object)
+ {
+ if ($object->service_icon_image && ! preg_match('/[\'"]/', $object->service_icon_image)) {
+ return $this->view->icon(
+ Macro::resolveMacros($object->service_icon_image, $object),
+ null,
+ array(
+ 'alt' => $object->service_icon_image_alt,
+ 'class' => 'service-icon-image',
+ 'title' => $object->service_icon_image_alt
+ )
+ );
+ }
+ return '';
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/Link.php b/modules/monitoring/application/views/helpers/Link.php
new file mode 100644
index 0000000..c5443a4
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/Link.php
@@ -0,0 +1,72 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+/**
+ * Helper for generating frequently used jump links
+ *
+ * Most of the monitoring overviews link to detail information, e.g. the full information of the involved monitored
+ * object. Instead of reintroducing link generation and translation in those views, this helper contains most
+ * frequently used jump links.
+ */
+class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
+{
+ /**
+ * Helper entry point
+ *
+ * @return $this
+ */
+ public function link()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a host link
+ *
+ * @param string $host Hostname
+ * @param string $linkText Link text, e.g. the host's display name
+ *
+ * @return string
+ */
+ public function host($host, $linkText)
+ {
+ return $this->view->qlink(
+ $linkText,
+ 'monitoring/host/show',
+ array('host' => $host),
+ array('title' => sprintf($this->view->translate('Show detailed information for host %s'), $linkText))
+ );
+ }
+
+ /**
+ * Create a service link
+ *
+ * @param string $service Service name
+ * @param string $serviceLinkText Text for the service link, e.g. the service's display name
+ * @param string $host Hostname
+ * @param string $hostLinkText Text for the host link, e.g. the host's display name
+ * @param string $class An optional class to use for this link
+ *
+ * @return string
+ */
+ public function service($service, $serviceLinkText, $host, $hostLinkText, $class = null)
+ {
+ return sprintf(
+ '%s&#58; %s',
+ $this->host($host, $hostLinkText),
+ $this->view->qlink(
+ $serviceLinkText,
+ 'monitoring/service/show',
+ array('host' => $host, 'service' => $service),
+ array(
+ 'title' => sprintf(
+ $this->view->translate('Show detailed information for service %s on host %s'),
+ $serviceLinkText,
+ $hostLinkText
+ ),
+ 'class' => $class
+ )
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/MonitoringFlags.php b/modules/monitoring/application/views/helpers/MonitoringFlags.php
new file mode 100644
index 0000000..8509e5b
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/MonitoringFlags.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/* use Icinga\Module\Monitoring\Object\MonitoredObject; */
+
+/**
+ * Rendering helper for object's properties which may be either enabled or disabled
+ */
+class Zend_View_Helper_MonitoringFlags extends Zend_View_Helper_Abstract
+{
+ /**
+ * Object's properties which may be either enabled or disabled and their human readable description
+ *
+ * @var string[]
+ */
+ private static $flags = array(
+ 'passive_checks_enabled' => 'Passive Checks',
+ 'active_checks_enabled' => 'Active Checks',
+ 'obsessing' => 'Obsessing',
+ 'notifications_enabled' => 'Notifications',
+ 'event_handler_enabled' => 'Event Handler',
+ 'flap_detection_enabled' => 'Flap Detection',
+ );
+
+ /**
+ * Retrieve flags as array with either true or false as value
+ *
+ * @param MonitoredObject $object
+ *
+ * @return array
+ */
+ public function monitoringFlags(/*MonitoredObject*/ $object)
+ {
+ $flags = array();
+ foreach (self::$flags as $column => $description) {
+ $flags[$description] = (bool) $object->{$column};
+ }
+ return $flags;
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/Perfdata.php b/modules/monitoring/application/views/helpers/Perfdata.php
new file mode 100644
index 0000000..e7bc72b
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/Perfdata.php
@@ -0,0 +1,116 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+use Icinga\Module\Monitoring\Plugin\Perfdata;
+use Icinga\Module\Monitoring\Plugin\PerfdataSet;
+use Icinga\Util\StringHelper;
+
+class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
+{
+ /**
+ * Display the given perfdata string to the user
+ *
+ * @param string $perfdataStr The perfdata string
+ * @param bool $compact Whether to display the perfdata in compact mode
+ * @param int $limit Max labels to show; 0 for no limit
+ * @param string $color The color indicating the perfdata state
+ *
+ * @return string
+ */
+ public function perfdata($perfdataStr, $compact = false, $limit = 0, $color = Perfdata::PERFDATA_OK)
+ {
+ $pieChartData = PerfdataSet::fromString($perfdataStr)->asArray();
+ uasort(
+ $pieChartData,
+ function ($a, $b) {
+ return $a->worseThan($b) ? -1 : ($b->worseThan($a) ? 1 : 0);
+ }
+ );
+ $results = array();
+ $keys = array('', 'label', 'value', 'min', 'max', 'warn', 'crit');
+ $columns = array();
+ $labels = array_combine(
+ $keys,
+ array(
+ '',
+ $this->view->translate('Label'),
+ $this->view->translate('Value'),
+ $this->view->translate('Min'),
+ $this->view->translate('Max'),
+ $this->view->translate('Warning'),
+ $this->view->translate('Critical')
+ )
+ );
+ foreach ($pieChartData as $perfdata) {
+ if ($perfdata->isVisualizable()) {
+ $columns[''] = '';
+ }
+ foreach ($perfdata->toArray() as $column => $value) {
+ if (empty($value) ||
+ $column === 'min' && floatval($value) === 0.0 ||
+ $column === 'max' && $perfdata->isPercentage() && floatval($value) === 100) {
+ continue;
+ }
+ $columns[$column] = $labels[$column];
+ }
+ }
+ // restore original column array sorting
+ $headers = array();
+ foreach ($keys as $column) {
+ if (isset($columns[$column])) {
+ $headers[$column] = $labels[$column];
+ }
+ }
+ $table = array('<thead><tr><th>' . implode('</th><th>', $headers) . '</th></tr></thead><tbody>');
+ foreach ($pieChartData as $perfdata) {
+ if ($compact && $perfdata->isVisualizable()) {
+ $results[] = $perfdata->asInlinePie($color)->render();
+ } else {
+ $data = array();
+ if ($perfdata->isVisualizable()) {
+ $data []= $perfdata->asInlinePie($color)->render();
+ } elseif (isset($columns[''])) {
+ $data []= '';
+ }
+ if (! $compact) {
+ foreach ($perfdata->toArray() as $column => $value) {
+ if (! isset($columns[$column])) {
+ continue;
+ }
+ $text = $this->view->escape(empty($value) ? '-' : $value);
+ $data []= sprintf(
+ '<span title="%s">%s</span>',
+ $text,
+ $text
+ );
+ }
+ }
+ $table []= '<tr><td class="sparkline-col">' . implode('</td><td>', $data) . '</td></tr>';
+ }
+ }
+ $table[] = '</tbody>';
+ if ($limit > 0) {
+ $count = $compact ? count($results) : count($table);
+ if ($count > $limit) {
+ if ($compact) {
+ $results = array_slice($results, 0, $limit);
+ $title = sprintf($this->view->translate('%d more ...'), $count - $limit);
+ $results[] = '<span aria-hidden="true" title="' . $title . '">...</span>';
+ } else {
+ $table = array_slice($table, 0, $limit);
+ }
+ }
+ }
+ if ($compact) {
+ return join('', $results);
+ } else {
+ if (empty($table)) {
+ return '';
+ }
+ return sprintf(
+ '<table class="performance-data-table collapsible" data-visible-rows="6">%s</table>',
+ implode("\n", $table)
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/PluginOutput.php b/modules/monitoring/application/views/helpers/PluginOutput.php
new file mode 100644
index 0000000..bcd3d9e
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/PluginOutput.php
@@ -0,0 +1,199 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+use Icinga\Web\Dom\DomNodeIterator;
+use Icinga\Web\View;
+use Icinga\Web\Helper\HtmlPurifier;
+
+/**
+ * Plugin output renderer
+ */
+class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
+{
+ /**
+ * Patterns to be replaced in plain text plugin output
+ *
+ * @var array
+ */
+ protected static $txtPatterns = array(
+ '~\\\t~',
+ '~\\\n~',
+ '~(\[|\()OK(\]|\))~',
+ '~(\[|\()WARNING(\]|\))~',
+ '~(\[|\()CRITICAL(\]|\))~',
+ '~(\[|\()UNKNOWN(\]|\))~',
+ '~(\[|\()UP(\]|\))~',
+ '~(\[|\()DOWN(\]|\))~',
+ '~\@{6,}~'
+ );
+
+ /**
+ * Replacements for $txtPatterns
+ *
+ * @var array
+ */
+ protected static $txtReplacements = array(
+ "\t",
+ "\n",
+ '<span class="state-ok">$1OK$2</span>',
+ '<span class="state-warning">$1WARNING$2</span>',
+ '<span class="state-critical">$1CRITICAL$2</span>',
+ '<span class="state-unknown">$1UNKNOWN$2</span>',
+ '<span class="state-up">$1UP$2</span>',
+ '<span class="state-down">$1DOWN$2</span>',
+ '@@@@@@',
+ );
+
+ /**
+ * Patterns to be replaced in html plugin output
+ *
+ * @var array
+ */
+ protected static $htmlPatterns = array(
+ '~\\\t~',
+ '~\\\n~',
+ '~<table~'
+ );
+
+ /**
+ * Replacements for $htmlPatterns
+ *
+ * @var array
+ */
+ protected static $htmlReplacements = array(
+ "\t",
+ "\n",
+ '<table style="font-size: 0.75em"'
+ );
+
+ /** @var \Icinga\Module\Monitoring\Web\Helper\PluginOutputHookRenderer */
+ protected $hookRenderer;
+
+ public function __construct()
+ {
+ $this->hookRenderer = (new \Icinga\Module\Monitoring\Web\Helper\PluginOutputHookRenderer())->registerHooks();
+ }
+
+ /**
+ * Render plugin output
+ *
+ * @param string $output
+ * @param bool $raw
+ * @param string $command Check command
+ *
+ * @return string
+ */
+ public function pluginOutput($output, $raw = false, $command = null)
+ {
+ if (empty($output)) {
+ return '';
+ }
+
+ if ($command !== null) {
+ $output = $this->hookRenderer->render($command, $output, ! $raw);
+ }
+
+ if (preg_match('~<\w+(?>\s\w+=[^>]*)?>~', $output)) {
+ // HTML
+ $output = HtmlPurifier::process(preg_replace(
+ self::$htmlPatterns,
+ self::$htmlReplacements,
+ $output
+ ));
+ $isHtml = true;
+ } else {
+ // Plaintext
+ $output = preg_replace(
+ self::$txtPatterns,
+ self::$txtReplacements,
+ // Not using the view here to escape this. The view sets `double_encode` to true
+ htmlspecialchars($output, ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5, View::CHARSET, false)
+ );
+ $isHtml = false;
+ }
+
+ $output = trim($output);
+ // Add zero-width space after commas which are not followed by a whitespace character
+ // in oder to help browsers to break words in plugin output
+ $output = preg_replace('/,(?=[^\s])/', ',&#8203;', $output);
+ if (! $raw) {
+ if ($isHtml) {
+ $output = $this->processHtml($output);
+ $output = '<div class="plugin-output">' . $output . '</div>';
+ } else {
+ $output = '<div class="plugin-output preformatted">' . $output . '</div>';
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Replace classic Icinga CGI links with Icinga Web 2 links and color state information, if any
+ *
+ * @param string $html
+ *
+ * @return string
+ */
+ protected function processHtml($html)
+ {
+ $pattern = '/[([](OK|WARNING|CRITICAL|UNKNOWN|UP|DOWN)[)\]]/';
+ $doc = new DOMDocument();
+ $doc->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);
+ $dom = new RecursiveIteratorIterator(new DomNodeIterator($doc), RecursiveIteratorIterator::SELF_FIRST);
+ $nodesToRemove = array();
+ foreach ($dom as $node) {
+ /** @var \DOMNode $node */
+ if ($node->nodeType === XML_TEXT_NODE) {
+ $start = 0;
+ while (preg_match($pattern, $node->nodeValue, $match, PREG_OFFSET_CAPTURE, $start)) {
+ $offsetLeft = $match[0][1];
+ $matchLength = strlen($match[0][0]);
+ $leftLength = $offsetLeft - $start;
+ // if there is text before the match
+ if ($leftLength) {
+ // create node for leading text
+ $text = new DOMText(substr($node->nodeValue, $start, $leftLength));
+ $node->parentNode->insertBefore($text, $node);
+ }
+ // create the new element for the match
+ $span = $doc->createElement('span', $match[0][0]);
+ $span->setAttribute('class', 'state-' . strtolower($match[1][0]));
+ $node->parentNode->insertBefore($span, $node);
+
+ // start for next match
+ $start = $offsetLeft + $matchLength;
+ }
+ if ($start) {
+ // is there text left?
+ if (strlen($node->nodeValue) > $start) {
+ // create node for trailing text
+ $text = new DOMText(substr($node->nodeValue, $start));
+ $node->parentNode->insertBefore($text, $node);
+ }
+ // delete the old node later
+ $nodesToRemove[] = $node;
+ }
+ } elseif ($node->nodeType === XML_ELEMENT_NODE) {
+ /** @var \DOMElement $node */
+ if ($node->tagName === 'a'
+ && preg_match('~^/cgi\-bin/status\.cgi\?(.+)$~', $node->getAttribute('href'), $match)
+ ) {
+ parse_str($match[1], $params);
+ if (isset($params['host'])) {
+ $node->setAttribute(
+ 'href',
+ $this->view->baseUrl('/monitoring/host/show?host=' . urlencode($params['host']))
+ );
+ }
+ }
+ }
+ }
+ foreach ($nodesToRemove as $node) {
+ /** @var \DOMNode $node */
+ $node->parentNode->removeChild($node);
+ }
+
+ return substr($doc->saveHTML(), 5, -7);
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/RuntimeVariables.php b/modules/monitoring/application/views/helpers/RuntimeVariables.php
new file mode 100644
index 0000000..e80e8aa
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/RuntimeVariables.php
@@ -0,0 +1,50 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * Convert runtime summary data into a simple usable stdClass
+ */
+class Zend_View_Helper_RuntimeVariables extends Zend_View_Helper_Abstract
+{
+ /**
+ * Create dispatch instance
+ *
+ * @return $this
+ */
+ public function runtimeVariables()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a condensed row of object data
+ *
+ * @param $result stdClass
+ *
+ * @return stdClass Condensed row
+ */
+ public function create(stdClass $result)
+ {
+ $out = new stdClass();
+ $out->total_hosts = isset($result->total_hosts)
+ ? $result->total_hosts
+ : 0;
+ $out->total_scheduled_hosts = isset($result->total_scheduled_hosts)
+ ? $result->total_scheduled_hosts
+ : 0;
+ $out->total_services = isset($result->total_services)
+ ? $result->total_services
+ : 0;
+ $out->total_scheduled_services = isset($result->total_scheduled_services)
+ ? $result->total_scheduled_services
+ : 0;
+ $out->average_services_per_host = $out->total_hosts > 0
+ ? $out->total_services / $out->total_hosts
+ : 0;
+ $out->average_scheduled_services_per_host = $out->total_scheduled_hosts > 0
+ ? $out->total_scheduled_services / $out->total_scheduled_hosts
+ : 0;
+
+ return $out;
+ }
+}
diff --git a/modules/monitoring/application/views/helpers/ServiceFlags.php b/modules/monitoring/application/views/helpers/ServiceFlags.php
new file mode 100644
index 0000000..47a351c
--- /dev/null
+++ b/modules/monitoring/application/views/helpers/ServiceFlags.php
@@ -0,0 +1,33 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+class Zend_View_Helper_ServiceFlags extends Zend_View_Helper_Abstract
+{
+ public function serviceFlags($service)
+ {
+ $icons = array();
+ if (! $service->service_handled && $service->service_state > 0) {
+ $icons[] = $this->view->icon('attention-alt', $this->view->translate('Unhandled'));
+ }
+ if ($service->service_acknowledged) {
+ $icons[] = $this->view->icon('ok', $this->view->translate('Acknowledged'));
+ }
+ if ($service->service_is_flapping) {
+ $icons[] = $this->view->icon('flapping', $this->view->translate('Flapping'));
+ }
+ if (! $service->service_notifications_enabled) {
+ $icons[] = $this->view->icon('bell-off-empty', $this->view->translate('Notifications Disabled'));
+ }
+ if ($service->service_in_downtime) {
+ $icons[] = $this->view->icon('plug', $this->view->translate('In Downtime'));
+ }
+ if (! $service->service_active_checks_enabled) {
+ if (! $service->service_passive_checks_enabled) {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active And Passive Checks Disabled'));
+ } else {
+ $icons[] = $this->view->icon('eye-off', $this->view->translate('Active Checks Disabled'));
+ }
+ }
+ return implode(' ', $icons);
+ }
+}
diff --git a/modules/monitoring/application/views/scripts/comment/remove.phtml b/modules/monitoring/application/views/scripts/comment/remove.phtml
new file mode 100644
index 0000000..73f8c68
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comment/remove.phtml
@@ -0,0 +1,11 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <?= $this->render('partials/downtime/downtime-header.phtml'); ?>
+</div>
+<div class="content object-command">
+ <?= $delDowntimeForm; ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/comment/show.phtml b/modules/monitoring/application/views/scripts/comment/show.phtml
new file mode 100644
index 0000000..3cbfb76
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comment/show.phtml
@@ -0,0 +1,86 @@
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <div data-base-target='_next'>
+ <?= $this->render('partials/comment/comment-header.phtml'); ?>
+ </div>
+</div>
+<div class="content">
+
+<h2><?= $this->translate('Comment detail information') ?></h2>
+<table class="name-value-table">
+ <tbody>
+ <tr>
+ <?php if ($this->comment->objecttype === 'service'): ?>
+ <th> <?= $this->translate('Service') ?> </th>
+ <td>
+ <?= $this->icon('service', $this->translate('Service')); ?>
+ <?= $this->link()->service(
+ $this->comment->service_description,
+ $this->comment->service_display_name,
+ $this->comment->host_name,
+ $this->comment->host_display_name
+ );
+ ?>
+ </td>
+ <?php else: ?>
+ <th> <?= $this->translate('Host') ?> </th>
+ <td>
+ <?= $this->icon('host', $this->translate('Host')); ?>
+ <?= $this->link()->host(
+ $this->comment->host_name,
+ $this->comment->host_display_name
+ );
+ ?>
+ </td>
+ <?php endif ?>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Author') ?></th>
+ <td><?= $this->icon('user', $this->translate('User')) ?> <?= $this->escape($this->comment->author) ?></td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Persistent') ?></th>
+ <td><?= $this->escape($this->comment->persistent) ? $this->translate('Yes') : $this->translate('No') ?></td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Created') ?></th>
+ <td><?= $this->formatDateTime($this->comment->timestamp) ?></td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Expires') ?></th>
+ <td>
+ <?= $this->comment->expiration ? sprintf(
+ $this->translate('This comment expires on %s at %s.'),
+ $this->formatDate($this->comment->expiration),
+ $this->formatTime($this->comment->expiration)
+ ) : $this->translate('This comment does not expire.');
+ ?>
+ </td>
+ </tr>
+
+ <tr>
+ <th><?= $this->translate('Comment') ?></th>
+ <td><?= $this->nl2br($this->createTicketLinks($this->markdown($comment->comment))) ?></td>
+ </tr>
+
+ <?php if (isset($delCommentForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr class="newsection">
+ <th><?= $this->translate('Commands') ?></th>
+ <td>
+ <?= $delCommentForm ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ </tbody>
+</table>
+
+</div>
+
diff --git a/modules/monitoring/application/views/scripts/comments/delete-all.phtml b/modules/monitoring/application/views/scripts/comments/delete-all.phtml
new file mode 100644
index 0000000..698c4ee
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comments/delete-all.phtml
@@ -0,0 +1,12 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <?= $this->render('partials/comment/comments-header.phtml'); ?>
+</div>
+
+<div class="content object-command">
+ <?= $delCommentForm ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/comments/show.phtml b/modules/monitoring/application/views/scripts/comments/show.phtml
new file mode 100644
index 0000000..67e1c6b
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/comments/show.phtml
@@ -0,0 +1,19 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/comment/comments-header.phtml') ?>
+</div>
+
+<div class="content multi-commands">
+ <h2><?= $this->translate('Commands') ?></h2>
+ <?= $this->qlink(
+ sprintf($this->translate('Remove %d comments'), $comments->count()),
+ $removeAllLink,
+ null,
+ array(
+ 'icon' => 'trash',
+ 'title' => $this->translate('Remove all selected comments')
+ )
+ ) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/config/form.phtml b/modules/monitoring/application/views/scripts/config/form.phtml
new file mode 100644
index 0000000..cbf0659
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/form.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs->showOnlyCloseButton(); ?>
+</div>
+<div class="content">
+ <?= $form; ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/config/index.phtml b/modules/monitoring/application/views/scripts/config/index.phtml
new file mode 100644
index 0000000..a1264c2
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/index.phtml
@@ -0,0 +1,78 @@
+<div class="controls">
+ <?= $tabs ?>
+</div>
+
+<div class="content" data-base-target="_next">
+ <div>
+ <h2><?= $this->translate('Monitoring Backends') ?></h2>
+ <?= $this->qlink(
+ $this->translate('Create a New Monitoring Backend') ,
+ 'monitoring/config/createbackend',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new monitoring backend')
+ )
+ ) ?>
+ <table class="table-row-selectable common-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Monitoring Backend') ?></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->backendsConfig as $backendName => $config): ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $backendName,
+ 'monitoring/config/editbackend',
+ array('backend-name' => $backendName),
+ array(
+ 'icon' => 'edit',
+ 'title' => sprintf($this->translate('Edit monitoring backend %s'), $backendName)
+ )
+ ) ?>
+ <span class="config-label-meta">&#40;<?= sprintf(
+ $this->translate('Type: %s'),
+ $this->escape($config->type === 'ido' ? 'IDO' : ucfirst($config->type))
+ ) ?>&#41;
+ </span>
+ </td>
+ <td class="text-right">
+ <?= $this->qlink(
+ '',
+ 'monitoring/config/removebackend',
+ array('backend-name' => $backendName),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove monitoring backend %s'), $backendName)
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+ </div>
+ <div>
+ <h2><?= $this->translate('Command Transports') ?></h2>
+ <?= $this->qlink(
+ $this->translate('Create a New Command Transport') ,
+ 'monitoring/config/createtransport',
+ null,
+ array(
+ 'class' => 'button-link',
+ 'icon' => 'plus',
+ 'title' => $this->translate('Create a new command transport')
+ )
+ ) ?>
+ <?php
+ /** @var \Icinga\Module\Monitoring\Forms\Config\TransportReorderForm $commandTransportReorderForm */
+ echo $commandTransportReorderForm;
+ ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/config/security.phtml b/modules/monitoring/application/views/scripts/config/security.phtml
new file mode 100644
index 0000000..3801678
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/config/security.phtml
@@ -0,0 +1,6 @@
+<div class="controls">
+ <?= $tabs; ?>
+</div>
+<div class="content">
+ <?= $form; ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/downtime/remove.phtml b/modules/monitoring/application/views/scripts/downtime/remove.phtml
new file mode 100644
index 0000000..34a7dbd
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtime/remove.phtml
@@ -0,0 +1,13 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <table>
+ <tr> <?= $this->render('partials/downtime/downtime-header.phtml') ?> </tr>
+ </table>
+</div>
+<div class="content object-command">
+ <?= $delDowntimeForm; ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/downtime/show.phtml b/modules/monitoring/application/views/scripts/downtime/show.phtml
new file mode 100644
index 0000000..4db03cb
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtime/show.phtml
@@ -0,0 +1,173 @@
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <table>
+ <tr> <?= $this->render('partials/downtime/downtime-header.phtml'); ?> </tr>
+ </table>
+</div>
+<div class="content"><h2><?= $this->translate('Details') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th>
+ <?= $this->isService ? $this->translate('Service') : $this->translate('Host') ?>
+ </th>
+ <td data-base-target="_next">
+ <?php if ($this->isService): ?>
+ <?php
+ $link = $this->link()->service(
+ $downtime->service_description,
+ $downtime->service_display_name,
+ $downtime->host_name,
+ $downtime->host_display_name
+ );
+ $icon = $this->icon('service', $this->translate('Service'));
+ ?>
+ <?php else: ?>
+ <?php
+ $icon = $this->icon('host', $this->translate('Host'));
+ $link = $this->link()->host($downtime->host_name, $downtime->host_display_name)
+ ?>
+ <?php endif ?>
+ <?= $icon ?>
+ <?= $link ?>
+ </td>
+ </tr>
+ <tr title="<?= $this->translate('The name of the person who scheduled this downtime'); ?>">
+ <th><?= $this->translate('Author') ?></th>
+ <td><?= $this->icon('user', $this->translate('User')) ?> <?= $this->escape($this->downtime->author_name) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('Date and time this downtime was entered'); ?>">
+ <th><?= $this->translate('Entry Time') ?></th>
+ <td><?= $this->formatDateTime($this->downtime->entry_time) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('A comment, as entered by the author, associated with the scheduled downtime'); ?>">
+ <th><?= $this->translate('Comment') ?></th>
+ <td><?= $this->nl2br($this->createTicketLinks($this->markdown($downtime->comment))) ?></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2> <?= $this->translate('Duration') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr class="newsection">
+ <th><?= $this->escape(
+ $this->downtime->is_flexible ?
+ $this->translate('Flexible') : $this->translate('Fixed')
+ ); ?>
+ <?= $this->icon('info-circled', $this->downtime->is_flexible ?
+ $this->translate('Flexible downtimes have a hard start and end time,'
+ . ' but also an additional restriction on the duration in which '
+ . ' the host or service may actually be down.') :
+ $this->translate('Fixed downtimes have a static start and end time.')) ?>
+ </th>
+ <td>
+ <?php if ($downtime->is_flexible): ?>
+ <?php if ($downtime->is_in_effect): ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This flexible service downtime was started on %s at %s and lasts for %s until %s at %s.')
+ : $this->translate('This flexible host downtime was started on %s at %s and lasts for %s until %s at %s.'),
+ $this->formatDate($downtime->start),
+ $this->formatTime($downtime->start),
+ $this->formatDuration($downtime->duration),
+ $this->formatDate($downtime->end),
+ $this->formatTime($downtime->end)
+ ) ?>
+ <?php else: ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This flexible service downtime has been scheduled to start between %s - %s and to last for %s.')
+ : $this->translate('This flexible host downtime has been scheduled to start between %s - %s and to last for %s.'),
+ $this->formatDateTime($downtime->scheduled_start),
+ $this->formatDateTime($downtime->scheduled_end),
+ $this->formatDuration($downtime->duration)
+ ) ?>
+ <?php endif ?>
+ <?php else: ?>
+ <?php if ($downtime->is_in_effect): ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This fixed service downtime was started on %s at %s and expires on %s at %s.')
+ : $this->translate('This fixed host downtime was started on %s at %s and expires on %s at %s.'),
+ $this->formatDate($downtime->start),
+ $this->formatTime($downtime->start),
+ $this->formatDate($downtime->end),
+ $this->formatTime($downtime->end)
+ ) ?>
+ <?php else: ?>
+ <?= sprintf(
+ $isService
+ ? $this->translate('This fixed service downtime has been scheduled to start on %s at %s and to end on %s at %s.')
+ : $this->translate('This fixed host downtime has been scheduled to start on %s at %s and to end on %s at %s.'),
+ $this->formatDate($downtime->start),
+ $this->formatTime($downtime->start),
+ $this->formatDate($downtime->end),
+ $this->formatTime($downtime->end)
+ ) ?>
+ <?php endif ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <tr title="<?= $this->translate('The date/time the scheduled downtime is'
+ . ' supposed to start. If this is a flexible (non-fixed) downtime, '
+ . 'this refers to the earliest possible time that the downtime'
+ . ' can start'); ?>">
+ <th><?= $this->translate('Scheduled start') ?></th>
+ <td><?= $this->formatDateTime($this->downtime->scheduled_start) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('The date/time the scheduled downtime is '
+ . 'supposed to end. If this is a flexible (non-fixed) downtime, '
+ . 'this refers to the last possible time that the downtime can '
+ . 'start'); ?>">
+ <th><?= $this->translate('Scheduled end') ?></th>
+ <td><?= $this->formatDateTime($this->downtime->scheduled_end) ?></td>
+ </tr>
+ <?php if ($this->downtime->is_flexible): ?>
+ <tr title="<?= $this->translate('Indicates the number of seconds that the '
+ . 'scheduled downtime should last. This is usually only needed if'
+ . ' this is a flexible downtime, which can start at a variable '
+ . 'time, but lasts for the specified duration'); ?>">
+ <th tit><?= $this->translate('Duration') ?></th>
+ <td><?= $this->formatDuration($this->downtime->duration) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('he date/time the scheduled downtime was'
+ . ' actually started'); ?>">
+ <th><?= $this->translate('Actual start time') ?></th>
+ <td><?= $this->formatDateTime($downtime->start) ?></td>
+ </tr>
+ <tr title="<?= $this->translate('The date/time the scheduled downtime '
+ . 'actually ended'); ?>">
+ <th><?= $this->translate('Actual end time') ?></th>
+ <td><?= $this->formatDateTime($downtime->end) ?></td>
+ </tr>
+ <?php endif; ?>
+
+ <tr class="newsection">
+ <th><?= $this->translate('In effect') ?></th>
+ <td>
+ <?= $this->escape(
+ $this->downtime->is_in_effect ?
+ $this->translate('Yes') : $this->translate('No')
+ );
+ ?>
+ </td>
+ </tr>
+
+ <?php if (isset($delDowntimeForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr class="newsection">
+ <th><?= $this->translate('Commands') ?></th>
+ <td>
+ <?= $delDowntimeForm ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+</div>
+
diff --git a/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml
new file mode 100644
index 0000000..e6435fe
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtimes/delete-all.phtml
@@ -0,0 +1,12 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+
+ <?= $this->render('partials/downtime/downtimes-header.phtml'); ?>
+</div>
+
+<div class="content object-command">
+ <?= $delAllDowntimeForm ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/downtimes/show.phtml b/modules/monitoring/application/views/scripts/downtimes/show.phtml
new file mode 100644
index 0000000..73d9bf6
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/downtimes/show.phtml
@@ -0,0 +1,19 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/downtime/downtimes-header.phtml') ?>
+</div>
+
+<div class="content multi-commands">
+ <h2> <?= $this->translate('Commands') ?> </h2>
+ <?= $this->qlink(
+ sprintf($this->translate('Remove all %d scheduled downtimes'), $downtimes->count()),
+ $removeAllLink,
+ null,
+ array(
+ 'icon' => 'trash',
+ 'title' => $this->translate('Remove all selected downtimes')
+ )
+ ) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/event/show.phtml b/modules/monitoring/application/views/scripts/event/show.phtml
new file mode 100644
index 0000000..c844a6f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/event/show.phtml
@@ -0,0 +1,34 @@
+<?php
+use Icinga\Module\Monitoring\Object\Service;
+
+/** @var string[][] $details */
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+/** @var \Icinga\Web\View $this */
+?>
+<div class="controls">
+<?php
+if (! $this->compact) {
+ echo $this->tabs;
+}
+
+echo $object instanceof Service
+ ? '<h2>' . $this->translate('Current Service State') . '</h2>' . $this->render('partials/object/service-header.phtml')
+ : '<h2>' . $this->translate('Current Host State') . '</h2>' . $this->render('partials/object/host-header.phtml');
+?>
+</div>
+<div class="content">
+ <?php
+ foreach ($extensionsHtml as $extensionHtml) {
+ echo $extensionHtml;
+ }
+ ?>
+
+ <h2><?= $this->escape($this->translate('Event Details')) ?></h2>
+ <table class="event-details name-value-table" data-base-target="_next">
+ <?php
+ foreach ($details as $detail) {
+ echo '<tr><th>' . $this->escape($detail[0]) . '</th><td>' . $detail[1] . '</td></tr>';
+ }
+ ?>
+ </table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml b/modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml
new file mode 100644
index 0000000..2f81610
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/form/reorder-command-transports.phtml
@@ -0,0 +1,93 @@
+<?php
+/** @var \Icinga\Web\View $this */
+/** @var \Icinga\Module\Monitoring\Forms\Config\TransportReorderForm $form */
+?>
+<form id="<?=
+$this->escape($form->getId())
+?>" name="<?=
+$this->escape($form->getName())
+?>" enctype="<?=
+$this->escape($form->getEncType())
+?>" method="<?=
+$this->escape($form->getMethod())
+?>" action="<?=
+$this->escape($form->getAction())
+?>">
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Transport') ?></th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php
+ $i = -1;
+ $transportConfig = $form->getConfig();
+ $total = $transportConfig->count();
+ foreach ($transportConfig as $transportName => $config):
+ ++$i;
+ ?>
+ <tr>
+ <td>
+ <?= $this->qlink(
+ $transportName,
+ 'monitoring/config/edittransport',
+ array('transport' => $transportName),
+ array(
+ 'icon' => 'edit',
+ 'title' => sprintf($this->translate('Edit command transport %s'), $transportName)
+ )
+ ); ?>
+ <span class="config-label-meta">&#40;<?= sprintf(
+ $this->translate('Type: %s'),
+ ucfirst($config->get('transport', 'local'))
+ ) ?>&#41;
+ </span>
+ </td>
+ <td class="text-right">
+ <?= $this->qlink(
+ '',
+ 'monitoring/config/removetransport',
+ array('transport' => $transportName),
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'cancel',
+ 'title' => sprintf($this->translate('Remove command transport %s'), $transportName)
+ )
+ ); ?>
+ </td>
+ <td class="icon-col text-right" data-base-target="_self">
+ <?php if ($i > 0): ?>
+ <button type="submit" name="transport_newpos" class="link-button icon-only animated move-up" value="<?= $this->escape(
+ ($i - 1) . '|' . $transportName
+ ) ?>" title="<?= $this->translate(
+ 'Move up in order'
+ ) ?>" aria-label="<?= $this->escape(sprintf(
+ $this->translate('Move command transport %s upwards'),
+ $transportName
+ )) ?>"><?=
+ $this->icon('up-small')
+ ?></button>
+ <?php endif ?>
+ <?php if ($i + 1 < $total): ?>
+ <button type="submit" name="transport_newpos" class="link-button icon-only animated move-down" value="<?= $this->escape(
+ ($i + 1) . '|' . $transportName
+ ) ?>" title="<?= $this->translate(
+ 'Move down in order'
+ ) ?>" aria-label="<?= $this->escape(sprintf(
+ $this->translate('Move command transport %s downwards'),
+ $transportName
+ )) ?>"><?=
+ $this->icon('down-small')
+ ?></button>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+ <?= $form->getElement($form->getTokenElementName()) ?>
+ <?= $form->getElement($form->getUidElementName()) ?>
+</form>
diff --git a/modules/monitoring/application/views/scripts/health/disable-notifications.phtml b/modules/monitoring/application/views/scripts/health/disable-notifications.phtml
new file mode 100644
index 0000000..e8c75e5
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/disable-notifications.phtml
@@ -0,0 +1,20 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs->showOnlyCloseButton(); ?>
+</div>
+<?php endif ?>
+<div class="content">
+ <h1><?= $title; ?></h1>
+ <?php if ((bool) $programStatus->notifications_enabled === false): ?>
+ <div>
+ <?= $this->translate('Host and service notifications are already disabled.') ?>
+ <?php if ($this->programStatus->disable_notif_expire_time): ?>
+ <?= sprintf(
+ $this->translate('Notifications will be re-enabled in <strong>%s</strong>.'),
+ $this->timeUntil($this->programStatus->disable_notif_expire_time)); ?>
+ <?php endif; ?>
+ </div>
+ <?php else: ?>
+ <?= $form; ?>
+ <?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/health/info.phtml b/modules/monitoring/application/views/scripts/health/info.phtml
new file mode 100644
index 0000000..76d9ee3
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/info.phtml
@@ -0,0 +1,87 @@
+<?php
+$rv = $this->runtimeVariables()->create($this->runtimevariables);
+$cp = $this->checkPerformance()->create($this->checkperformance);
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+</div>
+<?php endif ?>
+
+<div class="content processinfo">
+ <div class="boxview">
+ <div class="box process">
+ <h2 tabindex="0"><?= $this->translate('Process Info') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Program Version') ?></th>
+ <td><?= $this->programStatus->program_version
+ ? $this->programStatus->program_version
+ : $this->translate('N/A') ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Program Start Time') ?></th>
+ <td><?= $this->formatDateTime($this->programStatus->program_start_time) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last Status Update'); ?></th>
+ <td><?= $this->timeAgo($this->programStatus->status_update_time); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last External Command Check'); ?></th>
+ <td><?= $this->timeAgo($this->programStatus->last_command_check); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Last Log File Rotation'); ?></th>
+ <td><?= $this->programStatus->last_log_rotation
+ ? $this->timeSince($this->programStatus->last_log_rotation)
+ : $this->translate('N/A') ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Global Service Event Handler'); ?></th>
+ <td><?= $this->programStatus->global_service_event_handler
+ ? $this->programStatus->global_service_event_handler
+ : $this->translate('N/A'); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Global Host Event Handler'); ?></th>
+ <td><?= $this->programStatus->global_host_event_handler
+ ? $this->programStatus->global_host_event_handler
+ : $this->translate('N/A'); ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Active Endpoint'); ?></th>
+ <td><?= $this->programStatus->endpoint_name
+ ? $this->programStatus->endpoint_name
+ : $this->translate('N/A') ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Active Icinga Web 2 Endpoint'); ?></th>
+ <td><?= gethostname() ?: $this->translate('N/A') ?></td>
+ </tr>
+ </tbody>
+ </table>
+ <?php if ((bool) $this->programStatus->is_currently_running === true): ?>
+ <div class="backend-running">
+ <?= sprintf(
+ $this->translate(
+ '%1$s has been up and running with PID %2$d %3$s',
+ 'Last format parameter represents the time running'
+ ),
+ $this->backendName,
+ $this->programStatus->process_id,
+ $this->timeSince($this->programStatus->program_start_time)) ?>
+ </div>
+ <?php else: ?>
+ <div class="backend-not-running">
+ <?= sprintf($this->translate('Backend %s is not running'), $this->backendName) ?>
+ </div>
+ <?php endif ?>
+ </div>
+ <div class="box features">
+ <h2 tabindex="0"><?= $this->translate('Feature Commands') ?></h2>
+ <?= $this->toggleFeaturesForm ?>
+ </div>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/health/not-running.phtml b/modules/monitoring/application/views/scripts/health/not-running.phtml
new file mode 100644
index 0000000..8439fc4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/not-running.phtml
@@ -0,0 +1,8 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+</div>
+<?php endif ?>
+<div class="content">
+ <?= sprintf($this->translate('%s is currently not up and running'), $this->backendName) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/health/stats.phtml b/modules/monitoring/application/views/scripts/health/stats.phtml
new file mode 100644
index 0000000..5cfb8f9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/health/stats.phtml
@@ -0,0 +1,150 @@
+<?php
+$rv = $this->runtimeVariables()->create($this->runtimevariables);
+$cp = $this->checkPerformance()->create($this->checkperformance);
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+</div>
+<?php endif ?>
+
+<div class="content stats">
+ <div class="boxview">
+ <div class="box stats">
+ <h2 tabindex="0"><?= $this->unhandledProblems ?> <?= $this->translate('Unhandled Problems:') ?></h2>
+ <table class="name-value-table">
+ <thead>
+ <th></th>
+ <th colspan="3"></th>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Service Problems:') ?></th>
+ <td colspan="3">
+ <span class="badge state-critical">
+ <?=
+ $this->qlink(
+ $this->unhandledServiceProblems,
+ 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
+ null,
+ array('data-base-target' => '_next')
+ )
+ ?>
+ </span>
+ </td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Host Problems:') ?></th>
+ <td colspan="3">
+ <span class="badge state-critical">
+ <?=
+ $this->qlink(
+ $this->unhandledhostProblems,
+ 'monitoring/list/hosts?host_problem=1&host_handled=0',
+ null,
+ array('data-base-target' => '_next')
+ )
+ ?>
+ </span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2 tabindex="0" class="tinystatesummary" data-base-target="_next">
+ <?php $this->stats = $hoststats ?>
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ </h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Runtime Variables') ?></th>
+ <th colspan="3"><?= $this->translate('Host Checks') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Total') ?></th>
+ <td><?= $rv->total_scheduled_hosts ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Scheduled') ?></th>
+ <td><?= $rv->total_scheduled_hosts ?></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2 class="tinystatesummary" data-base-target="_next">
+ <?php $this->stats = $servicestats ?>
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ </h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Runtime Variables') ?></th>
+ <th><?= $this->translate('Service Checks') ?></th>
+ <th colspan="2"><?= $this->translate('Per Host') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Total') ?></th>
+ <td><?= $rv->total_services ?></td>
+ <td><?= sprintf('%.2f', $rv->average_services_per_host) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Scheduled') ?></th>
+ <td><?= $rv->total_scheduled_services ?></td>
+ <td><?= sprintf('%.2f', $rv->average_scheduled_services_per_host) ?></td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2><?= $this->translate('Active checks') ?></h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Check Performance') ?></th>
+ <th><?= $this->translate('Checks') ?></th>
+ <th><?= $this->translate('Latency') ?></th>
+ <th><?= $this->translate('Execution time') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Host Checks') ?></th>
+ <td><?= $cp->host_active_count; ?></td>
+ <td><?= sprintf('%.3f', $cp->host_active_latency_avg) ?>s</td>
+ <td><?= sprintf('%.3f', $cp->host_active_execution_avg) ?>s</td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Service Checks') ?></th>
+ <td><?= $cp->service_active_count; ?></td>
+ <td><?= sprintf('%.3f', $cp->service_active_latency_avg) ?>s</td>
+ <td><?= sprintf('%.3f', $cp->service_active_execution_avg) ?>s</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <h2><?= $this->translate('Passive checks') ?></h2>
+ <table class="name-value-table">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Check Performance') ?></th>
+ <th colspan="3"><?= $this->translate('Passive Checks') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <th><?= $this->translate('Host Checks') ?></th>
+ <td><?= $cp->host_passive_count ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Service Checks') ?></th>
+ <td><?= $cp->service_passive_count ?></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/host/services.phtml b/modules/monitoring/application/views/scripts/host/services.phtml
new file mode 100644
index 0000000..ac1dc5b
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/host/services.phtml
@@ -0,0 +1,23 @@
+<?php use Icinga\Data\Filter\Filter; ?>
+
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+ <?= $this->render('partials/object/host-header.phtml') ?>
+ <?php
+ $this->baseFilter = Filter::where('host', $object->host_name);
+ $this->stats = $object->stats;
+ echo $this->render('list/components/servicesummary.phtml');
+ ?>
+</div>
+<?= $this->partial(
+ 'list/services.phtml',
+ 'monitoring',
+ array(
+ 'compact' => true,
+ 'showHost' => false,
+ 'services' => $services,
+ 'addColumns' => array()
+ )
+); ?>
diff --git a/modules/monitoring/application/views/scripts/host/show.phtml b/modules/monitoring/application/views/scripts/host/show.phtml
new file mode 100644
index 0000000..72f5af4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/host/show.phtml
@@ -0,0 +1,14 @@
+<?php use Icinga\Data\Filter\Filter; ?>
+<div class="controls controls-separated">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/object/host-header.phtml') ?>
+<?php
+ $this->stats = $object->stats;
+ $this->baseFilter = Filter::where('host', $object->host_name);
+ echo $this->render('list/components/servicesummary.phtml');
+?>
+ <?= $this->render('partials/object/quick-actions.phtml') ?>
+</div>
+<?= $this->render('partials/object/detail-content.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/hosts/show.phtml b/modules/monitoring/application/views/scripts/hosts/show.phtml
new file mode 100644
index 0000000..97b8434
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/hosts/show.phtml
@@ -0,0 +1,206 @@
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $tabs; ?>
+ <?php endif ?>
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ <?= $this->render('partials/host/objects-header.phtml'); ?>
+ <?php
+ $hostCount = count($objects);
+ $unhandledCount = count($unhandledObjects);
+ $problemCount = count($problemObjects);
+ $unackCount = count($unacknowledgedObjects);
+ $scheduledDowntimeCount = count($objects->getScheduledDowntimes());
+ ?>
+</div>
+
+<div class="content">
+ <?php if ($hostCount === 0): ?>
+ <?= $this->translate('No hosts found matching the filter'); ?>
+ <?php else: ?>
+ <?= $this->render('show/components/extensions.phtml') ?>
+ <h2><?= $this->translate('Problem Handling') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?php
+
+ if ($unackCount > 0): ?>
+ <tr>
+ <th> <?= sprintf($this->translate('%d unhandled problems'), $unackCount) ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Acknowledge'),
+ $acknowledgeLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'check'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif; ?>
+
+ <?php if (($acknowledgedCount = count($acknowledgedObjects)) > 0): ?>
+ <tr>
+ <th> <?= sprintf(
+ $this->translatePlural(
+ '%s acknowledgement',
+ '%s acknowledgements',
+ $acknowledgedCount
+ ),
+ '<b>' . $acknowledgedCount . '</b>'
+ ); ?> </th>
+ <td>
+ <?= $removeAckForm->setLabelEnabled(true) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th> <?= $this->translate('Comments') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Add comments'),
+ $addCommentLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'comment-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (($commentCount = count($objects->getComments())) > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%s comment',
+ '%s comments',
+ $commentCount
+ ),
+ $commentCount
+ ),
+ $commentsLink,
+ null,
+ array('data-base-target' => '_next')
+ ); ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th>
+ <?= $this->translate('Downtimes') ?>
+ </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Schedule downtimes'),
+ $downtimeAllLink,
+ null,
+ array(
+ 'icon' => 'plug',
+ 'class' => 'action-link'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if ($scheduledDowntimeCount > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%d scheduled downtime',
+ '%d scheduled downtimes',
+ $scheduledDowntimeCount
+ ),
+ $scheduledDowntimeCount
+ ),
+ $showDowntimesLink,
+ null,
+ array(
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+ <?php if ($this->hasPermission('monitoring/command/send-custom-notification')): ?>
+ <h2> <?= $this->translate('Notifications') ?> </h2>
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Notifications') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Send notifications'),
+ $sendCustomNotificationLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'bell'
+ )
+ ) ?>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <?php endif ?>
+
+ <h2> <?= $this->translate('Check Execution') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Command') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Process check result'),
+ $processCheckResultAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'edit'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (isset($checkNowForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr>
+ <th> <?= $this->translate('Schedule Check') ?> </th>
+ <td> <?= $checkNowForm ?> </td>
+ </tr>
+ <?php endif ?>
+
+ <?php if (isset($rescheduleAllLink)): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Reschedule'),
+ $rescheduleAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'calendar-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+ <h2><?= $this->translate('Feature Commands') ?></h2>
+ <?= $toggleFeaturesForm ?>
+ <?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/comments.phtml b/modules/monitoring/application/views/scripts/list/comments.phtml
new file mode 100644
index 0000000..c7fb86a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/comments.phtml
@@ -0,0 +1,61 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $comments->hasResult()): ?>
+ <p><?= $this->translate('No comments found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next"
+ class="table-row-selectable common-table multiselect"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/comments/show') ?>"
+ data-icinga-multiselect-related="<?= $this->href("monitoring/comments") ?>"
+ data-icinga-multiselect-data="comment_id">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('Type') ?></th>
+ <th><?= $this->translate('Comment') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($comments->peekAhead($this->compact) as $comment): ?>
+ <tr href="<?= $this->href('monitoring/comment/show', array('comment_id' => $comment->id)) ?>">
+ <td class="icon-col">
+ <?= $this->partial('partials/comment/comment-description.phtml', array('comment' => $comment)) ?>
+ </td>
+ <td>
+ <?= $this->partial(
+ 'partials/comment/comment-detail.phtml',
+ array(
+ 'comment' => $comment,
+ 'delCommentForm' => isset($delCommentForm) ? $delCommentForm : null
+ // Form is unset if the current user lacks the respective permission
+ )) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($comments->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml
new file mode 100644
index 0000000..4b9f1cd
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/components/hostssummary.phtml
@@ -0,0 +1,92 @@
+<?php
+use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+use Icinga\Web\Url;
+
+// Don't fetch rows until they are actually needed to improve dashlet performance
+if (! $stats instanceof stdClass) {
+ $stats = $stats->fetchRow();
+}
+?>
+<div class="hosts-summary dont-print">
+ <span class="hosts-link"><?= $this->qlink(
+ sprintf($this->translatePlural('%u Host', '%u Hosts', $stats->hosts_total), $stats->hosts_total),
+ // @TODO(el): Fix that
+ Url::fromPath('monitoring/list/hosts')->setParams(isset($baseFilter) ? $baseFilter->getUrlParams() : array()),
+ null,
+ array('title' => sprintf(
+ $this->translatePlural('List %u host', 'List all %u hosts', $stats->hosts_total),
+ $stats->hosts_total
+ ))
+ ) ?>&#58;</span>
+<?php
+$stateBadges = new StateBadges();
+$stateBadges
+ ->setBaseFilter(isset($baseFilter) ? $baseFilter : null)
+ ->setUrl('monitoring/list/hosts')
+ ->add(
+ StateBadges::STATE_UP,
+ $stats->hosts_up,
+ array(
+ 'host_state' => 0
+ ),
+ 'List %u host that is currently in state UP',
+ 'List %u hosts which are currently in state UP',
+ array($stats->hosts_up)
+ )
+ ->add(
+ StateBadges::STATE_DOWN,
+ $stats->hosts_down_unhandled,
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 0
+ ),
+ 'List %u host that is currently in state DOWN',
+ 'List %u hosts which are currently in state DOWN',
+ array($stats->hosts_down_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_DOWN_HANDLED,
+ $stats->hosts_down_handled,
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 1
+ ),
+ 'List %u host that is currently in state DOWN (Acknowledged)',
+ 'List %u hosts which are currently in state DOWN (Acknowledged)',
+ array($stats->hosts_down_handled)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE,
+ $stats->hosts_unreachable_unhandled,
+ array(
+ 'host_state' => 2,
+ 'host_handled' => 0
+ ),
+ 'List %u host that is currently in state UNREACHABLE',
+ 'List %u hosts which are currently in state UNREACHABLE',
+ array($stats->hosts_unreachable_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE_HANDLED,
+ $stats->hosts_unreachable_handled,
+ array(
+ 'host_state' => 2,
+ 'host_handled' => 1
+ ),
+ 'List %u host that is currently in state UNREACHABLE (Acknowledged)',
+ 'List %u hosts which are currently in state UNREACHABLE (Acknowledged)',
+ array($stats->hosts_unreachable_handled)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $stats->hosts_pending,
+ array(
+ 'host_state' => 99
+ ),
+ 'List %u host that is currently in state PENDING',
+ 'List %u hosts which are currently in state PENDING',
+ array($stats->hosts_pending)
+ );
+echo $stateBadges->render();
+?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml b/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml
new file mode 100644
index 0000000..ec0fb85
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/components/selectioninfo.phtml
@@ -0,0 +1,15 @@
+<?php
+$helpMessage = $this->translate(
+ 'Press and hold the Ctrl key while clicking on rows to select multiple rows or press and hold the Shift key to'
+ .' select a range of rows',
+ 'Multi-selection help'
+);
+?>
+<div class="selection-info" title="<?= $this->escape($helpMessage) ?>">
+ <?= sprintf(
+ /// TRANSLATORS: Please leave %s as it is because the selection counter is wrapped in a span tag for updating
+ /// the counter via JavaScript
+ $this->translate('%s row(s) selected', 'Multi-selection count'),
+ '<span class="selection-info-count">0</span>'
+ ) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml
new file mode 100644
index 0000000..73a3b57
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/components/servicesummary.phtml
@@ -0,0 +1,118 @@
+<?php
+use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+use Icinga\Web\Url;
+
+// Don't fetch rows until they are actually needed, to improve dashlet performance
+if (! $stats instanceof stdClass) {
+ $stats = $stats->fetchRow();
+}
+?>
+<div class="services-summary dont-print">
+ <span class="services-link"><?= $this->qlink(
+ sprintf($this->translatePlural(
+ '%u Service', '%u Services', $stats->services_total),
+ $stats->services_total
+ ),
+ // @TODO(el): Fix that
+ Url::fromPath('monitoring/list/services')->setParams(isset($baseFilter) ? $baseFilter->getUrlParams() : array()),
+ null,
+ array('title' => sprintf(
+ $this->translatePlural('List %u service', 'List all %u services', $stats->services_total),
+ $stats->services_total
+ ))
+ ) ?>&#58;</span>
+<?php
+$stateBadges = new StateBadges();
+$stateBadges
+ ->setBaseFilter(isset($baseFilter) ? $baseFilter : null)
+ ->setUrl('monitoring/list/services')
+ ->add(
+ StateBadges::STATE_OK,
+ $stats->services_ok,
+ array(
+ 'service_state' => 0
+ ),
+ 'List %u service that is currently in state OK',
+ 'List %u services which are currently in state OK',
+ array($stats->services_ok)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL,
+ $stats->services_critical_unhandled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 0
+ ),
+ 'List %u service that is currently in state CRITICAL',
+ 'List %u services which are currently in state CRITICAL',
+ array($stats->services_critical_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL_HANDLED,
+ $stats->services_critical_handled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 1
+ ),
+ 'List %u handled service that is currently in state CRITICAL',
+ 'List %u handled services which are currently in state CRITICAL',
+ array($stats->services_critical_handled)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN,
+ $stats->services_unknown_unhandled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 0
+ ),
+ 'List %u service that is currently in state UNKNOWN',
+ 'List %u services which are currently in state UNKNOWN',
+ array($stats->services_unknown_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN_HANDLED,
+ $stats->services_unknown_handled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 1
+ ),
+ 'List %u handled service that is currently in state UNKNOWN',
+ 'List %u handled services which are currently in state UNKNOWN',
+ array($stats->services_unknown_handled)
+
+ )
+ ->add(
+ StateBadges::STATE_WARNING,
+ $stats->services_warning_unhandled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 0
+ ),
+ 'List %u service that is currently in state WARNING',
+ 'List %u services which are currently in state WARNING',
+ array($stats->services_warning_unhandled)
+ )
+ ->add(
+ StateBadges::STATE_WARNING_HANDLED,
+ $stats->services_warning_handled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 1
+ ),
+ 'List %u handled service that is currently in state WARNING',
+ 'List %u handled services which are currently in state WARNING',
+ array($stats->services_warning_handled)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $stats->services_pending,
+ array(
+ 'service_state' => 99
+ ),
+ 'List %u handled service that is currently in state PENDING',
+ 'List %u handled services which are currently in state PENDING',
+ array($stats->services_pending)
+ );
+echo $stateBadges->render();
+?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/contactgroups.phtml b/modules/monitoring/application/views/scripts/list/contactgroups.phtml
new file mode 100644
index 0000000..125aeea
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/contactgroups.phtml
@@ -0,0 +1,53 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $contactGroups->hasResult()): ?>
+ <p><?= $this->translate('No contact groups found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table table-row-selectable" data-base-target="_next">
+ <thead>
+ <tr>
+ <th></th>
+ <th><?= $this->translate('Contact Group') ?></th>
+ <th><?= $this->translate('Alias') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($contactGroups as $contactGroup): ?>
+ <tr>
+ <td class="count-col">
+ <span class="badge"><?= $contactGroup->contact_count ?></span>
+ </td>
+ <th>
+ <?= $this->qlink(
+ $contactGroup->contactgroup_name,
+ 'monitoring/list/contacts',
+ array('contactgroup_name' => $contactGroup->contactgroup_name),
+ array('title' => sprintf(
+ $this->translate('Show detailed information about %s'),
+ $contactGroup->contactgroup_name
+ ))
+ ) ?>
+ </th>
+ <td>
+ <?php if ($contactGroup->contactgroup_name !== $contactGroup->contactgroup_alias): ?>
+ <?= $contactGroup->contactgroup_alias ?>
+ <?php endif ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/contacts.phtml b/modules/monitoring/application/views/scripts/list/contacts.phtml
new file mode 100644
index 0000000..42ec778
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/contacts.phtml
@@ -0,0 +1,83 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $contacts->hasResult()): ?>
+ <p><?= $this->translate('No contacts found matching the filter') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table table-row-selectable" data-base-target="_next">
+ <thead>
+ <tr>
+ <th><?= $this->translate('Name') ?></th>
+ <th><?= $this->translate('Email') ?></th>
+ <th><?= $this->translate('Pager') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($contacts->peekAhead($this->compact) as $contact): ?>
+ <tr>
+ <th>
+ <?= $this->qlink(
+ $contact->contact_name,
+ 'monitoring/show/contact',
+ array('contact_name' => $contact->contact_name),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information about %s'),
+ $contact->contact_alias
+ )
+ )
+ ) ?>
+ </th>
+ <td>
+ <?= $this->translate('Email') ?>:
+ <a href="mailto:<?= $contact->contact_email ?>"
+ title="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias) ?>"
+ aria-label="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias) ?>">
+ <?= $this->escape($contact->contact_email) ?>
+ </a>
+ </td>
+ <td>
+ <?php if ($contact->contact_pager): ?>
+ <?= $this->escape($contact->contact_pager) ?>
+ <?php endif ?>
+ </td>
+
+ <?php if ($contact->contact_notify_service_timeperiod): ?>
+ <td>
+ <?= $this->escape($contact->contact_notify_service_timeperiod) ?>
+ </td>
+ <?php endif ?>
+
+ <?php if ($contact->contact_notify_host_timeperiod): ?>
+ <td>
+ <?= $this->escape($contact->contact_notify_host_timeperiod) ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($contacts->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/downtimes.phtml b/modules/monitoring/application/views/scripts/list/downtimes.phtml
new file mode 100644
index 0000000..46ce0bb
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/downtimes.phtml
@@ -0,0 +1,64 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $downtimes->hasResult()): ?>
+ <p><?= $this->translate('No downtimes found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table state-table table-row-selectable multiselect"
+ data-base-target="_next"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/downtimes/show') ?>"
+ data-icinga-multiselect-controllers="<?= $this->href("monitoring/downtimes") ?>"
+ data-icinga-multiselect-data="downtime_id">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <th><?= $this->translate('Downtime') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($downtimes->peekAhead($this->compact) as $downtime):
+ if (isset($downtime->service_description)) {
+ $this->isService = true;
+ $this->stateName = Service::getStateText($downtime->service_state);
+ } else {
+ $this->isService = false;
+ $this->stateName = Host::getStateText($downtime->host_state);
+ }
+ // Set downtime for partials
+ $this->downtime = $downtime;
+ ?>
+ <tr href="<?= $this->href('monitoring/downtime/show', array('downtime_id' => $downtime->id)) ?>">
+ <?= $this->render('partials/downtime/downtime-header.phtml') ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($downtimes->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/eventgrid.phtml b/modules/monitoring/application/views/scripts/list/eventgrid.phtml
new file mode 100644
index 0000000..8809c53
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/eventgrid.phtml
@@ -0,0 +1,123 @@
+<?php
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Widget\Chart\HistoryColorGrid;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->form ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php
+
+$settings = array(
+ 'cnt_up' => array(
+ 'tooltip' => $this->translate('%d hosts ok on %s'),
+ 'color' => '#49DF96',
+ 'opacity' => '0.55'
+ ),
+ 'cnt_unreachable_hard' => array(
+ 'tooltip' => $this->translate('%d hosts unreachable on %s'),
+ 'color' => '#77AAFF',
+ 'opacity' => '0.55'
+ ),
+ 'cnt_critical_hard' => array(
+ 'tooltip' => $this->translate('%d services critical on %s'),
+ 'color' => '#ff5566',
+ 'opacity' => '0.9'
+ ),
+
+ 'cnt_warning_hard' => array(
+ 'tooltip' => $this->translate('%d services warning on %s'),
+ 'color' => '#ffaa44',
+ 'opacity' => '1.0'
+ ),
+
+ 'cnt_down_hard' => array(
+ 'tooltip' => $this->translate('%d hosts down on %s'),
+ 'color' => '#ff5566',
+ 'opacity' => '0.9'
+ ),
+ 'cnt_unknown_hard' => array(
+ 'tooltip' => $this->translate('%d services unknown on %s'),
+ 'color' => '#cc77ff',
+ 'opacity' => '0.7'
+ ),
+ 'cnt_ok' => array(
+ 'tooltip' => $this->translate('%d services ok on %s'),
+ 'color' => '#49DF96',
+ 'opacity' => '0.55'
+ )
+);
+
+$data = array();
+foreach ($summary as $entry) {
+ $day = $entry->day;
+ $value = $entry->$column;
+ $caption = sprintf(
+ $settings[$column]['tooltip'],
+ $value,
+ $this->formatDate(strtotime($day ?? ''))
+ );
+ $linkFilter = Filter::matchAll(
+ Filter::expression('timestamp', '<', strtotime($day . ' 23:59:59')),
+ Filter::expression('timestamp', '>', strtotime($day . ' 00:00:00')),
+ $form->getFilter(),
+ $filter
+ );
+ $data[$day] = array(
+ 'value' => $value,
+ 'caption' => $caption,
+ 'url' => $this->href('monitoring/list/eventhistory?' . $linkFilter->toQueryString())
+ );
+}
+
+if (! $summary->hasResult()) {
+ echo $this->translate('No state changes in the selected time period.') . '</div>';
+ return;
+}
+
+$from = intval($form->getValue('from', strtotime('3 months ago')));
+$to = intval($form->getValue('to', time()));
+
+// don't display more than ten years, or else this will get really slow
+if ($to - $from > 315360000) {
+ $from = $to - 315360000;
+}
+
+$f = new DateTime();
+$f->setTimestamp($from);
+$t = new DateTime();
+$t->setTimestamp($to);
+$diff = $t->diff($f);
+$step = 124;
+
+for ($i = 0; $i < $diff->days; $i += $step) {
+ $end = clone $f;
+ if ($diff->days - $i > $step) {
+ // full range, move last day to next chunk
+ $end->add(new DateInterval('P' . ($step - 1) . 'D'));
+ } else {
+ // include last day
+ $end->add(new DateInterval('P' . ($diff->days - $i) . 'D'));
+ }
+ $grid = new HistoryColorGrid(null, $f->getTimestamp(), $end->getTimestamp());
+ $grid->setColor($settings[$column]['color']);
+ $grid->opacity = $settings[$column]['opacity'];
+ $grid->orientation = $orientation;
+ $grid->setData($data);
+ $grids[] = $grid;
+
+ $f->add(new DateInterval('P' . $step . 'D'));
+}
+?>
+<div style="width: 33.5em;">
+<?php foreach (array_reverse($grids) as $key => $grid): ?>
+ <div style=" <?= $this->orientation === 'horizontal' ? '' : 'display: inline-block; vertical-align: top; top; margin: 0.5em;' ?>">
+ <?= $grid; ?>
+ <?= $this->orientation === 'horizontal' ? '<br />' : '' ?>
+ </div>
+<?php endforeach ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/eventhistory.phtml b/modules/monitoring/application/views/scripts/list/eventhistory.phtml
new file mode 100644
index 0000000..0573e8a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/eventhistory.phtml
@@ -0,0 +1,22 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<?= $this->partial(
+ 'partials/event-history.phtml',
+ array(
+ 'compact' => $this->compact,
+ 'history' => $history,
+ 'isOverview' => true,
+ 'translationDomain' => $this->translationDomain
+ )
+) ?>
+
diff --git a/modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml b/modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml
new file mode 100644
index 0000000..34498d0
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/hostgroup-grid.phtml
@@ -0,0 +1,173 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/hostgroups')->addFilter($this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-inactive']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-active']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php /** @var \Icinga\Module\Monitoring\DataView\Hostgroup $hostGroups */
+if (! $hostGroups->hasResult()): ?>
+ <p><?= $this->translate('No host groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+<div class="group-grid">
+<?php foreach ($hostGroups as $hostGroup): ?>
+ <div class="group-grid-cell">
+ <?php if ($hostGroup->hosts_down_unhandled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_down_unhandled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 0,
+ 'host_state' => 1
+ ],
+ [
+ 'class' => 'state-down',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state DOWN in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN in the host group "%s"',
+ $hostGroup->hosts_down_unhandled
+ ),
+ $hostGroup->hosts_down_unhandled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_unreachable_unhandled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_unreachable_unhandled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 0,
+ 'host_state' => 2
+ ],
+ [
+ 'class' => 'state-unreachable',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UNREACHABLE in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"',
+ $hostGroup->hosts_unreachable_unhandled
+ ),
+ $hostGroup->hosts_unreachable_unhandled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_down_handled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_down_handled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 1,
+ 'host_state' => 1
+ ],
+ [
+ 'class' => 'state-down handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"',
+ $hostGroup->hosts_down_handled
+ ),
+ $hostGroup->hosts_down_handled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_unreachable_handled > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_unreachable_handled,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_handled' => 0,
+ 'host_state' => 2
+ ],
+ [
+ 'class' => 'state-unreachable handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ $hostGroup->hosts_unreachable_handled
+ ),
+ $hostGroup->hosts_unreachable_handled,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_pending > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_pending,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_state' => 99
+ ],
+ [
+ 'class' => 'state-pending',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state PENDING in the host group "%s"',
+ 'List %u hosts which are currently in state PENDING in the host group "%s"',
+ $hostGroup->hosts_pending
+ ),
+ $hostGroup->hosts_pending,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($hostGroup->hosts_up > 0): ?>
+ <?= $this->qlink(
+ $hostGroup->hosts_up,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'host_state' => 0
+ ],
+ [
+ 'class' => 'state-up',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UP in the host group "%s"',
+ 'List %u hosts which are currently in state UP in the host group "%s"',
+ $hostGroup->hosts_up
+ ),
+ $hostGroup->hosts_up,
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ <?php else: ?>
+ <div class="state-none">
+ 0
+ </div>
+ <?php endif ?>
+ <?= $this->qlink(
+ $hostGroup->hostgroup_alias,
+ $this->url('monitoring/list/hosts')->addFilter($this->filterEditor->getFilter()),
+ ['hostgroup_name' => $hostGroup->hostgroup_name],
+ [
+ 'title' => sprintf(
+ $this->translate('List all hosts in the group "%s"'),
+ $hostGroup->hostgroup_alias
+ )
+ ]
+ ) ?>
+ </div>
+<?php endforeach ?>
+</div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/hostgroups.phtml b/modules/monitoring/application/views/scripts/list/hostgroups.phtml
new file mode 100644
index 0000000..a0592c8
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/hostgroups.phtml
@@ -0,0 +1,296 @@
+<?php
+
+use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/hostgroup-grid')->addFilter(clone $this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-active']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-inactive']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+
+<div class="content">
+<?php /** @var \Icinga\Module\Monitoring\DataView\Hostgroup $hostGroups */ if (! $hostGroups->hasResult()): ?>
+ <p><?= $this->translate('No host groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="common-table table-row-selectable" data-base-target="_next">
+ <thead>
+ <tr>
+ <th></th>
+ <th><?= $this->translate('Host Group') ?></th>
+ <th><?= $this->translate('Host States') ?></th>
+ <th></th>
+ <th><?= $this->translate('Service States') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($hostGroups->peekAhead($this->compact) as $hostGroup): ?>
+ <tr>
+ <td class="count-col">
+ <span class="badge"><?= $hostGroup->hosts_total ?></span>
+ </td>
+ <th>
+ <?= $this->qlink(
+ $hostGroup->hostgroup_alias,
+ $this
+ ->url('monitoring/list/hosts')
+ ->setParams(['hostgroup_name' => $hostGroup->hostgroup_name])
+ ->addFilter($this->filterEditor->getFilter()),
+ ['sort' => 'host_severity'],
+ ['title' => sprintf(
+ $this->translate('List all hosts in the group "%s"'),
+ $hostGroup->hostgroup_alias
+ )]
+ ) ?>
+ </th>
+ <td>
+ <?php
+ $stateBadges = new StateBadges();
+ $stateBadges
+ ->setUrl('monitoring/list/hosts')
+ ->setBaseFilter($this->filterEditor->getFilter())
+ ->add(
+ StateBadges::STATE_UP,
+ $hostGroup->hosts_up,
+ array(
+ 'host_state' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state UP in the host group "%s"',
+ 'List %u hosts which are currently in state UP in the host group "%s"',
+ array($hostGroup->hosts_up, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_DOWN,
+ $hostGroup->hosts_down_unhandled,
+ array(
+ 'host_state' => 1,
+ 'host_acknowledged' => 0,
+ 'host_in_downtime' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state DOWN in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN in the host group "%s"',
+ array($hostGroup->hosts_down_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_DOWN_HANDLED,
+ $hostGroup->hosts_down_handled,
+ array(
+ 'host_state' => 1,
+ 'host_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state DOWN (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state DOWN (Acknowledged) in the host group "%s"',
+ array($hostGroup->hosts_down_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE,
+ $hostGroup->hosts_unreachable_unhandled,
+ array(
+ 'host_state' => 2,
+ 'host_acknowledged' => 0,
+ 'host_in_downtime' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state UNREACHABLE in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE in the host group "%s"',
+ array($hostGroup->hosts_unreachable_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNREACHABLE_HANDLED,
+ $hostGroup->hosts_unreachable_handled,
+ array(
+ 'host_state' => 2,
+ 'host_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ 'List %u hosts which are currently in state UNREACHABLE (Acknowledged) in the host group "%s"',
+ array($hostGroup->hosts_unreachable_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $hostGroup->hosts_pending,
+ array(
+ 'host_state' => 99,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'host_severity'
+ ),
+ 'List %u host that is currently in state PENDING in the host group "%s"',
+ 'List %u hosts which are currently in state PENDING in the host group "%s"',
+ array($hostGroup->hosts_pending, $hostGroup->hostgroup_alias)
+ );
+ echo $stateBadges->render();
+ ?>
+ </td>
+ <td class="count-col">
+ <?= $this->qlink(
+ $hostGroup->services_total,
+ $this
+ ->url('monitoring/list/services')
+ ->setParams(['hostgroup_name' => $hostGroup->hostgroup_name])
+ ->addFilter($this->filterEditor->getFilter()),
+ ['sort' => 'service_severity'],
+ [
+ 'title' => sprintf(
+ $this->translate('List all services of all hosts in host group "%s"'),
+ $hostGroup->hostgroup_alias
+ ),
+ 'class' => 'badge'
+ ]
+ ) ?>
+ </td>
+ <td>
+ <?php
+ $stateBadges = new StateBadges();
+ $stateBadges
+ ->setUrl('monitoring/list/services')
+ ->setBaseFilter($this->filterEditor->getFilter())
+ ->add(
+ StateBadges::STATE_OK,
+ $hostGroup->services_ok,
+ array(
+ 'service_state' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state OK on hosts in the host group "%s"',
+ 'List %u services which are currently in state OK on hosts in the host group "%s"',
+ array($hostGroup->services_ok, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL,
+ $hostGroup->services_critical_unhandled,
+ array(
+ 'service_state' => 2,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state CRITICAL on hosts in the host group "%s"',
+ 'List %u services which are currently in state CRITICAL on hosts in the host group "%s"',
+ array($hostGroup->services_critical_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL_HANDLED,
+ $hostGroup->services_critical_handled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"',
+ 'List %u services which are currently in state CRITICAL (Acknowledged) on hosts in the host group "%s"',
+ array($hostGroup->services_critical_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN,
+ $hostGroup->services_unknown_unhandled,
+ array(
+ 'service_state' => 3,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state UNKNOWN on hosts in the host group "%s"',
+ 'List %u services which are currently in state UNKNOWN on hosts in the host group "%s"',
+ array($hostGroup->services_unknown_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN_HANDLED,
+ $hostGroup->services_unknown_handled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"',
+ 'List %u services which are currently in state UNKNOWN (Acknowledged) on hosts in the host group "%s"',
+ array($hostGroup->services_unknown_handled, $hostGroup->hostgroup_alias)
+
+ )
+ ->add(
+ StateBadges::STATE_WARNING,
+ $hostGroup->services_warning_unhandled,
+ array(
+ 'service_state' => 1,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state WARNING on hosts in the host group "%s"',
+ 'List %u services which are currently in state WARNING on hosts in the host group "%s"',
+ array($hostGroup->services_warning_unhandled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_WARNING_HANDLED,
+ $hostGroup->services_warning_handled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 1,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state WARNING (Acknowledged) on hosts in the host group "%s"',
+ 'List %u services which are currently in state WARNING (Acknowledged) on hosts in the host group "%s"',
+ array($hostGroup->services_warning_handled, $hostGroup->hostgroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $hostGroup->services_pending,
+ array(
+ 'service_state' => 99,
+ 'hostgroup_name' => $hostGroup->hostgroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %u service that is currently in state PENDING on hosts in the host group "%s"',
+ 'List %u services which are currently in state PENDING on hosts in the host group "%s"',
+ array($hostGroup->services_pending, $hostGroup->hostgroup_alias)
+ );
+ echo $stateBadges->render();
+ ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($hostGroups->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml
new file mode 100644
index 0000000..6d7674e
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/hosts.phtml
@@ -0,0 +1,106 @@
+<?php
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Object\Host;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $hosts->hasResult()): ?>
+ <p><?= $this->translate('No hosts found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next"
+ class="table-row-selectable state-table multiselect"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/hosts/show') ?>"
+ data-icinga-multiselect-controllers="<?= $this->href("monitoring/hosts") ?>"
+ data-icinga-multiselect-data="host">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <th><?= $this->translate('Host') ?></th>
+ <?php foreach($this->addColumns as $col): ?>
+ <th><?= $this->escape($col) ?></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach($hosts->peekAhead($this->compact) as $host):
+ $hostStateName = Host::getStateText($host->host_state);
+ $hostLink = $this->href('monitoring/host/show', array('host' => $host->host_name));
+ $hostCheckOverdue = $host->host_next_update < time();?>
+ <tr<?= $hostCheckOverdue ? ' class="state-outdated"' : '' ?>>
+ <td class="state-col state-<?= $hostStateName ?><?= $host->host_handled ? ' handled' : '' ?>">
+ <div class="state-label">
+ <?php if ($hostCheckOverdue): ?>
+ <?= $this->icon('clock', sprintf($this->translate('Overdue %s'), DateFormatter::timeSince($host->host_next_update))) ?>
+ <?php endif ?>
+ <?= Host::getStateText($host->host_state, true) ?>
+ </div>
+ <?php if ((int) $host->host_state !== 99): ?>
+ <div class="state-meta">
+ <?= $this->timeSince($host->host_last_state_change, $this->compact) ?>
+ <?php if ((int) $host->host_state > 0 && (int) $host->host_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $host->host_attempt ?></div>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+ </td>
+ <td>
+ <div class="state-header">
+ <?= $this->iconImage()->host($host) ?>
+ <?= $this->qlink(
+ $host->host_display_name,
+ $hostLink,
+ null,
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $host->host_display_name
+ ),
+ 'class' => 'rowaction'
+ )
+ ) ?>
+ <span class="state-icons"><?= $this->hostFlags($host) ?></span>
+ </div>
+ <p class="overview-plugin-output"><?= $this->pluginOutput($this->ellipsis($host->host_output, 10000), true, $host->host_check_command) ?></p>
+ </td>
+ <?php foreach($this->addColumns as $col): ?>
+ <?php if ($host->$col && preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $col, $m)): ?>
+ <td><?= $this->escape(\Icinga\Module\Monitoring\Object\MonitoredObject::protectCustomVars([$m[2] => $host->$col])[$m[2]]) ?></td>
+ <?php else: ?>
+ <td><?= $this->escape($host->$col) ?></td>
+ <?php endif ?>
+ <?php endforeach ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($hosts->hasMore()): ?>
+ <div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </div>
+<?php endif ?>
+</div>
+<?php if (! $this->compact): ?>
+<div class="monitoring-statusbar dont-print">
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml
new file mode 100644
index 0000000..51ef432
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/notifications.phtml
@@ -0,0 +1,124 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $notifications->hasResult()): ?>
+ <p><?= $this->translate('No notifications found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next" class="table-row-selectable state-table">
+ <tbody>
+ <?php foreach ($notifications->peekAhead($this->compact) as $notification):
+ if (isset($notification->service_description)) {
+ $isService = true;
+ $stateLabel = Service::getStateText($notification->notification_state, true);
+ $stateName = Service::getStateText($notification->notification_state);
+ } else {
+ $isService = false;
+ $stateLabel = Host::getStateText($notification->notification_state, true);
+ $stateName = Host::getStateText($notification->notification_state);
+ }
+ ?>
+ <tr href="<?= $this->href('monitoring/event/show', ['id' => $notification->id, 'type' => 'notify']) ?>">
+ <td class="state-col state-<?= $stateName ?>">
+ <div class="state-label"><?= $stateLabel ?></div>
+ <div class="state-meta">
+ <?= $this->formatDateTime($notification->notification_timestamp) ?>
+ </div>
+ </td>
+ <td>
+ <div class="state-header">
+ <?php if ($isService) {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $notification->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $notification->host_name,
+ 'service' => $notification->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $notification->service_display_name,
+ $notification->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $notification->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $notification->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $notification->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+ } else {
+ echo $this->qlink(
+ $notification->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $notification->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $notification->host_display_name
+ )
+ ]
+ );
+ } ?>
+ <?php if (! $this->contact): ?>
+ <div class="notification-recipient">
+ <?php if ($notification->notification_contact_name): ?>
+ <?= sprintf(
+ $this->translate('Sent to %s'),
+ $this->qlink(
+ $notification->notification_contact_name,
+ 'monitoring/show/contact',
+ array('contact_name' => $notification->notification_contact_name)
+ )
+ ) ?>
+ <?php else: ?>
+ <?= $this->translate('Not sent out to any contact') ?>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+ </div>
+ <p class="overview-plugin-output"><?= $this->pluginOutput($this->ellipsis($notification->notification_output, 10000), true) ?></p>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($notifications->hasMore()): ?>
+ <div class="action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url(isset($notificationsUrl) ? $notificationsUrl : null)->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ); ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml b/modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml
new file mode 100644
index 0000000..d7b4c78
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegrid-flipped.phtml
@@ -0,0 +1,144 @@
+<?php
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->problemToggle ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+ <?php if (empty($pivotData)): ?>
+ <p><?= $this->translate('No services found matching the filter.') ?></p>
+</div>
+<?php return; endif;
+$serviceFilter = Filter::matchAny();
+foreach ($pivotData as $serviceDescription => $_) {
+ $serviceFilter->orFilter(Filter::where('service_description', $serviceDescription));
+}
+?>
+<table class="service-grid-table">
+ <thead>
+ <tr>
+ <th><?= $this->partial(
+ 'joystickPagination.phtml',
+ 'default',
+ array(
+ 'flippable' => true,
+ 'xAxisPaginator' => $horizontalPaginator,
+ 'yAxisPaginator' => $verticalPaginator
+ )
+ ) ?></th>
+ <?php foreach ($pivotHeader['cols'] as $hostName => $hostAlias): ?>
+ <th class="rotate-45"><div><span><?= $this->qlink(
+ $this->ellipsis($hostAlias, 24),
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($serviceFilter, Filter::where('host_name', $hostName))
+ ),
+ null,
+ array('title' => sprintf($this->translate('List all reported services on host %s'), $hostAlias)),
+ false
+ ) ?></span></div></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+
+ <?php $i = 0 ?>
+ <?php foreach ($pivotHeader['rows'] as $serviceDescription => $serviceDisplayName): ?>
+ <tr>
+ <th><?php
+ $hostFilter = Filter::matchAny();
+ foreach ($pivotData[$serviceDescription] as $hostName => $_) {
+ $hostFilter->orFilter(Filter::where('host_name', $hostName));
+ }
+ echo $this->qlink(
+ $serviceDisplayName,
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($hostFilter, Filter::where('service_description', $serviceDescription))
+ ),
+ null,
+ array('title' => sprintf(
+ $this->translate('List all services with the name "%s" on all reported hosts'),
+ $serviceDisplayName
+ ))
+ );
+ ?></th>
+ <?php foreach (array_keys($pivotHeader['cols']) as $hostName): ?>
+ <td><?php
+ $service = $pivotData[$serviceDescription][$hostName];
+ if ($service === null): ?>
+ <span aria-hidden="true">&middot;</span>
+ <?php continue; endif ?>
+ <?php $ariaDescribedById = $this->protectId($service->host_name . '_' . $service->service_description . '_desc') ?>
+ <span class="sr-only" id="<?= $ariaDescribedById ?>">
+ <?= $this->escape($service->service_output) ?>
+ </span>
+ <?= $this->qlink(
+ '',
+ 'monitoring/service/show',
+ array(
+ 'host' => $hostName,
+ 'service' => $serviceDescription
+ ),
+ array(
+ 'aria-describedby' => $ariaDescribedById,
+ 'aria-label' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'service-grid-link state-' . Service::getStateText($service->service_state) . ($service->service_handled ? ' handled' : ''),
+ 'title' => $service->service_output
+ )
+ ) ?>
+ </td>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->horizontalPaginator->getPages()->pageCount > 1): ?>
+ <td>
+ <?php $expandLink = $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => ($this->horizontalPaginator->getItemCountPerPage() + 20)
+ . ','
+ . $this->verticalPaginator->getItemCountPerPage()
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ <?= ++$i === (int) ceil(count($pivotHeader['rows']) / 2) ? $expandLink : '' ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->verticalPaginator->getPages()->pageCount > 1): ?>
+ <tr>
+ <td colspan="<?= count($pivotHeader['cols']) + 1?>" class="service-grid-table-more">
+ <?php echo $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => $this->horizontalPaginator->getItemCountPerPage()
+ . ','
+ . ($this->verticalPaginator->getItemCountPerPage() + 20)
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+</table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegrid.phtml b/modules/monitoring/application/views/scripts/list/servicegrid.phtml
new file mode 100644
index 0000000..d0ed4bc
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegrid.phtml
@@ -0,0 +1,144 @@
+<?php
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->problemToggle ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php if (empty($pivotData)): ?>
+ <p><?= $this->translate('No services found matching the filter.') ?></p>
+</div>
+<?php return; endif;
+$hostFilter = Filter::matchAny();
+foreach ($pivotData as $hostName => $_) {
+ $hostFilter->orFilter(Filter::where('host_name', $hostName));
+}
+?>
+ <table class="service-grid-table">
+ <thead>
+ <tr>
+ <th><?= $this->partial(
+ 'joystickPagination.phtml',
+ 'default',
+ array(
+ 'flippable' => true,
+ 'xAxisPaginator' => $horizontalPaginator,
+ 'yAxisPaginator' => $verticalPaginator
+ )
+ ) ?></th>
+ <?php foreach ($pivotHeader['cols'] as $serviceDescription => $serviceDisplayName): ?>
+ <th class="rotate-45"><div><span><?= $this->qlink(
+ $this->ellipsis($serviceDisplayName, 24),
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($hostFilter, Filter::where('service_description', $serviceDescription))
+ ),
+ null,
+ array('title' => sprintf(
+ $this->translate('List all services with the name "%s" on all reported hosts'),
+ $serviceDisplayName
+ )),
+ false
+ ) ?></span></div></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+
+ <?php $i = 0 ?>
+ <?php foreach ($pivotHeader['rows'] as $hostName => $hostDisplayName): ?>
+ <tr>
+ <th><?php
+ $serviceFilter = Filter::matchAny();
+ foreach ($pivotData[$hostName] as $serviceName => $_) {
+ $serviceFilter->orFilter(Filter::where('service_description', $serviceName));
+ }
+ echo $this->qlink(
+ $hostDisplayName,
+ Url::fromPath('monitoring/list/services')->addFilter(
+ Filter::matchAll($serviceFilter, Filter::where('host_name', $hostName))
+ ),
+ null,
+ array('title' => sprintf($this->translate('List all reported services on host %s'), $hostDisplayName))
+ );
+ ?></th>
+ <?php foreach (array_keys($pivotHeader['cols']) as $serviceDescription): ?>
+ <td>
+ <?php
+ $service = $pivotData[$hostName][$serviceDescription];
+ if ($service === null): ?>
+ <span aria-hidden="true">&middot;</span>
+ <?php continue; endif ?>
+ <?php $ariaDescribedById = $this->protectId($service->host_name . '_' . $service->service_description . '_desc') ?>
+ <span class="sr-only" id="<?= $ariaDescribedById ?>">
+ <?= $this->escape($service->service_output) ?>
+ </span>
+ <?= $this->qlink(
+ '',
+ 'monitoring/service/show',
+ array(
+ 'host' => $hostName,
+ 'service' => $serviceDescription
+ ),
+ array(
+ 'aria-describedby' => $ariaDescribedById,
+ 'aria-label' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'service-grid-link state-' . Service::getStateText($service->service_state) . ($service->service_handled ? ' handled' : ''),
+ 'title' => $service->service_output
+ )
+ ) ?>
+ </td>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->horizontalPaginator->getPages()->pageCount > 1): ?>
+ <td>
+ <?php $expandLink = $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => (
+ $this->horizontalPaginator->getItemCountPerPage() + 20) . ','
+ . $this->verticalPaginator->getItemCountPerPage()
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ <?= ++$i === (int) (count($pivotHeader['rows']) / 2) ? $expandLink : '' ?>
+ </td>
+ <?php endif ?>
+ </tr>
+ <?php endforeach ?>
+ <?php if (! $this->compact && $this->verticalPaginator->getPages()->pageCount > 1): ?>
+ <tr>
+ <td colspan="<?= count($pivotHeader['cols']) + 1?>" class="service-grid-table-more">
+ <?php echo $this->qlink(
+ $this->translate('Load more'),
+ Url::fromRequest(),
+ array(
+ 'limit' => $this->horizontalPaginator->getItemCountPerPage() . ',' .
+ ($this->verticalPaginator->getItemCountPerPage() + 20)
+ ),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml b/modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml
new file mode 100644
index 0000000..5ea6d17
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegroup-grid.phtml
@@ -0,0 +1,217 @@
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <div class="sort-controls-container">
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/servicegroups')->addFilter($this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-inactive']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-active']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content" data-base-target="_next">
+<?php /** @var \Icinga\Module\Monitoring\DataView\Servicegroup $serviceGroups */
+if (! $serviceGroups->hasResult()): ?>
+ <p><?= $this->translate('No service groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+<div class="group-grid">
+<?php foreach ($serviceGroups as $serviceGroup): ?>
+ <div class="group-grid-cell">
+ <?php if ($serviceGroup->services_critical_unhandled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_critical_unhandled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 0,
+ 'service_state' => 2
+ ],
+ [
+ 'class' => 'state-critical',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state CRITICAL in service group "%s"',
+ 'List %s services which are currently in state CRITICAL in service group "%s"',
+ $serviceGroup->services_critical_unhandled
+ ),
+ $serviceGroup->services_critical_unhandled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_warning_unhandled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_warning_unhandled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 0,
+ 'service_state' => 1
+ ],
+ [
+ 'class' => 'state-warning',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state WARNING in service group "%s"',
+ 'List %s services which are currently in state WARNING in service group "%s"',
+ $serviceGroup->services_warning_unhandled
+ ),
+ $serviceGroup->services_warning_unhandled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_unknown_unhandled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_unknown_unhandled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 0,
+ 'service_state' => 3
+ ],
+ [
+ 'class' => 'state-unknown',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state UNKNOWN in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN in service group "%s"',
+ $serviceGroup->services_unknown_unhandled
+ ),
+ $serviceGroup->services_unknown_unhandled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_critical_handled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_critical_handled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 1,
+ 'service_state' => 2
+ ],
+ [
+ 'class' => 'state-critical handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state CRITICAL (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state CRITICAL (Acknowledged) in service group "%s"',
+ $serviceGroup->services_critical_handled
+ ),
+ $serviceGroup->services_critical_handled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_warning_handled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_warning_handled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 1,
+ 'service_state' => 1
+ ],
+ [
+ 'class' => 'state-warning handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state WARNING (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state WARNING (Acknowledged) in service group "%s"',
+ $serviceGroup->services_warning_handled
+ ),
+ $serviceGroup->services_warning_handled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_unknown_handled > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_unknown_handled,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_handled' => 1,
+ 'service_state' => 3
+ ],
+ [
+ 'class' => 'state-unknown handled',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ $serviceGroup->services_unknown_handled
+ ),
+ $serviceGroup->services_unknown_handled,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_pending > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_pending,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_state' => 99
+ ],
+ [
+ 'class' => 'state-pending',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currenlty in state PENDING in service group "%s"',
+ 'List %s services which are currently in state PENDING in service group "%s"',
+ $serviceGroup->services_pending
+ ),
+ $serviceGroup->services_pending,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php elseif ($serviceGroup->services_ok > 0): ?>
+ <?= $this->qlink(
+ $serviceGroup->services_ok,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ [
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'service_state' => 0
+ ],
+ [
+ 'class' => 'state-ok',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %s service that is currently in state OK in service group "%s"',
+ 'List %s services which are currently in state OK in service group "%s"',
+ $serviceGroup->services_ok
+ ),
+ $serviceGroup->services_ok,
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ <?php else: ?>
+ <div class="state-none">
+ 0
+ </div>
+ <?php endif ?>
+ <?= $this->qlink(
+ $serviceGroup->servicegroup_alias,
+ $this->url('monitoring/list/servicegrid')->addFilter($this->filterEditor->getFilter()),
+ ['servicegroup_name' => $serviceGroup->servicegroup_name],
+ [
+ 'title' => sprintf(
+ $this->translate('List all services in the group "%s"'),
+ $serviceGroup->servicegroup_alias
+ )
+ ]
+ ) ?>
+ </div>
+<?php endforeach ?>
+</div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/servicegroups.phtml b/modules/monitoring/application/views/scripts/list/servicegroups.phtml
new file mode 100644
index 0000000..c915b30
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/servicegroups.phtml
@@ -0,0 +1,184 @@
+<?php use Icinga\Module\Monitoring\Web\Widget\StateBadges;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ <a href="<?= $this->href('monitoring/list/servicegroup-grid')->addFilter(clone $this->filterEditor->getFilter()) ?>" class="grid-toggle-link"
+ title="<?= $this->translate('Toogle grid view mode') ?>">
+ <?= $this->icon('th-list', null, ['class' => '-active']) ?>
+ <?= $this->icon('th-thumb-empty', null, ['class' => '-inactive']) ?>
+ </a>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $serviceGroups->hasResult()): ?>
+ <p><?= $this->translate('No service groups found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table class="table-row-selectable common-table" data-base-target="_next">
+ <thead>
+ <tr>
+ <th></th>
+ <th><?= $this->translate('Service Group') ?></th>
+ <th><?= $this->translate('Service States') ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($serviceGroups->peekAhead($this->compact) as $serviceGroup): ?>
+ <tr>
+ <td class="count-col">
+ <span class="badge"><?= $serviceGroup->services_total ?></span>
+ </td>
+ <th>
+ <?= $this->qlink(
+ $serviceGroup->servicegroup_alias,
+ $this
+ ->url('monitoring/list/services')
+ ->setParams(['servicegroup_name' => $serviceGroup->servicegroup_name])
+ ->addFilter($this->filterEditor->getFilter()),
+ ['sort' => 'service_severity'],
+ ['title' => sprintf($this->translate('List all services in the group "%s"'), $serviceGroup->servicegroup_alias)]
+ ) ?>
+ </th>
+ <td>
+ <?php
+ $stateBadges = new StateBadges();
+ $stateBadges
+ ->setUrl('monitoring/list/services')
+ ->setBaseFilter($this->filterEditor->getFilter())
+ ->add(
+ StateBadges::STATE_OK,
+ $serviceGroup->services_ok,
+ array(
+ 'service_state' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state OK in service group "%s"',
+ 'List %s services which are currently in state OK in service group "%s"',
+ array($serviceGroup->services_ok, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL,
+ $serviceGroup->services_critical_unhandled,
+ array(
+ 'service_state' => 2,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state CRITICAL in service group "%s"',
+ 'List %s services which are currently in state CRITICAL in service group "%s"',
+ array($serviceGroup->services_critical_unhandled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_CRITICAL_HANDLED,
+ $serviceGroup->services_critical_handled,
+ array(
+ 'service_state' => 2,
+ 'service_handled' => 1,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state CRITICAL (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state CRITICAL (Acknowledged) in service group "%s"',
+ array($serviceGroup->services_critical_handled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN,
+ $serviceGroup->services_unknown_unhandled,
+ array(
+ 'service_state' => 3,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state UNKNOWN in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN in service group "%s"',
+ array($serviceGroup->services_unknown_unhandled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_UNKNOWN_HANDLED,
+ $serviceGroup->services_unknown_handled,
+ array(
+ 'service_state' => 3,
+ 'service_handled' => 1,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state UNKNOWN (Acknowledged) in service group "%s"',
+ array($serviceGroup->services_unknown_handled, $serviceGroup->servicegroup_alias)
+
+ )
+ ->add(
+ StateBadges::STATE_WARNING,
+ $serviceGroup->services_warning_unhandled,
+ array(
+ 'service_state' => 1,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0,
+ 'host_problem' => 0,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state WARNING in service group "%s"',
+ 'List %s services which are currently in state WARNING in service group "%s"',
+ array($serviceGroup->services_warning_unhandled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_WARNING_HANDLED,
+ $serviceGroup->services_warning_handled,
+ array(
+ 'service_state' => 1,
+ 'service_handled' => 1,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currently in state WARNING (Acknowledged) in service group "%s"',
+ 'List %s services which are currently in state WARNING (Acknowledged) in service group "%s"',
+ array($serviceGroup->services_warning_handled, $serviceGroup->servicegroup_alias)
+ )
+ ->add(
+ StateBadges::STATE_PENDING,
+ $serviceGroup->services_pending,
+ array(
+ 'service_state' => 99,
+ 'servicegroup_name' => $serviceGroup->servicegroup_name,
+ 'sort' => 'service_severity'
+ ),
+ 'List %s service that is currenlty in state PENDING in service group "%s"',
+ 'List %s services which are currently in state PENDING in service group "%s"',
+ array($serviceGroup->services_pending, $serviceGroup->servicegroup_alias)
+ );
+ echo $stateBadges->render();
+ ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($serviceGroups->hasMore()): ?>
+<div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+</div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml
new file mode 100644
index 0000000..b2088e9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/list/services.phtml
@@ -0,0 +1,161 @@
+<?php
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->paginator ?>
+ <div class="sort-controls-container">
+ <?= $this->limiter ?>
+ <?= $this->sortBox ?>
+ </div>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content">
+<?php if (! $services->hasResult()): ?>
+ <p><?= $this->translate('No services found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <table data-base-target="_next"
+ class="table-row-selectable state-table multiselect<?php if ($this->compact): ?> compact<?php endif ?>"
+ data-icinga-multiselect-url="<?= $this->href('monitoring/services/show') ?>"
+ data-icinga-multiselect-controllers="<?= $this->href('monitoring/services') ?>"
+ data-icinga-multiselect-data="service,host">
+ <thead class="print-only">
+ <tr>
+ <th><?= $this->translate('State') ?></th>
+ <th><?= $this->translate('Service') ?></th>
+ <?php foreach($this->addColumns as $col): ?>
+ <th><?= $this->escape($col) ?></th>
+ <?php endforeach ?>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($services->peekAhead($this->compact) as $service):
+ $serviceLink = $this->href(
+ 'monitoring/service/show',
+ array(
+ 'host' => $service->host_name,
+ 'service' => $service->service_description
+ )
+ );
+ $hostLink = $this->href(
+ 'monitoring/host/show',
+ array(
+ 'host' => $service->host_name,
+ )
+ );
+ $serviceStateName = Service::getStateText($service->service_state);
+ $serviceCheckOverdue = $service->service_next_update < time(); ?>
+ <tr<?= $serviceCheckOverdue ? ' class="state-outdated"' : '' ?>>
+ <td class="state-col state-<?= $serviceStateName ?><?= $service->service_handled ? ' handled' : '' ?>">
+ <div class="state-label">
+ <?php if ($serviceCheckOverdue): ?>
+ <?= $this->icon('clock', sprintf($this->translate('Overdue %s'), DateFormatter::timeSince($service->service_next_update))) ?>
+ <?php endif ?>
+ <?= Service::getStateText($service->service_state, true) ?>
+ </div>
+ <?php if ((int) $service->service_state !== 99): ?>
+ <div class="state-meta">
+ <?= $this->timeSince($service->service_last_state_change, $this->compact) ?>
+ <?php if ((int) $service->service_state > 0 && (int) $service->service_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $service->service_attempt ?></div>
+ <?php endif ?>
+ </div>
+ <?php endif ?>
+ </td>
+
+ <td>
+ <div class="state-header">
+ <span class="service-on">
+ <?= $this->iconImage()->service($service) ?>
+ <?php
+ if ($this->showHost) {
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $service->service_display_name,
+ $serviceLink,
+ null,
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'rowaction'
+ )
+ ),
+ $this->qlink(
+ $service->host_display_name
+ . ($service->host_state != 0 ? ' (' . Host::getStateText($service->host_state, true) . ')' : ''),
+ $hostLink,
+ null,
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $service->host_display_name
+ )
+ ]
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $service->service_display_name,
+ $serviceLink,
+ null,
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $service->service_display_name,
+ $service->host_display_name
+ ),
+ 'class' => 'rowaction'
+ )
+ );
+ }
+ ?>
+ </span>
+ <span class="state-icons"><?= $this->serviceFlags($service) ?></span>
+ </div>
+ <div class="overview-plugin-output-container">
+ <div class="overview-performance-data">
+ <?= $this->perfdata($service->service_perfdata, true, 5) ?>
+ </div>
+ <p class="overview-plugin-output"><?= $this->pluginOutput($this->ellipsis($service->service_output, 10000), true, $service->service_check_command) ?></p>
+ </div>
+ </td>
+ <?php foreach($this->addColumns as $col): ?>
+ <?php if ($service->$col && preg_match('~^_(host|service)_([a-zA-Z0-9_]+)$~', $col, $m)): ?>
+ <td><?= $this->escape(\Icinga\Module\Monitoring\Object\MonitoredObject::protectCustomVars([$m[2] => $service->$col])[$m[2]]) ?></td>
+ <?php else: ?>
+ <td><?= $this->escape($service->$col) ?></td>
+ <?php endif ?>
+ <?php endforeach ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+ </table>
+<?php if ($services->hasMore()): ?>
+<div class="dont-print action-links">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+</div>
+<?php endif ?>
+</div>
+<?php if (! $this->compact): ?>
+<div class="monitoring-statusbar dont-print">
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ <?= $this->render('list/components/selectioninfo.phtml') ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/object/detail-history.phtml b/modules/monitoring/application/views/scripts/object/detail-history.phtml
new file mode 100644
index 0000000..692d3e4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/object/detail-history.phtml
@@ -0,0 +1,13 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+<?php if ($object->type === 'service') {
+ echo $this->render('partials/object/service-header.phtml');
+} else {
+ echo $this->render('partials/object/host-header.phtml');
+} ?>
+</div>
+<?php endif ?>
+<?= $this->render('partials/event-history.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/object/detail-tabhook.phtml b/modules/monitoring/application/views/scripts/object/detail-tabhook.phtml
new file mode 100644
index 0000000..abcfcc1
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/object/detail-tabhook.phtml
@@ -0,0 +1,21 @@
+<?php
+
+if (! $this->compact): ?>
+<div class="controls separated">
+ <?= $this->tabs ?>
+<?php
+if ($this->header === true) {
+ if ($object->type === 'service') {
+ echo $this->render('partials/object/service-header.phtml');
+ } else {
+ echo $this->render('partials/object/host-header.phtml');
+ }
+} elseif ($this->header !== false) {
+ echo $this->header;
+}
+?>
+</div>
+<?php endif ?>
+<div class="content">
+ <?= $this->content ?>
+</div> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml
new file mode 100644
index 0000000..b4e5a9c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/command/object-command-form.phtml
@@ -0,0 +1,18 @@
+<?php use Icinga\Data\Filter\Filter; ?>
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+<?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->render('partials/object/host-header.phtml');
+ $this->baseFilter = Filter::where('host', $object->host_name);
+ $this->stats = $object->stats;
+ echo $this->render('list/components/servicesummary.phtml');
+} else {
+ echo $this->render('partials/object/service-header.phtml');
+} ?>
+<?= $this->render('partials/object/quick-actions.phtml') ?>
+</div>
+<div class="content object-command">
+ <?= $form ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml
new file mode 100644
index 0000000..8d241ee
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/command/objects-command-form.phtml
@@ -0,0 +1,15 @@
+<div class="controls">
+<?php if (! $this->compact): ?>
+ <?= $tabs ?>
+<?php endif ?>
+<?php if (isset($serviceStates)): ?>
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ <?= $this->render('partials/service/objects-header.phtml') ?>
+<?php else: ?>
+ <?= $this->render('list/components/hostssummary.phtml') ?>
+ <?= $this->render('partials/host/objects-header.phtml') ?>
+<?php endif ?>
+</div>
+<div class="content objects-command">
+ <?= $form ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml
new file mode 100644
index 0000000..f35680c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comment-description.phtml
@@ -0,0 +1,24 @@
+<?php
+switch ($comment->type) {
+ case 'flapping':
+ $icon = 'flapping';
+ $title = $this->translate('Flapping');
+ $tooltip = $this->translate('Comment was caused by a flapping host or service');
+ break;
+ case 'comment':
+ $icon = 'user';
+ $title = $this->translate('User Comment');
+ $tooltip = $this->translate('Comment was created by an user');
+ break;
+ case 'downtime':
+ $icon = 'plug';
+ $title = $this->translate('Downtime');
+ $tooltip = $this->translate('Comment was caused by a downtime');
+ break;
+ case 'ack':
+ $icon = 'ok';
+ $title = $this->translate('Acknowledgement');
+ $tooltip = $this->translate('Comment was caused by an acknowledgement');
+ break;
+}
+echo $this->icon($icon, $tooltip, array('class' => 'large-icon'));
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml
new file mode 100644
index 0000000..c603d3c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comment-detail.phtml
@@ -0,0 +1,82 @@
+<div class="comment-author">
+<?php if ($comment->objecttype === 'service') {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $comment->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $comment->host_name,
+ 'service' => $comment->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $comment->service_display_name,
+ $comment->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $comment->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $comment->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $comment->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+} else {
+ echo $this->qlink(
+ $comment->host_display_name,
+ 'monitoring/host/show',
+ array('host' => $comment->host_name),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for this comment about host %s'),
+ $comment->host_display_name
+ )
+ )
+ );
+} ?>
+ <span class="comment-time">
+ <?= $this->translate('by') ?>
+ <?= $this->escape($comment->author) ?>
+ <?= $this->timeAgo($comment->timestamp) ?>
+ </span>
+ <span class="comment-icons" data-base-target="_self">
+ <?= $comment->persistent ? $this->icon('attach', 'This comment is persistent') : '' ?>
+ <?= $comment->expiration ? $this->icon('clock', sprintf(
+ $this->translate('This comment expires on %s at %s'),
+ $this->formatDate($comment->expiration),
+ $this->formatTime($comment->expiration)
+ )) : '' ?>
+ <?php if (isset($delCommentForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $uniqId = uniqid();
+ $buttonId = 'delete-comment-' . $uniqId;
+ $textId = 'comment-' . $uniqId;
+ $deleteButton = clone $delCommentForm;
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action dont-print');
+ $deleteButton->populate(
+ array(
+ 'comment_id' => $comment->id,
+ 'comment_is_service' => isset($comment->service_description),
+ 'comment_name' => $comment->name
+ )
+ );
+ $deleteButton->getElement('btn_submit')
+ ->setAttrib('aria-label', $this->translate('Delete comment'))
+ ->setAttrib('id', $buttonId)
+ ->setAttrib('aria-describedby', $buttonId . ' ' . $textId);
+ echo $deleteButton;
+ } ?>
+ </span>
+</div>
+<?= $this->nl2br($this->markdownLine($comment->comment, isset($textId) ? ['id' => $textId, 'class' => 'caption'] : [ 'class' => 'caption'])) ?>
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml
new file mode 100644
index 0000000..4472479
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comment-header.phtml
@@ -0,0 +1,10 @@
+<table>
+ <tr>
+ <td class="icon-col">
+ <?= $this->render('partials/comment/comment-description.phtml') ?>
+ </td>
+ <td>
+ <?= $this->render('partials/comment/comment-detail.phtml') ?>
+ </td>
+ </tr>
+</table>
diff --git a/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml
new file mode 100644
index 0000000..c4c92da
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/comment/comments-header.phtml
@@ -0,0 +1,32 @@
+<table>
+ <tbody>
+ <?php
+ foreach ($comments as $i => $comment):
+ if ($i === 5) {
+ break;
+ }
+ ?>
+ <tr>
+ <td class="icon-col">
+ <?= $this->partial('partials/comment/comment-description.phtml', array('comment' => $comment)) ?>
+ </td>
+ <td>
+ <?= $this->partial('partials/comment/comment-detail.phtml', array('comment' => $comment)) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+</table>
+<?php if ($comments->count() > 5): ?>
+<p>
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d comments'), $comments->count()),
+ $listAllLink,
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'down-open'
+ )
+ ) ?>
+</p>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml
new file mode 100644
index 0000000..dae6caa
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/downtime/downtime-header.phtml
@@ -0,0 +1,101 @@
+<td class="state-col state-<?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>">
+ <?php if ($downtime->start <= time() && ! $downtime->is_in_effect): ?>
+ <div class="state-label"><?= $this->translate('ENDS', 'Downtime status'); ?></div>
+ <div class="state-meta"><?= $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact, true) ?></div>
+ <?php else: ?>
+ <div class="state-label"><?= $downtime->is_in_effect ? $this->translate('EXPIRES', 'Downtime status') : $this->translate('STARTS', 'Downtime status'); ?></div>
+ <div class="state-meta"><?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact, true) ?></div>
+ <?php endif; ?>
+</td>
+<td>
+ <div class="comment-author">
+ <?php if ($isService) {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $downtime->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $downtime->host_name,
+ 'service' => $downtime->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $downtime->service_display_name,
+ $downtime->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $downtime->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $downtime->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $downtime->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+ } else {
+ echo $this->qlink(
+ $downtime->host_display_name,
+ 'monitoring/host/show',
+ array('host' => $downtime->host_name, 'downtime_id' => $downtime->id),
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for this downtime scheduled for host %s'),
+ $downtime->host_display_name
+ )
+ )
+ );
+ } ?>
+ <span class="comment-time">
+ <?= $this->escape(sprintf(
+ $downtime->is_flexible
+ ? $this->translate('Flexible downtime by %s')
+ : $this->translate('Fixed downtime by %s'),
+ $downtime->author_name
+ )) ?>
+ </span>
+ <?php if (! $downtime->is_in_effect && $downtime->start >= time()): ?>
+ <span><?= sprintf($this->translate('expires %s'), $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, false, true)) ?></span>
+ <?php endif ?>
+ <span class="comment-icons">
+ <?php if ($downtime->is_flexible): ?>
+ <?= $this->icon('magic', $this->translate('This downtime is flexible')); ?>
+ <?php endif ?>
+
+ <?php if ($downtime->is_in_effect): ?>
+ <?= $this->icon('plug', $this->translate('This downtime is in effect')); ?>
+ <?php endif ?>
+
+ <?php if (isset($delDowntimeForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $uniqId = uniqid();
+ $buttonId = 'delete-downtime-' . $uniqId;
+ $textId = 'downtime-' . $uniqId;
+ $deleteButton = clone $delDowntimeForm;
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action dont-print');
+ $deleteButton->populate(
+ array(
+ 'downtime_id' => $downtime->id,
+ 'downtime_is_service' => isset($downtime->service_description),
+ 'downtime_name' => $downtime->name
+ )
+ );
+ $deleteButton->getElement('btn_submit')
+ ->setAttrib('aria-label', $this->translate('Delete downtime'))
+ ->setAttrib('id', $buttonId)
+ ->setAttrib('aria-describedby', $buttonId . ' ' . $textId);
+ echo $deleteButton;
+ } ?>
+ </span>
+ </div>
+ <?= $this->nl2br($this->markdown($downtime->comment, isset($textId) ? ['id' => $textId] : null)) ?>
+</td>
diff --git a/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml
new file mode 100644
index 0000000..e2582c1
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/downtime/downtimes-header.phtml
@@ -0,0 +1,40 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+?>
+<table class="state-table common-table" data-base-target="_next">
+ <tbody>
+ <?php
+ foreach ($this->downtimes as $i => $downtime):
+ if ($i > 5) {
+ break;
+ }
+ if ($downtime->objecttype === 'service') {
+ $this->isService = true;
+ $this->stateName = Service::getStateText($downtime->service_state);
+ } else {
+ $this->isService = false;
+ $this->stateName = Host::getStateText($downtime->host_state);
+ }
+ $this->downtime = $downtime;
+ $this->displayComment = false;
+ ?>
+ <tr>
+ <?= $this->render('partials/downtime/downtime-header.phtml') ?>
+ </tr>
+ <?php endforeach ?>
+ </tbody>
+</table>
+<?php if ($downtimes->count() > 5): ?>
+<p>
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d downtimes'), $downtimes->count()),
+ $listAllLink,
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'down-open'
+ )
+ ) ?>
+</p>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/event-history.phtml b/modules/monitoring/application/views/scripts/partials/event-history.phtml
new file mode 100644
index 0000000..b81c95d
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/event-history.phtml
@@ -0,0 +1,267 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+use Icinga\Web\UrlParams;
+
+function contactsLink($match, $view) {
+ $links = array();
+ foreach (preg_split('/,\s/', $match[1]) as $contact) {
+ $links[] = $view->qlink(
+ $contact,
+ 'monitoring/show/contact',
+ array('contact_name' => $contact),
+ array('title' => sprintf($view->translate('Show detailed information about %s'), $contact))
+ );
+ }
+ return '[' . implode(', ', $links) . ']';
+}
+
+$self = $this;
+
+$url = $this->url();
+$limit = (int) $url->getParam('limit', 25);
+if (! $url->hasParam('page') || ($page = (int) $url->getParam('page')) < 1) {
+ $page = 1;
+}
+
+/** @var \Icinga\Module\Monitoring\DataView\EventHistory $history */
+$history->limit($limit * $page);
+?>
+<div class="content">
+<?php
+$dateFormatter = new IntlDateFormatter(setlocale(LC_TIME, 0), IntlDateFormatter::FULL, IntlDateFormatter::NONE);
+$lastDate = null;
+$flappingMsg = $this->translate('Flapping with a %.2f%% state change rate');
+$rowAction = Url::fromPath('monitoring/event/show');
+?>
+ <?php foreach ($history->peekAhead() as $event): ?>
+<?php if ($lastDate === null): ?>
+ <table class="table-row-selectable state-table" data-base-target="_next">
+ <tbody>
+<?php endif;
+ $icon = '';
+ $iconTitle = null;
+ $isService = isset($event->service_description);
+ $msg = $event->output;
+ $stateName = 'no-state';
+
+ $rowAction->setParams(new UrlParams())->addParams(array(
+ 'type' => $event->type,
+ 'id' => $event->id
+ ));
+ switch ($event->type) {
+ case substr($event->type, 0, 13) === 'notification_':
+ $rowAction->setParam('type', 'notify');
+ $icon = 'bell';
+ switch (substr($event->type, 13)) {
+ case 'state':
+ $iconTitle = $this->translate('State notification', 'tooltip');
+ $label = $this->translate('NOTIFICATION');
+ $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state);
+ break;
+ case 'ack':
+ $iconTitle = $this->translate('Ack Notification', 'tooltip');
+ $label = $this->translate('ACK NOTIFICATION');
+ break;
+ case 'dt_start':
+ $iconTitle = $this->translate('Downtime start notification', 'tooltip');
+ $label = $this->translate('DOWNTIME START NOTIFICATION');
+ break;
+ case 'dt_end':
+ $iconTitle = $this->translate('Downtime end notification', 'tooltip');
+ $label = $this->translate('DOWNTIME END NOTIFICATION');
+ break;
+ case 'flapping':
+ $iconTitle = $this->translate('Flapping notification', 'tooltip');
+ $label = $this->translate('FLAPPING NOTIFICATION');
+ break;
+ case 'flapping_end':
+ $iconTitle = $this->translate('Flapping end notification', 'tooltip');
+ $label = $this->translate('FLAPPING END NOTIFICATION');
+ break;
+ case 'custom':
+ $iconTitle = $this->translate('Custom notification', 'tooltip');
+ $label = $this->translate('CUSTOM NOTIFICATION');
+ break;
+ }
+ $msg = $msg ? preg_replace_callback(
+ '/^\[([^\]]+)\]/',
+ function($match) use ($self) { return contactsLink($match, $self); },
+ $msg
+ ) : $this->translate('This notification was not sent out to any contact.');
+ break;
+ case 'comment':
+ $icon = 'comment-empty';
+ $iconTitle = $this->translate('Comment', 'tooltip');
+ $label = $this->translate('COMMENT');
+ break;
+ case 'comment_deleted':
+ $icon = 'cancel';
+ $iconTitle = $this->translate('Comment removed', 'tooltip');
+ $label = $this->translate('COMMENT DELETED');
+ break;
+ case 'ack':
+ $icon = 'ok';
+ $iconTitle = $this->translate('Acknowledged', 'tooltip');
+ $label = $this->translate('ACKNOWLEDGED');
+ break;
+ case 'ack_deleted':
+ $icon = 'ok';
+ $iconTitle = $this->translate('Acknowledgement removed', 'tooltip');
+ $label = $this->translate('ACKNOWLEDGEMENT REMOVED');
+ break;
+ case 'dt_comment':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime scheduled', 'tooltip');
+ $label = $this->translate('SCHEDULED DOWNTIME');
+ break;
+ case 'dt_comment_deleted':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime removed', 'tooltip');
+ $label = $this->translate('DOWNTIME DELETED');
+ break;
+ case 'flapping':
+ $icon = 'flapping';
+ $iconTitle = $this->translate('Flapping started', 'tooltip');
+ $label = $this->translate('FLAPPING');
+ $msg = sprintf($flappingMsg, $msg);
+ break;
+ case 'flapping_deleted':
+ $icon = 'flapping';
+ $iconTitle = $this->translate('Flapping stopped', 'tooltip');
+ $label = $this->translate('FLAPPING STOPPED');
+ $msg = sprintf($flappingMsg, $msg);
+ break;
+ case 'hard_state':
+ if ((int) $event->state === 0) {
+ $icon = 'thumbs-up';
+ } else {
+ $icon = 'warning-empty';
+ }
+ $iconTitle = $this->translate('Hard state', 'tooltip');
+ $label = $isService ? Service::getStateText($event->state, true) : Host::getStateText($event->state, true);
+ $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state);
+ break;
+ case 'soft_state':
+ $icon = 'spinner';
+ $iconTitle = $this->translate('Soft state', 'tooltip');
+ $label = $isService ? Service::getStateText($event->state, true) : Host::getStateText($event->state, true);
+ $stateName = $isService ? Service::getStateText($event->state) : Host::getStateText($event->state);
+ break;
+ case 'dt_start':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime started', 'tooltip');
+ $label = $this->translate('DOWNTIME START');
+ break;
+ case 'dt_end':
+ $icon = 'plug';
+ $iconTitle = $this->translate('Downtime ended', 'tooltip');
+ $label = $this->translate('DOWNTIME END');
+ break;
+ } ?>
+ <?php
+ $currentDate = $dateFormatter->format($event->timestamp);
+ if ($currentDate !== $lastDate):
+ $lastDate = $currentDate;
+ ?>
+ <tr>
+ <th colspan="2"><?= $currentDate ?></th>
+ </tr>
+ <?php endif ?>
+ <tr href="<?= $rowAction ?>">
+ <td class="state-col state-<?= $stateName ?>">
+ <?php if ($history->getIteratorPosition() % $limit === 0): ?>
+ <a id="page-<?= $history->getIteratorPosition() / $limit + 1 ?>"></a>
+ <?php endif ?>
+ <div class="state-label"><?= $this->escape($label) ?></div>
+ <div class="state-meta"><?= $this->formatTime($event->timestamp) ?></div>
+ </td>
+ <td>
+ <div class="history-message-container">
+ <?php if ($icon): ?>
+ <div class="history-message-icon">
+ <?= $this->icon($icon, $iconTitle) ?>
+ </div>
+ <?php endif ?>
+ <div class="history-message-output">
+ <?php if ($this->isOverview): ?>
+ <?php if ($isService) {
+ echo '<span class="service-on">';
+ echo sprintf(
+ $this->translate('%s on %s', 'service on host'),
+ $this->qlink(
+ $event->service_display_name,
+ 'monitoring/service/show',
+ [
+ 'host' => $event->host_name,
+ 'service' => $event->service_description
+ ],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $event->service_display_name,
+ $event->host_display_name
+ )
+ ]
+ ),
+ $this->qlink(
+ $event->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $event->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $event->host_display_name
+ )
+ ]
+ )
+ );
+ echo '</span>';
+ } else {
+ echo $this->qlink(
+ $event->host_display_name,
+ 'monitoring/host/show',
+ ['host' => $event->host_name],
+ [
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $event->host_display_name
+ )
+ ]
+ );
+ } ?>
+ <?php endif ?>
+ <?= $this->nl2br($this->createTicketLinks($this->markdown($msg, ['class' => 'overview-plugin-output']))) ?>
+ </div>
+ </div>
+ </td>
+ </tr>
+ <?php endforeach ?>
+<?php if ($lastDate !== null): ?>
+ </tbody>
+ </table>
+<?php endif ?>
+<?php if ($history->hasMore()): ?>
+ <div class="action-links">
+ <?php if ($this->compact) {
+ echo $this->qlink(
+ $this->translate('Show More'),
+ $url->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_next'
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Load More'),
+ $url->setAnchor('page-' . ($page + 1)),
+ array('page' => $page + 1,),
+ array('class' => 'action-link')
+ );
+ } ?>
+ </div>
+<?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml
new file mode 100644
index 0000000..48141e2
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/host/objects-header.phtml
@@ -0,0 +1,41 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+
+if (! ($hostCount = count($objects))): return; endif ?>
+<table class="state-table host-detail-state">
+<tbody>
+<?php foreach ($objects as $i => $host): /** @var Host $host */
+ if ($i === 5) {
+ break;
+ } ?>
+ <tr>
+ <td class="state-col state-<?= Host::getStateText($host->host_state); ?><?= $host->host_handled ? ' handled' : '' ?>">
+ <span class="sr-only"><?= Host::getStateText($host->host_state) ?></span>
+ <div class="state-meta">
+ <?= $this->timeSince($host->host_last_state_change, $this->compact) ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->link()->host(
+ $host->host_name,
+ $host->host_display_name
+ ) ?>
+ <?= $this->hostFlags($host) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+</tbody>
+</table>
+<?php if ($hostCount > 5): ?>
+<div class="hosts-link">
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d hosts'), $hostCount),
+ $this->url()->setPath('monitoring/list/hosts'),
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'forward'
+ )
+ ) ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml b/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml
new file mode 100644
index 0000000..62bfd2c
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/detail-content.phtml
@@ -0,0 +1,53 @@
+<div class="content" data-base-target="_next">
+ <?= $this->render('show/components/output.phtml') ?>
+ <?= $this->render('show/components/grapher.phtml') ?>
+ <?= $this->render('show/components/extensions.phtml') ?>
+
+ <h2><?= $this->translate('Problem handling') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?= $this->render('show/components/acknowledgement.phtml') ?>
+ <?= $this->render('show/components/comments.phtml') ?>
+ <?= $this->render('show/components/downtime.phtml') ?>
+ <?= $this->render('show/components/notes.phtml') ?>
+ <?= $this->render('show/components/actions.phtml') ?>
+ <?= $this->render('show/components/flapping.phtml') ?>
+ <?php if ($object->type === 'service'): ?>
+ <?= $this->render('show/components/servicegroups.phtml') ?>
+ <?php else: ?>
+ <?= $this->render('show/components/hostgroups.phtml') ?>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+ <?= $this->render('show/components/perfdata.phtml') ?>
+
+ <h2><?= $this->translate('Notifications') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?= $this->render('show/components/notifications.phtml') ?>
+ <?php if ($this->hasPermission('*') || ! $this->hasPermission('no-monitoring/contacts')): ?>
+ <?= $this->render('show/components/contacts.phtml') ?>
+ <?php endif ?>
+ </tbody>
+ </table>
+
+ <h2><?= $this->translate('Check execution') ?></h2>
+ <table class="name-value-table">
+ <tbody>
+ <?= $this->render('show/components/command.phtml') ?>
+ <?= $this->render('show/components/checksource.phtml') ?>
+ <?= $this->render('show/components/reachable.phtml') ?>
+ <?= $this->render('show/components/checkstatistics.phtml') ?>
+ <?= $this->render('show/components/checktimeperiod.phtml') ?>
+ </tbody>
+ </table>
+
+ <?php if (! empty($object->customvars)): ?>
+ <h2><?= $this->translate('Custom Variables') ?></h2>
+ <div id="<?= $object->type ?>-customvars" data-visible-height="200" class="collapsible">
+ <?= (new \Icinga\Module\Monitoring\Web\Widget\CustomVarTable($object->customvarsWithOriginalNames, $object)) ?>
+ </div>
+ <?php endif ?>
+ <?= $this->render('show/components/flags.phtml') ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/object/host-header.phtml b/modules/monitoring/application/views/scripts/partials/object/host-header.phtml
new file mode 100644
index 0000000..4de4a01
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/host-header.phtml
@@ -0,0 +1,51 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Web\Url;
+
+/** @var Host $object */
+
+$url = Url::fromRequest();
+$linkHostName = ! ($url->getPath() === 'monitoring/host/show' && $url->getParam('host') === $object->host_name);
+?>
+<table class="state-table host-detail-state">
+ <tr>
+ <td class="state-col state-<?= Host::getStateText($object->host_state) ?><?= $object->host_handled ? ' handled' : '' ?>">
+ <div class="state-header"><?= Host::getStateText($object->host_state, true) ?></div>
+ <div class="state-meta">
+ <?= $this->timeSince($object->host_last_state_change) ?>
+ <?php if ((int) $object->host_state > 0 && (int) $object->host_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $object->host_attempt ?></div>
+ <?php endif ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->iconImage()->host($object) ?>
+ <?php
+ if ($linkHostName) {
+ echo '<a href="' . Url::fromPath('monitoring/host/show', array('host' => $object->host_name)) . '">';
+ }
+ ?>
+ <span class="selectable"><strong><?= $this->escape($object->host_display_name) ?></strong></span>
+ <?php if ($object->host_display_name !== $object->host_name): ?>
+ <span class="selectable host-meta">&#40;<?= $this->escape($object->host_name) ?>&#41;</span>
+ <?php endif ?>
+ <?php
+ if ($linkHostName) {
+ echo '</a>';
+ }
+ ?>
+ <?php if ($object->host_alias !== $object->host_display_name && $object->host_alias !== $object->host_name): ?>
+ <div class="selectable host-meta">
+ <?= $this->escape($this->translate('Alias', 'host') . ': ' . $object->host_alias) ?>
+ </div>
+ <?php endif ?>
+ <?= $this->hostFlags($object) ?>
+ <?php if ($object->host_address6 && $object->host_address6 !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv6 address') ?>"><?= $this->escape($object->host_address6) ?></div>
+ <?php endif ?>
+ <?php if ($object->host_address && $object->host_address !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv4 address') ?>"><?= $this->escape($object->host_address) ?></div>
+ <?php endif ?>
+ </td>
+ </tr>
+</table>
diff --git a/modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml b/modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml
new file mode 100644
index 0000000..fe05a84
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/quick-actions.phtml
@@ -0,0 +1,144 @@
+<div class="quick-actions">
+ <ul class="nav tab-nav">
+ <?php if (isset($removeAckForm)): ?>
+ <li>
+ <?php
+ $removeAckForm = clone $removeAckForm;
+ $removeAckForm->setAttrib('id', 'quickAction_' . $removeAckForm->getName()); // Avoids id duplication
+ $removeAckForm->setLabelEnabled(true);
+ echo $removeAckForm;
+ ?>
+ </li>
+ <?php elseif /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ ($this->hasPermission('monitoring/command/acknowledge-problem') && ! (in_array((int) $object->state, array(0, 99))) ): ?>
+ <li>
+ <?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Acknowledge'),
+ 'monitoring/host/acknowledge-problem',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $this->translate(
+ 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Acknowledge'),
+ 'monitoring/service/acknowledge-problem',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $this->translate(
+ 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled'
+ )
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ <?php if (isset($checkNowForm)): // Form is unset if the current user lacks the respective permission ?>
+ <?php ($checkNowForm = clone $checkNowForm)->setAttrib('id', 'quickAction_' . $checkNowForm->getName()); // Avoids id duplication ?>
+ <li><?= $checkNowForm ?></li>
+ <?php endif ?>
+ <?php if ($this->hasPermission('monitoring/command/comment/add')): ?>
+ <li>
+ <?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Comment'),
+ 'monitoring/host/add-comment',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this host')
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Comment'),
+ 'monitoring/service/add-comment',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this service')
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ <?php if ($this->hasPermission('monitoring/command/send-custom-notification')): ?>
+ <li>
+ <?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Notification'),
+ 'monitoring/host/send-custom-notification',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this host'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Notification'),
+ 'monitoring/service/send-custom-notification',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this service'
+ )
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ <?php if ($this->hasPermission('monitoring/command/downtime/schedule')): ?>
+ <li><?php if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Downtime'),
+ 'monitoring/host/schedule-downtime',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Downtime'),
+ 'monitoring/service/schedule-downtime',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ } ?>
+ </li>
+ <?php endif ?>
+ </ul>
+</div>
diff --git a/modules/monitoring/application/views/scripts/partials/object/service-header.phtml b/modules/monitoring/application/views/scripts/partials/object/service-header.phtml
new file mode 100644
index 0000000..318fe49
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/object/service-header.phtml
@@ -0,0 +1,72 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Web\Url;
+
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+
+$url = Url::fromRequest();
+$linkServiceName = ! ($url->getPath() === 'monitoring/service/show' && $url->getParam('service') === $object->service_description);
+?>
+<table class="state-table service-detail-state">
+ <tr>
+ <td class="state-col state-<?= Host::getStateText($object->host_state) ?><?= $object->host_handled ? ' handled' : '' ?>">
+ <div class="state-label"><?= Host::getStateText($object->host_state, true) ?></div>
+ <div class="state-meta">
+ <?= $this->timeSince($object->host_last_state_change) ?>
+ <?php if ((int) $object->host_state > 0 && (int) $object->host_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $object->host_attempt ?></div>
+ <?php endif ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->iconImage()->host($object) ?>
+ <a href="<?= Url::fromPath('monitoring/host/show', array('host' => $object->host_name)) ?>">
+ <span class="selectable"><strong><?= $this->escape($object->host_display_name) ?></strong></span>
+ <?php if ($object->host_display_name !== $object->host_name): ?>
+ <span class="selectable host-meta">&#40;<?= $this->escape($object->host_name) ?>&#41;</span>
+ <?php endif ?>
+ </a>
+ <?= $this->hostFlags($object) ?>
+ <?php if ($object->host_address6 && $object->host_address6 !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv6 address') ?>"><?= $this->escape($object->host_address6) ?></div>
+ <?php endif ?>
+ <?php if ($object->host_address && $object->host_address !== $object->host_name): ?>
+ <div class="selectable host-meta" title="<?= $this->translate('IPv4 address') ?>"><?= $this->escape($object->host_address) ?></div>
+ <?php endif ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="state-col state-<?= Service::getStateText($object->service_state) ?><?= $object->service_handled ? ' handled' : '' ?>">
+ <div class="state-label"><?= Service::getStateText($object->service_state, true) ?></div>
+ <div class="state-meta">
+ <?= $this->timeSince($object->service_last_state_change) ?>
+ <?php if ((int) $object->service_state > 0 && (int) $object->service_state_type === 0): ?>
+ <div><?= $this->translate('Soft', 'Soft state') ?> <?= $object->service_attempt ?></div>
+ <?php endif ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->iconImage()->service($object) ?>
+ <?= $this->translate('Service') ?>&#58;
+ <?php
+ if ($linkServiceName) {
+ echo '<a href="' . Url::fromPath('monitoring/service/show', array(
+ 'host' => $object->host_name,
+ 'service' => $object->service_description
+ )) . '">';
+ }
+ ?>
+ <span class="selectable"><strong><?= $this->escape($object->service_display_name) ?></strong></span>
+ <?php if ($object->service_display_name !== $object->service_description): ?>
+ <span class="selectable service-meta">&#40;<?= $this->escape($object->service_description) ?>&#41;</span>
+ <?php endif ?>
+ <?php
+ if ($linkServiceName) {
+ echo '</a>';
+ }
+ ?>
+ <?= $this->serviceFlags($object) ?>
+ </td>
+ </tr>
+</table>
diff --git a/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml
new file mode 100644
index 0000000..d342d87
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/service/objects-header.phtml
@@ -0,0 +1,45 @@
+<?php
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\Service;
+
+if (! ($serviceCount = count($objects))): return; endif ?>
+<table class="state-table service-detail-state">
+<tbody>
+<?php foreach ($objects as $i => $service): /** @var Service $service */
+ if ($i === 5) {
+ break;
+ } ?>
+ <tr>
+ <td class="state-col state-<?= Service::getStateText($service->service_state) ?><?= $service->service_handled ? ' handled' : '' ?>">
+ <span class="sr-only"><?= Service::getStateText($service->service_state) ?></span>
+ <div class="state-meta">
+ <?= $this->timeSince($service->service_last_state_change, $this->compact) ?>
+ </div>
+ </td>
+ <td>
+ <?= $this->link()->service(
+ $service->service_description,
+ $service->service_display_name,
+ $service->host_name,
+ $service->host_display_name
+ . ($service->host_state != 0 ? ' (' . Host::getStateText($service->host_state, true) . ')' : '')
+ ) ?>
+ <?= $this->serviceFlags($service) ?>
+ </td>
+ </tr>
+<?php endforeach ?>
+</tbody>
+</table>
+<?php if ($serviceCount > 5): ?>
+<div class="services-link">
+ <?= $this->qlink(
+ sprintf($this->translate('List all %d services'), $serviceCount),
+ $this->url()->setPath('monitoring/list/services'),
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'icon' => 'forward'
+ )
+ ) ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/partials/show-more.phtml b/modules/monitoring/application/views/scripts/partials/show-more.phtml
new file mode 100644
index 0000000..fd6a99d
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/partials/show-more.phtml
@@ -0,0 +1,15 @@
+<?php
+/** @var \Icinga\Module\Monitoring\DataView\DataView $dataView */
+if ($dataView->hasMore()): ?>
+<div class="text-right">
+ <?= $this->qlink(
+ $this->translate('Show More'),
+ $this->url()->without(array('showCompact', 'limit')),
+ null,
+ array(
+ 'data-base-target' => '_next',
+ 'class' => 'action-link'
+ )
+ ) ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/service/show.phtml b/modules/monitoring/application/views/scripts/service/show.phtml
new file mode 100644
index 0000000..bc9c612
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/service/show.phtml
@@ -0,0 +1,8 @@
+<div class="controls controls-separated">
+<?php if (! $this->compact): ?>
+ <?= $this->tabs ?>
+<?php endif ?>
+ <?= $this->render('partials/object/service-header.phtml') ?>
+ <?= $this->render('partials/object/quick-actions.phtml') ?>
+</div>
+<?= $this->render('partials/object/detail-content.phtml') ?>
diff --git a/modules/monitoring/application/views/scripts/services/show.phtml b/modules/monitoring/application/views/scripts/services/show.phtml
new file mode 100644
index 0000000..e9fb56f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/services/show.phtml
@@ -0,0 +1,208 @@
+<div class="controls">
+
+ <?php if (! $this->compact): ?>
+ <?= $tabs ?>
+ <?php endif ?>
+ <?= $this->render('list/components/servicesummary.phtml') ?>
+ <?= $this->render('partials/service/objects-header.phtml') ?>
+ <?php
+ $serviceCount = count($objects);
+ $unhandledCount = count($unhandledObjects);
+ $problemCount = count($problemObjects);
+ $unackCount = count($unacknowledgedObjects);
+ $scheduledDowntimeCount = count($objects->getScheduledDowntimes());
+ ?>
+</div>
+
+<div class="content">
+
+ <?php if ($serviceCount === 0): ?>
+ <?= $this->translate('No services found matching the filter') ?>
+ <?php else: ?>
+ <?= $this->render('show/components/extensions.phtml') ?>
+ <h2> <?= $this->translate('Problem handling') ?> </h2>
+ <table class="name-value-table">
+ <tbody>
+ <?php if ($unackCount > 0): ?>
+ <tr>
+ <th> <?= sprintf($this->translate('%d unhandled problems'), $unackCount) ?> </th>
+ <td> <?= $this->qlink(
+ $this->translate('Acknowledge'),
+ $acknowledgeLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'check'
+ )
+ ) ?> </td>
+ </tr>
+ <?php endif; ?>
+
+ <?php if (($acknowledgedCount = count($acknowledgedObjects)) > 0): ?>
+ <tr>
+ <th> <?= sprintf(
+ $this->translatePlural(
+ '%s acknowledgement',
+ '%s acknowledgements',
+ $acknowledgedCount
+ ),
+ '<b>' . $acknowledgedCount . '</b>'
+ ) ?>
+ </th>
+ <td>
+ <?= $removeAckForm->setLabelEnabled(true) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th> <?= $this->translate('Comments') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Add comments'),
+ $addCommentLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'comment-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (($commentCount = count($objects->getComments())) > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%s comment',
+ '%s comments',
+ $commentCount
+ ),
+ $commentCount
+ ),
+ $commentsLink,
+ null,
+ array('data-base-target' => '_next')
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ <tr>
+ <th>
+ <?= $this->translate('Downtimes') ?>
+ </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Schedule downtimes'),
+ $downtimeAllLink,
+ null,
+ array(
+ 'icon' => 'plug',
+ 'class' => 'action-link'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if ($scheduledDowntimeCount > 0): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%d scheduled downtime',
+ '%d scheduled downtimes',
+ $scheduledDowntimeCount
+ ),
+ $scheduledDowntimeCount
+ ),
+ $showDowntimesLink,
+ null,
+ array(
+ 'data-base-target' => '_next'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+
+ </tbody>
+ </table>
+
+ <?php if ($this->hasPermission('monitoring/command/send-custom-notification')): ?>
+
+ <h2> <?= $this->translate('Notifications') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Notifications') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Send notifications'),
+ $sendCustomNotificationLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'bell'
+ )
+ ) ?>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <?php endif ?>
+
+ <h2> <?= $this->translate('Check Execution') ?> </h2>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th> <?= $this->translate('Command') ?> </th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Process check result'),
+ $processCheckResultAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'edit'
+ )
+ ) ?>
+ </td>
+ </tr>
+
+ <?php if (isset($checkNowForm)): // Form is unset if the current user lacks the respective permission ?>
+ <tr>
+ <th> <?= $this->translate('Schedule Check') ?> </th>
+ <td> <?= $checkNowForm ?> </td>
+ </tr>
+ <?php endif ?>
+
+ <?php if (isset($rescheduleAllLink)): ?>
+ <tr>
+ <th></th>
+ <td>
+ <?= $this->qlink(
+ $this->translate('Reschedule'),
+ $rescheduleAllLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'icon' => 'calendar-empty'
+ )
+ ) ?>
+ </td>
+ </tr>
+ <?php endif ?>
+ </tbody>
+ </table>
+ <h2><?= $this->translate('Feature Commands') ?></h2>
+ <?= $toggleFeaturesForm ?>
+ <?php endif ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml
new file mode 100644
index 0000000..fd7f6bb
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/acknowledgement.phtml
@@ -0,0 +1,94 @@
+<?php
+
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+
+if (in_array((int) $object->state, array(0, 99))) {
+ // Ignore this markup if the object is in a non-problem state or pending
+ return;
+}
+
+if ($object->acknowledged):
+$acknowledgement = $object->acknowledgement;
+/** @var \Icinga\Module\Monitoring\Object\Acknowledgement $acknowledgement */
+?>
+<tr>
+ <th><?= $this->translate('Acknowledged') ?></th>
+ <td data-base-target="_self">
+ <?php if ($acknowledgement): ?>
+ <dl class="comment-list">
+ <dt>
+ <?= $this->escape($acknowledgement->getAuthor()) ?>
+ <span class="comment-time">
+ <?= $this->translate('acknowledged') ?>
+ <?= $this->timeAgo($acknowledgement->getEntryTime()) ?>
+ <?php if ($acknowledgement->expires()): ?>
+ <span aria-hidden="true">&#448;</span>
+ <?= sprintf(
+ $this->translate('Expires %s'),
+ $this->timeUntil($acknowledgement->getExpirationTime())
+ ) ?>
+ <?php endif ?>
+ </span>
+ <?php if ($acknowledgement->getSticky()): ?>
+ <?= $this->icon('pin', sprintf(
+ $this->translate(
+ 'Acknowledgement remains until the %1$s recovers even if the %1$s changes state'
+ ),
+ $object->getType(true)
+ )) ?>
+ <?php endif ?>
+ <?php if (isset($removeAckForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $removeAckForm->setAttrib('class', $removeAckForm->getAttrib('class') . ' remove-action');
+ echo $removeAckForm;
+ } ?>
+ </dt>
+ <dd>
+ <?= $this->nl2br($this->createTicketLinks($this->markdown($acknowledgement->getComment()))) ?>
+ </dd>
+ </dl>
+ <?php elseif (isset($removeAckForm)): ?>
+ <?= $removeAckForm ?>
+ <?php endif ?>
+ </td>
+</tr>
+<?php else: ?>
+<tr>
+ <th><?= $this->translate('Not acknowledged') ?></th>
+ <td>
+ <?php if ($this->hasPermission('monitoring/command/acknowledge-problem')) {
+ if ($object->getType() === $object::TYPE_HOST) {
+ $ackLink = $this->href(
+ 'monitoring/host/acknowledge-problem',
+ array('host' => $object->getName()),
+ null,
+ array('class' => 'action-link')
+ );
+ } else {
+ $ackLink = $this->href(
+ 'monitoring/service/acknowledge-problem',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ null,
+ array('class' => 'action-link')
+ );
+ }
+ ?>
+ <?= $this->qlink(
+ $this->translate('Acknowledge'),
+ $ackLink,
+ null,
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $this->translate(
+ 'Acknowledge this problem, suppress all future notifications for it and tag it as being handled'
+ )
+ )
+ ) ?>
+ <?php } else {
+ echo '&#45;';
+ } // endif ?>
+ </td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml
new file mode 100644
index 0000000..938ab2a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml
@@ -0,0 +1,43 @@
+<?php
+
+use Icinga\Web\Navigation\Navigation;
+
+$navigation = new Navigation();
+$navigation->load($object->getType() . '-action');
+foreach ($navigation as $item) {
+ $item->setObject($object);
+}
+
+foreach ($object->getActionUrls() as $i => $link) {
+ $navigation->addItem(
+
+ // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
+ $this->icon(
+ 'forward',
+ $this->translate('Link opens in new window'),
+ array('aria-label' => $this->translate('Link opens in new window'))
+ ) . ' Action ' . ($i + 1),
+ array(
+ 'url' => $link,
+ 'target' => '_blank',
+ 'renderer' => array(
+ 'NavigationItemRenderer',
+ 'escape_label' => false
+ )
+ )
+ );
+}
+
+if (isset($this->actions)) {
+ $navigation->merge($this->actions);
+}
+
+if ($navigation->isEmpty() || ! $navigation->hasRenderableItems()) {
+ return;
+}
+
+?>
+<tr>
+ <th><?= $this->translate('Actions'); ?></th>
+ <?= $navigation->getRenderer()->setElementTag('td')->setCssClass('actions go-ahead'); ?>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/checksource.phtml b/modules/monitoring/application/views/scripts/show/components/checksource.phtml
new file mode 100644
index 0000000..ac9799f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/checksource.phtml
@@ -0,0 +1,6 @@
+<?php if ($object->check_source !== null): ?>
+<tr>
+ <th><?= $this->translate('Check Source') ?></th>
+ <td><?= $this->escape($object->check_source) ?></td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml
new file mode 100644
index 0000000..e37e30a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/checkstatistics.phtml
@@ -0,0 +1,85 @@
+<?php
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+$activeChecksEnabled = (bool) $object->active_checks_enabled;
+?>
+
+<tr>
+ <th><?= $activeChecksEnabled ? $this->translate('Last check') : $this->translate('Last update') ?></th>
+ <td data-base-target="_self">
+<?php if ((int) $object->state !== 99): ?>
+ <?= $this->timeAgo($object->last_check) ?>
+ <?php if ($object->next_update < time()): ?>
+ <?= $this->icon('circle', $this->translate('Check result is late'), array('class' => 'icon-stateful state-critical')) ?>
+ <?php endif ?>
+<?php endif ?>
+ <?php if (isset($checkNowForm)) { // Form is unset if the current user lacks the respective permission
+ echo $checkNowForm;
+ } ?>
+ </td>
+</tr>
+
+<tr>
+ <th><?= $activeChecksEnabled ? $this->translate('Next check') : $this->translate('Next update') ?></th>
+ <td>
+ <?php if ((int) $object->state !== 99) {
+ if ($activeChecksEnabled) {
+ echo $this->timeUntil($object->next_check);
+ } else {
+ echo sprintf($this->translate('expected %s'), $this->timeUntil($object->next_update));
+ }
+ } ?>
+ <?php if ($activeChecksEnabled && $this->hasPermission('monitoring/command/schedule-check')) {
+ if ($object->getType() === $object::TYPE_SERVICE) {
+ echo $this->qlink(
+ $this->translate('Reschedule'),
+ 'monitoring/service/reschedule-check',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'calendar-empty',
+ 'title' => $this->translate(
+ 'Schedule the next active check at a different time than the current one'
+ )
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Reschedule'),
+ 'monitoring/host/reschedule-check',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'calendar-empty',
+ 'title' => $this->translate(
+ 'Schedule the next active check at a different time than the current one'
+ )
+ )
+ );
+ }
+ } ?>
+ </td>
+</tr>
+
+<tr>
+ <th><?= $this->translate('Check attempts') ?></th>
+ <td>
+ <?= $object->attempt ?>
+ (<?= (int) $object->state_type === 0 ? $this->translate('soft state') : $this->translate('hard state') ?>)
+ </td>
+</tr>
+
+<?php if ($object->check_execution_time): ?>
+<tr>
+ <th><?= $this->translate('Check execution time') ?></th>
+ <td><?= round((float) $object->check_execution_time, 3) ?>s</td>
+</tr>
+<?php endif ?>
+
+<?php if ($object->check_latency): ?>
+<tr>
+ <th><?= $this->translate('Check latency') ?></th>
+ <td><?= $object->check_latency ?>s</td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml b/modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml
new file mode 100644
index 0000000..34c4eb9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/checktimeperiod.phtml
@@ -0,0 +1,21 @@
+<?php if (isset($object->service_check_timeperiod)): ?>
+
+<tr>
+ <th><?= $this->translate('Check Timeperiod') ?></th>
+ <td>
+ <?= $object->service_check_timeperiod ?>
+ </td>
+</tr>
+
+<?php endif ?>
+
+<?php if (isset($object->host_check_timeperiod)): ?>
+
+ <tr>
+ <th><?= $this->translate('Check Timeperiod') ?></th>
+ <td>
+ <?= $object->host_check_timeperiod ?>
+ </td>
+ </tr>
+
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/command.phtml b/modules/monitoring/application/views/scripts/show/components/command.phtml
new file mode 100644
index 0000000..9b51458
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/command.phtml
@@ -0,0 +1,52 @@
+<?php
+$parts = explode('!', $object->check_command);
+$command = array_shift($parts);
+
+if ($showInstance): ?>
+<tr>
+ <th><?= $this->translate('Instance') ?></th>
+ <td><?= $this->escape($object->instance_name) ?></td>
+</tr>
+<?php endif ?>
+<tr>
+ <th><?= $this->translate('Command') ?></th>
+ <td>
+ <?= $this->escape($command) ?>
+ <?php if ($this->hasPermission('monitoring/command/process-check-result') && $object->passive_checks_enabled) {
+ $title = sprintf(
+ $this->translate('Submit a one time or so called passive result for the %s check'), $command
+ );
+ if ($object->getType() === $object::TYPE_HOST) {
+ echo $this->qlink(
+ $this->translate('Process check result'),
+ 'monitoring/host/process-check-result',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $title
+ )
+ );
+ } else {
+ echo $this->qlink(
+ $this->translate('Process check result'),
+ 'monitoring/service/process-check-result',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'edit',
+ 'title' => $title
+ )
+ );
+ }
+ } ?>
+ </td>
+</tr>
+
+<?php
+$row = "<tr>\n <th>%s</th>\n <td>%s</td>\n</tr>\n";
+for ($i = 0; $i < count($parts); $i++) {
+ printf($row, '$ARG' . ($i + 1) . '$', $this->escape($parts[$i]));
+}
diff --git a/modules/monitoring/application/views/scripts/show/components/comments.phtml b/modules/monitoring/application/views/scripts/show/components/comments.phtml
new file mode 100644
index 0000000..fd980ee
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/comments.phtml
@@ -0,0 +1,86 @@
+<?php
+$addLink = false;
+if ($this->hasPermission('monitoring/command/comment/add')) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ($object->getType() === $object::TYPE_HOST) {
+ $addLink = $this->qlink(
+ $this->translate('Add comment'),
+ 'monitoring/host/add-comment',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this host')
+ )
+ );
+ } else {
+ $addLink = $this->qlink(
+ $this->translate('Add comment'),
+ 'monitoring/service/add-comment',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'comment-empty',
+ 'title' => $this->translate('Add a new comment to this service')
+ )
+ );
+ }
+}
+if (empty($object->comments) && ! $addLink) {
+ return;
+}
+?>
+<tr>
+ <th><?php
+ echo $this->translate('Comments');
+ if (! empty($object->comments) && $addLink) {
+ echo '<br>' . $addLink;
+ }
+ ?></th>
+ <td data-base-target="_self">
+ <?php if (empty($object->comments)):
+ echo $addLink;
+ else: ?>
+ <dl class="comment-list">
+ <?php foreach ($object->comments as $comment): ?>
+ <dt>
+ <a data-base-target="_next" href="<?= $this->href('monitoring/comment/show', array('comment_id' => $comment->id)) ?>">
+ <?= $this->escape($comment->author) ?>
+ <span class="comment-time">
+ <?= $this->translate('commented') ?>
+ <?= $this->timeAgo($comment->timestamp) ?>
+ <?php if ($comment->expiration): ?>
+ <span aria-hidden="true">Ç€</span>
+ <?= sprintf(
+ $this->translate('Expires %s'),
+ $this->timeUntil($comment->expiration)
+ ) ?>
+ <?php endif ?>
+ </span>
+ </a>
+ <?= $comment->persistent ? $this->icon('attach', 'This comment is persistent.') : '' ?>
+ <?php if (isset($delCommentForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $deleteButton = clone($delCommentForm);
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action');
+ $deleteButton->populate(
+ array(
+ 'comment_id' => $comment->id,
+ 'comment_is_service' => isset($comment->service_description),
+ 'comment_name' => $comment->name
+ )
+ );
+ echo $deleteButton;
+ } ?>
+ </dt>
+ <dd>
+ <?= $this->nl2br($this->createTicketLinks($this->markdownLine($comment->comment, [ 'class' => 'caption']))) ?>
+ </dd>
+ <?php endforeach ?>
+ </dl>
+ <?php endif ?>
+ </td>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/contacts.phtml b/modules/monitoring/application/views/scripts/show/components/contacts.phtml
new file mode 100644
index 0000000..5661c1a
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/contacts.phtml
@@ -0,0 +1,38 @@
+<?php
+
+if ($object->contacts->hasResult()) {
+
+ $list = array();
+ foreach ($object->contacts as $contact) {
+ $list[] = $this->qlink(
+ $contact->contact_alias,
+ 'monitoring/show/contact',
+ array('contact_name' => $contact->contact_name),
+ array('title' => sprintf($this->translate('Show detailed information about %s'), $contact->contact_alias))
+ );
+ }
+
+ printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Contacts'),
+ implode(', ', $list)
+ );
+}
+
+if ($object->contactgroups->hasResult()) {
+ $list = array();
+ foreach ($object->contactgroups as $contactgroup) {
+ $list[] = $this->qlink(
+ $contactgroup->contactgroup_alias,
+ 'monitoring/list/contactgroups',
+ array('contactgroup_name' => $contactgroup->contactgroup_name),
+ array('title' => sprintf($this->translate('List contacts in contact-group "%s"'), $contactgroup->contactgroup_alias))
+ );
+ }
+
+ printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Contactgroups'),
+ implode(', ', $list)
+ );
+}
diff --git a/modules/monitoring/application/views/scripts/show/components/downtime.phtml b/modules/monitoring/application/views/scripts/show/components/downtime.phtml
new file mode 100644
index 0000000..618d4d9
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/downtime.phtml
@@ -0,0 +1,109 @@
+<?php
+$addLink = false;
+if ($this->hasPermission('monitoring/command/downtime/schedule')) {
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ($object->getType() === $object::TYPE_HOST) {
+ $addLink = $this->qlink(
+ $this->translate('Schedule downtime'),
+ 'monitoring/host/schedule-downtime',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ } else {
+ $addLink = $this->qlink(
+ $this->translate('Schedule downtime'),
+ 'monitoring/service/schedule-downtime',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'plug',
+ 'title' => $this->translate(
+ 'Schedule a downtime to suppress all problem notifications within a specific period of time'
+ )
+ )
+ );
+ }
+}
+if (empty($object->downtimes) && ! $addLink) {
+ return;
+}
+?>
+<tr>
+ <th><?php
+ echo $this->translate('Downtimes');
+ if (! empty($object->downtimes) && $addLink) {
+ echo '<br>' . $addLink;
+ }
+ ?></th>
+ <td data-base-target="_self">
+ <?php if (empty($object->downtimes)):
+ echo $addLink;
+ else: ?>
+ <dl class="comment-list">
+ <?php foreach ($object->downtimes as $downtime):
+ if ((bool) $downtime->is_in_effect) {
+ $state = sprintf(
+ $this->translate('expires %s', 'Last format parameter represents the downtime expire time'),
+ $this->timeUntil($downtime->end, false, true)
+ );
+ } else {
+ if ($downtime->start <= time()) {
+ $state = sprintf(
+ $this->translate('ends %s', 'Last format parameter represents the end time'),
+ $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, false, true)
+ );
+ } else {
+ $state = sprintf(
+ $this->translate('scheduled %s', 'Last format parameter represents the time scheduled'),
+ $this->timeUntil($downtime->start, false, true)
+ ) . ' ' . sprintf(
+ $this->translate('expires %s', 'Last format parameter represents the downtime expire time'),
+ $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, false, true)
+ );
+ }
+ }
+ ?>
+ <dt>
+ <?= $this->escape(sprintf(
+ $downtime->is_flexible
+ ? $this->translate('Flexible downtime by %s')
+ : $this->translate('Fixed downtime by %s'),
+ $downtime->author_name
+ )) ?>
+ <span class="comment-time">
+ <?= $state ?>
+ <span aria-hidden="true">&#448;</span>
+ <?= $this->translate('created') ?>
+ <?= $this->timeAgo($downtime->entry_time) ?>
+ </span>
+ <?php if (isset($delDowntimeForm)) {
+ // Form is unset if the current user lacks the respective permission
+ $deleteButton = clone($delDowntimeForm);
+ /** @var \Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm $deleteButton */
+ $deleteButton->setAttrib('class', $deleteButton->getAttrib('class') . ' remove-action');
+ $deleteButton->populate(
+ array(
+ 'downtime_id' => $downtime->id,
+ 'downtime_is_service' => $object->getType() === $object::TYPE_SERVICE,
+ 'downtime_name' => $downtime->name
+ )
+ );
+ echo $deleteButton;
+ } ?>
+ </dt>
+ <dd>
+ <?= $this->nl2br($this->createTicketLinks($this->markdown($downtime->comment))) ?>
+ </dd>
+ <?php endforeach ?>
+ </dl>
+ <?php endif ?>
+ </td>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/extensions.phtml b/modules/monitoring/application/views/scripts/show/components/extensions.phtml
new file mode 100644
index 0000000..263b7e4
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/extensions.phtml
@@ -0,0 +1,4 @@
+<?php
+foreach ($extensionsHtml as $extensionHtml) {
+ echo $extensionHtml;
+}
diff --git a/modules/monitoring/application/views/scripts/show/components/flags.phtml b/modules/monitoring/application/views/scripts/show/components/flags.phtml
new file mode 100644
index 0000000..871a4dd
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/flags.phtml
@@ -0,0 +1,4 @@
+<div data-base-target="_self">
+ <h2><?= $this->translate('Feature Commands') ?></h2>
+ <?= $toggleFeaturesForm ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/show/components/flapping.phtml b/modules/monitoring/application/views/scripts/show/components/flapping.phtml
new file mode 100644
index 0000000..f09b107
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/flapping.phtml
@@ -0,0 +1,14 @@
+<?php
+
+if ($object->is_flapping) {
+ printf(
+ "<tr><th>%s</th><td>%s %s</td></tr>\n",
+ 'Flapping',
+ $this->icon('flapping', 'Flapping'),
+ sprintf(
+ 'Currently flapping with a %.2f%% state change rate',
+ $object->percent_state_change
+ )
+ );
+}
+
diff --git a/modules/monitoring/application/views/scripts/show/components/grapher.phtml b/modules/monitoring/application/views/scripts/show/components/grapher.phtml
new file mode 100644
index 0000000..0b49e63
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/grapher.phtml
@@ -0,0 +1,6 @@
+<?php if (isset($graphers)) {
+ foreach ($graphers as $grapher) {
+ echo $grapher->getPreviewHtml($object);
+ }
+} ?>
+
diff --git a/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml b/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml
new file mode 100644
index 0000000..377b56f
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/hostgroups.phtml
@@ -0,0 +1,19 @@
+<?php
+
+if (empty($object->hostgroups)) return;
+
+$list = array();
+foreach ($object->hostgroups as $name => $alias) {
+ $list[] = $this->qlink(
+ $alias,
+ 'monitoring/list/hosts',
+ array('hostgroup_name' => $name),
+ array('title' => sprintf($this->translate('List all hosts in the group "%s"'), $alias))
+ );
+}
+printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Hostgroups'),
+ implode(', ', $list)
+);
+
diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml
new file mode 100644
index 0000000..c868c95
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml
@@ -0,0 +1,48 @@
+<?php
+
+use Icinga\Web\Navigation\Navigation;
+
+/** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+
+$navigation = new Navigation();
+$notes = trim($object->notes);
+
+$links = $object->getNotesUrls();
+if (! empty($links)) {
+ foreach ($links as $link) {
+ $navigation->addItem(
+ // add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
+ $this->icon(
+ 'forward',
+ $this->translate('Link opens in new window'),
+ array('aria-label' => $this->translate('Link opens in new window'))
+ ) . ' ' . $this->escape($link),
+ array(
+ 'url' => $link,
+ 'target' => '_blank',
+ 'renderer' => array(
+ 'NavigationItemRenderer',
+ 'escape_label' => false
+ )
+ )
+ );
+ }
+}
+
+if (($navigation->isEmpty() || ! $navigation->hasRenderableItems()) && $notes === '') {
+ return;
+}
+?>
+<tr>
+ <th><?= $this->translate('Notes') ?></th>
+ <td>
+ <?= $navigation->getRenderer() ?>
+ <?php if ($notes !== ''): ?>
+ <?= $this->markdown($notes, [
+ 'id' => $object->type . '-notes',
+ 'class' => 'collapsible',
+ 'data-visible-height' => 200
+ ]) ?>
+ <?php endif ?>
+ </td>
+</tr> \ No newline at end of file
diff --git a/modules/monitoring/application/views/scripts/show/components/notifications.phtml b/modules/monitoring/application/views/scripts/show/components/notifications.phtml
new file mode 100644
index 0000000..3e8c665
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/notifications.phtml
@@ -0,0 +1,68 @@
+<tr>
+ <th><?= $this->translate('Notifications') ?></th>
+ <td>
+ <?php
+ /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */
+ if ($this->hasPermission('monitoring/command/send-custom-notification')) {
+ if ($object->getType() === $object::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ echo $this->qlink(
+ $this->translate('Send notification'),
+ 'monitoring/host/send-custom-notification',
+ array('host' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this host'
+ )
+ )
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ echo $this->qlink(
+ $this->translate('Send notification'),
+ 'monitoring/service/send-custom-notification',
+ array('host' => $object->getHost()->getName(), 'service' => $object->getName()),
+ array(
+ 'class' => 'action-link',
+ 'data-base-target' => '_self',
+ 'icon' => 'bell',
+ 'title' => $this->translate(
+ 'Send a custom notification to contacts responsible for this service'
+ )
+ )
+ );
+ }
+ if (! in_array((int) $object->state, array(0, 99))) {
+ echo '<br>';
+ }
+ } elseif (in_array((int) $object->state, array(0, 99))) {
+ echo '&#45;';
+ }
+ // We are not interested in notifications for OK or pending objects
+ if (! in_array((int) $object->state, array(0, 99))) {
+ if ($object->current_notification_number > 0) {
+ if ((int) $object->current_notification_number === 1) {
+ $msg = sprintf(
+ $this->translate('A notification has been sent for this issue %s.'),
+ $this->timeAgo($object->last_notification)
+ );
+ } else {
+ $msg = sprintf(
+ $this->translate('%d notifications have been sent for this issue.'),
+ $object->current_notification_number
+ ) . '<br>' . sprintf(
+ $this->translate('The last one was sent %s.'),
+ $this->timeAgo($object->last_notification)
+ );
+ }
+ } else {
+ $msg = $this->translate('No notification has been sent for this issue.');
+ }
+ echo $msg;
+ }
+ ?>
+ </td>
+</tr>
diff --git a/modules/monitoring/application/views/scripts/show/components/output.phtml b/modules/monitoring/application/views/scripts/show/components/output.phtml
new file mode 100644
index 0000000..34d8268
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/output.phtml
@@ -0,0 +1,5 @@
+<h2><?= $this->translate('Plugin Output') ?></h2>
+<div id="check-output-<?= $this->escape(str_replace(' ', '-', $object->check_command)) ?>" class="collapsible" data-visible-height="100">
+ <?= $this->pluginOutput($object->output, false, $object->check_command) ?>
+ <?= $this->pluginOutput($object->long_output, false, $object->check_command) ?>
+</div>
diff --git a/modules/monitoring/application/views/scripts/show/components/perfdata.phtml b/modules/monitoring/application/views/scripts/show/components/perfdata.phtml
new file mode 100644
index 0000000..78ea6d2
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/perfdata.phtml
@@ -0,0 +1,4 @@
+<?php if ($object->perfdata): ?>
+<h2><?= $this->translate('Performance data') ?></h2>
+<div id="check-perfdata-<?= $this->escape(str_replace(' ', '-', $object->check_command)) ?>"><?= $this->perfdata($object->perfdata) ?></div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/reachable.phtml b/modules/monitoring/application/views/scripts/show/components/reachable.phtml
new file mode 100644
index 0000000..8d55e84
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/reachable.phtml
@@ -0,0 +1,15 @@
+<?php if ($object->is_reachable !== null): ?>
+<tr>
+ <th>
+ <?= $this->translate('Reachable') ?>
+ </th>
+ <td>
+ <span class="check-source-meta"><?= (bool) $object->is_reachable ? $this->translate('yes') : $this->translate('no') ?></span>
+ <?php if ((bool) $object->is_reachable) {
+ echo $this->icon('circle', $this->translate('Is reachable'), array('class' => 'icon-stateful state-ok'));
+ } else {
+ echo $this->icon('circle', $this->translate('Not reachable'), array('class' => 'icon-stateful state-critical'));
+ } ?>
+ </td>
+</tr>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml b/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml
new file mode 100644
index 0000000..09ff248
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/servicegroups.phtml
@@ -0,0 +1,20 @@
+<?php
+
+if (empty($object->servicegroups)) return;
+
+$list = array();
+foreach ($object->servicegroups as $name => $alias) {
+ $list[] = $this->qlink(
+ $alias,
+ 'monitoring/list/services',
+ array('servicegroup_name' => $name),
+ array('title' => sprintf($this->translate('List all services in the group "%s"'), $alias))
+ );
+}
+
+printf(
+ "<tr><th>%s</th><td class=\"go-ahead\">%s</td></tr>\n",
+ $this->translate('Servicegroups'),
+ implode(', ', $list)
+);
+
diff --git a/modules/monitoring/application/views/scripts/show/components/status.phtml b/modules/monitoring/application/views/scripts/show/components/status.phtml
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/components/status.phtml
diff --git a/modules/monitoring/application/views/scripts/show/contact.phtml b/modules/monitoring/application/views/scripts/show/contact.phtml
new file mode 100644
index 0000000..aa448ae
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/show/contact.phtml
@@ -0,0 +1,70 @@
+<?php $contactHelper = $this->getHelper('ContactFlags') ?>
+<div class="controls">
+ <?php if (! $this->compact): ?>
+ <?= $this->tabs; ?>
+ <?php endif ?>
+ <h1><?= $this->translate('Contact details') ?></h1>
+ <div class="circular" style="background-image: url('<?=
+ $this->href('static/gravatar', array('email' => $contact->contact_email))
+ ?>';width:120px;height:120px;)"></div>
+
+<?php if (! $contact): ?>
+ <?= $this->translate('No such contact') ?>: <?= $contactName ?>
+</div>
+<?php return; endif ?>
+
+ <table class="name-value-table">
+ <tbody>
+ <tr>
+ <th style="width: 20%"></th>
+ <td><strong><?= $this->escape($contact->contact_alias) ?></strong> (<?= $contact->contact_name ?>)</td>
+ </tr>
+<?php if ($contact->contact_email): ?>
+ <tr>
+ <th><?= $this->translate('Email') ?></th>
+ <td>
+ <a href="mailto:<?= $contact->contact_email; ?>" title="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias); ?>" aria-label="<?= sprintf($this->translate('Send a mail to %s'), $contact->contact_alias); ?>">
+ <?= $this->escape($contact->contact_email); ?>
+ </a>
+ </td>
+ </tr>
+<?php endif ?>
+<?php if ($contact->contact_pager): ?>
+ <tr>
+ <th><?= $this->translate('Pager') ?></th>
+ <td><?= $this->escape($contact->contact_pager) ?></td>
+ </tr>
+<?php endif ?>
+ <tr>
+ <th><?= $this->translate('Hosts') ?></th>
+ <td><?= $this->escape($contactHelper->contactFlags($contact, 'host')) ?><br />
+ <?= $this->escape($contact->contact_notify_host_timeperiod) ?></td>
+ </tr>
+ <tr>
+ <th><?= $this->translate('Services') ?></th>
+ <td><?= $this->escape($contactHelper->contactFlags($contact, 'service')) ?><br />
+ <?= $this->escape($contact->contact_notify_service_timeperiod) ?></td>
+ </tr>
+ </tbody>
+ </table>
+ <?php if (count($commands)): ?>
+ <h1><?= $this->translate('Commands') ?>:</h1>
+ <ul>
+ <?php foreach ($commands as $command): ?>
+ <li><?= $command->command_name ?></li>
+ <?php endforeach ?>
+ </ul>
+ <?php endif ?>
+ <h1><?= $this->translate('Notifications sent to this contact') ?></h1>
+ <?= $this->limiter; ?>
+ <?= $this->paginator; ?>
+</div>
+
+<?php if (count($notifications)): ?>
+<?= $this->partial('list/notifications.phtml', array(
+ 'notifications' => $notifications,
+ 'compact' => true
+)); ?>
+<?php else: ?>
+<div class="content"><?= $this->translate('No notifications have been sent for this contact') ?></div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml b/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml
new file mode 100644
index 0000000..e6dc0be
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/hostservicechecks.phtml
@@ -0,0 +1,131 @@
+<div class="box hostservicechecks col-1-2">
+ <div class="box header">
+ <h2><?= $this->translate('Host and Service Checks'); ?></h2>
+ </div>
+ <div class="box contents">
+ <table>
+ <thead>
+ <tr>
+ <th><?= $this->translate('Hosts'); ?></th>
+ <th><?= $this->translate('Services'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+<?php if ($this->statusSummary->hosts_active): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Active', '%u Active', $this->statusSummary->hosts_active),
+ $this->statusSummary->hosts_active
+ ),
+ 'monitoring/list/hosts',
+ array('host_active_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u actively checked host',
+ 'List %u actively checked hosts',
+ $this->statusSummary->hosts_active
+ ),
+ $this->statusSummary->hosts_active
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->hosts_passive): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Passive', '%d Passive', $this->statusSummary->hosts_passive),
+ $this->statusSummary->hosts_passive
+ ),
+ 'monitoring/list/hosts',
+ array('host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u passively checked host',
+ 'List %u passively checked hosts',
+ $this->statusSummary->hosts_passive
+ ),
+ $this->statusSummary->hosts_passive
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->hosts_not_checked): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Disabled', '%d Disabled', $this->statusSummary->hosts_not_checked),
+ $this->statusSummary->hosts_not_checked
+ ),
+ 'monitoring/list/hosts',
+ array('host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 0),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is not being checked at all',
+ 'List %u hosts which are not being checked at all',
+ $this->statusSummary->hosts_not_checked
+ ),
+ $this->statusSummary->hosts_not_checked
+ ))
+ ); ?></div>
+<?php endif ?>
+ </td>
+ <td>
+<?php if ($this->statusSummary->services_active): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Active', '%d Active', $this->statusSummary->services_active),
+ $this->statusSummary->services_active
+ ),
+ 'monitoring/list/services',
+ array('service_active_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u actively checked service',
+ 'List %u actively checked services',
+ $this->statusSummary->services_active
+ ),
+ $this->statusSummary->services_active
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->services_passive): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Passive', '%d Passive', $this->statusSummary->services_passive),
+ $this->statusSummary->services_passive
+ ),
+ 'monitoring/list/services',
+ array('service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u passively checked service',
+ 'List %u passively checked services',
+ $this->statusSummary->services_passive
+ ),
+ $this->statusSummary->services_passive
+ ))
+ ); ?></div>
+<?php endif ?>
+<?php if ($this->statusSummary->services_not_checked): ?>
+ <div class="box entry"><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%d Disabled', '%d Disabled', $this->statusSummary->services_not_checked),
+ $this->statusSummary->services_not_checked
+ ),
+ 'monitoring/list/services',
+ array('service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 0),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is not being checked at all',
+ 'List %u services which are not being checked at all',
+ $this->statusSummary->services_not_checked
+ ),
+ $this->statusSummary->services_not_checked
+ ))
+ ); ?></div>
+<?php endif ?>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml b/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml
new file mode 100644
index 0000000..eeeec16
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/monitoringfeatures.phtml
@@ -0,0 +1,287 @@
+<div class="box monitoringfeatures col-1-2">
+ <div class="box header">
+ <h2><?= $this->translate('Monitoring Features'); ?></h2>
+ </div>
+ <div class="box contents">
+<?php if ($this->statusSummary->hosts_without_flap_detection || $this->statusSummary->services_without_flap_detection ||
+ $this->statusSummary->hosts_flapping || $this->statusSummary->services_flapping): ?>
+ <div class="box-separator badge feature-highlight"><?= $this->translate('Flap Detection'); ?></div>
+<?php else: ?>
+ <div class="box-separator badge"><?= $this->translate('Flap Detection'); ?></div>
+<?php endif ?>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->hosts_without_flap_detection): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Disabled', '%u Hosts Disabled', $this->statusSummary->hosts_without_flap_detection),
+ $this->statusSummary->hosts_without_flap_detection
+ ),
+ 'monitoring/list/hosts',
+ array('host_flap_detection_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host for which flap detection has been disabled',
+ 'List %u hosts for which flap detection has been disabled',
+ $this->statusSummary->hosts_without_flap_detection
+ ),
+ $this->statusSummary->hosts_without_flap_detection
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Hosts Enabled'),
+ 'monitoring/list/hosts',
+ array('host_flap_detection_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all hosts, for which flap detection is enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($this->statusSummary->hosts_flapping): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Flapping', '%u Hosts Flapping', $this->statusSummary->hosts_flapping),
+ $this->statusSummary->hosts_flapping
+ ),
+ 'monitoring/list/hosts',
+ array('host_is_flapping' => 1),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently flapping',
+ 'List %u hosts which are currently flapping',
+ $this->statusSummary->hosts_flapping
+ ),
+ $this->statusSummary->hosts_flapping
+ )
+ )
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->services_without_flap_detection): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Disabled', '%u Services Disabled', $this->statusSummary->services_without_flap_detection),
+ $this->statusSummary->services_without_flap_detection
+ ),
+ 'monitoring/list/services',
+ array('service_flap_detection_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service for which flap detection has been disabled',
+ 'List %u services for which flap detection has been disabled',
+ $this->statusSummary->services_without_flap_detection
+ ),
+ $this->statusSummary->services_without_flap_detection
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Services Enabled'),
+ 'monitoring/list/services',
+ array('service_flap_detection_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all services, for which flap detection is enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($this->statusSummary->services_flapping): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Flapping', '%u Services Flapping', $this->statusSummary->services_flapping),
+ $this->statusSummary->services_flapping
+ ),
+ 'monitoring/list/services',
+ array('service_is_flapping' => 1),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently flapping',
+ 'List %u services which are currently flapping',
+ $this->statusSummary->services_flapping
+ ),
+ $this->statusSummary->services_flapping
+ )
+ )
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<?php if ($this->statusSummary->hosts_not_triggering_notifications || $this->statusSummary->services_not_triggering_notifications): ?>
+ <div class="box-separator badge feature-highlight"><?= $this->translate('Notifications'); ?></div>
+<?php else: ?>
+ <div class="box-separator badge"><?= $this->translate('Notifications'); ?></div>
+<?php endif ?>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->hosts_not_triggering_notifications): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Disabled', '%u Hosts Disabled', $this->statusSummary->hosts_not_triggering_notifications),
+ $this->statusSummary->hosts_not_triggering_notifications
+ ),
+ 'monitoring/list/hosts',
+ array('host_notifications_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host for which notifications are suppressed',
+ 'List %u hosts for which notifications are suppressed',
+ $this->statusSummary->hosts_not_triggering_notifications
+ ),
+ $this->statusSummary->hosts_not_triggering_notifications
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Hosts Enabled'),
+ 'monitoring/list/hosts',
+ array('host_notifications_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all hosts, for which notifications are enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->services_not_triggering_notifications): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Disabled', '%u Services Disabled', $this->statusSummary->services_not_triggering_notifications),
+ $this->statusSummary->services_not_triggering_notifications
+ ),
+ 'monitoring/list/services',
+ array('service_notifications_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service for which notifications are suppressed',
+ 'List %u services for which notifications are suppressed',
+ $this->statusSummary->services_not_triggering_notifications
+ ),
+ $this->statusSummary->services_not_triggering_notifications
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Services Enabled'),
+ 'monitoring/list/services',
+ array('service_notifications_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all services, for which notifications are enabled entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+<?php if ($this->statusSummary->hosts_not_processing_event_handlers || $this->statusSummary->services_not_processing_event_handlers): ?>
+ <div class="box-separator badge feature-highlight"><?= $this->translate('Event Handlers'); ?></div>
+<?php else: ?>
+ <div class="box-separator badge"><?= $this->translate('Event Handlers'); ?></div>
+<?php endif ?>
+ <table>
+ <tbody>
+ <tr>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->hosts_not_processing_event_handlers): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host Disabled', '%u Hosts Disabled', $this->statusSummary->hosts_not_processing_event_handlers),
+ $this->statusSummary->hosts_not_processing_event_handlers
+ ),
+ 'monitoring/list/hosts',
+ array('host_event_handler_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is not processing any event handlers',
+ 'List %u hosts which are not processing any event handlers',
+ $this->statusSummary->hosts_not_processing_event_handlers
+ ),
+ $this->statusSummary->hosts_not_processing_event_handlers
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Hosts Enabled'),
+ 'monitoring/list/hosts',
+ array('host_event_handler_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all hosts, which are processing event handlers entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ <td>
+ <div class="box entry">
+<?php if ($this->statusSummary->services_not_processing_event_handlers): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Service Disabled', '%u Services Disabled', $this->statusSummary->services_not_processing_event_handlers),
+ $this->statusSummary->services_not_processing_event_handlers
+ ),
+ 'monitoring/list/services',
+ array('service_event_handler_enabled' => 0),
+ array(
+ 'class' => 'feature-highlight',
+ 'title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is not processing any event handlers',
+ 'List %u services which are not processing any event handlers',
+ $this->statusSummary->services_not_processing_event_handlers
+ ),
+ $this->statusSummary->services_not_processing_event_handlers
+ )
+ )
+ ); ?>
+<?php else: ?>
+ <?= $this->qlink(
+ $this->translate('All Services Enabled'),
+ 'monitoring/list/services',
+ array('service_event_handler_enabled' => 1),
+ array('title' => $this->translate(
+ 'List all services, which are processing event handlers entirely'
+ ))
+ ); ?>
+<?php endif ?>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml b/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml
new file mode 100644
index 0000000..05ffd29
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/ok_hosts.phtml
@@ -0,0 +1,81 @@
+<?php
+$service_problems = (
+ $this->statusSummary->services_warning_handled_on_ok_hosts ||
+ $this->statusSummary->services_warning_unhandled_on_ok_hosts ||
+ $this->statusSummary->services_critical_handled_on_ok_hosts ||
+ $this->statusSummary->services_critical_unhandled_on_ok_hosts ||
+ $this->statusSummary->services_unknown_handled_on_ok_hosts ||
+ $this->statusSummary->services_unknown_unhandled_on_ok_hosts
+);
+?>
+<div class="box ok_hosts state_<?= $this->statusSummary->hosts_up ? 'up' : 'pending'; ?> col-1-2">
+ <div class="box header">
+ <?php if ($this->statusSummary->hosts_up): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host UP', '%u Hosts UP', $this->statusSummary->hosts_up),
+ $this->statusSummary->hosts_up
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 0),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UP',
+ 'List %u hosts which are currently in state UP',
+ $this->statusSummary->hosts_up
+ ),
+ $this->statusSummary->hosts_up
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ <?php if ($this->statusSummary->hosts_pending): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host PENDING', '%u Hosts PENDING', $this->statusSummary->hosts_pending),
+ $this->statusSummary->hosts_pending
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 99),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state PENDING',
+ 'List %u hosts which are currently in state PENDING',
+ $this->statusSummary->hosts_pending
+ ),
+ $this->statusSummary->hosts_pending
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ </div>
+<?php if ($service_problems || $this->statusSummary->hosts_down || $this->statusSummary->hosts_unreachable): ?>
+ <div class="box contents">
+ <?= $this->partial(
+ 'tactical/components/parts/servicestatesummarybyhoststate.phtml',
+ array(
+ 'translationDomain' => $this->translationDomain,
+ 'host_problem' => 0,
+ 'services_ok' => $this->statusSummary->services_ok_on_ok_hosts,
+ 'services_ok_not_checked' => $this->statusSummary->services_ok_not_checked_on_ok_hosts,
+ 'services_pending' => $this->statusSummary->services_pending_on_ok_hosts,
+ 'services_pending_not_checked' => $this->statusSummary->services_pending_not_checked_on_ok_hosts,
+ 'services_warning_handled' => $this->statusSummary->services_warning_handled_on_ok_hosts,
+ 'services_warning_unhandled' => $this->statusSummary->services_warning_unhandled_on_ok_hosts,
+ 'services_warning_passive' => $this->statusSummary->services_warning_passive_on_ok_hosts,
+ 'services_warning_not_checked' => $this->statusSummary->services_warning_not_checked_on_ok_hosts,
+ 'services_critical_handled' => $this->statusSummary->services_critical_handled_on_ok_hosts,
+ 'services_critical_unhandled' => $this->statusSummary->services_critical_unhandled_on_ok_hosts,
+ 'services_critical_passive' => $this->statusSummary->services_critical_passive_on_ok_hosts,
+ 'services_critical_not_checked' => $this->statusSummary->services_critical_not_checked_on_ok_hosts,
+ 'services_unknown_handled' => $this->statusSummary->services_unknown_handled_on_ok_hosts,
+ 'services_unknown_unhandled' => $this->statusSummary->services_unknown_unhandled_on_ok_hosts,
+ 'services_unknown_passive' => $this->statusSummary->services_unknown_passive_on_ok_hosts,
+ 'services_unknown_not_checked' => $this->statusSummary->services_unknown_not_checked_on_ok_hosts
+ )
+ ); ?>
+<?php else: ?>
+ <div class="box contents zero">
+ <h3>0</h3>
+ <span><?= $this->translate('Service Problems'); ?></span>
+<?php endif ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml b/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml
new file mode 100644
index 0000000..4f32daf
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/parts/servicestatesummarybyhoststate.phtml
@@ -0,0 +1,394 @@
+<?php
+
+use Icinga\Module\Monitoring\Object\Service;
+
+?>
+<?php if ($services_critical_handled || $services_critical_unhandled): ?>
+<div class="box badge entry state-<?= Service::getStateText(2); ?> <?= $services_critical_unhandled ? '' : 'handled'; ?>">
+<?php if ($services_critical_unhandled): ?>
+ <?= $this->qlink(
+ $services_critical_unhandled . ' ' . Service::getStateText(2, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 2,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL',
+ 'List %u services which are currently in state CRITICAL',
+ $services_critical_unhandled
+ ),
+ $services_critical_unhandled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_critical_handled): ?>
+ <?= $this->qlink(
+ $services_critical_handled . ' ' . (
+ $services_critical_unhandled ? $this->translate('Handled') : Service::getStateText(2, true)
+ ),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 2,
+ 'service_handled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL (Handled)',
+ 'List %u services which are currently in state CRITICAL (Handled)',
+ $services_critical_handled
+ ),
+ $services_critical_handled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_critical_passive): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is passively checked',
+ '%u are passively checked',
+ $services_critical_passive
+ ),
+ $services_critical_passive
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 2,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL and passively checked',
+ 'List %u services which are currently in state CRITICAL and passively checked',
+ $services_critical_passive
+ ),
+ $services_critical_passive
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_critical_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_critical_not_checked
+ ),
+ $services_critical_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 2,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state CRITICAL and not checked at all',
+ 'List %u services which are currently in state CRITICAL and not checked at all',
+ $services_critical_not_checked
+ ),
+ $services_critical_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_warning_handled || $services_warning_unhandled): ?>
+<div class="box badge entry state-<?= Service::getStateText(1); ?> <?= $services_warning_unhandled ? '' : 'handled'; ?>">
+<?php if ($services_warning_unhandled): ?>
+ <?= $this->qlink(
+ $services_warning_unhandled . ' ' . Service::getStateText(1, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 1,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING',
+ 'List %u services which are currently in state WARNING',
+ $services_warning_unhandled
+ ),
+ $services_warning_unhandled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_warning_handled): ?>
+ <?= $this->qlink(
+ $services_warning_handled . ' ' . (
+ $services_warning_unhandled ? $this->translate('Handled') : Service::getStateText(1, true)
+ ),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 1,
+ 'service_handled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING (Handled)',
+ 'List %u services which are currently in state WARNING (Handled)',
+ $services_warning_handled
+ ),
+ $services_warning_handled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_warning_passive): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is passively checked',
+ '%u are passively checked',
+ $services_warning_passive
+ ),
+ $services_warning_passive
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 1,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING and passively checked',
+ 'List %u services which are currently in state WARNING and passively checked',
+ $services_warning_passive
+ ),
+ $services_warning_passive
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_warning_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_warning_not_checked
+ ),
+ $services_warning_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 1,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state WARNING and not checked at all',
+ 'List %u services which are currently in state WARNING and not checked at all',
+ $services_warning_not_checked
+ ),
+ $services_warning_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_unknown_handled || $services_unknown_unhandled): ?>
+<div class="box badge entry state-<?= Service::getStateText(3); ?> <?= $services_unknown_unhandled ? '' : 'handled'; ?>">
+<?php if ($services_unknown_unhandled): ?>
+ <?= $this->qlink(
+ $services_unknown_unhandled . ' ' . Service::getStateText(3, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 3,
+ 'service_acknowledged' => 0,
+ 'service_in_downtime' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN',
+ 'List %u services which are currently in state UNKNOWN',
+ $services_unknown_unhandled
+ ),
+ $services_unknown_unhandled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_unknown_handled): ?>
+ <?= $this->qlink(
+ $services_unknown_handled . ' ' . (
+ $services_unknown_unhandled ? $this->translate('Handled') : Service::getStateText(3, true)
+ ),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 3,
+ 'service_handled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN (Handled)',
+ 'List %u services which are currently in state UNKNOWN (Handled)',
+ $services_unknown_handled
+ ),
+ $services_unknown_handled
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_unknown_passive): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is passively checked',
+ '%u are passively checked',
+ $services_unknown_passive
+ ),
+ $services_unknown_passive
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 3,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 1
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN and passively checked',
+ 'List %u services which are currently in state UNKNOWN and passively checked',
+ $services_unknown_passive
+ ),
+ $services_unknown_passive
+ ))
+ ); ?>
+<?php endif ?>
+<?php if ($services_unknown_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_unknown_not_checked
+ ),
+ $services_unknown_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 3,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state UNKNOWN and not checked at all',
+ 'List %u services which are currently in state UNKNOWN and not checked at all',
+ $services_unknown_not_checked
+ ),
+ $services_unknown_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_ok): ?>
+<div class="box badge entry state-<?= Service::getStateText(0); ?>">
+ <?= $this->qlink(
+ $services_ok . ' ' . Service::getStateText(0, true),
+ 'monitoring/list/services',
+ array(
+ 'host_problem' => $host_problem,
+ 'service_state' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state OK',
+ 'List %u services which are currently in state OK',
+ $services_ok
+ ),
+ $services_ok
+ ))
+ ); ?>
+<?php if ($services_ok_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_ok_not_checked
+ ),
+ $services_ok_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 0,
+ 'host_problem' => $host_problem,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state OK and not checked at all',
+ 'List %u services which are currently in state OK and not checked at all',
+ $services_ok_not_checked
+ ),
+ $services_ok_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
+<?php if ($services_pending): ?>
+<div class="box badge entry state-<?= Service::getStateText(99); ?>">
+ <?= $this->qlink(
+ $services_pending . ' ' . Service::getStateText(99, true),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 99
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state PENDING',
+ 'List %u services which are currently in state PENDING',
+ $services_pending
+ ),
+ $services_pending
+ ))
+ ); ?>
+<?php if ($services_pending_not_checked): ?>
+ <?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u is not checked at all',
+ '%u are not checked at all',
+ $services_pending_not_checked
+ ),
+ $services_pending_not_checked
+ ),
+ 'monitoring/list/services',
+ array(
+ 'service_state' => 99,
+ 'service_active_checks_enabled' => 0,
+ 'service_passive_checks_enabled' => 0
+ ),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u service that is currently in state PENDING and not checked at all',
+ 'List %u services which are currently in state PENDING and not checked at all',
+ $services_pending_not_checked
+ ),
+ $services_pending_not_checked
+ ))
+ ); ?>
+<?php endif ?>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml b/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml
new file mode 100644
index 0000000..6374ff8
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/components/problem_hosts.phtml
@@ -0,0 +1,74 @@
+<div class="box problem_hosts <?php
+ echo $this->statusSummary->hosts_down ? 'state_down' : 'state_unreachable';
+ if (!$this->statusSummary->hosts_down_unhandled && !$this->statusSummary->hosts_unreachable_unhandled) {
+ echo ' handled';
+ }
+?> col-1-2">
+ <div class="box header">
+ <?php if ($this->statusSummary->hosts_down): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural('%u Host DOWN', '%u Hosts DOWN', $this->statusSummary->hosts_down),
+ $this->statusSummary->hosts_down
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 1),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state DOWN',
+ 'List %u hosts which are currently in state DOWN',
+ $this->statusSummary->hosts_down
+ ),
+ $this->statusSummary->hosts_down
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ <?php if ($this->statusSummary->hosts_unreachable): ?>
+ <h2><?= $this->qlink(
+ sprintf(
+ $this->translatePlural(
+ '%u Host UNREACHABLE',
+ '%u Hosts UNREACHABLE',
+ $this->statusSummary->hosts_unreachable
+ ),
+ $this->statusSummary->hosts_unreachable
+ ),
+ 'monitoring/list/hosts',
+ array('host_state' => 2),
+ array('title' => sprintf(
+ $this->translatePlural(
+ 'List %u host that is currently in state UNREACHABLE',
+ 'List %u hosts which are currently in state UNREACHABLE',
+ $this->statusSummary->hosts_unreachable
+ ),
+ $this->statusSummary->hosts_unreachable
+ ))
+ ); ?></h2>
+ <?php endif ?>
+ </div>
+ <div class="box contents">
+ <?= $this->partial(
+ 'tactical/components/parts/servicestatesummarybyhoststate.phtml',
+ array(
+ 'translationDomain' => $this->translationDomain,
+ 'host_problem' => 1,
+ 'services_ok' => $this->statusSummary->services_ok_on_problem_hosts,
+ 'services_ok_not_checked' => $this->statusSummary->services_ok_not_checked_on_problem_hosts,
+ 'services_pending' => $this->statusSummary->services_pending_on_problem_hosts,
+ 'services_pending_not_checked' => $this->statusSummary->services_pending_not_checked_on_problem_hosts,
+ 'services_warning_handled' => $this->statusSummary->services_warning_handled_on_problem_hosts,
+ 'services_warning_unhandled' => $this->statusSummary->services_warning_unhandled_on_problem_hosts,
+ 'services_warning_passive' => $this->statusSummary->services_warning_passive_on_problem_hosts,
+ 'services_warning_not_checked' => $this->statusSummary->services_warning_not_checked_on_problem_hosts,
+ 'services_critical_handled' => $this->statusSummary->services_critical_handled_on_problem_hosts,
+ 'services_critical_unhandled' => $this->statusSummary->services_critical_unhandled_on_problem_hosts,
+ 'services_critical_passive' => $this->statusSummary->services_critical_passive_on_problem_hosts,
+ 'services_critical_not_checked' => $this->statusSummary->services_critical_not_checked_on_problem_hosts,
+ 'services_unknown_handled' => $this->statusSummary->services_unknown_handled_on_problem_hosts,
+ 'services_unknown_unhandled' => $this->statusSummary->services_unknown_unhandled_on_problem_hosts,
+ 'services_unknown_passive' => $this->statusSummary->services_unknown_passive_on_problem_hosts,
+ 'services_unknown_not_checked' => $this->statusSummary->services_unknown_not_checked_on_problem_hosts
+ )
+ ); ?>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/tactical/index.phtml b/modules/monitoring/application/views/scripts/tactical/index.phtml
new file mode 100644
index 0000000..12f4bc5
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/tactical/index.phtml
@@ -0,0 +1,145 @@
+<?php
+use Icinga\Data\Filter\Filter;
+?>
+<?php if (! $this->compact): ?>
+<div class="controls">
+ <?= $this->tabs ?>
+ <?= $this->filterEditor ?>
+</div>
+<?php endif ?>
+<div class="content tactical grid">
+<?php if (! count(array_filter((array) $statusSummary))): ?>
+ <p><?= $this->translate('No results found matching the filter.') ?></p>
+</div>
+<?php return; endif ?>
+ <div class="boxview" data-base-target="_next">
+ <div class="donut-container">
+ <h2 aria-label="<?= $this->translate('Host Summary') ?>"><?= $this->translate('Host Summary') ?></h2>
+ <div class="donut">
+ <?= $hostStatusSummaryChart ?>
+ </div>
+ <ul class="donut-legend">
+ <?php if ($statusSummary->hosts_up): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-ok badge"><?= $statusSummary->hosts_up ?></span><?= $this->translate('Up') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_down_handled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 1, 'host_handled' => 1, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical handled badge"><?= $statusSummary->hosts_down_handled ?></span><?= $this->translate('Down') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_down_unhandled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 1, 'host_handled' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical badge"><?= $statusSummary->hosts_down_unhandled ?></span><?= $this->translate('Down') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_unreachable_handled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 2, 'host_handled' => 1, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unreachable handled badge"><?= $statusSummary->hosts_unreachable_handled ?></span><?= $this->translate('Unreachable') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_unreachable_unhandled): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 2, 'host_handled' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unreachable badge"><?= $statusSummary->hosts_unreachable_unhandled ?></span><?= $this->translate('Unreachable') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_pending): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 99, 'sort' => 'host_last_check', 'dir' => 'asc'))->addFilter(Filter::not(Filter::where('host_active_checks_enabled', 0), Filter::where('host_passive_checks_enabled', 0))) ?>">
+ <span class="state state-pending badge"><?= $statusSummary->hosts_pending ?></span><?= $this->translate('Pending') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ <?php if ($statusSummary->hosts_pending_not_checked): ?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/hosts', array('host_state' => 99, 'host_active_checks_enabled' => 0, 'host_passive_checks_enabled' => 0, 'sort' => 'host_last_check', 'dir' => 'asc')) ?>">
+ <span class="state slice-state-not-checked badge"><?= $statusSummary->hosts_pending_not_checked ?></span><?= $this->translate('Not Checked') ?>
+ </a>
+ </li>
+ <?php endif ?>
+ </ul>
+ </div>
+ <div class="donut-container">
+ <h2 aria-label="<?= $this->translate('Service Summary') ?>"><?= $this->translate('Service Summary') ?></h2>
+ <div class="donut">
+ <?= $serviceStatusSummaryChart ?>
+ </div>
+ <ul class="donut-legend">
+ <?php if ($statusSummary->services_ok):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-ok badge"><?= $statusSummary->services_ok ?></span><?= $this->translate('Ok') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_warning_handled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 1, 'service_handled' => 1, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-warning handled badge"><?= $statusSummary->services_warning_handled ?></span><?= $this->translate('Warning') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_warning_unhandled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 1, 'service_handled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-warning badge"><?= $statusSummary->services_warning_unhandled ?></span><?= $this->translate('Warning') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_critical_handled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 2, 'service_handled' => 1, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical handled badge"><?= $statusSummary->services_critical_handled ?></span><?= $this->translate('Critical') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_critical_unhandled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 2, 'service_handled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-critical badge"><?= $statusSummary->services_critical_unhandled ?></span><?= $this->translate('Critical') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_unknown_handled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 3, 'service_handled' => 1, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unknown handled badge"><?= $statusSummary->services_unknown_handled ?></span><?= $this->translate('Unknown') ?> (<?= $this->translate('Handled') ?>)
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_unknown_unhandled):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 3, 'service_handled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state state-unknown badge"><?= $statusSummary->services_unknown_unhandled ?></span><?= $this->translate('Unknown') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_pending):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 99, 'sort' => 'service_last_check', 'dir' => 'asc'))->addFilter(Filter::not(Filter::where('service_active_checks_enabled', 0), Filter::where('service_passive_checks_enabled', 0))) ?>">
+ <span class="state state-pending badge"><?= $statusSummary->services_pending ?></span><?= $this->translate('Pending') ?>
+ </a>
+ </li>
+ <?php endif;
+ if ($statusSummary->services_pending_not_checked):?>
+ <li>
+ <a href="<?= $this->filteredUrl('monitoring/list/services', array('service_state' => 99, 'service_active_checks_enabled' => 0, 'service_passive_checks_enabled' => 0, 'sort' => 'service_last_check', 'dir' => 'asc')) ?>">
+ <span class="state slice-state-not-checked badge"><?= $statusSummary->services_pending_not_checked ?></span><?= $this->translate('Not Checked') ?>
+ </a>
+ </li>
+ <?php endif?>
+ </ul>
+ </div>
+ </div>
+</div>
diff --git a/modules/monitoring/application/views/scripts/timeline/index.phtml b/modules/monitoring/application/views/scripts/timeline/index.phtml
new file mode 100644
index 0000000..af3b406
--- /dev/null
+++ b/modules/monitoring/application/views/scripts/timeline/index.phtml
@@ -0,0 +1,145 @@
+<?php
+use Icinga\Web\Url;
+use Icinga\Util\Color;
+
+$groupInfo = $timeline->getGroupInfo();
+$firstRow = ! $beingExtended;
+
+if (! $beingExtended && !$this->compact): ?>
+<div class="controls">
+ <?= $this->tabs; ?>
+ <div class="dontprint">
+ <?= $intervalBox; ?>
+ </div>
+ <div class="timeline-legend">
+ <h2><?= $this->translate('Legend'); ?></h2>
+<?php foreach ($groupInfo as $labelAndClass): ?>
+ <span class="<?= $labelAndClass['class'] ?>">
+ <span><?= $labelAndClass['label']; ?></span>
+ </span>
+<?php endforeach ?>
+ </div>
+</div>
+<?php endif ?>
+<?php if (! $beingExtended): ?>
+<div class="content" data-base-target="_next">
+ <div class="timeline">
+<?php endif ?>
+<?php if ($switchedContext): ?>
+ <hr>
+<?php endif ?>
+<?php foreach ($timeline as $timeInfo):
+ switch ($intervalBox->getInterval()) {
+ case '1d':
+ $titleTime = sprintf(
+ $this->translate('on %s', 'timeline.link.title.time'),
+ $timeInfo[0]->end->format('d/m/Y')
+ );
+ break;
+ case '1w':
+ $titleTime = sprintf(
+ $this->translate('in week %s of %s', 'timeline.link.title.week.and.year'),
+ $timeInfo[0]->end->format('W'),
+ $timeInfo[0]->end->format('Y')
+ );
+ break;
+ case '1m':
+ $titleTime = sprintf(
+ $this->translate('in %s', 'timeline.link.title.month.and.year'),
+ $timeInfo[0]->end->format('F Y')
+ );
+ break;
+ case '1y':
+ $titleTime = sprintf(
+ $this->translate('in %s', 'timeline.link.title.year'),
+ $timeInfo[0]->end->format('Y')
+ );
+ break;
+ default:
+ $titleTime = sprintf(
+ $this->translate('between %s and %s', 'timeline.link.title.datetime.twice'),
+ $timeInfo[0]->end->format('d/m/Y g:i A'),
+ $timeInfo[0]->start->format('d/m/Y g:i A')
+ );
+ } ?>
+ <div class="timeframe">
+ <span><?= $this->qlink(
+ $timeInfo[0]->end->format($intervalFormat),
+ 'monitoring/list/eventhistory',
+ array(
+ 'timestamp<' => $timeInfo[0]->start->getTimestamp(),
+ 'timestamp>' => $timeInfo[0]->end->getTimestamp()
+ ),
+ array('title' => sprintf(
+ $this->translate('List all event records registered %s', 'timeline.link.title'),
+ $titleTime
+ )),
+ false
+ ); ?></span>
+<?php foreach ($groupInfo as $groupName => $labelAndColor): ?>
+<?php if (array_key_exists($groupName, $timeInfo[1])): ?>
+<?php
+$circleWidth = $timeline->calculateCircleWidth($timeInfo[1][$groupName], 2);
+$extrapolatedCircleWidth = $timeline->getExtrapolatedCircleWidth($timeInfo[1][$groupName], 2);
+?>
+<?php if ($firstRow && $extrapolatedCircleWidth !== $circleWidth): ?>
+ <div class="circle-box" style="width: <?= $extrapolatedCircleWidth; ?>;">
+ <div class="outer-circle extrapolated <?= $timeInfo[1][$groupName]->getClass() ?>" style="<?= sprintf(
+ 'width: %2$s; height: %2$s; margin-top: -%1$Fem;',
+ (float) substr($extrapolatedCircleWidth, 0, -2) / 2,
+ $extrapolatedCircleWidth
+ ); ?>">
+<?php else: ?>
+ <div class="circle-box" style="width: <?= $circleWidth; ?>;">
+ <div class="outer-circle" style="<?= sprintf(
+ 'width: %2$s; height: %2$s; margin-top: -%1$Fem;',
+ (float) substr($circleWidth, 0, -2) / 2,
+ $circleWidth
+ ); ?>">
+<?php endif ?>
+ <?= $this->qlink(
+ '',
+ $timeInfo[1][$groupName]->getDetailUrl(),
+ array(
+ 'type' => $groupName,
+ 'timestamp<' => $timeInfo[0]->start->getTimestamp(),
+ 'timestamp>' => $timeInfo[0]->end->getTimestamp()
+ ),
+ array(
+ 'title' => sprintf(
+ $this->translate('List %u %s registered %s', 'timeline.link.title'),
+ $timeInfo[1][$groupName]->getValue(),
+ strtolower($labelAndColor['label']),
+ $titleTime
+ ),
+ 'class' => 'inner-circle ' . $timeInfo[1][$groupName]->getClass(),
+ 'style' => sprintf(
+ 'width: %2$s; height: %2$s; margin-top: -%1$Fem; margin-left: -%1$Fem;',
+ (float) substr($circleWidth, 0, -2) / 2,
+ (string) $circleWidth
+ )
+ )
+ ); ?>
+ </div>
+ </div>
+<?php endif ?>
+<?php endforeach ?>
+ </div>
+ <?php $firstRow = false; ?>
+<?php endforeach ?>
+ <a aria-hidden="true" id="end" href="<?= Url::fromRequest()->remove(
+ array(
+ 'timestamp<',
+ 'timestamp>'
+ )
+ )->overwriteParams(
+ array(
+ 'start' => $nextRange->getStart()->getTimestamp(),
+ 'end' => $nextRange->getEnd()->getTimestamp(),
+ 'extend' => 1
+ )
+ ); ?>"></a>
+<?php if (!$beingExtended): ?>
+ </div>
+</div>
+<?php endif ?>
diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php
new file mode 100644
index 0000000..663db93
--- /dev/null
+++ b/modules/monitoring/configuration.php
@@ -0,0 +1,431 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+use Icinga\Authentication\Auth;
+
+/** @var $this \Icinga\Application\Modules\Module */
+
+$this->providePermission(
+ 'monitoring/command/*',
+ $this->translate('Allow all commands')
+);
+$this->providePermission(
+ 'monitoring/command/schedule-check',
+ $this->translate('Allow scheduling host and service checks')
+);
+$this->providePermission(
+ 'monitoring/command/schedule-check/active-only',
+ $this->translate('Allow scheduling host and service checks (Only on objects with active checks enabled)')
+);
+$this->providePermission(
+ 'monitoring/command/acknowledge-problem',
+ $this->translate('Allow acknowledging host and service problems')
+);
+$this->providePermission(
+ 'monitoring/command/remove-acknowledgement',
+ $this->translate('Allow removing problem acknowledgements')
+);
+$this->providePermission(
+ 'monitoring/command/comment/*',
+ $this->translate('Allow adding and deleting host and service comments')
+);
+$this->providePermission(
+ 'monitoring/command/comment/add',
+ $this->translate('Allow commenting on hosts and services')
+);
+$this->providePermission(
+ 'monitoring/command/comment/delete',
+ $this->translate('Allow deleting host and service comments')
+);
+$this->providePermission(
+ 'monitoring/command/downtime/*',
+ $this->translate('Allow scheduling and deleting host and service downtimes')
+);
+$this->providePermission(
+ 'monitoring/command/downtime/schedule',
+ $this->translate('Allow scheduling host and service downtimes')
+);
+$this->providePermission(
+ 'monitoring/command/downtime/delete',
+ $this->translate('Allow deleting host and service downtimes')
+);
+$this->providePermission(
+ 'monitoring/command/process-check-result',
+ $this->translate('Allow processing host and service check results')
+);
+$this->providePermission(
+ 'monitoring/command/feature/instance',
+ $this->translate('Allow processing commands for toggling features on an instance-wide basis')
+);
+$this->providePermission(
+ 'monitoring/command/feature/object/*',
+ $this->translate('Allow processing commands for toggling features on host and service objects')
+);
+$this->providePermission(
+ 'monitoring/command/feature/object/active-checks',
+ $this->translate('Allow processing commands for toggling active checks on host and service objects')
+);
+$this->providePermission(
+ 'monitoring/command/feature/object/passive-checks',
+ $this->translate('Allow processing commands for toggling passive checks on host and service objects')
+);
+$this->providePermission(
+ 'monitoring/command/feature/object/notifications',
+ $this->translate('Allow processing commands for toggling notifications on host and service objects')
+);
+$this->providePermission(
+ 'monitoring/command/feature/object/event-handler',
+ $this->translate('Allow processing commands for toggling event handlers on host and service objects')
+);
+$this->providePermission(
+ 'monitoring/command/feature/object/flap-detection',
+ $this->translate('Allow processing commands for toggling flap detection on host and service objects')
+);
+$this->providePermission(
+ 'monitoring/command/send-custom-notification',
+ $this->translate('Allow sending custom notifications for hosts and services')
+);
+$this->providePermission(
+ 'no-monitoring/contacts',
+ $this->translate('Prohibit access to contacts and contactgroups')
+);
+
+$this->provideRestriction(
+ 'monitoring/filter/objects',
+ $this->translate('Restrict views to the Icinga objects that match the filter')
+);
+$this->provideRestriction(
+ 'monitoring/blacklist/properties',
+ $this->translate('Hide the properties of monitored objects that match the filter')
+);
+
+$this->provideConfigTab('backends', array(
+ 'title' => $this->translate('Configure how to retrieve monitoring information'),
+ 'label' => $this->translate('Backends'),
+ 'url' => 'config'
+));
+$this->provideConfigTab('security', array(
+ 'title' => $this->translate('Configure how to protect your monitoring environment against prying eyes'),
+ 'label' => $this->translate('Security'),
+ 'url' => 'config/security'
+));
+$this->provideSetupWizard('Icinga\Module\Monitoring\MonitoringWizard');
+
+/*
+ * Available Search Urls
+ */
+$this->provideSearchUrl($this->translate('Tactical Overview'), 'monitoring/tactical', 100);
+$this->provideSearchUrl($this->translate('Hosts'), 'monitoring/list/hosts?sort=host_severity&limit=10', 99);
+$this->provideSearchUrl($this->translate('Services'), 'monitoring/list/services?sort=service_severity&limit=10', 98);
+$this->provideSearchUrl($this->translate('Hostgroups'), 'monitoring/list/hostgroups?limit=10', 97);
+$this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/servicegroups?limit=10', 96);
+
+/*
+ * Available navigation items
+ */
+$this->provideNavigationItem('host-action', $this->translate('Host Action'));
+$this->provideNavigationItem('service-action', $this->translate('Service Action'));
+// Notes are disabled as we're not sure whether to really make a difference between actions and notes
+//$this->provideNavigationItem('host-note', $this->translate('Host Note'));
+//$this->provideNavigationItem('service-note', $this->translate('Service Note'));
+
+/*
+ * Problems Section
+ */
+$section = $this->menuSection(N_('Problems'), array(
+ 'renderer' => array(
+ 'SummaryNavigationItemRenderer',
+ 'state' => 'critical'
+ ),
+ 'icon' => 'attention-circled',
+ 'priority' => 20
+));
+$section->add(N_('Host Problems'), array(
+ 'icon' => 'host',
+ 'description' => $this->translate('List current host problems'),
+ 'renderer' => array(
+ 'MonitoringBadgeNavigationItemRenderer',
+ 'columns' => array(
+ 'hosts_down_unhandled' => $this->translate('%d unhandled hosts down')
+ ),
+ 'state' => 'critical',
+ 'dataView' => 'unhandledhostproblems'
+ ),
+ 'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity',
+ 'priority' => 50
+));
+$section->add(N_('Service Problems'), array(
+ 'icon' => 'service',
+ 'description' => $this->translate('List current service problems'),
+ 'renderer' => array(
+ 'MonitoringBadgeNavigationItemRenderer',
+ 'columns' => array(
+ 'services_critical_unhandled' => $this->translate('%d unhandled services critical')
+ ),
+ 'state' => 'critical',
+ 'dataView' => 'unhandledserviceproblems'
+ ),
+ 'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc',
+ 'priority' => 60
+));
+$section->add(N_('Service Grid'), array(
+ 'icon' => 'services',
+ 'description' => $this->translate('Display service problems as grid'),
+ 'url' => 'monitoring/list/servicegrid?problems',
+ 'priority' => 70
+));
+$section->add(N_('Current Downtimes'), array(
+ 'icon' => 'plug',
+ 'description' => $this->translate('List current downtimes'),
+ 'url' => 'monitoring/list/downtimes?downtime_is_in_effect=1',
+ 'priority' => 80
+));
+
+/*
+ * Overview Section
+ */
+$section = $this->menuSection(N_('Overview'), array(
+ 'icon' => 'binoculars',
+ 'priority' => 30
+));
+$section->add(N_('Tactical Overview'), array(
+ 'icon' => 'chart-pie',
+ 'description' => $this->translate('Open tactical overview'),
+ 'url' => 'monitoring/tactical',
+ 'priority' => 40
+));
+$section->add(N_('Hosts'), array(
+ 'icon' => 'host',
+ 'description' => $this->translate('List hosts'),
+ 'url' => 'monitoring/list/hosts',
+ 'priority' => 50
+));
+$section->add(N_('Services'), array(
+ 'icon' => 'service',
+ 'description' => $this->translate('List services'),
+ 'url' => 'monitoring/list/services',
+ 'priority' => 50
+));
+$section->add(N_('Servicegroups'), array(
+ 'icon' => 'services',
+ 'description' => $this->translate('List service groups'),
+ 'url' => 'monitoring/list/servicegroups',
+ 'priority' => 60
+));
+$section->add(N_('Hostgroups'), array(
+ 'icon' => 'host',
+ 'description' => $this->translate('List host groups'),
+ 'url' => 'monitoring/list/hostgroups',
+ 'priority' => 60
+));
+
+// Checking the permission here since navigation items don't support negating permissions
+$auth = Auth::getInstance();
+if ($auth->hasPermission('*') || ! $auth->hasPermission('no-monitoring/contacts')) {
+ $section->add(N_('Contacts'), array(
+ 'icon' => 'user',
+ 'description' => $this->translate('List contacts'),
+ 'url' => 'monitoring/list/contacts',
+ 'priority' => 70
+ ));
+ $section->add(N_('Contactgroups'), array(
+ 'icon' => 'users',
+ 'description' => $this->translate('List users'),
+ 'url' => 'monitoring/list/contactgroups',
+ 'priority' => 70
+ ));
+}
+
+$section->add(N_('Comments'), array(
+ 'icon' => 'chat-empty',
+ 'description' => $this->translate('List comments'),
+ 'url' => 'monitoring/list/comments?comment_type=comment|comment_type=ack',
+ 'priority' => 80
+));
+$section->add(N_('Downtimes'), array(
+ 'icon' => 'plug',
+ 'description' => $this->translate('List downtimes'),
+ 'url' => 'monitoring/list/downtimes',
+ 'priority' => 80
+));
+
+/*
+ * History Section
+ */
+$section = $this->menuSection(N_('History'), array(
+ 'icon' => 'history',
+ 'priority' => 90
+));
+$section->add(N_('Event Grid'), array(
+ 'icon' => 'history',
+ 'description' => $this->translate('Open event grid'),
+ 'priority' => 10,
+ 'url' => 'monitoring/list/eventgrid'
+));
+$section->add(N_('Event Overview'), array(
+ 'icon' => 'history',
+ 'description' => $this->translate('Open event overview'),
+ 'priority' => 20,
+ 'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days'
+));
+$section->add(N_('Notifications'), array(
+ 'icon' => 'bell',
+ 'description' => $this->translate('List notifications'),
+ 'priority' => 30,
+ 'url' => 'monitoring/list/notifications?notification_timestamp>=-7%20days',
+));
+$section->add(N_('Timeline'), array(
+ 'icon' => 'clock',
+ 'description' => $this->translate('Open timeline'),
+ 'priority' => 40,
+ 'url' => 'monitoring/timeline'
+));
+
+/*
+ * Reporting Section
+ */
+$section = $this->menuSection(N_('Reporting'), array(
+ 'icon' => 'barchart',
+ 'priority' => 100
+));
+
+/*
+ * Current Incidents
+ */
+$dashboard = $this->dashboard(N_('Current Incidents'), array('priority' => 50));
+$dashboard->add(
+ N_('Service Problems'),
+ 'monitoring/list/services?service_problem=1&limit=10&sort=service_severity',
+ 100
+);
+$dashboard->add(
+ N_('Recently Recovered Services'),
+ 'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc',
+ 110
+);
+$dashboard->add(
+ N_('Host Problems'),
+ 'monitoring/list/hosts?host_problem=1&sort=host_severity',
+ 120
+);
+
+/*
+ * Overview
+ */
+//$dashboard = $this->dashboard(N_('Overview'), array('priority' => 60));
+//$dashboard->add(
+// N_('Service Grid'),
+// 'monitoring/list/servicegrid?limit=15,18'
+//);
+//$dashboard->add(
+// N_('Service Groups'),
+// 'monitoring/list/servicegroups'
+//);
+//$dashboard->add(
+// N_('Host Groups'),
+// 'monitoring/list/hostgroups'
+//);
+
+/*
+ * Most Overdue
+ */
+$dashboard = $this->dashboard(N_('Overdue'), array('priority' => 70));
+$dashboard->add(
+ N_('Late Host Check Results'),
+ 'monitoring/list/hosts?host_next_update<now',
+ 100
+);
+$dashboard->add(
+ N_('Late Service Check Results'),
+ 'monitoring/list/services?service_next_update<now',
+ 110
+);
+$dashboard->add(
+ N_('Acknowledgements Active For At Least Three Days'),
+ 'monitoring/list/comments?comment_type=Ack&comment_timestamp<-3 days&sort=comment_timestamp&dir=asc',
+ 120
+);
+$dashboard->add(
+ N_('Downtimes Active For More Than Three Days'),
+ 'monitoring/list/downtimes?downtime_is_in_effect=1&downtime_scheduled_start<-3%20days&sort=downtime_start&dir=asc',
+ 130
+);
+
+/*
+ * Muted Objects
+ */
+$dashboard = $this->dashboard(N_('Muted'), array('priority' => 80));
+$dashboard->add(
+ N_('Disabled Service Notifications'),
+ 'monitoring/list/services?service_notifications_enabled=0&limit=10',
+ 100
+);
+$dashboard->add(
+ N_('Disabled Host Notifications'),
+ 'monitoring/list/hosts?host_notifications_enabled=0&limit=10',
+ 110
+);
+$dashboard->add(
+ N_('Disabled Service Checks'),
+ 'monitoring/list/services?service_active_checks_enabled=0&limit=10',
+ 120
+);
+$dashboard->add(
+ N_('Disabled Host Checks'),
+ 'monitoring/list/hosts?host_active_checks_enabled=0&limit=10',
+ 130
+);
+$dashboard->add(
+ N_('Acknowledged Problem Services'),
+ 'monitoring/list/services?service_acknowledgement_type!=0&service_problem=1&sort=service_state&limit=10',
+ 140
+);
+$dashboard->add(
+ N_('Acknowledged Problem Hosts'),
+ 'monitoring/list/hosts?host_acknowledgement_type!=0&host_problem=1&sort=host_severity&limit=10',
+ 150
+);
+
+/*
+ * Activity Stream
+ */
+//$dashboard = $this->dashboard(N_('Activity Stream'), array('priority' => 90));
+//$dashboard->add(
+// N_('Recent Events'),
+// 'monitoring/list/eventhistory?timestamp>=-3%20days&sort=timestamp&dir=desc&limit=8'
+//);
+//$dashboard->add(
+// N_('Recent Hard State Changes'),
+// 'monitoring/list/eventhistory?timestamp>=-3%20days&type=hard_state&sort=timestamp&dir=desc&limit=8'
+//);
+//$dashboard->add(
+// N_('Recent Notifications'),
+// 'monitoring/list/eventhistory?timestamp>=-3%20days&type=notify&sort=timestamp&dir=desc&limit=8'
+//);
+//$dashboard->add(
+// N_('Downtimes Recently Started'),
+// 'monitoring/list/eventhistory?timestamp>=-3%20days&type=dt_start&sort=timestamp&dir=desc&limit=8'
+//);
+//$dashboard->add(
+// N_('Downtimes Recently Ended'),
+// 'monitoring/list/eventhistory?timestamp>=-3%20days&type=dt_end&sort=timestamp&dir=desc&limit=8'
+//);
+
+/*
+ * Stats
+ */
+//$dashboard = $this->dashboard(N_('Stats'), array('priority' => 99));
+//$dashboard->add(
+// N_('Check Stats'),
+// 'monitoring/health/stats'
+//);
+//$dashboard->add(
+// N_('Process Information'),
+// 'monitoring/health/info'
+//);
+
+/*
+ * CSS
+ */
+$this->provideCssFile('service-grid.less');
+$this->provideCssFile('tables.less');
diff --git a/modules/monitoring/doc/01-About.md b/modules/monitoring/doc/01-About.md
new file mode 100644
index 0000000..deb47bf
--- /dev/null
+++ b/modules/monitoring/doc/01-About.md
@@ -0,0 +1,10 @@
+# About the Monitoring Module <a id="monitoring-module-about"></a>
+
+Please read the following chapters for more insights on this module:
+
+* [Installation](02-Installation.md#monitoring-module-installation)
+* [Configuration](03-Configuration.md#monitoring-module-configuration)
+* [Security](06-Security.md#monitoring-module-security)
+* [Restrict Custom Variables](10-Restrict-Custom-Variables.md#monitoring-module-restrict-access-custom-variables)
+* [Hooks](20-Hooks.md#monitoring-module-hooks)
+* [Add Columns to List Views](11-Add-Columns-List-Views.md#monitoring-module-add-columns-list-views)
diff --git a/modules/monitoring/doc/02-Installation.md b/modules/monitoring/doc/02-Installation.md
new file mode 100644
index 0000000..43a7cd0
--- /dev/null
+++ b/modules/monitoring/doc/02-Installation.md
@@ -0,0 +1,15 @@
+# Monitoring Module Installation <a id="monitoring-module-installation"></a>
+
+This module is provided with the Icinga Web 2 package and does
+not need any extra installation step.
+
+## Enable the Module <a id="monitoring-module-enable"></a>
+
+Navigate to `Configuration` -> `Modules` -> `monitoring` and enable
+the module.
+
+You can also enable the module during the setup wizard, or on the CLI:
+
+```
+icingacli module enable monitoring
+```
diff --git a/modules/monitoring/doc/03-Configuration.md b/modules/monitoring/doc/03-Configuration.md
new file mode 100644
index 0000000..9adacc1
--- /dev/null
+++ b/modules/monitoring/doc/03-Configuration.md
@@ -0,0 +1,69 @@
+# Monitoring Module Configuration <a id="monitoring-module-configuration"></a>
+
+## Overview <a id="monitoring-module-configuration-overview"></a>
+
+The module specific configuration is stored in `/etc/icingaweb2/modules/monitoring`.
+
+File/Directory | Description
+----------------------------------------------------------------------|---------------------------------
+config.ini | Security settings (e.g. protected custom vars) for the `monitoring` module |
+[backends.ini](04-Backends.md#monitoring-module-backends) | Data backend (e.g. the IDO database [resource](../../../doc/04-Resources.md#resources-configuration-database) name).
+[commandtransports.ini](05-Command-Transports.md) | Command transports for specific Icinga instances
+
+
+## General Configuration <a id="monitoring-module-configuration-general"></a>
+
+Navigate into `Configuration` -> `Modules` -> `Monitoring`. This allows
+you to see the provided [permissions and restrictions](06-Security.md#monitoring-security)
+by this module.
+
+### Default Settings <a id="monitoring-module-configuration-settings"></a>
+
+Option | Description
+----------------------------------|-----------------------------------------------
+acknowledge_expire | **Optional.** Check "Use Expire Time" in Acknowledgement dialog by default. Defaults to **0 (false)**.
+acknowledge_expire_time | **Optional.** Set default value for "Expire Time" in Acknowledgement dialog, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
+acknowledge_notify | **Optional.** Check "Send Notification" in Acknowledgement dialog by default. Defaults to **1 (true)**.
+acknowledge_persistent | **Optional.** Check "Persistent Comment" in Acknowledgement dialog by default. Defaults to **0 (false)**.
+acknowledge_sticky | **Optional.** Check "Sticky Acknowledgement" in Acknowledgement dialog by default. Defaults to **0 (false)**.
+comment_expire | **Optional.** Check "Use Expire Time" in Comment dialog by default. Defaults to **0 (false)**.
+hostdowntime_comment_text | **Optional.** Set default text for "Comment" in Host Downtime dialog by default.
+servicedowntime_comment_text | **Optional.** Set default text for "Comment" in Service Downtime dialog by default.
+comment_expire_time | **Optional.** Set default value for "Expire Time" in Comment dialog, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
+custom_notification_forced | **Optional.** Check "Forced" in Custom Notification dialog by default. Defaults to **0 (false)**.
+hostcheck_all_services | **Optional.** Check "All Services" in Schedule Host Check dialog by default. Defaults to **0 (false)**.
+hostdowntime_all_services | **Optional.** Check "All Services" in Schedule Host Downtime dialog by default. Defaults to **0 (false)**.
+hostdowntime_end_fixed | **Optional.** Set default value for "End Time" in Schedule Host Downtime dialog for **Fixed** downtime, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
+hostdowntime_end_flexible | **Optional.** Set default value for "End Time" in Schedule Host Downtime dialog for **Flexible** downtime, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
+hostdowntime_flexible_duration | **Optional.** Set default value for "Flexible Duration" in Schedule Host Downtime dialog for **Flexible** downtime. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **2 hour (PT2H)**.
+servicedowntime_end_fixed | **Optional.** Set default value for "End Time" in Schedule Service Downtime dialog for **Fixed** downtime, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
+servicedowntime_end_flexible | **Optional.** Set default value for "End Time" in Schedule Service Downtime dialog for **Flexible** downtime, its calculated as now + this setting. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **1 hour (PT1H)**.
+servicedowntime_flexible_duration | **Optional.** Set default value for "Flexible Duration" in Schedule Service Downtime dialog for **Flexible** downtime. Format is a [PHP Dateinterval](http://www.php.net/manual/en/dateinterval.construct.php). Defaults to **2 hour (PT2H)**.
+
+Example for having acknowledgements with 2 hours expire time by default.
+
+```
+# vim /etc/icingaweb2/modules/monitoring/config.ini
+
+[settings]
+acknowledge_expire = 1
+acknowledge_expire_time = PT2H
+
+```
+
+### Security Configuration <a id="monitoring-module-configuration-security"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+protected\_customvars | **Optional.** Comma separated list of string patterns for custom variables which should be excluded from user's view.
+
+
+Example for custom variable names which match `*pw*` or `*pass*` or `community`.
+
+```
+# vim /etc/icingaweb2/modules/monitoring/config.ini
+
+[security]
+protected_customvars = "*pw*,*pass*,community"
+```
+
diff --git a/modules/monitoring/doc/04-Backends.md b/modules/monitoring/doc/04-Backends.md
new file mode 100644
index 0000000..2681109
--- /dev/null
+++ b/modules/monitoring/doc/04-Backends.md
@@ -0,0 +1,30 @@
+# Backends <a id="monitoring-module-backends"></a>
+
+The configuration file `backends.ini` contains information about data sources which are
+used to fetch monitoring objects presented to the user.
+
+The required [resources](../../../doc/04-Resources.md#resources-configuration-database) must be globally defined beforehand.
+
+## Configuration <a id="monitoring-module-backends-configuration"></a>
+
+Navigate into `Configuration` -> `Modules` -> `Monitoring` -> `Backends`.
+You can select a specified global resource here, and also update its details.
+
+Each section in `backends.ini` references a resource. By default you should only have one backend enabled.
+
+### IDO Backend <a id="monitoring-module-backends-ido"></a>
+
+Option | Description
+-------------------------|-----------------------------------------------
+type | **Required.** Specify the backend type. Must be set to `ido`.
+resource | **Required.** Specify a defined [resource](../../../doc/04-Resources.md#resources-configuration-database) name which provides details about the IDO database resource.
+
+
+Example for using the database resource `icinga2_ido_mysql`:
+
+```
+[icinga2_ido_mysql]
+type = "ido"
+resource = "icinga2_ido_mysql"
+```
+
diff --git a/modules/monitoring/doc/05-Command-Transports.md b/modules/monitoring/doc/05-Command-Transports.md
new file mode 100644
index 0000000..bdf9f56
--- /dev/null
+++ b/modules/monitoring/doc/05-Command-Transports.md
@@ -0,0 +1,185 @@
+# External Command Transport Configuration <a id="monitoring-module-commandtransports"></a>
+
+## Configuration <a id="monitoring-module-commandtransports-configuration"></a>
+
+Navigate into `Configuration` -> `Modules` -> `Monitoring` -> `Backends`.
+You can create/edit command transports here.
+
+The `commandtransports.ini` configuration file defines how Icinga Web 2
+transports commands to your Icinga instance in order to submit
+external commands. By default, this file is located at `/etc/icingaweb2/modules/monitoring/commandtransports.ini`.
+
+You can define multiple command transports in the `commandtransports.ini` file. Every transport starts with a section header
+containing its name, followed by the config directives for this transport in the standard INI-format.
+
+Icinga Web 2 will try one transport after another to send a command until the command is successfully sent.
+If [configured](05-Command-Transports.md#commandtransports-multiple-instances), Icinga Web 2 will take different instances into account.
+The order in which Icinga Web 2 processes the configured transports is defined by the order of sections in
+`commandtransports.ini`.
+
+## Use the Icinga 2 API <a id="commandtransports-icinga2-api"></a>
+
+If you're running Icinga 2 it's best to use the [Icinga 2 API](https://icinga.com/docs/icinga2/latest/doc/12-icinga2-api/)
+for transmitting external commands.
+
+### Icinga 2 Preparations <a id="commandtransports-icinga2-api-preparations"></a>
+
+You have to run the `api` setup on the Icinga 2 host where you want to send the commands to:
+
+```
+icinga2 api setup
+```
+
+Next, you have to create an ApiUser object for authenticating against the Icinga 2 API. This configuration also applies
+to the host where you want to send the commands to. We recommend to create/edit the file
+`/etc/icinga2/conf.d/api-users.conf`:
+
+```
+object ApiUser "icingaweb2" {
+ password = "bea11beb7b810ea9ce6ea" // Change this!
+ permissions = [ "status/query", "actions/*", "objects/modify/*", "objects/query/*" ]
+}
+```
+
+The permissions are mandatory in order to submit all external commands from within Icinga Web 2.
+
+**Restart Icinga 2** for the changes to take effect.
+
+```
+systemctl restart icinga2
+```
+
+### Configuration in Icinga Web 2 <a id="commandtransports-icinga2-api-configuration"></a>
+
+> **Note**
+>
+> Please make sure that your server running Icinga Web 2 has the `PHP cURL` extension installed and enabled.
+
+The Icinga 2 API requires the following settings:
+
+Option | Description
+-------------------------|-----------------------------------------------
+transport | **Required.** The transport type. Must be set to `api`.
+host | **Required.** The host address where the Icinga 2 API is listening on.
+port | **Required.** The port where the Icinga 2 API is listening on. Defaults to `5665`.
+username | **Required.** Basic auth username.
+password | **Required.** Basic auth password.
+
+Example:
+
+```
+# vim /etc/icingaweb2/modules/monitoring/commandtransports.ini
+
+[icinga2]
+transport = "api"
+host = "127.0.0.1" ; Icinga 2 host
+port = "5665"
+username = "icingaweb2"
+password = "bea11beb7b810ea9ce6ea" ; Change this!
+```
+
+## Use a Local Command Pipe <a id="commandtransports-local-command-pipe"></a>
+
+A local Icinga instance requires the following settings:
+
+Option | Description
+-------------------------|-----------------------------------------------
+transport | **Required.** The transport type. Must be set to `local`.
+path | **Required.** The absolute path to the local command pipe.
+
+Example:
+
+```
+# vim /etc/icingaweb2/modules/monitoring/commandtransports.ini
+
+[icinga2]
+transport = local
+path = /var/run/icinga2/cmd/icinga2.cmd
+```
+
+When commands are being sent to the Icinga instance, Icinga Web 2 opens the file found
+on the local filesystem underneath `path` and writes the external command to it.
+
+Please note that errors are not returned using this method. The Icinga 2 API sends
+error feedback.
+
+## Use SSH For a Remote Command Pipe <a id="commandtransports-ssh-remote-command-pipe"></a>
+
+A command pipe on a remote host's filesystem can be accessed by configuring a
+SSH based command transport and requires the following settings:
+
+Option | Description
+-------------------------|-----------------------------------------------
+transport | **Required.** The transport type. Must be set to `remote`.
+path | **Required.** The path on the remote server to its local command pipe.
+host | **Required.** The SSH host.
+port | **Optional.** The SSH port. Defaults to `22`.
+user | **Required.** The SSH auth user.
+resource | **Optional.** The SSH [resource](../../../doc/04-Resources.md#resources-configuration-ssh)
+instance | **Optional.** The Icinga instance name. Only required for multiple instances.
+
+Example:
+
+```
+# vim /etc/icingaweb2/modules/monitoring/commandtransports.ini
+
+[icinga2]
+transport = remote
+path = /var/run/icinga2/cmd/icinga2.cmd
+host = example.tld
+user = icinga
+;port = 22 ; Optional. The default is 22
+```
+
+To make this example work, you'll need to permit your web-server's user
+public-key based access to the defined remote host so that Icinga Web 2 can
+connect to it and login as the defined user.
+
+You can also make use of a dedicated SSH resource to permit access for a
+different user than the web-server's one. This way, you can provide a private
+key file on the local filesystem that is used to access the remote host.
+
+To accomplish this, a new resource is required that is defined in your
+transport's configuration instead of a user:
+
+```
+# vim /etc/icingaweb2/modules/monitoring/commandtransports.ini
+
+[icinga2]
+transport = remote
+path = /var/run/icinga2/cmd/icinga2.cmd
+host = example.tld
+resource = example.tld-icinga2
+;port = 22 ; Optional. The default is 22
+```
+
+The resource's configuration needs to be put into the resources.ini file:
+
+```
+# vim /etc/icingaweb2/resources.ini
+
+[example.tld-icinga2]
+type = ssh
+user = icinga
+private_key = /etc/icingaweb2/ssh/icinga
+```
+
+## Configure Transports for Different Icinga Instances <a id="commandtransports-multiple-instances"></a>
+
+If there are multiple but different Icinga instances writing to your IDO database,
+you can define which transport belongs to which Icinga instance by providing the
+`instance` setting. This setting must specify the name of the Icinga
+instance you want to assign to the transport:
+
+```
+[icinga1]
+...
+instance = icinga1
+
+[icinga2]
+...
+instance = icinga2
+```
+
+Associating a transport to a specific Icinga instance causes this transport to be used to send commands to the linked
+instance only. Transports without a linked Icinga instance are used to send commands to all instances.
diff --git a/modules/monitoring/doc/06-Security.md b/modules/monitoring/doc/06-Security.md
new file mode 100644
index 0000000..750eaef
--- /dev/null
+++ b/modules/monitoring/doc/06-Security.md
@@ -0,0 +1,66 @@
+# Security <a id="monitoring-module-security"></a>
+
+The monitoring module provides an additional set of restrictions and permissions
+that can be used for access control. The following sections will list those
+restrictions and permissions in detail:
+
+
+## Permissions <a id="monitoring-module-security-permissions"></a>
+
+The monitoring module allows to send commands to an Icinga 2 instance.
+A user needs specific permissions to be able to send those commands
+when using the monitoring module.
+
+
+Name | Permits
+-------------------------------------------------|-----------------------------------------------
+monitoring/command/* | Allow all commands.
+monitoring/command/schedule-check | Allow scheduling host and service checks.
+monitoring/command/schedule-check/active-only | Allow scheduling host and service checks. (Only on objects with active checks enabled)
+monitoring/command/acknowledge-problem | Allow acknowledging host and service problems.
+monitoring/command/remove-acknowledgement | Allow removing problem acknowledgements.
+monitoring/command/comment/* | Allow adding and deleting host and service comments.
+monitoring/command/comment/add | Allow commenting on hosts and services.
+monitoring/command/comment/delete | Allow deleting host and service comments.
+monitoring/command/downtime/* | Allow scheduling and deleting host and service downtimes.
+monitoring/command/downtime/schedule | Allow scheduling host and service downtimes.
+monitoring/command/downtime/delete | Allow deleting host and service downtimes.
+monitoring/command/process-check-result | Allow processing host and service check results.
+monitoring/command/feature/instance | Allow processing commands for toggling features on an instance-wide basis.
+monitoring/command/feature/object/* | Allow processing commands for toggling features on host and service objects.
+monitoring/command/feature/object/active-checks | Allow processing commands for toggling active checks on host and service objects.
+monitoring/command/feature/object/passive-checks | Allow processing commands for toggling passive checks on host and service objects.
+monitoring/command/feature/object/notifications | Allow processing commands for toggling notifications on host and service objects.
+monitoring/command/feature/object/event-handler | Allow processing commands for toggling event handlers on host and service objects.
+monitoring/command/feature/object/flap-detection | Allow processing commands for toggling flap detection on host and service objects.
+monitoring/command/send-custom-notification | Allow sending custom notifications for hosts and services.
+
+
+## Restrictions <a id="monitoring-module-security-restrictions"></a>
+
+The monitoring module allows filtering objects:
+
+
+Keys | Restricts
+--------------------------------------------|-----------------------------------------------
+monitoring/filter/objects | Applies a filter to all hosts and services.
+
+
+This filter will affect all hosts and services. Furthermore, it will also
+affect all related objects, like notifications, downtimes and events. If a
+service is hidden, all notifications, downtimes on that service will be hidden too.
+
+
+### Filter Column Names <a id="monitoring-module-security-restrictions-filter-column-names"></a>
+
+The following filter column names are available in filter expressions:
+
+
+Column | Description
+-----------------------------------------------------------|-----------------------------------------------
+instance\_name | Filter on an Icinga 2 instance.
+host\_name | Filter on host object names.
+hostgroup\_name | Filter on hostgroup object names.
+service\_description | Filter on service object names.
+servicegroup\_name | Filter on servicegroup object names.
+all custom variables prefixed with `_host_` or `_service_` | Filter on specified custom variables.
diff --git a/modules/monitoring/doc/10-Restrict-Custom-Variables.md b/modules/monitoring/doc/10-Restrict-Custom-Variables.md
new file mode 100644
index 0000000..8d3a3b1
--- /dev/null
+++ b/modules/monitoring/doc/10-Restrict-Custom-Variables.md
@@ -0,0 +1,77 @@
+# Restrict Access to Custom Variables <a id="monitoring-module-restrict-access-custom-variables"></a>
+
+* Restriction name: monitoring/blacklist/properties
+* Restriction value: Comma separated list of GLOB like filters
+
+Imagine the following host custom variable structure.
+
+```
+host.vars.
+|-- cmdb_name
+|-- cmdb_id
+|-- cmdb_location
+|-- wiki_id
+|-- passwords.
+| |-- mysql_password
+| |-- ldap_password
+| `-- mongodb_password
+|-- legacy.
+| |-- cmdb_name
+| |-- mysql_password
+| `-- wiki_id
+`-- backup.
+ `-- passwords.
+ |-- mysql_password
+ `-- ldap_password
+```
+
+`host.vars.cmdb_name`
+
+Blacklists `cmdb_name` in the first level of the custom variable structure only.
+`host.vars.legacy.cmdb_name` is not blacklisted.
+
+
+`host.vars.cmdb_*`
+
+All custom variables in the first level of the structure which begin with `cmdb_` become blacklisted.
+Deeper custom variables are ignored. `host.vars.legacy.cmdb_name` is not blacklisted.
+
+`host.vars.*id`
+
+All custom variables in the first level of the structure which end with `id` become blacklisted.
+Deeper custom variables are ignored. `host.vars.legacy.wiki_id` is not blacklisted.
+
+`host.vars.*.mysql_password`
+
+Matches all custom variables on the second level which are equal to `mysql_password`.
+
+`host.vars.*.*password`
+
+Matches all custom variables on the second level which end with `password`.
+
+`host.vars.*.mysql_password,host.vars.*.ldap_password`
+
+Matches all custorm variables on the second level which equal `mysql_password` or `ldap_password`.
+
+`host.vars.**.*password`
+
+Matches all custom variables on all levels which end with `password`.
+
+Please note the two asterisks, `**`, here for crossing level boundaries. This syntax is used for matching the complete
+custom variable structure.
+
+If you want to restrict all custom variables that end with password for both hosts and services, you have to define
+the following restriction.
+
+`host.vars.**.*password,service.vars.**.*password`
+
+## Escape Meta Characters <a id="restrict-access-custom-variables-escape-meta-chars"></a>
+
+Use backslash to escape the meta characters
+
+* *
+* ,
+
+`host.vars.\*fall`
+
+Matches all custom variables in the first level which equal `*fall`.
diff --git a/modules/monitoring/doc/11-Add-Columns-List-Views.md b/modules/monitoring/doc/11-Add-Columns-List-Views.md
new file mode 100644
index 0000000..2567ead
--- /dev/null
+++ b/modules/monitoring/doc/11-Add-Columns-List-Views.md
@@ -0,0 +1,32 @@
+# Add Columns to List Views <a id="monitoring-module-add-columns-list-views"></a>
+
+The monitoring module provides list views for hosts and services.
+These lists only provide the most common columns to reduce the backend
+query load.
+
+If you want to add more columns to the list view e.g. in order to use the URL in
+your dashboards or as external iframe integration, you need the `addColumns` URL
+parameter.
+
+
+
+Example for adding the host `address` attribute in a host list:
+
+```
+http://localhost/icingaweb2/monitoring/list/hosts?addColumns=host_address
+```
+
+![Screenshot](img/list_hosts_add_columns.png)
+
+
+
+
+Example for multiple columns as comma separated parameter string. This
+includes a reference to the Icinga 2 host object custom attribute `os` using
+`_host_` as custom variable identifier.
+
+```
+http://localhost/icingaweb2/monitoring/list/services?addColumns=host_address,_host_os
+```
+
+![Screenshot](img/list_services_add_columns.png)
diff --git a/modules/monitoring/doc/20-Hooks.md b/modules/monitoring/doc/20-Hooks.md
new file mode 100644
index 0000000..5d38843
--- /dev/null
+++ b/modules/monitoring/doc/20-Hooks.md
@@ -0,0 +1,161 @@
+# Monitoring Module Hooks <a id="monitoring-module-hooks"></a>
+
+## Detail View Extension Hook <a id="monitoring-module-hooks-detailviewextension"></a>
+
+This hook can be used to easily extend the detail view of monitored objects (hosts and services).
+
+### How it works <a id="monitoring-module-hooks-detailviewextension-how-it-works"></a>
+
+#### Directory structure <a id="monitoring-module-hooks-detailviewextension-directory-structure"></a>
+
+* `icingaweb2/modules/example`
+ * `library/Example/ProvidedHook/Monitoring/DetailviewExtension/Simple.php`
+ * `run.php`
+
+#### Files <a id="monitoring-module-hooks-detailviewextension-files"></a>
+
+##### run.php <a id="monitoring-module-hooks-detailviewextension-files-run-php"></a>
+
+```php
+<?php
+/** @var \Icinga\Application\Modules\Module $this */
+
+$this->provideHook(
+ 'monitoring/DetailviewExtension',
+ 'Icinga\Module\Example\ProvidedHook\Monitoring\DetailviewExtension\Simple'
+);
+```
+
+##### Simple.php <a id="monitoring-module-hooks-detailviewextension-files-simple-php"></a>
+
+```php
+<?php
+namespace Icinga\Module\Example\ProvidedHook\Monitoring\DetailviewExtension;
+
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+class Simple extends DetailviewExtensionHook
+{
+ public function getHtmlForObject(MonitoredObject $object)
+ {
+ $stats = array();
+ foreach (str_split($object->name) as $c) {
+ if (isset($stats[$c])) {
+ ++$stats[$c];
+ } else {
+ $stats[$c] = 1;
+ }
+ }
+
+ ksort($stats);
+
+ $view = $this->getView();
+
+ $thead = '';
+ $tbody = '';
+ foreach ($stats as $c => $amount) {
+ $thead .= '<th>' . $view->escape($c) . '</th>';
+ $tbody .= '<td>' . $amount . '</td>';
+ }
+
+ return '<h2>'
+ . $view->escape(sprintf($view->translate('A %s named "%s"'), $object->getType(), $object->name))
+ . '</h2>'
+ . '<h3>Character stats</h3>'
+ . '<table>'
+ . '<thead>' . $thead . '</thead>'
+ . '<tbody>' . $tbody . '</tbody>'
+ . '</table>';
+ }
+}
+```
+
+### How it looks <a id="monitoring-module-hooks-detailviewextension-how-it-looks"></a>
+
+![Screenshot](img/hooks-detailviewextension-01.png)
+
+## Plugin Output Hook <a id="monitoring-module-hooks-pluginoutput"></a>
+
+The Plugin Output Hook allows you to rewrite the plugin output based on check commands. You have to implement the
+following methods:
+
+* `getCommands()`
+* and `render()`
+
+With `getCommands()` you specify for which commands the provided hook is responsible for. You may return a single
+command as string or a list of commands as array. If you want your hook to be responsible for every command, you have to
+specify the `*`.
+
+In `render()` you rewrite the plugin output based on check commands. The parameter `$command` specifies the check
+command of the host or service and `$output` specifies the plugin output. The parameter `$detail` tells you
+whether the output is requested from the detail area of the host or service.
+
+Do not use complex logic for rewriting plugin output in list views because of the performance impact!
+
+You have to return the rewritten plugin output as string. It is also possible to return a HTML string here.
+Please refer to `\Icinga\Module\Monitoring\Web\Helper\PluginOutputPurifier` for a list of allowed tags.
+
+Please also have a look at the following examples.
+
+**Example hook which is responsible for disk checks:**
+
+```php
+<?php
+
+namespace Icinga\Module\Example\ProvidedHook\Monitoring;
+
+use Icinga\Module\Monitoring\Hook\PluginOutputHook;
+
+class PluginOutput extends PluginOutputHook
+{
+ public function getCommands()
+ {
+ return ['disk'];
+ }
+
+ public function render($command, $output, $detail)
+ {
+ if (! $detail) {
+ // Don't rewrite plugin output in list views
+ return $output;
+ }
+ return implode('<br>', explode(';', $output));
+ }
+}
+```
+
+**Example hook which is responsible for disk and procs checks:**
+
+```php
+<?php
+
+namespace Icinga\Module\Example\ProvidedHook\Monitoring;
+
+use Icinga\Module\Monitoring\Hook\PluginOutputHook;
+
+class PluginOutput extends PluginOutputHook
+{
+ public function getCommands()
+ {
+ return ['disk', 'procs'];
+ }
+
+ public function render($command, $output, $detail)
+ {
+ switch ($command) {
+ case 'disk':
+ if ($detail) {
+ // Only rewrite plugin output in the detail area
+ $output = implode('<br>', explode(';', $output));
+ }
+ break;
+ case 'procs':
+ $output = preg_replace('/(\d)+/', '<b>$1</b>', $output);
+ break;
+ }
+
+ return $output;
+ }
+}
+```
diff --git a/modules/monitoring/doc/img/hooks-detailviewextension-01.png b/modules/monitoring/doc/img/hooks-detailviewextension-01.png
new file mode 100644
index 0000000..a5ddaf1
--- /dev/null
+++ b/modules/monitoring/doc/img/hooks-detailviewextension-01.png
Binary files differ
diff --git a/modules/monitoring/doc/img/list_hosts_add_columns.png b/modules/monitoring/doc/img/list_hosts_add_columns.png
new file mode 100644
index 0000000..874a8f1
--- /dev/null
+++ b/modules/monitoring/doc/img/list_hosts_add_columns.png
Binary files differ
diff --git a/modules/monitoring/doc/img/list_services_add_columns.png b/modules/monitoring/doc/img/list_services_add_columns.png
new file mode 100644
index 0000000..dd0db82
--- /dev/null
+++ b/modules/monitoring/doc/img/list_services_add_columns.png
Binary files differ
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php b/modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php
new file mode 100644
index 0000000..71fc6a1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/IdoBackend.php
@@ -0,0 +1,10 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido;
+
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+
+class IdoBackend extends MonitoringBackend
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php
new file mode 100644
index 0000000..09779b6
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/AllcontactsQuery.php
@@ -0,0 +1,74 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Zend_Db_Select;
+
+class AllcontactsQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'contacts' => array(
+ 'contact_name' => 'c.contact_name',
+ 'host_object_id' => 'c.host_object_id',
+ 'host_name' => 'c.host_name',
+ 'service_object_id' => 'c.service_object_id',
+ 'service_host_name' => 'c.service_host_name',
+ 'service_description' => 'c.service_description',
+
+ 'contact_alias' => 'c.contact_alias',
+ 'contact_email' => 'c.contact_email',
+ 'contact_pager' => 'c.contact_pager',
+ 'contact_has_host_notfications' => 'c.contact_has_host_notfications',
+ 'contact_has_service_notfications' => 'c.contact_has_service_notfications',
+ 'contact_can_submit_commands' => 'c.contact_can_submit_commands',
+ 'contact_notify_service_recovery' => 'c.notify_service_recovery',
+ 'contact_notify_service_warning' => 'c.notify_service_warning',
+ 'contact_notify_service_critical' => 'c.notify_service_critical',
+ 'contact_notify_service_unknown' => 'c.notify_service_unknown',
+ 'contact_notify_service_flapping' => 'c.notify_service_flapping',
+ 'contact_notify_service_downtime' => 'c.notify_service_downtime',
+ 'contact_notify_host_recovery' => 'c.notify_host_recovery',
+ 'contact_notify_host_down' => 'c.notify_host_down',
+ 'contact_notify_host_unreachable' => 'c.notify_host_unreachable',
+ 'contact_notify_host_flapping' => 'c.notify_host_flapping',
+ 'contact_notify_host_downtime' => 'c.notify_host_downtime',
+
+
+ )
+ );
+
+ protected $contacts;
+ protected $contactgroups;
+ protected $useSubqueryCount = true;
+
+ public function requireColumn($alias)
+ {
+ $this->contacts->addColumn($alias);
+ $this->contactgroups->addColumn($alias);
+ return parent::requireColumn($alias);
+ }
+
+ protected function joinBaseTables()
+ {
+ $this->contacts = $this->createSubQuery(
+ 'contact',
+ array('contact_name')
+ );
+ $this->contactgroups = $this->createSubQuery(
+ 'contactgroup',
+ array('contact_name')
+ );
+ $sub = $this->db->select()->union(
+ array($this->contacts, $this->contactgroups),
+ Zend_Db_Select::SQL_UNION_ALL
+ );
+
+ $this->baseQuery = $this->db->select()->distinct()->from(
+ array('c' => $sub),
+ array()
+ );
+
+ $this->joinedVirtualTables = array('contacts' => true);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php
new file mode 100644
index 0000000..59a4ccb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php
@@ -0,0 +1,61 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for commands
+ */
+class CommandQuery extends IdoQuery
+{
+ /**
+ * @var array
+ */
+ protected $columnMap = array(
+ 'commands' => array(
+ 'command_id' => 'c.command_id',
+ 'command_instance_id' => 'c.instance_id',
+ 'command_config_type' => 'c.config_type',
+ 'command_line' => 'c.command_line',
+ 'command_name' => 'co.name1'
+ ),
+
+ 'contacts' => array(
+ 'contact_id' => 'con.contact_id',
+ 'contact_alias' => 'con.contact_alias'
+ )
+ );
+
+ /**
+ * Fetch basic information about commands
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('c' => $this->prefix . 'commands'),
+ array()
+ )->join(
+ array('co' => $this->prefix . 'objects'),
+ 'co.object_id = c.object_id',
+ array()
+ );
+
+ $this->joinedVirtualTables = array('commands' => true);
+ }
+
+ /**
+ * Join contacts
+ */
+ protected function joinContacts()
+ {
+ $this->select->join(
+ array('cnc' => $this->prefix . 'contact_notificationcommands'),
+ 'cnc.command_object_id = co.object_id',
+ array()
+ )->join(
+ array('con' => $this->prefix . 'contacts'),
+ 'con.contact_id = cnc.contact_id',
+ array()
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php
new file mode 100644
index 0000000..6c01931
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentQuery.php
@@ -0,0 +1,158 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service comments
+ */
+class CommentQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'comments' => array(
+ 'comment_author' => 'c.comment_author',
+ 'comment_author_name' => 'c.comment_author_name',
+ 'comment_data' => 'c.comment_data',
+ 'comment_expiration' => 'c.comment_expiration',
+ 'comment_internal_id' => 'c.comment_internal_id',
+ 'comment_is_persistent' => 'c.comment_is_persistent',
+ 'comment_name' => 'c.comment_name',
+ 'comment_timestamp' => 'c.comment_timestamp',
+ 'comment_type' => 'c.comment_type',
+ 'instance_name' => 'c.instance_name',
+ 'object_type' => 'c.object_type'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'c.host_display_name',
+ 'host_name' => 'c.host_name',
+ 'host_state' => 'c.host_state'
+ ),
+ 'services' => array(
+ 'service_description' => 'c.service_description',
+ 'service_display_name' => 'c.service_display_name',
+ 'service_host_name' => 'c.service_host_name',
+ 'service_state' => 'c.service_state'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $commentQuery;
+
+ /**
+ * Subqueries used for the comment query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.14.0', '<')) {
+ $this->columnMap['comments']['comment_name'] = '(NULL)';
+ }
+ $this->commentQuery = $this->db->select();
+ $this->select->from(
+ array('c' => $this->commentQuery),
+ array()
+ );
+ $this->joinedVirtualTables['comments'] = true;
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys($this->columnMap['comments'] + $this->columnMap['hosts']);
+ foreach (array_keys($this->columnMap['services']) as $column) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ $hosts = $this->createSubQuery('hostcomment', $columns);
+ $this->subQueries[] = $hosts;
+ $this->commentQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys($this->columnMap['comments'] + $this->columnMap['hosts'] + $this->columnMap['services']);
+ $services = $this->createSubQuery('servicecomment', $columns);
+ $this->subQueries[] = $services;
+ $this->commentQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php
new file mode 100644
index 0000000..8cb4ddb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service comment removal records
+ */
+class CommentdeletionhistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'commenthistory' => array(
+ 'id' => 'cdh.id',
+ 'object_type' => 'cdh.object_type'
+ ),
+ 'history' => array(
+ 'type' => 'cdh.type',
+ 'timestamp' => 'cdh.timestamp',
+ 'object_id' => 'cdh.object_id',
+ 'state' => 'cdh.state',
+ 'output' => 'cdh.output'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'cdh.host_display_name',
+ 'host_name' => 'cdh.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'cdh.service_description',
+ 'service_display_name' => 'cdh.service_display_name',
+ 'service_host_name' => 'cdh.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $commentDeletionHistoryQuery;
+
+ /**
+ * Subqueries used for the comment history query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Whether to additionally select all history columns
+ *
+ * @var bool
+ */
+ protected $fetchHistoryColumns = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->commentDeletionHistoryQuery = $this->db->select();
+ $this->select->from(
+ array('cdh' => $this->commentDeletionHistoryQuery),
+ array()
+ );
+ $this->joinedVirtualTables['commenthistory'] = true;
+ }
+
+ /**
+ * Join history related columns and tables
+ */
+ protected function joinHistory()
+ {
+ // TODO: Ensure that one is selecting the history columns first...
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ $this->requireVirtualTable('services');
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['commenthistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hostcommentdeletionhistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->commentDeletionHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Servicecommentdeletionhistory', $columns);
+ $this->subQueries[] = $services;
+ $this->commentDeletionHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php
new file mode 100644
index 0000000..c85adff
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenteventQuery.php
@@ -0,0 +1,39 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host and service comment entry and deletion events
+ */
+class CommenteventQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'commentevent' => array(
+ 'commentevent_id' => 'ch.commenthistory_id',
+ 'commentevent_entry_type' => "(CASE ch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' ELSE NULL END)",
+ 'commentevent_comment_time' => 'UNIX_TIMESTAMP(ch.comment_time)',
+ 'commentevent_author_name' => 'ch.author_name',
+ 'commentevent_comment_data' => 'ch.comment_data',
+ 'commentevent_is_persistent' => 'ch.is_persistent',
+ 'commentevent_comment_source' => "(CASE ch.comment_source WHEN 0 THEN 'icinga' WHEN 1 THEN 'user' ELSE NULL END)",
+ 'commentevent_expires' => 'ch.expires',
+ 'commentevent_expiration_time' => 'UNIX_TIMESTAMP(ch.expiration_time)',
+ 'commentevent_deletion_time' => 'UNIX_TIMESTAMP(ch.deletion_time)'
+ ),
+ 'object' => array(
+ 'host_name' => 'o.name1',
+ 'service_description' => 'o.name2'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select()
+ ->from(array('ch' => $this->prefix . 'commenthistory'), array())
+ ->join(array('o' => $this->prefix . 'objects'), 'ch.object_id = o.object_id', array());
+
+ $this->joinedVirtualTables['commentevent'] = true;
+ $this->joinedVirtualTables['object'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php
new file mode 100644
index 0000000..47dd97c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommenthistoryQuery.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service comment history records
+ */
+class CommenthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'commenthistory' => array(
+ 'id' => 'ch.id',
+ 'object_type' => 'ch.object_type'
+ ),
+ 'history' => array(
+ 'type' => 'ch.type',
+ 'timestamp' => 'ch.timestamp',
+ 'object_id' => 'ch.object_id',
+ 'state' => 'ch.state',
+ 'output' => 'ch.output'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'ch.host_display_name',
+ 'host_name' => 'ch.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'ch.service_description',
+ 'service_display_name' => 'ch.service_display_name',
+ 'service_host_name' => 'ch.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $commentHistoryQuery;
+
+ /**
+ * Subqueries used for the comment history query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Whether to additionally select all history columns
+ *
+ * @var bool
+ */
+ protected $fetchHistoryColumns = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->commentHistoryQuery = $this->db->select();
+ $this->select->from(
+ array('ch' => $this->commentHistoryQuery),
+ array()
+ );
+ $this->joinedVirtualTables['commenthistory'] = true;
+ }
+
+ /**
+ * Join history related columns and tables
+ */
+ protected function joinHistory()
+ {
+ // TODO: Ensure that one is selecting the history columns first...
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ $this->requireVirtualTable('services');
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['commenthistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hostcommenthistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->commentHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['commenthistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Servicecommenthistory', $columns);
+ $this->subQueries[] = $services;
+ $this->commentHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php
new file mode 100644
index 0000000..ca10323
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactQuery.php
@@ -0,0 +1,139 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for contacts
+ */
+class ContactQuery extends IdoQuery
+{
+ protected $columnMap = [
+ 'contacts' => [
+ 'contact_id' => 'c.contact_id',
+ 'contact' => 'c.contact',
+ 'contact_name' => 'c.contact_name',
+ 'contact_alias' => 'c.contact_alias',
+ 'contact_email' => 'c.contact_email',
+ 'contact_pager' => 'c.contact_pager',
+ 'contact_object_id' => 'c.contact_object_id',
+ 'contact_has_host_notfications' => 'c.contact_has_host_notfications',
+ 'contact_has_service_notfications' => 'c.contact_has_service_notfications',
+ 'contact_can_submit_commands' => 'c.contact_can_submit_commands',
+ 'contact_notify_service_recovery' => 'c.contact_notify_service_recovery',
+ 'contact_notify_service_warning' => 'c.contact_notify_service_warning',
+ 'contact_notify_service_critical' => 'c.contact_notify_service_critical',
+ 'contact_notify_service_unknown' => 'c.contact_notify_service_unknown',
+ 'contact_notify_service_flapping' => 'c.contact_notify_service_flapping',
+ 'contact_notify_service_downtime' => 'c.contact_notify_service_downtime',
+ 'contact_notify_host_recovery' => 'c.contact_notify_host_recovery',
+ 'contact_notify_host_down' => 'c.contact_notify_host_down',
+ 'contact_notify_host_unreachable' => 'c.contact_notify_host_unreachable',
+ 'contact_notify_host_flapping' => 'c.contact_notify_host_flapping',
+ 'contact_notify_host_downtime' => 'c.contact_notify_host_downtime',
+ 'contact_notify_host_timeperiod' => 'c.contact_notify_host_timeperiod',
+ 'contact_notify_service_timeperiod' => 'c.contact_notify_service_timeperiod'
+ ]
+ ];
+
+ /** @var Zend_Db_Select The union */
+ protected $contactQuery;
+
+ /** @var IdoQuery[] Subqueries used for the contact query */
+ protected $subQueries = [];
+
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ $strangers = array_diff(
+ $filter->listFilteredColumns(),
+ array_keys($this->columnMap['contacts'])
+ );
+ if (! empty($strangers)) {
+ $this->transformToUnion();
+ }
+
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+
+ return $this;
+ }
+
+ protected function joinBaseTables()
+ {
+ $this->contactQuery = $this->createSubQuery('Hostcontact', array_keys($this->columnMap['contacts']));
+ $this->contactQuery->setIsSubQuery();
+ $this->subQueries[] = $this->contactQuery;
+
+ $this->select->from(
+ ['c' => $this->contactQuery],
+ []
+ );
+
+ $this->joinedVirtualTables['contacts'] = true;
+ }
+
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+
+ public function transformToUnion()
+ {
+ $this->contactQuery = $this->db->select();
+ $this->select->reset();
+ $this->subQueries = [];
+
+ $this->select->distinct()->from(
+ ['c' => $this->contactQuery],
+ []
+ );
+
+ $hosts = $this->createSubQuery('Hostcontact', array_keys($this->columnMap['contacts']));
+ $this->subQueries[] = $hosts;
+ $this->contactQuery->union([$hosts], Zend_Db_Select::SQL_UNION_ALL);
+
+ $services = $this->createSubQuery('Servicecontact', array_keys($this->columnMap['contacts']));
+ $this->subQueries[] = $services;
+ $this->contactQuery->union([$services], Zend_Db_Select::SQL_UNION_ALL);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php
new file mode 100644
index 0000000..7d4cbc1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ContactgroupQuery.php
@@ -0,0 +1,214 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for contact groups
+ */
+class ContactgroupQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('contactgroups' => array('cg.contactgroup_id', 'cgo.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hosts', 'members', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'contactgroups' => array(
+ 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci',
+ 'contactgroup_name' => 'cgo.name1',
+ 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci'
+ ),
+ 'members' => array(
+ 'contact_count' => 'SUM(CASE WHEN cgmo.object_id IS NOT NULL THEN 1 ELSE 0 END)'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('cg' => $this->prefix . 'contactgroups'),
+ array()
+ )->join(
+ array('cgo' => $this->prefix . 'objects'),
+ 'cgo.object_id = cg.contactgroup_object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11',
+ array()
+ );
+ $this->joinedVirtualTables['contactgroups'] = true;
+ }
+
+ /**
+ * Join contact group members
+ */
+ protected function joinMembers()
+ {
+ $this->select->joinLeft(
+ array('cgm' => $this->prefix . 'contactgroup_members'),
+ 'cgm.contactgroup_id = cg.contactgroup_id',
+ array()
+ )->joinLeft(
+ array('cgmo' => $this->prefix . 'objects'),
+ 'cgmo.object_id = cgm.contact_object_id AND cgmo.is_active = 1 AND cgmo.objecttype_id = 10',
+ array()
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('hosts');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->joinLeft(
+ array('hcg' => $this->prefix . 'host_contactgroups'),
+ 'hcg.contactgroup_object_id = cg.contactgroup_object_id',
+ array()
+ )->joinLeft(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_id = hcg.host_id',
+ array()
+ )->joinLeft(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = cg.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.servicegroup_id = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('scg' => $this->prefix . 'service_contactgroups'),
+ 'scg.contactgroup_object_id = cg.contactgroup_object_id',
+ array()
+ )->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.service_id = scg.service_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $this->requireVirtualTable('hosts');
+
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $this->requireVirtualTable('services');
+
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php
new file mode 100644
index 0000000..1492894
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CustomvarQuery.php
@@ -0,0 +1,116 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Application\Config;
+use Icinga\Data\Filter\FilterExpression;
+
+class CustomvarQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'customvariablestatus' => array(
+ 'varname' => 'cvs.varname',
+ 'varvalue' => 'cvs.varvalue',
+ 'is_json' => 'cvs.is_json',
+ ),
+ 'objects' => array(
+ 'host' => 'cvo.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'cvo.name1',
+ 'service' => 'cvo.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'cvo.name2',
+ 'contact' => 'cvo.name1 COLLATE latin1_general_ci',
+ 'contact_name' => 'cvo.name1',
+ 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 10 THEN 'contact' ELSE 'invalid' END",
+ 'object_type_id' => 'cvo.objecttype_id'
+// 'object_type' => "CASE cvo.objecttype_id WHEN 1 THEN 'host' WHEN 2 THEN 'service' WHEN 3 THEN 'hostgroup' WHEN 4 THEN 'servicegroup' WHEN 5 THEN 'hostescalation' WHEN 6 THEN 'serviceescalation' WHEN 7 THEN 'hostdependency' WHEN 8 THEN 'servicedependency' WHEN 9 THEN 'timeperiod' WHEN 10 THEN 'contact' WHEN 11 THEN 'contactgroup' WHEN 12 THEN 'command' ELSE 'other' END"
+ ),
+ );
+
+ public function where($expression, $parameters = null)
+ {
+ $types = array('host' => 1, 'service' => 2, 'contact' => 10);
+ if ($expression === 'object_type') {
+ parent::where('object_type_id', $types[$parameters]);
+ } else {
+ parent::where($expression, $parameters);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $types = ['host' => 1, 'service' => 2, 'contact' => 10];
+ if ($ex->getColumn() === 'object_type') {
+ $ex = clone $ex;
+ $ex->setColumn('object_type_id');
+ $ex->setExpression($types[$ex->getExpression()]);
+ }
+
+ parent::whereEx($ex);
+
+ return $this;
+ }
+
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.12.0', '<')) {
+ $this->columnMap['customvariablestatus']['is_json'] = '(0)';
+ }
+
+ if (! (bool) Config::module('monitoring')->get('ido', 'use_customvar_status_table', true)) {
+ $table = 'customvariables';
+ } else {
+ $table = 'customvariablestatus';
+ }
+
+ $this->select->from(
+ array('cvs' => $this->prefix . $table),
+ array()
+ )->join(
+ array('cvo' => $this->prefix . 'objects'),
+ 'cvs.object_id = cvo.object_id AND cvo.is_active = 1',
+ array()
+ );
+ $this->joinedVirtualTables = array(
+ 'customvariablestatus' => true,
+ 'objects' => true
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = cvs.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroup()
+ {
+ $group = parent::getGroup();
+ if (! empty($group) && $this->ds->getDbType() === 'pgsql') {
+ foreach ($this->columnMap as $table => $columns) {
+ $pk = ($table === 'objects' ? 'cvo.' : 'cvs.') . $this->getPrimaryKeyColumn($table);
+ foreach ($columns as $alias => $_) {
+ if (! in_array($pk, $group, true) && in_array($alias, $group, true)) {
+ $group[] = $pk;
+ break;
+ }
+ }
+ }
+ }
+
+ return $group;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php
new file mode 100644
index 0000000..9bc1d88
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php
@@ -0,0 +1,163 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service downtimes
+ */
+class DowntimeQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimes' => array(
+ 'downtime_author' => 'd.downtime_author',
+ 'downtime_author_name' => 'd.downtime_author_name',
+ 'downtime_comment' => 'd.downtime_comment',
+ 'downtime_duration' => 'd.downtime_duration',
+ 'downtime_end' => 'd.downtime_end',
+ 'downtime_entry_time' => 'd.downtime_entry_time',
+ 'downtime_internal_id' => 'd.downtime_internal_id',
+ 'downtime_is_fixed' => 'd.downtime_is_fixed',
+ 'downtime_is_flexible' => 'd.downtime_is_flexible',
+ 'downtime_is_in_effect' => 'd.downtime_is_in_effect',
+ 'downtime_name' => 'd.downtime_name',
+ 'downtime_scheduled_end' => 'd.downtime_scheduled_end',
+ 'downtime_scheduled_start' => 'd.downtime_scheduled_start',
+ 'downtime_start' => 'd.downtime_start',
+ 'object_type' => 'd.object_type',
+ 'instance_name' => 'd.instance_name'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'd.host_display_name',
+ 'host_name' => 'd.host_name',
+ 'host_state' => 'd.host_state'
+ ),
+ 'services' => array(
+ 'service_description' => 'd.service_description',
+ 'service_display_name' => 'd.service_display_name',
+ 'service_host_name' => 'd.service_host_name',
+ 'service_state' => 'd.service_state'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $downtimeQuery;
+
+ /**
+ * Subqueries used for the downtime query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.14.0', '<')) {
+ $this->columnMap['downtimes']['downtime_name'] = '(NULL)';
+ }
+ $this->downtimeQuery = $this->db->select();
+ $this->select->from(
+ array('d' => $this->downtimeQuery),
+ array()
+ );
+ $this->joinedVirtualTables['downtimes'] = true;
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys($this->columnMap['downtimes'] + $this->columnMap['hosts']);
+ foreach (array_keys($this->columnMap['services']) as $column) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ $hosts = $this->createSubQuery('hostdowntime', $columns);
+ $this->subQueries[] = $hosts;
+ $this->downtimeQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys($this->columnMap['downtimes'] + $this->columnMap['hosts'] + $this->columnMap['services']);
+ $services = $this->createSubQuery('servicedowntime', $columns);
+ $this->subQueries[] = $services;
+ $this->downtimeQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php
new file mode 100644
index 0000000..de47418
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service downtime end history records
+ */
+class DowntimeendhistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimehistory' => array(
+ 'id' => 'deh.id',
+ 'object_type' => 'deh.object_type'
+ ),
+ 'history' => array(
+ 'type' => 'deh.type',
+ 'timestamp' => 'deh.timestamp',
+ 'object_id' => 'deh.object_id',
+ 'state' => 'deh.state',
+ 'output' => 'deh.output'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'deh.host_display_name',
+ 'host_name' => 'deh.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'deh.service_description',
+ 'service_display_name' => 'deh.service_display_name',
+ 'service_host_name' => 'deh.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $downtimeEndHistoryQuery;
+
+ /**
+ * Subqueries used for the downtime end history query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Whether to additionally select all history columns
+ *
+ * @var bool
+ */
+ protected $fetchHistoryColumns = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->downtimeEndHistoryQuery = $this->db->select();
+ $this->select->from(
+ array('deh' => $this->downtimeEndHistoryQuery),
+ array()
+ );
+ $this->joinedVirtualTables['downtimehistory'] = true;
+ }
+
+ /**
+ * Join history related columns and tables
+ */
+ protected function joinHistory()
+ {
+ // TODO: Ensure that one is selecting the history columns first...
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ $this->requireVirtualTable('services');
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['downtimehistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hostdowntimeendhistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->downtimeEndHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Servicedowntimeendhistory', $columns);
+ $this->subQueries[] = $services;
+ $this->downtimeEndHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php
new file mode 100644
index 0000000..04e6aa5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeeventQuery.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host and service downtime events
+ */
+class DowntimeeventQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'downtimeevent' => array(
+ 'downtimeevent_id' => 'dth.downtimehistory_id',
+ 'downtimeevent_entry_time' => 'UNIX_TIMESTAMP(dth.entry_time)',
+ 'downtimeevent_author_name' => 'dth.author_name',
+ 'downtimeevent_comment_data' => 'dth.comment_data',
+ 'downtimeevent_is_fixed' => 'dth.is_fixed',
+ 'downtimeevent_scheduled_start_time' => 'UNIX_TIMESTAMP(dth.scheduled_start_time)',
+ 'downtimeevent_scheduled_end_time' => 'UNIX_TIMESTAMP(dth.scheduled_end_time)',
+ 'downtimeevent_was_started' => 'dth.was_started',
+ 'downtimeevent_actual_start_time' => 'UNIX_TIMESTAMP(dth.actual_start_time)',
+ 'downtimeevent_actual_end_time' => 'UNIX_TIMESTAMP(dth.actual_end_time)',
+ 'downtimeevent_was_cancelled' => 'dth.was_cancelled',
+ 'downtimeevent_is_in_effect' => 'dth.is_in_effect',
+ 'downtimeevent_trigger_time' => 'UNIX_TIMESTAMP(dth.trigger_time)'
+ ),
+ 'object' => array(
+ 'host_name' => 'o.name1',
+ 'service_description' => 'o.name2'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select()
+ ->from(array('dth' => $this->prefix . 'downtimehistory'), array())
+ ->join(array('o' => $this->prefix . 'objects'), 'dth.object_id = o.object_id', array());
+
+ $this->joinedVirtualTables['downtimeevent'] = true;
+ $this->joinedVirtualTables['object'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php
new file mode 100644
index 0000000..3ba600d
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service downtime start history records
+ */
+class DowntimestarthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimehistory' => array(
+ 'id' => 'dsh.id',
+ 'object_type' => 'dsh.object_type'
+ ),
+ 'history' => array(
+ 'type' => 'dsh.type',
+ 'timestamp' => 'dsh.timestamp',
+ 'object_id' => 'dsh.object_id',
+ 'state' => 'dsh.state',
+ 'output' => 'dsh.output'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'dsh.host_display_name',
+ 'host_name' => 'dsh.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'dsh.service_description',
+ 'service_display_name' => 'dsh.service_display_name',
+ 'service_host_name' => 'dsh.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $downtimeStartHistoryQuery;
+
+ /**
+ * Subqueries used for the downtime start history query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Whether to additionally select all history columns
+ *
+ * @var bool
+ */
+ protected $fetchHistoryColumns = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->downtimeStartHistoryQuery = $this->db->select();
+ $this->select->from(
+ array('dsh' => $this->downtimeStartHistoryQuery),
+ array()
+ );
+ $this->joinedVirtualTables['downtimehistory'] = true;
+ }
+
+ /**
+ * Join history related columns and tables
+ */
+ protected function joinHistory()
+ {
+ // TODO: Ensure that one is selecting the history columns first...
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ $this->requireVirtualTable('services');
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['downtimehistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hostdowntimestarthistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->downtimeStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['downtimehistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Servicedowntimestarthistory', $columns);
+ $this->subQueries[] = $services;
+ $this->downtimeStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php
new file mode 100644
index 0000000..a99d6b7
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyhostgroupQuery.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class EmptyhostgroupQuery extends HostgroupQuery
+{
+ protected $subQueryTargets = [];
+
+ protected $columnMap = [
+ 'hostgroups' => [
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1',
+ 'host_name' => '(NULL)',
+ 'service_description' => '(NULL)',
+ 'servicegroup_name' => '(NULL)',
+ 'host_contact' => '(NULL)',
+ 'host_contactgroup' => '(NULL)'
+ ],
+ 'instances' => [
+ 'instance_name' => 'i.instance_name'
+ ]
+ ];
+
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables();
+
+ $this->select->joinLeft(
+ ['ehgm' => $this->prefix . 'hostgroup_members'],
+ 'ehgm.hostgroup_id = hg.hostgroup_id',
+ []
+ );
+ $this->select->group(['hgo.object_id', 'hg.hostgroup_id']);
+ $this->select->having('COUNT(ehgm.hostgroup_member_id) = ?', 0);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php
new file mode 100644
index 0000000..88ee4c3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EmptyservicegroupQuery.php
@@ -0,0 +1,51 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class EmptyservicegroupQuery extends ServicegroupQuery
+{
+ protected $subQueryTargets = [];
+
+ protected $columnMap = [
+ 'servicegroups' => [
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'host_name' => '(NULL)',
+ 'hostgroup_name' => '(NULL)',
+ 'service_description' => '(NULL)',
+ 'host_contact' => '(NULL)',
+ 'host_contactgroup' => '(NULL)',
+ 'service_contact' => '(NULL)',
+ 'service_contactgroup' => '(NULL)'
+ ],
+ 'instances' => [
+ 'instance_name' => 'i.instance_name'
+ ]
+ ];
+
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables();
+
+ $this->select->joinLeft(
+ ['esgm' => $this->prefix . 'servicegroup_members'],
+ 'esgm.servicegroup_id = sg.servicegroup_id',
+ []
+ );
+ $this->select->group(['sgo.object_id', 'sg.servicegroup_id']);
+ $this->select->having('COUNT(esgm.servicegroup_member_id) = ?', 0);
+ }
+
+ protected function joinHosts()
+ {
+ parent::joinHosts();
+
+ $this->select->joinLeft(
+ ['h' => 'icinga_hosts'],
+ 'h.host_object_id = s.host_object_id',
+ []
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php
new file mode 100644
index 0000000..297b20a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridQuery.php
@@ -0,0 +1,57 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+abstract class EventgridQuery extends StatehistoryQuery
+{
+ /**
+ * The columns additionally provided by this query
+ *
+ * @var array
+ */
+ protected $additionalColumns = array(
+ 'day' => 'DATE(FROM_UNIXTIME(sth.timestamp))',
+ 'cnt_up' => "SUM(CASE WHEN sth.state = 0 THEN 1 ELSE 0 END)",
+ 'cnt_down_hard' => "SUM(CASE WHEN sth.state = 1 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)",
+ 'cnt_down' => "SUM(CASE WHEN sth.state = 1 THEN 1 ELSE 0 END)",
+ 'cnt_unreachable_hard' => "SUM(CASE WHEN sth.state = 2 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)",
+ 'cnt_unreachable' => "SUM(CASE WHEN sth.state = 2 THEN 1 ELSE 0 END)",
+ 'cnt_unknown_hard' => "SUM(CASE WHEN sth.state = 3 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)",
+ 'cnt_unknown' => "SUM(CASE WHEN sth.state = 3 THEN 1 ELSE 0 END)",
+ 'cnt_unknown_hard' => "SUM(CASE WHEN sth.state = 3 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)",
+ 'cnt_critical' => "SUM(CASE WHEN sth.state = 2 THEN 1 ELSE 0 END)",
+ 'cnt_critical_hard' => "SUM(CASE WHEN sth.state = 2 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)",
+ 'cnt_warning' => "SUM(CASE WHEN sth.state = 1 THEN 1 ELSE 0 END)",
+ 'cnt_warning_hard' => "SUM(CASE WHEN sth.state = 1 AND sth.type = 'hard_state' THEN 1 ELSE 0 END)",
+ 'cnt_ok' => "SUM(CASE WHEN sth.state = 0 THEN 1 ELSE 0 END)"
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables();
+ $this->requireVirtualTable('history');
+ $this->columnMap['statehistory'] += $this->additionalColumns;
+ $this->select->group(array('DATE(FROM_UNIXTIME(sth.timestamp))'));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ if (array_key_exists($columnOrAlias, $this->additionalColumns)) {
+ $subQueries = $this->subQueries;
+ $this->subQueries = array();
+ parent::order($columnOrAlias, $dir);
+ $this->subQueries = $subQueries;
+ } else {
+ parent::order($columnOrAlias, $dir);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php
new file mode 100644
index 0000000..62d92e4
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridhostsQuery.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class EventgridhostsQuery extends EventgridQuery
+{
+
+ /**
+ * Join history related columns and tables, hosts only
+ */
+ protected function joinHistory()
+ {
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php
new file mode 100644
index 0000000..424de45
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventgridservicesQuery.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class EventgridservicesQuery extends EventgridQuery
+{
+ /**
+ * Join history related columns and tables, services only
+ */
+ protected function joinHistory()
+ {
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('services');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php
new file mode 100644
index 0000000..680e2ca
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/EventhistoryQuery.php
@@ -0,0 +1,134 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for event history records
+ */
+class EventhistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $useSubqueryCount = true;
+
+ /**
+ * Subqueries used for the event history query
+ *
+ * @type IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'eventhistory' => array(
+ 'id' => 'eh.id',
+ 'host_name' => 'eh.host_name',
+ 'service_description' => 'eh.service_description',
+ 'object_type' => 'eh.object_type',
+ 'timestamp' => 'eh.timestamp',
+ 'state' => 'eh.state',
+ 'output' => 'eh.output',
+ 'type' => 'eh.type',
+ 'host_display_name' => 'eh.host_display_name',
+ 'service_display_name' => 'eh.service_display_name'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $columns = array(
+ 'id',
+ 'timestamp',
+ 'output',
+ 'type',
+ 'state',
+ 'object_type',
+ 'host_name',
+ 'service_description',
+ 'host_display_name',
+ 'service_display_name'
+ );
+ $this->subQueries = array(
+ $this->createSubQuery('Notificationhistory', $columns),
+ $this->createSubQuery('Statehistory', $columns),
+ $this->createSubQuery('Downtimestarthistory', $columns),
+ $this->createSubQuery('Downtimeendhistory', $columns),
+ $this->createSubQuery('Commenthistory', $columns),
+ $this->createSubQuery('Commentdeletionhistory', $columns),
+ $this->createSubQuery('Flappingstarthistory', $columns),
+ $this->createSubQuery('Flappingendhistory', $columns)
+ );
+ $sub = $this->db->select()->union($this->subQueries, Zend_Db_Select::SQL_UNION_ALL);
+ $this->select->from(array('eh' => $sub), array());
+ $this->joinedVirtualTables['eventhistory'] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php
new file mode 100644
index 0000000..7bdf332
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingendhistoryQuery.php
@@ -0,0 +1,49 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service flapping end history records
+ */
+class FlappingendhistoryQuery extends FlappingstarthistoryQuery
+{
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['flappinghistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hostflappingendhistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->flappingStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['flappinghistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Serviceflappingendhistory', $columns);
+ $this->subQueries[] = $services;
+ $this->flappingStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php
new file mode 100644
index 0000000..d993467
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingeventQuery.php
@@ -0,0 +1,36 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host and service flapping events
+ */
+class FlappingeventQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'flappingevent' => array(
+ 'flappingevent_id' => 'fh.flappinghistory_id',
+ 'flappingevent_event_time' => 'UNIX_TIMESTAMP(fh.event_time)',
+ 'flappingevent_event_type' => "(CASE fh.event_type WHEN 1000 THEN 'flapping' WHEN 1001 THEN 'flapping_deleted' ELSE NULL END)",
+ 'flappingevent_reason_type' => "(CASE fh.reason_type WHEN 1 THEN 'stopped' WHEN 2 THEN 'disabled' ELSE NULL END)",
+ 'flappingevent_percent_state_change' => 'fh.percent_state_change',
+ 'flappingevent_low_threshold' => 'fh.low_threshold',
+ 'flappingevent_high_threshold' => 'fh.high_threshold'
+ ),
+ 'object' => array(
+ 'host_name' => 'o.name1',
+ 'service_description' => 'o.name2'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select()
+ ->from(array('fh' => $this->prefix . 'flappinghistory'), array())
+ ->join(array('o' => $this->prefix . 'objects'), 'fh.object_id = o.object_id', array());
+
+ $this->joinedVirtualTables['flappingevent'] = true;
+ $this->joinedVirtualTables['object'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php
new file mode 100644
index 0000000..5c8bec5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/FlappingstarthistoryQuery.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service flapping start history records
+ */
+class FlappingstarthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'flappinghistory' => array(
+ 'id' => 'fsh.id',
+ 'object_type' => 'fsh.object_type'
+ ),
+ 'history' => array(
+ 'type' => 'fsh.type',
+ 'timestamp' => 'fsh.timestamp',
+ 'object_id' => 'fsh.object_id',
+ 'state' => 'fsh.state',
+ 'output' => 'fsh.output'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'fsh.host_display_name',
+ 'host_name' => 'fsh.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'fsh.service_description',
+ 'service_display_name' => 'fsh.service_display_name',
+ 'service_host_name' => 'fsh.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $flappingStartHistoryQuery;
+
+ /**
+ * Subqueries used for the flapping start history query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Whether to additionally select all history columns
+ *
+ * @var bool
+ */
+ protected $fetchHistoryColumns = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->flappingStartHistoryQuery = $this->db->select();
+ $this->select->from(
+ array('fsh' => $this->flappingStartHistoryQuery),
+ array()
+ );
+ $this->joinedVirtualTables['flappinghistory'] = true;
+ }
+
+ /**
+ * Join history related columns and tables
+ */
+ protected function joinHistory()
+ {
+ // TODO: Ensure that one is selecting the history columns first...
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ $this->requireVirtualTable('services');
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['flappinghistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hostflappingstarthistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->flappingStartHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['flappinghistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Serviceflappingstarthistory', $columns);
+ $this->subQueries[] = $services;
+ $this->flappingStartHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php
new file mode 100644
index 0000000..60ea5ef
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php
@@ -0,0 +1,131 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Zend_Db_Select;
+
+/**
+ * Query for host and service group summaries
+ */
+class GroupsummaryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'hoststatussummary' => array(
+ 'hostgroup' => 'hostgroup COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hostgroup_alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hostgroup_name',
+ 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)',
+ 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN 1 ELSE 0 END)',
+ 'hosts_down_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)',
+ 'hosts_down_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)',
+ 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND acknowledged + in_downtime = 0 THEN 1 ELSE 0 END)',
+ 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)',
+ 'hosts_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 99 THEN state_change ELSE 0 END)',
+ 'hosts_severity' => 'MAX(CASE WHEN object_type = \'host\' THEN severity ELSE 0 END)',
+ 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime != 0 THEN state_change ELSE 0 END)',
+ 'hosts_unreachable_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'host\' AND state = 2 AND acknowledged + in_downtime = 0 THEN state_change ELSE 0 END)',
+ 'hosts_up_last_state_change' => 'MAX(CASE WHEN object_type = \'host\' AND state = 0 THEN state_change ELSE 0 END)'
+ ),
+ 'servicestatussummary' => array(
+ 'servicegroup' => 'servicegroup COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'servicegroup_alias COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'servicegroup_name',
+ 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)',
+ 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)',
+ 'services_critical_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)',
+ 'services_critical_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)',
+ 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)',
+ 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)',
+ 'services_ok_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 0 THEN state_change ELSE 0 END)',
+ 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)',
+ 'services_pending_last_state_change' => 'MAX(CASE WHEN object_type = \'service\' AND state = 99 THEN state_change ELSE 0 END)',
+ 'services_severity' => 'MAX(CASE WHEN object_type = \'service\' THEN severity ELSE 0 END)',
+ 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)',
+ 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)',
+ 'services_unknown_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)',
+ 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)',
+ 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN 1 ELSE 0 END)',
+ 'services_warning_last_state_change_handled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state > 0 THEN state_change ELSE 0 END)',
+ 'services_warning_last_state_change_unhandled' => 'MAX(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN state_change ELSE 0 END)',
+ 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND acknowledged + in_downtime + host_state = 0 THEN 1 ELSE 0 END)'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $useSubqueryCount = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $columns = array(
+ 'object_type',
+ 'host_state'
+ );
+
+ if (in_array('servicegroup', $this->desiredColumns) || in_array('servicegroup_name', $this->desiredColumns)) {
+ $columns[] = 'servicegroup';
+ $columns[] = 'servicegroup_name';
+ $columns[] = 'servicegroup_alias';
+ $groupColumns = array('servicegroup_name', 'servicegroup_alias');
+ } else {
+ $columns[] = 'hostgroup';
+ $columns[] = 'hostgroup_name';
+ $columns[] = 'hostgroup_alias';
+ $groupColumns = array('hostgroup_name', 'hostgroup_alias');
+ }
+ $hosts = $this->createSubQuery(
+ 'Hoststatus',
+ $columns + array(
+ 'state' => 'host_state',
+ 'acknowledged' => 'host_acknowledged',
+ 'in_downtime' => 'host_in_downtime',
+ 'state_change' => 'host_last_state_change',
+ 'severity' => 'host_severity'
+ )
+ );
+ if (in_array('servicegroup_name', $this->desiredColumns)) {
+ $hosts->group(array(
+ 'sgo.name1',
+ 'ho.object_id',
+ 'sg.alias',
+ 'state',
+ 'acknowledged',
+ 'in_downtime',
+ 'state_change',
+ 'severity'
+ ));
+ }
+ $services = $this->createSubQuery(
+ 'Status',
+ $columns + array(
+ 'state' => 'service_state',
+ 'acknowledged' => 'service_acknowledged',
+ 'in_downtime' => 'service_in_downtime',
+ 'state_change' => 'service_last_state_change',
+ 'severity' => 'service_severity'
+ )
+ );
+ $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
+ $this->select->from(array('statussummary' => $union), array())->group($groupColumns);
+ $this->joinedVirtualTables = array(
+ 'servicestatussummary' => true,
+ 'hoststatussummary' => true
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php
new file mode 100644
index 0000000..b388204
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentQuery.php
@@ -0,0 +1,202 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host comments
+ */
+class HostcommentQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('comments' => array('c.comment_id', 'ho.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'comments' => array(
+ 'comment_author' => 'c.author_name COLLATE latin1_general_ci',
+ 'comment_author_name' => 'c.author_name',
+ 'comment_data' => 'c.comment_data',
+ 'comment_expiration' => 'CASE c.expires WHEN 1 THEN UNIX_TIMESTAMP(c.expiration_time) ELSE NULL END',
+ 'comment_internal_id' => 'c.internal_comment_id',
+ 'comment_is_persistent' => 'c.is_persistent',
+ 'comment_name' => 'c.name',
+ 'comment_timestamp' => 'UNIX_TIMESTAMP(c.comment_time)',
+ 'comment_type' => "CASE c.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END",
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'object_type' => '(\'host\')'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'hoststatus' => array(
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.14.0', '<')) {
+ $this->columnMap['comments']['comment_name'] = '(NULL)';
+ }
+ $this->select->from(
+ array('c' => $this->prefix . 'comments'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = c.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ $this->joinedVirtualTables['comments'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = c.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php
new file mode 100644
index 0000000..d798d56
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommentdeletionhistoryQuery.php
@@ -0,0 +1,44 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host comment removal records
+ */
+class HostcommentdeletionhistoryQuery extends HostcommenthistoryQuery
+{
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('hch.deletion_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables();
+ $this->select->where("hch.deletion_time > '1970-01-02 00:00:00'");
+ $this->columnMap['commenthistory']['timestamp'] = str_replace(
+ 'comment_time',
+ 'deletion_time',
+ $this->columnMap['commenthistory']['timestamp']
+ );
+ $this->columnMap['commenthistory']['type'] = str_replace(
+ 'END)',
+ "END || '_deleted')",
+ $this->columnMap['commenthistory']['type']
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php
new file mode 100644
index 0000000..b8f166a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcommenthistoryQuery.php
@@ -0,0 +1,197 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host comment history records
+ */
+class HostcommenthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('commenthistory' => array('hch.commenthistory_id', 'ho.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'commenthistory' => array(
+ 'id' => 'hch.commenthistory_id',
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'object_id' => 'hch.object_id',
+ 'object_type' => '(\'host\')',
+ 'output' => "('[' || hch.author_name || '] ' || hch.comment_data)",
+ 'state' => '(-1)',
+ 'timestamp' => 'UNIX_TIMESTAMP(hch.comment_time)',
+ 'type' => "(CASE hch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)"
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('hch.comment_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('hch' => $this->prefix . 'commenthistory'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = hch.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ $this->joinedVirtualTables['commenthistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = hch.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php
new file mode 100644
index 0000000..23b0e90
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostcontactQuery.php
@@ -0,0 +1,247 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host contacts
+ */
+class HostcontactQuery extends IdoQuery
+{
+ protected $allowCustomVars = true;
+
+ protected $groupBase = [
+ 'contacts' => ['co.object_id', 'c.contact_id'],
+ 'timeperiods' => ['ht.timeperiod_id', 'st.timeperiod_id']
+ ];
+
+ protected $groupOrigin = ['contactgroups', 'hosts', 'services'];
+
+ protected $subQueryTargets = [
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ ];
+
+ protected $columnMap = [
+ 'contactgroups' => [
+ 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci',
+ 'contactgroup_name' => 'cgo.name1',
+ 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci'
+ ],
+ 'contacts' => [
+ 'contact_id' => 'c.contact_id',
+ 'contact' => 'co.name1 COLLATE latin1_general_ci',
+ 'contact_name' => 'co.name1',
+ 'contact_alias' => 'c.alias COLLATE latin1_general_ci',
+ 'contact_email' => 'c.email_address COLLATE latin1_general_ci',
+ 'contact_pager' => 'c.pager_address',
+ 'contact_object_id' => 'c.contact_object_id',
+ 'contact_has_host_notfications' => 'c.host_notifications_enabled',
+ 'contact_has_service_notfications' => 'c.service_notifications_enabled',
+ 'contact_can_submit_commands' => 'c.can_submit_commands',
+ 'contact_notify_service_recovery' => 'c.notify_service_recovery',
+ 'contact_notify_service_warning' => 'c.notify_service_warning',
+ 'contact_notify_service_critical' => 'c.notify_service_critical',
+ 'contact_notify_service_unknown' => 'c.notify_service_unknown',
+ 'contact_notify_service_flapping' => 'c.notify_service_flapping',
+ 'contact_notify_service_downtime' => 'c.notify_service_downtime',
+ 'contact_notify_host_recovery' => 'c.notify_host_recovery',
+ 'contact_notify_host_down' => 'c.notify_host_down',
+ 'contact_notify_host_unreachable' => 'c.notify_host_unreachable',
+ 'contact_notify_host_flapping' => 'c.notify_host_flapping',
+ 'contact_notify_host_downtime' => 'c.notify_host_downtime'
+ ],
+ 'hostgroups' => [
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ],
+ 'hosts' => [
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ],
+ 'instances' => [
+ 'instance_name' => 'i.instance_name'
+ ],
+ 'servicegroups' => [
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ],
+ 'services' => [
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ ],
+ 'timeperiods' => [
+ 'contact_notify_host_timeperiod' => 'ht.alias COLLATE latin1_general_ci',
+ 'contact_notify_service_timeperiod' => 'st.alias COLLATE latin1_general_ci'
+ ]
+ ];
+
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ ['c' => $this->prefix . 'contacts'],
+ []
+ )->join(
+ ['co' => $this->prefix . 'objects'],
+ 'co.object_id = c.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10',
+ []
+ );
+
+ $this->joinedVirtualTables = array('contacts' => true);
+ }
+
+ /**
+ * Join contact groups
+ */
+ protected function joinContactgroups()
+ {
+ $this->select->joinLeft(
+ ['cgm' => $this->prefix . 'contactgroup_members'],
+ 'co.object_id = cgm.contact_object_id',
+ []
+ )->joinLeft(
+ ['cg' => $this->prefix . 'contactgroups'],
+ 'cgm.contactgroup_id = cg.contactgroup_id',
+ []
+ )->joinLeft(
+ ['cgo' => $this->prefix . 'objects'],
+ 'cg.contactgroup_object_id = cgo.object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('hosts');
+
+ $this->select->joinLeft(
+ ['hgm' => $this->prefix . 'hostgroup_members'],
+ 'hgm.host_object_id = ho.object_id',
+ []
+ )->joinLeft(
+ ['hg' => $this->prefix . 'hostgroups'],
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ []
+ )->joinLeft(
+ ['hgo' => $this->prefix . 'objects'],
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ []
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->joinLeft(
+ ['hc' => $this->prefix . 'host_contacts'],
+ 'hc.contact_object_id = c.contact_object_id',
+ []
+ )->joinLeft(
+ ['h' => $this->prefix . 'hosts'],
+ 'h.host_id = hc.host_id',
+ []
+ )->joinLeft(
+ ['ho' => $this->prefix . 'objects'],
+ 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ []
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ ['i' => $this->prefix . 'instances'],
+ 'i.instance_id = c.instance_id',
+ []
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ ['sgm' => $this->prefix . 'servicegroup_members'],
+ 'sgm.service_object_id = s.service_object_id',
+ []
+ )->joinLeft(
+ ['sg' => $this->prefix . 'servicegroups'],
+ 'sg.servicegroup_id = sgm.servicegroup_id',
+ []
+ )->joinLeft(
+ ['sgo' => $this->prefix . 'objects'],
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ []
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->requireVirtualTable('hosts');
+
+ $this->select->joinLeft(
+ ['s' => $this->prefix . 'services'],
+ 's.host_object_id = ho.object_id',
+ []
+ )->joinLeft(
+ ['so' => $this->prefix . 'objects'],
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ []
+ );
+ }
+
+ /**
+ * Join time periods
+ */
+ protected function joinTimeperiods()
+ {
+ $this->select->joinLeft(
+ ['ht' => $this->prefix . 'timeperiods'],
+ 'ht.timeperiod_object_id = c.host_timeperiod_object_id',
+ []
+ );
+ $this->select->joinLeft(
+ ['st' => $this->prefix . 'timeperiods'],
+ 'st.timeperiod_object_id = c.service_timeperiod_object_id',
+ []
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $this->requireVirtualTable('hosts');
+
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $this->requireVirtualTable('services');
+
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php
new file mode 100644
index 0000000..62f5ceb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeQuery.php
@@ -0,0 +1,208 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host downtimes
+ */
+class HostdowntimeQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('downtimes' => array('sd.scheduleddowntime_id', 'ho.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimes' => array(
+ 'downtime_author' => 'sd.author_name COLLATE latin1_general_ci',
+ 'downtime_author_name' => 'sd.author_name',
+ 'downtime_comment' => 'sd.comment_data',
+ 'downtime_duration' => 'sd.duration',
+ 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
+ 'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)',
+ 'downtime_internal_id' => 'sd.internal_downtime_id',
+ 'downtime_is_fixed' => 'sd.is_fixed',
+ 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END',
+ 'downtime_is_in_effect' => 'sd.is_in_effect',
+ 'downtime_name' => 'sd.name',
+ 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
+ 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)',
+ 'downtime_start' => 'UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)',
+ 'downtime_triggered_by_id' => 'sd.triggered_by_id',
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'object_type' => '(\'host\')'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'hoststatus' => array(
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.14.0', '<')) {
+ $this->columnMap['downtimes']['downtime_name'] = '(NULL)';
+ }
+ $this->select->from(
+ array('sd' => $this->prefix . 'scheduleddowntime'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'sd.object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ $this->joinedVirtualTables['downtimes'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sd.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php
new file mode 100644
index 0000000..77d91e5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimeendhistoryQuery.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host downtime end history records
+ */
+class HostdowntimeendhistoryQuery extends HostdowntimestarthistoryQuery
+{
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('hdh.actual_end_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables(true);
+ $this->select->where("hdh.actual_end_time > '1970-01-02 00:00:00'");
+ $this->columnMap['downtimehistory']['type'] = "('dt_end')";
+ $this->columnMap['downtimehistory']['timestamp'] = str_replace(
+ 'actual_start_time',
+ 'actual_end_time',
+ $this->columnMap['downtimehistory']['timestamp']
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php
new file mode 100644
index 0000000..54ac6a1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostdowntimestarthistoryQuery.php
@@ -0,0 +1,204 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host downtime start history records
+ */
+class HostdowntimestarthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('downtimehistory' => array('hdh.downtimehistory_id', 'ho.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimehistory' => array(
+ 'id' => 'hdh.downtimehistory_id',
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'object_id' => 'hdh.object_id',
+ 'object_type' => '(\'host\')',
+ 'output' => "('[' || hdh.author_name || '] ' || hdh.comment_data)",
+ 'state' => '(-1)',
+ 'timestamp' => 'UNIX_TIMESTAMP(hdh.actual_start_time)',
+ 'type' => "('dt_start')"
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('hdh.actual_start_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('hdh' => $this->prefix . 'downtimehistory'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = hdh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+
+ if (func_num_args() === 0 || func_get_arg(0) === false) {
+ $this->select->where(
+ "hdh.actual_start_time > '1970-01-02 00:00:00'"
+ );
+ }
+
+ $this->joinedVirtualTables['downtimehistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = hdh.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php
new file mode 100644
index 0000000..ebc346b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingendhistoryQuery.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host flapping end history records
+ */
+class HostflappingendhistoryQuery extends HostflappingstarthistoryQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('hfh' => $this->prefix . 'flappinghistory'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = hfh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+
+ $this->select->where('hfh.event_type = 1001');
+
+ $this->joinedVirtualTables['flappinghistory'] = true;
+
+ $this->columnMap['flappinghistory']['type'] = '(\'flapping_deleted\')';
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php
new file mode 100644
index 0000000..497a493
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostflappingstarthistoryQuery.php
@@ -0,0 +1,200 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host flapping start history records
+ */
+class HostflappingstarthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('flappinghistory' => array('hfh.flappinghistory_id', 'ho.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'flappinghistory' => array(
+ 'id' => 'hfh.flappinghistory_id',
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'object_id' => 'hfh.object_id',
+ 'object_type' => '(\'host\')',
+ 'output' => '(hfh.percent_state_change || \'\')',
+ 'state' => '(-1)',
+ 'timestamp' => 'UNIX_TIMESTAMP(hfh.event_time)',
+ 'type' => '(\'flapping\')'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('hfh.event_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('hfh' => $this->prefix . 'flappinghistory'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = hfh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+
+ $this->select->where('hfh.event_type = 1000');
+
+ $this->joinedVirtualTables['flappinghistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = hfh.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php
new file mode 100644
index 0000000..463fba9
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupQuery.php
@@ -0,0 +1,295 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host groups
+ */
+class HostgroupQuery extends IdoQuery
+{
+ protected $allowCustomVars = true;
+
+ protected $groupBase = array(
+ 'hostgroups' => array('hgo.object_id', 'hg.hostgroup_id'),
+ 'hoststatus' => array('hs.hoststatus_id'),
+ 'servicestatus' => array('ss.servicestatus_id')
+ );
+
+ protected $groupOrigin = array('members');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ protected $columnMap = array(
+ 'contacts' => [
+ 'host_contact' => 'hco.name1'
+ ],
+ 'contactgroups' => [
+ 'host_contactgroup' => 'hcgo.name1'
+ ],
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hoststatus' => array(
+ 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
+ 'host_severity' => '
+ CASE
+ WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
+ THEN 16
+ ELSE
+ CASE
+ WHEN hs.current_state = 0
+ THEN 1
+ ELSE
+ CASE
+ WHEN hs.current_state = 1 THEN 64
+ WHEN hs.current_state = 2 THEN 32
+ ELSE 256
+ END
+ +
+ CASE
+ WHEN hs.problem_has_been_acknowledged = 1 THEN 2
+ WHEN hs.scheduled_downtime_depth > 0 THEN 1
+ ELSE 256
+ END
+ END
+ END',
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'members' => array(
+ 'host_name' => 'ho.name1'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup_name' => 'sgo.name1'
+ ),
+ 'services' => array(
+ 'service_description' => 'so.name2'
+ ),
+ 'servicestatus' => array(
+ 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END',
+ 'service_severity' => '
+ CASE WHEN ss.current_state = 0
+ THEN
+ CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL
+ THEN 16
+ ELSE 0
+ END
+ +
+ CASE WHEN ss.problem_has_been_acknowledged = 1
+ THEN 2
+ ELSE
+ CASE WHEN ss.scheduled_downtime_depth > 0
+ THEN 1
+ ELSE 4
+ END
+ END
+ ELSE
+ CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16
+ WHEN ss.current_state = 1 THEN 32
+ WHEN ss.current_state = 2 THEN 128
+ WHEN ss.current_state = 3 THEN 64
+ ELSE 256
+ END
+ +
+ CASE WHEN hs.current_state > 0
+ THEN 1024
+ ELSE
+ CASE WHEN ss.problem_has_been_acknowledged = 1
+ THEN 512
+ ELSE
+ CASE WHEN ss.scheduled_downtime_depth > 0
+ THEN 256
+ ELSE 2048
+ END
+ END
+ END
+ END',
+ 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('hgo' => $this->prefix . 'objects'),
+ array()
+ )->join(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_object_id = hgo.object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ $this->joinedVirtualTables['hostgroups'] = true;
+ }
+
+ /**
+ * Join contacts
+ */
+ protected function joinContacts()
+ {
+ $this->requireVirtualTable('hosts');
+
+ $this->select->joinLeft(
+ ['hc' => 'icinga_host_contacts'],
+ 'hc.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hco' => 'icinga_objects'],
+ 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10',
+ []
+ );
+ }
+
+ /**
+ * Join contact groups
+ */
+ protected function joinContactgroups()
+ {
+ $this->requireVirtualTable('hosts');
+
+ $this->select->joinLeft(
+ ['hcg' => 'icinga_host_contactgroups'],
+ 'hcg.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hcgo' => 'icinga_objects'],
+ 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('members');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->requireVirtualTable('members');
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = hg.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join members
+ */
+ protected function joinMembers()
+ {
+ $this->select->join(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.hostgroup_id = hg.hostgroup_id',
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'hgm.host_object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sgm.servicegroup_id = sg.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->requireVirtualTable('hosts');
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = h.host_object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ /**
+ * Join service status
+ */
+ protected function joinServicestatus()
+ {
+ $this->requireVirtualTable('services');
+ $this->requireVirtualTable('hoststatus');
+ $this->select->join(
+ array('ss' => $this->prefix . 'servicestatus'),
+ 'ss.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ // Propagate that the "parent" query has to be filtered as well
+ $additionalFilter = clone $filter;
+
+ $this->requireVirtualTable('members');
+
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $this->requireVirtualTable('members');
+
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php
new file mode 100644
index 0000000..a1b7182
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostgroupsummaryQuery.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Zend_Db_Expr;
+use Zend_Db_Select;
+
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host group summary
+ */
+class HostgroupsummaryQuery extends IdoQuery
+{
+ protected $allowCustomVars = true;
+
+ protected $columnMap = array(
+ 'hostgroupsummary' => array(
+ 'hostgroup_alias' => 'hostgroup_alias',
+ 'hostgroup_name' => 'hostgroup_name',
+ 'hosts_down' => 'SUM(CASE WHEN host_state = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_handled' => 'SUM(CASE WHEN host_state = 1 AND host_handled = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_unhandled' => 'SUM(CASE WHEN host_state = 1 AND host_handled = 0 THEN 1 ELSE 0 END)',
+ 'hosts_pending' => 'SUM(CASE WHEN host_state = 99 THEN 1 ELSE 0 END)',
+ 'hosts_severity' => 'MAX(host_severity)',
+ 'hosts_total' => 'SUM(CASE WHEN host_state IS NOT NULL THEN 1 ELSE 0 END)',
+ 'hosts_unreachable' => 'SUM(CASE WHEN host_state = 2 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_handled' => 'SUM(CASE WHEN host_state = 2 AND host_handled = 1 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN host_state = 2 AND host_handled = 0 THEN 1 ELSE 0 END)',
+ 'hosts_up' => 'SUM(CASE WHEN host_state = 0 THEN 1 ELSE 0 END)',
+ 'services_critical' => 'SUM(CASE WHEN service_state = 2 THEN 1 ELSE 0 END)',
+ 'services_critical_handled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 1 THEN 1 ELSE 0 END)',
+ 'services_critical_unhandled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 0 THEN 1 ELSE 0 END)',
+ 'services_ok' => 'SUM(CASE WHEN service_state = 0 THEN 1 ELSE 0 END)',
+ 'services_pending' => 'SUM(CASE WHEN service_state = 99 THEN 1 ELSE 0 END)',
+ 'services_total' => 'SUM(CASE WHEN service_state IS NOT NULL THEN 1 ELSE 0 END)',
+ 'services_unknown' => 'SUM(CASE WHEN service_state = 3 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 1 THEN 1 ELSE 0 END)',
+ 'services_unknown_unhandled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 0 THEN 1 ELSE 0 END)',
+ 'services_warning' => 'SUM(CASE WHEN service_state = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_handled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_unhandled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 0 THEN 1 ELSE 0 END)',
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $summaryQuery;
+
+ /**
+ * Subqueries used for the summary query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Count query
+ *
+ * @var IdoQuery
+ */
+ protected $countQuery;
+
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ $this->countQuery->applyFilter(clone $filter);
+ return $this;
+ }
+
+ protected function joinBaseTables()
+ {
+ $this->countQuery = $this->createSubQuery(
+ 'Hostgroup',
+ array()
+ );
+ $hosts = $this->createSubQuery(
+ 'Hostgroup',
+ array(
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'host_handled',
+ 'host_severity',
+ 'host_state',
+ 'service_handled' => new Zend_Db_Expr('NULL'),
+ 'service_severity' => new Zend_Db_Expr('0'),
+ 'service_state' => new Zend_Db_Expr('NULL'),
+ )
+ );
+ $this->subQueries[] = $hosts;
+ $services = $this->createSubQuery(
+ 'Hostgroup',
+ array(
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'host_handled' => new Zend_Db_Expr('NULL'),
+ 'host_severity' => new Zend_Db_Expr('0'),
+ 'host_state' => new Zend_Db_Expr('NULL'),
+ 'service_handled',
+ 'service_severity',
+ 'service_state'
+ )
+ );
+ $this->subQueries[] = $services;
+ $emptyGroups = $this->createSubQuery(
+ 'Emptyhostgroup',
+ [
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'host_handled' => new Zend_Db_Expr('NULL'),
+ 'host_severity' => new Zend_Db_Expr('0'),
+ 'host_state' => new Zend_Db_Expr('NULL'),
+ 'service_handled' => new Zend_Db_Expr('NULL'),
+ 'service_severity' => new Zend_Db_Expr('0'),
+ 'service_state' => new Zend_Db_Expr('NULL'),
+ ]
+ );
+ $this->subQueries[] = $emptyGroups;
+ $this->summaryQuery = $this->db->select()->union(
+ [$hosts, $services, $emptyGroups],
+ Zend_Db_Select::SQL_UNION_ALL
+ );
+ $this->select->from(array('hostgroupsummary' => $this->summaryQuery), array());
+ $this->group(array('hostgroup_name', 'hostgroup_alias'));
+ $this->joinedVirtualTables['hostgroupsummary'] = true;
+ }
+
+ public function getCountQuery()
+ {
+ $count = $this->countQuery->select();
+ $this->countQuery->applyFilterSql($count);
+ $count->columns(array('hgo.object_id'));
+ $count->group(array('hgo.object_id'));
+ return $this->db->select()->from($count, array('cnt' => 'COUNT(*)'));
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php
new file mode 100644
index 0000000..284468e
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HostnotificationQuery.php
@@ -0,0 +1,283 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host notifications
+ */
+class HostnotificationQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'contactnotifications' => array(
+ 'notification_contact_name' => 'co.name1'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
+ 'host_alias' => 'h.alias COLLATE latin1_general_ci',
+ ),
+ 'history' => array(
+ 'output' => null,
+ 'state' => 'hn.state',
+ 'timestamp' => 'UNIX_TIMESTAMP(hn.start_time)',
+ 'type' => '
+ CASE hn.notification_reason
+ WHEN 1 THEN \'notification_ack\'
+ WHEN 2 THEN \'notification_flapping\'
+ WHEN 3 THEN \'notification_flapping_end\'
+ WHEN 5 THEN \'notification_dt_start\'
+ WHEN 6 THEN \'notification_dt_end\'
+ WHEN 7 THEN \'notification_dt_end\'
+ WHEN 8 THEN \'notification_custom\'
+ ELSE \'notification_state\'
+ END',
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'notifications' => array(
+ 'id' => 'hn.notification_id',
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'notification_output' => 'hn.output',
+ 'notification_reason' => 'hn.notification_reason',
+ 'notification_state' => 'hn.state',
+ 'notification_timestamp' => 'UNIX_TIMESTAMP(hn.start_time)',
+ 'object_type' => '(\'host\')'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression) {
+ switch ($filter->getColumn()) {
+ case 'output':
+ $this->requireColumn('output');
+ $filter->setColumn('hn.output');
+ return null;
+ case 'timestamp':
+ case 'notification_timestamp':
+ $this->requireColumn($filter->getColumn());
+ $filter->setColumn('hn.start_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ switch ($this->ds->getDbType()) {
+ case 'mysql':
+ $concattedContacts = "GROUP_CONCAT("
+ . "DISTINCT co.name1 ORDER BY co.name1 SEPARATOR ', '"
+ . ") COLLATE latin1_general_ci";
+ break;
+ case 'pgsql':
+ // TODO: Find a way to order the contact alias list:
+ $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(DISTINCT co.name1), ', ')";
+ break;
+ }
+ $this->columnMap['history']['output'] = "('[' || $concattedContacts || '] ' || hn.output)";
+
+ $this->select->from(
+ array('hn' => $this->prefix . 'notifications'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = hn.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ $this->joinedVirtualTables['notifications'] = true;
+ }
+
+ /**
+ * Join virtual table history
+ */
+ protected function joinHistory()
+ {
+ $this->requireVirtualTable('contactnotifications');
+ }
+
+ /**
+ * Join contact notifications
+ */
+ protected function joinContactnotifications()
+ {
+ $this->select->joinLeft(
+ array('cn' => $this->prefix . 'contactnotifications'),
+ 'cn.notification_id = hn.notification_id',
+ array()
+ );
+ $this->select->joinLeft(
+ array('co' => $this->prefix . 'objects'),
+ 'co.object_id = cn.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10',
+ array()
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = hn.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroup()
+ {
+ $group = array();
+
+ if ($this->hasJoinedVirtualTable('history')
+ || $this->hasJoinedVirtualTable('services')
+ || $this->hasJoinedVirtualTable('hostgroups')
+ ) {
+ $group = array('hn.notification_id', 'ho.object_id');
+ if ($this->hasJoinedVirtualTable('contactnotifications') && !$this->hasJoinedVirtualTable('history')) {
+ $group[] = 'co.object_id';
+ }
+ } elseif ($this->hasJoinedVirtualTable('contactnotifications')) {
+ $group = array('hn.notification_id', 'co.object_id', 'ho.object_id');
+ }
+
+ if (! empty($group)) {
+ if ($this->hasJoinedVirtualTable('hosts')) {
+ $group[] = 'h.host_id';
+ }
+
+ if ($this->hasJoinedVirtualTable('instances')) {
+ $group[] = 'i.instance_id';
+ }
+ }
+
+ return $group;
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php
new file mode 100644
index 0000000..ac85c1f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatehistoryQuery.php
@@ -0,0 +1,222 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host state history records
+ */
+class HoststatehistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('statehistory' => array('hh.statehistory_id', 'ho.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'services');
+
+ /**
+ * Array to map type names to type ids for query optimization
+ *
+ * @var array
+ */
+ protected $types = array(
+ 'soft_state' => 0,
+ 'hard_state' => 1
+ );
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ ),
+ 'statehistory' => array(
+ 'id' => 'hh.statehistory_id',
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'object_id' => 'hh.object_id',
+ 'object_type' => '(\'host\')',
+ 'output' => '(CASE WHEN hh.state_type = 1 THEN hh.output ELSE \'[ \' || hh.current_check_attempt || \'/\' || hh.max_check_attempts || \' ] \' || hh.output END)',
+ 'state' => 'hh.state',
+ 'timestamp' => 'UNIX_TIMESTAMP(hh.state_time)',
+ 'type' => "(CASE WHEN hh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)"
+ ),
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression) {
+ switch ($filter->getColumn()) {
+ case 'timestamp':
+ $this->requireColumn('timestamp');
+ $filter->setColumn('hh.state_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ case 'type':
+ if (! is_array($filter->getExpression())) {
+ $this->requireColumn('type');
+ $filter->setColumn('hh.state_type');
+ if (isset($this->types[$filter->getExpression()])) {
+ $filter->setExpression($this->types[$filter->getExpression()]);
+ } else {
+ $filter->setExpression(-1);
+ }
+
+ return null;
+ }
+ }
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('hh' => $this->prefix . 'statehistory'),
+ array()
+ )->join(
+ array('ho' => $this->prefix . 'objects'),
+ 'ho.object_id = hh.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ $this->joinedVirtualTables['statehistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = hh.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php
new file mode 100644
index 0000000..e1b5480
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php
@@ -0,0 +1,338 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class HoststatusQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('hosts' => array('ho.object_id', 'h.host_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'checktimeperiods' => array(
+ 'host_check_timeperiod' => 'ctp.alias COLLATE latin1_general_ci'
+ ),
+ 'contacts' => [
+ 'host_contact' => 'hco.name1'
+ ],
+ 'contactgroups' => [
+ 'host_contactgroup' => 'hcgo.name1'
+ ],
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_action_url' => 'h.action_url',
+ 'host_address' => 'h.address',
+ 'host_address6' => 'h.address6',
+ 'host_alias' => 'h.alias',
+ 'host_check_interval' => '(h.check_interval * 60)',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
+ 'host_icon_image' => 'h.icon_image',
+ 'host_icon_image_alt' => 'h.icon_image_alt',
+ 'host_ipv4' => 'INET_ATON(h.address)',
+ 'host_name' => 'ho.name1',
+ 'host_notes' => 'h.notes',
+ 'host_notes_url' => 'h.notes_url',
+ 'object_type' => '(\'host\')',
+ 'object_id' => 'ho.object_id'
+ ),
+ 'hoststatus' => array(
+ 'host_acknowledged' => 'hs.problem_has_been_acknowledged',
+ 'host_acknowledgement_type' => 'hs.acknowledgement_type',
+ 'host_active_checks_enabled' => 'hs.active_checks_enabled',
+ 'host_active_checks_enabled_changed' => 'CASE WHEN hs.active_checks_enabled = h.active_checks_enabled THEN 0 ELSE 1 END',
+ 'host_attempt' => 'hs.current_check_attempt || \'/\' || hs.max_check_attempts',
+ 'host_check_command' => 'hs.check_command',
+ 'host_check_execution_time' => 'hs.execution_time',
+ 'host_check_latency' => 'hs.latency',
+ 'host_check_source' => 'hs.check_source',
+ 'host_check_type' => 'hs.check_type',
+ 'host_current_check_attempt' => 'hs.current_check_attempt',
+ 'host_current_notification_number' => 'hs.current_notification_number',
+ 'host_event_handler' => 'hs.event_handler',
+ 'host_event_handler_enabled' => 'hs.event_handler_enabled',
+ 'host_event_handler_enabled_changed' => 'CASE WHEN hs.event_handler_enabled = h.event_handler_enabled THEN 0 ELSE 1 END',
+ 'host_failure_prediction_enabled' => 'hs.failure_prediction_enabled',
+ 'host_flap_detection_enabled' => 'hs.flap_detection_enabled',
+ 'host_flap_detection_enabled_changed' => 'CASE WHEN hs.flap_detection_enabled = h.flap_detection_enabled THEN 0 ELSE 1 END',
+ 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
+ 'host_hard_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE CASE WHEN hs.state_type = 1 THEN hs.current_state ELSE hs.last_hard_state END END',
+ 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
+ 'host_is_flapping' => 'hs.is_flapping',
+ 'host_is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END',
+ 'host_is_reachable' => 'hs.is_reachable',
+ 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)',
+ 'host_last_hard_state' => 'hs.last_hard_state',
+ 'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)',
+ 'host_last_notification' => 'UNIX_TIMESTAMP(hs.last_notification)',
+ 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
+ 'host_last_state_change_ts' => 'hs.last_state_change',
+ 'host_last_time_down' => 'UNIX_TIMESTAMP(hs.last_time_down)',
+ 'host_last_time_unreachable' => 'UNIX_TIMESTAMP(hs.last_time_unreachable)',
+ 'host_last_time_up' => 'UNIX_TIMESTAMP(hs.last_time_up)',
+ 'host_long_output' => 'hs.long_output',
+ 'host_max_check_attempts' => 'hs.max_check_attempts',
+ 'host_modified_host_attributes' => 'hs.modified_host_attributes',
+ 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
+ 'host_next_notification' => 'UNIX_TIMESTAMP(hs.next_notification)',
+ 'host_next_update' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
+ THEN
+ CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) + (hs.normal_check_interval * 60) ELSE NULL END
+ ELSE
+ UNIX_TIMESTAMP(hs.next_check)
+ + (CASE WHEN
+ COALESCE(hs.current_state, 0) > 0 AND hs.state_type = 0
+ THEN
+ hs.retry_check_interval
+ ELSE
+ hs.normal_check_interval
+ END * 60)
+ + (CEIL(hs.execution_time + hs.latency) * 2)
+ END',
+ 'host_no_more_notifications' => 'hs.no_more_notifications',
+ 'host_normal_check_interval' => 'hs.normal_check_interval',
+ 'host_notifications_enabled' => 'hs.notifications_enabled',
+ 'host_notifications_enabled_changed' => 'CASE WHEN hs.notifications_enabled = h.notifications_enabled THEN 0 ELSE 1 END',
+ 'host_obsessing' => 'hs.obsess_over_host',
+ 'host_obsessing_changed' => 'CASE WHEN hs.obsess_over_host = h.obsess_over_host THEN 0 ELSE 1 END',
+ 'host_output' => 'hs.output',
+ 'host_passive_checks_enabled' => 'hs.passive_checks_enabled',
+ 'host_passive_checks_enabled_changed' => 'CASE WHEN hs.passive_checks_enabled = h.passive_checks_enabled THEN 0 ELSE 1 END',
+ 'host_percent_state_change' => 'hs.percent_state_change',
+ 'host_perfdata' => 'hs.perfdata',
+ 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
+ 'host_problem_has_been_acknowledged' => 'hs.problem_has_been_acknowledged',
+ 'host_process_performance_data' => 'hs.process_performance_data',
+ 'host_retry_check_interval' => 'hs.retry_check_interval',
+ 'host_scheduled_downtime_depth' => 'hs.scheduled_downtime_depth',
+ 'host_severity' => '
+ CASE
+ WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
+ THEN 16
+ ELSE
+ CASE
+ WHEN hs.current_state = 0
+ THEN 1
+ ELSE
+ CASE
+ WHEN hs.current_state = 1 THEN 64
+ WHEN hs.current_state = 2 THEN 32
+ ELSE 256
+ END
+ +
+ CASE
+ WHEN hs.problem_has_been_acknowledged = 1 THEN 2
+ WHEN hs.scheduled_downtime_depth > 0 THEN 1
+ ELSE 256
+ END
+ END
+ END',
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
+ 'host_state_type' => 'hs.state_type',
+ 'host_status_update_time' => 'hs.status_update_time',
+ 'host_unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END',
+ 'problems' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.10.0', '<')) {
+ $this->columnMap['hoststatus']['host_check_source'] = '(NULL)';
+ }
+ if (version_compare($this->getIdoVersion(), '1.13.0', '<')) {
+ $this->columnMap['hoststatus']['host_is_reachable'] = '(NULL)';
+ }
+
+ $this->select->from(
+ array('ho' => $this->prefix . 'objects'),
+ array()
+ )->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = ho.object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ );
+ $this->joinedVirtualTables['hosts'] = true;
+ }
+
+ /**
+ * Join check time periods
+ */
+ protected function joinChecktimeperiods()
+ {
+ $this->select->joinLeft(
+ array('ctp' => $this->prefix . 'timeperiods'),
+ 'ctp.timeperiod_object_id = h.check_timeperiod_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join contacts
+ */
+ protected function joinContacts()
+ {
+ $this->select->joinLeft(
+ ['hc' => 'icinga_host_contacts'],
+ 'hc.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hco' => 'icinga_objects'],
+ 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10',
+ []
+ );
+ }
+
+ /**
+ * Join contact groups
+ */
+ protected function joinContactgroups()
+ {
+ $this->select->joinLeft(
+ ['hcg' => 'icinga_host_contactgroups'],
+ 'hcg.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hcgo' => 'icinga_objects'],
+ 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = ho.object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = ho.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = ho.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = s.service_object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->requireVirtualTable('hosts');
+ $this->select->joinLeft(
+ array('s' => $this->prefix . 'services'),
+ 's.host_object_id = h.host_object_id',
+ array()
+ )->joinLeft(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 'ho.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('services');
+
+ return ['s.host_object_id', 'ho.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php
new file mode 100644
index 0000000..5d79143
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatussummaryQuery.php
@@ -0,0 +1,91 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host group summaries
+ */
+class HoststatussummaryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'hoststatussummary' => array(
+ 'hosts_down' => 'SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_handled' => 'SUM(CASE WHEN state = 1 AND handled = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_unhandled' => 'SUM(CASE WHEN state = 1 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'hosts_pending' => 'SUM(CASE WHEN state = 99 THEN 1 ELSE 0 END)',
+ 'hosts_total' => 'SUM(1)',
+ 'hosts_unreachable' => 'SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_handled' => 'SUM(CASE WHEN state = 2 AND handled = 1 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN state = 2 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'hosts_up' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)'
+ )
+ );
+
+ /**
+ * The host status sub select
+ *
+ * @var HostStatusQuery
+ */
+ protected $subSelect;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ return $this->subSelect->allowsCustomVars();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ $this->subSelect->applyFilter(clone $filter);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ // TODO(el): Allow to switch between hard and soft states
+ $this->subSelect = $this->createSubQuery(
+ 'Hoststatus',
+ array(
+ 'handled' => 'host_handled',
+ 'state' => 'host_state',
+ 'state_change' => 'host_last_state_change'
+ )
+ );
+ $this->select->from(
+ array('hoststatussummary' => $this->subSelect->setIsSubQuery(true)),
+ array()
+ );
+ $this->joinedVirtualTables['hoststatussummary'] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->subSelect->where($condition, $value);
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->subSelect->whereEx($ex);
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
new file mode 100644
index 0000000..bd7a077
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
@@ -0,0 +1,1599 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterNot;
+use Zend_Db_Expr;
+use Icinga\Application\Icinga;
+use Icinga\Application\Hook;
+use Icinga\Application\Logger;
+use Icinga\Data\Db\DbQuery;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Exception\IcingaException;
+use Icinga\Exception\NotImplementedError;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Exception\QueryException;
+use Icinga\Web\Session;
+use Icinga\Module\Monitoring\Data\ColumnFilterIterator;
+use Zend_Db_Select;
+
+/**
+ * Base class for Ido Queries
+ *
+ * This is the base class for all Ido queries and should be extended for new queries
+ * The starting point for implementations is the columnMap attribute. This is an asscociative array in the
+ * following form:
+ *
+ * <pre>
+ * <code>
+ * array(
+ * 'virtualTable' => array(
+ * 'fieldalias1' => 'queryColumn1',
+ * 'fieldalias2' => 'queryColumn2',
+ * ....
+ * ),
+ * 'virtualTable2' => array(
+ * 'host' => 'host_name1'
+ * )
+ * )
+ * </code>
+ * </pre>
+ *
+ * This allows you to select e.g. fieldalias1, which automatically calls the query code for joining 'virtualTable'. If
+ * you afterwards select 'host', 'virtualTable2' will be joined. The joining logic is up to you, in order to make the
+ * above example work you need to implement the joinVirtualTable() method which contain your
+ * custom (Zend_Db) logic for joining, filtering and querying the data you want.
+ *
+ */
+abstract class IdoQuery extends DbQuery
+{
+ /**
+ * The prefix to use
+ *
+ * @var string
+ */
+ protected $prefix;
+
+ /**
+ * An array to map aliases to column names
+ *
+ * @var array
+ */
+ protected $idxAliasColumn;
+
+ /**
+ * An array to map aliases to table names
+ *
+ * @var array
+ */
+ protected $idxAliasTable;
+
+ /**
+ * An array to map custom aliases to aliases
+ *
+ * @var array
+ */
+ protected $idxCustomAliases;
+
+ /**
+ * The column map containing all filterable columns
+ *
+ * This must be overwritten by child classes, in the format
+ * array(
+ * 'virtualTable' => array(
+ * 'fieldalias1' => 'queryColumn1',
+ * 'fieldalias2' => 'queryColumn2',
+ * ....
+ * )
+ * )
+ *
+ * @var array
+ */
+ protected $columnMap = array();
+
+ /**
+ * Custom vars available for this query
+ *
+ * @var array
+ */
+ protected $customVars = array();
+
+ /**
+ * Printf compatible string to joins custom vars
+ *
+ * - %1$s Source field, contain the object_id
+ * - %2$s Alias used for the relation
+ * - %3$s Name of the CustomVariable
+ *
+ * @var string
+ */
+ private $customVarsJoinTemplate = '%1$s = %2$s.object_id AND %2$s.varname = %3$s';
+
+ /**
+ * An array with all 'virtual' tables that are already joined
+ *
+ * Virtual tables are the keys of the columnMap array and require a
+ * join%VirtualTableName%() method to be defined in the concrete
+ * query
+ *
+ * @var array
+ */
+ protected $joinedVirtualTables = array();
+
+ /**
+ * A map of virtual table names and corresponding hook instances
+ *
+ * Joins for those tables will be delegated to them
+ *
+ * @var array
+ */
+ protected $hookedVirtualTables = array();
+
+ /**
+ * List of column aliases used for sorting the result
+ *
+ * @var array
+ */
+ protected $orderColumns = array();
+
+ /**
+ * Table to columns map which have to be added to the GROUP BY list if the query is grouped
+ *
+ * @var array
+ */
+ protected $groupBase = array();
+
+ /**
+ * List of table names which initiate grouping if one of them is joined
+ *
+ * @var array
+ */
+ protected $groupOrigin = array();
+
+ /**
+ * Map of table names to query names for which to create subquery filters
+ *
+ * @var array
+ */
+ protected $subQueryTargets = array();
+
+ /**
+ * The primary key column for the instances table
+ *
+ * @var string
+ */
+ protected $instance_id = 'instance_id';
+
+ /**
+ * The primary key column for the objects table
+ *
+ * @var string
+ */
+ protected $object_id = 'object_id';
+
+ /**
+ * The primary key column for the acknowledgements table
+ *
+ * @var string
+ */
+ protected $acknowledgement_id = 'acknowledgement_id';
+
+ /**
+ * The primary key column for the commenthistory table
+ *
+ * @var string
+ */
+ protected $commenthistory_id = 'commenthistory_id';
+
+ /**
+ * The primary key column for the contactnotifications table
+ *
+ * @var string
+ */
+ protected $contactnotification_id = 'contactnotification_id';
+
+ /**
+ * The primary key column for the downtimehistory table
+ *
+ * @var string
+ */
+ protected $downtimehistory_id = 'downtimehistory_id';
+
+ /**
+ * The primary key column for the flappinghistory table
+ *
+ * @var string
+ */
+ protected $flappinghistory_id = 'flappinghistory_id';
+
+ /**
+ * The primary key column for the notifications table
+ *
+ * @var string
+ */
+ protected $notification_id = 'notification_id';
+
+ /**
+ * The primary key column for the statehistory table
+ *
+ * @var string
+ */
+ protected $statehistory_id = 'statehistory_id';
+
+ /**
+ * The primary key column for the comments table
+ *
+ * @var string
+ */
+ protected $comment_id = 'comment_id';
+
+ /**
+ * The primary key column for the customvariablestatus table
+ *
+ * @var string
+ */
+ protected $customvariablestatus_id = 'customvariablestatus_id';
+
+ /**
+ * The primary key column for the hoststatus table
+ *
+ * @var string
+ */
+ protected $hoststatus_id = 'hoststatus_id';
+
+ /**
+ * The primary key column for the programstatus table
+ *
+ * @var string
+ */
+ protected $programstatus_id = 'programstatus_id';
+
+ /**
+ * The primary key column for the runtimevariables table
+ *
+ * @var string
+ */
+ protected $runtimevariable_id = 'runtimevariable_id';
+
+ /**
+ * The primary key column for the scheduleddowntime table
+ *
+ * @var string
+ */
+ protected $scheduleddowntime_id = 'scheduleddowntime_id';
+
+ /**
+ * The primary key column for the servicestatus table
+ *
+ * @var string
+ */
+ protected $servicestatus_id = 'servicestatus_id';
+
+ /**
+ * The primary key column for the contactstatus table
+ *
+ * @var string
+ */
+ protected $contactstatus_id = 'contactstatus_id';
+
+ /**
+ * The primary key column for the commands table
+ *
+ * @var string
+ */
+ protected $command_id = 'command_id';
+
+ /**
+ * The primary key column for the contactgroup_members table
+ *
+ * @var string
+ */
+ protected $contactgroup_member_id = 'contactgroup_member_id';
+
+ /**
+ * The primary key column for the contactgroups table
+ *
+ * @var string
+ */
+ protected $contactgroup_id = 'contactgroup_id';
+
+ /**
+ * The primary key column for the contacts table
+ *
+ * @var string
+ */
+ protected $contact_id = 'contact_id';
+
+ /**
+ * The primary key column for the customvariables table
+ *
+ * @var string
+ */
+ protected $customvariable_id = 'customvariable_id';
+
+ /**
+ * The primary key column for the host_contactgroups table
+ *
+ * @var string
+ */
+ protected $host_contactgroup_id = 'host_contactgroup_id';
+
+ /**
+ * The primary key column for the host_contacts table
+ *
+ * @var string
+ */
+ protected $host_contact_id = 'host_contact_id';
+
+ /**
+ * The primary key column for the hostgroup_members table
+ *
+ * @var string
+ */
+ protected $hostgroup_member_id = 'hostgroup_member_id';
+
+ /**
+ * The primary key column for the hostgroups table
+ *
+ * @var string
+ */
+ protected $hostgroup_id = 'hostgroup_id';
+
+ /**
+ * The primary key column for the hosts table
+ *
+ * @var string
+ */
+ protected $host_id = 'host_id';
+
+ /**
+ * The primary key column for the service_contactgroup table
+ *
+ * @var string
+ */
+ protected $service_contactgroup_id = 'service_contactgroup_id';
+
+ /**
+ * The primary key column for the service_contact table
+ *
+ * @var string
+ */
+ protected $service_contact_id = 'service_contact_id';
+
+ /**
+ * The primary key column for the servicegroup_members table
+ *
+ * @var string
+ */
+ protected $servicegroup_member_id = 'servicegroup_member_id';
+
+ /**
+ * The primary key column for the servicegroups table
+ *
+ * @var string
+ */
+ protected $servicegroup_id = 'servicegroup_id';
+
+ /**
+ * The primary key column for the services table
+ *
+ * @var string
+ */
+ protected $service_id = 'service_id';
+
+ /**
+ * The primary key column for the timeperiods table
+ *
+ * @var string
+ */
+ protected $timeperiod_id = 'timeperiod_id';
+
+ /**
+ * An array containing Column names that cause an aggregation of the query
+ *
+ * @var array
+ */
+ protected $aggregateColumnIdx = array();
+
+ /**
+ * True to allow customvar filters and queries
+ *
+ * @var bool
+ */
+ protected $allowCustomVars = false;
+
+ /**
+ * Current IDO version. This is bullshit and needs to be moved somewhere
+ * else. As someone decided that we need no Backend-specific connection
+ * class unfortunately there is no better place right now. And as of the
+ * 'check_source' patch we need a quick fix immediately. So here you go.
+ *
+ * TODO: Fix this.
+ *
+ * @var string
+ */
+ protected static $idoVersion;
+
+ /**
+ * List of column aliases mapped to their table where the COLLATE SQL-instruction has been removed
+ *
+ * This list is being populated in case of a PostgreSQL backend only,
+ * to ensure case-insensitive string comparison in WHERE clauses.
+ *
+ * @var array
+ */
+ protected $caseInsensitiveColumns;
+
+ /**
+ * Return true when the column is an aggregate column
+ *
+ * @param String $column The column to test
+ * @return bool True when the column is an aggregate column
+ */
+ public function isAggregateColumn($column)
+ {
+ return array_key_exists($column, $this->aggregateColumnIdx);
+ }
+
+ /**
+ * Order the result by the given alias
+ *
+ * @param string $alias The column alias to order by
+ * @param int $dir The sort direction or null to use the default direction
+ *
+ * @return $this
+ */
+ public function order($alias, $dir = null)
+ {
+ $this->requireColumn($alias);
+
+ if ($this->isCustomvar($alias)) {
+ $column = $this->getCustomvarColumnName($alias);
+ } elseif ($this->hasAliasName($alias)) {
+ $column = $this->aliasToColumnName($alias);
+ $table = $this->aliasToTableName($alias);
+ if (isset($this->caseInsensitiveColumns[$table][$alias])) {
+ $column = 'LOWER(' . $column . ')';
+ }
+ } else {
+ Logger::info('Can\'t order by column ' . $alias);
+ return $this;
+ }
+
+ $this->orderColumns[] = $alias;
+ return parent::order($column, $dir);
+ }
+
+ /**
+ * Return true when the given field can be used for filtering
+ *
+ * @param String $field The field to test
+ * @return bool True when the field can be used for querying, otherwise false
+ */
+ public function isValidFilterTarget($field)
+ {
+ return $this->getMappedField($field) !== null;
+ }
+
+ /**
+ * Return the resolved field for an alias
+ *
+ * @param String $field The alias to resolve
+ * @return String The resolved alias or null if unknown
+ */
+ public function getMappedField($field)
+ {
+ foreach ($this->columnMap as $columnSource => $columnSet) {
+ if (isset($columnSet[$field])) {
+ return $columnSet[$field];
+ }
+ }
+ if ($this->isCustomVar($field)) {
+ return $this->getCustomvarColumnName($field);
+ }
+ return null;
+ }
+
+ public function distinct()
+ {
+ $this->select->distinct();
+ return $this;
+ }
+
+ /**
+ * Prepare the given query so that it can be linked to the parent
+ *
+ * @param IdoQuery $query
+ * @param string $name
+ * @param FilterExpression $filter The filter which initiated the sub query
+ * @param bool $and Whether it's an AND filter
+ * @param bool $negate Whether it's an != filter
+ * @param FilterExpression $additionalFilter Filters which should be applied to the "parent" query
+ *
+ * @return array The first value is their, the second our key column
+ *
+ * @throws NotImplementedError In case the given query is unknown
+ */
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ throw new NotImplementedError('Query "%s" is unknown', $name);
+ }
+
+ /**
+ * Create and return a sub-query filter for the given filter expression
+ *
+ * @param FilterExpression $filter
+ * @param string $queryName
+ *
+ * @return Filter
+ *
+ * @throws QueryException
+ */
+ protected function createSubQueryFilter(FilterExpression $filter, $queryName)
+ {
+ $expr = $filter->getExpression();
+ $op = $filter->getSign();
+
+ if ($op === '=' && ! is_array($expr) && $op !== '!=') {
+ // We're joining a subquery only if the filter is enclosed in parentheses or if it's a != filter,
+ // e.g. hostgroup_name=(linux...), hostgroup_name!=linux, hostgroup_name!=(linux...)
+ throw new NotImplementedError('');
+ }
+
+ $subQuery = $this->createSubQuery($queryName);
+ $subQuery->setIsSubQuery();
+
+ $subQueryFilter = clone $filter;
+
+ if ($op === '!=') {
+ $negate = true;
+ if (! is_array($expr)) {
+ // We assume that expression is an array later on but we'll support subquery joins for != filters
+ // which are not enclosed in parentheses
+ $expr = [$expr];
+ }
+ } else {
+ $negate = false;
+ }
+
+ if (count($expr) === 1 && strpos($expr[0], '&') !== false) {
+ // Our current filter implementation does not specify & as a control character so the count of the
+ // expression array is always one in this case
+ $expr = array_unique(explode('&', $expr[0]));
+ $subQueryFilter->setExpression($expr);
+ $and = true;
+ } else {
+ // Or filters are respected by our filter implementation. No special handling needed here
+ $and = false;
+ }
+
+ $alias = $filter->getColumn();
+ $column = $subQuery->aliasToColumnName($alias);
+ if (isset($this->caseInsensitiveColumns[$subQuery->aliasToTableName($alias)][$alias])) {
+ $column = 'LOWER( ' . $column . ' )';
+ $subQueryFilter->setColumn($column);
+ $subQueryFilter->setExpression(array_map('strtolower', (array) $subQueryFilter->getExpression()));
+ } else {
+ $subQueryFilter->setColumn($column);
+ }
+
+ $additional = null;
+
+ list($theirs, $ours) = $this->joinSubQuery($subQuery, $queryName, $subQueryFilter, $and, $negate, $additional);
+
+ $zendSelect = $subQuery->select();
+ $fromPart = $zendSelect->getPart($zendSelect::FROM);
+ $zendSelect->reset($zendSelect::FROM);
+
+ foreach ($fromPart as $correlationName => $joinOptions) {
+ if (isset($joinOptions['joinCondition'])) {
+ $joinOptions['joinCondition'] = preg_replace(
+ '/(?<=^|\s)\w+(?=\.)/',
+ 'sub_$0',
+ $joinOptions['joinCondition']
+ );
+ }
+
+ $name = ['sub_' . $correlationName => $joinOptions['tableName']];
+ switch ($joinOptions['joinType']) {
+ case $zendSelect::FROM:
+ $zendSelect->from($name);
+ break;
+ case $zendSelect::INNER_JOIN:
+ $zendSelect->joinInner($name, $joinOptions['joinCondition'], null);
+ break;
+ case $zendSelect::LEFT_JOIN:
+ $zendSelect->joinLeft($name, $joinOptions['joinCondition'], null);
+ break;
+ default:
+ // TODO: Add support for other join types if required?
+ throw new QueryException(
+ 'Unsupported join type %s. Cannot create subquery filter.',
+ $joinOptions['joinType']
+ );
+ }
+ }
+
+ if ($and || $negate) {
+ // Having is only required for AND and != filters,
+ // e.g. hostgroup_name=(ping&linux), hostgroup_name!=ping, hostgroup_name!=(ping|linux)
+ $groups = $subQuery->getGroup();
+ if (! empty($groups)) {
+ $group = $groups[0];
+ $group = preg_replace('/(?<=^|\s)\w+(?=\.)/', 'sub_$0', $group);
+
+ $cnt = count($expr);
+
+ $subQuery->select()->having("COUNT(DISTINCT $group) >= $cnt");
+ }
+ }
+
+ $subQueryFilter->setColumn(preg_replace(
+ '/(?<=^|\s)\w+(?=\.)/',
+ 'sub_$0',
+ $column
+ ));
+
+ if ($negate) {
+ // != will be NOT EXISTS later
+ $subQueryFilter = $subQueryFilter->setSign('=');
+ }
+
+ $subQueryFilter = $subQueryFilter->andFilter(Filter::where(
+ preg_replace('/(?<=^|\s)\w+(?=\.)/', 'sub_$0', $theirs),
+ new Zend_Db_Expr($ours)
+ ));
+
+ $subQuery
+ ->setFilter($subQueryFilter)
+ ->clearGroupingRules()
+ ->select()
+ ->reset('columns')
+ ->columns([new Zend_Db_Expr('1')]);
+
+ // EXISTS is the column name because without any column $this->isCustomVar() fails badly otherwise.
+ // Additionally it bypasses the non-required optimizations made by our filter rendering implementation.
+ $exists = new FilterExpression($negate ? 'NOT EXISTS' : 'EXISTS', '', new Zend_Db_Expr($subQuery));
+
+ if ($additional !== null) {
+ return Filter::matchAll($exists, $additional);
+ }
+
+ return $exists;
+ }
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression) {
+ $alias = $filter->getColumn();
+
+ $virtualTable = $this->aliasToTableName($alias);
+ if (isset($this->subQueryTargets[$virtualTable])) {
+ try {
+ return $this->createSubQueryFilter($filter, $this->subQueryTargets[$virtualTable]);
+ } catch (NotImplementedError $e) {
+ // We don't want to create subquery filters in all cases
+ }
+ }
+
+ $this->requireColumn($alias);
+
+ if ($this->isCustomvar($alias)) {
+ $column = $this->getCustomvarColumnName($alias);
+ } else {
+ $column = $this->aliasToColumnName($alias);
+ if (isset($this->caseInsensitiveColumns[$this->aliasToTableName($alias)][$alias])) {
+ $column = 'LOWER(' . $column . ')';
+ $expression = $filter->getExpression();
+ if (is_array($expression)) {
+ $filter->setExpression(array_map('strtolower', $expression));
+ } else {
+ $filter->setExpression(strtolower($expression));
+ }
+ }
+ }
+
+ $filter->setColumn($column);
+ } else {
+ if (! $filter instanceof FilterNot) {
+ // Allow subquery filters in a filter chain
+ $columns = $filter->listFilteredColumns();
+ if (count($columns) === 1) {
+ $column = $columns[0];
+ $virtualTable = $this->aliasToTableName($column);
+ if (isset($this->subQueryTargets[$virtualTable])) {
+ $lastSign = null;
+ $filters = [];
+ $expressions = [];
+ foreach ($filter->filters() as $child) {
+ switch (true) {
+ case $child instanceof FilterExpression:
+ $expression = $child->getExpression();
+ if (! is_array($expression)) {
+ break;
+ }
+ // Move to default
+ default:
+ $filters[] = $child;
+ continue 2;
+ }
+ if ($lastSign === null) {
+ $lastSign = $child->getSign();
+ } else {
+ $sign = $child->getSign();
+ if ($sign !== $lastSign) {
+ $filters[] = new FilterExpression(
+ $column,
+ $lastSign,
+ $filter->getOperatorSymbol() === '&'
+ ? [implode('&', $expressions)]
+ : $expressions
+ );
+ $expressions = [];
+ $lastSign = $sign;
+ }
+ }
+ $expressions[] = $expression;
+ }
+ if (! empty($expressions)) {
+ $filters[] = new FilterExpression(
+ $column,
+ $lastSign,
+ $filter->getOperatorSymbol() === '&'
+ ? [implode('&', $expressions)]
+ : $expressions
+ );
+ }
+ $filter->setFilters($filters);
+ }
+ }
+ }
+
+ foreach ($filter->filters() as $child) {
+ $replacement = $this->requireFilterColumns($child);
+ if ($replacement !== null) {
+ // setId($child->getId()) is performed because replaceById() doesn't already do it
+ $filter->replaceById($child->getId(), $replacement->setId($child->getId()));
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ $filter = clone $filter;
+ return parent::addFilter($this->requireFilterColumns($filter) ?: $filter);
+ }
+
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ $col = $this->getMappedField($condition);
+ if ($col === null) {
+ throw new IcingaException(
+ 'No such field: %s',
+ $condition
+ );
+ }
+ return parent::where($col, $value);
+ }
+
+ /**
+ * Add a filter expression, with as less validation as possible
+ *
+ * @param FilterExpression $ex
+ *
+ * @internal If you use this outside the monitoring module, it's your fault if something breaks
+ * @return $this
+ */
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ $col = $this->getMappedField($ex->getColumn());
+ if ($col === null) {
+ throw new IcingaException(
+ 'No such field: %s',
+ $ex->getColumn()
+ );
+ }
+
+ parent::addFilter((clone $ex)->setColumn($col));
+
+ return $this;
+ }
+
+ /**
+ * Return true if an field contains an explicit timestamp
+ *
+ * @param string $field The field to test for containing an timestamp
+ *
+ * @return bool True when the field represents an timestamp
+ */
+ public function isTimestamp($field)
+ {
+ if ($this->isCustomVar($field)) {
+ return false;
+ }
+
+ return stripos($this->getMappedField($field) ?: $field, 'UNIX_TIMESTAMP') !== false;
+ }
+
+ /**
+ * Return whether the given alias provides case insensitive value comparison
+ *
+ * @param string $alias
+ *
+ * @return bool
+ */
+ public function isCaseInsensitive($alias)
+ {
+ if ($this->isCustomVar($alias)) {
+ return false;
+ }
+
+ $column = $this->getMappedField($alias);
+ if (! $column) {
+ return false;
+ }
+
+ if (empty($this->caseInsensitiveColumns)) {
+ return preg_match('/ COLLATE .+$/', $column) === 1;
+ }
+
+ if (strpos($column, 'LOWER') === 0) {
+ return true;
+ }
+
+ $table = $this->aliasToTableName($alias);
+ if (! $table) {
+ return false;
+ }
+
+ return isset($this->caseInsensitiveColumns[$table][$alias]);
+ }
+
+ /**
+ * Return our column map
+ *
+ * Might be useful for hooks
+ *
+ * @return array
+ */
+ public function getColumnMap()
+ {
+ return $this->columnMap;
+ }
+
+ /**
+ * Apply oracle specific query initialization
+ */
+ private function initializeForOracle()
+ {
+ // Oracle uses the reserved field 'id' for primary keys, so
+ // these must be used instead of the normally defined ids
+ $this->object_id = $this->host_id = $this->service_id
+ = $this->hostgroup_id = $this->servicegroup_id
+ = $this->contact_id = $this->contactgroup_id = 'id';
+ $this->customVarsJoinTemplate =
+ '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
+ foreach ($this->columnMap as &$columns) {
+ foreach ($columns as &$value) {
+ $value = preg_replace('/UNIX_TIMESTAMP/', 'localts2unixts', $value);
+ $value = preg_replace('/ COLLATE .+$/', '', $value);
+ }
+ }
+ }
+
+ /**
+ * Apply PostgreSQL specific query initialization
+ */
+ private function initializeForPostgres()
+ {
+ $this->customVarsJoinTemplate =
+ '%1$s = %2$s.object_id AND LOWER(%2$s.varname) = %3$s';
+ foreach ($this->columnMap as $table => & $columns) {
+ foreach ($columns as $alias => & $column) {
+ if ($column === null) {
+ continue;
+ }
+
+ // Using a regex here because COLLATE may occur anywhere in the string
+ $column = preg_replace('/ COLLATE .+$/', '', $column, -1, $count);
+ if ($count > 0) {
+ $this->caseInsensitiveColumns[$table][$alias] = true;
+ }
+
+ $column = preg_replace(
+ '/inet_aton\(([[:word:].]+)\)/i',
+ '(CASE WHEN $1 ~ \'(?:[0-9]{1,3}\\\\.){3}[0-9]{1,3}\' THEN $1::inet - \'0.0.0.0\' ELSE NULL END)',
+ $column
+ );
+ if (version_compare($this->getIdoVersion(), '1.14.2', '>=')) {
+ $column = str_replace('NOW()', 'NOW() AT TIME ZONE \'UTC\'', $column);
+ } else {
+ $column = preg_replace(
+ '/UNIX_TIMESTAMP(\((?>[^()]|(?-1))*\))/i',
+ 'CASE WHEN ($1 < \'1970-01-03 00:00:00+00\'::timestamp with time zone) THEN 0 ELSE UNIX_TIMESTAMP($1) END',
+ $column
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Set up this query and join the initial tables
+ *
+ * @see IdoQuery::initializeForPostgres For postgresql specific setup
+ */
+ protected function init()
+ {
+ parent::init();
+ $this->prefix = $this->ds->getTablePrefix();
+
+ foreach (Hook::all('monitoring/idoQueryExtension') as $hook) {
+ $extensions = $hook->extendColumnMap($this);
+ if (! is_array($extensions)) {
+ continue;
+ }
+
+ foreach ($extensions as $vTable => $cols) {
+ if (! array_key_exists($vTable, $this->columnMap)) {
+ $this->hookedVirtualTables[$vTable] = $hook;
+ $this->columMap[$vTable] = array();
+ }
+
+ foreach ($cols as $k => $v) {
+ $this->columnMap[$vTable][$k] = $v;
+ }
+ }
+ }
+
+ $dbType = $this->ds->getDbType();
+ if ($dbType === 'oracle') {
+ $this->initializeForOracle();
+ } elseif ($dbType === 'pgsql') {
+ $this->initializeForPostgres();
+ } else {
+ $charset = $this->ds->getConfig()->get('charset') ?: 'latin1';
+ $this->customVarsJoinTemplate .= " COLLATE {$charset}_general_ci";
+ }
+ $this->joinBaseTables();
+ $this->select->columns($this->columns);
+ $this->prepareAliasIndexes();
+ }
+
+ /**
+ * Join the base tables for this query
+ */
+ protected function joinBaseTables()
+ {
+ reset($this->columnMap);
+ $table = key($this->columnMap);
+
+ $this->select->from(
+ array($table => $this->prefix . $table),
+ array()
+ );
+
+ $this->joinedVirtualTables = array($table => true);
+ }
+
+ /**
+ * Populates the idxAliasTAble and idxAliasColumn properties
+ */
+ protected function prepareAliasIndexes()
+ {
+ foreach ($this->columnMap as $tbl => & $cols) {
+ foreach ($cols as $alias => $col) {
+ $this->idxAliasTable[$alias] = $tbl;
+ $this->idxAliasColumn[$alias] = preg_replace('~\n\s*~', ' ', $col);
+ }
+ }
+ }
+
+ /**
+ * Resolve columns aliases to their database field using the columnMap
+ *
+ * @param array $columns
+ *
+ * @return array
+ */
+ public function resolveColumns($columns)
+ {
+ $resolvedColumns = array();
+
+ foreach ($columns as $alias => $col) {
+ if ($col instanceof Zend_Db_Expr) {
+ // Support selecting NULL as column for example
+ $resolvedColumns[$alias] = $col;
+ continue;
+ }
+ $this->requireColumn($col);
+ if ($this->isCustomvar($col)) {
+ $name = $this->getCustomvarColumnName($col);
+ } else {
+ $name = $this->aliasToColumnName($col);
+ }
+ if (is_int($alias)) {
+ $alias = $col;
+ } else {
+ $this->idxCustomAliases[$alias] = $col;
+ }
+
+ $resolvedColumns[$alias] = preg_replace('|\n|', ' ', $name);
+ }
+
+ return $resolvedColumns;
+ }
+
+ /**
+ * Return all columns that will be selected when no columns are given in the constructor or from
+ *
+ * @return array An array of column aliases
+ */
+ public function getDefaultColumns()
+ {
+ reset($this->columnMap);
+ $table = key($this->columnMap);
+ return array_keys($this->columnMap[$table]);
+ }
+
+ /**
+ * Modify the query to the given alias can be used in the result set or queries
+ *
+ * This calls requireVirtualTable if needed
+ *
+ * @param string $alias The alias of the column to require
+ *
+ * @return $this Fluent interface
+ * @see IdoQuery::requireVirtualTable The method initializing required joins
+ * @throws \Icinga\Exception\ProgrammingError When an unknown column is requested
+ */
+ public function requireColumn($alias)
+ {
+ if ($this->hasAliasName($alias)) {
+ $this->requireVirtualTable($this->aliasToTableName($alias));
+ } elseif ($this->isCustomVar($alias)) {
+ $this->requireCustomvar($alias);
+ } else {
+ throw new ProgrammingError(
+ '%s : Got invalid column: %s',
+ get_called_class(),
+ $alias
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Return true if the given alias exists
+ *
+ * @param String $alias The alias to test for
+ * @return bool True when the alias exists, otherwise false
+ */
+ protected function hasAliasName($alias)
+ {
+ return array_key_exists($alias, $this->idxAliasColumn);
+ }
+
+ /**
+ * Require a virtual table for the given table name if not already required
+ *
+ * @param String $name The table name to require
+ * @return $this Fluent interface
+ */
+ protected function requireVirtualTable($name)
+ {
+ if ($this->hasJoinedVirtualTable($name)) {
+ return $this;
+ }
+
+ if ($this->virtualTableIsHooked($name)) {
+ return $this->joinHookedVirtualTable($name);
+ } else {
+ return $this->joinVirtualTable($name);
+ }
+ }
+
+ /**
+ * Whether a given virtual table name has been provided by a hook
+ *
+ * @param string $name Virtual table name
+ *
+ * @return boolean
+ */
+ protected function virtualTableIsHooked($name)
+ {
+ return array_key_exists($name, $this->hookedVirtualTables);
+ }
+
+ protected function conflictsWithVirtualTable($name)
+ {
+ if ($this->hasJoinedVirtualTable($name)) {
+ throw new ProgrammingError(
+ 'IDO query virtual table conflict with "%s"',
+ $name
+ );
+ }
+ return $this;
+ }
+
+ /**
+ * Call the method for joining a virtual table
+ *
+ * This requires a join$Table() method to exist
+ *
+ * @param String $table The table to join by calling join$Table() in the concrete implementation
+ * @return $this Fluent interface
+ *
+ * @throws \Icinga\Exception\ProgrammingError If the join method for this table does not exist
+ */
+ protected function joinVirtualTable($table)
+ {
+ $func = 'join' . ucfirst($table);
+ if (method_exists($this, $func)) {
+ $this->$func();
+ } else {
+ throw new ProgrammingError(
+ 'Cannot join "%s", no such table found',
+ $table
+ );
+ }
+ $this->joinedVirtualTables[$table] = true;
+ return $this;
+ }
+
+ /**
+ * Tell a hook to join a virtual table
+ *
+ * @param String $table
+ * @return $this
+ */
+ protected function joinHookedVirtualTable($table)
+ {
+ $this->hookedVirtualTables[$table]->joinVirtualTable($this, $table);
+ $this->joinedVirtualTables[$table] = true;
+ return $this;
+ }
+
+ /**
+ * Get the table for a specific alias
+ *
+ * @param String $alias The alias to request the table for
+ * @return String The table for the alias or null if it doesn't exist
+ */
+ protected function aliasToTableName($alias)
+ {
+ return isset($this->idxAliasTable[$alias]) ? $this->idxAliasTable[$alias] : null;
+ }
+
+ /**
+ * Return whether this query allows to join custom variables
+ *
+ * @return bool
+ */
+ public function allowsCustomVars()
+ {
+ return $this->allowCustomVars;
+ }
+
+ /**
+ * Return true if the given alias denotes a custom variable
+ *
+ * @param String $alias The alias to test for being a customvariable
+ * @return bool True if the alias is a customvariable, otherwise false
+ */
+ protected function isCustomVar($alias)
+ {
+ return $this->allowCustomVars && $alias[0] === '_';
+ }
+
+ protected function requireCustomvar($customvar)
+ {
+ if (! $this->hasCustomvar($customvar)) {
+ $this->joinCustomvar($customvar);
+ }
+ return $this;
+ }
+
+ protected function hasCustomvar($customvar)
+ {
+ return array_key_exists(strtolower($customvar), $this->customVars);
+ }
+
+ protected function joinCustomvar($customvar)
+ {
+ // TODO: This is not generic enough yet
+ list($type, $name) = $this->customvarNameToTypeName($customvar);
+ $alias = ($type === 'host' ? 'hcv_' : 'scv_') . preg_replace('~[^a-zA-Z0-9_]~', '_', $name);
+
+ // We're replacing any problematic char with an underscore, which will lead to duplicates, this avoids them
+ $from = $this->select->getPart(Zend_Db_Select::FROM);
+ for ($i = 2; array_key_exists($alias, $from); $i++) {
+ $alias = $alias . '_' . $i;
+ }
+
+ $this->customVars[strtolower($customvar)] = $alias;
+
+ if ($type === 'host') {
+ if ($this instanceof ServicecommentQuery
+ || $this instanceof ServicedowntimeQuery
+ || $this instanceof ServicecommenthistoryQuery
+ || $this instanceof ServicedowntimestarthistoryQuery
+ || $this instanceof ServiceflappingstarthistoryQuery
+ || $this instanceof ServicegroupQuery
+ || $this instanceof ServicenotificationQuery
+ || $this instanceof ServicestatehistoryQuery
+ || $this instanceof ServicestatusQuery
+ ) {
+ $this->requireVirtualTable('services');
+ $leftcol = 's.host_object_id';
+ } else {
+ $leftcol = 'ho.object_id';
+ if (! $this->hasJoinedTable('ho')) {
+ $this->requireVirtualTable('hosts');
+ }
+ }
+ } else { // $type === 'service'
+ $leftcol = 'so.object_id';
+ if (! $this->hasJoinedTable('so')) {
+ $this->requireVirtualTable('services');
+ }
+ }
+
+ $mapped = $this->getMappedField($leftcol);
+ if ($mapped !== null) {
+ $this->requireColumn($leftcol);
+ $leftcol = $mapped;
+ }
+
+ $joinOn = sprintf(
+ $this->customVarsJoinTemplate,
+ $leftcol,
+ $alias,
+ $this->db->quote($name)
+ );
+
+ $this->select->joinLeft(
+ array($alias => $this->prefix . 'customvariablestatus'),
+ $joinOn,
+ array()
+ );
+
+ return $this;
+ }
+
+ protected function customvarNameToTypeName($customvar)
+ {
+ $customvar = strtolower($customvar);
+ if (! preg_match('~^_(host|service)_(.+)$~', $customvar, $m)) {
+ throw new ProgrammingError(
+ 'Got invalid custom var: "%s"',
+ $customvar
+ );
+ }
+ return array($m[1], $m[2]);
+ }
+
+ protected function hasJoinedVirtualTable($name)
+ {
+ return array_key_exists($name, $this->joinedVirtualTables);
+ }
+
+ /**
+ * Get the query column of a already joined custom variable
+ *
+ * @param string $customvar
+ *
+ * @return string
+ * @throws QueryException If the custom variable has not been joined
+ */
+ protected function getCustomvarColumnName($customvar)
+ {
+ if (! isset($this->customVars[($customvar = strtolower($customvar))])) {
+ throw new QueryException('Custom variable %s has not been joined', $customvar);
+ }
+ return $this->customVars[$customvar] . '.varvalue';
+ }
+
+ public function aliasToColumnName($alias)
+ {
+ return $this->idxAliasColumn[$alias];
+ }
+
+ /**
+ * Get the alias of a column expression as defined in the {@link $columnMap} property.
+ *
+ * @param string $alias Potential custom alias
+ *
+ * @return string
+ */
+ public function customAliasToAlias($alias)
+ {
+ if (isset($this->idxCustomAliases[$alias])) {
+ return $this->idxCustomAliases[$alias];
+ }
+ return $alias;
+ }
+
+ /**
+ * Create a sub query
+ *
+ * @param string $queryName
+ * @param array $columns
+ *
+ * @return static
+ */
+ protected function createSubQuery($queryName, $columns = array())
+ {
+ $class = '\\'
+ . substr(__CLASS__, 0, strrpos(__CLASS__, '\\') + 1)
+ . ucfirst($queryName) . 'Query';
+ $query = new $class($this->ds, $columns);
+ return $query;
+ }
+
+ /**
+ * Set columns to select
+ *
+ * @param array $columns
+ *
+ * @return $this
+ */
+ public function columns(array $columns)
+ {
+ $this->idxCustomAliases = array();
+ $this->columns = $this->resolveColumns($columns);
+ // TODO: we need to refresh our select!
+ // $this->select->columns($columns);
+ return $this;
+ }
+
+ public function clearGroupingRules()
+ {
+ $this->groupBase = array();
+ $this->groupOrigin = array();
+ return $this;
+ }
+
+ /**
+ * Register the GROUP BY columns required for the given alias
+ *
+ * @param string $alias The alias to register columns for
+ * @param string $table The table the given alias is associated with
+ * @param array $groupedColumns The grouping columns registered so far
+ * @param array $groupedTables The tables for which columns were registered so far
+ */
+ protected function registerGroupColumns($alias, $table, array &$groupedColumns, array &$groupedTables)
+ {
+ switch ($table) {
+ case 'checktimeperiods':
+ $groupedColumns[] = 'ctp.timeperiod_id';
+ break;
+ case 'contacts':
+ $groupedColumns[] = 'co.object_id';
+ $groupedColumns[] = 'c.contact_id';
+ break;
+ case 'hostobjects':
+ $groupedColumns[] = 'ho.object_id';
+ break;
+ case 'hosts':
+ $groupedColumns[] = 'h.host_id';
+ break;
+ case 'hostgroups':
+ $groupedColumns[] = 'hgo.object_id';
+ $groupedColumns[] = 'hg.hostgroup_id';
+ break;
+ case 'hoststatus':
+ $groupedColumns[] = 'hs.hoststatus_id';
+ break;
+ case 'instances':
+ $groupedColumns[] = 'i.instance_id';
+ break;
+ case 'servicegroups':
+ $groupedColumns[] = 'sgo.object_id';
+ $groupedColumns[] = 'sg.servicegroup_id';
+ break;
+ case 'serviceobjects':
+ $groupedColumns[] = 'so.object_id';
+ break;
+ case 'serviceproblemsummary':
+ $groupedColumns[] = 'sps.unhandled_services_count';
+ break;
+ case 'services':
+ $groupedColumns[] = 'so.object_id';
+ $groupedColumns[] = 's.service_id';
+ break;
+ case 'servicestatus':
+ $groupedColumns[] = 'ss.servicestatus_id';
+ break;
+ default:
+ return;
+ }
+
+ $groupedTables[$table] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroup()
+ {
+ $group = parent::getGroup() ?: array();
+ if (! is_array($group)) {
+ $group = array($group);
+ }
+
+ $joinedOrigins = array_filter($this->groupOrigin, array($this, 'hasJoinedVirtualTable'));
+ if (empty($joinedOrigins)) {
+ return $group;
+ }
+
+ $groupedTables = array();
+ foreach ($this->groupBase as $baseTable => $aliasedPks) {
+ if (! $this->hasJoinedVirtualTable($baseTable)) {
+ continue;
+ }
+ $groupedTables[$baseTable] = true;
+ foreach ($aliasedPks as $aliasedPk) {
+ $group[] = $aliasedPk;
+ }
+ }
+
+ foreach (new ColumnFilterIterator($this->columns) as $desiredAlias => $desiredColumn) {
+ $alias = is_string($desiredAlias) ? $this->customAliasToAlias($desiredAlias) : $desiredColumn;
+ if ($this->isCustomVar($alias) && $this->getDatasource()->getDbType() === 'pgsql') {
+ $table = $this->customVars[$alias];
+ if (! isset($groupedTables[$table])) {
+ $group[] = $this->getCustomvarColumnName($alias);
+ $groupedTables[$table] = true;
+ }
+ continue;
+ }
+ $table = $this->aliasToTableName($alias);
+ if ($table && !isset($groupedTables[$table]) && (
+ in_array($table, $joinedOrigins, true) || $this->getDatasource()->getDbType() === 'pgsql')
+ ) {
+ $this->registerGroupColumns($alias, $table, $group, $groupedTables);
+ }
+ }
+
+ if (! empty($group) && $this->getDatasource()->getDbType() === 'pgsql') {
+ foreach (new ColumnFilterIterator($this->orderColumns) as $alias) {
+ if ($this->isCustomVar($alias)) {
+ $table = $this->customVars[$alias];
+ if (! isset($groupedTables[$table])) {
+ $group[] = $this->getCustomvarColumnName($alias);
+ $groupedTables[$table] = true;
+ }
+ continue;
+ }
+ $table = $this->aliasToTableName($alias);
+ if ($table && !isset($groupedTables[$table])
+ && !in_array($this->getMappedField($alias), $this->columns, true)
+ ) {
+ $this->registerGroupColumns($alias, $table, $group, $groupedTables);
+ }
+ }
+ }
+
+ return array_unique($group);
+ }
+
+ // TODO: Move this away, see note related to $idoVersion var
+ protected function getIdoVersion()
+ {
+ if (self::$idoVersion === null) {
+ $dbconf = $this->db->getConfig();
+ $id = $dbconf['host'] . '/' . $dbconf['dbname'];
+ $session = null;
+ if (Icinga::app()->isWeb()) {
+ // TODO: Once we have version per connection we should choose a
+ // namespace based on resource name
+ $session = Session::getSession()->getNamespace('monitoring/ido/' . $id);
+ if (isset($session->version)) {
+ self::$idoVersion = $session->version;
+ return self::$idoVersion;
+ }
+ }
+ self::$idoVersion = $this->db->fetchOne(
+ $this->db->select()->from($this->prefix . 'dbversion', 'version')
+ );
+ if ($session !== null) {
+ $session->version = self::$idoVersion;
+ }
+ }
+ return self::$idoVersion;
+ }
+
+ /**
+ * Return the name of the primary key column for the given table name
+ *
+ * @param string $table
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case $table is unknown
+ */
+ protected function getPrimaryKeyColumn($table)
+ {
+ // TODO: For god's sake, make this being a mapping
+ // (instead of matching a ton of properties using a ridiculous long switch case)
+ switch ($table) {
+ case 'instances':
+ return $this->instance_id;
+ case 'objects':
+ return $this->object_id;
+ case 'acknowledgements':
+ return $this->acknowledgement_id;
+ case 'commenthistory':
+ return $this->commenthistory_id;
+ case 'contactnotifiations':
+ return $this->contactnotification_id;
+ case 'downtimehistory':
+ return $this->downtimehistory_id;
+ case 'flappinghistory':
+ return $this->flappinghistory_id;
+ case 'notifications':
+ return $this->notification_id;
+ case 'statehistory':
+ return $this->statehistory_id;
+ case 'comments':
+ return $this->comment_id;
+ case 'customvariablestatus':
+ return $this->customvariablestatus_id;
+ case 'hoststatus':
+ return $this->hoststatus_id;
+ case 'programstatus':
+ return $this->programstatus_id;
+ case 'runtimevariables':
+ return $this->runtimevariable_id;
+ case 'scheduleddowntime':
+ return $this->scheduleddowntime_id;
+ case 'servicestatus':
+ return $this->servicestatus_id;
+ case 'contactstatus':
+ return $this->contactstatus_id;
+ case 'commands':
+ return $this->command_id;
+ case 'contactgroup_members':
+ return $this->contactgroup_member_id;
+ case 'contactgroups':
+ return $this->contactgroup_id;
+ case 'contacts':
+ return $this->contact_id;
+ case 'customvariables':
+ return $this->customvariable_id;
+ case 'host_contactgroups':
+ return $this->host_contactgroup_id;
+ case 'host_contacts':
+ return $this->host_contact_id;
+ case 'hostgroup_members':
+ return $this->hostgroup_member_id;
+ case 'hostgroups':
+ return $this->hostgroup_id;
+ case 'hosts':
+ return $this->host_id;
+ case 'service_contactgroups':
+ return $this->service_contactgroup_id;
+ case 'service_contacts':
+ return $this->service_contact_id;
+ case 'servicegroup_members':
+ return $this->servicegroup_member_id;
+ case 'servicegroups':
+ return $this->servicegroup_id;
+ case 'services':
+ return $this->service_id;
+ case 'timeperiods':
+ return $this->timeperiod_id;
+ default:
+ throw new ProgrammingError('Cannot provide a primary key column. Table "%s" is unknown', $table);
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php
new file mode 100644
index 0000000..ac538ec
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/InstanceQuery.php
@@ -0,0 +1,26 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class InstanceQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'instances' => array(
+ 'instance_id' => 'i.instance_id',
+ 'instance_name' => 'i.instance_name'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select()->from(array('i' => $this->prefix . 'instances'), array());
+ $this->joinedVirtualTables['instances'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php
new file mode 100644
index 0000000..8bfb725
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationQuery.php
@@ -0,0 +1,144 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service notifications
+ */
+class NotificationQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'notifications' => array(
+ 'id' => 'n.id',
+ 'instance_name' => 'n.instance_name',
+ 'notification_contact_name' => 'n.notification_contact_name',
+ 'notification_output' => 'n.notification_output',
+ 'notification_reason' => 'n.notification_reason',
+ 'notification_state' => 'n.notification_state',
+ 'notification_timestamp' => 'n.notification_timestamp'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'n.host_display_name',
+ 'host_name' => 'n.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'n.service_description',
+ 'service_display_name' => 'n.service_display_name',
+ 'service_host_name' => 'n.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $notificationQuery;
+
+ /**
+ * Subqueries used for the notification query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->notificationQuery = $this->db->select();
+ $this->select->from(
+ array('n' => $this->notificationQuery),
+ array()
+ );
+ $this->joinedVirtualTables['notifications'] = true;
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = $this->desiredColumns;
+ $columns = array_combine($columns, $columns);
+ foreach ($this->columnMap['services'] as $column => $_) {
+ if (isset($columns[$column])) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ }
+ $hosts = $this->createSubQuery('hostnotification', $columns);
+ $hosts->setIsSubQuery(true);
+ $this->subQueries[] = $hosts;
+ $this->notificationQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $services = $this->createSubQuery('servicenotification', $this->desiredColumns);
+ $services->setIsSubQuery(true);
+ $this->subQueries[] = $services;
+ $this->notificationQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php
new file mode 100644
index 0000000..87a71f6
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationeventQuery.php
@@ -0,0 +1,52 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host and service notification events
+ */
+class NotificationeventQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'notificationevent' => array(
+ 'notificationevent_id' => 'n.notification_id',
+ 'notificationevent_reason' => <<<EOF
+(CASE n.notification_reason
+ WHEN 0 THEN 'normal_notification'
+ WHEN 1 THEN 'ack'
+ WHEN 2 THEN 'flapping_started'
+ WHEN 3 THEN 'flapping_stopped'
+ WHEN 4 THEN 'flapping_disabled'
+ WHEN 5 THEN 'dt_start'
+ WHEN 6 THEN 'dt_end'
+ WHEN 7 THEN 'dt_cancel'
+ WHEN 99 THEN 'custom_notification'
+ ELSE NULL
+END)
+EOF
+ ,
+ 'notificationevent_start_time' => 'UNIX_TIMESTAMP(n.start_time)',
+ 'notificationevent_end_time' => 'UNIX_TIMESTAMP(n.end_time)',
+ 'notificationevent_state' => 'n.state',
+ 'notificationevent_output' => 'n.output',
+ 'notificationevent_long_output' => 'n.long_output',
+ 'notificationevent_escalated' => 'n.escalated',
+ 'notificationevent_contacts_notified' => 'n.contacts_notified'
+ ),
+ 'object' => array(
+ 'host_name' => 'o.name1',
+ 'service_description' => 'o.name2'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select()
+ ->from(array('n' => $this->prefix . 'notifications'), array())
+ ->join(array('o' => $this->prefix . 'objects'), 'n.object_id = o.object_id', array());
+
+ $this->joinedVirtualTables['notificationevent'] = true;
+ $this->joinedVirtualTables['object'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php
new file mode 100644
index 0000000..f629115
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service notification history
+ */
+class NotificationhistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'history' => array(
+ 'id' => 'n.id',
+ 'object_type' => 'n.object_type',
+ 'output' => 'n.output',
+ 'state' => 'n.state',
+ 'timestamp' => 'n.timestamp',
+ 'type' => 'n.type'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'n.host_display_name',
+ 'host_name' => 'n.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'n.service_description',
+ 'service_display_name' => 'n.service_display_name',
+ 'service_host_name' => 'n.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $notificationQuery;
+
+ /**
+ * Subqueries used for the notification query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->notificationQuery = $this->db->select();
+ $this->select->from(
+ array('n' => $this->notificationQuery),
+ array()
+ );
+ $this->joinedVirtualTables['history'] = true;
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = $this->desiredColumns;
+ $columns = array_combine($columns, $columns);
+ foreach ($this->columnMap['services'] as $column => $_) {
+ if (isset($columns[$column])) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ }
+ $hosts = $this->createSubQuery('hostnotification', $columns);
+ $this->subQueries[] = $hosts;
+ $this->notificationQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_flip($this->desiredColumns);
+ $services = $this->createSubQuery('servicenotification', array_flip($columns));
+ $this->subQueries[] = $services;
+ $this->notificationQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php
new file mode 100644
index 0000000..9e9f5f6
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ProgramstatusQuery.php
@@ -0,0 +1,68 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Program status query
+ */
+class ProgramstatusQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'programstatus' => array(
+ 'id' => 'programstatus_id',
+ 'status_update_time' => 'UNIX_TIMESTAMP(programstatus.status_update_time)',
+ 'program_version' => 'program_version',
+ 'program_start_time' => 'UNIX_TIMESTAMP(programstatus.program_start_time)',
+ 'program_end_time' => 'UNIX_TIMESTAMP(programstatus.program_end_time)',
+ 'is_currently_running' => 'CASE WHEN (UNIX_TIMESTAMP(programstatus.status_update_time) + 60 > UNIX_TIMESTAMP(NOW()))
+ THEN
+ 1
+ ELSE
+ 0
+ END',
+ 'process_id' => 'process_id',
+ 'endpoint_name' => 'endpoint_name',
+ 'daemon_mode' => 'daemon_mode',
+ 'last_command_check' => 'UNIX_TIMESTAMP(programstatus.last_command_check)',
+ 'last_log_rotation' => 'UNIX_TIMESTAMP(programstatus.last_log_rotation)',
+ 'notifications_enabled' => 'notifications_enabled',
+ 'disable_notif_expire_time' => 'UNIX_TIMESTAMP(programstatus.disable_notif_expire_time)',
+ 'active_service_checks_enabled' => 'active_service_checks_enabled',
+ 'passive_service_checks_enabled' => 'passive_service_checks_enabled',
+ 'active_host_checks_enabled' => 'active_host_checks_enabled',
+ 'passive_host_checks_enabled' => 'passive_host_checks_enabled',
+ 'event_handlers_enabled' => 'event_handlers_enabled',
+ 'flap_detection_enabled' => 'flap_detection_enabled',
+ 'failure_prediction_enabled' => 'failure_prediction_enabled',
+ 'process_performance_data' => 'process_performance_data',
+ 'obsess_over_hosts' => 'obsess_over_hosts',
+ 'obsess_over_services' => 'obsess_over_services',
+ 'modified_host_attributes' => 'modified_host_attributes',
+ 'modified_service_attributes' => 'modified_service_attributes',
+ 'global_host_event_handler' => 'global_host_event_handler',
+ 'global_service_event_handler' => 'global_service_event_handler',
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables();
+
+ if (version_compare($this->getIdoVersion(), '1.11.7', '<')) {
+ $this->columnMap['programstatus']['endpoint_name'] = '(0)';
+ }
+ if (version_compare($this->getIdoVersion(), '1.11.8', '<')) {
+ $this->columnMap['programstatus']['program_version'] = '(NULL)';
+ }
+ if (version_compare($this->getIdoVersion(), '1.8', '<')) {
+ $this->columnMap['programstatus']['disable_notif_expire_time'] = '(NULL)';
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php
new file mode 100644
index 0000000..1aa2257
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimesummaryQuery.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Zend_Db_Select;
+
+/**
+ * Query check summaries out of database
+ */
+class RuntimesummaryQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'runtimesummary' => array(
+ 'check_type' => 'check_type',
+ 'active_checks_enabled' => 'active_checks_enabled',
+ 'passive_checks_enabled' => 'passive_checks_enabled',
+ 'execution_time' => 'execution_time',
+ 'latency' => 'latency',
+ 'object_count' => 'object_count',
+ 'object_type' => 'object_type'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $hosts = $this->db->select()->from(
+ array('ho' => $this->prefix . 'objects'),
+ array()
+ )->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'ho.object_id = hs.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ array()
+ )->columns(
+ array(
+ 'check_type' => 'CASE '
+ . 'WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN \'passive\' '
+ . 'WHEN hs.active_checks_enabled = 1 THEN \'active\' '
+ . 'END',
+ 'active_checks_enabled' => 'hs.active_checks_enabled',
+ 'passive_checks_enabled' => 'hs.passive_checks_enabled',
+ 'execution_time' => 'SUM(hs.execution_time)',
+ 'latency' => 'SUM(hs.latency)',
+ 'object_count' => 'COUNT(*)',
+ 'object_type' => "('host')"
+ )
+ )->group('check_type')->group('active_checks_enabled')->group('passive_checks_enabled');
+
+ $services = $this->db->select()->from(
+ array('so' => $this->prefix . 'objects'),
+ array()
+ )->join(
+ array('ss' => $this->prefix . 'servicestatus'),
+ 'so.object_id = ss.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ )->columns(
+ array(
+ 'check_type' => 'CASE '
+ . 'WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN \'passive\' '
+ . 'WHEN ss.active_checks_enabled = 1 THEN \'active\' '
+ . 'END',
+ 'active_checks_enabled' => 'ss.active_checks_enabled',
+ 'passive_checks_enabled' => 'ss.passive_checks_enabled',
+ 'execution_time' => 'SUM(ss.execution_time)',
+ 'latency' => 'SUM(ss.latency)',
+ 'object_count' => 'COUNT(*)',
+ 'object_type' => "('service')"
+ )
+ )->group('check_type')->group('active_checks_enabled')->group('passive_checks_enabled');
+
+ $union = $this->db->select()->union(
+ array('s' => $services, 'h' => $hosts),
+ Zend_Db_Select::SQL_UNION_ALL
+ );
+
+ $this->select->from(array('hs' => $union));
+
+ $this->joinedVirtualTables = array('runtimesummary' => true);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php
new file mode 100644
index 0000000..494744a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/RuntimevariablesQuery.php
@@ -0,0 +1,18 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for runtimevariables table
+ */
+class RuntimevariablesQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'runtimevariables' => array(
+ 'id' => 'runtimevariable_id',
+ 'varname' => 'varname',
+ 'varvalue' => 'varvalue'
+ )
+ );
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php
new file mode 100644
index 0000000..cae11bc
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentQuery.php
@@ -0,0 +1,218 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for service comments
+ */
+class ServicecommentQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('comments' => array('c.comment_id', 'so.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'comments' => array(
+ 'comment_author' => 'c.author_name COLLATE latin1_general_ci',
+ 'comment_author_name' => 'c.author_name',
+ 'comment_data' => 'c.comment_data',
+ 'comment_expiration' => 'CASE c.expires WHEN 1 THEN UNIX_TIMESTAMP(c.expiration_time) ELSE NULL END',
+ 'comment_internal_id' => 'c.internal_comment_id',
+ 'comment_is_persistent' => 'c.is_persistent',
+ 'comment_name' => 'c.name',
+ 'comment_timestamp' => 'UNIX_TIMESTAMP(c.comment_time)',
+ 'comment_type' => "CASE c.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'downtime' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END",
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_type' => '(\'service\')',
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host' => 'so.name1 COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'hoststatus' => array(
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ ),
+ 'servicestatus' => array(
+ 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.14.0', '<')) {
+ $this->columnMap['comments']['comment_name'] = '(NULL)';
+ }
+ $this->select->from(
+ array('c' => $this->prefix . 'comments'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = c.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ $this->joinedVirtualTables['comments'] = true;
+ }
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = c.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service status
+ */
+ protected function joinServicestatus()
+ {
+ $this->select->join(
+ array('ss' => $this->prefix . 'servicestatus'),
+ 'ss.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $this->requireVirtualTable('services');
+
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 's.host_object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php
new file mode 100644
index 0000000..33aaa25
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommentdeletionhistoryQuery.php
@@ -0,0 +1,44 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service comment removal records
+ */
+class ServicecommentdeletionhistoryQuery extends ServicecommenthistoryQuery
+{
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('sch.deletion_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables();
+ $this->select->where("sch.deletion_time > '1970-01-02 00:00:00'");
+ $this->columnMap['commenthistory']['timestamp'] = str_replace(
+ 'comment_time',
+ 'deletion_time',
+ $this->columnMap['commenthistory']['timestamp']
+ );
+ $this->columnMap['commenthistory']['type'] = str_replace(
+ 'END)',
+ "END || '_deleted')",
+ $this->columnMap['commenthistory']['type']
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php
new file mode 100644
index 0000000..b3e9c16
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecommenthistoryQuery.php
@@ -0,0 +1,195 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service comment history records
+ */
+class ServicecommenthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('commenthistory' => array('sch.commenthistory_id', 'so.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups', 'services');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'commenthistory' => array(
+ 'id' => 'sch.commenthistory_id',
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_id' => 'sch.object_id',
+ 'object_type' => '(\'service\')',
+ 'output' => "('[' || sch.author_name || '] ' || sch.comment_data)",
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host' => 'so.name1 COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1',
+ 'state' => '(-1)',
+ 'timestamp' => 'UNIX_TIMESTAMP(sch.comment_time)',
+ 'type' => "(CASE sch.entry_type WHEN 1 THEN 'comment' WHEN 2 THEN 'dt_comment' WHEN 3 THEN 'flapping' WHEN 4 THEN 'ack' END)"
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('sch.comment_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('sch' => $this->prefix . 'commenthistory'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sch.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ $this->joinedVirtualTables['commenthistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sch.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('services');
+
+ return ['so.object_id', 'so.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php
new file mode 100644
index 0000000..0a46709
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicecontactQuery.php
@@ -0,0 +1,235 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for service contacts
+ */
+class ServicecontactQuery extends IdoQuery
+{
+ protected $allowCustomVars = true;
+
+ protected $groupBase = [
+ 'contacts' => ['co.object_id', 'c.contact_id'],
+ 'timeperiods' => ['ht.timeperiod_id', 'st.timeperiod_id']
+ ];
+
+ protected $groupOrigin = ['contactgroups', 'hosts', 'services'];
+
+ protected $subQueryTargets = [
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ ];
+
+ protected $columnMap = [
+ 'contactgroups' => [
+ 'contactgroup' => 'cgo.name1 COLLATE latin1_general_ci',
+ 'contactgroup_name' => 'cgo.name1',
+ 'contactgroup_alias' => 'cg.alias COLLATE latin1_general_ci'
+ ],
+ 'contacts' => [
+ 'contact_id' => 'c.contact_id',
+ 'contact' => 'co.name1 COLLATE latin1_general_ci',
+ 'contact_name' => 'co.name1',
+ 'contact_alias' => 'c.alias COLLATE latin1_general_ci',
+ 'contact_email' => 'c.email_address COLLATE latin1_general_ci',
+ 'contact_pager' => 'c.pager_address',
+ 'contact_object_id' => 'c.contact_object_id',
+ 'contact_has_host_notfications' => 'c.host_notifications_enabled',
+ 'contact_has_service_notfications' => 'c.service_notifications_enabled',
+ 'contact_can_submit_commands' => 'c.can_submit_commands',
+ 'contact_notify_service_recovery' => 'c.notify_service_recovery',
+ 'contact_notify_service_warning' => 'c.notify_service_warning',
+ 'contact_notify_service_critical' => 'c.notify_service_critical',
+ 'contact_notify_service_unknown' => 'c.notify_service_unknown',
+ 'contact_notify_service_flapping' => 'c.notify_service_flapping',
+ 'contact_notify_service_downtime' => 'c.notify_service_downtime',
+ 'contact_notify_host_recovery' => 'c.notify_host_recovery',
+ 'contact_notify_host_down' => 'c.notify_host_down',
+ 'contact_notify_host_unreachable' => 'c.notify_host_unreachable',
+ 'contact_notify_host_flapping' => 'c.notify_host_flapping',
+ 'contact_notify_host_downtime' => 'c.notify_host_downtime'
+ ],
+ 'hostgroups' => [
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ],
+ 'hosts' => [
+ 'host' => 'ho.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'ho.name1',
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ],
+ 'instances' => [
+ 'instance_name' => 'i.instance_name'
+ ],
+ 'servicegroups' => [
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ],
+ 'services' => [
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ ],
+ 'timeperiods' => [
+ 'contact_notify_host_timeperiod' => 'ht.alias COLLATE latin1_general_ci',
+ 'contact_notify_service_timeperiod' => 'st.alias COLLATE latin1_general_ci'
+ ]
+ ];
+
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ ['c' => $this->prefix . 'contacts'],
+ []
+ )->join(
+ ['co' => $this->prefix . 'objects'],
+ 'co.object_id = c.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10',
+ []
+ );
+
+ $this->select->joinLeft(
+ ['sc' => $this->prefix . 'service_contacts'],
+ 'sc.contact_object_id = c.contact_object_id',
+ []
+ )->joinLeft(
+ ['s' => $this->prefix . 'services'],
+ 's.service_id = sc.service_id',
+ []
+ )->joinLeft(
+ ['so' => $this->prefix . 'objects'],
+ 'so.object_id = s.service_object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ []
+ );
+
+ $this->joinedVirtualTables['contacts'] = true;
+ $this->joinedVirtualTables['services'] = true;
+ }
+
+ /**
+ * Join contact groups
+ */
+ protected function joinContactgroups()
+ {
+ $this->select->joinLeft(
+ ['cgm' => $this->prefix . 'contactgroup_members'],
+ 'co.object_id = cgm.contact_object_id',
+ []
+ )->joinLeft(
+ ['cg' => $this->prefix . 'contactgroups'],
+ 'cgm.contactgroup_id = cg.contactgroup_id',
+ []
+ )->joinLeft(
+ ['cgo' => $this->prefix . 'objects'],
+ 'cg.contactgroup_object_id = cgo.object_id AND cgo.is_active = 1 AND cgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('hosts');
+ $this->select->joinLeft(
+ ['hgm' => $this->prefix . 'hostgroup_members'],
+ 'hgm.host_object_id = ho.object_id',
+ []
+ )->joinLeft(
+ ['hg' => $this->prefix . 'hostgroups'],
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ []
+ )->joinLeft(
+ ['hgo' => $this->prefix . 'objects'],
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ []
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->joinLeft(
+ ['h' => $this->prefix . 'hosts'],
+ 'h.host_object_id = s.host_object_id',
+ []
+ )->joinLeft(
+ ['ho' => $this->prefix . 'objects'],
+ 'ho.object_id = h.host_object_id AND ho.is_active = 1 AND ho.objecttype_id = 1',
+ []
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ ['i' => $this->prefix . 'instances'],
+ 'i.instance_id = c.instance_id',
+ []
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ ['sgm' => $this->prefix . 'servicegroup_members'],
+ 'sgm.service_object_id = s.service_object_id',
+ []
+ )->joinLeft(
+ ['sg' => $this->prefix . 'servicegroups'],
+ 'sg.servicegroup_id = sgm.servicegroup_id',
+ []
+ )->joinLeft(
+ ['sgo' => $this->prefix . 'objects'],
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ []
+ );
+ }
+
+ /**
+ * Join time periods
+ */
+ protected function joinTimeperiods()
+ {
+ $this->select->joinLeft(
+ ['ht' => $this->prefix . 'timeperiods'],
+ 'ht.timeperiod_object_id = c.host_timeperiod_object_id',
+ []
+ );
+ $this->select->joinLeft(
+ ['st' => $this->prefix . 'timeperiods'],
+ 'st.timeperiod_object_id = c.service_timeperiod_object_id',
+ []
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 's.host_object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php
new file mode 100644
index 0000000..feea061
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeQuery.php
@@ -0,0 +1,222 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for service downtimes
+ */
+class ServicedowntimeQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('downtimes' => array('sd.scheduleddowntime_id', 'so.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimes' => array(
+ 'downtime_author' => 'sd.author_name COLLATE latin1_general_ci',
+ 'downtime_author_name' => 'sd.author_name',
+ 'downtime_comment' => 'sd.comment_data',
+ 'downtime_duration' => 'sd.duration',
+ 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END',
+ 'downtime_entry_time' => 'UNIX_TIMESTAMP(sd.entry_time)',
+ 'downtime_internal_id' => 'sd.internal_downtime_id',
+ 'downtime_is_fixed' => 'sd.is_fixed',
+ 'downtime_is_flexible' => 'CASE WHEN sd.is_fixed = 0 THEN 1 ELSE 0 END',
+ 'downtime_is_in_effect' => 'sd.is_in_effect',
+ 'downtime_name' => 'sd.name',
+ 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)',
+ 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)',
+ 'downtime_start' => 'UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)',
+ 'downtime_triggered_by_id' => 'sd.triggered_by_id',
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_type' => '(\'service\')',
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host' => 'so.name1 COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1'
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'hoststatus' => array(
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ ),
+ 'servicestatus' => array(
+ 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.14.0', '<')) {
+ $this->columnMap['downtimes']['downtime_name'] = '(NULL)';
+ }
+ $this->select->from(
+ array('sd' => $this->prefix . 'scheduleddowntime'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'sd.object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ $this->joinedVirtualTables['downtimes'] = true;
+ }
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sd.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sgm.servicegroup_id = sg.' . $this->servicegroup_id,
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service status
+ */
+ protected function joinServicestatus()
+ {
+ $this->select->join(
+ array('ss' => $this->prefix . 'servicestatus'),
+ 'ss.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('services');
+
+ return ['so.object_id', 'so.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php
new file mode 100644
index 0000000..2d592c8
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimeendhistoryQuery.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for host downtime end history records
+ */
+class ServicedowntimeendhistoryQuery extends ServicedowntimestarthistoryQuery
+{
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('sdh.actual_end_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ parent::joinBaseTables(true);
+ $this->select->where("sdh.actual_end_time > '1970-01-02 00:00:00'");
+ $this->columnMap['downtimehistory']['type'] = "('dt_end')";
+ $this->columnMap['downtimehistory']['timestamp'] = str_replace(
+ 'actual_start_time',
+ 'actual_end_time',
+ $this->columnMap['downtimehistory']['timestamp']
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php
new file mode 100644
index 0000000..f22e265
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicedowntimestarthistoryQuery.php
@@ -0,0 +1,202 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service downtime start history records
+ */
+class ServicedowntimestarthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('downtimehistory' => array('sdh.downtimehistory_id', 'so.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'downtimehistory' => array(
+ 'id' => 'sdh.downtimehistory_id',
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_id' => 'sdh.object_id',
+ 'object_type' => '(\'service\')',
+ 'output' => "('[' || sdh.author_name || '] ' || sdh.comment_data)",
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host' => 'so.name1 COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1',
+ 'state' => '(-1)',
+ 'timestamp' => 'UNIX_TIMESTAMP(sdh.actual_start_time)',
+ 'type' => "('dt_start')"
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('sdh.actual_start_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('sdh' => $this->prefix . 'downtimehistory'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sdh.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+
+ if (func_num_args() === 0 || func_get_arg(0) === false) {
+ $this->select->where(
+ "sdh.actual_start_time > '1970-01-02 00:00:00'"
+ );
+ }
+
+ $this->joinedVirtualTables['downtimehistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sdh.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('services');
+
+ return ['so.object_id', 'so.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php
new file mode 100644
index 0000000..48fb0bc
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingendhistoryQuery.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for service flapping end history records
+ */
+class ServiceflappingendhistoryQuery extends ServiceflappingstarthistoryQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('sfh' => $this->prefix . 'flappinghistory'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sfh.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+
+ $this->select->where('sfh.event_type = 1001');
+
+ $this->joinedVirtualTables['flappinghistory'] = true;
+
+ $this->columnMap['flappinghistory']['type'] = '(\'flapping_deleted\')';
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php
new file mode 100644
index 0000000..f068681
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServiceflappingstarthistoryQuery.php
@@ -0,0 +1,197 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service flapping start history records
+ */
+class ServiceflappingstarthistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('flappinghistory' => array('sfh.flappinghistory_id', 'so.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'flappinghistory' => array(
+ 'id' => 'sfh.flappinghistory_id',
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_id' => 'sfh.object_id',
+ 'object_type' => '(\'service\')',
+ 'output' => '(sfh.percent_state_change || \'\')',
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host_name' => 'so.name1',
+ 'state' => '(-1)',
+ 'timestamp' => 'UNIX_TIMESTAMP(sfh.event_time)',
+ 'type' => "('flapping')"
+ ),
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression && $filter->getColumn() === 'timestamp') {
+ $this->requireColumn('timestamp');
+ $filter->setColumn('sfh.event_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('sfh' => $this->prefix . 'flappinghistory'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sfh.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+
+ $this->select->where('sfh.event_type = 1000');
+
+ $this->joinedVirtualTables['flappinghistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sfh.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('services');
+
+ return ['so.object_id', 'so.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php
new file mode 100644
index 0000000..7f7be50
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupQuery.php
@@ -0,0 +1,303 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+class ServicegroupQuery extends IdoQuery
+{
+ protected $groupBase = array(
+ 'servicegroups' => array('sgo.object_id', 'sg.servicegroup_id'),
+ 'servicestatus' => array('ss.servicestatus_id', 'hs.hoststatus_id')
+ );
+
+ protected $groupOrigin = array('members');
+
+ protected $allowCustomVars = true;
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ protected $columnMap = array(
+ 'contacts' => [
+ 'service_contact' => 'sco.name1'
+ ],
+ 'contactgroups' => [
+ 'service_contactgroup' => 'scgo.name1'
+ ],
+ 'hostcontacts' => [
+ 'host_contact' => 'hco.name1'
+ ],
+ 'hostcontactgroups' => [
+ 'host_contactgroup' => 'hcgo.name1'
+ ],
+ 'hostgroups' => array(
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'h.host_object_id' => 's.host_object_id'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'members' => array(
+ 'host_name' => 'so.name1',
+ 'service_description' => 'so.name2'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1'
+ ),
+ 'servicestatus' => array(
+ 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END',
+ 'service_severity' => '
+ CASE WHEN ss.current_state = 0
+ THEN
+ CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL
+ THEN 16
+ ELSE 0
+ END
+ +
+ CASE WHEN ss.problem_has_been_acknowledged = 1
+ THEN 2
+ ELSE
+ CASE WHEN ss.scheduled_downtime_depth > 0
+ THEN 1
+ ELSE 4
+ END
+ END
+ ELSE
+ CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16
+ WHEN ss.current_state = 1 THEN 32
+ WHEN ss.current_state = 2 THEN 128
+ WHEN ss.current_state = 3 THEN 64
+ ELSE 256
+ END
+ +
+ CASE WHEN hs.current_state > 0
+ THEN 1024
+ ELSE
+ CASE WHEN ss.problem_has_been_acknowledged = 1
+ THEN 512
+ ELSE
+ CASE WHEN ss.scheduled_downtime_depth > 0
+ THEN 256
+ ELSE 2048
+ END
+ END
+ END
+ END',
+ 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('sgo' => $this->prefix . 'objects'),
+ array()
+ )->join(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.servicegroup_object_id = sgo.object_id AND sgo.objecttype_id = 4 AND sgo.is_active = 1',
+ array()
+ );
+ $this->joinedVirtualTables = array('servicegroups' => true);
+ }
+
+ /**
+ * Join contacts
+ */
+ protected function joinContacts()
+ {
+ $this->requireVirtualTable('services');
+
+ $this->select->joinLeft(
+ ['sc' => 'icinga_service_contacts'],
+ 'sc.service_id = s.service_id',
+ []
+ )->joinLeft(
+ ['sco' => 'icinga_objects'],
+ 'sco.object_id = sc.contact_object_id AND sco.is_active = 1 AND sco.objecttype_id = 10',
+ []
+ );
+ }
+
+ /**
+ * Join contact groups
+ */
+ protected function joinContactgroups()
+ {
+ $this->requireVirtualTable('services');
+
+ $this->select->joinLeft(
+ ['scg' => 'icinga_service_contactgroups'],
+ 'scg.service_id = s.service_id',
+ []
+ )->joinLeft(
+ ['scgo' => 'icinga_objects'],
+ 'scgo.object_id = scg.contactgroup_object_id AND scgo.is_active = 1 AND scgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host contacts
+ */
+ protected function joinHostcontacts()
+ {
+ $this->requireVirtualTable('services');
+
+ $this->select->joinLeft(
+ ['h' => 'icinga_hosts'],
+ 'h.host_object_id = s.host_object_id',
+ []
+ )->joinLeft(
+ ['hc' => 'icinga_host_contacts'],
+ 'hc.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hco' => 'icinga_objects'],
+ 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10',
+ []
+ );
+ }
+
+ /**
+ * Join host contact groups
+ */
+ protected function joinHostcontactgroups()
+ {
+ $this->requireVirtualTable('services');
+
+ $this->select->joinLeft(
+ ['h' => 'icinga_hosts'],
+ 'h.host_object_id = s.host_object_id',
+ []
+ )->joinLeft(
+ ['hcg' => 'icinga_host_contactgroups'],
+ 'hcg.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hcgo' => 'icinga_objects'],
+ 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.objecttype_id = 3 AND hgo.is_active = 1',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ *
+ * This is required to make filters work which filter by host custom variables.
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+
+ // Host custom var filters work w/o any host related table. If a host table join is necessary here some day,
+ // please adjust `joinHostcontact*()` where we explicitly do this already
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sg.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service objects
+ */
+ protected function joinMembers()
+ {
+ $this->select->join(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.servicegroup_id = sg.servicegroup_id',
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sgm.service_object_id AND so.objecttype_id = 2 AND so.is_active = 1',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->requireVirtualTable('members');
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service status
+ */
+ protected function joinServicestatus()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = s.host_object_id',
+ array()
+ );
+ $this->select->join(
+ array('ss' => $this->prefix . 'servicestatus'),
+ 'ss.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $this->requireVirtualTable('members');
+
+ $query->joinVirtualTable('services');
+
+ return ['so.object_id', 'so.object_id'];
+ } elseif ($name === 'servicegroup') {
+ // Propagate that the "parent" query has to be filtered as well
+ $additionalFilter = clone $filter;
+
+ $this->requireVirtualTable('members');
+
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php
new file mode 100644
index 0000000..11b62d0
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicegroupsummaryQuery.php
@@ -0,0 +1,113 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+
+/**
+ * Query for service group summary
+ */
+class ServicegroupsummaryQuery extends IdoQuery
+{
+
+ protected $allowCustomVars = true;
+
+ protected $columnMap = array(
+ 'servicegroupsummary' => array(
+ 'servicegroup_alias' => 'servicegroup_alias',
+ 'servicegroup_name' => 'servicegroup_name',
+ 'services_critical' => 'SUM(CASE WHEN service_state = 2 THEN 1 ELSE 0 END)',
+ 'services_critical_handled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 1 THEN 1 ELSE 0 END)',
+ 'services_critical_unhandled' => 'SUM(CASE WHEN service_state = 2 AND service_handled = 0 THEN 1 ELSE 0 END)',
+ 'services_ok' => 'SUM(CASE WHEN service_state = 0 THEN 1 ELSE 0 END)',
+ 'services_pending' => 'SUM(CASE WHEN service_state = 99 THEN 1 ELSE 0 END)',
+ 'services_severity' => 'MAX(service_severity)',
+ 'services_total' => 'SUM(1)',
+ 'services_unknown' => 'SUM(CASE WHEN service_state = 3 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 1 THEN 1 ELSE 0 END)',
+ 'services_unknown_unhandled' => 'SUM(CASE WHEN service_state = 3 AND service_handled = 0 THEN 1 ELSE 0 END)',
+ 'services_warning' => 'SUM(CASE WHEN service_state = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_handled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_unhandled' => 'SUM(CASE WHEN service_state = 1 AND service_handled = 0 THEN 1 ELSE 0 END)',
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $summaryQuery;
+
+ /**
+ * Subqueries used for the summary query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = [];
+
+ /**
+ * Count query
+ *
+ * @var IdoQuery
+ */
+ protected $countQuery;
+
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ $this->countQuery->applyFilter(clone $filter);
+ return $this;
+ }
+
+ protected function joinBaseTables()
+ {
+ $this->countQuery = $this->createSubQuery(
+ 'Servicegroup',
+ array()
+ );
+ $subQuery = $this->createSubQuery(
+ 'Servicegroup',
+ array(
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'service_handled',
+ 'service_severity',
+ 'service_state'
+ )
+ );
+ $this->subQueries[] = $subQuery;
+ $emptyGroups = $this->createSubQuery(
+ 'Emptyservicegroup',
+ [
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'service_handled' => new Zend_Db_Expr('NULL'),
+ 'service_severity' => new Zend_Db_Expr('0'),
+ 'service_state' => new Zend_Db_Expr('NULL'),
+ ]
+ );
+ $this->subQueries[] = $emptyGroups;
+ $this->summaryQuery = $this->db->select()->union(
+ [$subQuery, $emptyGroups],
+ Zend_Db_Select::SQL_UNION_ALL
+ );
+ $this->select->from(['servicesgroupsummary' => $this->summaryQuery], []);
+ $this->group(['servicegroup_name', 'servicegroup_alias']);
+ $this->joinedVirtualTables['servicegroupsummary'] = true;
+ }
+
+ public function getCountQuery()
+ {
+ $count = $this->countQuery->select();
+ $this->countQuery->applyFilterSql($count);
+ $count->columns(array('sgo.object_id'));
+ $count->group(array('sgo.object_id'));
+ return $this->db->select()->from($count, array('cnt' => 'COUNT(*)'));
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php
new file mode 100644
index 0000000..d3fccf0
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicenotificationQuery.php
@@ -0,0 +1,286 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service notifications
+ */
+class ServicenotificationQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'contactnotifications' => array(
+ 'notification_contact_name' => 'co.name1'
+ ),
+ 'history' => array(
+ 'output' => null,
+ 'state' => 'sn.state',
+ 'timestamp' => 'UNIX_TIMESTAMP(sn.start_time)',
+ 'type' => '
+ CASE sn.notification_reason
+ WHEN 1 THEN \'notification_ack\'
+ WHEN 2 THEN \'notification_flapping\'
+ WHEN 3 THEN \'notification_flapping_end\'
+ WHEN 5 THEN \'notification_dt_start\'
+ WHEN 6 THEN \'notification_dt_end\'
+ WHEN 7 THEN \'notification_dt_end\'
+ WHEN 8 THEN \'notification_custom\'
+ ELSE \'notification_state\'
+ END',
+ ),
+ 'hostgroups' => array(
+ 'hostgroup_name' => 'hgo.name1',
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
+ 'host_alias' => 'h.alias COLLATE latin1_general_ci',
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'notifications' => array(
+ 'id' => 'sn.notification_id',
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'notification_output' => 'sn.output',
+ 'notification_reason' => 'sn.notification_reason',
+ 'notification_state' => 'sn.state',
+ 'notification_timestamp' => 'UNIX_TIMESTAMP(sn.start_time)',
+ 'object_type' => '(\'service\')',
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host_name' => 'so.name1'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ )
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression) {
+ switch ($filter->getColumn()) {
+ case 'output':
+ $this->requireColumn('output');
+ $filter->setColumn('sn.output');
+ return null;
+ case 'timestamp':
+ case 'notification_timestamp':
+ $this->requireColumn($filter->getColumn());
+ $filter->setColumn('sn.start_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ }
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ switch ($this->ds->getDbType()) {
+ case 'mysql':
+ $concattedContacts = "GROUP_CONCAT("
+ . "DISTINCT co.name1 ORDER BY co.name1 SEPARATOR ', '"
+ . ") COLLATE latin1_general_ci";
+ break;
+ case 'pgsql':
+ // TODO: Find a way to order the contact alias list:
+ $concattedContacts = "ARRAY_TO_STRING(ARRAY_AGG(DISTINCT co.name1), ', ')";
+ break;
+ }
+ $this->columnMap['history']['output'] = "('[' || $concattedContacts || '] ' || sn.output)";
+
+ $this->select->from(
+ array('sn' => $this->prefix . 'notifications'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sn.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ $this->joinedVirtualTables['notifications'] = true;
+ }
+
+ /**
+ * Join virtual table history
+ */
+ protected function joinHistory()
+ {
+ $this->requireVirtualTable('contactnotifications');
+ }
+
+ /**
+ * Join contact notifications
+ */
+ protected function joinContactnotifications()
+ {
+ $this->select->joinLeft(
+ array('cn' => $this->prefix . 'contactnotifications'),
+ 'cn.notification_id = sn.notification_id',
+ array()
+ );
+ $this->select->joinLeft(
+ array('co' => $this->prefix . 'objects'),
+ 'co.object_id = cn.contact_object_id AND co.is_active = 1 AND co.objecttype_id = 10',
+ array()
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sn.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroup()
+ {
+ $group = array();
+
+ if ($this->hasJoinedVirtualTable('history')
+ || $this->hasJoinedVirtualTable('hostgroups')
+ || $this->hasJoinedVirtualTable('servicegroups')
+ ) {
+ $group = array('sn.notification_id', 'so.object_id');
+ if ($this->hasJoinedVirtualTable('contactnotifications') && !$this->hasJoinedVirtualTable('history')) {
+ $group[] = 'co.object_id';
+ }
+ } elseif ($this->hasJoinedVirtualTable('contactnotifications')) {
+ $group = array('sn.notification_id', 'co.object_id', 'so.object_id');
+ }
+
+ if (! empty($group)) {
+ if ($this->hasJoinedVirtualTable('hosts')) {
+ $group[] = 'h.host_id';
+ }
+
+ if ($this->hasJoinedVirtualTable('services')) {
+ $group[] = 's.service_id';
+ }
+
+ if ($this->hasJoinedVirtualTable('instances')) {
+ $group[] = 'i.instance_id';
+ }
+ }
+
+ return $group;
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $this->requireVirtualTable('services');
+
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 's.host_object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php
new file mode 100644
index 0000000..f93ca8a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatehistoryQuery.php
@@ -0,0 +1,220 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service state history records
+ */
+class ServicestatehistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('statehistory' => array('sh.statehistory_id', 'so.object_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups');
+
+ /**
+ * Array to map type names to type ids for query optimization
+ *
+ * @var array
+ */
+ protected $types = array(
+ 'soft_state' => 0,
+ 'hard_state' => 1
+ );
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_alias' => 'h.alias',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci'
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'services' => array(
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci'
+ ),
+ 'statehistory' => array(
+ 'id' => 'sh.statehistory_id',
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_id' => 'sh.object_id',
+ 'object_type' => '(\'service\')',
+ 'output' => '(CASE WHEN sh.state_type = 1 THEN sh.output ELSE \'[ \' || sh.current_check_attempt || \'/\' || sh.max_check_attempts || \' ] \' || sh.output END)',
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_description' => 'so.name2',
+ 'service_host' => 'so.name1 COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1',
+ 'state' => 'sh.state',
+ 'timestamp' => 'UNIX_TIMESTAMP(sh.state_time)',
+ 'type' => "(CASE WHEN sh.state_type = 1 THEN 'hard_state' ELSE 'soft_state' END)"
+ ),
+ );
+
+ protected function requireFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterExpression) {
+ switch ($filter->getColumn()) {
+ case 'timestamp':
+ $this->requireColumn('timestamp');
+ $filter->setColumn('sh.state_time');
+ $filter->setExpression($this->timestampForSql($this->valueToTimestamp($filter->getExpression())));
+ return null;
+ case 'type':
+ if (! is_array($filter->getExpression())) {
+ $this->requireColumn('type');
+ $filter->setColumn('sh.state_type');
+ if (isset($this->types[$filter->getExpression()])) {
+ $filter->setExpression($this->types[$filter->getExpression()]);
+ } else {
+ $filter->setExpression(-1);
+ }
+
+ return null;
+ }
+ }
+ }
+
+ return parent::requireFilterColumns($filter);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->select->from(
+ array('sh' => $this->prefix . 'statehistory'),
+ array()
+ )->join(
+ array('so' => $this->prefix . 'objects'),
+ 'so.object_id = sh.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ $this->joinedVirtualTables['statehistory'] = true;
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->requireVirtualTable('services');
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = sh.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.' . $this->servicegroup_id . ' = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $this->select->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('services');
+
+ return ['so.object_id', 'so.object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php
new file mode 100644
index 0000000..fafa03b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php
@@ -0,0 +1,524 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for service status
+ */
+class ServicestatusQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowCustomVars = true;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupBase = array('services' => array('so.object_id', 's.service_id'));
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $groupOrigin = array('hostgroups', 'servicegroups', 'contacts', 'contactgroups');
+
+ protected $subQueryTargets = array(
+ 'hostgroups' => 'hostgroup',
+ 'servicegroups' => 'servicegroup'
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'checktimeperiods' => array(
+ 'service_check_timeperiod' => 'ctp.alias COLLATE latin1_general_ci'
+ ),
+ 'contacts' => [
+ 'service_contact' => 'sco.name1'
+ ],
+ 'contactgroups' => [
+ 'service_contactgroup' => 'scgo.name1'
+ ],
+ 'hostcontacts' => [
+ 'host_contact' => 'hco.name1'
+ ],
+ 'hostcontactgroups' => [
+ 'host_contactgroup' => 'hcgo.name1'
+ ],
+ 'hostgroups' => array(
+ 'hostgroup' => 'hgo.name1 COLLATE latin1_general_ci',
+ 'hostgroup_alias' => 'hg.alias COLLATE latin1_general_ci',
+ 'hostgroup_name' => 'hgo.name1'
+ ),
+ 'hosts' => array(
+ 'host_action_url' => 'h.action_url',
+ 'host_address' => 'h.address',
+ 'host_address6' => 'h.address6',
+ 'host_alias' => 'h.alias COLLATE latin1_general_ci',
+ 'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
+ 'host_icon_image' => 'h.icon_image',
+ 'host_icon_image_alt' => 'h.icon_image_alt',
+ 'host_ipv4' => 'INET_ATON(h.address)',
+ 'host_notes' => 'h.notes',
+ 'host_notes_url' => 'h.notes_url'
+ ),
+ 'hoststatus' => array(
+ 'host_acknowledged' => 'hs.problem_has_been_acknowledged',
+ 'host_acknowledgement_type' => 'hs.acknowledgement_type',
+ 'host_active_checks_enabled' => 'hs.active_checks_enabled',
+ 'host_active_checks_enabled_changed' => 'CASE WHEN hs.active_checks_enabled = h.active_checks_enabled THEN 0 ELSE 1 END',
+ 'host_attempt' => 'hs.current_check_attempt || \'/\' || hs.max_check_attempts',
+ 'host_check_command' => 'hs.check_command',
+ 'host_check_execution_time' => 'hs.execution_time',
+ 'host_check_latency' => 'hs.latency',
+ 'host_check_source' => 'hs.check_source',
+ 'host_check_timeperiod_object_id' => 'hs.check_timeperiod_object_id',
+ 'host_check_type' => 'hs.check_type',
+ 'host_current_check_attempt' => 'hs.current_check_attempt',
+ 'host_current_notification_number' => 'hs.current_notification_number',
+ 'host_event_handler' => 'hs.event_handler',
+ 'host_event_handler_enabled' => 'hs.event_handler_enabled',
+ 'host_event_handler_enabled_changed' => 'CASE WHEN hs.event_handler_enabled = h.event_handler_enabled THEN 0 ELSE 1 END',
+ 'host_failure_prediction_enabled' => 'hs.failure_prediction_enabled',
+ 'host_flap_detection_enabled' => 'hs.flap_detection_enabled',
+ 'host_flap_detection_enabled_changed' => 'CASE WHEN hs.flap_detection_enabled = h.flap_detection_enabled THEN 0 ELSE 1 END',
+ 'host_handled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) > 0 THEN 1 ELSE 0 END',
+ 'host_hard_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE CASE WHEN hs.state_type = 1 THEN hs.current_state ELSE hs.last_hard_state END END',
+ 'host_in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END',
+ 'host_is_flapping' => 'hs.is_flapping',
+ 'host_is_reachable' => 'hs.is_reachable',
+ 'host_last_check' => 'UNIX_TIMESTAMP(hs.last_check)',
+ 'host_last_hard_state' => 'hs.last_hard_state',
+ 'host_last_hard_state_change' => 'UNIX_TIMESTAMP(hs.last_hard_state_change)',
+ 'host_last_notification' => 'UNIX_TIMESTAMP(hs.last_notification)',
+ 'host_last_state_change' => 'UNIX_TIMESTAMP(hs.last_state_change)',
+ 'host_last_time_down' => 'UNIX_TIMESTAMP(hs.last_time_down)',
+ 'host_last_time_unreachable' => 'UNIX_TIMESTAMP(hs.last_time_unreachable)',
+ 'host_last_time_up' => 'UNIX_TIMESTAMP(hs.last_time_up)',
+ 'host_long_output' => 'hs.long_output',
+ 'host_max_check_attempts' => 'hs.max_check_attempts',
+ 'host_modified_host_attributes' => 'hs.modified_host_attributes',
+ 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END',
+ 'host_next_notification' => 'UNIX_TIMESTAMP(hs.next_notification)',
+ 'host_no_more_notifications' => 'hs.no_more_notifications',
+ 'host_normal_check_interval' => 'hs.normal_check_interval',
+ 'host_notifications_enabled' => 'hs.notifications_enabled',
+ 'host_notifications_enabled_changed' => 'CASE WHEN hs.notifications_enabled = h.notifications_enabled THEN 0 ELSE 1 END',
+ 'host_obsessing' => 'hs.obsess_over_host',
+ 'host_obsessing_changed' => 'CASE WHEN hs.obsess_over_host = h.obsess_over_host THEN 0 ELSE 1 END',
+ 'host_output' => 'hs.output',
+ 'host_passive_checks_enabled' => 'hs.passive_checks_enabled',
+ 'host_passive_checks_enabled_changed' => 'CASE WHEN hs.passive_checks_enabled = h.passive_checks_enabled THEN 0 ELSE 1 END',
+ 'host_percent_state_change' => 'hs.percent_state_change',
+ 'host_perfdata' => 'hs.perfdata',
+ 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END',
+ 'host_problem_has_been_acknowledged' => 'hs.problem_has_been_acknowledged',
+ 'host_process_performance_data' => 'hs.process_performance_data',
+ 'host_retry_check_interval' => 'hs.retry_check_interval',
+ 'host_scheduled_downtime_depth' => 'hs.scheduled_downtime_depth',
+ 'host_severity' => '
+ CASE
+ WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL
+ THEN 16
+ ELSE
+ CASE
+ WHEN hs.current_state = 0
+ THEN 1
+ ELSE
+ CASE
+ WHEN hs.current_state = 1 THEN 64
+ WHEN hs.current_state = 2 THEN 32
+ ELSE 256
+ END
+ +
+ CASE
+ WHEN hs.problem_has_been_acknowledged = 1 THEN 2
+ WHEN hs.scheduled_downtime_depth > 0 THEN 1
+ ELSE 256
+ END
+ END
+ END',
+ 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END',
+ 'host_state_type' => 'hs.state_type',
+ 'host_status_update_time' => 'hs.status_update_time',
+ 'host_unhandled' => 'CASE WHEN (hs.problem_has_been_acknowledged + hs.scheduled_downtime_depth) = 0 THEN 1 ELSE 0 END'
+
+ ),
+ 'instances' => array(
+ 'instance_name' => 'i.instance_name'
+ ),
+ 'services' => array(
+ 'host' => 'so.name1 COLLATE latin1_general_ci',
+ 'host_name' => 'so.name1',
+ 'object_type' => '(\'service\')',
+ 'service' => 'so.name2 COLLATE latin1_general_ci',
+ 'service_action_url' => 's.action_url',
+ 'service_check_interval' => '(s.check_interval * 60)',
+ 'service_description' => 'so.name2',
+ 'service_display_name' => 's.display_name COLLATE latin1_general_ci',
+ 'service_host' => 'so.name1 COLLATE latin1_general_ci',
+ 'service_host_name' => 'so.name1',
+ 'service_icon_image' => 's.icon_image',
+ 'service_icon_image_alt' => 's.icon_image_alt',
+ 'service_notes_url' => 's.notes_url',
+ 'service_notes' => 's.notes'
+ ),
+ 'servicegroups' => array(
+ 'servicegroup' => 'sgo.name1 COLLATE latin1_general_ci',
+ 'servicegroup_name' => 'sgo.name1',
+ 'servicegroup_alias' => 'sg.alias COLLATE latin1_general_ci'
+ ),
+ 'servicestatus' => array(
+ 'service_acknowledged' => 'ss.problem_has_been_acknowledged',
+ 'service_acknowledgement_type' => 'ss.acknowledgement_type',
+ 'service_active_checks_enabled' => 'ss.active_checks_enabled',
+ 'service_active_checks_enabled_changed' => 'CASE WHEN ss.active_checks_enabled=s.active_checks_enabled THEN 0 ELSE 1 END',
+ 'service_attempt' => 'ss.current_check_attempt || \'/\' || ss.max_check_attempts',
+ 'service_check_command' => 'ss.check_command',
+ 'service_check_execution_time' => 'ss.execution_time',
+ 'service_check_latency' => 'ss.latency',
+ 'service_check_source' => 'ss.check_source',
+ 'service_check_timeperiod_object_id' => 'ss.check_timeperiod_object_id',
+ 'service_check_type' => 'ss.check_type',
+ 'service_current_check_attempt' => 'ss.current_check_attempt',
+ 'service_current_notification_number' => 'ss.current_notification_number',
+ 'service_event_handler' => 'ss.event_handler',
+ 'service_event_handler_enabled' => 'ss.event_handler_enabled',
+ 'service_event_handler_enabled_changed' => 'CASE WHEN ss.event_handler_enabled=s.event_handler_enabled THEN 0 ELSE 1 END',
+ 'service_failure_prediction_enabled' => 'ss.failure_prediction_enabled',
+ 'service_flap_detection_enabled' => 'ss.flap_detection_enabled',
+ 'service_flap_detection_enabled_changed' => 'CASE WHEN ss.flap_detection_enabled=s.flap_detection_enabled THEN 0 ELSE 1 END',
+ 'service_handled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END',
+ 'service_hard_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE CASE WHEN ss.state_type = 1 THEN ss.current_state ELSE ss.last_hard_state END END',
+ 'service_in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0 OR ss.scheduled_downtime_depth IS NULL) THEN 0 ELSE 1 END',
+ 'service_is_flapping' => 'ss.is_flapping',
+ 'service_is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END',
+ 'service_is_reachable' => 'ss.is_reachable',
+ 'service_last_check' => 'UNIX_TIMESTAMP(ss.last_check)',
+ 'service_last_hard_state' => 'ss.last_hard_state',
+ 'service_last_hard_state_change' => 'UNIX_TIMESTAMP(ss.last_hard_state_change)',
+ 'service_last_notification' => 'UNIX_TIMESTAMP(ss.last_notification)',
+ 'service_last_state_change' => 'UNIX_TIMESTAMP(ss.last_state_change)',
+ 'service_last_state_change_ts' => 'ss.last_state_change',
+ 'service_last_time_critical' => 'ss.last_time_critical',
+ 'service_last_time_ok' => 'ss.last_time_ok',
+ 'service_last_time_unknown' => 'ss.last_time_unknown',
+ 'service_last_time_warning' => 'ss.last_time_warning',
+ 'service_long_output' => 'ss.long_output',
+ 'service_max_check_attempts' => 'ss.max_check_attempts',
+ 'service_modified_service_attributes' => 'ss.modified_service_attributes',
+ 'service_next_check' => 'UNIX_TIMESTAMP(ss.next_check)',
+ 'service_next_notification' => 'UNIX_TIMESTAMP(ss.next_notification)',
+ 'service_next_update' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL
+ THEN
+ CASE ss.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(ss.next_check) + (ss.normal_check_interval * 60) ELSE NULL END
+ ELSE
+ UNIX_TIMESTAMP(ss.next_check)
+ + (CASE WHEN
+ COALESCE(ss.current_state, 0) > 0 AND ss.state_type = 0
+ THEN
+ ss.retry_check_interval
+ ELSE
+ ss.normal_check_interval
+ END * 60)
+ + (CEIL(ss.execution_time + ss.latency) * 2)
+ END',
+ 'service_no_more_notifications' => 'ss.no_more_notifications',
+ 'service_normal_check_interval' => 'ss.normal_check_interval',
+ 'service_notifications_enabled' => 'ss.notifications_enabled',
+ 'service_notifications_enabled_changed' => 'CASE WHEN ss.notifications_enabled=s.notifications_enabled THEN 0 ELSE 1 END',
+ 'service_obsessing' => 'ss.obsess_over_service',
+ 'service_obsessing_changed' => 'CASE WHEN ss.obsess_over_service=s.obsess_over_service THEN 0 ELSE 1 END',
+ 'service_output' => 'ss.output',
+ 'service_passive_checks_enabled' => 'ss.passive_checks_enabled',
+ 'service_passive_checks_enabled_changed' => 'CASE WHEN ss.passive_checks_enabled=s.passive_checks_enabled THEN 0 ELSE 1 END',
+ 'service_percent_state_change' => 'ss.percent_state_change',
+ 'service_perfdata' => 'ss.perfdata',
+ 'service_problem' => 'CASE WHEN COALESCE(ss.current_state, 0) = 0 THEN 0 ELSE 1 END',
+ 'service_problem_has_been_acknowledged' => 'ss.problem_has_been_acknowledged',
+ 'service_process_performance_data' => 'ss.process_performance_data',
+ 'service_retry_check_interval' => 'ss.retry_check_interval',
+ 'service_scheduled_downtime_depth' => 'ss.scheduled_downtime_depth',
+ 'service_severity' => 'CASE WHEN ss.current_state = 0
+ THEN
+ CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL
+ THEN 16
+ ELSE 0
+ END
+ +
+ CASE WHEN ss.problem_has_been_acknowledged = 1
+ THEN 2
+ ELSE
+ CASE WHEN ss.scheduled_downtime_depth > 0
+ THEN 1
+ ELSE 4
+ END
+ END
+ ELSE
+ CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 16
+ WHEN ss.current_state = 1 THEN 32
+ WHEN ss.current_state = 2 THEN 128
+ WHEN ss.current_state = 3 THEN 64
+ ELSE 256
+ END
+ +
+ CASE WHEN hs.current_state > 0
+ THEN 1024
+ ELSE
+ CASE WHEN ss.problem_has_been_acknowledged = 1
+ THEN 512
+ ELSE
+ CASE WHEN ss.scheduled_downtime_depth > 0
+ THEN 256
+ ELSE 2048
+ END
+ END
+ END
+ END',
+ 'service_state' => 'CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL THEN 99 ELSE ss.current_state END',
+ 'service_state_type' => 'ss.state_type',
+ 'service_status_update_time' => 'ss.status_update_time',
+ 'service_unhandled' => 'CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) = 0 THEN 1 ELSE 0 END',
+ 'problems' => 'CASE WHEN COALESCE(ss.current_state, 0) = 0 THEN 0 ELSE 1 END'
+ )
+ );
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ if (version_compare($this->getIdoVersion(), '1.10.0', '<')) {
+ $this->columnMap['hoststatus']['host_check_source'] = '(NULL)';
+ $this->columnMap['servicestatus']['service_check_source'] = '(NULL)';
+ }
+ if (version_compare($this->getIdoVersion(), '1.13.0', '<')) {
+ $this->columnMap['hoststatus']['host_is_reachable'] = '(NULL)';
+ $this->columnMap['servicestatus']['service_is_reachable'] = '(NULL)';
+ }
+
+ $this->select->from(
+ array('so' => $this->prefix . 'objects'),
+ array()
+ )->join(
+ array('s' => $this->prefix . 'services'),
+ 's.service_object_id = so.object_id AND so.is_active = 1 AND so.objecttype_id = 2',
+ array()
+ );
+ $this->joinedVirtualTables['services'] = true;
+ }
+
+ /**
+ * Join check time periods
+ */
+ protected function joinChecktimeperiods()
+ {
+ $this->select->joinLeft(
+ array('ctp' => $this->prefix . 'timeperiods'),
+ 'ctp.timeperiod_object_id = s.check_timeperiod_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join contacts
+ */
+ protected function joinContacts()
+ {
+ $this->select->joinLeft(
+ ['sc' => 'icinga_service_contacts'],
+ 'sc.service_id = s.service_id',
+ []
+ )->joinLeft(
+ ['sco' => 'icinga_objects'],
+ 'sco.object_id = sc.contact_object_id AND sco.is_active = 1 AND sco.objecttype_id = 10',
+ []
+ );
+ }
+
+ /**
+ * Join contact groups
+ */
+ protected function joinContactgroups()
+ {
+ $this->select->joinLeft(
+ ['scg' => 'icinga_service_contactgroups'],
+ 'scg.service_id = s.service_id',
+ []
+ )->joinLeft(
+ ['scgo' => 'icinga_objects'],
+ 'scgo.object_id = scg.contactgroup_object_id AND scgo.is_active = 1 AND scgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host contacts
+ */
+ protected function joinHostcontacts()
+ {
+ $this->requireVirtualTable('hosts');
+
+ $this->select->joinLeft(
+ ['hc' => 'icinga_host_contacts'],
+ 'hc.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hco' => 'icinga_objects'],
+ 'hco.object_id = hc.contact_object_id AND hco.is_active = 1 AND hco.objecttype_id = 10',
+ []
+ );
+ }
+
+ /**
+ * Join host contact groups
+ */
+ protected function joinHostcontactgroups()
+ {
+ $this->requireVirtualTable('hosts');
+
+ $this->select->joinLeft(
+ ['hcg' => 'icinga_host_contactgroups'],
+ 'hcg.host_id = h.host_id',
+ []
+ )->joinLeft(
+ ['hcgo' => 'icinga_objects'],
+ 'hcgo.object_id = hcg.contactgroup_object_id AND hcgo.is_active = 1 AND hcgo.objecttype_id = 11',
+ []
+ );
+ }
+
+ /**
+ * Join host groups
+ */
+ protected function joinHostgroups()
+ {
+ $this->select->joinLeft(
+ array('hgm' => $this->prefix . 'hostgroup_members'),
+ 'hgm.host_object_id = s.host_object_id',
+ array()
+ )->joinLeft(
+ array('hg' => $this->prefix . 'hostgroups'),
+ 'hg.hostgroup_id = hgm.hostgroup_id',
+ array()
+ )->joinLeft(
+ array('hgo' => $this->prefix . 'objects'),
+ 'hgo.object_id = hg.hostgroup_object_id AND hgo.is_active = 1 AND hgo.objecttype_id = 3',
+ array()
+ );
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $this->select->join(
+ array('h' => $this->prefix . 'hosts'),
+ 'h.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join host status
+ */
+ protected function joinHoststatus()
+ {
+ $this->select->join(
+ array('hs' => $this->prefix . 'hoststatus'),
+ 'hs.host_object_id = s.host_object_id',
+ array()
+ );
+ }
+
+ /**
+ * Join instances
+ */
+ protected function joinInstances()
+ {
+ $this->select->join(
+ array('i' => $this->prefix . 'instances'),
+ 'i.instance_id = so.instance_id',
+ array()
+ );
+ }
+
+ /**
+ * Join service groups
+ */
+ protected function joinServicegroups()
+ {
+ $this->select->joinLeft(
+ array('sgm' => $this->prefix . 'servicegroup_members'),
+ 'sgm.service_object_id = so.object_id',
+ array()
+ )->joinLeft(
+ array('sg' => $this->prefix . 'servicegroups'),
+ 'sg.servicegroup_id = sgm.servicegroup_id',
+ array()
+ )->joinLeft(
+ array('sgo' => $this->prefix . 'objects'),
+ 'sgo.object_id = sg.servicegroup_object_id AND sgo.is_active = 1 AND sgo.objecttype_id = 4',
+ array()
+ );
+ }
+
+ /**
+ * Join service status
+ */
+ protected function joinServicestatus()
+ {
+ $this->requireVirtualTable('hoststatus');
+ $this->select->join(
+ array('ss' => $this->prefix . 'servicestatus'),
+ 'ss.service_object_id = so.object_id',
+ array()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function registerGroupColumns($alias, $table, array &$groupedColumns, array &$groupedTables)
+ {
+ if ($alias === 'service_handled' || $alias === 'service_severity' || $alias === 'service_unhandled') {
+ if (! isset($groupedTables['hoststatus'])) {
+ $groupedColumns[] = 'hs.hoststatus_id';
+ $groupedTables['hoststatus'] = true;
+ }
+
+ if (! isset($groupedTables['servicestatus'])) {
+ $groupedColumns[] = 'ss.servicestatus_id';
+ $groupedTables['servicestatus'] = true;
+ }
+ } elseif ($table === 'contacts') {
+ $groupedColumns[] = 'sc.service_contact_id';
+ $groupedColumns[] = 'sco.object_id';
+ $groupedTables[$table] = true;
+ } elseif ($table === 'contactgroups') {
+ $groupedColumns[] = 'scg.service_contactgroup_id';
+ $groupedColumns[] = 'scgo.object_id';
+ $groupedTables[$table] = true;
+ } else {
+ parent::registerGroupColumns($alias, $table, $groupedColumns, $groupedTables);
+ }
+ }
+
+ protected function joinSubQuery(IdoQuery $query, $name, $filter, $and, $negate, &$additionalFilter)
+ {
+ if ($name === 'hostgroup') {
+ $query->joinVirtualTable('members');
+
+ return ['hgm.host_object_id', 's.host_object_id'];
+ } elseif ($name === 'servicegroup') {
+ $query->joinVirtualTable('members');
+
+ return ['sgm.service_object_id', 'so.object_id'];
+ }
+
+ return parent::joinSubQuery($query, $name, $filter, $and, $negate, $additionalFilter);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php
new file mode 100644
index 0000000..cf59cf3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatussummaryQuery.php
@@ -0,0 +1,104 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterExpression;
+
+/**
+ * Query for service status summary
+ *
+ * TODO(el): Allow to switch between hard and soft states
+ */
+class ServicestatussummaryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'servicestatussummary' => array(
+ 'services_critical' => 'SUM(CASE WHEN state = 2 THEN 1 ELSE 0 END)',
+ 'services_critical_handled' => 'SUM(CASE WHEN state = 2 AND handled = 1 THEN 1 ELSE 0 END)',
+// 'services_critical_handled_last_state_change' => 'MAX(CASE WHEN state = 2 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_critical_unhandled' => 'SUM(CASE WHEN state = 2 AND handled = 0 THEN 1 ELSE 0 END)',
+// 'services_critical_unhandled_last_state_change' => 'MAX(CASE WHEN state = 2 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_ok' => 'SUM(CASE WHEN state = 0 THEN 1 ELSE 0 END)',
+// 'services_ok_last_state_change' => 'MAX(CASE WHEN state = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_pending' => 'SUM(CASE WHEN state = 99 THEN 1 ELSE 0 END)',
+// 'services_pending_last_state_change' => 'MAX(CASE WHEN state = 99 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_total' => 'SUM(1)',
+ 'services_unknown' => 'SUM(CASE WHEN state = 3 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled' => 'SUM(CASE WHEN state = 3 AND handled = 1 THEN 1 ELSE 0 END)',
+// 'services_unknown_handled_last_state_change' => 'MAX(CASE WHEN state = 3 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_unknown_unhandled' => 'SUM(CASE WHEN state = 3 AND handled = 0 THEN 1 ELSE 0 END)',
+// 'services_unknown_unhandled_last_state_change' => 'MAX(CASE WHEN state = 3 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_warning' => 'SUM(CASE WHEN state = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_handled' => 'SUM(CASE WHEN state = 1 AND handled = 1 THEN 1 ELSE 0 END)',
+// 'services_warning_handled_last_state_change' => 'MAX(CASE WHEN state = 1 AND handled = 1 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)',
+ 'services_warning_unhandled' => 'SUM(CASE WHEN state = 1 AND handled = 0 THEN 1 ELSE 0 END)',
+// 'services_warning_unhandled_last_state_change' => 'MAX(CASE WHEN state = 1 AND handled = 0 THEN UNIX_TIMESTAMP(last_state_change) ELSE NULL END)'
+ )
+ );
+
+ /**
+ * The service status sub select
+ *
+ * @var ServiceStatusQuery
+ */
+ protected $subSelect;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ return $this->subSelect->allowsCustomVars();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ $this->subSelect->applyFilter(clone $filter);
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ // TODO(el): Allow to switch between hard and soft states
+ $this->subSelect = $this->createSubQuery(
+ 'servicestatus',
+ array(
+ 'handled' => 'service_handled',
+ 'state' => 'service_state',
+ 'state_change' => 'service_last_state_change'
+ )
+ );
+ $this->select->from(
+ array('servicestatussummary' => $this->subSelect->setIsSubQuery(true)),
+ array()
+ );
+ $this->joinedVirtualTables['servicestatussummary'] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->subSelect->where($condition, $value);
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->subSelect->whereEx($ex);
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php
new file mode 100644
index 0000000..18d893f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatechangeeventQuery.php
@@ -0,0 +1,41 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+/**
+ * Query for host and service state change events
+ */
+class StatechangeeventQuery extends IdoQuery
+{
+ protected $columnMap = array(
+ 'statechangeevent' => array(
+ 'statechangeevent_id' => 'sh.statehistory_id',
+ 'statechangeevent_state_time' => 'UNIX_TIMESTAMP(sh.state_time)',
+ 'statechangeevent_state_change' => 'sh.state_change',
+ 'statechangeevent_state' => 'sh.state',
+ 'statechangeevent_state_type' => "(CASE sh.state_type WHEN 0 THEN 'soft_state' WHEN 1 THEN 'hard_state' ELSE NULL END)",
+ 'statechangeevent_current_check_attempt' => 'sh.current_check_attempt',
+ 'statechangeevent_max_check_attempts' => 'sh.max_check_attempts',
+ 'statechangeevent_last_state' => 'sh.last_state',
+ 'statechangeevent_last_hard_state' => 'sh.last_hard_state',
+ 'statechangeevent_output' => 'sh.output',
+ 'statechangeevent_long_output' => 'sh.long_output',
+ 'statechangeevent_check_source' => 'sh.check_source'
+ ),
+ 'object' => array(
+ 'host_name' => 'o.name1',
+ 'service_description' => 'o.name2'
+ )
+ );
+
+ protected function joinBaseTables()
+ {
+ $this->select()
+ ->from(array('sh' => $this->prefix . 'statehistory'), array())
+ ->join(array('o' => $this->prefix . 'objects'), 'sh.object_id = o.object_id', array());
+
+ $this->joinedVirtualTables['statechangeevent'] = true;
+ $this->joinedVirtualTables['object'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php
new file mode 100644
index 0000000..56d1e3b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatehistoryQuery.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service state history records
+ */
+class StatehistoryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'statehistory' => array(
+ 'id' => 'sth.id',
+ 'object_type' => 'sth.object_type'
+ ),
+ 'history' => array(
+ 'type' => 'sth.type',
+ 'timestamp' => 'sth.timestamp',
+ 'object_id' => 'sth.object_id',
+ 'state' => 'sth.state',
+ 'output' => 'sth.output'
+ ),
+ 'hosts' => array(
+ 'host_display_name' => 'sth.host_display_name',
+ 'host_name' => 'sth.host_name'
+ ),
+ 'services' => array(
+ 'service_description' => 'sth.service_description',
+ 'service_display_name' => 'sth.service_display_name',
+ 'service_host_name' => 'sth.service_host_name'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $stateHistoryQuery;
+
+ /**
+ * Subqueries used for the state history query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * Whether to additionally select all history columns
+ *
+ * @var bool
+ */
+ protected $fetchHistoryColumns = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ $this->stateHistoryQuery = $this->db->select();
+ $this->select->from(
+ array('sth' => $this->stateHistoryQuery),
+ array()
+ );
+ $this->joinedVirtualTables['statehistory'] = true;
+ }
+
+ /**
+ * Join history related columns and tables
+ */
+ protected function joinHistory()
+ {
+ // TODO: Ensure that one is selecting the history columns first...
+ $this->fetchHistoryColumns = true;
+ $this->requireVirtualTable('hosts');
+ $this->requireVirtualTable('services');
+ }
+
+ /**
+ * Join hosts
+ */
+ protected function joinHosts()
+ {
+ $columns = array_keys(
+ $this->columnMap['statehistory'] + $this->columnMap['hosts']
+ );
+ foreach ($this->columnMap['services'] as $column => $_) {
+ $columns[$column] = new Zend_Db_Expr('NULL');
+ }
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $hosts = $this->createSubQuery('Hoststatehistory', $columns);
+ $this->subQueries[] = $hosts;
+ $this->stateHistoryQuery->union(array($hosts), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * Join services
+ */
+ protected function joinServices()
+ {
+ $columns = array_keys(
+ $this->columnMap['statehistory'] + $this->columnMap['hosts'] + $this->columnMap['services']
+ );
+ if ($this->fetchHistoryColumns) {
+ $columns = array_merge($columns, array_keys($this->columnMap['history']));
+ }
+ $services = $this->createSubQuery('Servicestatehistory', $columns);
+ $this->subQueries[] = $services;
+ $this->stateHistoryQuery->union(array($services), Zend_Db_Select::SQL_UNION_ALL);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php
new file mode 100644
index 0000000..b1ee9e2
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatussummaryQuery.php
@@ -0,0 +1,243 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\FilterExpression;
+use Zend_Db_Expr;
+use Zend_Db_Select;
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for host and service status summary
+ */
+class StatussummaryQuery extends IdoQuery
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $columnMap = array(
+ 'hoststatussummary' => array(
+ 'hosts_total' => 'SUM(CASE WHEN object_type = \'host\' THEN 1 ELSE 0 END)',
+ 'hosts_up' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 THEN 1 ELSE 0 END)',
+ 'hosts_up_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'hosts_pending' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 THEN 1 ELSE 0 END)',
+ 'hosts_pending_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'hosts_down' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'hosts_down_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'hosts_down_passive' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'hosts_down_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_handled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_unhandled' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_passive' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'hosts_unreachable_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'hosts_active' => 'SUM(CASE WHEN object_type = \'host\' AND is_active_checked = 1 THEN 1 ELSE 0 END)',
+ 'hosts_passive' => 'SUM(CASE WHEN object_type = \'host\' AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'hosts_not_checked' => 'SUM(CASE WHEN object_type = \'host\' AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'hosts_not_processing_event_handlers' => 'SUM(CASE WHEN object_type = \'host\' AND is_processing_events = 0 THEN 1 ELSE 0 END)',
+ 'hosts_not_triggering_notifications' => 'SUM(CASE WHEN object_type = \'host\' AND is_triggering_notifications = 0 THEN 1 ELSE 0 END)',
+ 'hosts_without_flap_detection' => 'SUM(CASE WHEN object_type = \'host\' AND is_allowed_to_flap = 0 THEN 1 ELSE 0 END)',
+ 'hosts_flapping' => 'SUM(CASE WHEN object_type = \'host\' AND is_flapping = 1 THEN 1 ELSE 0 END)'
+ ),
+ 'servicestatussummary' => array(
+ 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)',
+ 'services_problem' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 THEN 1 ELSE 0 END)',
+ 'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND handled + host_problem > 0 THEN 1 ELSE 0 END)',
+ 'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND handled + host_problem = 0 THEN 1 ELSE 0 END)',
+ 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)',
+ 'services_ok_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)',
+ 'services_pending_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_problem > 0 THEN 1 ELSE 0 END)',
+ 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND handled + host_problem = 0 THEN 1 ELSE 0 END)',
+ 'services_warning_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)',
+ 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_problem > 0 THEN 1 ELSE 0 END)',
+ 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND handled + host_problem = 0 THEN 1 ELSE 0 END)',
+ 'services_critical_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_critical_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_problem > 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND handled + host_problem = 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_unknown_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_active' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_passive' => 'SUM(CASE WHEN object_type = \'service\' AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_not_processing_event_handlers' => 'SUM(CASE WHEN object_type = \'service\' AND is_processing_events = 0 THEN 1 ELSE 0 END)',
+ 'services_not_triggering_notifications' => 'SUM(CASE WHEN object_type = \'service\' AND is_triggering_notifications = 0 THEN 1 ELSE 0 END)',
+ 'services_without_flap_detection' => 'SUM(CASE WHEN object_type = \'service\' AND is_allowed_to_flap = 0 THEN 1 ELSE 0 END)',
+ 'services_flapping' => 'SUM(CASE WHEN object_type = \'service\' AND is_flapping = 1 THEN 1 ELSE 0 END)',
+
+/*
+NOTE: in case you might wonder, please see #7303. As a quickfix I did:
+
+:%s/(host_state = 0 OR host_state = 99)/host_state != 1 AND host_state != 2/g
+:%s/(host_state = 1 OR host_state = 2)/host_state != 0 AND host_state != 99/g
+
+We have to find a better solution here.
+
+*/
+ 'services_ok_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 0 THEN 1 ELSE 0 END)',
+ 'services_ok_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_pending_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 99 THEN 1 ELSE 0 END)',
+ 'services_pending_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_warning_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'services_warning_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'services_warning_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_critical_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'services_critical_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'services_critical_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_critical_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_unhandled_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_passive_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_unknown_not_checked_on_ok_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 1 AND host_state != 2 AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_ok_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 0 THEN 1 ELSE 0 END)',
+ 'services_ok_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_pending_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 99 THEN 1 ELSE 0 END)',
+ 'services_pending_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_warning_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'services_warning_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'services_warning_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_warning_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_critical_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'services_critical_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'services_critical_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_critical_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_handled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND handled > 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_unhandled_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND handled = 0 THEN 1 ELSE 0 END)',
+ 'services_unknown_passive_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)',
+ 'services_unknown_not_checked_on_problem_hosts' => 'SUM(CASE WHEN object_type = \'service\' AND host_state != 0 AND host_state != 99 AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)'
+ )
+ );
+
+ /**
+ * The union
+ *
+ * @var Zend_Db_Select
+ */
+ protected $summaryQuery;
+
+ /**
+ * Subqueries used for the summary query
+ *
+ * @var IdoQuery[]
+ */
+ protected $subQueries = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function allowsCustomVars()
+ {
+ foreach ($this->subQueries as $query) {
+ if (! $query->allowsCustomVars()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ foreach ($this->subQueries as $sub) {
+ $sub->applyFilter(clone $filter);
+ }
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function joinBaseTables()
+ {
+ // TODO(el): Allow to switch between hard and soft states
+ $hosts = $this->createSubQuery(
+ 'Hoststatus',
+ array(
+ 'handled' => 'host_handled',
+ 'host_problem',
+ 'host_state' => new Zend_Db_Expr('NULL'),
+ 'is_active_checked' => 'host_active_checks_enabled',
+ 'is_allowed_to_flap' => 'host_flap_detection_enabled',
+ 'is_flapping' => 'host_is_flapping',
+ 'is_passive_checked' => 'host_is_passive_checked',
+ 'is_processing_events' => 'host_event_handler_enabled',
+ 'is_triggering_notifications' => 'host_notifications_enabled',
+ 'object_type',
+ 'severity' => 'host_severity',
+ 'state_change' => 'host_last_state_change',
+ 'state' => 'host_state'
+ )
+ );
+ $this->subQueries[] = $hosts;
+ $services = $this->createSubQuery(
+ 'Servicestatus',
+ array(
+ 'handled' => 'service_handled',
+ 'host_problem',
+ 'host_state' => 'host_hard_state',
+ 'is_active_checked' => 'service_active_checks_enabled',
+ 'is_allowed_to_flap' => 'service_flap_detection_enabled',
+ 'is_flapping' => 'service_is_flapping',
+ 'is_passive_checked' => 'service_is_passive_checked',
+ 'is_processing_events' => 'service_event_handler_enabled',
+ 'is_triggering_notifications' => 'service_notifications_enabled',
+ 'object_type',
+ 'severity' => 'service_severity',
+ 'state_change' => 'service_last_state_change',
+ 'state' => 'service_state'
+ )
+ );
+ $this->subQueries[] = $services;
+ $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL);
+ $this->select->from(array('statussummary' => $this->summaryQuery), array());
+ $this->joinedVirtualTables['hoststatussummary'] = true;
+ $this->joinedVirtualTables['servicestatussummary'] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function order($columnOrAlias, $dir = null)
+ {
+ if (! $this->hasAliasName($columnOrAlias)) {
+ foreach ($this->subQueries as $sub) {
+ $sub->requireColumn($columnOrAlias);
+ }
+ }
+ return parent::order($columnOrAlias, $dir);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ $this->requireColumn($condition);
+ foreach ($this->subQueries as $sub) {
+ $sub->where($condition, $value);
+ }
+ return $this;
+ }
+
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->requireColumn($ex->getColumn());
+ foreach ($this->subQueries as $sub) {
+ $sub->whereEx($ex);
+ }
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php
new file mode 100644
index 0000000..f4c4e07
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledhostproblemsQuery.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for unhandled host problems
+ */
+class UnhandledhostproblemsQuery extends IdoQuery
+{
+ protected $allowCustomVars = true;
+
+ protected $columnMap = array(
+ 'problems' => array(
+ 'hosts_down_unhandled' => 'COUNT(*)',
+ )
+ );
+
+ /**
+ * The service status sub select
+ *
+ * @var HoststatusQuery
+ */
+ protected $subSelect;
+
+ public function addFilter(Filter $filter)
+ {
+ $this->subSelect->applyFilter(clone $filter);
+ return $this;
+ }
+
+ protected function joinBaseTables()
+ {
+ $this->subSelect = $this->createSubQuery(
+ 'Hoststatus',
+ array('host_name')
+ );
+ $this->subSelect->where('host_handled', 0);
+ $this->subSelect->where('host_state', 1);
+ $this->select->from(
+ array('problems' => $this->subSelect->setIsSubQuery(true)),
+ array()
+ );
+ $this->joinedVirtualTables['problems'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php
new file mode 100644
index 0000000..a218caf
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/UnhandledserviceproblemsQuery.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend\Ido\Query;
+
+use Icinga\Data\Filter\Filter;
+
+/**
+ * Query for unhandled service problems
+ */
+class UnhandledserviceproblemsQuery extends IdoQuery
+{
+ protected $allowCustomVars = true;
+
+ protected $columnMap = array(
+ 'problems' => array(
+ 'services_critical_unhandled' => 'COUNT(*)',
+ )
+ );
+
+ /**
+ * The service status sub select
+ *
+ * @var ServicestatusQuery
+ */
+ protected $subSelect;
+
+ public function addFilter(Filter $filter)
+ {
+ $this->subSelect->applyFilter(clone $filter);
+ return $this;
+ }
+
+ protected function joinBaseTables()
+ {
+ $this->subSelect = $this->createSubQuery(
+ 'Servicestatus',
+ array('service_description')
+ );
+ $this->subSelect->where('service_handled', 0);
+ $this->subSelect->where('service_state', 2);
+ $this->select->from(
+ array('problems' => $this->subSelect->setIsSubQuery(true)),
+ array()
+ );
+ $this->joinedVirtualTables['problems'] = true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php b/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php
new file mode 100644
index 0000000..5400957
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Backend/MonitoringBackend.php
@@ -0,0 +1,348 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Backend;
+
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Data\ConnectionInterface;
+use Icinga\Data\Queryable;
+use Icinga\Data\Selectable;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\ProgrammingError;
+
+class MonitoringBackend implements Selectable, Queryable, ConnectionInterface
+{
+ /**
+ * Backend configuration
+ *
+ * @var ConfigObject
+ */
+ protected $config;
+
+ /**
+ * Resource
+ *
+ * @var mixed
+ */
+ protected $resource;
+
+ /**
+ * Type
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * The configured name of this backend
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * Already created instances
+ *
+ * @var array
+ */
+ protected static $instances = array();
+
+ /**
+ * Create a new backend
+ *
+ * @param string $name
+ * @param ConfigObject $config
+ */
+ protected function __construct($name, ConfigObject $config)
+ {
+ $this->name = $name;
+ $this->config = $config;
+ }
+
+ /**
+ * Get a backend instance
+ *
+ * You may ask for a specific backend name or get the default one otherwise
+ *
+ * @param string $name Backend name
+ *
+ * @return MonitoringBackend
+ */
+ public static function instance($name = null)
+ {
+ if (! array_key_exists($name, self::$instances)) {
+ list($foundName, $config) = static::loadConfig($name);
+ $type = $config->get('type');
+ $class = implode(
+ '\\',
+ array(
+ __NAMESPACE__,
+ ucfirst($type),
+ ucfirst($type) . 'Backend'
+ )
+ );
+
+ if (!class_exists($class)) {
+ throw new ConfigurationError(
+ mt('monitoring', 'There is no "%s" monitoring backend'),
+ $class
+ );
+ }
+
+ self::$instances[$name] = new $class($foundName, $config);
+ if ($name === null) {
+ self::$instances[$foundName] = self::$instances[$name];
+ }
+ }
+
+ return self::$instances[$name];
+ }
+
+ /**
+ * Clear all cached instances. Mostly for testing purposes.
+ */
+ public static function clearInstances()
+ {
+ self::$instances = array();
+ }
+
+ /**
+ * Whether this backend is of a specific type
+ *
+ * @param string $type Backend type
+ *
+ * @return boolean
+ */
+ public function is($type)
+ {
+ return $this->getType() === $type;
+ }
+
+ /**
+ * Get the configured name of this backend
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get the backend type name
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ if ($this->type === null) {
+ $parts = preg_split('~\\\~', get_class($this));
+ $class = array_pop($parts);
+ if (substr($class, -7) === 'Backend') {
+ $this->type = lcfirst(substr($class, 0, -7));
+ } else {
+ throw new ProgrammingError(
+ '%s is not a valid monitoring backend class name',
+ $class
+ );
+ }
+ }
+ return $this->type;
+ }
+
+ /**
+ * Return the configuration for the first enabled or the given backend
+ */
+ protected static function loadConfig($name = null)
+ {
+ $backends = Config::module('monitoring', 'backends');
+
+ if ($name === null) {
+ $count = 0;
+
+ foreach ($backends as $name => $config) {
+ $count++;
+ if ((bool) $config->get('disabled', false) === false) {
+ return array($name, $config);
+ }
+ }
+
+ if ($count === 0) {
+ $message = mt('monitoring', 'No backend has been configured');
+ } else {
+ $message = mt('monitoring', 'All backends are disabled');
+ }
+
+ throw new ConfigurationError($message);
+ } else {
+ $config = $backends->getSection($name);
+
+ if ($config->isEmpty()) {
+ throw new ConfigurationError(
+ mt('monitoring', 'No configuration for backend %s'),
+ $name
+ );
+ }
+
+ if ((bool) $config->get('disabled', false) === true) {
+ throw new ConfigurationError(
+ mt('monitoring', 'Configuration for backend %s is disabled'),
+ $name
+ );
+ }
+
+ return array($name, $config);
+ }
+ }
+
+ /**
+ * Get this backend's internal resource
+ *
+ * @return mixed
+ */
+ public function getResource()
+ {
+ if ($this->resource === null) {
+ $config = ResourceFactory::getResourceConfig($this->config->get('resource'));
+ if ($this->is('ido') && $config->type === 'db' && $config->db === 'mysql' && $config->charset === null) {
+ $config->charset = 'latin1';
+ }
+ $this->resource = ResourceFactory::createResource($config);
+ if ($this->is('ido') && $this->resource->getDbType() !== 'oracle') {
+ // TODO(el): The resource should set the table prefix
+ $this->resource->setTablePrefix('icinga_');
+ }
+ }
+ return $this->resource;
+ }
+
+ /**
+ * Backend entry point
+ *
+ * @return $this
+ */
+ public function select()
+ {
+ return $this;
+ }
+
+ /**
+ * Create a data view to fetch data from
+ *
+ * @param string $name
+ * @param array $columns
+ *
+ * @return \Icinga\Module\Monitoring\DataView\DataView
+ */
+ public function from($name, array $columns = null)
+ {
+ $class = $this->buildViewClassName($name);
+ return new $class($this, $columns);
+ }
+
+ /**
+ * View name to class name resolution
+ *
+ * @param string $view
+ *
+ * @return string
+ *
+ * @throws ProgrammingError In case the view does not exist
+ */
+ protected function buildViewClassName($view)
+ {
+ $class = ucfirst(strtolower($view));
+ $classPath = '\\Icinga\\Module\\Monitoring\\DataView\\' . $class;
+ if (! class_exists($classPath)) {
+ throw new ProgrammingError('DataView %s does not exist', $class);
+ }
+
+ return $classPath;
+ }
+
+ /**
+ * Get a specific query class instance
+ *
+ * @param string $name Query name
+ * @param array $columns Optional column list
+ *
+ * @return Icinga\Data\QueryInterface
+ *
+ * @throws ProgrammingError When the query does not exist for this backend
+ */
+ public function query($name, $columns = null)
+ {
+ $class = $this->buildQueryClassName($name);
+
+ if (!class_exists($class)) {
+ throw new ProgrammingError(
+ 'Query "%s" does not exist for backend %s',
+ $name,
+ $this->getType()
+ );
+ }
+
+ return new $class($this->getResource(), $columns);
+ }
+
+ /**
+ * Whether this backend supports the given query
+ *
+ * @param string $name Query name to check for
+ *
+ * @return bool
+ */
+ public function hasQuery($name)
+ {
+ return class_exists($this->buildQueryClassName($name));
+ }
+
+ /**
+ * Query name to class name resolution
+ *
+ * @param string $query
+ *
+ * @return string
+ */
+ protected function buildQueryClassName($query)
+ {
+ $parts = preg_split('~\\\~', get_class($this));
+ array_pop($parts);
+ array_push($parts, 'Query', ucfirst(strtolower($query)) . 'Query');
+ return implode('\\', $parts);
+ }
+
+ /**
+ * Fetch and return the program version of the current instance
+ *
+ * @return string
+ */
+ public function getProgramVersion()
+ {
+ return preg_replace(
+ '/^[vr]/',
+ '',
+ $this->select()->from('programstatus', array('program_version'))->fetchOne()
+ );
+ }
+
+ /**
+ * Get whether the backend is Icinga 2
+ *
+ * @param string $programVersion
+ *
+ * @return bool
+ */
+ public function isIcinga2($programVersion = null)
+ {
+ if ($programVersion === null) {
+ $programVersion = $this->select()->from('programstatus', array('program_version'))->fetchOne();
+ }
+ return (bool) preg_match(
+ '/^[vr]?2\.\d+\.\d+.*$/',
+ $programVersion
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/BackendStep.php b/modules/monitoring/library/Monitoring/BackendStep.php
new file mode 100644
index 0000000..e94625f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/BackendStep.php
@@ -0,0 +1,206 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring;
+
+use Exception;
+use Icinga\Module\Setup\Step;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+
+class BackendStep extends Step
+{
+ protected $data;
+
+ protected $backendIniError;
+
+ protected $resourcesIniError;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $success = $this->createBackendsIni();
+ $success &= $this->createResourcesIni();
+ return $success;
+ }
+
+ protected function createBackendsIni()
+ {
+ $config = array();
+ $config[$this->data['backendConfig']['name']] = array(
+ 'type' => $this->data['backendConfig']['type'],
+ 'resource' => $this->data['resourceConfig']['name']
+ );
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('modules/monitoring/backends.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->backendIniError = $e;
+ return false;
+ }
+
+ $this->backendIniError = false;
+ return true;
+ }
+
+ protected function createResourcesIni()
+ {
+ $resourceConfig = $this->data['resourceConfig'];
+ $resourceName = $resourceConfig['name'];
+ unset($resourceConfig['name']);
+
+ try {
+ $config = Config::app('resources', true);
+ $config->setSection($resourceName, $resourceConfig);
+ $config->saveIni();
+ } catch (Exception $e) {
+ $this->resourcesIniError = $e;
+ return false;
+ }
+
+ $this->resourcesIniError = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ $pageTitle = '<h2>' . mt('monitoring', 'Monitoring Backend', 'setup.page.title') . '</h2>';
+ $backendDescription = '<p>' . sprintf(
+ mt(
+ 'monitoring',
+ 'Icinga Web 2 will retrieve information from your monitoring environment'
+ . ' using a backend called "%s" and the specified resource below:'
+ ),
+ $this->data['backendConfig']['name']
+ ) . '</p>';
+
+ if ($this->data['resourceConfig']['type'] === 'db') {
+ $resourceTitle = '<h3>' . mt('monitoring', 'Database Resource') . '</h3>';
+ $resourceHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Resource Name') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Database Type') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['db'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Host') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['host'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Port') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Database Name') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['dbname'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Username') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['username'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Password') . '</strong></td>'
+ . '<td>' . str_repeat('*', strlen($this->data['resourceConfig']['password'])) . '</td>'
+ . '</tr>';
+
+ if (defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && isset($this->data['resourceConfig']['ssl_do_not_verify_server_cert'])
+ && $this->data['resourceConfig']['ssl_do_not_verify_server_cert']
+ ) {
+ $resourceHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('SSL Do Not Verify Server Certificate') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_do_not_verify_server_cert'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['resourceConfig']['ssl_key']) && $this->data['resourceConfig']['ssl_key']) {
+ $resourceHtml .= ''
+ .'<tr>'
+ . '<td><strong>' . t('SSL Key') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_key'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['resourceConfig']['ssl_cert']) && $this->data['resourceConfig']['ssl_cert']) {
+ $resourceHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('SSL Cert') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_cert'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['resourceConfig']['ssl_ca']) && $this->data['resourceConfig']['ssl_ca']) {
+ $resourceHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('CA') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_ca'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['resourceConfig']['ssl_capath']) && $this->data['resourceConfig']['ssl_capath']) {
+ $resourceHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('CA Path') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_capath'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['resourceConfig']['ssl_cipher']) && $this->data['resourceConfig']['ssl_cipher']) {
+ $resourceHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('Cipher') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_cipher'] . '</td>'
+ . '</tr>';
+ }
+
+ $resourceHtml .= ''
+ . '</tbody>'
+ . '</table>';
+ }
+
+ return $pageTitle . '<div class="topic">' . $backendDescription . $resourceTitle . $resourceHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ $report = array();
+
+ if ($this->backendIniError === false) {
+ $report[] = sprintf(
+ mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s'),
+ Config::resolvePath('modules/monitoring/backends.ini')
+ );
+ } elseif ($this->backendIniError !== null) {
+ $report[] = sprintf(
+ mt(
+ 'monitoring',
+ 'Monitoring backend configuration could not be written to: %s. An error occured:'
+ ),
+ Config::resolvePath('modules/monitoring/backends.ini')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->backendIniError));
+ }
+
+ if ($this->resourcesIniError === false) {
+ $report[] = sprintf(
+ mt('monitoring', 'Resource configuration has been successfully updated: %s'),
+ Config::resolvePath('resources.ini')
+ );
+ } elseif ($this->resourcesIniError !== null) {
+ $report[] = sprintf(
+ mt('monitoring', 'Resource configuration could not be udpated: %s. An error occured:'),
+ Config::resolvePath('resources.ini')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->resourcesIniError));
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Cli/CliUtils.php b/modules/monitoring/library/Monitoring/Cli/CliUtils.php
new file mode 100644
index 0000000..3d7d3ee
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Cli/CliUtils.php
@@ -0,0 +1,122 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Cli;
+
+use Icinga\Cli\Screen;
+
+class CliUtils
+{
+ protected $hostColors = array(
+ 0 => array('black', 'lightgreen'),
+ 1 => array('black', 'lightred'),
+ 2 => array('black', 'brown'),
+ 99 => array('black', 'lightgray'),
+ );
+ protected $serviceColors = array(
+ 0 => array('black', 'lightgreen'),
+ 1 => array('black', 'yellow'),
+ 2 => array('black', 'lightred'),
+ 3 => array('black', 'lightpurple'),
+ 99 => array('black', 'lightgray'),
+ );
+ protected $hostStates = array(
+ 0 => 'UP',
+ 1 => 'DOWN',
+ 2 => 'UNREACHABLE',
+ 99 => 'PENDING',
+ );
+
+ protected $serviceStates = array(
+ 0 => 'OK',
+ 1 => 'WARNING',
+ 2 => 'CRITICAL',
+ 3 => 'UNKNOWN',
+ 99 => 'PENDING',
+ );
+
+ protected $screen;
+ protected $hostState;
+ protected $serviceState;
+
+ public function __construct(Screen $screen)
+ {
+ $this->screen = $screen;
+ }
+
+ public function setHostState($state)
+ {
+ $this->hostState = $state;
+ }
+
+ public function setServiceState($state)
+ {
+ $this->serviceState = $state;
+ }
+
+ public function shortHostState($state = null)
+ {
+ if ($state === null) {
+ $state = $this->hostState;
+ }
+ return sprintf('%-4s', substr($this->hostStates[$state], 0, 4));
+ }
+
+ public function shortServiceState($state = null)
+ {
+ if ($state === null) {
+ $state = $this->serviceState;
+ }
+ return sprintf('%-4s', substr($this->serviceStates[$state], 0, 4));
+ }
+
+ public function hostStateBackground($text, $state = null)
+ {
+ if ($state === null) {
+ $state = $this->hostState;
+ }
+ return $this->screen->colorize(
+ $text,
+ $this->hostColors[$state][0],
+ $this->hostColors[$state][1]
+ );
+ }
+
+ public function serviceStateBackground($text, $state = null)
+ {
+ if ($state === null) {
+ $state = $this->serviceState;
+ }
+ return $this->screen->colorize(
+ $text,
+ $this->serviceColors[$state][0],
+ $this->serviceColors[$state][1]
+ );
+ }
+
+ public function objectStateFlags($type, &$row)
+ {
+ $extra = array();
+ if ($row->{$type . '_in_downtime'}) {
+ if ($this->screen->hasUtf8()) {
+ $extra[] = 'DOWNTIME ⌚';
+ } else {
+ $extra[] = 'DOWNTIME';
+ }
+ }
+ if ($row->{$type . '_acknowledged'}) {
+ if ($this->screen->hasUtf8()) {
+ $extra[] = 'ACK ✓';
+ } else {
+ $extra[] = 'ACK';
+ }
+ }
+
+ if (empty($extra)) {
+ $extra = '';
+ } else {
+ $extra = sprintf(' [ %s ]', implode(', ', $extra));
+ }
+ return $extra;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php b/modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php
new file mode 100644
index 0000000..c33157f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/IcingaApiCommand.php
@@ -0,0 +1,126 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command;
+
+class IcingaApiCommand
+{
+ /**
+ * Command data
+ *
+ * @var array
+ */
+ protected $data;
+
+ /**
+ * Name of the endpoint
+ *
+ * @var string
+ */
+ protected $endpoint;
+
+ /**
+ * Next Icinga API command to be sent, if any
+ *
+ * @var static
+ */
+ protected $next;
+
+ /**
+ * Create a new Icinga 2 API command
+ *
+ * @param string $endpoint
+ * @param array $data
+ *
+ * @return static
+ */
+ public static function create($endpoint, array $data)
+ {
+ $command = new static();
+ $command
+ ->setEndpoint($endpoint)
+ ->setData($data);
+ return $command;
+ }
+
+ /**
+ * Get the command data
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Set the command data
+ *
+ * @param array $data
+ *
+ * @return $this
+ */
+ public function setData($data)
+ {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ /**
+ * Get the name of the endpoint
+ *
+ * @return string
+ */
+ public function getEndpoint()
+ {
+ return $this->endpoint;
+ }
+
+ /**
+ * Set the name of the endpoint
+ *
+ * @param string $endpoint
+ *
+ * @return $this
+ */
+ public function setEndpoint($endpoint)
+ {
+ $this->endpoint = $endpoint;
+
+ return $this;
+ }
+
+ /**
+ * Get whether another Icinga API command should be sent after this one
+ *
+ * @return bool
+ */
+ public function hasNext()
+ {
+ return $this->next !== null;
+ }
+
+ /**
+ * Get the next Icinga API command
+ *
+ * @return IcingaApiCommand
+ */
+ public function getNext()
+ {
+ return $this->next;
+ }
+
+ /**
+ * Set the next Icinga API command
+ *
+ * @param IcingaApiCommand $next
+ *
+ * @return IcingaApiCommand
+ */
+ public function setNext(IcingaApiCommand $next)
+ {
+ $this->next = $next;
+ return $next;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/IcingaCommand.php b/modules/monitoring/library/Monitoring/Command/IcingaCommand.php
new file mode 100644
index 0000000..49ce586
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/IcingaCommand.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command;
+
+/**
+ * Base class for commands sent to an Icinga instance
+ */
+abstract class IcingaCommand
+{
+ /**
+ * Get the name of the command
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ $nsParts = explode('\\', get_called_class());
+ return substr_replace(end($nsParts), '', -7); // Remove 'Command' Suffix
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php b/modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php
new file mode 100644
index 0000000..1d3ce9d
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Instance/DisableNotificationsExpireCommand.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Instance;
+
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+
+/**
+ * Disable host and service notifications w/ expire time on an Icinga instance
+ */
+class DisableNotificationsExpireCommand extends IcingaCommand
+{
+ /**
+ * The time when notifications should be re-enabled after disabling
+ *
+ * @var int|null Unix timestamp
+ */
+ protected $expireTime;
+
+ /**
+ * Set time when notifications should be re-enabled after disabling
+ *
+ * @param $expireTime int Unix timestamp
+ *
+ * @return $this
+ */
+ public function setExpireTime($expireTime)
+ {
+ $this->expireTime = (int) $expireTime;
+ return $this;
+ }
+
+ /**
+ * Get the date and time when notifications should be re-enabled after disabling
+ *
+ * @return int|null Unix timestamp
+ */
+ public function getExpireTime()
+ {
+ return $this->expireTime;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php b/modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php
new file mode 100644
index 0000000..8a8a8ca
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Instance/ToggleInstanceFeatureCommand.php
@@ -0,0 +1,122 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Instance;
+
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+
+/**
+ * Enable or disable a feature of an Icinga instance
+ */
+class ToggleInstanceFeatureCommand extends IcingaCommand
+{
+ /**
+ * Feature for enabling or disabling active host checks on an Icinga instance
+ */
+ const FEATURE_ACTIVE_HOST_CHECKS = 'active_host_checks_enabled';
+
+ /**
+ * Feature for enabling or disabling active service checks on an Icinga instance
+ */
+ const FEATURE_ACTIVE_SERVICE_CHECKS = 'active_service_checks_enabled';
+
+ /**
+ * Feature for enabling or disabling host and service event handlers on an Icinga instance
+ */
+ const FEATURE_EVENT_HANDLERS = 'event_handlers_enabled';
+
+ /**
+ * Feature for enabling or disabling host and service flap detection on an Icinga instance
+ */
+ const FEATURE_FLAP_DETECTION = 'flap_detection_enabled';
+
+ /**
+ * Feature for enabling or disabling host and service notifications on an Icinga instance
+ */
+ const FEATURE_NOTIFICATIONS = 'notifications_enabled';
+
+ /**
+ * Feature for enabling or disabling processing of host checks via the OCHP command on an Icinga instance
+ */
+ const FEATURE_HOST_OBSESSING = 'obsess_over_hosts';
+
+ /**
+ * Feature for enabling or disabling processing of service checks via the OCHP command on an Icinga instance
+ */
+ const FEATURE_SERVICE_OBSESSING = 'obsess_over_services';
+
+ /**
+ * Feature for enabling or disabling passive host checks on an Icinga instance
+ */
+ const FEATURE_PASSIVE_HOST_CHECKS = 'passive_host_checks_enabled';
+
+ /**
+ * Feature for enabling or disabling passive service checks on an Icinga instance
+ */
+ const FEATURE_PASSIVE_SERVICE_CHECKS = 'passive_service_checks_enabled';
+
+ /**
+ * Feature for enabling or disabling the processing of host and service performance data on an Icinga instance
+ */
+ const FEATURE_PERFORMANCE_DATA = 'process_performance_data';
+
+ /**
+ * Feature that is to be enabled or disabled
+ *
+ * @var string
+ */
+ protected $feature;
+
+ /**
+ * Whether the feature should be enabled or disabled
+ *
+ * @var bool
+ */
+ protected $enabled;
+
+ /**
+ * Set the feature that is to be enabled or disabled
+ *
+ * @param string $feature
+ *
+ * @return $this
+ */
+ public function setFeature($feature)
+ {
+ $this->feature = (string) $feature;
+ return $this;
+ }
+
+ /**
+ * Get the feature that is to be enabled or disabled
+ *
+ * @return string
+ */
+ public function getFeature()
+ {
+ return $this->feature;
+ }
+
+ /**
+ * Set whether the feature should be enabled or disabled
+ *
+ * @param bool $enabled
+ *
+ * @return $this
+ */
+ public function setEnabled($enabled = true)
+ {
+ $this->enabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Get whether the feature should be enabled or disabled
+ *
+ * @return bool
+ */
+ public function getEnabled()
+ {
+ return $this->enabled;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php b/modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php
new file mode 100644
index 0000000..2001e78
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/AcknowledgeProblemCommand.php
@@ -0,0 +1,144 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Acknowledge a host or service problem
+ */
+class AcknowledgeProblemCommand extends WithCommentCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST,
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Whether the acknowledgement is sticky
+ *
+ * Sticky acknowledgements remain until the host or service recovers. Non-sticky acknowledgements will be
+ * automatically removed when the host or service state changes.
+ *
+ * @var bool
+ */
+ protected $sticky = false;
+
+ /**
+ * Whether to send a notification about the acknowledgement
+
+ * @var bool
+ */
+ protected $notify = false;
+
+ /**
+ * Whether the comment associated with the acknowledgement is persistent
+ *
+ * Persistent comments are not lost the next time the monitoring host restarts.
+ *
+ * @var bool
+ */
+ protected $persistent = false;
+
+ /**
+ * Optional time when the acknowledgement should expire
+ *
+ * @var int|null
+ */
+ protected $expireTime;
+
+ /**
+ * Set whether the acknowledgement is sticky
+ *
+ * @param bool $sticky
+ *
+ * @return $this
+ */
+ public function setSticky($sticky = true)
+ {
+ $this->sticky = (bool) $sticky;
+ return $this;
+ }
+
+ /**
+ * Is the acknowledgement sticky?
+ *
+ * @return bool
+ */
+ public function getSticky()
+ {
+ return $this->sticky;
+ }
+
+ /**
+ * Set whether to send a notification about the acknowledgement
+ *
+ * @param bool $notify
+ *
+ * @return $this
+ */
+ public function setNotify($notify = true)
+ {
+ $this->notify = (bool) $notify;
+ return $this;
+ }
+
+ /**
+ * Get whether to send a notification about the acknowledgement
+ *
+ * @return bool
+ */
+ public function getNotify()
+ {
+ return $this->notify;
+ }
+
+ /**
+ * Set whether the comment associated with the acknowledgement is persistent
+ *
+ * @param bool $persistent
+ *
+ * @return $this
+ */
+ public function setPersistent($persistent = true)
+ {
+ $this->persistent = (bool) $persistent;
+ return $this;
+ }
+
+ /**
+ * Is the comment associated with the acknowledgement is persistent?
+ *
+ * @return bool
+ */
+ public function getPersistent()
+ {
+ return $this->persistent;
+ }
+
+ /**
+ * Set the time when the acknowledgement should expire
+ *
+ * @param int $expireTime
+ *
+ * @return $this
+ */
+ public function setExpireTime($expireTime)
+ {
+ $this->expireTime = (int) $expireTime;
+ return $this;
+ }
+
+ /**
+ * Get the time when the acknowledgement should expire
+ *
+ * @return int|null
+ */
+ public function getExpireTime()
+ {
+ return $this->expireTime;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php
new file mode 100644
index 0000000..9e3151f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/AddCommentCommand.php
@@ -0,0 +1,80 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Add a comment to a host or service
+ */
+class AddCommentCommand extends WithCommentCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST,
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Whether the comment is persistent
+ *
+ * Persistent comments are not lost the next time the monitoring host restarts.
+ */
+ protected $persistent;
+
+ /**
+ * Optional time when the acknowledgement should expire
+ *
+ * @var int|null
+ */
+ protected $expireTime;
+
+ /**
+ * Set whether the comment is persistent
+ *
+ * @param bool $persistent
+ *
+ * @return $this
+ */
+ public function setPersistent($persistent = true)
+ {
+ $this->persistent = $persistent;
+ return $this;
+ }
+
+ /**
+ * Is the comment persistent?
+ *
+ * @return bool
+ */
+ public function getPersistent()
+ {
+ return $this->persistent;
+ }
+
+ /**
+ * Set the time when the acknowledgement should expire
+ *
+ * @param int $expireTime
+ *
+ * @return $this
+ */
+ public function setExpireTime($expireTime)
+ {
+ $this->expireTime = (int) $expireTime;
+
+ return $this;
+ }
+
+ /**
+ * Get the time when the acknowledgement should expire
+ *
+ * @return int|null
+ */
+ public function getExpireTime()
+ {
+ return $this->expireTime;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php
new file mode 100644
index 0000000..6495375
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ApiScheduleHostDowntimeCommand.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Schedule host downtime command for API command transport and Icinga >= 2.11.0 that
+ * sends all_services and child_options in a single request
+ */
+class ApiScheduleHostDowntimeCommand extends ScheduleHostDowntimeCommand
+{
+ /** @var int Whether no, triggered, or non-triggered child downtimes should be scheduled */
+ protected $childOptions;
+
+ protected $forAllServicesNative = true;
+
+ /**
+ * Get child options, i.e. whether no, triggered, or non-triggered child downtimes should be scheduled
+ *
+ * @return int
+ */
+ public function getChildOptions()
+ {
+ return $this->childOptions;
+ }
+
+ /**
+ * Set child options, i.e. whether no, triggered, or non-triggered child downtimes should be scheduled
+ *
+ * @param int $childOptions
+ *
+ * @return $this
+ */
+ public function setChildOptions($childOptions)
+ {
+ $this->childOptions = $childOptions;
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php b/modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php
new file mode 100644
index 0000000..577e3df
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/CommandAuthor.php
@@ -0,0 +1,38 @@
+<?php
+
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+trait CommandAuthor
+{
+ /**
+ * Author of the command
+ *
+ * @var string
+ */
+ protected $author;
+
+ /**
+ * Set the author
+ *
+ * @param string $author
+ *
+ * @return $this
+ */
+ public function setAuthor($author)
+ {
+ $this->author = (string) $author;
+ return $this;
+ }
+
+ /**
+ * Get the author
+ *
+ * @return string
+ */
+ public function getAuthor()
+ {
+ return $this->author;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php
new file mode 100644
index 0000000..348175a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteCommentCommand.php
@@ -0,0 +1,110 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+
+/**
+ * Delete a host or service comment
+ */
+class DeleteCommentCommand extends IcingaCommand
+{
+ use CommandAuthor;
+
+ /**
+ * ID of the comment that is to be deleted
+ *
+ * @var int
+ */
+ protected $commentId;
+
+ /**
+ * Name of the comment (Icinga 2.4+)
+ *
+ * Required for removing the comment via Icinga 2's API.
+ *
+ * @var string
+ */
+ protected $commentName;
+
+ /**
+ * Whether the command affects a service comment
+ *
+ * @var boolean
+ */
+ protected $isService = false;
+
+ /**
+ * Get the ID of the comment that is to be deleted
+ *
+ * @return int
+ */
+ public function getCommentId()
+ {
+ return $this->commentId;
+ }
+
+ /**
+ * Set the ID of the comment that is to be deleted
+ *
+ * @param int $commentId
+ *
+ * @return $this
+ */
+ public function setCommentId($commentId)
+ {
+ $this->commentId = (int) $commentId;
+ return $this;
+ }
+
+ /**
+ * Get the name of the comment (Icinga 2.4+)
+ *
+ * Required for removing the comment via Icinga 2's API.
+ *
+ * @return string
+ */
+ public function getCommentName()
+ {
+ return $this->commentName;
+ }
+
+ /**
+ * Set the name of the comment (Icinga 2.4+)
+ *
+ * Required for removing the comment via Icinga 2's API.
+ *
+ * @param string $commentName
+ *
+ * @return $this
+ */
+ public function setCommentName($commentName)
+ {
+ $this->commentName = $commentName;
+ return $this;
+ }
+
+ /**
+ * Get whether the command affects a service comment
+ *
+ * @return boolean
+ */
+ public function getIsService()
+ {
+ return $this->isService;
+ }
+
+ /**
+ * Set whether the command affects a service comment
+ *
+ * @param bool $isService
+ *
+ * @return $this
+ */
+ public function setIsService($isService = true)
+ {
+ $this->isService = (bool) $isService;
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php
new file mode 100644
index 0000000..a314864
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/DeleteDowntimeCommand.php
@@ -0,0 +1,110 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+
+/**
+ * Delete a host or service downtime
+ */
+class DeleteDowntimeCommand extends IcingaCommand
+{
+ use CommandAuthor;
+
+ /**
+ * ID of the downtime that is to be deleted
+ *
+ * @var int
+ */
+ protected $downtimeId;
+
+ /**
+ * Name of the downtime (Icinga 2.4+)
+ *
+ * Required for removing the downtime via Icinga 2's API.
+ *
+ * @var string
+ */
+ protected $downtimeName;
+
+ /**
+ * Whether the command affects a service downtime
+ *
+ * @var boolean
+ */
+ protected $isService = false;
+
+ /**
+ * Get the ID of the downtime that is to be deleted
+ *
+ * @return int
+ */
+ public function getDowntimeId()
+ {
+ return $this->downtimeId;
+ }
+
+ /**
+ * Set the ID of the downtime that is to be deleted
+ *
+ * @param int $downtimeId
+ *
+ * @return $this
+ */
+ public function setDowntimeId($downtimeId)
+ {
+ $this->downtimeId = (int) $downtimeId;
+ return $this;
+ }
+
+ /**
+ * Get the name of the downtime (Icinga 2.4+)
+ *
+ * Required for removing the downtime via Icinga 2's API.
+ *
+ * @return string
+ */
+ public function getDowntimeName()
+ {
+ return $this->downtimeName;
+ }
+
+ /**
+ * Set the name of the downtime (Icinga 2.4+)
+ *
+ * Required for removing the downtime via Icinga 2's API.
+ *
+ * @param string $downtimeName
+ *
+ * @return $this
+ */
+ public function setDowntimeName($downtimeName)
+ {
+ $this->downtimeName = $downtimeName;
+ return $this;
+ }
+
+ /**
+ * Get whether the command affects a service
+ *
+ * @return bool
+ */
+ public function getIsService()
+ {
+ return $this->isService;
+ }
+
+ /**
+ * Set whether the command affects a service
+ *
+ * @param bool $isService
+ *
+ * @return $this
+ */
+ public function setIsService($isService = true)
+ {
+ $this->isService = (bool) $isService;
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php
new file mode 100644
index 0000000..43ab645
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ObjectCommand.php
@@ -0,0 +1,61 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Base class for commands that involve a monitored object, i.e. a host or service
+ */
+abstract class ObjectCommand extends IcingaCommand
+{
+ /**
+ * Type host
+ */
+ const TYPE_HOST = MonitoredObject::TYPE_HOST;
+
+ /**
+ * Type service
+ */
+ const TYPE_SERVICE = MonitoredObject::TYPE_SERVICE;
+
+ /**
+ * Allowed Icinga object types for the command
+ *
+ * @var string[]
+ */
+ protected $allowedObjects = array();
+
+ /**
+ * Involved object
+ *
+ * @var MonitoredObject
+ */
+ protected $object;
+
+ /**
+ * Set the involved object
+ *
+ * @param MonitoredObject $object
+ *
+ * @return $this
+ */
+ public function setObject(MonitoredObject $object)
+ {
+ $object->assertOneOf($this->allowedObjects);
+ $this->object = $object;
+ return $this;
+ }
+
+ /**
+ * Get the involved object
+ *
+ * @return MonitoredObject
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php
new file mode 100644
index 0000000..cd2db33
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ProcessCheckResultCommand.php
@@ -0,0 +1,176 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+use InvalidArgumentException;
+use LogicException;
+
+/**
+ * Submit a passive check result for a host or service
+ */
+class ProcessCheckResultCommand extends ObjectCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST,
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Host up
+ */
+ const HOST_UP = 0;
+
+ /**
+ * Host down
+ */
+ const HOST_DOWN = 1;
+
+ /**
+ * Host unreachable
+ */
+ const HOST_UNREACHABLE = 2; // TODO: Icinga 2.x does not support submitting results with this state, yet
+
+ /**
+ * Service ok
+ */
+ const SERVICE_OK = 0;
+
+ /**
+ * Service warning
+ */
+ const SERVICE_WARNING = 1;
+
+ /**
+ * Service critical
+ */
+ const SERVICE_CRITICAL = 2;
+
+ /**
+ * Service unknown
+ */
+ const SERVICE_UNKNOWN = 3;
+
+ /**
+ * Possible status codes for passive host and service checks
+ *
+ * @var array
+ */
+ public static $statusCodes = array(
+ self::TYPE_HOST => array(
+ self::HOST_UP, self::HOST_DOWN, self::HOST_UNREACHABLE
+ ),
+ self::TYPE_SERVICE => array(
+ self::SERVICE_OK, self::SERVICE_WARNING, self::SERVICE_CRITICAL, self::SERVICE_UNKNOWN
+ )
+ );
+
+ /**
+ * Status code of the host or service check result
+ *
+ * @var int
+ */
+ protected $status;
+
+ /**
+ * Text output of the host or service check result
+ *
+ * @var string
+ */
+ protected $output;
+
+ /**
+ * Optional performance data of the host or service check result
+ *
+ * @var string
+ */
+ protected $performanceData;
+
+
+ /**
+ * Set the status code of the host or service check result
+ *
+ * @param int $status
+ *
+ * @return $this
+ *
+ * @throws LogicException If the object is null
+ * @throws InvalidArgumentException If status is not one of the valid status codes for the object's type
+ */
+ public function setStatus($status)
+ {
+ if ($this->object === null) {
+ throw new LogicException('You\'re required to call setObject() before calling setStatus()');
+ }
+ $status = (int) $status;
+ if (! in_array($status, self::$statusCodes[$this->object->getType()])) {
+ throw new InvalidArgumentException(sprintf(
+ 'The status code %u you provided is not one of the valid status codes for type %s',
+ $status,
+ $this->object->getType()
+ ));
+ }
+ $this->status = $status;
+ return $this;
+ }
+
+ /**
+ * Get the status code of the host or service check result
+ *
+ * @return int
+ */
+ public function getStatus()
+ {
+ return $this->status;
+ }
+
+ /**
+ * Set the text output of the host or service check result
+ *
+ * @param string $output
+ *
+ * @return $this
+ */
+ public function setOutput($output)
+ {
+ $this->output = (string) $output;
+ return $this;
+ }
+
+ /**
+ * Get the text output of the host or service check result
+ *
+ * @return string
+ */
+ public function getOutput()
+ {
+ return $this->output;
+ }
+
+ /**
+ * Set the performance data of the host or service check result
+ *
+ * @param string $performanceData
+ *
+ * @return $this
+ */
+ public function setPerformanceData($performanceData)
+ {
+ $this->performanceData = (string) $performanceData;
+ return $this;
+ }
+
+ /**
+ * Get the performance data of the host or service check result
+ *
+ * @return string
+ */
+ public function getPerformanceData()
+ {
+ return $this->performanceData;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php
new file mode 100644
index 0000000..3fd350c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/PropagateHostDowntimeCommand.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Schedule and propagate host downtime
+ */
+class PropagateHostDowntimeCommand extends ScheduleServiceDowntimeCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST
+ );
+
+ /**
+ * Whether the downtime for child hosts are all set to be triggered by this' host downtime
+ *
+ * @var bool
+ */
+ protected $triggered = false;
+
+ /**
+ * Set whether the downtime for child hosts are all set to be triggered by this' host downtime
+ *
+ * @param bool $triggered
+ *
+ * @return $this
+ */
+ public function setTriggered($triggered = true)
+ {
+ $this->triggered = (bool) $triggered;
+ return $this;
+ }
+
+ /**
+ * Get whether the downtime for child hosts are all set to be triggered by this' host downtime
+ *
+ * @return bool
+ */
+ public function getTriggered()
+ {
+ return $this->triggered;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php b/modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php
new file mode 100644
index 0000000..31c8180
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/RemoveAcknowledgementCommand.php
@@ -0,0 +1,21 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Remove a problem acknowledgement from a host or service
+ */
+class RemoveAcknowledgementCommand extends ObjectCommand
+{
+ use CommandAuthor;
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST,
+ self::TYPE_SERVICE
+ );
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php
new file mode 100644
index 0000000..8a0a2cb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostCheckCommand.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Schedule a host check
+ */
+class ScheduleHostCheckCommand extends ScheduleServiceCheckCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST
+ );
+
+ /**
+ * Whether to schedule a check of all services associated with a particular host
+ *
+ * @var bool
+ */
+ protected $ofAllServices = false;
+
+ /**
+ * Set whether to schedule a check of all services associated with a particular host
+ *
+ * @param bool $ofAllServices
+ *
+ * @return $this
+ */
+ public function setOfAllServices($ofAllServices = true)
+ {
+ $this->ofAllServices = (bool) $ofAllServices;
+ return $this;
+ }
+
+ /**
+ * Get whether to schedule a check of all services associated with a particular host
+ *
+ * @return bool
+ */
+ public function getOfAllServices()
+ {
+ return $this->ofAllServices;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php
new file mode 100644
index 0000000..3ac37d3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleHostDowntimeCommand.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Schedule a host downtime
+ */
+class ScheduleHostDowntimeCommand extends ScheduleServiceDowntimeCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST
+ );
+
+ /**
+ * Whether to schedule a downtime for all services associated with a particular host
+ *
+ * @var bool
+ */
+ protected $forAllServices = false;
+
+ /** @var bool Whether to send the all_services API parameter */
+ protected $forAllServicesNative;
+
+ /**
+ * Set whether to schedule a downtime for all services associated with a particular host
+ *
+ * @param bool $forAllServices
+ *
+ * @return $this
+ */
+ public function setForAllServices($forAllServices = true)
+ {
+ $this->forAllServices = (bool) $forAllServices;
+ return $this;
+ }
+
+ /**
+ * Get whether to schedule a downtime for all services associated with a particular host
+ *
+ * @return bool
+ */
+ public function getForAllServices()
+ {
+ return $this->forAllServices;
+ }
+
+ /**
+ * Get whether to send the all_services API parameter
+ *
+ * @return bool
+ */
+ public function isForAllServicesNative()
+ {
+ return $this->forAllServicesNative;
+ }
+
+ /**
+ * Get whether to send the all_services API parameter
+ *
+ * @param bool $forAllServicesNative
+ *
+ * @return $this
+ */
+ public function setForAllServicesNative($forAllServicesNative = true)
+ {
+ $this->forAllServicesNative = (bool) $forAllServicesNative;
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php
new file mode 100644
index 0000000..8880984
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceCheckCommand.php
@@ -0,0 +1,92 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Schedule a service check
+ */
+class ScheduleServiceCheckCommand extends ObjectCommand
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowedObjects = array(
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Time when the next check of a host or service is to be scheduled
+ *
+ * If active checks are disabled on a host- or service-specific or program-wide basis or the host or service is
+ * already scheduled to be checked at an earlier time, etc. The check may not actually be scheduled at the time
+ * specified. This behaviour can be overridden by setting `ScheduledCheck::$forced' to true.
+ *
+ * @var int Unix timestamp
+ */
+ protected $checkTime;
+
+ /**
+ * Whether the check is forced
+ *
+ * Forced checks are performed regardless of what time it is (e.g. time period restrictions are ignored) and whether
+ * or not active checks are enabled on a host- or service-specific or program-wide basis.
+ *
+ * @var bool
+ */
+ protected $forced = false;
+
+ /**
+ * Set the time when the next check of a host or service is to be scheduled
+ *
+ * @param int $checkTime Unix timestamp
+ *
+ * @return $this
+ */
+ public function setCheckTime($checkTime)
+ {
+ $this->checkTime = (int) $checkTime;
+ return $this;
+ }
+
+ /**
+ * Get the time when the next check of a host or service is to be scheduled
+ *
+ * @return int Unix timestamp
+ */
+ public function getCheckTime()
+ {
+ return $this->checkTime;
+ }
+
+ /**
+ * Set whether the check is forced
+ *
+ * @param bool $forced
+ *
+ * @return $this
+ */
+ public function setForced($forced = true)
+ {
+ $this->forced = (bool) $forced;
+ return $this;
+ }
+
+ /**
+ * Get whether the check is forced
+ *
+ * @return bool
+ */
+ public function getForced()
+ {
+ return $this->forced;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'ScheduleCheck';
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php
new file mode 100644
index 0000000..a023ab5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ScheduleServiceDowntimeCommand.php
@@ -0,0 +1,190 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Schedule a service downtime
+ */
+class ScheduleServiceDowntimeCommand extends AddCommentCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Downtime starts at the exact time specified
+ *
+ * If `Downtime::$fixed' is set to false, the time between `Downtime::$start' and `Downtime::$end' at which a
+ * host or service transitions to a problem state determines the time at which the downtime actually starts.
+ * The downtime will then last for `Downtime::$duration' seconds.
+ *
+ * @var int Unix timestamp
+ */
+ protected $start;
+
+ /**
+ * Downtime ends at the exact time specified
+ *
+ * If `Downtime::$fixed' is set to false, the time between `Downtime::$start' and `Downtime::$end' at which a
+ * host or service transitions to a problem state determines the time at which the downtime actually starts.
+ * The downtime will then last for `Downtime::$duration' seconds.
+ *
+ * @var int Unix timestamp
+ */
+ protected $end;
+
+ /**
+ * Whether it's a fixed or flexible downtime
+ *
+ * @var bool
+ */
+ protected $fixed = true;
+
+ /**
+ * ID of the downtime which triggers this downtime
+ *
+ * The start of this downtime is triggered by the start of the other scheduled host or service downtime.
+ *
+ * @var int|null
+ */
+ protected $triggerId;
+
+ /**
+ * The duration in seconds the downtime must last if it's a flexible downtime
+ *
+ * If `Downtime::$fixed' is set to false, the downtime will last for the duration in seconds specified, even
+ * if the host or service recovers before the downtime expires.
+ *
+ * @var int|null
+ */
+ protected $duration;
+
+ /**
+ * Set the time when the downtime should start
+ *
+ * @param int $start Unix timestamp
+ *
+ * @return $this
+ */
+ public function setStart($start)
+ {
+ $this->start = (int) $start;
+ return $this;
+ }
+
+ /**
+ * Get the time when the downtime should start
+ *
+ * @return int Unix timestamp
+ */
+ public function getStart()
+ {
+ return $this->start;
+ }
+
+ /**
+ * Set the time when the downtime should end
+ *
+ * @param int $end Unix timestamp
+ *
+ * @return $this
+ */
+ public function setEnd($end)
+ {
+ $this->end = (int) $end;
+ return $this;
+ }
+
+ /**
+ * Get the time when the downtime should end
+ *
+ * @return int Unix timestamp
+ */
+ public function getEnd()
+ {
+ return $this->end;
+ }
+
+ /**
+ * Set whether it's a fixed or flexible downtime
+ *
+ * @param boolean $fixed
+ *
+ * @return $this
+ */
+ public function setFixed($fixed = true)
+ {
+ $this->fixed = (bool) $fixed;
+ return $this;
+ }
+
+ /**
+ * Is the downtime fixed?
+ *
+ * @return boolean
+ */
+ public function getFixed()
+ {
+ return $this->fixed;
+ }
+
+ /**
+ * Set the ID of the downtime which triggers this downtime
+ *
+ * @param int $triggerId
+ *
+ * @return $this
+ */
+ public function setTriggerId($triggerId)
+ {
+ $this->triggerId = (int) $triggerId;
+ return $this;
+ }
+
+ /**
+ * Get the ID of the downtime which triggers this downtime
+ *
+ * @return int|null
+ */
+ public function getTriggerId()
+ {
+ return $this->triggerId;
+ }
+
+ /**
+ * Set the duration in seconds the downtime must last if it's a flexible downtime
+ *
+ * @param int $duration
+ *
+ * @return $this
+ */
+ public function setDuration($duration)
+ {
+ $this->duration = (int) $duration;
+ return $this;
+ }
+
+ /**
+ * Get the duration in seconds the downtime must last if it's a flexible downtime
+ *
+ * @return int|null
+ */
+ public function getDuration()
+ {
+ return $this->duration;
+ }
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\IcingaCommand::getName() For the method documentation.
+ */
+ public function getName()
+ {
+ return 'ScheduleDowntime';
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php b/modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php
new file mode 100644
index 0000000..ac8889c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/SendCustomNotificationCommand.php
@@ -0,0 +1,82 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Send custom notifications for a host or service
+ */
+class SendCustomNotificationCommand extends WithCommentCommand
+{
+ /**
+ * {@inheritdoc}
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST,
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Whether the notification is forced
+ *
+ * Forced notifications are sent out regardless of time restrictions and whether or not notifications are enabled.
+ *
+ * @var bool
+ */
+ protected $forced;
+
+ /**
+ * Whether to broadcast the notification
+ *
+ * Broadcast notifications are sent out to all normal and escalated contacts.
+ *
+ * @var bool
+ */
+ protected $broadcast;
+
+ /**
+ * Get whether to force the notification
+ *
+ * @return bool
+ */
+ public function getForced()
+ {
+ return $this->forced;
+ }
+
+ /**
+ * Set whether to force the notification
+ *
+ * @param bool $forced
+ *
+ * @return $this
+ */
+ public function setForced($forced = true)
+ {
+ $this->forced = $forced;
+ return $this;
+ }
+
+ /**
+ * Get whether to broadcast the notification
+ *
+ * @return bool
+ */
+ public function getBroadcast()
+ {
+ return $this->broadcast;
+ }
+
+ /**
+ * Set whether to broadcast the notification
+ *
+ * @param bool $broadcast
+ *
+ * @return $this
+ */
+ public function setBroadcast($broadcast = true)
+ {
+ $this->broadcast = $broadcast;
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php b/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php
new file mode 100644
index 0000000..e3ba8a2
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/ToggleObjectFeatureCommand.php
@@ -0,0 +1,113 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Enable or disable a feature of an Icinga object, i.e. host or service
+ */
+class ToggleObjectFeatureCommand extends ObjectCommand
+{
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Module\Monitoring\Command\Object\ObjectCommand::$allowedObjects For the property documentation.
+ */
+ protected $allowedObjects = array(
+ self::TYPE_HOST,
+ self::TYPE_SERVICE
+ );
+
+ /**
+ * Feature for enabling or disabling active checks of a host or service
+ */
+ const FEATURE_ACTIVE_CHECKS = 'active_checks_enabled';
+
+ /**
+ * Feature for enabling or disabling passive checks of a host or service
+ */
+ const FEATURE_PASSIVE_CHECKS = 'passive_checks_enabled';
+
+ /**
+ * Feature for enabling or disabling processing of host or service checks via the OCHP command for a host or service
+ */
+ const FEATURE_OBSESSING = 'obsessing';
+
+ /**
+ * Feature for enabling or disabling notifications for a host or service
+ *
+ * Notifications will be sent out only if notifications are enabled on a program-wide basis as well.
+ */
+ const FEATURE_NOTIFICATIONS = 'notifications_enabled';
+
+ /**
+ * Feature for enabling or disabling event handler for a host or service
+ */
+ const FEATURE_EVENT_HANDLER = 'event_handler_enabled';
+
+ /**
+ * Feature for enabling or disabling flap detection for a host or service.
+ *
+ * In order to enable flap detection flap detection must be enabled on a program-wide basis as well.
+ */
+ const FEATURE_FLAP_DETECTION = 'flap_detection_enabled';
+
+ /**
+ * Feature that is to be enabled or disabled
+ *
+ * @var string
+ */
+ protected $feature;
+
+ /**
+ * Whether the feature should be enabled or disabled
+ *
+ * @var bool
+ */
+ protected $enabled;
+
+ /**
+ * Set the feature that is to be enabled or disabled
+ *
+ * @param string $feature
+ *
+ * @return $this
+ */
+ public function setFeature($feature)
+ {
+ $this->feature = (string) $feature;
+ return $this;
+ }
+
+ /**
+ * Get the feature that is to be enabled or disabled
+ *
+ * @return string
+ */
+ public function getFeature()
+ {
+ return $this->feature;
+ }
+
+ /**
+ * Set whether the feature should be enabled or disabled
+ *
+ * @param bool $enabled
+ *
+ * @return $this
+ */
+ public function setEnabled($enabled = true)
+ {
+ $this->enabled = (bool) $enabled;
+ return $this;
+ }
+
+ /**
+ * Get whether the feature should be enabled or disabled
+ *
+ * @return bool
+ */
+ public function getEnabled()
+ {
+ return $this->enabled;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php b/modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php
new file mode 100644
index 0000000..aa2e439
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Object/WithCommentCommand.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Object;
+
+/**
+ * Base class for commands adding comments
+ */
+abstract class WithCommentCommand extends ObjectCommand
+{
+ use CommandAuthor;
+
+ /**
+ * Comment
+ *
+ * @var string
+ */
+ protected $comment;
+
+ /**
+ * Set the comment
+ *
+ * @param string $comment
+ *
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->comment = (string) $comment;
+ return $this;
+ }
+
+ /**
+ * Get the comment
+ *
+ * @return string
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php
new file mode 100644
index 0000000..8370314
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaApiCommandRenderer.php
@@ -0,0 +1,324 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Renderer;
+
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Command\IcingaApiCommand;
+use Icinga\Module\Monitoring\Command\Instance\ToggleInstanceFeatureCommand;
+use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand;
+use Icinga\Module\Monitoring\Command\Object\AddCommentCommand;
+use Icinga\Module\Monitoring\Command\Object\ApiScheduleHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand;
+use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ProcessCheckResultCommand;
+use Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\RemoveAcknowledgementCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\SendCustomNotificationCommand;
+use Icinga\Module\Monitoring\Command\Object\ToggleObjectFeatureCommand;
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use InvalidArgumentException;
+
+/**
+ * Icinga command renderer for the Icinga command file
+ */
+class IcingaApiCommandRenderer implements IcingaCommandRendererInterface
+{
+ /**
+ * Name of the Icinga application object
+ *
+ * @var string
+ */
+ protected $app = 'app';
+
+ /**
+ * Get the name of the Icinga application object
+ *
+ * @return string
+ */
+ public function getApp()
+ {
+ return $this->app;
+ }
+
+ /**
+ * Set the name of the Icinga application object
+ *
+ * @param string $app
+ *
+ * @return $this
+ */
+ public function setApp($app)
+ {
+ $this->app = $app;
+
+ return $this;
+ }
+
+ /**
+ * Apply filter to query data
+ *
+ * @param array $data
+ * @param MonitoredObject $object
+ *
+ * @return array
+ */
+ protected function applyFilter(array &$data, MonitoredObject $object)
+ {
+ if ($object->getType() === $object::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $data['host'] = $object->getName();
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $data['service'] = sprintf('%s!%s', $object->getHost()->getName(), $object->getName());
+ }
+ }
+
+ /**
+ * Render a command
+ *
+ * @param IcingaCommand $command
+ *
+ * @return IcingaApiCommand
+ */
+ public function render(IcingaCommand $command)
+ {
+ $renderMethod = 'render' . $command->getName();
+ if (! method_exists($this, $renderMethod)) {
+ die($renderMethod);
+ }
+ return $this->$renderMethod($command);
+ }
+
+ public function renderAddComment(AddCommentCommand $command)
+ {
+ $endpoint = 'actions/add-comment';
+ $data = array(
+ 'author' => $command->getAuthor(),
+ 'comment' => $command->getComment()
+ );
+
+ if ($command->getExpireTime() !== null) {
+ $data['expiry'] = $command->getExpireTime();
+ }
+
+ $this->applyFilter($data, $command->getObject());
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderSendCustomNotification(SendCustomNotificationCommand $command)
+ {
+ $endpoint = 'actions/send-custom-notification';
+ $data = array(
+ 'author' => $command->getAuthor(),
+ 'comment' => $command->getComment(),
+ 'force' => $command->getForced()
+ );
+ $this->applyFilter($data, $command->getObject());
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderProcessCheckResult(ProcessCheckResultCommand $command)
+ {
+ $endpoint = 'actions/process-check-result';
+ $data = array(
+ 'exit_status' => $command->getStatus(),
+ 'plugin_output' => $command->getOutput(),
+ 'performance_data' => $command->getPerformanceData()
+ );
+ $this->applyFilter($data, $command->getObject());
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderScheduleCheck(ScheduleServiceCheckCommand $command)
+ {
+ $endpoint = 'actions/reschedule-check';
+ $data = array(
+ 'next_check' => $command->getCheckTime(),
+ 'force' => $command->getForced()
+ );
+ $this->applyFilter($data, $command->getObject());
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderScheduleDowntime(ScheduleServiceDowntimeCommand $command)
+ {
+ $endpoint = 'actions/schedule-downtime';
+ $data = array(
+ 'author' => $command->getAuthor(),
+ 'comment' => $command->getComment(),
+ 'start_time' => $command->getStart(),
+ 'end_time' => $command->getEnd(),
+ 'duration' => $command->getDuration(),
+ 'fixed' => $command->getFixed(),
+ 'trigger_name' => $command->getTriggerId()
+ );
+ $commandData = $data;
+
+ if ($command instanceof PropagateHostDowntimeCommand) {
+ $commandData['child_options'] = $command->getTriggered() ? 1 : 2;
+ } elseif ($command instanceof ApiScheduleHostDowntimeCommand) {
+ // We assume that it has previously been verified that the Icinga version is
+ // equal to or greater than 2.11.0
+ $commandData['child_options'] = $command->getChildOptions();
+ }
+
+ $allServicesCompat = false;
+ if ($command instanceof ScheduleHostDowntimeCommand) {
+ if ($command->isForAllServicesNative()) {
+ // We assume that it has previously been verified that the Icinga version is
+ // equal to or greater than 2.11.0
+ $commandData['all_services'] = $command->getForAllServices();
+ } else {
+ $allServicesCompat = $command->getForAllServices();
+ }
+ }
+
+ $this->applyFilter($commandData, $command->getObject());
+ $apiCommand = IcingaApiCommand::create($endpoint, $commandData);
+
+ if ($allServicesCompat) {
+ $commandData = $data + [
+ 'type' => 'Service',
+ 'filter' => 'host.name == host_name',
+ 'filter_vars' => [
+ 'host_name' => $command->getObject()->getName()
+ ]
+ ];
+ $apiCommand->setNext(IcingaApiCommand::create($endpoint, $commandData));
+ }
+
+ return $apiCommand;
+ }
+
+ public function renderAcknowledgeProblem(AcknowledgeProblemCommand $command)
+ {
+ $endpoint = 'actions/acknowledge-problem';
+ $data = array(
+ 'author' => $command->getAuthor(),
+ 'comment' => $command->getComment(),
+ 'sticky' => $command->getSticky(),
+ 'notify' => $command->getNotify(),
+ 'persistent' => $command->getPersistent()
+ );
+ if ($command->getExpireTime() !== null) {
+ $data['expiry'] = $command->getExpireTime();
+ }
+ $this->applyFilter($data, $command->getObject());
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderToggleObjectFeature(ToggleObjectFeatureCommand $command)
+ {
+ if ($command->getEnabled() === true) {
+ $enabled = true;
+ } else {
+ $enabled = false;
+ }
+ switch ($command->getFeature()) {
+ case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS:
+ $attr = 'enable_active_checks';
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS:
+ $attr = 'enable_passive_checks';
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS:
+ $attr = 'enable_notifications';
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER:
+ $attr = 'enable_event_handler';
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION:
+ $attr = 'enable_flapping';
+ break;
+ default:
+ throw new InvalidArgumentException($command->getFeature());
+ }
+ $endpoint = 'objects/';
+ $object = $command->getObject();
+ if ($object->getType() === ToggleObjectFeatureCommand::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $endpoint .= 'hosts';
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $endpoint .= 'services';
+ }
+ $data = array(
+ 'attrs' => array(
+ $attr => $enabled
+ )
+ );
+ $this->applyFilter($data, $object);
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderDeleteComment(DeleteCommentCommand $command)
+ {
+ $endpoint = 'actions/remove-comment';
+ $data = [
+ 'author' => $command->getAuthor(),
+ 'comment' => $command->getCommentName()
+ ];
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderDeleteDowntime(DeleteDowntimeCommand $command)
+ {
+ $endpoint = 'actions/remove-downtime';
+ $data = [
+ 'author' => $command->getAuthor(),
+ 'downtime' => $command->getDowntimeName()
+ ];
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderRemoveAcknowledgement(RemoveAcknowledgementCommand $command)
+ {
+ $endpoint = 'actions/remove-acknowledgement';
+ $data = ['author' => $command->getAuthor()];
+ $this->applyFilter($data, $command->getObject());
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+
+ public function renderToggleInstanceFeature(ToggleInstanceFeatureCommand $command)
+ {
+ $endpoint = 'objects/icingaapplications/' . $this->getApp();
+ if ($command->getEnabled() === true) {
+ $enabled = true;
+ } else {
+ $enabled = false;
+ }
+ switch ($command->getFeature()) {
+ case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS:
+ $attr = 'enable_host_checks';
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS:
+ $attr = 'enable_service_checks';
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS:
+ $attr = 'enable_event_handlers';
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION:
+ $attr = 'enable_flapping';
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS:
+ $attr = 'enable_notifications';
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA:
+ $attr = 'enable_perfdata';
+ break;
+ default:
+ throw new InvalidArgumentException($command->getFeature());
+ }
+ $data = array(
+ 'attrs' => array(
+ $attr => $enabled
+ )
+ );
+ return IcingaApiCommand::create($endpoint, $data);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php
new file mode 100644
index 0000000..97d1314
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandFileCommandRenderer.php
@@ -0,0 +1,478 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Renderer;
+
+use Icinga\Module\Monitoring\Command\Instance\DisableNotificationsExpireCommand;
+use Icinga\Module\Monitoring\Command\Instance\ToggleInstanceFeatureCommand;
+use Icinga\Module\Monitoring\Command\Object\AcknowledgeProblemCommand;
+use Icinga\Module\Monitoring\Command\Object\AddCommentCommand;
+use Icinga\Module\Monitoring\Command\Object\DeleteCommentCommand;
+use Icinga\Module\Monitoring\Command\Object\DeleteDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\ProcessCheckResultCommand;
+use Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\RemoveAcknowledgementCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceCheckCommand;
+use Icinga\Module\Monitoring\Command\Object\ScheduleServiceDowntimeCommand;
+use Icinga\Module\Monitoring\Command\Object\SendCustomNotificationCommand;
+use Icinga\Module\Monitoring\Command\Object\ToggleObjectFeatureCommand;
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use InvalidArgumentException;
+
+/**
+ * Icinga command renderer for the Icinga command file
+ */
+class IcingaCommandFileCommandRenderer implements IcingaCommandRendererInterface
+{
+ /**
+ * Escape a command string
+ *
+ * @param string $commandString
+ *
+ * @return string
+ */
+ protected function escape($commandString)
+ {
+ return str_replace(array("\r", "\n"), array('\r', '\n'), $commandString);
+ }
+
+ /**
+ * Render a command
+ *
+ * @param IcingaCommand $command
+ * @param int|null $now
+ *
+ * @return string
+ */
+ public function render(IcingaCommand $command, $now = null)
+ {
+ $renderMethod = 'render' . $command->getName();
+ if (! method_exists($this, $renderMethod)) {
+ die($renderMethod);
+ }
+ if ($now === null) {
+ $now = time();
+ }
+ return sprintf('[%u] %s', $now, $this->escape($this->$renderMethod($command)));
+ }
+
+ public function renderAddComment(AddCommentCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $commandString = sprintf(
+ 'ADD_HOST_COMMENT;%s',
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ 'ADD_SVC_COMMENT;%s;%s',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ return sprintf(
+ '%s;%u;%s;%s',
+ $commandString,
+ $command->getPersistent(),
+ $command->getAuthor(),
+ $command->getComment()
+ );
+ }
+
+ public function renderSendCustomNotification(SendCustomNotificationCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $commandString = sprintf(
+ 'SEND_CUSTOM_HOST_NOTIFICATION;%s',
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ 'SEND_CUSTOM_SVC_NOTIFICATION;%s;%s',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ $options = 0; // 0 for no options
+ if ($command->getBroadcast() === true) {
+ $options |= 1;
+ }
+ if ($command->getForced() === true) {
+ $options |= 2;
+ }
+ return sprintf(
+ '%s;%u;%s;%s',
+ $commandString,
+ $options,
+ $command->getAuthor(),
+ $command->getComment()
+ );
+ }
+
+ public function renderProcessCheckResult(ProcessCheckResultCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $commandString = sprintf(
+ 'PROCESS_HOST_CHECK_RESULT;%s',
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ 'PROCESS_SERVICE_CHECK_RESULT;%s;%s',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ $output = $command->getOutput();
+ if ($command->getPerformanceData() !== null) {
+ $output .= '|' . $command->getPerformanceData();
+ }
+ return sprintf(
+ '%s;%u;%s',
+ $commandString,
+ $command->getStatus(),
+ $output
+ );
+ }
+
+ public function renderScheduleCheck(ScheduleServiceCheckCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ /** @var \Icinga\Module\Monitoring\Command\Object\ScheduleHostCheckCommand $command */
+ if ($command->getOfAllServices() === true) {
+ if ($command->getForced() === true) {
+ $commandName = 'SCHEDULE_FORCED_HOST_SVC_CHECKS';
+ } else {
+ $commandName = 'SCHEDULE_HOST_SVC_CHECKS';
+ }
+ } else {
+ if ($command->getForced() === true) {
+ $commandName = 'SCHEDULE_FORCED_HOST_CHECK';
+ } else {
+ $commandName = 'SCHEDULE_HOST_CHECK';
+ }
+ }
+ $commandString = sprintf(
+ '%s;%s',
+ $commandName,
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ '%s;%s;%s',
+ $command->getForced() === true ? 'SCHEDULE_FORCED_SVC_CHECK' : 'SCHEDULE_SVC_CHECK',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ return sprintf(
+ '%s;%u',
+ $commandString,
+ $command->getCheckTime()
+ );
+ }
+
+ public function renderScheduleDowntime(ScheduleServiceDowntimeCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ /** @var \Icinga\Module\Monitoring\Command\Object\ScheduleHostDowntimeCommand $command */
+ if ($command instanceof PropagateHostDowntimeCommand) {
+ /** @var \Icinga\Module\Monitoring\Command\Object\PropagateHostDowntimeCommand $command */
+ $commandName = $command->getTriggered() === true ? 'SCHEDULE_AND_PROPAGATE_TRIGGERED_HOST_DOWNTIME'
+ : 'SCHEDULE_AND_PROPAGATE_HOST_DOWNTIME';
+ } elseif ($command->getForAllServices() === true) {
+ $commandName = 'SCHEDULE_HOST_SVC_DOWNTIME';
+ } else {
+ $commandName = 'SCHEDULE_HOST_DOWNTIME';
+ }
+ $commandString = sprintf(
+ '%s;%s',
+ $commandName,
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ '%s;%s;%s',
+ 'SCHEDULE_SVC_DOWNTIME',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ return sprintf(
+ '%s;%u;%u;%u;%u;%u;%s;%s',
+ $commandString,
+ $command->getStart(),
+ $command->getEnd(),
+ $command->getFixed(),
+ $command->getTriggerId(),
+ $command->getDuration(),
+ $command->getAuthor(),
+ $command->getComment()
+ );
+ }
+
+ public function renderAcknowledgeProblem(AcknowledgeProblemCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $commandString = sprintf(
+ '%s;%s',
+ $command->getExpireTime() !== null ? 'ACKNOWLEDGE_HOST_PROBLEM_EXPIRE' : 'ACKNOWLEDGE_HOST_PROBLEM',
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ '%s;%s;%s',
+ $command->getExpireTime() !== null ? 'ACKNOWLEDGE_SVC_PROBLEM_EXPIRE' : 'ACKNOWLEDGE_SVC_PROBLEM',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ $commandString = sprintf(
+ '%s;%u;%u;%u',
+ $commandString,
+ $command->getSticky() ? 2 : 0,
+ $command->getNotify(),
+ $command->getPersistent()
+ );
+ if ($command->getExpireTime() !== null) {
+ $commandString = sprintf(
+ '%s;%u',
+ $commandString,
+ $command->getExpireTime()
+ );
+ }
+ return sprintf(
+ '%s;%s;%s',
+ $commandString,
+ $command->getAuthor(),
+ $command->getComment()
+ );
+ }
+
+ public function renderToggleObjectFeature(ToggleObjectFeatureCommand $command)
+ {
+ if ($command->getEnabled() === true) {
+ $commandPrefix = 'ENABLE';
+ } else {
+ $commandPrefix = 'DISABLE';
+ }
+ switch ($command->getFeature()) {
+ case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS:
+ $commandFormat = sprintf('%s_%%s_CHECK', $commandPrefix);
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS:
+ $commandFormat = sprintf('%s_PASSIVE_%%s_CHECKS', $commandPrefix);
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_OBSESSING:
+ if ($command->getEnabled() === true) {
+ $commandPrefix = 'START';
+ } else {
+ $commandPrefix = 'STOP';
+ }
+ $commandFormat = sprintf('%s_OBSESSING_OVER_%%s', $commandPrefix);
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS:
+ $commandFormat = sprintf('%s_%%s_NOTIFICATIONS', $commandPrefix);
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER:
+ $commandFormat = sprintf('%s_%%s_EVENT_HANDLER', $commandPrefix);
+ break;
+ case ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION:
+ $commandFormat = sprintf('%s_%%s_FLAP_DETECTION', $commandPrefix);
+ break;
+ default:
+ throw new InvalidArgumentException($command->getFeature());
+ }
+ $object = $command->getObject();
+ if ($object->getType() === ToggleObjectFeatureCommand::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $commandString = sprintf(
+ $commandFormat . ';%s',
+ 'HOST',
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ $commandFormat . ';%s;%s',
+ 'SVC',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ return $commandString;
+ }
+
+ public function renderDeleteComment(DeleteCommentCommand $command)
+ {
+ return sprintf(
+ '%s;%u',
+ $command->getIsService() ? 'DEL_SVC_COMMENT' : 'DEL_HOST_COMMENT',
+ $command->getCommentId()
+ );
+ }
+
+ public function renderDeleteDowntime(DeleteDowntimeCommand $command)
+ {
+ return sprintf(
+ '%s;%u',
+ $command->getIsService() ? 'DEL_SVC_DOWNTIME' : 'DEL_HOST_DOWNTIME',
+ $command->getDowntimeId()
+ );
+ }
+
+ public function renderRemoveAcknowledgement(RemoveAcknowledgementCommand $command)
+ {
+ $object = $command->getObject();
+ if ($command->getObject()->getType() === $command::TYPE_HOST) {
+ /** @var \Icinga\Module\Monitoring\Object\Host $object */
+ $commandString = sprintf(
+ '%s;%s',
+ 'REMOVE_HOST_ACKNOWLEDGEMENT',
+ $object->getName()
+ );
+ } else {
+ /** @var \Icinga\Module\Monitoring\Object\Service $object */
+ $commandString = sprintf(
+ '%s;%s;%s',
+ 'REMOVE_SVC_ACKNOWLEDGEMENT',
+ $object->getHost()->getName(),
+ $object->getName()
+ );
+ }
+ return $commandString;
+ }
+
+ public function renderDisableNotificationsExpire(DisableNotificationsExpireCommand $command)
+ {
+ return sprintf(
+ '%s;%u;%u',
+ 'DISABLE_NOTIFICATIONS_EXPIRE_TIME',
+ time(),
+ $command->getExpireTime()
+ );
+ }
+
+ public function renderToggleInstanceFeature(ToggleInstanceFeatureCommand $command)
+ {
+ switch ($command->getFeature()) {
+ case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS:
+ case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS:
+ case ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING:
+ case ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING:
+ case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS:
+ case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS:
+ if ($command->getEnabled() === true) {
+ $commandPrefix = 'START';
+ } else {
+ $commandPrefix = 'STOP';
+ }
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS:
+ case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION:
+ case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS:
+ case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA:
+ if ($command->getEnabled() === true) {
+ $commandPrefix = 'ENABLE';
+ } else {
+ $commandPrefix = 'DISABLE';
+ }
+ break;
+ default:
+ throw new InvalidArgumentException($command->getFeature());
+ }
+ switch ($command->getFeature()) {
+ case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'EXECUTING_HOST_CHECKS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'EXECUTING_SVC_CHECKS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'EVENT_HANDLERS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'FLAP_DETECTION'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'NOTIFICATIONS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'OBSESSING_OVER_HOST_CHECKS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'OBSESSING_OVER_SVC_CHECKS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'ACCEPTING_PASSIVE_HOST_CHECKS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'ACCEPTING_PASSIVE_SVC_CHECKS'
+ );
+ break;
+ case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA:
+ $commandString = sprintf(
+ '%s_%s',
+ $commandPrefix,
+ 'PERFORMANCE_DATA'
+ );
+ break;
+ default:
+ throw new InvalidArgumentException($command->getFeature());
+ }
+ return $commandString;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php
new file mode 100644
index 0000000..e3ef6ba
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Renderer/IcingaCommandRendererInterface.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Renderer;
+
+/**
+ * Interface for Icinga command renderer
+ */
+interface IcingaCommandRendererInterface
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php b/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php
new file mode 100644
index 0000000..06e6afd
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php
@@ -0,0 +1,291 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Transport;
+
+use Icinga\Application\Hook\AuditHook;
+use Icinga\Application\Logger;
+use Icinga\Exception\Json\JsonDecodeException;
+use Icinga\Module\Monitoring\Command\IcingaApiCommand;
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use Icinga\Module\Monitoring\Command\Renderer\IcingaApiCommandRenderer;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+use Icinga\Module\Monitoring\Exception\CurlException;
+use Icinga\Module\Monitoring\Web\Rest\RestRequest;
+use Icinga\Util\Json;
+
+/**
+ * Command transport over Icinga 2's REST API
+ */
+class ApiCommandTransport implements CommandTransportInterface
+{
+ /**
+ * Transport identifier
+ */
+ const TRANSPORT = 'api';
+
+ /**
+ * API host
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * API password
+ *
+ * @var string
+ */
+ protected $password;
+
+ /**
+ * API port
+ *
+ * @var int
+ */
+ protected $port = 5665;
+
+ /**
+ * Command renderer
+ *
+ * @var IcingaApiCommandRenderer
+ */
+ protected $renderer;
+
+ /**
+ * API username
+ *
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * Create a new API command transport
+ */
+ public function __construct()
+ {
+ $this->renderer = new IcingaApiCommandRenderer();
+ }
+
+ /**
+ * Set the name of the Icinga application object
+ *
+ * @param string $app
+ *
+ * @return $this
+ */
+ public function setApp($app)
+ {
+ $this->renderer->setApp($app);
+
+ return $this;
+ }
+
+ /**
+ * Get the API host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Set the API host
+ *
+ * @param string $host
+ *
+ * @return $this
+ */
+ public function setHost($host)
+ {
+ $this->host = $host;
+
+ return $this;
+ }
+
+ /**
+ * Get the API password
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Set the API password
+ *
+ * @param string $password
+ *
+ * @return $this
+ */
+ public function setPassword($password)
+ {
+ $this->password = $password;
+
+ return $this;
+ }
+
+ /**
+ * Get the API port
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Set the API port
+ *
+ * @param int $port
+ *
+ * @return $this
+ */
+ public function setPort($port)
+ {
+ $this->port = (int) $port;
+
+ return $this;
+ }
+
+ /**
+ * Get the API username
+ *
+ * @return string
+ */
+ public function getUsername()
+ {
+ return $this->username;
+ }
+
+ /**
+ * Set the API username
+ *
+ * @param string $username
+ *
+ * @return $this
+ */
+ public function setUsername($username)
+ {
+ $this->username = $username;
+
+ return $this;
+ }
+
+ /**
+ * Get URI for endpoint
+ *
+ * @param string $endpoint
+ *
+ * @return string
+ */
+ protected function getUriFor($endpoint)
+ {
+ return sprintf('https://%s:%u/v1/%s', $this->getHost(), $this->getPort(), $endpoint);
+ }
+
+ protected function sendCommand(IcingaApiCommand $command)
+ {
+ Logger::debug(
+ 'Sending Icinga command "%s" to the API "%s:%u"',
+ $command->getEndpoint(),
+ $this->getHost(),
+ $this->getPort()
+ );
+
+ $data = $command->getData();
+ $payload = Json::encode($data);
+ AuditHook::logActivity(
+ 'monitoring/command',
+ "Issued command {$command->getEndpoint()} with the following payload: $payload",
+ $data
+ );
+
+ try {
+ $response = RestRequest::post($this->getUriFor($command->getEndpoint()))
+ ->authenticateWith($this->getUsername(), $this->getPassword())
+ ->sendJson()
+ ->noStrictSsl()
+ ->setPayload($command->getData())
+ ->send();
+ } catch (JsonDecodeException $e) {
+ throw new CommandTransportException(
+ 'Got invalid JSON response from the Icinga 2 API: %s',
+ $e->getMessage()
+ );
+ }
+
+ if (isset($response['error'])) {
+ throw new CommandTransportException(
+ 'Can\'t send external Icinga command: %u %s',
+ $response['error'],
+ $response['status']
+ );
+ }
+ $result = array_pop($response['results']);
+ if (! empty($result)
+ && ($result['code'] < 200 || $result['code'] >= 300)
+ ) {
+ throw new CommandTransportException(
+ 'Can\'t send external Icinga command: %u %s',
+ $result['code'],
+ $result['status']
+ );
+ }
+ if ($command->hasNext()) {
+ $this->sendCommand($command->getNext());
+ }
+ }
+
+ /**
+ * Send the Icinga command over the Icinga 2 API
+ *
+ * @param IcingaCommand $command
+ * @param int|null $now
+ *
+ * @throws CommandTransportException
+ */
+ public function send(IcingaCommand $command, $now = null)
+ {
+ $this->sendCommand($this->renderer->render($command));
+ }
+
+ /**
+ * Try to connect to the API
+ *
+ * @throws CommandTransportException In case of failure
+ */
+ public function probe()
+ {
+ $request = RestRequest::get($this->getUriFor(null))
+ ->authenticateWith($this->getUsername(), $this->getPassword())
+ ->noStrictSsl();
+
+ try {
+ $response = $request->send();
+ } catch (CurlException $e) {
+ throw new CommandTransportException(
+ 'Couldn\'t connect to the Icinga 2 API: %s',
+ $e->getMessage()
+ );
+ } catch (JsonDecodeException $e) {
+ throw new CommandTransportException(
+ 'Got invalid JSON response from the Icinga 2 API: %s',
+ $e->getMessage()
+ );
+ }
+
+ if (isset($response['error'])) {
+ throw new CommandTransportException(
+ 'Can\'t connect to the Icinga 2 API: %u %s',
+ $response['error'],
+ $response['status']
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php
new file mode 100644
index 0000000..aa47547
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php
@@ -0,0 +1,170 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Transport;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Application\Logger;
+use Icinga\Data\ConfigObject;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use Icinga\Module\Monitoring\Command\Object\ObjectCommand;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+
+/**
+ * Command transport
+ *
+ * This class is subject to change as we do not have environments yet (#4471).
+ */
+class CommandTransport implements CommandTransportInterface
+{
+ /**
+ * Transport configuration
+ *
+ * @var Config
+ */
+ protected static $config;
+
+ /**
+ * Get transport configuration
+ *
+ * @return Config
+ *
+ * @throws ConfigurationError
+ */
+ public static function getConfig()
+ {
+ if (static::$config === null) {
+ $config = Config::module('monitoring', 'commandtransports');
+ if ($config->isEmpty()) {
+ throw new ConfigurationError(
+ mt('monitoring', 'No command transports have been configured in "%s".'),
+ $config->getConfigFile()
+ );
+ }
+
+ static::$config = $config;
+ }
+
+ return static::$config;
+ }
+
+ /**
+ * Create a transport from config
+ *
+ * @param ConfigObject $config
+ *
+ * @return LocalCommandFile|RemoteCommandFile
+ *
+ * @throws ConfigurationError
+ */
+ public static function createTransport(ConfigObject $config)
+ {
+ $config = clone $config;
+ switch (strtolower($config->transport)) {
+ case RemoteCommandFile::TRANSPORT:
+ $transport = new RemoteCommandFile();
+ break;
+ case ApiCommandTransport::TRANSPORT:
+ $transport = new ApiCommandTransport();
+ break;
+ case LocalCommandFile::TRANSPORT:
+ case '': // Casting null to string is the empty string
+ $transport = new LocalCommandFile();
+ break;
+ default:
+ throw new ConfigurationError(
+ mt(
+ 'monitoring',
+ 'Cannot create command transport "%s". Invalid transport'
+ . ' defined in "%s". Use one of "%s", "%s" or "%s".'
+ ),
+ $config->transport,
+ static::getConfig()->getConfigFile(),
+ LocalCommandFile::TRANSPORT,
+ RemoteCommandFile::TRANSPORT,
+ ApiCommandTransport::TRANSPORT
+ );
+ }
+
+ unset($config->transport);
+ foreach ($config as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ if (! method_exists($transport, $method)) {
+ // Ignore settings from config that don't have a setter on the transport instead of throwing an
+ // exception here because the transport should throw an exception if it's not fully set up
+ // when being about to send a command
+ continue;
+ }
+
+ $transport->$method($value);
+ }
+
+ return $transport;
+ }
+
+ /**
+ * Send the given command over an appropriate Icinga command transport
+ *
+ * This will try one configured transport after another until the command has been successfully sent.
+ *
+ * @param IcingaCommand $command The command to send
+ * @param int|null $now Timestamp of the command or null for now
+ *
+ * @throws CommandTransportException If sending the Icinga command failed
+ */
+ public function send(IcingaCommand $command, $now = null)
+ {
+ $errors = array();
+
+ foreach (static::getConfig() as $name => $transportConfig) {
+ $transport = static::createTransport($transportConfig);
+ if ($this->transferPossible($command, $transport)) {
+ try {
+ $transport->send($command, $now);
+ } catch (Exception $e) {
+ Logger::error($e);
+ $errors[] = sprintf('%s: %s.', $name, rtrim($e->getMessage(), '.'));
+ continue; // Try the next transport
+ }
+
+ return; // The command was successfully sent
+ }
+ }
+
+ if (! empty($errors)) {
+ throw new CommandTransportException(implode("\n", $errors));
+ }
+
+ throw new CommandTransportException(
+ mt(
+ 'monitoring',
+ 'Failed to send external Icinga command. No transport has been configured'
+ . ' for this instance. Please contact your Icinga Web administrator.'
+ )
+ );
+ }
+
+ /**
+ * Return whether it is possible to send the given command using the given transport
+ *
+ * @param IcingaCommand $command
+ * @param CommandTransportInterface $transport
+ *
+ * @return bool
+ */
+ protected function transferPossible($command, $transport)
+ {
+ if (! method_exists($transport, 'getInstance') || !$command instanceof ObjectCommand) {
+ return true;
+ }
+
+ $transportInstance = $transport->getInstance();
+ if (! $transportInstance || $transportInstance === 'none') {
+ return true;
+ }
+
+ return strtolower($transportInstance) === strtolower($command->getObject()->instance_name);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php
new file mode 100644
index 0000000..e9cb086
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransportInterface.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Transport;
+
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+
+/**
+ * Interface for Icinga command transports
+ */
+interface CommandTransportInterface
+{
+ /**
+ * Send an Icinga command over the Icinga command transport
+ *
+ * @param IcingaCommand $command The command to send
+ * @param int|null $now Timestamp of the command or null for now
+ *
+ * @throws \Icinga\Module\Monitoring\Exception\CommandTransportException If sending the Icinga command failed
+ */
+ public function send(IcingaCommand $command, $now = null);
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php
new file mode 100644
index 0000000..891a46f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php
@@ -0,0 +1,168 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Transport;
+
+use Exception;
+use RuntimeException;
+use Icinga\Application\Logger;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use Icinga\Module\Monitoring\Command\Renderer\IcingaCommandFileCommandRenderer;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+use Icinga\Util\File;
+
+/**
+ * A local Icinga command file
+ */
+class LocalCommandFile implements CommandTransportInterface
+{
+ /**
+ * Transport identifier
+ */
+ const TRANSPORT = 'local';
+
+ /**
+ * The name of the Icinga instance this transport will transfer commands to
+ *
+ * @var string
+ */
+ protected $instanceName;
+
+ /**
+ * Path to the icinga command file
+ *
+ * @var String
+ */
+ protected $path;
+
+ /**
+ * Mode used to open the icinga command file
+ *
+ * @var string
+ */
+ protected $openMode = 'wn';
+
+ /**
+ * Command renderer
+ *
+ * @var IcingaCommandFileCommandRenderer
+ */
+ protected $renderer;
+
+ /**
+ * Create a new local command file command transport
+ */
+ public function __construct()
+ {
+ $this->renderer = new IcingaCommandFileCommandRenderer();
+ }
+
+ /**
+ * Set the name of the Icinga instance this transport will transfer commands to
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setInstance($name)
+ {
+ $this->instanceName = $name;
+ return $this;
+ }
+
+ /**
+ * Return the name of the Icinga instance this transport will transfer commands to
+ *
+ * @return string
+ */
+ public function getInstance()
+ {
+ return $this->instanceName;
+ }
+
+ /**
+ * Set the path to the local Icinga command file
+ *
+ * @param string $path
+ *
+ * @return $this
+ */
+ public function setPath($path)
+ {
+ $this->path = (string) $path;
+ return $this;
+ }
+
+ /**
+ * Get the path to the local Icinga command file
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Set the mode used to open the icinga command file
+ *
+ * @param string $openMode
+ *
+ * @return $this
+ */
+ public function setOpenMode($openMode)
+ {
+ $this->openMode = (string) $openMode;
+ return $this;
+ }
+
+ /**
+ * Get the mode used to open the icinga command file
+ *
+ * @return string
+ */
+ public function getOpenMode()
+ {
+ return $this->openMode;
+ }
+
+ /**
+ * Write the command to the local Icinga command file
+ *
+ * @param IcingaCommand $command
+ * @param int|null $now
+ *
+ * @throws ConfigurationError
+ * @throws CommandTransportException
+ */
+ public function send(IcingaCommand $command, $now = null)
+ {
+ if (! isset($this->path)) {
+ throw new ConfigurationError(
+ 'Can\'t send external Icinga Command. Path to the local command file is missing'
+ );
+ }
+ $commandString = $this->renderer->render($command, $now);
+ Logger::debug(
+ 'Sending external Icinga command "%s" to the local command file "%s"',
+ $commandString,
+ $this->path
+ );
+ try {
+ $file = new File($this->path, $this->openMode);
+ $file->fwrite($commandString . "\n");
+ } catch (Exception $e) {
+ $message = $e->getMessage();
+ if ($e instanceof RuntimeException && ($pos = strrpos($message, ':')) !== false) {
+ // Assume RuntimeException thrown by SplFileObject in the format: __METHOD__ . "({$filename}): Message"
+ $message = substr($message, $pos + 1);
+ }
+ throw new CommandTransportException(
+ 'Can\'t send external Icinga command to the local command file "%s": %s',
+ $this->path,
+ $message
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php
new file mode 100644
index 0000000..5426bb9
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php
@@ -0,0 +1,465 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Command\Transport;
+
+use Icinga\Application\Logger;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Monitoring\Command\IcingaCommand;
+use Icinga\Module\Monitoring\Command\Renderer\IcingaCommandFileCommandRenderer;
+use Icinga\Module\Monitoring\Exception\CommandTransportException;
+
+/**
+ * A remote Icinga command file
+ *
+ * Key-based SSH login must be possible for the user to log in as on the remote host
+ */
+class RemoteCommandFile implements CommandTransportInterface
+{
+ /**
+ * Transport identifier
+ */
+ const TRANSPORT = 'remote';
+
+ /**
+ * The name of the Icinga instance this transport will transfer commands to
+ *
+ * @var string
+ */
+ protected $instanceName;
+
+ /**
+ * Remote host
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * Port to connect to on the remote host
+ *
+ * @var int
+ */
+ protected $port = 22;
+
+ /**
+ * User to log in as on the remote host
+ *
+ * Defaults to current PHP process' user
+ *
+ * @var string
+ */
+ protected $user;
+
+ /**
+ * Path to the private key file for the key-based authentication
+ *
+ * @var string
+ */
+ protected $privateKey;
+
+ /**
+ * Path to the Icinga command file on the remote host
+ *
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * Command renderer
+ *
+ * @var IcingaCommandFileCommandRenderer
+ */
+ protected $renderer;
+
+ /**
+ * SSH subprocess pipes
+ *
+ * @var array
+ */
+ protected $sshPipes;
+
+ /**
+ * SSH subprocess
+ *
+ * @var resource
+ */
+ protected $sshProcess;
+
+ /**
+ * Create a new remote command file command transport
+ */
+ public function __construct()
+ {
+ $this->renderer = new IcingaCommandFileCommandRenderer();
+ }
+
+ /**
+ * Set the name of the Icinga instance this transport will transfer commands to
+ *
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function setInstance($name)
+ {
+ $this->instanceName = $name;
+ return $this;
+ }
+
+ /**
+ * Return the name of the Icinga instance this transport will transfer commands to
+ *
+ * @return string
+ */
+ public function getInstance()
+ {
+ return $this->instanceName;
+ }
+
+ /**
+ * Set the remote host
+ *
+ * @param string $host
+ *
+ * @return $this
+ */
+ public function setHost($host)
+ {
+ $this->host = (string) $host;
+ return $this;
+ }
+
+ /**
+ * Get the remote host
+ *
+ * @return string
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Set the port to connect to on the remote host
+ *
+ * @param int $port
+ *
+ * @return $this
+ */
+ public function setPort($port)
+ {
+ $this->port = (int) $port;
+ return $this;
+ }
+
+ /**
+ * Get the port to connect on the remote host
+ *
+ * @return int
+ */
+ public function getPort()
+ {
+ return $this->port;
+ }
+
+ /**
+ * Set the user to log in as on the remote host
+ *
+ * @param string $user
+ *
+ * @return $this
+ */
+ public function setUser($user)
+ {
+ $this->user = (string) $user;
+ return $this;
+ }
+
+ /**
+ * Get the user to log in as on the remote host
+ *
+ * Defaults to current PHP process' user
+ *
+ * @return string|null
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ /**
+ * Set the path to the private key file
+ *
+ * @param string $privateKey
+ *
+ * @return $this
+ */
+ public function setPrivateKey($privateKey)
+ {
+ $this->privateKey = (string) $privateKey;
+ return $this;
+ }
+
+ /**
+ * Get the path to the private key
+ *
+ * @return string
+ */
+ public function getPrivateKey()
+ {
+ return $this->privateKey;
+ }
+
+ /**
+ * Use a given resource to set the user and the key
+ *
+ * @param string
+ *
+ * @throws ConfigurationError
+ */
+ public function setResource($resource = null)
+ {
+ $config = ResourceFactory::getResourceConfig($resource);
+
+ if (! isset($config->user)) {
+ throw new ConfigurationError(
+ t("Can't send external Icinga Command. Remote user is missing")
+ );
+ }
+ if (! isset($config->private_key)) {
+ throw new ConfigurationError(
+ t("Can't send external Icinga Command. The private key for the remote user is missing")
+ );
+ }
+
+ $this->setUser($config->user);
+ $this->setPrivateKey($config->private_key);
+ }
+
+ /**
+ * Set the path to the Icinga command file on the remote host
+ *
+ * @param string $path
+ *
+ * @return $this
+ */
+ public function setPath($path)
+ {
+ $this->path = (string) $path;
+ return $this;
+ }
+
+ /**
+ * Get the path to the Icinga command file on the remote host
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Write the command to the Icinga command file on the remote host
+ *
+ * @param IcingaCommand $command
+ * @param int|null $now
+ *
+ * @throws ConfigurationError
+ * @throws CommandTransportException
+ */
+ public function send(IcingaCommand $command, $now = null)
+ {
+ if (! isset($this->path)) {
+ throw new ConfigurationError(
+ 'Can\'t send external Icinga Command. Path to the remote command file is missing'
+ );
+ }
+ if (! isset($this->host)) {
+ throw new ConfigurationError('Can\'t send external Icinga Command. Remote host is missing');
+ }
+ $commandString = $this->renderer->render($command, $now);
+ Logger::debug(
+ 'Sending external Icinga command "%s" to the remote command file "%s:%u%s"',
+ $commandString,
+ $this->host,
+ $this->port,
+ $this->path
+ );
+ return $this->sendCommandString($commandString);
+ }
+
+ /**
+ * Get the SSH command
+ *
+ * @return string
+ */
+ protected function sshCommand()
+ {
+ $cmd = sprintf(
+ 'exec ssh -o BatchMode=yes -p %u',
+ $this->port
+ );
+ // -o BatchMode=yes for disabling interactive authentication methods
+
+ if (isset($this->user)) {
+ $cmd .= ' -l ' . escapeshellarg($this->user);
+ }
+
+ if (isset($this->privateKey)) {
+ // TODO: StrictHostKeyChecking=no for compat only, must be removed
+ $cmd .= ' -o StrictHostKeyChecking=no'
+ . ' -i ' . escapeshellarg($this->privateKey);
+ }
+
+ $cmd .= sprintf(
+ ' %s "cat > %s"',
+ escapeshellarg($this->host),
+ escapeshellarg($this->path)
+ );
+
+ return $cmd;
+ }
+
+ /**
+ * Send the command over SSH
+ *
+ * @param string $commandString
+ *
+ * @throws CommandTransportException
+ */
+ protected function sendCommandString($commandString)
+ {
+ if ($this->isSshAlive()) {
+ $ret = fwrite($this->sshPipes[0], $commandString . "\n");
+ if ($ret === false) {
+ $this->throwSshFailure('Cannot write to the remote command pipe');
+ } elseif ($ret !== strlen($commandString) + 1) {
+ $this->throwSshFailure(
+ 'Failed to write the whole command to the remote command pipe'
+ );
+ }
+ } else {
+ $this->throwSshFailure();
+ }
+ }
+
+ /**
+ * Get the pipes of the SSH subprocess
+ *
+ * @return array
+ */
+ protected function getSshPipes()
+ {
+ if ($this->sshPipes === null) {
+ $this->forkSsh();
+ }
+
+ return $this->sshPipes;
+ }
+
+ /**
+ * Get the SSH subprocess
+ *
+ * @return resource
+ */
+ protected function getSshProcess()
+ {
+ if ($this->sshProcess === null) {
+ $this->forkSsh();
+ }
+
+ return $this->sshProcess;
+ }
+
+ /**
+ * Get the status of the SSH subprocess
+ *
+ * @param string $what
+ *
+ * @return mixed
+ */
+ protected function getSshProcessStatus($what = null)
+ {
+ $status = proc_get_status($this->getSshProcess());
+ if ($what === null) {
+ return $status;
+ } else {
+ return $status[$what];
+ }
+ }
+
+ /**
+ * Get whether the SSH subprocess is alive
+ *
+ * @return bool
+ */
+ protected function isSshAlive()
+ {
+ return $this->getSshProcessStatus('running');
+ }
+
+ /**
+ * Fork SSH subprocess
+ *
+ * @throws CommandTransportException If fork fails
+ */
+ protected function forkSsh()
+ {
+ $descriptors = array(
+ 0 => array('pipe', 'r'),
+ 1 => array('pipe', 'w'),
+ 2 => array('pipe', 'w')
+ );
+
+ $this->sshProcess = proc_open($this->sshCommand(), $descriptors, $this->sshPipes);
+
+ if (! is_resource($this->sshProcess)) {
+ throw new CommandTransportException(
+ 'Can\'t send external Icinga command: Failed to fork SSH'
+ );
+ }
+ }
+
+ /**
+ * Read from STDERR
+ *
+ * @return string
+ */
+ protected function readStderr()
+ {
+ return stream_get_contents($this->sshPipes[2]);
+ }
+
+ /**
+ * Throw SSH failure
+ *
+ * @param string $msg
+ *
+ * @throws CommandTransportException
+ */
+ protected function throwSshFailure($msg = 'Can\'t send external Icinga command')
+ {
+ throw new CommandTransportException(
+ '%s: %s',
+ $msg,
+ $this->readStderr() . var_export($this->getSshProcessStatus(), true)
+ );
+ }
+
+ /**
+ * Close SSH pipes and SSH subprocess
+ */
+ public function __destruct()
+ {
+ if (is_resource($this->sshProcess)) {
+ fclose($this->sshPipes[0]);
+ fclose($this->sshPipes[1]);
+ fclose($this->sshPipes[2]);
+
+ proc_close($this->sshProcess);
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Controller.php b/modules/monitoring/library/Monitoring/Controller.php
new file mode 100644
index 0000000..2628935
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Controller.php
@@ -0,0 +1,159 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring;
+
+use ArrayIterator;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\QueryException;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use Icinga\File\Csv;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\Monitoring\Data\CustomvarProtectionIterator;
+use Icinga\Util\Json;
+use Icinga\Web\Controller as IcingaWebController;
+use Icinga\Web\Url;
+
+/**
+ * Base class for all monitoring action controller
+ */
+class Controller extends IcingaWebController
+{
+ /**
+ * The backend used for this controller
+ *
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ protected function moduleInit()
+ {
+ $this->backend = MonitoringBackend::instance($this->_getParam('backend'));
+ $this->view->url = Url::fromRequest();
+ }
+
+ protected function handleFormatRequest($query)
+ {
+ $desiredContentType = $this->getRequest()->getHeader('Accept');
+ if ($desiredContentType === 'application/json') {
+ $desiredFormat = 'json';
+ } elseif ($desiredContentType === 'text/csv') {
+ $desiredFormat = 'csv';
+ } else {
+ $desiredFormat = strtolower($this->params->get('format', 'html'));
+ }
+
+ if ($desiredFormat !== 'html' && ! $this->params->has('limit')) {
+ $query->limit(); // Resets any default limit and offset
+ }
+
+ switch ($desiredFormat) {
+ case 'sql':
+ echo '<pre>'
+ . htmlspecialchars(wordwrap($query->dump()))
+ . '</pre>';
+ exit;
+ case 'json':
+ $response = $this->getResponse();
+ $response
+ ->setHeader('Content-Type', 'application/json')
+ ->setHeader('Cache-Control', 'no-store')
+ ->setHeader(
+ 'Content-Disposition',
+ 'inline; filename=' . $this->getRequest()->getActionName() . '.json'
+ )
+ ->appendBody(
+ Json::sanitize(
+ iterator_to_array(
+ new CustomvarProtectionIterator(
+ new ArrayIterator($query->fetchAll())
+ )
+ )
+ )
+ )
+ ->sendResponse();
+ exit;
+ case 'csv':
+ $response = $this->getResponse();
+ $response
+ ->setHeader('Content-Type', 'text/csv')
+ ->setHeader('Cache-Control', 'no-store')
+ ->setHeader(
+ 'Content-Disposition',
+ 'attachment; filename=' . $this->getRequest()->getActionName() . '.csv'
+ )
+ ->appendBody((string) Csv::fromQuery(new CustomvarProtectionIterator($query)))
+ ->sendResponse();
+ exit;
+ }
+ }
+
+ /**
+ * Apply a restriction of the authenticated on the given filterable
+ *
+ * @param string $name Name of the restriction
+ * @param Filterable $filterable Filterable to restrict
+ *
+ * @return Filterable The filterable having the restriction applied
+ */
+ protected function applyRestriction($name, Filterable $filterable)
+ {
+ $filterable->applyFilter($this->getRestriction($name));
+ return $filterable;
+ }
+
+ /**
+ * Get a restriction of the authenticated
+ *
+ * @param string $name Name of the restriction
+ *
+ * @return Filter Filter object
+ * @throws ConfigurationError If the restriction contains invalid filter columns
+ */
+ protected function getRestriction($name)
+ {
+ $restriction = Filter::matchAny();
+ $restriction->setAllowedFilterColumns(array(
+ 'host_name',
+ 'hostgroup_name',
+ 'instance_name',
+ 'service_description',
+ 'servicegroup_name',
+ function ($c) {
+ return preg_match('/^_(?:host|service)_/i', $c);
+ }
+ ));
+ foreach ($this->getRestrictions($name) as $filter) {
+ if ($filter === '*') {
+ return Filter::matchAll();
+ }
+ try {
+ $restriction->addFilter(Filter::fromQueryString($filter));
+ } catch (QueryException $e) {
+ throw new ConfigurationError(
+ $this->translate(
+ 'Cannot apply restriction %s using the filter %s. You can only use the following columns: %s'
+ ),
+ $name,
+ $filter,
+ implode(', ', array(
+ 'instance_name',
+ 'host_name',
+ 'hostgroup_name',
+ 'service_description',
+ 'servicegroup_name',
+ '_(host|service)_<customvar-name>'
+ )),
+ $e
+ );
+ }
+ }
+
+ if ($restriction->isEmpty()) {
+ return Filter::matchAll();
+ }
+
+ return $restriction;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php b/modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php
new file mode 100644
index 0000000..0ad051b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Data/ColumnFilterIterator.php
@@ -0,0 +1,30 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Data;
+
+use ArrayIterator;
+use FilterIterator;
+use Zend_Db_Expr;
+
+/**
+ * Iterator over non-pseudo monitoring query columns
+ */
+class ColumnFilterIterator extends FilterIterator
+{
+ /**
+ * Create a new ColumnFilterIterator
+ *
+ * @param array $columns
+ */
+ public function __construct(array $columns)
+ {
+ parent::__construct(new ArrayIterator($columns));
+ }
+
+ public function accept(): bool
+ {
+ $column = $this->current();
+ return ! ($column instanceof Zend_Db_Expr || $column === '(NULL)');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php b/modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php
new file mode 100644
index 0000000..c3cc01a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Data/CustomvarProtectionIterator.php
@@ -0,0 +1,25 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Data;
+
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use IteratorIterator;
+
+class CustomvarProtectionIterator extends IteratorIterator
+{
+ const IS_CV_RE = '~^_(host|service)_([a-zA-Z0-9_]+)$~';
+
+ public function current(): object
+ {
+ $row = parent::current();
+
+ foreach ($row as $col => $val) {
+ if (preg_match(self::IS_CV_RE, $col, $m)) {
+ $row->$col = MonitoredObject::protectCustomVars([$m[2] => $val])[$m[2]];
+ }
+ }
+
+ return $row;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Command.php b/modules/monitoring/library/Monitoring/DataView/Command.php
new file mode 100644
index 0000000..6beb8bc
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Command.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * View representation for commands
+ */
+class Command extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'command_id',
+ 'command_instance_id',
+ 'command_config_type',
+ 'command_line',
+ 'command_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Comment.php b/modules/monitoring/library/Monitoring/DataView/Comment.php
new file mode 100644
index 0000000..3a035bc
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Comment.php
@@ -0,0 +1,82 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Host and service comments view
+ */
+class Comment extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'comment_author_name',
+ 'comment_data',
+ 'comment_expiration',
+ 'comment_internal_id',
+ 'comment_is_persistent',
+ 'comment_name',
+ 'comment_timestamp',
+ 'comment_type',
+ 'host_display_name',
+ 'host_name',
+ 'object_type',
+ 'service_description',
+ 'service_display_name',
+ 'service_host_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'comment_author',
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('host_display_name', 'service_display_name');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'comment_timestamp' => array(
+ 'order' => self::SORT_DESC
+ ),
+ 'host_display_name' => array(
+ 'columns' => array(
+ 'host_display_name',
+ 'service_display_name'
+ ),
+ 'order' => self::SORT_ASC
+ ),
+ 'service_display_name' => array(
+ 'columns' => array(
+ 'service_display_name',
+ 'host_display_name'
+ ),
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Commentevent.php b/modules/monitoring/library/Monitoring/DataView/Commentevent.php
new file mode 100644
index 0000000..316700a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Commentevent.php
@@ -0,0 +1,30 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Commentevent extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'commentevent_id',
+ 'commentevent_entry_type',
+ 'commentevent_comment_time',
+ 'commentevent_author_name',
+ 'commentevent_comment_data',
+ 'commentevent_is_persistent',
+ 'commentevent_comment_source',
+ 'commentevent_expires',
+ 'commentevent_expiration_time',
+ 'commentevent_deletion_time',
+ 'host_name',
+ 'service_description'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array('commentevent_id');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Contact.php b/modules/monitoring/library/Monitoring/DataView/Contact.php
new file mode 100644
index 0000000..986acab
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Contact.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Contact extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'contact_object_id',
+ 'contact_id',
+ 'contact_name',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ 'contact_has_host_notfications',
+ 'contact_has_service_notfications',
+ 'contact_can_submit_commands',
+ 'contact_notify_service_recovery',
+ 'contact_notify_service_warning',
+ 'contact_notify_service_critical',
+ 'contact_notify_service_unknown',
+ 'contact_notify_service_flapping',
+ 'contact_notify_service_downtime',
+ 'contact_notify_host_recovery',
+ 'contact_notify_host_down',
+ 'contact_notify_host_unreachable',
+ 'contact_notify_host_flapping',
+ 'contact_notify_host_downtime',
+ 'contact_notify_host_timeperiod',
+ 'contact_notify_service_timeperiod'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'contact_name' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'contact', 'instance_name',
+ 'contactgroup', 'contactgroup_name', 'contactgroup_alias',
+ 'host', 'host_name', 'host_display_name', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('contact_alias');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Contactgroup.php b/modules/monitoring/library/Monitoring/DataView/Contactgroup.php
new file mode 100644
index 0000000..84eecd1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Contactgroup.php
@@ -0,0 +1,57 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Contactgroup extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'contactgroup_name',
+ 'contactgroup_alias',
+ 'contact_count'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'contactgroup_name' => array(
+ 'order' => self::SORT_ASC
+ ),
+ 'contactgroup_alias' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'contactgroup',
+ 'host', 'host_name', 'host_display_name', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('contactgroup_alias');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Customvar.php b/modules/monitoring/library/Monitoring/DataView/Customvar.php
new file mode 100644
index 0000000..c02d52f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Customvar.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Customvar extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'varname',
+ 'varvalue',
+ 'is_json',
+ 'host_name',
+ 'service_description',
+ 'contact_name',
+ 'object_type',
+ 'object_type_id'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'varname' => array(
+ 'columns' => array(
+ 'varname',
+ 'varvalue'
+ )
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array('host', 'service', 'contact');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php
new file mode 100644
index 0000000..5b16e28
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/DataView.php
@@ -0,0 +1,608 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Data\Filter\FilterMatch;
+use IteratorAggregate;
+use Icinga\Application\Hook;
+use Icinga\Data\ConnectionInterface;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\FilterColumns;
+use Icinga\Data\PivotTable;
+use Icinga\Data\QueryInterface;
+use Icinga\Data\SortRules;
+use Icinga\Exception\QueryException;
+use Icinga\Module\Monitoring\Backend\Ido\Query\IdoQuery;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Web\Request;
+use Icinga\Web\Url;
+use Traversable;
+
+/**
+ * A read-only view of an underlying query
+ */
+abstract class DataView implements QueryInterface, SortRules, FilterColumns, IteratorAggregate
+{
+ /**
+ * The query used to populate the view
+ *
+ * @var IdoQuery
+ */
+ protected $query;
+
+ protected $connection;
+
+ protected $isSorted = false;
+
+ /**
+ * The cache for all filter columns
+ *
+ * @var array
+ */
+ protected $filterColumns;
+
+ /**
+ * Create a new view
+ *
+ * @param ConnectionInterface $connection
+ * @param array $columns
+ */
+ public function __construct(ConnectionInterface $connection, array $columns = null)
+ {
+ $this->connection = $connection;
+ $this->query = $connection->query($this->getQueryName(), $columns);
+ }
+
+ /**
+ * Return a iterator for all rows of the result set
+ *
+ * @return IdoQuery
+ */
+ public function getIterator(): Traversable
+ {
+ return $this->getQuery();
+ }
+
+ /**
+ * Return the current position of the result set's iterator
+ *
+ * @return int
+ */
+ public function getIteratorPosition()
+ {
+ return $this->query->getIteratorPosition();
+ }
+
+ /**
+ * Get the query name this data view relies on
+ *
+ * By default this is this class' name without its namespace
+ *
+ * @return string
+ */
+ public static function getQueryName()
+ {
+ $tableName = explode('\\', get_called_class());
+ $tableName = end($tableName);
+ return $tableName;
+ }
+
+ public function where($condition, $value = null)
+ {
+ $this->query->where($condition, $value);
+ return $this;
+ }
+
+ /**
+ * Add a filter expression, with as less validation as possible
+ *
+ * @param FilterExpression $ex
+ *
+ * @internal If you use this outside the monitoring module, it's your fault if something breaks
+ * @return $this
+ */
+ public function whereEx(FilterExpression $ex)
+ {
+ $this->query->whereEx($ex);
+ return $this;
+ }
+
+ public function dump()
+ {
+ if (! $this->isSorted) {
+ $this->order();
+ }
+ return $this->query->dump();
+ }
+
+ /**
+ * Retrieve columns provided by this view
+ *
+ * @return array
+ */
+ abstract public function getColumns();
+
+ protected function getHookedColumns()
+ {
+ $columns = array();
+ foreach (Hook::all('monitoring/dataviewExtension') as $hook) {
+ foreach ($hook->getAdditionalQueryColumns($this->getQueryName()) as $col) {
+ $columns[] = $col;
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Create view from params
+ *
+ * @param array $params
+ * @param array $columns
+ *
+ * @return static
+ */
+ public static function fromParams(array $params, array $columns = null)
+ {
+ $view = new static(MonitoringBackend::instance($params['backend']), $columns);
+
+ foreach ($params as $key => $value) {
+ if ($view->isValidFilterTarget($key)) {
+ $view->where($key, $value);
+ }
+ }
+
+ if (isset($params['sort'])) {
+ $order = isset($params['order']) ? $params['order'] : null;
+ if ($order !== null) {
+ if (strtolower($order) === 'desc') {
+ $order = self::SORT_DESC;
+ } else {
+ $order = self::SORT_ASC;
+ }
+ }
+
+ $view->order($params['sort'], $order);
+ }
+ return $view;
+ }
+
+ /**
+ * Check whether the given column is a valid filter column
+ *
+ * @param string $column
+ *
+ * @return bool
+ */
+ public function isValidFilterTarget($column)
+ {
+ // Customvar
+ if ($column[0] === '_' && preg_match('/^_(?:host|service)_/i', $column)) {
+ return true;
+ }
+ return in_array($column, $this->getColumns()) || in_array($column, $this->getStaticFilterColumns());
+ }
+
+ /**
+ * Return all filter columns with their optional label as key
+ *
+ * This will merge the results of self::getColumns(), self::getStaticFilterColumns() and
+ * self::getDynamicFilterColumns() *once*. (i.e. subsequent calls of this function will
+ * return the same result.)
+ *
+ * @return array
+ */
+ public function getFilterColumns()
+ {
+ if ($this->filterColumns === null) {
+ $columns = array_merge(
+ $this->getColumns(),
+ $this->getStaticFilterColumns(),
+ $this->getDynamicFilterColumns()
+ );
+
+ $this->filterColumns = array();
+ foreach ($columns as $label => $column) {
+ if (is_int($label)) {
+ $label = ucwords(str_replace('_', ' ', $column));
+ }
+
+ if ($this->query->isCaseInsensitive($column)) {
+ $label .= ' ' . t('(Case insensitive)');
+ }
+
+ $this->filterColumns[$label] = $column;
+ }
+ }
+
+ return $this->filterColumns;
+ }
+
+ /**
+ * Return all static filter columns
+ *
+ * @return array
+ */
+ public function getStaticFilterColumns()
+ {
+ return array();
+ }
+
+ /**
+ * Return all dynamic filter columns such as custom variables
+ *
+ * @return array
+ */
+ public function getDynamicFilterColumns()
+ {
+ $columns = array();
+ if (! $this->query->allowsCustomVars()) {
+ return $columns;
+ }
+
+ $query = MonitoringBackend::instance()
+ ->select()
+ ->from('customvar', array('varname', 'object_type'))
+ ->where('is_json', 0)
+ ->where('object_type_id', array(1, 2))
+ ->getQuery()->group(array('varname', 'object_type'));
+ foreach ($query as $row) {
+ if ($row->object_type === 'host') {
+ $label = t('Host') . ' ' . ucwords(str_replace('_', ' ', $row->varname));
+ $columns[$label] = '_host_' . $row->varname;
+ } else { // $row->object_type === 'service'
+ $label = t('Service') . ' ' . ucwords(str_replace('_', ' ', $row->varname));
+ $columns[$label] = '_service_' . $row->varname;
+ }
+ }
+
+ return $columns;
+ }
+
+ /**
+ * Return the current filter
+ *
+ * @return Filter
+ */
+ public function getFilter()
+ {
+ return $this->query->getFilter();
+ }
+
+ /**
+ * Return a pivot table for the given columns based on the current query
+ *
+ * @param string $xAxisColumn The column to use for the x axis
+ * @param string $yAxisColumn The column to use for the y axis
+ * @param Filter $xAxisFilter The filter to apply on a query for the x axis
+ * @param Filter $yAxisFilter The filter to apply on a query for the y axis
+ *
+ * @return PivotTable
+ */
+ public function pivot($xAxisColumn, $yAxisColumn, Filter $xAxisFilter = null, Filter $yAxisFilter = null)
+ {
+ $pivot = new PivotTable($this->query, $xAxisColumn, $yAxisColumn);
+ return $pivot->setXAxisFilter($xAxisFilter)->setYAxisFilter($yAxisFilter);
+ }
+
+ /**
+ * Sort result set either by the given column (and direction) or the sort defaults
+ *
+ * @param string $column
+ * @param string $direction
+ *
+ * @return $this
+ */
+ public function order($column = null, $direction = null)
+ {
+ $sortRules = $this->getSortRules();
+ if ($column === null) {
+ // Use first available sort rule as default
+ if (empty($sortRules)) {
+ return $this;
+ }
+ $sortColumns = reset($sortRules);
+ if (! isset($sortColumns['columns'])) {
+ $sortColumns['columns'] = array(key($sortRules));
+ }
+ } else {
+ if (isset($sortRules[$column])) {
+ $sortColumns = $sortRules[$column];
+ if (! isset($sortColumns['columns'])) {
+ $sortColumns['columns'] = array($column);
+ }
+ } else {
+ $sortColumns = array(
+ 'columns' => array($column),
+ 'order' => $direction
+ );
+ };
+ }
+
+ $direction = $direction === null ? ($sortColumns['order'] ?? static::SORT_ASC) : $direction;
+ $direction = (strtoupper($direction) === static::SORT_ASC) ? 'ASC' : 'DESC';
+
+ foreach ($sortColumns['columns'] as $column) {
+ list($column, $order) = $this->query->splitOrder($column);
+ if (! $this->isValidFilterTarget($column)) {
+ throw new QueryException(
+ mt('monitoring', 'The sort column "%s" is not allowed in "%s".'),
+ $column,
+ get_class($this)
+ );
+ }
+ $this->query->order($column, $order !== null ? $order : $direction);
+ }
+ $this->isSorted = true;
+ return $this;
+ }
+
+ /**
+ * Retrieve default sorting rules for particular columns. These involve sort order and potential additional to sort
+ *
+ * @return array
+ */
+ public function getSortRules()
+ {
+ return array();
+ }
+
+ /**
+ * Whether an order is set
+ *
+ * @return bool
+ */
+ public function hasOrder()
+ {
+ return $this->query->hasOrder();
+ }
+
+ /**
+ * Get the order if any
+ *
+ * @return array|null
+ */
+ public function getOrder()
+ {
+ return $this->query->getOrder();
+ }
+
+ public function getMappedField($field)
+ {
+ return $this->query->getMappedField($field);
+ }
+
+ /**
+ * Return the query which was created in the constructor
+ *
+ * @return \Icinga\Data\SimpleQuery
+ */
+ public function getQuery()
+ {
+ if (! $this->isSorted) {
+ $this->order();
+ }
+ return $this->query;
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ $this->validateFilterColumns($filter);
+
+ return $this->addFilter($filter);
+ }
+
+ /**
+ * Validates recursive the Filter columns against the isValidFilterTarget() method
+ *
+ * @param Filter $filter
+ *
+ * @throws \Icinga\Data\Filter\FilterException
+ */
+ public function validateFilterColumns(Filter $filter)
+ {
+ if ($filter instanceof FilterMatch) {
+ if (! $this->isValidFilterTarget($filter->getColumn())) {
+ throw new QueryException(
+ mt('monitoring', 'The filter column "%s" is not allowed here.'),
+ $filter->getColumn()
+ );
+ }
+ }
+
+ if (method_exists($filter, 'filters')) {
+ foreach ($filter->filters() as $filter) {
+ $this->validateFilterColumns($filter);
+ }
+ }
+ }
+
+ public function clearFilter()
+ {
+ $this->query->clearFilter();
+ return $this;
+ }
+
+ /**
+ * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
+ * column validation. Filter::matchAny() for the IdoQuery (or the DbQuery or the SimpleQuery I didn't have a look)
+ * is required for the filter to work properly.
+ */
+ public function setFilter(Filter $filter)
+ {
+ $this->query->setFilter($filter);
+ return $this;
+ }
+
+ /**
+ * Get the view's search columns
+ *
+ * @return string[]
+ */
+ public function getSearchColumns()
+ {
+ return array();
+ }
+
+ /**
+ * @deprecated(EL): Only use DataView::applyFilter() for applying filter because all other functions are missing
+ * column validation.
+ */
+ public function addFilter(Filter $filter)
+ {
+ $this->query->addFilter($filter);
+ return $this;
+ }
+
+ /**
+ * Count result set
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return $this->query->count();
+ }
+
+ /**
+ * Set whether the query should peek ahead for more results
+ *
+ * Enabling this causes the current query limit to be increased by one. The potential extra row being yielded will
+ * be removed from the result set. Note that this only applies when fetching multiple results of limited queries.
+ *
+ * @return $this
+ */
+ public function peekAhead($state = true)
+ {
+ $this->query->peekAhead($state);
+ return $this;
+ }
+
+ /**
+ * Return whether the query did not yield all available results
+ *
+ * @return bool
+ */
+ public function hasMore()
+ {
+ return $this->query->hasMore();
+ }
+
+ /**
+ * Return whether this query will or has yielded any result
+ *
+ * @return bool
+ */
+ public function hasResult()
+ {
+ return $this->query->hasResult();
+ }
+
+ /**
+ * Set a limit count and offset
+ *
+ * @param int $count Number of rows to return
+ * @param int $offset Start returning after this many rows
+ *
+ * @return self
+ */
+ public function limit($count = null, $offset = null)
+ {
+ $this->query->limit($count, $offset);
+ return $this;
+ }
+
+ /**
+ * Whether a limit is set
+ *
+ * @return bool
+ */
+ public function hasLimit()
+ {
+ return $this->query->hasLimit();
+ }
+
+ /**
+ * Get the limit if any
+ *
+ * @return int|null
+ */
+ public function getLimit()
+ {
+ return $this->query->getLimit();
+ }
+
+ /**
+ * Whether an offset is set
+ *
+ * @return bool
+ */
+ public function hasOffset()
+ {
+ return $this->query->hasOffset();
+ }
+
+ /**
+ * Get the offset if any
+ *
+ * @return int|null
+ */
+ public function getOffset()
+ {
+ return $this->query->getOffset();
+ }
+
+ /**
+ * Retrieve an array containing all rows of the result set
+ *
+ * @return array
+ */
+ public function fetchAll()
+ {
+ return $this->getQuery()->fetchAll();
+ }
+
+ /**
+ * Fetch the first row of the result set
+ *
+ * @return mixed
+ */
+ public function fetchRow()
+ {
+ return $this->getQuery()->fetchRow();
+ }
+
+ /**
+ * Fetch the first column of all rows of the result set as an array
+ *
+ * @return array
+ */
+ public function fetchColumn()
+ {
+ return $this->getQuery()->fetchColumn();
+ }
+
+ /**
+ * Fetch the first column of the first row of the result set
+ *
+ * @return string
+ */
+ public function fetchOne()
+ {
+ return $this->getQuery()->fetchOne();
+ }
+
+ /**
+ * Fetch all rows of the result set as an array of key-value pairs
+ *
+ * The first column is the key, the second column is the value.
+ *
+ * @return array
+ */
+ public function fetchPairs()
+ {
+ return $this->getQuery()->fetchPairs();
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Downtime.php b/modules/monitoring/library/Monitoring/DataView/Downtime.php
new file mode 100644
index 0000000..ca42e2d
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Downtime.php
@@ -0,0 +1,96 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Host and service downtimes view
+ */
+class Downtime extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'downtime_author_name',
+ 'downtime_comment',
+ 'downtime_duration',
+ 'downtime_end',
+ 'downtime_entry_time',
+ 'downtime_internal_id',
+ 'downtime_is_fixed',
+ 'downtime_is_flexible',
+ 'downtime_is_in_effect',
+ 'downtime_name',
+ 'downtime_scheduled_end',
+ 'downtime_scheduled_start',
+ 'downtime_start',
+ 'host_display_name',
+ 'host_name',
+ 'host_state',
+ 'object_type',
+ 'service_description',
+ 'service_display_name',
+ 'service_host_name',
+ 'service_state'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'downtime_author',
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('host_display_name', 'service_display_name');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'downtime_is_in_effect' => array(
+ 'columns' => array(
+ 'downtime_is_in_effect',
+ 'downtime_scheduled_start'
+ ),
+ 'order' => self::SORT_DESC
+ ),
+ 'downtime_start' => array(
+ 'order' => self::SORT_DESC
+ ),
+ 'host_display_name' => array(
+ 'columns' => array(
+ 'host_display_name',
+ 'service_display_name'
+ ),
+ 'order' => self::SORT_ASC
+ ),
+ 'service_display_name' => array(
+ 'columns' => array(
+ 'service_display_name',
+ 'host_display_name'
+ ),
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Downtimeevent.php b/modules/monitoring/library/Monitoring/DataView/Downtimeevent.php
new file mode 100644
index 0000000..a1fc0f6
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Downtimeevent.php
@@ -0,0 +1,33 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Downtimeevent extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'downtimeevent_id',
+ 'downtimeevent_entry_time',
+ 'downtimeevent_author_name',
+ 'downtimeevent_comment_data',
+ 'downtimeevent_is_fixed',
+ 'downtimeevent_scheduled_start_time',
+ 'downtimeevent_scheduled_end_time',
+ 'downtimeevent_was_started',
+ 'downtimeevent_actual_start_time',
+ 'downtimeevent_actual_end_time',
+ 'downtimeevent_was_cancelled',
+ 'downtimeevent_is_in_effect',
+ 'downtimeevent_trigger_time',
+ 'host_name',
+ 'service_description'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array('downtimeevent_id');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgrid.php b/modules/monitoring/library/Monitoring/DataView/Eventgrid.php
new file mode 100644
index 0000000..1639e6b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Eventgrid.php
@@ -0,0 +1,60 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Eventgrid extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'day',
+ 'cnt_up',
+ 'cnt_down_hard',
+ 'cnt_down',
+ 'cnt_unreachable_hard',
+ 'cnt_unreachable',
+ 'cnt_unknown_hard',
+ 'cnt_unknown',
+ 'cnt_critical',
+ 'cnt_critical_hard',
+ 'cnt_warning',
+ 'cnt_warning_hard',
+ 'cnt_ok',
+ 'host_name',
+ 'host_display_name',
+ 'service_description',
+ 'service_display_name',
+ 'timestamp'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'day' => array(
+ 'order' => self::SORT_DESC
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_host_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php b/modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php
new file mode 100644
index 0000000..9d9acc9
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Eventgridhosts.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Eventgridhosts extends Eventgrid
+{
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Eventgridservices.php b/modules/monitoring/library/Monitoring/DataView/Eventgridservices.php
new file mode 100644
index 0000000..faa1065
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Eventgridservices.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Eventgridservices extends Eventgrid
+{
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Eventhistory.php b/modules/monitoring/library/Monitoring/DataView/Eventhistory.php
new file mode 100644
index 0000000..cd947f5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Eventhistory.php
@@ -0,0 +1,60 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class EventHistory extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'id',
+ 'instance_name',
+ 'host_name',
+ 'host_display_name',
+ 'service_description',
+ 'service_display_name',
+ 'object_type',
+ 'timestamp',
+ 'state',
+ 'output',
+ 'type'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'timestamp' => array(
+ 'order' => self::SORT_DESC
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('host_display_name', 'service_display_name');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Flappingevent.php b/modules/monitoring/library/Monitoring/DataView/Flappingevent.php
new file mode 100644
index 0000000..bc79497
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Flappingevent.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Flappingevent extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'flappingevent_id',
+ 'flappingevent_event_time',
+ 'flappingevent_event_type',
+ 'flappingevent_reason_type',
+ 'flappingevent_percent_state_change',
+ 'flappingevent_low_threshold',
+ 'flappingevent_high_threshold',
+ 'host_name',
+ 'service_description'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array('flappingevent_id');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hostcomment.php b/modules/monitoring/library/Monitoring/DataView/Hostcomment.php
new file mode 100644
index 0000000..74fc2ef
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hostcomment.php
@@ -0,0 +1,45 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Host comment view
+ */
+class Hostcomment extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'comment_author',
+ 'comment_author_name',
+ 'comment_data',
+ 'comment_expiration',
+ 'comment_internal_id',
+ 'comment_is_persistent',
+ 'comment_name',
+ 'comment_timestamp',
+ 'comment_type',
+ 'host_display_name',
+ 'host_name',
+ 'object_type'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hostcontact.php b/modules/monitoring/library/Monitoring/DataView/Hostcontact.php
new file mode 100644
index 0000000..ecfed2f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hostcontact.php
@@ -0,0 +1,17 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Hostcontact extends Contact
+{
+ public function getColumns()
+ {
+ return [
+ 'contact_name',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager'
+ ];
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hostdowntime.php b/modules/monitoring/library/Monitoring/DataView/Hostdowntime.php
new file mode 100644
index 0000000..f5e4e80
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hostdowntime.php
@@ -0,0 +1,50 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Host downtime view
+ */
+class Hostdowntime extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'downtime_author',
+ 'downtime_author_name',
+ 'downtime_comment',
+ 'downtime_duration',
+ 'downtime_end',
+ 'downtime_entry_time',
+ 'downtime_internal_id',
+ 'downtime_is_fixed',
+ 'downtime_is_flexible',
+ 'downtime_is_in_effect',
+ 'downtime_name',
+ 'downtime_scheduled_end',
+ 'downtime_scheduled_start',
+ 'downtime_start',
+ 'host_display_name',
+ 'host_name',
+ 'object_type'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hostgroup.php b/modules/monitoring/library/Monitoring/DataView/Hostgroup.php
new file mode 100644
index 0000000..b204fcd
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hostgroup.php
@@ -0,0 +1,34 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Host group data view
+ */
+class Hostgroup extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'hostgroup_alias',
+ 'hostgroup_name'
+ );
+ }
+
+ public function getSortRules()
+ {
+ return array(
+ 'hostgroup_alias' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name', 'host_name', 'service_description', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php
new file mode 100644
index 0000000..9ed2eb9
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hostgroupsummary.php
@@ -0,0 +1,81 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Data view for the host group summary
+ */
+class Hostgroupsummary extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_pending',
+ 'hosts_severity',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_up',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ );
+ }
+
+ public function getSearchColumns()
+ {
+ return array('hostgroup', 'hostgroup_alias');
+ }
+
+ public function getSortRules()
+ {
+ return array(
+ 'hostgroup_alias' => array(
+ 'order' => self::SORT_ASC
+ ),
+ 'hosts_severity' => array(
+ 'columns' => array(
+ 'hosts_severity',
+ 'hostgroup_alias ASC'
+ ),
+ 'order' => self::SORT_DESC
+ )
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host_contact', 'host_contactgroup', 'host_name',
+ 'hostgroup',
+ 'service_description',
+ 'servicegroup_name'
+ );
+ }
+
+ public function getFilterColumns()
+ {
+ if ($this->filterColumns === null) {
+ $filterColumns = parent::getFilterColumns();
+ $diff = array_diff($filterColumns, $this->getColumns());
+ $this->filterColumns = array_merge($diff, [
+ 'Hostgroup Name' => 'hostgroup_name',
+ 'Hostgroup Alias' => 'hostgroup_alias'
+ ]);
+ }
+
+ return $this->filterColumns;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
new file mode 100644
index 0000000..6440fe5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
@@ -0,0 +1,129 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class HostStatus extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array_merge($this->getHookedColumns(), array(
+ 'host_acknowledged',
+ 'host_acknowledgement_type',
+ 'host_action_url',
+ 'host_active_checks_enabled',
+ 'host_active_checks_enabled_changed',
+ 'host_address',
+ 'host_address6',
+ 'host_alias',
+ 'host_check_command',
+ 'host_check_execution_time',
+ 'host_check_latency',
+ 'host_check_source',
+ 'host_check_timeperiod',
+ 'host_current_check_attempt',
+ 'host_current_notification_number',
+ 'host_display_name',
+ 'host_event_handler_enabled',
+ 'host_event_handler_enabled_changed',
+ 'host_flap_detection_enabled',
+ 'host_flap_detection_enabled_changed',
+ 'host_handled',
+ 'host_hard_state',
+ 'host_in_downtime',
+ 'host_ipv4',
+ 'host_is_flapping',
+ 'host_is_reachable',
+ 'host_last_check',
+ 'host_last_notification',
+ 'host_last_state_change',
+ 'host_last_state_change_ts',
+ 'host_long_output',
+ 'host_max_check_attempts',
+ 'host_modified_host_attributes',
+ 'host_name',
+ 'host_next_check',
+ 'host_notes_url',
+ 'host_notifications_enabled',
+ 'host_notifications_enabled_changed',
+ 'host_obsessing',
+ 'host_obsessing_changed',
+ 'host_output',
+ 'host_passive_checks_enabled',
+ 'host_passive_checks_enabled_changed',
+ 'host_percent_state_change',
+ 'host_perfdata',
+ 'host_problem',
+ 'host_severity',
+ 'host_state',
+ 'host_state_type',
+ 'host_unhandled',
+ 'instance_name'
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host', 'host_contact', 'host_contactgroup',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns($search = null)
+ {
+ if ($search !== null
+ && (@inet_pton($search) !== false || preg_match('/^\d{1,3}\.\d{1,3}\./', $search))
+ ) {
+ return array('host', 'host_address', 'host_address6');
+ } else {
+ if ($this->connection->isIcinga2()) {
+ return array('host', 'host_display_name');
+ } else {
+ return array('host', 'host_display_name', 'host_alias');
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'host_display_name' => array(
+ 'order' => self::SORT_ASC
+ ),
+ 'host_severity' => array(
+ 'columns' => array(
+ 'host_severity',
+ 'host_last_state_change_ts DESC'
+ ),
+ 'order' => self::SORT_DESC
+ ),
+ 'host_address' => array(
+ 'columns' => array(
+ 'host_ipv4'
+ ),
+ 'order' => self::SORT_ASC
+ ),
+ 'host_last_state_change' => array(
+ 'columns' => array(
+ 'host_last_state_change_ts'
+ ),
+ 'order' => self::SORT_DESC
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php b/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php
new file mode 100644
index 0000000..a857466
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Hoststatussummary.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Data view for host status summaries
+ */
+class Hoststatussummary extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_pending',
+ 'hosts_total',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_up',
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host', 'host_alias', 'host_display_name', 'host_name',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Instance.php b/modules/monitoring/library/Monitoring/DataView/Instance.php
new file mode 100644
index 0000000..98ef1d6
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Instance.php
@@ -0,0 +1,33 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * View representation for instances
+ */
+class Instance extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'instance_id',
+ 'instance_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'instance_name' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Notification.php b/modules/monitoring/library/Monitoring/DataView/Notification.php
new file mode 100644
index 0000000..90755de
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Notification.php
@@ -0,0 +1,59 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Notification extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'host_display_name',
+ 'host_name',
+ 'notification_contact_name',
+ 'notification_output',
+ 'notification_reason',
+ 'notification_state',
+ 'notification_timestamp',
+ 'object_type',
+ 'service_description',
+ 'service_display_name',
+ 'service_host_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'notification_timestamp' => array(
+ 'order' => self::SORT_DESC
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'hostgroup_name',
+ 'instance_name',
+ 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('host_display_name', 'service_display_name');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Notificationevent.php b/modules/monitoring/library/Monitoring/DataView/Notificationevent.php
new file mode 100644
index 0000000..82dd212
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Notificationevent.php
@@ -0,0 +1,29 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Notificationevent extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'notificationevent_id',
+ 'notificationevent_reason',
+ 'notificationevent_start_time',
+ 'notificationevent_end_time',
+ 'notificationevent_state',
+ 'notificationevent_output',
+ 'notificationevent_long_output',
+ 'notificationevent_escalated',
+ 'notificationevent_contacts_notified',
+ 'host_name',
+ 'service_description'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array('notificationevent_id');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Programstatus.php b/modules/monitoring/library/Monitoring/DataView/Programstatus.php
new file mode 100644
index 0000000..d611c72
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Programstatus.php
@@ -0,0 +1,44 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * View for programstatus query
+ */
+class Programstatus extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'id',
+ 'status_update_time',
+ 'program_start_time',
+ 'program_end_time',
+ 'is_currently_running',
+ 'process_id',
+ 'daemon_mode',
+ 'last_command_check',
+ 'last_log_rotation',
+ 'notifications_enabled',
+ 'disable_notif_expire_time',
+ 'active_service_checks_enabled',
+ 'passive_service_checks_enabled',
+ 'active_host_checks_enabled',
+ 'passive_host_checks_enabled',
+ 'event_handlers_enabled',
+ 'flap_detection_enabled',
+ 'failure_prediction_enabled',
+ 'process_performance_data',
+ 'obsess_over_hosts',
+ 'obsess_over_services',
+ 'modified_host_attributes',
+ 'modified_service_attributes',
+ 'global_host_event_handler',
+ 'global_service_event_handler',
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Runtimesummary.php b/modules/monitoring/library/Monitoring/DataView/Runtimesummary.php
new file mode 100644
index 0000000..bf80226
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Runtimesummary.php
@@ -0,0 +1,38 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * View for runtimesummary query
+ */
+class Runtimesummary extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'check_type',
+ 'active_checks_enabled',
+ 'passive_checks_enabled',
+ 'execution_time',
+ 'latency',
+ 'object_count',
+ 'object_type'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'active_checks_enabled' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Runtimevariables.php b/modules/monitoring/library/Monitoring/DataView/Runtimevariables.php
new file mode 100644
index 0000000..b3624b7
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Runtimevariables.php
@@ -0,0 +1,34 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * View for runtimevariables query
+ */
+class Runtimevariables extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'id',
+ 'varname',
+ 'varvalue'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'id' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicecomment.php b/modules/monitoring/library/Monitoring/DataView/Servicecomment.php
new file mode 100644
index 0000000..78c1333
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicecomment.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Service comment view
+ */
+class Servicecomment extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'comment_author',
+ 'comment_author_name',
+ 'comment_data',
+ 'comment_expiration',
+ 'comment_internal_id',
+ 'comment_is_persistent',
+ 'comment_name',
+ 'comment_timestamp',
+ 'comment_type',
+ 'host_display_name',
+ 'host_name',
+ 'object_type',
+ 'service_description',
+ 'service_display_name',
+ 'service_host_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicecontact.php b/modules/monitoring/library/Monitoring/DataView/Servicecontact.php
new file mode 100644
index 0000000..55c9950
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicecontact.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Servicecontact extends Hostcontact
+{
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicedowntime.php b/modules/monitoring/library/Monitoring/DataView/Servicedowntime.php
new file mode 100644
index 0000000..43d895e
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicedowntime.php
@@ -0,0 +1,50 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Servicedowntime extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'downtime_author',
+ 'downtime_author_name',
+ 'downtime_comment',
+ 'downtime_duration',
+ 'downtime_end',
+ 'downtime_entry_time',
+ 'downtime_internal_id',
+ 'downtime_is_fixed',
+ 'downtime_is_flexible',
+ 'downtime_is_in_effect',
+ 'downtime_name',
+ 'downtime_scheduled_end',
+ 'downtime_scheduled_start',
+ 'downtime_start',
+ 'host_display_name',
+ 'host_name',
+ 'object_type',
+ 'service_description',
+ 'service_display_name',
+ 'service_host_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host', 'host_alias',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'instance_name',
+ 'service',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicegroup.php b/modules/monitoring/library/Monitoring/DataView/Servicegroup.php
new file mode 100644
index 0000000..9909a68
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicegroup.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Servicegroup extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'servicegroup_alias',
+ 'servicegroup_name'
+ );
+ }
+
+ public function getSortRules()
+ {
+ return array(
+ 'servicegroup_alias' => array(
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name', 'host_name', 'hostgroup_name', 'service_description'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
new file mode 100644
index 0000000..9dc3ee0
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicegroupsummary.php
@@ -0,0 +1,75 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Data view for service group summaries
+ */
+class Servicegroupsummary extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'servicegroup_alias',
+ 'servicegroup_name',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_severity',
+ 'services_total',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ );
+ }
+
+ public function getSearchColumns()
+ {
+ return array('servicegroup', 'servicegroup_alias');
+ }
+
+ public function getSortRules()
+ {
+ return array(
+ 'servicegroup_alias' => array(
+ 'order' => self::SORT_ASC
+ ),
+ 'services_severity' => array(
+ 'columns' => array(
+ 'services_severity',
+ 'servicegroup_alias ASC'
+ ),
+ 'order' => self::SORT_DESC
+ )
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'services_severity',
+ 'host_contact', 'host_contactgroup', 'host_name',
+ 'hostgroup_name',
+ 'service_contact', 'service_contactgroup', 'service_description',
+ 'servicegroup'
+ );
+ }
+
+ public function getFilterColumns()
+ {
+ if ($this->filterColumns === null) {
+ $filterColumns = parent::getFilterColumns();
+ $diff = array_diff($filterColumns, $this->getColumns());
+ $this->filterColumns = array_merge($diff, [
+ 'Servicegroup Name' => 'servicegroup_name',
+ 'Servicegroup Alias' => 'servicegroup_alias'
+ ]);
+ }
+
+ return $this->filterColumns;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
new file mode 100644
index 0000000..e80c6f0
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
@@ -0,0 +1,180 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class ServiceStatus extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array_merge($this->getHookedColumns(), array(
+ 'host_acknowledged',
+ 'host_action_url',
+ 'host_active_checks_enabled',
+ 'host_address',
+ 'host_address6',
+ 'host_alias',
+ 'host_check_source',
+ 'host_display_name',
+ 'host_handled',
+ 'host_hard_state',
+ 'host_in_downtime',
+ 'host_ipv4',
+ 'host_is_flapping',
+ 'host_last_check',
+ 'host_last_hard_state',
+ 'host_last_hard_state_change',
+ 'host_last_state_change',
+ 'host_last_time_down',
+ 'host_last_time_unreachable',
+ 'host_last_time_up',
+ 'host_long_output',
+ 'host_modified_host_attributes',
+ 'host_name',
+ 'host_notes_url',
+ 'host_notifications_enabled',
+ 'host_output',
+ 'host_passive_checks_enabled',
+ 'host_perfdata',
+ 'host_problem',
+ 'host_severity',
+ 'host_state',
+ 'host_state_type',
+ 'host_unhandled_service_count',
+ 'instance_name',
+ 'service_acknowledged',
+ 'service_acknowledgement_type',
+ 'service_action_url',
+ 'service_active_checks_enabled',
+ 'service_active_checks_enabled_changed',
+ 'service_attempt',
+ 'service_check_command',
+ 'service_check_source',
+ 'service_check_timeperiod',
+ 'service_current_check_attempt',
+ 'service_current_notification_number',
+ 'service_description',
+ 'service_display_name',
+ 'service_event_handler_enabled',
+ 'service_event_handler_enabled_changed',
+ 'service_flap_detection_enabled',
+ 'service_flap_detection_enabled_changed',
+ 'service_handled',
+ 'service_hard_state',
+ 'service_host_name',
+ 'service_in_downtime',
+ 'service_is_flapping',
+ 'service_is_reachable',
+ 'service_last_check',
+ 'service_last_hard_state',
+ 'service_last_hard_state_change',
+ 'service_last_notification',
+ 'service_last_state_change',
+ 'service_last_state_change_ts',
+ 'service_last_time_critical',
+ 'service_last_time_ok',
+ 'service_last_time_unknown',
+ 'service_last_time_warning',
+ 'service_long_output',
+ 'service_max_check_attempts',
+ 'service_modified_service_attributes',
+ 'service_next_check',
+ 'service_notes',
+ 'service_notes_url',
+ 'service_notifications_enabled',
+ 'service_notifications_enabled_changed',
+ 'service_obsessing',
+ 'service_obsessing_changed',
+ 'service_output',
+ 'service_passive_checks_enabled',
+ 'service_passive_checks_enabled_changed',
+ 'service_perfdata',
+ 'service_problem',
+ 'service_severity',
+ 'service_state',
+ 'service_state_type',
+ 'service_unhandled'
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSortRules()
+ {
+ return array(
+ 'service_display_name' => array(
+ 'order' => self::SORT_ASC
+ ),
+ 'service_severity' => array(
+ 'columns' => array(
+ 'service_severity',
+ 'service_last_state_change_ts DESC'
+ ),
+ 'order' => self::SORT_DESC
+ ),
+ 'service_last_state_change' => array(
+ 'columns' => array(
+ 'service_last_state_change_ts'
+ ),
+ 'order' => self::SORT_DESC
+ ),
+ 'host_severity' => array(
+ 'columns' => array(
+ 'host_severity',
+ 'host_last_state_change DESC',
+ 'host_display_name ASC',
+ 'service_display_name ASC'
+ ),
+ 'order' => self::SORT_DESC
+ ),
+ 'host_display_name' => array(
+ 'columns' => array(
+ 'host_display_name',
+ 'service_display_name'
+ ),
+ 'order' => self::SORT_ASC
+ ),
+ 'host_address' => array(
+ 'columns' => array(
+ 'host_ipv4',
+ 'service_display_name'
+ ),
+ 'order' => self::SORT_ASC
+ )
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'host',
+ 'host_contact',
+ 'host_contactgroup',
+ 'hostgroup',
+ 'hostgroup_alias',
+ 'hostgroup_name',
+ 'service',
+ 'service_contact',
+ 'service_contactgroup',
+ 'service_host',
+ 'servicegroup',
+ 'servicegroup_alias',
+ 'servicegroup_name'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSearchColumns()
+ {
+ return array('service', 'service_display_name');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php b/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php
new file mode 100644
index 0000000..abd3593
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Servicestatussummary.php
@@ -0,0 +1,45 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Data view for service status summaries
+ */
+class Servicestatussummary extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'services_critical',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_ok',
+ 'services_pending',
+ 'services_total',
+ 'services_unknown',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_warning',
+ 'services_warning_handled',
+ 'services_warning_unhandled'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host', 'host_alias', 'host_display_name', 'host_name',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Statechangeevent.php b/modules/monitoring/library/Monitoring/DataView/Statechangeevent.php
new file mode 100644
index 0000000..0b01aff
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Statechangeevent.php
@@ -0,0 +1,32 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class Statechangeevent extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'statechangeevent_id',
+ 'statechangeevent_state_time',
+ 'statechangeevent_state_change',
+ 'statechangeevent_state',
+ 'statechangeevent_state_type',
+ 'statechangeevent_current_check_attempt',
+ 'statechangeevent_max_check_attempts',
+ 'statechangeevent_last_state',
+ 'statechangeevent_last_hard_state',
+ 'statechangeevent_output',
+ 'statechangeevent_long_output',
+ 'statechangeevent_check_source',
+ 'host_name',
+ 'service_description'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array('statechangeevent_id');
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Statussummary.php b/modules/monitoring/library/Monitoring/DataView/Statussummary.php
new file mode 100644
index 0000000..36efccb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Statussummary.php
@@ -0,0 +1,111 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+class StatusSummary extends DataView
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getColumns()
+ {
+ return array(
+ 'hosts_up',
+ 'hosts_up_not_checked',
+ 'hosts_pending',
+ 'hosts_pending_not_checked',
+ 'hosts_down',
+ 'hosts_down_handled',
+ 'hosts_down_unhandled',
+ 'hosts_down_passive',
+ 'hosts_down_not_checked',
+ 'hosts_unreachable',
+ 'hosts_unreachable_handled',
+ 'hosts_unreachable_unhandled',
+ 'hosts_unreachable_passive',
+ 'hosts_unreachable_not_checked',
+ 'hosts_active',
+ 'hosts_passive',
+ 'hosts_not_checked',
+ 'hosts_not_processing_event_handlers',
+ 'hosts_not_triggering_notifications',
+ 'hosts_without_flap_detection',
+ 'hosts_flapping',
+ 'services_ok',
+ 'services_ok_not_checked',
+ 'services_pending',
+ 'services_pending_not_checked',
+ 'services_warning',
+ 'services_warning_handled',
+ 'services_warning_unhandled',
+ 'services_warning_passive',
+ 'services_warning_not_checked',
+ 'services_critical',
+ 'services_critical_handled',
+ 'services_critical_unhandled',
+ 'services_critical_passive',
+ 'services_critical_not_checked',
+ 'services_unknown',
+ 'services_unknown_handled',
+ 'services_unknown_unhandled',
+ 'services_unknown_passive',
+ 'services_unknown_not_checked',
+ 'services_active',
+ 'services_passive',
+ 'services_not_checked',
+ 'services_not_processing_event_handlers',
+ 'services_not_triggering_notifications',
+ 'services_without_flap_detection',
+ 'services_flapping',
+
+
+ 'services_ok_on_ok_hosts',
+ 'services_ok_not_checked_on_ok_hosts',
+ 'services_pending_on_ok_hosts',
+ 'services_pending_not_checked_on_ok_hosts',
+ 'services_warning_handled_on_ok_hosts',
+ 'services_warning_unhandled_on_ok_hosts',
+ 'services_warning_passive_on_ok_hosts',
+ 'services_warning_not_checked_on_ok_hosts',
+ 'services_critical_handled_on_ok_hosts',
+ 'services_critical_unhandled_on_ok_hosts',
+ 'services_critical_passive_on_ok_hosts',
+ 'services_critical_not_checked_on_ok_hosts',
+ 'services_unknown_handled_on_ok_hosts',
+ 'services_unknown_unhandled_on_ok_hosts',
+ 'services_unknown_passive_on_ok_hosts',
+ 'services_unknown_not_checked_on_ok_hosts',
+ 'services_ok_on_problem_hosts',
+ 'services_ok_not_checked_on_problem_hosts',
+ 'services_pending_on_problem_hosts',
+ 'services_pending_not_checked_on_problem_hosts',
+ 'services_warning_handled_on_problem_hosts',
+ 'services_warning_unhandled_on_problem_hosts',
+ 'services_warning_passive_on_problem_hosts',
+ 'services_warning_not_checked_on_problem_hosts',
+ 'services_critical_handled_on_problem_hosts',
+ 'services_critical_unhandled_on_problem_hosts',
+ 'services_critical_passive_on_problem_hosts',
+ 'services_critical_not_checked_on_problem_hosts',
+ 'services_unknown_handled_on_problem_hosts',
+ 'services_unknown_unhandled_on_problem_hosts',
+ 'services_unknown_passive_on_problem_hosts',
+ 'services_unknown_not_checked_on_problem_hosts'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host', 'host_alias', 'host_display_name', 'host_name',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php b/modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php
new file mode 100644
index 0000000..4f5f392
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Unhandledhostproblems.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Data view for unhandled host problems
+ */
+class Unhandledhostproblems extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'hosts_down_unhandled'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host', 'host_alias', 'host_display_name', 'host_name',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php b/modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php
new file mode 100644
index 0000000..3af4502
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/DataView/Unhandledserviceproblems.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\DataView;
+
+/**
+ * Data view for unhandled service problems
+ */
+class Unhandledserviceproblems extends DataView
+{
+ public function getColumns()
+ {
+ return array(
+ 'services_critical_unhandled'
+ );
+ }
+
+ public function getStaticFilterColumns()
+ {
+ return array(
+ 'instance_name',
+ 'host', 'host_alias', 'host_display_name', 'host_name',
+ 'hostgroup', 'hostgroup_alias', 'hostgroup_name',
+ 'service', 'service_description', 'service_display_name',
+ 'servicegroup', 'servicegroup_alias', 'servicegroup_name'
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Exception/CommandTransportException.php b/modules/monitoring/library/Monitoring/Exception/CommandTransportException.php
new file mode 100644
index 0000000..5c08351
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Exception/CommandTransportException.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Exception;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown if a command was not sent
+ */
+class CommandTransportException extends IcingaException
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Exception/CurlException.php b/modules/monitoring/library/Monitoring/Exception/CurlException.php
new file mode 100644
index 0000000..01757af
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Exception/CurlException.php
@@ -0,0 +1,13 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Exception;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Exception thrown if {@link curl_exec()} fails
+ */
+class CurlException extends IcingaException
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php b/modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php
new file mode 100644
index 0000000..94d1af2
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Exception/UnsupportedBackendException.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Module\Monitoring\Exception;
+
+use Icinga\Exception\IcingaException;
+
+class UnsupportedBackendException extends IcingaException
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php b/modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php
new file mode 100644
index 0000000..700bfd5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/CustomVarRendererHook.php
@@ -0,0 +1,98 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Closure;
+use Exception;
+use Icinga\Application\Hook;
+use Icinga\Application\Logger;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+abstract class CustomVarRendererHook
+{
+ /**
+ * Prefetch the data the hook needs to render custom variables
+ *
+ * @param MonitoredObject $object The object for which they'll be rendered
+ *
+ * @return bool Return true if the hook can render variables for the given object, false otherwise
+ */
+ abstract public function prefetchForObject(MonitoredObject $object);
+
+ /**
+ * Render the given variable name
+ *
+ * @param string $key
+ *
+ * @return ?mixed
+ */
+ abstract public function renderCustomVarKey($key);
+
+ /**
+ * Render the given variable value
+ *
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return ?mixed
+ */
+ abstract public function renderCustomVarValue($key, $value);
+
+ /**
+ * Return a group name for the given variable name
+ *
+ * @param string $key
+ *
+ * @return ?string
+ */
+ abstract public function identifyCustomVarGroup($key);
+
+ /**
+ * Prepare available hooks to render custom variables of the given object
+ *
+ * @param MonitoredObject $object
+ *
+ * @return Closure A callback ($key, $value) which returns an array [$newKey, $newValue, $group]
+ */
+ final public static function prepareForObject(MonitoredObject $object)
+ {
+ $hooks = [];
+ foreach (Hook::all('Monitoring/CustomVarRenderer') as $hook) {
+ /** @var self $hook */
+ try {
+ if ($hook->prefetchForObject($object)) {
+ $hooks[] = $hook;
+ }
+ } catch (Exception $e) {
+ Logger::error('Failed to load hook %s:', get_class($hook), $e);
+ }
+ }
+
+ return function ($key, $value) use ($hooks, $object) {
+ $newKey = $key;
+ $newValue = $value;
+ $group = null;
+ foreach ($hooks as $hook) {
+ /** @var self $hook */
+
+ try {
+ $renderedKey = $hook->renderCustomVarKey($key);
+ $renderedValue = $hook->renderCustomVarValue($key, $value);
+ $group = $hook->identifyCustomVarGroup($key);
+ } catch (Exception $e) {
+ Logger::error('Failed to use hook %s:', get_class($hook), $e);
+ continue;
+ }
+
+ if ($renderedKey !== null || $renderedValue !== null) {
+ $newKey = $renderedKey !== null ? $renderedKey : $key;
+ $newValue = $renderedValue !== null ? $renderedValue : $value;
+ break;
+ }
+ }
+
+ return [$newKey, $newValue, $group];
+ };
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php
new file mode 100644
index 0000000..24b97c5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/DataviewExtensionHook.php
@@ -0,0 +1,20 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+abstract class DataviewExtensionHook
+{
+ public function getAdditionalQueryColumns($queryName)
+ {
+ $cols = $this->provideAdditionalQueryColumns($queryName);
+
+ if (! is_array($cols)) {
+ return array();
+ }
+
+ return $cols;
+ }
+
+ abstract public function provideAdditionalQueryColumns($queryName);
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php
new file mode 100644
index 0000000..9eb5ca3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/DetailviewExtensionHook.php
@@ -0,0 +1,126 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Application\ClassLoader;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Module;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Module\Monitoring\Object\ObjectList;
+use Icinga\Web\View;
+
+/**
+ * Base class for hooks extending the detail view of monitored objects
+ *
+ * Extend this class if you want to extend the detail view of monitored objects with custom HTML.
+ */
+abstract class DetailviewExtensionHook
+{
+ /**
+ * The view the generated HTML will be included in
+ *
+ * @var View
+ */
+ private $view;
+
+ /**
+ * The module of the derived class
+ *
+ * @var Module
+ */
+ private $module;
+
+ /**
+ * Create a new hook
+ *
+ * @see init() For hook initialization.
+ */
+ final public function __construct()
+ {
+ $this->init();
+ }
+
+ /**
+ * Overwrite this function for hook initialization, e.g. loading the hook's config
+ */
+ protected function init()
+ {
+ }
+
+ /**
+ * Shall return valid HTML to include in the detail view
+ *
+ * @param MonitoredObject $object The object to generate HTML for
+ *
+ * @return string
+ */
+ abstract public function getHtmlForObject(MonitoredObject $object);
+
+ /**
+ * Shall return valid HTML to include in the detail view of a multi-select view
+ *
+ * @param ObjectList $objects A list of objects shown in the multi-select view
+ *
+ * @return string
+ */
+ public function getHtmlForObjects($objects)
+ {
+ // For compatibility empty by default
+ return '';
+ }
+
+ /**
+ * Get {@link view}
+ *
+ * @return View
+ */
+ public function getView()
+ {
+ return $this->view;
+ }
+
+ /**
+ * Set {@link view}
+ *
+ * @param View $view
+ *
+ * @return $this
+ */
+ public function setView($view)
+ {
+ $this->view = $view;
+ return $this;
+ }
+
+ /**
+ * Get the module of the derived class
+ *
+ * @return Module
+ */
+ public function getModule()
+ {
+ if ($this->module === null) {
+ $class = get_class($this);
+ if (ClassLoader::classBelongsToModule($class)) {
+ $this->module = Icinga::app()->getModuleManager()->getModule(ClassLoader::extractModuleName($class));
+ }
+ }
+
+ return $this->module;
+ }
+
+ /**
+ * Set the module of the derived class
+ *
+ * @param Module $module
+ *
+ * @return $this
+ */
+ public function setModule(Module $module)
+ {
+ $this->module = $module;
+
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php
new file mode 100644
index 0000000..e0375d5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/EventDetailsExtensionHook.php
@@ -0,0 +1,79 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Application\ClassLoader;
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Module;
+
+/**
+ * Base class for hooks extending the event view of monitored objects
+ *
+ * Extend this class if you want to extend the event view of monitored objects with custom HTML.
+ */
+abstract class EventDetailsExtensionHook
+{
+ /**
+ * The module of the derived class
+ *
+ * @var Module
+ */
+ private $module;
+
+ /**
+ * Create a new hook
+ *
+ * @see init() For hook initialization.
+ */
+ final public function __construct()
+ {
+ $this->init();
+ }
+ /**
+ * Overwrite this function for hook initialization, e.g. loading the hook's config
+ */
+ protected function init()
+ {
+ }
+
+
+ /**
+ * Shall return valid HTML to include in the detail view
+ *
+ * @param object $event The object to generate HTML for
+ *
+ * @return string
+ */
+ abstract public function getHtmlForEvent($event);
+
+ /**
+ * Get the module of the derived class
+ *
+ * @return Module
+ * @throws \Icinga\Exception\ProgrammingError
+ */
+ public function getModule()
+ {
+ if ($this->module === null) {
+ $class = get_class($this);
+ if (ClassLoader::classBelongsToModule($class)) {
+ $this->module = Icinga::app()->getModuleManager()->getModule(ClassLoader::extractModuleName($class));
+ }
+ }
+ return $this->module;
+ }
+
+ /**
+ * Set the module of the derived class
+ *
+ * @param Module $module
+ *
+ * @return $this
+ */
+ public function setModule(Module $module)
+ {
+ $this->module = $module;
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php
new file mode 100644
index 0000000..def0090
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/HostActionsHook.php
@@ -0,0 +1,52 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Module\Monitoring\Object\Host;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Base class for host action hooks
+ */
+abstract class HostActionsHook extends ObjectActionsHook
+{
+ /**
+ * Implementors of this method should return an array containing
+ * additional action links for a specific host. You get a full Host
+ * object, which allows you to return specific links only for nodes
+ * with specific properties.
+ *
+ * The result array should be in the form title => url, where title will
+ * be used as link caption. Url should be an Icinga\Web\Url object when
+ * the link should point to an Icinga Web url - otherwise a string would
+ * be fine.
+ *
+ * Mixed example:
+ * <code>
+ * return array(
+ * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($host->host_name),
+ * 'Logstash' => Url::fromPath(
+ * 'logstash/search/syslog',
+ * array('host' => $host->host_name)
+ * )
+ * );
+ * </code>
+ *
+ * One might also provide ssh:// or rdp:// urls if equipped with fitting
+ * (safe) URL handlers for his browser(s).
+ *
+ * TODO: I'd love to see some kind of a Link/LinkSet object implemented
+ * for this and similar hooks.
+ *
+ * @param Host $host Monitoring host object
+ *
+ * @return array An array containing a list of host action links
+ */
+ abstract public function getActionsForHost(Host $host);
+
+ public function getActionsForObject(MonitoredObject $object)
+ {
+ return $this->getActionsForHost($object);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php b/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php
new file mode 100644
index 0000000..64ac65c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/IdoQueryExtensionHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Module\Monitoring\Backend\Ido\Query\IdoQuery;
+
+abstract class IdoQueryExtensionHook
+{
+ abstract public function extendColumnMap(IdoQuery $query);
+
+ public function joinVirtualTable(IdoQuery $query, $virtualTable)
+ {
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php b/modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php
new file mode 100644
index 0000000..eb2d910
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/ObjectActionsHook.php
@@ -0,0 +1,47 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Base class for object action hooks
+ */
+abstract class ObjectActionsHook
+{
+ /**
+ * Return the action navigation for the given object
+ *
+ * @return Navigation
+ */
+ public function getNavigation(MonitoredObject $object)
+ {
+ $urls = $this->getActionsForObject($object);
+ if (is_array($urls)) {
+ $navigation = new Navigation();
+ foreach ($urls as $label => $url) {
+ $navigation->addItem($label, array('url' => $url));
+ }
+ } else {
+ $navigation = $urls;
+ }
+
+ return $navigation;
+ }
+
+ /**
+ * Create and return a new Navigation object
+ *
+ * @param array $actions Optional array of actions to add to the returned object
+ *
+ * @return Navigation
+ */
+ protected function createNavigation(array $actions = null)
+ {
+ return empty($actions) ? new Navigation() : Navigation::fromArray($actions);
+ }
+
+ abstract public function getActionsForObject(MonitoredObject $object);
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php b/modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php
new file mode 100644
index 0000000..15fa9bb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/ObjectDetailsTabHook.php
@@ -0,0 +1,60 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Authentication\Auth;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Web\Request;
+
+/**
+ * Base class for object host details custom tab hooks
+ */
+abstract class ObjectDetailsTabHook
+{
+ /**
+ * Return the tab name - it must be unique
+ *
+ * @return string
+ */
+ abstract public function getName();
+
+ /**
+ * Return the tab label
+ *
+ * @return string
+ */
+ abstract public function getLabel();
+
+ /**
+ * Return the tab header
+ *
+ * @param MonitoredObject $monitoredObject The monitored object related to that page
+ * @param Request $request
+ * @return string/bool The HTML string that compose the tab header,
+ * bool True if the default header should be shown, False to display nothing
+ */
+ public function getHeader(MonitoredObject $monitoredObject, Request $request)
+ {
+ return true;
+ }
+
+ /**
+ * Return the tab content
+ *
+ * @param MonitoredObject $monitoredObject The monitored object related to that page
+ * @param Request $request
+ * @return string The HTML string that compose the tab content
+ */
+ abstract public function getContent(MonitoredObject $monitoredObject, Request $request);
+
+ /**
+ * This method returns true if the tab is visible for the logged user, otherwise false
+ *
+ * @return bool True if the tab is visible for the logged user, otherwise false
+ */
+ public function shouldBeShown(MonitoredObject $monitoredObject, Auth $auth)
+ {
+ return true;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php b/modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php
new file mode 100644
index 0000000..52ecd09
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/PluginOutputHook.php
@@ -0,0 +1,46 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+/**
+ * Base class for plugin output hooks
+ *
+ * The Plugin Output Hook allows you to rewrite the plugin output based on check commands.
+ * You have to implement the following methods:
+ * * {@link getCommands()}
+ * * and {@link render()}
+ */
+abstract class PluginOutputHook
+{
+ /**
+ * Get the command or list of commands the hook is responsible for
+ *
+ * With this method you specify for which commands the provided hook is responsible for. You may return a single
+ * command as string or a list of commands as array.
+ * If you want your hook to be responsible for every command, you have to return the asterisk `'*'`.
+ *
+ * @return string|array
+ */
+ abstract public function getCommands();
+
+ /**
+ * Render the given plugin output based on the specified check command
+ *
+ * With this method you rewrite the plugin output based on check commands. The parameter `$command` specifies the
+ * check command of the host or service and `$output` specifies the plugin output. The parameter `$detail` tells you
+ * whether the output is requested from the detail area of the host or service.
+ *
+ * Do not use complex logic for rewriting plugin output in list views because of the performance impact!
+ *
+ * You have to return the rewritten plugin output as string. It is also possible to return a HTML string here.
+ * Please refer to {@link \Icinga\Module\Monitoring\Web\Helper\PluginOutputPurifier} for a list of allowed tags.
+ *
+ * @param string $command Check command
+ * @param string $output Plugin output
+ * @param bool $detail Whether the output is requested from the detail area
+ *
+ * @return string Rewritten plugin output
+ */
+ abstract public function render($command, $output, $detail);
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php
new file mode 100644
index 0000000..c6cf5f5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/ServiceActionsHook.php
@@ -0,0 +1,52 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Module\Monitoring\Object\Service;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+
+/**
+ * Base class for host action hooks
+ */
+abstract class ServiceActionsHook extends ObjectActionsHook
+{
+ /**
+ * Implementors of this method should return an array containing
+ * additional action links for a specific host. You get a full Service
+ * object, which allows you to return specific links only for nodes
+ * with specific properties.
+ *
+ * The result array should be in the form title => url, where title will
+ * be used as link caption. Url should be an Icinga\Web\Url object when
+ * the link should point to an Icinga Web url - otherwise a string would
+ * be fine.
+ *
+ * Mixed example:
+ * <code>
+ * return array(
+ * 'Wiki' => 'http://my.wiki/host=' . rawurlencode($service->service_name),
+ * 'Logstash' => Url::fromPath(
+ * 'logstash/search/syslog',
+ * array('service' => $service->host_name)
+ * )
+ * );
+ * </code>
+ *
+ * One might also provide ssh:// or rdp:// urls if equipped with fitting
+ * (safe) URL handlers for his browser(s).
+ *
+ * TODO: I'd love to see some kind of a Link/LinkSet object implemented
+ * for this and similar hooks.
+ *
+ * @param Service $service Monitoring service object
+ *
+ * @return array An array containing a list of service action links
+ */
+ abstract public function getActionsForService(Service $service);
+
+ public function getActionsForObject(MonitoredObject $object)
+ {
+ return $this->getActionsForService($object);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php
new file mode 100644
index 0000000..d302d12
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Hook/TimelineProviderHook.php
@@ -0,0 +1,37 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Hook;
+
+use Icinga\Module\Monitoring\Timeline\TimeRange;
+
+/**
+ * Base class for TimeLine providers
+ */
+abstract class TimelineProviderHook
+{
+ /**
+ * Return the names by which to group entries
+ *
+ * @return array An array with the names as keys and their attribute-lists as values
+ */
+ abstract public function getIdentifiers();
+
+ /**
+ * Return the visible entries supposed to be shown on the timeline
+ *
+ * @param TimeRange $range The range of time for which to fetch entries
+ *
+ * @return array The entries to display on the timeline
+ */
+ abstract public function fetchEntries(TimeRange $range);
+
+ /**
+ * Return the entries supposed to be used to calculate forecasts
+ *
+ * @param TimeRange $range The range of time for which to fetch forecasts
+ *
+ * @return array The entries to calculate forecasts with
+ */
+ abstract public function fetchForecasts(TimeRange $range);
+}
diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php
new file mode 100644
index 0000000..51ead8a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php
@@ -0,0 +1,159 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring;
+
+use Icinga\Web\Form;
+use Icinga\Web\Wizard;
+use Icinga\Web\Request;
+use Icinga\Module\Setup\Setup;
+use Icinga\Module\Setup\SetupWizard;
+use Icinga\Module\Setup\RequirementSet;
+use Icinga\Module\Setup\Forms\SummaryPage;
+use Icinga\Module\Monitoring\Forms\Setup\WelcomePage;
+use Icinga\Module\Monitoring\Forms\Setup\SecurityPage;
+use Icinga\Module\Monitoring\Forms\Setup\TransportPage;
+use Icinga\Module\Monitoring\Forms\Setup\IdoResourcePage;
+use Icinga\Module\Setup\Requirement\PhpModuleRequirement;
+
+/**
+ * Monitoring Module Setup Wizard
+ */
+class MonitoringWizard extends Wizard implements SetupWizard
+{
+ /**
+ * Register all pages for this wizard
+ */
+ public function init()
+ {
+ $this->addPage(new WelcomePage());
+ $this->addPage(new IdoResourcePage());
+ $this->addPage(new TransportPage());
+ $this->addPage(new SecurityPage());
+ $this->addPage(new SummaryPage(array('name' => 'setup_monitoring_summary')));
+ }
+
+ /**
+ * Setup the given page that is either going to be displayed or validated
+ *
+ * @param Form $page The page to setup
+ * @param Request $request The current request
+ */
+ public function setupPage(Form $page, Request $request)
+ {
+ if ($page->getName() === 'setup_requirements') {
+ $page->setRequirements($this->getRequirements());
+ } elseif ($page->getName() === 'setup_monitoring_summary') {
+ $page->setSummary($this->getSetup()->getSummary());
+ $page->setSubjectTitle(mt('monitoring', 'the monitoring module', 'setup.summary.subject'));
+ } elseif ($this->getDirection() === static::FORWARD
+ && ($page->getName() === 'setup_monitoring_ido')
+ ) {
+ if ((($authDbResourceData = $this->getPageData('setup_auth_db_resource')) !== null
+ && $authDbResourceData['name'] === $request->getPost('name'))
+ || (($configDbResourceData = $this->getPageData('setup_config_db_resource')) !== null
+ && $configDbResourceData['name'] === $request->getPost('name'))
+ || (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null
+ && $ldapResourceData['name'] === $request->getPost('name'))
+ ) {
+ $page->error(mt('monitoring', 'The given resource name is already in use.'));
+ }
+ }
+ }
+
+ /**
+ * Add buttons to the given page based on its position in the page-chain
+ *
+ * @param Form $page The page to add the buttons to
+ *
+ * @todo This is never called, because its a sub-wizard only
+ * @todo This is missing the ´transport_validation´ case
+ * @see WebWizard::addButtons which does some of the needed work
+ */
+ protected function addButtons(Form $page)
+ {
+ parent::addButtons($page);
+
+ $pages = $this->getPages();
+ $index = array_search($page, $pages, true);
+ if ($index === 0) {
+ // Used t() here as "Start" is too generic and already translated in the icinga domain
+ $page->getElement(static::BTN_NEXT)->setLabel(t('Start', 'setup.welcome.btn.next'));
+ } elseif ($index === count($pages) - 1) {
+ $page->getElement(static::BTN_NEXT)->setLabel(
+ mt('monitoring', 'Setup the monitoring module for Icinga Web 2', 'setup.summary.btn.finish')
+ );
+ }
+
+ if ($page->getName() === 'setup_monitoring_ido') {
+ $page->addElement(
+ 'submit',
+ 'backend_validation',
+ array(
+ 'ignore' => true,
+ 'label' => t('Validate Configuration'),
+ 'data-progress-label' => t('Validation In Progress'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));
+ }
+ }
+
+ /**
+ * Return the setup for this wizard
+ *
+ * @return Setup
+ */
+ public function getSetup()
+ {
+ $pageData = $this->getPageData();
+ $setup = new Setup();
+
+ $setup->addStep(
+ new BackendStep(array(
+ 'backendConfig' => ['name' => 'icinga', 'type' => 'ido'],
+ 'resourceConfig' => array_diff_key(
+ $pageData['setup_monitoring_ido'], //TODO: Prefer a new backend once implemented.
+ array('skip_validation' => null)
+ )
+ ))
+ );
+
+ $setup->addStep(
+ new TransportStep(array(
+ 'transportConfig' => $pageData['setup_command_transport']
+ ))
+ );
+
+ $setup->addStep(
+ new SecurityStep(array(
+ 'securityConfig' => $pageData['setup_monitoring_security']
+ ))
+ );
+
+ return $setup;
+ }
+
+ /**
+ * Return the requirements of this wizard
+ *
+ * @return RequirementSet
+ */
+ public function getRequirements()
+ {
+ $set = new RequirementSet();
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'curl',
+ 'alias' => 'cURL',
+ 'description' => mt(
+ 'monitoring',
+ 'To send external commands over Icinga 2\'s API the cURL module for PHP is required.'
+ )
+ )));
+
+ return $set;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Acknowledgement.php b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php
new file mode 100644
index 0000000..3cd0d20
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Acknowledgement.php
@@ -0,0 +1,215 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use InvalidArgumentException;
+use Traversable;
+use Icinga\Util\StringHelper;
+
+/**
+ * Acknowledgement of a host or service incident
+ */
+class Acknowledgement
+{
+ /**
+ * Author of the acknowledgement
+ *
+ * @var string
+ */
+ protected $author;
+
+ /**
+ * Comment of the acknowledgement
+ *
+ * @var string
+ */
+ protected $comment;
+
+ /**
+ * Entry time of the acknowledgement
+ *
+ * @var int
+ */
+ protected $entryTime;
+
+ /**
+ * Expiration time of the acknowledgment
+ *
+ * @var int|null
+ */
+ protected $expirationTime;
+
+ /**
+ * Whether the acknowledgement is sticky
+ *
+ * Sticky acknowledgements suppress notifications until the host or service recovers
+ *
+ * @var bool
+ */
+ protected $sticky = false;
+
+ /**
+ * Create a new acknowledgement of a host or service incident
+ *
+ * @param array|object|Traversable $properties
+ *
+ * @throws InvalidArgumentException If the type of the given properties is invalid
+ */
+ public function __construct($properties = null)
+ {
+ if ($properties !== null) {
+ $this->setProperties($properties);
+ }
+ }
+
+ /**
+ * Get the author of the acknowledgement
+ *
+ * @return string
+ */
+ public function getAuthor()
+ {
+ return $this->author;
+ }
+
+ /**
+ * Set the author of the acknowledgement
+ *
+ * @param string $author
+ *
+ * @return $this
+ */
+ public function setAuthor($author)
+ {
+ $this->author = (string) $author;
+ return $this;
+ }
+
+ /**
+ * Get the comment of the acknowledgement
+ *
+ * @return string
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Set the comment of the acknowledgement
+ *
+ * @param string $comment
+ *
+ * @return $this
+ */
+ public function setComment($comment)
+ {
+ $this->comment = (string) $comment;
+
+ return $this;
+ }
+
+ /**
+ * Get the entry time of the acknowledgement
+ *
+ * @return int
+ */
+ public function getEntryTime()
+ {
+ return $this->entryTime;
+ }
+
+ /**
+ * Set the entry time of the acknowledgement
+ *
+ * @param int $entryTime
+ *
+ * @return $this
+ */
+ public function setEntryTime($entryTime)
+ {
+ $this->entryTime = (int) $entryTime;
+
+ return $this;
+ }
+
+ /**
+ * Get the expiration time of the acknowledgement
+ *
+ * @return int|null
+ */
+ public function getExpirationTime()
+ {
+ return $this->expirationTime;
+ }
+
+ /**
+ * Set the expiration time of the acknowledgement
+ *
+ * @param int|null $expirationTime Unix timestamp
+ *
+ * @return $this
+ */
+ public function setExpirationTime($expirationTime = null)
+ {
+ $this->expirationTime = $expirationTime !== null ? (int) $expirationTime : null;
+
+ return $this;
+ }
+
+ /**
+ * Get whether the acknowledgement is sticky
+ *
+ * @return bool
+ */
+ public function getSticky()
+ {
+ return $this->sticky;
+ }
+
+ /**
+ * Set whether the acknowledgement is sticky
+ *
+ * @param bool $sticky
+ *
+ * @return $this
+ */
+ public function setSticky($sticky = true)
+ {
+ $this->sticky = (bool) $sticky;
+ return $this;
+ }
+
+ /**
+ * Get whether the acknowledgement expires
+ *
+ * @return bool
+ */
+ public function expires()
+ {
+ return $this->expirationTime !== null;
+ }
+
+ /**
+ * Set the properties of the acknowledgement
+ *
+ * @param array|object|Traversable $properties
+ *
+ * @return $this
+ * @throws InvalidArgumentException If the type of the given properties is invalid
+ */
+ public function setProperties($properties)
+ {
+ if (! is_array($properties) && ! is_object($properties) && ! $properties instanceof Traversable) {
+ throw new InvalidArgumentException('Properties must be either an array or an instance of Traversable');
+ }
+ foreach ($properties as $name => $value) {
+ $setter = 'set' . ucfirst(StringHelper::cname($name));
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ }
+ }
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php
new file mode 100644
index 0000000..dfb25ed
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Host.php
@@ -0,0 +1,204 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\Filter\FilterEqual;
+use InvalidArgumentException;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+
+/**
+ * An Icinga host
+ */
+class Host extends MonitoredObject
+{
+ /**
+ * Host state 'UP'
+ */
+ const STATE_UP = 0;
+
+ /**
+ * Host state 'DOWN'
+ */
+ const STATE_DOWN = 1;
+
+ /**
+ * Host state 'UNREACHABLE'
+ */
+ const STATE_UNREACHABLE = 2;
+
+ /**
+ * Host state 'PENDING'
+ */
+ const STATE_PENDING = 99;
+
+ /**
+ * Type of the Icinga host
+ *
+ * @var string
+ */
+ public $type = self::TYPE_HOST;
+
+ /**
+ * Prefix of the Icinga host
+ *
+ * @var string
+ */
+ public $prefix = 'host_';
+
+ /**
+ * Hostname
+ *
+ * @var string
+ */
+ protected $host;
+
+ /**
+ * The services running on the hosts
+ *
+ * @var \Icinga\Module\Monitoring\Object\Service[]
+ */
+ protected $services;
+
+ /**
+ * Create a new host
+ *
+ * @param MonitoringBackend $backend Backend to fetch host information from
+ * @param string $host Hostname
+ */
+ public function __construct(MonitoringBackend $backend, $host)
+ {
+ parent::__construct($backend);
+ $this->host = $host;
+ }
+
+ /**
+ * Get the hostname
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Get the data view to fetch the host information from
+ *
+ * @return \Icinga\Module\Monitoring\DataView\HostStatus
+ */
+ protected function getDataView()
+ {
+ $columns = array(
+ 'host_acknowledged',
+ 'host_acknowledgement_type',
+ 'host_action_url',
+ 'host_active_checks_enabled',
+ 'host_active_checks_enabled_changed',
+ 'host_address',
+ 'host_address6',
+ 'host_alias',
+ 'host_attempt',
+ 'host_check_command',
+ 'host_check_execution_time',
+ 'host_check_interval',
+ 'host_check_latency',
+ 'host_check_source',
+ 'host_check_timeperiod',
+ 'host_current_check_attempt',
+ 'host_current_notification_number',
+ 'host_display_name',
+ 'host_event_handler_enabled',
+ 'host_event_handler_enabled_changed',
+ 'host_flap_detection_enabled',
+ 'host_flap_detection_enabled_changed',
+ 'host_handled',
+ 'host_icon_image',
+ 'host_icon_image_alt',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_is_reachable',
+ 'host_last_check',
+ 'host_last_notification',
+ 'host_last_state_change',
+ 'host_long_output',
+ 'host_max_check_attempts',
+ 'host_name',
+ 'host_next_check',
+ 'host_next_update',
+ 'host_notes',
+ 'host_notes_url',
+ 'host_notifications_enabled',
+ 'host_notifications_enabled_changed',
+ 'host_obsessing',
+ 'host_obsessing_changed',
+ 'host_output',
+ 'host_passive_checks_enabled',
+ 'host_passive_checks_enabled_changed',
+ 'host_percent_state_change',
+ 'host_perfdata',
+ 'host_process_perfdata' => 'host_process_performance_data',
+ 'host_state',
+ 'host_state_type',
+ 'instance_name'
+ );
+ return $this->backend->select()->from('hoststatus', $columns)
+ ->whereEx(new FilterEqual('host_name', '=', $this->host));
+ }
+
+ /**
+ * Fetch the services running on the host
+ *
+ * @return $this
+ */
+ public function fetchServices()
+ {
+ $services = array();
+ foreach ($this->backend->select()->from('servicestatus', array('service_description'))
+ ->where('host_name', $this->host)
+ ->applyFilter($this->getFilter())
+ ->getQuery() as $service) {
+ $services[] = new Service($this->backend, $this->host, $service->service_description);
+ }
+ $this->services = $services;
+ return $this;
+ }
+
+ /**
+ * Get the optional translated textual representation of a host state
+ *
+ * @param int $state
+ * @param bool $translate
+ *
+ * @return string
+ * @throws InvalidArgumentException If the host state is not valid
+ */
+ public static function getStateText($state, $translate = false)
+ {
+ $translate = (bool) $translate;
+ switch ((int) $state) {
+ case self::STATE_UP:
+ $text = $translate ? mt('monitoring', 'UP') : 'up';
+ break;
+ case self::STATE_DOWN:
+ $text = $translate ? mt('monitoring', 'DOWN') : 'down';
+ break;
+ case self::STATE_UNREACHABLE:
+ $text = $translate ? mt('monitoring', 'UNREACHABLE') : 'unreachable';
+ break;
+ case self::STATE_PENDING:
+ $text = $translate ? mt('monitoring', 'PENDING') : 'pending';
+ break;
+ default:
+ throw new InvalidArgumentException('Invalid host state \'%s\'', $state);
+ }
+ return $text;
+ }
+
+ public function getNotesUrls()
+ {
+ return $this->resolveAllStrings(
+ MonitoredObject::parseAttributeUrls($this->host_notes_url)
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/HostList.php b/modules/monitoring/library/Monitoring/Object/HostList.php
new file mode 100644
index 0000000..8b1947d
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/HostList.php
@@ -0,0 +1,133 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use Icinga\Util\StringHelper;
+
+/**
+ * A host list
+ */
+class HostList extends ObjectList
+{
+ protected $dataViewName = 'hoststatus';
+
+ protected $columns = array('host_name');
+
+ protected function fetchObjects()
+ {
+ $hosts = array();
+ $query = $this->backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter)
+ ->getQuery()->getSelectQuery()->query();
+ foreach ($query as $row) {
+ /** @var object $row */
+ $host = new Host($this->backend, $row->host_name);
+ $host->setProperties($row);
+ $hosts[] = $host;
+ }
+ return $hosts;
+ }
+
+ /**
+ * Create a state summary of all hosts that can be consumed by hostssummary.phtml
+ *
+ * @return SimpleQuery
+ */
+ public function getStateSummary()
+ {
+ $hostStates = array_fill_keys(self::getHostStatesSummaryEmpty(), 0);
+ foreach ($this as $host) {
+ $unhandled = (bool) $host->problem === true && (bool) $host->handled === false;
+
+ $stateName = 'hosts_' . $host::getStateText($host->state);
+ ++$hostStates[$stateName];
+ ++$hostStates[$stateName. ($unhandled ? '_unhandled' : '_handled')];
+ }
+
+ $hostStates['hosts_total'] = count($this);
+
+ $ds = new ArrayDatasource(array((object) $hostStates));
+ return $ds->select();
+ }
+
+ /**
+ * Return an empty array with all possible host state names
+ *
+ * @return array An array containing all possible host states as keys and 0 as values.
+ */
+ public static function getHostStatesSummaryEmpty()
+ {
+ return StringHelper::cartesianProduct(
+ array(
+ array('hosts'),
+ array(
+ Host::getStateText(Host::STATE_UP),
+ Host::getStateText(Host::STATE_DOWN),
+ Host::getStateText(Host::STATE_UNREACHABLE),
+ Host::getStateText(Host::STATE_PENDING)
+ ),
+ array(null, 'handled', 'unhandled')
+ ),
+ '_'
+ );
+ }
+
+ /**
+ * Returns a Filter that matches all hosts in this list
+ *
+ * @return Filter
+ */
+ public function objectsFilter($columns = array('host' => 'host'))
+ {
+ $filterExpression = array();
+ foreach ($this as $host) {
+ $filterExpression[] = Filter::where($columns['host'], $host->getName());
+ }
+ return FilterOr::matchAny($filterExpression);
+ }
+
+ /**
+ * Get the comments
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Hostcomment
+ */
+ public function getComments()
+ {
+ return $this->backend
+ ->select()
+ ->from('hostcomment', array('host_name'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * Get the scheduled downtimes
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Hostdowntime
+ */
+ public function getScheduledDowntimes()
+ {
+ return $this->backend
+ ->select()
+ ->from('hostdowntime', array('host_name'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getUnacknowledgedObjects()
+ {
+ $unhandledObjects = array();
+ foreach ($this as $object) {
+ if (! in_array((int) $object->state, array(0, 99)) &&
+ (bool) $object->host_acknowledged === false) {
+ $unhandledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($unhandledObjects);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Macro.php b/modules/monitoring/library/Monitoring/Object/Macro.php
new file mode 100644
index 0000000..3f67154
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Macro.php
@@ -0,0 +1,82 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Exception;
+use Icinga\Application\Logger;
+
+/**
+ * Expand macros in string in the context of MonitoredObjects
+ */
+class Macro
+{
+ /**
+ * Known icinga macros
+ *
+ * @var array
+ */
+ private static $icingaMacros = array(
+ 'HOSTNAME' => 'host_name',
+ 'HOSTADDRESS' => 'host_address',
+ 'HOSTADDRESS6' => 'host_address6',
+ 'SERVICEDESC' => 'service_description',
+ 'host.name' => 'host_name',
+ 'host.address' => 'host_address',
+ 'host.address6' => 'host_address6',
+ 'service.description' => 'service_description',
+ 'service.name' => 'service_description'
+ );
+
+ /**
+ * Return the given string with macros being resolved
+ *
+ * @param string $input The string in which to look for macros
+ * @param MonitoredObject|stdClass $object The host or service used to resolve macros
+ *
+ * @return string The substituted or unchanged string
+ */
+ public static function resolveMacros($input, $object)
+ {
+ $matches = array();
+ if (preg_match_all('@\$([^\$\s]+)\$@', $input, $matches)) {
+ foreach ($matches[1] as $key => $value) {
+ $newValue = self::resolveMacro($value, $object);
+ if ($newValue !== $value) {
+ $input = str_replace($matches[0][$key], $newValue, $input);
+ }
+ }
+ }
+
+ return $input;
+ }
+
+ /**
+ * Resolve a macro based on the given object
+ *
+ * @param string $macro The macro to resolve
+ * @param MonitoredObject|stdClass $object The object used to resolve the macro
+ *
+ * @return string The new value or the macro if it cannot be resolved
+ */
+ public static function resolveMacro($macro, $object)
+ {
+ if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) {
+ return $object->{self::$icingaMacros[$macro]};
+ }
+
+ try {
+ $value = $object->$macro;
+ } catch (Exception $e) {
+ $objectName = $object->getName();
+ if ($object instanceof Service) {
+ $objectName = $object->getHost()->getName() . '!' . $objectName;
+ }
+
+ $value = null;
+ Logger::debug('Unable to resolve macro "%s" on object "%s". An error occured: %s', $macro, $objectName, $e);
+ }
+
+ return $value !== null ? $value : $macro;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
new file mode 100644
index 0000000..91fd9e7
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
@@ -0,0 +1,930 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\Filter\FilterEqual;
+use stdClass;
+use InvalidArgumentException;
+use Icinga\Authentication\Auth;
+use Icinga\Application\Config;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use Icinga\Exception\InvalidPropertyException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Util\GlobFilter;
+use Icinga\Web\UrlParams;
+
+/**
+ * A monitored Icinga object, i.e. host or service
+ */
+abstract class MonitoredObject implements Filterable
+{
+ /**
+ * Type host
+ */
+ const TYPE_HOST = 'host';
+
+ /**
+ * Type service
+ */
+ const TYPE_SERVICE = 'service';
+
+ /**
+ * Acknowledgement of the host or service if any
+ *
+ * @var object
+ */
+ protected $acknowledgement;
+
+ /**
+ * Backend to fetch object information from
+ *
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * Comments
+ *
+ * @var array
+ */
+ protected $comments;
+
+ /**
+ * This object's obfuscated custom variables
+ *
+ * @var array
+ */
+ protected $customvars;
+
+ /**
+ * This object's obfuscated custom variables, names not lower case
+ *
+ * @var array
+ */
+ protected $customvarsWithOriginalNames;
+
+ /**
+ * The host custom variables
+ *
+ * @var array
+ */
+ protected $hostVariables;
+
+ /**
+ * The service custom variables
+ *
+ * @var array
+ */
+ protected $serviceVariables;
+
+ /**
+ * Contact groups
+ *
+ * @var array
+ */
+ protected $contactgroups;
+
+ /**
+ * Contacts
+ *
+ * @var array
+ */
+ protected $contacts;
+
+ /**
+ * Downtimes
+ *
+ * @var array
+ */
+ protected $downtimes;
+
+ /**
+ * Event history
+ *
+ * @var \Icinga\Module\Monitoring\DataView\EventHistory
+ */
+ protected $eventhistory;
+
+ /**
+ * Filter
+ *
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * Host groups
+ *
+ * @var array
+ */
+ protected $hostgroups;
+
+ /**
+ * Prefix of the Icinga object, i.e. 'host_' or 'service_'
+ *
+ * @var string
+ */
+ protected $prefix;
+
+ /**
+ * Properties
+ *
+ * @var object
+ */
+ protected $properties;
+
+ /**
+ * Service groups
+ *
+ * @var array
+ */
+ protected $servicegroups;
+
+ /**
+ * Type of the Icinga object, i.e. 'host' or 'service'
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * Stats
+ *
+ * @var object
+ */
+ protected $stats;
+
+ /**
+ * The properties to hide from the user
+ *
+ * @var GlobFilter
+ */
+ protected $blacklistedProperties = null;
+
+ /**
+ * Create a monitored object, i.e. host or service
+ *
+ * @param MonitoringBackend $backend Backend to fetch object information from
+ */
+ public function __construct(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ }
+
+ /**
+ * Get the object's data view
+ *
+ * @return \Icinga\Module\Monitoring\DataView\DataView
+ */
+ abstract protected function getDataView();
+
+ /**
+ * Get all note urls configured for this monitored object
+ *
+ * @return array All note urls as a string
+ */
+ abstract public function getNotesUrls();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addFilter(Filter $filter)
+ {
+ // Left out on purpose. Interface is deprecated.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applyFilter(Filter $filter)
+ {
+ $this->getFilter()->addFilter($filter);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFilter()
+ {
+ if ($this->filter === null) {
+ $this->filter = Filter::matchAll();
+ }
+
+ return $this->filter;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setFilter(Filter $filter)
+ {
+ // Left out on purpose. Interface is deprecated.
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function where($condition, $value = null)
+ {
+ // Left out on purpose. Interface is deprecated.
+ }
+
+ /**
+ * Require the object's type to be one of the given types
+ *
+ * @param array $oneOf
+ *
+ * @return bool
+ * @throws InvalidArgumentException If the object's type is not one of the given types.
+ */
+ public function assertOneOf(array $oneOf)
+ {
+ if (! in_array($this->type, $oneOf)) {
+ throw new InvalidArgumentException;
+ }
+ return true;
+ }
+
+ /**
+ * Fetch the object's properties
+ *
+ * @return bool
+ */
+ public function fetch()
+ {
+ $properties = $this->getDataView()->applyFilter($this->getFilter())->getQuery()->fetchRow();
+
+ if ($properties === false) {
+ return false;
+ }
+
+ if (isset($properties->host_contacts)) {
+ $this->contacts = array();
+ foreach (preg_split('~,~', $properties->host_contacts) as $contact) {
+ $this->contacts[] = (object) array(
+ 'contact_name' => $contact,
+ 'contact_alias' => $contact,
+ 'contact_email' => null,
+ 'contact_pager' => null,
+ );
+ }
+ }
+
+ $this->properties = $properties;
+
+ return true;
+ }
+
+ /**
+ * Fetch the object's acknowledgement
+ */
+ public function fetchAcknowledgement()
+ {
+ if ($this->comments === null) {
+ $this->fetchComments();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch the object's comments
+ *
+ * @return $this
+ */
+ public function fetchComments()
+ {
+ $commentsView = $this->backend->select()->from('comment', array(
+ 'author' => 'comment_author_name',
+ 'comment' => 'comment_data',
+ 'expiration' => 'comment_expiration',
+ 'id' => 'comment_internal_id',
+ 'name' => 'comment_name',
+ 'persistent' => 'comment_is_persistent',
+ 'timestamp' => 'comment_timestamp',
+ 'type' => 'comment_type'
+ ));
+ if ($this->type === self::TYPE_SERVICE) {
+ $commentsView
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $commentsView->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $commentsView
+ ->whereEx(new FilterEqual('comment_type', '=', ['ack', 'comment']))
+ ->whereEx(new FilterEqual('object_type', '=', $this->type));
+
+ $comments = $commentsView->fetchAll();
+
+ if ((bool) $this->properties->{$this->prefix . 'acknowledged'}) {
+ $ackCommentIdx = null;
+
+ foreach ($comments as $i => $comment) {
+ if ($comment->type === 'ack') {
+ $this->acknowledgement = new Acknowledgement(array(
+ 'author' => $comment->author,
+ 'comment' => $comment->comment,
+ 'entry_time' => $comment->timestamp,
+ 'expiration_time' => $comment->expiration,
+ 'sticky' => (int) $this->properties->{$this->prefix . 'acknowledgement_type'} === 2
+ ));
+ $ackCommentIdx = $i;
+ break;
+ }
+ }
+
+ if ($ackCommentIdx !== null) {
+ unset($comments[$ackCommentIdx]);
+ }
+ }
+
+ $this->comments = $comments;
+
+ return $this;
+ }
+
+ /**
+ * Fetch the object's contact groups
+ *
+ * @return $this
+ */
+ public function fetchContactgroups()
+ {
+ $contactsGroups = $this->backend->select()->from('contactgroup', array(
+ 'contactgroup_name',
+ 'contactgroup_alias'
+ ));
+ if ($this->type === self::TYPE_SERVICE) {
+ $contactsGroups
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $contactsGroups->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $this->contactgroups = $contactsGroups;
+ return $this;
+ }
+
+ /**
+ * Fetch the object's contacts
+ *
+ * @return $this
+ */
+ public function fetchContacts()
+ {
+ $contacts = $this->backend->select()->from("{$this->type}contact", array(
+ 'contact_name',
+ 'contact_alias',
+ 'contact_email',
+ 'contact_pager',
+ ));
+ if ($this->type === self::TYPE_SERVICE) {
+ $contacts
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $contacts->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $this->contacts = $contacts;
+ return $this;
+ }
+
+ /**
+ * Fetch this object's obfuscated custom variables
+ *
+ * @return $this
+ */
+ public function fetchCustomvars()
+ {
+
+ if ($this->type === self::TYPE_SERVICE) {
+ $this->fetchServiceVariables();
+ $customvars = $this->serviceVariables;
+ } else {
+ $this->fetchHostVariables();
+ $customvars = $this->hostVariables;
+ }
+
+ $this->customvars = $customvars;
+ $this->hideBlacklistedProperties();
+ $this->customvars = $this->obfuscateCustomVars($this->customvars, null);
+ $this->customvarsWithOriginalNames = $this->obfuscateCustomVars($this->customvarsWithOriginalNames, null);
+
+ return $this;
+ }
+
+ /**
+ * Obfuscate custom variables recursively
+ *
+ * @param stdClass|array $customvars The custom variables to obfuscate
+ *
+ * @return stdClass|array The obfuscated custom variables
+ */
+ protected function obfuscateCustomVars($customvars, $_)
+ {
+ return self::protectCustomVars($customvars);
+ }
+
+ public static function protectCustomVars($customvars)
+ {
+ $blacklist = [];
+ $blacklistPattern = '';
+
+ if (($blacklistConfig = Config::module('monitoring')->get('security', 'protected_customvars', '')) !== '') {
+ foreach (explode(',', $blacklistConfig) as $customvar) {
+ $nonWildcards = array();
+ foreach (explode('*', $customvar) as $nonWildcard) {
+ $nonWildcards[] = preg_quote($nonWildcard, '/');
+ }
+ $blacklist[] = implode('.*', $nonWildcards);
+ }
+ $blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i';
+ }
+
+ if (! $blacklistPattern) {
+ return $customvars;
+ }
+
+ $obfuscator = function ($vars) use ($blacklistPattern, &$obfuscator) {
+ $result = [];
+ foreach ($vars as $name => $value) {
+ if ($blacklistPattern && preg_match($blacklistPattern, $name)) {
+ $result[$name] = '***';
+ } elseif ($value instanceof stdClass || is_array($value)) {
+ $obfuscated = $obfuscator($value);
+ $result[$name] = $value instanceof stdClass ? (object) $obfuscated : $obfuscated;
+ } else {
+ $result[$name] = $value;
+ }
+ }
+
+ return $result;
+ };
+ $obfuscatedCustomVars = $obfuscator($customvars);
+
+ return $customvars instanceof stdClass ? (object) $obfuscatedCustomVars : $obfuscatedCustomVars;
+ }
+
+ /**
+ * Hide all blacklisted properties from the user as restricted by monitoring/blacklist/properties
+ *
+ * Currently this only affects the custom variables
+ */
+ protected function hideBlacklistedProperties()
+ {
+ if ($this->blacklistedProperties === null) {
+ $this->blacklistedProperties = new GlobFilter(
+ Auth::getInstance()->getRestrictions('monitoring/blacklist/properties')
+ );
+ }
+
+ $allProperties = $this->blacklistedProperties->removeMatching(
+ [$this->type => ['vars' => $this->customvars]]
+ );
+ $this->customvars = isset($allProperties[$this->type]['vars'])
+ ? $allProperties[$this->type]['vars']
+ : [];
+
+ $allProperties = $this->blacklistedProperties->removeMatching(
+ [$this->type => ['vars' => $this->customvarsWithOriginalNames]]
+ );
+ $this->customvarsWithOriginalNames = isset($allProperties[$this->type]['vars'])
+ ? $allProperties[$this->type]['vars']
+ : [];
+ }
+
+ /**
+ * Fetch the host custom variables related to this object
+ *
+ * @return $this
+ */
+ public function fetchHostVariables()
+ {
+ $query = $this->backend->select()->from('customvar', array(
+ 'varname',
+ 'varvalue',
+ 'is_json'
+ ))
+ ->whereEx(new FilterEqual('object_type', '=', static::TYPE_HOST))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+
+ $this->hostVariables = [];
+
+ if ($this->type === static::TYPE_HOST) {
+ $this->customvarsWithOriginalNames = [];
+ }
+
+ foreach ($query as $row) {
+ if ($row->is_json) {
+ $this->hostVariables[strtolower($row->varname)] = json_decode($row->varvalue);
+ } else {
+ $this->hostVariables[strtolower($row->varname)] = $row->varvalue;
+ }
+
+ if ($this->type === static::TYPE_HOST) {
+ $this->customvarsWithOriginalNames[$row->varname] = $this->hostVariables[strtolower($row->varname)];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch the service custom variables related to this object
+ *
+ * @return $this
+ *
+ * @throws ProgrammingError In case this object is not a service
+ */
+ public function fetchServiceVariables()
+ {
+ if ($this->type !== static::TYPE_SERVICE) {
+ throw new ProgrammingError('Cannot fetch service custom variables for non-service objects');
+ }
+
+ $query = $this->backend->select()->from('customvar', array(
+ 'varname',
+ 'varvalue',
+ 'is_json'
+ ))
+ ->whereEx(new FilterEqual('object_type', '=', static::TYPE_SERVICE))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+
+ $this->serviceVariables = [];
+ $this->customvarsWithOriginalNames = [];
+ foreach ($query as $row) {
+ if ($row->is_json) {
+ $this->customvarsWithOriginalNames[$row->varname] = json_decode($row->varvalue);
+ $this->serviceVariables[strtolower($row->varname)] = $this->customvarsWithOriginalNames[$row->varname];
+ } else {
+ $this->serviceVariables[strtolower($row->varname)] = $row->varvalue;
+ $this->customvarsWithOriginalNames[$row->varname] = $row->varvalue;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fetch the object's downtimes
+ *
+ * @return $this
+ */
+ public function fetchDowntimes()
+ {
+ $downtimes = $this->backend->select()->from('downtime', array(
+ 'author_name' => 'downtime_author_name',
+ 'comment' => 'downtime_comment',
+ 'duration' => 'downtime_duration',
+ 'end' => 'downtime_end',
+ 'entry_time' => 'downtime_entry_time',
+ 'id' => 'downtime_internal_id',
+ 'is_fixed' => 'downtime_is_fixed',
+ 'is_flexible' => 'downtime_is_flexible',
+ 'is_in_effect' => 'downtime_is_in_effect',
+ 'name' => 'downtime_name',
+ 'objecttype' => 'object_type',
+ 'scheduled_end' => 'downtime_scheduled_end',
+ 'scheduled_start' => 'downtime_scheduled_start',
+ 'start' => 'downtime_start'
+ ))
+ ->whereEx(new FilterEqual('object_type', '=', $this->type))
+ ->order('downtime_is_in_effect', 'DESC')
+ ->order('downtime_scheduled_start', 'ASC');
+ if ($this->type === self::TYPE_SERVICE) {
+ $downtimes
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service_description));
+ } else {
+ $downtimes
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+ }
+ $this->downtimes = $downtimes->getQuery()->fetchAll();
+ return $this;
+ }
+
+ /**
+ * Fetch the object's event history
+ *
+ * @return $this
+ */
+ public function fetchEventhistory()
+ {
+ $eventHistory = $this->backend
+ ->select()
+ ->from(
+ 'eventhistory',
+ array(
+ 'id',
+ 'object_type',
+ 'host_name',
+ 'host_display_name',
+ 'service_description',
+ 'service_display_name',
+ 'timestamp',
+ 'state',
+ 'output',
+ 'type'
+ )
+ )
+ ->whereEx(new FilterEqual('object_type', '=', $this->type))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+
+ if ($this->type === self::TYPE_SERVICE) {
+ $eventHistory->whereEx(
+ new FilterEqual('service_description', '=', $this->service_description)
+ );
+ }
+
+ $this->eventhistory = $eventHistory;
+ return $this;
+ }
+
+ /**
+ * Fetch the object's host groups
+ *
+ * @return $this
+ */
+ public function fetchHostgroups()
+ {
+ $this->hostgroups = $this->backend->select()
+ ->from('hostgroup', array('hostgroup_name', 'hostgroup_alias'))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name))
+ ->applyFilter($this->getFilter())
+ ->fetchPairs();
+ return $this;
+ }
+
+ /**
+ * Fetch the object's service groups
+ *
+ * @return $this
+ */
+ public function fetchServicegroups()
+ {
+ $query = $this->backend->select()
+ ->from('servicegroup', array('servicegroup_name', 'servicegroup_alias'))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host_name));
+
+ if ($this->type === self::TYPE_SERVICE) {
+ $query->whereEx(
+ new FilterEqual('service_description', '=', $this->service_description)
+ );
+ }
+
+ $this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs();
+ return $this;
+ }
+
+ /**
+ * Fetch stats
+ *
+ * @return $this
+ */
+ public function fetchStats()
+ {
+ $this->stats = $this->backend->select()->from('servicestatussummary', array(
+ 'services_total',
+ 'services_ok',
+ 'services_critical',
+ 'services_critical_unhandled',
+ 'services_critical_handled',
+ 'services_warning',
+ 'services_warning_unhandled',
+ 'services_warning_handled',
+ 'services_unknown',
+ 'services_unknown_unhandled',
+ 'services_unknown_handled',
+ 'services_pending',
+ ))
+ ->whereEx(new FilterEqual('service_host_name', '=', $this->host_name))
+ ->applyFilter($this->getFilter())
+ ->fetchRow();
+ return $this;
+ }
+
+ /**
+ * Get all action urls configured for this monitored object
+ *
+ * @return array All note urls as a string
+ */
+ public function getActionUrls()
+ {
+ return $this->resolveAllStrings(
+ MonitoredObject::parseAttributeUrls($this->action_url)
+ );
+ }
+
+ /**
+ * Get the type of the object
+ *
+ * @param bool $translate
+ *
+ * @return string
+ */
+ public function getType($translate = false)
+ {
+ if ($translate !== false) {
+ switch ($this->type) {
+ case self::TYPE_HOST:
+ $type = mt('montiroing', 'host');
+ break;
+ case self::TYPE_SERVICE:
+ $type = mt('monitoring', 'service');
+ break;
+ default:
+ throw new InvalidArgumentException('Invalid type ' . $this->type);
+ }
+ } else {
+ $type = $this->type;
+ }
+ return $type;
+ }
+
+ /**
+ * Parse the content of the action_url or notes_url attributes
+ *
+ * Find all occurences of http links, separated by whitespaces and quoted
+ * by single or double-ticks.
+ *
+ * @link http://docs.icinga.com/latest/de/objectdefinitions.html
+ *
+ * @param string $urlString A string containing one or more urls
+ * @return array Array of urls as strings
+ */
+ public static function parseAttributeUrls($urlString)
+ {
+ if (empty($urlString)) {
+ return array();
+ }
+ $links = array();
+ if (strpos($urlString, "' ") === false) {
+ $links[] = $urlString;
+ } else {
+ // parse notes-url format
+ foreach (explode("' ", $urlString) as $url) {
+ $url = strpos($url, "'") === 0 ? substr($url, 1) : $url;
+ $url = strrpos($url, "'") === strlen($url) - 1 ? substr($url, 0, strlen($url) - 1) : $url;
+ $links[] = $url;
+ }
+ }
+ return $links;
+ }
+
+ /**
+ * Fetch all available data of the object
+ *
+ * @return $this
+ */
+ public function populate()
+ {
+ $this
+ ->fetchComments()
+ ->fetchContactgroups()
+ ->fetchContacts()
+ ->fetchCustomvars()
+ ->fetchDowntimes();
+
+ // Call fetchHostgroups or fetchServicegroups depending on the object's type
+ $fetchGroups = 'fetch' . ucfirst($this->type) . 'groups';
+ $this->$fetchGroups();
+
+ return $this;
+ }
+
+ /**
+ * Resolve macros in all given strings in the current object context
+ *
+ * @param array $strs An array of urls as string
+ *
+ * @return array
+ */
+ protected function resolveAllStrings(array $strs)
+ {
+ foreach ($strs as $i => $str) {
+ $strs[$i] = Macro::resolveMacros($str, $this);
+ }
+ return $strs;
+ }
+
+ /**
+ * Set the object's properties
+ *
+ * @param object $properties
+ *
+ * @return $this
+ */
+ public function setProperties($properties)
+ {
+ $this->properties = (object) $properties;
+ return $this;
+ }
+
+ public function __isset($name)
+ {
+ if (property_exists($this->properties, $name)) {
+ return isset($this->properties->$name);
+ } elseif (property_exists($this, $name)) {
+ return isset($this->$name);
+ }
+ return false;
+ }
+
+ public function __get($name)
+ {
+ if (property_exists($this->properties, $name)) {
+ return $this->properties->$name;
+ } elseif (property_exists($this, $name)) {
+ if ($this->$name === null) {
+ $fetchMethod = 'fetch' . ucfirst($name);
+ $this->$fetchMethod();
+ }
+
+ return $this->$name;
+ } elseif (preg_match('/^_(host|service)_(.+)/i', $name, $matches)) {
+ if (strtolower($matches[1]) === static::TYPE_HOST) {
+ if ($this->hostVariables === null) {
+ $this->fetchHostVariables();
+ }
+
+ $customvars = $this->hostVariables;
+ } else {
+ if ($this->serviceVariables === null) {
+ $this->fetchServiceVariables();
+ }
+
+ $customvars = $this->serviceVariables;
+ }
+
+ $variableName = strtolower($matches[2]);
+ if (isset($customvars[$variableName])) {
+ return $customvars[$variableName];
+ }
+
+ return null; // Unknown custom variables MUST NOT throw an error
+ } elseif (in_array($name, array('contact_name', 'contactgroup_name', 'hostgroup_name', 'servicegroup_name'))) {
+ if ($name === 'contact_name') {
+ if ($this->contacts === null) {
+ $this->fetchContacts();
+ }
+
+ return array_map(function ($el) {
+ return $el->contact_name;
+ }, $this->contacts);
+ } elseif ($name === 'contactgroup_name') {
+ if ($this->contactgroups === null) {
+ $this->fetchContactgroups();
+ }
+
+ return array_map(function ($el) {
+ return $el->contactgroup_name;
+ }, $this->contactgroups);
+ } elseif ($name === 'hostgroup_name') {
+ if ($this->hostgroups === null) {
+ $this->fetchHostgroups();
+ }
+
+ return array_keys($this->hostgroups);
+ } else { // $name === 'servicegroup_name'
+ if ($this->servicegroups === null) {
+ $this->fetchServicegroups();
+ }
+
+ return array_keys($this->servicegroups);
+ }
+ } elseif (strpos($name, $this->prefix) !== 0) {
+ $propertyName = strtolower($name);
+ $prefixedName = $this->prefix . $propertyName;
+ if (property_exists($this->properties, $prefixedName)) {
+ return $this->properties->$prefixedName;
+ }
+
+ if ($this->type === static::TYPE_HOST) {
+ if ($this->hostVariables === null) {
+ $this->fetchHostVariables();
+ }
+
+ $customvars = $this->hostVariables;
+ } else { // $this->type === static::TYPE_SERVICE
+ if ($this->serviceVariables === null) {
+ $this->fetchServiceVariables();
+ }
+
+ $customvars = $this->serviceVariables;
+ }
+
+ if (isset($customvars[$propertyName])) {
+ return $customvars[$propertyName];
+ }
+ }
+
+ throw new InvalidPropertyException('Can\'t access property \'%s\'. Property does not exist.', $name);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/ObjectList.php b/modules/monitoring/library/Monitoring/Object/ObjectList.php
new file mode 100644
index 0000000..36b922a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/ObjectList.php
@@ -0,0 +1,293 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use ArrayIterator;
+use Countable;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use IteratorAggregate;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Traversable;
+
+abstract class ObjectList implements Countable, IteratorAggregate, Filterable
+{
+ /**
+ * @var string
+ */
+ protected $dataViewName;
+
+ /**
+ * @var MonitoringBackend
+ */
+ protected $backend;
+
+ /**
+ * @var array
+ */
+ protected $columns;
+
+ /**
+ * @var Filter
+ */
+ protected $filter;
+
+ /**
+ * @var array
+ */
+ protected $objects;
+
+ /**
+ * @var int
+ */
+ protected $count;
+
+ public function __construct(MonitoringBackend $backend)
+ {
+ $this->backend = $backend;
+ }
+
+ /**
+ * @param array $columns
+ *
+ * @return $this
+ */
+ public function setColumns(array $columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * @param Filter $filter
+ *
+ * @return $this
+ */
+ public function setFilter(Filter $filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * @return Filter
+ */
+ public function getFilter()
+ {
+ if ($this->filter === null) {
+ $this->filter = Filter::matchAll();
+ }
+
+ return $this->filter;
+ }
+
+ public function applyFilter(Filter $filter)
+ {
+ $this->getFilter()->addFilter($filter);
+ return $this;
+ }
+
+ public function addFilter(Filter $filter)
+ {
+ $this->getFilter()->addFilter($filter);
+ }
+
+ public function where($condition, $value = null)
+ {
+ $this->getFilter()->addFilter(Filter::where($condition, $value));
+ }
+
+ abstract protected function fetchObjects();
+
+ /**
+ * @return array
+ */
+ public function fetch()
+ {
+ if ($this->objects === null) {
+ $this->objects = $this->fetchObjects();
+ }
+ return $this->objects;
+ }
+
+ public function count(): int
+ {
+ if ($this->count === null) {
+ $this->count = (int) $this->backend
+ ->select()
+ ->from($this->dataViewName, $this->columns)
+ ->applyFilter($this->filter)
+ ->getQuery()
+ ->count();
+ }
+
+ return $this->count;
+ }
+
+ public function getIterator(): Traversable
+ {
+ if ($this->objects === null) {
+ $this->fetch();
+ }
+ return new ArrayIterator($this->objects);
+ }
+
+ /**
+ * Get the comments
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Comment
+ */
+ public function getComments()
+ {
+ return $this->backend->select()->from('comment')->applyFilter($this->filter);
+ }
+
+ /**
+ * Get the scheduled downtimes
+ *
+ * @return type
+ */
+ public function getScheduledDowntimes()
+ {
+ return $this->backend->select()->from('downtime')->applyFilter($this->filter);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getAcknowledgedObjects()
+ {
+ $acknowledgedObjects = array();
+ foreach ($this as $object) {
+ if ((bool) $object->acknowledged === true) {
+ $acknowledgedObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($acknowledgedObjects);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getObjectsInDowntime()
+ {
+ $objectsInDowntime = array();
+ foreach ($this as $object) {
+ if ((bool) $object->in_downtime === true) {
+ $objectsInDowntime[] = $object;
+ }
+ }
+ return $this->newFromArray($objectsInDowntime);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getUnhandledObjects()
+ {
+ $unhandledObjects = array();
+ foreach ($this as $object) {
+ if ((bool) $object->problem === true && (bool) $object->handled === false) {
+ $unhandledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($unhandledObjects);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getProblemObjects()
+ {
+ $handledObjects = array();
+ foreach ($this as $object) {
+ if ((bool) $object->problem === true) {
+ $handledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($handledObjects);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ abstract public function getUnacknowledgedObjects();
+
+ /**
+ * Create a ObjectList from an array of hosts without querying a backend
+ *
+ * @return ObjectList
+ */
+ protected function newFromArray(array $objects)
+ {
+ $class = get_called_class();
+ $list = new $class($this->backend);
+ $list->objects = $objects;
+ $list->count = count($objects);
+ $list->filter = $list->objectsFilter();
+ return $list;
+ }
+
+ /**
+ * Create a filter that matches exactly the elements of this object list
+ *
+ * @param array $columns Override default column names.
+ *
+ * @return Filter
+ */
+ abstract public function objectsFilter($columns = array());
+
+ /**
+ * Get the feature status
+ *
+ * @return array
+ */
+ public function getFeatureStatus()
+ {
+ // null - init
+ // 0 - disabled
+ // 1 - enabled
+ // 2 - enabled & disabled
+ $featureStatus = array(
+ 'active_checks_enabled' => null,
+ 'passive_checks_enabled' => null,
+ 'obsessing' => null,
+ 'notifications_enabled' => null,
+ 'event_handler_enabled' => null,
+ 'flap_detection_enabled' => null
+ );
+
+ $features = array();
+
+ foreach ($featureStatus as $feature => &$status) {
+ $features[$feature] = &$status;
+ }
+
+ foreach ($this as $object) {
+ foreach ($features as $feature => &$status) {
+ $enabled = (int) $object->{$feature};
+ if (! isset($status)) {
+ $status = $enabled;
+ } elseif ($status !== $enabled) {
+ $status = 2;
+ unset($features[$status]);
+ if (empty($features)) {
+ break 2;
+ }
+ break;
+ }
+ }
+ }
+
+ return $featureStatus;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php
new file mode 100644
index 0000000..95c00fc
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/Service.php
@@ -0,0 +1,219 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\Filter\FilterEqual;
+use InvalidArgumentException;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+
+/**
+ * An Icinga service
+ */
+class Service extends MonitoredObject
+{
+ /**
+ * Service state 'OK'
+ */
+ const STATE_OK = 0;
+
+ /**
+ * Service state 'WARNING'
+ */
+ const STATE_WARNING = 1;
+
+ /**
+ * Service state 'CRITICAL'
+ */
+ const STATE_CRITICAL = 2;
+
+ /**
+ * Service state 'UNKNOWN'
+ */
+ const STATE_UNKNOWN = 3;
+
+ /**
+ * Service state 'PENDING'
+ */
+ const STATE_PENDING = 99;
+
+ /**
+ * Type of the Icinga service
+ *
+ * @var string
+ */
+ public $type = self::TYPE_SERVICE;
+
+ /**
+ * Prefix of the Icinga service
+ *
+ * @var string
+ */
+ public $prefix = 'service_';
+
+ /**
+ * Host the service is running on
+ *
+ * @var Host
+ */
+ protected $host;
+
+ /**
+ * Service name
+ *
+ * @var string
+ */
+ protected $service;
+
+ /**
+ * Create a new service
+ *
+ * @param MonitoringBackend $backend Backend to fetch service information from
+ * @param string $host Hostname the service is running on
+ * @param string $service Service name
+ */
+ public function __construct(MonitoringBackend $backend, $host, $service)
+ {
+ parent::__construct($backend);
+ $this->host = new Host($backend, $host);
+ $this->service = $service;
+ }
+
+ /**
+ * Get the host the service is running on
+ *
+ * @return Host
+ */
+ public function getHost()
+ {
+ return $this->host;
+ }
+
+ /**
+ * Get the service name
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->service;
+ }
+
+ /**
+ * Get the data view
+ *
+ * @return \Icinga\Module\Monitoring\DataView\ServiceStatus
+ */
+ protected function getDataView()
+ {
+ return $this->backend->select()->from('servicestatus', array(
+ 'instance_name',
+ 'host_attempt',
+ 'host_icon_image',
+ 'host_icon_image_alt',
+ 'host_acknowledged',
+ 'host_active_checks_enabled',
+ 'host_address',
+ 'host_address6',
+ 'host_alias',
+ 'host_display_name',
+ 'host_handled',
+ 'host_in_downtime',
+ 'host_is_flapping',
+ 'host_last_state_change',
+ 'host_name',
+ 'host_notifications_enabled',
+ 'host_passive_checks_enabled',
+ 'host_state',
+ 'host_state_type',
+ 'service_icon_image',
+ 'service_icon_image_alt',
+ 'service_acknowledged',
+ 'service_acknowledgement_type',
+ 'service_action_url',
+ 'service_active_checks_enabled',
+ 'service_active_checks_enabled_changed',
+ 'service_attempt',
+ 'service_check_command',
+ 'service_check_execution_time',
+ 'service_check_interval',
+ 'service_check_latency',
+ 'service_check_source',
+ 'service_check_timeperiod',
+ 'service_current_notification_number',
+ 'service_description',
+ 'service_display_name',
+ 'service_event_handler_enabled',
+ 'service_event_handler_enabled_changed',
+ 'service_flap_detection_enabled',
+ 'service_flap_detection_enabled_changed',
+ 'service_handled',
+ 'service_in_downtime',
+ 'service_is_flapping',
+ 'service_is_reachable',
+ 'service_last_check',
+ 'service_last_notification',
+ 'service_last_state_change',
+ 'service_long_output',
+ 'service_next_check',
+ 'service_next_update',
+ 'service_notes',
+ 'service_notes_url',
+ 'service_notifications_enabled',
+ 'service_notifications_enabled_changed',
+ 'service_obsessing',
+ 'service_obsessing_changed',
+ 'service_output',
+ 'service_passive_checks_enabled',
+ 'service_passive_checks_enabled_changed',
+ 'service_percent_state_change',
+ 'service_perfdata',
+ 'service_process_perfdata' => 'service_process_performance_data',
+ 'service_state',
+ 'service_state_type'
+ ))
+ ->whereEx(new FilterEqual('host_name', '=', $this->host->getName()))
+ ->whereEx(new FilterEqual('service_description', '=', $this->service));
+ }
+
+ /**
+ * Get the optional translated textual representation of a service state
+ *
+ * @param int $state
+ * @param bool $translate
+ *
+ * @return string
+ * @throws InvalidArgumentException If the service state is not valid
+ */
+ public static function getStateText($state, $translate = false)
+ {
+ $translate = (bool) $translate;
+ switch ((int) $state) {
+ case self::STATE_OK:
+ $text = $translate ? mt('monitoring', 'OK') : 'ok';
+ break;
+ case self::STATE_WARNING:
+ $text = $translate ? mt('monitoring', 'WARNING') : 'warning';
+ break;
+ case self::STATE_CRITICAL:
+ $text = $translate ? mt('monitoring', 'CRITICAL') : 'critical';
+ break;
+ case self::STATE_UNKNOWN:
+ $text = $translate ? mt('monitoring', 'UNKNOWN') : 'unknown';
+ break;
+ case self::STATE_PENDING:
+ $text = $translate ? mt('monitoring', 'PENDING') : 'pending';
+ break;
+ default:
+ throw new InvalidArgumentException('Invalid service state \'%s\'', $state);
+ }
+ return $text;
+ }
+
+ public function getNotesUrls()
+ {
+ return $this->resolveAllStrings(
+ MonitoredObject::parseAttributeUrls($this->service_notes_url)
+ );
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Object/ServiceList.php b/modules/monitoring/library/Monitoring/Object/ServiceList.php
new file mode 100644
index 0000000..5bc0bdb
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Object/ServiceList.php
@@ -0,0 +1,184 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Object;
+
+use Icinga\Data\DataArray\ArrayDatasource;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterOr;
+use Icinga\Data\SimpleQuery;
+use Icinga\Util\StringHelper;
+
+/**
+ * A service list
+ */
+class ServiceList extends ObjectList
+{
+ protected $hostStateSummary;
+
+ protected $serviceStateSummary;
+
+ protected $dataViewName = 'servicestatus';
+
+ protected $columns = array('host_name', 'service_description');
+
+ protected function fetchObjects()
+ {
+ $services = array();
+ $query = $this->backend->select()->from($this->dataViewName, $this->columns)->applyFilter($this->filter)
+ ->getQuery()->getSelectQuery()->query();
+ foreach ($query as $row) {
+ /** @var object $row */
+ $service = new Service($this->backend, $row->host_name, $row->service_description);
+ $service->setProperties($row);
+ $services[] = $service;
+ }
+ return $services;
+ }
+
+ /**
+ * Create a state summary of all services that can be consumed by servicesummary.phtml
+ *
+ * @return SimpleQuery
+ */
+ public function getServiceStateSummary()
+ {
+ if (! $this->serviceStateSummary) {
+ $this->initStateSummaries();
+ }
+
+ $ds = new ArrayDatasource(array((object) $this->serviceStateSummary));
+ return $ds->select();
+ }
+
+ /**
+ * Create a state summary of all hosts that can be consumed by hostsummary.phtml
+ *
+ * @return SimpleQuery
+ */
+ public function getHostStateSummary()
+ {
+ if (! $this->hostStateSummary) {
+ $this->initStateSummaries();
+ }
+
+ $ds = new ArrayDatasource(array((object) $this->hostStateSummary));
+ return $ds->select();
+ }
+
+ /**
+ * Calculate the current state summary and populate hostStateSummary and serviceStateSummary
+ * properties
+ */
+ protected function initStateSummaries()
+ {
+ $serviceStates = array_fill_keys(self::getServiceStatesSummaryEmpty(), 0);
+ $hostStates = array_fill_keys(HostList::getHostStatesSummaryEmpty(), 0);
+
+ foreach ($this as $service) {
+ $unhandled = false;
+ if ((bool) $service->problem === true && (bool) $service->handled === false) {
+ $unhandled = true;
+ }
+
+ $stateName = 'services_' . $service::getStateText($service->state);
+ ++$serviceStates[$stateName];
+ ++$serviceStates[$stateName . ($unhandled ? '_unhandled' : '_handled')];
+
+ if (! isset($knownHostStates[$service->getHost()->getName()])) {
+ $unhandledHost = (bool) $service->host_problem === true && (bool) $service->host_handled === false;
+ ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)];
+ ++$hostStates['hosts_' . $service->getHost()->getStateText($service->host_state)
+ . ($unhandledHost ? '_unhandled' : '_handled')];
+ $knownHostStates[$service->getHost()->getName()] = true;
+ }
+ }
+
+ $serviceStates['services_total'] = count($this);
+ $this->hostStateSummary = $hostStates;
+ $this->serviceStateSummary = $serviceStates;
+ }
+
+ /**
+ * Return an empty array with all possible host state names
+ *
+ * @return array An array containing all possible host states as keys and 0 as values.
+ */
+ public static function getServiceStatesSummaryEmpty()
+ {
+ return StringHelper::cartesianProduct(
+ array(
+ array('services'),
+ array(
+ Service::getStateText(Service::STATE_OK),
+ Service::getStateText(Service::STATE_WARNING),
+ Service::getStateText(Service::STATE_CRITICAL),
+ Service::getStateText(Service::STATE_UNKNOWN),
+ Service::getStateText(Service::STATE_PENDING)
+ ),
+ array(null, 'handled', 'unhandled')
+ ),
+ '_'
+ );
+ }
+
+ /**
+ * Returns a Filter that matches all hosts in this HostList
+ *
+ * @param array $columns Override filter column names
+ *
+ * @return Filter
+ */
+ public function objectsFilter($columns = array('host' => 'host', 'service' => 'service'))
+ {
+ $filterExpression = array();
+ foreach ($this as $service) {
+ $filterExpression[] = Filter::matchAll(
+ Filter::where($columns['host'], $service->getHost()->getName()),
+ Filter::where($columns['service'], $service->getName())
+ );
+ }
+ return FilterOr::matchAny($filterExpression);
+ }
+
+ /**
+ * Get the comments
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Hostcomment
+ */
+ public function getComments()
+ {
+ return $this->backend
+ ->select()
+ ->from('servicecomment', array('host_name', 'service_description'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * Get the scheduled downtimes
+ *
+ * @return \Icinga\Module\Monitoring\DataView\Servicedowntime
+ */
+ public function getScheduledDowntimes()
+ {
+ return $this->backend
+ ->select()
+ ->from('servicedowntime', array('host_name', 'service_description'))
+ ->applyFilter(clone $this->filter);
+ }
+
+ /**
+ * @return ObjectList
+ */
+ public function getUnacknowledgedObjects()
+ {
+ $unhandledObjects = array();
+ foreach ($this as $object) {
+ if (! in_array((int) $object->state, array(0, 99)) &&
+ (bool) $object->service_acknowledged === false) {
+ $unhandledObjects[] = $object;
+ }
+ }
+ return $this->newFromArray($unhandledObjects);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Plugin.php b/modules/monitoring/library/Monitoring/Plugin.php
new file mode 100644
index 0000000..e8e1f5d
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Plugin.php
@@ -0,0 +1,12 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring;
+
+use Icinga\Application\Cli;
+
+require_once ICINGA_LIBDIR . '/Icinga/Application/Cli.php';
+
+class Plugin extends Cli
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Plugin/Perfdata.php b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php
new file mode 100644
index 0000000..476354a
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Plugin/Perfdata.php
@@ -0,0 +1,550 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Plugin;
+
+use Icinga\Util\Format;
+use InvalidArgumentException;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Web\Widget\Chart\InlinePie;
+use Icinga\Module\Monitoring\Object\Service;
+use Zend_Controller_Front;
+
+class Perfdata
+{
+ const PERFDATA_OK = 'ok';
+ const PERFDATA_WARNING = 'warning';
+ const PERFDATA_CRITICAL = 'critical';
+
+ /**
+ * The performance data value being parsed
+ *
+ * @var string
+ */
+ protected $perfdataValue;
+
+ /**
+ * Unit of measurement (UOM)
+ *
+ * @var string
+ */
+ protected $unit;
+
+ /**
+ * The label
+ *
+ * @var string
+ */
+ protected $label;
+
+ /**
+ * The value
+ *
+ * @var float
+ */
+ protected $value;
+
+ /**
+ * The minimum value
+ *
+ * @var float
+ */
+ protected $minValue;
+
+ /**
+ * The maximum value
+ *
+ * @var float
+ */
+ protected $maxValue;
+
+ /**
+ * The WARNING threshold
+ *
+ * @var ThresholdRange
+ */
+ protected $warningThreshold;
+
+ /**
+ * The CRITICAL threshold
+ *
+ * @var ThresholdRange
+ */
+ protected $criticalThreshold;
+
+ /**
+ * Create a new Perfdata object based on the given performance data label and value
+ *
+ * @param string $label The perfdata label
+ * @param string $value The perfdata value
+ */
+ public function __construct($label, $value)
+ {
+ $this->perfdataValue = $value;
+ $this->label = $label;
+ $this->parse();
+
+ if ($this->unit === '%') {
+ if ($this->minValue === null) {
+ $this->minValue = 0.0;
+ }
+ if ($this->maxValue === null) {
+ $this->maxValue = 100.0;
+ }
+ }
+
+ $warn = $this->warningThreshold->getMax();
+ if ($warn !== null) {
+ $crit = $this->criticalThreshold->getMax();
+ if ($crit !== null && $warn > $crit) {
+ $this->warningThreshold->setInverted();
+ $this->criticalThreshold->setInverted();
+ }
+ }
+ }
+
+ /**
+ * Return a new Perfdata object based on the given performance data key=value pair
+ *
+ * @param string $perfdata The key=value pair to parse
+ *
+ * @return Perfdata
+ *
+ * @throws InvalidArgumentException In case the given performance data has no content or a invalid format
+ */
+ public static function fromString($perfdata)
+ {
+ if (empty($perfdata)) {
+ throw new InvalidArgumentException('Perfdata::fromString expects a string with content');
+ } elseif (strpos($perfdata, '=') === false) {
+ throw new InvalidArgumentException(
+ 'Perfdata::fromString expects a key=value formatted string. Got "' . $perfdata . '" instead'
+ );
+ }
+
+ list($label, $value) = explode('=', $perfdata, 2);
+ return new static(trim($label), trim($value));
+ }
+
+ /**
+ * Return whether this performance data's value is a number
+ *
+ * @return bool True in case it's a number, otherwise False
+ */
+ public function isNumber()
+ {
+ return $this->unit === null;
+ }
+
+ /**
+ * Return whether this performance data's value are seconds
+ *
+ * @return bool True in case it's seconds, otherwise False
+ */
+ public function isSeconds()
+ {
+ return in_array($this->unit, array('s', 'ms', 'us'));
+ }
+
+ /**
+ * Return whether this performance data's value is a temperature
+ *
+ * @return bool True in case it's temperature, otherwise False
+ */
+ public function isTemperature()
+ {
+ return in_array($this->unit, array('°c', '°f'));
+ }
+
+ /**
+ * Return whether this performance data's value is in percentage
+ *
+ * @return bool True in case it's in percentage, otherwise False
+ */
+ public function isPercentage()
+ {
+ return $this->unit === '%';
+ }
+
+ /**
+ * Return whether this performance data's value is in bytes
+ *
+ * @return bool True in case it's in bytes, otherwise False
+ */
+ public function isBytes()
+ {
+ return in_array($this->unit, array('b', 'kb', 'mb', 'gb', 'tb'));
+ }
+
+ /**
+ * Return whether this performance data's value is a counter
+ *
+ * @return bool True in case it's a counter, otherwise False
+ */
+ public function isCounter()
+ {
+ return $this->unit === 'c';
+ }
+
+ /**
+ * Returns whether it is possible to display a visual representation
+ *
+ * @return bool True when the perfdata is visualizable
+ */
+ public function isVisualizable()
+ {
+ return isset($this->minValue) && isset($this->maxValue) && isset($this->value);
+ }
+
+ /**
+ * Return this perfomance data's label
+ */
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ /**
+ * Return the value or null if it is unknown (U)
+ *
+ * @return null|float
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Return the unit as a string
+ *
+ * @return string
+ */
+ public function getUnit()
+ {
+ return $this->unit;
+ }
+
+ /**
+ * Return the value as percentage (0-100)
+ *
+ * @return null|float
+ */
+ public function getPercentage()
+ {
+ if ($this->isPercentage()) {
+ return $this->value;
+ }
+
+ if ($this->maxValue !== null) {
+ $minValue = $this->minValue !== null ? $this->minValue : 0.0;
+ if ($this->maxValue == $minValue) {
+ return null;
+ }
+
+ if ($this->value > $minValue) {
+ return (($this->value - $minValue) / ($this->maxValue - $minValue)) * 100;
+ }
+ }
+ }
+
+ /**
+ * Return this performance data's warning treshold
+ *
+ * @return ThresholdRange
+ */
+ public function getWarningThreshold()
+ {
+ return $this->warningThreshold;
+ }
+
+ /**
+ * Return this performance data's critical treshold
+ *
+ * @return ThresholdRange
+ */
+ public function getCriticalThreshold()
+ {
+ return $this->criticalThreshold;
+ }
+
+ /**
+ * Return the minimum value or null if it is not available
+ *
+ * @return null|string
+ */
+ public function getMinimumValue()
+ {
+ return $this->minValue;
+ }
+
+ /**
+ * Return the maximum value or null if it is not available
+ *
+ * @return null|float
+ */
+ public function getMaximumValue()
+ {
+ return $this->maxValue;
+ }
+
+ /**
+ * Return this performance data as string
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->formatLabel();
+ }
+
+ /**
+ * Parse the current performance data value
+ *
+ * @todo Handle optional min/max if UOM == %
+ */
+ protected function parse()
+ {
+ $parts = explode(';', $this->perfdataValue);
+
+ $matches = array();
+ if (preg_match('@^(-?(?:\d+)?(?:\.\d+)?)([a-zA-Z%°]{1,3})$@u', $parts[0], $matches)) {
+ $this->unit = strtolower($matches[2]);
+ $this->value = self::convert($matches[1], $this->unit);
+ } else {
+ $this->value = self::convert($parts[0]);
+ }
+
+ switch (count($parts)) {
+ /* @noinspection PhpMissingBreakStatementInspection */
+ case 5:
+ if ($parts[4] !== '') {
+ $this->maxValue = self::convert($parts[4], $this->unit);
+ }
+ /* @noinspection PhpMissingBreakStatementInspection */
+ case 4:
+ if ($parts[3] !== '') {
+ $this->minValue = self::convert($parts[3], $this->unit);
+ }
+ /* @noinspection PhpMissingBreakStatementInspection */
+ case 3:
+ $this->criticalThreshold = self::convert(
+ ThresholdRange::fromString(trim($parts[2])),
+ $this->unit
+ );
+ // Fallthrough
+ case 2:
+ $this->warningThreshold = self::convert(
+ ThresholdRange::fromString(trim($parts[1])),
+ $this->unit
+ );
+ }
+
+ if ($this->warningThreshold === null) {
+ $this->warningThreshold = new ThresholdRange();
+ }
+ if ($this->criticalThreshold === null) {
+ $this->criticalThreshold = new ThresholdRange();
+ }
+ }
+
+ /**
+ * Return the given value converted to its smallest supported representation
+ *
+ * @param string $value The value to convert
+ * @param string $fromUnit The unit the value currently represents
+ *
+ * @return null|float Null in case the value is not a number
+ */
+ protected static function convert($value, $fromUnit = null)
+ {
+ if ($value instanceof ThresholdRange) {
+ $value = clone $value;
+
+ $min = $value->getMin();
+ if ($min !== null) {
+ $value->setMin(self::convert($min, $fromUnit));
+ }
+
+ $max = $value->getMax();
+ if ($max !== null) {
+ $value->setMax(self::convert($max, $fromUnit));
+ }
+
+ return $value;
+ }
+
+ if (is_numeric($value)) {
+ switch ($fromUnit) {
+ case 'us':
+ return $value / pow(10, 6);
+ case 'ms':
+ return $value / pow(10, 3);
+ case 'tb':
+ return floatval($value) * pow(2, 40);
+ case 'gb':
+ return floatval($value) * pow(2, 30);
+ case 'mb':
+ return floatval($value) * pow(2, 20);
+ case 'kb':
+ return floatval($value) * pow(2, 10);
+ default:
+ return (float) $value;
+ }
+ }
+ }
+
+ protected function calculatePieChartData()
+ {
+ $rawValue = $this->getValue();
+ $minValue = $this->getMinimumValue() !== null ? $this->getMinimumValue() : 0;
+ $usedValue = ($rawValue - $minValue);
+
+ $green = $orange = $red = 0;
+
+ if ($this->criticalThreshold->contains($rawValue)) {
+ if ($this->warningThreshold->contains($rawValue)) {
+ $green = $usedValue;
+ } else {
+ $orange = $usedValue;
+ }
+ } else {
+ $red = $usedValue;
+ }
+
+ return array($green, $orange, $red, ($this->getMaximumValue() - $minValue) - $usedValue);
+ }
+
+
+ public function asInlinePie()
+ {
+ if (! $this->isVisualizable()) {
+ throw new ProgrammingError('Cannot calculate piechart data for unvisualizable perfdata entry.');
+ }
+
+ $data = $this->calculatePieChartData();
+ $pieChart = new InlinePie($data, $this);
+ $pieChart->setColors(array('#44bb77', '#ffaa44', '#ff5566', '#ddccdd'));
+
+ return $pieChart;
+ }
+
+ /**
+ * Format the given value depending on the currently used unit
+ */
+ protected function format($value)
+ {
+ if ($value === null) {
+ return null;
+ }
+
+ if ($value instanceof ThresholdRange) {
+ if ($value->getMin()) {
+ return (string) $value;
+ }
+
+ $max = $value->getMax();
+ return $max === null ? '' : $this->format($max);
+ }
+
+ if ($this->isPercentage()) {
+ return (string)$value . '%';
+ }
+ if ($this->isBytes()) {
+ return Format::bytes($value);
+ }
+ if ($this->isSeconds()) {
+ return Format::seconds($value);
+ }
+ if ($this->isTemperature()) {
+ return (string)$value . strtoupper($this->unit);
+ }
+ return number_format($value, 2) . ($this->unit !== null ? ' ' . $this->unit : '');
+ }
+
+ /**
+ * Format the title string that represents this perfdata set
+ *
+ * @param bool $html
+ *
+ * @return string
+ */
+ public function formatLabel($html = false)
+ {
+ return sprintf(
+ $html ? '<b>%s %s</b> (%s%%)' : '%s %s (%s%%)',
+ htmlspecialchars($this->getLabel()),
+ $this->format($this->value),
+ number_format($this->getPercentage() ?? 0, 2)
+ );
+ }
+
+ public function toArray()
+ {
+ return array(
+ 'label' => $this->getLabel(),
+ 'value' => $this->format($this->getvalue()),
+ 'min' => isset($this->minValue) && !$this->isPercentage()
+ ? $this->format($this->minValue)
+ : '',
+ 'max' => isset($this->maxValue) && !$this->isPercentage()
+ ? $this->format($this->maxValue)
+ : '',
+ 'warn' => $this->format($this->warningThreshold),
+ 'crit' => $this->format($this->criticalThreshold)
+ );
+ }
+
+ /**
+ * Return the state indicated by this perfdata
+ *
+ * @see Service
+ *
+ * @return int
+ */
+ public function getState()
+ {
+ if ($this->value === null) {
+ return Service::STATE_UNKNOWN;
+ }
+
+ if (! $this->criticalThreshold->contains($this->value)) {
+ return Service::STATE_CRITICAL;
+ }
+
+ if (! $this->warningThreshold->contains($this->value)) {
+ return Service::STATE_WARNING;
+ }
+
+ return Service::STATE_OK;
+ }
+
+ /**
+ * Return whether the state indicated by this perfdata is worse than
+ * the state indicated by the other perfdata
+ * CRITICAL > UNKNOWN > WARNING > OK
+ *
+ * @param Perfdata $rhs the other perfdata
+ *
+ * @return bool
+ */
+ public function worseThan(Perfdata $rhs)
+ {
+ if (($state = $this->getState()) === ($rhsState = $rhs->getState())) {
+ return $this->getPercentage() > $rhs->getPercentage();
+ }
+
+ if ($state === Service::STATE_CRITICAL) {
+ return true;
+ }
+
+ if ($state === Service::STATE_UNKNOWN) {
+ return $rhsState !== Service::STATE_CRITICAL;
+ }
+
+ if ($state === Service::STATE_WARNING) {
+ return $rhsState === Service::STATE_OK;
+ }
+
+ return false;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php
new file mode 100644
index 0000000..ef1ca0c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Plugin/PerfdataSet.php
@@ -0,0 +1,144 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Plugin;
+
+use ArrayIterator;
+use IteratorAggregate;
+use Traversable;
+
+class PerfdataSet implements IteratorAggregate
+{
+ /**
+ * The performance data being parsed
+ *
+ * @var string
+ */
+ protected $perfdataStr;
+
+ /**
+ * The current parsing position
+ *
+ * @var int
+ */
+ protected $parserPos = 0;
+
+ /**
+ * A list of Perfdata objects
+ *
+ * @var array
+ */
+ protected $perfdata = array();
+
+ /**
+ * Create a new set of performance data
+ *
+ * @param string $perfdataStr A space separated list of label/value pairs
+ */
+ protected function __construct($perfdataStr)
+ {
+ if ($perfdataStr && ($perfdataStr = trim($perfdataStr))) {
+ $this->perfdataStr = $perfdataStr;
+ $this->parse();
+ }
+ }
+
+ /**
+ * Return a iterator for this set of performance data
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->asArray());
+ }
+
+ /**
+ * Return a new set of performance data
+ *
+ * @param string $perfdataStr A space separated list of label/value pairs
+ *
+ * @return PerfdataSet
+ */
+ public static function fromString($perfdataStr)
+ {
+ return new static($perfdataStr);
+ }
+
+ /**
+ * Return this set of performance data as array
+ *
+ * @return array
+ */
+ public function asArray()
+ {
+ return $this->perfdata;
+ }
+
+ /**
+ * Parse the current performance data
+ */
+ protected function parse()
+ {
+ while ($this->parserPos < strlen($this->perfdataStr)) {
+ $label = trim($this->readLabel());
+ $value = trim($this->readUntil(' '));
+
+ if ($label) {
+ $this->perfdata[] = new Perfdata($label, $value);
+ }
+ }
+ }
+
+ /**
+ * Return the next label found in the performance data
+ *
+ * @return string The label found
+ */
+ protected function readLabel()
+ {
+ $this->skipSpaces();
+ if (in_array($this->perfdataStr[$this->parserPos], array('"', "'"))) {
+ $quoteChar = $this->perfdataStr[$this->parserPos++];
+ $label = $this->readUntil('=');
+ $this->parserPos++;
+
+ if (($closingPos = strpos($label, $quoteChar)) > 0) {
+ $label = substr($label, 0, $closingPos);
+ }
+ } else {
+ $label = $this->readUntil('=');
+ $this->parserPos++;
+ }
+
+ $this->skipSpaces();
+ return $label;
+ }
+
+ /**
+ * Return all characters between the current parser position and the given character
+ *
+ * @param string $stopChar The character on which to stop
+ *
+ * @return string
+ */
+ protected function readUntil($stopChar)
+ {
+ $start = $this->parserPos;
+ while ($this->parserPos < strlen($this->perfdataStr) && $this->perfdataStr[$this->parserPos] !== $stopChar) {
+ $this->parserPos++;
+ }
+
+ return substr($this->perfdataStr, $start, $this->parserPos - $start);
+ }
+
+ /**
+ * Advance the parser position to the next non-whitespace character
+ */
+ protected function skipSpaces()
+ {
+ while ($this->parserPos < strlen($this->perfdataStr) && $this->perfdataStr[$this->parserPos] === ' ') {
+ $this->parserPos++;
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php b/modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php
new file mode 100644
index 0000000..bd27b8b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Plugin/ThresholdRange.php
@@ -0,0 +1,179 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Plugin;
+
+/**
+ * The warning/critical threshold of a measured value
+ */
+class ThresholdRange
+{
+ /**
+ * The smallest value inside the range (null stands for -∞)
+ *
+ * @var float|null
+ */
+ protected $min;
+
+ /**
+ * The biggest value inside the range (null stands for ∞)
+ *
+ * @var float|null
+ */
+ protected $max;
+
+ /**
+ * Whether to invert the result of contains()
+ *
+ * @var bool
+ */
+ protected $inverted = false;
+
+ /**
+ * The unmodified range as passed to fromString()
+ *
+ * @var string
+ */
+ protected $raw;
+
+ /**
+ * Create a new instance based on a threshold range conforming to <https://nagios-plugins.org/doc/guidelines.html>
+ *
+ * @param string $rawRange
+ *
+ * @return ThresholdRange
+ */
+ public static function fromString($rawRange)
+ {
+ $range = new static();
+ $range->raw = $rawRange;
+
+ if ($rawRange == '') {
+ return $range;
+ }
+
+ $rawRange = ltrim($rawRange);
+ if (substr($rawRange, 0, 1) === '@') {
+ $range->setInverted();
+ $rawRange = substr($rawRange, 1);
+ }
+
+ if (strpos($rawRange, ':') === false) {
+ $min = 0.0;
+ $max = floatval(trim($rawRange));
+ } else {
+ list($min, $max) = explode(':', $rawRange, 2);
+ $min = trim($min);
+ $max = trim($max);
+
+ switch ($min) {
+ case '':
+ $min = 0.0;
+ break;
+ case '~':
+ $min = null;
+ break;
+ default:
+ $min = floatval($min);
+ }
+
+ $max = empty($max) ? null : floatval($max);
+ }
+
+ return $range->setMin($min)
+ ->setMax($max);
+ }
+
+ /**
+ * Set the smallest value inside the range (null stands for -∞)
+ *
+ * @param float|null $min
+ *
+ * @return $this
+ */
+ public function setMin($min)
+ {
+ $this->min = $min;
+ return $this;
+ }
+
+ /**
+ * Get the smallest value inside the range (null stands for -∞)
+ *
+ * @return float|null
+ */
+ public function getMin()
+ {
+ return $this->min;
+ }
+
+ /**
+ * Set the biggest value inside the range (null stands for ∞)
+ *
+ * @param float|null $max
+ *
+ * @return $this
+ */
+ public function setMax($max)
+ {
+ $this->max = $max;
+ return $this;
+ }
+
+ /**
+ * Get the biggest value inside the range (null stands for ∞)
+ *
+ * @return float|null
+ */
+ public function getMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Set whether to invert the result of contains()
+ *
+ * @param bool $inverted
+ *
+ * @return $this
+ */
+ public function setInverted($inverted = true)
+ {
+ $this->inverted = $inverted;
+ return $this;
+ }
+
+ /**
+ * Get whether to invert the result of contains()
+ *
+ * @return bool
+ */
+ public function isInverted()
+ {
+ return $this->inverted;
+ }
+
+ /**
+ * Return whether $value is inside $this
+ *
+ * @param float $value
+ *
+ * @return bool
+ */
+ public function contains($value)
+ {
+ return (bool) ($this->inverted ^ (
+ ($this->min === null || $this->min <= $value) && ($this->max === null || $this->max >= $value)
+ ));
+ }
+
+ /**
+ * Return the textual representation of $this, suitable for fromString()
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->raw;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php b/modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php
new file mode 100644
index 0000000..4e2e61c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/ProvidedHook/ApplicationState.php
@@ -0,0 +1,32 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\ProvidedHook;
+
+use Icinga\Application\Hook\ApplicationStateHook;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+
+class ApplicationState extends ApplicationStateHook
+{
+ public function collectMessages()
+ {
+ $backend = MonitoringBackend::instance();
+
+ $programStatus = $backend
+ ->select()
+ ->from(
+ 'programstatus',
+ ['is_currently_running', 'status_update_time']
+ )
+ ->fetchRow();
+
+ if ($programStatus === false || ! (bool) $programStatus->is_currently_running) {
+ $message = sprintf(
+ mt('monitoring', "Monitoring backend '%s' is not running."),
+ $backend->getName()
+ );
+
+ $this->addError('monitoring/backend-down', $programStatus->status_update_time, $message);
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/ProvidedHook/Health.php b/modules/monitoring/library/Monitoring/ProvidedHook/Health.php
new file mode 100644
index 0000000..8f9c893
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/ProvidedHook/Health.php
@@ -0,0 +1,102 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\ProvidedHook;
+
+use Icinga\Application\Hook\HealthHook;
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use ipl\Web\Url;
+
+class Health extends HealthHook
+{
+ /** @var object */
+ protected $programStatus;
+
+ public function getName()
+ {
+ return 'Icinga';
+ }
+
+ public function getUrl()
+ {
+ return Url::fromPath('monitoring/health/info');
+ }
+
+ public function checkHealth()
+ {
+ $backendName = MonitoringBackend::instance()->getName();
+ $programStatus = $this->getProgramStatus();
+ if ($programStatus === false) {
+ $this->setState(self::STATE_UNKNOWN);
+ $this->setMessage(sprintf(t('%s is currently not up and running'), $backendName));
+ return;
+ }
+
+ if ($programStatus->is_currently_running) {
+ $this->setState(self::STATE_OK);
+ $this->setMessage(sprintf(
+ t(
+ '%1$s has been up and running with PID %2$d %3$s',
+ 'Last format parameter represents the time running'
+ ),
+ $backendName,
+ $programStatus->process_id,
+ DateFormatter::timeSince($programStatus->program_start_time)
+ ));
+
+ $warningMessages = [];
+
+ if (! $programStatus->active_host_checks_enabled) {
+ $this->setState(self::STATE_WARNING);
+ $warningMessages[] = t('Active host checks are disabled');
+ }
+
+ if (! $programStatus->active_service_checks_enabled) {
+ $this->setState(self::STATE_WARNING);
+ $warningMessages[] = t('Active service checks are disabled');
+ }
+
+ if (! $programStatus->notifications_enabled) {
+ $this->setState(self::STATE_WARNING);
+ $warningMessages[] = t('Notifications are disabled');
+ }
+
+ if ($this->getState() === self::STATE_WARNING) {
+ $this->setMessage(implode("; ", $warningMessages));
+ }
+ } else {
+ $this->setState(self::STATE_CRITICAL);
+ $this->setMessage(sprintf(t('Backend %s is not running'), $backendName));
+ }
+
+ $this->setMetrics((array) $programStatus);
+ }
+
+ protected function getProgramStatus()
+ {
+ if ($this->programStatus === null) {
+ $this->programStatus = MonitoringBackend::instance()->select()
+ ->from('programstatus', [
+ 'program_version',
+ 'status_update_time',
+ 'program_start_time',
+ 'program_end_time',
+ 'endpoint_name',
+ 'is_currently_running',
+ 'process_id',
+ 'last_command_check',
+ 'last_log_rotation',
+ 'notifications_enabled',
+ 'active_service_checks_enabled',
+ 'active_host_checks_enabled',
+ 'event_handlers_enabled',
+ 'flap_detection_enabled',
+ 'process_performance_data'
+ ])
+ ->fetchRow();
+ }
+
+ return $this->programStatus;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php b/modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php
new file mode 100644
index 0000000..fd1818f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/ProvidedHook/X509/Sni.php
@@ -0,0 +1,35 @@
+<?php
+/* Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\ProvidedHook\X509;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Module\X509\Hook\SniHook;
+
+class Sni extends SniHook
+{
+ public function getHosts(Filter $filter = null)
+ {
+ $hosts = MonitoringBackend::instance()
+ ->select()
+ ->from('hoststatus', [
+ 'host_name',
+ 'host_address',
+ 'host_address6'
+ ]);
+ if ($filter !== null) {
+ $hosts->applyFilter($filter);
+ }
+
+ foreach ($hosts as $host) {
+ if (! empty($host->host_address)) {
+ yield $host->host_address => $host->host_name;
+ }
+
+ if (! empty($host->host_address6)) {
+ yield $host->host_address6 => $host->host_name;
+ }
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/SecurityStep.php b/modules/monitoring/library/Monitoring/SecurityStep.php
new file mode 100644
index 0000000..94053b3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/SecurityStep.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring;
+
+use Exception;
+use Icinga\Module\Setup\Step;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+
+class SecurityStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $config = array();
+ $config['security'] = $this->data['securityConfig'];
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('modules/monitoring/config.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->error = $e;
+ return false;
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ $pageTitle = '<h2>' . mt('monitoring', 'Monitoring Security', 'setup.page.title') . '</h2>';
+ $pageDescription = '<p>' . mt(
+ 'monitoring',
+ 'Icinga Web 2 will protect your monitoring environment against'
+ . ' prying eyes using the configuration specified below:'
+ ) . '</p>';
+
+ $pageHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Protected Custom Variables') . '</strong></td>'
+ . '<td>' . ($this->data['securityConfig']['protected_customvars'] ? (
+ $this->data['securityConfig']['protected_customvars']
+ ) : mt('monitoring', 'None', 'monitoring.protected_customvars')) . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+
+ return $pageTitle . '<div class="topic">' . $pageDescription . $pageHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ return array(sprintf(
+ mt('monitoring', 'Monitoring security configuration has been successfully created: %s'),
+ Config::resolvePath('modules/monitoring/config.ini')
+ ));
+ } elseif ($this->error !== null) {
+ return array(
+ sprintf(
+ mt(
+ 'monitoring',
+ 'Monitoring security configuration could not be written to: %s. An error occured:'
+ ),
+ Config::resolvePath('modules/monitoring/config.ini')
+ ),
+ sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error))
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php b/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php
new file mode 100644
index 0000000..ee313b3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Timeline/TimeEntry.php
@@ -0,0 +1,233 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Timeline;
+
+use DateTime;
+use Icinga\Web\Url;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * An event group that is part of a timeline
+ */
+class TimeEntry
+{
+ /**
+ * The name of this group
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The amount of events that are part of this group
+ *
+ * @var int
+ */
+ protected $value;
+
+ /**
+ * The date and time of this group
+ *
+ * @var DateTime
+ */
+ protected $dateTime;
+
+ /**
+ * The url to this group's detail view
+ *
+ * @var Url
+ */
+ protected $detailUrl;
+
+ /**
+ * The weight of this group
+ *
+ * @var float
+ */
+ protected $weight = 1.0;
+
+ /**
+ * The label of this group
+ *
+ * @var string
+ */
+ protected $label;
+
+ /**
+ * The CSS class of the entry
+ *
+ * @var string
+ */
+ protected $class;
+
+ /**
+ * Return a new TimeEntry object with the given attributes being set
+ *
+ * @param array $attributes The attributes to set
+ * @return TimeEntry The resulting TimeEntry object
+ * @throws ProgrammingError If one of the given attributes cannot be set
+ */
+ public static function fromArray(array $attributes)
+ {
+ $entry = new TimeEntry();
+
+ foreach ($attributes as $name => $value) {
+ $methodName = 'set' . ucfirst($name);
+ if (method_exists($entry, $methodName)) {
+ $entry->{$methodName}($value);
+ } else {
+ throw new ProgrammingError(
+ 'Method "%s" does not exist on object of type "%s"',
+ $methodName,
+ __CLASS__
+ );
+ }
+ }
+
+ return $entry;
+ }
+
+ /**
+ * Set this group's name
+ *
+ * @param string $name The name to set
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+ }
+
+ /**
+ * Return the name of this group
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set this group's amount of events
+ *
+ * @param int $value The value to set
+ */
+ public function setValue($value)
+ {
+ $this->value = intval($value);
+ }
+
+ /**
+ * Return the amount of events in this group
+ *
+ * @return int
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set this group's date and time
+ *
+ * @param DateTime $dateTime The date and time to set
+ */
+ public function setDateTime(DateTime $dateTime)
+ {
+ $this->dateTime = $dateTime;
+ }
+
+ /**
+ * Return the date and time of this group
+ *
+ * @return DateTime
+ */
+ public function getDateTime()
+ {
+ return $this->dateTime;
+ }
+
+ /**
+ * Set the url to this group's detail view
+ *
+ * @param Url $detailUrl The url to set
+ */
+ public function setDetailUrl(Url $detailUrl)
+ {
+ $this->detailUrl = $detailUrl;
+ }
+
+ /**
+ * Return the url to this group's detail view
+ *
+ * @return Url
+ */
+ public function getDetailUrl()
+ {
+ return $this->detailUrl;
+ }
+
+ /**
+ * Set this group's weight
+ *
+ * @param float $weight The weight for this group
+ */
+ public function setWeight($weight)
+ {
+ $this->weight = floatval($weight);
+ }
+
+ /**
+ * Return the weight of this group
+ *
+ * @return float
+ */
+ public function getWeight()
+ {
+ return $this->weight;
+ }
+
+ /**
+ * Set this group's label
+ *
+ * @param string $label The label to set
+ */
+ public function setLabel($label)
+ {
+ $this->label = $label;
+ }
+
+ /**
+ * Return the label of this group
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ /**
+ * Get the CSS class
+ *
+ * @return string
+ */
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ /**
+ * Set the CSS class
+ *
+ * @param string $class
+ *
+ * @return $this
+ */
+ public function setClass($class)
+ {
+ $this->class = $class;
+ return $this;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeLine.php b/modules/monitoring/library/Monitoring/Timeline/TimeLine.php
new file mode 100644
index 0000000..128b64b
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Timeline/TimeLine.php
@@ -0,0 +1,491 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Timeline;
+
+use DateTime;
+use Exception;
+use ArrayIterator;
+use Icinga\Exception\IcingaException;
+use IteratorAggregate;
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Hook;
+use Icinga\Web\Session\SessionNamespace;
+use Icinga\Module\Monitoring\DataView\DataView;
+use Traversable;
+
+/**
+ * Represents a set of events in a specific range of time
+ */
+class TimeLine implements IteratorAggregate
+{
+ /**
+ * The resultset returned by the dataview
+ *
+ * @var array
+ */
+ private $resultset;
+
+ /**
+ * The groups this timeline uses for display purposes
+ *
+ * @var array
+ */
+ private $displayGroups;
+
+ /**
+ * The session to use
+ *
+ * @var SessionNamespace
+ */
+ protected $session;
+
+ /**
+ * The base that is used to calculate each circle's diameter
+ *
+ * @var float
+ */
+ protected $calculationBase;
+
+ /**
+ * The dataview to fetch entries from
+ *
+ * @var DataView
+ */
+ protected $dataview;
+
+ /**
+ * The names by which to group entries
+ *
+ * @var array
+ */
+ protected $identifiers;
+
+ /**
+ * The range of time for which to display entries
+ *
+ * @var TimeRange
+ */
+ protected $displayRange;
+
+ /**
+ * The range of time for which to calculate forecasts
+ *
+ * @var TimeRange
+ */
+ protected $forecastRange;
+
+ /**
+ * The maximum diameter each circle can have
+ *
+ * @var float
+ */
+ protected $circleDiameter = 100.0;
+
+ /**
+ * The minimum diameter each circle can have
+ *
+ * @var float
+ */
+ protected $minCircleDiameter = 1.0;
+
+ /**
+ * The unit of a circle's diameter
+ *
+ * @var string
+ */
+ protected $diameterUnit = 'px';
+
+ /**
+ * Return a iterator for this timeline
+ *
+ * @return ArrayIterator
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->toArray());
+ }
+
+ /**
+ * Create a new timeline
+ *
+ * The given dataview must provide the following columns:
+ * - name A string identifying an entry (Corresponds to the keys of "$identifiers")
+ * - time A unix timestamp that defines where to place an entry on the timeline
+ *
+ * @param DataView $dataview The dataview to fetch entries from
+ * @param array $identifiers The names by which to group entries
+ */
+ public function __construct(DataView $dataview, array $identifiers)
+ {
+ $this->dataview = $dataview;
+ $this->identifiers = $identifiers;
+ }
+
+ /**
+ * Set the session to use
+ *
+ * @param SessionNamespace $session The session to use
+ */
+ public function setSession(SessionNamespace $session)
+ {
+ $this->session = $session;
+ }
+
+ /**
+ * Set the range of time for which to display elements
+ *
+ * @param TimeRange $range The range of time for which to display elements
+ */
+ public function setDisplayRange(TimeRange $range)
+ {
+ $this->displayRange = $range;
+ }
+
+ /**
+ * Set the range of time for which to calculate forecasts
+ *
+ * @param TimeRange $range The range of time for which to calculate forecasts
+ */
+ public function setForecastRange(TimeRange $range)
+ {
+ $this->forecastRange = $range;
+ }
+
+ /**
+ * Set the maximum diameter each circle can have
+ *
+ * @param string $width The diameter to set, suffixed with its unit
+ *
+ * @throws Exception If the given diameter is invalid
+ */
+ public function setMaximumCircleWidth($width)
+ {
+ $matches = array();
+ if (preg_match('#([\d|\.]+)([a-z]+|%)#', $width, $matches)) {
+ $this->circleDiameter = floatval($matches[1]);
+ $this->diameterUnit = $matches[2];
+ } else {
+ throw new IcingaException(
+ 'Width "%s" is not a valid width',
+ $width
+ );
+ }
+ }
+
+ /**
+ * Set the minimum diameter each circle can have
+ *
+ * @param string $width The diameter to set, suffixed with its unit
+ *
+ * @throws Exception If the given diameter is invalid or its unit differs from the maximum
+ */
+ public function setMinimumCircleWidth($width)
+ {
+ $matches = array();
+ if (preg_match('#([\d|\.]+)([a-z]+|%)#', $width, $matches)) {
+ if ($matches[2] === $this->diameterUnit) {
+ $this->minCircleDiameter = floatval($matches[1]);
+ } else {
+ throw new IcingaException(
+ 'Unit needs to be in "%s"',
+ $this->diameterUnit
+ );
+ }
+ } else {
+ throw new IcingaException(
+ 'Width "%s" is not a valid width',
+ $width
+ );
+ }
+ }
+
+ /**
+ * Return all known group types (identifiers) with their respective labels and classess as array
+ *
+ * @return array
+ */
+ public function getGroupInfo()
+ {
+ $groupInfo = array();
+ foreach ($this->identifiers as $name => $attributes) {
+ if (isset($attributes['groupBy'])) {
+ $name = $attributes['groupBy'];
+ }
+
+ $groupInfo[$name]['class'] = $attributes['class'];
+ $groupInfo[$name]['label'] = $attributes['label'];
+ }
+
+ return $groupInfo;
+ }
+
+ /**
+ * Return the circle's diameter for the given event group
+ *
+ * @param TimeEntry $group The group for which to return a circle width
+ * @param int $precision Amount of decimal places to preserve
+ *
+ * @return string
+ */
+ public function calculateCircleWidth(TimeEntry $group, $precision = 0)
+ {
+ $base = $this->getCalculationBase(true);
+ $factor = log($group->getValue() * $group->getWeight(), $base) / 100;
+ $width = $this->circleDiameter * $factor;
+ return sprintf(
+ '%.' . $precision . 'F%s',
+ $width > $this->minCircleDiameter ? $width : $this->minCircleDiameter,
+ $this->diameterUnit
+ );
+ }
+
+ /**
+ * Return an extrapolated circle width for the given event group
+ *
+ * @param TimeEntry $group The event group for which to return an extrapolated circle width
+ * @param int $precision Amount of decimal places to preserve
+ *
+ * @return string
+ */
+ public function getExtrapolatedCircleWidth(TimeEntry $group, $precision = 0)
+ {
+ $eventCount = 0;
+ foreach ($this->displayGroups as $groups) {
+ if (array_key_exists($group->getName(), $groups)) {
+ $eventCount += $groups[$group->getName()]->getValue();
+ }
+ }
+
+ $extrapolatedCount = (int) $eventCount / count($this->displayGroups);
+ if ($extrapolatedCount < $group->getValue()) {
+ return $this->calculateCircleWidth($group, $precision);
+ }
+
+ return $this->calculateCircleWidth(
+ TimeEntry::fromArray(
+ array(
+ 'value' => $extrapolatedCount,
+ 'weight' => $group->getWeight()
+ )
+ ),
+ $precision
+ );
+ }
+
+ /**
+ * Return the base that should be used to calculate circle widths
+ *
+ * @param bool $create Whether to generate a new base if none is known yet
+ *
+ * @return float|null
+ */
+ public function getCalculationBase($create)
+ {
+ if ($this->calculationBase === null) {
+ $calculationBase = $this->session !== null ? $this->session->get('calculationBase') : null;
+
+ if ($create) {
+ $new = $this->generateCalculationBase();
+ if ($new > $calculationBase) {
+ $this->calculationBase = $new;
+
+ if ($this->session !== null) {
+ $this->session->calculationBase = $new;
+ }
+ } else {
+ $this->calculationBase = $calculationBase;
+ }
+ } else {
+ return $calculationBase;
+ }
+ }
+
+ return $this->calculationBase;
+ }
+
+ /**
+ * Generate a new base to calculate circle widths with
+ *
+ * @return float
+ */
+ protected function generateCalculationBase()
+ {
+ $allEntries = $this->groupEntries(
+ array_merge(
+ $this->fetchEntries(),
+ $this->fetchForecasts()
+ ),
+ new TimeRange(
+ $this->displayRange->getStart(),
+ $this->forecastRange->getEnd(),
+ $this->displayRange->getInterval()
+ )
+ );
+
+ $highestValue = 0;
+ foreach ($allEntries as $groups) {
+ foreach ($groups as $group) {
+ if ($group->getValue() * $group->getWeight() > $highestValue) {
+ $highestValue = $group->getValue() * $group->getWeight();
+ }
+ }
+ }
+
+ return pow($highestValue, 1 / 100); // 100 == 100%
+ }
+
+ /**
+ * Fetch all entries and forecasts by using the dataview associated with this timeline
+ *
+ * @return array The dataview's result
+ */
+ private function fetchResults()
+ {
+ $hookResults = array();
+ foreach (Hook::all('timeline') as $timelineProvider) {
+ $hookResults = array_merge(
+ $hookResults,
+ $timelineProvider->fetchEntries($this->displayRange),
+ $timelineProvider->fetchForecasts($this->forecastRange)
+ );
+
+ foreach ($timelineProvider->getIdentifiers() as $identifier => $attributes) {
+ if (!array_key_exists($identifier, $this->identifiers)) {
+ $this->identifiers[$identifier] = $attributes;
+ }
+ }
+ }
+
+ $query = $this->dataview;
+ $filter = Filter::matchAll(
+ Filter::where('type', array_keys($this->identifiers)),
+ Filter::expression('timestamp', '<=', $this->displayRange->getStart()->getTimestamp()),
+ Filter::expression('timestamp', '>', $this->displayRange->getEnd()->getTimestamp())
+ );
+ $query->applyFilter($filter);
+ return array_merge($query->getQuery()->fetchAll(), $hookResults);
+ }
+
+ /**
+ * Fetch all entries
+ *
+ * @return array The entries to display on the timeline
+ */
+ protected function fetchEntries()
+ {
+ if ($this->resultset === null) {
+ $this->resultset = $this->fetchResults();
+ }
+
+ $range = $this->displayRange;
+ return array_filter(
+ $this->resultset,
+ function ($e) use ($range) {
+ return $range->validateTime($e->time);
+ }
+ );
+ }
+
+ /**
+ * Fetch all forecasts
+ *
+ * @return array The entries to calculate forecasts with
+ */
+ protected function fetchForecasts()
+ {
+ if ($this->resultset === null) {
+ $this->resultset = $this->fetchResults();
+ }
+
+ $range = $this->forecastRange;
+ return array_filter(
+ $this->resultset,
+ function ($e) use ($range) {
+ return $range->validateTime($e->time);
+ }
+ );
+ }
+
+ /**
+ * Return the given entries grouped together
+ *
+ * @param array $entries The entries to group
+ * @param TimeRange $timeRange The range of time to group by
+ *
+ * @return array displayGroups The grouped entries
+ */
+ protected function groupEntries(array $entries, TimeRange $timeRange)
+ {
+ $counts = array();
+ foreach ($entries as $entry) {
+ $entryTime = new DateTime();
+ $entryTime->setTimestamp($entry->time);
+ $timestamp = $timeRange->findTimeframe($entryTime, true);
+
+ if ($timestamp !== null) {
+ if (array_key_exists($entry->name, $counts)) {
+ if (array_key_exists($timestamp, $counts[$entry->name])) {
+ $counts[$entry->name][$timestamp] += 1;
+ } else {
+ $counts[$entry->name][$timestamp] = 1;
+ }
+ } else {
+ $counts[$entry->name][$timestamp] = 1;
+ }
+ }
+ }
+
+ $groups = array();
+ foreach ($counts as $name => $data) {
+ foreach ($data as $timestamp => $count) {
+ $dateTime = new DateTime();
+ $dateTime->setTimestamp($timestamp);
+
+ $groupName = $name;
+ if (isset($this->identifiers[$name]['groupBy'])) {
+ $groupName = $this->identifiers[$name]['groupBy'];
+ }
+
+ if (isset($groups[$timestamp][$groupName])) {
+ $groups[$timestamp][$groupName]->setValue(
+ $groups[$timestamp][$groupName]->getValue() + $count
+ );
+ } else {
+ $groups[$timestamp][$groupName] = TimeEntry::fromArray(
+ array(
+ 'name' => $groupName,
+ 'value' => $count,
+ 'dateTime' => $dateTime,
+ 'class' => $this->identifiers[$name]['class'],
+ 'detailUrl' => $this->identifiers[$name]['detailUrl'],
+ 'label' => $this->identifiers[$name]['label']
+ )
+ );
+ }
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Return the contents of this timeline as array
+ *
+ * @return array
+ */
+ protected function toArray()
+ {
+ $this->displayGroups = $this->groupEntries($this->fetchEntries(), $this->displayRange);
+
+ $array = array();
+ foreach ($this->displayRange as $timestamp => $timeframe) {
+ $array[] = array(
+ $timeframe,
+ array_key_exists($timestamp, $this->displayGroups) ? $this->displayGroups[$timestamp] : array()
+ );
+ }
+
+ return $array;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Timeline/TimeRange.php b/modules/monitoring/library/Monitoring/Timeline/TimeRange.php
new file mode 100644
index 0000000..08c7a2c
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Timeline/TimeRange.php
@@ -0,0 +1,258 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Timeline;
+
+use StdClass;
+use Iterator;
+use DateTime;
+use DateInterval;
+use Icinga\Util\Format;
+
+/**
+ * A range of time split into a specific interval
+ *
+ * @see Iterator
+ */
+class TimeRange implements Iterator
+{
+ /**
+ * The start of this time range
+ *
+ * @var DateTime
+ */
+ protected $start;
+
+ /**
+ * The end of this time range
+ *
+ * @var DateTime
+ */
+ protected $end;
+
+ /**
+ * The interval by which this time range is split
+ *
+ * @var DateInterval
+ */
+ protected $interval;
+
+ /**
+ * The current date in the iteration
+ *
+ * @var DateTime
+ */
+ protected $current;
+
+ /**
+ * Whether the date iteration is negative
+ *
+ * @var bool
+ */
+ protected $negative;
+
+ /**
+ * Initialize a new time range
+ *
+ * @param DateTime $start When the time range should start
+ * @param DateTime $end When the time range should end
+ * @param DateInterval $interval The interval of the time range
+ */
+ public function __construct(DateTime $start, DateTime $end, DateInterval $interval)
+ {
+ $this->interval = $interval;
+ $this->start = $start;
+ $this->end = $end;
+ $this->negative = $this->start > $this->end;
+ }
+
+ /**
+ * Return when this range of time starts
+ *
+ * @return DateTime
+ */
+ public function getStart()
+ {
+ return $this->start;
+ }
+
+ /**
+ * Return when this range of time ends
+ *
+ * @return DateTime
+ */
+ public function getEnd()
+ {
+ return $this->end;
+ }
+
+ /**
+ * Return the interval by which this time range is split
+ *
+ * @return DateInterval
+ */
+ public function getInterval()
+ {
+ return $this->interval;
+ }
+
+ /**
+ * Return the appropriate timeframe for the given date and time or null if none could be found
+ *
+ * @param DateTime $dateTime The date and time for which to search the timeframe
+ * @param bool $asTimestamp Whether the start of the timeframe should be returned as timestamp
+ * @return StdClass|int An object with a ´start´ and ´end´ property or a timestamp
+ */
+ public function findTimeframe(DateTime $dateTime, $asTimestamp = false)
+ {
+ foreach ($this as $timeframeIdentifier => $timeframe) {
+ if ($this->negative) {
+ if ($dateTime <= $timeframe->start && $dateTime >= $timeframe->end) {
+ return $asTimestamp ? $timeframeIdentifier : $timeframe;
+ }
+ } elseif ($dateTime >= $timeframe->start && $dateTime <= $timeframe->end) {
+ return $asTimestamp ? $timeframeIdentifier : $timeframe;
+ }
+ }
+ }
+
+ /**
+ * Return whether the given time is within this range of time
+ *
+ * @param string|int|DateTime $time The timestamp or date and time to check
+ */
+ public function validateTime($time)
+ {
+ if ($time instanceof DateTime) {
+ $dateTime = $time;
+ } elseif (is_string($time)) {
+ $dateTime = DateTime::createFromFormat('d/m/Y g:i A', $time);
+ } else {
+ $dateTime = new DateTime();
+ $dateTime->setTimestamp($time);
+ }
+
+ return ($this->negative && ($dateTime <= $this->start && $dateTime >= $this->end)) ||
+ (!$this->negative && ($dateTime >= $this->start && $dateTime <= $this->end));
+ }
+
+ /**
+ * Return the appropriate timeframe for the given timeframe start
+ *
+ * @param int|DateTime $time The timestamp or date and time for which to return the timeframe
+ * @return StdClass An object with a ´start´ and ´end´ property
+ */
+ public function getTimeframe($time)
+ {
+ if ($time instanceof DateTime) {
+ $startTime = clone $time;
+ } else {
+ $startTime = new DateTime();
+ $startTime->setTimestamp($time);
+ }
+
+ return $this->buildTimeframe($startTime, $this->applyInterval(clone $startTime, 1));
+ }
+
+ /**
+ * Apply the current interval to the given date and time
+ *
+ * @param DateTime $dateTime The date and time to apply the interval to
+ * @param int $adjustBy By how much seconds the resulting date and time should be adjusted
+ *
+ * @return DateTime
+ */
+ protected function applyInterval(DateTime $dateTime, $adjustBy)
+ {
+ if (!$this->interval->y && !$this->interval->m) {
+ if ($this->negative) {
+ return $dateTime->sub($this->interval)->add(new DateInterval('PT' . $adjustBy . 'S'));
+ } else {
+ return $dateTime->add($this->interval)->sub(new DateInterval('PT' . $adjustBy . 'S'));
+ }
+ } elseif ($this->interval->m) {
+ for ($i = 0; $i < $this->interval->m; $i++) {
+ if ($this->negative) {
+ $dateTime->sub(new DateInterval('PT' . Format::secondsByMonth($dateTime) . 'S'));
+ } else {
+ $dateTime->add(new DateInterval('PT' . Format::secondsByMonth($dateTime) . 'S'));
+ }
+ }
+ } elseif ($this->interval->y) {
+ for ($i = 0; $i < $this->interval->y; $i++) {
+ if ($this->negative) {
+ $dateTime->sub(new DateInterval('PT' . Format::secondsByYear($dateTime) . 'S'));
+ } else {
+ $dateTime->add(new DateInterval('PT' . Format::secondsByYear($dateTime) . 'S'));
+ }
+ }
+ }
+ $adjustment = new DateInterval('PT' . $adjustBy . 'S');
+ return $this->negative ? $dateTime->add($adjustment) : $dateTime->sub($adjustment);
+ }
+
+ /**
+ * Return an object representation of the given timeframe
+ *
+ * @param DateTime $start The start of the timeframe
+ * @param DateTime $end The end of the timeframe
+ * @return StdClass
+ */
+ protected function buildTimeframe(DateTime $start, DateTime $end)
+ {
+ $timeframe = new StdClass();
+ $timeframe->start = $start;
+ $timeframe->end = $end;
+ return $timeframe;
+ }
+
+ /**
+ * Reset the iterator to its initial state
+ */
+ public function rewind(): void
+ {
+ $this->current = clone $this->start;
+ }
+
+ /**
+ * Return whether the current iteration step is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ if ($this->negative) {
+ return $this->current > $this->end;
+ } else {
+ return $this->current < $this->end;
+ }
+ }
+
+ /**
+ * Return the current value in the iteration
+ *
+ * @return StdClass
+ */
+ public function current(): object
+ {
+ return $this->getTimeframe($this->current);
+ }
+
+ /**
+ * Return a unique identifier for the current value in the iteration
+ *
+ * @return int
+ */
+ public function key(): int
+ {
+ return $this->current->getTimestamp();
+ }
+
+ /**
+ * Advance the iterator position by one
+ */
+ public function next(): void
+ {
+ $this->applyInterval($this->current, 0);
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/TransportStep.php b/modules/monitoring/library/Monitoring/TransportStep.php
new file mode 100644
index 0000000..d138eb4
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/TransportStep.php
@@ -0,0 +1,143 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring;
+
+use Exception;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Setup\Step;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+
+class TransportStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $transportConfig = $this->data['transportConfig'];
+ $transportName = $transportConfig['name'];
+ unset($transportConfig['name']);
+
+ try {
+ Config::fromArray(array($transportName => $transportConfig))
+ ->setConfigFile(Config::resolvePath('modules/monitoring/commandtransports.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->error = $e;
+ return false;
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ switch ($this->data['transportConfig']['transport']) {
+ case 'local':
+ $details = '<p>' . sprintf(
+ mt(
+ 'monitoring',
+ 'Icinga Web 2 will use the named pipe located at "%s"'
+ . ' to send commands to your monitoring instance.'
+ ),
+ $this->data['transportConfig']['path']
+ ) . '</p>';
+ break;
+ case 'remote':
+ $details = '<p>'
+ . sprintf(
+ mt(
+ 'monitoring',
+ 'Icinga Web 2 will use the named pipe located on a remote machine at "%s" to send commands'
+ . ' to your monitoring instance by using the connection details listed below:'
+ ),
+ $this->data['transportConfig']['path']
+ )
+ . '</p>'
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Remote Host') . '</strong></td>'
+ . '<td>' . $this->data['transportConfig']['host'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Remote SSH Port') . '</strong></td>'
+ . '<td>' . $this->data['transportConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Remote SSH User') . '</strong></td>'
+ . '<td>' . $this->data['transportConfig']['user'] . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+ break;
+ case 'api':
+ $details = '<p>'
+ . mt(
+ 'monitoring',
+ 'Icinga Web 2 will use the Icinga 2 API to send commands'
+ . ' to your monitoring instance by using the connection details listed below:'
+ )
+ . '</p>'
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Host') . '</strong></td>'
+ . '<td>' . $this->data['transportConfig']['host'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Port') . '</strong></td>'
+ . '<td>' . $this->data['transportConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Username') . '</strong></td>'
+ . '<td>' . $this->data['transportConfig']['username'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('monitoring', 'Password') . '</strong></td>'
+ . '<td>' . str_repeat('*', strlen($this->data['transportConfig']['password'])) . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+ break;
+ default:
+ throw new ProgrammingError(
+ 'Unknown command transport type: %s',
+ $this->data['transportConfig']['transport']
+ );
+ }
+
+ return '<h2>' . mt('monitoring', 'Command Transport', 'setup.page.title') . '</h2>'
+ . '<div class="topic">' . $details . '</div>';
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ return array(sprintf(
+ mt('monitoring', 'Command transport configuration has been successfully created: %s'),
+ Config::resolvePath('modules/monitoring/commandtransports.ini')
+ ));
+ } elseif ($this->error !== null) {
+ return array(
+ sprintf(
+ mt(
+ 'monitoring',
+ 'Command transport configuration could not be written to: %s. An error occured:'
+ ),
+ Config::resolvePath('modules/monitoring/commandtransports.ini')
+ ),
+ sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error))
+ );
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
new file mode 100644
index 0000000..014ac43
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
@@ -0,0 +1,337 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Controller;
+
+use Exception;
+use Icinga\Module\Monitoring\Controller;
+use Icinga\Module\Monitoring\Forms\Command\Object\CheckNowCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ObjectsCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\RemoveAcknowledgementCommandForm;
+use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandForm;
+use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook;
+use Icinga\Module\Monitoring\Hook\ObjectDetailsTabHook;
+use Icinga\Web\Hook;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
+
+/**
+ * Base class for the host and service controller
+ */
+abstract class MonitoredObjectController extends Controller
+{
+ /**
+ * The requested host or service
+ *
+ * @var \Icinga\Module\Monitoring\Object\Host|\Icinga\Module\Monitoring\Object\Host
+ */
+ protected $object;
+
+ /**
+ * URL to redirect to after a command was handled
+ *
+ * @var string
+ */
+ protected $commandRedirectUrl;
+
+ /**
+ * List of visible hooked tabs
+ *
+ * @var ObjectDetailsTabHook[]
+ */
+ protected $tabHooks = [];
+
+ /**
+ * (non-PHPDoc)
+ * @see \Icinga\Web\Controller\ActionController For the method documentation.
+ */
+ public function prepareInit()
+ {
+ parent::prepareInit();
+ if (Hook::has('ticket')) {
+ $this->view->tickets = Hook::first('ticket');
+ }
+ if (Hook::has('grapher')) {
+ $this->view->graphers = Hook::all('grapher');
+ }
+ }
+
+ /**
+ * Show a host or service
+ */
+ public function showAction()
+ {
+ $this->setAutorefreshInterval(10);
+ $this->setupQuickActionForms();
+ $auth = $this->Auth();
+ $this->object->populate();
+ $this->handleFormatRequest();
+ $toggleFeaturesForm = new ToggleObjectFeaturesCommandForm(array(
+ 'backend' => $this->backend,
+ 'objects' => $this->object
+ ));
+ $toggleFeaturesForm
+ ->load($this->object)
+ ->handleRequest();
+ $this->view->toggleFeaturesForm = $toggleFeaturesForm;
+ if (! empty($this->object->comments) && $auth->hasPermission('monitoring/command/comment/delete')) {
+ $delCommentForm = new DeleteCommentCommandForm();
+ $delCommentForm->handleRequest();
+ $this->view->delCommentForm = $delCommentForm;
+ }
+ if (! empty($this->object->downtimes) && $auth->hasPermission('monitoring/command/downtime/delete')) {
+ $delDowntimeForm = new DeleteDowntimeCommandForm();
+ $delDowntimeForm->handleRequest();
+ $this->view->delDowntimeForm = $delDowntimeForm;
+ }
+ $this->view->showInstance = $this->backend->select()->from('instance')->count() > 1;
+ $this->view->object = $this->object;
+
+ $this->view->extensionsHtml = array();
+ foreach (Hook::all('Monitoring\DetailviewExtension') as $hook) {
+ /** @var DetailviewExtensionHook $hook */
+
+ try {
+ $html = $hook->setView($this->view)->getHtmlForObject($this->object);
+ } catch (Exception $e) {
+ $html = $this->view->escape($e->getMessage());
+ }
+
+ if ($html) {
+ $module = $this->view->escape($hook->getModule()->getName());
+ $this->view->extensionsHtml[] =
+ '<div class="icinga-module module-' . $module . '" data-icinga-module="' . $module . '">'
+ . $html
+ . '</div>';
+ }
+ }
+ }
+
+ /**
+ * Show the history for a host or service
+ */
+ public function historyAction()
+ {
+ $this->getTabs()->activate('history');
+ $this->view->history = $this->object->fetchEventHistory()->eventhistory;
+ $this->applyRestriction('monitoring/filter/objects', $this->view->history);
+
+ $this->setupLimitControl(50);
+ $this->setupPaginationControl($this->view->history, 50);
+ $this->view->object = $this->object;
+ $this->render('object/detail-history', null, true);
+ }
+
+ /**
+ * Show the content of a custom tab
+ */
+ public function tabhookAction()
+ {
+ $hookName = $this->params->get('hook');
+ $this->getTabs()->activate($hookName);
+
+ $hook = $this->tabHooks[$hookName];
+
+ $this->view->header = $hook->getHeader($this->object, $this->getRequest());
+ $this->view->content = $hook->getContent($this->object, $this->getRequest());
+ $this->view->object = $this->object;
+ $this->render('object/detail-tabhook', null, true);
+ }
+
+ /**
+ * Handle a command form
+ *
+ * @param ObjectsCommandForm $form
+ *
+ * @return ObjectsCommandForm
+ */
+ protected function handleCommandForm(ObjectsCommandForm $form)
+ {
+ $form
+ ->setBackend($this->backend)
+ ->setObjects($this->object)
+ ->setRedirectUrl(Url::fromPath($this->commandRedirectUrl)->setParams($this->params))
+ ->handleRequest();
+ $this->view->form = $form;
+ $this->view->object = $this->object;
+ $this->view->tabs->remove('dashboard');
+ $this->view->tabs->remove('menu-entry');
+ $this->_helper->viewRenderer('partials/command/object-command-form', null, true);
+ $this->setupQuickActionForms();
+ return $form;
+ }
+
+ /**
+ * Export to JSON if requested
+ */
+ protected function handleFormatRequest($query = null)
+ {
+ if ($this->params->get('format') === 'json'
+ || $this->getRequest()->getHeader('Accept') === 'application/json'
+ ) {
+ $payload = (array) $this->object->properties;
+ $payload['vars'] = $this->object->customvars;
+
+ if ($this->hasPermission('*') || ! $this->hasPermission('no-monitoring/contacts')) {
+ $payload['contacts'] = $this->object->contacts->fetchPairs();
+ $payload['contact_groups'] = $this->object->contactgroups->fetchPairs();
+ } else {
+ $payload['contacts'] = [];
+ $payload['contact_groups'] = [];
+ }
+
+ $groupName = $this->object->getType() . 'groups';
+ $payload[$groupName] = $this->object->$groupName;
+ $this->getResponse()->json()
+ ->setSuccessData($payload)
+ ->setAutoSanitize()
+ ->sendResponse();
+ }
+ }
+
+ /**
+ * Acknowledge a problem
+ */
+ abstract public function acknowledgeProblemAction();
+
+ /**
+ * Add a comment
+ */
+ abstract public function addCommentAction();
+
+ /**
+ * Reschedule a check
+ */
+ abstract public function rescheduleCheckAction();
+
+ /**
+ * Schedule a downtime
+ */
+ abstract public function scheduleDowntimeAction();
+
+ /**
+ * Create tabs
+ */
+ protected function createTabs()
+ {
+ $tabs = $this->getTabs();
+ $object = $this->object;
+ if ($object->getType() === $object::TYPE_HOST) {
+ $isService = false;
+ $params = array(
+ 'host' => $object->getName()
+ );
+ if ($this->params->has('service')) {
+ $params['service'] = $this->params->get('service');
+ }
+ } else {
+ $isService = true;
+ $params = array(
+ 'host' => $object->getHost()->getName(),
+ 'service' => $object->getName()
+ );
+ }
+ $tabs->add(
+ 'host',
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for host %s'),
+ $isService ? $object->getHost()->getName() : $object->getName()
+ ),
+ 'label' => $this->translate('Host'),
+ 'url' => 'monitoring/host/show',
+ 'urlParams' => $params
+ )
+ );
+ if ($isService || $this->params->has('service')) {
+ $tabs->add(
+ 'service',
+ array(
+ 'title' => sprintf(
+ $this->translate('Show detailed information for service %s on host %s'),
+ $isService ? $object->getName() : $this->params->get('service'),
+ $isService ? $object->getHost()->getName() : $object->getName()
+ ),
+ 'label' => $this->translate('Service'),
+ 'url' => 'monitoring/service/show',
+ 'urlParams' => $params
+ )
+ );
+ }
+ $tabs->add(
+ 'services',
+ array(
+ 'title' => sprintf(
+ $this->translate('List all services on host %s'),
+ $isService ? $object->getHost()->getName() : $object->getName()
+ ),
+ 'label' => $this->translate('Services'),
+ 'url' => 'monitoring/host/services',
+ 'urlParams' => $params
+ )
+ );
+ if ($this->backend->hasQuery('eventhistory')) {
+ $tabs->add(
+ 'history',
+ array(
+ 'title' => $isService
+ ? sprintf(
+ $this->translate('Show all event records of service %s on host %s'),
+ $object->getName(),
+ $object->getHost()->getName()
+ )
+ : sprintf($this->translate('Show all event records of host %s'), $object->getName())
+ ,
+ 'label' => $this->translate('History'),
+ 'url' => $isService ? 'monitoring/service/history' : 'monitoring/host/history',
+ 'urlParams' => $params
+ )
+ );
+ }
+
+ /** @var ObjectDetailsTabHook $hook */
+ foreach (Hook::all('Monitoring\\ObjectDetailsTab') as $hook) {
+ $hookName = $hook->getName();
+ if ($hook->shouldBeShown($object, $this->Auth())) {
+ $this->tabHooks[$hookName] = $hook;
+ $tabs->add($hookName, [
+ 'label' => $hook->getLabel(),
+ 'url' => $isService ? 'monitoring/service/tabhook' : 'monitoring/host/tabhook',
+ 'urlParams' => $params + [ 'hook' => $hookName ]
+ ]);
+ }
+ }
+
+ $tabs->extend(new DashboardAction())->extend(new MenuAction());
+ }
+
+ /**
+ * Create quick action forms and pass them to the view
+ */
+ protected function setupQuickActionForms()
+ {
+ $auth = $this->Auth();
+ if ($auth->hasPermission('monitoring/command/schedule-check')
+ || ($auth->hasPermission('monitoring/command/schedule-check/active-only')
+ && $this->object->active_checks_enabled
+ )
+ ) {
+ $this->view->checkNowForm = $checkNowForm = new CheckNowCommandForm();
+ $checkNowForm
+ ->setObjects($this->object)
+ ->handleRequest();
+ }
+ if (! in_array((int) $this->object->state, array(0, 99))
+ && $this->object->acknowledged
+ && $auth->hasPermission('monitoring/command/remove-acknowledgement')
+ ) {
+ $this->view->removeAckForm = $removeAckForm = new RemoveAcknowledgementCommandForm();
+ $removeAckForm
+ ->setObjects($this->object)
+ ->handleRequest();
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php b/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php
new file mode 100644
index 0000000..50b6c65
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Helper/PluginOutputHookRenderer.php
@@ -0,0 +1,105 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Helper;
+
+use Icinga\Application\Logger;
+use Icinga\Web\Hook;
+
+/**
+ * Renderer for plugin output based on hooks
+ */
+class PluginOutputHookRenderer
+{
+ /** @var array */
+ protected $commandMap = [];
+
+ /**
+ * Register PluginOutput hooks
+ *
+ * Map PluginOutput hooks to their responsible commands.
+ *
+ * @return $this
+ */
+ public function registerHooks()
+ {
+ if (! Hook::has('monitoring/PluginOutput')) {
+ return $this;
+ }
+
+ foreach (Hook::all('monitoring/PluginOutput') as $hook) {
+ /** @var \Icinga\Module\Monitoring\Hook\PluginOutputHook $hook */
+ try {
+ $commands = $hook->getCommands();
+ } catch (\Exception $e) {
+ Logger::error(
+ 'Failed to get applicable commands from hook "%s". An error occurred: %s',
+ get_class($hook),
+ $e
+ );
+
+ continue;
+ }
+
+ if (! is_array($commands)) {
+ $commands = [$commands];
+ }
+
+ foreach ($commands as $command) {
+ if (! isset($this->commandMap[$command])) {
+ $this->commandMap[$command] = [];
+ }
+
+ $this->commandMap[$command][] = $hook;
+ }
+ }
+
+ return $this;
+ }
+
+ protected function renderCommand($command, $output, $detail)
+ {
+ if (isset($this->commandMap[$command])) {
+ foreach ($this->commandMap[$command] as $hook) {
+ /** @var \Icinga\Module\Monitoring\Hook\PluginOutputHook $hook */
+
+ try {
+ $output = $hook->render($command, $output, $detail);
+ } catch (\Exception $e) {
+ Logger::error(
+ 'Failed to render plugin output from hook "%s". An error occurred: %s',
+ get_class($hook),
+ $e
+ );
+
+ continue;
+ }
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Render the given plugin output based on the specified check command
+ *
+ * Traverse all hooks which are responsible for the specified check command and call their `render()` methods.
+ *
+ * @param string $command Check command
+ * @param string $output Plugin output
+ * @param bool $detail Whether the output is requested from the detail area
+ *
+ * @return string
+ */
+ public function render($command, $output, $detail)
+ {
+ if (empty($this->commandMap)) {
+ return $output;
+ }
+
+ $output = $this->renderCommand('*', $output, $detail);
+ $output = $this->renderCommand($command, $output, $detail);
+
+ return $output;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php
new file mode 100644
index 0000000..fdfe18f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/HostActionsHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Hook;
+
+use Icinga\Module\Monitoring\Hook\HostActionsHook as BaseHook;
+
+/**
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
+ */
+abstract class HostActionsHook extends BaseHook
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
new file mode 100644
index 0000000..0ffbf45
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/ServiceActionsHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Hook;
+
+use Icinga\Module\Monitoring\Hook\ServiceActionsHook as BaseHook;
+
+/**
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
+ */
+abstract class ServiceActionsHook extends BaseHook
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
new file mode 100644
index 0000000..f6f110f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Hook/TimelineProviderHook.php
@@ -0,0 +1,15 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Hook;
+
+use Icinga\Module\Monitoring\Hook\TimelineProviderHook as BaseHook;
+
+/**
+ * Compat only
+ *
+ * Please implement hooks in our Hook direcory
+ */
+abstract class TimelineProviderHook extends BaseHook
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Action.php b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php
new file mode 100644
index 0000000..7e4ffe3
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/Action.php
@@ -0,0 +1,123 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Module\Monitoring\Object\Macro;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use Icinga\Web\Url;
+
+/**
+ * Action for monitored objects
+ */
+class Action extends NavigationItem
+{
+ /**
+ * Whether this action's macros were already resolved
+ *
+ * @var bool
+ */
+ protected $resolved = false;
+
+ /**
+ * This action's object
+ *
+ * @var MonitoredObject
+ */
+ protected $object;
+
+ /**
+ * The filter to use when being asked whether to render this action
+ *
+ * @var string
+ */
+ protected $filter;
+
+ /**
+ * This action's raw url attribute
+ *
+ * @var string
+ */
+ protected $rawUrl;
+
+ /**
+ * Set this action's object
+ *
+ * @param MonitoredObject $object
+ *
+ * @return $this
+ */
+ public function setObject(MonitoredObject $object)
+ {
+ $this->object = $object;
+ return $this;
+ }
+
+ /**
+ * Return this action's object
+ *
+ * @return MonitoredObject
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+
+ /**
+ * Set the filter to use when being asked whether to render this action
+ *
+ * @param string $filter
+ *
+ * @return $this
+ */
+ public function setFilter($filter)
+ {
+ $this->filter = $filter;
+ return $this;
+ }
+
+ /**
+ * Return the filter to use when being asked whether to render this action
+ *
+ * @return string
+ */
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function setUrl($url)
+ {
+ if (is_string($url)) {
+ $this->rawUrl = $url;
+ } else {
+ parent::setUrl($url);
+ }
+
+ return $this;
+ }
+
+ public function getUrl()
+ {
+ $url = parent::getUrl();
+ if (! $this->resolved && $url === null && $this->rawUrl !== null) {
+ $this->setUrl(Url::fromPath(Macro::resolveMacros($this->rawUrl, $this->getObject())));
+ $this->resolved = true;
+ return parent::getUrl();
+ } else {
+ return $url;
+ }
+ }
+
+ public function getRender()
+ {
+ if ($this->render === null) {
+ $filter = $this->getFilter();
+ $this->render = $filter ? Filter::fromQueryString($filter)->matches($this->getObject()) : true;
+ }
+
+ return $this->render;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php
new file mode 100644
index 0000000..2e950f1
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostAction.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A host action
+ */
+class HostAction extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php b/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php
new file mode 100644
index 0000000..2cf0cdf
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/HostNote.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A host note
+ */
+class HostNote extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php
new file mode 100644
index 0000000..e06526e
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/Renderer/MonitoringBadgeNavigationItemRenderer.php
@@ -0,0 +1,167 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation\Renderer;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Authentication\Auth;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filterable;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
+
+/**
+ * Render generic DataView columns as badges in menu items
+ *
+ * It is possible to configure the class of the rendered badge as option 'class', the
+ * columns to fetch using the option 'columns' and the DataView from which the columns
+ * will be fetched using the option 'dataview'.
+ */
+class MonitoringBadgeNavigationItemRenderer extends BadgeNavigationItemRenderer
+{
+ /**
+ * Cached count
+ *
+ * @var int
+ */
+ protected $count;
+
+ /**
+ * Caches the responses for all executed summaries
+ *
+ * @var array
+ */
+ protected static $summaries = array();
+
+ /**
+ * Accumulates all needed columns for a view to allow fetching the needed columns in
+ * one single query
+ *
+ * @var array
+ */
+ protected static $dataViews = array();
+
+ /**
+ * The dataview referred to by the navigation item
+ *
+ * @var string
+ */
+ protected $dataView;
+
+ /**
+ * The columns and titles displayed in the badge
+ *
+ * @var array
+ */
+ protected $columns;
+
+ /**
+ * Set the dataview referred to by the navigation item
+ *
+ * @param string $dataView
+ *
+ * @return $this
+ */
+ public function setDataView($dataView)
+ {
+ $this->dataView = $dataView;
+ return $this;
+ }
+
+ /**
+ * Return the dataview referred to by the navigation item
+ *
+ * @return string
+ */
+ public function getDataView()
+ {
+ return $this->dataView;
+ }
+
+ /**
+ * Set the columns and titles displayed in the badge
+ *
+ * @param array $columns
+ *
+ * @return $this
+ */
+ public function setColumns(array $columns)
+ {
+ $this->columns = $columns;
+ return $this;
+ }
+
+ /**
+ * Return the columns and titles displayed in the badge
+ *
+ * @return array
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Apply a restriction on the given data view
+ *
+ * @param string $restriction The name of restriction
+ * @param Filterable $filterable The filterable to restrict
+ *
+ * @return Filterable The filterable
+ */
+ protected static function applyRestriction($restriction, Filterable $filterable)
+ {
+ $restrictions = Filter::matchAny();
+ foreach (Auth::getInstance()->getRestrictions($restriction) as $filter) {
+ $restrictions->addFilter(Filter::fromQueryString($filter));
+ }
+ $filterable->applyFilter($restrictions);
+ return $filterable;
+ }
+
+ /**
+ * Fetch the dataview from the database
+ *
+ * @return object
+ */
+ protected function fetchDataView()
+ {
+ $summary = MonitoringBackend::instance()->select()->from(
+ $this->getDataView(),
+ array_keys($this->getColumns())
+ );
+ static::applyRestriction('monitoring/filter/objects', $summary);
+ return $summary->fetchRow();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCount()
+ {
+ if ($this->count === null) {
+ try {
+ $summary = $this->fetchDataView();
+ } catch (Exception $e) {
+ Logger::debug($e);
+ $this->count = 1;
+ $this->state = static::STATE_UNKNOWN;
+ $this->title = $e->getMessage();
+ return $this->count;
+ }
+ $count = 0;
+ $titles = array();
+ foreach ($this->getColumns() as $column => $title) {
+ if (isset($summary->$column) && $summary->$column > 0) {
+ $titles[] = sprintf($title, $summary->$column);
+ $count += $summary->$column;
+ }
+ }
+ $this->count = $count;
+ $this->title = implode('. ', $titles);
+ }
+
+ return $this->count;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php
new file mode 100644
index 0000000..a88e94f
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceAction.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A service action
+ */
+class ServiceAction extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php
new file mode 100644
index 0000000..4858bf5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Navigation/ServiceNote.php
@@ -0,0 +1,11 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Navigation;
+
+/**
+ * A service note
+ */
+class ServiceNote extends Action
+{
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php b/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php
new file mode 100644
index 0000000..fcbe0ca
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Rest/RestRequest.php
@@ -0,0 +1,297 @@
+<?php
+/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Rest;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Util\Json;
+use Icinga\Module\Monitoring\Exception\CurlException;
+
+/**
+ * REST Request
+ */
+class RestRequest
+{
+ /**
+ * Request URI
+ *
+ * @var string
+ */
+ protected $uri;
+
+ /**
+ * Request method
+ *
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * Request content type
+ *
+ * @var string
+ */
+ protected $contentType;
+
+ /**
+ * Whether to authenticate with basic auth
+ *
+ * @var bool
+ */
+ protected $hasBasicAuth;
+
+ /**
+ * Auth username
+ *
+ * @var string
+ */
+ protected $username;
+
+ /**
+ * Auth password
+ *
+ * @var string
+ */
+ protected $password;
+
+ /**
+ * Request payload
+ *
+ * @var mixed
+ */
+ protected $payload;
+
+ /**
+ * Whether strict SSL is enabled
+ *
+ * @var bool
+ */
+ protected $strictSsl = true;
+
+ /**
+ * Request timeout
+ *
+ * @var int
+ */
+ protected $timeout = 30;
+
+ /**
+ * Create a GET REST request
+ *
+ * @param string $uri
+ *
+ * @return static
+ */
+ public static function get($uri)
+ {
+ $request = new static;
+ $request->uri = $uri;
+ $request->method = 'GET';
+ return $request;
+ }
+
+ /**
+ * Create a POST REST request
+ *
+ * @param string $uri
+ *
+ * @return static
+ */
+ public static function post($uri)
+ {
+ $request = new static;
+ $request->uri = $uri;
+ $request->method = 'POST';
+ return $request;
+ }
+
+ /**
+ * Send content type JSON
+ *
+ * @return $this
+ */
+ public function sendJson()
+ {
+ $this->contentType = 'application/json';
+
+ return $this;
+ }
+
+ /**
+ * Set basic auth credentials
+ *
+ * @param string $username
+ * @param string $password
+ *
+ * @return $this
+ */
+ public function authenticateWith($username, $password)
+ {
+ $this->hasBasicAuth = true;
+ $this->username = $username;
+ $this->password = $password;
+
+ return $this;
+ }
+
+ /**
+ * Set request payload
+ *
+ * @param mixed $payload
+ *
+ * @return $this
+ */
+ public function setPayload($payload)
+ {
+ $this->payload = $payload;
+
+ return $this;
+ }
+
+ /**
+ * Disable strict SSL
+ *
+ * @return $this
+ */
+ public function noStrictSsl()
+ {
+ $this->strictSsl = false;
+
+ return $this;
+ }
+
+ /**
+ * Serialize payload according to content type
+ *
+ * @param mixed $payload
+ * @param string $contentType
+ *
+ * @return string
+ */
+ public function serializePayload($payload, $contentType)
+ {
+ switch ($contentType) {
+ case 'application/json':
+ $payload = Json::encode($payload);
+ break;
+ }
+
+ return $payload;
+ }
+
+ /**
+ * Send the request
+ *
+ * @return mixed
+ *
+ * @throws Exception
+ */
+ public function send()
+ {
+ $defaults = array(
+ 'host' => 'localhost',
+ 'path' => '/'
+ );
+
+ $url = array_merge($defaults, parse_url($this->uri));
+
+ if (isset($url['port'])) {
+ $url['host'] .= sprintf(':%u', $url['port']);
+ }
+
+ if (isset($url['query'])) {
+ $url['path'] .= sprintf('?%s', $url['query']);
+ }
+
+ $headers = array(
+ "{$this->method} {$url['path']} HTTP/1.1",
+ "Host: {$url['host']}",
+ "Content-Type: {$this->contentType}",
+ 'Accept: application/json',
+ // Bypass "Expect: 100-continue" timeouts
+ 'Expect:'
+ );
+
+ $options = array(
+ CURLOPT_URL => $this->uri,
+ CURLOPT_TIMEOUT => $this->timeout,
+ // Ignore proxy settings
+ CURLOPT_PROXY => '',
+ CURLOPT_CUSTOMREQUEST => $this->method
+ );
+
+ // Record cURL command line for debugging
+ $curlCmd = array('curl', '-s', '-X', $this->method, '-H', escapeshellarg('Accept: application/json'));
+
+ if ($this->strictSsl) {
+ $options[CURLOPT_SSL_VERIFYHOST] = 2;
+ $options[CURLOPT_SSL_VERIFYPEER] = true;
+ } else {
+ $options[CURLOPT_SSL_VERIFYHOST] = false;
+ $options[CURLOPT_SSL_VERIFYPEER] = false;
+ $curlCmd[] = '-k';
+ }
+
+ if ($this->hasBasicAuth) {
+ $options[CURLOPT_USERPWD] = sprintf('%s:%s', $this->username, $this->password);
+ $curlCmd[] = sprintf('-u %s:%s', escapeshellarg($this->username), escapeshellarg($this->password));
+ }
+
+ if (! empty($this->payload)) {
+ $payload = $this->serializePayload($this->payload, $this->contentType);
+ $options[CURLOPT_POSTFIELDS] = $payload;
+ $curlCmd[] = sprintf('-d %s', escapeshellarg($payload));
+ }
+
+ $options[CURLOPT_HTTPHEADER] = $headers;
+
+ $stream = null;
+ $logger = Logger::getInstance();
+ if ($logger !== null && $logger->getLevel() === Logger::DEBUG) {
+ $stream = fopen('php://temp', 'w');
+ $options[CURLOPT_VERBOSE] = true;
+ $options[CURLOPT_STDERR] = $stream;
+ }
+
+ Logger::debug(
+ 'Executing %s %s',
+ implode(' ', $curlCmd),
+ escapeshellarg($this->uri)
+ );
+
+ $result = $this->curlExec($options);
+
+ if (is_resource($stream)) {
+ rewind($stream);
+ Logger::debug(stream_get_contents($stream));
+ fclose($stream);
+ }
+
+ return Json::decode($result, true);
+ }
+
+ /**
+ * Set up a new cURL handle with the given options and call {@link curl_exec()}
+ *
+ * @param array $options
+ *
+ * @return string The response
+ *
+ * @throws CurlException
+ */
+ protected function curlExec(array $options)
+ {
+ $ch = curl_init();
+ $options[CURLOPT_RETURNTRANSFER] = true;
+ curl_setopt_array($ch, $options);
+ $result = curl_exec($ch);
+
+ if ($result === false) {
+ throw new CurlException('%s', curl_error($ch));
+ }
+
+ curl_close($ch);
+ return $result;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php
new file mode 100644
index 0000000..4cbdad5
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Widget/CustomVarTable.php
@@ -0,0 +1,270 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Widget;
+
+use Icinga\Module\Monitoring\Hook\CustomVarRendererHook;
+use Icinga\Module\Monitoring\Object\MonitoredObject;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Web\Widget\Icon;
+
+class CustomVarTable extends BaseHtmlElement
+{
+ /** @var iterable The variables */
+ protected $data;
+
+ /** @var ?MonitoredObject The object the variables are bound to */
+ protected $object;
+
+ /** @var Closure Callback to apply hooks */
+ protected $hookApplier;
+
+ /** @var array The groups as identified by hooks */
+ protected $groups = [];
+
+ /** @var string Header title */
+ protected $headerTitle;
+
+ /** @var int The nesting level */
+ protected $level = 0;
+
+ protected $tag = 'table';
+
+ /** @var HtmlElement The table body */
+ protected $body;
+
+ protected $defaultAttributes = [
+ 'class' => ['custom-var-table', 'name-value-table']
+ ];
+
+ /**
+ * Create a new CustomVarTable
+ *
+ * @param iterable $data
+ * @param ?MonitoredObject $object
+ */
+ public function __construct($data, MonitoredObject $object = null)
+ {
+ $this->data = $data;
+ $this->object = $object;
+ $this->body = new HtmlElement('tbody');
+ }
+
+ /**
+ * Set the header to show
+ *
+ * @param string $title
+ *
+ * @return $this
+ */
+ protected function setHeader($title)
+ {
+ $this->headerTitle = (string) $title;
+
+ return $this;
+ }
+
+ /**
+ * Add a new row to the body
+ *
+ * @param mixed $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ protected function addRow($name, $value)
+ {
+ $this->body->addHtml(new HtmlElement(
+ 'tr',
+ Attributes::create(['class' => "level-{$this->level}"]),
+ new HtmlElement('th', null, Html::wantHtml($name)),
+ new HtmlElement('td', null, Html::wantHtml($value))
+ ));
+ }
+
+ /**
+ * Render a variable
+ *
+ * @param mixed $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ protected function renderVar($name, $value)
+ {
+ if ($this->object !== null && $this->level === 0) {
+ list($name, $value, $group) = call_user_func($this->hookApplier, $name, $value);
+ if ($group !== null) {
+ $this->groups[$group][] = [$name, $value];
+ return;
+ }
+ }
+
+ $isArray = is_array($value);
+ if (! $isArray && $value instanceof \stdClass) {
+ $value = (array) $value;
+ $isArray = true;
+ }
+
+ switch (true) {
+ case $isArray && is_int(key($value)):
+ $this->renderArray($name, $value);
+ break;
+ case $isArray:
+ $this->renderObject($name, $value);
+ break;
+ default:
+ $this->renderScalar($name, $value);
+ }
+ }
+
+ /**
+ * Render an array
+ *
+ * @param mixed $name
+ * @param array $array
+ *
+ * @return void
+ */
+ protected function renderArray($name, array $array)
+ {
+ $numItems = count($array);
+ $name = (new HtmlDocument())->addHtml(
+ Html::wantHtml($name),
+ Text::create(' (Array)')
+ );
+
+ $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems));
+
+ ++$this->level;
+
+ ksort($array);
+ foreach ($array as $key => $value) {
+ $this->renderVar("[$key]", $value);
+ }
+
+ --$this->level;
+ }
+
+ /**
+ * Render an object (associative array)
+ *
+ * @param mixed $name
+ * @param array $object
+ *
+ * @return void
+ */
+ protected function renderObject($name, array $object)
+ {
+ $numItems = count($object);
+ $this->addRow($name, sprintf(tp('%d item', '%d items', $numItems), $numItems));
+
+ ++$this->level;
+
+ ksort($object);
+ foreach ($object as $key => $value) {
+ $this->renderVar($key, $value);
+ }
+
+ --$this->level;
+ }
+
+ /**
+ * Render a scalar
+ *
+ * @param mixed $name
+ * @param mixed $value
+ *
+ * @return void
+ */
+ protected function renderScalar($name, $value)
+ {
+ if ($value === '') {
+ $value = new HtmlElement('span', Attributes::create(['class' => 'empty']), Text::create(t('empty string')));
+ }
+
+ $this->addRow($name, $value);
+ }
+
+ /**
+ * Render a group
+ *
+ * @param string $name
+ * @param iterable $entries
+ *
+ * @return void
+ */
+ protected function renderGroup($name, $entries)
+ {
+ $table = new self($entries);
+
+ $wrapper = $this->getWrapper();
+ if ($wrapper === null) {
+ $wrapper = new HtmlDocument();
+ $wrapper->addHtml($this);
+ $this->prependWrapper($wrapper);
+ }
+
+ $wrapper->addHtml($table->setHeader($name));
+ }
+
+ protected function assemble()
+ {
+ if ($this->object !== null) {
+ $this->hookApplier = CustomVarRendererHook::prepareForObject($this->object);
+ }
+
+ if ($this->headerTitle !== null) {
+ $this->getAttributes()
+ ->add('class', 'collapsible')
+ ->add('data-visible-height', 100)
+ ->add('data-toggle-element', 'thead')
+ ->add(
+ 'id',
+ preg_replace('/\s+/', '-', strtolower($this->headerTitle)) . '-customvars'
+ );
+
+ $this->addHtml(new HtmlElement('thead', null, new HtmlElement(
+ 'tr',
+ null,
+ new HtmlElement(
+ 'th',
+ Attributes::create(['colspan' => 2]),
+ new HtmlElement(
+ 'span',
+ null,
+ new Icon('angle-right'),
+ new Icon('angle-down')
+ ),
+ Text::create($this->headerTitle)
+ )
+ )));
+ }
+
+ if (is_array($this->data)) {
+ ksort($this->data);
+ }
+
+ foreach ($this->data as $name => $value) {
+ $this->renderVar($name, $value);
+ }
+
+ $this->addHtml($this->body);
+
+ // Hooks can return objects as replacement for keys, hence a generator is needed for group entries
+ $genGenerator = function ($entries) {
+ foreach ($entries as list($key, $value)) {
+ yield $key => $value;
+ }
+ };
+
+ foreach ($this->groups as $group => $entries) {
+ $this->renderGroup($group, $genGenerator($entries));
+ }
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
new file mode 100644
index 0000000..48b98ac
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Widget/SelectBox.php
@@ -0,0 +1,120 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Widget;
+
+use Icinga\Web\Form;
+use Icinga\Web\Request;
+use Icinga\Web\Widget\AbstractWidget;
+
+class SelectBox extends AbstractWidget
+{
+ /**
+ * The name of the form that will be created
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * An array containing all intervals with their associated labels
+ *
+ * @var array
+ */
+ private $values;
+
+ /**
+ * The label displayed next to the select box
+ *
+ * @var string
+ */
+ private $label;
+
+ /**
+ * The name of the url parameter to set
+ *
+ * @var string
+ */
+ private $parameter;
+
+ /**
+ * A request object used for initial form population
+ *
+ * @var Request
+ */
+ private $request;
+
+ /**
+ * Create a TimelineIntervalBox
+ *
+ * @param string $name The name of the form that will be created
+ * @param array $values An array containing all intervals with their associated labels
+ * @param string $label The label displayed next to the select box
+ * @param string $param The request parameter name to set
+ */
+ public function __construct($name, array $values, $label = 'Select', $param = 'selection')
+ {
+ $this->name = $name;
+ $this->values = $values;
+ $this->label = $label;
+ $this->parameter = $param;
+ }
+
+ /**
+ * Apply the parameters from the given request on this widget
+ *
+ * @param Request $request The request to use for populating the form
+ */
+ public function applyRequest(Request $request)
+ {
+ $this->request = $request;
+ }
+
+ /**
+ * Return the chosen interval value or null
+ *
+ * @param Request $request The request to fetch the value from
+ *
+ * @return string|null
+ */
+ public function getInterval(Request $request = null)
+ {
+ if ($request === null && $this->request) {
+ $request = $this->request;
+ }
+
+ if ($request) {
+ return $request->getParam('interval');
+ }
+ }
+
+ /**
+ * Renders this widget and returns the HTML as a string
+ *
+ * @return string
+ */
+ public function render()
+ {
+ $form = new Form();
+ $form->setAttrib('class', Form::DEFAULT_CLASSES . ' inline');
+ $form->setMethod('GET');
+ $form->setUidDisabled();
+ $form->setTokenDisabled();
+ $form->setName($this->name);
+ $form->addElement(
+ 'select',
+ $this->parameter,
+ array(
+ 'label' => $this->label,
+ 'multiOptions' => $this->values,
+ 'autosubmit' => true
+ )
+ );
+
+ if ($this->request) {
+ $form->populate($this->request->getParams());
+ }
+
+ return $form;
+ }
+}
diff --git a/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php b/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php
new file mode 100644
index 0000000..fdaac51
--- /dev/null
+++ b/modules/monitoring/library/Monitoring/Web/Widget/StateBadges.php
@@ -0,0 +1,341 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Monitoring\Web\Widget;
+
+use Icinga\Web\Form;
+use Icinga\Web\Navigation\Navigation;
+use Icinga\Web\Navigation\NavigationItem;
+use Icinga\Web\Url;
+use Icinga\Web\Widget\AbstractWidget;
+use Icinga\Data\Filter\Filter;
+
+class StateBadges extends AbstractWidget
+{
+ /**
+ * CSS class for the widget
+ *
+ * @var string
+ */
+ const CSS_CLASS = 'state-badges';
+
+ /**
+ * State critical
+ *
+ * @var string
+ */
+ const STATE_CRITICAL = 'state-critical';
+
+ /**
+ * State critical handled
+ *
+ * @var string
+ */
+ const STATE_CRITICAL_HANDLED = 'state-critical handled';
+
+ /**
+ * State down
+ *
+ * @var string
+ */
+ const STATE_DOWN = 'state-down';
+
+ /**
+ * State down handled
+ *
+ * @var string
+ */
+ const STATE_DOWN_HANDLED = 'state-down handled';
+
+ /**
+ * State ok
+ *
+ * @var string
+ */
+ const STATE_OK = 'state-ok';
+
+ /**
+ * State pending
+ *
+ * @var string
+ */
+ const STATE_PENDING = 'state-pending';
+
+ /**
+ * State unknown
+ *
+ * @var string
+ */
+ const STATE_UNKNOWN = 'state-unknown';
+
+ /**
+ * State unknown handled
+ *
+ * @var string
+ */
+ const STATE_UNKNOWN_HANDLED = 'state-unknown handled';
+
+ /**
+ * State unreachable
+ *
+ * @var string
+ */
+ const STATE_UNREACHABLE = 'state-unreachable';
+
+ /**
+ * State unreachable handled
+ *
+ * @var string
+ */
+ const STATE_UNREACHABLE_HANDLED = 'state-unreachable handled';
+
+ /**
+ * State up
+ *
+ * @var string
+ */
+ const STATE_UP = 'state-up';
+
+ /**
+ * State warning
+ *
+ * @var string
+ */
+ const STATE_WARNING = 'state-warning';
+
+ /**
+ * State warning handled
+ *
+ * @var string
+ */
+ const STATE_WARNING_HANDLED = 'state-warning handled';
+
+ /**
+ * State badges
+ *
+ * @var object[]
+ */
+ protected $badges = array();
+
+ /**
+ * Internal counter for badge priorities
+ *
+ * @var int
+ */
+ protected $priority = 1;
+
+ /**
+ * The base filter applied to any badge link
+ *
+ * @var Filter
+ */
+ protected $baseFilter;
+
+ /**
+ * Base URL
+ *
+ * @var Url
+ */
+ protected $url;
+
+ /**
+ * Get the base URL
+ *
+ * @return Url
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set the base URL
+ *
+ * @param Url|string $url
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ if (! $url instanceof $url) {
+ $url = Url::fromPath($url);
+ }
+ $this->url = $url;
+ return $this;
+ }
+
+ /**
+ * Get the base filter
+ *
+ * @return Filter
+ */
+ public function getBaseFilter()
+ {
+ return $this->baseFilter;
+ }
+
+ /**
+ * Set the base filter
+ *
+ * @param Filter $baseFilter
+ *
+ * @return $this
+ */
+ public function setBaseFilter($baseFilter)
+ {
+ $this->baseFilter = $baseFilter;
+ return $this;
+ }
+
+ /**
+ * Add a state badge
+ *
+ * @param string $state
+ * @param int $count
+ * @param array $filter
+ * @param string $translateSingular
+ * @param string $translatePlural
+ * @param array $translateArgs
+ *
+ * @return $this
+ */
+ public function add(
+ $state,
+ $count,
+ array $filter,
+ $translateSingular,
+ $translatePlural,
+ array $translateArgs = array()
+ ) {
+ $this->badges[$state] = (object) array(
+ 'count' => (int) $count,
+ 'filter' => $filter,
+ 'translateArgs' => $translateArgs,
+ 'translatePlural' => $translatePlural,
+ 'translateSingular' => $translateSingular
+ );
+ return $this;
+ }
+
+ /**
+ * Create a badge
+ *
+ * @param string $state
+ * @param Navigation $badges
+ *
+ * @return $this
+ */
+ public function createBadge($state, Navigation $badges)
+ {
+ if ($this->has($state)) {
+ $badge = $this->get($state);
+ $url = clone $this->url->setParams($badge->filter);
+ if (isset($this->baseFilter)) {
+ $url->addFilter($this->baseFilter);
+ }
+ $badges->addItem(new NavigationItem($state, array(
+ 'attributes' => array('class' => 'badge ' . $state),
+ 'label' => $badge->count,
+ 'priority' => $this->priority++,
+ 'title' => vsprintf(
+ mtp('monitoring', $badge->translateSingular, $badge->translatePlural, $badge->count),
+ $badge->translateArgs
+ ),
+ 'url' => $url
+ )));
+ }
+ return $this;
+ }
+
+ /**
+ * Create a badge group
+ *
+ * @param array $states
+ * @param Navigation $badges
+ *
+ * @return $this
+ */
+ public function createBadgeGroup(array $states, Navigation $badges)
+ {
+ $group = array_intersect_key($this->badges, array_flip($states));
+ if (! empty($group)) {
+ $groupItem = new NavigationItem(
+ uniqid(),
+ array(
+ 'cssClass' => 'state-badge-group',
+ 'label' => '',
+ 'priority' => $this->priority++
+ )
+ );
+ $groupBadges = new Navigation();
+ $groupBadges->setLayout(Navigation::LAYOUT_TABS);
+ foreach (array_keys($group) as $state) {
+ $this->createBadge($state, $groupBadges);
+ }
+ $groupItem->setChildren($groupBadges);
+ $badges->addItem($groupItem);
+ }
+ return $this;
+ }
+
+ /**
+ * Get whether a badge for the given state has been added
+ *
+ * @param string $state
+ *
+ * @return bool
+ */
+ public function has($state)
+ {
+ return isset($this->badges[$state]) && $this->badges[$state]->count;
+ }
+
+ /**
+ * Get the badge for the given state
+ *
+ * @param string $state
+ *
+ * @return object
+ */
+ public function get($state)
+ {
+ return $this->badges[$state];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render()
+ {
+ $badges = new Navigation();
+ $badges->setLayout(Navigation::LAYOUT_TABS);
+ $this
+ ->createBadgeGroup(
+ array(static::STATE_CRITICAL, static::STATE_CRITICAL_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_DOWN, static::STATE_DOWN_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_WARNING, static::STATE_WARNING_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_UNREACHABLE, static::STATE_UNREACHABLE_HANDLED),
+ $badges
+ )
+ ->createBadgeGroup(
+ array(static::STATE_UNKNOWN, static::STATE_UNKNOWN_HANDLED),
+ $badges
+ )
+ ->createBadge(static::STATE_OK, $badges)
+ ->createBadge(static::STATE_UP, $badges)
+ ->createBadge(static::STATE_PENDING, $badges);
+ return $badges
+ ->getRenderer()
+ ->setCssClass(static::CSS_CLASS)
+ ->render();
+ }
+}
diff --git a/modules/monitoring/module.info b/modules/monitoring/module.info
new file mode 100644
index 0000000..82c520d
--- /dev/null
+++ b/modules/monitoring/module.info
@@ -0,0 +1,5 @@
+Module: monitoring
+Version: 2.11.4
+Description: Icinga monitoring module
+ IDO accessor and UI for your monitoring. This is the initial instalment for a
+ graphical presentation of Icinga environments. The predecessor of Icinga DB.
diff --git a/modules/monitoring/public/css/module.less b/modules/monitoring/public/css/module.less
new file mode 100644
index 0000000..f97031c
--- /dev/null
+++ b/modules/monitoring/public/css/module.less
@@ -0,0 +1,1919 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+.monitoring-statusbar {
+ position: relative;
+ background-color: @body-bg-color;
+ border-top: 1px solid @gray-lighter;
+ padding: .25em @gutter;
+ line-height: 1.3;
+
+ .services-summary,
+ .hosts-summary {
+ float: right;
+ margin-bottom: 0;
+ }
+
+ .selection-info {
+ float: left;
+ margin-top: 0.182em;
+ }
+}
+
+// Hostgroup- and servicegroup-grid styles
+
+.grid-toggle-link {
+ display: inline-block;
+ margin-left: 1em;
+ text-decoration: none;
+ vertical-align: middle;
+
+ > i {
+ font-size: 1.25em;
+
+ &.-active {
+ color: @icinga-blue;
+ }
+
+ &.-inactive {
+ color: @gray-light;
+ }
+ }
+}
+
+.group-grid {
+ display: grid;
+ grid-gap: 1em 3em;
+ grid-template-columns: repeat(auto-fit, 14em);
+
+ .group-grid-cell > a:last-child {
+ display: inline-block;
+ max-width: 10em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ text-align: center;
+ vertical-align: middle;
+ }
+
+ .group-grid-cell > a:first-child,
+ .group-grid-cell > div.state-none {
+ .bg-stateful();
+ .rounded-corners();
+
+ display: inline-block;
+ margin-right: 1em;
+ padding: .5em;
+ height: 2.5em;
+ width: 2.5em;
+ text-align: center;
+ vertical-align: middle;
+ color: white;
+ }
+ .group-grid-cell > div.state-none {
+ background-color: @gray-light;
+ }
+}
+
+// Styles for the icon displayed if a check result is late
+.check-result-late {
+ &:before {
+ // Remove right margin because the check now form may be displayed right next to the icon and we already have a gap
+ // because of inline-blocks
+ margin-right: 0;
+ }
+}
+
+// Show more and load more links in overviews
+.action-links {
+ text-align: right;
+}
+
+.actions .nav {
+ li > a,
+ li > span {
+ display: inline-block;
+ }
+}
+
+// State summary badges
+.state-badges {
+ display: inline-block;
+ vertical-align: middle;
+
+ > ul > li {
+ padding-right: @vertical-padding;
+
+ &:last-child {
+ padding-right: 0;
+ }
+ }
+
+ .state-badge-group li {
+ margin-right: 1px;
+ }
+
+ .state-badge-group li:last-child {
+ margin-right: 0;
+ }
+
+ .state-badge-group .badge {
+ border-radius: 0;
+ }
+
+ .state-badge-group li:first-child > .badge {
+ border-top-left-radius: 0.4em;
+ border-bottom-left-radius: 0.4em;
+ }
+
+ .state-badge-group li:last-child > .badge {
+ border-top-right-radius: 0.4em;
+ border-bottom-right-radius: 0.4em;
+ }
+}
+
+// Performance data pie charts
+.inline-pie {
+ display: inline-block;
+ height: 14/12em;
+ margin-right: 0.1em;
+ position: relative;
+ top: 0.1em;
+ width: 14/12em;
+}
+
+// Host and service summaries in detail and list views
+.hosts-summary,
+.services-summary {
+ display: inline-block;
+ margin-bottom: 0.5em;
+
+ > .hosts-link,
+ > .services-link,
+ > .state-badges {
+ vertical-align: middle;
+ }
+}
+
+.service-on {
+ color: @text-color-light;
+
+ > a {
+ color: @text-color;
+ letter-spacing: normal;
+ font-weight: bold;
+ }
+}
+
+// State table in the host and service multi-selection and detail views
+.host-detail-state,
+.service-detail-state {
+ margin-bottom: 0.5em;
+}
+
+.grid {
+ .hosts-summary,
+ .services-summary {
+ float: left;
+ }
+}
+
+// Quick actions
+.quick-actions {
+ margin: 0 -.5em;
+
+ &:last-child {
+ margin-bottom: -.25em;
+ }
+
+ li {
+ color: @icinga-blue;
+ }
+
+ a,
+ button {
+ .rounded-corners();
+ padding: .25em .5em;
+
+ &:hover {
+ background-color: @gray-lighter;
+ text-decoration: none;
+ }
+ }
+}
+
+/* Generic box element */
+
+.boxview > div.box {
+ text-align: center;
+ vertical-align: top;
+ display: inline-block;
+ padding: 20px;
+}
+
+
+
+/* Box body of contents */
+
+.boxview div.box.contents {
+ padding-top: 20px;
+}
+
+.boxview div.box.contents table {
+ width: 100%;
+}
+
+.boxview div.box.contents td {
+ vertical-align: top;
+}
+
+/* Box entry */
+
+/* Any line of a box entry */
+.boxview div.box.entry a {
+ display: block;
+}
+
+.boxview div.box.badge {
+ padding: 5px;
+}
+
+
+/* First line of a box entry */
+.boxview div.box.entry a:first-child {
+}
+
+/* End of generic box element */
+
+/* Tactical overview element styles */
+
+.tactical > .boxview > div.box {
+ min-height: 45em;
+ padding: 0px;
+}
+
+.tactical div.box.header {
+ margin: 10px;
+ min-height: 8em;
+ color: @text-color-inverted;
+ font-size: @font-size-dashboard;
+}
+
+.tactical div.box.badge {
+ border-radius: 0.0em;
+}
+
+div.box.ok_hosts.state_up {
+ background-color: @color-ok;
+ border: 1px solid white;
+}
+
+div.box.problem_hosts.state_down {
+ background-color: @color-critical;
+ border: 1px solid white;
+}
+
+div.box.ok_hosts div.box.entry, div.box.problem_hosts div.box.entry {
+ min-width: 8em;
+ min-height: 4em;
+}
+
+.tactical div.box.contents {
+ background-color: white;
+ min-height: 13em;
+ font-size: @font-size-dashboard-small;
+ text-align: left;
+}
+
+div.box.monitoringfeatures {
+ border: 1px solid @gray-lighter;
+ border-left: 15px @gray;
+}
+
+div.box.monitoringfeatures div.box-separator {
+ color: white;
+ background-color: @color-ok;
+}
+
+div.box.monitoringfeatures div.feature-highlight {
+ background-color: @color-critical;
+}
+
+div.box.monitoringfeatures a.feature-highlight {
+}
+
+div.box.hostservicechecks {
+ border: 1px solid @gray-lighter;
+ border-left: 15px @gray;
+}
+
+div.box.hostservicechecks th {
+ padding-bottom: 20px;
+}
+
+/* Monitoring health - PROCESS - element styles */
+
+div.box.process {
+ width: 100%;
+ max-width: 50em;
+ border: 1px solid @gray-lighter;
+ border-left: 15px @gray;
+ margin-bottom: 1em;
+ margin-right: 1em;
+}
+
+.process div.box.header {
+ min-height: 5em;
+ border-bottom: 1px solid @gray-lighter;
+}
+
+.process > .boxview > div.box {
+ min-height: 30em;
+}
+
+.process h2 {
+ margin-top: 0;
+ margin-bottom: 1em;
+ padding-bottom: 1em;
+ border-bottom: 1px solid @gray-lighter;
+}
+
+.process th {
+ width: 50%;
+ text-align: right;
+}
+
+.process td {
+ width: 50%;
+ padding-left: 2em;
+ text-align: left;
+}
+
+div.backend-running {
+ background: @color-ok;
+ color: white;
+ text-align: center;
+ margin-top: 1em;
+ padding: 0.5em;
+
+ &.span {
+ color: white;
+ }
+}
+
+div.backend-not-running {
+ background: @color-critical;
+ color: white;
+ text-align: center;
+ padding: 0.1em;
+}
+
+
+/* Monitoring health - FEATURE - element styles */
+
+div.box.features {
+ width: 100%;
+ max-width: 50em;
+ border: 1px solid @gray-lighter;
+ border-left: 15px @gray;
+}
+
+.features div.box.header {
+ min-height: 5em;
+ border-bottom: 1px solid @gray-lighter;
+}
+
+.features > .boxview > div.box {
+ min-height: 30em;
+}
+
+.features h2 {
+ margin-top: 0;
+ margin-bottom: 1em;
+ padding-bottom: 1em;
+ border-bottom: 1px solid @gray-lighter;
+}
+
+
+/* Monitoring health - STATS - element styles */
+
+div.box.stats {
+ width: 100%;
+ max-width: 50em;
+ border: 1px solid @gray-lighter;
+ border-left: 15px @gray;
+ color: @text-color;
+}
+
+.stats > .boxview > div.box {
+ min-height: 30em;
+}
+
+.stats > .name-value-table {
+ table-layout: fixed;
+ text-align: left;
+}
+
+.stats > table > thead {
+ color: @gray;
+}
+
+.stats > h2 {
+ text-align: left;
+ border-bottom: 1px solid @gray-lighter;
+
+ > .hosts-summary,
+ > .services-summary {
+ width: 100%;
+ > .state-badges {
+ float: right;
+ }
+ }
+}
+
+.tinystatesummary .badge {
+ font-weight: normal;
+}
+
+/* Monitoring timeline styles */
+
+div.timeline-legend {
+ padding: 0.5em;
+ margin-top: 2em;
+ border: 1px solid @gray-lighter;
+ border-left-width: 15px;
+
+ h2 {
+ margin: 0;
+ margin-left: 0.5em;
+ line-height: 1.1em;
+ }
+
+ & > span {
+ display: inline-block;
+ padding: 0.5em;
+ margin: 0.5em;
+
+ span {
+ white-space: nowrap;
+ min-width: 25px;
+ font-family: tahoma, verdana, sans-serif;
+ font-weight: @font-weight-bold;
+ font-size: 11px;
+ text-align: center;
+ color: @text-color-inverted;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ }
+ }
+}
+
+div.timeline {
+ div.timeframe {
+ height: 7em;
+ margin-bottom: 1em;
+ clear: left;
+
+ span {
+ width: 8em;
+ margin-top: 2.3em;
+ margin-right: 1.5em;
+ display: block;
+ float: left;
+ text-align: center;
+
+ a {
+ font-weight: bold;
+ white-space: nowrap;
+ }
+ }
+
+ div.circle-box {
+ // width: inline-style;
+ height: 100%;
+ margin-right: 0.5em;
+ position: relative;
+ float: left;
+
+ div.outer-circle {
+ // width: inline-style;
+ // height: inline-style;
+ position: absolute;
+ top: 50%;
+ // margin-top: inline-style;
+
+ &.extrapolated {
+ border-width: 2px;
+ border-style: dotted;
+ //border-color: inline-style;
+ border-radius: 100%;
+ // background-color: inline-style;
+ }
+
+ a.inner-circle {
+ // width: inline-style;
+ // height: inline-style;
+ display: block;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ // margin-top: inline-style;
+ // margin-left: inline-style;
+ border-radius: 100%;
+ // background-color: inline-style;
+ }
+ }
+ }
+ }
+
+ hr {
+ border: 0;
+ height: 1px;
+ background-image: linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+ background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+ background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+ background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+ background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+ }
+}
+
+@timeline-notification-color: #3a71ea;
+@timeline-hard-state-color: #ff7000;
+@timeline-comment-color: #79bdba;
+@timeline-ack-color: #a2721d;
+@timeline-downtime-start-color: #8e8e8e;
+@timeline-downtime-end-color: #d5d6ad;
+
+.timeline-notification {
+ background-color: @timeline-notification-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-notification-color, 20%);
+ }
+}
+
+.timeline-hard-state {
+ background-color: @timeline-hard-state-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-hard-state-color, 20%);
+ }
+}
+
+.timeline-comment {
+ background-color: @timeline-comment-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-comment-color, 20%);
+ }
+}
+
+.timeline-ack {
+ background-color: @timeline-ack-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-ack-color, 20%);
+ }
+}
+
+.timeline-downtime-start {
+ background-color: @timeline-downtime-start-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-downtime-start-color, 20%);
+ }
+}
+
+.timeline-downtime-end {
+ background-color: @timeline-downtime-end-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-downtime-end-color, 20%);
+ }
+}
+
+/* End of monitoring timeline styles */
+
+/* Object features */
+
+form.instance-features span.description, form.object-features span.description {
+ text-align: left;
+}
+
+.object-features {
+ .control-label-group {
+ text-align: left;
+ margin-right: 0;
+ width: @name-value-table-name-width;
+ color: @text-color-light;
+
+ label {
+ font-size: inherit;
+ }
+ }
+
+ .control-group {
+ margin-top: 0;
+ margin-bottom: 0;
+
+ &.indeterminate {
+ justify-content: flex-start;
+
+ .control-label-group {
+ flex: 0 1 auto;
+ }
+
+ select {
+ width: auto;
+ flex: 0 1 auto;
+
+ & + span.hint {
+ flex: 0 1 auto;
+ }
+ }
+ }
+ }
+
+ .toggle-switch {
+ margin-left: @table-column-padding;
+ }
+
+ select {
+ margin-right: .5em;
+ margin-left: @table-column-padding;
+
+ & + span.hint {
+ margin: .35em;
+ color: @gray-light;
+ font-style: italic;
+ }
+ }
+}
+
+.plugin-output {
+ border-left: 5px solid @gray-lighter;
+ padding: 0.66em 0.33em;
+
+ .state-critical {
+ background-color: @color-critical;
+ color: @body-bg-color;
+ padding: 0.2em;
+ }
+
+ .state-ok {
+ background-color: @color-ok;
+ color: @body-bg-color;
+ padding: 0.2em;
+ }
+
+ .state-unknown {
+ background-color: @color-unknown;
+ color: @body-bg-color;
+ padding: 0.2em;
+ }
+
+ .state-warning {
+ background-color: @color-warning;
+ color: @body-bg-color;
+ padding: 0.2em;
+ }
+
+ .state-down {
+ background-color: @color-down;
+ color: @body-bg-color;
+ padding: 0.2em;
+ }
+
+ .state-up {
+ background-color: @color-up;
+ color: @body-bg-color;
+ padding: 0.2em;
+ }
+}
+
+.go-ahead,
+.markdown,
+.plugin-output {
+ a {
+ border-bottom: 1px dotted @gray-light;
+
+ &:hover {
+ border-bottom: 1px solid @text-color;
+ text-decoration: none;
+ }
+ }
+}
+
+.event-details {
+ .badge {
+ font-size: 0.6em;
+ margin-right: 0.5em;
+ }
+
+ .state-label {
+ vertical-align: middle;
+ }
+}
+
+/* Object customvars */
+.custom-var-table {
+ .level-1 th {
+ padding-left: .5em;
+ }
+
+ .level-2 th {
+ padding-left: 1em;
+ }
+
+ .level-3 th {
+ padding-left: 1.5em;
+ }
+
+ .level-4 th {
+ padding-left: 2em;
+ }
+
+ .level-5 th {
+ padding-left: 2.5em;
+ }
+
+ .level-6 th {
+ padding-left: 3em;
+ }
+
+ .empty {
+ color: @gray-semilight;
+ }
+
+ thead th {
+ padding-left: 0;
+ text-align: left;
+ font-weight: bold;
+ font-size: 1.167em;
+
+ > span {
+ :nth-child(1),
+ :nth-child(2) {
+ display: none;
+ }
+ }
+ }
+
+ &[data-can-collapse] thead th > span {
+ :nth-child(1) {
+ display: none;
+ }
+
+ :nth-child(2) {
+ display: inline-block;
+ }
+ }
+
+ &.collapsed thead th > span {
+ :nth-child(1) {
+ display: inline-block;
+ }
+
+ :nth-child(2) {
+ display: none;
+ }
+ }
+}
+
+//p.pluginoutput {
+// width: 100%;
+// white-space: pre-wrap;
+// font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'DejaVu Sans Mono', 'Courier New', Courier, monospace;
+//}
+//
+//table.action td .pluginoutput {
+// font-size: 0.875em;
+// line-height: 1.2em;
+// padding: 0;
+// margin: 0;
+//}
+//
+//div.pluginoutput {
+// overflow: auto;
+// color: #888;
+// margin-bottom: 1em;
+// padding: 0.2em;
+//}
+//
+//div.pluginoutput pre {
+// white-space: pre-wrap;
+// border-left: 4px solid #d8d8d8;
+// padding: 0.3em 0 0.3em 1em;
+// font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'DejaVu Sans Mono', 'Courier New', Courier, monospace;
+//}
+//
+//table.objectstate td.state {
+// padding-top: 0.5em;
+// padding-bottom: 0.5em;
+//}
+//
+//div.contacts div.contact {
+// background-color: #eee;
+// padding: 0.5em;
+// border: 1px solid #d9d9d9;
+// overflow: hidden;
+// margin: 0.125em;
+// float: left;
+//}
+//
+//div.contacts div.contact a{
+// color: @colorTextDefault;
+//}
+//
+//div.contacts div.contact > img {
+// width: 80px;
+// height: 80px;
+// margin-right: 8px;
+// float: left;
+//}
+//
+//div.contacts div.notification-periods {
+// margin-top: 0.5em;
+//}
+//
+//.tinystatesummary {
+// .badges {
+// display: inline-block;
+// margin-bottom: 4px;
+// margin-left: 1em;
+// height: auto;
+// }
+//
+// .state > a {
+// color: white;
+// font-size: 0.857em;
+// padding: 2px 5px;
+// }
+//}
+//
+///* State badges */
+//span.state {
+// font-weight: bold;
+// color: white;
+// font-weight: bold;
+// padding: 2px 3px;
+// margin-right: 5px;
+//}
+//
+//span.state.active {
+// border: 2px solid #555;
+// padding: 2px 4px;
+// margin-right: 4px;
+//}
+//
+//span.state span.state {
+// margin: 0 -6px 0 5px;
+//}
+//
+//span.state.ok {
+// background: @colorOk;
+//}
+//
+//span.state.up {
+// background: @colorOk;
+//}
+//
+//span.state.critical {
+// background: @colorCritical;
+//}
+//
+//span.state.down {
+// background: @colorCritical;
+//}
+//
+//span.state.handled.critical {
+// background: @colorCriticalHandled;
+//}
+//
+//span.state.handled.down {
+// background: @colorCriticalHandled;
+//}
+//
+//span.state.warning {
+// background: @colorWarning;
+//}
+//
+//span.state.handled.warning {
+// background: @colorWarningHandled;
+//}
+//
+//span.state.unknown {
+// background: @colorUnknown;
+//}
+//
+//span.state.handled.unknown {
+// background: @colorUnknownHandled;
+//}
+//
+//span.state.pending {
+// background: @colorPending;
+//}
+//
+//form.instance-features span.description, form.object-features span.description {
+// display: inline;
+//}
+//
+//.boxview div.box form.instance-features div.header {
+// border-bottom: 1px solid #d9d9d9;
+// margin-bottom: 0.5em;
+//
+// h2 {
+// border: 0;
+// padding-bottom: 0;
+// }
+//}
+//
+//table.avp form.object-features div.header h4 {
+// margin: 0;
+//}
+//
+//table.avp {
+// th {
+// font-weight: normal;
+// font-size: 0.875em;
+// padding-top: 0.25em;
+// }
+//
+// h2 {
+// font-size: 0.875em;
+// line-height: 1.2em;
+// padding-bottom: 0.1em;
+// }
+//
+// td {
+// color: #666;
+// padding-bottom: 0.3em;
+// line-height: 1.5em;
+// th, td {
+// padding: 0;
+// }
+// }
+//
+// .badge a[href] {
+// color: @colorGray;
+// }
+//
+// .go-ahead {
+// a, button.link-like {
+// color: #222;
+// }
+// }
+//
+// .object-features {
+// label {
+// font-weight: normal;
+// margin-right: 0;
+// width: 14em;
+// font-size: 0.875em;
+// }
+//
+// input {
+// margin: 0;
+// }
+// }
+//}
+//
+//table.avp .customvar ul {
+// list-style-type: none;
+// margin: 0;
+// padding: 0;
+// padding-left: 1.5em;
+//}
+//
+//div.selection-info {
+// padding-top: 0.4em;
+// float: right;
+// cursor: help;
+// font-size: 0.857em;
+//}
+//
+//.optionbox {
+// margin-left: 0em;
+// margin-right: 3em;
+//}
+//
+//.optionbox label {
+// max-width: 6.5em;
+// text-align: left;
+// vertgical-align: middle;
+// margin-right: 0em;
+//}
+//
+//.optionbox input {
+// vertical-align: middle;
+//}
+//
+//.object-command form h1, .objects-command form h1 {
+// border: none;
+//}
+//
+//hr.command-separator {
+// border: none;
+// border-bottom: 2px solid @colorPetrol;
+//}
+//
+//div.backend-not-running {
+// background: @colorCritical;
+// color: white;
+// text-align: center;
+// padding: 0.1em;
+//}
+//
+//td.state {
+// .time-ago,
+// .time-since,
+// .time-until {
+// text-transform: capitalize;
+// }
+//}
+//
+//.inline-comments {
+// padding: 0;
+// margin: 0;
+// font-size: 0.857em;
+//
+// .time-ago {
+// font-style: italic;
+// color: #919191;
+// }
+//
+// li {
+// list-style-type: none;
+// margin-bottom: 8px;
+// }
+//
+// h3 {
+// border: none;
+// border-bottom: 1px solid gray;
+// font-weight: normal;
+// font-size: inherit;
+// color: inherit;
+// margin: 0;
+// padding-bottom: 0.1em;
+// }
+//
+// h3 .author {
+// font-weight: bold;
+// }
+//
+// h3 form {
+// display: none;
+// }
+//
+// h3 form {
+// float: right;
+// }
+//
+// li:hover h3 {
+// background: #F9F9F9;
+// position: relative;
+//
+// form {
+// display: inline;
+// }
+// }
+//
+// p {
+// margin: 0;
+//
+// a {
+// color: #222;
+// }
+// }
+//}
+//
+///* Special tables and states */
+//
+//table.colors {
+// font-size: 0.8em;
+// width: 98%;
+// margin: 0 1%;
+//}
+//
+//table.colors td {
+// text-align: center;
+// vertical-align: middle;
+// width: 10%;
+// height: 1.6em;
+// font-weight: normal;
+// border: 0.079em solid white;
+//}
+//
+//table.action td.state, table.objectstate td.state {
+// font-size: 0.857em;
+// text-align: center;
+//}
+//
+//
+///* State row behaviour */
+//
+//tr.state img.icon {
+// margin-right: 2px;
+//}
+//
+///* Hostgroup badge quickfix */
+//tr.state span a {
+// color: white;
+// font-size: 0.857em;
+// padding: 2px 5px;
+//}
+//
+//tr.state:hover a {
+// color: inherit;
+//}
+//
+//tr.state a.active {
+//}
+//
+//tr.state.new td.state {
+// font-weight: bold;
+//}
+//
+//tr.state td.state {
+// width: 9em;
+// color: white;
+// border-bottom: none;
+//}
+//
+//tr.state.handled td.state, tr.state.ok td.state, tr.state.up td.state, tr.state.pending td.state {
+// border-left-style: solid;
+// border-left-width: 1.5em;
+// padding-left: 0em;
+// padding-right: 0.5em;
+// color: black;
+// background-color: transparent;
+//}
+//
+//tr.state.ok td.state, tr.state.up td.state {
+// border-left-color: @colorOk;
+//}
+//
+//tr.state.warning td.state {
+// background-color: @colorWarning;
+//}
+//
+//tr.state.warning.handled td.state {
+// border-left-color: @colorWarningHandled;
+//}
+//
+//tr.state.critical td.state, tr.state.down td.state {
+// background-color: @colorCritical;
+//}
+//
+//tr.state.critical.handled td.state, tr.state.down.handled td.state {
+// border-left-color: @colorCriticalHandled;
+//}
+//
+//tr.state.unreachable td.state {
+// background-color: @colorUnreachable;
+//}
+//
+//tr.state.unreachable.handled td.state {
+// border-left-color: @colorUnreachableHandled;
+//}
+//
+//tr.state.unknown td.state {
+// background-color: @colorUnknown;
+//}
+//
+//tr.state.unknown.handled td.state {
+// border-left-color: @colorUnknownHandled;
+//}
+//
+//tr.state.pending td.state {
+// border-left-color: @colorPending;
+//}
+//
+//tr.state.invalid td.state {
+// background-color: @colorInvalid;
+//}
+//
+//tr.state.unreachable td.state {
+// background-color: @colorUnreachable;
+//}
+//
+//tr.state.unreachable.handled td.state {
+// border-left-color: @colorUnreachableHandled;
+//}
+//
+//tr.state.handled td.state {
+// color: inherit;
+// background-color: transparent !important;
+//}
+//
+///* HOVER colors */
+//
+//tr.state[href]:hover td.state {
+// background-color: #eee;
+//}
+//
+//tr.state.ok[href]:hover, tr.state.up[href]:hover {
+// background-color: @colorOk;
+//}
+//
+//tr.state.handled[href]:hover, tr.state.handled[href]:hover td.state {
+// color: #121212 !important;
+//}
+//
+//tr.state.warning[href]:hover {
+// background-color: @colorWarning;
+// color: white;
+//}
+//
+//tr.state.warning.handled[href]:hover {
+// background-color: @colorWarningHandled;
+//}
+//
+//tr.state.critical[href]:hover, tr.state.down[href]:hover {
+// background-color: @colorCritical;
+// color: white;
+//}
+//
+//tr.state.critical.handled[href]:hover, tr.state.down.handled[href]:hover {
+// background-color: @colorCriticalHandled;
+// color: #333;
+//}
+//
+//tr.state.unknown[href]:hover {
+// background-color: @colorUnknown;
+// color: white;
+//}
+//
+//tr.state.unknown.handled[href]:hover {
+// background-color: @colorUnknownHandled;
+//}
+//
+//tr.state.pending[href]:hover {
+// background-color: @colorPending;
+//}
+//
+//tr.state.invalid[href]:hover {
+// background-color: @colorInvalid;
+// color: white;
+//}
+//
+//tr.state.unreachable[href]:hover {
+// background-color: @colorUnreachable;
+//}
+//
+//tr.state.unreachable.handled[href]:hover {
+// background-color: @colorUnreachableHandled;
+//}
+//
+//tr.state[href]:hover td.state {
+// background-color: inherit !important;
+//}
+//
+///* END of HOVER colors */
+//
+///* END of special tables and states */
+//
+//
+///* Generic colors */
+//
+//a.critical {
+// color: @colorCritical;
+//}
+//
+///* END of Generic colors */
+//
+//
+///* Generic box element */
+//
+//.boxview a {
+// text-decoration: none;
+//}
+//
+//.boxview > div.box {
+// text-align: center;
+// vertical-align: top;
+// display: inline-block;
+// padding: 0.4em;
+// margin: 0.4em;
+// border: 1px solid #d9d9d9;
+// background: #eee;
+//}
+//
+///* Box header */
+//.boxview div.box.header {
+// padding-bottom: 0.5em;
+// margin-bottom: 0.5em;
+// border-bottom: 1px solid #888;
+//}
+//
+//.boxview div.box.header h2 {
+// margin-top: 0.1em;
+// margin-bottom: 0;
+// font-size: 0.8em;
+// border-bottom: none;
+// color: @colorTextDefault;
+//}
+//
+//.boxview div.box.header h2:first-child {
+// margin-top: 0.2em;
+// font-size: inherit;
+// color: @colorTextDefault;
+//}
+//
+//.boxview div.box.header h2 > a {
+// color: inherit;
+//}
+//
+//.boxview div.box.header h2 > a:hover {
+// text-decoration: underline;
+//}
+//
+//.boxview div.box.header h3 {
+// line-height: 1.5em;
+// font-size: 0.9em;
+// color: #555;
+//}
+//
+///* Box body of contents */
+//.boxview div.box.contents {
+// padding: 0.2em;
+//}
+//
+//.boxview div.box.contents table {
+// width: 100%;
+//}
+//
+//.boxview div.box.contents td {
+// width: 13em;
+// vertical-align: top;
+//}
+//
+///* Box separator */
+//.boxview div.box-separator:first-child {
+// border-top-width: 0;
+//}
+//
+//.boxview div.box-separator {
+// font-size: 0.8em;
+// padding: 0.4em 0 0.4em;
+// border: 1px solid #d9d9d9;
+//
+// font-weight: bold;
+// letter-spacing: 0.1em;
+//}
+//
+///* Box entry */
+//.boxview div.box.entry {
+// min-height: 2.7em;
+// margin: 0.2em;
+// font-size: 0.9em;
+// white-space: nowrap;
+//
+// color: @colorTextDefault;
+//}
+//
+///* Any line of a box entry */
+//.boxview div.box.entry a {
+// display: block;
+//
+// color: inherit;
+//}
+//
+//.boxview div.box.entry a:hover {
+// color: @colorTextDefault;
+//}
+//
+///* First line of a box entry */
+//.boxview div.box.entry a:first-child {
+// font-size: 1em;
+//}
+//
+///* End of generic box element */
+//
+//
+///* Monitoring box element styles */
+//
+///* Host- and Servicegroup element styles */
+//
+//div.box.entry.state_up, div.box.entry.state_ok {
+// border: 1px solid @colorOk;
+// border-left: 1em solid @colorOk;
+//}
+//
+//div.box.entry.state_pending {
+// border: 1px solid @colorPending;
+// border-left: 1em solid @colorPending;
+//}
+//
+//div.box.entry.state_down, div.box.entry.state_critical {
+// border: 1px solid @colorCritical;
+// border-left: 1em solid @colorCritical;
+// background-color: @colorCritical;
+// color: white;
+//}
+//
+//div.box.entry.state_down a:hover, div.box.entry.state_critical a:hover {
+// color: #dcdcdc;
+//}
+//
+//div.box.entry.state_warning {
+// border: 1px solid @colorWarning;
+// border-left: 1em solid @colorWarning;
+// background-color: @colorWarning;
+// color: white;
+//}
+//
+//div.box.entry.state_warning a:hover {
+// color: #dcdcdc;
+//}
+//
+//div.box.entry.state_unreachable, div.box.entry.state_unknown {
+// border: 1px solid @colorUnknown;
+// border-left: 1em solid @colorUnknown;
+// background-color: @colorUnknown;
+// color: white;
+//}
+//
+//div.box.entry.state_unreachable a:hover, div.box.entry.state_unknown a:hover {
+// color: #dcdcdc;
+//}
+//
+//div.box.entry.handled {
+// background-color: transparent;
+// color: inherit;
+//}
+//
+//div.box.entry.handled a:hover {
+// color: @colorTextDefault;
+//}
+//
+/* Tactical overview element styles */
+//
+//.tactical > .boxview > div.box {
+// min-height: 20em;
+// min-width: 12.1em;
+//}
+//
+//.tactical div.box.contents {
+// min-height: 14.5em;
+//}
+//
+//div.box.contents.zero {
+// min-width: 11.1em;
+//
+// background-color: transparent;
+//}
+//
+//div.box.contents.zero span {
+// font-weight: bold;
+// line-height: 2em;
+//
+// color: #666;
+//}
+//
+//div.box.contents.zero h3 {
+// margin: 0;
+// font-size: 12em;
+// line-height: 1em;
+//
+// color: #666;
+//}
+//
+//div.box.ok_hosts.state_up {
+// border: 5px solid @colorOk;
+//}
+//
+//div.box.ok_hosts.state_pending {
+// background-color: @colorPending;
+//}
+//
+//div.box.problem_hosts.state_down {
+// border: 5px solid @colorCritical;
+//}
+//
+//div.box.problem_hosts.state_down.handled {
+// background-color: @colorCriticalHandled;
+//}
+//
+//div.box.problem_hosts.state_unreachable {
+// background-color: @colorUnreachable;
+//}
+//
+//div.box.problem_hosts.state_unreachable.handled {
+// background-color: @colorUnreachableHandled;
+//}
+//
+//div.box.ok_hosts div.box.entry, div.box.problem_hosts div.box.entry {
+// min-width: 11.1em;
+//}
+//
+//div.box.monitoringfeatures div.box.contents {
+// padding: 0 2 0em;
+//}
+//
+//div.box.monitoringfeatures {
+// border: 5px solid #d9d9d9;
+//}
+//
+//div.box.monitoringfeatures div.box-separator {
+// color: white;
+// background-color: @colorOk;
+//}
+//
+//div.box.monitoringfeatures div.feature-highlight {
+// background-color: @colorCritical;
+//}
+//
+//div.box.monitoringfeatures a.feature-highlight {
+// font-weight: bold;
+//}
+//
+//div.box.hostservicechecks {
+// border: 5px solid #d9d9d9;
+//}
+//
+///* Contactgroup element styles */
+//
+//div.box.contactgroup {
+// width: 18em;
+// padding: 0.8em;
+//}
+//
+//div.box.contactgroup div.box.contents {
+// padding: 0.6em;
+//}
+//
+//div.box.contactgroup div.box.entry {
+// overflow: hidden;
+// clear: left;
+//}
+//
+//div.box.contactgroup div.box.entry img {
+// width: 80px;
+// height: 80px;
+// float: left;
+//
+//}
+//
+//div.box.contactgroup div.box.entry a {
+// margin-top: 0.4em;
+//
+// font-weight: bold;
+//}
+//
+//div.box.contactgroup div.box.entry p {
+// margin: 0.4em 0 0;
+//}
+//
+//div.circular {
+// margin-top: 0.5em;
+// margin-left: 2em;
+// margin-right: 1em;
+// width: 80px;
+// height: 80px;
+// float: left;
+// background-size: 100% 100%;
+//}
+//
+///* End of monitoring box element styles */
+//
+//
+///* Monitoring pivot table styles */
+//
+//div.pivot-pagination {
+// margin: 1em;
+//
+// table {
+// table-layout: fixed;
+// border-spacing: 1px;
+// border-collapse: separate;
+// border: 1px solid LightGrey;
+// border-radius: 0.3em;
+//
+// td {
+// width: 16px;
+// height: 16px;
+// padding: 0;
+// background-color: #fbfbfb;
+//
+// &:hover, &.active {
+// background-color: #e5e5e5;
+// }
+//
+// a {
+// width: 16px;
+// height: 16px;
+// display: block;
+// }
+// }
+// }
+//}
+//
+//table.joystick-pagination {
+// margin-top: -1.5em;
+//
+// td {
+// width: 1.25em;
+// height: 1.3em;
+// }
+//}
+//
+///* End of monitoring pivot table styles */
+//
+///* Monitoring timeline styles */
+//
+//div.timeline-legend {
+// float: left;
+// padding: 0.5em;
+// border: 1px solid #d9d9d9;
+// background-color: #eee;
+//
+// h2 {
+// margin: 0;
+// margin-left: 0.5em;
+// line-height: 1.1em;
+// }
+//
+// & > span {
+// display: inline-block;
+// padding: 0.5em;
+// margin: 0.5em;
+//
+// span {
+// color: white;
+// font-size: 0.8em;
+// font-weight: bold;
+// white-space: nowrap;
+// }
+// }
+//}
+//
+//div.timeline {
+// div.timeframe {
+// height: 7em;
+// margin-bottom: 1em;
+// clear: left;
+//
+// span {
+// width: 8em;
+// margin-top: 2.3em;
+// margin-right: 1.5em;
+// display: block;
+// float: left;
+// text-align: center;
+//
+// a {
+// color: @colorTextDefault;
+// font-size: 0.8em;
+// font-weight: bold;
+// text-decoration: none;
+// white-space: nowrap;
+//
+// &:hover {
+// color: @colorTextDefault;
+// text-decoration: underline;
+//
+// }
+// }
+// }
+//
+// div.circle-box {
+// // width: inline-style;
+// height: 100%;
+// margin-right: 0.5em;
+// position: relative;
+// float: left;
+//
+// div.outer-circle {
+// // width: inline-style;
+// // height: inline-style;
+// position: absolute;
+// top: 50%;
+// // margin-top: inline-style;
+//
+// &.extrapolated {
+// border-width: 2px;
+// border-style: dotted;
+// //border-color: inline-style;
+// border-radius: 100%;
+// // background-color: inline-style;
+// }
+//
+// a.inner-circle {
+// // width: inline-style;
+// // height: inline-style;
+// display: block;
+// position: absolute;
+// top: 50%;
+// left: 50%;
+// // margin-top: inline-style;
+// // margin-left: inline-style;
+// border-radius: 100%;
+// // background-color: inline-style;
+// }
+// }
+// }
+// }
+//
+// hr {
+// border: 0;
+// height: 1px;
+// background-image: linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+// background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+// background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+// background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+// background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,0.3), rgba(0,0,0,0));
+// }
+//}
+//
+///* End of monitoring timeline styles */
+//
+///* Monitoring groupsummary styles */
+//
+//.dashboard table.groupview {
+// margin-top: 0;
+//}
+//
+//table.groupview {
+// width: 100%;
+// margin-top: 1em;
+// border-collapse: separate;
+// border-spacing: 0.1em;
+//
+// th {
+// font-size: 1.0em;
+// font-weight: normal;
+// text-align: center;
+// white-space: nowrap;
+// border-bottom: 2px solid @gray-light;
+// }
+//
+// td {
+// &.groupname {
+// width: 60%;
+//
+// a {
+// color: inherit;
+// text-decoration: none;
+//
+// &:hover {
+// text-decoration: underline;
+// }
+// }
+// }
+//
+// &.total {
+// width: 10%;
+// }
+//
+// &.state {
+// width: 20%;
+// white-space: nowrap;
+//
+// &.change {
+// width: 10%;
+// text-align: center;
+// border-left-width: 1.5em;
+// border-left-style: solid;
+// padding: 0.3em 0.5em 0.3em 0.5em;
+//
+// strong {
+// font-size: 0.8em;
+// }
+//
+// &.ok {
+// border-color: @colorOk;
+// }
+//
+// &.pending {
+// border-color: @colorPending;
+// }
+//
+// &.warning {
+// border-color: @colorWarningHandled;
+//
+// &.unhandled {
+// color: white;
+// border-left-width: 0;
+// background-color: @colorWarning;
+// }
+// }
+//
+// &.unknown {
+// border-color: @colorUnknownHandled;
+//
+// &.unhandled {
+// color: white;
+// border-left-width: 0;
+// background-color: @colorUnknown;
+// }
+// }
+//
+// &.critical {
+// border-color: @colorCriticalHandled;
+//
+// &.unhandled {
+// color: white;
+// border-left-width: 0;
+// background-color: @colorCritical;
+// }
+// }
+// }
+//
+// span.state {
+// &.handled {
+// margin-right: 2px;
+// }
+//
+// a {
+// font-size: 0.9em;
+// color: white;
+// text-decoration: none;
+//
+// &:hover {
+// text-decoration: underline;
+// }
+// }
+// }
+// }
+// }
+//}
+//
+///* End of monitoring groupsummary styles */
+//
+///* compact table */
+//table.statesummary {
+// text-align: left;
+// width: auto;
+// border-collapse: separate;
+//
+// tr.state td.state {
+// width: auto;
+// font-weight: bold;
+// }
+//
+// td {
+// font-size: 0.9em;
+// line-height: 1.2em;
+// padding-left: 0.2em;
+// margin: 0;
+// }
+//
+// td.state {
+// padding: 0.2em;
+// min-width: 75px;
+// font-size: 0.75em;
+// text-align: center;
+// }
+//
+// td.name {
+// font-weight: bold;
+// }
+//
+// td a {
+// color: inherit;
+// text-decoration: none;
+// }
+//}
+//
+//table.action .objectflags {
+// float: right;
+//}
+//
+//table.objectstate {
+// border-collapse: separate;
+// border-spacing: 1px;
+//}
+//
+//table.objectstate td {
+// padding-left: 1em;
+//}
+//
+//table.objectstate tr.state td.state {
+// width: 9em;
+// text-align: center;
+// padding-left: 0;
+// border-radius: 0;
+//}
+//
+//table.avp td.performance-data {
+// padding: 0.3em 0 0.3em 1em;
+//}
+//
+//table.perfdata {
+// min-width: 24em;
+// font-size: 0.9em;
+// width: 100%;
+//}
+//
+//table.perfdata th {
+// padding: 0;
+// text-align: left;
+// padding-right: 0.5em;
+//}
+//
+//table.perfdata td {
+// white-space: nowrap;
+// padding-right: 0.5em;
+//}
diff --git a/modules/monitoring/public/css/service-grid.less b/modules/monitoring/public/css/service-grid.less
new file mode 100644
index 0000000..fd22097
--- /dev/null
+++ b/modules/monitoring/public/css/service-grid.less
@@ -0,0 +1,75 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+.service-grid-table {
+ width: 0;
+ white-space: nowrap;
+
+ td {
+ color: @gray-light;
+ padding: 0.2em;
+ text-align: center;
+ width: 1em;
+ }
+
+ .rotate-45 {
+ height: 8em;
+
+ div {
+ .transform(translate(0.4em, 2.8em) rotate(315deg));
+ width: 1.5em;
+ }
+ }
+
+ .service-grid-table-more {
+ text-align: center;
+ a {
+ display: inline;
+ }
+ }
+}
+
+.joystick-pagination {
+ margin: 0 auto;
+ font-size: 130%;
+
+ a {
+ color: @text-color;
+ outline: none;
+
+ &:hover {
+ color: @text-color-light;
+ }
+ &:focus, &:active {
+ color: @icinga-blue;
+ }
+ }
+
+ i {
+ display: block;
+ height: 1.5em;
+ width: 1.5em;
+ }
+}
+
+.service-grid-link {
+ .bg-stateful();
+ .rounded-corners();
+
+ display: inline-block;
+ height: 1.5em;
+ vertical-align: middle;
+ width: 1.5em;
+}
+
+form.filter-toggle {
+ label:not(.toggle-switch) {
+ display: inline-block;
+ vertical-align: top;
+ margin-left: .5em;
+ color: @gray-light;
+ }
+
+ input[type="checkbox"]:checked ~ label {
+ color: inherit;
+ }
+}
diff --git a/modules/monitoring/public/css/tables.less b/modules/monitoring/public/css/tables.less
new file mode 100644
index 0000000..c5b5f27
--- /dev/null
+++ b/modules/monitoring/public/css/tables.less
@@ -0,0 +1,282 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+@border-left-width: 6px;
+
+// Icon images in list and detail views
+.host-icon-image,
+.service-icon-image {
+ max-width: 2em;
+ vertical-align: middle;
+}
+
+// Check source reachable information in the host and service detail views
+.check-source-meta {
+ font-size: @font-size-small;
+}
+
+// Object link and comment author in the comment overview
+.comment-author {
+ margin-bottom: 0.25em;
+
+ > a {
+ font-weight: bold;
+ }
+}
+
+// Comment icons, e.g. persistent in the comment overview
+.comment-icons {
+ float: right;
+}
+
+.caption {
+ height: 3em;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+
+ img {
+ max-height: 1em;
+ }
+}
+
+// Type information for backends in the monitoring config
+.config-label-meta {
+ font-size: @font-size-small;
+}
+
+// Column for counts, e.g. host group members
+.count-col {
+ width: 4em;
+}
+
+// Custom variables in the host and service detail view
+.custom-variables > ul {
+ list-style-type: none;
+ margin: 0;
+}
+
+// Host name and IP addresses in the host and service detail view
+.host-meta {
+ color: @text-color-light;
+ font-size: @font-size-small;
+}
+
+// Notification recipient in the notifications overview
+.notification-recipient {
+ color: @text-color-light;
+ float: right;
+ font-size: @font-size-small;
+}
+
+
+// Container for plugin output and performance data in overviews
+.overview-plugin-output-container {
+ .clearfix();
+}
+
+// Performance data pies in overviews
+.overview-performance-data {
+ float: right;
+ font-size: @font-size-small;
+}
+
+// Plugin output in detail views
+.plugin-output,
+// Plugin output in overviews
+.overview-plugin-output {
+ -webkit-hyphens: auto;
+ -moz-hyphens: auto;
+ -ms-hyphens: auto;
+ hyphens: auto;
+
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+}
+
+// Plugin output in overviews
+.overview-plugin-output {
+ color: @text-color-light;
+ font-family: @font-family-fixed;
+ font-size: @font-size-small;
+ margin: 0;
+ white-space: pre-wrap;
+ // Long text in table cells overflows the table's width if the table's layout is not fixed.
+ // Thus overflow-wrap will not have any effect. But w/ the following we set a width of any value
+ // plus a min-width of 100% to consume the full width nonetheless which seems to always
+ // instruct browsers to not overflow the table. Ridiculous.
+ min-width: 100%;
+ width: 1em;
+}
+
+// Table for performance data in detail views
+.performance-data-table {
+ display: block;
+ overflow-x: auto;
+ position: relative;
+
+ > thead > tr > th {
+ text-align: left;
+ }
+
+ > thead > tr > th:first-child,
+ > tbody > tr > td:first-child {
+ // Reset base padding
+ padding-left: 0;
+ }
+
+ > thead > tr > th,
+ > tbody > tr > td {
+ white-space: nowrap;
+ }
+}
+
+// Performance data table column for sparkline pie charts in detail views
+.sparkline-col {
+ width: 2em;
+}
+
+// Service description if in the service detail view
+.service-meta {
+ color: @text-color-light;
+ font-size: @font-size-small;
+}
+
+// State column for label and duration in overviews
+.state-col {
+ &.state-ok,
+ &.state-up {
+ border-left: @border-left-width solid @color-ok;
+ }
+
+ &.state-pending {
+ border-left: @border-left-width solid @color-pending;
+ }
+
+ &.state-critical,
+ &.state-down {
+ background-color: @color-critical;
+ color: @text-color-inverted;
+
+ &.handled {
+ background-color: inherit;
+ color: inherit;
+ border-left: @border-left-width solid @color-critical-handled;
+ }
+ }
+
+ &.state-warning {
+ background-color: @color-warning;
+ color: @text-color-inverted;
+
+ &.handled {
+ background-color: inherit;
+ color: inherit;
+ border-left: @border-left-width solid @color-warning-handled;
+ }
+ }
+
+ &.state-unknown {
+ background-color: @color-unknown;
+ color: @text-color-inverted;
+
+ &.handled {
+ background-color: inherit;
+ color: inherit;
+ border-left: @border-left-width solid @color-unknown-handled;
+ }
+ }
+
+ &.state-unreachable {
+ background-color: @color-unreachable;
+ color: @text-color-inverted;
+
+ &.handled {
+ background-color: inherit;
+ color: inherit;
+ border-left: @border-left-width solid @color-unreachable-handled;
+ }
+ }
+
+ // State class for history events
+ &.state-no-state {
+ border-left: @border-left-width solid @text-color-light;
+ }
+
+ * {
+ color: inherit;
+ }
+
+ text-align: center;
+ width: 8em;
+}
+
+// Wraps links, icons and meta in overviews
+.state-header {
+ .clearfix();
+
+ > a {
+ font-weight: bold;
+ }
+}
+
+// State icons, e.g. acknowledged in overviews
+.state-icons {
+ float: right;
+}
+
+// State labels in overviews
+.state-label {
+ font-family: @font-family-wide;
+ font-size: @font-size-small;
+ letter-spacing: 1px;
+}
+
+// State duration and state type information in overviews
+.state-meta {
+ font-size: @font-size-small;
+}
+
+.state-table {
+ border-collapse: separate;
+ border-spacing: 0 1px;
+ width: 100%;
+
+ tr[href] {
+ -webkit-transform: translate3d(0,0,0); /* Without this, hovering in Safari is broken in history table rows */
+ -moz-transform: none; /* Firefox collapses border spacing due to the above */
+ }
+
+ tr[href].active {
+ background-color: @tr-active-color;
+ }
+
+ tr[href]:hover {
+ background-color: @tr-hover-color;
+ cursor: pointer;
+ }
+
+ tr[href].state-outdated:not(:hover):not(.active) td:not(.state-col) {
+ opacity: 0.7;
+ }
+}
+
+// Event history
+.history-message-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ > .history-message-icon {
+ padding: 0.25em;
+ }
+
+ > .history-message-output {
+ flex: 1;
+
+ > a {
+ font-weight: bold;
+ }
+ }
+}
diff --git a/modules/monitoring/public/js/module.js b/modules/monitoring/public/js/module.js
new file mode 100644
index 0000000..d665e6b
--- /dev/null
+++ b/modules/monitoring/public/js/module.js
@@ -0,0 +1,84 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+(function(Icinga) {
+
+ var Monitoring = function(module) {
+ /**
+ * The Icinga.Module instance
+ */
+ this.module = module;
+
+ /**
+ * The observer used to handle the timeline's infinite loading
+ */
+ this.scrollCheckTimer = null;
+
+ /**
+ * Whether to skip the timeline's scroll-check
+ */
+ this.skipScrollCheck = false;
+
+ this.initialize();
+ };
+
+ Monitoring.prototype = {
+
+ initialize: function()
+ {
+ this.module.on('rendered', this.enableScrollCheck);
+ this.module.icinga.logger.debug('Monitoring module loaded');
+ },
+
+ /**
+ * Enable the timeline's scroll-check
+ */
+ enableScrollCheck: function()
+ {
+ /**
+ * Re-enable the scroll-check in case the timeline has just been extended
+ */
+ if (this.skipScrollCheck) {
+ this.skipScrollCheck = false;
+ }
+
+ /**
+ * Prepare the timer to handle the timeline's infinite loading
+ */
+ var $timeline = $('div.timeline');
+ if ($timeline.length && !$timeline.closest('.dashboard').length) {
+ if (this.scrollCheckTimer === null) {
+ this.scrollCheckTimer = this.module.icinga.timer.register(
+ this.checkTimelinePosition,
+ this,
+ 800
+ );
+ this.module.icinga.logger.debug('Enabled timeline scroll-check');
+ }
+ }
+ },
+
+ /**
+ * Check whether the user scrolled to the end of the timeline
+ */
+ checkTimelinePosition: function()
+ {
+ if (!$('div.timeline').length) {
+ this.module.icinga.timer.unregister(this.scrollCheckTimer);
+ this.scrollCheckTimer = null;
+ this.module.icinga.logger.debug('Disabled timeline scroll-check');
+ } else if (!this.skipScrollCheck && this.module.icinga.utils.isVisible('#end')) {
+ this.skipScrollCheck = true;
+ this.module.icinga.loader.loadUrl(
+ $('#end').remove().attr('href'),
+ $('div.timeline'),
+ undefined,
+ undefined,
+ 'append'
+ ).addToHistory = false;
+ }
+ }
+ };
+
+ Icinga.availableModules.monitoring = Monitoring;
+
+}(Icinga));
diff --git a/modules/monitoring/run.php b/modules/monitoring/run.php
new file mode 100644
index 0000000..6fe4921
--- /dev/null
+++ b/modules/monitoring/run.php
@@ -0,0 +1,8 @@
+<?php
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+/** @var $this \Icinga\Application\Modules\Module */
+
+$this->provideHook('ApplicationState');
+$this->provideHook('Health');
+$this->provideHook('X509/Sni');
diff --git a/modules/setup/application/clicommands/ConfigCommand.php b/modules/setup/application/clicommands/ConfigCommand.php
new file mode 100644
index 0000000..130d797
--- /dev/null
+++ b/modules/setup/application/clicommands/ConfigCommand.php
@@ -0,0 +1,185 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Clicommands;
+
+use Icinga\Application\Logger;
+use Icinga\Cli\Command;
+use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Setup\Webserver;
+
+class ConfigCommand extends Command
+{
+ /**
+ * Create Icinga Web 2's configuration directory
+ *
+ * USAGE:
+ *
+ * icingacli setup config directory [options]
+ *
+ * OPTIONS:
+ *
+ * --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
+ *
+ * --mode=<mode> The access mode to use [2770]
+ *
+ * --group=<group> Owner group for the configuration directory [icingaweb2]
+ *
+ * EXAMPLES:
+ *
+ * icingacli setup config directory
+ *
+ * icingacli setup config directory --mode=2775 --config=/opt/icingaweb2/etc
+ */
+ public function directoryAction()
+ {
+ $configDir = trim($this->params->get('config', $this->app->getConfigDir()));
+ if (strlen($configDir) === 0) {
+ $this->fail($this->translate(
+ 'The argument --config expects a path to Icinga Web 2\'s configuration files'
+ ));
+ }
+
+ $group = trim($this->params->get('group', 'icingaweb2'));
+ if (strlen($group) === 0) {
+ $this->fail($this->translate(
+ 'The argument --group expects a owner group for the configuration directory'
+ ));
+ }
+
+ $mode = trim($this->params->get('mode', '2770'));
+ if (strlen($mode) === 0) {
+ $this->fail($this->translate(
+ 'The argument --mode expects an access mode for the configuration directory'
+ ));
+ }
+
+ if (! file_exists($configDir) && ! @mkdir($configDir, 0755, true)) {
+ $e = error_get_last();
+ $this->fail(sprintf(
+ $this->translate('Can\'t create configuration directory %s: %s'),
+ $configDir,
+ $e['message']
+ ));
+ }
+
+ if (! @chmod($configDir, octdec($mode))) {
+ $e = error_get_last();
+ $this->fail(sprintf(
+ $this->translate('Can\'t change the mode of the configuration directory to %s: %s'),
+ $mode,
+ $e['message']
+ ));
+ }
+
+ if (! @chgrp($configDir, $group)) {
+ $e = error_get_last();
+ $this->fail(sprintf(
+ $this->translate('Can\'t change the group of %s to %s: %s'),
+ $configDir,
+ $group,
+ $e['message']
+ ));
+ }
+
+ printf($this->translate('Successfully created configuration directory %s') . PHP_EOL, $configDir);
+ }
+
+ /**
+ * Create webserver configuration
+ *
+ * USAGE:
+ *
+ * icingacli setup config webserver <apache|nginx> [options]
+ *
+ * OPTIONS:
+ *
+ * --path=<urlpath> The URL path to Icinga Web 2 [/icingaweb2]
+ *
+ * --root|--document-root=<directory> The directory from which the webserver will serve files
+ * [/path/to/icingaweb2/public]
+ *
+ * --enable-fpm Enable FPM handler for Apache (Nginx is always enabled)
+ *
+ * --fpm-uri=<uri> Address or path where to pass requests to FPM [127.0.0.1:9000]
+ *
+ * --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
+ *
+ * --file=<filename> Write configuration to file [stdout]
+ *
+ * EXAMPLES:
+ *
+ * icingacli setup config webserver apache
+ *
+ * icingacli setup config webserver apache \
+ * --path=/icingaweb2 \
+ * --document-root=/usr/share/icingaweb2/public \
+ * --config=/etc/icingaweb2
+ *
+ * icingacli setup config webserver apache \
+ * --file=/etc/apache2/conf.d/icingaweb2.conf
+ *
+ * icingacli setup config webserver nginx \
+ * --root=/usr/share/icingaweb2/public \
+ * --fpm-uri=unix:/var/run/php5-fpm.sock
+ */
+ public function webserverAction()
+ {
+ if (($type = $this->params->getStandalone()) === null) {
+ $this->fail($this->translate('Argument type is mandatory.'));
+ }
+ try {
+ $webserver = Webserver::createInstance($type);
+ } catch (ProgrammingError $e) {
+ $this->fail($this->translate('Unknown type') . ': ' . $type);
+ }
+ $urlPath = trim($this->params->get('path', $webserver->getUrlPath()));
+ if (strlen($urlPath) === 0) {
+ $this->fail($this->translate('The argument --path expects a URL path'));
+ }
+ $documentRoot = trim(
+ $this->params->get('root', $this->params->get('document-root', $webserver->getDocumentRoot()))
+ );
+ if (strlen($documentRoot) === 0) {
+ $this->fail($this->translate(
+ 'The argument --root/--document-root expects a directory from which the webserver will serve files'
+ ));
+ }
+ $configDir = trim($this->params->get('config', $webserver->getConfigDir()));
+ if (strlen($configDir) === 0) {
+ $this->fail($this->translate(
+ 'The argument --config expects a path to Icinga Web 2\'s configuration files'
+ ));
+ }
+
+ $enableFpm = $this->params->shift('enable-fpm', $webserver->getEnableFpm());
+
+ $fpmUri = trim($this->params->get('fpm-uri', $webserver->getFpmUri()));
+ if (empty($fpmUri)) {
+ $this->fail($this->translate(
+ 'The argument --fpm-uri expects an address or path where to pass requests to FPM'
+ ));
+ }
+ $webserver
+ ->setDocumentRoot($documentRoot)
+ ->setConfigDir($configDir)
+ ->setUrlPath($urlPath)
+ ->setEnableFpm($enableFpm)
+ ->setFpmUri($fpmUri);
+ $config = $webserver->generate() . "\n";
+ if (($file = $this->params->get('file')) !== null) {
+ if (file_exists($file) === true) {
+ $this->fail(sprintf($this->translate('File %s already exists. Please delete it first.'), $file));
+ }
+ Logger::info($this->translate('Write %s configuration to file: %s'), $type, $file);
+ $re = file_put_contents($file, $config);
+ if ($re === false) {
+ $this->fail($this->translate('Could not write to file') . ': ' . $file);
+ }
+ Logger::info($this->translate('Successfully written %d bytes to file'), $re);
+ return true;
+ }
+ echo $config;
+ return true;
+ }
+}
diff --git a/modules/setup/application/clicommands/TokenCommand.php b/modules/setup/application/clicommands/TokenCommand.php
new file mode 100644
index 0000000..f1c30d1
--- /dev/null
+++ b/modules/setup/application/clicommands/TokenCommand.php
@@ -0,0 +1,89 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Clicommands;
+
+use Icinga\Cli\Command;
+
+/**
+ * Maintain the setup wizard's authentication
+ *
+ * The token command allows you to display the current setup token or to create a new one.
+ *
+ * Usage: icingacli setup token <action>
+ */
+class TokenCommand extends Command
+{
+ /**
+ * Display the current setup token
+ *
+ * Shows you the current setup token used to authenticate when setting up Icinga Web 2 using the web-based wizard.
+ *
+ * USAGE:
+ *
+ * icingacli setup token show [options]
+ *
+ * OPTIONS:
+ *
+ * --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
+ */
+ public function showAction()
+ {
+ $configDir = $this->params->get('config', $this->app->getConfigDir());
+ if (! is_string($configDir) || strlen(trim($configDir)) === 0) {
+ $this->fail($this->translate(
+ 'The argument --config expects a path to Icinga Web 2\'s configuration files'
+ ));
+ }
+
+ $token = file_get_contents($configDir . '/setup.token');
+ if (! $token) {
+ $this->fail(
+ $this->translate('Nothing to show. Please create a new setup token using the generateToken action.')
+ );
+ }
+
+ printf($this->translate("The current setup token is: %s\n"), $token);
+ }
+
+ /**
+ * Create a new setup token
+ *
+ * Re-generates the setup token used to authenticate when setting up Icinga Web 2 using the web-based wizard.
+ *
+ * USAGE:
+ *
+ * icingacli setup token create [options]
+ *
+ * OPTIONS:
+ *
+ * --config=<directory> Path to Icinga Web 2's configuration files [/etc/icingaweb2]
+ */
+ public function createAction()
+ {
+ $configDir = $this->params->get('config', $this->app->getConfigDir());
+ if (! is_string($configDir) || strlen(trim($configDir)) === 0) {
+ $this->fail($this->translate(
+ 'The argument --config expects a path to Icinga Web 2\'s configuration files'
+ ));
+ }
+
+ $file = $configDir . '/setup.token';
+
+ if (function_exists('openssl_random_pseudo_bytes')) {
+ $token = bin2hex(openssl_random_pseudo_bytes(8));
+ } else {
+ $token = substr(md5(mt_rand()), 16);
+ }
+
+ if (false === file_put_contents($file, $token)) {
+ $this->fail(sprintf($this->translate('Cannot write setup token "%s" to disk.'), $file));
+ }
+
+ if (! chmod($file, 0660)) {
+ $this->fail(sprintf($this->translate('Cannot change access mode of "%s" to %o.'), $file, 0660));
+ }
+
+ printf($this->translate("The newly generated setup token is: %s\n"), $token);
+ }
+}
diff --git a/modules/setup/application/controllers/IndexController.php b/modules/setup/application/controllers/IndexController.php
new file mode 100644
index 0000000..b75643c
--- /dev/null
+++ b/modules/setup/application/controllers/IndexController.php
@@ -0,0 +1,91 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Controllers;
+
+use Icinga\Module\Setup\WebWizard;
+use Icinga\Web\Controller;
+use Icinga\Web\Form;
+use Icinga\Web\Url;
+
+class IndexController extends Controller
+{
+ /**
+ * Whether the controller requires the user to be authenticated
+ *
+ * FALSE as the wizard uses token authentication
+ *
+ * @var bool
+ */
+ protected $requiresAuthentication = false;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected $innerLayout = 'inline';
+
+ /**
+ * Show the web wizard and run the configuration once finished
+ */
+ public function indexAction()
+ {
+ $wizard = new WebWizard();
+
+ if ($wizard->isFinished()) {
+ $setup = $wizard->getSetup();
+ $success = $setup->run();
+ if ($success) {
+ $wizard->clearSession();
+ } else {
+ $wizard->setIsFinished(false);
+ }
+
+ $this->view->success = $success;
+ $this->view->report = $setup->getReport();
+ } else {
+ $wizard->handleRequest();
+
+ $restartForm = new Form();
+ $restartForm->setUidDisabled();
+ $restartForm->setName('setup_restart_form');
+ $restartForm->setAction(Url::fromPath('setup/index/restart'));
+ $restartForm->setAttrib('class', 'restart-form');
+ $restartForm->addElement(
+ 'button',
+ 'btn_submit',
+ array(
+ 'type' => 'submit',
+ 'value' => 'btn_submit',
+ 'escape' => false,
+ 'label' => $this->view->icon('reply-all'),
+ 'title' => $this->translate('Restart the setup'),
+ 'decorators' => array('ViewHelper')
+ )
+ );
+
+ $this->view->restartForm = $restartForm;
+ }
+
+ $this->view->wizard = $wizard;
+ $this->view->title = $this->translate('Setup') . ' :: ' . $this->view->defaultTitle;
+ }
+
+ /**
+ * Reset session and restart the wizard
+ */
+ public function restartAction()
+ {
+ $this->assertHttpMethod('POST');
+
+ $form = new Form(array(
+ 'onSuccess' => function () {
+ $wizard = new WebWizard();
+ $wizard->clearSession(false);
+ }
+ ));
+ $form->setUidDisabled();
+ $form->setRedirectUrl('setup');
+ $form->setSubmitLabel('btn_submit');
+ $form->handleRequest();
+ }
+}
diff --git a/modules/setup/application/forms/AdminAccountPage.php b/modules/setup/application/forms/AdminAccountPage.php
new file mode 100644
index 0000000..3252ec1
--- /dev/null
+++ b/modules/setup/application/forms/AdminAccountPage.php
@@ -0,0 +1,423 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Authentication\User\ExternalBackend;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Authentication\User\DbUserBackend;
+use Icinga\Authentication\User\LdapUserBackend;
+use Icinga\Authentication\UserGroup\UserGroupBackend;
+use Icinga\Authentication\UserGroup\LdapUserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Data\Selectable;
+use Icinga\Exception\NotImplementedError;
+use Icinga\Web\Form;
+
+/**
+ * Wizard page to define the initial administrative account
+ */
+class AdminAccountPage extends Form
+{
+ /**
+ * The resource configuration to use
+ *
+ * @var array
+ */
+ protected $resourceConfig;
+
+ /**
+ * The user backend configuration to use
+ *
+ * @var array
+ */
+ protected $backendConfig;
+
+ /**
+ * The user group backend configuration to use
+ *
+ * @var array
+ */
+ protected $groupConfig;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_admin_account');
+ $this->setTitle($this->translate('Administration', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Now it\'s time to configure your first administrative account or group for Icinga Web 2.'
+ ));
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setResourceConfig(array $config)
+ {
+ $this->resourceConfig = $config;
+ return $this;
+ }
+
+ /**
+ * Set the user backend configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setBackendConfig(array $config)
+ {
+ $this->backendConfig = $config;
+ return $this;
+ }
+
+ /**
+ * Set the user group backend configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setGroupConfig(array $config = null)
+ {
+ $this->groupConfig = $config;
+ return $this;
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $choices = array();
+ if ($this->backendConfig['backend'] !== 'db') {
+ $choices['by_name'] = $this->translate('By Name', 'setup.admin');
+ $choice = isset($formData['user_type']) ? $formData['user_type'] : 'by_name';
+
+ if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) {
+ $groups = $this->fetchGroups();
+ if (! empty($groups)) {
+ $choices['user_group'] = $this->translate('User Group', 'setup.admin');
+ }
+ }
+ } else {
+ $choices['new_user'] = $this->translate('New User', 'setup.admin');
+ $choice = isset($formData['user_type']) ? $formData['user_type'] : 'new_user';
+ }
+
+ if (in_array($this->backendConfig['backend'], array('db', 'ldap', 'msldap'))) {
+ $users = $this->fetchUsers();
+ if (! empty($users)) {
+ $choices['existing_user'] = $this->translate('Existing User', 'setup.admin');
+ }
+ }
+
+ if (count($choices) > 1) {
+ $this->addElement(
+ 'select',
+ 'user_type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Type Of Definition'),
+ 'description' => $this->translate('Choose how to define the desired account.'),
+ 'multiOptions' => $choices,
+ 'value' => $choice
+ )
+ );
+ } else {
+ $this->addElement(
+ 'hidden',
+ 'user_type',
+ array(
+ 'required' => true,
+ 'value' => key($choices)
+ )
+ );
+ }
+
+ if ($choice === 'by_name') {
+ $this->addElement(
+ 'text',
+ 'by_name',
+ array(
+ 'required' => true,
+ 'value' => $this->getUsername(),
+ 'label' => $this->translate('Username'),
+ 'description' => $this->translate(
+ 'Define the initial administrative account by providing a username that reflects'
+ . ' a user created later or one that is authenticated using external mechanisms.'
+ )
+ )
+ );
+ }
+
+ if ($choice === 'user_group') {
+ $this->addElement(
+ 'select',
+ 'user_group',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Group Name'),
+ 'description' => $this->translate(
+ 'Choose a user group reported by the LDAP backend'
+ . ' to permit its members administrative access.',
+ 'setup.admin'
+ ),
+ 'multiOptions' => array_combine($groups, $groups)
+ )
+ );
+ }
+
+ if ($choice === 'existing_user') {
+ $this->addElement(
+ 'select',
+ 'existing_user',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Username'),
+ 'description' => sprintf(
+ $this->translate(
+ 'Choose a user reported by the %s backend as the initial administrative account.',
+ 'setup.admin'
+ ),
+ $this->backendConfig['backend'] === 'db'
+ ? $this->translate('database', 'setup.admin.authbackend')
+ : 'LDAP'
+ ),
+ 'multiOptions' => array_combine($users, $users)
+ )
+ );
+ }
+
+ if ($choice === 'new_user') {
+ $this->addElement(
+ 'text',
+ 'new_user',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Username'),
+ 'description' => $this->translate(
+ 'Enter the username to be used when creating an initial administrative account.'
+ )
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'new_user_password',
+ array(
+ 'required' => true,
+ 'renderPassword' => true,
+ 'label' => $this->translate('Password'),
+ 'description' => $this->translate(
+ 'Enter the password to assign to the newly created account.'
+ )
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'new_user_2ndpass',
+ array(
+ 'required' => true,
+ 'renderPassword' => true,
+ 'label' => $this->translate('Repeat password'),
+ 'description' => $this->translate(
+ 'Please repeat the password given above to avoid typing errors.'
+ ),
+ 'validators' => array(
+ array('identical', false, array('new_user_password'))
+ )
+ )
+ );
+ }
+ }
+
+ /**
+ * Validate the given request data and ensure that any new user does not already exist
+ *
+ * @param array $data The request data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (false === parent::isValid($data)) {
+ return false;
+ }
+
+ if ($data['user_type'] === 'new_user' && $this->hasUser($data['new_user'])) {
+ $this->getElement('new_user')->addError($this->translate('Username already exists.'));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return the name of the externally authenticated user
+ *
+ * @return string
+ */
+ protected function getUsername()
+ {
+ list($name, $_) = ExternalBackend::getRemoteUserInformation();
+ if ($name === null) {
+ return '';
+ }
+
+ if (isset($this->backendConfig['strip_username_regexp']) && $this->backendConfig['strip_username_regexp']) {
+ // No need to silence or log anything here because the pattern has
+ // already been successfully compiled during backend configuration
+ $name = preg_replace($this->backendConfig['strip_username_regexp'], '', $name);
+ }
+
+ return $name;
+ }
+
+ /**
+ * Return the names of all users the user backend currently provides
+ *
+ * @return array
+ */
+ protected function fetchUsers()
+ {
+ try {
+ $query = $this
+ ->createUserBackend()
+ ->select(array('user_name'))
+ ->order('user_name', 'asc', true);
+ if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) {
+ $query->getQuery()->setUsePagedResults();
+ }
+
+ return $query->fetchColumn();
+ } catch (Exception $_) {
+ // No need to handle anything special here. Error means no users found.
+ return array();
+ }
+ }
+
+ /**
+ * Return whether the user backend provides a user with the given name
+ *
+ * @param string $username
+ *
+ * @return bool
+ */
+ protected function hasUser($username)
+ {
+ try {
+ return $this
+ ->createUserBackend()
+ ->select()
+ ->where('user_name', $username)
+ ->count() > 1;
+ } catch (Exception $_) {
+ return false;
+ }
+ }
+
+ /**
+ * Create and return the user backend
+ *
+ * @return DbUserBackend|LdapUserBackend
+ */
+ protected function createUserBackend()
+ {
+ $resourceConfig = new Config();
+ $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig);
+ ResourceFactory::setConfig($resourceConfig);
+
+ $config = new ConfigObject($this->backendConfig);
+ $config->resource = $this->resourceConfig['name'];
+ return UserBackend::create(null, $config);
+ }
+
+ /**
+ * Return the names of all user groups the user group backend currently provides
+ *
+ * @return array
+ */
+ protected function fetchGroups()
+ {
+ try {
+ $query = $this
+ ->createUserGroupBackend()
+ ->select(array('group_name'));
+ if (in_array($this->backendConfig['backend'], array('ldap', 'msldap'))) {
+ $query->getQuery()->setUsePagedResults();
+ }
+
+ return $query->fetchColumn();
+ } catch (Exception $_) {
+ // No need to handle anything special here. Error means no groups found.
+ return array();
+ }
+ }
+
+ /**
+ * Return whether the user group backend provides a user group with the given name
+ *
+ * @param string $groupname
+ *
+ * @return bool
+ */
+ protected function hasGroup($groupname)
+ {
+ try {
+ return $this
+ ->createUserGroupBackend()
+ ->select()
+ ->where('group_name', $groupname)
+ ->count() > 1;
+ } catch (Exception $_) {
+ return false;
+ }
+ }
+
+ /**
+ * Create and return the user group backend
+ *
+ * @return LdapUserGroupBackend
+ */
+ protected function createUserGroupBackend()
+ {
+ $resourceConfig = new Config();
+ $resourceConfig->setSection($this->resourceConfig['name'], $this->resourceConfig);
+ ResourceFactory::setConfig($resourceConfig);
+
+ $backendConfig = new Config();
+ $backendConfig->setSection($this->backendConfig['name'], array_merge(
+ $this->backendConfig,
+ array('resource' => $this->resourceConfig['name'])
+ ));
+ UserBackend::setConfig($backendConfig);
+
+ if (empty($this->groupConfig)) {
+ $groupConfig = new ConfigObject(array(
+ 'backend' => $this->backendConfig['backend'], // _Should_ be "db" or "msldap"
+ 'resource' => $this->resourceConfig['name'],
+ 'user_backend' => $this->backendConfig['name'] // Gets ignored if 'backend' is "db"
+ ));
+ } else {
+ $groupConfig = new ConfigObject($this->groupConfig);
+ }
+
+ $backend = UserGroupBackend::create(null, $groupConfig);
+ if (! $backend instanceof Selectable) {
+ throw new NotImplementedError('Unsupported, until #9772 has been resolved');
+ }
+
+ return $backend;
+ }
+}
diff --git a/modules/setup/application/forms/AuthBackendPage.php b/modules/setup/application/forms/AuthBackendPage.php
new file mode 100644
index 0000000..4280c64
--- /dev/null
+++ b/modules/setup/application/forms/AuthBackendPage.php
@@ -0,0 +1,273 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Application\Config;
+use Icinga\Data\ResourceFactory;
+use Icinga\Forms\Config\UserBackendConfigForm;
+use Icinga\Forms\Config\UserBackend\DbBackendForm;
+use Icinga\Forms\Config\UserBackend\LdapBackendForm;
+use Icinga\Forms\Config\UserBackend\ExternalBackendForm;
+use Icinga\Web\Form;
+
+/**
+ * Wizard page to define authentication backend specific details
+ */
+class AuthBackendPage extends Form
+{
+ /**
+ * The resource configuration to use
+ *
+ * @var array
+ */
+ protected $config;
+
+ /**
+ * Default values for the subform's elements suggested by a previous step
+ *
+ * @var string[]
+ */
+ protected $suggestions = array();
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_authentication_backend');
+ $this->setTitle($this->translate('Authentication Backend', 'setup.page.title'));
+ $this->setValidatePartial(true);
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setResourceConfig(array $config)
+ {
+ $resourceConfig = new Config();
+ $resourceConfig->setSection($config['name'], $config);
+ ResourceFactory::setConfig($resourceConfig);
+
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ $this->addSkipValidationCheckbox();
+ }
+
+ if (! isset($this->config) || $this->config['type'] === 'external') {
+ $backendForm = new ExternalBackendForm();
+ $backendForm->create($formData);
+ $this->addDescription($this->translate(
+ 'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary'
+ . ' to adjust usernames before any permissions, restrictions, etc. are being applied.'
+ ));
+ } elseif ($this->config['type'] === 'db') {
+ $this->setRequiredCue(null);
+ $backendForm = new DbBackendForm();
+ $backendForm->setRequiredCue(null);
+ $backendForm->create($formData)->removeElement('resource');
+ $this->addDescription($this->translate(
+ 'As you\'ve chosen to use a database for authentication all you need '
+ . 'to do now is defining a name for your first authentication backend.'
+ ));
+ } elseif ($this->config['type'] === 'ldap') {
+ $type = null;
+ if (! isset($formData['type'])) {
+ if (isset($formData['backend'])) {
+ $formData['type'] = $type = $formData['backend'];
+ } elseif (isset($this->suggestions['backend'])) {
+ $formData['type'] = $type = $this->suggestions['backend'];
+ }
+ }
+
+ $backendForm = new LdapBackendForm();
+ $backendForm->setSuggestions($this->suggestions);
+ $backendForm->setResources(array($this->config['name']));
+ $backendForm->create($formData);
+ $backendForm->getElement('resource')->setIgnore(true);
+ $this->addDescription($this->translate(
+ 'Before you are able to authenticate using the LDAP connection defined earlier you need to'
+ . ' provide some more information so that Icinga Web 2 is able to locate account details.'
+ ));
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'ignore' => true,
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Backend Type'),
+ 'description' => $this->translate(
+ 'The type of the resource being used for this authenticaton provider'
+ ),
+ 'multiOptions' => array(
+ 'ldap' => 'LDAP',
+ 'msldap' => 'ActiveDirectory'
+ ),
+ 'value' => $type
+ )
+ );
+ }
+
+ $backendForm->getElement('name')->setValue('icingaweb2');
+ $this->addSubForm($backendForm, 'backend_form');
+ }
+
+ /**
+ * Retrieve all form element values
+ *
+ * @param bool $suppressArrayNotation Ignored
+ *
+ * @return array
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues();
+ $values = array_merge($values, $values['backend_form']);
+ unset($values['backend_form']);
+ return $values;
+ }
+
+ /**
+ * Validate the given form data and check whether it's possible to authenticate using the configured backend
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (! parent::isValid($data)) {
+ return false;
+ }
+
+ if (isset($this->config)) {
+ if ($this->config['type'] === 'ldap' && (
+ ! isset($data['skip_validation']) || $data['skip_validation'] == 0)
+ ) {
+ $self = clone $this;
+ $self->getSubForm('backend_form')->getElement('resource')->setIgnore(false);
+ $inspection = UserBackendConfigForm::inspectUserBackend($self);
+ if ($inspection && $inspection->hasError()) {
+ $this->error($inspection->getError());
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['backend_validation']) && parent::isValid($formData)) {
+ $self = clone $this;
+ if (($resourceElement = $self->getSubForm('backend_form')->getElement('resource')) !== null) {
+ $resourceElement->setIgnore(false);
+ }
+
+ $inspection = UserBackendConfigForm::inspectUserBackend($self);
+ if ($inspection !== null) {
+ $join = function ($e) use (&$join) {
+ return is_string($e) ? $e : join("\n", array_map($join, $e));
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+
+ if ($inspection->hasError()) {
+ $this->warning(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (isset($formData['discovery_btn']) || isset($formData['btn_discover_domain'])) {
+ return parent::isValidPartial($formData);
+ } elseif (! isset($formData['backend_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to this form by which the user can skip the authentication validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'order' => 0,
+ 'ignore' => true,
+ 'required' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate('Check this to not to validate authentication using this backend')
+ )
+ );
+ }
+
+ /**
+ * Get default values for the subform's elements suggested by a previous step
+ *
+ * @return string[]
+ */
+ public function getSuggestions()
+ {
+ return $this->suggestions;
+ }
+
+ /**
+ * Set default values for the subform's elements suggested by a previous step
+ *
+ * @param string[] $suggestions
+ *
+ * @return $this
+ */
+ public function setSuggestions(array $suggestions)
+ {
+ $this->suggestions = $suggestions;
+
+ return $this;
+ }
+}
diff --git a/modules/setup/application/forms/AuthenticationPage.php b/modules/setup/application/forms/AuthenticationPage.php
new file mode 100644
index 0000000..52e3c66
--- /dev/null
+++ b/modules/setup/application/forms/AuthenticationPage.php
@@ -0,0 +1,69 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Authentication\User\ExternalBackend;
+use Icinga\Web\Form;
+use Icinga\Application\Platform;
+
+/**
+ * Wizard page to choose an authentication backend
+ */
+class AuthenticationPage extends Form
+{
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setRequiredCue(null);
+ $this->setName('setup_authentication_type');
+ $this->setTitle($this->translate('Authentication', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Please choose how you want to authenticate when accessing Icinga Web 2.'
+ . ' Configuring backend specific details follows in a later step.'
+ ));
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ if (isset($formData['type']) && $formData['type'] === 'external') {
+ list($username, $_) = ExternalBackend::getRemoteUserInformation();
+ if ($username === null) {
+ $this->info(
+ $this->translate(
+ 'You\'re currently not authenticated using any of the web server\'s authentication '
+ . 'mechanisms. Make sure you\'ll configure such, otherwise you\'ll not be able to '
+ . 'log into Icinga Web 2.'
+ ),
+ false
+ );
+ }
+ }
+
+ $backendTypes = array();
+ if (Platform::hasMysqlSupport() || Platform::hasPostgresqlSupport()) {
+ $backendTypes['db'] = $this->translate('Database');
+ }
+ if (Platform::extensionLoaded('ldap')) {
+ $backendTypes['ldap'] = 'LDAP';
+ }
+ $backendTypes['external'] = $this->translate('External');
+
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Authentication Type'),
+ 'description' => $this->translate('The type of authentication to use when accessing Icinga Web 2'),
+ 'multiOptions' => $backendTypes
+ )
+ );
+ }
+}
diff --git a/modules/setup/application/forms/DatabaseCreationPage.php b/modules/setup/application/forms/DatabaseCreationPage.php
new file mode 100644
index 0000000..8660a21
--- /dev/null
+++ b/modules/setup/application/forms/DatabaseCreationPage.php
@@ -0,0 +1,208 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use PDOException;
+use Icinga\Web\Form;
+use Icinga\Module\Setup\Utils\DbTool;
+
+/**
+ * Wizard page to define a database user that is able to create databases and tables
+ */
+class DatabaseCreationPage extends Form
+{
+ /**
+ * The resource configuration to use
+ *
+ * @var array
+ */
+ protected $config;
+
+ /**
+ * The required privileges to setup the database
+ *
+ * @var array
+ */
+ protected $databaseSetupPrivileges;
+
+ /**
+ * The required privileges to operate the database
+ *
+ * @var array
+ */
+ protected $databaseUsagePrivileges;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setTitle($this->translate('Database Setup', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'It seems that either the database you defined earlier does not yet exist and cannot be created'
+ . ' using the provided access credentials, the database does not have the required schema to be'
+ . ' operated by Icinga Web 2 or the provided access credentials do not have the sufficient '
+ . 'permissions to access the database. Please provide appropriate access credentials to solve this.'
+ ));
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setResourceConfig(array $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Set the required privileges to setup the database
+ *
+ * @param array $privileges The privileges
+ *
+ * @return $this
+ */
+ public function setDatabaseSetupPrivileges(array $privileges)
+ {
+ $this->databaseSetupPrivileges = $privileges;
+ return $this;
+ }
+
+ /**
+ * Set the required privileges to operate the database
+ *
+ * @param array $privileges The privileges
+ *
+ * @return $this
+ */
+ public function setDatabaseUsagePrivileges(array $privileges)
+ {
+ $this->databaseUsagePrivileges = $privileges;
+ return $this;
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $skipValidation = isset($formData['skip_validation']) && $formData['skip_validation'];
+ $this->addElement(
+ 'text',
+ 'username',
+ array(
+ 'required' => false === $skipValidation,
+ 'label' => $this->translate('Username'),
+ 'description' => $this->translate(
+ 'A user which is able to create databases and/or touch the database schema'
+ )
+ )
+ );
+ $this->addElement(
+ 'password',
+ 'password',
+ array(
+ 'renderPassword' => true,
+ 'label' => $this->translate('Password'),
+ 'description' => $this->translate('The password for the database user defined above')
+ )
+ );
+
+ if ($skipValidation) {
+ $this->addSkipValidationCheckbox();
+ } else {
+ $this->addElement(
+ 'hidden',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'value' => 0
+ )
+ );
+ }
+ }
+
+ /**
+ * Validate the given form data and check whether the defined user has sufficient access rights
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (false === parent::isValid($data)) {
+ return false;
+ }
+
+ if (isset($data['skip_validation']) && $data['skip_validation']) {
+ return true;
+ }
+
+ $config = $this->config;
+ $config['username'] = $this->getValue('username');
+ $config['password'] = $this->getValue('password');
+ $db = new DbTool($config);
+
+ try {
+ $db->connectToDb(); // Are we able to login on the database?
+ } catch (PDOException $_) {
+ try {
+ $db->connectToHost(); // Are we able to login on the server?
+ } catch (PDOException $e) {
+ // We are NOT able to login on the server..
+ $this->error($e->getMessage());
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+ }
+
+ // In case we are connected the credentials filled into this
+ // form need to be granted to create databases, users...
+ if (false === $db->checkPrivileges($this->databaseSetupPrivileges)) {
+ $this->error(
+ $this->translate('The provided credentials cannot be used to create the database and/or the user.')
+ );
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+
+ // ...and to grant all required usage privileges to others
+ if (false === $db->isGrantable($this->databaseUsagePrivileges)) {
+ $this->error(sprintf(
+ $this->translate(
+ 'The provided credentials cannot be used to grant all required privileges to the login "%s".'
+ ),
+ $this->config['username']
+ ));
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the login and privilege validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'order' => 0,
+ 'required' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate(
+ 'Check this to not to validate the ability to login and required privileges'
+ )
+ )
+ );
+ }
+}
diff --git a/modules/setup/application/forms/DbResourcePage.php b/modules/setup/application/forms/DbResourcePage.php
new file mode 100644
index 0000000..b3f1784
--- /dev/null
+++ b/modules/setup/application/forms/DbResourcePage.php
@@ -0,0 +1,183 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Exception;
+use Icinga\Web\Form;
+use Icinga\Forms\Config\Resource\DbResourceForm;
+use Icinga\Module\Setup\Utils\DbTool;
+
+/**
+ * Wizard page to define connection details for a database resource
+ */
+class DbResourcePage extends Form
+{
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setTitle($this->translate('Database Resource', 'setup.page.title'));
+ $this->setValidatePartial(true);
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'hidden',
+ 'type',
+ array(
+ 'required' => true,
+ 'value' => 'db'
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ $this->addSkipValidationCheckbox();
+ } else {
+ $this->addElement(
+ 'hidden',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'value' => 0
+ )
+ );
+ }
+
+ $resourceForm = new DbResourceForm();
+ $this->addElements($resourceForm->createElements($formData)->getElements());
+ $this->getElement('name')->setValue('icingaweb_db');
+ }
+
+ /**
+ * Validate the given form data and check whether it's possible to connect to the database server
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (false === parent::isValid($data)) {
+ return false;
+ }
+
+ if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) {
+ if (! $this->validateConfiguration()) {
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Check whether it's possible to connect to the database server
+ *
+ * This will only run the check if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['backend_validation']) && parent::isValid($formData)) {
+ if (! $this->validateConfiguration()) {
+ return false;
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (! isset($formData['backend_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether the configuration is valid
+ *
+ * @return bool
+ */
+ protected function validateConfiguration()
+ {
+ try {
+ $db = new DbTool($this->getValues());
+ $db->checkConnectivity();
+ } catch (Exception $e) {
+ $this->error(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $e->getMessage()
+ ));
+ return false;
+ }
+
+ $state = true;
+ $connectionError = null;
+
+ try {
+ $db->connectToDb();
+ } catch (Exception $e) {
+ $connectionError = $e;
+ }
+
+ if ($connectionError === null && array_search('icinga_instances', $db->listTables(), true) !== false) {
+ $this->warning($this->translate(
+ 'The database you\'ve configured to use for Icinga Web 2 seems to be the one of Icinga. Please be aware'
+ . ' that this database configuration is supposed to be used for Icinga Web 2\'s configuration and that'
+ . ' it is highly recommended to not mix different schemas in the same database. If this is intentional,'
+ . ' you can skip the validation and ignore this warning. If not, please provide a different database.'
+ ));
+ $state = false;
+ }
+
+ if ($this->getValue('db') === 'pgsql') {
+ if ($connectionError !== null) {
+ $this->warning(sprintf(
+ $this->translate('Unable to check the server\'s version. This is usually not a critical error'
+ . ' as there is probably only access to the database permitted which does not exist yet. If you are'
+ . ' absolutely sure you are running PostgreSQL in a version equal to or newer than 9.1,'
+ . ' you can skip the validation and safely proceed to the next step. The error was: %s'),
+ $connectionError->getMessage()
+ ));
+ $state = false;
+ } else {
+ $version = $db->getServerVersion();
+ if (version_compare($version, '9.1', '<')) {
+ $this->error(sprintf(
+ $this->translate('The server\'s version %s is too old. The minimum required version is %s.'),
+ $version,
+ '9.1'
+ ));
+ $state = false;
+ }
+ }
+ }
+
+ return $state;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the configuration validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate('Check this to not to validate the configuration')
+ )
+ );
+ }
+}
diff --git a/modules/setup/application/forms/GeneralConfigPage.php b/modules/setup/application/forms/GeneralConfigPage.php
new file mode 100644
index 0000000..5b9f011
--- /dev/null
+++ b/modules/setup/application/forms/GeneralConfigPage.php
@@ -0,0 +1,41 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Forms\Config\General\ApplicationConfigForm;
+use Icinga\Forms\Config\General\LoggingConfigForm;
+use Icinga\Web\Form;
+
+/**
+ * Wizard page to define the application and logging configuration
+ */
+class GeneralConfigPage extends Form
+{
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_general_config');
+ $this->setTitle($this->translate('Application Configuration', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Now please adjust all application and logging related configuration options to fit your needs.'
+ ));
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $appConfigForm = new ApplicationConfigForm();
+ $appConfigForm->createElements($formData);
+ $appConfigForm->removeElement('global_module_path');
+ $appConfigForm->removeElement('global_config_resource');
+ $this->addElements($appConfigForm->getElements());
+
+ $loggingConfigForm = new LoggingConfigForm();
+ $this->addElements($loggingConfigForm->createElements($formData)->getElements());
+ }
+}
diff --git a/modules/setup/application/forms/LdapDiscoveryConfirmPage.php b/modules/setup/application/forms/LdapDiscoveryConfirmPage.php
new file mode 100644
index 0000000..33bc907
--- /dev/null
+++ b/modules/setup/application/forms/LdapDiscoveryConfirmPage.php
@@ -0,0 +1,133 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Data\ConfigObject;
+use Icinga\Web\Form;
+
+/**
+ * Wizard page to define the connection details for a LDAP resource
+ */
+class LdapDiscoveryConfirmPage extends Form
+{
+ const TYPE_AD = 'MS ActiveDirectory';
+ const TYPE_MISC = 'LDAP';
+
+ private $infoTemplate = <<< 'EOT'
+<table><tbody>
+ <tr><td><strong>Type:</strong></td><td>{type}</td></tr>
+ <tr><td><strong>Port:</strong></td><td>{port}</td></tr>
+ <tr><td><strong>Root DN:</strong></td><td>{root_dn}</td></tr>
+ <tr><td><strong>User Object Class:</strong></td><td>{user_class}</td></tr>
+ <tr><td><strong>User Name Attribute:</strong></td><td>{user_attribute}</td></tr>
+</tbody></table>
+EOT;
+
+ /**
+ * The previous configuration
+ *
+ * @var array
+ */
+ private $config;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_ldap_discovery_confirm');
+ $this->setTitle($this->translate('LDAP Discovery Results', 'setup.page.title'));
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setResourceConfig(array $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Return the resource configuration as Config object
+ *
+ * @return ConfigObject
+ */
+ public function getResourceConfig()
+ {
+ return new ConfigObject($this->config);
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $resource = $this->config['resource'];
+ $backend = $this->config['backend'];
+ $html = $this->infoTemplate;
+ $html = str_replace('{type}', $this->config['type'], $html);
+ $html = str_replace('{hostname}', $resource['hostname'], $html);
+ $html = str_replace('{port}', $resource['port'], $html);
+ $html = str_replace('{root_dn}', $resource['root_dn'], $html);
+ $html = str_replace('{user_attribute}', $backend['user_name_attribute'], $html);
+ $html = str_replace('{user_class}', $backend['user_class'], $html);
+
+ $this->addDescription(sprintf(
+ $this->translate('The following directory service has been found on domain "%s".'),
+ $this->config['domain']
+ ));
+
+ $this->addElement(
+ 'note',
+ 'suggestion',
+ array(
+ 'value' => $html,
+ 'decorators' => array(
+ 'ViewHelper',
+ array(
+ 'HtmlTag', array('tag' => 'div')
+ )
+ )
+ )
+ );
+
+ $this->addElement(
+ 'checkbox',
+ 'confirm',
+ array(
+ 'value' => '1',
+ 'label' => $this->translate('Use this configuration?')
+ )
+ );
+ }
+
+ /**
+ * Validate the given form data and check whether a BIND-request is successful
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (false === parent::isValid($data)) {
+ return false;
+ }
+ return true;
+ }
+
+ public function getValues($suppressArrayNotation = false)
+ {
+ if ($this->getValue('confirm') === '1') {
+ // use configuration
+ return $this->config;
+ }
+ return null;
+ }
+}
diff --git a/modules/setup/application/forms/LdapDiscoveryPage.php b/modules/setup/application/forms/LdapDiscoveryPage.php
new file mode 100644
index 0000000..7b5de17
--- /dev/null
+++ b/modules/setup/application/forms/LdapDiscoveryPage.php
@@ -0,0 +1,115 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Exception;
+use Zend_Validate_NotEmpty;
+use Icinga\Exception\IcingaException;
+use Icinga\Web\Form;
+use Icinga\Web\Form\ErrorLabeller;
+use Icinga\Forms\LdapDiscoveryForm;
+use Icinga\Protocol\Ldap\Discovery;
+use Icinga\Module\Setup\Forms\LdapDiscoveryConfirmPage;
+
+/**
+ * Wizard page to define the connection details for a LDAP resource
+ */
+class LdapDiscoveryPage extends Form
+{
+ /**
+ * @var Discovery
+ */
+ private $discovery;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_ldap_discovery');
+ $this->setTitle($this->translate('LDAP Discovery', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'You can use this page to discover LDAP or ActiveDirectory servers ' .
+ ' for authentication. If you don\'t want to execute a discovery, just skip this step.'
+ ));
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $discoveryForm = new LdapDiscoveryForm();
+ $this->addElements($discoveryForm->createElements($formData)->getElements());
+
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'label' => $this->translate('Skip'),
+ 'description' => $this->translate('Do not discover LDAP servers and enter all settings manually.')
+ )
+ );
+ }
+
+ /**
+ * Validate the given form data and check whether a BIND-request is successful
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (false === parent::isValid($data)) {
+ return false;
+ }
+ if (isset($data['skip_validation']) && $data['skip_validation']) {
+ return true;
+ }
+
+ if (isset($data['domain']) && $data['domain']) {
+ try {
+ $this->discovery = Discovery::discoverDomain($data['domain']);
+ if ($this->discovery->isSuccess()) {
+ return true;
+ } else {
+ $this->error($this->discovery->getError()->getMessage());
+ }
+ } catch (Exception $e) {
+ $this->error(sprintf(
+ $this->translate('Could not find any LDAP servers on the domain "%s". An error occurred: %s'),
+ $data['domain'],
+ IcingaException::describe($e)
+ ));
+ }
+ } else {
+ $labeller = new ErrorLabeller(array('element' => $this->getElement('domain')));
+ $this->getElement('domain')->addError($labeller->translate(Zend_Validate_NotEmpty::IS_EMPTY));
+ }
+
+ return false;
+ }
+
+ /**
+ * Suggest settings based on the underlying discovery
+ *
+ * @param bool $suppressArrayNotation
+ *
+ * @return array
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ if (! isset($this->discovery) || ! $this->discovery->isSuccess()) {
+ return [];
+ }
+ $disc = $this->discovery;
+ return array(
+ 'domain' => $this->getValue('domain'),
+ 'type' => $disc->isAd() ? LdapDiscoveryConfirmPage::TYPE_AD : LdapDiscoveryConfirmPage::TYPE_MISC,
+ 'resource' => $disc->suggestResourceSettings(),
+ 'backend' => $disc->suggestBackendSettings()
+ );
+ }
+}
diff --git a/modules/setup/application/forms/LdapResourcePage.php b/modules/setup/application/forms/LdapResourcePage.php
new file mode 100644
index 0000000..7786407
--- /dev/null
+++ b/modules/setup/application/forms/LdapResourcePage.php
@@ -0,0 +1,152 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Web\Form;
+use Icinga\Forms\Config\ResourceConfigForm;
+use Icinga\Forms\Config\Resource\LdapResourceForm;
+
+/**
+ * Wizard page to define the connection details for a LDAP resource
+ */
+class LdapResourcePage extends Form
+{
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_ldap_resource');
+ $this->setTitle($this->translate('LDAP Resource', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'Now please configure your AD/LDAP resource. This will later '
+ . 'be used to authenticate users logging in to Icinga Web 2.'
+ ));
+ $this->setValidatePartial(true);
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'hidden',
+ 'type',
+ array(
+ 'required' => true,
+ 'value' => 'ldap'
+ )
+ );
+
+ if (isset($formData['skip_validation']) && $formData['skip_validation']) {
+ $this->addSkipValidationCheckbox();
+ } else {
+ $this->addElement(
+ 'hidden',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'value' => 0
+ )
+ );
+ }
+
+ $resourceForm = new LdapResourceForm();
+ $this->addElements($resourceForm->createElements($formData)->getElements());
+ $this->getElement('name')->setValue('icingaweb_ldap');
+ }
+
+ /**
+ * Validate the given form data and check whether a BIND-request is successful
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (! parent::isValid($data)) {
+ return false;
+ }
+
+ if (! isset($data['skip_validation']) || $data['skip_validation'] == 0) {
+ $inspection = ResourceConfigForm::inspectResource($this);
+ if ($inspection !== null && $inspection->hasError()) {
+ $this->error($inspection->getError());
+ $this->addSkipValidationCheckbox();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Run the configured backend's inspection checks and show the result, if necessary
+ *
+ * This will only run any validation if the user pushed the 'backend_validation' button.
+ *
+ * @param array $formData
+ *
+ * @return bool
+ */
+ public function isValidPartial(array $formData)
+ {
+ if (isset($formData['backend_validation']) && parent::isValid($formData)) {
+ $inspection = ResourceConfigForm::inspectResource($this);
+ if ($inspection !== null) {
+ $join = function ($e) use (&$join) {
+ return is_string($e) ? $e : join("\n", array_map($join, $e));
+ };
+ $this->addElement(
+ 'note',
+ 'inspection_output',
+ array(
+ 'order' => 0,
+ 'value' => '<strong>' . $this->translate('Validation Log') . "</strong>\n\n"
+ . join("\n", array_map($join, $inspection->toArray())),
+ 'decorators' => array(
+ 'ViewHelper',
+ array('HtmlTag', array('tag' => 'pre', 'class' => 'log-output')),
+ )
+ )
+ );
+
+ if ($inspection->hasError()) {
+ $this->warning(sprintf(
+ $this->translate('Failed to successfully validate the configuration: %s'),
+ $inspection->getError()
+ ));
+ return false;
+ }
+ }
+
+ $this->info($this->translate('The configuration has been successfully validated.'));
+ } elseif (! isset($formData['backend_validation'])) {
+ // This is usually done by isValid(Partial), but as we're not calling any of these...
+ $this->populate($formData);
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a checkbox to the form by which the user can skip the connection validation
+ */
+ protected function addSkipValidationCheckbox()
+ {
+ $this->addElement(
+ 'checkbox',
+ 'skip_validation',
+ array(
+ 'required' => true,
+ 'label' => $this->translate('Skip Validation'),
+ 'description' => $this->translate(
+ 'Check this to not to validate connectivity with the given directory service'
+ )
+ )
+ );
+ }
+}
diff --git a/modules/setup/application/forms/ModulePage.php b/modules/setup/application/forms/ModulePage.php
new file mode 100644
index 0000000..d62b5a9
--- /dev/null
+++ b/modules/setup/application/forms/ModulePage.php
@@ -0,0 +1,108 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Modules\Module;
+use Icinga\Web\Form;
+
+class ModulePage extends Form
+{
+ protected $modules;
+
+ protected $modulePaths;
+
+ protected $foundIcingaDB = false;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_modules');
+ $this->setViewScript('form/setup-modules.phtml');
+
+ $this->modulePaths = array();
+ if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) {
+ $this->modulePaths[] = $appModulePath;
+ }
+ }
+
+ public function createElements(array $formData)
+ {
+ foreach ($this->getModules() as $module) {
+ $checked = false;
+ if ($module->getName() === 'monitoring') {
+ $checked = ! $this->foundIcingaDB;
+ } elseif ($this->foundIcingaDB && $module->getName() === 'icingadb') {
+ $checked = true;
+ }
+
+ $this->addElement(
+ 'checkbox',
+ $module->getName(),
+ array(
+ 'description' => $module->getDescription(),
+ 'label' => ucfirst($module->getName()),
+ 'value' => (int) $checked,
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ }
+ }
+
+ /**
+ * @return Module[]
+ */
+ protected function getModules()
+ {
+ if ($this->modules !== null) {
+ return $this->modules;
+ } else {
+ $this->modules = array();
+ }
+
+ $moduleManager = Icinga::app()->getModuleManager();
+ $moduleManager->detectInstalledModules($this->modulePaths);
+ foreach ($moduleManager->listInstalledModules() as $moduleName) {
+ if ($moduleName !== 'setup') {
+ $this->modules[$moduleName] = $moduleManager->loadModule($moduleName)->getModule($moduleName);
+ }
+
+ if ($moduleName === 'icingadb') {
+ $this->foundIcingaDB = true;
+ }
+ }
+
+ return $this->modules;
+ }
+
+ public function getCheckedModules()
+ {
+ $modules = $this->getModules();
+
+ $checked = array();
+ foreach ($this->getElements() as $name => $element) {
+ if (array_key_exists($name, $modules) && $element->isChecked()) {
+ $checked[$name] = $modules[$name];
+ }
+ }
+
+ return $checked;
+ }
+
+ public function getModuleWizards()
+ {
+ $checked = $this->getCheckedModules();
+
+ $wizards = array();
+ foreach ($checked as $name => $module) {
+ if ($module->providesSetupWizard()) {
+ $wizards[$name] = $module->getSetupWizard();
+ }
+ }
+
+ return $wizards;
+ }
+}
diff --git a/modules/setup/application/forms/RequirementsPage.php b/modules/setup/application/forms/RequirementsPage.php
new file mode 100644
index 0000000..d1fb70e
--- /dev/null
+++ b/modules/setup/application/forms/RequirementsPage.php
@@ -0,0 +1,68 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Web\Form;
+use Icinga\Module\Setup\SetupWizard;
+
+/**
+ * Wizard page to list setup requirements
+ */
+class RequirementsPage extends Form
+{
+ /**
+ * The wizard
+ *
+ * @var SetupWizard
+ */
+ protected $wizard;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_requirements');
+ $this->setViewScript('form/setup-requirements.phtml');
+ }
+
+ /**
+ * Set the wizard
+ *
+ * @param SetupWizard $wizard
+ *
+ * @return $this
+ */
+ public function setWizard(SetupWizard $wizard)
+ {
+ $this->wizard = $wizard;
+ return $this;
+ }
+
+ /**
+ * Return the wizard
+ *
+ * @return SetupWizard
+ */
+ public function getWizard()
+ {
+ return $this->wizard;
+ }
+
+ /**
+ * Validate the given form data and check whether the wizard's requirements are fulfilled
+ *
+ * @param array $data The data to validate
+ *
+ * @return bool
+ */
+ public function isValid($data)
+ {
+ if (false === parent::isValid($data)) {
+ return false;
+ }
+
+ return $this->wizard->getRequirements()->fulfilled();
+ }
+}
diff --git a/modules/setup/application/forms/SummaryPage.php b/modules/setup/application/forms/SummaryPage.php
new file mode 100644
index 0000000..ab62d55
--- /dev/null
+++ b/modules/setup/application/forms/SummaryPage.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use LogicException;
+use Icinga\Web\Form;
+
+/**
+ * Wizard page that displays a summary of what is going to be "done"
+ */
+class SummaryPage extends Form
+{
+ /**
+ * The title of what is being set up
+ *
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * The summary to show
+ *
+ * @var array
+ */
+ protected $summary;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ if ($this->getName() === $this->filterName(get_class($this))) {
+ throw new LogicException(
+ 'When utilizing ' . get_class($this) . ' it is required to set a unique name by using the form options'
+ );
+ }
+
+ $this->setViewScript('form/setup-summary.phtml');
+ }
+
+ /**
+ * Set the title of what is being set up
+ *
+ * @param string $title
+ */
+ public function setSubjectTitle($title)
+ {
+ $this->title = $title;
+ }
+
+ /**
+ * Return the title of what is being set up
+ *
+ * @return string
+ */
+ public function getSubjectTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set the summary to show
+ *
+ * @param array $summary
+ *
+ * @return $this
+ */
+ public function setSummary(array $summary)
+ {
+ $this->summary = $summary;
+ return $this;
+ }
+
+ /**
+ * Return the summary to show
+ *
+ * @return array
+ */
+ public function getSummary()
+ {
+ return $this->summary;
+ }
+}
diff --git a/modules/setup/application/forms/UserGroupBackendPage.php b/modules/setup/application/forms/UserGroupBackendPage.php
new file mode 100644
index 0000000..751270f
--- /dev/null
+++ b/modules/setup/application/forms/UserGroupBackendPage.php
@@ -0,0 +1,147 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Application\Config;
+use Icinga\Authentication\User\UserBackend;
+use Icinga\Data\ResourceFactory;
+use Icinga\Forms\Config\UserGroup\LdapUserGroupBackendForm;
+use Icinga\Web\Form;
+
+/**
+ * Wizard page to define user group backend specific details
+ */
+class UserGroupBackendPage extends Form
+{
+ /**
+ * The resource configuration to use
+ *
+ * @var array
+ */
+ protected $resourceConfig;
+
+ /**
+ * The user backend configuration to use
+ *
+ * @var array
+ */
+ protected $backendConfig;
+
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setName('setup_usergroup_backend');
+ $this->setTitle($this->translate('User Group Backend', 'setup.page.title'));
+ $this->addDescription($this->translate(
+ 'To allow Icinga Web 2 to associate users and groups, you\'ll need to provide some further information'
+ . ' about the LDAP Connection that is already going to be used to locate account details.'
+ ));
+ }
+
+ /**
+ * Set the resource configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setResourceConfig(array $config)
+ {
+ $this->resourceConfig = $config;
+ return $this;
+ }
+
+ /**
+ * Set the user backend configuration to use
+ *
+ * @param array $config
+ *
+ * @return $this
+ */
+ public function setBackendConfig(array $config)
+ {
+ $this->backendConfig = $config;
+ return $this;
+ }
+
+ /**
+ * Return the resource configuration as Config object
+ *
+ * @return Config
+ */
+ protected function createResourceConfiguration()
+ {
+ $config = new Config();
+ $config->setSection($this->resourceConfig['name'], $this->resourceConfig);
+ return $config;
+ }
+
+ /**
+ * Return the user backend configuration as Config object
+ *
+ * @return Config
+ */
+ protected function createBackendConfiguration()
+ {
+ $config = new Config();
+ $backendConfig = $this->backendConfig;
+ $backendConfig['resource'] = $this->resourceConfig['name'];
+ $config->setSection($this->backendConfig['name'], $backendConfig);
+ return $config;
+ }
+
+ /**
+ * Create and add elements to this form
+ *
+ * @param array $formData
+ */
+ public function createElements(array $formData)
+ {
+ // LdapUserGroupBackendForm requires these factories to provide valid configurations
+ ResourceFactory::setConfig($this->createResourceConfiguration());
+ UserBackend::setConfig($this->createBackendConfiguration());
+
+ $backendForm = new LdapUserGroupBackendForm();
+ $formData['type'] = 'ldap';
+ $backendForm->create($formData);
+ $backendForm->getElement('name')->setValue('icingaweb2');
+ $this->addSubForm($backendForm, 'backend_form');
+
+ $backendForm->addElement(
+ 'hidden',
+ 'resource',
+ array(
+ 'required' => true,
+ 'value' => $this->resourceConfig['name'],
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ $backendForm->addElement(
+ 'hidden',
+ 'user_backend',
+ array(
+ 'required' => true,
+ 'value' => $this->backendConfig['name'],
+ 'decorators' => array('ViewHelper')
+ )
+ );
+ }
+
+ /**
+ * Retrieve all form element values
+ *
+ * @param bool $suppressArrayNotation Ignored
+ *
+ * @return array
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues();
+ $values = array_merge($values, $values['backend_form']);
+ unset($values['backend_form']);
+ return $values;
+ }
+}
diff --git a/modules/setup/application/forms/WelcomePage.php b/modules/setup/application/forms/WelcomePage.php
new file mode 100644
index 0000000..124a31f
--- /dev/null
+++ b/modules/setup/application/forms/WelcomePage.php
@@ -0,0 +1,45 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Forms;
+
+use Icinga\Application\Icinga;
+use Icinga\Web\Form;
+use Icinga\Module\Setup\Web\Form\Validator\TokenValidator;
+
+/**
+ * Wizard page to authenticate and welcome the user
+ */
+class WelcomePage extends Form
+{
+ /**
+ * Initialize this page
+ */
+ public function init()
+ {
+ $this->setRequiredCue(null);
+ $this->setName('setup_welcome');
+ $this->setViewScript('form/setup-welcome.phtml');
+ }
+
+ /**
+ * @see Form::createElements()
+ */
+ public function createElements(array $formData)
+ {
+ $this->addElement(
+ 'text',
+ 'token',
+ array(
+ 'class' => 'autofocus',
+ 'required' => true,
+ 'label' => $this->translate('Setup Token'),
+ 'description' => $this->translate(
+ 'For security reasons we need to ensure that you are permitted to run this wizard.'
+ . ' Please provide a token by following the instructions below.'
+ ),
+ 'validators' => array(new TokenValidator(Icinga::app()->getConfigDir() . '/setup.token'))
+ )
+ );
+ }
+}
diff --git a/modules/setup/application/views/scripts/form/setup-modules.phtml b/modules/setup/application/views/scripts/form/setup-modules.phtml
new file mode 100644
index 0000000..e57c7dc
--- /dev/null
+++ b/modules/setup/application/views/scripts/form/setup-modules.phtml
@@ -0,0 +1,33 @@
+<?php
+
+use Icinga\Web\Wizard;
+
+?>
+<form
+ id="<?= $this->escape($form->getName()); ?>"
+ name="<?= $this->escape($form->getName()); ?>"
+ enctype="<?= $this->escape($form->getEncType()); ?>"
+ method="<?= $this->escape($form->getMethod()); ?>"
+ action="<?= $this->escape($form->getAction()); ?>"
+ class="icinga-controls"
+ data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
+>
+<h2><?= $this->translate('Modules', 'setup.page.title'); ?></h2>
+<p><?= $this->translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?></p>
+<?php foreach ($form->getElements() as $element): ?>
+ <?php if (! in_array($element->getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, Wizard::PROGRESS_ELEMENT, $form->getTokenElementName(), $form->getUidElementName()))): ?>
+ <div class="module">
+ <div class="header">
+ <h3><label for="<?= $element->getId(); ?>"><strong><?= $element->getLabel(); ?></strong></label></h3>
+ <div class="element">
+ <?= $element; ?>
+ </div>
+ </div>
+ <label class="description" for="<?= $element->getId(); ?>"><?= $element->getDescription(); ?></label>
+ </div>
+ <?php endif ?>
+<?php endforeach ?>
+ <?= $form->getElement($form->getTokenElementName()); ?>
+ <?= $form->getElement($form->getUidElementName()); ?>
+ <?= $form->getDisplayGroup('buttons'); ?>
+</form>
diff --git a/modules/setup/application/views/scripts/form/setup-requirements.phtml b/modules/setup/application/views/scripts/form/setup-requirements.phtml
new file mode 100644
index 0000000..544f284
--- /dev/null
+++ b/modules/setup/application/views/scripts/form/setup-requirements.phtml
@@ -0,0 +1,48 @@
+<?php
+
+use Icinga\Web\Wizard;
+
+if (! $form->getWizard()->getRequirements()->fulfilled()) {
+ $form->getElement(Wizard::BTN_NEXT)->setAttrib('disabled', 1);
+}
+
+?>
+<h1>Icinga Web 2</h1>
+<?= $form->getWizard()->getRequirements(true); ?>
+<?php foreach ($form->getWizard()->getPage('setup_modules')->getModuleWizards() as $moduleName => $wizard): ?>
+<h1><?= ucwords($moduleName) . ' ' . $this->translate('Module'); ?></h1>
+<?= $wizard->getRequirements(); ?>
+<?php endforeach ?>
+<form
+ id="<?= $this->escape($form->getName()); ?>"
+ name="<?= $this->escape($form->getName()); ?>"
+ enctype="<?= $this->escape($form->getEncType()); ?>"
+ method="<?= $this->escape($form->getMethod()); ?>"
+ action="<?= $this->escape($form->getAction()); ?>"
+ data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
+>
+ <?= $form->getElement($form->getTokenElementName()); ?>
+ <?= $form->getElement($form->getUidElementName()); ?>
+ <div class="buttons">
+ <?php
+ $double = clone $form->getElement(Wizard::BTN_NEXT);
+ echo $double->setAttrib('class', 'double');
+ ?>
+ <?= $form->getElement(Wizard::BTN_PREV); ?>
+ <?= $form->getElement(Wizard::BTN_NEXT); ?>
+ <?= $form->getElement(Wizard::PROGRESS_ELEMENT); ?>
+ <div class="requirements-refresh">
+ <?php $title = $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?>
+ <?= $this->qlink(
+ $this->translate('Refresh'),
+ null,
+ null,
+ array(
+ 'class' => 'button-link',
+ 'title' => $title,
+ 'aria-label' => sprintf($this->translate('Refresh the page; %s'), $title)
+ )
+ ); ?>
+ </div>
+ </div>
+</form> \ No newline at end of file
diff --git a/modules/setup/application/views/scripts/form/setup-summary.phtml b/modules/setup/application/views/scripts/form/setup-summary.phtml
new file mode 100644
index 0000000..3ad0265
--- /dev/null
+++ b/modules/setup/application/views/scripts/form/setup-summary.phtml
@@ -0,0 +1,40 @@
+<?php
+
+use Icinga\Web\Wizard;
+
+$form->getElement(Wizard::BTN_NEXT)->setAttrib(
+ 'class',
+ $form->getElement(Wizard::BTN_NEXT)->getAttrib('class') . ' finish'
+);
+
+?>
+<p><?= sprintf(
+ $this->translate(
+ 'You\'ve configured %1$s successfully. You can review the changes supposed to be made before setting it up.'
+ . ' Make sure that everything is correct (Feel free to navigate back to make any corrections!) so'
+ . ' that you can start using %1$s right after it has successfully been set up.'
+ ),
+ $form->getSubjectTitle()
+); ?></p>
+<div class="summary">
+<?php foreach ($form->getSummary() as $pageHtml): ?>
+ <?php if ($pageHtml): ?>
+ <div class="page">
+ <?= $pageHtml; ?>
+ </div>
+ <?php endif ?>
+<?php endforeach ?>
+</div>
+<form
+ id="<?= $this->escape($form->getName()); ?>"
+ name="<?= $this->escape($form->getName()); ?>"
+ enctype="<?= $this->escape($form->getEncType()); ?>"
+ method="<?= $this->escape($form->getMethod()); ?>"
+ action="<?= $this->escape($form->getAction()); ?>"
+ data-progress-element="<?= Wizard::PROGRESS_ELEMENT; ?>"
+ class="summary"
+>
+ <?= $form->getElement($form->getTokenElementName()); ?>
+ <?= $form->getElement($form->getUidElementName()); ?>
+ <?= $form->getDisplayGroup('buttons'); ?>
+</form> \ No newline at end of file
diff --git a/modules/setup/application/views/scripts/form/setup-welcome.phtml b/modules/setup/application/views/scripts/form/setup-welcome.phtml
new file mode 100644
index 0000000..1be68f3
--- /dev/null
+++ b/modules/setup/application/views/scripts/form/setup-welcome.phtml
@@ -0,0 +1,120 @@
+<?php
+
+use Icinga\Application\Icinga;
+use Icinga\Application\Config;
+use Icinga\Application\Platform;
+use Icinga\Web\Wizard;
+
+$phpUser = Platform::getPhpUser();
+$configDir = Icinga::app()->getConfigDir();
+$setupTokenPath = rtrim($configDir, '/') . '/setup.token';
+$cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
+
+$groupadd = null;
+$docker = getenv('ICINGAWEB_OFFICIAL_DOCKER_IMAGE');
+
+if (! (false === ($distro = Platform::getLinuxDistro(1)) || $distro === 'linux')) {
+ foreach (array(
+ 'groupadd -r icingaweb2' => array(
+ 'redhat', 'rhel', 'centos', 'fedora',
+ 'suse', 'sles', 'sled', 'opensuse'
+ ),
+ 'addgroup --system icingaweb2' => array('debian', 'ubuntu')
+ ) as $groupadd_ => $distros) {
+ if (in_array($distro, $distros)) {
+ $groupadd = $groupadd_;
+ break;
+ }
+ }
+
+ switch ($distro) {
+ case 'redhat':
+ case 'rhel':
+ case 'centos':
+ case 'fedora':
+ $usermod = 'usermod -a -G icingaweb2 %s';
+ $webSrvUser = 'apache';
+ break;
+ case 'suse':
+ case 'sles':
+ case 'sled':
+ case 'opensuse':
+ $usermod = 'usermod -A icingaweb2 %s';
+ $webSrvUser = 'wwwrun';
+ break;
+ case 'debian':
+ case 'ubuntu':
+ $usermod = 'usermod -a -G icingaweb2 %s';
+ $webSrvUser = 'www-data';
+ break;
+ default:
+ $usermod = $webSrvUser = null;
+ }
+}
+?>
+<div class="welcome-page">
+ <h2><?= $this->translate('Welcome to the configuration of Icinga Web 2!') ?></h2>
+ <?php if (false === file_exists($setupTokenPath) && file_exists(Config::resolvePath('config.ini'))): ?>
+ <p class="restart-warning"><?= $this->translate(
+ 'You\'ve already completed the configuration of Icinga Web 2. Note that most of your configuration'
+ . ' files will be overwritten in case you\'ll re-configure Icinga Web 2 using this wizard!'
+ ); ?></p>
+ <?php else: ?>
+ <p><?= $this->translate(
+ 'This wizard will guide you through the configuration of Icinga Web 2. Once completed and successfully'
+ . ' finished you are able to log in and to explore all the new and stunning features!'
+ ); ?></p>
+ <?php endif ?>
+ <form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>" class="icinga-controls">
+ <?= $form->getElement('token'); ?>
+ <?= $form->getElement($form->getTokenElementName()); ?>
+ <?= $form->getElement($form->getUidElementName()); ?>
+ <div class="buttons">
+ <?= $form->getElement(Wizard::BTN_NEXT); ?>
+ </div>
+ </form>
+ <div class="note">
+ <h3><?= $this->translate('Generating a New Setup Token'); ?></h3>
+ <div>
+ <p><?=
+ $this->translate(
+ 'To run this wizard a user needs to authenticate using a token which is usually'
+ . ' provided to him by an administrator who\'d followed the instructions below.'
+ ); ?></p>
+ <?php if (! $docker): ?>
+ <p><?= $this->translate('In any case, make sure that all of the following applies to your environment:'); ?></p>
+ <ul>
+ <li><?= $this->translate('A system group called "icingaweb2" exists'); ?></li>
+ <?php if ($phpUser): ?>
+ <li><?= sprintf($this->translate('The user "%s" is a member of the system group "icingaweb2"'), $phpUser); ?></li>
+ <?php else: ?>
+ <li><?= $this->translate('Your webserver\'s user is a member of the system group "icingaweb2"'); ?></li>
+ <?php endif ?>
+ </ul>
+ <?php if (! ($groupadd === null || $usermod === null)) { ?>
+ <div class="code">
+ <span><?= $this->escape($groupadd . ';') ?></span>
+ <span><?= $this->escape(sprintf($usermod, $phpUser ?: $webSrvUser) . ';') ?></span>
+ </div>
+ <?php } ?>
+ <p><?= $this->translate('If you\'ve got the IcingaCLI installed you can do the following:'); ?></p>
+ <?php endif; ?>
+ <div class="code">
+ <?php if (! $docker): ?>
+ <span><?= $cliPath ? $cliPath : 'icingacli'; ?> setup config directory --group icingaweb2<?= $configDir !== '/etc/icingaweb2' ? ' --config ' . $configDir : ''; ?>;</span>
+ <?php endif; ?>
+ <span><?= $cliPath ? $cliPath : 'icingacli'; ?> setup token create;</span>
+ </div>
+ <?php if (! $docker): ?>
+ <p><?= $this->translate('In case the IcingaCLI is missing you can create the token manually:'); ?></p>
+ <div class="code">
+ <span>su <?= $phpUser ?: $this->translate('<your-webserver-user>'); ?> -s /bin/sh -c "mkdir -m 2770 <?= dirname($setupTokenPath); ?>; chgrp icingaweb2 <?= dirname($setupTokenPath); ?>; head -c 12 /dev/urandom | base64 | tee <?= $setupTokenPath; ?>; chmod 0660 <?= $setupTokenPath; ?>;";</span>
+ </div>
+ <?php endif; ?>
+ <p><?= sprintf(
+ $this->translate('Please see the %s for an extensive description on how to access and use this wizard.'),
+ '<a href="http://docs.icinga.com/">' . $this->translate('Icinga Web 2 documentation') . '</a>' // TODO: Add link to iw2 docs which points to the installation topic
+ ); ?></p>
+ </div>
+ </div>
+</div>
diff --git a/modules/setup/application/views/scripts/index/index.phtml b/modules/setup/application/views/scripts/index/index.phtml
new file mode 100644
index 0000000..b2b3bda
--- /dev/null
+++ b/modules/setup/application/views/scripts/index/index.phtml
@@ -0,0 +1,153 @@
+<?php
+
+use Icinga\Web\Notification;
+
+$pages = $wizard->getPages();
+$finished = isset($success);
+$configPages = array_slice($pages, 3, count($pages) - 4, true);
+$currentPos = array_search($wizard->getCurrentPage(), $pages, true);
+list($configPagesLeft, $configPagesRight) = array_chunk($configPages, (int)(count($configPages) / 2), true);
+
+$visitedPages = array_keys($wizard->getPageData());
+$maxProgress = max(array_merge([0], array_keys(array_filter(
+ $pages,
+ function ($page) use ($visitedPages) { return in_array($page->getName(), $visitedPages); }
+))));
+
+?>
+<div id="setup-content-wrapper" data-base-target="layout">
+ <div class="setup-header">
+ <?= $this->img('img/icinga-logo-big.png'); ?>
+ <div class="progress-bar">
+ <div class="step" style="width: 10%;">
+ <h1><?= $this->translate('Welcome', 'setup.progress'); ?></h1>
+ <?php $stateClass = $finished || $currentPos > 0 ? 'complete' : (
+ $maxProgress > 0 ? 'visited' : 'active'
+ ); ?>
+ <table><tbody><tr>
+ <td class="left"></td>
+ <td class="middle"><div class="bubble <?= $stateClass; ?>"></div></td>
+ <td class="right"><div class="line right <?= $stateClass; ?>"></div></td>
+ </tr></tbody></table>
+ </div>
+ <div class="step" style="width: 10%;">
+ <h1><?= $this->translate('Modules', 'setup.progress'); ?></h1>
+ <?php $stateClass = $finished || $currentPos > 1 ? ' complete' : (
+ $maxProgress > 1 ? ' visited' : (
+ $currentPos === 1 ? ' active' : ''
+ )
+ ); ?>
+ <table><tbody><tr>
+ <td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
+ <td class="middle"><div class="bubble <?= $stateClass; ?>"></div></td>
+ <td class="right"><div class="line right <?= $stateClass; ?>"></div></td>
+ </tr></tbody></table>
+ <?php if (($maxProgress < $currentPos && $currentPos === 1) || ($maxProgress >= $currentPos && $maxProgress === 1)): ?>
+ <?= $this->restartForm ?>
+ <?php endif ?>
+ </div>
+ <div class="step" style="width: 10%;">
+ <h1><?= $this->translate('Requirements', 'setup.progress'); ?></h1>
+ <?php $stateClass = $finished || $currentPos > 2 ? ' complete' : (
+ $maxProgress > 2 ? ' visited' : (
+ $currentPos === 2 ? ' active' : ''
+ )
+ ); ?>
+ <table><tbody><tr>
+ <td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
+ <td class="middle"><div class="bubble<?= $stateClass; ?>"></div></td>
+ <td class="right"><div class="line right<?= $stateClass; ?>"></div></td>
+ </tr></tbody></table>
+ <?php if (($maxProgress < $currentPos && $currentPos === 2) || ($maxProgress >= $currentPos && $maxProgress === 2)): ?>
+ <?= $this->restartForm ?>
+ <?php endif ?>
+ </div>
+ <div class="step" style="width: 60%;">
+ <h1><?= $this->translate('Configuration', 'setup.progress'); ?></h1>
+ <table><tbody><tr>
+ <td class="left">
+ <?php
+ $firstPage = current($configPagesLeft);
+ $lastPage = end($configPagesLeft);
+ $lineWidth = sprintf('%.2F', round(100 / count($configPagesLeft), 2, PHP_ROUND_HALF_DOWN));
+ ?>
+ <?php foreach ($configPagesLeft as $pos => $page): ?>
+ <?php $stateClass = $finished || $pos < $currentPos ? ' complete' : (
+ $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
+ ); ?>
+ <?php if ($page === $firstPage): ?>
+ <div class="line left<?= $stateClass; ?>" style="float: left; width: <?= sprintf(
+ '%.2F',
+ 100 - (count($configPagesLeft) - 1) * $lineWidth
+ ); ?>%; margin-right: 0"></div>
+ <?php elseif ($page === $lastPage): ?>
+ <div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-right: -0.1em;"></div>
+ <?php else: ?>
+ <div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%;"></div>
+ <?php endif ?>
+ <?php endforeach ?>
+ </td>
+ <td class="middle">
+ <div class="bubble<?= array_key_exists($currentPos, $configPagesLeft) ? (
+ key($configPagesRight) <= $maxProgress ? ' visited' : ' active') : (
+ $finished || $currentPos > 2 ? ' complete' : (
+ key($configPagesRight) < $maxProgress ? ' visited' : ''
+ )
+ ); ?>"></div>
+ </td>
+ <td class="right">
+ <?php
+ $firstPage = current($configPagesRight);
+ $lastPage = end($configPagesRight);
+ $lineWidth = sprintf('%.2F', round(100 / count($configPagesRight), 2, PHP_ROUND_HALF_DOWN));
+ ?>
+ <?php foreach ($configPagesRight as $pos => $page): ?>
+ <?php $stateClass = $finished || $pos < $currentPos ? ' complete' : (
+ $pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
+ ); ?>
+ <?php if ($page === $firstPage): ?>
+ <div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-left: -0.1em;"></div>
+ <?php elseif ($page === $lastPage): ?>
+ <div class="line right<?= $stateClass; ?>" style="float: left; width: <?= sprintf(
+ '%.2F',
+ 100 - (count($configPagesRight) - 1) * $lineWidth
+ ); ?>%; margin-left: 0;"></div>
+ <?php else: ?>
+ <div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%;"></div>
+ <?php endif ?>
+ <?php endforeach ?>
+ </td>
+ </tr></tbody></table>
+ <?php if ($maxProgress > 2 || $currentPos > 2): ?>
+ <?= $this->restartForm ?>
+ <?php endif ?>
+ </div>
+ <div class="step" style="width: 10%;">
+ <h1><?= $this->translate('Finish', 'setup.progress'); ?></h1>
+ <?php $stateClass = $finished ? ' complete' : ($pages[$currentPos] === end($pages) ? ' active' : ''); ?>
+ <table><tbody><tr>
+ <td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
+ <td class="middle"><div class="bubble<?= $stateClass; ?>"></div></td>
+ <td class="right"></td>
+ </tr></tbody></table>
+ </div>
+ </div>
+ </div>
+ <div class="setup-content">
+<?php if ($finished): ?>
+ <?= $this->render('index/parts/finish.phtml'); ?>
+<?php else: ?>
+ <?= $this->render('index/parts/wizard.phtml'); ?>
+<?php endif ?>
+ </div>
+</div>
+<div id="footer">
+ <ul role="alert" id="notifications"><?php
+ $notifications = Notification::getInstance();
+ if ($notifications->hasMessages()) {
+ foreach ($notifications->popMessages() as $m) {
+ echo '<li class="' . $m->type . '">' . $this->escape($m->message) . '</li>';
+ }
+ }
+ ?></ul>
+</div>
diff --git a/modules/setup/application/views/scripts/index/parts/finish.phtml b/modules/setup/application/views/scripts/index/parts/finish.phtml
new file mode 100644
index 0000000..dc5ba1c
--- /dev/null
+++ b/modules/setup/application/views/scripts/index/parts/finish.phtml
@@ -0,0 +1,34 @@
+<div id="setup-finish">
+ <?php if ($success): ?>
+ <h2 class="success"><?= $this->translate('Congratulations! Icinga Web 2 has been successfully set up.'); ?></h2>
+ <?php else: ?>
+ <h2 class="failure"><?= $this->translate('Sorry! Failed to set up Icinga Web 2 successfully.'); ?></h2>
+ <?php endif ?>
+ <div class="buttons pull-right">
+ <?php if ($success): ?>
+ <?= $this->qlink(
+ $this->translate('Login to Icinga Web 2'),
+ 'authentication/login',
+ null,
+ array(
+ 'class' => 'button-link login',
+ 'data-no-icinga-ajax' => true,
+ 'title' => $this->translate('Show the login page of Icinga Web 2')
+ )
+ ); ?>
+ <?php else: ?>
+ <?= $this->qlink(
+ $this->translate('Back'),
+ null,
+ null,
+ array(
+ 'class' => 'button-link',
+ 'title' => $this->translate('Show previous wizard-page')
+ )
+ ); ?>
+ <?php endif ?>
+ </div>
+ <pre class="log-output"><?= join("\n\n", array_map(function($a) {
+ return join("\n", $a);
+ }, $report)); ?></pre>
+</div>
diff --git a/modules/setup/application/views/scripts/index/parts/wizard.phtml b/modules/setup/application/views/scripts/index/parts/wizard.phtml
new file mode 100644
index 0000000..94891f9
--- /dev/null
+++ b/modules/setup/application/views/scripts/index/parts/wizard.phtml
@@ -0,0 +1 @@
+<?= $wizard->getForm()->render(); ?> \ No newline at end of file
diff --git a/modules/setup/library/Setup/Exception/SetupException.php b/modules/setup/library/Setup/Exception/SetupException.php
new file mode 100644
index 0000000..c3ae591
--- /dev/null
+++ b/modules/setup/library/Setup/Exception/SetupException.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Exception;
+
+use Icinga\Exception\IcingaException;
+
+/**
+ * Class SetupException
+ *
+ * Used to indicate that a setup should be aborted.
+ */
+class SetupException extends IcingaException
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function __construct()
+ {
+ parent::__construct('Setup abortion');
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement.php b/modules/setup/library/Setup/Requirement.php
new file mode 100644
index 0000000..fd16405
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement.php
@@ -0,0 +1,343 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use LogicException;
+
+abstract class Requirement
+{
+ /**
+ * The state of this requirement
+ *
+ * @var bool
+ */
+ protected $state;
+
+ /**
+ * A descriptive text representing the current state of this requirement
+ *
+ * @var string
+ */
+ protected $stateText;
+
+ /**
+ * The descriptions of this requirement
+ *
+ * @var array
+ */
+ protected $descriptions;
+
+ /**
+ * The title of this requirement
+ *
+ * @var string
+ */
+ protected $title;
+
+ /**
+ * The condition of this requirement
+ *
+ * @var mixed
+ */
+ protected $condition;
+
+ /**
+ * Whether this requirement is optional
+ *
+ * @var bool
+ */
+ protected $optional;
+
+ /**
+ * The alias to display the condition with in a human readable way
+ *
+ * @var string
+ */
+ protected $alias;
+
+ /**
+ * The text to display if the given requirement is fulfilled
+ *
+ * @var string
+ */
+ protected $textAvailable;
+
+ /**
+ * The text to display if the given requirement is not fulfilled
+ *
+ * @var string
+ */
+ protected $textMissing;
+
+ /**
+ * Create a new requirement
+ *
+ * @param array $options
+ *
+ * @throws LogicException In case there exists no setter for an option's key
+ */
+ public function __construct(array $options = array())
+ {
+ $this->optional = false;
+ $this->descriptions = array();
+
+ foreach ($options as $key => $value) {
+ $setMethod = 'set' . ucfirst($key);
+ $addMethod = 'add' . ucfirst($key);
+ if (method_exists($this, $setMethod)) {
+ $this->$setMethod($value);
+ } elseif (method_exists($this, $addMethod)) {
+ $this->$addMethod($value);
+ } else {
+ throw LogicException('No setter found for option key: ' . $key);
+ }
+ }
+ }
+
+ /**
+ * Set the state of this requirement
+ *
+ * @param bool $state
+ *
+ * @return Requirement
+ */
+ public function setState($state)
+ {
+ $this->state = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return the state of this requirement
+ *
+ * Evaluates the requirement in case there is no state set yet.
+ *
+ * @return int
+ */
+ public function getState()
+ {
+ if ($this->state === null) {
+ $this->state = $this->evaluate();
+ }
+
+ return $this->state;
+ }
+
+ /**
+ * Set a descriptive text for this requirement's current state
+ *
+ * @param string $text
+ *
+ * @return Requirement
+ */
+ public function setStateText($text)
+ {
+ $this->stateText = $text;
+ return $this;
+ }
+
+ /**
+ * Return a descriptive text for this requirement's current state
+ *
+ * @return string
+ */
+ public function getStateText()
+ {
+ $state = $this->getState();
+ if ($this->stateText === null) {
+ return $state ? $this->getTextAvailable() : $this->getTextMissing();
+ }
+ return $this->stateText;
+ }
+
+ /**
+ * Add a description for this requirement
+ *
+ * @param string $description
+ *
+ * @return Requirement
+ */
+ public function addDescription($description)
+ {
+ $this->descriptions[] = $description;
+ return $this;
+ }
+
+ /**
+ * Return the descriptions of this wizard
+ *
+ * @return array
+ */
+ public function getDescriptions()
+ {
+ return $this->descriptions;
+ }
+
+ /**
+ * Set the title for this requirement
+ *
+ * @param string $title
+ *
+ * @return Requirement
+ */
+ public function setTitle($title)
+ {
+ $this->title = $title;
+ return $this;
+ }
+
+ /**
+ * Return the title of this requirement
+ *
+ * In case there is no title set the alias is returned instead.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ if ($this->title === null) {
+ return $this->getAlias();
+ }
+
+ return $this->title;
+ }
+
+ /**
+ * Set the condition for this requirement
+ *
+ * @param mixed $condition
+ *
+ * @return Requirement
+ */
+ public function setCondition($condition)
+ {
+ $this->condition = $condition;
+ return $this;
+ }
+
+ /**
+ * Return the condition of this requirement
+ *
+ * @return mixed
+ */
+ public function getCondition()
+ {
+ return $this->condition;
+ }
+
+ /**
+ * Set whether this requirement is optional
+ *
+ * @param bool $state
+ *
+ * @return Requirement
+ */
+ public function setOptional($state = true)
+ {
+ $this->optional = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this requirement is optional
+ *
+ * @return bool
+ */
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ /**
+ * Set the alias to display the condition with in a human readable way
+ *
+ * @param string $alias
+ *
+ * @return Requirement
+ */
+ public function setAlias($alias)
+ {
+ $this->alias = $alias;
+ return $this;
+ }
+
+ /**
+ * Return the alias to display the condition with in a human readable way
+ *
+ * @return string
+ */
+ public function getAlias()
+ {
+ return $this->alias;
+ }
+
+ /**
+ * Set the text to display if the given requirement is fulfilled
+ *
+ * @param string $textAvailable
+ *
+ * @return Requirement
+ */
+ public function setTextAvailable($textAvailable)
+ {
+ $this->textAvailable = $textAvailable;
+ return $this;
+ }
+
+ /**
+ * Get the text to display if the given requirement is fulfilled
+ *
+ * @return string
+ */
+ public function getTextAvailable()
+ {
+ return $this->textAvailable;
+ }
+
+ /**
+ * Set the text to display if the given requirement is not fulfilled
+ *
+ * @param string $textMissing
+ *
+ * @return Requirement
+ */
+ public function setTextMissing($textMissing)
+ {
+ $this->textMissing = $textMissing;
+ return $this;
+ }
+
+ /**
+ * Get the text to display if the given requirement is not fulfilled
+ *
+ * @return string
+ */
+ public function getTextMissing()
+ {
+ return $this->textMissing;
+ }
+
+ /**
+ * Evaluate this requirement and return whether it is fulfilled
+ *
+ * @return bool
+ */
+ abstract protected function evaluate();
+
+ /**
+ * Return whether the given requirement equals this one
+ *
+ * @param Requirement $requirement
+ *
+ * @return bool
+ */
+ public function equals(Requirement $requirement)
+ {
+ if ($requirement instanceof static) {
+ return $this->getCondition() === $requirement->getCondition();
+ }
+
+ return false;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/ClassRequirement.php b/modules/setup/library/Setup/Requirement/ClassRequirement.php
new file mode 100644
index 0000000..d884c31
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/ClassRequirement.php
@@ -0,0 +1,48 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class ClassRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ return Platform::classExists($this->getCondition());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStateText()
+ {
+ $stateText = parent::getStateText();
+ if ($stateText === null) {
+ $alias = $this->getAlias();
+ if ($this->getState()) {
+ $stateText = $alias === null
+ ? sprintf(
+ mt('setup', 'The %s class is available.', 'setup.requirement.class'),
+ $this->getCondition()
+ )
+ : sprintf(
+ mt('setup', 'The %s is available.', 'setup.requirement.class'),
+ $alias
+ );
+ } else {
+ $stateText = $alias === null
+ ? sprintf(
+ mt('setup', 'The %s class is missing.', 'setup.requirement.class'),
+ $this->getCondition()
+ )
+ : sprintf(
+ mt('setup', 'The %s is missing.', 'setup.requirement.class'),
+ $alias
+ );
+ }
+ }
+ return $stateText;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php
new file mode 100644
index 0000000..7e9044c
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/ConfigDirectoryRequirement.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Module\Setup\Requirement;
+
+class ConfigDirectoryRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === null) {
+ return mt('setup', 'Read- and writable configuration directory');
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $path = $this->getCondition();
+ if (file_exists($path)) {
+ $readable = is_readable($path);
+ if ($readable && is_writable($path)) {
+ $this->setStateText(sprintf(mt('setup', 'The directory %s is read- and writable.'), $path));
+ return true;
+ } else {
+ $this->setStateText(sprintf(
+ $readable
+ ? mt('setup', 'The directory %s is not writable.')
+ : mt('setup', 'The directory %s is not readable.'),
+ $path
+ ));
+ return false;
+ }
+ } else {
+ $this->setStateText(sprintf(mt('setup', 'The directory %s does not exist.'), $path));
+ return false;
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/OSRequirement.php b/modules/setup/library/Setup/Requirement/OSRequirement.php
new file mode 100644
index 0000000..760c97a
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/OSRequirement.php
@@ -0,0 +1,27 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class OSRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === null) {
+ return sprintf(mt('setup', '%s Platform'), ucfirst($this->getCondition()));
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $phpOS = Platform::getOperatingSystemName();
+ $this->setStateText(sprintf(mt('setup', 'You are running PHP on a %s system.'), ucfirst($phpOS)));
+ return strtolower($phpOS) === strtolower($this->getCondition());
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php
new file mode 100644
index 0000000..6c77af5
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/PhpConfigRequirement.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class PhpConfigRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ list($configDirective, $value) = $this->getCondition();
+ $configValue = Platform::getPhpConfig($configDirective);
+ $this->setStateText(
+ $configValue
+ ? sprintf(mt('setup', 'The PHP config `%s\' is set to "%s".'), $configDirective, $configValue)
+ : sprintf(mt('setup', 'The PHP config `%s\' is not defined.'), $configDirective)
+ );
+ return is_bool($value) ? $configValue == $value : $configValue === $value;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php
new file mode 100644
index 0000000..f8ab129
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/PhpModuleRequirement.php
@@ -0,0 +1,42 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class PhpModuleRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === $this->getAlias()) {
+ if ($title === null) {
+ $title = $this->getCondition();
+ }
+
+ return sprintf(mt('setup', 'PHP Module: %s'), $title);
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $moduleName = $this->getCondition();
+ if (Platform::extensionLoaded($moduleName)) {
+ $this->setStateText(sprintf(
+ mt('setup', 'The PHP module %s is available.'),
+ $this->getAlias() ?: $moduleName
+ ));
+ return true;
+ } else {
+ $this->setStateText(sprintf(
+ mt('setup', 'The PHP module %s is missing.'),
+ $this->getAlias() ?: $moduleName
+ ));
+ return false;
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php
new file mode 100644
index 0000000..b811ca8
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/PhpVersionRequirement.php
@@ -0,0 +1,28 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement;
+
+class PhpVersionRequirement extends Requirement
+{
+ public function getTitle()
+ {
+ $title = parent::getTitle();
+ if ($title === null) {
+ return mt('setup', 'PHP Version');
+ }
+
+ return $title;
+ }
+
+ protected function evaluate()
+ {
+ $phpVersion = Platform::getPhpVersion();
+ $this->setStateText(sprintf(mt('setup', 'You are running PHP version %s.'), $phpVersion));
+ list($operator, $requiredVersion) = $this->getCondition();
+ return version_compare($phpVersion, $requiredVersion, $operator);
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/SetRequirement.php b/modules/setup/library/Setup/Requirement/SetRequirement.php
new file mode 100644
index 0000000..77cbaf0
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/SetRequirement.php
@@ -0,0 +1,34 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Module\Setup\Requirement;
+
+/**
+ * Add requirement field
+ *
+ * @package Icinga\Module\Setup\Requirement
+ */
+class SetRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ $condition = $this->getCondition();
+
+ if ($condition->getState()) {
+ $this->setStateText(sprintf(
+ mt('setup', '%s is available.'),
+ $this->getAlias() ?: $this->getTitle()
+ ));
+ return true;
+ }
+
+ $this->setStateText(sprintf(
+ mt('setup', '%s is missing.'),
+ $this->getAlias() ?: $this->getTitle()
+ ));
+
+ return false;
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/WebLibraryRequirement.php b/modules/setup/library/Setup/Requirement/WebLibraryRequirement.php
new file mode 100644
index 0000000..bab587a
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/WebLibraryRequirement.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Setup\Requirement;
+
+class WebLibraryRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ list($name, $op, $version) = $this->getCondition();
+
+ $libs = Icinga::app()->getLibraries();
+ if (! $libs->has($name)) {
+ $this->setStateText(sprintf(mt('setup', '%s is not installed'), $this->getAlias()));
+ return false;
+ }
+
+ $this->setStateText(sprintf(mt('setup', '%s version: %s'), $this->getAlias(), $libs->get($name)->getVersion()));
+ return $libs->has($name, $op . $version);
+ }
+}
diff --git a/modules/setup/library/Setup/Requirement/WebModuleRequirement.php b/modules/setup/library/Setup/Requirement/WebModuleRequirement.php
new file mode 100644
index 0000000..ad600e1
--- /dev/null
+++ b/modules/setup/library/Setup/Requirement/WebModuleRequirement.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+namespace Icinga\Module\Setup\Requirement;
+
+use Icinga\Application\Icinga;
+use Icinga\Module\Setup\Requirement;
+
+class WebModuleRequirement extends Requirement
+{
+ protected function evaluate()
+ {
+ list($name, $op, $version) = $this->getCondition();
+
+ $mm = Icinga::app()->getModuleManager();
+ if (! $mm->hasInstalled($name)) {
+ $this->setStateText(sprintf(mt('setup', '%s is not installed'), $this->getAlias()));
+ return false;
+ }
+
+ $module = $mm->getModule($name, false);
+
+ $moduleVersion = $module->getVersion();
+ if ($moduleVersion[0] === 'v') {
+ $moduleVersion = substr($moduleVersion, 1);
+ }
+
+ $this->setStateText(sprintf(mt('setup', '%s version: %s'), $this->getAlias(), $moduleVersion));
+ return version_compare($moduleVersion, $version, $op);
+ }
+}
diff --git a/modules/setup/library/Setup/RequirementSet.php b/modules/setup/library/Setup/RequirementSet.php
new file mode 100644
index 0000000..672fad4
--- /dev/null
+++ b/modules/setup/library/Setup/RequirementSet.php
@@ -0,0 +1,335 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use LogicException;
+use RecursiveIterator;
+use Traversable;
+
+/**
+ * Container to store and handle requirements
+ */
+class RequirementSet implements RecursiveIterator
+{
+ /**
+ * Mode AND (all requirements must be met)
+ */
+ const MODE_AND = 0;
+
+ /**
+ * Mode OR (at least one requirement must be met)
+ */
+ const MODE_OR = 1;
+
+ /**
+ * Whether all requirements meet their condition
+ *
+ * @var bool
+ */
+ protected $state;
+
+ /**
+ * Whether this set is optional
+ *
+ * @var bool
+ */
+ protected $optional;
+
+ /**
+ * The mode by which the requirements are evaluated
+ *
+ * @var string
+ */
+ protected $mode;
+
+ /**
+ * The registered requirements
+ *
+ * @var array
+ */
+ protected $requirements;
+
+ /**
+ * The raw state of this set's requirements
+ *
+ * @var bool
+ */
+ private $forcedState;
+
+ /**
+ * Initialize a new set of requirements
+ *
+ * @param bool $optional Whether this set is optional
+ * @param int $mode The mode by which to evaluate this set
+ */
+ public function __construct($optional = false, $mode = null)
+ {
+ $this->optional = $optional;
+ $this->requirements = array();
+ $this->setMode($mode ?: static::MODE_AND);
+ }
+
+ /**
+ * Set the state of this set
+ *
+ * @param bool $state
+ *
+ * @return RequirementSet
+ */
+ public function setState($state)
+ {
+ $this->state = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return the state of this set
+ *
+ * Alias for RequirementSet::fulfilled(true).
+ *
+ * @return bool
+ */
+ public function getState()
+ {
+ return $this->fulfilled(true);
+ }
+
+ /**
+ * Set whether this set of requirements should be optional
+ *
+ * @param bool $state
+ *
+ * @return RequirementSet
+ */
+ public function setOptional($state = true)
+ {
+ $this->optional = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether this set of requirements is optional
+ *
+ * @return bool
+ */
+ public function isOptional()
+ {
+ return $this->optional;
+ }
+
+ /**
+ * Set the mode by which to evaluate the requirements
+ *
+ * @param int $mode
+ *
+ * @return RequirementSet
+ *
+ * @throws LogicException In case the given mode is invalid
+ */
+ public function setMode($mode)
+ {
+ if ($mode !== static::MODE_AND && $mode !== static::MODE_OR) {
+ throw new LogicException(sprintf('Invalid mode %u given.'), $mode);
+ }
+
+ $this->mode = $mode;
+ return $this;
+ }
+
+ /**
+ * Return the mode by which the requirements are evaluated
+ *
+ * @return int
+ */
+ public function getMode()
+ {
+ return $this->mode;
+ }
+
+ /**
+ * Register a requirement
+ *
+ * @param Requirement $requirement The requirement to add
+ *
+ * @return RequirementSet
+ */
+ public function add(Requirement $requirement)
+ {
+ $merged = false;
+ foreach ($this->requirements as $knownRequirement) {
+ if ($knownRequirement instanceof Requirement && $requirement->equals($knownRequirement)) {
+ $knownRequirement->setOptional($requirement->isOptional());
+ foreach ($requirement->getDescriptions() as $description) {
+ $knownRequirement->addDescription($description);
+ }
+
+ $merged = true;
+ break;
+ }
+ }
+
+ if (! $merged) {
+ $this->requirements[] = $requirement;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return all registered requirements
+ *
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->requirements;
+ }
+
+ /**
+ * Register the given set of requirements
+ *
+ * @param RequirementSet $set The set to register
+ *
+ * @return RequirementSet
+ */
+ public function merge(RequirementSet $set)
+ {
+ if ($this->getMode() === $set->getMode() && $this->isOptional() === $set->isOptional()) {
+ foreach ($set->getAll() as $requirement) {
+ if ($requirement instanceof static) {
+ $this->merge($requirement);
+ } else {
+ $this->add($requirement);
+ }
+ }
+ } else {
+ $this->requirements[] = $set;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return whether all requirements can successfully be evaluated based on the current mode
+ *
+ * In case this is a optional set of requirements (and $force is false), true is returned immediately.
+ *
+ * @param bool $force Whether to ignore the optionality of a set or single requirement
+ *
+ * @return bool
+ */
+ public function fulfilled($force = false)
+ {
+ $state = $this->isOptional();
+ if (! $force && $state) {
+ return true;
+ }
+
+ if (! $force && $this->state !== null) {
+ return $this->state;
+ } elseif ($force && $this->forcedState !== null) {
+ return $this->forcedState;
+ }
+
+ $self = $this->requirements;
+ foreach ($self as $requirement) {
+ if ($requirement->getState()) {
+ $state = true;
+ if ($this->getMode() === static::MODE_OR) {
+ break;
+ }
+ } elseif ($force || !$requirement->isOptional()) {
+ $state = false;
+ if ($this->getMode() === static::MODE_AND) {
+ break;
+ }
+ }
+ }
+
+ if ($force) {
+ return $this->forcedState = $state;
+ }
+
+ return $this->state = $state;
+ }
+
+ /**
+ * Return whether the current element represents a nested set of requirements
+ *
+ * @return bool
+ */
+ public function hasChildren(): bool
+ {
+ $current = $this->current();
+ return $current instanceof static;
+ }
+
+ /**
+ * Return a iterator for the current nested set of requirements
+ *
+ * @return ?RecursiveIterator
+ */
+ public function getChildren(): ?RecursiveIterator
+ {
+ return $this->current();
+ }
+
+ /**
+ * Rewind the iterator to its first element
+ */
+ public function rewind(): void
+ {
+ reset($this->requirements);
+ }
+
+ /**
+ * Return whether the current iterator position is valid
+ *
+ * @return bool
+ */
+ public function valid(): bool
+ {
+ return key($this->requirements) !== null;
+ }
+
+ /**
+ * Return the current element in the iteration
+ *
+ * @return Requirement|RequirementSet
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return current($this->requirements);
+ }
+
+ /**
+ * Return the position of the current element in the iteration
+ *
+ * @return int
+ */
+ public function key(): int
+ {
+ return key($this->requirements);
+ }
+
+ /**
+ * Advance the iterator to the next element
+ */
+ public function next(): void
+ {
+ next($this->requirements);
+ }
+
+ /**
+ * Return this set of requirements rendered as HTML
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $renderer = new RequirementsRenderer($this);
+ return (string) $renderer;
+ }
+}
diff --git a/modules/setup/library/Setup/RequirementsRenderer.php b/modules/setup/library/Setup/RequirementsRenderer.php
new file mode 100644
index 0000000..cc9392a
--- /dev/null
+++ b/modules/setup/library/Setup/RequirementsRenderer.php
@@ -0,0 +1,64 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use RecursiveIteratorIterator;
+
+class RequirementsRenderer extends RecursiveIteratorIterator
+{
+ public function beginIteration(): void
+ {
+ $this->tags[] = '<ul class="requirements">';
+ }
+
+ public function endIteration(): void
+ {
+ $this->tags[] = '</ul>';
+ }
+
+ public function beginChildren(): void
+ {
+ $this->tags[] = '<li>';
+ $currentSet = $this->getSubIterator();
+ $state = $currentSet->getState() ? 'fulfilled' : ($currentSet->isOptional() ? 'not-available' : 'missing');
+ $this->tags[] = '<ul class="set-state ' . $state . '">';
+ }
+
+ public function endChildren(): void
+ {
+ $this->tags[] = '</ul>';
+ $this->tags[] = '</li>';
+ }
+
+ public function render()
+ {
+ foreach ($this as $requirement) {
+ $this->tags[] = '<li class="clearfix">';
+ $this->tags[] = '<div class="title"><h2>' . $requirement->getTitle() . '</h2></div>';
+ $this->tags[] = '<div class="description">';
+ $descriptions = $requirement->getDescriptions();
+ if (count($descriptions) > 1) {
+ $this->tags[] = '<ul>';
+ foreach ($descriptions as $d) {
+ $this->tags[] = '<li>' . $d . '</li>';
+ }
+ $this->tags[] = '</ul>';
+ } elseif (! empty($descriptions)) {
+ $this->tags[] = $descriptions[0];
+ }
+ $this->tags[] = '</div>';
+ $this->tags[] = '<div class="state ' . ($requirement->getState() ? 'fulfilled' : (
+ $requirement->isOptional() ? 'not-available' : 'missing'
+ )) . '">' . $requirement->getStateText() . '</div>';
+ $this->tags[] = '</li>';
+ }
+
+ return implode("\n", $this->tags);
+ }
+
+ public function __toString()
+ {
+ return $this->render();
+ }
+}
diff --git a/modules/setup/library/Setup/Setup.php b/modules/setup/library/Setup/Setup.php
new file mode 100644
index 0000000..7b0baed
--- /dev/null
+++ b/modules/setup/library/Setup/Setup.php
@@ -0,0 +1,99 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use ArrayIterator;
+use IteratorAggregate;
+use Icinga\Module\Setup\Exception\SetupException;
+use Traversable;
+
+/**
+ * Container for multiple configuration steps
+ */
+class Setup implements IteratorAggregate
+{
+ protected $steps;
+
+ protected $state;
+
+ public function __construct()
+ {
+ $this->steps = array();
+ }
+
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->getSteps());
+ }
+
+ public function addStep(Step $step)
+ {
+ $this->steps[] = $step;
+ }
+
+ public function addSteps(array $steps)
+ {
+ foreach ($steps as $step) {
+ $this->addStep($step);
+ }
+ }
+
+ public function getSteps()
+ {
+ return $this->steps;
+ }
+
+ /**
+ * Run the configuration and return whether it succeeded
+ *
+ * @return bool
+ */
+ public function run()
+ {
+ $this->state = true;
+
+ try {
+ foreach ($this->steps as $step) {
+ $this->state &= $step->apply();
+ }
+ } catch (SetupException $_) {
+ $this->state = false;
+ }
+
+ return $this->state;
+ }
+
+ /**
+ * Return a summary of all actions designated to run
+ *
+ * @return array An array of HTML strings
+ */
+ public function getSummary()
+ {
+ $summaries = array();
+ foreach ($this->steps as $step) {
+ $summaries[] = $step->getSummary();
+ }
+
+ return $summaries;
+ }
+
+ /**
+ * Return a report of all actions that were run
+ *
+ * @return array An array of arrays of strings
+ */
+ public function getReport()
+ {
+ $reports = array();
+ foreach ($this->steps as $step) {
+ $report = $step->getReport();
+ if (! empty($report)) {
+ $reports[] = $report;
+ }
+ }
+
+ return $reports;
+ }
+}
diff --git a/modules/setup/library/Setup/SetupWizard.php b/modules/setup/library/Setup/SetupWizard.php
new file mode 100644
index 0000000..c7ad0c3
--- /dev/null
+++ b/modules/setup/library/Setup/SetupWizard.php
@@ -0,0 +1,24 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+/**
+ * Interface for wizards providing a setup and requirements
+ */
+interface SetupWizard
+{
+ /**
+ * Return the setup for this wizard
+ *
+ * @return Setup
+ */
+ public function getSetup();
+
+ /**
+ * Return the requirements of this wizard
+ *
+ * @return RequirementSet
+ */
+ public function getRequirements();
+}
diff --git a/modules/setup/library/Setup/Step.php b/modules/setup/library/Setup/Step.php
new file mode 100644
index 0000000..1d0797d
--- /dev/null
+++ b/modules/setup/library/Setup/Step.php
@@ -0,0 +1,31 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+/**
+ * Class to implement functionality for a single setup step
+ */
+abstract class Step
+{
+ /**
+ * Apply this step's configuration changes
+ *
+ * @return bool
+ */
+ abstract public function apply();
+
+ /**
+ * Return a HTML representation of this step's configuration changes supposed to be made
+ *
+ * @return string
+ */
+ abstract public function getSummary();
+
+ /**
+ * Return a textual summary of all configuration changes made
+ *
+ * @return array
+ */
+ abstract public function getReport();
+}
diff --git a/modules/setup/library/Setup/Steps/AuthenticationStep.php b/modules/setup/library/Setup/Steps/AuthenticationStep.php
new file mode 100644
index 0000000..3c6c64a
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/AuthenticationStep.php
@@ -0,0 +1,238 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\IcingaException;
+use Icinga\Authentication\User\DbUserBackend;
+use Icinga\Module\Setup\Step;
+
+class AuthenticationStep extends Step
+{
+ protected $data;
+
+ protected $dbError;
+
+ protected $authIniError;
+
+ protected $permIniError;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $success = $this->createAuthenticationIni();
+ if (isset($this->data['adminAccountData']['resourceConfig'])) {
+ $success &= $this->createAccount();
+ }
+
+ $success &= $this->createRolesIni();
+ return $success;
+ }
+
+ protected function createAuthenticationIni()
+ {
+ $config = array();
+ $backendConfig = $this->data['backendConfig'];
+ $backendName = $backendConfig['name'];
+ unset($backendConfig['name']);
+ $config[$backendName] = $backendConfig;
+ if (isset($this->data['resourceName'])) {
+ $config[$backendName]['resource'] = $this->data['resourceName'];
+ }
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('authentication.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->authIniError = $e;
+ return false;
+ }
+
+ $this->authIniError = false;
+ return true;
+ }
+
+ protected function createRolesIni()
+ {
+ if (isset($this->data['adminAccountData']['username'])) {
+ $config = array(
+ 'users' => $this->data['adminAccountData']['username'],
+ 'permissions' => '*'
+ );
+
+ if ($this->data['backendConfig']['backend'] === 'db') {
+ $config['groups'] = mt('setup', 'Administrators', 'setup.role.name');
+ }
+ } else { // isset($this->data['adminAccountData']['groupname'])
+ $config = array(
+ 'groups' => $this->data['adminAccountData']['groupname'],
+ 'permissions' => '*'
+ );
+ }
+
+ try {
+ Config::fromArray(array(mt('setup', 'Administrators', 'setup.role.name') => $config))
+ ->setConfigFile(Config::resolvePath('roles.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->permIniError = $e;
+ return false;
+ }
+
+ $this->permIniError = false;
+ return true;
+ }
+
+ protected function createAccount()
+ {
+ try {
+ $backend = new DbUserBackend(
+ ResourceFactory::createResource(new ConfigObject($this->data['adminAccountData']['resourceConfig']))
+ );
+
+ if ($backend->select()->where('user_name', $this->data['adminAccountData']['username'])->count() === 0) {
+ $backend->insert('user', array(
+ 'user_name' => $this->data['adminAccountData']['username'],
+ 'password' => $this->data['adminAccountData']['password'],
+ 'is_active' => true
+ ));
+ $this->dbError = false;
+ }
+ } catch (Exception $e) {
+ $this->dbError = $e;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getSummary()
+ {
+ $pageTitle = '<h2>' . mt('setup', 'Authentication', 'setup.page.title') . '</h2>';
+ $backendTitle = '<h3>' . mt('setup', 'Authentication Backend', 'setup.page.title') . '</h3>';
+ $adminTitle = '<h3>' . mt('setup', 'Administration', 'setup.page.title') . '</h3>';
+
+ $authType = $this->data['backendConfig']['backend'];
+ $backendDesc = '<p>' . sprintf(
+ mt('setup', 'Users will authenticate using %s.', 'setup.summary.auth'),
+ $authType === 'db' ? mt('setup', 'a database', 'setup.summary.auth.type') : (
+ $authType === 'ldap' || $authType === 'msldap' ? 'LDAP' : (
+ mt('setup', 'webserver authentication', 'setup.summary.auth.type')
+ )
+ )
+ ) . '</p>';
+
+ $backendHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Backend Name') . '</strong></td>'
+ . '<td>' . $this->data['backendConfig']['name'] . '</td>'
+ . '</tr>'
+ . ($authType === 'ldap' || $authType === 'msldap' ? (
+ '<tr>'
+ . '<td><strong>' . mt('setup', 'User Object Class') . '</strong></td>'
+ . '<td>' . ($authType === 'msldap' ? 'user' : $this->data['backendConfig']['user_class']) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Custom Filter') . '</strong></td>'
+ . '<td>' . (trim($this->data['backendConfig']['filter']) ?: t('None', 'auth.ldap.filter')) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'User Name Attribute') . '</strong></td>'
+ . '<td>' . ($authType === 'msldap'
+ ? 'sAMAccountName'
+ : $this->data['backendConfig']['user_name_attribute']) . '</td>'
+ . '</tr>'
+ ) : ($authType === 'external' ? (
+ '<tr>'
+ . '<td><strong>' . t('Filter Pattern') . '</strong></td>'
+ . '<td>' . $this->data['backendConfig']['strip_username_regexp'] . '</td>'
+ . '</tr>'
+ ) : ''))
+ . '</tbody>'
+ . '</table>';
+
+ if (isset($this->data['adminAccountData']['username'])) {
+ $adminHtml = '<p>' . (isset($this->data['adminAccountData']['resourceConfig']) ? sprintf(
+ mt('setup', 'Administrative rights will initially be granted to a new account called "%s".'),
+ $this->data['adminAccountData']['username']
+ ) : sprintf(
+ mt('setup', 'Administrative rights will initially be granted to an existing account called "%s".'),
+ $this->data['adminAccountData']['username']
+ )) . '</p>';
+ } else { // isset($this->data['adminAccountData']['groupname'])
+ $adminHtml = '<p>' . sprintf(
+ mt('setup', 'Administrative rights will initially be granted to members of the user group "%s".'),
+ $this->data['adminAccountData']['groupname']
+ ) . '</p>';
+ }
+
+ return $pageTitle . '<div class="topic">' . $backendDesc . $backendTitle . $backendHtml . '</div>'
+ . '<div class="topic">' . $adminTitle . $adminHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ $report = array();
+
+ if ($this->authIniError === false) {
+ $report[] = sprintf(
+ mt('setup', 'Authentication configuration has been successfully written to: %s'),
+ Config::resolvePath('authentication.ini')
+ );
+ } elseif ($this->authIniError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Authentication configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('authentication.ini')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->authIniError));
+ }
+
+ if ($this->dbError === false) {
+ $report[] = sprintf(
+ mt('setup', 'Account "%s" has been successfully created.'),
+ $this->data['adminAccountData']['username']
+ );
+ } elseif ($this->dbError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Unable to create account "%s". An error occured:'),
+ $this->data['adminAccountData']['username']
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->dbError));
+ }
+
+ if ($this->permIniError === false) {
+ $report[] = isset($this->data['adminAccountData']['username']) ? sprintf(
+ mt('setup', 'Account "%s" has been successfully defined as initial administrator.'),
+ $this->data['adminAccountData']['username']
+ ) : sprintf(
+ mt('setup', 'The members of the user group "%s" were successfully defined as initial administrators.'),
+ $this->data['adminAccountData']['groupname']
+ );
+ } elseif ($this->permIniError !== null) {
+ $report[] = isset($this->data['adminAccountData']['username']) ? sprintf(
+ mt('setup', 'Unable to define account "%s" as initial administrator. An error occured:'),
+ $this->data['adminAccountData']['username']
+ ) : sprintf(
+ mt(
+ 'setup',
+ 'Unable to define the members of the user group "%s" as initial administrators. An error occured:'
+ ),
+ $this->data['adminAccountData']['groupname']
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->permIniError));
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/DatabaseStep.php b/modules/setup/library/Setup/Steps/DatabaseStep.php
new file mode 100644
index 0000000..32b2d15
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/DatabaseStep.php
@@ -0,0 +1,266 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use PDOException;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+use Icinga\Module\Setup\Utils\DbTool;
+use Icinga\Module\Setup\Exception\SetupException;
+
+class DatabaseStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ protected $messages;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ $this->messages = array();
+ }
+
+ public function apply()
+ {
+ $resourceConfig = $this->data['resourceConfig'];
+ if (isset($this->data['adminName'])) {
+ $resourceConfig['username'] = $this->data['adminName'];
+ if (isset($this->data['adminPassword'])) {
+ $resourceConfig['password'] = $this->data['adminPassword'];
+ }
+ }
+
+ $db = new DbTool($resourceConfig);
+
+ try {
+ if ($resourceConfig['db'] === 'mysql') {
+ $this->setupMysqlDatabase($db);
+ } elseif ($resourceConfig['db'] === 'pgsql') {
+ $this->setupPgsqlDatabase($db);
+ }
+ } catch (Exception $e) {
+ $this->error = $e;
+ throw new SetupException();
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ protected function setupMysqlDatabase(DbTool $db)
+ {
+ try {
+ $db->connectToDb();
+ $this->log(
+ mt('setup', 'Successfully connected to existing database "%s"...'),
+ $this->data['resourceConfig']['dbname']
+ );
+ } catch (PDOException $_) {
+ $db->connectToHost();
+ $this->log(mt('setup', 'Creating new database "%s"...'), $this->data['resourceConfig']['dbname']);
+ $db->exec('CREATE DATABASE ' . $db->quoteIdentifier($this->data['resourceConfig']['dbname']));
+ $db->reconnect($this->data['resourceConfig']['dbname']);
+ }
+
+ if (array_search(reset($this->data['tables']), $db->listTables(), true) !== false) {
+ $this->log(mt('setup', 'Database schema already exists...'));
+ } else {
+ $this->log(mt('setup', 'Creating database schema...'));
+ $db->import($this->data['schemaPath'] . '/mysql.schema.sql');
+ }
+
+ if ($db->hasLogin($this->data['resourceConfig']['username'])) {
+ $this->log(mt('setup', 'Login "%s" already exists...'), $this->data['resourceConfig']['username']);
+ } else {
+ $this->log(mt('setup', 'Creating login "%s"...'), $this->data['resourceConfig']['username']);
+ $db->addLogin($this->data['resourceConfig']['username'], $this->data['resourceConfig']['password']);
+ }
+
+ $username = $this->data['resourceConfig']['username'];
+ if ($db->checkPrivileges($this->data['privileges'], $this->data['tables'], $username)) {
+ $this->log(
+ mt('setup', 'Required privileges were already granted to login "%s".'),
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $this->log(
+ mt('setup', 'Granting required privileges to login "%s"...'),
+ $this->data['resourceConfig']['username']
+ );
+ $db->grantPrivileges(
+ $this->data['privileges'],
+ $this->data['tables'],
+ $this->data['resourceConfig']['username']
+ );
+ }
+ }
+
+ protected function setupPgsqlDatabase(DbTool $db)
+ {
+ try {
+ $db->connectToDb();
+ $this->log(
+ mt('setup', 'Successfully connected to existing database "%s"...'),
+ $this->data['resourceConfig']['dbname']
+ );
+ } catch (PDOException $_) {
+ $db->connectToHost();
+ $this->log(mt('setup', 'Creating new database "%s"...'), $this->data['resourceConfig']['dbname']);
+ $db->exec(sprintf(
+ "CREATE DATABASE %s WITH ENCODING 'UTF-8'",
+ $db->quoteIdentifier($this->data['resourceConfig']['dbname'])
+ ));
+ $db->reconnect($this->data['resourceConfig']['dbname']);
+ }
+
+ if (array_search(reset($this->data['tables']), $db->listTables(), true) !== false) {
+ $this->log(mt('setup', 'Database schema already exists...'));
+ } else {
+ $this->log(mt('setup', 'Creating database schema...'));
+ $db->import($this->data['schemaPath'] . '/pgsql.schema.sql');
+ }
+
+ if ($db->hasLogin($this->data['resourceConfig']['username'])) {
+ $this->log(mt('setup', 'Login "%s" already exists...'), $this->data['resourceConfig']['username']);
+ } else {
+ $this->log(mt('setup', 'Creating login "%s"...'), $this->data['resourceConfig']['username']);
+ $db->addLogin($this->data['resourceConfig']['username'], $this->data['resourceConfig']['password']);
+ }
+
+ $username = $this->data['resourceConfig']['username'];
+ if ($db->checkPrivileges($this->data['privileges'], $this->data['tables'], $username)) {
+ $this->log(
+ mt('setup', 'Required privileges were already granted to login "%s".'),
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $this->log(
+ mt('setup', 'Granting required privileges to login "%s"...'),
+ $this->data['resourceConfig']['username']
+ );
+ $db->grantPrivileges(
+ $this->data['privileges'],
+ $this->data['tables'],
+ $this->data['resourceConfig']['username']
+ );
+ }
+ }
+
+ public function getSummary()
+ {
+ $resourceConfig = $this->data['resourceConfig'];
+ if (isset($this->data['adminName'])) {
+ $resourceConfig['username'] = $this->data['adminName'];
+ if (isset($this->data['adminPassword'])) {
+ $resourceConfig['password'] = $this->data['adminPassword'];
+ }
+ }
+
+ $db = new DbTool($resourceConfig);
+
+ try {
+ $db->connectToDb();
+ if (array_search(reset($this->data['tables']), $db->listTables(), true) === false) {
+ if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to setup the missing schema required by Icinga'
+ . ' Web 2 in database "%s" and to grant access to it to a new login called "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname'],
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to setup the missing'
+ . ' schema required by Icinga Web 2 in database "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname']
+ );
+ }
+ } else {
+ $message = sprintf(
+ mt('setup', 'The database "%s" already seems to be fully set up. No action required.'),
+ $resourceConfig['dbname']
+ );
+ }
+ } catch (PDOException $_) {
+ try {
+ $db->connectToHost();
+ if ($resourceConfig['username'] !== $this->data['resourceConfig']['username']) {
+ if ($db->hasLogin($this->data['resourceConfig']['username'])) {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to create the missing database'
+ . ' "%s" with the schema required by Icinga Web 2 and to grant'
+ . ' access to it to an existing login called "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname'],
+ $this->data['resourceConfig']['username']
+ );
+ } else {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to create the missing database'
+ . ' "%s" with the schema required by Icinga Web 2 and to grant'
+ . ' access to it to a new login called "%s".'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname'],
+ $this->data['resourceConfig']['username']
+ );
+ }
+ } else {
+ $message = sprintf(
+ mt(
+ 'setup',
+ 'The database user "%s" will be used to create the missing'
+ . ' database "%s" with the schema required by Icinga Web 2.'
+ ),
+ $resourceConfig['username'],
+ $resourceConfig['dbname']
+ );
+ }
+ } catch (Exception $_) {
+ $message = mt(
+ 'setup',
+ 'No connection to database host possible. You\'ll need to setup the'
+ . ' database with the schema required by Icinga Web 2 manually.'
+ );
+ }
+ }
+
+ return '<h2>' . mt('setup', 'Database Setup', 'setup.page.title') . '</h2><p>' . $message . '</p>';
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ $report = $this->messages;
+ $report[] = mt('setup', 'The database has been fully set up!');
+ return $report;
+ } elseif ($this->error !== null) {
+ $report = $this->messages;
+ $report[] = mt('setup', 'Failed to fully setup the database. An error occured:');
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error));
+ return $report;
+ }
+ }
+
+ protected function log()
+ {
+ $this->messages[] = call_user_func_array('sprintf', func_get_args());
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/GeneralConfigStep.php b/modules/setup/library/Setup/Steps/GeneralConfigStep.php
new file mode 100644
index 0000000..2c928f6
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/GeneralConfigStep.php
@@ -0,0 +1,131 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Logger;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class GeneralConfigStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $config = array();
+ foreach ($this->data['generalConfig'] as $sectionAndPropertyName => $value) {
+ list($section, $property) = explode('_', $sectionAndPropertyName, 2);
+ $config[$section][$property] = $value;
+ }
+
+ $config['global']['config_resource'] = $this->data['resourceName'];
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('config.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->error = $e;
+ return false;
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ $pageTitle = '<h2>' . mt('setup', 'Application Configuration', 'setup.page.title') . '</h2>';
+ $generalTitle = '<h3>' . t('General', 'app.config') . '</h3>';
+ $loggingTitle = '<h3>' . t('Logging', 'app.config') . '</h3>';
+
+ $generalHtml = ''
+ . '<ul>'
+ . '<li>' . ($this->data['generalConfig']['global_show_stacktraces']
+ ? t('An exception\'s stacktrace is shown to every user by default.')
+ : t('An exception\'s stacktrace is hidden from every user by default.')
+ ) . '</li>'
+ . '<li>' . t('Preferences will be stored using a database.') . '</li>'
+ . '</ul>';
+
+ $type = $this->data['generalConfig']['logging_log'];
+ if ($type === 'none') {
+ $loggingHtml = '<p>' . mt('setup', 'Logging will be disabled.') . '</p>';
+ } else {
+ $level = $this->data['generalConfig']['logging_level'];
+
+ switch ($type) {
+ case 'php':
+ $typeDescription = t('Webserver Log', 'app.config.logging.type');
+ $typeSpecificHtml = '';
+ break;
+
+ case 'syslog':
+ $typeDescription = 'Syslog';
+ $typeSpecificHtml = '<td><strong>' . t('Application Prefix') . '</strong></td>'
+ . '<td>' . $this->data['generalConfig']['logging_application'] . '</td>';
+ break;
+
+ case 'file':
+ $typeDescription = t('File', 'app.config.logging.type');
+ $typeSpecificHtml = '<td><strong>' . t('Filepath') . '</strong></td>'
+ . '<td>' . $this->data['generalConfig']['logging_file'] . '</td>';
+ break;
+ }
+
+ $loggingHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Type', 'app.config.logging') . '</strong></td>'
+ . '<td>' . $typeDescription . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Level', 'app.config.logging') . '</strong></td>'
+ . '<td>' . ($level === Logger::$levels[Logger::ERROR] ? t('Error', 'app.config.logging.level') : (
+ $level === Logger::$levels[Logger::WARNING] ? t('Warning', 'app.config.logging.level') : (
+ $level === Logger::$levels[Logger::INFO] ? t('Information', 'app.config.logging.level') : (
+ t('Debug', 'app.config.logging.level')
+ )
+ )
+ )) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . $typeSpecificHtml
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+ }
+
+ return $pageTitle . '<div class="topic">' . $generalTitle . $generalHtml . '</div>'
+ . '<div class="topic">' . $loggingTitle . $loggingHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ return array(sprintf(
+ mt('setup', 'General configuration has been successfully written to: %s'),
+ Config::resolvePath('config.ini')
+ ));
+ } elseif ($this->error !== null) {
+ return array(
+ sprintf(
+ mt('setup', 'General configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('config.ini')
+ ),
+ sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error))
+ );
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/ResourceStep.php b/modules/setup/library/Setup/Steps/ResourceStep.php
new file mode 100644
index 0000000..d9daf3b
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/ResourceStep.php
@@ -0,0 +1,199 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class ResourceStep extends Step
+{
+ protected $data;
+
+ protected $error;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $resourceConfig = array();
+ if (isset($this->data['dbResourceConfig'])) {
+ $dbConfig = $this->data['dbResourceConfig'];
+ $resourceName = $dbConfig['name'];
+ unset($dbConfig['name']);
+ $resourceConfig[$resourceName] = $dbConfig;
+ }
+
+ if (isset($this->data['ldapResourceConfig'])) {
+ $ldapConfig = $this->data['ldapResourceConfig'];
+ $resourceName = $ldapConfig['name'];
+ unset($ldapConfig['name']);
+ $resourceConfig[$resourceName] = $ldapConfig;
+ }
+
+ try {
+ Config::fromArray($resourceConfig)
+ ->setConfigFile(Config::resolvePath('resources.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->error = $e;
+ return false;
+ }
+
+ $this->error = false;
+ return true;
+ }
+
+ public function getSummary()
+ {
+ if (isset($this->data['dbResourceConfig']) && isset($this->data['ldapResourceConfig'])) {
+ $pageTitle = '<h2>' . mt('setup', 'Resources', 'setup.page.title') . '</h2>';
+ } else {
+ $pageTitle = '<h2>' . mt('setup', 'Resource', 'setup.page.title') . '</h2>';
+ }
+
+ if (isset($this->data['dbResourceConfig'])) {
+ $dbTitle = '<h3>' . mt('setup', 'Database', 'setup.page.title') . '</h3>';
+ $dbHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Resource Name') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Database Type') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['db'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Host') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['host'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Port') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Database Name') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['dbname'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Username') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['username'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Password') . '</strong></td>'
+ . '<td>' . str_repeat('*', strlen($this->data['dbResourceConfig']['password'])) . '</td>'
+ . '</tr>';
+
+ if (defined('\PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && isset($this->data['resourceConfig']['ssl_do_not_verify_server_cert'])
+ && $this->data['resourceConfig']['ssl_do_not_verify_server_cert']
+ ) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('SSL Do Not Verify Server Certificate') . '</strong></td>'
+ . '<td>' . $this->data['resourceConfig']['ssl_do_not_verify_server_cert'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_key']) && $this->data['dbResourceConfig']['ssl_key']) {
+ $dbHtml .= ''
+ .'<tr>'
+ . '<td><strong>' . t('SSL Key') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_key'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_cert']) && $this->data['dbResourceConfig']['ssl_cert']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('SSL Cert') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_cert'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_ca']) && $this->data['dbResourceConfig']['ssl_ca']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('CA') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_ca'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_capath']) && $this->data['dbResourceConfig']['ssl_capath']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('CA Path') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_capath'] . '</td>'
+ . '</tr>';
+ }
+ if (isset($this->data['dbResourceConfig']['ssl_cipher']) && $this->data['dbResourceConfig']['ssl_cipher']) {
+ $dbHtml .= ''
+ . '<tr>'
+ . '<td><strong>' . t('Cipher') . '</strong></td>'
+ . '<td>' . $this->data['dbResourceConfig']['ssl_cipher'] . '</td>'
+ . '</tr>';
+ }
+
+ $dbHtml .= ''
+ . '</tbody>'
+ . '</table>';
+ }
+
+ if (isset($this->data['ldapResourceConfig'])) {
+ $ldapTitle = '<h3>LDAP</h3>';
+ $ldapHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Resource Name') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Host') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['hostname'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Port') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['port'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Root DN') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['root_dn'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Bind DN') . '</strong></td>'
+ . '<td>' . $this->data['ldapResourceConfig']['bind_dn'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . t('Bind Password') . '</strong></td>'
+ . '<td>' . str_repeat('*', strlen($this->data['ldapResourceConfig']['bind_pw'])) . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+ }
+
+ return $pageTitle . (isset($dbTitle) ? '<div class="topic">' . $dbTitle . $dbHtml . '</div>' : '')
+ . (isset($ldapTitle) ? '<div class="topic">' . $ldapTitle . $ldapHtml . '</div>' : '');
+ }
+
+ public function getReport()
+ {
+ if ($this->error === false) {
+ return array(sprintf(
+ mt('setup', 'Resource configuration has been successfully written to: %s'),
+ Config::resolvePath('resources.ini')
+ ));
+ } elseif ($this->error !== null) {
+ return array(
+ sprintf(
+ mt('setup', 'Resource configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('resources.ini')
+ ),
+ sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error))
+ );
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Steps/UserGroupStep.php b/modules/setup/library/Setup/Steps/UserGroupStep.php
new file mode 100644
index 0000000..4aab676
--- /dev/null
+++ b/modules/setup/library/Setup/Steps/UserGroupStep.php
@@ -0,0 +1,213 @@
+<?php
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Steps;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Authentication\UserGroup\DbUserGroupBackend;
+use Icinga\Data\ConfigObject;
+use Icinga\Data\ResourceFactory;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class UserGroupStep extends Step
+{
+ protected $data;
+
+ protected $groupError;
+
+ protected $memberError;
+
+ protected $groupIniError;
+
+ public function __construct(array $data)
+ {
+ $this->data = $data;
+ }
+
+ public function apply()
+ {
+ $success = $this->createGroupsIni();
+ if (isset($this->data['resourceConfig'])) {
+ $success &= $this->createUserGroup();
+ if ($success) {
+ $success &= $this->createMembership();
+ }
+ }
+
+ return $success;
+ }
+
+ protected function createGroupsIni()
+ {
+ $config = array();
+ if (isset($this->data['groupConfig'])) {
+ $backendConfig = $this->data['groupConfig'];
+ $backendName = $backendConfig['name'];
+ unset($backendConfig['name']);
+ $config[$backendName] = $backendConfig;
+ } else {
+ $backendConfig = array(
+ 'backend' => $this->data['backendConfig']['backend'], // "db" or "msldap"
+ 'resource' => $this->data['resourceName']
+ );
+
+ if ($backendConfig['backend'] === 'msldap') {
+ $backendConfig['user_backend'] = $this->data['backendConfig']['name'];
+ }
+
+ $config[$this->data['backendConfig']['name']] = $backendConfig;
+ }
+
+ try {
+ Config::fromArray($config)
+ ->setConfigFile(Config::resolvePath('groups.ini'))
+ ->saveIni();
+ } catch (Exception $e) {
+ $this->groupIniError = $e;
+ return false;
+ }
+
+ $this->groupIniError = false;
+ return true;
+ }
+
+ protected function createUserGroup()
+ {
+ try {
+ $backend = new DbUserGroupBackend(
+ ResourceFactory::createResource(new ConfigObject($this->data['resourceConfig']))
+ );
+
+ $groupName = mt('setup', 'Administrators', 'setup.role.name');
+ if ($backend->select()->where('group_name', $groupName)->count() === 0) {
+ $backend->insert('group', array(
+ 'group_name' => $groupName
+ ));
+ $this->groupError = false;
+ }
+ } catch (Exception $e) {
+ $this->groupError = $e;
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function createMembership()
+ {
+ try {
+ $backend = new DbUserGroupBackend(
+ ResourceFactory::createResource(new ConfigObject($this->data['resourceConfig']))
+ );
+
+ $groupName = mt('setup', 'Administrators', 'setup.role.name');
+ $userName = $this->data['username'];
+ if ($backend
+ ->select()
+ ->from('group_membership')
+ ->where('group_name', $groupName)
+ ->where('user_name', $userName)
+ ->count() === 0
+ ) {
+ $backend->insert('group_membership', array(
+ 'group_name' => $groupName,
+ 'user_name' => $userName
+ ));
+ $this->memberError = false;
+ }
+ } catch (Exception $e) {
+ $this->memberError = $e;
+ return false;
+ }
+
+ return true;
+ }
+
+ public function getSummary()
+ {
+ if (! isset($this->data['groupConfig'])) {
+ return; // It's not necessary to show the user something he didn't configure..
+ }
+
+ $pageTitle = '<h2>' . mt('setup', 'User Groups', 'setup.page.title') . '</h2>';
+ $backendTitle = '<h3>' . mt('setup', 'User Group Backend', 'setup.page.title') . '</h3>';
+
+ $backendHtml = ''
+ . '<table>'
+ . '<tbody>'
+ . '<tr>'
+ . '<td><strong>' . t('Backend Name') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['name'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Group Object Class') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['group_class'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Custom Filter') . '</strong></td>'
+ . '<td>' . (trim($this->data['groupConfig']['group_filter']) ?: t('None', 'auth.ldap.filter')) . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Group Name Attribute') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['group_name_attribute'] . '</td>'
+ . '</tr>'
+ . '<tr>'
+ . '<td><strong>' . mt('setup', 'Group Member Attribute') . '</strong></td>'
+ . '<td>' . $this->data['groupConfig']['group_member_attribute'] . '</td>'
+ . '</tr>'
+ . '</tbody>'
+ . '</table>';
+
+ return $pageTitle . '<div class="topic">' . $backendTitle . $backendHtml . '</div>';
+ }
+
+ public function getReport()
+ {
+ $report = array();
+
+ if ($this->groupIniError === false) {
+ $report[] = sprintf(
+ mt('setup', 'User Group Backend configuration has been successfully written to: %s'),
+ Config::resolvePath('groups.ini')
+ );
+ } elseif ($this->groupIniError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'User Group Backend configuration could not be written to: %s. An error occured:'),
+ Config::resolvePath('groups.ini')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->groupIniError));
+ }
+
+ if ($this->groupError === false) {
+ $report[] = sprintf(
+ mt('setup', 'User Group "%s" has been successfully created.'),
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ } elseif ($this->groupError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Unable to create user group "%s". An error occured:'),
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->groupError));
+ }
+
+ if ($this->memberError === false) {
+ $report[] = sprintf(
+ mt('setup', 'Account "%s" has been successfully added as member to user group "%s".'),
+ $this->data['username'],
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ } elseif ($this->memberError !== null) {
+ $report[] = sprintf(
+ mt('setup', 'Unable to add account "%s" as member to user group "%s". An error occured:'),
+ $this->data['username'],
+ mt('setup', 'Administrators', 'setup.role.name')
+ );
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->memberError));
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/setup/library/Setup/Utils/DbTool.php b/modules/setup/library/Setup/Utils/DbTool.php
new file mode 100644
index 0000000..5cf203e
--- /dev/null
+++ b/modules/setup/library/Setup/Utils/DbTool.php
@@ -0,0 +1,943 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Utils;
+
+use PDO;
+use PDOException;
+use LogicException;
+use Zend_Db_Adapter_Pdo_Mysql;
+use Zend_Db_Adapter_Pdo_Pgsql;
+use Icinga\Util\File;
+use Icinga\Exception\ConfigurationError;
+
+/**
+ * Utility class to ease working with databases when setting up Icinga Web 2 or one of its modules
+ */
+class DbTool
+{
+ /**
+ * The PDO database connection
+ *
+ * @var PDO
+ */
+ protected $pdoConn;
+
+ /**
+ * The Zend database adapter
+ *
+ * @var Zend_Db_Adapter_Pdo_Abstract
+ */
+ protected $zendConn;
+
+ /**
+ * The resource configuration
+ *
+ * @var array
+ */
+ protected $config;
+
+ /**
+ * Whether we are connected to the database from the resource configuration
+ *
+ * @var bool
+ */
+ protected $dbFromConfig = false;
+
+ /**
+ * GRANT privilege level identifiers
+ */
+ const GLOBAL_LEVEL = 1;
+ const PROCEDURE_LEVEL = 2;
+ const DATABASE_LEVEL = 4;
+ const TABLE_LEVEL = 8;
+ const COLUMN_LEVEL = 16;
+ const FUNCTION_LEVEL = 32;
+
+ /**
+ * All MySQL GRANT privileges with their respective level identifiers
+ *
+ * @var array
+ */
+ protected $mysqlGrantContexts = array(
+ 'ALL' => 31,
+ 'ALL PRIVILEGES' => 31,
+ 'ALTER' => 13,
+ 'ALTER ROUTINE' => 7,
+ 'CREATE' => 13,
+ 'CREATE ROUTINE' => 5,
+ 'CREATE TEMPORARY TABLES' => 5,
+ 'CREATE USER' => 1,
+ 'CREATE VIEW' => 13,
+ 'DELETE' => 13,
+ 'DROP' => 13,
+ 'EXECUTE' => 5, // MySQL reference states this also supports database level, 5.1.73 not though
+ 'FILE' => 1,
+ 'GRANT OPTION' => 15,
+ 'INDEX' => 13,
+ 'INSERT' => 29,
+ 'LOCK TABLES' => 5,
+ 'PROCESS' => 1,
+ 'REFERENCES' => 12,
+ 'RELOAD' => 1,
+ 'REPLICATION CLIENT' => 1,
+ 'REPLICATION SLAVE' => 1,
+ 'SELECT' => 29,
+ 'SHOW DATABASES' => 1,
+ 'SHOW VIEW' => 13,
+ 'SHUTDOWN' => 1,
+ 'SUPER' => 1,
+ 'UPDATE' => 29
+ );
+
+ /**
+ * All PostgreSQL GRANT privileges with their respective level identifiers
+ *
+ * @var array
+ */
+ protected $pgsqlGrantContexts = array(
+ 'ALL' => 63,
+ 'ALL PRIVILEGES' => 63,
+ 'SELECT' => 24,
+ 'INSERT' => 24,
+ 'UPDATE' => 24,
+ 'DELETE' => 8,
+ 'TRUNCATE' => 8,
+ 'REFERENCES' => 24,
+ 'TRIGGER' => 8,
+ 'CREATE' => 12,
+ 'CONNECT' => 4,
+ 'TEMPORARY' => 4,
+ 'TEMP' => 4,
+ 'EXECUTE' => 32,
+ 'USAGE' => 33,
+ 'CREATEROLE' => 1
+ );
+
+ /**
+ * Create a new DbTool
+ *
+ * @param array $config The resource configuration to use
+ */
+ public function __construct(array $config)
+ {
+ if (! isset($config['port'])) {
+ // TODO: This is not quite correct, but works as it previously did. Previously empty values were not
+ // transformed no NULL (now they are) so if the port is now null, it's been the empty string.
+ $config['port'] = '';
+ }
+
+ $this->config = $config;
+ }
+
+ /**
+ * Connect to the server
+ *
+ * @return $this
+ */
+ public function connectToHost()
+ {
+ $this->assertHostAccess();
+
+ if ($this->config['db'] == 'pgsql') {
+ // PostgreSQL requires us to specify a database on each connection and will use
+ // the current user name as default database in cases none is provided. If
+ // that database doesn't exist (which might be the case here) it will error.
+ // Therefore, we specify the maintenance database 'postgres' as database, which
+ // is most probably present and public. (http://stackoverflow.com/q/4483139)
+ $this->connect('postgres');
+ } else {
+ $this->connect();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Connect to the database
+ *
+ * @return $this
+ */
+ public function connectToDb()
+ {
+ $this->assertHostAccess();
+ $this->assertDatabaseAccess();
+ $this->connect($this->config['dbname']);
+ return $this;
+ }
+
+ /**
+ * Assert that all configuration values exist that are required to connect to a server
+ *
+ * @throws ConfigurationError
+ */
+ protected function assertHostAccess()
+ {
+ if (! isset($this->config['db'])) {
+ throw new ConfigurationError('Can\'t connect to database server of unknown type');
+ } elseif (! isset($this->config['host'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a hostname or address');
+ } elseif (! isset($this->config['port'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a port');
+ } elseif (! isset($this->config['username'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a username');
+ } elseif (! isset($this->config['password'])) {
+ throw new ConfigurationError('Can\'t connect to database server without a password');
+ }
+ }
+
+ /**
+ * Assert that all configuration values exist that are required to connect to a database
+ *
+ * @throws ConfigurationError
+ */
+ protected function assertDatabaseAccess()
+ {
+ if (! isset($this->config['dbname'])) {
+ throw new ConfigurationError('Can\'t connect to database without a valid database name');
+ }
+ }
+
+ /**
+ * Assert that a connection with a database has been established
+ *
+ * @throws LogicException
+ */
+ protected function assertConnectedToDb()
+ {
+ if ($this->zendConn === null) {
+ throw new LogicException('Not connected to database');
+ }
+ }
+
+ /**
+ * Return whether a connection with the server has been established
+ *
+ * @return bool
+ */
+ public function isConnected()
+ {
+ return $this->pdoConn !== null;
+ }
+
+ /**
+ * Establish a connection with the database or just the server by omitting the database name
+ *
+ * @param string $dbname The name of the database to connect to
+ */
+ public function connect($dbname = null)
+ {
+ $this->pdoConnect($dbname);
+ if ($dbname !== null) {
+ $this->zendConnect($dbname);
+ $this->dbFromConfig = $dbname === $this->config['dbname'];
+ }
+ }
+
+ /**
+ * Reestablish a connection with the database or just the server by omitting the database name
+ *
+ * @param string $dbname The name of the database to connect to
+ */
+ public function reconnect($dbname = null)
+ {
+ $this->pdoConn = null;
+ $this->zendConn = null;
+ $this->connect($dbname);
+ }
+
+ /**
+ * Initialize Zend database adapter
+ *
+ * @param string $dbname The name of the database to connect with
+ *
+ * @throws ConfigurationError In case the resource type is not a supported PDO driver name
+ */
+ private function zendConnect($dbname)
+ {
+ if ($this->zendConn !== null) {
+ return;
+ }
+
+ $config = array(
+ 'dbname' => $dbname,
+ 'host' => $this->config['host'],
+ 'port' => $this->config['port'],
+ 'username' => $this->config['username'],
+ 'password' => $this->config['password']
+ );
+
+ if ($this->config['db'] === 'mysql') {
+ if (isset($this->config['use_ssl']) && $this->config['use_ssl']) {
+ $this->config['driver_options'] = array();
+ # The presence of these keys as empty strings or null cause non-ssl connections to fail
+ if ($this->config['ssl_key']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_KEY] = $this->config['ssl_key'];
+ }
+ if ($this->config['ssl_cert']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CERT] = $this->config['ssl_cert'];
+ }
+ if ($this->config['ssl_ca']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CA] = $this->config['ssl_ca'];
+ }
+ if ($this->config['ssl_capath']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CAPATH] = $this->config['ssl_capath'];
+ }
+ if ($this->config['ssl_cipher']) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_CIPHER] = $this->config['ssl_cipher'];
+ }
+ if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && $this->config['ssl_do_not_verify_server_cert']
+ ) {
+ $config['driver_options'][PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
+ }
+ }
+ $this->zendConn = new Zend_Db_Adapter_Pdo_Mysql($config);
+ } elseif ($this->config['db'] === 'pgsql') {
+ $this->zendConn = new Zend_Db_Adapter_Pdo_Pgsql($config);
+ } else {
+ throw new ConfigurationError(
+ 'Failed to connect to database. Unsupported PDO driver "%s"',
+ $this->config['db']
+ );
+ }
+
+ $this->zendConn->getConnection(); // Force connection attempt
+ }
+
+ /**
+ * Initialize PDO connection
+ *
+ * @param string $dbname The name of the database to connect with
+ */
+ private function pdoConnect($dbname)
+ {
+ if ($this->pdoConn !== null) {
+ return;
+ }
+
+ $driverOptions = array(
+ PDO::ATTR_TIMEOUT => 1,
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
+ );
+
+ if ($this->config['db'] === 'mysql'
+ && isset($this->config['use_ssl'])
+ && $this->config['use_ssl']
+ ) {
+ # The presence of these keys as empty strings or null cause non-ssl connections to fail
+ if ($this->config['ssl_key']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_KEY] = $this->config['ssl_key'];
+ }
+ if ($this->config['ssl_cert']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CERT] = $this->config['ssl_cert'];
+ }
+ if ($this->config['ssl_ca']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CA] = $this->config['ssl_ca'];
+ }
+ if ($this->config['ssl_capath']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CAPATH] = $this->config['ssl_capath'];
+ }
+ if ($this->config['ssl_cipher']) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_CIPHER] = $this->config['ssl_cipher'];
+ }
+ if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')
+ && $this->config['ssl_do_not_verify_server_cert']
+ ) {
+ $driverOptions[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
+ }
+ }
+
+ $this->pdoConn = new PDO(
+ $this->buildDsn($this->config['db'], $dbname),
+ $this->config['username'],
+ $this->config['password'],
+ $driverOptions
+ );
+ }
+
+ /**
+ * Return a datasource name for the given database type and name
+ *
+ * @param string $dbtype
+ * @param string $dbname
+ *
+ * @return string
+ *
+ * @throws ConfigurationError In case the passed database type is not supported
+ */
+ protected function buildDsn($dbtype, $dbname = null)
+ {
+ if ($dbtype === 'mysql') {
+ return 'mysql:host=' . $this->config['host'] . ';port=' . $this->config['port']
+ . ($dbname !== null ? ';dbname=' . $dbname : '');
+ } elseif ($dbtype === 'pgsql') {
+ return 'pgsql:host=' . $this->config['host'] . ';port=' . $this->config['port']
+ . ($dbname !== null ? ';dbname=' . $dbname : '');
+ } else {
+ throw new ConfigurationError(
+ 'Failed to build data source name. Unsupported PDO driver "%s"',
+ $dbtype
+ );
+ }
+ }
+
+ /**
+ * Try to connect to the server and throw an exception if this fails
+ *
+ * @throws PDOException In case an error occurs that does not indicate that authentication failed
+ */
+ public function checkConnectivity()
+ {
+ try {
+ $this->connectToHost();
+ } catch (PDOException $e) {
+ if ($this->config['db'] === 'mysql') {
+ $code = $e->getCode();
+ /*
+ * 1040 .. Too many connections
+ * 1045 .. Access denied for user '%s'@'%s' (using password: %s)
+ * 1698 .. Access denied for user '%s'@'%s'
+ */
+ if ($code !== 1040 && $code !== 1045 && $code !== 1698) {
+ throw $e;
+ }
+ } elseif ($this->config['db'] === 'pgsql') {
+ if (strpos($e->getMessage(), $this->config['username']) === false) {
+ throw $e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the given identifier escaped with backticks
+ *
+ * @param string $identifier The identifier to escape
+ *
+ * @return string
+ *
+ * @throws LogicException In case there is no behaviour implemented for the current PDO driver
+ */
+ public function quoteIdentifier($identifier)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return '`' . str_replace('`', '``', $identifier) . '`';
+ } elseif ($this->config['db'] === 'pgsql') {
+ return '"' . str_replace('"', '""', $identifier) . '"';
+ } else {
+ throw new LogicException('Unable to quote identifier.');
+ }
+ }
+
+ /**
+ * Return the given table name with all wildcards being escaped
+ *
+ * @param string $tableName
+ *
+ * @return string
+ *
+ * @throws LogicException In case there is no behaviour implemented for the current PDO driver
+ */
+ public function escapeTableWildcards($tableName)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return str_replace(array('_', '%'), array('\_', '\%'), $tableName);
+ }
+
+ throw new LogicException('Unable to escape table wildcards.');
+ }
+
+ /**
+ * Return the given value escaped as string
+ *
+ * @param mixed $value The value to escape
+ *
+ * @return string
+ *
+ * @throws LogicException In case there is no behaviour implemented for the current PDO driver
+ */
+ public function quote($value)
+ {
+ $quoted = $this->pdoConn->quote($value);
+ if ($quoted === false) {
+ throw new LogicException(sprintf('Unable to quote value: %s', $value));
+ }
+
+ return $quoted;
+ }
+
+ /**
+ * Execute a SQL statement and return the affected row count
+ *
+ * Use $params to use a prepared statement.
+ *
+ * @param string $statement The statement to execute
+ * @param array $params The params to bind
+ *
+ * @return int
+ */
+ public function exec($statement, $params = array())
+ {
+ if (empty($params)) {
+ return $this->pdoConn->exec($statement);
+ }
+
+ $stmt = $this->pdoConn->prepare($statement);
+ $stmt->execute($params);
+ return $stmt->rowCount();
+ }
+
+ /**
+ * Execute a SQL statement and return the result
+ *
+ * Use $params to use a prepared statement.
+ *
+ * @param string $statement The statement to execute
+ * @param array $params The params to bind
+ *
+ * @return mixed
+ */
+ public function query($statement, $params = array())
+ {
+ if ($this->zendConn !== null) {
+ return $this->zendConn->query($statement, $params);
+ }
+
+ if (empty($params)) {
+ return $this->pdoConn->query($statement);
+ }
+
+ $stmt = $this->pdoConn->prepare($statement);
+ $stmt->execute($params);
+ return $stmt;
+ }
+
+ /**
+ * Return the version of the server currently connected to
+ *
+ * @return string|null
+ */
+ public function getServerVersion()
+ {
+ if ($this->config['db'] === 'mysql') {
+ return $this->query('show variables like "version"')->fetchColumn(1) ?: null;
+ } elseif ($this->config['db'] === 'pgsql') {
+ return $this->query('show server_version')->fetchColumn() ?: null;
+ } else {
+ throw new LogicException(
+ sprintf('Unable to fetch the server\'s version. Unsupported PDO driver "%s"', $this->config['db'])
+ );
+ }
+ }
+
+ /**
+ * Import the given SQL file
+ *
+ * @param string $filepath The file to import
+ */
+ public function import($filepath)
+ {
+ $file = new File($filepath);
+ $content = join(PHP_EOL, iterator_to_array($file)); // There is no fread() before PHP 5.5 :(
+
+ foreach (preg_split('@;(?! \\\\)@', $content) as $statement) {
+ if (($statement = trim($statement)) !== '') {
+ $this->exec($statement);
+ }
+ }
+ }
+
+ /**
+ * Return whether the given privileges were granted
+ *
+ * @param array $privileges An array of strings with the required privilege names
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The login name for which to check the privileges,
+ * if NULL the current login is used
+ *
+ * @return bool
+ */
+ public function checkPrivileges(array $privileges, array $context = null, $username = null)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return $this->checkMysqlPrivileges($privileges, false, $context, $username);
+ } elseif ($this->config['db'] === 'pgsql') {
+ return $this->checkPgsqlPrivileges($privileges, false, $context, $username);
+ }
+ }
+
+ /**
+ * Return whether the given privileges are grantable to other users
+ *
+ * @param array $privileges The privileges that should be grantable
+ *
+ * @return bool
+ */
+ public function isGrantable($privileges)
+ {
+ if ($this->config['db'] === 'mysql') {
+ return $this->checkMysqlPrivileges($privileges, true);
+ } elseif ($this->config['db'] === 'pgsql') {
+ return $this->checkPgsqlPrivileges($privileges, true);
+ }
+ }
+
+ /**
+ * Grant all given privileges to the given user
+ *
+ * @param array $privileges The privilege names to grant
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The username to grant the privileges to
+ */
+ public function grantPrivileges(array $privileges, array $context, $username)
+ {
+ if ($this->config['db'] === 'mysql') {
+ list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
+ $quotedDbName = $this->quoteIdentifier($this->config['dbname']);
+
+ $grant = 'GRANT %s';
+ $on = ' ON %s.%s';
+ $to = sprintf(
+ ' TO %s@%s',
+ $this->quoteIdentifier($username),
+ $this->quoteIdentifier($host)
+ );
+
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach (array_intersect($privileges, array_keys($this->mysqlGrantContexts)) as $privilege) {
+ if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ } elseif ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ if (! empty($tablePrivileges)) {
+ $tableGrant = sprintf($grant, join(',', $tablePrivileges));
+ foreach ($context as $table) {
+ $this->exec($tableGrant . sprintf($on, $quotedDbName, $this->quoteIdentifier($table)) . $to);
+ }
+ }
+
+ if (! empty($dbPrivileges)) {
+ $this->exec(
+ sprintf($grant, join(',', $dbPrivileges))
+ . sprintf($on, $this->escapeTableWildcards($quotedDbName), '*')
+ . $to
+ );
+ }
+ } elseif ($this->config['db'] === 'pgsql') {
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
+ if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ } elseif ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ if (! empty($dbPrivileges)) {
+ $this->exec(sprintf(
+ 'GRANT %s ON DATABASE %s TO %s',
+ join(',', $dbPrivileges),
+ $this->config['dbname'],
+ $username
+ ));
+ }
+
+ if (! empty($tablePrivileges)) {
+ foreach ($context as $table) {
+ $this->exec(sprintf(
+ 'GRANT %s ON TABLE %s TO %s',
+ join(',', $tablePrivileges),
+ $table,
+ $username
+ ));
+ }
+ }
+ }
+ }
+
+ /**
+ * Return a list of all existing database tables
+ *
+ * @return array
+ */
+ public function listTables()
+ {
+ $this->assertConnectedToDb();
+ return $this->zendConn->listTables();
+ }
+
+ /**
+ * Return whether the given database login exists
+ *
+ * @param string $username The username to search
+ *
+ * @return bool
+ */
+ public function hasLogin($username)
+ {
+ if ($this->config['db'] === 'mysql') {
+ $queryString = <<<EOD
+SELECT 1
+ FROM information_schema.user_privileges
+ WHERE grantee = REPLACE(CONCAT("'", REPLACE(CURRENT_USER(), '@', "'@'"), "'"), :current, :wanted)
+EOD;
+
+ $query = $this->query(
+ $queryString,
+ array(
+ ':current' => $this->config['username'],
+ ':wanted' => $username
+ )
+ );
+ return count($query->fetchAll()) > 0;
+ } elseif ($this->config['db'] === 'pgsql') {
+ $query = $this->query(
+ 'SELECT 1 FROM pg_catalog.pg_user WHERE usename = :ident LIMIT 1',
+ array(':ident' => $username)
+ );
+ return count($query->fetchAll()) === 1;
+ }
+ }
+
+ /**
+ * Add a new database login
+ *
+ * @param string $username The username of the new login
+ * @param string $password The password of the new login
+ */
+ public function addLogin($username, $password)
+ {
+ if ($this->config['db'] === 'mysql') {
+ list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
+ $this->exec(
+ 'CREATE USER :user@:host IDENTIFIED BY :passw',
+ array(':user' => $username, ':host' => $host, ':passw' => $password)
+ );
+ } elseif ($this->config['db'] === 'pgsql') {
+ $this->exec(sprintf(
+ 'CREATE USER %s WITH PASSWORD %s',
+ $this->quoteIdentifier($username),
+ $this->quote($password)
+ ));
+ }
+ }
+
+ /**
+ * Check whether the current user has the given privileges
+ *
+ * @param array $privileges The privilege names
+ * @param bool $requireGrants Only return true when all privileges can be granted to others
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The login name to which the passed privileges need to be granted
+ *
+ * @return bool
+ */
+ protected function checkMysqlPrivileges(
+ array $privileges,
+ $requireGrants = false,
+ array $context = null,
+ $username = null
+ ) {
+ $mysqlPrivileges = array_intersect($privileges, array_keys($this->mysqlGrantContexts));
+ list($_, $host) = explode('@', $this->query('select current_user()')->fetchColumn());
+ $grantee = "'" . ($username === null ? $this->config['username'] : $username) . "'@'" . $host . "'";
+
+ if (isset($this->config['dbname'])) {
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach ($mysqlPrivileges as $privilege) {
+ if (! empty($context) && $this->mysqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ }
+ if ($this->mysqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ $dbPrivilegesGranted = true;
+ $tablePrivilegesGranted = true;
+
+ if (! empty($dbPrivileges)) {
+ $queryString = 'SELECT COUNT(*) as matches'
+ . ' FROM information_schema.schema_privileges'
+ . ' WHERE grantee = :grantee'
+ . ' AND table_schema = :dbname'
+ . ' AND privilege_type IN (%s)'
+ . ($requireGrants ? " AND is_grantable = 'YES'" : '');
+
+ $dbAndTableQuery = $this->query(
+ sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbPrivileges))),
+ array(':grantee' => $grantee, ':dbname' => $this->escapeTableWildcards($this->config['dbname']))
+ );
+ $grantedDbAndTablePrivileges = (int) $dbAndTableQuery->fetchObject()->matches;
+ if ($grantedDbAndTablePrivileges === count($dbPrivileges)) {
+ $tableExclusivePrivileges = array_diff($tablePrivileges, $dbPrivileges);
+ if (! empty($tableExclusivePrivileges)) {
+ $tablePrivileges = $tableExclusivePrivileges;
+ $tablePrivilegesGranted = false;
+ }
+ } else {
+ $tablePrivilegesGranted = false;
+ $dbExclusivePrivileges = array_diff($dbPrivileges, $tablePrivileges);
+ if (! empty($dbExclusivePrivileges)) {
+ $dbExclusiveQuery = $this->query(
+ sprintf($queryString, join(',', array_map(array($this, 'quote'), $dbExclusivePrivileges))),
+ array(
+ ':grantee' => $grantee,
+ ':dbname' => $this->escapeTableWildcards($this->config['dbname'])
+ )
+ );
+ $dbPrivilegesGranted = (int) $dbExclusiveQuery->fetchObject()->matches === count(
+ $dbExclusivePrivileges
+ );
+ }
+ }
+ }
+
+ if (! $tablePrivilegesGranted && !empty($tablePrivileges)) {
+ $query = $this->query(
+ 'SELECT COUNT(*) as matches'
+ . ' FROM information_schema.table_privileges'
+ . ' WHERE grantee = :grantee'
+ . ' AND table_schema = :dbname'
+ . ' AND table_name IN (' . join(',', array_map(array($this, 'quote'), $context)) . ')'
+ . ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $tablePrivileges)) . ')'
+ . ($requireGrants ? " AND is_grantable = 'YES'" : ''),
+ array(':grantee' => $grantee, ':dbname' => $this->config['dbname'])
+ );
+ $expectedAmountOfMatches = count($context) * count($tablePrivileges);
+ $tablePrivilegesGranted = (int) $query->fetchObject()->matches === $expectedAmountOfMatches;
+ }
+
+ if ($dbPrivilegesGranted && $tablePrivilegesGranted) {
+ return true;
+ }
+ }
+
+ $query = $this->query(
+ 'SELECT COUNT(*) as matches FROM information_schema.user_privileges WHERE grantee = :grantee'
+ . ' AND privilege_type IN (' . join(',', array_map(array($this, 'quote'), $mysqlPrivileges)) . ')'
+ . ($requireGrants ? " AND is_grantable = 'YES'" : ''),
+ array(':grantee' => $grantee)
+ );
+ return (int) $query->fetchObject()->matches === count($mysqlPrivileges);
+ }
+
+ /**
+ * Check whether the current user has the given privileges
+ *
+ * Note that database and table specific privileges (i.e. not SUPER, CREATE and CREATEROLE) are ignored
+ * in case no connection to the database defined in the resource configuration has been established
+ *
+ * @param array $privileges The privilege names
+ * @param bool $requireGrants Only return true when all privileges can be granted to others
+ * @param array $context An array describing the context for which the given privileges need to apply.
+ * Only one or more table names are currently supported
+ * @param string $username The login name to which the passed privileges need to be granted
+ *
+ * @return bool
+ */
+ public function checkPgsqlPrivileges(
+ array $privileges,
+ $requireGrants = false,
+ array $context = null,
+ $username = null
+ ) {
+ $privilegesGranted = true;
+ if ($this->dbFromConfig) {
+ $dbPrivileges = array();
+ $tablePrivileges = array();
+ foreach (array_intersect($privileges, array_keys($this->pgsqlGrantContexts)) as $privilege) {
+ if (! empty($context) && $this->pgsqlGrantContexts[$privilege] & static::TABLE_LEVEL) {
+ $tablePrivileges[] = $privilege;
+ }
+ if ($this->pgsqlGrantContexts[$privilege] & static::DATABASE_LEVEL) {
+ $dbPrivileges[] = $privilege;
+ }
+ }
+
+ if (! empty($dbPrivileges)) {
+ $dbExclusivesGranted = true;
+ foreach ($dbPrivileges as $dbPrivilege) {
+ $query = $this->query(
+ 'SELECT has_database_privilege(:user, :dbname, :privilege) AS db_privilege_granted',
+ array(
+ ':user' => $username !== null ? $username : $this->config['username'],
+ ':dbname' => $this->config['dbname'],
+ ':privilege' => $dbPrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
+ )
+ );
+ if (! $query->fetchObject()->db_privilege_granted) {
+ $privilegesGranted = false;
+ if (! in_array($dbPrivilege, $tablePrivileges)) {
+ $dbExclusivesGranted = false;
+ }
+ }
+ }
+
+ if ($privilegesGranted) {
+ // Do not check privileges twice if they are already granted at database level
+ $tablePrivileges = array_diff($tablePrivileges, $dbPrivileges);
+ } elseif ($dbExclusivesGranted) {
+ $privilegesGranted = true;
+ }
+ }
+
+ if ($privilegesGranted && !empty($tablePrivileges)) {
+ foreach (array_intersect($context, $this->listTables()) as $table) {
+ foreach ($tablePrivileges as $tablePrivilege) {
+ $query = $this->query(
+ 'SELECT has_table_privilege(:user, :table, :privilege) AS table_privilege_granted',
+ array(
+ ':user' => $username !== null ? $username : $this->config['username'],
+ ':table' => $table,
+ ':privilege' => $tablePrivilege . ($requireGrants ? ' WITH GRANT OPTION' : '')
+ )
+ );
+ $privilegesGranted &= $query->fetchObject()->table_privilege_granted;
+ }
+ }
+ }
+ } else {
+ // In case we cannot check whether the user got the required db-/table-privileges due to not being
+ // connected to the database defined in the resource configuration it is safe to just ignore them
+ // as the chances are very high that the database is created later causing the current user being
+ // the owner with ALL privileges. (Which in turn can be granted to others.)
+
+ if (array_search('CREATE', $privileges, true) !== false) {
+ $query = $this->query(
+ 'select rolcreatedb from pg_roles where rolname = :user',
+ array(':user' => $username !== null ? $username : $this->config['username'])
+ );
+ $privilegesGranted &= $query->fetchColumn() !== false;
+ }
+ }
+
+ if (array_search('CREATEROLE', $privileges, true) !== false) {
+ $query = $this->query(
+ 'select rolcreaterole from pg_roles where rolname = :user',
+ array(':user' => $username !== null ? $username : $this->config['username'])
+ );
+ $privilegesGranted &= $query->fetchColumn() !== false;
+ }
+
+ if (array_search('SUPER', $privileges, true) !== false) {
+ $query = $this->query(
+ 'select rolsuper from pg_roles where rolname = :user',
+ array(':user' => $username !== null ? $username : $this->config['username'])
+ );
+ $privilegesGranted &= $query->fetchColumn() !== false;
+ }
+
+ return (bool) $privilegesGranted;
+ }
+}
diff --git a/modules/setup/library/Setup/Utils/EnableModuleStep.php b/modules/setup/library/Setup/Utils/EnableModuleStep.php
new file mode 100644
index 0000000..92af5b7
--- /dev/null
+++ b/modules/setup/library/Setup/Utils/EnableModuleStep.php
@@ -0,0 +1,77 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Utils;
+
+use Exception;
+use Icinga\Application\Icinga;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Setup\Step;
+
+class EnableModuleStep extends Step
+{
+ protected $modulePaths;
+
+ protected $moduleNames;
+
+ protected $errors;
+
+ protected $warnings;
+
+ public function __construct(array $moduleNames)
+ {
+ $this->moduleNames = $moduleNames;
+
+ $this->modulePaths = array();
+ if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) {
+ $this->modulePaths[] = $appModulePath;
+ }
+ }
+
+ public function apply()
+ {
+ $moduleManager = Icinga::app()->getModuleManager();
+ $moduleManager->detectInstalledModules($this->modulePaths);
+
+ $success = true;
+ foreach ($this->moduleNames as $moduleName) {
+ try {
+ $moduleManager->enableModule($moduleName);
+ } catch (ConfigurationError $e) {
+ $this->warnings[$moduleName] = $e;
+ } catch (Exception $e) {
+ $this->errors[$moduleName] = $e;
+ $success = false;
+ }
+ }
+
+ return $success;
+ }
+
+ public function getSummary()
+ {
+ // Enabling a module is like a implicit action, which does not need to be shown to the user...
+ }
+
+ public function getReport()
+ {
+ $okMessage = mt('setup', 'Module "%s" has been successfully enabled.');
+ $failMessage = mt('setup', 'Module "%s" could not be enabled. An error occured:');
+
+ $report = array();
+ foreach ($this->moduleNames as $moduleName) {
+ if (isset($this->errors[$moduleName])) {
+ $report[] = sprintf($failMessage, $moduleName);
+ $report[] = sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->errors[$moduleName]));
+ } elseif (isset($this->warnings[$moduleName])) {
+ $report[] = sprintf($failMessage, $moduleName);
+ $report[] = sprintf(mt('setup', 'WARNING: %s'), $this->warnings[$moduleName]->getMessage());
+ } else {
+ $report[] = sprintf($okMessage, $moduleName);
+ }
+ }
+
+ return $report;
+ }
+}
diff --git a/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php b/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php
new file mode 100644
index 0000000..a3f218b
--- /dev/null
+++ b/modules/setup/library/Setup/Web/Form/Validator/TokenValidator.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Web\Form\Validator;
+
+use Exception;
+use Zend_Validate_Abstract;
+use Icinga\Util\File;
+
+/**
+ * Validator that checks if a token matches with the contents of a corresponding token-file
+ */
+class TokenValidator extends Zend_Validate_Abstract
+{
+ /**
+ * The path to the token file
+ *
+ * @var string
+ */
+ protected $tokenPath;
+
+ /**
+ * Create a new TokenValidator
+ *
+ * @param string $tokenPath The path to the token-file
+ */
+ public function __construct($tokenPath)
+ {
+ $this->tokenPath = $tokenPath;
+ $this->_messageTemplates = array(
+ 'TOKEN_FILE_ERROR' => sprintf(
+ mt('setup', 'Cannot validate token: %s (%s)'),
+ $tokenPath,
+ '%value%'
+ ),
+ 'TOKEN_FILE_EMPTY' => sprintf(
+ mt('setup', 'Cannot validate token, file "%s" is empty. Please define a token.'),
+ $tokenPath
+ ),
+ 'TOKEN_INVALID' => mt('setup', 'Invalid token supplied.')
+ );
+ }
+
+ /**
+ * Validate the given token with the one in the token-file
+ *
+ * @param string $value The token to validate
+ * @param null $context The form context (ignored)
+ *
+ * @return bool
+ */
+ public function isValid($value, $context = null)
+ {
+ try {
+ $file = new File($this->tokenPath);
+ $expectedToken = trim($file->fgets());
+ } catch (Exception $e) {
+ $msg = $e->getMessage();
+ $this->_error('TOKEN_FILE_ERROR', substr($msg, strpos($msg, ']: ') + 3));
+ return false;
+ }
+
+ if (empty($expectedToken)) {
+ $this->_error('TOKEN_FILE_EMPTY');
+ return false;
+ } elseif ($value !== $expectedToken) {
+ $this->_error('TOKEN_INVALID');
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php
new file mode 100644
index 0000000..f25be55
--- /dev/null
+++ b/modules/setup/library/Setup/WebWizard.php
@@ -0,0 +1,752 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use Icinga\Application\Platform;
+use Icinga\Module\Setup\Requirement\SetRequirement;
+use Icinga\Module\Setup\Requirement\WebLibraryRequirement;
+use PDOException;
+use Icinga\Web\Form;
+use Icinga\Web\Wizard;
+use Icinga\Web\Request;
+use Icinga\Application\Config;
+use Icinga\Application\Icinga;
+use Icinga\Module\Setup\Forms\ModulePage;
+use Icinga\Module\Setup\Forms\WelcomePage;
+use Icinga\Module\Setup\Forms\SummaryPage;
+use Icinga\Module\Setup\Forms\DbResourcePage;
+use Icinga\Module\Setup\Forms\AuthBackendPage;
+use Icinga\Module\Setup\Forms\AdminAccountPage;
+use Icinga\Module\Setup\Forms\LdapDiscoveryPage;
+//use Icinga\Module\Setup\Forms\LdapDiscoveryConfirmPage;
+use Icinga\Module\Setup\Forms\LdapResourcePage;
+use Icinga\Module\Setup\Forms\RequirementsPage;
+use Icinga\Module\Setup\Forms\GeneralConfigPage;
+use Icinga\Module\Setup\Forms\AuthenticationPage;
+use Icinga\Module\Setup\Forms\DatabaseCreationPage;
+use Icinga\Module\Setup\Forms\UserGroupBackendPage;
+use Icinga\Module\Setup\Steps\DatabaseStep;
+use Icinga\Module\Setup\Steps\GeneralConfigStep;
+use Icinga\Module\Setup\Steps\ResourceStep;
+use Icinga\Module\Setup\Steps\AuthenticationStep;
+use Icinga\Module\Setup\Steps\UserGroupStep;
+use Icinga\Module\Setup\Utils\EnableModuleStep;
+use Icinga\Module\Setup\Utils\DbTool;
+use Icinga\Module\Setup\Requirement\OSRequirement;
+use Icinga\Module\Setup\Requirement\PhpModuleRequirement;
+use Icinga\Module\Setup\Requirement\PhpVersionRequirement;
+use Icinga\Module\Setup\Requirement\ConfigDirectoryRequirement;
+use Icinga\Module\Monitoring\Forms\Config\Transport\ApiTransportForm;
+
+/**
+ * Icinga Web 2 Setup Wizard
+ */
+class WebWizard extends Wizard implements SetupWizard
+{
+ /**
+ * The privileges required by Icinga Web 2 to create the database and a login
+ *
+ * @var array
+ */
+ protected $databaseCreationPrivileges = array(
+ 'CREATE',
+ 'CREATE USER', // MySQL
+ 'CREATEROLE' // PostgreSQL
+ );
+
+ /**
+ * The privileges required by Icinga Web 2 to setup the database
+ *
+ * @var array
+ */
+ protected $databaseSetupPrivileges = array(
+ 'CREATE',
+ 'ALTER', // MySQL only
+ 'REFERENCES'
+ );
+
+ /**
+ * The privileges required by Icinga Web 2 to operate the database
+ *
+ * @var array
+ */
+ protected $databaseUsagePrivileges = array(
+ 'SELECT',
+ 'INSERT',
+ 'UPDATE',
+ 'DELETE',
+ 'EXECUTE',
+ 'TEMPORARY', // PostgreSql
+ 'CREATE TEMPORARY TABLES' // MySQL
+ );
+
+ /**
+ * The database tables operated by Icinga Web 2
+ *
+ * @var array
+ */
+ protected $databaseTables = array(
+ 'icingaweb_group',
+ 'icingaweb_group_membership',
+ 'icingaweb_user',
+ 'icingaweb_user_preference',
+ 'icingaweb_rememberme'
+ );
+
+ /**
+ * Register all pages and module wizards for this wizard
+ */
+ protected function init()
+ {
+ $this->addPage(new WelcomePage());
+ $this->addPage(new ModulePage());
+ $this->addPage(new RequirementsPage());
+ $this->addPage(new AuthenticationPage());
+ $this->addPage(new DbResourcePage(array('name' => 'setup_auth_db_resource')));
+ $this->addPage(new DatabaseCreationPage(array('name' => 'setup_auth_db_creation')));
+ $this->addPage(new LdapDiscoveryPage());
+ //$this->addPage(new LdapDiscoveryConfirmPage());
+ $this->addPage(new LdapResourcePage());
+ $this->addPage(new AuthBackendPage());
+ $this->addPage(new UserGroupBackendPage());
+ $this->addPage(new AdminAccountPage());
+ $this->addPage(new GeneralConfigPage());
+ $this->addPage(new DbResourcePage(array('name' => 'setup_config_db_resource')));
+ $this->addPage(new DatabaseCreationPage(array('name' => 'setup_config_db_creation')));
+ $this->addPage(new SummaryPage(array('name' => 'setup_summary')));
+
+ if (($modulePageData = $this->getPageData('setup_modules')) !== null) {
+ $modulePage = $this->getPage('setup_modules')->populate($modulePageData);
+ foreach ($modulePage->getModuleWizards() as $moduleWizard) {
+ $this->addPage($moduleWizard);
+ }
+ }
+ }
+
+ /**
+ * Setup the given page that is either going to be displayed or validated
+ *
+ * @param Form $page The page to setup
+ * @param Request $request The current request
+ */
+ public function setupPage(Form $page, Request $request)
+ {
+ if ($page->getName() === 'setup_requirements') {
+ $page->setWizard($this);
+ } elseif ($page->getName() === 'setup_authentication_backend') {
+ /** @var AuthBackendPage $page */
+
+ $authData = $this->getPageData('setup_authentication_type');
+ if ($authData['type'] === 'db') {
+ $page->setResourceConfig($this->getPageData('setup_auth_db_resource'));
+ } elseif ($authData['type'] === 'ldap') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_resource'));
+
+ $suggestions = $this->getPageData('setup_ldap_discovery');
+ if (isset($suggestions['backend'])) {
+ $page->setSuggestions($suggestions['backend']);
+ }
+
+ if ($this->getDirection() === static::FORWARD) {
+ $backendConfig = $this->getPageData('setup_authentication_backend');
+ if ($backendConfig !== null && $request->getPost('name') !== $backendConfig['name']) {
+ $pageData = & $this->getPageData();
+ unset($pageData['setup_usergroup_backend']);
+ }
+ }
+ }
+
+ if ($this->getDirection() === static::FORWARD) {
+ $backendConfig = $this->getPageData('setup_authentication_backend');
+ if ($backendConfig !== null && $request->getPost('backend') !== $backendConfig['backend']) {
+ $pageData = & $this->getPageData();
+ unset($pageData['setup_usergroup_backend']);
+ }
+ }
+ /*} elseif ($page->getName() === 'setup_ldap_discovery_confirm') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_discovery'));*/
+ } elseif ($page->getName() === 'setup_auth_db_resource') {
+ $page->addDescription(mt(
+ 'setup',
+ 'Now please configure the database resource where to store users and user groups.'
+ ));
+ $page->addDescription(mt(
+ 'setup',
+ 'Note that the database itself does not need to exist at this time as'
+ . ' it is going to be created once the wizard is about to be finished.'
+ ));
+ } elseif ($page->getName() === 'setup_usergroup_backend') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_resource'));
+ $page->setBackendConfig($this->getPageData('setup_authentication_backend'));
+ } elseif ($page->getName() === 'setup_admin_account') {
+ $page->setBackendConfig($this->getPageData('setup_authentication_backend'));
+ $page->setGroupConfig($this->getPageData('setup_usergroup_backend'));
+ $authData = $this->getPageData('setup_authentication_type');
+ if ($authData['type'] === 'db') {
+ $page->setResourceConfig($this->getPageData('setup_auth_db_resource'));
+ } elseif ($authData['type'] === 'ldap') {
+ $page->setResourceConfig($this->getPageData('setup_ldap_resource'));
+ }
+ } elseif ($page->getName() === 'setup_auth_db_creation' || $page->getName() === 'setup_config_db_creation') {
+ $page->setDatabaseSetupPrivileges(
+ array_unique(array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges))
+ );
+ $page->setDatabaseUsagePrivileges($this->databaseUsagePrivileges);
+ $page->setResourceConfig(
+ $this->getPageData('setup_auth_db_resource') ?: $this->getPageData('setup_config_db_resource')
+ );
+ } elseif ($page->getName() === 'setup_summary') {
+ $page->setSubjectTitle('Icinga Web 2');
+ $page->setSummary($this->getSetup()->getSummary());
+ } elseif ($page->getName() === 'setup_config_db_resource') {
+ $page->addDescription(mt(
+ 'setup',
+ 'Now please configure the database resource where to store user preferences.'
+ ));
+ $page->addDescription(mt(
+ 'setup',
+ 'Note that the database itself does not need to exist at this time as'
+ . ' it is going to be created once the wizard is about to be finished.'
+ ));
+
+ $ldapData = $this->getPageData('setup_ldap_resource');
+ if ($ldapData !== null && $request->getPost('name') === $ldapData['name']) {
+ $page->error(
+ mt('setup', 'The given resource name must be unique and is already in use by the LDAP resource')
+ );
+ }
+ } elseif ($page->getName() === 'setup_ldap_resource') {
+ $suggestion = $this->getPageData('setup_ldap_discovery');
+ if (isset($suggestion['resource'])) {
+ $page->populate($suggestion['resource']);
+ }
+
+ if ($this->getDirection() === static::FORWARD) {
+ $resourceConfig = $this->getPageData('setup_ldap_resource');
+ if ($resourceConfig !== null && $request->getPost('name') !== $resourceConfig['name']) {
+ $pageData = & $this->getPageData();
+ unset($pageData['setup_usergroup_backend']);
+ }
+ }
+ } elseif ($page->getName() === 'setup_authentication_type') {
+ $authData = $this->getPageData($page->getName());
+ $pageData = & $this->getPageData();
+
+ if ($authData !== null && $request->getPost('type') !== $authData['type']) {
+ // Drop any existing page data in case the authentication type has changed,
+ // otherwise it will conflict with other forms that depend on this one
+ unset($pageData['setup_admin_account']);
+ unset($pageData['setup_authentication_backend']);
+
+ if ($authData['type'] === 'db') {
+ unset($pageData['setup_auth_db_resource']);
+ unset($pageData['setup_auth_db_creation']);
+ } elseif ($request->getPost('type') === 'db') {
+ unset($pageData['setup_config_db_resource']);
+ unset($pageData['setup_config_db_creation']);
+ }
+ } elseif (isset($authData['type']) && $authData['type'] == 'external') {
+ // If you choose the authentication type external and validate the database and then come
+ // back to change the authentication type but do not change it, you will get an database configuration
+ // related error message on the next page. To avoid this error, the 'setup_config_db_resource'
+ // page must be unset.
+
+ unset($pageData['setup_config_db_resource']);
+ }
+ }
+ }
+
+ /**
+ * Return the new page to set as current page
+ *
+ * {@inheritdoc} Runs additional checks related to some registered pages.
+ *
+ * @param string $requestedPage The name of the requested page
+ * @param Form $originPage The origin page
+ *
+ * @return Form The new page
+ *
+ * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet
+ */
+ protected function getNewPage($requestedPage, Form $originPage)
+ {
+ $skip = false;
+ $newPage = parent::getNewPage($requestedPage, $originPage);
+ if ($newPage->getName() === 'setup_auth_db_resource') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] !== 'db';
+ } elseif ($newPage->getname() === 'setup_ldap_discovery') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] !== 'ldap';
+ /*} elseif ($newPage->getName() === 'setup_ldap_discovery_confirm') {
+ $skip = false === $this->hasPageData('setup_ldap_discovery');*/
+ } elseif ($newPage->getName() === 'setup_ldap_resource') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] !== 'ldap';
+ } elseif ($newPage->getName() === 'setup_usergroup_backend') {
+ $backendConfig = $this->getPageData('setup_authentication_backend');
+ $skip = $backendConfig['backend'] !== 'ldap';
+ } elseif ($newPage->getName() === 'setup_config_db_resource') {
+ $authData = $this->getPageData('setup_authentication_type');
+ $skip = $authData['type'] === 'db';
+ } elseif (in_array($newPage->getName(), array('setup_auth_db_creation', 'setup_config_db_creation'))) {
+ if (($newPage->getName() === 'setup_auth_db_creation' || $this->hasPageData('setup_config_db_resource'))
+ && (($config = $this->getPageData('setup_auth_db_resource')) !== null
+ || ($config = $this->getPageData('setup_config_db_resource')) !== null)
+ && !$config['skip_validation'] && $this->getDirection() == static::FORWARD
+ ) {
+ // Execute this code only if the direction is forward.
+ // Otherwise, an error will be output when you go back.
+ $db = new DbTool($config);
+
+ try {
+ $db->connectToDb(); // Are we able to login on the database?
+
+ if (array_search(reset($this->databaseTables), $db->listTables(), true) === false) {
+ // In case the database schema does not yet exist the
+ // user needs the privileges to setup the database
+ $skip = $db->checkPrivileges($this->databaseSetupPrivileges, $this->databaseTables);
+ } else {
+ // In case the database schema exists the user needs the required privileges
+ // to operate the database, if those are missing we ask for another user
+ $skip = $db->checkPrivileges($this->databaseUsagePrivileges, $this->databaseTables);
+ }
+ } catch (PDOException $_) {
+ try {
+ $db->connectToHost(); // Are we able to login on the server?
+ // It is not possible to reliably determine whether a database exists or not if a user can't
+ // log in to the database, so we just require the user to be able to create the database
+ $skip = $db->checkPrivileges(
+ array_unique(
+ array_merge($this->databaseCreationPrivileges, $this->databaseSetupPrivileges)
+ ),
+ $this->databaseTables
+ );
+ } catch (PDOException $_) {
+ // We are NOT able to login on the server..
+ }
+ }
+ } else {
+ $skip = true;
+ }
+ }
+
+ return $skip ? $this->skipPage($newPage) : $newPage;
+ }
+
+ /**
+ * Add buttons to the given page based on its position in the page-chain
+ *
+ * @param Form $page The page to add the buttons to
+ */
+ protected function addButtons(Form $page)
+ {
+ parent::addButtons($page);
+
+ $pages = $this->getPages();
+ $index = array_search($page, $pages, true);
+ if ($index === 0) {
+ $page->getElement(static::BTN_NEXT)->setLabel(
+ mt('setup', 'Start', 'setup.welcome.btn.next')
+ );
+ } elseif ($index === count($pages) - 1) {
+ $page->getElement(static::BTN_NEXT)->setLabel(
+ mt('setup', 'Setup Icinga Web 2', 'setup.summary.btn.finish')
+ );
+ }
+
+ $authData = $this->getPageData('setup_authentication_type');
+ $veto = $page->getName() === 'setup_authentication_backend' && $authData['type'] === 'db';
+ if (! $veto && in_array($page->getName(), array(
+ 'setup_authentication_backend',
+ 'setup_auth_db_resource',
+ 'setup_config_db_resource',
+ 'setup_ldap_resource',
+ 'setup_monitoring_ido',
+ 'setup_icingadb_resource',
+ 'setup_icingadb_redis',
+ 'setup_icingadb_api_transport'
+ ))) {
+ $page->addElement(
+ 'submit',
+ 'backend_validation',
+ array(
+ 'ignore' => true,
+ 'label' => t('Validate Configuration'),
+ 'data-progress-label' => t('Validation In Progress'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->getDisplayGroup('buttons')->addElement($page->getElement('backend_validation'));
+ }
+
+ if ($page->getName() === 'setup_command_transport') {
+ if ($page->getSubForm('transport_form')->getSubForm('transport_form') instanceof ApiTransportForm) {
+ $page->addElement(
+ 'submit',
+ 'transport_validation',
+ array(
+ 'ignore' => true,
+ 'label' => t('Validate Configuration'),
+ 'data-progress-label' => t('Validation In Progress'),
+ 'decorators' => array('ViewHelper'),
+ 'formnovalidate' => 'formnovalidate'
+ )
+ );
+ $page->getDisplayGroup('buttons')->addElement($page->getElement('transport_validation'));
+ }
+ }
+ }
+
+ /**
+ * Clear the session being used by this wizard
+ *
+ * @param bool $removeToken If true, the setup token will be removed
+ */
+ public function clearSession($removeToken = true)
+ {
+ parent::clearSession();
+
+ if ($removeToken) {
+ $tokenPath = Config::resolvePath('setup.token');
+ if (file_exists($tokenPath)) {
+ @unlink($tokenPath);
+ }
+ }
+ }
+
+ /**
+ * Return the setup for this wizard
+ *
+ * @return Setup
+ */
+ public function getSetup()
+ {
+ $pageData = $this->getPageData();
+ $setup = new Setup();
+
+ if (isset($pageData['setup_auth_db_resource'])
+ && !$pageData['setup_auth_db_resource']['skip_validation']
+ && (! isset($pageData['setup_auth_db_creation'])
+ || !$pageData['setup_auth_db_creation']['skip_validation']
+ )
+ ) {
+ $setup->addStep(
+ new DatabaseStep(array(
+ 'tables' => $this->databaseTables,
+ 'privileges' => $this->databaseUsagePrivileges,
+ 'resourceConfig' => $pageData['setup_auth_db_resource'],
+ 'adminName' => isset($pageData['setup_auth_db_creation']['username'])
+ ? $pageData['setup_auth_db_creation']['username']
+ : null,
+ 'adminPassword' => isset($pageData['setup_auth_db_creation']['password'])
+ ? $pageData['setup_auth_db_creation']['password']
+ : null,
+ 'schemaPath' => Config::module('setup')
+ ->get('schema', 'path', Icinga::app()->getBaseDir('schema'))
+ ))
+ );
+ } elseif (isset($pageData['setup_config_db_resource'])
+ && !$pageData['setup_config_db_resource']['skip_validation']
+ && (! isset($pageData['setup_config_db_creation'])
+ || !$pageData['setup_config_db_creation']['skip_validation']
+ )
+ ) {
+ $setup->addStep(
+ new DatabaseStep(array(
+ 'tables' => $this->databaseTables,
+ 'privileges' => $this->databaseUsagePrivileges,
+ 'resourceConfig' => $pageData['setup_config_db_resource'],
+ 'adminName' => isset($pageData['setup_config_db_creation']['username'])
+ ? $pageData['setup_config_db_creation']['username']
+ : null,
+ 'adminPassword' => isset($pageData['setup_config_db_creation']['password'])
+ ? $pageData['setup_config_db_creation']['password']
+ : null,
+ 'schemaPath' => Config::module('setup')
+ ->get('schema', 'path', Icinga::app()->getBaseDir('schema'))
+ ))
+ );
+ }
+
+ $setup->addStep(
+ new GeneralConfigStep(array(
+ 'generalConfig' => $pageData['setup_general_config'],
+ 'resourceName' => isset($pageData['setup_auth_db_resource']['name'])
+ ? $pageData['setup_auth_db_resource']['name']
+ : (isset($pageData['setup_config_db_resource']['name'])
+ ? $pageData['setup_config_db_resource']['name']
+ : null
+ )
+ ))
+ );
+
+ $adminAccountType = $pageData['setup_admin_account']['user_type'];
+ if ($adminAccountType === 'user_group') {
+ $adminAccountData = array('groupname' => $pageData['setup_admin_account'][$adminAccountType]);
+ } else {
+ $adminAccountData = array('username' => $pageData['setup_admin_account'][$adminAccountType]);
+ if ($adminAccountType === 'new_user' && !$pageData['setup_auth_db_resource']['skip_validation']
+ && (! isset($pageData['setup_auth_db_creation'])
+ || !$pageData['setup_auth_db_creation']['skip_validation']
+ )
+ ) {
+ $adminAccountData['resourceConfig'] = $pageData['setup_auth_db_resource'];
+ $adminAccountData['password'] = $pageData['setup_admin_account']['new_user_password'];
+ }
+ }
+ $authType = $pageData['setup_authentication_type']['type'];
+ $setup->addStep(
+ new AuthenticationStep(array(
+ 'adminAccountData' => $adminAccountData,
+ 'backendConfig' => $pageData['setup_authentication_backend'],
+ 'resourceName' => $authType === 'db' ? $pageData['setup_auth_db_resource']['name'] : (
+ $authType === 'ldap' ? $pageData['setup_ldap_resource']['name'] : null
+ )
+ ))
+ );
+
+ if ($authType !== 'external') {
+ $setup->addStep(
+ new UserGroupStep(array(
+ 'backendConfig' => $pageData['setup_authentication_backend'],
+ 'groupConfig' => isset($pageData['setup_usergroup_backend'])
+ ? $pageData['setup_usergroup_backend']
+ : null,
+ 'resourceName' => $authType === 'db'
+ ? $pageData['setup_auth_db_resource']['name']
+ : $pageData['setup_ldap_resource']['name'],
+ 'resourceConfig' => $authType === 'db'
+ ? $pageData['setup_auth_db_resource']
+ : null,
+ 'username' => $authType === 'db'
+ ? $pageData['setup_admin_account'][$adminAccountType]
+ : null
+ ))
+ );
+ }
+
+ if (isset($pageData['setup_auth_db_resource'])
+ || isset($pageData['setup_config_db_resource'])
+ || isset($pageData['setup_ldap_resource'])
+ ) {
+ $setup->addStep(
+ new ResourceStep(array(
+ 'dbResourceConfig' => isset($pageData['setup_auth_db_resource'])
+ ? array_diff_key($pageData['setup_auth_db_resource'], array('skip_validation' => null))
+ : (isset($pageData['setup_config_db_resource'])
+ ? array_diff_key($pageData['setup_config_db_resource'], array('skip_validation' => null))
+ : null
+ ),
+ 'ldapResourceConfig' => isset($pageData['setup_ldap_resource'])
+ ? array_diff_key($pageData['setup_ldap_resource'], array('skip_validation' => null))
+ : null
+ ))
+ );
+ }
+
+ foreach ($this->getWizards() as $wizard) {
+ if ($wizard->isComplete()) {
+ $setup->addSteps($wizard->getSetup()->getSteps());
+ }
+ }
+
+ $setup->addStep(new EnableModuleStep(array_keys($this->getPage('setup_modules')->getCheckedModules())));
+
+ return $setup;
+ }
+
+ /**
+ * Return the requirements of this wizard
+ *
+ * @return RequirementSet
+ */
+ public function getRequirements($skipModules = false)
+ {
+ $set = new RequirementSet();
+
+ $set->add(new PhpVersionRequirement(array(
+ 'condition' => array('>=', '7.2'),
+ 'description' => sprintf(mt(
+ 'setup',
+ 'Running Icinga Web 2 requires PHP version %s.'
+ ), '7.2')
+ )));
+
+ $set->add(new OSRequirement(array(
+ 'optional' => true,
+ 'condition' => 'linux',
+ 'description' => mt(
+ 'setup',
+ 'Icinga Web 2 is developed for and tested on Linux. While we cannot'
+ . ' guarantee they will, other platforms may also perform as well.'
+ )
+ )));
+
+ $set->add(new WebLibraryRequirement(array(
+ 'condition' => ['icinga-php-library', '>=', '0.9.0'],
+ 'alias' => 'Icinga PHP library',
+ 'description' => mt(
+ 'setup',
+ 'The Icinga PHP library (IPL) is required for Icinga Web 2 and modules'
+ )
+ )));
+
+ $set->add(new WebLibraryRequirement(array(
+ 'condition' => ['icinga-php-thirdparty', '>=', '0.11.0'],
+ 'alias' => 'Icinga PHP Thirdparty',
+ 'description' => mt(
+ 'setup',
+ 'The Icinga PHP Thirdparty library is required for Icinga Web 2 and modules'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'OpenSSL',
+ 'description' => mt(
+ 'setup',
+ 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'XML',
+ 'description' => mt(
+ 'setup',
+ 'The XML module for PHP is required for Markdown and custom HTML annotations.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'JSON',
+ 'description' => mt(
+ 'setup',
+ 'The JSON module for PHP is required for various export functionalities as well as APIs.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'gettext',
+ 'description' => mt(
+ 'setup',
+ 'For message localization, the gettext module for PHP is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'INTL',
+ 'description' => mt(
+ 'setup',
+ 'For language, timezone and date/time format negotiation, the INTL module for PHP is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'condition' => 'DOM',
+ 'description' => mt(
+ 'setup',
+ 'For charts and exports of views and reports to PDF, the DOM module for PHP is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'LDAP',
+ 'description' => mt(
+ 'setup',
+ 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'mbstring',
+ 'description' => mt(
+ 'setup',
+ 'In case you want views being exported to PDF, you\'ll need the mbstring extension for PHP.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'GD',
+ 'description' => mt(
+ 'setup',
+ 'In case you want views being exported to PDF, you\'ll need the GD extension for PHP.'
+ )
+ )));
+
+ $set->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'Imagick',
+ 'description' => mt(
+ 'setup',
+ 'In case you want graphs being exported to PDF as well, you\'ll need the ImageMagick extension for PHP.'
+ )
+ )));
+
+ $dbSet = new RequirementSet(false, RequirementSet::MODE_OR);
+ $dbSet->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'pdo_mysql',
+ 'alias' => 'PDO-MySQL',
+ 'description' => mt(
+ 'setup',
+ 'To store users or preferences in a MySQL database the PDO-MySQL module for PHP is required.'
+ )
+ )));
+ $dbSet->add(new PhpModuleRequirement(array(
+ 'optional' => true,
+ 'condition' => 'pdo_pgsql',
+ 'alias' => 'PDO-PostgreSQL',
+ 'description' => mt(
+ 'setup',
+ 'To store users or preferences in a PostgreSQL database the PDO-PostgreSQL module for PHP is required.'
+ )
+ )));
+ $set->merge($dbSet);
+
+ $dbRequire = (new SetRequirement(array(
+ 'optional' => false,
+ 'condition' => $dbSet,
+ 'title' =>'Database',
+ 'alias' => 'PDO-MySQL OR PDO-PostgreSQL',
+ 'description' => mt(
+ 'setup',
+ 'A database is mandatory, therefore at least one module '
+ . 'PDO-MySQL or PDO-PostgreSQL for PHP is required.'
+ )
+ )));
+
+ $set->add($dbRequire);
+
+ $set->add(new ConfigDirectoryRequirement(array(
+ 'condition' => Icinga::app()->getStorageDir(),
+ 'title' => mt('setup', 'Read- and writable storage directory'),
+ 'description' => mt(
+ 'setup',
+ 'The Icinga Web 2 storage directory defaults to "/var/lib/icingaweb2", if' .
+ ' not explicitly set in the environment variable "ICINGAWEB_STORAGEDIR".'
+ )
+ )));
+
+ $set->add(new ConfigDirectoryRequirement(array(
+ 'condition' => Icinga::app()->getConfigDir(),
+ 'description' => mt(
+ 'setup',
+ 'The Icinga Web 2 configuration directory defaults to "/etc/icingaweb2", if' .
+ ' not explicitly set in the environment variable "ICINGAWEB_CONFIGDIR".'
+ )
+ )));
+
+ if (! $skipModules) {
+ foreach ($this->getWizards() as $wizard) {
+ $set->merge($wizard->getRequirements());
+ }
+ }
+
+ return $set;
+ }
+}
diff --git a/modules/setup/library/Setup/Webserver.php b/modules/setup/library/Setup/Webserver.php
new file mode 100644
index 0000000..2251ba3
--- /dev/null
+++ b/modules/setup/library/Setup/Webserver.php
@@ -0,0 +1,233 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup;
+
+use Icinga\Application\Icinga;
+use Icinga\Exception\ProgrammingError;
+
+/**
+ * Base class for generating webserver configuration
+ */
+abstract class Webserver
+{
+ /**
+ * Document root
+ *
+ * @var string
+ */
+ protected $documentRoot;
+
+ /**
+ * URL path of Icinga Web 2
+ *
+ * @var string
+ */
+ protected $urlPath = '/icingaweb2';
+
+ /**
+ * Path to Icinga Web 2's configuration files
+ *
+ * @var string
+ */
+ protected $configDir;
+
+ /**
+ * Address or path where to pass requests to FPM
+ *
+ * @var string
+ */
+ protected $fpmUri;
+
+ /**
+ * Enable to pass requests to FPM
+ *
+ * @var bool
+ */
+ protected $enableFpm = false;
+
+ /**
+ * Create instance by type name
+ *
+ * @param string $type
+ *
+ * @return WebServer
+ *
+ * @throws ProgrammingError
+ */
+ public static function createInstance($type)
+ {
+ $class = __NAMESPACE__ . '\\Webserver\\' . ucfirst($type);
+ if (class_exists($class)) {
+ return new $class();
+ }
+ throw new ProgrammingError('Class "%s" does not exist', $class);
+ }
+
+ /**
+ * Generate configuration
+ *
+ * @return string
+ */
+ public function generate()
+ {
+ $template = $this->getTemplate();
+
+ $searchTokens = array(
+ '{urlPath}',
+ '{documentRoot}',
+ '{aliasDocumentRoot}',
+ '{configDir}',
+ '{fpmUri}'
+ );
+ $replaceTokens = array(
+ $this->getUrlPath(),
+ $this->getDocumentRoot(),
+ preg_match('~/$~', $this->getUrlPath()) ? $this->getDocumentRoot() . '/' : $this->getDocumentRoot(),
+ $this->getConfigDir(),
+ $this->getFpmUri()
+ );
+ $template = str_replace($searchTokens, $replaceTokens, $template);
+ return $template;
+ }
+
+ /**
+ * Specific template
+ *
+ * @return string
+ */
+ abstract protected function getTemplate();
+
+ /**
+ * Set the URL path of Icinga Web 2
+ *
+ * @param string $urlPath
+ *
+ * @return $this
+ */
+ public function setUrlPath($urlPath)
+ {
+ $this->urlPath = '/' . ltrim(trim((string) $urlPath), '/');
+ return $this;
+ }
+
+ /**
+ * Get the URL path of Icinga Web 2
+ *
+ * @return string
+ */
+ public function getUrlPath()
+ {
+ return $this->urlPath;
+ }
+
+ /**
+ * Set the document root
+ *
+ * @param string $documentRoot
+ *
+ * @return $this
+ */
+ public function setDocumentRoot($documentRoot)
+ {
+ $this->documentRoot = trim((string) $documentRoot);
+ return $this;
+ }
+
+ /**
+ * Detect the document root
+ *
+ * @return string
+ */
+ public function detectDocumentRoot()
+ {
+ return Icinga::app()->getBaseDir('public');
+ }
+
+ /**
+ * Get the document root
+ *
+ * @return string
+ */
+ public function getDocumentRoot()
+ {
+ if ($this->documentRoot === null) {
+ $this->documentRoot = $this->detectDocumentRoot();
+ }
+ return $this->documentRoot;
+ }
+
+ /**
+ * Set the configuration directory
+ *
+ * @param string $configDir
+ *
+ * @return $this
+ */
+ public function setConfigDir($configDir)
+ {
+ $this->configDir = (string) $configDir;
+ return $this;
+ }
+
+ /**
+ * Get the configuration directory
+ *
+ * @return string
+ */
+ public function getConfigDir()
+ {
+ if ($this->configDir === null) {
+ return Icinga::app()->getConfigDir();
+ }
+ return $this->configDir;
+ }
+
+ /**
+ * Get whether FPM is enabled
+ *
+ * @return bool
+ */
+ public function getEnableFpm()
+ {
+ return $this->enableFpm;
+ }
+
+ /**
+ * Set FPM enabled
+ *
+ * @param bool $flag
+ *
+ * @return $this
+ */
+ public function setEnableFpm($flag)
+ {
+ $this->enableFpm = (bool) $flag;
+
+ return $this;
+ }
+
+ /**
+ * Get the address or path where to pass requests to FPM
+ *
+ * @return string
+ */
+ public function getFpmUri()
+ {
+ return $this->fpmUri;
+ }
+
+ /**
+ * Set the address or path where to pass requests to FPM
+ *
+ * @param string $uri
+ *
+ * @return $this
+ */
+ public function setFpmUri($uri)
+ {
+ $this->fpmUri = (string) $uri;
+
+ return $this;
+ }
+}
diff --git a/modules/setup/library/Setup/Webserver/Apache.php b/modules/setup/library/Setup/Webserver/Apache.php
new file mode 100644
index 0000000..fdb367f
--- /dev/null
+++ b/modules/setup/library/Setup/Webserver/Apache.php
@@ -0,0 +1,142 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Webserver;
+
+use Icinga\Module\Setup\Webserver;
+
+/**
+ * Generate Apache 2.x configuration
+ */
+class Apache extends Webserver
+{
+ protected $fpmUri = '127.0.0.1:9000';
+
+ protected function getTemplate()
+ {
+ if (! $this->enableFpm) {
+ return <<<'EOD'
+Alias {urlPath} "{aliasDocumentRoot}"
+
+# Remove comments if you want to use PHP FPM and your Apache version is older than 2.4
+#<IfVersion < 2.4>
+# # Forward PHP requests to FPM
+# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+# <LocationMatch "^{urlPath}/(.*\.php)$">
+# ProxyPassMatch "fcgi://{fpmUri}/{documentRoot}/$1"
+# </LocationMatch>
+#</IfVersion>
+
+<Directory "{documentRoot}">
+ Options SymLinksIfOwnerMatch
+ AllowOverride None
+
+ DirectoryIndex index.php
+
+ <IfModule mod_authz_core.c>
+ # Apache 2.4
+ <RequireAll>
+ Require all granted
+ </RequireAll>
+ </IfModule>
+
+ <IfModule !mod_authz_core.c>
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+ </IfModule>
+
+ SetEnv ICINGAWEB_CONFIGDIR "{configDir}"
+
+ EnableSendfile Off
+
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteBase {urlPath}/
+ RewriteCond %{REQUEST_FILENAME} -s [OR]
+ RewriteCond %{REQUEST_FILENAME} -l [OR]
+ RewriteCond %{REQUEST_FILENAME} -d
+ RewriteRule ^.*$ - [NC,L]
+ RewriteRule ^.*$ index.php [NC,L]
+ </IfModule>
+
+ <IfModule !mod_rewrite.c>
+ DirectoryIndex error_norewrite.html
+ ErrorDocument 404 {urlPath}/error_norewrite.html
+ </IfModule>
+
+# Remove comments if you want to use PHP FPM and your Apache version
+# is greater than or equal to 2.4
+# <IfVersion >= 2.4>
+# # Forward PHP requests to FPM
+# SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+# <FilesMatch "\.php$">
+# SetHandler "proxy:fcgi://{fpmUri}"
+# ErrorDocument 503 {urlPath}/error_unavailable.html
+# </FilesMatch>
+# </IfVersion>
+</Directory>
+EOD;
+ } else {
+ return <<<'EOD'
+Alias {urlPath} "{aliasDocumentRoot}"
+
+<IfVersion < 2.4>
+ # Forward PHP requests to FPM
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+ <LocationMatch "^{urlPath}/(.*\.php)$">
+ ProxyPassMatch "fcgi://{fpmUri}/{documentRoot}/$1"
+ </LocationMatch>
+</IfVersion>
+
+<Directory "{documentRoot}">
+ Options SymLinksIfOwnerMatch
+ AllowOverride None
+
+ DirectoryIndex index.php
+
+ <IfModule mod_authz_core.c>
+ # Apache 2.4
+ <RequireAll>
+ Require all granted
+ </RequireAll>
+ </IfModule>
+
+ <IfModule !mod_authz_core.c>
+ # Apache 2.2
+ Order allow,deny
+ Allow from all
+ </IfModule>
+
+ SetEnv ICINGAWEB_CONFIGDIR "{configDir}"
+
+ EnableSendfile Off
+
+ <IfModule mod_rewrite.c>
+ RewriteEngine on
+ RewriteBase {urlPath}/
+ RewriteCond %{REQUEST_FILENAME} -s [OR]
+ RewriteCond %{REQUEST_FILENAME} -l [OR]
+ RewriteCond %{REQUEST_FILENAME} -d
+ RewriteRule ^.*$ - [NC,L]
+ RewriteRule ^.*$ index.php [NC,L]
+ </IfModule>
+
+ <IfModule !mod_rewrite.c>
+ DirectoryIndex error_norewrite.html
+ ErrorDocument 404 {urlPath}/error_norewrite.html
+ </IfModule>
+
+ <IfVersion >= 2.4>
+ # Forward PHP requests to FPM
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+ <FilesMatch "\.php$">
+ SetHandler "proxy:fcgi://{fpmUri}"
+ ErrorDocument 503 {urlPath}/error_unavailable.html
+ </FilesMatch>
+ </IfVersion>
+</Directory>
+EOD;
+ }
+ }
+}
diff --git a/modules/setup/library/Setup/Webserver/Nginx.php b/modules/setup/library/Setup/Webserver/Nginx.php
new file mode 100644
index 0000000..c7ae716
--- /dev/null
+++ b/modules/setup/library/Setup/Webserver/Nginx.php
@@ -0,0 +1,36 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Setup\Webserver;
+
+use Icinga\Module\Setup\Webserver;
+
+/**
+ * Generate nginx configuration
+ */
+class Nginx extends Webserver
+{
+ protected $fpmUri = '127.0.0.1:9000';
+
+ protected $enableFpm = true;
+
+ protected function getTemplate()
+ {
+ return <<<'EOD'
+location ~ ^{urlPath}/index\.php(.*)$ {
+ fastcgi_pass {fpmUri};
+ fastcgi_index index.php;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME {documentRoot}/index.php;
+ fastcgi_param ICINGAWEB_CONFIGDIR {configDir};
+ fastcgi_param REMOTE_USER $remote_user;
+}
+
+location ~ ^{urlPath}(.+)? {
+ alias {documentRoot};
+ index index.php;
+ try_files $1 $uri $uri/ {urlPath}/index.php$is_args$args;
+}
+EOD;
+ }
+}
diff --git a/modules/setup/module.info b/modules/setup/module.info
new file mode 100644
index 0000000..e3570bd
--- /dev/null
+++ b/modules/setup/module.info
@@ -0,0 +1,6 @@
+Module: setup
+Version: 2.11.4
+Description: Setup module
+ Web based wizard for setting up Icinga Web 2 and its modules.
+ This includes the data backends (e.g. relational database, LDAP),
+ the authentication method, where to store the user preferences and much more.
diff --git a/modules/translation/application/clicommands/CompileCommand.php b/modules/translation/application/clicommands/CompileCommand.php
new file mode 100644
index 0000000..8408009
--- /dev/null
+++ b/modules/translation/application/clicommands/CompileCommand.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Translation\Clicommands;
+
+use Icinga\Module\Translation\Cli\TranslationCommand;
+
+/**
+ * Translation compiler
+ *
+ * This command will compile gettext catalogs of modules.
+ *
+ * Once a catalog is compiled its content is used by Icinga Web 2 to display
+ * messages in the configured language.
+ */
+class CompileCommand extends TranslationCommand
+{
+ /**
+ * Compile a module gettext catalog
+ *
+ * This will compile the catalog of the given module and locale.
+ *
+ * USAGE:
+ *
+ * icingacli translation compile <module> <locale>
+ *
+ * EXAMPLES:
+ *
+ * icingacli translation compile demo de_DE
+ * icingacli translation compile demo fr_FR
+ */
+ public function moduleAction()
+ {
+ $module = $this->validateModuleName($this->params->shift());
+ $locale = $this->validateLocaleCode($this->params->shift());
+
+ $helper = $this->getTranslationHelper($locale);
+ $helper->compileModuleTranslation($module);
+ }
+}
diff --git a/modules/translation/application/clicommands/RefreshCommand.php b/modules/translation/application/clicommands/RefreshCommand.php
new file mode 100644
index 0000000..b4b2dc0
--- /dev/null
+++ b/modules/translation/application/clicommands/RefreshCommand.php
@@ -0,0 +1,40 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Translation\Clicommands;
+
+use Icinga\Module\Translation\Cli\TranslationCommand;
+
+/**
+ * Translation updater
+ *
+ * This command will create a new or update any existing gettext catalog of a module.
+ *
+ * Once a catalog has been created/updated one can open it with a editor for
+ * PO-files and start with the actual translation.
+ */
+class RefreshCommand extends TranslationCommand
+{
+ /**
+ * Generate or update a module gettext catalog
+ *
+ * This will create/update the PO-file of the given module and locale.
+ *
+ * USAGE:
+ *
+ * icingacli translation refresh module <module> <locale>
+ *
+ * EXAMPLES:
+ *
+ * icingacli translation refresh module demo de_DE
+ * icingacli translation refresh module demo fr_FR
+ */
+ public function moduleAction()
+ {
+ $module = $this->validateModuleName($this->params->shift());
+ $locale = $this->validateLocaleCode($this->params->shift());
+
+ $helper = $this->getTranslationHelper($locale);
+ $helper->updateModuleTranslations($module);
+ }
+}
diff --git a/modules/translation/application/clicommands/TestCommand.php b/modules/translation/application/clicommands/TestCommand.php
new file mode 100644
index 0000000..347c2c9
--- /dev/null
+++ b/modules/translation/application/clicommands/TestCommand.php
@@ -0,0 +1,140 @@
+<?php
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Translation\Clicommands;
+
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Translation\Cli\ArrayToTextTableHelper;
+use Icinga\Module\Translation\Cli\TranslationCommand;
+use ipl\I18n\GettextTranslator;
+use ipl\I18n\StaticTranslator;
+
+/**
+ * Timestamp test helper
+ *
+ *
+ */
+class TestCommand extends TranslationCommand
+{
+ protected $locales = array();
+
+ /**
+ * Get translation examples for DateFormatter
+ *
+ * To help you check if the values got translated correctly
+ *
+ * USAGE:
+ *
+ * icingacli translation test dateformatter <locale>
+ *
+ * EXAMPLES:
+ *
+ * icingacli translation test dateformatter de_DE
+ * icingacli translation test dateformatter fr_FR
+ */
+ public function dateformatterAction()
+ {
+ $time = time();
+
+ /** @uses DateFormatter::timeAgo */
+ $this->printTable($this->getMultiTranslated(
+ 'Time Ago',
+ array('Icinga\Date\DateFormatter', 'timeAgo'),
+ array(
+ "15 sec" => $time - 15,
+ "62 sec" => $time - 62,
+ "10 min" => $time - 600,
+ "1h" => $time - 1 * 3600,
+ "3h" => $time - 3 * 3600,
+ "25h" => $time - 25 * 3600,
+ "31d" => $time - 31 * 24 * 3600,
+ )
+ ));
+
+ $this->printTable($this->getMultiTranslated(
+ 'Time Since',
+ array('Icinga\Date\DateFormatter', 'timeSince'),
+ array(
+ "15 sec" => $time - 15,
+ "62 sec" => $time - 62,
+ "10 min" => $time - 600,
+ "1h" => $time - 1 * 3600,
+ "3h" => $time - 3 * 3600,
+ "25h" => $time - 25 * 3600,
+ "31d" => $time - 31 * 24 * 3600,
+ )
+ ));
+
+ $this->printTable($this->getMultiTranslated(
+ 'Time Until',
+ array('Icinga\Date\DateFormatter', 'timeUntil'),
+ array(
+ "15 sec" => $time + 15,
+ "62 sec" => $time + 62,
+ "10 min" => $time + 600,
+ "1h" => $time + 1 * 3600,
+ "3h" => $time + 3 * 3600,
+ "25h" => $time + 25 * 3600,
+ "31d" => $time + 31 * 24 * 3600,
+ )
+ ));
+ }
+
+ public function defaultAction()
+ {
+ $this->dateformatterAction();
+ }
+
+ public function init()
+ {
+ foreach ($this->params->getAllStandalone() as $l) {
+ $this->locales[] = $l;
+ }
+
+ if (empty($this->locales)) {
+ /** @var GettextTranslator $translator */
+ $translator = StaticTranslator::$instance;
+ $this->locales = $translator->listLocales();
+ }
+ }
+
+ protected function callTranslated($callback, $arguments, $locale = 'en_US')
+ {
+ /** @var GettextTranslator $translator */
+ $translator = StaticTranslator::$instance;
+ $translator->setLocale($locale);
+ return call_user_func_array($callback, $arguments);
+ }
+
+ protected function getMultiTranslated($name, $callback, $arguments, $locales = null)
+ {
+ if ($locales === null) {
+ $locales = $this->locales;
+ }
+ array_unshift($locales, 'C');
+
+ $rows = array();
+
+ foreach ($arguments as $k => $args) {
+ $row = array($name => $k);
+
+ if (! is_array($args)) {
+ $args = array($args);
+ }
+ foreach ($locales as $locale) {
+ $row[$locale] = $this->callTranslated($callback, $args, $locale);
+ }
+ $rows[] = $row;
+ }
+
+ return $rows;
+ }
+
+ protected function printTable($rows)
+ {
+ $tt = new ArrayToTextTableHelper($rows);
+ $tt->showHeaders(true);
+ $tt->render();
+ echo "\n\n";
+ }
+}
diff --git a/modules/translation/doc/01-About.md b/modules/translation/doc/01-About.md
new file mode 100644
index 0000000..2eaacfa
--- /dev/null
+++ b/modules/translation/doc/01-About.md
@@ -0,0 +1,6 @@
+# About the Translation Module <a id="translation-module-about"></a>
+
+Please read the following chapters for more insights on this module:
+
+* [Installation](02-Installation.md#translation-module-installation)
+* [Translations](03-Translation.md#module-translation-introduction)
diff --git a/modules/translation/doc/02-Installation.md b/modules/translation/doc/02-Installation.md
new file mode 100644
index 0000000..04f85c8
--- /dev/null
+++ b/modules/translation/doc/02-Installation.md
@@ -0,0 +1,15 @@
+# Translation Module Installation <a id="translation-module-installation"></a>
+
+This module is provided with the Icinga Web 2 package and does
+not need any extra installation step.
+
+## Enable the Module <a id="translation-module-enable"></a>
+
+Navigate to `Configuration` -> `Modules` -> `translation` and enable
+the module.
+
+You can also enable the module during the setup wizard, or on the CLI:
+
+```
+icingacli module enable translation
+```
diff --git a/modules/translation/doc/03-Translation.md b/modules/translation/doc/03-Translation.md
new file mode 100644
index 0000000..14e2e88
--- /dev/null
+++ b/modules/translation/doc/03-Translation.md
@@ -0,0 +1,204 @@
+# Introduction <a id="module-translation-introduction"></a>
+
+Icinga Web 2 provides localization out of the box - for itself and the core modules.
+This module is for third party module developers to aid them to localize their work.
+
+The chapters [Translation for Developers](03-Translation.md#module-translation-developers),
+[Translation for Translators](03-Translation.md#module-translation-translators) and
+[Testing Translations](03-Translation.md#module-translation-tests) will introduce and
+explain you, how to take part on localizing modules to different languages.
+
+## Translation for Developers <a id="module-translation-developers"></a>
+
+To make use of the built-in translations in your module's code or views, you should use the method
+`$this->translate('String to be translated')`, let's have a look at an example:
+
+```php
+<?php
+
+class ExampleController extends Controller
+{
+ public function indexAction()
+ {
+ $this->view->title = $this->translate('Hello World');
+ }
+}
+```
+
+So if there a translation available for the `Hello World` string you will get an translated output, depends on the
+language which is set in your configuration as the default language, if it is `de_DE` the output would be
+`Hallo Welt`.
+
+The same works also for views:
+
+```
+<h1><?= $this->title ?></h1>
+<p>
+ <?= $this->translate('Hello World') ?>
+ <?= $this->translate('String to be translated') ?>
+</p>
+```
+
+If you need to provide placeholders in your messages, you should wrap the `$this->translate()` with `sprintf()` for e.g.
+ sprintf($this->translate('Hello User: (%s)'), $user->getName())
+
+### Translating plural forms <a id="module-translation-plural-forms"></a>
+
+To provide a plural translation, just use the `translatePlural()` function.
+
+```php
+<?php
+
+class ExampleController extends Controller
+{
+ public function indexAction()
+ {
+ $this->view->message = $this->translatePlural('Service', 'Services', 3);
+ }
+}
+```
+
+### Context based translation <a id="module-translation-context-based"></a>
+
+If you want to provide context based translations, you can easily do it with an extra parameter in both methods
+`translate()` and `translatePlural()`.
+
+```php
+<?php
+
+class ExampleController extends Controller
+{
+ public function indexAction()
+ {
+ $this->view->title = $this->translate('My Title', 'mycontext');
+ $this->view->message = $this->translatePlural('Service', 'Services', 3, 'mycontext');
+ }
+}
+```
+
+## Translation for Translators <a id="module-translation-translators"></a>
+
+> **Note**:
+>
+> If you want to translate Icinga Web 2 or any module made by Icinga, please head over to
+> [translate.icinga.com](https://translate.icinga.com) instead. We won't accept any contributions
+> in this regard other than those made there.
+
+Icinga Web 2 internally uses the UNIX standard gettext tool to perform internationalization, this means translation
+files in the .po file format are supplied for text strings used in the code.
+
+There are a lot of tools and techniques to work with .po localization files, you can choose what ever you prefer. We
+won't let you alone on your first steps and therefore we'll introduce you a nice tool, called Poedit.
+
+### Poedit <a id="module-translation-translators-poedit"></a>
+
+First of all, you have to download and install [Poedit](http://poedit.net).
+When you are done, you have to configure Poedit.
+
+#### Configuration <a id="module-translation-translators-poedit-configuration"></a>
+
+`Personalize`: Please provide your Name and E-Mail under Identity.
+
+![Personalize](img/poedit_001.png)
+
+`Editor`: Under the `Behavior` the Automatically compile .mo files on save, should be disabled.
+
+![Editor](img/poedit_002.png)
+
+`Translations Memory`: Under the `Database` please add your languages, for which are you writing translations.
+
+![Translations Memory](img/poedit_003.png)
+
+When you are done, just save your new settings.
+
+#### Editing .po files <a id="module-translation-translators-poedit-edit-po-files"></a>
+
+> **Note**
+>
+> ll_CC stands for ll=language and CC=country code for e.g `de_DE`, `fr_FR`, `ru_RU`, `it_IT` etc.
+
+To work with .po files, open or create the one for your language located under
+`application/locale/ll_CC/LC_MESSAGES/yourmodule.po`. As shown below, you will
+get then a full list of all available translation strings for the module. Each
+module names its translation files `%module_name%.po`.
+
+![Full list of strings](img/poedit_004.png)
+
+Now you can make changes and when there is no translation available, Poedit would mark it with a blue color, as shown
+below.
+
+![Untranslated strings](img/poedit_005.png)
+
+And when you want to test your changes, please read more about under the chapter
+[Testing Translations](Testing Translations).
+
+## Testing Translations <a id="module-translation-tests"></a>
+
+If you want to try out your translation changes in Icinga Web 2, you can make use of the CLI translations commands.
+
+> **Note**:
+>
+> Please make sure that the gettext package is installed
+
+To get an easier development with translations, you can activate the `translation module` which provides CLI commands,
+after that you would be able to refresh and compile your .po files.
+
+Let's assume, we want to provide German translations for our just new created module `yourmodule`.
+
+If we haven't yet any translations strings in our .po file or even the .po file, we can use the CLI command, to do the
+job for us:
+
+```
+icingacli translation refresh module yourmodule de_DE
+```
+
+This will go through all .php and .phtml files inside the module and a look after `$this->translate()` if there is
+something to translate - if there is something and is not available in the `yourmodule.po` it will update this file
+for us with new strings.
+
+Now you can open the `application/locale/de_DE/LC_MESSAGES/yourmodule.po` and you will see something similar:
+
+```
+# Icinga Web 2 - Head for multiple monitoring backends.
+# Copyright (C) 2014 Icinga Development Team
+# This file is distributed under the same license as Development Module.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Development Module (0.0.1)\n"
+"Report-Msgid-Bugs-To: dev@icinga.com\n"
+"POT-Creation-Date: 2014-09-09 10:12+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language: ll_CC\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: /modules/yourmodule/configuration.php:6
+msgid "yourmodule"
+msgstr ""
+```
+
+Great, now you can adjust the file and provide the German `msgstr` for `yourmodule`.
+
+```
+#: /modules/yourmodule/configuration.php:6
+msgid "Dummy"
+msgstr "Attrappe"
+```
+
+The last step is to compile the `yourmodule.po` to the `yourmodule.mo`:
+
+```
+icingacli translation compile module yourmodule de_DE
+```
+
+> **Note**
+>
+> After compiling it you need to restart the web server to get new translations available in your module.
+
+At this moment, everywhere in the module where the `Dummy` should be translated, it would return the translated
+string `Attrappe`.
diff --git a/modules/translation/doc/img/poedit_001.png b/modules/translation/doc/img/poedit_001.png
new file mode 100644
index 0000000..2d07b8e
--- /dev/null
+++ b/modules/translation/doc/img/poedit_001.png
Binary files differ
diff --git a/modules/translation/doc/img/poedit_002.png b/modules/translation/doc/img/poedit_002.png
new file mode 100644
index 0000000..d31e5ba
--- /dev/null
+++ b/modules/translation/doc/img/poedit_002.png
Binary files differ
diff --git a/modules/translation/doc/img/poedit_003.png b/modules/translation/doc/img/poedit_003.png
new file mode 100644
index 0000000..5f285f9
--- /dev/null
+++ b/modules/translation/doc/img/poedit_003.png
Binary files differ
diff --git a/modules/translation/doc/img/poedit_004.png b/modules/translation/doc/img/poedit_004.png
new file mode 100644
index 0000000..2c85dd9
--- /dev/null
+++ b/modules/translation/doc/img/poedit_004.png
Binary files differ
diff --git a/modules/translation/doc/img/poedit_005.png b/modules/translation/doc/img/poedit_005.png
new file mode 100644
index 0000000..3ae59ba
--- /dev/null
+++ b/modules/translation/doc/img/poedit_005.png
Binary files differ
diff --git a/modules/translation/library/Translation/Cli/ArrayToTextTableHelper.php b/modules/translation/library/Translation/Cli/ArrayToTextTableHelper.php
new file mode 100644
index 0000000..af01d5f
--- /dev/null
+++ b/modules/translation/library/Translation/Cli/ArrayToTextTableHelper.php
@@ -0,0 +1,232 @@
+<?php
+
+namespace Icinga\Module\Translation\Cli;
+
+/**
+ * Array to Text Table Generation Class
+ *
+ * @author Tony Landis <tony@tonylandis.com>
+ * @link http://www.tonylandis.com/
+ * @copyright Copyright (C) 2006-2009 Tony Landis
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ */
+class ArrayToTextTableHelper
+{
+ /**
+ * @var array The array for processing
+ */
+ protected $rows;
+
+ /**
+ * @var int The column width settings
+ */
+ protected $cs = array();
+
+ /**
+ * @var int The Row lines settings
+ */
+ protected $rs = array();
+
+ /**
+ * @var int The Column index of keys
+ */
+ protected $keys = array();
+
+ /**
+ * @var int Max Column Height (returns)
+ */
+ protected $mH = 2;
+
+ /**
+ * @var int Max Row Width (chars)
+ */
+ protected $mW = 30;
+
+ protected $head = false;
+ protected $pcen = "+";
+ protected $prow = "-";
+ protected $pcol = "|";
+
+
+ /**
+ * Prepare array into textual format
+ *
+ * @param array $rows The input array
+ * @param bool $head Show heading
+ * @param int $maxWidth Max Column Height (returns)
+ * @param int $maxHeight Max Row Width (chars)
+ */
+ public function __construct($rows)
+ {
+ $this->rows =& $rows;
+ $this->cs = array();
+ $this->rs = array();
+
+ if (! $xc = count($this->rows)) {
+ return false;
+ }
+
+ $this->keys = array_keys($this->rows[0]);
+ $columns = count($this->keys);
+
+ for ($x = 0; $x < $xc; $x++) {
+ for ($y = 0; $y < $columns; $y++) {
+ $this->setMax($x, $y, $this->rows[$x][$this->keys[$y]]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Show the headers using the key values of the array for the titles
+ *
+ * @param bool $bool
+ */
+ public function showHeaders($bool)
+ {
+ if ($bool) {
+ $this->setHeading();
+ }
+ }
+
+ /**
+ * Set the maximum width (number of characters) per column before truncating
+ *
+ * @param int $maxWidth
+ */
+ public function setMaxWidth($maxWidth)
+ {
+ $this->mW = (int) $maxWidth;
+ }
+
+ /**
+ * Set the maximum height (number of lines) per row before truncating
+ *
+ * @param int $maxHeight
+ */
+ public function setMaxHeight($maxHeight)
+ {
+ $this->mH = (int) $maxHeight;
+ }
+
+ /**
+ * Prints the data to a text table
+ *
+ * @param bool $return Set to 'true' to return text rather than printing
+ *
+ * @return mixed
+ */
+ public function render($return = false)
+ {
+ if ($return) {
+ ob_start(null, 0, true);
+ }
+
+ $this->printLine();
+ $this->printHeading();
+
+ $rc = count($this->rows);
+ for ($i = 0; $i < $rc; $i++) {
+ $this->printRow($i);
+ }
+
+ $this->printLine(false);
+
+ if ($return) {
+ $contents = ob_get_contents();
+ ob_end_clean();
+ return $contents;
+ }
+ return null;
+ }
+
+ protected function setHeading()
+ {
+ $data = array();
+ foreach ($this->keys as $colKey => $value) {
+ $this->setMax(false, $colKey, $value);
+ $data[$colKey] = strtoupper($value);
+ }
+ if (! is_array($data)) {
+ return false;
+ }
+ $this->head = $data;
+
+ return $this;
+ }
+
+ protected function printLine($nl = true)
+ {
+ print $this->pcen;
+ foreach ($this->cs as $key => $val) {
+ print $this->prow .
+ str_pad('', $val, $this->prow, STR_PAD_RIGHT) .
+ $this->prow .
+ $this->pcen;
+ }
+ if ($nl) {
+ print "\n";
+ }
+ }
+
+ protected function printHeading()
+ {
+ if (! is_array($this->head)) {
+ return false;
+ }
+
+ print $this->pcol;
+ foreach ($this->cs as $key => $val) {
+ print ' ' .
+ str_pad($this->head[$key], $val, ' ', STR_PAD_BOTH) .
+ ' ' .
+ $this->pcol;
+ }
+
+ print "\n";
+ $this->printLine();
+
+ return $this;
+ }
+
+ protected function printRow($rowKey)
+ {
+ // loop through each line
+ for ($line = 1; $line <= $this->rs[$rowKey]; $line++) {
+ print $this->pcol;
+ for ($colKey = 0; $colKey < count($this->keys); $colKey++) {
+ print " ";
+ print str_pad(
+ substr($this->rows[$rowKey][$this->keys[$colKey]], ($this->mW * ($line - 1)), $this->mW),
+ $this->cs[$colKey],
+ ' ',
+ STR_PAD_RIGHT
+ );
+ print " " . $this->pcol;
+ }
+ print "\n";
+ }
+ }
+
+ protected function setMax($rowKey, $colKey, &$colVal)
+ {
+ $w = mb_strlen($colVal);
+ $h = 1;
+ if ($w > $this->mW) {
+ $h = ceil($w % $this->mW);
+ if ($h > $this->mH) {
+ $h = $this->mH;
+ }
+ $w = $this->mW;
+ }
+
+ if (! isset($this->cs[$colKey]) || $this->cs[$colKey] < $w) {
+ $this->cs[$colKey] = $w;
+ }
+
+ if ($rowKey !== false && (! isset($this->rs[$rowKey]) || $this->rs[$rowKey] < $h)) {
+ $this->rs[$rowKey] = $h;
+ }
+ }
+}
diff --git a/modules/translation/library/Translation/Cli/TranslationCommand.php b/modules/translation/library/Translation/Cli/TranslationCommand.php
new file mode 100644
index 0000000..af3582c
--- /dev/null
+++ b/modules/translation/library/Translation/Cli/TranslationCommand.php
@@ -0,0 +1,73 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Translation\Cli;
+
+use Exception;
+use Icinga\Cli\Command;
+use Icinga\Exception\IcingaException;
+use Icinga\Module\Translation\Util\GettextTranslationHelper;
+
+/**
+ * Base class for translation commands
+ */
+class TranslationCommand extends Command
+{
+ /**
+ * Get the gettext translation helper
+ *
+ * @param string $locale
+ *
+ * @return GettextTranslationHelper
+ */
+ public function getTranslationHelper($locale)
+ {
+ $helper = new GettextTranslationHelper($this->app, $locale);
+ $helper->setConfig($this->Config());
+ return $helper;
+ }
+
+ /**
+ * Check whether the given locale code is valid
+ *
+ * @param string $code The locale code to validate
+ *
+ * @return string The validated locale code
+ *
+ * @throws Exception In case the locale code is invalid
+ */
+ public function validateLocaleCode($code)
+ {
+ if (! preg_match('@[a-z]{2}_[A-Z]{2}@', $code)) {
+ throw new IcingaException(
+ 'Locale code \'%s\' is not valid. Expected format is: ll_CC',
+ $code
+ );
+ }
+
+ return $code;
+ }
+
+ /**
+ * Check whether the given module is available and enabled
+ *
+ * @param string $name The module name to validate
+ *
+ * @return string The validated module name
+ *
+ * @throws Exception In case the given module is not available or not enabled
+ */
+ public function validateModuleName($name)
+ {
+ $enabledModules = $this->app->getModuleManager()->listEnabledModules();
+
+ if (! in_array($name, $enabledModules)) {
+ throw new IcingaException(
+ 'Module with name \'%s\' not found or is not enabled',
+ $name
+ );
+ }
+
+ return $name;
+ }
+}
diff --git a/modules/translation/library/Translation/Util/GettextTranslationHelper.php b/modules/translation/library/Translation/Util/GettextTranslationHelper.php
new file mode 100644
index 0000000..d1e6ac2
--- /dev/null
+++ b/modules/translation/library/Translation/Util/GettextTranslationHelper.php
@@ -0,0 +1,442 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Module\Translation\Util;
+
+use Exception;
+use Icinga\Application\ApplicationBootstrap;
+use Icinga\Application\Config;
+use Icinga\Application\Modules\Manager;
+use Icinga\Exception\IcingaException;
+use Icinga\Util\File;
+
+/**
+ * This class provides some useful utility functions to handle gettext translations
+ */
+class GettextTranslationHelper
+{
+ /**
+ * All project files are supposed to have the same/this encoding
+ */
+ const FILE_ENCODING = 'UTF-8';
+
+ /**
+ * Config
+ *
+ * @var Config
+ */
+ protected $config;
+
+ /**
+ * The source files to parse
+ *
+ * @var array
+ */
+ private $sourceExtensions = array(
+ 'php',
+ 'phtml'
+ );
+
+ /**
+ * The module manager of the application's bootstrap
+ *
+ * @var Manager
+ */
+ private $moduleMgr;
+
+ /**
+ * The current version of Icingaweb 2 or of the module the catalog is being created for
+ *
+ * @var string
+ */
+ private $version;
+
+ /**
+ * The name of the module if any
+ *
+ * @var string
+ */
+ private $moduleName;
+
+ /**
+ * The locale used by this helper
+ *
+ * @var string
+ */
+ private $locale;
+
+ /**
+ * The path to the module, if any
+ *
+ * @var string
+ */
+ private $moduleDir;
+
+ /**
+ * The path to the file catalog
+ *
+ * @var string
+ */
+ private $catalogPath;
+
+ /**
+ * The path to the *.pot file
+ *
+ * @var string
+ */
+ private $templatePath;
+
+ /**
+ * The path to the *.po file
+ *
+ * @var string
+ */
+ private $tablePath;
+
+ /**
+ * Create a new TranslationHelper object
+ *
+ * @param ApplicationBootstrap $bootstrap The application's bootstrap object
+ * @param string $locale The locale to be used by this helper
+ */
+ public function __construct(ApplicationBootstrap $bootstrap, $locale)
+ {
+ $this->moduleMgr = $bootstrap->getModuleManager();
+ $this->locale = $locale;
+ }
+
+ /**
+ * Cleanup temporary files
+ */
+ public function __destruct()
+ {
+ if ($this->catalogPath !== null && file_exists($this->catalogPath)) {
+ unlink($this->catalogPath);
+ }
+
+ if ($this->templatePath !== null && file_exists($this->templatePath)) {
+ unlink($this->templatePath);
+ }
+ }
+
+ /**
+ * Get the config
+ *
+ * @return Config
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Set the config
+ *
+ * @param Config $config
+ *
+ * @return $this
+ */
+ public function setConfig(Config $config)
+ {
+ $this->config = $config;
+ return $this;
+ }
+
+ /**
+ * Update the translation table for a particular module
+ *
+ * @param string $module The name of the module for which to update the translation table
+ */
+ public function updateModuleTranslations($module)
+ {
+ $this->catalogPath = tempnam(sys_get_temp_dir(), 'IcingaTranslation_');
+ $this->templatePath = tempnam(sys_get_temp_dir(), 'IcingaPot_');
+ $this->version = $this->moduleMgr->getModule($module)->getVersion();
+ $this->moduleName = $this->moduleMgr->getModule($module)->getName();
+
+ $this->moduleDir = $this->moduleMgr->getModuleDir($module);
+ $this->tablePath = implode(
+ DIRECTORY_SEPARATOR,
+ array(
+ $this->moduleDir,
+ 'application',
+ 'locale',
+ $this->locale,
+ 'LC_MESSAGES',
+ $module . '.po'
+ )
+ );
+
+ $this->createFileCatalog();
+ $this->createTemplateFile();
+ $this->updateTranslationTable();
+ }
+
+ /**
+ * Compile the translation table for a particular module
+ *
+ * @param string $module The name of the module for which to compile the translation table
+ */
+ public function compileModuleTranslation($module)
+ {
+ $this->moduleDir = $this->moduleMgr->getModuleDir($module);
+ $this->tablePath = implode(
+ DIRECTORY_SEPARATOR,
+ array(
+ $this->moduleDir,
+ 'application',
+ 'locale',
+ $this->locale,
+ 'LC_MESSAGES',
+ $module . '.po'
+ )
+ );
+
+ $this->compileTranslationTable();
+ }
+
+ /**
+ * Update any existing or create a new translation table using the gettext tools
+ *
+ * @throws Exception In case the translation table does not yet exist and cannot be created
+ */
+ private function updateTranslationTable()
+ {
+ if (is_file($this->tablePath)) {
+ shell_exec(sprintf(
+ '%s --update --backup=none %s %s 2>&1',
+ $this->getConfig()->get('translation', 'msgmerge', '/usr/bin/env msgmerge'),
+ $this->tablePath,
+ $this->templatePath
+ ));
+ } else {
+ if ((!is_dir(dirname($this->tablePath)) && !@mkdir(dirname($this->tablePath), 0755, true)) ||
+ !rename($this->templatePath, $this->tablePath)) {
+ throw new IcingaException(
+ 'Unable to create %s',
+ $this->tablePath
+ );
+ }
+ }
+ $this->updateHeader($this->tablePath);
+ $this->fixSourceLocations($this->tablePath);
+ }
+
+ /**
+ * Create the template file using the gettext tools
+ */
+ private function createTemplateFile()
+ {
+ shell_exec(
+ implode(
+ ' ',
+ array(
+ $this->getConfig()->get('translation', 'xgettext', '/usr/bin/env xgettext'),
+ '--language=PHP',
+ '--keyword=translate',
+ '--keyword=translate:1,2c',
+ '--keyword=translateInDomain:2',
+ '--keyword=translateInDomain:2,3c',
+ '--keyword=translatePlural:1,2',
+ '--keyword=translatePlural:1,2,4c',
+ '--keyword=translatePluralInDomain:2,3',
+ '--keyword=translatePluralInDomain:2,3,5c',
+ '--keyword=mt:2',
+ '--keyword=mt:2,3c',
+ '--keyword=mtp:2,3',
+ '--keyword=mtp:2,3,5c',
+ '--keyword=t',
+ '--keyword=t:1,2c',
+ '--keyword=tp:1,2',
+ '--keyword=tp:1,2,4c',
+ '--keyword=N_',
+ '--sort-output',
+ '--force-po',
+ '--omit-header',
+ '--from-code=' . self::FILE_ENCODING,
+ '--files-from="' . $this->catalogPath . '"',
+ '--output="' . $this->templatePath . '"'
+ )
+ )
+ );
+ }
+
+ /**
+ * Create or update a gettext conformant header in the given file
+ *
+ * @param string $path The path to the file
+ */
+ private function updateHeader($path)
+ {
+ $headerInfo = array(
+ 'title' => $this->moduleMgr->getModule($this->moduleName)->getTitle(),
+ 'copyright_holder' => 'TEAM NAME',
+ 'copyright_year' => date('Y'),
+ 'author_name' => 'FIRST AUTHOR',
+ 'author_mail' => 'EMAIL@ADDRESS',
+ 'author_year' => 'YEAR',
+ 'project_name' => ucfirst($this->moduleName) . ' Module',
+ 'project_version' => $this->version,
+ 'project_bug_mail' => 'ISSUE TRACKER',
+ 'pot_creation_date' => date('Y-m-d H:iO'),
+ 'po_revision_date' => 'YEAR-MO-DA HO:MI+ZONE',
+ 'translator_name' => 'FULL NAME',
+ 'translator_mail' => 'EMAIL@ADDRESS',
+ 'language' => $this->locale,
+ 'language_team_name' => 'LANGUAGE',
+ 'language_team_url' => 'LL@li.org',
+ 'charset' => self::FILE_ENCODING
+ );
+
+ $content = file_get_contents($path);
+ if (strpos($content, '# ') === 0) {
+ $authorInfo = array();
+ if (preg_match('@# (.+) <(.+)>, (\d+|YEAR)\.@', $content, $authorInfo)) {
+ $headerInfo['author_name'] = $authorInfo[1];
+ $headerInfo['author_mail'] = $authorInfo[2];
+ $headerInfo['author_year'] = $authorInfo[3];
+ }
+ $revisionInfo = array();
+ if (preg_match('@Revision-Date: (\d{4}-\d{2}-\d{2} \d{2}:\d{2}\+\d{4})@', $content, $revisionInfo)) {
+ $headerInfo['po_revision_date'] = $revisionInfo[1];
+ }
+ $translatorInfo = array();
+ if (preg_match('@Last-Translator: (.+) <(.+)>@', $content, $translatorInfo)) {
+ $headerInfo['translator_name'] = $translatorInfo[1];
+ $headerInfo['translator_mail'] = $translatorInfo[2];
+ }
+ $languageTeamInfo = array();
+ if (preg_match('@Language-Team: (.+) <(.+)>@', $content, $languageTeamInfo)) {
+ $headerInfo['language_team_name'] = $languageTeamInfo[1];
+ $headerInfo['language_team_url'] = $languageTeamInfo[2];
+ }
+ $languageInfo = array();
+ if (preg_match('@Language: ([a-z]{2}_[A-Z]{2})@', $content, $languageInfo)) {
+ $headerInfo['language'] = $languageInfo[1];
+ }
+ }
+
+ file_put_contents(
+ $path,
+ implode(
+ PHP_EOL,
+ array(
+ '# ' . $headerInfo['title'] . '.',
+ '# Copyright (C) ' . $headerInfo['copyright_year'] . ' ' . $headerInfo['copyright_holder'],
+ '# This file is distributed under the same license as ' . $headerInfo['project_name'] . '.',
+ '# ' . $headerInfo['author_name'] . ' <' . $headerInfo['author_mail']
+ . '>, ' . $headerInfo['author_year'] . '.',
+ '# ',
+ '#, fuzzy',
+ 'msgid ""',
+ 'msgstr ""',
+ '"Project-Id-Version: ' . $headerInfo['project_name'] . ' ('
+ . $headerInfo['project_version'] . ')\n"',
+ '"Report-Msgid-Bugs-To: ' . $headerInfo['project_bug_mail'] . '\n"',
+ '"POT-Creation-Date: ' . $headerInfo['pot_creation_date'] . '\n"',
+ '"PO-Revision-Date: ' . $headerInfo['po_revision_date'] . '\n"',
+ '"Last-Translator: ' . $headerInfo['translator_name'] . ' <'
+ . $headerInfo['translator_mail'] . '>\n"',
+ '"Language: ' . $headerInfo['language'] . '\n"',
+ '"Language-Team: ' . $headerInfo['language_team_name'] . ' <'
+ . $headerInfo['language_team_url'] . '>\n"',
+ '"MIME-Version: 1.0\n"',
+ '"Content-Type: text/plain; charset=' . $headerInfo['charset'] . '\n"',
+ '"Content-Transfer-Encoding: 8bit\n"',
+ '"Plural-Forms: nplurals=2; plural=(n != 1);\n"',
+ '"X-Poedit-Basepath: .\n"',
+ '"X-Poedit-SearchPath-0: .\n"',
+ ''
+ )
+ ) . PHP_EOL . substr($content, strpos($content, '#: '))
+ );
+ }
+
+ /**
+ * Adjust all absolute source file paths so that they're all relative to the catalog's location
+ *
+ * @param string $path
+ */
+ protected function fixSourceLocations($path)
+ {
+ shell_exec(sprintf(
+ "sed -i 's;%s;../../../..;g' %s",
+ $this->moduleDir,
+ $path
+ ));
+ }
+
+ /**
+ * Create the file catalog
+ *
+ * @throws Exception In case the catalog-file cannot be created
+ */
+ private function createFileCatalog()
+ {
+ $catalog = new File($this->catalogPath, 'w');
+
+ try {
+ $this->getSourceFileNames($this->moduleDir, $catalog);
+ } catch (Exception $error) {
+ throw $error;
+ }
+
+ $catalog->fflush();
+ }
+
+ /**
+ * Recursively scan the given directory for translatable source files
+ *
+ * @param string $directory The directory where to search for sources
+ * @param File $file The file where to write the results
+ * @param array $blacklist A list of directories to omit
+ *
+ * @throws Exception In case the given directory is not readable
+ */
+ private function getSourceFileNames($directory, File $file)
+ {
+ $directoryHandle = opendir($directory);
+ if (!$directoryHandle) {
+ throw new IcingaException(
+ 'Unable to read files from %s',
+ $directory
+ );
+ }
+
+ $subdirs = array();
+ while (($filename = readdir($directoryHandle)) !== false) {
+ if ($filename[0] === '.' || $filename === 'vendor') {
+ continue;
+ }
+ $filepath = $directory . DIRECTORY_SEPARATOR . $filename;
+ if (preg_match('@^[^\.].+\.(' . implode('|', $this->sourceExtensions) . ')$@', $filename)) {
+ $file->fwrite($filepath . PHP_EOL);
+ } elseif (! is_link($filepath) && is_dir($filepath)) {
+ $subdirs[] = $filepath;
+ }
+ }
+ closedir($directoryHandle);
+
+ foreach ($subdirs as $subdir) {
+ $this->getSourceFileNames($subdir, $file);
+ }
+ }
+
+ /**
+ * Compile the translation table
+ */
+ private function compileTranslationTable()
+ {
+ $targetPath = substr($this->tablePath, 0, strrpos($this->tablePath, '.')) . '.mo';
+ shell_exec(
+ implode(
+ ' ',
+ array(
+ $this->getConfig()->get('translation', 'msgfmt', '/usr/bin/env msgfmt'),
+ '-o ' . $targetPath,
+ $this->tablePath
+ )
+ )
+ );
+ }
+}
diff --git a/modules/translation/module.info b/modules/translation/module.info
new file mode 100644
index 0000000..57a0dd2
--- /dev/null
+++ b/modules/translation/module.info
@@ -0,0 +1,7 @@
+Module: translation
+Version: 2.11.4
+Description: Translation module
+ This module allows developers and translators to translate modules for multiple
+ languages. You do not need this module to run an internationalized web frontend.
+ This is only for people who want to contribute translations or translate just
+ their own modules.
diff --git a/public/css/icinga/about.less b/public/css/icinga/about.less
new file mode 100644
index 0000000..4ccaaf8
--- /dev/null
+++ b/public/css/icinga/about.less
@@ -0,0 +1,99 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+#about {
+ &.content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ section {
+ width: auto;
+
+ > * {
+ margin-bottom: 2em;
+ }
+ }
+
+ h2 {
+ margin: 0;
+ }
+
+ .name-value-table {
+ th {
+ width: 100%;
+ }
+
+ th,
+ td {
+ white-space: nowrap;
+
+ a {
+ color: @icinga-blue
+ }
+ }
+ }
+
+ section:not(:last-child),
+ .icinga-logo {
+ margin-bottom: 2em;
+ }
+
+ .external-links {
+ .rounded-corners();
+ border: 1px solid @gray-light;
+ display: flex;
+ padding: .5em 0;
+ overflow: hidden;
+
+ .col {
+ flex: 1 1 auto;
+ text-align: center;
+ font-size: 12/14em;
+ }
+
+ .col:not(:last-child) {
+ border-right: 1px solid @gray-light;
+ }
+
+ a {
+ display: block;
+ padding: .75em 1em;
+ margin: -7/12em 0;
+ }
+
+ a:hover {
+ text-decoration: none;
+ background: @gray-light;
+ }
+
+ i {
+ font-size: 2*14/12em;
+ opacity: .8;
+ margin-bottom: .25em;
+ display: block;
+
+ &:before {
+ margin-right: 0;
+ }
+ }
+ }
+
+ footer {
+ margin-top: auto;
+ align-self: stretch;
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+
+ a {
+ i {
+ font-size: 2em;
+ }
+
+ &:hover {
+ opacity: .6;
+ }
+ }
+ }
+}
diff --git a/public/css/icinga/animation.less b/public/css/icinga/animation.less
new file mode 100644
index 0000000..aad3ffb
--- /dev/null
+++ b/public/css/icinga/animation.less
@@ -0,0 +1,366 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+.animate(@animate) {
+ -moz-animation: @animate;
+ -o-animation: @animate;
+ -webkit-animation: @animate;
+ animation: @animate;
+}
+
+@-moz-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-webkit-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-o-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@-ms-keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+@keyframes spin {
+ 0% {
+ -moz-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ -moz-transform: rotate(359deg);
+ -o-transform: rotate(359deg);
+ -webkit-transform: rotate(359deg);
+ transform: rotate(359deg);
+ }
+}
+
+@-moz-keyframes move-vertical {
+ 0% {
+ -moz-transform: translate(0, 100%);
+ -o-transform: translate(0, 100%);
+ -webkit-transform: translate(0, 100%);
+ transform: translate(0, 100%);
+ }
+
+ 17% {
+ -moz-transform: translate(0, 66%);
+ -o-transform: translate(0, 66%);
+ -webkit-transform: translate(0, 66%);
+ transform: translate(0, 66%);
+ }
+
+ 33% {
+ -moz-transform: translate(0, 33%);
+ -o-transform: translate(0, 33%);
+ -webkit-transform: translate(0, 33%);
+ transform: translate(0, 33%);
+ }
+
+ 50% {
+ -moz-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ -webkit-transform: translate(0, 0);
+ transform: translate(0, 0);
+ }
+
+ 67% {
+ -moz-transform: translate(0, -33%);
+ -o-transform: translate(0, -33%);
+ -webkit-transform: translate(0, -33%);
+ transform: translate(0, -33%);
+ }
+
+ 83% {
+ -moz-transform: translate(0, -66%);
+ -o-transform: translate(0, -66%);
+ -webkit-transform: translate(0, -66%);
+ transform: translate(0, -66%);
+ }
+
+ 100% {
+ -moz-transform: translate(0, -100%);
+ -o-transform: translate(0, -100%);
+ -webkit-transform: translate(0, -100%);
+ transform: translate(0, -100%);
+ }
+}
+@-webkit-keyframes move-vertical {
+ 0% {
+ -moz-transform: translate(0, 100%);
+ -o-transform: translate(0, 100%);
+ -webkit-transform: translate(0, 100%);
+ transform: translate(0, 100%);
+ }
+
+ 17% {
+ -moz-transform: translate(0, 66%);
+ -o-transform: translate(0, 66%);
+ -webkit-transform: translate(0, 66%);
+ transform: translate(0, 66%);
+ }
+
+ 33% {
+ -moz-transform: translate(0, 33%);
+ -o-transform: translate(0, 33%);
+ -webkit-transform: translate(0, 33%);
+ transform: translate(0, 33%);
+ }
+
+ 50% {
+ -moz-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ -webkit-transform: translate(0, 0);
+ transform: translate(0, 0);
+ }
+
+ 67% {
+ -moz-transform: translate(0, -33%);
+ -o-transform: translate(0, -33%);
+ -webkit-transform: translate(0, -33%);
+ transform: translate(0, -33%);
+ }
+
+ 83% {
+ -moz-transform: translate(0, -66%);
+ -o-transform: translate(0, -66%);
+ -webkit-transform: translate(0, -66%);
+ transform: translate(0, -66%);
+ }
+
+ 100% {
+ -moz-transform: translate(0, -100%);
+ -o-transform: translate(0, -100%);
+ -webkit-transform: translate(0, -100%);
+ transform: translate(0, -100%);
+ }
+}
+@-o-keyframes move-vertical {
+ 0% {
+ -moz-transform: translate(0, 100%);
+ -o-transform: translate(0, 100%);
+ -webkit-transform: translate(0, 100%);
+ transform: translate(0, 100%);
+ }
+
+ 17% {
+ -moz-transform: translate(0, 66%);
+ -o-transform: translate(0, 66%);
+ -webkit-transform: translate(0, 66%);
+ transform: translate(0, 66%);
+ }
+
+ 33% {
+ -moz-transform: translate(0, 33%);
+ -o-transform: translate(0, 33%);
+ -webkit-transform: translate(0, 33%);
+ transform: translate(0, 33%);
+ }
+
+ 50% {
+ -moz-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ -webkit-transform: translate(0, 0);
+ transform: translate(0, 0);
+ }
+
+ 67% {
+ -moz-transform: translate(0, -33%);
+ -o-transform: translate(0, -33%);
+ -webkit-transform: translate(0, -33%);
+ transform: translate(0, -33%);
+ }
+
+ 83% {
+ -moz-transform: translate(0, -66%);
+ -o-transform: translate(0, -66%);
+ -webkit-transform: translate(0, -66%);
+ transform: translate(0, -66%);
+ }
+
+ 100% {
+ -moz-transform: translate(0, -100%);
+ -o-transform: translate(0, -100%);
+ -webkit-transform: translate(0, -100%);
+ transform: translate(0, -100%);
+ }
+}
+@-ms-keyframes move-vertical {
+ 0% {
+ -moz-transform: translate(0, 100%);
+ -o-transform: translate(0, 100%);
+ -webkit-transform: translate(0, 100%);
+ transform: translate(0, 100%);
+ }
+
+ 17% {
+ -moz-transform: translate(0, 66%);
+ -o-transform: translate(0, 66%);
+ -webkit-transform: translate(0, 66%);
+ transform: translate(0, 66%);
+ }
+
+ 33% {
+ -moz-transform: translate(0, 33%);
+ -o-transform: translate(0, 33%);
+ -webkit-transform: translate(0, 33%);
+ transform: translate(0, 33%);
+ }
+
+ 50% {
+ -moz-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ -webkit-transform: translate(0, 0);
+ transform: translate(0, 0);
+ }
+
+ 67% {
+ -moz-transform: translate(0, -33%);
+ -o-transform: translate(0, -33%);
+ -webkit-transform: translate(0, -33%);
+ transform: translate(0, -33%);
+ }
+
+ 83% {
+ -moz-transform: translate(0, -66%);
+ -o-transform: translate(0, -66%);
+ -webkit-transform: translate(0, -66%);
+ transform: translate(0, -66%);
+ }
+
+ 100% {
+ -moz-transform: translate(0, -100%);
+ -o-transform: translate(0, -100%);
+ -webkit-transform: translate(0, -100%);
+ transform: translate(0, -100%);
+ }
+}
+@keyframes move-vertical {
+ 0% {
+ -moz-transform: translate(0, 100%);
+ -o-transform: translate(0, 100%);
+ -webkit-transform: translate(0, 100%);
+ transform: translate(0, 100%);
+ }
+
+ 17% {
+ -moz-transform: translate(0, 66%);
+ -o-transform: translate(0, 66%);
+ -webkit-transform: translate(0, 66%);
+ transform: translate(0, 66%);
+ }
+
+ 33% {
+ -moz-transform: translate(0, 33%);
+ -o-transform: translate(0, 33%);
+ -webkit-transform: translate(0, 33%);
+ transform: translate(0, 33%);
+ }
+
+ 50% {
+ -moz-transform: translate(0, 0);
+ -o-transform: translate(0, 0);
+ -webkit-transform: translate(0, 0);
+ transform: translate(0, 0);
+ }
+
+ 67% {
+ -moz-transform: translate(0, -33%);
+ -o-transform: translate(0, -33%);
+ -webkit-transform: translate(0, -33%);
+ transform: translate(0, -33%);
+ }
+
+ 83% {
+ -moz-transform: translate(0, -66%);
+ -o-transform: translate(0, -66%);
+ -webkit-transform: translate(0, -66%);
+ transform: translate(0, -66%);
+ }
+
+ 100% {
+ -moz-transform: translate(0, -100%);
+ -o-transform: translate(0, -100%);
+ -webkit-transform: translate(0, -100%);
+ transform: translate(0, -100%);
+ }
+}
+
+@keyframes blink {
+ 0% {
+ opacity: 0.2;
+ }
+
+ 20% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0.2;
+ }
+}
+
+@keyframes pulse {
+ 0% {
+ opacity: .5;
+ transform: scale(1);
+ }
+
+ 50% {
+ opacity: 1;
+ transform: scale(1.2);
+ }
+
+ 100% {
+ opacity: .5;
+ transform: scale(1);
+ }
+}
diff --git a/public/css/icinga/audit.less b/public/css/icinga/audit.less
new file mode 100644
index 0000000..23ab5b9
--- /dev/null
+++ b/public/css/icinga/audit.less
@@ -0,0 +1,363 @@
+// Style
+
+.privilege-audit-role-control {
+ list-style-type: none;
+
+ li {
+ .rounded-corners(3px);
+ border: 1px solid;
+ border-color: @low-sat-blue;
+
+ &.active {
+ border-color: @icinga-blue;
+ }
+ }
+}
+
+.privilege-audit {
+ &, ul, ol {
+ list-style-type: none;
+ }
+
+ .privilege-section > summary {
+ font-weight: @font-weight-bold;
+ border-bottom: 1px solid @gray-light;
+ }
+
+ .privilege-section > summary em,
+ .previews em,
+ .privilege-label em {
+ color: @text-color-light;
+ }
+ .privilege-section > summary em {
+ font-weight: normal;
+ }
+ .privilege-label em {
+ font-style: normal;
+ }
+
+ .icon {
+ color: @gray-light;
+
+ &.granted {
+ color: @color-granted;
+ }
+
+ &.refused {
+ color: @color-refused;
+ }
+
+ &.restricted {
+ color: @color-restricted;
+ }
+ }
+
+ .privilege-list > li {
+ .spacer {
+ opacity: 0;
+ .transition(opacity .5s ease-out);
+ }
+
+ &:hover .spacer {
+ .transition(opacity .25s .25s ease-in);
+ border: 0 dashed;
+ border-color: @gray-light;
+ border-top-width: .2em;
+ opacity: 1;
+ }
+ }
+
+ .vertical-line {
+ border: 0 solid;
+ border-left-width: 2px;
+
+ &.granted {
+ border-color: @color-granted;
+ }
+
+ &.refused {
+ border-color: @color-refused;
+ }
+ }
+
+ .connector {
+ border: 0 solid;
+ border-color: @gray-lighter;
+ border-bottom-width: 2px;
+
+ &.granted {
+ border-color: @color-granted;
+ }
+
+ &.refused {
+ border-color: @color-refused;
+ }
+
+ &:first-child {
+ border-width: 0 0 2px 2px;
+ border-bottom-left-radius: .5em;
+ }
+ }
+
+ .role {
+ .rounded-corners(1em);
+ border: 2px solid;
+ border-color: @gray-lighter;
+
+ &.granted {
+ border: 2px solid;
+ border-color: @color-granted;
+ }
+
+ &.refused {
+ border: 2px solid;
+ border-color: @color-refused;
+ }
+ }
+
+ .restriction {
+ font-family: @font-family-fixed;
+ background-color: @gray-lighter;
+ }
+}
+
+// Layout
+
+.privilege-audit-role-control {
+ display: inline-flex;
+ flex-wrap: wrap;
+
+ margin: 0 0 0 1em;
+ padding: 0;
+
+ li {
+ margin-top: @vertical-padding;
+
+ &:not(:first-child) {
+ margin-left: .5em;
+ }
+ }
+}
+
+.privilege-audit {
+ &, ul, ol {
+ margin: 0;
+ padding: 0;
+ }
+
+ .flex-overflow,
+ .privilege-list > li,
+ .inheritance-paths > ol {
+ display: flex;
+ }
+
+ .privilege-list > li {
+ margin-top: 1em;
+
+ > :last-child {
+ // This aids the usage of text-overflow:ellipsis in any of the children.
+ // It seems that to get this working while none of the children has a
+ // defined width, any flex item on the way up to the clipped container
+ // also must have a overflow value of "hidden".
+ // https://codepen.io/unthinkingly/pen/XMwJLG
+ overflow: hidden;
+ }
+
+ > details:last-child {
+ // The overflow above cuts off the outline of the summary otherwise
+ margin: -4px;
+ padding: 4px;
+ }
+ }
+
+ .privilege-section {
+ &:not(.collapsed) {
+ margin-bottom: 2em;
+ }
+ }
+
+ .privilege-section > summary {
+ display: flex;
+ align-items: baseline;
+ font-size: 1.167em;
+ margin: 0.556em 0 0.333em;
+
+ > :first-child {
+ flex: 3 1 auto;
+ min-width: 20em;
+ max-width: 40em / 1.167em; // privilege label width + spacer width / summary font-size
+ }
+
+ .audit-preview {
+ flex: 1 1 auto;
+
+ .icon:before {
+ width: 1.25em;
+ font-size: 1.25em / 1.167em; // privilege state icon font-size / summary font-size
+ }
+ }
+
+ em {
+ font-size: .857em;
+ }
+ }
+
+ h4,
+ .privilege-label {
+ flex-shrink: 0;
+ width: 20em;
+ margin: 0;
+ text-align: right;
+ }
+
+ ol + h4 {
+ margin-top: 1em;
+ }
+
+ .spacer {
+ flex: 20 1 auto;
+ min-width: 10em; // TODO: Mobile?
+ max-width: 18.8em; // 20em - (margin-left + margin-right)
+ margin: .6em;
+ }
+
+ .inheritance-paths,
+ .restrictions {
+ flex: 1 1 auto;
+
+ > summary {
+ line-height: 1;
+
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ > .icon:before {
+ width: 1.25em;
+ font-size: 1.25em;
+ }
+ }
+ }
+
+ .vertical-line {
+ margin-left: ~"calc(.75em - 1px)";
+ }
+
+ .connector {
+ flex: 1 1 auto;
+ width: 2em;
+ max-width: 2em;
+ min-width: 1em;
+ margin-bottom: ~"calc(1em - 1px)";
+
+ &:first-child {
+ margin-left: ~"calc(.75em - 1px)";
+ }
+
+ &.initiator {
+ z-index: 1;
+ margin-right: ~"calc(-.25em - 2px)";
+ }
+ }
+
+ .vertical-line + .connector {
+ min-width: ~"calc(.75em - 2px)";
+ width: ~"calc(.75em - 2px)";
+ flex-grow: 0;
+
+ &.initiator {
+ width: ~"calc(1em - 1px)";
+ }
+ }
+ .connector:first-child {
+ min-width: .75em;
+ width: .75em;
+ flex-grow: 0;
+
+ &.initiator {
+ width: 1em;
+ }
+ }
+
+ .role {
+ padding: .25em .5em .25em .5em;
+ line-height: 1;
+
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ .icon:before {
+ font-size: 1.25em;
+ }
+ }
+ .inheritance-paths .role {
+ min-width: 4em;
+ margin-top: .5em;
+ padding-left: .25em;
+ }
+ .restrictions .role {
+ display: inline-block;
+ }
+
+ .previews {
+ display: flex;
+ margin-top: .25em;
+
+ em {
+ // explicit margin + ((header icon width + its margin right) * 125% font-size)
+ margin: 0 1em 0 1em + ((1.25em + .2em) * 1.25em);
+ }
+ }
+
+ .links li:not(:last-child):after {
+ content: ",";
+ }
+
+ .restrictions > ul > li {
+ margin-top: .5em;
+
+ .role {
+ margin-left: 1.25em + .2em * 1.25em; // (header icon width + its margin right) * 125% font-size
+ margin-right: 1em;
+ }
+ }
+
+ .restriction {
+ font-size: .8em;
+ padding: .335em / .8em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ .user-select(all);
+ }
+}
+
+#layout.minimal-layout,
+#layout.poor-layout {
+ .privilege-audit {
+ .privilege-section > summary > :first-child {
+ flex-grow: 99;
+ }
+
+ h4,
+ .privilege-label {
+ width: 12em;
+ }
+
+ .spacer {
+ flex: 0;
+ min-width: 0;
+ }
+ }
+}
+
+// Integrations
+
+.privilege-audit .collapsible {
+ .collapsible-control {
+ cursor: pointer;
+ .user-select(none);
+ }
+}
diff --git a/public/css/icinga/badges.less b/public/css/icinga/badges.less
new file mode 100644
index 0000000..eae95c2
--- /dev/null
+++ b/public/css/icinga/badges.less
@@ -0,0 +1,22 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+@badge-color: @body-bg-color;
+@badge-line-height: 1.2;
+@badge-padding: 0.25em;
+
+.badge {
+ .bg-stateful();
+ .rounded-corners();
+
+ background-color: @gray;
+ color: @badge-color;
+ display: inline-block;
+ font-family: @font-family-wide;
+ font-size: @font-size-small;
+ line-height: @badge-line-height;
+ min-width: 2em;
+ padding: @badge-padding;
+ text-align: center;
+ vertical-align: middle;
+ white-space: nowrap;
+}
diff --git a/public/css/icinga/base.less b/public/css/icinga/base.less
new file mode 100644
index 0000000..9ce5d7e
--- /dev/null
+++ b/public/css/icinga/base.less
@@ -0,0 +1,344 @@
+/*! Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+// Black colors
+@black: #535353;
+@white: #fff;
+
+// Gray colors
+@gray: #c4c4c4;
+@gray-semilight: #888;
+@gray-light: #5c5c5c;
+@gray-lighter: #4b4b4b;
+@gray-lightest: #3a3a3a;
+
+@disabled-gray: #9a9a9a;
+
+// State colors
+@color-ok: #44bb77;
+@color-up: @color-ok;
+@color-warning: #ffaa44;
+@color-warning-handled: #ffcc66;
+@color-critical: #ff5566;
+@color-critical-handled: #ff99aa;
+@color-critical-accentuated: darken(@color-critical, 10%);
+@color-down: @color-critical;
+@color-down-handled: @color-critical-handled;
+@color-unknown: #aa44ff;
+@color-unknown-handled: #cc77ff;
+@color-unreachable: @color-unknown;
+@color-unreachable-handled: @color-unknown-handled;
+@color-pending: #77aaff;
+
+// Icinga colors
+@icinga-blue: #00C3ED;
+@icinga-secondary: #EF4F98;
+@icinga-secondary-dark: darken(@icinga-secondary, 10%);
+@low-sat-blue: #404d72;
+@low-sat-blue-dark: #434374;
+@icinga-blue-light: fade(@icinga-blue, 50%);
+@icinga-blue-dark: #0081a6;
+
+// Notification colors
+@color-notification-error: @color-critical;
+@color-notification-info: @color-pending;
+@color-notification-success: @color-ok;
+@color-notification-warning: @color-warning;
+
+// Background color for <body>
+@body-bg-color: #282E39;
+@body-bg-color-transparent: fade(@body-bg-color, 0);
+
+// Text colors
+@text-color: @white;
+@text-color-inverted: @body-bg-color;
+@text-color-light: fade(@text-color, 75%);
+@text-color-on-icinga-blue: @body-bg-color;
+@light-text-bg-color: fade(@gray, 5%);
+
+// Text color on <a>
+@link-color: @text-color;
+
+@tr-active-color: fade(@icinga-blue, 25);
+@tr-hover-color: fade(@icinga-blue, 5);
+
+// Menu colors
+@menu-bg-color: #06062B;
+@menu-hover-bg-color: lighten(@menu-bg-color, 5%);
+@menu-search-hover-bg-color: @menu-hover-bg-color;
+@menu-active-bg-color: #181742;
+@menu-active-hover-bg-color: lighten(@menu-active-bg-color, 5%);
+@menu-color: #DBDBDB;
+@menu-active-color: @text-color;
+@menu-highlight-color: @icinga-blue;
+@menu-highlight-hover-bg-color: @icinga-blue-dark;
+@menu-2ndlvl-color: #c4c4c4;
+@menu-2ndlvl-highlight-bg-color: @tr-hover-color;
+@menu-2ndlvl-active-bg-color: @menu-highlight-color;
+@menu-2ndlvl-active-color: @text-color-inverted;
+@menu-2ndlvl-active-hover-bg-color: darken(@menu-2ndlvl-active-bg-color, 5%);
+@menu-2ndlvl-active-hover-color: @menu-2ndlvl-active-color;
+@menu-flyout-bg-color: @body-bg-color;
+@menu-flyout-color: @text-color;
+@tab-hover-bg-color: fade(@body-bg-color, 50%);
+
+// Form colors
+@form-info-bg-color: fade(@color-ok, 20%);
+@form-error-bg-color: fade(@color-critical, 30%);
+@form-warning-bg-color: fade(@color-warning, 40%);
+@login-box-background: fade(#0B0B2F, 30%);
+
+// Other colors
+@color-granted: #59cd59;
+@color-refused: #ee7373;
+@color-restricted: #dede7d;
+
+// Font families
+@font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+@font-family-fixed: "Liberation Mono", "Lucida Console", Courier, monospace;
+@font-family-wide: Tahoma, Verdana, sans-serif;
+
+// Font sizes
+@font-size: 0.750em; // 12px
+@font-size-small: 11/12em; // 11px
+@font-size-dashboard: 3.5em; // 56px
+@font-size-dashboard-small: 1.1em; // 17px
+@font-weight-bold: 600;
+
+// Set line-height w/o unit so that the line-height is dynamically calculated as font-size * line-height
+@line-height: 1.5;
+
+@table-column-padding: 0.333em; // 4px
+
+@vertical-padding: 0.5em; // 6px
+@horizontal-padding: 1em; // 12px
+
+@light-mode: {
+ @light-body-bg-color: #F5F9FA;
+
+ @iplWebLightRules();
+
+ :root {
+ --body-bg-color: @light-body-bg-color;
+ --body-bg-color-transparent: fade(@light-body-bg-color, 0);
+ --badge-color: #F5F9FA;
+ --text-color-inverted: #F5F9FA;
+ --text-color-on-icinga-blue: #F5F9FA;
+ --menu-flyout-bg-color: #F5F9FA;
+ --tab-hover-bg-color: fade(#F5F9FA, 50%);
+
+ --menu-color: #535353;
+ --menu-bg-color: #DEECF1;
+ --menu-hover-bg-color: darken(#DEECF1, 10%);
+ --menu-search-hover-bg-color: darken(#DEECF1, 10%);
+ --menu-active-bg-color: #EDF7FC;
+ --menu-active-hover-bg-color: darken(#EDF7FC, 20%);
+ --menu-highlight-hover-bg-color: darken(#EDF7FC, 20%);
+ --menu-2ndlvl-color: #676767;
+
+ --text-color: #535353;
+ --text-color-light: fade(#535353, 75%);
+ --light-text-bg-color: fade(#7F7F7F, 5%);
+ --link-color: #535353;
+ --menu-active-color: #535353;
+ --menu-flyout-color: #535353;
+
+ --low-sat-blue: #DEECF1;
+ --low-sat-blue-dark: #c0cccd;
+
+ --gray: #819398;
+ --gray-semilight: #94a5a6;
+ --gray-light: #d0d3da;
+ --gray-lighter: #e8ecef;
+ --gray-lightest: #F7F7F7;
+
+ // ipl-web overrides
+ --base-gray: var(--gray);
+ --base-gray-light: var(--gray-light);
+ --base-gray-lighter: var(--gray-lighter);
+
+ --default-text-color: var(--text-color);
+ --default-text-color-light: var(--text-color-light);
+ --default-text-color-inverted: var(--text-color-inverted);
+
+ --searchbar-bg: var(--low-sat-blue);
+
+ --search-logical-operator-bg: fade(#819398, 50%); // --gray
+ }
+};
+
+// ipl-web overrides
+@default-bg: @body-bg-color;
+
+@base-gray: @gray;
+@base-gray-light: @gray-light;
+@base-gray-lighter: @gray-lighter;
+@base-disabled: @disabled-gray;
+
+@base-primary-color: @icinga-blue;
+@base-primary-bg: @icinga-blue;
+
+@default-text-color: @text-color;
+@default-text-color-light: @text-color-light;
+
+@state-ok: @color-ok;
+@state-warning: @color-warning;
+@state-critical: @color-critical;
+@state-pending: @color-pending;
+@state-unknown: @color-unknown;
+
+@primary-button-hover-bg: @icinga-blue-dark;
+
+@searchbar-bg: @low-sat-blue;
+
+// Make padding not affect the final computed width of an element
+html {
+ box-sizing: border-box;
+}
+details > * {
+ // children somehow default to content-box no matter the inheritance
+ box-sizing: border-box;
+}
+*,
+*:before,
+*:after {
+ -webkit-box-sizing: inherit;
+ -moz-box-sizing: inherit;
+ box-sizing: inherit;
+}
+
+a {
+ // Reset defaults
+ color: inherit;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+:focus {
+ outline: 3px solid fade(@icinga-blue, 50%);
+ outline-offset: 1px;
+}
+
+// Default margin for block text
+blockquote, p, pre {
+ margin: 0 0 1em 0;
+}
+
+blockquote {
+ border-left: 5px solid @gray-lighter;
+ padding: 0.667em 0.333em;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: @font-weight-bold;
+ margin: 0.556em 0 0.333em;
+}
+
+h1 {
+ border-bottom: 1px solid @gray-lighter;
+ font-size: 1.333em;
+}
+
+h2 {
+ font-size: 1.333em;
+}
+
+h3 {
+ font-size: 1.167em;
+}
+
+h4 {
+ font-size: 1em;
+}
+
+h5 {
+ font-size: @font-size-small;
+}
+
+h6 {
+ font-size: @font-size-small;
+ font-weight: normal;
+}
+
+pre {
+ .rounded-corners(.25em);
+ background-color: @gray-lighter;
+ font-family: @font-family-fixed;
+ font-size: @font-size-small;
+ padding: @vertical-padding @horizontal-padding;
+ white-space: pre-wrap;
+}
+
+td, th {
+ padding: @table-column-padding;
+}
+
+[class^="icon-"], [class*=" icon-"] {
+ // Smooth icons; ifont claims to have it, but it does not work in :before
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ &:before {
+ margin-left: 0;
+ }
+}
+
+// Styles for when the page is loading. JS will remove this class once the document is ready
+.loading * {
+ // Disable all transition on page load
+ -webkit-transition: none !important;
+ -moz-transition: none !important;
+ -o-transition: none !important;
+ transition: none !important;
+}
+
+.container {
+ &:before,
+ > .content:before {
+ content: "";
+ display: block;
+
+ background: url(../img/icinga-loader.gif) no-repeat center center;
+ background-color: @body-bg-color;
+ background-size: 4em 4em;
+
+ opacity: 0;
+ z-index: -1;
+ pointer-events: none;
+ .transition(none);
+ }
+
+ &.impact,
+ > .content.impact {
+ overflow: hidden;
+ position: relative;
+
+ &:before {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+
+ opacity: .7;
+ z-index: 1000;
+ pointer-events: all;
+ .transition(opacity 1s 2s linear);
+ }
+ }
+
+ &.impact:before {
+ top: 2.5em;
+ }
+}
+
+@light-mode: {
+ .container {
+ &:before,
+ > .content:before {
+ background-image: url(../img/icinga-loader-light.gif)
+ }
+ }
+};
diff --git a/public/css/icinga/compat.less b/public/css/icinga/compat.less
new file mode 100644
index 0000000..d57a189
--- /dev/null
+++ b/public/css/icinga/compat.less
@@ -0,0 +1,35 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+@colorMainLayout: @icinga-blue;
+@colorMainBackground: @body-bg-color;
+@colorMainForeground: @text-color;
+@colorMainLink: @text-color;
+@colorSecondary: @gray-lightest;
+@colorGray: @gray-lightest;
+@colorLinkDefault: @text-color;
+@colorTextDefault: @text-color;
+@colorTextDarkDefault: @text-color;
+@colorOk: @color-ok;
+@colorWarning: #ffaa44;
+@colorWarningHandled: #ffcc66;
+@colorCritical: #ff5566;
+@colorCriticalHandled: #ff99aa;
+@colorUnknown: #aa44ff;
+@colorUnknownHandled: #cc77ff;
+@colorUnreachable: #aa44ff;
+@colorUnreachableHandled: #cc77ff;
+@colorPending: #77aaff;
+@colorInvalid: #999;
+@colorFormNotificationInfo: #77aaff;
+@colorFormNotificationWarning: #ffaa44;
+@colorFormNotificationError: #ff5566;
+@colorPetrol: @icinga-blue;
+@menu-2ndlvl-highlight-color: @menu-2ndlvl-active-color;
+
+table.action {
+ .common-table();
+}
+
+table.avp {
+ .name-value-table();
+}
diff --git a/public/css/icinga/configmenu.less b/public/css/icinga/configmenu.less
new file mode 100644
index 0000000..05e50e8
--- /dev/null
+++ b/public/css/icinga/configmenu.less
@@ -0,0 +1,303 @@
+#menu {
+ margin-bottom: 3em;
+}
+
+.sidebar-collapsed #menu {
+ margin-bottom: 8em;
+}
+
+#menu .config-menu {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background-color: @menu-bg-color;
+ margin-top: auto;
+
+ > ul {
+ display: flex;
+ flex-wrap: nowrap;
+ padding: 0;
+
+ > li {
+ > a {
+ padding: 0.5em 0.5em 0.5em 0.75em;
+ line-height: 2.167em;
+ white-space: nowrap;
+ text-decoration: none;
+
+ }
+
+ &:hover .nav-level-1 {
+ display: block;
+ }
+ }
+
+ li.active a:after {
+ display: none;
+ }
+
+ .user-nav-item {
+ width: 100%;
+ overflow: hidden; // necessary for .text-ellipsis of <a>
+
+ > a {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &:not(.active):hover a,
+ &:not(.active) a:focus {
+ background: @menu-hover-bg-color;
+ }
+ }
+
+ .config-nav-item {
+ line-height: 2;
+ display: flex;
+ align-items: center;
+ position: relative;
+
+ > button {
+ background: none;
+ border: none;
+ display: block;
+ .rounded-corners();
+
+ > .state-badge {
+ position: absolute;
+ pointer-events: none;
+ }
+
+ .icon {
+ opacity: .8;
+ font-size: 1.25em;
+
+ &:before {
+ margin-right: 0;
+ }
+ }
+ }
+
+ &:hover > button {
+ background: fade(@menu-hover-bg-color, 25);
+
+ > .state-badge {
+ display: none;
+ }
+ }
+
+ button:focus {
+ background: fade(@menu-hover-bg-color, 25);
+ }
+
+ &.active > button {
+ color: @text-color-inverted;
+ background: @icinga-blue;
+ }
+ }
+
+ .state-badge {
+ line-height: 1.2;
+ padding: .25em;
+ font-family: @font-family-wide;
+ }
+ }
+
+ .nav-level-1 li {
+ &.badge-nav-item > a {
+ display: flex;
+ align-items: baseline;
+ width: 100%;
+
+ .state-badge {
+ margin-left: auto;
+ }
+ }
+ }
+
+ .nav-item-logout {
+ color: @color-critical;
+ border-top: 1px solid @gray-lighter;
+ }
+
+ .user-ball {
+ .ball();
+ .ball-size-l();
+ .ball-solid(@icinga-blue);
+
+ // icingadb-web/public/css/common.less: .user-ball
+ font-weight: bold;
+ text-transform: uppercase;
+
+ // compensate border vertically and add space to the right;
+ margin: -1px .2em -2px 0;
+ border: 1px solid @text-color-inverted;
+ font-style: normal;
+ line-height: 1.2;
+ }
+}
+
+#layout:not(.sidebar-collapsed) #menu .config-menu {
+ .user-nav-item {
+ > a {
+ padding-right: 4.75em;
+ }
+
+ &.active.selected + .config-nav-item {
+ > button {
+ color: @text-color-inverted;
+ }
+ }
+ }
+
+ .config-nav-item {
+ position: absolute;
+ right: 2.5em;
+ bottom: 0;
+ top: 0;
+
+ .state-badge {
+ left: -1em;
+ top: 0;
+ }
+ }
+
+ .flyout {
+ bottom: 100%;
+ right: -2em;
+ width: 15em;
+ }
+}
+
+.sidebar-collapsed #menu .config-menu {
+ ul {
+ flex-direction: column;
+
+ .user-ball {
+ margin-left: .25em * 1.5/2;
+ margin-right: .5em + .25em * 1.5/2;
+ width: 2em * 1.5/2 ;
+ height: 2em * 1.5/2;
+ font-size: 2/1.5em;
+ line-height: 1;
+ }
+
+ .config-nav-item {
+ padding-right: 0;
+ margin-bottom: 3em;
+
+ .icon {
+ font-size: 1.5em;
+ }
+
+ button {
+ position: relative;
+ width: 3em;
+ margin: .125em .5em;
+ padding: .5em .75em;
+
+ .state-badge {
+ right: -.25em;
+ bottom: -.25em;
+ font-size: .75em;
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 4em;
+ }
+ }
+ }
+ }
+
+ .flyout {
+ bottom: 0;
+ left: 100%;
+ width: 14em;
+
+ &:before {
+ left: -.6em;
+ bottom: 1em;
+ transform: rotate(135deg);
+ }
+ }
+}
+
+.flyout {
+ display: none;
+ position: absolute;
+ border: 1px solid @gray-lighter;
+ background: @body-bg-color;
+ box-shadow: 0 0 1em 0 rgba(0,0,0,.25);
+ z-index: 1;
+ .rounded-corners();
+
+ a {
+ font-size: 11/12em;
+ padding: 0.364em 0.545em 0.364em 2em;
+ line-height: 2;
+
+ &:hover {
+ text-decoration: none;
+ background: @menu-2ndlvl-highlight-bg-color;
+ }
+ }
+
+ h3 {
+ font-size: 10/12em;
+ color: @text-color-light;
+ letter-spacing: .1px;
+ padding: 0.364em 0.545em 0.364em 0.545em;
+ margin: 0;
+ }
+
+ .flyout-content {
+ overflow: auto;
+ // Partially escape to have ems calculated
+ max-height: calc(~"100vh - " 50/12em);
+ padding: .5em 0;
+ position: relative;
+ }
+
+ // Caret
+ &:before {
+ content: "";
+ display: block;
+ position: absolute;
+ transform: rotate(45deg);
+ background: @body-bg-color;
+ border-bottom: 1px solid @gray-lighter;
+ border-right: 1px solid @gray-lighter;
+ height: 1.1em;
+ width: 1.1em;
+ bottom: -.6em;
+ right: 2.5em;
+ }
+}
+
+// Prevent flyout to vanish on autorefresh
+#layout.config-flyout-open .config-nav-item {
+ .flyout {
+ display: block;
+ }
+
+ > button > .state-badge {
+ display: none;
+ }
+}
+
+#layout.minimal-layout .config-menu {
+ display: none;
+}
+
+#layout.minimal-layout #menu {
+ margin-bottom: 0;
+}
+
+#layout:not(.minimal-layout) #menu .primary-nav {
+ .user-nav-item,
+ .configuration-nav-item,
+ .system-nav-item {
+ display: none;
+ }
+}
diff --git a/public/css/icinga/controls.less b/public/css/icinga/controls.less
new file mode 100644
index 0000000..01cf152
--- /dev/null
+++ b/public/css/icinga/controls.less
@@ -0,0 +1,281 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+// TODO(el): Rename .filter to .filter-control
+
+// Hide auto sumbit info in controls
+.controls .autosubmit-info {
+ display: none;
+}
+
+
+// Backend selection control in user and group list views
+.backend-selection {
+ float: left;
+
+ .control-label-group, select {
+ display: inline-block;
+ }
+
+ select {
+ margin-left: .5em;
+ }
+}
+
+.controls input.search,
+input.search {
+ .transition(border 0.3s ease);
+ .transition(background-image 0.2s ease);
+
+ background-image: url(../img/icons/search_white.png);
+ background-repeat: no-repeat;
+ background-size: 1em 1em;
+ background-position: .25em center;
+ outline: none;
+ padding-left: 1.5em;
+ width: 20em;
+
+ &:focus {
+ background-image: url(../img/icons/search_icinga_blue.png);
+ }
+
+ &:focus:not([readonly]) {
+ border-color: @icinga-blue;
+ }
+}
+
+@light-mode: {
+ #menu input.search,
+ .controls input.search,
+ input.search {
+ background-image: url(../img/icons/search.png);
+ }
+};
+
+.backend-selection,
+.pagination-control,
+.selection-info,
+.sort-controls-container {
+ margin-bottom: 0.5em;
+}
+
+.filter {
+ // Display filter control on a new line
+ clear: both;
+ margin: .5em 0;
+
+ > a {
+ color: @icinga-blue;
+ padding: .5em;
+ line-height: 1;
+ }
+
+ > a > i {
+ text-align: center;
+ &:before {
+ margin-right: 0;
+ }
+ }
+
+ .form input {
+ padding: @vertical-padding @vertical-padding;
+ }
+}
+
+.controls .filter {
+ form .search {
+ height: 2em;
+ }
+}
+
+.controls .button-link {
+ height: 2em;
+}
+
+.limiter-control > select {
+ margin-left: .5em;
+}
+
+.pagination-control {
+ // Display the pagination-control on a new line
+ clear: both;
+ float: left;
+
+ li {
+ line-height: 1;
+
+ &.active {
+ border-bottom: 2px solid @icinga-blue;
+
+ > a,
+ > a:hover {
+ color: @icinga-blue;
+ /* Compensate border-bottom: 2px */
+ margin-bottom: -2px;
+ }
+
+ > a:hover {
+ background: none;
+ cursor: default;
+ text-decoration: none;
+ }
+ }
+
+ &.disabled {
+ color: @disabled-gray;
+ cursor: no-drop;
+ }
+
+ > a,
+ > span {
+ padding: 0.5em;
+ }
+ > a:hover {
+ background-color: @gray-lighter;
+ text-decoration: none;
+ }
+ }
+
+ .previous-page {
+ padding-left: 0;
+ }
+
+ .next-page {
+ padding-right: 0;
+ }
+}
+
+// Multi-selection info
+.selection-info {
+ float: right;
+ font-size: @font-size-small;
+
+ &:hover {
+ cursor: help;
+ }
+}
+
+.sort-control {
+ label {
+ width: auto;
+ margin-right: 0.5em;
+ }
+
+ select[name=sort] {
+ width: 12em;
+ margin-left: 0;
+ }
+
+ select[name=dir] {
+ width: 8em;
+ margin-left: 0;
+ }
+}
+
+.sort-controls-container {
+ clear: right;
+ float: right;
+ display: flex;
+
+ > *:not(:last-child) {
+ margin-right: .5em;
+ }
+}
+
+.sort-direction-control {
+ margin-left: 0.25em;
+ width: 1em;
+
+ .spinner {
+ line-height: 1;
+ }
+}
+
+.controls .icinga-controls {
+ .control-label-group {
+ margin-top: 0;
+ margin-bottom: 0;
+ line-height: 1.5em;
+ padding-top: 0.25em;
+ padding-bottom: 0.25em;
+ }
+
+ input,
+ select {
+ max-width: 16em;
+ }
+
+ select {
+ padding-right: 1.526em;
+ margin-top: 0;
+ margin-bottom: 0;
+ /* compensate inconsistent select height calculations */
+ line-height: 1;
+ max-height: 2em;
+ }
+}
+
+// Datetime picker colors
+
+// The less variables are essentially the official dark theme for the flatpickr
+@fp-calendarBackground: #3f4458;
+@fp-calendarBorderColor: darken(#3f4458, 50%);
+
+@fp-monthForeground: #fff;
+@fp-monthBackground: #3f4458;
+
+@fp-weekdaysBackground: transparent;
+@fp-weekdaysForeground: #fff;
+
+@fp-dayForeground: fadeout(white, 5%);
+@fp-dayHoverBackground: lighten(@fp-calendarBackground, 25%);
+
+@fp-todayColor: #eee;
+@fp-today_fg_color: #3f4458;
+
+@fp-selectedDayBackground: #80CBC4;
+
+.icinga-datetime-picker .flatpickr-day.today {
+ &:hover,
+ &:focus {
+ color: @fp-today_fg_color;
+ }
+}
+
+@light-mode: {
+ :root {
+ // These are actually the default colors for the flatpickr
+
+ @fp-dayForeground: #393939;
+ @fp-dayHoverBackground: #e6e6e6;
+
+ --fp-calendarBackground: #ffffff;
+ --fp-calendarBorderColor: @fp-dayHoverBackground;
+
+ --fp-arrowColor: fadeout(@fp-dayForeground, 40%);
+ --fp-arrow_hover_color: #f64747;
+
+ --fp-monthForeground: fadeout(black, 10%);
+ --fp-monthBackground: transparent;
+
+ --fp-weekdaysBackground: transparent;
+ --fp-weekdaysForeground: fadeout(black, 46%);
+ --fp-weekNumberForeground: fadeout(@fp-dayForeground, 70%);
+
+ --fp-dayForeground: @fp-dayForeground;
+ --fp-dayHoverBackground: @fp-dayHoverBackground;
+ --fp-disabledDayForeground: fadeout(@fp-dayForeground, 90%);
+ --fp-outsideRangeDayForeground: fadeout(@fp-dayForeground, 70%);
+ --fp-selectedDayBackground: #569FF7;
+ --fp-todayColor: #959ea9;
+
+ --fp-timeHoverBg: lighten(@fp-dayHoverBackground, 3);
+
+ --fp-hoverInvertedBg: fadeout(black, 95%);
+
+ --fp-numChooserSvgFillColor: fadeout(fadeout(black, 10%), 50%);
+ --fp-hoverNumChooserBg: fadeout(black, 90%);
+ --fp-numChooserBorderColor: fadeout(@fp-dayForeground, 85%);
+ }
+};
+
+// Datetime picker colors (end)
diff --git a/public/css/icinga/dev.less b/public/css/icinga/dev.less
new file mode 100644
index 0000000..a1e34be
--- /dev/null
+++ b/public/css/icinga/dev.less
@@ -0,0 +1,10 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+#fontsize-calc {
+ display: none;
+ width: 1000em;
+ height: 1em;
+ font-size: 1em;
+ position: absolute;
+ top: -2em;
+}
diff --git a/public/css/icinga/forms.less b/public/css/icinga/forms.less
new file mode 100644
index 0000000..00389ea
--- /dev/null
+++ b/public/css/icinga/forms.less
@@ -0,0 +1,596 @@
+/*! Icinga Web 2 | (c) 2019 Icinga Development Team | GPLv2+ */
+
+/**
+ Rules found in here are structured with two layers:
+
+ 1) form.icinga-form, that's what defines the general structure of our single/individual forms. It's not
+ supposed to be used for any other forms that are not the only content on the page (e.g. inline-forms)
+ 2) .icinga-controls, this defines the design of our controls. Any input that's part of a container with
+ this class gets our design applied
+ */
+
+// General form layout
+
+form.icinga-form {
+ max-width: 70em;
+ width: 80%;
+
+ .control-group {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+
+ // Negative margin-right because every child gets 1em right but we can't exclude
+ // the last element as it's impossible to identify the last *visible* element
+ margin: 1em -1em 1em 0;
+
+ > fieldset {
+ > .control-group:first-of-type {
+ margin-top: 0;
+ }
+
+ > .control-group:last-of-type {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ .control-group > :not(.control-label-group) {
+ margin-right: 1em;
+ }
+
+ .form-controls {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ &.inline {
+ width: auto;
+
+ .control-group {
+ margin: 0;
+ align-items: center;
+
+ > :not(.control-label-group) {
+ margin-right: .5em;
+ }
+
+ &:last-child {
+ margin-right: -.5em;
+ }
+ }
+ }
+}
+
+form.inline {
+ display: inline-block;
+
+ fieldset {
+ display: inline-block;
+ vertical-align: top;
+ border: none;
+ }
+}
+
+// Minimal form layout
+
+#layout.minimal-layout,
+#layout.twocols:not(.wide-layout) {
+ form.icinga-form {
+ &:not(.inline) {
+ width: 100%;
+ }
+
+ .control-label-group {
+ text-align: left;
+ padding-bottom: 0;
+ padding-left: 0;
+ margin-bottom: 0;
+ }
+
+ .toggle-switch ~ .control-info:before {
+ margin-left: 0;
+ }
+
+ .errors {
+ margin: 0;
+ }
+ }
+}
+
+#layout.minimal-layout .icinga-form {
+ .form-controls {
+ input[type="submit"] {
+ width: 100%;
+
+ &:not(:last-child) {
+ margin-bottom: 1em;
+ }
+ }
+ }
+}
+
+// Label styles
+
+form.icinga-form .control-label-group {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ line-height: 1.1em;
+ padding: .5625em .5625em .5625em 0;
+ max-height: 2.5em;
+ text-align: right;
+ width: 14em;
+}
+
+form.icinga-form.inline .control-label-group {
+ width: auto;
+ line-height: 0.857em;
+}
+
+.icinga-controls fieldset {
+ margin: 0;
+ padding: 0;
+ border: none;
+
+ legend {
+ font-weight: bold;
+ font-size: 4/3em;
+ margin: 0.556em 0 0.333em;
+ }
+}
+
+.icinga-controls .control-info {
+ line-height: 2.25em;
+ opacity: .6;
+
+ &:before {
+ margin-right: 0;
+ }
+
+ &:hover {
+ opacity: 1;
+ }
+}
+
+form.icinga-form .control-group .control-info {
+ margin-left: -.5em;
+}
+form.icinga-form .control-group .toggle-switch ~ .control-info {
+ margin-left: 0;
+}
+
+// General input styles
+
+.icinga-controls {
+ input[type="text"],
+ input[type="password"],
+ input[type="number"],
+ input[type="datetime-local"],
+ input[type="date"],
+ input[type="time"],
+ input[type="file"],
+ textarea,
+ select {
+ background-color: @low-sat-blue;
+ }
+}
+
+form.icinga-form {
+ input[type="text"],
+ input[type="password"],
+ input[type="number"],
+ input[type="datetime-local"],
+ input[type="date"],
+ input[type="time"],
+ input[type="file"],
+ .control-group > fieldset,
+ textarea,
+ select {
+ flex: 1 1 auto;
+ width: 0;
+ }
+}
+
+.icinga-controls {
+ input:not([type="radio"]),
+ .toggle-switch,
+ button,
+ select,
+ textarea {
+ border: none;
+ .rounded-corners(.25em);
+ .appearance(none);
+ }
+}
+
+.icinga-controls {
+ input:not([type="checkbox"]),
+ .toggle-switch,
+ select,
+ textarea,
+ button,
+ .toggle-switch {
+ font-size: inherit;
+ padding: @vertical-padding;
+ }
+
+ input[type="radio"] {
+ margin-right: .25em;
+ }
+}
+
+form.icinga-form {
+ .control-group .toggle-switch,
+ .form-controls .toggle-switch {
+ margin-top: 0.5em*0.666666667;
+ margin-bottom: 0.5em*0.666666667;
+ }
+}
+
+form.icinga-form select:not([multiple]) {
+ // Compensate inconsistent select height calculations
+ line-height: 1em;
+ height: 2.25em;
+}
+
+// Remove native dropdown arrow in IE10+
+.icinga-controls select::-ms-expand {
+ display: none;
+ opacity: 0;
+}
+
+.icinga-controls select:not([multiple]) {
+ padding-right: 1.5625em;
+ background-image: url(../img/select-icon.svg);
+ background-repeat: no-repeat;
+ background-position: right center;
+ background-size: contain;
+}
+
+form.icinga-form select {
+ width: 0; // Prevent selects with long option values from exceeding the container
+}
+
+form.inline select {
+ width: auto;
+}
+
+
+// Specific input styles
+
+.link-button {
+ .action-link();
+ // Reset defaults
+ background: none;
+ border: none;
+ display: inline-block;
+ padding: 0;
+
+ text-align: left;
+}
+
+.icinga-controls {
+ input ~ .spinner,
+ button ~ .spinner,
+ select ~ .spinner,
+ textarea ~ .spinner {
+ line-height: normal;
+ padding: .5em 0;
+
+ &:before {
+ vertical-align: middle;
+ margin-left: .5em;
+ opacity: 0.4;
+ }
+ }
+}
+
+/* selects get their spinner specifically placed */
+.icinga-controls select:not([multiple]) + .spinner {
+ height: 2.25em;
+ margin: 0;
+
+ &:before {
+ margin-left: -3.75em;
+ }
+}
+
+form.icinga-form .form-controls {
+ .spinner {
+ order: -1;
+ }
+
+ .btn-primary {
+ order: 99;
+ }
+}
+
+// Button styles
+
+.icinga-controls {
+ button:not([type]),
+ button[type="submit"],
+ input[type="submit"],
+ input[type="submit"].btn-confirm {
+ .button();
+ }
+
+ input[type="submit"].btn-remove {
+ .button(@body-bg-color, @color-critical, @color-critical-accentuated);
+ }
+
+ input[type="submit"].btn-cancel {
+ .button(@body-bg-color, @gray, @black);
+ }
+
+ button.noscript-apply {
+ color: @gray;
+ background-color: @gray-lightest;
+ border-color: @gray;
+ border-width: 1px;
+ }
+
+ button[type="button"] {
+ background-color: @low-sat-blue;
+ }
+}
+
+form.icinga-form {
+ button[type="button"] {
+ line-height: normal;
+ }
+}
+
+form.inline {
+ :not([type="hidden"]) {
+ & ~ button:not([type]),
+ & ~ button[type="submit"],
+ & ~ input[type="submit"],
+ & ~ input[type="submit"].btn-confirm {
+ margin-left: @horizontal-padding;
+ }
+ }
+
+ button.noscript-apply {
+ margin-left: .5em;
+ padding: .1em;
+ }
+}
+
+// Toggle styles
+
+.icinga-controls .toggle-switch {
+ cursor: pointer;
+ position: relative;
+ display: inline-block;
+ height: 1.5em;
+ width: 2.625em;
+}
+
+.icinga-controls .toggle-switch .toggle-slider {
+ position: absolute;
+ left: 0;
+ top: 0;
+
+ display: inline-block;
+ background: @low-sat-blue;
+ border: 1px solid;
+ border-color: @low-sat-blue;
+ box-sizing: content-box;
+ border-radius: 1em;
+ height: 4/3em;
+ width: 8/3em;
+ vertical-align: middle;
+}
+
+.icinga-controls .toggle-switch .toggle-slider:before {
+ position: absolute;
+ top: 0;
+ left: 0;
+
+ background: @text-color-inverted;
+ border-radius: 1em;
+ border: 1px solid;
+ border-color: @low-sat-blue;
+ box-sizing: border-box;
+ content: "";
+ display: block;
+ height: 4/3em;
+ margin-left: 0;
+ width: 4/3em;
+
+ @transition: left .2s ease, margin .2s ease;
+ -webkit-transition: @transition;
+ -moz-transition: @transition;
+ -o-transition: @transition;
+ transition: @transition;
+}
+
+.icinga-controls input[type="checkbox"]:checked + .toggle-switch .toggle-slider {
+ background-color: @icinga-blue;
+ border: 1px solid;
+ border-color: @icinga-blue;
+}
+
+.icinga-controls input[type="checkbox"]:focus + .toggle-switch .toggle-slider {
+ box-shadow: 0 0 0 2px @body-bg-color, 0 0 0 4px @icinga-blue-light;
+}
+
+.icinga-controls input[type="checkbox"]:checked + .toggle-switch .toggle-slider:before {
+ border: 1px solid;
+ border-color: @icinga-blue;
+ left: 100%;
+ margin-left: -4/3em;
+}
+
+// Disabled inputs
+
+.icinga-controls .toggle-switch.disabled {
+ cursor: default;
+
+ & > .toggle-slider {
+ background-color: @gray-light;
+ border-color: @gray-light;
+
+ &:before {
+ background-color: @gray-lighter;
+ border-color: @gray-light;
+ }
+ }
+}
+
+form.icinga-form .control-group.disabled .control-label-group {
+ color: @disabled-gray;
+}
+
+.icinga-controls {
+ input[disabled],
+ select[disabled] {
+ background-color: @gray-lighter;
+ border-color: transparent;
+ }
+}
+
+// Errors and additional information
+
+form.icinga-form {
+ .form-notifications,
+ .form-description {
+ border-radius: .25em;
+ display: flex;
+ list-style: none;
+ align-items: center;
+ margin: 0 0 1em 0;
+ padding: .25em .5em;
+
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 0 .5em;
+ }
+
+ li {
+ list-style: none;
+ }
+
+ & .form-notification-icon,
+ & .form-description-icon {
+ font-size: 2em;
+ margin-left: .25em;
+ opacity: .4;
+ align-self: flex-start;
+ }
+ }
+
+ .form-notifications {
+ &.info {
+ background-color: @form-info-bg-color;
+ }
+
+ &.error {
+ background-color: @form-error-bg-color;
+ }
+
+ &.warning {
+ background-color: @form-warning-bg-color;
+ }
+ }
+
+ .form-description {
+ background-color: @light-text-bg-color;
+ }
+
+ .errors {
+ list-style: none;
+ display: block;
+ width: 100%;
+ margin: 0 0 0 15em;
+ padding: 0;
+
+ & > li {
+ margin: 0.5em;
+ color: #f56;
+ }
+ }
+}
+
+form.icinga-form .form-info {
+ color: @text-color-light;
+ font-size: @font-size-small;
+ list-style: none;
+ padding-left: 0;
+}
+
+// Placeholder styles
+
+.icinga-controls {
+ input::placeholder {
+ color: @disabled-gray;
+ font-style: italic;
+ opacity: 1;
+ }
+
+ input:-ms-input-placeholder {
+ color: @disabled-gray;
+ font-style: italic;
+ opacity: 1;
+ }
+}
+
+// Specific form styles
+
+.search.inline {
+ display: inline-block;
+}
+
+/* Flyover form styles */
+
+.flyover-content form:not(.inline):not([role="search"]) {
+ width: auto;
+}
+
+.flyover-content .control-label-group {
+ text-align: left;
+}
+
+.theme-mode-input {
+ display: none;
+
+ &:checked + img {
+ border-color: @icinga-blue;
+ border-radius: .25em;
+ }
+
+ & + img {
+ margin: 0 auto;
+ border: .25em solid transparent;
+ display: block;
+ width: 6em;
+ overflow: hidden;
+ box-shadow: 0 0 .25em 0 rgba(0,0,0,.4);
+ }
+
+ &[disabled] ~ img,
+ &[disabled] ~ span {
+ opacity: .25;
+ }
+
+ & ~ span {
+ display: block;
+ text-align: center;
+ }
+}
+
+#layout.minimal-layout .icinga-form {
+ .theme-mode {
+ .control-label-group {
+ width: 100%;
+ margin-bottom: .5em;
+ }
+
+ label:first-of-type {
+ margin-left: auto;
+ }
+ }
+}
diff --git a/public/css/icinga/grid.less b/public/css/icinga/grid.less
new file mode 100644
index 0000000..e061bc8
--- /dev/null
+++ b/public/css/icinga/grid.less
@@ -0,0 +1,47 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+.grid {
+ .clearfix();
+}
+
+[class^="col-"],
+[class*=" col-"] {
+ float: left;
+ // Fix that empty columns don't consume their width
+ min-height: 1px;
+}
+
+.controls {
+ [class^="col-"],
+ [class*=" col-"] {
+ padding: @vertical-padding / 2 0;
+ }
+}
+
+.col-1-2 {
+ width: 50%;
+}
+
+.col-1-3 {
+ width: 33.33%;
+}
+
+.col-2-3 {
+ width: 66.66%;
+}
+
+.col-3-3 {
+ width: 100%;
+}
+
+// TODO(el): Set proper breakpoints
+#layout.twocols,
+#layout.compact-layout,
+#layout.minimal-layout,
+#layout.poor-layout {
+ [class^="col-"],
+ [class*=" col-"] {
+ float: none;
+ width: 100%;
+ }
+}
diff --git a/public/css/icinga/health.less b/public/css/icinga/health.less
new file mode 100644
index 0000000..50cc11a
--- /dev/null
+++ b/public/css/icinga/health.less
@@ -0,0 +1,69 @@
+// Style
+
+.app-health {
+ header {
+ color: @text-color-light;
+
+ span {
+ color: @text-color;
+ }
+ }
+
+ span {
+ &.state-ok {
+ background-color: @color-ok;
+ }
+
+ &.state-warning {
+ background-color: @color-warning;
+ }
+
+ &.state-critical {
+ background-color: @color-critical;
+ }
+
+ &.state-unknown {
+ background-color: @color-unknown;
+ }
+ }
+
+ a {
+ font-weight: bold;
+ }
+
+ tbody tr, tr.active {
+ border: none;
+ }
+
+ tr:not(:last-child) td {
+ border: 0 solid;
+ border-color: @gray-light;
+ border-bottom-width: 1px;
+ }
+
+ section {
+ color: @text-color-light;
+ font-family: @font-family-fixed;
+ }
+}
+
+// Layout
+
+.app-health {
+ th {
+ width: 2.5em;
+ padding: .5em 1em 0 .5em;
+ vertical-align: top;
+ }
+
+ td {
+ padding: .5em 0;
+ }
+
+ section {
+ margin-top: .25em;
+ height: 3em;
+ overflow: hidden;
+ word-break: break-word;
+ }
+}
diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less
new file mode 100644
index 0000000..b1ca8e6
--- /dev/null
+++ b/public/css/icinga/layout-structure.less
@@ -0,0 +1,167 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+html {
+ height: 100%;
+ font-family: 'default-layout';
+}
+
+body {
+ height: 100%;
+ overflow: hidden;
+}
+
+#layout {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+#content-wrapper {
+ flex: 1 1 auto;
+ display: flex;
+ height: 0;
+
+}
+
+#sidebar {
+ width: 16em;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ z-index: 2;
+}
+
+#layout:not(.minimal-layout) #sidebar:after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 1em;
+ background: linear-gradient(to left, rgba(0,0,0,.1), rgba(0,0,0,0));
+ z-index: 0;
+ pointer-events: none;
+}
+
+#main {
+ flex: 1;
+ display: flex;
+ z-index: 1;
+}
+
+.iframe {
+ #header, #sidebar {
+ display: none;
+ }
+}
+
+#responsive-debug {
+ font-size: 0.9em;
+ font-family: Courier new, monospace;
+ padding: 0.5em;
+ width: 25em;
+ color: white;
+ height: 10em;
+ display: none;
+ position: fixed;
+ bottom: 0.5em;
+ right: 2em;
+ overflow: hidden;
+ z-index: 1000;
+ background: #333;
+ border-radius: 0.5em;
+ opacity: 0.9;
+}
+
+#layout.minimal-layout #responsive-debug {
+ font-size: 0.6em;
+}
+
+#layout.poor-layout #responsive-debug {
+ font-size: 0.7em;
+}
+
+#layout.compact-layout #responsive-debug {
+ font-size: 0.8em;
+}
+
+#layout.wide-layout #responsive-debug {
+ font-size: 1em;
+}
+
+/** Fullscreen layout **/
+#layout.fullscreen-layout {
+ #sidebar {
+ display: none;
+ }
+
+ .container .controls {
+ padding: 0;
+ }
+
+ .controls > ul.tabs {
+ margin-top: 0;
+ height: 1.5em;
+ font-size: 0.75em;
+ padding: 0.2em 0 0;
+ }
+
+ .controls > ul.tabs > li > a {
+ line-height: 1.5em;
+ }
+}
+
+.controls-separated,
+.container .controls.separated {
+ box-shadow: 0 3px 4px -4px rgba(0, 0, 0, 0.2);
+// border-bottom: 1px solid @gray-lightest;
+ padding-bottom: @horizontal-padding / 2
+}
+
+.hbox {
+ display: inline-block;
+}
+
+.hbox-item {
+ display: inline-block;
+ vertical-align: top;
+ margin-top: 0.5em;
+ margin-bottom: 0.25em;
+ margin-left: 1em;
+ margin-right: 1em;
+}
+
+.hbox-spacer {
+ display: inline-block;
+ vertical-align: top;
+ width: 2em;
+}
+
+/*
+ * Class to hide content from users but available for screen reader
+ * Based on: https://cloudfour.com/thinks/see-no-evil-hidden-content-and-accessibility/
+ */
+.sr-only {
+ border: 0;
+ clip: rect(0 0 0 0);
+ clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
+ -webkit-clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: fixed; // absolute causes view port glitches in chrome (#4310)
+ width: 1px;
+ white-space: nowrap;
+}
+
+// Hide non-javascript elements if javascript is enabled
+html.js *.no-js {
+ .sr-only;
+}
+
+// Hide javascript elements if javascript is disabled
+html.no-js *.js {
+ .sr-only;
+}
diff --git a/public/css/icinga/layout.less b/public/css/icinga/layout.less
new file mode 100644
index 0000000..c37da79
--- /dev/null
+++ b/public/css/icinga/layout.less
@@ -0,0 +1,379 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+#footer {
+ bottom: 0;
+ right: 0;
+ left: 12em;
+ position: fixed;
+ z-index: 999;
+}
+
+#layout.minimal-layout #footer {
+ left: 0;
+}
+
+.sidebar-collapsed #footer {
+ left: 3em;
+}
+
+#guest-error {
+ background-color: @icinga-blue;
+ height: 100%;
+ overflow: auto;
+}
+
+#guest-error #icinga-logo {
+ .fadein();
+}
+
+#guest-error-message {
+ .fadein();
+ color: @body-bg-color;
+ font-size: 2em;
+}
+
+#header,
+#login,
+#content-wrapper {
+ font-size: @font-size;
+ line-height: @line-height;
+}
+
+#header-logo-container {
+ background: @menu-bg-color;
+ height: 6em;
+ padding: 1.25em;
+ width: 16em;
+}
+
+#header-logo,
+#mobile-menu-logo {
+ background-image: url('../img/icinga-logo.svg');
+ background-position: center center;
+ background-repeat: no-repeat;
+ background-size: contain;
+ display: block;
+ height: 100%;
+ width: 100%;
+
+ &:focus {
+ opacity: .6;
+ outline: none;
+ }
+}
+
+#mobile-menu-logo {
+ width: 50%;
+ float: left;
+ height: 2em;
+ margin-top: .25em;
+ background-position: .75em center;
+}
+
+#mobile-menu-toggle .icon-cancel {
+ display: none;
+}
+
+#icinga-logo {
+ background-image: url('../img/icinga-logo-big.svg');
+ background-position: center bottom;
+ background-repeat: no-repeat;
+ background-size: contain; // Does not work in IE < 10
+ height: 177px;
+ margin-bottom: 2em;
+ width: 100%;
+
+ &.invert {
+ background-image: url('../img/icinga-logo-big-dark.svg');
+ }
+}
+
+#layout {
+ background-color: @body-bg-color;
+ color: @text-color;
+ font-family: @font-family;
+}
+
+#login {
+ overflow: auto;
+}
+
+@gutter: 1em;
+
+// x-column-layout
+#main {
+ .clearfix();
+
+ & > .container {
+ width: 0;
+ overflow: auto;
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+
+ &:empty {
+ display: none;
+ }
+
+ & > .content {
+ flex: 1 1 auto;
+ overflow: auto;
+ }
+
+ & > .controls {
+ > .tabs {
+ // Remove gutter for tabs
+ margin-left: -1 * @gutter;
+ margin-right: -1 * @gutter;
+ height: 2.5em;
+ }
+
+ .tabs:first-child:not(:last-child) {
+ margin-bottom: .5em;
+ }
+ }
+ }
+}
+
+// Not part of the above to relax specificity and to allow modules adjust this
+:not(.dashboard) > .container {
+ & > .controls {
+ padding-left: @gutter;
+ padding-right: @gutter;
+ }
+
+ & > .content {
+ padding: @gutter;
+ }
+}
+
+// Mobile menu
+#layout.minimal-layout #sidebar {
+ background-color: @menu-bg-color;
+}
+
+#mobile-menu-toggle {
+ color: @menu-color;
+ text-align: right;
+
+ > button {
+ background: none;
+ border: none;
+ font-size: 2em;
+ padding: 0 .5em;
+ line-height: 2;
+
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ -ms-appearance: none;
+ appearance: none;
+ }
+
+ i:before {
+ margin-right: 0;
+ }
+}
+
+.container,
+.error-message,
+.modal-window {
+ // Don't outline containers and error messages when focused because they receive focus for accessibility only
+ // programmatically
+ outline: none;
+}
+
+.controls {
+ > .tabs {
+ overflow: hidden;
+ }
+}
+
+// Dashboard grid
+
+.dashboard {
+ letter-spacing: -0.417em;
+
+ > .container {
+ display: inline-block;
+ letter-spacing: normal;
+ vertical-align: top;
+ // Column width controlled by #layout
+ width: 100%;
+
+ &:last-of-type {
+ // See reponsive.less for gutters
+ padding-right: 0;
+ }
+ }
+}
+
+// Notification styles
+
+#notifications {
+ // Reset defaults
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+#notifications > li {
+ color: @text-color;
+ display: block;
+ line-height: 2.5em;
+ border-left: .5em solid @gray-light;
+ background: @body-bg-color;
+ margin-bottom: 1px;
+ box-shadow: 0 0 1em 0 rgba(0,0,0,0.25);
+
+ .icon {
+ padding: .5em;
+ width: 3em;
+ text-align: center;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &.error {
+ border-color: @color-notification-error;
+ background: @color-notification-error;
+ color: @text-color-on-icinga-blue;
+
+ .icon {
+ color: @text-color-on-icinga-blue;
+ }
+ }
+
+ &.info {
+ border-color: @color-notification-info;
+
+ .icon {
+ color: @color-notification-info;
+ }
+ }
+
+ &.success {
+ border-color: @color-notification-success;
+
+ .icon {
+ color: @color-notification-success;
+ }
+ }
+
+ &.warning {
+ border-color: @color-notification-warning;
+ background: @color-notification-warning;
+ color: @text-color-inverted;
+
+ .icon {
+ color: @text-color-inverted;
+ }
+ }
+}
+
+// Collapsed sidebar
+#layout:not(.minimal-layout).sidebar-collapsed {
+ #header-logo-container {
+ height: 3em;
+ padding: 0.25em 0.125em;
+ width: 4em;
+ }
+
+ #header-logo {
+ background-image: url('../img/icinga-logo-compact.svg');
+ }
+
+ #sidebar {
+ width: 4em;
+ }
+
+ #open-sidebar {
+ display: inline;
+ }
+
+ #close-sidebar {
+ display: none;
+ }
+
+ #menu {
+ .nav-level-1 {
+ > .badge-nav-item > a {
+ position: relative;
+
+ > .badge {
+ position: absolute;
+ right: .5em;
+ bottom: .25em;
+ font-size: 75%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 4em;
+ }
+ }
+
+ > .nav-item.active > a > .badge {
+ display: unset;
+ }
+ }
+
+ img.icon {
+ margin: 0 1.25em -.25em 0.25em;
+ font-size: 1.5em;
+ }
+
+ .nav-item {
+ white-space: nowrap;
+ }
+
+ .nav-item.no-icon > a {
+ padding-left: .75em;
+ }
+
+ .nav-level-1 > .nav-item i {
+ font-size: 1.5em;
+ margin-right: .5em;
+ }
+
+ > .search-control {
+ height: 3.333em;
+ }
+ }
+
+ #search {
+ padding-left: 3.75em;
+ }
+
+ #search:focus {
+ background-color: @menu-bg-color;
+ border-radius: 0 .25em .25em 0;
+ box-shadow: 0 0 .25em 0 rgba(0, 0, 0, .2);
+ color: @menu-color;
+ width: 20em;
+ position: fixed;
+ z-index: 1;
+ }
+
+ .search-input {
+ font-size: 1.25em;
+ padding-right: .625em;
+ }
+
+ .search-reset {
+ display: none;
+ }
+
+ .skip-links {
+ a, button {
+ width: 20em;
+ }
+ }
+}
+
+@light-mode: {
+ #header-logo,
+ #mobile-menu-logo,
+ #about .icinga-logo {
+ filter: brightness(0.415) sepia(1) ~"saturate(0.1)" hue-rotate(144deg);
+ }
+};
diff --git a/public/css/icinga/login-orbs.less b/public/css/icinga/login-orbs.less
new file mode 100644
index 0000000..b0426dd
--- /dev/null
+++ b/public/css/icinga/login-orbs.less
@@ -0,0 +1,104 @@
+/*! Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+#login {
+ background-image: none;
+
+ .login-form {
+ background: none;
+ box-shadow: none;
+ }
+}
+
+.orb {
+ display: block;
+ position: absolute;
+ pointer-events: none;
+ transform-origin: center center;
+}
+
+.orb img {
+ height: auto;
+ width: 100%;
+}
+
+#orb-analytics {
+ top: -19%;
+ width: 25%;
+ left: 22.5%;
+ z-index: 0;
+}
+
+#orb-analytics img {
+ opacity: .2;
+}
+
+#orb-automation {
+ bottom: -6%;
+ width: 60%;
+ left: 7%;
+ z-index: 0;
+ margin-left: -30%;
+ margin-bottom: -30%;
+}
+
+#orb-automation img {
+ opacity: .75;
+}
+
+#orb-cloud {
+ top: -6%;
+ width: 25%;
+ right: 4%;
+ z-index: 0;
+ margin-right: -12.5%;
+ margin-top: -12.5%;
+}
+
+#orb-cloud img {
+ opacity: .4;
+}
+
+#orb-notifactions {
+ top: 7%;
+ right: 46%;
+ width: 10%;
+ margin: -5%;
+}
+
+#orb-notifactions img {
+ opacity: .5;
+}
+
+#orb-metrics {
+ left: 5%;
+ top: 20%;
+ width: 35%;
+ margin: -17.5%;
+}
+
+#orb-metrics img {
+ opacity: .5;
+}
+
+#orb-icinga {
+ left: 50%;
+ top: 50%;
+ margin-top: -38.5em;
+ margin-left: -38em;
+ width: 75em;
+ z-index: 0;
+}
+
+#orb-icinga img {
+ opacity: .8;
+}
+
+#orb-infrastructure {
+ top: -36%;
+ left: -15%;
+ width: 30%;
+}
+
+#orb-infrastructure img {
+ opacity: .6;
+}
diff --git a/public/css/icinga/login.less b/public/css/icinga/login.less
new file mode 100644
index 0000000..b37dbd8
--- /dev/null
+++ b/public/css/icinga/login.less
@@ -0,0 +1,183 @@
+/*! Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+// Login page styles
+
+#login {
+ height: 100%;
+ background-color: @menu-bg-color;
+ background-image: url(../img/icingaweb2-background-orbs.jpg);
+ background-repeat: no-repeat;
+ background-size: cover;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ .login-form {
+ width: 36em;
+ position: relative;
+ z-index: 10;
+ padding: 2em 6em;
+ background-color: @login-box-background;
+ .box-shadow(0, 0, 1em, 1em, @login-box-background);
+ }
+
+ #icinga-logo {
+ width: 100%;
+ max-width: 18em;
+ height: auto;
+ margin: 0 auto 2em auto;
+
+ &:after {
+ content: "";
+ display: block;
+ width: 100%;
+ padding-bottom: 35%;
+ }
+ }
+
+ .errors,
+ .form-errors {
+ list-style-type: none;
+ padding: 0.5em;
+ }
+
+ .errors {
+ background-color: @color-critical;
+ color: white;
+ }
+
+ .form-errors {
+ margin-top: 0;
+ padding: 0;
+ }
+
+ .form-errors,
+ .control-group {
+ &:not(:last-child) {
+ margin-bottom: 1em;
+ }
+ }
+
+ input[type=password],
+ input[type=text] {
+ display: block;
+ height: 2.5em;
+ margin: 0;
+ transition: none;
+ width: 100%;
+
+ &:focus {
+ .rounded-corners(3px);
+ border-radius: 0;
+ padding-bottom: 3px;
+ }
+ }
+
+ input[type="submit"]:focus {
+ outline: 3px solid;
+ outline-color: @icinga-blue-light;
+ }
+
+ input[type=submit] {
+ border-radius: .25em;
+ background: @icinga-secondary;
+ color: white;
+ border: none;
+ height: 2.5em;
+ margin: 0;
+ width: 100%;
+
+ &:hover {
+ background-color: @icinga-secondary-dark;
+ }
+ }
+
+ .config-note {
+ background-color: @color-critical;
+ margin: 0 auto 2em auto; // Center horizontally w/ bottom margin
+ max-width: 50%;
+ min-width: 24em;
+ padding: 1em;
+
+ a {
+ color: @text-color-inverted;
+ font-weight: bold;
+ }
+ }
+
+ .remember-me-box {
+ display: flex;
+ align-items: flex-start;
+
+ .toggle-switch {
+ margin-right: 1em;
+ }
+
+ .control-info {
+ line-height: 1.5;
+ margin-left: .5em;
+ }
+ }
+}
+
+#social {
+ position: fixed;
+ right: 1em; bottom: 1em;
+ letter-spacing: -.417em;
+ margin: 0;
+
+ > * {
+ letter-spacing: normal;
+ }
+
+ > li {
+ display: inline-block;
+
+ a {
+ display: block;
+ text-decoration: none;
+ -webkit-transform: scale(1, 1);
+ -moz-transform: scale(1, 1);
+ -ms-transform: scale(1, 1);
+ transform: scale(1, 1);
+ }
+
+ i {
+ font-size: 3em;
+ color: white;
+ text-shadow: 0 0 .5em #01507B;
+ }
+ }
+
+ > li a:hover {
+ -webkit-transform: scale(1.2, 1.2);
+ -moz-transform: scale(1.2, 1.2);
+ -ms-transform: scale(1.2, 1.2);
+ transform: scale(1.2, 1.2);
+ }
+
+ li:not(:last-child) {
+ margin-right: 2em;
+ }
+}
+
+#login-footer {
+ padding: .5em 0;
+
+ p {
+ margin-bottom: 0;
+ }
+
+ a {
+ text-decoration: underline;
+
+ &:hover {
+ opacity: .8;
+ }
+ }
+}
+
+.orb {
+ display: none;
+}
diff --git a/public/css/icinga/main.less b/public/css/icinga/main.less
new file mode 100644
index 0000000..bfe97d5
--- /dev/null
+++ b/public/css/icinga/main.less
@@ -0,0 +1,452 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+// Url for static ipl assets
+@iplWebAssets: "../lib/icinga/icinga-php-library";
+
+// Width for the name column--th--of name-value-table
+@name-value-table-name-width: 38/3em;
+
+.action-link {
+ color: @icinga-blue;
+}
+
+.error-message {
+ font-weight: @font-weight-bold;
+}
+
+.error-reason {
+ margin-top: 4em;
+}
+
+.large-icon {
+ font-size: 200%;
+}
+
+.content-centered {
+ margin: 0 auto;
+ text-align: center;
+}
+
+.icon-col {
+ text-align: center;
+ width: 1em;
+}
+
+.preformatted {
+ font-family: @font-family-fixed;
+ white-space: pre-wrap;
+}
+
+.markdown {
+ > * {
+ margin-left: 0;
+ margin-right: 0;
+ }
+
+ > *:last-child {
+ margin-bottom: 0;
+ }
+
+ img {
+ max-width: 100%;
+ height: auto;
+ }
+
+ a {
+ border-bottom: 1px @text-color-light dotted;
+
+ &:hover, &:focus {
+ border-bottom: 1px @text-color solid;
+ text-decoration: none;
+ }
+
+ img {
+ max-width: 32em;
+ }
+
+ &.with-thumbnail {
+ img {
+ padding: 1px;
+ }
+
+ &:hover, &:focus {
+ img {
+ padding: 0;
+ }
+ }
+ }
+ }
+
+ table {
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ background-color: @gray-lighter;
+ }
+
+ &, th, td {
+ border: 1px solid @gray-light;
+ }
+ }
+}
+
+.no-wrap {
+ white-space: nowrap;
+}
+
+.pull-right {
+ float: right;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.user-avatar {
+ height: 16px;
+ width: 16px;
+}
+
+.v-center {
+ > * {
+ vertical-align: middle;
+ }
+}
+
+.section {
+ margin-bottom: 2em;
+}
+
+a:hover > .icon-cancel {
+ color: @color-critical;
+}
+
+.icon-stateful {
+ .fg-stateful();
+}
+
+// Link styles
+
+.button-link {
+ .action-link();
+ .rounded-corners(3px);
+
+ background: @low-sat-blue;
+ display: inline-block;
+ padding: 0.25em 0.5em;
+
+ &:hover {
+ background: @low-sat-blue-dark;
+ text-decoration: none;
+ }
+}
+
+// List styles
+
+.comment-list {
+ margin: 0;
+
+ > dt {
+ border-bottom: 1px solid @gray-lighter;
+ margin-bottom: 0.25em;
+
+ &:hover {
+ background-color: @gray-lightest;
+
+ > .remove-action button:not(.spinner.active) {
+ visibility: visible;
+ }
+ }
+
+ > .remove-action button:not(.spinner.active) {
+ visibility: hidden;
+ }
+ }
+
+ > dd {
+ margin: 0 0 1em 0;
+ }
+}
+
+.comment-time {
+ color: @text-color-light;
+ font-size: @font-size-small;
+}
+
+.name-value-list {
+ > dd {
+ // Reset default margin
+ margin: 0;
+ }
+
+ > dt {
+ color: @text-color-light;
+ font-size: @font-size-small;
+ }
+}
+
+// Table styles
+
+.common-table {
+ width: 100%;
+
+ td, th {
+ padding-top: 1em;
+ }
+
+ td {
+ padding-bottom: 1em;
+ }
+
+ th {
+ text-align: left;
+ padding-bottom: 0.5em;
+ }
+
+ thead {
+ border-bottom: 1px solid @gray-light;
+ }
+
+ tbody tr {
+ border-bottom: 1px solid @gray-lightest;
+ border-left: 5px solid transparent;
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+
+ tr[href].active {
+ background-color: @tr-active-color;
+ border-left-color: @icinga-blue;
+ }
+
+ tr[href]:hover {
+ background-color: @tr-hover-color;
+ cursor: pointer;
+ }
+}
+
+.name-value-table {
+ width: 100%;
+}
+
+.name-value-table > caption {
+ margin-top: .5em;
+ text-align: left;
+ font-weight: bold;
+}
+
+.name-value-table > tbody > tr > th {
+ color: @text-color-light;
+ // Reset default font-weight
+ font-weight: normal;
+ padding-left: 0;
+ text-align: left;
+ vertical-align: top;
+ width: @name-value-table-name-width;
+}
+
+/* Styles for centering content of unknown width and height both horizontally and vertically
+ *
+ * Example markup:
+ * <div class="centered-ghost">
+ * <div class="centered-content">
+ * <p>I'm centered.</p>
+ * </div>
+ * </div>
+ */
+
+.centered-content {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.centered-ghost {
+ height: 100%;
+ text-align: center;
+ letter-spacing: -0.417em; // Remove gap between content and ghost
+}
+
+.centered-ghost > * {
+ letter-spacing: normal;
+}
+
+.centered-ghost:after {
+ content: '';
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+}
+
+// Responsive iFrames
+
+.iframe-container {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ padding-bottom: 75%;
+ width: 100%;
+
+ & > iframe {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ }
+}
+
+// Collapsible Control
+#collapsible-control-ghost {
+ display: none;
+}
+
+.collapsible + .collapsible-control {
+ position: relative;
+ z-index: 1;
+
+ button {
+ .rounded-corners(50%);
+
+ float: right;
+ width: 2em;
+ height: 2em;
+ padding: 0;
+ margin-top: -1em;
+ margin-right: .25em;
+
+ background: @gray-lighter;
+ color: @gray;
+ border: none;
+ -webkit-box-shadow: 0 0 1/3em rgba(0,0,0,.3);
+ -moz-box-shadow: 0 0 1/3em rgba(0,0,0,.3);
+ box-shadow: 0 0 1/3em rgba(0,0,0,.3);
+
+ &:hover {
+ background: @gray-light;
+ }
+ }
+
+ button i:before {
+ margin-right: 0;
+ }
+}
+
+.collapsible[data-can-collapse]:not(.collapsed) + .collapsible-control button,
+.collapsible[data-can-collapse]:not(.collapsed) > .collapsible-control,
+details.collapsible[open] + .collapsible-control button,
+details.collapsible[open] > .collapsible-control {
+ i.expand-icon {
+ display: none;
+ }
+
+ i.collapse-icon {
+ display: inline;
+ }
+}
+
+.collapsible.collapsed + .collapsible-control button,
+.collapsible.collapsed > .collapsible-control,
+details.collapsible:not([open]) + .collapsible-control button,
+details.collapsible:not([open]) > .collapsible-control {
+ i.expand-icon {
+ display: inline;
+ }
+
+ i.collapse-icon {
+ display: none;
+ }
+}
+
+// Collapsibles
+
+.collapsible.collapsed:not(details) {
+ overflow: hidden;
+}
+
+.collapsible.collapsed:not([data-toggle-element], details) {
+ position: relative;
+
+ &:after {
+ content: "";
+ display: block;
+ height: 2em;
+ background: linear-gradient(@body-bg-color-transparent, @body-bg-color);
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1;
+
+ opacity: 1;
+ transition: opacity 2s 1s linear;
+ }
+}
+
+.role-memberships {
+ letter-spacing: -0.417em;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+ > li {
+ display: inline-block;
+ letter-spacing: normal;
+ margin: 0;
+ padding: 0 0.25em 0 0;
+
+ &:last-child {
+ padding-right: 0;
+ }
+ }
+}
+
+.module-dependencies {
+ .unmet-dependencies {
+ background-color: @color-warning;
+ color: @text-color-on-icinga-blue;
+ padding: .25em .5em;
+ margin-left: -.5em;
+ }
+
+ .name-value-table {
+ > caption {
+ font-weight: normal;
+ color: @text-color-light;
+ }
+
+ > tbody > tr > th {
+ font-weight: bold;
+ color: @text-color;
+ }
+
+ .missing {
+ color: @color-critical;
+ font-weight: bold;
+ }
+
+ td {
+ white-space: nowrap;
+
+ &.or-separator {
+ width: 100%;
+ transform: translate(0, 50%);
+ padding-left: 3em;
+
+ &::before {
+ content: "";
+ position: absolute;
+ height: 1.5em;
+ width: 1.5em;
+ left: 0.5em;
+ border-top: 3px solid @gray;
+ border-right: 3px solid @gray;
+ border-top-right-radius: .50em;
+ transform: rotate(45deg);
+ }
+ }
+ }
+ }
+}
diff --git a/public/css/icinga/menu.less b/public/css/icinga/menu.less
new file mode 100644
index 0000000..98650a2
--- /dev/null
+++ b/public/css/icinga/menu.less
@@ -0,0 +1,554 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+#menu [class^="icon-"],
+#menu [class*=" icon-"] {
+ &:before {
+ width: 1.5em;
+ }
+}
+
+@icon-width: 1.7em; // 1.5em width + 0.2em right margin
+
+#menu {
+ background-color: @menu-bg-color;
+ width: 100%;
+ flex: 1;
+ overflow: auto;
+ overflow-x: hidden;
+}
+
+#menu .nav-item {
+ vertical-align: middle;
+
+ > a {
+ position: relative;
+
+ &:focus {
+ outline: none;
+ }
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+}
+
+#layout:not(.sidebar-collapsed) #menu .nav-item > a:first-of-type {
+ // Respect overflowing content
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item {
+ overflow: hidden;
+}
+
+#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item > a {
+ // Clip overflowing content
+ overflow: hidden;
+ width: 4em;
+}
+
+#menu .nav-level-1 > .nav-item {
+ line-height: 2.167em; // 26 px
+ color: @menu-color;
+
+ &.active {
+ color: @menu-active-color;
+
+ > a > .badge {
+ display: none;
+ }
+
+ background-color: @menu-active-bg-color;
+ }
+
+ &.no-icon > a {
+ padding-left: @icon-width + .75em;
+ }
+
+ > a {
+ padding: 0.5em 0.5em 0.5em .75em;
+ }
+
+ &.active:not(.selected) > a:focus,
+ &.active:not(.selected) > a:hover {
+ background-color: @menu-active-hover-bg-color;
+ }
+
+ &:not(.selected) > a:hover,
+ &:not(.selected) > a:focus {
+ background-color: @menu-hover-bg-color;
+ }
+
+ // Balance icon weight for non active menu items
+ &:not(.active) > a > i {
+ opacity: .8;
+ }
+
+ & > a > .icon-letter:before {
+ content: attr(data-letter);
+ font-family: @font-family;
+ font-weight: 800;
+ text-transform: uppercase;
+ }
+}
+
+#menu ul:not(.nav-level-2) > .selected > a {
+ background-color: @menu-highlight-color;
+ color: @text-color-inverted;
+
+ &:focus {
+ background-color: @menu-highlight-hover-bg-color;
+ }
+
+ &:after {
+ .transform(rotate(45deg));
+
+ position: absolute;
+ right: -.75em;
+
+ background-color: @body-bg-color;
+ box-shadow: 0 0 1em 0 rgba(0,0,0,0.6);
+ content: "";
+ display: block;
+ height: 1.25em;
+ margin-top: -1.75em;
+ width: 1.25em;
+ }
+}
+
+#menu .nav-level-2 > .nav-item {
+ // Collapse menu by default
+ display: none;
+ line-height: 1.833em; // 22px
+
+ > a {
+ color: @menu-2ndlvl-color;
+ font-size: @font-size-small;
+ padding: 0.364em 0.545em 0.364em 0.545em;
+
+ &:first-of-type {
+ padding-left: (@icon-width + .75em)/@font-size-small;
+ }
+ }
+
+ &.active {
+ overflow: hidden;
+ position: relative;
+ }
+
+ // Little caret on active level-2 item
+ &.active:after {
+ .transform(rotate(45deg));
+
+ background-color: @body-bg-color;
+ box-shadow: 0 0 1em 0 rgba(0,0,0,.6);
+ content: "";
+ display: block;
+ height: 1.25em;
+ width: 1.25em;
+ position: absolute;
+ top: .5em;
+ right: -.75em;
+ z-index: 3;
+ }
+
+ &.active > a {
+ color: @menu-2ndlvl-active-color;
+ background-color: @menu-2ndlvl-active-bg-color;
+
+ &:focus {
+ &:first-of-type,
+ &:first-of-type ~ a {
+ color: @menu-2ndlvl-active-hover-color;
+ background-color: @menu-2ndlvl-active-hover-bg-color;
+ }
+ }
+ }
+}
+
+.no-js #menu .nav-level-2 > .nav-item {
+ // Expand menu if JavaScript is disabled
+ display: block;
+}
+
+#layout:not(.sidebar-collapsed) {
+ #menu .nav-level-1 > .nav-item {
+ &.active {
+ .nav-level-2 > li {
+ // Expand menu if active
+ display: block;
+ }
+ }
+ }
+}
+
+#menu img.icon {
+ line-height: 1;
+ margin: 0 0.5em -.05em 0.25em;
+ width: 1em;
+}
+
+#menu img[src*="/img/icons/"] {
+ &:not([src$="tux.png"]):not([src$="win.png"]):not([src$="_white.png"]) {
+ -webkit-filter: invert(100%);
+ -moz-filter: invert(100%);
+ -ms-filter: invert(100%);
+ filter: invert(100%);
+ }
+}
+
+.nav-item:hover img.icon {
+ opacity: .6;
+}
+
+#menu input.search {
+ background: transparent url('../img/icons/search_white.png') no-repeat 1em center;
+ background-size: 1em auto;
+ border: none;
+ color: @menu-color;
+ line-height: 2.167em;
+ padding: .25em;
+ padding-left: @icon-width + .75em;
+ width: 100%;
+
+ &:focus::placeholder {
+ color: @menu-color;
+ }
+ &:focus::-ms-input-placeholder {
+ color: @menu-color;
+ }
+
+ &.active {
+ background-color: @menu-active-bg-color;
+ }
+
+ &:hover,
+ &:focus {
+ background-color: @menu-search-hover-bg-color;
+ }
+}
+
+// Badge offset correction
+#menu > nav > .nav-level-1 > .badge-nav-item > a > .badge {
+ margin-top: 0.2em;
+}
+
+#menu .nav-level-2 > .badge-nav-item > a > .badge {
+ margin-top: 0.2em;
+ margin-right: .5em
+}
+
+// Hovered menu
+#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item.hover,
+#layout:not(.minimal-layout) #menu .nav-level-1 > .nav-item:not(.active).hover {
+ > .nav-level-2 {
+ background-color: @menu-flyout-bg-color;
+ border: 1px solid;
+ border-color: @gray-light;
+ border-radius: .25em;
+ box-shadow: 0 0 1em 0 rgba(0,0,0,.3);
+ padding: @vertical-padding 0;
+ width: 14em;
+ position: fixed;
+ z-index: 1;
+
+ &:after {
+ .transform(rotate(45deg));
+
+ background-color: @body-bg-color;
+ border-bottom: 1px solid @gray-light;
+ border-left: 1px solid @gray-light;
+ content: "";
+ display: block;
+ height: 1.1em;
+ width: 1.1em;
+ position: absolute;
+ top: 1em;
+ left: -.6em;
+ z-index: -1;
+ }
+ > .nav-item {
+ display: block;
+ padding-left: 0;
+ position: relative;
+
+ > a {
+ color: @menu-flyout-color;
+
+ &:first-of-type {
+ padding-left: 1.5em;
+ }
+ }
+
+ &:not(.active) {
+ a:hover, a:focus {
+ &:first-of-type,
+ &:first-of-type ~ a {
+ background-color: @menu-2ndlvl-highlight-bg-color;
+ }
+ }
+ }
+
+ &.active > a {
+ color: @menu-color;
+ }
+
+ // Hide activity caret when displayed as flyout
+ &:after {
+ display: none;
+ }
+ }
+ }
+
+ > a > .badge {
+ display: none;
+ }
+
+ img.icon {
+ opacity: .6;
+ }
+}
+
+#layout:not(.minimal-layout) #menu .nav-level-1 > .nav-item:not(.active).hover {
+ > .nav-level-2 {
+ // Position relative to parent
+ margin-left: 16em;
+ margin-top: -3.167em;
+ }
+}
+
+#layout:not(.minimal-layout).sidebar-collapsed #menu .nav-level-1 > .nav-item.hover {
+ > .nav-level-2 {
+ // Position relative to parent
+ margin-left: 4em;
+ margin-top: -3.333em;
+
+ > .badge-nav-item {
+ display: flex;
+
+ a:first-of-type {
+ flex: 1 1 auto;
+ width: 0;
+ }
+
+ a:first-of-type ~ a {
+ flex: 0;
+ width: auto;
+
+ &:hover,
+ &:focus {
+ .badge {
+ opacity: .6;
+ }
+ }
+ }
+ }
+ }
+}
+
+// Accessibility skip links
+.skip-links {
+ position: relative;
+ font-size: 1/.75em;
+
+ ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ li {
+ display: block;
+ a, button[type="submit"] {
+ background-color: @body-bg-color;
+ border: none;
+ left: -999px;
+ padding: @vertical-padding @horizontal-padding;
+ position: absolute;
+ width: 100%;
+ z-index: 1;
+ &:focus {
+ left: 0;
+ outline-offset: -3px;
+ }
+ }
+ button[type="submit"] {
+ text-align: left;
+ }
+ }
+ }
+}
+
+#sidebar.expanded {
+ #mobile-menu-toggle .icon-menu {
+ display: none;
+ }
+
+ #mobile-menu-toggle .icon-cancel {
+ display: inline-block;
+ }
+}
+
+.search-control {
+ position: relative;
+}
+
+.search-input:focus ~ .search-reset:hover {
+ background-color: @menu-active-hover-bg-color;
+}
+
+.search-reset {
+ background: none;
+ border: 0;
+ color: @menu-color;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ padding: 0;
+ user-select: none;
+ position: absolute;
+ right: 0;
+ top: 0;
+
+ &:focus,
+ &:hover {
+ background-color: @menu-search-hover-bg-color;
+ outline: none;
+ }
+}
+
+// Override forms.less
+input[type=text].search-input {
+ padding-right: 1.4em;
+ text-overflow: ellipsis;
+ transition: none;
+}
+
+.search-input:focus:-moz-placeholder { // FF 18-
+ color: @gray-light;
+}
+
+.search-input:focus::-moz-placeholder { // FF 19+
+ color: @gray-light;
+}
+
+.search-input:focus:-ms-input-placeholder {
+ color: @gray-light;
+}
+
+.search-input:focus::-webkit-input-placeholder {
+ color: @gray-light;
+}
+
+.search-input ~ .search-reset {
+ opacity: 0;
+}
+
+.search-input:valid ~ .search-reset {
+ display: block;
+ opacity: 1;
+}
+
+.search-input:invalid,
+.search-input:-moz-submit-invalid,
+.search-input:-moz-ui-invalid {
+ // Disable glow
+ box-shadow: none;
+}
+
+// Toggle sidebar button
+#toggle-sidebar {
+ font-size: 1/.75em;
+
+ // Reset button styles
+ background: none;
+ border: none;
+ padding: 0;
+ color: @text-color-light;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ z-index: 3;
+ line-height: 2;
+
+ i {
+ background-color: @body-bg-color;
+ border-radius: .25em 0 0 .25em;
+ font-size: 1.125em;
+ width: 2em;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ &:hover, &:focus {
+ i {
+ color: @menu-highlight-color;
+ }
+ }
+}
+
+html.no-js #toggle-sidebar {
+ display: none;
+}
+
+#layout.minimal-layout #toggle-sidebar {
+ display: none;
+}
+
+#open-sidebar {
+ display: none;
+}
+
+#open-sidebar:before,
+#close-sidebar:before {
+ width: 1.4em;
+ margin-right: 0;
+}
+
+#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li {
+ &.nav-item:not(.badge-nav-item) {
+ &:not(.selected):not(.active) a:hover,
+ &:not(.selected):not(.active) a:focus {
+ background-color: @menu-2ndlvl-highlight-bg-color;
+ }
+ }
+}
+
+#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li,
+#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item:not(.active).hover .nav-level-2 > li {
+ &.badge-nav-item {
+ display: flex;
+ }
+
+ &.badge-nav-item a:first-of-type {
+ flex: 1 1 auto;
+ width: 0;
+ }
+
+ &.badge-nav-item a:first-of-type ~ a {
+ flex: 0;
+ width: auto;
+
+ &:hover,
+ &:focus {
+ .badge {
+ opacity: .6;
+ }
+ }
+ }
+}
+
+#layout:not(.sidebar-collapsed) #menu .nav-level-1 > .nav-item.active .nav-level-2 > li {
+ &.badge-nav-item:not(.selected) {
+ a:hover,
+ a:focus {
+ &:first-of-type,
+ &:first-of-type ~ a {
+ background-color: @menu-2ndlvl-highlight-bg-color;
+ }
+ }
+ }
+}
diff --git a/public/css/icinga/mixins.less b/public/css/icinga/mixins.less
new file mode 100644
index 0000000..6c55512
--- /dev/null
+++ b/public/css/icinga/mixins.less
@@ -0,0 +1,201 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+.button(
+ @background-color: @body-bg-color,
+ @border-font-color: @icinga-blue,
+ @color-dark: @icinga-blue-dark
+) {
+ .rounded-corners(3px);
+
+ display: inline-flex;
+ align-items: baseline;
+ background-color: @background-color;
+ border: 2px solid @border-font-color;
+ color: @border-font-color;
+ cursor: pointer;
+ line-height: normal;
+ outline: none;
+ padding: ~"calc(@{vertical-padding} - 2px)" @horizontal-padding;
+
+ @duration: 0.2s;
+ // The trailing semicolon is needed to be able to pass this as a css list
+ .transition(background @duration, border @duration ease, color @duration ease;);
+
+ &:focus,
+ &:hover,
+ &.btn-primary {
+ background-color: @border-font-color;
+ color: @background-color;
+ }
+
+ &.btn-primary:focus,
+ &.btn-primary:hover {
+ background-color: @color-dark;
+ border-color: @color-dark;
+ color: @background-color;
+ }
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+.clearfix {
+ &:after {
+ content: "";
+ clear: both;
+ display: table;
+ }
+}
+
+.opacity(@opacity: 0.6) {
+ opacity: @opacity;
+}
+
+.transform(@transform) {
+ -webkit-transform: @transform;
+ -moz-transform: @transform;
+ -ms-transform: @transform;
+ -o-transform: @transform;
+ transform: @transform;
+}
+
+.user-select(@user-select) {
+ -webkit-user-select: @user-select;
+ -moz-user-select: @user-select;
+ -ms-user-select: @user-select;
+ user-select: @user-select;
+}
+
+.transition (@transition) {
+ -webkit-transition: @transition;
+ -moz-transition: @transition;
+ -o-transition: @transition;
+ transition: @transition;
+}
+
+// Fadein animation
+
+/* Chrome, WebKit */
+@-webkit-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* FF < 16 */
+@-moz-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+/* Opera < 12.1 */
+@-o-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+@keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.fadein() {
+ opacity: 0;
+
+ -webkit-animation: fadein 2s ease-in; /* Chrome, WebKit */
+ -moz-animation: fadein 2s ease-in; /* FF < 16 */
+ -o-animation: fadein 2s ease-in; /* Opera < 12.1 */
+ animation: fadein 2s ease-in;
+
+ // Make sure that after animation is done we remain at the last keyframe value (opacity: 1)
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-fill-mode: forwards;
+ -o-animation-fill-mode: forwards;
+ animation-fill-mode: forwards;
+}
+
+// Mixin for stateful foreground colors, e.g. text or icons
+.fg-stateful {
+ &.state-ok {
+ color: @color-ok;
+ }
+ &.state-up {
+ color: @color-up;
+ }
+ &.state-warning {
+ color: @color-warning;
+ &.handled {
+ color: @color-warning-handled;
+ }
+ }
+ &.state-critical {
+ color: @color-critical;
+ &.handled {
+ color: @color-critical-handled;
+ }
+ }
+ &.state-down {
+ color: @color-down;
+ &.handled {
+ color: @color-down-handled;
+ }
+ }
+ &.state-unreachable {
+ color: @color-unreachable;
+ &.handled {
+ color: @color-unreachable-handled;
+ }
+ }
+ &.state-unknown {
+ color: @color-unknown;
+ &.handled {
+ color: @color-unknown-handled;
+ }
+ }
+ &.state-pending {
+ color: @color-pending;
+ }
+}
+
+// Mixin for stateful background colors
+.bg-stateful {
+ &.state-ok {
+ background-color: @color-ok;
+ }
+ &.state-up {
+ background-color: @color-up;
+ }
+ &.state-warning {
+ background-color: @color-warning;
+ &.handled {
+ background-color: @color-warning-handled;
+ }
+ }
+ &.state-critical {
+ background-color: @color-critical;
+ &.handled {
+ background-color: @color-critical-handled;
+ }
+ }
+ &.state-down {
+ background-color: @color-down;
+ &.handled {
+ background-color: @color-down-handled;
+ }
+ }
+ &.state-unreachable {
+ background-color: @color-unreachable;
+ &.handled {
+ background-color: @color-unreachable-handled;
+ }
+ }
+ &.state-unknown {
+ background-color: @color-unknown;
+ &.handled {
+ background-color: @color-unknown-handled;
+ }
+ }
+ &.state-pending {
+ background-color: @color-pending;
+ }
+}
diff --git a/public/css/icinga/modal.less b/public/css/icinga/modal.less
new file mode 100644
index 0000000..3d497d6
--- /dev/null
+++ b/public/css/icinga/modal.less
@@ -0,0 +1,113 @@
+#layout > #modal {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+
+ background-color: rgba(0, 0, 0, .6);
+ opacity: 0;
+ font-size: @font-size;
+ line-height: @line-height;
+ pointer-events: none;
+ transition: opacity .2s ease-in; // This is coupled with a `setTimout` in modal.js
+ z-index: 1000;
+
+ &.active {
+ opacity: 1;
+ pointer-events: auto;
+ }
+
+ > div {
+ height: 100%;
+ pointer-events: none;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+}
+
+#modal-content {
+ display: flex;
+ flex: 10;
+ flex-direction: column;
+ justify-content: stretch;
+
+ > .content {
+ padding: 1em;
+
+ > .icinga-form {
+ width: 100%;
+ }
+ }
+}
+
+#modal-ghost {
+ display: none;
+}
+
+.modal-area {
+ display: flex;
+ flex-direction: row;
+ flex-grow: 1;
+ justify-content: stretch;
+}
+
+.modal-header {
+ padding: .25em 0;
+ position: relative;
+ text-align: center;
+
+ > button {
+ position: absolute;
+ top: .75em;
+ right: 1em;
+
+ background-color: @gray;
+ border: none;
+ border-radius: 50%;
+ color: @text-color-inverted;
+ height: 1.5em;
+ line-height: 1em;
+ padding: 0;
+ text-align: center;
+ width: 1.5em;
+
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ -ms-appearance: none;
+ appearance: none;
+ }
+
+ > button:hover {
+ opacity: .8;
+ }
+
+ > button > .icon-cancel:before {
+ margin-right: 0;
+ }
+}
+
+.modal-header h1 {
+ padding: .25em;
+ margin: 0;
+}
+
+.modal-window {
+ overflow: auto;
+ pointer-events: auto;
+
+ display: flex;
+ align-items: stretch;
+ flex-direction: column;
+
+ background-color: @body-bg-color;
+ border-radius: .5em;
+ box-shadow: 0 0 2em 0 rgba(0, 0, 0, .6);
+ flex: 1;
+ margin: 0 auto;
+ max-height: 80%;
+ min-height: 40vh;
+ max-width: 60em;
+}
diff --git a/public/css/icinga/nav.less b/public/css/icinga/nav.less
new file mode 100644
index 0000000..f7950f1
--- /dev/null
+++ b/public/css/icinga/nav.less
@@ -0,0 +1,50 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+.badge-nav-item {
+ > a {
+ .clearfix();
+
+ > .badge {
+ float: right;
+ }
+ }
+}
+
+.dropdown-nav-item > ul {
+ display: none;
+ position: absolute;
+}
+
+.dropdown-nav-item.active > ul,
+.dropdown-nav-item:hover > ul {
+ display: block;
+}
+
+.nav {
+ // Reset defaults
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+ li > a,
+ li > span {
+ // Rollover
+ display: block;
+ }
+}
+
+.nav .nav-item .icon:before {
+ width: 1.5em;
+}
+
+.tab-nav {
+ .clearfix();
+
+ > li {
+ float: left;
+ }
+}
+
+.primary-nav a {
+ font-weight: 500;
+}
diff --git a/public/css/icinga/php-diff.less b/public/css/icinga/php-diff.less
new file mode 100644
index 0000000..d8f6a80
--- /dev/null
+++ b/public/css/icinga/php-diff.less
@@ -0,0 +1,17 @@
+@diff-bg-color: transparent;
+@diff-text-color: @text-color;
+
+@diff-bg-color-ins-base: @color-up;
+@diff-bg-color-del-base: @color-down;
+@diff-bg-color-rep-base: @color-warning;
+
+@diff-border-color: @gray-light;
+
+@light-mode: {
+ :root {
+ --diff-bg-color: var(--body-bg-color);
+ --diff-text-color: var(--text-color);
+
+ --diff-border-color: var(--gray-light);
+ }
+};
diff --git a/public/css/icinga/print.less b/public/css/icinga/print.less
new file mode 100644
index 0000000..75d4728
--- /dev/null
+++ b/public/css/icinga/print.less
@@ -0,0 +1,39 @@
+/*! Icinga Web 2 | (c) 2015 Icinga GmbH | GPLv2+ */
+
+@media print {
+ #sidebar,
+ #migrate-popup, // Icinga DB Web
+ .controls,
+ .footer, // ipl
+ .dontprint, // Compat only, use dont-print instead
+ .dont-print {
+ display: none !important;
+ }
+
+ #main > .container {
+ overflow: visible !important;
+
+ > .content {
+ overflow: visible !important;
+ }
+ }
+
+ :root {
+ --body-bg-color: #fff !important;
+ --text-color: #535353 !important;
+ --text-color-light: #7F7F7F !important;
+ --tr-active-color: #fff !important;
+ --tr-hover-color: #fff !important;
+
+ // ipl-web overrides
+ --default-bg: #fff !important;
+ --default-text-color: #535353 !important;
+ --default-text-color-inverted: #fff !important;
+ }
+}
+
+@media not print {
+ .print-only {
+ display: none !important;
+ }
+}
diff --git a/public/css/icinga/responsive.less b/public/css/icinga/responsive.less
new file mode 100644
index 0000000..6d1cae8
--- /dev/null
+++ b/public/css/icinga/responsive.less
@@ -0,0 +1,167 @@
+/*! Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+// Not growing larger than 3840px at 1em=16px right now
+@media screen and (min-width: 240em) {
+ #header {
+ min-width: 240em;
+ }
+
+ #main {
+ width: 227em;
+ }
+}
+
+// More than 100em, usually 1600px at 1em=16px
+@media screen and (min-width: 100em) {
+ html {
+ font-family: 'wide-layout';
+ }
+}
+
+// Up to 1152px at 1em=16px
+@media screen and (max-width:72em) {
+ html {
+ font-family: 'compact-layout';
+ }
+}
+
+// Up to 752px at 1em=16px
+@media screen and (max-width: 47em) {
+ html {
+ font-family: 'poor-layout';
+ }
+}
+
+// Up to 576px at 1em=16px, should fit 320px devices
+@media screen and (max-width: 36em) {
+ html {
+ font-family: 'minimal-layout';
+ }
+}
+
+#layout.compact-layout {
+ font-size: 0.875em;
+}
+
+#layout.poor-layout {
+ font-size: 0.875em;
+
+ #layout.twocols {
+ #col1 {
+ display: none;
+ }
+
+ #main > .container {
+ width: 100%;
+ }
+ }
+
+ .dashboard > div.container {
+ width: 100%;
+ }
+}
+
+#layout:not(.minimal-layout) {
+ #mobile-menu-toggle {
+ display: none;
+ }
+}
+
+#layout.minimal-layout {
+ #sidebar {
+ width: 100%;
+ overflow: auto;
+ }
+
+ #header-logo-container {
+ width: auto;
+ height: 4em;
+ padding: 0;
+ background: inherit;
+ }
+
+ #header-logo {
+ float: left;
+ width: 9em;
+ height: 3em;
+ margin: .5em 1em;
+ background-position: left center;
+ }
+
+ #mobile-menu-toggle {
+ float: right;
+ }
+
+ #sidebar:not(.expanded) #menu {
+ display: none;
+ }
+
+ #menu {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+
+ #content-wrapper {
+ flex-direction: column;
+ }
+
+ #main {
+ flex: 1 1 auto;
+ height: 0;
+ }
+
+ ul > .selected > a:after,
+ ul > .nav-item.active:after {
+ display: none;
+ }
+
+ .dashboard > div.container {
+ width: 100%;
+ }
+}
+
+// Dashboard
+
+.dashboard > .container {
+ padding-right: 0;
+ width: 100%;
+}
+
+#layout:not(.twocols).default-layout .dashboard > .container:not(:only-child) {
+ padding-right: @gutter;
+ width: 50%;
+}
+
+#layout:not(.twocols).wide-layout .dashboard > .container:not(:only-child) {
+ padding-right: @gutter;
+ width: 33.33%;
+}
+
+// Columns
+
+#layout.twocols #col2 {
+ border-left: 1px solid @gray-lighter;
+}
+
+#layout.twocols.wide-layout #col2 {
+ flex-grow: 2;
+}
+
+// Safe areas for iPhone X
+
+#header, #sidebar, #footer {
+ padding-left: constant(safe-area-inset-left);
+}
+
+#main, #footer {
+ padding-right: constant(safe-area-inset-right);
+}
+
+#layout.twocols #col2 {
+ border-left: 1px solid @gray-lighter;
+
+ &:empty {
+ display: flex;
+ }
+}
diff --git a/public/css/icinga/setup.less b/public/css/icinga/setup.less
new file mode 100644
index 0000000..c848bc7
--- /dev/null
+++ b/public/css/icinga/setup.less
@@ -0,0 +1,479 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+#setup-content-wrapper {
+ height: 0;
+ display: flex;
+ flex: 1 1 auto;
+ flex-direction: column;
+
+ > .setup-content {
+ height: 0;
+ overflow: auto;
+ flex: 1 1 auto;
+ }
+}
+
+.setup-header {
+ width: 100%;
+ height: 5.5em;
+ background-color: @icinga-blue;
+ text-align: center;
+
+ img {
+ width: 7.5em;
+ margin: 1.5em;
+ float: left;
+ }
+
+ form[name='setup_restart_form'] button {
+ background: none;
+ border: none;
+ color: #ffffff;
+ cursor: pointer;
+ outline: none;
+ font-size: 1.4em;
+ margin-right: 0.6em;
+ -moz-transform: scale(1, -1);
+ -webkit-transform: scale(1, -1);
+ -o-transform: scale(1, -1);
+ -ms-transform: scale(1, -1);
+ transform: scale(1, -1);
+ }
+
+ .progress-bar {
+ overflow: hidden;
+ padding-top: 1em;
+
+ .step {
+ float: left;
+
+ h1 {
+ margin: 0;
+ color: white;
+ font-size: 0.9em;
+ text-align: center;
+ border-bottom: none;
+ }
+
+ table {
+ margin-top: 0.3em;
+
+ td {
+ padding: 0;
+
+ &.left, &.right {
+ width: 50%;
+ }
+ }
+ }
+
+ div {
+ background-color: lightgrey;
+
+ &.line {
+ height: 0.4em;
+
+ &.left {
+ margin-left: 0.1em;
+ margin-right: -0.1em;
+ border-top-left-radius: 0.5em;
+ border-bottom-left-radius: 0.5em;
+ }
+
+ &.right {
+ margin-left: -0.1em;
+ margin-right: 0.1em;
+ border-top-right-radius: 0.5em;
+ border-bottom-right-radius: 0.5em;
+ }
+ }
+
+ &.bubble {
+ width: 1.2em;
+ height: 1.2em;
+ border-radius: 1.2em;
+
+ // Make sure that such a bubble overlays lines
+ position: relative;
+ z-index: 1337;
+ }
+
+ &.active {
+ background-color: white;
+ }
+
+ &.complete {
+ background-color: @color-ok;
+ }
+
+ &.visited {
+ background-color: #eee;
+ }
+ }
+ }
+ }
+}
+
+.setup-content {
+ padding: 1.5em 10em 0 10em;
+
+ h1 {
+ font-weight: bold;
+ }
+
+ form {
+ h2 {
+ font-size: 2.0em;
+ }
+ }
+}
+
+.setup-content .control-group > :not([hidden]) {
+ display: inline-block;
+ margin-right: 1em;
+}
+
+.setup-content div.buttons {
+ margin-top: 1.5em; // Yes, -top and -bottom, keep it like that...
+ margin-bottom: 1.5em;
+
+ .double {
+ position: absolute;
+ left: -1337px;
+ }
+
+ .control-button,
+ input[type="submit"] {
+ .button();
+ }
+
+ .control-button[disabled] {
+ background: none;
+ cursor: default;
+ color: @control-disabled-color;
+ border: 1px solid @control-disabled-color;
+
+ &:hover {
+ color: @control-disabled-color;
+ background: none;
+ border: 1px solid @control-disabled-color;
+ }
+ }
+
+ button.finish, a.button-like.login {
+ min-width: 25em;
+ }
+
+ .spinner {
+ margin-left: 1em;
+ }
+}
+
+.setup-content div.buttons + ul.hints {
+ margin-top: -1.5em;
+ margin-bottom: 1.5em;
+}
+
+form#setup_requirements {
+ margin-top: 2em;
+ padding-top: 0.5em;
+ border-top: 1px solid #888;
+
+ div.buttons div.requirements-refresh {
+ width: 25%;
+ float: right;
+ text-align: center;
+
+ a.button-like {
+ padding: 0.1em 0.4em;
+ }
+ }
+}
+
+.setup-content ul.requirements {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ li {
+ margin-bottom: 1em;
+
+ & > ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ }
+
+ div {
+ float: left;
+ padding-top: 0.4em;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ }
+
+ div.title {
+ width: 25%;
+
+ h2 {
+ padding: 0;
+ margin: 0 1em 0 0;
+ border-bottom: 0;
+ }
+ }
+
+ div.description {
+ width: 50%;
+ border-left: 0.4em solid transparent;
+ border-right: 0.4em solid transparent;
+
+ ul {
+ margin: 0;
+ padding-left: 1em;
+ list-style-type: square;
+ }
+ }
+
+ div.state {
+ width: 25%;
+ color: white;
+ padding: 0.4em;
+
+ &.fulfilled {
+ background-color: @color-ok;
+ }
+
+ &.not-available {
+ color: black;
+ background-color: #e8ec70;
+ }
+
+ &.missing {
+ background-color: @color-critical;
+ }
+ }
+ }
+}
+
+#setup_ldap_discovery_confirm table {
+ margin: 1em 0;
+ border-collapse: separate;
+ border-spacing: 1em 0.2em;
+}
+
+#setup_admin_account {
+ div.instructions {
+ width: 30.2em;
+ display: inline-block;
+ }
+
+ div.radiobox {
+ vertical-align: top;
+ display: inline-block;
+ padding: 0.9em 0.2em 0;
+ }
+}
+
+.setup-content {
+ div.summary {
+ font-size: 90%;
+
+ div.page {
+ float: left;
+ width: 25em;
+ min-height: 25em;
+ padding: 0 1em 1em;
+ margin: 1em 1.5em 1.5em;
+ border: 1px dashed lightgrey;
+
+ h2 {
+ font-size: 1.2em;
+ font-weight: bold;
+ }
+
+ div.topic {
+ margin-left: 2em;
+
+ h3 {
+ font-size: 1em;
+ }
+
+ ul {
+ list-style-type: circle;
+ }
+
+ table {
+ border-spacing: 0.5em;
+ border-collapse: separate;
+ font-size: 0.9em;
+ margin-left: 2em;
+ }
+ }
+ }
+ }
+
+ form.summary {
+ clear: left;
+ }
+}
+
+#setup-finish {
+ h2 {
+ padding: 0.5em;
+ border-bottom: 0;
+ font-variant: normal;
+ font-weight: bold;
+ color: white;
+
+ &.success {
+ background-color: @color-ok;
+ }
+
+ &.failure {
+ background-color: @color-critical;
+ }
+ }
+
+ pre.log-output {
+ width: 66%;
+ height: 25em;
+ max-height: none;
+ }
+
+ div.buttons {
+ margin-top: 0;
+ text-align: center;
+
+ a {
+ padding: 0.5em;
+ }
+ }
+}
+
+.welcome-page {
+ margin-top: 3em;
+ text-align: center;
+
+ h2 {
+ font-size: 2.0em;
+ margin-bottom: 2em;
+ }
+
+ div.info {
+ padding: 0 1em;
+ background-color: #eee;
+ border: 1px solid lightgrey;
+ }
+
+ p.restart-warning {
+ color: coral;
+ font-weight: bold;
+ }
+
+ form ul.errors {
+ display: block;
+
+ list-style-type: none;
+ color: red;
+ }
+
+ div.note {
+ padding: 1em 1em 0;
+ margin: 3em auto 0;
+ text-align: left;
+ font-size: 0.9em;
+ border: 1px solid;
+ border-color: @gray-light;
+
+ h3 {
+ padding: 0.2em;
+ margin: -1em -1em 1em;
+ text-align: center;
+ color: @text-color;
+ background-color: @gray-lightest;
+ border: 1px solid;
+ border-color: @gray-light;
+ }
+
+ img {
+ float: right;
+ }
+
+ p {
+ margin: 2em 0 1em 0;
+
+ &:first-child {
+ margin-top: 1em;
+ }
+ }
+
+ div.code {
+ margin: 0 2em;
+
+ span {
+ display: block;
+ font-family: monospace;
+ }
+ }
+ }
+}
+
+#setup_monitoring_welcome {
+ .welcome-page;
+ margin-top: 0;
+ padding: 1em;
+
+ h2 {
+ margin-top: 0;
+ }
+}
+
+#setup_modules {
+ div.module {
+ float: left;
+ width: 15em;
+ height: 15em;
+ margin: 1em;
+ padding: 0.3em;
+ border: 1px solid;
+ border-color: @gray-semilight;
+ background-color: @gray-lightest;
+
+ .header {
+ height: 2.5em;
+ display: flex;
+ justify-content: space-between;
+ }
+
+ h3 {
+ margin: 0;
+ border: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ label {
+ cursor: pointer;
+ }
+ }
+
+ label.description {
+ display: inline-block;
+ width: 14.4em;
+ height: 12em;
+ overflow: auto;
+ cursor: pointer;
+ font-weight: normal;
+ }
+
+ input[type=checkbox] {
+ height: 10em;
+ float: right;
+ margin: 0;
+ }
+ }
+
+ div.buttons {
+ padding-top: 1em;
+ clear: both;
+ }
+}
diff --git a/public/css/icinga/spinner.less b/public/css/icinga/spinner.less
new file mode 100644
index 0000000..c1cf93e
--- /dev/null
+++ b/public/css/icinga/spinner.less
@@ -0,0 +1,42 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+.refresh-container-control > i:before {
+ margin: 0;
+}
+
+a.spinner.active > i,
+button.spinner.active > i,
+i.spinner.active {
+ &:before {
+ .animate(spin 2s infinite linear);
+
+ // icon-spin6
+ content: '\e874';
+ }
+
+ &.fa:before {
+ // fa spinner
+ content: '\f110';
+ }
+}
+
+div.spinner {
+ display: inline-block;
+ vertical-align: middle;
+
+ i {
+ visibility: hidden;
+
+ &.active {
+ visibility: visible;
+
+ &:before {
+ .animate(spin 2s infinite linear);
+ }
+ }
+
+ &:before {
+ margin: 0; // Disables wobbling
+ }
+ }
+}
diff --git a/public/css/icinga/tabs.less b/public/css/icinga/tabs.less
new file mode 100644
index 0000000..8103735
--- /dev/null
+++ b/public/css/icinga/tabs.less
@@ -0,0 +1,105 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+// Styles for tab navigation of containers
+
+.tabs {
+ background-color: @menu-bg-color;
+ letter-spacing: -0.417em;
+}
+
+.tabs > li {
+ display: inline-block;
+ letter-spacing: normal;
+}
+
+.tabs a {
+ padding: 0 1em;
+ line-height: 2.5em;
+
+ &:focus {
+ outline-offset: -0.5em;
+ }
+}
+
+.tabs > li {
+ &:not(:last-child) {
+ margin-right: 0.5em;
+ }
+
+ > a {
+ color: @menu-color;
+
+ &:hover {
+ text-decoration: none;
+ background: @tab-hover-bg-color;
+ }
+ }
+
+ &.active > a,
+ > a:focus {
+ background-color: @body-bg-color;
+ color: @text-color;
+ }
+}
+
+.tabs > .dropdown-nav-item > a,
+.tabs > li > .close-container-control,
+.tabs > li > .refresh-container-control {
+ text-align: center;
+ width: 3em;
+}
+
+.tabs > .dropdown-nav-item:hover > a,
+.tabs > .dropdown-nav-item > a:focus,
+.tabs > li > .close-container-control:focus,
+.tabs > li > .close-container-control:hover,
+.tabs > li > .refresh-container-control:focus,
+.tabs > li > .refresh-container-control:hover {
+ background-color: @body-bg-color;
+ color: @text-color;
+ text-decoration: none;
+}
+
+.tabs > .dropdown-nav-item > ul {
+ .box-shadow();
+ .rounded-corners(0 0 0.3em 0.3em);
+
+ background-color: @body-bg-color;
+ border: 1px solid;
+ border-color: @gray-light;
+ border-top: none;
+ margin-left: -1px;
+ min-width: 14em;
+ z-index: 10;
+}
+
+.tabs > .dropdown-nav-item > ul > li:hover > a {
+ background-color: @gray-lighter;
+ text-decoration: none;
+}
+
+// Dropdown tabs after the fourth title should be right-aligned
+.tabs > li:nth-child(n+5).dropdown-nav-item > ul {
+ transform: translate(~"calc(-100% + 3em)"); // -full width + tab width
+ margin-left: 1px;
+}
+
+// TODO(el): Rename display-on-hover and move it to main.less
+.display-on-hover {
+ font-size: @font-size-small;
+ left: -999em;
+ position: relative;
+}
+
+.dropdown-nav-item > ul > li > a:focus > .display-on-hover,
+.dropdown-nav-item > ul > li:hover > a > .display-on-hover {
+ position: static;
+}
+
+.tabs > li > .close-container-control {
+ display: none;
+}
+
+#layout.twocols .tabs > li > .close-container-control {
+ display: block;
+}
diff --git a/public/css/icinga/widgets.less b/public/css/icinga/widgets.less
new file mode 100644
index 0000000..654e7d5
--- /dev/null
+++ b/public/css/icinga/widgets.less
@@ -0,0 +1,643 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+#announcements > ul {
+ background-color: @body-bg-color;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ > li {
+ border-bottom: 1px solid @gray-lighter;
+ line-height: 1.5em;
+ padding: 0.5em 1em 0.5em 3em;
+
+ position: relative;
+
+ &:before {
+ color: @icinga-blue;
+ content: "\e811";
+ font-family: 'ifont';
+
+ position: absolute;
+ left: 1.25em;
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ a {
+ color: @icinga-blue;
+ }
+
+ .message {
+ display: inline-block;
+ vertical-align: middle;
+ padding-right: 1.5em;
+ font-size: 7/6em;
+ }
+
+ p {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.acknowledge-announcement-control,
+.application-state-acknowledge-message-control {
+ background: none;
+ border: none;
+ display: block;
+ margin-top: -0.75em;
+
+ position: absolute;
+ right: .75em;
+ top: 50%;
+}
+
+.application-state-acknowledge-message-control .link-button {
+ color: #fff;
+
+ &:hover .icon-cancel {
+ color: @icinga-blue;
+ }
+}
+
+#application-state-summary > div {
+ background-color: @color-critical;
+ color: @text-color-on-icinga-blue;
+ line-height: 1.5em;
+ padding: 0.5em 1em 0.5em 3em;
+
+ position: relative;
+
+ &:before {
+ content: "\e84d";
+ font-family: 'ifont';
+
+ position: absolute;
+ text-align: center;
+ left: .4em;
+ padding: .5em;
+ width: 3em;
+ top: 0;
+ }
+
+ > section {
+ margin-left: .5em;
+ }
+
+ > form .icon-cancel:before {
+ color: @text-color-on-icinga-blue;
+ }
+}
+
+.dashboard-link {
+ .clearfix();
+ display: block;
+ max-width: 100%;
+ vertical-align: middle;
+ padding: 1em;
+ width: 36em;
+
+ &:hover {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+
+ -webkit-box-shadow: 0 0 0.5em 0 rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 0 0.5em 0 rgba(0, 0, 0, 0.2);
+ box-shadow: 0 0 0.5em 0 rgba(0, 0, 0, 0.2);
+
+ background-color: @tr-hover-color;
+ text-decoration: none;
+ }
+}
+
+.dashboard.content > .container {
+ overflow-x: auto;
+}
+
+.link-meta {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.link-label {
+ font-weight: @font-weight-bold;
+}
+
+.link-description {
+ color: @text-color-light;
+}
+
+.link-icon {
+ display: table-cell;
+ padding-right: .5em;
+ vertical-align: middle;
+ text-align: center;
+
+ > i {
+ font-size: 3em;
+ opacity: 0.7;
+ line-height: 1.5;
+
+ &:before {
+ min-width: 1.25em;
+ }
+ }
+
+ > img {
+ width: 3em;
+ height: 3em;
+ margin-right: .6em;
+ }
+}
+
+table.historycolorgrid {
+ font-size: 1.5em;
+}
+
+table.historycolorgrid th {
+ width: 1em;
+ height: 1em;
+ margin: 0.5em;
+ font-size: 0.55em;
+ font-weight: bold;
+}
+
+table.historycolorgrid td {
+ width: 1em;
+ height: 1em;
+ margin: 1em;
+}
+
+table.historycolorgrid td:hover {
+ opacity: 0.5;
+}
+
+table.historycolorgrid td.weekday {
+ font-size: 0.55em;
+ font-weight: bold;
+ width: 2.5em;
+ opacity: 1.0;
+}
+
+table.historycolorgrid a, table.historycolorgrid span {
+ .rounded-corners(0.2em);
+ margin: 0;
+ text-decoration: none;
+ display: block;
+ width: 1.1em;
+ height: 1.1em;
+}
+
+table.historycolorgrid a:hover {
+ text-decoration: none;
+}
+
+table.multiselect tr[href] td {
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+}
+
+#main div.filter {
+ form.editor {
+ input[type=text], select {
+ width: 12em;
+ height: 2em;
+ line-height: 1;
+ }
+
+ ul.tree li.active {
+ background-color: @gray-lightest;
+ }
+
+ button {
+ padding: .5em;
+ border: none;
+ background: none;
+ color: @text-color;
+
+ &:hover, &:focus {
+ color: @icinga-blue;
+ }
+ }
+
+ .buttons {
+ margin-left: 25em;
+ padding: .25em 0;
+ }
+
+ .buttons input:not(:last-child) {
+ margin-right:.5em;
+ }
+ }
+}
+
+form.role-form {
+ &.icinga-form .control-label-group {
+ width: 20em;
+ }
+
+ .control-label-group em {
+ color: @text-color-light;
+ font-style: normal;
+ }
+
+ .control-label > * {
+ display: inline-block;
+ }
+
+ summary {
+ border-bottom: 1px solid @gray-light;
+ .user-select(none);
+ cursor: pointer;
+
+ font-weight: @font-weight-bold;
+ margin: 0.556em 0 0.333em;
+ font-size: 1.167em;
+
+ display: flex;
+ align-items: baseline;
+ .privilege-preview {
+ flex: 1 1 auto;
+ }
+
+ > :first-child {
+ display: inline-block;
+ width: 20em / 1.167em; // element label width / summary font-size
+ }
+
+ > :nth-last-child(1),
+ > :nth-last-child(2) {
+ font-size: .75em;
+ opacity: .6;
+ }
+
+ .privilege-preview .icon {
+ &.granted {
+ color: @color-granted;
+ }
+
+ &.refused {
+ color: @color-refused;
+ }
+
+ &.restricted {
+ color: @color-restricted;
+ }
+ }
+ }
+
+ .collapsible {
+ summary em {
+ font-size: .857em;
+ font-weight: normal;
+ color: @text-color-light;
+ }
+
+ h4 {
+ display: inline-block;
+ width: 20em;
+ margin-top: 1.5em;
+ padding-right: .5625em;
+ text-align: right;
+
+ & ~ i {
+ display: inline-block;
+ width: 2.625em;
+ margin-right: 1em;
+ text-align: center;
+
+ &.icon-ok {
+ color: @color-granted;
+ }
+
+ &.icon-cancel {
+ color: @color-refused;
+ }
+ }
+ }
+ }
+}
+
+ul.tree select:first-of-type { /* ?? */
+ margin-bottom: 0.3em;
+ margin-left: 2em;
+}
+
+ul.tree {
+ padding: 0;
+ margin: 0;
+ padding-top: .5em;
+}
+
+ul.tree ul {
+ padding-left: 1em;
+}
+
+ul.tree li {
+ margin: 0;
+ list-style-type: none;
+ position: relative;
+ padding: 0;
+}
+
+ul.tree li .handle {
+ background-image: url('../img/tree/tree-minus.gif');
+ background-repeat: no-repeat;
+ display: inline-block;
+ position: absolute;
+ width: 1.5em;
+ height: 2em;
+ left: 0em;
+ background-position: center center;
+ z-index: 1;
+ cursor: pointer;
+}
+
+ul.tree li.collapsed > .handle {
+ background-image: url('../img/tree/tree-plus.gif');
+}
+
+ul.tree li.collapsed > ul {
+ display: none;
+}
+
+ul.tree li::before, ul.tree li::after {
+ content: '';
+ position: absolute;
+ right: auto;
+ left: -0.2em;
+ border-color: @gray-light;
+ border-style: dotted;
+ border-width: 0;
+}
+
+/* This is the left vertical line */
+ul.tree li::before {
+ border-left-width: 1px;
+ top: -.5em;
+ width: 1em;
+ height: 2.5em;
+ bottom: 1em;
+}
+
+/* This is the horizontal dash in front of each item */
+ul.tree li::after {
+ border-top-width: 1px;
+ top: 1em;
+ width: 2em;
+ height: 1em;
+}
+
+/* Stop left vertical line at "mid-height" after last nodes (at each level) */
+ul.tree li:last-child::before {
+ height: 1.5em;
+}
+
+/* No border for the root element - there must be only ONE root */
+ul.tree > li::before, ul.tree > li::after {
+ display: none;
+}
+
+/* No connector before (each) root element */
+ul.tree > ul > li::before, ul.tree > ul > li::after {
+ border: 0;
+}
+
+ul.tree li a {
+ display: inline-block;
+ line-height: 2em;
+ padding: 0 .5em;
+ text-decoration: none;
+ color: @gray;
+ background-repeat: no-repeat;
+ background-position: 0.8em 0.4em;
+}
+
+ul.tree li a.error {
+ color: @color-critical-handled;
+}
+
+ul.tree li a:hover {
+ color: @text-color;
+ text-decoration: underline;
+}
+
+ul.tree li a.error:hover {
+ color: @color-critical;
+}
+
+/* charts should grow as much as possible but never beyond the current viewport's size */
+.svg-container-responsive {
+ padding: 1.5em;
+ height: 80vh;
+}
+
+.tipsy .tipsy-inner {
+ // overwrite tooltip max width, we need them to grow bigger
+ font-family: @font-family;
+ font-size: @font-size-small;
+ max-width: 300px;
+ text-align: left;
+ background-color: rgba(0,0,0,0.8);
+}
+
+.progress-label span {
+ font-size: 1.5em;
+ .animate(blink 1.4s infinite both);
+
+ &:nth-child(2) {
+ animation-delay: .2s;
+ }
+
+ &:nth-child(3) {
+ animation-delay: .4s;
+ }
+}
+
+.flyover:not(.flyover-expanded) .flyover-content {
+ display: none;
+}
+
+.flyover {
+ position: relative;
+
+ .flyover-content {
+ background-color: @body-bg-color;
+ border: 1px solid;
+ border-color: @gray-lighter;
+ box-shadow: 0 0 .5em 0 rgba(0, 0, 0, 0.2);
+ position: absolute;
+ padding: @vertical-padding @horizontal-padding;
+ .rounded-corners();
+ }
+
+ &.flyover-arrow-top .flyover-content:before {
+ background: @body-bg-color;
+ border-left: 1px solid @gray-lighter;
+ border-top: 1px solid @gray-lighter;
+ content: "";
+ height: 1em;
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ width: 1em;
+
+ position: absolute;
+ left: 6px;
+ top: -7px;
+ }
+
+ &.flyover-right .flyover-content {
+ left: auto;
+ right: 0;
+ }
+
+ &.flyover-arrow-top.flyover-right .flyover-content:before {
+ left: auto;
+ right: 6px;
+ }
+}
+
+.slice-state-ok {
+ stroke: @color-ok;
+ background: @color-ok;
+}
+
+.slice-state-warning-handled {
+ stroke: @color-warning-handled;
+ background: @color-warning-handled;
+}
+
+.slice-state-warning {
+ stroke: @color-warning;
+ background: @color-unreachable-handled;
+}
+
+.slice-state-critical-handled {
+ stroke: @color-critical-handled;
+ background: @color-critical-handled;
+}
+
+.slice-state-critical {
+ stroke: @color-critical;
+ background: @color-critical;
+}
+
+.slice-state-unknown-handled {
+ stroke: @color-unknown-handled;
+ background: @color-unknown-handled;
+}
+
+.slice-state-unknown {
+ stroke: @color-unknown;
+ background: @color-unknown;
+}
+
+.slice-state-unreachable-handled {
+ stroke: @color-unreachable-handled;
+ background: @color-unreachable-handled;
+}
+
+.slice-state-unreachable {
+ stroke: @color-unreachable;
+ background: @color-unreachable;
+}
+
+.slice-state-pending {
+ stroke: @color-pending;
+ background: @color-pending;
+}
+
+.slice-state-not-checked {
+ stroke: @gray-light;
+ background: @gray-light;
+}
+
+.donut {
+ width: 22em;
+ height: 22em;
+ min-width: 11.5em;
+ display: table;
+}
+
+.donut-graph {
+ width: 22em;
+ height: 22em;
+}
+
+.donut-label {
+ font-weight: bold;
+ fill: @text-color;
+}
+
+.donut-label {
+ margin-top: -12.5em;
+ text-align: center;
+}
+
+.donut-label-big {
+ color: @gray-light;
+ .fg-stateful();
+ font-size: 6em;
+ line-height: 0;
+ text-anchor: middle;
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+.donut-label-small {
+ fill: @text-color;
+ font-size: 1.2em;
+ text-anchor: middle;
+ -moz-transform: translateY(0.35em);
+ -ms-transform: translateY(0.35em);
+ -webkit-transform: translateY(0.35em);
+ transform: translateY(0.35em);
+}
+
+.donut-container {
+ float: left;
+
+ &:not(:last-of-type) {
+ margin-right: 10em;
+ }
+}
+
+.dashboard .donut-container .donut-legend {
+ margin-left: auto;
+}
+
+.donut-legend {
+ width: 50%;
+ padding: 0;
+ margin-left: 18em;
+ list-style-type: none;
+
+ li {
+ vertical-align: middle;
+
+ &:not(:last-child) {
+ margin-bottom: .5em;
+ }
+
+ .badge {
+ font-weight: bold;
+ margin-right: .5em;
+ vertical-align: initial;
+ }
+ }
+}
+
+html.no-js .progress-label {
+ display: none;
+}
diff --git a/public/css/modes/light.less b/public/css/modes/light.less
new file mode 100644
index 0000000..7044007
--- /dev/null
+++ b/public/css/modes/light.less
@@ -0,0 +1,4 @@
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+@enable-color-preference: 999999px;
+@prefer-light-color-scheme: 0px;
diff --git a/public/css/modes/none.less b/public/css/modes/none.less
new file mode 100644
index 0000000..05c618c
--- /dev/null
+++ b/public/css/modes/none.less
@@ -0,0 +1,4 @@
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+@enable-color-preference: 999999px;
+@prefer-light-color-scheme: 999999px;
diff --git a/public/css/modes/system.less b/public/css/modes/system.less
new file mode 100644
index 0000000..07bd06a
--- /dev/null
+++ b/public/css/modes/system.less
@@ -0,0 +1,4 @@
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+@enable-color-preference: 0px;
+@prefer-light-color-scheme: 999999px;
diff --git a/public/css/pdf/pdfprint.less b/public/css/pdf/pdfprint.less
new file mode 100644
index 0000000..2c68d37
--- /dev/null
+++ b/public/css/pdf/pdfprint.less
@@ -0,0 +1,103 @@
+/*! Icinga Web 2 | (c) 2014 Icinga GmbH | GPLv2+ */
+
+// Ensure styling is light, exports use a white background
+
+@gray: #7F7F7F;
+@gray-semilight: #A9A9A9;
+@gray-light: #C9C9C9;
+@gray-lighter: #EEEEEE;
+@gray-lightest: #F7F7F7;
+@icinga-blue: #0095BF;
+@low-sat-blue: #dae3e6;
+@low-sat-blue-dark: #becbcf;
+@body-bg-color: #fff;
+@text-color: @black;
+@text-color-light: @gray;
+@tr-active-color: @body-bg-color;
+@tr-hover-color: @body-bg-color;
+
+// Page layout
+
+@page {
+ margin: 1cm;
+}
+
+body {
+ font-family: sans-serif;
+ margin: 0;
+ padding-top: 37px; // ~ logo height in the header
+}
+
+.content {
+ font-size: 9pt;
+}
+
+#header,
+#footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ color: #aaa;
+ font-size: 0.9em;
+}
+
+#header {
+ top: 0;
+ border-bottom: 0.1pt solid #aaa;
+
+ .title {
+ text-align: left;
+ }
+
+ img {
+ margin-bottom: 3px;
+ }
+}
+
+#footer {
+ bottom: 0;
+ padding-top: 2em;
+}
+
+.content table {
+ margin-bottom: 3em;
+}
+
+#header table,
+#footer table {
+ width: 100%;
+ border-collapse: collapse;
+ border: none;
+}
+
+#header td,
+#header th,
+#footer td,
+#footer th {
+ padding: 0;
+ width: 50%;
+}
+
+.page-number {
+ padding-top: 0.5em;
+ border-top: 0.1pt solid #aaa;
+ text-align: center;
+}
+
+.page-number:before {
+ content: "Page " counter(page);
+}
+
+hr {
+ page-break-after: always;
+ border: 0;
+}
+
+// General style
+.state-icons,
+.overview-performance-data,
+.controls,
+.dontprint, // Compat only, use dont-print instead
+.dont-print {
+ display: none !important;
+}
diff --git a/public/css/themes/Winter.less b/public/css/themes/Winter.less
new file mode 100644
index 0000000..547d109
--- /dev/null
+++ b/public/css/themes/Winter.less
@@ -0,0 +1,32 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+@icinga-blue: #2b95ff;
+@control-color: @icinga-blue;
+
+@menu-2ndlvl-highlight-bg-color: transparent;
+
+.letitsnow {
+ background-image: url('../img/winter/snow1.png'), url('../img/winter/snow2.png'), url('../img/winter/snow3.png');
+ animation: ~"snow" 10s linear infinite;
+}
+
+#header-logo {
+ background-image: url('../img/winter/logo_icinga_big_winter.png');
+}
+
+/* Snow, from http://codepen.io/NickyCDK/pen/AIonk */
+#login, #header-logo-container, #main > .container > .controls > .tabs {
+ .letitsnow()
+}
+
+@keyframes ~"snow" {
+ 0% {background-position: 0px 0px, 0px 0px, 0px 0px;}
+ 50% {background-position: 500px 500px, 100px 200px, -100px 150px;}
+ 100% {background-position: 500px 1000px, 200px 400px, -100px 300px;}
+}
+
+#menu ul.nav-level-1 > .nav-item {
+ &:focus, &:hover {
+ .letitsnow()
+ }
+}
diff --git a/public/css/themes/colorblind.less b/public/css/themes/colorblind.less
new file mode 100644
index 0000000..c6df585
--- /dev/null
+++ b/public/css/themes/colorblind.less
@@ -0,0 +1,29 @@
+/*! Icinga Web 2 | (c) 2019 Icinga Development Team | GPLv2+ */
+
+@color-ok: fade(#77E08E, 25%);
+@color-critical: #FE5566;
+@color-critical-handled: fade(@color-critical, 33%);
+@color-warning: #B0A029;
+@color-warning-handled: fade(@color-warning, 33%);
+@color-unknown: #7791E0;
+@color-unknown-handled: fade(@color-unknown, 50%);
+@color-unreachable: @color-unknown;
+@color-unreachable-handled: @color-unknown-handled;
+@color-pending: fade(#FFFFFF, 75%);
+
+/* Adapt font color to match handled / unhandled states */
+.badge,
+.state-badge {
+ font-weight: bold;
+ color: @text-color !important;
+
+ &.handled,
+ &.state-up,
+ &.state-ok {
+ color: fade(@text-color, 75%) !important;
+ }
+}
+
+.processinfo .process > div.backend-running {
+ color: @text-color;
+}
diff --git a/public/css/themes/high-contrast.less b/public/css/themes/high-contrast.less
new file mode 100644
index 0000000..b22ffd5
--- /dev/null
+++ b/public/css/themes/high-contrast.less
@@ -0,0 +1,250 @@
+/*! Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
+
+@icinga-blue: #006D8C;
+
+// Gray colors
+@gray: #7F7F7F;
+@gray-semilight: #A9A9A9;
+@gray-light: #C9C9C9;
+@gray-lighter: #EEEEEE;
+@gray-lightest: #F7F7F7;
+@disabled-gray: #9a9a9a;
+
+// State colors
+@color-ok: #006400;
+@color-critical: #EE0000;
+@color-critical-handled: #EE0000;
+@color-warning: #8B5A00;
+@color-warning-handled: #8B5A00;
+@color-unknown: #800080;
+@color-unknown-handled: #800080;
+@color-unreachable: #800080;
+@color-unreachable-handled: #800080;
+@color-pending: #0000EE;
+
+// Icinga colors
+@low-sat-blue: #dae3e6;
+@low-sat-blue-dark: #c0cccd;
+
+// Background color for <body>
+@body-bg-color: @white;
+
+@text-color: #191919;
+@text-color-light: #555555;
+
+@menu-highlight-color: white;
+@menu-2ndlvl-color: white;
+@menu-2ndlvl-highlight-color: white;
+@menu-2ndlvl-active-hover-color: @text-color;
+
+#menu ul.nav-level-1 > .nav-item > a {
+ &:focus, &:hover {
+ text-decoration: underline;
+ }
+}
+
+#menu ul.nav-level-1 > .nav-item.active > a,
+#menu .nav-level-1 > .nav-item.active:not(.selected) > a:hover {
+ color: @text-color;
+ background-color: @white;
+}
+
+#menu .nav-level-2 > .nav-item.active {
+ background-color: @white;
+
+ a {
+ color: @text-color;
+ }
+
+ > a:focus, > a:hover {
+ opacity: 1;
+ }
+}
+
+#menu .nav-level-2 > .nav-item > a {
+ &:hover, &:focus {
+ text-decoration: underline;
+ }
+}
+
+#menu ul:not(.nav-level-2) > .selected > a {
+ color: @text-color;
+}
+
+#menu .active > a {
+ text-decoration: underline;
+}
+
+.badge:not(.handled),
+.state-badge:not(.handled) {
+ &.state-warning {
+ border: 1px solid @color-warning;
+ }
+
+ &.state-critical,
+ &.state-down {
+ border: 1px solid @color-critical;
+ }
+
+ &.state-unreachable {
+ border: 1px solid @color-unreachable;
+ }
+
+ &.state-unknown {
+ border: 1px solid @color-unknown;
+ }
+
+ &.state-ok,
+ &.state-up {
+ border: 1px solid @color-ok;
+ }
+}
+
+.badge.handled,
+.badge.state-ok,
+.state-badge.handled,
+.state-badge.state-ok {
+ background-color: @body-bg-color !important;
+ color: @text-color !important;
+
+ &.state-warning {
+ border: 1px solid @color-warning-handled;
+ }
+
+ &.state-critical,
+ &.state-down {
+ border: 1px solid @color-critical-handled;
+ }
+
+ &.state-unreachable {
+ border: 1px solid @color-unreachable-handled;
+ }
+
+ &.state-unknown {
+ border: 1px solid @color-unknown-handled;
+ }
+}
+
+.boxview a:focus {
+ color: @text-color;
+ text-decoration: underline;
+}
+
+.icinga-module.module-monitoring {
+ @timeline-notification-color: #1650CF;
+ @timeline-hard-state-color: #A24600;
+ @timeline-comment-color: #346964;
+ @timeline-ack-color: #855D18;
+ @timeline-downtime-start-color: #515151;
+ @timeline-downtime-end-color: #5e5e2f;
+
+ // Unfortunately it does not suffice to only override the timeline colors here, because our less compiler seems to
+ // have the related style block in module.less already evaluated
+
+ .timeline-notification {
+ background-color: @timeline-notification-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-notification-color, 20%);
+ }
+ }
+
+ .timeline-hard-state {
+ background-color: @timeline-hard-state-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-hard-state-color, 20%);
+ }
+ }
+
+ .timeline-comment {
+ background-color: @timeline-comment-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-comment-color, 20%);
+ }
+ }
+
+ .timeline-ack {
+ background-color: @timeline-ack-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-ack-color, 20%);
+ }
+ }
+
+ .timeline-downtime-start {
+ background-color: @timeline-downtime-start-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-downtime-start-color, 20%);
+ }
+ }
+
+ .timeline-downtime-end {
+ background-color: @timeline-downtime-end-color;
+
+ &.extrapolated {
+ background-color: lighten(@timeline-downtime-end-color, 20%);
+ }
+ }
+}
+
+.icinga-controls {
+ input:not([type="checkbox"]):not([type="radio"]),
+ .toggle-switch .toggle-slider:before,
+ .toggle-switch > .toggle-slider,
+ select,
+ textarea {
+ border: 1px solid @icinga-blue;
+ }
+
+ input[type="checkbox"]:not(:checked) + .toggle-switch .toggle-slider:before {
+ height: 1.166666667em;
+ width: 1.166666667em;
+ margin: 1px;
+ }
+}
+
+.search-suggestions {
+ input:not([type="checkbox"]):not([type="radio"]),
+ .toggle-switch .toggle-slider:before,
+ .toggle-switch > .toggle-slider,
+ select,
+ textarea {
+ border: none;
+ }
+}
+
+.icinga-module.module-icingadb .list-item.overdue {
+ background: none;
+
+ header > *:not(time),
+ .caption {
+ opacity: 1;
+ }
+}
+
+.controls input.search,
+input.search {
+ background-image: url(../img/icons/search.png);
+}
+
+.search-bar,
+.button-link,
+.view-mode-switcher > label {
+ border: 1px solid @icinga-blue;
+}
+
+// compensate for 1px border
+.filter-input-area {
+ padding: 1/12em !important;
+}
+
+.view-mode-switcher > label {
+ padding: (10/16)*.25em (10/16)*.5em !important;
+
+ &:not(:first-of-type) {
+ border-left: none;
+ }
+}
diff --git a/public/css/vendor/normalize.css b/public/css/vendor/normalize.css
new file mode 100644
index 0000000..458eea1
--- /dev/null
+++ b/public/css/vendor/normalize.css
@@ -0,0 +1,427 @@
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box; /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
diff --git a/public/error_norewrite.html b/public/error_norewrite.html
new file mode 100644
index 0000000..d85a002
--- /dev/null
+++ b/public/error_norewrite.html
@@ -0,0 +1 @@
+<h1>The rewrite module is not enabled</h1>
diff --git a/public/error_unavailable.html b/public/error_unavailable.html
new file mode 100644
index 0000000..1c2df0e
--- /dev/null
+++ b/public/error_unavailable.html
@@ -0,0 +1,7 @@
+<h1>Backend unavailable</h1>
+<p>
+ It seems that the PHP FPM service is not running. Make sure to start PHP FPM service in order to access Icinga Web 2.
+ If you upgraded Icinga Web 2 recently, make sure to read the
+ <a href="https://icinga.com/docs/icingaweb2/latest/doc/02-Installation/">docs regarding PHP FPM</a>,
+ also locally available under <code>/usr/share/icingaweb2/doc/02-Installation.md</code>.
+</p>
diff --git a/public/font/ifont.eot b/public/font/ifont.eot
new file mode 100644
index 0000000..3a9092f
--- /dev/null
+++ b/public/font/ifont.eot
Binary files differ
diff --git a/public/font/ifont.svg b/public/font/ifont.svg
new file mode 100644
index 0000000..2747b47
--- /dev/null
+++ b/public/font/ifont.svg
@@ -0,0 +1,286 @@
+<?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>Copyright (C) 2018 by original authors @ fontello.com</metadata>
+<defs>
+<font id="ifont" horiz-adv-x="1000" >
+<font-face font-family="ifont" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
+<missing-glyph horiz-adv-x="1000" />
+<glyph glyph-name="dashboard" unicode="&#xe800;" d="M286 154v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m0 285v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m357-285v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m-357 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m357-286v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m357-285v-108q0-22-16-37t-38-16h-178q-22 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m-357 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m357-286v-107q0-22-16-38t-38-15h-178q-22 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m0 286v-107q0-22-16-38t-38-16h-178q-22 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="user" unicode="&#xe801;" d="M714 69q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
+
+<glyph glyph-name="users" unicode="&#xe802;" d="M331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="ok" unicode="&#xe803;" d="M352-10l-334 333 158 160 176-174 400 401 159-160z" horiz-adv-x="928" />
+
+<glyph glyph-name="cancel" unicode="&#xe804;" d="M799 116l-156-157-234 235-235-235-156 157 234 234-234 234 156 157 235-235 234 235 156-157-234-234z" horiz-adv-x="817" />
+
+<glyph glyph-name="plus" unicode="&#xe805;" d="M911 462l0-223-335 0 0-336-223 0 0 336-335 0 0 223 335 0 0 335 223 0 0-335 335 0z" horiz-adv-x="928" />
+
+<glyph glyph-name="minus" unicode="&#xe806;" d="M18 239l0 223 893 0 0-223-893 0z" horiz-adv-x="928" />
+
+<glyph glyph-name="folder-empty" unicode="&#xe807;" d="M464 685l447 0 0-669q0-47-33-80t-79-33l-669 0q-46 0-79 33t-33 80l0 781 446 0 0-112z m-334 0l0-223 669 0 0 112-446 0 0 111-223 0z m669-669l0 335-669 0 0-335 669 0z" horiz-adv-x="928" />
+
+<glyph glyph-name="download" unicode="&#xe808;" d="M714 100q0 15-10 25t-25 11-25-11-11-25 11-25 25-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-39l-250-250q-10-11-25-11t-25 11l-250 250q-17 16-8 39 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="upload" unicode="&#xe809;" d="M714 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m143 0q0 14-10 25t-26 10-25-10-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-38t-38-16h-821q-23 0-38 16t-16 38v179q0 22 16 38t38 15h238q12-31 39-51t62-20h143q34 0 61 20t40 51h238q22 0 38-15t16-38z m-182 361q-9-22-33-22h-143v-250q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v250h-143q-23 0-33 22-9 22 8 39l250 250q10 10 25 10t25-10l250-250q18-17 8-39z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="git" unicode="&#xe80a;" d="M332 5q0 56-92 56-88 0-88-58 0-57 96-57 84 0 84 59z m-33 422q0 34-17 56t-49 23q-69 0-69-81 0-75 69-75 66 0 66 77z m150 180v-112q-20-7-44-13 9-24 9-47 0-70-41-120t-110-63q-22-5-33-15t-11-33q0-17 13-28t32-18 44-12 48-15 44-21 32-35 13-55q0-170-203-170-38 0-72 7t-65 23-49 46-18 71q0 92 102 125v3q-38 22-38 70 0 61 35 76v3q-40 13-66 60t-27 93q0 77 53 129t131 51q54 0 100-26 54 0 121 26z m178-491h-124q2 25 2 74v340q0 53-2 72h124q-3-19-3-69v-343q0-49 3-74z m335 124v-110q-40-22-97-22-35 0-60 12t-39 27-22 44-10 51-2 58v196h1v2q-4 0-11 0t-10 1q-12 0-33-3v106h54v42q0 30-4 50h127q-3-23-3-92h95v-106q-8 0-24 1t-24 1h-47v-204q0-73 48-73 34 0 61 19z m-321 528q0-32-22-57t-54-24q-32 0-54 24t-23 57q0 33 22 57t55 25q33 0 54-25t22-57z" horiz-adv-x="1000" />
+
+<glyph glyph-name="cubes" unicode="&#xe80b;" d="M357-61l214 107v176l-214-92v-191z m-36 254l226 96-226 97-225-97z m608-254l214 107v176l-214-92v-191z m-36 254l225 96-225 97-226-97z m-250 163l214 92v149l-214-92v-149z m-36 212l246 105-246 106-246-106z m607-289v-233q0-20-10-37t-29-26l-250-125q-14-8-32-8t-32 8l-250 125q-2 1-4 2-1-1-4-2l-250-125q-14-8-32-8t-31 8l-250 125q-19 9-29 26t-11 37v233q0 21 12 39t32 26l242 104v223q0 22 12 40t31 26l250 107q13 6 28 6t28-6l250-107q20-9 32-26t12-40v-223l242-104q20-8 32-26t11-39z" horiz-adv-x="1285.7" />
+
+<glyph glyph-name="database" unicode="&#xe80c;" d="M429 421q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-19-215 19-156 52-58 71v95q66-47 181-71t248-24z m0-428q132 0 247 24t181 71v-95q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v95q66-47 181-71t248-24z m0 214q132 0 247 24t181 71v-95q0-38-57-71t-157-52-214-20-215 20-156 52-58 71v95q66-47 181-71t248-24z m0 643q116 0 214-19t157-52 57-72v-71q0-39-57-72t-157-52-214-19-215 19-156 52-58 72v71q0 39 58 72t156 52 215 19z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="gauge" unicode="&#xe80d;" d="M214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
+
+<glyph glyph-name="sitemap" unicode="&#xe80e;" d="M1000 154v-179q0-22-16-38t-38-16h-178q-22 0-38 16t-16 38v179q0 22 16 38t38 15h53v107h-285v-107h53q23 0 38-15t16-38v-179q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v179q0 22 16 38t38 15h53v107h-285v-107h53q23 0 38-15t16-38v-179q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v179q0 22 16 38t38 15h53v107q0 29 21 51t51 21h285v107h-53q-23 0-38 16t-16 37v179q0 22 16 38t38 16h178q23 0 38-16t16-38v-179q0-22-16-37t-38-16h-53v-107h285q29 0 51-21t21-51v-107h53q23 0 38-15t16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="sort-name-up" unicode="&#xe80f;" d="M665 622h98l-40 122-6 26q-2 9-2 11h-2l-1-11q0 0-2-10t-5-16z m-254-576q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m466-66v-130h-326v50l206 295q7 11 12 16l6 5v1q-1 0-3 0t-5 0q-6-2-16-2h-130v-64h-67v128h317v-50l-206-296q-4-4-12-14l-6-7v-1l8 1q5 2 16 2h139v66h67z m50 501v-60h-161v60h42l-26 80h-136l-26-80h42v-60h-160v60h39l128 369h91l128-369h39z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="sort-name-down" unicode="&#xe810;" d="M665 51h98l-40 122-6 26q-2 9-2 11h-2l-1-11q0-1-2-10t-5-16z m-254-5q0-6-6-13l-178-178q-5-5-13-5-6 0-12 5l-179 179q-8 9-4 19 4 11 17 11h107v768q0 8 5 13t13 5h107q8 0 13-5t5-13v-768h107q8 0 13-5t5-13z m516-137v-59h-161v59h42l-26 80h-136l-26-80h42v-59h-160v59h39l128 370h91l128-370h39z m-50 643v-131h-326v51l206 295q7 10 12 15l6 5v2q-1 0-3-1t-5 0q-6-2-16-2h-130v-64h-67v128h317v-50l-206-295q-4-5-12-15l-6-5v-2l8 2q5 0 16 0h139v67h67z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="megaphone" unicode="&#xe811;" d="M929 493q29 0 50-21t21-51-21-50-50-21v-214q0-29-22-50t-50-22q-233 194-453 212-32-10-51-36t-17-57 22-51q-11-19-13-37t4-32 19-31 26-28 35-28q-17-32-63-46t-94-7-73 31q-4 13-17 49t-18 53-12 50-9 56 2 55 12 62h-68q-36 0-63 26t-26 63v107q0 37 26 63t63 26h268q243 0 500 215 29 0 50-22t22-50v-214z m-72-337v532q-220-168-428-191v-151q210-23 428-190z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bug" unicode="&#xe812;" d="M911 314q0-14-11-25t-25-10h-125q0-96-37-162l116-117q10-11 10-25t-10-25q-10-11-25-11t-25 11l-111 110q-3-3-8-7t-24-16-36-21-46-16-54-7v500h-71v-500q-29 0-57 7t-49 19-36 22-25 18l-8 8-102-116q-11-12-27-12-13 0-24 9-11 10-11 25t8 26l113 127q-32 63-32 153h-125q-15 0-25 10t-11 25 11 25 25 11h125v164l-97 97q-11 10-11 25t11 25 25 10 25-10l97-97h471l96 97q11 10 25 10t26-10 10-25-10-25l-97-97v-164h125q15 0 25-11t11-25z m-268 322h-357q0 74 52 126t126 52 127-52 52-126z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="tasks" unicode="&#xe813;" d="M571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="filter" unicode="&#xe814;" d="M783 685q9-22-8-39l-275-275v-414q0-23-22-33-7-3-14-3-15 0-25 11l-143 143q-10 11-10 25v271l-275 275q-18 17-8 39 9 22 33 22h714q23 0 33-22z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="off" unicode="&#xe815;" d="M857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 23-111t61-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-23 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="book" unicode="&#xe816;" d="M915 583q22-31 10-72l-154-505q-10-36-42-60t-69-25h-515q-43 0-83 30t-55 74q-14 37-1 71 0 2 1 15t3 20q0 5-2 12t-2 11q1 6 5 12t9 13 9 13q13 21 25 51t17 51q2 6 0 17t0 16q2 6 9 15t10 13q12 20 23 51t14 51q1 5-1 17t0 16q2 7 12 17t13 13q10 14 23 47t16 54q0 4-2 14t-1 15q1 4 5 10t10 13 10 11q4 7 9 17t8 20 9 20 11 18 15 13 20 6 26-3l0-1q21 5 28 5h425q41 0 64-32t10-72l-153-506q-20-66-40-85t-72-20h-485q-15 0-21-8-6-9-1-24 14-39 81-39h515q16 0 31 9t20 23l167 550q4 13 3 32 21-8 33-24z m-594-1q-2-7 1-12t11-6h339q8 0 15 6t9 12l12 36q2 7-1 12t-12 6h-339q-7 0-14-6t-9-12z m-46-143q-3-7 1-12t11-6h339q7 0 14 6t10 12l11 36q3 7-1 13t-11 5h-339q-7 0-14-5t-10-13z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="paste" unicode="&#xe817;" d="M429-79h500v358h-233q-22 0-37 15t-16 38v232h-214v-643z m142 804v36q0 7-5 12t-12 6h-393q-7 0-13-6t-5-12v-36q0-7 5-13t13-5h393q7 0 12 5t5 13z m143-375h167l-167 167v-167z m286-71v-375q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v89h-303q-23 0-38 16t-16 37v750q0 23 16 38t38 16h607q22 0 38-16t15-38v-183q12-7 20-15l228-228q16-15 27-42t11-49z" horiz-adv-x="1000" />
+
+<glyph glyph-name="scissors" unicode="&#xe818;" d="M536 350q14 0 25-11t10-25-10-25-25-10-25 10-11 25 11 25 25 11z m167-36l283-222q16-11 14-31-3-20-19-28l-72-36q-7-4-16-4-10 0-17 4l-385 216-62-36q-4-3-7-3 8-28 6-54-4-43-31-83t-74-69q-74-47-154-47-76 0-124 44-51 47-44 116 4 42 31 82t73 69q74 47 155 47 46 0 84-18 5 8 13 13l68 40-68 41q-8 5-13 12-38-17-84-17-81 0-155 47-46 30-73 69t-31 82q-3 33 8 63t36 52q47 44 124 44 80 0 154-47 46-29 74-68t31-83q2-27-6-54 3-1 7-3l62-37 385 216q7 5 17 5 9 0 16-4l72-36q16-9 19-28 2-20-14-32z m-380 145q26 24 12 61t-59 65q-52 33-107 33-42 0-63-20-26-24-12-60t59-66q51-33 107-33 41 0 63 20z m-47-415q45 28 59 65t-12 60q-22 20-63 20-56 0-107-33-45-28-59-65t12-60q21-20 63-20 55 0 107 33z m99 342l54-33v7q0 20 18 31l8 4-44 26-15-14q-1-2-5-6t-7-7q-1-1-2-2t-2-1z m125-125l54-18 410 321-71 36-429-240v-64l-89-53 5-5q1-1 4-3 2-2 6-7t6-6l15-15z m393-232l71 35-290 228-99-77q-1-2-7-4z" horiz-adv-x="1000" />
+
+<glyph glyph-name="globe" unicode="&#xe819;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m153-291q-2-1-6-5t-7-6q1 0 2 3t3 6 2 4q3 4 12 8 8 4 29 7 19 5 29-6-1 1 5 7t8 7q2 1 8 3t9 4l1 12q-7-1-10 4t-3 12q0-2-4-5 0 4-2 5t-7-1-5-1q-5 2-8 5t-5 9-2 8q-1 3-5 6t-5 6q-1 1-2 3t-1 4-3 3-3 1-4-3-4-5-2-3q-2 1-4 1t-2-1-3-1-3-2q-1-2-4-2t-5-1q8 3-1 6-5 2-9 2 6 2 5 6t-5 8h3q-1 2-5 5t-10 5-7 3q-5 3-19 5t-18 1q-3-4-3-6t2-8 2-7q1-3-3-7t-3-7q0-4 7-9t6-12q-2-4-9-9t-9-6q-3-5-1-11t6-9q1-1 1-2t-2-3-3-2-4-2l-1-1q-7-3-12 3t-7 15q-4 14-9 17-13 4-16-1-3 7-23 15-14 5-33 2 4 0 0 8-4 9-10 7 1 3 2 10t0 7q2 8 7 13 1 1 4 5t5 7 1 4q19-3 28 6 2 3 6 9t6 10q5 3 8 3t8-3 8-3q8-1 8 6t-4 11q7 0 2 10-2 4-5 5-6 2-15-3-4-2 2-4-1 0-6-6t-9-10-9 3q0 0-3 7t-5 8q-5 0-9-9 1 5-6 9t-14 4q11 7-4 15-4 3-12 3t-11-2q-2-4-3-7t3-4 6-3 6-2 5-2q8-6 5-8-1 0-5-2t-6-2-4-2q-1-3 0-8t-1-8q-3 3-5 10t-4 9q4-5-14-3l-5 0q-3 0-9-1t-12-1-7 5q-3 4 0 11 0 2 2 1-2 2-6 5t-6 5q-25-8-52-23 3 0 6 1 3 1 8 4t5 3q19 7 24 4l3 2q7-9 11-14-4 3-17 1-11-3-12-7 4-6 2-10-2 2-6 6t-8 6-8 3q-9 0-13-1-81-45-131-124 4-4 7-4 2-1 3-5t1-6 6 1q5-4 2-10 1 0 25-15 10-10 11-12 2-6-5-10-1 1-5 5t-5 2q-2-3 0-10t6-7q-4 0-5-9t-2-20 0-13l1-1q-2-6 3-19t12-11q-7-1 11-24 3-4 4-5 2-1 7-4t9-6 5-5q2-3 6-13t8-13q-2-3 5-11t6-13q-1 0-2-1t-1 0q2-4 9-8t8-7q1-2 1-6t2-6 4-1q2 11-13 35-8 13-9 16-2 2-4 8t-2 8q1 0 3 0t5-2 4-3 1-1q-1-4 1-10t7-10 10-11 6-7q4-4 8-11t0-8q5 0 11-5t10-11q3-5 4-15t3-13q1-4 5-8t7-5l9-5t7-3q3-2 10-6t12-7q6-2 9-2t8 1 8 2q8 1 16-8t12-12q20-10 30-6-1 0 1-4t4-9 5-8 3-5q3-3 10-8t10-8q4 2 4 5-1-5 4-11t10-6q8 2 8 18-17-8-27 10 0 0-2 3t-2 5-1 4 0 5 2 1q5 0 6 2t-1 7-2 8q-1 4-6 11t-7 8q-3-5-9-4t-9 5q0-1-1-3t-1-4q-7 0-8 0 1 2 1 10t2 13q1 2 3 6t5 9 2 7-3 5-9 1q-11 0-15-11-1-2-2-6t-2-6-5-4q-4-2-14-1t-13 3q-8 4-13 16t-5 20q0 6 1 15t2 14-3 14q2 1 5 5t5 6q2 1 3 1t3 0 2 1 1 3q0 1-2 2-1 1-2 1 4-1 16 1t15-1q9-6 12 1 0 1-1 6t0 7q3-15 16-5 2-1 9-3t9-2q2-1 4-3t3-3 3 0 5 4q5-8 7-13 6-23 10-25 4-2 6-1t3 5 0 8-1 7l-1 5v10l0 4q-8 2-10 7t0 10 9 10q0 1 4 2t9 4 7 4q12 11 8 20 4 0 6 5 0 0-2 2t-5 2-2 2q5 2 1 8 3 2 4 7t4 5q5-6 12-1 5 5 1 9 2 4 11 6t10 5q4-1 5 1t0 7 2 7q2 2 9 5t7 2l9 7q2 2 0 2 10-1 18 6 5 6-4 11 2 4-1 5t-9 4q2 0 7 0t5 1q9 5-3 9-10 2-24-7z m-91-490q115 21 195 106-1 2-7 2t-7 2q-10 4-13 5 1 4-1 7t-5 5-7 5-6 4q-1 1-4 3t-4 3-4 2-5 2-5-1l-2-1q-2 0-3-1t-3-2-2-1 0-2q-12 10-20 13-3 0-6 3t-6 4-6 0-6-3q-3-3-4-9t-1-7q-4 3 0 10t1 10q-1 3-6 2t-6-2-7-5-5-3-4-3-5-5q-2-2-4-6t-2-6q-1 2-7 3t-5 3q1-5 2-19t3-22q4-17-7-26-15-14-16-23-2-12 7-14 0-4-5-12t-4-12q0-3 2-9z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="cloud" unicode="&#xe81a;" d="M1071 207q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 74 40 135t104 91q-1 16-1 24 0 118 84 202t202 84q88 0 159-49t105-129q39 35 93 35 59 0 101-42t42-101q0-42-23-77 72-17 119-75t46-134z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="flash" unicode="&#xe81b;" d="M494 534q10-11 4-24l-302-646q-7-14-23-14-2 0-8 1-9 3-14 11t-3 16l110 451-226-56q-2-1-7-1-10 0-17 7-10 8-7 21l112 461q2 8 9 13t15 5h183q11 0 18-7t7-17q0-4-2-10l-96-258 221 54q5 2 7 2 11 0 19-9z" horiz-adv-x="500" />
+
+<glyph glyph-name="barchart" unicode="&#xe81c;" d="M143 46v-107q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v107q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 72v-179q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v179q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 143v-322q0-8-5-13t-12-5h-108q-7 0-12 5t-5 13v322q0 8 5 13t12 5h108q7 0 12-5t5-13z m215 214v-536q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v536q0 8 5 13t13 5h107q8 0 13-5t5-13z m214 286v-822q0-8-5-13t-13-5h-107q-8 0-13 5t-5 13v822q0 8 5 13t13 5h107q8 0 13-5t5-13z" horiz-adv-x="1000" />
+
+<glyph glyph-name="down-dir" unicode="&#xe81d;" d="M571 457q0-14-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 11-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="up-dir" unicode="&#xe81e;" d="M571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="left-dir" unicode="&#xe81f;" d="M357 600v-500q0-14-10-25t-26-11-25 11l-250 250q-10 11-10 25t10 25l250 250q11 11 25 11t26-11 10-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="right-dir" unicode="&#xe820;" d="M321 350q0-14-10-25l-250-250q-11-11-25-11t-25 11-11 25v500q0 15 11 25t25 11 25-11l250-250q10-10 10-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="down-open" unicode="&#xe821;" d="M939 399l-414-413q-10-11-25-11t-25 11l-414 413q-11 11-11 26t11 25l93 92q10 11 25 11t25-11l296-296 296 296q11 11 25 11t26-11l92-92q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+
+<glyph glyph-name="right-open" unicode="&#xe822;" d="M618 361l-414-415q-11-10-25-10t-25 10l-93 93q-11 11-11 25t11 25l296 297-296 296q-11 11-11 25t11 25l93 93q10 11 25 11t25-11l414-414q10-11 10-25t-10-25z" horiz-adv-x="714.3" />
+
+<glyph glyph-name="up-open" unicode="&#xe823;" d="M939 107l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
+
+<glyph glyph-name="left-open" unicode="&#xe824;" d="M654 682l-297-296 297-297q10-10 10-25t-10-25l-93-93q-11-10-25-10t-25 10l-414 415q-11 10-11 25t11 25l414 414q10 11 25 11t25-11l93-93q10-10 10-25t-10-25z" horiz-adv-x="714.3" />
+
+<glyph glyph-name="up-big" unicode="&#xe825;" d="M899 308q0-28-21-50l-41-42q-22-21-51-21-30 0-50 21l-165 164v-393q0-29-20-47t-51-19h-71q-30 0-51 19t-21 47v393l-164-164q-20-21-50-21t-50 21l-42 42q-21 21-21 50 0 30 21 51l363 363q20 21 50 21 30 0 51-21l363-363q21-22 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="right-big" unicode="&#xe826;" d="M821 314q0-30-20-50l-363-364q-22-20-51-20-29 0-50 20l-42 42q-22 21-22 51t22 51l163 163h-393q-29 0-47 21t-18 51v71q0 30 18 51t47 20h393l-163 165q-22 20-22 50t22 50l42 42q21 21 50 21 29 0 51-21l363-363q20-20 20-51z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="left-big" unicode="&#xe827;" d="M857 350v-71q0-30-18-51t-47-21h-393l164-164q21-20 21-50t-21-50l-42-43q-21-20-51-20-29 0-50 20l-364 364q-20 21-20 50 0 29 20 51l364 363q21 21 50 21 29 0 51-21l42-41q21-22 21-51t-21-51l-164-164h393q29 0 47-20t18-51z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="down-big" unicode="&#xe828;" d="M899 386q0-30-21-50l-363-364q-22-21-51-21-29 0-50 21l-363 364q-21 20-21 50 0 29 21 51l41 41q22 21 51 21 29 0 50-21l164-164v393q0 29 21 50t51 22h71q29 0 50-22t21-50v-393l165 164q20 21 50 21 29 0 51-21l41-41q21-22 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="resize-full-alt" unicode="&#xe829;" d="M716 548l-198-198 198-198 80 80q17 18 39 8 22-9 22-33v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 21 7 38l81 81-198 198-198-198 80-81q17-17 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l80-80 198 198-198 198-80-80q-11-11-25-11-7 0-14 3-22 9-22 33v250q0 14 11 25t25 11h250q23 0 33-23 9-21-8-38l-80-81 198-198 198 198-81 81q-17 17-7 38 9 23 32 23h250q15 0 26-11t10-25v-250q0-24-22-33-7-3-14-3-14 0-25 11z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="resize-full" unicode="&#xe82a;" d="M421 261q0-7-5-13l-185-185 80-81q10-10 10-25t-10-25-25-11h-250q-15 0-25 11t-11 25v250q0 15 11 25t25 11 25-11l80-80 186 185q5 6 12 6t13-6l64-63q5-6 5-13z m436 482v-250q0-15-10-25t-26-11-25 11l-80 80-185-185q-6-6-13-6t-13 6l-64 64q-5 5-5 12t5 13l186 185-81 81q-10 10-10 25t10 25 25 11h250q15 0 26-11t10-25z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="resize-small" unicode="&#xe82b;" d="M429 314v-250q0-14-11-25t-25-10-25 10l-81 81-185-186q-5-5-13-5t-12 5l-64 64q-6 6-6 13t6 13l185 185-80 80q-11 11-11 25t11 25 25 11h250q14 0 25-11t11-25z m421 375q0-7-6-12l-185-186 80-80q11-11 11-25t-11-25-25-11h-250q-14 0-25 11t-10 25v250q0 14 10 25t25 10 25-10l81-80 185 185q6 5 13 5t13-5l63-64q6-5 6-13z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="move" unicode="&#xe82c;" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-215v-215h72q14 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-11 10-11 25t11 25 25 10h72v215h-215v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h215v215h-72q-14 0-25 10t-11 25 11 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26t-11-25-25-10h-72v-215h215v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="resize-horizontal" unicode="&#xe82d;" d="M1000 350q0-14-11-25l-142-143q-11-11-26-11t-25 11-10 25v72h-572v-72q0-14-10-25t-25-11-25 11l-143 143q-11 11-11 25t11 25l143 143q10 11 25 11t25-11 10-25v-72h572v72q0 14 10 25t25 11 26-11l142-143q11-10 11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="resize-vertical" unicode="&#xe82e;" d="M393 671q0-14-11-25t-25-10h-71v-572h71q15 0 25-10t11-25-11-25l-143-143q-10-11-25-11t-25 11l-143 143q-10 10-10 25t10 25 25 10h72v572h-72q-14 0-25 10t-10 25 10 26l143 142q11 11 25 11t25-11l143-142q11-11 11-26z" horiz-adv-x="428.6" />
+
+<glyph glyph-name="zoom-in" unicode="&#xe82f;" d="M571 404v-36q0-7-5-13t-12-5h-125v-125q0-7-6-13t-12-5h-36q-7 0-13 5t-5 13v125h-125q-7 0-12 5t-6 13v36q0 7 6 12t12 5h125v125q0 8 5 13t13 5h36q7 0 12-5t6-13v-125h125q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="block" unicode="&#xe830;" d="M732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-111-41-153q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="zoom-out" unicode="&#xe831;" d="M571 404v-36q0-7-5-13t-12-5h-322q-7 0-12 5t-6 13v36q0 7 6 12t12 5h322q7 0 12-5t5-12z m72-18q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-21-50t-51-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="lightbulb" unicode="&#xe832;" d="M411 529q0-8-6-13t-12-5-13 5-5 13q0 25-30 39t-59 14q-7 0-13 5t-5 13 5 13 13 5q28 0 55-9t49-30 21-50z m89 0q0 40-19 74t-50 57-69 35-76 12-76-12-69-35-50-57-20-74q0-57 38-101 6-6 17-18t17-19q72-85 79-166h127q8 81 79 166 6 6 17 19t17 18q38 44 38 101z m71 0q0-87-57-150-25-27-42-48t-33-54-19-60q26-15 26-46 0-20-13-35 13-15 13-36 0-29-25-45 8-13 8-26 0-26-18-40t-43-14q-11-25-34-39t-48-15-49 15-33 39q-26 0-44 14t-17 40q0 13 7 26-25 16-25 45 0 21 14 36-14 15-14 35 0 31 26 46-2 28-19 60t-33 54-41 48q-58 63-58 150 0 55 25 103t65 79 92 49 104 19 104-19 91-49 66-79 24-103z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="clock" unicode="&#xe833;" d="M500 546v-250q0-7-5-12t-13-5h-178q-8 0-13 5t-5 12v36q0 8 5 13t13 5h125v196q0 8 5 13t12 5h36q8 0 13-5t5-13z m232-196q0 83-41 152t-110 111-152 41-153-41-110-111-41-152 41-152 110-111 153-41 152 41 110 111 41 152z m125 0q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="volume-up" unicode="&#xe834;" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z m214-304q0-42-24-79t-63-52q-5-3-14-3-14 0-25 10t-10 26q0 12 6 20t17 14 19 12 16 21 6 31-6 32-16 20-19 13-17 13-6 20q0 15 10 26t25 10q9 0 14-3 39-15 63-52t24-79z m143 0q0-85-48-158t-125-105q-7-3-14-3-15 0-26 11t-10 25q0 22 21 33 32 16 43 25 41 30 64 75t23 97-23 97-64 75q-11 9-43 25-21 11-21 33 0 14 10 25t25 11q8 0 15-3 78-33 125-105t48-158z m143 0q0-128-71-236t-189-158q-7-3-14-3-15 0-25 11t-11 25q0 20 22 33 4 2 12 6t13 6q25 14 46 28 68 51 107 127t38 161-38 161-107 127q-21 15-46 28-4 3-13 6t-12 6q-22 13-22 33 0 15 11 25t25 11q7 0 14-3 118-51 189-158t71-236z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="volume-down" unicode="&#xe835;" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z m214-304q0-42-24-79t-63-52q-5-3-14-3-14 0-25 10t-10 26q0 12 6 20t17 14 19 12 16 21 6 31-6 32-16 20-19 13-17 13-6 20q0 15 10 26t25 10q9 0 14-3 39-15 63-52t24-79z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="volume-off" unicode="&#xe836;" d="M429 654v-608q0-14-11-25t-25-10-25 10l-186 186h-146q-15 0-25 11t-11 25v214q0 15 11 25t25 11h146l186 186q10 10 25 10t25-10 11-25z" horiz-adv-x="428.6" />
+
+<glyph glyph-name="mute" unicode="&#xe837;" d="M151 323l-56-57q-24 58-24 120v71q0 15 11 25t25 11 25-11 11-25v-71q0-30 8-63z m622 336l-202-202v-71q0-74-52-126t-126-53q-31 0-61 11l-53-54q54-28 114-28 103 0 177 73t73 177v71q0 15 11 25t25 11 25-11 10-25v-71q0-124-82-215t-203-104v-74h142q15 0 26-11t10-25-10-25-26-11h-357q-14 0-25 11t-10 25 10 25 25 11h143v74q-70 7-131 45l-142-142q-5-6-13-6t-12 6l-46 46q-6 5-6 13t6 12l689 689q5 6 12 6t13-6l46-46q6-5 6-13t-6-12z m-212 73l-347-346v285q0 74 53 127t126 52q57 0 103-33t65-85z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="mic" unicode="&#xe838;" d="M643 457v-71q0-124-82-215t-204-104v-74h143q15 0 25-11t11-25-11-25-25-11h-357q-15 0-25 11t-11 25 11 25 25 11h143v74q-121 13-204 104t-82 215v71q0 15 11 25t25 11 25-11 10-25v-71q0-103 74-177t176-73 177 73 73 177v71q0 15 11 25t25 11 25-11 11-25z m-143 214v-285q0-74-52-126t-127-53-126 53-52 126v285q0 74 52 127t126 52 127-52 52-127z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="endtime" unicode="&#xe839;" d="M661 350q0-14-11-25l-303-304q-11-10-26-10t-25 10-10 25v161h-250q-15 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 10 25t25 10 26-10l303-304q11-10 11-25z m196 196v-392q0-67-47-114t-114-47h-178q-7 0-13 5t-5 13q0 2-1 11t0 15 2 13 5 11 12 3h178q37 0 64 27t26 63v392q0 37-26 64t-64 26h-174t-6 0-6 2-5 3-4 5-1 8q0 2-1 11t0 15 2 13 5 11 12 3h178q67 0 114-47t47-114z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="starttime" unicode="&#xe83a;" d="M357 46q0-2 1-11t0-14-2-14-5-11-12-3h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="calendar-empty" unicode="&#xe83b;" d="M71-79h786v572h-786v-572z m215 679v161q0 8-5 13t-13 5h-36q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h36q8 0 13 5t5 13z m428 0v161q0 8-5 13t-13 5h-35q-8 0-13-5t-5-13v-161q0-8 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="calendar" unicode="&#xe83c;" d="M71-79h161v161h-161v-161z m197 0h178v161h-178v-161z m-197 197h161v178h-161v-178z m197 0h178v178h-178v-178z m-197 214h161v161h-161v-161z m411-411h179v161h-179v-161z m-214 411h178v161h-178v-161z m428-411h161v161h-161v-161z m-214 197h179v178h-179v-178z m-196 482v161q0 7-6 12t-12 6h-36q-7 0-12-6t-6-12v-161q0-7 6-13t12-5h36q7 0 12 5t6 13z m410-482h161v178h-161v-178z m-214 214h179v161h-179v-161z m214 0h161v161h-161v-161z m18 268v161q0 7-5 12t-13 6h-35q-7 0-13-6t-5-12v-161q0-7 5-13t13-5h35q8 0 13 5t5 13z m215 36v-715q0-29-22-50t-50-21h-786q-29 0-50 21t-21 50v715q0 29 21 50t50 21h72v54q0 37 26 63t63 26h36q37 0 63-26t26-63v-54h214v54q0 37 27 63t63 26h35q37 0 64-26t26-63v-54h71q29 0 50-21t22-50z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="wrench" unicode="&#xe83d;" d="M214 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="sliders" unicode="&#xe83e;" d="M196 64v-71h-196v71h196z m197 72q14 0 25-11t11-25v-143q0-14-11-25t-25-11h-143q-14 0-25 11t-11 25v143q0 15 11 25t25 11h143z m89 214v-71h-482v71h482z m-357 286v-72h-125v72h125z m732-572v-71h-411v71h411z m-536 643q15 0 26-10t10-26v-142q0-15-10-25t-26-11h-142q-15 0-25 11t-11 25v142q0 15 11 26t25 10h142z m358-286q14 0 25-10t10-25v-143q0-15-10-25t-25-11h-143q-15 0-25 11t-11 25v143q0 14 11 25t25 10h143z m178-71v-71h-125v71h125z m0 286v-72h-482v72h482z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="services" unicode="&#xe83f;" d="M500 350q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m429-286q0 29-22 51t-50 21-50-21-21-51q0-29 21-50t50-21 51 21 21 50z m0 572q0 29-22 50t-50 21-50-21-21-50q0-30 21-51t50-21 51 21 21 51z m-215-235v-103q0-6-4-11t-8-6l-87-14q-6-19-18-42 19-27 50-64 4-6 4-11 0-7-4-11-12-17-46-50t-43-33q-7 0-12 4l-64 50q-21-11-43-17-6-60-13-87-4-13-17-13h-104q-6 0-11 4t-5 10l-13 85q-19 6-42 18l-66-50q-4-4-11-4-6 0-12 4-80 75-80 90 0 5 4 10 5 8 23 30t26 34q-13 24-20 46l-85 13q-5 1-9 5t-4 11v104q0 5 4 10t9 6l86 14q7 19 18 42-19 27-50 64-4 6-4 11 0 7 4 12 12 16 46 49t44 33q6 0 12-4l64-50q19 10 43 18 6 60 13 86 3 13 16 13h104q6 0 11-4t6-10l13-85q19-6 42-17l65 49q5 4 12 4 6 0 11-4 81-75 81-90 0-4-4-10-7-9-24-30t-25-34q13-27 19-46l85-12q6-2 9-6t4-11z m357-298v-78q0-9-83-17-6-15-16-29 28-63 28-77 0-2-2-4-68-40-69-40-5 0-26 27t-29 37q-11-1-17-1t-17 1q-7-11-29-37t-25-27q-1 0-69 40-3 2-3 4 0 14 29 77-10 14-17 29-83 8-83 17v78q0 9 83 18 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-38q12 1 17 1t17-1q28 40 51 63l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-9 83-18z m0 572v-78q0-9-83-18-6-15-16-29 28-63 28-77 0-2-2-4-68-39-69-39-5 0-26 26t-29 38q-11-1-17-1t-17 1q-7-12-29-38t-25-26q-1 0-69 39-3 2-3 4 0 14 29 77-10 14-17 29-83 9-83 18v78q0 9 83 17 7 16 17 29-29 63-29 77 0 2 3 4 2 1 19 11t33 19 17 9q4 0 25-26t29-37q12 1 17 1t17-1q28 39 51 62l4 1q2 0 69-39 2-2 2-4 0-14-28-77 9-13 16-29 83-8 83-17z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="service" unicode="&#xe840;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="phone" unicode="&#xe841;" d="M786 158q0-15-6-39t-12-38q-11-28-68-60-52-28-103-28-15 0-30 2t-32 7-26 8-31 11-28 10q-54 20-97 47-71 44-148 120t-120 148q-27 43-46 97-2 5-10 28t-12 31-8 26-7 32-2 29q0 52 29 104 31 57 59 68 14 6 38 12t39 6q8 0 12-2 10-3 30-42 6-11 16-31t20-35 17-30q2-2 10-14t12-20 4-16q0-11-16-27t-35-31-34-30-16-25q0-5 3-13t4-11 8-14 7-10q42-77 97-132t131-97q1 0 10-6t14-8 11-5 13-2q10 0 25 16t30 34 31 35 28 16q7 0 15-4t20-12 14-10q14-8 30-17t36-20 30-17q39-19 42-29 2-4 2-12z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="file-pdf" unicode="&#xe842;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-287 331q18-14 47-31 33 4 65 4 82 0 99-27 9-13 1-29 0-1-1-1l-1-2v0q-3-21-39-21-27 0-64 11t-73 29q-123-13-219-46-85-146-135-146-8 0-15 4l-14 7q0 0-3 2-6 6-4 20 5 23 32 51t73 54q8 5 13-3 1-1 1-2 29 47 60 110 38 76 58 146-13 46-17 89t4 71q6 22 23 22h12q13 0 20-8 10-12 5-38-1-3-2-4 0-2 0-5v-17q-1-68-8-107 31-91 82-133z m-321-229q29 13 76 88-29-22-49-47t-27-41z m222 513q-9-23-2-73 1 4 4 24 0 2 4 24 1 3 3 5-1 0-1 1-1 1-1 2 0 12-7 20 0-1 0-1v-2z m-70-368q76 30 159 45-1 0-7 5t-9 8q-43 37-71 98-15-48-46-110-17-31-26-46z m361 9q-13 13-78 13 42-16 69-16 8 0 10 1 0 0-1 2z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="file-word" unicode="&#xe843;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-656 500v-59h39l92-369h88l72 271q4 11 5 25 2 9 2 14h2l1-14q1-1 2-11t3-14l72-271h89l91 369h39v59h-167v-59h50l-55-245q-3-11-4-25l-1-12h-3q0 2 0 4t-1 4 0 4q-1 2-2 11t-3 14l-81 304h-63l-81-304q-1-5-2-13t-2-12l-2-12h-2l-2 12q-1 14-3 25l-56 245h50v59h-167z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="file-excel" unicode="&#xe844;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-547 131v-59h157v59h-42l58 90q3 4 5 9t5 8 2 2h1q0-2 3-6 1-2 2-4t3-4 4-5l60-90h-43v-59h163v59h-38l-107 152 108 158h38v59h-156v-59h41l-57-89q-2-4-6-9t-5-8l-1-1h-1q0 2-3 5-3 6-9 13l-59 89h42v59h-162v-59h38l106-152-109-158h-38z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="doc-text" unicode="&#xe845;" d="M819 638q16-16 27-42t11-50v-642q0-23-15-38t-38-16h-750q-23 0-38 16t-16 38v892q0 23 16 38t38 16h500q22 0 49-11t42-27z m-248 136v-210h210q-5 17-12 23l-175 175q-6 7-23 12z m215-853v572h-232q-23 0-38 16t-16 37v233h-429v-858h715z m-572 483q0 7 5 12t13 5h393q8 0 13-5t5-12v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36z m411-125q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z m0-143q8 0 13-5t5-13v-36q0-8-5-13t-13-5h-393q-8 0-13 5t-5 13v36q0 8 5 13t13 5h393z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="trash" unicode="&#xe846;" d="M286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="comment-empty" unicode="&#xe847;" d="M500 636q-114 0-213-39t-157-105-59-142q0-62 40-119t113-98l48-28-15-53q-13-51-39-97 85 36 154 96l24 21 32-3q38-5 72-5 114 0 213 39t157 105 59 142-59 142-157 105-213 39z m500-286q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12h-3q-8 0-15 6t-9 15v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 97 67 179t182 130 251 48 251-48 182-130 67-179z" horiz-adv-x="1000" />
+
+<glyph glyph-name="comment" unicode="&#xe848;" d="M1000 350q0-97-67-179t-182-130-251-48q-39 0-81 4-110-97-257-135-27-8-63-12-10-1-17 5t-10 16v1q-2 2 0 6t1 6 2 5l4 5t4 5 4 5q4 5 17 19t20 22 17 22 18 28 15 33 15 42q-88 50-138 123t-51 157q0 73 40 139t106 114 160 76 194 28q136 0 251-48t182-130 67-179z" horiz-adv-x="1000" />
+
+<glyph glyph-name="chat" unicode="&#xe849;" d="M786 421q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" />
+
+<glyph glyph-name="chat-empty" unicode="&#xe84a;" d="M393 636q-85 0-160-29t-118-79-44-107q0-45 30-88t83-73l54-32-19-46q19 11 34 21l25 18 30-6q43-8 85-8 85 0 160 29t118 79 43 106-43 107-118 79-160 29z m0 71q106 0 197-38t143-104 53-144-53-143-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38z m459-652q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128q0-67-40-126t-108-98z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bell" unicode="&#xe84b;" d="M509-96q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-372 160h726q-149 168-149 465 0 28-13 58t-39 58-67 45-95 17-95-17-67-45-39-58-13-58q0-297-149-465z m827 0q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
+
+<glyph glyph-name="bell-alt" unicode="&#xe84c;" d="M509-96q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m455 160q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
+
+<glyph glyph-name="attention-alt" unicode="&#xe84d;" d="M286 154v-125q0-15-11-25t-25-11h-143q-14 0-25 11t-11 25v125q0 14 11 25t25 10h143q15 0 25-10t11-25z m17 589l-16-429q-1-14-12-25t-25-10h-143q-14 0-25 10t-12 25l-15 429q-1 14 10 25t24 11h179q14 0 25-11t10-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="print" unicode="&#xe84e;" d="M214-7h500v143h-500v-143z m0 357h500v214h-89q-22 0-38 16t-16 38v89h-357v-357z m643-36q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m72 0v-232q0-7-6-12t-12-6h-125v-89q0-22-16-38t-38-16h-536q-22 0-37 16t-16 38v89h-125q-7 0-13 6t-5 12v232q0 44 32 76t75 31h36v304q0 22 16 38t37 16h375q23 0 50-12t42-26l85-85q15-16 27-43t11-49v-143h35q45 0 76-31t32-76z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="edit" unicode="&#xe84f;" d="M496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
+
+<glyph glyph-name="forward" unicode="&#xe850;" d="M1000 493q0-15-11-25l-285-286q-11-11-25-11t-25 11-11 25v143h-125q-55 0-98-3t-86-12-74-24-59-39-45-56-27-77-10-101q0-31 3-69 0-4 2-13t1-15q0-8-5-14t-13-6q-9 0-15 10-4 5-8 12t-7 17-6 13q-71 159-71 252 0 111 30 186 90 225 488 225h125v143q0 14 11 25t25 10 25-10l285-286q11-11 11-25z" horiz-adv-x="1000" />
+
+<glyph glyph-name="reply" unicode="&#xe851;" d="M1000 225q0-93-71-252-1-4-6-13t-7-17-7-12q-7-10-16-10-8 0-13 6t-5 14q0 5 1 15t2 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3h-125v-143q0-14-10-25t-26-11-25 11l-285 286q-11 10-11 25t11 25l285 286q11 10 25 10t26-10 10-25v-143h125q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
+
+<glyph glyph-name="reply-all" unicode="&#xe852;" d="M357 246v-39q0-23-22-33-7-3-14-3-15 0-25 11l-285 286q-11 10-11 25t11 25l285 286q17 17 39 8 22-10 22-33v-39l-221-222q-11-11-11-25t11-25z m643-21q0-32-9-74t-22-77-27-70-22-51l-11-22q-5-10-16-10-3 0-5 1-14 4-13 19 24 223-59 315-36 40-95 62t-150 29v-140q0-23-21-33-8-3-14-3-15 0-25 11l-286 286q-11 10-11 25t11 25l286 286q16 17 39 8 21-10 21-33v-147q230-15 335-123 94-96 94-284z" horiz-adv-x="1000" />
+
+<glyph glyph-name="eye" unicode="&#xe853;" d="M929 314q-85 132-213 197 34-58 34-125 0-103-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 243 68 186 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
+
+<glyph glyph-name="tag" unicode="&#xe854;" d="M250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="tags" unicode="&#xe855;" d="M250 600q0 30-21 51t-50 20-51-20-21-51 21-50 51-21 50 21 21 50z m595-321q0-30-20-51l-274-274q-22-21-51-21-30 0-50 21l-399 399q-21 21-36 57t-15 65v232q0 29 21 50t50 22h233q29 0 65-15t57-36l399-399q20-21 20-50z m215 0q0-30-21-51l-274-274q-22-21-51-21-20 0-33 8t-29 25l262 262q21 21 21 51 0 29-21 50l-399 399q-21 21-57 36t-65 15h125q29 0 65-15t57-36l399-399q21-21 21-50z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="lock-open-alt" unicode="&#xe856;" d="M589 421q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v179q0 103 74 177t176 73 177-73 73-177q0-14-10-25t-25-11h-36q-14 0-25 11t-11 25q0 59-42 101t-101 42-101-42-41-101v-179h410z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="lock-open" unicode="&#xe857;" d="M929 529v-143q0-15-11-25t-25-11h-36q-14 0-25 11t-11 25v143q0 59-41 101t-101 41-101-41-42-101v-108h53q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h375v108q0 103 73 176t177 74 176-74 74-176z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="lock" unicode="&#xe858;" d="M179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
+
+<glyph glyph-name="home" unicode="&#xe859;" d="M786 296v-267q0-15-11-25t-25-11h-214v214h-143v-214h-214q-15 0-25 11t-11 25v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-3-7 1-12 6l-35 41q-4 6-3 13t6 12l401 334q18 15 42 15t43-15l136-113v108q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q6-4 6-12t-4-13z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="info" unicode="&#xe85a;" d="M357 100v-71q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v71q0 15 11 25t25 11h35v214h-35q-15 0-25 11t-11 25v71q0 15 11 25t25 11h214q15 0 25-11t11-25v-321h35q15 0 26-11t10-25z m-71 643v-107q0-15-11-25t-25-11h-143q-14 0-25 11t-11 25v107q0 14 11 25t25 11h143q15 0 25-11t11-25z" horiz-adv-x="357.1" />
+
+<glyph glyph-name="help" unicode="&#xe85b;" d="M393 149v-134q0-9-7-15t-15-7h-134q-9 0-16 7t-7 15v134q0 9 7 16t16 6h134q9 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 11t-6 20v26q0 46 37 87t79 60q33 16 47 32t14 42q0 24-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="search" unicode="&#xe85c;" d="M643 386q0 103-73 176t-177 74-177-74-73-176 73-177 177-73 177 73 73 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 153-31 125-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="flapping" unicode="&#xe85d;" d="M372 582q-34-52-77-153-12 25-20 41t-23 35-28 32-36 19-45 8h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125q139 0 229-125z m628-446q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107q-18 0-48 0t-45-1-41 1-39 3-36 6-35 10-32 16-33 22-31 30-31 39q33 52 76 152 12-25 20-40t23-36 28-31 35-20 46-8h143v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z m0 500q0-8-5-13l-179-179q-5-5-12-5-8 0-13 6t-5 12v107h-143q-27 0-49-8t-38-25-29-34-25-44q-18-34-43-95-16-37-28-62t-30-59-36-55-41-47-50-38-60-23-71-10h-125q-8 0-13 5t-5 13v107q0 8 5 13t13 5h125q27 0 48 9t39 25 28 34 26 43q17 35 43 96 16 36 28 62t30 58 36 56 41 46 50 39 59 23 72 9h143v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
+
+<glyph glyph-name="rewind" unicode="&#xe85e;" d="M532 736q170 0 289-120t119-290-119-290-289-120q-142 0-252 88l70 74q84-60 182-60 126 0 216 90t90 218-90 218-216 90q-124 0-214-87t-92-211l142 0-184-204-184 204 124 0q2 166 122 283t286 117z" horiz-adv-x="940" />
+
+<glyph glyph-name="chart-line" unicode="&#xe85f;" d="M1143-7v-72h-1143v858h71v-786h1072z m-72 696v-242q0-12-10-17t-20 4l-68 68-353-353q-6-6-13-6t-13 6l-130 130-232-233-107 108 327 326q5 6 12 6t13-6l130-130 259 259-67 68q-9 8-5 19t17 11h243q7 0 12-5t5-13z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="bell-off" unicode="&#xe860;" d="M869 375q35-199 167-311 0-29-21-50t-51-21h-250q0-59-42-101t-101-42-100 42-42 100z m-298-480q9 0 9 9t-9 8q-32 0-56 24t-24 57q0 9-9 9t-9-9q0-41 29-70t69-28z m560 893q4-6 4-14t-6-12l-1045-905q-5-5-13-4t-12 6l-47 53q-4 6-4 14t6 12l104 90q-11 17-11 36 28 24 51 49t47 67 42 89 28 115 11 145q0 84 65 157t171 89q-4 10-4 21 0 23 16 38t37 16 38-16 16-38q0-11-4-21 69-10 122-46t82-88l234 202q5 5 13 4t12-6z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="bell-off-empty" unicode="&#xe861;" d="M580-96q0 8-9 8-32 0-56 24t-24 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-299 265l489 424q-23 49-74 82t-125 32q-51 0-94-17t-68-45-38-58-14-58q0-215-76-360z m755-105q0-29-21-50t-51-21h-250q0-59-42-101t-101-42-100 42-42 100l83 72h422q-92 105-126 256l61 55q35-199 167-311z m48 777l47-53q4-6 4-14t-6-12l-1045-905q-5-5-13-4t-12 6l-47 53q-4 6-4 14t6 12l104 90q-11 17-11 36 28 24 51 49t47 67 42 89 28 115 11 145q0 84 65 157t171 89q-4 10-4 21 0 23 16 38t37 16 38-16 16-38q0-11-4-21 69-10 122-46t82-88l234 202q5 5 13 4t12-6z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="plug" unicode="&#xe862;" d="M979 597q21-21 21-50t-21-51l-223-223 83-84-89-89q-91-91-217-104t-230 56l-202-202h-101v101l202 202q-69 103-56 230t104 217l89 89 84-83 223 223q21 21 51 21t50-21 21-50-21-51l-223-223 131-131 223 223q22 21 51 21t50-21z" horiz-adv-x="1000" />
+
+<glyph glyph-name="eye-off" unicode="&#xe863;" d="M310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
+
+<glyph glyph-name="arrows-cw" unicode="&#xe864;" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="cw" unicode="&#xe865;" d="M408 760q168 0 287-116t123-282l122 0-184-206-184 206 144 0q-4 124-94 210t-214 86q-126 0-216-90t-90-218q0-126 90-216t216-90q104 0 182 60l70-76q-110-88-252-88-168 0-288 120t-120 290 120 290 288 120z" horiz-adv-x="940" />
+
+<glyph glyph-name="host" unicode="&#xe866;" d="M232 136q-37 0-63 26t-26 63v393q0 37 26 63t63 26h607q37 0 63-26t27-63v-393q0-37-27-63t-63-26h-607z m-18 482v-393q0-7 6-13t12-5h607q8 0 13 5t5 13v393q0 7-5 12t-13 6h-607q-7 0-12-6t-6-12z m768-518h89v-54q0-22-26-37t-63-16h-893q-36 0-63 16t-26 37v54h982z m-402-54q9 0 9 9t-9 9h-89q-9 0-9-9t9-9h89z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="thumbs-up" unicode="&#xe867;" d="M143 100q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m643 321q0 29-22 50t-50 22h-196q0 32 27 89t26 89q0 55-17 81t-72 27q-14-15-21-48t-17-70-33-61q-13-13-43-51-2-3-13-16t-18-23-19-24-22-25-22-19-22-15-20-6h-18v-357h18q7 0 18-1t18-4 21-6 20-7 20-6 16-6q118-41 191-41h67q107 0 107 93 0 15-2 31 16 9 26 30t10 41-10 38q29 28 29 67 0 14-5 31t-14 26q18 1 30 26t12 45z m71 1q0-50-27-91 5-18 5-38 0-43-21-81 1-12 1-24 0-56-33-99 0-78-48-123t-126-45h-72q-54 0-106 13t-121 36q-65 23-77 23h-161q-29 0-50 21t-21 50v357q0 30 21 51t50 21h153q20 13 77 86 32 42 60 72 13 14 19 48t17 70 35 60q22 21 50 21 47 0 84-18t57-57 20-104q0-51-27-107h98q58 0 101-42t42-100z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="thumbs-down" unicode="&#xe868;" d="M143 600q0 15-11 25t-25 11-25-11-11-25 11-25 25-11 25 11 11 25z m643-321q0 19-12 45t-30 26q8 10 14 27t5 31q0 38-29 66 10 17 10 38 0 21-10 41t-26 30q2 16 2 31 0 47-27 70t-76 23h-71q-73 0-191-41-3-1-16-5t-20-7-20-7-21-6-18-4-18-1h-18v-357h18q9 0 20-5t22-15 22-20 22-25 19-24 18-22 13-17q30-38 43-51 23-24 33-61t17-70 21-48q54 0 72 27t17 81q0 33-26 89t-27 89h196q28 0 50 22t22 50z m71-1q0-57-42-100t-101-42h-98q27-55 27-107 0-66-20-104-19-39-57-57t-84-18q-28 0-50 21-19 18-30 45t-14 51-10 47-17 36q-27 28-60 71-57 73-77 86h-153q-29 0-50 21t-21 51v357q0 29 21 50t50 21h161q12 0 77 23 72 24 125 36t111 13h63q78 0 126-44t48-121v-3q33-43 33-99 0-12-1-24 21-38 21-80 0-21-5-39 27-41 27-91z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="spinner" unicode="&#xe869;" d="M294 72q0-29-21-50t-51-21q-29 0-50 21t-21 50q0 30 21 51t50 21 51-21 21-51z m277-115q0-29-20-50t-51-21-50 21-21 50 21 51 50 21 51-21 20-51z m-392 393q0-30-21-50t-51-21-50 21-21 50 21 51 50 20 51-20 21-51z m670-278q0-29-21-50t-50-21q-30 0-51 21t-20 50 20 51 51 21 50-21 21-51z m-538 556q0-37-26-63t-63-26-63 26-26 63 26 63 63 26 63-26 26-63z m653-278q0-30-21-50t-50-21-51 21-21 50 21 51 51 20 50-20 21-51z m-357 393q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m296-115q0-52-37-88t-88-37q-52 0-88 37t-37 88q0 51 37 88t88 37q51 0 88-37t37-88z" horiz-adv-x="1000" />
+
+<glyph glyph-name="attach" unicode="&#xe86a;" d="M784 77q0-65-45-109t-109-44q-75 0-131 55l-434 434q-63 64-63 151 0 89 62 150t150 62q88 0 152-63l338-338q5-5 5-12 0-9-17-26t-26-17q-7 0-12 5l-339 339q-44 43-101 43-59 0-100-42t-40-101q0-58 42-101l433-433q35-36 81-36 36 0 59 24t24 59q0 46-35 81l-325 324q-14 14-33 14-16 0-27-11t-11-27q0-18 14-33l229-228q6-6 6-13 0-9-18-26t-26-17q-6 0-12 5l-229 229q-35 34-35 83 0 46 32 78t77 32q49 0 84-35l324-325q56-54 56-131z" horiz-adv-x="785.7" />
+
+<glyph glyph-name="keyboard" unicode="&#xe86b;" d="M214 198v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m72 143v-53q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h125q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m572-286v-53q0-9-9-9h-482q-9 0-9 9v53q0 9 9 9h482q9 0 9-9z m-357 143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-72 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-8-9h-54q-9 0-9 9v53q0 9 9 9h54q8 0 8-9z m-71 143v-54q0-9-9-9h-53q-9 0-9 9v54q0 9 9 9h53q9 0 9-9z m214-143v-53q0-9-9-9h-53q-9 0-9 9v53q0 9 9 9h53q9 0 9-9z m215-143v-53q0-9-9-9h-54q-9 0-9 9v53q0 9 9 9h54q9 0 9-9z m-286 286v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-54q0-9-9-9h-54q-9 0-9 9v54q0 9 9 9h54q9 0 9-9z m143 0v-196q0-9-9-9h-125q-9 0-9 9v53q0 9 9 9h62v134q0 9 9 9h54q9 0 9-9z m71-420v500h-929v-500h929z m71 500v-500q0-29-20-50t-51-21h-929q-29 0-50 21t-21 50v500q0 30 21 51t50 21h929q30 0 51-21t20-51z" horiz-adv-x="1071.4" />
+
+<glyph glyph-name="menu" unicode="&#xe86c;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="wifi" unicode="&#xe86d;" d="M571 0q-11 0-51 41t-41 52q0 18 35 30t57 13 58-13 35-30q0-11-41-52t-52-41z m151 151q-1 0-22 14t-57 28-72 14-71-14-57-28-22-14q-10 0-52 42t-42 52q0 7 5 13 44 43 109 67t130 25 131-25 109-67q5-6 5-13 0-10-42-52t-52-42z m152 152q-6 0-12 5-76 58-141 86t-150 27q-47 0-95-12t-83-29-63-35-44-30-18-12q-9 0-51 42t-42 52q0 7 6 12 74 74 178 115t212 40 213-40 178-115q6-5 6-12 0-10-42-52t-52-42z m152 151q-6 0-13 5-99 88-207 132t-235 45-234-45-207-132q-7-5-13-5-9 0-51 42t-43 52q0 7 6 13 104 104 248 161t294 57 295-57 248-161q5-6 5-13 0-10-42-52t-51-42z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="moon" unicode="&#xe86e;" d="M704 123q-30-5-61-5-102 0-188 50t-137 137-50 188q0 107 58 199-112-33-183-128t-72-214q0-72 29-139t76-113 114-77 139-28q80 0 152 34t123 96z m114 47q-53-113-159-181t-230-68q-87 0-167 34t-136 92-92 137-34 166q0 85 32 163t87 135 132 92 161 38q25 1 34-22 11-23-8-40-48-43-73-101t-26-122q0-83 41-152t111-111 152-41q66 0 127 29 23 10 40-7 8-8 10-19t-2-22z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="chart-pie" unicode="&#xe86f;" d="M429 353l304-304q-59-61-138-94t-166-34q-117 0-216 58t-155 156-58 215 58 215 155 156 216 58v-426z m104-3h431q0-88-33-167t-94-138z m396 71h-429v429q117 0 215-57t156-156 58-216z" horiz-adv-x="1000" />
+
+<glyph glyph-name="chart-area" unicode="&#xe870;" d="M1143-7v-72h-1143v858h71v-786h1072z m-214 571l142-500h-928v322l250 321 321-321z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="chart-bar" unicode="&#xe871;" d="M357 350v-286h-143v286h143z m214 286v-572h-142v572h142z m572-643v-72h-1143v858h71v-786h1072z m-357 500v-429h-143v429h143z m214 214v-643h-143v643h143z" horiz-adv-x="1142.9" />
+
+<glyph glyph-name="beaker" unicode="&#xe872;" d="M852 42q31-50 12-85t-78-36h-643q-59 0-78 36t12 85l280 443v222h-36q-14 0-25 11t-10 25 10 25 25 11h286q15 0 25-11t11-25-11-25-25-11h-36v-222z m-435 405l-151-240h397l-152 240-11 17v243h-71v-243z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="magic" unicode="&#xe873;" d="M664 526l164 163-60 60-164-163z m250 163q0-15-10-25l-718-718q-10-10-25-10t-25 10l-111 111q-10 10-10 25t10 25l718 718q10 10 25 10t25-10l111-111q10-10 10-25z m-754 106l54-16-54-17-17-55-17 55-55 17 55 16 17 55z m195-90l109-34-109-33-34-109-33 109-109 33 109 34 33 109z m519-267l55-17-55-16-17-55-17 55-54 16 54 17 17 55z m-357 357l54-16-54-17-17-55-17 55-54 17 54 16 17 55z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="spin6" unicode="&#xe874;" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
+
+<glyph glyph-name="down-small" unicode="&#xe875;" d="M505 346q15-15 15-37t-15-37l-245-245-245 245q-15 15-15 37t15 37 37 15 37-15l120-119 0 395q0 21 15 36t36 15 37-15 16-36l0-395 120 119q15 15 36 15t36-15z" horiz-adv-x="520" />
+
+<glyph glyph-name="left-small" unicode="&#xe876;" d="M595 403q21 0 36-16t15-37-15-37-36-15l-395 0 119-119q15-15 15-37t-15-37-36-15q-23 0-38 15l-245 245 245 245q15 15 37 15t37-15 15-37-15-37l-119-118 395 0z" horiz-adv-x="646" />
+
+<glyph glyph-name="right-small" unicode="&#xe877;" d="M328 595q15 15 36 15t37-15l245-245-245-245q-15-15-36-15-22 0-37 15t-15 37 15 37l120 119-395 0q-22 0-37 15t-16 37 16 37 37 16l395 0-120 118q-15 15-15 37t15 37z" horiz-adv-x="646" />
+
+<glyph glyph-name="up-small" unicode="&#xe878;" d="M260 673l245-245q15-15 15-37t-15-37-36-15-36 15l-120 120 0-395q0-21-16-37t-37-15-36 15-15 37l0 395-120-120q-15-15-37-15t-37 15-15 37 15 37z" horiz-adv-x="520" />
+
+<glyph glyph-name="pin" unicode="&#xe879;" d="M573 37q0-23-15-38t-37-15q-21 0-37 16l-169 169-315-236 236 315-168 169q-24 23-12 56 14 32 48 32 157 0 270 57 90 45 151 171 9 24 36 32t50-13l208-209q21-23 14-50t-32-36q-127-63-172-152-56-110-56-268z" horiz-adv-x="834" />
+
+<glyph glyph-name="angle-double-left" unicode="&#xe87a;" d="M350 82q0-7-6-13l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13z m214 0q0-7-5-13l-28-28q-6-5-13-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q6 6 13 6t13-6l28-28q5-5 5-13t-5-12l-220-220 220-219q5-6 5-13z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="angle-double-right" unicode="&#xe87b;" d="M332 314q0-7-5-12l-261-261q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l261-260q5-5 5-13z m214 0q0-7-5-12l-260-261q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13z" horiz-adv-x="571.4" />
+
+<glyph glyph-name="circle" unicode="&#xe87c;" d="M857 350q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="info-circled" unicode="&#xe87d;" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m52-152q-42 0-65-24t-23-50q-2-28 15-44t49-16q38 0 61 22t23 54q0 58-60 58z m-120-594q30 0 84 26t106 78l-18 24q-48-36-72-36-14 0-4 38l42 160q26 96-22 96-30 0-89-29t-115-75l16-26q52 34 74 34 12 0 0-34l-36-152q-26-104 34-104z" horiz-adv-x="920" />
+
+<glyph glyph-name="twitter" unicode="&#xe87e;" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="facebook-squared" unicode="&#xe87f;" d="M696 779q67 0 114-48t47-113v-536q0-66-47-113t-114-48h-104v333h111l16 129h-127v83q0 31 13 46t51 16l68 1v115q-35 5-100 5-75 0-121-44t-45-127v-95h-112v-129h112v-333h-297q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="gplus-squared" unicode="&#xe880;" d="M512 345q0 15-4 36h-202v-74h122q-2-13-10-28t-21-29-37-25-54-10q-55 0-94 40t-39 95 39 95 94 40q52 0 86-33l58 57q-60 55-144 55-89 0-151-62t-63-152 63-151 151-63q92 0 149 58t57 151z m192-26h61v62h-61v61h-61v-61h-61v-62h61v-61h61v61z m153 299v-536q0-66-47-113t-114-48h-535q-67 0-114 48t-47 113v536q0 66 47 113t114 48h535q67 0 114-48t47-113z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="attention-circled" unicode="&#xe881;" d="M429 779q116 0 215-58t156-156 57-215-57-215-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58z m71-696v106q0 8-5 13t-12 5h-107q-8 0-13-5t-6-13v-106q0-8 6-13t13-6h107q7 0 12 6t5 13z m-1 192l10 346q0 7-6 10-5 5-13 5h-123q-8 0-13-5-6-3-6-10l10-346q0-6 5-10t14-4h103q8 0 13 4t6 10z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="check" unicode="&#xe883;" d="M786 331v-177q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-6-5-13-5-1 0-5 1-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v141q0 8 5 13l36 35q6 6 13 6 3 0 7-2 11-4 11-16z m129 273l-455-454q-13-14-31-14t-32 14l-240 240q-14 13-14 31t14 32l61 62q14 13 32 13t32-13l147-147 361 361q13 13 31 13t32-13l62-61q13-14 13-32t-13-32z" horiz-adv-x="928.6" />
+
+<glyph glyph-name="reschedule" unicode="&#xe884;" d="M186 140l116 116 0-292-276 16 88 86q-116 122-114 290t120 288q100 100 240 116l4-102q-100-16-172-88-88-88-90-213t84-217z m332 598l276-16-88-86q116-122 114-290t-120-288q-96-98-240-118l-2 104q98 16 170 88 88 88 90 213t-84 217l-114-116z" horiz-adv-x="820" />
+
+<glyph glyph-name="warning-empty" unicode="&#xe885;" d="M514 701q-49 0-81-55l-308-513q-32-55-11-95t87-40l625 0q65 0 87 40t-12 95l-307 513q-33 55-80 55z m0 105q106 0 169-107l308-513q63-105 12-199-52-93-177-93l-625 0q-123 0-177 93-53 92 11 199l309 513q62 107 170 107z m-69-652q0 69 69 69 67 0 67-69 0-67-67-67-69 0-69 67z m146 313q0-14-6-29l-71-179q-44 108-73 179-6 15-6 29 0 32 23 55t56 24 55-24 22-55z" horiz-adv-x="1026" />
+
+<glyph glyph-name="th-list" unicode="&#xf009;" d="M0 62q0-30 21-51t51-21h32q30 0 51 21t21 51-21 51-51 21h-32q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h32q30 0 51 21t21 51-21 51-51 21h-32q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h32q30 0 51 21t21 51-21 51-51 21h-32q-30 0-51-21t-21-51z m234-576q0-30 21-51t51-21h559q30 0 51 21t21 51-21 51-51 21h-559q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h559q30 0 51 21t21 51-21 51-51 21h-559q-30 0-51-21t-21-51z m0 288q0-30 21-51t51-21h559q30 0 51 21t21 51-21 51-51 21h-559q-30 0-51-21t-21-51z" horiz-adv-x="937.5" />
+
+<glyph glyph-name="th-thumb-empty" unicode="&#xf00b;" d="M0-66v286q0 22 15 37t37 16h286q21 0 37-16t15-37v-286q0-21-15-36t-37-15h-286q-22 0-37 15t-15 36z m0 546v286q0 21 15 36t37 15h286q21 0 37-15t15-36v-286q0-22-15-37t-37-16h-286q-21 0-37 16t-15 37z m88-510h214v214h-214v-214z m0 546h214v213h-214v-213z m459-582v286q0 22 15 37t37 16h286q21 0 37-16t15-37v-286q0-21-15-36t-37-15h-286q-21 0-37 15t-15 36z m0 546v286q0 21 15 36t37 15h286q22 0 37-15t15-36v-286q0-22-15-37t-37-16h-286q-21 0-37 16t-15 37z m88-510h215v214h-215v-214z m0 546h215v213h-215v-213z" horiz-adv-x="937.5" />
+
+<glyph glyph-name="github-circled" unicode="&#xf09b;" d="M429 779q116 0 215-58t156-156 57-215q0-140-82-252t-211-155q-15-3-22 4t-7 17q0 1 0 43t0 75q0 54-29 79 32 3 57 10t53 22 45 37 30 58 11 84q0 67-44 115 21 51-4 114-16 5-46-6t-51-25l-21-13q-52 15-107 15t-108-15q-8 6-23 15t-47 22-47 7q-25-63-5-114-44-48-44-115 0-47 12-83t29-59 45-37 52-22 57-10q-21-20-27-58-12-5-25-8t-32-3-36 12-31 35q-11 18-27 29t-28 14l-11 1q-12 0-16-2t-3-7 5-8 7-6l4-3q12-6 24-21t18-29l6-13q7-21 24-34t37-17 39-3 31 1l13 3q0-22 0-50t1-30q0-10-8-17t-22-4q-129 43-211 155t-82 252q0 117 58 215t155 156 216 58z m-267-616q2 4-3 7-6 1-8-1-1-4 4-7 5-3 7 1z m18-19q4 3-1 9-6 5-9 2-4-3 1-9 5-6 9-2z m16-25q6 4 0 11-4 7-9 3-5-3 0-10t9-4z m24-23q4 4-2 10-7 7-11 2-5-5 2-11 6-6 11-1z m32-14q1 6-8 9-8 2-10-4t7-9q8-3 11 4z m35-3q0 7-10 6-9 0-9-6 0-7 10-6 9 0 9 6z m32 5q-1 7-10 5-9-1-8-8t10-4 8 7z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="history" unicode="&#xf1da;" d="M857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z m-357 161v-250q0-8-5-13t-13-5h-178q-8 0-13 5t-5 13v35q0 8 5 13t13 5h125v197q0 8 5 13t12 5h36q8 0 13-5t5-13z" horiz-adv-x="857.1" />
+
+<glyph glyph-name="binoculars" unicode="&#xf1e5;" d="M393 671v-428q0-15-11-25t-25-11v-321q0-15-10-25t-26-11h-285q-15 0-25 11t-11 25v285l139 488q4 12 17 12h237z m178 0v-392h-142v392h142z m429-500v-285q0-15-11-25t-25-11h-285q-15 0-25 11t-11 25v321q-15 0-25 11t-11 25v428h237q13 0 17-12z m-589 661v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z m375 0v-125h-197v125q0 8 5 13t13 5h161q8 0 13-5t5-13z" horiz-adv-x="1000" />
+</font>
+</defs>
+</svg> \ No newline at end of file
diff --git a/public/font/ifont.ttf b/public/font/ifont.ttf
new file mode 100644
index 0000000..8dd6019
--- /dev/null
+++ b/public/font/ifont.ttf
Binary files differ
diff --git a/public/font/ifont.woff b/public/font/ifont.woff
new file mode 100644
index 0000000..2efa0ae
--- /dev/null
+++ b/public/font/ifont.woff
Binary files differ
diff --git a/public/font/ifont.woff2 b/public/font/ifont.woff2
new file mode 100644
index 0000000..25ca736
--- /dev/null
+++ b/public/font/ifont.woff2
Binary files differ
diff --git a/public/img/favicon.png b/public/img/favicon.png
new file mode 100644
index 0000000..d5c0044
--- /dev/null
+++ b/public/img/favicon.png
Binary files differ
diff --git a/public/img/icinga-loader-light.gif b/public/img/icinga-loader-light.gif
new file mode 100644
index 0000000..04ac07f
--- /dev/null
+++ b/public/img/icinga-loader-light.gif
Binary files differ
diff --git a/public/img/icinga-loader.gif b/public/img/icinga-loader.gif
new file mode 100644
index 0000000..727f4b4
--- /dev/null
+++ b/public/img/icinga-loader.gif
Binary files differ
diff --git a/public/img/icinga-logo-big-dark.png b/public/img/icinga-logo-big-dark.png
new file mode 100644
index 0000000..c75d194
--- /dev/null
+++ b/public/img/icinga-logo-big-dark.png
Binary files differ
diff --git a/public/img/icinga-logo-big-dark.svg b/public/img/icinga-logo-big-dark.svg
new file mode 100644
index 0000000..ed45b8b
--- /dev/null
+++ b/public/img/icinga-logo-big-dark.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 286 99" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
+ <rect id="icinga-logo-big-dark" x="0" y="0" width="286" height="99" style="fill:none;"/>
+ <clipPath id="_clip1">
+ <rect id="icinga-logo-big-dark1" serif:id="icinga-logo-big-dark" x="0" y="0" width="286" height="99"/>
+ </clipPath>
+ <g clip-path="url(#_clip1)">
+ <g>
+ <path id="Mask" d="M78.018,47.989C77.199,48.807 76.791,49.804 76.791,50.979L76.791,93.158C76.791,94.336 77.213,95.332 78.057,96.15C78.901,96.968 79.884,97.377 81.01,97.377C82.186,97.377 83.182,96.968 84,96.15C84.818,95.332 85.227,94.336 85.227,93.158L85.227,50.979C85.227,49.804 84.806,48.807 83.962,47.989C83.118,47.171 82.133,46.762 81.01,46.762C79.833,46.762 78.835,47.171 78.018,47.989ZM102.215,59.76C102.751,58.508 103.491,57.421 104.439,56.501C105.384,55.58 106.484,54.853 107.736,54.316C108.987,53.779 110.33,53.51 111.762,53.51C113.96,53.51 115.916,54.123 117.629,55.351C119.341,56.577 120.582,58.138 121.348,60.03C122.165,61.716 123.444,62.56 125.183,62.56C126.512,62.56 127.534,62.125 128.25,61.257C128.966,60.387 129.323,59.442 129.323,58.418C129.323,58.112 129.286,57.755 129.209,57.345C129.132,56.937 128.991,56.553 128.787,56.195C128.071,54.559 127.139,53.063 125.987,51.708C124.838,50.355 123.521,49.178 122.039,48.18C120.555,47.183 118.944,46.417 117.207,45.88C115.469,45.343 113.653,45.074 111.762,45.074C109.155,45.074 106.713,45.561 104.439,46.531C102.162,47.503 100.183,48.832 98.494,50.52C96.808,52.206 95.478,54.189 94.507,56.462C93.535,58.739 93.05,61.155 93.05,63.709L93.05,80.352C93.05,82.909 93.548,85.312 94.545,87.56C95.543,89.811 96.885,91.779 98.571,93.465C100.259,95.153 102.238,96.495 104.515,97.492C106.789,98.489 109.205,98.987 111.762,98.987C113.756,98.987 115.647,98.706 117.438,98.143C119.226,97.58 120.861,96.777 122.345,95.728C123.828,94.68 125.144,93.442 126.295,92.009C127.445,90.578 128.353,88.991 129.018,87.253C129.221,86.794 129.323,86.257 129.323,85.643C129.323,84.621 128.953,83.676 128.211,82.806C127.47,81.937 126.46,81.502 125.183,81.502C124.517,81.502 123.814,81.732 123.074,82.192C122.332,82.653 121.782,83.24 121.426,83.955C120.607,85.849 119.341,87.42 117.629,88.672C115.916,89.926 113.96,90.551 111.762,90.551C110.382,90.551 109.066,90.282 107.814,89.746C106.559,89.209 105.461,88.481 104.515,87.56C103.568,86.641 102.815,85.555 102.253,84.301C101.69,83.049 101.41,81.732 101.41,80.352L101.41,63.709C101.41,62.329 101.678,61.014 102.215,59.76M141.672,46.762C140.494,46.762 139.496,47.171 138.68,47.989C137.861,48.807 137.453,49.804 137.453,50.979L137.453,93.158C137.453,94.336 137.874,95.332 138.718,96.15C139.562,96.968 140.546,97.377 141.672,97.377C142.846,97.377 143.843,96.968 144.661,96.15C145.479,95.332 145.888,94.336 145.888,93.158L145.888,50.979C145.888,49.804 145.467,48.807 144.623,47.989C143.779,47.171 142.795,46.762 141.672,46.762M185.536,50.673C183.849,48.986 181.867,47.643 179.593,46.646C177.317,45.65 174.901,45.151 172.346,45.151C169.788,45.151 167.373,45.65 165.099,46.646C162.822,47.643 160.842,48.986 159.154,50.673C157.468,52.36 156.138,54.328 155.166,56.577C154.195,58.827 153.71,61.231 153.71,63.787L153.71,93.158C153.71,94.336 154.119,95.332 154.937,96.15C155.754,96.968 156.752,97.377 157.927,97.377C159.103,97.377 160.087,96.968 160.881,96.15C161.672,95.332 162.068,94.336 162.068,93.158L162.068,63.787C162.068,62.405 162.338,61.09 162.875,59.837C163.412,58.585 164.14,57.497 165.06,56.577C165.98,55.657 167.066,54.928 168.319,54.392C169.571,53.855 170.913,53.587 172.346,53.587C173.776,53.587 175.118,53.855 176.372,54.392C177.624,54.928 178.723,55.657 179.67,56.577C180.615,57.497 181.356,58.585 181.893,59.837C182.431,61.09 182.698,62.405 182.698,63.787L182.698,93.158C182.698,94.336 183.121,95.332 183.965,96.15C184.807,96.968 185.792,97.377 186.917,97.377C188.092,97.377 189.076,96.968 189.869,96.15C190.661,95.332 191.057,94.336 191.057,93.158L191.057,63.787C191.057,61.231 190.559,58.827 189.562,56.577C188.565,54.328 187.223,52.36 185.536,50.673M232.623,69.922L224.264,69.922C223.089,69.922 222.103,70.332 221.311,71.149C220.519,71.968 220.123,72.939 220.123,74.064C220.123,75.239 220.519,76.237 221.311,77.054C222.103,77.872 223.089,78.281 224.264,78.281L228.405,78.281L228.405,80.352C228.405,81.732 228.137,83.049 227.601,84.301C227.063,85.555 226.335,86.641 225.415,87.56C224.494,88.481 223.408,89.209 222.156,89.746C220.901,90.282 219.56,90.551 218.129,90.551C216.749,90.551 215.432,90.282 214.18,89.746C212.926,89.209 211.827,88.481 210.882,87.56C209.936,86.641 209.195,85.555 208.658,84.301C208.121,83.049 207.852,81.732 207.852,80.352L207.852,63.787C207.852,62.405 208.121,61.09 208.658,59.837C209.195,58.585 209.924,57.497 210.844,56.577C211.765,55.657 212.85,54.928 214.103,54.392C215.355,53.855 216.697,53.587 218.129,53.587C220.326,53.587 222.282,54.201 223.996,55.428C225.709,56.654 226.974,58.24 227.792,60.182C228.148,61.001 228.685,61.614 229.403,62.023C230.118,62.433 230.834,62.637 231.55,62.637C232.879,62.637 233.901,62.202 234.617,61.332C235.333,60.464 235.69,59.519 235.69,58.496C235.69,58.189 235.678,57.894 235.652,57.613C235.627,57.332 235.563,57.065 235.462,56.808C234.745,55.07 233.812,53.485 232.662,52.054C231.511,50.623 230.181,49.396 228.674,48.372C227.165,47.351 225.517,46.557 223.727,45.994C221.937,45.433 220.071,45.151 218.129,45.151C215.572,45.151 213.156,45.637 210.882,46.608C208.606,47.579 206.625,48.908 204.937,50.596C203.252,52.283 201.909,54.265 200.913,56.54C199.915,58.815 199.416,61.231 199.416,63.787L199.416,80.352C199.416,82.909 199.915,85.312 200.913,87.56C201.909,89.811 203.252,91.778 204.937,93.465C206.625,95.152 208.606,96.495 210.882,97.492C213.156,98.488 215.572,98.986 218.129,98.986C220.685,98.986 223.102,98.488 225.376,97.492C227.65,96.495 229.633,95.152 231.32,93.465C233.008,91.778 234.349,89.811 235.346,87.56C236.343,85.312 236.842,82.909 236.842,80.352L236.842,74.064C236.842,72.888 236.432,71.903 235.614,71.11C234.796,70.318 233.8,69.922 232.623,69.922" style="fill:rgb(51,41,49);fill-rule:nonzero;"/>
+ <path d="M257.318,75.366C257.879,74.037 258.416,72.786 258.928,71.608C259.438,70.433 259.95,69.283 260.463,68.158C260.972,67.034 261.47,65.895 261.957,64.744C262.443,63.595 262.966,62.405 263.53,61.178L269.666,75.366L257.318,75.366ZM285.616,91.701L267.364,48.986C266.954,48.065 266.214,47.35 265.14,46.838C264.884,46.684 264.615,46.583 264.335,46.531C264.053,46.48 263.784,46.454 263.53,46.454C261.791,46.454 260.512,47.298 259.694,48.986L241.443,91.701C241.339,91.907 241.263,92.162 241.213,92.469C241.161,92.775 241.136,93.056 241.136,93.312C241.136,94.284 241.505,95.218 242.248,96.111C242.988,97.007 243.999,97.453 245.278,97.453C246.094,97.453 246.836,97.249 247.502,96.84C248.165,96.43 248.702,95.792 249.111,94.923C249.879,93.031 250.658,91.179 251.451,89.362C252.243,87.548 253.047,85.694 253.866,83.803L273.27,83.803L278.024,94.923C278.688,96.61 279.966,97.453 281.859,97.453C283.135,97.453 284.145,97.007 284.887,96.111C285.628,95.218 286,94.284 286,93.312C286,92.851 285.871,92.315 285.616,91.701ZM80.591,27.652C77.112,27.915 74.507,30.948 74.767,34.423C74.807,34.951 74.917,35.455 75.076,35.935L49.432,42.259C48.984,39.778 47.922,37.37 46.218,35.278C44.84,33.588 43.176,32.272 41.357,31.329L48.881,15.691C51.373,16.477 54.201,16.036 56.379,14.265C59.818,11.459 60.332,6.397 57.531,2.959C54.726,-0.48 49.662,-0.996 46.224,1.805C42.785,4.608 42.266,9.671 45.073,13.111C45.834,14.047 46.767,14.756 47.785,15.252L40.289,30.83C35.188,28.666 29.089,29.321 24.498,33.062C23.996,33.471 23.539,33.913 23.103,34.369L14.839,27.604C15.509,26.342 15.596,24.794 14.923,23.414C13.805,21.128 11.043,20.18 8.758,21.3C6.472,22.422 5.524,25.181 6.642,27.465C7.762,29.751 10.521,30.701 12.809,29.582C13.334,29.324 13.78,28.971 14.154,28.565L22.312,35.243C17.747,40.802 17.563,48.986 22.29,54.774C23.395,56.131 24.687,57.235 26.091,58.111L16.657,71.637C14.711,70.169 12.246,69.366 9.623,69.565C3.893,69.995 -0.402,74.987 0.03,80.712C0.459,86.443 5.452,90.736 11.181,90.305C16.909,89.875 21.201,84.881 20.771,79.154C20.57,76.476 19.364,74.121 17.56,72.405L27.113,58.707C31.657,61.075 37.195,61.055 41.792,58.478L47.17,67.462C46.447,67.974 45.932,68.769 45.823,69.717C45.616,71.493 46.888,73.099 48.665,73.305C50.439,73.507 52.043,72.237 52.251,70.46C52.455,68.687 51.185,67.081 49.408,66.876C49.005,66.829 48.614,66.868 48.244,66.962L42.804,57.874C43.213,57.601 43.615,57.31 44.005,56.993C48.186,53.582 50.109,48.429 49.595,43.434L75.559,37.031C76.722,39.104 79.009,40.435 81.536,40.245C85.014,39.987 87.621,36.954 87.359,33.478C87.097,30 84.066,27.393 80.591,27.652" style="fill:rgb(51,41,49);fill-rule:nonzero;"/>
+ </g>
+ </g>
+</svg>
diff --git a/public/img/icinga-logo-big.png b/public/img/icinga-logo-big.png
new file mode 100644
index 0000000..90f64e4
--- /dev/null
+++ b/public/img/icinga-logo-big.png
Binary files differ
diff --git a/public/img/icinga-logo-big.svg b/public/img/icinga-logo-big.svg
new file mode 100644
index 0000000..740cbc8
--- /dev/null
+++ b/public/img/icinga-logo-big.svg
@@ -0,0 +1 @@
+<svg width="286" height="99" viewBox="0 0 286 99" xmlns="http://www.w3.org/2000/svg"><title>icinga-logo-big</title><g fill="#FEFEFE"><path d="M78.018 47.989c-.819.818-1.227 1.815-1.227 2.99v42.179c0 1.178.422 2.174 1.266 2.992.844.818 1.827 1.227 2.953 1.227 1.176 0 2.172-.409 2.99-1.227.818-.818 1.227-1.814 1.227-2.992v-42.179c0-1.175-.421-2.172-1.265-2.99-.844-.818-1.829-1.227-2.952-1.227-1.177 0-2.175.409-2.992 1.227zM102.215 59.76c.536-1.252 1.276-2.339 2.224-3.259.945-.921 2.045-1.648 3.297-2.185 1.251-.537 2.594-.806 4.026-.806 2.198 0 4.154.613 5.867 1.841 1.712 1.226 2.953 2.787 3.719 4.679.817 1.686 2.096 2.53 3.835 2.53 1.329 0 2.351-.435 3.067-1.303.716-.87 1.073-1.815 1.073-2.839 0-.306-.037-.663-.114-1.073-.077-.408-.218-.792-.422-1.15-.716-1.636-1.648-3.132-2.8-4.487-1.149-1.353-2.466-2.53-3.948-3.528-1.484-.997-3.095-1.763-4.832-2.3-1.738-.537-3.554-.806-5.445-.806-2.607 0-5.049.487-7.323 1.457-2.277.972-4.256 2.301-5.945 3.989-1.686 1.686-3.016 3.669-3.987 5.942-.972 2.277-1.457 4.693-1.457 7.247v16.643c0 2.557.498 4.96 1.495 7.208.998 2.251 2.34 4.219 4.026 5.905 1.688 1.688 3.667 3.03 5.944 4.027 2.274.997 4.69 1.495 7.247 1.495 1.994 0 3.885-.281 5.676-.844 1.788-.563 3.423-1.366 4.907-2.415 1.483-1.048 2.799-2.286 3.95-3.719 1.15-1.431 2.058-3.018 2.723-4.756.203-.459.305-.996.305-1.61 0-1.022-.37-1.967-1.112-2.837-.741-.869-1.751-1.304-3.028-1.304-.666 0-1.369.23-2.109.69-.742.461-1.292 1.048-1.648 1.763-.819 1.894-2.085 3.465-3.797 4.717-1.713 1.254-3.669 1.879-5.867 1.879-1.38 0-2.696-.269-3.948-.805-1.255-.537-2.353-1.265-3.299-2.186-.947-.919-1.7-2.005-2.262-3.259-.563-1.252-.843-2.569-.843-3.949v-16.643c0-1.38.268-2.695.805-3.949M141.672 46.762c-1.178 0-2.176.409-2.992 1.227-.819.818-1.227 1.815-1.227 2.99v42.179c0 1.178.421 2.174 1.265 2.992.844.818 1.828 1.227 2.954 1.227 1.174 0 2.171-.409 2.989-1.227.818-.818 1.227-1.814 1.227-2.992v-42.179c0-1.175-.421-2.172-1.265-2.99-.844-.818-1.828-1.227-2.951-1.227M185.536 50.673c-1.687-1.687-3.669-3.03-5.943-4.027-2.276-.996-4.692-1.495-7.247-1.495-2.558 0-4.973.499-7.247 1.495-2.277.997-4.257 2.34-5.945 4.027-1.686 1.687-3.016 3.655-3.988 5.904-.971 2.25-1.456 4.654-1.456 7.21v29.371c0 1.178.409 2.174 1.227 2.992.817.818 1.815 1.227 2.99 1.227 1.176 0 2.16-.409 2.954-1.227.791-.818 1.187-1.814 1.187-2.992v-29.371c0-1.382.27-2.697.807-3.95.537-1.252 1.265-2.34 2.185-3.26.92-.92 2.006-1.649 3.259-2.185 1.252-.537 2.594-.805 4.027-.805 1.43 0 2.772.268 4.026.805 1.252.536 2.351 1.265 3.298 2.185.945.92 1.686 2.008 2.223 3.26.538 1.253.805 2.568.805 3.95v29.371c0 1.178.423 2.174 1.267 2.992.842.818 1.827 1.227 2.952 1.227 1.175 0 2.159-.409 2.952-1.227.792-.818 1.188-1.814 1.188-2.992v-29.371c0-2.556-.498-4.96-1.495-7.21-.997-2.249-2.339-4.217-4.026-5.904M232.623 69.922h-8.359c-1.175 0-2.161.41-2.953 1.227-.792.819-1.188 1.79-1.188 2.915 0 1.175.396 2.173 1.188 2.99.792.818 1.778 1.227 2.953 1.227h4.141v2.071c0 1.38-.268 2.697-.804 3.949-.538 1.254-1.266 2.34-2.186 3.259-.921.921-2.007 1.649-3.259 2.186-1.255.536-2.596.805-4.027.805-1.38 0-2.697-.269-3.949-.805-1.254-.537-2.353-1.265-3.298-2.186-.946-.919-1.687-2.005-2.224-3.259-.537-1.252-.806-2.569-.806-3.949v-16.565c0-1.382.269-2.697.806-3.95.537-1.252 1.266-2.34 2.186-3.26.921-.92 2.006-1.649 3.259-2.185 1.252-.537 2.594-.805 4.026-.805 2.197 0 4.153.614 5.867 1.841 1.713 1.226 2.978 2.812 3.796 4.754.356.819.893 1.432 1.611 1.841.715.41 1.431.614 2.147.614 1.329 0 2.351-.435 3.067-1.305.716-.868 1.073-1.813 1.073-2.836 0-.307-.012-.602-.038-.883-.025-.281-.089-.548-.19-.805-.717-1.738-1.65-3.323-2.8-4.754-1.151-1.431-2.481-2.658-3.988-3.682-1.509-1.021-3.157-1.815-4.947-2.378-1.79-.561-3.656-.843-5.598-.843-2.557 0-4.973.486-7.247 1.457-2.276.971-4.257 2.3-5.945 3.988-1.685 1.687-3.028 3.669-4.024 5.944-.998 2.275-1.497 4.691-1.497 7.247v16.565c0 2.557.499 4.96 1.497 7.208.996 2.251 2.339 4.218 4.024 5.905 1.688 1.687 3.669 3.03 5.945 4.027 2.274.996 4.69 1.494 7.247 1.494 2.556 0 4.973-.498 7.247-1.494 2.274-.997 4.257-2.34 5.944-4.027 1.688-1.687 3.029-3.654 4.026-5.905.997-2.248 1.496-4.651 1.496-7.208v-6.288c0-1.176-.41-2.161-1.228-2.954-.818-.792-1.814-1.188-2.991-1.188" id="Mask"/><path d="M257.318 75.366c.561-1.329 1.098-2.58 1.61-3.758.51-1.175 1.022-2.325 1.535-3.45.509-1.124 1.007-2.263 1.494-3.414.486-1.149 1.009-2.339 1.573-3.566l6.136 14.188h-12.348zm28.298 16.335l-18.252-42.715c-.41-.921-1.15-1.636-2.224-2.148-.256-.154-.525-.255-.805-.307-.282-.051-.551-.077-.805-.077-1.739 0-3.018.844-3.836 2.532l-18.251 42.715c-.104.206-.18.461-.23.768-.052.306-.077.587-.077.843 0 .972.369 1.906 1.112 2.799.74.896 1.751 1.342 3.03 1.342.816 0 1.558-.204 2.224-.613.663-.41 1.2-1.048 1.609-1.917.768-1.892 1.547-3.744 2.34-5.561.792-1.814 1.596-3.668 2.415-5.559h19.404l4.754 11.12c.664 1.687 1.942 2.53 3.835 2.53 1.276 0 2.286-.446 3.028-1.342.741-.893 1.113-1.827 1.113-2.799 0-.461-.129-.997-.384-1.611zM80.591 27.652c-3.479.263-6.084 3.296-5.824 6.771.04.528.15 1.032.309 1.512l-25.644 6.324c-.448-2.481-1.51-4.889-3.214-6.981-1.378-1.69-3.042-3.006-4.861-3.949l7.524-15.638c2.492.786 5.32.345 7.498-1.426 3.439-2.806 3.953-7.868 1.152-11.306-2.805-3.439-7.869-3.955-11.307-1.154-3.439 2.803-3.958 7.866-1.151 11.306.761.936 1.694 1.645 2.712 2.141l-7.496 15.578c-5.101-2.164-11.2-1.509-15.791 2.232-.502.409-.959.851-1.395 1.307l-8.264-6.765c.67-1.262.757-2.81.084-4.19-1.118-2.286-3.88-3.234-6.165-2.114-2.286 1.122-3.234 3.881-2.116 6.165 1.12 2.286 3.879 3.236 6.167 2.117.525-.258.971-.611 1.345-1.017l8.158 6.678c-4.565 5.559-4.749 13.743-.022 19.531 1.105 1.357 2.397 2.461 3.801 3.337l-9.434 13.526c-1.946-1.468-4.411-2.271-7.034-2.072-5.73.43-10.025 5.422-9.593 11.147.429 5.731 5.422 10.024 11.151 9.593 5.728-.43 10.02-5.424 9.59-11.151-.201-2.678-1.407-5.033-3.211-6.749l9.553-13.698c4.544 2.368 10.082 2.348 14.679-.229l5.378 8.984c-.723.512-1.238 1.307-1.347 2.255-.207 1.776 1.065 3.382 2.842 3.588 1.774.202 3.378-1.068 3.586-2.845.204-1.773-1.066-3.379-2.843-3.584-.403-.047-.794-.008-1.164.086l-5.44-9.088c.409-.273.811-.564 1.201-.881 4.181-3.411 6.104-8.564 5.59-13.559l25.964-6.403c1.163 2.073 3.45 3.404 5.977 3.214 3.478-.258 6.085-3.291 5.823-6.767-.262-3.478-3.293-6.085-6.768-5.826"/></g></svg>
diff --git a/public/img/icinga-logo-compact-inverted.svg b/public/img/icinga-logo-compact-inverted.svg
new file mode 100644
index 0000000..e71c632
--- /dev/null
+++ b/public/img/icinga-logo-compact-inverted.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 41 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
+ <g>
+ <path d="M17.444,16.237L9.477,27.661M17.444,16.237L33.957,12.164M17.757,16.572L22.381,24.296M15.795,14.324L9.01,8.77M18.425,13.096L22.419,4.799" style="fill:none;fill-rule:nonzero;stroke:rgb(126,129,130);stroke-width:0.75px;"/>
+ <path d="M5.234,28.221C5.083,26.219 6.585,24.474 8.588,24.324C10.591,24.171 12.337,25.675 12.486,27.677C12.636,29.679 11.136,31.425 9.133,31.575C7.13,31.726 5.384,30.224 5.234,28.221" style="fill:rgb(126,129,130);fill-rule:nonzero;"/>
+ <path d="M21.246,24.377C21.317,23.756 21.878,23.312 22.5,23.383C23.121,23.455 23.565,24.017 23.494,24.637C23.42,25.258 22.86,25.702 22.24,25.631C21.619,25.56 21.174,24.998 21.246,24.377M7.546,9.604C7.155,8.805 7.486,7.839 8.286,7.449C9.085,7.057 10.05,7.389 10.442,8.188C10.832,8.987 10.502,9.953 9.702,10.343C8.902,10.734 7.938,10.402 7.546,9.604M31.367,12.036C31.275,10.821 32.186,9.762 33.403,9.669C34.618,9.578 35.678,10.489 35.77,11.705C35.861,12.921 34.95,13.98 33.733,14.072C32.518,14.163 31.458,13.252 31.367,12.036M21.386,0.631C22.588,-0.348 24.359,-0.168 25.34,1.034C26.32,2.236 26.139,4.008 24.937,4.988C23.734,5.967 21.963,5.786 20.984,4.584C20.002,3.381 20.184,1.611 21.386,0.631M13.79,11.561C16.099,9.679 19.499,10.025 21.384,12.334C23.264,14.644 22.918,18.043 20.61,19.928C18.298,21.808 14.899,21.463 13.018,19.152C11.132,16.843 11.481,13.442 13.79,11.561" style="fill:rgb(126,129,130);fill-rule:nonzero;"/>
+ </g>
+</svg>
diff --git a/public/img/icinga-logo-compact.svg b/public/img/icinga-logo-compact.svg
new file mode 100644
index 0000000..8b6a531
--- /dev/null
+++ b/public/img/icinga-logo-compact.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="41px" height="35px" viewBox="0 82.75 41 35" enable-background="new 0 82.75 41 35" xml:space="preserve">
+<g>
+ <path fill="none" stroke="#FFFFFF" stroke-width="0.75" d="M17.444,98.987l-7.967,11.424 M17.444,98.987l16.513-4.073
+ M17.757,99.322l4.624,7.724 M15.795,97.074L9.01,91.52 M18.425,95.846l3.994-8.297"/>
+ <defs>
+ <filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="5.224" y="107.062" width="7.272" height="7.272">
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
+ </filter>
+ </defs>
+ <mask maskUnits="userSpaceOnUse" x="5.224" y="107.062" width="7.272" height="7.272" id="f_1_">
+ <g filter="url(#Adobe_OpacityMaskFilter)">
+ <polygon id="e_1_" fill="#FFFFFF" points="5.224,107.062 5.224,114.335 12.497,114.335 12.497,107.062 "/>
+ </g>
+ </mask>
+ <path mask="url(#f_1_)" fill="#FFFFFF" d="M5.234,110.971c-0.151-2.002,1.351-3.747,3.354-3.897
+ c2.003-0.153,3.749,1.351,3.898,3.353c0.15,2.002-1.35,3.748-3.353,3.898C7.13,114.476,5.384,112.974,5.234,110.971"/>
+ <path fill="#FFFFFF" d="M21.246,107.127c0.071-0.621,0.632-1.065,1.254-0.994c0.621,0.072,1.065,0.634,0.994,1.254
+ c-0.074,0.621-0.634,1.065-1.254,0.994C21.619,108.31,21.174,107.748,21.246,107.127 M7.546,92.354
+ c-0.391-0.799-0.06-1.765,0.74-2.155c0.799-0.392,1.764-0.06,2.156,0.739c0.39,0.799,0.06,1.765-0.74,2.155
+ C8.902,93.484,7.938,93.152,7.546,92.354 M31.367,94.786c-0.092-1.215,0.819-2.274,2.036-2.367c1.215-0.091,2.275,0.82,2.367,2.036
+ c0.091,1.216-0.82,2.275-2.037,2.367C32.518,96.913,31.458,96.002,31.367,94.786 M21.386,83.381
+ c1.202-0.979,2.973-0.799,3.954,0.403c0.98,1.202,0.799,2.974-0.403,3.954c-1.203,0.979-2.974,0.798-3.953-0.404
+ C20.002,86.131,20.184,84.361,21.386,83.381 M13.79,94.311c2.309-1.882,5.709-1.536,7.594,0.773c1.88,2.31,1.534,5.709-0.774,7.594
+ c-2.312,1.88-5.711,1.535-7.592-0.776C11.132,99.593,11.481,96.192,13.79,94.311"/>
+</g>
+</svg>
diff --git a/public/img/icinga-logo-dark.svg b/public/img/icinga-logo-dark.svg
new file mode 100644
index 0000000..44be390
--- /dev/null
+++ b/public/img/icinga-logo-dark.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 101 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
+ <g transform="matrix(1,0,0,1,-327,0)">
+ <g id="icinga-logo-dark" transform="matrix(1,0,0,1,327.107,0)">
+ <rect x="0" y="0" width="100" height="35" style="fill:none;"/>
+ <clipPath id="_clip1">
+ <rect x="0" y="0" width="100" height="35"/>
+ </clipPath>
+ <g clip-path="url(#_clip1)">
+ <g>
+ <path d="M26.85,32.573C26.85,32.975 27.011,33.324 27.306,33.619C27.6,33.914 27.922,34.048 28.325,34.048C28.727,34.048 29.075,33.914 29.371,33.619C29.665,33.324 29.8,32.975 29.8,32.573L29.8,17.825C29.8,17.423 29.665,17.074 29.371,16.779C29.075,16.484 28.727,16.35 28.325,16.35C27.922,16.35 27.574,16.484 27.279,16.779C26.984,17.074 26.85,17.423 26.85,17.825L26.85,32.573Z" style="fill:rgb(51,41,49);"/>
+ <g transform="matrix(1,0,0,1,32,15.61)">
+ <path d="M0.535,12.484C0.535,13.369 0.723,14.227 1.072,15.004C1.42,15.782 1.876,16.479 2.466,17.069C3.056,17.659 3.753,18.115 4.557,18.463C5.335,18.812 6.193,19 7.077,19C7.775,19 8.445,18.892 9.062,18.705C9.679,18.517 10.268,18.222 10.778,17.847C11.288,17.498 11.77,17.069 12.173,16.56C12.575,16.05 12.87,15.514 13.111,14.897C13.191,14.736 13.218,14.548 13.218,14.334C13.218,13.985 13.084,13.637 12.843,13.342C12.575,13.047 12.226,12.886 11.77,12.886C11.529,12.886 11.288,12.966 11.046,13.128C10.778,13.288 10.59,13.503 10.456,13.744C10.161,14.414 9.733,14.951 9.143,15.38C8.526,15.836 7.855,16.05 7.077,16.05C6.595,16.05 6.139,15.942 5.71,15.755C5.254,15.567 4.879,15.326 4.557,15.004C4.209,14.683 3.941,14.307 3.753,13.851C3.565,13.422 3.458,12.966 3.458,12.484L3.458,6.665C3.458,6.182 3.565,5.727 3.753,5.271C3.941,4.842 4.182,4.466 4.531,4.144C4.852,3.822 5.227,3.554 5.683,3.367C6.112,3.179 6.568,3.099 7.077,3.099C7.855,3.099 8.526,3.314 9.143,3.742C9.733,4.171 10.161,4.707 10.43,5.378C10.724,5.968 11.154,6.263 11.77,6.263C12.226,6.263 12.601,6.102 12.843,5.807C13.084,5.512 13.218,5.163 13.218,4.815C13.218,4.707 13.218,4.573 13.191,4.439C13.165,4.305 13.111,4.171 13.031,4.037C12.789,3.474 12.468,2.938 12.065,2.455C11.663,1.999 11.181,1.57 10.671,1.221C10.161,0.873 9.598,0.605 8.982,0.417C8.365,0.23 7.748,0.149 7.077,0.149C6.166,0.149 5.308,0.31 4.531,0.659C3.726,1.007 3.029,1.463 2.439,2.053C1.849,2.643 1.393,3.34 1.045,4.117C0.696,4.922 0.535,5.78 0.535,6.665L0.535,12.484Z" style="fill:rgb(51,41,49);"/>
+ </g>
+ <path d="M48.06,32.573C48.06,32.975 48.221,33.324 48.516,33.619C48.811,33.914 49.133,34.048 49.535,34.048C49.937,34.048 50.286,33.914 50.581,33.619C50.875,33.324 51.01,32.975 51.01,32.573L51.01,17.825C51.01,17.423 50.875,17.074 50.581,16.779C50.286,16.484 49.937,16.35 49.535,16.35C49.133,16.35 48.784,16.484 48.489,16.779C48.194,17.074 48.06,17.423 48.06,17.825L48.06,32.573ZM53.745,32.573C53.745,32.975 53.879,33.324 54.174,33.619C54.469,33.914 54.818,34.048 55.22,34.048C55.622,34.048 55.971,33.914 56.266,33.619C56.534,33.324 56.668,32.975 56.668,32.573L56.668,22.303C56.668,21.82 56.775,21.365 56.963,20.909C57.151,20.48 57.392,20.105 57.714,19.782C58.036,19.461 58.411,19.193 58.867,19.005C59.296,18.817 59.751,18.737 60.261,18.737C60.77,18.737 61.226,18.817 61.682,19.005C62.111,19.193 62.487,19.461 62.835,19.782C63.157,20.105 63.425,20.48 63.613,20.909C63.8,21.365 63.881,21.82 63.881,22.303L63.881,32.573C63.881,32.975 64.042,33.324 64.337,33.619C64.632,33.914 64.953,34.048 65.356,34.048C65.758,34.048 66.106,33.914 66.401,33.619C66.669,33.324 66.804,32.975 66.804,32.573L66.804,22.303C66.804,21.418 66.643,20.56 66.294,19.782C65.946,19.005 65.463,18.308 64.873,17.718C64.283,17.128 63.586,16.645 62.808,16.297C62.004,15.948 61.146,15.787 60.261,15.787C59.376,15.787 58.518,15.948 57.741,16.297C56.936,16.645 56.239,17.128 55.649,17.718C55.059,18.308 54.603,19.005 54.255,19.782C53.906,20.56 53.745,21.418 53.745,22.303L53.745,32.573Z" style="fill:rgb(51,41,49);"/>
+ <g transform="matrix(1,0,0,1,69,15.61)">
+ <path d="M0.726,12.484C0.726,13.369 0.913,14.227 1.262,15.004C1.611,15.782 2.067,16.479 2.657,17.069C3.247,17.659 3.944,18.115 4.748,18.463C5.525,18.812 6.384,19 7.268,19C8.154,19 9.012,18.812 9.816,18.463C10.594,18.115 11.291,17.659 11.881,17.069C12.471,16.479 12.954,15.782 13.302,15.004C13.65,14.227 13.811,13.369 13.811,12.484L13.811,10.285C13.811,9.883 13.677,9.534 13.382,9.239C13.087,8.971 12.739,8.837 12.337,8.837L9.414,8.837C9.012,8.837 8.663,8.971 8.395,9.266C8.1,9.561 7.966,9.883 7.966,10.285C7.966,10.687 8.1,11.036 8.395,11.331C8.663,11.626 9.012,11.76 9.414,11.76L10.862,11.76L10.862,12.484C10.862,12.966 10.781,13.422 10.594,13.851C10.406,14.307 10.138,14.683 9.816,15.004C9.495,15.326 9.119,15.567 8.69,15.755C8.234,15.942 7.778,16.05 7.268,16.05C6.786,16.05 6.33,15.942 5.901,15.755C5.445,15.567 5.07,15.326 4.748,15.004C4.4,14.683 4.158,14.307 3.971,13.851C3.783,13.422 3.676,12.966 3.676,12.484L3.676,6.692C3.676,6.209 3.783,5.754 3.971,5.297C4.158,4.868 4.4,4.493 4.722,4.171C5.043,3.849 5.418,3.581 5.874,3.394C6.303,3.206 6.759,3.126 7.268,3.126C8.046,3.126 8.717,3.34 9.333,3.769C9.923,4.198 10.352,4.761 10.647,5.431C10.781,5.727 10.969,5.941 11.21,6.075C11.452,6.209 11.72,6.29 11.961,6.29C12.417,6.29 12.792,6.129 13.034,5.834C13.275,5.539 13.409,5.19 13.409,4.842L13.409,4.52C13.382,4.439 13.355,4.332 13.329,4.252C13.087,3.635 12.766,3.099 12.364,2.589C11.961,2.079 11.479,1.651 10.969,1.302C10.433,0.954 9.843,0.659 9.226,0.471C8.609,0.283 7.939,0.176 7.268,0.176C6.384,0.176 5.525,0.337 4.748,0.686C3.944,1.034 3.247,1.489 2.657,2.079C2.067,2.67 1.611,3.367 1.262,4.144C0.913,4.949 0.726,5.807 0.726,6.692L0.726,12.484Z" style="fill:rgb(51,41,49);"/>
+ </g>
+ <path d="M94.289,26.352L89.972,26.352C90.159,25.896 90.347,25.44 90.535,25.038C90.723,24.636 90.883,24.234 91.071,23.831C91.259,23.429 91.419,23.027 91.607,22.624C91.768,22.223 91.956,21.82 92.143,21.391L94.289,26.352ZM84.421,32.063C84.394,32.144 84.367,32.225 84.34,32.331C84.313,32.438 84.313,32.546 84.313,32.626C84.313,32.975 84.448,33.297 84.716,33.592C84.957,33.914 85.306,34.075 85.762,34.075C86.057,34.075 86.298,33.994 86.539,33.86C86.781,33.726 86.968,33.485 87.102,33.19C87.371,32.519 87.639,31.876 87.934,31.232C88.202,30.615 88.47,29.972 88.765,29.302L95.549,29.302L97.212,33.19C97.453,33.78 97.882,34.075 98.552,34.075C99.008,34.075 99.357,33.914 99.625,33.592C99.866,33.297 100,32.975 100,32.626C100,32.466 99.947,32.278 99.866,32.063L93.484,17.127C93.351,16.806 93.082,16.565 92.706,16.377C92.626,16.323 92.519,16.297 92.439,16.27C92.331,16.243 92.224,16.243 92.143,16.243C91.527,16.243 91.098,16.538 90.803,17.127L84.421,32.063Z" style="fill:rgb(51,41,49);"/>
+ <path d="M12.22,16.237L4.253,27.661M12.22,16.237L28.733,12.164M12.533,16.572L17.157,24.296M10.571,14.325L3.786,8.77M13.201,13.096L17.195,4.798" style="fill:none;stroke:rgb(51,41,49);stroke-width:0.75px;"/>
+ <g transform="matrix(1,0,0,1,0,23.61)">
+ <path d="M0.01,4.61C-0.141,2.608 1.361,0.863 3.364,0.713C5.367,0.56 7.113,2.063 7.262,4.065C7.412,6.068 5.912,7.814 3.909,7.964C1.906,8.115 0.16,6.613 0.01,4.61" style="fill:rgb(51,41,49);"/>
+ </g>
+ <path d="M16.022,24.376C16.093,23.756 16.654,23.311 17.276,23.382C17.897,23.454 18.341,24.016 18.27,24.636C18.196,25.257 17.636,25.702 17.016,25.631C16.395,25.559 15.95,24.998 16.022,24.376M2.322,9.603C1.931,8.805 2.262,7.839 3.062,7.448C3.861,7.057 4.826,7.388 5.218,8.187C5.608,8.986 5.278,9.952 4.478,10.343C3.678,10.734 2.713,10.403 2.322,9.603M26.143,12.036C26.051,10.821 26.962,9.761 28.179,9.669C29.394,9.578 30.454,10.489 30.546,11.705C30.637,12.921 29.726,13.981 28.509,14.072C27.294,14.163 26.234,13.252 26.143,12.036M16.162,0.631C17.364,-0.349 19.135,-0.168 20.116,1.034C21.096,2.236 20.915,4.007 19.713,4.988C18.51,5.966 16.739,5.786 15.76,4.584C14.778,3.381 14.96,1.611 16.162,0.631M8.566,11.561C10.875,9.679 14.275,10.025 16.16,12.335C18.04,14.644 17.694,18.044 15.386,19.927C13.074,21.808 9.675,21.462 7.794,19.152C5.908,16.843 6.257,13.443 8.566,11.561" style="fill:rgb(51,41,49);"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/public/img/icinga-logo-inverted.svg b/public/img/icinga-logo-inverted.svg
new file mode 100644
index 0000000..ec918e3
--- /dev/null
+++ b/public/img/icinga-logo-inverted.svg
@@ -0,0 +1 @@
+<svg height="35" viewBox="0 0 100 35" width="100" xmlns="http://www.w3.org/2000/svg"><path d="m85.0767 33.7594c.671 0 1.288.081 1.905.268.616.188 1.179.456 1.689.804.51.349.992.778 1.394 1.234.403.483.724 1.019.966 1.582.08.134.134.268.16.402.027.134.027.268.027.376 0 .348-.134.697-.375.992-.242.295-.617.456-1.073.456-.616 0-1.046-.295-1.34-.885-.269-.671-.697-1.207-1.287-1.636-.617-.428-1.288-.643-2.066-.643-.509 0-.965.08-1.394.268-.456.187-.831.455-1.152.777-.349.322-.59.698-.778 1.127-.1566667.38-.2570833.7593056-.2862037 1.1546991l-.0087963.2393009v5.819c0 .482.107.938.295 1.367.188.456.456.832.804 1.153.322.322.697.563 1.153.751.429.187.885.295 1.367.295.778 0 1.449-.214 2.066-.67.59-.429 1.018-.966 1.313-1.636.134-.241.322-.456.59-.616.242-.162.483-.242.724-.242.456 0 .805.161 1.073.456.241.295.375.643.375.992 0 .1605-.0151875.306375-.0565312.4372031l-.0504688.1257969c-.241.617-.536 1.153-.938 1.663-.403.509-.885.938-1.395 1.287-.51.375-1.099.67-1.716.858-.617.187-1.287.295-1.985.295-.884 0-1.742-.188-2.52-.537-.804-.348-1.501-.804-2.091-1.394s-1.046-1.287-1.394-2.065c-.305375-.679875-.4874844-1.4217656-.5282402-2.1894961l-.0087598-.3305039v-5.819c0-.885.161-1.743.51-2.548.348-.777.804-1.474 1.394-2.064s1.287-1.046 2.092-1.394c.777-.349 1.635-.51 2.546-.51zm37.1914.027c.671 0 1.341.107 1.958.295s1.207.483 1.743.831c.51.349.992.777 1.395 1.287.402.51.723 1.046.965 1.663l.08.268v.322c0 .348-.134.697-.375.992-.242.295-.617.456-1.073.456-.241 0-.509-.081-.751-.215-.241-.134-.429-.348-.563-.644-.295-.67-.724-1.233-1.314-1.662-.616-.429-1.287-.643-2.065-.643-.509 0-.965.08-1.394.268-.456.187-.831.455-1.152.777-.322.322-.564.697-.751 1.126-.156667.3808333-.257083.7602778-.286204 1.1556944l-.008796.2393056v5.792c0 .482.107.938.295 1.367.187.456.429.832.777 1.153.322.322.697.563 1.153.751.429.187.885.295 1.367.295.51 0 .966-.108 1.422-.295.429-.188.805-.429 1.126-.751.322-.321.59-.697.778-1.153.155833-.3575.238056-.73375.261134-1.1281713l.006866-.2388287v-.724h-1.448c-.402 0-.751-.134-1.019-.429-.295-.295-.429-.644-.429-1.046s.134-.724.429-1.019c.2345-.258125.531016-.3929844.870789-.422666l.148211-.006334h2.923c.402 0 .75.134 1.045.402.258125.258125.392984.5575938.422666.8977363l.006334.1482637v2.199c0 .885-.161 1.743-.509 2.52-.348.778-.831 1.475-1.421 2.065s-1.287 1.046-2.065 1.394c-.804.349-1.662.537-2.548.537-.884 0-1.743-.188-2.52-.537-.804-.348-1.501-.804-2.091-1.394s-1.046-1.287-1.395-2.065c-.305375-.679875-.486719-1.4217656-.527283-2.1894961l-.008717-.3305039v-5.792c0-.885.187-1.743.536-2.548.349-.777.805-1.474 1.395-2.065.59-.59 1.287-1.045 2.091-1.393.777-.349 1.636-.51 2.52-.51zm15.8745.4566c.081 0 .188 0 .296.027.053333.018.118667.0355556.18.0612593l.087.0457407c.322286.1611429.565959.3612245.712758.6172478l.065242.1327522 6.382 14.936c.081.215.134.403.134.563 0 .349-.134.671-.375.966-.268.322-.617.483-1.073.483-.614167 0-1.025826-.2478819-1.275803-.7436458l-.064197-.1413542-1.663-3.888h-6.784c-.295.67-.563 1.313-.831 1.93-.295.644-.563 1.287-.832 1.958-.134.295-.321.536-.563.67-.241.134-.482.215-.777.215-.399 0-.716078-.1232656-.950564-.3697969l-.095436-.1132031c-.268-.295-.403-.617-.403-.966 0-.08 0-.188.027-.295l.027-.0985926.054-.1694074 6.382-14.936c.295-.589.724-.884 1.34-.884zm-63.8175.1067c.402 0 .75.134 1.046.429.25725.258125.3927656.5575938.422625.8977363l.006375.1482637v14.748c0 .402-.135.751-.429 1.046-.296.295-.644.429-1.046.429-.403 0-.725-.134-1.019-.429-.258125-.258125-.4136562-.5575937-.4485059-.8977363l-.0074941-.1482637v-14.748c0-.402.134-.751.429-1.046s.643-.429 1.046-.429zm21.21 0c.402 0 .751.134 1.046.429.25725.258125.3927656.5575938.422625.8977363l.006375.1482637v14.748c0 .402-.135.751-.429 1.046-.295.295-.644.429-1.046.429s-.724-.134-1.019-.429c-.258125-.258125-.4136562-.5575937-.4485059-.8977363l-.0074941-.1482637v-14.748c0-.402.134-.751.429-1.046s.644-.429 1.046-.429zm10.7255-.563c.885 0 1.743.161 2.547.51.778.348 1.475.831 2.065 1.421s1.073 1.287 1.421 2.064c.305375.68075.466813 1.42275.5024 2.1904941l.0076.3305059v10.27c0 .402-.135.751-.403 1.046-.295.295-.643.429-1.045.429-.403 0-.724-.134-1.019-.429-.258125-.258125-.413656-.5575937-.448506-.8977363l-.007494-.1482637v-10.27c0-.483-.081-.938-.268-1.394-.188-.429-.456-.804-.778-1.127-.348-.321-.724-.589-1.153-.777-.456-.188-.912-.268-1.421-.268-.51 0-.965.08-1.394.268-.456.188-.831.456-1.153.777-.322.323-.563.698-.751 1.127-.156667.38-.257083.7593056-.286204 1.1546991l-.008796.2393009v10.27c0 .402-.134.751-.402 1.046-.295.295-.644.429-1.046.429s-.751-.134-1.046-.429c-.258125-.258125-.3929844-.5575937-.422666-.8977363l-.006334-.1482637v-10.27c0-.885.161-1.743.51-2.521.348-.777.804-1.474 1.394-2.064s1.287-1.073 2.092-1.421c.777-.349 1.635-.51 2.52-.51zm-40.145-14.7524c.98 1.202.799 2.973-.403 3.954-.7135427.5800871-1.6269138.7527777-2.4563199.5464512l-2.6461727 5.4979481c.5779292.3227096 1.1058725.7577926 1.5497926 1.3018007.5578806.6851842.919745 1.4664392 1.0924916 2.2756424l8.9486237-2.2073659c-.0292334-.1184503-.0490052-.2409327-.0584153-.3666765-.092-1.215.819-2.275 2.036-2.367 1.215-.091 2.275.82 2.367 2.036.091 1.216-.82 2.276-2.037 2.367-.8211665.061503-1.5715316-.3346905-1.9997973-.9734027l-9.1509619 2.2572841c.1199705 1.6931832-.5575939 3.4169998-1.9719408 4.5715186-.0916538.0745282-.1850159.1455597-.279924.2131107l1.9393556 3.2387843c.0753599-.0066904.1525329-.005914.2306684.003005.621.072 1.065.634.994 1.254-.074.621-.634 1.066-1.254.995-.621-.072-1.066-.633-.994-1.255.0309663-.2704096.1551414-.5075304.3369336-.6830715l-1.8928981-3.1609715c-1.5260558.7939481-3.3251285.7992664-4.8352112.0718318l-3.3672948 4.8286847c.5634239.5877707.9336449 1.3674834.9987705 2.2425265.15 2.003-1.35 3.749-3.353 3.899-2.003.151-3.749-1.351-3.899-3.354-.151-2.002 1.351-3.747 3.354-3.897.8569941-.0654619 1.6669418.1722239 2.3248898.6224372l3.2886724-4.7148027c-.4356465-.2913976-.8358066-.6517899-1.1839622-1.0795345-1.6157836-1.9781784-1.5911265-4.7571267-.0989533-6.6962604l-2.8636784-2.3463302c-.1044197.0911892-.2224378.1702938-.3529683.2340906-.8.391-1.765.06-2.156-.74-.391-.798-.06-1.764.74-2.155.799-.391 1.764-.06 2.156.739.2078646.4258559.2111957.8991522.0478454 1.3075444l2.9271152 2.3991038c.1179203-.1155836.2421499-.22679.3726394-.3331482 1.5583374-1.2701564 3.6136103-1.5254897 5.3647177-.8471552l2.6265341-5.4562041c-.2974624-.1674645-.5690752-.392321-.7975518-.6728407-.982-1.203-.8-2.973.402-3.953s2.973-.799 3.954.403zm74.173 25.3177-2.146-4.961-.367037.8304444c-.05863.135-.115296.2688889-.168963.4025556l-1.072 2.414c-.094.201-.188.4155-.281875.63675l-.281125.67725z" fill="#7e8182" transform="translate(-46 -18)"/></svg> \ No newline at end of file
diff --git a/public/img/icinga-logo.png b/public/img/icinga-logo.png
new file mode 100644
index 0000000..670f45c
--- /dev/null
+++ b/public/img/icinga-logo.png
Binary files differ
diff --git a/public/img/icinga-logo.svg b/public/img/icinga-logo.svg
new file mode 100644
index 0000000..49a2ef5
--- /dev/null
+++ b/public/img/icinga-logo.svg
@@ -0,0 +1,32 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="35" viewBox="0 0 100 35" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <polygon id="a" points=".535 19 13.218 19 13.218 .149 .535 .149"/>
+ <polygon id="c" points=".726 19 13.811 19 13.811 .176 .726 .176 .726 19"/>
+ <polygon id="e" points="0 .703 0 7.975 7.273 7.975 7.273 .703 0 .703"/>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <path fill="#FFFFFF" d="M26.8501,32.5727 C26.8501,32.9747 27.0111,33.3237 27.3061,33.6187 C27.6001,33.9137 27.9221,34.0477 28.3251,34.0477 C28.7271,34.0477 29.0751,33.9137 29.3711,33.6187 C29.6651,33.3237 29.8001,32.9747 29.8001,32.5727 L29.8001,17.8247 C29.8001,17.4227 29.6651,17.0737 29.3711,16.7787 C29.0751,16.4837 28.7271,16.3497 28.3251,16.3497 C27.9221,16.3497 27.5741,16.4837 27.2791,16.7787 C26.9841,17.0737 26.8501,17.4227 26.8501,17.8247 L26.8501,32.5727 Z"/>
+ <g transform="translate(32 15.61)">
+ <mask id="b" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ <path fill="#FFFFFF" d="M0.5347,12.4844 C0.5347,13.3694 0.7227,14.2274 1.0717,15.0044 C1.4197,15.7824 1.8757,16.4794 2.4657,17.0694 C3.0557,17.6594 3.7527,18.1154 4.5567,18.4634 C5.3347,18.8124 6.1927,19.0004 7.0767,19.0004 C7.7747,19.0004 8.4447,18.8924 9.0617,18.7054 C9.6787,18.5174 10.2677,18.2224 10.7777,17.8474 C11.2877,17.4984 11.7697,17.0694 12.1727,16.5604 C12.5747,16.0504 12.8697,15.5144 13.1107,14.8974 C13.1907,14.7364 13.2177,14.5484 13.2177,14.3344 C13.2177,13.9854 13.0837,13.6374 12.8427,13.3424 C12.5747,13.0474 12.2257,12.8864 11.7697,12.8864 C11.5287,12.8864 11.2877,12.9664 11.0457,13.1284 C10.7777,13.2884 10.5897,13.5034 10.4557,13.7444 C10.1607,14.4144 9.7327,14.9514 9.1427,15.3804 C8.5257,15.8364 7.8547,16.0504 7.0767,16.0504 C6.5947,16.0504 6.1387,15.9424 5.7097,15.7554 C5.2537,15.5674 4.8787,15.3264 4.5567,15.0044 C4.2087,14.6834 3.9407,14.3074 3.7527,13.8514 C3.5647,13.4224 3.4577,12.9664 3.4577,12.4844 L3.4577,6.6654 C3.4577,6.1824 3.5647,5.7274 3.7527,5.2714 C3.9407,4.8424 4.1817,4.4664 4.5307,4.1444 C4.8517,3.8224 5.2267,3.5544 5.6827,3.3674 C6.1117,3.1794 6.5677,3.0994 7.0767,3.0994 C7.8547,3.0994 8.5257,3.3144 9.1427,3.7424 C9.7327,4.1714 10.1607,4.7074 10.4297,5.3784 C10.7237,5.9684 11.1537,6.2634 11.7697,6.2634 C12.2257,6.2634 12.6007,6.1024 12.8427,5.8074 C13.0837,5.5124 13.2177,5.1634 13.2177,4.8154 C13.2177,4.7074 13.2177,4.5734 13.1907,4.4394 C13.1647,4.3054 13.1107,4.1714 13.0307,4.0374 C12.7887,3.4744 12.4677,2.9384 12.0647,2.4554 C11.6627,1.9994 11.1807,1.5704 10.6707,1.2214 C10.1607,0.8734 9.5977,0.6054 8.9817,0.4174 C8.3647,0.2304 7.7477,0.1494 7.0767,0.1494 C6.1657,0.1494 5.3077,0.3104 4.5307,0.6594 C3.7257,1.0074 3.0287,1.4634 2.4387,2.0534 C1.8487,2.6434 1.3927,3.3404 1.0447,4.1174 C0.6957,4.9224 0.5347,5.7804 0.5347,6.6654 L0.5347,12.4844 Z" mask="url(#b)"/>
+ </g>
+ <path fill="#FFFFFF" d="M48.0601 32.5727C48.0601 32.9747 48.2211 33.3237 48.5161 33.6187 48.8111 33.9137 49.1331 34.0477 49.5351 34.0477 49.9371 34.0477 50.2861 33.9137 50.5811 33.6187 50.8751 33.3237 51.0101 32.9747 51.0101 32.5727L51.0101 17.8247C51.0101 17.4227 50.8751 17.0737 50.5811 16.7787 50.2861 16.4837 49.9371 16.3497 49.5351 16.3497 49.1331 16.3497 48.7841 16.4837 48.4891 16.7787 48.1941 17.0737 48.0601 17.4227 48.0601 17.8247L48.0601 32.5727zM53.7446 32.5727C53.7446 32.9747 53.8786 33.3237 54.1736 33.6187 54.4686 33.9137 54.8176 34.0477 55.2196 34.0477 55.6216 34.0477 55.9706 33.9137 56.2656 33.6187 56.5336 33.3237 56.6676 32.9747 56.6676 32.5727L56.6676 22.3027C56.6676 21.8197 56.7746 21.3647 56.9626 20.9087 57.1506 20.4797 57.3916 20.1047 57.7136 19.7817 58.0356 19.4607 58.4106 19.1927 58.8666 19.0047 59.2956 18.8167 59.7506 18.7367 60.2606 18.7367 60.7696 18.7367 61.2256 18.8167 61.6816 19.0047 62.1106 19.1927 62.4866 19.4607 62.8346 19.7817 63.1566 20.1047 63.4246 20.4797 63.6126 20.9087 63.7996 21.3647 63.8806 21.8197 63.8806 22.3027L63.8806 32.5727C63.8806 32.9747 64.0416 33.3237 64.3366 33.6187 64.6316 33.9137 64.9526 34.0477 65.3556 34.0477 65.7576 34.0477 66.1056 33.9137 66.4006 33.6187 66.6686 33.3237 66.8036 32.9747 66.8036 32.5727L66.8036 22.3027C66.8036 21.4177 66.6426 20.5597 66.2936 19.7817 65.9456 19.0047 65.4626 18.3077 64.8726 17.7177 64.2826 17.1277 63.5856 16.6447 62.8076 16.2967 62.0036 15.9477 61.1456 15.7867 60.2606 15.7867 59.3756 15.7867 58.5176 15.9477 57.7406 16.2967 56.9356 16.6447 56.2386 17.1277 55.6486 17.7177 55.0586 18.3077 54.6026 19.0047 54.2546 19.7817 53.9056 20.5597 53.7446 21.4177 53.7446 22.3027L53.7446 32.5727z"/>
+ <g transform="translate(69 15.61)">
+ <mask id="d" fill="white">
+ <use xlink:href="#c"/>
+ </mask>
+ <path fill="#FFFFFF" d="M0.7261,12.4844 C0.7261,13.3694 0.9131,14.2274 1.2621,15.0044 C1.6111,15.7824 2.0671,16.4794 2.6571,17.0694 C3.2471,17.6594 3.9441,18.1154 4.7481,18.4634 C5.5251,18.8124 6.3841,19.0004 7.2681,19.0004 C8.1541,19.0004 9.0121,18.8124 9.8161,18.4634 C10.5941,18.1154 11.2911,17.6594 11.8811,17.0694 C12.4711,16.4794 12.9541,15.7824 13.3021,15.0044 C13.6501,14.2274 13.8111,13.3694 13.8111,12.4844 L13.8111,10.2854 C13.8111,9.8834 13.6771,9.5344 13.3821,9.2394 C13.0871,8.9714 12.7391,8.8374 12.3371,8.8374 L9.4141,8.8374 C9.0121,8.8374 8.6631,8.9714 8.3951,9.2664 C8.1001,9.5614 7.9661,9.8834 7.9661,10.2854 C7.9661,10.6874 8.1001,11.0364 8.3951,11.3314 C8.6631,11.6264 9.0121,11.7604 9.4141,11.7604 L10.8621,11.7604 L10.8621,12.4844 C10.8621,12.9664 10.7811,13.4224 10.5941,13.8514 C10.4061,14.3074 10.1381,14.6834 9.8161,15.0044 C9.4951,15.3264 9.1191,15.5674 8.6901,15.7554 C8.2341,15.9424 7.7781,16.0504 7.2681,16.0504 C6.7861,16.0504 6.3301,15.9424 5.9011,15.7554 C5.4451,15.5674 5.0701,15.3264 4.7481,15.0044 C4.4001,14.6834 4.1581,14.3074 3.9711,13.8514 C3.7831,13.4224 3.6761,12.9664 3.6761,12.4844 L3.6761,6.6924 C3.6761,6.2094 3.7831,5.7544 3.9711,5.2974 C4.1581,4.8684 4.4001,4.4934 4.7221,4.1714 C5.0431,3.8494 5.4181,3.5814 5.8741,3.3944 C6.3031,3.2064 6.7591,3.1264 7.2681,3.1264 C8.0461,3.1264 8.7171,3.3404 9.3331,3.7694 C9.9231,4.1984 10.3521,4.7614 10.6471,5.4314 C10.7811,5.7274 10.9691,5.9414 11.2101,6.0754 C11.4521,6.2094 11.7201,6.2904 11.9611,6.2904 C12.4171,6.2904 12.7921,6.1294 13.0341,5.8344 C13.2751,5.5394 13.4091,5.1904 13.4091,4.8424 L13.4091,4.5204 C13.3821,4.4394 13.3551,4.3324 13.3291,4.2524 C13.0871,3.6354 12.7661,3.0994 12.3641,2.5894 C11.9611,2.0794 11.4791,1.6514 10.9691,1.3024 C10.4331,0.9544 9.8431,0.6594 9.2261,0.4714 C8.6091,0.2834 7.9391,0.1764 7.2681,0.1764 C6.3841,0.1764 5.5251,0.3374 4.7481,0.6864 C3.9441,1.0344 3.2471,1.4894 2.6571,2.0794 C2.0671,2.6704 1.6111,3.3674 1.2621,4.1444 C0.9131,4.9494 0.7261,5.8074 0.7261,6.6924 L0.7261,12.4844 Z" mask="url(#d)"/>
+ </g>
+ <path fill="#FFFFFF" d="M94.2886,26.352 L89.9716,26.352 C90.1586,25.896 90.3466,25.44 90.5346,25.038 C90.7226,24.636 90.8826,24.234 91.0706,23.831 C91.2586,23.429 91.4186,23.027 91.6066,22.624 C91.7676,22.223 91.9556,21.82 92.1426,21.391 L94.2886,26.352 Z M84.4206,32.063 C84.3936,32.144 84.3666,32.225 84.3396,32.331 C84.3126,32.438 84.3126,32.546 84.3126,32.626 C84.3126,32.975 84.4476,33.297 84.7156,33.592 C84.9566,33.914 85.3056,34.075 85.7616,34.075 C86.0566,34.075 86.2976,33.994 86.5386,33.86 C86.7806,33.726 86.9676,33.485 87.1016,33.19 C87.3706,32.519 87.6386,31.876 87.9336,31.232 C88.2016,30.615 88.4696,29.972 88.7646,29.302 L95.5486,29.302 L97.2116,33.19 C97.4526,33.78 97.8816,34.075 98.5516,34.075 C99.0076,34.075 99.3566,33.914 99.6246,33.592 C99.8656,33.297 99.9996,32.975 99.9996,32.626 C99.9996,32.466 99.9466,32.278 99.8656,32.063 L93.4836,17.127 C93.3506,16.806 93.0816,16.565 92.7056,16.377 C92.6256,16.323 92.5186,16.297 92.4386,16.27 C92.3306,16.243 92.2236,16.243 92.1426,16.243 C91.5266,16.243 91.0976,16.538 90.8026,17.127 L84.4206,32.063 Z"/>
+ <path stroke="#FFFFFF" stroke-width=".75" d="M12.2197 16.2373L4.2527 27.6613M12.2197 16.2373L28.7327 12.1643M12.5327 16.5717L17.1567 24.2957M10.5713 14.3247L3.7863 8.7697M13.2007 13.0962L17.1947 4.7982"/>
+ <g transform="translate(0 23.61)">
+ <mask id="f" fill="white">
+ <use xlink:href="#e"/>
+ </mask>
+ <path fill="#FFFFFF" d="M0.0103,4.6104 C-0.1407,2.6084 1.3613,0.8634 3.3643,0.7134 C5.3673,0.5604 7.1133,2.0634 7.2623,4.0654 C7.4123,6.0684 5.9123,7.8144 3.9093,7.9644 C1.9063,8.1154 0.1603,6.6134 0.0103,4.6104" mask="url(#f)"/>
+ </g>
+ <path fill="#FFFFFF" d="M16.022 24.3764C16.093 23.7564 16.654 23.3114 17.276 23.3824 17.897 23.4544 18.341 24.0164 18.27 24.6364 18.196 25.2574 17.636 25.7024 17.016 25.6314 16.395 25.5594 15.95 24.9984 16.022 24.3764M2.3223 9.603C1.9313 8.805 2.2623 7.839 3.0623 7.448 3.8613 7.057 4.8263 7.388 5.2183 8.187 5.6083 8.986 5.2783 9.952 4.4783 10.343 3.6783 10.734 2.7133 10.403 2.3223 9.603M26.1426 12.0361C26.0506 10.8211 26.9616 9.7611 28.1786 9.6691 29.3936 9.5781 30.4536 10.4891 30.5456 11.7051 30.6366 12.9211 29.7256 13.9811 28.5086 14.0721 27.2936 14.1631 26.2336 13.2521 26.1426 12.0361M16.1616.6313C17.3636-.3487 19.1346-.1677 20.1156 1.0343 21.0956 2.2363 20.9146 4.0073 19.7126 4.9883 18.5096 5.9663 16.7386 5.7863 15.7596 4.5843 14.7776 3.3813 14.9596 1.6113 16.1616.6313M8.5659 11.5605C10.8749 9.6785 14.2749 10.0245 16.1599 12.3345 18.0399 14.6435 17.6939 18.0435 15.3859 19.9275 13.0739 21.8075 9.6749 21.4625 7.7939 19.1515 5.9079 16.8425 6.2569 13.4425 8.5659 11.5605"/>
+ </g>
+</svg>
diff --git a/public/img/icingaweb2-background-orbs.jpg b/public/img/icingaweb2-background-orbs.jpg
new file mode 100644
index 0000000..bb6d40e
--- /dev/null
+++ b/public/img/icingaweb2-background-orbs.jpg
Binary files differ
diff --git a/public/img/icingaweb2-background.jpg b/public/img/icingaweb2-background.jpg
new file mode 100644
index 0000000..6a36024
--- /dev/null
+++ b/public/img/icingaweb2-background.jpg
Binary files differ
diff --git a/public/img/icons/acknowledgement.png b/public/img/icons/acknowledgement.png
new file mode 100644
index 0000000..eb03d2d
--- /dev/null
+++ b/public/img/icons/acknowledgement.png
Binary files differ
diff --git a/public/img/icons/acknowledgement_petrol.png b/public/img/icons/acknowledgement_petrol.png
new file mode 100644
index 0000000..e8345ea
--- /dev/null
+++ b/public/img/icons/acknowledgement_petrol.png
Binary files differ
diff --git a/public/img/icons/active_checks_disabled.png b/public/img/icons/active_checks_disabled.png
new file mode 100644
index 0000000..c21e445
--- /dev/null
+++ b/public/img/icons/active_checks_disabled.png
Binary files differ
diff --git a/public/img/icons/active_checks_disabled_petrol.png b/public/img/icons/active_checks_disabled_petrol.png
new file mode 100644
index 0000000..5614589
--- /dev/null
+++ b/public/img/icons/active_checks_disabled_petrol.png
Binary files differ
diff --git a/public/img/icons/active_passive_checks_disabled.png b/public/img/icons/active_passive_checks_disabled.png
new file mode 100644
index 0000000..664ecec
--- /dev/null
+++ b/public/img/icons/active_passive_checks_disabled.png
Binary files differ
diff --git a/public/img/icons/active_passive_checks_disabled_petrol.png b/public/img/icons/active_passive_checks_disabled_petrol.png
new file mode 100644
index 0000000..c82bfd9
--- /dev/null
+++ b/public/img/icons/active_passive_checks_disabled_petrol.png
Binary files differ
diff --git a/public/img/icons/comment.png b/public/img/icons/comment.png
new file mode 100644
index 0000000..b7d88a0
--- /dev/null
+++ b/public/img/icons/comment.png
Binary files differ
diff --git a/public/img/icons/comment_petrol.png b/public/img/icons/comment_petrol.png
new file mode 100644
index 0000000..53c1223
--- /dev/null
+++ b/public/img/icons/comment_petrol.png
Binary files differ
diff --git a/public/img/icons/configuration.png b/public/img/icons/configuration.png
new file mode 100644
index 0000000..fb52cd6
--- /dev/null
+++ b/public/img/icons/configuration.png
Binary files differ
diff --git a/public/img/icons/configuration_petrol.png b/public/img/icons/configuration_petrol.png
new file mode 100644
index 0000000..8168133
--- /dev/null
+++ b/public/img/icons/configuration_petrol.png
Binary files differ
diff --git a/public/img/icons/create.png b/public/img/icons/create.png
new file mode 100644
index 0000000..b9bedf8
--- /dev/null
+++ b/public/img/icons/create.png
Binary files differ
diff --git a/public/img/icons/create_petrol.png b/public/img/icons/create_petrol.png
new file mode 100644
index 0000000..4e104af
--- /dev/null
+++ b/public/img/icons/create_petrol.png
Binary files differ
diff --git a/public/img/icons/csv.png b/public/img/icons/csv.png
new file mode 100644
index 0000000..5b41917
--- /dev/null
+++ b/public/img/icons/csv.png
Binary files differ
diff --git a/public/img/icons/csv_petrol.png b/public/img/icons/csv_petrol.png
new file mode 100644
index 0000000..05d53f0
--- /dev/null
+++ b/public/img/icons/csv_petrol.png
Binary files differ
diff --git a/public/img/icons/dashboard.png b/public/img/icons/dashboard.png
new file mode 100644
index 0000000..e5f9c09
--- /dev/null
+++ b/public/img/icons/dashboard.png
Binary files differ
diff --git a/public/img/icons/dashboard_petrol.png b/public/img/icons/dashboard_petrol.png
new file mode 100644
index 0000000..04936ba
--- /dev/null
+++ b/public/img/icons/dashboard_petrol.png
Binary files differ
diff --git a/public/img/icons/disabled.png b/public/img/icons/disabled.png
new file mode 100644
index 0000000..54d0364
--- /dev/null
+++ b/public/img/icons/disabled.png
Binary files differ
diff --git a/public/img/icons/disabled_petrol.png b/public/img/icons/disabled_petrol.png
new file mode 100644
index 0000000..7319fb7
--- /dev/null
+++ b/public/img/icons/disabled_petrol.png
Binary files differ
diff --git a/public/img/icons/down.png b/public/img/icons/down.png
new file mode 100644
index 0000000..a844413
--- /dev/null
+++ b/public/img/icons/down.png
Binary files differ
diff --git a/public/img/icons/down_petrol.png b/public/img/icons/down_petrol.png
new file mode 100644
index 0000000..dda1283
--- /dev/null
+++ b/public/img/icons/down_petrol.png
Binary files differ
diff --git a/public/img/icons/downtime_end.png b/public/img/icons/downtime_end.png
new file mode 100644
index 0000000..e1d0d97
--- /dev/null
+++ b/public/img/icons/downtime_end.png
Binary files differ
diff --git a/public/img/icons/downtime_end_petrol.png b/public/img/icons/downtime_end_petrol.png
new file mode 100644
index 0000000..5d0666b
--- /dev/null
+++ b/public/img/icons/downtime_end_petrol.png
Binary files differ
diff --git a/public/img/icons/downtime_start.png b/public/img/icons/downtime_start.png
new file mode 100644
index 0000000..8e33280
--- /dev/null
+++ b/public/img/icons/downtime_start.png
Binary files differ
diff --git a/public/img/icons/downtime_start__petrol.png b/public/img/icons/downtime_start__petrol.png
new file mode 100644
index 0000000..9d56acd
--- /dev/null
+++ b/public/img/icons/downtime_start__petrol.png
Binary files differ
diff --git a/public/img/icons/edit.png b/public/img/icons/edit.png
new file mode 100644
index 0000000..00d8e29
--- /dev/null
+++ b/public/img/icons/edit.png
Binary files differ
diff --git a/public/img/icons/edit_petrol.png b/public/img/icons/edit_petrol.png
new file mode 100644
index 0000000..8199b01
--- /dev/null
+++ b/public/img/icons/edit_petrol.png
Binary files differ
diff --git a/public/img/icons/error.png b/public/img/icons/error.png
new file mode 100644
index 0000000..6245899
--- /dev/null
+++ b/public/img/icons/error.png
Binary files differ
diff --git a/public/img/icons/error_petrol.png b/public/img/icons/error_petrol.png
new file mode 100644
index 0000000..817ec76
--- /dev/null
+++ b/public/img/icons/error_petrol.png
Binary files differ
diff --git a/public/img/icons/error_white.png b/public/img/icons/error_white.png
new file mode 100644
index 0000000..0f50874
--- /dev/null
+++ b/public/img/icons/error_white.png
Binary files differ
diff --git a/public/img/icons/expand.png b/public/img/icons/expand.png
new file mode 100644
index 0000000..8ee1725
--- /dev/null
+++ b/public/img/icons/expand.png
Binary files differ
diff --git a/public/img/icons/expand_petrol.png b/public/img/icons/expand_petrol.png
new file mode 100644
index 0000000..a794ef5
--- /dev/null
+++ b/public/img/icons/expand_petrol.png
Binary files differ
diff --git a/public/img/icons/flapping.png b/public/img/icons/flapping.png
new file mode 100644
index 0000000..4b253e0
--- /dev/null
+++ b/public/img/icons/flapping.png
Binary files differ
diff --git a/public/img/icons/flapping_petrol.png b/public/img/icons/flapping_petrol.png
new file mode 100644
index 0000000..c6d045a
--- /dev/null
+++ b/public/img/icons/flapping_petrol.png
Binary files differ
diff --git a/public/img/icons/history.png b/public/img/icons/history.png
new file mode 100644
index 0000000..db4520d
--- /dev/null
+++ b/public/img/icons/history.png
Binary files differ
diff --git a/public/img/icons/history_petrol.png b/public/img/icons/history_petrol.png
new file mode 100644
index 0000000..1aba36b
--- /dev/null
+++ b/public/img/icons/history_petrol.png
Binary files differ
diff --git a/public/img/icons/host.png b/public/img/icons/host.png
new file mode 100644
index 0000000..33e2f5d
--- /dev/null
+++ b/public/img/icons/host.png
Binary files differ
diff --git a/public/img/icons/host_petrol.png b/public/img/icons/host_petrol.png
new file mode 100644
index 0000000..6568fe6
--- /dev/null
+++ b/public/img/icons/host_petrol.png
Binary files differ
diff --git a/public/img/icons/hostgroup.png b/public/img/icons/hostgroup.png
new file mode 100644
index 0000000..e71e3c9
--- /dev/null
+++ b/public/img/icons/hostgroup.png
Binary files differ
diff --git a/public/img/icons/hostgroup_petrol.png b/public/img/icons/hostgroup_petrol.png
new file mode 100644
index 0000000..68dbd19
--- /dev/null
+++ b/public/img/icons/hostgroup_petrol.png
Binary files differ
diff --git a/public/img/icons/in_downtime.png b/public/img/icons/in_downtime.png
new file mode 100644
index 0000000..d609df6
--- /dev/null
+++ b/public/img/icons/in_downtime.png
Binary files differ
diff --git a/public/img/icons/in_downtime_petrol.png b/public/img/icons/in_downtime_petrol.png
new file mode 100644
index 0000000..f6ee49a
--- /dev/null
+++ b/public/img/icons/in_downtime_petrol.png
Binary files differ
diff --git a/public/img/icons/json.png b/public/img/icons/json.png
new file mode 100644
index 0000000..d8e74c3
--- /dev/null
+++ b/public/img/icons/json.png
Binary files differ
diff --git a/public/img/icons/json_petrol.png b/public/img/icons/json_petrol.png
new file mode 100644
index 0000000..0197455
--- /dev/null
+++ b/public/img/icons/json_petrol.png
Binary files differ
diff --git a/public/img/icons/logout.png b/public/img/icons/logout.png
new file mode 100644
index 0000000..b93487a
--- /dev/null
+++ b/public/img/icons/logout.png
Binary files differ
diff --git a/public/img/icons/logout_petrol.png b/public/img/icons/logout_petrol.png
new file mode 100644
index 0000000..82ece09
--- /dev/null
+++ b/public/img/icons/logout_petrol.png
Binary files differ
diff --git a/public/img/icons/next.png b/public/img/icons/next.png
new file mode 100644
index 0000000..905e598
--- /dev/null
+++ b/public/img/icons/next.png
Binary files differ
diff --git a/public/img/icons/next_petrol.png b/public/img/icons/next_petrol.png
new file mode 100644
index 0000000..488df30
--- /dev/null
+++ b/public/img/icons/next_petrol.png
Binary files differ
diff --git a/public/img/icons/notification.png b/public/img/icons/notification.png
new file mode 100644
index 0000000..ea805d5
--- /dev/null
+++ b/public/img/icons/notification.png
Binary files differ
diff --git a/public/img/icons/notification_disabled.png b/public/img/icons/notification_disabled.png
new file mode 100644
index 0000000..ffcf1e6
--- /dev/null
+++ b/public/img/icons/notification_disabled.png
Binary files differ
diff --git a/public/img/icons/notification_disabled_petrol.png b/public/img/icons/notification_disabled_petrol.png
new file mode 100644
index 0000000..96dde74
--- /dev/null
+++ b/public/img/icons/notification_disabled_petrol.png
Binary files differ
diff --git a/public/img/icons/notification_petrol.png b/public/img/icons/notification_petrol.png
new file mode 100644
index 0000000..fc2fde8
--- /dev/null
+++ b/public/img/icons/notification_petrol.png
Binary files differ
diff --git a/public/img/icons/pdf.png b/public/img/icons/pdf.png
new file mode 100644
index 0000000..eefcef6
--- /dev/null
+++ b/public/img/icons/pdf.png
Binary files differ
diff --git a/public/img/icons/pdf_petrol.png b/public/img/icons/pdf_petrol.png
new file mode 100644
index 0000000..ad044e6
--- /dev/null
+++ b/public/img/icons/pdf_petrol.png
Binary files differ
diff --git a/public/img/icons/prev.png b/public/img/icons/prev.png
new file mode 100644
index 0000000..edfabd3
--- /dev/null
+++ b/public/img/icons/prev.png
Binary files differ
diff --git a/public/img/icons/prev_petrol.png b/public/img/icons/prev_petrol.png
new file mode 100644
index 0000000..9152912
--- /dev/null
+++ b/public/img/icons/prev_petrol.png
Binary files differ
diff --git a/public/img/icons/refresh.png b/public/img/icons/refresh.png
new file mode 100644
index 0000000..b0222a5
--- /dev/null
+++ b/public/img/icons/refresh.png
Binary files differ
diff --git a/public/img/icons/refresh_petrol.png b/public/img/icons/refresh_petrol.png
new file mode 100644
index 0000000..50e8155
--- /dev/null
+++ b/public/img/icons/refresh_petrol.png
Binary files differ
diff --git a/public/img/icons/remove.png b/public/img/icons/remove.png
new file mode 100644
index 0000000..98ada74
--- /dev/null
+++ b/public/img/icons/remove.png
Binary files differ
diff --git a/public/img/icons/remove_petrol.png b/public/img/icons/remove_petrol.png
new file mode 100644
index 0000000..8ec49a2
--- /dev/null
+++ b/public/img/icons/remove_petrol.png
Binary files differ
diff --git a/public/img/icons/reschedule.png b/public/img/icons/reschedule.png
new file mode 100644
index 0000000..5efc20d
--- /dev/null
+++ b/public/img/icons/reschedule.png
Binary files differ
diff --git a/public/img/icons/reschedule_petrol.png b/public/img/icons/reschedule_petrol.png
new file mode 100644
index 0000000..3a24c90
--- /dev/null
+++ b/public/img/icons/reschedule_petrol.png
Binary files differ
diff --git a/public/img/icons/save.png b/public/img/icons/save.png
new file mode 100644
index 0000000..2db2719
--- /dev/null
+++ b/public/img/icons/save.png
Binary files differ
diff --git a/public/img/icons/save_petrol.png b/public/img/icons/save_petrol.png
new file mode 100644
index 0000000..3e42cd7
--- /dev/null
+++ b/public/img/icons/save_petrol.png
Binary files differ
diff --git a/public/img/icons/search.png b/public/img/icons/search.png
new file mode 100644
index 0000000..a66c3ca
--- /dev/null
+++ b/public/img/icons/search.png
Binary files differ
diff --git a/public/img/icons/search_icinga_blue.png b/public/img/icons/search_icinga_blue.png
new file mode 100644
index 0000000..0a284ca
--- /dev/null
+++ b/public/img/icons/search_icinga_blue.png
Binary files differ
diff --git a/public/img/icons/search_petrol.png b/public/img/icons/search_petrol.png
new file mode 100644
index 0000000..c02b702
--- /dev/null
+++ b/public/img/icons/search_petrol.png
Binary files differ
diff --git a/public/img/icons/search_white.png b/public/img/icons/search_white.png
new file mode 100644
index 0000000..638be2e
--- /dev/null
+++ b/public/img/icons/search_white.png
Binary files differ
diff --git a/public/img/icons/service.png b/public/img/icons/service.png
new file mode 100644
index 0000000..9df3b48
--- /dev/null
+++ b/public/img/icons/service.png
Binary files differ
diff --git a/public/img/icons/service_petrol.png b/public/img/icons/service_petrol.png
new file mode 100644
index 0000000..ea165a2
--- /dev/null
+++ b/public/img/icons/service_petrol.png
Binary files differ
diff --git a/public/img/icons/servicegroup.png b/public/img/icons/servicegroup.png
new file mode 100644
index 0000000..d4b87df
--- /dev/null
+++ b/public/img/icons/servicegroup.png
Binary files differ
diff --git a/public/img/icons/servicegroup_petrol.png b/public/img/icons/servicegroup_petrol.png
new file mode 100644
index 0000000..8b963c0
--- /dev/null
+++ b/public/img/icons/servicegroup_petrol.png
Binary files differ
diff --git a/public/img/icons/softstate.png b/public/img/icons/softstate.png
new file mode 100644
index 0000000..8efa442
--- /dev/null
+++ b/public/img/icons/softstate.png
Binary files differ
diff --git a/public/img/icons/submit.png b/public/img/icons/submit.png
new file mode 100644
index 0000000..40e4826
--- /dev/null
+++ b/public/img/icons/submit.png
Binary files differ
diff --git a/public/img/icons/submit_petrol.png b/public/img/icons/submit_petrol.png
new file mode 100644
index 0000000..b0b3156
--- /dev/null
+++ b/public/img/icons/submit_petrol.png
Binary files differ
diff --git a/public/img/icons/success.png b/public/img/icons/success.png
new file mode 100644
index 0000000..2f95639
--- /dev/null
+++ b/public/img/icons/success.png
Binary files differ
diff --git a/public/img/icons/success_petrol.png b/public/img/icons/success_petrol.png
new file mode 100644
index 0000000..302461f
--- /dev/null
+++ b/public/img/icons/success_petrol.png
Binary files differ
diff --git a/public/img/icons/tux.png b/public/img/icons/tux.png
new file mode 100644
index 0000000..c51a0b4
--- /dev/null
+++ b/public/img/icons/tux.png
Binary files differ
diff --git a/public/img/icons/uebersicht.png b/public/img/icons/uebersicht.png
new file mode 100644
index 0000000..501edc4
--- /dev/null
+++ b/public/img/icons/uebersicht.png
Binary files differ
diff --git a/public/img/icons/unhandled.png b/public/img/icons/unhandled.png
new file mode 100644
index 0000000..59dab84
--- /dev/null
+++ b/public/img/icons/unhandled.png
Binary files differ
diff --git a/public/img/icons/unhandled_petrol.png b/public/img/icons/unhandled_petrol.png
new file mode 100644
index 0000000..3972337
--- /dev/null
+++ b/public/img/icons/unhandled_petrol.png
Binary files differ
diff --git a/public/img/icons/up.png b/public/img/icons/up.png
new file mode 100644
index 0000000..beef484
--- /dev/null
+++ b/public/img/icons/up.png
Binary files differ
diff --git a/public/img/icons/up_petrol.png b/public/img/icons/up_petrol.png
new file mode 100644
index 0000000..946bef1
--- /dev/null
+++ b/public/img/icons/up_petrol.png
Binary files differ
diff --git a/public/img/icons/user.png b/public/img/icons/user.png
new file mode 100644
index 0000000..694ddfc
--- /dev/null
+++ b/public/img/icons/user.png
Binary files differ
diff --git a/public/img/icons/user_petrol.png b/public/img/icons/user_petrol.png
new file mode 100644
index 0000000..3fae64d
--- /dev/null
+++ b/public/img/icons/user_petrol.png
Binary files differ
diff --git a/public/img/icons/win.png b/public/img/icons/win.png
new file mode 100644
index 0000000..7ffcea7
--- /dev/null
+++ b/public/img/icons/win.png
Binary files differ
diff --git a/public/img/orb-analytics.png b/public/img/orb-analytics.png
new file mode 100644
index 0000000..7adae6f
--- /dev/null
+++ b/public/img/orb-analytics.png
Binary files differ
diff --git a/public/img/orb-automation.png b/public/img/orb-automation.png
new file mode 100644
index 0000000..eb292b5
--- /dev/null
+++ b/public/img/orb-automation.png
Binary files differ
diff --git a/public/img/orb-cloud.png b/public/img/orb-cloud.png
new file mode 100644
index 0000000..f5e4a64
--- /dev/null
+++ b/public/img/orb-cloud.png
Binary files differ
diff --git a/public/img/orb-icinga.png b/public/img/orb-icinga.png
new file mode 100644
index 0000000..9f7f2a6
--- /dev/null
+++ b/public/img/orb-icinga.png
Binary files differ
diff --git a/public/img/orb-infrastructure.png b/public/img/orb-infrastructure.png
new file mode 100644
index 0000000..8713965
--- /dev/null
+++ b/public/img/orb-infrastructure.png
Binary files differ
diff --git a/public/img/orb-metrics.png b/public/img/orb-metrics.png
new file mode 100644
index 0000000..199f21c
--- /dev/null
+++ b/public/img/orb-metrics.png
Binary files differ
diff --git a/public/img/orb-notifications.png b/public/img/orb-notifications.png
new file mode 100644
index 0000000..297ba58
--- /dev/null
+++ b/public/img/orb-notifications.png
Binary files differ
diff --git a/public/img/select-icon-2x.png b/public/img/select-icon-2x.png
new file mode 100644
index 0000000..57845df
--- /dev/null
+++ b/public/img/select-icon-2x.png
Binary files differ
diff --git a/public/img/select-icon.png b/public/img/select-icon.png
new file mode 100644
index 0000000..e0d9a5a
--- /dev/null
+++ b/public/img/select-icon.png
Binary files differ
diff --git a/public/img/select-icon.svg b/public/img/select-icon.svg
new file mode 100644
index 0000000..cc8a011
--- /dev/null
+++ b/public/img/select-icon.svg
@@ -0,0 +1 @@
+<svg height="32" viewBox="0 0 24 32" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m5.20126707.78766623 4.45386238 4.20402191c.16721345.15783356.16291017.40979541-.00961164.56277256-.081158.0719638-.18974398.11220597-.3027668.11220597h-8.90772462c-.24025844 0-.43502639-.17818569-.43502639-.39798892 0-.10340014.04398717-.20274128.12264801-.27698961l4.45386234-4.20402191c.16721345-.15783357.44262326-.16177048.61514507-.00879333.00325382.00288518.00645805.00581661.00961165.00879333z" fill="#00c3ed" transform="matrix(1 0 0 -1 7 20.666667)"/></svg> \ No newline at end of file
diff --git a/public/img/textarea-corner-2x.png b/public/img/textarea-corner-2x.png
new file mode 100644
index 0000000..ee9cb50
--- /dev/null
+++ b/public/img/textarea-corner-2x.png
Binary files differ
diff --git a/public/img/textarea-corner.png b/public/img/textarea-corner.png
new file mode 100644
index 0000000..3a2242c
--- /dev/null
+++ b/public/img/textarea-corner.png
Binary files differ
diff --git a/public/img/theme-mode-thumbnail-dark.svg b/public/img/theme-mode-thumbnail-dark.svg
new file mode 100644
index 0000000..40f93e8
--- /dev/null
+++ b/public/img/theme-mode-thumbnail-dark.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="800px" height="480px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+ <g transform="matrix(1,0,0,1,-984,0)">
+ <g id="dark" transform="matrix(1,0,0,0.8,984.021,0)">
+ <rect x="0" y="0" width="800" height="600" style="fill:none;"/>
+ <clipPath id="_clip1">
+ <rect x="0" y="0" width="800" height="600"/>
+ </clipPath>
+ <g clip-path="url(#_clip1)">
+ <g transform="matrix(1,0,0,1.25,0,9.09495e-13)">
+ <clipPath id="_clip2">
+ <rect x="0" y="-0" width="800" height="480"/>
+ </clipPath>
+ <g clip-path="url(#_clip2)">
+ <g transform="matrix(1.81105,0,0,1,0,0)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(7,5,44);"/>
+ </g>
+ <g transform="matrix(1.81105,0,0,0.30498,-1.13687e-13,190.035)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(25,21,68);"/>
+ </g>
+ <g transform="matrix(0.77088,0,0,0.290377,-644.149,483.662)">
+ <rect x="835.602" y="-1337.3" width="326.111" height="326.111" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g>
+ <path d="M251.392,110.852L220.365,141.879L251.392,172.906L251.392,353.271L188.167,353.271C182.556,353.271 178.008,357.819 178.008,363.43L178.008,416.496C178.008,422.107 182.556,426.655 188.167,426.655L251.392,426.655L251.392,600L800,600L800,-0L251.392,-0L251.392,110.852Z" style="fill:rgb(39,46,58);"/>
+ <path d="M251.392,110.852L220.365,141.879L251.392,172.906L251.392,353.271L188.167,353.271C182.556,353.271 178.008,357.819 178.008,363.43L178.008,416.496C178.008,422.107 182.556,426.655 188.167,426.655L251.392,426.655L251.392,600L800,600L800,-0L251.392,-0L251.392,110.852Z" style="fill:rgb(39,46,58);"/>
+ </g>
+ <g transform="matrix(4.8965,0,0,4.8965,-290.421,-2191.75)">
+ <g transform="matrix(1,0,0,1,1341.37,-355.199)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(0.500319,0,0,0.90861,751.454,-280.049)">
+ <path d="M-1178.23,822.298C-1178.23,819.786 -1180.04,817.378 -1183.26,815.602C-1186.49,813.826 -1190.86,812.828 -1195.42,812.828L-1195.43,812.828C-1199.99,812.828 -1204.37,813.826 -1207.59,815.602C-1210.82,817.378 -1212.63,819.786 -1212.63,822.298L-1212.63,822.302C-1212.63,824.813 -1210.82,827.222 -1207.59,828.998C-1204.37,830.774 -1199.99,831.771 -1195.43,831.771L-1195.42,831.771C-1190.86,831.771 -1186.49,830.774 -1183.26,828.998C-1180.04,827.222 -1178.23,824.813 -1178.23,822.302L-1178.23,822.298Z" style="fill:rgb(39,46,58);"/>
+ </g>
+ </g>
+ <g transform="matrix(4.8965,0,0,4.8965,-96.0097,-2195.22)">
+ <g transform="matrix(1,0,0,1,1341.37,-355.199)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(62,77,116);"/>
+ </g>
+ <g transform="matrix(0.500319,0,0,0.90861,751.454,-280.049)">
+ <path d="M-1178.23,822.298C-1178.23,819.786 -1180.04,817.378 -1183.26,815.602C-1186.49,813.826 -1190.86,812.828 -1195.42,812.828L-1195.43,812.828C-1199.99,812.828 -1204.37,813.826 -1207.59,815.602C-1210.82,817.378 -1212.63,819.786 -1212.63,822.298L-1212.63,822.302C-1212.63,824.813 -1210.82,827.222 -1207.59,828.998C-1204.37,830.774 -1199.99,831.771 -1195.43,831.771L-1195.42,831.771C-1190.86,831.771 -1186.49,830.774 -1183.26,828.998C-1180.04,827.222 -1178.23,824.813 -1178.23,822.302L-1178.23,822.298Z" style="fill:rgb(39,46,58);"/>
+ </g>
+ </g>
+ <g transform="matrix(1.39597,0,0,1,-120.257,-227.628)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(1.39597,0,0,1,-120.257,-71.831)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054C403.105,523.054 617.133,523.054 675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434ZM687.21,421.434L687.21,502.154C687.21,510.935 682.111,518.054 675.821,518.054C617.133,518.054 403.105,518.054 344.417,518.054C338.127,518.054 333.028,510.935 333.028,502.154L333.028,421.434C333.028,412.653 338.127,405.534 344.417,405.534C344.417,405.534 675.768,405.534 675.821,405.534C682.111,405.534 687.21,412.653 687.21,421.434Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(4.71406,0,0,4.71406,191.129,368.75)">
+ <path d="M8.977,8.086L9.507,7.554C9.727,7.336 9.727,6.98 9.507,6.759L7.248,4.5L9.505,2.238C9.725,2.02 9.725,1.664 9.505,1.444L8.975,0.914C8.757,0.694 8.401,0.694 8.18,0.914L4.993,4.102C4.773,4.322 4.773,4.678 4.995,4.898L8.183,8.086C8.401,8.306 8.757,8.306 8.977,8.086ZM4.475,8.086L5.005,7.556C5.225,7.336 5.225,6.98 5.005,6.762L2.748,4.5L5.007,2.241C5.227,2.02 5.227,1.664 5.007,1.446L4.477,0.914C4.257,0.694 3.901,0.694 3.683,0.914L0.495,4.102C0.273,4.322 0.273,4.678 0.493,4.898L3.68,8.086C3.901,8.306 4.257,8.306 4.475,8.086Z" style="fill:rgb(0,196,240);fill-rule:nonzero;"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/public/img/theme-mode-thumbnail-light.svg b/public/img/theme-mode-thumbnail-light.svg
new file mode 100644
index 0000000..a04f441
--- /dev/null
+++ b/public/img/theme-mode-thumbnail-light.svg
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="800px" height="480px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+ <g transform="matrix(1,0,0,1,-984,-674)">
+ <g id="light" transform="matrix(1,0,0,0.8,984.021,674)">
+ <rect x="0" y="0" width="800" height="600" style="fill:none;"/>
+ <g>
+ <clipPath id="_clip1">
+ <rect x="0" y="0" width="800" height="600"/>
+ </clipPath>
+ <g clip-path="url(#_clip1)">
+ <g transform="matrix(1.81105,0,0,1.25,0,-1.13687e-13)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(219,236,242);"/>
+ </g>
+ <g transform="matrix(1.81105,0,0,0.391381,-1.13687e-13,237.544)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(235,247,253);"/>
+ </g>
+ <g transform="matrix(0.77088,0,0,0.362971,-644.149,604.578)">
+ <rect x="835.602" y="-1337.3" width="326.111" height="326.111" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g>
+ <path d="M251.392,139.576L220.365,178.36L251.392,217.144L251.392,750L800,750L800,-0L251.392,-0L251.392,139.576ZM251.392,434.008L188.167,434.008C182.556,434.008 178.008,439.693 178.008,446.707L178.008,513.039C178.008,520.053 182.556,525.739 188.167,525.739L251.392,525.739L251.392,434.008Z" style="fill:rgb(244,249,250);"/>
+ <path d="M251.392,139.576L220.365,178.36L251.392,217.144L251.392,750L800,750L800,-0L251.392,-0L251.392,139.576ZM251.392,434.008L188.167,434.008C182.556,434.008 178.008,439.693 178.008,446.707L178.008,513.039C178.008,520.053 182.556,525.739 188.167,525.739L251.392,525.739L251.392,434.008Z" style="fill:rgb(244,249,250);"/>
+ </g>
+ <g transform="matrix(4.8965,0,0,6.12063,-290.421,-2739.68)">
+ <g transform="matrix(1,0,0,1,1341.37,-355.199)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(0.500319,0,0,0.90861,751.454,-280.049)">
+ <path d="M-1178.23,822.298C-1178.23,819.786 -1180.04,817.378 -1183.26,815.602C-1186.49,813.826 -1190.86,812.828 -1195.42,812.828L-1195.43,812.828C-1199.99,812.828 -1204.37,813.826 -1207.59,815.602C-1210.82,817.378 -1212.63,819.786 -1212.63,822.298L-1212.63,822.302C-1212.63,824.813 -1210.82,827.222 -1207.59,828.998C-1204.37,830.774 -1199.99,831.771 -1195.43,831.771L-1195.42,831.771C-1190.86,831.771 -1186.49,830.774 -1183.26,828.998C-1180.04,827.222 -1178.23,824.813 -1178.23,822.302L-1178.23,822.298Z" style="fill:rgb(244,249,250);"/>
+ </g>
+ </g>
+ <g transform="matrix(4.8965,0,0,6.12063,-96.0097,-2741.58)">
+ <g transform="matrix(1,0,0,1,1341.37,-355.199)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(219,236,242);"/>
+ </g>
+ <g transform="matrix(0.500319,0,0,0.90861,736.813,-280.049)">
+ <path d="M-1178.23,822.298C-1178.23,819.786 -1180.04,817.378 -1183.26,815.602C-1186.49,813.826 -1190.86,812.828 -1195.42,812.828L-1195.43,812.828C-1199.99,812.828 -1204.37,813.826 -1207.59,815.602C-1210.82,817.378 -1212.63,819.786 -1212.63,822.298L-1212.63,822.302C-1212.63,824.813 -1210.82,827.222 -1207.59,828.998C-1204.37,830.774 -1199.99,831.771 -1195.43,831.771L-1195.42,831.771C-1190.86,831.771 -1186.49,830.774 -1183.26,828.998C-1180.04,827.222 -1178.23,824.813 -1178.23,822.302L-1178.23,822.298Z" style="fill:rgb(244,249,250);"/>
+ </g>
+ </g>
+ <g transform="matrix(1.39597,0,0,1.25,-120.257,-292.116)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(1.39597,0,0,1.25,-120.257,-97.3693)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434Z" style="fill:none;stroke:rgb(0,196,240);stroke-width:4.12px;"/>
+ </g>
+ <g transform="matrix(4.71406,0,0,5.89258,191.129,453.357)">
+ <path d="M8.977,8.086L9.507,7.554C9.727,7.336 9.727,6.98 9.507,6.759L7.248,4.5L9.505,2.238C9.725,2.02 9.725,1.664 9.505,1.444L8.975,0.914C8.757,0.694 8.401,0.694 8.18,0.914L4.993,4.102C4.773,4.322 4.773,4.678 4.995,4.898L8.183,8.086C8.401,8.306 8.757,8.306 8.977,8.086ZM4.475,8.086L5.005,7.556C5.225,7.336 5.225,6.98 5.005,6.762L2.748,4.5L5.007,2.241C5.227,2.02 5.227,1.664 5.007,1.446L4.477,0.914C4.257,0.694 3.901,0.694 3.683,0.914L0.495,4.102C0.273,4.322 0.273,4.678 0.493,4.898L3.68,8.086C3.901,8.306 4.257,8.306 4.475,8.086Z" style="fill:rgb(0,196,240);fill-rule:nonzero;"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/public/img/theme-mode-thumbnail-system.svg b/public/img/theme-mode-thumbnail-system.svg
new file mode 100644
index 0000000..55e2168
--- /dev/null
+++ b/public/img/theme-mode-thumbnail-system.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="800px" height="480px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+ <g transform="matrix(1,0,0,1,-984,-1360)">
+ <g id="auto" transform="matrix(1,0,0,0.8,984.021,1360)">
+ <rect x="0" y="0" width="800" height="600" style="fill:none;"/>
+ <clipPath id="_clip1">
+ <rect x="0" y="0" width="800" height="600"/>
+ </clipPath>
+ <g clip-path="url(#_clip1)">
+ <g id="light">
+ <clipPath id="_clip2">
+ <rect x="0" y="0" width="800" height="600"/>
+ </clipPath>
+ <g clip-path="url(#_clip2)">
+ <g transform="matrix(1.81105,0,0,1.25,0,-1.13687e-13)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(219,236,242);"/>
+ </g>
+ <g transform="matrix(1.81105,0,0,0.391381,-1.13687e-13,237.544)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(235,247,253);"/>
+ </g>
+ <g transform="matrix(0.77088,0,0,0.362971,-644.149,604.578)">
+ <rect x="835.602" y="-1337.3" width="326.111" height="326.111" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g>
+ <path d="M251.392,139.576L220.365,178.36L251.392,217.144L251.392,750L800,750L800,-0L251.392,-0L251.392,139.576ZM251.392,434.008L188.167,434.008C182.556,434.008 178.008,439.693 178.008,446.707L178.008,513.039C178.008,520.053 182.556,525.739 188.167,525.739L251.392,525.739L251.392,434.008Z" style="fill:rgb(244,249,250);"/>
+ <path d="M251.392,139.576L220.365,178.36L251.392,217.144L251.392,750L800,750L800,-0L251.392,-0L251.392,139.576ZM251.392,434.008L188.167,434.008C182.556,434.008 178.008,439.693 178.008,446.707L178.008,513.039C178.008,520.053 182.556,525.739 188.167,525.739L251.392,525.739L251.392,434.008Z" style="fill:rgb(244,249,250);"/>
+ </g>
+ <g transform="matrix(4.8965,0,0,6.12063,-290.421,-2739.68)">
+ <g transform="matrix(1,0,0,1,1341.37,-355.199)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(0.500319,0,0,0.90861,751.454,-280.049)">
+ <path d="M-1178.23,822.298C-1178.23,819.786 -1180.04,817.378 -1183.26,815.602C-1186.49,813.826 -1190.86,812.828 -1195.42,812.828L-1195.43,812.828C-1199.99,812.828 -1204.37,813.826 -1207.59,815.602C-1210.82,817.378 -1212.63,819.786 -1212.63,822.298L-1212.63,822.302C-1212.63,824.813 -1210.82,827.222 -1207.59,828.998C-1204.37,830.774 -1199.99,831.771 -1195.43,831.771L-1195.42,831.771C-1190.86,831.771 -1186.49,830.774 -1183.26,828.998C-1180.04,827.222 -1178.23,824.813 -1178.23,822.302L-1178.23,822.298Z" style="fill:rgb(244,249,250);"/>
+ </g>
+ </g>
+ <g transform="matrix(4.8965,0,0,6.12063,-96.0097,-2741.58)">
+ <g transform="matrix(1,0,0,1,1341.37,-355.199)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(219,236,242);"/>
+ </g>
+ <g transform="matrix(0.500319,0,0,0.90861,736.813,-280.049)">
+ <path d="M-1178.23,822.298C-1178.23,819.786 -1180.04,817.378 -1183.26,815.602C-1186.49,813.826 -1190.86,812.828 -1195.42,812.828L-1195.43,812.828C-1199.99,812.828 -1204.37,813.826 -1207.59,815.602C-1210.82,817.378 -1212.63,819.786 -1212.63,822.298L-1212.63,822.302C-1212.63,824.813 -1210.82,827.222 -1207.59,828.998C-1204.37,830.774 -1199.99,831.771 -1195.43,831.771L-1195.42,831.771C-1190.86,831.771 -1186.49,830.774 -1183.26,828.998C-1180.04,827.222 -1178.23,824.813 -1178.23,822.302L-1178.23,822.298Z" style="fill:rgb(244,249,250);"/>
+ </g>
+ </g>
+ <g transform="matrix(1.39597,0,0,1.25,-120.257,-292.116)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(1.39597,0,0,1.25,-120.257,-97.3693)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434Z" style="fill:none;stroke:rgb(0,196,240);stroke-width:4.12px;"/>
+ </g>
+ <g transform="matrix(4.71406,0,0,5.89258,191.129,453.357)">
+ <path d="M8.977,8.086L9.507,7.554C9.727,7.336 9.727,6.98 9.507,6.759L7.248,4.5L9.505,2.238C9.725,2.02 9.725,1.664 9.505,1.444L8.975,0.914C8.757,0.694 8.401,0.694 8.18,0.914L4.993,4.102C4.773,4.322 4.773,4.678 4.995,4.898L8.183,8.086C8.401,8.306 8.757,8.306 8.977,8.086ZM4.475,8.086L5.005,7.556C5.225,7.336 5.225,6.98 5.005,6.762L2.748,4.5L5.007,2.241C5.227,2.02 5.227,1.664 5.007,1.446L4.477,0.914C4.257,0.694 3.901,0.694 3.683,0.914L0.495,4.102C0.273,4.322 0.273,4.678 0.493,4.898L3.68,8.086C3.901,8.306 4.257,8.306 4.475,8.086Z" style="fill:rgb(0,196,240);fill-rule:nonzero;"/>
+ </g>
+ </g>
+ </g>
+ <g id="dark" transform="matrix(1,0,0,1.25,400,1.13864e-12)">
+ <clipPath id="_clip3">
+ <rect x="0" y="-0" width="800" height="480"/>
+ </clipPath>
+ <g clip-path="url(#_clip3)">
+ <g transform="matrix(1.81105,0,0,1,0,0)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(7,5,44);"/>
+ </g>
+ <g transform="matrix(1.81105,0,0,0.30498,-1.13687e-13,190.035)">
+ <rect x="0" y="0" width="138.81" height="600" style="fill:rgb(25,21,68);"/>
+ </g>
+ <g transform="matrix(0.77088,0,0,0.290377,-644.149,483.662)">
+ <rect x="835.602" y="-1337.3" width="326.111" height="326.111" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g>
+ <path d="M251.392,110.852L220.365,141.879L251.392,172.906L251.392,353.271L188.167,353.271C182.556,353.271 178.008,357.819 178.008,363.43L178.008,416.496C178.008,422.107 182.556,426.655 188.167,426.655L251.392,426.655L251.392,600L800,600L800,-0L251.392,-0L251.392,110.852Z" style="fill:rgb(39,46,58);"/>
+ <path d="M251.392,110.852L220.365,141.879L251.392,172.906L251.392,353.271L188.167,353.271C182.556,353.271 178.008,357.819 178.008,363.43L178.008,416.496C178.008,422.107 182.556,426.655 188.167,426.655L251.392,426.655L251.392,600L800,600L800,-0L251.392,-0L251.392,110.852Z" style="fill:rgb(39,46,58);"/>
+ </g>
+ <g transform="matrix(4.8965,0,0,4.8965,6277.58,-3930.98)">
+ <path d="M-1178.23,822.298C-1178.23,819.787 -1179.23,817.378 -1181,815.602C-1182.78,813.826 -1185.19,812.828 -1187.7,812.828C-1192.51,812.828 -1198.34,812.828 -1203.16,812.828C-1205.67,812.828 -1208.08,813.826 -1209.85,815.602C-1211.63,817.378 -1212.63,819.787 -1212.63,822.298C-1212.63,822.299 -1212.63,822.3 -1212.63,822.301C-1212.63,824.813 -1211.63,827.222 -1209.85,828.997C-1208.08,830.773 -1205.67,831.771 -1203.16,831.771C-1198.34,831.771 -1192.51,831.771 -1187.7,831.771C-1185.19,831.771 -1182.78,830.773 -1181,828.997C-1179.23,827.222 -1178.23,824.813 -1178.23,822.301C-1178.23,822.3 -1178.23,822.299 -1178.23,822.298Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(1.39597,0,0,1,-120.257,-227.628)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534L344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(1.39597,0,0,1,-120.257,-71.831)">
+ <path d="M690.792,421.434C690.792,409.891 684.089,400.534 675.821,400.534C617.133,400.534 403.105,400.534 344.417,400.534C336.149,400.534 329.446,409.891 329.446,421.434L329.446,502.154C329.446,513.697 336.149,523.054 344.417,523.054L675.821,523.054C684.089,523.054 690.792,513.697 690.792,502.154L690.792,421.434ZM687.21,421.434L687.21,502.154C687.21,510.935 682.111,518.054 675.821,518.054C675.821,518.054 344.417,518.054 344.417,518.054C338.127,518.054 333.028,510.935 333.028,502.154L333.028,421.434C333.028,412.653 338.127,405.534 344.417,405.534C403.105,405.534 617.133,405.534 675.821,405.534C682.111,405.534 687.21,412.653 687.21,421.434Z" style="fill:rgb(0,196,240);"/>
+ </g>
+ <g transform="matrix(4.71406,0,0,4.71406,191.129,368.75)">
+ <path d="M8.977,8.086L9.507,7.554C9.727,7.336 9.727,6.98 9.507,6.759L7.248,4.5L9.505,2.238C9.725,2.02 9.725,1.664 9.505,1.444L8.975,0.914C8.757,0.694 8.401,0.694 8.18,0.914L4.993,4.102C4.773,4.322 4.773,4.678 4.995,4.898L8.183,8.086C8.401,8.306 8.757,8.306 8.977,8.086ZM4.475,8.086L5.005,7.556C5.225,7.336 5.225,6.98 5.005,6.762L2.748,4.5L5.007,2.241C5.227,2.02 5.227,1.664 5.007,1.446L4.477,0.914C4.257,0.694 3.901,0.694 3.683,0.914L0.495,4.102C0.273,4.322 0.273,4.678 0.493,4.898L3.68,8.086C3.901,8.306 4.257,8.306 4.475,8.086Z" style="fill:rgb(0,196,240);fill-rule:nonzero;"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/public/img/touch-icon.png b/public/img/touch-icon.png
new file mode 100644
index 0000000..c5d5fdd
--- /dev/null
+++ b/public/img/touch-icon.png
Binary files differ
diff --git a/public/img/tree/tree-minus.gif b/public/img/tree/tree-minus.gif
new file mode 100644
index 0000000..551817e
--- /dev/null
+++ b/public/img/tree/tree-minus.gif
Binary files differ
diff --git a/public/img/tree/tree-plus.gif b/public/img/tree/tree-plus.gif
new file mode 100644
index 0000000..72446cc
--- /dev/null
+++ b/public/img/tree/tree-plus.gif
Binary files differ
diff --git a/public/img/website-icon.svg b/public/img/website-icon.svg
new file mode 100644
index 0000000..6ca6ee5
--- /dev/null
+++ b/public/img/website-icon.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
+ <path d="M8.37,2.58c-0.126,-0.074 -0.241,-0.171 -0.339,-0.29c-0.436,-0.535 -0.356,-1.323 0.179,-1.759c0.535,-0.436 1.323,-0.355 1.759,0.179c0.436,0.535 0.356,1.323 -0.179,1.759c-0.276,0.225 -0.619,0.312 -0.947,0.271l-1.112,2.75c0.369,0.169 0.705,0.423 0.979,0.758c0.236,0.29 0.403,0.614 0.502,0.952l3.299,-1.008c-0.003,-0.022 -0.005,-0.044 -0.007,-0.067c-0.062,-0.825 0.557,-1.545 1.384,-1.608c0.825,-0.062 1.545,0.557 1.608,1.384c0.062,0.826 -0.558,1.546 -1.384,1.608c-0.63,0.047 -1.199,-0.302 -1.46,-0.838l-3.343,1.022c0.091,0.885 -0.251,1.797 -0.991,2.402c-0.031,0.024 -0.062,0.049 -0.093,0.072l0.629,1.051c0.292,-0.211 0.662,-0.317 1.048,-0.273c0.823,0.095 1.412,0.841 1.317,1.662c-0.097,0.823 -0.839,1.412 -1.662,1.319c-0.823,-0.096 -1.413,-0.84 -1.317,-1.663c0.029,-0.251 0.118,-0.481 0.252,-0.676l-0.694,-1.159c-0.806,0.397 -1.753,0.367 -2.526,-0.058l-1.839,2.234c0.316,0.324 0.525,0.758 0.561,1.246c0.083,1.101 -0.742,2.062 -1.844,2.144c-1.102,0.083 -2.062,-0.743 -2.144,-1.845c-0.083,-1.101 0.742,-2.061 1.845,-2.143c0.434,-0.033 0.847,0.075 1.192,0.287l1.814,-2.204c-0.138,-0.114 -0.268,-0.243 -0.385,-0.388c-0.805,-0.986 -0.806,-2.364 -0.086,-3.344l-1.849,-1.514c-0.031,0.02 -0.064,0.038 -0.098,0.055c-0.496,0.243 -1.094,0.037 -1.337,-0.459c-0.243,-0.495 -0.037,-1.094 0.459,-1.337c0.496,-0.243 1.094,-0.037 1.337,0.458c0.147,0.302 0.129,0.642 -0.019,0.917l1.839,1.506c0.046,-0.044 0.095,-0.086 0.145,-0.127c0.696,-0.567 1.587,-0.735 2.397,-0.531l1.11,-2.745Z" />
+</svg>
diff --git a/public/img/winter/logo_icinga_big_winter.png b/public/img/winter/logo_icinga_big_winter.png
new file mode 100644
index 0000000..9fbabd3
--- /dev/null
+++ b/public/img/winter/logo_icinga_big_winter.png
Binary files differ
diff --git a/public/img/winter/snow1.png b/public/img/winter/snow1.png
new file mode 100644
index 0000000..034f5b5
--- /dev/null
+++ b/public/img/winter/snow1.png
Binary files differ
diff --git a/public/img/winter/snow2.png b/public/img/winter/snow2.png
new file mode 100644
index 0000000..325487b
--- /dev/null
+++ b/public/img/winter/snow2.png
Binary files differ
diff --git a/public/img/winter/snow3.png b/public/img/winter/snow3.png
new file mode 100644
index 0000000..5f40f16
--- /dev/null
+++ b/public/img/winter/snow3.png
Binary files differ
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..39a7891
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,4 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+require_once dirname(__DIR__) . '/library/Icinga/Application/webrouter.php';
diff --git a/public/js/define.js b/public/js/define.js
new file mode 100644
index 0000000..a3ce8c6
--- /dev/null
+++ b/public/js/define.js
@@ -0,0 +1,118 @@
+/*! Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+(function(window) {
+
+ 'use strict';
+
+ /**
+ * Provide a reference to be later required by foreign code
+ *
+ * @param {string} name Optional, defaults to the name (and path) of the file
+ * @param {string[]} requirements Optional, list of required references, may be relative if from the same package
+ * @param {function} factory Required, function that accepts as many params as there are requirements and that
+ * produces a value to be referenced
+ */
+ var define = function (name, requirements, factory) {
+ define.defines[name] = {
+ requirements: requirements,
+ factory: factory,
+ ref: null
+ }
+
+ define.resolve(name);
+ }
+
+ /**
+ * Return whether the given name references a value
+ *
+ * @param {string} name The absolute name of the reference
+ * @return {boolean}
+ */
+ define.has = function (name) {
+ return name in define.defines && define.defines[name]['ref'] !== null;
+ }
+
+ /**
+ * Get the value of a reference
+ *
+ * @param {string} name The absolute name of the reference
+ * @return {*}
+ */
+ define.get = function (name) {
+ return define.defines[name]['ref'];
+ }
+
+ /**
+ * Set the value of a reference
+ *
+ * @param {string} name The absolute name of the reference
+ * @param {*} ref The value to reference
+ */
+ define.set = function (name, ref) {
+ define.defines[name]['ref'] = ref;
+ }
+
+ /**
+ * Resolve a reference and, if successful, dependent references
+ *
+ * @param {string} name The absolute name of the reference
+ * @return {boolean}
+ */
+ define.resolve = function (name) {
+ var requirements = define.defines[name]['requirements'];
+
+ var exports, ref;
+ var requiredRefs = [];
+ for (var i = 0; i < requirements.length; i++) {
+ if (define.has(requirements[i])) {
+ ref = define.get(requirements[i]);
+ } else if (requirements[i] === 'exports') {
+ exports = ref = {};
+ } else {
+ return false;
+ }
+
+ requiredRefs.push(ref);
+ }
+
+ var factory = define.defines[name]['factory'];
+ var resolved = factory.apply(null, requiredRefs);
+
+ if (typeof exports === 'object') {
+ if (typeof resolved !== 'undefined') {
+ throw new Error('Factory for ' + name + ' returned, although exports were populated');
+ }
+
+ resolved = exports;
+ }
+
+ define.set(name, resolved);
+
+ for (var definedName in define.defines) {
+ if (define.defines[definedName]['requirements'].indexOf(name) >= 0) {
+ define.resolve(definedName);
+ }
+ }
+ }
+
+ /**
+ * Require a reference
+ *
+ * @param {string} name The absolute name of the reference
+ * @return {*}
+ */
+ var require = function(name) {
+ if (define.has(name)) {
+ return define.get(name);
+ }
+
+ throw new ReferenceError(name + ' is not defined');
+ }
+
+ define.icinga = true;
+ define.defines = {};
+
+ window.define = define;
+ window.require = require;
+
+})(window);
diff --git a/public/js/helpers.js b/public/js/helpers.js
new file mode 100644
index 0000000..bcb2736
--- /dev/null
+++ b/public/js/helpers.js
@@ -0,0 +1,91 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/* jQuery Plugins */
+(function ($) {
+
+ 'use strict';
+
+ /* Get data value or default */
+ $.fn.getData = function (name, fallback) {
+ var value = this.data(name);
+ if (typeof value !== 'undefined') {
+ return value;
+ }
+
+ return fallback;
+ };
+
+ /* Whether a HTML tag has a specific attribute */
+ $.fn.hasAttr = function(name) {
+ // We have inconsistent behaviour across browsers (false VS undef)
+ var val = this.attr(name);
+ return typeof val !== 'undefined' && val !== false;
+ };
+
+ /* Get class list */
+ $.fn.classes = function (callback) {
+
+ var classes = [];
+
+ $.each(this, function (i, el) {
+ var c = $(el).attr('class');
+ if (typeof c === 'string') {
+ $.each(c.split(/\s+/), function(i, p) {
+ if (classes.indexOf(p) === -1) {
+ classes.push(p);
+ }
+ });
+ }
+ });
+
+ if (typeof callback === 'function') {
+ for (var i in classes) {
+ if (classes.hasOwnProperty(i)) {
+ callback(classes[i]);
+ }
+ }
+ }
+
+ return classes;
+ };
+
+ /* Serialize form elements to an object */
+ $.fn.serializeObject = function()
+ {
+ var o = {};
+ var a = this.serializeArray();
+ $.each(a, function() {
+ if (o[this.name] !== undefined) {
+ if (!o[this.name].push) {
+ o[this.name] = [o[this.name]];
+ }
+ o[this.name].push(this.value || '');
+ } else {
+ o[this.name] = this.value || '';
+ }
+ });
+ return o;
+ };
+
+ $.fn.offsetTopRelativeTo = function($ancestor) {
+ if (typeof $ancestor === 'undefined') {
+ return false;
+ }
+
+ var el = this[0];
+ var offset = el.offsetTop;
+ var $parent = $(el.offsetParent);
+
+ if ($parent.is('body') || $parent.is($ancestor)) {
+ return offset;
+ }
+
+ if (el.tagName === 'TR') {
+ // TODO: Didn't found a better way, this will probably break sooner or later
+ return $parent.offsetTopRelativeTo($ancestor);
+ }
+
+ return offset + $parent.offsetTopRelativeTo($ancestor);
+ };
+
+})(jQuery);
diff --git a/public/js/icinga.js b/public/js/icinga.js
new file mode 100644
index 0000000..e8b8bcc
--- /dev/null
+++ b/public/js/icinga.js
@@ -0,0 +1,279 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga starts here.
+ *
+ * Usage example:
+ *
+ * <code>
+ * var icinga = new Icinga({
+ * baseUrl: '/icinga',
+ * });
+ * </code>
+ */
+(function(window, $) {
+
+ 'use strict';
+
+ var Icinga = function (config) {
+
+ this.initialized = false;
+
+ /**
+ * Our config object
+ */
+ this.config = config;
+
+ /**
+ * Icinga.Logger
+ */
+ this.logger = null;
+
+ /**
+ * Icinga.UI
+ */
+ this.ui = null;
+
+ /**
+ * Icinga.Loader
+ */
+ this.loader = null;
+
+ /**
+ * Icinga.Events
+ */
+ this.events = null;
+
+ /**
+ * Icinga.Timer
+ */
+ this.timer = null;
+
+ /**
+ * Icinga.History
+ */
+ this.history = null;
+
+ /**
+ * Icinga.Utils
+ */
+ this.utils = null;
+
+ /**
+ * Additional site behavior
+ */
+ this.behaviors = {};
+
+ /**
+ * Loaded modules
+ */
+ this.modules = {};
+
+ var _this = this;
+ $(document).ready(function () {
+ _this.initialize();
+ _this = null;
+ });
+ };
+
+ Icinga.prototype = {
+
+ /**
+ * Icinga startup, will be triggerd once the document is ready
+ */
+ initialize: function () {
+ if (this.initialized) {
+ return false;
+ }
+
+ this.timezone = new Icinga.Timezone();
+ this.utils = new Icinga.Utils(this);
+ this.logger = new Icinga.Logger(this);
+ this.timer = new Icinga.Timer(this);
+ this.ui = new Icinga.UI(this);
+ this.loader = new Icinga.Loader(this);
+ this.events = new Icinga.Events(this);
+ this.history = new Icinga.History(this);
+ var _this = this;
+ $.each(Icinga.Behaviors, function(name, Behavior) {
+ _this.behaviors[name.toLowerCase()] = new Behavior(_this);
+ });
+
+ this.timezone.initialize();
+ this.timer.initialize();
+ this.events.initialize();
+ this.history.initialize();
+ this.ui.initialize();
+ this.loader.initialize();
+
+ this.logger.info('Icinga is ready, running on jQuery ', $().jquery);
+ this.initialized = true;
+
+ // Trigger our own post-init event, `onLoad` is not reliable enough
+ $(document).trigger('icinga-init');
+ },
+
+ /**
+ * Load a given module by name
+ *
+ * @param {string} name
+ *
+ * @return {boolean}
+ */
+ loadModule: function (name) {
+
+ if (this.isLoadedModule(name)) {
+ this.logger.error('Cannot load module ' + name + ' twice');
+ return false;
+ }
+
+ if (! this.hasModule(name)) {
+ this.logger.error('Cannot find module ' + name);
+ return false;
+ }
+
+ this.modules[name] = new Icinga.Module(
+ this,
+ name,
+ Icinga.availableModules[name]
+ );
+ return true;
+ },
+
+ /**
+ * Whether a module matching the given name exists or is loaded
+ *
+ * @param {string} name
+ *
+ * @return {boolean}
+ */
+ hasModule: function (name) {
+ return this.isLoadedModule(name) ||
+ 'undefined' !== typeof Icinga.availableModules[name];
+ },
+
+ /**
+ * Return whether the given module is loaded
+ *
+ * @param {string} name The name of the module
+ *
+ * @returns {Boolean}
+ */
+ isLoadedModule: function (name) {
+ return 'undefined' !== typeof this.modules[name];
+ },
+
+ /**
+ * Ensure we have loaded the javascript code for a module
+ *
+ * @param {string} moduleName
+ */
+ ensureModule: function(moduleName) {
+ if (this.hasModule(moduleName) && ! this.isLoadedModule(moduleName)) {
+ this.loadModule(moduleName);
+ }
+ },
+
+ /**
+ * If a container contains sub-containers for other modules,
+ * make sure the javascript code for each module is loaded.
+ *
+ * Containers are identified by "data-icinga-module" which
+ * holds the module name.
+ *
+ * @param container
+ */
+ ensureSubModules: function (container) {
+ var icinga = this;
+
+ $(container).find('[data-icinga-module]').each(function () {
+ var moduleName = $(this).data('icingaModule');
+ if (moduleName) {
+ icinga.ensureModule(moduleName);
+ }
+ });
+ },
+
+ /**
+ * Get a module by name
+ *
+ * @param {string} name
+ *
+ * @return {object}
+ */
+ module: function (name) {
+
+ if (this.hasModule(name) && !this.isLoadedModule(name)) {
+ this.modules[name] = new Icinga.Module(
+ this,
+ name,
+ Icinga.availableModules[name]
+ );
+ }
+
+ return this.modules[name];
+ },
+
+ /**
+ * Clean up and unload all Icinga components
+ */
+ destroy: function () {
+
+ $.each(this.modules, function (name, module) {
+ module.destroy();
+ });
+
+ this.timezone.destroy();
+ this.timer.destroy();
+ this.events.destroy();
+ this.loader.destroy();
+ this.ui.destroy();
+ this.logger.debug('Icinga has been destroyed');
+ this.logger.destroy();
+ this.utils.destroy();
+
+ this.modules = [];
+ this.timer = this.events = this.loader = this.ui = this.logger =
+ this.utils = null;
+ this.initialized = false;
+ },
+
+ reload: function () {
+ setTimeout(function () {
+ var oldjQuery = window.jQuery;
+ var oldConfig = window.icinga.config;
+ var oldIcinga = window.Icinga;
+ window.icinga.destroy();
+ window.Icinga = undefined;
+ window.$ = undefined;
+ window.jQuery = undefined;
+ jQuery = undefined;
+ $ = undefined;
+
+ oldjQuery.getScript(
+ oldConfig.baseUrl.replace(/\/$/, '') + '/js/icinga.min.js'
+ ).done(function () {
+ var jQuery = window.jQuery;
+ window.icinga = new window.Icinga(oldConfig);
+ window.icinga.initialize();
+ window.icinga.ui.reloadCss();
+ oldjQuery = undefined;
+ oldConfig = undefined;
+ oldIcinga = undefined;
+ }).fail(function () {
+ window.jQuery = oldjQuery;
+ window.$ = window.jQuery;
+ window.Icinga = oldIcinga;
+ window.icinga = new Icinga(oldConfig);
+ window.icinga.ui.reloadCss();
+ });
+ }, 0);
+ }
+
+ };
+
+ window.Icinga = Icinga;
+
+ Icinga.availableModules = {};
+
+})(window, jQuery);
diff --git a/public/js/icinga/behavior/actiontable.js b/public/js/icinga/behavior/actiontable.js
new file mode 100644
index 0000000..0f914f7
--- /dev/null
+++ b/public/js/icinga/behavior/actiontable.js
@@ -0,0 +1,498 @@
+/* Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Behavior.ActionTable
+ *
+ * A multi selection that distincts between the table rows using the row action URL filter
+ */
+(function(Icinga, $) {
+
+ "use strict";
+
+ /**
+ * Remove one leading and trailing bracket and all text outside those brackets
+ *
+ * @param str {String}
+ * @returns {string}
+ */
+ var stripBrackets = function (str) {
+ return str.replace(/^[^\(]*\(/, '').replace(/\)[^\)]*$/, '');
+ };
+
+ /**
+ * Parse the filter query contained in the given url filter string
+ *
+ * @param filterString {String}
+ *
+ * @returns {Array} An object containing each row filter
+ */
+ var parseSelectionQuery = function(filterString) {
+ var selections = [];
+ $.each(stripBrackets(filterString).split('|'), function(i, row) {
+ var tuple = {};
+ $.each(stripBrackets(row).split('&'), function(i, keyValue) {
+ var s = keyValue.split('=');
+ tuple[s[0]] = decodeURIComponent(s[1]);
+ });
+ selections.push(tuple);
+ });
+ return selections;
+ };
+
+ /**
+ * Handle the selection of an action table
+ *
+ * @param table {HTMLElement} The table
+ * @param icinga {Icinga}
+ *
+ * @constructor
+ */
+ var Selection = function(table, icinga) {
+ this.$el = $(table);
+ this.icinga = icinga;
+ this.col = this.$el.closest('div.container').attr('id');
+
+ if (this.hasMultiselection()) {
+ if (! this.getMultiselectionKeys().length) {
+ icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
+ }
+ if (! this.getMultiselectionUrl()) {
+ icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
+ }
+ }
+ };
+
+ Selection.prototype = {
+
+ /**
+ * The container id in which this selection happens
+ */
+ col: null,
+
+ /**
+ * Return all rows as jQuery selector
+ *
+ * @returns {jQuery}
+ */
+ rows: function() {
+ return this.$el.find('tr');
+ },
+
+ /**
+ * Return all row action links as jQuery selector
+ *
+ * @returns {jQuery}
+ */
+ rowActions: function() {
+ return this.$el.find('tr[href]');
+ },
+
+ /**
+ * Return all selected rows as jQuery selector
+ *
+ * @returns {jQuery}
+ */
+ selections: function() {
+ return this.$el.find('tr.active');
+ },
+
+ /**
+ * If this selection allows selecting multiple rows
+ *
+ * @returns {Boolean}
+ */
+ hasMultiselection: function() {
+ return this.$el.hasClass('multiselect');
+ },
+
+ /**
+ * Return all filter keys that are significant when applying the selection
+ *
+ * @returns {Array}
+ */
+ getMultiselectionKeys: function() {
+ var data = this.$el.data('icinga-multiselect-data');
+ return (data && data.split(',')) || [];
+ },
+
+ /**
+ * Return the main target URL that is used when multi selecting rows
+ *
+ * This URL may differ from the url that is used when applying single rows
+ *
+ * @returns {String}
+ */
+ getMultiselectionUrl: function() {
+ return this.$el.data('icinga-multiselect-url');
+ },
+
+ /**
+ * Check whether the given url is
+ *
+ * @param {String} url
+ */
+ hasMultiselectionUrl: function(url) {
+ var urls = this.$el.data('icinga-multiselect-url').split(' ');
+
+ var related = this.$el.data('icinga-multiselect-controllers');
+ if (related && related.length) {
+ urls = urls.concat(this.$el.data('icinga-multiselect-controllers').split(' '));
+ }
+
+ var hasSelection = false;
+ $.each(urls, function (i, object) {
+ if (url.indexOf(object) === 0) {
+ hasSelection = true;
+ }
+ });
+ return hasSelection;
+ },
+
+ /**
+ * Read all filter data from the given row
+ *
+ * @param row {jQuery} The row element
+ *
+ * @returns {Object} An object containing all filter data in this row as key-value pairs
+ */
+ getRowData: function(row) {
+ var params = this.icinga.utils.parseUrl(row.attr('href')).params;
+ var tuple = {};
+ var keys = this.getMultiselectionKeys();
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ for (var j = 0; j < params.length; j++) {
+ if (params[j].key === key && (params[j].value || params[j].value === null)) {
+ tuple[key] = params[j].value ? decodeURIComponent(params[j].value): params[j].value;
+ break;
+ }
+ }
+ }
+ return tuple;
+ },
+
+ /**
+ * Deselect all selected rows
+ */
+ clear: function() {
+ this.selections().removeClass('active');
+ },
+
+ /**
+ * Add all rows that match the given filter to the selection
+ *
+ * @param filter {jQuery|Object} Either an object containing filter variables or the actual row to select
+ */
+ select: function(filter) {
+ if (filter instanceof jQuery) {
+ filter.addClass('active');
+ return;
+ }
+ var _this = this;
+ this.rowActions()
+ .filter(
+ function (i, el) {
+ return _this.icinga.utils.objectsEqual(_this.getRowData($(el)), filter);
+ }
+ )
+ .closest('tr')
+ .addClass('active');
+ },
+
+ /**
+ * Toggle the selection of the row between on and off
+ *
+ * @param row {jQuery} The row to toggle
+ */
+ toggle: function(row) {
+ row.toggleClass('active');
+ },
+
+ /**
+ * Add a new selection range to the closest table, using the selected row as
+ * range target.
+ *
+ * @param row {jQuery} The target of the selected range.
+ *
+ * @returns {boolean} If the selection was changed.
+ */
+ range: function(row) {
+ var from, to;
+ var selected = row.first().get(0);
+ this.rows().each(function(i, el) {
+ if ($(el).hasClass('active') || el === selected) {
+ if (!from) {
+ from = el;
+ }
+ to = el;
+ }
+ });
+ var inRange = false;
+ this.rows().each(function(i, el) {
+ if (el === from) {
+ inRange = true;
+ }
+ if (inRange) {
+ $(el).addClass('active');
+ }
+ if (el === to) {
+ inRange = false;
+ }
+ });
+ return false;
+ },
+
+ /**
+ * Select rows that target the given url
+ *
+ * @param url {String} The target url
+ */
+ selectUrl: function(url) {
+ var formerHref = this.$el.closest('.container').data('icinga-actiontable-former-href')
+
+ var $row = this.rows().filter('[href="' + url + '"]');
+
+ if ($row.length) {
+ this.clear();
+ $row.addClass('active');
+ } else {
+ if (this.col !== 'col2') {
+ // rows sometimes need to be displayed as active when related actions
+ // like command actions are being opened. Do not do this for col2, as it
+ // would always select the opened URL itself.
+ var $row = this.rows().filter('[href$="' + icinga.utils.parseUrl(url).query + '"]');
+ if ($row.length) {
+ this.clear();
+ $row.addClass('active');
+ } else {
+ var $row = this.rows().filter('[href$="' + formerHref + '"]');
+ if ($row.length) {
+ this.clear();
+ $row.addClass('active');
+ } else {
+ var tbl = this.$el;
+ if (ActionTable.prototype.tables(
+ tbl.closest('.dashboard').find('.container')).not(tbl).find('tr.active').length
+ ) {
+ this.clear();
+ }
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Convert all currently selected rows into an url query string
+ *
+ * @returns {String} The filter string
+ */
+ toQuery: function() {
+ var _this = this;
+ var selections = this.selections();
+ var queries = [];
+ var utils = this.icinga.utils;
+ if (selections.length === 1) {
+ return $(selections[0]).attr('href');
+ } else if (selections.length > 1 && _this.hasMultiselection()) {
+ selections.each(function (i, el) {
+ var parts = [];
+ $.each(_this.getRowData($(el)), function(key, value) {
+ var condition = utils.fixedEncodeURIComponent(key);
+ if (value !== null) {
+ condition += '=' + utils.fixedEncodeURIComponent(value);
+ }
+
+ parts.push(condition);
+ });
+ queries.push('(' + parts.join('&') + ')');
+ });
+ return _this.getMultiselectionUrl() + '?(' + queries.join('|') + ')';
+ } else {
+ return '';
+ }
+ },
+
+ /**
+ * Refresh the displayed active columns using the current page location
+ */
+ refresh: function() {
+ var hash = icinga.history.getCol2State().replace(/^#!/, '');
+ if (this.hasMultiselection()) {
+ var query = parseSelectionQuery(hash);
+ if (query.length > 1 && this.hasMultiselectionUrl(this.icinga.utils.parseUrl(hash).path)) {
+ this.clear();
+ // select all rows with matching filters
+ var _this = this;
+ $.each(query, function(i, selection) {
+ _this.select(selection);
+ });
+ }
+ if (query.length > 1) {
+ return;
+ }
+ }
+ this.selectUrl(hash);
+ }
+ };
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ var ActionTable = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+
+ /**
+ * If currently loading
+ *
+ * @var Boolean
+ */
+ this.loading = false;
+
+ this.on('rendered', '#main .container', this.onRendered, this);
+ this.on('beforerender', '#main .container', this.beforeRender, this);
+ this.on('click', 'table.action tr[href], table.table-row-selectable tr[href]', this.onRowClicked, this);
+ };
+ ActionTable.prototype = new Icinga.EventListener();
+
+ /**
+ * Return all active tables in this table, or in the context as jQuery selector
+ *
+ * @param context {HTMLElement}
+ * @returns {jQuery}
+ */
+ ActionTable.prototype.tables = function(context) {
+ if (context) {
+ return $(context).find('table.action, table.table-row-selectable');
+ }
+ return $('table.action, table.table-row-selectable');
+ };
+
+ /**
+ * Handle clicks on table rows and update selection and history
+ */
+ ActionTable.prototype.onRowClicked = function (event) {
+ var _this = event.data.self;
+ var $target = $(event.target);
+ var $tr = $(event.currentTarget);
+ var table = new Selection($tr.closest('table.action, table.table-row-selectable')[0], _this.icinga);
+
+ if ($tr.closest('[data-no-icinga-ajax]').length > 0) {
+ return true;
+ }
+
+ // some rows may contain form actions that trigger a different action, pass those through
+ if (!$target.hasClass('rowaction') && $target.closest('form').length &&
+ ($target.closest('a').length || // allow regular link clinks
+ $target.closest('button').length || // allow submitting forms
+ $target.closest('input').length || $target.closest('label').length)) { // allow selecting form elements
+ return;
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+
+ // update selection
+ if (table.hasMultiselection()) {
+ if (event.ctrlKey || event.metaKey) {
+ // add to selection
+ table.toggle($tr);
+ } else if (event.shiftKey) {
+ // range selection
+ table.range($tr);
+ } else {
+ // single selection
+ table.clear();
+ table.select($tr);
+ }
+ } else {
+ table.clear();
+ table.select($tr);
+ }
+
+ var count = table.selections().length;
+ if (count > 0) {
+ var query = table.toQuery();
+ _this.icinga.loader.loadUrl(query, _this.icinga.loader.getLinkTargetFor($tr));
+ } else {
+ if (_this.icinga.loader.getLinkTargetFor($tr).attr('id') === 'col2') {
+ _this.icinga.ui.layout1col();
+ }
+ }
+
+ // redraw all table selections
+ _this.tables().each(function () {
+ new Selection(this, _this.icinga).refresh();
+ });
+
+ // update selection info
+ $('.selection-info-count').text(count);
+ return false;
+ };
+
+ /**
+ * Render the selection and prepare selection rows
+ */
+ ActionTable.prototype.onRendered = function(evt) {
+ var container = evt.target;
+ var _this = evt.data.self;
+
+ if (evt.currentTarget !== container) {
+ // Nested containers are not processed multiple times
+ return;
+ }
+
+ // initialize all rows with the correct row action
+ $('table.action tr, table.table-row-selectable tr', container).each(function(idx, el) {
+
+ // decide which row action to use: links declared with the class rowaction take
+ // the highest precedence before hrefs defined in the tr itself and regular links
+ var $a = $('a[href].rowaction', el).first();
+ if ($a.length) {
+ $(el).attr('href', $a.attr('href'));
+ return;
+ }
+ if ($(el).attr('href') && $(el).attr('href').length) {
+ return;
+ }
+ $a = $('a[href]', el).first();
+ if ($a.length) {
+ $(el).attr('href', $a.attr('href'));
+ }
+ });
+
+ // draw all active selections that have disappeared on reload
+ _this.tables().each(function(i, el) {
+ new Selection(el, _this.icinga).refresh();
+ });
+
+ // update displayed selection counter
+ var table = new Selection(_this.tables(container).first());
+ $(container).find('.selection-info-count').text(table.selections().length);
+ };
+
+ ActionTable.prototype.beforeRender = function(evt) {
+ var container = evt.target;
+ var _this = evt.data.self;
+
+ if (evt.currentTarget !== container) {
+ // Nested containers are not processed multiple times
+ return;
+ }
+
+ var active = _this.tables().find('tr.active');
+ if (active.length) {
+ $(container).data('icinga-actiontable-former-href', active.attr('href'));
+ }
+ };
+
+ ActionTable.prototype.clearAll = function () {
+ var _this = this;
+ this.tables().each(function () {
+ new Selection(this, _this.icinga).clear();
+ });
+ $('.selection-info-count').text('0');
+ };
+
+ Icinga.Behaviors.ActionTable = ActionTable;
+
+}) (Icinga, jQuery);
diff --git a/public/js/icinga/behavior/application-state.js b/public/js/icinga/behavior/application-state.js
new file mode 100644
index 0000000..213c96c
--- /dev/null
+++ b/public/js/icinga/behavior/application-state.js
@@ -0,0 +1,40 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ var ApplicationState = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('rendered', '#layout', this.onRendered, this);
+ this.icinga = icinga;
+ };
+
+ ApplicationState.prototype = new Icinga.EventListener();
+
+ ApplicationState.prototype.onRendered = function(e) {
+ if (e.currentTarget !== e.target) {
+ // Nested containers are ignored
+ return;
+ }
+
+ if (! $('#application-state').length
+ && ! $('#login').length
+ && ! $('#guest-error').length
+ && ! $('#setup').length
+ ) {
+ var _this = e.data.self;
+
+ $('#layout').append(
+ '<div id="application-state" class="container" style="display: none" data-icinga-url="'
+ + _this.icinga.loader.baseUrl
+ + '/application-state" data-icinga-refresh="60"></div>'
+ );
+ }
+ };
+
+ Icinga.Behaviors.ApplicationState = ApplicationState;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/autofocus.js b/public/js/icinga/behavior/autofocus.js
new file mode 100644
index 0000000..e131d9e
--- /dev/null
+++ b/public/js/icinga/behavior/autofocus.js
@@ -0,0 +1,28 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ var Autofocus = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('rendered', this.onRendered, this);
+ };
+
+ Autofocus.prototype = new Icinga.EventListener();
+
+ Autofocus.prototype.onRendered = function(e) {
+ setTimeout(function() {
+ if (document.activeElement === e.target
+ || document.activeElement === document.body
+ ) {
+ e.data.self.icinga.ui.focusElement($(e.target).find('.autofocus'));
+ }
+ }, 0);
+ };
+
+ Icinga.Behaviors.Autofocus = Autofocus;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/collapsible.js b/public/js/icinga/behavior/collapsible.js
new file mode 100644
index 0000000..c0b5349
--- /dev/null
+++ b/public/js/icinga/behavior/collapsible.js
@@ -0,0 +1,463 @@
+/*! Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+;(function(Icinga) {
+
+ 'use strict';
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ let $ = window.$;
+
+ try {
+ $ = require('icinga/icinga-php-library/notjQuery');
+ } catch (e) {
+ console.warn('[Collapsible] notjQuery unavailable. Using jQuery for now');
+ }
+
+ /**
+ * Behavior for collapsible containers.
+ *
+ * @param icinga Icinga The current Icinga Object
+ */
+ class Collapsible extends Icinga.EventListener {
+ constructor(icinga) {
+ super(icinga);
+
+ this.on('layout-change', this.onLayoutChange, this);
+ this.on('rendered', '#main > .container, #modal-content', this.onRendered, this);
+ this.on('click', '.collapsible + .collapsible-control, .collapsible .collapsible-control',
+ this.onControlClicked, this);
+
+ this.icinga = icinga;
+ this.defaultVisibleRows = 2;
+ this.defaultVisibleHeight = 36;
+
+ this.state = new Icinga.Storage.StorageAwareMap.withStorage(
+ Icinga.Storage.BehaviorStorage('collapsible'),
+ 'expanded'
+ )
+ .on('add', this.onExpand, this)
+ .on('delete', this.onCollapse, this);
+ }
+
+ /**
+ * Initializes all collapsibles. Triggered on rendering of a container.
+ *
+ * @param event Event The `onRender` event triggered by the rendered container
+ */
+ onRendered(event) {
+ let _this = event.data.self,
+ toCollapse = [],
+ toExpand = [];
+
+ event.target.querySelectorAll('.collapsible').forEach(collapsible => {
+ // Assumes that any newly rendered elements are expanded
+ if (! ('canCollapse' in collapsible.dataset) && _this.canCollapse(collapsible)) {
+ if (_this.setupCollapsible(collapsible)) {
+ toCollapse.push([collapsible, _this.calculateCollapsedHeight(collapsible)]);
+ } else if (_this.isDetails(collapsible)) {
+ // Except if it's a <details> element, which may not be expanded by default
+ toExpand.push(collapsible);
+ }
+ }
+ });
+
+ // Elements are all collapsed in a row now, after height calculations are done.
+ // This avoids reflows since instantly collapsing an element will cause one if
+ // the height of the next element is being calculated.
+ for (const collapseInfo of toCollapse) {
+ _this.collapse(collapseInfo[0], collapseInfo[1]);
+ }
+
+ for (const collapsible of toExpand) {
+ _this.expand(collapsible);
+ }
+ }
+
+ /**
+ * Updates all collapsibles.
+ *
+ * @param event Event The `layout-change` event triggered by window resizing or column changes
+ */
+ onLayoutChange(event) {
+ let _this = event.data.self;
+ let toCollapse = [];
+
+ document.querySelectorAll('.collapsible').forEach(collapsible => {
+ if ('canCollapse' in collapsible.dataset) {
+ if (! _this.canCollapse(collapsible)) {
+ let toggleSelector = collapsible.dataset.toggleElement;
+ if (! this.isDetails(collapsible)) {
+ if (! toggleSelector) {
+ collapsible.nextElementSibling.remove();
+ } else {
+ let toggle = document.getElementById(toggleSelector);
+ if (toggle) {
+ toggle.classList.remove('collapsed');
+ delete toggle.dataset.canCollapse;
+ }
+ }
+ }
+
+ delete collapsible.dataset.canCollapse;
+ _this.expand(collapsible);
+ }
+ } else if (_this.canCollapse(collapsible) && _this.setupCollapsible(collapsible)) {
+ // It's expanded but shouldn't
+ toCollapse.push([collapsible, _this.calculateCollapsedHeight(collapsible)]);
+ }
+ });
+
+ setTimeout(function () {
+ for (const collapseInfo of toCollapse) {
+ _this.collapse(collapseInfo[0], collapseInfo[1]);
+ }
+ }, 0);
+ }
+
+ /**
+ * A collapsible got expanded in another window, try to apply this here as well
+ *
+ * @param {string} collapsiblePath
+ */
+ onExpand(collapsiblePath) {
+ let collapsible = document.querySelector(collapsiblePath);
+
+ if (collapsible && 'canCollapse' in collapsible.dataset) {
+ if ('stateCollapses' in collapsible.dataset) {
+ this.collapse(collapsible, this.calculateCollapsedHeight(collapsible));
+ } else {
+ this.expand(collapsible);
+ }
+ }
+ }
+
+ /**
+ * A collapsible got collapsed in another window, try to apply this here as well
+ *
+ * @param {string} collapsiblePath
+ */
+ onCollapse(collapsiblePath) {
+ let collapsible = document.querySelector(collapsiblePath);
+
+ if (collapsible && this.canCollapse(collapsible)) {
+ if ('stateCollapses' in collapsible.dataset) {
+ this.expand(collapsible);
+ } else {
+ this.collapse(collapsible, this.calculateCollapsedHeight(collapsible));
+ }
+ }
+ }
+
+ /**
+ * Event handler for toggling collapsibles. Switches the collapsed state of the respective container.
+ *
+ * @param event Event The `onClick` event triggered by the clicked collapsible-control element
+ */
+ onControlClicked(event) {
+ let _this = event.data.self,
+ target = event.currentTarget;
+
+ let collapsible = target.previousElementSibling;
+ if ('collapsibleAt' in target.dataset) {
+ collapsible = document.querySelector(target.dataset.collapsibleAt);
+ } else if (! collapsible) {
+ collapsible = target.closest('.collapsible');
+ }
+
+ if (! collapsible) {
+ _this.icinga.logger.error(
+ '[Collapsible] Collapsible control has no associated .collapsible: ', target);
+
+ return;
+ } else if ('noPersistence' in collapsible.dataset) {
+ if (collapsible.classList.contains('collapsed')) {
+ _this.expand(collapsible);
+ } else {
+ _this.collapse(collapsible, _this.calculateCollapsedHeight(collapsible));
+ }
+ } else {
+ let collapsiblePath = _this.icinga.utils.getCSSPath(collapsible),
+ stateCollapses = 'stateCollapses' in collapsible.dataset;
+
+ if (_this.state.has(collapsiblePath)) {
+ _this.state.delete(collapsiblePath);
+
+ if (stateCollapses) {
+ _this.expand(collapsible);
+ } else {
+ _this.collapse(collapsible, _this.calculateCollapsedHeight(collapsible));
+ }
+ } else {
+ _this.state.set(collapsiblePath);
+
+ if (stateCollapses) {
+ _this.collapse(collapsible, _this.calculateCollapsedHeight(collapsible));
+ } else {
+ _this.expand(collapsible);
+ }
+ }
+ }
+
+ if (_this.isDetails(collapsible)) {
+ // The browser handles these clicks as well, and would toggle the state again
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Setup the given collapsible
+ *
+ * @param collapsible The given collapsible container element
+ *
+ * @returns {boolean} Whether it needs to collapse or not
+ */
+ setupCollapsible(collapsible) {
+ if (this.isDetails(collapsible)) {
+ let summary = collapsible.querySelector(':scope > summary');
+ if (! summary.classList.contains('collapsible-control')) {
+ summary.classList.add('collapsible-control');
+ }
+
+ if (collapsible.open) {
+ collapsible.dataset.stateCollapses = '';
+ }
+ } else if (!! collapsible.dataset.toggleElement) {
+ let toggleSelector = collapsible.dataset.toggleElement,
+ toggle = collapsible.querySelector(toggleSelector),
+ externalToggle = false;
+ if (! toggle) {
+ if (collapsible.nextElementSibling && collapsible.nextElementSibling.matches(toggleSelector)) {
+ toggle = collapsible.nextElementSibling;
+ } else {
+ externalToggle = true;
+ toggle = document.getElementById(toggleSelector);
+ }
+ }
+
+ if (! toggle) {
+ if (externalToggle) {
+ this.icinga.logger.error(
+ '[Collapsible] External control with id `'
+ + toggleSelector
+ + '` not found for .collapsible',
+ collapsible
+ );
+ } else {
+ this.icinga.logger.error(
+ '[Collapsible] Control `' + toggleSelector + '` not found in .collapsible', collapsible);
+ }
+
+ return false;
+ } else if (externalToggle) {
+ collapsible.dataset.hasExternalToggle = '';
+
+ toggle.dataset.canCollapse = '';
+ toggle.dataset.collapsibleAt = this.icinga.utils.getCSSPath(collapsible);
+ $(toggle).on('click', e => {
+ // Only required as onControlClicked() is compatible with Icinga.EventListener
+ e.data = { self: this };
+ this.onControlClicked(e);
+ });
+ } else if (! toggle.classList.contains('collapsible-control')) {
+ toggle.classList.add('collapsible-control');
+ }
+ } else {
+ setTimeout(function () {
+ let collapsibleControl = document
+ .getElementById('collapsible-control-ghost')
+ .cloneNode(true);
+ collapsibleControl.removeAttribute('id');
+ collapsible.parentNode.insertBefore(collapsibleControl, collapsible.nextElementSibling);
+ }, 0);
+ }
+
+ collapsible.dataset.canCollapse = '';
+
+ if ('noPersistence' in collapsible.dataset) {
+ return ! ('stateCollapses' in collapsible.dataset);
+ }
+
+ if ('stateCollapses' in collapsible.dataset) {
+ return this.state.has(this.icinga.utils.getCSSPath(collapsible));
+ } else {
+ return ! this.state.has(this.icinga.utils.getCSSPath(collapsible));
+ }
+ }
+
+ /**
+ * Return an appropriate row element selector
+ *
+ * @param collapsible The given collapsible container element
+ *
+ * @returns {string}
+ */
+ getRowSelector(collapsible) {
+ if (!! collapsible.dataset.visibleHeight) {
+ return '';
+ }
+
+ if (collapsible.tagName === 'TABLE') {
+ return ':scope > tbody > tr';
+ } else if (collapsible.tagName === 'UL' || collapsible.tagName === 'OL') {
+ return ':scope > li:not(.collapsible-control)';
+ }
+
+ return '';
+ }
+
+ /**
+ * Check whether the given collapsible needs to collapse
+ *
+ * @param collapsible The given collapsible container element
+ *
+ * @returns {boolean}
+ */
+ canCollapse(collapsible) {
+ if (this.isDetails(collapsible)) {
+ return collapsible.querySelector(':scope > summary') !== null;
+ }
+
+ let rowSelector = this.getRowSelector(collapsible);
+ if (!! rowSelector) {
+ let visibleRows = Number(collapsible.dataset.visibleRows);
+ if (isNaN(visibleRows)) {
+ visibleRows = this.defaultVisibleRows;
+ } else if (visibleRows === 0) {
+ return true;
+ }
+
+ return collapsible.querySelectorAll(rowSelector).length > visibleRows * 2;
+ } else {
+ let maxHeight = Number(collapsible.dataset.visibleHeight);
+ if (isNaN(maxHeight)) {
+ maxHeight = this.defaultVisibleHeight;
+ } else if (maxHeight === 0) {
+ return true;
+ }
+
+ let actualHeight = collapsible.scrollHeight - parseFloat(
+ window.getComputedStyle(collapsible).getPropertyValue('padding-top')
+ );
+
+ return actualHeight >= maxHeight * 2;
+ }
+ }
+
+ /**
+ * Calculate the height the given collapsible should have when collapsed
+ *
+ * @param collapsible
+ */
+ calculateCollapsedHeight(collapsible) {
+ let height;
+
+ if (this.isDetails(collapsible)) {
+ return -1;
+ }
+
+ let rowSelector = this.getRowSelector(collapsible);
+ if (!! rowSelector) {
+ height = collapsible.scrollHeight;
+ height -= parseFloat(window.getComputedStyle(collapsible).getPropertyValue('padding-bottom'));
+
+ let visibleRows = Number(collapsible.dataset.visibleRows);
+ if (isNaN(visibleRows)) {
+ visibleRows = this.defaultVisibleRows;
+ }
+
+ let rows = Array.from(collapsible.querySelectorAll(rowSelector)).slice(visibleRows);
+ for (let i = 0; i < rows.length; i++) {
+ let row = rows[i];
+
+ if (row.previousElementSibling === null) { // very first element
+ height -= row.offsetHeight;
+ height -= parseFloat(window.getComputedStyle(row).getPropertyValue('margin-top'));
+ } else if (i < rows.length - 1) { // every element but the last one
+ let prevBottomBorderAt = row.previousElementSibling.offsetTop;
+ prevBottomBorderAt += row.previousElementSibling.offsetHeight;
+ height -= row.offsetTop - prevBottomBorderAt + row.offsetHeight;
+ } else { // the last element
+ height -= row.offsetHeight;
+ height -= parseFloat(window.getComputedStyle(row).getPropertyValue('margin-top'));
+ height -= parseFloat(window.getComputedStyle(row).getPropertyValue('margin-bottom'));
+ }
+ }
+ } else {
+ height = Number(collapsible.dataset.visibleHeight);
+ if (isNaN(height)) {
+ height = this.defaultVisibleHeight;
+ }
+
+ height += parseFloat(window.getComputedStyle(collapsible).getPropertyValue('padding-top'));
+
+ if (
+ !! collapsible.dataset.toggleElement
+ && ! ('hasExternalToggle' in collapsible.dataset)
+ && (! collapsible.nextElementSibling
+ || ! collapsible.nextElementSibling.matches(collapsible.dataset.toggleElement))
+ ) {
+ let toggle = collapsible.querySelector(collapsible.dataset.toggleElement);
+ height += toggle.offsetHeight; // TODO: Very expensive at times. (50ms+) Check why!
+ height += parseFloat(window.getComputedStyle(toggle).getPropertyValue('margin-top'));
+ height += parseFloat(window.getComputedStyle(toggle).getPropertyValue('margin-bottom'));
+ }
+ }
+
+ return height;
+ }
+
+ /**
+ * Collapse the given collapsible
+ *
+ * @param collapsible The given collapsible container element
+ * @param toHeight {int} The height in pixels to collapse to
+ */
+ collapse(collapsible, toHeight) {
+ if (this.isDetails(collapsible)) {
+ collapsible.open = false;
+ } else {
+ collapsible.style.cssText = 'display: block; height: ' + toHeight + 'px; padding-bottom: 0';
+
+ if ('hasExternalToggle' in collapsible.dataset) {
+ document.getElementById(collapsible.dataset.toggleElement).classList.add('collapsed');
+ }
+ }
+
+ collapsible.classList.add('collapsed');
+ }
+
+ /**
+ * Expand the given collapsible
+ *
+ * @param collapsible The given collapsible container element
+ */
+ expand(collapsible) {
+ collapsible.classList.remove('collapsed');
+
+ if (this.isDetails(collapsible)) {
+ collapsible.open = true;
+ } else {
+ collapsible.style.cssText = '';
+
+ if ('hasExternalToggle' in collapsible.dataset) {
+ document.getElementById(collapsible.dataset.toggleElement).classList.remove('collapsed');
+ }
+ }
+ }
+
+ /**
+ * Get whether the given collapsible is a <details> element
+ *
+ * @param collapsible
+ *
+ * @return {Boolean}
+ */
+ isDetails(collapsible) {
+ return collapsible.tagName === 'DETAILS';
+ }
+ }
+
+ Icinga.Behaviors.Collapsible = Collapsible;
+
+})(Icinga);
diff --git a/public/js/icinga/behavior/datetime-picker.js b/public/js/icinga/behavior/datetime-picker.js
new file mode 100644
index 0000000..fb0ddff
--- /dev/null
+++ b/public/js/icinga/behavior/datetime-picker.js
@@ -0,0 +1,222 @@
+/* Icinga Web 2 | (c) 2021 Icinga GmbH | GPLv2+ */
+
+/**
+ * DatetimePicker - Behavior for inputs that should show a date and time picker
+ */
+;(function(Icinga, $) {
+
+ 'use strict';
+
+ try {
+ var Flatpickr = require('icinga/icinga-php-library/vendor/flatpickr');
+ var notjQuery = require('icinga/icinga-php-library/notjQuery');
+ } catch (e) {
+ console.warn('Unable to provide datetime picker. Libraries not available:', e);
+ return;
+ }
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ /**
+ * Behavior for datetime pickers.
+ *
+ * @param icinga {Icinga} The current Icinga Object
+ */
+ var DatetimePicker = function(icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.icinga = icinga;
+
+ /**
+ * The formats the server expects
+ *
+ * In a syntax flatpickr understands. Based on https://flatpickr.js.org/formatting/
+ *
+ * @type {string}
+ */
+ this.server_full_format = 'Y-m-d\\TH:i:S';
+ this.server_date_format = 'Y-m-d';
+ this.server_time_format = 'H:i:S';
+
+ /**
+ * The flatpickr instances created
+ *
+ * @type {Map<Flatpickr, string>}
+ * @private
+ */
+ this._pickers = new Map();
+
+ this.on('rendered', '#main > .container, #modal-content', this.onRendered, this);
+ this.on('close-column', this.onCloseContainer, this);
+ this.on('close-modal', this.onCloseContainer, this);
+ };
+
+ DatetimePicker.prototype = new Icinga.EventListener();
+
+ /**
+ * Add flatpickr widget on selected inputs
+ *
+ * @param event {Event}
+ */
+ DatetimePicker.prototype.onRendered = function(event) {
+ var _this = event.data.self;
+ var containerId = event.target.dataset.icingaContainerId;
+ var inputs = event.target.querySelectorAll('input[data-use-datetime-picker]');
+
+ // Cleanup left-over pickers from the previous content
+ _this.cleanupPickers(containerId);
+
+ $.each(inputs, function () {
+ if (this.type !== 'text') {
+ // Ignore native inputs. Browser widgets are (mostly) superior.
+ // TODO: This makes the type distinction below useless.
+ // Refactor this once we decided how we continue here in the future.
+ return;
+ }
+
+ var server_format = _this.server_full_format;
+ if (this.type === 'date') {
+ server_format = _this.server_date_format;
+ } else if (this.type === 'time') {
+ server_format = _this.server_time_format;
+ }
+
+ // Inject calendar container into a new empty div, inside the column/modal but outside the form.
+ // See https://github.com/flatpickr/flatpickr/issues/2054 for details.
+ var appendTo = document.createElement('div');
+ this.form.parentNode.insertBefore(appendTo, this.form.nextSibling);
+
+ var enableTime = server_format !== _this.server_date_format;
+ var disableDate = server_format === _this.server_time_format;
+ var dateTimeFormatter = _this.createFormatter(! disableDate, enableTime);
+ var options = {
+ locale: _this.loadFlatpickrLocale(),
+ appendTo: appendTo,
+ altInput: true,
+ enableTime: enableTime,
+ noCalendar: disableDate,
+ dateFormat: server_format,
+ formatDate: function (date, format, locale) {
+ return format === this.dateFormat
+ ? Flatpickr.formatDate(date, format, locale)
+ : dateTimeFormatter.format(date);
+ }
+ };
+
+ for (name in this.dataset) {
+ if (name.length > 9 && name.substr(0, 9) === 'flatpickr') {
+ var value = this.dataset[name];
+ if (value === '') {
+ value = true;
+ }
+
+ options[name.charAt(9).toLowerCase() + name.substr(10)] = value;
+ }
+ }
+
+ var element = this;
+ if (!! options.wrap) {
+ element = this.parentNode;
+ }
+
+ var fp = Flatpickr(element, options);
+ fp.calendarContainer.classList.add('icinga-datetime-picker');
+
+ if (! !!options.wrap) {
+ this.parentNode.insertBefore(_this.renderIcon(), fp.altInput.nextSibling);
+ }
+
+ _this._pickers.set(fp, containerId);
+ });
+ };
+
+ /**
+ * Cleanup all flatpickr instances in the closed container
+ *
+ * @param event {Event}
+ */
+ DatetimePicker.prototype.onCloseContainer = function (event) {
+ var _this = event.data.self;
+ var containerId = event.target.dataset.icingaContainerId;
+
+ _this.cleanupPickers(containerId);
+ };
+
+ /**
+ * Destroy all flatpickr instances in the container with the given id
+ *
+ * @param containerId {String}
+ */
+ DatetimePicker.prototype.cleanupPickers = function (containerId) {
+ this._pickers.forEach(function (cId, fp) {
+ if (cId === containerId) {
+ this._pickers.delete(fp);
+ fp.destroy();
+ }
+ }, this);
+ };
+
+ /**
+ * Close all other flatpickr instances and keep the given one
+ *
+ * @param fp {Flatpickr}
+ */
+ DatetimePicker.prototype.closePickers = function (fp) {
+ var containerId = this._pickers.get(fp);
+ this._pickers.forEach(function (cId, fp2) {
+ if (cId === containerId && fp2 !== fp) {
+ fp2.close();
+ }
+ }, this);
+ };
+
+ DatetimePicker.prototype.createFormatter = function (withDate, withTime) {
+ var options = {};
+ if (withDate) {
+ options.year = 'numeric';
+ options.month = 'numeric';
+ options.day = 'numeric';
+ }
+ if (withTime) {
+ options.hour = 'numeric';
+ options.minute = 'numeric';
+ options.timeZoneName = 'short';
+ options.timeZone = this.icinga.config.timezone;
+ }
+
+ return new Intl.DateTimeFormat([this.icinga.config.locale, 'en'], options);
+ };
+
+ DatetimePicker.prototype.loadFlatpickrLocale = function () {
+ switch (this.icinga.config.locale) {
+ case 'ar':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/ar').Arabic;
+ case 'de':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/de').German;
+ case 'es':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/es').Spanish;
+ case 'fi':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/fi').Finnish;
+ case 'fr':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/fr').French;
+ case 'it':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/it').Italian;
+ case 'ja':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/ja').Japanese;
+ case 'pt':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/pt').Portuguese;
+ case 'ru':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/ru').Russian;
+ case 'uk':
+ return require('icinga/icinga-php-library/vendor/flatpickr/l10n/uk').Ukrainian;
+ default:
+ return 'default';
+ }
+ };
+
+ DatetimePicker.prototype.renderIcon = function () {
+ return notjQuery.render('<i class="icon fa fa-calendar" role="image"></i>');
+ };
+
+ Icinga.Behaviors.DatetimePicker = DatetimePicker;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/detach.js b/public/js/icinga/behavior/detach.js
new file mode 100644
index 0000000..16fe157
--- /dev/null
+++ b/public/js/icinga/behavior/detach.js
@@ -0,0 +1,73 @@
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Behavior.Detach
+ *
+ * Detaches DOM elements before an auto-refresh and attaches them back afterwards
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ function Detach(icinga) {
+ Icinga.EventListener.call(this, icinga);
+ }
+
+ Detach.prototype = new Icinga.EventListener();
+
+ /**
+ * Mutates the HTML before it is placed in the DOM after a reload
+ *
+ * @param content {string} The content to be rendered
+ * @param $container {jQuery} The target container
+ * @param action {string} The URL that caused the reload
+ * @param autorefresh {bool} Whether the rendering is due to an auto-refresh
+ *
+ * @return {string|null} The content to be rendered or null, when nothing should be changed
+ */
+ Detach.prototype.renderHook = function(content, $container, action, autorefresh) {
+ // Exit early
+ if (! autorefresh) {
+ return content;
+ } else {
+ var containerId = $container.attr('id');
+
+ if (containerId === 'menu' || containerId === 'application-state') {
+ return content;
+ }
+ }
+
+ if (! $container.find('.detach:first').length) {
+ return content;
+ }
+
+ var $content = $('<div></div>').append(content);
+ var icinga = this.icinga;
+
+ $content.find('.detach').each(function() {
+ // Selector only works w/ IDs because it was initially built to work w/ absolute paths only
+ var $detachTarget = $(this);
+ var detachTargetId = $detachTarget.attr('id');
+ if (detachTargetId === undefined) {
+ return;
+ }
+
+ var selector = '#' + detachTargetId + ':first';
+ var $detachSource = $container.find(selector);
+
+ if ($detachSource.length) {
+ icinga.logger.debug('Detaching ' + selector);
+ $detachSource.detach();
+ $detachTarget.replaceWith($detachSource);
+ $detachTarget.remove();
+ }
+ });
+
+ return $content.html();
+ };
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ Icinga.Behaviors.Detach = Detach;
+
+}) (Icinga, jQuery);
diff --git a/public/js/icinga/behavior/dropdown.js b/public/js/icinga/behavior/dropdown.js
new file mode 100644
index 0000000..691e634
--- /dev/null
+++ b/public/js/icinga/behavior/dropdown.js
@@ -0,0 +1,66 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+;(function(Icinga, $) {
+
+ "use strict";
+
+ /**
+ * Toggle the CSS class active of the dropdown navigation item
+ *
+ * Called when the dropdown toggle has been activated via mouse or keyobard. This will expand/collpase the dropdown
+ * menu according to CSS.
+ *
+ * @param {object} e Event
+ */
+ function setActive(e) {
+ $(this).parent().toggleClass('active');
+ }
+
+ /**
+ * Clear active state of the dropdown navigation item when the mouse leaves the navigation item
+ *
+ * @param {object} e Event
+ */
+ function clearActive(e) {
+ $(this).removeClass('active');
+ }
+
+ /**
+ * Clear active state of the dropdown navigation item when the navigation items loses focus
+ *
+ * @param {object} e Event
+ */
+ function clearFocus(e) {
+ var $dropdown = $(this);
+ // Timeout is required to wait for the next element in the DOM to receive focus
+ setTimeout(function() {
+ if (! $.contains($dropdown[0], document.activeElement)) {
+ $dropdown.removeClass('active');
+ }
+ }, 10);
+ }
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ /**
+ * Behavior for dropdown navigation items
+ *
+ * The dropdown behavior listens for activity on dropdown navigation items for toggling the CSS class
+ * active on them. CSS is responsible for the expanded and collapsed state.
+ *
+ * @param {Icinga} icinga
+ *
+ * @constructor
+ */
+ var Dropdown = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('click', '.dropdown-nav-item > a', setActive, this);
+ this.on('mouseleave', '.dropdown-nav-item', clearActive, this);
+ this.on('focusout', '.dropdown-nav-item', clearFocus, this);
+ };
+
+ Dropdown.prototype = new Icinga.EventListener();
+
+ Icinga.Behaviors.Dropdown = Dropdown;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/filtereditor.js b/public/js/icinga/behavior/filtereditor.js
new file mode 100644
index 0000000..ffcad01
--- /dev/null
+++ b/public/js/icinga/behavior/filtereditor.js
@@ -0,0 +1,77 @@
+/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Behavior.FilterEditor
+ *
+ * Initially expanded, but collapsable subtrees
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ var containerId = /^col(\d+)$/;
+ var filterEditors = {};
+
+ function FilterEditor(icinga) {
+ Icinga.EventListener.call(this, icinga);
+
+ this.on('beforerender', '#main > .container', this.beforeRender, this);
+ this.on('rendered', '#main > .container', this.onRendered, this);
+ }
+
+ FilterEditor.prototype = new Icinga.EventListener();
+
+ FilterEditor.prototype.beforeRender = function(event) {
+ if (event.currentTarget !== event.target) {
+ // Nested containers are ignored
+ return;
+ }
+
+ var $container = $(event.target);
+ var match = containerId.exec($container.attr('id'));
+
+ if (match !== null) {
+ var id = match[1];
+ var subTrees = {};
+ filterEditors[id] = subTrees;
+
+ $container.find('.tree .handle').each(function () {
+ var $li = $(this).closest('li');
+
+ subTrees[$li.find('select').first().attr('name')] = $li.hasClass('collapsed');
+ });
+ }
+ };
+
+ FilterEditor.prototype.onRendered = function(event) {
+ if (event.currentTarget !== event.target) {
+ // Nested containers are ignored
+ return;
+ }
+
+ var $container = $(event.target);
+ var match = containerId.exec($container.attr('id'));
+
+ if (match !== null) {
+ var id = match[1];
+
+ if (typeof filterEditors[id] !== "undefined") {
+ var subTrees = filterEditors[id];
+ delete filterEditors[id];
+
+ $container.find('.tree .handle').each(function () {
+ var $li = $(this).closest('li');
+ var name = $li.find('select').first().attr('name');
+ if (typeof subTrees[name] !== "undefined" && subTrees[name] !== $li.hasClass('collapsed')) {
+ $li.toggleClass('collapsed');
+ }
+ });
+ }
+ }
+ };
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ Icinga.Behaviors.FilterEditor = FilterEditor;
+
+}) (Icinga, jQuery);
diff --git a/public/js/icinga/behavior/flyover.js b/public/js/icinga/behavior/flyover.js
new file mode 100644
index 0000000..207d577
--- /dev/null
+++ b/public/js/icinga/behavior/flyover.js
@@ -0,0 +1,85 @@
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Behavior.Flyover
+ *
+ * A toggleable flyover
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ var expandedFlyovers = {};
+
+ function Flyover(icinga) {
+ Icinga.EventListener.call(this, icinga);
+
+ this.on('rendered', '#main > .container', this.onRendered, this);
+ this.on('click', this.onClick, this);
+ this.on('click', '.flyover-toggle', this.onClickFlyoverToggle, this);
+ }
+
+ Flyover.prototype = new Icinga.EventListener();
+
+ Flyover.prototype.onRendered = function(event) {
+ // Re-expand expanded containers after an auto-refresh
+
+ $(event.target).find('.flyover').each(function() {
+ var $this = $(this);
+
+ if (typeof expandedFlyovers['#' + $this.attr('id')] !== 'undefined') {
+ var $container = $this.closest('.container');
+
+ if ($this.offset().left - $container.offset().left > $container.innerWidth() / 2) {
+ $this.addClass('flyover-right');
+ }
+
+ $this.toggleClass('flyover-expanded');
+ }
+ });
+ };
+
+ Flyover.prototype.onClick = function(event) {
+ // Close flyover on click outside the flyover
+ var $target = $(event.target);
+
+ if (! $target.closest('.flyover').length) {
+ var _this = event.data.self;
+ $.each(expandedFlyovers, function (id) {
+ _this.onClickFlyoverToggle({target: $('.flyover-toggle', id)[0]});
+ });
+ }
+ };
+
+ Flyover.prototype.onClickFlyoverToggle = function(event) {
+ var $flyover = $(event.target).closest('.flyover');
+
+ $flyover.toggleClass('flyover-expanded');
+
+ var $container = $flyover.closest('.container');
+ if ($flyover.hasClass('flyover-expanded')) {
+ if ($flyover.offset().left - $container.offset().left > $container.innerWidth() / 2) {
+ $flyover.addClass('flyover-right');
+ }
+
+ if ($flyover.is('[data-flyover-suspends-auto-refresh]')) {
+ $container[0].dataset.suspendAutorefresh = '';
+ }
+
+ expandedFlyovers['#' + $flyover.attr('id')] = null;
+ } else {
+ $flyover.removeClass('flyover-right');
+
+ if ($flyover.is('[data-flyover-suspends-auto-refresh]')) {
+ delete $container[0].dataset.suspendAutorefresh;
+ }
+
+ delete expandedFlyovers['#' + $flyover.attr('id')];
+ }
+ };
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ Icinga.Behaviors.Flyover = Flyover;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/form.js b/public/js/icinga/behavior/form.js
new file mode 100644
index 0000000..ca9db3b
--- /dev/null
+++ b/public/js/icinga/behavior/form.js
@@ -0,0 +1,96 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Controls behavior of form elements, depending reload and
+ */
+(function(Icinga, $) {
+
+ "use strict";
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ var Form = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('rendered', '.container', this.onRendered, this);
+
+ // store the modification state of all input fields
+ this.inputs = new WeakMap();
+ };
+ Form.prototype = new Icinga.EventListener();
+
+ /**
+ * @param event
+ */
+ Form.prototype.onRendered = function (event) {
+ var _this = event.data.self;
+ var container = event.target;
+
+ container.querySelectorAll('form input').forEach(function (input) {
+ if (! _this.inputs.has(input) && input.type !== 'hidden') {
+ _this.inputs.set(input, input.value);
+ _this.icinga.logger.debug('registering "' + input.value + '" as original input value');
+ }
+ });
+ };
+
+ /**
+ * Mutates the HTML before it is placed in the DOM after a reload
+ *
+ * @param content {String} The content to be rendered
+ * @param $container {jQuery} The target container where the html will be rendered in
+ * @param action {String} The action-url that caused the reload
+ * @param autorefresh {Boolean} Whether the rendering is due to an autoRefresh
+ * @param autoSubmit {Boolean} Whether the rendering is due to an autoSubmit
+ *
+ * @returns {string|NULL} The content to be rendered, or NULL, when nothing should be changed
+ */
+ Form.prototype.renderHook = function(content, $container, action, autorefresh, autoSubmit) {
+ if ($container.attr('id') === 'menu') {
+ var $search = $container.find('#search');
+ if ($search[0] === document.activeElement) {
+ return null;
+ }
+ if ($search.length) {
+ var $content = $('<div></div>').append(content);
+ $content.find('#search').attr('value', $search.val()).addClass('active');
+ return $content.html();
+ }
+ return content;
+ }
+
+ if (! autorefresh || autoSubmit) {
+ return content;
+ }
+
+ var _this = this;
+ var changed = false;
+ $container[0].querySelectorAll('form input').forEach(function (input) {
+ if (_this.inputs.has(input) && _this.inputs.get(input) !== input.value) {
+ changed = true;
+ _this.icinga.logger.debug(
+ '"' + _this.inputs.get(input) + '" was changed ("' + input.value + '") and aborts reload...'
+ );
+ }
+ });
+ if (changed) {
+ return null;
+ }
+
+ var origFocus = document.activeElement;
+ var containerId = $container.attr('id');
+ if ($container.has(origFocus).length
+ && $(origFocus).length
+ && ! $(origFocus).hasClass('autofocus')
+ && $(origFocus).closest('form').length
+ && $(origFocus).not(':input[type=button], :input[type=submit], :input[type=reset]').length
+ ) {
+ this.icinga.logger.debug('Not changing content for ' + containerId + ' form has focus');
+ return null;
+ }
+
+ return content;
+ };
+
+ Icinga.Behaviors.Form = Form;
+
+}) (Icinga, jQuery);
diff --git a/public/js/icinga/behavior/input-enrichment.js b/public/js/icinga/behavior/input-enrichment.js
new file mode 100644
index 0000000..1540941
--- /dev/null
+++ b/public/js/icinga/behavior/input-enrichment.js
@@ -0,0 +1,148 @@
+/* Icinga Web 2 | (c) 2020 Icinga GmbH | GPLv2+ */
+
+/**
+ * InputEnrichment - Behavior for forms with enriched inputs
+ */
+(function(Icinga) {
+
+ "use strict";
+
+ try {
+ var SearchBar = require('icinga/icinga-php-library/widget/SearchBar');
+ var SearchEditor = require('icinga/icinga-php-library/widget/SearchEditor');
+ var FilterInput = require('icinga/icinga-php-library/widget/FilterInput');
+ var TermInput = require('icinga/icinga-php-library/widget/TermInput');
+ var Completer = require('icinga/icinga-php-library/widget/Completer');
+ } catch (e) {
+ console.warn('Unable to provide input enrichments. Libraries not available:', e);
+ return;
+ }
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ /**
+ * @param icinga
+ * @constructor
+ */
+ let InputEnrichment = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+
+ this.on('beforerender', '#main > .container, #modal-content', this.onBeforeRender, this);
+ this.on('rendered', '#main > .container, #modal-content', this.onRendered, this);
+
+ /**
+ * Enriched inputs
+ *
+ * @type {WeakMap<object, SearchEditor|SearchBar|FilterInput|TermInput|Completer>}
+ * @private
+ */
+ this._enrichments = new WeakMap();
+
+ /**
+ * Cached enrichments
+ *
+ * Holds values only during the time between `beforerender` and `rendered`
+ *
+ * @type {{}}
+ * @private
+ */
+ this._cachedEnrichments = {};
+ };
+ InputEnrichment.prototype = new Icinga.EventListener();
+
+ /**
+ * @param data
+ */
+ InputEnrichment.prototype.update = function (data) {
+ var input = document.querySelector(data[0]);
+ if (input !== null && this._enrichments.has(input)) {
+ this._enrichments.get(input).updateTerms(data[1]);
+ }
+ };
+
+ /**
+ * @param event
+ * @param content
+ * @param action
+ * @param autorefresh
+ * @param scripted
+ */
+ InputEnrichment.prototype.onBeforeRender = function (event, content, action, autorefresh, scripted) {
+ if (! autorefresh) {
+ return;
+ }
+
+ let _this = event.data.self;
+ let inputs = event.target.querySelectorAll('[data-enrichment-type]');
+
+ // Remember current instances
+ inputs.forEach((input) => {
+ let enrichment = _this._enrichments.get(input);
+ if (enrichment) {
+ _this._cachedEnrichments[_this.icinga.utils.getDomPath(input).join(' > ')] = enrichment;
+ }
+ });
+ };
+
+ /**
+ * @param event
+ * @param autorefresh
+ * @param scripted
+ */
+ InputEnrichment.prototype.onRendered = function (event, autorefresh, scripted) {
+ let _this = event.data.self;
+ let container = event.target;
+
+ if (autorefresh) {
+ // Apply remembered instances
+ for (let inputPath in _this._cachedEnrichments) {
+ let enrichment = _this._cachedEnrichments[inputPath];
+ let input = container.querySelector(inputPath);
+ if (input !== null) {
+ enrichment.refresh(input);
+ _this._enrichments.set(input, enrichment);
+ } else {
+ enrichment.destroy();
+ }
+
+ delete _this._cachedEnrichments[inputPath];
+ }
+ }
+
+ // Create new instances
+ let inputs = container.querySelectorAll('[data-enrichment-type]');
+ inputs.forEach((input) => {
+ let enrichment = _this._enrichments.get(input);
+ if (! enrichment) {
+ switch (input.dataset.enrichmentType) {
+ case 'search-bar':
+ enrichment = (new SearchBar(input)).bind();
+ break;
+ case 'search-editor':
+ enrichment = (new SearchEditor(input)).bind();
+ break;
+ case 'filter':
+ enrichment = (new FilterInput(input)).bind();
+ enrichment.restoreTerms();
+
+ if (_this._enrichments.has(input.form)) {
+ _this._enrichments.get(input.form).setFilterInput(enrichment);
+ }
+
+ break;
+ case 'terms':
+ enrichment = (new TermInput(input)).bind();
+ enrichment.restoreTerms();
+ break;
+ case 'completion':
+ enrichment = (new Completer(input)).bind();
+ }
+
+ _this._enrichments.set(input, enrichment);
+ }
+ });
+ };
+
+ Icinga.Behaviors.InputEnrichment = InputEnrichment;
+
+})(Icinga);
diff --git a/public/js/icinga/behavior/modal.js b/public/js/icinga/behavior/modal.js
new file mode 100644
index 0000000..0083f76
--- /dev/null
+++ b/public/js/icinga/behavior/modal.js
@@ -0,0 +1,231 @@
+/*! Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+;(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ /**
+ * Behavior for modal dialogs.
+ *
+ * @param icinga {Icinga} The current Icinga Object
+ */
+ var Modal = function(icinga) {
+ Icinga.EventListener.call(this, icinga);
+
+ this.icinga = icinga;
+ this.$layout = $('#layout');
+ this.$ghost = $('#modal-ghost');
+
+ this.on('submit', '#modal form', this.onFormSubmit, this);
+ this.on('change', '#modal form select.autosubmit', this.onFormAutoSubmit, this);
+ this.on('change', '#modal form input.autosubmit', this.onFormAutoSubmit, this);
+ this.on('click', '[data-icinga-modal]', this.onModalToggleClick, this);
+ this.on('mousedown', '#layout > #modal', this.onModalLeave, this);
+ this.on('click', '.modal-header > button', this.onModalClose, this);
+ this.on('keydown', this.onKeyDown, this);
+ };
+
+ Modal.prototype = new Icinga.EventListener();
+
+ /**
+ * Event handler for toggling modals. Shows the link target in a modal dialog.
+ *
+ * @param event {Event} The `onClick` event triggered by the clicked modal-toggle element
+ * @returns {boolean}
+ */
+ Modal.prototype.onModalToggleClick = function(event) {
+ var _this = event.data.self;
+ var $a = $(event.currentTarget);
+ var url = $a.attr('href');
+ var $modal = _this.$ghost.clone();
+ var $urlTarget = _this.icinga.loader.getLinkTargetFor($a, false);
+
+ _this.modalOpener = event.currentTarget;
+
+ // Disable pointer events to block further function calls
+ _this.modalOpener.style.pointerEvents = 'none';
+
+ // Add showCompact, we don't want controls in a modal
+ url = _this.icinga.utils.addUrlFlag(url, 'showCompact');
+
+ // Set the toggle's base target on the modal to use it as redirect target
+ $modal.data('redirectTarget', $urlTarget);
+
+ // Final preparations, the id is required so that it's not `display:none` anymore
+ $modal.attr('id', 'modal');
+ _this.$layout.append($modal);
+
+ var req = _this.icinga.loader.loadUrl(url, $modal.find('#modal-content'));
+ req.addToHistory = false;
+ req.done(function () {
+ _this.setTitle($modal, req.$target.data('icingaTitle').replace(/\s::\s.*/, ''));
+ _this.show($modal);
+ _this.focus($modal);
+ });
+ req.fail(function (req, _, errorThrown) {
+ if (req.status >= 500) {
+ // Yes, that's done twice (by us and by the base fail handler),
+ // but `renderContentToContainer` does too many useful things..
+ _this.icinga.loader.renderContentToContainer(req.responseText, $urlTarget, req.action);
+ } else if (req.status > 0) {
+ var msg = $(req.responseText).find('.error-message').text();
+ if (msg && msg !== errorThrown) {
+ errorThrown += ': ' + msg;
+ }
+
+ _this.icinga.loader.createNotice('error', errorThrown);
+ }
+
+ _this.hide($modal);
+ });
+
+ return false;
+ };
+
+ /**
+ * Event handler for form submits within a modal.
+ *
+ * @param event {Event} The `submit` event triggered by a form within the modal
+ * @param $autoSubmittedBy {jQuery} The element triggering the auto submit, if any
+ * @returns {boolean}
+ */
+ Modal.prototype.onFormSubmit = function(event, $autoSubmittedBy) {
+ var _this = event.data.self;
+ var $form = $(event.currentTarget).closest('form');
+ var $modal = $form.closest('#modal');
+
+ var $button;
+ var $rememberedSubmittButton = $form.data('submitButton');
+ if (typeof $rememberedSubmittButton != 'undefined') {
+ if ($form.has($rememberedSubmittButton)) {
+ $button = $rememberedSubmittButton;
+ }
+ $form.removeData('submitButton');
+ }
+
+ var req = _this.icinga.loader.submitForm($form, $autoSubmittedBy, $button);
+ req.addToHistory = false;
+ req.$redirectTarget = $modal.data('redirectTarget');
+ req.done(function (data, textStatus, req) {
+ var title = req.getResponseHeader('X-Icinga-Title');
+ if (!! title) {
+ _this.setTitle($modal, decodeURIComponent(title).replace(/\s::\s.*/, ''));
+ }
+
+ if (req.getResponseHeader('X-Icinga-Redirect')) {
+ _this.hide($modal);
+ }
+ });
+
+ if (typeof $autoSubmittedBy === 'undefined') {
+ // otherwise the form is submitted several times by clicking the "Submit" button several times
+ $form.find('input[type=submit],button[type=submit],button:not([type])').prop('disabled', true);
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ return false;
+ };
+
+ /**
+ * Event handler for form auto submits within a modal.
+ *
+ * @param event {Event} The `change` event triggered by a form input within the modal
+ * @returns {boolean}
+ */
+ Modal.prototype.onFormAutoSubmit = function(event) {
+ return event.data.self.onFormSubmit(event, $(event.currentTarget));
+ };
+
+ /**
+ * Event handler for closing the modal. Closes it when the user clicks on the overlay.
+ *
+ * @param event {Event} The `click` event triggered by clicking on the overlay
+ */
+ Modal.prototype.onModalLeave = function(event) {
+ var _this = event.data.self;
+ var $target = $(event.target);
+
+ if ($target.is('#modal')) {
+ _this.hide($target);
+ }
+ };
+
+ /**
+ * Event handler for closing the modal. Closes it when the user clicks on the close button.
+ *
+ * @param event {Event} The `click` event triggered by clicking on the close button
+ */
+ Modal.prototype.onModalClose = function(event) {
+ var _this = event.data.self;
+
+ _this.hide($(event.currentTarget).closest('#modal'));
+ };
+
+ /**
+ * Event handler for closing the modal. Closes it when the user pushed ESC.
+ *
+ * @param event {Event} The `keydown` event triggered by pushing a key
+ */
+ Modal.prototype.onKeyDown = function(event) {
+ var _this = event.data.self;
+
+ if (! event.isDefaultPrevented() && event.key === 'Escape') {
+ let $modal = _this.$layout.children('#modal');
+ if ($modal.length) {
+ _this.hide($modal);
+ }
+ }
+ };
+
+ /**
+ * Make final preparations and add the modal to the DOM
+ *
+ * @param $modal {jQuery} The modal element
+ */
+ Modal.prototype.show = function($modal) {
+ $modal.addClass('active');
+ };
+
+ /**
+ * Set a title for the modal
+ *
+ * @param $modal {jQuery} The modal element
+ * @param title {string} The title
+ */
+ Modal.prototype.setTitle = function($modal, title) {
+ $modal.find('.modal-header > h1').html(title);
+ };
+
+ /**
+ * Focus the modal
+ *
+ * @param $modal {jQuery} The modal element
+ */
+ Modal.prototype.focus = function($modal) {
+ this.icinga.ui.focusElement($modal.find('.modal-window'));
+ };
+
+ /**
+ * Hide the modal and remove it from the DOM
+ *
+ * @param $modal {jQuery} The modal element
+ */
+ Modal.prototype.hide = function($modal) {
+ // Remove pointerEvent none style to make the button clickable again
+ this.modalOpener.style.pointerEvents = '';
+ this.modalOpener = null;
+
+ $modal.removeClass('active');
+ // Using `setTimeout` here to let the transition finish
+ setTimeout(function () {
+ $modal.find('#modal-content').trigger('close-modal');
+ $modal.remove();
+ }, 200);
+ };
+
+ Icinga.Behaviors.Modal = Modal;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/navigation.js b/public/js/icinga/behavior/navigation.js
new file mode 100644
index 0000000..df0e1e6
--- /dev/null
+++ b/public/js/icinga/behavior/navigation.js
@@ -0,0 +1,464 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+(function(Icinga, $) {
+
+ "use strict";
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ var Navigation = function (icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('click', '#menu a', this.linkClicked, this);
+ this.on('click', '#menu tr[href]', this.linkClicked, this);
+ this.on('rendered', '#menu', this.onRendered, this);
+ this.on('mouseenter', '#menu .primary-nav .nav-level-1 > .nav-item', this.showFlyoutMenu, this);
+ this.on('mouseleave', '#menu .primary-nav', this.hideFlyoutMenu, this);
+ this.on('click', '#toggle-sidebar', this.toggleSidebar, this);
+
+ this.on('click', '#menu .config-nav-item button', this.toggleConfigFlyout, this);
+ this.on('mouseenter', '#menu .config-menu .config-nav-item', this.showConfigFlyout, this);
+ this.on('mouseleave', '#menu .config-menu .config-nav-item', this.hideConfigFlyout, this);
+
+ this.on('keydown', '#menu .config-menu .config-nav-item', this.onKeyDown, this);
+
+ /**
+ * The DOM-Path of the active item
+ *
+ * @see getDomPath
+ *
+ * @type {null|Array}
+ */
+ this.active = null;
+
+ /**
+ * The menu
+ *
+ * @type {jQuery}
+ */
+ this.$menu = null;
+
+ /**
+ * Local storage
+ *
+ * @type {Icinga.Storage}
+ */
+ this.storage = Icinga.Storage.BehaviorStorage('navigation');
+
+ this.storage.setBackend(window.sessionStorage);
+
+ // Restore collapsed sidebar if necessary
+ if (this.storage.get('sidebar-collapsed')) {
+ $('#layout').addClass('sidebar-collapsed');
+ }
+ };
+
+ Navigation.prototype = new Icinga.EventListener();
+
+ /**
+ * Activate menu items if their class is set to active or if the current URL matches their link
+ *
+ * @param {Object} e Event
+ */
+ Navigation.prototype.onRendered = function(e) {
+ var _this = e.data.self;
+
+ _this.$menu = $(e.target);
+
+ if (! _this.active) {
+ // There is no stored menu item, therefore it is assumed that this is the first rendering
+ // of the navigation after the page has been opened.
+
+ // initialise the menu selected by the backend as active.
+ var $active = _this.$menu.find('li.active');
+ if ($active.length) {
+ $active.each(function() {
+ _this.setActiveAndSelected($(this));
+ });
+ } else {
+ // if no item is marked as active, try to select the menu from the current URL
+ _this.setActiveAndSelectedByUrl($('#col1').data('icingaUrl'));
+ }
+ }
+
+ _this.refresh();
+ };
+
+ /**
+ * Re-render the menu selection according to the current state
+ */
+ Navigation.prototype.refresh = function() {
+ // restore selection to current active element
+ if (this.active) {
+ var $el = $(this.icinga.utils.getElementByDomPath(this.active));
+ this.setActiveAndSelected($el);
+
+ /*
+ * Recreate the html content of the menu item to force the browser to update the layout, or else
+ * the link would only be visible as active after another click or page reload in Gecko and WebKit.
+ *
+ * fixes #7897
+ */
+ if ($el.is('li')) {
+ $el.html($el.html());
+ }
+ }
+ };
+
+ /**
+ * Handle a link click in the menu
+ *
+ * @param event
+ */
+ Navigation.prototype.linkClicked = function(event) {
+ var $a = $(this);
+ var href = $a.attr('href');
+ var _this = event.data.self;
+ var icinga = _this.icinga;
+
+ // Check for ctrl or cmd click to open new tab and don't unfold other menus
+ if (event.ctrlKey || event.metaKey) {
+ return false;
+ }
+
+ if (href.match(/#/)) {
+ // ...it may be a menu section without a dedicated link.
+ // Switch the active menu item:
+ _this.setActiveAndSelected($a);
+ } else {
+ _this.setActiveAndSelected($(event.target));
+ }
+
+ // update target url of the menu container to the clicked link
+ var $menu = $('#menu');
+ var menuDataUrl = icinga.utils.parseUrl($menu.data('icinga-url'));
+ menuDataUrl = icinga.utils.addUrlParams(menuDataUrl.path, { url: href });
+ $menu.data('icinga-url', menuDataUrl);
+ };
+
+ /**
+ * Activate a menu item based on the current URL
+ *
+ * Activate a menu item that is an exact match or fall back to items that match the base URL
+ *
+ * @param url {String} The url to match
+ */
+ Navigation.prototype.setActiveAndSelectedByUrl = function(url) {
+ var $menu = $('#menu');
+
+ if (! $menu.length) {
+ return;
+ }
+
+ // try to active the first item that has an exact URL match
+ this.setActiveAndSelected($menu.find('[href="' + url + '"]'));
+
+ // the url may point to the search field, which must be activated too
+ if (! this.active) {
+ this.setActiveAndSelected($menu.find('form[action="' + this.icinga.utils.parseUrl(url).path + '"]'));
+ }
+
+ // some urls may have custom filters which won't match any menu item, in that case search
+ // for a menu item that points to the base action without any filters
+ if (! this.active) {
+ this.setActiveAndSelected($menu.find('[href="' + this.icinga.utils.parseUrl(url).path + '"]').first());
+ }
+ };
+
+ /**
+ * Try to select a new URL by
+ *
+ * @param url
+ */
+ Navigation.prototype.trySetActiveAndSelectedByUrl = function(url) {
+ var active = this.active;
+ this.setActiveAndSelectedByUrl(url);
+
+ if (! this.active && active) {
+ this.setActiveAndSelected($(this.icinga.utils.getElementByDomPath(active)));
+ }
+ };
+
+ /**
+ * Remove all active elements
+ */
+ Navigation.prototype.clear = function() {
+ if (this.$menu) {
+ this.$menu.find('.active').removeClass('active');
+ }
+ };
+
+ /**
+ * Remove all selected elements
+ */
+ Navigation.prototype.clearSelected = function() {
+ if (this.$menu) {
+ this.$menu.find('.selected').removeClass('selected');
+ }
+ };
+
+ /**
+ * Select all menu items in the selector as active and unfold surrounding menus when necessary
+ *
+ * @param $item {jQuery} The jQuery selector
+ */
+ Navigation.prototype.select = function($item) {
+ // support selecting the url of the menu entry
+ var $input = $item.find('input');
+ $item = $item.closest('li');
+
+ if ($item.length) {
+ // select the current item
+ var $selectedMenu = $item.addClass('active');
+
+ // unfold the containing menu
+ var $outerMenu = $selectedMenu.parent().closest('li');
+ if ($outerMenu.length) {
+ $outerMenu.addClass('active');
+ }
+ } else if ($input.length) {
+ $input.addClass('active');
+ }
+ };
+
+ Navigation.prototype.setActiveAndSelected = function ($el) {
+ if ($el.length > 1) {
+ $el.each((key, el) => {
+ if (! this.active) {
+ this.setActiveAndSelected($(el));
+ }
+ });
+ } else if ($el.length) {
+ let parent = $el[0].closest('.nav-level-1 > .nav-item, .config-menu');
+
+ if ($el[0].offsetHeight || $el[0].offsetWidth || parent.offsetHeight || parent.offsetWidth) {
+ // It's either a visible menu item or a config menu item
+ this.setActive($el);
+ this.setSelected($el);
+ }
+ }
+ };
+
+ /**
+ * Change the active menu element
+ *
+ * @param $el {jQuery} A selector pointing to the active element
+ */
+ Navigation.prototype.setActive = function($el) {
+ this.clear();
+ this.select($el);
+ if ($el.closest('li')[0]) {
+ this.active = this.icinga.utils.getDomPath($el.closest('li')[0]);
+ } else if ($el.find('input')[0]) {
+ this.active = this.icinga.utils.getDomPath($el[0]);
+ } else {
+ this.active = null;
+ }
+ // TODO: push to history
+ };
+
+ Navigation.prototype.setSelected = function($el) {
+ this.clearSelected();
+ $el = $el.closest('li');
+
+ if ($el.length) {
+ $el.addClass('selected');
+ }
+ };
+
+ /**
+ * Reset the active element to nothing
+ */
+ Navigation.prototype.resetActive = function() {
+ this.clear();
+ this.active = null;
+ };
+
+ /**
+ * Reset the selected element to nothing
+ */
+ Navigation.prototype.resetSelected = function() {
+ this.clearSelected();
+ this.selected = null;
+ };
+
+ /**
+ * Show the fly-out menu
+ *
+ * @param e
+ */
+ Navigation.prototype.showFlyoutMenu = function(e) {
+ var $layout = $('#layout');
+
+ if ($layout.hasClass('minimal-layout')) {
+ return;
+ }
+
+ var $target = $(this);
+ var $flyout = $target.find('.nav-level-2');
+
+ if (! $flyout.length) {
+ $layout.removeClass('menu-hovered');
+ $target.siblings().not($target).removeClass('hover');
+ return;
+ }
+
+ var delay = 300;
+
+ if ($layout.hasClass('menu-hovered')) {
+ delay = 0;
+ }
+
+ setTimeout(function() {
+ try {
+ if (! $target.is(':hover')) {
+ return;
+ }
+ } catch(e) { /* Bypass because if IE8 */ }
+
+ $layout.addClass('menu-hovered');
+ $target.siblings().not($target).removeClass('hover');
+ $target.addClass('hover');
+
+ $flyout.css({
+ bottom: 'auto',
+ top: $target.offset().top + $target.outerHeight()
+ });
+
+ var rect = $flyout[0].getBoundingClientRect();
+
+ if (rect.y + rect.height > window.innerHeight) {
+ $flyout.css({
+ bottom: 0,
+ top: 'auto'
+ });
+ }
+ }, delay);
+ };
+
+ /**
+ * Hide the fly-out menu
+ *
+ * @param e
+ */
+ Navigation.prototype.hideFlyoutMenu = function(e) {
+ var $layout = $('#layout');
+ var $nav = $(e.currentTarget);
+ var $hovered = $nav.find('.nav-level-1 > .nav-item.hover');
+
+ if (! $hovered.length) {
+ $layout.removeClass('menu-hovered');
+
+ return;
+ }
+
+ setTimeout(function() {
+ try {
+ if ($hovered.is(':hover') || $nav.is(':hover')) {
+ return;
+ }
+ } catch(e) { /* Bypass because if IE8 */ };
+ $hovered.removeClass('hover');
+ $layout.removeClass('menu-hovered');
+ }, 600);
+ };
+
+ /**
+ * Collapse or expand sidebar
+ *
+ * @param {Object} e Event
+ */
+ Navigation.prototype.toggleSidebar = function(e) {
+ var _this = e.data.self;
+ var $layout = $('#layout');
+ $layout.toggleClass('sidebar-collapsed');
+ _this.storage.set('sidebar-collapsed', $layout.is('.sidebar-collapsed'));
+ $(window).trigger('resize');
+ };
+
+ /**
+ * Toggle config flyout visibility
+ *
+ * @param {Object} e Event
+ */
+ Navigation.prototype.toggleConfigFlyout = function(e) {
+ var _this = e.data.self;
+ if ($('#layout').is('.config-flyout-open')) {
+ _this.hideConfigFlyout(e);
+ } else {
+ _this.showConfigFlyout(e);
+ }
+ }
+
+ /**
+ * Hide config flyout
+ *
+ * @param {Object} e Event
+ */
+ Navigation.prototype.hideConfigFlyout = function(e) {
+ $('#layout').removeClass('config-flyout-open');
+ if (e.target) {
+ delete $(e.target).closest('.container')[0].dataset.suspendAutorefresh;
+ }
+ }
+
+ /**
+ * Show config flyout
+ *
+ * @param {Object} e Event
+ */
+ Navigation.prototype.showConfigFlyout = function(e) {
+ $('#layout').addClass('config-flyout-open');
+ $(e.target).closest('.container')[0].dataset.suspendAutorefresh = '';
+ }
+
+ /**
+ * Hide, config flyout when "Enter" key is pressed to follow `.flyout` nav item link
+ *
+ * @param {Object} e Event
+ */
+ Navigation.prototype.onKeyDown = function(e) {
+ var _this = e.data.self;
+
+ if (e.key == 'Enter' && $(document.activeElement).is('.flyout a')) {
+ _this.hideConfigFlyout(e);
+ }
+ }
+
+ /**
+ * Called when the history changes
+ *
+ * @param url The url of the new state
+ * @param data The active menu item of the new state
+ */
+ Navigation.prototype.onPopState = function (url, data) {
+ // 1. get selection data and set active menu
+ if (data) {
+ var active = this.icinga.utils.getElementByDomPath(data);
+ if (!active) {
+ this.logger.fail(
+ 'Could not restore active menu from history, path in DOM not found.',
+ data,
+ url
+ );
+ return;
+ }
+ this.setActiveAndSelected($(active))
+ } else {
+ this.resetActive();
+ this.resetSelected();
+ }
+ };
+
+ /**
+ * Called when the current state gets pushed onto the history, can return a value
+ * to be preserved as the current state
+ *
+ * @returns {null|Array} The currently active menu item
+ */
+ Navigation.prototype.onPushState = function () {
+ return this.active;
+ };
+
+ Icinga.Behaviors.Navigation = Navigation;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/behavior/selectable.js b/public/js/icinga/behavior/selectable.js
new file mode 100644
index 0000000..3f32840
--- /dev/null
+++ b/public/js/icinga/behavior/selectable.js
@@ -0,0 +1,49 @@
+/*! Icinga Web 2 | (c) 2015 Icinga Development Team | GPLv2+ */
+
+;(function(Icinga, $) {
+ 'use strict';
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ /**
+ * Select all contents from the target of the given event
+ *
+ * @param {object} e Event
+ */
+ function onSelect(e) {
+ var b = document.body,
+ r;
+ if (b.createTextRange) {
+ r = b.createTextRange();
+ r.moveToElementText(e.target);
+ r.select();
+ } else if (window.getSelection) {
+ var s = window.getSelection();
+ r = document.createRange();
+ r.selectNodeContents(e.target);
+ s.removeAllRanges();
+ s.addRange(r);
+ }
+ }
+
+ /**
+ * Behavior for text that is selectable via double click
+ *
+ * @param {Icinga} icinga
+ *
+ * @constructor
+ */
+ var Selectable = function(icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('rendered', this.onRendered, this);
+ };
+
+ $.extend(Selectable.prototype, new Icinga.EventListener(), {
+ onRendered: function(e) {
+ $(e.target).find('.selectable').on('dblclick', onSelect);
+ }
+ });
+
+ Icinga.Behaviors.Selectable = Selectable;
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/eventlistener.js b/public/js/icinga/eventlistener.js
new file mode 100644
index 0000000..678e775
--- /dev/null
+++ b/public/js/icinga/eventlistener.js
@@ -0,0 +1,78 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * EventListener contains event handlers and can bind / and unbind them from
+ * event emitting objects
+ */
+(function(Icinga, $) {
+
+ "use strict";
+
+ var EventListener = function (icinga) {
+ this.icinga = icinga;
+ this.handlers = [];
+ };
+
+ /**
+ * Add an handler to this EventLister
+ *
+ * @param evt {String} The name of the triggering event
+ * @param cond {String} The filter condition
+ * @param fn {Function} The event handler to execute
+ * @param scope {Object} The optional 'this' of the called function
+ */
+ EventListener.prototype.on = function(evt, cond, fn, scope) {
+ if (typeof cond === 'function') {
+ scope = fn;
+ fn = cond;
+ cond = 'body';
+ }
+ this.icinga.logger.debug('on: ' + evt + '(' + cond + ')');
+ this.handlers.push({ evt: evt, cond: cond, fn: fn, scope: scope });
+ };
+
+ /**
+ * Bind all listeners to the given event emitter
+ *
+ * All event handlers will be executed when the associated event is
+ * triggered on the given Emitter.
+ *
+ * @param emitter {String} An event emitter that supports the function
+ * 'on' to register listeners
+ */
+ EventListener.prototype.bind = function (emitter) {
+ var _this = this;
+
+ if (typeof emitter.jquery === 'undefined') {
+ emitter = $(emitter);
+ }
+
+ $.each(this.handlers, function(i, handler) {
+ _this.icinga.logger.debug('bind: ' + handler.evt + '(' + handler.cond + ')');
+ emitter.on(
+ handler.evt, handler.cond,
+ {
+ self: handler.scope || emitter,
+ icinga: _this.icinga
+ }, handler.fn
+ );
+ });
+ };
+
+ /**
+ * Unbind all listeners from the given event emitter
+ *
+ * @param emitter {String} An event emitter that supports the function
+ * 'off' to un-register listeners.
+ */
+ EventListener.prototype.unbind = function (emitter) {
+ var _this = this;
+ $.each(this.handlers, function(i, handler) {
+ _this.icinga.logger.debug('unbind: ' + handler.evt + '(' + handler.cond + ')');
+ emitter.off(handler.evt, handler.cond, handler.fn);
+ });
+ };
+
+ Icinga.EventListener = EventListener;
+
+}) (Icinga, jQuery);
diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js
new file mode 100644
index 0000000..186a41c
--- /dev/null
+++ b/public/js/icinga/events.js
@@ -0,0 +1,415 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Events
+ *
+ * Event handlers
+ */
+(function (Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Events = function (icinga) {
+ this.icinga = icinga;
+
+ this.searchValue = '';
+ this.searchTimer = null;
+ };
+
+ Icinga.Events.prototype = {
+
+ /**
+ * Icinga will call our initialize() function once it's ready
+ */
+ initialize: function () {
+ this.applyGlobalDefaults();
+ },
+
+ /**
+ * Global default event handlers
+ */
+ applyGlobalDefaults: function () {
+ $(document).on('visibilitychange', { self: this }, this.onVisibilityChange);
+
+ $.each(this.icinga.behaviors, function (name, behavior) {
+ behavior.bind($(document));
+ });
+
+ // Initialize module javascript (Applies only to module.js code)
+ this.icinga.ensureSubModules(document);
+
+ // We catch resize events
+ $(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize);
+
+ // Trigger 'rendered' event also on page loads
+ $(document).on('icinga-init', { self: this }, this.onInit);
+
+ // Destroy Icinga, clean up and interrupt pending requests on unload
+ $( window ).on('unload', { self: this }, this.onUnload);
+ $( window ).on('beforeunload', { self: this }, this.onUnload);
+
+ // Remove notifications on click
+ $(document).on('click', '#notifications li', function () { $(this).remove(); });
+
+ // We want to catch each link click
+ $(document).on('click', 'a', { self: this }, this.linkClicked);
+ $(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
+
+ $(document).on('click', 'input[type="submit"], button[type="submit"]', this.rememberSubmitButton);
+ // We catch all form submit events
+ $(document).on('submit', 'form', { self: this }, this.submitForm);
+
+ // We support an 'autosubmit' class on dropdown form elements
+ $(document).on('change', 'form select.autosubmit', { self: this }, this.autoSubmitForm);
+ $(document).on('change', 'form input.autosubmit', { self: this }, this.autoSubmitForm);
+
+ // Automatically check a radio button once a specific input is focused
+ $(document).on('focus', 'form select[data-related-radiobtn]', { self: this }, this.autoCheckRadioButton);
+ $(document).on('focus', 'form input[data-related-radiobtn]', { self: this }, this.autoCheckRadioButton);
+
+ $(document).on('rendered', '#menu', { self: this }, this.onRenderedMenu);
+ $(document).on('keyup', '#search', { self: this }, this.autoSubmitSearch);
+
+ $(document).on('click', '.tree .handle', { self: this }, this.treeNodeToggle);
+
+ $(document).on('click', '#search + .search-reset', this.clearSearch);
+
+ $(document).on('rendered', '.container', { self: this }, this.loadDashlets);
+
+ // TBD: a global autocompletion handler
+ // $(document).on('keyup', 'form.auto input', this.formChangeDelayed);
+ // $(document).on('change', 'form.auto input', this.formChanged);
+ // $(document).on('change', 'form.auto select', this.submitForm);
+ },
+
+ treeNodeToggle: function () {
+ var $parent = $(this).closest('li');
+ if ($parent.hasClass('collapsed')) {
+ $('li', $parent).addClass('collapsed');
+ $parent.removeClass('collapsed');
+ } else {
+ $parent.addClass('collapsed');
+ }
+ },
+
+ onInit: function (event) {
+ $('body').removeClass('loading');
+
+ // Trigger the initial `rendered` events
+ $('.container').trigger('rendered');
+
+ // Additionally trigger a `rendered` event on the layout, some behaviors may
+ // want to differentiate whether a container or the entire layout is rendered
+ $('#layout').trigger('rendered');
+ },
+
+ onUnload: function (event) {
+ var icinga = event.data.self.icinga;
+ icinga.logger.info('Unloading Icinga');
+ icinga.destroy();
+ },
+
+ onVisibilityChange: function (event) {
+ var icinga = event.data.self.icinga;
+
+ if (document.visibilityState === undefined || document.visibilityState === 'visible') {
+ icinga.loader.autorefreshSuspended = false;
+ icinga.logger.debug('Page visible, enabling auto-refresh');
+ } else {
+ icinga.loader.autorefreshSuspended = true;
+ icinga.logger.debug('Page invisible, disabling auto-refresh');
+ }
+ },
+
+ autoCheckRadioButton: function (event) {
+ var $input = $(event.currentTarget);
+ var $radio = $('#' + $input.attr('data-related-radiobtn'));
+ if ($radio.length) {
+ $radio.prop('checked', true);
+ }
+ return true;
+ },
+
+ onRenderedMenu: function(event) {
+ var _this = event.data.self;
+ var $target = $(event.target);
+
+ var $searchField = $target.find('input.search');
+ // Remember initial search field value if any
+ if ($searchField.length && $searchField.val().length) {
+ _this.searchValue = $searchField.val();
+ }
+ },
+
+ autoSubmitSearch: function(event) {
+ var _this = event.data.self;
+ var $searchField = $(event.target);
+
+ if ($searchField.val() === _this.searchValue) {
+ return;
+ }
+ _this.searchValue = $searchField.val();
+
+ if (_this.searchTimer !== null) {
+ clearTimeout(_this.searchTimer);
+ _this.searchTimer = null;
+ }
+ var _event = $.extend({}, event); // event seems gc'd once the timeout is over
+ _this.searchTimer = setTimeout(function () {
+ _this.submitForm(_event, $searchField);
+ _this.searchTimer = null;
+ }, 500);
+ },
+
+ loadDashlets: function(event) {
+ var _this = event.data.self;
+ var $target = $(event.target);
+
+ if ($target.children('.dashboard').length) {
+ $target.find('.dashboard > .container').each(function () {
+ var $dashlet = $(this);
+ var url = $dashlet.data('icingaUrl');
+ if (typeof url !== 'undefined') {
+ _this.icinga.loader.loadUrl(url, $dashlet).autorefresh = true;
+ }
+ });
+ }
+ },
+
+ rememberSubmitButton: function(e) {
+ var $button = $(this);
+ var $form = $button.closest('form');
+ $form.data('submitButton', $button);
+ },
+
+ autoSubmitForm: function (event) {
+ return event.data.self.submitForm(event, $(event.currentTarget));
+ },
+
+ /**
+ *
+ */
+ submitForm: function (event, $autoSubmittedBy) {
+ var _this = event.data.self;
+
+ // .closest is not required unless subelements to trigger this
+ var $form = $(event.currentTarget).closest('form');
+
+ if ($form.closest('[data-no-icinga-ajax]').length > 0) {
+ return true;
+ }
+
+ var $button;
+ var $rememberedSubmittButton = $form.data('submitButton');
+ if (typeof $rememberedSubmittButton != 'undefined') {
+ if ($form.has($rememberedSubmittButton)) {
+ $button = $rememberedSubmittButton;
+ }
+ $form.removeData('submitButton');
+ }
+
+ if (typeof $button === 'undefined') {
+ var $el;
+
+ if (typeof event.originalEvent !== 'undefined'
+ && typeof event.originalEvent.explicitOriginalTarget === 'object') { // Firefox
+ $el = $(event.originalEvent.explicitOriginalTarget);
+ _this.icinga.logger.debug('events/submitForm: Button is event.originalEvent.explicitOriginalTarget');
+ } else {
+ $el = $(event.currentTarget);
+ _this.icinga.logger.debug('events/submitForm: Button is event.currentTarget');
+ }
+
+ if ($el && ($el.is('input[type=submit]') || $el.is('button[type=submit]'))) {
+ $button = $el;
+ } else {
+ _this.icinga.logger.debug(
+ 'events/submitForm: Can not determine submit button, using the first one in form'
+ );
+ }
+ }
+
+ if (! $autoSubmittedBy && event.detail && event.detail.submittedBy) {
+ $autoSubmittedBy = $(event.detail.submittedBy);
+ }
+
+ _this.icinga.loader.submitForm($form, $autoSubmittedBy, $button);
+
+ event.stopPropagation();
+ event.preventDefault();
+ return false;
+ },
+
+ handleExternalTarget: function($node) {
+ var linkTarget = $node.attr('target');
+
+ // TODO: Let remote links pass through. Right now they only work
+ // combined with target="_blank" or target="_self"
+ // window.open is used as return true; didn't work reliable
+ if (linkTarget === '_blank' || linkTarget === '_self') {
+ window.open($node.attr('href'), linkTarget);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Someone clicked a link or tr[href]
+ */
+ linkClicked: function (event) {
+ var _this = event.data.self;
+ var icinga = _this.icinga;
+ var $a = $(this);
+ var $eventTarget = $(event.target);
+ var href = $a.attr('href');
+ var linkTarget = $a.attr('target');
+ var $target;
+ var formerUrl;
+
+ if (! href) {
+ return;
+ }
+
+ if (href.match(/^(?:(?:mailto|javascript|data):|[a-z]+:\/\/)/)) {
+ event.stopPropagation();
+ return true;
+ }
+
+ if ($a.closest('[data-no-icinga-ajax]').length > 0) {
+ return true;
+ }
+
+ // Check for ctrl or cmd click to open new tab unless clicking on a multiselect row
+ if ((event.ctrlKey || event.metaKey) && href !== '#' && $a.is('a')) {
+ window.open(href, linkTarget);
+ return false;
+ }
+
+ // Special checks for link clicks in action tables
+ if (! $a.is('tr[href]') && $a.closest('table.action').length > 0) {
+
+ // ignore clicks to ANY link with special key pressed
+ if ($a.closest('table.multiselect').length > 0 && (event.ctrlKey || event.metaKey || event.shiftKey)) {
+ return true;
+ }
+
+ // ignore inner links matching the row URL
+ if ($a.attr('href') === $a.closest('tr[href]').attr('href')) {
+ return true;
+ }
+ }
+
+ // window.open is used as return true; didn't work reliable
+ if (linkTarget === '_blank' || linkTarget === '_self') {
+ window.open(href, linkTarget);
+ return false;
+ }
+
+ if (! $eventTarget.is($a)) {
+ if ($eventTarget.is('input') || $eventTarget.is('button')) {
+ // Ignore form elements in action rows
+ return;
+ } else {
+ var $button = $('input[type=submit]:focus').add('button[type=submit]:focus');
+ if ($button.length > 0 && $.contains($button[0], $eventTarget[0])) {
+ // Ignore any descendant of form elements
+ return;
+ }
+ }
+ }
+
+ // ignore multiselect table row clicks
+ if ($a.is('tr') && $a.closest('table.multiselect').length > 0) {
+ return;
+ }
+
+ // Handle all other links as XHR requests
+ event.stopPropagation();
+ event.preventDefault();
+
+ // This is an anchor only
+ if (href.substr(0, 1) === '#' && href.length > 1
+ && href.substr(1, 1) !== '!'
+ ) {
+ icinga.ui.focusElement(href.substr(1), $a.closest('.container'));
+ return;
+ }
+
+ // activate spinner indicator
+ if ($a.hasClass('spinner')) {
+ $a.addClass('active');
+ }
+
+ // If link has hash tag...
+ if (href.match(/#/)) {
+ if (href === '#') {
+ if ($a.hasClass('close-container-control')) {
+ if (! icinga.ui.isOneColLayout()) {
+ var $cont = $a.closest('.container').first();
+ if ($cont.attr('id') === 'col1') {
+ icinga.ui.moveToLeft();
+ icinga.ui.layout1col();
+ } else {
+ icinga.ui.layout1col();
+ }
+ icinga.history.pushCurrentState();
+ }
+ }
+ return false;
+ }
+ $target = icinga.loader.getLinkTargetFor($a);
+
+ formerUrl = $target.data('icingaUrl');
+ if (typeof formerUrl !== 'undefined' && formerUrl.split(/#/)[0] === href.split(/#/)[0]) {
+ icinga.ui.focusElement(href.split(/#/)[1], $target);
+ $target.data('icingaUrl', href);
+ if (formerUrl !== href) {
+ icinga.history.pushCurrentState();
+ }
+ return false;
+ }
+ } else {
+ $target = icinga.loader.getLinkTargetFor($a);
+ }
+
+ // Load link URL
+ icinga.loader.loadUrl(href, $target);
+
+ if ($a.closest('#menu').length > 0) {
+ // Menu links should remove all but the first layout column
+ icinga.ui.layout1col();
+ }
+
+ return false;
+ },
+
+ clearSearch: function (event) {
+ $(event.target).parent().find('#search').attr('value', '');
+ },
+
+ unbindGlobalHandlers: function () {
+ $.each(this.icinga.behaviors, function (name, behavior) {
+ behavior.unbind($(document));
+ });
+ $(window).off('resize', this.onWindowResize);
+ $(window).off('load', this.onLoad);
+ $(window).off('unload', this.onUnload);
+ $(window).off('beforeunload', this.onUnload);
+ $(document).off('scroll', '.container', this.onContainerScroll);
+ $(document).off('click', 'a', this.linkClicked);
+ $(document).off('submit', 'form', this.submitForm);
+ $(document).off('change', 'form select.autosubmit', this.submitForm);
+ $(document).off('change', 'form input.autosubmit', this.submitForm);
+ $(document).off('focus', 'form select[data-related-radiobtn]', this.autoCheckRadioButton);
+ $(document).off('focus', 'form input[data-related-radiobtn]', this.autoCheckRadioButton);
+ $(document).off('visibilitychange', this.onVisibilityChange);
+ },
+
+ destroy: function() {
+ // This is gonna be hard, clean up the mess
+ this.unbindGlobalHandlers();
+ this.icinga = null;
+ }
+ };
+
+}(Icinga, jQuery));
diff --git a/public/js/icinga/history.js b/public/js/icinga/history.js
new file mode 100644
index 0000000..150be7c
--- /dev/null
+++ b/public/js/icinga/history.js
@@ -0,0 +1,338 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.History
+ *
+ * This is where we care about the browser History API
+ */
+(function (Icinga, $) {
+
+ 'use strict';
+
+ Icinga.History = function (icinga) {
+
+ /**
+ * YES, we need Icinga
+ */
+ this.icinga = icinga;
+
+ /**
+ * Our base url
+ */
+ this.baseUrl = icinga.config.baseUrl;
+
+ /**
+ * Initial URL at load time
+ */
+ this.initialUrl = location.href;
+
+ /**
+ * Whether the History API is enabled
+ */
+ this.enabled = false;
+ };
+
+ Icinga.History.prototype = {
+
+ /**
+ * Icinga will call our initialize() function once it's ready
+ */
+ initialize: function () {
+
+ // History API will not be enabled without browser support, no fallback
+ if ('undefined' !== typeof window.history &&
+ typeof window.history.pushState === 'function'
+ ) {
+ this.enabled = true;
+ this.icinga.logger.debug('History API enabled');
+ this.applyLocationBar(true);
+ $(window).on('popstate', { self: this }, this.onHistoryChange);
+ }
+
+ },
+
+ /**
+ * Get the current state (url and title) as object
+ *
+ * @returns {object}
+ */
+ getCurrentState: function () {
+ if (! this.enabled) {
+ return null;
+ }
+
+ var title = null;
+ var url = null;
+
+ // We only store URLs of containers sitting directly under #main:
+ $('#main > .container').each(function (idx, container) {
+ var $container = $(container),
+ cUrl = $container.data('icingaUrl'),
+ cTitle = $container.data('icingaTitle');
+
+ // TODO: I'd prefer to have the rightmost URL first
+ if ('undefined' !== typeof cUrl) {
+ // TODO: solve this on server side cUrl = icinga.utils.removeUrlParams(cUrl, blacklist);
+ if (! url) {
+ url = cUrl;
+ } else {
+ url = url + '#!' + cUrl;
+ }
+ }
+
+ if (typeof cTitle !== 'undefined') {
+ title = cTitle; // Only uses the rightmost title
+ }
+ });
+
+ return {
+ title: title,
+ url: url,
+ };
+ },
+
+ /**
+ * Detect active URLs and push combined URL to history
+ *
+ * TODO: How should we handle POST requests? e.g. search VS login
+ */
+ pushCurrentState: function () {
+ // No history API, no action
+ if (! this.enabled) {
+ return;
+ }
+
+ var state = this.getCurrentState();
+
+ // Did we find any URL? Then push it!
+ if (state.url) {
+ this.icinga.logger.debug('Pushing current state to history');
+ this.push(state.url);
+ }
+ if (state.title) {
+ this.icinga.ui.setTitle(state.title);
+ }
+ },
+
+ /**
+ * Replace the current history entry with the current state
+ */
+ replaceCurrentState: function () {
+ if (! this.enabled) {
+ return;
+ }
+
+ var state = this.getCurrentState();
+
+ if (state.url) {
+ this.icinga.logger.debug('Replacing current history state');
+ this.lastPushUrl = state.url;
+ window.history.replaceState(
+ this.getBehaviorState(),
+ null,
+ state.url
+ );
+ }
+ },
+
+ /**
+ * Push the given url as the new history state, unless the history is disabled
+ *
+ * @param {string} url The full url path, including anchor
+ */
+ pushUrl: function (url) {
+ // No history API, no action
+ if (!this.enabled) {
+ return;
+ }
+ this.push(url);
+ },
+
+ /**
+ * Execute the history state, preserving the current state of behaviors
+ *
+ * Used internally by the history and should not be called externally, instead use {@link pushUrl}.
+ *
+ * @param {string} url
+ */
+ push: function (url) {
+ url = url.replace(/[\?&]?_(render|reload)=[a-z0-9]+/g, '');
+ if (this.lastPushUrl === url) {
+ this.icinga.logger.debug(
+ 'Ignoring history state push for url ' + url + ' as it\' currently on top of the stack'
+ );
+ return;
+ }
+ this.lastPushUrl = url;
+ window.history.pushState(
+ this.getBehaviorState(),
+ null,
+ url
+ );
+ },
+
+ /**
+ * Fetch the current state of all JS behaviors that need history support
+ *
+ * @return {Object} A key-value map, mapping behavior names to state
+ */
+ getBehaviorState: function () {
+ var data = {};
+ $.each(this.icinga.behaviors, function (i, behavior) {
+ if (behavior.onPushState instanceof Function) {
+ data[i] = behavior.onPushState();
+ }
+ });
+ return data;
+ },
+
+ /**
+ * Event handler for pop events
+ *
+ * TODO: Fix active selection, multiple cols
+ */
+ onHistoryChange: function (event) {
+
+ var _this = event.data.self,
+ icinga = _this.icinga;
+
+ icinga.logger.debug('Got a history change');
+
+ // We might find browsers showing strange behaviour, this log could help
+ if (event.originalEvent.state === null) {
+ icinga.logger.debug('No more history steps available');
+ } else {
+ icinga.logger.debug('History state', event.originalEvent.state);
+ }
+
+ // keep the last pushed url in sync with history changes
+ _this.lastPushUrl = location.href;
+
+ _this.applyLocationBar();
+
+ // notify behaviors of the state change
+ $.each(this.icinga.behaviors, function (i, behavior) {
+ if (behavior.onPopState instanceof Function && history.state) {
+ behavior.onPopState(location.href, history.state[i]);
+ }
+ });
+ },
+
+ /**
+ * Update the application containers to match the current url
+ *
+ * Read the pane url from the current URL and load the corresponding panes into containers to
+ * match the current history state.
+ *
+ * @param {Boolean} onload Set to true when the main pane should not be updated, defaults to false
+ */
+ applyLocationBar: function (onload = false) {
+ let col2State = this.getCol2State();
+
+ if (onload && document.querySelector('#layout > #login')) {
+ // The user landed on the login
+ let redirectInput = document.querySelector('#login form input[name=redirect]');
+ redirectInput.value = redirectInput.value + col2State;
+ return;
+ }
+
+ let col1 = document.getElementById('col1'),
+ col2 = document.getElementById('col2'),
+ col1Url = document.location.pathname + document.location.search;
+
+ let col2Url;
+ if (col2State && col2State.match(/^#!/)) {
+ col2Url = col2State.split(/#!/)[1];
+ }
+
+ // This uses jQuery only because of its internal data attribute cache -.-
+ let currentCol1Url = $(col1).data('icingaUrl'),
+ currentCol2Url = $(col2).data('icingaUrl');
+
+ let loadCol1 = ! onload,
+ loadCol2 = !! col2Url;
+ if (currentCol2Url === col1Url) {
+ // User navigated forward
+ this.icinga.ui.moveToLeft();
+ loadCol1 = false;
+ } else if (currentCol1Url === col2Url) {
+ // User navigated back
+ this.icinga.ui.moveToRight();
+ loadCol2 = false;
+ }
+
+ if (loadCol1 && currentCol1Url !== col1Url) {
+ let anchor = this.getPaneAnchor(0);
+ if (anchor) {
+ col1Url += '#' + anchor;
+ }
+
+ this.icinga.loader.loadUrl(col1Url, $(col1)).addToHistory = false;
+ }
+
+ if (loadCol2 && currentCol2Url !== col2Url) {
+ let col2Req = this.icinga.loader.loadUrl(col2Url, $(col2));
+ col2Req.addToHistory = false;
+ col2Req.scripted = onload;
+
+ this.icinga.ui.layout2col();
+ } else if (! loadCol2 && ! col2Url) {
+ this.icinga.ui.layout1col();
+ }
+ },
+
+ /**
+ * Get the state of the selected pane
+ *
+ * @param col {int} The column index 0 or 1
+ *
+ * @returns {String} The string representing the state
+ */
+ getPaneAnchor: function (col) {
+ if (col !== 1 && col !== 0) {
+ throw 'Trying to get anchor for non-existing column: ' + col;
+ }
+ var panes = document.location.toString().split('#!')[col];
+ return panes && panes.split('#')[1] || '';
+ },
+
+ /**
+ * Get the side pane state after (and including) the #!
+ *
+ * @returns {string} The pane url
+ */
+ getCol2State: function () {
+ var hash = document.location.hash;
+ if (hash) {
+ if (hash.match(/^#[^!]/)) {
+ var hashs = hash.split('#');
+ hashs.shift();
+ hashs.shift();
+ hash = '#' + hashs.join('#');
+ }
+ }
+ return hash || '';
+ },
+
+ /**
+ * Return the main pane state fragment
+ *
+ * @returns {string} The main url including anchors, without #!
+ */
+ getCol1State: function () {
+ var anchor = this.getPaneAnchor(0);
+ var hash = window.location.pathname + window.location.search +
+ (anchor.length ? ('#' + anchor) : '');
+ return hash || '';
+ },
+
+ /**
+ * Cleanup
+ */
+ destroy: function () {
+ $(window).off('popstate', this.onHistoryChange);
+ this.icinga = null;
+ }
+ };
+
+}(Icinga, jQuery));
diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js
new file mode 100644
index 0000000..2ad05d2
--- /dev/null
+++ b/public/js/icinga/loader.js
@@ -0,0 +1,1323 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Loader
+ *
+ * This is where we take care of XHR requests, responses and failures.
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Loader = function (icinga) {
+
+ /**
+ * YES, we need Icinga
+ */
+ this.icinga = icinga;
+
+ /**
+ * Our base url
+ */
+ this.baseUrl = icinga.config.baseUrl;
+
+ this.failureNotice = null;
+
+ /**
+ * Pending requests
+ */
+ this.requests = {};
+
+ this.iconCache = {};
+
+ /**
+ * Whether auto-refresh is enabled
+ */
+ this.autorefreshEnabled = true;
+
+ /**
+ * Whether auto-refresh is suspended due to visibility of page
+ */
+ this.autorefreshSuspended = false;
+ };
+
+ Icinga.Loader.prototype = {
+
+ initialize: function () {
+ this.icinga.timer.register(this.autorefresh, this, 500);
+ },
+
+ submitForm: function ($form, $autoSubmittedBy, $button) {
+ var icinga = this.icinga;
+ var url = $form.attr('action');
+ var method = $form.attr('method');
+ var encoding = $form.attr('enctype');
+ var progressTimer;
+ var $target;
+ var data;
+
+ if (typeof method === 'undefined') {
+ method = 'POST';
+ } else {
+ method = method.toUpperCase();
+ }
+
+ if (typeof encoding === 'undefined') {
+ encoding = 'application/x-www-form-urlencoded';
+ }
+
+ if (typeof $autoSubmittedBy === 'undefined') {
+ $autoSubmittedBy = false;
+ }
+
+ if (typeof $button === 'undefined') {
+ $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form);
+ }
+
+ if ($button.length === 0) {
+ $button = $('input[type=submit]', $form).add('button[type=submit]', $form).first();
+ }
+
+ if ($button.length) {
+ // Activate spinner
+ if ($button.hasClass('spinner')) {
+ $button.addClass('active');
+ }
+
+ $target = this.getLinkTargetFor($button);
+ } else {
+ $target = this.getLinkTargetFor($form);
+ }
+
+ if (! url) {
+ // Use the URL of the target container if the form's action is not set
+ url = $target.closest('.container').data('icinga-url');
+ }
+
+ icinga.logger.debug('Submitting form: ' + method + ' ' + url, method);
+
+ if (method === 'GET') {
+ var dataObj = $form.serializeObject();
+
+ if (! $autoSubmittedBy) {
+ if ($button.length && $button.attr('name') !== 'undefined') {
+ dataObj[$button.attr('name')] = $button.attr('value');
+ }
+ }
+
+ url = icinga.utils.addUrlParams(url, dataObj);
+ } else {
+ if (encoding === 'multipart/form-data') {
+ data = new window.FormData($form[0]);
+ } else {
+ data = $form.serializeArray();
+ }
+
+ if (! $autoSubmittedBy) {
+ if ($button.length && $button.attr('name') !== 'undefined') {
+ if (encoding === 'multipart/form-data') {
+ data.append($button.attr('name'), $button.attr('value'));
+ } else {
+ data.push({
+ name: $button.attr('name'),
+ value: $button.attr('value')
+ });
+ }
+ }
+ }
+ }
+
+ // Disable all form controls to prevent resubmission except for our search input
+ // Note that disabled form inputs will not be enabled via JavaScript again
+ if (! $autoSubmittedBy
+ && ! $form.is('[role="search"]')
+ && $target.attr('id') === $form.closest('.container').attr('id')
+ ) {
+ $form.find('input[type=submit],button[type=submit],button:not([type])').prop('disabled', true);
+ }
+
+ // Show a spinner depending on how the form is being submitted
+ if ($autoSubmittedBy && $autoSubmittedBy.siblings('.spinner').length) {
+ $autoSubmittedBy.siblings('.spinner').first().addClass('active');
+ } else if ($button.length && $button.is('button') && $button.hasClass('animated')) {
+ $button.addClass('active');
+ } else if ($button.length && $button.attr('data-progress-label')) {
+ var isInput = $button.is('input');
+ if (isInput) {
+ $button.prop('value', $button.attr('data-progress-label') + '...');
+ } else {
+ $button.html($button.attr('data-progress-label') + '...');
+ }
+
+ // Use a fixed width to prevent the button from wobbling
+ $button.css('width', $button.css('width'));
+
+ progressTimer = icinga.timer.register(function () {
+ var label = isInput ? $button.prop('value') : $button.html();
+ var dots = label.substr(-3);
+
+ // Using empty spaces here to prevent centered labels from wobbling
+ if (dots === '...') {
+ label = label.slice(0, -2) + ' ';
+ } else if (dots === '.. ') {
+ label = label.slice(0, -1) + '.';
+ } else if (dots === '. ') {
+ label = label.slice(0, -2) + '. ';
+ }
+
+ if (isInput) {
+ $button.prop('value', label);
+ } else {
+ $button.html(label);
+ }
+ }, null, 100);
+ } else if ($button.length && $button.next().hasClass('spinner')) {
+ $('i', $button.next()).addClass('active');
+ } else if ($form.attr('data-progress-element')) {
+ var $progressElement = $('#' + $form.attr('data-progress-element'));
+ if ($progressElement.length) {
+ if ($progressElement.hasClass('spinner')) {
+ $('i', $progressElement).addClass('active');
+ } else {
+ $('i.spinner', $progressElement).addClass('active');
+ }
+ }
+ }
+
+ var extraHeaders = {};
+ if ($autoSubmittedBy && ($autoSubmittedBy.attr('name') || $autoSubmittedBy.attr('id'))) {
+ extraHeaders['X-Icinga-AutoSubmittedBy'] = $autoSubmittedBy.attr('name') || $autoSubmittedBy.attr('id');
+ }
+
+ var req = this.loadUrl(url, $target, data, method, undefined, undefined, undefined, extraHeaders);
+ req.forceFocus = $autoSubmittedBy ? $autoSubmittedBy : $button.length ? $button : null;
+ req.autosubmit = !! $autoSubmittedBy;
+ req.addToHistory = method === 'GET';
+ req.progressTimer = progressTimer;
+
+ if ($autoSubmittedBy) {
+ if ($autoSubmittedBy.closest('.controls').length) {
+ $('.content', req.$target).addClass('impact');
+ } else {
+ req.$target.addClass('impact');
+ }
+ }
+
+ return req;
+ },
+
+ /**
+ * Load the given URL to the given target
+ *
+ * @param {string} url URL to be loaded
+ * @param {object} target Target jQuery element
+ * @param {object} data Optional parameters, usually for POST requests
+ * @param {string} method HTTP method, default is 'GET'
+ * @param {string} action How to handle the response ('replace' or 'append'), default is 'replace'
+ * @param {boolean} autorefresh Whether the cause is a autorefresh or not
+ * @param {object} progressTimer A timer to be stopped when the request is done
+ * @param {object} extraHeaders Extra header entries
+ */
+ loadUrl: function (url, $target, data, method, action, autorefresh, progressTimer, extraHeaders) {
+ var id = null;
+
+ // Default method is GET
+ if ('undefined' === typeof method) {
+ method = 'GET';
+ }
+ if ('undefined' === typeof action) {
+ action = 'replace';
+ }
+ if ('undefined' === typeof autorefresh) {
+ autorefresh = false;
+ }
+
+ this.icinga.logger.debug('Loading ', url, ' to ', $target);
+
+ // We should do better and ignore requests without target and/or id
+ if (typeof $target !== 'undefined' && $target.attr('id')) {
+ id = $target.attr('id');
+ }
+
+ // If we have a pending request for the same target...
+ if (typeof this.requests[id] !== 'undefined') {
+ // ... ignore the new request if it is already pending with the same URL. Only abort GETs, as those
+ // are the only methods that are guaranteed to return the same value
+ if (this.requests[id].url === url && method === 'GET') {
+ if (autorefresh) {
+ return false;
+ }
+
+ this.icinga.logger.debug('Request to ', url, ' is already running for ', $target);
+ return this.requests[id];
+ }
+
+ // ...or abort the former request otherwise
+ this.icinga.logger.debug(
+ 'Aborting pending request loading ',
+ url,
+ ' to ',
+ $target
+ );
+
+ this.requests[id].abort();
+ }
+
+ // Not sure whether we need this Accept-header
+ var headers = { 'X-Icinga-Accept': 'text/html' };
+
+ if (!! id) {
+ headers['X-Icinga-Container'] = id;
+ }
+
+ if (autorefresh) {
+ headers['X-Icinga-Autorefresh'] = '1';
+ }
+
+ // Ask for a new window id in case we don't already have one
+ if (this.icinga.ui.hasWindowId()) {
+ var windowId = this.icinga.ui.getWindowId();
+ var containerId = this.icinga.ui.getUniqueContainerId($target);
+ if (containerId) {
+ windowId = windowId + '_' + containerId;
+ }
+ headers['X-Icinga-WindowId'] = windowId;
+ } else {
+ headers['X-Icinga-WindowId'] = 'undefined';
+ }
+
+ if (typeof extraHeaders !== 'undefined') {
+ headers = $.extend(headers, extraHeaders);
+ }
+
+ // This is jQuery's default content type
+ var contentType = 'application/x-www-form-urlencoded; charset=UTF-8';
+
+ var isFormData = typeof window.FormData !== 'undefined' && data instanceof window.FormData;
+ if (isFormData) {
+ // Setting false is mandatory as the form's data
+ // won't be recognized by the server otherwise
+ contentType = false;
+ }
+
+ var _this = this;
+ var req = $.ajax({
+ type : method,
+ url : url,
+ data : data,
+ headers: headers,
+ context: _this,
+ contentType: contentType,
+ processData: ! isFormData
+ });
+
+ req.$target = $target;
+ req.$redirectTarget = $target;
+ req.url = url;
+ req.done(this.onResponse);
+ req.fail(this.onFailure);
+ req.always(this.onComplete);
+ req.autorefresh = autorefresh;
+ req.autosubmit = false;
+ req.scripted = false;
+ req.method = method;
+ req.action = action;
+ req.addToHistory = true;
+ req.progressTimer = progressTimer;
+
+ if (url.match(/#/)) {
+ req.forceFocus = url.split(/#/)[1];
+ }
+
+ if (id) {
+ this.requests[id] = req;
+ }
+ if (! autorefresh) {
+ setTimeout(function () {
+ // The column may have not been shown before. To make the transition
+ // delay working we have to wait for the column getting rendered
+ if (! req.autosubmit && req.state() === 'pending') {
+ req.$target.addClass('impact');
+ }
+ }, 0);
+ }
+ this.icinga.ui.refreshDebug();
+ return req;
+ },
+
+ /**
+ * Create an URL relative to the Icinga base Url, still unused
+ *
+ * @param {string} url Relative url
+ */
+ url: function (url) {
+ if (typeof url === 'undefined') {
+ return this.baseUrl;
+ }
+ return this.baseUrl + url;
+ },
+
+ stopPendingRequestsFor: function ($el) {
+ var id;
+ if (typeof $el === 'undefined' || ! (id = $el.attr('id'))) {
+ return;
+ }
+
+ if (typeof this.requests[id] !== 'undefined') {
+ this.requests[id].abort();
+ }
+ },
+
+ filterAutorefreshingContainers: function () {
+ return $(this).data('icingaRefresh') > 0 && ! $(this).is('[data-suspend-autorefresh]');
+ },
+
+ autorefresh: function () {
+ var _this = this;
+
+ $('.container').filter(this.filterAutorefreshingContainers).each(function (idx, el) {
+ var $el = $(el);
+ var id = $el.attr('id');
+
+ // Always request application-state
+ if (id !== 'application-state' && (! _this.autorefreshEnabled || _this.autorefreshSuspended)) {
+ // Continue
+ return true;
+ }
+
+ if (typeof _this.requests[id] !== 'undefined') {
+ _this.icinga.logger.debug('No refresh, request pending for ', id);
+ return;
+ }
+
+ var interval = $el.data('icingaRefresh');
+ var lastUpdate = $el.data('lastUpdate');
+
+ if (typeof interval === 'undefined' || ! interval) {
+ _this.icinga.logger.info('No interval, setting default', id);
+ interval = 10;
+ }
+
+ if (typeof lastUpdate === 'undefined' || ! lastUpdate) {
+ _this.icinga.logger.info('No lastUpdate, setting one', id);
+ $el.data('lastUpdate',(new Date()).getTime());
+ return;
+ }
+ interval = interval * 1000;
+
+ // TODO:
+ if ((lastUpdate + interval) > (new Date()).getTime()) {
+ // self.icinga.logger.info(
+ // 'Skipping refresh',
+ // id,
+ // lastUpdate,
+ // interval,
+ // (new Date()).getTime()
+ // );
+ return;
+ }
+
+ if (_this.loadUrl($el.data('icingaUrl'), $el, undefined, undefined, undefined, true) === false) {
+ _this.icinga.logger.debug(
+ 'NOT autorefreshing ' + id + ', even if ' + interval + ' ms passed. Request pending?'
+ );
+ } else {
+ _this.icinga.logger.debug(
+ 'Autorefreshing ' + id + ' ' + interval + ' ms passed'
+ );
+ }
+ el = null;
+ });
+ },
+
+ /**
+ * Disable the autorefresh mechanism
+ */
+ disableAutorefresh: function () {
+ this.autorefreshEnabled = false;
+ },
+
+ /**
+ * Enable the autorefresh mechanism
+ */
+ enableAutorefresh: function () {
+ this.autorefreshEnabled = true;
+ },
+
+ processNotificationHeader: function(req) {
+ var header = req.getResponseHeader('X-Icinga-Notification');
+ var _this = this;
+ if (! header) return false;
+ var list = header.split('&');
+ $.each(list, function(idx, el) {
+ var parts = decodeURIComponent(el).split(' ');
+ _this.createNotice(parts.shift(), parts.join(' '));
+ });
+ return true;
+ },
+
+ /**
+ * Process the X-Icinga-Redirect HTTP Response Header
+ *
+ * If the response includes the X-Icinga-Redirect header, redirects to the URL associated with the header.
+ *
+ * @param {object} req Current request
+ *
+ * @returns {boolean} Whether we're about to redirect
+ */
+ processRedirectHeader: function(req) {
+ var icinga = this.icinga,
+ redirect = req.getResponseHeader('X-Icinga-Redirect');
+
+ if (! redirect) {
+ return false;
+ }
+
+ redirect = decodeURIComponent(redirect);
+ if (redirect.match(/__SELF__/)) {
+ if (req.autorefresh) {
+ // Redirect to the current window's URL in case it's an auto-refresh request. If authenticated
+ // externally this ensures seamless re-login if the session's expired
+ redirect = redirect.replace(
+ /__SELF__/,
+ encodeURIComponent(
+ document.location.pathname + document.location.search + document.location.hash
+ )
+ );
+ } else {
+ // Redirect to the URL which required authentication. When clicking a link this ensures that we
+ // redirect to the link's URL instead of the current window's URL (see above)
+ redirect = redirect.replace(/__SELF__/, req.url);
+ }
+ } else if (redirect.match(/__BACK__/)) {
+ if (req.$redirectTarget.is('#col1')) {
+ icinga.logger.warn('Cannot navigate back. Redirect target is #col1');
+ return false;
+ }
+
+ var originUrl = req.$target.data('icingaUrl');
+
+ $(window).on('popstate.__back__', { self: this }, function (event) {
+ var _this = event.data.self;
+ var $refreshTarget = $('#col2');
+ var refreshUrl;
+
+ var hash = icinga.history.getCol2State();
+ if (hash && hash.match(/^#!/)) {
+ // TODO: These three lines are copied from history.js, I don't like this
+ var parts = hash.split(/#!/);
+
+ if (parts[1] === originUrl) {
+ // After a page load a call to back() seems not to have an effect
+ icinga.ui.layout1col();
+ } else {
+ refreshUrl = parts[1];
+ }
+ }
+
+ if (typeof refreshUrl === 'undefined' && icinga.ui.isOneColLayout()) {
+ refreshUrl = icinga.history.getCol1State();
+ $refreshTarget = $('#col1');
+ }
+
+ // loadUrl won't run this request, as due to the history handling it's already running.
+ // The important bit though is that it returns this (already running) request. This way
+ // it's possible to set `scripted = true` on the request. (`addToHistory = true` should
+ // already be the case, though it's added again just in case it's not already `true`..)
+ var req = _this.loadUrl(refreshUrl, $refreshTarget);
+ req.addToHistory = false;
+ req.scripted = true;
+
+ setTimeout(function () {
+ // TODO: Find a better solution than a hardcoded one
+ // This is still the *cheat* to get live results
+ // (in case there's a delay and a change is not instantly effective)
+ var req = _this.loadUrl(refreshUrl, $refreshTarget);
+ req.addToHistory = false;
+ req.scripted = true;
+ }, 1000);
+
+ $(window).off('popstate.__back__');
+ });
+
+ // Navigate back, no redirect desired
+ window.history.back();
+
+ return true;
+ } else if (redirect.match(/__CLOSE__/)) {
+ if (req.$redirectTarget.is('#col1')) {
+ icinga.logger.warn('Cannot close #col1');
+ return false;
+ }
+
+ // Close right column as requested
+ icinga.ui.layout1col();
+
+ // Refresh left column and produce a new history state for it
+ var $col1 = $('#col1');
+ var col1Url = icinga.history.getCol1State();
+ var refresh = this.loadUrl(col1Url, $col1);
+ refresh.scripted = true;
+
+ var _this = this;
+ setTimeout(function () {
+ // TODO: Find a better solution than a hardcoded one
+ // This is still the *cheat* to get live results
+ // (in case there's a delay and a change is not instantly effective)
+ var secondRefresh = _this.loadUrl(col1Url, $col1);
+ if (secondRefresh !== refresh) {
+ // Only change these properties if it's not still the first refresh
+ secondRefresh.addToHistory = false;
+ secondRefresh.scripted = true;
+ }
+ }, 1000);
+
+ return true;
+ }
+
+ var useHttp = req.getResponseHeader('X-Icinga-Redirect-Http');
+ if (useHttp === 'yes') {
+ window.location.replace(redirect);
+ return true;
+ }
+
+ this.redirectToUrl(redirect, req.$redirectTarget, req);
+ return true;
+ },
+
+ /**
+ * Redirect to the given url
+ *
+ * @param {string} url
+ * @param {object} $target
+ * @param {XMLHttpRequest} referrer
+ */
+ redirectToUrl: function (url, $target, referrer) {
+ var icinga = this.icinga,
+ rerenderLayout,
+ autoRefreshInterval,
+ forceFocus,
+ origin;
+
+ if (typeof referrer !== 'undefined') {
+ rerenderLayout = referrer.getResponseHeader('X-Icinga-Rerender-Layout');
+ autoRefreshInterval = referrer.getResponseHeader('X-Icinga-Refresh');
+ forceFocus = referrer.forceFocus;
+ origin = referrer.url;
+ }
+
+ icinga.logger.debug(
+ 'Got redirect for ', $target, ', URL was ' + url
+ );
+
+ if (rerenderLayout) {
+ var parts = url.split(/#!/);
+ url = parts.shift();
+ var redirectionUrl = icinga.utils.addUrlFlag(url, 'renderLayout');
+ var r = this.loadUrl(redirectionUrl, $('#layout'));
+ r.historyUrl = url;
+ if (parts.length) {
+ r.loadNext = parts;
+ } else if (!! document.location.hash) {
+ // Retain detail URL if the layout is rerendered
+ parts = document.location.hash.split('#!').splice(1);
+ if (parts.length) {
+ r.loadNext = $.grep(parts, function (url) {
+ if (url !== origin) {
+ icinga.logger.debug('Retaining detail url ' + url);
+ return true;
+ }
+
+ icinga.logger.debug('Discarding detail url ' + url + ' as it\'s the origin of the redirect');
+ return false;
+ });
+ }
+ }
+ } else {
+ if (url.match(/#!/)) {
+ var parts = url.split(/#!/);
+ icinga.ui.layout2col();
+ this.loadUrl(parts.shift(), $('#col1'));
+ this.loadUrl(parts.shift(), $('#col2'));
+ } else {
+ if ($target.attr('id') === 'col2') { // TODO: multicol
+ if (($target.data('icingaUrl') || '').split('?')[0] === url.split('?')[0]) {
+ // Don't do anything in this case
+ } else if ($('#col1').data('icingaUrl').split('?')[0] === url.split('?')[0]) {
+ icinga.ui.layout1col();
+ $target = $('#col1');
+ delete(this.requests['col2']);
+ }
+ }
+
+ var req = this.loadUrl(url, $target);
+ req.forceFocus = url === origin ? forceFocus : null;
+ req.autoRefreshInterval = autoRefreshInterval;
+ req.referrer = referrer;
+ }
+ }
+ },
+
+ /**
+ * Handle successful XHR response
+ */
+ onResponse: function (data, textStatus, req) {
+ var _this = this;
+ if (this.failureNotice !== null) {
+ if (! this.failureNotice.hasClass('fading-out')) {
+ this.failureNotice.remove();
+ }
+ this.failureNotice = null;
+ }
+
+ var target = req.getResponseHeader('X-Icinga-Container');
+ var newBody = false;
+ var oldNotifications = false;
+ if (target) {
+ if (target === 'ignore') {
+ return;
+ }
+
+ var $newTarget = this.identifyLinkTarget(target, req.$target);
+ if ($newTarget.length) {
+ // If we change the target, oncomplete will fail to clean up
+ // This fixes the problem, not using req.$target would be better
+ delete this.requests[req.$target.attr('id')];
+
+ req.$target = $newTarget;
+ req.$redirectTarget = $newTarget;
+
+ if (target === 'layout') {
+ oldNotifications = $('#notifications li').detach();
+ this.icinga.ui.layout1col();
+ newBody = true;
+ }
+ }
+ }
+
+ if (req.autorefresh && req.$target.is('[data-suspend-autorefresh]')) {
+ return;
+ }
+
+ this.icinga.logger.debug(
+ 'Got response for ', req.$target, ', URL was ' + req.url
+ );
+ this.processNotificationHeader(req);
+
+ var cssreload = req.getResponseHeader('X-Icinga-Reload-Css');
+ if (cssreload) {
+ this.icinga.ui.reloadCss();
+ }
+
+ if (req.getResponseHeader('X-Icinga-Redirect')) {
+ return;
+ }
+
+ if (req.getResponseHeader('X-Icinga-Announcements') === 'refresh') {
+ var announceReq = _this.loadUrl(_this.url('/layout/announcements'), $('#announcements'));
+ announceReq.addToHistory = false;
+ announceReq.scripted = true;
+ }
+
+ var classes;
+
+ if (target !== 'layout') {
+ var moduleName = req.getResponseHeader('X-Icinga-Module');
+ classes = $.grep(req.$target.classes(), function (el) {
+ if (el === 'icinga-module' || el.match(/^module\-/)) {
+ return false;
+ }
+ return true;
+ });
+ if (moduleName) {
+ // Lazy load module javascript (Applies only to module.js code)
+ _this.icinga.ensureModule(moduleName);
+
+ req.$target.data('icingaModule', moduleName);
+ classes.push('icinga-module');
+ classes.push('module-' + moduleName);
+ } else {
+ req.$target.removeData('icingaModule');
+ if (req.$target.attr('data-icinga-module')) {
+ req.$target.removeAttr('data-icinga-module');
+ }
+ }
+ req.$target.attr('class', classes.join(' '));
+
+ var refresh = req.autoRefreshInterval || req.getResponseHeader('X-Icinga-Refresh');
+ if (refresh) {
+ req.$target.data('icingaRefresh', refresh);
+ } else {
+ req.$target.removeData('icingaRefresh');
+ if (req.$target.attr('data-icinga-refresh')) {
+ req.$target.removeAttr('data-icinga-refresh');
+ }
+ }
+ }
+
+ var title = req.getResponseHeader('X-Icinga-Title');
+ if (title && (target === 'layout' || req.$target.is('#layout'))) {
+ this.icinga.ui.setTitle(decodeURIComponent(title));
+ } else if (title && ! req.autorefresh && req.$target.closest('.dashboard').length === 0) {
+ req.$target.data('icingaTitle', decodeURIComponent(title));
+ }
+
+ // Set a window identifier if the server asks us to do so
+ var windowId = req.getResponseHeader('X-Icinga-WindowId');
+ if (windowId) {
+ this.icinga.ui.setWindowId(windowId);
+ }
+
+ var referrer = req.referrer;
+ if (typeof referrer === 'undefined') {
+ referrer = req;
+ }
+
+ var autoSubmit = false;
+ var currentUrl = this.icinga.utils.parseUrl(req.$target.data('icingaUrl'));
+ if (referrer.method === 'POST') {
+ var newUrl = this.icinga.utils.parseUrl(req.url);
+ if (newUrl.path === currentUrl.path && this.icinga.utils.arraysEqual(newUrl.params, currentUrl.params)) {
+ autoSubmit = true;
+ }
+ }
+
+ req.$target.data('icingaUrl', req.url);
+
+ if (typeof req.progressTimer !== 'undefined') {
+ this.icinga.timer.unregister(req.progressTimer);
+ }
+
+ var contentSeparator = req.getResponseHeader('X-Icinga-Multipart-Content');
+ if (!! contentSeparator) {
+ var locationQuery = req.getResponseHeader('X-Icinga-Location-Query');
+ if (locationQuery !== null) {
+ let url = currentUrl.path + (locationQuery ? '?' + locationQuery : '');
+ if (req.autosubmit || autoSubmit) {
+ // Also update a form's action if it doesn't differ from the container's url
+ var $form = $(referrer.forceFocus).closest('form');
+ var formAction = $form.attr('action');
+ if (!! formAction) {
+ formAction = this.icinga.utils.parseUrl(formAction);
+ if (formAction.path === currentUrl.path
+ && this.icinga.utils.arraysEqual(formAction.params, currentUrl.params)
+ ) {
+ $form.attr('action', url);
+ }
+ }
+ }
+
+ req.$target.data('icingaUrl', url);
+ this.icinga.history.replaceCurrentState();
+ }
+
+ $.each(req.responseText.split(contentSeparator), function (idx, el) {
+ var match = el.match(/for=(Behavior:)?(\S+)\s+([^]*)/m);
+ if (!! match) {
+ if (match[1]) {
+ var behavior = _this.icinga.behaviors[match[2].toLowerCase()];
+ if (typeof behavior !== 'undefined' && typeof behavior.update === 'function') {
+ behavior.update(JSON.parse(match[3]));
+ } else {
+ _this.icinga.logger.warn(
+ 'Invalid behavior. Cannot update behavior "' + match[2] + '"');
+ }
+ } else {
+ var $target = $('#' + match[2]);
+ if ($target.length) {
+ var forceFocus;
+ if (req.forceFocus
+ && typeof req.forceFocus.jquery !== 'undefined'
+ && $.contains($target[0], req.forceFocus[0])
+ ) {
+ forceFocus = req.forceFocus;
+ }
+
+ _this.renderContentToContainer(
+ match[3],
+ $target,
+ 'replace',
+ req.autorefresh,
+ forceFocus,
+ req.autosubmit || autoSubmit,
+ req.scripted
+ );
+ } else {
+ _this.icinga.logger.warn(
+ 'Invalid target ID. Cannot render multipart to #' + match[2]);
+ }
+ }
+ } else {
+ _this.icinga.logger.error('Ill-formed multipart', el);
+ }
+ })
+ } else {
+ this.renderContentToContainer(
+ req.responseText,
+ req.$target,
+ req.action,
+ req.autorefresh,
+ req.forceFocus,
+ req.autosubmit || autoSubmit,
+ req.scripted
+ );
+ }
+
+ if (oldNotifications) {
+ oldNotifications.appendTo($('#notifications'));
+ }
+ if (newBody) {
+ this.icinga.ui.fixDebugVisibility().triggerWindowResize();
+ }
+ },
+
+ /**
+ * Regardless of whether a request succeeded of failed, clean up
+ */
+ onComplete: function (dataOrReq, textStatus, reqOrError) {
+ var _this = this;
+ var req;
+
+ if (typeof dataOrReq === 'object') {
+ req = dataOrReq;
+ } else {
+ req = reqOrError;
+ }
+
+ // Remove 'impact' class if there was such
+ if (req.$target.hasClass('impact')) {
+ req.$target.removeClass('impact');
+ } else {
+ var $impact = req.$target.find('.impact').first();
+ if ($impact.length) {
+ $impact.removeClass('impact');
+ }
+ }
+
+ if (req.getResponseHeader('X-Icinga-Reload-Window') === 'yes') {
+ window.location.reload();
+ return;
+ }
+
+ if (! req.autorefresh && ! req.autosubmit) {
+ // TODO: Hook for response/url?
+ var url = req.url;
+
+ if (req.$target[0].id === 'col1') {
+ this.icinga.behaviors.navigation.trySetActiveAndSelectedByUrl(url);
+ }
+
+ var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]');
+ var $matches = $.merge($('[href="' + url + '"]'), $forms);
+ $matches.each(function (idx, el) {
+ var $el = $(el);
+ if ($el.closest('#menu').length) {
+ if ($el.is('form')) {
+ $('input', $el).addClass('active');
+ }
+ // Interrupt .each, only one menu item shall be active
+ return false;
+ }
+ });
+ }
+
+ // Update history when necessary
+ if (! req.autorefresh && req.addToHistory) {
+ if (req.$target.hasClass('container')) {
+ // We only want to care about top-level containers
+ if (req.$target.parent().closest('.container').length === 0) {
+ this.icinga.history.pushCurrentState();
+ }
+ } else {
+ // Request wasn't for a container, so it's usually the body
+ // or the full layout. Push request URL to history:
+ var url = typeof req.historyUrl !== 'undefined' ? req.historyUrl : req.url;
+ this.icinga.history.pushUrl(url);
+ }
+ }
+
+ req.$target.data('lastUpdate', (new Date()).getTime());
+ delete this.requests[req.$target.attr('id')];
+ this.icinga.ui.fadeNotificationsAway();
+
+ var extraUpdates = req.getResponseHeader('X-Icinga-Extra-Updates');
+ if (!! extraUpdates && req.getResponseHeader('X-Icinga-Redirect-Http') !== 'yes') {
+ $.each(extraUpdates.split(','), function (idx, el) {
+ var parts = el.trim().split(';');
+ var $target;
+ var url;
+ if (parts.length === 2) {
+ $target = $('#' + parts[0]);
+ if (! $target.length) {
+ _this.icinga.logger.warn('Invalid target ID. Cannot load extra URL', el);
+ return;
+ }
+
+ url = parts[1];
+ } else if (parts.length === 1) {
+ $target = $(parts[0]).closest(".container").not(req.$target);
+ if (! $target.length) {
+ _this.icinga.logger.warn('Invalid target ID. Cannot load extra URL', el);
+ return;
+ }
+
+ url = $target.data('icingaUrl');
+ } else {
+ _this.icinga.logger.error('Invalid extra update', el);
+ return;
+ }
+
+ _this.loadUrl(url, $target).addToHistory = false;
+ });
+ }
+
+ if (this.processRedirectHeader(req)) {
+ return;
+ }
+
+ if (typeof req.loadNext !== 'undefined' && req.loadNext.length) {
+ if ($('#col2').length) {
+ var r = this.loadUrl(req.loadNext[0], $('#col2'));
+ r.addToHistory = req.addToHistory;
+ this.icinga.ui.layout2col();
+ } else {
+ this.icinga.logger.error('Failed to load URL for #col2', req.loadNext);
+ }
+ }
+
+ // Lazy load module javascript (Applies only to module.js code)
+ this.icinga.ensureSubModules(req.$target);
+
+ req.$target.find('.container').each(function () {
+ $(this).trigger('rendered', [req.autorefresh, req.scripted, req.autosubmit]);
+ });
+ req.$target.trigger('rendered', [req.autorefresh, req.scripted, req.autosubmit]);
+
+ this.icinga.ui.refreshDebug();
+ },
+
+ /**
+ * Handle failed XHR response
+ */
+ onFailure: function (req, textStatus, errorThrown) {
+ var url = req.url;
+
+ /*
+ * Test if a manual actions comes in and autorefresh is active: Stop refreshing
+ */
+ if (req.addToHistory && ! req.autorefresh) {
+ req.$target.data('icingaRefresh', 0);
+ req.$target.data('icingaUrl', url);
+ icinga.history.pushCurrentState();
+ }
+
+ if (typeof req.progressTimer !== 'undefined') {
+ this.icinga.timer.unregister(req.progressTimer);
+ }
+
+ if (req.status > 0 && req.status < 501) {
+ this.icinga.logger.error(
+ req.status,
+ errorThrown + ':',
+ $(req.responseText).text().replace(/\s+/g, ' ').slice(0, 100)
+ );
+ this.renderContentToContainer(
+ req.responseText,
+ req.$target,
+ req.action,
+ req.autorefresh,
+ undefined,
+ req.autosubmit,
+ req.scripted
+ );
+ } else {
+ if (errorThrown === 'abort') {
+ this.icinga.logger.debug(
+ 'Request to ' + url + ' has been aborted for ',
+ req.$target
+ );
+
+ // Aborted requests should not be added to browser history
+ req.addToHistory = false;
+ } else {
+ if (this.failureNotice === null) {
+ var now = new Date();
+ var padString = this.icinga.utils.padString;
+ this.failureNotice = this.createNotice(
+ 'error',
+ 'The connection to the Icinga web server was lost at '
+ + now.getFullYear()
+ + '-' + padString(now.getMonth() + 1, 0, 2)
+ + '-' + padString(now.getDate(), 0, 2)
+ + ' ' + padString(now.getHours(), 0, 2)
+ + ':' + padString(now.getMinutes(), 0, 2)
+ + '.',
+ true
+ );
+ }
+
+ this.icinga.logger.error(
+ 'Failed to contact web server loading ',
+ url,
+ ' for ',
+ req.$target
+ );
+ }
+ }
+ },
+
+ /**
+ * Create a notification. Can be improved.
+ */
+ createNotice: function (severity, message, persist) {
+ var c = severity,
+ icon;
+ if (persist) {
+ c += ' persist';
+ }
+
+ switch (severity) {
+ case 'success':
+ icon = 'check-circle';
+ break;
+ case 'error':
+ icon = 'times';
+ break;
+ case 'warning':
+ icon = 'exclamation-triangle';
+ break;
+ case 'info':
+ icon = 'info-circle';
+ break;
+ }
+
+ var $notice = $(
+ '<li class="' + c + '">' +
+ '<i class="icon fa fa-' + icon + '"></i>' +
+ this.icinga.utils.escape(message) + '</li>'
+ ).appendTo($('#notifications'));
+
+ if (!persist) {
+ this.icinga.ui.fadeNotificationsAway();
+ }
+
+ return $notice;
+ },
+
+ /**
+ * Detect the link/form target for a given element (link, form, whatever)
+ *
+ * @param {object} $el jQuery set with the element
+ * @param {boolean} prepare Pass `false` to disable column preparation
+ */
+ getLinkTargetFor: function($el, prepare)
+ {
+ if (typeof prepare === 'undefined') {
+ prepare = true;
+ }
+
+ // If everything else fails, our target is the first column...
+ var $target = $('#col1');
+
+ // ...but usually we will use our own container...
+ var $container = $el.closest('.container');
+ if ($container.length) {
+ $target = $container;
+ }
+
+ // You can of course override the default behaviour:
+ if ($el.closest('[data-base-target]').length) {
+ var targetId = $el.closest('[data-base-target]').data('baseTarget');
+
+ $target = this.identifyLinkTarget(targetId, $el);
+ if (! $target.length) {
+ this.icinga.logger.warn('Link target "#' + targetId + '" does not exist in DOM.');
+ }
+ }
+
+ if (prepare) {
+ this.icinga.ui.prepareColumnFor($el, $target);
+ }
+
+ return $target;
+ },
+
+ /**
+ * Identify link target by the given id
+ *
+ * The id may also be one of the column aliases: `_next`, `_self` and `_main`
+ *
+ * @param {string} id
+ * @param {object} $of
+ * @return {object}
+ */
+ identifyLinkTarget: function (id, $of) {
+ var $target;
+
+ if (id === '_next') {
+ if (this.icinga.ui.hasOnlyOneColumn()) {
+ $target = $('#col1');
+ } else {
+ $target = $('#col2');
+ }
+ } else if (id === '_self') {
+ $target = $of.closest('.container');
+ } else if (id === '_main') {
+ $target = $('#col1');
+ } else {
+ $target = $('#' + id);
+ }
+
+ return $target;
+ },
+
+ /**
+ * Smoothly render given HTML to given container
+ */
+ renderContentToContainer: function (content, $container, action, autorefresh, forceFocus, autoSubmit, scripted) {
+ // Container update happens here
+ var scrollPos = false;
+ var _this = this;
+ var containerId = $container.attr('id');
+
+ var activeElementPath = false;
+ var navigationAnchor = false;
+ var focusFallback = false;
+
+ if (forceFocus && forceFocus.length) {
+ if (typeof forceFocus === 'string') {
+ navigationAnchor = forceFocus;
+ } else {
+ activeElementPath = this.icinga.utils.getCSSPath($(forceFocus));
+ }
+ } else if (document.activeElement && document.activeElement.id === 'search') {
+ activeElementPath = '#search';
+ } else if (document.activeElement
+ && document.activeElement !== document.body
+ && $.contains($container[0], document.activeElement)
+ ) {
+ // Active element in container
+ var $activeElement = $(document.activeElement);
+ var $pagination = $activeElement.closest('.pagination-control');
+ if ($pagination.length) {
+ focusFallback = {
+ 'parent': this.icinga.utils.getCSSPath($pagination),
+ 'child': '.active > a'
+ };
+ }
+ activeElementPath = this.icinga.utils.getCSSPath($activeElement);
+ }
+
+ var scrollTarget = $container;
+ if (typeof containerId !== 'undefined') {
+ if (autorefresh || autoSubmit) {
+ if ($container.css('display') === 'flex' && $container.is('.container')) {
+ var $scrollableContent = $container.children('.content');
+ scrollPos = $scrollableContent.scrollTop();
+ scrollTarget = _this.icinga.utils.getCSSPath($scrollableContent);
+ } else {
+ scrollPos = $container.scrollTop();
+ }
+ } else {
+ scrollPos = 0;
+ }
+ }
+
+ $container.trigger('beforerender', [content, action, autorefresh, scripted, autoSubmit]);
+
+ var discard = false;
+ $.each(_this.icinga.behaviors, function(name, behavior) {
+ if (behavior.renderHook) {
+ var changed = behavior.renderHook(content, $container, action, autorefresh, autoSubmit);
+ if (changed === null) {
+ discard = true;
+ } else {
+ content = changed;
+ }
+ }
+ });
+
+ $('.container', $container).each(function() {
+ _this.stopPendingRequestsFor($(this));
+ });
+
+ if (! discard) {
+ if ($container.closest('.dashboard').length) {
+ var title = $('h1', $container).first().detach();
+ $container.html(title).append(content);
+ } else if (action === 'replace') {
+ $container.html(content);
+ } else {
+ $container.append(content);
+ }
+ }
+
+ this.icinga.ui.assignUniqueContainerIds();
+
+ if (! discard && navigationAnchor) {
+ var $element = $container.find('#' + navigationAnchor);
+ if ($element.length) {
+ // data-icinga-no-scroll-on-focus is NOT designed to avoid scrolling for non-XHR requests
+ setTimeout(this.icinga.ui.focusElement.bind(this.icinga.ui), 0,
+ $element, $container, ! $element.is('[data-icinga-no-scroll-on-focus]'));
+ }
+ } else if (! activeElementPath) {
+ // Active element was not in this container
+ if (! autorefresh && ! autoSubmit && ! scripted) {
+ setTimeout(function() {
+ if (typeof $container.attr('tabindex') === 'undefined') {
+ $container.attr('tabindex', -1);
+ }
+ // Do not touch focus in case a module or component already placed it
+ if ($(document.activeElement).closest('.container').attr('id') !== containerId) {
+ _this.icinga.ui.focusElement($container);
+ }
+ }, 0);
+ }
+ } else {
+ setTimeout(function() {
+ var $activeElement = $(activeElementPath);
+
+ if ($activeElement.length && $activeElement.is(':visible')) {
+ $activeElement[0].focus({preventScroll: autorefresh || autoSubmit});
+ } else if (! autorefresh && ! autoSubmit && ! scripted) {
+ if (focusFallback) {
+ _this.icinga.ui.focusElement($(focusFallback.parent).find(focusFallback.child));
+ } else if (typeof $container.attr('tabindex') === 'undefined') {
+ $container.attr('tabindex', -1);
+ }
+ _this.icinga.ui.focusElement($container);
+ }
+ }, 0);
+ }
+
+ if (scrollPos !== false) {
+ var $scrollTarget = $(scrollTarget);
+ $scrollTarget.scrollTop(scrollPos);
+
+ // Fallback for browsers without support for focus({preventScroll: true})
+ setTimeout(function () {
+ if ($scrollTarget.scrollTop() !== scrollPos) {
+ $scrollTarget.scrollTop(scrollPos);
+ }
+ }, 0);
+ }
+
+ // Re-enable all click events (disabled as of performance reasons)
+ // $('*').off('click');
+ },
+
+ /**
+ * On shutdown we kill all pending requests
+ */
+ destroy: function() {
+ $.each(this.requests, function(id, request) {
+ request.abort();
+ });
+ this.icinga = null;
+ this.requests = {};
+ }
+
+ };
+
+}(Icinga, jQuery));
diff --git a/public/js/icinga/logger.js b/public/js/icinga/logger.js
new file mode 100644
index 0000000..471393c
--- /dev/null
+++ b/public/js/icinga/logger.js
@@ -0,0 +1,129 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Logger
+ *
+ * Well, log output. Rocket science.
+ */
+(function (Icinga) {
+
+ 'use strict';
+
+ Icinga.Logger = function (icinga) {
+
+ this.icinga = icinga;
+
+ this.logLevel = 'info';
+
+ this.logLevels = {
+ 'debug': 0,
+ 'info' : 1,
+ 'warn' : 2,
+ 'error': 3
+ };
+
+ };
+
+ Icinga.Logger.prototype = {
+
+ /**
+ * Whether the browser has a console object
+ */
+ hasConsole: function () {
+ return 'undefined' !== typeof console;
+ },
+
+ /**
+ * Raise or lower current log level
+ *
+ * Messages below this threshold will be silently discarded
+ */
+ setLevel: function (level) {
+ if ('undefined' !== typeof this.numericLevel(level)) {
+ this.logLevel = level;
+ }
+ return this;
+ },
+
+ /**
+ * Log a debug message
+ */
+ debug: function () {
+ return this.writeToConsole('debug', arguments);
+ },
+
+ /**
+ * Log an informational message
+ */
+ info: function () {
+ return this.writeToConsole('info', arguments);
+ },
+
+ /**
+ * Log a warning message
+ */
+ warn: function () {
+ return this.writeToConsole('warn', arguments);
+ },
+
+ /**
+ * Log an error message
+ */
+ error: function () {
+ return this.writeToConsole('error', arguments);
+ },
+
+ /**
+ * Write a log message with the given level to the console
+ */
+ writeToConsole: function (level, args) {
+
+ args = Array.prototype.slice.call(args);
+
+ // We want our log messages to carry precise timestamps
+ args.unshift(this.icinga.utils.timeWithMs());
+
+ if (this.hasConsole() && this.hasLogLevel(level)) {
+ if (typeof console[level] !== 'undefined') {
+ if (typeof console[level].apply === 'function') {
+ console[level].apply(console, args);
+ } else {
+ args.unshift('[' + level + ']');
+ console[level](args.join(' '));
+ }
+ } else if ('undefined' !== typeof console.log) {
+ args.unshift('[' + level + ']');
+ console.log(args.join(' '));
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Return the numeric identifier for a given log level
+ */
+ numericLevel: function (level) {
+ var ret = this.logLevels[level];
+ if ('undefined' === typeof ret) {
+ throw 'Got invalid log level ' + level;
+ }
+ return ret;
+ },
+
+ /**
+ * Whether a given log level exists
+ */
+ hasLogLevel: function (level) {
+ return this.numericLevel(level) >= this.numericLevel(this.logLevel);
+ },
+
+ /**
+ * There isn't much to clean up here
+ */
+ destroy: function () {
+ this.enabled = false;
+ this.icinga = null;
+ }
+ };
+
+}(Icinga));
diff --git a/public/js/icinga/module.js b/public/js/icinga/module.js
new file mode 100644
index 0000000..2c2368e
--- /dev/null
+++ b/public/js/icinga/module.js
@@ -0,0 +1,134 @@
+/*! Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+/**
+ * This is how we bootstrap JS code in our modules
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Module = function (icinga, name, prototyp) {
+
+ // The Icinga instance
+ this.icinga = icinga;
+
+ // Applied event handlers
+ this.handlers = [];
+
+ // Event handlers registered by this module
+ this.registeredHandlers = [];
+
+ // The module name
+ this.name = name;
+
+ // The JS prototype for this module
+ this.prototyp = prototyp;
+
+ // Once initialized, this will be an instance of the modules prototype
+ this.object = {};
+
+ // Initialize this module
+ this.initialize();
+ };
+
+ Icinga.Module.prototype = {
+
+ initialize: function () {
+
+ if (typeof this.prototyp !== 'function') {
+ this.icinga.logger.error(
+ 'Unable to load module "' + this.name + '", constructor is missing'
+ );
+ return false;
+ }
+
+ try {
+
+ // The constructor of the modules prototype must be prepared to get an
+ // instance of Icinga.Module
+ this.object = new this.prototyp(this);
+ this.applyHandlers();
+ } catch(e) {
+ this.icinga.logger.error(
+ 'Failed to load module ' + this.name + ': ',
+ e
+ );
+
+ return false;
+ }
+
+ // That's all, the module is ready
+ this.icinga.logger.debug(
+ 'Module ' + this.name + ' has been initialized'
+ );
+
+ return true;
+ },
+
+ /**
+ * Register this modules event handlers
+ */
+ on: function (event, filter, handler) {
+ if (typeof handler === 'undefined') {
+ handler = filter;
+ filter = '.module-' + this.name;
+ } else {
+ filter = '.module-' + this.name + ' ' + filter;
+ }
+ this.registeredHandlers.push({event: event, filter: filter, handler: handler});
+
+ },
+
+ applyHandlers: function () {
+ var _this = this;
+
+ $.each(this.registeredHandlers, function (key, on) {
+ _this.bindEventHandler(
+ on.event,
+ on.filter,
+ on.handler
+ );
+ });
+ _this = null;
+
+ return this;
+ },
+
+ /**
+ * Effectively bind the given event handler
+ */
+ bindEventHandler: function (event, filter, handler) {
+ var _this = this;
+ this.icinga.logger.debug('Bound ' + filter + ' .' + event + '()');
+ this.handlers.push([event, filter, handler]);
+ $(document).on(event, filter, handler.bind(_this.object));
+ },
+
+ /**
+ * Unbind all event handlers bound by this module
+ */
+ unbindEventHandlers: function () {
+ $.each(this.handlers, function (idx, handler) {
+ $(document).off(handler[0], handler[1], handler[2]);
+ });
+ },
+
+ /**
+ * Allow to destroy and clean up this module
+ */
+ destroy: function () {
+
+ this.unbindEventHandlers();
+
+ if (typeof this.object.destroy === 'function') {
+ this.object.destroy();
+ }
+
+ this.object = null;
+ this.icinga = null;
+ this.prototyp = null;
+ }
+
+ };
+
+}(Icinga, jQuery));
diff --git a/public/js/icinga/storage.js b/public/js/icinga/storage.js
new file mode 100644
index 0000000..fa312d2
--- /dev/null
+++ b/public/js/icinga/storage.js
@@ -0,0 +1,549 @@
+/*! Icinga Web 2 | (c) 2019 Icinga GmbH | GPLv2+ */
+
+;(function(Icinga) {
+
+ 'use strict';
+
+ const KEY_TTL = 7776000000; // 90 days (90×24×60×60×1000)
+
+ /**
+ * Icinga.Storage
+ *
+ * localStorage access
+ *
+ * @param {string} prefix
+ */
+ Icinga.Storage = function(prefix) {
+
+ /**
+ * Prefix to use for keys
+ *
+ * @type {string}
+ */
+ this.prefix = prefix;
+
+ /**
+ * Storage backend
+ *
+ * @type {Storage}
+ */
+ this.backend = window.localStorage;
+ };
+
+ /**
+ * Callbacks for storage events on particular keys
+ *
+ * @type {{function}}
+ */
+ Icinga.Storage.subscribers = {};
+
+ /**
+ * Pass storage events to subscribers
+ *
+ * @param {StorageEvent} event
+ */
+ window.addEventListener('storage', function(event) {
+ var url = icinga.utils.parseUrl(event.url);
+ if (! url.path.startsWith(icinga.config.baseUrl)) {
+ // A localStorage is shared between all paths on the same origin.
+ // So we need to make sure it's us who made a change.
+ return;
+ }
+
+ if (typeof Icinga.Storage.subscribers[event.key] !== 'undefined') {
+ var newValue = null,
+ oldValue = null;
+ if (!! event.newValue) {
+ try {
+ newValue = JSON.parse(event.newValue);
+ } catch(error) {
+ icinga.logger.error('[Storage] Failed to parse new value (\`' + event.newValue
+ + '\`) for key "' + event.key + '". Error was: ' + error);
+ event.storageArea.removeItem(event.key);
+ return;
+ }
+ }
+ if (!! event.oldValue) {
+ try {
+ oldValue = JSON.parse(event.oldValue);
+ } catch(error) {
+ icinga.logger.warn('[Storage] Failed to parse old value (\`' + event.oldValue
+ + '\`) of key "' + event.key + '". Error was: ' + error);
+ oldValue = null;
+ }
+ }
+
+ Icinga.Storage.subscribers[event.key].forEach(function (subscriber) {
+ subscriber[0].call(subscriber[1], newValue, oldValue, event);
+ });
+ }
+ });
+
+ /**
+ * Create a new storage with `behavior.<name>` as prefix
+ *
+ * @param {string} name
+ *
+ * @returns {Icinga.Storage}
+ */
+ Icinga.Storage.BehaviorStorage = function(name) {
+ return new Icinga.Storage('behavior.' + name);
+ };
+
+ Icinga.Storage.prototype = {
+
+ /**
+ * Set the storage backend
+ *
+ * @param {Storage} backend
+ */
+ setBackend: function(backend) {
+ this.backend = backend;
+ },
+
+ /**
+ * Prefix the given key
+ *
+ * @param {string} key
+ *
+ * @returns {string}
+ */
+ prefixKey: function(key) {
+ var prefix = 'icinga.';
+ if (typeof this.prefix !== 'undefined') {
+ prefix = prefix + this.prefix + '.';
+ }
+
+ return prefix + key;
+ },
+
+ /**
+ * Store the given key-value pair
+ *
+ * @param {string} key
+ * @param {*} value
+ *
+ * @returns {void}
+ */
+ set: function(key, value) {
+ this.backend.setItem(this.prefixKey(key), JSON.stringify(value));
+ },
+
+ /**
+ * Get value for the given key
+ *
+ * @param {string} key
+ *
+ * @returns {*}
+ */
+ get: function(key) {
+ key = this.prefixKey(key);
+ var value = this.backend.getItem(key);
+
+ try {
+ return JSON.parse(value);
+ } catch(error) {
+ icinga.logger.error('[Storage] Failed to parse value (\`' + value
+ + '\`) of key "' + key + '". Error was: ' + error);
+ this.backend.removeItem(key);
+ return null;
+ }
+ },
+
+ /**
+ * Remove given key from storage
+ *
+ * @param {string} key
+ *
+ * @returns {void}
+ */
+ remove: function(key) {
+ this.backend.removeItem(this.prefixKey(key));
+ },
+
+ /**
+ * Subscribe with a callback for events on a particular key
+ *
+ * @param {string} key
+ * @param {function} callback
+ * @param {object} context
+ *
+ * @returns {void}
+ */
+ onChange: function(key, callback, context) {
+ if (this.backend !== window.localStorage) {
+ throw new Error('[Storage] Only the localStorage emits events');
+ }
+
+ var prefixedKey = this.prefixKey(key);
+
+ if (typeof Icinga.Storage.subscribers[prefixedKey] === 'undefined') {
+ Icinga.Storage.subscribers[prefixedKey] = [];
+ }
+
+ Icinga.Storage.subscribers[prefixedKey].push([callback, context]);
+ }
+ };
+
+ /**
+ * Icinga.Storage.StorageAwareMap
+ *
+ * @param {object} items
+ * @constructor
+ */
+ Icinga.Storage.StorageAwareMap = function(items) {
+
+ /**
+ * Storage object
+ *
+ * @type {Icinga.Storage}
+ */
+ this.storage = undefined;
+
+ /**
+ * Storage key
+ *
+ * @type {string}
+ */
+ this.key = undefined;
+
+ /**
+ * Event listeners for our internal events
+ *
+ * @type {{}}
+ */
+ this.eventListeners = {
+ 'add': [],
+ 'delete': []
+ };
+
+ /**
+ * The internal (real) map
+ *
+ * @type {Map<*>}
+ */
+ this.data = new Map();
+
+ // items is not passed directly because IE11 doesn't support constructor arguments
+ if (typeof items !== 'undefined' && !! items) {
+ Object.keys(items).forEach(function(key) {
+ this.data.set(key, items[key]);
+ }, this);
+ }
+ };
+
+ /**
+ * Create a new StorageAwareMap for the given storage and key
+ *
+ * @param {Icinga.Storage} storage
+ * @param {string} key
+ *
+ * @returns {Icinga.Storage.StorageAwareMap}
+ */
+ Icinga.Storage.StorageAwareMap.withStorage = function(storage, key) {
+ var items = storage.get(key);
+ if (typeof items !== 'undefined' && !! items) {
+ Object.keys(items).forEach(function(key) {
+ var value = items[key];
+
+ if (typeof value !== 'object' || typeof value['lastAccess'] === 'undefined') {
+ items[key] = {'value': value, 'lastAccess': Date.now()};
+ } else if (Date.now() - value['lastAccess'] > KEY_TTL) {
+ delete items[key];
+ }
+ }, this);
+ }
+
+ if (!! items && Object.keys(items).length) {
+ storage.set(key, items);
+ } else if (items !== null) {
+ storage.remove(key);
+ }
+
+ return (new Icinga.Storage.StorageAwareMap(items).setStorage(storage, key));
+ };
+
+ Icinga.Storage.StorageAwareMap.prototype = {
+
+ /**
+ * Bind this map to the given storage and key
+ *
+ * @param {Icinga.Storage} storage
+ * @param {string} key
+ *
+ * @returns {this}
+ */
+ setStorage: function(storage, key) {
+ this.storage = storage;
+ this.key = key;
+
+ if (storage.backend === window.localStorage) {
+ storage.onChange(key, this.onChange, this);
+ }
+
+ return this;
+ },
+
+ /**
+ * Return a boolean indicating this map got a storage
+ *
+ * @returns {boolean}
+ */
+ hasStorage: function() {
+ return typeof this.storage !== 'undefined' && typeof this.key !== 'undefined';
+ },
+
+ /**
+ * Update the storage
+ *
+ * @returns {void}
+ */
+ updateStorage: function() {
+ if (! this.hasStorage()) {
+ return;
+ }
+
+ if (this.size > 0) {
+ this.storage.set(this.key, this.toObject());
+ } else {
+ this.storage.remove(this.key);
+ }
+ },
+
+ /**
+ * Update the map
+ *
+ * @param {object} newValue
+ */
+ onChange: function(newValue) {
+ // Check for deletions first. Uses keys() to iterate over a copy
+ this.keys().forEach(function (key) {
+ if (newValue === null || typeof newValue[key] === 'undefined') {
+ var value = this.data.get(key)['value'];
+ this.data.delete(key);
+ this.trigger('delete', key, value);
+ }
+ }, this);
+
+ if (newValue === null) {
+ return;
+ }
+
+ // Now check for new entries
+ Object.keys(newValue).forEach(function(key) {
+ var known = this.data.has(key);
+ // Always override any known value as we want to keep track of all `lastAccess` changes
+ this.data.set(key, newValue[key]);
+
+ if (! known) {
+ this.trigger('add', key, newValue[key]['value']);
+ }
+ }, this);
+ },
+
+ /**
+ * Register an event handler to handle storage updates
+ *
+ * Available events are: add, delete. The callback receives the
+ * key and its value as first and second argument, respectively.
+ *
+ * @param {string} event
+ * @param {function} callback
+ * @param {object} thisArg
+ *
+ * @returns {this}
+ */
+ on: function(event, callback, thisArg) {
+ if (typeof this.eventListeners[event] === 'undefined') {
+ throw new Error('Invalid event "' + event + '"');
+ }
+
+ this.eventListeners[event].push([callback, thisArg]);
+ return this;
+ },
+
+ /**
+ * Trigger all event handlers for the given event
+ *
+ * @param {string} event
+ * @param {string} key
+ * @param {*} value
+ */
+ trigger: function(event, key, value) {
+ this.eventListeners[event].forEach(function (handler) {
+ var thisArg = handler[1];
+ if (typeof thisArg === 'undefined') {
+ thisArg = this;
+ }
+
+ handler[0].call(thisArg, key, value);
+ });
+ },
+
+ /**
+ * Return the number of key/value pairs in the map
+ *
+ * @returns {number}
+ */
+ get size() {
+ return this.data.size;
+ },
+
+ /**
+ * Set the value for the key in the map
+ *
+ * @param {string} key
+ * @param {*} value Default null
+ *
+ * @returns {this}
+ */
+ set: function(key, value) {
+ if (typeof value === 'undefined') {
+ value = null;
+ }
+
+ this.data.set(key, {'value': value, 'lastAccess': Date.now()});
+
+ this.updateStorage();
+ return this;
+ },
+
+ /**
+ * Remove all key/value pairs from the map
+ *
+ * @returns {void}
+ */
+ clear: function() {
+ this.data.clear();
+ this.updateStorage();
+ },
+
+ /**
+ * Remove the given key from the map
+ *
+ * @param {string} key
+ *
+ * @returns {boolean}
+ */
+ delete: function(key) {
+ var retVal = this.data.delete(key);
+
+ this.updateStorage();
+ return retVal;
+ },
+
+ /**
+ * Return a list of [key, value] pairs for every item in the map
+ *
+ * @returns {Array}
+ */
+ entries: function() {
+ var list = [];
+
+ if (this.size > 0) {
+ this.data.forEach(function (value, key) {
+ list.push([key, value['value']]);
+ });
+ }
+
+ return list;
+ },
+
+ /**
+ * Execute a provided function once for each item in the map, in insertion order
+ *
+ * @param {function} callback
+ * @param {object} thisArg
+ *
+ * @returns {void}
+ */
+ forEach: function(callback, thisArg) {
+ if (typeof thisArg === 'undefined') {
+ thisArg = this;
+ }
+
+ this.data.forEach(function(value, key) {
+ callback.call(thisArg, value['value'], key);
+ });
+ },
+
+ /**
+ * Return the value associated to the key, or undefined if there is none
+ *
+ * @param {string} key
+ *
+ * @returns {*}
+ */
+ get: function(key) {
+ var value = this.data.get(key)['value'];
+ this.set(key, value); // Update `lastAccess`
+
+ return value;
+ },
+
+ /**
+ * Return a boolean asserting whether a value has been associated to the key in the map
+ *
+ * @param {string} key
+ *
+ * @returns {boolean}
+ */
+ has: function(key) {
+ return this.data.has(key);
+ },
+
+ /**
+ * Return an array of keys in the map
+ *
+ * @returns {Array}
+ */
+ keys: function() {
+ var list = [];
+
+ if (this.size > 0) {
+ // .forEach() is used because IE11 doesn't support .keys()
+ this.data.forEach(function(_, key) {
+ list.push(key);
+ });
+ }
+
+ return list;
+ },
+
+ /**
+ * Return an array of values in the map
+ *
+ * @returns {Array}
+ */
+ values: function() {
+ var list = [];
+
+ if (this.size > 0) {
+ // .forEach() is used because IE11 doesn't support .values()
+ this.data.forEach(function(value) {
+ list.push(value['value']);
+ });
+ }
+
+ return list;
+ },
+
+ /**
+ * Return this map as simple object
+ *
+ * @returns {object}
+ */
+ toObject: function() {
+ var obj = {};
+
+ if (this.size > 0) {
+ this.data.forEach(function (value, key) {
+ obj[key] = value;
+ });
+ }
+
+ return obj;
+ }
+ };
+
+}(Icinga));
diff --git a/public/js/icinga/timer.js b/public/js/icinga/timer.js
new file mode 100644
index 0000000..0fea4d9
--- /dev/null
+++ b/public/js/icinga/timer.js
@@ -0,0 +1,176 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.Timer
+ *
+ * Timer events are triggered once a second. Runs all reegistered callback
+ * functions and is able to preserve a desired scope.
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Timer = function (icinga) {
+
+ /**
+ * We keep a reference to the Icinga instance even if we don't need it
+ */
+ this.icinga = icinga;
+
+ /**
+ * The Interval object
+ */
+ this.ticker = null;
+
+ /**
+ * Fixed default interval is 250ms
+ */
+ this.interval = 250;
+
+ /**
+ * Our registerd observers
+ */
+ this.observers = [];
+
+ /**
+ * Counter
+ */
+ this.stepCounter = 0;
+
+ this.start = (new Date()).getTime();
+
+
+ this.lastRuntime = [];
+
+ this.isRunning = false;
+ };
+
+ Icinga.Timer.prototype = {
+
+ /**
+ * The initialization function starts our ticker
+ */
+ initialize: function () {
+ this.isRunning = true;
+
+ var _this = this;
+ var f = function () {
+ if (_this.isRunning) {
+ _this.tick();
+ setTimeout(f, _this.interval);
+ }
+ };
+ f();
+ },
+
+ /**
+ * We will trigger our tick function once a second. It will call each
+ * registered observer.
+ */
+ tick: function () {
+
+ var icinga = this.icinga;
+
+ $.each(this.observers, function (idx, observer) {
+ if (observer.isDue()) {
+ observer.run();
+ } else {
+ // Not due
+ }
+ });
+ icinga = null;
+ },
+
+ /**
+ * Register a given callback function to be run within an optional scope.
+ */
+ register: function (callback, scope, interval) {
+
+ var observer;
+
+ try {
+
+ if (typeof scope === 'undefined') {
+ observer = new Icinga.Timer.Interval(callback, interval);
+ } else {
+ observer = new Icinga.Timer.Interval(
+ callback.bind(scope),
+ interval
+ );
+ }
+
+ this.observers.push(observer);
+
+ } catch(err) {
+ this.icinga.logger.error(err);
+ }
+
+ return observer;
+ },
+
+ unregister: function (observer) {
+
+ var idx = $.inArray(observer, this.observers);
+ if (idx > -1) {
+ this.observers.splice(idx, 1);
+ }
+
+ return this;
+ },
+
+ /**
+ * Our destroy function will clean up everything. Unused right now.
+ */
+ destroy: function () {
+ this.isRunning = false;
+
+ this.icinga = null;
+ $.each(this.observers, function (idx, observer) {
+ observer.destroy();
+ });
+
+ this.observers = [];
+ }
+ };
+
+ Icinga.Timer.Interval = function (callback, interval) {
+
+ if ('undefined' === typeof interval) {
+ throw 'Timer interval is required';
+ }
+
+ if (interval < 100) {
+ throw 'Timer interval cannot be less than 100ms, got ' + interval;
+ }
+
+ this.lastRun = (new Date()).getTime();
+
+ this.interval = interval;
+
+ this.scheduledNextRun = this.lastRun + interval;
+
+ this.callback = callback;
+ };
+
+ Icinga.Timer.Interval.prototype = {
+
+ isDue: function () {
+ return this.scheduledNextRun < (new Date()).getTime();
+ },
+
+ run: function () {
+ this.lastRun = (new Date()).getTime();
+
+ while (this.scheduledNextRun < this.lastRun) {
+ this.scheduledNextRun += this.interval;
+ }
+
+ this.callback();
+ },
+
+ destroy: function () {
+ this.callback = null;
+ }
+ };
+
+}(Icinga, jQuery));
diff --git a/public/js/icinga/timezone.js b/public/js/icinga/timezone.js
new file mode 100644
index 0000000..1c2647b
--- /dev/null
+++ b/public/js/icinga/timezone.js
@@ -0,0 +1,105 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+(function(Icinga, $) {
+
+ 'use strict';
+
+ /**
+ * Get the maximum timezone offset
+ *
+ * @returns {Number}
+ */
+ Date.prototype.getStdTimezoneOffset = function() {
+ var year = new Date().getFullYear();
+ var offsetInJanuary = new Date(year, 0, 2).getTimezoneOffset();
+ var offsetInJune = new Date(year, 5, 2).getTimezoneOffset();
+
+ return Math.max(offsetInJanuary, offsetInJune);
+ };
+
+ /**
+ * Test for daylight saving time zone
+ *
+ * @returns {boolean}
+ */
+ Date.prototype.isDst = function() {
+ return this.getStdTimezoneOffset() !== this.getTimezoneOffset();
+ };
+
+ /**
+ * Write timezone information into a cookie
+ *
+ * @constructor
+ */
+ Icinga.Timezone = function() {
+ this.cookieName = 'icingaweb2-tzo';
+ };
+
+ Icinga.Timezone.prototype = {
+ /**
+ * Initialize interface method
+ */
+ initialize: function () {
+ this.writeTimezone();
+ },
+
+ destroy: function() {
+ // PASS
+ },
+
+ /**
+ * Write timezone information into cookie
+ */
+ writeTimezone: function() {
+ var date = new Date();
+ var timezoneOffset = (date.getTimezoneOffset()*60) * -1;
+ var dst = date.isDst();
+
+ if (this.readCookie(this.cookieName)) {
+ return;
+ }
+
+ this.writeCookie(this.cookieName, timezoneOffset + '-' + Number(dst), 1);
+ },
+
+ /**
+ * Write cookie data
+ *
+ * @param {String} name
+ * @param {String} value
+ * @param {Number} days
+ */
+ writeCookie: function(name, value, days) {
+ var expires = '';
+
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime()+(days*24*60*60*1000));
+ var expires = '; expires=' + date.toGMTString();
+ }
+ document.cookie = name + '=' + value + expires + '; path=/';
+ },
+
+ /**
+ * Read cookie data
+ *
+ * @param {String} name
+ * @returns {*}
+ */
+ readCookie: function(name) {
+ var nameEq = name + '=';
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') {
+ c = c.substring(1,c.length);
+ }
+ if (c.indexOf(nameEq) == 0) {
+ return c.substring(nameEq.length,c.length);
+ }
+ }
+ return null;
+ }
+ };
+
+})(Icinga, jQuery);
diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js
new file mode 100644
index 0000000..b41de06
--- /dev/null
+++ b/public/js/icinga/ui.js
@@ -0,0 +1,634 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga.UI
+ *
+ * Our user interface
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.UI = function (icinga) {
+
+ this.icinga = icinga;
+
+ this.currentLayout = 'default';
+
+ this.debug = false;
+
+ this.debugTimer = null;
+
+ this.timeCounterTimer = null;
+
+ // detect currentLayout
+ var classList = $('#layout').attr('class').split(/\s+/);
+ var _this = this;
+ var matched;
+ $.each(classList, function(index, item) {
+ if (null !== (matched = item.match(/^([a-z]+)-layout$/))) {
+ var layout = matched[1];
+ if (layout !== 'fullscreen') {
+ _this.currentLayout = layout;
+ // Break loop
+ return false;
+ }
+ }
+ });
+ };
+
+ Icinga.UI.prototype = {
+
+ initialize: function () {
+ $('html').removeClass('no-js').addClass('js');
+ this.enableTimeCounters();
+ this.triggerWindowResize();
+ this.fadeNotificationsAway();
+
+ $(document).on('click', '#mobile-menu-toggle', this.toggleMobileMenu);
+ $(document).on('keypress', '#search',{ self: this, type: 'key' }, this.closeMobileMenu);
+ $(document).on('mouseleave', '#sidebar', { self: this, type: 'leave' }, this.closeMobileMenu);
+ $(document).on('click', '#sidebar a', { self: this, type: 'navigate' }, this.closeMobileMenu);
+ },
+
+ fadeNotificationsAway: function() {
+ var icinga = this.icinga;
+ $('#notifications li')
+ .not('.fading-out')
+ .not('.persist')
+ .addClass('fading-out')
+ .delay(7000)
+ .fadeOut('slow',
+ function() {
+ $(this).remove();
+ });
+ },
+
+ toggleDebug: function() {
+ if (this.debug) {
+ return this.disableDebug();
+ } else {
+ return this.enableDebug();
+ }
+ },
+
+ enableDebug: function () {
+ if (this.debug === true) { return this; }
+ this.debug = true;
+ this.debugTimer = this.icinga.timer.register(
+ this.refreshDebug,
+ this,
+ 1000
+ );
+ this.fixDebugVisibility();
+
+ return this;
+ },
+
+ fixDebugVisibility: function () {
+ if (this.debug) {
+ $('#responsive-debug').css({display: 'block'});
+ } else {
+ $('#responsive-debug').css({display: 'none'});
+ }
+ return this;
+ },
+
+ disableDebug: function () {
+ if (this.debug === false) { return; }
+
+ this.debug = false;
+ this.icinga.timer.unregister(this.debugTimer);
+ this.debugTimer = null;
+ this.fixDebugVisibility();
+ return this;
+ },
+
+ reloadCss: function () {
+ var icinga = this.icinga;
+ icinga.logger.info('Reloading CSS');
+ $('link').each(function() {
+ var $oldLink = $(this);
+ if ($oldLink.hasAttr('type') && $oldLink.attr('type').indexOf('css') > -1) {
+ var $newLink = $oldLink.clone().attr(
+ 'href',
+ icinga.utils.addUrlParams(
+ $oldLink.attr('href'),
+ { id: new Date().getTime() } // Only required for Firefox to reload CSS automatically
+ )
+ ).on('load', function() {
+ $oldLink.remove();
+ $('head').trigger('css-reloaded');
+ });
+
+ $newLink.appendTo($('head'));
+ }
+ });
+ },
+
+ enableTimeCounters: function () {
+ this.timeCounterTimer = this.icinga.timer.register(
+ this.refreshTimeSince,
+ this,
+ 1000
+ );
+ return this;
+ },
+
+ disableTimeCounters: function () {
+ this.icinga.timer.unregister(this.timeCounterTimer);
+ this.timeCounterTimer = null;
+ return this;
+ },
+
+ /**
+ * Focus the given element and scroll to its position
+ *
+ * @param {string} element The name or id of the element to focus
+ * @param {object} [$container] The container containing the element
+ * @param {boolean} [scroll] Whether the viewport should be scrolled to the focused element
+ */
+ focusElement: function(element, $container, scroll) {
+ var $element = element;
+
+ if (typeof scroll === 'undefined') {
+ scroll = true;
+ }
+
+ if (typeof element === 'string') {
+ if ($container && $container.length) {
+ $element = $container.find('#' + element);
+ } else {
+ $element = $('#' + element);
+ }
+
+ if (! $element.length) {
+ // The name attribute is actually deprecated, on anchor tags,
+ // but we'll possibly handle links from another source
+ // (module etc) so that's used as a fallback
+ if ($container && $container.length) {
+ $element = $container.find('[name="' + element.replace(/'/, '\\\'') + '"]');
+ } else {
+ $element = $('[name="' + element.replace(/'/, '\\\'') + '"]');
+ }
+ }
+ }
+
+ if ($element.length) {
+ if (! this.isFocusable($element)) {
+ $element.attr('tabindex', -1);
+ }
+
+ $element[0].focus();
+
+ if (scroll && $container && $container.length) {
+ if (! $container.is('.container')) {
+ $container = $container.closest('.container');
+ }
+
+ if ($container.css('display') === 'flex' && $container.is('.container')) {
+ var $controls = $container.find('.controls');
+ var $content = $container.find('.content');
+ $content.scrollTop($element.offsetTopRelativeTo($content) - $controls.outerHeight() - (
+ $element.outerHeight(true) - $element.innerHeight()
+ ));
+ } else {
+ $container.scrollTop($element.first().position().top);
+ }
+ }
+ }
+ },
+
+ isFocusable: function ($element) {
+ return $element.is('*[tabindex], a[href], input:not([disabled]), button:not([disabled])' +
+ ', select:not([disabled]), textarea:not([disabled]), iframe, area[href], object' +
+ ', embed, *[contenteditable]');
+ },
+
+ moveToLeft: function () {
+ var col2 = this.cutContainer($('#col2'));
+ var kill = this.cutContainer($('#col1'));
+ this.pasteContainer($('#col1'), col2);
+ this.icinga.behaviors.navigation.trySetActiveAndSelectedByUrl($('#col1').data('icingaUrl'));
+ },
+
+ moveToRight: function () {
+ let col1 = document.getElementById('col1'),
+ col2 = document.getElementById('col2'),
+ col1Backup = this.cutContainer($(col1));
+
+ this.cutContainer($(col2)); // Clear col2 states
+ this.pasteContainer($(col2), col1Backup);
+ this.layout2col();
+ },
+
+ cutContainer: function ($col) {
+ var props = {
+ 'elements': $('#' + $col.attr('id') + ' > *').detach(),
+ 'data': {
+ 'data-icinga-url': $col.data('icingaUrl'),
+ 'data-icinga-title': $col.data('icingaTitle'),
+ 'data-icinga-refresh': $col.data('icingaRefresh'),
+ 'data-last-update': $col.data('lastUpdate'),
+ 'data-icinga-module': $col.data('icingaModule'),
+ 'data-icinga-container-id': $col[0].dataset.icingaContainerId
+ },
+ 'class': $col.attr('class')
+ };
+ this.icinga.loader.stopPendingRequestsFor($col);
+ $col.removeData('icingaUrl');
+ $col.removeData('icingaTitle');
+ $col.removeData('icingaRefresh');
+ $col.removeData('lastUpdate');
+ $col.removeData('icingaModule');
+ delete $col[0].dataset.icingaContainerId;
+ $col.removeAttr('class').attr('class', 'container');
+ return props;
+ },
+
+ pasteContainer: function ($col, backup) {
+ backup['elements'].appendTo($col);
+ $col.attr('class', backup['class']); // TODO: ie memleak? remove first?
+ $col.data('icingaUrl', backup['data']['data-icinga-url']);
+ $col.data('icingaTitle', backup['data']['data-icinga-title']);
+ $col.data('icingaRefresh', backup['data']['data-icinga-refresh']);
+ $col.data('lastUpdate', backup['data']['data-last-update']);
+ $col.data('icingaModule', backup['data']['data-icinga-module']);
+ $col[0].dataset.icingaContainerId = backup['data']['data-icinga-container-id'];
+ },
+
+ triggerWindowResize: function () {
+ this.onWindowResize({data: {self: this}});
+ },
+
+ /**
+ * Our window got resized, let's fix our UI
+ */
+ onWindowResize: function (event) {
+ var _this = event.data.self;
+
+ if (_this.layoutHasBeenChanged()) {
+ _this.icinga.logger.info(
+ 'Layout change detected, switching to',
+ _this.currentLayout
+ );
+ }
+
+ _this.refreshDebug();
+ },
+
+ /**
+ * Returns whether the layout is too small for more than one column
+ *
+ * @returns {boolean} True when more than one column is available
+ */
+ hasOnlyOneColumn: function () {
+ return this.currentLayout === 'poor' || this.currentLayout === 'minimal';
+ },
+
+ layoutHasBeenChanged: function () {
+
+ var layout = $('html').css('fontFamily').replace(/['",]/g, '');
+ var matched;
+
+ if (null !== (matched = layout.match(/^([a-z]+)-layout$/))) {
+ if (matched[1] === this.currentLayout &&
+ $('#layout').hasClass(layout)
+ ) {
+ return false;
+ } else {
+ $('#layout').removeClass(this.currentLayout + '-layout').addClass(layout);
+ this.currentLayout = matched[1];
+ if (this.currentLayout === 'poor' || this.currentLayout === 'minimal') {
+ this.layout1col();
+ } else if (this.icinga.initialized) {
+ // layout1col() also triggers this, that's why an else is required
+ $('#layout').trigger('layout-change');
+ }
+ return true;
+ }
+ }
+ this.icinga.logger.error(
+ 'Someone messed up our responsiveness hacks, html font-family is',
+ layout
+ );
+ return false;
+ },
+
+ /**
+ * Returns whether only one column is displayed
+ *
+ * @returns {boolean} True when only one column is displayed
+ */
+ isOneColLayout: function () {
+ return ! $('#layout').hasClass('twocols');
+ },
+
+ layout1col: function () {
+ if (this.isOneColLayout()) { return; }
+ this.icinga.logger.debug('Switching to single col');
+ $('#layout').removeClass('twocols');
+ this.closeContainer($('#col2'));
+
+ if (this.icinga.initialized) {
+ $('#layout').trigger('layout-change');
+ }
+
+ // one-column layouts never have any selection active
+ $('#col1').removeData('icinga-actiontable-former-href');
+ this.icinga.behaviors.actiontable.clearAll();
+ },
+
+ closeContainer: function($c) {
+ $c.removeData('icingaUrl');
+ $c.removeData('icingaTitle');
+ $c.removeData('icingaRefresh');
+ $c.removeData('lastUpdate');
+ $c.removeData('icingaModule');
+ delete $c[0].dataset.icingaContainerId;
+ $c.removeAttr('class').attr('class', 'container');
+ this.icinga.loader.stopPendingRequestsFor($c);
+ $c.trigger('close-column');
+ $c.html('');
+ },
+
+ layout2col: function () {
+ if (! this.isOneColLayout()) { return; }
+ this.icinga.logger.debug('Switching to double col');
+ $('#layout').addClass('twocols');
+
+ if (this.icinga.initialized) {
+ $('#layout').trigger('layout-change');
+ }
+ },
+
+ prepareColumnFor: function ($el, $target) {
+ var explicitTarget;
+
+ if ($target.attr('id') === 'col2') {
+ if ($el.closest('#col2').length) {
+ explicitTarget = $el.closest('[data-base-target]').data('baseTarget');
+ if (typeof explicitTarget !== 'undefined' && explicitTarget === '_next') {
+ this.moveToLeft();
+ }
+ } else {
+ this.layout2col();
+ }
+ } else { // if ($target.attr('id') === 'col1')
+ explicitTarget = $el.closest('[data-base-target]').data('baseTarget');
+ if (typeof explicitTarget !== 'undefined' && explicitTarget === '_main') {
+ this.layout1col();
+ }
+ }
+ },
+
+ getAvailableColumnSpace: function () {
+ return $('#main').width() / this.getDefaultFontSize();
+ },
+
+ setColumnCount: function (count) {
+ if (count === 3) {
+ $('#main > .container').css({
+ width: '33.33333%'
+ });
+ } else if (count === 2) {
+ $('#main > .container').css({
+ width: '50%'
+ });
+ } else {
+ $('#main > .container').css({
+ width: '100%'
+ });
+ }
+ },
+
+ setTitle: function (title) {
+ document.title = title;
+ return this;
+ },
+
+ getColumnCount: function () {
+ return $('#main > .container').length;
+ },
+
+ /**
+ * Assign a unique ID to each .container without such
+ *
+ * This usually applies to dashlets
+ */
+ assignUniqueContainerIds: function() {
+ var currentMax = 0;
+ $('.container').each(function() {
+ var $el = $(this);
+ var m;
+ if (!$el.attr('id')) {
+ return;
+ }
+ if (m = $el.attr('id').match(/^ciu_(\d+)$/)) {
+ if (parseInt(m[1]) > currentMax) {
+ currentMax = parseInt(m[1]);
+ }
+ }
+ });
+ $('.container').each(function() {
+ var $el = $(this);
+ if (!!$el.attr('id')) {
+ return;
+ }
+ currentMax++;
+ $el.attr('id', 'ciu_' + currentMax);
+ });
+ },
+
+ refreshDebug: function () {
+ if (! this.debug) {
+ return;
+ }
+
+ var size = this.getDefaultFontSize().toString();
+ var winWidth = $( window ).width();
+ var winHeight = $( window ).height();
+ var loading = '';
+
+ $.each(this.icinga.loader.requests, function (el, req) {
+ if (loading === '') {
+ loading = '<br />Loading:<br />';
+ }
+ loading += el + ' => ' + encodeURI(req.url);
+ });
+
+ $('#responsive-debug').html(
+ ' Time: ' +
+ this.icinga.utils.formatHHiiss(new Date()) +
+ '<br /> 1em: ' +
+ size +
+ 'px<br /> Win: ' +
+ winWidth +
+ 'x'+
+ winHeight +
+ 'px<br />' +
+ ' Layout: ' +
+ this.currentLayout +
+ loading
+ );
+ },
+
+ /**
+ * Refresh partial time counters
+ *
+ * This function runs every second.
+ */
+ refreshTimeSince: function () {
+ $('.time-ago, .time-since').each(function (idx, el) {
+ var partialTime = /(\d{1,2})m (\d{1,2})s/.exec(el.innerHTML);
+ if (partialTime !== null) {
+ var minute = parseInt(partialTime[1], 10),
+ second = parseInt(partialTime[2], 10);
+ if (second < 59) {
+ ++second;
+ } else {
+ ++minute;
+ second = 0;
+ }
+ el.innerHTML = el.innerHTML.substr(0, partialTime.index) + minute.toString() + 'm '
+ + second.toString() + 's' + el.innerHTML.substr(partialTime.index + partialTime[0].length);
+ }
+ });
+
+ $('.time-until').each(function (idx, el) {
+ var partialTime = /(-?)(\d{1,2})m (\d{1,2})s/.exec(el.innerHTML);
+ if (partialTime !== null) {
+ var minute = parseInt(partialTime[2], 10),
+ second = parseInt(partialTime[3], 10),
+ invert = partialTime[1];
+ if (invert.length) {
+ // Count up because partial time is negative
+ if (second < 59) {
+ ++second;
+ } else {
+ ++minute;
+ second = 0;
+ }
+ } else {
+ // Count down because partial time is positive
+ if (second === 0) {
+ if (minute === 0) {
+ // Invert counter
+ minute = 0;
+ second = 1;
+ invert = '-';
+ } else {
+ --minute;
+ second = 59;
+ }
+ } else {
+ --second;
+ }
+ }
+ el.innerHTML = el.innerHTML.substr(0, partialTime.index) + invert + minute.toString() + 'm '
+ + second.toString() + 's' + el.innerHTML.substr(partialTime.index + partialTime[0].length);
+ }
+ });
+ },
+
+ createFontSizeCalculator: function () {
+ var $el = $('<div id="fontsize-calc">&nbsp;</div>');
+ $('#layout').append($el);
+ return $el;
+ },
+
+ getDefaultFontSize: function () {
+ var $calc = $('#fontsize-calc');
+ if (! $calc.length) {
+ $calc = this.createFontSizeCalculator();
+ }
+ return $calc.width() / 1000;
+ },
+
+ /**
+ * Toggle mobile menu
+ *
+ * @param {object} e Event
+ */
+ toggleMobileMenu: function(e) {
+ $('#sidebar').toggleClass('expanded');
+ },
+
+ /**
+ * Close mobile menu when the enter key is pressed during search or the user leaves the sidebar
+ *
+ * @param {object} e Event
+ */
+ closeMobileMenu: function(e) {
+ if (e.data.self.currentLayout !== 'minimal') {
+ return;
+ }
+
+ if (e.data.type === 'key') {
+ if (e.which === 13) {
+ $('#sidebar').removeClass('expanded');
+ $(e.target)[0].blur();
+ }
+ } else {
+ $('#sidebar').removeClass('expanded');
+ }
+ },
+
+ toggleFullscreen: function () {
+ $('#layout').toggleClass('fullscreen-layout');
+ },
+
+ getUniqueContainerId: function (container) {
+ if (typeof container.jquery !== 'undefined') {
+ if (! container.length) {
+ return null;
+ }
+
+ container = container[0];
+ } else if (typeof container === 'undefined') {
+ return null;
+ }
+
+ var containerId = container.dataset.icingaContainerId || null;
+ if (containerId === null) {
+ /**
+ * Only generate an id if it's not for col1 or the menu (which are using the non-suffixed window id).
+ * This is based on the assumption that the server only knows about the menu and first column
+ * and therefore does not need to protect its ids. (As the menu is most likely part of the sidebar)
+ */
+ var col1 = document.getElementById('col1');
+ if (container.id !== 'menu' && col1 !== null && ! col1.contains(container)) {
+ containerId = this.icinga.utils.generateId(6); // Random because the content may move
+ container.dataset.icingaContainerId = containerId;
+ }
+ }
+
+ return containerId;
+ },
+
+ getWindowId: function () {
+ if (! this.hasWindowId()) {
+ return undefined;
+ }
+ return window.name.match(/^Icinga-([a-zA-Z0-9]+)$/)[1];
+ },
+
+ hasWindowId: function () {
+ var res = window.name.match(/^Icinga-([a-zA-Z0-9]+)$/);
+ return typeof res === 'object' && null !== res;
+ },
+
+ setWindowId: function (id) {
+ this.icinga.logger.debug('Setting new window id', id);
+ window.name = 'Icinga-' + id;
+ },
+
+ destroy: function () {
+ // This is gonna be hard, clean up the mess
+ this.icinga = null;
+ this.debugTimer = null;
+ this.timeCounterTimer = null;
+ }
+ };
+
+}(Icinga, jQuery));
diff --git a/public/js/icinga/utils.js b/public/js/icinga/utils.js
new file mode 100644
index 0000000..280e4f6
--- /dev/null
+++ b/public/js/icinga/utils.js
@@ -0,0 +1,582 @@
+/*! Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+/**
+ * Icinga utility functions
+ */
+(function(Icinga, $) {
+
+ 'use strict';
+
+ Icinga.Utils = function (icinga) {
+
+ /**
+ * Utility functions may need access to their Icinga instance
+ */
+ this.icinga = icinga;
+
+ /**
+ * We will use this to create an URL helper only once
+ */
+ this.urlHelper = null;
+ };
+
+ Icinga.Utils.prototype = {
+
+ timeWithMs: function (now) {
+
+ if (typeof now === 'undefined') {
+ now = new Date();
+ }
+
+ var ms = now.getMilliseconds() + '';
+ while (ms.length < 3) {
+ ms = '0' + ms;
+ }
+
+ return now.toLocaleTimeString() + '.' + ms;
+ },
+
+ timeShort: function (now) {
+
+ if (typeof now === 'undefined') {
+ now = new Date();
+ }
+
+ return now.toLocaleTimeString().replace(/:\d{2}$/, '');
+ },
+
+ formatHHiiss: function (date) {
+ var hours = date.getHours();
+ var minutes = date.getMinutes();
+ var seconds = date.getSeconds();
+ if (hours < 10) hours = '0' + hours;
+ if (minutes < 10) minutes = '0' + minutes;
+ if (seconds < 10) seconds = '0' + seconds;
+ return hours + ':' + minutes + ':' + seconds;
+ },
+
+ /**
+ * Format the given byte-value into a human-readable string
+ *
+ * @param {number} The amount of bytes to format
+ * @returns {string} The formatted string
+ */
+ formatBytes: function (bytes) {
+ var log2 = Math.log(bytes) / Math.LN2;
+ var pot = Math.floor(log2 / 10);
+ var unit = (['b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'])[pot];
+ return ((bytes / Math.pow(1024, pot)).toFixed(2)) + ' ' + unit;
+ },
+
+ /**
+ * Return whether the given element is visible in the users view
+ *
+ * Borrowed from: http://stackoverflow.com/q/487073
+ *
+ * @param {selector} element The element to check
+ * @returns {Boolean}
+ */
+ isVisible: function(element) {
+ var $element = $(element);
+ if (!$element.length) {
+ return false;
+ }
+
+ var docViewTop = $(window).scrollTop();
+ var docViewBottom = docViewTop + $(window).height();
+ var elemTop = $element.offset().top;
+ var elemBottom = elemTop + $element.height();
+
+ return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) &&
+ (elemBottom <= docViewBottom) && (elemTop >= docViewTop));
+ },
+
+ getUrlHelper: function () {
+ if (this.urlHelper === null) {
+ this.urlHelper = document.createElement('a');
+ }
+
+ return this.urlHelper;
+ },
+
+ /**
+ * Parse a given Url and return an object
+ */
+ parseUrl: function (url) {
+
+ var a = this.getUrlHelper();
+ a.href = url;
+
+ var result = {
+ source : url,
+ protocol: a.protocol.replace(':', ''),
+ host : a.hostname,
+ port : a.port,
+ query : a.search,
+ file : (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
+ hash : a.hash.replace('#',''),
+ path : a.pathname.replace(/^([^\/])/,'/$1'),
+ relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
+ segments: a.pathname.replace(/^\//,'').split('/'),
+ params : this.parseParams(a)
+ };
+ a = null;
+
+ return result;
+ },
+
+ // Local URLs only
+ addUrlParams: function (url, params) {
+ var parts = this.parseUrl(url),
+ result = parts.path,
+ newparams = parts.params;
+
+ // We overwrite existing params
+ $.each(params, function (key, value) {
+ key = encodeURIComponent(key);
+ value = typeof value !== 'string' || !! value ? encodeURIComponent(value) : null;
+
+ var found = false;
+ for (var i = 0; i < newparams.length; i++) {
+ if (newparams[i].key === key) {
+ newparams[i].value = value;
+ found = true;
+ break;
+ }
+ }
+
+ if (! found) {
+ newparams.push({ key: key, value: value });
+ }
+ });
+
+ if (newparams.length) {
+ result += '?' + this.buildQuery(newparams);
+ }
+
+ if (parts.hash.length) {
+ result += '#' + parts.hash;
+ }
+
+ return result;
+ },
+
+ // Local URLs only
+ removeUrlParams: function (url, params) {
+ var parts = this.parseUrl(url),
+ result = parts.path,
+ newparams = parts.params;
+
+ $.each(params, function (_, key) {
+ key = encodeURIComponent(key);
+
+ for (var i = 0; i < newparams.length; i++) {
+ if (newparams[i].key === key) {
+ newparams.splice(i, 1);
+ return;
+ }
+ }
+ });
+
+ if (newparams.length) {
+ result += '?' + this.buildQuery(newparams);
+ }
+
+ if (parts.hash.length) {
+ result += '#' + parts.hash;
+ }
+
+ return result;
+ },
+
+ /**
+ * Return a query string for the given params
+ *
+ * @param {Array} params
+ * @return {string}
+ */
+ buildQuery: function (params) {
+ var query = '';
+
+ for (var i = 0; i < params.length; i++) {
+ if (!! query) {
+ query += '&';
+ }
+
+ query += params[i].key;
+ switch (params[i].value) {
+ case true:
+ break;
+ case false:
+ query += '=0';
+ break;
+ case null:
+ query += '=';
+ break;
+ default:
+ query += '=' + params[i].value;
+ }
+ }
+
+ return query;
+ },
+
+ /**
+ * Parse url params
+ */
+ parseParams: function (a) {
+ var params = [],
+ segment = a.search.replace(/^\?/,'').split('&'),
+ len = segment.length,
+ i = 0,
+ key,
+ value,
+ equalPos;
+
+ for (; i < len; i++) {
+ if (! segment[i]) {
+ continue;
+ }
+
+ equalPos = segment[i].indexOf('=');
+ if (equalPos !== -1) {
+ key = segment[i].slice(0, equalPos);
+ value = segment[i].slice(equalPos + 1);
+ } else {
+ key = segment[i];
+ value = true;
+ }
+
+ params.push({ key: key, value: value });
+ }
+
+ return params;
+ },
+
+ /**
+ * Add the specified flag to the given URL
+ *
+ * @param {string} url
+ * @param {string} flag
+ *
+ * @returns {string}
+ */
+ addUrlFlag: function (url, flag) {
+ var pos = url.search(/#(?!!)/);
+
+ if (url.indexOf('?') !== -1) {
+ flag = '&' + flag;
+ } else {
+ flag = '?' + flag;
+ }
+
+ if (pos === -1) {
+ return url + flag;
+ }
+
+ return url.slice(0, pos) + flag + url.slice(pos);
+ },
+
+ /**
+ * Check whether two HTMLElements overlap
+ *
+ * @param a {HTMLElement}
+ * @param b {HTMLElement}
+ *
+ * @returns {Boolean} whether elements overlap, will return false when one
+ * element is not in the DOM
+ */
+ elementsOverlap: function(a, b)
+ {
+ // a bounds
+ var aoff = $(a).offset();
+ if (!aoff) {
+ return false;
+ }
+ var at = aoff.top;
+ var ah = a.offsetHeight || (a.getBBox && a.getBBox().height);
+ var al = aoff.left;
+ var aw = a.offsetWidth || (a.getBBox && a.getBBox().width);
+
+ // b bounds
+ var boff = $(b).offset();
+ if (!boff) {
+ return false;
+ }
+ var bt = boff.top;
+ var bh = b.offsetHeight || (b.getBBox && b.getBBox().height);
+ var bl = boff.left;
+ var bw = b.offsetWidth || (b.getBBox && b.getBBox().width);
+
+ return !(at > (bt + bh) || bt > (at + ah)) && !(bl > (al + aw) || al > (bl + bw));
+ },
+
+ /**
+ * Create a selector that can be used to fetch the element the same position in the DOM-Tree
+ *
+ * Create the path to the given element in the DOM-Tree, comparable to an X-Path. Climb the
+ * DOM tree upwards until an element with an unique ID is found, this id is used as the anchor,
+ * all other elements will be addressed by their position in the parent.
+ *
+ * @param {HTMLElement} el The element to extract the path for.
+ *
+ * @returns {Array} The path of the element, that can be passed to getElementByPath
+ */
+ getDomPath: function (el) {
+ if (! el) {
+ return [];
+ }
+ if (el.id !== '') {
+ return ['#' + el.id];
+ }
+ if (el === document.body) {
+ return ['body'];
+ }
+
+ var siblings = el.parentNode.childNodes;
+ var index = 0;
+ for (var i = 0; i < siblings.length; i ++) {
+ if (siblings[i].nodeType === 1) {
+ index ++;
+ }
+
+ if (siblings[i] === el) {
+ var p = this.getDomPath(el.parentNode);
+ p.push(':nth-child(' + (index) + ')');
+ return p;
+ }
+ }
+ },
+
+ /**
+ * Get the CSS selector to the given node
+ *
+ * @param {HTMLElement} element
+ *
+ * @returns {string}
+ */
+ getCSSPath: function(element) {
+ if (typeof element === 'undefined') {
+ throw 'Requires a element';
+ }
+
+ if (typeof element.jquery !== 'undefined') {
+ if (! element.length) {
+ throw 'Requires a element';
+ }
+
+ element = element[0];
+ }
+
+ var path = [];
+
+ while (true) {
+ let id = element.id;
+ if (typeof id !== 'undefined' && typeof id !== 'string') {
+ // Sometimes there may be a form element with the name "id"
+ id = element.getAttribute("id");
+ }
+
+ if (!! id) {
+ // Only use ids if they're truly unique
+ let results = document.querySelectorAll('* #' + this.escapeCSSSelector(id));
+ if (results.length === 1) {
+ path.push('#' + id);
+ break;
+ }
+ }
+
+ var tagName = element.tagName;
+ var parent = element.parentElement;
+
+ if (! parent) {
+ path.push(tagName.toLowerCase());
+ break;
+ }
+
+ if (parent.children.length) {
+ var index = 0;
+ do {
+ if (element.tagName === tagName) {
+ index++;
+ }
+ } while ((element = element.previousElementSibling));
+
+ path.push(tagName.toLowerCase() + ':nth-of-type(' + index + ')');
+ } else {
+ path.push(tagName.toLowerCase());
+ }
+
+ element = parent;
+ }
+
+ return path.reverse().join(' > ');
+ },
+
+ /**
+ * Escape the given string to be used in a CSS selector
+ *
+ * @param {string} selector
+ * @returns {string}
+ */
+ escapeCSSSelector: function (selector) {
+ if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
+ return CSS.escape(selector);
+ }
+
+ return selector.replaceAll(/^(\d)/, '\\\\3$1 ');
+ },
+
+ /**
+ * Climbs up the given dom path and returns the element
+ *
+ * This is the counterpart
+ *
+ * @param path {Array} The selector
+ * @returns {HTMLElement} The corresponding element
+ */
+ getElementByDomPath: function (path) {
+ var $element;
+ $.each(path, function (i, selector) {
+ if (! $element) {
+ $element = $(selector);
+ } else {
+ $element = $element.children(selector).first();
+ if (! $element[0]) {
+ return false;
+ }
+ }
+ });
+ return $element[0];
+ },
+
+ objectKeys: Object.keys || function (obj) {
+ var keys = [];
+ $.each(obj, function (key) {
+ keys.push(key);
+ });
+ return keys;
+ },
+
+ objectsEqual: function equals(obj1, obj2) {
+ var obj1Keys = Object.keys(obj1);
+ var obj2Keys = Object.keys(obj2);
+ if (obj1Keys.length !== obj2Keys.length) {
+ return false;
+ }
+
+ return obj1Keys.concat(obj2Keys)
+ .every(function (key) {
+ return obj1[key] === obj2[key];
+ });
+ },
+
+ arraysEqual: function (array1, array2) {
+ if (array1.length !== array2.length) {
+ return false;
+ }
+
+ var value1, value2;
+ for (var i = 0; i < array1.length; i++) {
+ value1 = array1[i];
+ value2 = array2[i];
+
+ if (typeof value1 === 'object') {
+ if (typeof value2 !== 'object' || ! this.objectsEqual(value1, value2)) {
+ return false;
+ }
+ } else if (value1 !== value2) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Cleanup
+ */
+ destroy: function () {
+ this.urlHelper = null;
+ this.icinga = null;
+ },
+
+ /**
+ * Encode the parenthesis too
+ *
+ * @param str {String} A component of a URI
+ *
+ * @returns {String} Encoded component
+ */
+ fixedEncodeURIComponent: function (str) {
+ return encodeURIComponent(str).replace(/[()]/g, function(c) {
+ return '%' + c.charCodeAt(0).toString(16);
+ });
+ },
+
+ escape: function (str) {
+ return String(str).replace(
+ /[&<>"']/gm,
+ function (c) {
+ return {
+ '&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ '"': '&quot;',
+ "'": '&#039;'
+ }[c];
+ }
+ );
+ },
+
+ /**
+ * Pad a string with another one
+ *
+ * @param {String} str the string to pad
+ * @param {String} padding the string to use for padding
+ * @param {Number} minLength the minimum length of the result
+ *
+ * @returns {String} the padded string
+ */
+ padString: function(str, padding, minLength) {
+ str = String(str);
+ padding = String(padding);
+ while (str.length < minLength) {
+ str = padding + str;
+ }
+ return str;
+ },
+
+ /**
+ * Shuffle a string
+ *
+ * @param {String} str The string to shuffle
+ *
+ * @returns {String} The shuffled string
+ */
+ shuffleString: function(str) {
+ var a = str.split(""),
+ n = a.length;
+
+ for(var i = n - 1; i > 0; i--) {
+ var j = Math.floor(Math.random() * (i + 1));
+ var tmp = a[i];
+ a[i] = a[j];
+ a[j] = tmp;
+ }
+ return a.join("");
+ },
+
+ /**
+ * Generate an id
+ *
+ * @param {Number} len The desired length of the id
+ *
+ * @returns {String} The id
+ */
+ generateId: function(len) {
+ return this.shuffleString('abcefghijklmnopqrstuvwxyz').substr(0, len);
+ }
+ };
+
+}(Icinga, jQuery));
diff --git a/schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql b/schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql
new file mode 100644
index 0000000..e9d4f86
--- /dev/null
+++ b/schema/mysql-upgrades/2.0.0beta3-2.0.0rc1.sql
@@ -0,0 +1,26 @@
+# Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+
+
+DROP TABLE `icingaweb_group_membership`;
+DROP TABLE `icingaweb_group`;
+
+CREATE TABLE `icingaweb_group`(
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
+ `parent` int(10) unsigned NULL DEFAULT NULL,
+ `ctime` timestamp NULL DEFAULT NULL,
+ `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `idx_name` (`name`),
+ CONSTRAINT `fk_icingaweb_group_parent_id` FOREIGN KEY (`parent`)
+ REFERENCES `icingaweb_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE `icingaweb_group_membership`(
+ `group_id` int(10) unsigned NOT NULL,
+ `username` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
+ `ctime` timestamp NULL DEFAULT NULL,
+ `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`group_id`,`username`),
+ CONSTRAINT `fk_icingaweb_group_membership_icingaweb_group` FOREIGN KEY (`group_id`)
+ REFERENCES `icingaweb_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/schema/mysql-upgrades/2.11.0.sql b/schema/mysql-upgrades/2.11.0.sql
new file mode 100644
index 0000000..99f18cf
--- /dev/null
+++ b/schema/mysql-upgrades/2.11.0.sql
@@ -0,0 +1,33 @@
+ALTER TABLE `icingaweb_group` ROW_FORMAT=DYNAMIC;
+ALTER TABLE `icingaweb_group_membership` ROW_FORMAT=DYNAMIC;
+ALTER TABLE `icingaweb_user` ROW_FORMAT=DYNAMIC;
+ALTER TABLE `icingaweb_user_preference` ROW_FORMAT=DYNAMIC;
+ALTER TABLE `icingaweb_rememberme` ROW_FORMAT=DYNAMIC;
+
+ALTER TABLE `icingaweb_group` CONVERT TO CHARACTER SET utf8mb4;
+ALTER TABLE `icingaweb_group_membership` CONVERT TO CHARACTER SET utf8mb4;
+ALTER TABLE `icingaweb_user` CONVERT TO CHARACTER SET utf8mb4;
+ALTER TABLE `icingaweb_user_preference` CONVERT TO CHARACTER SET utf8mb4;
+
+ALTER TABLE `icingaweb_group`
+ MODIFY COLUMN `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL;
+ALTER TABLE `icingaweb_group_membership`
+ MODIFY COLUMN `username` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL;
+ALTER TABLE `icingaweb_user`
+ MODIFY COLUMN `name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL;
+
+ALTER TABLE `icingaweb_user_preference`
+ MODIFY COLUMN `username` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
+ MODIFY COLUMN `section` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
+ MODIFY COLUMN `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL;
+
+CREATE TABLE icingaweb_schema (
+ id int unsigned NOT NULL AUTO_INCREMENT,
+ version smallint unsigned NOT NULL,
+ timestamp int unsigned NOT NULL,
+
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
+
+INSERT INTO icingaweb_schema (version, timestamp)
+ VALUES (6, UNIX_TIMESTAMP());
diff --git a/schema/mysql-upgrades/2.5.0.sql b/schema/mysql-upgrades/2.5.0.sql
new file mode 100644
index 0000000..08a05c0
--- /dev/null
+++ b/schema/mysql-upgrades/2.5.0.sql
@@ -0,0 +1,5 @@
+# Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+
+
+ALTER TABLE `icingaweb_group_membership` MODIFY COLUMN `username` varchar(254) COLLATE utf8_unicode_ci NOT NULL;
+ALTER TABLE `icingaweb_user` MODIFY COLUMN `name` varchar(254) COLLATE utf8_unicode_ci NOT NULL;
+ALTER TABLE `icingaweb_user_preference` MODIFY COLUMN `username` varchar(254) COLLATE utf8_unicode_ci NOT NULL;
diff --git a/schema/mysql-upgrades/2.9.0.sql b/schema/mysql-upgrades/2.9.0.sql
new file mode 100644
index 0000000..7ed7ad6
--- /dev/null
+++ b/schema/mysql-upgrades/2.9.0.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `icingaweb_rememberme`(
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
+ username varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
+ passphrase varchar(256) NOT NULL,
+ random_iv varchar(24) NOT NULL,
+ http_user_agent text NOT NULL,
+ expires_at timestamp NULL DEFAULT NULL,
+ ctime timestamp NULL DEFAULT NULL,
+ mtime timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
diff --git a/schema/mysql-upgrades/2.9.1.sql b/schema/mysql-upgrades/2.9.1.sql
new file mode 100644
index 0000000..a7af6ce
--- /dev/null
+++ b/schema/mysql-upgrades/2.9.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `icingaweb_rememberme`
+ MODIFY random_iv varchar(32) NOT NULL;
diff --git a/schema/mysql.schema.sql b/schema/mysql.schema.sql
new file mode 100644
index 0000000..1600618
--- /dev/null
+++ b/schema/mysql.schema.sql
@@ -0,0 +1,65 @@
+# Icinga Web 2 | (c) 2014 Icinga GmbH | GPLv2+
+
+CREATE TABLE `icingaweb_group`(
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `parent` int(10) unsigned NULL DEFAULT NULL,
+ `ctime` timestamp NULL DEFAULT NULL,
+ `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `idx_name` (`name`),
+ CONSTRAINT `fk_icingaweb_group_parent_id` FOREIGN KEY (`parent`)
+ REFERENCES `icingaweb_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `icingaweb_group_membership`(
+ `group_id` int(10) unsigned NOT NULL,
+ `username` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `ctime` timestamp NULL DEFAULT NULL,
+ `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`group_id`,`username`),
+ CONSTRAINT `fk_icingaweb_group_membership_icingaweb_group` FOREIGN KEY (`group_id`)
+ REFERENCES `icingaweb_group` (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `icingaweb_user`(
+ `name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `active` tinyint(1) NOT NULL,
+ `password_hash` varbinary(255) NOT NULL,
+ `ctime` timestamp NULL DEFAULT NULL,
+ `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `icingaweb_user_preference`(
+ `username` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `section` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `name` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
+ `value` varchar(255) NOT NULL,
+ `ctime` timestamp NULL DEFAULT NULL,
+ `mtime` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`username`,`section`,`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `icingaweb_rememberme`(
+ id int(10) unsigned NOT NULL AUTO_INCREMENT,
+ username varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
+ passphrase varchar(256) NOT NULL,
+ random_iv varchar(32) NOT NULL,
+ http_user_agent text NOT NULL,
+ expires_at timestamp NULL DEFAULT NULL,
+ ctime timestamp NULL DEFAULT NULL,
+ mtime timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE icingaweb_schema (
+ id int unsigned NOT NULL AUTO_INCREMENT,
+ version smallint unsigned NOT NULL,
+ timestamp int unsigned NOT NULL,
+
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;
+
+INSERT INTO icingaweb_schema (version, timestamp)
+ VALUES (6, UNIX_TIMESTAMP());
diff --git a/schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql b/schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql
new file mode 100644
index 0000000..7b5b575
--- /dev/null
+++ b/schema/pgsql-upgrades/2.0.0beta3-2.0.0rc1.sql
@@ -0,0 +1,60 @@
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+DROP TABLE "icingaweb_group_membership";
+DROP TABLE "icingaweb_group";
+
+CREATE OR REPLACE FUNCTION unix_timestamp(timestamp with time zone) RETURNS bigint AS '
+ SELECT EXTRACT(EPOCH FROM $1)::bigint AS result
+' LANGUAGE sql;
+
+CREATE TABLE "icingaweb_group" (
+ "id" serial,
+ "name" character varying(64) NOT NULL,
+ "parent" int NULL DEFAULT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_group"
+ ADD CONSTRAINT pk_icingaweb_group
+ PRIMARY KEY (
+ "id"
+);
+
+CREATE UNIQUE INDEX idx_icingaweb_group
+ ON "icingaweb_group"
+ USING btree (
+ lower((name)::text)
+);
+
+ALTER TABLE ONLY "icingaweb_group"
+ ADD CONSTRAINT fk_icingaweb_group_parent_id
+ FOREIGN KEY (
+ "parent"
+ )
+ REFERENCES "icingaweb_group" (
+ "id"
+);
+
+CREATE TABLE "icingaweb_group_membership" (
+ "group_id" int NOT NULL,
+ "username" character varying(64) NOT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_group_membership"
+ ADD CONSTRAINT pk_icingaweb_group_membership
+ FOREIGN KEY (
+ "group_id"
+ )
+ REFERENCES "icingaweb_group" (
+ "id"
+);
+
+CREATE UNIQUE INDEX idx_icingaweb_group_membership
+ ON "icingaweb_group_membership"
+ USING btree (
+ group_id,
+ lower((username)::text)
+);
diff --git a/schema/pgsql-upgrades/2.11.0.sql b/schema/pgsql-upgrades/2.11.0.sql
new file mode 100644
index 0000000..9e57ce7
--- /dev/null
+++ b/schema/pgsql-upgrades/2.11.0.sql
@@ -0,0 +1,10 @@
+CREATE TABLE "icingaweb_schema" (
+ "id" serial,
+ "version" smallint NOT NULL,
+ "timestamp" int NOT NULL,
+
+ CONSTRAINT pk_icingaweb_schema PRIMARY KEY ("id")
+);
+
+INSERT INTO icingaweb_schema ("version", "timestamp")
+ VALUES (6, extract(epoch from now()));
diff --git a/schema/pgsql-upgrades/2.5.0.sql b/schema/pgsql-upgrades/2.5.0.sql
new file mode 100644
index 0000000..8139281
--- /dev/null
+++ b/schema/pgsql-upgrades/2.5.0.sql
@@ -0,0 +1,5 @@
+/* Icinga Web 2 | (c) 2017 Icinga Development Team | GPLv2+ */
+
+ALTER TABLE "icingaweb_group_membership" ALTER COLUMN "username" TYPE character varying(254);
+ALTER TABLE "icingaweb_user" ALTER COLUMN "name" TYPE character varying(254);
+ALTER TABLE "icingaweb_user_preference" ALTER COLUMN "username" TYPE character varying(254);
diff --git a/schema/pgsql-upgrades/2.9.0.sql b/schema/pgsql-upgrades/2.9.0.sql
new file mode 100644
index 0000000..c017b91
--- /dev/null
+++ b/schema/pgsql-upgrades/2.9.0.sql
@@ -0,0 +1,16 @@
+CREATE TABLE "icingaweb_rememberme" (
+ "id" serial,
+ "username" character varying(254) NOT NULL,
+ "passphrase" character varying(256) NOT NULL,
+ "random_iv" character varying(24) NOT NULL,
+ "http_user_agent" text NOT NULL,
+ "expires_at" timestamp NULL DEFAULT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_rememberme"
+ ADD CONSTRAINT pk_icingaweb_rememberme
+ PRIMARY KEY (
+ "id"
+);
diff --git a/schema/pgsql-upgrades/2.9.1.sql b/schema/pgsql-upgrades/2.9.1.sql
new file mode 100644
index 0000000..02c683f
--- /dev/null
+++ b/schema/pgsql-upgrades/2.9.1.sql
@@ -0,0 +1,2 @@
+ALTER TABLE ONLY "icingaweb_rememberme"
+ ALTER COLUMN random_iv type character varying(32);
diff --git a/schema/pgsql.schema.sql b/schema/pgsql.schema.sql
new file mode 100644
index 0000000..8bf4ca0
--- /dev/null
+++ b/schema/pgsql.schema.sql
@@ -0,0 +1,130 @@
+/* Icinga Web 2 | (c) 2014 Icinga GmbH | GPLv2+ */
+
+CREATE OR REPLACE FUNCTION unix_timestamp(timestamp with time zone) RETURNS bigint AS '
+ SELECT EXTRACT(EPOCH FROM $1)::bigint AS result
+' LANGUAGE sql;
+
+CREATE TABLE "icingaweb_group" (
+ "id" serial,
+ "name" character varying(64) NOT NULL,
+ "parent" int NULL DEFAULT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_group"
+ ADD CONSTRAINT pk_icingaweb_group
+ PRIMARY KEY (
+ "id"
+);
+
+CREATE UNIQUE INDEX idx_icingaweb_group
+ ON "icingaweb_group"
+ USING btree (
+ lower((name)::text)
+);
+
+ALTER TABLE ONLY "icingaweb_group"
+ ADD CONSTRAINT fk_icingaweb_group_parent_id
+ FOREIGN KEY (
+ "parent"
+ )
+ REFERENCES "icingaweb_group" (
+ "id"
+);
+
+CREATE TABLE "icingaweb_group_membership" (
+ "group_id" int NOT NULL,
+ "username" character varying(254) NOT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_group_membership"
+ ADD CONSTRAINT pk_icingaweb_group_membership
+ FOREIGN KEY (
+ "group_id"
+ )
+ REFERENCES "icingaweb_group" (
+ "id"
+);
+
+CREATE UNIQUE INDEX idx_icingaweb_group_membership
+ ON "icingaweb_group_membership"
+ USING btree (
+ group_id,
+ lower((username)::text)
+);
+
+CREATE TABLE "icingaweb_user" (
+ "name" character varying(254) NOT NULL,
+ "active" smallint NOT NULL,
+ "password_hash" bytea NOT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_user"
+ ADD CONSTRAINT pk_icingaweb_user
+ PRIMARY KEY (
+ "name"
+);
+
+CREATE UNIQUE INDEX idx_icingaweb_user
+ ON "icingaweb_user"
+ USING btree (
+ lower((name)::text)
+);
+
+CREATE TABLE "icingaweb_user_preference" (
+ "username" character varying(254) NOT NULL,
+ "name" character varying(64) NOT NULL,
+ "section" character varying(64) NOT NULL,
+ "value" character varying(255) NOT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_user_preference"
+ ADD CONSTRAINT pk_icingaweb_user_preference
+ PRIMARY KEY (
+ "username",
+ "section",
+ "name"
+);
+
+CREATE UNIQUE INDEX idx_icingaweb_user_preference
+ ON "icingaweb_user_preference"
+ USING btree (
+ lower((username)::text),
+ lower((section)::text),
+ lower((name)::text)
+);
+
+CREATE TABLE "icingaweb_rememberme" (
+ "id" serial,
+ "username" character varying(254) NOT NULL,
+ "passphrase" character varying(256) NOT NULL,
+ "random_iv" character varying(32) NOT NULL,
+ "http_user_agent" text NOT NULL,
+ "expires_at" timestamp NULL DEFAULT NULL,
+ "ctime" timestamp NULL DEFAULT NULL,
+ "mtime" timestamp NULL DEFAULT NULL
+);
+
+ALTER TABLE ONLY "icingaweb_rememberme"
+ ADD CONSTRAINT pk_icingaweb_rememberme
+ PRIMARY KEY (
+ "id"
+);
+
+CREATE TABLE "icingaweb_schema" (
+ "id" serial,
+ "version" smallint NOT NULL,
+ "timestamp" int NOT NULL,
+
+ CONSTRAINT pk_icingaweb_schema PRIMARY KEY ("id")
+);
+
+INSERT INTO icingaweb_schema (version, timestamp)
+ VALUES (6, extract(epoch from now()));